summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGPL662
-rw-r--r--CREDITS1
-rw-r--r--ChangeLog35737
-rw-r--r--FS/Changes5
-rw-r--r--FS/FS.pm494
-rw-r--r--FS/FS/AccessRight.pm380
-rw-r--r--FS/FS/CGI.pm327
-rw-r--r--FS/FS/ClientAPI.pm42
-rw-r--r--FS/FS/ClientAPI/Agent.pm125
-rw-r--r--FS/FS/ClientAPI/Bulk.pm384
-rw-r--r--FS/FS/ClientAPI/MasonComponent.pm131
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm1736
-rw-r--r--FS/FS/ClientAPI/PrepaidPhone.pm253
-rw-r--r--FS/FS/ClientAPI/SGNG.pm277
-rw-r--r--FS/FS/ClientAPI/Signup.pm800
-rw-r--r--FS/FS/ClientAPI/passwd.pm46
-rw-r--r--FS/FS/ClientAPI_SessionCache.pm79
-rw-r--r--FS/FS/Conf.pm3651
-rw-r--r--FS/FS/ConfDefaults.pm86
-rw-r--r--FS/FS/ConfItem.pm63
-rw-r--r--FS/FS/Conf_compat17.pm2520
-rw-r--r--FS/FS/Cron/alert_expiration.pm177
-rw-r--r--FS/FS/Cron/backup.pm43
-rw-r--r--FS/FS/Cron/bill.pm236
-rw-r--r--FS/FS/Cron/breakage.pm84
-rw-r--r--FS/FS/Cron/check.pm200
-rw-r--r--FS/FS/Cron/expire_user_pref.pm20
-rw-r--r--FS/FS/Cron/notify.pm149
-rw-r--r--FS/FS/Cron/upload.pm176
-rw-r--r--FS/FS/Cron/vacuum.pm23
-rw-r--r--FS/FS/CurrentUser.pm67
-rw-r--r--FS/FS/Daemon.pm100
-rw-r--r--FS/FS/InitHandler.pm91
-rw-r--r--FS/FS/Mason.pm507
-rw-r--r--FS/FS/Mason/Request.pm89
-rw-r--r--FS/FS/Misc.pm807
-rw-r--r--FS/FS/Misc/DateTime.pm64
-rw-r--r--FS/FS/Misc/eps2png.pm278
-rw-r--r--FS/FS/Misc/prune.pm131
-rw-r--r--FS/FS/Msgcat.pm100
-rw-r--r--FS/FS/Pony.pm23
-rw-r--r--FS/FS/Record.pm3043
-rw-r--r--FS/FS/Report.pm46
-rw-r--r--FS/FS/Report/FCC_477.pm90
-rw-r--r--FS/FS/Report/Table.pm27
-rw-r--r--FS/FS/Report/Table/Monthly.pm590
-rw-r--r--FS/FS/Schema.pm2878
-rw-r--r--FS/FS/SearchCache.pm96
-rw-r--r--FS/FS/Setup.pm552
-rw-r--r--FS/FS/TicketSystem.pm30
-rw-r--r--FS/FS/TicketSystem/RT_External.pm401
-rw-r--r--FS/FS/TicketSystem/RT_Internal.pm314
-rw-r--r--FS/FS/TicketSystem/RT_Libs.pm10
-rw-r--r--FS/FS/Tron.pm123
-rw-r--r--FS/FS/UI/Web.pm640
-rw-r--r--FS/FS/UI/Web/small_custview.pm129
-rw-r--r--FS/FS/UI/bytecount.pm101
-rw-r--r--FS/FS/UID.pm405
-rw-r--r--FS/FS/Upgrade.pm276
-rw-r--r--FS/FS/XMLRPC.pm166
-rw-r--r--FS/FS/Yori.pm94
-rw-r--r--FS/FS/access_group.pm162
-rw-r--r--FS/FS/access_groupagent.pm146
-rw-r--r--FS/FS/access_right.pm163
-rw-r--r--FS/FS/access_user.pm518
-rw-r--r--FS/FS/access_user_pref.pm129
-rw-r--r--FS/FS/access_usergroup.pm143
-rw-r--r--FS/FS/acct_rt_transaction.pm316
-rw-r--r--FS/FS/acct_snarf.pm128
-rwxr-xr-xFS/FS/addr_block.pm385
-rw-r--r--FS/FS/agent.pm579
-rw-r--r--FS/FS/agent_payment_gateway.pm139
-rw-r--r--FS/FS/agent_type.pm191
-rw-r--r--FS/FS/banned_pay.pm140
-rw-r--r--FS/FS/category_Common.pm87
-rw-r--r--FS/FS/cdr.pm894
-rw-r--r--FS/FS/cdr/asterisk.pm45
-rw-r--r--FS/FS/cdr/bell_west.pm122
-rw-r--r--FS/FS/cdr/broadsoft.pm108
-rw-r--r--FS/FS/cdr/genband.pm120
-rw-r--r--FS/FS/cdr/genband_meetme.pm17
-rw-r--r--FS/FS/cdr/indosoft.pm71
-rw-r--r--FS/FS/cdr/netcentrex.pm783
-rw-r--r--FS/FS/cdr/nextone.pm26
-rw-r--r--FS/FS/cdr/openser.pm24
-rw-r--r--FS/FS/cdr/sansay.pm408
-rw-r--r--FS/FS/cdr/simple.pm52
-rw-r--r--FS/FS/cdr/simple2.pm51
-rw-r--r--FS/FS/cdr/taqua.pm190
-rw-r--r--FS/FS/cdr/transnexus.pm66
-rw-r--r--FS/FS/cdr/troop.pm128
-rw-r--r--FS/FS/cdr/unitel.pm39
-rw-r--r--FS/FS/cdr/vitelity.pm25
-rw-r--r--FS/FS/cdr/wip.pm49
-rw-r--r--FS/FS/cdr_batch.pm128
-rw-r--r--FS/FS/cdr_calltype.pm115
-rw-r--r--FS/FS/cdr_carrier.pm116
-rw-r--r--FS/FS/cdr_termination.pm155
-rw-r--r--FS/FS/cdr_type.pm119
-rw-r--r--FS/FS/cgp_rule.pm320
-rw-r--r--FS/FS/cgp_rule_action.pm141
-rw-r--r--FS/FS/cgp_rule_condition.pm148
-rw-r--r--FS/FS/class_Common.pm143
-rw-r--r--FS/FS/clientapi_session.pm121
-rw-r--r--FS/FS/clientapi_session_field.pm124
-rw-r--r--FS/FS/conf.pm114
-rw-r--r--FS/FS/contact.pm177
-rw-r--r--FS/FS/contact_email.pm128
-rw-r--r--FS/FS/contact_phone.pm143
-rw-r--r--FS/FS/cust_attachment.pm199
-rw-r--r--FS/FS/cust_bill.pm4557
-rw-r--r--FS/FS/cust_bill_ApplicationCommon.pm518
-rw-r--r--FS/FS/cust_bill_event.pm380
-rw-r--r--FS/FS/cust_bill_pay.pm186
-rw-r--r--FS/FS/cust_bill_pay_batch.pm120
-rw-r--r--FS/FS/cust_bill_pay_pkg.pm224
-rw-r--r--FS/FS/cust_bill_pkg.pm873
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm377
-rw-r--r--FS/FS/cust_bill_pkg_discount.pm158
-rw-r--r--FS/FS/cust_bill_pkg_display.pm166
-rw-r--r--FS/FS/cust_bill_pkg_tax_location.pm225
-rw-r--r--FS/FS/cust_bill_pkg_tax_rate_location.pm221
-rw-r--r--FS/FS/cust_category.pm97
-rw-r--r--FS/FS/cust_class.pm120
-rw-r--r--FS/FS/cust_credit.pm625
-rw-r--r--FS/FS/cust_credit_bill.pm170
-rw-r--r--FS/FS/cust_credit_bill_pkg.pm352
-rw-r--r--FS/FS/cust_credit_refund.pm186
-rw-r--r--FS/FS/cust_event.pm511
-rw-r--r--FS/FS/cust_location.pm265
-rw-r--r--FS/FS/cust_main.pm8858
-rw-r--r--FS/FS/cust_main/Import.pm427
-rw-r--r--FS/FS/cust_main_Mixin.pm344
-rw-r--r--FS/FS/cust_main_county.pm503
-rw-r--r--FS/FS/cust_main_exemption.pm128
-rw-r--r--FS/FS/cust_main_invoice.pm188
-rw-r--r--FS/FS/cust_main_note.pm159
-rw-r--r--FS/FS/cust_pay.pm915
-rw-r--r--FS/FS/cust_pay_batch.pm277
-rw-r--r--FS/FS/cust_pay_pending.pm340
-rw-r--r--FS/FS/cust_pay_refund.pm188
-rw-r--r--FS/FS/cust_pay_void.pm260
-rw-r--r--FS/FS/cust_pkg.pm3212
-rw-r--r--FS/FS/cust_pkg_detail.pm140
-rw-r--r--FS/FS/cust_pkg_discount.pm246
-rw-r--r--FS/FS/cust_pkg_option.pm115
-rw-r--r--FS/FS/cust_pkg_reason.pm325
-rw-r--r--FS/FS/cust_recon.pm193
-rw-r--r--FS/FS/cust_refund.pm388
-rw-r--r--FS/FS/cust_statement.pm272
-rw-r--r--FS/FS/cust_svc.pm735
-rw-r--r--FS/FS/cust_svc_option.pm134
-rw-r--r--FS/FS/cust_tax_adjustment.pm149
-rw-r--r--FS/FS/cust_tax_exempt.pm152
-rw-r--r--FS/FS/cust_tax_exempt_pkg.pm152
-rw-r--r--FS/FS/cust_tax_location.pm344
-rw-r--r--FS/FS/discount.pm182
-rw-r--r--FS/FS/domain_record.pm438
-rw-r--r--FS/FS/export_device.pm136
-rw-r--r--FS/FS/export_svc.pm322
-rw-r--r--FS/FS/h_Common.pm124
-rw-r--r--FS/FS/h_cust_bill.pm33
-rw-r--r--FS/FS/h_cust_credit.pm33
-rw-r--r--FS/FS/h_cust_pay.pm33
-rw-r--r--FS/FS/h_cust_pkg.pm34
-rw-r--r--FS/FS/h_cust_pkg_reason.pm34
-rw-r--r--FS/FS/h_cust_svc.pm165
-rw-r--r--FS/FS/h_cust_tax_exempt.pm40
-rw-r--r--FS/FS/h_domain_record.pm33
-rw-r--r--FS/FS/h_svc_acct.pm78
-rw-r--r--FS/FS/h_svc_broadband.pm33
-rw-r--r--FS/FS/h_svc_domain.pm33
-rw-r--r--FS/FS/h_svc_external.pm33
-rw-r--r--FS/FS/h_svc_forward.pm85
-rw-r--r--FS/FS/h_svc_mailinglist.pm33
-rw-r--r--FS/FS/h_svc_pbx.pm33
-rw-r--r--FS/FS/h_svc_phone.pm33
-rw-r--r--FS/FS/h_svc_www.pm67
-rw-r--r--FS/FS/inventory_class.pm264
-rw-r--r--FS/FS/inventory_item.pm182
-rw-r--r--FS/FS/location_Mixin.pm57
-rw-r--r--FS/FS/m2m_Common.pm170
-rw-r--r--FS/FS/m2name_Common.pm177
-rw-r--r--FS/FS/mailinglist.pm173
-rw-r--r--FS/FS/mailinglistmember.pm245
-rw-r--r--FS/FS/msgcat.pm166
-rw-r--r--FS/FS/nas.pm150
-rw-r--r--FS/FS/o2m_Common.pm152
-rw-r--r--FS/FS/option_Common.pm345
-rw-r--r--FS/FS/otaker_Mixin.pm67
-rw-r--r--FS/FS/part_bill_event.pm368
-rw-r--r--FS/FS/part_device.pm148
-rw-r--r--FS/FS/part_event.pm444
-rw-r--r--FS/FS/part_event/Action.pm240
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_pkg.pm63
-rw-r--r--FS/FS/part_event/Action/addpost.pm20
-rw-r--r--FS/FS/part_event/Action/apply.pm24
-rw-r--r--FS/FS/part_event/Action/bill.pm26
-rw-r--r--FS/FS/part_event/Action/cancel.pm30
-rw-r--r--FS/FS/part_event/Action/collect.pm26
-rw-r--r--FS/FS/part_event/Action/cust_bill_batch.pm25
-rw-r--r--FS/FS/part_event/Action/cust_bill_comp.pm28
-rw-r--r--FS/FS/part_event/Action/cust_bill_email.pm23
-rw-r--r--FS/FS/part_event/Action/cust_bill_fee_percent.pm28
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_card.pm28
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_check.pm28
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_lec.pm28
-rw-r--r--FS/FS/part_event/Action/cust_bill_send.pm20
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_agent.pm42
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_alternate.pm31
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm50
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_if_newest.pm38
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_reminder.pm31
-rw-r--r--FS/FS/part_event/Action/cust_bill_spool_csv.pm59
-rw-r--r--FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm42
-rw-r--r--FS/FS/part_event/Action/cust_statement.pm39
-rw-r--r--FS/FS/part_event/Action/cust_statement_send.pm26
-rw-r--r--FS/FS/part_event/Action/fee.pm58
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit.pm39
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit_pkg.pm9
-rw-r--r--FS/FS/part_event/Action/pkg_employee_credit.pm39
-rw-r--r--FS/FS/part_event/Action/pkg_employee_credit_pkg.pm9
-rw-r--r--FS/FS/part_event/Action/pkg_referral_credit.pm62
-rw-r--r--FS/FS/part_event/Action/pkg_referral_credit_pkg.pm9
-rw-r--r--FS/FS/part_event/Action/suspend.pm32
-rw-r--r--FS/FS/part_event/Action/suspend_if_pkgpart.pm40
-rw-r--r--FS/FS/part_event/Action/suspend_unless_pkgpart.pm40
-rw-r--r--FS/FS/part_event/Action/writeoff.pm33
-rw-r--r--FS/FS/part_event/Condition.pm448
-rw-r--r--FS/FS/part_event/Condition/agent.pm37
-rw-r--r--FS/FS/part_event/Condition/agent_type.pm40
-rw-r--r--FS/FS/part_event/Condition/balance.pm48
-rw-r--r--FS/FS/part_event/Condition/balance_age.pm54
-rw-r--r--FS/FS/part_event/Condition/balance_under.pm42
-rw-r--r--FS/FS/part_event/Condition/cust_bill_age.pm46
-rw-r--r--FS/FS/part_event/Condition/cust_bill_has_noauto.pm33
-rw-r--r--FS/FS/part_event/Condition/cust_bill_has_service.pm56
-rw-r--r--FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm33
-rw-r--r--FS/FS/part_event/Condition/cust_bill_owed.pm54
-rw-r--r--FS/FS/part_event/Condition/cust_bill_owed_under.pm49
-rw-r--r--FS/FS/part_event/Condition/cust_pay_batch_declined.pm51
-rw-r--r--FS/FS/part_event/Condition/cust_payments.pm43
-rw-r--r--FS/FS/part_event/Condition/cust_payments_pkg.pm68
-rw-r--r--FS/FS/part_event/Condition/cust_status.pm32
-rw-r--r--FS/FS/part_event/Condition/dundate.pm26
-rw-r--r--FS/FS/part_event/Condition/every.pm67
-rw-r--r--FS/FS/part_event/Condition/has_pkg_class.pm40
-rw-r--r--FS/FS/part_event/Condition/has_pkgpart.pm41
-rw-r--r--FS/FS/part_event/Condition/has_referral_custnum.pm48
-rw-r--r--FS/FS/part_event/Condition/hasnt_pkgpart.pm40
-rw-r--r--FS/FS/part_event/Condition/once.pm55
-rw-r--r--FS/FS/part_event/Condition/once_percust.pm67
-rw-r--r--FS/FS/part_event/Condition/payby.pm50
-rw-r--r--FS/FS/part_event/Condition/pkg_age.pm58
-rw-r--r--FS/FS/part_event/Condition/pkg_class.pm38
-rw-r--r--FS/FS/part_event/Condition/pkg_notchange.pm31
-rw-r--r--FS/FS/part_event/Condition/pkg_pkgpart.pm39
-rw-r--r--FS/FS/part_event/Condition/pkg_recurring.pm31
-rw-r--r--FS/FS/part_event/Condition/pkg_status.pm37
-rw-r--r--FS/FS/part_event/Condition/pkg_unless_pkgpart.pm39
-rw-r--r--FS/FS/part_event_condition.pm354
-rw-r--r--FS/FS/part_event_condition_option.pm151
-rw-r--r--FS/FS/part_event_condition_option_option.pm129
-rw-r--r--FS/FS/part_event_option.pm214
-rw-r--r--FS/FS/part_export.pm490
-rw-r--r--FS/FS/part_export/acct_freeside.pm139
-rw-r--r--FS/FS/part_export/acct_plesk.pm121
-rw-r--r--FS/FS/part_export/acct_sql.pm310
-rw-r--r--FS/FS/part_export/amazon_ec2.pm169
-rw-r--r--FS/FS/part_export/apache.pm47
-rw-r--r--FS/FS/part_export/artera_turbo.pm181
-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.pm994
-rw-r--r--FS/FS/part_export/communigate_pro_singledomain.pm37
-rw-r--r--FS/FS/part_export/cp.pm161
-rw-r--r--FS/FS/part_export/cpanel.pm192
-rw-r--r--FS/FS/part_export/cyrus.pm120
-rw-r--r--FS/FS/part_export/dashcs_e911.pm153
-rw-r--r--FS/FS/part_export/domain_shellcommands.pm165
-rw-r--r--FS/FS/part_export/domain_sql.pm238
-rw-r--r--FS/FS/part_export/domreg_net_dri.pm614
-rw-r--r--FS/FS/part_export/domreg_opensrs.pm613
-rw-r--r--FS/FS/part_export/everyone_net.pm132
-rw-r--r--FS/FS/part_export/forward_shellcommands.pm182
-rw-r--r--FS/FS/part_export/globalpops_voip.pm370
-rw-r--r--FS/FS/part_export/grandstream.pm257
-rw-r--r--FS/FS/part_export/http.pm134
-rw-r--r--FS/FS/part_export/indosoft.pm219
-rw-r--r--FS/FS/part_export/infostreet.pm277
-rw-r--r--FS/FS/part_export/internal_diddb.pm134
-rw-r--r--FS/FS/part_export/ldap.pm294
-rw-r--r--FS/FS/part_export/nas_wrapper.pm311
-rw-r--r--FS/FS/part_export/netsapiens.pm312
-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/phone_shellcommands.pm140
-rw-r--r--FS/FS/part_export/phone_sqlradius.pm158
-rw-r--r--FS/FS/part_export/postfix.pm32
-rw-r--r--FS/FS/part_export/prizm.pm591
-rw-r--r--FS/FS/part_export/radiator.pm167
-rw-r--r--FS/FS/part_export/router.pm375
-rw-r--r--FS/FS/part_export/shellcommands.pm474
-rw-r--r--FS/FS/part_export/shellcommands_withdomain.pm138
-rw-r--r--FS/FS/part_export/snmp.pm256
-rw-r--r--FS/FS/part_export/soma.pm412
-rw-r--r--FS/FS/part_export/sqlmail.pm220
-rw-r--r--FS/FS/part_export/sqlradius.pm814
-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/thirdlane.pm348
-rw-r--r--FS/FS/part_export/trango.pm434
-rw-r--r--FS/FS/part_export/vitelity.pm239
-rw-r--r--FS/FS/part_export/vpopmail.pm254
-rw-r--r--FS/FS/part_export/www_plesk.pm138
-rw-r--r--FS/FS/part_export/www_shellcommands.pm190
-rw-r--r--FS/FS/part_export_option.pm134
-rw-r--r--FS/FS/part_pkg.pm1494
-rw-r--r--FS/FS/part_pkg/agent.pm177
-rw-r--r--FS/FS/part_pkg/base_delayed.pm52
-rw-r--r--FS/FS/part_pkg/base_rate.pm96
-rw-r--r--FS/FS/part_pkg/bulk.pm108
-rw-r--r--FS/FS/part_pkg/cdr_termination.pm212
-rw-r--r--FS/FS/part_pkg/flat.pm312
-rw-r--r--FS/FS/part_pkg/flat_comission.pm69
-rw-r--r--FS/FS/part_pkg/flat_comission_cust.pm67
-rw-r--r--FS/FS/part_pkg/flat_comission_pkg.pm60
-rw-r--r--FS/FS/part_pkg/flat_delayed.pm69
-rw-r--r--FS/FS/part_pkg/flat_introrate.pm60
-rw-r--r--FS/FS/part_pkg/incomplete/billoneday.pm48
-rw-r--r--FS/FS/part_pkg/prepaid.pm54
-rw-r--r--FS/FS/part_pkg/prorate.pm128
-rw-r--r--FS/FS/part_pkg/prorate_delayed.pm67
-rw-r--r--FS/FS/part_pkg/recur_Common.pm61
-rw-r--r--FS/FS/part_pkg/rt_time.pm70
-rw-r--r--FS/FS/part_pkg/sesmon_hour.pm59
-rw-r--r--FS/FS/part_pkg/sesmon_minute.pm58
-rw-r--r--FS/FS/part_pkg/sql_external.pm82
-rw-r--r--FS/FS/part_pkg/sql_generic.pm90
-rw-r--r--FS/FS/part_pkg/sqlradacct_hour.pm173
-rw-r--r--FS/FS/part_pkg/subscription.pm113
-rw-r--r--FS/FS/part_pkg/voip_cdr.pm760
-rw-r--r--FS/FS/part_pkg/voip_inbound.pm374
-rw-r--r--FS/FS/part_pkg/voip_sqlradacct.pm194
-rw-r--r--FS/FS/part_pkg_link.pm163
-rw-r--r--FS/FS/part_pkg_option.pm155
-rw-r--r--FS/FS/part_pkg_report_option.pm125
-rw-r--r--FS/FS/part_pkg_taxclass.pm226
-rw-r--r--FS/FS/part_pkg_taxoverride.pm119
-rw-r--r--FS/FS/part_pkg_taxproduct.pm139
-rw-r--r--FS/FS/part_pkg_taxrate.pm420
-rw-r--r--FS/FS/part_pop_local.pm113
-rw-r--r--FS/FS/part_referral.pm208
-rw-r--r--FS/FS/part_svc.pm870
-rw-r--r--FS/FS/part_svc_column.pm123
-rwxr-xr-xFS/FS/part_svc_router.pm33
-rwxr-xr-xFS/FS/part_virtual_field.pm301
-rw-r--r--FS/FS/pay_batch.pm505
-rw-r--r--FS/FS/pay_batch/BoM.pm73
-rw-r--r--FS/FS/pay_batch/PAP.pm103
-rw-r--r--FS/FS/pay_batch/RBC.pm143
-rw-r--r--FS/FS/pay_batch/ach_spiritone.pm65
-rw-r--r--FS/FS/pay_batch/chase_canada.pm104
-rw-r--r--FS/FS/pay_batch/paymentech.pm117
-rw-r--r--FS/FS/pay_batch/td_canada_trust.pm104
-rw-r--r--FS/FS/payby.pm209
-rw-r--r--FS/FS/payinfo_Mixin.pm290
-rw-r--r--FS/FS/payinfo_transaction_Mixin.pm123
-rw-r--r--FS/FS/payment_gateway.pm247
-rw-r--r--FS/FS/payment_gateway_option.pm126
-rw-r--r--FS/FS/phone_avail.pm186
-rw-r--r--FS/FS/phone_device.pm299
-rw-r--r--FS/FS/phone_type.pm137
-rw-r--r--FS/FS/pkg_category.pm132
-rw-r--r--FS/FS/pkg_class.pm119
-rw-r--r--FS/FS/pkg_referral.pm126
-rw-r--r--FS/FS/pkg_svc.pm160
-rw-r--r--FS/FS/port.pm154
-rw-r--r--FS/FS/prepay_credit.pm203
-rw-r--r--FS/FS/prospect_main.pm292
-rw-r--r--FS/FS/queue.pm512
-rw-r--r--FS/FS/queue_arg.pm120
-rw-r--r--FS/FS/queue_depend.pm121
-rw-r--r--FS/FS/raddb.pm1912
-rw-r--r--FS/FS/radius_usergroup.pm131
-rw-r--r--FS/FS/rate.pm422
-rw-r--r--FS/FS/rate_detail.pm613
-rw-r--r--FS/FS/rate_prefix.pm160
-rw-r--r--FS/FS/rate_region.pm315
-rw-r--r--FS/FS/reason.pm130
-rw-r--r--FS/FS/reason_type.pm209
-rw-r--r--FS/FS/reg_code.pm223
-rw-r--r--FS/FS/reg_code_pkg.pm139
-rw-r--r--FS/FS/registrar.pm119
-rwxr-xr-xFS/FS/router.pm152
-rw-r--r--FS/FS/session.pm265
-rw-r--r--FS/FS/svc_CGPRule_Mixin.pm61
-rw-r--r--FS/FS/svc_Common.pm1032
-rw-r--r--FS/FS/svc_Domain_Mixin.pm134
-rw-r--r--FS/FS/svc_External_Common.pm199
-rw-r--r--FS/FS/svc_Parent_Mixin.pm103
-rw-r--r--FS/FS/svc_acct.pm3257
-rw-r--r--FS/FS/svc_acct_pop.pm206
-rwxr-xr-xFS/FS/svc_broadband.pm370
-rw-r--r--FS/FS/svc_domain.pm711
-rw-r--r--FS/FS/svc_external.pm205
-rw-r--r--FS/FS/svc_forward.pm368
-rw-r--r--FS/FS/svc_mailinglist.pm330
-rw-r--r--FS/FS/svc_pbx.pm277
-rw-r--r--FS/FS/svc_phone.pm633
-rw-r--r--FS/FS/svc_www.pm286
-rw-r--r--FS/FS/tax_class.pm392
-rw-r--r--FS/FS/tax_rate.pm2039
-rw-r--r--FS/FS/tax_rate_location.pm317
-rw-r--r--FS/FS/type_pkgs.pm130
-rw-r--r--FS/FS/usage_class.pm413
-rw-r--r--FS/MANIFEST513
-rw-r--r--FS/MANIFEST.SKIP1
-rw-r--r--FS/Makefile.PL10
-rwxr-xr-xFS/bin/freeside-addgroup50
-rw-r--r--FS/bin/freeside-addoutsource32
-rw-r--r--FS/bin/freeside-addoutsourceuser18
-rw-r--r--FS/bin/freeside-adduser119
-rwxr-xr-xFS/bin/freeside-apply-credits21
-rwxr-xr-xFS/bin/freeside-apply_payments_and_credits79
-rwxr-xr-xFS/bin/freeside-cdr-sftp_and_import187
-rw-r--r--FS/bin/freeside-cdrd160
-rw-r--r--FS/bin/freeside-cdrrewrited129
-rw-r--r--FS/bin/freeside-check31
-rwxr-xr-xFS/bin/freeside-count-active-customers17
-rwxr-xr-xFS/bin/freeside-daily135
-rwxr-xr-xFS/bin/freeside-dbdef-create47
-rwxr-xr-xFS/bin/freeside-dedup-cust_bill_pkg_detail-header57
-rwxr-xr-xFS/bin/freeside-delete-addr_blocks31
-rw-r--r--FS/bin/freeside-deloutsource14
-rw-r--r--FS/bin/freeside-deloutsourceuser6
-rw-r--r--FS/bin/freeside-deluser64
-rwxr-xr-xFS/bin/freeside-disable-reasons64
-rwxr-xr-xFS/bin/freeside-email55
-rwxr-xr-xFS/bin/freeside-fetch93
-rwxr-xr-xFS/bin/freeside-history-requeue100
-rwxr-xr-xFS/bin/freeside-init-config45
-rwxr-xr-xFS/bin/freeside-monthly94
-rwxr-xr-xFS/bin/freeside-paymentech-download137
-rwxr-xr-xFS/bin/freeside-paymentech-upload133
-rw-r--r--FS/bin/freeside-prepaidd106
-rwxr-xr-xFS/bin/freeside-prune-applications63
-rw-r--r--FS/bin/freeside-queued298
-rw-r--r--FS/bin/freeside-radgroup76
-rw-r--r--FS/bin/freeside-reexport71
-rwxr-xr-xFS/bin/freeside-reset-fixed69
-rw-r--r--FS/bin/freeside-selfservice-server272
-rw-r--r--FS/bin/freeside-setinvoice42
-rwxr-xr-xFS/bin/freeside-setup165
-rwxr-xr-xFS/bin/freeside-sqlradius-dedup-group82
-rw-r--r--FS/bin/freeside-sqlradius-radacctd145
-rwxr-xr-xFS/bin/freeside-sqlradius-reset118
-rw-r--r--FS/bin/freeside-sqlradius-seconds58
-rwxr-xr-xFS/bin/freeside-sqlradius-set-lastlog102
-rwxr-xr-xFS/bin/freeside-upgrade242
-rwxr-xr-xFS/bin/freeside-void-payments239
-rw-r--r--FS/bin/freeside-yori16
-rw-r--r--FS/t/AccessRight.t5
-rw-r--r--FS/t/CGI.t5
-rw-r--r--FS/t/ClientAPI.t5
-rw-r--r--FS/t/ClientAPI_SessionCache.t5
-rw-r--r--FS/t/Conf.t5
-rw-r--r--FS/t/ConfDefaults.t5
-rw-r--r--FS/t/ConfItem.t5
-rw-r--r--FS/t/Cron-backup.t5
-rw-r--r--FS/t/Cron-bill.t5
-rw-r--r--FS/t/Cron-vacuum.t5
-rw-r--r--FS/t/Daemon.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-FCC_477.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/access_group.t5
-rw-r--r--FS/t/access_groupagent.t5
-rw-r--r--FS/t/access_right.t5
-rw-r--r--FS/t/access_user.t5
-rw-r--r--FS/t/access_user_pref.t5
-rw-r--r--FS/t/access_usergroup.t5
-rw-r--r--FS/t/acct_rt_transaction.t5
-rw-r--r--FS/t/acct_snarf.t5
-rw-r--r--FS/t/addr_block.t5
-rw-r--r--FS/t/agent.t5
-rw-r--r--FS/t/agent_payment_gateway.t5
-rw-r--r--FS/t/agent_type.t5
-rw-r--r--FS/t/banned_pay.t5
-rw-r--r--FS/t/category_Common.t5
-rw-r--r--FS/t/cdr.t5
-rw-r--r--FS/t/cdr_batch.t5
-rw-r--r--FS/t/cdr_calltype.t5
-rw-r--r--FS/t/cdr_carrier.t5
-rw-r--r--FS/t/cdr_termination.t5
-rw-r--r--FS/t/cdr_type.t5
-rw-r--r--FS/t/cgp_rule.t5
-rw-r--r--FS/t/cgp_rule_action.t5
-rw-r--r--FS/t/cgp_rule_condition.t5
-rw-r--r--FS/t/class_Common.t5
-rw-r--r--FS/t/clientapi_session.t5
-rw-r--r--FS/t/clientapi_session_field.t5
-rw-r--r--FS/t/conf.t5
-rw-r--r--FS/t/contact.t5
-rw-r--r--FS/t/contact_email.t5
-rw-r--r--FS/t/contact_phone.t5
-rw-r--r--FS/t/cust_attachment.t5
-rw-r--r--FS/t/cust_bill.t5
-rw-r--r--FS/t/cust_bill_ApplicationCommon.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_pay_batch.t5
-rw-r--r--FS/t/cust_bill_pay_pkg.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_bill_pkg_discount.t5
-rw-r--r--FS/t/cust_bill_pkg_display.t5
-rw-r--r--FS/t/cust_bill_pkg_tax_location.t5
-rw-r--r--FS/t/cust_bill_pkg_tax_rate_location.t5
-rw-r--r--FS/t/cust_category.t5
-rw-r--r--FS/t/cust_class.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_bill_pkg.t5
-rw-r--r--FS/t/cust_credit_refund.t5
-rw-r--r--FS/t/cust_event.t5
-rw-r--r--FS/t/cust_location.t5
-rw-r--r--FS/t/cust_main.t5
-rw-r--r--FS/t/cust_main_Mixin.t5
-rw-r--r--FS/t/cust_main_county.t5
-rw-r--r--FS/t/cust_main_exemption.t5
-rw-r--r--FS/t/cust_main_invoice.t5
-rw-r--r--FS/t/cust_main_note.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_pending.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_pkg_detail.t5
-rw-r--r--FS/t/cust_pkg_discount.t5
-rw-r--r--FS/t/cust_pkg_option.t5
-rw-r--r--FS/t/cust_pkg_reason.t5
-rw-r--r--FS/t/cust_recon.t5
-rw-r--r--FS/t/cust_refund.t5
-rw-r--r--FS/t/cust_statement.t5
-rw-r--r--FS/t/cust_svc.t5
-rw-r--r--FS/t/cust_svc_option.t5
-rw-r--r--FS/t/cust_tax_adjustment.t5
-rw-r--r--FS/t/cust_tax_exempt.t5
-rw-r--r--FS/t/cust_tax_exempt_pkg.t5
-rw-r--r--FS/t/cust_tax_location.t5
-rw-r--r--FS/t/discount.t5
-rw-r--r--FS/t/domain_record.t5
-rw-r--r--FS/t/export_device.t5
-rw-r--r--FS/t/export_svc.t5
-rw-r--r--FS/t/h_Common.t5
-rw-r--r--FS/t/h_cust_bill.t5
-rw-r--r--FS/t/h_cust_credit.t5
-rw-r--r--FS/t/h_cust_pay.t5
-rw-r--r--FS/t/h_cust_pkg.t5
-rw-r--r--FS/t/h_cust_pkg_reason.t5
-rw-r--r--FS/t/h_cust_svc.t5
-rw-r--r--FS/t/h_cust_tax_exempt.t5
-rw-r--r--FS/t/h_domain_record.t5
-rw-r--r--FS/t/h_svc_acct.t5
-rw-r--r--FS/t/h_svc_broadband.t5
-rw-r--r--FS/t/h_svc_domain.t5
-rw-r--r--FS/t/h_svc_external.t5
-rw-r--r--FS/t/h_svc_forward.t5
-rw-r--r--FS/t/h_svc_mailinglist.t5
-rw-r--r--FS/t/h_svc_pbx.t5
-rw-r--r--FS/t/h_svc_www.t5
-rw-r--r--FS/t/inventory_class.t5
-rw-r--r--FS/t/inventory_item.t5
-rw-r--r--FS/t/location_Mixin.t5
-rw-r--r--FS/t/mailinglist.t5
-rw-r--r--FS/t/mailinglistmember.t5
-rw-r--r--FS/t/msgcat.t5
-rw-r--r--FS/t/nas.t5
-rw-r--r--FS/t/option_Common.t5
-rw-r--r--FS/t/part_bill_event.t5
-rw-r--r--FS/t/part_device.t5
-rw-r--r--FS/t/part_event-Action.t5
-rw-r--r--FS/t/part_event-Condition.t5
-rw-r--r--FS/t/part_event.t5
-rw-r--r--FS/t/part_event_condition.t5
-rw-r--r--FS/t/part_event_condition_option.t5
-rw-r--r--FS/t/part_event_condition_option_option.t5
-rw-r--r--FS/t/part_event_option.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-radiator.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-flat.t5
-rw-r--r--FS/t/part_pkg-flat_comission.t5
-rw-r--r--FS/t/part_pkg-flat_comission_cust.t5
-rw-r--r--FS/t/part_pkg-flat_comission_pkg.t5
-rw-r--r--FS/t/part_pkg-flat_delayed.t5
-rw-r--r--FS/t/part_pkg-prorate.t5
-rw-r--r--FS/t/part_pkg-sesmon_hour.t5
-rw-r--r--FS/t/part_pkg-sesmon_minute.t5
-rw-r--r--FS/t/part_pkg-sql_external.t5
-rw-r--r--FS/t/part_pkg-sql_generic.t5
-rw-r--r--FS/t/part_pkg-sqlradacct_hour.t5
-rw-r--r--FS/t/part_pkg-subscription.t5
-rw-r--r--FS/t/part_pkg-voip_cdr.t5
-rw-r--r--FS/t/part_pkg-voip_sqlradacct.t5
-rw-r--r--FS/t/part_pkg.t5
-rw-r--r--FS/t/part_pkg_link.t5
-rw-r--r--FS/t/part_pkg_option.t5
-rw-r--r--FS/t/part_pkg_report_option.t5
-rw-r--r--FS/t/part_pkg_taxclass.t5
-rw-r--r--FS/t/part_pkg_taxoverride.t5
-rw-r--r--FS/t/part_pkg_taxproduct.t5
-rw-r--r--FS/t/part_pkg_taxrate.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/pay_batch.t5
-rw-r--r--FS/t/payby.t5
-rw-r--r--FS/t/payinfo_Mixin.t5
-rw-r--r--FS/t/payment_gateway.t5
-rw-r--r--FS/t/payment_gateway_option.t5
-rw-r--r--FS/t/phone_avail.t5
-rw-r--r--FS/t/phone_device.t5
-rw-r--r--FS/t/phone_type.t5
-rw-r--r--FS/t/pkg_category.t5
-rw-r--r--FS/t/pkg_class.t5
-rw-r--r--FS/t/pkg_referral.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/prospect_main.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/rate.t5
-rw-r--r--FS/t/rate_detail.t5
-rw-r--r--FS/t/rate_prefix.t5
-rw-r--r--FS/t/rate_region.t5
-rw-r--r--FS/t/reason.t5
-rw-r--r--FS/t/reason_type.t5
-rw-r--r--FS/t/reg_code.t5
-rw-r--r--FS/t/reg_code_pkg.t5
-rw-r--r--FS/t/registrar.t5
-rw-r--r--FS/t/router.t5
-rw-r--r--FS/t/session.t5
-rw-r--r--FS/t/svc_CGPRule_Mixin.t5
-rw-r--r--FS/t/svc_Common.t5
-rw-r--r--FS/t/svc_Domain_Mixin.t5
-rw-r--r--FS/t/svc_External_Common.t5
-rw-r--r--FS/t/svc_Parent_Mixin.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_mailinglist.t5
-rw-r--r--FS/t/svc_pbx.t5
-rw-r--r--FS/t/svc_phone.t5
-rw-r--r--FS/t/svc_www.t5
-rw-r--r--FS/t/tax_class.t5
-rw-r--r--FS/t/tax_rate.t5
-rw-r--r--FS/t/tax_rate_location.t5
-rw-r--r--FS/t/type_pkgs.t5
-rw-r--r--FS/t/usage_class.t5
-rw-r--r--INSTALL3
-rw-r--r--Makefile433
-rw-r--r--README36
-rwxr-xr-xbin/add-history-records.pl139
-rwxr-xr-xbin/all-postal-no-email22
-rwxr-xr-xbin/apache.export94
-rw-r--r--bin/artera.import75
-rw-r--r--bin/backup-dvd45
-rwxr-xr-xbin/bill-as-nextmonth5
-rwxr-xr-xbin/bill-as-nextmonth-BILL5
-rwxr-xr-xbin/bill-as-nextyear5
-rwxr-xr-xbin/bill-as-nextyear-BILL5
-rwxr-xr-xbin/bill-for-nextmonth5
-rwxr-xr-xbin/bill-for-nextyear5
-rwxr-xr-xbin/bill-nextmonth5
-rwxr-xr-xbin/bill-nextyear5
-rwxr-xr-xbin/bind.export195
-rwxr-xr-xbin/bind.import235
-rw-r--r--bin/breakdown-bill-applications25
-rwxr-xr-xbin/bsdshell.export114
-rwxr-xr-xbin/build_exten.php790
-rwxr-xr-xbin/cch_tax_tool59
-rwxr-xr-xbin/cdr-netsapiens.import237
-rwxr-xr-xbin/cdr-transnexus.import143
-rwxr-xr-xbin/cdr.http_and_import108
-rw-r--r--bin/cdr.import28
-rwxr-xr-xbin/cdr_calltype.import41
-rwxr-xr-xbin/cdr_upstream_rate.import142
-rwxr-xr-xbin/confdiff27
-rwxr-xr-xbin/countdeclines22
-rw-r--r--bin/create-fetchmailrc47
-rw-r--r--bin/cust_main_special.pm608
-rwxr-xr-xbin/cust_pay_histogram115
-rwxr-xr-xbin/customer-faker124
-rwxr-xr-xbin/cvs2cl2
-rwxr-xr-xbin/del-old-history30
-rw-r--r--bin/drop_slony.slonik9
-rwxr-xr-xbin/expand-country29
-rw-r--r--bin/explain-ar-total.sql976
-rw-r--r--bin/explain-bill-query34
-rwxr-xr-xbin/fetch_and_expand_taxes55
-rw-r--r--bin/find-overapplied27
-rwxr-xr-xbin/fix-sequences69
-rw-r--r--bin/follow-tax-rename52
-rwxr-xr-xbin/freeside-create-initial-data31
-rwxr-xr-xbin/freeside-init60
-rw-r--r--bin/freeside-migrate-events266
-rwxr-xr-xbin/freeside-session-kill103
-rwxr-xr-xbin/freeside-upgrade-unicode72
-rw-r--r--bin/freeside.import146
-rwxr-xr-xbin/fs-migrate-cust_tax_exempt323
-rwxr-xr-xbin/fs-migrate-part_svc41
-rwxr-xr-xbin/fs-migrate-payref31
-rwxr-xr-xbin/fs-migrate-svc_acct_sm227
-rwxr-xr-xbin/fs-radius-add-check68
-rwxr-xr-xbin/fs-radius-add-reply69
-rwxr-xr-xbin/generate-prepay35
-rwxr-xr-xbin/generate-raddb53
-rwxr-xr-xbin/generate-table-module103
-rwxr-xr-xbin/generate-tests21
-rwxr-xr-xbin/h_cust_main-wipe_paycvv30
-rwxr-xr-xbin/import-county-tax-rates30
-rwxr-xr-xbin/import-optigold.pl1077
-rwxr-xr-xbin/import-tax-rates56
-rwxr-xr-xbin/ispman.ldap.import114
-rwxr-xr-xbin/japan.pl32
-rwxr-xr-xbin/make-pkg-fruit172
-rwxr-xr-xbin/mapsecrets2access_user87
-rwxr-xr-xbin/masonize80
-rwxr-xr-xbin/monitor127
-rwxr-xr-xbin/move-customers678
-rwxr-xr-xbin/move-unlinked99
-rwxr-xr-xbin/opensrs_domain_pkgs126
-rwxr-xr-xbin/passwd.import121
-rwxr-xr-xbin/payment-faker54
-rw-r--r--bin/pg-readonly24
-rwxr-xr-xbin/pg-sizer36
-rwxr-xr-xbin/pg-version13
-rwxr-xr-xbin/ping58
-rwxr-xr-xbin/pod2x145
-rwxr-xr-xbin/postfix.export122
-rwxr-xr-xbin/postfix_courierimap.import137
-rwxr-xr-xbin/print-directory_assist12
-rwxr-xr-xbin/print-schema7
-rwxr-xr-xbin/rate-us.import109
-rw-r--r--bin/rate.delete3
-rwxr-xr-xbin/rate.import95
-rwxr-xr-xbin/reassemble_taxes35
-rwxr-xr-xbin/rebill132
-rwxr-xr-xbin/reset-cust_credit-otaker88
-rwxr-xr-xbin/rollback38
-rwxr-xr-xbin/rotate-cdrs38
-rwxr-xr-xbin/rt-drop-tables29
-rw-r--r--bin/rt-update-links36
-rw-r--r--bin/sendmail.import178
-rw-r--r--bin/sequences.reset32
-rwxr-xr-xbin/shadow.reimport125
-rwxr-xr-xbin/slony-setup109
-rwxr-xr-xbin/sqlradius-norealm.reimport113
-rw-r--r--bin/sqlradius.import152
-rwxr-xr-xbin/sqlradius.reimport160
-rwxr-xr-xbin/strip-eps20
-rw-r--r--bin/svc_acct-recalculate_usage110
-rwxr-xr-xbin/svc_acct.import237
-rwxr-xr-xbin/svc_acct_pop.import59
-rwxr-xr-xbin/svc_broadband.renumber84
-rwxr-xr-xbin/svc_domain.erase15
-rwxr-xr-xbin/sysvshell.export112
-rwxr-xr-xbin/tax_rate_location.import48
-rw-r--r--bin/test_scrub48
-rwxr-xr-xbin/tron-scan24
-rw-r--r--conf/agent_defaultpkg (renamed from rt/html/NoAuth/js/scriptaculous/controls.js)0
-rw-r--r--conf/alerter_template18
-rw-r--r--conf/blank_logo.eps22
-rw-r--r--conf/company_address2
-rw-r--r--conf/company_name1
-rw-r--r--conf/cust_pkg-change_svcpart (renamed from rt/html/NoAuth/js/scriptaculous/effects.js)0
-rw-r--r--conf/declinetemplate10
-rw-r--r--conf/home1
-rw-r--r--conf/impending_recur_template20
-rw-r--r--conf/invoice_from1
-rw-r--r--conf/invoice_html254
-rw-r--r--conf/invoice_html_statement124
-rw-r--r--conf/invoice_htmlsummary74
-rw-r--r--conf/invoice_latex358
-rw-r--r--conf/invoice_latex.diff138
-rw-r--r--conf/invoice_latex_statement244
-rw-r--r--conf/invoice_latexcoupon36
-rw-r--r--conf/invoice_latexfooter1
-rw-r--r--conf/invoice_latexnotes8
-rw-r--r--conf/invoice_latexnotes_statement8
-rw-r--r--conf/invoice_latexsmallfooter1
-rw-r--r--conf/invoice_latexsummary45
-rw-r--r--conf/invoice_template26
-rw-r--r--conf/invoice_template_statement26
-rw-r--r--conf/locale1
-rw-r--r--conf/logo.eps13510
-rw-r--r--conf/logo.pngbin0 -> 4887 bytes
-rw-r--r--conf/lpr1
-rw-r--r--conf/maxsearchrecordsperpage1
-rw-r--r--conf/payment_receipt_email26
-rw-r--r--conf/shells5
-rw-r--r--conf/show-msgcat-codes (renamed from rt/html/NoAuth/js/scriptaculous/prototype.js)0
-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--conf/ticket_system1
-rw-r--r--conf/ticket_system-default_queueid1
-rw-r--r--conf/welcome_letter121
-rw-r--r--debian/README.Debian25
-rw-r--r--debian/TODO38
-rw-r--r--debian/changelog26
-rw-r--r--debian/compat1
-rw-r--r--debian/config19
-rw-r--r--debian/control59
-rw-r--r--debian/copyright45
-rw-r--r--debian/cron.d4
-rw-r--r--debian/dbconfig-common.install90
-rw-r--r--debian/dbconfig-common.upgrade3
-rw-r--r--debian/freeside-webui.links4
-rw-r--r--debian/freeside.apache-alias.conf1
-rw-r--r--debian/freeside.default12
-rw-r--r--debian/freeside.docs1
-rw-r--r--debian/init.d.ex157
-rw-r--r--debian/init.d.lsb.ex281
-rw-r--r--debian/postinst54
-rw-r--r--debian/postrm48
-rw-r--r--debian/preinst100
-rw-r--r--debian/prerm46
-rwxr-xr-xdebian/rules230
-rw-r--r--debian/templates (renamed from rt/html/NoAuth/js/scriptaculous/scriptaculous.js)0
-rwxr-xr-xeg/TEMPLATE_cust_main.import196
-rw-r--r--eg/cdr_template.pm101
-rw-r--r--eg/export_template.pm113
-rw-r--r--eg/part_event-Action-template.pm55
-rw-r--r--eg/part_event-Condition-template.pm57
-rw-r--r--eg/table_template-svc.pm212
-rw-r--r--eg/table_template.pm116
-rwxr-xr-xeg/xmlrpc-example.pl23
-rw-r--r--etc/abbr_state.txt72
-rw-r--r--etc/areacodes.txt353
-rw-r--r--etc/countries.txt239
-rw-r--r--etc/domain-template.txt231
-rw-r--r--etc/fslongtable.sty438
-rwxr-xr-xetc/megapop.pl114
-rw-r--r--etc/sql-reserved-words.txt103
-rwxr-xr-xfs_passwd/fs_passwd131
-rwxr-xr-xfs_selfservice/DEPLOY30
-rw-r--r--fs_selfservice/FS-SelfService/Changes6
-rw-r--r--fs_selfservice/FS-SelfService/MANIFEST9
-rw-r--r--fs_selfservice/FS-SelfService/Makefile.PL21
-rw-r--r--fs_selfservice/FS-SelfService/SelfService.pm1832
-rw-r--r--fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm61
-rw-r--r--fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm88
-rw-r--r--fs_selfservice/FS-SelfService/cgi/ach_payment_results.html10
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent.cgi458
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html7
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html17
-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.html33
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_menu.html15
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_provision.html23
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html16
-rw-r--r--fs_selfservice/FS-SelfService/cgi/bill.html15
-rw-r--r--fs_selfservice/FS-SelfService/cgi/card.html47
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/change_bill.html19
-rw-r--r--fs_selfservice/FS-SelfService/cgi/change_password.html46
-rw-r--r--fs_selfservice/FS-SelfService/cgi/change_pay.html69
-rw-r--r--fs_selfservice/FS-SelfService/cgi/change_pkg.html37
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/change_ship.html98
-rw-r--r--fs_selfservice/FS-SelfService/cgi/check.html54
-rw-r--r--fs_selfservice/FS-SelfService/cgi/contact.html135
-rw-r--r--fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi25
-rw-r--r--fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html6
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/customer_order_pkg.html6
-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/decline.html14
-rw-r--r--fs_selfservice/FS-SelfService/cgi/delete_svc.html11
-rw-r--r--fs_selfservice/FS-SelfService/cgi/footer.html3
-rw-r--r--fs_selfservice/FS-SelfService/cgi/header.html62
-rw-r--r--fs_selfservice/FS-SelfService/cgi/iframecontentmws.js59
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/image.cgi20
-rw-r--r--fs_selfservice/FS-SelfService/cgi/images/cross.pngbin0 -> 655 bytes
-rw-r--r--fs_selfservice/FS-SelfService/cgi/images/wait-orange.gifbin0 -> 1849 bytes
-rw-r--r--fs_selfservice/FS-SelfService/cgi/list_customers.html36
-rw-r--r--fs_selfservice/FS-SelfService/cgi/login.html95
-rw-r--r--fs_selfservice/FS-SelfService/cgi/logout.html13
-rw-r--r--fs_selfservice/FS-SelfService/cgi/make_ach_payment.html42
-rw-r--r--fs_selfservice/FS-SelfService/cgi/make_payment.html52
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html35
-rw-r--r--fs_selfservice/FS-SelfService/cgi/map.gifbin0 -> 8181 bytes
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/areacodes.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/counties.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/exchanges.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/part_svc-columns.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/phonenums.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/states.cgi18
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/misc/svc_acct-domains.cgi18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/myaccount.html100
-rw-r--r--fs_selfservice/FS-SelfService/cgi/myaccount_menu.html153
-rw-r--r--fs_selfservice/FS-SelfService/cgi/order_pkg.html47
-rw-r--r--fs_selfservice/FS-SelfService/cgi/overlibmws.js620
-rw-r--r--fs_selfservice/FS-SelfService/cgi/overlibmws_crossframe.js53
-rw-r--r--fs_selfservice/FS-SelfService/cgi/overlibmws_draggable.js85
-rw-r--r--fs_selfservice/FS-SelfService/cgi/overlibmws_iframe.js93
-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.html10
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_change_bill.html4
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_change_password.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_change_pay.html4
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_change_pkg.html4
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_change_ship.html4
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/process_order_pkg.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_order_recharge.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_svc_acct.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_svc_external.html8
-rw-r--r--fs_selfservice/FS-SelfService/cgi/promocode.html14
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision_list.html92
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/recharge_prepay.html30
-rw-r--r--fs_selfservice/FS-SelfService/cgi/recharge_results.html18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/regcode.html14
-rw-r--r--fs_selfservice/FS-SelfService/cgi/selfservice.cgi743
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup-agentselect.html195
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup-alternate.html218
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup-billaddress.html307
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup-freeoption.html262
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup-snarf.html228
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup.cgi487
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup.html435
-rw-r--r--fs_selfservice/FS-SelfService/cgi/stateselect.html134
-rw-r--r--fs_selfservice/FS-SelfService/cgi/success-delayed.html16
-rw-r--r--fs_selfservice/FS-SelfService/cgi/success.html40
-rw-r--r--fs_selfservice/FS-SelfService/cgi/svc_acct.html58
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/verify.cgi177
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_cdr_details.html54
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_customer.html24
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_invoice.html6
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_support_details.html78
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_usage.html85
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_usage_details.html80
-rw-r--r--fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi18
-rw-r--r--fs_selfservice/FS-SelfService/freeside-selfservice-clientd377
-rw-r--r--fs_selfservice/FS-SelfService/freeside-selfservice-soap-server53
-rw-r--r--fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server59
-rw-r--r--fs_selfservice/FS-SelfService/iZoomOnlineProvisionService.pm75
-rwxr-xr-xfs_selfservice/FS-SelfService/ieak.template40
-rw-r--r--fs_selfservice/FS-SelfService/test.pl17
-rw-r--r--fs_selfservice/fri/CHANGE.log271
-rw-r--r--fs_selfservice/fri/LICENSE.txt340
-rw-r--r--fs_selfservice/fri/README.txt123
-rw-r--r--fs_selfservice/fri/includes/ajax.php132
-rw-r--r--fs_selfservice/fri/includes/asi.php156
-rw-r--r--fs_selfservice/fri/includes/bootstrap.php315
-rw-r--r--fs_selfservice/fri/includes/common.php434
-rw-r--r--fs_selfservice/fri/includes/crypt.php81
-rw-r--r--fs_selfservice/fri/includes/database.php72
-rw-r--r--fs_selfservice/fri/includes/display.php222
-rw-r--r--fs_selfservice/fri/includes/freeside.class.php38
-rw-r--r--fs_selfservice/fri/includes/lang.php112
-rw-r--r--fs_selfservice/fri/includes/login.php515
-rw-r--r--fs_selfservice/fri/includes/main.conf.php331
-rw-r--r--fs_selfservice/fri/index.php20
-rw-r--r--fs_selfservice/fri/locale/ari.po590
-rw-r--r--fs_selfservice/fri/locale/ari.utf-8.po590
-rw-r--r--fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.mobin0 -> 4161 bytes
-rw-r--r--fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.po631
-rw-r--r--fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.mobin0 -> 5158 bytes
-rw-r--r--fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.po648
-rw-r--r--fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.mobin0 -> 9562 bytes
-rw-r--r--fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.po616
-rw-r--r--fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.mobin0 -> 6751 bytes
-rw-r--r--fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.po635
-rw-r--r--fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.mobin0 -> 4430 bytes
-rw-r--r--fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.po646
-rw-r--r--fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.mobin0 -> 4051 bytes
-rw-r--r--fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.po645
-rw-r--r--fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.mobin0 -> 16728 bytes
-rw-r--r--fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.po999
-rw-r--r--fs_selfservice/fri/locale/locale.txt37
-rw-r--r--fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.mobin0 -> 2064 bytes
-rw-r--r--fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.po647
-rw-r--r--fs_selfservice/fri/locale/readme.txt37
-rw-r--r--fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.mobin0 -> 7134 bytes
-rw-r--r--fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.po678
-rw-r--r--fs_selfservice/fri/misc/audio.php61
-rw-r--r--fs_selfservice/fri/misc/popup.css10
-rw-r--r--fs_selfservice/fri/misc/recording_popup.php46
-rw-r--r--fs_selfservice/fri/modules.template/blank.module81
-rw-r--r--fs_selfservice/fri/modules/VmX.module661
-rw-r--r--fs_selfservice/fri/modules/billing.module250
-rw-r--r--fs_selfservice/fri/modules/callmonitor.module675
-rw-r--r--fs_selfservice/fri/modules/dashboard.module166
-rw-r--r--fs_selfservice/fri/modules/featurecodes.module152
-rw-r--r--fs_selfservice/fri/modules/followme.module678
-rw-r--r--fs_selfservice/fri/modules/myaccount.module109
-rw-r--r--fs_selfservice/fri/modules/phonefeatures.module342
-rw-r--r--fs_selfservice/fri/modules/settings.module813
-rw-r--r--fs_selfservice/fri/modules/voicemail.module805
-rw-r--r--fs_selfservice/fri/theme/global.css87
-rw-r--r--fs_selfservice/fri/theme/header.css83
-rw-r--r--fs_selfservice/fri/theme/iefixes.css16
-rw-r--r--fs_selfservice/fri/theme/images/arrow-asc.gifbin0 -> 86 bytes
-rw-r--r--fs_selfservice/fri/theme/images/arrow-desc.gifbin0 -> 85 bytes
-rw-r--r--fs_selfservice/fri/theme/layout.css420
-rw-r--r--fs_selfservice/fri/theme/logo.gifbin0 -> 2819 bytes
-rw-r--r--fs_selfservice/fri/theme/main.css13
-rw-r--r--fs_selfservice/fri/theme/navigation.css166
-rw-r--r--fs_selfservice/fri/theme/page.tpl.php78
-rw-r--r--fs_selfservice/fri/theme/spacer.gifbin0 -> 43 bytes
-rw-r--r--fs_selfservice/fri/theme/text.css10
-rw-r--r--fs_selfservice/fri/version.php10
-rwxr-xr-xfs_selfservice/fs_passwd_test19
-rwxr-xr-xfs_selfservice/java/biz/freeside/SelfService.java52
-rwxr-xr-xfs_selfservice/java/freeside_create_ticket_example.java85
-rwxr-xr-xfs_selfservice/java/freeside_login_example.java45
-rwxr-xr-xfs_selfservice/java/freeside_signup_example.java69
-rwxr-xr-xfs_selfservice/perl/xmlrpc-create_ticket.pl41
-rw-r--r--fs_selfservice/php/freeside.class.php34
-rw-r--r--fs_selfservice/php/freeside.login_example.php37
-rw-r--r--fs_selfservice/php/freeside_signup_example.php49
-rw-r--r--fs_selfservice/php/login.php90
-rw-r--r--fs_selfservice/php/main.php39
-rw-r--r--fs_selfservice/php/order_renew.php166
-rw-r--r--fs_selfservice/php/process_login.php38
-rw-r--r--fs_selfservice/php/process_payment_order_renew.php74
-rw-r--r--htetc/freeside-base1.99.conf38
-rw-r--r--htetc/freeside-base1.conf35
-rw-r--r--htetc/freeside-base2.conf38
-rw-r--r--htetc/freeside-rt.conf36
-rw-r--r--htetc/handler.pl111
-rw-r--r--htetc/htpasswd.logout1
-rwxr-xr-xhttemplate/.htaccess3
-rw-r--r--httemplate/autohandler44
-rw-r--r--httemplate/browse/access_group.html106
-rw-r--r--httemplate/browse/access_user.html77
-rw-r--r--httemplate/browse/addr_block.cgi145
-rwxr-xr-xhttemplate/browse/agent.cgi424
-rwxr-xr-xhttemplate/browse/agent_type.cgi63
-rw-r--r--httemplate/browse/cgp_rule.html93
-rwxr-xr-xhttemplate/browse/cust_attachment.html185
-rw-r--r--httemplate/browse/cust_category.html32
-rw-r--r--httemplate/browse/cust_class.html45
-rwxr-xr-xhttemplate/browse/cust_main_county.cgi524
-rw-r--r--httemplate/browse/discount.html28
-rw-r--r--httemplate/browse/elements/browse.html6
-rw-r--r--httemplate/browse/inventory_class.html39
-rw-r--r--httemplate/browse/invoice_template.html124
-rwxr-xr-xhttemplate/browse/msgcat.cgi44
-rwxr-xr-xhttemplate/browse/nas.cgi82
-rwxr-xr-xhttemplate/browse/part_bill_event.cgi122
-rw-r--r--httemplate/browse/part_device.html27
-rw-r--r--httemplate/browse/part_event.html167
-rwxr-xr-xhttemplate/browse/part_export.cgi65
-rwxr-xr-xhttemplate/browse/part_pkg.cgi443
-rw-r--r--httemplate/browse/part_pkg_report_option.html28
-rw-r--r--httemplate/browse/part_pkg_taxclass.html27
-rwxr-xr-xhttemplate/browse/part_pkg_taxproduct.cgi263
-rwxr-xr-xhttemplate/browse/part_referral.html181
-rwxr-xr-xhttemplate/browse/part_svc.cgi228
-rw-r--r--httemplate/browse/part_virtual_field.cgi42
-rw-r--r--httemplate/browse/payment_gateway.html98
-rw-r--r--httemplate/browse/pkg_category.html32
-rw-r--r--httemplate/browse/pkg_class.html46
-rw-r--r--httemplate/browse/rate.cgi64
-rw-r--r--httemplate/browse/rate_detail.html100
-rw-r--r--httemplate/browse/rate_region.html136
-rw-r--r--httemplate/browse/reason.html53
-rw-r--r--httemplate/browse/reason_type.html68
-rw-r--r--httemplate/browse/router.cgi52
-rwxr-xr-xhttemplate/browse/svc_acct_pop.cgi78
-rwxr-xr-xhttemplate/browse/tax_class.html92
-rwxr-xr-xhttemplate/browse/tax_rate.cgi348
-rw-r--r--httemplate/browse/usage_class.html45
-rw-r--r--httemplate/config/config-delete.cgi22
-rw-r--r--httemplate/config/config-download.cgi28
-rw-r--r--httemplate/config/config-image.cgi22
-rw-r--r--httemplate/config/config-process.cgi138
-rw-r--r--httemplate/config/config-view.cgi367
-rw-r--r--httemplate/config/config.cgi348
-rw-r--r--httemplate/docs/AGPL.html672
-rw-r--r--httemplate/docs/about.html53
-rw-r--r--httemplate/docs/ach.html10
-rwxr-xr-xhttemplate/docs/admin.html41
-rw-r--r--httemplate/docs/credits.html177
-rw-r--r--httemplate/docs/cvv2.html24
-rw-r--r--httemplate/docs/ieak.html75
-rw-r--r--httemplate/docs/index.html32
-rwxr-xr-xhttemplate/docs/legacy.html39
-rw-r--r--httemplate/docs/license.html120
-rw-r--r--httemplate/docs/man/FS/part_export/.cvs_is_on_crack0
-rw-r--r--httemplate/docs/overview-new.diabin0 -> 2422 bytes
-rw-r--r--httemplate/docs/overview-new.pngbin0 -> 29062 bytes
-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 -> 16364 bytes
-rw-r--r--httemplate/docs/schema.html533
-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/edit/REAL_cust_pkg.cgi220
-rw-r--r--httemplate/edit/access_group.html80
-rw-r--r--httemplate/edit/access_user.html65
-rwxr-xr-xhttemplate/edit/agent.cgi144
-rw-r--r--httemplate/edit/agent_payment_gateway.html68
-rwxr-xr-xhttemplate/edit/agent_type.cgi57
-rw-r--r--httemplate/edit/allocate.html33
-rw-r--r--httemplate/edit/bulk-cust_main_county.html128
-rw-r--r--httemplate/edit/bulk-cust_svc.html95
-rw-r--r--httemplate/edit/cgp_rule.html102
-rwxr-xr-xhttemplate/edit/cust_bill_pay.cgi14
-rw-r--r--httemplate/edit/cust_category.html5
-rw-r--r--httemplate/edit/cust_class.html5
-rwxr-xr-xhttemplate/edit/cust_credit.cgi81
-rwxr-xr-xhttemplate/edit/cust_credit_bill.cgi14
-rwxr-xr-xhttemplate/edit/cust_credit_refund.cgi14
-rwxr-xr-xhttemplate/edit/cust_main.cgi321
-rw-r--r--httemplate/edit/cust_main/billing.html492
-rw-r--r--httemplate/edit/cust_main/birthdate.html15
-rw-r--r--httemplate/edit/cust_main/bottomfixup.html19
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js399
-rw-r--r--httemplate/edit/cust_main/choose_tax_location.html87
-rw-r--r--httemplate/edit/cust_main/contact.html150
-rw-r--r--httemplate/edit/cust_main/first_pkg.html55
-rw-r--r--httemplate/edit/cust_main/first_pkg/select-part_pkg.html170
-rw-r--r--httemplate/edit/cust_main/first_pkg/svc_acct.html88
-rw-r--r--httemplate/edit/cust_main/first_pkg/svc_phone.html82
-rw-r--r--httemplate/edit/cust_main/top_misc.html109
-rwxr-xr-xhttemplate/edit/cust_main_attach.cgi70
-rwxr-xr-xhttemplate/edit/cust_main_county-expand.cgi52
-rw-r--r--httemplate/edit/cust_main_county.html64
-rwxr-xr-xhttemplate/edit/cust_main_note.cgi51
-rwxr-xr-xhttemplate/edit/cust_pay.cgi149
-rw-r--r--httemplate/edit/cust_pay_pending.html154
-rwxr-xr-xhttemplate/edit/cust_pay_refund.cgi14
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi150
-rw-r--r--httemplate/edit/cust_pkg_detail.html142
-rwxr-xr-xhttemplate/edit/cust_pkg_discount.html76
-rwxr-xr-xhttemplate/edit/cust_refund.cgi167
-rw-r--r--httemplate/edit/cust_tax_adjustment.html102
-rw-r--r--httemplate/edit/discount.html139
-rw-r--r--httemplate/edit/elements/ApplicationCommon.html552
-rw-r--r--httemplate/edit/elements/category_Common.html24
-rw-r--r--httemplate/edit/elements/class_Common.html32
-rw-r--r--httemplate/edit/elements/edit.html854
-rw-r--r--httemplate/edit/elements/svc_Common.html211
-rw-r--r--httemplate/edit/inventory_class.html16
-rw-r--r--httemplate/edit/invoice_logo.html136
-rw-r--r--httemplate/edit/invoice_template.html69
-rw-r--r--httemplate/edit/mailinglistmember.html25
-rwxr-xr-xhttemplate/edit/msgcat.cgi54
-rwxr-xr-xhttemplate/edit/part_bill_event.cgi570
-rw-r--r--httemplate/edit/part_device.html45
-rw-r--r--httemplate/edit/part_event.html679
-rw-r--r--httemplate/edit/part_export.cgi138
-rwxr-xr-xhttemplate/edit/part_pkg.cgi735
-rw-r--r--httemplate/edit/part_pkg_report_option.html23
-rw-r--r--httemplate/edit/part_pkg_taxclass.html23
-rw-r--r--httemplate/edit/part_pkg_taxoverride.html132
-rwxr-xr-xhttemplate/edit/part_referral.html19
-rwxr-xr-xhttemplate/edit/part_svc.cgi418
-rw-r--r--httemplate/edit/part_virtual_field.cgi104
-rw-r--r--httemplate/edit/payment_gateway.html139
-rw-r--r--httemplate/edit/phone_device.html37
-rw-r--r--httemplate/edit/pkg_category.html28
-rw-r--r--httemplate/edit/pkg_class.html5
-rw-r--r--httemplate/edit/prepay_credit.cgi113
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi49
-rw-r--r--httemplate/edit/process/access_group.html28
-rw-r--r--httemplate/edit/process/access_user.html21
-rwxr-xr-xhttemplate/edit/process/addr_block/add.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/allocate.cgi16
-rwxr-xr-xhttemplate/edit/process/addr_block/deallocate.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/manual_flag.cgi30
-rwxr-xr-xhttemplate/edit/process/addr_block/split.cgi27
-rwxr-xr-xhttemplate/edit/process/agent.cgi16
-rw-r--r--httemplate/edit/process/agent_payment_gateway.html29
-rwxr-xr-xhttemplate/edit/process/agent_type.cgi35
-rw-r--r--httemplate/edit/process/bulk-cust_main_county.html66
-rw-r--r--httemplate/edit/process/bulk-cust_svc.cgi9
-rw-r--r--httemplate/edit/process/cgp_rule.html24
-rw-r--r--httemplate/edit/process/change-cust_pkg.html46
-rwxr-xr-xhttemplate/edit/process/cust_bill_pay.cgi13
-rw-r--r--httemplate/edit/process/cust_category.html11
-rw-r--r--httemplate/edit/process/cust_class.html11
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi63
-rwxr-xr-xhttemplate/edit/process/cust_credit_bill.cgi19
-rwxr-xr-xhttemplate/edit/process/cust_credit_refund.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi260
-rw-r--r--httemplate/edit/process/cust_main_attach.cgi99
-rwxr-xr-xhttemplate/edit/process/cust_main_county-collapse.cgi46
-rwxr-xr-xhttemplate/edit/process/cust_main_county-expand.cgi83
-rw-r--r--httemplate/edit/process/cust_main_county.html13
-rwxr-xr-xhttemplate/edit/process/cust_main_note.cgi55
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi57
-rw-r--r--httemplate/edit/process/cust_pay_pending.html68
-rwxr-xr-xhttemplate/edit/process/cust_pay_refund.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_pkg.cgi42
-rw-r--r--httemplate/edit/process/cust_pkg_detail.html59
-rw-r--r--httemplate/edit/process/cust_pkg_discount.html47
-rwxr-xr-xhttemplate/edit/process/cust_refund.cgi56
-rw-r--r--httemplate/edit/process/cust_svc.cgi30
-rw-r--r--httemplate/edit/process/cust_tax_adjustment.html41
-rw-r--r--httemplate/edit/process/discount.html12
-rwxr-xr-xhttemplate/edit/process/domain_record.cgi30
-rwxr-xr-xhttemplate/edit/process/domreg.cgi62
-rw-r--r--httemplate/edit/process/elements/ApplicationCommon.html103
-rw-r--r--httemplate/edit/process/elements/process.html309
-rw-r--r--httemplate/edit/process/elements/svc_Common.html15
-rw-r--r--httemplate/edit/process/generic.cgi77
-rw-r--r--httemplate/edit/process/inventory_class.html11
-rw-r--r--httemplate/edit/process/invoice_logo.html25
-rw-r--r--httemplate/edit/process/invoice_template.html15
-rw-r--r--httemplate/edit/process/mailinglistmember.html6
-rw-r--r--httemplate/edit/process/msgcat.cgi22
-rwxr-xr-xhttemplate/edit/process/part_bill_event.cgi106
-rw-r--r--httemplate/edit/process/part_device.html15
-rw-r--r--httemplate/edit/process/part_event.html86
-rw-r--r--httemplate/edit/process/part_export.cgi42
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi222
-rw-r--r--httemplate/edit/process/part_pkg_report_option.html11
-rw-r--r--httemplate/edit/process/part_pkg_taxclass.html17
-rwxr-xr-xhttemplate/edit/process/part_referral.html12
-rwxr-xr-xhttemplate/edit/process/part_svc.cgi9
-rw-r--r--httemplate/edit/process/payment_gateway.html22
-rw-r--r--httemplate/edit/process/phone_device.html18
-rw-r--r--httemplate/edit/process/pkg_category.html11
-rw-r--r--httemplate/edit/process/pkg_class.html11
-rw-r--r--httemplate/edit/process/prepay_credit.cgi63
-rw-r--r--httemplate/edit/process/prospect_main.html34
-rw-r--r--httemplate/edit/process/quick-charge.cgi75
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi84
-rwxr-xr-xhttemplate/edit/process/rate.cgi9
-rw-r--r--httemplate/edit/process/rate_detail.html13
-rwxr-xr-xhttemplate/edit/process/rate_region.cgi57
-rw-r--r--httemplate/edit/process/reason.html12
-rw-r--r--httemplate/edit/process/reason_type.html12
-rw-r--r--httemplate/edit/process/reg_code.cgi45
-rw-r--r--httemplate/edit/process/router.cgi20
-rw-r--r--httemplate/edit/process/svc_Common.html16
-rwxr-xr-xhttemplate/edit/process/svc_acct.cgi82
-rwxr-xr-xhttemplate/edit/process/svc_acct_pop.cgi33
-rw-r--r--httemplate/edit/process/svc_broadband.cgi8
-rwxr-xr-xhttemplate/edit/process/svc_domain.cgi57
-rwxr-xr-xhttemplate/edit/process/svc_external.cgi31
-rw-r--r--httemplate/edit/process/svc_external.html10
-rwxr-xr-xhttemplate/edit/process/svc_forward.cgi31
-rw-r--r--httemplate/edit/process/svc_mailinglist.html11
-rw-r--r--httemplate/edit/process/svc_phone.html29
-rw-r--r--httemplate/edit/process/svc_www.cgi38
-rw-r--r--httemplate/edit/process/tax_class.html49
-rw-r--r--httemplate/edit/process/tax_rate.html22
-rw-r--r--httemplate/edit/process/usage_class.html11
-rw-r--r--httemplate/edit/prospect_main.html115
-rw-r--r--httemplate/edit/quick-charge.html294
-rw-r--r--httemplate/edit/rate.cgi45
-rw-r--r--httemplate/edit/rate_detail.html72
-rw-r--r--httemplate/edit/rate_region.cgi189
-rw-r--r--httemplate/edit/reason.html50
-rw-r--r--httemplate/edit/reason_type.html29
-rw-r--r--httemplate/edit/reg_code.cgi44
-rwxr-xr-xhttemplate/edit/router.cgi54
-rw-r--r--httemplate/edit/svc_Common.html33
-rwxr-xr-xhttemplate/edit/svc_acct.cgi754
-rwxr-xr-xhttemplate/edit/svc_acct_pop.cgi53
-rw-r--r--httemplate/edit/svc_broadband.cgi107
-rwxr-xr-xhttemplate/edit/svc_domain.cgi442
-rw-r--r--httemplate/edit/svc_external.cgi1
-rwxr-xr-xhttemplate/edit/svc_forward.cgi186
-rw-r--r--httemplate/edit/svc_mailinglist.cgi25
-rw-r--r--httemplate/edit/svc_phone.cgi77
-rw-r--r--httemplate/edit/svc_www.cgi240
-rw-r--r--httemplate/edit/tax_class.html36
-rw-r--r--httemplate/edit/tax_rate.html106
-rw-r--r--httemplate/edit/usage_class.html42
-rw-r--r--httemplate/elements/about_freeside.html19
-rw-r--r--httemplate/elements/about_rt.html13
-rw-r--r--httemplate/elements/ajaxcontentmws.js185
-rw-r--r--httemplate/elements/calendar-en.js127
-rw-r--r--httemplate/elements/calendar-setup.js200
-rw-r--r--httemplate/elements/calendar-win2k-2.css272
-rw-r--r--httemplate/elements/calendar.js1806
-rw-r--r--httemplate/elements/calendar_stripped.js14
-rw-r--r--httemplate/elements/checkbox.html19
-rw-r--r--httemplate/elements/checkboxes-table-name.html90
-rw-r--r--httemplate/elements/checkboxes-table.html129
-rw-r--r--httemplate/elements/checkboxes.html107
-rw-r--r--httemplate/elements/city.html145
-rw-r--r--httemplate/elements/columnend.html6
-rw-r--r--httemplate/elements/columnnext.html4
-rw-r--r--httemplate/elements/columnstart.html6
-rw-r--r--httemplate/elements/communigate_pro-accessmodes.html30
-rw-r--r--httemplate/elements/contact.html56
-rw-r--r--httemplate/elements/cssexpr.js66
-rw-r--r--httemplate/elements/customer-table.html524
-rw-r--r--httemplate/elements/dashboard-install_welcome.html10
-rw-r--r--httemplate/elements/dashboard-toplist.html113
-rw-r--r--httemplate/elements/error.html4
-rw-r--r--httemplate/elements/errorpage-popup.html15
-rw-r--r--httemplate/elements/errorpage.html11
-rw-r--r--httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc15
-rw-r--r--httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc36
-rw-r--r--httemplate/elements/fckeditor/editor/css/fck_editorarea.css110
-rw-r--r--httemplate/elements/fckeditor/editor/css/fck_internal.css199
-rw-r--r--httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css49
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_address.pngbin0 -> 288 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_blockquote.pngbin0 -> 293 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_div.pngbin0 -> 229 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h1.pngbin0 -> 218 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h2.pngbin0 -> 220 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h3.pngbin0 -> 219 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h4.pngbin0 -> 229 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h5.pngbin0 -> 236 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_h6.pngbin0 -> 216 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_p.pngbin0 -> 205 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/block_pre.pngbin0 -> 223 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/fck_anchor.gifbin0 -> 184 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gifbin0 -> 599 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gifbin0 -> 105 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gifbin0 -> 54 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/css/images/fck_plugin.gifbin0 -> 1709 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css85
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js347
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc24
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/images/locked.gifbin0 -> 74 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/images/reset.gifbin0 -> 104 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gifbin0 -> 75 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml30
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_about.html161
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gifbin0 -> 2044 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gifbin0 -> 920 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gifbin0 -> 1447 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_anchor.html220
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_button.html104
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html104
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html172
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_div.html396
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_docprops.html600
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html113
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_find.html173
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_flash.html152
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js300
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html50
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_form.html109
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html115
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_image.html258
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js512
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html72
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_link.html295
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js893
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_listprop.html120
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_paste.html347
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html104
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_replace.html650
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_scayt.html746
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_scayt/scayt_dialog.css169
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_select.html180
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js194
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_smiley.html111
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_source.html68
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html121
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html70
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html0
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js87
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html153
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl181
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js461
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html71
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css49
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js272
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_table.html440
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html293
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_template.html242
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gifbin0 -> 375 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gifbin0 -> 333 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gifbin0 -> 422 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_textarea.html94
-rw-r--r--httemplate/elements/fckeditor/editor/dialog/fck_textfield.html136
-rw-r--r--httemplate/elements/fckeditor/editor/dtd/fck_dtd_test.html41
-rw-r--r--httemplate/elements/fckeditor/editor/dtd/fck_xhtml10strict.js116
-rw-r--r--httemplate/elements/fckeditor/editor/dtd/fck_xhtml10transitional.js140
-rw-r--r--httemplate/elements/fckeditor/editor/fckdebug.html153
-rw-r--r--httemplate/elements/fckeditor/editor/fckdialog.html819
-rw-r--r--httemplate/elements/fckeditor/editor/fckeditor.html317
-rw-r--r--httemplate/elements/fckeditor/editor/fckeditor.original.html425
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css87
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html200
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl63
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl158
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi137
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl131
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl667
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl60
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html95
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html114
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html198
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html169
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html69
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html115
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gifbin0 -> 138 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gifbin0 -> 128 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gifbin0 -> 281 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gifbin0 -> 132 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gifbin0 -> 264 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gifbin0 -> 132 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gifbin0 -> 1140 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gifbin0 -> 454 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gifbin0 -> 709 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gifbin0 -> 224 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gifbin0 -> 177 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gifbin0 -> 258 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gifbin0 -> 260 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gifbin0 -> 170 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gifbin0 -> 946 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gifbin0 -> 704 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gifbin0 -> 1527 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gifbin0 -> 1527 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gifbin0 -> 463 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gifbin0 -> 274 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gifbin0 -> 274 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gifbin0 -> 454 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gifbin0 -> 567 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gifbin0 -> 464 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gifbin0 -> 254 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gifbin0 -> 1493 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gifbin0 -> 725 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gifbin0 -> 724 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gifbin0 -> 213 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gifbin0 -> 277 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gifbin0 -> 271 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gifbin0 -> 408 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gifbin0 -> 368 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gifbin0 -> 403 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gifbin0 -> 249 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gifbin0 -> 126 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gifbin0 -> 128 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gifbin0 -> 113 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gifbin0 -> 132 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gifbin0 -> 140 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gifbin0 -> 109 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gifbin0 -> 382 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gifbin0 -> 125 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gifbin0 -> 621 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gifbin0 -> 621 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gifbin0 -> 125 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gifbin0 -> 139 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gifbin0 -> 146 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gifbin0 -> 249 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gifbin0 -> 230 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gifbin0 -> 125 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gifbin0 -> 139 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gifbin0 -> 606 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gifbin0 -> 388 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gifbin0 -> 388 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gifbin0 -> 122 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gifbin0 -> 136 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gifbin0 -> 138 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gifbin0 -> 231 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gifbin0 -> 235 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gifbin0 -> 43 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js88
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js147
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/basexml.pl68
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/commands.pl200
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/config.pl39
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/connector.cgi129
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/io.pl141
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload.cgi87
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload_fck.pl686
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/perl/util.pl66
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/test.html210
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/connectors/uploadtest.html192
-rw-r--r--httemplate/elements/fckeditor/editor/filemanager/upload/test.html133
-rw-r--r--httemplate/elements/fckeditor/editor/images/anchor.gifbin0 -> 184 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/arrow_ltr.gifbin0 -> 49 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/arrow_rtl.gifbin0 -> 49 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gifbin0 -> 445 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gifbin0 -> 453 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gifbin0 -> 423 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gifbin0 -> 453 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gifbin0 -> 322 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gifbin0 -> 473 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gifbin0 -> 444 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gifbin0 -> 1077 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gifbin0 -> 1030 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gifbin0 -> 1012 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gifbin0 -> 978 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gifbin0 -> 303 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gifbin0 -> 342 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gifbin0 -> 1036 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gifbin0 -> 1039 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gifbin0 -> 1059 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gifbin0 -> 1064 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gifbin0 -> 992 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gifbin0 -> 989 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gifbin0 -> 1055 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gifbin0 -> 1034 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gifbin0 -> 1041 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/images/spacer.gif (renamed from rt/html/NoAuth/images/spacer.gif)bin43 -> 43 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/js/fckadobeair.js176
-rw-r--r--httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js109
-rw-r--r--httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js110
-rw-r--r--httemplate/elements/fckeditor/editor/lang/_getfontformat.html85
-rw-r--r--httemplate/elements/fckeditor/editor/lang/_translationstatus.txt79
-rw-r--r--httemplate/elements/fckeditor/editor/lang/af.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ar.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/bg.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/bn.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/bs.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ca.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/cs.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/da.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/de.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/el.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/en-au.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/en-ca.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/en-uk.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/en.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/eo.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/es.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/et.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/eu.js540
-rw-r--r--httemplate/elements/fckeditor/editor/lang/fa.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/fi.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/fo.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/fr-ca.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/fr.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/gl.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/gu.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/he.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/hi.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/hr.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/hu.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/is.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/it.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ja.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/km.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ko.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/lt.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/lv.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/mn.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ms.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/nb.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/nl.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/no.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/pl.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/pt-br.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/pt.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ro.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/ru.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/sk.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/sl.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/sr-latn.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/sr.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/sv.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/th.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/tr.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/uk.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/vi.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/zh-cn.js539
-rw-r--r--httemplate/elements/fckeditor/editor/lang/zh.js539
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js111
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/bbcode/fckplugin.js123
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/dragresizetable/fckplugin.js529
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html105
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js187
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/es.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js27
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gifbin0 -> 96 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js29
-rw-r--r--httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js33
-rw-r--r--httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html121
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css402
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/fck_dialog_ie6.js110
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/fck_editor.css464
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/fck_strip.gifbin0 -> 5175 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.gifbin0 -> 48 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.pngbin0 -> 178 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.rtl.pngbin0 -> 181 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/sprites.gifbin0 -> 959 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/sprites.pngbin0 -> 3250 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gifbin0 -> 53 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gifbin0 -> 46 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gifbin0 -> 43 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gifbin0 -> 58 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gifbin0 -> 105 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css402
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog_ie6.js110
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css476
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gifbin0 -> 9668 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.gifbin0 -> 48 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.pngbin0 -> 203 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.rtl.pngbin0 -> 205 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.gifbin0 -> 959 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.pngbin0 -> 3305 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gifbin0 -> 53 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gifbin0 -> 73 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gifbin0 -> 46 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gifbin0 -> 124 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gifbin0 -> 67 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gifbin0 -> 99 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css402
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/fck_dialog_ie6.js110
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css473
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gifbin0 -> 5175 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.gifbin0 -> 48 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.pngbin0 -> 198 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.rtl.pngbin0 -> 200 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/sprites.gifbin0 -> 959 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/sprites.pngbin0 -> 3278 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gifbin0 -> 53 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gifbin0 -> 46 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gifbin0 -> 829 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gifbin0 -> 43 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gifbin0 -> 152 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gifbin0 -> 58 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gifbin0 -> 105 bytes
-rw-r--r--httemplate/elements/fckeditor/editor/wsc/ciframe.html65
-rw-r--r--httemplate/elements/fckeditor/editor/wsc/tmpFrameset.html67
-rw-r--r--httemplate/elements/fckeditor/editor/wsc/w.html227
-rw-r--r--httemplate/elements/fckeditor/fckconfig.js325
-rw-r--r--httemplate/elements/fckeditor/fckeditor.js330
-rw-r--r--httemplate/elements/fckeditor/fckpackager.xml264
-rw-r--r--httemplate/elements/fckeditor/fckstyles.xml111
-rw-r--r--httemplate/elements/fckeditor/fcktemplates.xml103
-rw-r--r--httemplate/elements/file-upload.html74
-rw-r--r--httemplate/elements/footer.html5
-rw-r--r--httemplate/elements/form-file_upload.html93
-rw-r--r--httemplate/elements/freeside-menu.css148
-rw-r--r--httemplate/elements/freeside.css152
-rw-r--r--httemplate/elements/header-minimal.html19
-rw-r--r--httemplate/elements/header-popup.html43
-rw-r--r--httemplate/elements/header.html187
-rw-r--r--httemplate/elements/hidden.html11
-rw-r--r--httemplate/elements/htmlarea.html37
-rw-r--r--httemplate/elements/iframecontentmws.js59
-rw-r--r--httemplate/elements/init_calendar.html5
-rw-r--r--httemplate/elements/init_overlib.html9
-rw-r--r--httemplate/elements/input-text.html48
-rw-r--r--httemplate/elements/jsrsClient.js356
-rw-r--r--httemplate/elements/jsrsServer.html4
-rw-r--r--httemplate/elements/location.html158
-rw-r--r--httemplate/elements/logout.html44
-rw-r--r--httemplate/elements/mcp_lint.html40
-rw-r--r--httemplate/elements/menu.html622
-rw-r--r--httemplate/elements/menuarrow.gifbin0 -> 68 bytes
-rw-r--r--httemplate/elements/menubar.html59
-rw-r--r--httemplate/elements/overlibmws.js621
-rw-r--r--httemplate/elements/overlibmws_crossframe.js58
-rw-r--r--httemplate/elements/overlibmws_draggable.js85
-rw-r--r--httemplate/elements/overlibmws_iframe.js93
-rw-r--r--httemplate/elements/pager.html55
-rw-r--r--httemplate/elements/phonenumber.html81
-rw-r--r--httemplate/elements/popup_link-cust_main.html42
-rw-r--r--httemplate/elements/popup_link-cust_pkg.html47
-rw-r--r--httemplate/elements/popup_link-cust_svc.html47
-rw-r--r--httemplate/elements/popup_link-ping.html30
-rw-r--r--httemplate/elements/popup_link.html51
-rw-r--r--httemplate/elements/popup_link_onclick.html74
-rw-r--r--httemplate/elements/progress-init.html87
-rw-r--r--httemplate/elements/progress-popup.html116
-rw-r--r--httemplate/elements/qlib/box.js29
-rw-r--r--httemplate/elements/qlib/boxctrl.js48
-rw-r--r--httemplate/elements/qlib/boxres.js42
-rw-r--r--httemplate/elements/qlib/button.js74
-rw-r--r--httemplate/elements/qlib/buttonres.js23
-rw-r--r--httemplate/elements/qlib/control.js51
-rw-r--r--httemplate/elements/qlib/counter.js81
-rw-r--r--httemplate/elements/qlib/imagelist.js25
-rw-r--r--httemplate/elements/qlib/label.js72
-rw-r--r--httemplate/elements/qlib/messagebox.js57
-rw-r--r--httemplate/elements/qlib/progress.js73
-rw-r--r--httemplate/elements/qlib/sound.js47
-rw-r--r--httemplate/elements/qlib/sprite.js125
-rw-r--r--httemplate/elements/qlib/window.js25
-rw-r--r--httemplate/elements/qlib/wndctrl.js322
-rw-r--r--httemplate/elements/rs_init_object.html29
-rw-r--r--httemplate/elements/search-cust_main.html201
-rw-r--r--httemplate/elements/searchbar-address2.html34
-rw-r--r--httemplate/elements/searchbar-cust_bill.html34
-rw-r--r--httemplate/elements/searchbar-cust_main.html38
-rw-r--r--httemplate/elements/searchbar-cust_svc.html33
-rw-r--r--httemplate/elements/searchbar-prospect.html33
-rw-r--r--httemplate/elements/searchbar-ticket.html35
-rw-r--r--httemplate/elements/select-access_group.html16
-rw-r--r--httemplate/elements/select-agent.html31
-rw-r--r--httemplate/elements/select-agent_type.html21
-rw-r--r--httemplate/elements/select-agent_types.html30
-rw-r--r--httemplate/elements/select-areacode.html92
-rw-r--r--httemplate/elements/select-cdrbatch.html14
-rw-r--r--httemplate/elements/select-cgp_rule_action.html116
-rw-r--r--httemplate/elements/select-cgp_rule_condition.html200
-rw-r--r--httemplate/elements/select-country.html130
-rw-r--r--httemplate/elements/select-county.html166
-rw-r--r--httemplate/elements/select-cust-fields.html24
-rw-r--r--httemplate/elements/select-cust-part_pkg.html41
-rw-r--r--httemplate/elements/select-cust-pkg_class.html12
-rw-r--r--httemplate/elements/select-cust_class.html18
-rw-r--r--httemplate/elements/select-cust_main-status.html33
-rw-r--r--httemplate/elements/select-cust_pkg-balances.html32
-rw-r--r--httemplate/elements/select-cust_pkg-status.html33
-rw-r--r--httemplate/elements/select-did.html91
-rw-r--r--httemplate/elements/select-discount.html28
-rw-r--r--httemplate/elements/select-domain.html13
-rw-r--r--httemplate/elements/select-exchange.html86
-rw-r--r--httemplate/elements/select-month_year.html62
-rw-r--r--httemplate/elements/select-otaker.html27
-rw-r--r--httemplate/elements/select-part_event.html6
-rw-r--r--httemplate/elements/select-part_pkg.html51
-rw-r--r--httemplate/elements/select-part_referral.html20
-rw-r--r--httemplate/elements/select-part_svc.html18
-rw-r--r--httemplate/elements/select-payby.html42
-rw-r--r--httemplate/elements/select-phonenum.html84
-rw-r--r--httemplate/elements/select-pkg_class.html22
-rw-r--r--httemplate/elements/select-rate.html9
-rw-r--r--httemplate/elements/select-state.html66
-rw-r--r--httemplate/elements/select-svc-domain.html50
-rw-r--r--httemplate/elements/select-svc_acct-domain.html46
-rw-r--r--httemplate/elements/select-svc_pbx.html57
-rw-r--r--httemplate/elements/select-table.html200
-rw-r--r--httemplate/elements/select-taxclass.html41
-rw-r--r--httemplate/elements/select-taxoverride.html28
-rw-r--r--httemplate/elements/select-taxproduct.html28
-rw-r--r--httemplate/elements/select-terms.html41
-rw-r--r--httemplate/elements/select-user.html30
-rw-r--r--httemplate/elements/select.html79
-rw-r--r--httemplate/elements/selectlayers.html242
-rw-r--r--httemplate/elements/small_custview.html3
-rw-r--r--httemplate/elements/table-grid.html25
-rw-r--r--httemplate/elements/table.html11
-rw-r--r--httemplate/elements/tablebreak-tr-title.html14
-rw-r--r--httemplate/elements/tr-checkbox-multiple.html40
-rw-r--r--httemplate/elements/tr-checkbox.html19
-rw-r--r--httemplate/elements/tr-checkboxes-table.html20
-rw-r--r--httemplate/elements/tr-contact.html24
-rw-r--r--httemplate/elements/tr-fixed-country.html10
-rw-r--r--httemplate/elements/tr-fixed-state.html10
-rw-r--r--httemplate/elements/tr-fixed.html15
-rw-r--r--httemplate/elements/tr-freq.html54
-rw-r--r--httemplate/elements/tr-input-beginning_ending.html79
-rw-r--r--httemplate/elements/tr-input-date-field.html56
-rw-r--r--httemplate/elements/tr-input-lessthan_greaterthan.html13
-rw-r--r--httemplate/elements/tr-input-money.html13
-rw-r--r--httemplate/elements/tr-input-percentage.html8
-rw-r--r--httemplate/elements/tr-input-text.html15
-rw-r--r--httemplate/elements/tr-justtitle.html11
-rw-r--r--httemplate/elements/tr-part_pkg_freq.html24
-rw-r--r--httemplate/elements/tr-password.html4
-rw-r--r--httemplate/elements/tr-pkg_svc.html93
-rw-r--r--httemplate/elements/tr-search-cust_main.html15
-rw-r--r--httemplate/elements/tr-select-access_group.html22
-rw-r--r--httemplate/elements/tr-select-agent.html33
-rw-r--r--httemplate/elements/tr-select-agent_type.html39
-rw-r--r--httemplate/elements/tr-select-agent_types.html19
-rw-r--r--httemplate/elements/tr-select-cdrbatch.html30
-rw-r--r--httemplate/elements/tr-select-cust-fields.html15
-rw-r--r--httemplate/elements/tr-select-cust-part_pkg.html115
-rw-r--r--httemplate/elements/tr-select-cust_class.html27
-rw-r--r--httemplate/elements/tr-select-cust_location.html232
-rw-r--r--httemplate/elements/tr-select-cust_main-status.html29
-rw-r--r--httemplate/elements/tr-select-cust_pkg-balances.html31
-rw-r--r--httemplate/elements/tr-select-cust_pkg-status.html29
-rw-r--r--httemplate/elements/tr-select-did.html41
-rw-r--r--httemplate/elements/tr-select-discount.html179
-rw-r--r--httemplate/elements/tr-select-domain.html12
-rw-r--r--httemplate/elements/tr-select-from_to.html52
-rw-r--r--httemplate/elements/tr-select-invoice_template.html39
-rw-r--r--httemplate/elements/tr-select-otaker.html10
-rw-r--r--httemplate/elements/tr-select-part_event.html20
-rw-r--r--httemplate/elements/tr-select-part_pkg.html39
-rw-r--r--httemplate/elements/tr-select-part_referral.html37
-rw-r--r--httemplate/elements/tr-select-part_svc.html26
-rw-r--r--httemplate/elements/tr-select-payby.html37
-rw-r--r--httemplate/elements/tr-select-pkg_class.html27
-rw-r--r--httemplate/elements/tr-select-rate.html21
-rwxr-xr-xhttemplate/elements/tr-select-reason.html189
-rw-r--r--httemplate/elements/tr-select-svc-domain.html34
-rw-r--r--httemplate/elements/tr-select-svc_acct-domain.html34
-rw-r--r--httemplate/elements/tr-select-svc_pbx.html60
-rw-r--r--httemplate/elements/tr-select-table.html20
-rw-r--r--httemplate/elements/tr-select-taxclass.html38
-rw-r--r--httemplate/elements/tr-select-taxoverride.html18
-rw-r--r--httemplate/elements/tr-select-taxproduct.html18
-rw-r--r--httemplate/elements/tr-select-user.html10
-rw-r--r--httemplate/elements/tr-select.html22
-rw-r--r--httemplate/elements/tr-selectlayers-select.html1
-rw-r--r--httemplate/elements/tr-selectlayers.html25
-rw-r--r--httemplate/elements/tr-selectmultiple-part_pkg.html19
-rw-r--r--httemplate/elements/tr-td-label.html17
-rw-r--r--httemplate/elements/tr-textarea.html30
-rw-r--r--httemplate/elements/tr-title.html10
-rw-r--r--httemplate/elements/xmenu.css166
-rw-r--r--httemplate/elements/xmenu.js668
-rw-r--r--httemplate/elements/xmenu.top.css168
-rw-r--r--httemplate/elements/xmenu.top.js671
-rw-r--r--httemplate/elements/xmlhttp.html108
-rw-r--r--httemplate/graph/cust_bill_pkg.cgi151
-rw-r--r--httemplate/graph/cust_bill_pkg_detail.cgi137
-rw-r--r--httemplate/graph/cust_pkg.cgi63
-rw-r--r--httemplate/graph/cust_pkg_cost.cgi61
-rw-r--r--httemplate/graph/elements/monthly.html139
-rw-r--r--httemplate/graph/elements/report.html296
-rw-r--r--httemplate/graph/money_time.cgi98
-rw-r--r--httemplate/graph/report_cust_bill_pkg.html57
-rw-r--r--httemplate/graph/report_cust_bill_pkg_detail.html48
-rw-r--r--httemplate/graph/report_cust_pkg.html28
-rw-r--r--httemplate/graph/report_cust_pkg_cost.html26
-rw-r--r--httemplate/graph/report_money_time.html43
-rw-r--r--httemplate/graph/report_signupdate.html30
-rw-r--r--httemplate/graph/signupdate.cgi60
-rw-r--r--httemplate/images/32clear.gifbin0 -> 815 bytes
-rw-r--r--httemplate/images/ach.pngbin0 -> 29759 bytes
-rw-r--r--httemplate/images/arrow.down.black.pngbin0 -> 168 bytes
-rw-r--r--httemplate/images/arrow.down.pngbin0 -> 155 bytes
-rw-r--r--httemplate/images/arrow.right.black.pngbin0 -> 160 bytes
-rw-r--r--httemplate/images/arrow.right.pngbin0 -> 160 bytes
-rw-r--r--httemplate/images/background-cheat.pngbin0 -> 338 bytes
-rw-r--r--httemplate/images/black-gray-corner.pngbin0 -> 460 bytes
-rw-r--r--httemplate/images/black-gray-top.pngbin0 -> 203 bytes
-rw-r--r--httemplate/images/calendar-disabled.pngbin0 -> 209 bytes
-rw-r--r--httemplate/images/calendar.pngbin0 -> 426 bytes
-rw-r--r--httemplate/images/cross.pngbin0 -> 655 bytes
-rw-r--r--httemplate/images/cvv2.pngbin0 -> 7795 bytes
-rw-r--r--httemplate/images/cvv2_amex.pngbin0 -> 9522 bytes
-rw-r--r--httemplate/images/error.pngbin0 -> 666 bytes
-rw-r--r--httemplate/images/menu-left-example.pngbin0 -> 24709 bytes
-rw-r--r--httemplate/images/menu-top-example.pngbin0 -> 22816 bytes
-rw-r--r--httemplate/images/progressbar-empty.pngbin0 -> 90 bytes
-rw-r--r--httemplate/images/progressbar-full.pngbin0 -> 79 bytes
-rw-r--r--httemplate/images/red_telephone_mimooh_01.pngbin0 -> 921 bytes
-rw-r--r--httemplate/images/small-logo.pngbin0 -> 4887 bytes
-rw-r--r--httemplate/images/tick.pngbin0 -> 537 bytes
-rw-r--r--httemplate/images/wait-orange.gifbin0 -> 1849 bytes
-rw-r--r--httemplate/index.html54
-rw-r--r--httemplate/loginout/logout.html18
-rw-r--r--httemplate/misc/areacodes.cgi24
-rw-r--r--httemplate/misc/batch-cust_pay.html51
-rwxr-xr-xhttemplate/misc/bill.cgi38
-rwxr-xr-xhttemplate/misc/bulk_change_pkg.cgi62
-rwxr-xr-xhttemplate/misc/bulk_pkg_increment_bill.cgi50
-rwxr-xr-xhttemplate/misc/cancel-unaudited.cgi33
-rw-r--r--httemplate/misc/cancel_cust.html63
-rwxr-xr-xhttemplate/misc/cancel_pkg.html112
-rwxr-xr-xhttemplate/misc/catchall.cgi118
-rw-r--r--httemplate/misc/cdr-import.html61
-rw-r--r--httemplate/misc/cdr.cgi48
-rwxr-xr-xhttemplate/misc/change_pkg.cgi68
-rw-r--r--httemplate/misc/cities.cgi7
-rw-r--r--httemplate/misc/copy-rate_detail.html61
-rw-r--r--httemplate/misc/counties.cgi7
-rw-r--r--httemplate/misc/cust-part_pkg.cgi30
-rw-r--r--httemplate/misc/cust_attachment.cgi34
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi57
-rw-r--r--httemplate/misc/cust_main-import.cgi148
-rw-r--r--httemplate/misc/cust_main-import_charges.cgi22
-rw-r--r--httemplate/misc/cust_main_note-import.cgi207
-rw-r--r--httemplate/misc/cust_main_note-import.html39
-rw-r--r--httemplate/misc/cust_pay-import.cgi62
-rwxr-xr-xhttemplate/misc/delay_susp_pkg.html73
-rw-r--r--httemplate/misc/delete-agent_payment_gateway.cgi15
-rw-r--r--httemplate/misc/delete-cgp_rule.html23
-rw-r--r--httemplate/misc/delete-cust_bill.html21
-rwxr-xr-xhttemplate/misc/delete-cust_credit.cgi21
-rwxr-xr-xhttemplate/misc/delete-cust_pay.cgi21
-rw-r--r--httemplate/misc/delete-cust_pkg_discount.html32
-rwxr-xr-xhttemplate/misc/delete-cust_refund.cgi21
-rwxr-xr-xhttemplate/misc/delete-customer.cgi64
-rwxr-xr-xhttemplate/misc/delete-domain_record.cgi20
-rw-r--r--httemplate/misc/delete-mailinglistmember.html20
-rwxr-xr-xhttemplate/misc/delete-part_export.cgi20
-rwxr-xr-xhttemplate/misc/delete-phone_device.html23
-rw-r--r--httemplate/misc/disable-payment_gateway.cgi25
-rw-r--r--httemplate/misc/download-batch.cgi23
-rw-r--r--httemplate/misc/dump.cgi20
-rw-r--r--httemplate/misc/email-customers.html145
-rwxr-xr-xhttemplate/misc/email-invoice.cgi19
-rw-r--r--httemplate/misc/email_events.cgi9
-rw-r--r--httemplate/misc/email_invoice_events.cgi9
-rw-r--r--httemplate/misc/email_invoices.cgi9
-rwxr-xr-xhttemplate/misc/enable_or_disable_tax.html37
-rw-r--r--httemplate/misc/exchanges.cgi24
-rwxr-xr-xhttemplate/misc/fax-invoice.cgi19
-rw-r--r--httemplate/misc/fax_events.cgi9
-rw-r--r--httemplate/misc/fax_invoice_events.cgi9
-rw-r--r--httemplate/misc/fax_invoices.cgi9
-rw-r--r--httemplate/misc/file-upload.html53
-rw-r--r--httemplate/misc/ftp_invoices.cgi9
-rw-r--r--httemplate/misc/inventory_item-import.html70
-rw-r--r--httemplate/misc/inventory_item-move.cgi23
-rwxr-xr-xhttemplate/misc/link.cgi85
-rw-r--r--httemplate/misc/location.cgi30
-rw-r--r--httemplate/misc/meta-import.cgi79
-rw-r--r--httemplate/misc/order_pkg.html134
-rw-r--r--httemplate/misc/part_device-import.html53
-rw-r--r--httemplate/misc/part_svc-columns.cgi13
-rw-r--r--httemplate/misc/payment.cgi335
-rw-r--r--httemplate/misc/phone_avail-import.html88
-rw-r--r--httemplate/misc/phone_device_config.html57
-rw-r--r--httemplate/misc/phonenums.cgi29
-rw-r--r--httemplate/misc/ping.html102
-rwxr-xr-xhttemplate/misc/print-invoice.cgi19
-rw-r--r--httemplate/misc/print_events.cgi9
-rw-r--r--httemplate/misc/print_invoice_events.cgi9
-rw-r--r--httemplate/misc/print_invoices.cgi9
-rw-r--r--httemplate/misc/process/batch-cust_pay.cgi47
-rwxr-xr-xhttemplate/misc/process/bulk_change_pkg.cgi56
-rwxr-xr-xhttemplate/misc/process/bulk_pkg_increment_bill.cgi76
-rwxr-xr-xhttemplate/misc/process/cancel_pkg.html72
-rwxr-xr-xhttemplate/misc/process/catchall.cgi35
-rw-r--r--httemplate/misc/process/cdr-import.html9
-rw-r--r--httemplate/misc/process/copy-rate_detail.html61
-rw-r--r--httemplate/misc/process/cust_main-import.cgi10
-rw-r--r--httemplate/misc/process/cust_main-import_charges.cgi23
-rw-r--r--httemplate/misc/process/cust_main_note-import.cgi82
-rw-r--r--httemplate/misc/process/cust_pay-import.cgi21
-rwxr-xr-xhttemplate/misc/process/delay_susp_pkg.html41
-rwxr-xr-xhttemplate/misc/process/delete-customer.cgi33
-rw-r--r--httemplate/misc/process/email-customers.html9
-rwxr-xr-xhttemplate/misc/process/enable_or_disable_tax.html41
-rw-r--r--httemplate/misc/process/inventory_item-import.html9
-rwxr-xr-xhttemplate/misc/process/link.cgi78
-rw-r--r--httemplate/misc/process/meta-import.cgi190
-rw-r--r--httemplate/misc/process/part_device-import.html9
-rw-r--r--httemplate/misc/process/payment.cgi204
-rw-r--r--httemplate/misc/process/phone_avail-import.html9
-rw-r--r--httemplate/misc/process/rate-import.html9
-rw-r--r--httemplate/misc/process/rate_edit_excel.html10
-rwxr-xr-xhttemplate/misc/process/recharge_svc.html91
-rwxr-xr-xhttemplate/misc/process/recharge_svc.new85
-rw-r--r--httemplate/misc/process/tax-fetch_and_import.cgi9
-rw-r--r--httemplate/misc/process/tax-fetch_and_replace.cgi9
-rw-r--r--httemplate/misc/process/tax-import.cgi9
-rw-r--r--httemplate/misc/process/tax-upgrade.cgi147
-rw-r--r--httemplate/misc/process/timeworked.html59
-rw-r--r--httemplate/misc/queue.cgi49
-rwxr-xr-xhttemplate/misc/queued_report.html29
-rw-r--r--httemplate/misc/rate-import.html76
-rw-r--r--httemplate/misc/rate_edit_excel.html70
-rwxr-xr-xhttemplate/misc/recharge_svc.html105
-rw-r--r--httemplate/misc/send-invoice.cgi30
-rwxr-xr-xhttemplate/misc/send-statement.cgi28
-rw-r--r--httemplate/misc/spool_invoices.cgi9
-rw-r--r--httemplate/misc/states.cgi7
-rw-r--r--httemplate/misc/svc_acct-domains.cgi31
-rw-r--r--httemplate/misc/tax-fetch_and_import.cgi48
-rw-r--r--httemplate/misc/tax-fetch_and_replace.cgi48
-rw-r--r--httemplate/misc/tax-import.cgi74
-rwxr-xr-xhttemplate/misc/timeworked.html138
-rwxr-xr-xhttemplate/misc/unadjourn_pkg.cgi17
-rwxr-xr-xhttemplate/misc/unapply-cust_credit.cgi20
-rwxr-xr-xhttemplate/misc/unapply-cust_pay.cgi20
-rwxr-xr-xhttemplate/misc/unexpire_pkg.cgi17
-rwxr-xr-xhttemplate/misc/unprovision.cgi26
-rwxr-xr-xhttemplate/misc/unsusp_pkg.cgi20
-rwxr-xr-xhttemplate/misc/unvoid-cust_pay_void.cgi21
-rw-r--r--httemplate/misc/upload-batch.cgi36
-rwxr-xr-xhttemplate/misc/void-cust_pay.cgi26
-rw-r--r--httemplate/misc/whois.cgi33
-rw-r--r--httemplate/misc/xmlhttp-calculate_taxes.html106
-rw-r--r--httemplate/misc/xmlhttp-cust_main-address_standardize.html92
-rw-r--r--httemplate/misc/xmlhttp-cust_main-censustract.html105
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi36
-rw-r--r--httemplate/misc/xmlhttp-ping.html20
-rw-r--r--httemplate/misc/xmlrpc.cgi18
-rw-r--r--httemplate/pref/pref-process.html70
-rw-r--r--httemplate/pref/pref.html189
-rwxr-xr-xhttemplate/search/477.html92
-rwxr-xr-xhttemplate/search/477partIA_detail.html125
-rwxr-xr-xhttemplate/search/477partIA_summary.html80
-rwxr-xr-xhttemplate/search/477partIIA.html113
-rwxr-xr-xhttemplate/search/477partIIB.html102
-rwxr-xr-xhttemplate/search/477partIV.html17
-rwxr-xr-xhttemplate/search/477partV.html53
-rwxr-xr-xhttemplate/search/477partVI.html130
-rw-r--r--httemplate/search/agent_inventory.html40
-rw-r--r--httemplate/search/cdr.html289
-rwxr-xr-xhttemplate/search/cust_bill.html250
-rw-r--r--httemplate/search/cust_bill_event.cgi166
-rwxr-xr-xhttemplate/search/cust_bill_event.html67
-rw-r--r--httemplate/search/cust_bill_pay.html141
-rw-r--r--httemplate/search/cust_bill_pkg.cgi595
-rw-r--r--httemplate/search/cust_bill_pkg_discount.html151
-rwxr-xr-xhttemplate/search/cust_credit.html104
-rw-r--r--httemplate/search/cust_credit_bill.html135
-rw-r--r--httemplate/search/cust_credit_bill_pkg.html520
-rw-r--r--httemplate/search/cust_credit_refund.html130
-rw-r--r--httemplate/search/cust_event.html271
-rwxr-xr-xhttemplate/search/cust_main-otaker.cgi31
-rw-r--r--httemplate/search/cust_main-zip.html110
-rwxr-xr-xhttemplate/search/cust_main.cgi741
-rwxr-xr-xhttemplate/search/cust_main.html111
-rwxr-xr-xhttemplate/search/cust_pay.cgi7
-rwxr-xr-xhttemplate/search/cust_pay_batch.cgi200
-rwxr-xr-xhttemplate/search/cust_pay_pending.html57
-rwxr-xr-xhttemplate/search/cust_pay_void.html13
-rwxr-xr-xhttemplate/search/cust_pkg.cgi283
-rw-r--r--httemplate/search/cust_pkg_discount.html122
-rw-r--r--httemplate/search/cust_refund.html7
-rw-r--r--httemplate/search/cust_svc.html140
-rw-r--r--httemplate/search/cust_tax_adjustment.html54
-rw-r--r--httemplate/search/cust_tax_exempt.cgi139
-rw-r--r--httemplate/search/cust_tax_exempt.html31
-rw-r--r--httemplate/search/cust_tax_exempt_pkg.cgi182
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html268
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html345
-rw-r--r--httemplate/search/elements/metasearch.html71
-rw-r--r--httemplate/search/elements/search-csv.html54
-rw-r--r--httemplate/search/elements/search-html.html477
-rw-r--r--httemplate/search/elements/search-xls.html85
-rw-r--r--httemplate/search/elements/search-xml.html88
-rw-r--r--httemplate/search/elements/search.html415
-rwxr-xr-xhttemplate/search/h_cust_pay.html9
-rw-r--r--httemplate/search/inventory_item.html198
-rw-r--r--httemplate/search/mailinglistmember.html57
-rw-r--r--httemplate/search/part_pkg.html213
-rwxr-xr-xhttemplate/search/pay_batch.cgi130
-rw-r--r--httemplate/search/pay_batch.html33
-rw-r--r--httemplate/search/phone_avail.html102
-rw-r--r--httemplate/search/prepay_credit.html67
-rw-r--r--httemplate/search/prospect_main.html74
-rw-r--r--httemplate/search/queue.html142
-rw-r--r--httemplate/search/reg_code.html40
-rwxr-xr-xhttemplate/search/report_477.html201
-rw-r--r--httemplate/search/report_agent_inventory.html26
-rw-r--r--httemplate/search/report_cdr.html148
-rw-r--r--httemplate/search/report_cust_bill.html51
-rw-r--r--httemplate/search/report_cust_bill_pkg_discount.html47
-rw-r--r--httemplate/search/report_cust_credit.html48
-rw-r--r--httemplate/search/report_cust_event.html49
-rw-r--r--httemplate/search/report_cust_main-zip.html70
-rwxr-xr-xhttemplate/search/report_cust_main.html154
-rw-r--r--httemplate/search/report_cust_pay.html116
-rw-r--r--httemplate/search/report_cust_pay_batch.html44
-rwxr-xr-xhttemplate/search/report_cust_pkg.html208
-rw-r--r--httemplate/search/report_cust_pkg_discount.html50
-rw-r--r--httemplate/search/report_cust_refund.html116
-rw-r--r--httemplate/search/report_employee_commission.html30
-rw-r--r--httemplate/search/report_h_cust_pay.html124
-rwxr-xr-xhttemplate/search/report_newtax.cgi207
-rwxr-xr-xhttemplate/search/report_newtax.html23
-rw-r--r--httemplate/search/report_prepaid_income.cgi235
-rw-r--r--httemplate/search/report_prepaid_income.html64
-rw-r--r--httemplate/search/report_prospect_main.html32
-rwxr-xr-xhttemplate/search/report_queued_newtax.cgi16
-rwxr-xr-xhttemplate/search/report_receivables.cgi52
-rwxr-xr-xhttemplate/search/report_receivables.html47
-rw-r--r--httemplate/search/report_rt_transaction.html24
-rw-r--r--httemplate/search/report_sql.html23
-rwxr-xr-xhttemplate/search/report_svc_acct.html134
-rw-r--r--httemplate/search/report_svc_phone.html32
-rwxr-xr-xhttemplate/search/report_tax.cgi786
-rwxr-xr-xhttemplate/search/report_tax.html79
-rw-r--r--httemplate/search/report_timeworked.html28
-rwxr-xr-xhttemplate/search/report_unapplied_cust_pay.html47
-rw-r--r--httemplate/search/rt_transaction.html96
-rw-r--r--httemplate/search/sql.html13
-rw-r--r--httemplate/search/sqlradius.cgi328
-rw-r--r--httemplate/search/sqlradius.html123
-rwxr-xr-xhttemplate/search/svc_acct.cgi334
-rwxr-xr-xhttemplate/search/svc_broadband.cgi123
-rwxr-xr-xhttemplate/search/svc_domain.cgi112
-rwxr-xr-xhttemplate/search/svc_external.cgi135
-rwxr-xr-xhttemplate/search/svc_forward.cgi146
-rw-r--r--httemplate/search/svc_phone.cgi174
-rwxr-xr-xhttemplate/search/svc_www.cgi113
-rw-r--r--httemplate/search/timeworked.html130
-rwxr-xr-xhttemplate/search/unapplied_cust_pay.html30
-rwxr-xr-xhttemplate/view/REAL_logo.cgi14
-rw-r--r--httemplate/view/attachment.html16
-rwxr-xr-xhttemplate/view/cust_bill-logo.cgi31
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi40
-rwxr-xr-xhttemplate/view/cust_bill-ps.cgi35
-rwxr-xr-xhttemplate/view/cust_bill.cgi151
-rwxr-xr-xhttemplate/view/cust_main.cgi262
-rwxr-xr-xhttemplate/view/cust_main/attachments.html156
-rw-r--r--httemplate/view/cust_main/billing.html253
-rw-r--r--httemplate/view/cust_main/change_history.html302
-rw-r--r--httemplate/view/cust_main/contacts.html122
-rw-r--r--httemplate/view/cust_main/misc.html126
-rwxr-xr-xhttemplate/view/cust_main/notes.html96
-rw-r--r--httemplate/view/cust_main/one_time_charge_link.html91
-rw-r--r--httemplate/view/cust_main/order_pkg_link.html16
-rwxr-xr-xhttemplate/view/cust_main/packages.html228
-rw-r--r--httemplate/view/cust_main/packages/location.html53
-rw-r--r--httemplate/view/cust_main/packages/package.html238
-rw-r--r--httemplate/view/cust_main/packages/services.html135
-rw-r--r--httemplate/view/cust_main/packages/status.html484
-rw-r--r--httemplate/view/cust_main/payment_history.html445
-rw-r--r--httemplate/view/cust_main/payment_history/credit.html156
-rw-r--r--httemplate/view/cust_main/payment_history/invoice.html45
-rw-r--r--httemplate/view/cust_main/payment_history/payment.html225
-rw-r--r--httemplate/view/cust_main/payment_history/refund.html50
-rw-r--r--httemplate/view/cust_main/payment_history/statement.html34
-rw-r--r--httemplate/view/cust_main/payment_history/voided_payment.html56
-rw-r--r--httemplate/view/cust_main/tickets.html102
-rw-r--r--httemplate/view/cust_pay.html162
-rw-r--r--httemplate/view/cust_pay_void.html1
-rw-r--r--httemplate/view/cust_refund.html142
-rwxr-xr-xhttemplate/view/cust_statement-pdf.cgi28
-rwxr-xr-xhttemplate/view/cust_statement.html79
-rw-r--r--httemplate/view/cust_svc.cgi23
-rw-r--r--httemplate/view/elements/svc_Common.html167
-rw-r--r--httemplate/view/elements/svc_export_settings.html34
-rw-r--r--httemplate/view/elements/tr.html9
-rw-r--r--httemplate/view/logo.cgi47
-rw-r--r--httemplate/view/prospect_main.html92
-rw-r--r--httemplate/view/svc_Common.html29
-rwxr-xr-xhttemplate/view/svc_acct.cgi141
-rw-r--r--httemplate/view/svc_acct/basics.html136
-rw-r--r--httemplate/view/svc_acct/change_svc.html21
-rw-r--r--httemplate/view/svc_acct/change_svc_form.html23
-rw-r--r--httemplate/view/svc_acct/communigate.html77
-rw-r--r--httemplate/view/svc_acct/hosting.html38
-rw-r--r--httemplate/view/svc_acct/radius_usage.html77
-rw-r--r--httemplate/view/svc_acct/usage.html27
-rw-r--r--httemplate/view/svc_broadband.cgi219
-rwxr-xr-xhttemplate/view/svc_domain.cgi82
-rw-r--r--httemplate/view/svc_domain/acct_defaults.html141
-rw-r--r--httemplate/view/svc_domain/basics.html151
-rw-r--r--httemplate/view/svc_domain/dns.html94
-rw-r--r--httemplate/view/svc_external.cgi65
-rwxr-xr-xhttemplate/view/svc_forward.cgi121
-rw-r--r--httemplate/view/svc_mailinglist.cgi71
-rw-r--r--httemplate/view/svc_phone.cgi153
-rw-r--r--httemplate/view/svc_www.cgi106
-rw-r--r--init.d/freeside-init119
-rw-r--r--rpm/INSTALL4
-rw-r--r--rpm/freeside-selfservice.conf11
-rw-r--r--rpm/freeside.spec492
-rw-r--r--rpm/freeside.sysconfig5
-rwxr-xr-xrpm/rpm2Bundle111
-rw-r--r--rt/.gitignore42
-rw-r--r--rt/FREESIDE_MODIFIED50
-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/Makefile30
-rw-r--r--rt/Makefile.in2
-rwxr-xr-xrt/bin/mason_handler.fcgi2
-rwxr-xr-xrt/bin/mason_handler.scgi2
-rw-r--r--rt/bin/mason_handler.svc2
-rwxr-xr-xrt/bin/rt2586
-rw-r--r--rt/bin/rt-commit-handler2
-rw-r--r--rt/bin/rt-commit-handler.in846
-rw-r--r--rt/bin/rt-crontool2
-rwxr-xr-xrt/bin/rt-mailgate2
-rwxr-xr-xrt/bin/standalone_httpd186
-rwxr-xr-xrt/bin/webmux.pl176
-rw-r--r--rt/config256
-rw-r--r--rt/config.layout188
-rw-r--r--rt/config.layout.in128
-rw-r--r--rt/config.log437
-rw-r--r--rt/config.pld21
-rw-r--r--rt/etc/RT_Config.pm23
-rw-r--r--rt/etc/RT_Config.pm.in11
-rw-r--r--rt/etc/RT_SiteConfig.pm35
-rw-r--r--rt/etc/rt.spec137
-rw-r--r--rt/etc/schema.Oracle399
-rwxr-xr-xrt/etc/schema.mysql463
-rw-r--r--rt/etc/upgrade/2.1.71211
-rw-r--r--rt/html/Admin/CustomFields/GroupRights.html119
-rw-r--r--rt/html/Admin/CustomFields/Modify.html258
-rw-r--r--rt/html/Admin/CustomFields/Objects.html147
-rw-r--r--rt/html/Admin/CustomFields/UserRights.html170
-rw-r--r--rt/html/Admin/CustomFields/index.html93
-rw-r--r--rt/html/Admin/Elements/AddCustomFieldValue74
-rw-r--r--rt/html/Admin/Elements/ConfigureMyRT80
-rw-r--r--rt/html/Admin/Elements/CreateUserCalled50
-rw-r--r--rt/html/Admin/Elements/CustomFieldTabs118
-rw-r--r--rt/html/Admin/Elements/EditCustomField159
-rw-r--r--rt/html/Admin/Elements/EditCustomFieldValues96
-rw-r--r--rt/html/Admin/Elements/EditCustomFields205
-rw-r--r--rt/html/Admin/Elements/EditQueueWatchers78
-rw-r--r--rt/html/Admin/Elements/EditScrip183
-rw-r--r--rt/html/Admin/Elements/EditScrips125
-rw-r--r--rt/html/Admin/Elements/EditTemplates128
-rw-r--r--rt/html/Admin/Elements/EditUserComments56
-rwxr-xr-xrt/html/Admin/Elements/GlobalCustomFieldTabs95
-rw-r--r--rt/html/Admin/Elements/GroupTabs102
-rw-r--r--rt/html/Admin/Elements/Header52
-rw-r--r--rt/html/Admin/Elements/ListGlobalCustomFields61
-rw-r--r--rt/html/Admin/Elements/ListGlobalScrips76
-rw-r--r--rt/html/Admin/Elements/ModifyQueue78
-rw-r--r--rt/html/Admin/Elements/ModifyTemplate84
-rw-r--r--rt/html/Admin/Elements/ModifyUser99
-rw-r--r--rt/html/Admin/Elements/ObjectCustomFields111
-rw-r--r--rt/html/Admin/Elements/PickCustomFields98
-rw-r--r--rt/html/Admin/Elements/PickObjects81
-rw-r--r--rt/html/Admin/Elements/QueueRightsForUser64
-rw-r--r--rt/html/Admin/Elements/QueueTabs120
-rw-r--r--rt/html/Admin/Elements/SelectCustomFieldLookupType60
-rw-r--r--rt/html/Admin/Elements/SelectCustomFieldType60
-rw-r--r--rt/html/Admin/Elements/SelectGroups62
-rw-r--r--rt/html/Admin/Elements/SelectModifyGroup57
-rw-r--r--rt/html/Admin/Elements/SelectModifyQueue57
-rw-r--r--rt/html/Admin/Elements/SelectModifyUser73
-rw-r--r--rt/html/Admin/Elements/SelectNewGroupMembers99
-rw-r--r--rt/html/Admin/Elements/SelectRights118
-rw-r--r--rt/html/Admin/Elements/SelectScrip72
-rw-r--r--rt/html/Admin/Elements/SelectScripAction73
-rw-r--r--rt/html/Admin/Elements/SelectScripCondition72
-rw-r--r--rt/html/Admin/Elements/SelectSingleOrMultiple67
-rw-r--r--rt/html/Admin/Elements/SelectStage66
-rw-r--r--rt/html/Admin/Elements/SelectTemplate87
-rw-r--r--rt/html/Admin/Elements/SelectUsers64
-rw-r--r--rt/html/Admin/Elements/SystemTabs97
-rw-r--r--rt/html/Admin/Elements/Tabs93
-rwxr-xr-xrt/html/Admin/Elements/ToolTabs80
-rw-r--r--rt/html/Admin/Elements/UserTabs113
-rw-r--r--rt/html/Admin/Global/CustomField.html86
-rw-r--r--rt/html/Admin/Global/CustomFields.html69
-rw-r--r--rt/html/Admin/Global/CustomFields/Groups.html58
-rwxr-xr-xrt/html/Admin/Global/CustomFields/Queue-Tickets.html58
-rwxr-xr-xrt/html/Admin/Global/CustomFields/Queue-Transactions.html58
-rw-r--r--rt/html/Admin/Global/CustomFields/Users.html58
-rw-r--r--rt/html/Admin/Global/CustomFields/index.html93
-rw-r--r--rt/html/Admin/Global/GroupRights.html123
-rw-r--r--rt/html/Admin/Global/MyRT.html111
-rw-r--r--rt/html/Admin/Global/Scrip.html87
-rw-r--r--rt/html/Admin/Global/Scrips.html77
-rw-r--r--rt/html/Admin/Global/Template.html122
-rw-r--r--rt/html/Admin/Global/Templates.html77
-rw-r--r--rt/html/Admin/Global/UserRights.html103
-rw-r--r--rt/html/Admin/Global/index.html94
-rw-r--r--rt/html/Admin/Groups/CustomFields.html48
-rw-r--r--rt/html/Admin/Groups/GroupRights.html119
-rw-r--r--rt/html/Admin/Groups/History.html68
-rw-r--r--rt/html/Admin/Groups/Members.html168
-rw-r--r--rt/html/Admin/Groups/Modify.html174
-rw-r--r--rt/html/Admin/Groups/UserRights.html116
-rw-r--r--rt/html/Admin/Groups/index.html113
-rw-r--r--rt/html/Admin/Queues/CustomField.html87
-rw-r--r--rt/html/Admin/Queues/CustomFields.html72
-rw-r--r--rt/html/Admin/Queues/GroupRights.html134
-rw-r--r--rt/html/Admin/Queues/Modify.html193
-rw-r--r--rt/html/Admin/Queues/People.html210
-rw-r--r--rt/html/Admin/Queues/Scrip.html100
-rw-r--r--rt/html/Admin/Queues/Scrips.html87
-rw-r--r--rt/html/Admin/Queues/Template.html130
-rw-r--r--rt/html/Admin/Queues/Templates.html81
-rw-r--r--rt/html/Admin/Queues/UserRights.html114
-rw-r--r--rt/html/Admin/Queues/index.html86
-rw-r--r--rt/html/Admin/Tools/Configuration.html100
-rw-r--r--rt/html/Admin/Tools/index.html55
-rw-r--r--rt/html/Admin/Users/CustomFields.html71
-rw-r--r--rt/html/Admin/Users/History.html68
-rw-r--r--rt/html/Admin/Users/Memberships.html67
-rw-r--r--rt/html/Admin/Users/Modify.html433
-rw-r--r--rt/html/Admin/Users/MyRT.html132
-rw-r--r--rt/html/Admin/Users/Prefs.html122
-rw-r--r--rt/html/Admin/Users/index.html115
-rw-r--r--rt/html/Admin/autohandler53
-rw-r--r--rt/html/Admin/index.html101
-rw-r--r--rt/html/Approvals/Display.html72
-rw-r--r--rt/html/Approvals/Elements/Approve94
-rw-r--r--rt/html/Approvals/Elements/PendingMyApproval111
-rw-r--r--rt/html/Approvals/Elements/ShowDependency109
-rw-r--r--rt/html/Approvals/Elements/Tabs58
-rw-r--r--rt/html/Approvals/index.html90
-rw-r--r--rt/html/Callbacks/ActivityReports/Elements/Tabs/Default7
-rw-r--r--rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default71
-rw-r--r--rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions7
-rw-r--r--rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default13
-rw-r--r--rt/html/Callbacks/kStatistics/Elements/Tabs/Default11
-rw-r--r--rt/html/Developer/CronTool/autohandler9
-rw-r--r--rt/html/Developer/CronTool/index.html116
-rw-r--r--rt/html/Download/CustomFieldValue/dhandler77
-rw-r--r--rt/html/Download/Tabular/dhandler76
-rw-r--r--rt/html/Elements/BevelBoxRaisedEnd50
-rw-r--r--rt/html/Elements/BevelBoxRaisedStart50
-rw-r--r--rt/html/Elements/Callback92
-rw-r--r--rt/html/Elements/Checkbox63
-rw-r--r--rt/html/Elements/CollectionAsTable/Header125
-rw-r--r--rt/html/Elements/CollectionAsTable/ParseFormat106
-rw-r--r--rt/html/Elements/CollectionAsTable/Row117
-rw-r--r--rt/html/Elements/CreateTicket50
-rw-r--r--rt/html/Elements/EditCustomField99
-rw-r--r--rt/html/Elements/EditCustomFieldBinary62
-rw-r--r--rt/html/Elements/EditCustomFieldCombobox68
-rw-r--r--rt/html/Elements/EditCustomFieldFreeform74
-rw-r--r--rt/html/Elements/EditCustomFieldImage62
-rw-r--r--rt/html/Elements/EditCustomFieldSelect128
-rw-r--r--rt/html/Elements/EditCustomFieldText67
-rw-r--r--rt/html/Elements/EditCustomFieldWikitext67
-rwxr-xr-xrt/html/Elements/EditLinks177
-rw-r--r--rt/html/Elements/EmailInput47
-rw-r--r--rt/html/Elements/Error86
-rw-r--r--rt/html/Elements/Footer84
-rw-r--r--rt/html/Elements/GotoTicket48
-rw-r--r--rt/html/Elements/Header133
-rw-r--r--rt/html/Elements/ListActions65
-rw-r--r--rt/html/Elements/Login138
-rw-r--r--rt/html/Elements/Logo56
-rw-r--r--rt/html/Elements/Menu134
-rw-r--r--rt/html/Elements/MessageBox74
-rw-r--r--rt/html/Elements/MyAdminQueues54
-rw-r--r--rt/html/Elements/MyRT100
-rwxr-xr-xrt/html/Elements/MyReminders73
-rw-r--r--rt/html/Elements/MyRequests49
-rw-r--r--rt/html/Elements/MySupportQueues54
-rw-r--r--rt/html/Elements/MyTickets49
-rw-r--r--rt/html/Elements/PageLayout237
-rw-r--r--rt/html/Elements/QueryString63
-rw-r--r--rt/html/Elements/QueueSummary92
-rw-r--r--rt/html/Elements/QuickCreate71
-rw-r--r--rt/html/Elements/Quicksearch61
-rw-r--r--rt/html/Elements/RT__Ticket/ColumnMap319
-rw-r--r--rt/html/Elements/Refresh69
-rw-r--r--rt/html/Elements/RefreshHomepage51
-rw-r--r--rt/html/Elements/ScrubHTML73
-rw-r--r--rt/html/Elements/Section51
-rw-r--r--rt/html/Elements/SelectAttachmentField56
-rw-r--r--rt/html/Elements/SelectBoolean71
-rw-r--r--rt/html/Elements/SelectCustomFieldOperator64
-rw-r--r--rt/html/Elements/SelectCustomFieldValue65
-rw-r--r--rt/html/Elements/SelectDate75
-rw-r--r--rt/html/Elements/SelectDateRelation60
-rw-r--r--rt/html/Elements/SelectDateType60
-rw-r--r--rt/html/Elements/SelectEqualityOperator64
-rw-r--r--rt/html/Elements/SelectGroups62
-rw-r--r--rt/html/Elements/SelectLang80
-rw-r--r--rt/html/Elements/SelectLinkType61
-rw-r--r--rt/html/Elements/SelectMatch82
-rw-r--r--rt/html/Elements/SelectNewTicketQueue50
-rw-r--r--rt/html/Elements/SelectOwner110
-rw-r--r--rt/html/Elements/SelectQueue97
-rw-r--r--rt/html/Elements/SelectResultsPerPage68
-rw-r--r--rt/html/Elements/SelectSortOrder65
-rw-r--r--rt/html/Elements/SelectStatus67
-rw-r--r--rt/html/Elements/SelectTicketSortBy62
-rw-r--r--rt/html/Elements/SelectTicketTypes58
-rwxr-xr-xrt/html/Elements/SelectTimeUnits57
-rw-r--r--rt/html/Elements/SelectUsers62
-rw-r--r--rt/html/Elements/SelectWatcherType71
-rw-r--r--rt/html/Elements/SetupSessionCookie133
-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/ShowCustomFieldBinary51
-rw-r--r--rt/html/Elements/ShowCustomFieldImage53
-rw-r--r--rt/html/Elements/ShowCustomFieldWikitext58
-rw-r--r--rt/html/Elements/ShowCustomFields115
-rw-r--r--rt/html/Elements/ShowLink64
-rwxr-xr-xrt/html/Elements/ShowLinks112
-rw-r--r--rt/html/Elements/ShowMemberships88
-rw-r--r--rt/html/Elements/ShowSearch125
-rw-r--r--rt/html/Elements/SimpleSearch51
-rw-r--r--rt/html/Elements/Submit86
-rw-r--r--rt/html/Elements/Tabs122
-rw-r--r--rt/html/Elements/TicketList178
-rw-r--r--rt/html/Elements/TitleBox51
-rw-r--r--rt/html/Elements/TitleBoxEnd51
-rw-r--r--rt/html/Elements/TitleBoxStart51
-rw-r--r--rt/html/Elements/ValidateCustomFields81
-rw-r--r--rt/html/Elements/ViewUser51
-rw-r--r--rt/html/Helpers/CalPopup.html129
-rw-r--r--rt/html/Helpers/EmailAutocomplete47
-rw-r--r--rt/html/NoAuth/Logout.html74
-rw-r--r--rt/html/NoAuth/Reminder.html50
-rw-r--r--rt/html/NoAuth/css/3.4-compat/body.css75
-rw-r--r--rt/html/NoAuth/css/3.4-compat/footer.css61
-rw-r--r--rt/html/NoAuth/css/3.4-compat/forms.css104
-rw-r--r--rt/html/NoAuth/css/3.4-compat/header.css88
-rw-r--r--rt/html/NoAuth/css/3.4-compat/login.css54
-rw-r--r--rt/html/NoAuth/css/3.4-compat/main.css69
-rw-r--r--rt/html/NoAuth/css/3.4-compat/misc.css49
-rw-r--r--rt/html/NoAuth/css/3.4-compat/nav.css106
-rw-r--r--rt/html/NoAuth/css/3.4-compat/quickbar.css82
-rw-r--r--rt/html/NoAuth/css/3.4-compat/ticket.css50
-rw-r--r--rt/html/NoAuth/css/3.4-compat/titlebox.css103
-rw-r--r--rt/html/NoAuth/css/3.4-compat/transactions.css83
-rw-r--r--rt/html/NoAuth/css/3.5-default/approvals.css97
-rwxr-xr-xrt/html/NoAuth/css/3.5-default/body.css81
-rw-r--r--rt/html/NoAuth/css/3.5-default/footer.css91
-rwxr-xr-xrt/html/NoAuth/css/3.5-default/forms.css136
-rw-r--r--rt/html/NoAuth/css/3.5-default/header.css152
-rw-r--r--rt/html/NoAuth/css/3.5-default/login.css85
-rw-r--r--rt/html/NoAuth/css/3.5-default/logo.css60
-rw-r--r--rt/html/NoAuth/css/3.5-default/main.css61
-rwxr-xr-xrt/html/NoAuth/css/3.5-default/misc.css91
-rw-r--r--rt/html/NoAuth/css/3.5-default/nav.css163
-rw-r--r--rt/html/NoAuth/css/3.5-default/quickbar.css98
-rw-r--r--rt/html/NoAuth/css/3.5-default/ticket.css57
-rw-r--r--rt/html/NoAuth/css/3.5-default/titlebox.css179
-rwxr-xr-xrt/html/NoAuth/css/3.5-default/transactions.css146
-rw-r--r--rt/html/NoAuth/css/autohandler53
-rw-r--r--rt/html/NoAuth/css/dhandler77
-rw-r--r--rt/html/NoAuth/css/print.css85
-rw-r--r--rt/html/NoAuth/images/autohandler28
-rw-r--r--rt/html/NoAuth/images/back_home.gifbin330 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/bplogo.gifbin755 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cb-light.gifbin186 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cb.gifbin163 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cbr-b2g.gifbin135 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cbr-b2lb.gifbin137 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cbr-gray.gifbin137 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cbr-trans.gifbin183 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/cbr.gifbin188 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ct-light.gifbin162 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ct.gifbin162 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ctr-b2g.gifbin136 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ctr-b2lb.gifbin114 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ctr-gray.gifbin138 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ctr-trans.gifbin182 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/ctr.gifbin188 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/dark-arrow-up.pngbin346 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/dark-arrow.pngbin337 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/fieldbg-autocomplete.gifbin1164 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/light-arrow-up.pngbin348 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/light-arrow.pngbin340 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/rolldown-arrow.gifbin83 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/rolldown-arrow.pngbin259 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/css/rollup-arrow.gifbin82 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/favicon.pngbin335 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/head_requestracker.gifbin1233 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/rt.jpgbin917 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/space.gifbin43 -> 0 bytes
-rw-r--r--rt/html/NoAuth/images/squares_blue.gifbin219 -> 0 bytes
-rw-r--r--rt/html/NoAuth/js/ahah.js80
-rw-r--r--rt/html/NoAuth/js/autohandler53
-rw-r--r--rt/html/NoAuth/js/cascaded.js66
-rw-r--r--rt/html/NoAuth/js/class.js62
-rw-r--r--rt/html/NoAuth/js/combobox.js265
-rw-r--r--rt/html/NoAuth/js/list.js159
-rw-r--r--rt/html/NoAuth/js/titlebox-state.js83
-rw-r--r--rt/html/NoAuth/js/util.js250
-rw-r--r--rt/html/NoAuth/printrt.css77
-rw-r--r--rt/html/NoAuth/webrt.css628
-rw-r--r--rt/html/Prefs/Elements/Tabs72
-rw-r--r--rt/html/Prefs/MyRT.html151
-rw-r--r--rt/html/Prefs/Quicksearch.html96
-rw-r--r--rt/html/Prefs/Search.html108
-rw-r--r--rt/html/Prefs/SearchOptions.html114
-rw-r--r--rt/html/REST/1.0/Forms/queue/default170
-rw-r--r--rt/html/REST/1.0/Forms/queue/ns62
-rw-r--r--rt/html/REST/1.0/Forms/ticket/attachments135
-rwxr-xr-xrt/html/REST/1.0/Forms/ticket/comment152
-rw-r--r--rt/html/REST/1.0/Forms/ticket/default345
-rw-r--r--rt/html/REST/1.0/Forms/ticket/history200
-rw-r--r--rt/html/REST/1.0/Forms/ticket/links172
-rwxr-xr-xrt/html/REST/1.0/Forms/ticket/merge96
-rwxr-xr-xrt/html/REST/1.0/Forms/ticket/take135
-rw-r--r--rt/html/REST/1.0/Forms/transaction/default143
-rw-r--r--rt/html/REST/1.0/Forms/user/default188
-rw-r--r--rt/html/REST/1.0/Forms/user/ns65
-rw-r--r--rt/html/REST/1.0/NoAuth/mail-gateway84
-rw-r--r--rt/html/REST/1.0/autohandler56
-rw-r--r--rt/html/REST/1.0/dhandler316
-rw-r--r--rt/html/REST/1.0/logout51
-rw-r--r--rt/html/REST/1.0/search/dhandler56
-rw-r--r--rt/html/REST/1.0/search/ticket158
-rw-r--r--rt/html/REST/1.0/ticket/comment177
-rw-r--r--rt/html/REST/1.0/ticket/link123
-rw-r--r--rt/html/REST/1.0/ticket/merge102
-rwxr-xr-xrt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart39
-rwxr-xr-xrt/html/RTx/Statistics/CallsMultiQueue/index.html330
-rwxr-xr-xrt/html/RTx/Statistics/CallsQueueDay/Elements/Chart29
-rw-r--r--rt/html/RTx/Statistics/CallsQueueDay/Results.tsv191
-rwxr-xr-xrt/html/RTx/Statistics/CallsQueueDay/index.html275
-rwxr-xr-xrt/html/RTx/Statistics/DayOfWeek/Elements/Chart26
-rwxr-xr-xrt/html/RTx/Statistics/DayOfWeek/index.html155
-rwxr-xr-xrt/html/RTx/Statistics/DurationAsString18
-rw-r--r--rt/html/RTx/Statistics/Elements/CollectionAsTable/Header126
-rw-r--r--rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat109
-rw-r--r--rt/html/RTx/Statistics/Elements/CollectionAsTable/Row112
-rw-r--r--rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox103
-rw-r--r--rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage5
-rw-r--r--rt/html/RTx/Statistics/Elements/DateSelectRow55
-rwxr-xr-xrt/html/RTx/Statistics/Elements/DurationAsString18
-rw-r--r--rt/html/RTx/Statistics/Elements/GraphBox27
-rwxr-xr-xrt/html/RTx/Statistics/Elements/SelectMultiQueue81
-rw-r--r--rt/html/RTx/Statistics/Elements/StatColumnMap173
-rwxr-xr-xrt/html/RTx/Statistics/Elements/Tabs72
-rw-r--r--rt/html/RTx/Statistics/FAQ/index.html23
-rwxr-xr-xrt/html/RTx/Statistics/OpenStalled/Elements/Chart27
-rw-r--r--rt/html/RTx/Statistics/OpenStalled/Results.tsv114
-rwxr-xr-xrt/html/RTx/Statistics/OpenStalled/index.html188
-rwxr-xr-xrt/html/RTx/Statistics/Resolution/Elements/Chart29
-rw-r--r--rt/html/RTx/Statistics/Resolution/index.html269
-rwxr-xr-xrt/html/RTx/Statistics/TimeToResolve/Elements/Chart23
-rwxr-xr-xrt/html/RTx/Statistics/TimeToResolve/index.html75
-rwxr-xr-xrt/html/RTx/Statistics/UserTest/Elements/Chart28
-rwxr-xr-xrt/html/RTx/Statistics/UserTest/index.html54
-rwxr-xr-xrt/html/RTx/Statistics/index.html59
-rw-r--r--rt/html/Reports/Activity/ActivityDetail.html83
-rw-r--r--rt/html/Reports/Activity/ActivitySummary.html61
-rw-r--r--rt/html/Reports/Activity/Elements/LimitReport23
-rw-r--r--rt/html/Reports/Activity/Elements/MiniPlot57
-rw-r--r--rt/html/Reports/Activity/Elements/PrintFooter7
-rw-r--r--rt/html/Reports/Activity/Elements/PrintHeader32
-rw-r--r--rt/html/Reports/Activity/Elements/ScreenFooter13
-rw-r--r--rt/html/Reports/Activity/Elements/ScreenHeader8
-rw-r--r--rt/html/Reports/Activity/Elements/Tabs52
-rw-r--r--rt/html/Reports/Activity/Elements/Wrapper16
-rw-r--r--rt/html/Reports/Activity/ResolutionComments.html62
-rw-r--r--rt/html/Reports/Activity/ResolutionStatistics.html95
-rw-r--r--rt/html/Reports/Activity/index.html29
-rw-r--r--rt/html/Search/Build.html832
-rw-r--r--rt/html/Search/Bulk.html397
-rw-r--r--rt/html/Search/Chart188
-rw-r--r--rt/html/Search/Chart.html73
-rwxr-xr-xrt/html/Search/Edit.html88
-rw-r--r--rt/html/Search/Elements/BuildFormatString244
-rw-r--r--rt/html/Search/Elements/Chart139
-rw-r--r--rt/html/Search/Elements/DisplayOptions144
-rw-r--r--rt/html/Search/Elements/EditFormat116
-rw-r--r--rt/html/Search/Elements/EditQuery67
-rw-r--r--rt/html/Search/Elements/EditSearches103
-rw-r--r--rt/html/Search/Elements/NewListActions68
-rw-r--r--rt/html/Search/Elements/PickBasics176
-rw-r--r--rt/html/Search/Elements/PickCFs80
-rw-r--r--rt/html/Search/Elements/PickCriteria82
-rw-r--r--rt/html/Search/Elements/PickRestriction142
-rw-r--r--rt/html/Search/Elements/SearchPrivacy55
-rw-r--r--rt/html/Search/Elements/SearchesForObject65
-rw-r--r--rt/html/Search/Elements/SelectAndOr53
-rw-r--r--rt/html/Search/Elements/SelectChartType56
-rw-r--r--rt/html/Search/Elements/SelectGroup67
-rw-r--r--rt/html/Search/Elements/SelectGroupBy63
-rw-r--r--rt/html/Search/Elements/SelectLinks66
-rw-r--r--rt/html/Search/Elements/SelectPersonType84
-rw-r--r--rt/html/Search/Elements/SelectSearchObject60
-rw-r--r--rt/html/Search/Elements/SelectSearchesForObjects69
-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.html113
-rwxr-xr-xrt/html/Search/Results.html177
-rw-r--r--rt/html/Search/Results.rdf87
-rw-r--r--rt/html/Search/Results.tsv134
-rw-r--r--rt/html/Search/Simple.html107
-rw-r--r--rt/html/SelfService/Attachment/dhandler51
-rw-r--r--rt/html/SelfService/Closed.html56
-rw-r--r--rt/html/SelfService/Create.html177
-rwxr-xr-xrt/html/SelfService/CreateTicketInQueue.html63
-rw-r--r--rt/html/SelfService/Display.html235
-rw-r--r--rt/html/SelfService/Elements/GotoTicket48
-rw-r--r--rt/html/SelfService/Elements/Header49
-rw-r--r--rt/html/SelfService/Elements/MyRequests84
-rw-r--r--rt/html/SelfService/Elements/Tabs113
-rw-r--r--rt/html/SelfService/Error.html70
-rw-r--r--rt/html/SelfService/Prefs.html92
-rw-r--r--rt/html/SelfService/Update.html129
-rw-r--r--rt/html/SelfService/index.html54
-rw-r--r--rt/html/Ticket/Attachment/dhandler94
-rw-r--r--rt/html/Ticket/Create.html406
-rw-r--r--rt/html/Ticket/Display.html184
-rw-r--r--rt/html/Ticket/Elements/AddWatchers123
-rw-r--r--rt/html/Ticket/Elements/BulkLinks77
-rw-r--r--rt/html/Ticket/Elements/EditBasics117
-rw-r--r--rt/html/Ticket/Elements/EditCustomField57
-rw-r--r--rt/html/Ticket/Elements/EditCustomFields110
-rw-r--r--rt/html/Ticket/Elements/EditDates77
-rw-r--r--rt/html/Ticket/Elements/EditLinks133
-rw-r--r--rt/html/Ticket/Elements/EditPeople93
-rw-r--r--rt/html/Ticket/Elements/EditWatchers81
-rwxr-xr-xrt/html/Ticket/Elements/FindAttachments95
-rwxr-xr-xrt/html/Ticket/Elements/LoadTextAttachments94
-rwxr-xr-xrt/html/Ticket/Elements/PreviewScrips133
-rw-r--r--rt/html/Ticket/Elements/Reminders168
-rw-r--r--rt/html/Ticket/Elements/ShowAttachments104
-rw-r--r--rt/html/Ticket/Elements/ShowBasics85
-rw-r--r--rt/html/Ticket/Elements/ShowCustomFields51
-rw-r--r--rt/html/Ticket/Elements/ShowDates86
-rw-r--r--rt/html/Ticket/Elements/ShowDependencies65
-rw-r--r--rt/html/Ticket/Elements/ShowGroupMembers63
-rw-r--r--rt/html/Ticket/Elements/ShowHistory166
-rw-r--r--rt/html/Ticket/Elements/ShowLink40
-rw-r--r--rt/html/Ticket/Elements/ShowLinks87
-rw-r--r--rt/html/Ticket/Elements/ShowMemberOf57
-rw-r--r--rt/html/Ticket/Elements/ShowMembers68
-rw-r--r--rt/html/Ticket/Elements/ShowMessageHeaders92
-rw-r--r--rt/html/Ticket/Elements/ShowMessageStanza84
-rw-r--r--rt/html/Ticket/Elements/ShowPeople68
-rw-r--r--rt/html/Ticket/Elements/ShowQueue56
-rw-r--r--rt/html/Ticket/Elements/ShowReferences72
-rw-r--r--rt/html/Ticket/Elements/ShowRequestor89
-rw-r--r--rt/html/Ticket/Elements/ShowSummary114
-rw-r--r--rt/html/Ticket/Elements/ShowTime55
-rw-r--r--rt/html/Ticket/Elements/ShowTransaction197
-rw-r--r--rt/html/Ticket/Elements/ShowTransactionAttachments209
-rw-r--r--rt/html/Ticket/Elements/ShowUserEntry61
-rw-r--r--rt/html/Ticket/Elements/Tabs248
-rw-r--r--rt/html/Ticket/History.html89
-rw-r--r--rt/html/Ticket/Modify.html91
-rw-r--r--rt/html/Ticket/ModifyAll.html225
-rw-r--r--rt/html/Ticket/ModifyDates.html77
-rw-r--r--rt/html/Ticket/ModifyLinks.html82
-rw-r--r--rt/html/Ticket/ModifyPeople.html94
-rwxr-xr-xrt/html/Ticket/Reminders.html71
-rw-r--r--rt/html/Ticket/ShowEmailRecord.html73
-rw-r--r--rt/html/Ticket/Update.html228
-rw-r--r--rt/html/Tools/Elements/Tabs84
-rw-r--r--rt/html/Tools/MyDay.html117
-rw-r--r--rt/html/Tools/Offline.html166
-rw-r--r--rt/html/Tools/Reports/CreatedByDates.html94
-rw-r--r--rt/html/Tools/Reports/Elements/Tabs89
-rw-r--r--rt/html/Tools/Reports/ResolvedByDates.html95
-rw-r--r--rt/html/Tools/Reports/ResolvedByOwner.html70
-rw-r--r--rt/html/Tools/Reports/index.html50
-rw-r--r--rt/html/Tools/index.html52
-rw-r--r--rt/html/User/Delegation.html107
-rw-r--r--rt/html/User/Elements/DelegateRights109
-rw-r--r--rt/html/User/Elements/GroupTabs84
-rw-r--r--rt/html/User/Elements/Tabs89
-rw-r--r--rt/html/User/Groups/Members.html160
-rw-r--r--rt/html/User/Groups/Modify.html157
-rw-r--r--rt/html/User/Groups/index.html67
-rw-r--r--rt/html/User/Prefs.html289
-rw-r--r--rt/html/Widgets/ComboBox70
-rw-r--r--rt/html/Widgets/SavedSearch158
-rw-r--r--rt/html/Widgets/SelectionBox243
-rw-r--r--rt/html/Widgets/TitleBox54
-rwxr-xr-xrt/html/Widgets/TitleBoxEnd59
-rwxr-xr-xrt/html/Widgets/TitleBoxStart86
-rw-r--r--rt/html/autohandler331
-rw-r--r--rt/html/index.html117
-rw-r--r--rt/html/l52
-rw-r--r--rt/lib/RT.pm80
-rw-r--r--rt/lib/RT.pm.in54
-rw-r--r--rt/lib/RT/Config.pm4
-rw-r--r--rt/lib/RT/Extension/ActivityReports.pm3
-rw-r--r--rt/lib/RT/Groups_Overlay.pm1
-rw-r--r--rt/lib/RT/I18N/en_malkovich.po3973
-rw-r--r--rt/lib/RT/I18N/no.po6563
-rw-r--r--rt/lib/RT/I18N/pt_br.po6528
-rw-r--r--rt/lib/RT/I18N/pt_pt.po5194
-rw-r--r--rt/lib/RT/I18N/zh_cn.po8423
-rw-r--r--rt/lib/RT/I18N/zh_tw.po8360
-rw-r--r--rt/lib/RT/Interface/Web_Vendor.pm201
-rwxr-xr-xrt/lib/RT/Record.pm31
-rw-r--r--rt/lib/RT/SearchBuilder.pm2
-rw-r--r--rt/lib/RT/TicketCustomFieldValue.pm308
-rw-r--r--rt/lib/RT/TicketCustomFieldValue_Overlay.pm74
-rw-r--r--rt/lib/RT/TicketCustomFieldValues.pm137
-rw-r--r--rt/lib/RT/TicketCustomFieldValues_Overlay.pm108
-rw-r--r--rt/lib/RT/Ticket_Overlay.pm82
-rw-r--r--rt/lib/RT/Transaction_Overlay.pm5
-rw-r--r--rt/lib/RT/URI/freeside.pm297
-rw-r--r--rt/lib/RT/URI/freeside/Internal.pm145
-rw-r--r--rt/lib/RT/URI/freeside/XMLRPC.pm122
-rw-r--r--rt/lib/RT/User_Overlay.pm262
-rw-r--r--rt/lib/RT/Users_Overlay.pm1
-rw-r--r--rt/lib/RTx/Calendar.pm233
-rwxr-xr-xrt/lib/RTx/Statistics.pm239
-rw-r--r--rt/lib/RTx/WebCronTool.pm41
-rw-r--r--rt/lib/t/00smoke.t13
-rw-r--r--rt/lib/t/00smoke.t.in14
-rw-r--r--rt/lib/t/01harness.t.in12
-rw-r--r--rt/lib/t/02regression.t7
-rw-r--r--rt/lib/t/02regression.t.in47
-rw-r--r--rt/lib/t/03web.pl78
-rw-r--r--rt/lib/t/03web.pl.in170
-rw-r--r--rt/lib/t/04_send_email.pl25
-rw-r--r--rt/lib/t/04_send_email.pl.in506
-rw-r--r--rt/lib/t/05cronsupport.pl.in84
-rw-r--r--rt/lib/t/create_data.pl136
-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/lorem-ipsum5
-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/rt-send-cc5
-rw-r--r--rt/lib/t/data/russian-subject-no-content-type42
-rw-r--r--rt/lib/t/data/subject-with-folding-ws10
-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/data/very-long-subject12
-rw-r--r--rt/lib/t/regression/00-mason-syntax.t43
-rw-r--r--rt/lib/t/regression/00placeholder1
-rw-r--r--rt/lib/t/regression/01ticket_link_searching.t159
-rw-r--r--rt/lib/t/regression/02basic_web.t159
-rw-r--r--rt/lib/t/regression/03web_compiliation_errors.t64
-rw-r--r--rt/lib/t/regression/04send_email.t549
-rw-r--r--rt/lib/t/regression/05cronsupport.t91
-rw-r--r--rt/lib/t/regression/06-mime_decoding.t64
-rw-r--r--rt/lib/t/regression/06mailgateway.t663
-rw-r--r--rt/lib/t/regression/07acl.t138
-rw-r--r--rt/lib/t/regression/07rights.t140
-rw-r--r--rt/lib/t/regression/08web_cf_access.t119
-rw-r--r--rt/lib/t/regression/09record_cf_api.t204
-rw-r--r--rt/lib/t/regression/10merge.t72
-rw-r--r--rt/lib/t/regression/11-template-insert.t27
-rw-r--r--rt/lib/t/regression/12-search.t281
-rw-r--r--rt/lib/t/regression/13-attribute-tests.t87
-rw-r--r--rt/lib/t/regression/14linking.t243
-rw-r--r--rt/lib/t/regression/14merge.t31
-rw-r--r--rt/lib/t/regression/15cf_combo_cascade.t49
-rw-r--r--rt/lib/t/regression/15cf_pattern.t54
-rw-r--r--rt/lib/t/regression/15cf_single_values_are_single.t39
-rw-r--r--rt/lib/t/regression/16-transaction_cf_tests.t61
-rw-r--r--rt/lib/t/regression/17custom_search.t88
-rw-r--r--rt/lib/t/regression/17multiple_deleg_revocation.t135
-rw-r--r--rt/lib/t/regression/18custom_frontpage.t75
-rw-r--r--rt/lib/t/regression/18stale_delegations_cleanup.t458
-rw-r--r--rt/lib/t/regression/19-rtname.t38
-rw-r--r--rt/lib/t/regression/19quicksearch.t39
-rw-r--r--rt/lib/t/regression/20-sort-by-queue.t103
-rw-r--r--rt/lib/t/regression/20-sort-by-requestor.t143
-rw-r--r--rt/lib/t/regression/20-sort-by-user.t155
-rw-r--r--rt/lib/t/regression/20savedsearch.t180
-rw-r--r--rt/lib/t/regression/21query-builder.t247
-rw-r--r--rt/lib/t/regression/22search_tix_by_txn.t38
-rw-r--r--rt/lib/t/regression/22search_tix_by_watcher.t279
-rw-r--r--rt/lib/t/regression/23-batch-upload-csv.t47
-rw-r--r--rt/lib/t/regression/23-web_attachments.t60
-rw-r--r--rt/lib/t/regression/23cfsort-freeform-multiple.t137
-rw-r--r--rt/lib/t/regression/23cfsort-freeform-single.t191
-rw-r--r--rt/lib/t/regression/23cfsort.t177
-rw-r--r--rt/lib/t/regression/24-watchers.t157
-rw-r--r--rt/lib/t/regression/24pawsort.t104
-rw-r--r--rt/lib/t/regression/25scrip_order.t57
-rw-r--r--rt/lib/t/regression/26command_line.t450
-rw-r--r--rt/lib/t/regression/27verp.t9
-rw-r--r--rt/lib/t/regression/mime_tests19
-rw-r--r--rt/lib/t/setup_regression.t34
-rw-r--r--rt/sbin/extract_pod_tests159
-rw-r--r--rt/sbin/regression_harness56
-rwxr-xr-xrt/sbin/rt-dump-database199
-rw-r--r--rt/sbin/rt-setup-database474
-rw-r--r--rt/sbin/rt-setup-database.in4
-rw-r--r--rt/sbin/rt-test-dependencies600
-rwxr-xr-xrt/share/html/Admin/Users/Modify.html12
-rw-r--r--rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head2
-rw-r--r--rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default19
-rw-r--r--rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default9
-rw-r--r--rt/share/html/Elements/AddCustomers59
-rw-r--r--rt/share/html/Elements/CalendarEvent129
-rw-r--r--rt/share/html/Elements/CollectionList4
-rw-r--r--rt/share/html/Elements/ColumnMap7
-rw-r--r--rt/share/html/Elements/EditCustomers63
-rwxr-xr-xrt/share/html/Elements/Footer19
-rwxr-xr-xrt/share/html/Elements/Header92
-rw-r--r--rt/share/html/Elements/MyCalendar78
-rwxr-xr-xrt/share/html/Elements/PageLayout12
-rwxr-xr-xrt/share/html/Elements/SelectDate17
-rw-r--r--rt/share/html/NoAuth/Calendar/dhandler159
-rw-r--r--rt/share/html/NoAuth/css/calendar.css75
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/InHeader54
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/admin.css60
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/base.css63
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/boxes.css192
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/collection.css52
-rwxr-xr-xrt/share/html/NoAuth/css/freeside2.1/forms.css242
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/freeside.css9
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/images/dhandler8
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.pngbin0 -> 394 bytes
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/layout.css237
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/login.css82
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/main.css71
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/misc.css87
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/msie.css246
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/msie6.css88
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/nav.css206
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/portlets.css71
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css172
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/ticket-search.css199
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/ticket.css230
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/tools.css56
-rw-r--r--rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css7
-rw-r--r--rt/share/html/NoAuth/images/created.pngbin0 -> 994 bytes
-rw-r--r--rt/share/html/NoAuth/images/created_due.pngbin0 -> 997 bytes
-rw-r--r--rt/share/html/NoAuth/images/due.pngbin0 -> 936 bytes
-rw-r--r--rt/share/html/NoAuth/images/reminder.pngbin0 -> 921 bytes
-rw-r--r--rt/share/html/NoAuth/images/resolved.pngbin0 -> 229 bytes
-rw-r--r--rt/share/html/NoAuth/images/started.pngbin0 -> 934 bytes
-rw-r--r--rt/share/html/NoAuth/images/starts.pngbin0 -> 935 bytes
-rw-r--r--rt/share/html/NoAuth/images/starts_due.pngbin0 -> 173 bytes
-rw-r--r--rt/share/html/NoAuth/images/updated.pngbin0 -> 191 bytes
-rw-r--r--rt/share/html/Prefs/Calendar.html123
-rw-r--r--rt/share/html/Prefs/Elements/CalendarFeed68
-rw-r--r--rt/share/html/Prefs/SearchOptions.html2
-rw-r--r--rt/share/html/Search/Build.html2
-rw-r--r--rt/share/html/Search/Calendar.html238
-rwxr-xr-xrt/share/html/Ticket/Display.html4
-rw-r--r--rt/share/html/Ticket/Elements/AddCustomers52
-rw-r--r--rt/share/html/Ticket/Elements/EditCustomers63
-rw-r--r--rt/share/html/Ticket/Elements/ShowCustomers38
-rwxr-xr-xrt/share/html/Ticket/Elements/ShowSummary7
-rw-r--r--rt/share/html/Ticket/Elements/ShowTransactionAttachments10
-rwxr-xr-xrt/share/html/Ticket/Elements/Tabs4
-rw-r--r--rt/share/html/Ticket/ModifyCustomers.html49
-rwxr-xr-xrt/share/html/User/Prefs.html2
-rwxr-xr-xrt/share/html/Widgets/TitleBoxEnd2
-rwxr-xr-xtest/cgi-test558
-rwxr-xr-xtest/dup-test32
2863 files changed, 348673 insertions, 98559 deletions
diff --git a/AGPL b/AGPL
new file mode 100644
index 0000000..939a6f4
--- /dev/null
+++ b/AGPL
@@ -0,0 +1,662 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license
+for software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are
+designed to take away your freedom to share and change the works. By
+contrast, our General Public Licenses are intended to guarantee your
+freedom to share and change all versions of a program--to make sure it
+remains free software for all its users.
+
+ 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
+them 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.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public
+License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further restriction,
+you may remove that term. If a license document contains a further
+restriction but permits relicensing or conveying under this License, you
+may add to a covered work material governed by the terms of that license
+document, provided that the further restriction does not survive such
+relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have permission
+to link or combine any covered work with a work licensed under version 3
+of the GNU General Public License into a single combined work, and to
+convey the resulting work. The terms of this License will continue to
+apply to the part which is the covered work, but the work with which it is
+combined will remain governed by version 3 of the GNU General Public
+License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero 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 that a certain numbered version of the GNU Affero
+General Public License "or any later version" applies to it, you have
+the option of following the terms and conditions either of that
+numbered version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number
+of the GNU Affero General Public License, you may choose any version
+ever published by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that
+proxy's public statement of acceptance of a version permanently
+authorizes you to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ 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.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ 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
+state 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) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..2702fe8
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1 @@
+See httemplate/docs/credits.html
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..1a98b44
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,35737 @@
+2010-05-25 05:42 ivan
+
+ * httemplate/elements/dashboard-install_welcome.html: adding,
+ though unused
+
+2010-05-25 05:41 ivan
+
+ * httemplate/misc/: rate-import.html, process/rate-import.html:
+ unfinished rate import
+
+2010-05-25 05:40 ivan
+
+ * bin/explain-bill-query: adding
+
+2010-05-25 05:35 ivan
+
+ * ChangeLog, debian/changelog: Updated for 2.1.0
+
+2010-05-25 05:33 ivan
+
+ * Makefile: fix the rel target
+
+2010-05-25 05:30 ivan
+
+ * ChangeLog, rpm/freeside.spec, debian/changelog: Updated for 2.1.0
+
+2010-05-25 05:16 ivan
+
+ * Makefile: its time
+
+2010-05-25 05:14 ivan
+
+ * httemplate/misc/process/payment.cgi: cardfortress bit
+
+2010-05-25 05:14 ivan
+
+ * httemplate/elements/select-pkg_class.html: add showdisabled
+ option
+
+2010-05-25 05:12 ivan
+
+ * httemplate/docs/about.html: 2.1.0
+
+2010-05-25 04:51 ivan
+
+ * rt/: FREESIDE_MODIFIED, share/html/Search/Build.html: wfm to show
+ the add buttons on rt ticket search
+
+2010-05-25 04:41 ivan
+
+ * rt/share/html/NoAuth/css/freeside2.1/: base.css, boxes.css,
+ collection.css, main.css, misc.css, msie.css, portlets.css: merge
+ in web2 changes from 3.8.7 to 3.8.8
+
+2010-05-25 04:11 ivan
+
+ * rt/share/html/Elements/ColumnMap: fix rt/rt/ bad links, RT#7873
+
+2010-05-25 04:07 ivan
+
+ * rt/share/html/Elements/ColumnMap: fix rt/rt/ bad links, RT#7873
+
+2010-05-25 00:53 mark
+
+ * httemplate/: misc/inventory_item-move.cgi,
+ search/inventory_item.html: RT#7010: inventory items can be moved
+ between agents
+
+2010-05-24 23:04 ivan
+
+ * FS/FS/Misc.pm: prevent "Can't locate object method "code" via
+ package "HTML::Mason::Exception" errors
+
+2010-05-24 16:54 mark
+
+ * httemplate/: edit/cust_main_note.cgi,
+ edit/process/cust_main_note.cgi, view/cust_main/notes.html,
+ pref/pref-process.html, pref/pref.html: RT#8224: allow user to
+ disable HTML editor
+
+2010-05-24 09:56 ivan
+
+ * httemplate/search/timeworked.html: fix Pg-ism
+
+2010-05-22 18:59 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cgp_rule.pm, FS/FS/cgp_rule_action.pm,
+ FS/FS/cgp_rule_condition.pm, FS/FS/svc_CGPRule_Mixin.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ FS/FS/part_export/communigate_pro.pm, FS/t/svc_CGPRule_Mixin.t,
+ httemplate/browse/cgp_rule.html: communigate pro rules, RT#7515
+
+2010-05-22 13:00 ivan
+
+ * httemplate/search/svc_acct.cgi: consistent with other places,
+ showing database primary keys other than custnum, invnum is
+ stilly
+
+2010-05-22 12:57 jeff
+
+ * FS/FS/Report/FCC_477.pm, FS/t/Report-FCC_477.t,
+ httemplate/search/477.html,
+ httemplate/search/477partIA_detail.html,
+ httemplate/search/477partIA_summary.html,
+ httemplate/search/477partIIA.html,
+ httemplate/search/477partIIB.html,
+ httemplate/search/477partIV.html,
+ httemplate/search/477partV.html,
+ httemplate/search/477partVI.html,
+ httemplate/search/report_477.html, FS/MANIFEST, FS/FS/Conf.pm,
+ FS/FS/Mason.pm, FS/FS/Schema.pm, FS/FS/cust_pkg.pm,
+ FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/search/elements/metasearch.html,
+ httemplate/search/elements/search-html.html,
+ httemplate/search/elements/search-xml.html,
+ httemplate/search/elements/search.html: improved fcc 477 report
+ #7783
+
+2010-05-22 12:47 ivan
+
+ * httemplate/search/svc_acct.cgi: really hide uid on account search
+
+2010-05-22 12:13 ivan
+
+ * httemplate/search/svc_acct.cgi: hide uid on account search
+
+2010-05-22 11:52 ivan
+
+ * FS/FS/Report/Table/Monthly.pm: fix net sales amount (credits were
+ being applied in wrong month), RT#7502
+
+2010-05-21 16:35 ivan
+
+ * httemplate/search/agent_inventory.html,
+ httemplate/search/inventory_item.html,
+ httemplate/search/report_agent_inventory.html,
+ FS/FS/inventory_class.pm, httemplate/browse/inventory_class.html,
+ httemplate/elements/menu.html: reporting on agent inventory,
+ RT#7010
+
+2010-05-21 14:18 mark
+
+ * FS/FS/cust_bill.pm: typo
+
+2010-05-21 12:57 ivan
+
+ * FS/FS/cust_bill.pm: fix 'Use of uninitialized value' spew,
+ fallout from RT#7266
+
+2010-05-21 12:09 mark
+
+ * FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/report_cust_bill_pkg.html: RT#8504: option to
+ aggregate agents on sales report
+
+2010-05-20 20:48 mark
+
+ * httemplate/edit/part_pkg.cgi: fix typo
+
+2010-05-20 20:42 mark
+
+ * FS/FS/part_pkg/prorate.pm, FS/FS/part_pkg/voip_inbound.pm,
+ httemplate/edit/part_pkg.cgi: fix display bug, RT#8524
+
+2010-05-20 20:28 mark
+
+ * FS/FS/part_pkg/voip_inbound.pm: fix display bug, RT#8524
+
+2010-05-20 17:21 ivan
+
+ * httemplate/edit/elements/edit.html: pass through agent_null to
+ select-table so manual selection from inventory still works,
+ RT#7010
+
+2010-05-20 17:09 ivan
+
+ * FS/FS/inventory_class.pm, FS/FS/svc_Common.pm,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/search/inventory_item.html: agent virt inventory,
+ RT#7010
+
+2010-05-20 15:48 ivan
+
+ * FS/FS/Schema.pm, FS/FS/AccessRight.pm, FS/FS/inventory_item.pm,
+ httemplate/browse/inventory_class.html,
+ httemplate/elements/menu.html,
+ httemplate/misc/inventory_item-import.html,
+ httemplate/search/inventory_item.html,
+ httemplate/search/elements/search.html: agent virt inventory,
+ RT#7010
+
+2010-05-20 03:13 ivan
+
+ * rt/share/html/Elements/TicketList: oops
+
+2010-05-20 02:59 ivan
+
+ * rt/FREESIDE_MODIFIED: update modified file list
+
+2010-05-20 02:53 ivan
+
+ * rt/share/html/Elements/CollectionList: THIS fixes ticketing main
+ layout problems. whew! RT#6640
+
+2010-05-20 02:44 ivan
+
+ * rt/share/html/Elements/TicketList: no...
+
+2010-05-20 02:35 ivan
+
+ * rt/share/html/Elements/TicketList: should fix the ui drain
+ bramage on ticketing main... right?
+
+2010-05-20 01:48 ivan
+
+ * httemplate/: elements/columnstart.html, elements/freeside.css,
+ elements/header.html, elements/tr-justtitle.html,
+ elements/tr-pkg_svc.html, elements/tr-title.html,
+ graph/elements/report.html, misc/email-customers.html,
+ search/cust_tax_exempt.html, search/report_477.html,
+ search/report_cdr.html, search/report_cust_event.html,
+ search/report_cust_main.html, search/report_cust_pay.html,
+ search/report_cust_pkg.html, search/report_cust_refund.html,
+ search/report_h_cust_pay.html, search/report_prepaid_income.html,
+ search/report_prospect_main.html, search/report_receivables.html,
+ search/report_svc_acct.html, search/report_svc_phone.html,
+ search/report_timeworked.html,
+ search/report_unapplied_cust_pay.html,
+ search/elements/cust_main_dayranges.html,
+ search/elements/search.html, view/prospect_main.html: fix table
+ titles for new bg color
+
+2010-05-20 01:17 ivan
+
+ * httemplate/elements/: xmenu.css, xmenu.top.css, menu.html: nicer
+ styling for the menus
+
+2010-05-20 01:10 ivan
+
+ * httemplate/elements/contact.html: uinit
+
+2010-05-19 18:33 mark
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_credit.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm,
+ httemplate/search/report_receivables.cgi: RT#7266: aging report
+ "as of" date now limits applied payments
+
+2010-05-19 17:57 ivan
+
+ * rt/share/html/: NoAuth/css/calendar.css, Search/Calendar.html:
+ fix leaking date css, RT#6467
+
+2010-05-19 16:37 ivan
+
+ * rt/share/html/: NoAuth/css/calendar.css, Search/Calendar.html:
+ calenaring (RT#6467): a little UI cleanup goes a long way
+
+2010-05-19 12:04 ivan
+
+ * rt/: lib/RTx/Calendar.pm, share/html/Search/Calendar.html: week
+ view on calendars from Sunday -> Saturday
+
+2010-05-18 21:05 jeff
+
+ * FS/FS/cust_bill_pkg.pm: avoid spurious bash default usage
+ category charges
+
+2010-05-18 19:32 ivan
+
+ * rt/: lib/RTx/Calendar.pm, share/html/Elements/CalendarEvent,
+ share/html/Elements/MyCalendar,
+ share/html/NoAuth/css/calendar.css,
+ share/html/Prefs/Calendar.html, etc/RT_Config.pm,
+ etc/RT_Config.pm.in,
+ share/html/Callbacks/RTx-Calendar/Elements/Header/Head,
+ share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default,
+ share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default,
+ share/html/NoAuth/Calendar/dhandler,
+ share/html/NoAuth/images/created.png,
+ share/html/NoAuth/images/created_due.png,
+ share/html/NoAuth/images/due.png,
+ share/html/NoAuth/images/reminder.png,
+ share/html/NoAuth/images/resolved.png,
+ share/html/NoAuth/images/started.png,
+ share/html/NoAuth/images/starts.png,
+ share/html/NoAuth/images/starts_due.png,
+ share/html/NoAuth/images/updated.png,
+ share/html/Prefs/Elements/CalendarFeed,
+ share/html/Search/Calendar.html: add RTx::Calendar 0.07
+
+2010-05-18 12:58 ivan
+
+ * rt/lib/: RT.pm, RT.pm.in: fix InitSignalHandlers patch
+
+2010-05-18 12:54 ivan
+
+ * rt/config.layout.in: add fonitdir to freeside layout to avoid
+ install errors
+
+2010-05-18 12:41 ivan
+
+ * httemplate/edit/access_user.html: fix "Re-enter password" label
+ on employee edit
+
+2010-05-18 12:20 ivan
+
+ * rt/: Makefile, Makefile.in, config.status,
+ bin/mason_handler.fcgi, bin/mason_handler.scgi, etc/RT_Config.pm,
+ etc/RT_Config.pm.in, lib/RT.pm, lib/RT.pm.in, lib/RT/Config.pm,
+ lib/RT/Groups_Overlay.pm, lib/RT/Record.pm,
+ lib/RT/SearchBuilder.pm, lib/RT/Ticket_Overlay.pm,
+ lib/RT/User_Overlay.pm, lib/RT/Users_Overlay.pm,
+ share/html/Admin/Users/Modify.html,
+ share/html/Ticket/Elements/ShowSummary,
+ share/html/Ticket/Elements/ShowTransactionAttachments,
+ share/html/Ticket/Elements/Tabs, share/html/User/Prefs.html:
+ merging rt \3.8.8 to HEAD
+
+2010-05-18 11:47 ivan
+
+ * rt/: share/html/NoAuth/css/base/misc.css,
+ share/html/NoAuth/css/web2/collection.css,
+ share/html/NoAuth/rss/dhandler, share/fonts/Droid.README,
+ share/fonts/DroidSansFallback.ttf, share/fonts/DroidSans.ttf,
+ docs/timezones_in_charts.pod, etc/upgrade/3.8.8/content,
+ t/api/rights_show_ticket.t, t/web/search_rss.t: Initial revision
+
+2010-05-18 11:43 ivan
+
+ * rt/: lib/RT/I18N/pt_PT.po, lib/RT/I18N/nn.po,
+ bin/fastcgi_server.in, bin/fastcgi_server,
+ share/html/Search/Elements/ResultsRSSView,
+ share/html/Elements/EditPassword,
+ share/html/Elements/ShowRelationLabel,
+ share/html/Elements/RT__CustomField/ColumnMap: Initial revision
+
+2010-05-17 20:25 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm,
+ httemplate/search/report_svc_acct.html,
+ httemplate/search/svc_acct.cgi: add more info to
+ (customer-specific) service report, RT#6180
+
+2010-05-13 19:16 ivan
+
+ * FS/FS/rate.pm, httemplate/edit/rate.cgi: fix losing rates when
+ renaming a rate plan, RT#8173
+
+2010-05-12 22:43 jeff
+
+ * httemplate/misc/queued_report.html,
+ httemplate/search/report_queued_newtax.cgi, FS/FS/queue.pm,
+ FS/FS/tax_rate.pm, FS/bin/freeside-queued,
+ httemplate/search/queue.html,
+ httemplate/search/report_newtax.html: cope with poor tax
+ liability report performance by allowing queuing of reports
+ RT#8274
+
+2010-05-12 22:16 jeff
+
+ * FS/FS/cust_main.pm: merge new bop routines into old bop routines
+ rt#4103
+
+2010-05-12 20:06 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: add "Owed" and "Payment
+ date" columns to unearned revenue detail, RT#7776
+
+2010-05-12 19:51 ivan
+
+ * Makefile: make dev kludge: unstable has 5.10.1
+
+2010-05-12 18:47 ivan
+
+ * FS/FS/Setup.pm: fix bootstrapping for 2.1 installs, broken by
+ fallout from otaker stuff, RT#8395
+
+2010-05-12 18:25 ivan
+
+ * Makefile: fix "-e DBI:Pg:dbname=freeside" datasrc winding up in
+ secrets as a result of weird implementations of echo as a shell
+ builtin that ignore -e
+
+2010-05-10 23:57 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: should fix asterisks in destination
+ numbers causing invoice generation to barf, RT#7840
+
+2010-05-10 23:38 ivan
+
+ * FS/FS/rate.pm: fix ignore_unrateable flag, RT#8149
+
+2010-05-07 19:38 ivan
+
+ * FS/FS/Mason.pm, httemplate/edit/pkg_category.html: fix package
+ Categorys spelling, RT#8314
+
+2010-05-04 15:25 mark
+
+ * FS/FS/: cdr.pm, cdr/wip.pm: RT#8026: WIP CDR format
+
+2010-04-29 19:32 ivan
+
+ * FS/FS/Mason.pm, FS/FS/Schema.pm, FS/FS/cgp_rule_action.pm,
+ FS/FS/cgp_rule_condition.pm, httemplate/browse/cgp_rule.html,
+ httemplate/edit/cgp_rule.html,
+ httemplate/elements/select-cgp_rule_action.html,
+ httemplate/elements/select-cgp_rule_condition.html: communigate
+ (phase 2): rules: show conditions/actions on rule browse, fix
+ rule edit for conditions and actions without op/param, fix rule
+ edit stickiness on errors. RT#7514
+
+2010-04-29 00:40 ivan
+
+ * httemplate/elements/select-cgp_rule_condition.html,
+ FS/FS/cgp_rule.pm, httemplate/edit/cgp_rule.html,
+ httemplate/edit/process/cgp_rule.html,
+ httemplate/elements/input-text.html,
+ httemplate/elements/select-cgp_rule_action.html,
+ httemplate/elements/select.html: communigate (phase 2): rules:
+ adding conditions and actions to rule edit. RT#7514
+
+2010-04-28 19:59 ivan
+
+ * httemplate/: elements/select-cgp_rule_action.html,
+ edit/elements/edit.html, elements/select-cgp_rule_condition.html:
+ communigate (phase 2): rules: adding conditions and actions to
+ rule edit. RT#7514
+
+2010-04-27 03:56 ivan
+
+ * httemplate/: edit/cgp_rule.html,
+ elements/select-cgp_rule_action.html,
+ elements/select-cgp_rule_condition.html, elements/select.html,
+ elements/tr-select.html: communigate (phase 2): rules: start of
+ adding conditions and actions to rule edit. RT#7514
+
+2010-04-27 02:23 ivan
+
+ * httemplate/edit/elements/edit.html: tyop
+
+2010-04-26 22:38 mark
+
+ * FS/FS/: ClientAPI/MyAccount.pm, Schema.pm, cdr.pm,
+ cdr_termination.pm, cust_svc.pm, svc_phone.pm,
+ part_pkg/voip_cdr.pm, part_pkg/voip_inbound.pm: RT#7046: inbound
+ rate for rate plan billing
+
+2010-04-22 23:47 ivan
+
+ * httemplate/view/svc_domain/basics.html: communigate (phase 2):
+ rules. RT#7514
+
+2010-04-22 23:43 ivan
+
+ * FS/FS/Mason.pm, FS/FS/cgp_rule.pm, FS/FS/cgp_rule_action.pm,
+ FS/FS/cgp_rule_condition.pm, httemplate/browse/cgp_rule.html,
+ httemplate/edit/cgp_rule.html,
+ httemplate/edit/process/cgp_rule.html,
+ httemplate/misc/delete-cgp_rule.html,
+ httemplate/view/svc_acct/basics.html,
+ httemplate/view/svc_acct/communigate.html, FS/FS/Schema.pm:
+ communigate (phase 2): rules. RT#7514
+
+2010-04-21 19:00 ivan
+
+ * FS/: FS/Schema.pm, FS/cgp_rule.pm, FS/cgp_rule_action.pm,
+ FS/cgp_rule_condition.pm, FS.pm, MANIFEST, t/cgp_rule.t,
+ t/cgp_rule_action.t, t/cgp_rule_condition.t: communigate (phase
+ 2): rules. RT#7514
+
+2010-04-21 02:31 ivan
+
+ * httemplate/elements/select-part_pkg.html: "
+
+2010-04-21 02:09 ivan
+
+ * httemplate/config/config.cgi: prevent inadvertantly losing
+ disabled package defs, service defs or package classes in a
+ config value that uses them (i.e. support_packages)
+
+2010-04-21 01:57 ivan
+
+ * httemplate/elements/select-part_pkg.html: prevent inadvertantly
+ losing disabled package defs, service defs or package classes in
+ a config value that uses them (i.e. support_packages)
+
+2010-04-20 20:56 ivan
+
+ * Makefile: ensure that the trainwreck which was 1.9.2 does not
+ happen again
+
+2010-04-19 21:29 ivan
+
+ * FS/FS/svc_domain.pm, httemplate/edit/part_svc.cgi: communigate
+ (phase 2): add a textarea type to edit/part_svc so you can set it
+ to a default or fixed trailer that's multi-line, RT#7514
+
+2010-04-19 21:13 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ httemplate/view/svc_acct/basics.html,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/view/svc_domain/acct_defaults.html: communigate (phase
+ 2): Account Preferences (& Domain::Account Defaults:Preferences):
+ ProntoSkinName RT#7514
+
+2010-04-19 18:51 ivan
+
+ * FS/FS/svc_acct.pm, FS/FS/Schema.pm, FS/FS/svc_domain.pm,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/view/svc_acct/basics.html,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/view/svc_domain/acct_defaults.html: communigate (phase
+ 2): Account Preferences (& Domain::Account Defaults:Preferences):
+ Language, Time zone, Layout, Send read receipts. RT#7514
+
+2010-04-19 00:09 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct/basics.html: communigate (phase 2),
+ Account:Settings PasswordRecovery. also fix modification of
+ svc_acct booleans in export. RT#7514
+
+2010-04-18 23:15 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct/basics.html: communigate provisioning
+ phase 2: Account:Settings: RulesAllowed, RPOPAllowed, MailToAll,
+ AddMailTrailer. RT#7514
+
+2010-04-18 22:01 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_domain.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_domain.cgi,
+ httemplate/view/svc_domain/acct_defaults.html: communigate
+ provisioning phase 2: Domain:Account Defaults:Settings:
+ RulesAllowed, RPOPAllowed, MailToAll, AddMailTrailer. RT#7514
+
+2010-04-18 18:25 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_domain.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_domain.cgi,
+ httemplate/view/svc_domain/basics.html: communigate provisioning
+ phase 2: add svc_domain.trailer -> communigate TrailerText,
+ RT#7514
+
+2010-04-09 21:21 mark
+
+ * httemplate/search/elements/cust_main_dayranges.html: RT#866: fix
+ payment links
+
+2010-04-09 01:17 ivan
+
+ * httemplate/search/elements/cust_main_dayranges.html, FS/MANIFEST,
+ FS/FS/Mason.pm, FS/FS/Misc/DateTime.pm, FS/FS/UI/Web.pm,
+ FS/FS/cust_main/Import.pm, httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/cust_pay.cgi,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/misc/process/cancel_pkg.html,
+ httemplate/misc/process/delay_susp_pkg.html,
+ httemplate/search/pay_batch.cgi,
+ httemplate/search/report_prepaid_income.cgi,
+ httemplate/search/sqlradius.cgi: fix date parsing when using
+ international dates (package date edit), RT#8027
+
+2010-04-09 01:09 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, conf/invoice_html: modify
+ total items for balance below line and current charges above line
+ with configurable description. also bug fixes that might close
+ 7896
+
+2010-04-08 12:23 jeff
+
+ * httemplate/elements/about_rt.html: close tag rather than open a
+ new one
+
+2010-04-07 18:15 mark
+
+ * FS/FS/cust_pkg.pm, FS/FS/Report/Table/Monthly.pm,
+ httemplate/elements/menu.html,
+ httemplate/graph/cust_pkg_cost.cgi,
+ httemplate/graph/report_cust_pkg_cost.html,
+ httemplate/search/cust_pkg.cgi: RT#1382: beginning of package
+ cost report
+
+2010-04-07 01:24 ivan
+
+ * httemplate/edit/agent.cgi: also in agent edit, fix form getting
+ submitted before customer search can complete. throws "status
+ connecting to" error and custnum does not get filled in, RT#8014
+
+2010-04-07 01:15 ivan
+
+ * httemplate/: edit/access_user.html, edit/elements/edit.html,
+ elements/search-cust_main.html: in employee edit, fix form
+ getting submitted before customer search can complete. throws
+ "status connecting to" error and custnum does not get filled in,
+ RT#8014
+
+2010-04-06 23:20 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm: fix capitalization of RT
+ tables and columns - mysql fix
+
+2010-04-06 22:52 mark
+
+ * FS/FS/pay_batch/RBC.pm: fix floating point error
+
+2010-04-06 21:11 ivan
+
+ * httemplate/search/part_pkg.html: fix a mysql-ism in employee
+ commission report, now compatible with Pg, that's a new one.
+ RT#6991
+
+2010-04-06 14:43 ivan
+
+ * httemplate/search/report_employee_commission.html: fix 1.9-style
+ employee comission report, RT#6991
+
+2010-04-05 05:24 jeff
+
+ * FS/FS/part_export/dashcs_e911.pm: fix noisy but harmless
+ redeclaration
+
+2010-04-04 23:58 ivan
+
+ * conf/invoice_from: company email
+
+2010-04-04 23:57 ivan
+
+ * FS/bin/freeside-upgrade: require DBIx::DBSchema 0.39 for mysql
+
+2010-04-04 23:50 ivan
+
+ * FS/FS/part_export/thirdlane.pm: thirdlane deletion fix
+
+2010-04-04 22:20 ivan
+
+ * FS/FS/part_export/indosoft.pm: initial indosoft export, RT#4068
+
+2010-04-04 18:23 ivan
+
+ * httemplate/search/part_pkg.html: last fix for employee
+ commisssion report, RT#6991
+
+2010-04-04 17:59 ivan
+
+ * httemplate/elements/menu.html: add employee commission report to
+ menu, RT#6991
+
+2010-04-04 16:47 ivan
+
+ * FS/FS/Schema.pm: fix otaker upgrade
+
+2010-04-04 16:37 ivan
+
+ * FS/FS/cust_pay_void.pm: fix otaker upgrade
+
+2010-04-04 16:27 ivan
+
+ * FS/FS/: cust_attachment.pm, cust_main_note.pm: fix otaker upgrade
+ for cust_attachment & cust_main_note, hopefully
+
+2010-04-04 16:18 ivan
+
+ * FS/FS/: cust_attachment.pm, cust_main_note.pm: fix otaker upgrade
+ for cust_attachment & cust_main_note, hopefully
+
+2010-04-04 15:52 ivan
+
+ * FS/FS/: cust_main.pm: fix otaker upgrade for cust_main
+
+2010-04-04 15:44 ivan
+
+ * FS/FS/cust_main_note.pm: fix note editing of old notes until we
+ can fix the cust_main_note.otaker disaster, RT#7991
+
+2010-04-01 14:30 mark
+
+ * httemplate/search/elements/cust_main_dayranges.html: fix column
+ alignment
+
+2010-04-01 01:10 ivan
+
+ * FS/FS/part_pkg/: flat.pm, subscription.pm: fix nasty discount
+ fallout (i hope)
+
+2010-04-01 00:39 ivan
+
+ * FS/FS/part_pkg/flat.pm: fix nasty discount fallout (i hope)
+
+2010-03-31 21:43 mark
+
+ * FS/FS/UI/Web.pm, httemplate/search/report_receivables.cgi,
+ httemplate/search/elements/cust_main_dayranges.html,
+ httemplate/search/elements/search-html.html,
+ httemplate/search/elements/search.html: RT#866: links to process
+ payments from aging report
+
+2010-03-31 08:48 jeff
+
+ * FS/: MANIFEST, FS/part_export/dashcs_e911.pm: add dash carrier
+ services e911 support RT7103
+
+2010-03-31 00:41 mark
+
+ * httemplate/search/: report_receivables.cgi,
+ report_receivables.html, report_unapplied_cust_pay.html,
+ unapplied_cust_pay.html, elements/cust_main_dayranges.html:
+ RT#7266: aging reports as of a past date
+
+2010-03-31 00:35 ivan
+
+ * httemplate/: edit/process/cust_main_attach.cgi,
+ edit/process/cust_main_note.cgi, view/cust_main/attachments.html,
+ view/cust_main/notes.html: fix customer notes and attachments wrt
+ s/otaker/usernum/ changes; still need to look at the migration,
+ RT#7935
+
+2010-03-30 23:30 ivan
+
+ * FS/FS/Misc.pm: eliminate harmless "Use of uninitialized value
+ $enc in string eq" warnings
+
+2010-03-30 05:12 ivan
+
+ * httemplate/search/part_pkg.html, FS/FS/Schema.pm: employee
+ commission reporting, RT#6991
+
+2010-03-30 05:07 ivan
+
+ * httemplate/search/report_employee_commission.html,
+ FS/FS/cust_main.pm, FS/FS/cust_credit.pm, FS/FS/cust_event.pm,
+ FS/FS/part_event/Action/pkg_agent_credit.pm,
+ FS/FS/part_event/Action/pkg_employee_credit.pm,
+ FS/FS/part_event/Action/pkg_referral_credit.pm: employee
+ commission reporting, RT#6991
+
+2010-03-29 20:28 ivan
+
+ * FS/FS/Record.pm: regexp_sql
+
+2010-03-29 20:10 ivan
+
+ * FS/MANIFEST: employee (otaker / access_user) commissioning,
+ RT#6991
+
+2010-03-29 19:53 ivan
+
+ * httemplate/browse/access_user.html,
+ httemplate/edit/access_user.html, FS/FS/part_pkg.pm,
+ FS/FS/part_event/Action/pkg_agent_credit.pm,
+ FS/FS/part_event/Action/pkg_agent_credit_pkg.pm,
+ FS/FS/part_event/Action/pkg_employee_credit_pkg.pm,
+ FS/FS/part_event/Action/pkg_referral_credit.pm,
+ FS/FS/part_event/Action/pkg_referral_credit_pkg.pm,
+ FS/FS/part_event/Action/Mixin/credit_pkg.pm,
+ httemplate/elements/search-cust_main.html,
+ httemplate/elements/tr-search-cust_main.html: employee (otaker /
+ access_user) commissioning, RT#6991
+
+2010-03-29 19:52 ivan
+
+ * FS/FS/part_event/Action/pkg_employee_credit.pm: employee
+ commissions, RT#6991
+
+2010-03-29 19:04 ivan
+
+ * FS/FS/: part_event_condition.pm, part_event/Condition/balance.pm,
+ part_event/Condition/balance_age.pm,
+ part_event/Condition/balance_under.pm,
+ part_event/Condition/cust_bill_has_service.pm,
+ part_event/Condition/cust_bill_owed.pm,
+ part_event/Condition/cust_bill_owed_under.pm: fixes for MySQL
+ CAST drain bramage
+
+2010-03-29 02:18 mark
+
+ * FS/FS/Mason.pm, FS/FS/cust_main.pm,
+ httemplate/elements/select-user.html,
+ httemplate/elements/tr-select-user.html,
+ httemplate/graph/report_signupdate.html,
+ httemplate/graph/signupdate.cgi,
+ httemplate/graph/elements/monthly.html,
+ httemplate/graph/elements/report.html,
+ httemplate/search/cust_main.html: RT#884: search customers by
+ signup time of day
+
+2010-03-28 17:38 ivan
+
+ * FS/FS/cust_pay.pm: move from otaker to proper usernum FK
+
+2010-03-28 17:23 ivan
+
+ * FS/FS/: Schema.pm, Upgrade.pm, access_user.pm, banned_pay.pm,
+ cust_attachment.pm, cust_credit.pm, cust_main.pm,
+ cust_main_note.pm, cust_pay_void.pm, cust_pkg.pm,
+ cust_pkg_discount.pm, cust_pkg_reason.pm, cust_refund.pm,
+ otaker_Mixin.pm: move from otaker to proper usernum FK
+
+2010-03-26 23:21 ivan
+
+ * FS/FS/cust_bill_pkg_detail.pm: fix cust_bill_pkg_detail throwing
+ a fatal error w/MySQL
+
+2010-03-26 23:09 ivan
+
+ * httemplate/: view/cust_main.cgi, view/cust_main/notes.html,
+ edit/cust_main_note.cgi: resize customer not add popup, eliminate
+ needless <BR> in popup
+
+2010-03-26 21:37 ivan
+
+ * FS/FS/Upgrade.pm: these are now supported in supported in
+ DBIx-DBSchema and friends
+
+2010-03-26 20:43 ivan
+
+ * FS/FS/: reason.pm, tax_rate.pm: these are now supported in
+ supported in DBIx-DBSchema and friends
+
+2010-03-26 15:25 ivan
+
+ * httemplate/elements/: select-areacode.html, select-did.html: fix
+ E911 vs. DID selector on phone provision, RT#7819
+
+2010-03-26 14:43 ivan
+
+ * FS/FS/cust_pkg.pm: no DISTINCT ON in MySQL makes kittens cry
+
+2010-03-26 00:52 ivan
+
+ * httemplate/elements/: freeside-menu.css, freeside.css: that'll do
+ for now
+
+2010-03-25 22:02 ivan
+
+ * FS/FS/Schema.pm: kludge a fix for the MySQL statustext index
+ problem, fix s/serial/int/ for non-primary keys in
+ part_pkg_taxoverride, and s/TEXT/LONGTEXT/ ourselves until
+ DBIx::DBSchema 0.39
+
+2010-03-25 21:50 ivan
+
+ * FS/FS/UID.pm: don't warn about the configuration table during
+ setup
+
+2010-03-25 19:19 ivan
+
+ * bin/build_exten.php: adding build_exten.php since FreePBX won't
+ ship it
+
+2010-03-25 18:06 ivan
+
+ * rt/lib/RT/: Ticket_Overlay.pm: ticket auto-association in the
+ correct spot. how the hell did it get merged there? RT#7882
+
+2010-03-25 15:30 ivan
+
+ * httemplate/elements/header.html: fix new header in RT
+
+2010-03-25 15:21 ivan
+
+ * rt/lib/RT/User_Overlay.pm: fix user modification?
+
+2010-03-25 14:22 ivan
+
+ * rt/lib/RT/URI/freeside.pm: generate a stack backtrace for mystery
+ freeside link resolution problems
+
+2010-03-25 13:21 ivan
+
+ * httemplate/elements/: header.html, searchbar-address2.html,
+ searchbar-cust_bill.html, searchbar-cust_main.html,
+ searchbar-cust_svc.html, searchbar-prospect.html,
+ searchbar-ticket.html: when using a side menubar, put search
+ boxes on the side too
+
+2010-03-24 18:37 mark
+
+ * FS/FS/Mason.pm, httemplate/view/cust_main/notes.html: RT#6226:
+ security fix for customer notes
+
+2010-03-24 01:37 ivan
+
+ * FS/FS/Conf.pm, httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_prepaid_income.cgi: better prepaid
+ income reporting, with line item detail, RT#7776
+
+2010-03-23 02:13 ivan
+
+ * FS/FS/mailinglist.pm, FS/FS/mailinglistmember.pm,
+ FS/FS/svc_mailinglist.pm, FS/FS/part_export/communigate_pro.pm,
+ httemplate/elements/header.html,
+ httemplate/search/mailinglistmember.html: export svc_mailinglist
+ to CGP groups, RT#7514
+
+2010-03-23 01:27 ivan
+
+ * httemplate/images/: black-gray-side.png, gray-black-side.png:
+ goodbye and good riddance
+
+2010-03-22 20:53 ivan
+
+ * FS/: FS.pm, FS/h_svc_mailinglist.pm, t/h_svc_mailinglist.t,
+ FS/Mason.pm: adding svc_mailinglist for communigate "groups"
+ (mailing lists), RT#7514
+
+2010-03-22 20:47 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/mailinglist.pm,
+ FS/FS/mailinglistmember.pm, FS/FS/svc_mailinglist.pm,
+ FS/t/mailinglist.t, FS/t/mailinglistmember.t,
+ FS/t/svc_mailinglist.t, httemplate/edit/mailinglistmember.html,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_mailinglist.cgi,
+ httemplate/edit/process/mailinglistmember.html,
+ httemplate/edit/process/svc_mailinglist.html,
+ httemplate/misc/delete-mailinglistmember.html,
+ httemplate/search/mailinglistmember.html,
+ httemplate/view/svc_mailinglist.cgi: adding svc_mailinglist for
+ communigate "groups" (mailing lists), RT#7514
+
+2010-03-22 20:34 ivan
+
+ * httemplate/elements/freeside.css: little bit further, just a
+ little bit more
+
+2010-03-22 18:17 ivan
+
+ * httemplate/elements/menubar.html: don't like that <BR> after
+ all... for now
+
+2010-03-22 18:07 ivan
+
+ * httemplate/elements/freeside.css: wfm
+
+2010-03-22 18:04 ivan
+
+ * httemplate/elements/: xmenu.css, xmenu.top.css: purple on pink is
+ WAY too my little pony. man, its not easy being purple
+
+2010-03-22 12:46 ivan
+
+ * httemplate/elements/: freeside.css, menubar.html: 1.9-style
+ menubar was hurting my eyes
+
+2010-03-22 07:08 jeff
+
+ * FS/FS/tax_rate.pm: fix restore of setup and recur taxproducts on
+ tax data replacement
+
+2010-03-21 23:34 ivan
+
+ * httemplate/elements/freeside.css: fix link hover effect so it
+ doesn't underline A NAME tags
+
+2010-03-21 16:56 ivan
+
+ * httemplate/elements/freeside.css: fix inadvertant button styling
+
+2010-03-21 16:13 ivan
+
+ * FS/FS/Schema.pm, FS/FS/rate_detail.pm,
+ FS/FS/part_pkg/voip_cdr.pm, httemplate/browse/rate_detail.html,
+ httemplate/edit/rate_detail.html,
+ httemplate/edit/rate_region.cgi,
+ httemplate/edit/process/rate_region.cgi,
+ httemplate/misc/rate_edit_excel.html,
+ httemplate/misc/process/copy-rate_detail.html: connection fee for
+ initial N seconds support, RT#7018
+
+2010-03-21 12:34 ivan
+
+ * rt/etc/RT_SiteConfig.pm: make the fckeditor taller, way too small
+
+2010-03-20 22:20 ivan
+
+ * rt/share/html/Elements/Header: there was nothing wrong with the
+ dhandler
+
+2010-03-20 22:17 ivan
+
+ * rt/: share/html/Widgets/TitleBoxEnd, FREESIDE_MODIFIED: think
+ this fixes the "results box color infects everything else"
+ problem
+
+2010-03-20 21:47 ivan
+
+ * rt/share/html/Elements/Header: dunno why squish dhandler is
+ borked
+
+2010-03-20 16:30 ivan
+
+ * httemplate/docs/credits.html: more RT integration / reskin / 2.1,
+ RT#6640
+
+2010-03-20 16:22 ivan
+
+ * httemplate/: elements/menu.html,
+ elements/popup_link_onclick.html, images/cvv2.png,
+ images/cvv2_amex.png: more RT integration / reskin / 2.1, RT#6640
+
+2010-03-20 15:44 ivan
+
+ * httemplate/elements/: freeside-menu.css, header.html, xmenu.css,
+ xmenu.js, xmenu.top.css: more RT integration / reskin / 2.1,
+ RT#6640
+
+2010-03-20 15:11 ivan
+
+ * httemplate/elements/calendar-win2k-2.css,
+ httemplate/elements/freeside-menu.css,
+ httemplate/elements/header.html, httemplate/elements/menu.html,
+ httemplate/elements/xmenu.top.css,
+ httemplate/elements/xmenu.top.js, rt/share/html/Elements/Footer,
+ rt/share/html/Elements/PageLayout,
+ rt/share/html/Elements/SelectDate,
+ rt/share/html/Prefs/SearchOptions.html,
+ rt/share/html/User/Prefs.html,
+ httemplate/images/arrow.down.black.png,
+ httemplate/images/black-gradient.png,
+ httemplate/images/black-gray-gradient.png, rt/FREESIDE_MODIFIED,
+ httemplate/elements/freeside.css,
+ rt/share/html/NoAuth/css/freeside2.1/layout.css: more RT
+ integration / reskin / 2.1, RT#6640
+
+2010-03-19 01:27 ivan
+
+ * httemplate/: docs/cvv2.html, elements/header-minimal.html,
+ elements/header-popup.html, elements/header.html,
+ elements/tr-justtitle.html, elements/tr-title.html,
+ elements/freeside-menu.css, elements/menu.html,
+ elements/menubar.html, elements/xmenu.top.css: skin RT 3.8,
+ slight new look for 2.1, RT#6640
+
+2010-03-19 01:26 ivan
+
+ * rt/: FREESIDE_MODIFIED, share/html/Elements/Header,
+ share/html/Elements/PageLayout,
+ share/html/NoAuth/css/freeside2.1/freeside.css,
+ share/html/NoAuth/css/freeside2.1/layout.css,
+ share/html/NoAuth/css/freeside2.1/nav.css: skin RT 3.8, RT#6640
+
+2010-03-19 01:20 mark
+
+ * httemplate/misc/batch-cust_pay.html: RT#7812: confirm before
+ closing quick payment entry
+
+2010-03-18 01:02 ivan
+
+ * httemplate/edit/cust_main/top_misc.html: signup date fix?
+
+2010-03-18 00:59 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/part_pkg.pm,
+ httemplate/edit/quick-charge.html,
+ FS/FS/part_event/Condition/cust_bill_has_noauto.pm,
+ FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/misc/order_pkg.html,
+ httemplate/view/cust_main/one_time_charge_link.html,
+ httemplate/view/cust_main/order_pkg_link.html,
+ httemplate/view/cust_main/packages/status.html: disable
+ auto-billing of specific customer packages, RT#6378
+
+2010-03-16 01:05 mark
+
+ * httemplate/edit/cust_main_note.cgi: minor bugfix
+
+2010-03-16 00:49 mark
+
+ * httemplate/elements/fckeditor/: fckconfig.js, fckeditor.js,
+ fckpackager.xml, fckstyles.xml, fcktemplates.xml,
+ editor/fckdebug.html, editor/fckdialog.html,
+ editor/fckeditor.html, editor/fckeditor.original.html,
+ editor/css/fck_editorarea.css, editor/css/fck_internal.css,
+ editor/css/fck_showtableborders_gecko.css,
+ editor/css/images/block_address.png,
+ editor/css/images/block_blockquote.png,
+ editor/css/images/block_div.png, editor/css/images/block_h1.png,
+ editor/css/images/block_h2.png, editor/css/images/block_h3.png,
+ editor/css/images/block_h4.png, editor/css/images/block_h5.png,
+ editor/css/images/block_h6.png, editor/css/images/block_p.png,
+ editor/css/images/block_pre.png,
+ editor/css/images/fck_plugin.gif, editor/dialog/fck_about.html,
+ editor/dialog/fck_anchor.html, editor/dialog/fck_button.html,
+ editor/dialog/fck_checkbox.html,
+ editor/dialog/fck_colorselector.html, editor/dialog/fck_div.html,
+ editor/dialog/fck_docprops.html, editor/dialog/fck_flash.html,
+ editor/dialog/fck_form.html, editor/dialog/fck_hiddenfield.html,
+ editor/dialog/fck_image.html, editor/dialog/fck_link.html,
+ editor/dialog/fck_listprop.html, editor/dialog/fck_paste.html,
+ editor/dialog/fck_radiobutton.html,
+ editor/dialog/fck_replace.html, editor/dialog/fck_scayt.html,
+ editor/dialog/fck_select.html, editor/dialog/fck_smiley.html,
+ editor/dialog/fck_source.html,
+ editor/dialog/fck_specialchar.html,
+ editor/dialog/fck_spellerpages.html,
+ editor/dialog/fck_table.html, editor/dialog/fck_tablecell.html,
+ editor/dialog/fck_template.html, editor/dialog/fck_textarea.html,
+ editor/dialog/fck_textfield.html,
+ editor/dialog/common/fck_dialog_common.css,
+ editor/dialog/common/fck_dialog_common.js,
+ editor/dialog/fck_about/sponsors/spellchecker_net.gif,
+ editor/dialog/fck_docprops/fck_document_preview.html,
+ editor/dialog/fck_flash/fck_flash.js,
+ editor/dialog/fck_flash/fck_flash_preview.html,
+ editor/dialog/fck_image/fck_image.js,
+ editor/dialog/fck_image/fck_image_preview.html,
+ editor/dialog/fck_link/fck_link.js,
+ editor/dialog/fck_scayt/scayt_dialog.css,
+ editor/dialog/fck_select/fck_select.js,
+ editor/dialog/fck_spellerpages/spellerpages/spellChecker.js,
+ editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css,
+ editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl,
+ editor/dtd/fck_dtd_test.html, editor/dtd/fck_xhtml10strict.js,
+ editor/dtd/fck_xhtml10transitional.js,
+ editor/filemanager/browser/default/browser.css,
+ editor/filemanager/browser/default/browser.html,
+ editor/filemanager/browser/default/frmactualfolder.html,
+ editor/filemanager/browser/default/frmcreatefolder.html,
+ editor/filemanager/browser/default/frmfolders.html,
+ editor/filemanager/browser/default/frmresourceslist.html,
+ editor/filemanager/browser/default/frmresourcetype.html,
+ editor/filemanager/browser/default/frmupload.html,
+ editor/filemanager/browser/default/js/common.js,
+ editor/filemanager/browser/default/js/fckxml.js,
+ editor/filemanager/connectors/test.html,
+ editor/filemanager/connectors/uploadtest.html,
+ editor/filemanager/connectors/perl/basexml.pl,
+ editor/filemanager/connectors/perl/commands.pl,
+ editor/filemanager/connectors/perl/config.pl,
+ editor/filemanager/connectors/perl/connector.cgi,
+ editor/filemanager/connectors/perl/io.pl,
+ editor/filemanager/connectors/perl/upload.cgi,
+ editor/filemanager/connectors/perl/upload_fck.pl,
+ editor/filemanager/connectors/perl/util.pl,
+ editor/js/fckadobeair.js, editor/js/fckeditorcode_gecko.js,
+ editor/js/fckeditorcode_ie.js,
+ editor/lang/_translationstatus.txt, editor/lang/af.js,
+ editor/lang/ar.js, editor/lang/bg.js, editor/lang/bn.js,
+ editor/lang/bs.js, editor/lang/ca.js, editor/lang/cs.js,
+ editor/lang/da.js, editor/lang/de.js, editor/lang/el.js,
+ editor/lang/en-au.js, editor/lang/en-ca.js, editor/lang/en-uk.js,
+ editor/lang/en.js, editor/lang/eo.js, editor/lang/es.js,
+ editor/lang/et.js, editor/lang/eu.js, editor/lang/fa.js,
+ editor/lang/fi.js, editor/lang/fo.js, editor/lang/fr-ca.js,
+ editor/lang/fr.js, editor/lang/gl.js, editor/lang/gu.js,
+ editor/lang/he.js, editor/lang/hi.js, editor/lang/hr.js,
+ editor/lang/hu.js, editor/lang/is.js, editor/lang/it.js,
+ editor/lang/ja.js, editor/lang/km.js, editor/lang/ko.js,
+ editor/lang/lt.js, editor/lang/lv.js, editor/lang/mn.js,
+ editor/lang/ms.js, editor/lang/nb.js, editor/lang/nl.js,
+ editor/lang/no.js, editor/lang/pl.js, editor/lang/pt-br.js,
+ editor/lang/pt.js, editor/lang/ro.js, editor/lang/ru.js,
+ editor/lang/sk.js, editor/lang/sl.js, editor/lang/sr-latn.js,
+ editor/lang/sr.js, editor/lang/sv.js, editor/lang/th.js,
+ editor/lang/tr.js, editor/lang/uk.js, editor/lang/vi.js,
+ editor/lang/zh-cn.js, editor/lang/zh.js,
+ editor/plugins/autogrow/fckplugin.js,
+ editor/plugins/bbcode/fckplugin.js,
+ editor/plugins/dragresizetable/fckplugin.js,
+ editor/plugins/placeholder/fck_placeholder.html,
+ editor/plugins/placeholder/fckplugin.js,
+ editor/plugins/placeholder/lang/de.js,
+ editor/plugins/placeholder/lang/en.js,
+ editor/plugins/placeholder/lang/es.js,
+ editor/plugins/placeholder/lang/fr.js,
+ editor/plugins/placeholder/lang/it.js,
+ editor/plugins/placeholder/lang/pl.js,
+ editor/plugins/simplecommands/fckplugin.js,
+ editor/plugins/tablecommands/fckplugin.js,
+ editor/skins/_fckviewstrips.html,
+ editor/skins/default/fck_dialog.css,
+ editor/skins/default/fck_dialog_ie6.js,
+ editor/skins/default/fck_editor.css,
+ editor/skins/default/fck_strip.gif,
+ editor/skins/default/images/dialog.sides.gif,
+ editor/skins/default/images/dialog.sides.png,
+ editor/skins/default/images/dialog.sides.rtl.png,
+ editor/skins/default/images/sprites.gif,
+ editor/skins/default/images/sprites.png,
+ editor/skins/office2003/fck_dialog.css,
+ editor/skins/office2003/fck_dialog_ie6.js,
+ editor/skins/office2003/fck_editor.css,
+ editor/skins/office2003/fck_strip.gif,
+ editor/skins/office2003/images/dialog.sides.gif,
+ editor/skins/office2003/images/dialog.sides.png,
+ editor/skins/office2003/images/dialog.sides.rtl.png,
+ editor/skins/office2003/images/sprites.gif,
+ editor/skins/office2003/images/sprites.png,
+ editor/skins/silver/fck_dialog.css,
+ editor/skins/silver/fck_dialog_ie6.js,
+ editor/skins/silver/fck_editor.css,
+ editor/skins/silver/fck_strip.gif,
+ editor/skins/silver/images/dialog.sides.gif,
+ editor/skins/silver/images/dialog.sides.png,
+ editor/skins/silver/images/dialog.sides.rtl.png,
+ editor/skins/silver/images/sprites.gif,
+ editor/skins/silver/images/sprites.png, editor/wsc/ciframe.html,
+ editor/wsc/tmpFrameset.html, editor/wsc/w.html: FCKeditor 2.6.6
+
+2010-03-15 19:51 mark
+
+ * httemplate/: edit/cust_main_note.cgi, view/cust_main/notes.html,
+ elements/htmlarea.html: RT#6226: fckeditor for customer notes
+
+2010-03-15 19:14 ivan
+
+ * rt/etc/rt.spec: remove rt.spec, borking our build somehow
+
+2010-03-15 18:00 ivan
+
+ * httemplate/search/: cust_main-zip.html,
+ report_cust_main-zip.html: w/svc_whatever option on zip code
+ distribution report, RT#7784
+
+2010-03-15 15:31 mark
+
+ * FS/bin/freeside-paymentech-upload: RT#7473: add -p option
+
+2010-03-15 00:13 ivan
+
+ * httemplate/edit/svc_broadband.cgi: fix editing svc_broadband
+ service w/no ip address, RT#7786
+
+2010-03-14 23:18 ivan
+
+ * httemplate/search/elements/cust_pay_or_refund.html: deleted
+ payment report, RT#7694
+
+2010-03-14 23:10 ivan
+
+ * httemplate/search/: h_cust_pay.html, report_h_cust_pay.html,
+ elements/cust_pay_or_refund.html: deleted payment report, RT#7694
+
+2010-03-14 19:06 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix diabled package optional
+ reporting classes still selectable on package def edit, RT#7658
+
+2010-03-13 15:50 ivan
+
+ * FS/FS/cust_pkg.pm: quiet extraneous warnings from
+ expire_months/start_1st stuff, getting in the way of debugging
+ for ncic, RT#7780
+
+2010-03-12 13:56 ivan
+
+ * FS/FS/Conf.pm, FS/FS/svc_phone.pm, httemplate/edit/svc_phone.cgi:
+ add svc_phone-phone_name-max_length config, RT#7047
+
+2010-03-12 13:29 ivan
+
+ * FS/FS/cust_location.pm, FS/FS/cust_main.pm,
+ FS/FS/location_Mixin.pm, FS/FS/svc_phone.pm,
+ httemplate/edit/svc_phone.cgi,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/svc_phone.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/view/svc_phone.cgi: finishing e911/svc_phone location,
+ RT#7047
+
+2010-03-12 03:48 mark
+
+ * httemplate/edit/svc_broadband.cgi: RT#7765: sort broadband router
+ names
+
+2010-03-11 19:36 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_pkg.pm, FS/FS/location_Mixin.pm,
+ FS/FS/svc_phone.pm, FS/t/location_Mixin.t,
+ httemplate/edit/svc_phone.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/elements/location.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/view/svc_phone.cgi: add location to svc_phone, RT#7047
+
+2010-03-11 19:30 ivan
+
+ * FS/MANIFEST: add location to svc_phone, RT#7047
+
+2010-03-10 19:27 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm,
+ httemplate/view/cust_main/tickets.html: add queue selection to
+ customer ticket creation, RT#7656
+
+2010-03-10 18:15 ivan
+
+ * httemplate/docs/credits.html: Mark Wells is now a member of the
+ core team. Kristian Hoffman has been moved to the core emeritus
+
+2010-03-10 00:20 mark
+
+ * httemplate/edit/svc_acct.cgi: fix probably misplaced text field
+
+2010-03-09 23:50 mark
+
+ * httemplate/edit/svc_domain.cgi: fix probably misplaced text field
+
+2010-03-09 22:52 ivan
+
+ * FS/FS/cust_pkg.pm: oops, move start_1st and expire_months
+ handling from check to insert, so it doesn't get triggered on
+ edit, RT#7347
+
+2010-03-09 22:47 ivan
+
+ * httemplate/edit/process/REAL_cust_pkg.cgi: fix warning about
+ adding a start date to actually check its *added*, RT#7352
+
+2010-03-09 15:47 ivan
+
+ * httemplate/misc/delay_susp_pkg.html: tyop too
+
+2010-03-09 14:37 mark
+
+ * httemplate/misc/cancel_pkg.html: fix typo
+
+2010-03-09 13:57 mark
+
+ * FS/FS/prepay_credit.pm, httemplate/edit/prepay_credit.cgi,
+ httemplate/edit/process/prepay_credit.cgi: RT#7407, variable
+ length prepaid card codes
+
+2010-03-09 00:37 ivan
+
+ * FS/FS/part_pkg/sql_external.pm: move sql_external to use
+ recur_Common, RT#7212
+
+2010-03-09 00:05 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: date editing fix, fallout from
+ date_format
+
+2010-03-08 18:43 ivan
+
+ * FS/FS/: cust_pkg.pm, part_pkg/flat.pm: a package that starts on
+ the 1st and expires after N months, RT#7738
+
+2010-03-08 18:39 ivan
+
+ * FS/FS/cust_main.pm: fix ACH refunds w/IPPay. B:OP 3.01 and IPPay
+ 0.05_02 required. RT#_7673
+
+2010-03-08 18:12 ivan
+
+ * FS/FS/cust_pkg.pm: init ticket system new-style
+
+2010-03-08 16:23 jeff
+
+ * FS/FS/Setup.pm: tyop broke populate_initial_data
+
+2010-03-08 16:18 jeff
+
+ * FS/FS/reason_type.pm: and this is not 1.7.x
+
+2010-03-08 15:07 jeff
+
+ * FS/FS/part_export/prizm.pm: try a delete before adding an element
+ to prizm
+
+2010-03-08 14:13 ivan
+
+ * FS/FS/cust_main.pm: fix ACH refunds w/IPPay. B:OP 3.01 and IPPay
+ 0.05_02 required. RT#_7673
+
+2010-03-08 02:57 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/cust_credit.cgi, httemplate/edit/cust_pay.cgi,
+ httemplate/edit/cust_refund.cgi,
+ httemplate/edit/quick-charge.html,
+ httemplate/edit/elements/ApplicationCommon.html,
+ httemplate/elements/tr-input-beginning_ending.html,
+ httemplate/elements/tr-input-date-field.html,
+ httemplate/misc/cancel_pkg.html,
+ httemplate/misc/delay_susp_pkg.html,
+ httemplate/misc/order_pkg.html,
+ httemplate/search/report_prepaid_income.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/payment_history/credit.html,
+ httemplate/view/cust_main/payment_history/payment.html,
+ httemplate/view/cust_main/payment_history/voided_payment.html:
+ proper use of date_format config for international date formats,
+ RT#7009
+
+2010-03-07 23:02 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/svc_Domain_Mixin.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_phone.pm, FS/MANIFEST,
+ FS/FS/part_export/netsapiens.pm, FS/t/svc_Domain_Mixin.t,
+ httemplate/edit/svc_phone.cgi,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/elements/select-svc-domain.html,
+ httemplate/elements/tr-select-svc-domain.html,
+ httemplate/view/svc_phone.cgi: domain names in netsapiens export
+ (domain name association w/svc_phone), RT#5864
+
+2010-03-07 10:07 jeff
+
+ * FS/FS/tax_rate.pm: pass format and correct filenames
+
+2010-03-05 11:09 ivan
+
+ * FS/FS/part_export/grandstream.pm: random nits for grandstream
+ provisioning :UI, docs, java path, s/system/IPC::Run/ for better
+ error handling. RT#7132
+
+2010-03-05 11:02 ivan
+
+ * httemplate/edit/part_device.html,
+ FS/FS/part_export/grandstream.pm: random nits for grandstream
+ provisioning :UI, docs, java path, s/system/IPC::Run/ for better
+ error handling. RT#7132
+
+2010-03-05 10:25 ivan
+
+ * httemplate/docs/ssh.html: moved to wiki
+
+2010-03-05 10:24 ivan
+
+ * FS/FS/part_export/: domain_shellcommands.pm,
+ forward_shellcommands.pm, phone_shellcommands.pm,
+ shellcommands.pm, shellcommands_withdomain.pm, textradius.pm,
+ vpopmail.pm, www_shellcommands.pm: point to wiki docs
+
+2010-03-05 10:10 ivan
+
+ * FS/FS/part_export/grandstream.pm,
+ httemplate/edit/part_device.html: random nits for grandstream
+ provisioning :UI, docs, java path, s/system/IPC::Run/ for better
+ error handling. RT#7132
+
+2010-03-03 21:31 jeff
+
+ * FS/FS/cust_bill.pm: always show the previous section when
+ previous_balance-summary_only is enabled
+
+2010-03-02 17:39 jeff
+
+ * FS/FS/tax_rate.pm, httemplate/misc/tax-import.cgi: tweak,
+ rearrange, and avoid the vacuum
+
+2010-03-02 17:00 jeff
+
+ * httemplate/edit/cust_main/bottomfixup.js: clear geocode when we
+ DO have plus four
+
+2010-03-02 09:18 ivan
+
+ * httemplate/: search/elements/search-html.html,
+ misc/bulk_pkg_increment_bill.cgi, search/cust_pkg.cgi: UI: get
+ rid of undocumented extra_choices_callback in search.html: a
+ terrible place to put action links from a UI perspective, move
+ package links to html_init, now consitent with customer links,
+ change label on popup from "increment bill date" to "increment
+ next bill date", RT#7132
+
+2010-03-01 14:18 ivan
+
+ * httemplate/edit/elements/svc_Common.html: fixup editing w/manual
+ inventory, RT#7010
+
+2010-03-01 14:00 ivan
+
+ * httemplate/elements/select-table.html: fix up editing services
+ w/manual select from inventory, RT#7010
+
+2010-03-01 13:56 ivan
+
+ * FS/FS/svc_Common.pm: fix up editing services w/manual select from
+ inventory, RT#7010
+
+2010-03-01 13:36 ivan
+
+ * httemplate/edit/elements/edit.html: fix up editing services
+ w/manual select from inventory, RT#7010
+
+2010-03-01 13:05 ivan
+
+ * httemplate/search/inventory_item.html: fix inventory item links
+ to non-svc_acct services, RT#7010
+
+2010-03-01 11:38 ivan
+
+ * FS/FS/svc_Common.pm: enable manual selection from inventory
+ dropdowns for svc_broadband, svc_external & svc_phone, RT#7010
+
+2010-03-01 11:29 ivan
+
+ * httemplate/: edit/part_svc.cgi, edit/elements/edit.html,
+ edit/elements/svc_Common.html, elements/select-table.html: enable
+ manual selection from inventory dropdowns for svc_broadband,
+ svc_external & svc_phone, RT#7010
+
+2010-03-01 11:10 jeff
+
+ * Makefile: get along better with RTless installs
+
+2010-02-26 19:15 ivan
+
+ * httemplate/edit/cust_refund.cgi: expiration date not meaningful
+ for non-CARD, RT#7419
+
+2010-02-26 18:59 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: fix rare "Illegal division
+ by zero" error applying things when using weights, RT#7491
+
+2010-02-24 15:32 mark
+
+ * httemplate/: misc/bulk_pkg_increment_bill.cgi,
+ misc/process/bulk_pkg_increment_bill.cgi, search/cust_pkg.cgi:
+ RT#7132: bulk increment package bill dates
+
+2010-02-22 02:35 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: communigate: domain account
+ defaults, RT#7083
+
+2010-02-22 01:18 ivan
+
+ * httemplate/edit/svc_domain.cgi: communigate: domain account
+ defaults, RT#7083
+
+2010-02-22 01:14 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/elements/tr.html,
+ httemplate/view/svc_acct/basics.html,
+ httemplate/view/svc_domain/acct_defaults.html,
+ httemplate/view/svc_domain/basics.html,
+ httemplate/view/svc_domain/dns.html,
+ httemplate/edit/svc_acct.cgi: communigate: domain account
+ defaults, RT#7083
+
+2010-02-21 23:13 ivan
+
+ * FS/FS/svc_domain.pm, FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_domain.cgi, httemplate/view/svc_domain.cgi:
+ communigate: domain aliases, enabled services & administrator
+ domain, RT#7083
+
+2010-02-21 19:22 ivan
+
+ * FS/FS/Schema.pm, FS/FS/part_export/communigate_pro.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ httemplate/edit/svc_domain.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/view/svc_domain.cgi,
+ httemplate/edit/process/svc_acct.cgi: communigate: domain
+ aliases, enabled services, RT#7083
+
+2010-02-21 14:54 ivan
+
+ * FS/FS/part_export/communigate_pro.pm, FS/FS/Conf.pm,
+ httemplate/edit/svc_forward.cgi, httemplate/view/svc_forward.cgi:
+ communigate forwarders, RT#7083
+
+2010-02-21 01:19 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct/basics.html: communigate, RT#7083
+
+2010-02-20 23:32 ivan
+
+ * httemplate/view/svc_acct/basics.html: communigate, RT#7083
+
+2010-02-20 23:28 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/view/elements/svc_export_settings.html,
+ httemplate/view/svc_acct/tr.html: communigate, RT#7083
+
+2010-02-20 19:16 ivan
+
+ * FS/FS/part_svc.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/elements/communigate_pro-accessmodes.html,
+ httemplate/pref/pref-process.html, httemplate/pref/pref.html,
+ httemplate/view/elements/svc_export_settings.html,
+ httemplate/view/svc_acct/basics.html: communigate, RT#7083
+
+2010-02-20 14:34 ivan
+
+ * FS/FS/Conf.pm: communigate pro provisioning, RT#7083
+
+2010-02-20 14:31 ivan
+
+ * httemplate/view/elements/svc_export_settings.html,
+ httemplate/view/svc_acct/basics.html,
+ httemplate/view/svc_acct/change_svc.html,
+ httemplate/view/svc_acct/change_svc_form.html,
+ httemplate/view/svc_acct/hosting.html,
+ httemplate/view/svc_acct/radius_usage.html,
+ httemplate/view/svc_acct/usage.html, FS/FS/Record.pm,
+ FS/FS/Schema.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/communigate_pro.pm,
+ httemplate/edit/svc_acct.cgi, httemplate/view/svc_acct.cgi,
+ httemplate/view/svc_domain.cgi: communigate pro provisioning,
+ RT#7083
+
+2010-02-17 19:48 mark
+
+ * FS/bin/freeside-void-payments: Documentation cleanup
+
+2010-02-17 01:00 ivan
+
+ * httemplate/edit/svc_Common.html: fix svc_pbx provisioniing
+
+2010-02-17 00:30 ivan
+
+ * FS/FS/part_export.pm, FS/FS/part_export/communigate_pro.pm,
+ FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/svc_Common.pm,
+ FS/FS/svc_domain.pm, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/view/svc_domain.cgi, FS/FS/cust_svc.pm: communigate
+ pro provisioning, RT#7083
+
+2010-02-16 10:36 ivan
+
+ * FS/FS/Conf.pm,
+ fs_selfservice/FS-SelfService/cgi/make_ach_payment.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ FS/FS/ClientAPI/MyAccount.pm: option to uncheck the save checkbox
+ in self-service by default, RT#6955
+
+2010-02-15 18:19 jeff
+
+ * FS/FS/tax_rate.pm, httemplate/misc/tax-import.cgi,
+ httemplate/misc/process/tax-import.cgi: refactor cch tax import
+ to remove tons of false laziness and improve flexibility; allow
+ reload from local files
+
+2010-02-15 08:34 jeff
+
+ * htetc/handler.pl: lexical instead of dynamic warnings
+
+2010-02-15 06:39 jeff
+
+ * htetc/handler.pl: get rid of some very annoying and pointless
+ noise
+
+2010-02-14 18:09 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Misc.pm, debian/control: switch to
+ Email::Sender and add options for every kind of mail encryption &
+ authentication, RT#7285
+
+2010-02-12 18:53 ivan
+
+ * FS/FS/svc_external.pm, httemplate/edit/svc_Common.html,
+ httemplate/edit/svc_external.cgi,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/svc_external.html: svc_external.title
+ from inventory, RT#7010
+
+2010-02-12 18:16 ivan
+
+ * httemplate/search/: inventory_item.html: fix inventory item
+ search w/customer classes
+
+2010-02-12 15:44 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: don't return an error about
+ changing the cust_main record to the user as a payment processing
+ error, log a bunch of verbose stuff in this case so we can track
+ down wtf is going on, RT#6955
+
+2010-02-12 14:42 ivan
+
+ * httemplate/search/: svc_acct.cgi, elements/search-html.html:
+ totals time used on svc_acct report
+
+2010-02-12 13:49 ivan
+
+ * fs_selfservice/java/freeside_create_ticket_example.java: tyop,
+ RT#7007
+
+2010-02-12 11:28 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/TicketSystem/RT_Internal.pm,
+ fs_selfservice/java/freeside_create_ticket_example.java,
+ fs_selfservice/perl/xmlrpc-create_ticket.pl: add mime_type option
+ to self-service ticket create, RT#7007
+
+2010-02-12 11:14 ivan
+
+ * fs_selfservice/perl/xmlrpc-create_ticket.pl,
+ FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/java/freeside_create_ticket_example.java: add
+ queue option to self-service ticket create, RT#7007
+
+2010-02-11 19:26 ivan
+
+ * FS/FS/part_export/thirdlane.pm: add omit_countrycode option,
+ RT#7379
+
+2010-02-11 17:35 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm, FS/FS/Conf.pm,
+ fs_selfservice/java/freeside_create_ticket_example.java,
+ fs_selfservice/perl/xmlrpc-create_ticket.pl,
+ httemplate/view/cust_svc.cgi, rt/lib/RT/URI/freeside.pm: add
+ ticket creation to self-service API, RT#7007
+
+2010-02-11 15:14 jeff
+
+ * FS/FS/part_export/grandstream.pm: fix quoting
+
+2010-02-09 18:12 mark
+
+ * FS/FS/: pay_batch.pm, pay_batch/RBC.pm: RT#7274: accept CR/LF in
+ RBC batch import
+
+2010-02-09 13:49 ivan
+
+ * rt/lib/: RT.pm, RT.pm.in: add NoSignalHandlers option to
+ RT::Init() so we can pull things up the way we want under
+ self-service, RT#7007
+
+2010-02-09 11:56 ivan
+
+ * httemplate/edit/process/discount.html: doh, fix for discounts
+ winding up with both amount and percent, RT#6679
+
+2010-02-08 19:37 ivan
+
+ * rt/lib/RT/Config.pm: fix RT wackiness when loaded concurrently
+ with Fcntl
+
+2010-02-08 11:01 ivan
+
+ * FS/: FS.pm, FS/h_svc_pbx.pm, t/h_svc_pbx.t, MANIFEST:
+ h_svc_pbx.pm, RT#7322
+
+2010-02-08 07:37 jeff
+
+ * FS/FS/Mason.pm, FS/FS/Schema.pm, FS/FS/export_device.pm,
+ FS/FS/part_device.pm, FS/FS/part_export.pm,
+ FS/FS/phone_device.pm, FS/FS/part_export/grandstream.pm,
+ FS/FS/part_export/netsapiens.pm, FS/t/export_device.t, Makefile,
+ FS/MANIFEST, httemplate/edit/part_device.html,
+ httemplate/edit/process/part_device.html,
+ httemplate/elements/checkboxes-table.html,
+ httemplate/misc/phone_device_config.html,
+ httemplate/view/svc_phone.cgi: grandstream device configuration
+ support #4220
+
+2010-02-05 18:57 ivan
+
+ * FS/FS/: cust_pkg_discount.pm, part_pkg/flat.pm: discount
+ reporting, RT#6679
+
+2010-02-05 18:54 ivan
+
+ * FS/FS/Schema.pm, FS/FS/Mason.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_bill_pkg_discount.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS.pm, FS/MANIFEST,
+ FS/FS/Report/Table/Monthly.pm, FS/t/cust_bill_pkg_discount.t,
+ httemplate/elements/menu.html,
+ httemplate/misc/delete-cust_pkg_discount.html,
+ httemplate/search/cust_bill_pkg_discount.html,
+ httemplate/search/cust_pkg_discount.html,
+ httemplate/search/report_cust_bill_pkg_discount.html,
+ httemplate/search/report_cust_pkg_discount.html: discount
+ reporting, RT#6679
+
+2010-02-04 19:02 ivan
+
+ * httemplate/edit/process/cust_pkg_discount.html: discounts,
+ RT#6679
+
+2010-02-04 18:39 ivan
+
+ * httemplate/view/cust_main/order_pkg_link.html, FS/FS/cust_pkg.pm,
+ FS/FS/cust_pkg_discount.pm, FS/FS/discount.pm,
+ FS/FS/part_pkg/flat.pm, httemplate/edit/cust_pkg_discount.html,
+ httemplate/edit/discount.html,
+ httemplate/edit/process/discount.html,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/elements/tr-select-discount.html,
+ httemplate/misc/order_pkg.html,
+ httemplate/view/cust_main/packages/status.html: discounts,
+ RT#6679
+
+2010-02-04 12:39 ivan
+
+ * FS/FS/discount.pm, httemplate/edit/discount.html,
+ httemplate/elements/select-discount.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-input-text.html,
+ httemplate/elements/tr-select-discount.html,
+ httemplate/elements/tr-select.html: discounts, RT#6679
+
+2010-02-04 02:00 ivan
+
+ * httemplate/edit/cust_main/top_misc.html, FS/FS/AccessRight.pm,
+ httemplate/elements/search-cust_main.html: add ability to edit
+ referring customer, RT#7174
+
+2010-02-03 22:53 ivan
+
+ * httemplate/: elements/phonenumber.html, pref/pref-process.html,
+ pref/pref.html: snom autodial integration
+
+2010-02-03 20:48 ivan
+
+ * FS/FS/part_event/Condition/every.pm: fix SQL error with date
+ field when using this condition, RT#7218
+
+2010-02-03 11:27 ivan
+
+ * FS/FS/cust_main.pm: better error msg for profiling
+
+2010-02-02 18:16 ivan
+
+ * FS/FS/svc_acct.pm: fix time limits getting double added if the
+ service is provisioned before first billing
+
+2010-01-30 23:04 ivan
+
+ * FS/FS/discount.pm: discounts, RT#6679
+
+2010-01-30 19:00 ivan
+
+ * httemplate/browse/part_pkg_taxclass.html: fix header on report
+
+2010-01-30 18:57 ivan
+
+ * FS/FS/: part_pkg/agent.pm, part_pkg/bulk.pm,
+ cust_pkg_discount.pm, part_pkg/flat.pm,
+ part_pkg/flat_comission.pm, part_pkg/flat_comission_cust.pm,
+ part_pkg/flat_comission_pkg.pm, part_pkg/prorate.pm,
+ part_pkg/recur_Common.pm, part_pkg/rt_time.pm,
+ part_pkg/sesmon_hour.pm, part_pkg/sesmon_minute.pm,
+ part_pkg/sql_external.pm, part_pkg/sql_generic.pm,
+ part_pkg/sqlradacct_hour.pm, part_pkg/subscription.pm,
+ part_pkg/voip_sqlradacct.pm: discounts, RT#6679
+
+2010-01-30 15:00 ivan
+
+ * httemplate/: view/cust_main/packages/package.html,
+ edit/cust_pkg_discount.html, edit/process/cust_pkg_discount.html,
+ misc/delete-cust_pkg_discount.html,
+ view/cust_main/packages/status.html: discounts, RT#6679
+
+2010-01-30 12:05 ivan
+
+ * httemplate/: misc/cust-part_pkg.cgi, misc/order_pkg.html,
+ view/cust_main/packages.html, elements/select-part_pkg.html,
+ elements/select-table.html,
+ elements/tr-select-cust-part_pkg.html,
+ view/cust_main/order_pkg_link.html: discounts, RT#6679
+
+2010-01-30 00:55 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_pkg_discount.pm,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/elements/select-discount.html,
+ httemplate/elements/tr-select-discount.html,
+ httemplate/misc/order_pkg.html,
+ httemplate/view/cust_main/packages/status.html,
+ httemplate/view/cust_main/packages/package.html: discounts,
+ RT#6679
+
+2010-01-29 23:55 ivan
+
+ * FS/FS/part_pkg/rt_time.pm: giving it a weight avoids weight use
+ of uninitialized value in sort messages in part_pkg.pm
+
+2010-01-29 23:38 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/AccessRight.pm, FS/FS/Mason.pm,
+ FS/FS/Schema.pm, FS/FS/cust_pkg_discount.pm, FS/FS/discount.pm,
+ FS/FS/part_pkg.pm, FS/FS/part_pkg/flat.pm,
+ FS/t/cust_pkg_discount.t, FS/t/discount.t,
+ httemplate/browse/discount.html, httemplate/edit/discount.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/discount.html,
+ httemplate/elements/menu.html,
+ httemplate/elements/tr-input-text.html: discounts, RT#6679
+
+2010-01-29 12:21 ivan
+
+ * FS/FS/Conf.pm: add Cleartext-Password to radius-password export
+ options, RT#7150
+
+2010-01-29 11:52 ivan
+
+ * FS/FS/part_export/thirdlane.pm: ssl option
+
+2010-01-26 21:27 ivan
+
+ * FS/FS/part_export/thirdlane.pm: URI escape pw, RT#7051
+
+2010-01-26 21:21 ivan
+
+ * FS/FS/part_export/thirdlane.pm: add port option, RT#7051
+
+2010-01-26 02:40 ivan
+
+ * FS/FS/svc_pbx.pm: deleting an svc_pbx unlinks svc_phones and
+ deletes svc_accts, RT#7051
+
+2010-01-26 02:34 ivan
+
+ * FS/FS/part_export/thirdlane.pm: admin operations from svc_acct
+ records
+
+2010-01-26 02:05 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/edit/process/svc_acct.cgi: more
+ fallout from default pw encryption: fix error reporting when
+ entering a bad password
+
+2010-01-26 00:35 ivan
+
+ * FS/FS/: svc_pbx.pm, part_export/thirdlane.pm: svc_pbx.title
+ uniqueness (kludgy) and force to alphanumeric+space and 19 char
+ max when using thirdlane (conservative guess for 1st pass
+ implementation), RT#7051
+
+2010-01-26 00:18 ivan
+
+ * FS/FS/svc_pbx.pm: label isn't thirdlane-specific, RT#7051
+
+2010-01-25 23:15 ivan
+
+ * FS/FS/part_export/thirdlane.pm: figured out success/failure
+ reporting, and wrote DID creation/deletion/assign/unassign,
+ RT#7051
+
+2010-01-25 22:15 ivan
+
+ * FS/FS/part_export/thirdlane.pm: initial thirdlane export w/tenant
+ insert/delete/replace, RT#7051
+
+2010-01-25 22:14 ivan
+
+ * FS/FS/svc_pbx.pm: fix label method and doc work
+
+2010-01-25 22:14 ivan
+
+ * FS/FS/svc_Common.pm: remove debugging
+
+2010-01-25 20:47 jayce
+
+ * FS/FS/: part_pkg/rt_time.pm, TicketSystem/RT_External.pm: First
+ version of RT Billing pkg. Basic concept is if a customer has
+ this package, then any time added to ticket comments in RT will
+ be added up and multiplied by the base rate, with each entry
+ showing up as a lineitem on their next invoice.
+
+ This has not been used in production yet by anybody, it was just
+ a proposal done for a customer. Modified Files:
+ TicketSystem/RT_External.pm Added Files:
+ part_pkg/rt_time.pm
+
+2010-01-25 14:14 ivan
+
+ * httemplate/elements/select-svc_pbx.html,
+ httemplate/elements/tr-select-svc_pbx.html, FS/FS/Schema.pm,
+ FS/FS/svc_Common.pm, FS/FS/svc_acct.pm, FS/FS/svc_pbx.pm,
+ FS/FS/svc_phone.pm, httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_phone.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_phone.cgi:
+ linking DIDs and users to PBXes, RT#7051
+
+2010-01-25 09:41 ivan
+
+ * httemplate/edit/part_svc.cgi, FS/MANIFEST, FS/FS/Mason.pm,
+ FS/FS/Schema.pm: initial svc_pbx implementation, RT#7051
+
+2010-01-25 09:35 ivan
+
+ * FS/: FS/svc_pbx.pm, t/svc_pbx.t: initial svc_pbx implementation,
+ RT#7051
+
+2010-01-24 16:31 ivan
+
+ * FS/FS/svc_acct.pm: fix password length checks from applyhing to
+ already-crypted legacy passwords, RT#7139
+
+2010-01-24 14:38 ivan
+
+ * httemplate/elements/: overlibmws.js, overlibmws_crossframe.js,
+ overlibmws_draggable.js, overlibmws_iframe.js: update overlib to
+ upstream v281 (jan 20, 2010)
+
+2010-01-24 12:37 ivan
+
+ * FS/FS/UI/Web.pm: right-align customer # in reports
+
+2010-01-24 11:53 ivan
+
+ * httemplate/search/cust_bill.html: fix alignment of customer data
+
+2010-01-24 11:52 ivan
+
+ * httemplate/search/cust_bill_pay.html: separate out the invoice
+ and payment info into multiple columns, and fix application date
+ showing as payment date
+
+2010-01-21 00:54 ivan
+
+ * FS/FS/Conf.pm: correct description for selfservice-body_footer
+
+2010-01-21 00:38 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: more
+ self-service skinning config options, and start taking a stab at
+ reorganizing config sections, RT#6893
+
+2010-01-21 00:34 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/ach_payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/change_bill.html,
+ fs_selfservice/FS-SelfService/cgi/change_password.html,
+ fs_selfservice/FS-SelfService/cgi/change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/header.html,
+ fs_selfservice/FS-SelfService/cgi/make_ach_payment.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_bill.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_password.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/process_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/process_order_recharge.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_external.html,
+ fs_selfservice/FS-SelfService/cgi/provision.html,
+ fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_prepay.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_results.html,
+ fs_selfservice/FS-SelfService/cgi/view_cdr_details.html,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html,
+ fs_selfservice/FS-SelfService/cgi/view_usage.html,
+ fs_selfservice/FS-SelfService/cgi/view_usage_details.html,
+ fs_selfservice/FS-SelfService/cgi/image.cgi,
+ FS/FS/ClientAPI/MyAccount.pm, httemplate/config/config-view.cgi,
+ FS/FS/Conf.pm: more self-service skinning config options, and
+ start taking a stab at reorganizing config sections, RT#6893
+
+2010-01-20 17:25 ivan
+
+ * FS/: FS/ClientAPI.pm, FS/Mason.pm,
+ bin/freeside-selfservice-server: fix self-service fallout from RT
+ 3.8, RT#6640
+
+2010-01-19 15:11 jeff
+
+ * FS/FS/part_export/prizm.pm: SM in new window
+
+2010-01-19 09:48 jeff
+
+ * FS/FS/cust_main.pm: eliminate harmless uninitialized value noise
+
+2010-01-18 20:54 ivan
+
+ * FS/FS/pay_batch/paymentech.pm: patch from Peter Loeppky to make
+ sure some xml fields don't get larger than what paymentech wants
+
+2010-01-18 19:24 mark
+
+ * httemplate/search/elements/search-csv.html: Fix mime type for CSV
+ files, RT#1526
+
+2010-01-18 18:09 jeff
+
+ * FS/FS/part_export/domreg_opensrs.pm: don't really need debugging
+ on
+
+2010-01-18 18:03 jeff
+
+ * FS/FS/: cust_bill_ApplicationCommon.pm,
+ part_export/domreg_opensrs.pm: debugged and tested opensrs export
+
+2010-01-18 16:13 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: fix color/style after
+ removal of billpkgnum display
+
+2010-01-16 22:40 ivan
+
+ * FS/FS/ClientAPI/MasonComponent.pm: fix for
+ selfservice_server-base_url without a trailing /
+
+2010-01-16 19:31 ivan
+
+ * FS/FS/cust_main.pm: return an error for 0 amount payments to
+ avoid a $0 payment getting stuck in declined or captured status,
+ RT#6993
+
+2010-01-16 18:34 ivan
+
+ * FS/FS/svc_phone.pm: strip non-digits and 1- when searching for
+ phone numbers, RT#7000
+
+2010-01-16 18:22 ivan
+
+ * FS/FS/: svc_acct.pm: remove svcnum from searchable fields for
+ svc_acct... custnum and invnum are user-visible, other database
+ ids, not so much. also, breaks phone number search w/current Pg,
+ RT#7000
+
+2010-01-16 17:20 ivan
+
+ * ChangeLog: preparing for 2.1.0
+
+2010-01-16 16:22 jeff
+
+ * FS/FS/cust_pkg.pm: work around bug in pre-perl5.10 which is at
+ best noisy and at worst missorting
+
+2010-01-16 15:39 ivan
+
+ * FS/FS/part_pkg/: cdr_termination.pm: add usage_mandate option to
+ termination price plan, RT#6932
+
+2010-01-16 15:06 ivan
+
+ * httemplate/search/cust_svc.html: fix searching for unlinked
+ services? RT#7059
+
+2010-01-14 19:59 ivan
+
+ * httemplate/edit/process/part_pkg.cgi: remove extraneous debugging
+
+2010-01-12 19:14 ivan
+
+ * FS/FS/tax_rate.pm: if ignore_icalculable_taxes is on, don't call
+ the errors fatal
+
+2010-01-12 09:08 jeff
+
+ * FS/FS/part_export/prizm.pm: add some debugging
+
+2010-01-12 08:40 jeff
+
+ * FS/FS/part_export/prizm.pm: eliminate harmless argument isn't
+ numeric messages
+
+2010-01-09 15:41 ivan
+
+ * rt/etc/RT_SiteConfig.pm: QuickCreateLong obsolete with the better
+ quick create in 3.8
+
+2010-01-09 15:38 ivan
+
+ * rt/share/html/NoAuth/css/freeside2.1/main.css: bring in
+ freeside.css
+
+2010-01-09 15:36 ivan
+
+ * rt/: etc/RT_SiteConfig.pm, lib/RT/Config.pm, FREESIDE_MODIFIED,
+ etc/RT_Config.pm, etc/RT_Config.pm.in,
+ share/html/NoAuth/css/freeside2.1/InHeader,
+ share/html/NoAuth/css/freeside2.1/admin.css,
+ share/html/NoAuth/css/freeside2.1/base.css,
+ share/html/NoAuth/css/freeside2.1/boxes.css,
+ share/html/NoAuth/css/freeside2.1/forms.css,
+ share/html/NoAuth/css/freeside2.1/freeside.css,
+ share/html/NoAuth/css/freeside2.1/layout.css,
+ share/html/NoAuth/css/freeside2.1/login.css,
+ share/html/NoAuth/css/freeside2.1/main.css,
+ share/html/NoAuth/css/freeside2.1/misc.css,
+ share/html/NoAuth/css/freeside2.1/msie.css,
+ share/html/NoAuth/css/freeside2.1/msie6.css,
+ share/html/NoAuth/css/freeside2.1/nav.css,
+ share/html/NoAuth/css/freeside2.1/portlets.css,
+ share/html/NoAuth/css/freeside2.1/ticket-lists.css,
+ share/html/NoAuth/css/freeside2.1/ticket-search.css,
+ share/html/NoAuth/css/freeside2.1/ticket.css,
+ share/html/NoAuth/css/freeside2.1/tools.css,
+ share/html/NoAuth/css/freeside2.1/yui-fonts.css,
+ share/html/NoAuth/css/freeside2.1/images/dhandler,
+ share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png:
+ add a "freeside2.1" stylesheet to isolate our skinning changes
+
+2010-01-09 14:48 ivan
+
+ * Makefile, rt/FREESIDE_MODIFIED, rt/sbin/rt-setup-database.in:
+ fixup initial RT setup for 3.8
+
+2010-01-09 02:03 ivan
+
+ * FS/FS/Record.pm: need DBIx::DBSchema w/quoted_default to make
+ this all work right :/
+
+2010-01-09 00:21 ivan
+
+ * FS/bin/freeside-upgrade: undo damage from DBIx::DBSchema 0.37_03
+
+2010-01-08 10:55 jeff
+
+ * httemplate/loginout/logout.html: this is part of the logout link
+ too! (RT 1330 & 5518)
+
+2010-01-08 08:05 jeff
+
+ * Makefile, htetc/freeside-base1.99.conf,
+ htetc/freeside-base1.conf, htetc/freeside-base2.conf,
+ htetc/htpasswd.logout, httemplate/elements/header.html,
+ httemplate/elements/logout.html,
+ httemplate/elements/rs_init_object.html,
+ httemplate/elements/xmlhttp.html: add a logout link (RT 1330 &
+ 5518)
+
+2010-01-07 01:48 mark
+
+ * FS/FS/: pay_batch.pm, pay_batch/RBC.pm: Fix problems with RBC
+ batch import (RT#6967)
+
+2010-01-05 20:47 ivan
+
+ * FS/FS/svc_acct.pm: eliminate spurious warnings on usage
+ changes...
+
+2010-01-05 20:43 ivan
+
+ * FS/FS/cust_main.pm: eliminate ya "use of uninitialized value"
+ warning
+
+2010-01-05 20:41 ivan
+
+ * FS/FS/cust_main.pm: doc
+
+2010-01-03 20:13 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill_ApplicationCommon.pm,
+ FS/FS/part_export/domreg_opensrs.pm, bin/opensrs_domain_pkgs:
+ untested triggering of export on payments, requires config enable
+ (RT5825)
+
+2010-01-03 19:23 jeff
+
+ * bin/monitor: add some debugging
+
+2010-01-03 00:04 ivan
+
+ * rt/: share/html/Ticket/Display.html,
+ share/html/Admin/Users/Modify.html,
+ share/html/Elements/AddCustomers,
+ share/html/Elements/EditCustomers,
+ share/html/Ticket/ModifyCustomers.html,
+ share/html/Ticket/Elements/AddCustomers,
+ share/html/Ticket/Elements/EditCustomers,
+ share/html/Ticket/Elements/ShowCustomers,
+ share/html/Ticket/Elements/ShowSummary,
+ share/html/Ticket/Elements/ShowTransactionAttachments,
+ share/html/Ticket/Elements/Tabs, FREESIDE_MODIFIED: port
+ skinning, customer display/edit from RT 3.6 integration to RT 3.8
+ integration
+
+2010-01-02 23:40 ivan
+
+ * htetc/: freeside-base1.99.conf, freeside-base1.conf,
+ freeside-base2.conf, handler.pl: port skinning, customer
+ display/edit from RT 3.6 integration to RT 3.8 integration
+
+2010-01-02 22:24 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm: attempt to get more
+ information on errors using an RT-instansiated session??
+
+2010-01-02 19:07 ivan
+
+ * Makefile, FS/FS/Mason.pm, FS/FS/Mason/Request.pm,
+ rt/etc/RT_SiteConfig.pm: can't we all just get along (with RT
+ 3.8.7)?
+
+2009-12-31 15:03 mark
+
+ * FS/FS/pay_batch/RBC.pm: Add pre-header line
+
+2009-12-31 06:31 ivan
+
+ * rt/: Makefile, Makefile.in: prevent fatality
+
+2009-12-31 06:11 ivan
+
+ * rt/Makefile: otherwise it aborts bitchinga bout missing
+ schema.Oracle
+
+2009-12-31 06:08 ivan
+
+ * rt/: Makefile, config.status: install-sh????
+
+2009-12-31 05:59 ivan
+
+ * rt/: .gitignore, Makefile, config.status, bin/mason_handler.fcgi,
+ bin/mason_handler.scgi, bin/mason_handler.svc, bin/rt-crontool,
+ bin/rt-mailgate, etc/RT_Config.pm, etc/RT_Config.pm.in,
+ etc/RT_SiteConfig.pm, etc/schema.Pg, etc/schema.mysql,
+ html/autohandler, html/index.html, html/l, lib/RT.pm,
+ lib/RT/Groups_Overlay.pm, lib/RT/Record.pm,
+ lib/RT/SearchBuilder.pm, lib/RT/Ticket_Overlay.pm,
+ lib/RT/Transaction_Overlay.pm, lib/RT/User_Overlay.pm,
+ lib/RT/Users_Overlay.pm, lib/RT/I18N/no.po, lib/RT/I18N/pt_br.po,
+ lib/RT/I18N/pt_pt.po, lib/RT/I18N/zh_cn.po, lib/RT/I18N/zh_tw.po,
+ lib/t/00smoke.t, lib/t/create_data.pl, lib/t/setup_regression.t,
+ sbin/extract_pod_tests, sbin/regression_harness,
+ sbin/rt-setup-database.in: merging 3.8.7!!!
+
+2009-12-31 05:15 ivan
+
+ * rt/: share/html/NoAuth/RichText/FCKeditor/editor/lang/af.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/bs.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/da.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/zh-cn.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/cs.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ro.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/uk.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/no.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/pt.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/vi.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/pt-br.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/bn.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/et.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/de.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/en-au.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ca.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/el.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/sk.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_colorselector.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_listprop.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_replace.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_select.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_smiley.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_textfield.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ms.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/wsc/ciframe.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/wsc/tmpFrameset.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/wsc/w.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_anchor.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_button.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_docprops.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_hiddenfield.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_link.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_paste.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_source.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_textarea.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_form.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_radiobutton.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_specialchar.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_tablecell.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_table.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_checkbox.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_div.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/logo_fckeditor.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/logo_fredck.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash/fck_flash.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash/fck_flash_preview.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/fck_dialog_common.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/fck_dialog_common.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/locked.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/reset.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/unlocked.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image/fck_image.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image/fck_image_preview.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_select/fck_select.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.cfm,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.php,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_docprops/fck_document_preview.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_link/fck_link.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template1.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template2.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template3.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/autogrow/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/dragresizetable/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/_sample/sample.config.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/_sample/sample.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/fck_placeholder.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/placeholder.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/de.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/en.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/es.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/fr.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/it.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/pl.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/simplecommands/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/plugins/tablecommands/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/_fckviewstrips.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_dialog.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_dialog_ie6.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_editor.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_strip.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.rtl.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/sprites.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/sprites.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.arrowright.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.buttonarrow.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.collapse.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.end.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.expand.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.separator.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.start.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_dialog.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_dialog_ie6.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_editor.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_strip.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.rtl.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/sprites.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/sprites.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.arrowright.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.bg.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.collapse.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.end.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.expand.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.separator.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.start.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_editor.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_dialog.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_dialog_ie6.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_strip.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.rtl.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/sprites.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/sprites.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.arrowright.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.buttonarrow.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.buttonbg.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.collapse.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.end.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.expand.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.separator.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.start.gif,
+ share/html/NoAuth/iCal/dhandler,
+ share/html/NoAuth/js/cascaded.js, share/html/NoAuth/js/ahah.js,
+ share/html/NoAuth/js/autohandler, share/html/NoAuth/js/class.js,
+ share/html/NoAuth/js/combobox.js, share/html/NoAuth/js/list.js,
+ share/html/NoAuth/js/titlebox-state.js,
+ share/html/NoAuth/js/util.js,
+ share/html/NoAuth/js/scriptaculous/controls.js,
+ share/html/NoAuth/js/scriptaculous/effects.js,
+ share/html/NoAuth/js/scriptaculous/scriptaculous.js,
+ share/html/NoAuth/js/IE7/IE8.js,
+ share/html/NoAuth/js/prototype/prototype.js,
+ share/html/Helpers/CalPopup.html,
+ share/html/NoAuth/js/IE7/IE7.js,
+ share/html/NoAuth/js/IE7/blank.gif,
+ share/html/NoAuth/js/IE7/ie7-recalc.js,
+ share/html/NoAuth/js/IE7/ie7-squish.js,
+ share/html/Widgets/BulkEdit, share/html/Widgets/BulkProcess,
+ share/html/Widgets/ComboBox,
+ share/html/Widgets/FinalizeWidgetArguments,
+ share/html/Widgets/SavedSearch, share/html/Widgets/SelectionBox,
+ share/html/Widgets/TitleBox, share/html/Widgets/TitleBoxEnd,
+ share/html/Widgets/TitleBoxStart,
+ share/html/Widgets/Form/Boolean, share/html/Widgets/Form/Integer,
+ share/html/Widgets/Form/Select, share/html/Widgets/Form/String,
+ share/html/Helpers/Autocomplete/CustomFieldValues,
+ share/html/Helpers/Toggle/TicketBookmark,
+ share/html/Tools/MyDay.html, share/html/Tools/Offline.html,
+ share/html/Tools/index.html, share/html/Tools/Elements/Tabs,
+ share/html/Tools/Reports/CreatedByDates.html,
+ share/html/Tools/Reports/ResolvedByDates.html,
+ share/html/Tools/Reports/ResolvedByOwner.html,
+ share/html/Admin/autohandler, share/html/Admin/index.html,
+ share/html/Admin/Queues/CustomField.html,
+ share/html/Admin/Queues/GroupRights.html,
+ share/html/Admin/Queues/People.html,
+ share/html/Download/CustomFieldValue/dhandler,
+ share/html/Download/Tabular/dhandler,
+ share/html/Tools/Reports/index.html,
+ share/html/Tools/Reports/Elements/Tabs,
+ share/html/Admin/Queues/CustomFields.html,
+ share/html/Admin/Queues/History.html,
+ share/html/Admin/Queues/Modify.html,
+ share/html/Admin/Queues/Scrip.html,
+ share/html/Admin/Queues/Scrips.html,
+ share/html/Admin/Queues/Template.html,
+ share/html/Admin/Queues/Templates.html,
+ share/html/Admin/Queues/index.html,
+ share/html/Admin/Elements/EditCustomFieldValues,
+ share/html/Admin/Elements/EditCustomFieldValuesSource,
+ share/html/Admin/Elements/EditUserComments,
+ share/html/Admin/Elements/GroupTabs,
+ share/html/Admin/Elements/ObjectCustomFields,
+ share/html/Admin/Elements/SelectCustomFieldType,
+ share/html/Admin/Elements/SelectScrip,
+ share/html/Admin/Elements/SelectScripCondition,
+ share/html/Admin/Elements/SelectStage,
+ share/html/Admin/Queues/UserRights.html,
+ share/html/Admin/Elements/ConfigureMyRT,
+ share/html/Admin/Elements/CustomFieldTabs,
+ share/html/Admin/Elements/EditCustomFields,
+ share/html/Admin/Elements/EditScrip,
+ share/html/Admin/Elements/QueueTabs,
+ share/html/Admin/Elements/SelectModifyQueue,
+ share/html/Admin/Elements/SelectScripAction,
+ share/html/Admin/Elements/SelectTemplate,
+ share/html/Admin/Elements/Tabs,
+ share/html/Admin/Elements/AddCustomFieldValue,
+ share/html/Admin/Elements/CreateUserCalled,
+ share/html/Admin/Elements/EditCustomField,
+ share/html/Admin/Elements/EditQueueWatchers,
+ share/html/Admin/Elements/EditScrips,
+ share/html/Admin/Elements/GlobalCustomFieldTabs,
+ share/html/Admin/Elements/ListGlobalCustomFields,
+ share/html/Admin/Elements/ListGlobalScrips,
+ share/html/Admin/Elements/ModifyTemplate,
+ share/html/Admin/Elements/PickCustomFields,
+ share/html/Admin/Elements/PickObjects,
+ share/html/Admin/Elements/SelectCustomFieldLookupType,
+ share/html/Admin/Elements/SelectGroups,
+ share/html/Admin/Elements/SelectModifyGroup,
+ share/html/Admin/Elements/SelectModifyUser,
+ share/html/Admin/Elements/SelectNewGroupMembers,
+ share/html/Admin/Elements/SelectSingleOrMultiple,
+ share/html/Admin/Elements/SelectUsers,
+ share/html/Admin/Elements/SystemTabs,
+ share/html/Admin/Elements/UserTabs,
+ share/html/Admin/Elements/EditTemplates,
+ share/html/Admin/Elements/Header,
+ share/html/Admin/Elements/QueueRightsForUser,
+ share/html/Admin/Elements/SelectCustomField,
+ share/html/Admin/Elements/SelectRights,
+ share/html/Admin/Elements/ShowKeyInfo,
+ share/html/Admin/Elements/ToolTabs,
+ share/html/Admin/Global/GroupRights.html,
+ share/html/Admin/Global/MyRT.html,
+ share/html/Admin/Global/Scrip.html,
+ share/html/Admin/Global/Scrips.html,
+ share/html/Admin/Global/Template.html,
+ share/html/Admin/Global/Templates.html,
+ share/html/Admin/Global/UserRights.html,
+ share/html/Admin/Global/index.html,
+ share/html/Admin/Global/CustomFields/Groups.html,
+ share/html/Admin/Global/CustomFields/Queue-Tickets.html,
+ share/html/Admin/Global/CustomFields/Queue-Transactions.html,
+ share/html/Admin/Global/CustomFields/Queues.html,
+ share/html/Admin/Global/CustomFields/Users.html,
+ share/html/Admin/Global/CustomFields/index.html,
+ share/html/Admin/Users/CustomFields.html,
+ share/html/Admin/Users/GnuPG.html,
+ share/html/Admin/Users/History.html,
+ share/html/Admin/Users/Memberships.html,
+ share/html/Admin/Users/Modify.html,
+ share/html/Admin/Users/MyRT.html,
+ share/html/Admin/CustomFields/GroupRights.html,
+ share/html/Admin/CustomFields/Modify.html,
+ share/html/Admin/CustomFields/Objects.html,
+ share/html/Admin/CustomFields/UserRights.html,
+ share/html/Admin/CustomFields/index.html,
+ share/html/Admin/Tools/Configuration.html,
+ share/html/Admin/Tools/index.html,
+ share/html/Admin/Tools/Shredder/autohandler,
+ share/html/Admin/Tools/Shredder/index.html,
+ share/html/Admin/Tools/Shredder/Elements/DumpFileLink,
+ share/html/Admin/Tools/Shredder/Elements/ObjectCheckBox,
+ share/html/Admin/Tools/Shredder/Elements/PluginArguments,
+ share/html/Admin/Tools/Shredder/Elements/PluginHelp,
+ share/html/Admin/Tools/Shredder/Elements/SelectObjects,
+ share/html/Admin/Tools/Shredder/Elements/SelectPlugin,
+ share/html/Admin/Tools/Shredder/Elements/Error/NoRights,
+ share/html/Admin/Users/index.html,
+ share/html/Admin/Groups/CustomFields.html,
+ share/html/Admin/Groups/GroupRights.html,
+ share/html/Admin/Groups/History.html,
+ share/html/Admin/Groups/Members.html,
+ share/html/Admin/Groups/Modify.html,
+ share/html/Admin/Tools/Shredder/Dumps/dhandler,
+ share/html/Admin/Tools/Shredder/Elements/Error/NoStorage,
+ share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment,
+ share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket,
+ share/html/Admin/Tools/Shredder/Elements/Object/RT--User,
+ share/html/Admin/Groups/UserRights.html,
+ share/html/Admin/Groups/index.html,
+ share/html/Ticket/Create.html, share/html/Ticket/GnuPG.html,
+ share/html/Ticket/Reminders.html,
+ share/html/Ticket/ShowEmailRecord.html,
+ share/html/Ticket/Display.html, share/html/Ticket/History.html,
+ share/html/Ticket/Modify.html,
+ share/html/Ticket/ModifyDates.html,
+ share/html/Ticket/ModifyLinks.html,
+ share/html/Ticket/Update.html, share/html/Ticket/Forward.html,
+ share/html/Ticket/ModifyAll.html,
+ share/html/Ticket/ModifyPeople.html,
+ share/html/Ticket/Elements/AddWatchers,
+ share/html/Ticket/Elements/ShowDates,
+ share/html/Ticket/Elements/ShowSummary,
+ share/html/Ticket/Elements/ShowTime,
+ share/html/Ticket/Elements/BulkLinks,
+ share/html/Ticket/Elements/EditWatchers,
+ share/html/Ticket/Elements/FindAttachments,
+ share/html/Ticket/Elements/ShowParents,
+ share/html/Ticket/Elements/ShowTransactionAttachments,
+ share/html/Ticket/Elements/Tabs,
+ share/html/Ticket/Elements/Bookmark,
+ share/html/Ticket/Elements/EditBasics,
+ share/html/Ticket/Elements/EditCustomFields,
+ share/html/Ticket/Elements/EditDates,
+ share/html/Ticket/Elements/EditPeople,
+ share/html/Ticket/Elements/EditTransactionCustomFields,
+ share/html/Ticket/Elements/PreviewScrips,
+ share/html/Ticket/Elements/Reminders,
+ share/html/Ticket/Elements/ShowAttachments,
+ share/html/Ticket/Elements/ShowBasics,
+ share/html/Ticket/Elements/ShowCustomFields,
+ share/html/Ticket/Elements/ShowDependencies,
+ share/html/Ticket/Elements/ShowGnuPGStatus,
+ share/html/Ticket/Elements/ShowGroupMembers,
+ share/html/Ticket/Elements/ShowHistory,
+ share/html/Ticket/Elements/ShowMembers,
+ share/html/Ticket/Elements/ShowMessageHeaders,
+ share/html/Ticket/Elements/ShowMessageStanza,
+ share/html/Ticket/Elements/ShowPeople,
+ share/html/Ticket/Elements/ShowPriority,
+ share/html/Ticket/Elements/ShowQueue,
+ share/html/Ticket/Elements/ShowRequestor,
+ share/html/Ticket/Elements/ShowTransaction,
+ share/html/Ticket/Elements/ShowUserEntry,
+ share/html/Ticket/Elements/UpdateCc,
+ share/html/SelfService/Error.html,
+ share/html/Ticket/Attachment/dhandler,
+ share/html/Ticket/Attachment/WithHeaders/dhandler,
+ share/html/Ticket/Elements/LoadTextAttachments,
+ share/html/Ticket/Elements/ShowUpdateStatus,
+ share/html/Ticket/Graphs/dhandler,
+ share/html/Ticket/Graphs/index.html,
+ share/html/Ticket/Graphs/Elements/EditGraphProperties,
+ share/html/Ticket/Graphs/Elements/ShowGraph,
+ share/html/Ticket/Graphs/Elements/ShowLegends,
+ share/html/SelfService/Closed.html,
+ share/html/SelfService/Create.html,
+ share/html/SelfService/CreateTicketInQueue.html,
+ share/html/SelfService/Display.html,
+ share/html/SelfService/Prefs.html,
+ share/html/SelfService/Update.html,
+ share/html/SelfService/index.html,
+ share/html/SelfService/Elements/GotoTicket,
+ share/html/SelfService/Elements/Tabs, etc/schema.mysql-4.0,
+ share/html/Approvals/Display.html,
+ share/html/Approvals/autohandler,
+ share/html/Approvals/index.html,
+ share/html/Approvals/Elements/Approve,
+ share/html/Approvals/Elements/PendingMyApproval,
+ share/html/Approvals/Elements/ShowDependency,
+ share/html/Approvals/Elements/Tabs,
+ share/html/SelfService/Attachment/dhandler,
+ share/html/SelfService/Elements/Header,
+ share/html/SelfService/Elements/MyRequests, etc/schema.mysql-4.1,
+ etc/upgrade/shrink_cgm_table.pl,
+ etc/upgrade/split-out-cf-categories,
+ etc/upgrade/split-out-cf-categories.in,
+ etc/upgrade/upgrade-mysql-schema.pl,
+ etc/upgrade/3.8-branded-queues-extension,
+ etc/upgrade/3.8-branded-queues-extension.in,
+ etc/upgrade/3.8-ical-extension,
+ etc/upgrade/3.8-ical-extension.in, etc/upgrade/3.7.1/content,
+ etc/upgrade/3.7.82/content, etc/upgrade/3.7.86/content,
+ etc/upgrade/3.7.87/content, etc/upgrade/3.8.0/content,
+ etc/upgrade/3.8.1/content, etc/upgrade/3.8.3/content,
+ etc/upgrade/3.8.3/schema.Pg, etc/upgrade/3.8.4/content,
+ etc/upgrade/3.8.6/content, etc/upgrade/3.7.10/content,
+ etc/upgrade/3.7.15/content, etc/upgrade/3.7.19/content,
+ etc/upgrade/3.7.3/schema.Oracle, etc/upgrade/3.7.3/schema.Pg,
+ etc/upgrade/3.7.3/schema.mysql, etc/upgrade/3.7.81/schema.Oracle,
+ etc/upgrade/3.7.81/schema.mysql, etc/upgrade/3.7.85/content,
+ etc/upgrade/3.8.2/content: Initial revision
+
+2009-12-31 05:12 ivan
+
+ * rt/: lib/RT/I18N/pt_BR.po, lib/RT/I18N/nb.po,
+ lib/RT/I18N/zh_CN.po, lib/RT/I18N/ar.po, lib/RT/I18N/pt.po,
+ lib/RT/I18N/ru.pm, lib/RT/Shredder/ACE.pm,
+ lib/RT/Shredder/CachedGroupMember.pm,
+ lib/RT/Shredder/Constants.pm, lib/RT/Shredder/Dependencies.pm,
+ lib/RT/Shredder/Dependency.pm,
+ lib/RT/Shredder/ObjectCustomFieldValue.pm,
+ lib/RT/Shredder/Queue.pm, lib/RT/Shredder/User.pm,
+ lib/RT/Shredder/CustomField.pm, lib/RT/Shredder/Exceptions.pm,
+ lib/RT/Shredder/Link.pm, lib/RT/Shredder/Plugin.pm,
+ lib/RT/Shredder/Principal.pm, lib/RT/Shredder/ScripAction.pm,
+ lib/RT/Shredder/ScripCondition.pm,
+ lib/RT/Shredder/Transaction.pm, lib/RT/Condition/ReopenTicket.pm,
+ lib/RT/Shredder/Attachment.pm,
+ lib/RT/Shredder/CustomFieldValue.pm, lib/RT/Shredder/Group.pm,
+ lib/RT/Shredder/GroupMember.pm, lib/RT/Shredder/POD.pm,
+ lib/RT/Shredder/Record.pm, lib/RT/Shredder/Scrip.pm,
+ lib/RT/Shredder/Template.pm, lib/RT/Shredder/Ticket.pm,
+ lib/RT/Shredder/Plugin/Attachments.pm,
+ lib/RT/Shredder/Plugin/Base.pm,
+ lib/RT/Shredder/Plugin/Objects.pm,
+ lib/RT/Shredder/Plugin/SQLDump.pm,
+ lib/RT/Shredder/Plugin/Summary.pm,
+ lib/RT/Shredder/Plugin/Tickets.pm,
+ lib/RT/Shredder/Plugin/Users.pm,
+ lib/RT/Shredder/Plugin/Base/Dump.pm,
+ lib/RT/Shredder/Plugin/Base/Search.pm, lib/RT/Approval/Rule.pm,
+ lib/RT/Condition/CloseTicket.pm, lib/RT/Graph/Tickets.pm,
+ lib/RT/Approval/Rule/Passed.pm,
+ docs/creating_external_custom_fields.pod,
+ docs/extending_clickable_links.pod, docs/gnupg_integration.pod,
+ docs/porting.windows, docs/queue_subject_tag.pod,
+ docs/templates.pod, docs/using_forms_widgets.pod,
+ lib/RT/Approval/Rule/Created.pm,
+ lib/RT/Approval/Rule/NewPending.pm,
+ lib/RT/Approval/Rule/Rejected.pm, lib/RT/Test/Email.pm,
+ lib/RT/Test/Web.pm,
+ docs/design_docs/gnupg_details_on_output_formats, t/00-compile.t,
+ t/00-mason-syntax.t, t/clicky.t, t/cron.t, t/pod.t, t/rtname.t,
+ t/savedsearch.t, t/customfields/access_via_queue.t,
+ t/customfields/sort_order.t, t/approval/basic.t,
+ t/data/configs/apache2.2+fastcgi.conf,
+ t/data/configs/apache2.2+fastcgi.conf.in,
+ t/data/configs/apache2.2+mod_perl.conf,
+ t/data/configs/apache2.2+mod_perl.conf.in,
+ t/data/emails/russian-subject-no-content-type,
+ t/data/emails/subject-with-folding-ws,
+ t/data/emails/text-html-in-russian,
+ t/data/emails/multipart-alternative-with-umlaut,
+ t/data/emails/new-ticket-from-iso-8859-1-full,
+ t/data/emails/notes-uuencoded, t/data/emails/rt-send-cc,
+ t/data/emails/multipart-report, t/data/emails/nested-mime-sample,
+ t/data/emails/nested-rfc-822,
+ t/data/emails/new-ticket-from-iso-8859-1,
+ t/data/emails/text-html-with-umlaut,
+ t/data/emails/crashes-file-based-parser,
+ t/data/emails/lorem-ipsum, t/data/emails/very-long-subject,
+ t/data/emails/8859-15-message-series/dir,
+ t/data/emails/8859-15-message-series/msg1,
+ t/data/emails/8859-15-message-series/msg2,
+ t/data/emails/8859-15-message-series/msg3,
+ t/data/emails/8859-15-message-series/msg4,
+ t/data/emails/8859-15-message-series/msg5,
+ t/data/emails/8859-15-message-series/msg6,
+ t/data/emails/8859-15-message-series/msg7,
+ t/data/gnupg/keyrings/trustdb.gpg,
+ t/data/gnupg/emails/1-signed-MIME-plain.txt,
+ t/data/gnupg/emails/12-encrypted-inline-binary.txt,
+ t/data/gnupg/emails/13-signed-encrypted-MIME-plain.txt,
+ t/data/gnupg/emails/14-signed-encrypted-MIME-attachment.txt,
+ t/data/gnupg/emails/15-signed-encrypted-MIME-binary.txt,
+ t/data/gnupg/emails/16-signed-encrypted-inline-plain.txt,
+ t/data/gnupg/emails/18-signed-encrypted-inline-binary.txt,
+ t/data/gnupg/emails/2-signed-MIME-plain-with-attachment.txt,
+ t/data/gnupg/emails/3-signed-MIME-plain-with-binary.txt,
+ t/data/gnupg/emails/6-signed-inline-with-binary.txt,
+ t/data/gnupg/emails/7-encrypted-MIME-plain.txt,
+ t/data/gnupg/emails/8-encrypted-MIME-with-attachment.txt,
+ t/data/gnupg/emails/9-encrypted-MIME-with-binary.txt,
+ t/data/gnupg/emails/README, t/data/gnupg/keyrings/pubring.gpg,
+ t/data/gnupg/keyrings/secring.gpg,
+ t/data/gnupg/keyrings/signed_old_style_with_attachment.eml,
+ t/data/gnupg/keys/general-at-example.com.2.public.key,
+ t/data/gnupg/keys/general-at-example.com.2.secret.key,
+ t/data/gnupg/keys/general-at-example.com.public.key,
+ t/data/gnupg/keys/general-at-example.com.secret.key,
+ t/data/gnupg/keys/recipient-at-example.com.public.key,
+ t/data/gnupg/keys/recipient-at-example.com.secret.key,
+ t/data/gnupg/keys/rt-recipient-at-example.com.public.key,
+ t/data/gnupg/keys/rt-recipient-at-example.com.secret.key,
+ t/data/gnupg/keys/rt-test-at-example.com.2.public.key,
+ t/data/gnupg/keys/rt-test-at-example.com.2.secret.key,
+ t/data/gnupg/keys/rt-test-at-example.com.public.key,
+ t/data/gnupg/keys/rt-test-at-example.com.secret.key,
+ t/api/currentuser.t, t/api/queue.t, t/api/uri-t.t,
+ t/data/gnupg/emails/10-encrypted-inline-plain.txt,
+ t/data/gnupg/emails/11-encrypted-inline-attachment.txt,
+ t/data/gnupg/emails/17-signed-encrypted-inline-attachment.txt,
+ t/data/gnupg/emails/19-signed-inline-plain-nested.txt,
+ t/data/gnupg/emails/4-signed-inline-plain.txt,
+ t/data/gnupg/emails/5-signed-inline-with-attachment.txt,
+ t/api/ace.t, t/api/action-createtickets.t, t/api/attachment.t,
+ t/api/attribute-tests.t, t/api/attribute.t, t/api/cf.t,
+ t/api/cf_combo_casacade.t, t/api/cf_external.t,
+ t/api/cf_pattern.t, t/api/cf_single_values.t,
+ t/api/cf_transaction.t, t/api/condition-ownerchange.t,
+ t/api/condition-reject.t, t/api/customfield.t, t/api/date.t,
+ t/api/emailparser.t, t/api/group.t, t/api/groups.t, t/api/i18n.t,
+ t/api/link.t, t/api/record.t, t/api/reminders.t, t/api/rights.t,
+ t/api/rt.t, t/api/scrip.t, t/api/scrip_order.t,
+ t/api/searchbuilder.t, t/api/system.t, t/api/template-insert.t,
+ t/api/template.t, t/api/ticket.t, t/api/tickets.t,
+ t/api/tickets_overlay_sql.t, t/api/uri-fsck_com_rt.t,
+ t/api/user.t, t/api/users.t, t/web/attachments.t,
+ t/web/cf_access.t, t/web/cf_onqueue.t, t/web/cf_select_one.t,
+ t/web/command_line_with_unknown_field.t,
+ t/web/compilation_errors.t, t/web/config_tab_right.t,
+ t/web/custom_frontpage.t, t/web/custom_search.t,
+ t/web/dashboards-permissions.t, t/web/gnupg-outgoing.t,
+ t/web/gnupg-select-keys-on-create.t, t/web/offline_utf8.t,
+ t/web/query_builder.t, t/web/rest.t, t/web/rights.t,
+ t/web/saved_search_chart.t, t/web/search_bulk_update_links.t,
+ t/web/ticket-create-utf8.t, t/web/ticket_owner.t,
+ t/web/ticket_seen.t, t/web/ticket_update_without_content.t,
+ t/web/unlimited_search.t, t/web/crypt-gnupg.t,
+ t/web/gnupg-select-keys-on-update.t,
+ t/web/offline_messages_utf8.t, t/web/rest-non-ascii-subject.t,
+ t/web/rights1.t, t/web/dashboards.t, t/ticket/badlinks.t,
+ t/ticket/merge.t, t/ticket/search_by_txn.t,
+ t/ticket/sort-by-user.t, t/web/basic.t, t/web/command_line.t,
+ t/web/dashboard_with_deleted_saved_search.t,
+ t/web/dashboards-groups.t, t/web/quicksearch.t,
+ t/web/saved_search_permissions.t, t/ticket/search_by_watcher.t,
+ share/html/autohandler, share/html/dhandler,
+ share/html/index.html, share/html/l,
+ share/html/Elements/BevelBoxRaisedEnd,
+ share/html/Elements/Callback, share/html/Elements/CollectionList,
+ share/html/Elements/CollectionListPaging,
+ share/html/Elements/DashboardTabs,
+ share/html/Elements/Dashboards,
+ share/html/Elements/EditCustomFieldAutocomplete,
+ share/html/Elements/EditCustomFieldImage,
+ share/html/Elements/EditCustomFieldSelect,
+ share/html/Elements/EditLinks, share/html/Elements/EmailInput,
+ share/html/Elements/Error, share/html/Elements/GotoTicket,
+ share/html/Elements/ListActions, share/html/Elements/ListMenu,
+ share/html/Elements/Login, share/html/Elements/MakeClicky,
+ share/html/Elements/Menu, share/html/Elements/MessageBox,
+ share/html/Elements/MyAdminQueues, share/html/Elements/MyTickets,
+ share/html/Elements/PageLayout, share/html/Elements/QueryString,
+ share/html/Elements/QueueSummary,
+ share/html/Elements/QuickCreate, share/html/Elements/Refresh,
+ share/html/Elements/RefreshHomepage,
+ share/html/Elements/ScrubHTML, share/html/Elements/Section,
+ share/html/Elements/SelectBoolean,
+ share/html/Elements/SelectCustomFieldValue,
+ share/html/Elements/SelectDate,
+ share/html/Elements/SelectDateRelation,
+ share/html/Elements/SelectLinkType,
+ share/html/Elements/SelectMatch,
+ share/html/Elements/SelectNewTicketQueue,
+ share/html/Elements/SelectOwner,
+ share/html/Elements/SelectPriority,
+ share/html/Elements/SelectQueue,
+ share/html/Elements/SelectStatus,
+ share/html/Elements/SelectTicketTypes,
+ share/html/Elements/SelectTimeUnits,
+ share/html/Elements/SelectTimezone,
+ share/html/Elements/SelectUsers,
+ share/html/Elements/SelectWatcherType,
+ share/html/Elements/ShowCustomFieldBinary,
+ share/html/Elements/ShowCustomFieldText,
+ share/html/Elements/ShowLink, share/html/Elements/ShowLinks,
+ share/html/Elements/ShowMemberships,
+ share/html/Elements/ShowUserConcise,
+ share/html/Elements/ShowUserEmailFrequency,
+ share/html/Elements/ShowUserVerbose,
+ share/html/Elements/SimpleSearch, share/html/Elements/Tabs,
+ share/html/Elements/TicketList, share/html/Elements/TitleBox,
+ t/delegation/cleanup_stalled.t, t/delegation/revocation.t,
+ t/i18n/default.t, t/mail/charsets-outgoing.t,
+ t/mail/crypt-gnupg.t, t/mail/extractsubjecttag.t,
+ t/mail/gateway.t, t/mail/gnupg-bad.t, t/mail/gnupg-incoming.t,
+ t/mail/gnupg-realmail.t, t/mail/gnupg-reverification.t,
+ t/mail/mime_decoding.t, t/mail/sendmail.t, t/mail/verp.t,
+ t/maildigest/attributes.t, t/shredder/00load.t,
+ t/shredder/00skeleton.t, t/shredder/01basics.t,
+ t/shredder/01ticket.t, t/shredder/02group_member.t,
+ t/shredder/02queue.t, t/shredder/02template.t,
+ t/shredder/02user.t, t/shredder/03plugin.t,
+ t/shredder/03plugin_summary.t, t/shredder/03plugin_tickets.t,
+ t/shredder/03plugin_users.t, t/shredder/utils.pl,
+ t/ticket/action_linear_escalate.t, t/ticket/add-watchers.t,
+ t/ticket/batch-upload-csv.t, t/ticket/cfsort-freeform-multiple.t,
+ t/ticket/cfsort-freeform-single.t, t/ticket/deferred_owner.t,
+ t/ticket/link_search.t, t/ticket/linking.t,
+ t/ticket/quicksearch.t, t/ticket/requestor-order.t,
+ t/ticket/scrips_batch.t, t/ticket/search.t,
+ t/ticket/search_by_cf_freeform_multiple.t,
+ t/ticket/search_by_cf_freeform_single.t,
+ t/ticket/search_by_links.t, t/ticket/search_long_cf_values.t,
+ t/ticket/sort-by-custom-ownership.t, t/ticket/sort-by-queue.t,
+ t/ticket/sort_by_cf.t, t/validator/group_members.t,
+ share/html/Elements/Checkbox, share/html/Elements/ColumnMap,
+ share/html/Elements/EditCustomField,
+ share/html/Elements/EditTimeValue,
+ share/html/Elements/MySupportQueues,
+ share/html/Elements/SelectCustomFieldOperator,
+ share/html/Elements/SelectEqualityOperator,
+ share/html/Elements/SelectResultsPerPage,
+ share/html/Elements/SelectTicketSortBy,
+ share/html/Elements/ShowUser, share/html/Elements/TitleBoxStart,
+ share/html/Elements/ShowSearch,
+ share/html/Dashboards/Modify.html,
+ share/html/Dashboards/Queries.html,
+ share/html/Dashboards/Render.html,
+ share/html/Dashboards/Subscription.html,
+ share/html/Dashboards/dhandler, share/html/Dashboards/index.html,
+ share/html/Dashboards/Elements/DashboardsForObject,
+ share/html/Dashboards/Elements/DashboardsForObjects,
+ share/html/Dashboards/Elements/Deleted,
+ share/html/Dashboards/Elements/HiddenSearches,
+ share/html/Dashboards/Elements/ListOfDashboards,
+ share/html/Dashboards/Elements/SelectPrivacy,
+ share/html/Dashboards/Elements/ShowDashboards,
+ share/html/Dashboards/Elements/ShowSubscription,
+ share/html/Dashboards/Elements/Tabs,
+ share/html/Dashboards/Elements/ShowPortlet/component,
+ share/html/Dashboards/Elements/ShowPortlet/dashboard,
+ share/html/Dashboards/Elements/ShowPortlet/search,
+ share/html/Elements/BevelBoxRaisedStart,
+ share/html/Elements/CreateTicket,
+ share/html/Elements/EditCustomFieldBinary,
+ share/html/Elements/EditCustomFieldCombobox,
+ share/html/Elements/EditCustomFieldFreeform,
+ share/html/Elements/EditCustomFieldText,
+ share/html/Elements/EditCustomFieldWikitext,
+ share/html/Elements/Footer, share/html/Elements/Header,
+ share/html/Elements/HeaderJavascript, share/html/Elements/Logo,
+ share/html/Elements/Logout, share/html/Elements/MyRT,
+ share/html/Elements/MyReminders, share/html/Elements/MyRequests,
+ share/html/Elements/PersonalQuickbar,
+ share/html/Elements/Quicksearch,
+ share/html/Elements/SelectAttachmentField,
+ share/html/Elements/SelectDateType,
+ share/html/Elements/SelectGroups, share/html/Elements/SelectLang,
+ share/html/Elements/SelectSortOrder,
+ share/html/Elements/SetupSessionCookie,
+ share/html/Elements/ShowCustomFieldImage,
+ share/html/Elements/ShowCustomFieldWikitext,
+ share/html/Elements/ShowCustomFields, share/html/Elements/Submit,
+ share/html/Elements/TitleBoxEnd,
+ share/html/Elements/ValidateCustomFields,
+ share/html/Elements/CollectionAsTable/Header,
+ share/html/Elements/CollectionAsTable/ParseFormat,
+ share/html/Elements/CollectionAsTable/Row,
+ share/html/Elements/GnuPG/KeyIssues,
+ share/html/Elements/GnuPG/SelectKeyForEncryption,
+ share/html/Elements/GnuPG/SelectKeyForSigning,
+ share/html/Elements/GnuPG/SignEncryptWidget,
+ share/html/Elements/RT__Group/ColumnMap,
+ share/html/Elements/RT__Queue/ColumnMap,
+ share/html/Elements/RT__Scrip/ColumnMap,
+ share/html/Elements/RT__Template/ColumnMap,
+ share/html/Elements/RT__Ticket/ColumnMap,
+ share/html/Elements/RT__User/ColumnMap,
+ share/html/Install/Basics.html,
+ share/html/Install/DatabaseDetails.html,
+ share/html/Install/DatabaseType.html,
+ share/html/Install/Finish.html, share/html/Install/Global.html,
+ share/html/Install/Initialize.html,
+ share/html/Install/Sendmail.html, share/html/Install/autohandler,
+ share/html/Install/index.html,
+ share/html/Install/Elements/Errors,
+ share/html/Install/Elements/Wrapper, share/html/Prefs/MyRT.html,
+ share/html/Prefs/Other.html, share/html/Prefs/Quicksearch.html,
+ share/html/Prefs/Search.html,
+ share/html/Prefs/SearchOptions.html,
+ share/html/Prefs/Elements/Tabs, share/html/Search/Build.html,
+ share/html/Search/Bulk.html, share/html/Search/Chart,
+ share/html/Search/Chart.html, share/html/Search/Edit.html,
+ share/html/Search/Graph.html, share/html/Search/Results.html,
+ share/html/Search/Results.rdf, share/html/Search/Results.tsv,
+ share/html/Search/Simple.html,
+ share/html/Search/Elements/SelectAndOr,
+ share/html/Search/Elements/SelectGroupBy,
+ share/html/Search/Elements/SelectPersonType,
+ share/html/User/Delegation.html, share/html/User/Prefs.html,
+ share/html/User/Elements/DelegateRights,
+ share/html/User/Elements/GroupTabs,
+ share/html/User/Elements/Tabs,
+ share/html/User/Groups/Members.html,
+ share/html/User/Groups/Modify.html,
+ share/html/User/Groups/index.html,
+ share/html/Search/Elements/BuildFormatString,
+ share/html/Search/Elements/ConditionRow,
+ share/html/Search/Elements/PickBasics,
+ share/html/Search/Elements/PickCFs,
+ share/html/Search/Elements/ResultViews,
+ share/html/Search/Elements/SearchesForObject,
+ share/html/Search/Elements/SelectSearchObject,
+ share/html/Search/Elements/SelectSearchesForObjects,
+ share/html/REST/1.0/autohandler, share/html/REST/1.0/dhandler,
+ share/html/REST/1.0/logout,
+ share/html/REST/1.0/Forms/group/customfields,
+ share/html/Search/Elements/Chart,
+ share/html/Search/Elements/DisplayOptions,
+ share/html/Search/Elements/EditFormat,
+ share/html/Search/Elements/EditQuery,
+ share/html/Search/Elements/EditSearches,
+ share/html/Search/Elements/Graph,
+ share/html/Search/Elements/NewListActions,
+ share/html/Search/Elements/PickCriteria,
+ share/html/Search/Elements/SearchPrivacy,
+ share/html/Search/Elements/SelectChartType,
+ share/html/Search/Elements/SelectGroup,
+ share/html/Search/Elements/SelectLinks,
+ share/html/REST/1.0/Forms/attachment/default,
+ share/html/REST/1.0/Forms/group/default,
+ share/html/REST/1.0/Forms/group/ns,
+ share/html/REST/1.0/Forms/queue/customfields,
+ share/html/REST/1.0/Forms/queue/default,
+ share/html/REST/1.0/Forms/queue/ns,
+ share/html/REST/1.0/Forms/queue/ticketcustomfields,
+ share/html/REST/1.0/Forms/ticket/merge,
+ share/html/REST/1.0/Forms/ticket/attachments,
+ share/html/REST/1.0/Forms/ticket/comment,
+ share/html/REST/1.0/Forms/ticket/default,
+ share/html/REST/1.0/Forms/ticket/links,
+ share/html/REST/1.0/Forms/ticket/take,
+ share/html/NoAuth/Logout.html, share/html/NoAuth/Reminder.html,
+ share/html/NoAuth/css/autohandler,
+ share/html/NoAuth/css/dhandler, share/html/NoAuth/css/print.css,
+ share/html/NoAuth/css/web2/InHeader,
+ share/html/NoAuth/css/web2/forms.css,
+ share/html/NoAuth/css/web2/login.css,
+ share/html/NoAuth/css/web2/misc.css,
+ share/html/NoAuth/css/web2/msie.css,
+ share/html/NoAuth/css/web2/msie6.css,
+ share/html/NoAuth/css/web2/nav.css,
+ share/html/NoAuth/css/web2/portlets.css,
+ share/html/NoAuth/css/web2/ticket-search.css,
+ share/html/NoAuth/css/web2/ticket.css,
+ share/html/NoAuth/css/web2/tools.css,
+ share/html/NoAuth/css/web2/yui-fonts.css,
+ share/html/REST/1.0/Forms/ticket/history,
+ share/html/REST/1.0/Forms/transaction/default,
+ share/html/REST/1.0/Forms/user/default,
+ share/html/REST/1.0/Forms/user/ns,
+ share/html/REST/1.0/NoAuth/mail-gateway,
+ share/html/REST/1.0/search/dhandler,
+ share/html/REST/1.0/search/ticket,
+ share/html/REST/1.0/ticket/comment,
+ share/html/REST/1.0/ticket/link,
+ share/html/REST/1.0/ticket/merge,
+ share/html/NoAuth/css/3.4-compat/footer.css,
+ share/html/NoAuth/css/3.4-compat/header.css,
+ share/html/NoAuth/css/3.4-compat/login.css,
+ share/html/NoAuth/css/3.4-compat/nav.css,
+ share/html/NoAuth/css/3.4-compat/ticket.css,
+ share/html/NoAuth/css/web2/admin.css,
+ share/html/NoAuth/css/web2/base.css,
+ share/html/NoAuth/css/web2/boxes.css,
+ share/html/NoAuth/css/web2/layout.css,
+ share/html/NoAuth/css/web2/main.css,
+ share/html/NoAuth/css/web2/ticket-lists.css,
+ share/html/NoAuth/css/web2/images/dhandler,
+ share/html/NoAuth/css/web2/images/source/background-gradient.png,
+ share/html/NoAuth/css/3.4-compat/body.css,
+ share/html/NoAuth/css/3.4-compat/forms.css,
+ share/html/NoAuth/css/3.4-compat/main.css,
+ share/html/NoAuth/css/3.4-compat/misc.css,
+ share/html/NoAuth/css/3.4-compat/quickbar.css,
+ share/html/NoAuth/css/3.4-compat/titlebox.css,
+ share/html/NoAuth/css/3.4-compat/transactions.css,
+ share/html/NoAuth/css/3.5-default/footer.css,
+ share/html/NoAuth/css/3.5-default/local.css,
+ share/html/NoAuth/css/3.5-default/login.css,
+ share/html/NoAuth/css/3.5-default/ticket-search.css,
+ share/html/NoAuth/css/3.5-default/approvals.css,
+ share/html/NoAuth/css/3.5-default/header.css,
+ share/html/NoAuth/css/3.5-default/logo.css,
+ share/html/NoAuth/css/3.5-default/misc.css,
+ share/html/NoAuth/css/3.5-default/nav.css,
+ share/html/NoAuth/css/3.5-default/quickbar.css,
+ share/html/NoAuth/css/3.5-default/ticket.css,
+ share/html/NoAuth/css/3.5-default/titlebox.css,
+ share/html/NoAuth/css/3.5-default/transactions.css,
+ share/html/NoAuth/css/3.5-default/forms.css,
+ share/html/NoAuth/RichText/dhandler,
+ share/html/NoAuth/RichText/FCKeditor/fckconfig.js,
+ share/html/NoAuth/RichText/FCKeditor/fckeditor.js,
+ share/html/NoAuth/RichText/FCKeditor/fckpackager.xml,
+ share/html/NoAuth/RichText/FCKeditor/fckstyles.xml,
+ share/html/NoAuth/RichText/FCKeditor/fcktemplates.xml,
+ share/html/NoAuth/css/3.5-default/body.css,
+ share/html/NoAuth/css/3.5-default/main.css,
+ share/html/NoAuth/css/3.5-default/nav-left.css,
+ share/html/NoAuth/images/autohandler,
+ share/html/NoAuth/images/bplogo.gif,
+ share/html/NoAuth/images/empty_star.gif,
+ share/html/NoAuth/images/favicon.png,
+ share/html/NoAuth/images/star.gif,
+ share/html/NoAuth/images/test.png,
+ share/html/NoAuth/images/css/cb-light.gif,
+ share/html/NoAuth/images/css/cb.gif,
+ share/html/NoAuth/images/css/cbr-b2g.gif,
+ share/html/NoAuth/images/css/cbr-b2lb.gif,
+ share/html/NoAuth/images/css/cbr-gray.gif,
+ share/html/NoAuth/images/css/cbr-trans.gif,
+ share/html/NoAuth/images/css/cbr.gif,
+ share/html/NoAuth/images/css/ct-light.gif,
+ share/html/NoAuth/images/css/ct.gif,
+ share/html/NoAuth/images/css/ctr-b2g.gif,
+ share/html/NoAuth/images/css/ctr-b2lb.gif,
+ share/html/NoAuth/images/css/ctr-gray.gif,
+ share/html/NoAuth/images/css/ctr-trans.gif,
+ share/html/NoAuth/images/css/ctr.gif,
+ share/html/NoAuth/images/css/dark-arrow-up.png,
+ share/html/NoAuth/images/css/dark-arrow.png,
+ share/html/NoAuth/images/css/fieldbg-autocomplete.gif,
+ share/html/NoAuth/images/css/light-arrow-up.png,
+ share/html/NoAuth/images/css/light-arrow.png,
+ share/html/NoAuth/images/css/rolldown-arrow.gif,
+ share/html/NoAuth/images/css/rolldown-arrow.png,
+ share/html/NoAuth/images/css/rollup-arrow.gif,
+ share/html/NoAuth/RichText/FCKeditor/license.txt,
+ share/html/NoAuth/RichText/FCKeditor/editor/fckdebug.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/fckdialog.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/fckeditor.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/fckeditor.original.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/fck_editorarea.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/fck_showtableborders_gecko.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/fckconstants.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/fckeditorapi.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/fckjscoreextensions.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/fckscriptloader.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckcodeformatter.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckconfig.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckregexlib.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktoolbaritems.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/fck_internal.css,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/behaviors/disablehandles.htc,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/behaviors/showtableborders.htc,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_address.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_blockquote.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_div.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h1.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h2.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h3.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h4.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h5.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h6.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_p.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_pre.png,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_anchor.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_flashlogo.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_hiddenfield.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_pagebreak.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_plugin.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdebug.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckplugins.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_contextmenu.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckstyles.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckurlparams.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdomtools.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdocumentprocessor.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklisthandler.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtmlentities.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckbrowserinfo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckcommands.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdebug_empty.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdialog.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklanguagemanager.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckundo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktoolbarset.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckblockquotecommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckcorestylecommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcktablecommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklistslib.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fck_othercommands.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckfitwindow.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckjustifycommands.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcklistcommands.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcknamedcommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckpasteplaintextcommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckpastewordcommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckremoveformatcommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckshowblocks.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckspellcheckcommand_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckspellcheckcommand_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckstylecommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcktextcolorcommand.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckcontextmenu.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontformatcombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarpanelbutton.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckw3crange.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckindentcommands.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckevents.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckiecleanup.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenublock.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbar.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarspecialcombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdocumentfragment_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckstyle.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdocumentfragment_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenuitem.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckspecialcombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbutton.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckimagepreloader.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckkeystrokehandler.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckpanel.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckeditingarea.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckicon.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbuttonui.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdataprocessor.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrangeiterator.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckelementpath.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckenterkey.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckhtmliterator.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenublockpanel.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckplugin.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbreak_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbreak_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontscombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontsizecombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarstylecombo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/anchor.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/arrow_ltr.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/arrow_rtl.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/spacer.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/teeth_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_dtd_test.html,
+ share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_xhtml10strict.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_xhtml10transitional.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/angel_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/angry_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/broken_heart.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/cake.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/confused_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/cry_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/devil_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/embaressed_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/envelope.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/heart.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/kiss.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/lightbulb.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/omg_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/regular_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/sad_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/shades_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/thumbs_down.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/thumbs_up.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/tounge_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/wink_smile.gif,
+ share/html/NoAuth/RichText/FCKeditor/editor/js/fckadobeair.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/js/fckeditorcode_ie.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/js/fckeditorcode_gecko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/es.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/eu.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/hr.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/km.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/it.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/sl.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/bg.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/fa.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/sv.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ja.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/mn.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/th.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/zh.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/fr-ca.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/he.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/sr-latn.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/en-uk.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/nb.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/fr.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/lv.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ar.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/fo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/is.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/en.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/pl.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/hi.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/lt.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/nl.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/sr.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/gl.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ko.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/eo.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/hu.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/ru.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/_translationstatus.txt,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/en-ca.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/gu.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/tr.js,
+ share/html/NoAuth/RichText/FCKeditor/editor/lang/fi.js: Initial
+ revision
+
+2009-12-31 05:09 ivan
+
+ * rt/: UPGRADING.mysql, lib/RT/Test.pm, sbin/merge-rosetta.pl,
+ sbin/rt-attributes-viewer, sbin/rt-attributes-viewer.in,
+ sbin/rt-clean-sessions, sbin/rt-clean-sessions.in,
+ sbin/rt-email-dashboards, sbin/rt-email-dashboards.in,
+ sbin/rt-email-digest, sbin/rt-email-digest.in,
+ sbin/rt-email-group-admin, sbin/rt-email-group-admin.in,
+ sbin/rt-server, sbin/rt-server.in, sbin/rt-shredder,
+ sbin/rt-shredder.in, sbin/rt-validator, sbin/rt-validator.in,
+ sbin/tweak-template-locstring, lib/RT/Search.pm,
+ lib/RT/Config.pm, lib/RT/Dashboard.pm, lib/RT/Installer.pm,
+ lib/RT/Plugin.pm, lib/RT/Ruleset.pm, lib/RT/Shredder.pm,
+ lib/RT/Approval.pm, lib/RT/Action.pm, lib/RT/SharedSetting.pm,
+ lib/RT/Condition.pm, lib/RT/SQL.pm, lib/RT/Util.pm,
+ lib/RT/Rule.pm, lib/RT/Crypt/GnuPG.pm,
+ lib/RT/Interface/Web/Request.pm, lib/RT/Interface/Web/Session.pm,
+ lib/RT/Interface/Web/Standalone/PreFork.pm,
+ lib/RT/Action/LinearEscalate.pm,
+ lib/RT/Action/ExtractSubjectTag.pm, lib/RT/Action/NotifyGroup.pm,
+ lib/RT/Action/NotifyGroupAsComment.pm,
+ lib/RT/CustomFieldValues/External.pm,
+ lib/RT/CustomFieldValues/Groups.pm, lib/RT/I18N/bg.po,
+ lib/RT/I18N/rt.pot, lib/RT/I18N/hr.po, lib/RT/I18N/zh_TW.po:
+ Initial revision
+
+2009-12-31 04:56 ivan
+
+ * rt/: Makefile, lib/RT.pm: rt 3.6.10
+
+2009-12-31 04:44 ivan
+
+ * rt/: etc/RT_Config.pm.in, lib/RT/Groups_Overlay.pm,
+ lib/RT/Record.pm, lib/RT/SearchBuilder.pm,
+ lib/RT/Ticket_Overlay.pm, lib/RT/Transaction_Overlay.pm,
+ lib/RT/User_Overlay.pm, lib/RT/Users_Overlay.pm,
+ sbin/rt-setup-database.in: rt 3.6.10
+
+2009-12-31 04:35 ivan
+
+ * rt/FREESIDE_MODIFIED: up-to-date
+
+2009-12-30 23:16 ivan
+
+ * FS/FS/Schema.pm: fix h_cdr acctid changing from bigint to int
+ with recent DBIx::DBSchema
+
+2009-12-30 21:10 ivan
+
+ * FS/: FS/Cron/bill.pm, bin/freeside-daily: specify multiple
+ agentnums with freeside-daily
+
+2009-12-30 19:33 ivan
+
+ * Makefile: goodbye conf dir
+
+2009-12-30 18:50 jeff
+
+ * FS/FS/cust_main.pm: really reduce the noise
+
+2009-12-30 18:20 ivan
+
+ * httemplate/: edit/prospect_main.html, edit/elements/edit.html,
+ edit/process/elements/process.html, elements/contact.html,
+ elements/menu.html: prospecting: proper contact error handling
+ when you add a prospect
+
+2009-12-29 22:41 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: define all detail header list
+ elements
+
+2009-12-29 22:05 jeff
+
+ * FS/FS/cust_main.pm: noise reduction
+
+2009-12-29 21:22 jeff
+
+ * FS/FS/tax_rate.pm: noise reduction
+
+2009-12-29 18:39 jeff
+
+ * FS/FS/cust_bill_pkg_detail.pm: use the class used
+
+2009-12-29 18:26 jeff
+
+ * FS/FS/cust_main.pm: noise reduction
+
+2009-12-28 20:49 ivan
+
+ * FS/bin/freeside-upgrade: output SQL statements as we run them
+ (and only the ones we run), not immediately
+
+2009-12-28 20:44 ivan
+
+ * FS/FS/Conf.pm: oops, syntax error adding queued-sleep_time
+
+2009-12-28 20:24 ivan
+
+ * FS/bin/: freeside-upgrade: don't change h_queue.job type under
+ non-mysql, takes forever on large dbs, RT#6946
+
+2009-12-28 17:38 ivan
+
+ * FS/bin/freeside-daily: add -u option for vacuuming, RT#5258
+
+2009-12-28 17:30 ivan
+
+ * bin/: del-old-history, pg-sizer: some random utils for disk space
+ analysis and eliminating old history records, RT#6914
+
+2009-12-28 17:00 mark
+
+ * FS/FS/AccessRight.pm, httemplate/browse/cust_attachment.html,
+ httemplate/elements/menu.html: Add Browse attachments ACL
+ (RT#4964)
+
+2009-12-28 16:38 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-queued: add queued-sleep_time
+
+2009-12-28 11:18 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Mason.pm, FS/FS/Schema.pm,
+ FS/FS/Setup.pm, FS/FS/Upgrade.pm, FS/FS/contact.pm,
+ FS/FS/contact_email.pm, FS/FS/contact_phone.pm,
+ FS/FS/cust_location.pm, FS/FS/o2m_Common.pm, FS/FS/phone_type.pm,
+ FS/FS/prospect_main.pm, FS/MANIFEST, FS/t/contact.t,
+ FS/t/contact_email.t, FS/t/contact_phone.t, FS/t/phone_type.t,
+ FS/t/prospect_main.t, httemplate/edit/prospect_main.html,
+ httemplate/edit/process/prospect_main.html,
+ httemplate/elements/city.html, httemplate/elements/contact.html,
+ httemplate/elements/header.html, httemplate/elements/menu.html,
+ httemplate/elements/tr-contact.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/search/prospect_main.html,
+ httemplate/search/report_prospect_main.html,
+ httemplate/view/prospect_main.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/elements/process.html,
+ httemplate/misc/location.cgi,
+ httemplate/view/cust_main/packages/location.html, FS/FS.pm:
+ beginning of prospect/CRM/contact work
+
+2009-12-27 21:25 ivan
+
+ * FS/FS/: part_pkg_taxclass.pm, phone_device.pm: use blessed
+
+2009-12-26 17:00 jeff
+
+ * FS/FS/: cust_location.pm, cust_main.pm: improve spacing around
+ county
+
+2009-12-23 15:32 jeff
+
+ * bin/monitor: remove debugging
+
+2009-12-23 15:29 jeff
+
+ * bin/monitor: add non-forking one machine monitor program
+
+2009-12-23 15:14 jeff
+
+ * FS/FS/Yori.pm: teach yori to do some load monitoring
+
+2009-12-23 13:21 jeff
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_location.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm,
+ httemplate/view/cust_main/packages/location.html: correct invoice
+ package address display and reduce false laziness
+
+2009-12-22 16:30 mark
+
+ * FS/FS/svc_acct.pm, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/svc_acct.cgi: Tweak set_password per
+ RT#6358
+
+2009-12-21 06:44 jeff
+
+ * FS/FS/: Schema.pm, cust_bill_pkg.pm,
+ cust_bill_pkg_tax_location.pm, cust_credit_bill_pkg.pm,
+ cust_tax_exempt_pkg.pm: manage tax exemptions (texas-tax) on
+ credit application RT953
+
+2009-12-21 06:36 jeff
+
+ * FS/FS/Conf.pm, FS/FS/tax_rate.pm, bin/fetch_and_expand_taxes,
+ bin/reassemble_taxes: move cch conf into database and add a
+ couple small tools for processing updates more manually
+
+2009-12-20 18:00 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/bill.cgi: have 'Bill now'
+ link cancel expired (and suspend adjourned) packages, and catch
+ and return errors in all cases, RT#6627
+
+2009-12-20 13:52 ivan
+
+ * FS/FS/Conf.pm: late fee package class specified in the event
+ action instead of a global finance_pkgclass config, RT#6617
+
+2009-12-20 13:48 ivan
+
+ * FS/FS/part_event/Action/: cust_bill_fee_percent.pm, fee.pm: late
+ fee package class specified in the event action instead of a
+ global finance_pkgclass config, RT#6617
+
+2009-12-20 11:42 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix usage details pulling from
+ wrong account for customers who have multiple accounts, RT#6681,
+ fallout from RT#4189
+
+2009-12-19 22:52 ivan
+
+ * httemplate/search/cust_pkg.cgi: fix address fields on advanced
+ package search, RT#6609
+
+2009-12-19 18:51 ivan
+
+ * FS/FS/cust_pkg.pm: fix bulk package order with one package
+ changing to one new package, RT#6519, fallout from RT#4499
+
+2009-12-19 17:02 ivan
+
+ * httemplate/view/cust_main/: packages.html, payment_history.html:
+ fix cust_main-packages-years, RT#6798
+
+2009-12-19 15:52 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: fix more
+ ->owed_setup/owed_recur clashing with
+ cust_bill->open_cust_bill_pkg separating line items, RT#4729
+
+2009-12-19 15:28 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: fix credit application, at
+ least in vedeya's case on HEAD, RT#6819, fallout from tax
+ credits, RT#4729
+
+2009-12-19 14:52 ivan
+
+ * httemplate/edit/elements/ApplicationCommon.html: fix fill-in of
+ credit amount when there aren't tax buttons
+
+2009-12-19 14:41 ivan
+
+ * httemplate/edit/cust_credit.cgi: add add'l info field as a
+ customer-editable field, RT#6505
+
+2009-12-19 14:32 ivan
+
+ * httemplate/view/cust_main/payment_history.html: make room for
+ credit reasons in popup
+
+2009-12-19 12:29 ivan
+
+ * FS/FS/cust_main_Mixin.pm: fix event report w/ a customer status
+
+2009-12-19 11:10 ivan
+
+ * FS/FS/part_pkg.pm: fix package order to really display only those
+ pacakges available
+
+2009-12-19 11:09 ivan
+
+ * httemplate/misc/cust-part_pkg.cgi: fix order of packages after
+ class selection changes
+
+2009-12-18 10:20 jeff
+
+ * FS/FS/cust_bill_pkg_tax_location.pm,
+ FS/FS/cust_bill_pkg_tax_rate_location.pm,
+ httemplate/edit/elements/ApplicationCommon.html,
+ httemplate/misc/xmlhttp-calculate_taxes.html: fix handling of tax
+ location records and add a 'clear' button for tax credits
+
+2009-12-17 16:41 ivan
+
+ * rt/bin/standalone_httpd: Initial revision
+
+2009-12-17 16:38 ivan
+
+ * rt/: .gitignore, sbin/rt-dump-database, lib/RT/I18N/pt_pt.po:
+ Initial revision
+
+2009-12-16 09:35 ivan
+
+ * httemplate/edit/cust_main_attach.cgi: tweak attachment adding UI:
+ table consistency, input sizes and maxlengths, RT#4964
+
+2009-12-16 07:03 jeff
+
+ * FS/FS/: Conf.pm, Record.pm, cust_bill.pm, cust_location.pm,
+ cust_main.pm, cust_pkg.pm: group invoice line items by location,
+ show location address on invoice, option for due date rather than
+ invoice date on prior unpaid invoice line items, and option for
+ aging on invoice (#6418, #5235, #4648)
+
+2009-12-13 23:52 ivan
+
+ * FS/FS/svc_acct.pm: use a global config too
+
+2009-12-13 23:10 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm, part_export/sqlradius.pm: add
+ overlimit_groups agent-specific config, overriding
+ export-specific overlimit_groups, RT#6622
+
+2009-12-13 19:36 mark
+
+ * httemplate/search/elements/search-xls.html: Fix filename for
+ Excel spreadsheet reports
+
+2009-12-13 17:41 mark
+
+ * FS/FS/AccessRight.pm, httemplate/browse/cust_attachment.html,
+ httemplate/edit/cust_main_attach.cgi,
+ httemplate/elements/menu.html, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/attachments.html: Add access right to
+ view attachments
+
+2009-12-12 15:53 ivan
+
+ * httemplate/search/cust_credit_bill_pkg.html,
+ FS/FS/cust_credit_bill_pkg.pm,
+ httemplate/search/cust_bill_pkg.cgi: better reporting for tax
+ credits, RT#4729
+
+2009-12-12 15:32 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi, cust_credit_bill_pkg.html,
+ report_tax.cgi: better reporting for tax credits, RT#4729
+
+2009-12-12 14:35 ivan
+
+ * FS/FS/cust_credit_bill_pkg.pm: doc
+
+2009-12-12 14:22 ivan
+
+ * Makefile: sync versioning with branches, sorry jeremy
+
+2009-12-12 13:38 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html: reporting with city taxes,
+ RT#6776
+
+2009-12-10 15:03 ivan
+
+ * FS/FS/cust_main.pm: fully fix bulk customer reports, RT#6778
+
+2009-12-07 22:21 mark
+
+ * FS/FS/Schema.pm: Fix cust_attachment.disabled type
+
+2009-12-06 15:44 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix initial value of agent types
+ when cloning
+
+2009-12-06 15:18 ivan
+
+ * httemplate/elements/header.html: fix search box clearing from
+ within RT, caused by different quoting rules in RT comp_root.
+ grr.
+
+2009-12-06 11:19 ivan
+
+ * FS/FS/Schema.pm: more reasonable sizes for filename, mime type
+ and title, RT#6823
+
+2009-12-04 10:37 jeff
+
+ * FS/FS/cust_bill.pm: holy cow! correct sense of skip usage testing
+
+2009-12-03 20:45 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/search/svc_acct.cgi:
+ customer-specific account report (and some small refactoring of
+ method names to clash less), RT#6180
+
+2009-12-03 20:40 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_bill_event.pm,
+ httemplate/search/477.html, httemplate/search/cust_bill.html,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_event.html,
+ httemplate/search/cust_main.html, httemplate/search/cust_pkg.cgi,
+ httemplate/search/report_svc_acct.html,
+ httemplate/view/cust_main/packages.html, FS/FS/cust_event.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/misc/email-customers.html,
+ httemplate/misc/process/bulk_change_pkg.cgi,
+ httemplate/misc/process/email-customers.html: customer-specific
+ account report (and some small refactoring of method names to
+ clash less), RT#6180
+
+2009-12-03 19:23 ivan
+
+ * FS/FS/cust_pkg.pm: doh, 1.9 fix customer package search (fallout
+ from customer classes) and add "not yet billed" status to
+ customer package report
+
+2009-12-03 11:22 ivan
+
+ * httemplate/graph/cust_bill_pkg.cgi: average per cust_pkg option
+ for peter
+
+2009-12-03 11:06 ivan
+
+ * FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/report_cust_bill_pkg.html: average per cust_pkg
+ option for peter
+
+2009-12-02 23:53 ivan
+
+ * FS/FS/Schema.pm: index cdrbatchnum
+
+2009-12-02 23:37 ivan
+
+ * FS/FS/: Record.pm, cdr/sansay.pm: fix startdate in sansay CDR
+ import (and skip blank lines), RT#6801
+
+2009-12-02 23:18 ivan
+
+ * httemplate/search/cdr.html: fix cdr search
+
+2009-12-02 20:22 ivan
+
+ * FS/FS/cdr/sansay.pm: no header?
+
+2009-12-02 12:58 ivan
+
+ * FS/FS/Schema.pm: add index to rate_prefix.npa for a performance
+ improvement on CDR billing, RT#6386
+
+2009-12-02 12:51 ivan
+
+ * httemplate/view/: svc_broadband.cgi, svc_domain.cgi,
+ svc_external.cgi, svc_forward.cgi, svc_www.cgi: fix viewing
+ unlinked forwards, domains, broadband/external/www services,
+ RT#6794
+
+2009-12-01 11:16 jeff
+
+ * FS/FS/cust_bill.pm: want a listref not a list
+
+2009-11-29 16:38 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/email-customers.html: fix
+ bulk sending of customer notices, RT#6778
+
+2009-11-29 16:06 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: fix
+
+2009-11-29 16:04 ivan
+
+ * httemplate/edit/: REAL_cust_pkg.cgi, process/REAL_cust_pkg.cgi:
+ UI changes to make it impossible to add a start date to a package
+ that already has a setup fee (can still remove an existing start
+ date causing problems), RT#6712
+
+2009-11-29 15:18 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: add
+ cust_pkg-change_pkgpart-bill_now option to bill the new package
+ immediately on package changes. Useful for prepaid situations
+ with RADIUS where an Expiration attribute base don the package
+ must be present at all times. RT#6692
+
+2009-11-29 15:15 ivan
+
+ * FS/FS/cust_main.pm: doc: spelling
+
+2009-11-25 16:27 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: debugging left on
+
+2009-11-25 11:58 ivan
+
+ * httemplate/edit/part_pkg.cgi: don't consider disabled report
+ classes, don't load up the whole table just to see if there are
+ any
+
+2009-11-25 11:32 jeff
+
+ * FS/FS/: cust_bill.pm, usage_class.pm: improve appearance of cdr
+ records in cdr sections
+
+2009-11-25 08:13 jeff
+
+ * FS/FS/cust_bill.pm: bug fixes, reuse summary_page, eliminate
+ dups, and rearrange
+
+2009-11-25 08:09 jeff
+
+ * conf/invoice_latex: really insert a pagebreak
+
+2009-11-25 08:07 jeff
+
+ * conf/invoice_latex: oops.. need updated template for new formats
+
+2009-11-24 13:00 ivan
+
+ * FS/FS/cust_bill.pm: debugging left on
+
+2009-11-24 09:05 jeff
+
+ * FS/FS/cust_bill.pm: cope with sections lacking a pkg_category
+
+2009-11-23 23:42 ivan
+
+ * httemplate/: misc/timeworked.html, misc/process/timeworked.html,
+ search/timeworked.html: timeworked report: carry the date range
+ through to the success redirect
+
+2009-11-23 23:23 ivan
+
+ * httemplate/: search/report_timeworked.html, elements/menu.html,
+ search/timeworked.html: add date constratint on time worked
+ search
+
+2009-11-23 22:04 ivan
+
+ * FS/FS/cust_main.pm: send card number with void transactions for
+ B:OP:IPPay, RT#5690
+
+2009-11-23 14:09 mark
+
+ * FS/FS/Schema.pm: Add title field to cust_attachment
+
+2009-11-22 16:25 jeff
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: apply to taxes last
+
+2009-11-20 09:33 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_detail.pm,
+ FS/FS/usage_class.pm, FS/FS/part_pkg/voip_cdr.pm,
+ conf/invoice_html, httemplate/browse/pkg_category.html,
+ httemplate/browse/usage_class.html,
+ httemplate/edit/pkg_category.html,
+ httemplate/edit/usage_class.html: invoice formatting: add
+ sections for usage, add sections per svc_phone, add folding like
+ line items into one #6592
+
+2009-11-19 01:47 ivan
+
+ * FS/FS.pm, FS/FS/Record.pm, FS/FS/cdr.pm,
+ FS/bin/freeside-cdr-sftp_and_import, bin/cdr-transnexus.import,
+ bin/cdr.http_and_import, bin/cdr.import,
+ httemplate/elements/select-cdrbatch.html,
+ httemplate/elements/tr-select-cdrbatch.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html,
+ httemplate/view/svc_phone.cgi: proper cdr_batch table, RT#6386
+
+2009-11-19 01:43 ivan
+
+ * FS/FS/: Mason.pm, Schema.pm, Upgrade.pm: proper cdr_batch table,
+ RT#6386
+
+2009-11-19 01:39 ivan
+
+ * FS/MANIFEST: proper cdr_batch table, RT#6386
+
+2009-11-19 01:32 ivan
+
+ * FS/: FS/cdr_batch.pm, t/cdr_batch.t: proper cdr_batch table,
+ RT#6386
+
+2009-11-18 23:06 ivan
+
+ * FS/FS/cdr.pm: format CDRs durations as NNm MMs instead of NN.MMm,
+ RT#6316
+
+2009-11-18 22:21 ivan
+
+ * FS/FS/Cron/breakage.pm: consider credits and refunds in breakage,
+ RT#6407
+
+2009-11-18 01:39 mark
+
+ * httemplate/search/cust_pay_batch.cgi: RT#4786, RBC batch format
+
+2009-11-18 01:27 mark
+
+ * FS/FS/Conf.pm, FS/FS/pay_batch/RBC.pm,
+ httemplate/search/cust_pay_batch.cgi: RT#4768, RBC batch format
+
+2009-11-18 01:09 mark
+
+ * httemplate/: browse/cust_attachment.html,
+ misc/cust_attachment.cgi: cust_attachment improvement, RT#4964
+ and #6225
+
+2009-11-17 14:06 ivan
+
+ * FS/FS/cust_main.pm: do a case-insensive search on
+ browser-remembered results, so starting to use USPS verification
+ (which UPPERCASES everything) doesn't invalidate everything your
+ browser remembered in quick payment entry
+
+2009-11-17 12:56 jeff
+
+ * httemplate/edit/elements/ApplicationCommon.html: work around ie7
+ javascript issues
+
+2009-11-16 23:42 ivan
+
+ * httemplate/search/: report_cust_pay.html,
+ report_cust_refund.html, elements/cust_pay_or_refund.html: add
+ otaker to payment/refund search, RT#6407
+
+2009-11-16 23:08 ivan
+
+ * httemplate/: elements/menu.html, search/report_cust_refund.html:
+ add refund report, RT#6407
+
+2009-11-16 16:06 mark
+
+ * httemplate/: edit/cust_main_attach.cgi,
+ edit/process/cust_main_attach.cgi, elements/menu.html,
+ search/elements/search-html.html, view/cust_main.cgi,
+ view/cust_main/attachments.html: cust_attachment improvement,
+ RT#4964 and #6225
+
+2009-11-15 19:55 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ httemplate/search/cust_bill.html,
+ httemplate/search/report_cust_bill.html: add ability to search on
+ ranges of charged, owed to adv. invoice report, RT#6407
+
+2009-11-15 18:27 ivan
+
+ * FS/: FS/Conf.pm, FS/Cron/bill.pm, bin/freeside-daily: add
+ disable_cron_billing config, RT#6407
+
+2009-11-13 16:08 ivan
+
+ * FS/FS/cust_main.pm, httemplate/elements/select-terms.html,
+ httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html: add invoice terms to
+ advanced customer report. dogfood.
+
+2009-11-12 23:12 ivan
+
+ * FS/FS/cust_pkg.pm: fix advanced package report fallout from
+ customer classes, RT#6677
+
+2009-11-12 13:45 mark
+
+ * FS/FS/Conf.pm, FS/FS/svc_acct.pm, httemplate/view/svc_acct.cgi,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/svc_acct.cgi: Add default password
+ encoding option
+
+2009-11-12 08:56 jeff
+
+ * FS/FS/: Schema.pm, tax_rate.pm: correct bugs in tax replacement
+ routine and allow updates to function
+
+2009-11-11 18:05 mark
+
+ * FS/FS/pay_batch/paymentech.pm: Fix date format string
+
+2009-11-11 18:03 mark
+
+ * FS/bin/: freeside-paymentech-upload,
+ freeside-paymentech-download: Fix zip password
+
+2009-11-11 08:38 jeff
+
+ * httemplate/edit/cust_credit.cgi: cruft removal
+
+2009-11-06 14:25 jeff
+
+ * httemplate/misc/xmlhttp-calculate_taxes.html: oops! forgot an
+ important file for applying tax credits #4729
+
+2009-11-06 14:07 ivan
+
+ * FS/FS/cust_main.pm: use business-onlinepayment-description in
+ 1.9, even without $pkgs
+
+2009-11-05 17:51 ivan
+
+ * FS/FS/: Conf.pm, cust_main_invoice.pm: add
+ emailinvoice-apostrophe config option to allow apostrophies in
+ invoice email addresses, RT#6464
+
+2009-11-05 16:25 ivan
+
+ * FS/FS/cust_main.pm, httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html: add "payment expiration
+ before" to customer report, RT#6447
+
+2009-11-05 15:55 ivan
+
+ * FS/FS/ConfDefaults.pm: try not to make the "Customer Fields"
+ SELECT so huge
+
+2009-11-05 14:01 ivan
+
+ * httemplate/search/svc_acct.cgi: improvements in time remaining
+ report: correctly account for unpaid time for non-monthly
+ customers, report in hours+minutes intead of using Time::Duration
+ days/hours
+
+2009-11-05 13:22 ivan
+
+ * FS/bin/: freeside-paymentech-download,
+ freeside-paymentech-upload: back to old host name, info from
+ customer/paymentech was bogus, RT#5650
+
+2009-11-04 17:47 ivan
+
+ * FS/bin/: freeside-paymentech-download,
+ freeside-paymentech-upload: update paymentech live server,
+ RT#5650
+
+2009-11-04 16:52 ivan
+
+ * FS/FS/pkg_category.pm: fix pkg_category upgrades
+
+2009-11-04 16:48 ivan
+
+ * FS/FS/cust_main.pm: fix bad interaction between new city tax code
+ & using taxclasses without cities, RT#6637
+
+2009-11-04 16:29 ivan
+
+ * FS/FS/Mason.pm: oops, for customer categories
+
+2009-11-04 16:04 ivan
+
+ * httemplate/browse/cust_main_county.cgi: fix county filter on tax
+ config, fallout from city tax changes, RT#5852
+
+2009-11-03 17:40 ivan
+
+ * FS/bin/: freeside-paymentech-download,
+ freeside-paymentech-upload: add explicit use of Expect module, so
+ the error is thrown immediate, RT#5650
+
+2009-11-03 17:04 ivan
+
+ * FS/FS/Cron/breakage.pm, httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi: reconcile breakage from stale
+ accounts, RT#6407
+
+2009-11-03 16:59 ivan
+
+ * FS/FS/part_pkg.pm: silence "use of uninitialized value in split"
+ warning
+
+2009-11-03 12:44 ivan
+
+ * FS/FS/pay_batch.pm: fix warning replacing pay_batch, RT#5650
+
+2009-11-03 11:56 ivan
+
+ * FS/bin/: freeside-paymentech-upload,
+ freeside-paymentech-download: check for zip and unzip commands,
+ use multi-arg version of system to prevent the shell getting its
+ hands on things (metacharacters in pw or whatnot), RT#5650
+
+2009-11-03 11:11 ivan
+
+ * FS/bin/: freeside-paymentech-download,
+ freeside-paymentech-upload: update usage & manpage w/ freeside-
+ prefix
+
+2009-11-03 11:03 ivan
+
+ * FS/bin/: freeside-paymentech-download,
+ freeside-paymentech-upload: fix usage of File::Temp->newdir, not
+ in 0.18 File::Temp on perl 5.10.0, RT#5650
+
+2009-11-03 10:53 ivan
+
+ * bin/paymentech-download, bin/paymentech-upload,
+ FS/bin/freeside-paymentech-download,
+ FS/bin/freeside-paymentech-upload: moving paymentech-* to FS/bin,
+ RT#5650
+
+2009-11-02 19:13 ivan
+
+ * httemplate/config/config.cgi, FS/FS/Conf.pm,
+ FS/bin/freeside-daily, httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi,
+ httemplate/elements/tr-select-part_pkg.html,
+ FS/FS/Cron/breakage.pm: (start of) reconcile breakage from stale
+ accounts, RT#6407
+
+2009-11-02 17:48 ivan
+
+ * bin/paymentech-download: spelling
+
+2009-11-02 17:44 ivan
+
+ * bin/paymentech-download: add -a option for archive dir
+
+2009-11-02 17:21 ivan
+
+ * FS/FS/Conf.pm: clarify description of batchconfig-paymentech
+ based on notes from #5650
+
+2009-11-02 13:21 ivan
+
+ * httemplate/edit/payment_gateway.html: add Elavon, SagePay,
+ WorldPay, fix extra space on PlugnPay, PPIPayMover, Protx
+
+2009-11-01 14:12 jeff
+
+ * httemplate/search/: 477.html, elements/search-csv.html: form 477
+ improvements #6499
+
+2009-10-31 13:09 jeff
+
+ * FS/FS/cust_bill.pm: unbork summary page invoices
+
+2009-10-30 16:29 ivan
+
+ * FS/FS/cust_event.pm, FS/FS/cust_main_Mixin.pm,
+ httemplate/elements/select-part_event.html,
+ httemplate/elements/select-payby.html,
+ httemplate/elements/tr-select-part_event.html,
+ httemplate/search/cust_event.html,
+ httemplate/search/report_cust_event.html: more reporting options
+ for failed billing events, RT#6447
+
+2009-10-30 11:37 ivan
+
+ * FS/FS/: Conf.pm: add paymentech to batch config options
+
+2009-10-29 17:02 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/bulk-cust_main_county.html,
+ edit/process/bulk-cust_main_county.html: bulk tax changes,
+ RT#6445
+
+2009-10-29 16:43 mark
+
+ * FS/FS/pay_batch/paymentech.pm: use XML::Writer for tighter
+ compliance with spec
+
+2009-10-29 16:42 mark
+
+ * bin/paymentech-upload: add option to upload all open batches
+
+2009-10-29 16:37 ivan
+
+ * httemplate/edit/bulk-cust_main_county.html: friendlier error
+ message when you select nothing, RT#6445
+
+2009-10-29 16:36 ivan
+
+ * httemplate/elements/errorpage-popup.html: adding errorpage popup,
+ RT#6445
+
+2009-10-29 15:55 ivan
+
+ * FS/bin/freeside-queued: have freeside-queued be more resillient
+ in the face of a database that's gone away, RT#6428
+
+2009-10-29 12:10 ivan
+
+ * FS/FS/cust_bill_pkg.pm: turn off debugging
+
+2009-10-29 12:08 ivan
+
+ * httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html, FS/FS/cust_main.pm,
+ httemplate/elements/select-table.html: customer classification,
+ RT#6376
+
+2009-10-29 11:38 ivan
+
+ * FS/MANIFEST, FS/FS/cust_main.pm, FS/t/category_Common.t,
+ FS/t/class_Common.t, httemplate/browse/pkg_category.html,
+ httemplate/edit/cust_main/top_misc.html,
+ httemplate/view/cust_main/misc.html,
+ httemplate/elements/select-cust_class.html,
+ httemplate/elements/tr-select-cust_class.html: customer
+ classification, RT#6376
+
+2009-10-28 18:08 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Schema.pm, FS/FS/category_Common.pm,
+ FS/FS/class_Common.pm, FS/FS/cust_category.pm,
+ FS/FS/cust_class.pm, FS/FS/cust_main.pm, FS/FS/pkg_category.pm,
+ FS/FS/pkg_class.pm, FS/t/cust_category.t, FS/t/cust_class.t,
+ httemplate/elements/menu.html,
+ httemplate/browse/cust_category.html,
+ httemplate/browse/cust_class.html,
+ httemplate/browse/part_pkg_report_option.html,
+ httemplate/browse/pkg_category.html,
+ httemplate/browse/pkg_class.html,
+ httemplate/edit/cust_category.html,
+ httemplate/edit/cust_class.html,
+ httemplate/edit/pkg_category.html,
+ httemplate/edit/pkg_class.html,
+ httemplate/edit/elements/category_Common.html,
+ httemplate/edit/elements/class_Common.html,
+ httemplate/edit/process/cust_category.html,
+ httemplate/edit/process/cust_class.html: customer classification,
+ RT#6376
+
+2009-10-28 12:04 ivan
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm, svc_domain.pm,
+ svc_forward.pm, svc_www.pm: fix problems using inventory for UID
+ (and other fields controlled by check in svc_acct and also
+ svc_www, svc_domain and svc_forward), RT#6366
+
+2009-10-28 12:01 jeff
+
+ * FS/FS/cust_bill_pkg.pm, FS/FS/cust_credit.pm, FS/FS/cust_main.pm,
+ httemplate/edit/elements/ApplicationCommon.html,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/elements/ApplicationCommon.html: UI
+ changes for credit applications include on the fly tax
+ calculations #4729
+
+2009-10-28 11:16 ivan
+
+ * FS/FS/part_svc.pm: fix removing a flag from a service definition
+ column
+
+2009-10-28 10:26 ivan
+
+ * httemplate/misc/inventory_item-import.html: fix inventory upload,
+ RT#6366
+
+2009-10-27 23:13 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/misc/bulk_change_pkg.cgi,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/view/cust_main/packages.html: customer link to package
+ reports for that customer, RT#6180
+
+2009-10-27 11:11 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm,
+ httemplate/elements/header.html: address1 search, RT#5060
+
+2009-10-26 18:26 mark
+
+ * httemplate/edit/payment_gateway.html: add Jety to the list
+
+2009-10-26 16:20 ivan
+
+ * httemplate/elements/tr-select-pkg_class.html: didn't want to
+ revert that, though
+
+2009-10-26 16:17 ivan
+
+ * httemplate/elements/tr-select-pkg_class.html: better fix for
+ ignoring disabled package classes that doesn't search them twice
+
+2009-10-26 00:12 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/cust_bill_ApplicationCommon.pm, FS/FS/cust_bill_pay_pkg.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_tax_location.pm,
+ FS/FS/cust_bill_pkg_tax_rate_location.pm,
+ FS/FS/cust_credit_bill_pkg.pm, httemplate/edit/cust_credit.cgi,
+ httemplate/edit/elements/ApplicationCommon.html,
+ httemplate/edit/process/elements/ApplicationCommon.html,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_newtax.cgi,
+ httemplate/search/report_tax.cgi,
+ httemplate/view/cust_main/payment_history/credit.html,
+ httemplate/view/cust_main/payment_history/payment.html: credits
+ return taxes, but the magic calculation button does not yet work
+ properly (grrr - more sleep required) RT#4729
+
+2009-10-25 18:11 ivan
+
+ * FS/FS/part_pkg/flat.pm: eliminiate noisy but harmless "Use of
+ uninitialized value in numeric gt (>)" warning
+
+2009-10-25 16:30 ivan
+
+ * FS/FS/: cust_main.pm, ClientAPI/MyAccount.pm: add apply option to
+ realtime_collect, RT#5071
+
+2009-10-24 17:29 ivan
+
+ * httemplate/search/cust_bill.html: import legacy invoice numbers
+ to cust_bill.agent_invid, RT#5351
+
+2009-10-24 16:37 mark
+
+ * FS/FS/pay_batch.pm, FS/FS/Conf.pm, FS/FS/pay_batch/paymentech.pm,
+ bin/paymentech-download, bin/paymentech-upload: Scripts for
+ paymentech batch transfer
+
+2009-10-23 19:04 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/decline.html,
+ fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ fs_selfservice/FS-SelfService/cgi/success.html: remove an
+ inadvertant debugging call left in, allow an agentnum to be
+ specfied for non-logged in situations, RT#6166
+
+2009-10-23 17:34 ivan
+
+ * FS/: FS/Conf.pm, FS/ClientAPI/MyAccount.pm,
+ FS/ClientAPI/Signup.pm, bin/freeside-selfservice-server:
+ selfservice per-agent skinning
+
+2009-10-23 17:16 ivan
+
+ * FS/FS/agent.pm: eliminate warnings from Business::CreditCard
+ about being passed an empty number
+
+2009-10-23 17:04 ivan
+
+ * FS/FS/Record.pm: fix cause of harmless 'Premature end of base64
+ data' warning
+
+2009-10-23 01:21 ivan
+
+ * bin/move-unlinked: adding unlinked account migration script,
+ RT#6126
+
+2009-10-22 18:41 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: add cust_bill-consolidate_services
+ config to collapse multiple phone numbers (or whatever) into as
+ few lines as possible on invoices, RT#5223
+
+2009-10-22 15:11 ivan
+
+ * FS/FS/cust_bill.pm: fix invoice sub-totals, RT#6489
+
+2009-10-22 12:56 ivan
+
+ * FS/FS/ClientAPI/MasonComponent.pm: make sure that in the case
+ where there's no uncancelled active packages, the filter doesn't
+ reduce the package list to nothing, RT#6029
+
+2009-10-22 12:53 ivan
+
+ * FS/FS/ClientAPI/MasonComponent.pm: acciendtally left debugging
+ in, RT#6029
+
+2009-10-22 05:50 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/part_pkg.pm,
+ FS/FS/ClientAPI/MasonComponent.pm,
+ httemplate/browse/part_pkg.cgi, httemplate/edit/part_pkg.cgi:
+ restrict additinal package order option, RT#6029
+
+2009-10-22 02:34 ivan
+
+ * httemplate/elements/city.html: yay, found blank city problem
+ w/new citytax foo, RT#5852
+
+2009-10-22 01:58 ivan
+
+ * httemplate/elements/select-county.html: fix all location selects
+ hidden on new customer add, fallout from city tax stuff, yay for
+ this not being on _1_9_BRANCH, RT#5852
+
+2009-10-22 01:08 ivan
+
+ * httemplate/search/svc_phone.cgi: should at least run under pg 8.3
+ now, need to test & see if the data make sense, RT#5496
+
+2009-10-21 22:32 ivan
+
+ * FS/FS/cust_bill_pkg_display.pm: fix incompatibility
+ w/invoice_sections vs. cust_bill_pkg.pkgnum -1 "virtual line
+ item" (rare)
+
+2009-10-21 17:28 ivan
+
+ * FS/FS/Schema.pm, bin/cdr-netsapiens.import: fix netsapiens CDR
+ import, RT#5226
+
+2009-10-21 00:09 ivan
+
+ * FS/FS/Schema.pm: MySQL doesn't like indexing a text field
+
+2009-10-20 23:34 ivan
+
+ * httemplate/search/prepay_credit.html: fix agent link in unused
+ prepaid card report
+
+2009-10-20 16:24 ivan
+
+ * FS/FS/cust_bill_pay_pkg.pm: should fix problems sending a receipt
+ against a specific package when taxes are in use
+
+2009-10-20 13:47 ivan
+
+ * bin/cdr-netsapiens.import: basic import working, still need
+ better src/dst mapping, RT#5226
+
+2009-10-20 13:43 ivan
+
+ * FS/FS/part_export/netsapiens.pm: better debug line showing full
+ URL, RT#5226
+
+2009-10-20 11:30 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/change_pay.html: don't show
+ duplicate 'Credit card' or 'Check' choices even when
+ signup_server-payby has CARD & DCRD (or CHEK & DCHK)
+
+2009-10-16 15:39 ivan
+
+ * FS/FS/cust_bill_pkg.pm: make warning about expensive lookup a
+ carp & controlled by $DEBUG
+
+2009-10-12 07:11 ivan
+
+ * bin/cdr-netsapiens.import: finish netsapiens import, RT#6365
+
+2009-10-12 06:09 ivan
+
+ * FS/FS/part_export/shellcommands.pm: terrible typo
+
+2009-10-12 05:50 ivan
+
+ * FS/FS/part_export/shellcommands.pm: add customer information to
+ shellcommands export, RT#5351
+
+2009-10-12 02:10 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: signup.cgi, signup.html: use
+ modules in signup.cgi instead of in the template, fixes "Insecure
+ dependency in rquire"
+
+2009-10-11 23:48 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html: fix
+ inadvertant "unknown package" error, fallout from pkg-balance
+ work, RT#6125
+
+2009-10-11 19:34 ivan
+
+ * FS/FS/cust_main.pm: using the per-city taxes sure was a lot
+ easier than the UI... RT#5852
+
+2009-10-11 19:30 ivan
+
+ * FS/FS/part_event/Action/: cust_bill_fee_percent.pm, fee.pm: fix
+ for no finance_pkgclass set
+
+2009-10-11 19:14 ivan
+
+ * httemplate/edit/cust_main_county.html: UI for per-city taxes
+ (setup and assigning to customers/package locations), RT#5852
+
+2009-10-11 18:58 ivan
+
+ * httemplate/: edit/cust_main/contact.html,
+ elements/select-country.html: UI for per-city taxes (setup and
+ assigning to customers/package locations), RT#5852
+
+2009-10-11 18:45 ivan
+
+ * FS/FS/Mason.pm, FS/FS/Misc.pm, FS/FS/Schema.pm,
+ FS/FS/cust_main_county.pm,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county-collapse.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/elements/city.html, httemplate/elements/location.html,
+ httemplate/elements/select-county.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/misc/cities.cgi: UI for per-city taxes (setup and
+ assigning to customers/package locations), RT#5852
+
+2009-10-11 00:44 ivan
+
+ * conf/ticket_system-default_queueid: default to 1
+
+2009-10-10 19:41 ivan
+
+ * ChangeLog, debian/changelog: Updated for 1.9.1
+
+2009-10-10 18:57 ivan
+
+ * ChangeLog, rpm/freeside.spec, debian/changelog: Updated for 1.9.1
+
+2009-10-10 18:50 ivan
+
+ * Makefile, bin/cvs2cl: helps to have the tool
+
+2009-10-10 18:48 ivan
+
+ * Makefile: do it
+
+2009-10-10 18:48 ivan
+
+ * rt/lib/RT.pm: huh #2
+
+2009-10-10 18:46 ivan
+
+ * rt/Makefile: huh
+
+2009-10-10 18:45 ivan
+
+ * httemplate/search/elements/search-html.html: don't try to follow
+ a blank redirect
+
+2009-10-10 18:45 ivan
+
+ * httemplate/search/report_cdr.html: end form
+
+2009-10-10 18:45 ivan
+
+ * httemplate/elements/tr-textarea.html: new rows and cols options
+
+2009-10-10 18:44 ivan
+
+ * httemplate/elements/tr-select-pkg_class.html: respect
+ element_name
+
+2009-10-10 18:43 ivan
+
+ * httemplate/browse/agent.cgi: cleanup
+
+2009-10-10 18:42 ivan
+
+ * eg/cdr_template.pm, httemplate/elements/selectlayers.html: doc
+
+2009-10-10 18:41 ivan
+
+ * FS/FS/part_export/globalpops_voip.pm: correct variable
+ initialization
+
+2009-10-10 18:40 ivan
+
+ * FS/FS/: access_right.pm, access_usergroup.pm,
+ clientapi_session_field.pm, cust_svc_option.pm: remembered to
+ customize this manpage
+
+2009-10-10 18:39 ivan
+
+ * FS/FS/Record.pm: ::1 becomes 127.0.0.1 in IP checks
+
+2009-10-09 20:22 ivan
+
+ * ChangeLog: commit message for a file about commitmessages
+
+2009-10-09 16:59 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/elements/select-terms.html, FS/FS/cust_bill.pm,
+ httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi: change invoice terms
+ for one-time charges (& bill them immediately), RT#5891
+
+2009-10-09 14:37 ivan
+
+ * FS/FS/cust_main.pm: add cust_pkg_ref option to charge
+
+2009-10-08 01:28 ivan
+
+ * httemplate/view/cust_main/packages.html: fix "show old packages"
+ accidentally hiding cancelled packages, RT#5276
+
+2009-10-07 23:00 ivan
+
+ * FS/FS/: cust_main.pm, Cron/bill.pm: freeside-daily -g and -m
+ cooperation (and no HASH() queue noise with -m)
+
+2009-10-07 22:44 ivan
+
+ * FS/FS/: cust_bill.pm, cust_main.pm: avoid some (look to be
+ harmless) warnings:
+
+ Argument "" isn't numeric in sprintf at
+ /usr/local/share/perl/5.10.0/FS/cust_bill.pm line 2358.
+
+ Argument "" isn't numeric in subtraction (-) at
+ /usr/local/share/perl/5.10.0/FS/cust_bill.pm line 2359.
+
+ Argument "" isn't numeric in addition (+) at
+ /usr/local/share/perl/5.10.0/FS/cust_main.pm line 2788.
+
+2009-10-07 22:09 ivan
+
+ * FS/FS/cust_bill.pm: tyop
+
+2009-10-07 18:15 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, conf/invoice_html,
+ conf/invoice_latex: conf switches to turn on smaller notes and
+ footer sections with stock templates, RT#5218
+
+2009-10-07 16:51 ivan
+
+ * httemplate/view/cust_statement.html: naming follow-up notices
+ from the event rather than creting a slew of separate templates,
+ RT#5217
+
+2009-10-07 16:44 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/misc/email-statement.cgi,
+ httemplate/misc/send-invoice.cgi,
+ httemplate/misc/send-statement.cgi,
+ httemplate/search/cust_event.html,
+ FS/FS/part_event/Action/cust_bill_send_reminder.pm,
+ conf/invoice_html, conf/invoice_latex, conf/invoice_template,
+ httemplate/view/cust_bill-pdf.cgi,
+ httemplate/view/cust_bill-ps.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_statement.html: naming follow-up notices
+ from the event rather than creting a slew of separate templates,
+ RT#5217
+
+2009-10-06 17:43 ivan
+
+ * FS/FS/Conf_compat17.pm: bring up-to-date wrt 1.7 Conf.pm
+ 1.180.2.52 (2009-10-03), RT#2873
+
+2009-10-06 16:51 ivan
+
+ * FS/FS/cust_bill_pkg.pm: remove inadvertant debugging warnings
+
+2009-10-06 08:49 jeff
+
+ * httemplate/edit/router.cgi: replace disappearing line
+
+2009-10-05 16:36 ivan
+
+ * FS/FS/svc_acct.pm: remove annoying warning: Use of uninitialized
+ value $FS::svc_acct::passwordmin in pattern match (m//) at
+ /usr/local/share/perl/5.10.0/FS/svc_acct.pm line 60.
+
+2009-10-05 11:17 ivan
+
+ * httemplate/misc/payment.cgi: oops, handle old cust_main.pm
+ properly (should we be bothering?) :/ RT#5889
+
+2009-10-05 09:57 ivan
+
+ * httemplate/view/cust_main/payment_history/voided_payment.html:
+ doh, fix error viewing voided payments, RT#6382
+
+2009-10-05 08:07 jeff
+
+ * httemplate/elements/tr-select-pkg_class.html: remove unused
+ option that DTWT
+
+2009-10-05 07:04 jeff
+
+ * httemplate/edit/cust_main/: bottomfixup.js,
+ choose_tax_location.html, contact.html: low hanging fruit:
+ improvement in tax location selection RT#6000
+
+2009-10-05 07:01 jeff
+
+ * httemplate/edit/router.cgi: low hanging fruit: restore router
+ virtual fields (and svcnum) in 1.9 RT#5960
+
+2009-10-04 19:40 ivan
+
+ * httemplate/misc/payment.cgi: add manual_process-skip_first
+ option, RT#5889
+
+2009-10-04 19:35 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, httemplate/misc/payment.cgi:
+ add manual_process-skip_first option, RT#5889
+
+2009-10-04 19:16 ivan
+
+ * FS/FS/Conf.pm, httemplate/misc/payment.cgi,
+ httemplate/misc/process/payment.cgi: add manual_process-display
+ config to subtract fee from amount instead of adding, RT#5889
+
+2009-10-04 18:40 jeff
+
+ * conf/invoice_html: can't revert because windows is ghei
+
+2009-10-04 18:07 jeff
+
+ * FS/FS/cust_main.pm: don't consider setup or recurring taxes when
+ billing usage on cancel
+
+2009-10-04 18:00 jeff
+
+ * FS/FS/: cust_bill.pm, cust_main.pm, part_pkg/voip_cdr.pm:
+ invoices with details in separate section but usage not separated
+
+2009-10-04 17:49 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/Upgrade.pm,
+ FS/FS/cust_bill.pm, conf/invoice_html, conf/invoice_htmlsummary,
+ conf/invoice_latex, conf/invoice_latexsummary,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_display.pm,
+ FS/FS/cust_main.pm, FS/FS/pkg_category.pm,
+ FS/FS/part_event/Action/cust_bill_fee_percent.pm,
+ FS/FS/part_event/Action/fee.pm,
+ httemplate/browse/pkg_category.html,
+ httemplate/edit/pkg_category.html: leading summary page invoices
+ #RT5086
+
+2009-10-04 16:36 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: fix the same
+ problem with processing payments & masked ACH amounts, RT#6374
+
+2009-10-04 16:31 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix problems "re-saving" a masked
+ card. hopefully the last of it!
+
+2009-10-03 19:09 ivan
+
+ * httemplate/misc/delete-cust_bill.html: delete invoices, RT#4048
+
+2009-10-03 19:04 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/Setup.pm,
+ FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm,
+ FS/bin/freeside-addgroup, bin/mapsecrets2access_user,
+ httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/payment_history/invoice.html: delete
+ invoices, RT#4048
+
+2009-10-03 17:08 ivan
+
+ * httemplate/view/cust_main.cgi: more room for cancellation reason
+
+2009-10-03 17:07 ivan
+
+ * httemplate/elements/popup_link-cust_main.html: doc
+
+2009-10-03 15:09 ivan
+
+ * httemplate/edit/part_bill_event.cgi: cancel to 80, comp @ 90,
+ RT#5674
+
+2009-10-03 15:06 ivan
+
+ * httemplate/edit/part_bill_event.cgi: in old invoice events,
+ chance default weight of cancel event to 90 so you can place it
+ at the same time as a realtime event, RT#5674
+
+2009-10-03 13:22 mark
+
+ * FS/FS/: pay_batch.pm, pay_batch/paymentech.pm: Load XML::Simple
+ at runtime to avoid breakage
+
+2009-10-01 16:48 ivan
+
+ * FS/FS/cust_main.pm: hopefully fully fix bulk email errors when
+ selecting a single payby
+
+2009-10-01 16:01 ivan
+
+ * FS/FS/Schema.pm: allow svc_acct._password to be NULL in the
+ schema
+
+2009-10-01 15:02 ivan
+
+ * httemplate/view/cust_main/payment_history/voided_payment.html:
+ consistency with un-voided payments wrt display
+
+2009-09-30 17:52 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: hopefully the
+ really final fix for the problems using remembered cards in
+ self-service. *sigh*
+
+2009-09-30 17:29 ivan
+
+ * FS/: FS/Conf.pm, FS/Cron/check.pm, bin/freeside-check: add login
+ check to FS::ClientAPI::SG/SGNG checks. RT#4610
+
+2009-09-30 12:33 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: hopefully the
+ final fix for the problems using remembered cards in self-service
+
+2009-09-29 15:08 ivan
+
+ * httemplate/misc/email-customers.html: should fix newsletter
+ sending error when you select a single payby only
+
+2009-09-28 23:17 ivan
+
+ * FS/t/cust_attachment.t: forgot
+
+2009-09-28 15:45 ivan
+
+ * FS/FS/Cron/check.pm: more accurate SG check, RT#4610
+
+2009-09-28 15:35 ivan
+
+ * FS/: FS/Cron/check.pm, bin/freeside-check: correct place for use
+ Email::Send, RT#4610
+
+2009-09-28 15:23 ivan
+
+ * FS/FS/Cron/check.pm: oops, hopefully fix sg cron check, RT#4610
+
+2009-09-28 08:55 jeff
+
+ * FS/FS/cust_pkg.pm: use object copy when billing on cancel to
+ avoid very weird side effects ( including 'impossible' history )
+ RT#5723
+
+2009-09-25 05:29 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi,
+ httemplate/config/config-process.cgi,
+ httemplate/misc/payment.cgi, httemplate/misc/process/payment.cgi:
+ processing fee on credit card recharges, RT#5889
+
+2009-09-25 03:29 ivan
+
+ * FS/FS/Conf.pm: this one too
+
+2009-09-25 03:14 ivan
+
+ * FS/FS/Conf.pm, FS/FS/svc_acct.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/elements/select-part_svc.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-part_svc.html: nomadix, RT#5876
+
+2009-09-24 19:30 mark
+
+ * FS/FS/pay_batch.pm, FS/FS/Conf.pm, FS/FS/pay_batch/BoM.pm,
+ FS/FS/pay_batch/PAP.pm, FS/FS/pay_batch/ach_spiritone.pm,
+ FS/FS/pay_batch/chase_canada.pm, FS/FS/pay_batch/paymentech.pm,
+ FS/FS/pay_batch/td_canada_trust.pm,
+ httemplate/misc/download-batch.cgi,
+ httemplate/search/cust_pay_batch.cgi: Batch payment refactoring
+
+2009-09-24 18:04 mark
+
+ * FS/MANIFEST: Add cust_attachment
+
+2009-09-24 12:59 ivan
+
+ * FS/FS/: Cron/check.pm, Conf.pm: get the ping username/pass from a
+ config, good enough, RT#4610
+
+2009-09-23 20:14 ivan
+
+ * FS/FS/Cron/check.pm, FS/bin/freeside-check,
+ FS/FS/ClientAPI/SGNG.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add SG and SGNG
+ ping, RT#4610
+
+2009-09-23 16:47 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: this should fix the occasional
+ extra ticket showing up on wrong customer record
+
+2009-09-21 20:08 ivan
+
+ * FS/FS/Mason.pm: add addl_comp_root.pl and addl_handler_use.pl
+ config files, RT#4743
+
+2009-09-21 13:48 ivan
+
+ * FS/FS/cust_bill.pm: i think it is new Pg (or... new Record.pm???)
+ that causes this problem... before it just returned nothing for
+ the search instead of erroring out?
+
+2009-09-20 23:05 ivan
+
+ * FS/FS/Conf.pm: doc clarification
+
+2009-09-20 22:35 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm: left debugging on
+
+2009-09-20 19:41 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_ApplicationCommon.pm, FS/FS/cust_bill_pay.pm,
+ FS/FS/cust_bill_pay_pkg.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/elements/ApplicationCommon.html: add
+ ability to trigger receipts when payment is used against a
+ specific package instead of when it was made, RT#5199
+
+2009-09-18 15:41 ivan
+
+ * FS/FS/: cust_main.pm,
+ part_event/Condition/has_referral_custnum.pm: add active option
+ to has_referral_custnum condition, RT#6150
+
+2009-09-18 15:35 ivan
+
+ * httemplate/elements/: select-cust_main-status.html,
+ select-cust_pkg-status.html: fix customer and package status
+ conditions not sticky on edit, noticed on RT#6150
+
+2009-09-18 12:28 ivan
+
+ * httemplate/edit/elements/edit.html: better error message when the
+ clone object isn't found, RT#6128
+
+2009-09-17 18:03 ivan
+
+ * httemplate/view/cust_main/one_time_charge_link.html: fix one-time
+ charge inadvertantly checking the "tax exempt" checkbox when the
+ tax product popup is swapped in and out, RT#6095
+
+2009-09-17 16:45 ivan
+
+ * FS/FS/cust_bill.pm: eliminate mystery "min ( N.NN, M.MM )"
+ warning
+
+2009-09-16 18:37 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: fix sales tax report
+ w/part_pkg overrides, RT#6197
+
+2009-09-15 17:52 ivan
+
+ * FS/FS/Mason.pm: quiet warning: Subroutine
+ Net::Ping::External::_ping_linux redefined
+
+2009-09-15 16:13 ivan
+
+ * FS/FS/: cdr.pm, part_pkg/voip_cdr.pm: set svcnum when rating CDR,
+ RT#5495
+
+2009-09-15 15:58 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: hopefully really finish fixing for
+ good "Real timeprocessing not enabled!" error when using signup
+ without any real-time processor, RT#6043
+
+2009-09-15 13:45 ivan
+
+ * httemplate/search/svc_phone.cgi: fix phone# usage search, RT#
+
+2009-09-15 13:44 ivan
+
+ * httemplate/search/svc_external.cgi: modernize external search
+
+2009-09-15 13:44 ivan
+
+ * FS/FS/: cust_main_Mixin.pm, UI/Web.pm: fix display_custnum on
+ reports, RT#
+
+2009-09-15 12:45 ivan
+
+ * htetc/handler.pl: eliminate "Use of uninitialized value in undef
+ operator at /etc/freeside/handler.pl line 79/80" errors
+
+2009-09-14 16:52 ivan
+
+ * FS/FS/Mason.pm, httemplate/elements/popup_link-ping.html,
+ httemplate/misc/ping.html, httemplate/misc/xmlhttp-ping.html,
+ httemplate/view/svc_broadband.cgi,
+ httemplate/view/cust_main/packages/services.html: ping tool,
+ RT#5845
+
+2009-09-11 10:40 ivan
+
+ * FS/FS/cust_bill_pkg_detail.pm: preserve allowing empty values,
+ doh, RT#6101
+
+2009-09-11 08:51 ivan
+
+ * FS/FS/: Schema.pm, cust_bill_pkg.pm, cust_bill_pkg_detail.pm: fix
+ (hopefully the rest of the) fallout from rating CDRs to sub-penny
+ amounts
+
+2009-09-10 10:55 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/success.html: remove random >
+ char from signup success.html, RT#6110
+
+2009-08-31 15:17 mark
+
+ * FS/FS/cdr/vitelity.pm: Add Vitelity CDR format
+
+2009-08-29 15:47 mark
+
+ * FS/FS/: cdr.pm, cdr/broadsoft.pm: Add Broadsoft CDR record format
+
+2009-08-29 15:45 mark
+
+ * FS/bin/freeside-cdr-sftp_and_import: Add FTP support
+
+2009-08-29 12:05 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: hopefully really fix self-service
+ when not using real-time gateway
+
+2009-08-29 11:51 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: hopefully fix self-service when not
+ using real-time gateway
+
+2009-08-27 14:41 ivan
+
+ * httemplate/elements/menu.html: oops, tax class menu item got lost
+
+2009-08-27 03:26 ivan
+
+ * bin/move-customers: try, try again
+
+2009-08-27 03:23 ivan
+
+ * bin/move-customers: doh
+
+2009-08-27 03:04 ivan
+
+ * bin/move-customers: ok
+
+2009-08-26 23:52 ivan
+
+ * FS/FS/part_export/amazon_ec2.pm: amazon ec2 provisioning
+
+2009-08-26 13:52 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: improve
+ testing need for invoicing list
+
+2009-08-26 09:19 rsiddall
+
+ * FS/FS/part_export/domreg_net_dri.pm: OpenSRS support for domain
+ registration first appears in Net::DRI 0.95.
+
+2009-08-26 04:22 ivan
+
+ * httemplate/search/: report_svc_phone.html, svc_phone.cgi: first
+ pass at balance reporting, RT#5496
+
+2009-08-26 03:18 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add granularity to single_price CDR
+ rating, RT#5495
+
+2009-08-25 12:08 jeff
+
+ * httemplate/search/: 477.html, report_477.html,
+ elements/search.html: improved 477 report #6004
+
+2009-08-25 10:03 jeff
+
+ * FS/FS/Record.pm: support listref to qsearch as UNION
+
+2009-08-24 04:09 ivan
+
+ * bin/move-customers: customer move script, RT#5351
+
+2009-08-24 01:50 ivan
+
+ * FS/FS/cust_main.pm, bin/move-customers: (start of) customer move
+ script, RT#5351
+
+2009-08-24 00:08 ivan
+
+ * bin/move-customers: (start of) customer move script, RT#5351
+
+2009-08-23 23:50 ivan
+
+ * bin/move-customers: (start of) customer move script, RT#5351
+
+2009-08-23 23:39 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm, FS/FS/part_pkg.pm,
+ bin/move-customers: (start of) customer move script, RT#5351
+
+2009-08-23 23:13 jeff
+
+ * httemplate/edit/process/part_pkg.cgi: fix select multiple report
+ option
+
+2009-08-23 22:09 jeff
+
+ * httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/cust_bill_pkg_detail.cgi,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/graph/report_cust_bill_pkg_detail.html,
+ FS/FS/Report/Table/Monthly.pm, httemplate/elements/menu.html,
+ httemplate/search/cust_bill_pkg.cgi: add rated call sales report
+ and option to sales report to count usage separately from
+ recurring #5588
+
+2009-08-21 17:31 ivan
+
+ * httemplate/view/cust_main/packages/status.html: better label
+
+2009-08-21 17:31 ivan
+
+ * httemplate/misc/states.cgi: mason style
+
+2009-08-21 17:30 ivan
+
+ * httemplate/elements/header.html: doc
+
+2009-08-21 17:29 ivan
+
+ * httemplate/edit/elements/edit.html: use name_singular if
+ available too
+
+2009-08-21 17:29 ivan
+
+ * httemplate/edit/cust_main/bottomfixup.js: comment
+
+2009-08-21 17:29 ivan
+
+ * httemplate/edit/quick-charge.html: comments
+
+2009-08-21 17:29 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/passwd.cgi: not necessary
+
+2009-08-21 17:27 ivan
+
+ * FS/FS/Cron/check.pm: fewer false positives
+
+2009-08-21 17:27 ivan
+
+ * FS/FS/cust_bill.pm: doc
+
+2009-08-21 17:26 ivan
+
+ * FS/FS/Record.pm: cleaner??
+
+2009-08-21 17:22 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi, FS/FS/Conf.pm,
+ FS/FS/ClientAPI/MasonComponent.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/misc/part_svc-columns.cgi,
+ fs_selfservice/FS-SelfService/cgi/misc/svc_acct-domains.cgi,
+ httemplate/edit/cust_main/first_pkg/select-part_pkg.html,
+ httemplate/edit/cust_main/first_pkg/svc_acct.html: order
+ svc_phone services from self-service too, RT#5085
+
+2009-08-20 13:44 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: mitigate harmless
+ warnings spamming logs
+
+2009-08-20 02:55 ivan
+
+ * FS/FS/part_event/Condition.pm: allow generic conditions for
+ cust_statement, RT#4860
+
+2009-08-20 02:47 ivan
+
+ * FS/FS/Mason.pm, FS/FS/cust_statement.pm, FS/FS/Cron/bill.pm,
+ FS/FS/part_event/Action/cust_statement.pm,
+ FS/FS/part_event/Action/cust_statement_send.pm,
+ FS/FS/part_event/Condition/has_pkg_class.pm,
+ FS/FS/part_event/Condition/has_pkgpart.pm,
+ FS/FS/part_event/Condition/hasnt_pkgpart.pm,
+ httemplate/misc/email-statement.cgi,
+ httemplate/view/cust_statement-pdf.cgi,
+ httemplate/view/cust_statement.html: email statements, RT#4860
+
+2009-08-20 02:26 ivan
+
+ * bin/generate-table-module: tired of asking
+
+2009-08-19 21:03 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_statement.pm, FS/FS/part_event.pm,
+ FS/t/cust_statement.t, FS/FS/Cron/bill.pm,
+ FS/FS/part_event/Action/cust_statement.pm,
+ FS/FS/part_event/Action/cust_statement_send.pm,
+ httemplate/view/cust_statement.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/payment_history/statement.html:
+ Emailing statements of accounts, RT#4860
+
+2009-08-19 19:40 ivan
+
+ * FS/FS/part_event/Action/cust_bill_send.pm: eliminate unnecessary
+ lookup
+
+2009-08-19 14:27 ivan
+
+ * FS/FS/part_event/Action/: cust_bill_fee_percent.pm, fee.pm,
+ pkg_referral_credit_pkg.pm: add option to disable late fee taxes
+
+2009-08-19 00:05 mark
+
+ * FS/FS/part_export/shellcommands_withdomain.pm: Turn off
+ userdel_no_queue for MagicMail export
+
+2009-08-18 23:15 jeff
+
+ * FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/search/cust_bill_pkg.cgi: option to count subpackages
+ outside packages in sales report #5588
+
+2009-08-18 05:21 jeff
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main/bottomfixup.js: support
+ 2009 - a better way?
+
+2009-08-17 16:08 mark
+
+ * FS/FS/part_export/: shellcommands.pm,
+ shellcommands_withdomain.pm: Add more fine-grained queue options
+
+2009-08-17 13:48 jeff
+
+ * FS/FS/: Record.pm, Schema.pm, cust_bill.pm, cust_bill_pkg.pm,
+ cust_bill_pkg_detail.pm, cust_svc.pm, part_pkg/voip_cdr.pm:
+ improve emailed cdr csv file (#5727 again)
+
+2009-08-16 15:45 jeff
+
+ * httemplate/edit/cust_main/bottomfixup.js: include a census
+ zipcode link and other minor improvements
+
+2009-08-14 15:13 jeff
+
+ * FS/FS/svc_acct.pm: add svcnum lookup for svc_acct
+
+2009-08-14 14:51 jeff
+
+ * FS/FS/part_export/shellcommands.pm: add pkgnum and custnum to use
+ as external ids
+
+2009-08-14 10:26 mark
+
+ * httemplate/edit/cust_main_attach.cgi: Fix incorrect access right
+
+2009-08-14 10:24 mark
+
+ * FS/FS/AccessRight.pm: Move note/attachment rights to new section
+
+2009-08-13 15:53 mark
+
+ * FS/bin/freeside-void-payments: Add option to
+ freeside-void-payments to cancel customers
+
+2009-08-13 08:13 jeff
+
+ * FS/FS/cust_pkg.pm: fix fix multiple pkgpart search (need sanity
+ check): don't let empty set limit search
+
+2009-08-13 02:35 ivan
+
+ * ChangeLog: adding autogen changelog on 1.9
+
+2009-08-13 02:25 ivan
+
+ * httemplate/view/cust_main.cgi: fix reverted changes. grr.
+ RT#4964
+
+2009-08-12 17:26 ivan
+
+ * init.d/freeside-init: useful stuff for webdemo & profiling
+
+2009-08-12 07:58 jeff
+
+ * bin/billco-upload, Makefile, FS/FS/Conf.pm, FS/FS/Cron/upload.pm,
+ FS/bin/freeside-daily, FS/bin/freeside-monthly,
+ httemplate/config/config-view.cgi: internalize billco-upload and
+ automate the transfer to the provider RT#5902
+
+2009-08-12 04:57 ivan
+
+ * httemplate/: docs/about.html, docs/credits.html,
+ docs/license.html, elements/header-popup.html: slight
+ about/credits UI tweak
+
+2009-08-11 22:22 ivan
+
+ * FS/FS/: cust_main.pm, part_event/Action.pm,
+ part_event/Action/cust_bill_fee_percent.pm,
+ part_event/Action/fee.pm: add pre-bill event stage for late fees,
+ RT#5589
+
+2009-08-11 19:36 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/misc/bulk_change_pkg.cgi,
+ httemplate/search/cust_pkg.cgi: fix multiple pkgpart search,
+ RT#5924
+
+2009-08-11 14:33 rsiddall
+
+ * rpm/freeside.spec: Back out kludge to show CVS snapshot date in
+ version number in GUI. You can get the snapshot date from the
+ release number using "rpm -q freeside".
+
+2009-08-11 14:29 rsiddall
+
+ * Makefile: Reset RPM release number back to 1 on a new version
+ release.
+
+2009-08-10 19:45 ivan
+
+ * FS/FS/part_event/Action/writeoff.pm: fix bad debt writeoff
+ action, RT#5798
+
+2009-08-10 19:34 ivan
+
+ * FS/FS/cust_main.pm: add $company_name and $company_address to
+ decline template, RT#5869
+
+2009-08-10 16:04 mark
+
+ * FS/FS/AccessRight.pm, httemplate/edit/cust_main_attach.cgi,
+ httemplate/edit/process/cust_main_attach.cgi,
+ httemplate/view/attachment.html, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/attachments.html: Improve handling of
+ deleted attachments
+
+2009-08-10 14:44 ivan
+
+ * FS/FS/part_export/netsapiens.pm: last nits on netsapiens export,
+ RT#5226
+
+2009-08-10 11:05 mark
+
+ * FS/FS/part_export/shellcommands_withdomain.pm: Add preset for
+ magicmail
+
+2009-08-10 04:57 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/process_svc_external.html: when
+ using pkg-balances, limit self-service access when a customer
+ with multiple packages logs on, RT#4189
+
+2009-08-10 04:50 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/logout.html,
+ fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html:
+ when using pkg-balances, limit self-service access when a
+ customer with multiple packages logs on, RT#4189
+
+2009-08-09 16:53 jeff
+
+ * FS/FS/part_event/Action/cust_bill_spool_csv.pm: whoops: theory
+ should match practice
+
+2009-08-09 16:45 jeff
+
+ * FS/FS/part_event/Action/cust_bill_spool_csv.pm: fix per agent
+ spools
+
+2009-08-09 15:47 jeff
+
+ * FS/FS/cust_bill_pkg.pm: don't bomb when the line item has no
+ start date
+
+2009-08-09 02:05 mark
+
+ * FS/FS/cust_attachment.pm, httemplate/edit/cust_main_attach.cgi,
+ httemplate/edit/process/cust_main_attach.cgi,
+ httemplate/view/attachment.html, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/attachments.html, FS/FS/AccessRight.pm,
+ FS/FS/Conf.pm, FS/FS/Mason.pm, FS/FS/Record.pm, FS/FS/Schema.pm:
+ Add cust_attachment stuff
+
+2009-08-07 16:08 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/top_misc.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/elements/tr-input-date-field.html: add ability to edit
+ signup dates (turn on cust_main-edit_signupdate config), RT#4644
+
+2009-08-06 17:39 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/elements/tr-input-date-field.html,
+ httemplate/misc/order_pkg.html,
+ httemplate/view/cust_main/packages.html: don't start recurring
+ billing when a start date hasn't been reached yet either... and
+ since that works, add the start date to new package order,
+ RT#5347
+
+2009-08-05 17:41 ivan
+
+ * FS/FS/part_export/netsapiens.pm: pass mac addresses as lower-case
+ to netsapiens, RT#5226
+
+2009-08-05 17:39 ivan
+
+ * FS/FS/svc_phone.pm: delete phone_device records when svc_phone is
+ deleted, RT#5226
+
+2009-08-05 16:32 ivan
+
+ * FS/FS/part_pkg/: agent.pm, base_rate.pm, flat.pm,
+ flat_delayed.pm, prorate_delayed.pm: fix cancellation errors with
+ updated flat_introrate, RT#5865
+
+2009-08-04 19:27 ivan
+
+ * FS/FS/: svc_acct.pm: export negative byte values to chillispot
+ attributes as 0, RT#5815
+
+2009-08-04 16:43 ivan
+
+ * FS/: FS/svc_acct.pm, bin/freeside-sqlradius-reset: hopefully
+ ignore errors about deleted accounts and properly finish
+ freeside-sqlradius-reset, RT#5868
+
+2009-08-04 15:04 ivan
+
+ * FS/: FS/svc_Common.pm, bin/freeside-sqlradius-reset: ignore
+ problams calling ->overlimit during sqlradius-reset, wtf?!
+ RT#5868
+
+2009-08-03 17:19 ivan
+
+ * FS/FS/cust_pkg.pm: don't reset usage on package change when
+ usage_rollover is on, it adds twice...
+
+2009-08-03 12:54 ivan
+
+ * httemplate/elements/header.html: looks slightly better in default
+ IE hopefully
+
+2009-08-03 07:17 jeff
+
+ * FS/FS/part_export/shellcommands.pm: new doesn't exist
+
+2009-08-03 07:07 jeff
+
+ * FS/FS/part_export/shellcommands.pm: bad tyops
+
+2009-08-01 12:16 jeff
+
+ * FS/FS/: Schema.pm, queue.pm, queue_arg.pm: support broader array
+ of queue args #5855, fallout from #5495
+
+2009-07-31 06:20 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/ach_payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/change_bill.html,
+ fs_selfservice/FS-SelfService/cgi/change_password.html,
+ fs_selfservice/FS-SelfService/cgi/change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/footer.html,
+ fs_selfservice/FS-SelfService/cgi/header.html,
+ fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/logout.html,
+ fs_selfservice/FS-SelfService/cgi/make_ach_payment.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_bill.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_password.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/process_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/process_order_recharge.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/provision.html,
+ fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_prepay.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_results.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html,
+ fs_selfservice/FS-SelfService/cgi/view_usage_details.html: skin
+ up self-service according to config passed from backend, RT#5530
+
+2009-07-31 00:58 ivan
+
+ * bin/cdr-transnexus.import: only need Customer-CDRs, RT#5229
+
+2009-07-31 00:51 ivan
+
+ * bin/cdr-transnexus.import: only need Customer-CDRs, RT#5229
+
+2009-07-30 02:19 ivan
+
+ * FS/FS/: cust_main.pm, ClientAPI/MyAccount.pm: pass a pkgnum from
+ self-service if applicable, RT#4339
+
+2009-07-30 01:43 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: lost fix for
+ illegal state?
+
+2009-07-30 00:39 ivan
+
+ * httemplate/view/cust_main/change_history.html: small fix to
+ change history to not error out with svc_acct services, RT#1005
+
+2009-07-30 00:35 mark
+
+ * FS/FS/part_export/shellcommands.pm: Make no_queue option work
+ correctly
+
+2009-07-29 23:52 ivan
+
+ * httemplate/view/cust_main/payment_history.html: didn't mean to
+ leave a Dump(er) there
+
+2009-07-29 23:50 ivan
+
+ * httemplate/edit/cust_pay.cgi: another accidentally (alliterated)
+ vestigial variable
+
+2009-07-29 23:49 ivan
+
+ * httemplate/edit/cust_credit.cgi: don't need to lookup cust_main
+ here
+
+2009-07-29 23:48 ivan
+
+ * httemplate/elements/select-cust_pkg-balances.html: fix for
+ stickiness on errors
+
+2009-07-29 23:42 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_ApplicationCommon.pm, FS/FS/cust_bill_pay.pm,
+ FS/FS/cust_credit.pm, FS/FS/cust_credit_bill.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pay_pending.pm,
+ FS/FS/cust_pay_void.pm, FS/FS/cust_pkg.pm, FS/FS/cust_main.pm,
+ httemplate/edit/cust_credit.cgi, httemplate/edit/cust_pay.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/elements/select-cust_pkg-balances.html,
+ httemplate/elements/tr-select-cust_pkg-balances.html,
+ httemplate/view/cust_bill.cgi, httemplate/view/cust_pay.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/packages/status.html,
+ httemplate/view/cust_main/payment_history/payment.html,
+ httemplate/view/cust_main/payment_history/credit.html,
+ httemplate/view/cust_main/payment_history/voided_payment.html:
+ experimental package balances, RT#4339
+
+2009-07-28 15:21 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, httemplate/edit/cust_main/billing.html,
+ httemplate/view/cust_main/billing.html: feature to email CSV of
+ CDRs with invoices #5727
+
+2009-07-28 14:17 ivan
+
+ * FS/FS/Conf.pm, FS/FS/AccessRight.pm, FS/FS/Mason.pm,
+ FS/FS/svc_external.pm, httemplate/pref/pref.html,
+ httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/change_history.html: adding a basic
+ change history using history tables, RT#1005, RT#4357
+
+2009-07-27 19:12 mark
+
+ * FS/FS/part_export/: shellcommands.pm,
+ shellcommands_withdomain.pm: Add no_queue option to shellcommands
+ exports
+
+2009-07-27 12:51 ivan
+
+ * httemplate/misc/cancel_pkg.html: fix spacing
+
+2009-07-27 02:59 ivan
+
+ * httemplate/search/: report_cust_pay.html,
+ elements/cust_pay_or_refund.html: searching for voided payments
+ by void date as well, RT#5786
+
+2009-07-27 02:07 ivan
+
+ * httemplate/: elements/menu.html, search/cust_pay_void.html,
+ search/report_cust_pay.html,
+ search/elements/cust_pay_or_refund.html, view/cust_pay.html,
+ view/cust_pay_void.html: voided payment report, RT#5786
+
+2009-07-26 23:17 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-view.cgi: add deprecated
+ config options back to Conf.pm to fix "unapplypayments" fails
+ existential comparison errors, RT#2927
+
+2009-07-26 20:26 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/bottomfixup.js,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/elements/location.html,
+ httemplate/misc/xmlhttp-cust_main-censustract.html,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/view/cust_main/misc.html: FCC from 477 improvements
+ #4912
+
+2009-07-26 13:23 jeff
+
+ * httemplate/search/svc_broadband.cgi: fix links
+
+2009-07-26 12:36 ivan
+
+ * rt/FREESIDE_MODIFIED: fix the date picker in RT to use jscalendar
+ instead of an HTML popup (that had acquired the page header,
+ eek), RT#1682
+
+2009-07-26 11:40 ivan
+
+ * FS/FS/Conf_compat17.pm: bring up-to-date wrt 1.7 Conf.pm
+ 1.180.2.49 (2009-7-26), RT#2873
+
+2009-07-25 23:38 jeff
+
+ * FS/bin/freeside-apply_payments_and_credits: command line tool to
+ apply payments and credits
+
+2009-07-25 15:00 ivan
+
+ * htetc/handler.pl: set a character encoding for all pages; this
+ should fix problems with diamond question marks even when the
+ server gets an UTF-8 default setting, RT#3094
+
+2009-07-25 14:33 ivan
+
+ * FS/FS/cust_event.pm, httemplate/search/cust_event.html: this
+ should fix the re-email/print links on event search pages sending
+ too much, RT#5740, RT#5570
+
+2009-07-25 00:57 ivan
+
+ * FS/FS/Record.pm: teach Record.pm about BYTEA handling in order to
+ store files in the db, RT#4964
+
+2009-07-24 02:38 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add handling of ChilliSpot (and
+ CoovaChilli) Max attributes, specifically
+ ChilliSpot-Max-{Input,Output,Total}-{Octets,Gigawords}, RT#5815
+
+2009-07-23 22:51 mark
+
+ * FS/bin/freeside-void-payments: Add -v switch (verbose) to
+ freeside-void-payments
+
+2009-07-23 12:46 ivan
+
+ * FS/FS/cust_pay.pm: avoid harmless warning: Use of uninitialized
+ value in string ne
+
+2009-07-23 12:40 ivan
+
+ * FS/bin/freeside-void-payments: fix -r option, RT#5675
+
+2009-07-23 09:48 jeff
+
+ * httemplate/browse/svc_acct_pop.cgi: restore svc_acct_pop editing
+
+2009-07-23 06:25 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/process/cust_main_county-collapse.cgi: add back remove
+ ("collapse") links again. on each line this time. RT#2973
+
+2009-07-22 23:58 ivan
+
+ * FS/FS/Tron.pm: need to see who is still on deb 4 & pg 7.4
+
+2009-07-22 15:05 ivan
+
+ * httemplate/view/cust_main/misc.html: fix bombing out on new
+ DateTime
+
+2009-07-21 11:44 ivan
+
+ * FS/FS/: Schema.pm, part_pkg/cdr_termination.pm: sub-penny
+ termination pricing too, RT#5495
+
+2009-07-21 00:03 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: yow. fix spurious charge errors
+ w/single_price, round to four decimal places (wtf?) instead of 2,
+ RT#5495
+
+2009-07-20 22:29 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: and fix min_charge option, RT#5495
+
+2009-07-20 22:27 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: if we're going to do recur_Common,
+ have to use and @ISA (and capitalize) it
+
+2009-07-20 17:20 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: don't have a money type in package
+ definitions at the moment
+
+2009-07-20 16:01 jeff
+
+ * FS/FS/: Conf.pm, cust_bill.pm: config setting to have emailed
+ invoices include call details #5275
+
+2009-07-20 07:26 jeff
+
+ * httemplate/elements/checkbox.html,
+ httemplate/elements/tr-checkbox.html,
+ httemplate/elements/tr-justtitle.html,
+ httemplate/elements/tr-title.html, FS/FS/Schema.pm,
+ FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm, FS/FS/cust_main.pm,
+ FS/FS/part_pkg.pm, FS/FS/part_pkg_link.pm,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/elements/edit.html,
+ httemplate/edit/process/part_pkg.cgi: bundle bill linked packages
+ into top line total when desired #5724
+
+2009-07-19 21:51 ivan
+
+ * FS/FS/cust_pkg.pm: one $conf is enough
+
+2009-07-19 21:40 ivan
+
+ * rt/: FREESIDE_MODIFIED, lib/RT/Transaction_Overlay.pm: slightly
+ improve terrible quoting behavior when you change MessageBoxWidth
+
+2009-07-19 21:19 ivan
+
+ * httemplate/elements/header.html: fix preferences links showing as
+ blue/purple on RT side
+
+2009-07-19 21:14 ivan
+
+ * rt/FREESIDE_MODIFIED: fix badly styled links on ticket create
+ (fallout from RT borging/styling)
+
+2009-07-17 16:33 ivan
+
+ * FS/FS/cdr.pm: remove unused cdr_upstream_rate
+
+2009-07-17 16:10 rsiddall
+
+ * rpm/freeside.spec: Filter out requirements for specific Freeside
+ modules so that you can install an RPM which requires missing
+ Freeside modules.
+
+2009-07-17 15:26 ivan
+
+ * FS/: FS/cust_main.pm, FS/Cron/bill.pm, bin/freeside-daily: commit
+ pkgpart exclusion for billing run, RT#5495
+
+2009-07-17 07:58 jeff
+
+ * bin/billco-upload: add mutex and commit changes found on
+ installed system
+
+2009-07-16 19:29 jeff
+
+ * httemplate/search/cust_pkg.cgi: FSM, another missed file for 477
+ reporting
+
+2009-07-16 18:44 jeff
+
+ * FS/FS/: Conf.pm, cust_main.pm, cust_pkg.pm, part_pkg/voip_cdr.pm:
+ bill usage when cancelling package
+
+2009-07-16 18:08 jeff
+
+ * bin/generate-table-module: black magic to edit Mason.pm as well
+
+2009-07-16 17:35 ivan
+
+ * FS/FS/part_pkg/: voip_cdr.pm: add single_price option so you can
+ do one per-minute price without rate tables, RT#5495
+
+2009-07-16 17:33 ivan
+
+ * FS/MANIFEST: get rid of cdr_upstream_rate table and some other
+ old convergent cruft
+
+2009-07-16 17:10 ivan
+
+ * FS/: FS/Schema.pm, FS/cdr.pm, FS/cdr_upstream_rate.pm,
+ FS/part_pkg/voip_cdr.pm, FS.pm, t/cdr_upstream_rate.t: get rid of
+ cdr_upstream_rate table and some other old convergent cruft
+
+2009-07-16 15:16 jeff
+
+ * FS/FS/Mason.pm: dark magic coming soon
+
+2009-07-15 18:57 jeff
+
+ * httemplate/search/477.html: duh! more 477 files
+
+2009-07-15 18:35 jeff
+
+ * httemplate/search/report_477.html: missed file for 477 reporting
+
+2009-07-15 16:06 ivan
+
+ * FS/FS/part_pkg/cdr_termination.pm: unused for now
+
+2009-07-15 15:49 ivan
+
+ * FS/FS/: Conf.pm, cdr.pm: add option to trim leading zeros when
+ setting charged_party to accountcode, RT#5495
+
+2009-07-14 12:06 rsiddall
+
+ * FS/FS/part_export/domreg_net_dri.pm: Minor bug fix, spotted by
+ Jeff.
+
+2009-07-14 12:05 rsiddall
+
+ * FS/FS/svc_domain.pm: Changed description of "action" field to
+ match domain registration exports.
+
+2009-07-13 20:14 ivan
+
+ * htetc/handler.pl: fix warnings, from RT merge fallout
+
+2009-07-13 19:53 ivan
+
+ * FS/FS/part_export/netsapiens.pm: fix netsapiens device
+ provisioning? or at least better debugging, RT#5226
+
+2009-07-13 19:52 ivan
+
+ * httemplate/elements/tr-select-did.html: stop Dumper spew
+
+2009-07-13 17:28 rsiddall
+
+ * Makefile, FS/FS/svc_domain.pm,
+ FS/FS/part_export/domreg_net_dri.pm,
+ httemplate/edit/process/domreg.cgi: New export to
+ register/transfer/renew/revoke domains using Net::DRI. Currently
+ optimized for OpenSRS. Should become more generalized in later
+ releases. Modified Makefile to insert the Freeside log folder
+ into the new export. Modified svc_domain.pm to prevent
+ generation of transfer requests when a domain is moved to a
+ different package with a domain registration attached to one of
+ the included services. Modified domreg.cgi to display errors on
+ a separate page.
+
+2009-07-13 09:02 jeff
+
+ * FS/FS/: tax_rate.pm, tax_rate_location.pm: correct ordering and
+ other bugs in tax updates
+
+2009-07-13 03:12 ivan
+
+ * FS/FS/part_pkg/recur_Common.pm: eliminate harmless "no %info hash
+ found in FS::part_pkg::recur_Common, skipping" warning
+
+2009-07-13 02:19 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi: fix
+ self-service agent-specific logos
+
+2009-07-13 02:10 ivan
+
+ * httemplate/view/cust_bill-logo.cgi: fix old-style agent-virt
+ logo?
+
+2009-07-13 00:21 ivan
+
+ * conf/invoice_html: thank you IE8
+
+2009-07-13 00:10 ivan
+
+ * httemplate/search/report_unapplied_cust_pay.html: clarify wording
+
+2009-07-12 18:50 ivan
+
+ * httemplate/elements/: header.html, menubar.html: style nits
+
+2009-07-12 16:45 ivan
+
+ * httemplate/elements/header.html: fix header sizes :/
+
+2009-07-12 16:38 ivan
+
+ * FS/FS/CGI.pm: this should fix $fsurl under the unified RT? sure
+ hope so
+
+2009-07-12 16:25 ivan
+
+ * httemplate/edit/cust_main.cgi: ACL on customer edit
+
+2009-07-12 16:22 ivan
+
+ * httemplate/elements/header.html, httemplate/elements/menu.html,
+ httemplate/elements/xmenu.css, httemplate/elements/xmenu.top.css,
+ rt/FREESIDE_MODIFIED, rt/etc/RT_SiteConfig.pm: resolve style
+ weirdness (fallout from RT integration), especially
+ non-fixed-width comment boxes, menu/searchbar differences,
+ RT#1169
+
+2009-07-12 12:32 ivan
+
+ * httemplate/elements/menu.html: update tickting config
+ descriptions
+
+2009-07-12 06:27 jeff
+
+ * FS/FS/Record.pm: stop gratuitous hash manipulatoin during enum
+ untaint
+
+2009-07-10 19:40 ivan
+
+ * bin/drop_slony.slonik: notes
+
+2009-07-10 10:50 ivan
+
+ * httemplate/elements/: select-did.html, tr-select-did.html: fix
+ svc_phone provisioning!
+
+2009-07-09 17:36 ivan
+
+ * FS/FS/cdr.pm: fix sansay CDR import to ignore "NA" in dates,
+ RT#5495
+
+2009-07-09 16:59 ivan
+
+ * FS/FS/cdr.pm, FS/FS/cdr/sansay.pm, eg/cdr_template.pm: sansay
+ CDRs, RT#5495
+
+2009-07-09 16:58 ivan
+
+ * FS/bin/freeside-cdr-sftp_and_import: add -r option
+
+2009-07-09 14:05 ivan
+
+ * FS/FS/cust_main.pm, httemplate/elements/menu.html,
+ httemplate/search/report_unapplied_cust_pay.html,
+ httemplate/search/unapplied_cust_pay.html: unapplied payments
+ report, RT#4861
+
+2009-07-09 13:36 ivan
+
+ * httemplate/search/elements/cust_main_dayranges.html: no idea how
+ i missed fixing this before
+
+2009-07-09 13:18 ivan
+
+ * httemplate/search/report_receivables.cgi: oops
+
+2009-07-08 04:12 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm, httemplate/elements/menu.html,
+ FS/FS/Conf.pm, FS/FS/Mason.pm, htetc/handler.pl,
+ httemplate/elements/about_freeside.html,
+ httemplate/elements/about_rt.html,
+ httemplate/elements/header.html,
+ httemplate/elements/popup_link.html, rt/FREESIDE_MODIFIED: borg
+ RT menus, RT#1169
+
+2009-07-07 20:33 ivan
+
+ * httemplate/search/: report_receivables.cgi,
+ report_unapplied_cust_pay.html, unapplied_cust_pay.html,
+ elements/cust_main_dayranges.html: factor out the range-handling
+ portions of receivables report, start on a similar unapplied
+ payment report, RT#4861
+
+2009-07-07 02:53 ivan
+
+ * FS/FS/Schema.pm, httemplate/view/svc_broadband.cgi: allow null
+ svc_broadband.ip_addr
+
+2009-07-07 02:23 ivan
+
+ * FS/FS/: Conf.pm, Schema.pm, svc_broadband.pm: allow null
+ svc_broadband.ip_addr
+
+2009-07-07 02:22 ivan
+
+ * httemplate/edit/svc_broadband.cgi: UI nit - double #
+
+2009-07-07 00:32 mark
+
+ * FS/bin/freeside-void-payments: Add freeside-void-payments script
+ for returned check processing
+
+2009-07-06 17:53 ivan
+
+ * FS/FS/cdr/netcentrex.pm: for netcentrex CDRs, import duration to
+ duration field (previously only billsec)
+
+2009-07-06 17:47 ivan
+
+ * FS/FS/cdr.pm: on CDR date parse, consider 1970-01-01 NULL like
+ 1900-01-01, RT#4081
+
+2009-07-06 16:14 ivan
+
+ * httemplate/search/: cdr.html, report_cdr.html: search cdr by
+ acctid
+
+2009-07-06 14:34 ivan
+
+ * httemplate/browse/rate_region.html: align, display countrycode
+ with +, space between prefixes allowing wrapping
+
+2009-07-05 17:28 ivan
+
+ * httemplate/view/svc_phone.cgi: fix incoming CDR links wrt
+ cdrbatch field
+
+2009-07-05 17:10 ivan
+
+ * httemplate/elements/: select-did.html, tr-select-did.html: doh
+
+2009-07-05 16:56 ivan
+
+ * httemplate/elements/: select-did.html, tr-select-did.html: allow
+ svc_phone.phonenum to be edited when a DID selector is not in use
+
+2009-07-05 16:17 ivan
+
+ * httemplate/search/cdr.html: format start/answer/end dates and
+ link svcnum to service
+
+2009-07-05 14:35 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cdr.pm,
+ httemplate/elements/checkboxes.html, httemplate/search/cdr.html,
+ httemplate/search/report_cdr.html: CDR search by dcontext,
+ charged_party, toggle of display fields, RT#4081
+
+2009-07-03 17:47 ivan
+
+ * FS/FS/part_event/Condition/: cust_payments.pm,
+ cust_payments_pkg.pm: add condition based on total customer
+ payments as a multiplier of a specific package, RT#3983
+
+2009-07-03 17:08 ivan
+
+ * FS/FS/part_event/Condition/once.pm: spealing
+
+2009-07-02 04:22 ivan
+
+ * FS/FS/cdr.pm, FS/FS/Schema.pm, FS/FS/cdr_termination.pm,
+ FS/FS/part_pkg/cdr_termination.pm,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html:
+ settlement cdr processing, RT#5495
+
+2009-07-01 19:02 ivan
+
+ * FS/FS/: cust_main.pm, part_event/Action/cust_bill_fee_percent.pm,
+ part_event/Action/fee.pm: fix late fees, RT#5665
+
+2009-07-01 18:26 jeff
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/part_pkg/flat.pm,
+ FS/FS/part_pkg/flat_introrate.pm, httemplate/search/cust_pkg.cgi:
+ update flat_introrate plan to better fit current codebase RT#4912
+
+2009-07-01 03:28 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/cdr_termination.pm,
+ FS/FS/cust_main.pm, FS/FS/part_pkg/cdr_termination.pm,
+ FS/FS/part_pkg/recur_Common.pm, FS/t/cdr_termination.t,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/view/cust_main/billing.html: start of settlement CDR
+ processing, RT#5495
+
+2009-06-30 22:34 ivan
+
+ * FS/FS/part_event/Action/cust_bill_fee_percent.pm,
+ FS/FS/part_event/Action/fee.pm,
+ httemplate/elements/select-taxclass.html,
+ httemplate/elements/tr-select-taxclass.html: add tax class
+ selection back for late charges w/1.9 events, RT#5665
+
+2009-06-30 13:18 jeff
+
+ * FS/FS.pm, httemplate/elements/menu.html: documentation
+ corrections
+
+2009-06-30 12:38 ivan
+
+ * FS/FS/Schema.pm, FS/FS/part_pkg_taxclass.pm,
+ httemplate/browse/part_pkg_taxclass.html,
+ httemplate/edit/part_pkg_taxclass.html,
+ httemplate/edit/process/part_pkg_taxclass.html,
+ httemplate/elements/menu.html,
+ httemplate/elements/select-taxclass.html,
+ httemplate/elements/tr-select-taxclass.html: disabling a
+ taxclass, RT#5472
+
+2009-06-30 05:32 ivan
+
+ * httemplate/edit/quick-charge.html: remove debugging
+
+2009-06-30 05:28 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/quick-charge.html,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/view/cust_main/packages/status.html,
+ FS/FS/Cron/bill.pm, httemplate/edit/process/quick-charge.cgi:
+ one-time charge "hold for later" / any package future start date,
+ RT#5347
+
+2009-06-30 04:09 ivan
+
+ * FS/FS/part_export/netsapiens.pm: this will help
+
+2009-06-30 02:38 ivan
+
+ * FS/FS/: phone_device.pm, part_export/netsapiens.pm: more steps to
+ netsapiens export, RT#5226
+
+2009-06-29 19:54 ivan
+
+ * FS/FS/part_export/netsapiens.pm: add DID association w/user?
+ docs from netsapiens rough... RT#5226
+
+2009-06-29 18:42 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Mason.pm, FS/FS/Schema.pm,
+ FS/FS/part_device.pm, FS/FS/phone_device.pm, FS/FS/svc_phone.pm,
+ FS/t/part_device.t, FS/t/phone_device.t,
+ httemplate/browse/part_device.html,
+ httemplate/edit/part_device.html,
+ httemplate/edit/phone_device.html,
+ httemplate/edit/process/part_device.html,
+ httemplate/edit/process/phone_device.html,
+ httemplate/misc/delete-phone_device.html,
+ httemplate/misc/part_device-import.html,
+ httemplate/elements/menu.html,
+ httemplate/misc/process/part_device-import.html,
+ httemplate/view/svc_phone.cgi: phone devices (for netsapiens
+ integration), RT#5226
+
+2009-06-29 15:48 rsiddall
+
+ * rpm/freeside.spec: Fix PDF invoice generation, including
+ requirement for ghostscript.
+
+2009-06-29 07:45 jeff
+
+ * bin/cust_pay_histogram: show total number of payments
+
+2009-06-29 06:55 jeff
+
+ * bin/cust_pay_histogram: commandline tool for examining cust_pay
+ records by date range #5652
+
+2009-06-29 06:53 jeff
+
+ * FS/FS/part_pkg_report_option.pm, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ FS/t/part_pkg_report_option.t, FS/FS.pm, FS/MANIFEST,
+ httemplate/browse/part_pkg_report_option.html,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_pkg_report_option.html,
+ httemplate/edit/cust_main/bottomfixup.html,
+ httemplate/edit/cust_main/bottomfixup.js,
+ httemplate/edit/cust_main/choose_tax_location.html,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/part_pkg_report_option.html,
+ httemplate/misc/xmlhttp-cust_main-censustract.html,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/elements/location.html, httemplate/elements/menu.html,
+ httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html,
+ httemplate/search/report_cust_pkg.html: FCC form 477 reporting
+ #4912
+
+2009-06-28 23:21 ivan
+
+ * FS/MANIFEST: remove freeside-expiration-alerter
+
+2009-06-26 17:55 ivan
+
+ * FS/bin/freeside-expiration-alerter: replaced by
+ FS::Cron::alert_expiration
+
+2009-06-26 16:21 ivan
+
+ * FS/bin/freeside-queued: doh, brainfart, RT#5572
+
+2009-06-26 16:12 ivan
+
+ * FS/bin/freeside-queued: add -s and -n flags to freeside-daily to
+ specify the kinds of jobs to be run, RT#5572
+
+2009-06-26 11:53 ivan
+
+ * FS/FS/cust_main.pm: in smart_search, move duplicate elimination
+ bits so that they're used even when doing an exact search on a
+ browser-remembered result
+
+2009-06-25 23:55 mark
+
+ * FS/: FS/Conf.pm, bin/freeside-daily: Add expiration alerts to
+ freeside-daily routine
+
+2009-06-25 13:23 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: finish fixing "Real time processing
+ not enabled!" error when using signup without any real-time
+ processor
+
+2009-06-25 12:55 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: eliminate harmless warning log spam:
+ Argument "" isn't numeric in numeric eq (==) at
+ /usr/local/share/perl/5.8.8/FS/ClientAPI/Signup.pm line 57
+
+2009-06-25 12:47 ivan
+
+ * FS/FS/: agent.pm, ClientAPI/Signup.pm: fix signups for the
+ no-gateway-at-all case, RT#5673
+
+2009-06-25 12:06 ivan
+
+ * httemplate/search/cust_tax_adjustment.html: fix tax adjustment
+ report
+
+2009-06-24 18:28 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/AccessRight.pm, FS/FS/Conf.pm,
+ FS/FS/Schema.pm, FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_tax_adjustment.pm,
+ FS/t/cust_tax_adjustment.t,
+ httemplate/edit/cust_tax_adjustment.html,
+ httemplate/edit/process/cust_tax_adjustment.html,
+ httemplate/search/cust_tax_adjustment.html,
+ httemplate/view/cust_main/payment_history.html: tax adjustments,
+ RT#5595
+
+2009-06-24 18:22 ivan
+
+ * httemplate/view/cust_main/one_time_charge_link.html: fix extra
+ whitespace in IE
+
+2009-06-24 11:36 ivan
+
+ * init.d/freeside-init, FS/bin/freeside-queued: add support for db
+ profiling, RT#5662
+
+2009-06-24 02:07 mark
+
+ * FS/: FS/Cron/alert_expiration.pm, bin/freeside-daily: Move
+ expiration alerts into FS::Cron::alert_expiration
+
+2009-06-23 21:42 ivan
+
+ * FS/FS/part_pkg.pm: fix upgrade issue w/ black part_pkg.comment,
+ RT#3988
+
+2009-06-23 18:40 ivan
+
+ * httemplate/graph/cust_bill_pkg.cgi: fix total links on
+ agent-specific sales report, RT#5449
+
+2009-06-23 13:33 rsiddall
+
+ * httemplate/view/svc_domain.cgi: Remove ability to renew domain
+ registration for more than one year as we can't automatically
+ bill for multi-year renewals at this time.
+
+2009-06-22 16:42 ivan
+
+ * FS/FS/cust_pay.pm: fix using encryption produces non-decrypted
+ data in payment receipts, RT#5536
+
+2009-06-22 15:55 ivan
+
+ * FS/FS/Mason.pm, httemplate/search/cust_main.html: fix advanced
+ customer report failure, RT#5515
+
+2009-06-22 10:00 jeff
+
+ * FS/FS/cust_main.pm: wtf? the tax applies but it doesn't? RT#5574
+
+2009-06-22 03:45 ivan
+
+ * FS/FS/part_pkg.pm: eliminate harmless upgrade error: Argument ""
+ isn't numeric in numeric eq (==) at
+ /usr/local/share/perl/5.8.8/FS/part_pkg.pm line 371.
+
+2009-06-22 03:39 ivan
+
+ * FS/FS/access_user.pm: fix pod
+
+2009-06-22 03:28 ivan
+
+ * FS/FS/part_pkg.pm: fix setup/recur -> setup_fee/recur_fee upgrade
+
+2009-06-22 03:06 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_tax.cgi: finish basic implemention of
+ tax exemption by tax name hack, RT#5127
+
+2009-06-22 00:50 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_exemption.pm,
+ FS/t/cust_main_exemption.t,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/view/cust_main/billing.html: tax exemption by tax
+ name, RT#5127
+
+2009-06-21 15:00 ivan
+
+ * FS/FS/part_pkg/agent.pm: eliminate leaky debugging and a warning
+ about it
+
+2009-06-21 08:42 jeff
+
+ * FS/FS/part_export/: www_plesk.pm, acct_plesk.pm: link to docs
+ #5855
+
+2009-06-21 08:21 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_pkg.pm, FS/FS/part_pkg.pm,
+ httemplate/browse/agent_type.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/agent_type.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/reg_code.cgi,
+ httemplate/edit/cust_main/first_pkg/select-part_pkg.html,
+ httemplate/misc/bulk_change_pkg.cgi,
+ httemplate/misc/cancel_pkg.html,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/delay_susp_pkg.html,
+ httemplate/misc/meta-import.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/reg_code.html,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/view/cust_main/packages/package.html,
+ httemplate/view/cust_main/packages/status.html: CUSTOM
+ packages/actual flag for custom packages #3988
+
+2009-06-19 14:49 jeff
+
+ * FS/FS/: cust_bill_pkg_detail.pm, tax_rate.pm: support some older
+ Pg when upgrading tax rates and cust_bill_pkg_details
+
+2009-06-19 10:53 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/verify.cgi: treat webpay type
+ payments as manual
+
+2009-06-19 05:08 ivan
+
+ * FS/FS/part_pkg/agent.pm: fix the prorating for the package's
+ first month (whew!) add an option not to prorate the accounts
+ themselves
+
+2009-06-19 00:46 ivan
+
+ * FS/FS/cust_main.pm: AND helps alot, RT#5572 for real
+
+2009-06-19 00:14 ivan
+
+ * FS/FS/cust_main.pm: attempt to optimize the easy parts of billing
+ run, RT#18737
+
+2009-06-18 04:09 ivan
+
+ * FS/FS/part_pkg/agent.pm: omit listing spurious customer details
+
+2009-06-18 04:07 ivan
+
+ * FS/FS/part_pkg/bulk.pm: bulk price plan fix - don't omit setup
+ fee
+
+2009-06-18 04:04 ivan
+
+ * FS/FS/part_pkg/agent.pm: add agent wholsale price plan, RT#4696
+
+2009-06-18 04:03 ivan
+
+ * FS/FS/Schema.pm, FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi:
+ add basic part_pkg cost columns for agent wholsale price plan,
+ RT#4696
+
+2009-06-18 03:07 ivan
+
+ * FS/FS/cust_main.pm: uuh, don't bomb out if there *isn't* a postal
+ fee package
+
+2009-06-18 02:52 ivan
+
+ * httemplate/images/gray-black-side.png: forgot this
+
+2009-06-17 18:17 mark
+
+ * httemplate/: elements/menu.html, search/report_receivables.cgi,
+ search/report_receivables.html: Fix receivables report ACL checks
+ and menu
+
+2009-06-16 23:43 mark
+
+ * FS/FS/AccessRight.pm, httemplate/elements/menu.html,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/report_receivables.html: Added separate access
+ right for receivables report
+
+2009-06-16 19:39 ivan
+
+ * FS/FS/Conf.pm, FS/FS/access_user.pm,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/elements/menubar.html,
+ httemplate/misc/process/link.cgi, httemplate/pref/pref.html,
+ httemplate/search/cust_event.html,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/view/cust_main.cgi: finish up initial work on customer
+ view tabs (ensure links back to customer view call include
+ show=packages if default view isn't jumbo or packages already),
+ RT#5586
+
+2009-06-15 20:29 ivan
+
+ * httemplate/: elements/menubar.html, pref/pref-process.html,
+ pref/pref.html, view/cust_main.cgi, view/cust_main/packages.html,
+ view/cust_main/payment_history.html, view/cust_main/tickets.html:
+ basic customer view tabs, RT#5586
+
+2009-06-15 14:41 rsiddall
+
+ * FS/FS/AccessRight.pm, httemplate/view/svc_domain.cgi: Add a new
+ access right for managing domain registration (registering,
+ transferring, revoking, renewing, etc.).
+
+2009-06-15 13:43 jeff
+
+ * FS/FS/cust_main.pm: check for need for postal fee before charging
+ the customer, not after
+
+2009-06-15 12:41 rsiddall
+
+ * FS/FS/part_export/domreg_opensrs.pm,
+ httemplate/edit/process/domreg.cgi,
+ httemplate/view/svc_domain.cgi: Add domain registration
+ operations to the View Domain screen, if the domain has an
+ associated export supporting registration. Shows the domain
+ status and allows registration, transfer, revocation, or renewal.
+ Revocation almost never works since the registries impose very
+ short windows after initial registration. Also updated the
+ OpenSRS registration export to support the additional operations.
+
+2009-06-12 20:13 rsiddall
+
+ * rpm/freeside.spec: Default configuration files had
+ directory-style permission values.
+
+2009-06-11 19:48 rsiddall
+
+ * rpm/freeside.spec: Freeside no longer uses a datasource-specific
+ configuration folder. Configuration is kept in the RDBMS and
+ initialized from the default_conf folder. RT 5579.
+
+2009-06-10 16:30 jeff
+
+ * conf/invoice_latex: prevent notes from bleeding onto coupon
+ RT#5537
+
+2009-06-10 14:58 ivan
+
+ * FS/FS/: cust_bill.pm, cust_bill_pkg.pm: should hopefully fix old
+ services showing up on invoices, RT#5451/RT#5514/RT#5564/RT#3032
+
+2009-06-10 12:50 ivan
+
+ * FS/FS/Cron/bill.pm: don't add another queued_bill job to the
+ queue if there's already one waiting to run for a customer,
+ RT#5572
+
+2009-06-10 00:58 ivan
+
+ * httemplate/search/cust_pkg.cgi: this should add the info s1 was
+ looking for, RT#5539
+
+2009-06-09 20:06 ivan
+
+ * FS/bin/freeside-upgrade: doh, semicolon
+
+2009-06-09 20:00 ivan
+
+ * FS/bin/freeside-upgrade: don't attempt to create h_queue indices,
+ for SG upgradability
+
+2009-06-09 19:08 ivan
+
+ * httemplate/edit/payment_gateway.html: update gateway list in
+ order to add WesternACH, RT#5409
+
+2009-06-09 17:02 ivan
+
+ * FS/FS/cust_main.pm: eliminate harmless
+
+ Argument "" isn't numeric in numeric comparison (<=>) at
+ /usr/local/share/perl/5.8.8/FS/cust_main.pm line 6759.
+
+ warning, sort batched payments on a column that actually exists
+
+2009-06-04 07:27 ivan
+
+ * httemplate/docs/credits.html: fix scrolling
+
+2009-06-04 07:09 ivan
+
+ * rpm/freeside.spec: there are lots of contributors too!
+
+2009-06-04 07:08 ivan
+
+ * httemplate/docs/credits.html: $core->add("jeremyd");
+
+2009-06-03 20:49 ivan
+
+ * bin/freeside-migrate-events: much more efficient event migration:
+ let the database do the work, RT#5426
+
+2009-06-03 17:49 ivan
+
+ * bin/freeside-migrate-events: add a -m mode to improve performance
+ so upgrade can complete for large databases, RT#5426
+
+2009-06-03 14:09 rsiddall
+
+ * rpm/freeside.spec: Incorrect permissions on the default
+ configuration folder caused installation to fail with an
+ incomplete database initialization. Failure to remove the
+ ticket_system file from the default configuration folder caused
+ Freeside to try to use RT.
+
+2009-06-03 12:52 ivan
+
+ * FS/FS/UID.pm: add a hack to set default schema, cf.
+ http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration:PostgreSQL_Schema
+
+2009-05-31 22:43 jeff
+
+ * FS/FS/part_pkg_taxproduct.pm, FS/FS/tax_rate.pm,
+ httemplate/misc/tax-fetch_and_replace.cgi,
+ httemplate/misc/process/tax-fetch_and_replace.cgi: a 'start over'
+ function for the taxproduct based tax data loading
+
+2009-05-31 02:57 ivan
+
+ * FS/FS/cust_bill.pm: wtf
+
+2009-05-31 02:45 ivan
+
+ * FS/FS/cust_bill.pm: don't fallback to 'Payable upon receipt'
+ invoice terms anymore. or in other words, honor setting
+ 'invoice_default_terms' blank, like in 1.7. RT#5415
+
+2009-05-31 01:39 ivan
+
+ * httemplate/browse/part_pkg.cgi: add "hide one-time charges"
+ toggle, RT#5255
+
+2009-05-30 23:59 ivan
+
+ * httemplate/: browse/part_pkg.cgi, elements/select-table.html:
+ package definition browse/search, filter by package class,
+ RT#5255
+
+2009-05-30 22:15 ivan
+
+ * bin/cdr-netsapiens.import: start of netsapeins cdr import, will
+ finish up when can connect again, RT#5226
+
+2009-05-30 21:57 ivan
+
+ * FS/FS/part_export/netsapiens.pm: necessary for
+ bin/cdr-netsapeins.import
+
+2009-05-30 05:15 ivan
+
+ * FS/FS/Schema.pm: very long transnexus filenames, RT#5229
+
+2009-05-30 04:21 ivan
+
+ * FS/FS/cdr/transnexus.pm: clid is base-64 encoded, huh, RT#5229
+
+2009-05-30 04:14 ivan
+
+ * bin/cdr-transnexus.import: quick hacked-up copy of
+ freeside-cdr-sftp_and_import for transnexus directory structure,
+ RT#5229
+
+2009-05-30 03:45 ivan
+
+ * FS/FS/: cdr.pm, cdr/transnexus.pm: add transnexus format, RT#5229
+
+2009-05-29 20:40 ivan
+
+ * FS/FS/part_pkg/prepaid.pm: and enable overlimit_action, RT#4995
+
+2009-05-29 20:14 ivan
+
+ * FS/FS/: svc_acct.pm, part_pkg/flat.pm, part_pkg/prepaid.pm: add
+ ability for prepaid packages to have usage limits and cancel if
+ they're hit, RT#4995
+
+2009-05-29 19:31 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: require svc_acct-usage_threshold to
+ be set explicitly, don't default to 80%
+
+2009-05-29 16:36 ivan
+
+ * httemplate/search/cdr.html: slightly better labels and field
+ order for CDR report, RT#4081
+
+2009-05-29 16:17 ivan
+
+ * FS/FS/: Conf.pm, cdr.pm, cdr/netcentrex.pm: fixup pivot code
+ handling in netcentrex CDR handling, RT#4081
+
+2009-05-28 21:43 jeff
+
+ * FS/FS/Conf.pm: correct description to reflect previous changes
+
+2009-05-28 17:22 jeff
+
+ * httemplate/edit/: quick-charge.html, process/quick-charge.cgi:
+ don't require a leading 0 in the quick charge amount
+
+2009-05-27 15:32 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: and multiple taxlcasses. i
+ think that should actually do it for now on RT#5446
+
+2009-05-27 15:23 ivan
+
+ * httemplate/search/report_tax.cgi: and the "tax invoiced" link
+ too! woo, working, RT#5446
+
+2009-05-27 15:13 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi: maybe this willf inally
+ fix total line links on tax report when using report_group
+ kludge? (still possibly not in all corner cases), RT#5446
+
+2009-05-27 15:10 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi, report_tax.cgi: maybe this
+ willf inally fix total line links on tax report when using
+ report_group kludge? (still possibly not in all corner cases),
+ RT#5446
+
+2009-05-27 14:35 ivan
+
+ * httemplate/search/report_tax.cgi: fix total line links on tax
+ report when using report_group kludge? (possibly not in all
+ corner cases), RT#5446
+
+2009-05-27 14:27 ivan
+
+ * httemplate/search/report_tax.cgi: fix total line links on tax
+ report when using report_group kludge? (possibly not in all
+ corner cases), RT#5446
+
+2009-05-27 14:24 jeff
+
+ * httemplate/elements/menuarrow.gif: close ticket 1517
+
+2009-05-27 10:18 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi, report_tax.cgi: fix total
+ line links on tax report when using report_group kludge?
+ (possibly not in all corner cases), RT#5446
+
+2009-05-27 00:50 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_recon.pm, FS/MANIFEST, FS/FS/cust_pkg.pm,
+ FS/FS/svc_acct.pm, FS/FS/ClientAPI/Bulk.pm,
+ FS/FS/part_pkg/voip_cdr.pm, FS/bin/freeside-selfservice-server,
+ FS/t/cust_recon.t, fs_selfservice/FS-SelfService/MANIFEST,
+ fs_selfservice/FS-SelfService/Makefile.PL,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd,
+ fs_selfservice/FS-SelfService/freeside-selfservice-soap-server,
+ fs_selfservice/FS-SelfService/iZoomOnlineProvisionService.pm:
+ bulk provisioning via ftp and SOAP #5202
+
+2009-05-26 05:32 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/: overlibmws.js,
+ overlibmws_crossframe.js, overlibmws_draggable.js,
+ overlibmws_iframe.js, iframecontentmws.js: include overlib in
+ selfservice
+
+2009-05-25 19:48 ivan
+
+ * httemplate/search/report_tax.cgi: and also fix "tax invoiced"
+ portion of total line when using config option for some basic tax
+ grouping by name, RT#5446
+
+2009-05-25 19:03 ivan
+
+ * httemplate/search/report_tax.cgi: mostly fix total line when
+ using config option for some basic tax grouping by name, RT#5446
+
+2009-05-25 18:41 ivan
+
+ * httemplate/search/: report_tax.html, report_tax.cgi: add a config
+ option for some basic tax grouping by name, RT#5446
+
+2009-05-25 18:35 ivan
+
+ * FS/FS/Conf.pm, httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html: add a config option for some
+ basic tax grouping by name, RT#5446
+
+2009-05-25 15:42 ivan
+
+ * httemplate/graph/cust_bill_pkg.cgi: fix total links on sales
+ graph when a package class is specified, RT#5449
+
+2009-05-24 20:59 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: international
+ self-service payments, RT#1592
+
+2009-05-24 18:49 ivan
+
+ * FS/FS/ClientAPI/MasonComponent.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/card.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/misc/counties.cgi,
+ fs_selfservice/FS-SelfService/cgi/misc/states.cgi,
+ httemplate/elements/location.html,
+ httemplate/elements/select-county.html: international
+ self-service payments, RT#1592
+
+2009-05-21 20:22 ivan
+
+ * FS/bin/freeside-upgrade: commit before sqlradius upgrade so
+ sqlradius upgrade errors from permissions can be ignored for now
+
+2009-05-21 20:02 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/search/report_cust_pkg.html: add
+ ability to report on packages w/status "not yet billed" as well,
+ RT#5409
+
+2009-05-20 08:27 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/packages/services.html: add "manage
+ device" link & config, RT#5438
+
+2009-05-20 01:27 ivan
+
+ * FS/FS/part_export/netsapiens.pm: get subscriber deletion working
+ and remove devel cruft, RT#5226
+
+2009-05-19 18:06 ivan
+
+ * FS/FS/part_export/netsapiens.pm: first pass at netsapiens
+ integration, RT#5226
+
+2009-05-18 12:23 jeff
+
+ * FS/FS/tax_rate_location.pm: miss use
+
+2009-05-18 12:21 jeff
+
+ * FS/FS/Schema.pm: allow empty state
+
+2009-05-18 11:21 jeff
+
+ * FS/bin/freeside-upgrade: prevent death on meritless sqlradius
+ upgrade attempts
+
+2009-05-18 02:55 ivan
+
+ * FS/FS/cust_svc.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/UI/bytecount.pm,
+ fs_selfservice/FS-SelfService/cgi/header.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_cdr_details.html,
+ fs_selfservice/FS-SelfService/cgi/view_usage.html,
+ fs_selfservice/FS-SelfService/SelfService.pm: basic CDR viewing
+ from self-service, RT#4018
+
+2009-05-18 01:40 ivan
+
+ * FS/bin/freeside-cdr-sftp_and_import: it would help to use the
+ correct port, RT#4081
+
+2009-05-17 21:56 jeff
+
+ * FS/FS/: part_pkg_taxrate.pm, tax_rate.pm: handle dates before
+ 1970
+
+2009-05-16 13:07 rsiddall
+
+ * FS/FS/part_export/domreg_opensrs.pm: Added information on common
+ failure causes to the perldoc.
+
+2009-05-16 12:27 rsiddall
+
+ * FS/FS/part_export/domreg_opensrs.pm: Defer use of Net::OpenSRS so
+ that failure to install the module doesn't stop Apache from
+ starting. (This causes attempts to register domains to fail
+ instead.)
+
+2009-05-15 19:29 rsiddall
+
+ * FS/FS/part_export/domreg_opensrs.pm: Provide better diagnostics
+ when the cust_main owning this domain does not provide fields
+ required for use as a contact handle in the domain registration
+ record. Also temporarily disable the ability to have the export
+ do only registration or only transfers.
+
+2009-05-15 12:41 rsiddall
+
+ * FS/FS/svc_domain.pm, FS/FS/part_export/domreg_opensrs.pm,
+ httemplate/edit/part_export.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/process/part_export.cgi,
+ httemplate/edit/process/svc_domain.cgi: Simple domain
+ registration at Tucows OpenSRS using an export based on
+ Net::OpenSRS. When a domain is added and the export runs, it
+ will register the domain or initiate a transfer. You can also
+ choose no action. There's currently no provision for revoking
+ domains or renewing registrations. Depending on the settings at
+ OpenSRS, orders may look like they've succeeded in Freeside but
+ actually be queued pending input by the reseller at OpenSRS. The
+ part_export CGIs were modified to allow a multi-valued select to
+ be used to control which TLDs are enabled for registration.
+
+2009-05-14 09:26 jeff
+
+ * httemplate/edit/cust_main/bottomfixup.js: ask for help assignting
+ geocode more often
+
+2009-05-14 09:25 jeff
+
+ * httemplate/view/cust_main/billing.html: show geocode when
+ taxproducts enabled
+
+2009-05-13 15:27 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_bill_pkg_tax_rate_location.pm, FS/FS/cust_main.pm,
+ FS/FS/tax_rate_location.pm, FS/MANIFEST, FS/FS/tax_rate.pm,
+ FS/t/cust_bill_pkg_tax_rate_location.t, FS/t/tax_rate_location.t,
+ bin/tax_rate_location.import, httemplate/misc/tax-import.cgi,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_newtax.cgi: improved taxproduct tax
+ report RT#4783
+
+2009-05-12 17:51 ivan
+
+ * conf/invoice_latex: remove obsolete comments
+
+2009-05-09 17:45 ivan
+
+ * FS/FS/svc_phone.pm: label phone_name correctly
+
+2009-05-09 16:54 ivan
+
+ * FS/bin/freeside-cdr-sftp_and_import, bin/cdr.sftp_and_import:
+ move cdr-sftp_and_import script to FS/bin, add -p option, RT#4081
+
+2009-05-09 00:56 ivan
+
+ * FS/FS/: Conf.pm, cdr.pm: add
+ cdr-charged_party-truncate_{length,prefix} in order to trim
+ charged_party to a certain length, RT#4081
+
+2009-05-08 18:44 ivan
+
+ * httemplate/: edit/elements/svc_Common.html,
+ edit/elements/edit.html, view/elements/svc_Common.html: use
+ service-def specific labels, at least for service pages that use
+ {view,edit}/elements/svc_Common.html RT#4081
+
+2009-05-08 17:39 ivan
+
+ * FS/FS/Schema.pm, FS/FS/part_svc.pm, FS/FS/part_svc_column.pm,
+ FS/FS/svc_acct.pm, httemplate/browse/part_svc.cgi,
+ httemplate/edit/part_svc.cgi: add ability to configure service
+ labels per-package (still need to actually use the labels),
+ RT#4081
+
+2009-05-08 01:41 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix cloning of custom packages
+ where you've changed '(CUSTOM)', RT#5350
+
+2009-05-05 15:02 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/agent.cgi: hide
+ agent.invoice_template edit by default, with a config to turn
+ back on, RT#5218
+
+2009-05-05 10:58 ivan
+
+ * FS/FS/cust_pay.pm: add company_name to payment receipt
+
+2009-05-05 03:40 ivan
+
+ * FS/FS/rate_detail.pm, httemplate/elements/file-upload.html,
+ httemplate/misc/rate_edit_excel.html,
+ httemplate/misc/process/rate_edit_excel.html: finish the import
+ portion of excel rate edit, RT#5108
+
+2009-05-04 18:41 jeff
+
+ * bin/cust_main_special.pm: ignore fee based taxes and eliminate
+ unused code
+
+2009-05-04 11:33 jeff
+
+ * bin/: cust_main_special.pm, rebill: this is a quick hack to
+ rebill customers when a cdr didn't happen
+
+2009-05-03 19:01 ivan
+
+ * httemplate/search/: report_prepaid_income.cgi,
+ report_prepaid_income.html: agent-virt prepaid income report,
+ RT#5311
+
+2009-05-03 18:17 ivan
+
+ * FS/FS/svc_acct.pm: eliminate Argument "" isn't numeric in
+ addition (+) warning
+
+2009-05-03 17:22 ivan
+
+ * httemplate/: browse/rate_region.html, elements/menu.html,
+ misc/rate_edit_excel.html, search/elements/search-xls.html,
+ search/elements/search.html: add menu item and page for d/ling
+ and edit rates with excel. RT#5108
+
+2009-05-03 15:45 ivan
+
+ * httemplate/search/elements/: search-csv.html, search-html.html,
+ search-xls.html, search.html: break down search.html into
+ components, RT#5108
+
+2009-05-03 11:34 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/search/cust_pay_batch.cgi: add
+ "Redownload resolved batches" ACL for s1, RT#4271
+
+2009-05-02 20:13 ivan
+
+ * httemplate/config/config.cgi: edit any config item
+
+2009-05-02 18:45 ivan
+
+ * httemplate/config/config-view.cgi: add ability to delete
+ invoice_latexreturnaddress and invoice_htmlreturnaddress too,
+ RT#5218
+
+2009-05-02 18:11 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-delete.cgi,
+ httemplate/config/config-view.cgi: add ability to remove
+ suffix-ed config items, RT#5218
+
+2009-05-02 17:40 ivan
+
+ * bin/confdiff: confdiff
+
+2009-05-02 15:11 ivan
+
+ * FS/FS/Misc/eps2png.pm: fix eps preview
+
+2009-05-01 17:01 ivan
+
+ * httemplate/: browse/agent.cgi, config/config-delete.cgi,
+ config/config-process.cgi, config/config-view.cgi: finish up
+ useful agent stuff on the config editor: adding, deleting
+ overrides too. also add a confirmation to override deletion from
+ the agent browse page. RT#5218
+
+2009-05-01 13:21 jeff
+
+ * FS/FS/cust_main.pm: calculate tax on tax per line and not on
+ aggregate
+
+2009-04-30 19:43 ivan
+
+ * httemplate/config/config-view.cgi: view all overrides when asked,
+ even the ones without values. hmm :/ RT#5218
+
+2009-04-30 19:08 ivan
+
+ * FS/FS/cust_event.pm: fix re-print/email from event page, yow.
+ RT#5293
+
+2009-04-30 18:07 ivan
+
+ * httemplate/config/: config-process.cgi, config-view.cgi: add
+ ability to edit the agent overrides from the main config, RT#4218
+
+2009-04-29 11:25 ivan
+
+ * httemplate/edit/svc_www.cgi: fix inadvertant select box, RT#5277
+
+2009-04-28 15:38 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/bill.html: UI
+
+2009-04-27 21:36 ivan
+
+ * FS/FS/cust_main.pm: add cancelled_cust-noevents flag to emulate
+ SG billing-daily -r behavior, RT#4412
+
+2009-04-27 21:13 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: add cancelled_cust-noevents flag
+ to emulate SG billing-daily -r behavior
+
+2009-04-27 16:06 ivan
+
+ * FS/FS/Mason.pm: typo
+
+2009-04-27 16:01 ivan
+
+ * FS/FS/Mason.pm: refuse to run w/CGI.pm 3.38. all lenny upgrades
+ will need libcgi-pm-perl removed, suck
+
+2009-04-26 16:43 ivan
+
+ * FS/FS/Schema.pm: would help to have an index on priority if we're
+ going to order based on it
+
+2009-04-26 16:19 ivan
+
+ * FS/bin/freeside-queued: start small jobs more efficiently,
+ RT#4412
+
+2009-04-26 16:09 ivan
+
+ * FS/FS/Conf.pm: add a config option for max # of queued kids,
+ RT#4412
+
+2009-04-25 15:42 ivan
+
+ * FS/: FS/Cron/bill.pm, bin/freeside-daily: add dry run to
+ multi-process mode for testing, RT#4412
+
+2009-04-23 13:34 jeff
+
+ * FS/FS/: Upgrade.pm, part_pkg_option.pm, part_pkg/voip_cdr.pm: add
+ subscription option to voip_cdr
+
+2009-04-23 13:31 jeff
+
+ * httemplate/misc/tax-fetch_and_import.cgi,
+ httemplate/misc/process/tax-fetch_and_import.cgi, FS/FS/Conf.pm,
+ FS/FS/cust_tax_location.pm, FS/FS/part_pkg_taxrate.pm,
+ FS/FS/tax_class.pm, FS/FS/tax_rate.pm,
+ httemplate/elements/menu.html: autodownload and update of cch tax
+ data
+
+2009-04-22 21:57 ivan
+
+ * FS/FS/Cron/bill.pm: we don't actually need the results ordered,
+ and i'm sure it doesn't help the planner get us results sooner.
+ last chance to try and get cursor approach working? RT#4412
+
+2009-04-22 21:14 ivan
+
+ * FS/FS/Cron/bill.pm: does pg try to finish the query when the job
+ addition is committed? well, if this works, that answers that.
+ RT#4412
+
+2009-04-22 13:24 ivan
+
+ * FS/FS/Cron/bill.pm: hopefully better performance running the big
+ query once and then fetching results with a cursor, rather than
+ running it multiple times with an OFFSET and LIMIT, RT#4412
+
+2009-04-22 11:58 ivan
+
+ * httemplate/edit/process/cust_main.cgi: fix not allowing
+ "on-demand" card or ACH, RT#5238 RT#5237 RT#5230
+
+2009-04-21 23:52 ivan
+
+ * FS/FS/Cron/bill.pm: perhaps a happier medium, RT#4412
+
+2009-04-21 17:54 ivan
+
+ * FS/FS/Cron/bill.pm: have the big query find customers in batches.
+ this should be way more efficient in multi-process mode, can
+ start billing before the big query completes. RT#4412
+
+2009-04-21 13:59 ivan
+
+ * httemplate/browse/rate_region.html: rate download/edit/upload,
+ RT#5108
+
+2009-04-21 13:28 ivan
+
+ * httemplate/browse/rate_region.html: rate download/edit/upload,
+ RT#5108
+
+2009-04-21 09:42 ivan
+
+ * FS/FS/Cron/bill.pm: yow. fix fallout from cust_main.archived
+ stuff causing nothing to bill. RT#4412
+
+2009-04-20 19:23 jeff
+
+ * FS/FS/UI/Web.pm, httemplate/elements/progress-popup.html: more
+ descriptive progress popups
+
+2009-04-20 13:57 rsiddall
+
+ * rpm/freeside.spec: Acceptance testing of the RPM build system
+ flushed out a place where /bin/rm stops waiting for input if
+ you're /bin/su as the user doing the build. Added a -f flag to
+ stop it waiting for input.
+
+2009-04-20 09:57 ivan
+
+ * FS/FS/: Schema.pm, cust_main.pm, Cron/bill.pm: add
+ cust_main.archived field, skip billing if Y, RT#4412
+
+2009-04-19 16:55 ivan
+
+ * bin/h_cust_main-wipe_paycvv: no, it was only cause their db is
+ somehow corrupt
+
+2009-04-19 16:52 ivan
+
+ * bin/h_cust_main-wipe_paycvv: warning about this not terminating
+
+2009-04-17 16:30 ivan
+
+ * bin/: fs-migrate-cust_tax_exempt, h_cust_main-wipe_paycvv:
+ something to wipe the CVV from very large databases
+
+2009-04-17 12:50 ivan
+
+ * FS/: FS/Schema.pm, FS/Cron/bill.pm, bin/freeside-queued: add
+ priority to job queue so billing jobs don't don't drown out
+ provisioning jobs
+
+2009-04-17 12:21 ivan
+
+ * FS/FS/Cron/bill.pm: 1 helps alot
+
+2009-04-17 12:20 ivan
+
+ * FS/bin/freeside-daily: backport freeside-daily -m and
+ cust_main::bill_and_collect to 1.7, RT#4412
+
+2009-04-17 01:25 ivan
+
+ * httemplate/docs/about.html: AFFERO BITCHES
+
+2009-04-17 01:16 ivan
+
+ * httemplate/docs/about.html: what time is now
+
+2009-04-17 01:02 ivan
+
+ * httemplate/config/: config-process.cgi, config-view.cgi,
+ config.cgi: show labels for select(select_hash) config options,
+ RT#3997
+
+2009-04-17 00:21 ivan
+
+ * FS/FS/cust_bill.pm: add invoice number to PDF filename in email
+ attachments, RT#3403
+
+2009-04-17 00:03 ivan
+
+ * httemplate/elements/header.html: hide service search unless user
+ has "View customer services" ACL, RT#3478
+
+2009-04-16 18:17 ivan
+
+ * httemplate/search/: report_receivables.cgi,
+ report_receivables.html: add customer status to receivables
+ report selection, hopefully help enet, RT#5187
+
+2009-04-15 20:58 rsiddall
+
+ * rpm/freeside.spec: More fixes for SuSE self-service: 1/ Put
+ binaries in the right folder 2/ Make sure freeside group is
+ created 3/ Make sure freeside home directory is created
+
+2009-04-15 00:14 ivan
+
+ * httemplate/view/: svc_Common.html, elements/svc_Common.html:
+ should fix view of unlinked phone numbers, RT#5171
+
+2009-04-14 22:52 ivan
+
+ * httemplate/view/svc_acct.cgi: s/GECOS/Real Name/ RT#3519
+
+2009-04-14 19:44 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix 1.7->1.9 upgrade glitch with
+ self-service where process_payment required "payby" instead of
+ defaulting to CARD, RT#3905
+
+2009-04-14 19:29 ivan
+
+ * FS/FS/cust_bill.pm: show 60 chars on typeset invoices but only 32
+ on plaintext, RT#3905
+
+2009-04-14 19:15 ivan
+
+ * FS/FS/cust_bill.pm: this should fix credits pushing typeset
+ invoices off the right
+
+2009-04-14 14:01 ivan
+
+ * httemplate/view/cust_main/packages.html: remove debugging
+
+2009-04-14 13:27 ivan
+
+ * httemplate/view/cust_main/packages.html: don't hide old packages
+ that have services, RT#5179
+
+2009-04-14 10:15 ivan
+
+ * FS/FS/: cust_main.pm, agent.pm: add configuration option to
+ control recurring_flag behavior, RT#3843
+
+2009-04-14 10:12 ivan
+
+ * FS/FS/: Conf.pm, Schema.pm: add configuration option to control
+ recurring_flag behavior, RT#3843
+
+2009-04-14 09:14 jeff
+
+ * httemplate/misc/process/tax-import.cgi: hmmm
+
+2009-04-14 09:12 jeff
+
+ * httemplate/misc/process/tax-import.cgi: better at least
+
+2009-04-13 17:09 ivan
+
+ * FS/FS/Conf.pm: add configuration option to control recurring_flag
+ behavior, RT#3843
+
+2009-04-13 16:37 ivan
+
+ * FS/FS/cust_main.pm: debugging
+
+2009-04-11 23:24 ivan
+
+ * httemplate/elements/tr-select-svc_acct-domain.html: adding
+
+2009-04-11 23:14 ivan
+
+ * httemplate/elements/selectlayers.html: add svc_phone on new
+ customer first package, RT#4315
+
+2009-04-11 23:09 ivan
+
+ * FS/FS/part_pkg.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/birthdate.html,
+ httemplate/edit/cust_main/bottomfixup.html,
+ httemplate/edit/cust_main/bottomfixup.js,
+ httemplate/edit/cust_main/first_pkg.html,
+ httemplate/edit/cust_main/top_misc.html,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_main/choose_tax_location.html,
+ httemplate/edit/cust_main/select-domain.html,
+ httemplate/edit/cust_main/first_pkg/select-part_pkg.html,
+ httemplate/edit/cust_main/first_pkg/svc_acct.html,
+ httemplate/edit/cust_main/first_pkg/svc_phone.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/elements/select-domain.html,
+ httemplate/misc/part_svc-columns.cgi,
+ httemplate/elements/select-svc_acct-domain.html: add svc_phone on
+ new customer first package, RT#4315
+
+2009-04-11 18:33 jeff
+
+ * etc/fslongtable.sty: sheesh
+
+2009-04-11 18:24 jeff
+
+ * Makefile, etc/fslongtable.sty, conf/invoice_latex: find and
+ correct the real double counting culprit
+
+2009-04-11 14:42 ivan
+
+ * FS/FS/part_event/Action/writeoff.pm, bin/freeside-migrate-events:
+ migrate send_email, suspend_if_balance and credit events, RT#3905
+
+2009-04-11 14:29 ivan
+
+ * FS/FS/part_event/Action/cust_bill_email.pm: add cust_bill_email
+ action
+
+2009-04-11 13:51 ivan
+
+ * FS/bin/freeside-upgrade: don't run configuration update when -s
+ is used for schema-only slony slave update
+
+2009-04-10 12:33 ivan
+
+ * FS/FS/Conf.pm: better description for enable_taxproducts and a
+ warning about tax-pkg_address with it
+
+2009-04-09 20:43 jeff
+
+ * FS/FS/cust_main.pm: orders of magnitude faster
+
+2009-04-09 15:51 jeff
+
+ * FS/FS/cdr/taqua.pm: used BillingNumber and not CallingPartyNumber
+ for non-toll-free calls
+
+2009-04-08 15:42 ivan
+
+ * FS/FS/cust_pkg.pm: fix 'agent X can't purchase pkgpart YY' error
+ w/agent packages, RT#5119
+
+2009-04-08 01:08 ivan
+
+ * FS/FS/Cron/check.pm: don't want to throw false positives, RT#5101
+
+2009-04-08 00:32 ivan
+
+ * FS/FS/: Record.pm, Upgrade.pm, cust_main.pm: eliminate all trace
+ of cvv from history records, RT#5093
+
+2009-04-07 18:13 ivan
+
+ * httemplate/misc/link.cgi: when linking a legacy phone number the
+ phone number could be typed in and not the service #. RT#3407
+
+2009-04-07 13:45 jeff
+
+ * FS/FS/: cdr.pm, cdr/taqua.pm: quick option to allow importing gmt
+ cdrs
+
+2009-04-07 11:20 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add a config to allow colon in
+ usernames, RT#5145
+
+2009-04-07 11:15 ivan
+
+ * FS/FS/svc_acct.pm: truncate long labels that are TOO long...
+ RT#3519
+
+2009-04-06 19:50 jeff
+
+ * FS/FS/cdr.pm: a tollfree regex that captures 88x and works with
+ +1
+
+2009-04-06 19:18 ivan
+
+ * FS/FS/cdr.pm: stop smoking crack
+
+2009-04-06 18:20 jeff
+
+ * FS/FS/: Record.pm, cdr.pm, cdr/taqua.pm, part_pkg/voip_cdr.pm:
+ correct taqua toll free handling and hasten cdr import (skip
+ uninteresting records)
+
+2009-04-06 16:19 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: better auto toll free regex
+
+2009-04-06 11:31 jeff
+
+ * FS/FS/cdr/taqua.pm: calltype 6 is international
+
+2009-04-05 17:52 jeff
+
+ * Makefile, etc/fslongtable.sty: stop doublecounting
+ extracouponspace but do not gratuitiously change existing
+ installs
+
+2009-04-05 16:18 jeff
+
+ * FS/FS/cust_main.pm: cleanup tax-pkg_location tax on tax fallout
+
+2009-04-04 09:22 jeff
+
+ * FS/FS/: Schema.pm, cust_bill_pkg_detail.pm: correct bad schema
+ bug in cust_bill_pkg_detail
+
+2009-04-03 09:57 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: add option for available rather than
+ provisioned svc_phones as unit count
+
+2009-04-02 13:22 jeff
+
+ * httemplate/edit/cust_main/billing.html: Net 20 as well
+
+2009-04-02 10:47 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: separate checkbox for enabling
+ prorate feature
+
+2009-04-02 08:46 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: prorating for the fixed recurring
+ portion of voip
+
+2009-04-02 07:56 jeff
+
+ * httemplate/edit/cust_main.cgi: obey tax-ship_address in 'manual'
+ geocoding
+
+2009-04-01 22:27 jeff
+
+ * FS/FS/part_pkg_taxrate.pm: noise reduction
+
+2009-04-01 19:36 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: more than you ever wanted to know
+ about rounding. http://en.wikipedia.org/wiki/Rounding RT#4666
+
+2009-04-01 17:14 ivan
+
+ * httemplate/browse/part_pkg.cgi: add some
+ (undocumented/unaccessable to web UI yet) options to package
+ browse to track down packages missing recurring fees
+
+2009-03-31 21:27 ivan
+
+ * FS/FS/cdr.pm: show post-granularity duration if available for all
+ export formats
+
+2009-03-31 20:51 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add options to skip CDRs under a
+ defined length and with specific lastapp
+
+2009-03-31 20:44 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add options to skip CDRs under a
+ defined length and with specific lastapp
+
+2009-03-31 12:51 ivan
+
+ * FS/FS/Cron/notify.pm: really fix notify for Pg 8.3
+
+2009-03-31 12:47 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: quiet warning: Argument "" isn't
+ numeric in numeric eq (==) at
+ /usr/local/share/perl/5.10.0/FS/part_pkg/voip_cdr.pm line 201
+
+2009-03-31 12:46 ivan
+
+ * FS/FS/Cron/notify.pm: fix impending billing notification for Pg
+ 8.3's more strict type checking
+
+2009-03-30 09:33 jeff
+
+ * FS/FS/cust_tax_location.pm: schema and module should agree on
+ column names
+
+2009-03-29 23:10 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/view/cust_main/packages.html: okay. counts are needed
+ for the package sort, so push the embedded counting into
+ cust_main.pm. sure hope this does it. RT#5083
+
+2009-03-29 22:08 ivan
+
+ * FS/FS/cust_pkg.pm: and hopefully actually using the count will
+ finally do it, RT#5083
+
+2009-03-29 21:50 ivan
+
+ * FS/FS/cust_pkg.pm: and hopefully actually using the count will
+ finally do it, RT#5083
+
+2009-03-29 21:41 ivan
+
+ * httemplate/view/cust_main/packages.html: double doh! RT#5083
+
+2009-03-29 21:35 ivan
+
+ * httemplate/view/cust_main/packages.html: doh! underscore,
+ RT#5083
+
+2009-03-29 21:31 ivan
+
+ * httemplate/view/cust_main/packages.html: try not to search for
+ nothing in cust_svc so much, RT#5083
+
+2009-03-29 21:15 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/view/cust_main/packages.html: try
+ not to search for nothing in cust_svc so much, RT#5083
+
+2009-03-29 20:47 ivan
+
+ * FS/FS/cust_main.pm: didn't need this, but more future-proof,
+ RT#5083
+
+2009-03-29 20:12 ivan
+
+ * httemplate/view/cust_main/packages.html: really prevent separate
+ part_pkg query, RT#5083
+
+2009-03-29 18:39 ivan
+
+ * httemplate/view/cust_main/packages.html: doh, fix pkg display,
+ RT#5083
+
+2009-03-29 18:28 ivan
+
+ * httemplate/view/cust_main/packages.html: fix setup date display,
+ RT#5083
+
+2009-03-29 18:09 ivan
+
+ * httemplate/view/cust_main/packages.html: forget caching, instead
+ scoop up cust_pkg and part_pkg in one query, RT#5083
+
+2009-03-29 18:05 ivan
+
+ * FS/FS/cust_main.pm, httemplate/view/cust_main/packages.html:
+ forget caching, instead scoop up cust_pkg and part_pkg in one
+ query, RT#5083
+
+2009-03-29 17:32 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/view/cust_main/packages.html:
+ part_pkg caching should speedup display of lots of packages,
+ RT#5083
+
+2009-03-29 16:44 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main/packages.html: hide over
+ 2 (or configured) cancelled and one-time charge packages, RT#5083
+
+2009-03-29 04:56 ivan
+
+ * FS/FS/Schema.pm: index pkg_svc.quantity, RT#5083
+
+2009-03-29 04:52 ivan
+
+ * FS/FS/cust_pkg.pm: seems to benchmark faster, RT#5083
+
+2009-03-29 03:39 ivan
+
+ * httemplate/view/cust_main/packages/package.html: avoid looking up
+ part_pkg redundantly in the pkg loop, RT#5083
+
+2009-03-29 03:34 ivan
+
+ * httemplate/view/cust_main/packages/package.html: avoid looking up
+ package details redundantly in the pkg loop, RT#5083
+
+2009-03-29 03:17 ivan
+
+ * FS/FS/: Record.pm, cust_pkg.pm: add "extra_param" option to
+ qsearch for more realisitic profiling data, RT#5083
+
+2009-03-29 02:38 ivan
+
+ * httemplate/view/cust_main/: one_time_charge_link.html,
+ packages.html, packages/status.html: optimize customer view when
+ there's lots of packages; *really* avoid looking up any config
+ inside the package loop, RT#5083
+
+2009-03-28 15:59 ivan
+
+ * httemplate/elements/select-cust-part_pkg.html,
+ httemplate/elements/select-cust-pkg_class.html,
+ httemplate/elements/select-part_pkg.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-cust-part_pkg.html,
+ httemplate/elements/tr-selectmultiple-part_pkg.html,
+ FS/FS/Conf.pm, FS/FS/part_pkg.pm, httemplate/misc/change_pkg.cgi,
+ httemplate/misc/cust-part_pkg.cgi,
+ httemplate/misc/order_pkg.html: package selector, split by
+ package class, RT#5077
+
+2009-03-25 20:59 ivan
+
+ * FS/FS/svc_acct.pm: yow
+
+2009-03-25 20:53 ivan
+
+ * FS/FS/cust_main.pm: sort packages by label of first (primary)
+ service, RT#5041
+
+2009-03-25 02:36 ivan
+
+ * httemplate/pref/pref-process.html: throw a proper error message
+ instead of a mason error on pw chagne problems, RT#5073
+
+2009-03-25 02:36 ivan
+
+ * httemplate/pref/pref.html: we're defaulting to a top menu in 1.9
+
+2009-03-25 01:45 ivan
+
+ * FS/FS/cdr.pm: correct headers on accountcode_default CDR output,
+ RT#5042
+
+2009-03-24 02:42 ivan
+
+ * bin/countdeclines: quick tool for RT#3843
+
+2009-03-23 23:31 ivan
+
+ * FS/FS/Schema.pm: add indices for analyzing cc failures, RT#3843
+
+2009-03-23 19:36 ivan
+
+ * FS/FS/: part_pkg/bulk.pm, Record.pm, cust_bill.pm, cust_svc.pm,
+ h_cust_svc.pm, part_pkg.pm: bulk price plan: label as Name
+ <email>, supress extraneous service list, RT#3519
+
+2009-03-23 16:33 jeff
+
+ * FS/FS/cust_pkg.pm, FS/FS/svc_acct.pm, FS/FS/part_pkg/flat.pm,
+ httemplate/misc/process/recharge_svc.html: more DTRT with usage
+ on service transfer between packages and recharges RT #2884,
+ #5040 + #4995 fallout
+
+2009-03-23 15:45 ivan
+
+ * httemplate/edit/svc_acct.cgi: change label for svc_acct.finger
+ from GECOS to "Real Name", RT#3519
+
+2009-03-23 10:02 ivan
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm, part_pkg/bulk.pm: add name
+ (svc_acct.finger) to bulk billing detail, RT#3519
+
+2009-03-21 20:33 ivan
+
+ * FS/FS/Cron/check.pm: 10 is too few, throwing false positives
+
+2009-03-21 19:47 ivan
+
+ * httemplate/misc/xmlhttp-cust_main-address_standardize.html: fix
+ usps address standardization when the zip returned has no zip+4,
+ RT#4882
+
+2009-03-21 16:37 ivan
+
+ * FS/bin/freeside-check: Locale::SubCountry warnings clogging up
+ cron output not useful
+
+2009-03-21 16:32 ivan
+
+ * FS/FS/Cron/check.pm: 403 forbidden is okay, at lest the server's
+ up
+
+2009-03-21 15:14 ivan
+
+ * Makefile, FS/FS/Cron/check.pm, FS/bin/freeside-check:
+ freeside-check local monitoring, RT#4610
+
+2009-03-19 19:14 ivan
+
+ * bin/ping: adding quick remote ping & alert script, RT#4610
+
+2009-03-18 08:11 jeff
+
+ * FS/FS/part_pkg_taxrate.pm: more error information
+
+2009-03-17 17:30 jeff
+
+ * FS/FS/svc_acct.pm, FS/FS/part_pkg/flat.pm,
+ httemplate/edit/part_svc.cgi: hide unused usage columns
+
+2009-03-17 15:02 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: add
+ payment_info_renew_info method to ClientAPI/MyAccount and
+ SG-equivalent previous_payment_info_renew_info to ClientAPI/SGNG
+
+2009-03-17 14:38 ivan
+
+ * FS/FS/ClientAPI/SGNG.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add
+ payment_info_renew_info method to ClientAPI/MyAccount and
+ SG-equivalent previous_payment_info_renew_info to ClientAPI/SGNG
+
+2009-03-17 13:41 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: add SG stuff
+
+2009-03-17 13:13 ivan
+
+ * FS/FS/Conf.pm: add a conf switch to enable sg multicust stuff,
+ since it could be dangerous
+
+2009-03-17 13:04 ivan
+
+ * FS/FS/Conf.pm: apacheip isn't actually deprecated yet
+
+2009-03-17 12:48 ivan
+
+ * FS/FS/ClientAPI/SGNG.pm: adding ClientAPI/SGNG.pm
+
+2009-03-17 09:06 jeff
+
+ * FS/FS/: Upgrade.pm, tax_rate.pm: column upgrade for tax_rate
+ RT#4903)
+
+2009-03-17 05:01 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm: agent-virt
+ invoice_*{notes,footer,smallfooter,coupon}, RT#5025
+
+2009-03-17 02:59 ivan
+
+ * FS/FS/Misc/eps2png.pm: less debugging
+
+2009-03-17 02:58 ivan
+
+ * httemplate/docs/credits.html, FS/FS/Conf.pm, FS/FS/Mason.pm,
+ FS/FS/Misc/eps2png.pm, httemplate/config/config-image.cgi,
+ httemplate/config/config-view.cgi, httemplate/docs/license.html:
+ add eps preview to config, for RT#5025
+
+2009-03-16 16:28 jeff
+
+ * FS/FS/Schema.pm: avoid the need for approximate comparisons
+ RT#4903
+
+2009-03-16 10:06 jeff
+
+ * FS/FS/Schema.pm, FS/FS/part_export/prizm.pm,
+ httemplate/edit/svc_broadband.cgi: have prizm use service data
+ rather than package data to select a profile RT#4853
+
+2009-03-16 08:52 jeff
+
+ * bin/make-pkg-fruit: a tool for migrating package elements to
+ services
+
+2009-03-16 01:08 jeff
+
+ * FS/FS/svc_broadband.pm: get the dup checking right
+
+2009-03-16 00:13 ivan
+
+ * httemplate/: misc/payment.cgi, misc/process/payment.cgi,
+ elements/location.html: allow country selection on credit card
+ entry, RT#4997
+
+2009-03-16 00:13 ivan
+
+ * htetc/: freeside-base1.99.conf, freeside-base1.conf,
+ freeside-base2.conf: eliminate black diamond arrows on iso-8859-1
+ chars in Locale::SubCountry states, RT#4997
+
+2009-03-15 23:22 ivan
+
+ * Makefile: 5.10! welcome to the future
+
+2009-03-15 22:54 ivan
+
+ * httemplate/view/svc_domain.cgi: fix custnum display on domain
+ view
+
+2009-03-15 21:21 jeff
+
+ * FS/FS/svc_broadband.pm: use part_svc_router
+
+2009-03-15 17:57 jeff
+
+ * FS/FS/svc_broadband.pm: comment change
+
+2009-03-15 15:44 ivan
+
+ * bin/svc_acct-recalculate_usage: adding quick usage resetting tool
+
+2009-03-15 15:33 ivan
+
+ * FS/FS/cust_svc.pm: don't throw 'Use of uninitialized value in
+ addition (+) at /usr/local/share/perl/5.8.8/FS/cust_svc.pm line
+ 626.' error when using attribute_since_sqlradacct
+
+2009-03-15 12:42 ivan
+
+ * FS/FS/cust_pkg_reason.pm: should give better performance if we
+ search for what we want instead of using a string match
+
+2009-03-15 03:46 ivan
+
+ * FS/FS/cust_main.pm: fix application of data fields from prepaid
+ cards in addition to time field
+
+2009-03-15 03:34 ivan
+
+ * httemplate/misc/process/recharge_svc.html: apply byte values from
+ prepaid cards as well as time value, RT#4995
+
+2009-03-15 03:30 ivan
+
+ * FS/FS/UI/bytecount.pm, httemplate/edit/prepay_credit.cgi: we're
+ not a disk drive manufacturer, don't use halfass base-10
+ megs/gigs
+
+2009-03-15 00:33 ivan
+
+ * FS/FS/cust_main.pm: cust_main::payment_info, for
+ ClientAPI::MyAccount
+
+2009-03-14 16:44 ivan
+
+ * FS/FS/cust_bill.pm: fix emailed logos to come from db config, not
+ old files, RT#3093 / RT#4963
+
+2009-03-13 11:22 jeff
+
+ * FS/FS/svc_broadband.pm: prevent more duplicate MACs from sneaking
+ in in the interim
+
+2009-03-11 03:03 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm: add previous_balance-summary_only
+ config, RT#4404
+
+2009-03-11 02:41 ivan
+
+ * FS/FS/cdr.pm: add cdr display with accountcode included, RT#4405
+
+2009-03-11 01:57 ivan
+
+ * bin/print-directory_assist: comma
+
+2009-03-11 01:46 ivan
+
+ * bin/print-directory_assist, etc/areacodes.txt: quick list of area
+ codes and a kludge to print DA numbers for all of them
+
+2009-03-10 09:14 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html,
+ fs_selfservice/FS-SelfService/cgi/verify.cgi,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html, FS/FS/Conf.pm,
+ FS/FS/Schema.pm, FS/FS/agent.pm, FS/FS/cust_main.pm,
+ httemplate/elements/tr-textarea.html, FS/FS/cust_pay_pending.pm,
+ FS/FS/cust_pkg.pm, FS/FS/payby.pm, FS/FS/payment_gateway.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ httemplate/browse/payment_gateway.html,
+ httemplate/edit/payment_gateway.html,
+ httemplate/edit/process/payment_gateway.html: merge webpay
+ support in with autoselection of old realtime_bop and
+ realtime_refund_bop
+
+2009-03-08 17:15 ivan
+
+ * httemplate/misc/svc_acct-domains.cgi: mistake, its back
+
+2009-03-08 17:15 ivan
+
+ * httemplate/misc/svc_acct-domains.cgi: doh
+
+2009-03-03 17:47 ivan
+
+ * FS/FS/UID.pm: mpm-itk hack, commented-out for now
+
+2009-03-03 15:56 ivan
+
+ * FS/FS/UID.pm: show the euid/ruid when throwing the "Not running
+ uid freeside" error
+
+2009-03-03 15:41 ivan
+
+ * FS/FS/queue.pm: eliminate harmless "Odd number of elements in
+ hash assignment" warning
+
+2009-03-02 00:49 ivan
+
+ * FS/FS/: part_export/vitelity.pm, Schema.pm, phone_avail.pm:
+ preliminary vitelity export, RT#4868
+
+2009-03-01 20:58 ivan
+
+ * FS/: bin/freeside-cdrrewrited, FS/Conf.pm, FS/cdr.pm: option to
+ do charged_party rewriting in the cdrrewrited daemon, RT#4342
+
+2009-03-01 16:10 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.html: have perl
+ signup.html use selfserice skin config too: selfservice-head,
+ selfserfice-body_header, selfservice-body_footer,
+ selfservice-body_bgcolor, selfservice-box_bgcolor
+
+2009-02-28 10:27 ivan
+
+ * Makefile: avoid erroring out running install-perl-modules when
+ you have a pristine, un-updated CVS checkout
+
+2009-02-25 19:51 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.html: make signup for a
+ bit friendlier for BILL signups for testingm RT#4018
+
+2009-02-25 12:05 ivan
+
+ * bin/japan.pl: adding quick tool to change the "states" for japan
+
+2009-02-24 02:15 ivan
+
+ * FS/FS/Setup.pm: be quiet
+
+2009-02-24 02:09 ivan
+
+ * conf/report_template: more bootstrapping bs
+
+2009-02-24 02:06 ivan
+
+ * FS/FS/part_referral.pm: bootstrapping issues
+
+2009-02-24 02:00 ivan
+
+ * FS/FS/Setup.pm: bootstrapping issues, aaargh
+
+2009-02-24 01:58 ivan
+
+ * FS/FS/Setup.pm: bootstrapping issues, ugh
+
+2009-02-24 01:50 ivan
+
+ * FS/FS/part_pkg.pm, FS/bin/freeside-setup,
+ bin/freeside-create-initial-data: bootstrapping issues
+
+2009-02-24 01:41 ivan
+
+ * bin/freeside-create-initial-data: somehow rc install wound up
+ without any data
+
+2009-02-24 00:45 ivan
+
+ * FS/bin/freeside-cdrrewrited: exact match, RT#3196
+
+2009-02-24 00:41 ivan
+
+ * FS/bin/freeside-cdrrewrited, FS/FS/Schema.pm, FS/FS/cdr.pm,
+ init.d/freeside-init: rewrite CDRs for forwarded Asterisk calls
+ to be billable, RT#3196
+
+2009-02-23 15:52 ivan
+
+ * FS/FS/Conf.pm: fix 1.7 -> 1.9 config upgrade for new "image"
+ config type
+
+2009-02-22 13:11 ivan
+
+ * httemplate/view/svc_www.cgi: remove flailing ", RT#4902
+
+2009-02-22 13:08 ivan
+
+ * FS/: FS/Upgrade.pm, bin/freeside-upgrade: a better rough idea of
+ where freeside-upgrade spends time
+
+2009-02-22 12:42 ivan
+
+ * httemplate/edit/svc_www.cgi: fix apache config editing, doh,
+ RT#4902
+
+2009-02-22 12:12 ivan
+
+ * bin/apache.export: add exportnum to apache export files so they
+ all get preserved in the case where you're using multiple apache
+ exports to the same machine, RT#4901
+
+2009-02-22 11:46 ivan
+
+ * FS/: MANIFEST, FS/h_cust_pkg.pm, FS/h_cust_pkg_reason.pm,
+ t/h_cust_pkg.t, t/h_cust_pkg_reason.t, FS/cust_pkg_reason.pm: add
+ h_cust_pkg and h_cust_pkg_reason packages, RT#4896
+
+2009-02-22 02:58 ivan
+
+ * FS/FS/part_pkg/flat.pm: don't do a credit for unused time for
+ packages that don't have a last bill date. really. RT#4881
+
+2009-02-22 02:34 ivan
+
+ * FS/FS/cust_pkg.pm: this would seem to be right, but...?
+
+2009-02-22 00:41 ivan
+
+ * httemplate/browse/part_pkg.cgi, FS/FS/part_pkg.pm,
+ FS/FS/type_pkgs.pm: add agent type list to package def browse,
+ RT#4880
+
+2009-02-21 18:37 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi, report_tax.cgi: fix tax
+ report for more complex situations with counties and taxclasses,
+ make taxable line items clickable, RT#4878
+
+2009-02-21 16:19 ivan
+
+ * FS/FS/cust_pkg.pm: okay, so no_empty_county was on crack. but
+ this fixes up tax reports nicely. RT#4878
+
+2009-02-21 12:56 ivan
+
+ * FS/FS/cust_pkg.pm: hmm, add no_empty_county option to
+ location_sql search, for tax reports. RT#4878
+
+2009-02-21 12:14 ivan
+
+ * FS/FS/cust_pkg.pm: hmm, add no_empty_county option to
+ location_sql search, for tax reports. RT#4878
+
+2009-02-21 09:56 ivan
+
+ * bin/follow-tax-rename, FS/FS/cust_bill_pkg.pm: adding
+ follow-tax-rename tool (well, quick hack), RT#4878
+
+2009-02-20 20:28 ivan
+
+ * FS/FS/Record.pm: and for obj creation too
+
+2009-02-20 20:27 ivan
+
+ * FS/FS/Record.pm: it would help to actually finish
+ nowarn_classload kludge
+
+2009-02-20 20:23 ivan
+
+ * FS/FS/Record.pm: add nowarn_classload kludge
+
+2009-02-20 07:07 jeff
+
+ * httemplate/edit/cust_main.cgi: support a default tax location
+ outside us/ca with cch data and better handling of response from
+ USPS (RT 4857)
+
+2009-02-19 18:55 ivan
+
+ * FS/FS/access_user.pm: oops, adding multiple-rightname support
+ broke ACL caching, bringing it back should be a good perf win for
+ large customer views, whew. RT#4830
+
+2009-02-19 18:41 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main/packages/status.html:
+ disable display of auto-suspend dates unless enabled by config.
+ at least until it can be made more efficient. this is slowing
+ down customer view waaaaaaaaaay too much. RT#4830
+
+2009-02-19 18:22 ivan
+
+ * httemplate/autohandler: harmless tyop
+
+2009-02-19 05:38 jeff
+
+ * httemplate/edit/: cust_main.cgi,
+ cust_main/choose_tax_location.html: do not attempt to assign a
+ geocode to non us/ca addresses (RT 4857)
+
+2009-02-18 23:57 ivan
+
+ * httemplate/search/: elements/cust_pay_or_refund.html,
+ elements/search.html, cust_pay_pending.html: redirect pending
+ payment report back to customer when the pending payment is
+ resolved, RT#4837, and fix otaker fallout from the pending stuff,
+ RT#4866
+
+2009-02-18 22:42 ivan
+
+ * httemplate/: autohandler, pref/pref-process.html, pref/pref.html:
+ add profiling to a file OOM situations, RT#4830
+
+2009-02-18 17:50 ivan
+
+ * FS/FS/Record.pm: fix "improved" float searching problems, RT#4878
+
+2009-02-16 23:43 ivan
+
+ * httemplate/edit/part_pkg.cgi: kludge to clone customer packages
+ you otherwise couldn't see, RT#4854
+
+2009-02-16 23:40 ivan
+
+ * httemplate/edit/part_pkg.cgi: kludge to clone customer packages
+ you otherwise couldn't see, RT#4854
+
+2009-02-16 18:28 ivan
+
+ * FS/FS/Mason.pm, httemplate/autohandler,
+ httemplate/pref/pref-process.html, httemplate/pref/pref.html:
+ per-user preference for turning on profiling display when
+ DBIx::Profile is loaded, RT#4830
+
+2009-02-16 18:01 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/view/cust_main/packages.html: add tax-exempt checkbox
+ to one-time charges, RT#4858
+
+2009-02-16 15:54 ivan
+
+ * FS/FS/cust_pay_pending.pm, FS/FS/AccessRight.pm,
+ FS/FS/cust_main.pm, httemplate/search/cust_pay_pending.html,
+ httemplate/search/elements/cust_pay_or_refund.html,
+ httemplate/edit/cust_pay_pending.html,
+ httemplate/edit/process/cust_pay_pending.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/elements/menu.html: add reporting on (and resolution
+ of) stuck pending transactions, RT#4837 (RT#3572)
+
+2009-02-15 22:40 jeff
+
+ * bin/cch_tax_tool: a cheesy little tool to assist in syncing cch
+ updates to the initial install
+
+2009-02-15 22:02 jeff
+
+ * FS/FS/: cust_tax_location.pm, part_pkg_taxrate.pm, tax_class.pm:
+ allow completely empty updates (again?)
+
+2009-02-15 21:59 jeff
+
+ * FS/FS/cust_tax_location.pm: wrong operator
+
+2009-02-15 09:38 jeff
+
+ * FS/FS/tax_rate.pm: remove useless line
+
+2009-02-15 09:23 jeff
+
+ * FS/FS/tax_rate.pm: tyop
+
+2009-02-15 09:20 jeff
+
+ * FS/FS/tax_rate.pm: trim whitespace on import
+
+2009-02-15 05:51 jeff
+
+ * FS/FS/Record.pm: improved float searching
+
+2009-02-13 16:40 ivan
+
+ * httemplate/: elements/select-agent.html,
+ elements/tr-select-agent.html, browse/addr_block.cgi: clean up
+ select-agent agent virtualization, RT#1405
+
+2009-02-12 11:48 jeff
+
+ * FS/FS/: cust_tax_location.pm, tax_rate.pm: proper match arguments
+ help
+
+2009-02-12 07:55 jeff
+
+ * httemplate/: misc/tax-import.cgi, elements/form-file_upload.html:
+ better upload error handling and correction of tax upload
+ filecount
+
+2009-02-11 10:44 ivan
+
+ * httemplate/search/cdr.html: fix select and unselect all buttons
+ on CDR bulk actions, RT#4766
+
+2009-02-11 08:06 jeff
+
+ * FS/FS/Schema.pm: mac is unique
+
+2009-02-10 08:25 jeff
+
+ * httemplate/edit/part_pkg.cgi: fix taxproduct fallout from IE 2083
+ limit workaround
+
+2009-02-10 02:35 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/misc/cdr.cgi,
+ httemplate/search/cdr.html: quick n' dirty CDR deletion from web
+ interface, RT#4766 / RT#4731
+
+2009-02-09 07:03 ivan
+
+ * httemplate/browse/agent.cgi: try for slightly better UI on agent
+ config overrides
+
+2009-02-09 06:05 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_pay.pm, FS/bin/freeside-expiration-alerter,
+ conf/invoice_html, conf/invoice_html_statement,
+ httemplate/config/config-image.cgi,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/elements/header.html, httemplate/view/REAL_logo.cgi,
+ httemplate/view/cust_bill-logo.cgi: rest of per-agent config for
+ company_name, company_address, logo, etc.. RT#3989
+
+2009-02-09 03:45 ivan
+
+ * FS/FS/UI/Web.pm: pull out the data for address fields too!
+ RT#4583
+
+2009-02-09 03:35 ivan
+
+ * FS/FS/ConfDefaults.pm: more consistent labeling, RT#4583
+
+2009-02-09 03:32 ivan
+
+ * FS/FS/: UI/Web.pm, ConfDefaults.pm: add some more customer output
+ formats that include service address, RT#4583
+
+2009-02-09 02:38 ivan
+
+ * FS/bin/freeside-fetch: increase LWP timeout, some reports can
+ take a while
+
+2009-02-08 21:59 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add skip_dcontext and
+ skip_dstchannel_prefix options, RT#3196
+
+2009-02-08 17:49 ivan
+
+ * FS/FS/access_user.pm, FS/FS/part_pkg.pm,
+ httemplate/browse/access_group.html,
+ httemplate/browse/part_pkg.cgi, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/elements/select-part_pkg.html: further work on agents
+ editing own packages: fix fallout on package customization from
+ turning agent_virt on in edit/part_pkg.cgi, add a "clone package"
+ to package browse, like clone service, and have agent type
+ selection disappear when you set an agentnum. RT#1331
+
+2009-02-07 18:05 ivan
+
+ * FS/FS/access_user.pm, FS/FS/cust_pkg.pm,
+ httemplate/browse/part_pkg.cgi, httemplate/edit/cust_main.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/elements/edit.html,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/elements/select-agent_types.html,
+ httemplate/elements/select-cust-part_pkg.html,
+ httemplate/elements/select-part_pkg.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-agent_types.html,
+ httemplate/search/elements/search.html,
+ FS/FS/ClientAPI/Signup.pm: further work on agents editing own
+ packages: allow them to see (but not edit) global packages for
+ their type, RT#1331
+
+2009-02-07 12:16 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add more
+ documentation on order_pkg and the ability to order svc_phone
+ too, RT#4722
+
+2009-02-07 11:35 ivan
+
+ * httemplate/view/cust_main/payment_history.html: right-align
+ amount in prev history row
+
+2009-02-07 11:34 ivan
+
+ * FS/FS/Record.pm: don't look up encryption config every search,
+ this should help perf a lot with database config in 1.9
+
+2009-02-07 11:05 ivan
+
+ * bin/pod2x: no, it doesn't look like we have query
+
+2009-02-07 00:23 ivan
+
+ * httemplate/view/cust_main/: payment_history.html, packages.html,
+ packages/location.html, packages/services.html,
+ packages/status.html, payment_history/payment.html,
+ payment_history/refund.html: optimize customer view: avoid
+ looking up config values inside loops, RT#4728
+
+2009-02-06 17:45 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: fix up POD
+ formatting, RT#4727
+
+2009-02-06 17:33 ivan
+
+ * bin/pod2x: don't need this
+
+2009-02-06 17:26 ivan
+
+ * bin/pod2x: update pod2x to use Mediawiki module instead of
+ WWW:::Mediawiki::Client. whew, that wasn't so bad. RT#4727
+
+2009-02-06 10:31 ivan
+
+ * httemplate/elements/location.html: fix spurious "Unit #" label
+ showing up, RT#4745
+
+2009-02-05 13:02 jeff
+
+ * FS/FS/tax_rate.pm: pluralization agreement
+
+2009-02-05 08:57 jeff
+
+ * FS/FS/: Conf.pm, tax_rate.pm: add a config flag to ignore new
+ style taxes instead of throwing fatal errors
+
+2009-02-04 07:58 jeff
+
+ * FS/FS/cust_main.pm: with usage classes, the probability of a
+ taxless line item tranche is too high for this to be a fatal
+ error. we risk overlooking misconfigured taxes/packages
+
+2009-02-03 13:33 jeff
+
+ * FS/FS/part_pkg.pm: the taxproductnum is ALWAYS one of the
+ filtering conditions
+
+2009-02-01 05:52 ivan
+
+ * bin/test_scrub: adding scrub tool
+
+2009-02-01 04:48 ivan
+
+ * httemplate/elements/menu.html: yow, don't hide the config menu
+ unnecessarily
+
+2009-02-01 04:28 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: ach_payment_results.html,
+ agent_delete_svc.html, agent_main.html, agent_order_pkg.html,
+ agent_provision.html, agent_provision_svc_acct.html,
+ change_bill.html, change_password.html, change_pay.html,
+ change_ship.html, customer_change_pkg.html,
+ customer_order_pkg.html, delete_svc.html, footer.html,
+ list_customers.html, make_ach_payment.html, make_payment.html,
+ myaccount.html, payment_results.html, process_change_bill.html,
+ process_change_password.html, process_change_pay.html,
+ process_change_pkg.html, process_change_ship.html,
+ process_order_pkg.html, process_order_recharge.html,
+ process_svc_acct.html, process_svc_external.html, provision.html,
+ provision_svc_acct.html, recharge_prepay.html,
+ recharge_results.html, selfservice.cgi, view_customer.html,
+ view_invoice.html, view_support_details.html, view_usage.html,
+ view_usage_details.html: put the footer in one frigging file,
+ whew
+
+2009-02-01 02:37 ivan
+
+ * FS/FS/Schema.pm: indexing cust_bill_event.eventpart should help
+ speed up freeside-migrate-events slightly... RT#4277
+
+2009-01-31 20:13 ivan
+
+ * FS/FS/Upgrade.pm: commit after each table upgrade, helps with
+ getting huge dbs upgraded, RT#4679
+
+2009-01-31 01:53 ivan
+
+ * FS/FS/access_user.pm: cache the results of ACL queries, should
+ improve performance of customer view page for customers with
+ shitloads of packages/services, RT#4696
+
+2009-01-30 12:44 ivan
+
+ * FS/FS/ClientAPI_SessionCache.pm: should use FS::Conf
+
+2009-01-29 16:40 ivan
+
+ * FS/FS/UI/bytecount.pm: we're not a disk drive manufacturer
+
+2009-01-29 11:21 ivan
+
+ * FS/FS/cust_pkg.pm: fix unsuspend-always_adjust_next_bill_date
+ config, RT#4271
+
+2009-01-28 08:29 rsiddall
+
+ * rpm/freeside.spec: Removed conflict between core billing package
+ and self-service RPMs so you can install them all on the same
+ machine. This may have applications if you're using XMLRPC to
+ talk to the self-service interface from PHP, Python, etc.
+
+2009-01-27 01:39 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/search/elements/search.html: add
+ ACL to allow download of browse/ stuff too, when possible.
+ RT#4681
+
+2009-01-25 20:22 ivan
+
+ * FS/FS/cust_main.pm: i think this was right after all, we do want
+ to look for a county-less state+country match before country only
+ and giving up, RT#4681
+
+2009-01-25 18:14 ivan
+
+ * FS/FS/cust_bill.pm: should fix: Argument "\\dollar 2.69" isn't
+ numeric in sprintf at /usr/local/share/perl/5.8.8/FS/cust_bill.pm
+ line 2193. Hopefully no problems with invoice with 0 tax
+ printing :/. RT#4681
+
+2009-01-25 17:36 ivan
+
+ * FS/FS/cust_main.pm: should be better error message for inability
+ to find tax rates, RT#4681. also pull in the add_freq changes.
+ *think* they're safe. famous last words.
+
+2009-01-25 17:07 ivan
+
+ * FS/FS/cust_pkg.pm: fix harmless warning, RT#4681: Argument ""
+ isn't numeric in numeric eq (==) at
+ /usr/local/share/perl/5.8.8/FS/cust_pkg.pm line 443.
+
+2009-01-25 15:58 ivan
+
+ * FS/FS/Record.pm, FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/elements/edit.html: fix one-time charges and
+ package customization for employees who don't have 'Edit global
+ package definition' ACL, RT#4668
+
+2009-01-25 14:20 ivan
+
+ * httemplate/browse/: access_group.html, access_user.html:
+ normalize terminology: s/internal users/employees/
+
+2009-01-25 12:43 ivan
+
+ * FS/bin/: freeside-cdrd, freeside-queued: reduce waiting time for
+ -cdrd and -queued, RT#4667
+
+2009-01-24 17:27 ivan
+
+ * FS/FS/cust_svc.pm: add some debugging to RADIUS db calls
+
+2009-01-24 13:53 ivan
+
+ * fs_selfservice/php/: order_renew.php,
+ process_payment_order_renew.php: finish up prepay example,
+ RT#4623
+
+2009-01-24 13:53 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: have the prepay amounts include the
+ current balance, RT#4623
+
+2009-01-24 13:49 ivan
+
+ * httemplate/edit/cust_main.cgi: wtf, don't pop up the geocode
+ chooser when taxproducts are off
+
+2009-01-24 13:04 ivan
+
+ * httemplate/: elements/select-domain.html,
+ elements/select-table.html, elements/tr-select-domain.html,
+ search/report_svc_acct.html, search/svc_acct.cgi: add domain
+ selection to advanced account report (side effect on RT#4623)
+
+2009-01-22 17:23 ivan
+
+ * fs_selfservice/php/: freeside.class.php, login.php, main.php,
+ order_renew.php, process_login.php,
+ process_payment_order_renew.php: add the start at PHP
+ self-service as a quick early renew example
+
+2009-01-22 16:49 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: typo in rounding the amounts
+ returned by renew_info, RT#4623
+
+2009-01-22 16:23 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: round the amounts returned by
+ renew_info, RT#4623
+
+2009-01-22 09:29 ivan
+
+ * FS/FS/part_pkg.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add self-service
+ methods renew_info, order_renew and process_payment_order_renew
+ to enable self-renewal through self-service. RT#4623
+
+2009-01-20 18:02 ivan
+
+ * httemplate/edit/: part_pkg.cgi, elements/edit.html: fix
+ (hopefully last of the) customize package bogosity in 1.9,
+ RT#4662
+
+2009-01-20 16:52 ivan
+
+ * FS/FS/cust_bill_pkg_tax_location.pm: stupid typo preventing
+ service addresses from working, RT#4663
+
+2009-01-20 12:08 ivan
+
+ * FS/FS/: cust_main.pm: whew, hopefully that will actually fix
+ agent-specific invoices migrated from 1.7->1.9, RT#4645
+
+2009-01-20 11:35 ivan
+
+ * FS/FS/cust_bill.pm: fix agent-specific logos migrated from 1.7,
+ RT#4645
+
+2009-01-19 15:53 ivan
+
+ * FS/FS/tax_rate.pm, httemplate/misc/process/tax-import.cgi:
+ tax-pkg_location changes broke new taxation, this should fix
+
+2009-01-19 15:44 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: turn debugging off
+
+2009-01-19 14:32 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: don't ignore the cdrtypenum rule for
+ 0, hopefully finally fix DA for QIS, RT#4502
+
+2009-01-19 09:37 ivan
+
+ * httemplate/edit/process/cust_pkg.cgi: fix error on bulk package
+ order/cancel, RT#4645
+
+2009-01-18 22:36 ivan
+
+ * httemplate/: elements/tr-select-cust_location.html,
+ view/cust_main/packages/location.html: default service location
+ is cust_main ship_ address when present! RT#4499
+
+2009-01-18 19:50 ivan
+
+ * FS/FS/Schema.pm, FS/FS/rate.pm,
+ httemplate/edit/process/rate_region.cgi: 10 digit prefix
+ matching, RT#4403
+
+2009-01-18 15:51 ivan
+
+ * FS/FS/cust_pkg.pm: finish package location tax reporing, RT#4499
+
+2009-01-18 15:43 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_bill_pkg_tax_location.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_county.pm, FS/FS/tax_rate.pm,
+ FS/t/cust_bill_pkg_tax_location.t,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_tax.cgi,
+ httemplate/view/cust_main/packages/location.html: finish package
+ location tax reporing, RT#4499
+
+2009-01-18 13:06 rsiddall
+
+ * rpm/freeside.sysconfig: bash didn't like spaces on each side of
+ an equals sign.
+
+2009-01-13 05:00 ivan
+
+ * FS/FS/part_export/internal_diddb.pm: fix internal_diddb delete &
+ return number to availability, RT#4603
+
+2009-01-12 19:25 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: whew, works fine, rewrite to 411 was
+ sticky that's why calls were being skipped for wrong carrierid,
+ RT#4502
+
+2009-01-12 18:39 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: by his noodly appendage, i hope this
+ is just a precendece problem, RT#4502
+
+2009-01-12 18:13 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: WORKING avoid looking up options
+ inside the rating loop, RT#4502
+
+2009-01-12 17:58 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: avoid looking up options inside the
+ rating loop, RT#4502
+
+2009-01-12 17:34 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: refactor out the ignoring rules into
+ check_chargable; ignore carrierid rule w/411 rewrite, RT#4502
+
+2009-01-12 16:17 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: wtf is up with 411_rewrite, RT#4502
+
+2009-01-12 15:51 ivan
+
+ * FS/FS/: Conf.pm, Record.pm, cdr/taqua.pm: taqua config to rewrite
+ DA calls, RT#4502
+
+2009-01-12 13:16 jeff
+
+ * FS/FS/cust_main.pm: tickets only exist when a ticket system
+ exists
+
+2009-01-12 12:59 jeff
+
+ * FS/FS/cust_main.pm: doc tyop
+
+2009-01-12 01:01 jeff
+
+ * FS/FS/svc_acct.pm: vfw callback failure
+
+2009-01-10 15:56 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, httemplate/misc/change_pkg.cgi,
+ httemplate/edit/process/change-cust_pkg.html,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/elements/location.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/view/cust_main/packages/location.html,
+ httemplate/view/cust_main/packages/package.html: implement
+ package changes w/location change, RT#4499
+
+2009-01-09 16:43 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/elements/location.html,
+ httemplate/elements/tr-select-cust_location.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/misc/location.cgi, httemplate/misc/order_pkg.html:
+ more work on package service addresses: hide locations when
+ they're all the default, config to show them anyway / finish
+ implementing package ordering, fix all the state/county weirdness
+ when changing the location dropdown. RT#4499
+
+2009-01-08 20:06 ivan
+
+ * httemplate/edit/cust_main/contact.html,
+ httemplate/elements/location.html,
+ httemplate/elements/select-country.html,
+ httemplate/elements/select-county.html,
+ httemplate/elements/select-state.html,
+ httemplate/elements/tr-select-part_referral.html,
+ httemplate/misc/location.cgi, httemplate/misc/order_pkg.html,
+ FS/FS/Mason.pm, FS/FS/cust_location.pm, FS/FS/cust_main.pm,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/packages/location.html: pick/enter a
+ location when ordering a package, RT#4499
+
+2009-01-07 17:45 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/cust_location.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ FS/t/cust_location.t, eg/table_template.pm,
+ eg/table_template-svc.pm,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/packages/location.html,
+ httemplate/view/cust_main/packages/package.html,
+ httemplate/view/cust_main/packages/services.html,
+ httemplate/view/cust_main/packages/status.html: start adding
+ package locations, RT#4499
+
+2009-01-07 08:59 jeff
+
+ * conf/invoice_latex: allow tex to do more column sizing
+
+2009-01-06 16:27 ivan
+
+ * httemplate/browse/rate_region.html: country code is two words
+
+2009-01-06 16:18 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: fixup error message, this is all for
+ RT#4524
+
+2009-01-06 16:14 ivan
+
+ * httemplate/browse/: rate.cgi, rate_region.html: add a dropdown to
+ help browse regions by countrycode
+
+2009-01-06 15:30 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: throw a fatal error if a call is
+ unrateable and add an ignore_unrateable flag to go back to the
+ old skip behavior
+
+2009-01-06 13:16 ivan
+
+ * FS/FS/: cdr.pm, part_pkg/voip_cdr.pm: swap price to last column
+ in default CDR output format; fix "all 0 prices" when using
+ simple output format w/internal rating, RT#4503
+
+2009-01-05 13:12 jeff
+
+ * FS/FS/tax_rate.pm: doh! change the interface here, too
+
+2009-01-04 16:26 ivan
+
+ * httemplate/elements/checkboxes-table-name.html,
+ httemplate/elements/checkboxes.html,
+ httemplate/elements/select-rate.html,
+ httemplate/elements/tr-select-rate.html, FS/FS/rate_prefix.pm,
+ httemplate/browse/rate.cgi,
+ httemplate/misc/copy-rate_detail.html,
+ httemplate/misc/process/copy-rate_detail.html: add rate copying,
+ RT#4431
+
+2009-01-04 14:07 ivan
+
+ * Makefile, init.d/freeside-init: don't run a self-service server
+ against localhost OOTB; eliminate those gigantic useless logfiles
+
+2009-01-02 17:52 ivan
+
+ * bin/cdr.http_and_import, bin/cdr.import, bin/cdr.sftp_and_import,
+ FS/FS/Record.pm, FS/FS/cdr.pm, FS/FS/cdr/indosoft.pm: indosoft
+ CDR format, RT#4425
+
+2009-01-02 14:03 ivan
+
+ * FS/FS/Record.pm, FS/FS/cdr.pm, FS/FS/cdr/bell_west.pm,
+ FS/FS/cdr/troop.pm, bin/cdr.import, bin/cdr.sftp_and_import: add
+ troop CDRs, RT#4413
+
+2009-01-02 10:01 ivan
+
+ * eg/cdr_template.pm: cdr template, RT#4413 and RT#4412
+
+2009-01-02 09:58 ivan
+
+ * FS/FS/cdr/troop.pm: commiting initial troop CDR template, RT#4413
+
+2009-01-01 12:11 rsiddall
+
+ * rpm/freeside-selfservice.conf: New Apache configuration file for
+ the self-service interface.
+
+2009-01-01 12:10 rsiddall
+
+ * rpm/freeside.spec: Modifications to let self-service work if you
+ really insist on installing it on the same machine as the billing
+ server. Also more fixes for SuSE, and a couple of changes to
+ minimize differences from the 1.7 branch.
+
+2008-12-31 14:04 ivan
+
+ * FS/FS/: Record.pm, cdr/bell_west.pm: finish up working bell_west
+ CDR format, RT#4403
+
+2008-12-31 10:07 jeff
+
+ * FS/FS/cust_main.pm: one got missed?
+
+2008-12-30 19:28 ivan
+
+ * FS/FS/Record.pm, FS/FS/cdr.pm, FS/FS/phone_avail.pm,
+ FS/FS/cdr/bell_west.pm, FS/FS/cdr/simple.pm,
+ FS/FS/part_pkg/voip_cdr.pm, httemplate/edit/rate_detail.html,
+ httemplate/misc/cdr-import.html,
+ httemplate/misc/process/cdr-import.html: bell west CDR format,
+ RT#4403
+
+2008-12-30 14:00 jeff
+
+ * FS/FS/: cust_pkg.pm, cust_pkg_reason.pm: yet more timestamping
+ improvements and corrections to reasons based on history records
+
+2008-12-30 13:45 jeff
+
+ * FS/FS/cdr.pm: move price to last column for default_source
+
+2008-12-30 11:13 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: allow upstream_simple to specify a
+ usage_class for tax purposes in calltypenum
+
+2008-12-29 10:06 jeff
+
+ * Makefile, conf/invoice_latex, conf/longtable.sty.patch,
+ etc/fslongtable.sty: ease deployment of patched longtable
+
+2008-12-28 11:10 ivan
+
+ * httemplate/browse/cust_main_county.cgi: finish dealing with
+ counties with spaces, etc., RT#4496
+
+2008-12-28 11:08 ivan
+
+ * httemplate/edit/process/cust_main_county-expand.cgi: allow normal
+ ut_textn strings in county expansion, RT#4496
+
+2008-12-28 10:59 ivan
+
+ * httemplate/browse/cust_main_county.cgi: deal with counties with
+ spaces, etc., RT#4496
+
+2008-12-28 10:52 ivan
+
+ * httemplate/browse/cust_main_county.cgi: put the
+ country/state/county selections on their own line, RT#4496
+
+2008-12-28 10:48 ivan
+
+ * httemplate/elements/: select-country.html, select-county.html,
+ select-state.html, select-did.html: fix browse results for
+ selecting counties (resulting from separating tax classes), also
+ add dropdowns to browse by state and county, RT#4496
+
+2008-12-28 10:44 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/cust_main/contact.html, edit/cust_main/billing.html,
+ misc/payment.cgi, edit/cust_main/select-country.html,
+ edit/cust_main/select-county.html,
+ edit/cust_main/select-state.html: fix browse results for
+ selecting counties (resulting from separating tax classes), also
+ add dropdowns to browse by state and county, RT#4496
+
+2008-12-24 16:45 jeff
+
+ * FS/FS/: cust_bill_pkg.pm, cust_main.pm, cust_main_county.pm: fix
+ "texas tax" in 1.9
+
+2008-12-23 13:41 jeff
+
+ * FS/FS/cust_main.pm: miss use
+
+2008-12-23 12:35 jeff
+
+ * FS/FS/cust_main.pm: correct bad tax calculation
+
+2008-12-22 16:32 ivan
+
+ * FS/FS/: cust_bill.pm: truncate package descriptions over 50 chars
+ to avoid pushing the total column out to the right, RT#4449
+
+2008-12-22 15:28 ivan
+
+ * httemplate/config/config.cgi: textareas are much less annoying to
+ work with when their scrollbar isn't scrolled off the side itself
+
+2008-12-22 14:30 rsiddall
+
+ * rpm/freeside.spec: Copying over modifications to support SuSE
+ from the 1.7 branch.
+
+2008-12-22 13:16 ivan
+
+ * httemplate/edit/: part_pkg.cgi, quick-charge.html: soft-limit
+ package names to 50 chars to avoid problems with typeset
+ invoices, RT#4449
+
+2008-12-21 13:53 ivan
+
+ * FS/FS/svc_phone.pm: and fix msgcat usage, this should do it?,
+ RT#4204
+
+2008-12-21 13:49 ivan
+
+ * FS/FS/svc_acct.pm: svc_acct.pm bogosity too, wtf?!, RT#4204
+
+2008-12-21 13:44 ivan
+
+ * FS/FS/: msgcat.pm: msgcat.pm upgrade bogosity, shrug, RT#4204
+
+2008-12-21 13:37 ivan
+
+ * FS/FS/: svc_Common.pm, Upgrade.pm: unique checking for svc_phone
+ like svc_acct, closes: RT#4204 (also a few lines of the new
+ per-agent config snuck in Conf.pm from RT#3989)
+
+2008-12-21 13:33 ivan
+
+ * FS/: FS/svc_phone.pm, FS/Conf.pm, FS/Record.pm, FS/Setup.pm,
+ FS/msgcat.pm, FS/svc_Common.pm, FS/svc_acct.pm,
+ bin/freeside-upgrade: unique checking for svc_phone like
+ svc_acct, closes: RT#4204 (also a few lines of the new per-agent
+ config snuck in Conf.pm from RT#3989)
+
+2008-12-21 10:38 ivan
+
+ * FS/bin/freeside-cdrd: cdrd brainfart, finishing up RT#4423
+
+2008-12-21 10:09 ivan
+
+ * FS/: FS/Schema.pm, FS/queue.pm, bin/freeside-cdrd: have
+ freeside-queued put billing jobs in the queue, so they run in
+ their own short-lived processes, RT#4423
+
+2008-12-21 09:40 ivan
+
+ * FS/FS/queue.pm: doc
+
+2008-12-15 16:08 jeff
+
+ * FS/FS/cust_pkg.pm: proper dates on expire and suspend reasons
+
+2008-12-11 13:11 jeff
+
+ * FS/FS/cust_main.pm: place tax on invoice only once
+
+2008-12-10 13:43 ivan
+
+ * httemplate/search/: cust_bill_event.html, report_cust_bill.html,
+ report_cust_credit.html, report_cust_event.html,
+ report_cust_main-zip.html, report_cust_main.html,
+ report_cust_pay.html, report_cust_pay_batch.html,
+ report_cust_pkg.html, report_newtax.html, report_svc_acct.html,
+ report_tax.html: allow all-agent reporting again
+
+2008-12-10 13:33 ivan
+
+ * httemplate/graph/: report_cust_bill_pkg.html,
+ report_cust_pkg.html, report_money_time.html: allow all-agent
+ reporting again
+
+2008-12-10 12:20 ivan
+
+ * FS/FS/cust_main/Import.pm: referral import fixes, RT#4427
+
+2008-12-10 11:43 rsiddall
+
+ * rpm/freeside.spec: Cleanup to quieten rpmlint. Fixes to cope
+ with moving code out of handler.pl, etc.
+
+2008-12-10 11:42 rsiddall
+
+ * rpm/rpm2Bundle: Further modifications to handle Perl RPM names
+ and map them back to Perl module names.
+
+2008-12-09 18:47 jeff
+
+ * FS/FS/cust_tax_location.pm: space is empty
+
+2008-12-08 17:49 ivan
+
+ * FS/FS/Record.pm: oops
+
+2008-12-08 17:46 ivan
+
+ * FS/FS/Record.pm, FS/FS/inventory_item.pm,
+ httemplate/misc/inventory_item-import.html,
+ httemplate/misc/process/inventory_item-import.html: use common
+ base for inventory import too, fixes problems with errors due to
+ dos line endings and allows Excel upload, RT#4346
+
+2008-12-08 02:13 ivan
+
+ * FS/FS/cust_bill_pkg.pm: make CDRs smaller, so we can fit more
+ columns, RT#4376
+
+2008-12-08 01:08 ivan
+
+ * FS/FS/Schema.pm: make room for CDRs, RT#4387
+
+2008-12-08 00:52 ivan
+
+ * FS/FS/: cdr.pm, part_pkg/voip_cdr.pm: respect output_format and
+ add an header for rating_method=prefix too, RT#4387
+
+2008-12-08 00:46 ivan
+
+ * conf/invoice_html: normal ext_desc shouldn't shove the second+
+ columns of CDRs out
+
+2008-12-05 09:23 jeff
+
+ * FS/FS/tax_rate.pm: passthrough support for gross revenue taxes
+
+2008-12-05 09:19 jeff
+
+ * FS/FS/part_pkg.pm: avoid taxation on products with no assigned
+ taxes
+
+2008-12-05 00:24 jeff
+
+ * FS/FS/cust_main.pm: missing uses, corrects 4388
+
+2008-12-04 20:20 jeff
+
+ * FS/FS/cust_main_county.pm: bad shortcut causes taxes not to be
+ charged
+
+2008-12-03 21:16 jeff
+
+ * FS/FS/part_export/soma.pm: wtf?
+
+2008-12-03 18:19 jeff
+
+ * httemplate/edit/cust_main.cgi: better placement of script
+ sourcing
+
+2008-12-03 18:03 jeff
+
+ * httemplate/elements/init_overlib.html: better placement of script
+ sourcing
+
+2008-12-03 17:46 jeff
+
+ * httemplate/edit/cust_main/choose_tax_location.html: better
+ behavior when zip code is missing
+
+2008-12-03 15:29 ivan
+
+ * httemplate/search/report_receivables.html: allow an all-agent
+ receivables report again
+
+2008-12-03 13:15 ivan
+
+ * httemplate/misc/whois.cgi: fix real customer numbers showing on
+ view pages, RT#4099/4379
+
+2008-12-03 13:12 ivan
+
+ * httemplate/view/: cust_pay.html, cust_refund.html, cust_bill.cgi,
+ svc_broadband.cgi, svc_domain.cgi, svc_external.cgi,
+ svc_forward.cgi, svc_www.cgi: fix real customer numbers showing
+ on view pages, RT#4099/4379
+
+2008-12-03 09:25 ivan
+
+ * FS/FS/part_export/internal_diddb.pm: fix internal_diddb to
+ default to countrycode 1
+
+2008-12-02 21:53 ivan
+
+ * httemplate/edit/: part_pkg.cgi, elements/edit.html: 60 char soft
+ max length for packages, so invoices don't wrap, RT#4328
+
+2008-12-02 17:42 jeff
+
+ * bin/import-tax-rates,
+ httemplate/edit/cust_main/choose_tax_location.html,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/elements/ajaxcontentmws.js, FS/FS/Misc.pm,
+ FS/FS/Schema.pm, FS/FS/cust_main.pm, FS/FS/cust_tax_location.pm,
+ FS/FS/part_pkg_taxrate.pm, FS/FS/tax_class.pm, FS/FS/tax_rate.pm,
+ httemplate/edit/cust_main.cgi, httemplate/misc/tax-import.cgi,
+ httemplate/misc/xmlhttp-cust_main-address_standardize.html:
+ support zip5 tax lookups, correct errors with fixed format cch
+ import, inital import performance improvements, noise reduction
+ on imports, tool for inital import
+
+2008-11-30 23:52 ivan
+
+ * FS/FS/: Conf.pm, cdr.pm: set charged_party to accoutncode for
+ vedeye, RT#4342
+
+2008-11-30 15:37 ivan
+
+ * httemplate/edit/access_user.html: s/Internal Access
+ Groups/Employee Groups/
+
+2008-11-30 15:34 ivan
+
+ * httemplate/: browse/access_user.html, edit/access_user.html:
+ s/Internal Users/Employees/
+
+2008-11-30 15:26 ivan
+
+ * httemplate/: browse/access_group.html, edit/access_group.html:
+ s/Internal Access Groups/Employee Groups/
+
+2008-11-30 13:01 ivan
+
+ * FS/FS/cdr.pm: _cdr_min_parser_maker fix for correct setting of
+ duration/billsec with simple & simple2 rate plans
+
+2008-11-29 13:54 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/svc_phone.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: add
+ selfservice_server-single_domain config, and login_info
+ self-service method to give the login page a bit more
+ configurability
+
+2008-11-29 12:32 ivan
+
+ * FS/FS/Conf.pm: add selfservice_server-single_domain config, and
+ login_info self-service method to give the login page a bit more
+ configurability
+
+2008-11-25 16:30 ivan
+
+ * FS/FS/ClientAPI/PrepaidPhone.pm: don't set a Session-Timeout if
+ the rate is 0
+
+2008-11-25 16:20 ivan
+
+ * FS/FS/: Record.pm, cust_main/Import.pm: should fix importing from
+ excel, closes: RT#4337
+
+2008-11-24 07:40 jeff
+
+ * FS/FS/part_export/soma.pm: more caffiene, please
+
+2008-11-24 07:36 jeff
+
+ * FS/FS/part_export/soma.pm: doh
+
+2008-11-24 07:18 jeff
+
+ * FS/FS/part_export/soma.pm: be more accepting
+
+2008-11-24 06:48 jeff
+
+ * FS/FS/part_export/soma.pm: esn's are hex
+
+2008-11-24 04:22 ivan
+
+ * FS/FS/cdr/genband.pm: update genband import to agree with
+ reality, RT#4177
+
+2008-11-24 02:59 ivan
+
+ * FS/FS/part_export/internal_diddb.pm: add countrycode option to
+ internal_diddb; throw a warning instead of an error if a number
+ couldn't be returned to inventory
+
+2008-11-24 02:47 ivan
+
+ * FS/FS/part_export/: phone_sqlradius.pm, sqlradius.pm: fix
+ phone_sqlradius CDR population?, RT#4100
+
+2008-11-24 02:11 ivan
+
+ * FS/FS/ClientAPI/PrepaidPhone.pm: add debugging, hopefully fix
+ seconds returned finally, RT#4100
+
+2008-11-24 00:47 ivan
+
+ * FS/FS/ClientAPI/PrepaidPhone.pm: look for a voip rate in pricing
+ add-ons too... eek. also correct rating to destination RT#4100
+
+2008-11-22 14:17 ivan
+
+ * FS/FS/: Conf.pm, Schema.pm, cust_credit.pm, cust_main.pm,
+ cust_pkg.pm, part_event.pm, part_event/Action/addpost.pm,
+ part_event/Condition.pm, part_event/Action/apply.pm,
+ part_event/Action/bill.pm, part_event/Action/cancel.pm,
+ part_event/Action/collect.pm,
+ part_event/Action/cust_bill_batch.pm,
+ part_event/Action/cust_bill_comp.pm,
+ part_event/Action/cust_bill_fee_percent.pm,
+ part_event/Action/cust_bill_realtime_card.pm,
+ part_event/Action/cust_bill_realtime_check.pm,
+ part_event/Action/cust_bill_realtime_lec.pm,
+ part_event/Action/cust_bill_send.pm,
+ part_event/Action/cust_bill_send_agent.pm,
+ part_event/Action/cust_bill_send_alternate.pm,
+ part_event/Action/cust_bill_send_csv_ftp.pm,
+ part_event/Action/cust_bill_send_if_newest.pm,
+ part_event/Action/cust_bill_spool_csv.pm,
+ part_event/Action/cust_bill_suspend_if_balance.pm,
+ part_event/Action/fee.pm,
+ part_event/Action/pkg_referral_credit.pm,
+ part_event/Action/pkg_referral_credit_pkg.pm,
+ part_event/Action/suspend.pm,
+ part_event/Action/suspend_if_pkgpart.pm,
+ part_event/Action/suspend_unless_pkgpart.pm,
+ part_event/Condition/balance.pm,
+ part_event/Condition/balance_age.pm,
+ part_event/Condition/balance_under.pm,
+ part_event/Condition/cust_bill_age.pm,
+ part_event/Condition/cust_bill_has_service.pm,
+ part_event/Condition/cust_bill_owed.pm,
+ part_event/Condition/cust_bill_owed_under.pm,
+ part_event/Condition/cust_payments.pm,
+ part_event/Condition/has_referral_custnum.pm,
+ part_event/Condition/once_percust.pm,
+ part_event/Condition/pkg_age.pm,
+ part_event/Condition/pkg_notchange.pm,
+ part_event/Condition/pkg_pkgpart.pm,
+ part_event/Condition/pkg_recurring.pm,
+ part_event/Condition/pkg_unless_pkgpart.pm, part_pkg/flat.pm:
+ referral credits overhaul, use billing events, agents can
+ self-configure, limit to once-per-customer, depend on any time
+ from referred package, referred customer payment, specific
+ packages, partial staged credits, RT#3983
+
+2008-11-21 23:10 ivan
+
+ * httemplate/: browse/part_event.html, edit/elements/edit.html:
+ billing event cloning
+
+2008-11-20 19:36 jeff
+
+ * FS/FS/part_export/soma.pm: initial somanetworks support
+
+2008-11-20 18:57 ivan
+
+ * httemplate/edit/elements/edit.html: remove debugging accidentally
+ left in
+
+2008-11-20 18:55 ivan
+
+ * httemplate/edit/process/elements/process.html: document
+ viewall_ext
+
+2008-11-20 17:58 ivan
+
+ * httemplate/: edit/elements/edit.html,
+ elements/tr-select-agent.html: undo voodoo, find real problem
+
+2008-11-20 16:06 ivan
+
+ * httemplate/edit/elements/edit.html: voodoo
+
+2008-11-20 11:02 jeff
+
+ * FS/FS/rate.pm: this must be what is meant
+
+2008-11-20 09:49 jeff
+
+ * FS/bin/freeside-dedup-cust_bill_pkg_detail-header: tool to remove
+ extra cdr headers
+
+2008-11-20 08:59 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: only one header per package, not one
+ per service -- fixes #4260
+
+2008-11-20 04:52 ivan
+
+ * FS/FS/cust_main/Import.pm: better end-of-spreadsheet detection
+ for excel import, hopefully. should fix "Error: Can't use an
+ undefined value as an ARRAY reference" error on import. RT#4297
+
+2008-11-20 03:35 ivan
+
+ * bin/rate.delete: fill in the ratenum. doesn't actually delete
+ the rate itself, just all its data (the hard part)
+
+2008-11-19 16:42 ivan
+
+ * FS/FS/cust_bill.pm: don't use payname for CARD or DCRD either,
+ closes: RT#3982
+
+2008-11-19 06:55 jeff
+
+ * FS/FS/Misc.pm, FS/FS/Schema.pm, FS/FS/cust_tax_location.pm,
+ FS/FS/part_pkg_taxrate.pm, FS/FS/tax_class.pm, FS/FS/tax_rate.pm,
+ httemplate/misc/tax-import.cgi: support for cch fixed format
+
+2008-11-18 17:24 ivan
+
+ * FS/FS/Schema.pm: fix phone_avail.availnum to be a proper primary
+ key, fix agent.agent_custnum unique index causing it to get a
+ value filled in by Record.pm
+
+2008-11-17 18:56 rsiddall
+
+ * rpm/rpm2Bundle: Now handles hyphenated Perl requirements as well
+ as those in parentheses, and handles more version relation types.
+
+2008-11-12 18:22 ivan
+
+ * FS/FS/access_groupagent.pm, FS/FS/agent.pm,
+ httemplate/browse/agent.cgi, httemplate/edit/agent.cgi,
+ httemplate/edit/process/agent.cgi: add ability to view/edit
+ access groups of an agent
+
+2008-11-11 00:55 ivan
+
+ * FS/FS/cust_bill.pm: add fax to invoice data, RT#3290
+
+2008-11-11 00:45 ivan
+
+ * conf/: invoice_html, invoice_latex: agent_custid and ship_fax
+ don't belong with the ship address, that was an unrelated
+ tampabay/pbx-change request, RT#3290
+
+2008-11-10 23:51 ivan
+
+ * conf/invoice_html: make HTML invoice more consistent with current
+ typeset invoice: center invoice date instead of right-justify
+
+2008-11-09 03:43 ivan
+
+ * httemplate/browse/agent.cgi: realign things in light of
+ small_custview in this table for master customering, roundaboutly
+ part of #2933
+
+2008-11-09 03:31 ivan
+
+ * FS/FS/cust_main/Import.pm: move batch customer import to its own
+ file; add svc_external_svc_phone export format, RT#4103
+
+2008-11-09 01:14 ivan
+
+ * FS/FS/Conf.pm, httemplate/elements/file-upload.html,
+ httemplate/misc/phone_avail-import.html: add a global countrycode
+ to phone_avail import and a conf for the default (some other conf
+ values snuck in also, oh well)
+
+2008-11-09 00:51 ivan
+
+ * FS/FS/Mason.pm, FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ FS/FS/cust_main/Import.pm, httemplate/misc/cust_main-import.cgi,
+ Makefile, FS/FS.pm, FS/MANIFEST, FS/bin/freeside-queued,
+ httemplate/misc/process/cust_main-import.cgi: move batch customer
+ import to its own file; add svc_external_svc_phone export format,
+ RT#4103
+
+2008-11-06 22:04 ivan
+
+ * httemplate/view/svc_acct.cgi: fix viewing of unlinked services.
+ wow, it has been a while
+
+2008-11-06 14:53 ivan
+
+ * FS/FS/part_export/sqlradius.pm: should fix open session RADIUS
+ search, RT #4233
+
+2008-11-06 14:20 ivan
+
+ * FS/FS/part_export/sqlradius.pm: fix radius search, RT#4233
+
+2008-11-05 20:22 ivan
+
+ * FS/FS/svc_acct.pm: avoid harmless "Use of uninitialized value in
+ concatenation (.) or string at
+ /usr/local/share/perl/5.8.8/FS/svc_acct.pm line 1140" error
+
+2008-11-05 20:18 ivan
+
+ * FS/FS/svc_acct.pm: . is used in some implementations of classic
+ crypt
+
+2008-11-03 07:28 jeff
+
+ * FS/FS/cust_main.pm: REAL otherwise there are no taxes
+
+2008-11-03 07:26 jeff
+
+ * FS/FS/cust_main.pm: yikes! not yet
+
+2008-11-03 07:14 jeff
+
+ * FS/FS/cust_main.pm: otherwise there are no taxes
+
+2008-11-02 17:10 ivan
+
+ * httemplate/view/svc_acct.cgi: time remaining is more useful to
+ display as hours + minutes than days, hours, minutes from
+ Time::Duration
+
+2008-11-02 12:27 ivan
+
+ * httemplate/search/: cust_bill_pkg.cgi, report_tax.cgi: (and
+ REALLY fix the line-item links too, whew) fix overreporting of
+ tax invoiced when using & reporting with taxclasses, RT#4131
+
+2008-11-02 12:03 ivan
+
+ * httemplate/search/report_tax.cgi: (and fix the line-item links
+ too, whew) fix overreporting of tax invoiced when using &
+ reporting with taxclasses, RT#4131
+
+2008-11-02 11:40 ivan
+
+ * httemplate/search/report_tax.cgi: (and fix the total too) fix
+ overreporting of tax invoiced when using & reporting with
+ taxclasses, RT#4131
+
+2008-11-02 11:26 ivan
+
+ * httemplate/search/report_tax.cgi: fix overreporting of tax
+ invoiced when using & reporting with taxclasses, RT#4131
+
+2008-11-01 15:12 ivan
+
+ * init.d/freeside-init, FS/FS/Daemon.pm, FS/bin/freeside-cdrd: have
+ freeside-cdrd disable itself if there's no appropriate package
+ definition, RT#4184
+
+2008-10-29 15:24 ivan
+
+ * FS/FS/cust_main.pm: eek, fix agent_plandata from comping up with
+ spurious hits
+
+2008-10-29 13:23 ivan
+
+ * FS/FS/part_event/Action.pm: remove debugging accidentally left in
+
+2008-10-29 13:21 ivan
+
+ * FS/FS/part_event/Action.pm: huh. how did event editing ever
+ work? is this 5.10-specific?
+
+2008-10-29 01:03 ivan
+
+ * FS/FS/cust_main.pm, init.d/freeside-init,
+ FS/FS/part_pkg/voip_cdr.pm, FS/bin/freeside-cdrd: prepaid cdr
+ pickup & bill daemon, RT#4184
+
+2008-10-29 00:50 ivan
+
+ * FS/FS/part_export/internal_diddb.pm: fix to internal_diddb
+ provisioning
+
+2008-10-27 18:23 ivan
+
+ * FS/FS/svc_phone.pm: fix svc_phone non-numeric "phone numbers",
+ RT#4204
+
+2008-10-24 17:37 ivan
+
+ * FS/FS/: Conf.pm, svc_phone.pm: add a switch to allow letters in
+ phone numbers, RT#4195
+
+2008-10-24 16:21 ivan
+
+ * httemplate/view/svc_phone.cgi: correct links to non-US CDRs from
+ svc_phone view
+
+2008-10-24 15:53 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm: use
+ Reply-Message for the RADIUS error message, RT#4100
+
+2008-10-24 15:23 ivan
+
+ * FS/FS/ClientAPI/PrepaidPhone.pm: adding prepaid self-service
+ hooks, RT#4100
+
+2008-10-24 14:31 ivan
+
+ * FS/FS/: rate.pm, ClientAPI/PrepaidPhone.pm, part_pkg/voip_cdr.pm,
+ part_pkg/voip_sqlradacct.pm: adding prepaid self-service hooks,
+ RT#4100
+
+2008-10-24 14:25 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService/: FreeRadiusVoip.pm:
+ rlm_perl hook for prepaid voip radius, RT#4100
+
+2008-10-24 14:22 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm:
+ rlm_perl hook for prepaid voip radius, RT#4100
+
+2008-10-24 12:58 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService/: FreeRadiusVoip.pm:
+ rlm_perl hook for prepaid voip radius, RT#4100
+
+2008-10-24 12:54 ivan
+
+ * fs_selfservice/FS-SelfService/: SelfService.pm,
+ SelfService/FreeRadiusVoip.pm: rlm_perl hook for prepaid voip
+ radius, RT#4100
+
+2008-10-24 12:45 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: rlm_perl hook for
+ prepaid voip radius, RT#4100
+
+2008-10-24 12:13 ivan
+
+ * fs_selfservice/FS-SelfService/: SelfService.pm,
+ SelfService/FreeRadiusVoip.pm: rlm_perl hook for prepaid voip
+ radius, RT#4100
+
+2008-10-23 19:54 ivan
+
+ * FS/: FS/part_export/phone_sqlradius.pm,
+ FS/part_export/sqlradius.pm, bin/freeside-sqlradius-radacctd:
+ untested code to suck in CDRs in from VoIP RADIUS exports,
+ RT#4100
+
+2008-10-23 19:08 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/xmlhttp-cust_main-search.cgi:
+ fixes to facilitate using agent_custid as custnum, RT#4190
+
+2008-10-23 18:45 ivan
+
+ * FS/: FS/svc_phone.pm, bin/freeside-sqlradius-reset: tiny nits for
+ phone RADIUS export: allow freeside-sqlradius-reset to reset a
+ phone_sqlradius export, but only if explicitly specified by
+ exportnum. also fix "Reference found where even-size list
+ expected" warning and junk winding up in radreply table". all
+ this phone_sqlradius stuff is RT#4100
+
+2008-10-23 18:19 ivan
+
+ * FS/FS/: Conf.pm, svc_phone.pm, part_export/phone_sqlradius.pm,
+ part_export/sqlradius.pm: add phone_sqlradius export
+
+2008-10-22 22:20 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: POD cleanup
+
+2008-10-22 11:50 ivan
+
+ * httemplate/view/svc_phone.cgi: fix CDR links
+
+2008-10-21 21:39 ivan
+
+ * FS/FS/part_export/sqlradius.pm, httemplate/search/sqlradius.cgi,
+ httemplate/search/sqlradius.html: fix error on open-ended RADIUS
+ search with Pg, add options for open session search and search on
+ start time, RT#4051
+
+2008-10-21 08:50 jeff
+
+ * fs_selfservice/java/: freeside_login_example.java,
+ freeside_signup_example.java, biz/freeside/SelfService.java:
+ biz.freeside.SelfService class and sample applications
+
+2008-10-18 18:57 ivan
+
+ * httemplate/config/config.cgi: correctly allow re-editing of
+ config options with " in them
+
+2008-10-18 17:38 ivan
+
+ * FS/FS/Schema.pm, FS/FS/agent.pm, httemplate/edit/agent.cgi,
+ httemplate/elements/search-cust_main.html,
+ httemplate/browse/agent.cgi: add a master custnum field to
+ agents, RT#2933 (roundabout)
+
+2008-10-17 18:22 jeff
+
+ * FS/FS/cust_main.pm: sheesh
+
+2008-10-17 18:19 jeff
+
+ * FS/FS/cust_main.pm: doh
+
+2008-10-17 18:08 jeff
+
+ * FS/FS/cust_main.pm: cope with overlapping (but with distinct
+ endpoints) tax areas
+
+2008-10-17 13:01 jeff
+
+ * conf/: invoice_latex, invoice_latexcoupon: address tweaks,
+ assumes a window at least 2.75in or 7cm wide
+
+2008-10-17 11:57 jeff
+
+ * FS/FS/cust_bill.pm: correct erroneous line dupplication on
+ invoices
+
+2008-10-16 15:45 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add an option to use duration instead
+ of billsec to calculate billable time, RT#4147
+
+2008-10-15 22:29 ivan
+
+ * FS/FS/Conf_compat17.pm: sync Conf_compat17.pm
+
+2008-10-15 22:29 ivan
+
+ * FS/FS/Conf.pm, httemplate/misc/payment.cgi,
+ httemplate/search/cust_pay_batch.cgi,
+ httemplate/view/cust_main/payment_history.html: add
+ batch-enable_payby and realtime_disable_payby for better control
+ over hybrid realtime/batch installs; deprecate never-used
+ paymentforcedtobatch, RT#4052
+
+2008-10-15 22:04 ivan
+
+ * httemplate/elements/menu.html: add batch-enable_payby and
+ realtime_disable_payby for better control over hybrid
+ realtime/batch installs; deprecate never-used
+ paymentforcedtobatch, RT#4052
+
+2008-10-14 14:27 ivan
+
+ * FS/FS/Tron.pm: not interested in payment gateway survey just now
+
+2008-10-13 17:50 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/cust_main-import.cgi: add an
+ import format for external services, including next bill date
+ (cust_pkg.bill), RT#4108
+
+2008-10-13 14:58 ivan
+
+ * FS/: bin/freeside-fetch, FS/Conf.pm: Change subject for
+ freeside-fetch emailed reports from "subject" to "Freeside
+ report", and add email_report-subject config to change it.
+ RT#4093
+
+2008-10-12 16:56 jeff
+
+ * conf/invoice_latex: better column widths and easier maintenance
+
+2008-10-12 14:22 jeff
+
+ * conf/invoice_latex: better value for non-broken tetex
+
+2008-10-12 12:43 jeff
+
+ * conf/longtable.sty.patch: check not just for fit, but move the
+ goalposts as well
+
+2008-10-11 17:58 ivan
+
+ * httemplate/browse/cust_main_county.cgi: fix link
+
+2008-10-11 17:54 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/bulk-cust_main_county.html,
+ edit/process/bulk-cust_main_county.html: add a quick bulk tax add
+ tool (eating my own dogfood instead of running a one-off SQL
+ query), RT#4117
+
+2008-10-10 17:32 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: enable suspension notices to an
+ administrator, RT#4083
+
+2008-10-10 16:30 ivan
+
+ * FS/FS/cust_pkg.pm: quick kludge to eliminate exact duplicates in
+ h_labels_short in an effort to reduce the number of "XXX service
+ listing twice on invoice" incidents, RT#3944. still should be
+ possible to fundamentally do better with the function in the
+ first place
+
+2008-10-10 14:30 jeff
+
+ * conf/: invoice_latex, longtable.sty.patch: avoid overprinting
+ remittance coupons
+
+2008-10-10 12:25 ivan
+
+ * FS/FS/cust_bill.pm: add options to auto-generate agent_custid and
+ display it as the customer number, RT#4099
+
+2008-10-09 18:15 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm,
+ FS/FS/UI/Web/small_custview.pm, httemplate/index.html,
+ httemplate/view/cust_main/misc.html, FS/FS/UI/Web.pm,
+ httemplate/edit/cust_main.cgi, httemplate/search/cust_main.cgi:
+ add options to auto-generate agent_custid and display it as the
+ customer number, RT#4099
+
+2008-10-09 13:06 ivan
+
+ * FS/FS/part_export/sqlradius.pm: use Freeradius := attribute for
+ ALL attributes except Password. Crypt-Password, User-Password,
+ Password-With-Header should now use := instead of ==. RT#4051
+
+2008-10-07 16:57 ivan
+
+ * httemplate/misc/: cdr-import.html, process/cdr-import.html: put
+ each CDR web import into a batch
+
+2008-10-07 16:23 ivan
+
+ * FS/FS/cdr/: genband.pm, nextone.pm: also set billsec for nextone
+ CDR format
+
+2008-10-06 17:10 ivan
+
+ * FS/FS/Upgrade.pm: oops, brainfart
+
+2008-10-06 17:09 ivan
+
+ * FS/FS/Upgrade.pm: when setting last_login/last_logout, ensure
+ only accounts actually attached to the export are updated
+
+2008-10-06 15:48 ivan
+
+ * FS/FS/svc_acct.pm: make RADIUS password exports
+ _password_encoding-aware so we export Password-With-Header when
+ necessary
+
+2008-10-06 15:19 ivan
+
+ * FS/FS/svc_acct.pm: make RADIUS password exports
+ _password_encoding-aware so we export Password-With-Header when
+ necessary
+
+2008-10-06 08:28 ivan
+
+ * FS/FS/: cdr.pm, cdr/netcentrex.pm: add initial netcentrex CDR
+ format
+
+2008-10-05 14:36 ivan
+
+ * httemplate/search/pay_batch.cgi: fix links to closed batches,
+ RT#4052
+
+2008-10-05 03:17 ivan
+
+ * httemplate/search/phone_avail.html: adding the start of available
+ phone# search. still needs a menu entry, search options page...
+ RT#3925
+
+2008-10-04 23:07 ivan
+
+ * httemplate/: edit/elements/ApplicationCommon.html,
+ view/cust_main/payment_history/credit.html,
+ view/cust_main/payment_history/payment.html: finish UI
+ improvements wrt refunds: now you have to post a check or cash
+ refund explicitly, no more implicit creation by 'applying'
+ credits. don't show useless application links. don't enable
+ apply button until you pick an invoice/refund. RT#3812
+
+2008-10-04 15:35 ivan
+
+ * FS/FS/: Misc.pm, cust_bill_ApplicationCommon.pm,
+ payinfo_transaction_Mixin.pm, Misc/prune.pm, rate_detail.pm,
+ usage_class.pm, part_event/Action.pm: POD cleanups
+
+2008-10-04 13:55 ivan
+
+ * httemplate/edit/rate_region.cgi: fix inappropriate rounding when
+ editing rates for a whole region
+
+2008-10-04 13:43 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add disable_tollfree option
+
+2008-10-03 17:30 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix recurring box graying out on
+ package customize
+
+2008-10-03 12:41 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add debugging
+
+2008-09-30 14:05 jeff
+
+ * httemplate/: search/report_newtax.cgi, search/cust_bill_pkg.cgi,
+ search/report_newtax.html, elements/menu.html: simple reporting
+ for new tax system
+
+2008-09-30 13:22 jeff
+
+ * FS/FS/: Conf.pm, cust_main.pm: option for no postal fee on
+ one-time charges
+
+2008-09-30 13:17 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/: bill.html, selfservice.cgi:
+ turn on and off postal billing from self-service
+
+2008-09-28 20:41 ivan
+
+ * httemplate/misc/delay_susp_pkg.html: use init_calendar.html
+
+2008-09-26 20:01 jeff
+
+ * httemplate/edit/process/addr_block/manual_flag.cgi,
+ FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/addr_block.pm,
+ FS/FS/svc_broadband.pm, httemplate/browse/addr_block.cgi,
+ httemplate/edit/svc_broadband.cgi: per address block ip auto
+ assignment and auto router selection
+
+2008-09-25 20:54 jeff
+
+ * FS/FS/part_event/Condition/dundate.pm,
+ httemplate/misc/delay_susp_pkg.html, FS/FS/AccessRight.pm,
+ FS/FS/Schema.pm, FS/FS/cust_main.pm, FS/FS/part_bill_event.pm,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/misc/process/delay_susp_pkg.html,
+ httemplate/view/cust_main/packages.html: push out event triggered
+ suspensions
+
+2008-09-25 16:44 jeff
+
+ * FS/FS/cust_main.pm: lost bits of reason
+
+2008-09-24 19:27 jeff
+
+ * FS/FS/part_export/prizm.pm: prizm export improvement for package
+ changes
+
+2008-09-18 16:17 jeff
+
+ * FS/FS/Schema.pm: trade space for time
+
+2008-09-16 08:58 jeff
+
+ * httemplate/search/report_tax.cgi: minor initialization issue
+
+2008-09-15 18:22 jeff
+
+ * httemplate/: edit/part_pkg.cgi, edit/elements/edit.html,
+ elements/tr-select-taxoverride.html,
+ elements/tr-select-taxproduct.html: correct package editor when
+ taxproducts off
+
+2008-09-15 00:18 ivan
+
+ * FS/FS/Schema.pm, FS/FS/Record.pm, FS/FS/phone_avail.pm,
+ FS/FS/part_export/internal_diddb.pm,
+ httemplate/elements/menu.html,
+ httemplate/misc/phone_avail-import.html,
+ httemplate/misc/process/phone_avail-import.html: add internal did
+ database & ability to query for availability, plus upload tool
+
+2008-09-14 17:40 ivan
+
+ * FS/FS/part_export/globalpops_voip.pm: add dry_run option to
+ globalpops_voip export
+
+2008-09-14 13:24 ivan
+
+ * FS/FS/cust_main_invoice.pm: silently strip out leading and
+ trailing spaces from invoicing email addresses instead of
+ throwing an error
+
+2008-09-14 13:20 ivan
+
+ * httemplate/config/: config-process.cgi, config-view.cgi: don't
+ reload the whole page every time a config option is changed,
+ RT#3989
+
+2008-09-14 12:13 ivan
+
+ * httemplate/elements/header.html: default the menu to top in 1.9,
+ still a pref
+
+2008-09-12 15:53 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: just give up and try again tommorow,
+ "1011" came from us not the CDRs anyway, RT#3985
+
+2008-09-12 15:48 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: not enough sleep to support multiple
+ internal_prefixen, RT#3985
+
+2008-09-12 15:38 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: not enough sleep to support multiple
+ internal_prefixen, RT#3985
+
+2008-09-12 15:18 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: grr, TRY to support multiple
+ internal_prefixen, RT#3985
+
+2008-09-12 14:56 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: support multiple internal_prefixen,
+ RT#3985
+
+2008-09-12 14:55 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: support multiple internal_prefixen
+
+2008-09-12 00:58 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: make the max # of indivudal
+ services printed on invoices configurable. RT#3904
+
+2008-09-12 00:07 ivan
+
+ * FS/FS/: part_pkg/flat.pm, part_pkg/voip_cdr.pm, cust_main.pm:
+ don't throw noisy warnings about missing new recur_temporality,
+ RT#3851
+
+2008-09-11 19:28 jeff
+
+ * FS/: FS/Schema.pm, FS/cust_bill.pm, FS/cust_bill_pkg.pm,
+ FS/cust_bill_pkg_display.pm, MANIFEST, FS/cust_main.pm,
+ t/cust_bill_pkg_display.t: re-repurpose cust_bill_pkg
+
+2008-09-11 19:01 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/Conf.pm,
+ httemplate/misc/spool_invoices.cgi,
+ httemplate/search/cust_bill.html: add billco respooling, not
+ re-FTPing, RT#3971
+
+2008-09-11 17:53 ivan
+
+ * FS/FS/part_pkg/: flat.pm, voip_cdr.pm: add recur_temporality to
+ flat.pm, RT#3851
+
+2008-09-11 17:41 ivan
+
+ * FS/FS/cust_main.pm: correct a (fortunately harmless) typo
+
+2008-09-10 01:33 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/Conf.pm,
+ httemplate/misc/ftp_invoices.cgi,
+ httemplate/search/cust_bill.html: add re-FTP reprint,
+ RT#create-me-tommorow-for-enet
+
+2008-09-10 00:55 ivan
+
+ * FS/FS/Schema.pm: better (?) place to put display and taxation
+ data than overloading real line items
+
+2008-09-10 00:32 ivan
+
+ * FS/FS/part_pkg.pm: well, allow things to work for now so work can
+ get done
+
+2008-09-10 00:30 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add upcoming/preceding option,
+ RT#3851
+
+2008-09-10 00:24 ivan
+
+ * FS/FS/cust_main.pm: add upcoming/preceding option, RT#3851
+
+2008-09-09 15:35 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: allow implied primary services to
+ log into selfservice when selfservice_server-primary_only is on
+
+2008-09-09 14:29 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: this should allow implied primary
+ services to log into selfservice when
+ selfservice_server-primary_only is on
+
+2008-09-09 14:04 ivan
+
+ * FS/FS/Upgrade.pm: show which _upgrade_data sub is being run
+
+2008-09-09 01:19 ivan
+
+ * FS/FS/Schema.pm: wtf, cust_pkg_reason has no indices?!
+ _upgrade_data is hosing cpu badly
+
+2008-09-08 19:35 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: doh, forgot to display new options,
+ RT#3838
+
+2008-09-08 19:24 ivan
+
+ * FS/FS/: cdr.pm, cdr/taqua.pm, part_pkg/voip_cdr.pm: additional
+ QIS/Taqua-specific CDR handling details, RT#3838
+
+2008-09-08 14:46 ivan
+
+ * FS/FS/cust_main.pm: oops, debugging got left on by accident
+
+2008-09-08 14:23 ivan
+
+ * httemplate/view/elements/svc_Common.html: also hide fixed+blank
+ fields on service view, RT#3829
+
+2008-09-08 14:02 ivan
+
+ * httemplate/edit/elements/svc_Common.html: completely hide
+ fixed+blank fields, RT#3829
+
+2008-09-07 19:49 ivan
+
+ * httemplate/edit/quick-charge.html: extraneous code cleanup
+
+2008-09-07 19:48 ivan
+
+ * FS/FS/cust_bill_pkg.pm: removing unacceptable display fields from
+ cust_bill_pkg
+
+2008-09-07 19:47 ivan
+
+ * FS/FS/: AccessRight.pm, cust_main.pm: add package invoice details
+ & comments, RT#3810
+
+2008-09-07 19:42 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_pkg_detail.pm, FS/MANIFEST,
+ FS/t/cust_pkg_detail.t, httemplate/pref/pref-process.html,
+ httemplate/pref/pref.html,
+ httemplate/view/cust_main/packages.html, FS/FS.pm,
+ httemplate/edit/cust_pkg_detail.html,
+ httemplate/edit/process/cust_pkg_detail.html, FS/FS/cust_pkg.pm:
+ add package invoice details & comments, RT#3810
+
+2008-09-06 13:54 ivan
+
+ * FS/FS/UI/Web.pm: don't link to customer service view unless the
+ user has the ACL to view the resulting page
+
+2008-09-04 06:29 jeff
+
+ * FS/FS/Upgrade.pm: three lost lines
+
+2008-09-03 20:10 ivan
+
+ * httemplate/edit/invoice_logo.html: ask for an EPS for EPS upload,
+ not incorrectly a PNG
+
+2008-09-03 19:44 ivan
+
+ * httemplate/edit/process/invoice_logo.html: fix invoice uplaoding
+ in light of database config where you absolutely need
+ ->set_binary for swtuf retreived with ->config_binary, RT#3936
+
+2008-09-03 12:08 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/access_right.pm, FS/FS/addr_block.pm,
+ FS/FS/router.pm, httemplate/browse/addr_block.cgi,
+ httemplate/browse/router.cgi, httemplate/browse/svc_acct_pop.cgi,
+ httemplate/edit/allocate.html, httemplate/edit/router.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/process/router.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/addr_block/add.cgi,
+ httemplate/edit/process/addr_block/allocate.cgi,
+ httemplate/edit/process/addr_block/deallocate.cgi,
+ httemplate/edit/process/addr_block/split.cgi,
+ httemplate/elements/menu.html: new access right names
+
+2008-09-03 11:59 jeff
+
+ * FS/FS/: Upgrade.pm, cust_pkg_reason.pm: system only reason update
+ routine
+
+2008-09-02 18:52 ivan
+
+ * httemplate/view/cust_main/payment_history.html: add back ability
+ to post a check/cash refund. be more explicit about it instead of
+ just being a checkbox when posting a credit. RT#3812
+
+2008-09-02 18:46 ivan
+
+ * FS/FS/payby.pm, httemplate/edit/cust_pay.cgi,
+ httemplate/edit/cust_refund.cgi,
+ httemplate/edit/process/cust_refund.cgi,
+ httemplate/elements/init_calendar.html,
+ httemplate/view/cust_refund.html,
+ httemplate/view/cust_main/payment_history/refund.html,
+ FS/FS/AccessRight.pm: add back ability to post a check/cash
+ refund. be more explicit about it instead of just being a
+ checkbox when posting a credit. RT#3812
+
+2008-09-02 08:37 jeff
+
+ * FS/FS/: cust_bill.pm, cust_bill_pkg.pm, ClientAPI/MyAccount.pm:
+ call details in self-service
+
+2008-08-30 14:34 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_main.pm, FS/FS/Report/Table/Monthly.pm,
+ FS/FS/part_pkg/voip_cdr.pm, httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_prepaid_income.cgi,
+ httemplate/search/report_tax.cgi: remove duplicate cust_bill_pkg
+ creation RT#3919
+
+2008-08-29 19:10 jeff
+
+ * FS/FS/cust_bill_pkg.pm, FS/FS/cust_main.pm, FS/FS/tax_rate.pm,
+ httemplate/edit/process/part_pkg.cgi: bug squashing for multiple
+ usage classes
+
+2008-08-29 16:13 ivan
+
+ * FS/FS/part_export/acct_freeside.pm: adding fs-to-fs provisioning
+ of simple accounts for cheepnet, RT#3805
+
+2008-08-29 13:10 ivan
+
+ * httemplate/elements/tr-pkg_svc.html: increase maxlength and size
+ of quantity fields on package edit, for RT#3805
+
+2008-08-28 18:09 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cdr.pm, bin/cdr.sftp_and_import,
+ httemplate/elements/select-cdrbatch.html,
+ httemplate/elements/tr-select-cdrbatch.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html:
+ add CDR batch TFTP feature, RT#3113
+
+2008-08-28 17:45 jeff
+
+ * FS/FS/part_pkg.pm: noise reduction
+
+2008-08-28 17:23 ivan
+
+ * httemplate/elements/: tr-select-taxclass.html,
+ select-taxclass.html: correct nits in tax class selection
+
+2008-08-28 15:00 jeff
+
+ * FS/FS/tax_rate.pm: updates can be completely empty
+
+2008-08-28 14:32 jeff
+
+ * FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ httemplate/browse/part_pkg_taxproduct.cgi,
+ httemplate/edit/part_pkg_taxoverride.html,
+ httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/elements/select-taxoverride.html,
+ httemplate/elements/select-taxproduct.html,
+ httemplate/view/cust_main/packages.html: taxproduct selection for
+ one time charges
+
+2008-08-28 12:09 ivan
+
+ * FS/FS/: Tron.pm, Yori.pm: payment gateway survey
+
+2008-08-28 00:38 jeff
+
+ * httemplate/elements/select-taxoverride.html,
+ httemplate/elements/select-taxproduct.html,
+ httemplate/elements/tr-select-taxoverride.html,
+ httemplate/elements/tr-select-taxproduct.html, FS/FS/Schema.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_pkg/voip_cdr.pm,
+ httemplate/browse/part_pkg_taxproduct.cgi,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_pkg_taxoverride.html,
+ httemplate/edit/process/part_pkg.cgi: multiple usage classes
+ checkpoint
+
+2008-08-26 17:15 ivan
+
+ * FS/FS/cust_main.pm: don't override countrydefault or whatever
+ with a blank value in bulk customer import
+
+2008-08-26 17:05 ivan
+
+ * httemplate/elements/mcp_lint.html: add unchecked vs. ok
+ distinction to lint
+
+2008-08-26 17:00 ivan
+
+ * httemplate/elements/mcp_lint.html: add unchecked vs. ok
+ distinction to lint
+
+2008-08-26 16:53 ivan
+
+ * FS/FS/Tron.pm, httemplate/elements/mcp_lint.html: add unchecked
+ vs. ok distinction to lint
+
+2008-08-26 07:00 rsiddall
+
+ * rpm/freeside.spec: More changes to the self-service RPMs, mostly
+ fixing up paths so the RPM-installed self-service files are not
+ under /usr/local on the remote machine. Also fixed an
+ initialization problem where the system configuration files for
+ Freeside were assumed to be under /etc/default, not
+ /etc/sysconfig
+
+2008-08-25 14:23 ivan
+
+ * httemplate/edit/elements/edit.html: fix package editor showing
+ "all" for pkg class selection
+
+2008-08-25 13:33 ivan
+
+ * FS/FS/part_event/Condition/cust_bill_has_service.pm: fix
+ comparison from svcnum to svcpart
+
+2008-08-24 22:53 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/Report/Table/Monthly.pm,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_prepaid_income.cgi,
+ httemplate/search/report_tax.cgi: correct fallout from duplicate
+ line items
+
+2008-08-24 22:18 jeff
+
+ * httemplate/search/cust_tax_exempt_pkg.cgi: correct fallout from
+ agent virtualizing packages
+
+2008-08-24 15:35 ivan
+
+ * httemplate/elements/checkboxes-table-name.html: add controls to
+ select/unselect/toggle all checkboxes
+
+2008-08-24 14:52 ivan
+
+ * bin/customer-faker: add -k option for pkgpart
+
+2008-08-24 14:49 ivan
+
+ * bin/customer-faker: add -a option for agentnum
+
+2008-08-23 20:41 rsiddall
+
+ * rpm/freeside.spec: Create discrete RPMs for different parts of
+ the self-service interface. Put the default configuration folder
+ in the main freeside RPM.
+
+2008-08-23 14:59 jeff
+
+ * FS/FS/Mason.pm, FS/FS/Schema.pm, FS/FS/usage_class.pm,
+ FS/FS/Setup.pm, FS/FS/Upgrade.pm, FS/FS/rate_detail.pm,
+ FS/t/usage_class.t, httemplate/browse/usage_class.html, FS/FS.pm,
+ FS/MANIFEST, httemplate/browse/rate_detail.html,
+ httemplate/edit/rate_detail.html,
+ httemplate/edit/rate_region.cgi,
+ httemplate/edit/usage_class.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/rate_region.cgi,
+ httemplate/edit/process/usage_class.html,
+ httemplate/elements/menu.html: add usage classes to rate details
+
+2008-08-22 20:29 jeff
+
+ * FS/FS/: Record.pm, cust_main.pm, part_pkg_taxrate.pm,
+ tax_rate.pm: tax data update bug fixes and error message
+ improvements
+
+2008-08-21 20:01 ivan
+
+ * FS/FS/Mason.pm, FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/Tron.pm,
+ FS/FS/cust_svc.pm, FS/FS/cust_svc_option.pm, bin/tron-scan,
+ FS/MANIFEST, FS/t/cust_svc_option.t,
+ httemplate/elements/dashboard-toplist.html,
+ httemplate/elements/mcp_lint.html: the master control program has
+ chosen YOU to serve your system on the game grid
+
+2008-08-21 16:21 ivan
+
+ * FS/: FS/Yori.pm, bin/freeside-yori, MANIFEST: add the client-side
+ reporting for MCP mode
+
+2008-08-21 11:21 jeff
+
+ * bin/import-optigold.pl: use options with proper names
+
+2008-08-19 11:42 ivan
+
+ * FS/FS/svc_acct.pm: beter error messages for duplicate accounts
+
+2008-08-19 04:35 ivan
+
+ * FS/FS/cdr.pm: fix duration on simple/simple2 CDR formats
+
+2008-08-19 03:09 ivan
+
+ * httemplate/edit/process/rate_region.cgi: also don't neglext nxx
+ here
+
+2008-08-19 03:06 ivan
+
+ * FS/FS/rate_region.pm, httemplate/browse/rate_region.html,
+ httemplate/edit/rate_region.cgi: more consistent prefix display,
+ and don't forget nxx'
+
+2008-08-15 12:42 ivan
+
+ * Makefile: install default conf with make create-config too, so it
+ doesn't go missing
+
+2008-08-15 12:26 ivan
+
+ * FS/bin/freeside-setup: allow a full pathname to be specified to
+ freeside-setup for initial configdir
+
+2008-08-14 18:09 jeff
+
+ * FS/FS/tax_class.pm: correct field ordering - invonsequential
+
+2008-08-14 17:41 ivan
+
+ * httemplate/elements/form-file_upload.html: can have a message
+ then a URL too
+
+2008-08-14 04:53 ivan
+
+ * FS/FS/Schema.pm, FS/FS/UID.pm, FS/FS/cust_main.pm,
+ httemplate/elements/progress-init.html,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import.cgi,
+ httemplate/elements/progress-popup.html,
+ httemplate/search/cust_main.html,
+ httemplate/elements/form-file_upload.html,
+ httemplate/misc/file-upload.html,
+ httemplate/elements/file-upload.html: customer import: add
+ progress bar & redirect to a search of the imported customers,
+ #3475
+
+2008-08-14 04:44 ivan
+
+ * FS/FS/tax_rate.pm, httemplate/misc/tax-import.cgi: customer
+ import: add progress bar & redirect to a search of the imported
+ customers, #3475
+
+2008-08-13 18:58 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Record.pm, FS/FS/cust_main.pm,
+ httemplate/elements/menu.html,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import.cgi: import customer
+ from Excel file too
+
+2008-08-13 18:52 ivan
+
+ * FS/FS/svc_Common.pm: tyop
+
+2008-08-13 18:38 ivan
+
+ * httemplate/view/svc_forward.cgi: tyop
+
+2008-08-08 13:29 jeff
+
+ * bin/import-optigold.pl: better opti table relationship following
+
+2008-08-08 11:13 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_main.pm, FS/FS/part_pkg/voip_cdr.pm,
+ conf/invoice_latex: cdrs can be in separate invoice section,
+ after total, summarized inline, with hints for page breaks
+
+2008-08-07 15:30 ivan
+
+ * Makefile: don't generate a new key on install-selfservice if
+ there's already an RSA one either
+
+2008-08-05 23:39 jeff
+
+ * FS/FS/part_pkg/voip_cdr.pm: prevent adding 0 value line items
+
+2008-08-05 21:05 jeff
+
+ * FS/FS/cust_main.pm: fix bug(s) introduced with billing loop
+ refactor
+
+2008-08-02 19:15 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_phone.pm,
+ httemplate/edit/svc_phone.cgi, httemplate/view/svc_phone.cgi: add
+ a name field to svc_phone
+
+2008-08-02 17:54 ivan
+
+ * FS/FS/cdr/simple2.pm: doh, fix regex
+
+2008-08-02 17:26 ivan
+
+ * FS/FS/: Record.pm: attempt to eliminate 'Can't call method
+ "exists" on an undefined value at
+ /usr/local/share/perl/5.8.8/FS/Record.pm line 812.' error on
+ upgrade
+
+2008-08-02 17:20 ivan
+
+ * FS/FS/Record.pm: attempt to eliminate 'Can't call method "exists"
+ on an undefined value at /usr/local/share/perl/5.8.8/FS/Record.pm
+ line 812.' error on upgrade
+
+2008-08-02 16:51 ivan
+
+ * FS/FS/: cdr.pm, cdr/asterisk.pm, cdr/genband.pm,
+ cdr/genband_meetme.pm, cdr/nextone.pm, cdr/openser.pm,
+ cdr/simple.pm, cdr/taqua.pm, cdr/unitel.pm, cdr/simple2.pm: fix
+ 'Can't call method "parse" on an undefined value' error from CDR
+ format refactor
+
+2008-08-01 21:20 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_main.pm,
+ FS/FS/part_pkg/voip_cdr.pm, conf/invoice_html,
+ conf/invoice_latex, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/view/cust_main/billing.html: bundled package
+ presentation improvements
+
+2008-08-01 21:09 jeff
+
+ * FS/FS/: Schema.pm, Upgrade.pm, cust_bill.pm, cust_bill_pkg.pm,
+ cust_bill_pkg_detail.pm, cust_main.pm, part_pkg.pm,
+ part_pkg/voip_cdr.pm: improve CDR usage presentation
+
+2008-08-01 14:41 ivan
+
+ * FS/FS/cust_main.pm: fix receivables report: credits/etc. should
+ be limited by date like before, closes: Bug#3801
+
+2008-08-01 13:21 ivan
+
+ * httemplate/edit/pkg_class.html: categories deserve labels too
+
+2008-07-31 16:32 ivan
+
+ * httemplate/search/report_receivables.cgi: this should fix columns
+ not showing up in receivables report... not surea bout #3801
+ (credits/etc show up in all time periods)
+
+2008-07-31 13:17 ivan
+
+ * httemplate/search/report_receivables.cgi, FS/FS/cust_main.pm: fix
+ receivables report: credits/etc. should be limited by date like
+ before, closes: Bug#3801
+
+2008-07-30 19:35 ivan
+
+ * httemplate/misc/delete-customer.cgi: fix error on customer
+ deletion
+
+2008-07-30 15:10 ivan
+
+ * FS/FS/cdr/: nextone.pm, nt.pm: rename nt to nextone
+
+2008-07-29 13:00 jeff
+
+ * FS/FS/cust_bill.pm: correct amount for new charges total on
+ sectioned invoices
+
+2008-07-29 10:29 rsiddall
+
+ * rpm/freeside.spec: Self-Service files were reorganized; changed
+ the way we copy them into the buildroot.
+
+2008-07-24 09:40 jeff
+
+ * FS/FS/cust_bill.pm: ensure invoice line items are delivered in
+ line number order
+
+2008-07-23 07:41 jeff
+
+ * httemplate/edit/tax_rate.html: add disabled column to new tax
+ rates, false laziness elimination, and bug fixes - closes #3566
+
+2008-07-23 07:36 jeff
+
+ * FS/FS/Schema.pm, FS/FS/tax_rate.pm,
+ httemplate/browse/tax_rate.cgi,
+ httemplate/misc/enable_or_disable_tax.html,
+ httemplate/misc/process/enable_or_disable_tax.html: add disabled
+ column to new tax rates, false laziness elimination, and bug
+ fixes - closes #3566
+
+2008-07-22 01:33 ivan
+
+ * FS/FS/part_export/phone_shellcommands.pm: freepbx modification
+ command
+
+2008-07-21 21:59 ivan
+
+ * httemplate/view/svc_phone.cgi: add "incoming CDRs" link to phone#
+ view also
+
+2008-07-21 15:34 ivan
+
+ * FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ fs_selfservice/FS-SelfService/cgi/success.html: svc_phone signup
+
+2008-07-21 14:23 ivan
+
+ * Makefile: oops, don't inadvertantly switch default db type
+
+2008-07-21 12:09 ivan
+
+ * FS/FS/Conf.pm, httemplate/elements/select-did.html,
+ FS/FS/ClientAPI/MasonComponent.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ fs_selfservice/FS-SelfService/cgi/misc/areacodes.cgi,
+ fs_selfservice/FS-SelfService/cgi/misc/exchanges.cgi,
+ fs_selfservice/FS-SelfService/cgi/images/cross.png,
+ fs_selfservice/FS-SelfService/cgi/images/wait-orange.gif,
+ fs_selfservice/FS-SelfService/cgi/misc/phonenums.cgi: signup
+ w/globalpops DID selection via mason components pass-through
+
+2008-07-21 11:58 ivan
+
+ * FS/MANIFEST, htetc/handler.pl, FS/FS/CGI.pm, FS/FS/Mason.pm,
+ FS/FS/Mason/Request.pm, Makefile: add framework for running Mason
+ components standalone
+
+2008-07-21 03:42 ivan
+
+ * FS/FS/svc_phone.pm: generate a SIP password if it is blank
+
+2008-07-18 15:31 ivan
+
+ * httemplate/search/report_rt_transaction.html: missing closing
+ FORM tag
+
+2008-07-18 15:30 ivan
+
+ * httemplate/elements/popup_link.html: add target param, i thought
+ this was needed for something...
+
+2008-07-18 15:29 ivan
+
+ * bin/bind.import: add -e option to bind.import (now to actually
+ implement it)
+
+2008-07-18 15:28 ivan
+
+ * FS/FS/rate_region.pm: show NXX is US if applicable
+
+2008-07-18 15:28 ivan
+
+ * FS/FS/: h_cust_svc.pm, part_export.pm, part_pkg.pm, svc_acct.pm,
+ UI/Web.pm: some random cleanups
+
+2008-07-18 15:27 ivan
+
+ * FS/FS/Record.pm: add no_check_foreign kludge for gigantic rate
+ imports
+
+2008-07-18 15:26 ivan
+
+ * FS/MANIFEST: add part_pkg_link to MANIFEST
+
+2008-07-17 16:55 ivan
+
+ * FS/FS/: cdr.pm, cdr/asterisk.pm, cdr/genband.pm,
+ cdr/genband_meetme.pm, cdr/nt.pm, cdr/openser.pm, cdr/simple.pm,
+ cdr/taqua.pm, cdr/unitel.pm: CDR updates; modularize CDR import
+ formats; add formats for OpenSER, Genband/Tekelec, and "NT"
+
+2008-07-16 16:55 ivan
+
+ * httemplate/search/svc_acct.cgi: fix account search by time
+ remaining to deal with situations w/o a recurring amount
+
+2008-07-15 16:25 ivan
+
+ * FS/FS/Upgrade.pm: where in the world is $DBI::errstr
+
+2008-07-15 16:18 ivan
+
+ * FS/FS/Upgrade.pm: parens help alot
+
+2008-07-15 16:17 ivan
+
+ * FS/FS/Upgrade.pm: report errors connecting to sqlradius dbs on
+ upgrade
+
+2008-07-15 13:56 ivan
+
+ * FS/FS/cust_main.pm: prevent inactive customers from showing up in
+ reports of cancelled customers
+
+2008-07-14 18:19 ivan
+
+ * FS/FS/part_export/phone_shellcommands.pm: add warning about
+ concurrency in FreePBX
+
+2008-07-14 16:59 ivan
+
+ * httemplate/view/cust_main/notes.html: fuck embedded iframes and
+ their stupid display problems with scrolling. also make the
+ gridding more consistent
+
+2008-07-14 16:08 ivan
+
+ * httemplate/: edit/process/cust_main_note.cgi, view/cust_main.cgi,
+ view/cust_main/notes.html: fuck embedded iframes and their stupid
+ display problems with scrolling. also make the gridding more
+ consistent
+
+2008-07-10 11:48 ivan
+
+ * httemplate/view/cust_main/packages.html: fix variable scoping
+ issues preventing customer view page from coing up
+
+2008-07-09 20:16 jeff
+
+ * FS/FS/cust_bill.pm: restore line item date ranges
+
+2008-07-09 13:37 ivan
+
+ * FS/FS/part_pkg.pm: should avoid spurious uninitialized value
+ warnings on upgrade
+
+2008-07-09 13:33 ivan
+
+ * FS/FS/svc_Common.pm: perl vs SQL brainfart
+
+2008-07-09 12:45 ivan
+
+ * httemplate/docs/license.html: fix famfamfam link
+
+2008-07-09 12:35 ivan
+
+ * FS/FS/svc_Common.pm: service searching should be case-insensitive
+ now
+
+2008-07-08 20:40 ivan
+
+ * httemplate/search/: cust_tax_exempt.cgi: helpful to see when
+ exemptions were inserted?
+
+2008-07-08 20:27 ivan
+
+ * httemplate/search/cust_tax_exempt.cgi: need the exemptnum...
+
+2008-07-08 20:18 ivan
+
+ * httemplate/search/: cust_tax_exempt.cgi, cust_tax_exempt.html:
+ search legacy tax exemptions by customer status
+
+2008-07-08 19:30 ivan
+
+ * httemplate/edit/process/part_pkg.cgi: don't require an agent type
+ to be specified when editing a disabled package
+
+2008-07-07 19:19 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: fix line-item reports on
+ taxclass-less regions
+
+2008-07-07 19:01 ivan
+
+ * FS/FS/cust_main_county.pm, httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/report_tax.cgi: fix line-item reports on
+ taxclass-less regions
+
+2008-07-07 17:35 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: order by number for line
+ items with the same datestamp
+
+2008-07-07 17:20 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: order line item reports by
+ date
+
+2008-07-07 16:47 ivan
+
+ * httemplate/search/report_tax.cgi: should be a proper fix for edge
+ cases where you have taxclass and empty-taxclass rates for a
+ region, whew
+
+2008-07-07 14:38 ivan
+
+ * FS/FS/Record.pm: SQL_FLOAT is probably unnecessary and causes
+ probelms on old (v1.x?) DBD::Pg
+
+2008-07-07 14:18 ivan
+
+ * FS/FS/Record.pm: eek, hopefully fix problems caused by adding
+ debugging of bind_param statements
+
+2008-07-07 14:07 ivan
+
+ * FS/FS/Record.pm: add debugging of bind_param statements
+
+2008-07-07 12:50 ivan
+
+ * FS/FS/Record.pm: add debugging of bind_param statements
+
+2008-07-03 16:23 ivan
+
+ * httemplate/view/cust_main/packages.html: fix bad sub names in
+ forward-port
+
+2008-07-02 21:19 ivan
+
+ * FS/FS/Record.pm: should FINALLY get binding correctly in light of
+ regression caused by get_real_fields refactor
+
+2008-07-02 21:12 ivan
+
+ * FS/FS/Record.pm: this should non-"=" searches on fields that
+ require SQL type binding...
+
+2008-07-02 21:00 ivan
+
+ * FS/FS/cust_pkg.pm: correct hash vs hashref brainfart on
+ "forward-port", i guess
+
+2008-07-02 20:57 ivan
+
+ * FS/FS/Record.pm: real should be bound to SQL_FLOAT Like float4...
+ 1.7? not touching it unless it breaks :)
+
+2008-07-01 19:55 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix errors paying with an on-file
+ card through self-service
+
+2008-07-01 00:02 jeff
+
+ * FS/FS/cust_pkg.pm: you shouldn't keep 'em separated
+
+2008-06-30 22:01 jeff
+
+ * httemplate/misc/unadjourn_pkg.cgi,
+ httemplate/misc/unexpire_pkg.cgi, FS/FS/Schema.pm,
+ FS/FS/cust_pkg.pm, FS/FS/cust_pkg_reason.pm,
+ FS/FS/part_export/shellcommands.pm,
+ FS/FS/part_export/sqlradius.pm,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/misc/process/cancel_pkg.html,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/view/cust_main/packages.html: correct internal reason
+ searching, prevent interleaved suspend/cancel/expire/adjourn,
+ backporting and refactoring
+
+2008-06-30 17:11 ivan
+
+ * FS/FS/svc_phone.pm: that should fix new sip_password field, whew
+
+2008-06-30 17:07 ivan
+
+ * FS/FS/svc_phone.pm: that should fix the new sip_password field, i
+ hope
+
+2008-06-30 17:00 ivan
+
+ * httemplate/view/svc_phone.cgi: add sip pw display
+
+2008-06-30 16:56 ivan
+
+ * FS/FS/Schema.pm, FS/FS/svc_phone.pm,
+ FS/FS/part_export/phone_shellcommands.pm,
+ httemplate/edit/svc_phone.cgi: add sip pw field
+
+2008-06-30 01:01 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: signup.cgi, signup.html:
+ checkpoint signup work
+
+2008-06-30 01:00 ivan
+
+ * FS/FS/part_export/phone_shellcommands.pm: reload after adding
+ extensions
+
+2008-06-29 13:53 ivan
+
+ * FS/FS/svc_phone.pm, FS/FS/part_export/globalpops_voip.pm,
+ httemplate/elements/select-phonenum.html,
+ httemplate/elements/tr-select-did.html: globalPOPs provisioning
+
+2008-06-28 21:33 jeff
+
+ * httemplate/view/cust_main/packages.html: spurious 'suspended'
+
+2008-06-28 17:41 ivan
+
+ * FS/: MANIFEST, FS.pm, t/phone_avail.t, FS/Schema.pm,
+ FS/phone_avail.pm, FS/part_export/globalpops_voip.pm:
+ state->areacode caching,
+
+2008-06-28 16:03 ivan
+
+ * httemplate/images/wait-orange.gif, FS/FS/part_svc.pm,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/elements/input-text.html,
+ httemplate/elements/select-areacode.html,
+ httemplate/elements/select-did.html,
+ httemplate/elements/select-exchange.html,
+ httemplate/elements/select-phonenum.html,
+ httemplate/elements/select-state.html,
+ httemplate/elements/tr-input-text.html,
+ httemplate/elements/tr-select-did.html,
+ httemplate/misc/areacodes.cgi, httemplate/misc/exchanges.cgi,
+ httemplate/misc/phonenums.cgi, FS/FS/Record.pm,
+ FS/FS/part_export/globalpops_voip.pm,
+ httemplate/edit/svc_phone.cgi,
+ httemplate/edit/cust_main/select-state.html: get DIDs from
+ globalpops
+
+2008-06-28 12:25 jeff
+
+ * httemplate/elements/tr-checkboxes-table.html,
+ httemplate/elements/checkboxes-table.html,
+ httemplate/elements/menu.html,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-table.html, FS/FS/AccessRight.pm,
+ FS/FS/addr_block.pm, FS/FS/router.pm, FS/FS/svc_broadband.pm,
+ httemplate/browse/addr_block.cgi, httemplate/browse/router.cgi,
+ httemplate/browse/svc_acct_pop.cgi,
+ httemplate/edit/allocate.html, httemplate/edit/router.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_broadband.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/router.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/svc_broadband.cgi,
+ httemplate/edit/process/addr_block/add.cgi,
+ httemplate/edit/process/addr_block/allocate.cgi,
+ httemplate/edit/process/addr_block/deallocate.cgi,
+ httemplate/edit/process/addr_block/split.cgi,
+ httemplate/edit/process/elements/process.html: agent virtualize
+ address blocks and routers
+
+2008-06-27 01:53 ivan
+
+ * FS/FS/Conf.pm, FS/FS/ClientAPI/Signup.pm, fs_selfservice/DEPLOY,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.html: adding
+ signup_server-service config
+
+2008-06-27 00:18 ivan
+
+ * FS/FS/part_export/globalpops_voip.pm: globalpops_voip export
+ compilation fixes
+
+2008-06-26 17:27 ivan
+
+ * FS/FS/part_export/globalpops_voip.pm: commiting globalpops export
+ start. stupid power failure.
+
+2008-06-26 14:55 ivan
+
+ * FS/FS/part_export/phone_shellcommands.pm: adding
+ phone_shellcommands with preliminary FreePBX integration commands
+
+2008-06-25 13:14 ivan
+
+ * httemplate/search/report_receivables.cgi: optimize total query in
+ receivables report. very significant speedup for large DBs
+
+2008-06-25 13:11 ivan
+
+ * FS/FS/: cust_main.pm, cust_bill.pm: POD updates
+
+2008-06-25 11:14 ivan
+
+ * FS/FS/Upgrade.pm: don't show error messages about FreesideStatus
+ index already existing either
+
+2008-06-24 17:29 ivan
+
+ * htetc/handler.pl, FS/FS/Record.pm: finish adding
+ str2time_sql_closing
+
+2008-06-24 11:50 ivan
+
+ * conf/invoice_html: i think we need a $ there???
+
+2008-06-24 09:39 jeff
+
+ * FS/FS/: Conf.pm, Conf_compat17.pm, cust_main.pm: postal invoice
+ fees
+
+2008-06-23 19:09 ivan
+
+ * FS/FS/part_export/sqlradius.pm: prevent decrementing
+ time/bandwidth for old RADIUS records
+
+2008-06-23 15:59 ivan
+
+ * bin/cdr.import: hahahd doh, we're in perl
+
+2008-06-23 15:11 ivan
+
+ * bin/cdr.import: add quick command line too for CDR imports
+
+2008-06-23 09:46 jeff
+
+ * conf/invoice_html: this is what it was for
+
+2008-06-23 08:36 jeff
+
+ * FS/FS/cust_bill.pm, conf/invoice_html, conf/invoice_latex:
+ invoice service address modifications
+
+2008-06-22 19:50 ivan
+
+ * conf/invoice_html: wtf was this for in the first place then
+
+2008-06-22 19:48 ivan
+
+ * conf/invoice_html: this seems to match the latex templates more
+ accurately, rather than push the sub-totals out into their own
+ column
+
+2008-06-22 19:35 ivan
+
+ * conf/invoice_html: fix leaking colspan in totals on sectioned
+ invoices
+
+2008-06-22 17:41 ivan
+
+ * FS/FS/cust_tax_exempt.pm: adding report on legacy tax exemptions
+ to assist in enet migraiton
+
+2008-06-22 17:37 ivan
+
+ * httemplate/search/cust_tax_exempt.cgi: legacy tax exemption
+ report fix, no _date
+
+2008-06-22 17:34 ivan
+
+ * httemplate/search/cust_tax_exempt.cgi: adding report on legacy
+ tax exemptions to assist in enet migraiton
+
+2008-06-19 22:47 jeff
+
+ * FS/FS/cust_bill.pm: extra values for invoices
+
+2008-06-19 20:36 ivan
+
+ * FS/FS/: cdr.pm, part_pkg/voip_cdr.pm: VoxLineSystems are lying
+ scum who charged back their customization work and then used the
+ software anyway
+
+2008-06-18 20:18 jeff
+
+ * FS/FS/pkg_category.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/part_pkg.pm, FS/FS/pkg_class.pm, FS/t/pkg_category.t,
+ FS/FS.pm, httemplate/browse/pkg_category.html,
+ httemplate/browse/pkg_class.html,
+ httemplate/edit/pkg_category.html,
+ httemplate/edit/pkg_class.html,
+ httemplate/edit/process/pkg_category.html, FS/MANIFEST,
+ htetc/handler.pl, httemplate/edit/elements/edit.html,
+ httemplate/elements/menu.html: package categories (meta package
+ classes) and grouping invoices by them
+
+2008-06-18 14:18 ivan
+
+ * FS/FS/Misc.pm: fix regression caused by use of IPC::Run to run
+ pslatex: send STDOUT and STDERR from pslatex to /dev/null, we
+ don't want them
+
+2008-06-18 12:24 ivan
+
+ * httemplate/edit/part_pkg.cgi: s helps alot
+
+2008-06-18 12:09 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix setup/recur fees on cloning
+ (customizing) package definitions w/new editor
+
+2008-06-18 11:50 jeff
+
+ * httemplate/: browse/tax_rate.cgi,
+ misc/enable_or_disable_tax.html,
+ misc/process/enable_or_disable_tax.html: allow enabling and
+ disabling if tax_rate rows in groups (RT 3566)
+
+2008-06-17 22:22 ivan
+
+ * FS/FS/cust_main.pm: %statuscolor is either a global or a my var,
+ make up your mind
+
+2008-06-17 17:49 ivan
+
+ * FS/FS/part_virtual_field.pm: this module, also, has no need to
+ import qsearch/qsearchs, and is causing dependency loop problems
+ (Record->part_virtual_field->Record)
+
+2008-06-17 17:46 ivan
+
+ * FS/FS/Record.pm: hopefully finally fix the dependency loops bs...
+ as simple as Record->Conf->Record here
+
+2008-06-17 17:42 ivan
+
+ * FS/FS/Msgcat.pm: REALLY, don't use FS::Conf from Msgcat until
+ runtime... should hopefully FINALLY eliminate the
+ Record->Msgcat->Conf->Record loop
+
+2008-06-17 17:36 ivan
+
+ * FS/FS/msgcat.pm: msgcat.pm doesn't actually need
+ qsearch/qsearchs... hopefully this is the last of the weird
+ dependency loops (this one is Record->Msgcat->msgcat->Record)
+
+2008-06-17 17:27 ivan
+
+ * FS/FS/Msgcat.pm: fix dependency loop problem with database
+ config, hopefully? (Record->Msgcat->Conf->Record)
+
+2008-06-17 17:10 ivan
+
+ * FS/FS/conf.pm: conf.pm doesn't actually need qsearch/qsearchs -
+ hopefully this will solve the weird circular dependency issue
+ (Record->Msgcat->Conf->conf->Record)
+
+2008-06-17 17:05 ivan
+
+ * httemplate/misc/xmlhttp-cust_main-address_standardize.html: turn
+ off debugging for address standardization
+
+2008-06-17 16:57 ivan
+
+ * httemplate/edit/part_pkg.cgi: don't lose the pricing on package
+ cloning w/new package editor
+
+2008-06-17 12:29 jeff
+
+ * conf/invoice_latex: fixup damage from quantity addition
+
+2008-06-16 20:35 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/edit/invoice_template.html,
+ FS/FS/ConfDefaults.pm, FS/FS/Misc.pm, FS/FS/cust_main.pm,
+ htetc/handler.pl, httemplate/elements/htmlarea.html,
+ httemplate/misc/email-customers.html,
+ httemplate/misc/process/email-customers.html,
+ httemplate/search/cust_main.html: finish adding a feature to
+ easily list all email addresses for an agent & send them email
+
+2008-06-16 20:13 jeff
+
+ * conf/invoice_latex: not forgetting to add fax and old customer id
+
+2008-06-16 18:43 ivan
+
+ * httemplate/edit/process/cust_main.cgi: have agent_custid editing
+ now
+
+2008-06-16 06:36 jeff
+
+ * bin/import-optigold.pl: catch one more customer
+
+2008-06-15 23:53 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi: add (with config)
+ ability to edit agent_custid
+
+2008-06-15 18:32 jeff
+
+ * bin/import-optigold.pl: ugh; cast about for svc/pkg linkages
+
+2008-06-12 16:15 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: fix visual regression not
+ displaying package and comment on date editing
+
+2008-06-12 14:53 ivan
+
+ * httemplate/: edit/process/elements/process.html,
+ view/cust_main/packages.html, edit/part_pkg.cgi,
+ edit/elements/edit.html, edit/process/part_pkg.cgi: fix cloning
+ w/new package editor
+
+2008-06-12 09:56 jeff
+
+ * bin/import-optigold.pl: date fixups
+
+2008-06-12 08:55 jeff
+
+ * bin/import-optigold.pl: import services from service providing
+ servers
+
+2008-06-10 16:24 ivan
+
+ * FS/FS/: Upgrade.pm: start of better error reporting for RADIUS
+ upgrade errors
+
+2008-06-10 10:39 ivan
+
+ * FS/FS/Misc.pm: turn off debugging that got left on by accident
+
+2008-06-09 19:12 ivan
+
+ * httemplate/edit/process/: cust_credit_refund.cgi,
+ cust_pay_refund.cgi: fix up application of things to refunds,
+ RT#3606/RT#3545
+
+2008-06-09 11:32 ivan
+
+ * FS/FS/cdr.pm: remove name from voxlinesystems2, really
+
+2008-06-05 15:44 ivan
+
+ * FS/FS/cdr.pm: add am/pm to voxlinesystems2 display format, remove
+ name, revsere src/dst
+
+2008-06-05 13:09 ivan
+
+ * FS/FS/: cust_main.pm, part_pkg/flat.pm: fix one-time charge
+ quantities &
+
+2008-06-05 12:44 ivan
+
+ * FS/FS/: cust_svc.pm, part_pkg/voip_cdr.pm: disable_src fixes
+
+2008-06-05 12:29 ivan
+
+ * FS/FS/Schema.pm: unit pricing didn't exist before, so it can be
+ NULL
+
+2008-06-05 12:25 ivan
+
+ * FS/FS/part_pkg/flat.pm: implement quantity charging for setup
+ fees
+
+2008-06-05 12:24 ivan
+
+ * FS/FS/cdr.pm, FS/FS/cust_main.pm, httemplate/search/cdr.html,
+ httemplate/view/svc_phone.cgi, httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi: voxlinesystems CDRs and
+ quantity bs
+
+2008-06-05 12:06 jeff
+
+ * conf/invoice_html: dash removal
+
+2008-06-05 10:05 jeff
+
+ * conf/invoice_html: fix unitprice/posttotal nit
+
+2008-06-05 09:56 jeff
+
+ * conf/invoice_html: fix unitprice/section nit
+
+2008-06-05 09:51 jeff
+
+ * conf/invoice_html: correct bogus porting
+
+2008-06-05 05:42 jeff
+
+ * conf/invoice_latex: replace lost braces
+
+2008-06-05 03:36 ivan
+
+ * FS/FS/cust_main.pm, httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html: add customer status to
+ adv. customer report, template customer search for future use in
+ emailing notices, RT#2731
+
+2008-06-05 03:34 ivan
+
+ * FS/FS/: cust_pkg.pm: docs for search_sql
+
+2008-06-05 01:54 ivan
+
+ * httemplate/search/report_cust_bill.html: minor invoice report UI
+
+2008-06-04 22:06 ivan
+
+ * FS/FS/cust_pay.pm, httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_pay_refund.cgi,
+ httemplate/view/cust_main/payment_history/credit.html,
+ httemplate/view/cust_main/payment_history/payment.html,
+ httemplate/edit/cust_credit_refund.cgi,
+ httemplate/edit/elements/ApplicationCommon.html,
+ httemplate/edit/process/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/cust_credit_refund.cgi,
+ httemplate/edit/process/cust_pay_refund.cgi,
+ httemplate/edit/process/elements/ApplicationCommon.html: payment
+ and credit applications have separate "apply to refund" choices
+ now, and no auto-refund choice in the invoice dropdown. RT#3545
+
+2008-06-04 15:44 jeff
+
+ * FS/FS/cust_bill.pm: moar tyop
+
+2008-06-04 15:42 jeff
+
+ * FS/FS/cust_bill.pm: tyop
+
+2008-06-04 11:50 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/misc/fax-invoice.cgi: this should
+ fix the random "HylaFax support has not been configured" error,
+ caused by cust_bill->fax getting called instead of cust_main->fax
+ field
+
+2008-06-04 11:40 jeff
+
+ * conf/invoice_html: more voxline invoice formatting
+
+2008-06-04 11:05 jeff
+
+ * FS/FS/cust_bill.pm: voxline invoice formatting
+
+2008-06-04 10:57 jeff
+
+ * FS/FS/cdr.pm, FS/FS/cust_bill_pkg.pm, FS/FS/part_pkg/voip_cdr.pm,
+ conf/invoice_html, conf/invoice_latex, FS/FS/Conf.pm,
+ FS/FS/Conf_compat17.pm: voxline invoice formatting
+
+2008-06-04 06:28 jeff
+
+ * FS/FS/: Record.pm, cust_main.pm, tax_rate.pm: tax on tax
+
+2008-06-04 06:26 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Conf_compat17.pm, FS/FS/cust_bill.pm,
+ conf/invoice_latex: service address on invoice
+
+2008-06-03 14:06 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/Schema.pm, FS/FS/cust_bill_pkg.pm,
+ FS/FS/cust_pkg.pm, httemplate/search/cust_pkg.cgi,
+ httemplate/view/cust_main/packages.html: very basic start at
+ adding quantities
+
+2008-06-02 11:59 ivan
+
+ * FS/FS/payinfo_transaction_Mixin.pm: fix payinfo_transaction
+
+2008-06-02 11:31 ivan
+
+ * FS/FS/UI/Web/small_custview.pm: fix ntable calls
+
+2008-06-02 10:58 ivan
+
+ * httemplate/misc/process/timeworked.html: fix error apply
+ fractional seconds
+
+2008-06-02 10:14 ivan
+
+ * FS/FS/UI/Web/small_custview.pm: doh!
+
+2008-06-02 10:06 ivan
+
+ * FS/FS/CGI.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/UI/Web/small_custview.pm, htetc/handler.pl,
+ rt/lib/RT/URI/freeside/Internal.pm,
+ rt/lib/RT/URI/freeside/XMLRPC.pm: badly placed small_custview all
+ of a sudden causing fatal errors?! wtf
+
+2008-06-02 04:16 jeff
+
+ * conf/invoice_latex, conf/invoice_latexcoupon, FS/FS/Conf.pm,
+ FS/FS/Conf_compat17.pm, FS/FS/cust_bill.pm: typeset tear-off
+ remittance coupon
+
+2008-06-01 19:47 ivan
+
+ * FS/FS/Misc.pm: and batchmode was probably right
+
+2008-06-01 19:45 ivan
+
+ * FS/FS/Misc.pm: yow, don't want everything to waitt until the
+ timeout
+
+2008-06-01 19:16 ivan
+
+ * FS/FS/: cust_bill.pm, Misc.pm: use IPC::Run to run pslatex & add
+ a timeout, this should prevent hanging on template errors
+
+2008-06-01 15:48 ivan
+
+ * FS/FS/cust_pay.pm, FS/FS/cust_refund.pm, FS/FS/payinfo_Mixin.pm,
+ FS/FS/payinfo_transaction_Mixin.pm,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/search/elements/cust_pay_or_refund.html,
+ httemplate/view/cust_refund.html,
+ httemplate/view/cust_main/payment_history/credit.html,
+ httemplate/view/cust_main/payment_history/invoice.html,
+ httemplate/view/cust_main/payment_history/payment.html,
+ httemplate/view/cust_main/payment_history/refund.html,
+ httemplate/view/cust_main/payment_history/voided_payment.html:
+ refactor payment history slightly, add refund receipts, have
+ "unapplied" refunds show like other unapplied/open things,
+ RT#3545
+
+2008-06-01 00:08 ivan
+
+ * FS/FS/cust_pay.pm: show "Check #" on payment receipts instead of
+ "Billing #"
+
+2008-05-31 22:43 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_bill_ApplicationCommon.pm,
+ httemplate/view/cust_main/payment_history.html: add date to
+ "applied to Invoice#" messages in history
+
+2008-05-31 20:19 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: fix line item report for
+ agent-virtualized packages, clean up sloppy $where
+ stringification, hard agent virtualization
+
+2008-05-31 19:19 jeff
+
+ * bin/import-optigold.pl: umm.. right.. really do some on-demand
+ stuff
+
+2008-05-31 18:23 ivan
+
+ * htetc/handler.pl: depend on CGI.pm 3.29 to fix RT attachment
+ problems
+
+2008-05-31 17:11 ivan
+
+ * FS/FS/cust_bill.pm: fix problems when service definition names
+ contain chars that need to be latex escaped
+
+2008-05-31 16:50 jeff
+
+ * bin/import-optigold.pl: guess at on demand billing, link
+ pre-existing services
+
+2008-05-31 10:54 ivan
+
+ * htetc/handler.pl: add an explicit use for RT's not-well-declared
+ dependency on CSS::Squish 0.06
+
+2008-05-31 07:49 jeff
+
+ * FS/FS/cust_bill.pm, conf/invoice_html, conf/invoice_latex:
+ invoice cosmetic improvements
+
+2008-05-29 21:04 ivan
+
+ * httemplate/edit/part_bill_event.cgi: 1.7 sucks. but people are
+ still going to be editing old-style invoice events for a little
+ while more yet, so space them out better (so options don't run
+ together)
+
+2008-05-29 20:55 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/edit/part_bill_event.cgi: 1.7
+ sucks. add a "balance over" option to the 1.7 style
+ agent-specific invoice send event
+
+2008-05-29 18:38 ivan
+
+ * FS/FS/Misc/prune.pm: fix a missing semicolon bug only triggered
+ when running prune_applications not in debug mode...
+
+2008-05-29 18:34 ivan
+
+ * FS/FS/Upgrade.pm: don't print out warnings about SQL RADIUS
+ FreesideStatus every time either. really?
+
+2008-05-29 18:33 ivan
+
+ * FS/bin/freeside-upgrade: don't print out the cust_credit_refund
+ pruning every time
+
+2008-05-29 18:28 ivan
+
+ * FS/bin/freeside-upgrade: don't print out the cust_credit_refund
+ pruning every time
+
+2008-05-29 18:02 ivan
+
+ * FS/FS/cust_pay_pending.pm: clean up any stray/old
+ cust_pay_pending records causing problems
+
+2008-05-29 17:53 ivan
+
+ * FS/FS/: Upgrade.pm, cust_pay_pending.pm: clean up any stray/old
+ cust_pay_pending records causing problems
+
+2008-05-28 17:11 ivan
+
+ * httemplate/graph/money_time.cgi: line things up better on the
+ 12mo report (prevent labels from taking up most of the graph
+
+2008-05-28 03:45 ivan
+
+ * htetc/handler.pl: mailgate realiability fix: don't bomb out when
+ FS dbdef hasn't been initialized yet (& need to import
+ adminsuidsetup)
+
+2008-05-28 03:41 ivan
+
+ * htetc/: handler.pl: mailgate realiability fix: don't bomb out
+ when FS dbdef hasn't been initialized yet
+
+2008-05-28 03:14 ivan
+
+ * htetc/handler.pl: mailgate realiability fix: don't bomb out when
+ FS dbdef hasn't been initialized yet
+
+2008-05-19 20:52 ivan
+
+ * FS/FS/Upgrade.pm: automatically create an index on the new
+ radacct.FreesideStatus column
+
+2008-05-19 15:31 jeff
+
+ * conf/invoice_latex, FS/FS/cust_bill_pkg.pm: fix broken pagenation
+
+2008-05-19 11:50 ivan
+
+ * FS/FS/Conf.pm: a better link to the T:T docs
+
+2008-05-18 21:29 ivan
+
+ * FS/FS/Schema.pm: add a key on ( history_action, $primary_key ) to
+ the h_ tables. this should speed up the cust_pay upgrade??
+
+2008-05-18 21:07 ivan
+
+ * FS/FS/cust_bill_pay.pm, FS/FS/payinfo_Mixin.pm,
+ FS/FS/cust_credit_refund.pm, FS/FS/cust_refund.pm,
+ FS/FS/payby.pm, FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/money_time.cgi,
+ httemplate/search/cust_bill_pay.html,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_credit_refund.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/cust_refund.html,
+ httemplate/search/elements/cust_pay_or_refund.html: make net
+ receipts clickable... and netreceipts != cashflow, really, so
+ separate those concepts, and cashflow gets gross & net variants.
+ also add gross/net refunds. #3012
+
+2008-05-18 20:51 jeff
+
+ * conf/invoice_latex: fix latex template bogosity
+
+2008-05-18 15:57 ivan
+
+ * FS/FS/Upgrade.pm: fix auto sqlradius upgrade: module
+ include/import
+
+2008-05-18 15:54 ivan
+
+ * FS/FS/part_export/sqlradius.pm: oops, fix minor refactoring of
+ auto sqlradius upgrade
+
+2008-05-18 15:53 ivan
+
+ * FS/bin/freeside-upgrade: fix the auto sqlradiusupgrade
+
+2008-05-18 15:42 ivan
+
+ * FS/FS/Upgrade.pm, FS/bin/freeside-sqlradius-radacctd,
+ FS/bin/freeside-upgrade, FS/FS/part_export/sqlradius.pm,
+ init.d/freeside-init: on upgrade, automatically seed from
+ sqlradius databases, and start freeside-sqlradius-radacctd by
+ default
+
+2008-05-17 23:50 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/process/access_group.html: add a
+ config value for disabling the ACLs... this should be good for a
+ demo in 1.7, 1.9 will need some way to disable ACL changes from
+ OUTSIDE the db
+
+2008-05-17 20:04 ivan
+
+ * httemplate/: elements/customer-table.html,
+ misc/batch-cust_pay.html: quick payment entry running total,
+ closes: #3470
+
+2008-05-16 12:26 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cdr.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_detail.pm,
+ FS/FS/part_pkg/voip_cdr.pm, conf/invoice_html,
+ conf/invoice_latex: typeset CDRs into 5 columns on invoices
+
+2008-05-15 15:48 ivan
+
+ * FS/FS/CGI.pm: fix minor problem with ship_zip not showing up in
+ small_custview
+
+2008-05-14 14:21 jeff
+
+ * FS/FS/: Conf.pm, Conf_compat17.pm, cust_bill.pm: config option to
+ omit statement type items from invoices
+
+2008-05-14 11:19 jeff
+
+ * bin/import-optigold.pl: do NOT delete the existing data
+
+2008-05-14 11:07 jeff
+
+ * FS/FS/: cust_bill_pkg.pm, part_pkg.pm, tax_rate.pm,
+ part_pkg/voip_cdr.pm: correct tax selection and *actually* handle
+ fee based taxes
+
+2008-05-14 09:52 ivan
+
+ * bin/import-optigold.pl: [no log message]
+
+2008-05-13 16:36 ivan
+
+ * FS/bin/freeside-adduser: tyop
+
+2008-05-13 14:20 ivan
+
+ * FS/FS/part_pkg.pm: eliminate warnings on upgrade: "(Odd number of
+ elements in anonymous hash | Use of unintialized value in
+ anonymous hash ) at
+ /usr/local/share/perl/5.8.8/FS/option_Common.pm line 176.
+
+2008-05-13 12:13 ivan
+
+ * Makefile: ensure new self-service libs are installed
+
+2008-05-12 20:49 ivan
+
+ * debian/rules: tyop
+
+2008-05-12 20:49 ivan
+
+ * Makefile, FS/bin/freeside-setup, debian/rules: better place for
+ initial configuration to be stored and retreived from than the
+ initial tarball...
+
+2008-05-08 23:34 ivan
+
+ * httemplate/misc/: xmlhttp-cust_main-address_standardize.html,
+ xmlhttp-cust_main-search.cgi: JSON 1.0 (on deb 4.0) doesn't have
+ to_json yet
+
+2008-05-08 22:59 ivan
+
+ * httemplate/: docs/credits.html, docs/license.html,
+ edit/cust_main.cgi, images/cross.png, images/error.png,
+ images/tick.png: finish usps address standardization
+
+2008-05-08 22:54 ivan
+
+ * httemplate/view/cust_main/packages.html: perl 5.10-ism? new
+ mason?
+
+2008-05-08 05:45 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi,
+ httemplate/elements/xmlhttp.html,
+ httemplate/misc/xmlhttp-cust_main-address_standardize.html,
+ httemplate/misc/xmlhttp-cust_main-search.cgi, htetc/handler.pl:
+ address standardization part one, finally checked in from here
+
+2008-05-07 14:36 ivan
+
+ * httemplate/: view/cust_main.cgi, misc/cancel_cust.html: fix
+ customer cancellation, sort of a side effect of #2872, fixes
+ #3480, #3481
+
+2008-05-05 18:15 ivan
+
+ * httemplate/elements/customer-table.html: put the documentation in
+ a proper <%doc> section
+
+2008-05-05 18:14 ivan
+
+ * httemplate/misc/xmlhttp-cust_main-search.cgi: agent-virtualize
+ customer # portion of quick payment entry
+
+2008-05-05 18:14 ivan
+
+ * httemplate/: misc/batch-cust_pay.html,
+ elements/customer-table.html: agent-virtualize quick payment
+ entry
+
+2008-05-04 19:11 ivan
+
+ * FS/FS/payinfo_Mixin.pm: hopefully really a better fix for using
+ new payment duplicate stuff with cc encryption :/
+
+2008-05-04 18:52 ivan
+
+ * FS/FS/payinfo_Mixin.pm: better fix for using new payment
+ duplicate stuff with cc encryption :)
+
+2008-05-04 18:18 ivan
+
+ * FS/FS/payinfo_Mixin.pm: fix for using new payment duplicate stuff
+ with cc encryption
+
+2008-05-02 13:30 ivan
+
+ * FS/bin/freeside-upgrade: add -s switch to freeside-upgrade for
+ schema-only changes (for slony slaves)
+
+2008-05-01 18:58 ivan
+
+ * FS/FS/Upgrade.pm: upgrade part_pkg before cust_credit
+
+2008-04-30 16:42 ivan
+
+ * Makefile: not here
+
+2008-04-30 16:36 ivan
+
+ * Makefile: that explains the bs with ChangeLog
+
+2008-04-30 16:29 ivan
+
+ * Makefile: last last-minute fix for last-minute change
+
+2008-04-30 16:26 ivan
+
+ * Makefile: last minute fix for last minute change
+
+2008-04-30 16:19 ivan
+
+ * Makefile: last minute release target update. not particularly
+ dangerous, nobody uses it but me
+
+2008-04-28 12:17 ivan
+
+ * FS/FS/: cust_main.pm, Cron/bill.pm: fix 1.9 queued billing from
+ doing weird things with expirations and adjournments because
+ freeside-queued $^T != freeside-daily $^T
+
+2008-04-27 09:28 jeff
+
+ * httemplate/elements/select-taxproduct.html: sticky, too
+
+2008-04-27 08:19 jeff
+
+ * httemplate/elements/select-taxproduct.html: fixup taxproduct
+ selection
+
+2008-04-24 15:51 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: add use_amaflags and use_disposition
+ flags to voip_cdr price plan
+
+2008-04-22 19:59 jeff
+
+ * FS/FS/cust_bill.pm: fixup return address fallback
+
+2008-04-22 12:46 ivan
+
+ * FS/FS/cust_bill.pm: no comma there
+
+2008-04-22 03:56 ivan
+
+ * FS/FS/: cdr.pm, part_pkg/voip_cdr.pm: voxline cdrs
+
+2008-04-18 14:27 ivan
+
+ * rt/lib/RT/Ticket_Overlay.pm: blah. last silly typo hopefully.
+ wish this was easier to test.
+
+2008-04-18 14:25 ivan
+
+ * rt/lib/RT/Ticket_Overlay.pm: silly semicolon
+
+2008-04-18 14:19 ivan
+
+ * rt/lib/RT/: Ticket_Overlay.pm, URI/freeside.pm: hopefully,
+ finally fix ticket auto-association not getting along with RT
+ ACLs
+
+2008-04-17 20:29 jeff
+
+ * FS/t/addr_block.t, FS/t/router.t, httemplate/edit/allocate.html,
+ httemplate/browse/addr_block.cgi, httemplate/browse/router.cgi,
+ httemplate/edit/process/addr_block/add.cgi,
+ httemplate/edit/process/addr_block/allocate.cgi,
+ httemplate/edit/process/addr_block/deallocate.cgi,
+ httemplate/edit/process/addr_block/split.cgi: drag address and
+ router even farther into the century
+
+2008-04-16 18:39 ivan
+
+ * FS/FS/: Schema.pm, cust_bill.pm, cust_bill_pkg.pm, cust_main.pm,
+ cust_main_county.pm, cust_pkg.pm, part_pkg/flat.pm: implement
+ line item bundling
+
+2008-04-16 18:28 ivan
+
+ * httemplate/elements/menu.html: this is more accurate
+
+2008-04-16 14:52 ivan
+
+ * FS/FS/: part_pkg.pm, cust_main.pm: start implementing billing
+ add-ons
+
+2008-04-16 14:12 ivan
+
+ * FS/FS/part_pkg.pm, httemplate/browse/part_pkg.cgi: implement
+ service add-ons
+
+2008-04-16 14:10 ivan
+
+ * FS/FS/Record.pm: be more forgiving about numeric things padded
+ with leading/trailing space
+
+2008-04-16 13:34 ivan
+
+ * httemplate/: edit/part_bill_event.cgi, edit/part_pkg.cgi,
+ edit/quick-charge.html, elements/select-taxclass.html,
+ elements/tr-select-taxclass.html: fix tax class on package def
+ edit
+
+2008-04-16 13:00 ivan
+
+ * httemplate/: browse/part_pkg.cgi, edit/part_pkg.cgi: show package
+ add-on links in browse
+
+2008-04-16 11:32 jeff
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_pkg_reason.pm,
+ httemplate/view/cust_main/packages.html: show user who created
+ (cancel/suspend) reason and possibly fix a lingering spurious
+ usergroup bug
+
+2008-04-16 04:54 jeff
+
+ * httemplate/edit/part_pkg_taxproduct.html: this no longer belongs
+
+2008-04-15 21:42 ivan
+
+ * httemplate/: edit/part_pkg.cgi, browse/part_pkg.cgi: add plan &
+ pricing to package browse...
+
+2008-04-15 18:03 ivan
+
+ * FS/FS/m2m_Common.pm, FS/FS/part_pkg.pm,
+ httemplate/edit/process/part_pkg.cgi: fix new link editing in new
+ package editor
+
+2008-04-15 17:29 ivan
+
+ * FS/FS/part_pkg.pm: new package def editor
+
+2008-04-15 17:19 ivan
+
+ * httemplate/edit/: part_pkg.cgi, elements/edit.html,
+ process/part_pkg.cgi: new package def editor
+
+2008-04-15 17:16 ivan
+
+ * FS/: FS.pm, FS/m2m_Common.pm, FS/part_pkg_link.pm,
+ t/part_pkg_link.t: new package def editor
+
+2008-04-15 13:47 jeff
+
+ * httemplate/elements/file-upload.html,
+ httemplate/elements/header-minimal.html,
+ httemplate/misc/file-upload.html, httemplate/misc/tax-import.cgi,
+ httemplate/misc/process/tax-import.cgi,
+ httemplate/misc/process/tax-upgrade.cgi, FS/FS/cust_main.pm,
+ FS/FS/cust_tax_location.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_pkg_taxproduct.pm, FS/FS/part_pkg_taxrate.pm,
+ FS/FS/tax_class.pm, FS/FS/tax_rate.pm: (finally) wrap up new tax
+ rate engine (for now)
+
+2008-04-15 12:43 ivan
+
+ * FS/FS/: part_pkg_option.pm, part_pkg/sesmon_hour.pm,
+ part_pkg/sesmon_minute.pm, part_pkg/sql_external.pm,
+ part_pkg/sql_generic.pm, part_pkg/sqlradacct_hour.pm,
+ part_pkg/voip_cdr.pm, part_pkg/voip_sqlradacct.pm:
+ s/recur_flat/recur_fee/
+
+2008-04-15 06:41 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_event.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/option_Common.pm,
+ FS/FS/part_event_option.pm, FS/FS/part_pkg.pm,
+ FS/FS/svc_Common.pm, FS/FS/svc_acct.pm, FS/FS/svc_domain.pm,
+ httemplate/edit/part_event.html, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/elements/process.html,
+ httemplate/elements/select-agent_types.html,
+ httemplate/elements/select-taxproduct.html,
+ httemplate/elements/selectlayers.html,
+ httemplate/elements/tr-input-text.html,
+ httemplate/elements/tr-part_pkg_freq.html,
+ httemplate/elements/tr-pkg_svc.html,
+ httemplate/elements/tr-select-agent_types.html,
+ httemplate/elements/tr-title.html: new package editor
+
+2008-04-15 03:50 ivan
+
+ * httemplate/docs/about.html: it's april 15th, and i sure do.
+
+2008-04-14 06:42 jeff
+
+ * FS/FS/Record.pm: Improve record searching
+
+2008-04-13 14:37 ivan
+
+ * FS/FS/part_pkg/: base_delayed.pm, flat_delayed.pm,
+ prorate_delayed.pm: fix recur_notify label for *_delayed price
+ plans
+
+2008-04-13 13:35 ivan
+
+ * httemplate/elements/: select-cust-part_pkg.html,
+ select-part_pkg.html, tr-select-part_pkg.html, select-table.html:
+ select-table and select-part_pkg updates so we can use
+ select-part_pkg as an edit/elements/edit.html m2 type
+
+2008-04-13 03:21 ivan
+
+ * httemplate/elements/tr-justtitle.html: adding just a title with
+ no blank space above
+
+2008-04-13 03:19 ivan
+
+ * httemplate/elements/: tr-selectlayers-select.html,
+ selectlayers.html: add a tr- for just the select bit of a
+ selectlayers...
+
+2008-04-13 01:21 ivan
+
+ * httemplate/elements/tr-input-text.html: add disabled option to
+ text elements
+
+2008-04-13 00:41 ivan
+
+ * FS/FS/part_pkg/: base_delayed.pm, base_rate.pm, bulk.pm, flat.pm,
+ flat_comission.pm, flat_comission_cust.pm, flat_comission_pkg.pm,
+ flat_delayed.pm, flat_introrate.pm, prepaid.pm, prorate.pm,
+ prorate_delayed.pm, sesmon_hour.pm, sesmon_minute.pm,
+ sql_external.pm, sql_generic.pm, sqlradacct_hour.pm,
+ subscription.pm, voip_cdr.pm, voip_sqlradacct.pm: shorter names
+ and rearranged weights for a brighter tommorow^W^Wbetter price
+ plan <SELECT>
+
+2008-04-12 20:03 ivan
+
+ * httemplate/elements/tr-select-table.html: more stuff to allow
+ select-table to be used as as edit/elements/edit.html m2*
+ element: add tr-select-table.html
+
+2008-04-12 20:03 ivan
+
+ * httemplate/elements/select-table.html: some stuff to allow
+ select-table to be used as as edit/elements/edit.html m2*
+ element: add js_only and html_only options, add id option to
+ specify element id, add a kludge to onchange option
+
+2008-04-12 19:58 ivan
+
+ * httemplate/elements/tablebreak-tr-title.html: allow table id to
+ be specified for tablebreak elements (so edit/elements/edit.html
+ m2* stuff can be used after a tablebreak)
+
+2008-04-12 19:56 ivan
+
+ * httemplate/elements/: select-taxproduct.html,
+ tr-select-taxproduct.html: add elements for selecting taxproduct
+
+2008-04-12 19:55 ivan
+
+ * httemplate/elements/tr-part_pkg_freq.html: add an element for
+ selecting part_pkg frequencies
+
+2008-04-12 16:31 ivan
+
+ * httemplate/elements/: columnnext.html, columnstart.html: space
+ columns out
+
+2008-04-12 16:18 ivan
+
+ * httemplate/elements/: columnend.html, columnnext.html,
+ columnstart.html: add some column elements so we can replicate
+ multi-column edit forms with edit.html
+
+2008-04-11 02:20 ivan
+
+ * FS/FS/cdr.pm, httemplate/misc/cdr-import.html: taqua cdrs!
+
+2008-04-10 18:50 ivan
+
+ * FS/FS/cdr.pm, httemplate/misc/cdr-import.html: checkpoint taqua
+
+2008-04-10 15:00 ivan
+
+ * rt/lib/RT/Ticket_Overlay.pm: use non-ACL'ed _AddLink instead of
+ AddLink so that the auto-association stuff works when creating a
+ ticket
+
+2008-04-09 18:35 jeff
+
+ * FS/FS/Conf.pm: sync terminology
+
+2008-04-09 16:51 ivan
+
+ * httemplate/elements/menu.html: add a menu entry for the new tax
+ stuff
+
+2008-04-09 14:54 ivan
+
+ * httemplate/misc/tax-import.cgi: clean up irrelevant stuff on
+ batch tax import
+
+2008-04-09 14:44 ivan
+
+ * httemplate/docs/AGPL.html: oops, adding explicit AGPL.html
+
+2008-04-09 14:27 ivan
+
+ * FS/FS/UID.pm: please mr. bootstrapping tree, give us some bootsap
+
+2008-04-09 14:17 ivan
+
+ * FS/FS/UID.pm: 1.9 bootstrapping: apparantly, we have a dbdef at
+ this point, but its not in the database yet, so checking
+ dbdef->table doesn't DWWW
+
+2008-04-09 14:07 ivan
+
+ * debian/control: adding 1.9 dependencies
+
+2008-04-08 20:33 ivan
+
+ * FS/FS/part_pkg/sqlradacct_hour.pm: fix hourly cap for SQL overage
+ charges
+
+2008-04-07 22:49 ivan
+
+ * debian/: README.Debian, TODO, changelog, compat, conffiles.ex,
+ config, control, copyright, cron.d, cron.d.ex,
+ dbconfig-common.install, dbconfig-common.upgrade, dirs, docs,
+ ex.doc-base.package, freeside-doc.docs, freeside-doc.files,
+ freeside-webui.links, freeside.apache-alias.conf,
+ freeside.default, freeside.docs, init.d.ex, init.d.lsb.ex,
+ manpage.1.ex, manpage.sgml.ex, menu.ex, postinst, postinst.ex,
+ postrm, postrm.ex, preinst, preinst.ex, prerm, prerm.ex, rules,
+ templates, watch.ex: debian packages!
+
+2008-04-06 09:12 jeff
+
+ * httemplate/browse/part_pkg_taxproduct.cgi,
+ httemplate/browse/tax_rate.cgi, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_county.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_pkg_taxrate.pm, FS/FS/tax_rate.pm,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/tax_rate.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/tax_rate.html,
+ httemplate/edit/process/elements/process.html: new tax rating
+ engine
+
+2008-04-05 00:00 ivan
+
+ * FS/bin/freeside-prepaidd: configurable logfile location for
+ freeside-prepaidd
+
+2008-04-04 12:47 ivan
+
+ * httemplate/elements/header.html: sync with 1_7_BRANCH
+
+2008-04-04 10:47 ivan
+
+ * httemplate/search/elements/search.html: this worked fine. don't
+ understand why it was changed in the first place.
+
+2008-04-04 10:42 ivan
+
+ * httemplate/search/elements/search.html: fix range selection on
+ searches, huh
+
+2008-04-02 13:42 jeff
+
+ * httemplate/browse/tax_class.html, FS/FS/Schema.pm,
+ FS/FS/part_pkg_taxoverride.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_pkg_taxoverride.html,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/search/elements/search.html: checkpoint tax editors
+ and correct a blunder
+
+2008-04-02 10:28 ivan
+
+ * Makefile: substitute FREESIDE_CONF in apache conf files
+
+2008-04-02 10:26 ivan
+
+ * htetc/: freeside-base1.99.conf, freeside-base1.conf,
+ freeside-base2.conf: template AuthUserFile htpasswd to be in
+ FREESIDE_CONF dir instead of hardcoded location
+
+2008-04-02 10:00 rsiddall
+
+ * Makefile: Quick fix to update the release version number in the
+ RPM specfile. This leaves the specfile unusable for CVS builds.
+
+2008-04-02 08:38 ivan
+
+ * init.d/freeside-init: tiny init file nit that helps with deb
+ packaging (and probably rpm too) - source /etc/default/freeside
+ if it exists
+
+2008-04-01 21:32 ivan
+
+ * httemplate/docs/credits.html: slight browser adjustments and DONE
+
+2008-04-01 21:20 ivan
+
+ * httemplate/elements/header.html: new header about: box
+
+2008-04-01 21:19 ivan
+
+ * httemplate/docs/about.html: slightly taller
+
+2008-04-01 21:07 ivan
+
+ * httemplate/docs/: credits.html, license.html: updated credits and
+ license
+
+2008-04-01 21:05 ivan
+
+ * httemplate/docs/about.html: adding about.html splash
+
+2008-04-01 20:48 ivan
+
+ * httemplate/elements/popup_link_onclick.html: remove excessive
+ iframe borders, add "scrolling" and "nofalse" options
+
+2008-04-01 17:22 ivan
+
+ * FS/FS/cust_pay.pm: don't do (as much) useless work on upgrade
+
+2008-04-01 16:56 ivan
+
+ * httemplate/elements/iframecontentmws.js: fix hasty safari iframe
+ fix
+
+2008-04-01 16:30 ivan
+
+ * httemplate/docs/: credits.html, license.html: adding license and
+ credits in app itself
+
+2008-04-01 16:24 ivan
+
+ * TODO: remove TODO
+
+2008-04-01 16:24 ivan
+
+ * SCHEMA_CHANGE: remove SCHEMA_CHANGE
+
+2008-04-01 16:20 ivan
+
+ * README: welcome to the new world
+
+2008-04-01 16:15 ivan
+
+ * INSTALL: clean up top level files
+
+2008-04-01 16:08 ivan
+
+ * CREDITS, INSTALL: clean up some of the top-level files
+
+2008-04-01 02:40 ivan
+
+ * Makefile: sync with 1.7.3
+
+2008-04-01 02:19 ivan
+
+ * FS/FS/cust_bill.pm: don't show services on invoices that are
+ newer than the invoice, closes: #3032
+
+2008-04-01 01:43 ivan
+
+ * rt/lib/RT/Ticket_Overlay.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_invoice.pm, rt/lib/RT/URI/freeside/Internal.pm:
+ last bit of magic for RT ticket customer auto-association: look
+ for requestor email addresses in cust_main_invoice and svc_acct,
+ closes; RT#1160
+
+2008-03-31 23:37 ivan
+
+ * httemplate/edit/cust_main_county.html: probably doesn't matter
+ now, but better error reporting anyway
+
+2008-03-31 23:34 ivan
+
+ * httemplate/: misc/cancel_cust.html, misc/cancel_pkg.html,
+ edit/cust_credit.cgi: popup iframes don't have predictable names
+ anymore, so locate submit buttons by id instead
+
+2008-03-31 23:26 ivan
+
+ * httemplate/elements/: popup_link.html, popup_link_onclick.html:
+ doc height param
+
+2008-03-31 23:16 ivan
+
+ * httemplate/elements/iframecontentmws.js: append a random number
+ to the iframe name on-the-fly to keep safari from caching it
+ under all circumstances
+
+2008-03-31 22:49 ivan
+
+ * httemplate/view/cust_main/payment_history.html: finish
+ component-izing overlib links
+
+2008-03-31 21:37 ivan
+
+ * httemplate/search/cust_pkg.cgi: use popup_link...
+
+2008-03-31 21:19 ivan
+
+ * httemplate/browse/rate_detail.html: can't run include() sub at
+ <%once> time
+
+2008-03-31 20:56 ivan
+
+ * httemplate/browse/rate_detail.html: component-ize overlib init
+ and popup link
+
+2008-03-31 20:51 ivan
+
+ * httemplate/edit/bulk-cust_svc.html: component-ize overlib init
+
+2008-03-31 20:49 ivan
+
+ * httemplate/elements/phonenumber.html: use popup_link element for
+ phonenumber.html silly vonage popup
+
+2008-03-31 17:54 jeff
+
+ * FS/FS/cust_tax_location.pm, FS/FS/part_pkg_taxoverride.pm,
+ FS/FS/part_pkg_taxproduct.pm, FS/FS/part_pkg_taxrate.pm,
+ FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/tax_class.pm,
+ FS/FS/tax_rate.pm, FS/FS/part_pkg.pm, FS/t/cust_tax_location.t,
+ FS/t/part_pkg_taxoverride.t, FS/t/part_pkg_taxproduct.t,
+ FS/t/part_pkg_taxrate.t, FS/t/tax_class.t, FS/t/tax_rate.t,
+ httemplate/browse/tax_rate.cgi, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_pkg_taxoverride.html,
+ httemplate/edit/part_pkg_taxproduct.html,
+ httemplate/edit/tax_class.html, httemplate/edit/tax_rate.html,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/tax_class.html,
+ httemplate/edit/process/tax_rate.html,
+ httemplate/misc/tax-import.cgi, FS/MANIFEST, htetc/handler.pl,
+ httemplate/elements/menu.html,
+ httemplate/misc/process/recharge_svc.new,
+ httemplate/misc/process/tax-import.cgi: checkpoint of new tax
+ rating system
+
+2008-03-31 16:19 ivan
+
+ * httemplate/elements/progress-init.html: overlib include
+
+2008-03-31 16:14 ivan
+
+ * httemplate/elements/: popup_link-cust_main.html,
+ popup_link-cust_pkg.html, popup_link-cust_svc.html,
+ popup_link.html, popup_link_onclick.html: fix examples to use
+ elements/init_overlib.html
+
+2008-03-31 16:09 ivan
+
+ * httemplate/: browse/cust_main_county.cgi, config/config-view.cgi:
+ overlib include
+
+2008-03-31 16:07 ivan
+
+ * httemplate/: view/cust_main.cgi,
+ elements/popup_link_onclick.html, view/cust_main/notes.html:
+ clean up overlib usage for customer notes
+
+2008-03-31 15:52 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.cgi: signup.cgi fix for
+ hidden agentnum tags with trailling "/ >"
+
+2008-03-31 15:27 ivan
+
+ * httemplate/: edit/cust_main/billing.html,
+ elements/init_overlib.html, misc/payment.cgi: overlib cleanup:
+ easy static CVV2/echeck popups
+
+2008-03-31 14:52 ivan
+
+ * httemplate/elements/iframecontentmws.js: update overlibmws to
+ current code
+
+2008-03-31 14:45 ivan
+
+ * httemplate/: config/config-view.cgi, elements/popup_link.html:
+ move config edit popup to elements/popup_link.html
+
+2008-03-31 14:43 ivan
+
+ * httemplate/config/config.cgi: more approprietly sized textareas
+ prevent scrollbars from being necessary in config popups
+
+2008-03-31 14:11 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ elements/popup_link.html, elements/popup_link_onclick.html: fix
+ safari weirdness with caching iframes and auto-submitting them
+ (in tax editor at least)
+
+2008-03-31 13:40 ivan
+
+ * httemplate/elements/: popup_link-cust_main.html,
+ popup_link-cust_pkg.html, popup_link-cust_svc.html,
+ popup_link.html: clean up popup link elements a bit, have the
+ iframe name autogenerate to work around safari fuckery
+
+2008-03-31 11:50 ivan
+
+ * httemplate/elements/: overlibmws.js, overlibmws_crossframe.js,
+ overlibmws_draggable.js, overlibmws_iframe.js: update overlibmws
+ to current code before delving into safari problem... hopefully
+ no disasterous side effects :)
+
+2008-03-29 19:42 ivan
+
+ * httemplate/: search/cust_bill.html, view/cust_bill.cgi: don't
+ show invoice resend and payment posting links if you can't do
+ that anyway
+
+2008-03-29 19:32 ivan
+
+ * FS/FS/cust_bill.pm: okay, REALLY fix substitution problems with
+ ancient-style invoice template includes
+
+2008-03-29 18:26 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm: fix missing backslash preventing
+ ancient invoice template includes from working, whew! and
+ spiffied up the error reporting on template compile problems,
+ since they're bound to happen when folks edit
+
+2008-03-29 17:57 ivan
+
+ * FS/FS/Conf.pm: document config_orbase, add key_orbase for
+ debugging info
+
+2008-03-29 02:11 ivan
+
+ * httemplate/view/: svc_phone.cgi, elements/svc_Common.html: add
+ quick links for unprocessed and processed CDRs to svc_phone view
+
+2008-03-29 01:32 ivan
+
+ * httemplate/misc/order_pkg.html: in the unlikely error case, the
+ order button should stay enabled
+
+2008-03-29 01:15 ivan
+
+ * httemplate/: elements/popup_link-cust_main.html,
+ elements/tr-select-reason.html, misc/cancel_cust.html,
+ misc/order_pkg.html, view/cust_main/packages.html: okay. and
+ thank goodness its on 1.9. really fix error reporting on quick
+ package order this time. have to change cust cancel popup
+ slightly too, but its for the better (easier custnum parsing).
+ and lastly make it easier for reason selection to be reused
+ without stickiness-on-errors being a big pain in the ass
+
+2008-03-28 23:42 ivan
+
+ * httemplate/edit/process/quick-cust_pkg.cgi: fix quick pkg order
+ redirect
+
+2008-03-28 15:31 ivan
+
+ * FS/FS/cust_svc.pm: fix double charging for CDRs when number is in
+ both src and charged_party fields, and disable_src is not checked
+
+2008-03-27 15:04 ivan
+
+ * httemplate/edit/process/cust_main.cgi: fixes edit if routing code
+ only, closes: #3085
+
+2008-03-27 14:05 ivan
+
+ * httemplate/view/cust_main/: payment_history.html: fix for ACH
+ info masking
+
+2008-03-27 14:01 ivan
+
+ * httemplate/view/cust_main/: billing.html, payment_history.html:
+ mask ACH info in payment history
+
+2008-03-27 13:40 ivan
+
+ * httemplate/: elements/menu.html, search/report_sql.html: add back
+ raw SQL query (it had an appropriate ACL)
+
+2008-03-27 13:19 ivan
+
+ * httemplate/: search/cust_pay.cgi, view/cust_pay.html: resolve
+ minor ACL glitch linking to payments
+
+2008-03-26 11:18 ivan
+
+ * FS/FS/Report/Table/Monthly.pm: add a netcredits_12mo sub so that
+ 12mo checkbox works again... sure do need these to be
+ auto-generated or $AUTOLOADED or something :)
+
+2008-03-26 08:42 jeff
+
+ * FS/FS/rate_detail.pm, httemplate/browse/rate_detail.html,
+ httemplate/edit/rate_detail.html,
+ httemplate/edit/rate_region.cgi: fixup per call billing
+
+2008-03-25 20:04 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: don't granular-ize 0 billsec calls
+ into having a minimum charge
+
+2008-03-25 19:27 ivan
+
+ * httemplate/search/: cdr.html, report_cdr.html: add ability to
+ query ranges of duration & billable seconds to CDR report
+
+2008-03-25 19:19 ivan
+
+ * httemplate/search/: report_cust_main.html, report_svc_acct.html:
+ UI nit: separate search & display options
+
+2008-03-25 18:03 ivan
+
+ * httemplate/edit/: part_bill_event.cgi,
+ process/part_bill_event.cgi: quick shortcut for easier adding of
+ new 1.7-style invoice events
+
+2008-03-24 19:52 ivan
+
+ * rt/lib/RT/: Groups_Overlay.pm, Users_Overlay.pm: REALLY require
+ DBIx::SB 1.50, jeez
+
+2008-03-24 19:33 ivan
+
+ * rt/lib/RT/SearchBuilder.pm: bring declared SearchBuilder
+ dependency inline with reality
+
+2008-03-24 15:35 ivan
+
+ * httemplate/edit/part_bill_event.cgi: this probably never worked.
+ ->send isn't at all what is wanted, that sends the original
+ invoice, not the new one with the late charge
+
+2008-03-23 20:07 ivan
+
+ * rt/lib/RT/: Record.pm, Ticket_Overlay.pm: woo! final part of
+ #1160! yup, auto-associating tickets with customers now. whew!
+
+2008-03-23 18:28 ivan
+
+ * rt/lib/RT/: Record.pm, Interface/Web_Vendor.pm: part two of
+ #1160: linking a ticket to its first customer will auto-link any
+ customerless requestors
+
+2008-03-23 16:38 ivan
+
+ * rt/lib/RT/: User_Overlay.pm, Interface/Web_Vendor.pm: part 1 of
+ #1160: associate users w/customers, manual editing
+
+2008-03-19 12:48 jeff
+
+ * httemplate/edit/part_bill_event.cgi: quick fix for deep recursion
+ (RT#3267)
+
+2008-03-17 23:43 ivan
+
+ * fs_selfservice/fri/modules/: billing.module, dashboard.module,
+ myaccount.module: pre-show checkpoint. duct tape!
+
+2008-03-17 09:24 ivan
+
+ * fs_selfservice/fri/modules/: billing.module, dashboard.module,
+ myaccount.module: checkpoint
+
+2008-03-16 19:49 ivan
+
+ * fs_selfservice/fri/: includes/freeside.class.php,
+ modules.template/blank.module: missed bits
+
+2008-03-16 19:48 ivan
+
+ * fs_selfservice/fri/: includes/login.php, includes/main.conf.php,
+ locale/ari.po, modules/billing.module,
+ modules/callmonitor.module, modules/dashboard.module,
+ modules/myaccount.module, modules/settings.module,
+ theme/page.tpl.php: first bits of working FRI! woop!
+
+2008-03-16 19:37 ivan
+
+ * httemplate/view/cust_main/packages.html: fix oops in new popup
+ components
+
+2008-03-16 16:36 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: hopefully allow phone login to work
+
+2008-03-16 16:25 ivan
+
+ * httemplate/view/svc_phone.cgi: view PINs too
+
+2008-03-16 16:05 jeff
+
+ * httemplate/: elements/popup_link-cust_main.html,
+ elements/popup_link-cust_pkg.html,
+ elements/popup_link-cust_svc.html, elements/popup_link.html,
+ view/cust_main.cgi, view/cust_main/packages.html: componentize
+ conflicting %once subroutines (rt#3250)
+
+2008-03-16 15:48 ivan
+
+ * httemplate/edit/svc_phone.cgi: phone gets a pin
+
+2008-03-16 15:39 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: phonenum + pin login
+
+2008-03-16 15:24 ivan
+
+ * FS/FS/: Conf.pm, svc_phone.pm, ClientAPI/MyAccount.pm: phonenum +
+ pin login
+
+2008-03-16 14:07 ivan
+
+ * fs_selfservice/fri/includes/: common.php, login.php: first try at
+ single sign-on
+
+2008-03-16 12:58 ivan
+
+ * fs_selfservice/fri/: CHANGE.log, LICENSE.txt, README.txt,
+ index.php, version.php, theme/global.css, theme/header.css,
+ theme/iefixes.css, theme/layout.css, theme/logo.gif,
+ theme/main.css, theme/navigation.css, theme/page.tpl.php,
+ theme/spacer.gif, theme/text.css, theme/images/arrow-asc.gif,
+ theme/images/arrow-desc.gif, modules/callmonitor.module,
+ modules/phonefeatures.module, modules/settings.module,
+ modules/voicemail.module, modules/featurecodes.module,
+ modules/followme.module, includes/ajax.php, includes/crypt.php,
+ includes/main.conf.php, misc/audio.php, misc/popup.css,
+ misc/recording_popup.php, modules/VmX.module, includes/asi.php,
+ includes/bootstrap.php, includes/common.php,
+ includes/database.php, includes/display.php, includes/lang.php,
+ includes/login.php, locale/readme.txt, locale/ari.po,
+ locale/ari.utf-8.po, locale/locale.txt,
+ locale/de_DE/LC_MESSAGES/ari.mo, locale/hu_HU/LC_MESSAGES/ari.mo,
+ locale/hu_HU/LC_MESSAGES/ari.po, locale/pt_BR/LC_MESSAGES/ari.mo,
+ locale/pt_BR/LC_MESSAGES/ari.po, locale/de_DE/LC_MESSAGES/ari.po,
+ locale/el_GR/LC_MESSAGES/ari.mo, locale/el_GR/LC_MESSAGES/ari.po,
+ locale/es_ES/LC_MESSAGES/ari.mo, locale/es_ES/LC_MESSAGES/ari.po,
+ locale/sv_SE/LC_MESSAGES/ari.mo, locale/sv_SE/LC_MESSAGES/ari.po,
+ locale/it_IT/LC_MESSAGES/ari.mo, locale/fr_FR/LC_MESSAGES/ari.mo,
+ locale/fr_FR/LC_MESSAGES/ari.po, locale/he_IL/LC_MESSAGES/ari.mo,
+ locale/he_IL/LC_MESSAGES/ari.po, locale/it_IT/LC_MESSAGES/ari.po:
+ Initial revision
+
+2008-03-16 09:57 jeff
+
+ * httemplate/misc/recharge_svc.html: doh
+
+2008-03-16 01:04 ivan
+
+ * FS/FS/UID.pm: goodness, i hope this fixes the bootstrapping
+
+2008-03-16 00:25 ivan
+
+ * FS/FS/UID.pm: okay, really now, how about this
+
+2008-03-16 00:24 ivan
+
+ * FS/FS/UID.pm: okay, how about this
+
+2008-03-16 00:21 ivan
+
+ * FS/FS/UID.pm: fix 1.7->1.9 bootstrapping, i think
+
+2008-03-15 22:22 ivan
+
+ * httemplate/search/svc_broadband.cgi: clean up and
+ agent-virtualize svc_broadband searching
+
+2008-03-15 22:13 ivan
+
+ * httemplate/edit/part_svc.cgi: finish bringing sanity to
+ svc_broadband service definition blocknum edit
+
+2008-03-15 22:13 ivan
+
+ * FS/FS/: addr_block.pm, svc_broadband.pm: bring some sanity to
+ address block selection in svc_broadband service definition edit
+
+2008-03-15 21:54 ivan
+
+ * httemplate/browse/: addr_block.cgi, router.cgi: drag address &
+ router browse into this centiry
+
+2008-03-15 21:00 ivan
+
+ * httemplate/elements/: header.html, menu.html: hide customer
+ search if user does not have "List customers" ACL, closes:
+ RT#3105
+
+2008-03-15 15:18 ivan
+
+ * httemplate/edit/part_pkg.cgi, FS/FS/part_pkg/voip_cdr.pm:
+ slightly better voip_cdr package edit: use radio buttons for
+ selecting long things
+
+2008-03-15 14:39 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: be asssertive
+
+2008-03-15 14:14 ivan
+
+ * FS/FS/: cust_svc.pm, part_pkg/voip_cdr.pm: configurable
+ disable_src, domestic_prefix and international_prefix options for
+ voip price plan
+
+2008-03-15 12:52 ivan
+
+ * httemplate/search/cust_bill.html, FS/FS/cust_bill.pm: check on
+ invoice re-printing newest percust... can't reproduce any
+ problem, prints newest per cust fine, closes: #3161
+
+2008-03-14 17:30 ivan
+
+ * httemplate/: view/svc_domain.cgi, misc/catchall.cgi: fix harmless
+ code leaking out onto the page, hehe, closes: Bug#3253, also
+ remove silly formatting on catchall edit
+
+2008-03-14 13:11 ivan
+
+ * FS/FS/cust_main.pm: eliminate some harmless uninitialized value
+ warnings
+
+2008-03-14 10:30 jeff
+
+ * FS/FS/part_export/prizm.pm: insist on Net::Prizm 0.04
+
+2008-03-14 01:13 ivan
+
+ * httemplate/edit/process/cust_pkg.cgi: whew, glad that change
+ package bugfix was easy to find, closes: Bug#3241
+
+2008-03-14 00:48 ivan
+
+ * FS/FS/cust_credit_bill.pm, FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/money_time.cgi,
+ httemplate/search/cust_credit_bill.html,
+ httemplate/search/cust_credit.html: add net credits to
+ sales/credits/receipts report
+
+2008-03-13 19:10 ivan
+
+ * FS/FS/Conf.pm: add Net20 invoice terms, closes: #3219
+
+2008-03-13 19:09 ivan
+
+ * FS/FS/Conf.pm: show where the warning about no conf comes from
+
+2008-03-13 02:28 ivan
+
+ * FS/FS/cust_pay.pm: yow. very sleep deprived. remove the
+ infinite loop rather than add one.
+
+2008-03-13 02:14 ivan
+
+ * FS/FS/cust_pay.pm: infinite loop protection
+
+2008-03-13 01:57 ivan
+
+ * FS/FS/cust_pay.pm: allow cust_pay.otaker upgrade to proceed even
+ if there are some old crufty records around attached to
+ now-deleted customers
+
+2008-03-12 09:22 jeff
+
+ * FS/FS/cust_svc.pm, FS/FS/part_export.pm, FS/FS/svc_Common.pm,
+ FS/FS/UI/Web.pm, FS/FS/part_export/prizm.pm,
+ eg/export_template.pm, httemplate/view/cust_main/packages.html:
+ allow exports to add links to customer view (#1407)
+
+2008-03-04 13:07 ivan
+
+ * FS/FS/cust_refund.pm: fix otaker population for cust_refund like
+ cust_credit and cust_pay
+
+2008-03-04 13:06 ivan
+
+ * FS/FS/cust_pay.pm: fix cust_pay.otaker population
+
+2008-03-04 11:15 ivan
+
+ * httemplate/: edit/cust_pkg.cgi, edit/process/cust_pkg.cgi,
+ misc/change_pkg.cgi: fix edit/process/cust_pkg getting confused
+ about the two different places that would be calling it... i.e.
+ with an empty new_pkgnum it would redirect the popup back the
+ customer view, hehe
+
+2008-03-03 19:38 ivan
+
+ * FS/FS/cust_bill.pm: AND fix latex->html notes substituion for ~s
+
+2008-03-03 19:37 ivan
+
+ * FS/FS/cust_bill.pm: and fix latex->html notes substituion for
+ escaped #s
+
+2008-03-03 19:34 ivan
+
+ * FS/FS/cust_bill.pm: fix latex->html notes substituion for
+ newlines
+
+2008-03-03 18:27 ivan
+
+ * httemplate/browse/rate_region.html: should improve compatibility
+ with older Pg. i hope
+
+2008-03-03 17:49 ivan
+
+ * FS/FS/UID.pm: argh, don't load up a user until the other
+ initialization is done
+
+2008-03-03 15:12 ivan
+
+ * rt/: etc/RT_Config.pm.in, sbin/rt-setup-database.in: merge in rt
+ 3.6.6. *gulp*!
+
+2008-03-01 19:07 ivan
+
+ * httemplate/elements/header.html: fix annoying resize of "New
+ customer" button; closes: RT#1642
+
+2008-03-01 18:50 ivan
+
+ * htetc/handler.pl: better indication of why these things are the
+ way they are
+
+2008-03-01 17:31 ivan
+
+ * FS/FS/payment_gateway.pm, httemplate/browse/payment_gateway.html,
+ httemplate/misc/disable-payment_gateway.cgi,
+ httemplate/search/elements/search.html: add payment gateway
+ disabling (and move payment gateway browse over to new template)
+
+2008-03-01 15:30 ivan
+
+ * FS/FS/: Conf.pm, payinfo_Mixin.pm: added a config value to
+ control the extent of credit card masking, but since
+ cust_main.paymask is in the db, need something to update it for
+ all existing customers too
+
+2008-03-01 14:39 ivan
+
+ * httemplate/search/cust_main.html: ah, that's better, prettyfy and
+ link adv. customer search
+
+2008-03-01 14:23 ivan
+
+ * httemplate/: elements/select-payby.html,
+ elements/tr-select-payby.html, search/cust_main.html,
+ search/report_cust_main.html: add payby selection to adv.
+ customer search
+
+2008-02-29 09:57 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, conf/invoice_html,
+ conf/invoice_latex: refactor print_*; invoice sections by package
+ class; could still stand some more refactoring
+
+2008-02-28 20:31 ivan
+
+ * FS/FS/cdr.pm, httemplate/misc/cdr-import.html: working asterisk
+ CDR CSV import (not just direct DB)
+
+2008-02-28 18:29 ivan
+
+ * FS/FS/: Record.pm, access_user.pm: disallow uppercase usernames
+ in the first place. also Record::str2time_sql_closing snuck in.
+
+2008-02-21 17:32 ivan
+
+ * httemplate/search/svc_acct.cgi: on accounts overview: show time
+ applied after multipliers, not actual time worked
+
+2008-02-21 17:09 ivan
+
+ * FS/FS/cust_pay.pm: want to upgrade legacy payments even if they
+ are deprecated COMP payby...
+
+2008-02-21 16:57 ivan
+
+ * FS/FS/cust_pay.pm: don't bomb out on this transition on old
+ databases without history records for everything...
+
+2008-02-21 16:54 ivan
+
+ * httemplate/search/svc_acct.cgi: on accounts overview: show time
+ applied after multipliers, not actual time worked
+
+2008-02-20 14:45 ivan
+
+ * httemplate/misc/inventory_item-import.html: doh. ivan is so in
+ the doghouse for this one
+
+2008-02-19 18:07 ivan
+
+ * FS/FS/prepay_credit.pm,
+ httemplate/edit/process/prepay_credit.cgi: retry collisions a
+ bit, acme is getting duplicate errors... guess they're using lots
+ and lots of prepaid cards. closes: RT#3104
+
+2008-02-19 17:21 ivan
+
+ * FS/FS/Schema.pm: update the tax class editor to enable taxclass
+ adding, RT#2929
+
+2008-02-19 17:16 ivan
+
+ * httemplate/edit/part_pkg_taxclass.html,
+ httemplate/edit/process/part_pkg_taxclass.html,
+ httemplate/browse/cust_main_county.cgi, FS/FS/Upgrade.pm,
+ FS/FS/part_pkg_taxclass.pm, htetc/handler.pl,
+ httemplate/elements/tr-select-taxclass.html: update the tax class
+ editor to enable taxclass adding, RT#2929
+
+2008-02-19 17:10 ivan
+
+ * httemplate/edit/: cust_main_county-expand.cgi,
+ process/cust_main_county-expand.cgi, process/invoice_logo.html,
+ process/reg_code.cgi: process/part_pkg_taxclass.html
+
+2008-02-19 01:15 ivan
+
+ * FS/: FS/part_pkg_taxclass.pm, t/part_pkg_taxclass.t, FS.pm,
+ MANIFEST: adding taxclass table
+
+2008-02-19 01:10 ivan
+
+ * bin/generate-table-module: fix this to generate more
+ wiki-friendly column list
+
+2008-02-18 19:30 ivan
+
+ * FS/FS/svc_external.pm: add some descriptions for svc_external
+ fields, allowing id to fill in from inventory
+
+2008-02-18 19:17 ivan
+
+ * FS/FS/svc_external.pm: return svc_external id and title as label
+ instead of svcnum
+
+2008-02-18 18:57 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: fixup and expand
+ POD docs for self-service list_pkgs & list_svcs
+
+2008-02-18 18:38 ivan
+
+ * bin/pod2x: fix API upload
+
+2008-02-18 18:37 ivan
+
+ * FS/FS.pm: update base API docs contents
+
+2008-02-18 18:33 ivan
+
+ * FS/FS/cust_pay_pending.pm: clean up POD docs for better wiki
+ exportability
+
+2008-02-18 18:28 ivan
+
+ * FS/FS/cust_credit.pm: clean up POD docs for better wiki
+ exportability
+
+2008-02-18 18:25 ivan
+
+ * FS/FS/cust_bill_event.pm: clean up POD docs for better wiki
+ exportability
+
+2008-02-18 18:20 ivan
+
+ * FS/FS/cust_bill.pm: clean up POD docs for better wiki
+ exportability
+
+2008-02-18 18:15 ivan
+
+ * FS/FS/: access_user.pm, acct_rt_transaction.pm: clean up POD docs
+ for better wiki exportability
+
+2008-02-14 00:31 ivan
+
+ * httemplate/search/cust_pkg.cgi: brown-bag missing comma from last
+ update. time to go home
+
+2008-02-14 00:28 ivan
+
+ * httemplate/search/cust_pkg.cgi: re-align links and styling in
+ package report correctly
+
+2008-02-13 19:52 ivan
+
+ * FS/FS/Schema.pm, FS/FS/Upgrade.pm, FS/FS/cust_pay.pm,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/search/cust_pay.cgi: add cust_pay.otaker field;
+ populate it based on history on upgrades, and show the order
+ taker on all payments (on customer view and payment search)
+ closes: #2953
+
+2008-02-13 19:48 ivan
+
+ * FS/: FS/h_cust_pay.pm, t/h_cust_credit.t, t/h_cust_pay.t: add
+ FS::h_cust_pay class
+
+2008-02-13 17:48 ivan
+
+ * httemplate/view/cust_main/payment_history.html: this ACL has been
+ around for long enough (and almost nobody uses batched payments,
+ so showing them is probably bad)
+
+2008-02-13 15:49 jeff
+
+ * FS/FS/svc_broadband.pm, httemplate/elements/header.html:
+ svc_broadband MAC searches RT#2985
+
+2008-02-13 15:45 jeff
+
+ * FS/FS/cust_main.pm, httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi: package class selection
+ for one-time charges RT#1322
+
+2008-02-13 15:39 jeff
+
+ * FS/FS/rate_detail.pm, FS/FS/part_pkg/voip_cdr.pm,
+ httemplate/edit/rate_region.cgi: directory assistance (flat
+ per-call) billing RT#3114
+
+2008-02-13 13:50 ivan
+
+ * httemplate/misc/process/link.cgi: fix compile error from hasty
+ refactoring
+
+2008-02-12 22:02 ivan
+
+ * FS/FS/part_pkg/bulk.pm: yup, bulk price plan appears to be
+ working
+
+2008-02-12 22:00 ivan
+
+ * README: modernizing
+
+2008-02-12 21:15 ivan
+
+ * FS/FS/cust_bill.pm: fix missing fill-in values on invoices
+ resulting from skewed hash ($conf->config returning empty list in
+ list context)
+
+2008-02-12 11:24 ivan
+
+ * httemplate/search/: rt_transaction.html, timeworked.html: for our
+ internal time tracking: TimeWorked can happen on a Create
+ transaction as well as Correspond and Comment. is there anywhere
+ else this needs to be changed?
+
+2008-02-11 19:11 ivan
+
+ * FS/FS/Misc.pm: update for MailTools v2.00
+
+2008-02-10 18:37 ivan
+
+ * FS/FS/reason.pm: upgrading reason table not quite working with
+ mysql, hopefully no old installs need this, new ones should be
+ fine hopefully
+
+2008-02-10 18:36 ivan
+
+ * FS/FS/: Upgrade.pm, cust_svc.pm, h_cust_svc.pm: fix & cleanup
+ duplicate history records
+
+2008-02-10 18:03 ivan
+
+ * FS/FS/reason.pm: better debugging if things go awry
+
+2008-02-10 16:53 ivan
+
+ * FS/FS/Cron/notify.pm: third time's the charm: MySQL didn't like
+ casting to INTEGER, it wants SIGNED. whew.
+
+2008-02-10 16:02 ivan
+
+ * FS/FS/Cron/notify.pm: ah, no, its count (*) vs count(*), wow
+
+2008-02-10 15:58 ivan
+
+ * FS/FS/Cron/notify.pm: comment out unused db statements in a way
+ that doesn't get to the database, to avoid Pg vs mysql
+ comment-out differences
+
+2008-02-09 11:16 ivan
+
+ * httemplate/edit/process/agent_type.cgi: typo, fix from mtou,
+ thanks
+
+2008-02-08 13:59 rsiddall
+
+ * rpm/freeside.spec: Removed code that create a freeside user on
+ the build machine and added code to modify Makefile to remove
+ clauses that require such a user. This should allow the RPMs to
+ build under mock.
+
+2008-02-08 07:00 jeff
+
+ * FS/FS/TicketSystem/RT_External.pm: tyop
+
+2008-02-04 19:37 ivan
+
+ * httemplate/edit/process/part_pkg.cgi: fix package editing on
+ HEAD, whew. bad semicolon.
+
+2008-02-02 18:24 ivan
+
+ * FS/FS/cust_main.pm: should eliminate "Use of uninitialized value
+ in length at /usr/local/share/perl/5.8.8/FS/cust_main.pm line
+ 5194." warnings
+
+2008-02-02 17:43 ivan
+
+ * httemplate/edit/cust_main.cgi: oops, fix display of errors on
+ customer edit
+
+2008-01-30 08:14 jeff
+
+ * httemplate/misc/bulk_change_pkg.cgi: pointless false laziness
+ removal
+
+2008-01-28 20:15 jeff
+
+ * FS/FS/cust_pkg.pm: missed use
+
+2008-01-28 19:56 jeff
+
+ * FS/FS/cust_pkg.pm: 1394 regression fixup
+
+2008-01-28 19:34 jeff
+
+ * FS/FS/cust_pkg.pm, httemplate/misc/bulk_change_pkg.cgi,
+ httemplate/misc/process/bulk_change_pkg.cgi,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/elements/search.html: bulk package changing
+ (RT#1394)
+
+2008-01-25 10:26 jeff
+
+ * httemplate/misc/cust_pay-import.cgi,
+ httemplate/misc/process/cust_pay-import.cgi, FS/FS/cust_pay.pm,
+ httemplate/elements/menu.html: simple payment CSV import
+
+2008-01-24 18:55 jeff
+
+ * FS/FS/part_export/prizm.pm: add an always BAM option and be
+ explicit about transactions
+
+2008-01-24 13:16 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ httemplate/view/cust_main/tickets.html: slightly more sane names
+ for customer tickets hash, display ticket owners on customer view
+ page
+
+2008-01-24 13:10 ivan
+
+ * htetc/handler.pl: used in rt autohandler. i wonder why this
+ never bit before
+
+2008-01-23 11:18 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/bill.html,
+ fs_selfservice/FS-SelfService/cgi/card.html,
+ fs_selfservice/FS-SelfService/cgi/change_bill.html,
+ fs_selfservice/FS-SelfService/cgi/change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/check.html,
+ fs_selfservice/FS-SelfService/cgi/contact.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_bill.html,
+ FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/make_ach_payment.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pay.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_ship.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: change
+ service, billing, and payment info in selfservice
+
+2008-01-20 16:12 ivan
+
+ * bin/explain-ar-total.sql: [no log message]
+
+2008-01-20 15:24 ivan
+
+ * FS/FS/Schema.pm: add and fix some indices, this should marginally
+ (but not drastically) improve the time to pull up A/R report
+ totals
+
+2008-01-16 20:23 ivan
+
+ * httemplate/edit/: part_pkg.cgi, process/part_pkg.cgi: fix ACLs to
+ allow the limited "package editing" of customizing customer
+ packages
+
+2008-01-16 19:18 ivan
+
+ * httemplate/view/cust_main/billing.html: don't show 'Bill now'
+ link unless the current user can...
+
+2008-01-13 16:22 ivan
+
+ * httemplate/: edit/part_bill_event.cgi,
+ elements/select-taxclass.html: maintenance on old events, yay.
+ fix for late charges without a taxclass, closes: RT#2988
+
+2008-01-13 16:17 ivan
+
+ * httemplate/edit/: cust_main.cgi, svc_www.cgi: fix tyops
+
+2008-01-13 15:31 ivan
+
+ * httemplate/search/timeworked.html: almost could work under mysql?
+ need TO_NUMBER and TO_CHAR equivalents
+
+2008-01-13 15:30 ivan
+
+ * httemplate/misc/timeworked.html: missing semicolon, doh
+
+2008-01-13 13:46 ivan
+
+ * httemplate/view/cust_pay.html: remove 1.7 ACL cruft on HEAD
+
+2008-01-13 13:45 ivan
+
+ * httemplate/view/cust_main/payment_history.html: remove 1.7.3
+ cruft
+
+2008-01-13 13:41 ivan
+
+ * httemplate/edit/: cust_bill_pay.cgi, process/cust_bill_pay.cgi,
+ cust_credit_bill.cgi, process/cust_credit_bill.cgi: new ACL name
+ in 1.9 right away
+
+2008-01-13 13:39 ivan
+
+ * httemplate/edit/: part_pkg.cgi, process/part_pkg.cgi: remove 1.7
+ vs. 1.9 cruft
+
+2008-01-13 13:35 ivan
+
+ * httemplate/edit/: REAL_cust_pkg.cgi, access_user.html, agent.cgi,
+ agent_payment_gateway.html, agent_type.cgi, bulk-cust_svc.html,
+ cust_bill_pay.cgi, cust_credit.cgi, cust_credit_bill.cgi,
+ cust_main.cgi, cust_main_county-expand.cgi, cust_main_note.cgi,
+ cust_pay.cgi, cust_pkg.cgi, cust_refund.cgi,
+ inventory_class.html, part_bill_event.cgi, part_export.cgi,
+ part_pkg.cgi, part_referral.html, part_svc.cgi,
+ part_virtual_field.cgi, payment_gateway.html, pkg_class.html,
+ prepay_credit.cgi, quick-charge.html, rate.cgi, rate_region.cgi,
+ reason.html, reason_type.html, reg_code.cgi, router.cgi,
+ svc_Common.html, svc_acct.cgi, svc_acct_pop.cgi,
+ svc_broadband.cgi, svc_domain.cgi, svc_external.cgi,
+ svc_forward.cgi, svc_phone.cgi, svc_www.cgi,
+ process/REAL_cust_pkg.cgi, process/access_user.html,
+ process/agent.cgi, process/agent_payment_gateway.html,
+ process/agent_type.cgi, process/bulk-cust_svc.cgi,
+ process/cust_bill_pay.cgi, process/cust_credit.cgi,
+ process/cust_credit_bill.cgi, process/cust_main.cgi,
+ process/cust_main_county-collapse.cgi,
+ process/cust_main_county-expand.cgi,
+ process/cust_main_county.html, process/cust_main_note.cgi,
+ process/cust_pay.cgi, process/cust_pkg.cgi,
+ process/cust_refund.cgi, process/cust_svc.cgi,
+ process/domain_record.cgi, process/generic.cgi,
+ process/inventory_class.html, process/msgcat.cgi,
+ process/part_bill_event.cgi, process/part_export.cgi,
+ process/part_pkg.cgi, process/part_referral.html,
+ process/part_svc.cgi, process/payment_gateway.html,
+ process/pkg_class.html, process/prepay_credit.cgi,
+ process/quick-charge.cgi, process/quick-cust_pkg.cgi,
+ process/rate.cgi, process/reason.html, process/reason_type.html,
+ process/reg_code.cgi, process/router.cgi,
+ process/svc_Common.html, process/svc_acct.cgi,
+ process/svc_acct_pop.cgi, process/svc_broadband.cgi,
+ process/svc_domain.cgi, process/svc_external.cgi,
+ process/svc_forward.cgi, process/svc_phone.html,
+ process/svc_www.cgi: ACLs
+
+2008-01-13 13:14 ivan
+
+ * httemplate/misc/: batch-cust_pay.html, bill.cgi,
+ cancel-unaudited.cgi, cancel_cust.html, cancel_pkg.html,
+ catchall.cgi, cdr-import.html, cust_main-cancel.cgi,
+ cust_main-import.cgi, cust_main-import_charges.cgi,
+ delete-cust_credit.cgi, delete-cust_pay.cgi,
+ delete-cust_refund.cgi, delete-customer.cgi,
+ delete-domain_record.cgi, delete-part_export.cgi, dump.cgi,
+ email-invoice.cgi, email_invoice_events.cgi, email_invoices.cgi,
+ fax-invoice.cgi, fax_invoice_events.cgi, fax_invoices.cgi,
+ inventory_item-import.html, link.cgi, meta-import.cgi,
+ payment.cgi, print-invoice.cgi, print_invoice_events.cgi,
+ print_invoices.cgi, queue.cgi, recharge_svc.html,
+ svc_acct-domains.cgi, unapply-cust_credit.cgi,
+ unapply-cust_pay.cgi, unprovision.cgi, unsusp_pkg.cgi,
+ unvoid-cust_pay_void.cgi, upload-batch.cgi, void-cust_pay.cgi,
+ whois.cgi, process/batch-cust_pay.cgi, process/cancel_pkg.html,
+ process/catchall.cgi, process/cdr-import.html,
+ process/cust_main-import.cgi,
+ process/cust_main-import_charges.cgi,
+ process/delete-customer.cgi, process/inventory_item-import.html,
+ process/link.cgi, process/meta-import.cgi, process/payment.cgi,
+ process/recharge_svc.html: ACLs
+
+2008-01-13 13:08 ivan
+
+ * httemplate/misc/: email_events.cgi, fax_events.cgi,
+ print_events.cgi, order_pkg.html: ACLs
+
+2008-01-13 12:55 ivan
+
+ * httemplate/: browse/part_bill_event.cgi, search/cust_event.html,
+ search/timeworked.html, search/cust_bill.html,
+ search/cust_bill_event.cgi, search/cust_pay_batch.cgi,
+ search/prepay_credit.html, search/queue.html,
+ search/report_prepaid_income.cgi, search/report_tax.cgi,
+ search/sqlradius.cgi: ACLs
+
+2008-01-13 12:50 ivan
+
+ * httemplate/elements/: errorpage.html, tr-select-reason.html:
+ stale leftovers from XSSmas
+
+2008-01-13 12:46 ivan
+
+ * httemplate/view/: cust_bill.cgi, svc_acct.cgi, svc_broadband.cgi,
+ svc_domain.cgi, svc_external.cgi, svc_forward.cgi, svc_www.cgi,
+ cust_main/payment_history.html, elements/svc_Common.html: ACLs
+
+2008-01-13 12:38 ivan
+
+ * httemplate/browse/: access_group.html, access_user.html,
+ addr_block.cgi, agent.cgi, agent_type.cgi, nas.cgi, part_pkg.cgi,
+ part_svc.cgi, part_virtual_field.cgi, payment_gateway.html,
+ reason.html, router.cgi: ACLs
+
+2008-01-13 12:30 ivan
+
+ * FS/FS/AccessRight.pm: new ACLs
+
+2008-01-12 16:35 ivan
+
+ * htetc/handler.pl: use this in handler.pl, not in mason files
+
+2008-01-11 15:10 ivan
+
+ * FS/FS/cust_main.pm: add option to enable searching of
+ cust_main.agent_custid with one or two letter prefix before the
+ numeric part
+
+2008-01-11 14:53 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: add option to enable searching of
+ cust_main.agent_custid with one or two letter prefix before the
+ numeric part
+
+2008-01-11 09:30 ivan
+
+ * httemplate/edit/elements/svc_Common.html: turn off debugging
+
+2008-01-10 14:13 ivan
+
+ * httemplate/search/elements/search.html: doc
+
+2008-01-10 14:06 ivan
+
+ * httemplate/elements/menu.html: new rate editor
+
+2008-01-10 13:53 ivan
+
+ * FS/FS/rate_detail.pm, httemplate/browse/rate.cgi,
+ httemplate/edit/rate.cgi, httemplate/edit/rate_region.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/rate_region.cgi: new rate editor
+
+2008-01-10 13:52 ivan
+
+ * httemplate/: browse/rate_detail.html, browse/rate_region.html,
+ edit/rate_detail.html, edit/process/rate_detail.html: new call
+ rate editor (VoIP/telephony)
+
+2008-01-10 13:17 ivan
+
+ * FS/FS/reason_type.pm: third person is better
+
+2008-01-10 13:14 ivan
+
+ * httemplate/browse/reason.html: eliminate harmless typo
+
+2008-01-08 13:33 jeff
+
+ * FS/FS/svc_acct.pm: transactiony cruft removal
+
+2008-01-08 03:23 ivan
+
+ * FS/FS/cust_credit.pm, FS/FS/cust_main.pm,
+ httemplate/search/report_receivables.cgi: show negative balances
+ on A/R report, closes: RT#2983
+
+2008-01-07 18:22 ivan
+
+ * httemplate/edit/process/cust_main.cgi: add some debugging control
+
+2008-01-03 18:42 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/cust_main_county-expand.cgi, edit/cust_main_county.html,
+ edit/process/cust_main_county-expand.cgi,
+ edit/process/cust_main_county.html,
+ edit/process/elements/process.html, search/elements/search.html:
+ new tax rate editor
+
+2008-01-03 18:35 ivan
+
+ * httemplate/: edit/cust_main_county.cgi,
+ edit/process/cust_main_county.cgi, browse/elements/browse.html:
+ new tax rate editor
+
+2008-01-03 18:27 ivan
+
+ * httemplate/edit/: prepay_credit.cgi, rate_region.cgi: leftovers
+ from XSSmas
+
+2008-01-03 18:20 ivan
+
+ * httemplate/elements/hidden.html: escape value
+
+2008-01-03 18:19 ivan
+
+ * httemplate/elements/: tablebreak-tr-title.html,
+ tr-fixed-country.html, tr-fixed-state.html,
+ tr-input-percentage.html: adding new elements: percentage input,
+ fixed country and state display, tablebreak+title
+
+2008-01-03 18:18 ivan
+
+ * httemplate/elements/tr-fixed.html: add a formatted_value option
+
+2008-01-03 18:18 ivan
+
+ * httemplate/elements/tr-input-text.html: add maxlength, text-align
+ and postfix options, escape value
+
+2008-01-03 18:13 ivan
+
+ * httemplate/edit/elements/edit.html: there's no such thing as
+ fixedhidden... hidden will do
+
+2008-01-03 18:02 ivan
+
+ * httemplate/edit/elements/edit.html: doc
+
+2008-01-03 17:43 ivan
+
+ * httemplate/edit/elements/edit.html: add popup option, add
+ percentage and tablebreak-tr-title field types, pass object to
+ included elements, documentation updates
+
+2007-12-28 11:02 jeff
+
+ * httemplate/: elements/menu.html, search/report_svc_acct.html,
+ search/svc_acct.cgi: advanced account reports (RT#2954)
+
+2007-12-28 07:19 jeff
+
+ * httemplate/elements/menu.html: whoops
+
+2007-12-28 07:10 jeff
+
+ * httemplate/: elements/menu.html, search/svc_acct.cgi: add never
+ logged in report
+
+2007-12-28 06:39 jeff
+
+ * FS/bin/freeside-sqlradius-set-lastlog: tool to seed
+ svc_acct.last_login and .last_logout
+
+2007-12-28 04:10 ivan
+
+ * htetc/handler.pl: remove just a tiny bit more cruft
+
+2007-12-28 04:01 ivan
+
+ * htetc/handler.pl: restore in-browser error reporting
+
+2007-12-27 23:45 ivan
+
+ * htetc/handler.pl: a little late help from kwanzabot for XSSmas...
+ clean up handler.pl so we can have separate default_escape_flags
+ for FS and RT and don't have to make FS's |h flag useless
+
+2007-12-27 17:41 jeff
+
+ * FS/FS/Schema.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/sqlradius.pm, httemplate/search/svc_acct.cgi,
+ httemplate/view/svc_acct.cgi: last login reporting (#2952)
+
+2007-12-27 15:49 ivan
+
+ * httemplate/search/report_tax.cgi: fix reporting bug for invisimax
+ in edge case where you have taxclass and empty-taxclass rates for
+ a country/state(/county) and also have a different set of
+ taxclasses for some other country/state
+
+2007-12-26 14:52 jeff
+
+ * FS/FS/part_export/prizm.pm: conifgurable siteName and docs
+
+2007-12-26 00:23 ivan
+
+ * httemplate/misc/timeworked.html: leftovers from XSSmas
+
+2007-12-25 23:51 ivan
+
+ * httemplate/edit/quick-charge.html: alas, XSSmas draws to a close
+
+2007-12-25 15:49 ivan
+
+ * httemplate/: browse/addr_block.cgi,
+ browse/part_virtual_field.cgi, browse/router.cgi,
+ config/config.cgi, edit/agent.cgi,
+ edit/agent_payment_gateway.html, edit/agent_type.cgi,
+ edit/cust_bill_pay.cgi, edit/cust_credit.cgi,
+ edit/cust_credit_bill.cgi, edit/cust_main_note.cgi,
+ edit/cust_pay.cgi, edit/cust_pkg.cgi, edit/cust_refund.cgi,
+ edit/msgcat.cgi, edit/part_bill_event.cgi, edit/part_export.cgi,
+ edit/part_pkg.cgi, edit/part_virtual_field.cgi,
+ edit/payment_gateway.html, edit/reg_code.cgi, edit/router.cgi,
+ edit/svc_acct.cgi, edit/svc_broadband.cgi, edit/svc_domain.cgi,
+ edit/svc_forward.cgi, edit/elements/edit.html,
+ elements/error.html, misc/batch-cust_pay.html,
+ misc/cancel_cust.html, misc/cancel_pkg.html, misc/change_pkg.cgi,
+ misc/recharge_svc.html, misc/process/meta-import.cgi,
+ search/cust_bill_event.html, search/report_cust_event.html: ho ho
+ ho, merry XSSmas
+
+2007-12-23 14:05 jeff
+
+ * FS/FS/part_pkg/flat.pm, FS/FS/part_pkg/prorate.pm,
+ FS/FS/part_pkg/subscription.pm,
+ httemplate/misc/process/recharge_svc.html: add a reset feature to
+ manual recharges (#1858)
+
+2007-12-20 11:23 jeff
+
+ * FS/FS/part_export/prizm.pm: increase siteName allowed length
+
+2007-12-19 14:25 jeff
+
+ * httemplate/misc/cust_main-cancel.cgi: redirect redirect
+
+2007-12-19 13:05 jeff
+
+ * FS/FS/ConfDefaults.pm, FS/FS/UI/Web.pm,
+ httemplate/search/cust_main.html,
+ httemplate/search/report_cust_main.html: add options for balance
+ over/under to advanced customer report
+
+2007-12-18 17:55 jeff
+
+ * FS/FS/cust_credit.pm: underscoring the important
+
+2007-12-18 14:41 jeff
+
+ * httemplate/: edit/cust_credit.cgi,
+ elements/tr-select-reason.html: correct bad credit reason
+ conflict resolution
+
+2007-12-18 12:58 ivan
+
+ * FS/FS/cust_main.pm: legacy agent_custid field should be as
+ searchable as actual custnum, right? at least if it is a
+ number...
+
+2007-12-18 12:42 ivan
+
+ * httemplate/edit/quick-charge.html: IE is case-sensitive when
+ setting maxLength (and other attributes?). hopefully that's all
+ it is.
+
+2007-12-17 15:57 jeff
+
+ * httemplate/view/cust_main/quick-charge.html: cruft removal
+
+2007-12-17 14:57 jeff
+
+ * FS/FS/cust_credit.pm: stricter otaker rules
+
+2007-12-17 11:59 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm: no, *that's* where it couldn't
+ hurt anything.
+
+2007-12-17 11:52 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: i don't think it can hurt
+ anything... (famous last words)
+
+2007-12-16 17:17 ivan
+
+ * httemplate/view/cust_main/contacts.html: fix the case where the
+ customer doesn't actually have a ship address
+
+2007-12-16 16:59 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/elements/header.html,
+ httemplate/view/cust_main/contacts.html,
+ httemplate/search/cust_main.cgi: add cust_main-require_address2
+ config, reimplement address2-search config ("Unit #" search in
+ searchbar), visual indication of require_invoicing_list_email,
+ closes: RT#2926
+
+2007-12-16 14:40 ivan
+
+ * httemplate/elements/errorpage.html: less is better
+
+2007-12-16 13:02 ivan
+
+ * httemplate/elements/header.html: more consistent naming:
+ cust_main_ADV.cgi becomes cust_main.html
+
+2007-12-16 13:01 ivan
+
+ * httemplate/search/report_cust_main.html:
+ httemplate/elements/header.html
+
+2007-12-16 12:48 ivan
+
+ * httemplate/search/: cust_main.html, cust_main_ADV.cgi: more
+ consistent naming: cust_main_ADV.cgi becomes cust_main.html
+
+2007-12-15 14:47 rsiddall
+
+ * rpm/: INSTALL, freeside.spec, freeside.sysconfig, rpm2Bundle:
+ Files to build Freeside as Redhat-ish RPMs
+
+2007-12-14 17:45 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/search/cust_bill.html: fix earlier
+ drain bramage
+
+2007-12-14 17:37 ivan
+
+ * httemplate/search/cust_bill.html: where helps alot
+
+2007-12-14 17:23 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/search/cust_bill.html: hopefully
+ put reprinting issues to rest for ejourney, yow
+
+2007-12-14 15:41 jeff
+
+ * FS/bin/freeside-upgrade: yikes
+
+2007-12-14 13:51 jeff
+
+ * FS/FS/Schema.pm: binary not required and breaks
+ FS::Record::_quote
+
+2007-12-14 13:32 jeff
+
+ * FS/bin/freeside-upgrade: correct order of operations
+
+2007-12-14 08:47 jeff
+
+ * FS/FS/cust_credit.pm: 1.9 Conf::set returns false on success
+
+2007-12-13 19:56 jeff
+
+ * FS/bin/freeside-upgrade: even more deliberate reset
+
+2007-12-13 19:55 ivan
+
+ * httemplate/search/svc_acct.cgi: quick hack to show time worked in
+ last 1-3 months...
+
+2007-12-12 16:17 jeff
+
+ * bin/reset-cust_credit-otaker: cust_credit reason/otaker tool
+
+2007-12-12 13:18 jeff
+
+ * FS/FS/part_export/www_shellcommands.pm: add suspend/unsuspend to
+ www_shellcommands export (#1227)
+
+2007-12-12 01:03 ivan
+
+ * FS/bin/freeside-history-requeue: adding this quick script
+
+2007-12-11 21:58 jeff
+
+ * FS/FS/Setup.pm, FS/FS/svc_acct.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/svc_acct.html,
+ httemplate/edit/svc_acct.cgi: self-service can select domain on
+ provision (#2801)
+
+2007-12-11 21:42 jeff
+
+ * FS/: FS/cust_credit.pm, FS/h_cust_credit.pm,
+ bin/freeside-disable-reasons, bin/freeside-upgrade: cust_credit
+ reason improvement, bugfix, and tool
+
+2007-12-06 17:08 ivan
+
+ * FS/FS/cust_credit.pm: doh! is not an object. let's call it ,
+ mmkay
+
+2007-12-06 17:04 ivan
+
+ * FS/FS/cust_credit.pm: some old databases may have 'hanging'
+ cust_credit records; we don't want to abort the upgrade because
+ of that, just warn
+
+2007-12-06 12:49 jeff
+
+ * httemplate/edit/part_pkg.cgi: agent setting during package edit
+ cleanup
+
+2007-12-06 08:51 jeff
+
+ * httemplate/view/svc_acct.cgi: support display of negative time
+
+2007-12-05 14:05 ivan
+
+ * Makefile: when installing selfservice, create freeside homedir if
+ it doesn't exist
+
+2007-12-05 12:50 ivan
+
+ * httemplate/elements/tr-select-reason.html: doh, order_by does
+ have to include ORDER BY!
+
+2007-12-05 12:47 ivan
+
+ * httemplate/elements/tr-select-reason.html: show types in reason
+ selection, order by type and reason
+
+2007-12-05 07:10 jeff
+
+ * FS/FS/ConfDefaults.pm: add in status and company fields
+
+2007-12-05 06:50 jeff
+
+ * FS/FS/cust_main.pm: ensure a valid date is onhand (fixes #2800)
+
+2007-12-04 20:57 jeff
+
+ * httemplate/edit/process/part_pkg.cgi: wtf?
+
+2007-12-04 16:45 ivan
+
+ * httemplate/search/elements/search.html: i hope this allows IE to
+ download excel over https?
+
+2007-12-04 12:51 ivan
+
+ * httemplate/: elements/menu.html, elements/select-otaker.html,
+ elements/tr-select-otaker.html, search/report_cust_credit.html,
+ search/report_rt_transaction.html, search/rt_transaction.html,
+ search/elements/search.html: add some time-worked reporting
+
+2007-12-04 10:35 jeff
+
+ * httemplate/search/cust_pkg.cgi: add suspend/cancel reason to
+ advanced package report (#2779)
+
+2007-12-04 10:19 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/Setup.pm, FS/FS/Upgrade.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_main.pm, FS/FS/Conf_compat17.pm, FS/FS/cust_pkg.pm,
+ FS/FS/reason.pm, FS/FS/reason_type.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/ClientAPI/Signup.pm,
+ FS/FS/part_pkg/flat_comission.pm,
+ FS/FS/part_pkg/flat_comission_cust.pm,
+ FS/FS/part_pkg/flat_comission_pkg.pm, FS/bin/freeside-upgrade,
+ httemplate/browse/reason.html,
+ httemplate/browse/reason_type.html,
+ httemplate/edit/cust_credit.cgi, httemplate/edit/reason.html,
+ httemplate/edit/reason_type.html,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/elements/menu.html,
+ httemplate/elements/tr-select-reason.html: change credit reasons
+ from freetext to new reason/reason type system (#2777)
+
+2007-12-02 16:18 ivan
+
+ * htetc/freeside-rt.conf: would help if i got the closing tag right
+
+2007-12-02 16:13 ivan
+
+ * htetc/freeside-rt.conf: this should get the Chart stuff in RT
+ statistic running, i hope
+
+2007-11-30 17:37 ivan
+
+ * FS/FS/part_pkg/prorate.pm: cleanup
+
+2007-11-30 17:34 ivan
+
+ * FS/FS/h_cust_svc.pm: add date_deleted method
+
+2007-11-30 17:34 ivan
+
+ * FS/FS/cust_svc.pm: add date_inserted method and reorganize things
+ slightly
+
+2007-11-30 17:33 ivan
+
+ * FS/FS/h_Common.pm: this doesn't actually have anything to do with
+ cancellation persay, since its generic now
+
+2007-11-30 17:32 ivan
+
+ * FS/FS/Record.pm: add h_date method too
+
+2007-11-30 17:30 ivan
+
+ * FS/FS/Record.pm: add h_search method, and reorganize mixed up
+ methods and subroutines
+
+2007-11-30 17:29 ivan
+
+ * FS/FS/part_pkg/bulk.pm: add bulk price plan
+
+2007-11-30 13:07 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/graph/money_time.cgi,
+ httemplate/search/cust_bill.html: add net vs gross amounts to
+ invoice report; make the "net sales" links on
+ sales/credit/receipts report & graph clickable
+
+2007-11-30 12:55 ivan
+
+ * httemplate/graph/elements/monthly.html: UI: download full results
+ links on separate lines, for consistency with search.html
+
+2007-11-30 10:13 ivan
+
+ * FS/FS/cust_bill.pm: fix bug specifying an HTML return address
+ separately
+
+2007-11-29 19:24 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/cust_main-import.cgi: add
+ customer import format with company
+
+2007-11-29 17:24 ivan
+
+ * httemplate/browse/access_user.html: update inline documentation
+
+2007-11-29 17:16 ivan
+
+ * FS/FS/cust_main.pm: remove unnecessary redefinition of
+ %method2payby, add a quick hack to fake B:OP success and failure
+ for testing purposes
+
+2007-11-29 15:51 ivan
+
+ * httemplate/edit/: REAL_cust_pkg.cgi, process/REAL_cust_pkg.cgi:
+ fix dates going all wacky on errors, when you're asked to confirm
+ a date move into the past
+
+2007-11-28 19:38 ivan
+
+ * FS/FS/cust_pay_pending.pm: double doh, remove cruft checking
+ nonexistent column and fix the statustext check
+
+2007-11-28 19:05 ivan
+
+ * FS/FS/Schema.pm: doh, fix primary key name in new table
+
+2007-11-28 18:54 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pay_pending.pm,
+ FS/t/cust_pay_pending.t, httemplate/misc/payment.cgi,
+ httemplate/misc/process/payment.cgi: even more reliable
+ multiple-payment/double-click/concurrent-payment-form protection
+
+2007-11-28 12:18 jeff
+
+ * httemplate/edit/: part_pkg.cgi, process/part_pkg.cgi: better
+ select multiple, discourage creation of packages no agent can
+ sell
+
+2007-11-28 10:49 jeff
+
+ * FS/FS/ConfDefaults.pm, FS/FS/UI/Web.pm,
+ httemplate/search/cust_main_ADV.cgi,
+ httemplate/search/report_cust_main.html: advanced customer report
+ rearrangement
+
+2007-11-28 00:46 ivan
+
+ * FS/FS/ClientAPI_SessionCache.pm, Makefile: default to FileCache
+ for selfservice session cache
+
+2007-11-27 17:54 ivan
+
+ * httemplate/search/cust_bill_event.cgi: removed unused \$failed
+ var
+
+2007-11-27 17:30 ivan
+
+ * README: license clarification for self-service code
+
+2007-11-27 15:47 ivan
+
+ * httemplate/search/cust_bill.html: fix small side-effect of
+ mysql-compat change eliminating DISTINCT ON: map changing values
+ of @where array
+
+2007-11-27 14:33 jeff
+
+ * FS/FS/part_export/prizm.pm: comma, please
+
+2007-11-27 14:30 jeff
+
+ * FS/FS/part_export/prizm.pm: name management tweaking
+
+2007-11-27 11:34 jeff
+
+ * FS/FS/part_export/prizm.pm: slop correction
+
+2007-11-26 19:02 jeff
+
+ * FS/FS/part_export/prizm.pm: attach SM's to default network,
+ improved unsuspend & delete, option for BAM only or EMS
+
+2007-11-26 18:51 jeff
+
+ * FS/FS/ClientAPI/MyAccount.pm: prevent BILL, DCRD, and DCHK
+ customers from circumventing a suspension via selfservice (2768)
+
+2007-11-25 18:19 ivan
+
+ * httemplate/search/elements/search.html: add a "printable copy"
+ link to searches to get full results as printable HTML without
+ other cruft, closes: #1885
+
+2007-11-25 18:18 ivan
+
+ * httemplate/elements/header-popup.html: add our stylesheet to
+ popup/print headers
+
+2007-11-23 17:26 ivan
+
+ * Makefile: hello brave new world of defaulting to apache2 in 1.9
+
+2007-11-23 12:25 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix stickiness of primary radio
+ buttons on errors, closes: RT#1035
+
+2007-11-21 17:03 ivan
+
+ * httemplate/edit/cust_main.cgi: usability: don't offer 'Select
+ agent' choice if the dropdown already has a value
+
+2007-11-20 09:18 ivan
+
+ * AGPL, GPL: it finally happened
+
+2007-11-18 12:02 ivan
+
+ * httemplate/search/: report_cust_pkg.html, svc_acct.cgi: add
+ "multiplier of monthly" info to "paid time" column in
+ svc_acct-display_paid_time_remaining report
+
+2007-11-14 15:10 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm,
+ httemplate/view/cust_main/tickets.html: add link to resolved
+ tickets too
+
+2007-11-09 11:20 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: add
+ business-onlinepayment-email_customer flag
+
+2007-11-08 19:26 ivan
+
+ * httemplate/search/report_receivables.html: better visual
+ indication of disabled "days" field on IE
+
+2007-11-08 19:12 ivan
+
+ * httemplate/search/: report_receivables.cgi,
+ report_receivables.html: add option for listing customers without
+ a balance (closes: RT#2752) and fix bug in total row (closes:
+ RT#2736)
+
+2007-11-08 16:44 jeff
+
+ * FS/FS/cust_main.pm: correct realtime_bop cvv handling
+
+2007-11-07 20:21 ivan
+
+ * FS/FS/: Conf.pm, ClientAPI/MyAccount.pm: add
+ selfservice-session_timeout config
+
+2007-11-07 18:10 ivan
+
+ * FS/FS/cust_main.pm: prevent warning: "Use of uninitialized value
+ in concatenation (.) or string at
+ /usr/local/share/perl/5.8.8/FS/cust_main.pm line 1668."
+
+2007-11-07 16:59 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/bin/freeside-expiration-alerter, conf/alerter_template,
+ conf/company_address, conf/company_name,
+ conf/impending_recur_template, conf/invoice_latexfooter,
+ conf/invoice_latexnotes, conf/invoice_latexreturnaddress,
+ conf/invoice_latexsmallfooter, conf/invoice_template,
+ conf/invoice_template_statement, conf/welcome_letter: for new
+ installs, centralize some stuff that was spread around different
+ config files.
+
+2007-11-05 17:59 ivan
+
+ * bin/customer-faker: keep our own list of states; remove some
+ states that Data::Faker and Locale::SubCountry disagree on
+
+2007-11-04 21:42 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: doc: amount param
+ to process_payment. just a bit important.
+
+2007-11-04 21:27 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add
+ process_payment_order_pkg method
+
+2007-11-04 10:22 jeff
+
+ * httemplate/search/cust_main_ADV.cgi: comma is a bad delimiter
+ choice according to ut_text()
+
+2007-11-03 10:38 ivan
+
+ * httemplate/elements/menu.html: move menu option for adv. customer
+ reports to second level, same as advanced invoice & package
+ reports
+
+2007-11-02 17:55 jeff
+
+ * httemplate/: elements/menu.html, search/cust_main_ADV.cgi,
+ search/report_cust_main.html: new customer report/flattened
+ package report (ticket 1428)
+
+2007-10-29 05:04 ivan
+
+ * httemplate/search/cust_pkg.cgi: bug ya bug resulting from
+ agent-virtualized packages
+
+2007-10-29 05:03 ivan
+
+ * httemplate/search/report_receivables.cgi: famous last mysql bug
+
+2007-10-29 04:34 ivan
+
+ * httemplate/edit/rate.cgi: yup, mysql-ing me harder
+
+2007-10-29 04:32 ivan
+
+ * httemplate/search/cdr.html: mysql
+
+2007-10-29 04:18 ivan
+
+ * FS/bin/freeside-setup: forgotton freeside-setup bits of mysql
+ locking workaround
+
+2007-10-29 03:31 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/part_event/Condition.pm,
+ FS/FS/part_event/Condition/balance_age.pm,
+ FS/FS/part_event/Condition/cust_bill_age.pm,
+ httemplate/search/cdr.html: mysql me 1.9 more times
+
+2007-10-29 03:30 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/h_Common.pm, httemplate/edit/rate.cgi,
+ httemplate/search/cust_bill.html,
+ httemplate/search/report_tax.cgi: mysql has no DISTINCT ON
+ either, sigh
+
+2007-10-28 18:08 ivan
+
+ * httemplate/view/cust_pay.html: still have link back to the
+ customer on printable receipt... should hide it from print at
+ some point though
+
+2007-10-28 05:51 ivan
+
+ * FS/: FS/Schema.pm, FS/Setup.pm, FS/svc_acct.pm,
+ bin/freeside-upgrade: finish mysql locking workaround
+
+2007-10-27 20:55 ivan
+
+ * FS/FS/Record.pm, FS/FS/cust_svc.pm,
+ FS/FS/part_export/sqlradius.pm, htetc/handler.pl,
+ httemplate/search/report_receivables.cgi: mysql me harder
+
+2007-10-26 21:46 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ httemplate/misc/process/payment.cgi,
+ httemplate/search/cust_pay.cgi, httemplate/view/cust_pay.html,
+ httemplate/view/cust_main/payment_history.html: simple payment
+ receipts in web interface, sorry arnie, RT#2738
+
+2007-10-25 12:01 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm, FS/FS/Schema.pm, Makefile:
+ mysql, yes, mysql.
+
+2007-10-25 01:29 ivan
+
+ * FS/bin/freeside-setup: fix error handling
+
+2007-10-22 15:29 ivan
+
+ * FS/FS/Report/Table/Monthly.pm: fix more fallout from
+ agent-virtualized packages
+
+2007-10-17 09:07 jayce
+
+ * FS/FS/cust_pkg.pm: insert_reason only worked if you passed in an
+ existing reason, the code to insert a new reason was in place,
+ but didn't let the new reason get used. Code to do this was
+ already in place, just not utilized.
+
+2007-10-14 16:14 ivan
+
+ * conf/invoice_html: this aligns better. i wonder how 1.9 loads up
+ config from here, though?
+
+2007-10-14 16:10 ivan
+
+ * FS/FS/cust_bill.pm: escape \dollar in invoice_latexnotes
+
+2007-10-14 13:07 ivan
+
+ * FS/FS/Cron/bill.pm: don't fill up memory with objects for every
+ customer being billed
+
+2007-10-13 18:51 ivan
+
+ * fs_selfservice/php/: freeside.class.php,
+ freeside.login_example.php, freeside_signup_example.php: adding
+ php examples
+
+2007-10-13 10:33 jeff
+
+ * httemplate/edit/: part_pkg.cgi, process/part_pkg.cgi: correct
+ agent_defaultpkg handling
+
+2007-10-10 13:54 ivan
+
+ * FS/FS/cust_main.pm: set expandtab
+
+2007-10-10 13:52 jayce
+
+ * FS/FS/cust_main.pm: With this line missing, cust_event objects
+ would not fully create (eventpart wasn't loading), causing the _X
+ methods to fail on ->part_event. Only noticed when you retried
+ events that had previously failed.
+
+2007-10-10 11:14 ivan
+
+ * httemplate/browse/part_pkg.cgi: REALLY fix the package browsing
+ this time, without messing up the ordering
+
+2007-10-10 10:55 ivan
+
+ * httemplate/edit/part_pkg.cgi: concurrent fix for package editing
+ also cleans this up slightly
+
+2007-10-10 10:50 jeff
+
+ * httemplate/edit/part_pkg.cgi: close:}
+
+2007-10-09 10:57 ivan
+
+ * FS/FS/cust_main.pm: be polite! don't spew debugging info unless
+ its asked for.
+
+2007-10-08 21:58 ivan
+
+ * FS/FS/Conf.pm: fix documenation links to point into wiki where
+ they belong
+
+2007-10-08 21:48 ivan
+
+ * FS/FS/: Conf.pm, TicketSystem/RT_External.pm: add
+ ticket_system-priority_reverse config
+
+2007-10-08 18:40 jeff
+
+ * httemplate/misc/timeworked.html: preserve order
+
+2007-10-08 18:06 ivan
+
+ * httemplate/misc/timeworked.html: interpolating the links would
+ help too
+
+2007-10-08 18:01 ivan
+
+ * httemplate/misc/timeworked.html: i think this should fix
+ timeworked subjects, and link to the transactions as well
+
+2007-10-08 16:48 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/view_support_details.html: line
+ up things better
+
+2007-10-08 16:32 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: self-service POD
+ cleanups
+
+2007-10-08 14:06 ivan
+
+ * FS/FS/part_event/Condition/cust_bill_owed.pm,
+ bin/freeside-migrate-events, httemplate/edit/part_event.html: add
+ cust_bill_owed as an implicit condition (whew), and make sure it
+ is added on migrations (also: fixed implicit conditions like this
+ which only apply to a subset of eventtables)
+
+2007-10-08 14:05 jeff
+
+ * httemplate/edit/part_pkg.cgi: disallow agent changing on cloned
+ packages
+
+2007-10-08 12:14 jeff
+
+ * htetc/handler.pl, httemplate/edit/rate_region.cgi,
+ httemplate/edit/reg_code.cgi,
+ httemplate/edit/process/cust_svc.cgi,
+ httemplate/edit/process/domain_record.cgi,
+ httemplate/edit/process/reg_code.cgi,
+ httemplate/elements/errorpage.html,
+ httemplate/elements/tr-select-part_referral.html,
+ httemplate/misc/bill.cgi, httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/delete-agent_payment_gateway.cgi,
+ httemplate/misc/delete-cust_credit.cgi,
+ httemplate/misc/delete-cust_pay.cgi,
+ httemplate/misc/delete-cust_refund.cgi,
+ httemplate/misc/delete-domain_record.cgi,
+ httemplate/misc/delete-part_export.cgi, httemplate/misc/dump.cgi,
+ httemplate/misc/inventory_item-import.html,
+ httemplate/misc/unapply-cust_credit.cgi,
+ httemplate/misc/unapply-cust_pay.cgi,
+ httemplate/misc/unprovision.cgi, httemplate/misc/unsusp_pkg.cgi,
+ httemplate/misc/unvoid-cust_pay_void.cgi,
+ httemplate/misc/upload-batch.cgi,
+ httemplate/misc/void-cust_pay.cgi,
+ httemplate/misc/process/cdr-import.html,
+ httemplate/misc/process/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import_charges.cgi,
+ httemplate/misc/process/inventory_item-import.html,
+ httemplate/misc/process/link.cgi,
+ httemplate/misc/process/payment.cgi,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_svc.html,
+ httemplate/search/inventory_item.html,
+ httemplate/search/reg_code.html, httemplate/search/sql.html,
+ httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_broadband.cgi: Remove remaining calls to
+ idiot/eidiot. Should fix 1479
+
+2007-10-05 13:41 ivan
+
+ * FS/FS/part_event/Condition/cust_bill_has_service.pm: reenalbe
+ this condition
+
+2007-10-05 12:53 jeff
+
+ * httemplate/misc/: timeworked.html, process/timeworked.html: new
+ ui for assigning support time
+
+2007-10-05 12:45 ivan
+
+ * httemplate/elements/tr-input-beginning_ending.html: fix date
+ selection on advanced invoice report, or future places where date
+ selection component is included multiple times
+
+2007-10-05 07:26 jayce
+
+ * httemplate/elements/tr-select-part_svc.html: Initial checkin, was
+ missing from the cust_bill_has_service condition checkin.
+
+2007-10-04 17:39 ivan
+
+ * FS/FS/: part_event_condition.pm, part_event/Condition.pm,
+ part_event/Condition/cust_bill_has_service.pm: add disable
+ ability to conditions & disable cust_bill_has_service, so the
+ condition edit page renders again
+
+2007-10-04 16:55 ivan
+
+ * FS/FS/part_event/Condition/once.pm: whitespace
+
+2007-10-04 16:52 ivan
+
+ * FS/FS/part_event/Condition/once.pm: such a dumb little thing, but
+ i think that should really do it. whew
+
+2007-10-04 16:41 ivan
+
+ * FS/FS/cust_main.pm: don't leave stray 'new' events around if they
+ don't pass their final condition check
+
+2007-10-04 16:40 ivan
+
+ * FS/FS/part_event/Condition/once.pm: hopefully fix once.pm
+ properly...
+
+2007-10-04 14:38 ivan
+
+ * FS/FS/part_event_condition.pm: add SKIP_CONDITION_SQL debugging
+ aid
+
+2007-10-04 12:14 ivan
+
+ * FS/FS/Cron/bill.pm: additional due_cust_event debugging
+
+2007-10-04 12:04 ivan
+
+ * FS/FS/cust_main.pm: nobody likes it when you forget ions
+
+2007-10-04 12:03 ivan
+
+ * FS/FS/cust_main.pm: additional due_cust_event debugging
+
+2007-10-04 11:28 ivan
+
+ * FS/FS/cust_main.pm: additional due_cust_event debugging
+
+2007-10-04 09:45 ivan
+
+ * httemplate/search/cust_bill_event.cgi: fix compilation error!
+
+2007-10-03 21:21 ivan
+
+ * FS/FS/cust_main.pm: i really hope this finally does it
+
+2007-10-03 21:09 ivan
+
+ * FS/FS/cust_main.pm: dot strikes again
+
+2007-10-03 21:08 ivan
+
+ * FS/FS/cust_main.pm: whew, i think this might actually fix it
+
+2007-10-03 20:48 ivan
+
+ * FS/FS/cust_main.pm: doh! does that really fix agent-specific
+ agent template migration from 1.7? sure hope so
+
+2007-10-03 20:37 ivan
+
+ * FS/FS/Record.pm: add debugging option to qsearch
+
+2007-10-03 19:52 ivan
+
+ * bin/pod2x: wikify individual files
+
+2007-10-03 19:22 ivan
+
+ * bin/pod2x: fix FS::SelfService::XMLRPC pod generation
+
+2007-10-03 19:15 ivan
+
+ * FS/FS/cust_main.pm: add missing =back
+
+2007-10-03 19:07 ivan
+
+ * bin/pod2x: bah! THIS should fix munging of non-internal links, i
+ hope
+
+2007-10-03 19:06 jeff
+
+ * FS/FS/UI/bytecount.pm, FS/FS/part_pkg/flat.pm,
+ FS/FS/part_pkg/prorate.pm, FS/FS/part_pkg/subscription.pm,
+ httemplate/edit/process/part_pkg.cgi: support part_pkg option
+ input validation, check bytecounts and allow commas (closes 1863)
+
+2007-10-03 18:56 ivan
+
+ * bin/pod2x: this *should* fix munging of non-internal links, i
+ hope
+
+2007-10-03 18:49 ivan
+
+ * Makefile, bin/pod2x: wiki documentation convertor now
+
+2007-10-03 18:47 ivan
+
+ * FS/FS.pm: add missing CLI utilities to FS.pm too
+
+2007-10-03 18:45 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: fixing wrong POD NAME doc
+
+2007-10-03 18:29 ivan
+
+ * FS/t/cust_tax_exempt.pm: wtf!
+
+2007-10-03 18:21 ivan
+
+ * FS/FS.pm: update FS base page for new stuff in 1.9
+
+2007-10-03 18:15 ivan
+
+ * FS/: FS.pm: update top-level FS manpage
+
+2007-10-03 17:51 ivan
+
+ * FS/FS/: access_right.pm, access_user_pref.pm,
+ access_usergroup.pm, pay_batch.pm: POD documentation updates
+
+2007-10-02 14:56 ivan
+
+ * FS/FS/cust_event.pm: doh!
+
+2007-10-02 08:31 jeff
+
+ * FS/FS/cust_bill_event.pm, FS/FS/UI/Web.pm,
+ httemplate/search/cust_bill_event.cgi: fix up re-email these
+ events
+
+2007-10-02 08:11 jeff
+
+ * httemplate/misc/process/timeworked.html: missed file in self
+ service support usage improvements
+
+2007-10-01 17:44 ivan
+
+ * httemplate/: browse/part_referral.html, elements/menu.html:
+ Configuration right no longer gives access to things controlled
+ by their own ACLs (advertising source & package edit
+
+2007-10-01 17:40 ivan
+
+ * httemplate/elements/menu.html: package definitions controlled by
+ their own ACL now
+
+2007-10-01 17:32 ivan
+
+ * FS/bin/freeside-daily: need the space
+
+2007-10-01 17:31 ivan
+
+ * FS/FS/Schema.pm: this flag has nothing to do with recur tax :)
+
+2007-10-01 17:29 ivan
+
+ * FS/FS/Cron/bill.pm: fix small problems with earlier commit: add
+ back earlier commits reverted by this patch, also revert
+ gratuitous whitespace changes
+
+2007-10-01 17:22 ivan
+
+ * FS/FS/cust_event.pm: proper fix for once.pm bug? hopefully it
+ works :)
+
+2007-10-01 17:18 ivan
+
+ * FS/FS/part_event/: Condition.pm, Condition/once.pm: proper fix
+ for once.pm bug? hopefully it works :)
+
+2007-09-29 17:26 ivan
+
+ * httemplate/search/: cdr.html, report_cdr.html: better CDR
+ searching
+
+2007-09-29 17:25 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: fix argument stickiness on
+ date editing errors (especially because resetting them pops up
+ the error confirmation)
+
+2007-09-29 17:23 ivan
+
+ * FS/FS/cust_main.pm: this might fix agent-specific invoicing when
+ migrated from 1.7?
+
+2007-09-29 17:22 ivan
+
+ * bin/freeside-upgrade-unicode: [no log message]
+
+2007-09-28 19:17 ivan
+
+ * FS/FS/access_user.pm, httemplate/search/cust_event.html,
+ httemplate/search/report_tax.cgi: fix ambiguous agentnum errors
+ in cust_event.html & report_tax.cgi (provide a table option in
+ access_user::agentnums_sql so this is easy to fix for other
+ reports too)
+
+2007-09-27 17:33 jayce
+
+ * FS/FS/cust_main.pm: Fixes a bug where retry_realtime isn't
+ limiting to that user's events, therefore selecting all retryable
+ events, and having to filter later. On a system with a running
+ history, this caused bad O() types of situations for performance,
+ especially bad when this was called by something that a user
+ would be awaiting feedback from
+
+2007-09-27 17:19 jayce
+
+ * FS/FS/Record.pm: Cleanup qsearch for readability, removes heinous
+ map {} of several hundred lines into two simple method calls
+
+2007-09-27 14:25 jeff
+
+ * httemplate/edit/svc_acct.cgi: hide fixed passwords
+
+2007-09-27 11:24 jayce
+
+ * FS/: FS/Schema.pm, FS/cust_main.pm, FS/Cron/bill.pm,
+ bin/freeside-daily: Multi-System Billing: with a -m flag, daily
+ will queue billing jobs instead of running each. freeside-queued
+ will then pick it up, allowing multiple simultaneous jobs to run,
+ as well as multiple machines. Also adds a 'Secure' column to the
+ queue system, allowing for billing jobs to define themselves as
+ 'secure only' in cases where a box might be using the encryption.
+ This allows you to run secure only jobs (such as a collect) on
+ boxes that can.
+
+2007-09-26 12:27 jayce
+
+ * FS/FS/part_event/Condition/cust_bill_has_service.pm: Initial
+ import. Condition tests to see if the Invoice bills for a
+ particular service.
+
+2007-09-26 12:24 jayce
+
+ * FS/FS/part_event/Condition/once.pm: Event loop changes after
+ initial creation caused this to never trigger, as the event loop
+ creates a 'NEW' record, as a placeholder. We need to not die
+ from that.
+
+2007-09-26 09:06 ivan
+
+ * httemplate/browse/part_pkg.cgi: fix package browse query, doh
+
+2007-09-24 08:47 jeff
+
+ * FS/FS/Schema.pm, FS/FS/acct_rt_transaction.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/TicketSystem/RT_External.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_support_details.html:
+ self-service support usage improvements (1733)
+
+2007-09-23 17:56 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Schema.pm, FS/FS/part_event.pm,
+ FS/FS/part_pkg.pm, httemplate/browse/part_pkg.cgi: beginning of
+ agent-virtualization of packages
+
+2007-09-23 16:17 ivan
+
+ * httemplate/edit/svc_www.cgi: i think this is a bug; should not
+ show the svc_www.usersvc selector unless it is non-fixed or
+ non-blank, NOT non-fixed or blank
+
+2007-09-23 16:16 ivan
+
+ * FS/FS/Record.pm: remove compat with pre-0.33 DBIx::DBSchema
+
+2007-09-23 13:21 ivan
+
+ * FS/FS/Conf.pm, httemplate/search/svc_acct.cgi: accounts w/time
+ remaining search
+
+2007-09-23 13:00 ivan
+
+ * httemplate/search/svc_acct.cgi: accounts w/time remaining search
+
+2007-09-23 12:54 ivan
+
+ * httemplate/search/svc_acct.cgi: accounts w/time remaining search
+
+2007-09-23 12:18 ivan
+
+ * httemplate/search/elements/search.html: remove extraneous blank
+ line causing Excel exports to fail
+
+2007-09-23 11:32 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/elements/menu.html,
+ httemplate/search/svc_acct.cgi: accounts w/time remaining search
+
+2007-09-22 15:45 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/myaccount_menu.html: fix
+ menubar border in example self-service template
+
+2007-09-22 14:31 ivan
+
+ * httemplate/config/: config-view.cgi, config.cgi: improve config
+ UI, especially for textareas
+
+2007-09-22 12:40 ivan
+
+ * httemplate/view/svc_acct.cgi: UI
+
+2007-09-22 12:03 ivan
+
+ * FS/FS/svc_domain.pm: have to import tld_exists function if we're
+ going to use it
+
+2007-09-22 11:27 ivan
+
+ * httemplate/view/svc_acct.cgi: add a list of any hosts associated
+ with accounts
+
+2007-09-20 19:24 ivan
+
+ * httemplate/search/timeworked.html: fix problems with time queue
+ search: ticket subjects need to be HTML-escaped, 'remaining time'
+ calculation had a NULL vs 0 issue, and link to tickets
+
+2007-09-19 15:38 ivan
+
+ * rt/lib/RT/SearchBuilder.pm: we need SearchBuilder 1.48 to avoid
+ annoying Pg bugs causing "This user's 10 highest priority
+ tickets" to be blank
+
+2007-09-18 17:27 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/myaccount.html: support custom
+ priorities
+
+2007-09-18 17:12 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/search/cust_bill.html: fix at
+ least one small problem with reprint/email/fax functionality: now
+ should understand the "most recent invoice per customer" and
+ invoice # min/max options
+
+2007-09-18 16:19 ivan
+
+ * httemplate/edit/cust_main.cgi: fix advertising source stickiness
+ when page is reloaded with an error
+
+2007-09-18 15:51 ivan
+
+ * rt/etc/RT_SiteConfig.pm: MyTicketsLength has been replaced with
+ an official upstream config
+
+2007-09-18 14:41 ivan
+
+ * httemplate/search/svc_broadband.cgi: and parens help alot, doh
+
+2007-09-18 14:37 ivan
+
+ * httemplate/search/svc_broadband.cgi: fix svc_broadband search by
+ svcpart (links from browse/part_svc, in particular
+
+2007-09-18 10:07 ivan
+
+ * FS/FS/Schema.pm: add missing column access_user_pref.expiration
+
+2007-09-17 23:32 jeff
+
+ * FS/FS/: cust_pkg.pm, part_pkg/base_rate.pm, part_pkg/flat.pm,
+ part_pkg/prorate.pm, part_pkg/subscription.pm: allow assignment
+ of auto recharge values AND rollover
+
+2007-09-17 23:07 jeff
+
+ * FS/FS/Cron/expire_user_pref.pm: auto commit for vacuum
+
+2007-09-17 17:21 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/myaccount.html: close table
+ tag; fix spacing
+
+2007-09-17 14:12 ivan
+
+ * FS/FS/cust_main.pm: fix scoping issues with $1 and $2 resulting
+ in payinfo trying getting set to xxEK, thanks to _vlad_ for
+ tracking this down and providing a fix
+
+2007-09-17 13:52 ivan
+
+ * FS/FS/Conf.pm: elaborate on ambiguous instructions
+
+2007-09-13 20:45 ivan
+
+ * httemplate/edit/process/quick-charge.cgi: Fix 'Can't use an
+ undefined value as an ARRAY reference at
+ /usr/local/share/perl/5.8.8/FS/cust_main.pm line 4383.' error
+
+2007-09-13 15:35 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/acct_rt_transaction.pm,
+ FS/FS/svc_acct_rt_transaction.pm, FS/t/acct_rt_transaction.t,
+ FS/t/svc_acct_rt_transaction.t, htetc/handler.pl,
+ httemplate/misc/process/timeworked.html,
+ httemplate/search/timeworked.html: rename svc_acct_rt_transaction
+ to acct_rt_transaction, as it is not a service, its something
+ that hangs off of an svc_acct (like an acct_snarf). thank
+ goodness was able to do this before its any sort of migration
+ problem...
+
+2007-09-13 15:25 ivan
+
+ * httemplate/browse/part_svc.cgi: random indentation fix
+
+2007-09-13 15:25 ivan
+
+ * FS/bin/freeside-upgrade: some notes about old part_svc columns,
+ since DBIx::DBSchema 0.33+ will now drop them...
+
+2007-09-13 15:24 ivan
+
+ * CREDITS: too little, too late attempt at spam reduction
+
+2007-09-13 15:05 ivan
+
+ * httemplate/search/cust_main.cgi: fix status color on customer
+ list
+
+2007-09-13 15:05 ivan
+
+ * httemplate/search/report_receivables.cgi: clean up some unused
+ code
+
+2007-09-13 13:16 ivan
+
+ * httemplate/misc/cust_main-import.cgi: add better inline docs
+ explaining fields on customer CSV import
+
+2007-09-12 13:40 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: don't want a fatal error when
+ we can't fetch the name for a queue...
+
+2007-09-11 22:38 ivan
+
+ * bin/freeside-migrate-events: freeside-migrate-events now moves
+ event options, whew
+
+2007-09-11 21:23 ivan
+
+ * FS/FS/cust_event.pm: fix problems migrating weird statustext from
+ cust_bill_event records
+
+2007-09-11 21:23 ivan
+
+ * FS/FS/cust_bill_event.pm: prevent (less than) and (greater than)
+ in statustext message (or anything else weird) from causing them
+ not to get inserted
+
+2007-09-11 21:20 ivan
+
+ * bin/freeside-migrate-events: fix for freeside-migrate-events bug
+ that would throw a duplicate row error instead of completing the
+ migration
+
+2007-09-11 20:39 ivan
+
+ * FS/FS/svc_domain.pm: fix "Useless use of a variable in void
+ context at /usr/local/share/perl/5.8.8/FS/svc_domain.pm line
+ 367." that might have possible set an incorrest svc_domain.suffix
+
+2007-09-10 18:33 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: add cust_main-require_phone and
+ cust_main-require_invoicing_list_email options
+
+2007-09-10 17:28 ivan
+
+ * FS/FS/cust_main.pm: better default paydate for letters
+
+2007-09-06 20:45 ivan
+
+ * FS/FS/cust_pkg.pm: default cancellation subject so the emails
+ work even when people don't set one in config
+
+2007-09-06 20:36 ivan
+
+ * FS/FS/part_export/sqlradius.pm: tweak debugging
+
+2007-08-23 01:23 ivan
+
+ * FS/FS/: Conf.pm, part_export/sqlradius.pm: fix bug where user
+ could log in to RADIUS with uppercase usernameand avoid bandwidth
+ acconting (mysql is case-insensitive? wtf! thanks a fucking lot
+ for nothing, grr!)
+
+2007-08-17 07:01 jeff
+
+ * FS/FS/cust_main.pm: miss use
+
+2007-08-17 06:59 jeff
+
+ * httemplate/misc/process/timeworked.html: access control
+
+2007-08-16 21:27 ivan
+
+ * Makefile: remove redunant redundancy
+
+2007-08-16 11:22 jeff
+
+ * FS/FS/svc_acct.pm: correct labels in service definition edit for
+ svc_acct (#1745)
+
+2007-08-16 10:40 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: remove debugging
+
+2007-08-16 09:19 jeff
+
+ * FS/bin/freeside-upgrade: agent_custid bugfix (1746)
+
+2007-08-16 06:40 jeff
+
+ * FS/FS/svc_acct_rt_transaction.pm, FS/FS/AccessRight.pm,
+ FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/t/svc_acct_rt_transaction.t,
+ httemplate/misc/batch-cust_pay.html,
+ httemplate/misc/timeworked.html, FS/MANIFEST,
+ httemplate/misc/process/timeworked.html,
+ httemplate/search/timeworked.html, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ htetc/handler.pl, httemplate/elements/menu.html,
+ httemplate/view/cust_main/tickets.html: support hours 'usage'
+ tracking for our own internal use (#1733)
+
+2007-08-15 17:09 ivan
+
+ * FS/FS/cust_main.pm: have generate_letter assume a default paydate
+ of 2037-12 so it doesn't bomb out on empty paydates
+
+2007-08-15 16:46 ivan
+
+ * FS/bin/freeside-sqlradius-reset: add -n option to
+ freeside-sqlradius-reset to supress deleting data
+
+2007-08-15 14:51 ivan
+
+ * httemplate/edit/cust_main.cgi: fix customer status color here too
+
+2007-08-14 20:26 ivan
+
+ * httemplate/browse/part_export.cgi: little more low-hanging fruit:
+ table-grid the export listing
+
+2007-08-14 14:46 ivan
+
+ * FS/FS/Report/Table/Monthly.pm, httemplate/elements/menu.html,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/cust_pkg.cgi, httemplate/graph/money_time.cgi,
+ httemplate/graph/report_cust_pkg.html,
+ httemplate/graph/elements/monthly.html,
+ httemplate/browse/agent.cgi: add package churn report/graph
+
+2007-08-14 13:18 ivan
+
+ * FS/FS/Cron/bill.pm: removing unneeded %saw var
+
+2007-08-13 19:40 ivan
+
+ * httemplate/elements/tr-select-cust_pkg-status.html: restore label
+ on package status selector
+
+2007-08-10 16:50 ivan
+
+ * bin/dbdef-create, FS/bin/freeside-dbdef-create: rename
+ dbdef-create to freeside-dbdef-create and move it to FS/bin, so
+ it gets %%%FREESIDE_CONF%%% substituted
+
+2007-08-10 15:48 ivan
+
+ * FS/FS/part_pkg.pm: add 3 day, 4 month and 4.5 month (137 day)
+ frequencies
+
+2007-08-10 00:01 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_pkg.pm,
+ httemplate/view/cust_main/packages.html: on changing packages,
+ don't set setup date unless old package has one
+
+2007-08-08 17:31 ivan
+
+ * FS/FS/svc_domain.pm: add TLD checking to svc_domain
+
+2007-08-08 12:18 ivan
+
+ * FS/FS/cust_pkg.pm: fix otaker regex
+
+2007-08-07 13:05 ivan
+
+ * FS/FS/cust_main.pm: more informative error msg
+
+2007-08-06 12:45 jeff
+
+ * fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm:
+ documentation improvements
+
+2007-08-06 01:51 jeff
+
+ * fs_selfservice/FS-SelfService/MANIFEST: there is a server
+
+2007-08-06 01:46 jeff
+
+ * fs_selfservice/FS-SelfService/: MANIFEST, Makefile.PL,
+ freeside-selfservice-xmlrpc-server, SelfService/XMLRPC.pm,
+ cgi/xmlrpc.cgi: add selfservice xmlrpc facilities (#591)
+
+2007-08-05 10:32 jeff
+
+ * FS/FS/ClientAPI/MyAccount.pm: eeb! fix realtime recharge
+
+2007-08-03 19:06 ivan
+
+ * rt/etc/RT_SiteConfig.pm: fix URL handling on RT redirects
+
+2007-08-03 18:58 ivan
+
+ * Makefile: update Makefile to always substitute in
+ RT_SiteConfig.pm when running install-rt
+
+2007-08-03 18:57 ivan
+
+ * httemplate/elements/header.html: point 1.9 to 1.9 documentation
+
+2007-08-03 17:13 ivan
+
+ * rt/: FREESIDE_MODIFIED, Makefile, config.log, config.status,
+ bin/mason_handler.fcgi, bin/mason_handler.scgi,
+ bin/mason_handler.svc, bin/rt-crontool, bin/rt-mailgate,
+ lib/RT.pm: clean up corners & colors
+
+2007-08-03 15:43 ivan
+
+ * rt/etc/: RT_Config.pm, RT_Config.pm.in: fix path so we can find
+ .css files
+
+2007-08-03 15:06 ivan
+
+ * rt/FREESIDE_MODIFIED: [no log message]
+
+2007-08-03 14:49 ivan
+
+ * rt/: FREESIDE_MODIFIED, etc/RT_SiteConfig.pm,
+ lib/RT/SearchBuilder.pm, sbin/rt-setup-database.in: merging
+ RT_3_6_4 to HEAD
+
+2007-08-02 16:44 ivan
+
+ * httemplate/view/cust_main/packages.html: increase package popup
+ width
+
+2007-08-02 16:09 ivan
+
+ * httemplate/: edit/process/cust_pkg.cgi,
+ elements/select-table.html, misc/change_pkg.cgi,
+ misc/order_pkg.html, view/cust_main/packages.html: fix slowness
+ on change package and also make it into a popup
+
+2007-08-02 16:01 ivan
+
+ * httemplate/elements/select-cust-part_pkg.html: fix slowness on
+ change package and also make it into a popup
+
+2007-08-02 15:55 ivan
+
+ * FS/FS/CGI.pm: fix status color on small_custview
+
+2007-08-02 14:57 ivan
+
+ * httemplate/view/cust_main/packages.html: line up package acitons
+ all on one line
+
+2007-08-02 12:54 ivan
+
+ * rt/: lib/RT/I18N/tr.po, lib/RT/Report/Tickets.pm,
+ lib/RT/Report/Tickets/Entry.pm,
+ docs/design_docs/ruleset-workflow.txt, etc/upgrade/3.5.1/content:
+ Initial revision
+
+2007-08-02 12:51 ivan
+
+ * rt/lib/RT/: Reminders.pm, Interface/Web/Menu.pm,
+ Interface/Web/Menu/Item.pm, Search/Googleish.pm, I18N/sv.po:
+ Initial revision
+
+2007-08-01 15:33 ivan
+
+ * FS/FS/Schema.pm: i guess svc_www.usersvc can be null...
+
+2007-08-01 15:24 ivan
+
+ * ANNOUNCE.1.5, CREDITS, FS/MANIFEST, FS/README,
+ FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/Record.pm,
+ FS/FS/Schema.pm, FS/FS/Setup.pm, FS/FS/access_group.pm,
+ FS/FS/access_user.pm, FS/FS/access_user_pref.pm, FS/FS/agent.pm,
+ FS/FS/cust_bill.pm, FS/FS/cust_credit.pm, FS/FS/cust_event.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pay.pm, FS/FS/cust_pay_batch.pm,
+ FS/FS/cust_pkg.pm, FS/FS/cust_refund.pm, FS/FS/m2name_Common.pm,
+ FS/FS/option_Common.pm, FS/FS/part_bill_event.pm,
+ FS/FS/part_event.pm, FS/FS/part_event_condition.pm,
+ FS/FS/part_event_condition_option.pm,
+ FS/FS/part_event_condition_option_option.pm,
+ FS/FS/part_event_option.pm, FS/FS/pay_batch.pm, FS/FS/payby.pm,
+ FS/FS/pkg_referral.pm, FS/FS/svc_Common.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_domain.pm, FS/FS/svc_forward.pm, FS/FS/svc_www.pm,
+ FS/FS/Cron/bill.pm, FS/FS/Cron/expire_user_pref.pm,
+ FS/FS/part_event/Action.pm, FS/FS/part_event/Condition.pm,
+ FS/FS/part_event/Action/addpost.pm,
+ FS/FS/part_event/Action/apply.pm,
+ FS/FS/part_event/Action/bill.pm,
+ FS/FS/part_event/Action/cancel.pm,
+ FS/FS/part_event/Action/collect.pm,
+ FS/FS/part_event/Action/cust_bill_batch.pm,
+ FS/FS/part_event/Action/cust_bill_comp.pm,
+ FS/FS/part_event/Action/cust_bill_fee_percent.pm,
+ FS/FS/part_event/Action/cust_bill_realtime_card.pm,
+ FS/FS/part_event/Action/cust_bill_realtime_check.pm,
+ FS/FS/part_event/Action/cust_bill_realtime_lec.pm,
+ FS/FS/part_event/Action/cust_bill_send.pm,
+ FS/FS/part_event/Action/cust_bill_send_agent.pm,
+ FS/FS/part_event/Action/cust_bill_send_alternate.pm,
+ FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm,
+ FS/FS/part_event/Action/cust_bill_send_if_newest.pm,
+ FS/FS/part_event/Action/cust_bill_spool_csv.pm,
+ FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm,
+ FS/FS/part_event/Action/fee.pm,
+ FS/FS/part_event/Action/suspend.pm,
+ FS/FS/part_event/Action/suspend_if_pkgpart.pm,
+ FS/FS/part_event/Action/suspend_unless_pkgpart.pm,
+ FS/FS/part_event/Condition/agent.pm,
+ FS/FS/part_event/Condition/agent_type.pm,
+ FS/FS/part_event/Condition/balance.pm,
+ FS/FS/part_event/Condition/balance_age.pm,
+ FS/FS/part_event/Condition/balance_under.pm,
+ FS/FS/part_event/Condition/cust_bill_age.pm,
+ FS/FS/part_event/Condition/cust_bill_owed.pm,
+ FS/FS/part_event/Condition/cust_bill_owed_under.pm,
+ FS/FS/part_event/Condition/cust_pay_batch_declined.pm,
+ FS/FS/part_event/Condition/cust_status.pm,
+ FS/FS/part_event/Condition/every.pm,
+ FS/FS/part_event/Condition/once.pm,
+ FS/FS/part_event/Condition/payby.pm,
+ FS/FS/part_event/Condition/pkg_class.pm,
+ FS/FS/part_event/Condition/pkg_status.pm,
+ FS/FS/part_export/textradius.pm, FS/FS/part_pkg/flat.pm,
+ FS/FS/part_pkg/flat_delayed.pm, FS/FS/part_pkg/prorate.pm,
+ FS/FS/part_pkg/prorate_delayed.pm,
+ FS/FS/part_pkg/subscription.pm, FS/bin/freeside-daily,
+ FS/bin/freeside-monthly, FS/t/cust_event.t,
+ FS/t/part_event-Action.t, FS/t/part_event-Condition.t,
+ FS/t/part_event.t, FS/t/part_event_condition.t,
+ FS/t/part_event_condition_option.t,
+ FS/t/part_event_condition_option_option.t,
+ FS/t/part_event_option.t, FS/t/pkg_referral.t,
+ bin/freeside-migrate-events, eg/part_event-Action-template.pm,
+ eg/part_event-Condition-template.pm, fs_selfservice/DEPLOY,
+ htetc/handler.pl, httemplate/browse/access_group.html,
+ httemplate/browse/access_user.html, httemplate/browse/agent.cgi,
+ httemplate/browse/invoice_template.html,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/browse/part_event.html,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.html,
+ httemplate/browse/pkg_class.html, httemplate/browse/reason.html,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/edit/access_group.html, httemplate/edit/agent.cgi,
+ httemplate/edit/cust_main.cgi, httemplate/edit/invoice_logo.html,
+ httemplate/edit/invoice_template.html,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_event.html, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_referral.html, httemplate/edit/reason.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/access_group.html,
+ httemplate/edit/process/invoice_logo.html,
+ httemplate/edit/process/invoice_template.html,
+ httemplate/edit/process/part_event.html,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/edit/process/elements/process.html,
+ httemplate/elements/checkboxes-table-name.html,
+ httemplate/elements/freeside.css,
+ httemplate/elements/hidden.html, httemplate/elements/menu.html,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-agent_type.html,
+ httemplate/elements/select-cust_main-status.html,
+ httemplate/elements/select-cust_pkg-status.html,
+ httemplate/elements/select-part_referral.html,
+ httemplate/elements/select-pkg_class.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/selectlayers.html,
+ httemplate/elements/table-grid.html,
+ httemplate/elements/tr-checkbox-multiple.html,
+ httemplate/elements/tr-checkbox.html,
+ httemplate/elements/tr-fixed.html,
+ httemplate/elements/tr-freq.html,
+ httemplate/elements/tr-input-money.html,
+ httemplate/elements/tr-input-text.html,
+ httemplate/elements/tr-password.html,
+ httemplate/elements/tr-select-agent.html,
+ httemplate/elements/tr-select-agent_type.html,
+ httemplate/elements/tr-select-cust_main-status.html,
+ httemplate/elements/tr-select-cust_pkg-status.html,
+ httemplate/elements/tr-select-invoice_template.html,
+ httemplate/elements/tr-select-part_pkg.html,
+ httemplate/elements/tr-select-part_referral.html,
+ httemplate/elements/tr-select-pkg_class.html,
+ httemplate/elements/tr-select-reason.html,
+ httemplate/elements/tr-select-taxclass.html,
+ httemplate/elements/tr-select.html,
+ httemplate/elements/tr-selectlayers.html,
+ httemplate/elements/tr-selectmultiple-part_pkg.html,
+ httemplate/elements/tr-td-label.html,
+ httemplate/elements/tr-title.html,
+ httemplate/elements/fckeditor/fckconfig.js,
+ httemplate/elements/fckeditor/fckeditor.js,
+ httemplate/elements/fckeditor/fckpackager.xml,
+ httemplate/elements/fckeditor/fckstyles.xml,
+ httemplate/elements/fckeditor/fcktemplates.xml,
+ httemplate/elements/fckeditor/editor/fckdebug.html,
+ httemplate/elements/fckeditor/editor/fckdialog.html,
+ httemplate/elements/fckeditor/editor/fckeditor.html,
+ httemplate/elements/fckeditor/editor/fckeditor.original.html,
+ httemplate/elements/fckeditor/editor/css/fck_editorarea.css,
+ httemplate/elements/fckeditor/editor/css/fck_internal.css,
+ httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css,
+ httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc,
+ httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc,
+ httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif,
+ httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif,
+ httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif,
+ httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_about.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_anchor.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_button.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_docprops.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_find.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_flash.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_form.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_image.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_link.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_listprop.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_paste.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_replace.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_select.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_smiley.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_source.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_table.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_template.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_textarea.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_textfield.html,
+ httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css,
+ httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js,
+ httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc,
+ httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml,
+ httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif,
+ httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif,
+ httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js,
+ httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl,
+ httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif,
+ httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js,
+ httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js,
+ httemplate/elements/fckeditor/editor/filemanager/upload/test.html,
+ httemplate/elements/fckeditor/editor/images/anchor.gif,
+ httemplate/elements/fckeditor/editor/images/arrow_ltr.gif,
+ httemplate/elements/fckeditor/editor/images/arrow_rtl.gif,
+ httemplate/elements/fckeditor/editor/images/spacer.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif,
+ httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif,
+ httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js,
+ httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js,
+ httemplate/elements/fckeditor/editor/lang/_getfontformat.html,
+ httemplate/elements/fckeditor/editor/lang/_translationstatus.txt,
+ httemplate/elements/fckeditor/editor/lang/af.js,
+ httemplate/elements/fckeditor/editor/lang/ar.js,
+ httemplate/elements/fckeditor/editor/lang/bg.js,
+ httemplate/elements/fckeditor/editor/lang/bn.js,
+ httemplate/elements/fckeditor/editor/lang/bs.js,
+ httemplate/elements/fckeditor/editor/lang/ca.js,
+ httemplate/elements/fckeditor/editor/lang/cs.js,
+ httemplate/elements/fckeditor/editor/lang/da.js,
+ httemplate/elements/fckeditor/editor/lang/de.js,
+ httemplate/elements/fckeditor/editor/lang/el.js,
+ httemplate/elements/fckeditor/editor/lang/en-au.js,
+ httemplate/elements/fckeditor/editor/lang/en-ca.js,
+ httemplate/elements/fckeditor/editor/lang/en-uk.js,
+ httemplate/elements/fckeditor/editor/lang/en.js,
+ httemplate/elements/fckeditor/editor/lang/eo.js,
+ httemplate/elements/fckeditor/editor/lang/es.js,
+ httemplate/elements/fckeditor/editor/lang/et.js,
+ httemplate/elements/fckeditor/editor/lang/eu.js,
+ httemplate/elements/fckeditor/editor/lang/fa.js,
+ httemplate/elements/fckeditor/editor/lang/fi.js,
+ httemplate/elements/fckeditor/editor/lang/fo.js,
+ httemplate/elements/fckeditor/editor/lang/fr.js,
+ httemplate/elements/fckeditor/editor/lang/gl.js,
+ httemplate/elements/fckeditor/editor/lang/he.js,
+ httemplate/elements/fckeditor/editor/lang/hi.js,
+ httemplate/elements/fckeditor/editor/lang/hr.js,
+ httemplate/elements/fckeditor/editor/lang/hu.js,
+ httemplate/elements/fckeditor/editor/lang/it.js,
+ httemplate/elements/fckeditor/editor/lang/ja.js,
+ httemplate/elements/fckeditor/editor/lang/km.js,
+ httemplate/elements/fckeditor/editor/lang/ko.js,
+ httemplate/elements/fckeditor/editor/lang/lt.js,
+ httemplate/elements/fckeditor/editor/lang/lv.js,
+ httemplate/elements/fckeditor/editor/lang/mn.js,
+ httemplate/elements/fckeditor/editor/lang/ms.js,
+ httemplate/elements/fckeditor/editor/lang/nb.js,
+ httemplate/elements/fckeditor/editor/lang/nl.js,
+ httemplate/elements/fckeditor/editor/lang/no.js,
+ httemplate/elements/fckeditor/editor/lang/pl.js,
+ httemplate/elements/fckeditor/editor/lang/pt-br.js,
+ httemplate/elements/fckeditor/editor/lang/pt.js,
+ httemplate/elements/fckeditor/editor/lang/ro.js,
+ httemplate/elements/fckeditor/editor/lang/ru.js,
+ httemplate/elements/fckeditor/editor/lang/sk.js,
+ httemplate/elements/fckeditor/editor/lang/sl.js,
+ httemplate/elements/fckeditor/editor/lang/sr-latn.js,
+ httemplate/elements/fckeditor/editor/lang/sr.js,
+ httemplate/elements/fckeditor/editor/lang/sv.js,
+ httemplate/elements/fckeditor/editor/lang/th.js,
+ httemplate/elements/fckeditor/editor/lang/tr.js,
+ httemplate/elements/fckeditor/editor/lang/uk.js,
+ httemplate/elements/fckeditor/editor/lang/vi.js,
+ httemplate/elements/fckeditor/editor/lang/zh-cn.js,
+ httemplate/elements/fckeditor/editor/lang/zh.js,
+ httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js,
+ httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js,
+ httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js,
+ httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js,
+ httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html,
+ httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css,
+ httemplate/elements/fckeditor/editor/skins/default/fck_editor.css,
+ httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif,
+ httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css,
+ httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css,
+ httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif,
+ httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css,
+ httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css,
+ httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif,
+ httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/graph/report_money_time.html,
+ httemplate/misc/cancel_cust.html,
+ httemplate/misc/cancel_pkg.html,
+ httemplate/misc/cust_main-cancel.cgi,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/email_events.cgi, httemplate/misc/fax_events.cgi,
+ httemplate/misc/order_pkg.html, httemplate/misc/print_events.cgi,
+ httemplate/misc/process/cancel_pkg.html,
+ httemplate/pref/pref-process.html,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_bill_event.html,
+ httemplate/search/cust_event.html,
+ httemplate/search/report_cust_bill.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_event.html,
+ httemplate/search/report_cust_main-zip.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_cust_pay_batch.html,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_broadband.cgi,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_forward.cgi,
+ httemplate/search/svc_phone.cgi, httemplate/search/svc_www.cgi,
+ httemplate/search/elements/search.html,
+ httemplate/view/cust_bill-logo.cgi,
+ httemplate/view/cust_bill.cgi, httemplate/view/cust_main.cgi,
+ httemplate/view/logo.cgi, httemplate/view/svc_Common.html,
+ httemplate/view/cust_main/order_pkg.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html, test/cgi-test:
+ event refactor, landing on HEAD!
+
+2007-08-01 15:20 ivan
+
+ * rt/: Makefile, config.log, config.status, bin/mason_handler.fcgi,
+ bin/mason_handler.scgi, bin/mason_handler.svc,
+ bin/rt-commit-handler, bin/rt-crontool, bin/rt-mailgate,
+ bin/webmux.pl, etc/RT_Config.pm, lib/RT.pm,
+ lib/RTx/WebCronTool.pm, lib/t/02regression.t, lib/t/03web.pl,
+ lib/t/04_send_email.pl: commit these RT differences, seem to be
+ mostly in autogen'ed stuff
+
+2007-08-01 15:13 ivan
+
+ * rt/html/Callbacks/kStatistics/Elements/Tabs/Default: because i
+ had missed commiting this on HEAD, that's why
+
+2007-08-01 12:24 ivan
+
+ * httemplate/misc/process/cancel_pkg.html: comment out unused code
+
+2007-08-01 12:22 ivan
+
+ * httemplate/elements/xmlhttp.html: masonization
+
+2007-08-01 12:21 ivan
+
+ * FS/bin/freeside-upgrade: freeside-upgrade: add'l documentation &
+ turn on DBIx::DBSchema::Index debugging too
+
+2007-08-01 12:19 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: better error reporting: all three
+ of these methods might return an error
+
+2007-08-01 12:19 ivan
+
+ * FS/FS/reason.pm: more generic modules first
+
+2007-08-01 12:18 ivan
+
+ * FS/FS/Conf.pm: some comments on config_orbase sub and add'l
+ description for emailcancel config
+
+2007-07-26 04:13 ivan
+
+ * FS/FS/Schema.pm: add index
+
+2007-07-20 12:58 ivan
+
+ * bin/apache.export: apache export: don't bomb out when svc_www
+ records aren't associated with an svc_acct, just leave those
+ subsitution vars blank
+
+2007-07-19 07:05 jeff
+
+ * httemplate/config/config-view.cgi: do not include description in
+ link to popup
+
+2007-07-18 14:13 ivan
+
+ * FS/FS/Schema.pm: Schema.pm doesn't need FS::UID datasrc anyway,
+ wtf
+
+2007-07-18 11:07 jeff
+
+ * FS/FS/Conf.pm, httemplate/browse/agent.cgi,
+ httemplate/config/config-delete.cgi,
+ httemplate/config/config-download.cgi,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi:
+ config in database cleanup, editing, and agent-specific config
+ (452, 1419)
+
+2007-07-17 20:23 jeff
+
+ * FS/FS/AccessRight.pm, httemplate/edit/svc_acct.cgi: svc_acct.dir
+ should be editable with ACL (#1730)
+
+2007-07-13 17:44 ivan
+
+ * bin/find-overapplied: a payment, by any other name
+
+2007-07-13 16:58 ivan
+
+ * bin/find-overapplied: adding quick find-overapplied script
+
+2007-07-13 16:52 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/ClientAPI/Signup.pm, FS/FS/Cron/bill.pm,
+ FS/bin/freeside-prepaidd, httemplate/misc/bill.cgi,
+ httemplate/misc/process/recharge_svc.html,
+ httemplate/edit/process/cust_main.cgi: fix race condition where
+ ->apply_payments_and_credits could double-apply in rare cases
+
+2007-07-13 10:00 jeff
+
+ * FS/FS/Conf.pm: a touch should not obliterate
+
+2007-07-12 14:41 ivan
+
+ * bin/apache.export: add debugging flag to apache export
+
+2007-07-12 14:08 jeff
+
+ * FS/FS/UID.pm: redundant test
+
+2007-07-12 06:36 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Conf_compat17.pm, FS/FS/Misc.pm,
+ FS/FS/Record.pm, FS/FS/UID.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/svc_acct.pm,
+ FS/bin/freeside-init-config, FS/bin/freeside-setup,
+ FS/bin/freeside-upgrade, httemplate/misc/download-batch.cgi:
+ refactor freeside-init-config to module code, compare results of
+ old/new code, have freeside-upgrade complain and revert to old
+ code/config on failure (#1477)
+
+2007-07-11 04:10 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: show bad
+ amount in error message
+
+2007-07-11 01:35 ivan
+
+ * FS/FS/cust_main.pm: finish adding payunique field
+
+2007-07-11 01:08 ivan
+
+ * FS/FS/cust_pay.pm, httemplate/misc/payment.cgi,
+ httemplate/misc/process/payment.cgi: finish adding payunique
+ field
+
+2007-07-10 21:23 jeff
+
+ * FS/FS/cust_main.pm: honor bop_realtime options for paystate,
+ paytype, stateid, and stateid_state for CHEK transactions (#1718)
+
+2007-07-06 16:55 jeff
+
+ * httemplate/edit/process/svc_acct.cgi: manually editing usage
+ counters removes overlimit status (#1706)
+
+2007-07-06 01:08 ivan
+
+ * GPL, README: v3!
+
+2007-07-05 15:05 ivan
+
+ * FS/FS/Record.pm: fix for compatibility w/DBIx::DBSchema v0.33+
+ (without requiring it)
+
+2007-07-01 17:15 ivan
+
+ * FS/FS/cust_pkg.pm: missing method name in docs
+
+2007-07-01 11:09 ivan
+
+ * TODO: remove obsolete information and dollar sign Id dollar sign
+ from TODO
+
+2007-06-30 17:36 ivan
+
+ * httemplate/edit/agent.cgi: separate agent interface is
+ deprecated...
+
+2007-06-29 14:06 ivan
+
+ * httemplate/: edit/svc_www.cgi, view/svc_www.cgi: add regular
+ header/footer to svc_www view and edit
+
+2007-06-28 18:45 ivan
+
+ * FS/FS/Record.pm: last small fix for new DBIx::DBSchema
+
+2007-06-28 18:42 ivan
+
+ * FS/FS/cust_pay.pm: silly missing parenthesis
+
+2007-06-28 18:27 ivan
+
+ * bin/: create-history-tables, strip-eps: removing
+ create-history-tables, freeside-upgrade does this anyway
+
+2007-06-28 18:23 ivan
+
+ * FS/FS/: Schema.pm, cust_pay.pm: add payunique field and dup
+ checking
+
+2007-06-28 18:16 ivan
+
+ * FS/FS/: Schema.pm: update Schema.pm to handle index updates, with
+ new DBIx::DBSchema
+
+2007-06-28 13:57 ivan
+
+ * bin/: bill-as-nextmonth, bill-as-nextmonth-BILL,
+ bill-as-nextyear, bill-as-nextyear-BILL: add some quick scripts
+ for cron jobs
+
+2007-06-26 11:58 ivan
+
+ * httemplate/search/cust_pay.cgi: fix check# search
+
+2007-06-26 11:20 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: we need
+ Number::Format 1.50, not just any version
+
+2007-06-26 09:55 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/view_usage_details.html: add
+ date range to page top
+
+2007-06-26 08:36 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/Cron/bill.pm, FS/FS/Cron/notify.pm,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/misc/cancel_pkg.html,
+ httemplate/misc/process/cancel_pkg.html,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/view/cust_main/packages.html: suspend later just like
+ expire (#1487)
+
+2007-06-25 19:31 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/view_usage_details.html: change
+ "====" to <HR> in selfservice usage stuff. welcome to this
+ millenium! :)
+
+2007-06-22 14:25 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/: selfservice.cgi,
+ view_usage_details.html: self-service usage report enhancements
+ (1495)
+
+2007-06-22 10:27 jeff
+
+ * httemplate/graph/elements/monthly.html: add csv and excel export
+ to sales reports (1426)
+
+2007-06-22 10:21 jeff
+
+ * FS/FS/part_export/www_shellcommands.pm: documentation improvement
+
+2007-06-20 21:02 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Misc.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pkg.pm, conf/welcome_letter: latex
+ welcome letters (1677)
+
+2007-06-20 15:58 ivan
+
+ * rt/etc/RT_SiteConfig.pm: add QuickCreateLong option, part of
+ merging spiritone RT changes
+
+2007-06-20 15:35 ivan
+
+ * rt/etc/RT_SiteConfig.pm: add $RT::MyTicketsLength patch from
+ spiritone, part of merging RT changes (#1661)
+
+2007-06-20 15:28 ivan
+
+ * CREDITS, rt/html/RTx/Statistics/DurationAsString,
+ rt/html/RTx/Statistics/index.html,
+ rt/html/RTx/Statistics/CallsMultiQueue/index.html,
+ rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart,
+ rt/html/RTx/Statistics/CallsQueueDay/Results.tsv,
+ rt/html/RTx/Statistics/CallsQueueDay/index.html,
+ rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart,
+ rt/html/RTx/Statistics/DayOfWeek/index.html,
+ rt/lib/RT/Extension/ActivityReports.pm, rt/lib/RTx/Statistics.pm,
+ rt/html/RTx/Statistics/DayOfWeek/Elements/Chart,
+ rt/html/RTx/Statistics/Elements/DateSelectRow,
+ rt/html/RTx/Statistics/Elements/DurationAsString,
+ rt/html/RTx/Statistics/Elements/GraphBox,
+ rt/html/RTx/Statistics/Elements/SelectMultiQueue,
+ rt/html/RTx/Statistics/Elements/StatColumnMap,
+ rt/html/RTx/Statistics/Elements/Tabs,
+ rt/html/RTx/Statistics/Elements/CollectionAsTable/Header,
+ rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat,
+ rt/html/RTx/Statistics/Elements/CollectionAsTable/Row,
+ rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox,
+ rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage,
+ rt/html/RTx/Statistics/FAQ/index.html,
+ rt/html/RTx/Statistics/OpenStalled/Results.tsv,
+ rt/html/RTx/Statistics/OpenStalled/index.html,
+ rt/html/RTx/Statistics/OpenStalled/Elements/Chart,
+ rt/html/RTx/Statistics/Resolution/index.html,
+ rt/html/RTx/Statistics/Resolution/Elements/Chart,
+ rt/html/Callbacks/ActivityReports/Elements/Tabs/Default,
+ rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default,
+ rt/html/RTx/Statistics/TimeToResolve/index.html,
+ rt/html/RTx/Statistics/TimeToResolve/Elements/Chart,
+ rt/html/RTx/Statistics/UserTest/index.html,
+ rt/html/RTx/Statistics/UserTest/Elements/Chart,
+ rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions,
+ rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default,
+ rt/html/Developer/CronTool/autohandler,
+ rt/html/Developer/CronTool/index.html,
+ rt/html/Reports/Activity/ActivityDetail.html,
+ rt/html/Reports/Activity/ActivitySummary.html,
+ rt/html/Reports/Activity/ResolutionComments.html,
+ rt/html/Reports/Activity/ResolutionStatistics.html,
+ rt/html/Reports/Activity/index.html,
+ rt/html/Reports/Activity/Elements/LimitReport,
+ rt/html/Reports/Activity/Elements/MiniPlot,
+ rt/html/Reports/Activity/Elements/PrintFooter,
+ rt/html/Reports/Activity/Elements/PrintHeader,
+ rt/html/Reports/Activity/Elements/ScreenFooter,
+ rt/html/Reports/Activity/Elements/ScreenHeader,
+ rt/html/Reports/Activity/Elements/Tabs,
+ rt/html/Reports/Activity/Elements/Wrapper: integrate
+ RTx::Statistics package, part of merging spiritone RT changes
+ (#1661)
+
+2007-06-18 09:45 jeff
+
+ * httemplate/edit/svc_www.cgi: must escape config data
+
+2007-06-15 11:44 jeff
+
+ * FS/FS/cust_main.pm, httemplate/edit/cust_refund.cgi,
+ httemplate/edit/process/cust_refund.cgi: paydate option for
+ realtime_refund_bop and UI entry for cust_pay records without it
+ (#1662 UI)
+
+2007-06-14 17:58 jeff
+
+ * FS/FS/: Schema.pm, cust_main.pm: store exp in cust_pay and pass
+ to B:OP during refunds with paynum (#1662)
+
+2007-06-13 09:56 jeff
+
+ * FS/FS/part_export/sqlradius.pm: additional debugging
+
+2007-06-08 17:53 ivan
+
+ * bin/: bill-for-nextmonth, bill-for-nextyear: adding
+ bill-for-next* hacks like bill-next* stuff but with -n flag, for
+ skycatcher
+
+2007-06-08 17:40 ivan
+
+ * FS/: FS/cust_main.pm, FS/Cron/bill.pm, bin/freeside-daily:
+ skycatcher modifications for pre-printing invoices, but with
+ today's date
+
+2007-06-08 16:21 ivan
+
+ * bin/all-postal-no-email: adding quick script to move all
+ customers to postal billing only for skycatcher
+
+2007-06-08 10:38 jeff
+
+ * FS/FS/part_export/sqlradius.pm: prevent multiple additions to
+ usergroup table (work around #1606)
+
+2007-06-08 07:40 jeff
+
+ * FS/FS/part_export/prizm.pm: queue suspend, unsuspend, and delete
+ (fixes #1657)
+
+2007-06-06 17:45 jeff
+
+ * FS/FS/cust_bill.pm: cruft removal
+
+2007-06-06 15:34 jeff
+
+ * FS/FS/: Conf.pm, cust_bill.pm: service dates on invoices optional
+ (#1658)
+
+2007-06-06 12:58 khoff
+
+ * FS/FS/: cust_main.pm, Conf.pm: Added 'disable_void_after' config
+ option to disable the VOID-before-credit behavior of
+ FS::cust_main::realtime_refund_bop after n seconds, if set. For
+ broken gateways like SkipJack that a pprove VOIDs for settled
+ transactions.
+
+2007-06-05 08:07 jeff
+
+ * FS/FS/svc_acct.pm: correct shorage of variables bound to prepared
+ statement
+
+2007-06-02 14:07 jeff
+
+ * httemplate/elements/header.html: drop ticket search form and
+ logic from ticketless installs
+
+2007-05-31 08:03 jeff
+
+ * httemplate/edit/payment_gateway.html: add TransFirst eLink
+ support
+
+2007-05-29 20:38 ivan
+
+ * FS/FS/svc_external.pm: should eliminiate error: Use of
+ uninitialized value in string eq at
+ /usr/local/share/perl/5.8.8/FS/svc_external.pm line 82.
+
+2007-05-29 20:05 ivan
+
+ * httemplate/view/svc_domain.cgi: fix javascript confirmation for
+ domain records with " in them...
+
+2007-05-21 17:34 ivan
+
+ * httemplate/search/report_tax.cgi: fix tax reports for some odd
+ upgrade edge cases: when you have both taxclass and non-taxclass
+ entries in cust_main_county for a single region (not correct
+ setup in the first place), and non-null but empty values in
+ taxname
+
+2007-05-20 19:18 ivan
+
+ * FS/FS/Schema.pm: haven't had anyone use this in years, and for
+ some reason DBIx::DBSchema can't reverse engineer the schema for
+ it, causing freeside-upgrade to error out trying to recreate it
+
+2007-05-18 16:44 ivan
+
+ * httemplate/edit/cust_main/billing.html: have "emailinvoiceonly"
+ hide postal/fax invoice options in backoffice as well as
+ self-service. closes: bug#1614
+
+2007-05-11 16:18 khoff
+
+ * httemplate/search/: report_cust_bill.html,
+ report_cust_credit.html, report_cust_main-zip.html,
+ report_cust_pay.html, report_cust_pay_batch.html,
+ report_cust_pkg.html: Fix (hopefully) all uses of
+ /elements/tr-select-agent.html where agentnum may be undefined.
+
+2007-05-11 12:03 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: logout seems not to return
+ an error status, so don't up the queue with "failed: Can't logout
+ of CGPro: No error"
+
+2007-05-11 11:14 khoff
+
+ * httemplate/elements/header.html: Use FS::TicketSystem to get RT
+ URL.
+
+2007-05-11 11:08 khoff
+
+ * FS/FS/XMLRPC.pm: Quiet debug output.
+
+2007-05-08 15:43 jeff
+
+ * FS/FS/part_pkg/: prorate.pm, subscription.pm: add formatting to
+ plan data
+
+2007-05-08 15:10 jeff
+
+ * FS/FS/part_pkg/flat.pm: charges and time are not measured in
+ megabytes
+
+2007-05-08 09:51 ivan
+
+ * bin/: bill-nextmonth, bill-nextyear: [no log message]
+
+2007-05-06 23:35 jeff
+
+ * FS/bin/: freeside-sqlradius-dedup-group,
+ freeside-sqlradius-reset: sqlradius usergroup tools
+
+2007-05-06 19:14 jeff
+
+ * httemplate/misc/download-batch.cgi: 1609 correct spiritone ACH
+
+2007-05-06 19:13 jeff
+
+ * FS/FS/cust_pkg.pm, FS/FS/svc_acct.pm,
+ httemplate/misc/process/link.cgi: 1606 correct bug in overlimit
+ groups handling
+
+2007-05-01 13:00 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: without debugging this time :)
+
+2007-05-01 12:57 ivan
+
+ * FS/: bin/freeside-selfservice-server, FS/ClientAPI/Signup.pm: use
+ FS::ClientAPI_SessionCache for signup info so it actually works -
+ speed up signups
+
+2007-04-29 15:55 jeff
+
+ * FS/FS/Conf.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/ach_payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/make_ach_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ httemplate/view/cust_main/billing.html: ticket 1568 config
+ options for new echeck fields and addition to selfservice
+ interface
+
+2007-04-27 15:54 jeff
+
+ * httemplate/edit/cust_main/contact.html: adjust default labelling
+
+2007-04-27 15:37 jeff
+
+ * httemplate/edit/cust_main/billing.html: SELECT doesn't accept a
+ value
+
+2007-04-25 21:46 jeff
+
+ * FS/FS/svc_acct.pm, FS/FS/ClientAPI/MyAccount.pm,
+ httemplate/misc/recharge_svc.html: more datavolume format
+
+2007-04-25 20:35 ivan
+
+ * FS/FS/UI/bytecount.pm: old Number::Format silently ignores
+ datavolume-forcemegabytes
+
+2007-04-25 20:09 jeff
+
+ * FS/FS/UI/bytecount.pm: improve configfile handling
+
+2007-04-25 18:50 jeff
+
+ * FS/FS/pay_batch.pm, httemplate/misc/download-batch.cgi: revert
+ dodgy ACH hack from ticket 1436
+
+2007-04-24 17:54 ivan
+
+ * FS/FS/: cust_bill.pm, Conf.pm: add lpr-postscript_prefix and
+ lpr-postscript_suffix config options for printer commands to
+ place printer in postscript mode
+
+2007-04-23 17:21 jeff
+
+ * FS/FS/svc_www.pm: untaint
+
+2007-04-23 17:04 jeff
+
+ * httemplate/view/svc_acct.cgi: missed a bytecount
+
+2007-04-23 16:34 ivan
+
+ * httemplate/search/cust_pkg.cgi: fix error: column reference
+ "fieldname" is ambiguous
+
+2007-04-22 20:41 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_bill.pm,
+ FS/FS/pay_batch.pm, httemplate/misc/download-batch.cgi,
+ httemplate/misc/payment.cgi, httemplate/misc/process/payment.cgi:
+ add to ACH batch feature from customer view page
+
+2007-04-20 21:44 ivan
+
+ * FS/FS/cust_svc.pm: bring POD documentation in line with reality
+ wrt where we're storing phone numbers
+
+2007-04-20 14:40 ivan
+
+ * conf/: invoice_html, invoice_latex: add customer # to default
+ invoice templates
+
+2007-04-20 14:31 ivan
+
+ * httemplate/view/cust_main/billing.html: don't put this
+ information way out on the right in its own columns.
+
+2007-04-19 16:34 ivan
+
+ * FS/FS/UI/Web.pm: not needed here anymore, bytecount stuff moved
+ to its own module
+
+2007-04-19 16:30 jeff
+
+ * FS/FS/svc_acct.pm, FS/FS/UI/Web.pm, FS/FS/UI/bytecount.pm,
+ FS/FS/part_pkg/flat.pm, FS/FS/ClientAPI/MyAccount.pm,
+ httemplate/edit/process/prepay_credit.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/search/prepay_credit.html, htetc/handler.pl: break
+ _bytecount subroutines out of FS::UI::Web
+
+2007-04-19 13:18 ivan
+
+ * htetc/handler.pl, httemplate/search/cust_svc.html,
+ httemplate/view/cust_main/packages.html, FS/FS/UI/Web.pm,
+ httemplate/browse/part_svc.cgi, httemplate/elements/menu.html:
+ fix very strange "Undefined subroutine &FS::UI::Web::rooturl"
+ with an explicit import. did i mention this was really really
+ weird?
+
+2007-04-18 20:15 ivan
+
+ * FS/FS/UID.pm: better error message about missing secrets file
+ errors. WTF is going on!
+
+2007-04-18 16:22 ivan
+
+ * FS/: FS/svc_acct.pm, FS/part_pkg/flat.pm, bin/freeside-queued:
+ fix use statements for FS::UI::Web. not "use"ing modules in the
+ actual modules you use them in is bad, mmmkay
+
+2007-04-18 13:01 ivan
+
+ * FS/FS/part_pkg.pm: praise the parser! amen! (fix a doc typo)
+
+2007-04-12 17:53 jeff
+
+ * FS/FS/svc_acct.pm: correct boneheaded afterthoughts
+
+2007-04-11 20:16 jeff
+
+ * FS/FS/Conf.pm, FS/FS/part_pkg.pm, FS/FS/part_svc.pm,
+ FS/FS/svc_acct.pm, FS/FS/UI/Web.pm, FS/FS/part_pkg/flat.pm,
+ FS/bin/freeside-queued, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/view/svc_acct.cgi: input and output on data volume
+ fields specified with k,m,g,or t
+
+2007-04-11 19:42 ivan
+
+ * FS/FS/svc_Common.pm: quiet "Use of uninitialized value in string
+ eq at /usr/local/share/perl/5.8.4/FS/svc_Common.pm line 131". i
+ think.
+
+2007-04-11 19:27 ivan
+
+ * httemplate/misc/process/recharge_svc.html: remove
+ 'backend-realtime' flag required for recharges, want that just
+ for signups, running recharges right away by default is fine.
+ also fix the 'fatal error - unknown payby' error that'll probably
+ never be reached
+
+2007-04-10 21:28 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm,
+ FS/FS/svc_Common.pm, FS/FS/svc_acct.pm,
+ httemplate/view/cust_main/packages.html: usage suspend vs admin
+ suspend -- avoid actual cust_pkg::suspend except legacy cases
+
+2007-04-09 18:44 ivan
+
+ * FS/FS/Conf.pm: remove a ton of deprecated config options
+
+2007-04-09 18:29 ivan
+
+ * FS/FS/Conf.pm: add checkbox to payment_receipt_email config
+
+2007-04-09 16:38 jeff
+
+ * httemplate/misc/: recharge_svc.html, process/recharge_svc.html:
+ trigger recharge from the backend as in self-service
+
+2007-04-07 18:14 jeff
+
+ * FS/FS/: Conf.pm, svc_acct.pm: configuration option to have
+ generated passwords be all caps
+
+2007-04-07 17:41 jeff
+
+ * httemplate/edit/svc_acct.cgi: correct usage editing bug
+
+2007-04-07 17:22 jeff
+
+ * FS/FS/svc_acct.pm: threshold usage email should not be sent to
+ svc_acct->email
+
+2007-04-06 17:07 jeff
+
+ * httemplate/view/cust_main/billing.html: ommitted file
+
+2007-04-06 16:57 jeff
+
+ * FS/FS/cust_main.pm, httemplate/misc/payment.cgi,
+ httemplate/misc/process/payment.cgi: integrate new echeck fields
+ into freeside backend payment processing
+
+2007-04-06 12:38 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_main/select-state.html: ticket 1443 add
+ account type and bank state for echeck processing
+
+2007-04-05 17:37 ivan
+
+ * FS/FS/Conf.pm: slight update for batch-enable description & add
+ ach-spiritone format to batch-default and batch-fixed_format-CHEK
+ config items
+
+2007-04-05 17:34 ivan
+
+ * FS/FS/Conf.pm: slight better descriptions
+
+2007-04-05 17:01 khoff
+
+ * FS/FS/part_export/nas_wrapper.pm: Disable debug output by
+ default. Pass @_ along to new exports.
+
+2007-04-05 15:01 ivan
+
+ * httemplate/elements/menu.html: they're comments now
+
+2007-04-05 07:15 jeff
+
+ * httemplate/misc/process/cust_main_note-import.cgi: use comments
+ and not notes
+
+2007-04-04 19:04 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/view/cust_main/billing.html: per-customer invoice
+ terms override
+
+2007-04-04 18:45 ivan
+
+ * httemplate/pref/pref.html: finish moving vonage integration to a
+ user pref
+
+2007-04-04 18:38 ivan
+
+ * httemplate/pref/pref-process.html: finish moving the vonage
+ integration to per-user
+
+2007-04-04 17:01 ivan
+
+ * FS/bin/freeside-init-config: correct usage
+
+2007-04-04 15:42 ivan
+
+ * Makefile: yes virginia
+
+2007-04-04 13:04 jeff
+
+ * FS/bin/freeside-upgrade: correct a bootstrap issue
+
+2007-04-04 08:30 jeff
+
+ * FS/FS/cust_bill.pm: squash bug which causes re-email failed
+ events to fail
+
+2007-04-03 12:37 jeff
+
+ * httemplate/misc/cust_main_note-import.cgi: correct handling of
+ non-unix line termination
+
+2007-04-02 18:39 ivan
+
+ * bin/fs-migrate-cust_tax_exempt: [no log message]
+
+2007-04-02 08:49 jeff
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/Setup.pm,
+ FS/FS/cust_main.pm, httemplate/edit/cust_main/contact.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/view/cust_main/contacts.html: ticket 1528 add driver's
+ license field, mask it and ssn
+
+2007-03-31 19:45 jeff
+
+ * httemplate/view/svc_www.cgi, FS/FS/AccessRight.pm,
+ FS/FS/Schema.pm, bin/apache.export, httemplate/edit/svc_www.cgi:
+ quick hack to add extra 'config lines' to svc_www and otherwise
+ enhance svc_www
+
+2007-03-27 20:59 ivan
+
+ * FS/FS/svc_acct.pm: fix bug with RADIUS groups not available to
+ shellcommands delete export
+
+2007-03-21 16:07 ivan
+
+ * FS/FS/cust_main.pm: fix bug displaying additional debugging info
+ when process returns no error_message
+
+2007-03-21 15:09 ivan
+
+ * FS/FS/cust_main.pm: realtime_bop: if a transaction fails without
+ an error_message, output additional debugging information, if
+ available
+
+2007-03-20 21:01 jeff
+
+ * FS/FS/pay_batch.pm, httemplate/misc/download-batch.cgi,
+ httemplate/search/cust_pay_batch.cgi: ticket 1436, ACH export
+ format, return processing and autopost
+
+2007-03-20 18:21 jeff
+
+ * FS/bin/freeside-fetch, httemplate/pref/pref-process.html,
+ httemplate/pref/pref.html: ticket 1427, automatically email excel
+ a/r report
+
+2007-03-20 15:10 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: fix bug: usage of promo code broke
+ agent-specific advertising sources
+
+2007-03-20 13:11 jeff
+
+ * httemplate/misc/: cust_main_note-import.cgi,
+ cust_main_note-import.html, process/cust_main_note-import.cgi:
+ acls on new import
+
+2007-03-20 13:01 jeff
+
+ * httemplate/elements/menu.html: tie note import into menu
+
+2007-03-20 10:03 jeff
+
+ * FS/FS/cust_main.pm, httemplate/misc/cust_main_note-import.html,
+ httemplate/misc/cust_main_note-import.cgi,
+ httemplate/misc/process/cust_main_note-import.cgi: ticket 1418, a
+ tool for customer note importation
+
+2007-03-15 18:35 ivan
+
+ * FS/FS/Conf.pm, httemplate/elements/phonenumber.html,
+ httemplate/pref/pref.html: vonage click2call integration should
+ not be sitewide, especially now that we have user prefs
+
+2007-03-15 13:54 ivan
+
+ * FS/FS/part_pkg.pm: 13 months!
+
+2007-03-15 13:08 khoff
+
+ * httemplate/edit/part_virtual_field.cgi: Set input form MAXLENGTH
+ attributes to match actual field sizes.
+
+2007-03-15 13:08 khoff
+
+ * FS/FS/part_export/router.pm: Added configurable error checks
+ instead of stupid /^ERROR/ check. Commands can now be processed
+ with Text::Template using [@-- --@] delimeters, in addition to
+ evaling a double-quoted string. Cleaned up spurious debug
+ output.
+
+2007-03-15 13:07 khoff
+
+ * FS/FS/part_virtual_field.pm: Escape the values in virtual field
+ html form inputs.
+
+2007-03-15 13:07 khoff
+
+ * FS/FS/Schema.pm: part_virtual_field.vfieldpart should be a
+ serial.
+
+2007-03-14 16:30 jeff
+
+ * FS/FS/svc_acct.pm: turn debugging off
+
+2007-03-14 16:27 jeff
+
+ * FS/FS/svc_acct.pm: correcting ->replace on bill
+
+2007-03-13 02:21 ivan
+
+ * FS/FS/cust_pkg.pm: okay, so this should link to usernum now. but
+ until then, it should be ->username, not ->name, eek. causing
+ "Error: Error inserting cust_pkg_reason: ERROR: value to long for
+ type character varying(32)" errors and will be harder to
+ normalize back to usernum when we fix that
+
+2007-03-10 20:13 ivan
+
+ * httemplate/browse/svc_acct_pop.cgi: move POP to browse template;
+ whew, its paged
+
+2007-03-09 16:16 khoff
+
+ * FS/FS/Record.pm: Added $FS::Record::no_update_diff flag to update
+ "identical" records anyway.
+
+2007-03-09 16:11 khoff
+
+ * FS/FS/: Record.pm, svc_broadband.pm: Added ut_coord and ut_coordn
+ for FS::svc_broadband.
+
+2007-03-09 09:11 jeff
+
+ * bin/svc_acct_pop.import: pop import tool
+
+2007-03-07 11:48 khoff
+
+ * FS/FS/: Conf.pm, cust_main.pm: Option to disable the charging of
+ the setup fee while a package is suspended.
+
+2007-03-06 11:56 ivan
+
+ * bin/slony-setup: slight update for slony setup script
+
+2007-03-05 17:59 ivan
+
+ * httemplate/search/cust_main.cgi: fix status colors in mozilla
+
+2007-03-05 15:01 jayce
+
+ * FS/FS/part_pkg/base_delayed.pm: Typo in the package name caused a
+ warning. Fixed.
+
+2007-03-05 11:48 khoff
+
+ * httemplate/search/report_cust_bill.html: Fixed a problem with the
+ %opts hash getting skewed (specifically $opt{'value'} == 'label')
+ when $cgi->param('agentnum') was unset.
+
+2007-03-02 17:29 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.cgi: pass the remote IP
+ address along with signup requests, for some gateways
+
+2007-03-02 15:48 ivan
+
+ * FS/FS/cust_main.pm: eProcessingNetwork returning an authorization
+ like "AUTH/TKT 123456"... will this make refunds work?
+
+2007-03-01 22:24 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: fix custom priority fields,
+ whew
+
+2007-03-01 12:56 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: better match for toll-free prefixes
+
+2007-03-01 12:12 ivan
+
+ * FS/FS/Misc/prune.pm: yeah, a typo
+
+2007-03-01 09:48 ivan
+
+ * FS/bin/: freeside-prune-applications, freeside-upgrade:
+ prune_applications moved to FS::Misc::prune
+
+2007-03-01 09:47 ivan
+
+ * FS/FS/Misc/prune.pm: adding FS::Misc::prune
+
+2007-03-01 09:44 ivan
+
+ * FS/FS/Misc.pm: fix weird compliation problem for quis, i hope
+
+2007-03-01 08:36 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: turn off debugging
+
+2007-02-28 21:24 jeff
+
+ * FS/FS/cust_credit.pm: dangling cust_credit_refund not allowed
+
+2007-02-28 21:13 jeff
+
+ * FS/: FS/Misc.pm, bin/freeside-prune-applications,
+ bin/freeside-upgrade: dangling cust_credit_refund not allowed
+
+2007-02-28 17:26 ivan
+
+ * FS/FS/Conf.pm: this isn't necessary around a single db operation
+
+2007-02-28 11:14 jeff
+
+ * httemplate/: edit/cust_main/select-domain.html,
+ misc/svc_acct-domains.cgi: restore (none) choice to first package
+ select
+
+2007-02-27 16:16 jayce
+
+ * FS/MANIFEST: Added base_rate files
+
+2007-02-27 15:59 jayce
+
+ * FS/FS/part_pkg/base_delayed.pm: adaptation of flat_delayed to
+ work with base_rate billing
+
+2007-02-27 14:10 jeff
+
+ * httemplate/config/config-download.cgi: put the C in ACL here, too
+
+2007-02-27 13:24 jeff
+
+ * FS/bin/freeside-setup: oops - inappropriate localization was
+ committed
+
+2007-02-26 17:51 jeff
+
+ * httemplate/config/config.cgi: config goes in database
+
+2007-02-26 17:48 jeff
+
+ * FS/MANIFEST, FS/FS/Conf.pm, FS/FS/UID.pm, FS/FS/conf.pm,
+ FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/svc_acct.pm,
+ FS/bin/freeside-init-config, FS/bin/freeside-setup,
+ FS/bin/freeside-upgrade, FS/t/conf.t, conf/invoice_latex,
+ httemplate/config/config-download.cgi,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, FS/FS/Schema.pm: config goes
+ in database
+
+2007-02-26 09:54 jeff
+
+ * htetc/handler.pl, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi: agent_type selector on new
+ packages
+
+2007-02-24 19:08 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-selfservice-server: add
+ selfservice-ignore_quantity flag
+
+2007-02-22 23:47 ivan
+
+ * FS/FS/Cron/bill.pm: take the package-def defined action here,
+ like freeside-prepaidd
+
+2007-02-21 03:26 ivan
+
+ * FS/FS/: Schema.pm, svc_acct.pm, part_export/shellcommands.pm: add
+ a _password_encoding field
+
+2007-02-20 18:53 jeff
+
+ * FS/FS/part_export/prizm.pm: work around a claimed 50 char limit,
+ and correct a description handling bug
+
+2007-02-19 07:40 ivan
+
+ * FS/FS/cust_bill.pm: this should fix Can't call method "part_pkg"
+ on an undefined value at .../cust_bill.pm line 434
+
+2007-02-16 14:06 ivan
+
+ * bin/rotate-cdrs: commiting this quick tool, cvs is complaining
+
+2007-02-16 12:54 jeff
+
+ * httemplate/misc/svc_acct-domains.cgi: respect svc_acct-alldomains
+ setting
+
+2007-02-16 12:21 jeff
+
+ * FS/FS/: Record.pm, svc_broadband.pm: coordinates can be negative
+ (deja vu?)
+
+2007-02-16 11:46 jayce
+
+ * FS/FS/part_pkg/base_rate.pm: Added a new Plan type, base_rate,
+ which uses the cust_pkg->options to control pricing. You assign
+ a base rate for charging, and it is multiplied by the 'units'
+ ordered. Solves a need where a company wants to offer an ASP
+ style service, that charges per user, at a base cost, but doesn't
+ want a plan for every combination of user amounts.
+
+2007-02-15 12:18 khoff
+
+ * FS/FS/cust_main.pm: *** ERROR: unterminated L<...> at line 4716
+ in file FS/cust_main.pm
+
+2007-02-14 17:09 ivan
+
+ * httemplate/docs/billing.html: removing docs moved to wiki
+
+2007-02-14 16:49 ivan
+
+ * httemplate/edit/part_bill_event.cgi: add send_email event
+
+2007-02-14 16:48 ivan
+
+ * conf/invoice_html: remove commented-out example notes section
+
+2007-02-14 15:32 jeff
+
+ * httemplate/: edit/cust_main/select-domain.html,
+ edit/cust_main.cgi, misc/svc_acct-domains.cgi: selectable domain
+ on first package in edit/cust_main
+
+2007-02-14 14:43 ivan
+
+ * httemplate/search/report_receivables.cgi: fix alignment on
+ receivables report
+
+2007-02-14 13:23 ivan
+
+ * httemplate/search/report_cust_pkg.html: grey out disabled text
+ boxes for IE
+
+2007-02-14 00:48 ivan
+
+ * httemplate/elements/select-cust_pkg-status.html,
+ httemplate/elements/tr-input-beginning_ending.html,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/report_cust_pkg.html, FS/FS/UI/Web.pm,
+ httemplate/images/calendar-disabled.png: add more options to
+ advanced package reporting
+
+2007-02-13 19:12 jeff
+
+ * FS/FS/: Conf.pm, Cron/notify.pm: add simple scalars to
+ impending_recur_notification
+
+2007-02-13 17:43 ivan
+
+ * httemplate/search/svc_www.cgi: fix up svc_www searching
+
+2007-02-13 16:19 jeff
+
+ * FS/FS/Conf.pm: better docs for impending doom
+
+2007-02-13 14:24 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: accept domsvc in selfservice
+ orders, patch from Sean Hanson/S1
+
+2007-02-13 14:08 ivan
+
+ * httemplate/edit/process/cust_main.cgi: use a domsvc when passed
+ in, patch from Sean Hanson/S1
+
+2007-02-12 21:00 jayce
+
+ * httemplate/edit/payment_gateway.html: Small hint on how the
+ options field is used.
+
+2007-02-12 17:16 jeff
+
+ * httemplate/edit/cust_main.cgi: another place to alphabetize
+
+2007-02-12 16:45 jeff
+
+ * FS/bin/freeside-delete-addr_blocks: for dumping addr_blocks
+
+2007-02-12 09:56 jeff
+
+ * FS/FS/ClientAPI/Signup.pm,
+ httemplate/view/cust_main/order_pkg.html: alpha sort packages in
+ new package order SELECT (ticket 1446)
+
+2007-02-11 19:11 ivan
+
+ * FS/FS/Misc.pm: fix unterminated L<...> in POD docs
+
+2007-02-11 00:51 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/myaccount_menu.html: we're at
+ now now
+
+2007-02-10 23:04 ivan
+
+ * FS/FS/UI/Web.pm: fix bug with customer info not showing up if
+ "cust-fields" config value has been set
+
+2007-02-09 22:13 ivan
+
+ * FS/FS/svc_acct.pm, FS/FS/svc_phone.pm,
+ httemplate/edit/part_svc.cgi: reincorporate the changes from
+ http://www.sisd.com/cgi-bin/viewcvs.cgi/freeside/httemplate/edit/part_svc.cgi?r1=1.52&r2=1.53
+ that were lost due to the concurrent service-refactor merge
+
+2007-02-09 16:42 ivan
+
+ * httemplate/misc/change_pkg.cgi: REALLY fix the package changing
+ this time
+
+2007-02-09 15:29 ivan
+
+ * httemplate/misc/change_pkg.cgi: fix change package link
+
+2007-02-08 20:54 ivan
+
+ * bin/import-county-tax-rates: quick hack to import rates for
+ landel
+
+2007-02-08 20:37 jayce
+
+ * httemplate/view/cust_main/packages.html: Set the correct access
+ right for the cancel link to work
+
+2007-02-08 17:05 ivan
+
+ * conf/: invoice_html_statement, invoice_latex_statement,
+ invoice_latexnotes_statement, invoice_template_statement: add
+ default statement conf
+
+2007-02-08 14:54 ivan
+
+ * FS/FS/part_export/sqlradius.pm: slightly less cryptic docs for
+ groups_susp_reason option
+
+2007-02-08 14:19 ivan
+
+ * FS/FS/cust_pkg.pm: use a LIMIT to retreive the first record,
+ don't rely on qsearchs to trim it for you - it is inefficient and
+ outputs long verbose warnings
+
+2007-02-08 14:08 ivan
+
+ * httemplate/pref/pref-process.html: second "my" localizing the
+ variable and causing password changes to fail
+
+2007-02-08 09:00 jeff
+
+ * FS/FS/svc_acct.pm, httemplate/edit/part_svc.cgi: fix usergroup in
+ edit/part_svc.cgi
+
+2007-02-08 08:04 jeff
+
+ * httemplate/edit/part_pkg.cgi: gratuitous quote
+
+2007-02-08 01:13 jeff
+
+ * htetc/handler.pl, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi: hold off until 1.7.2
+
+2007-02-08 00:19 ivan
+
+ * FS/FS/CGI.pm: fix the popup progress bars, whew
+
+2007-02-07 17:46 jeff
+
+ * FS/FS/part_pkg.pm, htetc/handler.pl,
+ httemplate/elements/select-table.html,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi: agent type on package
+ add/edit (ticket 1446)
+
+2007-02-06 16:46 ivan
+
+ * FS/FS/: cust_bill.pm, cust_pay.pm: only use new statements as
+ payment receipts if the conf file is created
+
+2007-02-05 15:48 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: fix payment/credit
+ line-item application erroring out on tax applications
+
+2007-02-05 14:12 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: turn debugging on until we
+ catch the Cant call method "part_pkg" on an undefined value at
+ /usr/local/share/perl/5.8.4/FS/cust_bill_ApplicationCommon.pm
+ line 181" bug
+
+2007-02-05 09:21 ivan
+
+ * FS/FS/: cust_pay_batch.pm, pay_batch.pm: move the due_events
+ import too... whew! this should be it
+
+2007-02-05 08:01 ivan
+
+ * FS/FS/pay_batch.pm: fix param passing
+
+2007-02-05 07:49 ivan
+
+ * httemplate/misc/upload-batch.cgi: fix st00pid mistakes in batch
+ upload
+
+2007-02-05 07:48 ivan
+
+ * httemplate/misc/upload-batch.cgi: misterminated <%init>
+
+2007-02-05 07:02 ivan
+
+ * FS/FS/pay_batch.pm: remove refactored code
+
+2007-02-05 05:11 ivan
+
+ * httemplate/search/svc_Smart.html: remove unused svc_Smart
+
+2007-02-05 05:10 ivan
+
+ * httemplate/search/: report_receivables.cgi,
+ report_receivables.html: add "over X days" option to receivables
+ report
+
+2007-02-05 04:51 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/view/cust_bill-logo.cgi,
+ httemplate/view/cust_bill-pdf.cgi,
+ httemplate/view/cust_bill-ps.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_Common.html, httemplate/view/svc_acct.cgi,
+ httemplate/view/svc_broadband.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_external.cgi,
+ httemplate/view/svc_forward.cgi, httemplate/view/svc_www.cgi,
+ httemplate/view/cust_main/contacts.html,
+ httemplate/view/elements/svc_Common.html: C is for
+ Cookie^WControl
+
+2007-02-05 03:35 ivan
+
+ * FS/FS/CGI.pm: not supporting Apache::ASP anymore
+
+2007-02-05 03:12 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: show RADIUS usage from last bill to
+ NOW, instead of last bill to next bill
+
+2007-02-05 01:45 ivan
+
+ * httemplate/misc/download-batch.cgi: upon first download, have
+ batches auto-set amount to customer balance if it is smaller
+
+2007-02-05 01:44 ivan
+
+ * FS/FS/Record.pm, FS/FS/cust_main.pm, FS/FS/cust_pay_batch.pm,
+ FS/FS/pay_batch.pm, httemplate/misc/upload-batch.cgi: move
+ cust_pay_batch::upload results subroutine to an FS::pay_batch
+ method. upon first download, have batches auto-reset their
+ amounts to the customer balance upon if it is smaller.
+
+2007-02-03 17:47 ivan
+
+ * httemplate/: search/cust_pay_batch.cgi,
+ view/cust_main/payment_history.html: add link to some batched
+ payment info to customer view
+
+2007-02-03 16:01 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: quieter
+
+2007-02-03 05:05 ivan
+
+ * FS/FS/cust_pkg.pm: this should fix services with negative
+ num_avail showing up for provisioning
+
+2007-02-03 04:07 ivan
+
+ * FS/FS/cust_main_Mixin.pm: remove debugging left on
+
+2007-02-03 03:36 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/ConfDefaults.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_Mixin.pm, FS/FS/UI/Web.pm,
+ httemplate/elements/menu.html,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/money_time.cgi,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/graph/report_money_time.html,
+ httemplate/search/cdr.html, httemplate/search/cust_bill.html,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_bill_event.html,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_main-otaker.cgi,
+ httemplate/search/cust_main-zip.html,
+ httemplate/search/cust_main.cgi,
+ httemplate/search/cust_main.html, httemplate/search/cust_pay.cgi,
+ httemplate/search/cust_pay_batch.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/cust_svc.html,
+ httemplate/search/cust_tax_exempt_pkg.cgi,
+ httemplate/search/inventory_item.html,
+ httemplate/search/pay_batch.cgi,
+ httemplate/search/pay_batch.html, httemplate/search/queue.html,
+ httemplate/search/reg_code.html,
+ httemplate/search/report_cdr.html,
+ httemplate/search/report_cust_bill.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_main-zip.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_cust_pay_batch.html,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/search/report_prepaid_income.cgi,
+ httemplate/search/report_prepaid_income.html,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/report_receivables.html,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html, httemplate/search/sql.html,
+ httemplate/search/sqlradius.cgi,
+ httemplate/search/sqlradius.html, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_broadband.cgi,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_external.cgi,
+ httemplate/search/svc_forward.cgi,
+ httemplate/search/svc_phone.cgi, httemplate/search/svc_www.cgi:
+ add customer status column to customer & most other reports.
+ also put the C in ACL in the search/ and graph/ directories.
+
+2007-02-02 18:39 jeff
+
+ * FS/bin/freeside-reset-fixed: do a setfixed
+
+2007-02-01 22:29 ivan
+
+ * FS/FS/part_pkg/flat.pm: quiet a harmless but loud warning
+
+2007-02-01 20:39 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: set resellser-specific advertising
+ sources in signup
+
+2007-01-31 19:08 jeff
+
+ * httemplate/view/cust_main/packages.html: oops. perhaps it should
+
+2007-01-31 18:37 jeff
+
+ * FS/FS/svc_acct.pm: spurious line
+
+2007-01-31 18:35 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: fix up the info passing for optional
+ packages
+
+2007-01-31 18:13 jeff
+
+ * FS/FS/: svc_acct.pm, part_export/sqlradius.pm: better solution to
+ ticket 1455
+
+2007-01-31 17:57 ivan
+
+ * FS/FS/cust_bill.pm: eliminate \\* in the cheesy latex->html notes
+ thing
+
+2007-01-31 10:28 jeff
+
+ * FS/FS/part_export/sqlradius.pm: possible solution to ticket 1455
+
+2007-01-30 22:30 khoff
+
+ * FS/FS/part_export/snmp.pm: Don't break if we're missing
+ Net::SNMP. Apparenty "require" doesn't do the trick.
+
+2007-01-30 21:45 khoff
+
+ * FS/FS/part_export/nas_wrapper.pm: Meta-export to allow more
+ flexibilty until the export subsystem rewrite.
+
+2007-01-30 21:43 khoff
+
+ * FS/FS/part_export/: router.pm, snmp.pm, trango.pm:
+ FS::part_export::router - Refactored to be more easily
+ sub-classed. - Moved per-export options to FS:;router virtual
+ fields. - Fixed other general brokenness.
+
+ FS::part_export::snmp - SNMP export sub-classed from
+ FS::part_export::router
+
+ FS::part_export::trango - Export for Trango proprietary access
+ points. Sub-classed from FS::part_export::snmp.
+
+2007-01-30 21:23 jeff
+
+ * FS/FS/cust_main.pm: minor improvement
+
+2007-01-30 20:30 jeff
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/misc/process/payment.cgi: small change in payment
+ receipt handling (ticket 1422)
+
+2007-01-30 20:26 ivan
+
+ * httemplate/: edit/quick-charge.html,
+ elements/select-taxclass.html, elements/tr-select-taxclass.html,
+ view/cust_main/packages.html: minor UI work on one-time charges
+ w/taxclasses
+
+2007-01-30 19:42 jeff
+
+ * FS/: MANIFEST, t/cust_pkg_option.t: odds and ends
+
+2007-01-30 18:08 ivan
+
+ * httemplate/misc/change_pkg.cgi: this should be a popup too, but,
+ until then, it shouldn't be missing the standard header
+
+2007-01-30 15:50 ivan
+
+ * FS/FS/cust_bill.pm: fix agent-specific logos in emailed html
+ invoices
+
+2007-01-30 13:21 jeff
+
+ * FS/FS/svc_acct.pm: avoiding brane pane
+
+2007-01-30 12:59 ivan
+
+ * httemplate/: browse/access_group.html, browse/access_user.html,
+ browse/addr_block.cgi, browse/agent.cgi, browse/agent_type.cgi,
+ browse/cust_main_county.cgi, browse/inventory_class.html,
+ browse/msgcat.cgi, browse/part_bill_event.cgi,
+ browse/part_export.cgi, browse/part_pkg.cgi,
+ browse/part_referral.html, browse/part_svc.cgi,
+ browse/part_virtual_field.cgi, browse/payment_gateway.html,
+ browse/pkg_class.html, browse/rate.cgi, browse/reason.html,
+ browse/reason_type.html, browse/router.cgi,
+ browse/svc_acct_pop.cgi, edit/msgcat.cgi,
+ search/prepay_credit.html: its all about control
+
+2007-01-30 11:45 ivan
+
+ * FS/FS/option_Common.pm: fix old-record detection bug in
+ option_Common
+
+2007-01-30 11:40 jeff
+
+ * FS/FS/: Conf.pm, svc_acct.pm, cust_main.pm: only add first user
+ to invoicing_list (ticket 1424)
+
+2007-01-30 10:52 jeff
+
+ * fs_selfservice/FS-SelfService/cgi/success-delayed.html,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi: additional info
+ available for self-service success (ticket 1420)
+
+2007-01-29 15:16 ivan
+
+ * httemplate/config/: config-process.cgi, config-view.cgi,
+ config.cgi: putting the C in ACL
+
+2007-01-29 13:11 ivan
+
+ * httemplate/misc/process/cancel_pkg.html: correct a typo, and
+ <%init> helps alot
+
+2007-01-29 10:50 ivan
+
+ * httemplate/pref/: pref-process.html, pref.html: record resolution
+ & colro depth in user prefs
+
+2007-01-29 08:16 ivan
+
+ * FS/FS/: cust_main.pm, rate.pm: turn off debugging
+
+2007-01-29 08:01 ivan
+
+ * FS/FS/: cust_main.pm, cust_main_Mixin.pm, UI/Web.pm: fix invoice
+ email display bug on advanced package report, closes: Bug#1416
+
+2007-01-29 04:07 ivan
+
+ * htetc/handler.pl: warnings in <%once> sections should not be
+ fatal
+
+2007-01-29 03:55 ivan
+
+ * FS/FS/option_Common.pm: remove extraneous debugging
+
+2007-01-29 03:31 ivan
+
+ * Makefile: looks like a duck
+
+2007-01-27 18:21 ivan
+
+ * FS/FS/svc_domain.pm, httemplate/view/svc_domain.cgi: pretty up
+ domain zone viewing a little
+
+2007-01-26 00:18 ivan
+
+ * FS/FS/access_user.pm: out of scope bad
+
+2007-01-26 00:17 ivan
+
+ * FS/FS/access_user.pm: spurious password changes bad...
+
+2007-01-26 00:11 ivan
+
+ * FS/FS/: Record.pm, option_Common.pm: oops, debugging turned on
+
+2007-01-26 00:04 ivan
+
+ * FS/FS/access_user.pm, httemplate/elements/header.html,
+ httemplate/elements/menu.html, httemplate/elements/xmenu.css,
+ httemplate/elements/xmenu.top.css,
+ httemplate/elements/xmenu.top.js,
+ httemplate/images/arrow.down.png,
+ httemplate/images/menu-left-example.png,
+ httemplate/images/menu-top-example.png,
+ httemplate/pref/pref-process.html, httemplate/pref/pref.html,
+ FS/FS/Record.pm, FS/FS/m2m_Common.pm, FS/FS/option_Common.pm: top
+ bar option!
+
+2007-01-24 22:04 ivan
+
+ * httemplate/elements/xmenu.css: fuck. you. IE7.
+
+2007-01-24 22:00 ivan
+
+ * httemplate/elements/: menu.html, xmenu.css: fuck. you. IE7.
+
+2007-01-24 15:41 ivan
+
+ * httemplate/misc/download-batch.cgi: 00 = sale, 01 = pre-auth. we
+ need the former
+
+2007-01-23 21:33 jeff
+
+ * httemplate/edit/process/elements/process.html: squarely between
+ shame in you and so close
+
+2007-01-23 15:42 jeff
+
+ * FS/FS.pm, FS/bin/freeside-daily, conf/impending_recur_template,
+ FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/cust_pkg_option.pm,
+ FS/FS/Cron/notify.pm, FS/FS/part_pkg/flat_delayed.pm: notices
+ before first charge on flat_delayed
+
+2007-01-22 23:44 ivan
+
+ * FS/FS/cust_pay_batch.pm: even have 4 digit zips in there eek
+
+2007-01-22 22:51 ivan
+
+ * FS/FS/Record.pm: remove spurious duplicateion from lineitem patch
+
+2007-01-22 22:45 ivan
+
+ * FS/FS/payby.pm: fix PREP payby
+
+2007-01-22 22:41 ivan
+
+ * FS/FS/cust_pay.pm: have to use payinfo_Mixin if we're going to
+ ISA it
+
+2007-01-22 09:05 ivan
+
+ * Makefile, htetc/freeside-base1.99.conf: halfass v2 too
+
+2007-01-22 06:29 ivan
+
+ * FS/FS/CGI.pm: hopefully this should be the last bit of the
+ relative URL fixing
+
+2007-01-22 03:41 ivan
+
+ * httemplate/view/cust_main.cgi: closing tags helps alot
+
+2007-01-22 03:39 ivan
+
+ * httemplate/view/: cust_main.cgi, cust_main/notes.html: fix
+ elements/ links here too
+
+2007-01-22 03:36 ivan
+
+ * httemplate/elements/: phonenumber.html, progress-init.html,
+ progress-popup.html, tr-input-beginning_ending.html,
+ tr-input-date-field.html: interpolation helps alot
+
+2007-01-22 03:30 ivan
+
+ * httemplate/elements/: phonenumber.html, progress-init.html,
+ progress-popup.html, tr-input-beginning_ending.html,
+ tr-input-date-field.html: use $fsurl instead of relative ../
+ addressing
+
+2007-01-21 18:42 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html: accept CVV2
+ on self-service manual payment screen
+
+2007-01-21 13:45 ivan
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_ApplicationCommon.pm, FS/FS/cust_bill_pay.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_credit_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/part_bill_event.pm, FS/FS/part_pkg.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/ClientAPI/Signup.pm,
+ FS/FS/Cron/bill.pm, httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/cust_main.cgi, httemplate/misc/bill.cgi:
+ Have lineitem-specific applications happen in all cases; add
+ weightsto control
+
+2007-01-21 05:42 ivan
+
+ * rt/: FREESIDE_MODIFIED, lib/RT/SearchBuilder.pm: add dependancy
+ on DBIx::SB 1.36 for Pg 8.1+
+
+2007-01-21 05:26 ivan
+
+ * Makefile, htetc/freeside-base.conf, htetc/freeside-base1.conf,
+ htetc/freeside-base2.conf: mod_perl v2, it finally happened
+
+2007-01-21 01:13 ivan
+
+ * httemplate/search/cust_svc.html: trim leading & trailing
+ whitespace from service searches
+
+2007-01-21 01:10 ivan
+
+ * FS/FS/: CGI.pm, UI/Web.pm: fix svc_url to work no matter where we
+ are coming from in the tree - use rooturl() instead of popurl()
+
+2007-01-19 16:08 jeff
+
+ * httemplate/edit/process/quick-charge.cgi: strip all blank lines,
+ not just trailing ones
+
+2007-01-19 15:00 jeff
+
+ * httemplate/misc/download-batch.cgi: work around CSV brokenness
+
+2007-01-19 14:02 jeff
+
+ * httemplate/edit/process/: access_user.html,
+ elements/process.html: blank password on error
+
+2007-01-19 08:31 jeff
+
+ * httemplate/edit/: access_user.html, process/access_user.html,
+ process/elements/process.html: no password in html source on
+ employee edit
+
+2007-01-18 10:14 ivan
+
+ * FS/FS/cust_pay_batch.pm: fix zip parsing for batch results -
+ don't want to abort processing because of an old not-well-checked
+ zip
+
+2007-01-17 15:28 ivan
+
+ * FS/FS/part_export/shellcommands.pm: fix crypted password bug
+ cause by recent fix to not quote things on STDIN, yuck.
+
+2007-01-17 15:27 jeff
+
+ * httemplate/edit/svc_acct.cgi, httemplate/misc/change_pkg.cgi,
+ httemplate/misc/payment.cgi,
+ httemplate/view/elements/svc_Common.html, FS/FS/CGI.pm,
+ httemplate/misc/process/payment.cgi,
+ httemplate/view/svc_acct.cgi: link to customer from manual
+ payment screen (ticket 1414)
+
+2007-01-17 09:41 jeff
+
+ * FS/FS/: cust_pkg.pm, reason.pm: fix bug wrt suspend/cancel
+ reasons (suspends fail in freeside-daily)
+
+2007-01-17 08:41 jeff
+
+ * FS/FS/part_pkg/flat.pm: noise reduction
+
+2007-01-17 07:50 jeff
+
+ * httemplate/view/cust_main/packages.html: fix suspend link
+
+2007-01-16 16:40 jeff
+
+ * httemplate/edit/: access_user.html, process/access_user.html:
+ employee edit (ticket 1412)
+
+2007-01-16 13:36 jeff
+
+ * rt/: FREESIDE_MODIFIED, etc/schema.Pg: revert unnecessary changes
+ for ticket 1364
+
+2007-01-16 09:39 jeff
+
+ * rt/: FREESIDE_MODIFIED, etc/schema.Pg: RT vs Pg8 (ticket 1364)
+
+2007-01-16 01:11 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: have signup package list respect
+ agentnum sent from client
+
+2007-01-15 14:15 jeff
+
+ * httemplate/edit/process/quick-charge.cgi: that was DUM
+
+2007-01-14 23:53 ivan
+
+ * httemplate/misc/download-batch.cgi: add batch expiration date
+ incrementer for 295
+
+2007-01-14 23:22 ivan
+
+ * FS/FS/Conf.pm: add descriptions for batchconfig-PAP and
+ batchconfig-csv-chase_canada-E-xactBatch
+
+2007-01-12 15:27 jeff
+
+ * FS/FS/Schema.pm, FS/FS/svc_broadband.pm,
+ httemplate/view/svc_broadband.cgi, FS/FS/part_export/prizm.pm,
+ httemplate/edit/svc_broadband.cgi: added svc_broadband
+ description which is appended to Site Name in prizm export
+
+2007-01-11 18:04 jeff
+
+ * FS/FS/cust_main.pm, FS/FS/part_pkg/flat.pm,
+ httemplate/edit/quick-charge.html,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/view/cust_main/packages.html: one-time charge
+ enhancements
+
+2007-01-10 15:21 ivan
+
+ * httemplate/search/queue.html: add "select all" and "unselect" all
+ buttons to bulk queue operations
+
+2007-01-10 00:56 ivan
+
+ * FS/FS/payinfo_Mixin.pm: fix harmless warning: Use of
+ uninitialized value in string ne at
+ /usr/local/share/perl/5.8.4/FS/payinfo_Mixin.pm line 116
+
+2007-01-10 00:50 ivan
+
+ * httemplate/view/cust_main/contacts.html: you have got to be
+ kidding me.
+
+2007-01-10 00:48 ivan
+
+ * httemplate/view/cust_main/contacts.html: nothing could have ever
+ gone wrong with so simple a change. oh noooo
+
+2007-01-09 23:48 ivan
+
+ * httemplate/view/cust_main/contacts.html: fix ship_county display
+ bug
+
+2007-01-09 21:51 ivan
+
+ * FS/FS/cust_bill.pm: add custnum to invoice template vars
+
+2007-01-09 18:41 jeff
+
+ * FS/FS/cust_pay_batch.pm: E-xactBatch masks card numbers
+
+2007-01-09 16:42 jeff
+
+ * httemplate/: misc/upload-batch.cgi, search/cust_pay_batch.cgi:
+ ignore filenames and rely on selected batch number
+
+2007-01-09 16:41 jeff
+
+ * httemplate/misc/download-batch.cgi: permit batch redownloads
+
+2007-01-09 14:57 ivan
+
+ * FS/FS/Schema.pm: 2147483647 should be enough bytes for anyone!
+
+2007-01-08 09:36 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/cust_credit_refund.pm,
+ FS/FS/cust_pay_refund.pm, FS/FS/cust_refund.pm,
+ httemplate/misc/delete-cust_refund.cgi,
+ httemplate/view/cust_main/payment_history.html: refund deletion
+
+2007-01-05 16:47 jeff
+
+ * httemplate/edit/part_svc.cgi: fix UI funkiness
+
+2007-01-04 21:19 jeff
+
+ * fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/view_usage.html,
+ FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/provision_list.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_usage_details.html: more
+ self-servicey stuff (change package, detailed usage)
+
+2007-01-03 23:26 ivan
+
+ * conf/blank_logo.eps: adding a simple blank logo; people are
+ trying to comment out stuff in the template
+
+2007-01-03 21:49 ivan
+
+ * httemplate/edit/part_bill_event.cgi: invoice event to credit out
+ a customer's balance, presumably as bad debt
+
+2007-01-03 11:47 ivan
+
+ * httemplate/view/cust_main/packages.html: fix suspend link
+
+2007-01-02 13:46 ivan
+
+ * FS/FS/UI/Web.pm, httemplate/search/cust_svc.html: fix service
+ searching & links
+
+2007-01-02 13:44 ivan
+
+ * FS/FS/cust_main.pm: stupid semicolon!
+
+2007-01-02 13:06 ivan
+
+ * FS/FS/cust_main.pm: fix num_pkgs to accept empty $sql param, no
+ trailing AND
+
+2007-01-02 12:29 ivan
+
+ * httemplate/view/cust_main/packages.html: fix popup target links
+
+2007-01-02 12:25 ivan
+
+ * httemplate/view/svc_Common.html: missing from svc patch, oops!
+
+2007-01-02 10:38 jeff
+
+ * FS/FS/svc_acct.pm: omit spurious export triggering
+
+2006-12-29 13:48 jeff
+
+ * httemplate/: elements/select-agent.html,
+ graph/report_cust_bill_pkg.html: correct bad include, and improve
+ no agent selected condition
+
+2006-12-29 00:51 ivan
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm, FS/FS/part_svc.pm,
+ FS/FS/pkg_svc.pm, FS/FS/registrar.pm, FS/FS/svc_Common.pm,
+ FS/FS/svc_External_Common.pm, FS/FS/svc_Parent_Mixin.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_broadband.pm, FS/FS/svc_domain.pm,
+ FS/FS/svc_external.pm, FS/FS/svc_forward.pm, FS/FS/svc_phone.pm,
+ FS/FS/svc_www.pm, FS/FS/UI/Web.pm, FS/t/registrar.t,
+ FS/t/svc_External_Common.t, FS/t/svc_Parent_Mixin.t,
+ eg/table_template-svc.pm, httemplate/browse/part_svc.cgi,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_Common.html,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_broadband.cgi,
+ httemplate/edit/svc_domain.cgi, httemplate/edit/svc_external.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/edit/svc_www.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/elements/process.html, FS/MANIFEST,
+ httemplate/edit/process/svc_Common.html,
+ httemplate/elements/header.html, httemplate/elements/menu.html,
+ httemplate/misc/link.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_svc.html, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_broadband.cgi,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_external.cgi,
+ httemplate/search/svc_forward.cgi,
+ httemplate/search/svc_phone.cgi, httemplate/search/svc_www.cgi,
+ httemplate/view/elements/svc_Common.html,
+ httemplate/view/cust_main/packages.html: service refactor!
+
+2006-12-29 00:41 ivan
+
+ * httemplate/edit/process/cust_main.cgi: slight pedanticism
+
+2006-12-29 00:39 ivan
+
+ * httemplate/edit/svc_broadband.cgi: svc broadband new arg parsing
+
+2006-12-29 00:35 ivan
+
+ * httemplate/misc/: payment.cgi, process/payment.cgi: just in case
+ it gets capitalized
+
+2006-12-29 00:34 ivan
+
+ * httemplate/edit/process/quick-charge.cgi: whitespace
+
+2006-12-29 00:34 ivan
+
+ * httemplate/edit/cust_main/billing.html: slight more space
+ necessary for ACH account numbers
+
+2006-12-29 00:31 ivan
+
+ * init.d/freeside-init: kill off all queued processes!
+
+2006-12-29 00:30 ivan
+
+ * httemplate/docs/index.html: link more docs to wiki
+
+2006-12-29 00:29 ivan
+
+ * httemplate/browse/pay_batch.cgi: this is unused, replaced by
+ search/pay_batch.cgi
+
+2006-12-29 00:25 ivan
+
+ * FS/FS/part_export/vpopmail.pm: didn't get checked in with
+ configurable target dirs, oops
+
+2006-12-29 00:25 ivan
+
+ * FS/FS/svc_Common.pm: doc
+
+2006-12-29 00:24 ivan
+
+ * FS/FS/part_pkg.pm: snot spacing
+
+2006-12-29 00:24 ivan
+
+ * FS/FS/part_export.pm: use FS::queue for exports, though they
+ should probably just use it themselves
+
+2006-12-29 00:21 ivan
+
+ * Changelog, Changes.1.5.7, Changes.1.5.8: removing old changelogs
+
+2006-12-29 00:20 ivan
+
+ * Changes.1.7.0: changelog now in the wiki
+
+2006-12-28 23:23 ivan
+
+ * httemplate/edit/elements/edit.html: document field types
+
+2006-12-28 16:30 jeff
+
+ * FS/FS/part_export/prizm.pm: minor fixups
+
+2006-12-28 12:52 jeff
+
+ * httemplate/elements/tr-select-reason.html: activate submit button
+ more readily
+
+2006-12-28 00:16 ivan
+
+ * FS/FS/payinfo_Mixin.pm: tyop
+
+2006-12-27 23:02 ivan
+
+ * bin/pg-version: adding pg-version
+
+2006-12-26 19:29 ivan
+
+ * httemplate/edit/cust_main_county-expand.cgi: fix sanity check on
+ taxclass expander
+
+2006-12-26 18:37 ivan
+
+ * FS/FS/Misc.pm, htetc/handler.pl,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/edit/cust_main/select-county.html,
+ httemplate/misc/counties.cgi: Add a (magically appearing and
+ disappearing) label on the county selector. confusing when
+ labeled "state"
+
+2006-12-26 17:35 ivan
+
+ * httemplate/view/cust_main/contacts.html: show county on customer
+ view
+
+2006-12-26 17:07 ivan
+
+ * httemplate/: edit/cust_main.cgi,
+ edit/cust_main/select-county.html, misc/counties.cgi: fix county
+ selector
+
+2006-12-26 11:53 ivan
+
+ * FS/FS/part_export/shellcommands.pm: do shell_quote-ing after
+ STDIN strings are evaluated
+
+2006-12-23 17:28 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/cust_pay.pm, FS/FS/cust_pay_void.pm,
+ FS/FS/cust_refund.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/view/cust_main/payment_history.html: fix more paymask
+ regressions: allow editing records with existing expired cards,
+ fix masking just-entered values when erroring out, fix echeck
+ entry inserting as empty routing code and "xxEK" account number,
+ remove old/deprecated/unused payinfo_maksed subs and calls
+
+2006-12-22 21:37 jeff
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/svc_broadband.pm,
+ FS/FS/part_export/prizm.pm, httemplate/edit/svc_broadband.cgi,
+ httemplate/view/svc_broadband.cgi: inital prizm support
+
+2006-12-21 22:18 ivan
+
+ * httemplate/search/: queue.html, elements/search.html: patch
+ fixing "retry selected" and "remove select" in queue view
+
+2006-12-20 19:05 ivan
+
+ * httemplate/misc/payment.cgi: fix on-demand payment form when
+ making payments from masked data
+
+2006-12-20 02:51 ivan
+
+ * FS/FS/: cust_main.pm, payinfo_Mixin.pm: fix "recurring_billing"
+ flag for the wonderful new world of paymasking
+
+2006-12-20 01:49 ivan
+
+ * FS/FS/Schema.pm, FS/FS/pkg_class.pm,
+ httemplate/browse/pkg_class.html, httemplate/edit/pkg_class.html,
+ httemplate/elements/select-part_referral.html,
+ httemplate/elements/select-pkg_class.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-pkg_class.html,
+ httemplate/search/elements/search.html: add ability to disable
+ package classes
+
+2006-12-19 17:20 jeff
+
+ * FS/FS/: part_pkg.pm, ClientAPI/MyAccount.pm: inappropriate
+ cluckage
+
+2006-12-19 02:02 ivan
+
+ * FS/FS/cust_pay_batch.pm, FS/FS/payby.pm, FS/FS/payinfo_Mixin.pm,
+ htetc/handler.pl, httemplate/edit/cust_main/billing.html,
+ httemplate/search/cust_pay.cgi: clean up payinfo_Mixin to use
+ payby.pm for payby info and have card masking full 6-digit BIN
+ prefix for card identification. have cust_pay_batch use
+ payinfo_Mixin. require B:CC 0.30 for mask-aware cardtype(). fix
+ payment reports to use mask too if available, so credit card type
+ selection still works with encryption.
+
+2006-12-18 03:01 ivan
+
+ * htetc/handler.pl, httemplate/edit/part_pkg.cgi: fix tax class not
+ adding when there is only one pkg_class choice (triggers
+ javascript error
+
+2006-12-18 00:08 ivan
+
+ * Makefile: fix MASONDATA include for dev target
+
+2006-12-15 12:55 ivan
+
+ * httemplate/elements/dashboard-toplist.html: add a new ticket link
+
+2006-12-15 05:09 ivan
+
+ * httemplate/elements/dashboard-toplist.html: add a quick start at
+ a "dashboard" customer list, mostly for internal use for starters
+
+2006-12-15 05:00 ivan
+
+ * httemplate/elements/dashboard-toplist.html: add a quick start at
+ a "dashboard" customer list, mostly for internal use for starters
+
+2006-12-15 04:43 ivan
+
+ * FS/FS/Conf.pm, httemplate/index.html,
+ httemplate/elements/dashboard-toplist.html: add a quick start at
+ a "dashboard" customer list, mostly for internal use for starters
+
+2006-12-15 00:37 ivan
+
+ * FS/FS/Setup.pm, bin/expand-country: add a quick tool for adding
+ states to old databases
+
+2006-12-15 00:25 ivan
+
+ * httemplate/edit/: cust_main.cgi, cust_main/billing.html:
+ eliminate funkiness with reappearing "Postal invoice" on errors
+
+2006-12-14 21:29 jeff
+
+ * htetc/handler.pl, httemplate/edit/process/cust_main.cgi,
+ httemplate/elements/tr-input-date-field.html,
+ httemplate/view/cust_main/misc.html: move use statements to
+ handler.pl, do not show 1/1/70 for new birthdates, and improve
+ error handling
+
+2006-12-14 16:56 ivan
+
+ * httemplate/edit/process/cust_main.cgi, htetc/handler.pl: add
+ DateTime as a proper rather than hidden dependency
+
+2006-12-14 16:28 ivan
+
+ * Makefile, FS/bin/freeside-addoutsource,
+ FS/bin/freeside-deloutsource, FS/bin/freeside-queued,
+ FS/bin/freeside-selfservice-server,
+ FS/bin/freeside-sqlradius-radacctd, htetc/handler.pl: tiny bit of
+ cleanup from the conf merge
+
+2006-12-14 01:27 ivan
+
+ * Makefile, FS/FS/Conf.pm, FS/FS/UID.pm, FS/FS/access_user.pm,
+ FS/FS/part_export/vpopmail.pm, FS/bin/freeside-addoutsource,
+ FS/bin/freeside-addoutsourceuser, FS/bin/freeside-adduser,
+ FS/bin/freeside-deloutsource, FS/bin/freeside-deloutsourceuser,
+ FS/bin/freeside-deluser, FS/bin/freeside-queued,
+ FS/bin/freeside-selfservice-server, FS/bin/freeside-setup,
+ FS/bin/freeside-sqlradius-radacctd, FS/bin/freeside-upgrade,
+ bin/dbdef-create, bin/mapsecrets2access_user, htetc/handler.pl:
+ make the config directory configurable
+
+2006-12-13 22:00 ivan
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pay_void.pm, FS/FS/cust_refund.pm,
+ FS/FS/payinfo_Mixin.pm, FS/FS/ClientAPI/MyAccount.pm,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/misc/payment.cgi, httemplate/misc/process/payment.cgi,
+ httemplate/search/cust_pay.cgi, FS/MANIFEST,
+ FS/t/payinfo_Mixin.t, httemplate/edit/cust_refund.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/view/cust_main/billing.html,
+ httemplate/view/cust_main/payment_history.html: encryption fixes
+ from huntsberg & jayce
+
+2006-12-08 07:11 jeff
+
+ * FS/FS/svc_acct.pm: stop unsuspending inappropriately
+
+2006-12-08 05:36 ivan
+
+ * httemplate/: view/cust_main/quick-charge.html,
+ edit/process/quick-charge.cgi: some javascript validation magic
+ to give one-time charges better UI
+
+2006-12-07 08:46 ivan
+
+ * bin/pod2x: update pod2x for new self-service path
+
+2006-12-07 07:24 ivan
+
+ * FS/FS/Setup.pm: oops, fix additional US pseudo-states
+
+2006-12-06 18:40 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Record.pm, FS/FS/Schema.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/prepay_credit.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_broadband.pm, FS/FS/part_pkg/flat.pm,
+ FS/FS/part_pkg/prorate.pm, FS/FS/part_pkg/subscription.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/process_order_recharge.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_usage.html,
+ httemplate/edit/prepay_credit.cgi,
+ httemplate/edit/process/prepay_credit.cgi,
+ httemplate/misc/process/recharge_svc.html,
+ httemplate/search/prepay_credit.html: retouch bandwidth countdown
+
+2006-12-05 07:53 jeff
+
+ * FS/FS/part_export/sqlmail.pm: correct longstanding oops
+
+2006-12-04 18:37 jeff
+
+ * httemplate/: edit/svc_acct.cgi, edit/process/svc_acct.cgi,
+ view/svc_acct.cgi: editable service usage
+
+2006-12-02 16:42 ivan
+
+ * FS/FS/cust_main.pm: clean up whitespace merge noise
+
+2006-12-02 14:04 ivan
+
+ * FS/FS/part_pkg/flat_comission.pm: fix for commission price plan
+
+2006-11-30 22:31 jeff
+
+ * FS/FS/Conf.pm, FS/FS/cust_pay_batch.pm,
+ httemplate/misc/download-batch.cgi,
+ httemplate/search/cust_pay_batch.cgi: chase canada E-xactBatch
+
+2006-11-30 17:41 ivan
+
+ * FS/bin/freeside-queued: remove unnecessary service use (in an
+ awfully odd place)
+
+2006-11-30 17:35 ivan
+
+ * FS/bin/freeside-expiration-alerter: go away and never come back,
+ $Id$
+
+2006-11-30 17:34 ivan
+
+ * FS/bin/freeside-email: begone $Id$ and your diff noise!
+
+2006-11-29 18:27 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/cust_main.pm, FS/FS/svc_acct.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/UI/Web.pm,
+ FS/FS/part_export/sqlradius.pm,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_usage.html,
+ httemplate/edit/prepay_credit.cgi,
+ httemplate/edit/process/prepay_credit.cgi,
+ httemplate/misc/recharge_svc.html,
+ httemplate/misc/process/recharge_svc.html,
+ httemplate/search/prepay_credit.html,
+ httemplate/view/cust_main/packages.html: prepaid download/upload
+ tracking
+
+2006-11-28 01:38 ivan
+
+ * FS/FS/access_user.pm: usernames should be alphanumeric only
+
+2006-11-26 23:11 jeff
+
+ * FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/process_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: order package
+ from self-service
+
+2006-11-26 17:08 ivan
+
+ * FS/FS/svc_acct.pm: some svc_acct.pm fixes for s1
+
+2006-11-26 15:09 jeff
+
+ * httemplate/elements/menu.html: minor tyop
+
+2006-11-26 15:06 jeff
+
+ * httemplate/: edit/process/cust_main_note.cgi, view/cust_main.cgi,
+ view/cust_main/notes.html: more ajaxy customer notes
+
+2006-11-25 13:04 jeff
+
+ * httemplate/view/cust_main.cgi: chill FF wrt POSTDATA
+
+2006-11-24 02:34 ivan
+
+ * httemplate/browse/part_pkg.cgi: link to and label one-time
+ charges correctly
+
+2006-11-24 00:48 ivan
+
+ * httemplate/elements/tr-input-beginning_ending.html,
+ httemplate/elements/tr-input-lessthan_greaterthan.html,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_pay.html, FS/FS/UI/Web.pm: add less
+ than and greater than amounts to credit and payment searches
+
+2006-11-20 02:34 ivan
+
+ * httemplate/browse/agent.cgi: remove annoying black box around
+ override info
+
+2006-11-19 19:05 ivan
+
+ * httemplate/autohandler: bug fix for open transactions
+
+2006-11-17 18:19 jeff
+
+ * FS/FS/part_export/www_plesk.pm: add templating and web hosting
+ optional on domain addition
+
+2006-11-17 02:16 ivan
+
+ * FS/bin/freeside-prepaidd: initial fix for prepaid renewal
+ problem; attempt to use an existing credit/payment before
+ suspending
+
+2006-11-16 07:37 jeff
+
+ * httemplate/edit/process/part_bill_event.cgi: bug squishing
+
+2006-11-15 22:20 jeff
+
+ * httemplate/: elements/tr-input-date-field.html,
+ edit/cust_main.cgi, edit/process/cust_main.cgi,
+ view/cust_main/misc.html: switch birthdate to DateTime
+
+2006-11-14 04:22 ivan
+
+ * httemplate/misc/cust_main-import.cgi: add some docs on required
+ fields to page
+
+2006-11-13 20:04 ivan
+
+ * httemplate/: browse/agent.cgi,
+ misc/delete-agent_payment_gateway.cgi: working agent gateway
+ override delete link
+
+2006-11-13 19:30 ivan
+
+ * FS/FS/Misc.pm: Tie::IxHash data disappears when sent with
+ Storable
+
+2006-11-13 19:16 ivan
+
+ * FS/FS/Misc.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.html: have signup page
+ use card-types config too
+
+2006-11-13 18:54 ivan
+
+ * FS/FS/: Conf.pm, ClientAPI/MyAccount.pm: add card-types
+ configuration value for self-service
+
+2006-11-12 17:09 ivan
+
+ * FS/FS/CGI.pm, httemplate/elements/error.html,
+ httemplate/elements/header.html,
+ httemplate/pref/pref-process.html, httemplate/pref/pref.html: add
+ preference page, start with just a password changer
+
+2006-11-08 18:52 jeff
+
+ * FS/FS/TicketSystem/RT_External.pm: recorrect ticket order
+
+2006-11-08 18:28 jeff
+
+ * FS/FS/TicketSystem/RT_External.pm: correct ticket order
+
+2006-11-05 14:39 ivan
+
+ * Makefile: call it 1.7.1 to avoid confusion
+
+2006-11-05 14:28 ivan
+
+ * bin/: customer-faker, payment-faker: some sample data creators
+
+2006-11-05 11:34 ivan
+
+ * FS/FS/cust_main.pm: fix empty invoice number omitting in
+ realtime_bop, oops
+
+2006-11-05 11:22 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: turn off debugging
+
+2006-11-05 10:06 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: for want of a
+ paren
+
+2006-11-05 10:03 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/selfservice.cgi: have card_type
+ be an optional param anyway
+
+2006-11-05 10:03 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix auto-population of card_type
+ SELECT in self-service payment form
+
+2006-11-05 09:36 ivan
+
+ * fs_selfservice/FS-SelfService/: SelfService.pm,
+ cgi/selfservice.cgi: add some self-service debugging
+
+2006-11-05 08:58 ivan
+
+ * FS/bin/freeside-selfservice-server: fix spurious disconnection
+ errors in selfservice server log
+
+2006-11-05 07:55 ivan
+
+ * httemplate/view/cust_main/payment_history.html: fix but with no
+ "show prior history" showing up when everything is hidden
+
+2006-11-03 14:02 ivan
+
+ * FS/FS/part_pkg.pm: add every 45 day option to available
+ frequencies
+
+2006-10-31 10:56 jeff
+
+ * FS/FS/part_export/sqlradius.pm: do not require a reason to have
+ been suspended
+
+2006-10-31 08:57 jeff
+
+ * httemplate/edit/process/cust_main_note.cgi: ugh, too restrictive
+
+2006-10-30 04:39 ivan
+
+ * FS/FS/cust_main.pm: realtime_bop: don't pass an empty
+ invoice_number to B:OP, omit the field entirely
+
+2006-10-29 17:28 ivan
+
+ * FS/MANIFEST: removing freeside-daily
+
+2006-10-29 16:26 ivan
+
+ * FS/bin/freeside-bill: removing pre-1.4 freeside-bill
+
+2006-10-27 12:10 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: add
+ unsuspend-always_adjust_next_bill_date config option for qis
+
+2006-10-27 10:01 jeff
+
+ * FS/FS/AccessRight.pm, httemplate/edit/cust_main_note.cgi,
+ httemplate/edit/process/cust_main_note.cgi,
+ httemplate/elements/overlibmws_crossframe.js,
+ httemplate/view/cust_main/notes.html: editable notes
+
+2006-10-26 19:12 jeff
+
+ * FS/FS/part_export/: shellcommands.pm, sqlradius.pm: export
+ enhancements for suspend reasons
+
+2006-10-26 01:35 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.html: don't display a
+ (none) package selection if there's a default
+
+2006-10-25 11:10 jeff
+
+ * httemplate/view/cust_main/packages.html: show reasons
+
+2006-10-25 10:36 ivan
+
+ * httemplate/: elements/pager.html, search/cust_main.cgi,
+ search/elements/search.html: pagination patch from UNTD - limit
+ the number of page links displayed and have a drop-down option
+ for selecting number of entries per page
+
+2006-10-25 05:04 ivan
+
+ * conf/ticket_system: default to RT_Internal ticketing
+
+2006-10-24 21:51 jeff
+
+ * httemplate/elements/tr-selectmultiple-part_pkg.html: heh
+
+2006-10-24 21:44 jeff
+
+ * httemplate/: elements/tr-selectmultiple-part_pkg.html,
+ search/cust_pkg.cgi, search/report_cust_pkg.html: select multiple
+ pkgparts on advanced package report
+
+2006-10-24 20:12 jeff
+
+ * httemplate/: edit/svc_www.cgi, view/svc_www.cgi: no usersvc
+ svc_www tweak
+
+2006-10-24 19:22 jeff
+
+ * FS/FS/part_svc.pm, FS/FS/part_svc_column.pm,
+ httemplate/browse/part_svc.cgi, httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_acct.cgi: limited domain select
+
+2006-10-24 11:26 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ httemplate/view/cust_main/misc.html: add customer signup date
+
+2006-10-23 02:44 ivan
+
+ * httemplate/elements/menu.html: have the unlinked account search
+ go to UN_username so it doesn't miss accounts with no UID
+
+2006-10-23 02:35 ivan
+
+ * httemplate/elements/header.html: eek, VALIGN=top for the page
+
+2006-10-23 02:24 ivan
+
+ * FS/FS/cust_main.pm: always do substring & fuzzy, getting
+ complains searches are not returning enough
+
+2006-10-23 01:47 ivan
+
+ * httemplate/misc/process/cancel_pkg.html: better error message if
+ you don't enter a reason. FS::reason::check can untaint its
+ data, and will allow punctuation in reasons
+
+2006-10-23 01:30 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: agent.cgi,
+ cust_bill-logo.cgi, passwd.cgi, selfservice.cgi, signup.html:
+ remove -w to prevent warnings from messing up otherwise working
+ CGIs
+
+2006-10-22 23:38 ivan
+
+ * FS/FS/Record.pm: add AU to list of zip-requiring countries
+
+2006-10-22 21:21 jeff
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ FS/FS/part_bill_event.pm, httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/reason.html,
+ httemplate/edit/process/part_bill_event.cgi,
+ httemplate/elements/tr-select-reason.html,
+ httemplate/misc/cancel_pkg.html,
+ httemplate/misc/process/cancel_pkg.html: events should attach
+ reasons
+
+2006-10-22 18:47 ivan
+
+ * httemplate/view/cust_main/packages.html: fix regression from
+ table re-layout: don't display provision links for cancelled
+ packages
+
+2006-10-22 04:42 ivan
+
+ * FS/FS/Conf.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi: add signup server
+ default package
+
+2006-10-21 22:22 ivan
+
+ * FS/FS/part_pkg/prepaid.pm: doh, forgot fieldorder. should just
+ use Tie::IxHash here too
+
+2006-10-21 22:18 ivan
+
+ * FS/: FS/part_pkg/prepaid.pm, bin/freeside-prepaidd: add price
+ plan option for prepaid packages to cancel instead of suspend
+
+2006-10-21 17:35 ivan
+
+ * httemplate/elements/menu.html: link to wrong place
+
+2006-10-20 15:49 ivan
+
+ * FS/FS/part_pkg.pm: add 48-hour billing
+
+2006-10-19 07:32 jeff
+
+ * FS/FS/Conf.pm: [no log message]
+
+2006-10-19 07:29 jeff
+
+ * FS/FS/cust_pkg_reason.pm: suspension and cancellation reasons
+
+2006-10-19 07:26 jeff
+
+ * httemplate/elements/tr-select-reason.html,
+ httemplate/misc/cancel_pkg.cgi, httemplate/misc/cancel_pkg.html,
+ httemplate/misc/expire_pkg.cgi,
+ httemplate/misc/process/cancel_pkg.html,
+ httemplate/misc/process/expire_pkg.cgi,
+ httemplate/misc/susp_pkg.cgi, FS/t/cust_pkg_reason.t: suspension
+ and cancellation reasons
+
+2006-10-19 07:23 jeff
+
+ * FS/FS/cancel_reason.pm, FS/FS/reason.pm, FS/FS/reason_type.pm,
+ FS/t/cancel_reason.t, FS/t/reason.t, FS/t/reason_type.t,
+ httemplate/browse/reason.html,
+ httemplate/browse/reason_type.html,
+ httemplate/edit/process/reason.html,
+ httemplate/edit/process/reason_type.html,
+ httemplate/edit/reason.html, httemplate/edit/reason_type.html:
+ suspension and cancellation reasons
+
+2006-10-18 21:41 jeff
+
+ * FS/FS/part_export/acct_plesk.pm: wrong method
+
+2006-10-18 16:07 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Schema.pm, FS/FS/part_bill_event.pm,
+ FS/MANIFEST, htetc/handler.pl,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/process/part_bill_event.cgi,
+ httemplate/elements/menu.html,
+ httemplate/view/cust_main/packages.html: suspension and
+ cancellation reasons
+
+2006-10-18 02:30 ivan
+
+ * httemplate/docs/: config.html, export.html: removing obsolete
+ config.html, moving info from export.html into wiki
+
+2006-10-17 17:05 jeff
+
+ * httemplate/elements/tr-input-date-field.html: fix ugly null dates
+
+2006-10-17 02:03 ivan
+
+ * rt/sbin/rt-setup-database.in: commiting rt 3.4.5 to HEAD
+
+2006-10-16 22:49 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: fix signup problem
+ on IE, whew!
+
+2006-10-16 10:59 ivan
+
+ * FS/FS/Record.pm: fix for agent_custid unique bs ('violates unique
+ constraint cust_main18'), hope this doesn't break anything else
+
+2006-10-16 10:13 ivan
+
+ * httemplate/: edit/cust_main.cgi, view/cust_main/misc.html: its
+ not my monday morning
+
+2006-10-16 10:11 ivan
+
+ * FS/FS/Conf.pm: closing bracket helps
+
+2006-10-16 10:10 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi,
+ httemplate/view/cust_main/misc.html: add a conf checkbox to turn
+ DOB on
+
+2006-10-16 09:56 ivan
+
+ * httemplate/edit/cust_main.cgi: fix stickiness bug when editing
+ advertising sources
+
+2006-10-15 19:21 ivan
+
+ * FS/FS/part_export/: acct_plesk.pm, www_plesk.pm: fix typo
+ switching Net::Plesk to a run-time dependency
+
+2006-10-14 16:53 ivan
+
+ * FS/FS/part_export/: acct_plesk.pm, www_plesk.pm: don't load
+ Net::Plesk until its needed, to prevent every install from
+ depending on it
+
+2006-10-14 13:47 jeff
+
+ * FS/FS/part_export/: acct_plesk.pm, www_plesk.pm: preliminary
+ plesk support
+
+2006-10-14 03:09 ivan
+
+ * httemplate/search/report_receivables.cgi: that should fix
+ statuses on receivables report
+
+2006-10-14 02:55 ivan
+
+ * httemplate/docs/: index.html, selfservice.html: move
+ signup/self-service install to wiki
+
+2006-10-12 02:42 ivan
+
+ * FS/FS/cust_main.pm: fix "amatch: $_ is undefined: what are you
+ matching" error when there are no companies in the fuzzy cache
+ yet
+
+2006-10-08 22:05 ivan
+
+ * FS/FS/Conf.pm: date selektah
+
+2006-10-08 21:27 jeff
+
+ * httemplate/edit/process/cust_main.cgi: more BoD
+
+2006-10-08 18:05 ivan
+
+ * FS/bin/: freeside-addoutsource, freeside-addoutsourceuser,
+ freeside-adduser: update the tools for dev installs
+
+2006-10-08 17:30 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main.cgi: have the new style
+ notes be the default
+
+2006-10-08 17:26 ivan
+
+ * httemplate/view/: cust_main.cgi, cust_main/tickets.html: clean up
+ the customer view a tiny bit
+
+2006-10-08 17:13 ivan
+
+ * htetc/handler.pl: add cust_main_note to handler.pl
+
+2006-10-08 01:17 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/Schema.pm,
+ FS/FS/Setup.pm, FS/FS/cust_bill.pm, FS/FS/pay_batch.pm,
+ FS/FS/payby.pm, bin/customer-faker, htetc/handler.pl,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/elements/menu.html,
+ httemplate/search/cust_pay_batch.cgi,
+ httemplate/search/pay_batch.cgi,
+ httemplate/search/elements/search.html: add menu items for credit
+ card batching, debug last-minute changes to payby.pm, add ACL for
+ re-processing batches, separate CARD and CHEK batches, fixed
+ defaults for batch formats
+
+2006-10-07 16:40 ivan
+
+ * FS/FS/cust_bill.pm: remove a spurious commit from batch_card and
+ document realtime option
+
+2006-10-07 14:40 ivan
+
+ * FS/FS/cust_main.pm: better error msg on CSV import with bad
+ pkgpart
+
+2006-10-04 14:22 jeff
+
+ * FS/FS/Schema.pm, FS/FS/cust_main.pm, FS/FS/Conf.pm,
+ FS/FS/Record.pm, httemplate/edit/cust_main.cgi,
+ httemplate/elements/tr-input-date-field.html,
+ httemplate/view/cust_main/misc.html: DoB
+
+2006-10-03 15:44 jeff
+
+ * FS/FS/AccessRight.pm, FS/FS/Conf.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_note.pm, FS/MANIFEST, FS/FS/Schema.pm,
+ FS/t/cust_main_note.t, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_note.cgi,
+ httemplate/edit/process/cust_main_note.cgi,
+ httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/notes.html: Enhanced customer notes
+
+2006-10-03 08:59 ivan
+
+ * FS/FS/: cust_main_invoice.pm, cust_pay_batch.pm, svc_external.pm,
+ svc_www.pm: s/repalce/replace/ in POD doc
+
+2006-09-29 10:22 ivan
+
+ * FS/FS/access_user.pm: fix problem with viewing unlinked services
+
+2006-09-29 09:18 ivan
+
+ * FS/FS/part_export/domain_sql.pm: precedence error causing schema
+ mapped values to disappear
+
+2006-09-29 08:36 ivan
+
+ * FS/FS/part_export/domain_sql.pm: fix mapping problem in
+ domain_sql export
+
+2006-09-28 10:45 ivan
+
+ * FS/FS/svc_acct.pm: missing $ in ldap_password
+
+2006-09-26 11:55 ivan
+
+ * httemplate/search/: svc_Smart.html, svc_acct.cgi: fix
+ username@domain search
+
+2006-09-23 11:33 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/acct_sql.pm: update to
+ mailserver integration
+
+2006-09-20 21:41 ivan
+
+ * FS/FS/part_export/acct_sql.pm: support static values in acct_sql
+ as well
+
+2006-09-20 09:00 ivan
+
+ * FS/FS/part_export/acct_sql.pm: eek, terminate the javascript
+
+2006-09-20 08:20 ivan
+
+ * FS/FS/part_export/acct_sql.pm: finish correcting the naming
+
+2006-09-20 08:19 ivan
+
+ * FS/FS/part_export/domain_sql.pm: tie correctly!
+
+2006-09-19 09:49 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/acct_sql.pm,
+ part_export/domain_sql.pm: add domain_sql export for new
+ mailserver config and modify acct_sql export for same
+
+2006-09-17 19:21 ivan
+
+ * httemplate/elements/menu.html: ticket system disableability for
+ rainbowshops
+
+2006-09-16 13:07 ivan
+
+ * bin/customer-faker: bulk fake customer insert
+
+2006-09-15 12:15 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/search/cust_tax_exempt_pkg.cgi,
+ httemplate/view/cust_main/payment_history.html: add link to
+ customer tax exemptions to customer view page
+
+2006-09-14 22:10 ivan
+
+ * FS/FS/: Schema.pm, cust_bill_ApplicationCommon.pm,
+ cust_bill_pay_pkg.pm, cust_credit_bill_pkg.pm: add sdate and
+ edate to cust_bill_pay_pkg and cust_credit_bill_pkg tables
+
+2006-09-14 12:33 ivan
+
+ * httemplate/edit/part_virtual_field.cgi: don't promise virtual
+ fields on tables we can't deliver them on. virtual fields suck
+ anyway, they should be real database fields
+
+2006-09-14 12:30 ivan
+
+ * httemplate/edit/process/generic.cgi: pointer to
+ elements/process.html, fwiw
+
+2006-09-13 09:01 ivan
+
+ * httemplate/search/report_cust_pay.html: add check # search, here
+ for now...
+
+2006-09-13 07:57 ivan
+
+ * FS/FS/cust_bill_ApplicationCommon.pm: yes, that does appear to be
+ the fix for all this trouble. s/qsearchs/qsearch/
+
+2006-09-13 07:53 ivan
+
+ * FS/FS/: cust_credit.pm, cust_pay.pm: some cleanup while i'm here
+
+2006-09-12 19:14 ivan
+
+ * FS/FS/Record.pm: encryption-on-insert bugfix from untd
+
+2006-09-12 19:11 ivan
+
+ * FS/FS/svc_Common.pm: in-place replace bugfix from untd &
+ debugging cleanup
+
+2006-09-12 14:27 ivan
+
+ * httemplate/search/cust_main.cgi: no need to log this
+
+2006-09-05 18:20 ivan
+
+ * bin/breakdown-bill-applications: not done yet
+
+2006-09-05 18:08 ivan
+
+ * fs_selfservice/DEPLOY: this was supposed to just be a local hack,
+ how did it wind up in CVS?
+
+2006-09-05 18:08 ivan
+
+ * FS/bin/freeside-setup: new world ACLs
+
+2006-09-05 18:07 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: less cut and paste
+
+2006-09-05 18:07 ivan
+
+ * FS/FS/payby.pm: ugh DCLN :/
+
+2006-09-05 18:06 ivan
+
+ * FS/FS/cust_svc.pm: add some debugging
+
+2006-09-05 18:06 ivan
+
+ * FS/FS/cdr.pm: add missing =back
+
+2006-09-05 09:44 ivan
+
+ * httemplate/edit/process/cust_refund.cgi: fix refunds, really
+
+2006-09-05 09:27 ivan
+
+ * httemplate/edit/process/cust_refund.cgi: fix FS::payby::payby2bop
+ usage
+
+2006-09-04 13:31 ivan
+
+ * httemplate/search/sqlradius.cgi: really fix the embedded duration
+ table
+
+2006-09-04 13:22 ivan
+
+ * httemplate/search/sqlradius.cgi: use the exact provided time
+
+2006-09-04 13:15 ivan
+
+ * httemplate/search/sqlradius.cgi: fix the date/time parsing
+
+2006-09-04 13:12 ivan
+
+ * httemplate/search/sqlradius.cgi: fix the embedded duration table
+
+2006-09-04 13:10 ivan
+
+ * httemplate/search/sqlradius.cgi: fix the gridding colors
+
+2006-09-04 13:05 ivan
+
+ * httemplate/: elements/tr-input-beginning_ending.html,
+ search/sqlradius.cgi, search/sqlradius.html: allow time selection
+ in RADIUS searches and grid-ize the resulting table
+
+2006-09-04 09:24 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/elements/menu.html: add RADIUS
+ session search back to main menu
+
+2006-09-02 20:14 ivan
+
+ * bin/dbdef-create: REALLY don't error out trying to create a dbdef
+ file from scratch
+
+2006-09-02 20:12 ivan
+
+ * bin/dbdef-create: don't error out trying to create a dbdef file
+ from scratch
+
+2006-09-02 13:38 ivan
+
+ * httemplate/search/: cust_bill_event.cgi, cust_bill_event.html:
+ agent-virtualize invoice event reports
+
+2006-09-01 00:52 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.html: pretty this up a
+ little
+
+2006-08-31 18:51 lawrence
+
+ * FS/FS/Conf.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.html: Progress
+ checkpoint on improved signup stuff for additional services
+
+2006-08-31 14:26 lawrence
+
+ * httemplate/config/config-process.cgi: make select-sub parameters
+ work
+
+2006-08-31 13:47 ivan
+
+ * eg/TEMPLATE_cust_main.import, etc/megapop.pl: BEGONE $Id
+
+2006-08-31 13:44 ivan
+
+ * bin/: svc_acct.import, svc_domain.erase: BEGONE $Id$
+
+2006-08-31 13:44 ivan
+
+ * bin/svc_acct.export: removing obsolete file
+
+2006-08-31 12:59 ivan
+
+ * bin/fs-migrate-svc_acct_sm: BEGONE $Id$
+
+2006-08-31 12:25 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.cgi: BEGONE $Id$
+
+2006-08-30 09:24 ivan
+
+ * bin/mapsecrets2access_user: need a username arg for now... this
+ should make the error message less confusing
+
+2006-08-30 08:41 ivan
+
+ * FS/bin/freeside-adduser: get rid of all the htpasswd stuff in
+ freeside-adduser
+
+2006-08-29 11:04 khoff
+
+ * FS/FS/part_pkg/flat_introrate.pm: Introductory rates...just what
+ we always wanted. Requires Date::Manip.
+
+2006-08-29 10:50 lawrence
+
+ * FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html: Signup
+ Enhancements
+
+2006-08-28 06:52 ivan
+
+ * httemplate/edit/process/cust_main.cgi: parameters to method calls
+ require ( ), arrgh
+
+2006-08-27 17:52 jeff
+
+ * FS/FS/cust_bill.pm: protect against a race
+
+2006-08-27 15:55 jeff
+
+ * Makefile: more clean
+
+2006-08-27 14:50 jeff
+
+ * FS/FS/cust_bill.pm, FS/FS/payby.pm,
+ httemplate/misc/process/payment.cgi: fix payby2bop brokenness
+
+2006-08-27 13:10 jeff
+
+ * FS/FS/cust_pay_batch.pm: no comment
+
+2006-08-27 13:09 jeff
+
+ * FS/FS/payby.pm: oops
+
+2006-08-27 12:33 ivan
+
+ * FS/FS/AccessRight.pm, httemplate/elements/menu.html: add back
+ invoice event reports
+
+2006-08-27 12:30 jeff
+
+ * httemplate/: edit/cust_bill_pay.cgi, edit/cust_credit.cgi,
+ edit/cust_credit_bill.cgi, misc/download-batch.cgi: correct bad
+ conflict resolution
+
+2006-08-26 16:15 jeff
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/Setup.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_event.pm, FS/FS/cust_bill_pay_batch.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pay_batch.pm,
+ FS/FS/part_bill_event.pm, FS/FS/payby.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/ClientAPI/Signup.pm,
+ FS/t/cust_bill_pay_batch.t, httemplate/browse/cust_pay_batch.cgi,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_refund.cgi,
+ httemplate/misc/bill.cgi, httemplate/misc/download-batch.cgi,
+ httemplate/misc/process/payment.cgi,
+ httemplate/search/cust_pay_batch.cgi,
+ httemplate/search/pay_batch.cgi,
+ httemplate/search/pay_batch.html,
+ httemplate/search/report_cust_pay_batch.html: batch refactor
+ continued
+
+2006-08-26 07:44 ivan
+
+ * FS/FS/Schema.pm: better error reporting for schema load failures
+ (w/DBIx::DBSchema 0.32+), hopefully...
+
+2006-08-26 07:39 ivan
+
+ * FS/FS/Schema.pm: better error reporting for schema load failures
+ (w/DBIx::DBSchema 0.32+), hopefully...
+
+2006-08-26 05:57 ivan
+
+ * httemplate/edit/: cust_bill_pay.cgi, cust_credit_bill.cgi: fix
+ javascript for auto-setting application amount
+
+2006-08-25 19:20 ivan
+
+ * httemplate/edit/cust_bill_pay.cgi: fix javascript for
+ auto-setting application amount
+
+2006-08-25 19:20 ivan
+
+ * htetc/handler.pl: i want max and min!
+
+2006-08-25 19:12 ivan
+
+ * FS/FS/: Record.pm: foiled by autovivification!
+
+2006-08-25 12:30 ivan
+
+ * FS/FS/Misc.pm, FS/FS/Record.pm,
+ httemplate/edit/cust_main/select-country.html,
+ httemplate/edit/cust_main/select-county.html,
+ httemplate/edit/cust_main/select-state.html: use GROUP BY instead
+ of DISTINCT ON in the state and country queries for better
+ cross-database compatibility, based on a preliminary patch from
+ Jason Thomas
+
+2006-08-23 18:37 ivan
+
+ * httemplate/edit/: cust_credit.cgi, cust_pay.cgi: oops,
+ "Auto-apply to invoices" dropdown disappeared
+
+2006-08-23 15:25 ivan
+
+ * Makefile, httemplate/index.html,
+ httemplate/browse/access_group.html,
+ httemplate/browse/access_user.html,
+ httemplate/browse/addr_block.cgi, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/cust_pay_batch.cgi,
+ httemplate/browse/inventory_class.html,
+ httemplate/browse/msgcat.cgi, httemplate/browse/nas.cgi,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/browse/part_export.cgi,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.html,
+ httemplate/browse/part_svc.cgi,
+ httemplate/browse/part_virtual_field.cgi,
+ httemplate/browse/payment_gateway.html,
+ httemplate/browse/pkg_class.html, httemplate/browse/rate.cgi,
+ httemplate/browse/router.cgi, httemplate/browse/svc_acct_pop.cgi,
+ httemplate/browse/elements/browse.html,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/docs/trouble.html, httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/access_group.html,
+ httemplate/edit/access_user.html, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_payment_gateway.html,
+ httemplate/edit/agent_type.cgi,
+ httemplate/edit/bulk-cust_svc.html,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/cust_refund.cgi,
+ httemplate/edit/inventory_class.html, httemplate/edit/msgcat.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_export.cgi, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_referral.html, httemplate/edit/part_svc.cgi,
+ httemplate/edit/part_virtual_field.cgi,
+ httemplate/edit/payment_gateway.html,
+ httemplate/edit/pkg_class.html,
+ httemplate/edit/prepay_credit.cgi, httemplate/edit/rate.cgi,
+ httemplate/edit/rate_region.cgi, httemplate/edit/reg_code.cgi,
+ httemplate/edit/router.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_broadband.cgi,
+ httemplate/edit/svc_domain.cgi, httemplate/edit/svc_external.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/edit/svc_phone.cgi,
+ httemplate/edit/svc_www.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/edit/cust_main/select-country.html,
+ httemplate/edit/cust_main/select-county.html,
+ httemplate/edit/cust_main/select-state.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/edit/process/access_group.html,
+ httemplate/edit/process/access_user.html,
+ httemplate/edit/process/agent.cgi,
+ httemplate/edit/process/agent_payment_gateway.html,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/bulk-cust_svc.cgi,
+ httemplate/edit/process/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_main_county-collapse.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/edit/process/cust_refund.cgi,
+ httemplate/edit/process/cust_svc.cgi,
+ httemplate/edit/process/domain_record.cgi,
+ httemplate/edit/process/generic.cgi,
+ httemplate/edit/process/inventory_class.html,
+ httemplate/edit/process/msgcat.cgi,
+ httemplate/edit/process/part_bill_event.cgi,
+ httemplate/edit/process/part_export.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/part_referral.html,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/edit/process/payment_gateway.html,
+ httemplate/edit/process/pkg_class.html,
+ httemplate/edit/process/prepay_credit.cgi,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/edit/process/rate.cgi,
+ httemplate/edit/process/rate_region.cgi,
+ httemplate/edit/process/reg_code.cgi,
+ httemplate/edit/process/router.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/svc_broadband.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/edit/process/svc_external.cgi,
+ httemplate/edit/process/svc_forward.cgi,
+ httemplate/edit/process/svc_phone.html,
+ httemplate/edit/process/svc_www.cgi,
+ httemplate/edit/process/addr_block/add.cgi,
+ httemplate/edit/process/addr_block/allocate.cgi,
+ httemplate/edit/process/addr_block/deallocate.cgi,
+ httemplate/edit/process/addr_block/split.cgi,
+ httemplate/edit/process/elements/process.html,
+ httemplate/edit/process/elements/svc_Common.html,
+ httemplate/elements/checkboxes-table-name.html,
+ httemplate/elements/checkboxes-table.html,
+ httemplate/elements/header-popup.html,
+ httemplate/elements/header.html,
+ httemplate/elements/jsrsServer.html,
+ httemplate/elements/menu.html, httemplate/elements/menubar.html,
+ httemplate/elements/pager.html,
+ httemplate/elements/phonenumber.html,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/progress-popup.html,
+ httemplate/elements/search-cust_main.html,
+ httemplate/elements/select-access_group.html,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-cust-fields.html,
+ httemplate/elements/select-cust_pkg-status.html,
+ httemplate/elements/select-month_year.html,
+ httemplate/elements/select-part_referral.html,
+ httemplate/elements/select-pkg_class.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/select-taxclass.html,
+ httemplate/elements/small_custview.html,
+ httemplate/elements/table-grid.html,
+ httemplate/elements/table.html,
+ httemplate/elements/tr-select-access_group.html,
+ httemplate/elements/tr-select-agent.html,
+ httemplate/elements/tr-select-cust-fields.html,
+ httemplate/elements/tr-select-cust_pkg-status.html,
+ httemplate/elements/tr-select-from_to.html,
+ httemplate/elements/tr-select-part_referral.html,
+ httemplate/elements/tr-select-pkg_class.html,
+ httemplate/elements/xmlhttp.html,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/money_time.cgi,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/graph/report_money_time.html,
+ httemplate/graph/elements/monthly.html,
+ httemplate/misc/batch-cust_pay.html, httemplate/misc/bill.cgi,
+ httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/cancel_pkg.cgi, httemplate/misc/catchall.cgi,
+ httemplate/misc/cdr-import.html, httemplate/misc/change_pkg.cgi,
+ httemplate/misc/counties.cgi,
+ httemplate/misc/cust_main-cancel.cgi,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/cust_main-import_charges.cgi,
+ httemplate/misc/delete-cust_credit.cgi,
+ httemplate/misc/delete-cust_pay.cgi,
+ httemplate/misc/delete-customer.cgi,
+ httemplate/misc/delete-domain_record.cgi,
+ httemplate/misc/delete-part_export.cgi,
+ httemplate/misc/download-batch.cgi, httemplate/misc/dump.cgi,
+ httemplate/misc/email-invoice.cgi,
+ httemplate/misc/email_invoice_events.cgi,
+ httemplate/misc/email_invoices.cgi,
+ httemplate/misc/expire_pkg.cgi, httemplate/misc/fax-invoice.cgi,
+ httemplate/misc/fax_invoice_events.cgi,
+ httemplate/misc/fax_invoices.cgi,
+ httemplate/misc/inventory_item-import.html,
+ httemplate/misc/link.cgi, httemplate/misc/meta-import.cgi,
+ httemplate/misc/payment.cgi, httemplate/misc/print-invoice.cgi,
+ httemplate/misc/print_invoice_events.cgi,
+ httemplate/misc/print_invoices.cgi, httemplate/misc/queue.cgi,
+ httemplate/misc/states.cgi, httemplate/misc/susp_pkg.cgi,
+ httemplate/misc/unapply-cust_credit.cgi,
+ httemplate/misc/unapply-cust_pay.cgi,
+ httemplate/misc/unprovision.cgi, httemplate/misc/unsusp_pkg.cgi,
+ httemplate/misc/unvoid-cust_pay_void.cgi,
+ httemplate/misc/upload-batch.cgi,
+ httemplate/misc/void-cust_pay.cgi, httemplate/misc/whois.cgi,
+ httemplate/misc/xmlhttp-cust_main-search.cgi,
+ httemplate/misc/xmlrpc.cgi,
+ httemplate/misc/process/batch-cust_pay.cgi,
+ httemplate/misc/process/catchall.cgi,
+ httemplate/misc/process/cdr-import.html,
+ httemplate/misc/process/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import_charges.cgi,
+ httemplate/misc/process/delete-customer.cgi,
+ httemplate/misc/process/expire_pkg.cgi,
+ httemplate/misc/process/inventory_item-import.html,
+ httemplate/misc/process/link.cgi,
+ httemplate/misc/process/meta-import.cgi,
+ httemplate/misc/process/payment.cgi, httemplate/search/cdr.html,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_bill_event.html,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_main-otaker.cgi,
+ httemplate/search/cust_main-zip.html,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pay.cgi,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/cust_tax_exempt_pkg.cgi,
+ httemplate/search/inventory_item.html,
+ httemplate/search/prepay_credit.html,
+ httemplate/search/queue.html, httemplate/search/reg_code.html,
+ httemplate/search/report_cdr.html,
+ httemplate/search/report_cust_bill.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_main-zip.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/search/report_prepaid_income.cgi,
+ httemplate/search/report_prepaid_income.html,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/report_receivables.html,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html, httemplate/search/sql.html,
+ httemplate/search/sqlradius.cgi,
+ httemplate/search/sqlradius.html,
+ httemplate/search/svc_Smart.html, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_broadband.cgi,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_external.cgi,
+ httemplate/search/svc_forward.cgi,
+ httemplate/search/svc_phone.cgi, httemplate/search/svc_www.cgi,
+ httemplate/search/elements/search.html,
+ httemplate/view/cust_bill-logo.cgi,
+ httemplate/view/cust_bill-pdf.cgi,
+ httemplate/view/cust_bill-ps.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_broadband.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_external.cgi,
+ httemplate/view/svc_forward.cgi, httemplate/view/svc_phone.cgi,
+ httemplate/view/svc_www.cgi,
+ httemplate/view/cust_main/billing.html,
+ httemplate/view/cust_main/contacts.html,
+ httemplate/view/cust_main/misc.html,
+ httemplate/view/cust_main/order_pkg.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/quick-charge.html,
+ httemplate/view/cust_main/tickets.html,
+ httemplate/view/elements/svc_Common.html: Will things ever be the
+ same again? It's the final masonize
+
+2006-08-23 14:53 ivan
+
+ * httemplate/misc/batch-cust_pay.html: remove extraneous "
+
+2006-08-23 05:13 ivan
+
+ * FS/FS/Schema.pm: removing already commented-out code
+
+2006-08-23 05:06 ivan
+
+ * Makefile, FS/FS/CGI.pm: don't use FREESIDE_URL for this, it
+ didn't work out...
+
+2006-08-22 05:23 ivan
+
+ * httemplate/docs/: index.html, upgrade-1.4.2.html, upgrade10.html,
+ upgrade9.html: get rid of old upgrade instructions
+
+2006-08-22 05:20 ivan
+
+ * README.1.7.0: wiki!
+
+2006-08-22 05:09 ivan
+
+ * README.1.5.0pre6, README.1.5.7, README.1.5.7.lastbit,
+ README.1.5.8: removing old upgrade instructions... can always
+ get the out of the Attic
+
+2006-08-21 16:01 ivan
+
+ * FS/: MANIFEST, FS/Schema.pm, FS/cust_bill.pm,
+ FS/cust_bill_ApplicationCommon.pm, FS/cust_bill_pay.pm,
+ FS/cust_bill_pay_pkg.pm, FS/cust_bill_pkg.pm,
+ FS/cust_credit_bill.pm, FS/cust_credit_bill_pkg.pm,
+ t/cust_bill_ApplicationCommon.t, t/cust_bill_pay_pkg.t,
+ t/cust_credit_bill_pkg.t: add cust_bill_pay_pkg and
+ cust_credit_bill_pkg - applying credits and payments against
+ specific line items
+
+2006-08-21 10:45 ivan
+
+ * FS/FS/: cust_main.pm: search for existing advertising sources
+ before adding a new one
+
+2006-08-21 02:46 ivan
+
+ * httemplate/docs/: install-rt.html, install.html, index.html:
+ we're off to see the wiki, the wonderful wiki of oz
+
+2006-08-18 05:18 ivan
+
+ * FS/FS/cust_main.pm: and a slight fix to the CSV import
+
+2006-08-18 04:56 ivan
+
+ * FS/FS/cust_main.pm: pass through the explicitly specified pkeys
+
+2006-08-18 04:52 ivan
+
+ * FS/FS/cust_main.pm: alas, now try with recursion
+
+2006-08-18 04:40 ivan
+
+ * FS/FS/cust_main.pm: fix the explicitly specified primary keys
+
+2006-08-18 04:36 ivan
+
+ * FS/FS/cust_main.pm: allow explicitly specified primary keys (to
+ get around big 8.1 Pg changes wrt reverse engineering
+
+2006-08-18 03:34 ivan
+
+ * FS/FS/cust_main.pm: suggestion to run dbdef-create here, yes...
+
+2006-08-18 03:31 ivan
+
+ * FS/FS/cust_main.pm: want ALL of cust_main-skeleton tables config,
+ not just the first line
+
+2006-08-18 03:27 ivan
+
+ * FS/FS/cust_main.pm: that was it, the sql had to be fixed...
+
+2006-08-18 03:26 ivan
+
+ * FS/FS/cust_main.pm: W T F
+
+2006-08-18 03:18 ivan
+
+ * FS/FS/cust_main.pm: even more skeleton debugging, ugh
+
+2006-08-18 03:10 ivan
+
+ * FS/FS/cust_main.pm: what's going on with the parameters for
+ skeleton inserts??
+
+2006-08-18 03:00 ivan
+
+ * FS/FS/cust_main.pm: skeleton typo
+
+2006-08-18 02:58 ivan
+
+ * FS/FS/cust_main.pm: oops, want CHILD table for skeleton inserts,
+ not parent
+
+2006-08-18 02:50 ivan
+
+ * FS/FS/cust_main.pm: add debugging to _copy_skel to get some idea
+ what's going on
+
+2006-08-18 01:33 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: first try at skeleton feature for
+ mg
+
+2006-08-16 01:19 ivan
+
+ * FS/bin/freeside-adduser: get rid of too-verbose debugging
+
+2006-08-15 07:20 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import.cgi: add a new, extended
+ CSV import format
+
+2006-08-14 06:28 ivan
+
+ * FS/FS/cust_main.pm: pass email, phone and ip adderss to B:OP when
+ doing refunds, hopefully this will fix OpenECHO refunds
+
+2006-08-14 05:24 ivan
+
+ * Changes.1.7.0: there's more, but this will have to do
+
+2006-08-14 05:13 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/cust_main.cgi,
+ httemplate/elements/header.html,
+ httemplate/elements/search-cust_main.html: sprinkle some magic
+ ajax fairy dust on referring customer SELEKTAH. rewind! make
+ smart search smarter, re-layout the top search bars and add an
+ invoice one
+
+2006-08-14 01:38 ivan
+
+ * FS/FS/access_user.pm: bugfix for agentless access users,
+ triggered by part_referral (advertising source) agent
+ virtualization
+
+2006-08-13 03:25 ivan
+
+ * FS/FS/Conf.pm, FS/FS/TicketSystem/RT_External.pm,
+ httemplate/edit/cust_pay.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/billing.html,
+ httemplate/view/cust_main/misc.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/tickets.html: customer view work:
+
+ DONE 1. add status and balance to top
+
+ DONE 2. add some sort of oldest date thing so the history
+ doesn't get too big (# years and a link to "show older")
+
+ 3. make the rest of the action links into js popups? maybe
+ later,
+ weird IENess when closing em
+ DONE (finished) - so revert out or finish/commit the Enter
+ check payment one - Process page can wait until another day..
+ it should be more of an *action*
+
+ DONE 4. Ticket list config knobs for wtxs (grid it too)
+
+ DONE 5. grid the package list
+
+2006-08-12 04:01 ivan
+
+ * httemplate/edit/cust_credit.cgi: s/Post/Enter/;
+
+2006-08-12 03:47 ivan
+
+ * FS/FS/Conf.pm, FS/FS/AccessRight.pm,
+ httemplate/view/cust_main/payment_history.html: fix acl rewrite
+ causing problems: void now shows up properly, deprecate all the
+ redundant config values
+
+2006-08-11 23:45 ivan
+
+ * FS/FS/cust_pkg.pm: don't adjust next bill date on unsuspension!
+ causes undesirable effects with prorate/subscription packages and
+ undesirably rewards customers for non-payment, closes: Bug#1325
+
+2006-08-11 01:02 ivan
+
+ * FS/FS/access_user.pm, FS/FS/part_referral.pm,
+ httemplate/browse/part_referral.html,
+ httemplate/edit/cust_main.cgi,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-part_referral.html,
+ httemplate/elements/tr-select-agent.html,
+ httemplate/elements/tr-select-part_referral.html: virtualize
+ referrals on customer addition
+
+2006-08-10 15:18 ivan
+
+ * httemplate/config/config.cgi: bugfix for selects that don't have
+ select_enum
+
+2006-08-10 06:50 ivan
+
+ * README.1.7.0, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/part_referral.pm, httemplate/browse/part_referral.html,
+ httemplate/view/cust_main/misc.html: add cust_main.agent_custid
+ (at least to schema and customer view, no manual editing yet)
+
+2006-08-10 05:01 ivan
+
+ * httemplate/browse/part_referral.html: bold the total footer
+
+2006-08-10 04:55 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Record.pm, FS/FS/Schema.pm,
+ FS/FS/access_user.pm, FS/FS/part_referral.pm,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_referral.html,
+ httemplate/edit/part_referral.cgi,
+ httemplate/edit/part_referral.html,
+ httemplate/edit/process/part_referral.cgi,
+ httemplate/edit/process/part_referral.html,
+ httemplate/elements/menu.html: agent-virtualize advertising
+ sources
+
+2006-08-09 20:10 ivan
+
+ * FS/FS/Schema.pm: don't set the default to NULL the string!
+ besides, that's already the default value of any nullable column,
+ which @date_type is...
+
+2006-08-09 19:27 ivan
+
+ * FS/FS/part_pkg.pm: better debugging for missing recur_fee so its
+ easier to check the db
+
+2006-08-09 14:46 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/myaccount_menu.html: take
+ "coming soon" options off the menu, its been Soon for too long -
+ they'll get here when they do
+
+2006-08-09 03:47 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm, FS/FS/svc_acct.pm,
+ fs_selfservice/FS-SelfService/cgi/change_password.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/process_change_password.html,
+ fs_selfservice/FS-SelfService/cgi/provision_list.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: self-service
+ interface: add proper password changer and prevent "Setup my
+ services" provisioner from showing broken links for services not
+ handled yet
+
+2006-08-09 00:46 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi: and the
+ days go by...
+
+2006-08-09 00:03 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi: this is not
+ my beautiful magic template! (water flowing underground)
+
+2006-08-08 23:43 jeff
+
+ * README.1.7.0, FS/FS/Schema.pm, FS/FS/Setup.pm,
+ FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/cust_pay_batch.pm,
+ FS/FS/part_bill_event.pm, FS/FS/pay_batch.pm, FS/FS/payby.pm,
+ httemplate/browse/cust_pay_batch.cgi,
+ httemplate/browse/pay_batch.cgi, httemplate/docs/schema.html,
+ httemplate/misc/download-batch.cgi: batch refactor
+
+2006-08-08 23:34 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html: self-service
+ interface: move from text to html invoices
+
+2006-08-08 20:45 ivan
+
+ * fs_selfservice/FS-SelfService/Makefile.PL: please bleeding-edge
+ debian perl, would you put it in /usr/local/sbin? thanks.
+
+2006-08-06 19:44 ivan
+
+ * FS/FS/CurrentUser.pm, FS/FS/Schema.pm, FS/FS/access_user.pm,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/access_user.html,
+ httemplate/edit/access_user.html,
+ httemplate/edit/elements/edit.html: add internal user disable-ing
+
+2006-08-06 19:19 ivan
+
+ * httemplate/search/elements/search.html: get rid of the extra
+ border in nested tables
+
+2006-08-06 16:39 ivan
+
+ * FS/: FS/access_user.pm, bin/freeside-adduser: slightly better
+ bootstrapping for htpasswd kludge... hopefully that will go away
+ in 1.7.1
+
+2006-08-06 14:37 ivan
+
+ * FS/FS/svc_Common.pm: make sure default RADIUS groups don't
+ override ones for existing records
+
+2006-08-06 13:23 ivan
+
+ * httemplate/edit/svc_acct.cgi: silly closing }
+
+2006-08-06 13:21 ivan
+
+ * FS/FS/svc_Common.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/svc_acct.cgi: yow. fix up group handling
+
+2006-08-06 13:07 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/edit/process/cust_main.cgi: this
+ should fix the barfing about default radius groups on the new
+ customer screen...
+
+2006-08-06 02:14 ivan
+
+ * FS/FS/svc_acct.pm: this just needs to be a hashref and we should
+ be all set with radius groups then
+
+2006-08-06 02:10 ivan
+
+ * FS/FS/svc_Common.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/svc_acct.cgi: this should process default
+ usergroup as well as fixed now
+
+2006-08-06 01:43 ivan
+
+ * FS/FS/svc_acct.pm: add debugging to track down RADIUS group
+ problem
+
+2006-08-06 01:10 ivan
+
+ * FS/FS/: svc_acct.pm: add confession here to diagnose etxrn's
+ problem better
+
+2006-07-27 17:33 ivan
+
+ * FS/FS/access_user.pm: htpasswd workaround no longer necessary -
+ closes: #1351
+
+2006-07-27 01:08 ivan
+
+ * FS/t/svc_phone.t: svc_phone.t
+
+2006-07-25 21:18 ivan
+
+ * FS/FS/UID.pm: ugh, fixup bootstrapping
+
+2006-07-25 01:39 ivan
+
+ * httemplate/search/cust_main.cgi: oops, extra else
+
+2006-07-25 01:33 ivan
+
+ * FS/FS/agent.pm, httemplate/browse/agent.cgi,
+ httemplate/elements/table-grid.html,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pkg.cgi:
+ this should finish adding the "inactive" status, i think?
+
+2006-07-25 00:27 ivan
+
+ * bin/rt-update-links: quick script to convert rt links from one
+ database name to another
+
+2006-07-24 15:40 ivan
+
+ * FS/FS/cust_main.pm: fix up smart searching to make the quick
+ payment entry behave better
+
+2006-07-23 07:23 ivan
+
+ * httemplate/search/report_receivables.cgi: sql num_pkgs
+ conflicting with method...
+
+2006-07-23 07:21 ivan
+
+ * httemplate/search/report_receivables.cgi: ugh, really fix the
+ statuses here
+
+2006-07-23 07:20 ivan
+
+ * httemplate/search/report_receivables.cgi: hopefully fix the
+ statuses here
+
+2006-07-23 07:17 ivan
+
+ * httemplate/search/report_receivables.cgi: i should REALLY go to
+ sleep
+
+2006-07-23 07:16 ivan
+
+ * httemplate/search/report_receivables.cgi: i should go to sleep
+
+2006-07-23 07:07 ivan
+
+ * FS/FS/cust_main.pm, httemplate/search/report_receivables.cgi:
+ more work towards adding an "inactive" status - add it to the A/R
+ report
+
+2006-07-15 17:55 ivan
+
+ * SCHEMA_CHANGE, FS/FS/Conf.pm, FS/FS/agent.pm, FS/FS/cust_bill.pm,
+ FS/FS/inventory_item.pm,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd,
+ httemplate/autohandler, httemplate/browse/cust_pay_batch.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/elements/overlibmws.js,
+ httemplate/elements/overlibmws_draggable.js,
+ httemplate/elements/phonenumber.html,
+ httemplate/search/cust_bill.html, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_forward.cgi, httemplate/search/svc_www.cgi,
+ httemplate/view/svc_external.cgi: odds and ends
+
+2006-07-13 21:20 ivan
+
+ * FS/FS/cust_pkg.pm: don't send cancellation emails on package
+ changes
+
+2006-07-13 20:27 ivan
+
+ * FS/FS/svc_Common.pm: this should fix radius group editing and the
+ "Reference found where even-sized list expected at
+ /usr/local/share/perl/5.8.4/FS/svc_Common.pm line 473" error
+
+2006-07-13 20:17 ivan
+
+ * FS/FS/CurrentUser.pm: this should fix the bootstrapping
+
+2006-07-13 18:42 ivan
+
+ * FS/FS/: CurrentUser.pm, UID.pm: add the small
+ FS::CurrentUser::BootstrapUser class for... surprise...
+ bootstrapping
+
+2006-07-13 17:42 ivan
+
+ * FS/FS/Setup.pm: fix acl bootstrapping
+
+2006-07-13 17:32 ivan
+
+ * FS/: FS/UID.pm, bin/freeside-setup: should be able to
+ freeside-setup without a username now
+
+2006-07-13 16:45 ivan
+
+ * FS/FS/UID.pm: mapsecrets file shouldn't be necessary at all...
+
+2006-07-12 20:54 ivan
+
+ * FS/FS/Report/Table/Monthly.pm,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_receivables.cgi: fix multiple-agent
+ virtualization properly for these reports
+
+2006-07-12 16:33 ivan
+
+ * FS/: bin/freeside-setup, FS/Setup.pm: add acl bootstrapping -
+ should be installable again now
+
+2006-07-11 17:20 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/Record.pm, FS/FS/Schema.pm,
+ FS/FS/cdr.pm, FS/FS/cust_svc.pm, FS/FS/h_svc_phone.pm,
+ FS/FS/part_svc.pm, FS/FS/svc_phone.pm,
+ httemplate/elements/menu.html, httemplate/misc/cdr-import.html,
+ FS/MANIFEST, htetc/handler.pl, httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_phone.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/elements/svc_Common.html,
+ httemplate/edit/process/svc_phone.html,
+ httemplate/edit/process/elements/process.html,
+ httemplate/edit/process/elements/svc_Common.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html,
+ httemplate/search/svc_phone.cgi, httemplate/view/svc_phone.cgi,
+ httemplate/view/elements/svc_Common.html,
+ FS/FS/part_pkg/voip_cdr.pm: svc_phone service and CDR billing
+ from imported CDRs
+
+2006-07-05 07:55 ivan
+
+ * FS/FS/cust_main.pm: patch to fix fuzzy searching from Ryan Gunn
+
+2006-07-05 04:23 ivan
+
+ * README.1.7.0: hint about permissions and users
+
+2006-07-04 05:01 ivan
+
+ * FS/: bin/freeside-addgroup, bin/freeside-adduser, MANIFEST:
+ adding freeside-addgroup
+
+2006-07-01 04:26 ivan
+
+ * FS/FS/Record.pm: another Pg 8.1 fix? noticed by Damon Vincent
+
+2006-06-30 07:30 ivan
+
+ * FS/bin/freeside-adduser: checkin freeside-adduser with the -g
+ flag! sheesh
+
+2006-06-29 08:45 ivan
+
+ * FS/FS/part_export/shellcommands.pm: small patch to set
+ $new_finger from Tim Yardley
+
+2006-06-29 08:19 ivan
+
+ * httemplate/edit/part_bill_event.cgi: percentage late fees too
+
+2006-06-29 06:47 ivan
+
+ * FS/FS/inventory_class.pm, FS/FS/svc_Common.pm,
+ httemplate/browse/inventory_class.html,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_broadband.cgi,
+ httemplate/edit/svc_domain.cgi, httemplate/edit/svc_external.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/edit/svc_www.cgi,
+ httemplate/search/inventory_item.html: finish at least the
+ automatic provisioning part
+
+2006-06-27 07:19 ivan
+
+ * httemplate/docs/index.html: going to wikiland
+
+2006-06-24 09:41 ivan
+
+ * FS/FS/part_svc.pm, FS/FS/part_svc_column.pm,
+ httemplate/edit/part_svc.cgi, httemplate/browse/part_svc.cgi,
+ httemplate/elements/select-table.html,
+ httemplate/elements/table-grid.html: Add the ability to link
+ customer service definition fields to inventory classes, with
+ an "automatic/manual" flag. Add the ability for the web
+ interface to maintain these links. Start prettying up the
+ service def. edit in preparation for Bigger Changes.
+
+2006-06-21 09:26 ivan
+
+ * httemplate/browse/part_pkg.cgi: fix show/hide disabled link
+ interaction w/pager
+
+2006-06-21 06:00 ivan
+
+ * httemplate/elements/iframecontentmws.js: oops, don't want this
+ debugging in here
+
+2006-06-21 06:00 ivan
+
+ * httemplate/view/cust_main/payment_history.html:
+ http://www.macridesweb.com/oltest/ONCLICK.html !!!!!
+
+2006-06-21 05:58 ivan
+
+ * httemplate/view/cust_main.cgi: need the OLiframecontent sub
+
+2006-06-21 05:57 ivan
+
+ * httemplate/elements/header-popup.html: adding a header element
+ for popups to replace CGI::header
+
+2006-06-21 05:56 ivan
+
+ * httemplate/elements/iframecontentmws.js: add overlib iframe
+ function
+
+2006-06-21 01:42 ivan
+
+ * FS/FS/agent_type.pm, httemplate/browse/agent_type.cgi: speed up
+ the agent type report when there are lots of package definitions
+
+2006-06-19 06:09 ivan
+
+ * httemplate/view/cust_main/packages.html: fix ACL name for service
+ provisioning and prevent disabled service provisioning from
+ messing up table formatting
+
+2006-06-19 05:47 ivan
+
+ * bin/mapsecrets2access_user: better error checking for this
+ thrown-together bootstrapping script
+
+2006-06-19 05:22 ivan
+
+ * bin/mapsecrets2access_user: ACL bootstrapping
+
+2006-06-19 05:15 ivan
+
+ * FS/: FS/CurrentUser.pm, bin/freeside-upgrade: ACL bootstrapping
+
+2006-06-19 05:09 ivan
+
+ * FS/t/ConfDefaults.t: forgot to commit this test
+
+2006-06-19 04:57 ivan
+
+ * Changes.1.7.0, README.1.7.0, bin/mapsecrets2access_user: ACL
+ bootstrapping/upgrade
+
+2006-06-19 04:25 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/access_user.pm, FS/FS/cust_main.pm,
+ httemplate/elements/menu.html, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html: ACLs, take three
+ or four or something
+
+2006-06-19 01:05 ivan
+
+ * FS/FS/Conf.pm, FS/FS/ConfDefaults.pm, FS/FS/cust_main_Mixin.pm,
+ FS/FS/cust_pkg.pm, FS/FS/UI/Web.pm, httemplate/config/config.cgi,
+ httemplate/elements/menu.html,
+ httemplate/elements/select-cust-fields.html,
+ httemplate/elements/select-cust_pkg-status.html,
+ httemplate/elements/tr-select-cust-fields.html,
+ httemplate/elements/tr-select-cust_pkg-status.html,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/search/cust_pkg_report.cgi,
+ httemplate/search/report_cust_pkg.html,
+ httemplate/view/cust_main/contacts.html: add ability to select
+ specific package defs. and package status to package report for
+ qis
+
+2006-06-18 23:03 ivan
+
+ * httemplate/search/cust_main.cgi: fix up the alternating colors on
+ the customer search results
+
+2006-06-18 19:33 ivan
+
+ * FS/MANIFEST, FS/FS/CurrentUser.pm, FS/FS/Record.pm,
+ FS/FS/Schema.pm, FS/FS/UID.pm, FS/FS/access_user.pm,
+ FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-agent.html,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/search/svc_acct.cgi, httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_forward.cgi: agent virtualization, take one
+ (stuff from "inactive" changeset snuck into cust_main.pm and the
+ package reporting changeset in search/cust_pkg.cgi here too)
+
+2006-06-18 05:56 ivan
+
+ * httemplate/edit/elements/edit.html: well, it isn't broken...
+
+2006-06-18 05:54 ivan
+
+ * FS/FS/AccessRight.pm, FS/FS/access_group.pm,
+ FS/FS/access_groupagent.pm, FS/FS/m2name_Common.pm,
+ FS/FS/part_pkg.pm, httemplate/edit/access_group.html,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/elements/edit.html,
+ httemplate/edit/process/access_group.html,
+ httemplate/elements/checkboxes-table-name.html,
+ httemplate/elements/checkboxes-table.html, FS/MANIFEST,
+ htetc/handler.pl, httemplate/browse/access_group.html,
+ httemplate/browse/access_user.html,
+ httemplate/edit/process/elements/process.html: ACLs: finish group
+ edit (agents + rights) & browse
+
+2006-06-15 18:23 jeff
+
+ * FS/FS/cust_bill.pm, FS/FS/pay_batch.pm,
+ httemplate/misc/download-batch.cgi: value issues and many bits
+ remain
+
+2006-06-15 17:47 ivan
+
+ * httemplate/misc/download-batch.cgi: and fix the name for TD
+ Canada Trust. and that's it for now. really.
+
+2006-06-15 17:33 ivan
+
+ * httemplate/misc/download-batch.cgi: oops i'm gonna do that too,
+ now that the batch format file is not the same as the batch
+ params files
+
+2006-06-15 17:27 ivan
+
+ * httemplate/misc/download-batch.cgi: s/printf/sprintf/ and make
+ the config a little less strange
+
+2006-06-08 03:32 ivan
+
+ * httemplate/elements/menu.html: fix link to prepaid card setup
+
+2006-06-06 03:30 ivan
+
+ * FS/FS/cdr.pm: fix unmatched =back somehow futzing things up with
+ automated install. wtf?!
+
+2006-06-02 06:20 ivan
+
+ * httemplate/elements/header.html,
+ httemplate/search/svc_Smart.html, rt/FREESIDE_MODIFIED: add a
+ service search
+
+2006-05-24 03:22 ivan
+
+ * FS/MANIFEST: removing duplicate entries
+
+2006-05-23 08:54 ivan
+
+ * README.1.7.0: adding batch upgrade instructions to 1.7.0
+ instructions too
+
+2006-05-22 11:27 ivan
+
+ * FS/FS/cust_main.pm: better error message for banned cards
+
+2006-05-22 11:05 ivan
+
+ * Changes.1.5.8: justification
+
+2006-05-21 19:06 ivan
+
+ * Makefile: docs are going in the wiki Real Soon Now anyway
+
+2006-05-21 19:04 ivan
+
+ * Changes.1.5.8: 1.5.8!
+
+2006-05-21 17:50 ivan
+
+ * Makefile: 1.7.0? why not?
+
+2006-05-21 17:44 ivan
+
+ * httemplate/: index.html, elements/menu.html: 1.7.0? why not!
+
+2006-05-21 11:40 ivan
+
+ * FS/FS/part_export/communigate_pro_singledomain.pm: tyop
+
+2006-05-20 13:06 jeff
+
+ * README.1.5.7.lastbit, README.1.5.8, FS/FS.pm, FS/MANIFEST,
+ FS/FS/Schema.pm, FS/FS/cust_bill.pm, FS/FS/cust_pay_batch.pm,
+ FS/FS/pay_batch.pm, FS/t/pay_batch.t, htetc/handler.pl,
+ httemplate/browse/cust_pay_batch.cgi,
+ httemplate/docs/schema.html, httemplate/docs/upgrade10.html,
+ httemplate/misc/download-batch.cgi: first stab at BoM download
+
+2006-05-15 06:57 ivan
+
+ * httemplate/: index.html, elements/freeside.css,
+ elements/header.html, elements/menu.html: move most of the crap
+ on the "main menu" to the sidebar
+
+2006-05-15 04:05 ivan
+
+ * httemplate/elements/freeside.css,
+ httemplate/elements/header.html, httemplate/elements/xmenu.css,
+ httemplate/search/cust_main.cgi, rt/FREESIDE_MODIFIED: more ACL
+ and re-skinning work, now with RT!
+
+2006-05-14 09:47 ivan
+
+ * CREDITS, Changes.1.7.0, htetc/handler.pl, httemplate/autohandler,
+ httemplate/index.html, httemplate/browse/access_group.html,
+ httemplate/browse/access_user.html,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/msgcat.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/edit/access_group.html,
+ httemplate/edit/access_user.html, httemplate/edit/agent_type.cgi,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_referral.cgi,
+ httemplate/edit/part_virtual_field.cgi,
+ httemplate/edit/svc_domain.cgi,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/access_group.html,
+ httemplate/edit/process/access_user.html,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/elements/process.html,
+ httemplate/elements/checkboxes-table.html,
+ httemplate/elements/cssexpr.js, httemplate/elements/footer.html,
+ httemplate/elements/header.html,
+ httemplate/elements/menubar.html,
+ httemplate/elements/select-access_group.html,
+ httemplate/elements/tr-select-access_group.html,
+ httemplate/elements/xmenu.css, httemplate/elements/xmenu.js,
+ httemplate/misc/batch-cust_pay.html, httemplate/misc/payment.cgi,
+ httemplate/search/cust_bill.cgi,
+ httemplate/search/cust_main-otaker.cgi,
+ httemplate/search/cust_main-payinfo.html,
+ httemplate/search/cust_main-quickpay.html,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pay.html,
+ httemplate/search/cust_pkg_report.cgi,
+ httemplate/search/report_cust_bill.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_prepaid_income.html,
+ httemplate/search/report_tax.html,
+ httemplate/search/sqlradius.html,
+ httemplate/search/svc_acct.html,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_domain.html,
+ httemplate/search/svc_external.cgi, FS/MANIFEST,
+ FS/FS/AccessRight.pm, FS/FS/CGI.pm, FS/FS/Schema.pm,
+ FS/FS/access_group.pm, FS/FS/access_groupagent.pm,
+ FS/FS/access_right.pm, FS/FS/access_user.pm,
+ FS/FS/access_user_pref.pm, FS/FS/access_usergroup.pm,
+ FS/FS/agent_type.pm, FS/FS/cust_bill.pm, FS/FS/m2m_Common.pm,
+ FS/FS/payby.pm, FS/FS/svc_domain.pm, FS/FS/UI/Web.pm,
+ FS/FS/part_pkg/billoneday.pm, FS/bin/freeside-addoutsourceuser,
+ FS/t/AccessRight.t, FS/t/access_group.t,
+ FS/t/access_groupagent.t, FS/t/access_right.t,
+ FS/t/access_user.t, FS/t/access_user_pref.t,
+ FS/t/access_usergroup.t, httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html: first part of ACL
+ and re-skinning work and some other small stuff
+
+2006-05-13 11:34 ivan
+
+ * httemplate/images/background-cheat.png: yay for cheating
+
+2006-05-13 08:31 ivan
+
+ * httemplate/images/: 32clear.gif, arrow.down.png,
+ arrow.right.black.png, arrow.right.png, black-gradient.png,
+ black-gray-corner.png, black-gray-gradient.png,
+ black-gray-side.png, black-gray-top.png: adding new images
+
+2006-05-12 06:57 ivan
+
+ * httemplate/search/report_receivables.cgi: Pg 8.1 fix was
+ incorrect and broke things, this should actually work
+
+2006-05-08 04:48 ivan
+
+ * README.1.5.8, README.1.7.0: suggest "make clean" on upgrade -
+ something is not quite right with perl Makefile hoohaw
+
+2006-05-08 04:28 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm,
+ httemplate/search/report_tax.cgi: add config switch to base tax
+ off shipping address if present
+
+2006-05-08 03:01 ivan
+
+ * FS/FS/Report/Table/Monthly.pm, httemplate/index.html,
+ httemplate/graph/cust_bill_pkg-graph.cgi,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/elements/monthly.html,
+ httemplate/search/cust_bill_pkg.cgi: sales report per agent and
+ package class looks good
+
+2006-05-07 13:27 ivan
+
+ * README.1.7.0, htetc/handler.pl, httemplate/docs/upgrade10.html,
+ httemplate/elements/select-month_year.html,
+ httemplate/elements/select-pkg_class.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-from_to.html,
+ httemplate/elements/tr-select-pkg_class.html,
+ httemplate/graph/cust_bill_pkg-graph.cgi,
+ httemplate/graph/cust_bill_pkg.cgi,
+ httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi,
+ httemplate/graph/report_cust_bill_pkg.html,
+ httemplate/graph/report_money_time.html,
+ httemplate/graph/elements/monthly.html: first pass at sales
+ reports per agent and package class
+
+2006-05-03 02:47 ivan
+
+ * httemplate/search/report_receivables.cgi: pg 8.1 fix from Chris
+ Cappuccio
+
+2006-05-02 08:23 ivan
+
+ * FS/: MANIFEST, FS/Pony.pm: yours!
+
+2006-05-02 08:03 ivan
+
+ * httemplate/: index.html, search/report_receivables.html: add an
+ agent pre-selection page to receivables report
+
+2006-05-02 06:29 ivan
+
+ * SCHEMA_CHANGE: need to install the new Schema.pm before you can
+ autogenerate off it
+
+2006-05-02 04:59 ivan
+
+ * httemplate/index.html, FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi,
+ httemplate/graph/report_money_time.html: add a "pre-report" page
+ to this report/graph as requested by lewis/wtxs, also add 12mo
+ total option
+
+2006-05-01 06:09 ivan
+
+ * FS/FS/part_pkg/prorate.pm: small fix to make prorate behave on
+ the 1st as it did before
+
+2006-05-01 05:38 ivan
+
+ * FS/FS/part_pkg/: prorate.pm, subscription.pm: fix some very
+ annoying clucks (warnings with backtraces) when cutoff day isn't
+ found in old packages
+
+2006-05-01 04:45 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: column reference "disabled" is
+ ambiguous
+
+2006-05-01 04:43 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: fix bug with duplicate tickets
+ showing up on customer view listing when the custom priority
+ field was edited
+
+2006-04-26 06:16 ivan
+
+ * Makefile: apache reload doesn't work when server isn't running
+ already
+
+2006-04-21 17:58 ivan
+
+ * httemplate/browse/generic.cgi,
+ httemplate/browse/inventory_class.html,
+ httemplate/browse/part_pkg.cgi, httemplate/browse/pkg_class.html,
+ httemplate/browse/rate.cgi,
+ httemplate/browse/elements/browse.html, htetc/handler.pl,
+ httemplate/index.html, httemplate/edit/inventory_class.html,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/pkg_class.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/inventory_class.html,
+ httemplate/edit/process/pkg_class.html,
+ httemplate/edit/process/elements/process.html,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/select-pkg_class.html,
+ httemplate/elements/select-table.html,
+ httemplate/elements/tr-select-pkg_class.html,
+ httemplate/search/cust_pkg.cgi,
+ httemplate/search/inventory_class.html, FS/FS/part_pkg.pm,
+ httemplate/search/elements/search.html: start of package class
+ web UI (add/edit package classes, package class selection in
+ package def edit)
+
+2006-04-21 07:21 ivan
+
+ * FS/FS/part_pkg/incomplete/billoneday.pm: throw this in here for
+ now
+
+2006-04-21 07:20 ivan
+
+ * FS/FS/part_pkg/: flat.pm, prorate.pm, subscription.pm: fix some
+ indentation and the default cutoff day
+
+2006-04-21 05:45 ivan
+
+ * httemplate/: index.html, browse/queue.cgi, misc/queue.cgi,
+ search/queue.html: s(browse/queue.cgi)(search/queue.html)
+
+2006-04-18 23:37 ivan
+
+ * FS/FS/cust_main.pm: DOH! perlvar: not counting patterns matched
+ in nested blocks that have been exited already.
+
+2006-04-18 12:33 ivan
+
+ * httemplate/docs/install.html: add JSON to initial install
+ instructions
+
+2006-04-15 06:32 ivan
+
+ * httemplate/graph/money_time.cgi: REALLY correct the period for
+ the total column this time
+
+2006-04-15 06:28 ivan
+
+ * httemplate/graph/money_time.cgi: correct period & use a yellow
+ color for the total column
+
+2006-04-15 06:25 ivan
+
+ * httemplate/graph/money_time.cgi: format & link the total column
+
+2006-04-14 17:21 ivan
+
+ * httemplate/graph/money_time.cgi: and </TD> the total column
+
+2006-04-14 17:16 ivan
+
+ * httemplate/graph/money_time.cgi: add a total column
+
+2006-04-14 04:55 ivan
+
+ * FS/FS/: Conf.pm, svc_forward.pm: add the
+ svc_forward-arbitrary_dst flag to enable arbitrary svc_forward
+ destinations
+
+2006-04-13 14:29 ivan
+
+ * FS/bin/freeside-adduser: don't do the duplicate check unless
+ there's a file already; fixes problem with first use of
+ freeside-adduser
+
+2006-04-12 05:36 ivan
+
+ * httemplate/index.html, FS/FS/cust_main.pm,
+ httemplate/search/cust_main-zip.html,
+ httemplate/search/cust_main.cgi,
+ httemplate/search/report_cust_main-zip.html,
+ httemplate/search/elements/search.html: zip code report
+
+2006-04-09 16:41 ivan
+
+ * httemplate/view/: cust_main.cgi, cust_main/payment_history.html,
+ cust_main/tickets.html: a few more fixups for our favorite
+ include(...) from Scott Edwards
+
+2006-04-09 16:39 ivan
+
+ * httemplate/edit/cust_main.cgi: add "spool_cdr" to cust_main
+ fields
+
+2006-04-09 16:24 ivan
+
+ * FS/FS/part_export/domain_shellcommands.pm: should fix "Can't use
+ string ("old_uid") as a SCALAR ref while "strict refs" in use"
+ error
+
+2006-04-09 13:36 ivan
+
+ * FS/bin/freeside-adduser: error out if you try to add duplicates;
+ this should lower my annoyance-level
+
+2006-04-03 16:26 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: option to disable global uniqueness
+ checking
+
+2006-04-03 13:49 ivan
+
+ * FS/FS/svc_domain.pm: add PTR to sort order for DNS entries
+
+2006-04-03 04:36 ivan
+
+ * FS/FS/Record.pm: and also allow [ and ] in ut_textn
+
+2006-04-03 03:11 ivan
+
+ * FS/FS/Record.pm: allow [ and ] in ut_text
+
+2006-04-03 02:46 ivan
+
+ * htetc/handler.pl, FS/FS/Misc.pm,
+ httemplate/edit/cust_main/contact.html,
+ httemplate/edit/cust_main/select-country.html,
+ httemplate/edit/cust_main/select-state.html,
+ httemplate/misc/states.cgi,
+ httemplate/view/cust_main/contacts.html: have the UI use full
+ country names, and state names outside the US...
+
+2006-04-02 15:13 ivan
+
+ * FS/FS/: cust_main.pm, cust_pay_batch.pm: typo
+
+2006-03-31 15:22 ivan
+
+ * httemplate/browse/part_bill_event.cgi: quick sort fix for billing
+ events
+
+2006-03-31 01:20 lsc
+
+ * FS/FS/part_pkg/: prorate.pm, subscription.pm: fixed the errors
+ pointed out by Ivan in the following email:
+
+ ---- before and after now? I gave subscription and prorate a
+ try. Subscription came out as:
+
+ subscription 27th (03/25/06 - 04/27/06) $10.00
+ subscription 23rd (03/25/06 - 04/23/06) $10.00
+
+ the "23rd" one is right, but the "27th" one should have only
+ advanced the date two days to 3/27/06.
+
+ Prorate came out as:
+
+ prorate 23rd (03/25/06 - 04/23/06) $9.20
+ prorate 27th (03/25/06 - 04/27/06) $10.49
+
+ The "23rd" one is right, but the "27th" one should have only
+ advanced the date two days to 4/27/06.
+
+ lsc@prgmr.com
+
+2006-03-30 06:22 ivan
+
+ * README.1.7.0, FS/bin/freeside-upgrade: move all the
+ schema-updating magic into DBIx::DBSchema
+
+2006-03-24 18:23 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/queue_depend.pm, FS/FS/svc_acct.pm,
+ FS/bin/freeside-setup,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_external.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_results.html,
+ httemplate/misc/upload-batch.cgi,
+ httemplate/misc/process/cdr-import.html,
+ httemplate/misc/process/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import_charges.cgi,
+ httemplate/misc/process/inventory_item-import.html: successfully
+ correct the spelling of sucessful
+
+2006-03-24 11:49 ivan
+
+ * httemplate/misc/process/payment.cgi: fix spelling
+
+2006-03-23 04:00 lsc
+
+ * FS/FS/part_pkg/: billoneday.pm, prorate.pm, subscription.pm: for
+ subscription.pm and prorate.pm:
+
+ -modify the subscription and prorate price plans
+ (FS/FS/part_pkg/subscription.pm and prorate.pm) to have a
+ configurable (add a field to the %info hash) billing day instead
+ of "1st of the month" only. subscription will be easy, prorate
+ will be a little trickier.
+
+ essentially, I replaced the '1' in the 'day' field of the
+ timelocal that generates $$date with the value I added to the
+ %info hash, 'cutoff_day'
+
+ -implement a price plan (new file in FS/FS/part_pkg/ - probably
+ @ISA FS::part_pkg::subscription) that charges the first full
+ month if the customer signs up between the 1st and the
+ configurable billing day, and gives them the remainder of the
+ month free if they sign up between the configurable billing day
+ and the end of the month.
+
+ if this is the first time the customer is billed, and if the date
+ is greater than the cutoff date, advance $ssdate to cutoff_day of
+ next month, else $$date is cutoff_date of this month. Either
+ way, charge them for a month.
+
+ ----------------------------------------------------------------------
+
+2006-03-20 11:13 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/cdr.pm,
+ FS/FS/cdr_upstream_rate.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_svc.pm, FS/FS/rate_detail.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_pkg/voip_cdr.pm, httemplate/edit/part_pkg.cgi,
+ FS/MANIFEST, FS/t/cdr_upstream_rate.t,
+ bin/cdr_upstream_rate.import, httemplate/edit/rate.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html,
+ httemplate/view/cust_main/billing.html: add price plan to bill on
+ internal or external CDRs directly, add option to export CDRs to
+ a per-customer downstream file
+
+2006-03-17 06:56 ivan
+
+ * FS/FS/Daemon.pm: use IO::File, lucky this never threw an error...
+
+2006-03-14 23:34 ivan
+
+ * FS/FS/part_pkg/voip_cdr.pm: initial commit of this just cause i
+ want a revision history
+
+2006-03-14 20:17 ivan
+
+ * FS/FS/Record.pm: handle BIGSERIAL like SERIAL for the cdr table,
+ and normalize canadian zip codes as well as us ones
+
+2006-03-13 14:32 ivan
+
+ * httemplate/elements/: progress-init.html, progress-popup.html:
+ fix progress hoohaw for internet exploder again, whew. also make
+ sure error/finish messages are centered, looks better
+
+2006-03-10 23:27 ivan
+
+ * httemplate/elements/footer.html: some pages from ui hoohaw have
+ leaked footer include, need something here for now
+
+2006-03-10 21:21 ivan
+
+ * httemplate/: elements/progress-init.html, elements/xmlhttp.html,
+ misc/email_invoice_events.cgi, misc/email_invoices.cgi,
+ misc/fax_invoice_events.cgi, misc/fax_invoices.cgi,
+ misc/print_invoice_events.cgi, misc/print_invoices.cgi: fix the
+ progressbar bug with multiple progressbar forms on a page
+
+2006-03-10 14:30 ivan
+
+ * httemplate/: elements/progress-init.html,
+ misc/email_invoice_events.cgi, misc/email_invoices.cgi,
+ misc/fax_invoice_events.cgi, misc/fax_invoices.cgi,
+ misc/print_invoice_events.cgi, misc/print_invoices.cgi: fix to
+ (hopefully) allow multiple progress-init's in a page, also add
+ second $cgi arg to all these progressbar calls...
+
+2006-03-10 14:28 ivan
+
+ * FS/FS/UI/Web.pm: want to know who *called* this without the
+ required second arg
+
+2006-03-09 05:42 ivan
+
+ * htetc/handler.pl: fix that
+ blank-page-instead-of-profiling-redirect-when-called-from-an-include
+ bug triggered by mason 1.32 :)
+
+2006-03-09 03:48 ivan
+
+ * httemplate/view/cust_main.cgi: don't use a table with
+ WIDTH="100%", it shoves the custnum and "billing information"
+ boxes way out to the right
+
+2006-03-08 04:14 ivan
+
+ * FS/FS/inventory_item.pm,
+ httemplate/misc/inventory_item-import.html,
+ httemplate/misc/process/inventory_item-import.html,
+ httemplate/search/inventory_class.html,
+ httemplate/search/inventory_item.html: Add an option to the web
+ interface to batch upload new entries to the
+ inventory_item table.
+
+2006-03-08 02:05 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/inventory_class.pm,
+ FS/FS/inventory_item.pm, FS/t/inventory_class.t,
+ FS/t/inventory_item.t, httemplate/search/inventory_class.html,
+ httemplate/search/inventory_item.html, bin/generate-table-module,
+ htetc/handler.pl, httemplate/edit/inventory_class.html,
+ httemplate/edit/elements/edit.html,
+ httemplate/edit/process/inventory_class.html,
+ httemplate/edit/process/elements/process.html,
+ httemplate/search/elements/search.html: Add a new table for
+ inventory with for DIDs/serials/etc., and an additional new table
+ for inventory category (i.e. to distinguish DIDs, serials, MACs,
+ etc.)
+
+2006-03-08 00:21 ivan
+
+ * FS/FS/agent.pm: add space in error msg
+
+2006-03-03 07:02 ivan
+
+ * FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi: agent-specific
+ sales/credit/receipts summary
+
+2006-02-28 11:34 ivan
+
+ * FS/FS/part_pkg.pm: update POD docs regarding new price plans
+
+2006-02-22 05:07 ivan
+
+ * FS/FS/Conf.pm, httemplate/elements/phonenumber.html,
+ httemplate/images/red_telephone_mimooh_01.png,
+ httemplate/view/cust_main/contacts.html: add vonage click2call
+ feature
+
+2006-02-21 23:12 ivan
+
+ * FS/FS/CGI.pm: a better CGI::rooturl(), will have to do for now
+
+2006-02-18 03:14 ivan
+
+ * FS/FS/Schema.pm, FS/FS/cdr.pm, FS/FS/cdr_calltype.pm,
+ FS/FS/cdr_carrier.pm, FS/FS/cdr_type.pm, FS/FS/cust_main.pm,
+ README.1.7.0, README.2.0.0, FS/MANIFEST,
+ FS/FS/part_pkg/voip_sqlradacct.pm, FS/t/cdr.t,
+ FS/t/cdr_calltype.t, FS/t/cdr_carrier.t, FS/t/cdr_type.t,
+ FS/t/part_pkg-voip_cdr.t, htetc/handler.pl,
+ httemplate/misc/cdr-import.html,
+ httemplate/misc/process/cdr-import.html,
+ httemplate/search/cdr.html, httemplate/search/report_cdr.html,
+ bin/cdr_calltype.import: CDR schema and class
+
+2006-02-17 20:32 ivan
+
+ * htetc/global.asa, httemplate/docs/install.html: Mason it is
+
+2006-02-17 18:11 ivan
+
+ * FS/FS/: cust_tax_exempt_pkg.pm, domain_record.pm, msgcat.pm,
+ nas.pm, part_bill_event.pm, port.pm, prepay_credit.pm, queue.pm,
+ queue_arg.pm, rate_detail.pm, reg_code_pkg.pm: update POD
+ documentation left behind from example template
+
+2006-02-16 13:43 ivan
+
+ * FS/FS/Setup.pm, FS/bin/freeside-setup, bin/populate-msgcat,
+ httemplate/docs/admin.html, httemplate/docs/install.html:
+ automate more of the initial data adding...
+
+2006-02-08 23:18 ivan
+
+ * httemplate/elements/: calendar-en.js, calendar-setup.js,
+ calendar-win2k-2.css, calendar.js, calendar_stripped.js: update
+ jscalendar
+
+2006-02-08 14:53 ivan
+
+ * FS/bin/freeside-selfservice-server: don't leave ssh zombies
+ around either
+
+2006-02-07 19:50 ivan
+
+ * httemplate/docs/: upgrade7.html, upgrade8.html: remove ancient
+ upgrade instructions
+
+2006-02-07 19:49 ivan
+
+ * httemplate/docs/: upgrade10.html, index.html: slightly html-ize
+ the 1.5.8 upgrade instructions
+
+2006-02-07 18:26 ivan
+
+ * FS/FS/UID.pm: update error message when secrets file cannot be
+ found
+
+2006-02-07 05:49 ivan
+
+ * FS/FS/svc_acct.pm: well, it was already fatal. at least now the
+ error message is better.
+
+2006-02-07 03:12 ivan
+
+ * httemplate/edit/cust_pay.cgi: remove inadvertant extra table
+ statement preventing page from showing up in konq
+
+2006-02-05 04:27 ivan
+
+ * FS/FS/option_Common.pm, httemplate/browse/payment_gateway.html,
+ httemplate/edit/payment_gateway.html,
+ httemplate/edit/process/payment_gateway.html: payment gateway
+ editing
+
+2006-02-01 15:13 ivan
+
+ * FS/MANIFEST, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/Cron/backup.pm, FS/FS/Cron/bill.pm, FS/FS/Cron/vacuum.pm,
+ FS/bin/freeside-daily, FS/bin/freeside-monthly,
+ FS/t/Cron-backup.t, FS/t/Cron-bill.t, FS/t/Cron-vacuum.t,
+ httemplate/browse/part_bill_event.cgi: finish adding
+ freeside-monthly and monthly events
+
+2006-01-31 23:58 ivan
+
+ * Makefile: HEAD isn't 1.5.8 anymore
+
+2006-01-31 07:01 ivan
+
+ * FS/FS/payby.pm: oops, forgot $
+
+2006-01-31 03:02 ivan
+
+ * FS/FS/Schema.pm, FS/FS/payby.pm, FS/MANIFEST, FS/t/payby.t,
+ htetc/handler.pl, httemplate/browse/part_bill_event.cgi,
+ httemplate/edit/part_bill_event.cgi: [no log message]
+
+2006-01-30 20:26 ivan
+
+ * httemplate/: browse/addr_block.cgi, browse/agent.cgi,
+ browse/agent_type.cgi, browse/cust_pay_batch.cgi,
+ browse/part_bill_event.cgi, browse/part_export.cgi,
+ browse/part_pkg.cgi, browse/part_referral.cgi,
+ browse/part_svc.cgi, browse/part_virtual_field.cgi,
+ browse/payment_gateway.html, browse/queue.cgi, browse/rate.cgi,
+ browse/router.cgi, browse/svc_acct_pop.cgi,
+ config/config-view.cgi, config/config.cgi,
+ edit/REAL_cust_pkg.cgi, edit/agent.cgi,
+ edit/agent_payment_gateway.html, edit/agent_type.cgi,
+ edit/bulk-cust_svc.html, edit/cust_pay.cgi, edit/part_export.cgi,
+ edit/part_pkg.cgi, edit/part_svc.cgi, edit/payment_gateway.html,
+ edit/prepay_credit.cgi, edit/rate.cgi, edit/rate_region.cgi,
+ edit/reg_code.cgi, edit/svc_acct.cgi, edit/svc_broadband.cgi,
+ edit/svc_forward.cgi, edit/process/prepay_credit.cgi,
+ edit/process/reg_code.cgi, misc/batch-cust_pay.html,
+ misc/cust_main-import.cgi, misc/cust_main-import_charges.cgi,
+ misc/expire_pkg.cgi, misc/link.cgi, misc/meta-import.cgi,
+ misc/upload-batch.cgi, misc/whois.cgi,
+ misc/process/cust_main-import.cgi,
+ misc/process/cust_main-import_charges.cgi,
+ misc/process/meta-import.cgi, search/report_prepaid_income.cgi,
+ search/report_tax.cgi, search/svc_external.cgi,
+ view/cust_bill.cgi, view/cust_main.cgi, view/svc_acct.cgi,
+ view/svc_broadband.cgi, view/svc_domain.cgi,
+ view/svc_external.cgi: move header() to
+ include(/elements/header.html) so it can be changed in one place,
+ thanks to Scott Edwards
+
+2006-01-30 18:59 ivan
+
+ * FS/FS/Record.pm: fix "table not found" dbdef error message to
+ recommend freeside-upgrade instead create + dbdef-create
+
+2006-01-26 23:34 ivan
+
+ * httemplate/search/report_tax.cgi: small visual fix to alternating
+ row colors when show_taxclasses is on
+
+2006-01-26 17:33 ivan
+
+ * httemplate/search/cust_tax_exempt_pkg.cgi: on tax exemption
+ report, show more info on the specific line item and invoice
+
+2006-01-26 07:27 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/cust_tax_exempt_pkg.pm,
+ FS/FS/part_pkg.pm, FS/FS/pkg_class.pm, FS/t/pkg_class.t,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/cust_tax_exempt_pkg.cgi,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html: whew, FINALLY can fix monthly
+ exemption columns to work correctly. also make them
+ agent-specific. also fix package exemption columns, they were
+ bunk too, sheesh. start adding package classes for package class
+ tax reporting.
+
+2006-01-25 04:34 ivan
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_tax_exempt.pm,
+ FS/FS/cust_tax_exempt_pkg.pm, FS/FS/h_cust_bill.pm,
+ FS/FS/h_cust_tax_exempt.pm, FS/t/cust_tax_exempt_pkg.t,
+ FS/t/h_cust_bill.t, FS/t/h_cust_tax_exempt.t, README.2.0.0,
+ FS/MANIFEST: change texas-style tax exemptions to be against a
+ specific line item rather than just general per-customer, for
+ later tracking and tax reporting. fix 1969/1970 exemptions for
+ one-off charges
+
+2006-01-05 01:34 ivan
+
+ * httemplate/docs/upgrade10.html: my last 1.4 -> 1.5 upgrade...
+
+2006-01-04 18:03 ivan
+
+ * FS/FS/Schema.pm: remove redundant indices on cust_main ship_
+ columns
+
+2006-01-03 00:45 ivan
+
+ * bin/billco-upload: agentnums 1-3
+
+2005-12-29 18:41 rsiddall
+
+ * FS/FS/Conf.pm, FS/FS/ConfItem.pm, FS/FS/cust_pkg.pm,
+ FS/FS/cust_svc.pm, FS/FS/part_export/artera_turbo.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/provision_list.html,
+ httemplate/view/cust_main.cgi: Fixing a few typos.
+
+2005-12-24 19:41 ivan
+
+ * httemplate/search/report_tax.cgi: add switch to enable taxclass
+ breakdown, report invoiced tax separately in that case
+
+2005-12-24 19:38 ivan
+
+ * httemplate/edit/part_bill_event.cgi: make sure to specify a
+ money_char default
+
+2005-12-24 19:18 ivan
+
+ * httemplate/search/: report_tax.cgi, report_tax.html: add switch
+ to enable taxclass breakdown, report invoiced tax separately in
+ that case
+
+2005-12-24 19:07 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: correct "out of taxable
+ region" flag on new line item report - NULLs need to be compared
+ explicitly. apparantly NULL != NULL. bah SQL
+
+2005-12-24 19:00 ivan
+
+ * README.1.5.8, FS/FS/Schema.pm: update indices for better tax
+ report performance
+
+2005-12-24 18:31 ivan
+
+ * httemplate/search/cust_bill_pkg.cgi: fixup new line item report
+ brainfart
+
+2005-12-24 17:57 ivan
+
+ * httemplate/search/report_tax.cgi: correct end date display for
+ "now"
+
+2005-12-24 16:52 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/part_bill_event.pm,
+ httemplate/edit/part_bill_event.cgi: add invoice event to suspend
+ only when greater than N amount
+
+2005-12-24 16:36 ivan
+
+ * httemplate/edit/cust_main/billing.html: don't reenable postal
+ billing for existing customers just cause its blank...
+
+2005-12-21 20:24 ivan
+
+ * httemplate/search/report_tax.cgi: and s/$taxable/$tot_taxable/ in
+ the declaration too
+
+2005-12-21 20:22 ivan
+
+ * httemplate/search/report_tax.cgi: fix some ambiguous var names
+ causing " "my" variable $t masks earlier declaration in same
+ scope" errors
+
+2005-12-21 20:02 ivan
+
+ * Changes.1.5.8: more accurate description of tax report changes
+
+2005-12-21 20:01 ivan
+
+ * httemplate/search/cust_bill_event.html,
+ httemplate/search/cust_bill_pkg.cgi,
+ httemplate/search/cust_pkg_report.cgi,
+ httemplate/search/report_cust_bill.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html, FS/FS/cust_bill_pkg.pm,
+ httemplate/elements/select-agent.html,
+ httemplate/elements/table-grid.html,
+ httemplate/elements/tr-input-beginning_ending.html,
+ httemplate/elements/tr-select-agent.html,
+ httemplate/search/elements/search.html: tax report update, link
+ to new line item report, per-agent tax reporting
+
+2005-12-18 20:18 ivan
+
+ * FS/FS/part_pkg/: sesmon_hour.pm, sesmon_minute.pm,
+ sql_external.pm, sql_generic.pm, voip_sqlradacct.pm: correct
+ field labeling - not always monthly
+
+2005-12-18 20:18 ivan
+
+ * Changes.1.5.8, FS/FS/part_pkg/sqlradacct_hour.pm: add maximum
+ "caps" to RADIUS usage charges
+
+2005-12-16 14:47 ivan
+
+ * FS/FS/raddb.pm, bin/generate-raddb: add motorola canopy
+ attributes from wtxs, neaten up raddb.pm generation
+
+2005-12-15 17:49 ivan
+
+ * httemplate/edit/payment_gateway.html: add TransactionCentral
+
+2005-12-15 11:36 ivan
+
+ * httemplate/edit/: agent.cgi, agent_type.cgi: fix bug when adding
+ new agent types, noticed by Julius Igugu
+
+2005-12-15 10:45 ivan
+
+ * bin/print-schema: adding print-schema
+
+2005-12-14 20:04 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_pay_void.pm, FS/FS/Record.pm,
+ httemplate/misc/unvoid-cust_pay_void.cgi: payment "un-void"ing
+
+2005-12-14 12:57 ivan
+
+ * FS/FS/svc_acct.pm: allow a dir field to be set (or
+ auto-generated) even if uid is fixed
+
+2005-12-14 10:52 ivan
+
+ * FS/FS/part_export/radiator.pm: add 'STATE' field for suspensions
+ to Radiator export
+
+2005-12-12 19:13 ivan
+
+ * httemplate/edit/process/payment_gateway.html: fix options in
+ gateway adding
+
+2005-12-12 13:39 ivan
+
+ * FS/FS/part_export/radiator.pm: use crypt password for radiator
+ export
+
+2005-12-09 08:58 ivan
+
+ * httemplate/: edit/process/cust_credit_bill.cgi,
+ view/cust_main/payment_history.html: fill in reason if empty when
+ applying a credit to a refund
+
+2005-12-09 08:56 ivan
+
+ * httemplate/edit/: cust_main.cgi, cust_main/billing.html: fix
+ postal mail checkbox misbehaving (isn't sticky on errors -
+ reverts back to on)
+
+2005-12-07 15:48 ivan
+
+ * FS/: FS/Record.pm, bin/freeside-setup: for fetching inserted keys
+ without pg_oid_status, look up the actual sequence name from
+ dbdef rather than assuming ${table}_${column}_seq
+
+2005-12-06 14:25 ivan
+
+ * FS/FS/Record.pm: make sure zip is required for canada, also use
+ CURRVAL() function instead of pg_oid_status DBD attribute because
+ Pg 8.1 doesn't have oids by default anymore
+
+2005-12-05 11:01 ivan
+
+ * FS/FS/svc_Common.pm: avoid uninitialized value errors
+
+2005-12-05 09:19 ivan
+
+ * httemplate/edit/process/cust_main.cgi: eek, don't log all this
+ debugging info by default
+
+2005-12-02 23:26 ivan
+
+ * htetc/handler.pl: this should be the last of
+ s/RT::TicketCustomFieldValues/RT::ObjectCustomFieldValues/
+
+2005-12-02 23:12 ivan
+
+ * Makefile: those semicolons can't possibly have belonged there
+
+2005-12-02 23:02 ivan
+
+ * FS/bin/freeside-upgrade: force a dbdef reload. no wonder this
+ had to be run multiple times before
+
+2005-12-02 22:49 ivan
+
+ * README.1.5.8, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: add Term::ReadKey to install &
+ upgrade docs and README.1.5.8
+
+2005-12-01 18:30 ivan
+
+ * httemplate/docs/upgrade10.html: and Net::Whois::Raw
+
+2005-12-01 17:52 ivan
+
+ * README.1.5.8, httemplate/docs/upgrade10.html: note DBIx::DBSchema
+ 0.29 is required for Pg 7.2.x and earlier
+
+2005-12-01 17:22 ivan
+
+ * httemplate/docs/upgrade10.html: apache instructions already up
+ top
+
+2005-12-01 11:17 ivan
+
+ * httemplate/edit/agent_type.cgi: list disabled packages on agent
+ type edit if they are still associated with the type
+
+2005-12-01 09:36 ivan
+
+ * FS/FS/part_export/shellcommands.pm: shellcommands usermod_pwonly
+ shouldn't apply to RADIUS groups, this is messing up unrelated
+ RADIUS exports
+
+2005-11-30 09:48 ivan
+
+ * README.1.5.8: later versions are okay too
+
+2005-11-28 09:16 ivan
+
+ * FS/FS/cust_main.pm: turn off debugging
+
+2005-11-28 09:13 ivan
+
+ * FS/bin/freeside-sqlradius-radacctd: in POD example, remove extra
+ SQL that Pg doesn't need and MySQL doesn't like
+
+2005-11-28 09:12 ivan
+
+ * FS/bin/freeside-sqlradius-radacctd: update docs and error message
+ for all three supporte exports
+
+2005-11-28 09:07 ivan
+
+ * FS/bin/freeside-sqlradius-radacctd: startup
+ freeside-sqlradius-radacctd for radiator export too
+
+2005-11-28 08:59 ivan
+
+ * FS/FS/svc_Common.pm: allow defaults to override empty values for
+ new objects
+
+2005-11-28 08:38 ivan
+
+ * FS/FS/cust_svc.pm: better error msg
+
+2005-11-28 08:34 ivan
+
+ * FS/FS/cust_svc.pm: update cust_svc::seconds_since_sqlradacct to
+ deal with any usage-capable export
+
+2005-11-28 08:21 ivan
+
+ * FS/FS/cust_svc.pm: update cust_svc::seconds_since_sqlradacct to
+ deal with any usage-capable export
+
+2005-11-28 08:14 ivan
+
+ * FS/FS/Record.pm: okay, its been tested
+
+2005-11-28 08:07 ivan
+
+ * FS/FS/part_export/radiator.pm: fix small bug in radiator export
+
+2005-11-28 07:41 ivan
+
+ * FS/FS/cust_main.pm: add debugging info to cust_main to figure out
+ where the freeze is coming from
+
+2005-11-27 13:59 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/edit/part_bill_event.cgi:
+ per-agent billco spools
+
+2005-11-22 10:29 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/process/cust_main.cgi: add
+ backend-realtime config flag, should be more intuitive for
+ guyananet
+
+2005-11-22 08:41 ivan
+
+ * FS/FS/svc_Common.pm: oops, supposed to commit this one
+
+2005-11-22 08:39 ivan
+
+ * FS/FS/svc_Common.pm: set default fields in new method, mostly for
+ svc_acct.seconds
+
+2005-11-22 05:26 ivan
+
+ * FS/FS/part_export/radiator.pm: update radiator export to deal
+ with prepaid and some other random stuff
+
+2005-11-22 01:13 ivan
+
+ * httemplate/docs/install.html: correct common misconception RIGHT
+ THERE in the docs
+
+2005-11-21 07:24 ivan
+
+ * FS/FS/: export_svc.pm, part_svc.pm, rate.pm: when editing exports
+ and there's a duplicate error, show all conflicting accounts not
+ just the ones for different customers
+
+2005-11-21 06:25 ivan
+
+ * FS/FS/part_export/: shellcommands.pm,
+ shellcommands_withdomain.pm: add usermod_nousername flag to just
+ prohibit username changes
+
+2005-11-21 04:41 ivan
+
+ * httemplate/: edit/cust_main/billing.html, view/cust_bill.cgi,
+ view/cust_main/payment_history.html: ignore blank lines in payby
+ config
+
+2005-11-21 03:04 ivan
+
+ * README: slight update to README
+
+2005-11-21 02:47 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_pay_void.pm, FS/FS/cust_refund.pm,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_pay.cgi, httemplate/search/cust_pay.cgi,
+ httemplate/search/report_cust_pay.html,
+ httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main/payment_history.html: add MCRD payment
+ type for manually processed ccards
+
+2005-11-20 22:46 ivan
+
+ * httemplate/docs/schema.dia: load schema in current dia and save,
+ should load up for people now?
+
+2005-11-20 21:18 ivan
+
+ * FS/FS/Conf.pm: add config values used by external RT integration
+
+2005-11-18 07:08 ivan
+
+ * httemplate/search/report_receivables.cgi: fix link glitch in
+ receivables reports
+
+2005-11-18 06:59 ivan
+
+ * httemplate/edit/agent_type.cgi: template-ize agent type edit and
+ add comment to package listing
+
+2005-11-18 02:58 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: update custom priorioty field
+ BS for RT 3.4.4
+
+2005-11-18 02:53 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: update custom priorioty field
+ BS for RT 3.4.4
+
+2005-11-18 01:44 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: update custom priorioty field
+ BS for RT 3.4.4
+
+2005-11-17 23:05 ivan
+
+ * README.1.5.8: slightly more info in 1.5.8 upgrade instructions
+
+2005-11-17 19:48 ivan
+
+ * FS/FS/cust_main.pm: allow expired cards to remain on file; only
+ check edits for an expired card when the # has changed
+
+2005-11-17 07:56 ivan
+
+ * FS/FS/part_export/: shellcommands.pm,
+ shellcommands_withdomain.pm: make variable description more
+ consistant wrt vars that are already shell-quoted
+
+2005-11-17 04:53 ivan
+
+ * FS/FS/: UI/Web.pm, part_svc.pm: turn off debugging
+
+2005-11-17 04:27 ivan
+
+ * README.1.5.8, FS/FS/Schema.pm: add cust_pkg2 index on
+ cust_pkg.pkgpart
+
+2005-11-17 03:15 ivan
+
+ * FS/FS/svc_acct.pm: update debuggging information for replace
+ group info with user@domain
+
+2005-11-17 03:04 ivan
+
+ * httemplate/view/svc_acct.cgi: fix service change!
+
+2005-11-17 02:14 ivan
+
+ * FS/FS/UI/Web.pm: fix konqueror bug appending nulls to XMLHTTP
+ requests!
+
+2005-11-16 05:14 ivan
+
+ * FS/FS/part_svc.pm, httemplate/index.html,
+ httemplate/browse/part_svc.cgi, Changes.1.5.8, FS/FS/UI/Web.pm,
+ httemplate/edit/bulk-cust_svc.html,
+ httemplate/edit/process/bulk-cust_svc.cgi: bulk svcpart change
+
+2005-11-12 04:19 ivan
+
+ * README.1.5.8: one last bit for 1.5.7->1.5.8 RT upgrade
+ instructions
+
+2005-11-11 17:24 ivan
+
+ * httemplate/view/cust_main/tickets.html: huh seem to have
+ overlooked this
+
+2005-11-11 17:22 ivan
+
+ * FS/FS/cust_main.pm: set payip for all payment types
+
+2005-11-11 16:44 ivan
+
+ * FS/FS/part_export/cpanel.pm: interpolation helps alot
+
+2005-11-11 16:17 ivan
+
+ * FS/FS/part_export/cpanel.pm: try using web interface scrape
+ bullshit for adding pops instead of API, as per cpanel support
+ [cPanel tickets ID# 116044]
+
+2005-11-11 06:06 ivan
+
+ * FS/FS/: cust_pkg.pm, svc_acct.pm: for prepaid packages, trigger
+ export update of RADIUS Expiration attribute when cust_pkg.bill
+ changes
+
+2005-11-10 04:47 ivan
+
+ * httemplate/search/: cust_bill_event.cgi, cust_bill_event.html:
+ add part_bill_event.payby selection to failed invoice event
+ search
+
+2005-11-10 03:36 ivan
+
+ * FS/FS/part_export/everyone_net.pm: fix password changes with
+ everyone.net
+
+2005-11-09 16:00 ivan
+
+ * httemplate/view/svc_domain.cgi: add javascript confirmation to
+ unaudited domain deletion, add record being deleted to record
+ deletion popup
+
+2005-11-09 12:48 ivan
+
+ * httemplate/docs/install.html: closing paren
+
+2005-11-07 18:16 ivan
+
+ * httemplate/docs/admin.html: bah
+
+2005-11-07 14:07 ivan
+
+ * httemplate/search/cust_pay.cgi: fix Discover card report, closes:
+ Bug#1270
+
+2005-11-04 03:43 ivan
+
+ * httemplate/elements/xmlhttp.html: oops, typo applying patch
+
+2005-11-04 03:31 ivan
+
+ * CREDITS, httemplate/elements/xmlhttp.html: apply patch from Scott
+ Edwards to show mason errors received from XMLHTTP requests
+
+2005-11-02 13:18 ivan
+
+ * FS/FS/cust_bill.pm: spool invoice to billco if no other
+ destinations are set!
+
+2005-10-31 21:32 ivan
+
+ * FS/FS/: cust_bill.pm: yarg
+
+2005-10-31 21:27 ivan
+
+ * FS/FS/cust_bill.pm: only send to specific destinations, oops!
+
+2005-10-31 21:21 ivan
+
+ * bin/billco-upload: do the zip
+
+2005-10-31 21:14 ivan
+
+ * httemplate/edit/part_bill_event.cgi: add option for spool_csv
+ events to apply only to a specific destination type (i.e. postal
+ only)
+
+2005-10-31 19:16 ivan
+
+ * bin/billco-upload: good nuff for 11/1
+
+2005-10-31 19:15 ivan
+
+ * httemplate/edit/part_bill_event.cgi, FS/FS/cust_bill.pm: add
+ billco format option to FTP invoice send, add invoice event to
+ spool one giant (pair of) CSV files in addition to FTPing them
+ individually
+
+2005-10-28 10:10 ivan
+
+ * bin/billco-upload: beginning of quick billco zip & upload tool
+
+2005-10-28 04:56 ivan
+
+ * httemplate/search/cust_bill_event.cgi: don't show 'N/A'
+ statustext as a failed billing event
+
+2005-10-27 10:04 ivan
+
+ * httemplate/elements/select-taxclass.html: extraneous '; noticed
+ by joe@surferz
+
+2005-10-27 08:48 ivan
+
+ * httemplate/search/cust_pay.cgi: fix from joe @ surferz: lines 59
+ and 60 had the wrote quote. they had single quote where double
+ was needed...
+
+2005-10-24 04:59 ivan
+
+ * httemplate/edit/rate.cgi: optimize SQL on rate edit screen
+
+2005-10-24 04:56 ivan
+
+ * README.1.5.8, FS/FS/Schema.pm, httemplate/edit/process/rate.cgi,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/xmlhttp.html: fix rate plan editing with new
+ xmlhttp progressbar - use POST instead of GET. also optimize SQL
+ on rate search screen
+
+2005-10-21 08:21 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_pay_void.pm, FS/FS/cust_refund.pm,
+ httemplate/edit/cust_main.cgi, httemplate/edit/cust_pay.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_cust_pay.html,
+ httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main/payment_history.html: add CASH and WEST
+ payment types (payments only, not cust_main.payby)
+
+2005-10-21 06:15 ivan
+
+ * conf/invoice_html: brainfart
+
+2005-10-21 05:50 ivan
+
+ * conf/invoice_html: i thought i fixed this already
+
+2005-10-20 05:30 ivan
+
+ * FS/FS/: cust_pkg.pm, part_pkg/flat.pm: fix credit for remaining
+ service. fuck Date::Manip
+
+2005-10-16 23:46 ivan
+
+ * FS/bin/freeside-sqlradius-reset: set any fixed usergroup before
+ exporting so it'll export even if all svc_acct records don't have
+ the group yet
+
+2005-10-16 23:03 ivan
+
+ * FS/FS/part_svc.pm, httemplate/edit/part_svc.cgi: fix RADIUS
+ usergroup editing
+
+2005-10-16 08:02 ivan
+
+ * bin/bind.export: and make .HEADER optional for slaves too
+
+2005-10-16 07:59 ivan
+
+ * bin/bind.export: don't error out if there's no HEADER file, just
+ produce a useable snippet anyway
+
+2005-10-16 07:04 ivan
+
+ * FS/FS/Conf.pm, FS/FS/domain_record.pm, bin/bind.import: add
+ zone-underscore config file, update bind.import to use
+ command-line options instead of ask for input
+
+2005-10-15 06:48 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: don't error out when
+ ticket_system-default_queueid hasn't been set yet
+
+2005-10-15 06:40 ivan
+
+ * FS/FS/Conf.pm: make sure config still works if no ticket system
+ is configured...
+
+2005-10-15 06:37 ivan
+
+ * FS/FS/Conf.pm: make sure config still works if no ticket system
+ is configured...
+
+2005-10-15 05:58 ivan
+
+ * bin/backup-dvd: adding quick backup-to-dvd script
+
+2005-10-15 04:29 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Schema.pm, FS/FS/TicketSystem.pm,
+ FS/FS/agent.pm, FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm, httemplate/browse/agent.cgi,
+ httemplate/edit/agent.cgi, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi: agent option to select RT queue
+
+2005-10-15 04:25 ivan
+
+ * README.1.5.8: upgrade instructions for new RT verison
+
+2005-10-15 04:13 ivan
+
+ * README.1.5.8: land RT 3.4.4 on HEAD
+
+2005-10-15 02:33 ivan
+
+ * rt/: etc/RT_SiteConfig.pm, lib/RT/TicketCustomFieldValue.pm,
+ lib/RT/TicketCustomFieldValue_Overlay.pm,
+ lib/RT/TicketCustomFieldValues.pm,
+ lib/RT/TicketCustomFieldValues_Overlay.pm,
+ lib/RT/I18N/en_malkovich.po, lib/t/00smoke.t.in,
+ lib/t/01harness.t.in, lib/t/02regression.t.in, lib/t/03web.pl.in,
+ lib/t/04_send_email.pl.in, lib/t/05cronsupport.pl.in,
+ sbin/rt-setup-database.in: landing rt 3.4.4 on HEAD
+
+2005-10-15 02:09 ivan
+
+ * rt/: docs/design_docs/realflow.txt,
+ docs/design_docs/3.3-schema-redesign.txt,
+ docs/design_docs/rt-mvc, sbin/rt-dump-database.in,
+ lib/t/setup_regression.t, lib/t/create_data.pl,
+ lib/RT/SavedSearch.pm, lib/RT/ObjectCustomFields.pm,
+ lib/RT/CustomFieldValue_Overlay.pm, lib/RT/ObjectCustomField.pm,
+ lib/RT/ObjectCustomFields_Overlay.pm, lib/RT/SavedSearches.pm,
+ lib/RT/ObjectCustomFieldValues.pm,
+ lib/RT/ObjectCustomFieldValue.pm,
+ lib/RT/ObjectCustomField_Overlay.pm,
+ lib/RT/ObjectCustomFieldValues_Overlay.pm,
+ lib/RT/ObjectCustomFieldValue_Overlay.pm, lib/RT/I18N/pl.po,
+ lib/RT/I18N/id.po, lib/RT/URI/t.pm,
+ lib/RT/Interface/Web/QueryBuilder.pm,
+ lib/RT/Interface/Web/Standalone.pm,
+ lib/RT/Interface/Web/QueryBuilder/Tree.pm,
+ etc/upgrade/3.3.0/acl.Informix, etc/upgrade/3.3.0/acl.Oracle,
+ etc/upgrade/3.3.0/acl.Pg, etc/upgrade/3.3.0/acl.SQLite,
+ etc/upgrade/3.3.0/acl.mysql, etc/upgrade/3.3.0/content,
+ etc/upgrade/3.3.0/schema.Oracle, etc/upgrade/3.3.0/schema.Pg,
+ etc/upgrade/3.3.0/schema.mysql, etc/upgrade/3.3.11/acl.Oracle,
+ etc/upgrade/3.3.11/acl.Pg, etc/upgrade/3.3.11/acl.SQLite,
+ etc/upgrade/3.3.11/acl.mysql, etc/upgrade/3.3.11/content,
+ etc/upgrade/3.3.11/schema.Oracle, etc/upgrade/3.3.11/schema.Pg,
+ etc/upgrade/3.3.11/schema.SQLite,
+ etc/upgrade/3.3.11/schema.mysql: Initial revision
+
+2005-10-13 14:16 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/edit/svc_acct.cgi: Fix 'can't
+ change uid' error when the account *has* a uid but
+ svc_acct-edit_uid isn't turned on
+
+2005-10-12 05:02 ivan
+
+ * httemplate/misc/: batch-cust_pay.html,
+ process/batch-cust_pay.cgi: change button to say "post payment
+ batch" and main error message to indicate the whole batch should
+ be resubmitted, as per feedback from lewis
+
+2005-10-12 02:51 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix package customize losing
+ services
+
+2005-10-12 01:57 ivan
+
+ * FS/FS/part_export/cpanel.pm: add debugging to cpanel export
+
+2005-10-10 09:06 ivan
+
+ * FS/FS/part_export/everyone_net.pm: oops, that should fix
+ suspension problem
+
+2005-10-10 05:20 ivan
+
+ * Changes.1.5.8, README.1.5.8, FS/FS/cust_pay.pm, htetc/global.asa,
+ htetc/handler.pl, httemplate/index.html,
+ httemplate/docs/upgrade10.html,
+ httemplate/misc/batch-cust_pay.html,
+ httemplate/misc/xmlhttp-cust_main-search.cgi,
+ httemplate/misc/process/batch-cust_pay.cgi,
+ httemplate/search/cust_pay.cgi: updated quick payment entry
+
+2005-10-08 06:45 ivan
+
+ * httemplate/misc/batch-cust_pay.html: quick payment entry
+ javascript tested & working IE/firefix/konq
+
+2005-10-07 17:47 ivan
+
+ * httemplate/: edit/part_pkg.cgi, edit/process/part_pkg.cgi,
+ edit/process/quick-charge.cgi, elements/select-taxclass.html,
+ view/cust_main/quick-charge.html: fix tax class selection in
+ package add/edit too
+
+2005-10-07 16:28 ivan
+
+ * conf/invoice_latex: allow more width for return address, stuff is
+ wrapping
+
+2005-10-06 22:03 ivan
+
+ * README.1.5.8: need new B:CC
+
+2005-10-06 21:50 ivan
+
+ * httemplate/view/cust_main/billing.html: mask out echeck account
+ #s too
+
+2005-10-06 21:48 ivan
+
+ * FS/FS/cust_main.pm: fix on-demand credit cards not being masked
+ in UI. i believe huntsburg is in the doghouse for this one :)
+
+2005-10-06 19:25 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_pkg.pm,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/view/cust_main/quick-charge.html: add
+ require_taxclasses config flag
+
+2005-10-06 16:09 ivan
+
+ * httemplate/view/cust_bill-logo.cgi: really use default logo if
+ the agent-specific one isn't found
+
+2005-10-06 16:07 ivan
+
+ * httemplate/view/cust_bill-logo.cgi: use default logo if the
+ agent-specific one isn't found
+
+2005-10-06 16:03 ivan
+
+ * httemplate/view/cust_bill-logo.cgi: use default logo if the
+ agent-specific one isn't found
+
+2005-10-06 13:35 ivan
+
+ * FS/FS/part_export/everyone_net.pm: add debugging option to
+ everyone_net export
+
+2005-10-06 00:34 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_pay_refund.pm,
+ httemplate/view/cust_main/payment_history.html: add cc-void
+ option
+
+2005-10-05 18:40 ivan
+
+ * httemplate/edit/cust_main.cgi: fix bug with IE and advertising
+ sources (refnum) - needed to make sure the SELECT OPTIONs have
+ explicitly specified VALUEs
+
+2005-10-04 13:27 ivan
+
+ * FS/FS/part_export/everyone_net.pm: fix small error with
+ everyone.net export
+
+2005-10-02 18:41 ivan
+
+ * FS/FS/cust_svc.pm, FS/FS/part_svc.pm, FS/FS/svc_acct.pm,
+ httemplate/view/svc_acct.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/elements/small_custview.html, FS/FS/Conf.pm: update
+ account view and edit: convert to proper templates, make sure
+ usage information displays for any usage-capable export, add
+ ability to edit uid/gid
+
+2005-10-02 07:04 ivan
+
+ * httemplate/docs/install.html: fix leading % causing Mason to barf
+
+2005-10-02 00:09 ivan
+
+ * Makefile, htetc/handler.pl, init.d/freeside-init: add dev make
+ target for quick iterative development
+
+2005-09-29 13:19 ivan
+
+ * FS/FS/svc_acct.pm: make sure there is an expiraiton date to set
+ as well
+
+2005-09-28 11:27 ivan
+
+ * FS/FS/cust_main.pm: recognize hourly frequency in cust_main->bill
+
+2005-09-25 01:20 ivan
+
+ * httemplate/view/cust_main.cgi: this css did weird things under IE
+
+2005-09-25 01:13 ivan
+
+ * CREDITS, Changes.1.5.8, JSRS-LICENSE, README.1.5.7,
+ FS/FS/UI/Web.pm, httemplate/edit/cust_main/select-country.html,
+ httemplate/edit/cust_main/select-county.html,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/edit/process/rate.cgi,
+ httemplate/elements/jsrsServer.html,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/progress-popup.html,
+ httemplate/elements/xmlhttp.html, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: get rid of JSRS iframe foo for
+ progress bar, use XMLHTTPRequest instead. really should have
+ done that in the first place. JSON will wait until another
+ day...
+
+2005-09-24 15:53 ivan
+
+ * FS/FS/cust_main.pm: parse paybatch order number with dashes
+ correctly
+
+2005-09-22 15:25 ivan
+
+ * rt/lib/RT/URI/freeside/Internal.pm: should fix problem with empty
+ (dir-less) Conf showing up
+
+2005-09-21 05:47 ivan
+
+ * FS/FS/svc_acct.pm, FS/MANIFEST, FS/FS/Conf.pm, FS/FS/Record.pm,
+ FS/bin/freeside-prepaidd, httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/view/cust_main/packages.html, init.d/freeside-init,
+ FS/bin/freeside-daily: add prepaid support which sets RADIUS
+ Expiration attribute, update customer view package UI
+
+2005-09-16 03:10 ivan
+
+ * FS/FS/cust_main.pm: tyop
+
+2005-09-16 01:18 ivan
+
+ * FS/FS/cust_main.pm: with taxclasses, might have multiple records
+ for a state/county/country
+
+2005-09-14 04:01 ivan
+
+ * FS/t/agent_payment_gateway.t, FS/t/banned_pay.t,
+ FS/t/cancel_reason.t, FS/t/payment_gateway.t,
+ FS/t/payment_gateway_option.t, bin/generate-table-module: fix
+ autogenerated simple test
+
+2005-09-13 13:12 ivan
+
+ * httemplate/search/elements/search.html: fix redirect with single
+ item returned from search results and a coderef redirect
+
+2005-09-10 07:50 ivan
+
+ * CREDITS, httemplate/edit/cust_main/contact.html,
+ httemplate/edit/cust_main/select-country.html,
+ httemplate/edit/cust_main/select-county.html,
+ httemplate/edit/cust_main/select-state.html,
+ httemplate/edit/cust_main.cgi, httemplate/elements/xmlhttp.html,
+ httemplate/misc/counties.cgi, httemplate/misc/states.cgi:
+ ajax-style xmlhttprequest state/county/country selector!
+
+2005-09-08 12:15 ivan
+
+ * FS/FS/part_export/radiator.pm: MySQL is case sensitive about
+ table names! huh
+
+2005-09-08 01:50 ivan
+
+ * httemplate/edit/cust_main.cgi: fix dropping of payname
+
+2005-09-07 23:52 ivan
+
+ * httemplate/autohandler: don't need 4k of newlines anymore,
+ H:W:SelectLayers works better
+
+2005-09-07 15:52 ivan
+
+ * FS/FS/part_pkg.pm: add hourly frequency
+
+2005-09-07 15:04 ivan
+
+ * bin/generate-raddb: list current dictionary sources in example so
+ i don't forget
+
+2005-09-07 15:02 ivan
+
+ * FS/FS/raddb.pm: update with dictionaries from freeradius 1.0.4
+ plus dictionary.ip3networks
+
+2005-09-07 05:56 ivan
+
+ * FS/FS/Schema.pm: define username_len so the default add account
+ screen doesn't have a tiny username field (when usernamemax is
+ not defined)
+
+2005-09-07 05:48 ivan
+
+ * httemplate/edit/svc_acct.cgi: fix silly bug sizing username field
+ when adding an account (and usernamemax is not set)
+
+2005-09-07 05:44 ivan
+
+ * httemplate/edit/svc_acct.cgi: fix silly bug sizing username field
+ when adding an account (and username max is not set)
+
+2005-09-07 05:38 ivan
+
+ * httemplate/view/cust_main.cgi: oops, still need areyousure
+ javascript for some sub-bits of the page (package unprovisioning
+ and cancellation, bunch of payment history stuff)
+
+2005-09-07 03:40 ivan
+
+ * FS/FS/: part_svc.pm, part_virtual_field.pm: import dbdef from
+ FS::Schema instead of calling $FS::Record::dbdef directly
+
+2005-09-07 03:25 ivan
+
+ * FS/FS/option_Common.pm: fix bug with new option_Common stuff,
+ forgot to prefix FS::
+
+2005-09-07 03:16 ivan
+
+ * FS/FS/part_export/cpanel.pm: remote access key is a big long
+ thing
+
+2005-09-07 02:37 ivan
+
+ * Changes.1.5.8, FS/FS/part_export/cpanel.pm,
+ eg/export_template.pm: add cpanel export
+
+2005-09-06 14:58 ivan
+
+ * FS/FS/Conf.pm: document variables available in
+ payment_receipt_email
+
+2005-08-27 01:46 ivan
+
+ * FS/MANIFEST, FS/FS/Schema.pm, FS/FS/banned_pay.pm,
+ FS/FS/cancel_reason.pm, FS/FS/cust_main.pm, FS/t/banned_pay.t,
+ FS/t/cancel_reason.t, bin/generate-table-module,
+ httemplate/view/cust_main.cgi,
+ httemplate/misc/cust_main-cancel.cgi: add banned credit card /
+ ACH table, re-do cancel popup to have a checkbox to ban payinfo
+
+2005-08-24 07:07 ivan
+
+ * README.1.5.8, httemplate/docs/upgrade10.html: need
+ H:W:SelectLayers 0.05
+
+2005-08-24 06:47 ivan
+
+ * httemplate/edit/cust_main/billing.html: fix size of cvv2 help
+ popup
+
+2005-08-24 06:22 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/billing.html,
+ httemplate/edit/cust_main/contact.html, Changes.1.5.8,
+ FS/FS/cust_main.pm, FS/FS/cust_main_county.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ httemplate/docs/ach.html, httemplate/docs/cvv2.html,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/elements/overlibmws_draggable.js,
+ httemplate/elements/overlibmws_iframe.js,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/select-month_year.html,
+ httemplate/images/cvv2.png, httemplate/images/cvv2_amex.png,
+ httemplate/view/cust_main/billing.html: customer edit: abstract
+ out billing info to billing.html, re-do payment type selection
+ with HTML::Widgets::SelectLayers, add Switch/Solo/Maestro support
+ for start date & issue #. customer view: re-order fields for
+ consistency. selfservice API: support paystart_month,
+ paystart_year, payissue and payip in MyAccount::process_payment
+ and ::edit_info and Signup::new_customer,
+ FS::cust_main::realtime_bop: support paystart_month paystart_year
+ payissue payip fields
+
+2005-08-23 05:38 ivan
+
+ * httemplate/edit/part_svc.cgi: fix bug with service editing caused
+ by moving dbdef stuff around
+
+2005-08-18 00:58 ivan
+
+ * FS/FS/cust_main.pm: add taxclass kludge to gateway overrides, fix
+ parsing of new-style paybatch
+
+2005-08-17 22:41 ivan
+
+ * httemplate/edit/process/agent_payment_gateway.html: hopefully
+ this is the last missing file
+
+2005-08-17 22:12 ivan
+
+ * httemplate/edit/process/payment_gateway.html: oops another
+ missing file
+
+2005-08-17 21:16 ivan
+
+ * FS/FS/agent_payment_gateway.pm: missing file
+
+2005-08-17 15:23 ivan
+
+ * FS/FS/Record.pm, FS/FS/Schema.pm, FS/FS/cust_main.pm,
+ FS/FS/option_Common.pm, FS/FS/part_export.pm,
+ FS/FS/part_export_option.pm, FS/FS/part_pkg.pm,
+ FS/FS/payment_gateway.pm, FS/FS/payment_gateway_option.pm,
+ FS/bin/freeside-setup, FS/bin/freeside-upgrade, README.1.5.8,
+ SCHEMA_CHANGE, FS/FS.pm, FS/MANIFEST, FS/t/option_Common.t,
+ FS/t/payment_gateway.t, FS/t/payment_gateway_option.t,
+ bin/generate-table-module, htetc/global.asa, htetc/handler.pl,
+ httemplate/index.html, httemplate/browse/agent.cgi,
+ httemplate/browse/payment_gateway.html,
+ httemplate/docs/upgrade10.html,
+ httemplate/edit/agent_payment_gateway.html,
+ httemplate/edit/payment_gateway.html: infrastructure for easier
+ schema changes, and: add payment_gateway, payment_gateway_option
+ and agent_payment_gateway tables, add paystart_month,
+ paystart_year, payissue and payip fields to cust_main, add
+ preliminary gateway and gateway override editing to web UI, use
+ payment gateway override when processing payments (card type, not
+ taxclass yet)
+
+2005-08-14 18:55 ivan
+
+ * FS/FS/UI/Web.pm: fix brainfart parsing end dates, closes:
+ Bug#1248
+
+2005-08-09 14:38 ivan
+
+ * Changes.1.5.8, FS/FS/part_export/radiator.pm,
+ FS/FS/part_export/sqlradius.pm, FS/t/part_export-radiator.t,
+ FS/FS/part_export/sqlradius_withdomain.pm: add native Radiator
+ export
+
+2005-08-08 08:15 ivan
+
+ * Changes.1.5.8, FS/FS/part_export/everyone_net.pm: add export to
+ everyone.net outsource mail service
+
+2005-08-07 20:15 ivan
+
+ * httemplate/docs/selfservice.html: add some docs on
+ signup_server-payby and -realtime configuration values
+
+2005-08-06 17:41 ivan
+
+ * FS/FS/part_export/forward_shellcommands.pm: update
+ forward_shellcommands export to know about literal source
+ addresses, closes: Bug#1246
+
+2005-08-06 17:40 ivan
+
+ * Changes.1.5.8, FS/FS/agent.pm, FS/FS/cust_pkg.pm,
+ FS/FS/cust_svc.pm, FS/FS/UI/Web.pm, httemplate/browse/agent.cgi,
+ httemplate/search/cust_pay.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/search/elements/search.html: move cust_pkg search to
+ new template, add active/suspended/cancelled customer packages to
+ agent browse
+
+2005-08-04 17:57 ivan
+
+ * httemplate/search/cust_credit.html: fix credit searches by otaker
+
+2005-08-04 01:39 ivan
+
+ * httemplate/docs/install.html: add IPC::Run3 and instructions for
+ adding fs_queue and fs_selfservice users to install docs
+
+2005-08-03 18:42 ivan
+
+ * Makefile: don't use install -D flag, doesn't work on bsd
+
+2005-08-03 00:38 ivan
+
+ * FS/FS/cust_bill.pm: eliminate scary (but harmless) "Use of
+ uninitalized value in length" warnings
+
+2005-08-02 19:23 ivan
+
+ * FS/FS/part_export/: domain_shellcommands.pm,
+ forward_shellcommands.pm, www_shellcommands.pm: don't try to run
+ blank commands for non-svc_acct shellcommand exports too
+
+2005-08-02 12:17 khoff
+
+ * FS/FS/h_cust_svc.pm: Only complain, not die, if we can't find a
+ svc_x record for an h_cust_svc record.
+
+2005-07-14 04:55 ivan
+
+ * FS/FS/cust_main.pm: oops, fix last minute bug with new
+ configurable customer fields on reports
+
+2005-07-14 04:46 ivan
+
+ * FS/FS/: svc_Common.pm, UI/Web.pm: clean up some leftover bits
+ from cust-fields work
+
+2005-07-14 04:31 ivan
+
+ * httemplate/: index.html, search/svc_acct.cgi,
+ search/svc_forward.cgi: add unlinked mail forward (svc_forward)
+ report
+
+2005-07-14 04:18 ivan
+
+ * httemplate/search/cust_bill.html: move account search
+ (httemplate/search/svc_acct.cgi) to new template, cust-fields
+ configuration value to control which customer fields are shown on
+ reports
+
+2005-07-14 03:52 ivan
+
+ * Changes.1.5.8, FS/MANIFEST, FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_event.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_Mixin.pm, FS/FS/cust_pay.pm,
+ FS/FS/svc_Common.pm, FS/FS/UI/Web.pm, FS/t/cust_main_Mixin.t,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/svc_acct.cgi, httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_forward.cgi, httemplate/search/svc_www.cgi,
+ httemplate/search/elements/search.html: move account search
+ (httemplate/search/svc_acct.cgi) to new template, cust-fields
+ configuration value to control which customer fields are shown on
+ reports
+
+2005-07-12 04:54 ivan
+
+ * README.1.5.7: add alternative for very old Pg
+
+2005-07-12 02:31 ivan
+
+ * httemplate/: browse/part_pkg.cgi, view/cust_pkg.cgi: <rjbs> More
+ of the same: these patches make it safer to subclass
+ FS::part_pkg's pkg_svc method by eliminating qsearches on table
+ pkg_svc.
+
+2005-07-12 02:22 ivan
+
+ * httemplate/edit/part_pkg.cgi: <rjbs> This patch is part of my
+ continuing effort to avoid using SQL and qsearch from templates
+ to find the pkg_svc records for a package.
+
+2005-07-12 02:13 ivan
+
+ * CREDITS, FS/FS/Record.pm: patch from rjbs to add by_key
+ contructor to Record.pm
+
+2005-07-12 02:11 ivan
+
+ * Makefile: okay, really enable RT by default for good now
+
+2005-07-11 07:15 ivan
+
+ * bin/: rate.import, rt-drop-tables: adding rt-drop-tables
+
+2005-07-11 06:01 ivan
+
+ * Makefile: new server
+
+2005-07-11 05:53 ivan
+
+ * httemplate/docs/: index.html, upgrade10.html: note alternate
+ instructions for 0pre6->7
+
+2005-07-11 05:39 ivan
+
+ * Changelog, Changes.1.5.7: s/ANNOUNCE/Changelog/
+
+2005-07-11 05:22 ivan
+
+ * bin/postfix.export: fix regex
+
+2005-07-11 05:22 ivan
+
+ * httemplate/browse/queue.cgi: template
+
+2005-07-11 05:21 ivan
+
+ * Makefile: probably best to keep RT disabled by default, at least
+ for this release
+
+2005-07-11 05:09 ivan
+
+ * ANNOUNCE.1.5: last bits
+
+2005-07-11 03:58 ivan
+
+ * FS/t/ClientAPI_SessionCache.t:
+ s/ClientAPI::SessionCache/ClientAPI_SessionCache/ noticed by rjbs
+
+2005-07-11 02:49 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: bug fix for error message on
+ session expiration from Randall Lucas <rlucas@tercent.net>,
+ thanks!
+
+2005-07-09 08:41 ivan
+
+ * FS/FS/cust_bill.pm: fix silly bug preventing html invoicing from
+ finding their logo
+
+2005-07-09 03:55 ivan
+
+ * httemplate/edit/cust_pay.cgi: get rid of the godawful halfass
+ "invoice" on the post payment screen when posting against a
+ specific invoice, replace with having useless "Auto-apply to
+ invoices" box actually show the payment will be applied to one
+ invoice only... closes: Bug#1241
+
+2005-07-09 03:36 ivan
+
+ * FS/FS/: cust_bill.pm, cust_bill_pkg.pm, part_export/sqlradius.pm,
+ part_pkg/flat.pm, part_pkg/flat_comission.pm,
+ part_pkg/flat_comission_cust.pm, part_pkg/flat_comission_pkg.pm,
+ part_pkg/flat_delayed.pm, part_pkg/prepaid.pm,
+ part_pkg/prorate.pm, part_pkg/sesmon_hour.pm,
+ part_pkg/sesmon_minute.pm, part_pkg/sql_external.pm,
+ part_pkg/sql_generic.pm, part_pkg/sqlradacct_hour.pm,
+ part_pkg/subscription.pm, part_pkg/voip_sqlradacct.pm: add desc
+ method to cust_bill_pkg and use it in cust_bill... this should
+ help with any *other* cust_bill_pkg.pkgnum == -1 stuff that needs
+ to be sorted out
+
+2005-07-08 16:06 ivan
+
+ * FS/bin/freeside-daily: fix bug with new efficient
+ customer-finding code. sql isn't perl, null != 0
+
+2005-07-05 14:46 ivan
+
+ * httemplate/docs/install-rt.html: tyop
+
+2005-06-30 06:32 ivan
+
+ * FS/FS/Misc.pm: pod error
+
+2005-06-30 06:20 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add username-percent config option
+
+2005-06-30 05:44 ivan
+
+ * FS/FS/cust_main.pm: oops, really fix error with new prepaid card
+ foo
+
+2005-06-21 20:54 ivan
+
+ * httemplate/docs/upgrade10.html: add IPC::Run3 to install docs
+
+2005-06-16 22:31 ivan
+
+ * httemplate/misc/process/link.cgi: fix preference sort order for
+ linking: sort unaudited services first, secondary sort by svcaprt
+
+2005-06-16 22:07 ivan
+
+ * httemplate/misc/process/link.cgi: add more info to debugging
+
+2005-06-16 22:04 ivan
+
+ * httemplate/misc/process/link.cgi: fix debugging
+
+2005-06-16 21:42 ivan
+
+ * httemplate/misc/process/link.cgi: add debugging for accounts
+ picked to pick
+
+2005-06-14 21:46 ivan
+
+ * README.1.5.7, FS/FS/cust_bill.pm: better error reporting for
+ actual errors from lpr command
+
+2005-06-14 19:31 ivan
+
+ * FS/FS/cust_main.pm: fix error recharging w/prepaid card caused by
+ rounding off of money value, closes: Bug#1237
+
+2005-06-14 17:44 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/search/cust_bill.html: add
+ reprint/fax/email links to invoice search results
+
+2005-06-09 15:40 ivan
+
+ * httemplate/search/cust_bill_event.cgi, FS/FS/cust_bill.pm,
+ httemplate/index.html, httemplate/misc/email_invoice_events.cgi,
+ httemplate/misc/email_invoices.cgi,
+ httemplate/misc/fax_invoice_events.cgi,
+ httemplate/misc/fax_invoices.cgi,
+ httemplate/misc/print_invoice_events.cgi,
+ httemplate/misc/print_invoices.cgi,
+ httemplate/search/cust_bill.html,
+ httemplate/search/report_cust_bill.html: advanced invoice serach,
+ groundwork to add reprint/fax/email links to invoice search
+ results
+
+2005-06-09 13:26 ivan
+
+ * httemplate/browse/part_pkg.cgi: tiny refactor patch from rjbs: It
+ changes the service listing to use $part_pkg->pkg_svc instead of
+ a qsearch, which means that the listing will still work if
+ pkg_svc has been subclassed.
+
+2005-06-09 13:16 ivan
+
+ * FS/FS/cust_svc.pm: documentation fix, noticed by rjbs
+
+2005-06-09 02:15 ivan
+
+ * FS/FS/part_pkg.pm: don't rebless if we're already in the plan
+ subclass, fixes pkg customize link
+
+2005-06-09 00:19 ivan
+
+ * FS/bin/freeside-daily: declare new $opt_a
+
+2005-06-09 00:13 ivan
+
+ * FS/bin/freeside-daily: significant speedup from only selected
+ customers with outstanding packages or invoice events in the
+ initial select, and add -a flag for agentnum
+
+2005-06-08 23:56 ivan
+
+ * FS/FS/cust_bill.pm, conf/invoice_html,
+ httemplate/view/cust_bill-logo.cgi: agent-specific logos for html
+ invoices too
+
+2005-06-08 23:36 ivan
+
+ * FS/FS/: cust_bill.pm, part_bill_event.pm: and make
+ (html|latex)(small)?footer optionall per-agent too
+
+2005-06-08 23:22 ivan
+
+ * FS/FS/cust_bill.pm: more sane regex
+
+2005-06-08 23:14 ivan
+
+ * FS/FS/: cust_bill.pm: ugh, hopefully fixup agent_plandata regex
+ for multiple agents
+
+2005-06-08 22:59 ivan
+
+ * FS/FS/: cust_bill.pm: make sure invoice_(latex|html)returnaddress
+ is configurable per-agent
+
+2005-06-08 22:34 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/part_bill_event.pm,
+ httemplate/edit/part_bill_event.cgi: add multiple agent selection
+ to agent-specific invoicing
+
+2005-06-08 17:18 ivan
+
+ * FS/FS/cust_pay.pm: add ignore_noapply flag to make sure payments
+ are forced in anyway on import
+
+2005-06-08 14:52 ivan
+
+ * FS/FS/cust_bill_pkg.pm: last bit to allow -1 for non-pkg, non-tax
+ line items
+
+2005-06-08 02:03 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/svc_acct.pm,
+ FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_external.html,
+ fs_selfservice/FS-SelfService/cgi/provision.html,
+ fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_prepay.html,
+ fs_selfservice/FS-SelfService/cgi/recharge_results.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html,
+ httemplate/view/svc_acct.cgi: prepaid card recharge
+
+2005-06-07 14:02 ivan
+
+ * FS/FS/Misc.pm: debugging output change in send_email
+
+2005-06-06 13:07 ivan
+
+ * FS/FS/cust_bill.pm: eek, fix silly problem in invoice sending
+ refactoring
+
+2005-06-06 12:54 ivan
+
+ * FS/FS/Misc.pm: some additional warnings
+
+2005-06-06 10:00 ivan
+
+ * FS/FS/Misc.pm: fix message ID generation for ancient perl, bah
+
+2005-06-02 18:51 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: argh. this should finally fix
+ the last of the Internal vs External breakage i hope
+
+2005-06-02 02:36 ivan
+
+ * httemplate/search/cust_bill_event.html: Capitalization
+
+2005-06-02 02:29 ivan
+
+ * httemplate/index.html, httemplate/misc/print_invoices.cgi,
+ httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_bill_event.html, FS/FS/cust_bill_event.pm,
+ FS/FS/part_bill_event.pm, FS/FS/UI/Web.pm,
+ httemplate/misc/email-invoice.cgi,
+ httemplate/misc/email_invoices.cgi,
+ httemplate/misc/fax-invoice.cgi,
+ httemplate/misc/fax_invoices.cgi,
+ httemplate/misc/print-invoice.cgi, FS/FS/cust_bill.pm,
+ FS/FS/part_pkg.pm, httemplate/view/cust_bill.cgi,
+ FS/bin/freeside-queued, httemplate/search/elements/search.html,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/progress-popup.html, htetc/global.asa,
+ htetc/handler.pl: add ability to search on a date range of
+ invoice events and then reprint or reemail (boy was that a bit
+ more work than i expected), closes: Bug#946
+
+2005-06-01 16:40 ivan
+
+ * httemplate/view/cust_bill.cgi: align terminology for consistancy.
+ or something.
+
+2005-06-01 14:40 ivan
+
+ * httemplate/search/report_receivables.cgi: remove trailing
+ whitespace
+
+2005-06-01 13:31 ivan
+
+ * FS/FS/TicketSystem/: RT_External.pm, RT_Internal.pm: fix up
+ RT_Internal again, hopefully this is the last of the breakage
+ from RT_Internal
+
+2005-05-31 19:39 ivan
+
+ * httemplate/edit/part_pkg.cgi: really. *sigh*
+
+2005-05-31 16:42 ivan
+
+ * FS/FS/part_pkg/sesmon_minute.pm: tyop refactoring old
+ sesmon_minute price plkan, noticed by rjbs
+
+2005-05-31 16:32 ivan
+
+ * httemplate/edit/part_pkg.cgi: ack, this should finally fix the
+ package editing problem
+
+2005-05-26 12:30 ivan
+
+ * httemplate/view/: cust_bill.cgi: silly perl version bs
+
+2005-05-26 12:15 ivan
+
+ * FS/FS/svc_www.pm, httemplate/edit/svc_www.cgi,
+ httemplate/view/svc_www.cgi: make svc_www.usersvc optional
+
+2005-05-25 20:45 ivan
+
+ * httemplate/edit/part_pkg.cgi: 72?!
+
+2005-05-24 08:33 ivan
+
+ * Makefile: 1.5.7. and enable RT by default, finally
+
+2005-05-23 04:49 ivan
+
+ * htetc/freeside-rt.conf: so Search.tsf and Search.rdf work
+
+2005-05-22 16:38 ivan
+
+ * FS/FS/svc_acct.pm: fix quick crypt_password bug when its passwd
+ an empty param
+
+2005-05-22 13:39 ivan
+
+ * httemplate/view/cust_bill.cgi: oops, quote $link w/new templating
+
+2005-05-22 02:25 ivan
+
+ * httemplate/browse/agent.cgi: Avoid Gratuitous Capitalizaiton
+
+2005-05-21 21:24 ivan
+
+ * FS/FS/prepay_credit.pm: add optional agentnum field to POD doc
+
+2005-05-21 11:38 ivan
+
+ * httemplate/browse/agent.cgi: make sure the customers tables for
+ each agent line up with each other, too
+
+2005-05-21 10:26 ivan
+
+ * httemplate/search/cust_main.cgi: hehe, allow changing the browse
+ order of any sort
+
+2005-05-21 10:14 ivan
+
+ * httemplate/browse/agent.cgi: line up
+ prospect/active/suspended/cancelled customers
+
+2005-05-19 07:10 ivan
+
+ * FS/FS/part_export/shellcommands.pm: A group number must refer to
+ an already existing group - so add manually it if you want to
+ define a set of static gids or something, default is just going
+ to cause headaches
+
+2005-05-19 04:05 ivan
+
+ * FS/FS/svc_acct.pm: ! or !! also
+
+2005-05-19 03:45 ivan
+
+ * FS/FS/part_export/shellcommands.pm: fix up some defaults to
+ include gid too
+
+2005-05-19 03:29 ivan
+
+ * FS/FS/part_export/: acct_sql.pm, shellcommands.pm: missing ;
+
+2005-05-19 03:26 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/acct_sql.pm,
+ part_export/shellcommands.pm,
+ part_export/shellcommands_withdomain.pm: fix shellcommands export
+ encrypting "magic" shadow values * NP *LK*
+
+2005-05-19 02:49 ivan
+
+ * FS/FS/svc_acct.pm: oops, remove old-style duplicate check from
+ replace, _check_duplicate was already below it
+
+2005-05-19 01:51 ivan
+
+ * httemplate/view/cust_bill.cgi: oops, fix alternate view/etc.
+ links
+
+2005-05-19 01:43 ivan
+
+ * httemplate/: misc/email-invoice.cgi, misc/fax-invoice.cgi,
+ misc/print-invoice.cgi, view/cust_bill.cgi: re-email/fax/print
+ links should respect template, also add direct re-send links like
+ the view links and convert view/cust_bill.cgi to proper template
+
+2005-05-18 09:57 ivan
+
+ * httemplate/browse/agent.cgi: oops, it helps to get the link right
+
+2005-05-18 09:55 ivan
+
+ * httemplate/: browse/agent.cgi, search/report_receivables.cgi: add
+ agent-specific A/R aging report, closes: bug#1229
+
+2005-05-18 09:37 ivan
+
+ * bin/masonize: report the file that had a masonize error
+
+2005-05-18 07:50 ivan
+
+ * FS/FS/part_export/acct_sql.pm: actually, quota goes in pw_shell.
+ of course!
+
+2005-05-18 03:43 ivan
+
+ * FS/FS/cust_bill.pm: oops, losing notes!
+
+2005-05-15 06:49 ivan
+
+ * httemplate/search/report_receivables.cgi: this status column is
+ probably faster, one giant SQL query. and add back in the
+ customer link, oops
+
+2005-05-15 06:00 ivan
+
+ * FS/FS/cust_main.pm, httemplate/search/report_receivables.cgi,
+ httemplate/search/elements/search.html: move receivables report
+ to search template
+
+2005-05-15 04:58 ivan
+
+ * httemplate/search/cust_bill.html: use money_char config
+
+2005-05-15 04:46 ivan
+
+ * httemplate/search/cust_bill.html: align
+
+2005-05-14 13:11 ivan
+
+ * README.1.5.7.lastbit: hmm virtual fields have no history?
+
+2005-05-14 13:11 ivan
+
+ * README.1.5.7: 0.26
+
+2005-05-14 12:57 ivan
+
+ * README.1.5.7, rt/lib/RT/URI/freeside.pm,
+ rt/lib/RT/URI/freeside/Internal.pm: fixup RT integration grr!
+
+2005-05-14 11:03 ivan
+
+ * FS/FS/cust_bill.pm: h helps halot
+
+2005-05-14 10:59 ivan
+
+ * FS/FS/cust_bill.pm: well this is sorta halfass anyway but useful
+
+2005-05-14 10:04 ivan
+
+ * FS/FS/Conf.pm, conf/invoice_html,
+ httemplate/view/cust_bill-logo.cgi: html invoices: when
+ displaying, use the actual logo from the conf dir - same as the
+ emailed copy
+
+2005-05-14 09:30 ivan
+
+ * FS/FS/: Misc.pm, cust_bill.pm: disable debugging
+
+2005-05-14 09:27 ivan
+
+ * ANNOUNCE.1.5, FS/FS/Conf.pm, FS/FS/Misc.pm, FS/FS/cust_bill.pm,
+ FS/FS/part_bill_event.pm, conf/invoice_html, conf/logo.png,
+ httemplate/docs/billing.html, httemplate/view/cust_bill.cgi: html
+ invoices!
+
+ http://chris-linfoot.net/d6plinks/CWLT-5VZD4Y
+ http://www.dsv.su.se/~jpalme/ietf/mhtml.html
+ ftp://ftp.dsv.su.se/users/jpalme/draft-ietf-mhtml-info.txt
+ http://mailformat.dan.info/headers/mime.html
+ http://www.faqs.org/rfcs/rfc2392.html
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cdosys/html/_cdosys_content-type_multipart.asp
+
+ (MIME is hard, let's go shopping!)
+
+2005-05-13 08:06 ivan
+
+ * FS/FS/cust_bill.pm: fix picking up alternate invoice_latexnotes_*
+ files, and expand country codes on invoices. and oops,
+ print_html changes slipped in last commit too. well, they were
+ ready anyway.
+
+2005-05-13 03:55 ivan
+
+ * conf/invoice_latex: better to do something fancy like scale down
+ the font size to fit in the intended space or something but hey,
+ this is good enough for now :)
+
+2005-05-13 03:54 ivan
+
+ * FS/FS/cust_bill.pm: substitute a non-breaking space if there is
+ no invoice_latexreturnaddress file
+
+2005-05-11 07:07 ivan
+
+ * FS/FS/export_svc.pm: new progressbar display causes a silly error
+ in edge case where no dup checking necessary, closes: Bug#1226
+
+2005-05-05 06:04 ivan
+
+ * conf/: invoice_latex: no comma between state and address
+
+2005-05-04 18:46 ivan
+
+ * httemplate/search/svc_domain.cgi: the ? between svc_domain.cgi
+ and the domain id is missing
+
+2005-05-04 02:33 ivan
+
+ * FS/FS/part_pkg.pm: use new pkg_svc.pkgsvcnum primary key when
+ modifying pkg_svc records, closes: Bug#1221
+
+2005-05-04 02:20 ivan
+
+ * FS/FS/Record.pm: err msg
+
+2005-05-03 18:06 ivan
+
+ * FS/FS/cust_bill.pm: patch to just update the template or
+ conf/invoice_latex* to use the new one
+
+2005-05-03 17:40 ivan
+
+ * conf/invoice_latex, conf/invoice_latexfooter,
+ conf/invoice_latexreturnaddress, conf/logo.eps,
+ httemplate/docs/billing.html, FS/FS/Conf.pm, FS/FS/cust_bill.pm:
+ great new invoice template from kristian!
+
+2005-05-03 10:22 ivan
+
+ * FS/FS/cust_bill.pm: minutely better error reporting for pdf
+ problems
+
+2005-05-03 06:29 ivan
+
+ * FS/bin/: freeside-addoutsource, freeside-addoutsourceuser: remove
+ unnecessary host=localhost from outsource instance creation foo
+
+2005-05-03 05:56 ivan
+
+ * README.1.5.7: fix some wrapping
+
+2005-05-03 02:56 ivan
+
+ * FS/FS/cust_pay.pm: on receipts, show "Electronic Check" instead
+ of "Chek" for payby = CHEK transactions
+
+2005-05-03 01:51 ivan
+
+ * FS/bin/freeside-sqlradius-radacctd: not running with elevated
+ privs, -T not necessary
+
+2005-05-03 01:46 ivan
+
+ * FS/FS/domain_record.pm: [:ascii:] is not legal for 5.005, alas,
+ it still needs to be supported for just a little bit longer
+
+2005-05-01 23:32 ivan
+
+ * FS/FS/svc_Common.pm, httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_forward.cgi, httemplate/search/svc_www.cgi:
+ move httemplate/search/svc_domain to the new search template
+ along with svc_www and svc_forward
+
+2005-04-30 02:47 ivan
+
+ * httemplate/search/svc_forward.cgi: show "(unlinked)" for unlined
+ forwards instead of nothing
+
+2005-04-30 02:45 ivan
+
+ * httemplate/search/svc_forward.cgi: pull in the customer
+ information as part of the main query for efficiency
+
+2005-04-29 06:00 ivan
+
+ * httemplate/search/: svc_forward.cgi, svc_www.cgi: template
+ forward search using the standard search template: forwards are
+ now paged
+
+2005-04-28 10:05 ivan
+
+ * FS/FS/cust_bill.pm: add < and > to _latex_escape (khoff)
+
+2005-04-25 17:20 khoff
+
+ * bin/svc_broadband.renumber: For renumbering svc_broadband
+ services from one addr_block to another. Hopefully no one will
+ ever have to use this.
+
+2005-04-25 02:33 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Daemon.pm, FS/FS/svc_acct.pm, FS/t/Daemon.t,
+ FS/MANIFEST, FS/FS/part_export/sqlradius.pm,
+ FS/bin/freeside-queued, FS/bin/freeside-selfservice-server,
+ FS/bin/freeside-sqlradius-radacctd, init.d/freeside-init: pick up
+ freeside-sqlradius-radacctd again after all these years, now it
+ just needs to update the "seconds" field(s), finally closes:
+ Bug#1125
+
+2005-04-21 04:47 ivan
+
+ * FS/FS/part_export/acct_sql.pm: fix nit with crypt flag when
+ replacing too
+
+2005-04-21 04:35 ivan
+
+ * FS/FS/part_export/acct_sql.pm: support multiple primary keys
+
+2005-04-20 00:12 ivan
+
+ * FS/FS/part_export/acct_sql.pm: hehe oops
+
+2005-04-19 23:41 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/acct_sql.pm: add vpopmail
+ defaults to acct_sql export
+
+2005-04-19 10:25 ivan
+
+ * README.1.5.7: update pre6 -> 7 upgrade instructions too
+
+2005-04-19 02:50 ivan
+
+ * FS/FS/Conf.pm: disable RT_Libs for now
+
+2005-04-19 02:48 ivan
+
+ * httemplate/docs/install.html, httemplate/docs/upgrade10.html,
+ ANNOUNCE.1.5, README.1.5.7.lastbit, SCHEMA_CHANGE: did another
+ upgrade, fixed up the instructions
+
+2005-04-19 02:48 ivan
+
+ * FS/FS/part_pkg.pm: silence an annoying but harmless perl warning
+
+2005-04-18 00:37 ivan
+
+ * FS/FS/cust_main.pm: add some newlines to debugging output, no
+ need to print file and line # so much
+
+2005-04-16 15:26 ivan
+
+ * httemplate/edit/cust_pay.cgi: use money_char on enter payment
+ screen instead of hardcoding $
+
+2005-04-15 13:29 khoff
+
+ * FS/FS/Conf.pm, FS/FS/domain_record.pm, FS/FS/svc_domain.pm,
+ httemplate/view/svc_domain.cgi: Added support for TXT records.
+
+2005-04-14 02:41 ivan
+
+ * FS/FS/cust_bill.pm, conf/invoice_latex, conf/invoice_latex.diff:
+ move invoice_latex templating to Text::Template, with special
+ sauce^W^Wbackwards-compatibility for old templates
+
+2005-04-13 05:31 ivan
+
+ * httemplate/: edit/part_svc.cgi, view/svc_acct.cgi: get rid of
+ some super ancient assumptions about slipip and uid meaning
+ exporting to RADIUS and shell respectively
+
+2005-04-13 05:30 ivan
+
+ * FS/FS/: Record.pm, cust_svc.pm: add nowarn_identical flag to
+ Record.pm and use it in cust_svc svcpart replacement (which is
+ all about the exports anyway)
+
+2005-04-13 03:54 ivan
+
+ * FS/FS/svc_acct.pm: add a line to pod example regarding RADIUS
+ check attributes
+
+2005-04-13 03:32 ivan
+
+ * FS/FS/raddb.pm: add Radius-Operator
+
+2005-04-13 03:16 ivan
+
+ * FS/FS/part_pkg/flat.pm: add explicit use Date::Manip here, oops
+
+2005-04-13 02:55 ivan
+
+ * FS/FS/svc_acct.pm: quiet annoying "use of uninitialized value
+ errors"
+
+2005-04-13 01:16 ivan
+
+ * FS/FS/: cust_main.pm, svc_acct.pm: add skip_fuzzyfiles hack, and
+ add Session-Timeout RADIUS attribute if a svc_acct.seconds value
+ is present
+
+2005-04-12 20:38 ivan
+
+ * CREDITS, FS/FS/part_pkg/flat_comission_cust.pm: fix bug in
+ flat_commission price plan, thanks to Troy Hammonds
+
+2005-04-11 13:13 khoff
+
+ * FS/FS/: Conf.pm, cust_main.pm: Configuration option to override
+ the email address sent to your BOP processor, in case the
+ processor sends a pesky receipt that you don't want your
+ customers getting.
+
+2005-04-11 09:48 khoff
+
+ * FS/FS/: h_cust_svc.pm, h_svc_forward.pm: No need to inflict
+ debugging messages on everyone.
+
+2005-04-10 06:01 ivan
+
+ * httemplate/docs/selfservice.html: add apache snippet to
+ self-service install docs
+
+2005-04-10 03:16 ivan
+
+ * httemplate/search/elements/search.html: add some left and right
+ padding so cells don't run into each other so badly
+
+2005-04-09 18:13 ivan
+
+ * Makefile: fix install-apache for bsd make and reverse
+ accidentally-checked-in (again!) RT_ENABLED = 1
+
+2005-04-07 18:12 ivan
+
+ * htetc/freeside-rt.conf: ugh, don't know why that wasn't working
+ and don't f$&# care
+
+2005-04-07 03:35 ivan
+
+ * Makefile, htetc/freeside-base.conf, htetc/freeside-rt.conf: add
+ install/debian/3.1/INSTALL script and script up some apache
+ automation assuming a conf.d type dir
+
+2005-04-07 02:26 ivan
+
+ * FS/FS/Record.pm, FS/bin/freeside-setup, bin/dbdef-create,
+ bin/fix-sequences: depend on DBIx::DBSchema 0.26 for dbdef-create
+ (for Pg 'public' schema fix) and 0.25 in freeside-setup and
+ Record.pm (for DBD::Pg 1.40 is bunk fix)
+
+2005-04-06 23:08 ivan
+
+ * README.1.5.7.lastbit: and the history tables
+
+2005-04-06 20:28 khoff
+
+ * FS/FS/: h_cust_svc.pm, h_svc_forward.pm, h_svc_www.pm: Using
+ current (non-history) records in place of missing history
+ records.
+
+2005-04-06 18:29 ivan
+
+ * httemplate/docs/upgrade8.html: ancient upgrade fix, oops. thanks
+ Rick Harby <rharby at caarnet.com>
+
+2005-04-06 15:52 ivan
+
+ * FS/FS/svc_forward.pm: and fix the error msg haha
+
+2005-04-06 15:50 ivan
+
+ * FS/FS/svc_forward.pm: looks like a domain part for a
+ literally-specified forward src or dst is required, not optional
+
+2005-04-06 03:38 ivan
+
+ * httemplate/docs/install-rt.html: correct links to some
+ atypically-named CPAN distributions
+
+2005-04-05 17:50 khoff
+
+ * FS/FS/h_Common.pm: $pkey should be the primary key of the real
+ table, not the history table.
+
+2005-04-05 14:33 khoff
+
+ * bin/add-history-records.pl: This doesn't fix the problem. To be
+ continued...
+
+2005-04-02 15:49 ivan
+
+ * bin/slony-setup: must be as Pg superuser
+
+2005-04-02 14:46 ivan
+
+ * README.1.5.7, README.1.5.7.lastbit, FS/FS/cust_bill_pkg.pm,
+ FS/FS/part_svc_router.pm, FS/FS/pkg_svc.pm, FS/FS/rate_detail.pm,
+ FS/FS/reg_code_pkg.pm, FS/FS/type_pkgs.pm, FS/bin/freeside-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade10.html:
+ herding elephants: add primary keys to *all* tables for slony
+
+2005-04-02 12:34 ivan
+
+ * bin/slony-setup: adding quick slony setup script
+
+2005-04-01 14:52 khoff
+
+ * FS/FS/: h_cust_svc.pm, h_svc_acct.pm, h_svc_www.pm: Trap, and
+ attempt to resolve, problems caused by missing history records.
+
+2005-04-01 14:34 khoff
+
+ * FS/FS/Record.pm: so we can pass in a time if we're back-filling
+ history records
+
+2005-03-31 21:59 steve
+
+ * FS/FS/cust_main.pm: fix paybatch parsing to support bop::jettis
+
+2005-03-31 03:41 ivan
+
+ * FS/FS/cust_main.pm: add ability to link services w/order_pkg
+ method in addition to provisioning new ones
+
+2005-03-31 01:07 ivan
+
+ * FS/FS/cust_main_invoice.pm: show illegal email addresses used for
+ invoice destinations
+
+2005-03-30 22:56 khoff
+
+ * bin/add-history-records.pl: Printing insert statements is not
+ necessary
+
+2005-03-30 21:02 khoff
+
+ * httemplate/docs/upgrade10.html: Very annoying typo. >:-)
+
+2005-03-30 19:47 khoff
+
+ * bin/add-history-records.pl: Committing the inserts helps.
+
+2005-03-30 16:53 khoff
+
+ * bin/add-history-records.pl: (Apparently) working version.
+ Updates svc_*, cust_svc, and domain_record history tables.
+
+2005-03-30 16:31 khoff
+
+ * FS/FS/h_svc_forward.pm: Another missing 'u'.
+
+2005-03-30 13:40 khoff
+
+ * FS/FS/h_Common.pm: Dump a call trace if something calls
+ FS::h_Common::sql_h_search without END_TIMESTAMP.
+
+2005-03-30 13:22 khoff
+
+ * FS/FS/cust_svc.pm: Have to pass @_ to FS::svc_www::domain_record
+ in case we're really a FS::h_svc_www object.
+
+2005-03-30 13:09 khoff
+
+ * FS/FS/h_svc_www.pm: typo. 'use', not 'se'.
+
+2005-03-30 12:55 khoff
+
+ * bin/add-history-records.pl: Test script to add pre-history table
+ history records.
+
+2005-03-29 17:37 ivan
+
+ * FS/FS/part_export/cp.pm: according to landel CP no longer
+ supports changing username
+
+2005-03-29 17:32 ivan
+
+ * httemplate/docs/selfservice.html: separate out referring customer
+ info to optional section, add a note on setting the agentnum via
+ templte
+
+2005-03-29 17:18 ivan
+
+ * httemplate/search/cust_pay.cgi: fix ambiguous column error when
+ selecting by credit card, fixes: Bug#1189
+
+2005-03-29 14:41 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm,
+ httemplate/docs/selfservice.html: better self-service debugging,
+ don't point to install.html for suEXEC/setuid in self-service
+ setup docs
+
+2005-03-28 17:40 khoff
+
+ * FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm, httemplate/index.html: A few
+ RT_External fixes.
+
+2005-03-28 17:38 khoff
+
+ * FS/FS/Misc.pm: 'require' not 'use' Fax::Hylafax::Client so it's
+ an optional requirement.
+
+2005-03-27 15:21 ivan
+
+ * FS/FS/cust_main.pm: allow & in bank names
+
+2005-03-27 15:21 ivan
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm: svc_Common / svc_acct
+ child_objects can now set an alternate field for the svcnum, for
+ things like forwards
+
+2005-03-22 20:16 ivan
+
+ * httemplate/docs/install-rt.html, README.1.5.7: new RT requires
+ Tree::Simple too
+
+2005-03-22 18:59 ivan
+
+ * README.1.5.7, httemplate/docs/install-rt.html: add HTML::Scrubber
+ to rt install/upgrade docs
+
+2005-03-22 10:15 ivan
+
+ * httemplate/docs/upgrade10.html: small fix for indices in upgrade
+ instructions, found by s5
+
+2005-03-21 14:13 khoff
+
+ * FS/FS/Conf.pm, FS/FS/Misc.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_invoice.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pkg.pm, FS/FS/svc_acct.pm,
+ FS/FS/ClientAPI/MyAccount.pm, FS/FS/part_export/http.pm,
+ FS/FS/part_export/infostreet.pm,
+ FS/FS/part_export/shellcommands.pm, htetc/handler.pl,
+ httemplate/docs/install.html, httemplate/edit/cust_main.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/misc/fax-invoice.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main/billing.html,
+ httemplate/view/cust_main/tickets.html: Added support for FAX
+ invoice destinations using a HylaFAX server. Faxing plain text
+ invoices is not supported.
+
+2005-03-21 13:55 pbowen
+
+ * FS/FS/Record.pm: Forgot to load up the conf.
+
+2005-03-18 16:12 pbowen
+
+ * FS/FS/: Record.pm, cust_main.pm: Fixed a few things: -PB 1. Fixed
+ a nasty bug that would clear the payinfo if the private key was
+ not available. 2. Set the default module for encrypt/decrypt to
+ be Crypt::OpenSSL::RSA. 3. Added a die and error message so that
+ it doesn't just pass around plaintext if the encryption engine is
+ broken or missing. 4. Added code so that the masked payinfo is
+ handled correctly in the case that it is blank and it cannot be
+ generated (encrypted payinfo)
+
+2005-03-18 11:21 pbowen
+
+ * FS/FS/Conf.pm, FS/FS/Record.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm, FS/bin/freeside-setup,
+ httemplate/docs/upgrade10.html: Added encrypted fields for Credit
+ Cards, etc... - PB
+
+2005-03-18 11:15 pbowen
+
+ * FS/FS/part_pkg.pm: Added a few additional recurrences for domain
+ billing
+
+2005-03-18 08:47 pbowen
+
+ * FS/FS/Record.pm: Fixed a small bug... if replace is called by
+ SUPER, @_ == 1 if it only contains an undef. -PB
+
+2005-03-18 01:58 ivan
+
+ * httemplate/docs/selfservice.html: selfservice uses HTML::Entities
+
+2005-03-18 01:15 ivan
+
+ * FS/FS/cust_main_county.pm: no idea why this is only showing up on
+ freebsd install
+
+2005-03-17 17:00 ivan
+
+ * FS/FS/cust_main_county.pm: sorry it was late
+
+2005-03-17 13:56 khoff
+
+ * FS/FS/cust_bill.pm: Documentation tyop.
+
+2005-03-17 13:45 ivan
+
+ * httemplate/docs/install-rt.html: Freeside side uses MIME-tools
+ now
+
+2005-03-17 13:41 khoff
+
+ * FS/FS/Conf.pm, FS/FS/Misc.pm, FS/FS/cust_bill.pm,
+ httemplate/docs/install.html, httemplate/misc/email-invoice.cgi:
+ Added options invoice_email_pdf and invoice_email_pdf_note.
+ invoice_email_pdf - Attach PDF invoice to emailed plain text
+ invoices. invoice_email_pdf_note - Replace plain text invoice
+ with this note, when attaching a PDF.
+
+2005-03-16 03:31 ivan
+
+ * FS/FS/cust_main_county.pm: fix regionselector for CR in region
+ names for some reason
+
+2005-03-15 15:27 khoff
+
+ * httemplate/misc/payment.cgi: Missing semicolon.
+
+2005-03-13 03:47 ivan
+
+ * FS/FS/cust_main.pm: and store the refund correctly
+
+2005-03-13 03:34 ivan
+
+ * FS/FS/cust_main.pm: when refunding against a specific card
+ payment, use the card details from the payment instead of the
+ customer record
+
+2005-03-13 03:12 ivan
+
+ * FS/FS/cust_main.pm: add some additional debugging to refunds
+
+2005-03-12 08:07 ivan
+
+ * Makefile, httemplate/docs/install-rt.html,
+ rt/etc/RT_SiteConfig.pm, rt/lib/RT/URI/freeside.pm,
+ rt/lib/RT/URI/freeside/Internal.pm,
+ rt/lib/RT/URI/freeside/XMLRPC.pm: popurl(3) won't give us a good
+ freeside base url since RT calls it from multiple directory
+ depths... have to specify explicity, like external integration
+
+2005-03-12 06:35 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.cgi: - bring prepaid
+ support into this century (close: Bug#1124) - finally get rid of
+ fs_signup (everything is in fs_selfservice now) (Bug#413) -
+ organize main menu sysadmin section so it is slightly less
+ confusing
+
+2005-03-12 06:31 ivan
+
+ * httemplate/docs/schema.html, httemplate/docs/selfservice.html,
+ httemplate/docs/upgrade10.html, README.1.5.7, ANNOUNCE.1.5,
+ FS/FS/agent.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ FS/FS/prepay_credit.pm, FS/bin/freeside-setup,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/index.html, httemplate/browse/agent.cgi,
+ httemplate/edit/prepay_credit.cgi,
+ httemplate/search/prepay_credit.html,
+ httemplate/search/elements/search.html, fs_selfservice/DEPLOY,
+ fs_selfservice/FS-SelfService/ieak.template,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/decline.html,
+ httemplate/edit/process/prepay_credit.cgi,
+ httemplate/edit/process/reg_code.cgi,
+ fs_selfservice/FS-SelfService/cgi/map.gif,
+ fs_selfservice/FS-SelfService/cgi/promocode.html,
+ fs_selfservice/FS-SelfService/cgi/regcode.html,
+ fs_selfservice/FS-SelfService/cgi/signup-agentselect.html,
+ fs_selfservice/FS-SelfService/cgi/signup-alternate.html,
+ fs_selfservice/FS-SelfService/cgi/signup-billaddress.html,
+ fs_selfservice/FS-SelfService/cgi/signup-freeoption.html,
+ fs_selfservice/FS-SelfService/cgi/signup-snarf.html,
+ fs_selfservice/FS-SelfService/cgi/signup.cgi,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ fs_selfservice/FS-SelfService/cgi/stateselect.html,
+ fs_selfservice/FS-SelfService/cgi/success.html, htetc/global.asa,
+ htetc/handler.pl: - bring prepaid support into this century
+ (close: Bug#1124) - finally get rid of fs_signup (everything is
+ in fs_selfservice now) (Bug#413) - organize main menu sysadmin
+ section so it is slightly less confusing
+
+2005-03-11 02:35 ivan
+
+ * rt/FREESIDE_MODIFIED: update list of modified files
+
+2005-03-11 02:33 ivan
+
+ * rt/etc/RT_SiteConfig.pm: merging
+
+2005-03-11 02:18 ivan
+
+ * httemplate/docs/install-rt.html: note about RT_External
+
+2005-03-10 17:34 khoff
+
+ * rt/: etc/RT_SiteConfig.pm, lib/RT/URI/freeside.pm,
+ lib/RT/URI/freeside/Internal.pm, lib/RT/URI/freeside/XMLRPC.pm:
+ Reorganized RT->Freeside integration to support Internal (single
+ RT/Freeside database) and XMLRPC interfaces. All the UI stuff is
+ handled the same either way. Integration type is changed by
+ setting $RT::URI::freeside::IntegrationType to either 'Internal'
+ or 'XMLRPC' in your RT_SiteConfig.pm.
+
+2005-03-10 14:49 khoff
+
+ * FS/FS/XMLRPC.pm: Add the ability to do freeside configuration
+ lookups through the XMLRPC interface.
+
+2005-03-10 07:33 ivan
+
+ * README.1.5.7, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: arg
+
+2005-03-10 07:18 ivan
+
+ * README.1.5.7, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: add Frontier::RPC to docs too
+
+2005-03-10 04:06 ivan
+
+ * README.1.5.7, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: add IO-stringy (IO::Scalar) to
+ instructions
+
+2005-03-10 01:56 ivan
+
+ * FS/bin/freeside-daily: fine.
+
+2005-03-10 00:58 ivan
+
+ * httemplate/edit/: part_pkg.cgi, process/part_pkg.cgi: fixes
+ package editing problem with extraneous services showing up,
+ closes: Bug#1170
+
+2005-03-09 00:46 ivan
+
+ * httemplate/docs/install-rt.html: add complete apache config
+ instructions for RT, closes: Bug#1031
+
+2005-03-09 00:18 ivan
+
+ * ANNOUNCE.1.5, httemplate/docs/index.html,
+ httemplate/docs/install-rt.html, httemplate/docs/install.html:
+ preliminary RT docs
+
+2005-03-08 10:37 khoff
+
+ * FS/FS/XMLRPC.pm: Don't require that the method results be
+ FS::Record descendant objects. If they are, we map them to
+ hashrefs. Otherwise we return them verbatim and hope (maybe
+ check) that they're scalars, hashrefs, or arrayrefs.
+
+2005-03-08 10:15 khoff
+
+ * FS/FS/XMLRPC.pm: Minor re-work to allow for pseudo methods, like
+ 'version', and eventually config look-ups (next commit).
+
+2005-03-06 02:15 ivan
+
+ * ANNOUNCE.1.5: d
+
+2005-03-05 19:25 ivan
+
+ * httemplate/search/elements/search.html: specity an explicit EOL
+ for Text::CSV_XS, apparantly sometimes the default is null
+
+2005-03-05 19:22 ivan
+
+ * httemplate/search/elements/search.html: and a slight formatting
+ fix
+
+2005-03-05 19:04 ivan
+
+ * httemplate/search/elements/search.html, README.1.5.7,
+ htetc/global.asa, htetc/handler.pl, httemplate/index.html,
+ httemplate/docs/install.html, httemplate/docs/upgrade10.html,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_bill_event.html,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_main-otaker.cgi,
+ httemplate/search/cust_main-payinfo.html,
+ httemplate/search/cust_main-quickpay.html,
+ httemplate/search/cust_main.cgi,
+ httemplate/search/cust_main.html, httemplate/search/cust_pay.cgi,
+ httemplate/search/cust_pay.html,
+ httemplate/search/cust_pkg_report.cgi,
+ httemplate/search/reg_code.html,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_prepaid_income.html,
+ httemplate/search/report_tax.html,
+ httemplate/search/sqlradius.html,
+ httemplate/search/svc_acct.html,
+ httemplate/search/svc_domain.html, httemplate/search/svc_www.cgi,
+ httemplate/view/cust_bill-pdf.cgi,
+ httemplate/view/cust_bill-ps.cgi: add Excel and CSV download of
+ templated reports and clean up their HTML formatting, closes;
+ Bug#520, Bug#1107
+
+2005-03-04 14:24 ivan
+
+ * httemplate/search/: cust_credit.html, cust_pay.cgi: fix "Column
+ reference "payby" is ambiguous" error when selecting by payment
+ type, fix missing check #s caused by cust_main.payinfo masking
+ cust_pay.payinfo, closes (really this time): Bug#1105
+
+2005-03-04 04:57 ivan
+
+ * httemplate/search/: cust_credit.html, cust_pay.cgi: report
+ correctly even if the customer record has somehow been removed...
+
+2005-03-04 04:34 ivan
+
+ * httemplate/browse/agent.cgi, FS/FS/Record.pm, FS/FS/h_Common.pm,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/report_cust_pay.html, ANNOUNCE.1.5,
+ httemplate/search/elements/search.html: add agent selection to
+ payment and credit reports, add link to agent browse, closes:
+ Bug#1105
+
+2005-03-03 02:25 ivan
+
+ * FS/: MANIFEST, FS/h_Common.pm, FS/h_cust_svc.pm,
+ FS/h_domain_record.pm, FS/h_svc_acct.pm, FS/h_svc_forward.pm,
+ FS/h_svc_www.pm, t/h_domain_record.t: clean up some harmless but
+ scary "Multiple records in scalar search" warnings w/history
+ table searches
+
+2005-03-03 01:05 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: no need for line number in
+ debug output...
+
+2005-03-03 00:58 ivan
+
+ * FS/FS/part_pkg/: voip_sqlradacct.pm: voip: rearrange and compact
+ call details on invoices, so place names get cut off, not
+ anything important
+
+2005-03-03 00:52 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: no need for line number in
+ debug output...
+
+2005-03-03 00:52 ivan
+
+ * FS/FS/cust_bill_pkg_detail.pm: eek, fix foreign key check
+
+2005-03-03 00:45 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: oops, need to use Date::Format
+ for time2str
+
+2005-03-03 00:37 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: voip: add start time for calls
+ to invoice details
+
+2005-03-03 00:15 ivan
+
+ * FS/FS/Record.pm: want a full stack backtrace for this warning
+
+2005-03-03 00:05 ivan
+
+ * FS/FS/part_export/sqlradius.pm: doc
+
+2005-03-02 13:00 khoff
+
+ * httemplate/docs/install.html, eg/xmlrpc-example.pl,
+ httemplate/misc/xmlrpc.cgi, FS/FS/XMLRPC.pm, htetc/handler.pl:
+ Initial version of the xmlrpc interface for freeside.
+
+2005-03-01 16:47 ivan
+
+ * FS/FS/cust_pkg.pm: set setup date on package changes
+
+2005-03-01 16:03 ivan
+
+ * FS/FS/: cust_pkg.pm, part_pkg.pm: prevent bug causing 'Error
+ crediting customer for service remaining:
+ FS::cust_pkg=HASH(0x9958c60)' error on package cancellations
+ where the part_pkg record didn't have a plan, closes: Bug#1153
+
+2005-02-27 03:18 ivan
+
+ * FS/FS/: rate_prefix.pm, part_pkg/voip_sqlradacct.pm: fix VoIP
+ details on invoices, closes: Bug#1096
+
+2005-02-27 03:05 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: fix last bill date stickiness
+ on errors
+
+2005-02-27 02:18 ivan
+
+ * FS/FS/Record.pm: fix replacement in edge case with NULL integer
+ fields in a table without a primary key
+
+2005-02-26 13:29 ivan
+
+ * README.1.5.7, ANNOUNCE.1.5, ANNOUNCE.1.5.0, README.1.5.0pre7:
+ less cracktastic version numbering
+
+2005-02-25 14:14 ivan
+
+ * bin/pg-readonly: try to set the sequences right for modern Pg
+
+2005-02-25 14:07 ivan
+
+ * bin/pg-readonly: adding quick pg-readonly tool
+
+2005-02-25 12:52 ivan
+
+ * FS/FS/part_pkg.pm: small bugfix for options option
+
+2005-02-25 12:21 ivan
+
+ * FS/FS/part_pkg.pm: oops, need to check this in, adding "options"
+ option to part_pkg::insert to specify part_pkg_option records
+
+2005-02-24 06:22 ivan
+
+ * httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi, FS/FS/rate.pm,
+ FS/FS/svc_acct.pm, FS/FS/UI/Web.pm,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/progress-popup.html, FS/FS/export_svc.pm,
+ FS/FS/part_svc.pm: add progressbar to service definition add -
+ duplicate checking can take a while, closes: Bug#1126
+
+2005-02-22 22:43 ivan
+
+ * httemplate/docs/install.html: update docs wrt mysql support
+
+2005-02-22 10:26 khoff
+
+ * httemplate/edit/cust_pkg.cgi: Alphabetize/clean-up package list
+ to make it easier to find packages in large lists.
+
+2005-02-20 00:51 ivan
+
+ * FS/FS/UI/Web.pm: FS/UI/Web.pm did not return a true value
+
+2005-02-20 00:44 ivan
+
+ * FS/FS/UI/Web.pm, httemplate/edit/rate.cgi, CREDITS,
+ httemplate/elements/jsrsServer.html,
+ httemplate/elements/overlibmws.js,
+ httemplate/elements/progress-init.html,
+ httemplate/elements/progress-popup.html,
+ httemplate/misc/progress.html: use a javascript layer instead of
+ a browser popup (popup blockers), really generalize the
+ progressbar code to make it easy to use as a component
+
+2005-02-17 00:44 ivan
+
+ * httemplate/edit/rate.cgi: generalize progressbar code in
+ preparation for using it wherever needed
+
+2005-02-16 17:37 ivan
+
+ * httemplate/edit/process/rate.cgi: generalize progressbar code in
+ preparation for using it wherever needed
+
+2005-02-16 16:11 ivan
+
+ * FS/FS/: rate.pm, UI/Web.pm: generalize progressbar code in
+ preparation for using it wherever needed
+
+2005-02-15 18:53 ivan
+
+ * FS/FS/cust_bill.pm: slightly better error messages for LaTeX
+ problems
+
+2005-02-13 19:49 ivan
+
+ * FS/FS/svc_acct.pm: this should fix uid duplicate checking,
+ closes: Bug#1113
+
+2005-02-11 19:02 ivan
+
+ * httemplate/edit/process/svc_broadband.cgi: transaction not
+ necessary here
+
+2005-02-10 22:44 ivan
+
+ * FS/FS/rate.pm, FS/FS/UI/Web.pm, htetc/global.asa,
+ htetc/handler.pl, httemplate/edit/process/rate.cgi,
+ httemplate/edit/rate.cgi: generalize progressbar code in
+ preparation for using it wherever needed
+
+2005-02-08 17:08 ivan
+
+ * Makefile: more porable syntax for su
+
+2005-02-08 14:33 ivan
+
+ * FS/FS/clientapi_session_field.pm: tyop
+
+2005-02-08 12:22 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/upgrade10.html,
+ README.1.5.0pre7, FS/FS.pm, FS/MANIFEST,
+ FS/FS/ClientAPI_SessionCache.pm, FS/FS/Conf.pm,
+ FS/FS/clientapi_session.pm, FS/FS/clientapi_session_field.pm,
+ FS/FS/ClientAPI/Agent.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm, FS/t/ClientAPI_SessionCache.t,
+ FS/t/clientapi_session.t, FS/t/clientapi_session_field.t,
+ httemplate/docs/install.html, httemplate/docs/schema.html: make
+ self-service session cache module configurable, start framework
+ for in-database session cache
+
+2005-02-05 15:39 ivan
+
+ * FS/FS/: ClientAPI.pm, ClientAPI/Agent.pm, ClientAPI/MyAccount.pm,
+ ClientAPI/Signup.pm, ClientAPI/passwd.pm: remove unnecessary
+ complication from ClientAPI dispatch foo
+
+2005-02-04 17:30 ivan
+
+ * FS/FS/: ClientAPI.pm, ClientAPI/Agent.pm, ClientAPI/MyAccount.pm,
+ ClientAPI/Signup.pm, ClientAPI/passwd.pm: remove unnecessary
+ circular use of FS::ClientAPI, doesn't work with 5.8.[56] +
+ perl??
+
+2005-02-04 06:44 ivan
+
+ * Makefile: freebsd throws a fatal error if it can't stop apache
+ now, bah
+
+2005-02-04 02:38 ivan
+
+ * httemplate/edit/cust_main.cgi: typo
+
+2005-02-02 00:06 ivan
+
+ * FS/FS/UI/: Base.pm, CGI.pm, Gtk.pm, agent.pm: removing old UI
+ experiment
+
+2005-01-29 04:51 ivan
+
+ * ANNOUNCE.1.5.0: gotta do pre7 already
+
+2005-01-29 04:49 ivan
+
+ * httemplate/browse/agent.cgi, FS/FS/part_pkg/flat.pm: oops, last
+ bit for reg codes
+
+2005-01-29 04:34 ivan
+
+ * FS/FS.pm, FS/FS/agent.pm, FS/FS/cust_pkg.pm, FS/FS/part_pkg.pm,
+ FS/FS/reg_code.pm, FS/FS/reg_code_pkg.pm, FS/bin/freeside-setup,
+ FS/t/reg_code.t, FS/t/reg_code_pkg.t, README.1.5.0pre7,
+ FS/MANIFEST, FS/FS/ClientAPI/Signup.pm,
+ httemplate/docs/install.html, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html, httemplate/edit/reg_code.cgi,
+ httemplate/search/reg_code.html,
+ httemplate/edit/process/reg_code.cgi: registration codes
+
+2005-01-27 15:01 ivan
+
+ * httemplate/misc/payment.cgi, FS/FS/ClientAPI/MyAccount.pm:
+ s/defaultcountry/countrydefault/
+
+2005-01-27 14:19 ivan
+
+ * FS/FS/cust_pkg.pm: made a typo applying patch from pbowen
+
+2005-01-27 02:21 ivan
+
+ * httemplate/edit/process/rate.cgi, CREDITS, FS/FS/UID.pm,
+ FS/FS/queue.pm, FS/FS/rate.pm, httemplate/docs/install.html,
+ httemplate/elements/jsrsClient.js, httemplate/misc/progress.html,
+ JSRS-LICENSE, FS/bin/freeside-queued, httemplate/edit/rate.cgi,
+ httemplate/elements/qlib/box.js,
+ httemplate/elements/qlib/boxctrl.js,
+ httemplate/elements/qlib/boxres.js,
+ httemplate/elements/qlib/button.js,
+ httemplate/elements/qlib/buttonres.js,
+ httemplate/elements/qlib/control.js,
+ httemplate/elements/qlib/counter.js,
+ httemplate/elements/qlib/imagelist.js,
+ httemplate/elements/qlib/label.js,
+ httemplate/elements/qlib/messagebox.js,
+ httemplate/elements/qlib/progress.js,
+ httemplate/elements/qlib/sound.js,
+ httemplate/elements/qlib/sprite.js,
+ httemplate/elements/qlib/window.js,
+ httemplate/elements/qlib/wndctrl.js,
+ httemplate/images/progressbar-empty.png,
+ httemplate/images/progressbar-full.png: DHTML progress bar for
+ glacial rate adding and editing, closes: Bug#1100
+
+2005-01-19 13:25 ivan
+
+ * FS/FS/: cust_pkg.pm, part_pkg/flat.pm,
+ part_pkg/flat_comission.pm, part_pkg/flat_comission_cust.pm,
+ part_pkg/flat_comission_pkg.pm, part_pkg/flat_delayed.pm,
+ part_pkg/prorate.pm, part_pkg/sesmon_hour.pm,
+ part_pkg/sesmon_minute.pm, part_pkg/sql_external.pm,
+ part_pkg/sql_generic.pm, part_pkg/sqlradacct_hour.pm,
+ part_pkg/subscription.pm, part_pkg/voip_sqlradacct.pm: credit for
+ unused portion at cancellation, patch from pbowen
+
+2005-01-18 16:57 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm, cust_pkg.pm, part_pkg/flat.pm,
+ part_pkg/sesmon_hour.pm, part_pkg/sesmon_minute.pm,
+ part_pkg/sql_external.pm, part_pkg/sql_generic.pm,
+ part_pkg/sqlradacct_hour.pm, part_pkg/voip_sqlradacct.pm:
+ one-time referral credits
+
+2005-01-07 14:16 ivan
+
+ * httemplate/edit/: rate.cgi, process/rate.cgi: remove separate
+ file for rate processing, causing problems with giant query
+ string, kludge in the non-displayed US-rates, and disable the
+ submit button when pressed
+
+2005-01-06 12:58 ivan
+
+ * FS/FS/Conf.pm: fix description RT_External in ticket_system
+ option
+
+2005-01-06 12:20 ivan
+
+ * FS/FS/: cust_svc.pm, part_export/artera_turbo.pm: just 0-pad the
+ key codes, don't try fancy things with hex and sprintf
+
+2005-01-06 11:48 ivan
+
+ * FS/FS/: cust_svc.pm, part_export/artera_turbo.pm: format artera
+ turbo hex keycodes in uppercase
+
+2005-01-06 11:02 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: add enable_edit flag so
+ serials and key codes can be edited locally
+
+2005-01-05 11:29 ivan
+
+ * FS/FS/: cust_svc.pm, part_export/artera_turbo.pm: key codes are
+ hex!
+
+2005-01-05 02:01 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: add some debugging to
+ voip_sqlradacct
+
+2005-01-04 19:07 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: arg, really get the sub name
+ correct this time
+
+2005-01-04 19:01 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: missing underscore!
+
+2005-01-04 18:47 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: queue status changes rather
+ than run them immediately, always format keycode as %010d,
+ closes: Bug#936, Bug#1060
+
+2005-01-03 10:25 ivan
+
+ * FS/FS/cust_main.pm: eliminate warning: Argument "" isn\'t numeric
+ in numeric gt (>)
+
+2004-12-31 00:47 ivan
+
+ * httemplate/edit/part_pkg.cgi: add ignore_unrateable flag to voip
+ price plan
+
+2004-12-31 00:31 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: add ignore_unrateable flag to
+ voip price plan
+
+2004-12-31 00:24 ivan
+
+ * FS/FS/part_pkg/voip_sqlradacct.pm: add ignore_unrateable flag to
+ voip price plan
+
+2004-12-30 23:43 ivan
+
+ * FS/FS/cust_svc.pm: fix up some bugs in VoIP rating
+
+2004-12-30 16:48 ivan
+
+ * FS/FS/: cust_svc.pm, svc_acct.pm, part_pkg/voip_sqlradacct.pm:
+ fix up some bugs in VoIP rating
+
+2004-12-30 15:47 ivan
+
+ * htetc/global.asa: search the current dir *first*, otherwise some
+ weird Apache::ASP bugs could crop up if things are ever named the
+ same
+
+2004-12-30 01:59 ivan
+
+ * htetc/global.asa: kludge to fix nested includes with
+ Apache::ASP... dunno how much longer i want to support that,
+ should just switch to Mason
+
+2004-12-29 17:41 ivan
+
+ * FS/FS/: cust_svc.pm, domain_record.pm, Report/Table/Monthly.pm:
+ domain_record.pm
+
+2004-12-29 04:01 ivan
+
+ * CREDITS: update credits
+
+2004-12-29 04:00 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/h_cust_svc.pm, FS/FS/Record.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm,
+ FS/FS/h_Common.pm, FS/FS/h_svc_acct.pm, FS/FS/h_svc_broadband.pm,
+ FS/FS/h_svc_domain.pm, FS/FS/h_svc_external.pm,
+ FS/FS/h_svc_forward.pm, FS/FS/h_svc_www.pm, FS/FS/svc_acct.pm,
+ FS/MANIFEST, FS/t/h_Common.t, FS/t/h_cust_svc.t,
+ FS/t/h_svc_acct.t, FS/t/h_svc_broadband.t, FS/t/h_svc_domain.t,
+ FS/t/h_svc_external.t, FS/t/h_svc_forward.t, FS/t/h_svc_www.t,
+ httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/packages.html, ANNOUNCE.1.5.0:
+ historical (immutable) invoice details about services and other
+ history infrastructure
+
+2004-12-28 15:30 ivan
+
+ * FS/FS/part_export/sqlradius.pm: add debug flag to sqlradius
+ export
+
+2004-12-27 02:23 ivan
+
+ * ANNOUNCE.1.5.0: note integrated rt is updated
+
+2004-12-27 02:19 ivan
+
+ * FS/FS/: cust_pay.pm, cust_credit.pm: prevent unsuspension errors
+ from causing payment or credit transactions from finishing
+
+2004-12-27 01:23 ivan
+
+ * FS/FS/cust_main_county.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main/contact.html: UI: rework shipping
+ address javascript, closes: Bug#1085
+
+2004-12-24 15:35 ivan
+
+ * FS/FS/svc_acct.pm: smtp can have uid 0 in addition to root and
+ toor
+
+2004-12-24 15:28 ivan
+
+ * bin/passwd.import: remove $Id$ line
+
+2004-12-24 15:27 ivan
+
+ * bin/passwd.import: import NP and *LK* from shadow file as * (no
+ password)
+
+2004-12-23 18:54 ivan
+
+ * httemplate/edit/: cust_pay.cgi, process/cust_pay.cgi: add ability
+ to set payment date manually
+
+2004-12-23 03:39 ivan
+
+ * httemplate/edit/svc_www.cgi: fix typo that manifests when using
+ svc_www-enable_subdomains config option
+
+2004-12-23 01:15 ivan
+
+ * httemplate/edit/process/part_pkg.cgi: fix bug with custom pricing
+ packages when no primary service is selected
+
+2004-12-23 01:07 ivan
+
+ * FS/FS/part_pkg.pm: better debugging for options passed to insert
+ method
+
+2004-12-23 00:32 ivan
+
+ * README.1.5.0pre7, FS/bin/freeside-setup,
+ httemplate/docs/upgrade10.html: allow NULL zip in some countries
+
+2004-12-23 00:00 ivan
+
+ * README.1.5.0pre7, FS/bin/freeside-setup,
+ httemplate/docs/upgrade10.html: going with 6 digit misnamed "npa"
+ for now
+
+2004-12-23 00:00 ivan
+
+ * bin/: rate-us.import, rate.import: adding rate import scripts
+
+2004-12-22 23:29 ivan
+
+ * FS/FS/rate_region.pm: better short prefix display
+
+2004-12-22 23:28 ivan
+
+ * httemplate/edit/rate.cgi: hide US regions for now, we don't
+ currently need them for rating just invoice/session display and
+ the web pages are so giant they're timing out
+
+2004-12-22 06:40 ivan
+
+ * Makefile: chkconfig ON
+
+2004-12-22 06:09 ivan
+
+ * Makefile: /usr/bin/true on fleabsd
+
+2004-12-22 06:06 ivan
+
+ * httemplate/edit/: REAL_cust_pkg.cgi, process/REAL_cust_pkg.cgi:
+ warn and require confirmation when editing next bill dates to a
+ date in the past, closes; Bug#430
+
+2004-12-22 01:53 ivan
+
+ * FS/bin/freeside-daily: just "vaccum analyze" is fine
+
+2004-12-21 15:19 ivan
+
+ * FS/FS/rate_detail.pm: need to use table modules we call
+ ut_foreign_key on: rate, rate_region
+
+2004-12-20 03:41 ivan
+
+ * rt/FREESIDE_MODIFIED: backport from 3.3-TESTING to fix XSS on
+ ticket lists
+
+2004-12-20 02:13 ivan
+
+ * FS/FS/Misc.pm, FS/FS/cust_main.pm, httemplate/view/cust_bill.cgi:
+ better error messages on email errors
+
+2004-12-20 00:21 ivan
+
+ * Makefile: remove quotes which were causing problems. grr make
+
+2004-12-20 00:16 ivan
+
+ * Makefile: add init script enable command in deb and redhat
+
+2004-12-18 15:32 ivan
+
+ * httemplate/docs/: billing.html, install.html: add info about
+ teTeX and Ghostscript
+
+2004-12-18 02:52 ivan
+
+ * httemplate/index.html: fix spelling
+
+2004-12-13 01:13 ivan
+
+ * FS/FS/Conf.pm, httemplate/index.html: add config option for
+ address2 search, closes: Bug#1022
+
+2004-12-12 10:51 ivan
+
+ * httemplate/view/cust_main/quick-charge.html: fix form action url
+ for template
+
+2004-12-12 00:34 ivan
+
+ * FS/FS/cust_main.pm: fix customer status display for some cases
+ with suspended accounts
+
+2004-12-11 15:16 ivan
+
+ * FS/FS/part_export/acct_sql.pm: finish modification
+
+2004-12-11 14:50 ivan
+
+ * httemplate/docs/install.html: update install doc
+
+2004-12-11 12:41 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/part_bill_event.pm,
+ httemplate/edit/part_bill_event.cgi: add 'send_if_newest' invoice
+ event, closes: Bug#977
+
+2004-12-10 23:50 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/order_pkg.html,
+ httemplate/view/cust_main/packages.html,
+ httemplate/view/cust_main/payment_history.html,
+ httemplate/view/cust_main/quick-charge.html: voiding of echeck
+ payments instead of refunds
+
+2004-12-10 15:51 ivan
+
+ * httemplate/docs/selfservice.html: correct path to selfservice
+
+2004-12-10 14:28 ivan
+
+ * FS/FS/CGI.pm: ui tweak for small customer view - line up billing
+ and service address boxes
+
+2004-12-10 13:25 ivan
+
+ * httemplate/docs/: overview-new.dia, overview-new.png: move the
+ "self-service SSH tunnel" label to avoid ambiguity
+
+2004-12-09 16:51 ivan
+
+ * FS/bin/freeside-setup: promo codes not unique...
+
+2004-12-09 15:14 ivan
+
+ * httemplate/search/cust_main.cgi: typo
+
+2004-12-09 15:05 ivan
+
+ * httemplate/search/cust_main.cgi: typo
+
+2004-12-09 14:26 ivan
+
+ * httemplate/search/cust_main.cgi: UI: dont link to empty lists
+
+2004-12-09 14:23 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: fix links to null custom
+ fields
+
+2004-12-09 14:20 ivan
+
+ * httemplate/search/cust_main.cgi: UI: dont link to empty lists
+
+2004-12-09 14:06 ivan
+
+ * httemplate/search/cust_main.cgi: UI: same font size as the rest
+ of the page
+
+2004-12-09 14:02 ivan
+
+ * httemplate/search/cust_main.cgi: UI: condense ticket subtable a
+ little
+
+2004-12-09 13:59 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: hopefully make links to search
+ for empty value of custom tickets work???
+
+2004-12-09 13:53 ivan
+
+ * httemplate/index.html: remove extra list item for now
+
+2004-12-09 13:33 ivan
+
+ * httemplate/view/cust_main/tickets.html,
+ FS/FS/TicketSystem/RT_External.pm: set default requestor to email
+ invoice addresses
+
+2004-12-09 12:43 ivan
+
+ * FS/bin/freeside-setup: fix some typos noticed by pbowen
+
+2004-12-09 04:46 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: fix new ticket link
+
+2004-12-09 04:18 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: narrow select to avoid pickup
+ up wrong id field
+
+2004-12-09 04:03 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: use new var
+
+2004-12-09 04:01 ivan
+
+ * FS/FS/Conf.pm: typo
+
+2004-12-09 03:57 ivan
+
+ * FS/FS/: Conf.pm, TicketSystem/RT_External.pm: add config value
+ for default queue id
+
+2004-12-09 03:17 ivan
+
+ * httemplate/index.html: finish up for now
+
+2004-12-09 02:07 ivan
+
+ * httemplate/search/cust_main.cgi: deuglify
+
+2004-12-09 01:57 ivan
+
+ * httemplate/search/cust_main.cgi: fix typo
+
+2004-12-09 01:21 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm,
+ httemplate/search/cust_main.cgi,
+ FS/FS/TicketSystem/RT_External.pm, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_main/billing.html,
+ httemplate/view/cust_main/contacts.html,
+ httemplate/view/cust_main/misc.html,
+ httemplate/view/cust_main/tickets.html: more RT integration
+
+2004-12-06 06:42 ivan
+
+ * httemplate/search/cust_main.cgi: fix total link
+
+2004-12-06 06:36 ivan
+
+ * httemplate/search/cust_main.cgi: fix total links and line up
+ custom field columns
+
+2004-12-06 06:15 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: ... and this last piece of sql
+ too
+
+2004-12-06 06:13 ivan
+
+ * FS/FS/TicketSystem/RT_External.pm: and forgot to include the sql
+ for counting tickets with severities...
+
+2004-12-06 06:11 ivan
+
+ * FS/FS/TicketSystem/RT_Internal.pm: fix typo
+
+2004-12-06 06:09 ivan
+
+ * httemplate/search/cust_main.cgi: fix reading custome field values
+
+2004-12-06 06:00 ivan
+
+ * FS/FS/Conf.pm, FS/FS/TicketSystem.pm,
+ FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm, FS/FS/TicketSystem/RT_Libs.pm,
+ httemplate/search/cust_main.cgi: 1st try at adding custom field
+ handling
+
+2004-12-06 00:09 ivan
+
+ * FS/FS/cust_bill.pm: fix harmless "Use of uninitialized value in
+ length" warning in latex escapes
+
+2004-12-05 22:56 ivan
+
+ * httemplate/search/cust_main.cgi: link customers back to tickets!
+
+2004-12-03 15:41 ivan
+
+ * htetc/handler.pl: landing rt 3.2.2
+
+2004-12-03 13:23 ivan
+
+ * README.1.5.0pre7: landing RT 3.2.2
+
+2004-12-03 12:51 ivan
+
+ * rt/: config, config.pld, bin/rt-commit-handler.in,
+ etc/upgrade/2.1.71, sbin/rt-setup-database.in: landing rt 3.2.2
+
+2004-12-03 12:38 ivan
+
+ * rt/: lib/t/05cronsupport.pl.in, lib/RT/Attributes.pm,
+ lib/RT/Attribute_Overlay.pm, lib/RT/Attributes_Overlay.pm,
+ lib/RT/Attribute.pm, lib/RT/I18N/en_malkovich.po,
+ lib/RT/Action/RecordCorrespondence.pm,
+ lib/RT/Action/RecordComment.pm,
+ lib/RT/Interface/Email/Auth/GnuPG.pm,
+ lib/RT/Condition/PriorityChange.pm,
+ lib/RT/Interface/Web/Handler.pm, lib/RT/Search/FromSQL.pm,
+ bin/standalone_httpd.in, etc/schema.Sybase, etc/acl.Sybase,
+ etc/upgrade/3.1.0/acl.Informix, etc/upgrade/3.1.0/acl.Oracle,
+ etc/upgrade/3.1.0/acl.Pg, etc/upgrade/3.1.0/acl.SQLite,
+ etc/upgrade/3.1.0/acl.mysql, etc/upgrade/3.1.0/content,
+ etc/upgrade/3.1.0/schema.Informix,
+ etc/upgrade/3.1.0/schema.Oracle, etc/upgrade/3.1.0/schema.Pg,
+ etc/upgrade/3.1.0/schema.SQLite, etc/upgrade/3.1.0/schema.mysql,
+ etc/upgrade/3.1.15/content, etc/upgrade/3.1.17/content: Initial
+ revision
+
+2004-12-03 12:27 ivan
+
+ * rt/sbin/: rt-setup-database, rt-test-dependencies: remove
+ autogenerated file
+
+2004-12-02 02:18 ivan
+
+ * httemplate/search/cust_main.cgi: fix license boilerplate and
+ search sorting
+
+2004-12-02 01:59 ivan
+
+ * httemplate/images/small-logo.png, rt/FREESIDE_MODIFIED,
+ FS/FS/CGI.pm, FS/FS/Conf.pm, FS/FS/TicketSystem.pm,
+ FS/FS/cust_main.pm, FS/FS/ClientAPI/Agent.pm,
+ httemplate/index.html, FS/FS/TicketSystem/RT_Internal.pm,
+ FS/FS/TicketSystem/RT_Libs.pm, htetc/global.asa,
+ htetc/handler.pl, httemplate/search/cust_main.cgi,
+ rt/lib/RT/Interface/Web_Vendor.pm, rt/lib/RT/URI/freeside.pm:
+ second big RT integration checkin, customer linking/delinking
+ interface
+
+2004-12-01 10:49 ivan
+
+ * FS/FS/cust_pkg.pm: don't check that agent is allowed to purchase
+ the package on changes
+
+2004-11-30 19:35 ivan
+
+ * FS/FS/: part_pkg.pm, part_pkg/flat.pm,
+ part_pkg/flat_comission.pm, part_pkg/flat_comission_cust.pm,
+ part_pkg/flat_comission_pkg.pm, part_pkg/flat_delayed.pm,
+ part_pkg/prorate.pm, part_pkg/sesmon_hour.pm,
+ part_pkg/sesmon_minute.pm, part_pkg/sql_external.pm,
+ part_pkg/sql_generic.pm, part_pkg/sqlradacct_hour.pm,
+ part_pkg/subscription.pm, part_pkg/voip_sqlradacct.pm:
+ creditcard-less promo code signup
+
+2004-11-30 11:55 khoff
+
+ * httemplate/view/svc_broadband.cgi: Got a little trigger happy
+ with the search/replace.
+
+2004-11-29 15:52 khoff
+
+ * httemplate/view/svc_broadband.cgi: Include netmask and gateway in
+ broadband service view.
+
+2004-11-27 11:09 ivan
+
+ * FS/FS/Conf.pm, FS/FS/TicketSystem/RT_External.pm,
+ FS/FS/TicketSystem/RT_Internal.pm, FS/FS/TicketSystem/RT_Libs.pm,
+ httemplate/index.html, rt/FREESIDE_MODIFIED, ANNOUNCE.1.5.0,
+ Makefile, FS/FS.pm, htetc/global.asa, htetc/handler.pl: ticket
+ system integration framework and skin RT
+
+2004-11-26 01:39 ivan
+
+ * FS/FS/: agent_type.pm, cust_credit_refund.pm,
+ cust_main_invoice.pm, nas.pm, part_pop_local.pm,
+ part_svc_column.pm, part_virtual_field.pm, port.pm, queue.pm,
+ queue_arg.pm, router.pm, session.pm, svc_acct_pop.pm,
+ type_pkgs.pm: remove POD VERSION sections
+
+2004-11-26 01:08 ivan
+
+ * FS/FS/Conf.pm: update description for signup_server-realtime to
+ reflect current reality, closes: Bug#575
+
+2004-11-26 00:51 ivan
+
+ * FS/FS/part_pkg.pm, httemplate/edit/process/part_pkg.cgi: move
+ part_pkg transactional stuff from web interface to part_pkg.pm,
+ bumps Bug#18 to 1.5
+
+2004-11-25 22:50 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add password-noampersand and
+ password-noexlamation config files, patch from Stephpen Bechard,
+ closes: Bug#539
+
+2004-11-24 12:54 ivan
+
+ * Makefile: don't enabled RT by default
+
+2004-11-24 10:28 khoff
+
+ * httemplate/: index.html, search/svc_broadband.cgi: Added IP
+ address search.
+
+2004-11-24 02:28 ivan
+
+ * httemplate/edit/process/cust_main.cgi: fix redundant too-early
+ checks which are causing an error
+
+2004-11-24 01:00 ivan
+
+ * Makefile: some RT install updates
+
+2004-11-23 17:30 ivan
+
+ * FS/bin/freeside-daily: fix perms on automated backups
+
+2004-11-23 17:28 ivan
+
+ * httemplate/index.html: add rate plan maintenance to sysadmin
+ section
+
+2004-11-22 10:20 ivan
+
+ * README.1.5.0pre7, FS/FS/ClientAPI/Signup.pm,
+ FS/bin/freeside-setup, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html, FS/FS/Record.pm,
+ FS/FS/cust_pkg.pm, FS/FS/part_pkg.pm,
+ httemplate/edit/cust_main.cgi, httemplate/edit/part_pkg.cgi:
+ promo codes and separate signup addresses for hdn
+
+2004-11-22 03:11 ivan
+
+ * httemplate/search/sqlradius.cgi: Called-Station-ID label
+
+2004-11-21 18:31 ivan
+
+ * httemplate/search/: sqlradius.cgi, sqlradius.html: UI fix on
+ search directions, fix bug preventing show_called_station,
+ hide_ip and hide_data form working
+
+2004-11-20 09:26 ivan
+
+ * FS/FS/cust_svc.pm, FS/FS/rate.pm, FS/FS/rate_detail.pm,
+ FS/FS/rate_prefix.pm, FS/FS/rate_region.pm,
+ FS/t/part_pkg-voip_sqlradacct.t, FS/t/rate.t, FS/t/rate_detail.t,
+ FS/t/rate_prefix.t, FS/t/rate_region.t, ANNOUNCE.1.5.0,
+ README.1.5.0pre7, SCHEMA_CHANGE,
+ FS/FS/part_pkg/voip_sqlradacct.pm, FS/bin/freeside-setup,
+ httemplate/browse/rate.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/rate.cgi, httemplate/edit/rate_region.cgi,
+ httemplate/edit/process/rate.cgi,
+ httemplate/edit/process/rate_region.cgi, FS/FS.pm, FS/MANIFEST,
+ FS/FS/part_export/sqlradius.pm, eg/table_template.pm,
+ htetc/global.asa, htetc/handler.pl,
+ httemplate/search/sqlradius.cgi,
+ httemplate/search/sqlradius.html: first pass at VoIP rating
+
+2004-11-17 05:22 ivan
+
+ * httemplate/edit/part_pkg.cgi: #debugging cruft
+
+2004-11-17 05:19 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix package options to be sticky on
+ clone-ing (customize package)
+
+2004-11-16 06:19 ivan
+
+ * htetc/handler.pl: can't set $p without $cgi
+
+2004-11-16 06:16 ivan
+
+ * htetc/handler.pl: correct package for $r
+
+2004-11-16 06:11 ivan
+
+ * htetc/handler.pl: handle RT NoAuth sections
+
+2004-11-12 21:37 ivan
+
+ * FS/FS/svc_acct.pm: oops, that's better
+
+2004-11-12 21:32 ivan
+
+ * FS/FS/svc_acct.pm: fix problem with dup checking manifesting as
+ Argument isn't numeric errors
+
+2004-11-11 04:18 ivan
+
+ * rt/sbin/rt-setup-database.in: merge in changes to
+ rt-setup-database
+
+2004-11-11 04:12 ivan
+
+ * rt/lib/RT/I18N/: hu.po, da.po: Initial revision
+
+2004-11-09 03:42 ivan
+
+ * httemplate/search/report_tax.cgi: add handling for texas tax
+ exemption and warning that report might not make sense for
+ partial months other than the current one
+
+2004-11-09 03:00 ivan
+
+ * httemplate/search/report_tax.cgi: add handling for texas tax
+ exemption and warning that report might not make sense for
+ partial months other than the current one
+
+2004-11-09 01:31 ivan
+
+ * httemplate/search/report_tax.cgi: parenthesis help alot
+
+2004-11-09 01:13 ivan
+
+ * httemplate/search/report_tax.cgi: fix for correct reporting of
+ generic taxes
+
+2004-11-09 00:14 ivan
+
+ * FS/FS/cust_main.pm: don't generate invoices for COMP customers
+
+2004-11-08 23:23 ivan
+
+ * httemplate/search/report_tax.cgi: fixes to run under the mason
+ strictness
+
+2004-11-08 01:24 ivan
+
+ * FS/FS/part_pkg.pm: bypass plandata warning; we're accessing it on
+ purpose
+
+2004-11-08 01:16 ivan
+
+ * FS/FS/part_pkg.pm: fix bug that could cause mis-billing on
+ upgrades! (new installs ok)
+
+2004-11-07 21:33 ivan
+
+ * bin/rollback: adding in case this is needed again
+
+2004-11-07 14:58 ivan
+
+ * ANNOUNCE.1.5.0, httemplate/docs/install.html: update install
+ documentation for 1.5 HTML::Mason or Apache::ASP install
+
+2004-10-30 17:01 ivan
+
+ * httemplate/search/cust_main-quickpay.html: quick pay shouldnt
+ default to exact search
+
+2004-10-26 05:36 ivan
+
+ * Makefile: 1.5.0pre6!
+
+2004-10-26 05:33 ivan
+
+ * ANNOUNCE.1.5.0, httemplate/docs/billing.html,
+ httemplate/docs/export.html, httemplate/docs/index.html,
+ httemplate/docs/overview-new.dia,
+ httemplate/docs/overview-new.png, httemplate/docs/schema.html,
+ httemplate/docs/selfservice.html: slightly more up-to-date docs
+
+2004-10-26 05:07 ivan
+
+ * FS/FS/part_export.pm: allow an empty exporttype so you can create
+ new objects
+
+2004-10-26 04:51 ivan
+
+ * FS/FS/part_bill_event.pm: last thing for bug#901, 1.5.0pre6 and
+ webdemo!
+
+2004-10-26 04:26 ivan
+
+ * FS/FS/CGI.pm, FS/FS/Conf.pm, FS/FS/Record.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pkg.pm, FS/FS/part_export.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_pkg_option.pm, ANNOUNCE.1.5.0, README.1.5.0pre6,
+ SCHEMA_CHANGE, FS/FS.pm, FS/MANIFEST, FS/bin/freeside-setup,
+ FS/t/part_pkg-flat.t, FS/t/part_pkg-flat_comission.t,
+ FS/t/part_pkg-flat_comission_cust.t,
+ FS/t/part_pkg-flat_comission_pkg.t, FS/t/part_pkg-flat_delayed.t,
+ FS/t/part_pkg-prorate.t, FS/t/part_pkg-sesmon_hour.t,
+ FS/t/part_pkg-sesmon_minute.t, FS/t/part_pkg-sql_external.t,
+ FS/t/part_pkg-sql_generic.t, FS/t/part_pkg-sqlradacct_hour.t,
+ FS/t/part_pkg-subscription.t, FS/t/part_pkg_option.t,
+ httemplate/browse/part_pkg.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html, httemplate/edit/part_pkg.cgi,
+ httemplate/view/cust_main.cgi, FS/FS/part_pkg/flat.pm,
+ FS/FS/part_pkg/flat_comission.pm,
+ FS/FS/part_pkg/flat_comission_cust.pm,
+ FS/FS/part_pkg/flat_comission_pkg.pm,
+ FS/FS/part_pkg/flat_delayed.pm, FS/FS/part_pkg/prorate.pm,
+ FS/FS/part_pkg/sesmon_hour.pm, FS/FS/part_pkg/sesmon_minute.pm,
+ FS/FS/part_pkg/sql_external.pm, FS/FS/part_pkg/sql_generic.pm,
+ FS/FS/part_pkg/sqlradacct_hour.pm,
+ FS/FS/part_pkg/subscription.pm: modular price plans!
+
+2004-10-25 23:33 ivan
+
+ * httemplate/search/report_tax.cgi: and one last case with named
+ and not named taxes in the same region, ack
+
+2004-10-25 23:04 ivan
+
+ * httemplate/search/report_tax.cgi: taxes are hard
+
+2004-10-25 16:47 ivan
+
+ * httemplate/search/report_tax.cgi: hopefully fix tax report for
+ taxclass & named tax edge cases
+
+2004-10-25 16:39 ivan
+
+ * httemplate/search/report_tax.cgi: hopefully fix tax report for
+ taxclass & named tax edge cases
+
+2004-10-25 15:48 ivan
+
+ * httemplate/search/report_tax.cgi: hopefully fix tax report for
+ taxclass & named tax edge cases
+
+2004-10-25 14:35 ivan
+
+ * httemplate/search/report_tax.cgi: fix joins for proper tax
+ reporting
+
+2004-10-23 04:45 ivan
+
+ * conf/: alerter_template, invoice_latexfooter, invoice_template:
+ better
+
+2004-10-23 04:34 ivan
+
+ * conf/: invoice_latexfooter, invoice_latexnotes: update default
+ notes and footer
+
+2004-10-23 03:36 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: add debug flag
+
+2004-10-22 03:31 ivan
+
+ * httemplate/view/cust_main.cgi: add svc_external-skip_manual
+ support to main customer view, for artera turbo
+
+2004-10-22 03:14 ivan
+
+ * FS/FS/part_export/artera_turbo.pm: add option to specify a static
+ aid
+
+2004-10-21 01:54 ivan
+
+ * httemplate/browse/part_pkg.cgi: remove tiny formatting glitch
+
+2004-10-21 01:33 ivan
+
+ * httemplate/browse/part_pkg.cgi: if enabled, show taxclass on
+ package definition browse
+
+2004-10-21 00:07 ivan
+
+ * httemplate/search/report_tax.cgi: fix tax report edge cases when
+ using taxclasses in some regions but not others
+
+2004-10-20 01:28 ivan
+
+ * bin/artera.import: back to id
+
+2004-10-20 01:20 ivan
+
+ * bin/artera.import: locate existing ones by title, warn on errors
+
+2004-10-20 01:16 ivan
+
+ * httemplate/index.html: add external browse
+
+2004-10-20 01:14 ivan
+
+ * bin/artera.import: adding artera import
+
+2004-10-20 01:08 ivan
+
+ * bin/artera.import: adding artera import
+
+2004-10-20 01:07 ivan
+
+ * httemplate/: search/svc_external.cgi, index.html: add
+ svc_external search
+
+2004-10-19 17:44 ivan
+
+ * conf/logo.eps: new logo!
+
+2004-10-19 16:52 ivan
+
+ * conf/logo.eps: new logo!
+
+2004-10-19 04:50 ivan
+
+ * FS/bin/freeside-sqlradius-reset: isn't run with elevated
+ privledges, so -T not necessary
+
+2004-10-19 01:44 ivan
+
+ * Makefile: sleep long enough to be worthwhile
+
+2004-10-18 05:37 ivan
+
+ * httemplate/: index.html, images/mid-logo.png,
+ images/small-logo.png: that's right, a new logo
+
+2004-10-17 07:01 ivan
+
+ * FS/FS/part_export/artera_turbo.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/process_svc_external.html,
+ fs_selfservice/FS-SelfService/cgi/provision_list.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: add artera
+ turbo handling to self-service and reseller interfaces
+
+2004-10-17 02:54 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html: use
+ templated svc_acct provisioner, from agent interface
+
+2004-10-17 02:19 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_svc.pm, bin/populate-msgcat,
+ README.1.5.0pre6, FS/FS/part_export/artera_turbo.pm,
+ httemplate/docs/upgrade10.html, httemplate/view/svc_external.cgi:
+ add options to adjust UI for artera turbo as svc_export
+
+2004-10-16 03:15 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_export/artera_turbo.pm,
+ README.1.5.0pre6, FS/FS/svc_external.pm, FS/bin/freeside-setup,
+ httemplate/docs/upgrade10.html: add artera turbo export
+
+2004-10-12 22:46 ivan
+
+ * Makefile: apachectl sucks ass
+
+2004-10-12 14:59 ivan
+
+ * httemplate/view/svc_acct.cgi: fix edit link! oops
+
+2004-10-11 23:08 ivan
+
+ * httemplate/search/sqlradius.cgi: more info in error message for
+ unknown export type, fix test for sqlradius_withdomain export,
+ woo!
+
+2004-10-11 22:54 ivan
+
+ * Makefile: update apache restart line for local apache
+
+2004-10-09 03:57 ivan
+
+ * httemplate/view/svc_acct.cgi: rearrange things a bit and clean up
+ the RADIUS session data, link to the detail search
+
+2004-10-06 06:39 ivan
+
+ * httemplate/search/: sqlradius.cgi: more formatting updates to
+ RADIUS report
+
+2004-10-06 06:33 ivan
+
+ * httemplate/search/sqlradius.cgi: small formatting updates to
+ RADIUS report
+
+2004-10-06 06:27 ivan
+
+ * httemplate/search/sqlradius.cgi: small formatting updates to
+ RADIUS report
+
+2004-10-06 05:37 ivan
+
+ * FS/FS/part_export/sqlradius.pm: fix big in RADIUS session viewing
+ when using an ignored-accounting export
+
+2004-10-05 09:28 ivan
+
+ * httemplate/search/sqlradius.cgi,
+ httemplate/search/sqlradius.html, FS/FS/cust_svc.pm,
+ FS/FS/part_export/sqlradius.pm, httemplate/index.html,
+ httemplate/elements/header.html, httemplate/view/svc_acct.cgi:
+ RADIUS session viewing
+
+2004-10-05 07:16 ivan
+
+ * httemplate/view/cust_bill.cgi: links to show alternate invoices
+ also
+
+2004-10-05 06:52 ivan
+
+ * httemplate/view/cust_bill.cgi: links to show alternate invoices
+ also
+
+2004-10-05 06:43 ivan
+
+ * httemplate/view/: cust_bill-pdf.cgi, cust_bill-ps.cgi,
+ cust_bill.cgi: links to show alternate invoices also
+
+2004-10-05 06:35 ivan
+
+ * httemplate/view/: cust_bill-pdf.cgi, cust_bill-ps.cgi,
+ cust_bill.cgi: links to show alternate invoices also
+
+2004-10-05 05:17 ivan
+
+ * FS/bin/freeside-selfservice-server: DO open a database connection
+ in the parent process, this cached the $dbdef and speeds things
+ up significantly
+
+2004-10-05 04:38 ivan
+
+ * ANNOUNCE.1.5.0: [no log message]
+
+2004-09-22 04:28 ivan
+
+ * httemplate/search/: cust_bill_event.html,
+ report_cust_credit.html, report_cust_pay.html, report_tax.html:
+ add missing <TR> tags
+
+2004-09-22 04:04 ivan
+
+ * httemplate/elements/: calendar-en.js, calendar-setup.js,
+ calendar-win2k-2.css, calendar.js, calendar_stripped.js: update
+ jscalendar
+
+2004-09-21 00:57 ivan
+
+ * httemplate/view/cust_main.cgi: and for refunds too
+
+2004-09-21 00:50 ivan
+
+ * httemplate/view/cust_main.cgi: better display of echeck
+ payments/refunds/etc.
+
+2004-09-16 06:22 ivan
+
+ * FS/FS/export_svc.pm, httemplate/edit/part_svc.cgi: add uid to
+ mass duplicate checking on export changes, fix bug in new export
+ editing, error message includes the number of duplicate customers
+ also
+
+2004-09-16 00:19 ivan
+
+ * FS/FS/cust_main.pm: allow blank auth for echeck refunds
+
+2004-09-15 18:47 ivan
+
+ * FS/FS/svc_acct.pm: don't re-my var, quiet warning
+
+2004-09-15 08:31 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add option for global username or
+ username@domain uniqueness, closes: Bug#980
+
+2004-09-15 01:57 ivan
+
+ * FS/bin/freeside-selfservice-server: it would help to set the
+ permissions on the lockfile right, so the kids can open it...
+
+2004-09-15 01:45 ivan
+
+ * fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ obtain a new descriptor for the lock in kids, this should fix
+ locking problems
+
+2004-09-15 01:30 ivan
+
+ * FS/bin/freeside-selfservice-server: obtain a new descriptor for
+ the lock in kids, this should fix locking problems
+
+2004-09-14 06:00 ivan
+
+ * httemplate/view/cust_main.cgi, FS/FS/cust_main.pm,
+ httemplate/edit/cust_refund.cgi,
+ httemplate/edit/process/cust_refund.cgi: echeck/ACH refunds
+
+2004-09-13 23:47 ivan
+
+ * FS/bin/freeside-selfservice-server,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ selfservice: - server: don't reconnect again if we've already
+ been signalled to shutdown - server: add kid reaping to shutdown
+ sequence - server: add another optional logging level to response
+ sending - server: acquire write mutex for keepalives
+
+2004-09-09 05:04 ivan
+
+ * FS/FS/part_svc.pm, httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi: rework edit/part_svc.cgi so
+ it doesn't use a separate process/ file, this allows large error
+ messages to be displayed properly
+
+2004-09-06 05:44 ivan
+
+ * FS/bin/freeside-selfservice-server,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ self-service keepalives
+
+2004-09-06 02:44 ivan
+
+ * FS/bin/freeside-selfservice-server: don't open a database
+ connection in the parent process
+
+2004-09-06 02:28 ivan
+
+ * FS/bin/freeside-queued: don't die off even on database failures
+
+2004-09-05 16:21 ivan
+
+ * httemplate/edit/cust_main.cgi: fixed duplicate checking will
+ catch it, but add client-side protection against
+ double-submission also.
+
+2004-09-05 15:42 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/signup.html: fixed duplicate
+ checking will catch it, but add client-side protection against
+ double-submission also.
+
+2004-09-05 02:42 ivan
+
+ * test/dup-test: adding duplicate test
+
+2004-09-05 02:41 ivan
+
+ * FS/FS/svc_acct.pm: acquire a database lock to prevent race
+ conditions in duplicate checking
+
+2004-09-04 03:02 ivan
+
+ * FS/FS/export_svc.pm: first try at duplicate checking on new
+ export associations
+
+2004-08-27 04:33 ivan
+
+ * FS/bin/freeside-sqlradius-reset: oops use @ARGV not @_
+
+2004-08-27 04:16 ivan
+
+ * FS/bin/freeside-sqlradius-reset: add option to specify exports
+
+2004-08-24 05:22 ivan
+
+ * Makefile: small Makefile update
+
+2004-08-24 04:16 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html,
+ fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/agent_logout.html,
+ fs_selfservice/FS-SelfService/cgi/agent_main.html,
+ fs_selfservice/FS-SelfService/cgi/agent_menu.html,
+ fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/agent_provision.html,
+ fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/list_customers.html,
+ fs_selfservice/FS-SelfService/cgi/order_pkg.html,
+ fs_selfservice/FS-SelfService/cgi/provision.html,
+ fs_selfservice/FS-SelfService/cgi/provision_list.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/view_customer.html,
+ FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/ClientAPI/Agent.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ httemplate/search/cust_main.cgi: big update for reseller
+ interface
+
+2004-08-20 01:58 ivan
+
+ * bin/ispman.ldap.import: adding
+
+2004-08-19 09:35 ivan
+
+ * httemplate/search/cust_credit.html: add customer # to credit
+ reports too
+
+2004-08-19 03:53 ivan
+
+ * httemplate/edit/part_svc.cgi: shell field is now a dropdown of
+ legal shells, closes: Bug#118
+
+2004-08-18 17:22 ivan
+
+ * httemplate/search/: cust_pay.cgi, elements/search.html: add
+ customer # to payment reports, add table cell alignment option to
+ general search component
+
+2004-08-17 06:14 ivan
+
+ * FS/FS/cust_main.pm: prevent realtime_bop CVV removal from messing
+ up cust_main records, also don't pollute the original object when
+ used with override options, closes: Bug#982
+
+2004-08-17 00:43 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: passwd.cgi, passwd.html: fix
+ path to passwd.cgi!
+
+2004-08-14 05:26 ivan
+
+ * FS/FS/: export_svc.pm, part_svc.pm, svc_acct.pm: first try at
+ duplicate checking on new export associations
+
+2004-08-11 16:56 ivan
+
+ * FS/FS/cust_bill.pm: fix bank name showing up on invoices
+
+2004-08-09 12:03 ivan
+
+ * init.d/freeside-init: add /usr/local/bin to PATH
+
+2004-08-06 19:49 ivan
+
+ * httemplate/view/cust_main.cgi: disable order package button until
+ a package has been selected
+
+2004-08-05 11:47 ivan
+
+ * FS/FS.pm: fix pod typo
+
+2004-08-02 02:43 ivan
+
+ * httemplate/index.html: fix link to virtual host browse
+
+2004-08-01 17:41 ivan
+
+ * FS/FS/part_export/vpopmail.pm: no maintainer, use
+ shellcommands_withdomain instead
+
+2004-07-30 00:12 ivan
+
+ * FS/FS/svc_Common.pm: set fixed values from an explicitly
+ specified svcpart on replace too
+
+2004-07-29 21:54 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_svc.pm, FS/FS/svc_Common.pm,
+ conf/cust_pkg-change_svcpart,
+ httemplate/edit/process/cust_svc.cgi,
+ httemplate/misc/process/link.cgi, httemplate/view/svc_acct.cgi:
+ svcpart changes now trigger all necessary export actions, manual
+ svcpart changing on svc_acct view, linking changes svcpart if you
+ ask it to, closes: Bug#671, Bug#644
+
+2004-07-29 14:49 ivan
+
+ * FS/bin/freeside-setup: add index on cust_main.refnum, speeds up
+ advertising source list
+
+2004-07-15 15:40 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/delete_svc.html,
+ fs_selfservice/FS-SelfService/cgi/logout.html,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount_menu.html,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/provision.html,
+ fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html,
+ httemplate/view/cust_main.cgi: big update for customer
+ self-service: add provisioning/unprovisioning of purchased
+ services, like fs_selfadmin
+
+2004-07-12 06:51 ivan
+
+ * httemplate/view/cust_main.cgi: DEL out voided payments to
+ distinguish them visually better
+
+2004-07-10 07:46 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix edge case avoiding erronous
+ decline/cancel when customer has a negative balance & purchases
+ stuff with it
+
+2004-07-10 07:30 ivan
+
+ * FS/FS/: cust_svc.pm, part_pkg.pm, ClientAPI/MyAccount.pm: tyop;
+
+2004-07-10 06:30 ivan
+
+ * httemplate/: index.html, search/cust_bill_event.cgi,
+ search/cust_bill_event.html: add calendar to cust_bill_event
+ search page, make ending date default to open-ended like other
+ reports
+
+2004-07-10 06:21 ivan
+
+ * FS/FS/Record.pm: fix FS::Record::qsearch to (hopefully) work as
+ before and cluck loudly when the FS::tablename class isn't
+ loaded, rather than throw exceptions
+
+2004-07-10 06:08 ivan
+
+ * FS/FS/cust_svc.pm: use FS::svc_external so the label method
+ doesn't bomb out in FS::Record::qsearch with Can't locate object
+ method virtual_fields via package FS::svc_external
+
+2004-07-09 04:45 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/agent.cgi: add debugging to
+ agent.cgi, make sure warnings are turned off when parsing
+ templates to avoid too much output to STDERR triggering obscure
+ apache hang bug. thanks dean you rule.
+
+2004-07-09 02:29 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/agent_main.html: new style ;
+ param separator
+
+2004-07-08 04:15 ivan
+
+ * httemplate/edit/process/cust_main.cgi: don't change otaker when
+ just editing account, closes: Bug#921
+
+2004-07-07 09:06 ivan
+
+ * FS/FS/cust_pay.pm: allow payment modification so we can import
+ order_number info
+
+2004-07-06 12:10 ivan
+
+ * FS/FS/Record.pm: better error message for missing tables
+
+2004-07-06 10:27 ivan
+
+ * ANNOUNCE.1.5.0: new features
+
+2004-07-06 10:26 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ httemplate/edit/cust_refund.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_refund.cgi,
+ httemplate/view/cust_main.cgi: payment voiding part deux & credit
+ card refunds!
+
+2004-07-06 07:22 ivan
+
+ * FS/FS/cust_refund.pm: document and check refund reasons
+
+2004-07-06 06:26 ivan
+
+ * README.1.5.0pre1, README.1.5.0pre6, FS/FS.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_pay_void.pm, FS/t/cust_pay_void.t,
+ FS/bin/freeside-setup, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html,
+ httemplate/misc/void-cust_pay.cgi: add cust_pay_void table and
+ payment voiding web ui part one
+
+2004-07-06 01:43 ivan
+
+ * htetc/: global.asa, handler.pl: 0.32 (and then some) released
+
+2004-07-01 06:49 ivan
+
+ * FS/FS/: ClientAPI/MyAccount.pm, cust_main.pm: credit out
+ self-service
+
+2004-07-01 05:45 ivan
+
+ * FS/FS/agent.pm: fix silly bug editing agents
+
+2004-07-01 05:42 ivan
+
+ * FS/FS/Record.pm: show a full stack backtrace if we wind up in the
+ hash method with an empty Hash attribute, wtf?
+
+2004-06-30 11:19 ivan
+
+ * FS/FS/cust_pay.pm, conf/payment_receipt_email: payyment receipts
+ template fixes
+
+2004-06-30 11:12 ivan
+
+ * FS/FS/cust_pay.pm: payyment receipts: pass body has an arrayref,
+ also fill in name filed
+
+2004-06-30 11:01 ivan
+
+ * FS/FS/cust_pay.pm: typo
+
+2004-06-30 10:57 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill_pay.pm, FS/FS/cust_credit_bill.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pay_refund.pm,
+ conf/payment_receipt_email: payment receipts!
+
+2004-06-30 07:33 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ httemplate/misc/payment.cgi: forgot october! closes: Bug#880
+
+2004-06-30 03:02 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-daily: add option to pgp/gpg
+ encrypt scp dumps
+
+2004-06-30 02:56 ivan
+
+ * FS/FS/cust_bill.pm: fix warning message when agent-specific
+ plandata cannot be found
+
+2004-06-29 00:57 ivan
+
+ * httemplate/docs/schema.html: cust_pay_refund
+
+2004-06-28 21:02 ivan
+
+ * ANNOUNCE.1.5.0, README.1.5.0pre6, FS/FS.pm, FS/MANIFEST,
+ FS/FS/cust_bill_pay.pm, FS/FS/cust_credit_bill.pm,
+ FS/FS/cust_credit_refund.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_pay_refund.pm, FS/FS/cust_refund.pm,
+ FS/bin/freeside-setup, httemplate/docs/upgrade10.html,
+ httemplate/view/cust_main.cgi, FS/t/cust_pay_refund.t,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_bill_pay.cgi: add cust_pay_refund
+ table to refund payments
+
+2004-06-25 11:28 ivan
+
+ * FS/FS/cust_main.pm: fix Pg date parsing of expdate and thus
+ paydate_monthyear method and thus bug#862 and i need some sleep
+
+2004-06-25 10:57 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ httemplate/misc/payment.cgi: fix one-time card charging not
+ pulling in exp date?
+
+2004-06-25 10:26 ivan
+
+ * httemplate/misc/payment.cgi: set defaults so as to not change the
+ billing type when entering a one time payment
+
+2004-06-25 03:25 ivan
+
+ * FS/: FS/UID.pm, bin/freeside-queued: ping the database and retry
+ rather before doing anything
+
+2004-06-25 03:16 ivan
+
+ * FS/FS/cust_bill.pm: really fix latex printing when datasrc
+ contains a ;
+
+2004-06-25 03:07 ivan
+
+ * FS/FS/cust_bill.pm: fix latex printing when datasrc contains a ;
+
+2004-06-25 01:44 ivan
+
+ * FS/FS/cust_pkg.pm: move up next bill date on unsuspend
+
+2004-06-22 19:13 ivan
+
+ * FS/FS/cust_bill.pm: forgotten space in typeset invoice credit
+ lines
+
+2004-06-22 18:23 ivan
+
+ * httemplate/docs/upgrade10.html: escape html
+
+2004-06-21 20:12 ivan
+
+ * Makefile: snapshot before schema changes
+
+2004-06-21 20:11 ivan
+
+ * FS/FS.pm: small doc update
+
+2004-06-21 20:10 ivan
+
+ * bin/sqlradius.import: fix attribute importing bugs that borked
+ the passwords
+
+2004-06-21 07:24 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/agent.cgi: fix dup password
+ checking on add'l package order
+
+2004-06-21 06:36 ivan
+
+ * FS/MANIFEST: removing old report from MANIFEST
+
+2004-06-21 05:27 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: fix harmless typo, closes; Bug#872
+
+2004-06-21 03:58 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/agent.cgi: check password match
+ on agent add'l package order
+
+2004-06-21 03:45 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: field name is primary_svc, not
+ primary
+
+2004-06-21 03:26 ivan
+
+ * FS/FS/: Conf.pm, cust_svc.pm, ClientAPI/MyAccount.pm: option to
+ only allow primary users access to the self-service server
+
+2004-06-18 03:28 ivan
+
+ * bin/masonize, httemplate/search/cust_main-otaker.cgi: masonize
+ fix: avoid newline prepend fix from borking indented first <%,
+ fixes customer search by otaker under mason, closes: Bug#830
+
+2004-06-18 03:07 ivan
+
+ * httemplate/docs/install.html: recommend HTML::Mason
+
+2004-06-17 05:32 ivan
+
+ * FS/FS/part_export/: www_shellcommands.pm, apache.pm: add
+ frontpage extensions to www_shellcommands export
+
+2004-06-16 16:59 ivan
+
+ * FS/FS/svc_acct.pm: fix deletion of accounts connected to virtual
+ hosts
+
+2004-06-16 07:07 ivan
+
+ * httemplate/search/report_tax.cgi: update tax report for taxclass
+
+2004-06-15 06:27 ivan
+
+ * fs_passwd/fs_passwd.cgi, fs_passwd/fs_passwd.html,
+ fs_selfservice/FS-SelfService/cgi/passwd.cgi,
+ fs_selfservice/FS-SelfService/cgi/passwd.html: moving passwd cgi
+ to self-service
+
+2004-06-15 03:59 ivan
+
+ * FS/FS/Record.pm: add stack backtrace to fatal problems in virtual
+ field check
+
+2004-06-11 09:44 ivan
+
+ * httemplate/search/report_tax.cgi: fix date range for old perl,
+ count tax exempt and COMP customers correctly
+
+2004-06-11 07:57 ivan
+
+ * httemplate/search/report_tax.cgi: fix to find all customer
+ scorrectly
+
+2004-06-11 07:25 ivan
+
+ * httemplate/search/report_tax.cgi: tax report fix sort
+
+2004-06-11 07:03 ivan
+
+ * FS/FS/Conf.pm, httemplate/index.html: tax report!
+
+2004-06-11 06:44 ivan
+
+ * FS/bin/freeside-tax-report, httemplate/search/report_tax.cgi: tax
+ report!
+
+2004-06-11 00:37 ivan
+
+ * Makefile: add update-selfservice target
+
+2004-06-10 05:58 ivan
+
+ * httemplate/browse/agent.cgi: now available as methods
+
+2004-06-10 05:31 ivan
+
+ * FS/FS/CGI.pm, FS/FS/agent.pm, FS/FS/cust_main.pm,
+ FS/FS/ClientAPI/Agent.pm, FS/FS/ClientAPI/MyAccount.pm,
+ FS/FS/ClientAPI/Signup.pm,
+ fs_selfservice/FS-SelfService/cgi/agent.cgi,
+ fs_selfservice/FS-SelfService/cgi/agent_login.html,
+ fs_selfservice/FS-SelfService/cgi/agent_main.html,
+ fs_selfservice/FS-SelfService/cgi/cvv2.html,
+ fs_selfservice/FS-SelfService/cgi/cvv2.png,
+ fs_selfservice/FS-SelfService/cgi/cvv2_amex.png,
+ fs_selfservice/FS-SelfService/cgi/list_customers.html,
+ fs_selfservice/FS-SelfService/cgi/signup.html,
+ fs_selfservice/FS-SelfService/cgi/view_customer.html,
+ httemplate/browse/agent.cgi, httemplate/search/cust_main.cgi,
+ fs_selfservice/FS-SelfService/SelfService.pm: agent interface
+
+2004-06-10 04:28 ivan
+
+ * httemplate/search/cust_main-otaker.cgi: tyop
+
+2004-06-09 01:59 ivan
+
+ * Makefile: need this entry for myself though!
+
+2004-06-09 00:17 ivan
+
+ * httemplate/docs/install.html: explicitly specify Apache
+ httpd.conf. fear.
+
+2004-06-05 05:01 ivan
+
+ * Makefile: AND set its owner. whew.
+
+2004-06-05 04:55 ivan
+
+ * Makefile: and don't forget to make the dir
+
+2004-06-05 04:47 ivan
+
+ * Makefile: fix path and make var substitution
+
+2004-06-05 04:37 ivan
+
+ * Makefile: oops fix line endings in automated self-service lib
+ install
+
+2004-06-05 04:34 ivan
+
+ * Makefile: automated self-service lib install
+
+2004-06-05 02:34 ivan
+
+ * bin/: sqlradius-norealm.reimport, sqlradius.import,
+ sqlradius.reimport: sqlradius import updates
+
+2004-06-04 20:00 ivan
+
+ * Makefile: self-service installer?
+
+2004-06-03 03:09 ivan
+
+ * httemplate/misc/email-invoice.cgi: also fix agent-specific From:
+ address on "re-email" link
+
+2004-06-03 02:55 ivan
+
+ * FS/FS/cust_bill.pm: fix agent-specific template on "invoice view"
+ screen and "re-email/re-print" links, also fix agent-specific
+ From: address on "re-email" link
+
+2004-06-03 00:00 ivan
+
+ * FS/FS/cust_bill.pm: better error message for non-applicable
+ invoice events
+
+2004-06-02 14:27 ivan
+
+ * fs_selfservice/DEPLOY: simple kludge for testing
+
+2004-06-01 03:56 ivan
+
+ * httemplate/edit/part_bill_event.cgi: html table fix
+
+2004-06-01 03:53 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/edit/part_bill_event.cgi:
+ per-agent invoice_from addresses
+
+2004-06-01 02:23 ivan
+
+ * bin/postfix_courierimap.import: typo in sql
+
+2004-05-31 18:49 ivan
+
+ * FS/FS/cust_bill.pm: typo in error message
+
+2004-05-28 16:26 ivan
+
+ * bin/sqlradius.reimport: adding password/finger correction tool
+ too
+
+2004-05-28 06:48 ivan
+
+ * bin/postfix_courierimap.import: adding
+
+2004-05-28 04:33 ivan
+
+ * httemplate/misc/upload-batch.cgi: not entirely sure why we're
+ checking the filename at all... to catch empty form submissions?
+
+2004-05-28 04:21 ivan
+
+ * httemplate/misc/upload-batch.cgi: better error reporting on
+ unparsable filenames
+
+2004-05-28 03:38 ivan
+
+ * httemplate/search/elements/search.html: show a better message
+ when no results are found
+
+2004-05-28 03:17 ivan
+
+ * httemplate/search/cust_bill.html: correct count statement when
+ searching for individual invoices by #
+
+2004-05-28 01:40 ivan
+
+ * bin/sqlradius.import: really.
+
+2004-05-28 01:37 ivan
+
+ * bin/sqlradius.import: fixed up and working?
+
+2004-05-28 00:02 ivan
+
+ * FS/FS/svc_domain.pm: report value passed for illegal action
+ pseudo-field
+
+2004-05-27 02:30 ivan
+
+ * bin/sqlradius.import: fixup domain svcpart selection
+
+2004-05-27 02:14 ivan
+
+ * bin/sqlradius.import: adding sqlradius.import
+
+2004-05-26 11:59 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/docs/upgrade-1.4.2.html: require
+ the version of File::Temp with the OO interface
+
+2004-05-26 06:07 ivan
+
+ * FS/FS/part_export/acct_sql.pm: fix table name
+
+2004-05-26 06:02 ivan
+
+ * FS/FS/: part_export/acct_sql.pm, svc_acct.pm: update acct_sql
+ export some more to export to alias table also and in general be
+ more configurable
+
+2004-05-26 04:11 ivan
+
+ * FS/FS/cust_bill.pm: comma
+
+2004-05-26 04:11 ivan
+
+ * FS/FS/cust_bill.pm: use File::Temp for filenames and store the
+ temp files in cache.datasrc instead of /tmp
+
+2004-05-26 03:36 ivan
+
+ * FS/FS/cust_bill.pm: better error checking/reporting for latex
+ setup problems
+
+2004-05-26 02:14 ivan
+
+ * FS/FS/part_export/www_shellcommands.pm: default commands now keep
+ web content in user homedirs and link to /var/www
+
+2004-05-19 07:34 ivan
+
+ * FS/FS/part_export/acct_sql.pm: really fixing deletions in
+ acct_sql export
+
+2004-05-19 07:29 ivan
+
+ * FS/FS/part_export/acct_sql.pm: fixing deletions in acct_sql
+ export
+
+2004-05-19 07:22 ivan
+
+ * FS/FS/part_export/acct_sql.pm: fixing acct_sql export
+
+2004-05-19 06:41 ivan
+
+ * FS/: MANIFEST, FS/part_export/acct_sql.pm,
+ t/part_export-acct_sql.t: adding acct_sql export
+
+2004-05-19 05:31 ivan
+
+ * httemplate/edit/part_pkg.cgi: continue making
+ hours/input/output/total display on invoices conditional on there
+ being any charge for overages
+
+2004-05-19 05:30 ivan
+
+ * httemplate/edit/part_pkg.cgi: finish making
+ hours/input/output/total display on invoices conditional on there
+ being any charge for overages
+
+2004-05-19 05:28 ivan
+
+ * httemplate/edit/part_pkg.cgi: make hours/input/output/total
+ display on invoices conditional on there being any charge for
+ overages
+
+2004-05-17 17:20 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: patch from randell lucas for
+ order_pkg to return pkgnum also
+
+2004-05-14 05:25 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/part_bill_event.pm, httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/process/part_bill_event.cgi: add per-agent
+ invoice templates, add per-package suspend invoice events, fix
+ automatic creation of invoice_latex alternate templates
+
+2004-05-12 11:07 ivan
+
+ * bin/sequences.reset: easier this way
+
+2004-05-12 11:02 ivan
+
+ * bin/: sequences.reset, freeside.import: adding
+
+2004-05-12 03:03 ivan
+
+ * Makefile: for native apache installs
+
+2004-05-11 05:01 ivan
+
+ * httemplate/edit/svc_www.cgi: i'm not usually like this.
+
+2004-05-11 04:58 ivan
+
+ * httemplate/edit/svc_www.cgi: this has been an evening of logical
+ negation
+
+2004-05-11 04:54 ivan
+
+ * httemplate/edit/svc_www.cgi: so close
+
+2004-05-11 04:52 ivan
+
+ * httemplate/edit/svc_www.cgi: clean this up a bit, fix
+ svc_www-usersvc_svcpart and hopefully simplified zone select too
+
+2004-05-11 04:22 ivan
+
+ * httemplate/edit/svc_www.cgi: need to pull in $conf
+
+2004-05-11 04:19 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/svc_www.cgi: one reasonable
+ default and one kludge, to improve webhosting UI
+
+2004-05-11 02:50 ivan
+
+ * FS/FS/part_export/www_shellcommands.pm: fix commands
+
+2004-05-10 17:46 ivan
+
+ * httemplate/: index.html, search/svc_www.cgi: vary basic virtual
+ host browse
+
+2004-05-10 16:16 ivan
+
+ * httemplate/docs/upgrade10.html: fix sequences in upgrade docs?
+
+2004-05-10 06:46 ivan
+
+ * FS/FS/part_export/shellcommands_withdomain.pm: fix ISPMan
+ password changing command
+
+2004-05-10 06:17 ivan
+
+ * FS/FS/part_export/shellcommands.pm: fall back to password
+ changing in the case of blank suspension/unsuspension commands,
+ like some exports
+
+2004-05-10 04:10 ivan
+
+ * FS/FS/part_export/postfix.pm, bin/postfix.export: make postfix
+ export commands configrable
+
+2004-05-10 03:01 ivan
+
+ * bin/sendmail.import: properly nested greps
+
+2004-05-10 02:59 ivan
+
+ * bin/sendmail.import: allow for multiple svc_acct svcparts
+
+2004-05-10 02:40 ivan
+
+ * bin/sendmail.import: 5.005!
+
+2004-05-10 02:38 ivan
+
+ * bin/sendmail.import: ach 5.005
+
+2004-05-10 01:38 ivan
+
+ * CREDITS: rt and sql-ledger
+
+2004-05-08 00:46 ivan
+
+ * FS/FS/svc_acct.pm: default finger to first+last
+
+2004-05-06 15:37 ivan
+
+ * httemplate/edit/svc_www.cgi: brainfart
+
+2004-05-06 15:34 ivan
+
+ * httemplate/edit/svc_www.cgi: tyop
+
+2004-05-06 15:29 ivan
+
+ * FS/FS/part_export/apache.pm, bin/apache.export: add option to
+ change the restart command in apache exports
+
+2004-05-06 15:29 ivan
+
+ * FS/FS/svc_acct.pm: protect properly against deleting users linked
+ to virtual web sites
+
+2004-05-06 15:18 ivan
+
+ * httemplate/edit/svc_www.cgi: show service name and
+ fully-qualified address on service add
+
+2004-05-04 11:44 ivan
+
+ * FS/FS/queue.pm: don't truncate job args for display
+
+2004-05-03 08:40 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: 1. order_pkg accepts svcpart +
+ (svc_external: id, title / svc_acct: domain)
+
+2004-05-03 07:32 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/shellcommands.pm,
+ part_export/shellcommands_withdomain.pm: make RADIUS groups
+ available to shellcommands exports
+
+2004-05-01 15:49 ivan
+
+ * httemplate/search/: cust_bill.html, elements/search.html: don't
+ display links to missing customers
+
+2004-05-01 14:40 ivan
+
+ * httemplate/search/cust_bill.html: I'm slow this morning
+
+2004-05-01 14:38 ivan
+
+ * httemplate/search/cust_bill.html: oops tyop
+
+2004-05-01 14:05 ivan
+
+ * httemplate/search/cust_bill.html: handle missing customer records
+ without erroring out
+
+2004-04-30 20:54 ivan
+
+ * htetc/global.asa: very weird 5.005 problem
+
+2004-04-30 14:58 ivan
+
+ * FS/FS/Record.pm: accept empty zips for non-US countries...
+
+2004-04-30 13:22 ivan
+
+ * FS/FS/cust_main.pm: eliminate spurious "multiple records in
+ scalar search" warning
+
+2004-04-30 12:08 ivan
+
+ * httemplate/index.html: 15 day open invoice reports for qis
+
+2004-04-23 06:15 ivan
+
+ * FS/MANIFEST, FS/bin/freeside-cc-receipts-report,
+ FS/bin/freeside-credit-report, httemplate/index.html,
+ httemplate/search/report_cc.cgi,
+ httemplate/search/report_cc.html,
+ httemplate/search/report_credit.cgi,
+ httemplate/search/report_credit.html: add link to new credit
+ report on main menu, remove old obsolete shell-out reports
+
+2004-04-23 05:50 ivan
+
+ * Makefile, htetc/global.asa: fix up includes with Apache::ASP
+
+2004-04-23 05:19 ivan
+
+ * FS/FS/cust_credit.pm, FS/FS/Report/Table/Monthly.pm,
+ httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_credit.html,
+ httemplate/search/cust_pay.cgi,
+ httemplate/search/report_cust_credit.html,
+ httemplate/search/elements/search.html: credit report, add some
+ links to sales/credits/receipts summary, move payment search to
+ template
+
+2004-04-22 19:32 ivan
+
+ * httemplate/: search/cust_bill.html, search/sql.html, index.html,
+ search/elements/search.html: working templated invoice search!
+
+2004-04-22 00:38 ivan
+
+ * httemplate/elements/pager.html: silly pager fix
+
+2004-04-22 00:27 ivan
+
+ * httemplate/docs/: install.html, upgrade10.html: minor doc updates
+
+2004-04-22 00:07 ivan
+
+ * httemplate/search/report_cust_credit.html: initial copy from
+ report_cust_pay.html
+
+2004-04-21 13:52 ivan
+
+ * FS/FS/cust_bill.pm: as an invoice event, emailing/printing
+ problems should be fatal and trigger retry
+
+2004-04-20 18:49 ivan
+
+ * FS/FS/cust_main.pm: respect country default for batch import
+
+2004-04-20 13:24 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: silly bug noticed by matthewd
+
+2004-04-19 18:23 ivan
+
+ * FS/FS/cust_main.pm: accept expiration dates in the same format
+ they are output...
+
+2004-04-19 17:58 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ httemplate/view/cust_main.cgi: add methods for masking credit
+ cards, add payment info modification to self-service
+
+2004-04-13 18:00 ivan
+
+ * rt/FREESIDE_MODIFIED: keeping track of what's been changed to
+ make life easier when importing new upstream versions
+
+2004-04-13 17:48 khoff
+
+ * rt/lib/RT/URI/freeside.pm: Freeside's URI handler for RT3.
+
+2004-04-13 15:30 ivan
+
+ * FS/FS/svc_domain.pm: remove dependancy on Net::Whois that wasn't
+ being used anyway
+
+2004-04-13 13:01 ivan
+
+ * FS/t/part_export-communigate_pro.t: adding forgotten test
+
+2004-04-13 11:27 ivan
+
+ * Makefile: adding comments for fedora
+
+2004-04-10 18:50 ivan
+
+ * httemplate/search/: report_prepaid_income.cgi,
+ report_prepaid_income.html: update unearned revenue report based
+ on feedback from kevin
+
+2004-04-09 15:35 ivan
+
+ * Makefile: thank goodness its friday
+
+2004-04-09 15:34 ivan
+
+ * Makefile: really properly disable RT where not using
+
+2004-04-09 15:33 ivan
+
+ * Makefile: properly disable RT where not using
+
+2004-04-09 15:29 ivan
+
+ * fs_passwd/fs_passwd.cgi: oops, this one too
+
+2004-04-09 15:28 ivan
+
+ * fs_passwd/fs_passwd.html: fs_passwd.cgi
+
+2004-04-08 05:37 ivan
+
+ * Makefile: fix psql command line options for older pg
+
+2004-04-08 05:23 ivan
+
+ * Makefile: fix quotes
+
+2004-04-08 05:07 ivan
+
+ * Makefile: don't enable by default
+
+2004-04-08 05:05 ivan
+
+ * rt/sbin/: rt-setup-database, rt-setup-database.in: remove
+ accidentally doubled lines in usage inst
+
+2004-04-08 05:00 ivan
+
+ * Makefile, htetc/handler.pl, rt/FREESIDE_MODIFIED,
+ rt/etc/RT_SiteConfig.pm, rt/sbin/rt-setup-database,
+ rt/sbin/rt-setup-database.in: beginning of RT integration
+
+2004-04-07 22:53 ivan
+
+ * fs_selfservice/FS-SelfService/Makefile.PL: comment out xmlrpc
+ server until it is ready
+
+2004-04-07 06:12 ivan
+
+ * FS/bin/freeside-reexport: oops, update the usage too
+
+2004-04-07 06:11 ivan
+
+ * FS/bin/freeside-reexport: added options to select username,
+ svcnum, svcpart
+
+2004-04-07 04:39 ivan
+
+ * FS/FS/: Conf.pm, domain_record.pm: automatically update
+ reverse-ARPA records (Bug#462) / recognize SOA records with the
+ fqdn as well as @
+
+2004-04-07 04:04 ivan
+
+ * httemplate/view/svc_domain.cgi: javascript confirm when slaving a
+ domain
+
+2004-04-07 01:34 ivan
+
+ * FS/FS/part_export.pm: argh, the glob itself is tainted under
+ 5.005
+
+2004-04-07 01:07 ivan
+
+ * FS/FS/part_export.pm: make -T happy (under old perl?)
+
+2004-04-05 19:03 ivan
+
+ * FS/FS/part_export/www_shellcommands.pm: update ISPMan commands
+
+2004-04-05 07:05 ivan
+
+ * httemplate/misc/link.cgi, FS/FS/Conf.pm,
+ httemplate/misc/process/link.cgi: add a domain pulldown to
+ svc_acct linking, closes: Bug#277 / prevent "stealing" services
+ with link unless you set legacy_link-steal config option, closes:
+ Bug#321
+
+2004-04-05 04:55 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/view/cust_main.cgi: apply some
+ heuristics to transfer ordering: primaries first, then sorted by
+ quantity
+
+2004-04-05 02:08 ivan
+
+ * htetc/global.asa, htetc/handler.pl, httemplate/docs/install.html,
+ httemplate/docs/upgrade-1.4.2.html, httemplate/misc/whois.cgi,
+ httemplate/view/svc_domain.cgi: add whois functionality
+ internally instead of linking to geektools
+
+2004-04-04 15:20 ivan
+
+ * FS/FS/: Conf.pm, cust_pkg.pm: add cust_pkg-change_svcpart option
+ to optionally allow non-matching svcparts to be moved during
+ package changes, closes: Bug#667
+
+2004-04-04 15:14 ivan
+
+ * httemplate/view/cust_main.cgi: comment out extraneous warning
+
+2004-04-02 16:45 ivan
+
+ * httemplate/search/cust_bill.cgi: UI: stop making things small for
+ no reason
+
+2004-04-02 05:44 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/view/cust_bill-pdf.cgi: remove Pragma:no-cache header,
+ and set Content-Length and Cache-Control for viewing .pdf
+ invoices with IE over SSL.
+ http://support.microsoft.com/default.aspx?scid=kb;en-us;323308
+
+2004-04-02 03:23 ivan
+
+ * httemplate/view/: cust_bill-pdf.cgi, cust_bill.cgi: add a fake
+ .pdf extension to placate some versions of IE. yay IE.
+
+2004-04-01 18:09 ivan
+
+ * bin/bind.export, FS/FS/part_export/bind.pm: add option to set
+ (r)ndc command
+
+2004-04-01 06:50 ivan
+
+ * FS/FS/part_export/www_shellcommands.pm: fix paths to ispman
+ commands
+
+2004-04-01 03:14 ivan
+
+ * httemplate/browse/part_referral.cgi: remove extraneous html
+
+2004-04-01 03:09 ivan
+
+ * httemplate/browse/part_referral.cgi: oops!
+
+2004-04-01 02:56 ivan
+
+ * httemplate/browse/part_referral.cgi: add a yesterday column and a
+ total row, closes: Bug#797
+
+2004-03-31 16:44 ivan
+
+ * FS/: FS/cust_main_county.pm, bin/freeside-setup: get
+ sub-countries from Locale::SubCountry now
+
+2004-03-30 09:13 ivan
+
+ * httemplate/docs/upgrade10.html: little more explanation about
+ editing Pg dumps
+
+2004-03-30 08:43 ivan
+
+ * FS/FS/cust_main.pm: mutex the bill and collect functions
+ per-customer
+
+2004-03-30 01:20 ivan
+
+ * bin/bind.import: add -s and -c flags, add ipv6 default zones to
+ list of ignored zones, add nameservice records to existing
+ domains, update for API change inDNS::ZoneParse 0.84
+
+2004-03-29 13:49 ivan
+
+ * FS/FS/part_export/shellcommands.pm: freebsd pw(1) fixed in 4.10
+ also
+
+2004-03-28 22:03 ivan
+
+ * FS/FS/part_export.pm: fix export_info sub to return an empty
+ hashref instead of undef
+
+2004-03-26 17:05 khoff
+
+ * FS/FS/: Conf.pm, cust_pkg.pm, svc_acct.pm, svc_domain.pm: Cancel
+ services in a particular order to get around certain
+ inter-service dependancies
+
+2004-03-25 20:54 ivan
+
+ * fs_selfservice/FS-SelfService/Makefile.PL: depend on Storable
+ 2.09
+
+2004-03-25 20:46 ivan
+
+ * FS/FS/part_export/router.pm: don't depend on Net::Telnet unless
+ necessary
+
+2004-03-25 03:00 ivan
+
+ * FS/FS/part_export/: domain_shellcommands.pm,
+ www_shellcommands.pm: typo from refactoring
+
+2004-03-25 00:55 ivan
+
+ * FS/FS/part_export/: domain_shellcommands.pm,
+ forward_shellcommands.pm, shellcommands.pm,
+ shellcommands_withdomain.pm, www_shellcommands.pm: first pass at
+ ISPMan integration
+
+2004-03-24 22:42 ivan
+
+ * FS/FS/part_export/shellcommands.pm: freebsd fix will be in 5.3
+ and later only so far, still waiting to hear about 4.10
+
+2004-03-24 07:38 ivan
+
+ * httemplate/edit/part_export.cgi: catch misconfigured exports
+
+2004-03-24 06:28 ivan
+
+ * FS/FS/: part_export.pm, part_export/apache.pm,
+ part_export/bind.pm, part_export/bind_slave.pm,
+ part_export/bsdshell.pm, part_export/communigate_pro.pm,
+ part_export/communigate_pro_singledomain.pm, part_export/cp.pm,
+ part_export/cyrus.pm, part_export/domain_shellcommands.pm,
+ part_export/forward_shellcommands.pm, part_export/http.pm,
+ part_export/infostreet.pm, part_export/ldap.pm,
+ part_export/postfix.pm, part_export/shellcommands.pm,
+ part_export/shellcommands_withdomain.pm, part_export/sqlmail.pm,
+ part_export/sqlradius.pm, part_export/sqlradius_withdomain.pm,
+ part_export/sysvshell.pm, part_export/textradius.pm,
+ part_export/vpopmail.pm, part_export/www_shellcommands.pm: move
+ export info to the modules themselves
+
+2004-03-24 06:23 ivan
+
+ * FS/FS/part_export/router.pm: move export info to the modules
+ themselves
+
+2004-03-24 06:21 ivan
+
+ * FS/: MANIFEST, FS/part_export/passwdfile.pm,
+ t/part_export-passwdfile.t: adding passwdfile export base class
+
+2004-03-24 06:17 ivan
+
+ * eg/export_template.pm: update example export for the new world of
+ export data in themodule files
+
+2004-03-24 01:35 ivan
+
+ * FS/: MANIFEST, t/part_export-communigate_pro_singledomain.t,
+ t/part_export-postfix.t, t/part_export-router.t: add missing
+ compile tests
+
+2004-03-23 11:57 ivan
+
+ * httemplate/view/: cust_main.cgi: small UI fix for unapplied
+ partial credits
+
+2004-03-22 19:36 ivan
+
+ * htetc/global.asa: make Apache::ASP includes work as expected
+
+2004-03-22 19:31 ivan
+
+ * htetc/global.asa: includes fix
+
+2004-03-22 19:29 ivan
+
+ * htetc/global.asa, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: includes with Apache::ASP
+
+2004-03-22 16:06 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/ClientAPI/MyAccount.pm,
+ htetc/global.asa, htetc/handler.pl,
+ httemplate/elements/small_custview.html,
+ httemplate/misc/payment.cgi, httemplate/misc/process/payment.cgi,
+ httemplate/view/cust_main.cgi: one-time credit card and ACH
+ payments (like self-service) closes: Bug#648
+
+2004-03-22 16:06 ivan
+
+ * httemplate/search/report_receivables.cgi: fix sorting of NULL
+ companies differently than empty companies
+
+2004-03-22 11:02 ivan
+
+ * httemplate/elements/header.html, FS/FS/CGI.pm: consistant title
+ size of 6
+
+2004-03-22 09:13 ivan
+
+ * httemplate/: docs/ach.html, edit/cust_main.cgi, images/ach.png:
+ add ACH help graphic
+
+2004-03-22 07:18 ivan
+
+ * FS/FS/CGI.pm: tone down the titles
+
+2004-03-22 07:04 ivan
+
+ * httemplate/edit/cust_main.cgi: s/routing code/routing number/
+
+2004-03-22 04:50 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/make_payment.html: credit card
+ expiration selection now -> 15 years instead of hardcoded
+
+2004-03-22 03:05 ivan
+
+ * httemplate/: edit/process/REAL_cust_pkg.cgi,
+ edit/process/quick-cust_pkg.cgi, misc/cancel-unaudited.cgi,
+ misc/unprovision.cgi, misc/process/link.cgi,
+ search/cust_main.cgi, search/cust_pkg.cgi, view/cust_main.cgi,
+ view/svc_acct.cgi, view/svc_broadband.cgi, view/svc_domain.cgi,
+ view/svc_external.cgi, view/svc_forward.cgi, view/svc_www.cgi:
+ remove everything that links to package view also, closes:
+ Bug#569
+
+2004-03-22 02:36 ivan
+
+ * httemplate/view/cust_main.cgi: further small UI tweaks
+
+2004-03-22 02:16 ivan
+
+ * httemplate/: misc/expire_pkg.cgi, misc/process/expire_pkg.cgi,
+ view/cust_main.cgi: yay! remove package view entirely (closes:
+ Bug#569)
+
+2004-03-21 18:59 ivan
+
+ * httemplate/view/cust_main.cgi: much easier to understand listing
+ of credits/payments that get split up, closes: Bug#773, 762
+
+2004-03-19 04:36 ivan
+
+ * FS/FS/ClientAPI/passwd.pm: use FS::svc_domain explicitly
+
+2004-03-18 14:35 ivan
+
+ * FS/FS/part_export/shellcommands.pm: quote already-crypted
+ passwords to prevent variable substitution
+
+2004-03-18 14:32 ivan
+
+ * FS/FS/part_export/shellcommands.pm: don't re-encrypt password on
+ replace also
+
+2004-03-18 14:00 ivan
+
+ * bin/shadow.reimport: add -b option
+
+2004-03-18 12:58 ivan
+
+ * FS/FS/part_export/shellcommands.pm: don't re-crypt encrypted
+ passwords
+
+2004-03-17 17:46 ivan
+
+ * FS/bin/freeside-selfservice-server,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ require Storable minimum 2.09
+
+2004-03-17 15:16 ivan
+
+ * FS/FS/svc_acct.pm: fixup password checking to understand
+ old-style *SUSPENDED* accounts and not to allow access for * ! !!
+ passwords
+
+2004-03-17 15:08 ivan
+
+ * bin/shadow.reimport: skip root user and anyone with *LK* or NP
+ accounts
+
+2004-03-17 14:55 ivan
+
+ * bin/shadow.reimport: fix multiple svcparts
+
+2004-03-17 14:53 ivan
+
+ * bin/shadow.reimport: allow multiple svcparts
+
+2004-03-17 14:49 ivan
+
+ * bin/shadow.reimport: re-enable prompting
+
+2004-03-17 14:45 ivan
+
+ * bin/shadow.reimport: add -d and -r options
+
+2004-03-17 13:47 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, FS/FS/svc_acct.pm,
+ FS/FS/ClientAPI/passwd.pm, httemplate/docs/install.html,
+ httemplate/docs/upgrade-1.4.2.html: proper self-service login
+ supporting plaintext, crypt and MD5 passwords
+
+2004-03-16 12:41 ivan
+
+ * Makefile: rt-setup-database, not rt-initialize-database
+
+2004-03-15 23:06 ivan
+
+ * Makefile, rt/config.layout.in: masonstatedir can't be configured
+ form ./configure either, must use layout
+
+2004-03-15 23:04 ivan
+
+ * Makefile: and finally, run the ./configure
+
+2004-03-15 23:03 ivan
+
+ * Makefile: use a proper delimter
+
+2004-03-15 23:00 ivan
+
+ * Makefile: transposition
+
+2004-03-15 22:59 ivan
+
+ * Makefile: autogenerate rt/config.layout file
+
+2004-03-15 22:58 ivan
+
+ * rt/: FREESIDE_MODIFIED, config.layout, config.layout.in:
+ config.layout needs to be generated
+
+2004-03-15 22:45 ivan
+
+ * Makefile: initial RT targets
+
+2004-03-15 22:43 ivan
+
+ * rt/: FREESIDE_MODIFIED, sbin/rt-setup-database: initial
+ (hopefully rather unobtrusive) patch
+
+2004-03-15 20:36 ivan
+
+ * FS/FS/cust_svc.pm: recognize DBD::mysqlPP
+
+2004-03-15 16:34 ivan
+
+ * Makefile: dont want to error out here
+
+2004-03-12 17:27 ivan
+
+ * Makefile: really add the necessary backslashes this time, fix
+ s/// delimteres
+
+2004-03-12 16:53 ivan
+
+ * Makefile: oops
+
+2004-03-12 16:50 ivan
+
+ * Makefile, htetc/handler.pl: automatically set Mason comp_root in
+ Makefile
+
+2004-03-12 16:16 ivan
+
+ * Makefile: fix suse document root
+
+2004-03-12 04:39 ivan
+
+ * FS/FS/Conf.pm, httemplate/misc/unapply-cust_credit.cgi,
+ httemplate/view/cust_main.cgi: add unapplycredits configuration
+ option
+
+2004-03-12 04:11 ivan
+
+ * httemplate/graph/money_time.cgi: fix title
+
+2004-03-12 04:10 ivan
+
+ * httemplate/docs/upgrade10.html: add history tables to field
+ change upgrade instructions, add hints for pre-5.6 perl, add
+ index on cust_pay._date
+
+2004-03-12 02:22 ivan
+
+ * httemplate/index.html: add badly-named new report
+
+2004-03-12 02:19 ivan
+
+ * httemplate/docs/upgrade-1.4.2.html: few more 1.4.2 upgrade hints
+
+2004-03-12 00:56 ivan
+
+ * FS/FS/Report/Table/Monthly.pm: don't run my local expenses kludge
+ by default; horrible performance
+
+2004-03-12 00:17 ivan
+
+ * FS/FS/cust_main.pm: emaildecline-exclude skips any errors that
+ contain the strings now, not just match exactly
+
+2004-03-11 21:58 ivan
+
+ * httemplate/docs/upgrade10.html: document trouble schema changes
+ backported to 1.4.2
+
+2004-03-11 21:49 ivan
+
+ * httemplate/docs/upgrade10.html: remove comment
+
+2004-03-11 13:35 ivan
+
+ * httemplate/docs/upgrade10.html: add info for ancient Pg versions
+
+2004-03-11 13:19 ivan
+
+ * conf/logo.eps: oops, wrong logo
+
+2004-03-11 13:07 ivan
+
+ * conf/: invoice_latex, logo.eps: add typeset logo to conf dir and
+ point invoice_latex at it by default
+
+2004-03-11 00:54 ivan
+
+ * FS/bin/freeside-selfservice-server: turn down logging level
+
+2004-03-10 23:33 ivan
+
+ * fs_passwd/: fs_passwd, fs_passwd.cgi, fs_passwd_server,
+ fs_passwdd: update fs_passwd stuff as wrappers around
+ self-service
+
+2004-03-10 20:17 ivan
+
+ * FS/MANIFEST: incorrect listing in MANIFEST
+
+2004-03-10 18:03 ivan
+
+ * rt/lib/RT/: I18N/it.po, Interface/REST.pm: Initial revision
+
+2004-03-10 17:59 ivan
+
+ * rt/: README.Oracle, UPGRADING, bin/rt.in,
+ docs/rt3-schema-relationships.dot, etc/acl.Informix,
+ etc/drop.Informix, etc/drop.Oracle, etc/schema.Informix,
+ lib/RT/StyleGuide.pod: Initial revision
+
+2004-03-10 17:05 ivan
+
+ * Makefile: update for suse
+
+2004-03-10 14:27 khoff
+
+ * httemplate/edit/svc_broadband.cgi: Excluded virtual fields
+ weren't being properly masked on errors. The router/block select
+ box wasn't being generated on errors.
+
+2004-03-10 11:06 khoff
+
+ * httemplate/browse/router.cgi: Added hide/show customer router
+ link.
+
+2004-03-09 18:37 khoff
+
+ * httemplate/browse/router.cgi: UI cleanup.
+
+2004-03-05 16:57 ivan
+
+ * httemplate/docs/upgrade10.html: doc
+
+2004-03-05 06:34 ivan
+
+ * FS/MANIFEST, FS/FS/Report.pm, FS/FS/Report/Table.pm,
+ FS/FS/Report/Table/Monthly.pm, FS/t/Report-Table-Monthly.t,
+ FS/t/Report-Table.t, FS/t/Report.t, htetc/global.asa,
+ htetc/handler.pl, httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi: beginning of OO reporting
+ interface, create acadia-requested crosstab reports
+
+2004-03-04 21:59 ivan
+
+ * FS/bin/freeside-daily: fix -v
+
+2004-03-03 08:32 ivan
+
+ * bin/create-fetchmailrc: fix for case where no .fetchmailrc should
+ be create and fetchmail should not be run
+
+2004-03-03 05:42 ivan
+
+ * FS/FS/: cust_main.pm, cust_pkg.pm, queue.pm, svc_Common.pm,
+ svc_acct.pm, svc_broadband.pm, svc_domain.pm, svc_external.pm,
+ svc_forward.pm, svc_www.pm, ClientAPI/Signup.pm: fix welcome
+ emails being sent to signup server declined accounts, closes:
+ Bug#743
+
+2004-03-02 22:20 ivan
+
+ * httemplate/edit/svc_forward.cgi: grey out inactive text boxes as
+ well as disable them (IE doesn't grey out disabled text dialogs)
+
+2004-02-28 15:06 ivan
+
+ * httemplate/docs/ieak.html: for now
+
+2004-02-28 14:57 ivan
+
+ * Makefile: s/cleanwhisker/pouncequick/
+
+2004-02-28 14:49 ivan
+
+ * FS/t/acct_snarf.t: adding
+
+2004-02-28 14:48 ivan
+
+ * FS/FS/cust_pkg.pm: minor fixes
+
+2004-02-28 14:47 ivan
+
+ * CREDITS: credit where the typeset invoices came from!
+
+2004-02-28 14:43 ivan
+
+ * htetc/global.asa, httemplate/docs/install.html,
+ httemplate/docs/upgrade-1.4.2.html: Apache::ASP 2.55 required
+
+2004-02-28 14:40 ivan
+
+ * httemplate/edit/process/cust_main_county-collapse.cgi: style
+
+2004-02-28 14:40 ivan
+
+ * httemplate/edit/process/cust_main_county.cgi: new setuptax and
+ recurtax fields
+
+2004-02-28 14:26 ivan
+
+ * FS/FS/Record.pm: depend on DBIx::DBSchema 0.23 and thus DBD::Pg
+ 1.32, finally closes Bug#639
+
+2004-02-27 13:21 khoff
+
+ * FS/FS/svc_broadband.pm: When next_free_addr returned undef, we
+ were trying to call addr on an undefined object. That's not
+ good.
+
+2004-02-26 11:21 ivan
+
+ * httemplate/view/svc_forward.cgi: correct cancel link
+ s/account/mail forward/
+
+2004-02-25 20:01 ivan
+
+ * httemplate/edit/svc_forward.cgi: double quotes are not ASP
+
+2004-02-25 19:32 ivan
+
+ * FS/FS/Record.pm: allow replace with no arguments
+
+2004-02-25 02:37 ivan
+
+ * httemplate/edit/svc_forward.cgi, FS/FS/cust_svc.pm: fix up
+ forward editing for new svc_forward.src field
+
+2004-02-24 21:11 ivan
+
+ * FS/FS/CGI.pm: kludge around it completely
+
+2004-02-24 21:04 ivan
+
+ * FS/FS/CGI.pm: third time's the charm
+
+2004-02-24 21:00 ivan
+
+ * FS/FS/CGI.pm: oops, not the right way to blank them
+
+2004-02-24 20:56 ivan
+
+ * FS/FS/CGI.pm: query strings get passed through sometimes?
+
+2004-02-24 19:50 ivan
+
+ * httemplate/docs/install.html: formatting
+
+2004-02-23 00:12 ivan
+
+ * FS/FS/: svc_acct.pm, part_export.pm, svc_Common.pm: implement
+ fallback suspension code
+
+2004-02-13 10:58 ivan
+
+ * bin/postfix.export: postfix export
+
+2004-02-13 06:04 ivan
+
+ * FS/FS/part_export.pm, bin/postfix.export: add postfix export
+
+2004-02-13 05:53 ivan
+
+ * httemplate/edit/part_svc.cgi: typo
+
+2004-02-13 04:27 ivan
+
+ * FS/FS/part_export/postfix.pm: adding postfix export
+
+2004-02-13 03:47 ivan
+
+ * bin/sendmail.import: it lives!
+
+2004-02-13 03:44 ivan
+
+ * httemplate/docs/upgrade10.html: workaround for older Pg
+
+2004-02-13 03:28 ivan
+
+ * httemplate/: index.html, search/svc_forward.cgi,
+ view/svc_forward.cgi: add mail alias browse to main menu and fix
+ mail alias view to recognize new schema also
+
+2004-02-13 02:57 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/upgrade10.html: continue
+ adding svc_forward.src: make svc_forward.srcsvc nullable
+
+2004-02-13 02:35 ivan
+
+ * FS/FS/svc_forward.pm, FS/bin/freeside-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade10.html: add
+ svc_forward.src
+
+2004-02-13 00:02 ivan
+
+ * bin/sendmail.import: initial import
+
+2004-02-12 20:01 ivan
+
+ * FS/FS/part_export/: router.pm, domain_shellcommands.pm,
+ forward_shellcommands.pm, shellcommands.pm, www_shellcommands.pm:
+ depend on Net::SSH 0.08 for non-blocking STDERR read
+
+2004-02-12 02:44 ivan
+
+ * bin/bind.import: fix usage msg
+
+2004-02-11 22:31 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/misc/email-invoice.cgi,
+ httemplate/misc/print-invoice.cgi, httemplate/view/cust_bill.cgi:
+ re-email invoice, closes: bug#526 and have print and email
+ invoice links redirect back to top of customer view page instead
+ of #history tag
+
+2004-02-07 14:13 ivan
+
+ * FS/FS/part_export.pm: add link to FreeBSD patch for pw(1) problem
+
+2004-02-07 00:24 ivan
+
+ * FS/FS/: cust_svc.pm, part_export.pm: add ignore_accounting flag
+ to sqlradius and sqlradius_withdomain exports
+
+2004-02-05 17:00 ivan
+
+ * FS/FS/Conf.pm: add "Net 0" invoice_default_terms
+
+2004-02-02 16:19 ivan
+
+ * bin/shadow.reimport: adding shadow.reimport
+
+2004-02-01 01:29 ivan
+
+ * Makefile: use install to make all components of FREESIDE_CONF dir
+
+2004-01-30 22:33 ivan
+
+ * FS/FS/part_pkg.pm, FS/FS/pkg_svc.pm,
+ httemplate/browse/part_pkg.cgi, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi, FS/bin/freeside-setup,
+ htetc/global.asa, htetc/handler.pl, httemplate/docs/schema.html,
+ httemplate/docs/upgrade-1.4.2.html,
+ httemplate/docs/upgrade10.html: add pkg_svc.primary_svc flag to
+ enable an explicit first package flag
+
+2004-01-30 22:20 ivan
+
+ * FS/FS/Record.pm: add ut_snumber, fix replacement of records with
+ empty values in non-primary-keyed tables
+
+2004-01-30 12:40 ivan
+
+ * httemplate/docs/upgrade-1.4.2.html: not appropriate
+
+2004-01-29 19:58 ivan
+
+ * FS/FS/part_export.pm: add default freebsd and linux
+ suspension/unsuspension commands, work around FreeBSD pw(1) lack
+ of locking, and don't prepend "*SUSPENDED* " to suspend an
+ account anymore
+
+2004-01-29 19:05 ivan
+
+ * FS/FS/svc_acct.pm: don't prepend *SUSPENDED* to passwords to
+ suspend an account
+
+2004-01-29 17:27 ivan
+
+ * Makefile: multiple self-service machines aren't enclosed in
+ quotes
+
+2004-01-29 17:10 ivan
+
+ * Makefile, FS/FS/ClientAPI/MyAccount.pm,
+ FS/bin/freeside-selfservice-server, init.d/freeside-init: add
+ support for running selfservice server against multiple machines
+
+2004-01-27 17:45 ivan
+
+ * httemplate/search/svc_acct.cgi: fix link from service definition
+ browse to services
+
+2004-01-27 17:39 ivan
+
+ * httemplate/: index.html, browse/part_svc.cgi,
+ search/svc_acct.cgi, search/svc_domain.cgi: add service
+ definition browse by number of active services
+
+2004-01-23 03:20 ivan
+
+ * httemplate/: index.html, search/report_prepaid_income.cgi,
+ search/report_prepaid_income.html: add prepaid income to main
+ menu and allow arbitrary dates
+
+2004-01-23 01:04 ivan
+
+ * httemplate/search/report_prepaid_income.cgi: tidy up look
+
+2004-01-23 00:58 ivan
+
+ * httemplate/search/report_prepaid_income.cgi: oops
+
+2004-01-23 00:55 ivan
+
+ * httemplate/search/report_prepaid_income.cgi: first try at prepaid
+ income report
+
+2004-01-22 19:22 ivan
+
+ * FS/FS/cust_bill.pm: undo debugging change
+
+2004-01-22 19:21 ivan
+
+ * FS/FS/cust_bill.pm: consolidate large numbers of accounts to
+ avoid gigantic line items
+
+2004-01-22 19:01 ivan
+
+ * httemplate/edit/cust_main.cgi: and the same for ship_
+
+2004-01-22 18:53 ivan
+
+ * httemplate/edit/cust_main.cgi: fix state default and set min year
+ to this year, patch from <matthewd>, thanks!
+
+2004-01-22 16:49 ivan
+
+ * debian/control: update ideas about package splitup
+
+2004-01-21 16:21 ivan
+
+ * FS/FS/cust_main.pm: fix cancel method
+
+2004-01-21 16:11 ivan
+
+ * httemplate/misc/cust_main-cancel.cgi: report cancellation errors
+ properly
+
+2004-01-21 15:45 ivan
+
+ * httemplate/misc/dump.cgi: fix newline problem in dump
+
+2004-01-21 14:10 ivan
+
+ * FS/FS/cust_bill.pm: clean up all temp files!
+
+2004-01-21 14:00 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, conf/invoice_latex,
+ conf/invoice_latexsmallfooter: fix multi-page typeset invoices
+
+2004-01-20 16:04 ivan
+
+ * httemplate/search/: cust_bill_event.cgi, cust_pay.cgi,
+ cust_pkg.cgi: don't include the _next_ day, just the full ending
+ day
+
+2004-01-20 12:30 ivan
+
+ * httemplate/: index.html, misc/dump.cgi: add database dump from
+ web interface
+
+2004-01-19 15:21 ivan
+
+ * httemplate/docs/upgrade-1.4.2.html: typeset invoice doc update
+
+2004-01-18 13:03 ivan
+
+ * FS/FS/Record.pm: revert bind_param change _again_. passing not
+ as a hashref causes too many other problems. please leave as is.
+ DBD::Pg needs to be fixed. 1.31 is a lost cause.
+
+2004-01-16 13:45 ivan
+
+ * FS/FS/cust_bill.pm: apply variable substitutions in latex notes
+ also
+
+2004-01-12 12:52 ivan
+
+ * httemplate/docs/upgrade10.html: add IPC::ShareLite and
+ Locale::SubCountry
+
+2004-01-12 12:52 ivan
+
+ * httemplate/docs/upgrade-1.4.2.html: add IPC::ShareLite
+
+2004-01-12 12:40 khoff
+
+ * httemplate/edit/part_virtual_field.cgi: Lists are just better
+ sorted.
+
+2004-01-12 12:34 khoff
+
+ * httemplate/browse/part_virtual_field.cgi: $pvf isn't a global.
+
+2004-01-11 16:03 ivan
+
+ * httemplate/view/cust_bill.cgi: only display "view typeset
+ invoice" when there is an invoice_latex template
+
+2004-01-11 15:59 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/view/cust_bill.cgi: typeset
+ invoice view in web UI uses pdf instead of postscript, closes
+ Bug#614
+
+2004-01-11 15:38 ivan
+
+ * httemplate/view/cust_bill-pdf.cgi: fix for pdf
+
+2004-01-11 15:37 ivan
+
+ * httemplate/view/cust_bill-pdf.cgi: adding
+
+2004-01-10 03:17 ivan
+
+ * Makefile: workaround stubborn shells globbing [a-z] to include
+ CVS/
+
+2004-01-09 20:19 ivan
+
+ * FS/FS/cust_main.pm: don't require payname for DCHK either
+
+2004-01-09 19:50 ivan
+
+ * httemplate/edit/part_bill_event.cgi: fix UI: displaying "Add" on
+ invoice event edits
+
+2004-01-09 14:46 ivan
+
+ * httemplate/search/report_receivables.cgi: add customer number
+
+2004-01-09 14:39 ivan
+
+ * FS/FS/cust_credit.pm, httemplate/view/cust_main.cgi: credit
+ deletions: need to use Date::Format and FS::Misc qw(send_email)
+ in cust_credit.pm, need a link to delete unapplied credits too
+
+2004-01-09 14:15 ivan
+
+ * FS/FS/Conf.pm: correct tyop
+
+2004-01-09 14:11 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_credit.pm, FS/FS/cust_credit_bill.pm,
+ httemplate/view/cust_main.cgi,
+ httemplate/misc/delete-cust_credit.cgi: add deletecredits config
+ value to enable deletion of credits
+
+2004-01-09 13:09 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: adding system_usernames config
+ value
+
+2004-01-06 22:10 khoff
+
+ * httemplate/edit/part_pkg.cgi: Added billing plan sql_external for
+ billing services in an external sql database.
+
+2004-01-05 12:24 ivan
+
+ * httemplate/index.html: fix other packages by next bill date link
+
+2004-01-03 19:54 ivan
+
+ * httemplate/docs/signup.html: remove obsolete netscape CCK
+ supportsignup.cgi
+
+ depend on HTTP::BrowserDetect directly instead of via deprecated
+ HTTP::Headers::UserAgent (closes: Bug#578)
+
+2004-01-03 00:42 ivan
+
+ * httemplate/browse/agent.cgi: fix heading colspan when there is no
+ agent.disabled column
+
+2004-01-01 12:40 ivan
+
+ * FS/FS/cust_bill.pm: escape stuff from latex
+
+2003-12-29 22:02 khoff
+
+ * FS/FS/Record.pm: Pass type as scalar instead of a hashref to work
+ around a bug in DBD::Pg version 1.31.
+
+2003-12-27 00:23 ivan
+
+ * httemplate/: index.html, search/cust_pkg.cgi,
+ search/cust_pkg.html, search/cust_pkg_report.cgi: package reports
+ by agent
+
+2003-12-24 10:18 khoff
+
+ * FS/bin/freeside-setup: tyop
+
+2003-12-22 18:36 ivan
+
+ * FS/MANIFEST, FS/FS/part_export.pm, FS/t/svc_broadband.t,
+ FS/t/svc_external.t, htetc/global.asa, htetc/handler.pl,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_external.cgi,
+ httemplate/edit/process/svc_external.cgi,
+ httemplate/view/svc_external.cgi: add svc_external
+
+2003-12-22 17:46 ivan
+
+ * FS/FS.pm, FS/FS/svc_external.pm, FS/bin/freeside-setup,
+ httemplate/docs/upgrade10.html, FS/FS/cust_svc.pm: add
+ svc_external
+
+2003-12-22 17:10 ivan
+
+ * eg/table_template-svc.pm: update svc template for 1.5
+
+2003-12-22 17:06 ivan
+
+ * eg/table_template-svc.pm: fix path to svc_Common in example table
+
+2003-12-22 16:51 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: add edit_info to
+ selfservice API
+
+2003-12-22 16:41 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: allow_negative_charges config
+ option
+
+2003-12-22 13:37 ivan
+
+ * httemplate/browse/cust_pay_batch.cgi: add total cards & amount to
+ pending batch screen
+
+2003-12-22 13:22 ivan
+
+ * FS/FS/cust_main.pm: default to the whole-country tax rate if
+ states aren't defined
+
+2003-12-22 13:18 ivan
+
+ * FS/FS/cust_bill.pm: quiet warning
+
+2003-12-22 13:01 ivan
+
+ * FS/FS/cust_credit.pm: allow credits to be modified at API level
+
+2003-12-22 09:50 ivan
+
+ * FS/FS/cust_main.pm: add cust_credit, cust_pay and cust_refund
+ subs
+
+2003-12-21 15:16 ivan
+
+ * httemplate/browse/agent_type.cgi: work even if there are stray
+ type_pkgs records around
+
+2003-12-21 15:13 ivan
+
+ * FS/FS/Record.pm: cache virtual_fields method results to help
+ performance
+
+2003-12-21 13:12 ivan
+
+ * FS/bin/freeside-addoutsourceuser: add outsourced databases with
+ both addresses by default
+
+2003-12-19 19:47 ivan
+
+ * httemplate/browse/part_referral.cgi: fix inflated advertising
+ source numbers
+
+2003-12-15 00:08 ivan
+
+ * conf/invoice_latex: line up w/window envelopes
+
+2003-12-15 00:07 ivan
+
+ * httemplate/misc/print-invoice.cgi: print_ps returns ths invoice
+ now, do not count on latex printing it, oops
+
+2003-12-14 22:42 ivan
+
+ * FS/FS/: cust_main.pm, cust_bill.pm: fix bug that charged full
+ amounts of all open invoices as soon as balance went positive
+ (only manifests when any cust_bill->owed somehow got to be
+ negative)
+
+2003-12-14 19:41 ivan
+
+ * FS/FS/cust_bill.pm: add _latex_escape sub
+
+2003-12-14 09:53 ivan
+
+ * httemplate/view/cust_main.cgi: non-breaking spaces in custoemr
+ view field names
+
+2003-12-10 15:53 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: signup page w/advertising source
+ dropdown
+
+2003-12-10 14:51 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/upgrade10.html: add
+ part_referral.disabled, add disabled indices to agent and
+ part_bill_event
+
+2003-12-10 14:50 ivan
+
+ * FS/FS/part_referral.pm: add part_referral.disabled
+
+2003-11-30 00:06 ivan
+
+ * FS/FS/cust_bill.pm: explicitly tell dvips to write output to a
+ file
+
+2003-11-29 23:52 ivan
+
+ * httemplate/view/cust_bill.cgi: add link to view typeset invoice
+
+2003-11-29 23:41 ivan
+
+ * conf/invoice_latex: move to the left to line up with window
+ envelopes
+
+2003-11-29 02:50 ivan
+
+ * FS/FS/cust_credit_bill.pm: missing cut
+
+2003-11-29 02:48 ivan
+
+ * FS/FS/cust_bill.pm: use the latex template for normal printing
+ when available
+
+2003-11-29 02:39 ivan
+
+ * httemplate/edit/part_pkg.cgi: adding sql_generic price plan
+
+2003-11-29 00:32 ivan
+
+ * FS/FS/: Conf.pm, cust_bill_pay.pm, cust_credit_bill.pm: option to
+ send statements when a payment or credit is applied
+
+2003-11-29 00:18 ivan
+
+ * FS/FS/part_bill_event.pm: also set default latex template for
+ late bill templates
+
+2003-11-29 00:08 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, conf/invoice_latex,
+ conf/invoice_latexfooter, conf/invoice_latexnotes,
+ httemplate/misc/print-invoice.cgi,
+ httemplate/view/cust_bill-ps.cgi: postscript invoice redux
+
+2003-11-26 07:37 ivan
+
+ * FS/FS/cust_bill.pm: remove spurious Setup on one-time package
+ charges
+
+2003-11-26 06:25 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm: add invoice_default_terms config
+ value to control explicit due date printing on invoices
+
+2003-11-24 06:29 ivan
+
+ * httemplate/edit/part_export.cgi: fix html quoting problems
+
+2003-11-24 05:01 ivan
+
+ * httemplate/browse/part_export.cgi: fix html quoting of export
+ options
+
+2003-11-21 10:55 ivan
+
+ * FS/FS/Record.pm: revert bind_param change!
+
+2003-11-21 07:32 ivan
+
+ * httemplate/search/report_receivables.cgi: precedence helps alot
+
+2003-11-21 07:25 ivan
+
+ * httemplate/search/report_receivables.cgi: precedence helps alot
+
+2003-11-21 07:20 ivan
+
+ * httemplate/search/report_receivables.cgi: show customer status on
+ receivables report
+
+2003-11-20 04:48 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: don't error out when not
+ actually changing domain
+
+2003-11-19 10:13 ivan
+
+ * FS/FS/Record.pm: work around DBD::Pg problems with bind_param
+
+2003-11-19 04:21 ivan
+
+ * httemplate/: edit/REAL_cust_pkg.cgi, search/cust_pkg.html,
+ search/report_cc.html, search/report_credit.html,
+ search/report_cust_pay.html, search/report_tax.html: fix
+ jscalendar date ifFormat
+
+2003-11-18 17:37 ivan
+
+ * FS/FS/cust_bill.pm: reversing accidental commit of
+ work-in-progress
+
+2003-11-18 17:29 ivan
+
+ * FS/FS/: cust_bill.pm, cust_main.pm: disable debugging by default
+
+2003-11-18 07:14 ivan
+
+ * FS/FS/part_export.pm: fix communigate pro export descriptions
+
+2003-11-18 07:04 ivan
+
+ * FS/: FS/part_export/communigate_pro.pm,
+ FS/part_export/communigate_pro_singledomain.pm, MANIFEST,
+ FS/part_export.pm: add communigate_pro_singledomain export
+
+2003-11-18 03:17 ivan
+
+ * httemplate/docs/install.html: remove thread/PerlIO warning -
+ standard in 5.8.x and working fine
+
+2003-11-14 23:28 ivan
+
+ * FS/bin/freeside-selfservice-server: kill off ssh process when
+ re-opening connection
+
+2003-11-14 23:18 ivan
+
+ * fs_selfservice/FS-SelfService/freeside-selfservice-clientd: add
+ trailing newline to supress useless error messages in log
+
+2003-11-14 00:43 ivan
+
+ * FS/bin/freeside-selfservice-server: hopefully recover better from
+ lost ssh connections
+
+2003-11-13 18:52 ivan
+
+ * httemplate/search/report_receivables.cgi: sort these
+ case-insensitive
+
+2003-11-12 04:29 ivan
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm: allow provisioning of
+ unaudited services with a svcnum for imports
+
+2003-11-12 03:22 ivan
+
+ * FS/FS/cust_pkg.pm: better error msg
+
+2003-11-11 07:03 ivan
+
+ * FS/FS/svc_Common.pm: also make setx behave when setting svcnum
+ during an import
+
+2003-11-11 06:39 ivan
+
+ * FS/FS/svc_Common.pm: fix up virtual field reprucussions
+
+2003-11-11 06:21 ivan
+
+ * FS/FS/svc_Common.pm: simple change to cust_svc creation to help
+ imports with svcnums
+
+2003-11-11 00:35 ivan
+
+ * httemplate/browse/part_referral.cgi: really fix advertising
+ source edit links
+
+2003-11-11 00:01 ivan
+
+ * httemplate/search/report_receivables.cgi: remove spaces between
+ parens and contact name
+
+2003-11-10 23:51 ivan
+
+ * httemplate/browse/: part_referral.cgi, svc_acct_pop.cgi:
+ part_referral.cgi
+
+2003-11-10 05:54 ivan
+
+ * httemplate/misc/process/meta-import.cgi: hmm forgot to check this
+ in?
+
+2003-11-08 08:36 ivan
+
+ * FS/FS/: cust_main.pm, ClientAPI/MyAccount.pm: add order_pkg and
+ cancel_pkg functions to self-service
+
+2003-11-08 08:31 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: documentation for
+ self-service functions!
+
+2003-11-08 04:59 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: pass paybatch field to realtime_bop
+ so we can prevent double-charges
+
+2003-11-07 02:53 ivan
+
+ * httemplate/elements/: calendar-en.js, calendar-setup.js,
+ calendar-win2k-2.css, calendar.js, calendar_stripped.js: update
+ jscalendar
+
+2003-11-07 00:39 ivan
+
+ * FS/FS/cust_svc.pm: also fix attribute_since_sqlradacct for data
+ charging
+
+2003-11-07 00:36 ivan
+
+ * FS/FS/cust_svc.pm: finish fixing sqlradius_withdomain time
+ calculations
+
+2003-11-06 23:56 ivan
+
+ * httemplate/view/svc_acct.cgi: also show RADIUS usage information
+ for sqlradius_withdomain exports
+
+2003-11-06 06:45 ivan
+
+ * httemplate/search/report_receivables.cgi: add totals & simplify
+ expressions
+
+2003-11-06 06:08 ivan
+
+ * httemplate/search/report_receivables.cgi: ack! count credits,
+ not payments twice
+
+2003-11-06 06:00 ivan
+
+ * httemplate/search/report_receivables.cgi: typo
+
+2003-11-06 05:56 ivan
+
+ * httemplate/search/report_receivables.cgi: link to customer, don't
+ show custnum
+
+2003-11-06 05:40 ivan
+
+ * FS/MANIFEST: removing bin/freeside-receivables-report
+
+2003-11-06 05:39 ivan
+
+ * httemplate/: index.html, search/report_receivables.cgi: "current
+ receivables" -> A/R Aging summary
+
+2003-11-06 05:37 ivan
+
+ * FS/bin/freeside-receivables-report: removing (rewritten as a
+ proper html report)
+
+2003-11-05 03:13 ivan
+
+ * bin/create-fetchmailrc: fixup
+
+2003-11-05 02:26 ivan
+
+ * FS/FS/svc_acct.pm: allow letters in quota for use with edquota -p
+
+2003-11-04 10:01 ivan
+
+ * FS/FS/cust_main.pm: don't overwrite otaker on cust_main!
+
+2003-11-04 09:57 ivan
+
+ * bin/create-fetchmailrc: adding
+
+2003-11-04 09:30 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/shellcommands.pm: make snarf
+ info available to exports
+
+2003-11-04 02:56 ivan
+
+ * FS/FS/Record.pm: finish treating serials as ints!
+
+2003-11-04 02:55 ivan
+
+ * FS/FS/Record.pm: treat serial columns as ints too!
+
+2003-11-03 03:42 ivan
+
+ * FS/FS/svc_Common.pm, httemplate/edit/part_svc.cgi: does this fix
+ Bug#590??
+
+2003-11-03 03:30 ivan
+
+ * httemplate/edit/part_svc.cgi: kludge around this so i can add
+ service definitions for now
+
+2003-11-02 21:57 ivan
+
+ * httemplate/: index.html, browse/part_pkg.cgi,
+ search/cust_pkg.cgi: add suspended/canceled browse, fix the old
+ suspended browse
+
+2003-11-02 21:48 ivan
+
+ * httemplate/search/cust_pkg.cgi: add suspended/canceled package
+ browse
+
+2003-11-02 21:40 ivan
+
+ * httemplate/index.html: formatting
+
+2003-11-02 21:34 ivan
+
+ * httemplate/index.html: remove duplicate items from "Reports"
+ section
+
+2003-11-02 21:25 ivan
+
+ * httemplate/browse/part_pkg.cgi: tyops
+
+2003-11-02 21:21 ivan
+
+ * httemplate/browse/part_pkg.cgi: also show suspended and canceled
+ counts on active package browse
+
+2003-10-26 09:30 ivan
+
+ * httemplate/search/cust_main-quickpay.html: default quickpay to
+ exact search
+
+2003-10-25 17:39 ivan
+
+ * httemplate/search/: cust_pay.cgi, report_cust_pay.html: payment
+ reports broken down by Visa/MC / Amex / Discover
+
+2003-10-24 19:05 ivan
+
+ * FS/FS/: acct_snarf.pm, svc_Common.pm, svc_acct.pm,
+ ClientAPI/Signup.pm: signups with snarf info!
+
+2003-10-24 18:06 ivan
+
+ * httemplate/docs/signup.html: better link to .INS files
+ documentation
+
+2003-10-24 13:38 ivan
+
+ * FS/FS/: cust_main.pm, Conf.pm: add cvv-save configuration value
+ to save the cvv data for specific card types
+
+2003-10-24 12:28 ivan
+
+ * FS/FS/ClientAPI/Signup.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/process/cust_main.cgi, httemplate/docs/cvv2.html,
+ httemplate/docs/upgrade10.html, FS/FS/cust_main.pm,
+ FS/bin/freeside-setup, httemplate/docs/schema.html: cvv!
+
+2003-10-23 22:51 ivan
+
+ * httemplate/images/: cvv2.png, cvv2_amex.png: adding cvv2 images
+
+2003-10-23 17:50 ivan
+
+ * httemplate/browse/part_referral.cgi: UI: adjust alignment of
+ stats
+
+2003-10-23 17:39 ivan
+
+ * httemplate/browse/part_referral.cgi: referral listing now shows
+ customer signups today and past week/30/60/90/6months/year/total
+
+2003-10-23 15:37 ivan
+
+ * bin/dbdef-create: require DBIx::DBSchema 0.22 to deal with Pg
+ version problems
+
+2003-10-23 02:02 ivan
+
+ * httemplate/docs/legacy.html: fix cranky verbitage at the top
+
+2003-10-23 00:49 ivan
+
+ * FS/FS/cust_pay_batch.pm: Declined results from batches should now
+ suspend the relevant customer.
+
+2003-10-22 12:10 khoff
+
+ * httemplate/edit/process/router.cgi: dbh is a global.
+
+2003-10-19 22:01 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/view/cust_main.cgi: finish up weekly billing
+
+2003-10-19 21:25 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ httemplate/edit/part_pkg.cgi, httemplate/docs/upgrade10.html,
+ FS/bin/freeside-setup: daily/weekly billing
+
+2003-10-19 11:08 ivan
+
+ * httemplate/docs/install.html: fix CPAN link
+
+2003-10-16 15:57 khoff
+
+ * FS/FS/part_export/router.pm: Telnet/SSH router export for
+ svc_broadband.
+
+2003-10-16 06:48 ivan
+
+ * httemplate/docs/billing.html: fix Text::Template link
+
+2003-10-15 16:17 khoff
+
+ * httemplate/browse/router.cgi: $router isn't a global.
+
+2003-10-15 15:59 khoff
+
+ * httemplate/edit/router.cgi: More changes that got lost in the
+ merge somehow.
+
+2003-10-15 15:48 khoff
+
+ * httemplate/view/svc_broadband.cgi: File got munged during
+ svc_broadband merge. Added ability to create a 'customer
+ router'.
+
+2003-10-15 08:03 ivan
+
+ * FS/bin/freeside-selfservice-server,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/freeside-selfservice-clientd: add
+ tagging ability so we can run multiple self-service clients on
+ one machine
+
+2003-10-15 05:08 ivan
+
+ * FS/bin/freeside-setup: fix agent username and password
+ nullability
+
+2003-10-15 02:41 ivan
+
+ * FS/FS/cust_main.pm: - Business::OnlinePayment "recurring_billing"
+ flag is set for subsequent credit card transactions; some
+ processors (AuthorozeNet, others?) use this to waive the CVV2
+ requirement.
+
+2003-10-09 10:40 ivan
+
+ * FS/FS/cust_pay_batch.pm: parse last line from TD Canada Trust
+
+2003-10-09 04:05 ivan
+
+ * httemplate/docs/install.html: don't install on a public server!
+
+2003-10-07 21:09 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/misc/upload-batch.cgi: can't use
+ File::Basename::basename for windows filenames! use a regex
+ instead
+
+2003-10-07 06:50 ivan
+
+ * Makefile: mandrake
+
+2003-10-07 01:05 ivan
+
+ * FS/: FS/acct_snarf.pm, FS.pm, FS/part_export.pm: adding
+ acct_snarf
+
+2003-10-06 23:06 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: problem was scalar context
+ propogating to the wrong place in the grep
+
+2003-10-06 23:03 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: fix subroutine call from
+ UpdateAccountSettings sub
+
+2003-10-06 22:50 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: add suspend and unsuspend
+ hooks, don't try and set '*SUSPENDED*' passwords
+
+2003-10-06 22:49 ivan
+
+ * FS/FS/svc_acct.pm: doc
+
+2003-10-06 19:27 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: add suspension /
+ unsuspension export to communigate
+
+2003-10-06 05:05 ivan
+
+ * httemplate/search/cust_main-quickpay.html: - put link to main
+ menu on quick payment search page
+
+2003-10-06 04:39 ivan
+
+ * httemplate/search/svc_acct.cgi: fix URL argument processing for
+ account searches by popnum
+
+2003-10-06 04:22 ivan
+
+ * httemplate/search/svc_acct.cgi: typo
+
+2003-10-06 04:20 ivan
+
+ * httemplate/: browse/svc_acct_pop.cgi, search/svc_acct.cgi: fix
+ linking to account list per access number
+
+2003-10-06 04:14 ivan
+
+ * httemplate/: browse/svc_acct_pop.cgi, search/svc_acct.cgi: agent
+ browse shows # of active accounts & links to appropriate account
+ search
+
+2003-10-02 14:51 khoff
+
+ * httemplate/edit/svc_broadband.cgi: $field isn't a global.
+
+2003-10-02 07:26 ivan
+
+ * Makefile: minor Makefile updates
+
+2003-10-02 07:19 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: add emaildecline-exclude config
+ option
+
+2003-10-02 06:08 ivan
+
+ * fs_selfservice/FS-SelfService/freeside-selfservice-clientd: turn
+ off super-verbose logging
+
+2003-10-02 05:42 ivan
+
+ * fs_selfservice/FS-SelfService/Makefile.PL: update Makefile.PL
+ dependancies (PREREQ_PM) for self-service module and signup
+ wrapper
+
+2003-10-02 04:23 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/login.html: removing testing
+ info
+
+2003-10-02 03:18 ivan
+
+ * FS/bin/freeside-daily: added -y switch to freeside-daily to
+ specify an offset in days
+
+2003-10-02 01:56 ivan
+
+ * FS/FS/raddb.pm, bin/generate-raddb: update with dictionaries from
+ freeradius 0.9.1, and fix generate-raddb to avoid duplicates
+
+2003-09-30 08:01 ivan
+
+ * FS/FS/agent.pm: no duplicate usernames
+
+2003-09-30 07:58 ivan
+
+ * httemplate/docs/upgrade10.html: agent schema changes
+
+2003-09-30 05:48 ivan
+
+ * httemplate/misc/download-batch.cgi: IE doesn't like downloading
+ MIME type text/comma-separated-values
+
+2003-09-30 01:21 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi, CREDITS,
+ httemplate/elements/calendar-en.js,
+ httemplate/elements/calendar-setup.js,
+ httemplate/elements/calendar-win2k-2.css,
+ httemplate/elements/calendar.js,
+ httemplate/elements/calendar_stripped.js,
+ httemplate/images/calendar.png, httemplate/search/cust_pkg.html,
+ httemplate/search/report_cc.html,
+ httemplate/search/report_credit.html,
+ httemplate/search/report_cust_pay.html,
+ httemplate/search/report_tax.html: calendar popups!
+
+2003-09-30 00:04 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/docs/upgrade-1.4.2.html: CGI.pm 2.47 required for
+ ->upload() method
+
+2003-09-29 03:10 ivan
+
+ * httemplate/: browse/agent.cgi, search/cust_main.cgi: agent browse
+ now links to active/cancelled customers
+
+2003-09-28 23:51 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: signup with agent selection
+
+2003-09-28 23:35 ivan
+
+ * httemplate/index.html: remove Gratuitous Capitalization
+
+2003-09-28 22:51 ivan
+
+ * FS/FS/agent.pm, httemplate/browse/agent.cgi,
+ httemplate/edit/agent.cgi, httemplate/edit/cust_main.cgi: agents
+ can be disabled (auto-sensing based on schema)
+
+2003-09-28 22:51 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/schema.html: added
+ agent.disabled agent.username agent._password
+
+2003-09-28 19:17 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-selfservice-server: finish removing
+ quiet config options
+
+2003-09-27 19:36 ivan
+
+ * FS/FS/cust_pay_batch.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/browse/cust_pay_batch.cgi,
+ httemplate/misc/upload-batch.cgi: add upload of batch result from
+ TD Canada Trust some global.asa / handler.pl enhancements
+
+2003-09-26 16:33 khoff
+
+ * httemplate/edit/svc_www.cgi: $field is not a global.
+
+2003-09-26 14:02 khoff
+
+ * httemplate/edit/svc_acct.cgi: $field isn't a global.
+
+2003-09-26 06:37 ivan
+
+ * httemplate/docs/upgrade10.html: sql
+
+2003-09-26 06:04 ivan
+
+ * FS/: FS/cust_main.pm, bin/freeside-daily: re-setup option to
+ re-charge setup fee
+
+2003-09-26 02:31 ivan
+
+ * FS/FS/cust_main.pm: scalar/array scope fix... new, multiple (i.e.
+ canadian GST/PST) taxes work now!
+
+2003-09-26 02:15 ivan
+
+ * FS/FS/UID.pm: re-enable ChopBlanks for now
+
+2003-09-26 02:09 ivan
+
+ * FS/FS/cust_pkg.pm: fix manual_flag problem preventing cust_pkg
+ editing
+
+2003-09-26 01:11 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/cust_main_county.cgi: fix tax edit UI
+
+2003-09-25 04:56 ivan
+
+ * httemplate/docs/upgrade10.html: new per-tax setuptax and recurtax
+ fields
+
+2003-09-25 04:49 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/cust_main_county.cgi: UI for multiple named taxes w/setup &
+ recur exemptions 1.4 schema-auto-adjusting backport
+
+2003-09-25 04:17 ivan
+
+ * FS/FS/: cust_main.pm, cust_main_county.pm: multiple, named taxes
+ for a single region 1.4 backport auto-adjusts based on schema
+
+2003-09-25 03:40 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/schema.html: add setuptax
+ and recurtax fields to cust_main_county
+
+2003-09-25 03:28 ivan
+
+ * fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ freebsd portability fix
+
+2003-09-25 03:27 ivan
+
+ * FS/bin/freeside-selfservice-server: freebsd portability fixes
+
+2003-09-25 03:26 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm: quiet self-service server cancels
+
+2003-09-25 02:40 ivan
+
+ * httemplate/docs/upgrade-1.4.2.html: 1.4.2 upgrade
+
+2003-09-25 02:39 ivan
+
+ * httemplate/docs/upgrade9.html: update upgrade docs for bind
+ exports
+
+2003-09-24 10:20 ivan
+
+ * FS/FS/part_export/shellcommands.pm: don't change dir either when
+ username_pwonly is set
+
+2003-09-24 10:06 ivan
+
+ * FS/FS/part_export/shellcommands.pm: don't allow uid changes when
+ usermod_pwonly set
+
+2003-09-23 18:30 ivan
+
+ * httemplate/browse/agent.cgi: fix boolean precedence error leading
+ to inaccurate results on the new customer status list
+
+2003-09-21 00:31 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/misc/download-batch.cgi: remove
+ trancode from batching
+
+2003-09-20 23:52 ivan
+
+ * httemplate/misc/download-batch.cgi: eliminate extraneous newlines
+
+2003-09-20 18:22 ivan
+
+ * httemplate/misc/download-batch.cgi: preliminary batch download
+
+2003-09-20 18:22 ivan
+
+ * httemplate/browse/cust_pay_batch.cgi: add link to preliminary
+ batch download
+
+2003-09-19 05:40 ivan
+
+ * FS/FS/cust_main.pm: quiet option to cancel method
+
+2003-09-19 05:13 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: remove klunky $error ||= usage
+
+2003-09-19 05:04 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: oops, flipped an or to an and
+
+2003-09-19 05:02 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: cache initial signup_info for
+ performance
+
+2003-09-19 04:56 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: better error message when you don't
+ select a package
+
+2003-09-19 04:50 ivan
+
+ * init.d/freeside-init: correct pid filename for stopping
+ self-service server
+
+2003-09-19 04:35 ivan
+
+ * fs_selfservice/FS-SelfService/SelfService.pm: finish moving
+ signup server functions to self-service interace
+
+2003-09-19 04:00 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: module needs to return true
+
+2003-09-19 03:08 ivan
+
+ * FS/FS/ClientAPI/Signup.pm: make sure signup cancels are quiet
+
+2003-09-19 03:07 ivan
+
+ * Makefile, FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ FS/FS/ClientAPI/Signup.pm, httemplate/docs/index.html: move
+ signup server functions to self-service server. fix provisioning
+ & immediate suspension of declined signups.
+
+2003-09-19 02:59 ivan
+
+ * init.d/freeside-init: removing signup and passwd servers
+
+2003-09-18 21:25 ivan
+
+ * FS/FS/cust_main.pm: finish cleaning up the quiet stuff
+
+2003-09-18 21:13 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm, cust_pkg.pm: deprecate
+ signup_server-quiet config option
+
+2003-09-18 03:52 ivan
+
+ * httemplate/browse/agent.cgi: oops
+
+2003-09-18 03:46 ivan
+
+ * httemplate/browse/agent.cgi: include info on number of
+ active/cancelled customers in agent browse
+
+2003-09-15 14:33 ivan
+
+ * httemplate/search/cust_pkg.cgi: package search skips cancelled
+ packages when searching by date range
+
+2003-09-11 17:14 khoff
+
+ * FS/FS/addr_block.pm: @excludeaddr is a list of addresses, not
+ NetAddrs, so it shouldn't be map'd
+
+2003-09-11 14:57 ivan
+
+ * FS/FS/part_export.pm: also update sqlradius_withdomaind
+ description for freeradius 0.9.1
+
+2003-09-11 14:54 ivan
+
+ * FS/FS/part_export.pm: update doc note for freeradius 0.9.1
+
+2003-09-10 15:10 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: communigate pro quota bs
+
+2003-09-10 14:56 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: don't set an empty
+ externalFlag
+
+2003-09-10 14:45 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: communitgate pro
+ CreateAccount examples are on crack
+
+2003-09-10 14:39 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: fix for communigate pro
+ weird data structure on create
+
+2003-09-10 14:01 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: set initial password for
+ new accounts
+
+2003-09-10 03:54 ivan
+
+ * FS/FS/cust_pay.pm: adding cust_main method
+
+2003-09-09 15:36 ivan
+
+ * FS/MANIFEST: adding communigate_pro export
+
+2003-09-08 21:16 ivan
+
+ * FS/FS/part_export.pm: typo hiding remote username labels
+
+2003-09-06 00:20 ivan
+
+ * httemplate/browse/agent_type.cgi: hide display of disabled
+ packages from agent type browse
+
+2003-09-05 23:45 ivan
+
+ * FS/FS/part_export.pm: updated DBI link in sqlradius export notes
+
+2003-09-05 18:45 ivan
+
+ * FS/FS/svc_acct.pm: fix method name
+
+2003-09-05 18:44 ivan
+
+ * FS/FS/: cust_svc.pm, svc_acct.pm: add
+ get_session_history_sqlradacct have $ignore_quantity also ignore
+ 0 quantities
+
+2003-09-05 06:19 ivan
+
+ * FS/FS/part_export/communigate_pro.pm: typo
+
+2003-09-05 06:18 ivan
+
+ * FS/FS/: part_export.pm, part_export/communigate_pro.pm: add
+ communigate pro export
+
+2003-09-05 06:16 ivan
+
+ * FS/FS/Conf.pm: doc
+
+2003-09-05 02:13 ivan
+
+ * FS/FS/svc_acct.pm: allow ! password like !!
+
+2003-09-05 01:17 ivan
+
+ * FS/FS/Conf.pm: fix reference to obsolete shellcommands-useradd
+ conf value (refer to exports instead)
+
+2003-09-05 01:02 ivan
+
+ * FS/FS/Conf.pm: fix typo
+
+2003-09-05 01:01 ivan
+
+ * FS/FS/svc_acct.pm: allow up to 60 char encrypted passwords for
+ blowfish
+
+2003-09-05 00:55 ivan
+
+ * FS/bin/freeside-setup: svc_acct._password from 50 to 72 for
+ blowfish passwords
+
+2003-09-04 21:09 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: users-allow_comp config value to
+ control creation of complimentary accounts and minor pod updates
+
+2003-09-04 19:31 ivan
+
+ * httemplate/edit/part_svc.cgi: -w-safe and run under a 1.4 schema
+ too
+
+2003-09-03 16:06 ivan
+
+ * httemplate/view/cust_main.cgi: list extraneous services, closes:
+ #213
+
+2003-09-03 13:22 ivan
+
+ * FS/FS/Record.pm: turn off query debugging
+
+2003-09-03 11:46 ivan
+
+ * FS/FS/Record.pm: turn vfieldpart_hashref into a method, not a
+ subroutine another fix to support running 1.5.0 virtual field
+ code on 1.4.x databases
+
+2003-09-03 06:18 ivan
+
+ * FS/FS/part_export.pm: openbsd also uses sane useradd/mod/del
+ commands
+
+2003-09-03 05:35 ivan
+
+ * Makefile: makefile trivia for openbsd
+
+2003-08-07 22:54 ivan
+
+ * httemplate/view/svc_acct.cgi: - fix possible glitch with Mason on
+ account view screen
+
+2003-08-07 22:42 ivan
+
+ * FS/FS/CGI.pm, bin/masonize, httemplate/index.html,
+ httemplate/elements/header.html,
+ httemplate/elements/menubar.html, httemplate/elements/pager.html,
+ httemplate/elements/table.html, httemplate/search/sql.cgi,
+ httemplate/search/sql.html,
+ httemplate/search/elements/search.html: - (finish) includes!
+ (closes: Bug#551) - (finish) moving SQL search to including
+ generic elements/search.html - new elements: menubar.html,
+ header.html, pager.html and table.html - have masonize process
+ .html files also
+
+2003-08-07 19:02 ivan
+
+ * bin/masonize, htetc/global.asa, httemplate/autohandler,
+ httemplate/graph/money_time-graph.cgi,
+ httemplate/graph/money_time.cgi, httemplate/search/sql.html,
+ httemplate/search/elements/search.html: - fix Mason profiling to
+ pass-through images (for graph/) - fix graph/money-time.cgi use
+ of $m interfering with Mason - fix graph/money-time-graph.cgi to
+ set content-type in a Mason/ASP-independant fashion -
+ (beginning of) includes! - (beginning of) moving SQL search to
+ including generic elements/search.html - fix global.asa typo -
+ fix masonize to not prepend an extraneous blank line (breaking
+ graph/money-time-graph.cgi)
+
+2003-08-07 06:08 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/view/cust_main.cgi: - turn on profiling with mason
+ like with Apache::ASP (redirects now working) - fix mason error
+ with new view/cust_main.cgi UI
+
+2003-08-07 05:47 ivan
+
+ * Makefile, htetc/global.asa, htetc/handler.pl,
+ htetc/handler.pl-1.0x, httemplate/autohandler,
+ httemplate/index.html, httemplate/docs/install.html,
+ httemplate/docs/upgrade10.html: - switch to mason by default -
+ minimum mason version 1.1 (and doc) - evaluate .html files with
+ mason/asp - turn on profiling with mason like with Apache::ASP
+ (redirects not working) - (start of) includes
+
+2003-08-05 14:00 ivan
+
+ * httemplate/browse/part_pkg.cgi: doh, revert stuff that shouldn't
+ have been checked in
+
+2003-08-05 13:06 ivan
+
+ * htetc/: handler.pl, handler.pl-1.0x: no svc_acct_sm in 1.5
+
+2003-08-05 12:07 ivan
+
+ * FS/FS/part_export.pm: update sqlradius{_withdomain} documentation
+
+2003-08-05 11:52 ivan
+
+ * FS/FS/svc_domain.pm: remove spurious re-use of $error
+
+2003-08-05 11:45 ivan
+
+ * FS/FS/Record.pm: backwards compatibility if the schema hasn't
+ been updated
+
+2003-08-04 17:32 khoff
+
+ * httemplate/index.html: Fixed %%%VERSION%%% tag
+
+2003-08-04 17:20 khoff
+
+ * FS/MANIFEST, FS/FS/Record.pm, FS/FS/addr_block.pm,
+ FS/FS/agent.pm, FS/FS/agent_type.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_event.pm, FS/FS/cust_bill_pay.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_detail.pm,
+ FS/FS/cust_credit.pm, FS/FS/cust_credit_bill.pm,
+ FS/FS/cust_credit_refund.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_county.pm, FS/FS/cust_main_invoice.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pay_batch.pm, FS/FS/cust_pkg.pm,
+ FS/FS/cust_refund.pm, FS/FS/cust_svc.pm,
+ FS/FS/cust_tax_exempt.pm, FS/FS/domain_record.pm,
+ FS/FS/export_svc.pm, FS/FS/msgcat.pm, FS/FS/nas.pm,
+ FS/FS/part_bill_event.pm, FS/FS/part_export.pm,
+ FS/FS/part_export_option.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_pop_local.pm, FS/FS/part_referral.pm,
+ FS/FS/part_svc.pm, FS/FS/part_svc_column.pm,
+ FS/FS/part_virtual_field.pm, FS/FS/pkg_svc.pm, FS/FS/port.pm,
+ FS/FS/prepay_credit.pm, FS/FS/queue.pm, FS/FS/queue_arg.pm,
+ FS/FS/queue_depend.pm, FS/FS/radius_usergroup.pm,
+ FS/FS/router.pm, FS/FS/session.pm, FS/FS/svc_Common.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_acct_pop.pm, FS/FS/svc_broadband.pm,
+ FS/FS/svc_domain.pm, FS/FS/svc_forward.pm, FS/FS/svc_www.pm,
+ FS/FS/type_pkgs.pm, FS/bin/freeside-setup, htetc/global.asa,
+ htetc/handler.pl, htetc/handler.pl-1.0x, httemplate/index.html,
+ httemplate/browse/part_svc.cgi,
+ httemplate/browse/part_virtual_field.cgi,
+ httemplate/browse/router.cgi, httemplate/docs/upgrade10.html,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/part_virtual_field.cgi,
+ httemplate/edit/router.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_broadband.cgi, httemplate/edit/svc_www.cgi,
+ httemplate/edit/process/router.cgi,
+ httemplate/edit/process/svc_broadband.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_broadband.cgi,
+ httemplate/view/svc_forward.cgi, httemplate/view/svc_www.cgi:
+ Virtual field merge
+
+2003-08-04 17:00 khoff
+
+ * FS/FS/part_router_field.pm, FS/FS/part_sb_field.pm,
+ FS/FS/router_field.pm, FS/FS/sb_field.pm,
+ httemplate/browse/part_sb_field.cgi,
+ httemplate/edit/part_router_field.cgi,
+ httemplate/edit/part_sb_field.cgi: Virtual field merge
+
+2003-07-25 09:26 ivan
+
+ * FS/FS/svc_acct.pm: typo
+
+2003-07-25 06:13 ivan
+
+ * FS/FS/Conf.pm: doc
+
+2003-07-23 10:05 ivan
+
+ * FS/FS/part_export/domain_shellcommands.pm: bugfix for
+ non-catchall domains
+
+2003-07-23 08:36 ivan
+
+ * httemplate/edit/: cust_main.cgi, process/cust_main.cgi: fix
+ spurious "can't purchase pkgpart" errors
+
+2003-07-17 09:02 ivan
+
+ * FS/FS/cust_main.pm: one last tiny bugfix for the retry_realtime
+ functionality
+
+2003-07-16 09:05 ivan
+
+ * httemplate/search/cust_pay.cgi: UI
+
+2003-07-16 09:01 ivan
+
+ * httemplate/search/cust_pay.cgi: show totals in payment report
+
+2003-07-15 06:30 ivan
+
+ * rt/: ChangeLog, Makefile, README, TODO, bin/initacls.Oracle,
+ bin/initacls.Pg, bin/initacls.mysql, bin/mason_handler.fcgi,
+ bin/mason_handler.scgi, bin/rt, bin/rt-mailgate, bin/rtadmin,
+ bin/webmux.pl, docs/rt.gif, docs/design_docs/acls,
+ docs/design_docs/basic-definitions.txt,
+ docs/design_docs/cli_spec, docs/design_docs/evil_plans,
+ docs/design_docs/local_hacking, etc/acl.Oracle, etc/acl.Pg,
+ etc/acl.mysql, etc/config.pm, etc/schema.Oracle, etc/schema.Pg,
+ etc/schema.mysql, etc/schema.pm, lib/MANIFEST, lib/MANIFEST.SKIP,
+ lib/Makefile.PL, lib/RT.pm, lib/test.pl, lib/RT/ACE.pm,
+ lib/RT/ACL.pm, lib/RT/Attachment.pm, lib/RT/Attachments.pm,
+ lib/RT/CurrentUser.pm, lib/RT/Date.pm, lib/RT/EasySearch.pm,
+ lib/RT/Group.pm, lib/RT/GroupMember.pm, lib/RT/GroupMembers.pm,
+ lib/RT/Groups.pm, lib/RT/Handle.pm, lib/RT/Keyword.pm,
+ lib/RT/KeywordSelect.pm, lib/RT/KeywordSelects.pm,
+ lib/RT/Keywords.pm, lib/RT/Link.pm, lib/RT/Links.pm,
+ lib/RT/ObjectKeyword.pm, lib/RT/ObjectKeywords.pm,
+ lib/RT/Queue.pm, lib/RT/Queues.pm, lib/RT/Record.pm,
+ lib/RT/Scrip.pm, lib/RT/ScripAction.pm, lib/RT/ScripActions.pm,
+ lib/RT/ScripCondition.pm, lib/RT/ScripConditions.pm,
+ lib/RT/Scrips.pm, lib/RT/Template.pm, lib/RT/Templates.pm,
+ lib/RT/TestHarness.pm, lib/RT/Ticket.pm, lib/RT/Tickets.pm,
+ lib/RT/Transaction.pm, lib/RT/Transactions.pm, lib/RT/User.pm,
+ lib/RT/Users.pm, lib/RT/Watcher.pm, lib/RT/Watchers.pm,
+ lib/RT/Action/Autoreply.pm, lib/RT/Action/Generic.pm,
+ lib/RT/Action/Notify.pm, lib/RT/Action/NotifyAsComment.pm,
+ lib/RT/Action/OpenDependent.pm, lib/RT/Action/ResolveMembers.pm,
+ lib/RT/Action/SendEmail.pm, lib/RT/Action/SendPasswordEmail.pm,
+ lib/RT/Action/StallDependent.pm,
+ lib/RT/Condition/AnyTransaction.pm, lib/RT/Condition/Generic.pm,
+ lib/RT/Condition/NewDependency.pm,
+ lib/RT/Condition/StatusChange.pm, lib/RT/Interface/CLI.pm,
+ lib/RT/Interface/Email.pm, lib/RT/Interface/Web.pm: reverting to
+ vendor branch rt 3.0.4, hopefully
+
+2003-07-15 06:16 ivan
+
+ * rt/: Makefile.in, aclocal.m4, config, config.layout, Changelog,
+ config.log, config.pld, config.status, configure, configure.ac,
+ install-sh, bin/mason_handler.fcgi.in, bin/mason_handler.scgi.in,
+ bin/mason_handler.svc, bin/mason_handler.svc.in,
+ bin/rt-commit-handler, bin/rt-commit-handler.in,
+ bin/rt-crontool.in, bin/rt-mailgate.in, bin/webmux.pl.in,
+ bin/rt-crontool, docs/design_docs/approval_notices,
+ docs/design_docs/approval_template, docs/design_docs/cf_search,
+ docs/design_docs/delegation, docs/design_docs/groups_notes,
+ docs/design_docs/recursive_group_membership_algorithm,
+ docs/design_docs/rql_parser_machine.graphviz,
+ docs/design_docs/string-extraction-guide.txt,
+ docs/design_docs/ticket_templates, etc/RT_Config.pm,
+ etc/RT_Config.pm.in, etc/RT_SiteConfig.pm, etc/constraints.mysql,
+ etc/initialdata, etc/schema.SQLite, etc/upgrade/2.1.71,
+ html/autohandler, html/index.html, html/l, lib/RT.pm.in,
+ lib/RT/ACE_Overlay.pm, lib/RT/ACL_Overlay.pm,
+ lib/RT/Attachment_Overlay.pm, lib/RT/Attachments_Overlay.pm,
+ lib/RT/Base.pm, lib/RT/CachedGroupMember.pm,
+ lib/RT/CachedGroupMember_Overlay.pm,
+ lib/RT/CachedGroupMembers.pm,
+ lib/RT/CachedGroupMembers_Overlay.pm, lib/RT/CustomField.pm,
+ lib/RT/CustomFieldValue.pm, lib/RT/CustomFieldValues.pm,
+ lib/RT/CustomFieldValues_Overlay.pm,
+ lib/RT/CustomField_Overlay.pm, lib/RT/CustomFields.pm,
+ lib/RT/CustomFields_Overlay.pm, lib/RT/EmailParser.pm,
+ lib/RT/GroupMember_Overlay.pm, lib/RT/GroupMembers_Overlay.pm,
+ lib/RT/Group_Overlay.pm, lib/RT/Groups_Overlay.pm,
+ lib/RT/I18N.pm, lib/RT/Link_Overlay.pm, lib/RT/Links_Overlay.pm,
+ lib/RT/Principal.pm, lib/RT/Principal_Overlay.pm,
+ lib/RT/Principals.pm, lib/RT/Principals_Overlay.pm,
+ lib/RT/Queue_Overlay.pm, lib/RT/Queues_Overlay.pm,
+ lib/RT/ScripAction_Overlay.pm, lib/RT/ScripActions_Overlay.pm,
+ lib/RT/ScripCondition_Overlay.pm,
+ lib/RT/ScripConditions_Overlay.pm, lib/RT/Scrip_Overlay.pm,
+ lib/RT/Scrips_Overlay.pm, lib/RT/SearchBuilder.pm,
+ lib/RT/System.pm, lib/RT/Template_Overlay.pm,
+ lib/RT/Templates_Overlay.pm, lib/RT/TicketCustomFieldValue.pm,
+ lib/RT/TicketCustomFieldValue_Overlay.pm,
+ lib/RT/TicketCustomFieldValues.pm,
+ lib/RT/TicketCustomFieldValues_Overlay.pm,
+ lib/RT/Ticket_Overlay.pm, lib/RT/Tickets_Overlay.pm,
+ lib/RT/Tickets_Overlay_SQL.pm, lib/RT/Transaction_Overlay.pm,
+ lib/RT/Transactions_Overlay.pm, lib/RT/URI.pm,
+ lib/RT/User_Overlay.pm, lib/RT/Users_Overlay.pm,
+ lib/RT/Action/AutoOpen.pm, lib/RT/Action/CreateTickets.pm,
+ lib/RT/Action/EscalatePriority.pm, lib/RT/Action/SetPriority.pm,
+ lib/RT/Action/UserDefined.pm, lib/RT/Condition/BeforeDue.pm,
+ lib/RT/Condition/Overdue.pm, lib/RT/Condition/OwnerChange.pm,
+ lib/RT/Condition/PriorityExceeds.pm,
+ lib/RT/Condition/QueueChange.pm, lib/RT/Condition/UserDefined.pm,
+ lib/RT/I18N/cs.pm, lib/RT/I18N/cs.po, lib/RT/I18N/de.po,
+ lib/RT/I18N/en.po, lib/RT/I18N/es.po, lib/RT/I18N/fi.po,
+ lib/RT/I18N/fr.po, lib/RT/I18N/he.po, lib/RT/I18N/i_default.pm,
+ lib/RT/I18N/ja.po, lib/RT/I18N/nl.po, lib/RT/I18N/no.po,
+ lib/RT/I18N/pt_br.po, lib/RT/I18N/ru.po, lib/RT/I18N/zh_cn.po,
+ lib/RT/I18N/zh_tw.po, lib/RT/Interface/Email/Auth/MailFrom.pm,
+ lib/RT/Interface/Email/Filter/SpamAssassin.pm,
+ lib/RT/Search/ActiveTicketsInQueue.pm, lib/RT/Search/Generic.pm,
+ lib/RT/URI/base.pm, lib/RT/URI/fsck_com_rt.pm, lib/t/00smoke.t,
+ lib/t/00smoke.t.in, lib/t/01harness.t, lib/t/01harness.t.in,
+ lib/t/02regression.t, lib/t/02regression.t.in, lib/t/03web.pl,
+ lib/t/03web.pl.in, lib/t/04_send_email.pl,
+ lib/t/04_send_email.pl.in, m4/rt_enable_layout.m4,
+ m4/rt_expand_var.m4, m4/rt_layout.m4,
+ m4/rt_subst_expanded_arg.m4, sbin/extract-message-catalog,
+ sbin/extract_pod_tests, sbin/factory, sbin/license_tag,
+ sbin/regression_harness, sbin/rt-setup-database,
+ sbin/rt-setup-database.in, sbin/rt-test-dependencies.in,
+ autom4te.cache/output.0, autom4te.cache/requests,
+ autom4te.cache/traces.0, sbin/rt-test-dependencies: Initial
+ revision
+
+2003-07-15 04:23 ivan
+
+ * Makefile: 1.5.0pre3
+
+2003-07-13 23:21 ivan
+
+ * httemplate/search/cust_pay.cgi: fix bug when both a payment type
+ and date range are specified
+
+2003-07-12 04:14 ivan
+
+ * httemplate/index.html: remove Gratuitous capitalization
+
+2003-07-12 04:09 ivan
+
+ * httemplate/: index.html, browse/part_pkg.cgi,
+ browse/part_svc.cgi, search/cust_pkg.cgi: - better description of
+ payment search - some clarification of services/packages vs.
+ service/package definitions in browse/part_{svc,pkg}.cgi -
+ package definition report by # of active customer packages (with
+ links to customer package listing)
+
+2003-07-11 08:37 ivan
+
+ * FS/FS/cust_main.pm: fix timelocal AUTOLOAD problem introduced by
+ Time::Local fix in rc3 (closes: Bug#550)
+
+2003-07-11 08:23 ivan
+
+ * httemplate/: index.html, search/cust_pay.cgi,
+ search/cust_pkg.cgi, search/report_cust_pay.html: payments report
+ for inventivemedia / hpnx
+
+2003-07-11 00:54 ivan
+
+ * FS/FS/Record.pm: typo
+
+2003-07-11 00:30 ivan
+
+ * FS/FS/Record.pm: provide stack backtrace when we wind up in
+ FS::Record::AUTOLOAD by accident
+
+2003-07-09 07:39 ivan
+
+ * httemplate/edit/svc_domain.cgi: increase max domain length to 63
+
+2003-07-06 11:58 ivan
+
+ * httemplate/view/cust_main.cgi: don't bother displaying comments
+ that are only whitespace/newlines
+
+2003-07-03 20:34 ivan
+
+ * etc/acp_logfile-parse: removing obsolete file
+
+2003-07-03 20:31 ivan
+
+ * etc/abbr_state.txt: adding states
+
+2003-07-03 20:27 ivan
+
+ * etc/example-direct-cardin: removing ancient cybercash example
+
+2003-07-03 18:37 ivan
+
+ * FS/FS/svc_acct_pop.pm: don't populate the whole initial list if
+ there are tons of POPs
+
+2003-07-03 17:51 ivan
+
+ * FS/FS/svc_acct_pop.pm: optimize javascript to handle large
+ numbers of POPs
+
+2003-07-03 16:00 ivan
+
+ * htetc/global.asa: working DBIx::Profile again
+
+2003-07-02 05:58 ivan
+
+ * FS/FS/part_pkg.pm: tyop
+
+2003-07-02 05:56 ivan
+
+ * FS/FS/part_pkg.pm: relaxed "first package" restrictions; will
+ find any appropriate service with quantity 1
+
+2003-07-02 05:34 ivan
+
+ * Artistic, README: change license to GPL only
+
+2003-07-01 02:00 ivan
+
+ * httemplate/edit/part_export.cgi: larger textareas in export
+ options
+
+2003-06-30 17:27 ivan
+
+ * FS/FS/part_export.pm: typo
+
+2003-06-30 12:15 ivan
+
+ * FS/FS/cust_main.pm: bugfix for manual re-charging changes
+ (Bug#423)
+
+2003-06-30 11:56 ivan
+
+ * FS/FS/cust_main.pm: pass additional fields to ACH processors
+ (Authorize.Net should work now)
+
+2003-06-30 06:18 ivan
+
+ * FS/: MANIFEST, bin/freeside-overdue: removing deprecated
+ freeside-overdue
+
+2003-06-30 05:22 ivan
+
+ * FS/: FS/part_export.pm, FS/part_export/sqlradius.pm,
+ FS/part_export/sqlradius_withdomain.pm,
+ bin/freeside-sqlradius-reset,
+ t/part_export-sqlradius_withdomain.t: adding sqlradius_withdomain
+ export including realms, closes: bug#514
+
+2003-06-30 00:44 ivan
+
+ * FS/FS/svc_domain.pm: increase maximum domain length to 67
+
+2003-06-27 07:19 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add radius-ip configuration
+ parameter for Framed-IP-Address vs. Framed-Address
+
+2003-06-25 03:22 ivan
+
+ * httemplate/search/cust_bill_event.cgi: Pg 7.3 incompatibility
+ with empty dates in Failed Invoice Event search, patch from
+ rlucas@tercent.net
+
+2003-06-25 01:40 ivan
+
+ * FS/FS/: svc_acct.pm, part_export/cp.pm: svc_acct doc update cp
+ export - disable old-style suspending
+
+2003-06-24 17:57 ivan
+
+ * FS/FS/part_export/forward_shellcommands.pm: forward_shellcommands
+ update: might work now
+
+2003-06-24 07:12 ivan
+
+ * FS/FS/part_export/: forward_shellcommands.pm, sqlmail.pm:
+ explicitly use the necessary modules
+
+2003-06-22 07:21 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/bill.cgi: fix the credit card
+ retry on change or manual "retry_card" to ONCE per invoice
+
+2003-06-22 02:11 ivan
+
+ * bin/create-history-tables: skip pg_ tables also (ewww, showing up
+ in reverse-engineered schema)
+
+2003-06-22 02:04 ivan
+
+ * bin/create-history-tables: skip history tables that exist
+ already; easier to re-run now
+
+2003-06-13 19:04 ivan
+
+ * httemplate/docs/upgrade9.html: cust_bill2 index
+
+2003-06-13 19:02 ivan
+
+ * FS/bin/freeside-setup: add index on cust_bill._date
+
+2003-06-13 18:01 ivan
+
+ * bin/apache.export: \n\n between virtualhost entries
+
+2003-06-13 08:18 ivan
+
+ * FS/FS/part_export/shellcommands.pm: $new_quoted_password vs.
+ $quoted_new__password
+
+2003-06-13 02:12 ivan
+
+ * FS/FS/part_export.pm: proper command escape for vpopmail export
+
+2003-06-13 01:46 ivan
+
+ * httemplate/edit/part_export.cgi: missing semi
+
+2003-06-13 01:44 ivan
+
+ * httemplate/edit/part_export.cgi: correct stickiness for checkbox
+ options on export edit
+
+2003-06-13 01:38 ivan
+
+ * FS/FS/part_export.pm: pass vpopmail passwords on command-line
+ (unfortunately)
+
+2003-06-13 01:23 ivan
+
+ * FS/FS/part_export.pm: full path to vpopmail commands
+
+2003-06-13 01:11 ivan
+
+ * FS/FS/: part_export.pm, part_export/shellcommands.pm: add
+ vpopmail presets to shellcommands_withdomain export
+
+2003-06-12 07:08 ivan
+
+ * bin/passwd.import: /bin/halt and /sbin/halt
+
+2003-06-12 07:06 ivan
+
+ * bin/passwd.import: tyop
+
+2003-06-12 06:57 ivan
+
+ * bin/passwd.import: better error handling for re-imports
+
+2003-06-12 06:50 ivan
+
+ * bin/fix-sequences: remove overly-verbose debugging for now
+
+2003-06-12 06:50 ivan
+
+ * bin/fix-sequences: "doc"
+
+2003-06-12 06:49 ivan
+
+ * bin/fix-sequences: arg
+
+2003-06-12 06:47 ivan
+
+ * bin/fix-sequences: debugging
+
+2003-06-12 06:43 ivan
+
+ * bin/fix-sequences: update for long table names; use sequence name
+ directly instead of guessing
+
+2003-06-12 06:35 ivan
+
+ * bin/fix-sequences: moo
+
+2003-06-12 06:32 ivan
+
+ * bin/fix-sequences: adding fix-sequences
+
+2003-06-12 05:57 ivan
+
+ * FS/FS/part_export.pm: _default_ default now sets GECOS like the
+ OS-defaults
+
+2003-06-12 05:43 ivan
+
+ * FS/FS/part_export.pm: sqlradius doc update; don't need to allow
+ null OP columns
+
+2003-06-11 13:27 khoff
+
+ * FS/FS/Misc.pm: In scalar context, smtpsend returns the number of
+ addresses that the message was successfully delivered to. I'm
+ assuming 'Debug' causes Net::SMTP to warn the debug output, not
+ return it.
+
+2003-06-11 12:13 khoff
+
+ * FS/FS/Misc.pm: We're passing a list here, not one argument.
+
+2003-06-09 04:11 ivan
+
+ * FS/FS/cust_svc.pm: add "$ignore_quantity" bypass
+
+2003-06-08 17:59 ivan
+
+ * httemplate/docs/install.html: documentation update from "Jesse D.
+ Guardiani" <jesse@wingnet.net>, thanks
+
+2003-06-06 18:58 ivan
+
+ * FS/FS/cust_main.pm: and fix the error message
+
+2003-06-06 18:57 ivan
+
+ * FS/FS/cust_main.pm: really, really give a better error message
+ when used under 5.005 now. really.
+
+2003-06-06 18:47 ivan
+
+ * FS/FS/cust_main.pm: give a better error message regarding
+ Time::Local on old perls. really.
+
+2003-06-06 18:42 ivan
+
+ * FS/FS/cust_main.pm: require Time::Local 1.04 on perls before 5.6
+
+2003-06-06 03:49 ivan
+
+ * FS/FS/part_export/cp.pm: add suspend/unsuspend capability to CP
+ export
+
+2003-06-05 21:42 ivan
+
+ * htetc/global.asa: database profiling bs i should just switch to
+ mason
+
+2003-06-04 17:22 khoff
+
+ * FS/FS/cust_pkg.pm: order/cancel packages rewritten
+
+2003-06-04 09:14 ivan
+
+ * FS/FS/svc_acct.pm: allow empty slipip's
+
+2003-06-04 05:44 ivan
+
+ * httemplate/search/cust_pkg.cgi: fix last bill label
+
+2003-06-03 06:53 ivan
+
+ * httemplate/view/cust_main.cgi: fix provision links
+
+2003-06-03 05:41 ivan
+
+ * FS/FS/: part_export.pm, part_export/cp.pm: minor update to cp
+ export - eliminate redundant "host" parameter
+
+2003-06-03 05:40 ivan
+
+ * Makefile: 1.5.0pre2 (too late, hehe)
+
+2003-06-03 02:34 ivan
+
+ * bin/svc_acct_sm.import: removing (very) deprecated import
+
+2003-06-03 00:54 ivan
+
+ * httemplate/docs/upgrade10.html: upgrade docs
+
+2003-06-02 23:10 ivan
+
+ * httemplate/docs/upgrade10.html: slightly better upgrade docs
+
+2003-06-02 23:09 ivan
+
+ * FS/bin/freeside-setup: use serial for primary keys in new tables
+ too
+
+2003-06-02 22:49 ivan
+
+ * FS/bin/freeside-setup: router.svcnum nullability fix
+
+2003-06-02 07:51 ivan
+
+ * httemplate/view/cust_main.cgi: UI nit
+
+2003-06-02 05:22 ivan
+
+ * FS/FS/cust_pkg.pm: add last_bill field to manpage
+
+2003-05-30 02:40 ivan
+
+ * httemplate/docs/man/FS/part_export/.cvs_is_on_crack: force
+ inclusion of httemplate/docs/man hierarchy
+
+2003-05-30 02:22 ivan
+
+ * FS/FS/part_export/sqlradius.pm: sqlradius exports include "op"
+ field
+
+2003-05-30 01:45 ivan
+
+ * httemplate/docs/ssh.html: doc
+
+2003-05-19 22:43 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/payment_results.html,
+ fs_selfservice/FS-SelfService/cgi/process_payment.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: working
+ self-service self-payments!
+
+2003-05-19 06:54 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: fix up some
+ bugs in processing payments via self-service... appears to be
+ working so far
+
+2003-05-19 06:38 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/process_payment.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: first crack at
+ payment processing with self-service (step two of the process)
+
+2003-05-19 05:00 ivan
+
+ * FS/: MANIFEST, FS/Misc.pm, FS/cust_bill.pm, FS/cust_main.pm,
+ FS/cust_pay.pm, FS/cust_pkg.pm, FS/svc_acct.pm, FS/svc_domain.pm,
+ FS/ClientAPI/MyAccount.pm, t/Misc.t: maintenance: - add
+ FS::Misc with send_email subroutine, remove all duplicate code
+ from various modules - move the realtime_bop from cust_bill
+ to cust_main & change usage slightly. invnum is no longer
+ required. FS::cust_bill::realtime_bop remains as a wrapper.
+ self-service: - fix some syntax errors, make payment UI (step
+ one) really should be working now
+
+2003-05-18 17:15 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/: make_payment.html,
+ selfservice.cgi: processing payments...
+
+2003-05-18 04:44 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/cgi/make_payment.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html: interface for
+ making payments all done
+
+2003-05-18 01:09 ivan
+
+ * fs_selfservice/FS-SelfService/cgi/make_payment.html: forgot this
+ file
+
+2003-05-18 01:08 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm, fs_selfservice/DEPLOY: more
+ self-service make payment UI work
+
+2003-05-17 23:20 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm,
+ fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi: self-service:
+ make payment UI done
+
+2003-05-14 09:51 ivan
+
+ * FS/FS/cust_bill.pm: display recurring custom line items on
+ invoices as well as one-shot ones
+
+2003-05-12 23:01 ivan
+
+ * FS/FS/cust_bill_event.pm: - document missing fields in
+ cust_bill_event
+
+2003-05-12 22:47 ivan
+
+ * FS/FS/addr_block.pm: missing pod =cut at end
+
+2003-05-12 20:22 ivan
+
+ * FS/FS/svc_acct.pm, fs_selfservice/FS-SelfService/cgi/login.html,
+ fs_selfservice/FS-SelfService/cgi/myaccount.html,
+ fs_selfservice/FS-SelfService/cgi/selfservice.cgi,
+ fs_selfservice/FS-SelfService/cgi/view_invoice.html: -
+ self-service updates: cleanup and beginnings of "make a payment"
+ - fix pod masking FS::svc_acct::cust_svc
+
+2003-05-12 00:34 ivan
+
+ * fs_selfservice/FS-SelfService/freeside-selfservice-clientd:
+ setbuf call doesn't appear to be working...
+
+2003-05-12 00:33 ivan
+
+ * httemplate/view/cust_main.cgi: fix service links
+
+2003-05-11 15:55 ivan
+
+ * httemplate/index.html: mail forward search not yet implemented
+
+2003-05-11 15:53 ivan
+
+ * httemplate/search/cust_pkg.cgi: patch from Richard Siddall for
+ strict vars
+
+2003-05-11 15:34 ivan
+
+ * CREDITS, httemplate/view/cust_main.cgi: red here is too confusing
+
+2003-05-11 15:25 ivan
+
+ * htetc/: handler.pl, handler.pl-1.0x: handler.pl updates from
+ Richard Siddall <richard.siddall@elirion.net>
+
+2003-05-09 23:45 ivan
+
+ * httemplate/view/: cust_main.cgi, cust_pkg.cgi: first pass at new
+ package list UI
+
+2003-05-09 22:41 ivan
+
+ * FS/FS/cust_pkg.pm: fix bug in last_bill method which prevented
+ last_bill dates from being set
+
+2003-05-09 22:40 ivan
+
+ * FS/FS/cust_main.pm: - substitute '0' for existing blank
+ setup/recur fees - use timelocal_nocheck instead of timelocal for
+ proper wraparound
+
+2003-05-09 12:30 khoff
+
+ * httemplate/view/cust_main.cgi: Cleaned-up package view code and
+ added CSS tags.
+
+2003-05-08 23:03 ivan
+
+ * fs_selfservice/DEPLOY: updated freebsd install
+
+2003-05-08 03:28 ivan
+
+ * CREDITS: credit where credit's due
+
+2003-05-08 02:34 ivan
+
+ * Makefile, FS/FS/Record.pm: general Pg 7.3 fix for setting int
+ columns to '' / NULL
+
+2003-05-06 14:15 ivan
+
+ * httemplate/view/cust_main_alt.cgi: don't create new files in lieu
+ of branches
+
+2003-05-06 13:33 khoff
+
+ * httemplate/view/cust_main_alt.cgi: Can't pull out of thin air.
+
+2003-05-04 13:58 khoff
+
+ * httemplate/view/cust_main_alt.cgi: proposed cust_main.cgi
+
+2003-05-02 19:06 ivan
+
+ * FS/FS/part_export/infostreet.pm, httemplate/edit/svc_acct.cgi:
+ enable quota maintenance in infostreet export
+
+2003-05-02 19:03 ivan
+
+ * bin/apache.export: typo in usage instructions
+
+2003-05-02 18:30 ivan
+
+ * FS/FS/part_export.pm: clean up CVS cruft
+
+2003-05-02 16:51 khoff
+
+ * httemplate/view/cust_main.cgi: CARD && DCRD?
+
+2003-04-29 12:49 khoff
+
+ * httemplate/misc/catchall.cgi: 0 has a hash key looks like svcnum
+ = 0. Suprisingly, '' works.
+
+2003-04-29 11:28 khoff
+
+ * FS/FS/domain_record.pm: Better SOA checking
+
+2003-04-29 09:59 khoff
+
+ * httemplate/edit/svc_broadband.cgi: It helps if you can edit the
+ ip_addr field.
+
+2003-04-25 19:01 khoff
+
+ * FS/FS/cust_svc.pm: I don't like FS::Record warnings
+
+2003-04-25 17:28 khoff
+
+ * FS/FS/cust_bill.pm: Tyop
+
+2003-04-24 16:01 ivan
+
+ * httemplate/edit/cust_main.cgi: fix for bug triggered by
+ nonexistant referring customer numbers
+
+2003-04-24 11:46 khoff
+
+ * FS/FS/part_export/sqlmail.pm: MySQL returns zero on an update
+ when no values change. We would insert on an rv of zero, so now
+ we select count(*)... instead of relying on the rv of the update.
+
+2003-04-24 11:45 khoff
+
+ * FS/FS/domain_record.pm: Support for nWnDnHnMnS time format
+
+2003-04-23 19:46 ivan
+
+ * FS/FS/: part_export.pm, part_export/domain_shellcommands.pm:
+ better shellcommands documentation of all sorts
+
+2003-04-23 18:43 khoff
+
+ * FS/FS/part_export.pm, bin/bind.export: Support for exporting to
+ an ISC BIND9 name server
+
+2003-04-23 18:00 khoff
+
+ * FS/FS/part_export/sqlmail.pm: Apparently deleting elements from
+ svc_Common->hashref is bad.
+
+2003-04-23 16:12 ivan
+
+ * Makefile, htetc/handler.pl, htetc/handler.pl-1.0x,
+ httemplate/docs/install.html: compatible with mason 1.1! closes:
+ bug#492
+
+2003-04-23 15:16 ivan
+
+ * FS/FS/part_export.pm: might not be necessary, but to be safe...
+
+2003-04-22 22:36 khoff
+
+ * httemplate/search/cust_main.cgi: Missing comma.
+
+2003-04-22 21:53 khoff
+
+ * FS/bin/freeside-setup: DBD::Pg doesn't handle char types very
+ well.
+
+2003-04-22 11:46 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi: properly deprecate ancient apache &
+ sendmail config options
+
+2003-04-22 10:54 ivan
+
+ * httemplate/edit/part_export.cgi: mason is more strict about
+ variables - patch from Richard Siddall, thanks
+
+2003-04-21 21:39 ivan
+
+ * FS/MANIFEST, FS/FS/Conf.pm, FS/FS/part_export.pm,
+ FS/FS/svc_domain.pm, FS/FS/svc_forward.pm,
+ FS/FS/part_export/domain_shellcommands.pm,
+ FS/FS/part_export/forward_shellcommands.pm,
+ FS/t/part_export-forward_shellcommands.t,
+ httemplate/docs/install.html: - mysql 4.1 is available; update
+ documentation - remove last vestiges of 1.3-style qmail/vpopmail
+ exports from svc_domain and svc_forward; add appropriate
+ exports (closes: Bug#299)
+
+2003-04-21 15:40 ivan
+
+ * httemplate/view/svc_acct.cgi: typo
+
+2003-04-21 14:42 khoff
+
+ * FS/FS/addr_block.pm: renamed config option excludeaddr
+
+2003-04-21 14:40 khoff
+
+ * FS/FS/Conf.pm: renamed/clarified exclude_ip_addr option.
+
+2003-04-21 14:29 khoff
+
+ * httemplate/edit/: part_router_field.cgi, process/generic.cgi:
+ Navigation fixes.
+
+2003-04-21 14:13 khoff
+
+ * httemplate/edit/process/router.cgi: Fixed bug with $routernum and
+ new routers. Navigation cleanup.
+
+2003-04-21 14:12 khoff
+
+ * httemplate/edit/router.cgi: Lines added for clairity
+
+2003-04-21 13:53 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/part_bill_event.pm,
+ FS/bin/freeside-daily, FS/bin/freeside-expiration-alerter,
+ FS/bin/freeside-setup, httemplate/docs/schema.html,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/search/cust_main.cgi, httemplate/view/cust_main.cgi:
+ on-demand vs. automatic cards & checks: added DCRD and DCHK
+ payment types
+
+2003-04-21 13:31 khoff
+
+ * FS/FS/: part_export.pm, part_export/sqlmail.pm: Bug fixes for
+ sqlmail. Added support for courier and dovecot authentication.
+
+2003-04-21 13:27 khoff
+
+ * FS/FS/: Conf.pm, addr_block.pm: excludeaddr option for
+ svc_broadband
+
+2003-04-19 10:51 ivan
+
+ * FS/FS/: Conf.pm, part_pkg.pm: /^\s*$/ setup/recur expressions now
+ failsafe to 0 (closes: Bug#498) deprecate old 1.3-style qmail
+ integration
+
+2003-04-17 20:29 ivan
+
+ * httemplate/view/svc_acct.cgi: YA pg7.3 fix
+
+2003-04-02 06:52 ivan
+
+ * FS/FS/cust_main.pm: added order_pkgs sub
+
+2003-04-02 03:38 ivan
+
+ * FS/FS/: UID.pm, cust_main.pm: better callbacks
+
+2003-04-02 01:46 ivan
+
+ * httemplate/misc/process/meta-import.cgi: updated meta-import web
+ UI to allow duplicate import tables
+
+2003-04-01 19:23 ivan
+
+ * httemplate/edit/: cust_main.cgi, process/cust_main.cgi: fix
+ disappearing email invoice on errors, finally (closes: Bug#35)
+
+2003-04-01 00:03 ivan
+
+ * FS/FS/Record.pm, httemplate/docs/index.html,
+ httemplate/docs/upgrade10.html, httemplate/view/cust_main.cgi: -
+ update qsearch for Pg 7.3 - preliminary 1.5.0 upgrade docs -
+ syntax error in main customer view
+
+2003-03-31 22:55 ivan
+
+ * httemplate/: misc/cust_main-cancel.cgi, view/cust_main.cgi:
+ cancel button for customers (closes: Bug#25)
+
+2003-03-31 17:22 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/cust_pkg.pm, debian/changelog,
+ debian/control, httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_pkg.cgi: correct last_bill problems with $0
+ invoice (non-existant) edge cases
+
+2003-03-31 15:49 ivan
+
+ * httemplate/docs/schema.html: added last_bill column
+
+2003-03-31 15:48 ivan
+
+ * FS/bin/freeside-setup: add 'last_bill' column
+
+2003-03-28 21:34 ivan
+
+ * bin/apache.export: restart apache, not bind!
+
+2003-03-28 20:52 ivan
+
+ * FS/FS/cust_svc.pm, FS/FS/domain_record.pm,
+ FS/FS/part_export/www_shellcommands.pm,
+ httemplate/view/svc_www.cgi: correct web UI for svc_www services
+ & no more @.domain in www_shellcommands export
+
+2003-03-28 18:35 ivan
+
+ * FS/FS/part_export/www_shellcommands.pm: trim leading @. off zones
+
+2003-03-28 18:19 ivan
+
+ * FS/FS/part_export.pm: cust_svc and svc_x methods
+
+2003-03-28 13:59 ivan
+
+ * bin/apache.export: fix missing semicolon in apache export
+
+2003-03-28 13:43 ivan
+
+ * FS/FS/part_export/apache.pm: oops in null apache export
+
+2003-03-24 00:30 ivan
+
+ * httemplate/search/svc_acct.cgi: another Pg7.3 fix
+
+2003-03-19 20:36 ivan
+
+ * httemplate/edit/part_export.cgi: virtual wrap...
+
+2003-03-19 20:09 ivan
+
+ * httemplate/edit/part_export.cgi: wrap this textbox hard
+
+2003-03-19 20:00 ivan
+
+ * httemplate/edit/part_export.cgi: wide textareas
+
+2003-03-19 19:41 ivan
+
+ * FS/MANIFEST, FS/FS/domain_record.pm, FS/FS/part_export.pm,
+ FS/FS/part_export/apache.pm, FS/t/part_export-apache.t,
+ bin/apache.export, bin/bind.export, bin/bsdshell.export,
+ bin/sysvshell.export: apache export!
+
+2003-03-11 16:44 ivan
+
+ * httemplate/docs/upgrade9.html: doc from 1.4 branch
+
+2003-03-11 03:40 ivan
+
+ * FS/FS/svc_domain.pm: another pg7.3 fix
+
+2003-03-11 03:36 ivan
+
+ * httemplate/misc/: meta-import.cgi, process/meta-import.cgi: minor
+ meta import webUI updates
+
+2003-03-11 02:41 ivan
+
+ * httemplate/edit/part_pkg.cgi: another pg7.3 fix
+
+2003-03-03 17:56 khoff
+
+ * FS/MANIFEST: updated for svc_broadband changes
+
+2003-02-28 19:14 ivan
+
+ * FS/FS/cust_main.pm: change next bill date comparison from < to <=
+
+2003-02-24 21:40 ivan
+
+ * httemplate/edit/cust_main.cgi: remove max length on ACH account
+ number
+
+2003-02-11 17:21 khoff
+
+ * FS/bin/freeside-setup: s/;/,/
+
+2003-02-05 21:26 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_pkg.pm, FS/FS/cust_bill_pkg_detail.pm,
+ FS/FS/cust_main.pm, FS/bin/freeside-setup,
+ FS/t/cust_bill_pkg_detail.t, httemplate/docs/schema.html,
+ httemplate/docs/upgrade10.html, httemplate/edit/part_pkg.cgi:
+ time/data detail on invoices
+
+2003-02-05 15:22 khoff
+
+ * FS/FS/ac.pm, FS/FS/ac_block.pm, FS/FS/ac_field.pm,
+ FS/FS/ac_type.pm, FS/FS/part_ac_field.pm,
+ httemplate/browse/ac.cgi, httemplate/browse/ac_type.cgi,
+ httemplate/edit/ac.cgi, httemplate/edit/ac_type.cgi,
+ httemplate/edit/process/ac.cgi,
+ httemplate/edit/process/ac_block.cgi,
+ httemplate/edit/process/ac_field.cgi,
+ httemplate/edit/process/ac_type.cgi,
+ httemplate/edit/process/part_ac_field.cgi: svc_broadband rewrite
+
+2003-02-05 15:17 khoff
+
+ * FS/FS/addr_block.pm, FS/FS/part_router_field.pm,
+ FS/FS/part_sb_field.pm, FS/FS/part_svc_router.pm,
+ FS/FS/router.pm, FS/FS/router_field.pm, FS/FS/sb_field.pm,
+ FS/FS/svc_broadband.pm, httemplate/browse/addr_block.cgi,
+ httemplate/browse/generic.cgi,
+ httemplate/browse/part_sb_field.cgi,
+ httemplate/browse/router.cgi,
+ httemplate/edit/part_router_field.cgi,
+ httemplate/edit/part_sb_field.cgi, httemplate/edit/router.cgi,
+ httemplate/edit/svc_broadband.cgi,
+ httemplate/edit/process/addr_block/add.cgi,
+ httemplate/edit/process/addr_block/allocate.cgi,
+ httemplate/edit/process/addr_block/deallocate.cgi,
+ FS/bin/freeside-setup, httemplate/edit/process/generic.cgi,
+ httemplate/edit/process/router.cgi,
+ httemplate/edit/process/svc_broadband.cgi,
+ httemplate/edit/process/addr_block/split.cgi, htetc/global.asa,
+ htetc/handler.pl, httemplate/index.html,
+ httemplate/view/svc_broadband.cgi: svc_broadband rewrite
+
+2003-02-05 14:06 khoff
+
+ * FS/FS/cust_svc.pm: ip_netmask is gone now
+
+2003-02-04 14:04 ivan
+
+ * htetc/global.asa, httemplate/docs/install.html: 1.5-specific
+ fixes
+
+2003-01-27 23:47 ivan
+
+ * FS/bin/freeside-setup: eek, and this is what caused connectup to
+ fail too
+
+2003-01-27 22:08 ivan
+
+ * FS/FS/part_export/ldap.pm: crypt password export to ldap fix from
+ dave denney <daud@kaosol.net>
+
+2003-01-16 22:21 ivan
+
+ * FS/FS/ClientAPI/MyAccount.pm,
+ fs_selfservice/FS-SelfService/SelfService.pm: selfservice cancel
+ functionality
+
+2003-01-15 18:58 ivan
+
+ * FS/FS/part_export/shellcommands.pm: don't run empty shellcommands
+
+2003-01-14 02:15 ivan
+
+ * Makefile: selfservice
+
+2003-01-14 01:26 ivan
+
+ * FS/MANIFEST, FS/bin/freeside-selfservice-server,
+ fs_selfservice/freeside-selfservice-server, init.d/freeside-init:
+ move freeside-selfservice-server to proper MakeMaker install
+ location
+
+2003-01-14 00:49 ivan
+
+ * Makefile, FS/FS/svc_acct.pm, init.d/freeside-init: add
+ freeside-selfservice-server to init script add domsvc checking as
+ a foreign key
+
+2003-01-12 23:26 ivan
+
+ * httemplate/edit/part_export.cgi: another Pg 7.3 fix
+
+2003-01-12 22:19 ivan
+
+ * httemplate/edit/part_svc.cgi: whitespace inconsistancy causing
+ extraneous diffs
+
+2003-01-09 23:41 ivan
+
+ * FS/FS/cust_bill.pm: pop off an extra blank line in
+ business-onlinepayment options
+
+2002-12-28 01:16 ivan
+
+ * FS/FS/: cust_bill.pm, svc_acct.pm: prevent edge-case
+ business-onlinepayment mod_perl leakage in multi-database
+ installs. ugh.
+
+2002-12-27 04:56 ivan
+
+ * FS/FS/cust_main.pm: better times on failed billing events
+
+2002-12-24 22:59 ivan
+
+ * FS/bin/freeside-daily: declare $opt_p usage
+
+2002-12-24 14:41 ivan
+
+ * README.1.5.0pre1, FS/bin/freeside-setup,
+ httemplate/view/cust_main.cgi: optimization for ginourmous
+ numbers of packages for intergate, whew
+
+2002-12-23 15:56 ivan
+
+ * FS/FS/svc_acct.pm: remove gratuitous warning and better error
+ messages
+
+2002-12-23 07:21 ivan
+
+ * FS/FS/cust_bill.pm: make agent available to invoice templates
+
+2002-12-23 07:05 steve
+
+ * FS/FS/part_export/shellcommands.pm: add email address to
+ shellcommands
+
+ don't error out when importing unaudited accounts (even though
+ should probably be using $FS::svc_Common::noexport_hack anyway)
+
+2002-12-23 06:54 steve
+
+ * FS/FS/cust_pkg.pm: added stuff for selfservice_server-quiet,
+ signup_server-quiet, and emailcancel messages.
+
+2002-12-23 06:38 steve
+
+ * fs_selfservice/freeside-selfservice-server: added stuff for
+ selfservice_server-quiet, signup_server-quiet, and emailcancel
+ messages.
+
+2002-12-23 06:22 steve
+
+ * FS/FS/: cust_bill.pm, Conf.pm: added stuff for
+ selfservice_server-quiet, signup_server-quiet, and emailcancel
+ messages.
+
+2002-12-22 00:53 ivan
+
+ * FS/: FS/cust_pkg.pm, bin/freeside-daily: -p option for
+ freeside-daily to only run for a particular payby
+ $disable_agentcheck option for cust_pkg for import optimization
+
+2002-12-21 15:48 ivan
+
+ * httemplate/search/svc_domain.cgi: UI
+
+2002-12-21 15:44 ivan
+
+ * httemplate/search/svc_domain.cgi: don't display all accounts in
+ each domain
+
+2002-12-21 02:14 ivan
+
+ * FS/FS/cust_main.pm: could be multiple returns from these
+ searches, with taxclasses
+
+2002-12-21 02:02 ivan
+
+ * FS/FS/Record.pm: better qsearchs warning
+
+2002-12-20 23:36 ivan
+
+ * FS/FS/svc_acct.pm: $noexport hack moved to svc_Common
+
+2002-12-20 20:31 ivan
+
+ * FS/FS/svc_acct.pm: kludge around uninitialized value errors
+
+2002-12-19 19:29 ivan
+
+ * httemplate/view/cust_main.cgi: no changing cancelled packages
+
+2002-12-17 13:31 ivan
+
+ * FS/FS/cust_bill.pm: invoice_lines() fix
+
+2002-12-17 05:02 ivan
+
+ * httemplate/view/svc_acct.cgi: change wording
+
+2002-12-17 02:42 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: doh! but finally fixed
+
+2002-12-17 02:36 ivan
+
+ * FS/FS/cust_svc.pm: sqlradius time calculation fix wrt open
+ sessions
+
+2002-12-17 01:52 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: sheesh
+
+2002-12-17 01:48 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: sigh
+
+2002-12-17 01:47 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: is this broken or is the
+ calculation?
+
+2002-12-17 01:37 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: grr double doh
+
+2002-12-17 01:35 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: doh
+
+2002-12-17 01:30 ivan
+
+ * FS/bin/freeside-sqlradius-seconds: carriage return helps alot
+
+2002-12-17 01:24 ivan
+
+ * FS/: MANIFEST, bin/freeside-sqlradius-seconds: adding
+ freeside-sqlradius-seconds
+
+2002-12-16 22:36 ivan
+
+ * httemplate/docs/install.html: add PerlModule commands to install
+ instructions
+
+2002-12-16 13:52 ivan
+
+ * FS/FS/part_svc.pm, httemplate/edit/part_svc.cgi: fix for Pg 7.3,
+ are there others?
+
+2002-12-16 10:53 ivan
+
+ * httemplate/view/svc_domain.cgi: add PTR record to domain edit
+
+2002-12-16 02:47 ivan
+
+ * httemplate/graph/: money_time-graph.cgi, money_time.cgi: working
+ date range selector that defaults to the past year!
+
+2002-12-14 13:18 ivan
+
+ * FS/FS/cust_svc.pm: fix for auditing packages
+
+2002-12-12 16:31 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/process/meta-import.cgi:
+ taxclass fix (?)
+
+2002-12-12 13:44 ivan
+
+ * FS/FS/svc_acct.pm: custnum in welcome email
+
+2002-12-10 16:12 ivan
+
+ * httemplate/misc/: meta-import.cgi, process/meta-import.cgi:
+ beginning of web-based data importer
+
+2002-12-09 02:54 ivan
+
+ * httemplate/docs/install.html: doc
+
+2002-12-04 04:43 ivan
+
+ * bin/bind.export: really fixed now
+
+2002-12-04 04:37 ivan
+
+ * bin/bind.export: oops, typo
+
+2002-12-04 04:31 ivan
+
+ * FS/FS/cust_bill.pm: empty invoice_lines() fix
+
+2002-11-27 21:44 ivan
+
+ * FS/FS/part_export.pm: add -g FreeBSD shellcommands export as per
+ "Stephen Bechard" <steve@destek.net>
+
+2002-11-27 21:10 ivan
+
+ * FS/FS/Conf.pm, httemplate/docs/upgrade8.html: deprecate
+ username_policy
+
+2002-11-26 03:58 ivan
+
+ * httemplate/misc/process/link.cgi: link by username now only links
+ to same svcpart
+
+2002-11-26 02:25 ivan
+
+ * FS/FS/part_export/ldap.pm: ldap export: fix $crypt_password
+
+2002-11-25 21:42 ivan
+
+ * FS/FS/svc_acct.pm: remove harmless re-my to silence warning
+
+2002-11-25 02:46 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/edit/svc_acct.cgi: fix disappearing
+ radius group bug, whew
+
+2002-11-22 19:39 ivan
+
+ * httemplate/index.html: eww get rid of black border on konq3
+
+2002-11-22 04:19 ivan
+
+ * FS/FS/cust_bill.pm: fix nasty typo which would affect credit card
+ payments
+
+2002-11-22 03:14 ivan
+
+ * FS/FS/part_bill_event.pm, httemplate/edit/part_bill_event.cgi:
+ add lec billing event
+
+2002-11-22 02:48 ivan
+
+ * FS/FS/cust_main.pm: oops, one last LECB change
+
+2002-11-21 23:50 ivan
+
+ * FS/FS/: part_export.pm, part_export/ldap.pm: separate root and
+ user dn in ldap export
+
+2002-11-21 12:44 ivan
+
+ * FS/FS/part_export.pm: change DN labeling for those obtuse
+ blockheads at netmagic
+
+2002-11-20 13:13 ivan
+
+ * FS/FS/: part_export.pm, part_export/ldap.pm: add objectclass and
+ ability to have multiple comma-separated values to LDAP export
+
+2002-11-20 02:09 ivan
+
+ * FS/FS/part_export/ldap.pm: hmm, so you add the username to the DN
+ for the add call...? i don't get LDAP
+
+2002-11-20 01:10 ivan
+
+ * FS/FS/part_export/ldap.pm: fix silly bug in ldap export
+
+2002-11-20 01:07 ivan
+
+ * FS/bin/freeside-setup, httemplate/docs/upgrade9.html: ugh... need
+ to increase length of payinfo field in cust_pay and cust_refund
+ for ACH
+
+2002-11-19 14:55 ivan
+
+ * FS/FS/cust_bill_event.pm: give better error message on bad
+ invnum, also 'use FS::cust_bill' here
+
+2002-11-19 14:48 ivan
+
+ * FS/FS/cust_bill.pm: Business::OnlinePayment type is ECHECK not
+ CHECK
+
+2002-11-19 13:20 ivan
+
+ * FS/FS/part_export/ldap.pm: ldap export: don't use password if not
+ given
+
+2002-11-19 12:56 ivan
+
+ * FS/FS/part_export/ldap.pm: ldap export update
+
+2002-11-19 12:36 ivan
+
+ * FS/bin/freeside-setup: increase length of reczone and recdata
+ fields in domain_record
+
+2002-11-19 02:09 ivan
+
+ * FS/FS/cust_bill.pm: typo adding lec transactions
+
+2002-11-19 02:02 ivan
+
+ * FS/FS/cust_main.pm: silly regex bug parsing echeck info
+
+2002-11-19 01:51 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_refund.pm, FS/FS/part_bill_event.pm,
+ FS/FS/part_pkg.pm, FS/FS/part_export/ldap.pm,
+ httemplate/docs/schema.html, httemplate/edit/cust_main.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/view/cust_main.cgi: add LEC billing
+
+2002-11-18 21:17 ivan
+
+ * httemplate/edit/cust_main.cgi: oops, remove 10 digit account
+ number limit
+
+2002-11-18 02:15 ivan
+
+ * FS/MANIFEST, FS/FS/part_export.pm, FS/FS/part_export/ldap.pm,
+ FS/t/part_export-ldap.t, httemplate/edit/part_export.cgi:
+ preliminary ldap export
+
+2002-11-16 02:33 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm: separate ACH processor support
+
+2002-11-09 02:59 ivan
+
+ * httemplate/edit/part_pkg.cgi: javascript quoting problem in
+ per-hour charging
+
+2002-11-07 14:53 ivan
+
+ * FS/FS/cust_main.pm: doc
+
+2002-11-05 21:41 ivan
+
+ * httemplate/graph/money_time-graph.cgi: lala
+
+2002-11-05 20:23 ivan
+
+ * FS/FS/part_pkg.pm: safe regex for sqlradius hour/data billing,
+ closes: Bug#474
+
+2002-11-05 20:11 ivan
+
+ * httemplate/edit/part_pkg.cgi: data billing
+
+2002-11-05 19:58 ivan
+
+ * httemplate/edit/part_pkg.cgi: data charging
+
+2002-11-05 15:43 ivan
+
+ * httemplate/view/svc_acct.cgi: ui
+
+2002-11-05 15:41 ivan
+
+ * httemplate/: edit/part_pkg.cgi, view/svc_acct.cgi: fix for
+ correct radacct column names
+
+2002-11-05 15:34 ivan
+
+ * FS/FS/cust_svc.pm: can't use placeholders in SELECT SUM(?)
+
+2002-11-05 15:29 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/part_pkg.cgi, httemplate/view/svc_acct.cgi:
+ bandwidth charges from sqlradius
+
+2002-11-05 12:29 ivan
+
+ * FS/bin/freeside-sqlradius-radacctd: lost?
+
+2002-11-04 18:15 ivan
+
+ * httemplate/graph/money_time-graph.cgi: local kludge
+
+2002-11-04 15:40 ivan
+
+ * FS/FS/CGI.pm: balance on small_custview
+
+2002-11-04 13:20 ivan
+
+ * httemplate/docs/upgrade9.html: doc
+
+2002-11-04 12:51 ivan
+
+ * httemplate/view/svc_acct.cgi: fix cosmetic bug on online time
+ view
+
+2002-11-01 16:13 ivan
+
+ * httemplate/graph/: money_time-graph.cgi, money_time.cgi: whew,
+ glad i had a copy of this
+
+2002-10-28 05:22 ivan
+
+ * FS/bin/freeside-queued: signal-less queued child handling
+ (closes: Bug#477)
+
+2002-10-25 14:24 ivan
+
+ * FS/FS/part_export/shellcommands.pm: make $old_domain available
+ too
+
+2002-10-25 06:39 ivan
+
+ * httemplate/: browse/part_svc.cgi, edit/part_svc.cgi: show export
+ numbers
+
+2002-10-23 10:07 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-daily: database dump & scp support
+
+2002-10-23 08:49 ivan
+
+ * FS/FS/Conf.pm, httemplate/misc/unapply-cust_pay.cgi,
+ httemplate/view/cust_main.cgi: add option to unapply payments
+
+2002-10-22 02:15 ivan
+
+ * FS/FS/part_export/vpopmail.pm: bugfix in vpopmail restart
+
+2002-10-22 01:13 ivan
+
+ * FS/FS/part_bill_event.pm, httemplate/edit/part_bill_event.cgi:
+ ACH fixes from s5
+
+2002-10-21 23:28 ivan
+
+ * httemplate/edit/part_bill_event.cgi: oops - allow adding CHECK
+ invoice events too
+
+2002-10-21 08:20 ivan
+
+ * httemplate/edit/part_svc.cgi: don't disable for all items
+
+2002-10-21 08:14 ivan
+
+ * httemplate/edit/part_svc.cgi: Prevent a fixed or default username
+ or password from being defined
+
+2002-10-20 01:27 ivan
+
+ * FS/FS/: part_export.pm, part_export/shellcommands.pm: enable
+ shellcommands suspension/unsuspension hooks
+
+2002-10-20 00:26 ivan
+
+ * FS/FS/part_export/vpopmail.pm: don't run restart command unless
+ there is one
+
+2002-10-19 20:28 ivan
+
+ * FS/FS/part_export.pm, FS/FS/part_export/vpopmail.pm,
+ eg/vpopmailrestart: vpopmail restart export option
+
+2002-10-18 09:54 ivan
+
+ * Makefile: force executable permissions on bin/pod2x
+
+2002-10-18 06:28 ivan
+
+ * FS/bin/freeside-radgroup: argh
+
+2002-10-18 06:23 ivan
+
+ * FS/FS/svc_acct.pm: don't re-insert non-changed usernames to fuzzy
+ cache
+
+2002-10-18 03:28 ivan
+
+ * FS/: MANIFEST, bin/freeside-radgroup: adding
+
+2002-10-17 08:06 ivan
+
+ * httemplate/view/svc_acct.cgi: use consistant terminology
+
+2002-10-17 08:05 ivan
+
+ * httemplate/view/svc_acct.cgi: UI change for hour info
+
+2002-10-17 07:59 ivan
+
+ * FS/FS/cust_svc.pm: yay missing paren
+
+2002-10-17 07:50 ivan
+
+ * FS/FS/cust_svc.pm: *sigh* better debugging
+
+2002-10-17 07:46 ivan
+
+ * FS/FS/cust_svc.pm: really fix sqlradacct for old Pg
+
+2002-10-17 07:37 ivan
+
+ * FS/FS/cust_svc.pm: fix sqlradacct calculations for old Pg
+
+2002-10-17 07:33 ivan
+
+ * httemplate/view/svc_acct.cgi: sqlradacct hour update
+
+2002-10-17 07:16 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm, FS/FS/part_svc.pm,
+ FS/FS/svc_acct.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/view/svc_acct.cgi: radacct update: use sqlradius for
+ datasrc, not plandata options (whew)
+
+2002-10-17 04:17 ivan
+
+ * httemplate/search/cust_bill.cgi: another mason fix, this one from
+ 5
+
+2002-10-15 02:54 ivan
+
+ * FS/FS/cust_bill.pm: ach fix s/ECHECK/CHECK/
+
+2002-10-14 00:44 ivan
+
+ * FS/FS/Conf.pm: s/bool/checkbox/
+
+2002-10-14 00:30 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/svc_acct.cgi: svc-acct-alldomains
+ config file allows selection of accounts from any domain
+
+2002-10-13 23:17 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/edit/part_pkg.cgi: fix sql radacct
+ billing
+
+2002-10-13 23:16 ivan
+
+ * httemplate/view/svc_acct.cgi: show time online this billing cycle
+ on view account screen
+
+2002-10-13 00:14 ivan
+
+ * httemplate/docs/: session.html, upgrade9.html: doh
+
+2002-10-13 00:13 ivan
+
+ * httemplate/docs/upgrade9.html: msgcat docs for upgrade
+
+2002-10-12 23:49 ivan
+
+ * httemplate/docs/install.html: don't use ILIKE (7.1-ism) anymore
+
+2002-10-12 18:14 ivan
+
+ * FS/FS/Record.pm: change ILIKE into LOWER() for compatibility with
+ non-Pg and Pg before 7.1
+
+2002-10-12 18:05 ivan
+
+ * FS/FS/cust_bill.pm: bug fix in new ACH code
+
+2002-10-12 07:21 ivan
+
+ * httemplate/: edit/cust_main.cgi, view/cust_main.cgi: fix
+ dayphone/nightphone msgcat
+
+2002-10-12 06:46 ivan
+
+ * bin/populate-msgcat, httemplate/edit/cust_main.cgi,
+ httemplate/view/cust_main.cgi: dayphone/nightphone as
+ customizable labels, closes: Bug#464
+
+2002-10-12 06:26 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/part_pkg.cgi: sqlradacct_hour price plan to
+ charge per-hour against an external radacct table
+
+2002-10-12 03:15 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm,
+ FS/FS/part_bill_event.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/view/cust_main.cgi: ACH support
+
+2002-10-10 09:48 ivan
+
+ * httemplate/edit/cust_main.cgi: bugfix in payby-default HIDE
+ expiration dates
+
+2002-10-10 09:28 ivan
+
+ * httemplate/edit/cust_main.cgi: expiration date bugfix for HIDE
+
+2002-10-09 07:30 ivan
+
+ * FS/FS/Conf.pm: nasty typo
+
+2002-10-09 06:59 ivan
+
+ * FS/FS/Conf.pm: don't explicitly specify unclassified config
+ section
+
+2002-10-09 06:43 ivan
+
+ * FS/FS/part_export/vpopmail.pm: don't error out trying to create
+ existing directories in vpopmail export
+
+2002-10-09 06:07 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: radius-password config value to set
+ the attribute used for plaintext pw's
+
+2002-10-08 04:10 ivan
+
+ * httemplate/: index.html, search/cust_pkg.cgi: add suspended
+ package browse (closes: Bug#467)
+
+2002-10-08 03:50 ivan
+
+ * eg/export_template.pm: slightly less sucky
+
+2002-10-08 01:33 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/svc_acct.cgi: svc_acct-notes
+ displays static HTML on account view (closes: Bug#465)
+
+2002-10-07 21:46 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi,
+ httemplate/view/cust_main.cgi: payby-default config option, with
+ special "HIDE" option to disable billing information in the web
+ interface (closes: Bug#468)
+
+2002-10-07 01:47 ivan
+
+ * FS/bin/freeside-daily: cancel when it is *after* expiration date,
+ not when it is *before*
+
+2002-10-05 04:14 ivan
+
+ * FS/FS/part_export/sqlradius.pm: fix sqlradius export to not set
+ blank id fields
+
+2002-10-04 05:56 ivan
+
+ * FS/FS/type_pkgs.pm, FS/FS/part_export/sqlradius.pm,
+ FS/bin/freeside-setup, httemplate/docs/install.html,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/misc/change_pkg.cgi, httemplate/search/cust_main.cgi,
+ httemplate/view/cust_main.cgi: working on the road: - easier
+ "change package" link for changing one package to another -
+ sqlradius export now compatible with Pg - indices on phone
+ numbers - install instructions specify Pg 7.1 (at least until
+ ILIKE thing is changed) - searching on phone number fragments
+
+2002-10-04 05:39 ivan
+
+ * FS/bin/freeside-daily: turn on AutoCommit when vacuuming
+
+2002-10-04 05:29 ivan
+
+ * FS/bin/freeside-count-active-customers: adding
+
+2002-10-04 05:09 ivan
+
+ * FS/FS/cust_bill.pm: default for customers with no invoices was:
+ print is now: send email to invoice from address
+
+2002-10-03 08:29 ivan
+
+ * FS/FS/svc_acct.pm: fix implicit RADIUS password attribute to be
+ Crypt-Password for encrypted pw's
+
+2002-09-27 05:14 ivan
+
+ * FS/FS/svc_acct.pm: allow + in md5 encrypted passwords
+
+2002-09-27 05:14 ivan
+
+ * FS/FS/UID.pm: don't chop blanks
+
+2002-09-26 23:00 ivan
+
+ * htetc/global.asa: only load Devel::AutoProfiler if it is
+ installed
+
+2002-09-26 22:36 ivan
+
+ * FS/bin/freeside-adduser: lock mapsecrets file
+
+2002-09-25 22:28 ivan
+
+ * Makefile: fix $INIT_FILE usage
+
+2002-09-25 22:26 ivan
+
+ * FS/FS/svc_acct.pm: fix for inserting un-audited accounts
+
+2002-09-25 22:25 ivan
+
+ * Makefile: oops
+
+2002-09-25 21:45 ivan
+
+ * httemplate/search/svc_acct.cgi: tyop
+
+2002-09-25 02:11 ivan
+
+ * Makefile, httemplate/docs/install.html: doc
+
+2002-09-25 02:09 ivan
+
+ * FS/FS/raddb.pm: regenerate raddb.pm from freeradius-0.4 dicts add
+ "Authentication-Type" for netc.net.au (radiator?)
+
+2002-09-24 01:31 ivan
+
+ * FS/FS/Conf.pm: deprecate vpopmailrestart config value
+
+2002-09-23 07:27 ivan
+
+ * FS/FS/CGI.pm, htetc/global.asa, htetc/handler.pl: global.asa
+ changes for profiling redirects header-handling changes necessary
+ for chart .cgis
+
+2002-09-23 01:50 ivan
+
+ * Makefile: better default Pg datasource
+
+2002-09-21 04:17 ivan
+
+ * README.1.5.0pre1, FS/bin/freeside-setup,
+ httemplate/docs/schema.dia, httemplate/docs/schema.html,
+ FS/FS/cust_bill.pm, FS/FS/cust_bill_pkg.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_main_county.pm, bin/create-history-tables,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/process/cust_main_county.cgi: all taxes now have
+ names. closes: Bug#15
+
+2002-09-20 08:49 ivan
+
+ * README: d
+
+2002-09-20 08:49 ivan
+
+ * FS/Makefile.PL: installing into /usr/bin, bah
+
+2002-09-20 08:48 ivan
+
+ * FS/FS.pm: doc
+
+2002-09-20 08:48 ivan
+
+ * Makefile: 1.5.0
+
+2002-09-20 08:47 ivan
+
+ * FS/MANIFEST, FS/bin/freeside-addoutsourceuser,
+ FS/bin/freeside-deloutsource, FS/bin/freeside-deloutsourceuser,
+ FS/bin/freeside-deluser, FS/bin/freeside-setup,
+ bin/populate-msgcat: add freeside-deluser, freeside-deloutsource
+ and freeside-deloutsourceuser
+
+2002-09-20 08:46 ivan
+
+ * FS/FS/Record.pm: fix database sequence code, closes: Bug#69
+
+2002-09-20 07:48 ivan
+
+ * FS/FS/part_export.pm: extraneous warn
+
+2002-09-20 05:50 ivan
+
+ * FS/bin/freeside-setup, bin/fs-setup,
+ httemplate/docs/install.html: move from bin/fs-setup to
+ FS/bin/freeside-setup
+
+2002-09-20 05:04 ivan
+
+ * README.1.5.0pre1, httemplate/docs/install.html: doc NetAddr::IP
+ dependancy
+
+2002-09-20 03:16 ivan
+
+ * README.1.5.0pre1, FS/FS/UID.pm, bin/fs-setup: change otaker
+ fields to 32 chars
+
+2002-09-19 06:34 ivan
+
+ * bin/fs-setup, FS/FS/Record.pm, bin/dbdef-create: use database
+ SERIAL or AUTO_INCREMENT for primary keys, finally, yay! closes:
+ bug#69
+
+2002-09-19 06:25 ivan
+
+ * README.1.5.0pre1: preliminary upgrade instructions
+
+2002-09-19 01:43 ivan
+
+ * FS/bin/freeside-daily: package expiration
+
+2002-09-19 01:34 ivan
+
+ * FS/FS/svc_acct.pm: remove extra definition of $cust_pkg
+
+2002-09-19 00:15 ivan
+
+ * httemplate/view/cust_pkg.cgi: cancel later in view UI... hmm
+
+2002-09-18 15:50 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main_invoice.pm, FS/FS/svc_acct.pm,
+ FS/bin/freeside-email, httemplate/edit/svc_forward.cgi,
+ httemplate/search/svc_acct.cgi, httemplate/search/svc_domain.cgi,
+ httemplate/view/svc_acct.cgi: remove domain config file, closes:
+ Bug#269
+
+2002-09-18 15:38 ivan
+
+ * FS/FS/: svc_domain.pm, cust_svc.pm: removing svc_acct_sm
+
+2002-09-18 05:10 ivan
+
+ * httemplate/docs/: index.html, legacy.html, upgrade4.html,
+ upgrade5.html, upgrade6.html: doc
+
+2002-09-17 03:21 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ httemplate/edit/part_bill_event.cgi: remove obsolete cybercash
+ support
+
+2002-09-17 02:19 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/InitHandler.pm, FS/FS/cust_pkg.pm,
+ FS/FS/part_export.pm, FS/FS/part_svc.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_acct_sm.pm, FS/t/svc_acct_sm.t, httemplate/index.html,
+ httemplate/docs/schema.dia, httemplate/docs/schema.html,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct_sm.cgi,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/edit/process/svc_acct_sm.cgi,
+ httemplate/misc/link.cgi, httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_acct_sm.html,
+ httemplate/search/svc_domain.cgi,
+ httemplate/view/svc_acct_sm.cgi: remove svc_acct_sm
+
+2002-09-16 17:40 ivan
+
+ * FS/FS/cust_bill.pm: send_ftp doc fix
+
+2002-09-16 17:33 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm, cust_main.pm, svc_acct.pm: -
+ "emailinvoiceauto" implementation rewritten to work properly,
+ stop
+ overwriting existing invoice destinations
+
+2002-09-16 02:27 ivan
+
+ * FS/bin/freeside-expiration-alerter: skip empty expiration dates
+
+2002-09-11 02:28 ivan
+
+ * httemplate/browse/svc_acct_pop.cgi: mason error
+
+2002-09-11 02:09 ivan
+
+ * httemplate/search/cust_main.cgi: mason warnings
+
+2002-09-09 20:31 ivan
+
+ * FS/FS/cust_bill.pm: Business::OnlinePaymet fix for processors w/o
+ order numbers, like VirtualNet
+
+2002-09-09 17:37 ivan
+
+ * httemplate/docs/upgrade9.html: doc: need Net::SSH 0.07 for 1.4.1
+
+2002-09-09 16:05 khoff
+
+ * httemplate/: browse/ac.cgi, browse/ac_type.cgi, edit/ac.cgi,
+ edit/ac_type.cgi, edit/svc_broadband.cgi, edit/process/ac.cgi,
+ edit/process/ac_block.cgi, edit/process/ac_field.cgi,
+ edit/process/ac_type.cgi, edit/process/part_ac_field.cgi,
+ edit/process/svc_broadband.cgi, view/svc_broadband.cgi:
+ svc_broadband merge
+
+2002-09-09 16:01 khoff
+
+ * FS/FS/cust_svc.pm, FS/FS/part_export.pm, bin/fs-setup,
+ htetc/global.asa, httemplate/index.html,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi, FS/FS/ac.pm,
+ FS/FS/ac_block.pm, FS/FS/ac_field.pm, FS/FS/ac_type.pm,
+ FS/FS/part_ac_field.pm, FS/FS/svc_broadband.pm: svc_broadband
+ merge
+
+2002-09-09 15:57 ivan
+
+ * FS/bin/: freeside-cc-receipts-report, freeside-credit-report,
+ freeside-receivables-report, freeside-tax-report: allow . in
+ untaint_argv, for usernames
+
+2002-09-09 15:56 khoff
+
+ * FS/MANIFEST: svc_broadband merge
+
+2002-09-09 12:56 ivan
+
+ * FS/FS/part_export.pm: vpopmail export doc clarification: ssh as
+ vpopmail user
+
+2002-09-09 12:54 ivan
+
+ * httemplate/docs/ssh.html: doc clarification about users
+
+2002-09-09 05:34 ivan
+
+ * FS/FS/: ClientAPI.pm, InitHandler.pm, svc_acct.pm,
+ ClientAPI/passwd.pm: : is not legal in GECOS
+
+2002-09-08 05:57 ivan
+
+ * FS/FS/svc_domain.pm: ordering fix on delete: domain_record
+ records first, then svc_domain
+
+2002-09-08 05:40 ivan
+
+ * bin/bind.export: error out if can't open .HEADER files
+
+2002-09-08 04:36 ivan
+
+ * README: 1.4.1
+
+2002-09-08 02:49 ivan
+
+ * conf/: soadefaultttl, soaexpire, soarefresh, soaretry: some
+ useful default domain files
+
+2002-09-07 07:20 ivan
+
+ * FS/FS/part_export.pm: mention docs/ssh.html in vpopmail
+ description, give up hoping for a better description of the
+ export from jeff
+
+2002-09-06 19:27 ivan
+
+ * httemplate/docs/: index.html, upgrade9.html: 1.4.1
+
+2002-09-06 19:19 ivan
+
+ * FS/FS/part_export/vpopmail.pm: import flocking constants
+
+2002-09-05 11:51 ivan
+
+ * FS/FS/part_export/vpopmail.pm: clear up directory silliness ick
+
+2002-09-05 10:01 ivan
+
+ * httemplate/edit/cust_main.cgi: "same as billing address" box
+ would uncheck itself on errors (only looked at previous
+ ship_last, not CGI checkbox value too) Closes: Bug#448
+
+2002-09-05 09:51 ivan
+
+ * FS/FS/cust_bill.pm: Business::OnlinePayment::VitualNet
+ compatibility
+
+2002-09-05 06:59 ivan
+
+ * ANNOUCE.1.4.0: naw
+
+2002-09-05 06:50 ivan
+
+ * httemplate/browse/part_svc.cgi: don't show "clone an existing
+ service definition" if there aren't any yet
+
+2002-09-05 06:27 ivan
+
+ * FS/FS/cust_main.pm, httemplate/index.html,
+ httemplate/misc/cust_main-import_charges.cgi,
+ httemplate/misc/process/cust_main-import_charges.cgi: batch
+ charge/credit import
+
+2002-09-05 06:01 ivan
+
+ * FS/FS/svc_acct.pm: show illegal dir in error msg
+
+2002-09-05 02:10 ivan
+
+ * FS/FS/: part_export.pm, part_export/vpopmail.pm: get rid of
+ extraneous `vpopmail machine' field
+
+2002-09-04 05:43 ivan
+
+ * Makefile, httemplate/index.html: 1.4.1beta1
+
+2002-09-04 01:42 ivan
+
+ * httemplate/edit/part_pkg.cgi: spelling
+
+2002-08-30 16:48 ivan
+
+ * FS/FS/cust_bill.pm: oops, missing charged column in csv exports
+
+2002-08-30 16:42 ivan
+
+ * httemplate/edit/part_bill_event.cgi, FS/FS/cust_bill.pm,
+ httemplate/edit/process/part_bill_event.cgi: new invoice event:
+ upload a CSV file
+
+2002-08-30 16:17 ivan
+
+ * FS/FS/cust_main.pm: bill batch imported customers immediately (as
+ of their cust_pkg.bill date) - setting cust_pkg.bill date
+ directly bypasses setup fee
+
+2002-08-30 10:34 ivan
+
+ * FS/FS/cust_main.pm, httemplate/index.html,
+ httemplate/misc/cust_main-import.cgi,
+ httemplate/misc/process/cust_main-import.cgi: working CSV import
+ for crcmn
+
+2002-08-30 04:33 ivan
+
+ * httemplate/edit/cust_main.cgi: use eidiot rather than die for
+ (hopefully) better error message
+
+2002-08-30 03:25 ivan
+
+ * Makefile: silly conf/registries dir still hanging around in CVS
+ checkouts
+
+2002-08-29 07:11 ivan
+
+ * httemplate/search/cust_pkg.cgi: UI ROWSPAN fix
+
+2002-08-29 02:11 ivan
+
+ * FS/FS/svc_acct.pm: dont require uid for finger and quota, fix md5
+ import, make username/password for unexported services conflict
+ at least with self
+
+2002-08-29 01:13 khoff
+
+ * httemplate/view/svc_domain.cgi: Missing Mason/Apache::ASP tags
+
+2002-08-29 00:50 ivan
+
+ * FS/FS/: part_export.pm, part_export/vpopmail.pm: vpopmail updates
+
+2002-08-28 23:02 ivan
+
+ * FS/FS/UID.pm: stupid kludge until schema otakers are not 8 chars
+
+2002-08-28 22:57 ivan
+
+ * httemplate/view/svc_acct.cgi: password viewing UI change
+
+2002-08-27 00:26 khoff
+
+ * FS/FS/Record.pm: Yip yip, I sprained my brain
+
+2002-08-26 13:40 ivan
+
+ * FS/FS/cust_pkg.pm: allow . and - in otaker usernames
+
+2002-08-24 20:54 ivan
+
+ * FS/bin/freeside-addoutsourceuser: ?
+
+2002-08-24 20:42 ivan
+
+ * FS/bin/freeside-addoutsourceuser: correct secrets file path
+
+2002-08-24 19:35 ivan
+
+ * FS/FS/part_export/shellcommands.pm: separate vars for quoted
+ passwords
+
+2002-08-24 19:26 ivan
+
+ * FS/FS/part_export/shellcommands.pm: properly quote password as
+ well as finger
+
+2002-08-24 18:48 ivan
+
+ * FS/bin/freeside-addoutsourceuser: password
+
+2002-08-24 18:48 ivan
+
+ * FS/: MANIFEST, bin/freeside-addoutsourceuser: Added Files:
+ bin/freeside-addoutsourceuser
+
+2002-08-24 18:16 ivan
+
+ * FS/bin/: freeside-adduser, freeside-setup: doc
+
+2002-08-24 18:14 ivan
+
+ * FS/bin/freeside-setup: noninteractive freeside-setup
+
+2002-08-24 18:09 ivan
+
+ * FS/bin/: freeside-adduser, freeside-setup: doc
+
+2002-08-24 01:13 ivan
+
+ * FS/bin/freeside-adduser: also do -b flag
+
+2002-08-24 00:43 ivan
+
+ * FS/FS/part_export/shellcommands.pm: allow $domain as a variable
+ in commands
+
+2002-08-24 00:20 ivan
+
+ * httemplate/edit/part_export.cgi: don't substitute defaults for
+ empty options when editing exports
+
+2002-08-23 23:41 ivan
+
+ * FS/FS/part_export/: domain_shellcommands.pm, shellcommands.pm,
+ www_shellcommands.pm: - depend on Net::SSH 0.07, for OpenSSH -T
+ fix - no strict 'vars'; when necessary
+
+2002-08-23 20:29 ivan
+
+ * httemplate/: view/cust_main.cgi, misc/unprovision.cgi:
+ unprovision a single service
+
+2002-08-23 19:10 ivan
+
+ * FS/bin/freeside-addoutsource: fix path
+
+2002-08-23 18:53 ivan
+
+ * FS/bin/freeside-queued: depend on Net::SSH 0.07, for -T fix
+
+2002-08-23 17:51 ivan
+
+ * httemplate/view/cust_main.cgi: UI
+
+2002-08-23 17:16 ivan
+
+ * FS/: MANIFEST, bin/freeside-addoutsource: Added Files:
+ bin/freeside-addoutsource
+
+2002-08-23 16:43 ivan
+
+ * FS/: MANIFEST, FS/part_export.pm,
+ FS/part_export/domain_shellcommands.pm,
+ t/part_export-domain_shellcommands.t: add domain_shellcommands
+ export
+
+2002-08-19 16:08 ivan
+
+ * httemplate/view/cust_main.cgi: encode_entities for comments
+ entries
+
+2002-08-11 23:17 ivan
+
+ * rt/: COPYING, ChangeLog, Makefile, README, TODO,
+ bin/initacls.Oracle, bin/initacls.Pg, bin/initacls.mysql,
+ bin/mason_handler.fcgi, bin/mason_handler.scgi, bin/rt,
+ bin/rt-mailgate, bin/rtadmin, bin/webmux.pl, docs/README.docs,
+ docs/Security, docs/rt.gif, docs/design_docs/CARS,
+ docs/design_docs/TransactionTypes.txt, docs/design_docs/acls,
+ docs/design_docs/basic-definitions.txt,
+ docs/design_docs/cli_spec, docs/design_docs/cvs_integration,
+ docs/design_docs/evil_plans,
+ docs/design_docs/link-definitions.txt,
+ docs/design_docs/local_hacking,
+ docs/design_docs/subscription-definitions.txt,
+ docs/design_docs/users, etc/acl.Oracle, etc/acl.Pg,
+ etc/acl.mysql, etc/config.pm, etc/rt.spec, etc/schema.Oracle,
+ etc/schema.Pg, etc/schema.mysql, etc/schema.pm, lib/MANIFEST,
+ lib/MANIFEST.SKIP, lib/Makefile.PL, lib/RT.pm, lib/test.pl,
+ lib/RT/ACE.pm, lib/RT/ACL.pm, lib/RT/Attachment.pm,
+ lib/RT/Attachments.pm, lib/RT/CurrentUser.pm, lib/RT/Date.pm,
+ lib/RT/EasySearch.pm, lib/RT/Group.pm, lib/RT/GroupMember.pm,
+ lib/RT/GroupMembers.pm, lib/RT/Groups.pm, lib/RT/Handle.pm,
+ lib/RT/Keyword.pm, lib/RT/KeywordSelect.pm,
+ lib/RT/KeywordSelects.pm, lib/RT/Keywords.pm, lib/RT/Link.pm,
+ lib/RT/Links.pm, lib/RT/ObjectKeyword.pm,
+ lib/RT/ObjectKeywords.pm, lib/RT/Queue.pm, lib/RT/Queues.pm,
+ lib/RT/Record.pm, lib/RT/Scrip.pm, lib/RT/ScripAction.pm,
+ lib/RT/ScripActions.pm, lib/RT/ScripCondition.pm,
+ lib/RT/ScripConditions.pm, lib/RT/Scrips.pm, lib/RT/Template.pm,
+ lib/RT/Templates.pm, lib/RT/TestHarness.pm, lib/RT/Ticket.pm,
+ lib/RT/Tickets.pm, lib/RT/Transaction.pm, lib/RT/Transactions.pm,
+ lib/RT/User.pm, lib/RT/Users.pm, lib/RT/Watcher.pm,
+ lib/RT/Watchers.pm, lib/RT/Action/Autoreply.pm,
+ lib/RT/Action/Generic.pm, lib/RT/Action/Notify.pm,
+ lib/RT/Action/NotifyAsComment.pm, lib/RT/Action/OpenDependent.pm,
+ lib/RT/Action/ResolveMembers.pm, lib/RT/Action/SendEmail.pm,
+ lib/RT/Action/SendPasswordEmail.pm,
+ lib/RT/Action/StallDependent.pm,
+ lib/RT/Condition/AnyTransaction.pm, lib/RT/Condition/Generic.pm,
+ lib/RT/Condition/NewDependency.pm,
+ lib/RT/Condition/StatusChange.pm, lib/RT/Interface/CLI.pm,
+ lib/RT/Interface/Email.pm, lib/RT/Interface/Web.pm: import rt
+ 2.0.14
+
+2002-08-02 17:39 ivan
+
+ * httemplate/docs/: install.html, legacy.html: remove some extra
+ modules from install doc
+
+2002-07-31 11:55 ivan
+
+ * FS/FS/part_export.pm: delete directories when deleting users on
+ freebsd
+
+2002-07-31 06:18 ivan
+
+ * FS/FS/svc_acct.pm: fix bug with static IP addresses
+
+2002-07-26 20:56 ivan
+
+ * httemplate/misc/cancel-unaudited.cgi: move transaction from web
+ interface to lib code
+
+2002-07-26 19:47 ivan
+
+ * FS/bin/freeside-daily: vacuum pg databases daily
+
+2002-07-25 19:33 ivan
+
+ * FS/FS/: part_export.pm, part_export/shellcommands.pm:
+ shellcommands edit gecos field too
+
+2002-07-25 18:38 ivan
+
+ * httemplate/docs/install.html: tyop
+
+2002-07-25 18:31 ivan
+
+ * httemplate/docs/install.html: tyop
+
+2002-07-23 05:37 ivan
+
+ * FS/FS/svc_acct.pm: fix small bugs in duplicate username checking
+
+2002-07-22 03:50 ivan
+
+ * FS/t/ClientAPI.t, fs_selfservice/FS-SelfService/cgi/passwd.html:
+ adding
+
+2002-07-22 03:41 ivan
+
+ * httemplate/docs/install.html: no mysql in 1.4.0 release.
+ hopefully 1.4.1
+
+2002-07-22 03:20 ivan
+
+ * ANNOUCE.1.4.0, Makefile, README.1.4.0pre11, README.1.4.0pre12,
+ README.1.4.0pre13, README.1.4.0pre14, README.1.4.0pre8,
+ README.1.4.0pre9: 1.4.0
+
+2002-07-22 03:18 ivan
+
+ * FS/FS/CGI.pm: UI
+
+2002-07-22 03:18 ivan
+
+ * FS/FS/cust_main.pm: sort bills by date
+
+2002-07-16 05:29 ivan
+
+ * FS/: MANIFEST, FS/ClientAPI.pm, FS/ClientAPI/MyAccount.pm,
+ FS/ClientAPI/passwd.pm: ClientAPI
+
+2002-07-16 05:28 ivan
+
+ * fs_selfservice/: DEPLOY, freeside-selfservice-server,
+ fs_passwd_test, FS-SelfService/SelfService.pm,
+ FS-SelfService/freeside-selfservice-clientd,
+ FS-SelfService/cgi/login.html, FS-SelfService/cgi/myaccount.html,
+ FS-SelfService/cgi/selfservice.cgi,
+ FS-SelfService/cgi/view_invoice.html: invoice viewing...
+
+2002-07-15 20:57 ivan
+
+ * FS/FS/part_export/infostreet.pm: missing ; in eval'ed sub
+
+2002-07-15 20:56 ivan
+
+ * FS/FS/part_export/infostreet.pm: better error handling
+
+2002-07-15 20:47 ivan
+
+ * FS/FS/part_export/infostreet.pm: fix XML-RPC weirdness
+
+2002-07-15 01:28 ivan
+
+ * fs_selfservice/: freeside-selfservice-server, fs_passwd_test,
+ FS-SelfService/freeside-selfservice-clientd: working framework,
+ no hung clients, whew
+
+2002-07-14 18:44 ivan
+
+ * bin/bind.import: finally a working DNS::ZoneParse
+
+2002-07-13 17:28 ivan
+
+ * httemplate/docs/: install.html, upgrade8.html: no mason 1.1x yet
+ :(
+
+2002-07-11 06:52 ivan
+
+ * fs_selfservice/: DEPLOY, freeside-selfservice-server,
+ fs_passwd_test, FS-SelfService/MANIFEST,
+ FS-SelfService/SelfService.pm,
+ FS-SelfService/freeside-selfservice-clientd: finally working
+ async framework
+
+2002-07-08 19:23 ivan
+
+ * httemplate/docs/: schema.dia, schema.png: new schema diagram
+
+2002-07-08 10:14 ivan
+
+ * FS/bin/freeside-queued: oops, fix for bug only surfacing with
+ different freeside uid/gid
+
+2002-07-08 08:56 ivan
+
+ * httemplate/edit/svc_acct.cgi: better error message
+
+2002-07-08 06:07 ivan
+
+ * httemplate/edit/: REAL_cust_pkg.cgi, process/REAL_cust_pkg.cgi:
+ edit expiration dates
+
+2002-07-08 03:52 ivan
+
+ * httemplate/search/cust_bill.cgi: remove perl-side sort routines,
+ no longer needed
+
+2002-07-08 03:01 ivan
+
+ * FS/FS/part_export.pm: default linux/netbsd shellcommand userdel
+ should remove home directories
+
+2002-07-08 01:39 ivan
+
+ * httemplate/docs/install.html: new and improved instructions! now
+ even more idiot proof!
+
+2002-07-07 10:49 ivan
+
+ * FS/FS/svc_acct.pm: rewrite uid-dup checking to be
+ new-export-aware, closes: #431
+
+2002-07-07 07:33 ivan
+
+ * httemplate/search/cust_bill.cgi: clean up after rewrite... turned
+ out rather nice
+
+2002-07-07 07:28 ivan
+
+ * httemplate/search/cust_bill.cgi: fixing 30/60/90/120...
+
+2002-07-07 07:26 ivan
+
+ * httemplate/search/cust_bill.cgi: just might work
+
+2002-07-07 07:24 ivan
+
+ * httemplate/search/cust_bill.cgi: that would be nice...
+
+2002-07-07 07:18 ivan
+
+ * httemplate/search/cust_bill.cgi: fix 30/60/90/120 browses
+
+2002-07-07 07:14 ivan
+
+ * httemplate/search/cust_bill.cgi: UI
+
+2002-07-07 07:12 ivan
+
+ * httemplate/search/cust_bill.cgi: totals and order by
+
+2002-07-07 07:00 ivan
+
+ * httemplate/search/cust_bill.cgi: try for correct totals
+
+2002-07-07 06:52 ivan
+
+ * httemplate/search/cust_bill.cgi: don't recalculate owed
+
+2002-07-07 06:31 ivan
+
+ * httemplate/search/cust_bill.cgi: comma
+
+2002-07-07 06:30 ivan
+
+ * httemplate/search/cust_bill.cgi: calculate owed as subquery here
+ too
+
+2002-07-07 06:25 ivan
+
+ * httemplate/search/cust_bill.cgi: don't forget to where the where
+ clause
+
+2002-07-07 06:23 ivan
+
+ * httemplate/search/cust_bill.cgi: try for working paged invoices,
+ this time with subqueries
+
+2002-07-07 04:45 ivan
+
+ * httemplate/search/cust_bill.cgi: wtf?
+
+2002-07-07 04:30 ivan
+
+ * httemplate/search/cust_bill.cgi: invoice search with possibly
+ working pager
+
+2002-07-07 04:03 ivan
+
+ * httemplate/search/cust_bill.cgi: move query logic from perl to
+ sql for scalability
+
+2002-07-06 05:53 ivan
+
+ * httemplate/docs/install.html: new! improved! now even more
+ idiot-proof!
+
+2002-07-06 05:15 ivan
+
+ * Makefile: beta2
+
+2002-07-06 05:13 ivan
+
+ * bin/fs-setup: fix Can't use an undefined value as an ARRAY
+ reference at ./fs-setup line 209, <STDIN> line 3.
+
+2002-07-06 04:08 ivan
+
+ * httemplate/: edit/svc_forward.cgi, view/svc_forward.cgi: fix UI
+ for forwards - use HTML::Widgets::SelectLayers, closes: Bug#303
+
+2002-07-06 01:50 ivan
+
+ * httemplate/view/svc_acct.cgi: add "are you sure?" javascript on
+ view/svc_acct.cgi "cancel unaudited account" (closes: Bug#432)
+
+2002-07-06 01:29 ivan
+
+ * httemplate/view/: cust_pkg.cgi, cust_main.cgi: speling
+
+2002-07-06 00:32 ivan
+
+ * httemplate/edit/part_pkg.cgi: fix speling
+
+2002-07-06 00:31 ivan
+
+ * ANNOUCE.1.4.0: this file should probably go away soon...
+
+2002-07-06 00:30 ivan
+
+ * FS/FS/: Conf.pm, part_export.pm, svc_www.pm,
+ part_export/www_shellcommands.pm: move svc_www ssh jobs to the
+ job queue & exports, and make them configurable
+
+2002-07-05 21:20 ivan
+
+ * FS/FS/svc_acct.pm: typo
+
+2002-07-05 16:32 ivan
+
+ * httemplate/config/config.cgi: fix bug with config having a value
+ not in the select
+
+2002-07-04 03:35 ivan
+
+ * httemplate/browse/: agent.cgi, agent_type.cgi,
+ part_bill_event.cgi, part_export.cgi, part_pkg.cgi,
+ part_referral.cgi, part_svc.cgi, svc_acct_pop.cgi: move "add"
+ links to the top
+
+2002-07-03 07:45 ivan
+
+ * FS/t/part_export-http.t: s/_post//
+
+2002-07-03 07:21 ivan
+
+ * FS/: MANIFEST, FS/part_export.pm, FS/part_export/http.pm,
+ t/part_export-http.t: http export
+
+2002-07-03 04:37 ivan
+
+ * FS/FS/InitHandler.pm: 54
+
+2002-07-03 04:35 ivan
+
+ * FS/FS/InitHandler.pm: sacrifice memory for speed
+
+2002-07-03 04:31 ivan
+
+ * FS/FS/InitHandler.pm: preload modules
+
+2002-07-03 04:23 ivan
+
+ * FS/FS/: InitHandler.pm, UID.pm: fix to allow running during
+ apache init
+
+2002-07-03 04:10 ivan
+
+ * FS/FS/UID.pm: allow InitHandler to work during apache startup
+
+2002-07-03 03:50 ivan
+
+ * FS/FS/InitHandler.pm: skip comment & blank lines
+
+2002-07-03 03:48 ivan
+
+ * FS/FS/InitHandler.pm: debug
+
+2002-07-03 03:33 ivan
+
+ * FS/: MANIFEST, FS/InitHandler.pm, t/InitHandler.t: preload all
+ dbdefs
+
+2002-07-02 20:57 ivan
+
+ * FS/FS/cust_main.pm: working one-time charges again
+
+2002-07-02 20:52 ivan
+
+ * httemplate/view/cust_main.cgi: ui
+
+2002-07-02 20:47 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/quick-charge.cgi,
+ httemplate/view/cust_main.cgi: one-time charges with tax classes
+
+2002-07-02 19:25 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi: deprecate text radius config
+ options update config docs
+
+2002-07-02 18:47 ivan
+
+ * FS/: MANIFEST, bin/freeside-reexport: freeside-reexport
+
+2002-07-02 18:05 ivan
+
+ * FS/bin/freeside-sqlradius-reset: pod
+
+2002-07-02 18:01 ivan
+
+ * FS/bin/freeside-sqlradius-reset: fix usage message
+
+2002-07-02 16:03 ivan
+
+ * FS/FS/part_export.pm: fix usermod commands for freebsd
+
+2002-07-02 07:38 ivan
+
+ * ANNOUCE.1.4.0: beta1!
+
+2002-07-02 07:37 ivan
+
+ * Makefile: beta1!!
+
+2002-07-02 07:22 ivan
+
+ * FS/FS/part_export/textradius.pm: working textradius export
+
+2002-07-02 07:00 ivan
+
+ * FS/FS/part_export/textradius.pm: better diagnostics
+
+2002-07-02 06:22 ivan
+
+ * httemplate/view/cust_main.cgi: ui s/ /&nbsp;
+
+2002-07-02 06:04 ivan
+
+ * FS/FS/part_export/shellcommands.pm: no warnings
+
+2002-07-02 06:00 ivan
+
+ * FS/FS/: part_export.pm, part_export/shellcommands.pm: working
+ linux and freebsd shellcommands
+
+2002-07-02 04:29 ivan
+
+ * FS/FS/part_export.pm: fix for freebsd presets
+
+2002-07-02 04:27 ivan
+
+ * FS/FS/part_export.pm: useful shellcommands presets
+
+2002-07-02 03:14 ivan
+
+ * FS/bin/freeside-queued: grr old openssh grr freebsd
+
+2002-07-02 03:01 ivan
+
+ * httemplate/docs/install.html: freeside group
+
+2002-07-02 02:42 ivan
+
+ * FS/bin/freeside-queued: fleabsd grr
+
+2002-07-02 02:39 ivan
+
+ * FS/bin/freeside-queued: freebsd is sofa king broken
+
+2002-07-02 00:31 ivan
+
+ * FS/FS/part_export/textradius.pm: working textradius
+
+2002-07-02 00:13 ivan
+
+ * FS/FS/part_export/textradius.pm: better error reporting from
+ rsync
+
+2002-07-01 23:58 ivan
+
+ * FS/FS/part_export/: shellcommands.pm, www_shellcommands.pm:
+ s/options/option/, oops, and machine isn't an option
+
+2002-07-01 23:48 ivan
+
+ * FS/FS/queue.pm: error messages can have other chars
+
+2002-07-01 15:38 ivan
+
+ * FS/FS/part_export/textradius.pm: prevent any possible infinite
+ looping
+
+2002-07-01 02:15 ivan
+
+ * FS/FS/part_export/textradius.pm: real-time! text radius export
+
+2002-06-30 04:01 ivan
+
+ * ANNOUCE.1.4.0, CREDITS: administrivia
+
+2002-06-30 00:17 ivan
+
+ * FS/FS/Record.pm: oops, very bad
+
+2002-06-30 00:16 ivan
+
+ * FS/FS/Record.pm: get rid of unneeded Pg-cruft (don't use native
+ Pg money type)
+
+2002-06-30 00:04 ivan
+
+ * FS/FS/Record.pm: fix dbdef caching
+
+2002-06-29 19:13 ivan
+
+ * FS/: MANIFEST, FS/part_export.pm,
+ FS/part_export/shellcommands_withdomain.pm,
+ t/part_export-shellcommands_withdomain.t: export!
+
+2002-06-29 18:18 ivan
+
+ * httemplate/docs/ssh.html: adding
+
+2002-06-28 13:31 ivan
+
+ * FS/FS/svc_acct.pm, bin/populate-msgcat: better error message for
+ illegal password
+
+2002-06-28 13:23 ivan
+
+ * bin/fs-setup: not anymore...
+
+2002-06-28 13:21 ivan
+
+ * FS/FS/cust_main.pm: better error message for missing tax classes
+
+2002-06-28 03:49 ivan
+
+ * FS/FS/Record.pm: remove extraneous check
+
+2002-06-28 01:23 ivan
+
+ * FS/FS/: Record.pm, UID.pm: fix multi-database installs, while
+ hopefully keeping performance improvement
+
+2002-06-27 20:09 ivan
+
+ * httemplate/view/cust_main.cgi: more card display changes
+
+2002-06-27 19:21 ivan
+
+ * httemplate/view/cust_main.cgi: last 4 digits of card instead of
+ first
+
+2002-06-27 02:23 ivan
+
+ * FS/: FS/part_export/bind.pm, FS/part_export/bind_slave.pm,
+ t/part_export-bind.t, t/part_export-bind_slave.t: null bind
+ exports
+
+2002-06-27 02:19 ivan
+
+ * FS/: FS/part_export/bsdshell.pm, FS/part_export/null.pm,
+ FS/part_export/sysvshell.pm, FS/part_export/www_shellcommands.pm,
+ t/part_export-null.t, t/part_export-sysvshell.t,
+ t/part_export-www_shellcommands.t: export updates
+
+2002-06-26 01:36 ivan
+
+ * FS/FS/part_export.pm: tyop
+
+2002-06-26 01:32 ivan
+
+ * FS/FS/part_export.pm: better export docs/defaults
+
+2002-06-26 01:12 ivan
+
+ * FS/FS/cust_main.pm: add type_pkgs record if necessary for
+ one-time charges
+
+2002-06-26 01:05 ivan
+
+ * httemplate/view/cust_main.cgi: close form tags
+
+2002-06-26 00:42 ivan
+
+ * bin/: bsdshell.export, shell.export, sysvshell.export:
+ shell.export -> bsdshell.export & sysvshell.export
+
+2002-06-25 20:53 ivan
+
+ * FS/FS/part_export_option.pm: export options can be anything
+
+2002-06-25 19:37 ivan
+
+ * FS/FS/cust_bill.pm: fix not sending postal invoices to customers
+ with email invoices too
+
+2002-06-25 18:35 ivan
+
+ * FS/FS/cust_main.pm, httemplate/edit/process/quick-charge.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/view/cust_main.cgi: working one-time charges
+
+2002-06-25 17:41 ivan
+
+ * httemplate/edit/part_pkg.cgi: ui
+
+2002-06-25 16:27 ivan
+
+ * httemplate/view/svc_www.cgi: add link to controlling account
+
+2002-06-25 00:18 ivan
+
+ * FS/bin/freeside-queued: might work again under mysql
+
+2002-06-23 12:16 ivan
+
+ * FS/FS/domain_record.pm: domain_record records attached to svc_www
+ records are no longer delete-able, patch from "Stephen Bechard"
+ <steve@destek.net>, thanks! closes: Bug#434
+
+2002-06-22 22:44 ivan
+
+ * httemplate/edit/process/svc_www.cgi: patch to get this working
+ from Stephen Bechard <steve@destek.net>
+
+2002-06-22 22:43 ivan
+
+ * httemplate/view/svc_www.cgi: add link to edit and format nicely
+
+2002-06-22 18:36 ivan
+
+ * FS/FS/svc_www.pm: forgot . between zone and domain, patch from
+ "Stephen Bechard" <steve@destek.net>, thanks
+
+2002-06-22 18:33 ivan
+
+ * httemplate/edit/svc_www.cgi: forgot %> when converting from CGI,
+ works now, patch from "Stephen Bechard" <steve@destek.net>
+
+2002-06-21 14:49 ivan
+
+ * FS/FS/part_export/infostreet.pm: s/title/organization/ at
+ noment's request
+
+2002-06-21 13:26 ivan
+
+ * FS/FS/part_export/infostreet.pm: fix infostreet contact field
+ setting
+
+2002-06-21 13:17 ivan
+
+ * FS/FS/part_export/infostreet.pm: debugging option
+
+2002-06-21 02:57 ivan
+
+ * bin/passwd.import: no, don't check for duplicates like this by
+ default... not new-export style
+
+2002-06-21 02:56 ivan
+
+ * FS/FS/svc_acct.pm: better error messages on uid duplicates... uid
+ stuff still needs to be rewritten for new exports
+
+2002-06-21 02:28 ivan
+
+ * httemplate/docs/upgrade8.html: don't forget part_svc in upgrades
+
+2002-06-21 02:15 ivan
+
+ * bin/passwd.import: oops
+
+2002-06-21 02:13 ivan
+
+ * bin/: bind.import, fs-migrate-svc_acct_sm, passwd.import: import
+ fixes...
+
+2002-06-21 02:11 ivan
+
+ * bin/passwd.import: tiny bit better passwd.import
+
+2002-06-21 02:03 ivan
+
+ * httemplate/docs/upgrade8.html: doc
+
+2002-06-21 01:29 ivan
+
+ * httemplate/: index.html, docs/upgrade8.html: uid search on main
+ menu, updated upgrade docs
+
+2002-06-20 15:35 ivan
+
+ * FS/FS/part_export/infostreet.pm: fix infostreet contact field foo
+
+2002-06-20 15:31 ivan
+
+ * httemplate/docs/install.html: doc
+
+2002-06-19 18:29 ivan
+
+ * FS/FS/part_export.pm, FS/FS/part_export/shellcommands.pm,
+ FS/bin/freeside-queued, httemplate/edit/part_export.cgi,
+ httemplate/edit/process/part_export.cgi: shellcommands
+ w/passwords
+
+2002-06-18 21:54 ivan
+
+ * Makefile, bin/pod2x: pod build fix thanks to Stephen Bechard
+ <steve@destek.net>
+
+2002-06-18 21:03 ivan
+
+ * FS/FS/part_export/infostreet.pm: fix setContactField email
+
+2002-06-18 18:03 ivan
+
+ * FS/FS/svc_acct.pm: and the same for changes...
+
+2002-06-18 17:58 ivan
+
+ * FS/FS/svc_acct.pm: fix problem provisioning RADIUS groups
+ caused by kristian/mark/pc-intouch's changes moving exports into
+ svc_Common - changed sequence of events such that groups were
+ not provisioned when the sqlradius export was run
+
+2002-06-18 16:52 ivan
+
+ * httemplate/misc/queue.cgi: better error message
+
+2002-06-14 18:12 ivan
+
+ * FS/FS/part_export.pm, httemplate/edit/part_svc.cgi:
+ mark@pc-intouch.com: exporttype2svcdb removal
+
+2002-06-14 14:35 ivan
+
+ * FS/FS/part_export/infostreet.pm: freeside night to infostreet
+ faxNumber (? dunno, what noment wants)
+
+2002-06-14 04:44 ivan
+
+ * Makefile: better releaes target?
+
+2002-06-14 04:26 ivan
+
+ * ANNOUCE.1.4.0, Makefile, README.1.4.0pre14: 1.4.0pre14
+
+2002-06-14 04:22 ivan
+
+ * FS/: FS/Conf.pm, bin/freeside-queued, FS/cust_main.pm,
+ FS/queue.pm, FS/svc_Common.pm, FS/svc_acct.pm: working job
+ dependancies FS::queue::joblisting html excapes & truncates long
+ arguments welcome email (sheesh!) closes: Bug#420 (haha at 4:20
+ am, too. really!)
+
+2002-06-14 02:19 ivan
+
+ * FS/FS/UID.pm: only run callbacks once... should speed things up
+ (no dbdef reloading)
+
+2002-06-13 20:26 ivan
+
+ * httemplate/docs/legacy.html: s/svc_domain.import/bind.import/ to
+ match reality
+
+2002-06-13 20:12 ivan
+
+ * httemplate/: classic.html, index.html: remove classic interface
+
+2002-06-13 19:52 ivan
+
+ * httemplate/docs/: install.html, mysql.html: mysql support!
+
+2002-06-13 19:25 ivan
+
+ * FS/bin/freeside-queued: mysql compatibility?
+
+2002-06-13 17:12 ivan
+
+ * httemplate/: index.html, search/cust_main.cgi: search by for
+ address2 (unit) - commented out in default index.html
+
+2002-06-13 16:28 ivan
+
+ * httemplate/: index.html, search/cust_main.cgi: phone number
+ search (Bug#422)
+
+2002-06-13 16:00 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/index.html,
+ httemplate/search/svc_acct.cgi: fuzzy username searching
+ (Bug#422)
+
+2002-06-12 17:53 ivan
+
+ * httemplate/: index.html, search/cust_main.cgi: search on customer
+ number (Bug#422)
+
+2002-06-12 13:31 ivan
+
+ * FS/FS/svc_acct.pm: fix new duplicate username checking
+
+2002-06-12 10:29 ivan
+
+ * httemplate/index.html: fix company search
+
+2002-06-12 09:26 ivan
+
+ * FS/FS/cust_main.pm: fix problems with code that resets invoice
+ events
+
+2002-06-11 11:32 ivan
+
+ * bin/shell.export: don't export empty files
+
+2002-06-11 11:25 ivan
+
+ * bin/shell.export: use FS::cust_svc and FS::svc_acct
+
+2002-06-11 11:23 ivan
+
+ * bin/shell.export: bsd only for now
+
+2002-06-11 11:20 ivan
+
+ * bin/: bind.export, shell.export: perl 5.005 needs an explicit
+ mask for mkdir
+
+2002-06-11 02:51 ivan
+
+ * FS/FS/part_export.pm: notes
+
+2002-06-11 02:46 ivan
+
+ * bin/: bind.export, shell.export: add preliminary bsd shell export
+
+2002-06-11 02:14 ivan
+
+ * FS/FS/svc_acct.pm: export-based duplicate username checking!
+
+2002-06-11 01:29 ivan
+
+ * FS/FS/part_export.pm, httemplate/edit/part_export.cgi: add crypt
+ option to (bsd|sysv)shell export
+
+2002-06-10 20:25 ivan
+
+ * FS/FS/: part_export.pm, svc_Common.pm, svc_acct.pm,
+ part_export/infostreet.pm: - add new suspend and unsuspend export
+ hooks (with null defaults) - infostreet export: actually
+ suspend/unsuspend at infostreet (closes: Bug#418) - infostreet
+ export: set some contact fields @ infostreet (Bug#419)
+
+2002-06-10 17:58 ivan
+
+ * Makefile, init.d/freeside-init: dist fixes. locate more stuff in
+ Makefile. whew.
+
+2002-06-10 16:02 ivan
+
+ * FS/FS/domain_record.pm: fix *** ERROR: unterminated L<...> at
+ line 299 in file FS/domain_record.pm
+
+2002-06-10 15:48 ivan
+
+ * FS/FS/cust_main.pm, httemplate/misc/bill.cgi: also retry cards
+ when user clicks "Bill now" (closes: Bug#417)
+
+2002-06-10 15:07 ivan
+
+ * FS/FS/: cust_bill_event.pm, cust_main.pm: retry realtime_card
+ invoice events when a card changes (closes: Bug#417)
+
+2002-06-10 12:44 ivan
+
+ * httemplate/view/cust_main.cgi: final visual update on package
+ date view
+
+2002-06-10 12:41 ivan
+
+ * httemplate/view/cust_main.cgi: fixup time display in packages
+
+2002-06-10 12:30 ivan
+
+ * httemplate/view/cust_main.cgi: add small time display to dates
+
+2002-06-09 19:52 ivan
+
+ * FS/FS/: svc_Common.pm, svc_domain.pm: re-my'ed var
+
+2002-06-09 19:52 ivan
+
+ * FS/FS/cust_main_county.pm: silence undefined warnings
+
+2002-06-09 19:51 ivan
+
+ * FS/FS/CGI.pm: unused global
+
+2002-06-09 19:42 ivan
+
+ * Makefile: back to ASP for profiling...
+
+2002-06-09 18:39 khoff
+
+ * FS/FS/part_pkg.pm, FS/FS/pkg_svc.pm, bin/fs-setup,
+ httemplate/browse/part_pkg.cgi, httemplate/docs/schema.html,
+ httemplate/edit/part_pkg.cgi: Rollback part_pkg.def_svcpart
+ changes.
+
+2002-06-08 00:48 khoff
+
+ * FS/FS/part_pkg.pm, FS/FS/pkg_svc.pm, bin/fs-setup,
+ httemplate/browse/part_pkg.cgi, httemplate/docs/schema.html,
+ httemplate/edit/part_pkg.cgi: Default svcpart support for
+ part_pkg. Fixes 'bug' with new customer and online signup.
+
+2002-06-07 13:33 khoff
+
+ * FS/FS/cust_bill.pm: Setup hash for CC failed Text::Template
+
+2002-06-05 15:46 ivan
+
+ * httemplate/search/: cust_main.cgi, cust_pkg.cgi, svc_acct.cgi:
+ more mysql goodness, thanks dale
+
+2002-06-04 10:37 ivan
+
+ * httemplate/docs/install.html: small doc patch from
+ baloo@gimpgirl.org
+
+2002-06-04 07:35 ivan
+
+ * FS/FS/: cust_credit.pm, cust_pay.pm: fix unsuspendauto
+
+2002-06-04 07:02 ivan
+
+ * CREDITS, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/svc_acct.cgi:
+ mysql support! thanks to Donald Greer <dgreer@austintx.com> for
+ the SQL and Dale Hege <fhege@lumenexus.net> for the patches
+
+2002-06-04 06:46 ivan
+
+ * conf/declinetemplate: misspelling
+
+2002-05-31 15:37 ivan
+
+ * bin/fs-setup: no state necessary in batch cards (i18n)
+
+2002-05-31 13:34 ivan
+
+ * FS/FS/part_export.pm: dammit i want to catch export subclass
+ compilation problems
+
+2002-05-31 13:31 ivan
+
+ * FS/FS/cust_bill.pm: better error reporting
+
+2002-05-31 11:48 khoff
+
+ * FS/t/part_export-sqlmail.t: Don't ask me. I just hacked Ivan's
+ test.
+
+2002-05-31 11:45 khoff
+
+ * FS/MANIFEST: added sqlmail.pm and test
+
+2002-05-31 10:50 ivan
+
+ * FS/FS/svc_forward.pm: typo noticed by <Kaa>
+
+2002-05-30 19:13 ivan
+
+ * FS/FS/part_export.pm: no, actually throw an exception if an
+ export class won't compile.
+
+2002-05-30 17:33 khoff
+
+ * httemplate/edit/part_svc.cgi: Added support for part_exports that
+ are used with more than one svcdb.
+
+2002-05-30 17:22 khoff
+
+ * FS/FS/part_export.pm: updated hashes 'n stuff for
+ FS::part_export::sqlmail
+
+2002-05-30 17:20 khoff
+
+ * FS/FS/part_export/sqlmail.pm: part_export module to export
+ svc_acct, svc_domain, and svc_forward to an external database
+
+2002-05-30 17:18 khoff
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm, svc_domain.pm: Moved
+ new-style export calls to svc_Common.
+
+2002-05-30 14:51 ivan
+
+ * httemplate/misc/delete-customer.cgi: point to correct place for
+ hidecancelledcustomers config option
+
+2002-05-30 10:49 ivan
+
+ * FS/FS/part_export.pm: GRRRRRRRRRRRRR
+
+2002-05-29 13:45 ivan
+
+ * FS/bin/freeside-sqlradius-reset: eliminate harmless "Database
+ handle destroyed without explicit disconnect" errors
+
+2002-05-29 08:56 ivan
+
+ * FS/FS/part_export.pm: freeradius 0.5 doc
+
+2002-05-28 21:40 ivan
+
+ * fs_selfservice/: freeside-selfservice-server,
+ FS-SelfService/Changes, FS-SelfService/MANIFEST,
+ FS-SelfService/Makefile.PL, FS-SelfService/SelfService.pm,
+ FS-SelfService/test.pl: add fs_selfservice
+
+2002-05-28 14:22 ivan
+
+ * FS/FS/svc_acct.pm, bin/populate-msgcat: better error message for
+ "Illegal password"
+
+2002-05-28 00:55 ivan
+
+ * FS/FS/: part_export.pm, part_export/shellcommands.pm: point
+ people at DBI/DBD documentation for information on data sources.
+ *sigh*
+
+2002-05-26 23:53 ivan
+
+ * httemplate/edit/cust_bill_pay.cgi: fix javascript bug reported by
+ baloo@gimpgirl.com, thanks
+
+2002-05-23 06:00 ivan
+
+ * ANNOUCE.1.4.0, FS/FS/domain_record.pm, bin/bind.import,
+ bin/svc_domain.import, httemplate/edit/process/domain_record.cgi,
+ httemplate/view/svc_domain.cgi: bind: allow adding slave domains
+ too
+
+2002-05-22 11:44 ivan
+
+ * FS/FS/domain_record.pm, FS/FS/part_export.pm,
+ FS/FS/svc_domain.pm, bin/bind.export,
+ httemplate/edit/process/domain_record.cgi,
+ httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/delete-domain_record.cgi,
+ httemplate/misc/delete-part_export.cgi,
+ httemplate/view/svc_domain.cgi: bind export, editing zones,
+ deleting unaudited domains, mmm
+
+2002-05-22 05:17 ivan
+
+ * FS/FS/: cust_pkg.pm, cust_svc.pm: move some code from
+ FS::cust_pkg to FS::cust_svc, becomes the cancel method
+
+2002-05-22 04:39 ivan
+
+ * FS/FS/part_export.pm: - remove some out of date documentation -
+ die if an export class won't compile
+
+2002-05-22 03:53 ivan
+
+ * bin/bind.export: bind export
+
+2002-05-21 19:09 ivan
+
+ * FS/FS/part_export/sqlradius.pm: 5.6-isms
+
+2002-05-20 04:02 ivan
+
+ * bin/svc_domain.import: [no log message]
+
+2002-05-18 02:51 ivan
+
+ * FS/FS/: cust_bill.pm, cust_pay.pm, svc_domain.pm: Mail::Internet
+ 1.44
+
+2002-05-17 06:51 ivan
+
+ * httemplate/docs/install.html: tyop
+
+2002-05-16 20:47 ivan
+
+ * FS/FS/svc_acct.pm: freebsd `toor' user
+
+2002-05-16 07:28 ivan
+
+ * FS/: FS/part_export/bsdshell.pm, FS/part_export/textradius.pm,
+ t/part_export-bsdshell.t, t/part_export-textradius.t: adding
+ (stub) bsdshell and textradius exports
+
+2002-05-16 07:28 ivan
+
+ * bin/svc_acct.export: ip address is added by radius_reply method
+ already
+
+2002-05-16 07:27 ivan
+
+ * FS/MANIFEST, eg/export_template.pm: add (stub) bsdshell and
+ textradius exports
+
+2002-05-16 07:21 ivan
+
+ * FS/FS/part_export/infostreet.pm: force all infostreet arguments
+ to be string type, fixes: "0 as first character in password"
+ problem. also see the Frontier::Client manpage
+
+2002-05-16 06:42 ivan
+
+ * httemplate/: browse/part_svc.cgi, edit/part_svc.cgi: service
+ definition cloning
+
+2002-05-16 06:42 ivan
+
+ * FS/FS/svc_acct.pm: allow freebsd `toor' root user
+
+2002-05-16 00:53 ivan
+
+ * httemplate/docs/upgrade8.html: upgrade docs: run populate-msgcat
+
+2002-05-15 07:00 ivan
+
+ * FS/FS/: queue.pm, part_export/sqlradius.pm: use job dependancies
+ in FS::part_export::sqlradius.pm display job dependancies in
+ FS::queue::joblisting
+
+2002-05-15 06:24 ivan
+
+ * ANNOUCE.1.4.0, Makefile, README.1.4.0pre13, FS/FS.pm,
+ FS/MANIFEST, FS/FS/queue.pm, FS/FS/queue_depend.pm,
+ FS/FS/part_export/sqlradius.pm, FS/bin/freeside-queued,
+ FS/t/queue_depend.t, bin/fs-setup, eg/export_template.pm,
+ httemplate/docs/install.html, httemplate/docs/schema.dia,
+ httemplate/docs/schema.html, httemplate/docs/schema.png,
+ httemplate/docs/upgrade8.html: queue dependancies
+
+2002-05-14 00:36 ivan
+
+ * FS/FS/part_export/sqlradius.pm: don't use return value of UPDATE
+ to decide whether or not to INSERT.
+
+2002-05-13 17:27 ivan
+
+ * FS/FS/part_export/shellcommands.pm: shellcomands oops
+
+2002-05-10 00:54 ivan
+
+ * Makefile: always re-install init
+
+2002-05-10 00:50 ivan
+
+ * init.d/freeside-init: correct message
+
+2002-05-10 00:45 ivan
+
+ * FS/FS/svc_domain.pm: bad reuse of variable
+
+2002-05-10 00:42 ivan
+
+ * Makefile: INSTALLGROUP
+
+2002-05-09 09:41 ivan
+
+ * bin/populate-msgcat: permissions
+
+2002-05-09 08:26 ivan
+
+ * Makefile: final make release?
+
+2002-05-09 08:25 ivan
+
+ * Makefile: fix make release target
+
+2002-05-09 08:22 ivan
+
+ * Makefile: make release target
+
+2002-05-09 08:21 ivan
+
+ * httemplate/: edit/cust_main.cgi, view/cust_main.cgi:
+ s/Referral/Advertising source/
+
+2002-05-09 08:14 ivan
+
+ * httemplate/docs/: install.html, mysql.html: attempt to avoid
+ people installing and asking for help with MySQL despite the VERY
+ FUCKING CLEAR instructions that it isn't supported.
+
+2002-05-09 08:12 ivan
+
+ * FS/FS/cust_main_county.pm: don't duplicate state/county/country
+ pulldowns even with taxrates...
+
+2002-05-09 05:38 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_main.pm, FS/FS/cust_main_county.pm,
+ FS/FS/part_pkg.pm, FS/t/cust_tax_exempt.t, htetc/global.asa,
+ htetc/handler.pl, httemplate/browse/agent.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/edit/agent.cgi, httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi: texas tax!
+
+2002-05-06 06:36 ivan
+
+ * FS/FS/cust_bill.pm: fixes gratuitous "Illegal payname" errors
+ reported by noment
+
+2002-05-04 08:00 ivan
+
+ * README.1.4.0pre12, FS/FS.pm, FS/MANIFEST,
+ FS/FS/cust_main_county.pm, FS/FS/cust_tax_exempt.pm,
+ FS/FS/part_pkg.pm, FS/t/cust_tax_exempt.pm, bin/fs-setup,
+ httemplate/docs/admin.html, httemplate/docs/schema.dia,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html:
+ schema changes for proper texas tax
+
+2002-05-03 18:11 ivan
+
+ * httemplate/: index.html, search/cust_pkg.cgi: add package search
+ by next bill date to main menu
+
+2002-05-03 17:59 ivan
+
+ * httemplate/search/cust_pkg.cgi: fix ranges on cust_pkg search
+
+2002-05-03 17:49 ivan
+
+ * httemplate/search/cust_pkg.html: point at correct .cgi
+
+2002-05-03 17:47 ivan
+
+ * FS/FS/cust_pkg.pm: prevent stuff passed from template/user from
+ being used in searches by signup server
+
+2002-05-03 17:32 ivan
+
+ * httemplate/search/: cust_pkg.cgi, cust_pkg.html: lilunixbtch:
+ trying to pull accounts based on next billdate tofu_beast420: hmm
+ a report ordered by next bill date? tofu_beast420: i don't know
+ how you'd do that per _customer_ since a customer could have lots
+ of packages, but you could do a per-package report maybe?
+
+2002-04-29 22:43 ivan
+
+ * FS/FS/part_export.pm, bin/svc_domain.import: better BIND
+ integration
+
+2002-04-26 04:14 ivan
+
+ * FS/FS/: Conf.pm, svc_acct.pm: add username-nounderscore and
+ username-nodash config files
+
+2002-04-25 03:37 ivan
+
+ * FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi: free_delayed try
+ #2
+
+2002-04-25 02:47 ivan
+
+ * FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi: add flat_delayed
+ plan
+
+2002-04-24 18:15 ivan
+
+ * FS/FS/cust_main.pm: don't require_cardname for non-CARD payby's
+
+2002-04-24 02:09 ivan
+
+ * FS/FS/: Conf.pm, cust_main.pm: require_cardname option
+
+2002-04-24 02:03 ivan
+
+ * FS/FS/cust_main.pm, bin/populate-msgcat: msgcat error for credit
+ card expiration (closes: Bug#407)
+
+2002-04-23 00:32 ivan
+
+ * httemplate/edit/REAL_cust_pkg.cgi: harmless ui glitch
+
+2002-04-23 00:10 ivan
+
+ * FS/FS/Record.pm: fixes inserting strings that end in numbers to
+ TEXT columns... gah i hate SQL
+
+2002-04-22 15:45 ivan
+
+ * FS/FS/part_export.pm: use Tie::IxHash to present export options
+ in a reasonable order
+
+2002-04-22 14:36 ivan
+
+ * FS/FS/cust_main.pm: don't show extended debugging in error
+ messages that could end up on the signup server
+
+2002-04-22 14:23 ivan
+
+ * FS/FS/cust_pkg.pm: fix bug checking agents allowed to purchase
+ packages (moved from signups server)
+
+2002-04-22 14:18 ivan
+
+ * httemplate/edit/agent_type.cgi: add pkgpart to agent type listing
+
+2002-04-22 13:47 ivan
+
+ * FS/FS/cust_pkg.pm: - check agentnum ability to order packages in
+ FS::cust_pkg, not signup server - order by recur price in
+ signup-alternate template
+
+2002-04-20 05:37 ivan
+
+ * ANNOUCE.1.4.0, FS/FS/part_export.pm,
+ httemplate/edit/part_export.cgi: bind export
+
+2002-04-20 04:57 ivan
+
+ * FS/FS/domain_record.pm, FS/FS/part_export.pm, bin/passwd.import,
+ bin/svc_domain.erase, bin/svc_domain.import,
+ httemplate/docs/legacy.html:
+ working BIND import
+
+2002-04-20 03:49 ivan
+
+ * FS/FS/domain_record.pm: allow * MX records
+
+2002-04-20 03:12 ivan
+
+ * FS/FS/domain_record.pm: allow uppercase in zone data.
+
+2002-04-20 03:09 ivan
+
+ * FS/FS/domain_record.pm: allow uppercase zones...
+
+2002-04-19 23:34 ivan
+
+ * bin/populate-msgcat: perms
+
+2002-04-19 23:33 ivan
+
+ * bin/fs-setup: fixup fs-setup
+
+2002-04-19 19:06 ivan
+
+ * FS/FS/: cust_pkg.pm, cust_svc.pm: fix problem with edge case
+ where there *is* a pkg_svc record with quantity 0, when changing
+ packages and using the special case new service code
+
+2002-04-19 16:25 ivan
+
+ * FS/FS/svc_acct.pm: maybe just for debugging
+
+2002-04-19 07:27 ivan
+
+ * FS/FS/cust_bill.pm: add phone to Business::OnlinePayment usage
+
+2002-04-18 18:16 ivan
+
+ * Makefile, FS/FS/Record.pm, FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ init.d/freeside-init: - add init file installation to Makefile,
+ add unified init file - fix qsearch for op => '!=', value => ''
+ searches - fix invalid_catd typo - add payby method to part_pkg
+ and have fs_signup_server pass the data
+
+2002-04-17 13:43 ivan
+
+ * FS/FS/part_export/sqlradius.pm: fix usergroup_delete DELETE
+ syntax
+
+2002-04-17 12:47 ivan
+
+ * FS/FS/Record.pm: allow = in ut_text
+
+2002-04-17 05:06 ivan
+
+ * httemplate/index.html: fix default searches
+
+2002-04-17 04:41 ivan
+
+ * FS/FS/Record.pm: get rid of debugging cruft
+
+2002-04-16 22:48 ivan
+
+ * fs_passwd/fs_passwdd: forgotten semicolon
+
+2002-04-16 22:46 ivan
+
+ * fs_passwd/fs_passwdd: also untaint pid
+
+2002-04-16 22:44 ivan
+
+ * fs_passwd/fs_passwdd: *sigh*
+
+2002-04-16 22:42 ivan
+
+ * fs_passwd/fs_passwdd:
+ okay pid file has a change of working now
+
+2002-04-16 22:39 ivan
+
+ * fs_passwd/fs_passwdd: tyop
+
+2002-04-16 22:22 ivan
+
+ * fs_passwd/fs_passwdd: pid file foo
+
+2002-04-16 18:14 ivan
+
+ * fs_passwd/fs_passwd_server: kill off ssh kid on exit...
+
+2002-04-16 17:25 ivan
+
+ * httemplate/search/cust_main.cgi: better ordering in search
+ results
+
+2002-04-16 16:14 ivan
+
+ * FS/FS/cust_main.pm: there it is! fix bug with
+ FS::cust_main::agent
+
+2002-04-16 15:56 ivan
+
+ * FS/FS/cust_bill.pm: further debugging...
+
+2002-04-16 15:52 ivan
+
+ * README.1.4.0pre12, FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ bin/fs-setup: further authorize.net description debugging
+ (agent??) and get rid of bad unique index on cust_bill_event
+
+2002-04-16 15:38 ivan
+
+ * FS/FS/cust_bill.pm: eek, problem with authorize.net description
+ field
+
+2002-04-16 14:24 ivan
+
+ * FS/bin/freeside-queued: auto-use export classes
+
+2002-04-16 13:13 ivan
+
+ * httemplate/browse/part_svc.cgi: ui
+
+2002-04-16 12:50 ivan
+
+ * httemplate/index.html: exports, then services...
+
+2002-04-16 03:47 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm, FS/FS/cust_bill_event.pm,
+ httemplate/index.html, httemplate/search/cust_bill_event.cgi,
+ httemplate/search/cust_bill_event.html,
+ httemplate/search/report_cc.html,
+ httemplate/search/report_credit.html,
+ httemplate/search/report_tax.html, httemplate/view/cust_bill.cgi:
+ report on failed billing events...
+
+2002-04-16 02:38 ivan
+
+ * FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ FS/bin/freeside-expiration-alerter, conf/declinetemplate,
+ httemplate/docs/billing.html: - send a notice to the customer
+ when their card is declined - closes: Bug#351 -
+ freeside-expiration-alerter works fine, closes: Bug#7
+
+2002-04-16 01:52 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_export.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/vpopmail.pm, httemplate/docs/passwd.html,
+ httemplate/edit/part_export.cgi: move the last of the real-time
+ exports out of svc_acct.pm and into part_export
+
+2002-04-15 23:47 ivan
+
+ * httemplate/index.html: allow multiple search types on main
+ menu... more confusing but default is *too* fuzzy
+
+2002-04-14 02:11 ivan
+
+ * FS/MANIFEST, FS/FS/Conf.pm, FS/FS/part_export.pm,
+ FS/FS/svc_acct.pm, FS/FS/part_export/cp.pm,
+ FS/FS/part_export/cyrus.pm, FS/FS/part_export/infostreet.pm,
+ FS/FS/part_export/shellcommands.pm,
+ FS/FS/part_export/vpopmail.pm, FS/t/part_export-cp.t,
+ FS/t/part_export-cyrus.t, FS/t/part_export-shellcommands.t,
+ FS/t/part_export-vpopmail.t, eg/export_template.pm,
+ httemplate/docs/billing.html, httemplate/docs/legacy.html,
+ httemplate/docs/schema.dia, httemplate/docs/schema.html,
+ httemplate/docs/schema.png: - move cyrus, shellcommands, CP
+ exports exports to new-style - skeleton files for vpopmail
+ exports - documentation updates - add big schema diagram to docs
+
+2002-04-13 18:36 ivan
+
+ * httemplate/docs/billing.html: docu for invoice_lines(0) with no
+ arguments
+
+2002-04-13 06:36 ivan
+
+ * FS/FS.pm, FS/FS/Conf.pm, FS/FS/part_export.pm, FS/FS/svc_acct.pm,
+ FS/FS/part_export/cp.pm, FS/bin/freeside-overdue,
+ FS/bin/freeside-sqlradius-reset, bin/sqlradius_reset,
+ httemplate/edit/part_export.cgi: - documentation updates - move
+ Critical Path export to new-style export - bin/sqlradius_reset
+ gets a manpage and becomes FS/bin/freeside-sqlradius-reset
+
+2002-04-13 03:46 ivan
+
+ * httemplate/index.html: s/otaker/order-taker/
+
+2002-04-13 02:14 ivan
+
+ * FS/FS/cust_bill.pm: allow invoice_lines(0) meaning no limit, no
+ padding (see Bug#388)
+
+2002-04-13 01:51 ivan
+
+ * FS/FS/queue.pm, httemplate/misc/queue.cgi: bulk queue operations
+ (closes: Bug#389)
+
+2002-04-12 08:14 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm,
+ httemplate/edit/cust_pkg.cgi: fudge up FS::cust_pkg::order
+ ("Order and cancel packages") to try to move services between
+ svcparts as a last resort...
+
+2002-04-12 06:22 ivan
+
+ * FS/MANIFEST, FS/FS/Msgcat.pm, FS/FS/Record.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_invoice.pm, FS/FS/msgcat.pm,
+ FS/FS/part_export.pm, FS/FS/svc_acct.pm, FS/t/Msgcat.t,
+ htetc/global.asa, htetc/handler.pl,
+ httemplate/browse/part_svc.cgi, httemplate/edit/part_export.cgi,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/part_export.cgi,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/misc/delete-customer.cgi: - should finish off the
+ part_svc -> part_export s/one-to-many/many-to-many/ transition
+ (closes: Bug#375) - fixes a nasty export scoping bug with message
+ catalogs, whew
+
+2002-04-11 15:05 ivan
+
+ * README.1.4.0pre12, FS/FS.pm, FS/MANIFEST, FS/FS/export_svc.pm,
+ FS/FS/part_export.pm, FS/FS/part_svc.pm, FS/t/export_svc.t,
+ bin/fs-setup, bin/sqlradius_reset, httemplate/index.html,
+ httemplate/browse/part_export.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/part_export.cgi,
+ httemplate/edit/process/part_export.cgi,
+ httemplate/misc/delete-part_export.cgi: (almost) everything for
+ bug#375 - create export_svc table - part_svc to part_export is
+ now properly many-to-many, not one-to-many
+
+ still need to finish service editing (choosing exports) in
+ httemplate/edit/part_svc.cgi and
+ httemplate/edti/process/part_svc.cgi
+
+ and do somethinga about that manual $svcdb = 'svc_acct' in
+ httemplate/edit/part_export.cgi (do part_export records need a
+ svcdb? probably not... should be able to just pass an svcdb on
+ creation of new exports, move the big %exports hash into
+ part_export.pm and allow httemplate/edit/part_svc.cgi to query it
+ for exports that can apply to a given svcdb....
+
+2002-04-11 15:04 ivan
+
+ * FS/FS/Record.pm: use the AUTOLOAD that reports errors better &
+ fix problems with ut_domain losing data
+
+2002-04-10 06:42 ivan
+
+ * ANNOUCE.1.4.0, README.1.4.0pre12, FS/FS/Conf.pm, FS/FS/Record.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_main_invoice.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_acct_pop.pm, bin/populate-msgcat,
+ httemplate/browse/msgcat.cgi, httemplate/docs/signup.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/msgcat.cgi,
+ httemplate/edit/process/msgcat.cgi: bulk checkin from working on
+ the road:
+
+ - use msgcat for more error messages - should be all things that
+ would come3 back from the signup server normally now - signup
+ server: don't display access number <SELECT> if there's none or
+ one - signup_server-realtime config option to run billing for
+ signup server signups immediately - signup server: pkg
+ available to success templates, better documentation on success
+ templates - httemplate/edit/msgcat.cgi fields are properly sticky
+ on errors - httemplate/edit/process/msgcat.cgi - don't update
+ identical fields
+
+2002-04-10 01:39 ivan
+
+ * bin/fs-setup: fix mistake in part_pop_local schema (not used by
+ anyone really so no big deal)
+
+2002-04-06 22:23 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm, cust_pay.pm: send email on signup
+ server signups (closes: Bug#386)
+
+2002-04-06 21:56 ivan
+
+ * FS/FS/Conf.pm, FS/FS/msgcat.pm, bin/populate-msgcat,
+ conf/show-msgcat-codes, httemplate/browse/msgcat.cgi,
+ httemplate/docs/install.html, httemplate/edit/msgcat.cgi,
+ httemplate/edit/process/msgcat.cgi: working message catalogs (not
+ used for enough yet) - almost (but not quite) closes Bug#385 -
+ still have to catalog the backend things triggered by signup
+ server.
+
+2002-04-06 16:00 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/docs/signup.html: - config option for signup server
+ payment types - credit card type pulldown on signup server
+ (closes: Bug#383)
+
+2002-04-06 14:32 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm, cust_bill_pkg.pm, cust_main.pm:
+ add a config option to set the Business::OnlinePayment
+ description field, and make some useful data available for the
+ config option. closes: Bug#378
+
+2002-04-06 13:32 ivan
+
+ * httemplate/search/cust_main.cgi: fix visual glitch
+
+2002-04-06 12:37 ivan
+
+ * httemplate/docs/: install.html, signup.html: big signup server
+ cleanups. uses Storable for network protocol now. - makes Bugs
+ 384 & 385 easier - closes: Bug#382
+
+2002-04-05 16:08 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct.cgi: security phrase bug fixes
+
+2002-04-05 15:51 ivan
+
+ * ANNOUCE.1.4.0, CREDITS, Makefile, README.1.4.0pre12, FS/MANIFEST,
+ FS/FS/Conf.pm, FS/FS/msgcat.pm, FS/FS/svc_acct.pm, FS/t/msgcat.t,
+ bin/freeside-session-kill, bin/fs-setup, bin/populate-msgcat,
+ bin/sqlradius_reset, conf/locale, eg/table_template-svc.pm,
+ eg/table_template.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/index.html, httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/msgcat.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct.cgi: - add message catalog table &
+ beginning of web interface - add security_phrase and conf option
+ to svc_acct.pm - random other stuff
+
+2002-04-05 15:37 jeff
+
+ * httemplate/view/cust_main.cgi: credit/refund display correction
+
+2002-04-05 12:52 ivan
+
+ * httemplate/edit/process/cust_credit_bill.cgi: don't specify date
+ (not on submitting form)
+
+2002-04-05 08:37 ivan
+
+ * eg/export_template.pm, httemplate/search/sql.cgi,
+ FS/t/part_export-infostreet.t, FS/t/part_export-sqlradius.t: oops
+ forgot these from working on the road
+
+2002-04-04 16:42 ivan
+
+ * FS/MANIFEST, FS/FS/part_export.pm,
+ FS/FS/part_export/infostreet.pm, FS/FS/part_export/sqlradius.pm,
+ httemplate/index.html, httemplate/docs/install.html,
+ httemplate/edit/part_export.cgi: Checkin of disparate changes
+ from working on the road: - generic SQL query - move exports out
+ to their own files - small cleanup of selfadmin server
+
+2002-03-29 18:35 ivan
+
+ * httemplate/edit/part_referral.cgi: finish up
+ s/referral/advertising source/
+
+2002-03-29 09:27 ivan
+
+ * htetc/global.asa, htetc/handler.pl,
+ httemplate/search/svc_acct.cgi: fix unlinked svc browse!
+
+2002-03-27 16:18 ivan
+
+ * ANNOUCE.1.4.0: still pretty much just notes...
+
+2002-03-26 23:08 ivan
+
+ * FS/FS/queue.pm: don't show queue arguments (passwords!) on svc_*
+ screens (unless queue_dangerous_options!)
+
+2002-03-26 21:36 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/part_referral.pm,
+ httemplate/index.html, httemplate/browse/part_referral.cgi,
+ httemplate/docs/admin.html, httemplate/edit/part_referral.cgi:
+ s/referral/advertising source/
+
+ yes, the name sucks. got a better one?
+
+2002-03-26 15:18 ivan
+
+ * fs_passwd/fs_passwd.cgi: yay works now... just forgot to send
+ http header
+
+2002-03-26 08:24 ivan
+
+ * httemplate/search/cust_main.cgi: simple visual fix: &nbsp; for
+ blank company column
+
+2002-03-26 05:58 ivan
+
+ * FS/FS/part_svc.pm: forgot to use FS::part_export to search on it
+
+2002-03-26 05:20 ivan
+
+ * httemplate/docs/passwd.html: doc
+
+2002-03-26 05:04 ivan
+
+ * bin/create-history-tables: error message typo
+
+2002-03-26 04:35 ivan
+
+ * httemplate/docs/admin.html: s/Add/Provision/
+
+2002-03-25 16:32 ivan
+
+ * FS/FS/part_export.pm, FS/bin/freeside-queued,
+ bin/sqlradius_reset, httemplate/edit/part_export.cgi: further
+ export bugfixing add 10 kid limit to freeside-queued
+ sqlradius_reset now works (closes: Bug#372)
+
+2002-03-25 16:26 ivan
+
+ * Makefile: hhahah oops i _really_ ought to do something about that
+
+2002-03-25 06:59 ivan
+
+ * httemplate/: index.html, search/cust_main-otaker.cgi,
+ search/cust_main.cgi: customers by otaker report (ugly
+ search/cust_main.cgi mods; revert if they cause problems)
+
+2002-03-24 15:16 ivan
+
+ * bin/: icradius_reset, sqlradius_reset: s/icradius/sqlradius/
+
+2002-03-24 12:00 ivan
+
+ * httemplate/: edit/cust_main.cgi, search/svc_acct.cgi,
+ browse/agent.cgi: UI improvements for agents
+
+2002-03-24 10:23 ivan
+
+ * FS/FS/agent.pm: tyop
+
+2002-03-24 09:42 ivan
+
+ * CREDITS, FS/FS/part_pkg.pm, httemplate/edit/part_pkg.cgi:
+ "subscription" price plan from "Luke Pfeifer"
+ <freeside@globalli.com>
+
+2002-03-24 06:29 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_export.pm, FS/FS/queue.pm,
+ FS/FS/radius_usergroup.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/svc_acct.cgi:
+ ICRADIUS groups all done! UI and provisioning. closes: Bug#362
+
+ fix some bugs in the export and add queue_dangerous_controls
+ option too
+
+2002-03-23 09:49 ivan
+
+ * FS/FS/part_svc.pm, FS/FS/svc_acct.pm,
+ httemplate/browse/part_svc.cgi, httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi: okay group editing UI as
+ well as part_svc group editing UI seem to be working
+
+2002-03-23 08:16 ivan
+
+ * FS/FS/part_export.pm, FS/FS/queue.pm, FS/FS/svc_acct.pm,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/svc_acct.cgi: group editing seems to be
+ working... everything except defaults... oh and export...
+
+2002-03-22 23:54 ivan
+
+ * FS/bin/freeside-queued: redirect STDOUT/STDERR a bit later for
+ better error reporting
+
+2002-03-22 10:56 ivan
+
+ * Makefile, README.1.4.0pre12, FS/MANIFEST,
+ FS/FS/cust_main_county.pm, FS/FS/svc_acct.pm,
+ FS/t/radius_usergroup.t, bin/create-history-tables, bin/fs-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/view/svc_acct.cgi: RADIUS groups on the way!
+
+2002-03-22 04:49 ivan
+
+ * FS/MANIFEST, FS/FS/Record.pm, FS/FS/part_export.pm,
+ FS/t/part_export.t, FS/t/part_export_option.t,
+ httemplate/browse/part_svc.cgi, httemplate/edit/part_export.cgi,
+ httemplate/edit/process/part_export.cgi,
+ httemplate/misc/delete-part_export.cgi: more new export...
+
+2002-03-20 22:57 ivan
+
+ * FS/FS/part_export.pm, httemplate/edit/part_export.cgi,
+ httemplate/edit/process/part_export.cgi: more for the new world
+ of export...
+
+2002-03-20 13:31 ivan
+
+ * README.1.4.0pre12, FS/FS/Conf.pm, FS/FS/part_export.pm,
+ FS/FS/part_export_option.pm, FS/FS/part_svc.pm,
+ FS/FS/svc_acct.pm, FS/bin/freeside-queued, bin/icradius_reset,
+ bin/svc_acct.export, htetc/global.asa, htetc/handler.pl,
+ httemplate/docs/export.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_export.cgi,
+ httemplate/edit/process/part_export.cgi: new export! infostreet
+ and sqlradius provisioning switched over (Bug #299 - doesn't
+ close it, but all the groundwork is done)
+
+ also removes non-transactional ICRADIUS export from
+ svc_acct.export (closes: Bug#347)
+
+2002-03-19 23:37 ivan
+
+ * FS/FS/cust_main_county.pm: disable region caching for now
+
+2002-03-19 09:48 ivan
+
+ * FS/FS/cust_main_county.pm, httemplate/edit/cust_main.cgi,
+ httemplate/edit/process/cust_main.cgi: changes dum big
+ "state/county/country" select to three, linked with javascript
+ closes: Bug#353
+
+2002-03-18 13:40 ivan
+
+ * FS/FS/: Conf.pm, cust_bill.pm, part_bill_event.pm: bugfixes,
+ closes Bug#314
+
+2002-03-18 12:50 ivan
+
+ * httemplate/edit/part_bill_event.cgi, FS/FS/part_bill_event.pm,
+ FS/FS/Conf.pm: okay, now you can specify an alternate invoice
+ template, and it'll be auto-createad and added to the list of
+ configuration options. closes: Bug#314
+
+2002-03-18 11:49 ivan
+
+ * FS/FS/: Conf.pm, Record.pm, cust_bill.pm, cust_pay.pm: fixes: bug
+ #348 - adds the ability to email on deleted payments.
+
+2002-03-18 11:40 ivan
+
+ * httemplate/search/cust_main-quickpay.html: update quickpay for
+ current search capabilities
+
+2002-03-18 09:50 ivan
+
+ * httemplate/config/config.cgi:
+ fixes bug#367 (yay, that one was annoying):
+
+ in config editor, initial newlines in <textarea>s are lost. even
+ if you didn't mean to edit them you remove that..
+
+2002-03-18 08:07 ivan
+
+ * bin/backup-freeside: removing backup-freeside script from here
+
+2002-03-18 08:05 ivan
+
+ * FS/FS/: svc_Common.pm, svc_acct.pm: handle inserting cust_svc and
+ svc_acct records separately also, to handle imports preserving
+ svcnum
+
+2002-03-18 07:52 ivan
+
+ * httemplate/edit/: part_export.cgi, process/part_export.cgi: added
+ (incomplete) export foo
+
+2002-03-18 07:51 ivan
+
+ * httemplate/browse/part_svc.cgi: UI glitch; forgot ROWSPAN for
+ export column
+
+2002-03-18 06:17 ivan
+
+ * FS/FS/raddb.pm: add noment-specific RADIUS attributes
+
+2002-03-18 01:10 ivan
+
+ * README.1.4.0pre12, FS/FS/Conf.pm, FS/FS/svc_domain.pm,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/docs/upgrade8.html: new config value `defaultrecords',
+ documentation, javascript config file editor
+
+2002-03-17 00:33 ivan
+
+ * httemplate/config/: config-view.cgi, config.cgi: new domain
+ record editing foo
+
+2002-03-12 10:45 ivan
+
+ * httemplate/misc/cancel-unaudited.cgi: print error message for
+ failed cancels!
+
+2002-03-12 07:33 ivan
+
+ * README.1.4.0pre12, htetc/global.asa, htetc/handler.pl,
+ httemplate/docs/install.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/part_svc.cgi:
+ abstract stupid HTML layer trick out to
+ HTML::Widgets::SelectLayers
+
+2002-03-09 02:19 khoff
+
+ * FS/FS/: Conf.pm, svc_domain.pm: Support for default CNAME/A
+ records
+
+2002-03-07 11:50 jeff
+
+ * FS/bin/: freeside-cc-receipts-report, freeside-credit-report,
+ freeside-expiration-alerter, freeside-receivables-report,
+ freeside-tax-report: less shelling, more perly - abolish some
+ pipes to sendmail
+
+2002-03-07 06:13 ivan
+
+ * FS/FS/cust_bill.pm: better error msgs for mail errors
+
+2002-03-07 06:10 ivan
+
+ * FS/FS/: cust_bill.pm, part_export.pm, part_export_option.pm,
+ queue.pm: [no log message]
+
+2002-03-06 15:32 ivan
+
+ * conf/invoice_from: oops, missing invoice_from
+
+2002-03-06 15:31 ivan
+
+ * httemplate/docs/upgrade8.html: add freeside-expiration-alerter to
+ upgrade8.html
+
+2002-03-06 14:44 jeff
+
+ * README.1.4.0pre12, FS/MANIFEST, FS/FS/Conf.pm,
+ FS/bin/freeside-expiration-alerter, conf/alerter_template,
+ httemplate/docs/admin.html: billing expiration alerts
+
+2002-03-05 16:17 ivan
+
+ * FS/bin/: freeside-cc-receipts-report, freeside-credit-report,
+ freeside-receivables-report, freeside-tax-report: remove CVS Log
+ tag
+
+2002-03-05 15:13 jeff
+
+ * FS/bin/freeside-cc-receipts-report,
+ FS/bin/freeside-credit-report,
+ FS/bin/freeside-receivables-report, FS/bin/freeside-tax-report,
+ httemplate/search/report_cc.cgi,
+ httemplate/search/report_credit.cgi,
+ httemplate/search/report_tax.cgi: consistency is nice
+
+2002-03-05 01:44 ivan
+
+ * FS/bin/freeside-receivables-report: yes i have crazy customers
+ with 8-digit customer numbers
+
+2002-03-04 14:10 ivan
+
+ * FS/FS/Record.pm: make history tables conditional
+
+2002-03-04 04:48 ivan
+
+ * Makefile, README.1.4.0pre12, FS/FS/Record.pm, FS/FS/nas.pm,
+ bin/create-history-tables, bin/fs-radius-add-check,
+ bin/fs-radius-add-reply, bin/fs-setup,
+ httemplate/docs/upgrade8.html: [no log message]
+
+2002-03-03 15:46 ivan
+
+ * httemplate/docs/install.html: docu
+
+2002-02-28 15:30 ivan
+
+ * httemplate/search/svc_acct.cgi: tyop
+
+2002-02-28 15:18 ivan
+
+ * FS/FS/svc_acct.pm: clean up mess. *sigh*
+
+2002-02-28 15:17 ivan
+
+ * FS/FS/svc_acct.pm: clean up mess
+
+2002-02-28 15:13 ivan
+
+ * FS/FS/svc_acct.pm: eek
+
+2002-02-28 15:08 ivan
+
+ * httemplate/search/svc_acct.cgi: add main menu link
+
+2002-02-28 15:07 ivan
+
+ * httemplate/misc/cancel-unaudited.cgi: handle errors better
+
+2002-02-28 14:05 ivan
+
+ * FS/FS/Conf.pm, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi: s/depreciated/deprecated/
+
+2002-02-27 16:28 jeff
+
+ * FS/FS/svc_acct.pm: improved vpopmail support for svc_acct records
+
+2002-02-27 15:20 ivan
+
+ * FS/FS/cust_main.pm: oops, spurious error messages
+
+2002-02-27 15:03 ivan
+
+ * FS/FS/cust_main.pm: bubble up billing event errors
+
+2002-02-27 14:40 ivan
+
+ * FS/FS/cust_bill_event.pm: and missing ) *sigh*
+
+2002-02-27 14:40 ivan
+
+ * FS/FS/cust_bill_event.pm: errant ;
+
+2002-02-27 14:39 ivan
+
+ * README.1.4.0pre12, FS/FS/cust_bill_event.pm, bin/fs-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html,
+ httemplate/view/cust_bill.cgi: add status and statustext fields
+ to cust_bill_event
+
+2002-02-27 14:00 ivan
+
+ * FS/FS/cust_main.pm: well, don't make things worse when
+ debugging...
+
+2002-02-27 13:57 ivan
+
+ * FS/: FS/cust_main.pm, bin/freeside-daily: better debugging
+
+2002-02-27 09:07 khoff
+
+ * httemplate/docs/upgrade8.html: Should be "CREATE TABLE" not
+ "CREATE INDEX" for part_export_option
+
+2002-02-26 14:09 ivan
+
+ * httemplate/search/: report_cc.cgi, report_credit.cgi,
+ report_receivables.cgi, report_tax.cgi: fixup reports for
+ templated webUI
+
+2002-02-26 03:53 ivan
+
+ * FS/FS/cust_main.pm: okay, finally fix all the weirdness with
+ shipping adresses. whew.
+
+2002-02-26 03:42 ivan
+
+ * FS/FS/: Record.pm, cust_main.pm: in Record.pm - call ->check
+ **BEFORE** generating @diff for SQL. causing weird effects with
+ cust_main::check that modifies record (ship_ field weirdness)
+
+ fix nasty logic error triggered by changing a ship_ field from
+ something TO identical to the corresponding non-ship_ field.
+ ouch
+
+2002-02-26 03:30 ivan
+
+ * FS/FS/cust_main.pm: fix nasty logic error triggered by changing a
+ ship_ field from something TO identical to the corresponding
+ non-ship_ field. ouch.
+
+2002-02-26 03:11 ivan
+
+ * httemplate/edit/cust_main.cgi: fix a weird oops with service
+ address editing UI that would leave some fields blank... ?
+
+2002-02-26 01:06 ivan
+
+ * FS/FS/cust_bill.pm: ugh, payname needs first/last i guess
+
+2002-02-26 00:34 ivan
+
+ * FS/FS/cust_main.pm: die with a better error message on bad
+ locales
+
+2002-02-25 11:09 ivan
+
+ * httemplate/search/svc_acct.cgi: keith@landel: We have users
+ that have the character "." in their username we can't search
+ these customers by Username, can you please fix.
+
+2002-02-23 14:36 ivan
+
+ * httemplate/search/: report_cc.cgi, report_credit.cgi,
+ report_receivables.cgi, report_tax.cgi: take hardcoded paths out
+ of report cgis
+
+2002-02-23 03:56 ivan
+
+ * CREDITS, FS/FS/Record.pm, httemplate/docs/index.html,
+ httemplate/search/cust_main.cgi,
+ httemplate/search/cust_main.html: case-insensitive and substring
+ searching
+
+2002-02-23 03:46 ivan
+
+ * httemplate/index.html: silly html tweaking
+
+2002-02-22 23:48 ivan
+
+ * httemplate/browse/part_pkg.cgi: it's too late.
+
+2002-02-22 23:35 ivan
+
+ * httemplate/browse/part_pkg.cgi: i really should just start moving
+ things to templates
+
+2002-02-22 23:32 ivan
+
+ * httemplate/browse/: part_pkg.cgi, part_svc.cgi: UI nit fixes -
+ misaligned columns when viewing disabled services/packages
+
+2002-02-22 23:00 ivan
+
+ * FS/FS/UID.pm: nit
+
+2002-02-22 18:14 jeff
+
+ * FS/FS/UID.pm, bin/svc_acct.export,
+ httemplate/search/report_cc.cgi,
+ httemplate/search/report_credit.cgi,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/report_tax.cgi: report fixes and cruft removal
+
+2002-02-22 18:02 ivan
+
+ * httemplate/: index.html, docs/install.html, docs/upgrade8.html:
+ UI work on main menu, remove Archive::Tar from docs
+
+2002-02-22 15:18 jeff
+
+ * FS/MANIFEST, FS/bin/freeside-cc-receipts-report,
+ FS/bin/freeside-credit-report,
+ FS/bin/freeside-receivables-report, FS/bin/freeside-tax-report,
+ conf/report_template, httemplate/classic.html,
+ httemplate/index.html, httemplate/search/report_cc.cgi,
+ httemplate/search/report_cc.html,
+ httemplate/search/report_credit.cgi,
+ httemplate/search/report_credit.html,
+ httemplate/search/report_receivables.cgi,
+ httemplate/search/report_tax.cgi,
+ httemplate/search/report_tax.html: add some reporting features
+
+2002-02-22 15:08 ivan
+
+ * FS/FS/cust_pay_batch.pm: fix i18n zip
+
+2002-02-22 14:13 ivan
+
+ * httemplate/edit/cust_main.cgi: billing vs. shipping states
+ weren't sorted in the same order. UI glitch only
+
+2002-02-22 04:31 ivan
+
+ * httemplate/config/: config-process.cgi, config-view.cgi: fixup
+ <SELECT> configuration editing, fixes Bug#350
+
+2002-02-22 04:13 ivan
+
+ * httemplate/index.html: close </FORM> tags
+
+2002-02-22 01:01 ivan
+
+ * FS/FS/svc_acct.pm: correctly disable/enable accounts @ CP
+
+2002-02-22 00:58 ivan
+
+ * FS/FS/svc_acct.pm: freeside *SUSPENDED* -> CP set_mailbox_status
+ OTHER/OTHER_BOUNCE
+
+2002-02-21 23:50 ivan
+
+ * bin/fs-setup: doh
+
+2002-02-21 22:42 ivan
+
+ * FS/FS/queue.pm: UI in joblisting: don't split ( retry | remove )
+ links
+
+2002-02-21 22:39 ivan
+
+ * FS/FS/queue.pm: don't error out on bad svcnum's, just silently
+ remove them
+
+2002-02-21 22:23 ivan
+
+ * FS/FS/svc_acct.pm: don't leak perl line numbers on cp
+ provisioning errors
+
+2002-02-21 21:56 ivan
+
+ * FS/FS/svc_acct.pm: fix bugs in CP mailbox changes: cp_change and
+ cp_rename
+
+2002-02-21 17:07 ivan
+
+ * FS/FS/Conf.pm, httemplate/view/cust_main.cgi,
+ httemplate/view/cust_pkg.cgi: UI work:
+
+ make all functions of view/cust_pkg.cgi available on
+ view/cust_main.cgi - having them one link down in "Edit" is
+ confusing.
+
+ closes: Bug#325
+
+2002-02-21 15:17 ivan
+
+ * bin/fs-setup: queue.svcnum is nullable too, oops
+
+2002-02-21 13:43 ivan
+
+ * bin/fs-setup: looks like statustext field is missing NULL flag in
+ fs-setup
+
+2002-02-20 14:03 ivan
+
+ * httemplate/index.html: invoice reports belong here too
+
+2002-02-20 02:39 ivan
+
+ * FS/FS/CGI.pm: fixes eidiot under Mason, closes: Bug#344
+
+2002-02-20 01:44 ivan
+
+ * httemplate/view/cust_main.cgi: don't display full card in
+ webinterface
+
+2002-02-19 19:17 jeff
+
+ * FS/FS/cust_main.pm: correct sense of tax generation
+
+2002-02-19 17:03 ivan
+
+ * CREDITS, README.1.4.0pre11, README.1.4.0pre8, FS/FS/CGI.pm,
+ FS/FS/Record.pm, FS/FS/queue.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_domain.pm, FS/FS/svc_forward.pm,
+ FS/bin/freeside-queued, bin/fs-setup, htetc/global.asa,
+ htetc/handler.pl, httemplate/index.html,
+ httemplate/browse/queue.cgi, httemplate/misc/queue.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_domain.cgi,
+ httemplate/view/svc_forward.cgi, httemplate/view/svc_www.cgi: use
+ Net::SSH::ssh_cmd for all job queueing rather than local
+ duplicated ssh subs
+
+ queue daemon updates: retry & remove links work, bubble up error
+ message to webinterface, link to svcnum & have job listings on
+ view/svc_* pages, closes: Bug#280
+
+ s/option/optionname/ schema change, dumb mysql, closes: Bug#334
+
+2002-02-19 15:43 ivan
+
+ * httemplate/docs/: install.html, schema.html, upgrade8.html: set
+ EVERYTHING=1 for your own mod_perl compile
+
+2002-02-18 23:51 ivan
+
+ * httemplate/index.html: oops
+
+2002-02-18 23:00 ivan
+
+ * httemplate/classic.html: TMTOWTDI
+
+2002-02-18 20:43 ivan
+
+ * httemplate/index.html: new main menu!
+
+2002-02-18 19:22 jeff
+
+ * FS/FS/cust_refund.pm: fix refund posting
+
+2002-02-18 19:15 ivan
+
+ * FS/FS/cust_refund.pm: <rluser> ut_number instead of ut_numbern
+
+2002-02-18 19:06 ivan
+
+ * FS/FS/cust_refund.pm: need to use FS::Record qw(qsearch) !
+
+2002-02-18 18:57 ivan
+
+ * httemplate/: edit/part_pkg.cgi, view/cust_main.cgi: allow custom
+ packages to edit service quantities also
+
+2002-02-18 12:09 ivan
+
+ * httemplate/edit/part_svc.cgi: bad warning, causing errors with
+ strict
+
+2002-02-18 10:07 ivan
+
+ * FS/FS/prepay_credit.pm: document 'seconds'
+
+2002-02-18 02:14 ivan
+
+ * httemplate/edit/svc_www.cgi: oops!
+
+2002-02-18 00:53 ivan
+
+ * Makefile: make release and make update-webdemo targets
+
+2002-02-18 00:39 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_bill_event.pm, FS/FS/part_pkg.pm,
+ htetc/global.asa, httemplate/edit/part_pkg.cgi: safe web demo
+ operation! closes: Bug#217
+
+ fix bug in edit/part_pkg: s/bkg/pkg/
+
+ edit/part_pkg.cgi - plan <SELECT> is now properly stick on
+ errors, closes: Bug#323
+
+2002-02-17 19:45 ivan
+
+ * httemplate/edit/part_svc.cgi: fix modified <SELECT>s under
+ netcape4, use one for svc_acct.popnum too
+
+2002-02-17 17:26 ivan
+
+ * README.1.4.0pre11, httemplate/docs/install.html,
+ httemplate/docs/upgrade8.html: rsync docs
+
+2002-02-17 16:21 jeff
+
+ * Makefile: keep accidentally checking in Makefile changes, gotta
+ do something about that...
+
+2002-02-17 16:13 jeff
+
+ * Makefile, FS/FS/Conf.pm, bin/svc_acct.export, eg/vpopmailrestart:
+ trading in tar for rsync for improved vpopmail support
+
+2002-02-17 13:01 ivan
+
+ * FS/MANIFEST: removing CGIwrapper.pm
+
+2002-02-17 11:12 ivan
+
+ * README.1.4.0pre9, FS/MANIFEST, FS/FS/CGIwrapper.pm,
+ FS/t/CGIwrapper.t: get ride of CGIwrapper.pm
+
+2002-02-17 11:07 jeff
+
+ * FS/FS/svc_forward.pm: queue svc_forward remote commands; better
+ commands too
+
+2002-02-16 13:47 ivan
+
+ * htetc/global.asa: work with current Apache::ASP
+
+2002-02-16 10:14 ivan
+
+ * TODO: now in RT2!
+
+2002-02-15 23:27 ivan
+
+ * httemplate/docs/install.html: install warnings about mysql
+
+2002-02-15 12:21 jeff
+
+ * bin/svc_acct.export: remove arbitary uid requirement for vpasswd
+ generation
+
+2002-02-15 11:34 ivan
+
+ * Makefile: keep accidentally checking in Makefile changes, gotta
+ do something about that...
+
+2002-02-15 11:33 ivan
+
+ * Makefile, FS/FS/Conf.pm, FS/FS/svc_acct.pm: CP provisioning!!
+
+2002-02-14 14:37 jeff
+
+ * bin/svc_acct.export: fix bug in multiline radiusprepend
+
+2002-02-14 10:06 ivan
+
+ * bin/: fs-radius-add-check, fs-radius-add-reply: docs? haha
+
+2002-02-13 17:12 ivan
+
+ * FS/FS/cust_pay_batch.pm: don't require state (i18n)
+
+2002-02-12 10:56 ivan
+
+ * FS/FS/cust_bill.pm: more information in "cant send invoice email"
+ error message
+
+2002-02-12 10:47 ivan
+
+ * FS/FS/cust_main.pm: fixes:
+
+ Error running invoice event ($cust_main->charge( 10.00, 'Overdue
+ Bill' );): Illegal or empty (text) comment: at
+ /usr/local/lib/perl5/site_perl/5.005/FS/cust_main.pm line 1141.
+
+2002-02-12 10:37 ivan
+
+ * bin/svc_acct.export: fixes:
+
+ In string, @domain now must be written as \@domain at
+ ./svc_acct.export line 292, near "^append @domain" Global symbol
+ "@domain" requires explicit package name at ./svc_acct.export
+ line 292. Execution of ./svc_acct.export aborted due to
+ compilation errors.
+
+2002-02-11 21:58 ivan
+
+ * README.1.4.0pre9, bin/fs-setup, httemplate/docs/admin.html:
+ fixes: bug#331
+
+2002-02-11 20:49 ivan
+
+ * httemplate/edit/part_bill_event.cgi: oops
+
+2002-02-11 19:29 ivan
+
+ * README.1.4.0pre8, README.1.4.0pre9: 1.4.0pre9!!!
+
+2002-02-11 18:11 ivan
+
+ * FS/FS/Conf.pm, bin/svc_acct.export, httemplate/config/config.cgi:
+ add username_policy "@append domain"
+
+ add "select" config type, mmm
+
+2002-02-11 18:06 ivan
+
+ * FS/FS/svc_acct.pm: that's not a bug anymore, don't list it in the
+ BUGS section
+
+2002-02-11 17:25 ivan
+
+ * httemplate/edit/part_svc.cgi: red warning for unknown type :)
+
+2002-02-11 17:13 ivan
+
+ * httemplate/edit/part_svc.cgi: service definition domsvc is now a
+ domain pulldown closes: Bug#328
+
+2002-02-11 15:01 ivan
+
+ * FS/FS/svc_forward.pm: oops, code hidden by pod
+
+2002-02-11 13:51 ivan
+
+ * httemplate/edit/svc_acct.cgi: don't display useless finger and
+ shell fields if uid is set to fixed & blank in the service
+ definition - causes: Error: Can't have finger-name without uid
+
+2002-02-11 11:38 ivan
+
+ * FS/FS/Conf.pm, FS/FS/svc_www.pm, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/svc_www.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_www.cgi:
+ svc_www is working!
+
+ also auto-create and add A records if necessary using apacheip
+ config file.
+
+ and show all domain_records on view/svc_domain.cgi page
+
+2002-02-10 14:31 ivan
+
+ * httemplate/edit/svc_acct.cgi: ui
+
+2002-02-10 14:21 ivan
+
+ * httemplate/edit/part_pkg.cgi: slighly different wording
+
+2002-02-10 14:06 ivan
+
+ * FS/FS/cust_svc.pm: another bug in quantity checking
+
+2002-02-10 13:37 ivan
+
+ * FS/FS/cust_svc.pm: import qsearch() so the quantity checking
+ works
+
+2002-02-10 13:30 ivan
+
+ * FS/FS/Conf.pm, FS/FS/part_pkg.pm, conf/agent_defaultpkg: add new
+ package definitions to all agent types by default
+
+ config option to restore current behaviour (must explicitly add
+ new package definitions to each agent type)
+
+ closes: Bug#324
+
+2002-02-10 11:58 ivan
+
+ * FS/MANIFEST, FS/bin/freeside-bill, FS/bin/freeside-daily,
+ httemplate/docs/billing.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/process/part_bill_event.cgi: update billing
+ documentation for the new world of invoice events added
+ freeside-daily replacing freeside-bill for the new world of
+ invoice events
+
+2002-02-10 10:56 ivan
+
+ * README.1.4.0pre9, FS/FS/cust_pay.pm, bin/fs-setup,
+ httemplate/docs/upgrade8.html, httemplate/edit/cust_pay.cgi: use
+ unique tokens to prevent double-submission of payments in the web
+ UI (closes: Bug#320)
+
+2002-02-10 09:30 ivan
+
+ * httemplate/search/cust_main.cgi: eliminate duplicate cusomters
+ before figuring to display a list or redirect. eliiminates "2
+ matching found" displayed but only one in list.
+
+2002-02-10 09:02 ivan
+
+ * FS/FS/cust_main_invoice.pm: allow + in email addresses
+
+2002-02-10 08:49 ivan
+
+ * FS/FS/cust_main_invoice.pm: okay, for now, don't try to transform
+ email addresses into svcnum-linked destinations
+
+2002-02-10 08:40 ivan
+
+ * Makefile, httemplate/edit/svc_acct.cgi:
+ fix "no previous account to recall hidden password from" error
+ when adding new accounts & get an error on first try
+
+2002-02-10 08:14 ivan
+
+ * Makefile, httemplate/index.html,
+ httemplate/browse/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_pop.cgi: s/POP/Access Number/ in the
+ webui
+
+2002-02-10 08:05 ivan
+
+ * htetc/handler.pl, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/cust_pay_batch.cgi, httemplate/browse/nas.cgi,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/browse/queue.cgi,
+ httemplate/browse/svc_acct_pop.cgi,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ httemplate/edit/REAL_cust_pkg.cgi, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_type.cgi,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/part_referral.cgi,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/misc/bill.cgi, httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/catchall.cgi,
+ httemplate/misc/delete-customer.cgi, httemplate/misc/link.cgi,
+ httemplate/misc/process/link.cgi,
+ httemplate/search/cust_bill.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pay.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_domain.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_forward.cgi,
+ httemplate/view/svc_www.cgi: *finally* seems to be working under
+ Mason. sheesh. *finally* seems to be working under Mason.
+ sheesh. *finally* seems to be working under Mason. sheesh.
+ *finally* seems to be working under Mason. sheesh. *finally*
+ seems to be working under Mason. sheesh. *finally* seems to be
+ working under Mason. sheesh. *finally* seems to be working
+ under Mason. sheesh. *finally* seems to be working under Mason.
+ sheesh.
+
+2002-02-10 05:21 ivan
+
+ * htetc/handler.pl, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/cust_pay_batch.cgi, httemplate/browse/nas.cgi,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/browse/queue.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_type.cgi,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/part_referral.cgi,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/edit/process/agent.cgi,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_main_county-collapse.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/part_referral.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/svc_acct_sm.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/edit/process/svc_forward.cgi,
+ httemplate/misc/bill.cgi, httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/cancel_pkg.cgi, httemplate/misc/catchall.cgi,
+ httemplate/misc/delete-cust_pay.cgi,
+ httemplate/misc/delete-customer.cgi,
+ httemplate/misc/expire_pkg.cgi, httemplate/misc/link.cgi,
+ httemplate/misc/print-invoice.cgi, httemplate/misc/susp_pkg.cgi,
+ httemplate/misc/unsusp_pkg.cgi,
+ httemplate/misc/process/catchall.cgi,
+ httemplate/misc/process/delete-customer.cgi,
+ httemplate/misc/process/link.cgi,
+ httemplate/search/cust_bill.cgi, httemplate/search/cust_pay.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_domain.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_forward.cgi,
+ httemplate/view/svc_www.cgi: removed <!-- $Id$ --> from all files
+ to fix any redirects, whew
+
+ Mason handler.pl overrides CGI::redirect
+
+ fixed strict; problems in edit/part_pkg.cgi &
+ edit/process/part_pkg.cgi
+
+2002-02-09 18:28 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi,
+ httemplate/view/cust_main.cgi: hide SS# unless you turn it on via
+ config file
+
+2002-02-09 18:16 ivan
+
+ * Makefile, FS/FS/Conf.pm, FS/FS/cust_bill.pm,
+ FS/FS/cust_bill_event.pm, FS/FS/cust_main.pm, htetc/handler.pl,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_bill_event.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/view/cust_bill.cgi: pro-rating w/ web interface,
+ tested (closes: Bug#313).
+
+ view/cust_bill.cgi invoice view shows invoice events!
+
+ fix bug where adding events with no name silently failed instead
+ of giving an error
+
+ add new comission plans
+
+2002-02-09 17:47 ivan
+
+ * README.1.4.0pre8, httemplate/docs/upgrade8.html,
+ FS/bin/freeside-bill: remove -i option from freeside-bill
+ (obsoleted by invoice events)
+
+2002-02-09 10:24 ivan
+
+ * FS/FS/CGI.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/search/cust_main.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/search/svc_domain.cgi: no more exit() in templates
+
+2002-02-09 10:09 ivan
+
+ * FS/FS/svc_domain.pm: okay all external export from .pm files is
+ queued! (closes: Bug#249)
+
+2002-02-09 09:45 ivan
+
+ * FS/FS/cust_svc.pm, httemplate/misc/process/link.cgi: have
+ FS::cust_svc::check look up & check pkg_svc.quantity like
+ httemplate/view/cust_pkg.cgi (closes: Bug#43)
+
+2002-02-09 09:03 ivan
+
+ * FS/FS/CGI.pm, htetc/global.asa, htetc/handler.pl: okay, both
+ Apache::ASP and Mason should set no-cache headers now (closes:
+ Bug#23)
+
+2002-02-07 14:30 ivan
+
+ * Makefile: oops, reversing bad Makefile patch
+
+2002-02-07 14:29 ivan
+
+ * Makefile, FS/FS/Conf.pm, FS/FS/cust_bill_pay.pm,
+ FS/FS/cust_pay.pm, httemplate/browse/agent_type.cgi,
+ httemplate/browse/nas.cgi, httemplate/misc/delete-cust_pay.cgi,
+ httemplate/view/cust_main.cgi: delete payments
+
+2002-02-06 07:55 ivan
+
+ * FS/FS/cust_main.pm, bin/pod2x: doc updates and pod2x fix to skip
+ blib/ files
+
+2002-02-06 07:50 ivan
+
+ * FS/FS/cust_bill.pm: pod typo
+
+2002-02-06 07:49 ivan
+
+ * Makefile, httemplate/docs/install.html: documentation building
+ Makefile patch
+
+2002-02-06 07:36 ivan
+
+ * FS/FS/part_bill_event.pm: part_bill_event.plan can contain
+ punctuation
+
+2002-02-06 07:07 ivan
+
+ * bin/fs-setup: tyop
+
+2002-02-06 06:58 ivan
+
+ * FS/bin/freeside-adduser: fix for non-file auth
+
+2002-02-05 12:25 ivan
+
+ * FS/FS/Record.pm: better error messages if you haven't run
+ fs-setup ?
+
+2002-02-05 10:24 ivan
+
+ * httemplate/docs/install.html: &nbsp; for commands; confusing
+ otherwise
+
+2002-02-05 10:04 ivan
+
+ * httemplate/docs/install.html: doc
+
+2002-02-05 08:48 ivan
+
+ * fs_passwd/fs_passwd, fs_passwd/fs_passwd.cgi,
+ fs_passwd/fs_passwd.html, httemplate/docs/passwd.html: web-based
+ password changer!
+
+2002-02-05 03:06 ivan
+
+ * httemplate/docs/upgrade8.html: don't need DBIx::DataSource for
+ upgrades
+
+2002-02-05 01:46 ivan
+
+ * httemplate/docs/: install.html, upgrade8.html: doc
+
+2002-02-04 23:57 ivan
+
+ * README.1.4.0pre8, httemplate/docs/upgrade8.html: fix
+ part_export_option indices
+
+2002-02-04 10:12 ivan
+
+ * httemplate/docs/install.html: doc update
+
+2002-02-04 09:12 ivan
+
+ * httemplate/docs/man/FS/: Bill.html, Invoice.html: ancient files
+
+2002-02-04 09:06 ivan
+
+ * Makefile, README.1.4.0pre4567-8, README.1.4.0pre8: 1.4.0pre8!
+
+2002-02-04 09:04 ivan
+
+ * bin/fs-setup, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_bill_event.cgi: have fs-setup create the
+ necessary "default" billing events documentation on necessary
+ "default" billing events
+
+2002-02-04 08:44 ivan
+
+ * httemplate/docs/install.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/view/cust_bill.cgi, FS/FS/cust_bill.pm,
+ FS/FS/cust_main.pm: billing events!
+
+2002-01-30 10:22 ivan
+
+ * httemplate/: browse/part_bill_event.cgi,
+ edit/part_bill_event.cgi, edit/process/part_bill_event.cgi: fully
+ working invoice event web interface
+
+2002-01-30 06:18 ivan
+
+ * FS/FS/CGI.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/browse/agent.cgi, httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/cust_pay_batch.cgi, httemplate/browse/nas.cgi,
+ httemplate/browse/part_bill_event.cgi,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/browse/queue.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/docs/install.html,
+ httemplate/edit/agent.cgi, httemplate/edit/agent_type.cgi,
+ httemplate/edit/cust_bill_pay.cgi,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_referral.cgi, httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi,
+ httemplate/edit/process/agent.cgi,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/cust_bill_pay.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_main_county-collapse.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/part_referral.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/svc_acct_sm.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/edit/process/svc_forward.cgi,
+ httemplate/misc/bill.cgi, httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/cancel_pkg.cgi, httemplate/misc/catchall.cgi,
+ httemplate/misc/delete-customer.cgi,
+ httemplate/misc/expire_pkg.cgi, httemplate/misc/link.cgi,
+ httemplate/misc/print-invoice.cgi, httemplate/misc/susp_pkg.cgi,
+ httemplate/misc/unsusp_pkg.cgi,
+ httemplate/misc/process/catchall.cgi,
+ httemplate/misc/process/delete-customer.cgi,
+ httemplate/misc/process/link.cgi,
+ httemplate/search/cust_bill.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pay.cgi, httemplate/search/cust_pkg.cgi,
+ httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_domain.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_forward.cgi,
+ httemplate/view/svc_www.cgi: remove use Module; and $cgi =
+ new CGI; &cgisuidsetup(); from all templates. should work
+ better under Mason.
+
+2002-01-29 09:46 ivan
+
+ * httemplate/docs/man/: FS.html, FS/CGI.html, FS/CGIwrapper.html,
+ FS/Conf.html, FS/Record.html, FS/SessionClient.html,
+ FS/SignupClient.html, FS/UID.html, FS/agent.html,
+ FS/agent_type.html, FS/cust_bill.html, FS/cust_bill_pkg.html,
+ FS/cust_credit.html, FS/cust_main.html, FS/cust_main_county.html,
+ FS/cust_main_invoice.html, FS/cust_pay.html,
+ FS/cust_pay_batch.html, FS/cust_pkg.html, FS/cust_refund.html,
+ FS/cust_svc.html, FS/domain_record.html, FS/nas.html,
+ FS/part_pkg.html, FS/part_referral.html, FS/part_svc.html,
+ FS/pkg_svc.html, FS/port.html, FS/prepay_credit.html,
+ FS/session.html, FS/svc_Common.html, FS/svc_acct.html,
+ FS/svc_acct_pop.html, FS/svc_acct_sm.html, FS/svc_domain.html,
+ FS/svc_www.html, FS/type_pkgs.html: these are all auto-generated
+ by the installation; don't check them into CVS (probably should
+ generate them for a release tarball though)
+
+2002-01-29 09:42 ivan
+
+ * README.1.4.0pre4567-8, FS/FS/part_bill_event.pm, bin/fs-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html,
+ httemplate/docs/man/FS/SessionClient.html,
+ httemplate/docs/man/FS/UID.html,
+ httemplate/docs/man/FS/agent.html,
+ httemplate/docs/man/FS/agent_type.html,
+ httemplate/docs/man/FS/cust_bill.html,
+ httemplate/docs/man/FS/cust_bill_pkg.html,
+ httemplate/docs/man/FS/cust_credit.html,
+ httemplate/docs/man/FS/cust_main.html,
+ httemplate/docs/man/FS/cust_main_county.html,
+ httemplate/docs/man/FS/cust_main_invoice.html,
+ httemplate/docs/man/FS/cust_pay.html,
+ httemplate/docs/man/FS/cust_pay_batch.html,
+ httemplate/docs/man/FS/cust_pkg.html,
+ httemplate/docs/man/FS/cust_refund.html,
+ httemplate/docs/man/FS/cust_svc.html,
+ httemplate/docs/man/FS/domain_record.html,
+ httemplate/docs/man/FS/nas.html,
+ httemplate/docs/man/FS/part_pkg.html,
+ httemplate/docs/man/FS/part_referral.html,
+ httemplate/docs/man/FS/part_svc.html,
+ httemplate/docs/man/FS/pkg_svc.html,
+ httemplate/docs/man/FS/port.html,
+ httemplate/docs/man/FS/prepay_credit.html,
+ httemplate/docs/man/FS/session.html,
+ httemplate/docs/man/FS/svc_Common.html,
+ httemplate/docs/man/FS/svc_acct.html,
+ httemplate/docs/man/FS/svc_acct_pop.html,
+ httemplate/docs/man/FS/svc_acct_sm.html,
+ httemplate/docs/man/FS/svc_domain.html,
+ httemplate/docs/man/FS/svc_www.html,
+ httemplate/docs/man/FS/type_pkgs.html: weight, plan and plandata
+ fields in part_bill_event
+
+2002-01-29 08:33 ivan
+
+ * Makefile, FS/FS.pm, FS/FS/Record.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm,
+ FS/FS/svc_acct.pm, bin/pod2x, htetc/global.asa, htetc/handler.pl,
+ httemplate/index.html, httemplate/browse/part_bill_event.cgi,
+ httemplate/docs/install.html, httemplate/docs/man/FS.html,
+ httemplate/docs/man/FS/CGI.html,
+ httemplate/docs/man/FS/Conf.html,
+ httemplate/docs/man/FS/Record.html,
+ httemplate/docs/man/FS/SessionClient.html,
+ httemplate/docs/man/FS/SignupClient.html,
+ httemplate/docs/man/FS/UID.html,
+ httemplate/docs/man/FS/agent.html,
+ httemplate/docs/man/FS/agent_type.html,
+ httemplate/docs/man/FS/cust_bill.html,
+ httemplate/docs/man/FS/cust_bill_pkg.html,
+ httemplate/docs/man/FS/cust_credit.html,
+ httemplate/docs/man/FS/cust_main.html,
+ httemplate/docs/man/FS/cust_main_county.html,
+ httemplate/docs/man/FS/cust_main_invoice.html,
+ httemplate/docs/man/FS/cust_pay.html,
+ httemplate/docs/man/FS/cust_pay_batch.html,
+ httemplate/docs/man/FS/cust_pkg.html,
+ httemplate/docs/man/FS/cust_refund.html,
+ httemplate/docs/man/FS/cust_svc.html,
+ httemplate/docs/man/FS/domain_record.html,
+ httemplate/docs/man/FS/nas.html,
+ httemplate/docs/man/FS/part_pkg.html,
+ httemplate/docs/man/FS/part_referral.html,
+ httemplate/docs/man/FS/part_svc.html,
+ httemplate/docs/man/FS/pkg_svc.html,
+ httemplate/docs/man/FS/port.html,
+ httemplate/docs/man/FS/prepay_credit.html,
+ httemplate/docs/man/FS/session.html,
+ httemplate/docs/man/FS/svc_Common.html,
+ httemplate/docs/man/FS/svc_acct.html,
+ httemplate/docs/man/FS/svc_acct_pop.html,
+ httemplate/docs/man/FS/svc_acct_sm.html,
+ httemplate/docs/man/FS/svc_domain.html,
+ httemplate/docs/man/FS/svc_www.html,
+ httemplate/docs/man/FS/type_pkgs.html,
+ httemplate/edit/part_bill_event.cgi,
+ httemplate/edit/part_pkg.cgi,
+ httemplate/edit/process/part_bill_event.cgi: - web interface for
+ hourly account charges! (FS::cust_pkg, FS::cust_svc and
+ FS::svc_acct seconds_since methods) - Makefile target to
+ regenerate HTML manpages on install - FS.pm doc update -
+ $FS::Record::Debug now dumps all SQL - new FS::cust_main methods:
+ ->cancel, ->invoicing_list_addpost - start of a billing event web
+ interface - cust_pay::upgrade_replace doesn't error out if
+ history includes overapplied payments
+
+2002-01-29 03:11 ivan
+
+ * bin/fs-setup: oops bad column type for part_bill_event.payby
+
+2002-01-28 03:24 ivan
+
+ * FS/FS/Record.pm: oops, syntax error in new() check for missing
+ subclass table sub (eek)
+
+2002-01-27 22:57 ivan
+
+ * README.1.4.0pre4567-8, FS/FS/cust_bill.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_svc.pm, bin/fs-setup, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html: book closing schema changes
+
+2002-01-27 21:15 ivan
+
+ * README.1.4.0pre4567-8, FS/FS/part_export.pm,
+ FS/FS/part_export_option.pm, bin/fs-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html:
+ part_export schema changes
+
+2002-01-25 17:52 ivan
+
+ * FS/FS/cust_credit_refund.pm: another bug spotted by jeff
+
+2002-01-24 09:02 ivan
+
+ * FS/FS/Record.pm: emit a warning in this unlikely case again
+
+2002-01-24 08:58 ivan
+
+ * FS/FS/: cust_bill_pay.pm, cust_credit.pm, cust_credit_bill.pm,
+ cust_credit_refund.pm, cust_pay.pm, cust_refund.pm: <rluser>
+ rather than == 0 in the ::check's .... <rluser> you might
+ consider <= 0
+
+2002-01-24 08:54 ivan
+
+ * FS/FS/cust_credit_refund.pm: stack traces help alot
+
+2002-01-24 03:52 ivan
+
+ * FS/FS/: cust_credit_refund.pm, cust_pay.pm, cust_refund.pm:
+ jeff's on a bugfinding roll here, thanks!
+
+2002-01-24 03:43 ivan
+
+ * FS/FS/cust_credit_refund.pm: <rluser> and it seems that
+ cust_credit_refund::cust_credit should exist..
+
+2002-01-23 22:52 ivan
+
+ * FS/FS/cust_refund.pm: from jeff@fix <rluser> in
+ FS::cust_refund::check 'amount' should probably be replaced with
+ +'refund' in two places
+
+2002-01-23 22:46 ivan
+
+ * FS/FS/cust_refund.pm: more updates to cust_refund::update_replace
+
+2002-01-23 18:26 ivan
+
+ * FS/FS/cust_refund.pm: fix cust_refund::insert
+
+2002-01-22 07:57 ivan
+
+ * FS/FS/: cust_credit.pm, cust_pay.pm, cust_refund.pm: don't allow
+ $0.00 in credits/payments/refunds
+
+2002-01-22 06:55 ivan
+
+ * FS/FS/part_svc.pm: docu
+
+2002-01-22 06:53 ivan
+
+ * FS/FS/svc_acct.pm: silly compilation problem
+
+2002-01-22 06:42 ivan
+
+ * httemplate/docs/: index.html, upgrade.html, upgrade2.html,
+ upgrade3.html: remove old upgrade docs
+
+2002-01-21 03:30 ivan
+
+ * FS/FS/cust_pkg.pm: include FS::svc_forward in kludgy preload
+
+2002-01-19 07:16 ivan
+
+ * FS/FS/UID.pm: error message update
+
+2002-01-16 07:37 ivan
+
+ * FS/FS/svc_acct.pm: doc
+
+2002-01-14 12:28 ivan
+
+ * FS/FS/Conf.pm, FS/FS/svc_acct.pm, httemplate/docs/export.html:
+ pay some attention to 1.4 RADIUS SQL export
+
+2002-01-14 06:29 ivan
+
+ * FS/bin/freeside-overdue: fix -l option
+
+2002-01-11 23:23 ivan
+
+ * httemplate/docs/upgrade8.html: fix upgrade8.html to be in
+ agreement with fs-setup
+
+2002-01-09 05:29 ivan
+
+ * FS/FS/cust_main.pm, httemplate/search/cust_main.cgi: update fuzzy
+ cache files on customer replace.
+
+ do an exact search along with the fuzzy search (webui)
+
+2002-01-03 09:40 ivan
+
+ * README.1.4.0pre2-3, README.1.4.0pre3-4, README.1.4.0pre4567-8,
+ FS/MANIFEST, FS/FS/cust_bill_event.pm, FS/FS/part_bill_event.pm,
+ FS/t/cust_bill_event.t, FS/t/part_bill_event.t, bin/fs-setup,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html: more
+ schema changes: part_bill_event and cust_bill_event tables
+
+ remove old 1.4.0pre READMEs
+
+2001-12-28 07:17 ivan
+
+ * FS/bin/freeside-overdue: update usage message
+
+2001-12-28 07:14 ivan
+
+ * FS/: FS/cust_main.pm, bin/freeside-overdue: force printing in
+ freeside-overdue
+
+2001-12-28 06:40 ivan
+
+ * FS/FS/cust_main.pm, FS/bin/freeside-bill,
+ FS/bin/freeside-overdue, httemplate/edit/cust_main.cgi,
+ httemplate/edit/part_pkg.cgi: add more options to
+ freeside-overdue
+
+ add charge method to FS::cust_main
+
+ one-off packages default to disabled
+
+ billing payname defaults to first and last, not "Accounts
+ Payable"
+
+2001-12-27 01:26 ivan
+
+ * README.1.4.0pre4567-8, FS/FS/Record.pm, FS/FS/part_pkg.pm,
+ FS/FS/part_svc.pm, FS/bin/freeside-overdue, bin/fs-setup,
+ htetc/global.asa, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/edit/agent_type.cgi,
+ httemplate/edit/cust_main.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/view/cust_main.cgi: service and package disable!
+
+2001-12-26 15:59 ivan
+
+ * httemplate/docs/: install.html, schema.html, upgrade8.html: doc
+
+2001-12-26 07:41 ivan
+
+ * httemplate/view/cust_main.cgi: remove warnings
+
+2001-12-26 07:11 ivan
+
+ * httemplate/edit/process/cust_credit.cgi: apply credits!!
+
+2001-12-26 07:08 ivan
+
+ * FS/: MANIFEST, bin/freeside-overdue: add freeside-overdue
+
+2001-12-26 07:07 ivan
+
+ * httemplate/edit/: cust_credit.cgi, process/cust_credit.cgi: fix
+ posting credit
+
+2001-12-26 03:47 ivan
+
+ * FS/bin/freeside-overdue: don't provide example crontabs that run
+ at 4:20 _PM_
+
+2001-12-26 03:17 ivan
+
+ * FS/FS/cust_main.pm, FS/bin/freeside-overdue,
+ httemplate/docs/billing.html: (untested eek) freeside-overdue
+ script & cust_main balance_date & total_owed_date methods
+
+2001-12-26 01:18 ivan
+
+ * httemplate/: index.html, search/cust_pay.cgi,
+ search/cust_pay.html, view/cust_main.cgi: search by check #
+
+2001-12-25 23:53 ivan
+
+ * FS/FS/cust_pay.pm: doc
+
+2001-12-25 22:02 ivan
+
+ * httemplate/edit/cust_pay.cgi: UI
+
+2001-12-25 21:19 ivan
+
+ * httemplate/: index.html, edit/cust_pay.cgi,
+ edit/process/cust_pay.cgi, search/cust_main-payinfo.html,
+ search/cust_main-quickpay.html, search/cust_main.cgi,
+ search/cust_main.html: expedited check entry
+
+2001-12-25 21:02 ivan
+
+ * CREDITS: still missing tons of folks, i am sure
+
+2001-12-25 20:52 ivan
+
+ * httemplate/search/: cust_bill.html, svc_acct.html,
+ svc_domain.html, cust_main.html: same look as rest of search
+ pages
+
+2001-12-25 20:25 ivan
+
+ * FS/FS/CGI.pm, httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_pay.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_pay.cgi: auto-apply payments and
+ credits, post credit UI overhaul
+
+2001-12-25 18:33 ivan
+
+ * httemplate/edit/cust_pay.cgi: fix service display for duplicates
+
+2001-12-21 18:41 ivan
+
+ * httemplate/docs/install.html: doco on creating database manually
+
+2001-12-21 13:40 ivan
+
+ * FS/FS/cust_bill.pm, httemplate/edit/cust_pay.cgi,
+ httemplate/view/cust_main.cgi: add name/address to post payment
+ screen
+
+ get rid of some $-0.00 yay for ieee fp
+
+2001-12-21 12:55 ivan
+
+ * FS/FS/cust_bill_pay.pm: fixes
+
+ total cust_bill_pay.amount and cust_credit_bill.amount 19.95
+ for invnum 1659
+ greater than cust_bill.charged 19.95 at
+ /usr/local/lib/perl5/site_perl/5.005/FS/cust_main.pm line 1519.
+
+2001-12-19 21:34 ivan
+
+ * FS/FS/Record.pm: work better with DBIx::Profile
+
+2001-12-19 18:09 ivan
+
+ * FS/FS/svc_acct.pm: don't error trying to suspend accounts with
+ '*' password
+
+2001-12-19 18:07 ivan
+
+ * FS/FS/svc_acct.pm: quiet warnings
+
+2001-12-19 06:33 ivan
+
+ * FS/FS/svc_acct.pm: alas, a 5.6-ism
+
+2001-12-19 06:30 ivan
+
+ * FS/FS/svc_acct.pm: surpress warnings
+
+2001-12-18 11:36 ivan
+
+ * httemplate/edit/cust_bill_pay.cgi: another dum tyop
+
+2001-12-18 11:34 ivan
+
+ * httemplate/edit/cust_bill_pay.cgi: typo
+
+2001-12-18 11:32 ivan
+
+ * httemplate/edit/cust_bill_pay.cgi: declar vars
+
+2001-12-18 11:30 ivan
+
+ * httemplate/edit/: cust_bill_pay.cgi, cust_credit_bill.cgi,
+ process/cust_bill_pay.cgi, process/cust_credit_bill.cgi: apply
+ payment webinterface
+
+2001-12-17 23:12 ivan
+
+ * httemplate/search/cust_pkg.cgi: really working dates on package
+ browse. ouch.
+
+2001-12-17 23:08 ivan
+
+ * httemplate/search/cust_pkg.cgi: working dates on package browse
+
+2001-12-17 22:45 ivan
+
+ * httemplate/search/cust_pkg.cgi: oops. *sigh*
+
+2001-12-17 22:38 ivan
+
+ * httemplate/search/cust_pkg.cgi: show dates on package browse
+
+2001-12-17 22:29 ivan
+
+ * FS/FS/svc_acct_pop.pm: full number in POP pulldown
+
+2001-12-17 17:49 ivan
+
+ * FS/: MANIFEST, FS/svc_acct.pm: add freeside-setinvoice to
+ MANIFEST
+
+ fix warning:
+
+ FS::cust_main_invoice=HASH(0x90c86c4) at
+ /usr/local/lib/perl5/site_perl/5.005/FS/svc_acct.pm line 419.
+
+2001-12-17 15:59 ivan
+
+ * FS/FS/cust_bill.pm: fixes
+
+ Argument "" isn't numeric in ncmp at
+ /usr/local/lib/perl5/site_perl/5.005/FS/cust_bill.pm line 254.
+
+2001-12-16 15:50 ivan
+
+ * FS/FS/cust_main.pm: eek nasty bug
+
+2001-12-15 16:55 ivan
+
+ * FS/FS/cust_bill_pay.pm: fix weird rounding error: total
+ cust_bill_pay.amount 39.9 for paynum 240 greater than
+ cust_pay.paid 39.90
+
+2001-12-15 14:59 ivan
+
+ * httemplate/view/svc_acct.cgi: style
+
+2001-12-15 14:58 ivan
+
+ * FS/FS/cust_svc.pm: meaningful FS::cust_svc::label for svc_www
+ records
+
+2001-12-15 14:56 ivan
+
+ * httemplate/view/svc_www.cgi: view svc_www.cgi from Dave Burgess
+ <burgess@www.cynjut.net>, thanks
+
+2001-12-15 14:47 ivan
+
+ * httemplate/edit/cust_main.cgi: allow entering of referral
+ customer by number as well as by link off the view page, courtesy
+ of Dave Burgess <burgess@www.cynjut.net>
+
+2001-12-14 16:17 ivan
+
+ * FS/FS/cust_main.pm: style changes
+
+2001-12-13 10:37 ivan
+
+ * FS/bin/freeside-setinvoice: okay, it should really work now
+
+2001-12-13 09:52 ivan
+
+ * FS/bin/freeside-setinvoice: fix setinvoice script
+
+2001-12-13 01:17 ivan
+
+ * FS/bin/freeside-setinvoice: added util to set invoice
+ destinations
+
+2001-12-12 11:42 ivan
+
+ * httemplate/edit/: cust_main.cgi, svc_acct.cgi: allow >8 character
+ passwords in web interface
+
+2001-12-11 23:59 ivan
+
+ * bin/svc_acct.export: use pwd_mkdb to install
+ /etc/master.passwd.new instead of moving it into place
+
+2001-12-11 13:26 ivan
+
+ * httemplate/search/svc_acct.cgi: missing space in SQL, oops
+
+2001-12-11 02:38 ivan
+
+ * bin/svc_acct.export, httemplate/docs/install.html: radiusprepend
+ config file for export add Archive::Tar to docs
+
+2001-12-10 04:18 ivan
+
+ * FS/FS/Record.pm: Pg datatype pain
+
+2001-12-09 20:54 ivan
+
+ * httemplate/index.html: typo noticed by Dave Burgess
+ <burgess@www.cynjut.net>, thanks.
+
+2001-12-09 16:44 ivan
+
+ * httemplate/search/svc_acct.cgi: visual glitch: oops, $pager
+ wasn't getting substitued.
+
+2001-12-09 15:31 ivan
+
+ * httemplate/search/svc_acct.cgi: harmless? missing ;
+
+2001-12-08 02:08 ivan
+
+ * httemplate/config/config.cgi: need a POST here; browsers
+ (especially IE) are unhappy with the default GET
+
+2001-12-08 02:07 ivan
+
+ * FS/FS/cust_pay.pm: get custnum from invnum before trying to use
+ custnum!
+
+2001-12-08 02:03 ivan
+
+ * FS/FS/cust_main.pm: fix logic error creating invoice line items
+
+2001-12-08 02:01 ivan
+
+ * FS/FS/Conf.pm: radiusprepend config file for DEFAULT entries etc.
+
+2001-12-04 05:10 ivan
+
+ * httemplate/edit/svc_forward.cgi: from Dave Burgess
+ <burgess@neonramp.com>:
+
+ I had to change line 104 in /edit/svc_forward.cgi and add 'my'
+ as the
+ qualifier on the LHS of the assignment. This also solves the
+ problem with a similar error on the RHS of line 105. It also
+ seems to make
+ the routine work fairly reliably (it has been problematic for me
+ in the
+ past).
+
+2001-12-03 03:33 ivan
+
+ * httemplate/search/: cust_main.cgi, cust_pkg.cgi, svc_acct.cgi:
+ paged service browse!!
+
+2001-12-03 02:59 ivan
+
+ * httemplate/search/: cust_main.cgi, cust_pkg.cgi: paged package
+ browse
+
+2001-12-03 00:43 ivan
+
+ * httemplate/search/cust_main.cgi: paged customer browse!
+
+2001-12-03 00:41 ivan
+
+ * FS/FS/Conf.pm, FS/FS/Record.pm, conf/maxsearchrecordsperpage,
+ httemplate/search/cust_main.cgi: maxsearchrecordsperpage config
+ option paged implementation of customer browse!
+
+2001-11-29 16:04 ivan
+
+ * FS/FS/: cust_pkg.pm, cust_svc.pm, svc_Common.pm: more link
+ methods
+
+2001-11-20 19:42 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi: a more reasonalbe
+ name (!)
+
+2001-11-20 19:40 ivan
+
+ * FS/FS/Conf.pm, httemplate/edit/cust_main.cgi:
+ postalinvoicedefault config file
+
+2001-11-16 02:22 ivan
+
+ * httemplate/docs/billing.html: update link to Text::Template docs
+
+2001-11-13 13:27 ivan
+
+ * FS/: MANIFEST, bin/freeside-print-batch: remove
+ freeside-print-batch
+
+2001-11-12 05:19 ivan
+
+ * FS/FS/cust_main.pm: import hack to be less strict
+
+2001-11-09 10:26 ivan
+
+ * httemplate/docs/install.html: Pg7 dependancy
+
+2001-11-08 07:26 ivan
+
+ * FS/bin/freeside-queued: harmless typo noticed by "Edward
+ Shabotinsky" <lanshark@bsinet.net>, thanks
+
+2001-11-06 10:45 ivan
+
+ * httemplate/edit/process/part_pkg.cgi: remove gratuitous warnings
+
+2001-11-06 10:35 ivan
+
+ * Makefile: i REALLY suck at Makefiles
+
+2001-11-06 10:34 ivan
+
+ * Makefile: i suck at Makefiles
+
+2001-11-06 10:22 ivan
+
+ * Makefile: make foo for htetc/global.asa
+
+2001-11-06 09:58 ivan
+
+ * FS/FS/UID.pm: fix error message for s/htdocs/httemplate/
+
+2001-11-06 09:48 ivan
+
+ * bin/fs-setup: payinfo changed from length 16 to $char_d for
+ future expansion
+
+2001-11-05 13:30 ivan
+
+ * Makefile: more fixes for fresh installation
+
+2001-11-05 13:26 ivan
+
+ * Makefile: yet more Make
+
+2001-11-05 13:24 ivan
+
+ * Makefile: make create-database fix
+
+2001-11-05 12:12 ivan
+
+ * Makefile, FS/FS/Record.pm: makefile fixups
+
+2001-11-05 10:23 ivan
+
+ * httemplate/docs/install.html: doc
+
+2001-11-05 09:00 jeff
+
+ * FS/FS/svc_acct.pm: improved svc_acct replacement
+
+2001-11-05 08:42 ivan
+
+ * FS/FS/Record.pm: AUTOLOAD optimizations broke things rather
+ badly, oops
+
+2001-11-05 06:04 ivan
+
+ * FS/bin/freeside-bill: fixup getopt
+
+2001-11-05 06:03 ivan
+
+ * Makefile: move sys-dependant stuff to vars
+
+2001-11-05 05:57 ivan
+
+ * FS/FS/svc_acct.pm: doc tyop
+
+2001-11-05 04:07 ivan
+
+ * bin/svc_domain.import: this is unfinished and untested anyway,
+ but this corrects a silly typo
+
+2001-11-05 03:55 ivan
+
+ * FS/FS/cust_main.pm: better error messages for eval'ed setup/recur
+ expressions remove debugging warn output
+
+2001-11-03 09:49 ivan
+
+ * FS/MANIFEST, FS/FS/Record.pm, FS/FS/SearchCache.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pkg.pm, FS/FS/cust_svc.pm,
+ FS/FS/svc_acct.pm, FS/t/SearchCache.t,
+ httemplate/search/cust_main.cgi: new 'jsearch' call for big
+ joined searches & caching support preliminary customer browse
+ optimizations, much faster!
+
+2001-11-02 00:14 ivan
+
+ * FS/bin/freeside-print-batch: silence pod complaints
+
+2001-11-01 21:28 ivan
+
+ * httemplate/config/config.cgi: note config changes need
+ apache/freeside-queued restart to take effect
+
+2001-11-01 21:11 ivan
+
+ * FS/FS/Record.pm: depend on DBIx::DBSchema 0.19
+
+2001-11-01 20:55 ivan
+
+ * httemplate/config/config-view.cgi, httemplate/config/config.cgi,
+ FS/FS/Conf.pm: config web GUI updates. almost usable now.
+
+2001-10-31 16:16 ivan
+
+ * httemplate/search/cust_main.cgi: hit the database slightly less.
+ this page still takes forever with lots of customers.
+
+2001-10-31 08:52 ivan
+
+ * httemplate/browse/queue.cgi: queue display works again, even if
+ those links don't
+
+2001-10-30 11:05 ivan
+
+ * FS/FS/cust_pay_batch.pm, bin/fs-setup, httemplate/index.html,
+ httemplate/browse/cust_pay_batch.cgi,
+ httemplate/docs/schema.html: depriciate cust_pay_batch.trancode
+ web interface to view pending batch
+
+2001-10-30 07:42 ivan
+
+ * httemplate/edit/cust_main.cgi: put default country up top
+
+2001-10-30 07:41 ivan
+
+ * httemplate/edit/cust_main.cgi: precedence oops
+
+2001-10-30 07:39 ivan
+
+ * httemplate/edit/cust_main.cgi: better ordering of state selection
+ on new customer screen
+
+2001-10-30 06:54 ivan
+
+ * htetc/handler.pl, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/nas.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi, httemplate/browse/queue.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_type.cgi, httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_referral.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/misc/catchall.cgi,
+ httemplate/misc/delete-customer.cgi, httemplate/misc/link.cgi,
+ httemplate/search/cust_bill.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_domain.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_forward.cgi:
+ get rid of header foo in individual templates
+
+2001-10-30 06:28 ivan
+
+ * httemplate/docs/: install.html, upgrade8.html: docu
+
+2001-10-30 06:20 ivan
+
+ * htetc/handler.pl, httemplate/config/config-view.cgi,
+ httemplate/config/config.cgi: web config should workish now
+
+2001-10-30 05:49 ivan
+
+ * Makefile: work with CVS version too
+
+2001-10-30 05:48 ivan
+
+ * Makefile: make
+
+2001-10-30 05:47 ivan
+
+ * Makefile, FS/bin/freeside-adduser, conf/address, conf/domain,
+ conf/secrets, conf/shells, conf/smtpmachine,
+ httemplate/docs/install.html: `make create-config' installs
+ default config (conf dir update) freeside-adduser uses default
+ secrets file
+
+2001-10-30 04:38 ivan
+
+ * Makefile: make
+
+2001-10-30 04:35 ivan
+
+ * Makefile, httemplate/docs/install.html: makefile typo
+
+2001-10-30 03:47 ivan
+
+ * Makefile, FS/bin/freeside-adduser, httemplate/docs/admin.html,
+ httemplate/docs/config.html, httemplate/docs/index.html,
+ httemplate/docs/install.html: whew more install docs and
+ automation
+
+2001-10-30 02:20 ivan
+
+ * Makefile, FS/MANIFEST, FS/FS/Conf.pm, FS/bin/freeside-adduser,
+ httemplate/docs/install.html: setup/config updates. getting
+ easier...
+
+2001-10-29 13:22 ivan
+
+ * Makefile: fix make clean
+
+2001-10-29 12:54 ivan
+
+ * httemplate/docs/install.html: doc typo
+
+2001-10-29 12:53 ivan
+
+ * FS/FS/svc_forward.pm: methods for getting the associated svc_acct
+ records
+
+2001-10-29 09:17 ivan
+
+ * Makefile: automate a tiny bit more
+
+2001-10-26 03:24 ivan
+
+ * FS/FS/CGI.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/browse/agent.cgi, httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/nas.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi, httemplate/browse/queue.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_type.cgi, httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_referral.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/misc/catchall.cgi,
+ httemplate/misc/delete-customer.cgi, httemplate/misc/link.cgi,
+ httemplate/search/cust_bill.cgi, httemplate/search/cust_main.cgi,
+ httemplate/search/cust_pkg.cgi, httemplate/search/svc_acct.cgi,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_domain.cgi, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi, httemplate/view/svc_forward.cgi:
+ cache foo *sigh*
+
+2001-10-26 02:50 ivan
+
+ * httemplate/config/config-process.cgi: otherwise people will have
+ problems with `0' zero
+
+2001-10-25 14:24 ivan
+
+ * httemplate/edit/svc_domain.cgi: remove ancient tld cruft
+
+2001-10-25 09:13 ivan
+
+ * FS/FS/cust_main_invoice.pm: & in email addresses
+
+2001-10-25 01:41 ivan
+
+ * FS/FS/Conf.pm: remove debugging cruft
+
+2001-10-24 08:45 ivan
+
+ * httemplate/config/config-process.cgi: correct for browser
+ munching
+
+2001-10-24 08:29 ivan
+
+ * FS/FS.pm, FS/MANIFEST, FS/FS/Conf.pm, FS/FS/ConfItem.pm,
+ FS/FS/svc_acct.pm, FS/FS/svc_domain.pm, FS/t/ConfItem.t,
+ bin/fs-setup, bin/svc_acct.export, httemplate/index.html,
+ httemplate/config/config-process.cgi,
+ httemplate/config/config-view.cgi, httemplate/docs/config.html,
+ httemplate/docs/install.html, httemplate/search/svc_acct.cgi:
+ preliminary web config editor
+
+ new config files: username-ampersand, passwordmax
+
+ fs-setup updates
+
+ get rid of old and crufty and unused registries/ config foo
+
+ documentation updates
+
+2001-10-23 17:59 ivan
+
+ * httemplate/browse/queue.cgi: Can't find string terminator "!"
+ anywhere before EOF at queue.cgi line 42
+
+2001-10-23 13:53 ivan
+
+ * FS/bin/freeside-queued: Pg: FOR UPDATE LIMIT 1 mysql: LIMIT 1 FOR
+ UPDATE
+
+ greeeat.
+
+2001-10-23 11:57 ivan
+
+ * httemplate/docs/: config.html, install.html: document how to set
+ MySQL with BDB default tables
+
+2001-10-23 11:15 ivan
+
+ * bin/fs-setup: mysql fixes
+
+2001-10-22 07:48 ivan
+
+ * FS/FS/svc_acct.pm: fix dir check
+
+2001-10-22 05:22 ivan
+
+ * FS/FS/svc_domain.pm: fix delete method for new databases
+
+2001-10-22 01:31 ivan
+
+ * FS/FS/cust_main.pm: tyop
+
+2001-10-22 01:29 ivan
+
+ * FS/FS/: cust_main.pm, cust_pkg.pm: better delete customer code &
+ warnings, delete package ability (& warning)
+
+2001-10-20 05:17 ivan
+
+ * README.1.4.0pre3-4, FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ bin/fs-setup, htetc/global.asa, htetc/handler.pl,
+ httemplate/browse/part_pkg.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/part_pkg.cgi: setup and recurring fee tax
+ exempt flags, UI to edit rework part_pkg editing UI some more
+
+2001-10-16 13:33 jeff
+
+ * bin/svc_acct.export: added slipip insertion for icradius and
+ vpopmail restart config
+
+2001-10-15 07:58 ivan
+
+ * FS/FS/cust_pkg.pm, htetc/global.asa, htetc/handler.pl,
+ httemplate/edit/REAL_cust_pkg.cgi,
+ httemplate/edit/process/REAL_cust_pkg.cgi,
+ httemplate/view/cust_pkg.cgi: date editing
+
+2001-10-15 05:16 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_main.pm, FS/FS/cust_pkg.pm,
+ httemplate/misc/bill.cgi: print reasons with credits on invoices
+
+ use straight eval, not Safe::reval in cust_main::bill for now, as
+ i have no idea how to call methods on a share()'ed scalar.
+ hmm.
+
+ add cust_pkg::cust_main method
+
+ s/eidiot/idiot/ in httemplate/misc/bill.cgi
+
+2001-10-15 04:39 ivan
+
+ * httemplate/edit/part_pkg.cgi: remove errant javascript alert()
+ plan ||= 'flat' for custom pricing 9clone) package definitions
+ too
+
+2001-10-15 04:35 ivan
+
+ * httemplate/edit/cust_main.cgi: visual fix
+
+2001-10-15 03:42 ivan
+
+ * README.1.4.0pre3-4, FS/FS/cust_main.pm, FS/FS/part_pkg.pm,
+ bin/fs-setup, httemplate/browse/part_pkg.cgi,
+ httemplate/docs/config.html, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/part_pkg.cgi,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_pkg.cgi: price plans web gui 1st
+ pass, oh my
+
+2001-10-12 08:26 ivan
+
+ * httemplate/browse/queue.cgi: add (as yet inactive) retry & remove
+ links
+
+2001-10-11 10:46 ivan
+
+ * htetc/: global.asa, handler.pl: price plan uI!
+
+2001-10-11 10:44 ivan
+
+ * httemplate/edit/part_pkg.cgi: "price plans" UP support.CVS:
+ ----------------------------------------------------------------------
+
+2001-10-09 23:22 thalakan
+
+ * httemplate/search/cust_main.cgi: Reverted.
+
+2001-10-09 22:59 thalakan
+
+ * httemplate/search/cust_main.cgi: Stupid mistake. Works now.
+
+2001-10-09 22:42 ivan
+
+ * httemplate/search/cust_main.cgi: revert silly changes
+
+2001-10-09 22:33 thalakan
+
+ * httemplate/search/cust_main.cgi: Change to use ut_name instead.
+
+2001-10-09 22:24 ivan
+
+ * FS/FS/Record.pm: embarassing doc fix, thanks jason
+
+2001-10-09 16:10 ivan
+
+ * README.1.4.0pre3-4, FS/FS/cust_credit.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_pkg.pm,
+ httemplate/docs/config.html, httemplate/docs/install.html,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html,
+ bin/fs-setup: add `unsuspendauto' config file: enable 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
+
+ add cust_pkg.manual_flag to disable this behaviour per customer
+ package (no UI to set this yet)
+
+2001-10-09 06:16 ivan
+
+ * httemplate/docs/install.html: update mysql links
+
+2001-10-09 00:16 ivan
+
+ * httemplate/edit/process/part_svc.cgi: that was a confusing bug to
+ fix
+
+2001-10-08 20:11 ivan
+
+ * FS/FS/cust_pkg.pm: fix syntax error in newly-enabled insert sub,
+ sheesh
+
+2001-10-02 13:07 ivan
+
+ * httemplate/docs/upgrade8.html: doc clarification (?)
+
+2001-10-02 09:00 jeff
+
+ * README.1.4.0pre3-4, FS/FS/cust_pay_batch.pm, bin/fs-setup,
+ httemplate/docs/upgrade8.html: add pkey to batch payments and fix
+ a doc typo
+
+2001-10-02 04:10 ivan
+
+ * FS/FS/svc_acct.pm: allow some more characters in GECOS... showing
+ up in fix.net's password files
+
+2001-10-01 03:31 ivan
+
+ * FS/FS/cust_pkg.pm: oops
+
+2001-09-30 15:35 ivan
+
+ * FS/FS/svc_acct.pm: arg
+
+2001-09-30 15:19 ivan
+
+ * FS/FS/svc_acct.pm: $1 doesn't seem to last very long...
+
+2001-09-30 13:30 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/docs/config.html:
+ username-uppercase config file
+
+2001-09-27 21:33 ivan
+
+ * httemplate/docs/install.html: ssh recommendations
+
+2001-09-27 14:49 ivan
+
+ * FS/FS/svc_acct_pop.pm: not used
+
+2001-09-27 14:12 ivan
+
+ * httemplate/edit/: cust_main.cgi, svc_acct.cgi: popSELECTOR
+
+2001-09-27 13:41 ivan
+
+ * httemplate/edit/cust_main.cgi, httemplate/edit/svc_acct.cgi,
+ FS/FS/svc_acct_pop.pm, httemplate/view/svc_acct.cgi: pop
+ selector!
+
+2001-09-27 11:33 ivan
+
+ * httemplate/view/svc_acct.cgi: fix for pre-1.4.0 accounts with no
+ domsvc
+
+2001-09-26 04:34 ivan
+
+ * README.1.4.0pre3-4: index oops
+
+2001-09-26 02:17 ivan
+
+ * README.1.4.0pre3-4, FS/FS.pm, FS/MANIFEST,
+ FS/FS/cust_credit_bill.pm, FS/FS/cust_main.pm,
+ FS/FS/part_pop_local.pm, FS/FS/svc_acct_pop.pm,
+ FS/t/part_pop_local.t, bin/fs-setup, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html: add part_pop_local table
+
+2001-09-25 11:03 ivan
+
+ * FS/FS/cust_main.pm:
+ ror
+
+2001-09-25 11:01 ivan
+
+ * FS/FS/cust_main.pm, httemplate/docs/config.html: add
+ emailinvoiceonly config file and begin to use it
+
+2001-09-25 08:55 ivan
+
+ * FS/FS/cust_main.pm, httemplate/docs/config.html:
+ Business::OnlinePayment::BankOfAmerica
+
+2001-09-24 17:05 ivan
+
+ * FS/bin/freeside-queued: better REAPER
+
+2001-09-23 20:23 ivan
+
+ * FS/: FS/UID.pm, bin/freeside-queued: queue daemon forks now
+
+2001-09-20 20:47 ivan
+
+ * httemplate/view/cust_main.cgi: show company and name (instead of
+ just company) for referring customers that have a company
+
+2001-09-19 17:13 ivan
+
+ * FS/FS/cust_main.pm: really fix fuzzy searching
+
+2001-09-19 17:10 ivan
+
+ * FS/FS/cust_main.pm: fix fuzzy searching
+
+2001-09-19 14:51 ivan
+
+ * FS/bin/freeside-queued: set real uid too. whew. ssh now
+ working.
+
+2001-09-19 14:06 ivan
+
+ * README.1.4.0pre3-4, FS/MANIFEST, FS/FS/svc_acct.pm,
+ FS/bin/freeside-apply-credits, httemplate/docs/config.html:
+ directory hashing remove jeff's lib patch from
+ freeside-apply-credits add freeside-apply-credits to MANIFEST
+ README for pre3-4
+
+2001-09-19 12:41 ivan
+
+ * FS/FS/svc_acct.pm: tyop
+
+2001-09-19 12:39 ivan
+
+ * FS/FS/svc_acct.pm: hopefully report some sort of ssh error
+
+2001-09-19 12:28 ivan
+
+ * FS/FS/svc_acct.pm: ;args
+
+2001-09-19 12:19 ivan
+
+ * FS/FS/svc_acct.pm: FS::svc_acct::ssh
+
+2001-09-19 12:00 ivan
+
+ * FS/bin/freeside-queued: set $ENV{HOME}
+
+2001-09-18 17:24 ivan
+
+ * FS/FS/svc_acct.pm: icradius transactional password changes
+ (suspensions, unsuspensions)
+
+2001-09-16 05:45 ivan
+
+ * FS/FS/Record.pm, FS/FS/cust_main_invoice.pm, FS/FS/svc_acct.pm,
+ httemplate/index.html, httemplate/docs/config.html,
+ httemplate/search/cust_main.cgi, httemplate/view/cust_main.cgi:
+ fix oops in FS::cust_main_invoice::replace preventing package
+ cancellation
+
+ add toggle switch to cust_main searching to show/hide cancelled
+ customers. hidecancelledcustomers config file is just which
+ state it starts in.
+
+ add signupurl config file to enable showing of the customer's
+ signup URL on the view page.
+
+2001-09-14 12:54 ivan
+
+ * FS/FS/svc_acct.pm: fix for no svc_acct_sm!!!
+
+2001-09-14 11:05 ivan
+
+ * FS/FS/Record.pm: s/croak/confess/
+
+2001-09-12 08:54 ivan
+
+ * httemplate/docs/: install.html, upgrade8.html: mason warnings
+
+2001-09-12 08:45 ivan
+
+ * FS/FS/part_svc.pm: oops
+
+2001-09-11 16:44 ivan
+
+ * FS/FS/part_svc.pm, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/svc_forward.cgi, httemplate/view/svc_acct.cgi:
+ radius attributes & default/fixed values should work again now
+
+2001-09-11 15:20 ivan
+
+ * FS/FS/svc_Common.pm, bin/fs-radius-add-check,
+ bin/fs-radius-add-reply, bin/fs-setup, bin/svc_acct.export: fix
+ radius attribute adding
+
+2001-09-11 14:58 ivan
+
+ * FS/bin/freeside-queued: usage sub
+
+2001-09-11 13:59 ivan
+
+ * httemplate/edit/svc_acct_sm.cgi: persistance problem not clearing
+ variables between runs
+
+2001-09-11 13:44 ivan
+
+ * httemplate/search/svc_acct.cgi: getting "you should run the
+ bin/fs-migrate-svc_acct_sm" message unexpectedly
+
+2001-09-11 12:16 ivan
+
+ * httemplate/edit/part_svc.cgi: forgot closing form tag, breaks ie,
+ others?
+
+2001-09-11 06:10 ivan
+
+ * FS/FS/svc_acct.pm: transactional job-queued icradius/freeradius
+ export
+
+2001-09-11 05:46 ivan
+
+ * httemplate/edit/process/part_svc.cgi: more service edit fix
+
+2001-09-11 05:42 ivan
+
+ * httemplate/edit/part_svc.cgi: fix service editing
+
+2001-09-11 05:25 ivan
+
+ * FS/FS/queue.pm: queue clean fix
+
+2001-09-11 05:24 ivan
+
+ * FS/FS/queue.pm: job queue fix
+
+2001-09-11 05:10 ivan
+
+ * FS/FS/cust_main.pm: fuzzyfix
+
+2001-09-11 05:06 ivan
+
+ * FS/FS/svc_acct.pm: better logging
+
+2001-09-11 05:00 ivan
+
+ * FS/FS/svc_acct.pm: cyrus fix!
+
+2001-09-11 04:52 ivan
+
+ * FS/bin/freeside-queued: rar
+
+2001-09-11 03:05 ivan
+
+ * httemplate/edit/process/quick-cust_pkg.cgi: s/die/ediot/
+
+2001-09-11 02:56 ivan
+
+ * README.1.4.0pre2-3, httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/view/cust_main.cgi: fix quick order
+
+2001-09-10 21:44 ivan
+
+ * httemplate/browse/queue.cgi: web queue view
+
+2001-09-10 21:17 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/docs/config.html: username-noperiod
+ config file
+
+2001-09-10 20:15 ivan
+
+ * README.1.4.0pre2-3, FS/FS/cust_main.pm, FS/FS/svc_acct.pm,
+ FS/bin/freeside-queued, httemplate/docs/config.html,
+ httemplate/docs/export.html, httemplate/edit/part_svc.cgi: cyrus
+ support
+
+2001-09-10 18:09 ivan
+
+ * FS/bin/freeside-queued: working queued
+
+2001-09-10 17:08 ivan
+
+ * README.1.4.0pre2-3, FS/FS.pm, FS/MANIFEST, FS/FS/Record.pm,
+ FS/FS/cust_credit_bill.pm, FS/FS/cust_main.pm, FS/FS/part_svc.pm,
+ FS/FS/queue.pm, FS/FS/queue_arg.pm, FS/bin/freeside-bill,
+ FS/bin/freeside-queued, FS/t/queue.t, FS/t/queue_arg.t,
+ htetc/handler.pl, httemplate/index.html,
+ httemplate/browse/part_svc.cgi, httemplate/docs/install.html,
+ httemplate/docs/schema.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/search/cust_main.cgi: faster (cached) fuzzy searches
+ prelim. job queues! fixed part_svc editing
+
+2001-09-10 17:07 ivan
+
+ * bin/: freeside-init, fs-migrate-part_svc, fs-setup: fixing
+ fs-migrate-part_svc updateing fs-setup for job queues
+ freeside-init for starting freeside-queued
+
+2001-09-07 17:28 khoff
+
+ * bin/fs-setup: First post. Sorry. Missing comma.
+
+2001-09-07 13:49 ivan
+
+ * FS/FS/part_svc_column.pm: oops, forgot to add this
+
+2001-09-07 13:26 ivan
+
+ * FS/FS/raddb.pm, FS/FS/svc_acct.pm, bin/generate-raddb: tyops
+
+2001-09-07 13:19 ivan
+
+ * FS/MANIFEST: tyop
+
+2001-09-07 13:17 ivan
+
+ * FS/MANIFEST, FS/FS/raddb.pm, FS/FS/svc_acct.pm, FS/t/raddb.t,
+ bin/fs-radius-add-check, bin/fs-radius-add-reply, bin/fs-setup,
+ bin/generate-raddb: fix RADIUS attribute capitalization
+
+2001-09-06 14:43 ivan
+
+ * httemplate/docs/: install.html, postgresql.html: no more pg
+ RADIUS silliness
+
+2001-09-06 14:20 ivan
+
+ * FS/FS/svc_acct.pm, httemplate/docs/schema.html: doc
+
+2001-09-06 13:41 ivan
+
+ * README.1.4.0pre2-3, FS/FS.pm, FS/MANIFEST, FS/FS/UID.pm,
+ FS/FS/part_svc.pm, FS/FS/svc_Common.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_acct_sm.pm, FS/FS/svc_domain.pm, FS/FS/svc_forward.pm,
+ FS/FS/svc_www.pm, FS/t/part_svc_column.t,
+ bin/fs-migrate-part_svc, bin/fs-migrate-payref,
+ bin/fs-radius-add-check, bin/fs-radius-add-reply, bin/fs-setup,
+ httemplate/browse/part_svc.cgi, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/part_svc.cgi,
+ httemplate/edit/process/part_svc.cgi: finally fix part_svc!!!
+
+2001-09-04 08:12 ivan
+
+ * httemplate/docs/admin.html: tyop
+
+2001-09-04 08:06 ivan
+
+ * httemplate/edit/: agent_type.cgi, process/cust_main.cgi: oops
+ indeed
+
+2001-09-04 07:44 ivan
+
+ * FS/FS/cust_pkg.pm, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_svc.cgi, httemplate/docs/admin.html,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/process/quick-cust_pkg.cgi,
+ httemplate/view/cust_main.cgi: new admin documentation, quick
+ one-pkg order
+
+2001-09-04 04:15 ivan
+
+ * bin/fs-setup: rar
+
+2001-09-04 04:15 ivan
+
+ * bin/fs-setup: much better
+
+2001-09-04 04:14 ivan
+
+ * bin/fs-setup: tyops
+
+2001-09-04 04:03 ivan
+
+ * bin/fs-setup, httemplate/docs/config.html: silly syntax error and
+ doc updates
+
+2001-09-04 02:55 ivan
+
+ * httemplate/docs/install.html: better installation instructions
+
+2001-09-04 02:54 ivan
+
+ * htetc/global.asa: hehe, this should help out everybody without
+ DBIx::Profile (i.e. everybody)
+
+2001-09-03 15:16 ivan
+
+ * bin/fs-setup: this too
+
+2001-09-03 15:07 ivan
+
+ * FS/FS/cust_bill.pm, FS/FS/cust_bill_pay.pm, FS/FS/cust_main.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_svc.pm, FS/bin/freeside-bill,
+ httemplate/docs/signup.html, httemplate/docs/upgrade8.html,
+ httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/cust_pay.cgi,
+ httemplate/edit/process/cust_pay.cgi, httemplate/misc/bill.cgi,
+ httemplate/view/cust_bill.cgi, httemplate/view/cust_main.cgi: fix
+ more bugs
+
+2001-09-02 00:57 ivan
+
+ * CREDITS: credito
+
+2001-09-02 00:57 ivan
+
+ * bin/generate-tests: gawl
+
+2001-09-02 00:49 ivan
+
+ * FS/FS/cust_bill_pay.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_credit_bill.pm, FS/FS/cust_credit_refund.pm,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/view/cust_main.cgi: things are starting to work again,
+ sorta.
+
+2001-09-01 22:38 ivan
+
+ * FS/FS/cust_bill_pay.pm, FS/FS/cust_credit_refund.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm, bin/fs-migrate-payref:
+ migration. ugh.
+
+2001-09-01 21:51 ivan
+
+ * FS/FS/: cust_svc.pm, svc_acct.pm: better error msgs
+
+2001-09-01 21:25 ivan
+
+ * FS/: FS.pm, MANIFEST, test.pl, FS/cust_bill_pay.pm,
+ FS/cust_main.pm, FS/cust_refund.pm, t/CGI.t, t/CGIwrapper.t,
+ t/Conf.t, t/Record.t, t/UID.t, t/agent.t, t/agent_type.t,
+ t/cust_bill.t, t/cust_bill_pay.t, t/cust_bill_pkg.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_pkg.t, t/cust_refund.t,
+ t/cust_svc.t, t/domain_record.t, t/nas.t, t/part_pkg.t,
+ t/part_referral.t, t/part_svc.t, t/pkg_svc.t, t/port.t,
+ t/prepay_credit.t, t/session.t, t/svc_Common.t, t/svc_acct.t,
+ t/svc_acct_pop.t, t/svc_acct_sm.t, t/svc_domain.t,
+ t/svc_forward.t, t/svc_www.t, t/type_pkgs.t: lame testsuite and
+ the embarassing fixes in it found
+
+2001-09-01 20:42 ivan
+
+ * httemplate/docs/upgrade8.html: really. backup.
+
+2001-09-01 20:20 ivan
+
+ * httemplate/docs/upgrade8.html: upgrade
+
+2001-09-01 19:53 jeff
+
+ * FS/bin/freeside-apply-credits: adding a credit apply utility
+
+2001-09-01 19:46 ivan
+
+ * FS/FS/cust_bill_pay.pm, FS/FS/cust_main.pm, FS/FS/cust_pay.pm,
+ FS/FS/cust_refund.pm, bin/fs-setup, httemplate/docs/schema.html,
+ httemplate/docs/upgrade8.html: cust_refund and cust_pay get
+ custnums
+
+2001-09-01 18:27 ivan
+
+ * FS/FS/: cust_bill.pm, cust_bill_pay.pm, cust_credit.pm,
+ cust_credit_bill.pm, cust_credit_refund.pm, cust_main.pm,
+ cust_refund.pm: more udpates for the new world of unapplied
+ stuff. yay.
+
+2001-09-01 16:41 ivan
+
+ * httemplate/docs/upgrade8.html: cust_bill_pay and
+ cust_credit_refund
+
+2001-09-01 15:28 jeff
+
+ * FS/FS/cust_main.pm, httemplate/docs/upgrade8.html: haste makes
+ waste... and left a method out
+
+2001-09-01 15:18 ivan
+
+ * FS/FS/cust_credit_bill.pm, bin/fs-setup: add primary key
+
+2001-09-01 15:01 ivan
+
+ * FS/MANIFEST: cust_bill_pay & cust_credit_refund
+
+2001-09-01 14:55 jeff
+
+ * FS/FS/cust_bill_pay.pm: oops.. need this too
+
+2001-09-01 14:52 jeff
+
+ * FS/MANIFEST, FS/FS/cust_bill.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_credit_bill.pm, FS/FS/cust_main.pm, bin/fs-setup,
+ httemplate/docs/upgrade8.html,
+ httemplate/edit/cust_credit_bill.cgi,
+ httemplate/edit/process/cust_credit_bill.cgi,
+ httemplate/view/cust_main.cgi: add cust_credit_bill relating
+ multiple invoices to credits
+
+2001-09-01 13:11 ivan
+
+ * FS/FS/cust_bill_pay.pm, FS/FS/cust_credit_refund.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pay.pm, FS/FS/cust_refund.pm,
+ bin/fs-setup, httemplate/docs/schema.html: cust_bill_pay and
+ cust_credit_refund. payments can apply to multiple invoices and
+ refunds can apply to multiple credits.
+
+2001-09-01 05:35 ivan
+
+ * httemplate/search/cust_bill.cgi: totals
+
+2001-08-31 02:20 ivan
+
+ * FS/FS/Record.pm: check US zips more strictly
+
+2001-08-31 01:43 ivan
+
+ * httemplate/browse/svc_acct_pop.cgi: > Order by City might be
+ better
+ state, city, then number
+
+2001-08-31 00:28 ivan
+
+ * httemplate/edit/cust_main_county-expand.cgi: better error message
+ for nonexistant cust_main_county records
+
+2001-08-31 00:17 ivan
+
+ * httemplate/edit/process/cust_main_county-expand.cgi: fixes:
+ > US UT expand state
+ > Submit (no data entered)
+ > US UT is gone frome the list.
+
+2001-08-30 09:23 ivan
+
+ * httemplate/docs/signup.html: update signup server: template form
+ and success html, document
+
+2001-08-30 08:30 ivan
+
+ * httemplate/edit/part_svc.cgi: slighly more docu
+
+2001-08-29 02:11 ivan
+
+ * httemplate/edit/agent.cgi: ui
+
+2001-08-29 01:45 ivan
+
+ * bin/fs-setup, httemplate/docs/upgrade8.html: catchall *can* be
+ NULL
+
+2001-08-28 09:58 ivan
+
+ * httemplate/: docs/signup.html, docs/upgrade8.html,
+ search/cust_main.cgi: customer-to-customer referrals in singup
+ server
+
+2001-08-28 07:34 ivan
+
+ * FS/FS/cust_main.pm, httemplate/docs/config.html,
+ httemplate/docs/upgrade8.html, httemplate/edit/cust_main.cgi,
+ httemplate/search/cust_main.cgi, httemplate/view/cust_main.cgi:
+ customer-to-customer referrals!
+
+2001-08-25 22:06 ivan
+
+ * FS/FS/: cust_credit.pm, cust_main.pm: brainfart
+
+2001-08-25 22:05 ivan
+
+ * FS/FS/cust_credit.pm: reason can't be null
+
+2001-08-22 23:17 ivan
+
+ * FS/FS/cust_main.pm: no need to be y1.9k complient
+
+2001-08-21 03:27 ivan
+
+ * httemplate/edit/: svc_acct.cgi, svc_forward.cgi: fix these up,
+ most everything is working svc_forward-wise now
+
+2001-08-21 02:34 ivan
+
+ * FS/FS/Record.pm, FS/FS/UID.pm, FS/bin/freeside-bill,
+ htetc/global.asa, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/svc_acct_pop.cgi: no more &swapuid
+
+2001-08-20 20:03 ivan
+
+ * FS/FS/svc_acct.pm: fix domain method, and it works against old
+ databases now too
+
+2001-08-20 19:44 ivan
+
+ * FS/FS/domain_record.pm, FS/FS/prepay_credit.pm, FS/FS/svc_www.pm,
+ FS/bin/freeside-print-batch, eg/TEMPLATE_cust_main.import,
+ eg/table_template-svc.pm: remove $Log$
+
+2001-08-20 19:43 ivan
+
+ * bin/: dbdef-create, fs-migrate-svc_acct_sm, svc_acct_sm.import: i
+ think svc_acct_sm.import should go away, but...
+
+2001-08-20 19:32 ivan
+
+ * test/cgi-test: remove $Log$
+
+2001-08-20 19:31 ivan
+
+ * httemplate/: browse/agent.cgi, browse/agent_type.cgi,
+ browse/part_pkg.cgi, browse/part_referral.cgi,
+ browse/svc_acct_pop.cgi, edit/agent.cgi, edit/agent_type.cgi,
+ edit/cust_credit.cgi, edit/cust_main_county-expand.cgi,
+ edit/cust_main_county.cgi, edit/cust_pay.cgi, edit/cust_pkg.cgi,
+ edit/part_pkg.cgi, edit/part_referral.cgi, edit/svc_acct_pop.cgi,
+ edit/svc_acct_sm.cgi, edit/svc_domain.cgi, edit/svc_forward.cgi,
+ edit/process/agent.cgi, edit/process/agent_type.cgi,
+ edit/process/cust_credit.cgi, edit/process/cust_main_county.cgi,
+ edit/process/cust_pay.cgi, edit/process/cust_pkg.cgi,
+ edit/process/part_pkg.cgi, edit/process/part_referral.cgi,
+ edit/process/svc_acct.cgi, edit/process/svc_acct_pop.cgi,
+ edit/process/svc_acct_sm.cgi, edit/process/svc_domain.cgi,
+ edit/process/svc_forward.cgi, misc/bill.cgi,
+ misc/cancel-unaudited.cgi, misc/cancel_pkg.cgi,
+ misc/catchall.cgi, misc/delete-customer.cgi, misc/expire_pkg.cgi,
+ misc/link.cgi, misc/print-invoice.cgi, misc/susp_pkg.cgi,
+ misc/unsusp_pkg.cgi, misc/process/catchall.cgi,
+ misc/process/delete-customer.cgi, misc/process/link.cgi,
+ search/cust_bill.cgi, search/svc_acct_sm.cgi,
+ search/svc_domain.cgi, view/cust_bill.cgi, view/cust_pkg.cgi,
+ view/svc_acct.cgi, view/svc_acct_sm.cgi, view/svc_domain.cgi,
+ view/svc_forward.cgi: remove $Log$ messages. whew.
+
+2001-08-20 19:16 ivan
+
+ * httemplate/search/svc_acct.cgi: fix to work against 1.3.x
+ databases, with a warning
+
+2001-08-20 19:03 ivan
+
+ * httemplate/search/cust_pkg.cgi: deal with databases w/o separate
+ shipping address
+
+2001-08-20 17:39 ivan
+
+ * FS/FS/: svc_acct.pm, svc_domain.pm, svc_forward.pm: fix some
+ silly syntax errors
+
+2001-08-20 06:10 ivan
+
+ * FS/FS/svc_domain.pm: use var
+
+2001-08-20 05:15 ivan
+
+ * httemplate/edit/svc_acct.cgi: #
+
+2001-08-20 05:13 ivan
+
+ * httemplate/edit/svc_acct.cgi: only show domains associated with
+ this customer.
+
+2001-08-20 04:18 ivan
+
+ * FS/FS/cust_svc.pm: labels for svc_acct accounts are now fully
+ qualified!
+
+2001-08-20 04:04 ivan
+
+ * FS/FS/: svc_acct.pm, svc_domain.pm, svc_forward.pm: more
+ svc_forward work
+
+2001-08-20 02:41 ivan
+
+ * FS/FS/: svc_acct.pm, svc_acct_sm.pm, svc_domain.pm,
+ svc_forward.pm: dtrt when deleting accouts wrt forwards,
+ catchalls & other references to svc_acct records
+
+ depreciate svc_acct_sm further; move qmail catchall handling to
+ svc_domain
+
+2001-08-20 02:38 ivan
+
+ * httemplate/docs/: config.html, export.html, schema.html:
+ documentation updates
+
+2001-08-19 08:53 jeff
+
+ * FS/MANIFEST, FS/FS/cust_svc.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_forward.pm, httemplate/edit/part_svc.cgi,
+ httemplate/edit/svc_acct.cgi, httemplate/edit/svc_forward.cgi,
+ httemplate/edit/process/svc_forward.cgi,
+ httemplate/misc/catchall.cgi,
+ httemplate/misc/process/catchall.cgi,
+ httemplate/search/svc_acct.cgi, httemplate/search/svc_domain.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_domain.cgi,
+ httemplate/view/svc_forward.cgi: added user interface for
+ svc_forward and vpopmail support
+
+2001-08-19 06:50 ivan
+
+ * bin/fs-setup, httemplate/docs/upgrade8.html: indices on cust_main
+ ship_last and ship_country
+
+2001-08-19 03:25 ivan
+
+ * bin/svc_acct.import: add system shells to @FS::svc_acct:shells on
+ the fly, fixes: ticket #88
+
+2001-08-19 02:08 ivan
+
+ * TODO: this is what i fed to RT, might as well check it in.
+
+ no further modifications to TODO. everything is in the ticketing
+ system now.
+
+2001-08-19 01:32 ivan
+
+ * httemplate/search/cust_pkg.cgi: display bill and service
+ name/company, and services
+
+2001-08-19 01:18 ivan
+
+ * FS/FS/: svc_acct.pm, svc_domain.pm: ->setfixed untaints svcnum
+
+2001-08-19 01:15 ivan
+
+ * FS/FS/svc_acct.pm: set fixed fields before checking domsvc
+
+2001-08-18 17:48 ivan
+
+ * FS/FS/: Record.pm, cust_main.pm: country fields will now accept
+ names and change them to two-letter codes
+
+2001-08-17 04:33 ivan
+
+ * FS/FS/Record.pm: ut_phonen: fallback to ut_textn, instead of
+ ut_alphan for non-US/CA phone numbers
+
+2001-08-17 04:28 ivan
+
+ * FS/FS/Record.pm: canada has same phone# rules as US
+
+2001-08-17 04:05 ivan
+
+ * httemplate/: browse/cust_main_county.cgi,
+ edit/cust_main_county.cgi,
+ edit/process/cust_main_county-collapse.cgi,
+ edit/process/cust_main_county-expand.cgi: clean up tax rate
+ editing: sort by country->state->county, add "collapse state"
+ if the tax rates are the same statewide, redirect "expand
+ state" to the browse, not edit screen
+
+2001-08-17 03:57 ivan
+
+ * bin/: dbdef-create, svc_acct.export, svc_acct.import: gah
+
+2001-08-17 03:57 ivan
+
+ * FS/FS/svc_domain.pm: document catchall and untaint everything
+
+2001-08-17 03:55 ivan
+
+ * FS/FS/cust_main.pm, httemplate/docs/config.html:
+ Business::OnlinePayment
+
+2001-08-17 03:53 ivan
+
+ * Makefile: alldocs target
+
+2001-08-17 03:53 ivan
+
+ * CREDITS: thanks jeff!
+
+2001-08-15 03:04 ivan
+
+ * httemplate/search/: cust_pkg.cgi, svc_acct.cgi: remove (some of
+ the) bad direct exit; calls
+
+2001-08-13 16:15 ivan
+
+ * httemplate/view/cust_main.cgi: don't display empty comment box
+
+2001-08-13 16:10 ivan
+
+ * httemplate/edit/process/cust_main.cgi: properly massage
+ ship_state & work okay when no initial pkg is selected
+
+2001-08-12 18:00 ivan
+
+ * httemplate/docs/schema.html: add svc_forward, depriciate
+ svc_acct_sm
+
+2001-08-12 17:21 ivan
+
+ * FS/FS/svc_acct.pm: untaint svcnum & domsvc
+
+2001-08-12 17:19 ivan
+
+ * bin/fs-setup: depriciate svc_acct_sm, add unique index for
+ username+domsvc on svc_acct, remove silly $Log$
+
+2001-08-12 12:41 jeff
+
+ * FS/FS/svc_acct.pm, FS/FS/svc_domain.pm, FS/FS/svc_forward.pm,
+ bin/fs-migrate-svc_acct_sm, bin/fs-setup, bin/svc_acct.export,
+ bin/svc_acct_sm.export, httemplate/docs/config.html,
+ httemplate/docs/upgrade8.html: merging vpopmail support branch
+
+2001-08-12 01:56 ivan
+
+ * httemplate/edit/process/part_svc.cgi: don't use
+ Apache::ASP-specific $Redirect object
+
+2001-08-11 18:26 ivan
+
+ * httemplate/docs/postgresql.html: i did get it working
+
+2001-08-11 17:07 ivan
+
+ * httemplate/edit/: cust_main.cgi, process/cust_main.cgi: use
+ transactional invoice_list setting & eliminate non-sticky "first
+ package"!
+
+2001-08-11 17:07 ivan
+
+ * FS/FS/cust_main.pm: transactional invoice_list setting yay!!!
+
+2001-08-11 17:06 ivan
+
+ * FS/FS/cust_main_invoice.pm: better error msgs
+
+2001-08-11 16:19 ivan
+
+ * httemplate/edit/part_svc.cgi: better error checking, fix scalar
+ context to $cgi->keywords
+
+2001-08-11 16:18 ivan
+
+ * httemplate/browse/part_svc.cgi: missed a variable interpolation
+
+2001-08-10 22:53 ivan
+
+ * bin/fs-setup, httemplate/docs/upgrade8.html: add comments field
+
+2001-08-10 22:52 ivan
+
+ * httemplate/: edit/cust_main.cgi, view/cust_main.cgi: add customer
+ comments fields
+
+2001-08-10 22:52 ivan
+
+ * FS/FS/cust_main.pm: add comments field, fix ship_ address
+ handling (don't consider a value for ship_state field to mean
+ something meaningful is in ship_*)
+
+2001-08-10 22:51 ivan
+
+ * FS/FS/part_svc.pm: better error msg
+
+2001-08-10 22:50 ivan
+
+ * FS/FS/Record.pm: allow newlines in ut_anything fields fix example
+ for ut_anything
+
+2001-08-10 21:55 ivan
+
+ * httemplate/edit/part_svc.cgi: don't have any other choices in
+ svcdb dropdown for existing services, since you can't change it
+ anyway
+
+2001-08-10 21:29 ivan
+
+ * FS/FS/CGI.pm: that's what $etc is for!
+
+2001-08-10 21:15 ivan
+
+ * httemplate/misc/delete-customer.cgi: better docs
+
+2001-08-10 17:01 ivan
+
+ * FS/FS/cust_main_invoice.pm: "fixes"
+
+ Can't call method "username" on an undefined value at
+ /usr/lib/perl5/site_perl/5.6.0/FS/cust_main_invoice.pm line 162.
+
+ but cust_main_invoice records should probably be updated when
+ svc_acct records are deleted :/
+
+2001-08-08 02:47 ivan
+
+ * httemplate/docs/passwd.html: doc clarification
+
+2001-08-07 21:44 ivan
+
+ * FS/FS/Record.pm: better error msg
+
+2001-08-03 13:34 jeff
+
+ * FS/FS/cust_bill.pm:
+
+ added the tax method
+
+2001-07-30 03:43 ivan
+
+ * FS/FS/CGI.pm: Apache::ASP eidiot fix
+
+2001-07-30 03:41 ivan
+
+ * FS/FS/Record.pm, FS/FS/cust_main.pm,
+ httemplate/search/cust_main.cgi, httemplate/view/cust_main.cgi:
+ shipping address additions
+
+2001-07-30 02:53 ivan
+
+ * httemplate/docs/upgrade8.html: Pg primary key upgrade fix (create
+ fix in new DBIx::DBSchema)
+
+2001-07-30 01:03 ivan
+
+ * httemplate/: .htaccess, docs/overview.dia, docs/overview.png:
+ forgotten files
+
+2001-07-30 01:02 ivan
+
+ * htetc/: global.asa, handler.pl: template stuffs
+
+2001-07-30 01:01 ivan
+
+ * Makefile: need this too!
+
+2001-07-30 00:42 ivan
+
+ * bin/fs-setup: need an DBIx::DBSchema with delcolumn
+
+2001-07-30 00:36 ivan
+
+ * httemplate/index.html, httemplate/browse/agent.cgi,
+ httemplate/browse/agent_type.cgi,
+ httemplate/browse/cust_main_county.cgi,
+ httemplate/browse/nas.cgi, httemplate/browse/part_pkg.cgi,
+ httemplate/browse/part_referral.cgi,
+ httemplate/browse/part_svc.cgi,
+ httemplate/browse/svc_acct_pop.cgi, httemplate/docs/admin.html,
+ httemplate/docs/billing.html, httemplate/docs/config.html,
+ httemplate/docs/export.html, httemplate/docs/index.html,
+ httemplate/docs/install.html, httemplate/docs/legacy.html,
+ httemplate/docs/passwd.html, httemplate/docs/postgresql.html,
+ httemplate/docs/schema.html, httemplate/docs/session.html,
+ httemplate/docs/signup.html, httemplate/docs/trouble.html,
+ httemplate/docs/upgrade.html, httemplate/docs/upgrade2.html,
+ httemplate/docs/upgrade3.html, httemplate/docs/upgrade4.html,
+ httemplate/docs/upgrade5.html, httemplate/docs/upgrade6.html,
+ httemplate/docs/upgrade7.html, httemplate/docs/upgrade8.html,
+ httemplate/docs/man/FS.html, httemplate/docs/man/FS/Bill.html,
+ httemplate/docs/man/FS/CGI.html,
+ httemplate/docs/man/FS/CGIwrapper.html,
+ httemplate/docs/man/FS/Conf.html,
+ httemplate/docs/man/FS/Invoice.html,
+ httemplate/docs/man/FS/Record.html,
+ httemplate/docs/man/FS/SessionClient.html,
+ httemplate/docs/man/FS/SignupClient.html,
+ httemplate/docs/man/FS/UID.html,
+ httemplate/docs/man/FS/agent.html,
+ httemplate/docs/man/FS/agent_type.html,
+ httemplate/docs/man/FS/cust_bill.html,
+ httemplate/docs/man/FS/cust_bill_pkg.html,
+ httemplate/docs/man/FS/cust_credit.html,
+ httemplate/docs/man/FS/cust_main.html,
+ httemplate/docs/man/FS/cust_main_county.html,
+ httemplate/docs/man/FS/cust_main_invoice.html,
+ httemplate/docs/man/FS/cust_pay.html,
+ httemplate/docs/man/FS/cust_pay_batch.html,
+ httemplate/docs/man/FS/cust_pkg.html,
+ httemplate/docs/man/FS/cust_refund.html,
+ httemplate/docs/man/FS/cust_svc.html,
+ httemplate/docs/man/FS/domain_record.html,
+ httemplate/docs/man/FS/nas.html,
+ httemplate/docs/man/FS/part_pkg.html,
+ httemplate/docs/man/FS/part_referral.html,
+ httemplate/docs/man/FS/part_svc.html,
+ httemplate/docs/man/FS/pkg_svc.html,
+ httemplate/docs/man/FS/port.html,
+ httemplate/docs/man/FS/prepay_credit.html,
+ httemplate/docs/man/FS/session.html,
+ httemplate/docs/man/FS/svc_Common.html,
+ httemplate/docs/man/FS/svc_acct.html,
+ httemplate/docs/man/FS/svc_acct_pop.html,
+ httemplate/docs/man/FS/svc_acct_sm.html,
+ httemplate/docs/man/FS/svc_domain.html,
+ httemplate/docs/man/FS/svc_www.html,
+ httemplate/docs/man/FS/type_pkgs.html, bin/fs-setup,
+ bin/masonize, bin/pod2x, httemplate/edit/agent.cgi,
+ httemplate/edit/agent_type.cgi, httemplate/edit/cust_credit.cgi,
+ httemplate/edit/cust_main.cgi,
+ httemplate/edit/cust_main_county-expand.cgi,
+ httemplate/edit/cust_main_county.cgi,
+ httemplate/edit/cust_pay.cgi, httemplate/edit/cust_pkg.cgi,
+ httemplate/edit/part_pkg.cgi, httemplate/edit/part_referral.cgi,
+ httemplate/edit/part_svc.cgi, httemplate/edit/svc_acct.cgi,
+ httemplate/edit/svc_acct_pop.cgi,
+ httemplate/edit/svc_acct_sm.cgi, httemplate/edit/svc_domain.cgi,
+ httemplate/edit/process/agent.cgi,
+ httemplate/edit/process/agent_type.cgi,
+ httemplate/edit/process/cust_credit.cgi,
+ httemplate/edit/process/cust_main.cgi,
+ httemplate/edit/process/cust_main_county-expand.cgi,
+ httemplate/edit/process/cust_main_county.cgi,
+ httemplate/edit/process/cust_pay.cgi,
+ httemplate/edit/process/cust_pkg.cgi,
+ httemplate/edit/process/part_pkg.cgi,
+ httemplate/edit/process/part_referral.cgi,
+ httemplate/edit/process/part_svc.cgi,
+ httemplate/edit/process/svc_acct.cgi,
+ httemplate/edit/process/svc_acct_pop.cgi,
+ httemplate/edit/process/svc_acct_sm.cgi,
+ httemplate/edit/process/svc_domain.cgi,
+ httemplate/images/mid-logo.png, httemplate/images/small-logo.png,
+ httemplate/misc/bill.cgi, httemplate/misc/cancel-unaudited.cgi,
+ httemplate/misc/cancel_pkg.cgi,
+ httemplate/misc/delete-customer.cgi,
+ httemplate/misc/expire_pkg.cgi, httemplate/misc/link.cgi,
+ httemplate/misc/print-invoice.cgi, httemplate/misc/susp_pkg.cgi,
+ httemplate/misc/unsusp_pkg.cgi,
+ httemplate/misc/process/delete-customer.cgi,
+ httemplate/misc/process/link.cgi,
+ httemplate/search/cust_bill.cgi,
+ httemplate/search/cust_bill.html,
+ httemplate/search/cust_main-payinfo.html,
+ httemplate/search/cust_main.cgi,
+ httemplate/search/cust_main.html, httemplate/search/cust_pkg.cgi,
+ httemplate/search/svc_acct.cgi, httemplate/search/svc_acct.html,
+ httemplate/search/svc_acct_sm.cgi,
+ httemplate/search/svc_acct_sm.html,
+ httemplate/search/svc_domain.cgi,
+ httemplate/search/svc_domain.html, httemplate/view/cust_bill.cgi,
+ httemplate/view/cust_main.cgi, httemplate/view/cust_pkg.cgi,
+ httemplate/view/svc_acct.cgi, httemplate/view/svc_acct_sm.cgi,
+ httemplate/view/svc_domain.cgi: templates!!!
+
+2001-07-30 00:33 ivan
+
+ * FS/FS/: Record.pm, svc_acct.pm: podnitfix
+
+2001-07-30 00:14 ivan
+
+ * INSTALL, README: templates!
+
+2001-07-30 00:13 ivan
+
+ * TODO: todocruft
+
+2001-07-29 23:28 ivan
+
+ * FS/FS/svc_acct.pm: allow !! as password for disabled accounts
+
+2001-07-29 23:07 ivan
+
+ * bin/: svc_acct.export, svc_acct.import: allow !! for locked
+ accounts instead of changing to *SUSPENDED*
+
+2001-07-27 10:16 ivan
+
+ * FS/FS/cust_main_invoice.pm: clarification
+
+2001-07-26 23:17 thalakan
+
+ * FS/FS/cust_main_invoice.pm: Documented some subtle behavior of
+ the checkdest method.
+
+2001-06-21 09:27 ivan
+
+ * FS/FS/UID.pm: better error message
+
+2001-06-20 01:33 ivan
+
+ * bin/svc_acct.export: > Use of uninitialized value in
+ concatenation (.) at svc_acct.export line
+ > 276.
+
+2001-06-06 15:22 ivan
+
+ * TODO: templating thoughts
+
+2001-06-05 14:40 ivan
+
+ * debian/: README.Debian, changelog, conffiles.ex, control,
+ copyright, cron.d.ex, dirs, docs, ex.doc-base.package,
+ freeside-doc.docs, freeside-doc.files, init.d.ex, manpage.1.ex,
+ manpage.sgml.ex, menu.ex, postinst.ex, postrm.ex, preinst.ex,
+ prerm.ex, rules, watch.ex: add deb packaging foo (doesn't work
+ yet)
+
+2001-06-03 10:22 ivan
+
+ * FS/FS/svc_domain.pm: SOA serial number problem with Date::Format
+ %e: no leading zero
+
+2001-06-03 07:16 ivan
+
+ * README: 1.3.1!!!
+
+2001-06-03 07:16 ivan
+
+ * bin/fs-setup: allow empty refund reasons
+
+2001-06-03 07:15 ivan
+
+ * TODO: yes yes
+
+2001-06-03 05:36 ivan
+
+ * FS/FS/svc_acct.pm: add username-letter and username-letterfirst
+ config files
+
+2001-06-03 04:37 ivan
+
+ * FS/FS/svc_acct.pm: fixes
+
+ Can't locate object method "setfield" via package "svc_acct_sm"
+ at /usr/local/lib/perl5/site_perl/5.005/FS/Record.pm line 318
+
+2001-06-03 04:27 ivan
+
+ * FS/FS/Record.pm: track down
+
+ Can't locate object method "setfield" via package "svc_acct_sm"
+ at
+ /usr/local/lib/perl5/site_perl/5.005/FS/Record.pm line 318.
+
+ errors
+
+2001-06-03 03:51 ivan
+
+ * FS/FS/cust_main.pm: fixes "Error reval-ing" and won't bill errors
+
+2001-05-30 08:17 ivan
+
+ * FS/FS/CGI.pm: use Apache inside an eval BLOCK was mucking things
+ up for the non-mod_perl folks
+
+2001-05-22 09:43 ivan
+
+ * FS/FS/svc_domain.pm: oops, s/mx/MX/ noticed by "Shane Chrisp"
+ <shane@2000cn.com.au>, thanks!
+
+2001-05-18 07:08 ivan
+
+ * FS/FS/domain_record.pm: tyop
+
+2001-05-15 00:52 ivan
+
+ * FS/bin/freeside-email: simple program to list all email addresses
+
+2001-05-08 03:44 ivan
+
+ * bin/: svc_acct.export, svc_acct_sm.export: fix for OO Net::SCP
+
+2001-05-07 08:42 ivan
+
+ * FS/FS/Record.pm: tyop
+
+2001-05-07 08:36 ivan
+
+ * FS/FS/Record.pm: start to track down
+
+ Insecure dependency in eval while running with -T switch at
+ /usr/local/lib/site_perl/FS/Record.pm line 202.
+
+ errors
+
+2001-05-07 08:24 ivan
+
+ * bin/svc_acct.import: s/!/*/
+
+2001-05-06 19:07 ivan
+
+ * FS/FS/cust_main.pm:
+ http://www.sisd.com/freeside/list-archive/msg01906.html
+
+2001-05-05 01:51 ivan
+
+ * bin/svc_acct.import:
+ http://www.sisd.com/freeside/list-archive/msg01915.html
+
+2001-04-23 12:50 ivan
+
+ * FS/FS/cust_credit.pm: the real
+
+ Can't locate object method "setfield" via package "cust_refund"
+ at /usr/local/lib/site_perl/FS/Record.pm line 315.
+
+ fix
+
+2001-04-23 12:27 ivan
+
+ * FS/FS/cust_credit.pm: *sigh*
+
+2001-04-23 12:21 ivan
+
+ * FS/FS/cust_credit.pm: webdemo bugfix
+
+2001-04-23 08:37 ivan
+
+ * FS/MANIFEST: 1.3.1 sigh
+
+2001-04-23 05:44 ivan
+
+ * bin/freeside-session-kill: session killer implemeting timed
+ access
+
+2001-04-23 05:40 ivan
+
+ * FS/FS.pm: documentation and webdemo updates
+
+2001-04-23 02:00 ivan
+
+ * FS/FS/UID.pm: "shouldn't hurt" (famous last words)
+
+2001-04-23 00:12 ivan
+
+ * FS/FS/cust_main.pm: better error message (if kludgy) for no
+ referral remove outdated NSI foo from domain ordering. also,
+ fuck NSI.
+
+2001-04-21 18:56 ivan
+
+ * README, FS/FS.pm, FS/MANIFEST, FS/FS/SSH.pm, FS/FS/svc_acct.pm,
+ FS/FS/svc_acct_sm.pm, FS/FS/svc_domain.pm, FS/FS/svc_www.pm,
+ bin/svc_acct.export, bin/svc_acct.import, bin/svc_acct_sm.export,
+ bin/svc_acct_sm.import, bin/svc_domain.import,
+ fs_passwd/fs_passwd_server: get rid of FS::SSH.pm (became
+ Net::SSH and Net::SCP on CPAN)
+
+2001-04-21 18:38 ivan
+
+ * TODO, FS/FS/svc_domain.pm: svc_domain needs to import dbh sub
+ from Record view/cust_main.cgi needs to use ->owed method, not
+ check (depriciated) owed field search/cust_bill.cgi redirect
+ error when there's only one invoice
+
+2001-04-21 17:49 ivan
+
+ * FS/FS/svc_Common.pm: need to import dbh sub from Record
+
+2001-04-21 16:53 ivan
+
+ * FS/MANIFEST: no more dbdef
+
+2001-04-15 06:56 ivan
+
+ * TODO: [no log message]
+
+2001-04-15 06:35 ivan
+
+ * FS/FS/: nas.pm, session.pm, svc_Common.pm, svc_domain.pm:
+ transactions part deux
+
+2001-04-15 05:56 ivan
+
+ * TODO, FS/FS/Record.pm, FS/FS/dbdef.pm, FS/FS/dbdef_colgroup.pm,
+ FS/FS/dbdef_column.pm, FS/FS/dbdef_index.pm,
+ FS/FS/dbdef_table.pm, FS/FS/dbdef_unique.pm, bin/dbdef-create,
+ bin/fs-setup: s/dbdef/DBIx::DBSchema/
+
+2001-04-15 03:33 ivan
+
+ * CREDITS, TODO: better docs for the worst bit, finally
+
+2001-04-15 02:36 ivan
+
+ * bin/fs-setup:
+ http://www.sisd.com/freeside/list-archive/msg01450.html
+
+2001-04-09 16:05 ivan
+
+ * TODO, FS/FS/cust_bill.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_main.pm, FS/FS/cust_pay.pm, FS/FS/cust_pkg.pm,
+ FS/FS/cust_refund.pm, FS/FS/session.pm, FS/FS/svc_Common.pm,
+ bin/fs-setup: Transactions Part I!!!
+
+2001-04-09 08:50 ivan
+
+ * CREDITS, TODO, FS/MANIFEST: rar
+
+2001-03-30 09:33 ivan
+
+ * FS/FS/cust_bill.pm: config value money_char:
+
+ Presto! Now you can consider all numbers to be
+ shillings.
+
+ If you need to change the currency symbol, you can hunt
+ down the
+ instances of literal '$' in FS::cust_bill::print_text and
+ replace them
+ with your currency symbol. I guess this should turn into
+ a
+ configuration value.
+
+2001-02-26 16:59 ivan
+
+ * FS/FS/session.pm: silly typo, fix sent by Mack <mackn@mackn.net>,
+ thanks!
+
+2001-02-21 15:48 ivan
+
+ * bin/svc_acct.export: add icradius_secrets config file to export
+ to a non-Freeside MySQL database for ICRADIUS
+
+2001-02-20 17:48 ivan
+
+ * FS/bin/freeside-print-batch: stupid pod errors
+
+2001-02-20 17:46 ivan
+
+ * FS/FS/: Bill.pm, Invoice.pm: cruft
+
+2001-02-20 17:45 ivan
+
+ * FS/FS/nas.pm: pod tyop
+
+2001-02-20 17:45 ivan
+
+ * FS/FS/UID.pm: DBI autocommit needs 0, not true string 'false',
+ duh
+
+2001-02-20 08:31 ivan
+
+ * FS/FS/Record.pm: don't use prepare_cached for now
+
+2001-02-13 21:18 ivan
+
+ * README: add devel mailing list
+
+2001-02-13 20:33 ivan
+
+ * FS/FS/port.pm: get rid of gratuitous HISTORY
+
+2001-02-11 09:34 ivan
+
+ * FS/FS/cust_bill_pkg.pm: more doc updates from jason
+
+2001-02-11 09:17 ivan
+
+ * CREDITS, FS/FS/cust_bill.pm, FS/FS/cust_credit.pm,
+ FS/FS/cust_pay.pm, FS/FS/cust_refund.pm: documentation updates
+ from jason
+
+2001-02-03 06:03 ivan
+
+ * FS/FS/Record.pm, FS/FS/UID.pm, FS/FS/cust_main.pm, bin/fs-setup,
+ bin/generate-prepay: time-based prepaid cards, session monitor.
+ woop!
+
+2001-01-30 23:21 ivan
+
+ * TODO, FS/FS/cust_main.pm, FS/FS/svc_acct.pm: fix tyops
+
+2001-01-30 01:08 ivan
+
+ * FS/FS/port.pm: tyop, thanks to Mack Nagashima <mackn@moaner.org>
+
+2001-01-22 21:03 ivan
+
+ * FS/FS/cust_main.pm: harmless tyop
+
+2000-12-26 15:51 ivan
+
+ * CREDITS, TODO: statedefault & referraldefault config files
+
+2000-12-10 17:30 ivan
+
+ * TODO: more ancient todo stuff from my inbox
+
+2000-12-10 17:10 ivan
+
+ * TODO: ancient stuff out of my inbox
+
+2000-12-08 14:22 ivan
+
+ * FS/FS/session.pm: session callbacks
+
+2000-12-06 02:21 ivan
+
+ * FS/FS/Record.pm: DESTROY sub
+
+2000-12-03 16:13 ivan
+
+ * bin/fs-setup: fix nas.last type
+
+2000-12-03 12:25 ivan
+
+ * TODO, FS/FS/Record.pm, FS/FS/nas.pm, FS/FS/port.pm,
+ FS/FS/session.pm: session monitor updates
+
+2000-12-03 07:14 ivan
+
+ * CREDITS, FS/FS/cust_bill.pm, bin/svc_acct_sm.import: bugfixes
+ from Jeff Finucane <jeff@cmh.net>, thanks!
+
+2000-12-03 05:45 ivan
+
+ * FS/FS/agent.pm: patch from Jason Spence <thalakan@frys.com>:
+ admin.html doc, autocapgen
+
+2000-12-03 05:44 ivan
+
+ * FS/FS/port.pm: beginnings of web status for session monitor
+
+2000-12-03 02:09 ivan
+
+ * FS/: MANIFEST, FS/CGIwrapper.pm: bad caches!
+
+2000-12-01 10:34 ivan
+
+ * bin/fs-setup: another tyop
+
+2000-12-01 10:33 ivan
+
+ * bin/fs-setup: tyop
+
+2000-11-22 15:30 ivan
+
+ * FS/FS/svc_www.pm: tyop
+
+2000-11-07 07:00 ivan
+
+ * FS/MANIFEST, FS/FS/Record.pm, FS/FS/nas.pm, FS/FS/session.pm,
+ bin/fs-setup: session monitor
+
+2000-10-30 02:47 ivan
+
+ * bin/fs-setup: nas.last can't be defined NULL if indexed
+
+2000-10-27 13:18 ivan
+
+ * FS/FS/: nas.pm, port.pm, session.pm: oops, also necessary for
+ session monitor
+
+2000-10-27 13:15 ivan
+
+ * TODO, FS/FS/Record.pm, bin/fs-setup, eg/table_template.pm:
+ session monitor
+
+2000-10-15 05:58 ivan
+
+ * TODO: roo
+
+2000-10-11 17:44 ivan
+
+ * README: rawr!
+
+2000-09-20 03:35 ivan
+
+ * FS/FS/cust_bill.pm: since printed field isn't updated 'till after
+ print_text method is called, want to print overdue invoices if
+ printed > 0, not > 1
+
+2000-08-24 00:26 ivan
+
+ * TODO: untaint template source
+
+2000-08-09 11:40 ivan
+
+ * conf/invoice_template: example invoice template
+
+2000-08-09 04:30 ivan
+
+ * TODO, FS/FS/cust_bill.pm: templatable invoices
+
+2000-07-17 06:51 ivan
+
+ * FS/FS/svc_acct.pm: silly mistake
+
+2000-07-17 03:53 ivan
+
+ * FS/FS/svc_acct.pm: prevent accounts which are the target of mail
+ aliases from being deleted
+
+2000-07-17 03:37 ivan
+
+ * FS/FS/svc_acct.pm: make remote commands configurable
+
+2000-07-17 03:36 ivan
+
+ * CREDITS: rawr!
+
+2000-07-06 21:05 ivan
+
+ * fs_passwd/fs_passwd_server: wait()ing on SIGCHLD causing hangs
+ for some folks
+
+2000-07-06 06:56 ivan
+
+ * FS/FS/svc_acct.pm: mis-PODed =back should have been a =cut in
+ conjunction with AUTOLOAD this was sure a pain to find
+
+2000-07-06 06:23 ivan
+
+ * bin/svc_acct.export: tyop
+
+2000-07-06 06:19 ivan
+
+ * bin/: fs-radius-add-check, fs-radius-add-reply: remove duplicate
+ sql statement causing spurious errors
+
+2000-07-06 01:57 ivan
+
+ * TODO, FS/FS/svc_acct.pm, bin/fs-radius-add,
+ bin/fs-radius-add-check, bin/fs-radius-add-reply, bin/fs-setup,
+ bin/svc_acct.export: support for radius check attributes (except
+ importing). poorly documented.
+
+2000-07-05 20:37 ivan
+
+ * bin/svc_acct_sm.export: don't error out on invalid
+ svc_acct_sm.domuid's that can't be matched in svc_acct.uid - just
+ warn.
+
+2000-07-04 06:42 ivan
+
+ * FS/FS/svc_acct.pm: noted a API inconsistancy
+
+2000-07-03 02:13 ivan
+
+ * bin/svc_acct_sm.export: get rid of double sendmailrestart
+ invocation; no need for multiple sessions
+
+2000-07-03 02:09 ivan
+
+ * bin/svc_acct_sm.export: typo
+
+2000-07-03 02:03 ivan
+
+ * TODO, bin/svc_acct_sm.export: added sendmailrestart and
+ sendmailconfigpath config files
+
+2000-06-30 03:37 ivan
+
+ * FS/FS/svc_acct_sm.pm: maildisablecatchall configuration file
+
+2000-06-29 08:01 ivan
+
+ * bin/svc_acct.export: another silly typo in svc_acct.export
+
+2000-06-29 07:02 ivan
+
+ * bin/svc_acct_sm.export: add sendmailrestart configuration file
+
+2000-06-29 05:27 ivan
+
+ * bin/svc_acct.import: s/password/_password/ for PostgreSQL wasn't
+ done in the import.
+
+2000-06-29 05:00 ivan
+
+ * bin/fs-setup: support for pre-encrypted md5 passwords.
+
+2000-06-29 04:56 ivan
+
+ * FS/FS/svc_acct.pm: md5 passwords can are 34 characters long and
+ have $ in them.
+
+2000-06-29 04:12 ivan
+
+ * FS/FS/svc_domain.pm: don't block on $whois_hack trueness when
+ adding new domains.
+
+2000-06-29 03:51 ivan
+
+ * bin/svc_acct_sm.import: oops, silly mistake
+
+2000-06-29 03:48 ivan
+
+ * bin/svc_acct_sm.import: make svc_acct_sm skip blank lines in
+ sendmail import
+
+2000-06-28 05:54 ivan
+
+ * FS/FS/svc_acct.pm: superfluous my()
+
+2000-06-28 05:52 ivan
+
+ * FS/FS/svc_acct.pm: bugfix to accept shells that evaluate to false
+ in perl, like the empty string.
+
+2000-06-28 05:37 ivan
+
+ * bin/svc_acct.export: add support for config option
+ textradiusprepend
+
+2000-06-28 05:32 ivan
+
+ * bin/svc_acct.import: allow RADIUS lines with "Auth-Type = Local"
+ too
+
+2000-06-28 05:03 ivan
+
+ * bin/svc_acct.import: make svc_acct more forgiving about RADIUS
+ users files
+
+2000-06-28 03:51 ivan
+
+ * bin/fs-radius-add: forgot to import a sub
+
+2000-06-28 03:48 ivan
+
+ * bin/fs-radius-add: quick hack to add RADIUS attributes
+
+2000-06-27 05:15 ivan
+
+ * TODO: i18n
+
+2000-06-27 05:15 ivan
+
+ * FS/FS/: Record.pm, cust_main.pm: i18
+
+2000-06-27 04:29 ivan
+
+ * FS/FS/Record.pm: fix typo in last patch, and another gratuitous
+ -w pleaser
+
+2000-06-27 04:27 ivan
+
+ * FS/FS/Record.pm: logically identical, but -w safe
+
+2000-06-23 17:28 ivan
+
+ * FS/: FS/cust_main.pm, bin/freeside-bill: don't use Date::Manip;
+ report correct program name in freeside-bill usage msg
+
+2000-06-23 05:25 ivan
+
+ * CREDITS, TODO, FS/FS/Record.pm, FS/FS/UID.pm: FS::Record::qsearch
+ - more portable, doesn't depend on $sth->execute returning a
+ number of rows, uses placeholders and prepare_cached
+
+2000-06-22 03:52 ivan
+
+ * FS/bin/freeside-bill: tyop
+
+2000-06-20 00:13 ivan
+
+ * FS/FS/cust_main_invoice.pm: documentation update
+
+2000-06-17 14:48 ivan
+
+ * FS/FS/cust_pay_batch.pm: fix typo in error message
+
+2000-06-15 07:45 ivan
+
+ * CREDITS, TODO: text updates
+
+2000-06-15 07:07 ivan
+
+ * bin/svc_acct.export: added ICRADIUS radreply table support,
+ courtesy of Kenny Elliott
+
+2000-06-15 06:35 ivan
+
+ * FS/FS/svc_acct.pm: add radius method
+
+2000-06-15 05:38 ivan
+
+ * FS/FS/cust_main.pm: fix for ncancelled_pkgs - when called in
+ scalar context, was only returning second item
+
+2000-06-12 01:37 ivan
+
+ * bin/svc_acct_sm.export: sendmail fix from Jeff Finucane
+
+2000-05-13 14:57 ivan
+
+ * TODO, FS/bin/freeside-print-batch: add print_batch script from
+ Joel Griffiths
+
+2000-05-13 14:50 ivan
+
+ * FS/FS/: CGI.pm, UID.pm: cgisuidsetup takes an Apache object as
+ well as a CGI object now.
+
+2000-05-13 14:42 ivan
+
+ * CREDITS: add kristian, update joel's entry
+
+2000-04-10 17:06 ivan
+
+ * FS/FS/CGI.pm: CGI.pm detects mod_perl and calls appropriate exit
+ (Registry's override doesn't work here)
+
+2000-04-02 19:32 ivan
+
+ * FS/bin/freeside-bill: accept anything in ARGV for -d Date::Parse
+
+2000-03-06 08:38 ivan
+
+ * FS/FS/svc_acct.pm: better error message. bah.
+
+2000-03-06 08:00 ivan
+
+ * bin/svc_acct.export: sync up with working versoin
+
+2000-03-06 07:59 ivan
+
+ * bin/svc_acct.export: finally get MySQL locking working for
+ ICRADIUS export
+
+2000-03-06 07:15 ivan
+
+ * FS/FS/SSH.pm: backout silly change
+
+2000-03-06 07:04 ivan
+
+ * FS/FS/SSH.pm: bug in IPC::Open3 documentation?
+
+2000-03-06 06:59 ivan
+
+ * bin/svc_acct.export: s/sshopen2/sshopen3/ to prevent spurious
+ mysql "Enter password: " dialog from showing up in cron/terminal
+
+2000-03-06 06:51 ivan
+
+ * bin/svc_acct.export: eek
+
+2000-03-06 06:50 ivan
+
+ * bin/svc_acct.export: oop
+
+2000-03-06 06:48 ivan
+
+ * bin/svc_acct.export: s/icradiusmachine/machine/
+
+2000-03-06 06:46 ivan
+
+ * bin/svc_acct.export: not setuid or run by malicious user - no -T
+ necessary
+
+2000-03-06 06:19 ivan
+
+ * bin/svc_acct.export: ICRADIUS export bugfix
+
+2000-03-06 06:12 ivan
+
+ * TODO, bin/svc_acct.export: ICRADIUS export support
+
+2000-03-03 10:45 ivan
+
+ * FS/FS/CGI.pm: use Apache::exit instead of exit in &eidiot -
+ Registry wasn't overriding exit in modules
+
+2000-03-03 10:21 ivan
+
+ * FS/FS/Record.pm: changes backported from 1.2.3 release, bugfix
+ from web demo
+
+2000-03-01 23:44 ivan
+
+ * bin/fs-setup: typo forgot closing '
+
+2000-03-01 00:13 ivan
+
+ * FS/: MANIFEST, FS/svc_domain.pm, FS/svc_www.pm: compilation
+ bugfixes
+
+2000-02-02 21:16 ivan
+
+ * FS/FS/cust_pkg.pm, FS/FS/domain_record.pm, FS/FS/svc_domain.pm,
+ FS/FS/svc_www.pm, bin/fs-setup, bin/svc_acct.import,
+ bin/svc_acct_sm.import, bin/svc_domain.import: beginning of DNS
+ and Apache support
+
+2000-02-02 12:22 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/prepay_credit.pm, bin/generate-prepay:
+ bugfix prepayment in signup server
+
+2000-01-30 21:22 ivan
+
+ * FS/FS/cust_main.pm, FS/FS/prepay_credit.pm, bin/fs-setup: prepaid
+ "internet cards"
+
+2000-01-30 00:18 ivan
+
+ * CREDITS: [no log message]
+
+2000-01-29 22:11 ivan
+
+ * TODO: [no log message]
+
+2000-01-29 22:03 ivan
+
+ * bin/fs-setup: postgres 6.5 finally supports decimal(10,2)
+
+2000-01-29 13:10 ivan
+
+ * FS/FS/svc_domain.pm: doc update
+
+2000-01-28 14:53 ivan
+
+ * bin/fs-setup, FS/FS/svc_acct_pop.pm: track full phone number
+
+2000-01-26 16:37 ivan
+
+ * README: ack
+
+2000-01-26 16:27 ivan
+
+ * TODO: update TODO
+
+1999-11-08 13:38 ivan
+
+ * FS/FS/cust_pkg.pm: remove services using pkg_svc table now, oops!
+
+1999-10-04 01:23 ivan
+
+ * FS/bin/freeside-bill: silly 'use of unitialized value' errors
+
+1999-09-22 15:06 ivan
+
+ * TODO: ya todo update
+
+1999-08-26 23:25 ivan
+
+ * TODO: [no log message]
+
+1999-08-23 05:26 ivan
+
+ * test/cgi-test: need to untaint the command line
+
+1999-08-20 01:27 ivan
+
+ * README, FS/FS/part_pkg.pm: fix for bug noticed by Martin G.
+ Bayerle:
+ > if you eliminate services from any package, to reduce it to
+ only one service,
+ > once gone, they won't reappear.
+
+1999-08-11 21:16 ivan
+
+ * FS/FS/cust_main.pm: hidecancelledpackages config option
+
+1999-08-11 17:05 ivan
+
+ * FS/FS/svc_acct.pm: configurable min/max username length, min
+ password length, periods in usernames
+
+1999-08-11 13:51 ivan
+
+ * FS/bin/freeside-bill: [no log message]
+
+1999-08-11 13:41 ivan
+
+ * FS/MANIFEST, FS/Makefile.PL, FS/FS/svc_domain.pm,
+ FS/bin/freeside-bill, bin/bill: new bill script,
+
+1999-08-11 07:42 ivan
+
+ * bin/backup-freeside: [no log message]
+
+1999-08-11 06:12 ivan
+
+ * FS/FS/svc_domain.pm: require a working Net::Whois version
+
+1999-08-10 05:06 ivan
+
+ * fs_passwd/fs_passwdd: even though you should probably set this
+ for your installation
+
+1999-08-04 05:42 ivan
+
+ * bin/pod2x: new, kludgy-but-working html generator
+
+1999-08-04 05:41 ivan
+
+ * FS/FS.pm: pod fix
+
+1999-08-04 04:50 ivan
+
+ * FS/FS/cust_pkg.pm: pod syntax
+
+1999-08-04 03:41 ivan
+
+ * FS/FS/Record.pm: some pod syntax update to generate nicer html
+ docs
+
+1999-08-04 02:03 ivan
+
+ * FS/: MANIFEST, MANIFEST.SKIP, README, FS/Bill.pm, FS/CGI.pm,
+ FS/Conf.pm, FS/Invoice.pm, FS/Record.pm, FS/SSH.pm, FS/UID.pm,
+ FS/agent.pm, FS/agent_type.pm, FS/cust_bill.pm,
+ FS/cust_bill_pkg.pm, FS/cust_credit.pm, FS/cust_main.pm,
+ FS/cust_main_county.pm, FS/cust_main_invoice.pm, FS/cust_pay.pm,
+ FS/cust_pay_batch.pm, FS/cust_pkg.pm, FS/cust_refund.pm,
+ FS/cust_svc.pm, FS/dbdef.pm, FS/dbdef_colgroup.pm,
+ FS/dbdef_column.pm, FS/dbdef_index.pm, FS/dbdef_table.pm,
+ FS/dbdef_unique.pm, FS/part_pkg.pm, FS/part_referral.pm,
+ FS/part_svc.pm, FS/pkg_svc.pm, FS/svc_Common.pm, FS/svc_acct.pm,
+ FS/svc_acct_pop.pm, FS/svc_acct_sm.pm, FS/svc_domain.pm,
+ FS/type_pkgs.pm, FS/UI/Base.pm, FS/UI/CGI.pm, FS/UI/Gtk.pm,
+ FS/UI/agent.pm: initial checkin of module files for proper perl
+ installation
+
+1999-08-04 01:03 ivan
+
+ * eg/: table_template-svc.pm, table_template.pm: move table
+ subclass examples out of production directory
+
+1999-08-04 00:34 ivan
+
+ * FS/FS.pm: initial FS manpage
+
+1999-08-03 21:15 ivan
+
+ * FS/: Changes, FS.pm, MANIFEST, Makefile.PL, test.pl: initial h2xs
+
+1999-08-03 00:43 ivan
+
+ * TODO: use Net::Whois;
+
+1999-07-29 12:13 ivan
+
+ * TODO: [no log message]
+
+1999-07-29 01:50 ivan
+
+ * bin/fs-setup: wrong type for cust_pay_batch.exp
+
+1999-07-15 17:20 ivan
+
+ * TODO: [no log message]
+
+1999-07-15 16:50 ivan
+
+ * TODO: [no log message]
+
+1999-07-08 04:40 ivan
+
+ * CREDITS, README, TODO: [no log message]
+
+1999-07-07 19:32 ivan
+
+ * bin/svc_acct.import: import fix, noticed by Ben Leibig and Joel
+ Griffiths
+
+1999-07-07 18:49 ivan
+
+ * CREDITS: add joel
+
+1999-07-07 18:49 ivan
+
+ * bin/svc_acct.import: updates to avoid -w warnings from Joel
+ Griffiths <griff@aver-computer.com>
+
+1999-07-07 18:02 ivan
+
+ * TODO: [no log message]
+
+1999-04-29 02:37 ivan
+
+ * TODO: [no log message]
+
+1999-04-19 03:32 ivan
+
+ * etc/megapop.pl: if you are a megapop customer...
+
+1999-04-15 15:46 ivan
+
+ * bin/fs-setup: TT isn't a state!
+
+1999-04-14 06:14 ivan
+
+ * TODO: configuration option to edit referrals of existing
+ customers
+
+1999-04-14 04:27 ivan
+
+ * TODO: showpasswords config option to show passwords
+
+1999-04-14 01:58 ivan
+
+ * TODO: [no log message]
+
+1999-04-14 00:58 ivan
+
+ * bin/fs-setup: export getsecrets from FS::UID instead of calling
+ it explicitly
+
+1999-04-08 20:52 ivan
+
+ * TODO: [no log message]
+
+1999-04-08 06:21 ivan
+
+ * CREDITS, conf/address: [no log message]
+
+1999-04-08 06:11 ivan
+
+ * README: 1.2.0
+
+1999-04-08 06:05 ivan
+
+ * test/cgi-test: web interface tester / sample data creator
+
+1999-04-08 06:03 ivan
+
+ * TODO: [no log message]
+
+1999-03-26 05:15 ivan
+
+ * eg/TEMPLATE_cust_main.import: s/create/new/, use all necessary
+ FS::table_names to avoid warnings
+
+1999-03-26 05:00 ivan
+
+ * fs_passwd/fs_passwd_server: s/create/new/
+
+1999-03-25 00:42 ivan
+
+ * bin/: svc_acct.import, svc_acct_sm.import: import stuff uses
+ Term::Query and spits out (some kinds of) nonsensical input
+
+1999-03-23 16:51 ivan
+
+ * bin/svc_acct_sm.import: die if no relevant services... cvspain
+
+1999-03-23 16:43 ivan
+
+ * bin/svc_acct.import: die if no relevant services
+
+1999-02-28 11:44 ivan
+
+ * bin/fs-setup: constructors s/create/new/ pointed out by "Bao C.
+ Ha" <bao@hacom.net>
+
+1999-02-27 13:06 ivan
+
+ * bin/fs-setup: cust_main.paydate should be varchar(10), not
+ @date_type ; problem reported by Ben Leibig <leibig@colorado.edu>
+
+1999-02-23 00:09 ivan
+
+ * TODO: beginnings of one-screen new customer entry and some other
+ miscellania
+
+1999-02-10 01:02 ivan
+
+ * etc/sql-reserved-words.txt: some new doc files
+
+1999-02-10 00:27 ivan
+
+ * TODO: [no log message]
+
+1999-02-09 01:56 ivan
+
+ * TODO: [no log message]
+
+1999-02-07 01:59 ivan
+
+ * CREDITS, TODO, bin/fs-setup: more mod_perl fixes, and bugfixes
+ Peter Wemm sent via email
+
+1999-02-06 14:43 ivan
+
+ * CREDITS, TODO: don't use [e]idiot; display error messages on the
+ form page
+
+1999-02-03 22:09 ivan
+
+ * bin/fs-setup: add AU provences
+
+1999-02-03 02:42 ivan
+
+ * bin/fs-setup: [no log message]
+
+1999-01-25 04:09 ivan
+
+ * TODO: yet more mod_perl stuff
+
+1999-01-18 15:05 ivan
+
+ * TODO: update TODO for stuff that was done etc.
+
+1999-01-16 19:11 ivan
+
+ * bin/fs-setup: remove preliminary completehost changes
+
+1999-01-15 16:24 ivan
+
+ * CREDITS: [no log message]
+
+1999-01-08 21:38 ivan
+
+ * CREDITS: [no log message]
+
+1998-12-30 15:03 ivan
+
+ * TODO: bugfixes; fields isn't exported by derived classes
+
+1998-12-29 03:59 ivan
+
+ * TODO: mostly properly OO, some work still to be done with svc_
+ stuff
+
+1998-12-15 22:05 ivan
+
+ * bin/fs-setup: add table cust_main_invoice
+
+1998-12-15 21:38 ivan
+
+ * TODO: [no log message]
+
+1998-12-15 21:32 ivan
+
+ * fs_passwd/fs_passwd_server: adminsuidsetup now requires user
+
+1998-12-15 21:29 ivan
+
+ * eg/TEMPLATE_cust_main.import: adminsuidsetup now need user
+
+1998-12-14 20:36 ivan
+
+ * bin/fs-setup: s/croak/die/; #oops
+
+1998-12-14 20:33 ivan
+
+ * bin/fs-setup: dies if it isn't running as the freeside user
+
+1998-12-09 23:23 ivan
+
+ * bin/: svc_acct.export, svc_acct.import, svc_acct_sm.export,
+ svc_acct_sm.import: use FS::Conf, need user (for datasrc)
+
+1998-12-01 17:23 ivan
+
+ * TODO, CREDITS: [no log message]
+
+1998-11-19 03:17 ivan
+
+ * bin/dbdef-create: adminsuidsetup requires argument
+
+1998-11-18 01:01 ivan
+
+ * CREDITS, TODO, bin/fs-setup: i18n! i18n!
+
+1998-11-15 05:18 ivan
+
+ * bin/fs-setup: remove debugging
+
+1998-11-15 01:43 ivan
+
+ * bin/fs-setup: update for new config file syntax, new
+ adminsuidsetup
+
+1998-11-14 18:53 ivan
+
+ * bin/bill: afterthought
+
+1998-11-14 18:51 ivan
+
+ * bin/bill: adminsuidsetup needs user, pod, cleanup
+
+1998-11-13 01:56 ivan
+
+ * TODO: change configuration file layout to support multiple
+ distinct databases (with own set of config files, export, etc.)
+
+1998-11-07 17:09 ivan
+
+ * README: 1.1.6 release
+
+1998-11-07 02:24 ivan
+
+ * README: don't use depriciated FS::Bill and FS::Invoice, other
+ miscellania
+
+1998-11-07 00:25 ivan
+
+ * TODO: [no log message]
+
+1998-11-07 00:21 ivan
+
+ * bin/bill: missing use
+
+1998-11-07 00:19 ivan
+
+ * bin/bill:
+ still need to bless into FS::cust_main (for now)
+
+1998-11-07 00:08 ivan
+
+ * bin/bill:
+ Removed depriciated FS::Bill (now in FS::cust_main)
+
+1998-10-22 08:51 ivan
+
+ * bin/fs-setup: also varchar with no length specified - postgresql
+ fix broke mysql.
+
+1998-10-22 08:46 ivan
+
+ * bin/fs-setup: now smallint is illegal, so remove that too.
+
+1998-10-14 00:05 ivan
+
+ * README, bin/fs-setup: 1.1.4 release, fix postgresql
+
+1998-10-13 05:49 ivan
+
+ * TODO: [no log message]
+
+1998-10-13 05:07 ivan
+
+ * TODO, bin/svc_acct.import: Assigns password from the shadow file
+ for RADIUS password "UNIX"
+
+1998-10-13 02:17 ivan
+
+ * TODO: [no log message]
+
+1998-10-12 14:22 ivan
+
+ * TODO: [no log message]
+
+1998-10-12 14:15 ivan
+
+ * TODO: [no log message]
+
+1998-10-12 02:12 ivan
+
+ * TODO: remove adding cvs from the todo
+
+1998-10-12 02:09 ivan
+
+ * TODO: [no log message]
+
+1998-10-12 02:09 ivan
+
+ * TODO: Id to Header
+
+1998-10-12 02:07 ivan
+
+ * TODO: Test keyword substitution
+
+1998-10-12 01:59 ivan
+
+ * TODO: Commiting sample change.
+
+1998-10-12 00:15 ivan
+
+ * conf/shells: Initial revision
+
+1998-10-12 00:12 ivan
+
+ * conf/: address, home, lpr, secrets, smtpmachine, domain: Initial
+ revision
+
+1998-10-12 00:08 ivan
+
+ * TODO: Initial revision
+
+1998-10-12 00:03 ivan
+
+ * bin/fs-setup: Initial revision
+
+1998-10-11 23:56 ivan
+
+ * README: Initial revision
+
+1998-09-25 01:52 ivan
+
+ * bin/pod2x: Initial revision
+
+1998-09-17 22:43 ivan
+
+ * bin/svc_acct.export: Initial revision
+
+1998-09-02 21:50 ivan
+
+ * CREDITS: Initial revision
+
+1998-08-23 20:12 ivan
+
+ * fs_passwd/fs_passwd_server: Initial revision
+
+1998-08-23 20:04 ivan
+
+ * fs_passwd/fs_passwd: Initial revision
+
+1998-08-23 19:01 ivan
+
+ * bin/bill: Initial revision
+
+1998-08-19 21:42 ivan
+
+ * eg/TEMPLATE_cust_main.import: Initial revision
+
+1998-08-16 14:02 ivan
+
+ * bin/svc_acct.import: Initial revision
+
+1998-08-14 15:11 ivan
+
+ * bin/svc_acct_sm.export: Initial revision
+
+1998-08-12 20:55 ivan
+
+ * Artistic: Initial revision
+
+1998-07-18 00:11 ivan
+
+ * etc/domain-template.txt: Initial revision
+
+1998-07-17 00:43 ivan
+
+ * bin/svc_acct_sm.import: Initial revision
+
+1998-06-03 00:22 ivan
+
+ * bin/dbdef-create: Initial revision
+
+1998-03-23 00:20 ivan
+
+ * fs_passwd/fs_passwdd: Initial revision
+
+1998-03-22 23:46 ivan
+
+ * GPL, INSTALL, etc/acp_logfile-parse, etc/countries.txt,
+ etc/example-direct-cardin: Initial revision
+
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..abeb140
--- /dev/null
+++ b/FS/FS.pm
@@ -0,0 +1,494 @@
+package FS;
+
+use strict;
+use vars qw($VERSION);
+
+$VERSION = '%%%VERSION%%%';
+
+#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::Schema> - Freeside database schema
+
+L<FS::Setup> - Setup subroutines
+
+L<FS::Upgrade> - Upgrade subroutines
+
+L<FS::Conf> - Freeside configuration values
+
+L<FS::ConfItem> - Freeside configuration option meta-data.
+
+L<FS::ConfDefaults> - Freeside configuration default and available values
+
+L<FS::UID> - User class (not yet OO)
+
+L<FS::CurrentUser> - Package representing the current user
+
+L<FS::CGI> - Non OO-subroutines for the web interface.
+
+L<FS::Msgcat> - Message catalog
+
+L<FS::SearchCache> - Search cache
+
+L<FS::AccessRight> - Access control rights.
+
+L<FS::Report> - Report data objects
+
+L<FS::Report::Table> - Report data objects
+
+L<FS::Report::Table::Monthly> - Report data objects
+
+L<FS::XMLRPC> - Backend XML::RPC server
+
+L<FS::Misc> - Miscellaneous subroutines
+
+L<FS::payby> - Payment types
+
+L<FS::ClientAPI_SessionCache> - ClientAPI session cache
+
+L<FS::Pony> - A pony
+
+L<FS::cust_main::Import> - Batch customer importing
+
+=head2 Database record classes
+
+L<FS::Record> - Database record base class
+
+L<FS::m2m_Common> - Mixin class for classes in a many-to-many relationship
+
+L<FS::m2name_Common> - Base class for tables with a related table listing names
+
+L<FS::option_Common> - Base class for option sub-classes
+
+L<FS::class_Common> - Base class for classification classes
+
+L<FS::category_Common> - Base class for category (grooups of classifications) classes
+
+L<FS::conf> - Configuration value class
+
+L<FS::payinfo_Mixin> - Mixin class for records in tables that contain payinfo.
+
+L<FS::access_user> - Employees / internal users
+
+L<FS::access_user_pref> - Employee preferences
+
+L<FS::access_group> - Employee groups
+
+L<FS::access_usergroup> - Employee group membership
+
+L<FS::access_groupagent> - Group reseller access
+
+L<FS::access_right> - Access rights
+
+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::pkg_referral> - Package referral class
+
+L<FS::cust_main_county> - Locale (tax rate) class
+
+L<FS::cust_tax_exempt> - Tax exemption record class
+
+L<FS::cust_tax_adjustment> - Tax adjustment record class
+
+L<FS::cust_tax_exempt_pkg> - Line-item specific tax exemption record class
+
+L<FS::svc_Common> - Service base class
+
+L<FS::svc_Parent_Mixin> - Mixin class for svc_ classes with a parent_svcnum field
+
+L<FS::svc_acct> - Account (shell, RADIUS, POP3) class
+
+L<FS::acct_snarf> - External mail account class
+
+L<FS::acct_rt_transaction> - Time worked application to account class
+
+L<FS::radius_usergroup> - RADIUS groups
+
+L<FS::svc_domain> - Domain class
+
+L<FS::domain_record> - DNS zone entries
+
+L<FS::registrar> - Domain registrar class
+
+L<FS::cgp_rule> - Communigate pro rule class
+
+L<FS::cgp_rule_condition> - Communigate pro rule condition class
+
+L<FS::cgp_rule_action> - Communigate pro rule action class
+
+L<FS::svc_forward> - Mail forwarding class
+
+L<FS::svc_mailinglist> - (Customer) Mailing list class
+
+L<FS::mailinglist> - Mailing list class
+
+L<FS::mailinglistmember> - Mailing list member class
+
+L<FS::svc_www> - Web virtual host class.
+
+L<FS::svc_broadband> - DSL, wireless and other broadband class.
+
+L<FS::addr_block> - Address block class
+
+L<FS::router> - Router class
+
+L<FS::part_virtual_field> - Broadband virtual field class
+
+L<FS::svc_phone> - Phone service class
+
+L<FS::phone_device> - Phone device class
+
+L<FS::part_device> - Device definition class
+
+L<FS::phone_avail> - Phone number availability cache
+
+L<FS::cdr> - Call Detail Record class
+
+L<FS::cdr_batch> - Call Detail Record batch class
+
+L<FS::cdr_calltype> - CDR calltype class
+
+L<FS::cdr_carrier> - CDR carrier class
+
+L<FS::cdr_type> - CDR type class
+
+L<FS::svc_external> - Externally tracked service class.
+
+L<FS::inventory_class> - Inventory classes
+
+L<FS::inventory_item> - Inventory items
+
+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::pkg_category> - Package category class (invoice oriented)
+
+L<FS::pkg_class> - Package class class
+
+L<FS::part_pkg> - Package definition class
+
+L<FS::part_pkg_link> - Package definition link class
+
+L<FS::part_pkg_taxclass> - Tax class class
+
+L<FS::part_pkg_option> - Package definition option class
+
+L<FS::part_pkg_report_option> - Package reporting classification class
+
+L<FS::pkg_svc> - Class linking package definitions (see L<FS::part_pkg>) with
+service definitions (see L<FS::part_svc>)
+
+L<FS::reg_code> - One-time registration codes
+
+L<FS::reg_code_pkg> - Class linking registration codes (see L<FS::reg_code>) with package definitions (see L<FS::part_pkg>)
+
+L<FS::rate> - Rate plans for call billing
+
+L<FS::rate_region> - Rate regions for call billing
+
+L<FS::rate_prefix> - Rate region prefixes for call billing
+
+L<FS::rate_detail> - Rate plan detail for call billing
+
+L<FS::usage_class> - Usage class class
+
+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 definitions (see L<FS::part_pkg>)
+
+L<FS::payment_gateway> - Payment gateway class
+
+L<FS::payment_gateway_option> - Payment gateway option class
+
+L<FS::agent_payment_gateway> - Agent payment gateway class
+
+L<FS::cust_svc> - Service class
+
+L<FS::cust_pkg> - Customer package class
+
+L<FS::cust_pkg_option> - Customer package option class
+
+L<FS::cust_pkg_detail> - Customer package details class
+
+L<FS:;cust_pkg_discount> - Customer package discount class
+
+L<FS:;cust_bill_pkg_discount> - Customer package discount line item application class
+
+L<FS:;discount> - Discount class
+
+L<FS::reason_type> - Reason type class
+
+L<FS::reason> - Reason class
+
+L<FS::cust_pkg_reason> - Package reason class
+
+L<FS::contact> - Contact class
+
+L<FS::contact_phone> - Contact phone class
+
+L<FS::phone_type> - Phone type class
+
+L<FS::contact_email> - Contact email class
+
+L<FS::prospect_main> - Prospect class
+
+L<FS::cust_main> - Customer class
+
+L<FS::cust_location> - Customer location class
+
+L<FS::cust_main_Mixin> - Mixin class for records that contain fields from cust_main
+
+L<FS::cust_main_invoice> - Invoice destination class
+
+L<FS::cust_class> - Customer classification class
+
+L<FS::cust_category> - Customer category class
+
+L<FS::cust_main_exemption> - Customer tax exemption class
+
+L<FS::cust_main_note> - Customer note class
+
+L<FS::banned_pay> - Banned payment information class
+
+L<FS::cust_bill> - Invoice class
+
+L<FS::cust_statement> - Informational statement 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> - (Old) Invoice event definition class
+
+L<FS::cust_bill_event> - (Old) Completed invoice event class
+
+L<FS::part_event> - (New) Billing event definition class
+
+L<FS::part_event_option> - (New) Billing event option class
+
+L<FS::part_event::Condition> - (New) Billing event condition base class
+
+L<FS::part_event::Action> - (New) Billing event action base class
+
+L<FS::part_event_condition> - (New) Billing event condition class
+
+L<FS::part_event_condition_option> - (New) Billing event condition option class
+
+L<FS::part_event_condition_option_option> - (New) Billing event condition compound option class
+
+L<FS::cust_event> - (New) Customer event class
+
+L<FS::cust_bill_ApplicationCommon> - Base class for bill application classes
+
+L<FS::cust_pay> - Payment class
+
+L<FS::cust_pay_pending> - Pending payment class
+
+L<FS::cust_pay_void> - Voided payment class
+
+L<FS::cust_bill_pay> - Payment application class
+
+L<FS::cust_bill_pay_pkg> - Line-item specific payment application class
+
+L<FS::cust_bill_pay_batch> - Batch 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_credit_bill_pkg> - Line-item specific credit application to invoice class
+
+L<FS::cust_pay_refund> - Refund application to payment class
+
+L<FS::pay_batch> - Credit card transaction queue class
+
+L<FS::cust_pay_batch> - Credit card transaction member 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
+
+L<FS::clientapi_session>
+
+L<FS::clientapi_session_field>
+
+=head2 Historical database record classes
+
+L<FS::h_Common> - History table base class
+
+L<FS::h_cust_pay> - Historical record of customer payment changes
+
+L<FS::h_cust_credit> - Historical record of customer credit changes
+
+L<FS::h_cust_bill> - Historical record of customer tax changes (old-style)
+
+L<FS::h_cust_svc> - Object method for h_cust_svc objects
+
+L<FS::h_cust_tax_exempt> - Historical record of customer tax changes (old-style)
+
+L<FS::h_domain_record> - Historical DNS entry objects
+
+L<FS::h_svc_acct> - Historical account objects
+
+L<FS::h_svc_broadband> - Historical broadband connection objects
+
+L<FS::h_svc_domain> - Historical domain objects
+
+L<FS::h_svc_external> - Historical externally tracked service objects
+
+L<FS::h_svc_forward> - Historical mail forwarding alias objects
+
+L<FS::h_svc_mailinglist> - Historical mailing list objects
+
+L<FS::h_svc_phone> - Historical phone number objects
+
+L<FS::h_svc_pbx> - Historical PBX objects
+
+L<FS::h_svc_www> - Historical web virtual host objects
+
+=head2 Remote API modules
+
+L<FS::SelfService> - Self-service API
+
+L<FS::SelfService::XMLRPC> - Self-service XML-RPC API
+
+=head2 User Interface classes
+
+L<FS::UI::Web> - Web user-interface class
+
+L<FS::UI::bytecount> - Byte counter user-interface class
+
+=head2 Command-line utilities
+
+L<freeside-adduser> - Command line interface to add (freeside) users.
+
+L<freeside-daily> - Run daily billing and collection events.
+
+L<freeside-monthly> - Run monthly billing and invoice collection events.
+
+L<freeside-dbdef-create> - Recreate database schema cache
+
+L<freeside-deluser> - Command line interface to delete (freeside) users.
+
+L<freeside-expiration-alerter> - Emails notifications of credit card expirations.
+
+L<freeside-email> - Prints email addresses of all users on STDOUT
+
+L<freeside-fetch> - Send a freeside page to a list of employees.
+
+L<freeside-prepaidd> - Real-time daemon for prepaid packages
+
+L<freeside-prune-applications> - Removes stray applications of credit, payment to bills, refunds, etc.
+
+L<freeside-queued> - Job queue daemon
+
+L<freeside-radgroup> - Command line utility to manipulate radius groups
+
+L<freeside-reexport> - Command line tool to re-trigger export jobs for existing services
+
+L<freeside-reset-fixed> - Command line tool to set the fixed columns for existing services
+
+L<freeside-sqlradius-dedup-group> - Command line tool to eliminate duplicate usergroup entries from radius tables
+
+L<freeside-sqlradius-radacctd> - Real-time radacct import daemon
+
+L<freeside-sqlradius-reset> - Command line interface to reset and recreate RADIUS SQL tables
+
+L<freeside-sqlradius-seconds> - Command line time-online tool
+
+L<freeside-upgrade> - Upgrades database schema for new freeside verisons.
+
+=head1 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 wired and wireless ISPs,
+VoIP, hosting, service and content providers and other online businesses.
+
+The Freeside home page is at <http://www.sisd.com/freeside>.
+
+The main documentation is at <http://www.sisd.com/mediawiki>.
+
+=head1 SUPPORT
+
+A mailing list for users is available. Send a blank message to
+<freeside-users-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
+<freeside-devel-subscribe@sisd.com> to subscribe.
+
+Commercial support is available; see
+<http://www.sisd.com/freeside/commercial.html>.
+
+=head1 AUTHORS
+
+Primarily Ivan Kohler, with help from many kind folks, including core
+contributors Jeff Finucane, Kristian Hoffman, Jason Hall and Peter Bowen.
+
+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 at <http://www.sisd.com/mediawiki/>
+
+=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/AccessRight.pm b/FS/FS/AccessRight.pm
new file mode 100644
index 0000000..08d31d2
--- /dev/null
+++ b/FS/FS/AccessRight.pm
@@ -0,0 +1,380 @@
+package FS::AccessRight;
+
+use strict;
+use vars qw(@rights); # %rights);
+use Tie::IxHash;
+
+=head1 NAME
+
+FS::AccessRight - Access control rights.
+
+=head1 SYNOPSIS
+
+ use FS::AccessRight;
+
+ my @rights = FS::AccessRight->rights;
+
+ #my %rights = FS::AccessRight->rights_categorized;
+ tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_categorized;
+ foreach my $category ( keys %rights ) {
+ my @category_rights = @{ $rights{$category} };
+ }
+
+=head1 DESCRIPTION
+
+Access control rights - Permission to perform specific actions that can be
+assigned to users and/or groups.
+
+=cut
+
+#@rights = (
+# 'Reports' => [
+# '_desc' => 'Access to high-level reporting',
+# ],
+# 'Configuration' => [
+# '_desc' => 'Access to configuration',
+#
+# 'Settings' => {},
+#
+# 'agent' => [
+# '_desc' => 'Master access to reseller configuration',
+# 'agent_type' => {},
+# 'agent' => {},
+# ],
+#
+# 'export_svc_pkg' => [
+# '_desc' => 'Access to export, service and package configuration',
+# 'part_export' => {},
+# 'part_svc' => {},
+# 'part_pkg' => {},
+# 'pkg_class' => {},
+# ],
+#
+# 'billing' => [
+# '_desc' => 'Access to billing configuration',
+# 'payment_gateway' => {},
+# 'part_bill_event' => {},
+# 'prepay_credit' => {},
+# 'rate' => {},
+# 'cust_main_county' => {},
+# ],
+#
+# 'dialup' => [
+# '_desc' => 'Access to dialup configuraiton',
+# 'svc_acct_pop' => {},
+# ],
+#
+# 'broadband' => [
+# '_desc' => 'Access to broadband configuration',
+# 'router' => {},
+# 'addr_block' => {},
+# ],
+#
+# 'misc' => [
+# 'part_referral' => {},
+# 'part_virtual_field' => {},
+# 'msgcat' => {},
+# 'inventory_class' => {},
+# ],
+#
+# },
+#
+#);
+#
+##turn it into a more hash-like structure, but ordered via IxHash
+
+#well, this is what we have for now. getting better.
+tie my %rights, 'Tie::IxHash',
+
+ ###
+ # contact rights
+ ###
+ 'Contact and Prospect rights' => [
+ 'New prospect',
+ 'View prospect',
+ 'Edit prospect',
+ 'List prospects',
+ 'Edit contact', #!
+ #'New contact',
+ #'View customer contacts',
+ #'List contacts',
+ ],
+
+ ###
+ # basic customer rights
+ ###
+ 'Customer rights' => [
+ 'New customer',
+ 'View customer',
+ #'View Customer | View tickets',
+ 'Edit customer',
+ 'Edit referring customer',
+ 'View customer history',
+ 'Cancel customer',
+ 'Complimentary customer', #aka users-allow_comp
+ { rightname=>'Delete customer', desc=>"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 customer's packages if they cancel service." }, #aka. deletecustomers
+ 'Bill customer now', #NEW
+ 'Bulk send customer notices', #NEW
+ ],
+
+ ###
+ # customer package rights
+ ###
+ 'Customer package rights' => [
+ 'View customer packages', #NEW
+ 'Order customer package',
+ 'One-time charge',
+ 'Change customer package',
+ 'Bulk change customer packages',
+ 'Edit customer package dates',
+ 'Discount customer package', #NEW
+ 'Custom discount customer package', #NEW
+ 'Customize customer package',
+ 'Suspend customer package',
+ 'Suspend customer package later',
+ 'Unsuspend customer package',
+ 'Cancel customer package immediately',
+ 'Cancel customer package later',
+ 'Delay suspension events',
+ 'Add on-the-fly cancel reason', #NEW
+ 'Add on-the-fly suspend reason', #NEW
+ 'Edit customer package invoice details', #NEW
+ 'Edit customer package comments', #NEW
+ ],
+
+ ###
+ # customer service rights
+ ###
+ 'Customer service rights' => [
+ 'View customer services', #NEW
+ 'Provision customer service',
+ 'Recharge customer service', #NEW
+ 'Unprovision customer service',
+ 'Change customer service', #NEWNEW
+ 'Edit usage', #NEW
+ 'Edit home dir', #NEW
+ 'Edit www config', #NEW
+ 'Edit domain catchall', #NEW
+ 'Edit domain nameservice', #NEW
+ 'Manage domain registration',
+
+ { rightname=>'View/link unlinked services', global=>1 }, #not agent-virtualizable without more work
+ ],
+
+ ###
+ # customer invoice/financial info rights
+ ###
+ 'Customer invoice / financial info rights' => [
+ 'View invoices',
+ 'Resend invoices', #NEWNEW
+ 'Delete invoices', #new, but no need to phase in
+ 'View customer tax exemptions', #yow
+ 'Add customer tax adjustment', #new, but no need to phase in
+ 'View customer batched payments', #NEW
+ 'View customer pending payments', #NEW
+ 'Edit customer pending payments', #NEW
+ 'View customer billing events', #NEW
+ ],
+
+ ###
+ # customer payment rights
+ ###
+ 'Customer payment rights' => [
+ 'Post payment',
+ 'Post payment batch',
+ 'Apply payment', #NEWNEW
+ { rightname=>'Unapply payment', desc=>'Enable "unapplication" of unclosed payments from specific invoices.' }, #aka. unapplypayments
+ 'Process payment',
+ { rightname=>'Refund payment', desc=>'Enable refund of existing customer payments.' },
+
+ { rightname=>'Delete payment', desc=>'Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments.' }, #aka. deletepayments Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.
+
+ ],
+
+ ###
+ # customer credit rights
+ ###
+ 'Customer credit and refund rights' => [
+ 'Post credit',
+ 'Apply credit', #NEWNEW
+ { rightname=>'Unapply credit', desc=>'Enable "unapplication" of unclosed credits.' }, #aka unapplycredits
+ { rightname=>'Delete credit', desc=>'Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments.' }, #aka. deletecredits Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.
+ { rightname=>'Post refund', desc=>'Enable posting of check and cash refunds.' },
+# { rightname=>'Process refund', desc=>'Enable processing of generic credit card/ACH refunds (i.e. not associated with a specific prior payment).' },
+ 'Delete refund', #NEW
+ 'Add on-the-fly credit reason', #NEW
+ ],
+
+ ###
+ # customer voiding rights..
+ ###
+ 'Customer void rights' => [
+ { rightname=>'Credit card void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. cc-void
+ { rightname=>'Echeck void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. echeck-void
+ 'Regular void',
+ { rightname=>'Unvoid', desc=>'Enable unvoiding of voided payments' }, #aka. unvoid
+
+
+ ],
+
+ ###
+ # note/attachment rights...
+ ###
+ 'Customer note and attachment rights' => [
+ 'Add customer note', #NEW
+ 'Edit customer note', #NEW
+ 'View attachments', #NEW
+ 'Browse attachments', #NEW
+ 'Download attachment', #NEW
+ 'Add attachment', #NEW
+ 'Edit attachment', #NEW
+ 'Delete attachment', #NEW
+ 'View deleted attachments', #NEW
+ 'Undelete attachment', #NEW
+ 'Purge attachment', #NEW
+ ],
+
+ ###
+ # report/listing rights...
+ ###
+ 'Reporting/listing rights' => [
+ 'List customers',
+ 'List zip codes', #NEW
+ 'List invoices',
+ 'List packages',
+ 'List services',
+ 'List service passwords',
+
+ { rightname=> 'List rating data', desc=>'Usage reports', global=>1 },
+ 'Billing event reports',
+ 'Receivables report',
+ 'Financial reports',
+ ],
+
+ ###
+ # misc rights
+ ###
+ 'Miscellaneous rights' => [
+ { rightname=>'Job queue', global=>1 },
+ { rightname=>'Time queue', global=>1 },
+ { rightname=>'Process batches', global=>1 },
+ { rightname=>'Reprocess batches', global=>1 },
+ { rightname=>'Redownload resolved batches', global=>1 },
+ { rightname=>'Import', global=>1 }, #some of these are ag-virt'ed now? give em their own ACLs
+ { rightname=>'Export', global=>1 },
+ { rightname=> 'Edit rating data', desc=>'Delete CDRs', global=>1 },
+ #],
+ #
+ ###
+ # misc misc rights
+ ###
+ #'Database access rights' => [
+ { rightname=>'Raw SQL', global=>1 }, #NEW
+ ],
+
+ ###
+ # setup/config rights
+ ###
+ 'Configuration rights' => [
+ 'Edit advertising sources',
+ { rightname=>'Edit global advertising sources', global=>1 },
+
+ 'Edit package definitions',
+ { rightname=>'Edit global package definitions', global=>1 },
+
+ 'Edit billing events',
+ { rightname=>'Edit global billing events', global=>1 },
+
+ 'Edit inventory',
+ { rightname=>'Edit global inventory', global=>1 },
+
+ { rightname=>'Dialup configuration' },
+ { rightname=>'Dialup global configuration', global=>1 },
+
+ { rightname=>'Broadband configuration' },
+ { rightname=>'Broadband global configuration', global=>1 },
+
+ { rightname=>'Configuration', global=>1 }, #most of the rest of the configuraiton is not agent-virtualized
+
+ { rightname=>'Configuration download', }, #description of how it affects
+ #search/elements/search.html
+
+ ],
+
+;
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item rights
+
+Returns the full list of right names.
+
+=cut
+
+sub rights {
+ #my $class = shift;
+ map { ref($_) ? $_->{'rightname'} : $_ } map @{ $rights{$_} }, keys %rights;
+}
+
+=item default_superuser_rights
+
+Most (but not all) right names.
+
+=cut
+
+sub default_superuser_rights {
+ my $class = shift;
+ my %omit = map { $_=>1 } (
+ 'Delete customer',
+ 'Delete invoices',
+ 'Delete payment',
+ 'Delete credit', #?
+ 'Delete refund', #?
+ 'Time queue',
+ 'Redownload resolved batches',
+ 'Raw SQL',
+ 'Configuration download',
+ );
+
+ no warnings 'uninitialized';
+ grep { ! $omit{$_} } $class->rights;
+}
+
+=item rights_info
+
+Returns a list of key-value pairs suitable for assigning to a hash. Keys are
+category names and values are list references of rights. Each element of the
+list reference scalar right name or a hashref with the following keys:
+
+=over 4
+
+=item rightname - Right name
+
+=item desc - Extended right description
+
+=item global - Global flag, indicates that this access right provides access to global data which is shared among all agents.
+
+=back
+
+=cut
+
+sub rights_info {
+ %rights;
+}
+
+=back
+
+=head1 BUGS
+
+Damn those infernal six-legged creatures!
+
+=head1 SEE ALSO
+
+L<FS::access_right>, L<FS::access_group>, L<FS::access_user>
+
+=cut
+
+1;
+
diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm
new file mode 100644
index 0000000..f33a718
--- /dev/null
+++ b/FS/FS/CGI.pm
@@ -0,0 +1,327 @@
+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 rooturl table itable ntable
+ 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>
+ <CENTER>$title</CENTER>
+ </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 $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);
+ next if $item =~ /^\s*Main\s+Menu\s*$/i;
+ 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 $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 [URL]
+
+Returns current (or, optionally, passed) URL with LEVEL levels of path removed
+from the end (default 0).
+
+=cut
+
+sub popurl {
+ my $up = shift;
+
+ my $url_string;
+ if ( scalar(@_) ) {
+ $url_string = shift;
+ } else {
+ my $cgi = &FS::UID::cgi;
+ $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 rooturl
+
+=cut
+
+sub rooturl {
+ # better to start with the client-provided URL
+ my $cgi = &FS::UID::cgi;
+ my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
+ $url_string =~ s/\?.*//;
+
+ #even though this is kludgy
+ $url_string =~ s{ / index\.html /? $ }
+ {/}x;
+ $url_string =~
+ s{
+ /
+ (browse|config|docs|edit|graph|misc|search|view|pref|rt|elements)
+ /
+ (process/)?
+ ([\w\-\.\/]*)
+ $
+ }
+ {}x;
+
+ #elements because of progress-popup.html...
+ #XXX remove anything from elements that is called directly & prevent
+ #those pages from being served up
+
+ $url_string .= '/' unless $url_string =~ /\/$/;
+
+ $url_string;
+
+}
+
+=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;
+ my $width = ( scalar(@_) && shift ) ? '' : 'WIDTH="100%"'; #bah
+ if ( $col ) {
+ qq!<TABLE BGCOLOR="$col" BORDER=0 CELLSPACING=$cellspacing $width>!;
+ } else {
+ qq!<TABLE BORDER=0 CELLSPACING=$cellspacing $width>!;
+ }
+}
+
+=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">';
+ }
+
+}
+
+=back
+
+=head1 BUGS
+
+Not OO.
+
+Not complete.
+
+=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..1677fcc
--- /dev/null
+++ b/FS/FS/ClientAPI.pm
@@ -0,0 +1,42 @@
+package FS::ClientAPI;
+
+use strict;
+use base 'Exporter';
+use vars qw( @EXPORT_OK %handler $domain $DEBUG );
+
+@EXPORT_OK = qw( load_clientapi_modules );
+
+$DEBUG = 0;
+
+%handler = ();
+
+sub load_clientapi_modules {
+
+ #find modules
+ foreach my $INC ( @INC ) {
+ my $glob = "$INC/FS/ClientAPI/*.pm";
+ warn "FS::ClientAPI: searching $glob" if $DEBUG;
+ foreach my $file ( glob($glob) ) {
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized ClientAPI file: $file";
+ next
+ };
+ my $mod = $1;
+ warn "using FS::ClientAPI::$mod" if $DEBUG;
+ eval "use FS::ClientAPI::$mod;";
+ die "error using FS::ClientAPI::$mod: $@" if $@;
+ }
+ }
+
+}
+
+sub dispatch {
+ my ( $self, $name ) = ( shift, shift );
+ $name =~ s(/)(::)g;
+ my $sub = "FS::ClientAPI::$name";
+ no strict 'refs';
+ &{$sub}(@_);
+}
+
+1;
+
diff --git a/FS/FS/ClientAPI/Agent.pm b/FS/FS/ClientAPI/Agent.pm
new file mode 100644
index 0000000..daede59
--- /dev/null
+++ b/FS/FS/ClientAPI/Agent.pm
@@ -0,0 +1,125 @@
+package FS::ClientAPI::Agent;
+
+#some false laziness w/MyAccount
+
+use strict;
+use vars qw($cache);
+use subs qw(_cache);
+use Digest::MD5 qw(md5_hex);
+use FS::Record qw(qsearchs); # qsearch dbdef dbh);
+use FS::ClientAPI_SessionCache;
+use FS::agent;
+use FS::cust_main qw(smart_search);
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ '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 = smart_search( 'search' => $p->{'search'},
+ '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
+ ],
+ }
+
+}
+
+1;
diff --git a/FS/FS/ClientAPI/Bulk.pm b/FS/FS/ClientAPI/Bulk.pm
new file mode 100644
index 0000000..ec617df
--- /dev/null
+++ b/FS/FS/ClientAPI/Bulk.pm
@@ -0,0 +1,384 @@
+package FS::ClientAPI::Bulk;
+
+use strict;
+
+use vars qw( $DEBUG $cache );
+use Date::Parse;
+use FS::Record qw( qsearchs );
+use FS::Conf;
+use FS::ClientAPI_SessionCache;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::svc_external;
+use FS::cust_recon;
+use Data::Dumper;
+
+$DEBUG = 1;
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache ( {
+ 'namespace' => 'FS::ClientAPI::Agent', #yes, share session_ids
+ } );
+}
+
+sub _izoom_ftp_row_fixup {
+ my $hash = shift;
+
+ my @addr_fields = qw( address1 address2 city state zip );
+ my @fields = ( qw( agent_custid username _password first last ),
+ @addr_fields,
+ map { "ship_$_" } @addr_fields );
+
+ $hash->{$_} =~ s/[&\/\*'"]/_/g foreach @fields;
+
+ #$hash->{action} = '' if $hash->{action} eq 'R'; #unsupported for ftp
+
+ $hash->{refnum} = 1; #ahem
+ $hash->{country} = 'US';
+ $hash->{ship_country} = 'US';
+ $hash->{payby} = 'LECB';
+ $hash->{payinfo} = $hash->{daytime};
+ $hash->{ship_fax} = '' if ( !$hash->{sms} || $hash->{sms} eq 'F' );
+
+ my $has_ship =
+ grep { $hash->{"ship_$_"} &&
+ (! $hash->{$_} || $hash->{"ship_$_"} ne $hash->{$_} )
+ }
+ ( @addr_fields, 'fax' );
+
+ if ( $has_ship ) {
+ foreach ( @addr_fields, qw( first last ) ) {
+ $hash->{"ship_$_"} = $hash->{$_} unless $hash->{"ship_$_"};
+ }
+ }
+
+ delete $hash->{sms};
+
+ '';
+
+};
+
+sub _izoom_ftp_result {
+ my ($hash, $error) = @_;
+ my $cust_main =
+ qsearchs( 'cust_main', { 'agent_custid' => $hash->{agent_custid},
+ 'agentnum' => $hash->{agentnum}
+ }
+ );
+
+ my $custnum = $cust_main ? $cust_main->custnum : '';
+ my @response = ( $hash->{action}, $hash->{agent_custid}, $custnum );
+
+ if ( $error ) {
+ push @response, ( 'ERROR', $error );
+ } else {
+ push @response, ( 'OK', 'OK' );
+ }
+
+ join( ',', @response );
+
+}
+
+sub _izoom_ftp_badaction {
+ "Invalid action: $_[0] record: @_ ";
+}
+
+sub _izoom_soap_row_fixup { _izoom_ftp_row_fixup(@_) };
+
+sub _izoom_soap_result {
+ my ($hash, $error) = @_;
+
+ if ( $hash->{action} eq 'R' ) {
+ if ( $error ) {
+ return "Please check errors:\n $error"; # odd extra space
+ } else {
+ return join(' ', "Everything ok.", $hash->{pkg}, $hash->{adjourn} );
+ }
+ }
+
+ my $pkg = $hash->{pkg} || $hash->{saved_pkg} || '';
+ if ( $error ) {
+ return join(' ', $hash->{agent_custid}, $error );
+ } else {
+ return join(' ', $hash->{agent_custid}, $pkg, $hash->{adjourn} );
+ }
+
+}
+
+sub _izoom_soap_badaction {
+ "Unknown action '$_[13]' ";
+}
+
+my %format = (
+ 'izoom-ftp' => {
+ 'fields' => [ qw ( action agent_custid username _password
+ daytime ship_fax sms first last
+ address1 address2 city state zip
+ pkg adjourn ship_address1 ship_address2
+ ship_city ship_state ship_zip ) ],
+ 'fixup' => sub { _izoom_ftp_row_fixup(@_) },
+ 'result' => sub { _izoom_ftp_result(@_) },
+ 'action' => sub { _izoom_ftp_badaction(@_) },
+ },
+ 'izoom-soap' => {
+ 'fields' => [ qw ( agent_custid username _password
+ daytime first last address1 address2
+ city state zip pkg action adjourn
+ ship_fax sms ship_address1 ship_address2
+ ship_city ship_state ship_zip ) ],
+ 'fixup' => sub { _izoom_soap_row_fixup(@_) },
+ 'result' => sub { _izoom_soap_result(@_) },
+ 'action' => sub { _izoom_soap_badaction(@_) },
+ },
+);
+
+sub processrow {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $conf = new FS::Conf;
+ my $format = $conf->config('selfservice-bulk_format', $session->{agentnum})
+ || 'izoom-soap';
+ my ( @row ) = @{ $p->{row} };
+
+ warn "processrow called with '". join("' '", @row). "'\n" if $DEBUG;
+
+ return { 'error' => "unknown format: $format" }
+ unless exists $format{$format};
+
+ return { 'error' => "Invalid record record length: ". scalar(@row).
+ "record: @row " #sic
+ }
+ unless scalar(@row) == scalar(@{$format{$format}{fields}});
+
+ my %hash = ( 'agentnum' => $session->{agentnum} );
+ my $error;
+
+ foreach my $field ( @{ $format{ $format }{ fields } } ) {
+ $hash{$field} = shift @row;
+ }
+
+ $error ||= &{ $format{ $format }{ fixup } }( \%hash );
+
+ # put in the fixup routine?
+ if ( 'R' eq $hash{action} ) {
+ warn "processing reconciliation\n" if $DEBUG;
+ $error ||= process_recon($hash{agentnum}, $hash{agent_custid});
+ } elsif ( 'P' eq $hash{action} ) {
+ # do nothing
+ } elsif( 'D' eq $hash{action} ) {
+ $hash{promo_pkg} = 'disk-1-'. $session->{agent};
+ } elsif ( 'S' eq $hash{action} ) {
+ $hash{promo_pkg} = 'disk-2-'. $session->{agent};
+ $hash{saved_pkg} = $hash{pkg};
+ $hash{pkg} = '';
+ } else {
+ $error ||= &{ $format{ $format }{ action } }( @row );
+ }
+
+ warn "processing provision\n" if ($DEBUG && !$error && $hash{action} ne 'R');
+ $error ||= provision( %hash ) unless $hash{action} eq 'R';
+
+ my $result = &{ $format{ $format }{ result } }( \%hash, $error );
+
+ warn "processrow returning '". join("' '", $result, $error). "'\n"
+ if $DEBUG;
+
+ return { 'error' => $error, 'message' => $result };
+
+}
+
+sub provision {
+ my %args = ( @_ );
+
+ delete $args{action};
+
+ my $cust_main =
+ qsearchs( 'cust_main',
+ { map { $_ => $args{$_} } qw ( agent_custid agentnum ) },
+ );
+
+ unless ( $cust_main ) {
+ $cust_main = new FS::cust_main { %args };
+ my $error = $cust_main->insert;
+ return $error if $error;
+ }
+
+ my @pkgs = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
+ if ( scalar(@pkgs) > 1 ) {
+ return "Invalid account, should not be more then one active package ". #sic
+ "but found: ". scalar(@pkgs). " packages.";
+ }
+
+ my $part_pkg = qsearchs( 'part_pkg', { 'pkg' => $args{pkg} } )
+ or return "Unknown pkgpart: $args{pkg}"
+ if $args{pkg};
+
+
+ my $create_package = $args{pkg};
+ if ( scalar(@pkgs) && $create_package ) {
+ my $pkg = pop(@pkgs);
+
+ if ( $part_pkg->pkgpart != $pkg->pkgpart ) {
+ my @cust_bill_pkg = $pkg->cust_bill_pkg();
+ if ( 1 == scalar(@cust_bill_pkg) ) {
+ my $cbp= pop(@cust_bill_pkg);
+ my $cust_bill = $cbp->cust_bill;
+ $cust_bill->delete(); #really? wouldn't a credit be better?
+ }
+ $pkg->cancel();
+ } else {
+ $create_package = '';
+ $pkg->setfield('adjourn', str2time($args{adjourn}));
+ my $error = $pkg->replace();
+ return $error if $error;
+ }
+ }
+
+ if ( $create_package ) {
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'adjourn' => str2time( $args{adjourn} ),
+ } );
+
+ my $svcpart = $part_pkg->svcpart('svc_acct');
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ 'username' => $args{username},
+ '_password' => $args{_password},
+ } );
+
+ my $error = $cust_main->order_pkg( cust_pkg => $cust_pkg,
+ svcs => [ $svc_acct ],
+ );
+ return $error if $error;
+ }
+
+ if ( $args{promo_pkg} ) {
+ my $part_pkg =
+ qsearchs( 'part_pkg', { 'promo_code' => $args{promo_pkg} } )
+ or return "unknown pkgpart: $args{promo_pkg}";
+
+ my $svcpart = $part_pkg->svcpart('svc_external')
+ or return "unknown svcpart: svc_external";
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'svcpart' => $svcpart,
+ 'pkgpart' => $part_pkg->pkgpart,
+ } );
+
+ my $svc_ext = new FS::svc_external ( { 'svcpart' => $svcpart } );
+
+ my $ticket_subject = 'Send setup disk to customer '. $cust_main->custnum;
+ my $error = $cust_main->order_pkg ( cust_pkg => $cust_pkg,
+ svcs => [ $svc_ext ],
+ noexport => 1,
+ ticket_subject => $ticket_subject,
+ ticket_queue => "disk-$args{agentnum}",
+ );
+ return $error if $error;
+ }
+
+ my $error = $cust_main->bill();
+ return $error if $error;
+}
+
+sub process_recon {
+ my ( $agentnum, $id ) = @_;
+ my @recs = split /;/, $id;
+ my $err = '';
+ foreach my $rec ( @recs ) {
+ my @record = split /,/, $rec;
+ my $result = process_recon_record(@record, $agentnum);
+ $err .= "$result\n" if $result;
+ }
+ return $err;
+}
+
+sub process_recon_record {
+ my ( $agent_custid, $username, $_password, $daytime, $first, $last, $address1, $address2, $city, $state, $zip, $pkg, $adjourn, $agentnum) = @_;
+
+ warn "process_recon_record called with '". join("','", @_). "'\n" if $DEBUG;
+
+ my ($cust_pkg, $package);
+
+ my $cust_main =
+ qsearchs( 'cust_main',
+ { 'agent_custid' => $agent_custid, 'agentnum' => $agentnum },
+ );
+
+ my $comments = '';
+ if ( $cust_main ) {
+ my @cust_pkg = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
+ if ( scalar(@cust_pkg) == 1) {
+ $cust_pkg = pop(@cust_pkg);
+ $package = $cust_pkg->part_pkg->pkg;
+ $comments = "$agent_custid wrong package, expected: $pkg found: $package"
+ if ( $pkg ne $package );
+ } else {
+ $comments = "invalid account, should be one active package but found: ".
+ scalar(@cust_pkg). " packages.";
+ }
+ } else {
+ $comments =
+ "Customer not found agent_custid=$agent_custid, agentnum=$agentnum";
+ }
+
+ my $cust_recon = new FS::cust_recon( {
+ 'recondate' => time,
+ 'agentnum' => $agentnum,
+ 'first' => $first,
+ 'last' => $last,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'custnum' => $cust_main ? $cust_main->custnum : '', #really?
+ 'status' => $cust_main ? $cust_main->status : '',
+ 'pkg' => $package,
+ 'adjourn' => $cust_pkg ? $cust_pkg->adjourn : '',
+ 'agent_custid' => $agent_custid, # redundant?
+ 'agent_pkg' => $pkg,
+ 'agent_adjourn' => str2time($adjourn),
+ 'comments' => $comments,
+ } );
+
+ warn Dumper($cust_recon) if $DEBUG;
+ my $error = $cust_recon->insert;
+ return $error if $error;
+
+ warn "process_recon_record returning $comments\n" if $DEBUG;
+
+ $comments;
+
+}
+
+sub check_username {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $p->{domain} } )
+ or return { 'error' => 'Unknown domain '. $p->{domain} };
+
+ my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{user},
+ 'domsvc' => $svc_domain->svcnum,
+ },
+ );
+
+ return { 'error' => $p->{user}. '@'. $p->{domain}. " alerady in use" } # sic
+ if $svc_acct;
+
+ return { 'error' => '',
+ 'message' => $p->{user}. '@'. $p->{domain}. " is free"
+ };
+}
+
+1;
diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm
new file mode 100644
index 0000000..20b4e5b
--- /dev/null
+++ b/FS/FS/ClientAPI/MasonComponent.pm
@@ -0,0 +1,131 @@
+package FS::ClientAPI::MasonComponent;
+
+use strict;
+use vars qw( $cache $DEBUG $me );
+use subs qw( _cache );
+use FS::Mason qw( mason_interps );
+use FS::Conf;
+use FS::ClientAPI_SessionCache;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+use FS::part_pkg;
+
+$DEBUG = 0;
+$me = '[FS::ClientAPI::MasonComponent]';
+
+my %allowed_comps = map { $_=>1 } qw(
+ /elements/select-did.html
+ /misc/areacodes.cgi
+ /misc/exchanges.cgi
+ /misc/phonenums.cgi
+ /misc/states.cgi
+ /misc/counties.cgi
+ /misc/svc_acct-domains.cgi
+ /misc/part_svc-columns.cgi
+);
+
+my %session_comps = map { $_=>1 } qw(
+ /elements/location.html
+ /edit/cust_main/first_pkg/select-part_pkg.html
+);
+
+my %session_callbacks = (
+
+ '/elements/location.html' => sub {
+ my( $custnum, $argsref ) = @_;
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return "unknown custnum $custnum";
+ my %args = @$argsref;
+ $args{object} = $cust_main;
+ @$argsref = ( %args );
+ return ''; #no error
+ },
+
+ '/edit/cust_main/first_pkg/select-part_pkg.html' => sub {
+ my( $custnum, $argsref ) = @_;
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return "unknown custnum $custnum";
+
+ my $pkgpart = $cust_main->agent->pkgpart_hashref;
+
+ #false laziness w/ edit/cust_main/first_pkg.html
+ my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+ my @part_pkg =
+ grep { $_->svcpart(\@first_svc)
+ && ( $pkgpart->{ $_->pkgpart }
+ || ( $_->agentnum && $_->agentnum == $cust_main->agentnum )
+ )
+ }
+ qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('pkg-addon_classnum') ) {
+
+ my %classnum = map { ( $_->addon_classnum => 1 ) }
+ grep { $_->freq !~ /^0/ }
+ map { $_->part_pkg }
+ $cust_main->ncancelled_pkgs;
+
+ unless ( $classnum{''} || ! keys %classnum ) {
+ @part_pkg = grep $classnum{ $_->classnum }, @part_pkg;
+ }
+ }
+
+ my %args = @$argsref;
+ $args{part_pkg} = \@part_pkg;
+ @$argsref = ( %args );
+ return ''; #no error
+
+ },
+
+);
+
+my $outbuf;
+my( $fs_interp, $rt_interp ) = mason_interps('standalone', 'outbuf'=>\$outbuf);
+
+sub mason_comp {
+ my $packet = shift;
+
+ warn "$me mason_comp called on $packet\n" if $DEBUG;
+
+ my $comp = $packet->{'comp'};
+ unless ( $allowed_comps{$comp} || $session_comps{$comp} ) {
+ return { 'error' => 'Illegal component' };
+ }
+
+ my @args = $packet->{'args'} ? @{ $packet->{'args'} } : ();
+
+ if ( $session_comps{$comp} ) {
+
+ my $session = _cache->get($packet->{'session_id'})
+ or return ( 'error' => "Can't resume session" ); #better error message
+ my $custnum = $session->{'custnum'};
+
+ my $error = &{ $session_callbacks{$comp} }( $custnum, \@args );
+ return { 'error' => $error } if $error;
+
+ }
+
+ my $conf = new FS::Conf;
+ $FS::Mason::Request::FSURL = $conf->config('selfservice_server-base_url');
+ $FS::Mason::Request::FSURL .= '/' unless $FS::Mason::Request::FSURL =~ /\/$/;
+ $FS::Mason::Request::QUERY_STRING = $packet->{'query_string'} || '';
+
+ $outbuf = '';
+ $fs_interp->exec($comp, @args); #only FS for now alas...
+
+ #errors? (turn off in-line error reporting?)
+
+ return { 'output' => $outbuf };
+
+}
+
+#hmm
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+ } );
+}
+
+1;
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
new file mode 100644
index 0000000..71fe752
--- /dev/null
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -0,0 +1,1736 @@
+package FS::ClientAPI::MyAccount;
+
+use 5.008; #require 5.8+ for Time::Local 1.05+
+use strict;
+use vars qw( $cache $DEBUG $me );
+use subs qw( _cache _provision );
+use Data::Dumper;
+use Digest::MD5 qw(md5_hex);
+use Date::Format;
+use Business::CreditCard;
+use Time::Duration;
+use Time::Local qw(timelocal_nocheck);
+use FS::UI::Web::small_custview qw(small_custview); #less doh
+use FS::UI::Web;
+use FS::UI::bytecount qw( display_bytecount );
+use FS::Conf;
+#use FS::UID qw(dbh);
+use FS::Record qw(qsearch qsearchs dbh);
+use FS::Msgcat qw(gettext);
+use FS::Misc qw(card_types);
+use FS::ClientAPI_SessionCache;
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_phone;
+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::payby;
+use FS::acct_rt_transaction;
+use HTML::Entities;
+use FS::TicketSystem;
+
+$DEBUG = 0;
+$me = '[FS::ClientAPI::MyAccount]';
+
+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 paystart_month paystart_year payissue payip
+ ss paytype paystate stateid stateid_state
+);
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+ } );
+}
+
+sub skin_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ #return { 'error' => $session } if $context eq 'error';
+
+ my $agentnum = '';
+ if ( $context eq 'customer' ) {
+
+ my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?')
+ or die dbh->errstr;
+
+ $sth->execute($custnum) or die $sth->errstr;
+
+ $agentnum = $sth->fetchrow_arrayref->[0]
+ or die "no agentnum for custnum $custnum";
+
+ #} elsif ( $context eq 'agent' ) {
+ } elsif ( $p->{'agentnum'} =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ }
+
+ my $conf = new FS::Conf;
+
+ #false laziness w/Signup.pm
+
+ my $skin_info_cache_agent = _cache->get("skin_info_cache_agent$agentnum");
+
+ if ( $skin_info_cache_agent ) {
+
+ warn "$me loading cached skin info for agentnum $agentnum\n"
+ if $DEBUG > 1;
+
+ } else {
+
+ warn "$me populating skin info cache for agentnum $agentnum\n"
+ if $DEBUG > 1;
+
+ $skin_info_cache_agent = {
+ 'agentnum' => $agentnum,
+ ( map { $_ => scalar( $conf->config($_, $agentnum) ) }
+ qw( company_name ) ),
+ ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) }
+ qw( body_bgcolor box_bgcolor
+ text_color link_color vlink_color hlink_color alink_color
+ font title_color title_align title_size menu_bgcolor menu_fontsize
+ )
+ ),
+ ( map { $_ => $conf->exists("selfservice-$_", $agentnum ) }
+ qw( menu_skipblanks menu_skipheadings menu_nounderline )
+ ),
+ ( map { $_ => scalar($conf->config_binary("selfservice-$_", $agentnum)) }
+ qw( title_left_image title_right_image
+ menu_top_image menu_body_image menu_bottom_image
+ )
+ ),
+ 'logo' => scalar($conf->config_binary('logo.png', $agentnum )),
+ ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) }
+ qw( head body_header body_footer company_address ) ),
+ };
+
+ _cache->set("skin_info_cache_agent$agentnum", $skin_info_cache_agent);
+
+ }
+
+ #{ %$skin_info_cache_agent };
+ $skin_info_cache_agent;
+
+}
+
+sub login_info {
+ my $p = shift;
+
+ my $conf = new FS::Conf;
+
+ my %info = (
+ %{ skin_info($p) },
+ 'phone_login' => $conf->exists('selfservice_server-phone_login'),
+ 'single_domain'=> scalar($conf->config('selfservice_server-single_domain')),
+ );
+
+ return \%info;
+
+}
+
+#false laziness w/FS::ClientAPI::passwd::passwd
+sub login {
+ my $p = shift;
+
+ my $conf = new FS::Conf;
+
+ my $svc_x = '';
+ if ( $p->{'domain'} eq 'svc_phone'
+ && $conf->exists('selfservice_server-phone_login') ) {
+
+ my $svc_phone = qsearchs( 'svc_phone', { 'phonenum' => $p->{'username'} } );
+ return { error => 'Number not found.' } unless $svc_phone;
+
+ #XXX?
+ #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 PIN.' }
+ unless $svc_phone->check_pin($p->{'password'});
+
+ $svc_x = $svc_phone;
+
+ } else {
+
+ 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;
+
+ return { error => 'Incorrect password.' }
+ unless $svc_acct->check_password($p->{'password'});
+
+ $svc_x = $svc_acct;
+
+ }
+
+ my $session = {
+ 'svcnum' => $svc_x->svcnum,
+ };
+
+ my $cust_svc = $svc_x->cust_svc;
+ my $cust_pkg = $cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+ $session->{'custnum'} = $cust_main->custnum;
+ if ( $conf->exists('pkg-balances') ) {
+ my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ }
+ $cust_main->ncancelled_pkgs;
+ $session->{'pkgnum'} = $cust_pkg->pkgnum
+ if scalar(@cust_pkg) > 1;
+ }
+ }
+
+ #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' );
+ my $part_pkg = $cust_pkg->part_pkg;
+ return { error => 'Only primary user may log in.' }
+ if $conf->exists('selfservice_server-primary_only')
+ && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]);
+
+ my $session_id;
+ do {
+ $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+ } until ( ! defined _cache->get($session_id) ); #just in case
+
+ my $timeout = $conf->config('selfservice-session_timeout') || '1 hour';
+ _cache->set( $session_id, $session, $timeout );
+
+ return { 'error' => '',
+ 'session_id' => $session_id,
+ };
+}
+
+sub logout {
+ my $p = shift;
+ if ( $p->{'session_id'} ) {
+ _cache->remove($p->{'session_id'});
+ return { %{ skin_info($p) }, 'error' => '' };
+ } else {
+ return { %{ skin_info($p) }, 'error' => "Can't resume session" }; #better error message
+ }
+}
+
+sub access_info {
+ my $p = shift;
+
+ my $conf = new FS::Conf;
+
+ my $info = skin_info($p);
+
+ use vars qw( $cust_paybys ); #cache for performance
+ unless ( $cust_paybys ) {
+
+ my %cust_paybys = map { $_ => 1 }
+ map { FS::payby->payby2payment($_) }
+ $conf->config('signup_server-payby');
+
+ $cust_paybys = [ keys %cust_paybys ];
+
+ }
+ $info->{'cust_paybys'} = $cust_paybys;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ $info->{hide_payment_fields} =
+ [
+ map { my $pg = '';
+ if ( FS::payby->realtime($_) ) {
+ $pg = $cust_main->agent->payment_gateway(
+ 'method' => FS::payby->payby2bop($_),
+ 'nofatal' => 1,
+ );
+ }
+ $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ }
+ @{ $info->{cust_paybys} }
+ ];
+
+ return { %$info,
+ 'custnum' => $custnum,
+ 'access_pkgnum' => $session->{'pkgnum'},
+ 'access_svcnum' => $session->{'svcnum'},
+ };
+}
+
+sub customer_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my %return;
+
+ my $conf = new FS::Conf;
+ if ($conf->exists('cust_main-require_address2')) {
+ $return{'require_address2'} = '1';
+ }else{
+ $return{'require_address2'} = '';
+ }
+
+ 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" };
+
+ if ( $session->{'pkgnum'} ) {
+ $return{balance} = $cust_main->balance_pkgnum( $session->{'pkgnum'} );
+ } else {
+ $return{balance} = $cust_main->balance;
+ }
+
+ $return{tickets} = [ ($cust_main->tickets) ];
+
+ unless ( $session->{'pkgnum'} ) {
+ my @open = map {
+ {
+ invnum => $_->invnum,
+ date => time2str("%b %o, %Y", $_->_date),
+ owed => $_->owed,
+ };
+ } $cust_main->open_cust_bill;
+ $return{open_invoices} = \@open;
+ }
+
+ $return{small_custview} =
+ small_custview( $cust_main,
+ scalar($conf->config('countrydefault')),
+ ( $session->{'pkgnum'} ? 1 : 0 ), #nobalance
+ );
+
+ $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->paymask;
+ @return{'month', 'year'} = $cust_main->paydate_monthyear;
+ }
+
+ $return{'invoicing_list'} =
+ join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
+ $return{'postal_invoicing'} =
+ 0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
+
+ if (scalar($conf->config('support_packages'))) {
+ my @support_services = ();
+ foreach ($cust_main->support_services) {
+ my $seconds = $_->svc_x->seconds;
+ my $time_remaining = (($seconds < 0) ? '-' : '' ).
+ int(abs($seconds)/3600)."h".
+ sprintf("%02d",(abs($seconds)%3600)/60)."m";
+ my $cust_pkg = $_->cust_pkg;
+ my $pkgnum = '';
+ my $pkg = '';
+ $pkgnum = $cust_pkg->pkgnum if $cust_pkg;
+ $pkg = $cust_pkg->part_pkg->pkg if $cust_pkg;
+ push @support_services, { svcnum => $_->svcnum,
+ time => $time_remaining,
+ pkgnum => $pkgnum,
+ pkg => $pkg,
+ };
+ }
+ $return{support_services} = \@support_services;
+ }
+
+ } elsif ( $session->{'svcnum'} ) { #no customer record
+
+ my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
+ or die "unknown svcnum";
+ $return{name} = $svc_acct->email;
+
+ } else {
+
+ return { 'error' => 'Expired session' }; #XXX redirect to login w/this err!
+
+ }
+
+ 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;
+
+ my $payby = '';
+ if (exists($p->{'payby'})) {
+ $p->{'payby'} =~ /^([A-Z]{4})$/
+ or return { 'error' => "illegal_payby " . $p->{'payby'} };
+ $payby = $1;
+ }
+
+ if ( $payby =~ /^(CARD|DCRD)$/ ) {
+
+ $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
+
+ if ( $new->payinfo eq $cust_main->paymask ) {
+ $new->payinfo($cust_main->payinfo);
+ } else {
+ $new->payinfo($p->{'payinfo'});
+ }
+
+ $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
+
+ } elsif ( $payby =~ /^(CHEK|DCHK)$/ ) {
+
+ my $payinfo;
+ $p->{'payinfo1'} =~ /^([\dx]+)$/
+ or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
+ my $payinfo1 = $1;
+ $p->{'payinfo2'} =~ /^([\dx]+)$/
+ or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
+ my $payinfo2 = $1;
+ $payinfo = $payinfo1. '@'. $payinfo2;
+
+ $new->payinfo( ($payinfo eq $cust_main->paymask)
+ ? $cust_main->payinfo
+ : $payinfo
+ );
+
+ $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
+
+ } elsif ( $payby =~ /^(BILL)$/ ) {
+ #no-op
+ } elsif ( $payby ) { #notyet ready
+ return { 'error' => "unknown payby $payby" };
+ }
+
+ 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
+ ##
+
+ use vars qw($payment_info); #cache for performance
+ unless ( $payment_info ) {
+
+ my $conf = new FS::Conf;
+ my %states = map { $_->state => 1 }
+ qsearch('cust_main_county', {
+ 'country' => $conf->config('countrydefault') || 'US'
+ } );
+
+ my %cust_paybys = map { $_ => 1 }
+ map { FS::payby->payby2payment($_) }
+ $conf->config('signup_server-payby');
+
+ my @cust_paybys = keys %cust_paybys;
+
+ $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' => card_types(),
+
+ 'paytypes' => [ @FS::cust_main::paytypes ],
+
+ 'paybys' => [ $conf->config('signup_server-payby') ],
+ 'cust_paybys' => \@cust_paybys,
+
+ 'stateid_label' => FS::Msgcat::_gettext('stateid'),
+ 'stateid_state_label' => FS::Msgcat::_gettext('stateid_state'),
+
+ 'show_ss' => $conf->exists('show_ss'),
+ 'show_stateid' => $conf->exists('show_stateid'),
+ 'show_paystate' => $conf->exists('show_bankstate'),
+
+ 'save_unchecked' => $conf->exists('selfservice-save_unchecked'),
+ };
+
+ }
+
+ ##
+ #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{hide_payment_fields} =
+ [
+ map { my $pg = '';
+ if ( FS::payby->realtime($_) ) {
+ $pg = $cust_main->agent->payment_gateway(
+ 'method' => FS::payby->payby2bop($_),
+ 'nofatal' => 1,
+ );
+ }
+ $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ }
+ @{ $return{cust_paybys} }
+ ];
+
+ $return{balance} = $cust_main->balance; #XXX pkg-balances?
+
+ $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;
+ $return{stateid_state} = $cust_main->stateid_state;
+
+ if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($cust_main->payinfo);
+ $return{payinfo} = $cust_main->paymask;
+
+ @return{'month', 'year'} = $cust_main->paydate_monthyear;
+
+ }
+
+ if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $cust_main->paytype;
+ $return{paystate} = $cust_main->paystate;
+
+ }
+
+ #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->{'amount'} =~ /^\s*(\d+(\.\d{2})?)\s*$/
+ or return { 'error' => gettext('illegal_amount') };
+ my $amount = $1;
+ return { error => 'Amount must be greater than 0' } unless $amount > 0;
+
+ $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;
+
+ $p->{'payby'} ||= 'CARD';
+ $p->{'payby'} =~ /^([A-Z]{4})$/
+ or return { 'error' => "illegal_payby " . $p->{'payby'} };
+ my $payby = $1;
+
+ #false laziness w/process/payment.cgi
+ my $payinfo;
+ my $paycvv = '';
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+
+ $p->{'payinfo1'} =~ /^([\dx]+)$/
+ or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
+ my $payinfo1 = $1;
+ $p->{'payinfo2'} =~ /^([\dx]+)$/
+ or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
+ my $payinfo2 = $1;
+ $payinfo = $payinfo1. '@'. $payinfo2;
+
+ $payinfo = $cust_main->payinfo
+ if $cust_main->paymask eq $payinfo;
+
+ } elsif ( $payby eq 'CARD' || $payby eq 'DCRD' ) {
+
+ $payinfo = $p->{'payinfo'};
+
+ #more intelligent mathing will be needed here if you change
+ #card_masking_method and don't remove existing paymasks
+ $payinfo = $cust_main->payinfo
+ if $cust_main->paymask eq $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 ( length($p->{'paycvv'}) && $p->{'paycvv'} !~ /^\s*$/ ) {
+ if ( cardtype($payinfo) eq 'American Express card' ) {
+ $p->{'paycvv'} =~ /^\s*(\d{4})\s*$/
+ or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
+ $paycvv = $1;
+ } else {
+ $p->{'paycvv'} =~ /^\s*(\d{3})\s*$/
+ or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
+ $paycvv = $1;
+ }
+ }
+
+ } else {
+ die "unknown payby $payby";
+ }
+
+ my %payby2fields = (
+ 'CARD' => [ qw( paystart_month paystart_year payissue payip
+ address1 address2 city state zip country ) ],
+ 'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
+ );
+
+ my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
+ 'quiet' => 1,
+ 'payinfo' => $payinfo,
+ 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01',
+ 'payname' => $payname,
+ 'paybatch' => $paybatch, #this doesn't actually do anything
+ 'paycvv' => $paycvv,
+ 'pkgnum' => $session->{'pkgnum'},
+ map { $_ => $p->{$_} } @{ $payby2fields{$payby} }
+ );
+ return { 'error' => $error } if $error;
+
+ $cust_main->apply_payments;
+
+ if ( $p->{'save'} ) {
+ my $new = new FS::cust_main { $cust_main->hash };
+ if ($payby eq 'CARD' || $payby eq 'DCRD') {
+ $new->set( $_ => $p->{$_} )
+ foreach qw( payname paystart_month paystart_year payissue payip
+ address1 address2 city state zip country );
+ $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
+ } elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
+ $new->set( $_ => $p->{$_} )
+ foreach qw( payname payip paytype paystate
+ stateid stateid_state );
+ $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
+ }
+ $new->set( 'payinfo' => $payinfo );
+ $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
+ my $error = $new->replace($cust_main);
+ if ( $error ) {
+ #no, this causes customers to process their payments again
+ #return { 'error' => $error };
+ #XXX just warn verosely for now so i can figure out how these happen in
+ # the first place, eventually should redirect them to the "change
+ #address" page but indicate the payment did process??
+ delete($p->{'payinfo'}); #don't want to log this!
+ warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
+ "NEW: ". Dumper($new)."\n".
+ "OLD: ". Dumper($cust_main)."\n".
+ "PACKET: ". Dumper($p)."\n";
+ #} else {
+ #not needed...
+ #$cust_main = $new;
+ }
+ }
+
+ return { 'error' => '' };
+
+}
+
+sub realtime_collect {
+ 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 $error = $cust_main->realtime_collect(
+ 'method' => $p->{'method'},
+ 'pkgnum' => $session->{'pkgnum'},
+ 'session_id' => $p->{'session_id'},
+ 'apply' => 1,
+ );
+ return { 'error' => $error } unless ref( $error );
+
+ my $amount = $session->{'pkgnum'}
+ ? $cust_main->balance_pkgnum( $session->{'pkgnum'} )
+ : $cust_main->balance;
+
+ return { 'error' => '', amount => $amount, %$error };
+}
+
+sub process_payment_order_pkg {
+ my $p = shift;
+
+ my $hr = process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_pkg($p);
+}
+
+sub process_payment_order_renew {
+ my $p = shift;
+
+ my $hr = process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_renew($p);
+}
+
+sub process_prepay {
+
+ 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" };
+
+ my( $amount, $seconds, $upbytes, $downbytes, $totalbytes ) = ( 0, 0, 0, 0, 0 );
+ my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
+ \$amount,
+ \$seconds,
+ \$upbytes,
+ \$downbytes,
+ \$totalbytes,
+ );
+
+ return { 'error' => $error } if $error;
+
+ return { 'error' => '',
+ 'amount' => $amount,
+ 'seconds' => $seconds,
+ 'duration' => duration_exact($seconds),
+ 'upbytes' => $upbytes,
+ 'upload' => FS::UI::bytecount::bytecount_unexact($upbytes),
+ 'downbytes' => $downbytes,
+ 'download' => FS::UI::bytecount::bytecount_unexact($downbytes),
+ 'totalbytes'=> $totalbytes,
+ 'totalload' => FS::UI::bytecount::bytecount_unexact($totalbytes),
+ };
+
+}
+
+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 ),
+ 'invoice_html' => $cust_bill->print_html( { unsquelch_cdr => 1 } ),
+ };
+
+}
+
+sub invoice_logo {
+ my $p = shift;
+
+ #sessioning for this? how do we get the session id to the backend invoice
+ # template so it can add it to the link, blah
+
+ my $agentnum = '';
+ if ( $p->{'invnum'} ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $p->{'invnum'} } )
+ or return { 'error' => 'unknown invnum' };
+ $agentnum = $cust_bill->cust_main->agentnum;
+ }
+
+ my $templatename = $p->{'template'} || $p->{'templatename'};
+
+ #false laziness-ish w/view/cust_bill-logo.cgi
+
+ my $conf = new FS::Conf;
+ if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) {
+ $templatename = "_$1";
+ } else {
+ $templatename = '';
+ }
+
+ my $filename = "logo$templatename.png";
+
+ return { 'error' => '',
+ 'logo' => $conf->config_binary($filename, $agentnum),
+ 'content_type' => 'image/png', #should allow gif, jpg too
+ };
+}
+
+
+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('countrydefault') ),
+ };
+
+}
+
+sub list_svcs {
+ 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 @cust_svc = ();
+ #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
+ foreach my $cust_pkg ( $p->{'ncancelled'}
+ ? $cust_main->ncancelled_pkgs
+ : $cust_main->unsuspended_pkgs ) {
+ next if $session->{'pkgnum'} && $cust_pkg->pkgnum != $session->{'pkgnum'};
+ push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
+ }
+ if ( $p->{'svcdb'} ) {
+ my $svcdb = ref($p->{'svcdb'}) eq 'HASH'
+ ? $p->{'svcdb'}
+ : ref($p->{'svcdb'}) eq 'ARRAY'
+ ? { map { $_=>1 } @{ $p->{'svcdb'} } }
+ : { $p->{'svcdb'} => 1 };
+ @cust_svc = grep $svcdb->{ $_->part_svc->svcdb }, @cust_svc
+ }
+
+ #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
+ # @svc_x;
+
+ {
+ 'svcnum' => $session->{'svcnum'},
+ 'custnum' => $custnum,
+ 'svcs' => [
+ map {
+ my $svc_x = $_->svc_x;
+ my($label, $value) = $_->label;
+ my $svcdb = $_->part_svc->svcdb;
+ my $part_pkg = $_->cust_pkg->part_pkg;
+
+ my %hash = (
+ 'svcnum' => $_->svcnum,
+ 'svcdb' => $svcdb,
+ 'label' => $label,
+ 'value' => $value,
+ );
+
+ if ( $svcdb eq 'svc_acct' ) {
+ %hash = (
+ %hash,
+ 'username' => $svc_x->username,
+ 'email' => $svc_x->email,
+ 'seconds' => $svc_x->seconds,
+ 'upbytes' => display_bytecount($svc_x->upbytes),
+ 'downbytes' => display_bytecount($svc_x->downbytes),
+ 'totalbytes' => display_bytecount($svc_x->totalbytes),
+
+ 'recharge_amount' => $part_pkg->option('recharge_amount',1),
+ 'recharge_seconds' => $part_pkg->option('recharge_seconds',1),
+ 'recharge_upbytes' =>
+ display_bytecount($part_pkg->option('recharge_upbytes',1)),
+ 'recharge_downbytes' =>
+ display_bytecount($part_pkg->option('recharge_downbytes',1)),
+ 'recharge_totalbytes' =>
+ display_bytecount($part_pkg->option('recharge_totalbytes',1)),
+ # more...
+ );
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+ %hash = (
+ %hash,
+ );
+ }
+
+ \%hash;
+ }
+ @cust_svc
+ ],
+ };
+
+}
+
+sub _list_svc_usage {
+ my($svc_acct, $begin, $end) = @_;
+ my @usage = ();
+ foreach my $part_export (
+ map { qsearch ( 'part_export', { 'exporttype' => $_ } ) }
+ qw( sqlradius sqlradius_withdomain )
+ ) {
+ push @usage, @ { $part_export->usage_sessions($begin, $end, $svc_acct) };
+ }
+ (@usage);
+}
+
+sub list_svc_usage {
+ _usage_details(\&_list_svc_usage, @_);
+}
+
+sub _list_support_usage {
+ my($svc_acct, $begin, $end) = @_;
+ my @usage = ();
+ foreach ( grep { $begin <= $_->_date && $_->_date <= $end }
+ qsearch('acct_rt_transaction', { 'svcnum' => $svc_acct->svcnum })
+ ) {
+ push @usage, { 'seconds' => $_->seconds,
+ 'support' => $_->support,
+ '_date' => $_->_date,
+ 'id' => $_->transaction_id,
+ 'creator' => $_->creator,
+ 'subject' => $_->subject,
+ 'status' => $_->status,
+ 'ticketid' => $_->ticketid,
+ };
+ }
+ (@usage);
+}
+
+sub list_support_usage {
+ _usage_details(\&_list_support_usage, @_);
+}
+
+sub _list_cdr_usage {
+ my($svc_phone, $begin, $end) = @_;
+ map [ $_->downstream_csv('format' => 'default') ], #XXX config for format
+ $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, );
+}
+
+sub list_cdr_usage {
+ my $p = shift;
+ _usage_details( \&_list_cdr_usage, $p,
+ 'svcdb' => 'svc_phone',
+ );
+}
+
+sub _usage_details {
+ my($callback, $p, %opt) = @_;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'svcnum' => $p->{'svcnum'} };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+
+ my $svcdb = $opt{'svcdb'} || 'svc_acct';
+
+ my $svc_x = qsearchs( $svcdb, $search );
+ return { 'error' => 'No service selected in list_svc_usage' }
+ unless $svc_x;
+
+ my $header = $svcdb eq 'svc_phone'
+ ? [ split(',', FS::cdr::invoice_header('default') ) ] #XXX
+ : [];
+
+ my $cust_pkg = $svc_x->cust_svc->cust_pkg;
+ my $freq = $cust_pkg->part_pkg->freq;
+ my $start = $cust_pkg->setup;
+ #my $end = $cust_pkg->bill; # or time?
+ my $end = time;
+
+ unless ( $p->{beginning} ) {
+ $p->{beginning} = $cust_pkg->last_bill;
+ $p->{ending} = $end;
+ }
+
+ my (@usage) = &$callback($svc_x, $p->{beginning}, $p->{ending});
+
+ #kinda false laziness with FS::cust_main::bill, but perhaps
+ #we should really change this bit to DateTime and DateTime::Duration
+ #
+ #change this bit to use Date::Manip? CAREFUL with timezones (see
+ # mailing list archive)
+ my ($nsec,$nmin,$nhour,$nmday,$nmon,$nyear) =
+ (localtime($p->{ending}) )[0,1,2,3,4,5];
+ my ($psec,$pmin,$phour,$pmday,$pmon,$pyear) =
+ (localtime($p->{beginning}) )[0,1,2,3,4,5];
+
+ if ( $freq =~ /^\d+$/ ) {
+ $nmon += $freq;
+ until ( $nmon < 12 ) { $nmon -= 12; $nyear++; }
+ $pmon -= $freq;
+ until ( $pmon >= 0 ) { $pmon += 12; $pyear--; }
+ } elsif ( $freq =~ /^(\d+)w$/ ) {
+ my $weeks = $1;
+ $nmday += $weeks * 7;
+ $pmday -= $weeks * 7;
+ } elsif ( $freq =~ /^(\d+)d$/ ) {
+ my $days = $1;
+ $nmday += $days;
+ $pmday -= $days;
+ } elsif ( $freq =~ /^(\d+)h$/ ) {
+ my $hours = $1;
+ $nhour += $hours;
+ $phour -= $hours;
+ } else {
+ return { 'error' => "unparsable frequency: ". $freq };
+ }
+
+ my $previous = timelocal_nocheck($psec,$pmin,$phour,$pmday,$pmon,$pyear);
+ my $next = timelocal_nocheck($nsec,$nmin,$nhour,$nmday,$nmon,$nyear);
+
+ {
+ 'error' => '',
+ 'svcnum' => $p->{svcnum},
+ 'beginning' => $p->{beginning},
+ 'ending' => $p->{ending},
+ 'previous' => ($previous > $start) ? $previous : $start,
+ 'next' => ($next < $end) ? $next : $end,
+ 'header' => $header,
+ 'usage' => \@usage,
+ };
+}
+
+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" };
+
+ my $status = $cust_main->status;
+ #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 domsvc _password sec_phrase popnum ) ],
+ 'svc_domain' => [ qw( domain ) ],
+ 'svc_phone' => [ qw( phonenum pin sip_password phone_name ) ],
+ '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 $bill_error = _do_bop_realtime( $cust_main, $status );
+
+ if ($bill_error) {
+ $cust_pkg->cancel('quiet'=>1);
+ return $bill_error;
+ } else {
+ $cust_pkg->reexport;
+ }
+
+ } else {
+ $cust_pkg->reexport;
+ }
+
+ return { error => '', pkgnum => $cust_pkg->pkgnum };
+
+}
+
+sub change_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" };
+
+ my $status = $cust_main->status;
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $p->{pkgnum} } )
+ or return { 'error' => "unknown package $p->{pkgnum}" };
+
+ my @newpkg;
+ my $error = FS::cust_pkg::order( $custnum,
+ [$p->{pkgpart}],
+ [$p->{pkgnum}],
+ \@newpkg,
+ );
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('signup_server-realtime') ) {
+
+ my $bill_error = _do_bop_realtime( $cust_main, $status );
+
+ if ($bill_error) {
+ $newpkg[0]->suspend;
+ return $bill_error;
+ } else {
+ $newpkg[0]->reexport;
+ }
+
+ } else {
+ $newpkg[0]->reexport;
+ }
+
+ return { error => '', pkgnum => $cust_pkg->pkgnum };
+
+}
+
+sub order_recharge {
+ 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 $status = $cust_main->status;
+ my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $p->{'svcnum'} } )
+ or return { 'error' => "unknown service " . $p->{'svcnum'} };
+
+ my $svc_x = $cust_svc->svc_x;
+ my $part_pkg = $cust_svc->cust_pkg->part_pkg;
+
+ my %vhash =
+ map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_, 1) }
+ qw ( recharge_seconds recharge_upbytes recharge_downbytes
+ recharge_totalbytes );
+ my $amount = $part_pkg->option('recharge_amount', 1);
+
+ my ($l, $v, $d) = $cust_svc->label; # blah
+ my $pkg = "Recharge $v";
+
+ my $bill_error = $cust_main->charge($amount, $pkg,
+ "time: $vhash{seconds}, up: $vhash{upbytes}," .
+ "down: $vhash{downbytes}, total: $vhash{totalbytes}",
+ $part_pkg->taxclass); #meh
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('signup_server-realtime') && !$bill_error ) {
+
+ $bill_error = _do_bop_realtime( $cust_main, $status );
+
+ if ($bill_error) {
+ return $bill_error;
+ } else {
+ my $error = $svc_x->recharge (\%vhash);
+ return { 'error' => $error } if $error;
+ }
+
+ } else {
+ my $error = $bill_error;
+ $error ||= $svc_x->recharge (\%vhash);
+ return { 'error' => $error } if $error;
+ }
+
+ return { error => '', svc => $cust_svc->part_svc->svc };
+
+}
+
+sub _do_bop_realtime {
+ my ($cust_main, $status) = (shift, shift);
+
+ my $old_balance = $cust_main->balance;
+
+ my $bill_error = $cust_main->bill
+ || $cust_main->apply_payments_and_credits
+ || $cust_main->collect('realtime' => 1);
+
+ if ( $cust_main->balance > $old_balance
+ && $cust_main->balance > 0
+ && ( $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/ ?
+ 1 : $status eq 'suspended' ) ) {
+ #this makes sense. credit is "un-doing" the invoice
+ my $conf = new FS::Conf;
+ $cust_main->credit( sprintf("%.2f", $cust_main->balance - $old_balance ),
+ 'self-service decline',
+ 'reason_type' => $conf->config('signup_credit_type'),
+ );
+ $cust_main->apply_credits( 'order' => 'newest' );
+
+ return { 'error' => '_decline', 'bill_error' => $bill_error };
+ }
+
+ '';
+}
+
+sub renew_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my @cust_pkg = sort { $a->bill <=> $b->bill }
+ grep { $_->part_pkg->freq ne '0' }
+ $cust_main->ncancelled_pkgs;
+
+ #return { 'error' => 'No active packages to renew.' } unless @cust_pkg;
+
+ my $total = $cust_main->balance;
+
+ my @array = map {
+ my $bill = $_->bill;
+ $total += $_->part_pkg->base_recur($_, \$bill);
+ my $renew_date = $_->part_pkg->add_freq($_->bill);
+ {
+ 'pkgnum' => $_->pkgnum,
+ 'amount' => sprintf('%.2f', $total),
+ 'bill_date' => $_->bill,
+ 'bill_date_pretty' => time2str('%x', $_->bill),
+ 'renew_date' => $renew_date,
+ 'renew_date_pretty' => time2str('%x', $renew_date),
+ 'expire_date' => $_->expire,
+ 'expire_date_pretty' => time2str('%x', $_->expire),
+ };
+ }
+ @cust_pkg;
+
+ return { 'dates' => \@array };
+
+}
+
+sub payment_info_renew_info {
+ my $p = shift;
+ my $renew_info = renew_info($p);
+ my $payment_info = payment_info($p);
+ return { %$renew_info,
+ %$payment_info,
+ };
+}
+
+sub order_renew {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $date = $p->{'date'};
+
+ my $now = time;
+
+ #freeside-daily -n -d $date fs_daily $custnum
+ $cust_main->bill_and_collect( 'time' => $date,
+ 'invoice_time' => $now,
+ 'actual_time' => $now,
+ 'check_freq' => '1d',
+ );
+
+ return { 'error' => '' };
+
+}
+
+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;
+ warn "provision_acct called\n"
+ if $DEBUG;
+
+ return { 'error' => gettext('passwords_dont_match') }
+ if $p->{'_password'} ne $p->{'_password2'};
+ return { 'error' => gettext('empty_password') }
+ unless length($p->{'_password'});
+
+ if ($p->{'domsvc'}) {
+ my %domains = domain_select_hash FS::svc_acct(map { $_ => $p->{$_} }
+ qw ( svcpart pkgnum ) );
+ return { 'error' => gettext('invalid_domain') }
+ unless ($domains{$p->{'domsvc'}});
+ }
+
+ warn "provision_acct calling _provision\n"
+ if $DEBUG;
+ _provision( 'FS::svc_acct',
+ [qw(username _password domsvc)],
+ [qw(username _password domsvc)],
+ $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);
+ warn "_provision called for $class\n"
+ if $DEBUG;
+
+ 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'};
+
+ warn "searching for custnum $custnum pkgnum $pkgnum\n"
+ if $DEBUG;
+ my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+ 'pkgnum' => $pkgnum,
+ } )
+ or return { 'error' => "unknown pkgnum $pkgnum" };
+
+ warn "searching for svcpart ". $p->{'svcpart'}. "\n"
+ if $DEBUG;
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
+ or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
+
+ warn "creating $class record\n"
+ if $DEBUG;
+ my $svc_x = $class->new( {
+ 'pkgnum' => $p->{'pkgnum'},
+ 'svcpart' => $p->{'svcpart'},
+ map { $_ => $p->{$_} } @$fields
+ } );
+ warn "inserting $class record\n"
+ if $DEBUG;
+ my $error = $svc_x->insert;
+
+ unless ( $error ) {
+ warn "finding inserted record for svcnum ". $svc_x->svcnum. "\n"
+ if $DEBUG;
+ $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum })
+ }
+
+ my $return = { 'svc' => $part_svc->svc,
+ 'error' => $error,
+ map { $_ => $svc_x->get($_) } @$return_fields
+ };
+ warn "_provision returning ". Dumper($return). "\n"
+ if $DEBUG;
+ return $return;
+
+}
+
+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('countrydefault') ),
+
+ };
+
+}
+
+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('countrydefault') ),
+ };
+
+}
+
+sub myaccount_passwd {
+ my $p = shift;
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ return { 'error' => "New passwords don't match." }
+ if $p->{'new_password'} ne $p->{'new_password2'};
+
+ return { 'error' => 'Enter new password' }
+ unless length($p->{'new_password'});
+
+ #my $search = { 'custnum' => $custnum };
+ #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ $custnum =~ /^(\d+)$/ or die "illegal custnum";
+ my $search = " AND custnum = $1";
+ $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
+
+ my $svc_acct = qsearchs( {
+ 'table' => 'svc_acct',
+ 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '.
+ 'LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ 'LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $p->{'svcnum'}, },
+ 'extra_sql' => $search, #important
+ } )
+ or return { 'error' => "Service not found" };
+
+ $svc_acct->_password($p->{'new_password'});
+ my $error = $svc_acct->replace();
+
+ my($label, $value) = $svc_acct->cust_svc->label;
+
+ return { 'error' => $error,
+ 'label' => $label,
+ 'value' => $value,
+ };
+
+}
+
+sub create_ticket {
+ my $p = shift;
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ warn "$me create_ticket: initializing ticket system\n" if $DEBUG;
+ FS::TicketSystem->init();
+
+ my $conf = new FS::Conf;
+ my $queue = $p->{'queue'}
+ || $conf->config('ticket_system-selfservice_queueid')
+ || $conf->config('ticket_system-default_queueid');
+
+ warn "$me create_ticket: creating ticket\n" if $DEBUG;
+ my $err_or_ticket = FS::TicketSystem->create_ticket(
+ '', #create RT session based on FS CurrentUser (fs_selfservice)
+ 'queue' => $queue,
+ 'custnum' => $custnum,
+ 'svcnum' => $session->{'svcnum'},
+ map { $_ => $p->{$_} } qw( requestor cc subject message mime_type )
+ );
+
+ if ( ref($err_or_ticket) ) {
+ warn "$me create_ticket: sucessful: ". $err_or_ticket->id. "\n"
+ if $DEBUG;
+ return { 'error' => '',
+ 'ticket_id' => $err_or_ticket->id,
+ };
+ } else {
+ warn "$me create_ticket: unsucessful: $err_or_ticket\n"
+ if $DEBUG;
+ return { 'error' => $err_or_ticket };
+ }
+
+
+}
+
+#--
+
+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 FS::ClientAPI_SessionCache( {
+ '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/PrepaidPhone.pm b/FS/FS/ClientAPI/PrepaidPhone.pm
new file mode 100644
index 0000000..00bc0ff
--- /dev/null
+++ b/FS/FS/ClientAPI/PrepaidPhone.pm
@@ -0,0 +1,253 @@
+package FS::ClientAPI::PrepaidPhone;
+
+use strict;
+use vars qw($DEBUG $me);
+use FS::Record qw(qsearchs);
+use FS::rate;
+use FS::svc_phone;
+
+$DEBUG = 0;
+$me = '[FS::ClientAPI::PrepaidPhone]';
+
+#TODO:
+# - shared-secret auth? (set a conf value)
+
+=item call_time HASHREF
+
+HASHREF contains the following parameters:
+
+=over 4
+
+=item src
+
+Source number (with countrycode)
+
+=item dst
+
+Destination number (with countrycode)
+
+=back
+
+Always returns a hashref. If there is an error, the hashref contains a single
+"error" key with the error message as a value. Otherwise, returns a hashref
+with the following keys:
+
+=over 4
+
+=item custnum
+
+Empty if no customer is found associated with the number, customer number
+otherwise.
+
+=item seconds
+
+Number of seconds remaining for a call to destination number
+
+=back
+
+=cut
+
+sub call_time {
+ my $packet = shift;
+
+ my $src = $packet->{'src'};
+ my $dst = $packet->{'dst'};
+
+ my $chargeto;
+ my $rateby;
+ #my $conf = new FS::Conf;
+ #if ( #XXX toll-free? collect?
+ # $phonenum = $dst;
+ #} else { #use the src to find the customer
+ $chargeto = $src;
+ $rateby = $dst;
+ #}
+
+ my( $countrycode, $phonenum );
+ if ( $chargeto #an interesting regex to parse out 1&2 digit countrycodes
+ =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/
+ || $chargeto =~ /^(\d{3})(\d*)$/
+ )
+ {
+ $countrycode = $1;
+ $phonenum = $2;
+ } else {
+ return { 'error' => "unparsable billing number: $chargeto" };
+ }
+
+
+ my $svc_phone = qsearchs('svc_phone', { 'countrycode' => $countrycode,
+ 'phonenum' => $phonenum,
+ }
+ );
+
+ unless ( $svc_phone ) {
+ return { 'error' => "can't find customer for +$countrycode $phonenum" };
+# return { 'custnum' => '',
+# 'seconds' => 0,
+# #'balance' => 0,
+# };
+ };
+
+ my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
+ my $cust_main = $cust_pkg->cust_main;
+
+ my $part_pkg = $cust_pkg->part_pkg;
+ my @part_pkg = ( $part_pkg, map $_->dst_pkg, $part_pkg->bill_part_pkg_link );
+ #XXX uuh, behavior indeterminate if you have more than one voip_cdr+prefix
+ #add-on, i guess.
+ warn "$me ". scalar(@part_pkg). ': '.
+ join('/', map { $_->plan. $_->option('rating_method') } @part_pkg )
+ if $DEBUG;
+ @part_pkg =
+ grep { $_->plan eq 'voip_cdr' && $_->option('rating_method') eq 'prefix' }
+ @part_pkg;
+
+ my %return = (
+ 'custnum' => $cust_pkg->custnum,
+ #'balance' => $cust_pkg->cust_main->balance,
+ );
+
+ warn "$me: ". scalar(@part_pkg). ': '.
+ join('/', map { $_->plan. $_->option('rating_method') } @part_pkg )
+ if $DEBUG;
+ return \%return unless @part_pkg;
+
+ warn "$me searching for rate ". $part_pkg[0]->option('ratenum')
+ if $DEBUG;
+
+ my $rate = qsearchs('rate', { 'ratenum'=>$part_pkg[0]->option('ratenum') } );
+
+ unless ( $rate ) {
+ my $error = 'ratenum '. $part_pkg[0]->option('ratenum'). ' not found';
+ warn "$me $error"
+ if $DEBUG;
+ return { 'error'=>$error };
+ }
+
+ warn "$me found rate ". $rate->ratenum
+ if $DEBUG;
+
+ #rate the call and arrive at a max # of seconds for the customer's balance
+
+ my( $rate_countrycode, $rate_phonenum );
+ if ( $rateby #this is an interesting regex to parse out 1&2 digit countrycodes
+ =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/
+ || $rateby =~ /^(\d{3})(\d*)$/
+ )
+ {
+ $rate_countrycode = $1;
+ $rate_phonenum = $2;
+ } else {
+ return { 'error' => "unparsable rating number: $rateby" };
+ }
+
+ my $rate_detail = $rate->dest_detail({ 'countrycode' => $rate_countrycode,
+ 'phonenum' => $rate_phonenum,
+ });
+ unless ( $rate_detail ) {
+ return { 'error'=>"can't find rate for +$rate_countrycode $rate_phonenum"};
+ }
+
+ unless ( $rate_detail->min_charge > 0 ) {
+ #XXX no charge?? return lots of seconds, a default, 0 or what?
+ #return { 'error' => '0 rate for +$rate_countrycode $rate_phonenum; prepaid service not available" };
+ #customer wants no default for now# $return{'seconds'} = 1800; #half hour?!
+ return \%return;
+ }
+
+ #XXX granularity? included minutes? another day...
+ if ( $cust_main->balance >= 0 ) {
+ return { 'error'=>'No balance' };
+ } else {
+ $return{'seconds'} = int(60 * abs($cust_main->balance) / $rate_detail->min_charge);
+ }
+
+ warn "$me returning seconds: ". $return{'seconds'};
+
+ return \%return;
+
+}
+
+=item call_time_nanpa
+
+Like I<call_time>, except countrycode 1 is not required, and all other
+countrycodes must be prefixed with 011.
+
+=cut
+
+# - everything is assumed to be countrycode 1 unless it starts with 011(ccode)
+sub call_time_nanpa {
+ my $packet = shift;
+
+ foreach (qw( src dst )) {
+ if ( $packet->{$_} =~ /^011(\d+)/ ) {
+ $packet->{$_} = $1;
+ } elsif ( $packet->{$_} !~ /^1/ ) {
+ $packet->{$_} = '1'.$packet->{$_};
+ }
+ }
+
+ call_time($packet);
+
+}
+
+=item phonenum_balance HASHREF
+
+HASHREF contains the following parameters:
+
+=over 4
+
+=item countrycode
+
+Optional countrycode. Defaults to 1.
+
+=item phonenum
+
+Phone number.
+
+=back
+
+Always returns a hashref. If there is an error, the hashref contains a single
+"error" key with the error message as a value. Otherwise, returns a hashref
+with the following keys:
+
+=over 4
+
+=item custnum
+
+Empty if no customer is found associated with the number, customer number
+otherwise.
+
+=item balance
+
+Customer balance.
+
+=back
+
+=cut
+
+sub phonenum_balance {
+ my $packet = shift;
+
+ my $svc_phone = qsearchs('svc_phone', {
+ 'countrycode' => ( $packet->{'countrycode'} || 1 ),
+ 'phonenum' => $packet->{'phonenum'},
+ });
+
+ unless ( $svc_phone ) {
+ return { 'custnum' => '',
+ 'balance' => 0,
+ };
+ };
+
+ my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
+
+ return {
+ 'custnum' => $cust_pkg->custnum,
+ 'balance' => $cust_pkg->cust_main->balance,
+ };
+
+}
+
+1;
diff --git a/FS/FS/ClientAPI/SGNG.pm b/FS/FS/ClientAPI/SGNG.pm
new file mode 100644
index 0000000..7f784dc
--- /dev/null
+++ b/FS/FS/ClientAPI/SGNG.pm
@@ -0,0 +1,277 @@
+#this stuff is SG-specific (i.e. multi-customer company username hack)
+
+package FS::ClientAPI::SGNG;
+
+use strict;
+use vars qw( $cache $DEBUG );
+use Time::Local qw(timelocal timelocal_nocheck);
+use Business::CreditCard;
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::ClientAPI::MyAccount; #qw( payment_info process_payment )
+
+$DEBUG = 0;
+
+sub _cache {
+ $cache ||= new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount', #yes, share session_ids
+ } );
+}
+
+sub ping {
+ #my $p = shift;
+
+ return { 'pong' => '1' };
+
+}
+
+#this might almost be general-purpose
+sub decompify_pkgs {
+ 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" };
+
+ return { 'error' => 'Not a complimentary customer' }
+ unless $cust_main->payby eq 'COMP';
+
+ my $paydate =
+ $cust_main->paydate =~ /^\S+$/ ? $cust_main->paydate : '2037-12-31';
+
+ my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+
+ my $date = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+ foreach my $cust_pkg (
+ qsearch({ 'table' => 'cust_pkg',
+ 'hashref' => { 'custnum' => $custnum,
+ 'bill' => '',
+ },
+ 'extra_sql' => ' AND '. FS::cust_pkg->active_sql,
+ })
+ ) {
+ $cust_pkg->set('bill', $date);
+ my $error = $cust_pkg->replace;
+ return { 'error' => $error } if $error;
+ }
+
+ return { 'error' => '' };
+
+}
+
+#find old payment info
+# (should work just like MyAccount::payment_info, except returns previous info
+# too)
+# definitly sg-specific, no one else stores past customer records like this
+sub previous_payment_info {
+ my $p = shift;
+
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $payment_info = FS::ClientAPI::MyAccount::payment_info($p);
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ #?
+ return $payment_info if $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/;
+
+ foreach my $prev_cust_main (
+ reverse _previous_cust_main( 'custnum' => $custnum,
+ 'username' => $cust_main->company,
+ 'with_payments' => 1,
+ )
+ ) {
+
+ next unless $prev_cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/;
+
+ if ( $prev_cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+
+ #card expired?
+ my ($payyear,$paymonth,$payday) = split (/-/, $cust_main->paydate);
+
+ my $expdate = timelocal_nocheck(0,0,0,1,$paymonth,$payyear);
+
+ next if $expdate < time;
+
+ } elsif ( $prev_cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+
+ #any check? or just skip these in favor of cards?
+
+ }
+
+ return { %$payment_info,
+ #$prev_cust_main->payment_info
+ _cust_main_payment_info( $prev_cust_main ),
+ 'previous_custnum' => $prev_cust_main->custnum,
+ };
+
+ }
+
+ #still nothing? return an error?
+ return $payment_info;
+
+}
+
+#this is really FS::cust_main::payment_info, but here for now
+sub _cust_main_payment_info {
+ my $self = shift;
+
+ my %return = ();
+
+ $return{balance} = $self->balance;
+
+ $return{payname} = $self->payname
+ || ( $self->first. ' '. $self->get('last') );
+
+ $return{$_} = $self->get($_) for qw(address1 address2 city state zip);
+
+ $return{payby} = $self->payby;
+ $return{stateid_state} = $self->stateid_state;
+
+ if ( $self->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($self->payinfo);
+ $return{payinfo} = $self->paymask;
+
+ @return{'month', 'year'} = $self->paydate_monthyear;
+
+ }
+
+ if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $self->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $self->paytype;
+ $return{paystate} = $self->paystate;
+
+ }
+
+ #doubleclick protection
+ my $_date = time;
+ $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
+
+ %return;
+
+}
+
+#find old cust_main records (with payments)
+sub _previous_cust_main {
+
+ #safety check! return nothing unless we're enabled explicitly
+ return () unless FS::Conf->new->exists('sg-multicustomer_hack');
+
+ my %opt = @_;
+ my $custnum = $opt{'custnum'};
+ my $username = $opt{'username'};
+
+ my %search = ();
+ if ( $opt{'with_payments'} ) {
+ $search{'extra_sql'} =
+ ' AND 0 < ( SELECT COUNT(*) FROM cust_pay
+ WHERE cust_pay.custnum = cust_main.custnum
+ )
+ ';
+ }
+
+ qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'company' => { op => 'ILIKE', value => $opt{'username'} },
+ 'custnum' => { op => '!=', value => $opt{'custnum'} },
+ },
+ 'order_by' => 'ORDER BY custnum',
+ %search,
+ } );
+
+}
+
+#since we could be passing masked old CC data, need to look that up and
+#replace it (like regular process_payment does) w/info from old customer record
+sub previous_process_payment {
+ my $p = shift;
+
+ return FS::ClientAPI::MyAccount::process_payment($p)
+ unless $p->{'previous_custnum'}
+ && ( ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i )
+ || ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i )
+ );
+
+ 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" };
+
+ #make sure this is really a previous custnum of this customer
+ my @previous_cust_main =
+ grep { $_->custnum == $p->{'previous_custnum'} }
+ _previous_cust_main( 'custnum' => $custnum,
+ 'username' => $cust_main->company,
+ 'with_payments' => 1,
+ );
+
+ my $previous_cust_main = $previous_cust_main[0];
+
+ #causes problems with old data w/old masking method
+ #if $previous_cust_main->paymask eq $payinfo;
+
+ if ( $p->{'payby'} =~ /^(CHEK|DCHK)$/ && $p->{'payinfo1'} =~ /x/i ) {
+ ( $p->{'payinfo1'}, $p->{'payinfo2'} ) =
+ split('@', $previous_cust_main->payinfo);
+ } elsif ( $p->{'payby'} =~ /^(CARD|DCRD)$/ && $p->{'payinfo'} =~ /x/i ) {
+ $p->{'payinfo'} = $previous_cust_main->payinfo;
+ }
+
+ FS::ClientAPI::MyAccount::process_payment($p);
+
+}
+
+sub previous_payment_info_renew_info {
+ my $p = shift;
+ my $renew_info = renew_info($p);
+ my $payment_info = previous_payment_info($p);
+ return { %$renew_info,
+ %$payment_info,
+ };
+}
+
+sub previous_process_payment_order_pkg {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_pkg($p);
+}
+
+sub previous_process_payment_change_pkg {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ change_pkg($p);
+}
+
+sub previous_process_payment_order_renew {
+ my $p = shift;
+
+ my $hr = previous_process_payment($p);
+ return $hr if $hr->{'error'};
+
+ order_renew($p);
+}
+
+1;
+
diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm
new file mode 100644
index 0000000..b9cd6c6
--- /dev/null
+++ b/FS/FS/ClientAPI/Signup.pm
@@ -0,0 +1,800 @@
+package FS::ClientAPI::Signup;
+
+use strict;
+use vars qw( $DEBUG $me );
+use Data::Dumper;
+use Tie::RefHash;
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs dbdef);
+use FS::CGI qw(popurl);
+use FS::Msgcat qw(gettext);
+use FS::Misc qw(card_types);
+use FS::ClientAPI_SessionCache;
+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::svc_phone;
+use FS::acct_snarf;
+use FS::queue;
+use FS::reg_code;
+use FS::payby;
+
+$DEBUG = 0;
+$me = '[FS::ClientAPI::Signup]';
+
+sub signup_info {
+ my $packet = shift;
+
+ warn "$me signup_info called on $packet\n" if $DEBUG;
+
+ my $conf = new FS::Conf;
+ my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
+
+ my $cache = new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::Signup',
+ } );
+ my $signup_info_cache = $cache->get('signup_info_cache');
+
+ if ( $signup_info_cache ) {
+
+ warn "$me loading cached signup info\n" if $DEBUG > 1;
+
+ } else {
+
+ warn "$me populating signup info cache\n" if $DEBUG > 1;
+
+ my $agentnum2part_pkg =
+ {
+ map {
+ my $agent = $_;
+ my $href = $agent->pkgpart_hashref;
+ $agent->agentnum =>
+ [
+ map { { 'payby' => [ $_->payby ],
+ 'freq_pretty' => $_->freq_pretty,
+ 'options' => { $_->options },
+ %{$_->hashref}
+ } }
+ grep { $_->svcpart($svc_x)
+ && ( $href->{ $_->pkgpart }
+ || ( $_->agentnum
+ && $_->agentnum == $agent->agentnum
+ )
+ )
+ }
+ qsearch( 'part_pkg', { 'disabled' => '' } )
+ ];
+ } qsearch('agent', { 'disabled' => '' })
+ };
+
+ my $msgcat = { map { $_=>gettext($_) }
+ qw( passwords_dont_match invalid_card unknown_card_type
+ not_a empty_password illegal_or_empty_text )
+ };
+ warn "msgcat: ". Dumper($msgcat). "\n" if $DEBUG > 2;
+
+ my $label = { map { $_ => FS::Msgcat::_gettext($_) }
+ qw( stateid stateid_state )
+ };
+ warn "label: ". Dumper($label). "\n" if $DEBUG > 2;
+
+ my @agent_fields = qw( agentnum agent );
+
+ $signup_info_cache = {
+ 'cust_main_county' => [ map $_->hashref,
+ qsearch('cust_main_county', {} )
+ ],
+
+ 'agent' => [ map { my $agent = $_;
+ map { $_ => $agent->get($_) } @agent_fields;
+ }
+ qsearch('agent', { 'disabled' => '' } )
+ ],
+
+ 'part_referral' => [ map $_->hashref,
+ qsearch('part_referral', { 'disabled' => '' } )
+ ],
+
+ 'agentnum2part_pkg' => $agentnum2part_pkg,
+
+ 'svc_acct_pop' => [ map $_->hashref, qsearch('svc_acct_pop',{} ) ],
+
+ 'emailinvoiceonly' => $conf->exists('emailinvoiceonly'),
+
+ 'security_phrase' => $conf->exists('security_phrase'),
+
+ 'nomadix' => $conf->exists('signup_server-nomadix'),
+
+ 'payby' => [ $conf->config('signup_server-payby') ],
+
+ 'card_types' => card_types(),
+
+ 'paytypes' => [ @FS::cust_main::paytypes ],
+
+ 'cvv_enabled' => 1,
+
+ 'stateid_enabled' => $conf->exists('show_stateid'),
+
+ 'paystate_enabled' => $conf->exists('show_bankstate'),
+
+ 'ship_enabled' => 1,
+
+ 'msgcat' => $msgcat,
+
+ 'label' => $label,
+
+ 'statedefault' => scalar($conf->config('statedefault')) || 'CA',
+
+ 'countrydefault' => scalar($conf->config('countrydefault')) || 'US',
+
+ 'refnum' => scalar($conf->config('signup_server-default_refnum')),
+
+ 'default_pkgpart' => scalar($conf->config('signup_server-default_pkgpart')),
+
+ 'signup_service' => $svc_x,
+ 'default_svcpart' => scalar($conf->config('signup_server-default_svcpart')),
+
+ 'head' => join("\n", $conf->config('selfservice-head') ),
+ 'body_header' => join("\n", $conf->config('selfservice-body_header') ),
+ 'body_footer' => join("\n", $conf->config('selfservice-body_footer') ),
+ 'body_bgcolor' => scalar( $conf->config('selfservice-body_bgcolor') ),
+ 'box_bgcolor' => scalar( $conf->config('selfservice-box_bgcolor') ),
+
+ 'company_name' => scalar($conf->config('company_name')),
+
+ #per-agent?
+ 'agent_ship_address' => scalar($conf->exists('agent-ship_address')),
+
+ 'no_company' => scalar($conf->exists('signup-no_company')),
+ 'require_phone' => scalar($conf->exists('cust_main-require_phone')),
+ 'recommend_daytime' => scalar($conf->exists('signup-recommend_daytime')),
+ 'recommend_email' => scalar($conf->exists('signup-recommend_email')),
+
+ };
+
+ $cache->set('signup_info_cache', $signup_info_cache);
+
+ }
+
+ my $signup_info = { %$signup_info_cache };
+ warn "$me signup info loaded\n" if $DEBUG > 1;
+ warn Dumper($signup_info). "\n" if $DEBUG > 2;
+
+ my @addl = qw( signup_server-classnum2 signup_server-classnum3 );
+
+ if ( grep { $conf->exists($_) } @addl ) {
+
+ $signup_info->{optional_packages} = [];
+
+ foreach my $addl ( @addl ) {
+
+ warn "$me adding optional package info\n" if $DEBUG > 1;
+
+ my $classnum = $conf->config($addl) or next;
+
+ my @pkgs = map { {
+ 'freq_pretty' => $_->freq_pretty,
+ 'options' => { $_->options },
+ %{ $_->hashref }
+ };
+ }
+ qsearch( 'part_pkg', { classnum => $classnum } );
+
+ push @{$signup_info->{optional_packages}}, \@pkgs;
+
+ warn "$me done adding opt. package info for $classnum\n" if $DEBUG > 1;
+
+ }
+
+ }
+
+ my $agentnum = $packet->{'agentnum'}
+ || $conf->config('signup_server-default_agentnum');
+ $agentnum =~ /^(\d*)$/ or die "illegal agentnum";
+ $agentnum = $1;
+
+ my $session = '';
+ if ( exists $packet->{'session_id'} ) {
+
+ warn "$me loading agent session\n" if $DEBUG > 1;
+ my $cache = new FS::ClientAPI_SessionCache( {
+ '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
+ }
+ warn "$me done loading agent session\n" if $DEBUG > 1;
+
+ } elsif ( exists $packet->{'customer_session_id'} ) {
+
+ warn "$me loading customer session\n" if $DEBUG > 1;
+ my $cache = new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+ } );
+ $session = $cache->get($packet->{'customer_session_id'});
+ if ( $session ) {
+ my $custnum = $session->{'custnum'};
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum });
+ return { 'error' => "Can't find your customer record" } unless $cust_main;
+ $agentnum = $cust_main->agentnum;
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+ warn "$me done loading customer session\n" if $DEBUG > 1;
+
+ }
+
+ $signup_info->{'part_pkg'} = [];
+
+ if ( $packet->{'reg_code'} ) {
+
+ warn "$me setting package list via reg_code\n" if $DEBUG > 1;
+
+ $signup_info->{'part_pkg'} =
+ [ map { { 'payby' => [ $_->payby ],
+ 'freq_pretty' => $_->freq_pretty,
+ 'options' => { $_->options },
+ %{$_->hashref}
+ };
+ }
+ grep { $_->svcpart($svc_x) }
+ map { $_->part_pkg }
+ qsearchs( 'reg_code', { 'code' => $packet->{'reg_code'},
+ 'agentnum' => $agentnum, } )
+
+ ];
+
+ $signup_info->{'error'} = 'Unknown registration code'
+ unless @{ $signup_info->{'part_pkg'} };
+
+ warn "$me done setting package list via reg_code\n" if $DEBUG > 1;
+
+ } elsif ( $packet->{'promo_code'} ) {
+
+ warn "$me setting package list via promo_code\n" if $DEBUG > 1;
+
+ $signup_info->{'part_pkg'} =
+ [ map { { 'payby' => [ $_->payby ],
+ 'freq_pretty' => $_->freq_pretty,
+ 'options' => { $_->options },
+ %{$_->hashref}
+ } }
+ grep { $_->svcpart($svc_x) }
+ qsearch( 'part_pkg', { 'promo_code' => {
+ op=>'ILIKE',
+ value=>$packet->{'promo_code'}
+ },
+ 'disabled' => '', } )
+ ];
+
+ $signup_info->{'error'} = 'Unknown promotional code'
+ unless @{ $signup_info->{'part_pkg'} };
+
+ warn "$me done setting package list via promo_code\n" if $DEBUG > 1;
+ }
+
+ if ( $agentnum ) {
+
+ warn "$me setting agent-specific payment flag\n" if $DEBUG > 1;
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ warn "$me has agent $agent\n" if $DEBUG > 1;
+ if ( $agent ) { #else complain loudly?
+ $signup_info->{'hide_payment_fields'} = [];
+ foreach my $payby (@{$signup_info->{payby}}) {
+ warn "$me checking $payby payment fields\n" if $DEBUG > 1;
+ my $hide = 0;
+ if ( FS::payby->realtime($payby) ) {
+ my $payment_gateway =
+ $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby),
+ 'nofatal' => 1,
+ );
+ if ( $payment_gateway
+ && $payment_gateway->gateway_namespace
+ eq 'Business::OnlineThirdPartyPayment'
+ ) {
+ warn "$me hiding $payby payment fields\n" if $DEBUG > 1;
+ $hide = 1;
+ }
+ }
+ push @{$signup_info->{'hide_payment_fields'}}, $hide;
+ }
+ }
+ warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1;
+
+ warn "$me setting agent-specific package list\n" if $DEBUG > 1;
+ $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
+ unless @{ $signup_info->{'part_pkg'} };
+ warn "$me done setting agent-specific package list\n" if $DEBUG > 1;
+
+ warn "$me setting agent-specific adv. source list\n" if $DEBUG > 1;
+ $signup_info->{'part_referral'} =
+ [
+ map { $_->hashref }
+ qsearch( {
+ 'table' => 'part_referral',
+ 'hashref' => { 'disabled' => '' },
+ 'extra_sql' => "AND ( agentnum = $agentnum ".
+ " OR agentnum IS NULL ) ",
+ },
+ )
+ ];
+ warn "$me done setting agent-specific adv. source list\n" if $DEBUG > 1;
+
+ $signup_info->{'agent_name'} = $agent->agent;
+
+ $signup_info->{'company_name'} = $conf->config('company_name', $agentnum);
+
+ if ( $signup_info->{'agent_ship_address'} && $agent->agent_custnum ) {
+ my $cust_main = $agent->agent_cust_main;
+ my $prefix = length($cust_main->ship_last) ? 'ship_' : '';
+ $signup_info->{"ship_$_"} = $cust_main->get("$prefix$_")
+ foreach qw( address1 city county state zip country );
+ }
+
+ #some of the above could probably be cached, too
+
+ my $signup_info_cache_agent = $cache->get("signup_info_cache_agent$agentnum");
+
+ if ( $signup_info_cache_agent ) {
+
+ warn "$me loading cached signup info for agentnum $agentnum\n"
+ if $DEBUG > 1;
+
+ } else {
+
+ warn "$me populating signup info cache for agentnum $agentnum\n"
+ if $DEBUG > 1;
+
+ $signup_info_cache_agent = {
+ #( map { $_ => scalar( $conf->config($_, $agentnum) ) }
+ # qw( company_name ) ),
+ ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) }
+ qw( body_bgcolor box_bgcolor) ),
+ ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) }
+ qw( head body_header body_footer ) ),
+ };
+
+ $cache->set("signup_info_cache_agent$agentnum", $signup_info_cache_agent);
+
+ }
+
+ $signup_info->{$_} = $signup_info_cache_agent->{$_}
+ foreach keys %$signup_info_cache_agent;
+
+ }
+ # else {
+ # delete $signup_info->{'part_pkg'};
+ #}
+
+ warn "$me sorting package list\n" if $DEBUG > 1;
+ $signup_info->{'part_pkg'} = [ sort { $a->{pkg} cmp $b->{pkg} } # case?
+ @{ $signup_info->{'part_pkg'} }
+ ];
+ warn "$me done sorting package list\n" if $DEBUG > 1;
+
+ if ( exists $packet->{'session_id'} ) {
+ 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 domain_select_hash {
+ my $packet = shift;
+
+ my $response = {};
+
+ if ($packet->{pkgpart}) {
+ my $part_pkg = qsearchs('part_pkg' => { 'pkgpart' => $packet->{pkgpart} } );
+ #$packet->{svcpart} = $part_pkg->svcpart('svc_acct')
+ $packet->{svcpart} = $part_pkg->svcpart
+ if $part_pkg;
+ }
+
+ if ($packet->{svcpart}) {
+ my $part_svc = qsearchs('part_svc' => { 'svcpart' => $packet->{svcpart} } );
+ $response->{'domsvc'} = $part_svc->part_svc_column('domsvc')->columnvalue
+ if ($part_svc && $part_svc->part_svc_column('domsvc')->columnflag eq 'D');
+ }
+
+ $response->{'domains'}
+ = { domain_select_hash FS::svc_acct( map { $_ => $packet->{$_} }
+ qw(svcpart pkgnum)
+ ) };
+
+ $response;
+}
+
+sub new_customer {
+ my $packet = shift;
+
+ my $conf = new FS::Conf;
+ my $svc_x = $conf->config('signup_server-service') || 'svc_acct';
+
+ if ( $svc_x eq 'svc_acct' ) {
+
+ #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 FS::ClientAPI_SessionCache( {
+ '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 stateid stateid_state
+
+ ship_last ship_first ship_ss ship_company ship_address1 ship_address2
+ ship_city ship_county ship_state ship_zip ship_country
+ ship_daytime ship_night ship_fax
+
+ payby
+ payinfo paycvv paydate payname paystate paytype
+ paystart_month paystart_year payissue
+ payip
+
+ referral_custnum comments
+ )
+
+ } );
+
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ if ( $conf->exists('agent_ship_address') && $agent->agent_custnum ) {
+ my $agent_cust_main = $agent->agent_cust_main;
+ my $prefix = length($agent_cust_main->ship_last) ? 'ship_' : '';
+ $cust_main->set("ship_$_", $agent_cust_main->get("$prefix$_") )
+ foreach qw( address1 city county state zip country );
+
+ $cust_main->set("ship_$_", $cust_main->get($_))
+ foreach qw( last first );
+
+ }
+
+
+ return { 'error' => "Illegal payment type" }
+ unless grep { $_ eq $packet->{'payby'} }
+ $conf->config('signup_server-payby');
+
+ if (FS::payby->realtime($packet->{payby})) {
+ my $payby = $packet->{payby};
+
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
+ return { 'error' => "Unknown reseller" }
+ unless $agent;
+
+ my $gw = $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby),
+ 'nofatal' => 1,
+ );
+
+ $cust_main->payby('BILL') # MCRD better?
+ if $gw && $gw->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+ }
+
+ $cust_main->payinfo($cust_main->daytime)
+ if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
+
+ my @invoicing_list = $packet->{'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_x);
+
+ my $reg_code = '';
+ if ( $packet->{'reg_code'} ) {
+ $reg_code = qsearchs( 'reg_code', { 'code' => $packet->{'reg_code'},
+ 'agentnum' => $agentnum, } )
+ or return { 'error' => 'Unknown registration code' };
+ }
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ #later#'custnum' => $custnum,
+ 'pkgpart' => $packet->{'pkgpart'},
+ 'promo_code' => $packet->{'promo_code'},
+ 'reg_code' => $packet->{'reg_code'},
+ } );
+ #my $error = $cust_pkg->check;
+ #return { 'error' => $error } if $error;
+
+ #should be all auto-magic and shit
+ my @svc = ();
+ if ( $svc_x eq 'svc_acct' ) {
+
+ my $svc = 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->child_objects( \@acct_snarf );
+
+ push @svc, $svc;
+
+ } elsif ( $svc_x eq 'svc_phone' ) {
+
+ my $svc = new FS::svc_phone ( {
+ 'svcpart' => $svcpart,
+ map { $_ => $packet->{$_} }
+ qw( countrycode phonenum sip_password pin ),
+ } );
+
+ push @svc, $svc;
+
+ } else {
+ die "unknown signup service $svc_x";
+ }
+ my $y = $svc[0]->setdefault; # arguably should be in new method
+ return { 'error' => $y } if $y && !ref($y);
+
+ if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts'))
+ {
+
+ my %mac_addr_svcparts = map { $_ => 1 }
+ $conf->config('signup_server-mac_addr_svcparts');
+ my @pkg_svc = grep { $_->quantity && $mac_addr_svcparts{$_->svcpart} }
+ $cust_pkg->part_pkg->pkg_svc;
+
+ return { 'error' => 'No service defined to assign mac address' }
+ unless @pkg_svc;
+
+ my $svc = new FS::svc_acct {
+ 'svcpart' => $pkg_svc[0]->svcpart, #multiple matches? alas..
+ 'username' => $packet->{'mac_addr'},
+ '_password' => '', #blank as requested (set passwordmin to 0)
+ };
+
+ my $y = $svc->setdefault; # arguably should be in new method
+ return { 'error' => $y } if $y && !ref($y);
+
+ push @svc, $svc;
+
+ }
+
+ #$error = $svc->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',
+ } );
+ my $error = $placeholder->insert;
+ return { 'error' => $error } if $error;
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => \@svc );
+ #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;
+
+ $bill_error = $cust_main->apply_payments_and_credits;
+ #warn "[fs_signup_server] error applying payments and credits for".
+ # " new customer: $bill_error"
+ # if $bill_error;
+
+ if ($cust_main->_new_bop_required()) {
+ $bill_error = $cust_main->realtime_collect(
+ method => FS::payby->payby2bop( $packet->{payby} ),
+ depend_jobnum => $placeholder->jobnum,
+ );
+ } else {
+ $bill_error = $cust_main->collect('realtime' => 1);
+ }
+ #warn "[fs_signup_server] error collecting from new customer: $bill_error"
+ # if $bill_error;
+
+ if ($bill_error && ref($bill_error) eq 'HASH') {
+ return { 'error' => '_collect',
+ ( map { $_ => $bill_error->{$_} }
+ qw(popup_url reference collectitems)
+ ),
+ amount => $cust_main->balance,
+ };
+ }
+
+ if ( $cust_main->balance > 0 ) {
+
+ #this makes sense. credit is "un-doing" the invoice
+ $cust_main->credit( $cust_main->balance, 'signup server decline',
+ 'reason_type' => $conf->config('signup_credit_type'),
+ );
+ $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' };
+ }
+
+ }
+
+ if ( $reg_code ) {
+ $error = $reg_code->delete;
+ return { 'error' => $error } if $error;
+ }
+
+ $error = $placeholder->delete;
+ return { 'error' => $error } if $error;
+
+ my %return = ( 'error' => '',
+ 'signup_service' => $svc_x,
+ );
+
+ if ( $svc_x eq 'svc_acct' ) {
+ $return{$_} = $svc[0]->$_() for qw( username _password );
+ } elsif ( $svc_x eq 'svc_phone' ) {
+ $return{$_} = $svc[0]->$_() for qw( countrycode phonenum sip_password pin );
+ } else {
+ die "unknown signup service $svc_x";
+ }
+
+ return \%return;
+
+}
+
+sub capture_payment {
+ my $packet = shift;
+
+ warn "$me capture_payment called on $packet\n" if $DEBUG;
+
+ ###
+ # identify processor/gateway from called back URL
+ ###
+
+ my $conf = new FS::Conf;
+
+ my $url = $packet->{url};
+ my $payment_gateway =
+ qsearchs('payment_gateway', { 'gateway_callback_url' => popurl(0, $url) } );
+
+ unless ($payment_gateway) {
+
+ my ( $processor, $login, $password, $action, @bop_options ) =
+ $conf->config('business-onlinepayment');
+ $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;
+
+ $payment_gateway = new FS::payment_gateway( {
+ gateway_namespace => $conf->config('business-onlinepayment-namespace'),
+ gateway_module => $processor,
+ gateway_username => $login,
+ gateway_password => $password,
+ gateway_action => $action,
+ options => [ ( @bop_options ) ],
+ });
+
+ }
+
+ die "No real-time third party processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n*"
+ unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+
+ ###
+ # locate pending transaction
+ ###
+
+ eval "use Business::OnlineThirdPartyPayment";
+ die $@ if $@;
+
+ my $transaction =
+ new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
+ @{ [ $payment_gateway->options ] },
+ );
+
+ my $paypendingnum = $transaction->reference($packet->{data});
+
+ my $cust_pay_pending =
+ qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } );
+
+ unless ($cust_pay_pending) {
+ my $bill_error = "No payment is being processed with id $paypendingnum".
+ "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ if ($cust_pay_pending->status ne 'pending') {
+ my $bill_error = "Payment with id $paypendingnum is not pending, but ".
+ $cust_pay_pending->status. "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ my $cust_main = $cust_pay_pending->cust_main;
+ my $bill_error =
+ $cust_main->realtime_botpp_capture( $cust_pay_pending, %{$packet->{data}} );
+
+ return { 'error' => ( $bill_error->{bill_error} ? '_decline' : '' ),
+ %$bill_error,
+ };
+
+}
+
+1;
diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm
new file mode 100644
index 0000000..b22d761
--- /dev/null
+++ b/FS/FS/ClientAPI/passwd.pm
@@ -0,0 +1,46 @@
+package FS::ClientAPI::passwd;
+
+use strict;
+use FS::Record qw(qsearchs);
+use FS::svc_acct;
+use FS::svc_domain;
+
+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/ClientAPI_SessionCache.pm b/FS/FS/ClientAPI_SessionCache.pm
new file mode 100644
index 0000000..d72fb39
--- /dev/null
+++ b/FS/FS/ClientAPI_SessionCache.pm
@@ -0,0 +1,79 @@
+package FS::ClientAPI_SessionCache;
+
+use strict;
+use vars qw($module);
+use FS::UID qw(datasrc);
+use FS::Conf;
+
+#ask FS::UID to run this stuff for us later
+install_callback FS::UID sub {
+ my $conf = new FS::Conf;
+ $module = $conf->config('selfservice_server-cache_module')
+ || 'Cache::FileCache';
+};
+
+=head1 NAME
+
+FS::ClientAPI_SessionCache;
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+Minimal Cache::Cache-alike interface for storing session cache information.
+Backends to Cache::SharedMemoryCache, Cache::FileCache, or an internal
+implementation which stores information in the clientapi_session and
+clientapi_session_field database tables.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ unless ( $module =~ /^_Database$/ ) {
+ eval "use $module;";
+ die $@ if $@;
+ my $self = $module->new(@_);
+ $self->set_cache_root('%%%FREESIDE_CACHE%%%/clientapi_session.'.datasrc)
+ if $module =~ /^Cache::FileCache$/;
+ $self;
+ } else {
+ my $self = shift;
+ bless ($self, $class);
+ }
+}
+
+sub get {
+ my($self, $session_id) = @_;
+ die '_Database self-service session cache not yet implemented';
+}
+
+sub set {
+ my($self, $session_id, $session, $expiration) = @_;
+ die '_Database self-service session cache not yet implemented';
+}
+
+sub remove {
+ my($self, $session_id) = @_;
+ die '_Database self-service session cache not yet implemented';
+}
+
+=back
+
+=head1 BUGS
+
+Minimal documentation.
+
+=head1 SEE ALSO
+
+L<Cache::Cache>, L<FS::clientapi_session>, L<FS::clientapi_session_field>
+
+=cut
+
+1;
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
new file mode 100644
index 0000000..a1ee23c
--- /dev/null
+++ b/FS/FS/Conf.pm
@@ -0,0 +1,3651 @@
+package FS::Conf;
+
+use vars qw($base_dir @config_items @base_items @card_types $DEBUG);
+use Carp;
+use IO::File;
+use File::Basename;
+use MIME::Base64;
+use FS::ConfItem;
+use FS::ConfDefaults;
+use FS::Conf_compat17;
+use FS::payby;
+use FS::conf;
+use FS::Record qw(qsearch qsearchs);
+use FS::UID qw(dbh datasrc use_confcompat);
+
+$base_dir = '%%%FREESIDE_CONF%%%';
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::Conf - Freeside configuration values
+
+=head1 SYNOPSIS
+
+ use FS::Conf;
+
+ $conf = new FS::Conf;
+
+ $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
+
+Create a new configuration object.
+
+=cut
+
+sub new {
+ my($proto) = @_;
+ my($class) = ref($proto) || $proto;
+ my($self) = { 'base_dir' => $base_dir };
+ bless ($self, $class);
+}
+
+=item base_dir
+
+Returns the base directory. By default this is /usr/local/etc/freeside.
+
+=cut
+
+sub base_dir {
+ my($self) = @_;
+ my $base_dir = $self->{base_dir};
+ -e $base_dir or die "FATAL: $base_dir doesn't exist!";
+ -d $base_dir or die "FATAL: $base_dir isn't a directory!";
+ -r $base_dir or die "FATAL: Can't read $base_dir!";
+ -x $base_dir or die "FATAL: $base_dir not searchable (executable)!";
+ $base_dir =~ /^(.*)$/;
+ $1;
+}
+
+=item conf KEY [ AGENTNUM [ NODEFAULT ] ]
+
+Returns the L<FS::conf> record for the key and agent.
+
+=cut
+
+sub conf {
+ my $self = shift;
+ $self->_config(@_);
+}
+
+=item config KEY [ AGENTNUM [ NODEFAULT ] ]
+
+Returns the configuration value or values (depending on context) for key.
+The optional agent number selects an agent specific value instead of the
+global default if one is present. If NODEFAULT is true only the agent
+specific value(s) is returned.
+
+=cut
+
+sub _usecompat {
+ my ($self, $method) = (shift, shift);
+ carp "NO CONFIGURATION RECORDS FOUND -- USING COMPATIBILITY MODE"
+ if use_confcompat;
+ my $compat = new FS::Conf_compat17 ("$base_dir/conf." . datasrc);
+ $compat->$method(@_);
+}
+
+sub _config {
+ my($self,$name,$agentnum,$agentonly)=@_;
+ my $hashref = { 'name' => $name };
+ $hashref->{agentnum} = $agentnum;
+ local $FS::Record::conf = undef; # XXX evil hack prevents recursion
+ my $cv = FS::Record::qsearchs('conf', $hashref);
+ if (!$agentonly && !$cv && defined($agentnum) && $agentnum) {
+ $hashref->{agentnum} = '';
+ $cv = FS::Record::qsearchs('conf', $hashref);
+ }
+ return $cv;
+}
+
+sub config {
+ my $self = shift;
+ return $self->_usecompat('config', @_) if use_confcompat;
+
+ carp "FS::Conf->config(". join(', ', @_). ") called"
+ if $DEBUG > 1;
+
+ my $cv = $self->_config(@_) or return;
+
+ if ( wantarray ) {
+ my $v = $cv->value;
+ chomp $v;
+ (split "\n", $v, -1);
+ } else {
+ (split("\n", $cv->value))[0];
+ }
+}
+
+=item config_binary KEY [ AGENTNUM [ NODEFAULT ] ]
+
+Returns the exact scalar value for key.
+
+=cut
+
+sub config_binary {
+ my $self = shift;
+ return $self->_usecompat('config_binary', @_) if use_confcompat;
+
+ my $cv = $self->_config(@_) or return;
+ length($cv->value) ? decode_base64($cv->value) : '';
+}
+
+=item exists KEY [ AGENTNUM [ NODEFAULT ] ]
+
+Returns true if the specified key exists, even if the corresponding value
+is undefined.
+
+=cut
+
+sub exists {
+ my $self = shift;
+ return $self->_usecompat('exists', @_) if use_confcompat;
+
+ my($name, $agentnum)=@_;
+
+ carp "FS::Conf->exists(". join(', ', @_). ") called"
+ if $DEBUG > 1;
+
+ defined($self->_config(@_));
+}
+
+=item config_orbase KEY SUFFIX
+
+Returns the configuration value or values (depending on context) for
+KEY_SUFFIX, if it exists, otherwise for KEY
+
+=cut
+
+# outmoded as soon as we shift to agentnum based config values
+# well, mostly. still useful for e.g. late notices, etc. in that we want
+# these to fall back to standard values
+sub config_orbase {
+ my $self = shift;
+ return $self->_usecompat('config_orbase', @_) if use_confcompat;
+
+ my( $name, $suffix ) = @_;
+ if ( $self->exists("${name}_$suffix") ) {
+ $self->config("${name}_$suffix");
+ } else {
+ $self->config($name);
+ }
+}
+
+=item key_orbase KEY SUFFIX
+
+If the config value KEY_SUFFIX exists, returns KEY_SUFFIX, otherwise returns
+KEY. Useful for determining which exact configuration option is returned by
+config_orbase.
+
+=cut
+
+sub key_orbase {
+ my $self = shift;
+ #no compat for this...return $self->_usecompat('config_orbase', @_) if use_confcompat;
+
+ my( $name, $suffix ) = @_;
+ if ( $self->exists("${name}_$suffix") ) {
+ "${name}_$suffix";
+ } else {
+ $name;
+ }
+}
+
+=item invoice_templatenames
+
+Returns all possible invoice template names.
+
+=cut
+
+sub invoice_templatenames {
+ my( $self ) = @_;
+
+ my %templatenames = ();
+ foreach my $item ( $self->config_items ) {
+ foreach my $base ( @base_items ) {
+ my( $main, $ext) = split(/\./, $base);
+ $ext = ".$ext" if $ext;
+ if ( $item->key =~ /^${main}_(.+)$ext$/ ) {
+ $templatenames{$1}++;
+ }
+ }
+ }
+
+ map { $_ } #handle scalar context
+ sort keys %templatenames;
+
+}
+
+=item touch KEY [ AGENT ];
+
+Creates the specified configuration key if it does not exist.
+
+=cut
+
+sub touch {
+ my $self = shift;
+ return $self->_usecompat('touch', @_) if use_confcompat;
+
+ my($name, $agentnum) = @_;
+ unless ( $self->exists($name, $agentnum) ) {
+ $self->set($name, '', $agentnum);
+ }
+}
+
+=item set KEY VALUE [ AGENTNUM ];
+
+Sets the specified configuration key to the given value.
+
+=cut
+
+sub set {
+ my $self = shift;
+ return $self->_usecompat('set', @_) if use_confcompat;
+
+ my($name, $value, $agentnum) = @_;
+ $value =~ /^(.*)$/s;
+ $value = $1;
+
+ warn "[FS::Conf] SET $name\n" if $DEBUG;
+
+ my $old = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum});
+ my $new = new FS::conf { $old ? $old->hash
+ : ('name' => $name, 'agentnum' => $agentnum)
+ };
+ $new->value($value);
+
+ my $error;
+ if ($old) {
+ $error = $new->replace($old);
+ } else {
+ $error = $new->insert;
+ }
+
+ die "error setting configuration value: $error \n"
+ if $error;
+
+}
+
+=item set_binary KEY VALUE [ AGENTNUM ]
+
+Sets the specified configuration key to an exact scalar value which
+can be retrieved with config_binary.
+
+=cut
+
+sub set_binary {
+ my $self = shift;
+ return if use_confcompat;
+
+ my($name, $value, $agentnum)=@_;
+ $self->set($name, encode_base64($value), $agentnum);
+}
+
+=item delete KEY [ AGENTNUM ];
+
+Deletes the specified configuration key.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return $self->_usecompat('delete', @_) if use_confcompat;
+
+ my($name, $agentnum) = @_;
+ if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum}) ) {
+ warn "[FS::Conf] DELETE $name\n";
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $cv->delete;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die "error setting configuration value: $error \n"
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ }
+}
+
+=item import_config_item CONFITEM DIR
+
+ Imports the item specified by the CONFITEM (see L<FS::ConfItem>) into
+the database as a conf record (see L<FS::conf>). Imports from the file
+in the directory DIR.
+
+=cut
+
+sub import_config_item {
+ my ($self,$item,$dir) = @_;
+ my $key = $item->key;
+ if ( -e "$dir/$key" && ! use_confcompat ) {
+ warn "Inserting $key\n" if $DEBUG;
+ local $/;
+ my $value = readline(new IO::File "$dir/$key");
+ if ($item->type =~ /^(binary|image)$/ ) {
+ $self->set_binary($key, $value);
+ }else{
+ $self->set($key, $value);
+ }
+ }else {
+ warn "Not inserting $key\n" if $DEBUG;
+ }
+}
+
+=item verify_config_item CONFITEM DIR
+
+ Compares the item specified by the CONFITEM (see L<FS::ConfItem>) in
+the database to the legacy file value in DIR.
+
+=cut
+
+sub verify_config_item {
+ return '' if use_confcompat;
+ my ($self,$item,$dir) = @_;
+ my $key = $item->key;
+ my $type = $item->type;
+
+ my $compat = new FS::Conf_compat17 $dir;
+ my $error = '';
+
+ $error .= "$key fails existential comparison; "
+ if $self->exists($key) xor $compat->exists($key);
+
+ if ( $type !~ /^(binary|image)$/ ) {
+
+ {
+ no warnings;
+ $error .= "$key fails scalar comparison; "
+ unless scalar($self->config($key)) eq scalar($compat->config($key));
+ }
+
+ my (@new) = $self->config($key);
+ my (@old) = $compat->config($key);
+ unless ( scalar(@new) == scalar(@old)) {
+ $error .= "$key fails list comparison; ";
+ }else{
+ my $r=1;
+ foreach (@old) { $r=0 if ($_ cmp shift(@new)); }
+ $error .= "$key fails list comparison; "
+ unless $r;
+ }
+
+ } else {
+
+ $error .= "$key fails binary comparison; "
+ unless scalar($self->config_binary($key)) eq scalar($compat->config_binary($key));
+
+ }
+
+#remove deprecated config on our own terms, not freeside-upgrade's
+# if ($error =~ /existential comparison/ && $item->section eq 'deprecated') {
+# my $proto;
+# for ( @config_items ) { $proto = $_; last if $proto->key eq $key; }
+# unless ($proto->key eq $key) {
+# warn "removed config item $error\n" if $DEBUG;
+# $error = '';
+# }
+# }
+
+ $error;
+}
+
+#item _orbase_items OPTIONS
+#
+#Returns all of the possible extensible config items as FS::ConfItem objects.
+#See #L<FS::ConfItem>. OPTIONS consists of name value pairs. Possible
+#options include
+#
+# dir - the directory to search for configuration option files instead
+# of using the conf records in the database
+#
+#cut
+
+#quelle kludge
+sub _orbase_items {
+ my ($self, %opt) = @_;
+
+ my $listmaker = sub { my $v = shift;
+ $v =~ s/_/!_/g;
+ if ( $v =~ /\.(png|eps)$/ ) {
+ $v =~ s/\./!_%./;
+ }else{
+ $v .= '!_%';
+ }
+ map { $_->name }
+ FS::Record::qsearch( 'conf',
+ {},
+ '',
+ "WHERE name LIKE '$v' ESCAPE '!'"
+ );
+ };
+
+ if (exists($opt{dir}) && $opt{dir}) {
+ $listmaker = sub { my $v = shift;
+ if ( $v =~ /\.(png|eps)$/ ) {
+ $v =~ s/\./_*./;
+ }else{
+ $v .= '_*';
+ }
+ map { basename $_ } glob($opt{dir}. "/$v" );
+ };
+ }
+
+ ( map {
+ my $proto;
+ my $base = $_;
+ for ( @config_items ) { $proto = $_; last if $proto->key eq $base; }
+ die "don't know about $base items" unless $proto->key eq $base;
+
+ map { new FS::ConfItem {
+ 'key' => $_,
+ 'base_key' => $proto->key,
+ 'section' => $proto->section,
+ 'description' => 'Alternate ' . $proto->description . ' See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Invoice_templates">billing documentation</a> for details.',
+ 'type' => $proto->type,
+ };
+ } &$listmaker($base);
+ } @base_items,
+ );
+}
+
+=item config_items
+
+Returns all of the possible global/default configuration items as
+FS::ConfItem objects. See L<FS::ConfItem>.
+
+=cut
+
+sub config_items {
+ my $self = shift;
+ return $self->_usecompat('config_items', @_) if use_confcompat;
+
+ ( @config_items, $self->_orbase_items(@_) );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item init-config DIR
+
+Imports the configuration items from DIR (1.7 compatible)
+to conf records in the database.
+
+=cut
+
+sub init_config {
+ my $dir = shift;
+
+ {
+ local $FS::UID::use_confcompat = 0;
+ my $conf = new FS::Conf;
+ foreach my $item ( $conf->config_items(dir => $dir) ) {
+ $conf->import_config_item($item, $dir);
+ my $error = $conf->verify_config_item($item, $dir);
+ return $error if $error;
+ }
+
+ my $compat = new FS::Conf_compat17 $dir;
+ foreach my $item ( $compat->config_items ) {
+ my $error = $conf->verify_config_item($item, $dir);
+ return $error if $error;
+ }
+ }
+
+ $FS::UID::use_confcompat = 0;
+ ''; #success
+}
+
+=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).
+
+=cut
+
+#Business::CreditCard
+@card_types = (
+ "VISA card",
+ "MasterCard",
+ "Discover card",
+ "American Express card",
+ "Diner's Club/Carte Blanche",
+ "enRoute",
+ "JCB",
+ "BankCard",
+ "Switch",
+ "Solo",
+);
+
+@base_items = qw (
+ invoice_template
+ invoice_latex
+ invoice_latexreturnaddress
+ invoice_latexfooter
+ invoice_latexsmallfooter
+ invoice_latexnotes
+ invoice_latexcoupon
+ invoice_html
+ invoice_htmlreturnaddress
+ invoice_htmlfooter
+ invoice_htmlnotes
+ logo.png
+ logo.eps
+ );
+
+#Billing (81 items)
+#Invoicing (50 items)
+#UI (69 items)
+#Self-service (29 items)
+#...
+#Unclassified (77 items)
+
+
+@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' => 'alert_expiration',
+ 'section' => 'billing',
+ 'description' => 'Enable alerts about billing method expiration (i.e. expiring credit cards).',
+ 'type' => 'checkbox',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'alerter_template',
+ 'section' => 'billing',
+ 'description' => 'Template file for billing method expiration alerts (i.e. expiring credit cards). See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Credit_cards_and_Electronic_checks">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'apacheip',
+ #not actually deprecated yet
+ #'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',
+ 'section' => '',
+ 'description' => 'IP address to assign to new virtual hosts',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'encryption',
+ 'section' => 'billing',
+ 'description' => 'Enable encryption of credit cards.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'encryptionmodule',
+ 'section' => 'billing',
+ 'description' => 'Use which module for encryption?',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'encryptionpublickey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Public Key - Required if Encryption is turned on.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'encryptionprivatekey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Private Key - Including this will enable the "Bill Now" feature. However if the system is compromised, a hacker can use this key to decode the stored credit card information. This is generally not a good idea.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'billco-url',
+ 'section' => 'billing',
+ 'description' => 'The url to use for performing uploads to the invoice mailing service.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'billco-username',
+ 'section' => 'billing',
+ 'description' => 'The login name to use for uploads to the invoice mailing service.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ 'agentonly' => 1,
+ },
+
+ {
+ 'key' => 'billco-password',
+ 'section' => 'billing',
+ 'description' => 'The password to use for uploads to the invoice mailing service.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ 'agentonly' => 1,
+ },
+
+ {
+ 'key' => 'billco-clicode',
+ 'section' => 'billing',
+ 'description' => 'The clicode to use for uploads to the invoice mailing service.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ '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-namespace',
+ 'section' => 'billing',
+ 'description' => 'Specifies which perl module namespace (which group of collection routines) is used by default.',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'Business::OnlinePayment' => 'Direct API (Business::OnlinePayment)',
+ 'Business::OnlineThirdPartyPayment' => 'Web API (Business::ThirdPartyPayment)',
+ ],
+ },
+
+ {
+ '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 - not available in all situations)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'business-onlinepayment-email-override',
+ 'section' => 'billing',
+ 'description' => 'Email address used instead of customer email address when submitting a BOP transaction.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'business-onlinepayment-email_customer',
+ 'section' => 'billing',
+ 'description' => 'Controls the "email_customer" flag used by some Business::OnlinePayment processors to enable customer receipts.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'countrydefault',
+ 'section' => 'UI',
+ 'description' => 'Default two-letter country code (if not supplied, the default is `US\')',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'date_format',
+ 'section' => 'UI',
+ 'description' => 'Format for displaying dates',
+ 'type' => 'select',
+ 'select_hash' => [
+ '%m/%d/%Y' => 'MM/DD/YYYY',
+ '%d/%m/%Y' => 'DD/MM/YYYY',
+ '%Y/%m/%d' => 'YYYY/MM/DD',
+ ],
+ },
+
+ {
+ 'key' => 'deletecustomers',
+ 'section' => 'UI',
+ 'description' => 'Enable customer deletions. Be very careful! Deleting a customer will remove all traces that the 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' => 'deleteinvoices',
+ 'section' => 'UI',
+ 'description' => 'Enable invoices deletions. Be very careful! Deleting an invoice will remove all traces that the invoice ever existed! Normally, you would apply a credit against the invoice instead.', #invoice voiding?
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'deletepayments',
+ 'section' => 'billing',
+ 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. 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',
+ #not actually deprecated yet
+ #'section' => 'deprecated',
+ #'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to 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.',
+ 'section' => '',
+ 'description' => 'One or more comma-separated email addresses to be notified when a credit is deleted.',
+ 'type' => [qw( checkbox text )],
+ },
+
+ {
+ 'key' => 'deleterefunds',
+ 'section' => 'billing',
+ 'description' => 'Enable deletion of unclosed refunds. Be very careful! Only delete refunds that were data-entry errors, not adjustments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unapplypayments',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unapplycredits',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to nable "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_cust_attachment',
+ 'section' => '',
+ 'description' => 'Disable customer file attachments',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'max_attachment_size',
+ 'section' => '',
+ 'description' => 'Maximum size for customer file attachments (leave blank for unlimited)',
+ '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' => 'invoicing',
+ 'description' => 'Disables postal mail invoices',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'disablepostalinvoicedefault',
+ 'section' => 'invoicing',
+ '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' => 'invoicing',
+ 'description' => 'Automatically adds new accounts to the email invoice list',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'emailinvoiceautoalways',
+ 'section' => 'invoicing',
+ 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'emailinvoice-apostrophe',
+ 'section' => 'invoicing',
+ 'description' => 'Allows the apostrophe (single quote) character in the email addresses in 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' => 'auto_router',
+ 'section' => '',
+ 'description' => 'Automatically choose the correct router/block based on supplied ip address when possible while provisioning broadband services',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => 'shell',
+ 'description' => 'For new users, prefixed to username to create a directory name. Should have a leading but not a trailing slash.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'invoice_from',
+ 'section' => 'required',
+ 'description' => 'Return address on email invoices',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_subject',
+ 'section' => 'invoicing',
+ 'description' => 'Subject: header on email invoices. Defaults to "Invoice". The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_usesummary',
+ 'section' => 'invoicing',
+ 'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice_template',
+ 'section' => 'invoicing',
+ 'description' => 'Text template file for invoices. Used if no invoice_html template is defined, and also seen by users using non-HTML capable mail clients. See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Plaintext_invoice_templates">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_html',
+ 'section' => 'invoicing',
+ 'description' => 'Optional HTML template for invoices. See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#HTML_invoice_templates">billing documentation</a> for details.',
+
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_htmlnotes',
+ 'section' => 'invoicing',
+ 'description' => 'Notes section for HTML invoices. Defaults to the same data in invoice_latexnotes if not specified.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_htmlfooter',
+ 'section' => 'invoicing',
+ 'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_htmlsummary',
+ 'section' => 'invoicing',
+ 'description' => 'Summary initial page for HTML invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_htmlreturnaddress',
+ 'section' => 'invoicing',
+ 'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latex',
+ 'section' => 'invoicing',
+ 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexnotes',
+ 'section' => 'invoicing',
+ 'description' => 'Notes section for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_latexfooter',
+ 'section' => 'invoicing',
+ 'description' => 'Footer for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_latexsummary',
+ 'section' => 'invoicing',
+ 'description' => 'Summary initial page for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_latexcoupon',
+ 'section' => 'invoicing',
+ 'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_latexreturnaddress',
+ 'section' => 'invoicing',
+ 'description' => 'Return address for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexsmallfooter',
+ 'section' => 'invoicing',
+ 'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'invoice_email_pdf',
+ 'section' => 'invoicing',
+ 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
+ 'type' => 'checkbox'
+ },
+
+ {
+ 'key' => 'invoice_email_pdf_note',
+ 'section' => 'invoicing',
+ 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.',
+ 'type' => 'textarea'
+ },
+
+
+ {
+ 'key' => 'invoice_default_terms',
+ 'section' => 'invoicing',
+ '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 20', 'Net 30', 'Net 45', 'Net 60' ],
+ },
+
+ {
+ 'key' => 'invoice_show_prior_due_date',
+ 'section' => 'invoicing',
+ 'description' => 'Show previous invoice due dates when showing prior balances. Default is to show invoice date.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice_include_aging',
+ 'section' => 'invoicing',
+ 'description' => 'Show an aging line after the prior balance section. Only valud when invoice_sections is enabled.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice_sections',
+ 'section' => 'invoicing',
+ 'description' => 'Split invoice into sections and label according to package category when enabled.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'usage_class_as_a_section',
+ 'section' => 'invoicing',
+ 'description' => 'Split usage into sections and label according to usage class name when enabled. Only valid when invoice_sections is enabled.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_phone_sections',
+ 'section' => 'invoicing',
+ 'description' => 'Create a section for each svc_phone when enabled. Only valid when invoice_sections is enabled.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'finance_pkgclass',
+ 'section' => 'billing',
+ 'description' => 'The default package class for late fee charges, used if the fee event does not specify a package class itself.',
+ 'type' => 'select-pkg_class',
+ },
+
+ {
+ 'key' => 'separate_usage',
+ 'section' => 'invoicing',
+ 'description' => 'Split the rated call usage into a separate line from the recurring charges.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice_send_receipts',
+ 'section' => 'deprecated',
+ '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. Payment receipts are sent to the customer email invoice destination(s) when a payment is received. See the <a href="http://search.cpan.org/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance<li><code>$pkg</code> - Package (requires payment_receipt-trigger set to "when payment is applied".)</ul>',
+ 'type' => [qw( checkbox textarea )],
+ },
+
+ {
+ 'key' => 'payment_receipt-trigger',
+ 'section' => 'billing',
+ 'description' => 'When payment receipts are triggered. Defaults to when payment is made.',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'cust_pay' => 'When payment is made.',
+ 'cust_bill_pay_pkg' => 'When payment is applied.',
+ ],
+ },
+
+ {
+ 'key' => 'trigger_export_insert_on_payment',
+ 'section' => 'billing',
+ 'description' => 'Enable exports on payment application.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'lpr',
+ 'section' => 'required',
+ 'description' => 'Print command for paper invoices, for example `lpr -h\'',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'lpr-postscript_prefix',
+ 'section' => 'billing',
+ 'description' => 'Raw printer commands prepended to the beginning of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'lpr-postscript_suffix',
+ 'section' => 'billing',
+ 'description' => 'Raw printer commands added to the end of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'money_char',
+ 'section' => '',
+ 'description' => 'Currency symbol - defaults to `$\'',
+ 'type' => 'text',
+ },
+
+ {
+ '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 TXT)} },
+ { type=> 'text' }, ],
+ },
+
+ {
+ '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' => 'password-noampersand',
+ 'section' => 'password',
+ 'description' => 'Disallow ampersands in passwords',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'password-noexclamation',
+ 'section' => 'password',
+ 'description' => 'Disallow exclamations in passwords (Not setting this could break old text Livingston or Cistron Radius servers)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'default-password-encoding',
+ 'section' => 'password',
+ 'description' => 'Default storage format for passwords',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'plain' => 'Plain text',
+ 'crypt-des' => 'Unix password (DES encrypted)',
+ 'crypt-md5' => 'Unix password (MD5 digest)',
+ 'ldap-plain' => 'LDAP (plain text)',
+ 'ldap-crypt' => 'LDAP (DES encrypted)',
+ 'ldap-md5' => 'LDAP (MD5 digest)',
+ 'ldap-sha1' => 'LDAP (SHA1 digest)',
+ 'legacy' => 'Legacy mode',
+ ],
+ },
+
+ {
+ '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' => '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' => 'shells',
+ 'section' => 'shell',
+ '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' => 'report-showpasswords',
+ 'section' => 'UI',
+ 'description' => 'This is a terrible idea. Do not enable it. STRONGLY NOT RECOMMENDED. Enables display of passwords on services reports.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signupurl',
+ 'section' => 'UI',
+ 'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Self-Service_Installation">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' => 'smtp-username',
+ 'section' => '',
+ 'description' => 'Optional SMTP username for Freeside\'s outgoing mail',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'smtp-password',
+ 'section' => '',
+ 'description' => 'Optional SMTP password for Freeside\'s outgoing mail',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'smtp-encryption',
+ 'section' => '',
+ 'description' => 'Optional SMTP encryption method. The STARTTLS methods require smtp-username and smtp-password to be set.',
+ 'type' => 'select',
+ 'select_hash' => [ '25' => 'None (port 25)',
+ '25-starttls' => 'STARTTLS (port 25)',
+ '587-starttls' => 'STARTTLS / submission (port 587)',
+ '465-tls' => 'SMTPS (SSL) (port 465)',
+ ],
+ },
+
+ {
+ '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' => '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' => 'unsuspend-always_adjust_next_bill_date',
+ 'section' => 'billing',
+ 'description' => 'Global override that causes unsuspensions to always adjust the next bill date under any circumstances. This is now controlled on a per-package bases - probably best not to use this option unless you are a legacy installation that requires this behaviour.',
+ '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',
+ 'per_agent' => 1,
+ },
+
+ {
+ '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. Not recommended for use with FreeRADIUS with MySQL backend, which is case-insensitive by default.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-percent',
+ 'section' => 'username',
+ 'description' => 'Allow the percent character (%) in usernames.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-colon',
+ 'section' => 'username',
+ 'description' => 'Allow the colon character (:) in usernames.',
+ '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 social security numbers in the web interface. Sometimes required by electronic check (ACH) processors.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'show_stateid',
+ 'section' => 'UI',
+ 'description' => "Turns on display/collection of driver's license/state issued id numbers in the web interface. Sometimes required by electronic check (ACH) processors.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'show_bankstate',
+ 'section' => 'UI',
+ 'description' => "Turns on display/collection of state for bank accounts in the web interface. Sometimes required by electronic check (ACH) processors.",
+ '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' => 'signup_server-payby',
+ 'section' => 'self-service',
+ 'description' => 'Acceptable payment types for the signup server',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
+ },
+
+ {
+ 'key' => 'selfservice-save_unchecked',
+ 'section' => 'self-service',
+ 'description' => 'In self-service, uncheck "Remember information" checkboxes by default (normally, they are checked by default).',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup_server-default_agentnum',
+ 'section' => 'self-service',
+ 'description' => 'Default agent for the signup server',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::agent;
+ map { $_->agentnum => $_->agent }
+ FS::Record::qsearch('agent', { disabled=>'' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::agent;
+ my $agent = FS::Record::qsearchs(
+ 'agent', { 'agentnum'=>shift }
+ );
+ $agent ? $agent->agent : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_server-default_refnum',
+ 'section' => 'self-service',
+ 'description' => 'Default advertising source for the signup server',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::part_referral;
+ map { $_->refnum => $_->referral }
+ FS::Record::qsearch( 'part_referral',
+ { 'disabled' => '' }
+ );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::part_referral;
+ my $part_referral = FS::Record::qsearchs(
+ 'part_referral', { 'refnum'=>shift } );
+ $part_referral ? $part_referral->referral : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_server-default_pkgpart',
+ 'section' => 'self-service',
+ 'description' => 'Default package for the signup server',
+ 'type' => 'select-part_pkg',
+ },
+
+ {
+ 'key' => 'signup_server-default_svcpart',
+ 'section' => 'self-service',
+ 'description' => 'Default service definition for the signup server - only necessary for services that trigger special provisioning widgets (such as DID provisioning).',
+ 'type' => 'select-part_svc',
+ },
+
+ {
+ 'key' => 'signup_server-mac_addr_svcparts',
+ 'section' => 'self-service',
+ 'description' => 'Service definitions which can receive mac addresses (current mapped to username for svc_acct).',
+ 'type' => 'select-part_svc',
+ 'multiple' => 1,
+ },
+
+ {
+ 'key' => 'signup_server-nomadix',
+ 'section' => 'self-service',
+ 'description' => 'Signup page Nomadix integration',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup_server-service',
+ 'section' => 'self-service',
+ 'description' => 'Service for the signup server - "Account (svc_acct)" is the default setting, or "Phone number (svc_phone)" for ITSP signup',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'svc_acct' => 'Account (svc_acct)',
+ 'svc_phone' => 'Phone number (svc_phone)',
+ ],
+ },
+
+ {
+ 'key' => 'selfservice_server-base_url',
+ 'section' => 'self-service',
+ 'description' => 'Base URL for the self-service web interface - necessary for some widgets to find their way, including retrieval of non-US state information and phone number provisioning.',
+ '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' => 'self-service',
+ 'description' => 'Run billing for signup server signups immediately, and do not provision accounts which subsequently have a balance.',
+ 'type' => 'checkbox',
+ },
+ {
+ 'key' => 'signup_server-classnum2',
+ 'section' => 'self-service',
+ 'description' => 'Package Class for first optional purchase',
+ 'type' => 'select-pkg_class',
+ },
+
+ {
+ 'key' => 'signup_server-classnum3',
+ 'section' => 'self-service',
+ 'description' => 'Package Class for second optional purchase',
+ 'type' => 'select-pkg_class',
+ },
+
+ {
+ 'key' => 'backend-realtime',
+ 'section' => 'billing',
+ 'description' => 'Run billing for backend signups immediately.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'declinetemplate',
+ 'section' => 'billing',
+ 'description' => 'Template file for credit card and electronic check decline emails.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'emaildecline',
+ 'section' => 'billing',
+ 'description' => 'Enable emailing of credit card and electronic check 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. Make sure to fill in the cancelmessage and cancelsubject configuration values as well.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'bill_usage_on_cancel',
+ 'section' => 'billing',
+ 'description' => 'Enable automatic generation of an invoice for usage when a package is cancelled. Not all packages can do this. Usage data must already be available.',
+ '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' => 'require_taxclasses',
+ 'section' => 'billing',
+ 'description' => 'Require a taxclass to be entered for every package',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'enable_taxproducts',
+ 'section' => 'billing',
+ 'description' => 'Enable per-package mapping to vendor tax data from CCH or elsewhere.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'taxdatadirectdownload',
+ 'section' => 'billing', #well
+ 'description' => 'Enable downloading tax data directly from the vendor site. at least three lines: URL, username, and password.j',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'ignore_incalculable_taxes',
+ 'section' => 'billing',
+ 'description' => 'Prefer to invoice without tax over not billing at all',
+ '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/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code></ul>',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'welcome_email-from',
+ 'section' => '',
+ 'description' => 'From: address header for welcome email',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'welcome_email-subject',
+ 'section' => '',
+ 'description' => 'Subject: header for welcome email',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'welcome_email-mimetype',
+ 'section' => '',
+ 'description' => 'MIME type for welcome email',
+ 'type' => 'select',
+ 'select_enum' => [ 'text/plain', 'text/html' ],
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'welcome_letter',
+ 'section' => '',
+ 'description' => 'Optional LaTex template file for a printed welcome letter. A welcome letter is printed the first time a cust_pkg record is created. See the <a href="http://search.cpan.org/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record (<code>$first, $last, etc</code>). The following additional variables are available<ul><li><code>$payby</code> - a friendler represenation of the field<li><code>$payinfo</code> - the masked payment information<li><code>$expdate</code> - the time at which the payment method expires (a UNIX timestamp)<li><code>$returnaddress</code> - the invoice return address for this customer\'s agent</ul>',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'warning_email',
+ 'section' => '',
+ 'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'warning_email-from',
+ 'section' => '',
+ 'description' => 'From: address header for warning email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-cc',
+ 'section' => '',
+ 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-subject',
+ 'section' => '',
+ 'description' => 'Subject: header for warning email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-mimetype',
+ 'section' => '',
+ 'description' => 'MIME type for warning email',
+ 'type' => 'select',
+ 'select_enum' => [ 'text/plain', 'text/html' ],
+ },
+
+ {
+ 'key' => 'payby',
+ 'section' => 'billing',
+ 'description' => 'Available payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP) ],
+ },
+
+ {
+ '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 CASH WEST MCRD COMP HIDE) ],
+ },
+
+ {
+ 'key' => 'paymentforcedtobatch',
+ 'section' => 'deprecated',
+ 'description' => 'See batch-enable_payby and realtime-disable_payby. Used to (for CHEK): Cause per customer payment entry to be forced to a batch processor rather than performed realtime.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-notes',
+ 'section' => 'deprecated',
+ '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', 'Cleartext-Password' ],
+ },
+
+ {
+ 'key' => 'radius-ip',
+ 'section' => '',
+ 'description' => 'RADIUS attribute for IP addresses.',
+ 'type' => 'select',
+ 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ],
+ },
+
+ #http://dev.coova.org/svn/coova-chilli/doc/dictionary.chillispot
+ {
+ 'key' => 'radius-chillispot-max',
+ 'section' => '',
+ 'description' => 'Enable ChilliSpot (and CoovaChilli) Max attributes, specifically ChilliSpot-Max-{Input,Output,Total}-{Octets,Gigawords}.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, enable the <i>Complimentary customer</i> access right instead. Was: 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' => 'credit_card-recurring_billing_flag',
+ 'section' => 'billing',
+ 'description' => 'This controls when the system passes the "recurring_billing" flag on credit card transactions. If supported by your processor (and the Business::OnlinePayment processor module), passing the flag indicates this is a recurring transaction and may turn off the CVV requirement. ',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'actual_oncard' => 'Default/classic behavior: set the flag if a customer has actual previous charges on the card.',
+ 'transaction_is_recur' => 'Set the flag if the transaction itself is recurring, irregardless of previous charges on the card.',
+ ],
+ },
+
+ {
+ 'key' => 'credit_card-recurring_billing_acct_code',
+ 'section' => 'billing',
+ 'description' => 'When the "recurring billing" flag is set, also set the "acct_code" to "rebill". Useful for reporting purposes with supported gateways (PlugNPay, others?)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => \@card_types,
+ },
+
+ {
+ 'key' => 'manual_process-pkgpart',
+ 'section' => 'billing',
+ 'description' => 'Package to add to each manual credit card and ACH payments entered from the backend. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option.',
+ 'type' => 'select-part_pkg',
+ },
+
+ {
+ 'key' => 'manual_process-display',
+ 'section' => 'billing',
+ 'description' => 'When using manual_process-pkgpart, add the fee to the amount entered (default), or subtract the fee from the amount entered.',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'add' => 'Add fee to amount entered',
+ 'subtract' => 'Subtract fee from amount entered',
+ ],
+ },
+
+ {
+ 'key' => 'manual_process-skip_first',
+ 'section' => 'billing',
+ 'description' => "When using manual_process-pkgpart, omit the fee if it is the customer's first payment.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => 'cust_pkg-change_pkgpart-bill_now',
+ 'section' => '',
+ 'description' => "When changing packages, bill the new package immediately. Useful for prepaid situations with RADIUS where an Expiration attribute based on the package must be present at all times.",
+ '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' => 'select-part_svc',
+ 'multiple' => 1,
+ },
+
+ {
+ 'key' => 'selfservice_server-primary_only',
+ 'section' => 'self-service',
+ 'description' => 'Only allow primary accounts to access self-service functionality.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'selfservice_server-phone_login',
+ 'section' => 'self-service',
+ 'description' => 'Allow login to self-service with phone number and PIN.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'selfservice_server-single_domain',
+ 'section' => 'self-service',
+ 'description' => 'If specified, only use this one domain for self-service access.',
+ 'type' => 'text',
+ },
+
+ {
+ '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). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.',
+ 'type' => 'select',
+ 'select_enum' => [ 'none', 'username', 'username@domain', 'disabled' ],
+ },
+
+ {
+ 'key' => 'global_unique-phonenum',
+ 'section' => '',
+ 'description' => 'Global phone number uniqueness control: none (usual setting - check countrycode+phonenumun uniqueness per exports), or countrycode+phonenum (all countrycode+phonenum pairs are globally unique, regardless of exports). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.',
+ 'type' => 'select',
+ 'select_enum' => [ 'none', 'countrycode+phonenum', 'disabled' ],
+ },
+
+ {
+ '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', ],
+ },
+
+ {
+ 'key' => 'ticket_system',
+ 'section' => '',
+ 'description' => 'Ticketing system integration. <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:RT_Installation">integrated ticketing installation instructions</a>). <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
+ 'type' => 'select',
+ #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
+ 'select_enum' => [ '', qw(RT_Internal RT_External) ],
+ },
+
+ {
+ 'key' => 'ticket_system-default_queueid',
+ 'section' => '',
+ 'description' => 'Default queue used when creating new customer tickets.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queues();
+ } else {
+ ();
+ }
+ },
+ 'option_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queue(shift);
+ } else {
+ '';
+ }
+ },
+ },
+
+ {
+ 'key' => 'ticket_system-selfservice_queueid',
+ 'section' => '',
+ 'description' => 'Queue used when creating new customer tickets from self-service. Defautls to ticket_system-default_queueid if not specified.',
+ #false laziness w/above
+ 'type' => 'select-sub',
+ 'options_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queues();
+ } else {
+ ();
+ }
+ },
+ 'option_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queue(shift);
+ } else {
+ '';
+ }
+ },
+ },
+
+ {
+ 'key' => 'ticket_system-priority_reverse',
+ 'section' => '',
+ 'description' => 'Enable this to consider lower numbered priorities more important. A bad habit we picked up somewhere. You probably want to avoid it and use the default.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field',
+ 'section' => '',
+ 'description' => 'Custom field from the ticketing system to use as a custom priority classification.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field-values',
+ 'section' => '',
+ 'description' => 'Values for the custom field from the ticketing system to break down and sort customer ticket lists.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field_queue',
+ 'section' => '',
+ 'description' => 'Ticketing system queue in which the custom field specified in ticket_system-custom_priority_field is located.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'ticket_system-rt_external_datasrc',
+ 'section' => '',
+ 'description' => 'With external RT integration, the DBI data source for the external RT installation, for example, <code>DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt</code>',
+ 'type' => 'text',
+
+ },
+
+ {
+ 'key' => 'ticket_system-rt_external_url',
+ 'section' => '',
+ 'description' => 'With external RT integration, the URL for the external RT installation, for example, <code>https://rt.example.com/rt</code>',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'company_name',
+ 'section' => 'required',
+ 'description' => 'Your company name',
+ 'type' => 'text',
+ 'per_agent' => 1, #XXX just FS/FS/ClientAPI/Signup.pm
+ },
+
+ {
+ 'key' => 'company_address',
+ 'section' => 'required',
+ 'description' => 'Your company address',
+ 'type' => 'textarea',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'echeck-void',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cc-void',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of credit card payments in addition to refunds against the payment gateway',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unvoid',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable unvoiding of voided payments',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'address1-search',
+ 'section' => 'UI',
+ 'description' => 'Enable the ability to search the address1 field from customer search.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'address2-search',
+ 'section' => 'UI',
+ 'description' => 'Enable a "Unit" search box which searches the second address field. Useful for multi-tenant applications. See also: cust_main-require_address2',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_address2',
+ 'section' => 'UI',
+ 'description' => 'Second address field is required (on service address only, if billing and service addresses differ). Also enables "Unit" labeling of address2 on customer view and edit pages. Useful for multi-tenant applications. See also: address2-search',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'agent-ship_address',
+ 'section' => '',
+ 'description' => "Use the agent's master service address as the service address (only ship_address2 can be entered, if blank on the master address). Useful for multi-tenant applications.",
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'referral_credit',
+ 'section' => 'deprecated',
+ 'description' => "Used to enable one-time referral credits in the amount of one month <i>referred</i> customer's recurring fee (irregardless of frequency). Replace with a billing event on appropriate packages.",
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'selfservice_server-cache_module',
+ 'section' => 'self-service',
+ 'description' => 'Module used to store self-service session information. All modules handle any number of self-service servers. Cache::SharedMemoryCache is appropriate for a single database / single Freeside server. Cache::FileCache is useful for multiple databases on a single server, or when IPC::ShareLite is not available (i.e. FreeBSD).', # _Database stores session information in the database and is appropriate for multiple Freeside servers, but may be slower.',
+ 'type' => 'select',
+ 'select_enum' => [ 'Cache::SharedMemoryCache', 'Cache::FileCache', ], # '_Database' ],
+ },
+
+ {
+ 'key' => 'hylafax',
+ 'section' => 'billing',
+ 'description' => 'Options for a HylaFAX server to enable the FAX invoice destination. They should be in the form of a space separated list of arguments to the Fax::Hylafax::Client::sendfax subroutine. You probably shouldn\'t override things like \'docfile\'. *Note* Only supported when using typeset invoices (see the invoice_latex configuration option).',
+ 'type' => [qw( checkbox textarea )],
+ },
+
+ {
+ 'key' => 'cust_bill-ftpformat',
+ 'section' => 'invoicing',
+ 'description' => 'Enable FTP of raw invoice data - format.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'default', 'billco', ],
+ },
+
+ {
+ 'key' => 'cust_bill-ftpserver',
+ 'section' => 'invoicing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftpusername',
+ 'section' => 'invoicing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftppassword',
+ 'section' => 'invoicing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftpdir',
+ 'section' => 'invoicing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-spoolformat',
+ 'section' => 'invoicing',
+ 'description' => 'Enable spooling of raw invoice data - format.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'default', 'billco', ],
+ },
+
+ {
+ 'key' => 'cust_bill-spoolagent',
+ 'section' => 'invoicing',
+ 'description' => 'Enable per-agent spooling of raw invoice data.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_suspend',
+ 'section' => 'billing',
+ 'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_unsuspend',
+ 'section' => 'billing',
+ 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_threshold',
+ 'section' => 'billing',
+ 'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder. Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'overlimit_groups',
+ 'section' => '',
+ 'description' => 'RADIUS group (or comma-separated groups) to assign to svc_acct which has exceeded its bandwidth or time limit.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'cust-fields',
+ 'section' => 'UI',
+ 'description' => 'Which customer fields to display on reports by default',
+ 'type' => 'select',
+ 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ],
+ },
+
+ {
+ 'key' => 'cust_pkg-display_times',
+ 'section' => 'UI',
+ 'description' => 'Display full timestamps (not just dates) for customer packages. Useful if you are doing real-time things like hourly prepaid.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_pkg-always_show_location',
+ 'section' => 'UI',
+ 'description' => "Always display package locations, even when they're all the default service address.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_pkg-show_fcc_voice_grade_equivalent',
+ 'section' => 'UI',
+ 'description' => "Show a field on package definitions for assigning a DSO equivalency number suitable for use on FCC form 477.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-edit_uid',
+ 'section' => 'shell',
+ 'description' => 'Allow UID editing.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-edit_gid',
+ 'section' => 'shell',
+ 'description' => 'Allow GID editing.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'zone-underscore',
+ 'section' => 'BIND',
+ 'description' => 'Allow underscores in zone names. As underscores are illegal characters in zone names, this option is not recommended.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'echeck-nonus',
+ 'section' => 'billing',
+ 'description' => 'Disable ABA-format account checking for Electronic Check payment info',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'voip-cust_cdr_spools',
+ 'section' => '',
+ 'description' => 'Enable the per-customer option for individual CDR spools.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'voip-cust_cdr_squelch',
+ 'section' => '',
+ 'description' => 'Enable the per-customer option for not printing CDR on invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'voip-cdr_email',
+ 'section' => '',
+ 'description' => 'Include the call details on emailed invoices even if the customer is configured for not printing them on the invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'voip-cust_email_csv_cdr',
+ 'section' => '',
+ 'description' => 'Enable the per-customer option for including CDR information as a CSV attachment on emailed invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_forward-no_srcsvc',
+ 'section' => '',
+ 'description' => "Don't allow forwards from existing accounts, only arbitrary addresses. Useful when exporting to systems such as Communigate Pro which treat forwards in this fashion.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_forward-arbitrary_dst',
+ 'section' => '',
+ 'description' => "Allow forwards to point to arbitrary strings that don't necessarily look like email addresses. Only used when using forwards for weird, non-email things.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'tax-ship_address',
+ 'section' => 'billing',
+ 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead.',
+ 'type' => 'checkbox',
+ }
+,
+ {
+ 'key' => 'tax-pkg_address',
+ 'section' => 'billing',
+ 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the package address instead (when present). Note that this option is currently incompatible with vendor data taxation enabled by enable_taxproducts.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-ship_address',
+ 'section' => 'invoicing',
+ 'description' => 'Include the shipping address on invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-unitprice',
+ 'section' => 'invoicing',
+ 'description' => 'Enable unit pricing on invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-smallernotes',
+ 'section' => 'invoicing',
+ 'description' => 'Display the notes section in a smaller font on invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-smallerfooter',
+ 'section' => 'invoicing',
+ 'description' => 'Display footers in a smaller font on invoices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'postal_invoice-fee_pkgpart',
+ 'section' => 'billing',
+ 'description' => 'This allows selection of a package to insert on invoices for customers with postal invoices selected.',
+ 'type' => 'select-part_pkg',
+ },
+
+ {
+ 'key' => 'postal_invoice-recurring_only',
+ 'section' => 'billing',
+ 'description' => 'The postal invoice fee is omitted on invoices without reucrring charges when this is set.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'batch-enable',
+ 'section' => 'deprecated', #make sure batch-enable_payby is set for
+ #everyone before removing
+ 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'batch-enable_payby',
+ 'section' => 'billing',
+ 'description' => 'Enable batch processing for the specified payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( CARD CHEK )],
+ },
+
+ {
+ 'key' => 'realtime-disable_payby',
+ 'section' => 'billing',
+ 'description' => 'Disable realtime processing for the specified payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( CARD CHEK )],
+ },
+
+ {
+ 'key' => 'batch-default_format',
+ 'section' => 'billing',
+ 'description' => 'Default format for batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch',
+ 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
+ 'paymentech', 'ach-spiritone', 'RBC'
+ ]
+ },
+
+ #lists could be auto-generated from pay_batch info
+ {
+ 'key' => 'batch-fixed_format-CARD',
+ 'section' => 'billing',
+ 'description' => 'Fixed (unchangeable) format for credit card batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ,
+ 'csv-chase_canada-E-xactBatch', 'paymentech' ]
+ },
+
+ {
+ 'key' => 'batch-fixed_format-CHEK',
+ 'section' => 'billing',
+ 'description' => 'Fixed (unchangeable) format for electronic check batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP',
+ 'paymentech', 'ach-spiritone', 'RBC'
+ ]
+ },
+
+ {
+ 'key' => 'batch-increment_expiration',
+ 'section' => 'billing',
+ 'description' => 'Increment expiration date years in batches until cards are current. Make sure this is acceptable to your batching provider before enabling.',
+ 'type' => 'checkbox'
+ },
+
+ {
+ 'key' => 'batchconfig-BoM',
+ 'section' => 'billing',
+ 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'batchconfig-PAP',
+ 'section' => 'billing',
+ 'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'batchconfig-csv-chase_canada-E-xactBatch',
+ 'section' => 'billing',
+ 'description' => 'Gateway ID for Chase Canada E-xact batching',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'batchconfig-paymentech',
+ 'section' => 'billing',
+ 'description' => 'Configuration for Chase Paymentech batching, five lines: 1. BIN, 2. Terminal ID, 3. Merchant ID, 4. Username, 5. Password (for batch uploads)',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'batchconfig-RBC',
+ 'section' => 'billing',
+ 'description' => 'Configuration for Royal Bank of Canada PDS batching, four lines: 1. Client number, 2. Short name, 3. Long name, 4. Transaction code.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'payment_history-years',
+ 'section' => 'UI',
+ 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'change_history-years',
+ 'section' => 'UI',
+ 'description' => 'Number of years of change history to show by default. Currently defaults to 0.5.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-packages-years',
+ 'section' => 'UI',
+ 'description' => 'Number of years to show old (cancelled and one-time charge) packages by default. Currently defaults to 2.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-use_comments',
+ 'section' => 'UI',
+ 'description' => 'Display free form comments on the customer edit screen. Useful as a scratch pad.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-disable_notes',
+ 'section' => 'UI',
+ 'description' => 'Disable new style customer notes - timestamped and user identified customer notes. Useful in tracking who did what.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main_note-display_times',
+ 'section' => 'UI',
+ 'description' => 'Display full timestamps (not just dates) for customer notes.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-ticket_statuses',
+ 'section' => 'UI',
+ 'description' => 'Show tickets with these statuses on the customer view page.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( new open stalled resolved rejected deleted )],
+ },
+
+ {
+ 'key' => 'cust_main-max_tickets',
+ 'section' => 'UI',
+ 'description' => 'Maximum number of tickets to show on the customer view page.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-skeleton_tables',
+ 'section' => '',
+ 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cust_main-skeleton_custnum',
+ 'section' => '',
+ 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-enable_birthdate',
+ 'section' => 'UI',
+ 'descritpion' => 'Enable tracking of a birth date with each customer record',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'support-key',
+ 'section' => '',
+ 'description' => 'A support key enables access to commercial services delivered over the network, such as the payroll module, access to the internal ticket system, priority support and optional backups.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'card-types',
+ 'section' => 'billing',
+ 'description' => 'Select one or more card types to enable only those card types. If no card types are selected, all card types are available.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => \@card_types,
+ },
+
+ {
+ 'key' => 'disable-fuzzy',
+ 'section' => 'UI',
+ 'description' => 'Disable fuzzy searching. Speeds up searching for large sites, but only shows exact matches.',
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'pkg_referral',
+ 'section' => '',
+ 'description' => 'Enable package-specific advertising sources.',
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'pkg_referral-multiple',
+ 'section' => '',
+ 'description' => 'In addition, allow multiple advertising sources to be associated with a single package.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'dashboard-install_welcome',
+ 'section' => 'UI',
+ 'description' => 'New install welcome screen.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'ITSP_fsinc_hosted', ],
+ },
+
+ {
+ 'key' => 'dashboard-toplist',
+ 'section' => 'UI',
+ 'description' => 'List of items to display on the top of the front page',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'impending_recur_template',
+ 'section' => 'billing',
+ 'description' => 'Template file for alerts about looming first time recurrant billing. See the <a href="http://search.cpan.org/dist/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitition language. Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a> The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>',
+# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'logo.png',
+ 'section' => 'UI', #'invoicing' ?
+ 'description' => 'Company logo for HTML invoices and the backoffice interface, in PNG format. Suggested size somewhere near 92x62.',
+ 'type' => 'image',
+ 'per_agent' => 1, #XXX just view/logo.cgi, which is for the global
+ #old-style editor anyway...?
+ },
+
+ {
+ 'key' => 'logo.eps',
+ 'section' => 'invoicing',
+ 'description' => 'Company logo for printed and PDF invoices, in EPS format.',
+ 'type' => 'image',
+ 'per_agent' => 1, #XXX as above, kinda
+ },
+
+ {
+ 'key' => 'selfservice-ignore_quantity',
+ 'section' => 'self-service',
+ 'description' => 'Ignores service quantity restrictions in self-service context. Strongly not recommended - just set your quantities correctly in the first place.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'selfservice-session_timeout',
+ 'section' => 'self-service',
+ 'description' => 'Self-service session timeout. Defaults to 1 hour.',
+ 'type' => 'select',
+ 'select_enum' => [ '1 hour', '2 hours', '4 hours', '8 hours', '1 day', '1 week', ],
+ },
+
+ {
+ 'key' => 'disable_setup_suspended_pkgs',
+ 'section' => 'billing',
+ 'description' => 'Disables charging of setup fees for suspended packages.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'password-generated-allcaps',
+ 'section' => 'password',
+ 'description' => 'Causes passwords automatically generated to consist entirely of capital letters',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'datavolume-forcemegabytes',
+ 'section' => 'UI',
+ 'description' => 'All data volumes are expressed in megabytes',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'datavolume-significantdigits',
+ 'section' => 'UI',
+ 'description' => 'number of significant digits to use to represent data volumes',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_void_after',
+ 'section' => 'billing',
+ 'description' => 'Number of seconds after which freeside won\'t attempt to VOID a payment first when performing a refund.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_line_item_date_ranges',
+ 'section' => 'billing',
+ 'description' => 'Prevent freeside from automatically generating date ranges on invoice line items.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'support_packages',
+ 'section' => '',
+ 'description' => 'A list of packages eligible for RT ticket time transfer, one pkgpart per line.', #this should really be a select multiple, or specified in the packages themselves...
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+
+ {
+ 'key' => 'cust_main-require_phone',
+ 'section' => '',
+ 'description' => 'Require daytime or night phone for all customer records.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_invoicing_list_email',
+ 'section' => '',
+ 'description' => 'Email address field is required: require at least one invoicing email address for all customer records.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-display_paid_time_remaining',
+ 'section' => '',
+ 'description' => 'Show paid time remaining in addition to time remaining.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cancel_credit_type',
+ 'section' => 'billing',
+ 'description' => 'The group to use for new, automatically generated credit reasons resulting from cancellation.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'referral_credit_type',
+ 'section' => 'deprecated',
+ 'description' => 'Used to be the group to use for new, automatically generated credit reasons resulting from referrals. Now set in a package billing event for the referral.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_credit_type',
+ 'section' => 'billing', #self-service?
+ 'description' => 'The group to use for new, automatically generated credit reasons resulting from signup and self-service declines.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'cust_main-agent_custid-format',
+ 'section' => '',
+ 'description' => 'Enables searching of various formatted values in cust_main.agent_custid',
+ 'type' => 'select',
+ 'select_hash' => [
+ '' => 'Numeric only',
+ 'ww?d+' => 'Numeric with one or two letter prefix',
+ ],
+ },
+
+ {
+ 'key' => 'card_masking_method',
+ 'section' => 'UI',
+ 'description' => 'Digits to display when masking credit cards. Note that the first six digits are necessary to canonically identify the credit card type (Visa/MC, Amex, Discover, Maestro, etc.) in all cases. The first four digits can identify the most common credit card types in most cases (Visa/MC, Amex, and Discover). The first two digits can distinguish between Visa/MC and Amex. Note: You should manually remove stored paymasks if you change this value on an existing database, to avoid problems using stored cards.',
+ 'type' => 'select',
+ 'select_hash' => [
+ '' => '123456xxxxxx1234',
+ 'first6last2' => '123456xxxxxxxx12',
+ 'first4last4' => '1234xxxxxxxx1234',
+ 'first4last2' => '1234xxxxxxxxxx12',
+ 'first2last4' => '12xxxxxxxxxx1234',
+ 'first2last2' => '12xxxxxxxxxxxx12',
+ 'first0last4' => 'xxxxxxxxxxxx1234',
+ 'first0last2' => 'xxxxxxxxxxxxxx12',
+ ],
+ },
+
+ {
+ 'key' => 'disable_previous_balance',
+ 'section' => 'invoicing',
+ 'description' => 'Disable inclusion of previous balance, payment, and credit lines on invoices',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'previous_balance-exclude_from_total',
+ 'section' => 'invoicing',
+ 'description' => 'Do not include previous balance in the \'Total\' line. Only meaningful when invoice_sections is false. Optionally provide text to override the Total New Charges description',
+ 'type' => [ qw(checkbox text) ],
+ },
+
+ {
+ 'key' => 'previous_balance-summary_only',
+ 'section' => 'invoicing',
+ 'description' => 'Only show a single line summarizing the total previous balance rather than one line per invoice.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'balance_due_below_line',
+ 'section' => 'invoicing',
+ 'description' => 'Place the balance due message below a line. Only meaningful when when invoice_sections is false.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'usps_webtools-userid',
+ 'section' => 'UI',
+ 'description' => 'Production UserID for USPS web tools. Enables USPS address standardization. See the <a href="http://www.usps.com/webtools/">USPS website</a>, register and agree not to use the tools for batch purposes.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'usps_webtools-password',
+ 'section' => 'UI',
+ 'description' => 'Production password for USPS web tools. Enables USPS address standardization. See <a href="http://www.usps.com/webtools/">USPS website</a>, register and agree not to use the tools for batch purposes.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-auto_standardize_address',
+ 'section' => 'UI',
+ 'description' => 'When using USPS web tools, automatically standardize the address without asking.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_censustract',
+ 'section' => 'UI',
+ 'description' => 'Customer is required to have a census tract. Useful for FCC form 477 reports. See also: cust_main-auto_standardize_address',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'census_year',
+ 'section' => 'UI',
+ 'description' => 'The year to use in census tract lookups',
+ 'type' => 'select',
+ 'select_enum' => [ qw( 2009 2008 2007 2006 ) ],
+ },
+
+ {
+ 'key' => 'company_latitude',
+ 'section' => 'UI',
+ 'description' => 'Your company latitude (-90 through 90)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'company_longitude',
+ 'section' => 'UI',
+ 'description' => 'Your company longitude (-180 thru 180)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_acl_changes',
+ 'section' => '',
+ 'description' => 'Disable all ACL changes, for demos.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-edit_agent_custid',
+ 'section' => 'UI',
+ 'description' => 'Enable editing of the agent_custid field.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-default_agent_custid',
+ 'section' => 'UI',
+ 'description' => 'Display the agent_custid field when available instead of the custnum field.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_bill-default_agent_invid',
+ 'section' => 'UI',
+ 'description' => 'Display the agent_invid field when available instead of the invnum field.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-auto_agent_custid',
+ 'section' => 'UI',
+ 'description' => 'Automatically assign an agent_custid - select format',
+ 'type' => 'select',
+ 'select_hash' => [ '' => 'No',
+ '1YMMXXXXXXXX' => '1YMMXXXXXXXX',
+ ],
+ },
+
+ {
+ 'key' => 'cust_main-default_areacode',
+ 'section' => 'UI',
+ 'description' => 'Default area code for customers.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'mcp_svcpart',
+ 'section' => '',
+ 'description' => 'Master Control Program svcpart. Leave this blank.',
+ 'type' => 'text', #select-part_svc
+ },
+
+ {
+ 'key' => 'cust_bill-max_same_services',
+ 'section' => 'invoicing',
+ 'description' => 'Maximum number of the same service to list individually on invoices before condensing to a single line listing the number of services. Defaults to 5.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-consolidate_services',
+ 'section' => 'invoicing',
+ 'description' => 'Consolidate service display into fewer lines on invoices rather than one per service.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'suspend_email_admin',
+ 'section' => '',
+ 'description' => 'Destination admin email address to enable suspension notices',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'email_report-subject',
+ 'section' => '',
+ 'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'selfservice-head',
+ 'section' => 'self-service',
+ 'description' => 'HTML for the HEAD section of the self-service interface, typically used for LINK stylesheet tags',
+ 'type' => 'textarea', #htmlarea?
+ 'per_agent' => 1,
+ },
+
+
+ {
+ 'key' => 'selfservice-body_header',
+ 'section' => 'self-service',
+ 'description' => 'HTML header for the self-service interface',
+ 'type' => 'textarea', #htmlarea?
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-body_footer',
+ 'section' => 'self-service',
+ 'description' => 'HTML footer for the self-service interface',
+ 'type' => 'textarea', #htmlarea?
+ 'per_agent' => 1,
+ },
+
+
+ {
+ 'key' => 'selfservice-body_bgcolor',
+ 'section' => 'self-service',
+ 'description' => 'HTML background color for the self-service interface, for example, #FFFFFF',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-box_bgcolor',
+ 'section' => 'self-service',
+ 'description' => 'HTML color for self-service interface input boxes, for example, #C0C0C0',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-text_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML text color for the self-service interface, for example, #000000',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-link_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML link color for the self-service interface, for example, #0000FF',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-vlink_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML visited link color for the self-service interface, for example, #FF00FF',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-hlink_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML hover link color for the self-service interface, for example, #808080',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-alink_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML active (clicked) link color for the self-service interface, for example, #808080',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-font',
+ 'section' => 'self-service',
+ 'description' => 'HTML font CSS for the self-service interface, for example, 0.9em/1.5em Arial, Helvetica, Geneva, sans-serif',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-title_color',
+ 'section' => 'self-service',
+ 'description' => 'HTML color for the self-service title, for example, #000000',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-title_align',
+ 'section' => 'self-service',
+ 'description' => 'HTML alignment for the self-service title, for example, center',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+ {
+ 'key' => 'selfservice-title_size',
+ 'section' => 'self-service',
+ 'description' => 'HTML font size for the self-service title, for example, 3',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-title_left_image',
+ 'section' => 'self-service',
+ 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.',
+ 'type' => 'image',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-title_right_image',
+ 'section' => 'self-service',
+ 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.',
+ 'type' => 'image',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_skipblanks',
+ 'section' => 'self-service',
+ 'description' => 'Skip blank (spacer) entries in the self-service menu',
+ 'type' => 'checkbox',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_skipheadings',
+ 'section' => 'self-service',
+ 'description' => 'Skip the unclickable heading entries in the self-service menu',
+ 'type' => 'checkbox',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_bgcolor',
+ 'section' => 'self-service',
+ 'description' => 'HTML color for the self-service menu, for example, #C0C0C0',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_fontsize',
+ 'section' => 'self-service',
+ 'description' => 'HTML font size for the self-service menu, for example, -1',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+ {
+ 'key' => 'selfservice-menu_nounderline',
+ 'section' => 'self-service',
+ 'description' => 'Styles menu links in the self-service without underlining.',
+ 'type' => 'checkbox',
+ 'per_agent' => 1,
+ },
+
+
+ {
+ 'key' => 'selfservice-menu_top_image',
+ 'section' => 'self-service',
+ 'description' => 'Image used for the top of the menu in the self-service interface, in PNG format.',
+ 'type' => 'image',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_body_image',
+ 'section' => 'self-service',
+ 'description' => 'Repeating image used for the body of the menu in the self-service interface, in PNG format.',
+ 'type' => 'image',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-menu_bottom_image',
+ 'section' => 'self-service',
+ 'description' => 'Image used for the bottom of the menu in the self-service interface, in PNG format.',
+ 'type' => 'image',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-bulk_format',
+ 'section' => 'deprecated',
+ 'description' => 'Parameter arrangement for selfservice bulk features',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'izoom-soap', 'izoom-ftp' ],
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'selfservice-bulk_ftp_dir',
+ 'section' => 'deprecated',
+ 'description' => 'Enable bulk ftp provisioning in this folder',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'signup-no_company',
+ 'section' => 'self-service',
+ 'description' => "Don't display a field for company name on signup.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup-recommend_email',
+ 'section' => 'self-service',
+ 'description' => 'Encourage the entry of an invoicing email address on signup.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup-recommend_daytime',
+ 'section' => 'self-service',
+ 'description' => 'Encourage the entry of a daytime phone number invoicing email address on signup.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_phone-radius-default_password',
+ 'section' => '',
+ 'description' => 'Default password when exporting svc_phone records to RADIUS',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'svc_phone-allow_alpha_phonenum',
+ 'section' => '',
+ 'description' => 'Allow letters in phone numbers.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_phone-domain',
+ 'section' => '',
+ 'description' => 'Track an optional domain association with each phone service.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_phone-phone_name-max_length',
+ 'section' => '',
+ 'description' => 'Maximum length of the phone service "Name" field (svc_phone.phone_name). Sometimes useful to limit this (to 15?) when exporting as Caller ID data.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'default_phone_countrycode',
+ 'section' => '',
+ 'description' => 'Default countrcode',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cdr-charged_party-accountcode',
+ 'section' => '',
+ 'description' => 'Set the charged_party field of CDRs to the accountcode.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cdr-charged_party-accountcode-trim_leading_0s',
+ 'section' => '',
+ 'description' => 'When setting the charged_party field of CDRs to the accountcode, trim any leading zeros.',
+ 'type' => 'checkbox',
+ },
+
+# {
+# 'key' => 'cdr-charged_party-truncate_prefix',
+# 'section' => '',
+# 'description' => 'If the charged_party field has this prefix, truncate it to the length in cdr-charged_party-truncate_length.',
+# 'type' => 'text',
+# },
+#
+# {
+# 'key' => 'cdr-charged_party-truncate_length',
+# 'section' => '',
+# 'description' => 'If the charged_party field has the prefix in cdr-charged_party-truncate_prefix, truncate it to this length.',
+# 'type' => 'text',
+# },
+
+ {
+ 'key' => 'cdr-charged_party_rewrite',
+ 'section' => '',
+ 'description' => 'Do charged party rewriting in the freeside-cdrrewrited daemon; useful if CDRs are being dropped off directly in the database and require special charged_party processing such as cdr-charged_party-accountcode or cdr-charged_party-truncate*.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cdr-taqua-da_rewrite',
+ 'section' => '',
+ 'description' => 'For the Taqua CDR format, a comma-separated list of directory assistance 800 numbers. Any CDRs with these numbers as "BilledNumber" will be rewritten to the "CallingPartyNumber" (and CallType "12") on import.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_pkg-show_autosuspend',
+ 'section' => 'UI',
+ 'description' => 'Show package auto-suspend dates. Use with caution for now; can slow down customer view for large insallations.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cdr-asterisk_forward_rewrite',
+ 'section' => '',
+ 'description' => 'Enable special processing for CDRs representing forwarded calls: For CDRs that have a dcontext that starts with "Local/" but does not match dst, set charged_party to dst, parse a new dst from dstchannel, and set amaflags to "2" ("BILL"/"BILLING").',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'sg-multicustomer_hack',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'sg-ping_username',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sg-ping_password',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sg-login_username',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable-cust-pkg_class',
+ 'section' => 'UI',
+ 'description' => 'Disable the two-step dropdown for selecting package class and package, and return to the classic single dropdown.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'queued-max_kids',
+ 'section' => '',
+ 'description' => 'Maximum number of queued processes. Defaults to 10.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'queued-sleep_time',
+ 'section' => '',
+ 'description' => 'Time to sleep between attempts to find new jobs to process in the queue. Defaults to 10. Installations doing real-time CDR processing for prepaid may want to set it lower.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cancelled_cust-noevents',
+ 'section' => 'billing',
+ 'description' => "Don't run events for cancelled customers",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'agent-invoice_template',
+ 'section' => 'invoicing',
+ 'description' => 'Enable display/edit of old-style per-agent invoice template selection',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_broadband-manage_link',
+ 'section' => 'UI',
+ 'description' => 'URL for svc_broadband "Manage Device" link. The following substitutions are available: $ip_addr.',
+ 'type' => 'text',
+ },
+
+ #more fine-grained, service def-level control could be useful eventually?
+ {
+ 'key' => 'svc_broadband-allow_null_ip_addr',
+ 'section' => '',
+ 'description' => '',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'tax-report_groups',
+ 'section' => '',
+ 'description' => 'List of grouping possibilities for tax names on reports, one per line, "label op value" (op can be = or !=).',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'tax-cust_exempt-groups',
+ 'section' => '',
+ 'description' => 'List of grouping possibilities for tax names, for per-customer exemption purposes, one tax name per line. For example, "GST" would indicate the ability to exempt customers individually from taxes named "GST" (but not other taxes).',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cust_main-default_view',
+ 'section' => 'UI',
+ 'description' => 'Default customer view, for users who have not selected a default view in their preferences.',
+ 'type' => 'select',
+ 'select_hash' => [
+ #false laziness w/view/cust_main.cgi and pref/pref.html
+ 'basics' => 'Basics',
+ 'notes' => 'Notes',
+ 'tickets' => 'Tickets',
+ 'packages' => 'Packages',
+ 'payment_history' => 'Payment History',
+ 'change_history' => 'Change History',
+ 'jumbo' => 'Jumbo',
+ ],
+ },
+
+ {
+ 'key' => 'enable_tax_adjustments',
+ 'section' => 'billing',
+ 'description' => 'Enable the ability to add manual tax adjustments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'rt-crontool',
+ 'section' => '',
+ 'description' => 'Enable the RT CronTool extension.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'pkg-balances',
+ 'section' => 'billing',
+ 'description' => 'Enable experimental package balances. Not recommended for general use.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'pkg-addon_classnum',
+ 'section' => 'billing',
+ 'description' => 'Enable the ability to restrict additional package orders based on package class.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-edit_signupdate',
+ 'section' => 'UI',
+ 'descritpion' => 'Enable manual editing of the signup date.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-disable_access_number',
+ 'section' => 'UI',
+ 'descritpion' => 'Disable access number selection.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_bill_pay_pkg-manual',
+ 'section' => 'UI',
+ 'description' => 'Allow manual application of payments to line items.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_credit_bill_pkg-manual',
+ 'section' => 'UI',
+ 'description' => 'Allow manual application of credits to line items.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'breakage-days',
+ 'section' => 'billing',
+ 'description' => 'If set to a number of days, after an account goes that long without activity, recognizes any outstanding payments and credits as "breakage" by creating a breakage charge and invoice.',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
+ 'key' => 'breakage-pkg_class',
+ 'section' => 'billing',
+ 'description' => 'Package class to use for breakage reconciliation.',
+ 'type' => 'select-pkg_class',
+ },
+
+ {
+ 'key' => 'disable_cron_billing',
+ 'section' => 'billing',
+ 'description' => 'Disable billing and collection from being run by freeside-daily and freeside-monthly, while still allowing other actions to run, such as notifications and backup.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_domain-edit_domain',
+ 'section' => '',
+ 'description' => 'Enable domain renaming',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'enable_legacy_prepaid_income',
+ 'section' => '',
+ 'description' => "Enable legacy prepaid income reporting. Only useful when you have imported pre-Freeside packages with longer-than-monthly duration, and need to do prepaid income reporting on them before they've been invoiced the first time.",
+ 'type' => 'checkbox',
+ },
+
+ { key => "apacheroot", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "apachemachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "apachemachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "bindprimary", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "bindsecondaries", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "bsdshellmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "cyrus", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "cp_app", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "erpcdmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "icradiusmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "icradius_mysqldest", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "icradius_mysqlsource", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "icradius_secrets", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "maildisablecatchall", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "mxmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "nsmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "arecords", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "cnamerecords", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "nismachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "qmailmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "radiusmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "sendmailconfigpath", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "sendmailmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "sendmailrestart", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "shellmachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "shellmachine-useradd", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "shellmachine-userdel", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "shellmachine-usermod", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "shellmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "radiusprepend", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "textradiusprepend", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "username_policy", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "vpopmailmachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "vpopmailrestart", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "safe-part_pkg", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "selfservice_server-quiet", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "signup_server-quiet", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "signup_server-email", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "vonage-username", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "vonage-password", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+ { key => "vonage-fromnumber", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
+
+);
+
+1;
+
diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm
new file mode 100644
index 0000000..12c82e6
--- /dev/null
+++ b/FS/FS/ConfDefaults.pm
@@ -0,0 +1,86 @@
+package FS::ConfDefaults;
+
+=head1 NAME
+
+FS::ConfDefaults - Freeside configuration default and available values
+
+=head1 SYNOPSIS
+
+ use FS::ConfDefaults;
+
+ @avail_cust_fields = FS::ConfDefaults->cust_fields_avail();
+
+=head1 DESCRIPTION
+
+Just a small class to keep config default and available values
+
+=head1 METHODS
+
+=over 4
+
+=item cust_fields_avail
+
+Returns a list, suitable for assigning to a hash, of available values and
+labels for customer fields values.
+
+=cut
+
+# XXX should use msgcat for "Day phone" and "Night phone", but how?
+sub cust_fields_avail { (
+
+ 'Cust. Status | Customer' =>
+ 'Status | Last, First or Company (Last, First)',
+ 'Cust# | Cust. Status | Customer' =>
+ 'custnum | Status | Last, First or Company (Last, First)',
+
+ 'Cust. Status | Name | Company' =>
+ 'Status | Last, First | Company',
+ 'Cust# | Cust. Status | Name | Company' =>
+ 'custnum | Status | Last, First | Company',
+
+ 'Cust. Status | (bill) Customer | (service) Customer' =>
+ 'Status | Last, First or Company (Last, First) | (same for service contact if present)',
+ 'Cust# | Cust. Status | (bill) Customer | (service) Customer' =>
+ 'custnum | Status | Last, First or Company (Last, First) | (same for service contact if present)',
+
+ 'Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' =>
+ 'Status | Last, First | Company | (same for service contact if present)',
+ 'Cust# | Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' =>
+ 'custnum | Status | Last, First | Company | (same for service contact if present)',
+
+ 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Invoicing email(s)' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | Day phone | Night phone | Invoicing email(s)',
+
+ 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Fax number | Invoicing email(s) | Payment Type' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | (all phones) | Invoicing email(s) | Payment Type',
+
+ 'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Fax number | Invoicing email(s) | Payment Type | Current Balance' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | (all phones) | Invoicing email(s) | Payment Type | Current Balance',
+
+ 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | Invoicing email(s)' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | Day phone | Night phone | (service address) | Invoicing email(s)',
+
+ 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (bill) Fax number | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | (service) Fax number | Invoicing email(s) | Payment Type' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | (all phones) | (service address) | Invoicing email(s) | Payment Type',
+
+ 'Cust# | Cust. Status | (bill) Name | (bill) Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Day phone | (bill) Night phone | (bill) Fax number | (service) Name | (service) Company | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Day phone | (service) Night phone | (service) Fax number | Invoicing email(s) | Payment Type | Current Balance' =>
+ 'custnum | Status | Last, First | Company | (all address fields) | (all phones) | (service address) | Invoicing email(s) | Payment Type | Current Balance',
+
+ 'Invoicing email(s)' => 'Invoicing email(s)',
+ 'Cust# | Invoicing email(s)' => 'custnum | Invoicing email(s)',
+
+); }
+
+=back
+
+=head1 BUGS
+
+Not yet.
+
+=head1 SEE ALSO
+
+L<FS::Conf>
+
+=cut
+
+1;
diff --git a/FS/FS/ConfItem.pm b/FS/FS/ConfItem.pm
new file mode 100644
index 0000000..a0e997a
--- /dev/null
+++ b/FS/FS/ConfItem.pm
@@ -0,0 +1,63 @@
+package FS::ConfItem;
+
+=head1 NAME
+
+FS::ConfItem - Configuration 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/Conf_compat17.pm b/FS/FS/Conf_compat17.pm
new file mode 100644
index 0000000..15d4738
--- /dev/null
+++ b/FS/FS/Conf_compat17.pm
@@ -0,0 +1,2520 @@
+package FS::Conf_compat17;
+
+use vars qw($default_dir $base_dir @config_items @card_types $DEBUG );
+use IO::File;
+use File::Basename;
+use FS::ConfItem;
+use FS::ConfDefaults;
+
+$base_dir = '%%%FREESIDE_CONF%%%';
+$default_dir = '%%%FREESIDE_CONF%%%';
+
+
+$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,
+ 'base_dir' => $base_dir,
+ };
+ bless ($self, $class);
+}
+
+=item dir
+
+Returns the conf 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 base_dir
+
+Returns the base directory. By default this is /usr/local/etc/freeside.
+
+=cut
+
+sub base_dir {
+ my($self) = @_;
+ my $base_dir = $self->{base_dir};
+ -e $base_dir or die "FATAL: $base_dir doesn't exist!";
+ -d $base_dir or die "FATAL: $base_dir isn't a directory!";
+ -r $base_dir or die "FATAL: Can't read $base_dir!";
+ -x $base_dir or die "FATAL: $base_dir not searchable (executable)!";
+ $base_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 config_binary KEY
+
+Returns the exact scalar value for key.
+
+=cut
+
+sub config_binary {
+ my($self,$file)=@_;
+ my($dir)=$self->dir;
+ my $fh = new IO::File "<$dir/$file" or return;
+ local $/;
+ my $content = <$fh>;
+ $content;
+}
+
+=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 HTML template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ }
+ } glob($self->dir. '/invoice_html_*')
+ ),
+ ( map {
+ my $basename = basename($_);
+ $basename =~ /^(.*)$/;
+ $basename = $1;
+ ($latexname = $basename ) =~ s/latex/html/;
+ new FS::ConfItem {
+ 'key' => $basename,
+ 'section' => 'billing',
+ 'description' => "Alternate Notes section for HTML invoices. Defaults to the same data in $latexname if not specified.",
+ 'type' => 'textarea',
+ }
+ } glob($self->dir. '/invoice_htmlnotes_*')
+ ),
+ ( 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
+
+#Business::CreditCard
+@card_types = (
+ "VISA card",
+ "MasterCard",
+ "Discover card",
+ "American Express card",
+ "Diner's Club/Carte Blanche",
+ "enRoute",
+ "JCB",
+ "BankCard",
+ "Switch",
+ "Solo",
+);
+
+@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' => 'encryption',
+ 'section' => 'billing',
+ 'description' => 'Enable encryption of credit cards.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'encryptionmodule',
+ 'section' => 'billing',
+ 'description' => 'Use which module for encryption?',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'encryptionpublickey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Public Key - Required if Encryption is turned on.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'encryptionprivatekey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Private Key - Including this will enable the "Bill Now" feature. However if the system is compromised, a hacker can use this key to decode the stored credit card information. This is generally not a good idea.',
+ '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' => 'business-onlinepayment-email-override',
+ 'section' => 'billing',
+ 'description' => 'Email address used instead of customer email address when submitting a BOP transaction.',
+ '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' => 'business-onlinepayment-email_customer',
+ 'section' => 'billing',
+ 'description' => 'Controls the "email_customer" flag used by some Business::OnlinePayment processors to enable customer receipts.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'countrydefault',
+ 'section' => 'UI',
+ 'description' => 'Default two-letter country code (if not supplied, the default is `US\')',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'date_format',
+ 'section' => 'UI',
+ 'description' => 'Format for displaying dates',
+ 'type' => 'select',
+ 'select_hash' => [
+ '%m/%d/%Y' => 'MM/DD/YYYY',
+ '%Y/%m/%d' => 'YYYY/MM/DD',
+ ],
+ },
+
+ {
+ '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 the 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' => 'deleteinvoices',
+ 'section' => 'UI',
+ 'description' => 'Enable invoices deletions. Be very careful! Deleting an invoice will remove all traces that the invoice ever existed! Normally, you would apply a credit against the invoice instead.', #invoice voiding?
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'deletepayments',
+ 'section' => 'billing',
+ 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. 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' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to 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' => 'deleterefunds',
+ 'section' => 'billing',
+ 'description' => 'Enable deletion of unclosed refunds. Be very careful! Only delete refunds that were data-entry errors, not adjustments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unapplypayments',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unapplycredits',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to nable "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' => 'emailinvoiceautoalways',
+ 'section' => 'billing',
+ 'description' => 'Automatically adds new accounts to the email invoice list even when the list contains email addresses',
+ '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 authentication 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_subject',
+ 'section' => 'billing',
+ 'description' => 'Subject: header on email invoices. Defaults to "Invoice". The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
+ '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_html',
+ 'section' => 'billing',
+ 'description' => 'Optional HTML template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_htmlnotes',
+ 'section' => 'billing',
+ 'description' => 'Notes section for HTML invoices. Defaults to the same data in invoice_latexnotes if not specified.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_htmlfooter',
+ 'section' => 'billing',
+ 'description' => 'Footer for HTML invoices. Defaults to the same data in invoice_latexfooter if not specified.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_htmlreturnaddress',
+ 'section' => 'billing',
+ 'description' => 'Return address for HTML invoices. Defaults to the same data in invoice_latexreturnaddress if not specified.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latex',
+ 'section' => 'billing',
+ 'description' => 'Optional LaTeX template for typeset PostScript invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ '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_latexcoupon',
+ 'section' => 'billing',
+ 'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexreturnaddress',
+ 'section' => 'billing',
+ 'description' => 'Return address 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_email_pdf',
+ 'section' => 'billing',
+ 'description' => 'Send PDF invoice as an attachment to emailed invoices. By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
+ 'type' => 'checkbox'
+ },
+
+ {
+ 'key' => 'invoice_email_pdf_note',
+ 'section' => 'billing',
+ 'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF 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',
+ '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. Payment receipts are sent to the customer email invoice destination(s) when a payment is received. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available: <ul><li><code>$date</code> <li><code>$name</code> <li><code>$paynum</code> - Freeside payment number <li><code>$paid</code> - Amount of payment <li><code>$payby</code> - Payment type (Card, Check, Electronic check, etc.) <li><code>$payinfo</code> - Masked credit card number or check number <li><code>$balance</code> - New balance</ul>',
+ 'type' => [qw( checkbox 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' => 'lpr-postscript_prefix',
+ 'section' => 'billing',
+ 'description' => 'Raw printer commands prepended to the beginning of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'lpr-postscript_suffix',
+ 'section' => 'billing',
+ 'description' => 'Raw printer commands added to the end of postscript print jobs (evaluated as a double-quoted perl string - backslash escapes are available)',
+ 'type' => 'text',
+ },
+
+ {
+ '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 TXT)} },
+ { 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' => 'password-noampersand',
+ 'section' => 'password',
+ 'description' => 'Disallow ampersands in passwords',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'password-noexclamation',
+ 'section' => 'password',
+ 'description' => 'Disallow exclamations in passwords (Not setting this could break old text Livingston or Cistron Radius servers)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => 'unsuspend-always_adjust_next_bill_date',
+ 'section' => 'billing',
+ 'description' => 'Global override that causes unsuspensions to always adjust the next bill date under any circumstances. This is now controlled on a per-package bases - probably best not to use this option unless you are a legacy installation that requires this behaviour.',
+ '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-percent',
+ 'section' => 'username',
+ 'description' => 'Allow the percent character (%) 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' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, obsolete. Used to validate package definition setup and recur expressions against a preset list. Useful for webdemos, annoying to powerusers.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-colon',
+ 'section' => 'username',
+ 'description' => 'Allow the colon character (:) in usernames.',
+ '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' => 'show_stateid',
+ 'section' => 'UI',
+ 'description' => "Turns on display/collection of driver's license/state issued id numbers in the web interface. Sometimes required by electronic check (ACH) processors.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'show_bankstate',
+ 'section' => 'UI',
+ 'description' => "Turns on display/collection of state for bank accounts in the web interface. Sometimes required by electronic check (ACH) processors.",
+ '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 agent for the signup server',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::agent;
+ map { $_->agentnum => $_->agent }
+ FS::Record::qsearch('agent', { disabled=>'' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::agent;
+ my $agent = FS::Record::qsearchs(
+ 'agent', { 'agentnum'=>shift }
+ );
+ $agent ? $agent->agent : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_server-default_refnum',
+ 'section' => '',
+ 'description' => 'Default advertising source for the signup server',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::part_referral;
+ map { $_->refnum => $_->referral }
+ FS::Record::qsearch( 'part_referral',
+ { 'disabled' => '' }
+ );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::part_referral;
+ my $part_referral = FS::Record::qsearchs(
+ 'part_referral', { 'refnum'=>shift } );
+ $part_referral ? $part_referral->referral : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_server-default_pkgpart',
+ 'section' => '',
+ 'description' => 'Default pakcage for the signup server',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::part_pkg;
+ map { $_->pkgpart => $_->pkg.' - '.$_->comment }
+ FS::Record::qsearch( 'part_pkg',
+ { 'disabled' => ''}
+ );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::part_pkg;
+ my $part_pkg = FS::Record::qsearchs(
+ 'part_pkg', { 'pkgpart'=>shift }
+ );
+ $part_pkg
+ ? $part_pkg->pkg.' - '.$part_pkg->comment
+ : '';
+ },
+ },
+
+ {
+ '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 do not provision accounts which subsequently have a balance.',
+ 'type' => 'checkbox',
+ },
+ {
+ 'key' => 'signup_server-classnum2',
+ 'section' => '',
+ 'description' => 'Package Class for first optional purchase',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::pkg_class;
+ map { $_->classnum => $_->classname }
+ FS::Record::qsearch('pkg_class', {} );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::pkg_class;
+ my $pkg_class = FS::Record::qsearchs(
+ 'pkg_class', { 'classnum'=>shift }
+ );
+ $pkg_class ? $pkg_class->classname : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_server-classnum3',
+ 'section' => '',
+ 'description' => 'Package Class for second optional purchase',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::pkg_class;
+ map { $_->classnum => $_->classname }
+ FS::Record::qsearch('pkg_class', {} );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::pkg_class;
+ my $pkg_class = FS::Record::qsearchs(
+ 'pkg_class', { 'classnum'=>shift }
+ );
+ $pkg_class ? $pkg_class->classname : '';
+ },
+ },
+
+ {
+ 'key' => 'backend-realtime',
+ 'section' => '',
+ 'description' => 'Run billing for backend signups immediately.',
+ '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' => 'require_taxclasses',
+ 'section' => 'billing',
+ 'description' => 'Require a taxclass to be entered for every package',
+ '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/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code></ul>',
+ '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' => 'welcome_letter',
+ 'section' => '',
+ 'description' => 'Optional LaTex template file for a printed welcome letter. A welcome letter is printed the first time a cust_pkg record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation and the billing documentation for details on the template substitution language. A variable exists for each fieldname in the customer record (<code>$first, $last, etc</code>). The following additional variables are available<ul><li><code>$payby</code> - a friendler represenation of the field<li><code>$payinfo</code> - the masked payment information<li><code>$expdate</code> - the time at which the payment method expires (a UNIX timestamp)<li><code>$returnaddress</code> - the invoice return address for this customer\'s agent</ul>',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'warning_email',
+ 'section' => '',
+ 'description' => 'Template file for warning email. Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'warning_email-from',
+ 'section' => '',
+ 'description' => 'From: address header for warning email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-cc',
+ 'section' => '',
+ 'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-subject',
+ 'section' => '',
+ 'description' => 'Subject: header for warning email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'warning_email-mimetype',
+ 'section' => '',
+ 'description' => 'MIME type for warning email',
+ 'type' => 'select',
+ 'select_enum' => [ 'text/plain', 'text/html' ],
+ },
+
+ {
+ 'key' => 'payby',
+ 'section' => 'billing',
+ 'description' => 'Available payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB BILL CASH WEST MCRD COMP) ],
+ },
+
+ {
+ '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 CASH WEST MCRD COMP HIDE) ],
+ },
+
+ {
+ 'key' => 'paymentforcedtobatch',
+ 'section' => 'deprecated',
+ 'description' => 'See batch-enable_payby and realtime-disable_payby. Used to (for CHEK): Cause per customer payment entry to be forced to a batch processor rather than performed realtime.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' ],
+ },
+
+ #http://dev.coova.org/svn/coova-chilli/doc/dictionary.chillispot
+ {
+ 'key' => 'radius-chillispot-max',
+ 'section' => '',
+ 'description' => 'Enable ChilliSpot (and CoovaChilli) Max attributes, specifically ChilliSpot-Max-{Input,Output,Total}-{Octets,Gigawords}.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, enable the <i>Complimentary customer</i> access right instead. Was: 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' => 'credit_card-recurring_billing_flag',
+ 'section' => 'billing',
+ 'description' => 'This controls when the system passes the "recurring_billing" flag on credit card transactions. If supported by your processor (and the Business::OnlinePayment processor module), passing the flag indicates this is a recurring transaction and may turn off the CVV requirement. ',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'actual_oncard' => 'Default/classic behavior: set the flag if a customer has actual previous charges on the card.',
+ 'transaction_is_recur' => 'Set the flag if the transaction itself is recurring, irregardless of previous charges on the card.',
+ ],
+ },
+
+ {
+ 'key' => 'credit_card-recurring_billing_acct_code',
+ 'section' => 'billing',
+ 'description' => 'When the "recurring billing" flag is set, also set the "acct_code" to "rebill". Useful for reporting purposes with supported gateways (PlugNPay, others?)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ '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' => \@card_types,
+ },
+
+ {
+ '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). disabled turns off duplicate checking completely and is STRONGLY NOT RECOMMENDED unless you REALLY need to turn this off.',
+ 'type' => 'select',
+ 'select_enum' => [ 'none', 'username', 'username@domain', 'disabled' ],
+ },
+
+ {
+ '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', ],
+ },
+
+ {
+ 'key' => 'ticket_system',
+ 'section' => '',
+ 'description' => 'Ticketing system integration. <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="../docs/install-rt">integrated ticketing installation instructions</a>). <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
+ 'type' => 'select',
+ #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
+ 'select_enum' => [ '', qw(RT_Internal RT_External) ],
+ },
+
+ {
+ 'key' => 'ticket_system-default_queueid',
+ 'section' => '',
+ 'description' => 'Default queue used when creating new customer tickets.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queues();
+ } else {
+ ();
+ }
+ },
+ 'option_sub' => sub {
+ my $conf = new FS::Conf;
+ if ( $conf->config('ticket_system') ) {
+ eval "use FS::TicketSystem;";
+ die $@ if $@;
+ FS::TicketSystem->queue(shift);
+ } else {
+ '';
+ }
+ },
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field',
+ 'section' => '',
+ 'description' => 'Custom field from the ticketing system to use as a custom priority classification.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field-values',
+ 'section' => '',
+ 'description' => 'Values for the custom field from the ticketing system to break down and sort customer ticket lists.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'ticket_system-custom_priority_field_queue',
+ 'section' => '',
+ 'description' => 'Ticketing system queue in which the custom field specified in ticket_system-custom_priority_field is located.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'ticket_system-rt_external_datasrc',
+ 'section' => '',
+ 'description' => 'With external RT integration, the DBI data source for the external RT installation, for example, <code>DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt</code>',
+ 'type' => 'text',
+
+ },
+
+ {
+ 'key' => 'ticket_system-rt_external_url',
+ 'section' => '',
+ 'description' => 'With external RT integration, the URL for the external RT installation, for example, <code>https://rt.example.com/rt</code>',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'company_name',
+ 'section' => 'required',
+ 'description' => 'Your company name',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'echeck-void',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cc-void',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable local-only voiding of credit card payments in addition to refunds against the payment gateway',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unvoid',
+ 'section' => 'deprecated',
+ 'description' => '<B>DEPRECATED</B>, now controlled by ACLs. Used to enable unvoiding of voided payments',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'address2-search',
+ 'section' => 'UI',
+ 'description' => 'Enable a "Unit" search box which searches the second address field',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_address2',
+ 'section' => 'UI',
+ 'description' => 'Second address field is required (on service address only, if billing and service addresses differ). Also enables "Unit" labeling of address2 on customer view and edit pages. Useful for multi-tenant applications. See also: address2-search',
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'referral_credit',
+ 'section' => 'billing',
+ 'description' => "Enables one-time referral credits in the amount of one month <i>referred</i> customer's recurring fee (irregardless of frequency).",
+ 'type' => 'checkbox',
+ },
+
+ { 'key' => 'selfservice_server-cache_module',
+ 'section' => '',
+ 'description' => 'Module used to store self-service session information. All modules handle any number of self-service servers. Cache::SharedMemoryCache is appropriate for a single database / single Freeside server. Cache::FileCache is useful for multiple databases on a single server, or when IPC::ShareLite is not available (i.e. FreeBSD).', # _Database stores session information in the database and is appropriate for multiple Freeside servers, but may be slower.',
+ 'type' => 'select',
+ 'select_enum' => [ 'Cache::SharedMemoryCache', 'Cache::FileCache', ], # '_Database' ],
+ },
+
+ {
+ 'key' => 'hylafax',
+ 'section' => 'billing',
+ 'description' => 'Options for a HylaFAX server to enable the FAX invoice destination. They should be in the form of a space separated list of arguments to the Fax::Hylafax::Client::sendfax subroutine. You probably shouldn\'t override things like \'docfile\'. *Note* Only supported when using typeset invoices (see the invoice_latex configuration option).',
+ 'type' => [qw( checkbox textarea )],
+ },
+
+ {
+ 'key' => 'cust_bill-ftpformat',
+ 'section' => 'billing',
+ 'description' => 'Enable FTP of raw invoice data - format.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'default', 'billco', ],
+ },
+
+ {
+ 'key' => 'cust_bill-ftpserver',
+ 'section' => 'billing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftpusername',
+ 'section' => 'billing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftppassword',
+ 'section' => 'billing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-ftpdir',
+ 'section' => 'billing',
+ 'description' => 'Enable FTP of raw invoice data - server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-spoolformat',
+ 'section' => 'billing',
+ 'description' => 'Enable spooling of raw invoice data - format.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'default', 'billco', ],
+ },
+
+ {
+ 'key' => 'cust_bill-spoolagent',
+ 'section' => 'billing',
+ 'description' => 'Enable per-agent spooling of raw invoice data.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_suspend',
+ 'section' => 'billing',
+ 'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_unsuspend',
+ 'section' => 'billing',
+ 'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored). Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-usage_threshold',
+ 'section' => 'billing',
+ 'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder. Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd. Defaults to 80.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust-fields',
+ 'section' => 'UI',
+ 'description' => 'Which customer fields to display on reports by default',
+ 'type' => 'select',
+ 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ],
+ },
+
+ {
+ 'key' => 'cust_pkg-display_times',
+ 'section' => 'UI',
+ 'description' => 'Display full timestamps (not just dates) for customer packages. Useful if you are doing real-time things like hourly prepaid.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-edit_uid',
+ 'section' => 'shell',
+ 'description' => 'Allow UID editing.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_acct-edit_gid',
+ 'section' => 'shell',
+ 'description' => 'Allow GID editing.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'zone-underscore',
+ 'section' => 'BIND',
+ 'description' => 'Allow underscores in zone names. As underscores are illegal characters in zone names, this option is not recommended.',
+ 'type' => 'checkbox',
+ },
+
+ #these should become per-user...
+ {
+ 'key' => 'vonage-username',
+ 'section' => '',
+ 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
+ 'type' => 'text',
+ },
+ {
+ 'key' => 'vonage-password',
+ 'section' => '',
+ 'description' => 'Vonage Click2Call username (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
+ 'type' => 'text',
+ },
+ {
+ 'key' => 'vonage-fromnumber',
+ 'section' => '',
+ 'description' => 'Vonage Click2Call number (see <a href="https://secure.click2callu.com/">https://secure.click2callu.com/</a>)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'echeck-nonus',
+ 'section' => 'billing',
+ 'description' => 'Disable ABA-format account checking for Electronic Check payment info',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'voip-cust_cdr_spools',
+ 'section' => '',
+ 'description' => 'Enable the per-customer option for individual CDR spools.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_forward-arbitrary_dst',
+ 'section' => '',
+ 'description' => "Allow forwards to point to arbitrary strings that don't necessarily look like email addresses. Only used when using forwards for weird, non-email things.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'tax-ship_address',
+ 'section' => 'billing',
+ 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead. Note: Tax reports can take a long time when enabled.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-ship_address',
+ 'section' => 'billing',
+ 'description' => 'Enable this switch to include the ship address on the invoice.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'invoice-unitprice',
+ 'section' => 'billing',
+ 'description' => 'This switch enables unit pricing on the invoice.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'postal_invoice-fee_pkgpart',
+ 'section' => 'billing',
+ 'description' => 'This allows selection of a package to insert on invoices for customers with postal invoices selected.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::part_pkg;
+ map { $_->pkgpart => $_->pkg }
+ FS::Record::qsearch('part_pkg', { disabled=>'' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::part_pkg;
+ my $part_pkg = FS::Record::qsearchs(
+ 'part_pkg', { 'pkgpart'=>shift }
+ );
+ $part_pkg ? $part_pkg->pkg : '';
+ },
+ },
+
+ {
+ 'key' => 'postal_invoice-recurring_only',
+ 'section' => 'billing',
+ 'description' => 'The postal invoice fee is omitted on invoices without recurring charges when this is set',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'batch-enable',
+ 'section' => 'deprecated', #make sure batch-enable_payby is set for
+ #everyone before removing
+ 'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'batch-enable_payby',
+ 'section' => 'billing',
+ 'description' => 'Enable batch processing for the specified payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( CARD CHEK )],
+ },
+
+ {
+ 'key' => 'realtime-disable_payby',
+ 'section' => 'billing',
+ 'description' => 'Disable realtime processing for the specified payment types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( CARD CHEK )],
+ },
+
+ {
+ 'key' => 'batch-default_format',
+ 'section' => 'billing',
+ 'description' => 'Default format for batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch',
+ 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
+ 'ach-spiritone',
+ ]
+ },
+
+ {
+ 'key' => 'batch-fixed_format-CARD',
+ 'section' => 'billing',
+ 'description' => 'Fixed (unchangeable) format for credit card batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP' ,
+ 'csv-chase_canada-E-xactBatch', 'BoM', 'PAP' ]
+ },
+
+ {
+ 'key' => 'batch-fixed_format-CHEK',
+ 'section' => 'billing',
+ 'description' => 'Fixed (unchangeable) format for electronic check batches.',
+ 'type' => 'select',
+ 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM', 'PAP',
+ 'ach-spiritone',
+ ]
+ },
+
+ {
+ 'key' => 'batch-increment_expiration',
+ 'section' => 'billing',
+ 'description' => 'Increment expiration date years in batches until cards are current. Make sure this is acceptable to your batching provider before enabling.',
+ 'type' => 'checkbox'
+ },
+
+ {
+ 'key' => 'batchconfig-BoM',
+ 'section' => 'billing',
+ 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'batchconfig-PAP',
+ 'section' => 'billing',
+ 'description' => 'Configuration for PAP batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'batchconfig-csv-chase_canada-E-xactBatch',
+ 'section' => 'billing',
+ 'description' => 'Gateway ID for Chase Canada E-xact batching',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'payment_history-years',
+ 'section' => 'UI',
+ 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-use_comments',
+ 'section' => 'UI',
+ 'description' => 'Display free form comments on the customer edit screen. Useful as a scratch pad.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-disable_notes',
+ 'section' => 'UI',
+ 'description' => 'Disable new style customer notes - timestamped and user identified customer notes. Useful in tracking who did what.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main_note-display_times',
+ 'section' => 'UI',
+ 'description' => 'Display full timestamps (not just dates) for customer notes.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-ticket_statuses',
+ 'section' => 'UI',
+ 'description' => 'Show tickets with these statuses on the customer view page.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [qw( new open stalled resolved rejected deleted )],
+ },
+
+ {
+ 'key' => 'cust_main-max_tickets',
+ 'section' => 'UI',
+ 'description' => 'Maximum number of tickets to show on the customer view page.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-skeleton_tables',
+ 'section' => '',
+ 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cust_main-skeleton_custnum',
+ 'section' => '',
+ 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_main-enable_birthdate',
+ 'section' => 'UI',
+ 'descritpion' => 'Enable tracking of a birth date with each customer record',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'support-key',
+ 'section' => '',
+ 'description' => 'A support key enables access to commercial services delivered over the network, such as the payroll module, access to the internal ticket system, priority support and optional backups.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'card-types',
+ 'section' => 'billing',
+ 'description' => 'Select one or more card types to enable only those card types. If no card types are selected, all card types are available.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => \@card_types,
+ },
+
+ {
+ 'key' => 'dashboard-toplist',
+ 'section' => 'UI',
+ 'description' => 'List of items to display on the top of the front page',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'impending_recur_template',
+ 'section' => 'billing',
+ 'description' => 'Template file for alerts about looming first time recurrant billing. See the <a href="http://search.cpan.org/~mjd/Text-Template.pm">Text::Template</a> documentation for details on the template substitition language. Also see packages with a <a href="../browse/part_pkg.cgi">flat price plan</a> The following variables are available<ul><li><code>$packages</code> allowing <code>$packages->[0]</code> thru <code>$packages->[n]</code> <li><code>$package</code> the first package, same as <code>$packages->[0]</code> <li><code>$recurdates</code> allowing <code>$recurdates->[0]</code> thru <code>$recurdates->[n]</code> <li><code>$recurdate</code> the first recurdate, same as <code>$recurdate->[0]</code> <li><code>$first</code> <li><code>$last</code></ul>',
+# <li><code>$payby</code> <li><code>$expdate</code> most likely only confuse
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'selfservice-session_timeout',
+ 'section' => '',
+ 'description' => 'Self-service session timeout. Defaults to 1 hour.',
+ 'type' => 'select',
+ 'select_enum' => [ '1 hour', '2 hours', '4 hours', '8 hours', '1 day', '1 week', ],
+ },
+
+ {
+ 'key' => 'disable_setup_suspended_pkgs',
+ 'section' => 'billing',
+ 'description' => 'Disables charging of setup fees for suspended packages.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'password-generated-allcaps',
+ 'section' => 'password',
+ 'description' => 'Causes passwords automatically generated to consist entirely of capital letters',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'datavolume-forcemegabytes',
+ 'section' => 'UI',
+ 'description' => 'All data volumes are expressed in megabytes',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'datavolume-significantdigits',
+ 'section' => 'UI',
+ 'description' => 'number of significant digits to use to represent data volumes',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_void_after',
+ 'section' => 'billing',
+ 'description' => 'Number of seconds after which freeside won\'t attempt to VOID a payment first when performing a refund.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_line_item_date_ranges',
+ 'section' => 'billing',
+ 'description' => 'Prevent freeside from automatically generating date ranges on invoice line items.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_phone',
+ 'section' => '',
+ 'description' => 'Require daytime or night for all customer records.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-require_invoicing_list_email',
+ 'section' => '',
+ 'description' => 'Email address field is required: require at least one invoicing email address for all customer records.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cancel_credit_type',
+ 'section' => 'billing',
+ 'description' => 'The group to use for new, automatically generated credit reasons resulting from cancellation.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'referral_credit_type',
+ 'section' => 'billing',
+ 'description' => 'The group to use for new, automatically generated credit reasons resulting from referrals.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'signup_credit_type',
+ 'section' => 'billing',
+ 'description' => 'The group to use for new, automatically generated credit reasons resulting from signup and self-service declines.',
+ 'type' => 'select-sub',
+ 'options_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ map { $_->typenum => $_->type }
+ FS::Record::qsearch('reason_type', { class=>'R' } );
+ },
+ 'option_sub' => sub { require FS::Record;
+ require FS::reason_type;
+ my $reason_type = FS::Record::qsearchs(
+ 'reason_type', { 'typenum' => shift }
+ );
+ $reason_type ? $reason_type->type : '';
+ },
+ },
+
+ {
+ 'key' => 'cust_main-agent_custid-format',
+ 'section' => '',
+ 'description' => 'Enables searching of various formatted values in cust_main.agent_custid',
+ 'type' => 'select',
+ 'select_hash' => [
+ '' => 'Numeric only',
+ 'ww?d+' => 'Numeric with one or two letter prefix',
+ ],
+ },
+
+ {
+ 'key' => 'card_masking_method',
+ 'section' => 'UI',
+ 'description' => 'Digits to display when masking credit cards. Note that the first six digits are necessary to canonically identify the credit card type (Visa/MC, Amex, Discover, Maestro, etc.) in all cases. The first four digits can identify the most common credit card types in most cases (Visa/MC, Amex, and Discover). The first two digits can distinguish between Visa/MC and Amex.',
+ 'type' => 'select',
+ 'select_hash' => [
+ '' => '123456xxxxxx1234',
+ 'first6last2' => '123456xxxxxxxx12',
+ 'first4last4' => '1234xxxxxxxx1234',
+ 'first4last2' => '1234xxxxxxxxxx12',
+ 'first2last4' => '12xxxxxxxxxx1234',
+ 'first2last2' => '12xxxxxxxxxxxx12',
+ 'first0last4' => 'xxxxxxxxxxxx1234',
+ 'first0last2' => 'xxxxxxxxxxxxxx12',
+ ],
+ },
+
+ {
+ 'key' => 'disable_previous_balance',
+ 'section' => 'billing',
+ 'description' => 'Disable inclusion of previous balance lines on invoices',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'disable_acl_changes',
+ 'section' => '',
+ 'description' => 'Disable all ACL changes, for demos.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-edit_agent_custid',
+ 'section' => 'UI',
+ 'description' => 'Enable editing of the agent_custid field.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'cust_main-default_areacode',
+ 'section' => 'UI',
+ 'description' => 'Default area code for customers.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cust_bill-max_same_services',
+ 'section' => 'billing',
+ 'description' => 'Maximum number of the same service to list individually on invoices before condensing to a single line listing the number of services. Defaults to 5.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'suspend_email_admin',
+ 'section' => '',
+ 'description' => 'Destination admin email address to enable suspension notices',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'email_report-subject',
+ 'section' => '',
+ 'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sg-multicustomer_hack',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'sg-ping_username',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sg-ping_password',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sg-login_username',
+ 'section' => '',
+ 'description' => "Don't use this.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'queued-max_kids',
+ 'section' => '',
+ 'description' => 'Maximum number of queued processes. Defaults to 10.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cancelled_cust-noevents',
+ 'section' => 'billing',
+ 'description' => "Don't run events for cancelled customers",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_broadband-manage_link',
+ 'section' => 'UI',
+ 'description' => 'URL for svc_broadband "Manage Device" link. The following substitutions are available: $ip_addr.',
+ 'type' => 'text',
+ },
+
+);
+
+1;
+
diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm
new file mode 100644
index 0000000..a9b9da9
--- /dev/null
+++ b/FS/FS/Cron/alert_expiration.pm
@@ -0,0 +1,177 @@
+package FS::Cron::alert_expiration;
+
+use vars qw( @ISA @EXPORT_OK);
+use Exporter;
+use FS::Record qw(qsearch);
+use FS::Conf;
+use FS::cust_main;
+use FS::Misc;
+use Time::Local;
+use Date::Parse qw(str2time);
+
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( alert_expiration );
+
+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;
+
+sub alert_expiration {
+ my $conf = new FS::Conf;
+ my $smtpmachine = $conf->config('smtpmachine');
+
+ my %opt = @_;
+ my ($_date) = $opt{'d'} ? str2time($opt{'d'}) : $^T;
+ $_date += $opt{'y'} * 86400 if $opt{'y'};
+ my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($_date)) [0..5];
+ $mon++;
+
+ my $debug = 0;
+ $debug = 1 if $opt{'v'};
+ $debug = $opt{'l'} if $opt{'l'};
+
+ $FS::cust_main::DEBUG = $debug;
+
+ # Get a list of customers.
+
+ my %limit;
+ $limit{'agentnum'} = $opt{'a'} if $opt{'a'};
+ $limit{'payby'} = $opt{'p'} if $opt{'p'};
+
+ my @customers;
+
+ if(my @custnums = @ARGV) {
+ # We're given an explicit list of custnums, so select those. Then check against
+ # -a and -p to avoid doing anything unexpected.
+ foreach (@custnums) {
+ my $customer = FS::cust_main->by_key($_);
+ if($customer and (!$opt{'a'} or $customer->agentnum == $opt{'a'})
+ and (!$opt{'p'} or $customer->payby eq $opt{'p'}) ) {
+ push @customers, $customer;
+ }
+ }
+ }
+ else { # no @ARGV
+ @customers = qsearch('cust_main', \%limit);
+ }
+ return if(!@customers);
+ foreach my $customer (@customers) {
+ my $paydate = $customer->paydate;
+ next if $paydate =~ /^\s*$/; # skip empty expiration dates
+
+ my $custnum = $customer->custnum;
+ my $first = $customer->first;
+ my $last = $customer->last;
+ my $company = $customer->company;
+ my $payby = $customer->payby;
+ my $payinfo = $customer->payinfo;
+ my $daytime = $customer->daytime;
+ my $night = $customer->night;
+
+ my ($paymonth, $payyear) = $customer->paydate_monthyear;
+ $paymonth--; # localtime() convention
+ $payday = 1; # This is enforced by FS::cust_main::check.
+ my $expire_time;
+ if($payby eq 'CARD' || $payby eq 'DCRD') {
+ # Credit cards expire at the end of the month/year.
+ if($paymonth == 11) {
+ $payyear++;
+ $paymonth = 0;
+ } else {
+ $paymonth++;
+ }
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear) - 1;
+ }
+ else {
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+ }
+
+ if (grep { $expire_time < $_date + $_ &&
+ $expire_time > $_date + $_ - $window_time }
+ ($warning_time, $urgent_time, $panic_time) ) {
+ my $agentnum = $customer->agentnum;
+ $mail_sender = $conf->config('invoice_from', $agentnum);
+ $failure_recipient = $conf->config('invoice_from', $agentnum)
+ || 'postmaster';
+
+ my @alerter_template = $conf->config('alerter_template', $agentnum)
+ 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 Text::Template object: $Text::Template::ERROR";
+
+ $alerter->compile()
+ or die "can't compile template: $Text::Template::ERROR";
+
+ my @packages = $customer->ncancelled_pkgs;
+ if(@packages) {
+ my @invoicing_list = $customer->invoicing_list;
+ my @to_addrs = grep { $_ ne 'POST' } @invoicing_list;
+ if(@to_addrs) {
+ # Set up template fields.
+ my %fill_in;
+ $fill_in{$_} = $customer->getfield($_)
+ foreach(qw(first last company));
+ $fill_in{'expdate'} = $expire_time;
+ $fill_in{'company_name'} = $conf->config('company_name', $agentnum);
+ $fill_in{'company_address'} =
+ join("\n",$conf->config('company_address',$agentnum))."\n";
+ if($payby eq 'CARD' || $payby eq 'DCRD') {
+ $fill_in{'payby'} = "credit card (".
+ substr($customer->payinfo, 0, 2) . "xxxxxxxxxx" .
+ substr($payinfo, -4) . ")";
+ }
+ elsif($payby eq 'COMP') {
+ $fill_in{'payby'} = 'complimentary account';
+ }
+ else {
+ $fill_in{'payby'} = 'current method';
+ }
+ # Send it already!
+ my $error = FS::Misc::send_email (
+ from => $mail_sender,
+ to => [ @to_addrs ],
+ subject => 'Billing Arrangement Expiration',
+ body => [ $alerter->fill_in( HASH => \%fill_in ) ],
+ );
+ die "can't send expiration alert: $error"
+ if $error;
+ }
+ else { # if(@to_addrs)
+ push @{$agent_failure_body{$customer->agentnum}},
+ sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
+ $custnum,
+ $first . " " . $last . " " . $company,
+ $payby,
+ $paydate,
+ $daytime,
+ $night );
+ }
+ } # if(@packages)
+ } # if(expired)
+ } # foreach(@customers)
+
+ # Failure notification
+ foreach my $agentnum (keys %agent_failure_body) {
+ $mail_sender = $conf->config('invoice_from', $agentnum)
+ if($conf->exists('invoice_from', $agentnum));
+ $failure_recipient = $conf->config('invoice_from', $agentnum)
+ if($conf->exists('invoice_from', $agentnum));
+ my $error = FS::Misc::send_email (
+ from => $mail_sender,
+ to => $failure_recipient,
+ subject => 'Unnotified Billing Arrangement Expirations',
+ body => [ @{$agent_failure_body{$agentnum}} ],
+ );
+ die "can't send alerter failure email to $failure_recipient: $error"
+ if $error;
+ }
+
+}
+
+1;
diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm
new file mode 100644
index 0000000..204069a
--- /dev/null
+++ b/FS/FS/Cron/backup.pm
@@ -0,0 +1,43 @@
+package FS::Cron::backup;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+use FS::UID qw(driver_name datasrc);
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( backup_scp );
+
+sub backup_scp {
+ 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);";
+ die $@ if $@;
+ 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;';
+ die $@ if $@;
+ my $gpg = new GnuPG;
+ $gpg->encrypt( plaintext => "/var/tmp/$database.sql",
+ output => "/var/tmp/$database.gpg",
+ recipient => $conf->config('dump-pgpid'),
+ );
+ chmod 0600, '/var/tmp/$database.gpg';
+ scp("/var/tmp/$database.gpg", $dest);
+ unlink "/var/tmp/$database.gpg" or die $!;
+ } else {
+ chmod 0600, '/var/tmp/$database.sql';
+ scp("/var/tmp/$database.sql", $dest);
+ }
+ unlink "/var/tmp/$database.sql" or die $!;
+ }
+}
+
+1;
diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm
new file mode 100644
index 0000000..62bb321
--- /dev/null
+++ b/FS/FS/Cron/bill.pm
@@ -0,0 +1,236 @@
+package FS::Cron::bill;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+use Date::Parse;
+use DBI 1.33; #The "clone" method was added in DBI 1.33.
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+use FS::queue;
+use FS::cust_main;
+use FS::part_event;
+use FS::part_event_condition;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw ( bill bill_where );
+
+#freeside-daily %opt:
+# -s: re-charge setup fees
+# -v: enable debugging
+# -l: debugging level
+# -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
+# -r: Multi-process mode dry run option
+# -g: Don't bill these pkgparts
+
+sub bill {
+ my %opt = @_;
+
+ my $check_freq = $opt{'check_freq'} || '1d';
+
+ my $debug = 0;
+ $debug = 1 if $opt{'v'};
+ $debug = $opt{'l'} if $opt{'l'};
+ $FS::cust_main::DEBUG = $debug;
+ #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('disable_cron_billing') ) {
+ warn "disable_cron_billing set, skipping billing\n" if $debug;
+ return;
+ }
+
+ #we're at now now (and later).
+ $opt{'time'} = $opt{'d'} ? str2time($opt{'d'}) : $^T;
+ $opt{'time'} += $opt{'y'} * 86400 if $opt{'y'};
+
+ $opt{'invoice_time'} = $opt{'n'} ? $^T : $opt{'time'};
+
+ #hashref here doesn't work with -m
+ #my $not_pkgpart = $opt{g} ? { map { $_=>1 } split(/,\s*/, $opt{g}) }
+ # : {};
+
+ ###
+ # get a list of custnums
+ ###
+
+ my $cursor_dbh = dbh->clone;
+
+ $cursor_dbh->do(
+ "DECLARE cron_bill_cursor CURSOR FOR ".
+ " SELECT custnum FROM cust_main WHERE ". bill_where( %opt )
+ ) or die $cursor_dbh->errstr;
+
+ while ( 1 ) {
+
+ my $sth = $cursor_dbh->prepare('FETCH 100 FROM cron_bill_cursor'); #mysql?
+
+ $sth->execute or die $sth->errstr;
+
+ my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
+
+ last unless scalar(@custnums);
+
+ ###
+ # for each custnum, queue or make one customer object and bill
+ # (one at a time, to reduce memory footprint with large #s of customers)
+ ###
+
+ foreach my $custnum ( @custnums ) {
+
+ my %args = (
+ 'time' => $opt{'time'},
+ 'invoice_time' => $opt{'invoice_time'},
+ 'actual_time' => $^T, #when freeside-bill was started
+ #(not, when using -m, freeside-queued)
+ 'check_freq' => $check_freq,
+ 'resetup' => ( $opt{'s'} ? $opt{'s'} : 0 ),
+ 'not_pkgpart' => $opt{'g'}, #$not_pkgpart,
+ );
+
+ if ( $opt{'m'} ) {
+
+ if ( $opt{'r'} ) {
+ warn "DRY RUN: would add custnum $custnum for queued_bill\n";
+ } else {
+
+ #avoid queuing another job if there's one still waiting to run
+ next if qsearch( 'queue', { 'job' => 'FS::cust_main::queued_bill',
+ 'custnum' => $custnum,
+ 'status' => 'new',
+ }
+ );
+
+ #add job to queue that calls bill_and_collect with options
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_main::queued_bill',
+ 'secure' => 'Y',
+ 'priority' => 99, #don't get in the way of provisioning jobs
+ };
+ my $error = $queue->insert( 'custnum'=>$custnum, %args );
+
+ }
+
+ } else {
+
+ my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
+ $cust_main->bill_and_collect( %args, 'debug' => $debug );
+
+ }
+
+ }
+
+ }
+
+ $cursor_dbh->commit or die $cursor_dbh->errstr;
+
+}
+
+# freeside-daily %opt:
+# -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).
+#
+# -n: When used with "-d" and/or "-y", specifies that invoices should be dated
+# with today's date, irregardless of the pretend date used to pre-generate
+# the invoices.
+#
+# -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
+#
+# -a: Only process customers with the specified agentnum
+#
+# -v: enable debugging
+#
+# -l: debugging level
+
+sub bill_where {
+ my( %opt ) = @_;
+
+ my $time = $opt{'time'};
+ my $invoice_time = $opt{'invoice_time'};
+
+ my $check_freq = $opt{'check_freq'} || '1d';
+
+ my @search = ();
+
+ push @search, "( cust_main.archived != 'Y' OR archived IS NULL )"; #disable?
+
+ push @search, "cust_main.payby = '". $opt{'p'}. "'"
+ if $opt{'p'};
+ push @search, "cust_main.agentnum IN ( ". $opt{'a'}. " ) "
+ if $opt{'a'};
+
+ #it would be useful if i recognized $opt{g} / $not_pkgpart...
+
+ if ( @ARGV ) {
+ push @search, "( ".
+ join(' OR ', map "cust_main.custnum = $_", @ARGV ).
+ " )";
+ }
+
+ ###
+ # generate where_pkg/where_event search clause
+ ###
+
+ # select * from cust_main where
+ my $where_pkg = <<"END";
+ EXISTS(
+ SELECT 1 FROM cust_pkg
+ WHERE cust_main.custnum = cust_pkg.custnum
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND ( ( ( setup IS NULL OR setup = 0 )
+ AND ( start_date IS NULL OR start_date = 0
+ OR ( start_date IS NOT NULL AND start_date <= $^T )
+ )
+ )
+ OR bill IS NULL OR bill <= $time
+ OR ( expire IS NOT NULL AND expire <= $^T )
+ OR ( adjourn IS NOT NULL AND adjourn <= $^T )
+ )
+ )
+END
+
+ my $where_event = join(' OR ', map {
+ my $eventtable = $_;
+
+ my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
+ my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
+ 'time'=>$time,
+ );
+ $where = $where ? "AND $where" : '';
+
+ my $are_part_event =
+ "EXISTS ( SELECT 1 FROM part_event $join
+ WHERE check_freq = '$check_freq'
+ AND eventtable = '$eventtable'
+ AND ( disabled = '' OR disabled IS NULL )
+ $where
+ )
+ ";
+
+ if ( $eventtable eq 'cust_main' ) {
+ $are_part_event;
+ } else {
+ "EXISTS ( SELECT 1 FROM $eventtable
+ WHERE cust_main.custnum = $eventtable.custnum
+ AND $are_part_event
+ )
+ ";
+ }
+
+ } FS::part_event->eventtables);
+
+ push @search, "( $where_pkg OR $where_event )";
+
+ warn "searching for customers:\n". join("\n", @search). "\n"
+ if $opt{'v'} || $opt{'l'};
+
+ join(' AND ', @search);
+
+}
+
+1;
diff --git a/FS/FS/Cron/breakage.pm b/FS/FS/Cron/breakage.pm
new file mode 100644
index 0000000..6dd904d
--- /dev/null
+++ b/FS/FS/Cron/breakage.pm
@@ -0,0 +1,84 @@
+package FS::Cron::breakage;
+
+use strict;
+use base 'Exporter';
+use vars qw( @EXPORT_OK );
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::agent;
+use FS::cust_main;
+
+@EXPORT_OK = qw ( reconcile_breakage );
+
+#freeside-daily %opt
+# -v: enable debugging
+# -l: debugging level
+
+sub reconcile_breakage {
+ my %opt = @_;
+
+ my $conf = new FS::Conf;
+
+ foreach my $agent (qsearch('agent', {})) {
+
+ my $days = $conf->config('breakage-days', $agent->agentnum)
+ or next;
+
+ my $since = int( $^T - ($days * 86400) );
+
+ warn 'searching '. $agent->agent. " for customers with unapplied payments more than $days days old\n"
+ if $opt{'v'};
+
+ #find customers w/negative balance older than $days (and no activity since)
+ # and no activity (invoices/payments/credits/refunds) newer than $since
+ # (XXX except antother breakage invoice???)
+
+ my $extra_sql =
+ ' AND 0 > '. FS::cust_main->balance_sql.
+ ' AND '. join(' AND ', map {
+ " NOT EXISTS (
+ SELECT 1 FROM $_
+ WHERE $_.custnum = cust_main.custnum
+ AND _date >= $since
+ ) "
+ } qw( cust_bill cust_pay cust_credit cust_refund )
+ );
+
+ my @customers = qsearch({
+ 'table' => 'cust_main',
+ 'hashref' => { 'agentnum' => $agent->agentnum,
+ 'payby' => { op=>'!=', value=>'COMP', },
+ },
+ 'extra_sql' => $extra_sql,
+ });
+
+ #and then create a "breakage" charge & invoice for them
+
+ foreach my $cust_main ( @customers ) {
+
+ warn 'reconciling breakage for customer '. $cust_main->custnum.
+ ': '. $cust_main->name. "\n"
+ if $opt{'v'};
+
+ my $error =
+ $cust_main->charge({
+ 'amount' => sprintf('%.2f', 0 - $cust_main->balance ),
+ 'pkg' => 'Breakage',
+ 'comment' => 'breakage reconciliation',
+ 'classnum' => scalar($conf->config('breakage-pkg_class')),
+ 'setuptax' => 'Y',
+ 'bill_now' => 1,
+ })
+ || $cust_main->apply_payments_and_credits;
+
+ if ( $error ) {
+ warn "error charging for breakage reconciliation: $error\n";
+ }
+
+ }
+
+ }
+
+}
+
+1;
diff --git a/FS/FS/Cron/check.pm b/FS/FS/Cron/check.pm
new file mode 100644
index 0000000..9d3ffbd
--- /dev/null
+++ b/FS/FS/Cron/check.pm
@@ -0,0 +1,200 @@
+package FS::Cron::check;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $FS_RUN $error_msg
+ $SELFSERVICE_USER $SELFSERVICE_MACHINES @SELFSERVICE_MACHINES
+ );
+use Exporter;
+use LWP::UserAgent;
+use HTTP::Request;
+use URI::Escape;
+use Email::Send;
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::cust_pay_pending;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw(
+ check_queued check_selfservice check_apache check_bop_failures
+ check_sg check_sg_login check_sgng
+ alert error_msg
+);
+
+$DEBUG = 0;
+
+$FS_RUN = '/var/run';
+
+sub check_queued {
+ _check_fsproc('queued');
+}
+
+$SELFSERVICE_USER = '%%%SELFSERVICE_USER%%%';
+
+$SELFSERVICE_MACHINES = '%%%SELFSERVICE_MACHINES%%%'; #substituted by Makefile
+$SELFSERVICE_MACHINES =~ s/^\s+//;
+$SELFSERVICE_MACHINES =~ s/\s+$//;
+@SELFSERVICE_MACHINES = split(/\s+/, $SELFSERVICE_MACHINES);
+@SELFSERVICE_MACHINES = ()
+ if scalar(@SELFSERVICE_MACHINES) == 1
+ && $SELFSERVICE_MACHINES[0] eq '%%%'.'SELFSERVICE_MACHINES'.'%%%';
+
+sub check_selfservice {
+ foreach my $machine ( @SELFSERVICE_MACHINES ) {
+ unless ( _check_fsproc("selfservice-server.$SELFSERVICE_USER.$machine") ) {
+ $error_msg = "Self-service daemon not running for $machine";
+ return 0;
+ }
+ }
+ return 1;
+}
+
+sub check_sg {
+ my $conf = new FS::Conf;
+ #different trigger if they ever stop using multicustomer_hack ?
+ return 1 unless $conf->exists('sg-multicustomer_hack');
+
+ my $ua = new LWP::UserAgent;
+ $ua->agent("FreesideCronCheck/0.1 " . $ua->agent);
+
+ my $USER = $conf->config('sg-ping_username');
+ my $PASS = $conf->config('sg-ping_password');
+ my $req = new HTTP::Request GET=>"https://$USER:$PASS\@localhost/sg/ping.cgi";
+ my $res = $ua->request($req);
+
+ return 1 if $res->is_success
+ && $res->content =~ /OK/
+ && $res->content !~ /error/i; #doh, the error message includes "OK"
+
+ $error_msg = $res->is_success ? $res->content : $res->status_line;
+ return 0;
+}
+
+sub check_sg_login {
+ my $conf = new FS::Conf;
+ #different trigger if they ever stop using multicustomer_hack ?
+ return 1 unless $conf->exists('sg-multicustomer_hack');
+
+ my $ua = new LWP::UserAgent;
+ $ua->agent("FreesideCronCheck/0.1 " . $ua->agent);
+
+ my $USER = $conf->config('sg-ping_username');
+ my $PASS = $conf->config('sg-ping_password');
+ my $USERNAME = $conf->config('sg-login_username');
+ my $req = new HTTP::Request
+ GET=>"https://$USER:$PASS\@localhost/sg/start.cgi?".
+ 'username='. uri_escape($USERNAME);
+ my $res = $ua->request($req);
+
+ return 1 if $res->is_success
+ && $res->content =~ /[\da-f]{32}/i #session_id
+ && $res->content !~ /error/i;
+
+ $error_msg = $res->is_success ? $res->content : $res->status_line;
+ return 0;
+}
+
+sub check_sgng {
+ my $conf = new FS::Conf;
+ #different trigger if they ever stop using multicustomer_hack ?
+ return 1 unless $conf->exists('sg-multicustomer_hack');
+
+ eval 'use RPC::XML; use RPC::XML::Client;';
+ if ($@) { $error_msg = $@; return 0; };
+
+ my $cli = RPC::XML::Client->new('https://localhost/selfservice/xmlrpc.cgi');
+ my $resp = $cli->send_request('FS.SelfService.XMLRPC.ping');
+
+ return 1 if ref($resp)
+ && ! $resp->is_fault
+ && ref($resp->value)
+ && $resp->value->{'pong'} == 1;
+
+ #hua
+ $error_msg = ref($resp)
+ ? ( $resp->is_fault
+ ? $resp->string
+ : ( ref($resp->value) ? $resp->value->{'error'}
+ : $resp->value
+ )
+ )
+ : $resp;
+ return 0;
+}
+
+sub _check_fsproc {
+ my $arg = shift;
+ _check_pidfile( "freeside-$arg.pid" );
+}
+
+sub _check_pidfile {
+ my $pidfile = shift;
+ open(PID, "$FS_RUN/$pidfile") or return 0;
+ chomp( my $pid = scalar(<PID>) );
+ close PID; # or return 0;
+
+ $pid && kill 0, $pid;
+}
+
+sub check_apache {
+ my $ua = new LWP::UserAgent;
+ $ua->agent("FreesideCronCheck/0.1 " . $ua->agent);
+
+ my $req = new HTTP::Request GET => 'https://localhost/';
+ my $res = $ua->request($req);
+
+ return 1 if $res->is_success || $res->status_line =~ /^403/;
+ $error_msg = $res->status_line;
+ return 0;
+
+}
+
+#and now for something entirely different...
+my $num_consecutive_bop_failures = 60;
+sub check_bop_failures {
+
+ return 1 if grep { $_->statustext eq 'captured' }
+ qsearch({
+ 'table' => 'cust_pay_pending',
+ 'hashref' => { 'status' => 'done' },
+ 'order_by' => 'ORDER BY paypendingnum DESC'.
+ " LIMIT $num_consecutive_bop_failures",
+ });
+ $error_msg = "Last $num_consecutive_bop_failures real-time payments failed";
+ return 0;
+}
+
+#
+
+sub error_msg {
+ $error_msg;
+}
+
+sub alert {
+ my( $alert, @emails ) = @_;
+
+ my $conf = new FS::Conf;
+ my $smtpmachine = $conf->config('smtpmachine');
+ my $company_name = $conf->config('company_name');
+
+ foreach my $email (@emails) {
+ warn "warning $email about $alert\n" if $DEBUG;
+
+ my $message = <<"__MESSAGE__";
+From: support\@freeside.biz
+To: $email
+Subject: FREESIDE ALERT for $company_name
+
+FREESIDE ALERT: $alert
+
+__MESSAGE__
+
+ my $sender = Email::Send->new({ mailer => 'SMTP' });
+ $sender->mailer_args([ Host => $smtpmachine ]);
+ $sender->send($message);
+
+ }
+
+}
+
+1;
+
diff --git a/FS/FS/Cron/expire_user_pref.pm b/FS/FS/Cron/expire_user_pref.pm
new file mode 100644
index 0000000..3226927
--- /dev/null
+++ b/FS/FS/Cron/expire_user_pref.pm
@@ -0,0 +1,20 @@
+package FS::Cron::expire_user_pref;
+
+use vars qw( @ISA @EXPORT_OK);
+use Exporter;
+use FS::UID qw(dbh);
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( expire_user_pref );
+
+sub expire_user_pref {
+ my $sql = "DELETE FROM access_user_pref WHERE expiration IS NOT NULL".
+ " AND expiration < ?";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute(time) or die $sth->errstr;
+
+ dbh->commit or die dbh->errstr if $FS::UID::AutoCommit
+
+}
+
+1;
diff --git a/FS/FS/Cron/notify.pm b/FS/FS/Cron/notify.pm
new file mode 100644
index 0000000..5b0e186
--- /dev/null
+++ b/FS/FS/Cron/notify.pm
@@ -0,0 +1,149 @@
+package FS::Cron::notify;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use FS::UID qw( dbh driver_name );
+use FS::Record qw(qsearch);
+use FS::cust_main;
+use FS::cust_pkg;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw ( notify_flat_delay );
+$DEBUG = 0;
+
+sub notify_flat_delay {
+
+ my %opt = @_;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ $DEBUG = 1 if $opt{'v'};
+
+ #we're at now now (and later).
+ my($time) = $^T;
+
+ my $integer = driver_name =~ /^mysql/ ? 'SIGNED' : 'INTEGER';
+
+ # select * from cust_pkg where
+ my $where_pkg = <<"END";
+ where ( cancel is null or cancel = 0 )
+ and ( bill > 0 )
+ and
+ 0 < ( select count(*) from part_pkg
+ where cust_pkg.pkgpart = part_pkg.pkgpart
+ and part_pkg.plan = 'flat_delayed'
+ and 0 < ( select count(*) from part_pkg_option
+ where part_pkg.pkgpart = part_pkg_option.pkgpart
+ and part_pkg_option.optionname = 'recur_notify'
+ and CAST( part_pkg_option.optionvalue AS INTEGER ) > 0
+ and 0 <= ( $time
+ + CAST( part_pkg_option.optionvalue AS $integer )
+ * 86400
+ - cust_pkg.bill
+ )
+ and ( cust_pkg.expire is null
+ or cust_pkg.expire > ( $time
+ + CAST( part_pkg_option.optionvalue AS $integer )
+ * 86400
+ )
+END
+
+#/* and ( cust_pkg.adjourn is null
+# or cust_pkg.adjourn > $time
+#-- Should notify suspended ones + cast(part_pkg_option.optionvalue as $integer)
+# * 86400
+#*/
+
+ $where_pkg .= <<"END";
+ )
+ )
+ )
+ and
+ 0 = ( select count(*) from cust_pkg_option
+ where cust_pkg.pkgnum = cust_pkg_option.pkgnum
+ and cust_pkg_option.optionname = 'impending_recur_notification_sent'
+ and CAST( cust_pkg_option.optionvalue AS INTEGER ) = 1
+ )
+END
+
+ if ($opt{a}) {
+ $where_pkg .= <<END;
+ and 0 < ( select count(*) from cust_main
+ where cust_pkg.custnum = cust_main.custnum
+ and cust_main.agentnum = $opt{a}
+ )
+END
+ }
+
+ my @cust_pkg;
+ if ( @ARGV ) {
+ $where_pkg .= "and ( " . join( "OR ", map { "custnum = $_" } @ARGV) . " )";
+ }
+
+ my $orderby = "order by custnum, bill";
+
+ my $extra_sql = "$where_pkg $orderby";
+
+ @cust_pkg = qsearch('cust_pkg', {}, '', $extra_sql );
+
+ my @packages = ();
+ my @recurdates = ();
+ my @cust_pkgs = ();
+ while ( scalar(@cust_pkg) ) {
+ my $cust_main = $cust_pkg[0]->cust_main;
+ my $custnum = $cust_pkg[0]->custnum;
+ warn "working on $custnum" if $DEBUG;
+ while (scalar(@cust_pkg)){
+ last if ($cust_pkg[0]->custnum != $custnum);
+ warn "storing information on " . $cust_pkg[0]->pkgnum if $DEBUG;
+ push @packages, $cust_pkg[0]->part_pkg->pkg;
+ push @recurdates, $cust_pkg[0]->bill;
+ push @cust_pkgs, $cust_pkg[0];
+ shift @cust_pkg;
+ }
+ my $error =
+ $cust_main->notify( 'impending_recur_template',
+ 'extra_fields' => { 'packages' => \@packages,
+ 'recurdates' => \@recurdates,
+ 'package' => $packages[0],
+ 'recurdate' => $recurdates[0],
+ },
+ );
+ warn "Error notifying, custnum ". $cust_main->custnum. ": $error" if $error;
+
+ unless ($error) {
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ for (@cust_pkgs) {
+ my %options = ($_->options, 'impending_recur_notification_sent' => 1 );
+ $error = $_->replace( $_, options => \%options );
+ if ($error){
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die "Error updating package options for customer". $cust_main->custnum.
+ ": $error" if $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ }
+
+ @packages = ();
+ @recurdates = ();
+ @cust_pkgs = ();
+
+ }
+
+ dbh->commit or die dbh->errstr if $oldAutoCommit;
+
+}
+
+1;
diff --git a/FS/FS/Cron/upload.pm b/FS/FS/Cron/upload.pm
new file mode 100644
index 0000000..fea3d2c
--- /dev/null
+++ b/FS/FS/Cron/upload.pm
@@ -0,0 +1,176 @@
+package FS::Cron::upload;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $me $DEBUG );
+use Exporter;
+use Date::Format;
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+use FS::queue;
+use FS::agent;
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Request::Common;
+use HTTP::Response;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw ( upload );
+$DEBUG = 0;
+$me = '[FS::Cron::upload]';
+
+#freeside-daily %opt:
+# -v: enable debugging
+# -l: debugging level
+# -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
+# -r: Multi-process mode dry run option
+# -a: Only process customers with the specified agentnum
+
+
+sub upload {
+ my %opt = @_;
+
+ my $debug = 0;
+ $debug = 1 if $opt{'v'};
+ $debug = $opt{'l'} if $opt{'l'};
+
+ local $DEBUG = $debug if $debug;
+
+ warn "$me upload called\n" if $DEBUG;
+
+ my $conf = new FS::Conf;
+ my @agent = grep { $conf->config( 'billco-username', $_->agentnum, 1 ) }
+ grep { $conf->config( 'billco-password', $_->agentnum, 1 ) }
+ qsearch( 'agent', {} );
+
+ my $date = time2str('%Y%m%d%H%M%S', $^T); # more?
+
+ @agent = grep { $_ == $opt{'a'} } @agent if $opt{'a'};
+
+ foreach my $agent ( @agent ) {
+
+ my $agentnum = $agent->agentnum;
+
+ if ( $opt{'m'} ) {
+
+ if ( $opt{'r'} ) {
+ warn "DRY RUN: would add agent $agentnum for queued upload\n";
+ } else {
+
+ my $queue = new FS::queue {
+ 'job' => 'FS::Cron::upload::billco_upload',
+ };
+ my $error = $queue->insert(
+ 'agentnum' => $agentnum,
+ 'date' => $date,
+ 'l' => $opt{'l'} || '',
+ 'm' => $opt{'m'} || '',
+ 'v' => $opt{'v'} || '',
+ );
+
+ }
+
+ } else {
+
+ eval "&billco_upload( 'agentnum' => $agentnum, 'date' => $date );";
+ warn "billco_upload failed: $@\n"
+ if ( $@ );
+
+ }
+
+ }
+
+}
+
+sub billco_upload {
+ my %opt = @_;
+
+ warn "$me billco_upload called\n" if $DEBUG;
+ my $conf = new FS::Conf;
+ my $dir = '%%%FREESIDE_EXPORT%%%/export.'. $FS::UID::datasrc. '/cust_bill';
+
+ my $agentnum = $opt{agentnum} or die "no agentnum provided\n";
+ my $url = $conf->config( 'billco-url', $agentnum )
+ or die "no url for agent $agentnum\n";
+ my $username = $conf->config( 'billco-username', $agentnum, 1 )
+ or die "no username for agent $agentnum\n";
+ my $password = $conf->config( 'billco-password', $agentnum, 1 )
+ or die "no password for agent $agentnum\n";
+ my $clicode = $conf->config( 'billco-clicode', $agentnum )
+ or die "no clicode for agent $agentnum\n";
+
+ die "no date provided\n" unless $opt{date};
+ my $zipfile = "$dir/agentnum$agentnum-$opt{date}.zip";
+
+ 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 $agent = qsearchs( 'agent', { agentnum => $agentnum } )
+ or die "no such agent: $agentnum";
+ $agent->select_for_update; #mutex
+
+ unless ( -f "$dir/agentnum$agentnum-header.csv" ||
+ -f "$dir/agentnum$agentnum-detail.csv" )
+ {
+ warn "$me neither $dir/agentnum$agentnum-header.csv nor ".
+ "$dir/agentnum$agentnum-detail.csv found\n" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return;
+ }
+
+ # a better way?
+ if ($opt{m}) {
+ my $sql = "SELECT count(*) FROM queue LEFT JOIN cust_main USING(custnum) ".
+ "WHERE queue.job='FS::cust_main::queued_bill' AND cust_main.agentnum = ?";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ while (1) {
+ $sth->execute( $agentnum )
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ last if $sth->fetchow_arrayref->[0];
+ sleep 300;
+ }
+ }
+
+ foreach ( qw ( header detail ) ) {
+ rename "$dir/agentnum$agentnum-$_.csv",
+ "$dir/agentnum$agentnum-$opt{date}-$_.csv";
+ }
+
+ my $command = "cd $dir; zip $zipfile ".
+ "agentnum$agentnum-$opt{date}-header.csv ".
+ "agentnum$agentnum-$opt{date}-detail.csv";
+
+ system($command) and die "$command failed\n";
+
+ unlink "agentnum$agentnum-$opt{date}-header.csv",
+ "agentnum$agentnum-$opt{date}-detail.csv";
+
+ my $ua = new LWP::UserAgent;
+ my $res = $ua->request( POST( $url,
+ 'Content_Type' => 'form-data',
+ 'Content' => [ 'username' => $username,
+ 'pass' => $password,
+ 'custid' => $username,
+ 'clicode' => $clicode,
+ 'file1' => [ $zipfile ],
+ ],
+ )
+ );
+
+ die "upload failed: ". $res->status_line. "\n"
+ unless $res->is_success;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+1;
diff --git a/FS/FS/Cron/vacuum.pm b/FS/FS/Cron/vacuum.pm
new file mode 100644
index 0000000..075572d
--- /dev/null
+++ b/FS/FS/Cron/vacuum.pm
@@ -0,0 +1,23 @@
+package FS::Cron::vacuum;
+
+use vars qw( @ISA @EXPORT_OK);
+use Exporter;
+use FS::UID qw(driver_name dbh);
+use FS::Schema qw(dbdef);
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( vacuum );
+
+sub vacuum {
+
+ if ( driver_name eq 'Pg' ) {
+ dbh->{AutoCommit} = 1; #so we can vacuum
+ foreach my $table ( dbdef->tables ) {
+ my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ }
+ }
+
+}
+
+1;
diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm
new file mode 100644
index 0000000..bcd337d
--- /dev/null
+++ b/FS/FS/CurrentUser.pm
@@ -0,0 +1,67 @@
+package FS::CurrentUser;
+
+use vars qw($CurrentUser $upgrade_hack);
+
+#not at compile-time, circular dependancey causes trouble
+#use FS::Record qw(qsearchs);
+#use FS::access_user;
+
+$upgrade_hack = 0;
+
+=head1 NAME
+
+FS::CurrentUser - Package representing the current user
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=cut
+
+sub load_user {
+ my( $class, $user ) = @_; #, $pass
+
+ if ( $upgrade_hack ) {
+ return $CurrentUser = new FS::CurrentUser::BootstrapUser;
+ }
+
+ #return "" if $user =~ /^fs_(queue|selfservice)$/;
+
+ #not the best thing in the world...
+ eval "use FS::Record qw(qsearchs);";
+ die $@ if $@;
+ eval "use FS::access_user;";
+ die $@ if $@;
+
+ $CurrentUser = qsearchs('access_user', {
+ 'username' => $user,
+ #'_password' =>
+ 'disabled' => '',
+ } );
+
+ die "unknown user: $user" unless $CurrentUser; # or bad password
+
+ $CurrentUser;
+}
+
+=head1 BUGS
+
+Creepy crawlies
+
+=head1 SEE ALSO
+
+=cut
+
+package FS::CurrentUser::BootstrapUser;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+}
+
+sub AUTOLOAD { 1 };
+
+1;
+
diff --git a/FS/FS/Daemon.pm b/FS/FS/Daemon.pm
new file mode 100644
index 0000000..ca18134
--- /dev/null
+++ b/FS/FS/Daemon.pm
@@ -0,0 +1,100 @@
+package FS::Daemon;
+
+use vars qw( @ISA @EXPORT_OK );
+use vars qw( $pid_dir $me $pid_file $sigint $sigterm $logfile );
+use Exporter;
+use Fcntl qw(:flock);
+use POSIX qw(setsid);
+use IO::File;
+use Date::Format;
+
+#this is a simple refactoring of the stuff from freeside-queued, just to
+#avoid duplicate code. eventually this should use something from CPAN.
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(
+ daemonize1 drop_root daemonize2 myexit logfile sigint sigterm
+);
+%EXPORT_TAGS = ( 'all' => [ @EXPORT_OK ] );
+
+$pid_dir = '/var/run';
+
+sub daemonize1 {
+ $me = shift;
+
+ $pid_file = "$pid_dir/$me";
+ $pid_file .= '.'.shift if scalar(@_);
+ $pid_file .= '.pid';
+
+ 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 "$me 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;
+ }
+
+ #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++; };
+}
+
+sub drop_root {
+ my $freeside_gid = scalar(getgrnam('freeside'))
+ or die "can't find 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;
+}
+
+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: $!";
+
+ $SIG{__DIE__} = \&_die;
+ $SIG{__WARN__} = \&_logmsg;
+
+ warn "$me starting\n";
+}
+
+sub sigint { $sigint; }
+sub sigterm { $sigterm; }
+
+sub logfile { $logfile = shift; } #_logmsg('test'); }
+
+sub myexit {
+ unlink $pid_file if -e $pid_file;
+ exit;
+}
+
+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 ">>$logfile";
+ 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;
+}
+
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/Mason.pm b/FS/FS/Mason.pm
new file mode 100644
index 0000000..10edd6d
--- /dev/null
+++ b/FS/FS/Mason.pm
@@ -0,0 +1,507 @@
+package FS::Mason;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $addl_handler_use );
+use Exporter;
+use File::Slurp qw( slurp );
+use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect
+use HTML::Mason::Interp;
+use HTML::Mason::Compiler::ToObject;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( mason_interps );
+
+=head1 NAME
+
+FS::Mason - Initialize the Mason environment
+
+=head1 SYNOPSIS
+
+ use FS::Mason qw( mason_interps );
+
+ my( $fs_interp, $rt_interp ) = mason_interps('apache');
+
+ #OR
+
+ my( $fs_interp, $rt_interp ) = mason_interps('standalone'); #XXX name?
+
+=head1 DESCRIPTION
+
+Initializes the Mason environment, loads all Freeside and RT libraries, etc.
+
+=cut
+
+$addl_handler_use = '';
+my $addl_handler_use_file = '%%%FREESIDE_CONF%%%/addl_handler_use.pl';
+if ( -e $addl_handler_use_file ) {
+ $addl_handler_use = slurp( $addl_handler_use_file );
+}
+
+# List of modules that you want to use from components (see Admin
+# manual for details)
+{
+ package HTML::Mason::Commands;
+
+ use strict;
+ use vars qw( %session );
+ use CGI 3.29 qw(-private_tempfiles); #3.29 to fix RT attachment problems
+
+ #breaks quick payment entry
+ #http://rt.cpan.org/Public/Bug/Display.html?id=37365
+ die "CGI.pm v3.38 is broken, use any other version >= 3.29".
+ " (Debian 5.0? aptitude remove libcgi-pm-perl)"
+ if $CGI::VERSION == 3.38;
+
+ #use CGI::Carp qw(fatalsToBrowser);
+ use CGI::Cookie;
+ use List::Util qw( max min );
+ use Data::Dumper;
+ use Date::Format;
+ use Time::Local;
+ use Time::HiRes;
+ use Time::Duration;
+ use DateTime;
+ use DateTime::Format::Strptime;
+ use FS::Misc::DateTime qw( parse_datetime );
+ use Lingua::EN::Inflect qw(PL);
+ Lingua::EN::Inflect::classical names=>0; #Categorys
+ use Tie::IxHash;
+ use URI;
+ use URI::Escape;
+ use HTML::Entities;
+ use HTML::TreeBuilder;
+ use HTML::FormatText;
+ use HTML::Defang;
+ use JSON;
+ use MIME::Base64;
+ use IO::Handle;
+ use IO::File;
+ use IO::Scalar;
+ #not actually using this yet anyway...# use IPC::Run3 0.036;
+ use Net::Whois::Raw qw(whois);
+ if ( $] < 5.006 ) {
+ eval "use Net::Whois::Raw 0.32 qw(whois)";
+ die $@ if $@;
+ }
+ use Text::CSV_XS;
+ use Spreadsheet::WriteExcel;
+ use Business::CreditCard 0.30; #for mask-aware cardtype()
+ use NetAddr::IP;
+ use Net::Ping;
+ use Net::Ping::External;
+ #if CPAN #7815 ever gets fixed# if ( $Net::Ping::External::VERSION <= 0.12 )
+ {
+ no warnings 'redefine';
+ eval 'sub Net::Ping::External::_ping_linux {
+ my %args = @_;
+ my $command = "ping -s $args{size} -c $args{count} -w $args{timeout} $args{host}";
+ return Net::Ping::External::_ping_system($command, 0);
+ }
+ ';
+ die $@ if $@;
+ }
+ use String::Approx qw(amatch);
+ use Chart::LinesPoints;
+ use Chart::Mountain;
+ use Chart::Bars;
+ use Color::Scheme;
+ use HTML::Widgets::SelectLayers 0.07; #should go away in favor of
+ #selectlayers.html
+ use Locale::Country;
+ use Business::US::USPS::WebTools::AddressStandardization;
+ use FS;
+ use FS::UID qw( getotaker dbh datasrc driver_name );
+ use FS::Record qw( qsearch qsearchs fields dbdef
+ str2time_sql str2time_sql_closing
+ );
+ use FS::Conf;
+ use FS::CGI qw(header menubar table itable ntable idiot
+ eidiot myexit http_header);
+ use FS::UI::Web qw(svc_url);
+ use FS::UI::Web::small_custview qw(small_custview);
+ use FS::UI::bytecount;
+ use FS::Msgcat qw(gettext geterror);
+ use FS::Misc qw( send_email send_fax
+ states_hash counties cities state_label
+ );
+ use FS::Misc::eps2png qw( eps2png );
+ use FS::Report::FCC_477;
+ use FS::Report::Table::Monthly;
+ use FS::TicketSystem;
+ use FS::Tron qw( tron_lint );
+
+ 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 qw(smart_search);
+ use FS::cust_main::Import;
+ use FS::cust_main_county;
+ use FS::cust_location;
+ use FS::cust_pay;
+ use FS::cust_pkg;
+ use FS::part_pkg_taxclass;
+ use FS::cust_pkg_reason;
+ use FS::cust_refund;
+ use FS::cust_credit_refund;
+ use FS::cust_pay_refund;
+ use FS::cust_svc;
+ use FS::nas;
+ use FS::part_bill_event;
+ use FS::part_event;
+ use FS::part_event_condition;
+ use FS::part_pkg;
+ use FS::part_referral;
+ use FS::part_svc;
+ use FS::part_svc_router;
+ use FS::part_virtual_field;
+ use FS::pay_batch;
+ 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::acct_rt_transaction;
+ 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::export_device;
+ use FS::msgcat;
+ use FS::rate;
+ use FS::rate_region;
+ use FS::rate_prefix;
+ use FS::rate_detail;
+ use FS::usage_class;
+ use FS::payment_gateway;
+ use FS::agent_payment_gateway;
+ use FS::XMLRPC;
+ use FS::payby;
+ use FS::cdr;
+ use FS::cdr_batch;
+ use FS::inventory_class;
+ use FS::inventory_item;
+ use FS::pkg_category;
+ use FS::pkg_class;
+ use FS::access_user;
+ use FS::access_user_pref;
+ use FS::access_group;
+ use FS::access_usergroup;
+ use FS::access_groupagent;
+ use FS::access_right;
+ use FS::AccessRight;
+ use FS::svc_phone;
+ use FS::phone_device;
+ use FS::part_device;
+ use FS::reason_type;
+ use FS::reason;
+ use FS::cust_main_note;
+ use FS::tax_class;
+ use FS::cust_tax_location;
+ use FS::part_pkg_taxproduct;
+ use FS::part_pkg_taxoverride;
+ use FS::part_pkg_taxrate;
+ use FS::tax_rate;
+ use FS::part_pkg_report_option;
+ use FS::cust_attachment;
+ use FS::h_cust_pkg;
+ use FS::h_svc_acct;
+ use FS::h_svc_broadband;
+ use FS::h_svc_domain;
+ #use FS::h_domain_record;
+ use FS::h_svc_external;
+ use FS::h_svc_forward;
+ use FS::h_svc_phone;
+ #use FS::h_phone_device;
+ use FS::h_svc_www;
+ use FS::cust_statement;
+ use FS::cust_class;
+ use FS::cust_category;
+ use FS::prospect_main;
+ use FS::contact;
+ use FS::svc_pbx;
+ use FS::discount;
+ use FS::cust_pkg_discount;
+ use FS::cust_bill_pkg_discount;
+ use FS::svc_mailinglist;
+ use FS::cgp_rule;
+ use FS::cgp_rule_condition;
+ use FS::cgp_rule_action;
+ # Sammath Naur
+
+ if ( $FS::Mason::addl_handler_use ) {
+ eval $FS::Mason::addl_handler_use;
+ die $@ if $@;
+ }
+
+ if ( %%%RT_ENABLED%%% ) {
+ eval '
+ use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
+ use vars qw($Nobody $SystemUser);
+ use RT;
+ 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::ObjectCustomFieldValues;
+
+ #blah. manually updated from RT::Interface::Web::Handler
+ use RT::Interface::Web;
+ use MIME::Entity;
+ use Text::Wrapper;
+ use Time::ParseDate;
+ use Time::HiRes;
+ use HTML::Scrubber;
+
+ #blah. not even in RT::Interface::Web::Handler, just in
+ #html/NoAuth/css/dhandler and rt-test-dependencies. ask for it here
+ #to throw a real error instead of just a mysterious unstyled RT
+ use CSS::Squish 0.06;
+
+ use RT::Interface::Web::Request;
+
+ #slow, unreliable, segfaults and is optional
+ #see rt/html/Ticket/Elements/ShowTransactionAttachments
+ #use Text::Quoted;
+
+ #?#use File::Path qw( rmtree );
+ #?#use File::Glob qw( bsd_glob );
+ #?#use File::Spec::Unix;
+
+ ';
+ die $@ if $@;
+ }
+
+ *CGI::redirect = sub {
+ my $self = shift;
+ my $cookie = '';
+ if ( $_[0] eq '-cookie' ) { #this isn't actually used at the moment
+ (my $x, $cookie) = (shift, shift);
+ $HTML::Mason::r->err_headers_out->add( 'Set-cookie' => $cookie );
+ }
+ my $location = shift;
+
+ use vars qw($m);
+
+ # false laziness w/below
+ if ( defined(@DBIx::Profile::ISA) ) {
+
+ if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) {
+
+ #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 {
+
+ #clear db profile, but normal redirect
+ dbh->{'private_profile'} = {};
+ $m->redirect($location);
+ '';
+
+ }
+
+ } else { #normal redirect
+
+ $m->redirect($location);
+ '';
+
+ }
+
+ };
+
+ sub include {
+ use vars qw($m);
+ $m->scomp(@_);
+ }
+
+ sub errorpage {
+ use vars qw($m);
+ $m->comp('/elements/errorpage.html', @_);
+ }
+
+ sub redirect {
+ my( $location ) = @_;
+ use vars qw($m);
+ $m->clear_buffer;
+ #false laziness w/above
+ if ( defined(@DBIx::Profile::ISA) ) {
+
+ if ( $FS::CurrentUser::CurrentUser->option('show_db_profile') ) {
+
+ #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'} = {};
+
+ } else {
+
+ #clear db profile, but normal redirect
+ dbh->{'private_profile'} = {};
+ $m->redirect($location);
+
+ }
+
+ } else { #normal redirect
+
+ $m->redirect($location);
+
+ }
+
+ }
+
+} # end package HTML::Mason::Commands;
+
+=head1 SUBROUTINE
+
+=over 4
+
+=item mason_interps [ MODE [ OPTION => VALUE ... ] ]
+
+Returns a list consisting of two HTML::Mason::Interp objects, the first for
+Freeside pages, and the second for RT pages.
+
+MODE can be 'apache' or 'standalone'. If not specified, defaults to 'apache'.
+
+Options and values can be passed following mode. Currently available options
+are:
+
+I<outbuf> should be set to a scalar reference in standalone mode.
+
+=cut
+
+my %defang_opts = ( attribs_to_callback => ['src'], attribs_callback => sub { 1 });
+
+sub mason_interps {
+ my $mode = shift || 'apache';
+ my %opt = @_;
+
+ #my $request_class = 'HTML::Mason::Request'.
+ #( $mode eq 'apache' ? '::ApacheHandler' : '' );
+ my $request_class = 'FS::Mason::Request';
+
+ #not entirely sure it belongs here, but what the hey
+ if ( %%%RT_ENABLED%%% && $mode ne 'standalone' ) {
+ RT::LoadConfig();
+ }
+
+ # A hook supporting strange legacy ways people have added stuff on
+
+ my @addl_comp_root = ();
+ my $addl_comp_root_file = '%%%FREESIDE_CONF%%%/addl_comp_root.pl';
+ if ( -e $addl_comp_root_file ) {
+ warn "reading $addl_comp_root_file\n";
+ my $text = slurp( $addl_comp_root_file );
+ my @addl = eval $text;
+ if ( @addl && ! $@ ) {
+ @addl_comp_root = @addl;
+ } elsif ($@) {
+ warn "error parsing $addl_comp_root_file: $@\n";
+ }
+ }
+
+ my %interp = (
+ request_class => $request_class,
+ data_dir => '%%%MASONDATA%%%',
+ error_mode => 'output',
+ error_format => 'html',
+ ignore_warnings_expr => '.',
+ comp_root => [
+ [ 'freeside'=>'%%%FREESIDE_DOCUMENT_ROOT%%%' ],
+ [ 'rt' =>'%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ],
+ @addl_comp_root,
+ ],
+ );
+
+ $interp{out_method} = $opt{outbuf} if $mode eq 'standalone' && $opt{outbuf};
+
+ my $html_defang = new HTML::Defang (%defang_opts);
+
+ my $fs_interp = new HTML::Mason::Interp (
+ %interp,
+ escape_flags => { 'js_string' => sub {
+ #${$_[0]} =~ s/(['\\\n])/'\\'.($1 eq "\n" ? 'n' : $1)/ge;
+ ${$_[0]} =~ s/(['\\])/\\$1/g;
+ ${$_[0]} =~ s/\n/\\n/g;
+ ${$_[0]} = "'". ${$_[0]}. "'";
+ },
+ 'defang' => sub {
+ ${$_[0]} = $html_defang->defang(${$_[0]});
+ },
+ },
+ compiler => HTML::Mason::Compiler::ToObject->new(
+ allow_globals => [qw(%session)],
+ ),
+ );
+
+ my $rt_interp = new HTML::Mason::Interp (
+ %interp,
+ escape_flags => { 'h' => \&RT::Interface::Web::EscapeUTF8 },
+ compiler => HTML::Mason::Compiler::ToObject->new(
+ default_escape_flags => 'h',
+ allow_globals => [qw(%session)],
+ ),
+ );
+
+ ( $fs_interp, $rt_interp );
+
+}
+
+=back
+
+=head1 BUGS
+
+Lurking in the darkness...
+
+=head1 SEE ALSO
+
+L<HTML::Mason>, L<FS>, L<RT>
+
+=cut
+
+1;
diff --git a/FS/FS/Mason/Request.pm b/FS/FS/Mason/Request.pm
new file mode 100644
index 0000000..8d66f4f
--- /dev/null
+++ b/FS/FS/Mason/Request.pm
@@ -0,0 +1,89 @@
+package FS::Mason::Request;
+
+use strict;
+use warnings;
+use vars qw( $FSURL $QUERY_STRING );
+use base 'HTML::Mason::Request';
+
+$FSURL = 'http://Set/FS_Mason_Request_FSURL/in_standalone_mode/';
+$QUERY_STRING = '';
+
+sub new {
+ my $class = shift;
+
+ my $superclass = $HTML::Mason::ApacheHandler::VERSION ?
+ 'HTML::Mason::Request::ApacheHandler' :
+ $HTML::Mason::CGIHandler::VERSION ?
+ 'HTML::Mason::Request::CGI' :
+ 'HTML::Mason::Request';
+
+ $class->alter_superclass( $superclass );
+
+ #huh... shouldn't alter_superclass take care of this for us?
+ __PACKAGE__->valid_params( %{ $superclass->valid_params() } );
+
+ my %opt = @_;
+ my $mode = $superclass =~ /Apache/i ? 'apache' : 'standalone';
+ freeside_setup($opt{'comp'}, $mode);
+
+ $class->SUPER::new(@_);
+
+}
+
+#override alter_superclass ala RT::Interface::Web::Request ??
+# for Mason 1.39 vs. Perl 5.10.0
+
+sub freeside_setup {
+
+ my( $filename, $mode ) = @_;
+
+ #warn "initializing for $filename\n";
+
+ if ( $filename !~ /\/rt\/.*NoAuth/ ) { #not RT images/JS
+
+ package HTML::Mason::Commands;
+ use vars qw( $cgi $p $fsurl );
+ use FS::UID qw( cgisuidsetup );
+ use FS::CGI qw( popurl rooturl );
+
+ if ( $mode eq 'apache' ) {
+ $cgi = new CGI;
+ &cgisuidsetup($cgi);
+ #&cgisuidsetup($r);
+ $fsurl = rooturl();
+ $p = popurl(2);
+ } elsif ( $mode eq 'standalone' ) {
+ $cgi = new CGI $FS::Mason::Request::QUERY_STRING; #better keep setting
+ #if you set it once
+ $FS::UID::cgi = $cgi;
+ $fsurl = $FS::Mason::Request::FSURL; #kludgy, but what the hell
+ $p = popurl(2, "$fsurl$filename");
+ } else {
+ die "unknown mode $mode";
+ }
+
+ } elsif ( $filename =~ /\/rt\/REST\/.*NoAuth/ ) {
+
+ package HTML::Mason::Commands; #?
+ use FS::UID qw( adminsuidsetup );
+
+ #need to log somebody in for the mail gw
+
+ ##old installs w/fs_selfs or selfserv??
+ #&adminsuidsetup('fs_selfservice');
+
+ &adminsuidsetup('fs_queue');
+
+ }
+
+}
+
+sub callback {
+ RT::Interface::Web::Request::callback(@_);
+}
+
+sub request_path {
+ RT::Interface::Web::Request::request_path(@_);
+}
+
+1;
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
new file mode 100644
index 0000000..b275c9d
--- /dev/null
+++ b/FS/FS/Misc.pm
@@ -0,0 +1,807 @@
+package FS::Misc;
+
+use strict;
+use vars qw ( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use Carp;
+use Data::Dumper;
+use IPC::Run qw( run timeout ); # for _pslatex
+use IPC::Run3; # for do_print... should just use IPC::Run i guess
+use File::Temp;
+#do NOT depend on any FS:: modules here, causes weird (sometimes unreproducable
+#until on client machine) dependancy loops. put them in FS::Misc::Something
+#instead
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( generate_email send_email send_fax
+ states_hash counties cities state_label
+ card_types
+ generate_ps generate_pdf do_print
+ csv_from_fixed
+ );
+
+$DEBUG = 0;
+
+=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 generate_email OPTION => VALUE ...
+
+Options:
+
+=over 4
+
+=item from
+
+Sender address, required
+
+=item to
+
+Recipient address, required
+
+=item subject
+
+email subject, required
+
+=item html_body
+
+Email body (HTML alternative). Arrayref of lines, or scalar.
+
+Will be placed inside an HTML <BODY> tag.
+
+=item text_body
+
+Email body (Text alternative). Arrayref of lines, or scalar.
+
+=back
+
+Returns an argument list to be passsed to L<send_email>.
+
+=cut
+
+#false laziness w/FS::cust_bill::generate_email
+
+use MIME::Entity;
+use HTML::Entities;
+
+sub generate_email {
+ my %args = @_;
+
+ my $me = '[FS::Misc::generate_email]';
+
+ my %return = (
+ 'from' => $args{'from'},
+ 'to' => $args{'to'},
+ 'subject' => $args{'subject'},
+ );
+
+ #if (ref($args{'to'}) eq 'ARRAY') {
+ # $return{'to'} = $args{'to'};
+ #} else {
+ # $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
+ # $self->cust_main->invoicing_list
+ # ];
+ #}
+
+ warn "$me creating HTML/text multipart message"
+ if $DEBUG;
+
+ $return{'nobody'} = 1;
+
+ my $alternative = build MIME::Entity
+ 'Type' => 'multipart/alternative',
+ 'Encoding' => '7bit',
+ 'Disposition' => 'inline'
+ ;
+
+ my $data;
+ if ( ref($args{'text_body'}) eq 'ARRAY' ) {
+ $data = $args{'text_body'};
+ } else {
+ $data = [ split(/\n/, $args{'text_body'}) ];
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/plain',
+ #'Encoding' => 'quoted-printable',
+ 'Encoding' => '7bit',
+ 'Data' => $data,
+ 'Disposition' => 'inline',
+ );
+
+ my @html_data;
+ if ( ref($args{'html_body'}) eq 'ARRAY' ) {
+ @html_data = @{ $args{'html_body'} };
+ } else {
+ @html_data = split(/\n/, $args{'html_body'});
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/html',
+ 'Encoding' => 'quoted-printable',
+ 'Data' => [ '<html>',
+ ' <head>',
+ ' <title>',
+ ' '. encode_entities($return{'subject'}),
+ ' </title>',
+ ' </head>',
+ ' <body bgcolor="#e8e8e8">',
+ @html_data,
+ ' </body>',
+ '</html>',
+ ],
+ 'Disposition' => 'inline',
+ #'Filename' => 'invoice.pdf',
+ );
+
+ #no other attachment:
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+
+ $return{'content-type'} = 'multipart/related';
+ $return{'mimeparts'} = [ $alternative ];
+ $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
+ #$return{'disposition'} = 'inline';
+
+ %return;
+
+}
+
+=item send_email OPTION => VALUE ...
+
+Options:
+
+=over 4
+
+=item from
+
+(required)
+
+=item to
+
+(required) comma-separated scalar or arrayref of recipients
+
+=item subject
+
+(required)
+
+=item content-type
+
+(optional) MIME type for the body
+
+=item body
+
+(required unless I<nobody> is true) arrayref of body text lines
+
+=item mimeparts
+
+(optional, but required if I<nobody> is true) arrayref of MIME::Entity->build PARAMHASH refs or MIME::Entity objects. These will be passed as arguments to MIME::Entity->attach().
+
+=item nobody
+
+(optional) when set true, send_email will ignore the I<body> option and simply construct a message with the given I<mimeparts>. In this case,
+I<content-type>, if specified, overrides the default "multipart/mixed" for the outermost MIME container.
+
+=item content-encoding
+
+(optional) when using nobody, optional top-level MIME
+encoding which, if specified, overrides the default "7bit".
+
+=item type
+
+(optional) type parameter for multipart/related messages
+
+=back
+
+=cut
+
+use vars qw( $conf );
+use Date::Format;
+use MIME::Entity;
+use Email::Sender::Simple qw(sendmail);
+use Email::Sender::Transport::SMTP;
+use Email::Sender::Transport::SMTP::TLS;
+use FS::UID;
+
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
+sub send_email {
+ my(%options) = @_;
+ if ( $DEBUG ) {
+ my %doptions = %options;
+ $doptions{'body'} = '(full body not shown in debug)';
+ warn "FS::Misc::send_email called with options:\n ". Dumper(\%doptions);
+# join("\n", map { " $_: ". $options{$_} } keys %options ). "\n"
+ }
+
+ my $to = ref($options{to}) ? join(', ', @{ $options{to} } ) : $options{to};
+
+ my @mimeargs = ();
+ my @mimeparts = ();
+ if ( $options{'nobody'} ) {
+
+ croak "'mimeparts' option required when 'nobody' option given\n"
+ unless $options{'mimeparts'};
+
+ @mimeparts = @{$options{'mimeparts'}};
+
+ @mimeargs = (
+ 'Type' => ( $options{'content-type'} || 'multipart/mixed' ),
+ 'Encoding' => ( $options{'content-encoding'} || '7bit' ),
+ );
+
+ } else {
+
+ @mimeparts = @{$options{'mimeparts'}}
+ if ref($options{'mimeparts'}) eq 'ARRAY';
+
+ if (scalar(@mimeparts)) {
+
+ @mimeargs = (
+ 'Type' => 'multipart/mixed',
+ 'Encoding' => '7bit',
+ );
+
+ unshift @mimeparts, {
+ 'Type' => ( $options{'content-type'} || 'text/plain' ),
+ 'Data' => $options{'body'},
+ 'Encoding' => ( $options{'content-type'} ? '-SUGGEST' : '7bit' ),
+ 'Disposition' => 'inline',
+ };
+
+ } else {
+
+ @mimeargs = (
+ 'Type' => ( $options{'content-type'} || 'text/plain' ),
+ 'Data' => $options{'body'},
+ 'Encoding' => ( $options{'content-type'} ? '-SUGGEST' : '7bit' ),
+ );
+
+ }
+
+ }
+
+ my $domain;
+ if ( $options{'from'} =~ /\@([\w\.\-]+)/ ) {
+ $domain = $1;
+ } else {
+ warn 'no domain found in invoice from address '. $options{'from'}.
+ '; constructing Message-ID (and saying HELO) @example.com';
+ $domain = 'example.com';
+ }
+ my $message_id = join('.', rand()*(2**32), $$, time). "\@$domain";
+
+ my $message = MIME::Entity->build(
+ 'From' => $options{'from'},
+ 'To' => $to,
+ 'Sender' => $options{'from'},
+ 'Reply-To' => $options{'from'},
+ 'Date' => time2str("%a, %d %b %Y %X %z", time),
+ 'Subject' => $options{'subject'},
+ 'Message-ID' => "<$message_id>",
+ @mimeargs,
+ );
+
+ if ( $options{'type'} ) {
+ #false laziness w/cust_bill::generate_email
+ $message->head->replace('Content-type',
+ $message->mime_type.
+ '; boundary="'. $message->head->multipart_boundary. '"'.
+ '; type='. $options{'type'}
+ );
+ }
+
+ foreach my $part (@mimeparts) {
+
+ if ( UNIVERSAL::isa($part, 'MIME::Entity') ) {
+
+ warn "attaching MIME part from MIME::Entity object\n"
+ if $DEBUG;
+ $message->add_part($part);
+
+ } elsif ( ref($part) eq 'HASH' ) {
+
+ warn "attaching MIME part from hashref:\n".
+ join("\n", map " $_: ".$part->{$_}, keys %$part ). "\n"
+ if $DEBUG;
+ $message->attach(%$part);
+
+ } else {
+ croak "mimepart $part isn't a hashref or MIME::Entity object!";
+ }
+
+ }
+
+ #send the email
+
+ my %smtp_opt = ( 'host' => $conf->config('smtpmachine'),
+ 'helo' => $domain,
+ );
+
+ my($port, $enc) = split('-', ($conf->config('smtp-encryption') || '25') );
+ $smtp_opt{'port'} = $port;
+
+ my $transport;
+ if ( defined($enc) && $enc eq 'starttls' ) {
+ $smtp_opt{$_} = $conf->config("smtp-$_") for qw(username password);
+ $transport = Email::Sender::Transport::SMTP::TLS->new( %smtp_opt );
+ } else {
+ if ( $conf->exists('smtp-username') && $conf->exists('smtp-password') ) {
+ $smtp_opt{"sasl_$_"} = $conf->config("smtp-$_") for qw(username password);
+ }
+ $smtp_opt{'ssl'} = 1 if defined($enc) && $enc eq 'tls';
+ $transport = Email::Sender::Transport::SMTP->new( %smtp_opt );
+ }
+
+ eval { sendmail($message, { transport => $transport }); };
+ ref($@) eq 'Email::Sender::Failure'
+ ? ( $@->code ? $@->code.' ' : '' ). $@->message
+ : $@;
+
+}
+
+=item send_fax OPTION => VALUE ...
+
+Options:
+
+I<dialstring> - (required) 10-digit phone number w/ area code
+
+I<docdata> - (required) Array ref containing PostScript or TIFF Class F document
+
+-or-
+
+I<docfile> - (required) Filename of PostScript TIFF Class F document
+
+...any other options will be passed to L<Fax::Hylafax::Client::sendfax>
+
+
+=cut
+
+sub send_fax {
+
+ my %options = @_;
+
+ die 'HylaFAX support has not been configured.'
+ unless $conf->exists('hylafax');
+
+ eval {
+ require Fax::Hylafax::Client;
+ };
+
+ if ($@) {
+ if ($@ =~ /^Can't locate Fax.*/) {
+ die "You must have Fax::Hylafax::Client installed to use invoice faxing."
+ } else {
+ die $@;
+ }
+ }
+
+ my %hylafax_opts = map { split /\s+/ } $conf->config('hylafax');
+
+ die 'Called send_fax without a \'dialstring\'.'
+ unless exists($options{'dialstring'});
+
+ if (exists($options{'docdata'}) and ref($options{'docdata'}) eq 'ARRAY') {
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp(
+ TEMPLATE => 'faxdoc.'. $options{'dialstring'} . '.XXXXXXXX',
+ DIR => $dir,
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ $options{docfile} = $fh->filename;
+
+ print $fh @{$options{'docdata'}};
+ close $fh;
+
+ delete $options{'docdata'};
+ }
+
+ die 'Called send_fax without a \'docfile\' or \'docdata\'.'
+ unless exists($options{'docfile'});
+
+ #FIXME: Need to send canonical dialstring to HylaFAX, but this only
+ # works in the US.
+
+ $options{'dialstring'} =~ s/[^\d\+]//g;
+ if ($options{'dialstring'} =~ /^\d{10}$/) {
+ $options{dialstring} = '+1' . $options{'dialstring'};
+ } else {
+ return 'Invalid dialstring ' . $options{'dialstring'} . '.';
+ }
+
+ my $faxjob = &Fax::Hylafax::Client::sendfax(%options, %hylafax_opts);
+
+ if ($faxjob->success) {
+ warn "Successfully queued fax to '$options{dialstring}' with jobid " .
+ $faxjob->jobid
+ if $DEBUG;
+ return '';
+ } else {
+ return 'Error while sending FAX: ' . $faxjob->trace;
+ }
+
+}
+
+=item states_hash COUNTRY
+
+Returns a list of key/value pairs containing state (or other sub-country
+division) abbriviations and names.
+
+=cut
+
+use FS::Record qw(qsearch);
+use Locale::SubCountry;
+
+sub states_hash {
+ my($country) = @_;
+
+ my @states =
+# sort
+ map { s/[\n\r]//g; $_; }
+ map { $_->state; }
+ qsearch({
+ 'select' => 'state',
+ 'table' => 'cust_main_county',
+ 'hashref' => { 'country' => $country },
+ 'extra_sql' => 'GROUP BY state',
+ });
+
+ #it could throw a fatal "Invalid country code" error (for example "AX")
+ my $subcountry = eval { new Locale::SubCountry($country) }
+ or return ( '', '(n/a)' );
+
+ #"i see your schwartz is as big as mine!"
+ map { ( $_->[0] => $_->[1] ) }
+ sort { $a->[1] cmp $b->[1] }
+ map { [ $_ => state_label($_, $subcountry) ] }
+ @states;
+}
+
+=item counties STATE COUNTRY
+
+Returns a list of counties for this state and country.
+
+=cut
+
+sub counties {
+ my( $state, $country ) = @_;
+
+ map { $_ } #return num_counties($state, $country) unless wantarray;
+ sort map { s/[\n\r]//g; $_; }
+ map { $_->county }
+ qsearch({
+ 'select' => 'DISTINCT county',
+ 'table' => 'cust_main_county',
+ 'hashref' => { 'state' => $state,
+ 'country' => $country,
+ },
+ });
+}
+
+=item cities COUNTY STATE COUNTRY
+
+Returns a list of cities for this county, state and country.
+
+=cut
+
+sub cities {
+ my( $county, $state, $country ) = @_;
+
+ map { $_ } #return num_cities($county, $state, $country) unless wantarray;
+ sort map { s/[\n\r]//g; $_; }
+ map { $_->city }
+ qsearch({
+ 'select' => 'DISTINCT city',
+ 'table' => 'cust_main_county',
+ 'hashref' => { 'county' => $county,
+ 'state' => $state,
+ 'country' => $country,
+ },
+ });
+}
+
+=item state_label STATE COUNTRY_OR_LOCALE_SUBCOUNRY_OBJECT
+
+=cut
+
+sub state_label {
+ my( $state, $country ) = @_;
+
+ unless ( ref($country) ) {
+ $country = eval { new Locale::SubCountry($country) }
+ or return'(n/a)';
+
+ }
+
+ # US kludge to avoid changing existing behaviour
+ # also we actually *use* the abbriviations...
+ my $full_name = $country->country_code eq 'US'
+ ? ''
+ : $country->full_name($state);
+
+ $full_name = '' if $full_name eq 'unknown';
+ $full_name =~ s/\(see also.*\)\s*$//;
+ $full_name .= " ($state)" if $full_name;
+
+ $full_name || $state || '(n/a)';
+
+}
+
+=item card_types
+
+Returns a hash reference of the accepted credit card types. Keys are shorter
+identifiers and values are the longer strings used by the system (see
+L<Business::CreditCard>).
+
+=cut
+
+#$conf from above
+
+sub card_types {
+ my $conf = new FS::Conf;
+
+ my %card_types = (
+ #displayname #value (Business::CreditCard)
+ "VISA" => "VISA card",
+ "MasterCard" => "MasterCard",
+ "Discover" => "Discover card",
+ "American Express" => "American Express card",
+ "Diner's Club/Carte Blanche" => "Diner's Club/Carte Blanche",
+ "enRoute" => "enRoute",
+ "JCB" => "JCB",
+ "BankCard" => "BankCard",
+ "Switch" => "Switch",
+ "Solo" => "Solo",
+ );
+ my @conf_card_types = grep { ! /^\s*$/ } $conf->config('card-types');
+ if ( @conf_card_types ) {
+ #perhaps the hash is backwards for this, but this way works better for
+ #usage in selfservice
+ %card_types = map { $_ => $card_types{$_} }
+ grep {
+ my $d = $_;
+ grep { $card_types{$d} eq $_ } @conf_card_types
+ }
+ keys %card_types;
+ }
+
+ \%card_types;
+}
+
+=item generate_ps FILENAME
+
+Returns an postscript rendition of the LaTex file, as a scalar.
+FILENAME does not contain the .tex suffix and is unlinked by this function.
+
+=cut
+
+use String::ShellQuote;
+
+sub generate_ps {
+ my $file = shift;
+
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ chdir($dir);
+
+ _pslatex($file);
+
+ 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 = '';
+
+ if ( $conf->exists('lpr-postscript_prefix') ) {
+ my $prefix = $conf->config('lpr-postscript_prefix');
+ $ps .= eval qq("$prefix");
+ }
+
+ while (<POSTSCRIPT>) {
+ $ps .= $_;
+ }
+
+ close POSTSCRIPT;
+
+ if ( $conf->exists('lpr-postscript_suffix') ) {
+ my $suffix = $conf->config('lpr-postscript_suffix');
+ $ps .= eval qq("$suffix");
+ }
+
+ return $ps;
+
+}
+
+=item generate_pdf FILENAME
+
+Returns an PDF rendition of the LaTex file, as a scalar. FILENAME does not
+contain the .tex suffix and is unlinked by this function.
+
+=cut
+
+use String::ShellQuote;
+
+sub generate_pdf {
+ my $file = shift;
+
+ 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.
+
+ _pslatex($file);
+
+ my $sfile = shell_quote $file;
+
+ #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;
+
+}
+
+sub _pslatex {
+ my $file = shift;
+
+ #my $sfile = shell_quote $file;
+
+ my @cmd = (
+ 'latex',
+ '-interaction=batchmode',
+ '\AtBeginDocument{\RequirePackage{pslatex}}',
+ '\def\PSLATEXTMP{\futurelet\PSLATEXTMP\PSLATEXTMPB}',
+ '\def\PSLATEXTMPB{\ifx\PSLATEXTMP\nonstopmode\else\input\fi}',
+ '\PSLATEXTMP',
+ "$file.tex"
+ );
+
+ my $timeout = 30; #? should be more than enough
+
+ for ( 1, 2 ) {
+
+ local($SIG{CHLD}) = sub {};
+ run( \@cmd, '>'=>'/dev/null', '2>'=>'/dev/null', timeout($timeout) )
+ or die "pslatex $file.tex failed; see $file.log for details?\n";
+
+ }
+
+}
+
+=item print ARRAYREF
+
+Sends the lines in ARRAYREF to the printer.
+
+=cut
+
+sub do_print {
+ my $data = shift;
+
+ my $lpr = $conf->config('lpr');
+
+ my $outerr = '';
+ run3 $lpr, $data, \$outerr, \$outerr;
+ if ( $? ) {
+ $outerr = ": $outerr" if length($outerr);
+ die "Error from $lpr (exit status ". ($?>>8). ")$outerr\n";
+ }
+
+}
+
+=item csv_from_fixed, FILEREF COUNTREF, [ LENGTH_LISTREF, [ CALLBACKS_LISTREF ] ]
+
+Converts the filehandle referenced by FILEREF from fixed length record
+lines to a CSV file according to the lengths specified in LENGTH_LISTREF.
+The CALLBACKS_LISTREF refers to a correpsonding list of coderefs. Each
+should return the value to be substituted in place of its single argument.
+
+Returns false on success or an error if one occurs.
+
+=cut
+
+sub csv_from_fixed {
+ my( $fhref, $countref, $lengths, $callbacks) = @_;
+
+ eval { require Text::CSV_XS; };
+ return $@ if $@;
+
+ my $ofh = $$fhref;
+ my $unpacker = new Text::CSV_XS;
+ my $total = 0;
+ my $template = join('', map {$total += $_; "A$_"} @$lengths) if $lengths;
+
+ my $dir = "%%%FREESIDE_CACHE%%%/cache.$FS::UID::datasrc";
+ my $fh = new File::Temp( TEMPLATE => "FILE.csv.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0,
+ ) or return "can't open temp file: $!\n"
+ if $template;
+
+ while ( defined(my $line=<$ofh>) ) {
+ $$countref++;
+ if ( $template ) {
+ my $column = 0;
+
+ chomp $line;
+ return "unexpected input at line $$countref: $line".
+ " -- expected $total but received ". length($line)
+ unless length($line) == $total;
+
+ $unpacker->combine( map { my $i = $column++;
+ defined( $callbacks->[$i] )
+ ? &{ $callbacks->[$i] }( $_ )
+ : $_
+ } unpack( $template, $line )
+ )
+ or return "invalid data for CSV: ". $unpacker->error_input;
+
+ print $fh $unpacker->string(), "\n"
+ or return "can't write temp file: $!\n";
+ }
+ }
+
+ if ( $template ) { close $$fhref; $$fhref = $fh }
+
+ seek $$fhref, 0, 0;
+ '';
+}
+
+
+=back
+
+=head1 BUGS
+
+This package exists.
+
+=head1 SEE ALSO
+
+L<FS::UID>, L<FS::CGI>, L<FS::Record>, the base documentation.
+
+L<Fax::Hylafax::Client>
+
+=cut
+
+1;
diff --git a/FS/FS/Misc/DateTime.pm b/FS/FS/Misc/DateTime.pm
new file mode 100644
index 0000000..a32c15a
--- /dev/null
+++ b/FS/FS/Misc/DateTime.pm
@@ -0,0 +1,64 @@
+package FS::Misc::DateTime;
+
+use base qw( Exporter );
+use vars qw( @EXPORT_OK );
+use Carp;
+use Date::Parse;
+use DateTime::Format::Natural;
+use FS::Conf;
+
+@EXPORT_OK = qw( parse_datetime );
+
+=head1 NAME
+
+FS::Misc::DateTime - Date and time subroutines
+
+=head1 SYNOPSIS
+
+use FS::Misc::DateTime qw( parse_datetime );
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item parse_datetime STRING
+
+Parses a date (and possibly time) from the supplied string and returns
+the date as an integer UNIX timestamp.
+
+=cut
+
+sub parse_datetime {
+ my $string = shift;
+ return '' unless $string =~ /\S/;
+
+ my $conf = new FS::Conf;
+ my $format = $conf->config('date_format') || '%m/%d/%Y';
+
+ if ( $format eq '%d/%m/%Y' ) { # =~ /\%d.*\%m/ ) {
+ #$format =~ s/\%//g;
+ my $parser = DateTime::Format::Natural->new( 'time_zone' => 'local',
+ #'format'=>'d/m/y',#lc($format)
+ );
+ $dt = $parser->parse_datetime($string);
+ if ( $parser->success ) {
+ return $dt->epoch;
+ } else {
+ #carp "WARNING: can't parse date: ". $parser->error;
+ #return '';
+ #huh, very common, we still need the "partially" (fully enough for our purposes) parsed date.
+ $dt->epoch;
+ }
+ } else {
+ return str2time($string);
+ }
+
+}
+
+=back
+
+=head1 BUGS
+
+=cut
+
+1;
diff --git a/FS/FS/Misc/eps2png.pm b/FS/FS/Misc/eps2png.pm
new file mode 100644
index 0000000..aa8e572
--- /dev/null
+++ b/FS/FS/Misc/eps2png.pm
@@ -0,0 +1,278 @@
+package FS::Misc::eps2png;
+
+#based on eps2png by Johan Vromans
+#Copyright 1994,2008 by Johan Vromans.
+#This program is free software; you can redistribute it and/or
+#modify it under the terms of the Perl Artistic License or 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.
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+use File::Temp;
+use File::Slurp qw( slurp );
+#use FS::UID;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( eps2png );
+
+################ Program parameters ################
+
+# Some GhostScript programs can produce GIF directly.
+# If not, we need the PBM package for the conversion.
+# NOTE: This will be changed upon install.
+my $use_pbm = 0;
+
+my $res = 82; # default resolution
+my $scale = 1; # default scaling
+my $mono = 0; # produce BW images if non-zero
+my $format; # output format
+my $gs_format; # GS output type
+my $output; # output, defaults to STDOUT
+my $antialias = 4; # antialiasing
+my $DEF_width; # desired widht
+my $DEF_height; # desired height
+#my $DEF_width = 90; # desired widht
+#my $DEF_height = 36; # desired height
+
+my ($verbose,$trace,$test,$debug) = (0,0,0,0);
+#handle_options ();
+set_out_type ('png'); # unless defined $format;
+warn "Producing $format ($gs_format) image.\n" if $verbose;
+
+$trace |= $test | $debug;
+$verbose |= $trace;
+
+################ Presets ################
+
+################ The Process ################
+
+my $err = 0;
+
+sub eps2png {
+ my( $eps, %options ) = @_; #well, no options yet
+
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $eps_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.eps',
+ #UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+ print $eps_file $eps;
+ close $eps_file;
+
+ my @eps = split(/\r?\n/, $eps);
+
+ warn "converting eps (". length($eps). " bytes, ". scalar(@eps). " lines)\n"
+ if $verbose;
+
+ my $line = shift @eps; #<EPS>;
+ unless ( $eps =~ /^%!PS-Adobe.*EPSF-/ ) {
+ warn "not EPS file (no %!PS-Adobe header)\n";
+ return; #empty png file?
+ }
+
+ my $ps = ""; # PostScript input data
+ my $xscale;
+ my $yscale;
+ my $gotbb;
+
+ # Prevent derived values from propagating.
+ my $width = $DEF_width;
+ my $height = $DEF_height;
+
+ while ( @eps ) {
+
+ $line = shift(@eps)."\n";
+
+ # Search for BoundingBox.
+ if ( $line =~ /^%%BoundingBox:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/i ) {
+ $gotbb++;
+ warn "$eps_file: x0=$1, y0=$2, w=", $3-$1, ", h=", $4-$2
+ if $verbose;
+
+ if ( defined $width ) {
+ $res = 72;
+ $xscale = $width / ($3 - $1);
+ if ( defined $height ) {
+ $yscale = $height / ($4 - $2);
+ }
+ else {
+ $yscale = $xscale;
+ $height = ($4 - $2) * $yscale;
+ }
+ }
+ elsif ( defined $height ) {
+ $res = 72;
+ $yscale = $height / ($4 - $2);
+ if ( defined $width ) {
+ $xscale = $width / ($3 - $1);
+ }
+ else {
+ $xscale = $yscale;
+ $width = ($3 - $1) * $xscale;
+ }
+ }
+ unless ( defined $xscale ) {
+ $xscale = $yscale = $scale;
+ # Calculate actual width.
+ $width = $3 - $1;
+ $height = $4 - $2;
+ # Normal PostScript resolution is 72.
+ $width *= $res/72 * $xscale;
+ $height *= $res/72 * $yscale;
+ # Round up.
+ $width = int ($width + 0.5) + 1;
+ $height = int ($height + 0.5) + 1;
+ }
+ warn ", width=$width, height=$height\n" if $verbose;
+
+ # Scale.
+ $ps .= "$xscale $yscale scale\n"
+ if $xscale != 1 || $yscale != 1;
+
+ # Create PostScript code to translate coordinates.
+ $ps .= (0-$1) . " " . (0-$2) . " translate\n"
+ unless $1 == 0 && $2 == 0;
+
+ # Include the image, show and quit.
+ $ps .= "($eps_file) run\n".
+ "showpage\n".
+ "quit\n";
+
+ last;
+ }
+ elsif ( $line =~ /^%%EndComments/i ) {
+ last;
+ }
+ }
+
+ unless ( $gotbb ) {
+ warn "No bounding box in $eps_file\n";
+ return;
+ }
+
+ #it would be better to ask gs to spit out files on stdout, but c'est la vie
+
+ #my $out_file; # output file
+ #my $pbm_file; # temporary file for PBM conversion
+
+ my $out_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.png',
+ #UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ my $pbm_file = new File::Temp( TEMPLATE => 'image.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.pbm',
+ #UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ # Note the temporary PBM file is created where the output file is
+ # located, since that will guarantee accessibility (and a valid
+ # filename).
+ warn "Creating $out_file\n" if $verbose;
+
+ my $gs0 = "gs -q -dNOPAUSE -r$res -g${width}x$height";
+ my $gs1 = "-";
+ $gs0 .= " -dTextAlphaBits=$antialias -dGraphicsAlphaBits=$antialias"
+ if $antialias;
+ if ( $format eq 'png' ) {
+ mysystem ("$gs0 -sDEVICE=". ($mono ? "pngmono" : $gs_format).
+ " -sOutputFile=$out_file $gs1", $ps);
+ }
+ elsif ( $format eq 'jpg' ) {
+ mysystem ("$gs0 -sDEVICE=". ($mono ? "jpeggray" : $gs_format).
+ " -sOutputFile=$out_file $gs1", $ps);
+ }
+ elsif ( $format eq 'gif' ) {
+ if ( $use_pbm ) {
+ # Convert to PPM and use some of the PBM converters.
+ mysystem ("$gs0 -sDEVICE=". ($mono ? "pbm" : "ppm").
+ " -sOutputFile=$pbm_file $gs1", $ps);
+ # mysystem ("pnmcrop $pbm_file | ppmtogif > $out_file");
+ mysystem ("ppmtogif $pbm_file > $out_file");
+ unlink ($pbm_file);
+ }
+ else {
+ # GhostScript has GIF drivers built-in.
+ mysystem ("$gs0 -sDEVICE=". ($mono ? "gifmono" : "gif8").
+ " -sOutputFile=$out_file $gs1", $ps);
+ }
+ }
+ else {
+ warn "ASSERT ERROR: Unhandled output type: $format\n";
+ exit (1);
+ }
+
+# unless ( -s $out_file ) {
+# warn "Problem creating $out_file for $eps_file\n";
+# $err++;
+# }
+
+ slurp($out_file);
+
+}
+
+exit 1 if $err;
+
+################ Subroutines ################
+
+sub mysystem {
+ my ($cmd, $data) = @_;
+ warn "+ $cmd\n" if $trace;
+ if ( $data ) {
+ if ( $trace ) {
+ my $dp = ">> " . $data;
+ $dp =~ s/\n(.)/\n>> $1/g;
+ warn "$dp";
+ }
+ open (CMD, "|$cmd") or die ("$cmd: $!\n");
+ print CMD $data;
+ close CMD or die ("$cmd close: $!\n");
+ }
+ else {
+ system ($cmd);
+ }
+}
+
+sub set_out_type {
+ my ($opt) = lc (shift (@_));
+ if ( $opt =~ /^png(mono|gray|16|256|16m|alpha)?$/ ) {
+ $format = 'png';
+ $gs_format = $format.(defined $1 ? $1 : '16m');
+ }
+ elsif ( $opt =~ /^gif(mono)?$/ ) {
+ $format = 'gif';
+ $gs_format = $format.(defined $1 ? $1 : '');
+ }
+ elsif ( $opt =~ /^(jpg|jpeg)(gray)?$/ ) {
+ $format = 'jpg';
+ $gs_format = 'jpeg'.(defined $2 ? $2 : '');
+ }
+ else {
+ warn "ASSERT ERROR: Invalid value to set_out_type: $opt\n";
+ exit (1);
+ }
+}
+
+# 'antialias|aa=i' => \$antialias,
+# 'noantialias|noaa' => sub { $antialias = 0 },
+# 'scale=f' => \$scale,
+# 'width=i' => \$width,
+# 'height=i' => \$height,
+# 'resolution=i' => \$res,
+
+# die ("Antialias value must be 0, 1, 2, 4, or 8\n")
+
+# -width XXX desired with
+# -height XXX desired height
+# -resolution XXX resolution (default = $res)
+# -scale XXX scaling factor
+# -antialias XX antialias factor (must be 0, 1, 2, 4 or 8; default: 4)
+# -noantialias no antialiasing (same as -antialias 0)
+
+1;
diff --git a/FS/FS/Misc/prune.pm b/FS/FS/Misc/prune.pm
new file mode 100644
index 0000000..3f0c79d
--- /dev/null
+++ b/FS/FS/Misc/prune.pm
@@ -0,0 +1,131 @@
+package FS::Misc::prune;
+
+use strict;
+use vars qw ( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use FS::Record qw(dbh qsearch);
+use FS::cust_credit_refund;
+#use FS::cust_credit_bill;
+#use FS::cust_bill_pay;
+#use FS::cust_pay_refund;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( prune_applications );
+
+=head1 NAME
+
+FS::Misc::prune - misc. pruning subroutines
+
+=head1 SYNOPSIS
+
+use FS::Misc::prune qw(prune_applications);
+
+prune_applications();
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item prune_applications OPTION_HASH
+
+Removes applications of credits to refunds in the event that the database
+is corrupt and either the credits or refunds are missing (see
+L<FS::cust_credit>, L<FS::cust_refund>, and L<FS::cust_credit_refund>).
+If the OPTION_HASH contains the element 'dry_run' then a report of
+affected records is returned rather than actually deleting the records.
+
+=cut
+
+sub prune_applications {
+ my $options = shift;
+ my $dbh = dbh;
+
+ local $DEBUG = 1 if exists($options->{debug});
+
+ my $ccr = <<EOW;
+ WHERE
+ 0 = (select count(*) from cust_credit
+ where cust_credit_refund.crednum = cust_credit.crednum)
+ or
+ 0 = (select count(*) from cust_refund
+ where cust_credit_refund.refundnum = cust_refund.refundnum)
+EOW
+ my $ccb = <<EOW;
+ WHERE
+ 0 = (select count(*) from cust_credit
+ where cust_credit_bill.crednum = cust_credit.crednum)
+ or
+ 0 = (select count(*) from cust_bill
+ where cust_credit_bill.invnum = cust_bill.invnum)
+EOW
+ my $cbp = <<EOW;
+ WHERE
+ 0 = (select count(*) from cust_bill
+ where cust_bill_pay.invnum = cust_bill.invnum)
+ or
+ 0 = (select count(*) from cust_pay
+ where cust_bill_pay.paynum = cust_pay.paynum)
+EOW
+ my $cpr = <<EOW;
+ WHERE
+ 0 = (select count(*) from cust_pay
+ where cust_pay_refund.paynum = cust_pay.paynum)
+ or
+ 0 = (select count(*) from cust_refund
+ where cust_pay_refund.refundnum = cust_refund.refundnum)
+EOW
+
+ my %strays = (
+ 'cust_credit_refund' => { clause => $ccr,
+ link1 => 'crednum',
+ link2 => 'refundnum',
+ },
+# 'cust_credit_bill' => { clause => $ccb,
+# link1 => 'crednum',
+# link2 => 'refundnum',
+# },
+# 'cust_bill_pay' => { clause => $cbp,
+# link1 => 'crednum',
+# link2 => 'refundnum',
+# },
+# 'cust_pay_refund' => { clause => $cpr,
+# link1 => 'crednum',
+# link2 => 'refundnum',
+# },
+ );
+
+ if ( exists($options->{dry_run}) ) {
+ my @response = ();
+ foreach my $table (keys %strays) {
+ my $clause = $strays{$table}->{clause};
+ my $link1 = $strays{$table}->{link1};
+ my $link2 = $strays{$table}->{link2};
+ my @rec = qsearch($table, {}, '', $clause);
+ my $keyname = $rec[0]->primary_key if $rec[0];
+ foreach (@rec) {
+ push @response, "$table " .$_->$keyname . " claims attachment to ".
+ "$link1 " . $_->$link1 . " and $link2 " . $_->$link2 . "\n";
+ }
+ }
+ return (@response);
+ } else {
+ foreach (keys %strays) {
+ my $statement = "DELETE FROM $_ " . $strays{$_}->{clause};
+ warn $statement if $DEBUG;
+ my $sth = $dbh->prepare($statement)
+ or die $dbh->errstr;
+ $sth->execute
+ or die $sth->errstr;
+ }
+ return ();
+ }
+}
+
+=back
+
+=head1 BUGS
+
+=cut
+
+1;
+
diff --git a/FS/FS/Msgcat.pm b/FS/FS/Msgcat.pm
new file mode 100644
index 0000000..70933b2
--- /dev/null
+++ b/FS/FS/Msgcat.pm
@@ -0,0 +1,100 @@
+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; #wtf? causes dependency loops too.
+use FS::msgcat;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( gettext geterror );
+
+FS::UID->install_callback( sub {
+ eval "use FS::Conf;";
+ die $@ if $@;
+ $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/Pony.pm b/FS/FS/Pony.pm
new file mode 100644
index 0000000..c37dd78
--- /dev/null
+++ b/FS/FS/Pony.pm
@@ -0,0 +1,23 @@
+package FS::Pony;
+
+=head1 NAME
+
+FS::Pony - A pony
+
+=head1 SYNOPSYS
+
+use FS::Pony; # <-- yours!
+
+=head1 DESCRIPTION
+
+We told you it came with a pony.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+http://420.am/~ivan/nopony.jpg
+
+=cut
+
+1;
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
new file mode 100644
index 0000000..0845cc6
--- /dev/null
+++ b/FS/FS/Record.pm
@@ -0,0 +1,3043 @@
+package FS::Record;
+
+use strict;
+use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
+ $conf $conf_encryption $me
+ %virtual_fields_cache
+ $nowarn_identical $nowarn_classload
+ $no_update_diff $no_check_foreign
+ );
+use Exporter;
+use Carp qw(carp cluck croak confess);
+use Scalar::Util qw( blessed );
+use File::CounterFile;
+use Locale::Country;
+use Text::CSV_XS;
+use File::Slurp qw( slurp );
+use DBI qw(:sql_types);
+use DBIx::DBSchema 0.38;
+use FS::UID qw(dbh getotaker datasrc driver_name);
+use FS::CurrentUser;
+use FS::Schema qw(dbdef);
+use FS::SearchCache;
+use FS::Msgcat qw(gettext);
+#use FS::Conf; #dependency loop bs, in install_callback below instead
+
+use FS::part_virtual_field;
+
+use Tie::IxHash;
+
+@ISA = qw(Exporter);
+
+#export dbdef for now... everything else expects to find it here
+@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch
+ str2time_sql str2time_sql_closing regexp_sql not_regexp_sql );
+
+$DEBUG = 0;
+$me = '[FS::Record]';
+
+$nowarn_identical = 0;
+$nowarn_classload = 0;
+$no_update_diff = 0;
+$no_check_foreign = 0;
+
+my $rsa_module;
+my $rsa_loaded;
+my $rsa_encrypt;
+my $rsa_decrypt;
+
+$conf = '';
+$conf_encryption = '';
+FS::UID->install_callback( sub {
+ eval "use FS::Conf;";
+ die $@ if $@;
+ $conf = FS::Conf->new;
+ $conf_encryption = $conf->exists('encryption');
+ $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc;
+ if ( driver_name eq 'Pg' ) {
+ eval "use DBD::Pg ':pg_types'";
+ die $@ if $@;
+ } else {
+ eval "sub PG_BYTEA { die 'guru meditation #9: calling PG_BYTEA when not running Pg?'; }";
+ }
+} );
+
+=head1 NAME
+
+FS::Record - Database record objects
+
+=head1 SYNOPSIS
+
+ use FS::Record;
+ use FS::Record qw(dbh fields qsearch qsearchs);
+
+ $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_floatn('column');
+ $error = $record->ut_number('column');
+ $error = $record->ut_numbern('column');
+ $error = $record->ut_snumber('column');
+ $error = $record->ut_snumbern('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');
+
+ $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'}
+ unless $nowarn_classload;
+ }
+
+ $self->{'Hash'} = shift;
+
+ foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
+ $self->{'Hash'}{$field}='';
+ }
+
+ $self->_rebless if $self->can('_rebless');
+
+ $self->{'modified'} = 0;
+
+ $self->_cache($self->{'Hash'}, 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 PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
+
+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.
+
+The preferred usage is to pass a hash reference of named parameters:
+
+ @records = qsearch( {
+ 'table' => 'table_name',
+ 'hashref' => { 'field' => 'value'
+ 'field' => { 'op' => '<',
+ 'value' => '420',
+ },
+ },
+
+ #these are optional...
+ 'select' => '*',
+ 'extra_sql' => 'AND field = ? AND intfield = ?',
+ 'extra_param' => [ 'value', [ 5, 'int' ] ],
+ 'order_by' => 'ORDER BY something',
+ #'cache_obj' => '', #optional
+ 'addl_from' => 'LEFT JOIN othtable USING ( field )',
+ 'debug' => 1,
+ }
+ );
+
+Much code still uses old-style positional parameters, this is also probably
+fine in the common case where there are only two parameters:
+
+ my @records = qsearch( 'table', { 'field' => 'value' } );
+
+Also possible is an experimental LISTREF of PARAMS_HASHREFs for a UNION of
+the individual PARAMS_HASHREF queries
+
+###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
+
+my %TYPE = (); #for debugging
+
+sub _bind_type {
+ my($type, $value) = @_;
+
+ my $bind_type = { TYPE => SQL_VARCHAR };
+
+ if ( $type =~ /(big)?(int|serial)/i && $value =~ /^\d+(\.\d+)?$/ ) {
+
+ $bind_type = { TYPE => SQL_INTEGER };
+
+ } elsif ( $type =~ /^bytea$/i || $type =~ /(blob|varbinary)/i ) {
+
+ if ( driver_name eq 'Pg' ) {
+ no strict 'subs';
+ $bind_type = { pg_type => PG_BYTEA };
+ #} else {
+ # $bind_type = ? #SQL_VARCHAR could be fine?
+ }
+
+ #DBD::Pg 1.49: Cannot bind ... unknown sql_type 6 with SQL_FLOAT
+ #fixed by DBD::Pg 2.11.8
+ #can change back to SQL_FLOAT in early-mid 2010, once everyone's upgraded
+ #(make a Tron test first)
+ } elsif ( _is_fs_float( $type, $value ) ) {
+
+ $bind_type = { TYPE => SQL_DECIMAL };
+
+ }
+
+ $bind_type;
+
+}
+
+sub _is_fs_float {
+ my($type, $value) = @_;
+ if ( ( $type =~ /(numeric)/i && $value =~ /^[+-]?\d+(\.\d+)?$/ ) ||
+ ( $type =~ /(real|float4)/i && $value =~ /[-+]?\d*\.?\d+([eE][-+]?\d+)?/)
+ ) {
+ return 1;
+ }
+ '';
+}
+
+sub qsearch {
+ my( @stable, @record, @cache );
+ my( @select, @extra_sql, @extra_param, @order_by, @addl_from );
+ my @debug = ();
+ my %union_options = ();
+ if ( ref($_[0]) eq 'ARRAY' ) {
+ my $optlist = shift;
+ %union_options = @_;
+ foreach my $href ( @$optlist ) {
+ push @stable, ( $href->{'table'} or die "table name is required" );
+ push @record, ( $href->{'hashref'} || {} );
+ push @select, ( $href->{'select'} || '*' );
+ push @extra_sql, ( $href->{'extra_sql'} || '' );
+ push @extra_param, ( $href->{'extra_param'} || [] );
+ push @order_by, ( $href->{'order_by'} || '' );
+ push @cache, ( $href->{'cache_obj'} || '' );
+ push @addl_from, ( $href->{'addl_from'} || '' );
+ push @debug, ( $href->{'debug'} || '' );
+ }
+ die "at least one hashref is required" unless scalar(@stable);
+ } elsif ( ref($_[0]) eq 'HASH' ) {
+ my $opt = shift;
+ $stable[0] = $opt->{'table'} or die "table name is required";
+ $record[0] = $opt->{'hashref'} || {};
+ $select[0] = $opt->{'select'} || '*';
+ $extra_sql[0] = $opt->{'extra_sql'} || '';
+ $extra_param[0] = $opt->{'extra_param'} || [];
+ $order_by[0] = $opt->{'order_by'} || '';
+ $cache[0] = $opt->{'cache_obj'} || '';
+ $addl_from[0] = $opt->{'addl_from'} || '';
+ $debug[0] = $opt->{'debug'} || '';
+ } else {
+ ( $stable[0],
+ $record[0],
+ $select[0],
+ $extra_sql[0],
+ $cache[0],
+ $addl_from[0]
+ ) = @_;
+ $select[0] ||= '*';
+ }
+ my $cache = $cache[0];
+
+ my @statement = ();
+ my @value = ();
+ my @bind_type = ();
+ my $dbh = dbh;
+ foreach my $stable ( @stable ) {
+ my $record = shift @record;
+ my $select = shift @select;
+ my $extra_sql = shift @extra_sql;
+ my $extra_param = shift @extra_param;
+ my $order_by = shift @order_by;
+ my $cache = shift @cache;
+ my $addl_from = shift @addl_from;
+ my $debug = shift @debug;
+
+ #$stable =~ /^([\w\_]+)$/ or die "Illegal table: $table";
+ #for jsearch
+ $stable =~ /^([\w\s\(\)\.\,\=]+)$/ or die "Illegal table: $stable";
+ $stable = $1;
+
+ my $table = $cache ? $cache->table : $stable;
+ my $dbdef_table = dbdef->table($table)
+ or die "No schema for table $table found - ".
+ "do you need to run freeside-upgrade?";
+ 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"
+ unless $nowarn_classload;
+ @virtual_fields = ();
+ }
+
+ my $statement .= "SELECT $select FROM $stable";
+ $statement .= " $addl_from" if $addl_from;
+ if ( @real_fields or @virtual_fields ) {
+ $statement .= ' WHERE '. join(' AND ',
+ get_real_fields($table, $record, \@real_fields) ,
+ get_virtual_fields($table, $pkey, $record, \@virtual_fields),
+ );
+ }
+
+ $statement .= " $extra_sql" if defined($extra_sql);
+ $statement .= " $order_by" if defined($order_by);
+
+ push @statement, $statement;
+
+ warn "[debug]$me $statement\n" if $DEBUG > 1 || $debug;
+
+
+ foreach my $field (
+ grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
+ ) {
+
+ my $value = $record->{$field};
+ my $op = (ref($value) && $value->{op}) ? $value->{op} : '=';
+ $value = $value->{'value'} if ref($value);
+ my $type = dbdef->table($table)->column($field)->type;
+
+ my $bind_type = _bind_type($type, $value);
+
+ #if ( $DEBUG > 2 ) {
+ # no strict 'refs';
+ # %TYPE = map { &{"DBI::$_"}() => $_ } @{ $DBI::EXPORT_TAGS{sql_types} }
+ # unless keys %TYPE;
+ # warn " bind_param $bind (for field $field), $value, TYPE $TYPE{$TYPE}\n";
+ #}
+
+ push @value, $value;
+ push @bind_type, $bind_type;
+
+ }
+
+ foreach my $param ( @$extra_param ) {
+ my $bind_type = { TYPE => SQL_VARCHAR };
+ my $value = $param;
+ if ( ref($param) ) {
+ $value = $param->[0];
+ my $type = $param->[1];
+ $bind_type = _bind_type($type, $value);
+ }
+ push @value, $value;
+ push @bind_type, $bind_type;
+ }
+ }
+
+ my $statement = join( ' ) UNION ( ', @statement );
+ $statement = "( $statement )" if scalar(@statement) > 1;
+ $statement .= " $union_options{order_by}" if $union_options{order_by};
+
+ my $sth = $dbh->prepare($statement)
+ or croak "$dbh->errstr doing $statement";
+
+ my $bind = 1;
+ foreach my $value ( @value ) {
+ my $bind_type = shift @bind_type;
+ $sth->bind_param($bind++, $value, $bind_type );
+ }
+
+# $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;
+
+ # virtual fields and blessings are nonsense in a heterogeneous UNION, right?
+ my $table = $stable[0];
+ my $pkey = '';
+ $table = '' if grep { $_ ne $table } @stable;
+ $pkey = dbdef->table($table)->primary_key if $table;
+
+ my @virtual_fields = ();
+ if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
+ @virtual_fields = "FS::$table"->virtual_fields;
+ } else {
+ cluck "warning: FS::$table not loaded; virtual fields not returned either"
+ unless $nowarn_classload;
+ @virtual_fields = ();
+ }
+
+ my %result;
+ tie %result, "Tie::IxHash";
+ my @stuff = @{ $sth->fetchall_arrayref( {} ) };
+ if ( $pkey && scalar(@stuff) && $stuff[0]->{$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;
+ }
+ }
+ }
+ my @return;
+ 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 ) {
+ @return = map {
+ new_or_cached( "FS::$table", { %{$_} }, $cache )
+ } values(%result);
+ } else {
+ @return = map {
+ new( "FS::$table", { %{$_} } )
+ } values(%result);
+ }
+ } else {
+ #okay, its been tested
+ # warn "untested code (class FS::$table uses custom new method)";
+ @return = map {
+ eval 'FS::'. $table. '->new( { %{$_} } )';
+ } values(%result);
+ }
+
+ # Check for encrypted fields and decrypt them.
+ ## only in the local copy, not the cached object
+ if ( $conf_encryption
+ && eval 'defined(@FS::'. $table . '::encrypted_fields)' ) {
+ foreach my $record (@return) {
+ foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
+ # Set it directly... This may cause a problem in the future...
+ $record->setfield($field, $record->decrypt($record->getfield($field)));
+ }
+ }
+ }
+ } else {
+ cluck "warning: FS::$table not loaded; returning FS::Record objects"
+ unless $nowarn_classload;
+ @return = map {
+ FS::Record->new( $table, { %{$_} } );
+ } values(%result);
+ }
+ return @return;
+}
+
+## makes this easier to read
+
+sub get_virtual_fields {
+ my $table = shift;
+ my $pkey = shift;
+ my $record = shift;
+ my $virtual_fields = shift;
+
+ return
+ ( 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 } ) ;
+}
+
+sub get_real_fields {
+ my $table = shift;
+ my $record = shift;
+ my $real_fields = shift;
+
+ ## this huge map was previously inline, just broke it out to help read the qsearch method, should be optimized for readability
+ return (
+ map {
+
+ my $op = '=';
+ my $column = $_;
+ my $type = dbdef->table($table)->column($column)->type;
+ my $value = $record->{$column};
+ $value = $value->{'value'} if ref($value);
+ 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' ) {
+ if ( $type =~ /(int|numeric|real|float4|(big)?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' ) {
+ if ( $type =~ /(int|numeric|real|float4|(big)?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 "" )-;
+ }
+ }
+ #if this needs to be re-enabled, it needs to use a custom op like
+ #"APPROX=" or something (better name?, not '=', to avoid affecting other
+ # searches
+ #} elsif ( $op eq 'APPROX=' && _is_fs_float( $type, $value ) ) {
+ # ( "$column <= ?", "$column >= ?" );
+ } else {
+ "$column $op ?";
+ }
+ } @{ $real_fields } );
+}
+
+=item by_key PRIMARY_KEY_VALUE
+
+This is a class method that returns the record with the given primary key
+value. This method is only useful in FS::Record subclasses. For example:
+
+ my $cust_main = FS::cust_main->by_key(1); # retrieve customer with custnum 1
+
+is equivalent to:
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => 1 } );
+
+=cut
+
+sub by_key {
+ my ($class, $pkey_value) = @_;
+
+ my $table = $class->table
+ or croak "No table for $class found";
+
+ 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
+ or die "No primary key for table $table";
+
+ return qsearchs($table, { $pkey => $pkey_value });
+}
+
+=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 PARAMS_HASHREF | TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ, ADDL_FROM
+
+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(@_);
+ cluck "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 primary_key
+
+Returns the primary key for the table.
+
+=cut
+
+sub primary_key {
+ my $self = shift;
+ my $pkey = $self->dbdef_table->primary_key;
+}
+
+=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->{'modified'} = 1;
+ $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 blessed($self) && $self->can('setfield');
+ $self->setfield($field,$value);
+ } else {
+ confess "errant AUTOLOAD $field for $self (no args)"
+ unless blessed($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. This may be deprecated in the
+future; if there's a reason you can't just use the autoloaded or get/set
+methods, speak up.
+
+=cut
+
+sub hashref {
+ my($self) = @_;
+ $self->{'Hash'};
+}
+
+=item modified
+
+Returns true if any of this object's values have been modified with set (or via
+an autoloaded method). Doesn't yet recognize when you retreive a hashref and
+modify that.
+
+=cut
+
+sub modified {
+ my $self = shift;
+ $self->{'modified'};
+}
+
+=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;
+ my $primary_key = $self->primary_key;
+ qsearchs( {
+ 'select' => '*',
+ 'table' => $self->table,
+ 'hashref' => { $primary_key => $self->$primary_key() },
+ 'extra_sql' => 'FOR UPDATE',
+ } );
+}
+
+=item lock_table
+
+Locks this table with a database-driver specific lock method. This is used
+as a mutex in order to do a duplicate search.
+
+For PostgreSQL, does "LOCK TABLE tablename IN SHARE ROW EXCLUSIVE MODE".
+
+For MySQL, does a SELECT FOR UPDATE on the duplicate_lock table.
+
+Errors are fatal; no useful return value.
+
+Note: To use this method for new tables other than svc_acct and svc_phone,
+edit freeside-upgrade and add those tables to the duplicate_lock list.
+
+=cut
+
+sub lock_table {
+ my $self = shift;
+ my $table = $self->table;
+
+ warn "$me locking $table table\n" if $DEBUG;
+
+ if ( driver_name =~ /^Pg/i ) {
+
+ dbh->do("LOCK TABLE $table IN SHARE ROW EXCLUSIVE MODE")
+ or die dbh->errstr;
+
+ } elsif ( driver_name =~ /^mysql/i ) {
+
+ dbh->do("SELECT * FROM duplicate_lock
+ WHERE lockname = '$table'
+ FOR UPDATE"
+ ) or die dbh->errstr;
+
+ } else {
+
+ die "unknown database ". driver_name. "; don't know how to lock table";
+
+ }
+
+ warn "$me acquired $table table lock\n" if $DEBUG;
+
+}
+
+=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 $saved = {};
+
+ warn "$self -> insert" if $DEBUG;
+
+ 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) =~ /^(BIG)?SERIAL\d?/
+ || ( driver_name eq 'Pg'
+ && defined($col->default)
+ && $col->quoted_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;
+
+ # Encrypt before the database
+ if ( defined(eval '@FS::'. $table . '::encrypted_fields')
+ && scalar( eval '@FS::'. $table . '::encrypted_fields')
+ && $conf->exists('encryption')
+ ) {
+ foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
+ $self->{'saved'} = $self->getfield($field);
+ $self->setfield($field, $self->encrypt($self->getfield($field)));
+ }
+ }
+
+ #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 ";
+ if ( @real_fields ) {
+ $statement .=
+ "( ".
+ join( ', ', @real_fields ).
+ ") VALUES (".
+ join( ', ', @values ).
+ ")"
+ ;
+ } else {
+ $statement .= 'DEFAULT 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;
+
+ # get inserted id from the database, if applicable & needed
+ if ( $db_seq && ! $self->getfield($primary_key) ) {
+ warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
+
+ my $insertid = '';
+
+ if ( driver_name eq 'Pg' ) {
+
+ #my $oid = $sth->{'pg_oid_status'};
+ #my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?";
+
+ my $default = $self->dbdef_table->column($primary_key)->quoted_default;
+ unless ( $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i ) {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return "can't parse $table.$primary_key default value".
+ " for sequence name: $default";
+ }
+ my $sequence = $1;
+
+ my $i_sql = "SELECT currval('$sequence')";
+ my $i_sth = dbh->prepare($i_sql) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+ $i_sth->execute() or do { #$i_sth->execute($oid)
+ 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;
+
+ # Now that it has been saved, reset the encrypted fields so that $new
+ # can still be used.
+ foreach my $field (keys %{$saved}) {
+ $self->setfield($field, $saved->{$field});
+ }
+
+ '';
+}
+
+=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, $old) = (shift, shift);
+
+ $old = $new->replace_old unless defined($old);
+
+ warn "[debug]$me $new ->replace $old\n" if $DEBUG;
+
+ if ( $new->can('replace_check') ) {
+ my $error = $new->replace_check($old);
+ return $error if $error;
+ }
+
+ 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 $primary_key ".
+ 'from '. $old->getfield($primary_key).
+ ' to ' . $new->getfield($primary_key)
+ if $primary_key
+ && ( $old->getfield($primary_key) ne $new->getfield($primary_key) );
+
+ my $error = $new->check;
+ return $error if $error;
+
+ # Encrypt for replace
+ my $saved = {};
+ if ( $conf->exists('encryption')
+ && defined(eval '@FS::'. $new->table . '::encrypted_fields')
+ && scalar( eval '@FS::'. $new->table . '::encrypted_fields')
+ ) {
+ foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
+ $saved->{$field} = $new->getfield($field);
+ $new->setfield($field, $new->encrypt($new->getfield($field)));
+ }
+ }
+
+ #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) || $no_update_diff ) {
+ carp "[warning]$me $new -> replace $old: records identical"
+ unless $nowarn_identical;
+ return '';
+ }
+
+ my $statement = "UPDATE ". $old->table. " SET ". join(', ',
+ map {
+ "$_ = ". _quote($new->getfield($_),$old->table,$_)
+ } real_fields($old->table)
+ ). ' WHERE '.
+ join(' AND ',
+ map {
+
+ if ( $old->getfield($_) eq '' ) {
+
+ #false laziness w/qsearch
+ if ( driver_name eq 'Pg' ) {
+ my $type = $old->dbdef_table->column($_)->type;
+ if ( $type =~ /(int|(big)?serial)/i ) {
+ qq-( $_ IS NULL )-;
+ } else {
+ qq-( $_ IS NULL OR $_ = '' )-;
+ }
+ } else {
+ qq-( $_ IS NULL OR $_ = "" )-;
+ }
+
+ } else {
+ "$_ = ". _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;
+
+ # Now that it has been saved, reset the encrypted fields so that $new
+ # can still be used.
+ foreach my $field (keys %{$saved}) {
+ $new->setfield($field, $saved->{$field});
+ }
+
+ '';
+
+}
+
+sub replace_old {
+ my( $self ) = shift;
+ warn "[$me] replace called with no arguments; autoloading old record\n"
+ if $DEBUG;
+
+ my $primary_key = $self->dbdef_table->primary_key;
+ if ( $primary_key ) {
+ $self->by_key( $self->$primary_key() ) #this is what's returned
+ or croak "can't find ". $self->table. ".$primary_key ".
+ $self->$primary_key();
+ } else {
+ croak $self->table. " has no primary key; pass old record as argument";
+ }
+
+}
+
+=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, $_);
+ }
+ }
+ '';
+}
+
+=item process_batch_import JOB OPTIONS_HASHREF PARAMS
+
+Processes a batch import as a queued JSRPC job
+
+JOB is an FS::queue entry.
+
+OPTIONS_HASHREF can have the following keys:
+
+=over 4
+
+=item table
+
+Table name (required).
+
+=item params
+
+Listref of field names for static fields. They will be given values from the
+PARAMS hashref and passed as a "params" hashref to batch_import.
+
+=item formats
+
+Formats hashref. Keys are field names, values are listrefs that define the
+format.
+
+Each listref value can be a column name or a code reference. Coderefs are run
+with the row object, data and a FS::Conf object as the three parameters.
+For example, this coderef does the same thing as using the "columnname" string:
+
+ sub {
+ my( $record, $data, $conf ) = @_;
+ $record->columnname( $data );
+ },
+
+Coderefs are run after all "column name" fields are assigned.
+
+=item format_types
+
+Optional format hashref of types. Keys are field names, values are "csv",
+"xls" or "fixedlength". Overrides automatic determination of file type
+from extension.
+
+=item format_headers
+
+Optional format hashref of header lines. Keys are field names, values are 0
+for no header, 1 to ignore the first line, or to higher numbers to ignore that
+number of lines.
+
+=item format_sep_chars
+
+Optional format hashref of CSV sep_chars. Keys are field names, values are the
+CSV separation character.
+
+=item format_fixedlenth_formats
+
+Optional format hashref of fixed length format defintiions. Keys are field
+names, values Parse::FixedLength listrefs of field definitions.
+
+=item default_csv
+
+Set true to default to CSV file type if the filename does not contain a
+recognizable ".csv" or ".xls" extension (and type is not pre-specified by
+format_types).
+
+=back
+
+PARAMS is a base64-encoded Storable string containing the POSTed data as
+a hash ref. It normally contains at least one field, "uploaded files",
+generated by /elements/file-upload.html and containing the list of uploaded
+files. Currently only supports a single file named "file".
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_batch_import {
+ my($job, $opt) = ( shift, shift );
+
+ my $table = $opt->{table};
+ my @pass_params = $opt->{params} ? @{ $opt->{params} } : ();
+ my %formats = %{ $opt->{formats} };
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $files = $param->{'uploaded_files'}
+ or die "No files provided.\n";
+
+ my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+ my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+ my $file = $dir. $files{'file'};
+
+ my %iopt = (
+ #class-static
+ table => $table,
+ formats => \%formats,
+ format_types => $opt->{format_types},
+ format_headers => $opt->{format_headers},
+ format_sep_chars => $opt->{format_sep_chars},
+ format_fixedlength_formats => $opt->{format_fixedlength_formats},
+ #per-import
+ job => $job,
+ file => $file,
+ #type => $type,
+ format => $param->{format},
+ params => { map { $_ => $param->{$_} } @pass_params },
+ #?
+ default_csv => $opt->{default_csv},
+ );
+
+ if ( $opt->{'batch_namecol'} ) {
+ $iopt{'batch_namevalue'} = $param->{ $opt->{'batch_namecol'} };
+ $iopt{$_} = $opt->{$_} foreach qw( batch_keycol batch_table batch_namecol );
+ }
+
+ my $error = FS::Record::batch_import( \%iopt );
+
+ unlink $file;
+
+ die "$error\n" if $error;
+}
+
+=item batch_import PARAM_HASHREF
+
+Class method for batch imports. Available params:
+
+=over 4
+
+=item table
+
+=item formats
+
+=item format_types
+
+=item format_headers
+
+=item format_sep_chars
+
+=item format_fixedlength_formats
+
+=item params
+
+=item job
+
+FS::queue object, will be updated with progress
+
+=item file
+
+=item type
+
+csv, xls or fixedlength
+
+=item format
+
+=item empty_ok
+
+=back
+
+=cut
+
+sub batch_import {
+ my $param = shift;
+
+ warn "$me batch_import call with params: \n". Dumper($param)
+ if $DEBUG;
+
+ my $table = $param->{table};
+ my $formats = $param->{formats};
+
+ my $job = $param->{job};
+ my $file = $param->{file};
+ my $format = $param->{'format'};
+ my $params = $param->{params} || {};
+
+ die "unknown format $format" unless exists $formats->{ $format };
+
+ my $type = $param->{'format_types'}
+ ? $param->{'format_types'}{ $format }
+ : $param->{type} || 'csv';
+
+ unless ( $type ) {
+ if ( $file =~ /\.(\w+)$/i ) {
+ $type = lc($1);
+ } else {
+ #or error out???
+ warn "can't parse file type from filename $file; defaulting to CSV";
+ $type = 'csv';
+ }
+ $type = 'csv'
+ if $param->{'default_csv'} && $type ne 'xls';
+ }
+
+ my $header = $param->{'format_headers'}
+ ? $param->{'format_headers'}{ $param->{'format'} }
+ : 0;
+
+ my $sep_char = $param->{'format_sep_chars'}
+ ? $param->{'format_sep_chars'}{ $param->{'format'} }
+ : ',';
+
+ my $fixedlength_format =
+ $param->{'format_fixedlength_formats'}
+ ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
+ : '';
+
+ my @fields = @{ $formats->{ $format } };
+
+ my $row = 0;
+ my $count;
+ my $parser;
+ my @buffer = ();
+ if ( $type eq 'csv' || $type eq 'fixedlength' ) {
+
+ if ( $type eq 'csv' ) {
+
+ my %attr = ();
+ $attr{sep_char} = $sep_char if $sep_char;
+ $parser = new Text::CSV_XS \%attr;
+
+ } elsif ( $type eq 'fixedlength' ) {
+
+ eval "use Parse::FixedLength;";
+ die $@ if $@;
+ $parser = new Parse::FixedLength $fixedlength_format;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ @buffer = split(/\r?\n/, slurp($file) );
+ splice(@buffer, 0, ($header || 0) );
+ $count = scalar(@buffer);
+
+ } elsif ( $type eq 'xls' ) {
+
+ eval "use Spreadsheet::ParseExcel;";
+ die $@ if $@;
+
+ eval "use DateTime::Format::Excel;";
+ #for now, just let the error be thrown if it is used, since only CDR
+ # formats bill_west and troop use it, not other excel-parsing things
+ #die $@ if $@;
+
+ my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
+
+ $parser = $excel->{Worksheet}[0]; #first sheet
+
+ $count = $parser->{MaxRow} || $parser->{MinRow};
+ $count++;
+
+ $row = $header || 0;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ #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;
+
+ if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) {
+ my $batch_col = $param->{'batch_keycol'};
+
+ my $batch_class = 'FS::'. $param->{'batch_table'};
+ my $batch = $batch_class->new({
+ $param->{'batch_namecol'} => $param->{'batch_namevalue'}
+ });
+ my $error = $batch->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert batch record: $error";
+ }
+ #primary key via dbdef? (so the column names don't have to match)
+ my $batch_value = $batch->get( $param->{'batch_keycol'} );
+
+ $params->{ $batch_col } = $batch_value;
+ }
+
+ my $line;
+ my $imported = 0;
+ my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
+ while (1) {
+
+ my @columns = ();
+ if ( $type eq 'csv' ) {
+
+ last unless scalar(@buffer);
+ $line = shift(@buffer);
+
+ next if $line =~ /^\s*$/; #skip empty lines
+
+ $parser->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $parser->error_input();
+ };
+ @columns = $parser->fields();
+
+ } elsif ( $type eq 'fixedlength' ) {
+
+ @columns = $parser->parse($line);
+
+ } elsif ( $type eq 'xls' ) {
+
+ last if $row > ($parser->{MaxRow} || $parser->{MinRow})
+ || ! $parser->{Cells}[$row];
+
+ my @row = @{ $parser->{Cells}[$row] };
+ @columns = map $_->{Val}, @row;
+
+ #my $z = 'A';
+ #warn $z++. ": $_\n" for @columns;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ my @later = ();
+ my %hash = %$params;
+
+ foreach my $field ( @fields ) {
+
+ my $value = shift @columns;
+
+ if ( ref($field) eq 'CODE' ) {
+ #&{$field}(\%hash, $value);
+ push @later, $field, $value;
+ } else {
+ #??? $hash{$field} = $value if length($value);
+ $hash{$field} = $value if defined($value) && length($value);
+ }
+
+ }
+
+ my $class = "FS::$table";
+
+ my $record = $class->new( \%hash );
+
+ my $param = {};
+ while ( scalar(@later) ) {
+ my $sub = shift @later;
+ my $data = shift @later;
+ &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf);
+ last if exists( $param->{skiprow} );
+ }
+ next if exists( $param->{skiprow} );
+
+ my $error = $record->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert record". ( $line ? " for $line" : '' ). ": $error";
+ }
+
+ $row++;
+ $imported++;
+
+ if ( $job && time - $min_sec > $last ) { #progress bar
+ $job->update_statustext( int(100 * $imported / $count) );
+ $last = time;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
+
+ return "Empty file!" unless $imported || $param->{empty_ok};
+
+ ''; #no error
+
+}
+
+sub _h_statement {
+ my( $self, $action, $time ) = @_;
+
+ $time ||= time;
+
+ my %nohistory = map { $_=>1 } $self->nohistory_fields;
+
+ my @fields =
+ grep { defined($self->get($_)) && $self->get($_) ne "" && ! $nohistory{$_} }
+ real_fields($self->table);
+ ;
+
+ # If we're encrypting then don't store the payinfo in the history
+ if ( $conf && $conf->exists('encryption') ) {
+ @fields = grep { $_ ne 'payinfo' } @fields;
+ }
+
+ 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) =~ /^\s*(\d+\.\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(\d+\.\d+e\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(\d+e\d+)\s*$/)
+ or return "Illegal or empty (float) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+=item ut_floatn COLUMN
+
+Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10. May be
+null. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+#false laziness w/ut_ipn
+sub ut_floatn {
+ my( $self, $field ) = @_;
+ if ( $self->getfield($field) =~ /^()$/ ) {
+ $self->setfield($field,'');
+ '';
+ } else {
+ $self->ut_float($field);
+ }
+}
+
+=item ut_sfloat COLUMN
+
+Check/untaint signed 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_sfloat {
+ my($self,$field)=@_ ;
+ ($self->getfield($field) =~ /^\s*(-?\d+\.\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(-?\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(-?\d+\.\d+[eE]-?\d+)\s*$/ ||
+ $self->getfield($field) =~ /^\s*(-?\d+[eE]-?\d+)\s*$/)
+ or return "Illegal or empty (float) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+=item ut_sfloatn COLUMN
+
+Check/untaint signed floating point numeric data: 1.1, 1, 1.1e10, 1e10. May be
+null. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_sfloatn {
+ my( $self, $field ) = @_;
+ if ( $self->getfield($field) =~ /^()$/ ) {
+ $self->setfield($field,'');
+ '';
+ } else {
+ $self->ut_sfloat($field);
+ }
+}
+
+=item ut_snumber COLUMN
+
+Check/untaint signed numeric data (whole numbers). If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub ut_snumber {
+ my($self, $field) = @_;
+ $self->getfield($field) =~ /^\s*(-?)\s*(\d+)\s*$/
+ or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
+ $self->setfield($field, "$1$2");
+ '';
+}
+
+=item ut_snumbern COLUMN
+
+Check/untaint signed numeric data (whole numbers). If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub ut_snumbern {
+ my($self, $field) = @_;
+ $self->getfield($field) =~ /^\s*(-?)\s*(\d*)\s*$/
+ or return "Illegal (numeric) $field: ". $self->getfield($field);
+ if ($1) {
+ return "Illegal (numeric) $field: ". $self->getfield($field)
+ unless $2;
+ }
+ $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) =~ /^\s*(\d+)\s*$/
+ 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) =~ /^\s*(\d*)\s*$/
+ 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) =~ /^\s*(\-)?\s*(\d*)(\.\d{2})?\s*$/
+ or return "Illegal (money) $field: ". $self->getfield($field);
+ #$self->setfield($field, "$1$2$3" || 0);
+ $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
+ '';
+}
+
+=item ut_moneyn COLUMN
+
+Check/untaint monetary numbers. May be negative. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_moneyn {
+ my($self,$field)=@_;
+ if ($self->getfield($field) eq '') {
+ $self->setfield($field, '');
+ return '';
+ }
+ $self->ut_money($field);
+}
+
+=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_alphan 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_alphasn COLUMN
+
+Check/untaint alphanumeric strings, spaces allowed. May be null. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alphasn {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^([\w ]*)$/
+ or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+
+=item ut_alpha_lower COLUMN
+
+Check/untaint lowercase alphanumeric strings (no spaces). May not be null. If
+there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alpha_lower {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /[[:upper:]]/
+ and return "Uppercase characters are not permitted in $field";
+ $self->ut_alpha($field);
+}
+
+=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 = $conf->config('cust_main-default_areacode').$phonen
+ if length($phonen)==7 && $conf->config('cust_main-default_areacode');
+ $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_hex COLUMN
+
+Check/untaint hexadecimal values.
+
+=cut
+
+sub ut_hex {
+ my($self, $field) = @_;
+ $self->getfield($field) =~ /^([\da-fA-F]+)$/
+ or return "Illegal (hex) $field: ". $self->getfield($field);
+ $self->setfield($field, uc($1));
+ '';
+}
+
+=item ut_hexn COLUMN
+
+Check/untaint hexadecimal values. May be null.
+
+=cut
+
+sub ut_hexn {
+ my($self, $field) = @_;
+ $self->getfield($field) =~ /^([\da-fA-F]*)$/
+ or return "Illegal (hex) $field: ". $self->getfield($field);
+ $self->setfield($field, uc($1));
+ '';
+}
+=item ut_ip COLUMN
+
+Check/untaint ip addresses. IPv4 only for now, though ::1 is auto-translated
+to 127.0.0.1.
+
+=cut
+
+sub ut_ip {
+ my( $self, $field ) = @_;
+ $self->setfield($field, '127.0.0.1') if $self->getfield($field) eq '::1';
+ $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, though ::1 is auto-translated
+to 127.0.0.1. May be null.
+
+=cut
+
+sub ut_ipn {
+ my( $self, $field ) = @_;
+ if ( $self->getfield($field) =~ /^()$/ ) {
+ $self->setfield($field,'');
+ '';
+ } else {
+ $self->ut_ip($field);
+ }
+}
+
+=item ut_coord COLUMN [ LOWER [ UPPER ] ]
+
+Check/untaint coordinates.
+Accepts the following forms:
+DDD.DDDDD
+-DDD.DDDDD
+DDD MM.MMM
+-DDD MM.MMM
+DDD MM SS
+-DDD MM SS
+DDD MM MMM
+-DDD MM MMM
+
+The "DDD MM SS" and "DDD MM MMM" are potentially ambiguous.
+The latter form (that is, the MMM are thousands of minutes) is
+assumed if the "MMM" is exactly three digits or two digits > 59.
+
+To be safe, just use the DDD.DDDDD form.
+
+If LOWER or UPPER are specified, then the coordinate is checked
+for lower and upper bounds, respectively.
+
+=cut
+
+sub ut_coord {
+
+ my ($self, $field) = (shift, shift);
+
+ my $lower = shift if scalar(@_);
+ my $upper = shift if scalar(@_);
+ my $coord = $self->getfield($field);
+ my $neg = $coord =~ s/^(-)//;
+
+ my ($d, $m, $s) = (0, 0, 0);
+
+ if (
+ (($d) = ($coord =~ /^(\s*\d{1,3}(?:\.\d+)?)\s*$/)) ||
+ (($d, $m) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2}(?:\.\d+))\s*$/)) ||
+ (($d, $m, $s) = ($coord =~ /^(\s*\d{1,3})\s+(\d{1,2})\s+(\d{1,3})\s*$/))
+ ) {
+ $s = (((($s =~ /^\d{3}$/) or $s > 59) ? ($s / 1000) : ($s / 60)) / 60);
+ $m = $m / 60;
+ if ($m > 59) {
+ return "Invalid (coordinate with minutes > 59) $field: "
+ . $self->getfield($field);
+ }
+
+ $coord = ($neg ? -1 : 1) * sprintf('%.8f', $d + $m + $s);
+
+ if (defined($lower) and ($coord < $lower)) {
+ return "Invalid (coordinate < $lower) $field: "
+ . $self->getfield($field);;
+ }
+
+ if (defined($upper) and ($coord > $upper)) {
+ return "Invalid (coordinate > $upper) $field: "
+ . $self->getfield($field);;
+ }
+
+ $self->setfield($field, $coord);
+ return '';
+ }
+
+ return "Invalid (coordinate) $field: " . $self->getfield($field);
+
+}
+
+=item ut_coordn COLUMN [ LOWER [ UPPER ] ]
+
+Same as ut_coord, except optionally null.
+
+=cut
+
+sub ut_coordn {
+
+ my ($self, $field) = (shift, shift);
+
+ if ($self->getfield($field) =~ /^$/) {
+ return '';
+ } else {
+ return $self->ut_coord($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
+
+my @zip_reqd_countries = qw( AU CA US ); #CA, US implicit...
+
+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);
+
+ } elsif ( $country eq 'CA' ) {
+
+ $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i
+ or return gettext('illegal_zip'). " $field for country $country: ".
+ $self->getfield($field);
+ $self->setfield($field, "$1 $2");
+
+ } else {
+
+ if ( $self->getfield($field) =~ /^\s*$/
+ && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries )
+ )
+ {
+ $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($field, $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 ) = @_;
+ return '' if $no_check_foreign;
+ qsearchs($table, { $foreign => $self->getfield($field) })
+ or return "Can't find ". $self->table. ".$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 ut_agentnum_acl COLUMN [ NULL_RIGHT | NULL_RIGHT_LISTREF ]
+
+Checks this column as an agentnum, taking into account the current users's
+ACLs. NULL_RIGHT or NULL_RIGHT_LISTREF, if specified, indicates the access
+right or rights allowing no agentnum.
+
+=cut
+
+sub ut_agentnum_acl {
+ my( $self, $field ) = (shift, shift);
+ my $null_acl = scalar(@_) ? shift : [];
+ $null_acl = [ $null_acl ] unless ref($null_acl);
+
+ my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum');
+ return "Illegal agentnum: $error" if $error;
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+
+ if ( $self->$field() ) {
+
+ return "Access denied"
+ unless $curuser->agentnum($self->$field());
+
+ } else {
+
+ return "Access denied"
+ unless grep $curuser->access_right($_), @$null_acl;
+
+ }
+
+ '';
+
+}
+
+=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 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 "Error executing virtual fields query: $query: ". $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());
+}
+
+=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 } );
+ }
+ ''
+}
+
+=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 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 encrypt($value)
+
+Encrypts the credit card using a combination of PK to encrypt and uuencode to armour.
+
+Returns the encrypted string.
+
+You should generally not have to worry about calling this, as the system handles this for you.
+
+=cut
+
+sub encrypt {
+ my ($self, $value) = @_;
+ my $encrypted;
+
+ if ($conf->exists('encryption')) {
+ if ($self->is_encrypted($value)) {
+ # Return the original value if it isn't plaintext.
+ $encrypted = $value;
+ } else {
+ $self->loadRSA;
+ if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
+ # RSA doesn't like the empty string so let's pack it up
+ # The database doesn't like the RSA data so uuencode it
+ my $length = length($value)+1;
+ $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
+ } else {
+ die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption");
+ }
+ }
+ }
+ return $encrypted;
+}
+
+=item is_encrypted($value)
+
+Checks to see if the string is encrypted and returns true or false (1/0) to indicate it's status.
+
+=cut
+
+
+sub is_encrypted {
+ my ($self, $value) = @_;
+ # Possible Bug - Some work may be required here....
+
+ if ($value =~ /^M/ && length($value) > 80) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+=item decrypt($value)
+
+Uses the private key to decrypt the string. Returns the decryoted string or undef on failure.
+
+You should generally not have to worry about calling this, as the system handles this for you.
+
+=cut
+
+sub decrypt {
+ my ($self,$value) = @_;
+ my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
+ if ($conf->exists('encryption') && $self->is_encrypted($value)) {
+ $self->loadRSA;
+ if (ref($rsa_decrypt) =~ /::RSA/) {
+ my $encrypted = unpack ("u*", $value);
+ $decrypted = unpack("Z*", eval{$rsa_decrypt->decrypt($encrypted)});
+ if ($@) {warn "Decryption Failed"};
+ }
+ }
+ return $decrypted;
+}
+
+sub loadRSA {
+ my $self = shift;
+ #Initialize the Module
+ $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
+
+ if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
+ $rsa_module = $conf->config('encryptionmodule');
+ }
+
+ if (!$rsa_loaded) {
+ eval ("require $rsa_module"); # No need to import the namespace
+ $rsa_loaded++;
+ }
+ # Initialize Encryption
+ if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
+ my $public_key = join("\n",$conf->config('encryptionpublickey'));
+ $rsa_encrypt = $rsa_module->new_public_key($public_key);
+ }
+
+ # Intitalize Decryption
+ if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
+ my $private_key = join("\n",$conf->config('encryptionprivatekey'));
+ $rsa_decrypt = $rsa_module->new_private_key($private_key);
+ }
+}
+
+=item h_search ACTION
+
+Given an ACTION, either "insert", or "delete", returns the appropriate history
+record corresponding to this record, if any.
+
+=cut
+
+sub h_search {
+ my( $self, $action ) = @_;
+
+ my $table = $self->table;
+ $table =~ s/^h_//;
+
+ my $primary_key = dbdef->table($table)->primary_key;
+
+ qsearchs({
+ 'table' => "h_$table",
+ 'hashref' => { $primary_key => $self->$primary_key(),
+ 'history_action' => $action,
+ },
+ });
+
+}
+
+=item h_date ACTION
+
+Given an ACTION, either "insert", or "delete", returns the timestamp of the
+appropriate history record corresponding to this record, if any.
+
+=cut
+
+sub h_date {
+ my($self, $action) = @_;
+ my $h = $self->h_search($action);
+ $h ? $h->history_date : '';
+}
+
+=item scalar_sql SQL
+
+A class method with a propensity for becoming an instance method. This
+method executes the sql statement represented by SQL and returns a scalar
+representing the result. Don't ask for rows -- you get the first column
+of the first row. Don't give me bogus SQL or I'll die on you.
+
+Returns an empty string in the event of no rows.
+
+=cut
+
+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] || '';
+}
+
+=back
+
+=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 _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;
+ my $nullable = $column_obj->null;
+
+ warn " $table.$column: $value ($column_type".
+ ( $nullable ? ' NULL' : ' NOT NULL' ).
+ ")\n" if $DEBUG > 2;
+
+ if ( $value eq '' && $nullable ) {
+ 'NULL';
+ } elsif ( $value eq '' && $column_type =~ /^(int|numeric)/ ) {
+ 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;
+ } elsif (( $column_type =~ /^bytea$/i || $column_type =~ /(blob|varbinary)/i )
+ && driver_name eq 'Pg'
+ )
+ {
+ no strict 'subs';
+# dbh->quote($value, { pg_type => PG_BYTEA() }); # doesn't work right
+ # Pg binary string quoting: convert each character to 3-digit octal prefixed with \\,
+ # single-quote the whole mess, and put an "E" in front.
+ return ("E'" . join('', map { sprintf('\\\\%03o', ord($_)) } split(//, $value) ) . "'");
+ } else {
+ dbh->quote($value);
+ }
+}
+
+=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; };
+# }
+
+=item str2time_sql [ DRIVER_NAME ]
+
+Returns a function to convert to unix time based on database type, such as
+"EXTRACT( EPOCH FROM" for Pg or "UNIX_TIMESTAMP(" for mysql. See
+the str2time_sql_closing method to return a closing string rather than just
+using a closing parenthesis as previously suggested.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub str2time_sql {
+ my $driver = shift || driver_name;
+
+ return 'UNIX_TIMESTAMP(' if $driver =~ /^mysql/i;
+ return 'EXTRACT( EPOCH FROM ' if $driver =~ /^Pg/i;
+
+ warn "warning: unknown database type $driver; guessing how to convert ".
+ "dates to UNIX timestamps";
+ return 'EXTRACT(EPOCH FROM ';
+
+}
+
+=item str2time_sql_closing [ DRIVER_NAME ]
+
+Returns the closing suffix of a function to convert to unix time based on
+database type, such as ")::integer" for Pg or ")" for mysql.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub str2time_sql_closing {
+ my $driver = shift || driver_name;
+
+ return ' )::INTEGER ' if $driver =~ /^Pg/i;
+ return ' ) ';
+}
+
+=item regexp_sql [ DRIVER_NAME ]
+
+Returns the operator to do a regular expression comparison based on database
+type, such as '~' for Pg or 'REGEXP' for mysql.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub regexp_sql {
+ my $driver = shift || driver_name;
+
+ return '~' if $driver =~ /^Pg/i;
+ return 'REGEXP' if $driver =~ /^mysql/i;
+
+ die "don't know how to use regular expressions in ". driver_name." databases";
+
+}
+
+=item not_regexp_sql [ DRIVER_NAME ]
+
+Returns the operator to do a regular expression negation based on database
+type, such as '!~' for Pg or 'NOT REGEXP' for mysql.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub not_regexp_sql {
+ my $driver = shift || driver_name;
+
+ return '!~' if $driver =~ /^Pg/i;
+ return 'NOT REGEXP' if $driver =~ /^mysql/i;
+
+ die "don't know how to use regular expressions in ". driver_name." databases";
+
+}
+
+=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.
+
+http://poop.sf.net/
+
+=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/FCC_477.pm b/FS/FS/Report/FCC_477.pm
new file mode 100644
index 0000000..518b9f0
--- /dev/null
+++ b/FS/FS/Report/FCC_477.pm
@@ -0,0 +1,90 @@
+package FS::Report::FCC_477;
+
+use strict;
+use vars qw( @ISA @upload @download @technology @part2aoption @part2boption );
+use FS::Report;
+
+@ISA = qw( FS::Report );
+
+=head1 NAME
+
+FS::Report::FCC_477 - Routines for FCC Form 477 reports
+
+=head1 SYNOPSIS
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+=cut
+
+@upload = qw(
+ <200kpbs
+ 200-768kpbs
+ 768kbps-1.5mbps
+ 1.5-3mpbs
+ 3-6mbps
+ 6-10mbps
+ 10-25mbps
+ 25-100mbps
+ >100bmps
+);
+
+@download = qw(
+ 200-768kpbs
+ 768kbps-1.5mbps
+ 1.5-3mpbs
+ 3-6mbps
+ 6-10mbps
+ 10-25mbps
+ 25-100mbps
+ >100bmps
+);
+
+@technology = (
+ 'Asymetric xDSL',
+ 'Symetric xDSL',
+ 'Other Wireline',
+ 'Cable Modem',
+ 'Optical Carrier',
+ 'Satellite',
+ 'Terrestrial Fixed Wireless',
+ 'Terrestrial Mobile Wireless',
+ 'Electric Power Line',
+ 'Other Technology',
+);
+
+@part2aoption = (
+ 'LD carrier',
+ 'owned loops',
+ 'unswitched UNE loops',
+ 'UNE-P',
+ 'UNE-P replacement',
+ 'FTTP',
+ 'coax',
+ 'wireless',
+);
+
+@part2boption = (
+ 'nomadic',
+ 'copper',
+ 'FTTP',
+ 'coax',
+ 'wireless',
+ 'other broadband',
+);
+
+sub parse_technology_option {
+ my $cgi = shift;
+ my @result = ();
+ my $i = 0;
+ for (my $i = 0; $i < scalar(@technology); $i++) {
+ my $value = $cgi->param("part1_technology_option_$i"); #lame
+ push @result, $value =~ /^\d+$/ ? $value : 0;
+ }
+ return (@result);
+}
+
+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..376ee5c
--- /dev/null
+++ b/FS/FS/Report/Table/Monthly.pm
@@ -0,0 +1,590 @@
+package FS::Report::Table::Monthly;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Time::Local;
+use FS::UID qw( dbh );
+use FS::Report::Table;
+use FS::CurrentUser;
+
+@ISA = qw( FS::Report::Table );
+$DEBUG = 0; # turning this on will trace all SQL statements, VERY noisy
+
+=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,
+ #opt
+ 'agentnum' => 54
+ 'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ...
+ 'remove_empty' => 1, #collapse empty rows, default 0
+ 'item_labels' => [ ], #useful with remove_empty
+ );
+
+ my $data = $report->data;
+
+=head1 METHODS
+
+=over 4
+
+=item data
+
+Returns a hashref of data (!! describe)
+
+=cut
+
+sub data {
+ my $self = shift;
+
+ #use Data::Dumper;
+ #warn Dumper($self);
+
+ my $smonth = $self->{'start_month'};
+ my $syear = $self->{'start_year'};
+ my $emonth = $self->{'end_month'};
+ my $eyear = $self->{'end_year'};
+ my $agentnum = $self->{'agentnum'};
+
+ 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;
+
+ my $col = 0;
+ my @row = ();
+ foreach my $item ( @{$self->{'items'}} ) {
+ my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: ();
+ my $value = $self->$item($speriod, $eperiod, $agentnum, @param);
+ #push @{$data{$item}}, $value;
+ push @{$data{data}->[$col++]}, $value;
+ }
+
+ }
+
+ #these need to get generalized, sheesh
+ $data{'items'} = $self->{'items'};
+ $data{'item_labels'} = $self->{'item_labels'} || $self->{'items'};
+ $data{'colors'} = $self->{'colors'};
+ $data{'links'} = $self->{'links'} || [];
+
+ #use Data::Dumper;
+ #warn Dumper(\%data);
+
+ if ( $self->{'remove_empty'} ) {
+
+ #warn "removing empty rows\n";
+
+ my $col = 0;
+ #these need to get generalized, sheesh
+ my @newitems = ();
+ my @newlabels = ();
+ my @newdata = ();
+ my @newcolors = ();
+ my @newlinks = ();
+ foreach my $item ( @{$self->{'items'}} ) {
+
+ if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) {
+ push @newitems, $data{'items'}->[$col];
+ push @newlabels, $data{'item_labels'}->[$col];
+ push @newdata, $data{'data'}->[$col];
+ push @newcolors, $data{'colors'}->[$col];
+ push @newlinks, $data{'links'}->[$col];
+ }
+
+ $col++;
+ }
+
+ $data{'items'} = \@newitems;
+ $data{'item_labels'} = \@newlabels;
+ $data{'data'} = \@newdata;
+ $data{'colors'} = \@newcolors;
+ $data{'links'} = \@newlinks;
+
+ }
+
+ #use Data::Dumper;
+ #warn Dumper(\%data);
+
+ \%data;
+
+}
+
+sub invoiced { #invoiced
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+ $self->scalar_sql("
+ SELECT SUM(charged)
+ FROM cust_bill
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ );
+
+}
+
+sub netsales { #net sales
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+ $self->invoiced($speriod,$eperiod,$agentnum)
+ - $self->netcredits($speriod,$eperiod,$agentnum);
+}
+
+#deferred revenue
+
+sub cashflow {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+ $self->payments($speriod, $eperiod, $agentnum)
+ - $self->refunds( $speriod, $eperiod, $agentnum);
+}
+
+sub netcashflow {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+ $self->receipts($speriod, $eperiod, $agentnum)
+ - $self->netrefunds( $speriod, $eperiod, $agentnum);
+}
+
+sub payments {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(paid)
+ FROM cust_pay
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ );
+}
+
+sub credits {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(amount)
+ FROM cust_credit
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ );
+}
+
+sub refunds {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(refund)
+ FROM cust_refund
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ );
+}
+
+sub netcredits {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_credit_bill.amount)
+ FROM cust_credit_bill
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_bill._date'
+ )
+ );
+}
+
+sub receipts { #net payments
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_bill_pay.amount)
+ FROM cust_bill_pay
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_bill._date'
+ )
+ );
+}
+
+sub netrefunds {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_credit_refund.amount)
+ FROM cust_credit_refund
+ LEFT JOIN cust_credit USING ( crednum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_credit._date'
+ )
+ );
+}
+
+#these should be auto-generated or $AUTOLOADed or something
+sub invoiced_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->invoiced($speriod, $eperiod, $agentnum);
+}
+
+sub netsales_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netsales($speriod, $eperiod, $agentnum);
+}
+
+sub receipts_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->receipts($speriod, $eperiod, $agentnum);
+}
+
+sub payments_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->payments($speriod, $eperiod, $agentnum);
+}
+
+sub credits_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->credits($speriod, $eperiod, $agentnum);
+}
+
+sub netcredits_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netcredits($speriod, $eperiod, $agentnum);
+}
+
+sub cashflow_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->cashflow($speriod, $eperiod, $agentnum);
+}
+
+sub netcashflow_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->cashflow($speriod, $eperiod, $agentnum);
+}
+
+sub refunds_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->refunds($speriod, $eperiod, $agentnum);
+}
+
+sub netrefunds_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netrefunds($speriod, $eperiod, $agentnum);
+}
+
+
+#not being too bad with the false laziness
+use Time::Local qw(timelocal);
+sub _subtract_11mo {
+ my($self, $time) = @_;
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
+ $mon -= 11;
+ if ( $mon < 0 ) { $mon+=12; $year--; }
+ timelocal($sec,$min,$hour,$mday,$mon,$year);
+}
+
+sub cust_pkg_setup_cost {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = 'IS NULL';
+ }
+ else {
+ $comparison = "= $1";
+ }
+ $where = "AND part_pkg.classnum $comparison";
+ }
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql = " SELECT SUM(part_pkg.setup_cost) ";
+ $total_sql .= " FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ WHERE pkgnum != 0
+ $where
+ AND ".$self->in_time_period_and_agent(
+ $speriod, $eperiod, $agentnum, 'cust_pkg.setup');
+ return $self->scalar_sql($total_sql);
+}
+
+sub cust_pkg_recur_cost {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = 'IS NULL';
+ }
+ else {
+ $comparison = "= $1";
+ }
+ $where = " AND part_pkg.classnum $comparison";
+ }
+ $agentnum ||= $opt{'agentnum'};
+ # duplication of in_time_period_and_agent
+ # because we do it a little differently here
+ $where .= " AND cust_main.agentnum = $agentnum" if $agentnum;
+ $where .= " AND ".
+ $FS::CurrentUser::CurrentUser->agentnums_sql('table' => 'cust_main');
+
+ my $total_sql = " SELECT SUM(part_pkg.recur_cost) ";
+ $total_sql .= " FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ WHERE pkgnum != 0
+ $where
+ AND cust_pkg.setup < $eperiod
+ AND (cust_pkg.cancel > $speriod OR cust_pkg.cancel IS NULL)
+ ";
+ return $self->scalar_sql($total_sql);
+}
+
+sub cust_bill_pkg {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $opt{'use_override'} ) {
+ $where = "AND (
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ $where = "AND part_pkg.classnum $comparison";
+ }
+ }
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql =
+ " SELECT COALESCE( SUM(cust_bill_pkg.setup + cust_bill_pkg.recur), 0 ) ";
+
+ $total_sql .=
+ " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+ WHERE pkgnum != 0
+ $where
+ AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+
+ if ($opt{use_usage} && $opt{use_usage} eq 'recurring') {
+ my $total = $self->scalar_sql($total_sql);
+ my $usage = cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
+ return $total-$usage;
+ } elsif ($opt{use_usage} && $opt{use_usage} eq 'usage') {
+ return cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
+ } else {
+ return $self->scalar_sql($total_sql);
+ }
+}
+
+sub cust_bill_pkg_detail {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ my @where = ( "cust_bill_pkg.pkgnum != 0" );
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $opt{'use_override'} ) {
+ push @where, "(
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ push @where, "part_pkg.classnum $comparison";
+ }
+ }
+
+ if ( $opt{'usageclass'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ push @where, "cust_bill_pkg_detail.classnum $comparison";
+ }
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $where = join( ' AND ', @where );
+
+ my $total_sql = " SELECT SUM(amount) ";
+
+ $total_sql .=
+ " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg_detail
+ LEFT JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_bill ON cust_bill_pkg.invnum = cust_bill.invnum
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_pkg ON cust_bill_pkg.pkgnum = cust_pkg.pkgnum
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+ WHERE $where
+ AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+
+ $self->scalar_sql($total_sql);
+
+}
+
+sub cust_bill_pkg_discount {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ #my $where = '';
+ #my $comparison = '';
+ #if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ # if ( $1 == 0 ) {
+ # $comparison = "IS NULL";
+ # } else {
+ # $comparison = "= $1";
+ # }
+ #
+ # if ( $opt{'use_override'} ) {
+ # $where = "(
+ # part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ # override.classnum $comparison AND pkgpart_override IS NOT NULL
+ # )";
+ # } else {
+ # $where = "part_pkg.classnum $comparison";
+ # }
+ #}
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql =
+ " SELECT COALESCE( SUM( cust_bill_pkg_discount.amount ), 0 ) ";
+
+ #$total_sql .=
+ # " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ # if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg_discount
+ LEFT JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+ # LEFT JOIN cust_pkg_discount USING ( pkgdiscountnum )
+ # LEFT JOIN discount USING ( discountnum )
+ # LEFT JOIN cust_pkg USING ( pkgnum )
+ # LEFT JOIN part_pkg USING ( pkgpart )
+ # LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+
+ return $self->scalar_sql($total_sql);
+
+}
+
+sub setup_pkg { shift->pkg_field( @_, 'setup' ); }
+sub susp_pkg { shift->pkg_field( @_, 'susp' ); }
+sub cancel_pkg { shift->pkg_field( @_, 'cancel'); }
+
+sub pkg_field {
+ my( $self, $speriod, $eperiod, $agentnum, $field ) = @_;
+ $self->scalar_sql("
+ SELECT COUNT(*) FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ "cust_pkg.$field",
+ )
+ );
+
+}
+
+#this is going to be harder..
+#sub unsusp_pkg {
+# my( $self, $speriod, $eperiod, $agentnum ) = @_;
+# $self->scalar_sql("
+# SELECT COUNT(*) FROM h_cust_pkg
+# WHERE
+#
+#}
+
+sub in_time_period_and_agent {
+ my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4);
+ my $col = @_ ? shift() : '_date';
+
+ my $sql = "$col >= $speriod AND $col < $eperiod";
+
+ #agent selection
+ $sql .= " AND cust_main.agentnum = $agentnum"
+ if $agentnum;
+
+ #agent virtualization
+ $sql .= ' AND '.
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table'=>'cust_main' );
+
+ $sql;
+}
+
+sub scalar_sql {
+ my( $self, $sql ) = ( shift, shift );
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ warn "FS::Report::Table::Monthly\n$sql\n" if $DEBUG;
+ $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/Schema.pm b/FS/FS/Schema.pm
new file mode 100644
index 0000000..0b54282
--- /dev/null
+++ b/FS/FS/Schema.pm
@@ -0,0 +1,2878 @@
+package FS::Schema;
+
+use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
+use subs qw(reload_dbdef);
+use Exporter;
+use DBIx::DBSchema 0.33;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column 0.06;
+use DBIx::DBSchema::Index;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( dbdef dbdef_dist reload_dbdef );
+
+$DEBUG = 0;
+$me = '[FS::Schema]';
+
+=head1 NAME
+
+FS::Schema - Freeside database schema
+
+=head1 SYNOPSYS
+
+ use FS::Schema qw(dbdef dbdef_dist reload_dbdef);
+
+ $dbdef = reload_dbdef;
+ $dbdef = reload_dbdef "/non/standard/filename";
+ $dbdef = dbdef;
+ $dbdef_dist = dbdef_dist;
+
+=head1 DESCRIPTION
+
+This class represents the database schema.
+
+=head1 METHODS
+
+=over 4
+
+=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::Schema::setup_hack> is true. Returns a DBIx::DBSchema object.
+
+=cut
+
+sub reload_dbdef {
+ my $file = shift;
+
+ 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: $DBIx::DBSchema::errstr\n";
+ } else {
+ warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG;
+ }
+ $dbdef = $dbdef_cache{$file};
+}
+
+=item dbdef
+
+Returns the current database definition (represents the current database,
+assuming it is up-to-date). See L<DBIx::DBSchema>.
+
+=cut
+
+sub dbdef { $dbdef; }
+
+=item dbdef_dist [ DATASRC ]
+
+Returns the current canoical database definition as defined in this file.
+
+Optionally, pass a DBI data source to enable syntax specific to that database.
+Currently, this enables "TYPE=InnoDB" for MySQL databases.
+
+=cut
+
+sub dbdef_dist {
+ my $datasrc = @_ ? shift : '';
+
+ my $local_options = '';
+ if ( $datasrc =~ /^dbi:mysql/i ) {
+ $local_options = 'TYPE=InnoDB';
+ }
+
+ ###
+ # create a dbdef object from the old data structure
+ ###
+
+ my $tables_hashref = tables_hashref();
+
+ #turn it into objects
+ my $dbdef = new DBIx::DBSchema map {
+
+ my $tablename = $_;
+ my $indexnum = 1;
+
+ my @columns;
+ while (@{$tables_hashref->{$tablename}{'columns'}}) {
+ #my($name, $type, $null, $length, $default, $local) =
+ my @coldef =
+ splice @{$tables_hashref->{$tablename}{'columns'}}, 0, 6;
+ my %hash = map { $_ => shift @coldef }
+ qw( name type null length default local );
+
+ #can be removed once we depend on DBIx::DBSchema 0.39;
+ $hash{'type'} = 'LONGTEXT'
+ if $hash{'type'} =~ /^TEXT$/i && $datasrc =~ /^dbi:mysql/i;
+
+ unless ( defined $hash{'default'} ) {
+ warn "$tablename:\n".
+ join('', map "$_ => $hash{$_}\n", keys %hash) ;# $stop = <STDIN>;
+ }
+
+ push @columns, new DBIx::DBSchema::Column ( \%hash );
+ }
+
+ #false laziness w/sub indices in DBIx::DBSchema::DBD (well, sorta)
+ #and sub sql_create_table in DBIx::DBSchema::Table (slighty more?)
+ my $unique = $tables_hashref->{$tablename}{'unique'};
+ my @index = @{ $tables_hashref->{$tablename}{'index'} };
+
+ # kludge to avoid avoid "BLOB/TEXT column 'statustext' used in key
+ # specification without a key length".
+ # better solution: teach DBIx::DBSchema to specify a default length for
+ # MySQL indices on text columns, or just to support an index length at all
+ # so we can pass something in.
+ # best solution: eliminate need for this index in cust_main::retry_realtime
+ @index = grep { @{$_}[0] ne 'statustext' } @index
+ if $datasrc =~ /^dbi:mysql/i;
+
+ my @indices = ();
+ push @indices, map {
+ DBIx::DBSchema::Index->new({
+ 'name' => $tablename. $indexnum++,
+ 'unique' => 1,
+ 'columns' => $_,
+ });
+ }
+ @$unique;
+ push @indices, map {
+ DBIx::DBSchema::Index->new({
+ 'name' => $tablename. $indexnum++,
+ 'unique' => 0,
+ 'columns' => $_,
+ });
+ }
+ @index;
+
+ DBIx::DBSchema::Table->new({
+ 'name' => $tablename,
+ 'primary_key' => $tables_hashref->{$tablename}{'primary_key'},
+ 'columns' => \@columns,
+ 'indices' => \@indices,
+ 'local_options' => $local_options,
+ });
+
+ } keys %$tables_hashref;
+
+ if ( $DEBUG ) {
+ warn "[debug]$me initial dbdef_dist created ($dbdef) with tables:\n";
+ warn "[debug]$me $_\n" foreach $dbdef->tables;
+ }
+
+ #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 { ! /^clientapi_session/ }
+ grep { ! /^h_/ }
+ $dbdef->tables
+ ) {
+ my $tableobj = $dbdef->table($table)
+ or die "unknown table $table";
+
+ my %indices = $tableobj->indices;
+
+ my %h_indices = map {
+ ( "h_$_" =>
+ DBIx::DBSchema::Index->new({
+ 'name' => 'h_'. $indices{$_}->name,
+ 'unique' => 0,
+ 'columns' => [ @{$indices{$_}->columns} ],
+ })
+ );
+ }
+ keys %indices;
+
+ $h_indices{"h_${table}_srckey"} = DBIx::DBSchema::Index->new({
+ 'name' => "h_${table}_srckey",
+ 'unique' => 0,
+ 'columns' => [ 'history_action', #right?
+ $tableobj->primary_key,
+ ],
+ });
+
+ $h_indices{"h_${table}_srckey2"} = DBIx::DBSchema::Index->new({
+ 'name' => "h_${table}_srckey2",
+ 'unique' => 0,
+ 'columns' => [ 'history_date',
+ $tableobj->primary_key,
+ ],
+ });
+
+ my $h_tableobj = DBIx::DBSchema::Table->new( {
+ 'name' => "h_$table",
+ 'primary_key' => 'historynum',
+ 'indices' => \%h_indices,
+ 'local_options' => $local_options,
+ '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 )
+ } );
+
+ if ( $column->type =~ /^(\w*)SERIAL$/i ) {
+ $column->type(uc($1).'INT');
+ $column->null('NULL');
+ }
+ #$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);
+ }
+
+ if ( $datasrc =~ /^dbi:mysql/i ) {
+
+ my $dup_lock_table = DBIx::DBSchema::Table->new( {
+ 'name' => 'duplicate_lock',
+ 'primary_key' => 'duplocknum',
+ 'local_options' => $local_options,
+ 'columns' => [
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'duplocknum',
+ 'type' => 'serial',
+ 'null' => 'NOT NULL',
+ 'length' => '',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'lockname',
+ 'type' => 'varchar',
+ 'null' => 'NOT NULL',
+ 'length' => '80',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ ],
+ 'indices' => { 'duplicate_lock1' =>
+ DBIx::DBSchema::Index->new({
+ 'name' => 'duplicate_lock1',
+ 'unique' => 1,
+ 'columns' => [ 'lockname' ],
+ })
+ },
+ } );
+
+ $dbdef->addtable($dup_lock_table);
+
+ }
+
+ $dbdef;
+
+}
+
+sub tables_hashref {
+
+ 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' );
+ my @money_typen = ( 'decimal', 'NULL', '10,2' );
+ my @taxrate_type = ( 'decimal', '', '14,8' ); # requires pg 8 for
+ my @taxrate_typen = ( 'decimal', 'NULL', '14,8' ); # fs-upgrade to work
+
+ my $username_len = 32; #usernamemax config file
+
+ # name type nullability length default local
+
+ return {
+
+ 'agent' => {
+ 'columns' => [
+ 'agentnum', 'serial', '', '', '', '',
+ 'agent', 'varchar', '', $char_d, '', '',
+ 'typenum', 'int', '', '', '', '',
+ 'ticketing_queueid', 'int', 'NULL', '', '', '',
+ 'invoice_template', 'varchar', 'NULL', $char_d, '', '',
+ 'agent_custnum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ 'username', 'varchar', 'NULL', $char_d, '', '', #deprecated
+ '_password', 'varchar', 'NULL', $char_d, '', '', #deprecated
+ 'freq', 'int', 'NULL', '', '', '', #deprecated (never used)
+ 'prog', @perl_type, '', '', #deprecated (never used)
+ ],
+ 'primary_key' => 'agentnum',
+ #'unique' => [ [ 'agent_custnum' ] ], #one agent per customer?
+ #insert is giving it a value, tho..
+ #'index' => [ ['typenum'], ['disabled'] ],
+ 'unique' => [],
+ 'index' => [ ['typenum'], ['disabled'], ['agent_custnum'] ],
+ },
+
+ 'agent_type' => {
+ 'columns' => [
+ 'typenum', 'serial', '', '', '', '',
+ 'atype', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'typenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'type_pkgs' => {
+ 'columns' => [
+ 'typepkgnum', 'serial', '', '', '', '',
+ 'typenum', 'int', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'typepkgnum',
+ 'unique' => [ ['typenum', 'pkgpart'] ],
+ 'index' => [ ['typenum'] ],
+ },
+
+ 'cust_attachment' => {
+ 'columns' => [
+ 'attachnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'filename', 'varchar', '', 255, '', '',
+ 'mime_type', 'varchar', '', $char_d, '', '',
+ 'title', 'varchar', 'NULL', $char_d, '', '',
+ 'body', 'blob', 'NULL', '', '', '',
+ 'disabled', @date_type, '', '',
+ ],
+ 'primary_key' => 'attachnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['usernum'], ],
+ },
+
+ 'cust_bill' => {
+ 'columns' => [
+ #regular fields
+ 'invnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'charged', @money_type, '', '',
+ 'invoice_terms', 'varchar', 'NULL', $char_d, '', '',
+
+ #customer balance info at invoice generation time
+ 'previous_balance', @money_typen, '', '', #eventually not nullable
+ 'billing_balance', @money_typen, '', '', #eventually not nullable
+
+ #deprecated (unused by now, right?)
+ 'printed', 'int', '', '', '', '',
+
+ #specific use cases
+ 'closed', 'char', 'NULL', 1, '', '', #not yet used much
+ 'statementnum', 'int', 'NULL', '', '', '', #invoice aggregate statements
+ 'agent_invid', 'int', 'NULL', '', '', '', #(varchar?) importing legacy
+ ],
+ 'primary_key' => 'invnum',
+ 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
+ 'index' => [ ['custnum'], ['_date'], ['statementnum'], ['agent_invid'] ],
+ },
+
+ 'cust_statement' => {
+ 'columns' => [
+ 'statementnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ ],
+ 'primary_key' => 'statementnum',
+ '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'], ['eventpart'],
+ ['statustext'], ['_date'],
+ ],
+ },
+
+ 'part_bill_event' => {
+ 'columns' => [
+ 'eventpart', 'serial', '', '', '', '',
+ 'freq', 'varchar', 'NULL', $char_d, '', '',
+ 'payby', 'char', '', 4, '', '',
+ 'event', 'varchar', '', $char_d, '', '',
+ 'eventcode', @perl_type, '', '',
+ 'seconds', 'int', 'NULL', '', '', '',
+ 'weight', 'int', '', '', '', '',
+ 'plan', 'varchar', 'NULL', $char_d, '', '',
+ 'plandata', 'text', 'NULL', '', '', '',
+ 'reason', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'eventpart',
+ 'unique' => [],
+ 'index' => [ ['payby'], ['disabled'], ],
+ },
+
+ 'part_event' => {
+ 'columns' => [
+ 'eventpart', 'serial', '', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ 'event', 'varchar', '', $char_d, '', '',
+ 'eventtable', 'varchar', '', $char_d, '', '',
+ 'check_freq', 'varchar', 'NULL', $char_d, '', '',
+ 'weight', 'int', '', '', '', '',
+ 'action', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'eventpart',
+ 'unique' => [],
+ 'index' => [ ['agentnum'], ['eventtable'], ['check_freq'], ['disabled'], ],
+ },
+
+ 'part_event_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'eventpart', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventpart' ], [ 'optionname' ] ],
+ },
+
+ 'part_event_condition' => {
+ 'columns' => [
+ 'eventconditionnum', 'serial', '', '', '', '',
+ 'eventpart', 'int', '', '', '', '',
+ 'conditionname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'eventconditionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventpart' ], [ 'conditionname' ] ],
+ },
+
+ 'part_event_condition_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'eventconditionnum', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventconditionnum' ], [ 'optionname' ] ],
+ },
+
+ 'part_event_condition_option_option' => {
+ 'columns' => [
+ 'optionoptionnum', 'serial', '', '', '', '',
+ 'optionnum', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionoptionnum',
+ 'unique' => [],
+ 'index' => [ [ 'optionnum' ], [ 'optionname' ] ],
+ },
+
+ 'cust_event' => {
+ 'columns' => [
+ 'eventnum', 'serial', '', '', '', '',
+ 'eventpart', 'int', '', '', '', '',
+ 'tablenum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'status', 'varchar', '', $char_d, '', '',
+ 'statustext', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'eventnum',
+ #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
+ 'unique' => [],
+ 'index' => [ ['eventpart'], ['tablenum'], ['status'],
+ ['statustext'], ['_date'],
+ ],
+ },
+
+ 'cust_bill_pkg' => {
+ 'columns' => [
+ 'billpkgnum', 'serial', '', '', '', '',
+ 'invnum', 'int', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'pkgpart_override', 'int', 'NULL', '', '', '',
+ 'setup', @money_type, '', '',
+ 'recur', @money_type, '', '',
+ 'sdate', @date_type, '', '',
+ 'edate', @date_type, '', '',
+ 'itemdesc', 'varchar', 'NULL', $char_d, '', '',
+ 'itemcomment', 'varchar', 'NULL', $char_d, '', '',
+ 'section', 'varchar', 'NULL', $char_d, '', '',
+ 'quantity', 'int', 'NULL', '', '', '',
+ 'unitsetup', @money_typen, '', '',
+ 'unitrecur', @money_typen, '', '',
+ 'hidden', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'billpkgnum',
+ 'unique' => [],
+ 'index' => [ ['invnum'], [ 'pkgnum' ], [ 'itemdesc' ], ],
+ },
+
+ 'cust_bill_pkg_detail' => {
+ 'columns' => [
+ 'detailnum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', 'NULL', '', '', '', # should not be nullable
+ 'pkgnum', 'int', 'NULL', '', '', '', # deprecated
+ 'invnum', 'int', 'NULL', '', '', '', # deprecated
+ 'amount', 'decimal', 'NULL', '10,4', '', '',
+ 'format', 'char', 'NULL', 1, '', '',
+ 'classnum', 'int', 'NULL', '', '', '',
+ 'duration', 'int', 'NULL', '', 0, '',
+ 'phonenum', 'varchar', 'NULL', 15, '', '',
+ 'regionname', 'varchar', 'NULL', $char_d, '', '',
+ 'detail', 'varchar', '', 255, '', '',
+ ],
+ 'primary_key' => 'detailnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'classnum' ], [ 'pkgnum', 'invnum' ] ],
+ },
+
+ 'cust_bill_pkg_display' => {
+ 'columns' => [
+ 'billpkgdisplaynum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'section', 'varchar', 'NULL', $char_d, '', '',
+ #'unitsetup', @money_typen, '', '', #override the linked real one?
+ #'unitrecur', @money_typen, '', '', #this too?
+ 'post_total', 'char', 'NULL', 1, '', '',
+ 'type', 'char', 'NULL', 1, '', '',
+ 'summary', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'billpkgdisplaynum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ],
+ },
+
+ 'cust_bill_pkg_tax_location' => {
+ 'columns' => [
+ 'billpkgtaxlocationnum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'taxnum', 'int', '', '', '', '',
+ 'taxtype', 'varchar', '', $char_d, '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'locationnum', 'int', '', '', '', '', #redundant?
+ 'amount', @money_type, '', '',
+ ],
+ 'primary_key' => 'billpkgtaxlocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'pkgnum' ], [ 'locationnum' ] ],
+ },
+
+ 'cust_bill_pkg_tax_rate_location' => {
+ 'columns' => [
+ 'billpkgtaxratelocationnum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'taxnum', 'int', '', '', '', '',
+ 'taxtype', 'varchar', '', $char_d, '', '',
+ 'locationtaxid', 'varchar', 'NULL', $char_d, '', '',
+ 'taxratelocationnum', 'int', '', '', '', '',
+ 'amount', @money_type, '', '',
+ ],
+ 'primary_key' => 'billpkgtaxratelocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'taxratelocationnum' ] ],
+ },
+
+ 'cust_credit' => {
+ 'columns' => [
+ 'crednum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'amount', @money_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'reason', 'text', 'NULL', '', '', '',
+ 'reasonnum', 'int', 'NULL', '', '', '',
+ 'addlinfo', 'text', 'NULL', '', '', '',
+ 'closed', 'char', 'NULL', 1, '', '',
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ 'eventnum', 'int', 'NULL', '', '', '', #triggering event for commission
+ ],
+ 'primary_key' => 'crednum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], ['usernum'], ['eventnum'] ],
+ },
+
+ 'cust_credit_bill' => {
+ 'columns' => [
+ 'creditbillnum', 'serial', '', '', '', '',
+ 'crednum', 'int', '', '', '', '',
+ 'invnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'amount', @money_type, '', '',
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ ],
+ 'primary_key' => 'creditbillnum',
+ 'unique' => [],
+ 'index' => [ ['crednum'], ['invnum'] ],
+ },
+
+ 'cust_credit_bill_pkg' => {
+ 'columns' => [
+ 'creditbillpkgnum', 'serial', '', '', '', '',
+ 'creditbillnum', 'int', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'billpkgtaxlocationnum', 'int', 'NULL', '', '', '',
+ 'billpkgtaxratelocationnum', 'int', 'NULL', '', '', '',
+ 'amount', @money_type, '', '',
+ 'setuprecur', 'varchar', '', $char_d, '', '',
+ 'sdate', @date_type, '', '',
+ 'edate', @date_type, '', '',
+ ],
+ 'primary_key' => 'creditbillpkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'creditbillnum' ],
+ [ 'billpkgnum' ],
+ [ 'billpkgtaxlocationnum' ],
+ [ 'billpkgtaxratelocationnum' ],
+ ],
+ },
+
+ 'cust_main' => {
+ 'columns' => [
+ 'custnum', 'serial', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ 'agent_custid', 'varchar', 'NULL', $char_d, '', '',
+ 'classnum', 'int', 'NULL', '', '', '',
+ 'custbatch', 'varchar', 'NULL', $char_d, '', '',
+# 'titlenum', 'int', 'NULL', '', '', '',
+ 'last', 'varchar', '', $char_d, '', '',
+# 'middle', 'varchar', 'NULL', $char_d, '', '',
+ 'first', 'varchar', '', $char_d, '', '',
+ 'ss', 'varchar', 'NULL', 11, '', '',
+ 'stateid', 'varchar', 'NULL', $char_d, '', '',
+ 'stateid_state', 'varchar', 'NULL', $char_d, '', '',
+ 'birthdate' ,@date_type, '', '',
+ 'signupdate',@date_type, '', '',
+ 'dundate', @date_type, '', '',
+ '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', 'NULL', 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', 512, '', '',
+ 'paycvv', 'varchar', 'NULL', 512, '', '',
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ #'paydate', @date_type, '', '',
+ 'paydate', 'varchar', 'NULL', 10, '', '',
+ 'paystart_month', 'int', 'NULL', '', '', '',
+ 'paystart_year', 'int', 'NULL', '', '', '',
+ 'payissue', 'varchar', 'NULL', 2, '', '',
+ 'payname', 'varchar', 'NULL', $char_d, '', '',
+ 'paystate', 'varchar', 'NULL', $char_d, '', '',
+ 'paytype', 'varchar', 'NULL', $char_d, '', '',
+ 'payip', 'varchar', 'NULL', 15, '', '',
+ 'geocode', 'varchar', 'NULL', 20, '', '',
+ 'censustract', 'varchar', 'NULL', 20, '', '', # 7 to save space?
+ 'tax', 'char', 'NULL', 1, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'refnum', 'int', '', '', '', '',
+ 'referral_custnum', 'int', 'NULL', '', '', '',
+ 'comments', 'text', 'NULL', '', '', '',
+ 'spool_cdr','char', 'NULL', 1, '', '',
+ 'squelch_cdr','char', 'NULL', 1, '', '',
+ 'cdr_termination_percentage', 'decimal', 'NULL', '', '', '',
+ 'invoice_terms', 'varchar', 'NULL', $char_d, '', '',
+ 'archived', 'char', 'NULL', 1, '', '',
+ 'email_csv_cdr', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'custnum',
+ 'unique' => [ [ 'agentnum', 'agent_custid' ] ],
+ #'index' => [ ['last'], ['company'] ],
+ 'index' => [
+ [ 'agentnum' ], [ 'refnum' ], [ 'classnum' ], [ 'usernum' ],
+ [ 'custbatch' ],
+ [ 'referral_custnum' ],
+ [ 'payby' ], [ 'paydate' ],
+ [ 'archived' ],
+ #billing
+ [ 'last' ], [ 'company' ],
+ [ 'county' ], [ 'state' ], [ 'country' ],
+ [ 'zip' ],
+ [ 'daytime' ], [ 'night' ], [ 'fax' ],
+ #shipping
+ [ 'ship_last' ], [ 'ship_company' ],
+ [ 'ship_county' ], [ 'ship_state' ], [ 'ship_country' ],
+ [ 'ship_zip' ],
+ [ 'ship_daytime' ], [ 'ship_night' ], [ 'ship_fax' ],
+ ],
+ },
+
+ 'cust_recon' => { # what purpose does this serve?
+ 'columns' => [
+ 'reconid', 'serial', '', '', '', '',
+ 'recondate', @date_type, '', '',
+ 'custnum', 'int' , '', '', '', '',
+ 'agentnum', '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', 'NULL', 10, '', '',
+ 'pkg', 'varchar', 'NULL', $char_d, '', '',
+ 'adjourn', @date_type, '', '',
+ 'status', 'varchar', 'NULL', 10, '', '',
+ 'agent_custid', 'varchar', '', $char_d, '', '',
+ 'agent_pkg', 'varchar', 'NULL', $char_d, '', '',
+ 'agent_adjourn', @date_type, '', '',
+ 'comments', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'reconid',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ #eventually for cust_main too
+ 'contact' => {
+ 'columns' => [
+ 'contactnum', 'serial', '', '', '', '',
+ 'prospectnum', 'int', 'NULL', '', '', '',
+ 'custnum', 'int', 'NULL', '', '', '',
+ 'locationnum', 'int', 'NULL', '', '', '', #not yet
+# 'titlenum', 'int', 'NULL', '', '', '', #eg Mr. Mrs. Dr. Rev.
+ 'last', 'varchar', '', $char_d, '', '',
+# 'middle', 'varchar', 'NULL', $char_d, '', '',
+ 'first', 'varchar', '', $char_d, '', '',
+ 'title', 'varchar', '', $char_d, '', '', #eg Head Bottle Washer
+ 'comment', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'contactnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ], [ 'locationnum' ],
+ [ 'last' ], [ 'first' ],
+ ],
+ },
+
+ 'contact_phone' => {
+ 'columns' => [
+ 'contactphonenum', 'serial', '', '', '', '',
+ 'contactnum', 'int', '', '', '', '',
+ 'phonetypenum', 'int', '', '', '', '',
+ 'countrycode', 'varchar', '', 3, '', '',
+ 'phonenum', 'varchar', '', 14, '', '',
+ 'extension', 'varchar', '', 7, '', '',
+ #?#'comment', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'contactphonenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'phone_type' => {
+ 'columns' => [
+ 'phonetypenum', 'serial', '', '', '', '',
+ 'typename', 'varchar', '', $char_d, '', '',
+ 'weight', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'phonetypenum',
+ 'unique' => [ [ 'typename' ], ],
+ 'index' => [],
+ },
+
+ 'contact_email' => {
+ 'columns' => [
+ 'contactemailnum', 'serial', '', '', '', '',
+ 'contactnum', 'int', '', '', '', '',
+ 'emailaddress', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'contactemailnum',
+ 'unique' => [ [ 'emailaddress' ], ],
+ 'index' => [],
+ },
+
+ 'prospect_main' => {
+ 'columns' => [
+ 'prospectnum', 'serial', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ 'company', 'varchar', '', $char_d, '', '',
+ #'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'prospectnum',
+ 'unique' => [],
+ 'index' => [ [ 'company' ], [ 'agentnum' ], ],
+ },
+
+ #eventually use for billing & ship from cust_main too
+ #for now, just cust_pkg locations
+ 'cust_location' => { #'location' now that its prospects too, but...
+ 'columns' => [
+ 'locationnum', 'serial', '', '', '', '',
+ 'prospectnum', 'int', 'NULL', '', '', '',
+ 'custnum', 'int', 'NULL', '', '', '',
+ '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', 'NULL', 10, '', '',
+ 'country', 'char', '', 2, '', '',
+ 'geocode', 'varchar', 'NULL', 20, '', '',
+ ],
+ 'primary_key' => 'locationnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ],
+ [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
+ ],
+ },
+
+ 'cust_main_invoice' => {
+ 'columns' => [
+ 'destnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'dest', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'destnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ],
+ },
+
+ 'cust_main_note' => {
+ 'columns' => [
+ 'notenum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'comments', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'notenum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ '_date' ], [ 'usernum' ], ],
+ },
+
+ 'cust_category' => {
+ 'columns' => [
+ 'categorynum', 'serial', '', '', '', '',
+ 'categoryname', 'varchar', '', $char_d, '', '',
+ 'weight', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'categorynum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'cust_class' => {
+ 'columns' => [
+ 'classnum', 'serial', '', '', '', '',
+ 'classname', 'varchar', '', $char_d, '', '',
+ 'categorynum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'cust_main_exemption' => {
+ 'columns' => [
+ 'exemptionnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'taxname', 'varchar', '', $char_d, '', '',
+ #start/end dates? for reporting?
+ ],
+ 'primary_key' => 'exemptionnum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ] ],
+ },
+
+ 'cust_tax_adjustment' => {
+ 'columns' => [
+ 'adjustmentnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'taxname', 'varchar', '', $char_d, '', '',
+ 'amount', @money_type, '', '',
+ 'comment', 'varchar', 'NULL', $char_d, '', '',
+ 'billpkgnum', 'int', 'NULL', '', '', '',
+ #more? no cust_bill_pkg_tax_location?
+ ],
+ 'primary_key' => 'adjustmentnum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ 'billpkgnum' ] ],
+ },
+
+ 'cust_main_county' => { #county+state+country are checked off the
+ #cust_main_county for validation and to provide
+ # a tax rate.
+ 'columns' => [
+ 'taxnum', 'serial', '', '', '', '',
+ 'city', 'varchar', 'NULL', $char_d, '', '',
+ 'county', 'varchar', 'NULL', $char_d, '', '',
+ 'state', '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' => [ [ 'city' ], [ 'county' ], [ 'state' ], [ 'country' ],
+ [ 'taxclass' ],
+ ],
+ },
+
+ 'tax_rate' => {
+ 'columns' => [
+ 'taxnum', 'serial', '', '', '', '',
+ 'geocode', 'varchar', 'NULL', $char_d, '', '',#cch provides 10 char
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',#auto update source
+ 'location', 'varchar', 'NULL', $char_d, '', '',#provided by tax authority
+ 'taxclassnum', 'int', '', '', '', '',
+ 'effective_date', @date_type, '', '',
+ 'tax', @taxrate_type, '', '', # tax %
+ 'excessrate', @taxrate_typen, '', '', # second tax %
+ 'taxbase', @money_typen, '', '', # amount at first tax rate
+ 'taxmax', @money_typen, '', '', # maximum about at both rates
+ 'usetax', @taxrate_typen, '', '', # tax % when non-local
+ 'useexcessrate', @taxrate_typen, '', '', # second tax % when non-local
+ 'unittype', 'int', 'NULL', '', '', '', # for fee
+ 'fee', @taxrate_typen, '', '', # amount tax per unit
+ 'excessfee', @taxrate_typen, '', '', # second amount tax per unit
+ 'feebase', @taxrate_typen, '', '', # units taxed at first rate
+ 'feemax', @taxrate_typen, '', '', # maximum number of unit taxed
+ 'maxtype', 'int', 'NULL', '', '', '', # indicator of how thresholds accumulate
+ 'taxname', 'varchar', 'NULL', $char_d, '', '', # may appear on invoice
+ 'taxauth', 'int', 'NULL', '', '', '', # tax authority
+ 'basetype', 'int', 'NULL', '', '', '', # indicator of basis for tax
+ 'passtype', 'int', 'NULL', '', '', '', # indicator declaring how item should be shown
+ 'passflag', 'char', 'NULL', 1, '', '', # Y = required to list as line item, N = Prohibited
+ 'setuptax', 'char', 'NULL', 1, '', '', # Y = setup tax exempt
+ 'recurtax', 'char', 'NULL', 1, '', '', # Y = recur tax exempt
+ 'inoutcity', 'char', 'NULL', 1, '', '', # '', 'I', or 'O'
+ 'inoutlocal', 'char', 'NULL', 1, '', '', # '', 'I', or 'O'
+ 'manual', 'char', 'NULL', 1, '', '', # Y = manually edited
+ 'disabled', 'char', 'NULL', 1, '', '', # Y = tax disabled
+ ],
+ 'primary_key' => 'taxnum',
+ 'unique' => [],
+ 'index' => [ ['taxclassnum'], ['data_vendor', 'geocode'] ],
+ },
+
+ 'tax_rate_location' => {
+ 'columns' => [
+ 'taxratelocationnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',
+ 'geocode', 'varchar', '', 20, '', '',
+ 'city', 'varchar', 'NULL', $char_d, '', '',
+ 'county', 'varchar', 'NULL', $char_d, '', '',
+ 'state', 'char', 'NULL', 2, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'taxratelocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'data_vendor', 'geocode', 'disabled' ] ],
+ },
+
+ 'cust_tax_location' => {
+ 'columns' => [
+ 'custlocationnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '', # update source
+ 'city', 'varchar', 'NULL', $char_d, '', '',
+ 'postalcity', 'varchar', 'NULL', $char_d, '', '',
+ 'county', 'varchar', 'NULL', $char_d, '', '',
+ 'zip', 'char', '', 5, '', '',
+ 'state', 'char', '', 2, '', '',
+ 'plus4hi', 'char', 'NULL', 4, '', '',
+ 'plus4lo', 'char', 'NULL', 4, '', '',
+ 'default_location','char', 'NULL', 1, '', '', # Y = default for zip
+ 'cityflag', 'char', 'NULL', 1, '', '', # I(n)/O(out)/B(oth)/NULL
+ 'geocode', 'varchar', '', 20, '', '',
+ ],
+ 'primary_key' => 'custlocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'zip', 'plus4lo', 'plus4hi' ] ],
+ },
+
+ 'tax_class' => {
+ 'columns' => [
+ 'taxclassnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',
+ 'taxclass', 'varchar', '', $char_d, '', '',
+ 'description', 'varchar', '', 2*$char_d, '', '',
+ ],
+ 'primary_key' => 'taxclassnum',
+ 'unique' => [ [ 'data_vendor', 'taxclass' ] ],
+ 'index' => [],
+ },
+
+ 'cust_pay_pending' => {
+ 'columns' => [
+ 'paypendingnum','serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'paid', @money_type, '', '',
+ '_date', @date_type, '', '',
+ 'payby', 'char', '', 4, '', '', #CARD/BILL/COMP, should
+ # be index into payby
+ # table eventually
+ 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paydate', 'varchar', 'NULL', 10, '', '',
+ 'recurring_billing', 'varchar', 'NULL', $char_d, '', '',
+ #'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
+ 'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
+
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ 'status', 'varchar', '', $char_d, '', '',
+ 'session_id', 'varchar', 'NULL', $char_d, '', '', #only need 32
+ 'statustext', 'text', 'NULL', '', '', '',
+ 'gatewaynum', 'int', 'NULL', '', '', '',
+ #'cust_balance', @money_type, '', '',
+ 'paynum', 'int', 'NULL', '', '', '',
+ 'jobnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'paypendingnum',
+ 'unique' => [ [ 'payunique' ] ],
+ 'index' => [ [ 'custnum' ], [ 'status' ], ],
+ },
+
+ 'cust_pay' => {
+ 'columns' => [
+ 'paynum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'paid', @money_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
+ # index into payby table
+ # eventually
+ 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paydate', 'varchar', 'NULL', 10, '', '',
+ 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
+ 'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
+ 'closed', 'char', 'NULL', 1, '', '',
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ ],
+ 'primary_key' => 'paynum',
+ #i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it# 'unique' => [ [ 'payunique' ] ],
+ 'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ], [ 'usernum' ] ],
+ },
+
+ 'cust_pay_void' => {
+ 'columns' => [
+ 'paynum', 'int', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'paid', @money_type, '', '',
+ '_date', @date_type, '', '',
+ 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
+ # index into payby table
+ # eventually
+ 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
+ 'closed', 'char', 'NULL', 1, '', '',
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ 'void_date', @date_type, '', '',
+ 'reason', 'varchar', 'NULL', $char_d, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'paynum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ 'usernum' ], ],
+ },
+
+ 'cust_bill_pay' => {
+ 'columns' => [
+ 'billpaynum', 'serial', '', '', '', '',
+ 'invnum', 'int', '', '', '', '',
+ 'paynum', 'int', '', '', '', '',
+ 'amount', @money_type, '', '',
+ '_date', @date_type, '', '',
+ 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+ ],
+ 'primary_key' => 'billpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+ },
+
+ 'cust_bill_pay_batch' => {
+ 'columns' => [
+ 'billpaynum', 'serial', '', '', '', '',
+ 'invnum', 'int', '', '', '', '',
+ 'paybatchnum', 'int', '', '', '', '',
+ 'amount', @money_type, '', '',
+ '_date', @date_type, '', '',
+ ],
+ 'primary_key' => 'billpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ],
+ },
+
+ 'cust_bill_pay_pkg' => {
+ 'columns' => [
+ 'billpaypkgnum', 'serial', '', '', '', '',
+ 'billpaynum', 'int', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'billpkgtaxlocationnum', 'int', 'NULL', '', '', '',
+ 'billpkgtaxratelocationnum', 'int', 'NULL', '', '', '',
+ 'amount', @money_type, '', '',
+ 'setuprecur', 'varchar', '', $char_d, '', '',
+ 'sdate', @date_type, '', '',
+ 'edate', @date_type, '', '',
+ ],
+ 'primary_key' => 'billpaypkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
+ },
+
+ 'pay_batch' => { #batches of payments to an external processor
+ 'columns' => [
+ 'batchnum', 'serial', '', '', '', '',
+ 'payby', 'char', '', 4, '', '', # CARD/CHEK
+ 'status', 'char', 'NULL', 1, '', '',
+ 'download', @date_type, '', '',
+ 'upload', @date_type, '', '',
+ ],
+ 'primary_key' => 'batchnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cust_pay_batch' => { #what's this used for again? list of customers
+ #in current CARD batch? (necessarily CARD?)
+ 'columns' => [
+ 'paybatchnum', 'serial', '', '', '', '',
+ 'batchnum', 'int', '', '', '', '',
+ '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', 'NULL', 10, '', '',
+ 'country', 'char', '', 2, '', '',
+ # 'trancode', 'int', '', '', '', ''
+ 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be
+ 'payinfo', 'varchar', '', 512, '', '',
+ #'exp', @date_type, '', ''
+ 'exp', 'varchar', 'NULL', 11, '', '',
+ 'payname', 'varchar', 'NULL', $char_d, '', '',
+ 'amount', @money_type, '', '',
+ 'status', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'paybatchnum',
+ 'unique' => [],
+ 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ],
+ },
+
+ 'cust_pkg' => {
+ 'columns' => [
+ 'pkgnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ 'locationnum', 'int', 'NULL', '', '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'start_date', @date_type, '', '',
+ 'setup', @date_type, '', '',
+ 'bill', @date_type, '', '',
+ 'last_bill', @date_type, '', '',
+ 'susp', @date_type, '', '',
+ 'adjourn', @date_type, '', '',
+ 'cancel', @date_type, '', '',
+ 'expire', @date_type, '', '',
+ 'change_date', @date_type, '', '',
+ 'change_pkgnum', 'int', 'NULL', '', '', '',
+ 'change_pkgpart', 'int', 'NULL', '', '', '',
+ 'change_locationnum', 'int', 'NULL', '', '', '',
+ 'manual_flag', 'char', 'NULL', 1, '', '',
+ 'no_auto', 'char', 'NULL', 1, '', '',
+ 'quantity', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'pkgnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['pkgpart'], [ 'locationnum' ], [ 'usernum' ],
+ [ 'start_date' ], ['setup'], ['last_bill'], ['bill'],
+ ['susp'], ['adjourn'], ['expire'], ['cancel'],
+ ['change_date'],
+ ],
+ },
+
+ 'cust_pkg_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ],
+ },
+
+ 'cust_pkg_detail' => {
+ 'columns' => [
+ 'pkgdetailnum', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'detail', 'varchar', '', $char_d, '', '',
+ 'detailtype', 'char', '', 1, '', '', # "I"nvoice or "C"omment
+ 'weight', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'pkgdetailnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum', 'detailtype' ] ],
+ },
+
+ 'cust_pkg_reason' => {
+ 'columns' => [
+ 'num', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'reasonnum','int', '', '', '', '',
+ 'action', 'char', 'NULL', 1, '', '', #should not be nullable
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'date', @date_type, '', '',
+ ],
+ 'primary_key' => 'num',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'reasonnum' ], ['action'], [ 'usernum' ], ],
+ },
+
+ 'cust_pkg_discount' => {
+ 'columns' => [
+ 'pkgdiscountnum', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'discountnum', 'int', '', '', '', '',
+ 'months_used', 'decimal', 'NULL', '', '', '',
+ 'end_date', @date_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'discountnum' ], [ 'usernum' ], ],
+ },
+
+ 'cust_bill_pkg_discount' => {
+ 'columns' => [
+ 'billpkgdiscountnum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'pkgdiscountnum', 'int', '', '', '', '',
+ 'amount', @money_type, '', '',
+ 'months', 'decimal', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'billpkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'pkgdiscountnum' ] ],
+ },
+
+ 'discount' => {
+ 'columns' => [
+ 'discountnum', 'serial', '', '', '', '',
+ #'agentnum', 'int', 'NULL', '', '', '',
+ 'name', 'varchar', 'NULL', $char_d, '', '',
+ 'amount', @money_type, '', '',
+ 'percent', 'decimal', '', '', '', '',
+ 'months', 'decimal', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'discountnum',
+ 'unique' => [],
+ 'index' => [], # [ 'agentnum' ], ],
+ },
+
+ 'cust_refund' => {
+ 'columns' => [
+ 'refundnum', 'serial', '', '', '', '',
+ 'custnum', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'refund', @money_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'reason', 'varchar', '', $char_d, '', '',
+ 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should
+ # be index into payby
+ # table eventually
+ 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paybatch', 'varchar', 'NULL', $char_d, '', '',
+ 'closed', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'refundnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], [ 'usernum' ], ],
+ },
+
+ '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', '', '', '', '',
+ 'overlimit', @date_type, '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ],
+ },
+
+ 'cust_svc_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ], [ 'optionname' ] ],
+ },
+
+ 'part_pkg' => {
+ 'columns' => [
+ 'pkgpart', 'serial', '', '', '', '',
+ 'pkg', 'varchar', '', $char_d, '', '',
+ 'comment', 'varchar', '', $char_d, '', '',
+ 'promo_code', 'varchar', 'NULL', $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, '', '',
+ 'custom', 'char', 'NULL', 1, '', '',
+ 'taxclass', 'varchar', 'NULL', $char_d, '', '',
+ 'classnum', 'int', 'NULL', '', '', '',
+ 'addon_classnum','int', 'NULL', '', '', '',
+ 'taxproductnum', 'int', 'NULL', '', '', '',
+ 'setup_cost', @money_typen, '', '',
+ 'recur_cost', @money_typen, '', '',
+ 'pay_weight', 'real', 'NULL', '', '', '',
+ 'credit_weight', 'real', 'NULL', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ 'fcc_ds0s', 'int', 'NULL', '', '', '',
+ 'no_auto', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkgpart',
+ 'unique' => [],
+ 'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'classnum' ],
+ [ 'agentnum' ],
+ ],
+ },
+
+ 'part_pkg_link' => {
+ 'columns' => [
+ 'pkglinknum', 'serial', '', '', '', '',
+ 'src_pkgpart', 'int', '', '', '', '',
+ 'dst_pkgpart', 'int', '', '', '', '',
+ 'link_type', 'varchar', '', $char_d, '', '',
+ 'hidden', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkglinknum',
+ 'unique' => [ [ 'src_pkgpart', 'dst_pkgpart', 'link_type', 'hidden' ] ],
+ 'index' => [ [ 'src_pkgpart' ] ],
+ },
+ # XXX somewhat borked unique: we don't really want a hidden and unhidden
+ # it turns out we'd prefer to use svc, bill, and invisibill (or something)
+
+ 'part_pkg_taxclass' => {
+ 'columns' => [
+ 'taxclassnum', 'serial', '', '', '', '',
+ 'taxclass', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'taxclassnum',
+ 'unique' => [ [ 'taxclass' ] ],
+ 'index' => [ [ 'disabled' ] ],
+ },
+
+ 'part_pkg_taxproduct' => {
+ 'columns' => [
+ 'taxproductnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',
+ 'taxproduct', 'varchar', '', $char_d, '', '',
+ 'description', 'varchar', '', 3*$char_d, '', '',
+ ],
+ 'primary_key' => 'taxproductnum',
+ 'unique' => [ [ 'data_vendor', 'taxproduct' ] ],
+ 'index' => [],
+ },
+
+ 'part_pkg_taxrate' => {
+ 'columns' => [
+ 'pkgtaxratenum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '', # update source
+ 'geocode', 'varchar', 'NULL', $char_d, '', '', # cch provides 10
+ 'taxproductnum', 'int', '', '', '', '',
+ 'city', 'varchar', 'NULL', $char_d, '', '', # tax_location?
+ 'county', 'varchar', 'NULL', $char_d, '', '',
+ 'state', 'varchar', 'NULL', $char_d, '', '',
+ 'local', 'varchar', 'NULL', $char_d, '', '',
+ 'country', 'char', 'NULL', 2, '', '',
+ 'taxclassnumtaxed', 'int', 'NULL', '', '', '',
+ 'taxcattaxed', 'varchar', 'NULL', $char_d, '', '',
+ 'taxclassnum', 'int', 'NULL', '', '', '',
+ 'effdate', @date_type, '', '',
+ 'taxable', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkgtaxratenum',
+ 'unique' => [],
+ 'index' => [ [ 'data_vendor', 'geocode', 'taxproductnum' ] ],
+ },
+
+ 'part_pkg_taxoverride' => {
+ 'columns' => [
+ 'taxoverridenum', 'serial', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ 'taxclassnum', 'int', '', '', '', '',
+ 'usage_class', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'taxoverridenum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ], [ 'taxclassnum' ] ],
+ },
+
+# 'part_title' => {
+# 'columns' => [
+# 'titlenum', 'int', '', '',
+# 'title', 'varchar', '', $char_d,
+# ],
+# 'primary_key' => 'titlenum',
+# 'unique' => [ [] ],
+# 'index' => [ [] ],
+# },
+
+ 'pkg_svc' => {
+ 'columns' => [
+ 'pkgsvcnum', 'serial', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ 'svcpart', 'int', '', '', '', '',
+ 'quantity', 'int', '', '', '', '',
+ 'primary_svc','char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkgsvcnum',
+ 'unique' => [ ['pkgpart', 'svcpart'] ],
+ 'index' => [ ['pkgpart'], ['quantity'] ],
+ },
+
+ 'part_referral' => {
+ 'columns' => [
+ 'refnum', 'serial', '', '', '', '',
+ 'referral', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'refnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'], ['agentnum'], ],
+ },
+
+ '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, '', '',
+ 'columnlabel', 'varchar', 'NULL', $char_d, '', '',
+ '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, '', '',
+ '_password', 'varchar', 'NULL', 512, '', '',
+ '_password_encoding', 'varchar', 'NULL', $char_d, '', '',
+ '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
+ 'seconds_threshold', 'int', 'NULL', '', '', '',
+ 'upbytes', 'bigint', 'NULL', '', '', '',
+ 'upbytes_threshold', 'bigint', 'NULL', '', '', '',
+ 'downbytes', 'bigint', 'NULL', '', '', '',
+ 'downbytes_threshold', 'bigint', 'NULL', '', '', '',
+ 'totalbytes','bigint', 'NULL', '', '', '',
+ 'totalbytes_threshold', 'bigint', 'NULL', '', '', '',
+ 'domsvc', 'int', '', '', '', '',
+ 'pbxsvc', 'int', 'NULL', '', '', '',
+ 'last_login', @date_type, '', '',
+ 'last_logout', @date_type, '', '',
+ #communigate pro fields (quota = MaxAccountSize)
+ 'cgp_aliases', 'varchar', 'NULL', 255, '', '',
+ #settings
+ 'cgp_type', 'varchar', 'NULL', $char_d, '', '', #AccountType
+ 'file_quota', 'varchar', 'NULL', $char_d, '', '', #MaxWebSize
+ 'file_maxnum', 'varchar', 'NULL', $char_d, '', '', #MaxWebFiles
+ 'file_maxsize', 'varchar', 'NULL', $char_d, '', '', #MaxFileSize
+ 'cgp_accessmodes', 'varchar', 'NULL', 255, '', '', #AccessModes
+ 'password_selfchange','char', 'NULL', 1, '', '', #PWDAllowed
+ 'password_recover', 'char', 'NULL', 1, 'Y','', #PasswordRecovery
+ 'cgp_rulesallowed','varchar', 'NULL', $char_d, '', '', #RulesAllowed
+ 'cgp_rpopallowed', 'char', 'NULL', 1, '', '', #RPOPAllowed
+ 'cgp_mailtoall', 'char', 'NULL', 1, '', '', #MailToAll
+ 'cgp_addmailtrailer', 'char', 'NULL', 1, '', '', #AddMailTrailer
+ #XXX archive messages, mailing lists
+ #preferences
+ 'cgp_deletemode', 'varchar', 'NULL', $char_d, '', '',#DeleteMode
+ 'cgp_emptytrash', 'varchar', 'NULL', $char_d, '', '',#EmptyTrash
+ 'cgp_language', 'varchar', 'NULL', $char_d, '', '',#Language
+ 'cgp_timezone', 'varchar', 'NULL', $char_d, '', '',#TimeZone
+ 'cgp_skinname', 'varchar', 'NULL', $char_d, '', '',#SkinName
+ 'cgp_prontoskinname', 'varchar', 'NULL', $char_d, '', '',#ProntoSkinName
+ 'cgp_sendmdnmode', 'varchar', 'NULL', $char_d, '', '',#SendMDNMode
+ #mail
+#vacation message, redirect all mail, mail rules
+ #XXX RPOP settings
+ ],
+ 'primary_key' => 'svcnum',
+ #'unique' => [ [ 'username', 'domsvc' ] ],
+ 'unique' => [],
+ 'index' => [ ['username'], ['domsvc'], ['pbxsvc'] ],
+ },
+
+ 'acct_rt_transaction' => {
+ 'columns' => [
+ 'svcrtid', 'int', '', '', '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'transaction_id', 'int', '', '', '', '',
+ '_date', @date_type, '', '',
+ 'seconds', 'int', '', '', '', '', #uhhhh
+ 'support', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'svcrtid',
+ 'unique' => [],
+ 'index' => [ ['svcnum', 'transaction_id'] ],
+ },
+
+ #'svc_charge' => {
+ # 'columns' => [
+ # 'svcnum', 'int', '', '',
+ # 'amount', @money_type,
+ # ],
+ # 'primary_key' => 'svcnum',
+ # 'unique' => [ [] ],
+ # 'index' => [ [] ],
+ #},
+
+ 'svc_domain' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'domain', 'varchar', '', $char_d, '', '',
+ 'suffix', 'varchar', 'NULL', $char_d, '', '',
+ 'catchall', 'int', 'NULL', '', '', '',
+ 'parent_svcnum', 'int', 'NULL', '', '', '',
+ 'registrarnum', 'int', 'NULL', '', '', '',
+ 'registrarkey', 'varchar', 'NULL', 512, '', '',
+ 'setup_date', @date_type, '', '',
+ 'renewal_interval', 'int', 'NULL', '', '', '',
+ 'expiration_date', @date_type, '', '',
+ #communigate pro fields (quota = MaxAccountSize)
+ 'max_accounts', 'int', 'NULL', '', '', '',
+ 'trailer', 'text', 'NULL', '', '', '',
+ 'cgp_aliases', 'varchar', 'NULL', 255, '', '',
+ 'cgp_accessmodes','varchar','NULL', 255, '', '', #DomainAccessModes
+ #settings
+ 'acct_def_password_selfchange', 'char', 'NULL', 1, '', '',
+ 'acct_def_password_recover', 'char', 'NULL', 1, 'Y', '',
+ 'acct_def_cgp_accessmodes', 'varchar', 'NULL', 255, '', '',
+ 'acct_def_quota', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_file_quota', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_file_maxnum', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_file_maxsize', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_rulesallowed', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_rpopallowed', 'char', 'NULL', 1, '', '',
+ 'acct_def_cgp_mailtoall', 'char', 'NULL', 1, '', '',
+ 'acct_def_cgp_addmailtrailer', 'char', 'NULL', 1, '', '',
+ #XXX archive messages
+ #preferences
+ 'acct_def_cgp_deletemode', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_emptytrash', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_language', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_timezone', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_skinname', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_prontoskinname', 'varchar', 'NULL', $char_d, '', '',
+ 'acct_def_cgp_sendmdnmode', 'varchar', 'NULL', $char_d, '', '',
+ #mail
+ #XXX rules, archive rule, spam foldering rule(s)
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ ],
+ 'index' => [ ['domain'] ],
+ },
+
+ 'domain_record' => {
+ 'columns' => [
+ 'recnum', 'serial', '', '', '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'reczone', 'varchar', '', 255, '', '',
+ 'recaf', 'char', '', 2, '', '',
+ 'rectype', 'varchar', '', 5, '', '',
+ 'recdata', 'varchar', '', 255, '', '',
+ ],
+ 'primary_key' => 'recnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'] ],
+ },
+
+ 'registrar' => {
+ 'columns' => [
+ 'registrarnum', 'serial', '', '', '', '',
+ 'registrarname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'registrarnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cgp_rule' => {
+ 'columns' => [
+ 'rulenum', 'serial', '', '', '', '',
+ 'name', 'varchar', '', $char_d, '', '',
+ 'comment', 'varchar', 'NULL', $char_d, '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'priority', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'rulenum',
+ 'unique' => [ [ 'svcnum', 'name' ] ],
+ 'index' => [ [ 'svcnum' ] ],
+ },
+
+ 'cgp_rule_condition' => {
+ 'columns' => [
+ 'ruleconditionnum', 'serial', '', '', '', '',
+ 'condition', 'varchar', '', $char_d, '', '',
+ 'op', 'varchar', 'NULL', $char_d, '', '',
+ 'params', 'varchar', 'NULL', 255, '', '',
+ 'rulenum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'ruleconditionnum',
+ 'unique' => [],
+ 'index' => [ [ 'rulenum' ] ],
+ },
+
+ 'cgp_rule_action' => {
+ 'columns' => [
+ 'ruleactionnum', 'serial', '', '', '', '',
+ 'action', 'varchar', '', $char_d, '', '',
+ 'params', 'varchar', 'NULL', 255, '', '',
+ 'rulenum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'ruleactionnum',
+ 'unique' => [],
+ 'index' => [ [ 'rulenum' ] ],
+ },
+
+ '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', 'NULL', '', '', '',
+ 'config', 'text', 'NULL', '', '', '',
+ ],
+ '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', '', '', '',
+ 'upbytes', 'bigint', 'NULL', '', '', '',
+ 'downbytes', 'bigint', 'NULL', '', '', '',
+ 'totalbytes', 'bigint', 'NULL', '', '', '',
+ 'agentnum', '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', 'varchar', '', 512, '', '',
+ '_date', 'int', '', '', '', '',
+ 'status', 'varchar', '', $char_d, '', '',
+ 'statustext', 'text', 'NULL', '', '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ 'custnum', 'int', 'NULL', '', '', '',
+ 'secure', 'char', 'NULL', 1, '', '',
+ 'priority', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'jobnum',
+ 'unique' => [],
+ 'index' => [ [ 'secure' ], [ 'priority' ],
+ [ 'job' ], [ 'svcnum' ], [ 'custnum' ], [ 'status' ],
+ ],
+ },
+
+ 'queue_arg' => {
+ 'columns' => [
+ 'argnum', 'serial', '', '', '', '',
+ 'jobnum', 'int', '', '', '', '',
+ 'frozen', 'char', 'NULL', 1, '', '',
+ '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' ] ],
+ },
+
+ 'export_device' => {
+ 'columns' => [
+ 'exportdevicenum' => 'serial', '', '', '', '',
+ 'exportnum' => 'int', '', '', '', '',
+ 'devicepart' => 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'exportdevicenum',
+ 'unique' => [ [ 'exportnum', 'devicepart' ] ],
+ 'index' => [ [ 'exportnum' ], [ 'devicepart' ] ],
+ },
+
+ 'part_export' => {
+ 'columns' => [
+ 'exportnum', 'serial', '', '', '', '',
+ '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' => [],
+ },
+
+ 'cust_tax_exempt_pkg' => {
+ 'columns' => [
+ 'exemptpkgnum', 'serial', '', '', '', '',
+ #'custnum', 'int', '', '', '', ''
+ 'billpkgnum', 'int', '', '', '', '',
+ 'taxnum', 'int', '', '', '', '',
+ 'year', 'int', '', '', '', '',
+ 'month', 'int', '', '', '', '',
+ 'creditbillpkgnum', 'int', 'NULL', '', '', '',
+ 'amount', @money_type, '', '',
+ ],
+ 'primary_key' => 'exemptpkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'taxnum', 'year', 'month' ],
+ [ 'billpkgnum' ],
+ [ 'taxnum' ],
+ [ 'creditbillpkgnum' ],
+ ],
+ },
+
+ 'router' => {
+ 'columns' => [
+ 'routernum', 'serial', '', '', '', '',
+ 'routername', 'varchar', '', $char_d, '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'routernum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'part_svc_router' => {
+ 'columns' => [
+ 'svcrouternum', 'serial', '', '', '', '',
+ 'svcpart', 'int', '', '', '', '',
+ 'routernum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'svcrouternum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'addr_block' => {
+ 'columns' => [
+ 'blocknum', 'serial', '', '', '', '',
+ 'routernum', 'int', '', '', '', '',
+ 'ip_gateway', 'varchar', '', 15, '', '',
+ 'ip_netmask', 'int', '', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ 'manual_flag', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'blocknum',
+ 'unique' => [ [ 'blocknum', 'routernum' ] ],
+ 'index' => [],
+ },
+
+ 'svc_broadband' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'description', 'varchar', 'NULL', $char_d, '', '',
+ 'blocknum', 'int', 'NULL', '', '', '',
+ 'speed_up', 'int', '', '', '', '',
+ 'speed_down', 'int', '', '', '', '',
+ 'ip_addr', 'varchar', 'NULL', 15, '', '',
+ 'mac_addr', 'varchar', 'NULL', 12, '', '',
+ 'authkey', 'varchar', 'NULL', 32, '', '',
+ 'latitude', 'decimal', 'NULL', '', '', '',
+ 'longitude', 'decimal', 'NULL', '', '', '',
+ 'altitude', 'decimal', 'NULL', '', '', '',
+ 'vlan_profile', 'varchar', 'NULL', $char_d, '', '',
+ 'performance_profile', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ [ 'mac_addr' ] ],
+ 'index' => [],
+ },
+
+ 'part_virtual_field' => {
+ 'columns' => [
+ 'vfieldpart', 'serial', '', '', '', '',
+ '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' => [
+ 'vfieldnum', 'serial', '', '', '', '',
+ 'recnum', 'int', '', '', '', '',
+ 'vfieldpart', 'int', '', '', '', '',
+ 'value', 'varchar', '', 128, '', '',
+ ],
+ 'primary_key' => 'vfieldnum',
+ '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'] ],
+ },
+
+ 'part_pkg_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ], [ 'optionname' ] ],
+ },
+
+ 'part_pkg_report_option' => {
+ 'columns' => [
+ 'num', 'serial', '', '', '', '',
+ 'name', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'num',
+ 'unique' => [ [ 'name' ] ],
+ 'index' => [ [ 'disabled' ] ],
+ },
+
+ 'rate' => {
+ 'columns' => [
+ 'ratenum', 'serial', '', '', '', '',
+ 'ratename', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'ratenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'rate_detail' => {
+ 'columns' => [
+ 'ratedetailnum', 'serial', '', '', '', '',
+ 'ratenum', 'int', '', '', '', '',
+ 'orig_regionnum', 'int', 'NULL', '', '', '',
+ 'dest_regionnum', 'int', '', '', '', '',
+ 'min_included', 'int', '', '', '', '',
+ 'conn_charge', @money_type, '0', '', #'decimal','','10,5','0','',
+ 'conn_sec', 'int', '', '', '0', '',
+ 'min_charge', 'decimal', '', '10,5', '', '', #@money_type, '', '',
+ 'sec_granularity', 'int', '', '', '', '',
+ #time period (link to table of periods)?
+ 'classnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'ratedetailnum',
+ 'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
+ 'index' => [ [ 'ratenum', 'dest_regionnum' ] ],
+ },
+
+ 'rate_region' => {
+ 'columns' => [
+ 'regionnum', 'serial', '', '', '', '',
+ 'regionname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'regionnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'rate_prefix' => {
+ 'columns' => [
+ 'prefixnum', 'serial', '', '', '', '',
+ 'regionnum', 'int', '', '', '', '',
+ 'countrycode', 'varchar', '', 3, '', '',
+ 'npa', 'varchar', 'NULL', 10, '', '', #actually the whole prefix
+ 'nxx', 'varchar', 'NULL', 3, '', '', #actually not used
+ ],
+ 'primary_key' => 'prefixnum',
+ 'unique' => [],
+ 'index' => [ [ 'countrycode' ], [ 'npa' ], [ 'regionnum' ] ],
+ },
+
+ 'usage_class' => {
+ 'columns' => [
+ 'classnum', 'serial', '', '', '', '',
+ 'weight', 'int', 'NULL', '', '', '',
+ 'classname', 'varchar', '', $char_d, '', '',
+ 'format', 'varchar', 'NULL', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'reg_code' => {
+ 'columns' => [
+ 'codenum', 'serial', '', '', '', '',
+ 'code', 'varchar', '', $char_d, '', '',
+ 'agentnum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'codenum',
+ 'unique' => [ [ 'agentnum', 'code' ] ],
+ 'index' => [ [ 'agentnum' ] ],
+ },
+
+ 'reg_code_pkg' => {
+ 'columns' => [
+ 'codepkgnum', 'serial', '', '', '', '',
+ 'codenum', 'int', '', '', '', '',
+ 'pkgpart', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'codepkgnum',
+ 'unique' => [ [ 'codenum', 'pkgpart' ] ],
+ 'index' => [ [ 'codenum' ] ],
+ },
+
+ 'clientapi_session' => {
+ 'columns' => [
+ 'sessionnum', 'serial', '', '', '', '',
+ 'sessionid', 'varchar', '', $char_d, '', '',
+ 'namespace', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'sessionnum',
+ 'unique' => [ [ 'sessionid', 'namespace' ] ],
+ 'index' => [],
+ },
+
+ 'clientapi_session_field' => {
+ 'columns' => [
+ 'fieldnum', 'serial', '', '', '', '',
+ 'sessionnum', 'int', '', '', '', '',
+ 'fieldname', 'varchar', '', $char_d, '', '',
+ 'fieldvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'fieldnum',
+ 'unique' => [ [ 'sessionnum', 'fieldname' ] ],
+ 'index' => [],
+ },
+
+ 'payment_gateway' => {
+ 'columns' => [
+ 'gatewaynum', 'serial', '', '', '', '',
+ 'gateway_namespace','varchar', 'NULL', $char_d, '', '',
+ 'gateway_module', 'varchar', '', $char_d, '', '',
+ 'gateway_username', 'varchar', 'NULL', $char_d, '', '',
+ 'gateway_password', 'varchar', 'NULL', $char_d, '', '',
+ 'gateway_action', 'varchar', 'NULL', $char_d, '', '',
+ 'gateway_callback_url', 'varchar', 'NULL', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'gatewaynum',
+ 'unique' => [],
+ 'index' => [ [ 'disabled' ] ],
+ },
+
+ 'payment_gateway_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '', '', '',
+ 'gatewaynum', 'int', '', '', '', '',
+ 'optionname', 'varchar', '', $char_d, '', '',
+ 'optionvalue', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'gatewaynum' ], [ 'optionname' ] ],
+ },
+
+ 'agent_payment_gateway' => {
+ 'columns' => [
+ 'agentgatewaynum', 'serial', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ 'gatewaynum', 'int', '', '', '', '',
+ 'cardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'taxclass', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'agentgatewaynum',
+ 'unique' => [],
+ 'index' => [ [ 'agentnum', 'cardtype' ], ],
+ },
+
+ 'banned_pay' => {
+ 'columns' => [
+ 'bannum', 'serial', '', '', '', '',
+ 'payby', 'char', '', 4, '', '',
+ 'payinfo', 'varchar', '', 128, '', '', #say, a 512-big digest _hex encoded
+ #'paymask', 'varchar', 'NULL', $char_d, '', ''
+ '_date', @date_type, '', '',
+ 'otaker', 'varchar', 'NULL', 32, '', '',
+ 'usernum', 'int', 'NULL', '', '', '',
+ 'reason', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'bannum',
+ 'unique' => [ [ 'payby', 'payinfo' ] ],
+ 'index' => [ [ 'usernum' ] ],
+ },
+
+ 'pkg_category' => {
+ 'columns' => [
+ 'categorynum', 'serial', '', '', '', '',
+ 'categoryname', 'varchar', '', $char_d, '', '',
+ 'weight', 'int', 'NULL', '', '', '',
+ 'condense', 'char', 'NULL', 1, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'categorynum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'pkg_class' => {
+ 'columns' => [
+ 'classnum', 'serial', '', '', '', '',
+ 'classname', 'varchar', '', $char_d, '', '',
+ 'categorynum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'cdr' => {
+ 'columns' => [
+ # qw( name type null length default local );
+
+ ###
+ #asterisk fields
+ ###
+
+ 'acctid', 'bigserial', '', '', '', '',
+ #'calldate', 'TIMESTAMP with time zone', '', '', \'now()', '',
+ 'calldate', 'timestamp', '', '', \'now()', '',
+ 'clid', 'varchar', '', $char_d, \"''", '',
+ 'src', 'varchar', '', $char_d, \"''", '',
+ 'dst', 'varchar', '', $char_d, \"''", '',
+ 'dcontext', 'varchar', '', $char_d, \"''", '',
+ 'channel', 'varchar', '', $char_d, \"''", '',
+ 'dstchannel', 'varchar', '', $char_d, \"''", '',
+ 'lastapp', 'varchar', '', $char_d, \"''", '',
+ 'lastdata', 'varchar', '', $char_d, \"''", '',
+
+ #these don't seem to be logged by most of the SQL cdr_* modules
+ #except tds under sql-illegal names, so;
+ # ... don't rely on them for rating?
+ # and, what they hey, i went ahead and changed the names and data types
+ # to freeside-style dates...
+ #'start', 'timestamp', 'NULL', '', '', '',
+ #'answer', 'timestamp', 'NULL', '', '', '',
+ #'end', 'timestamp', 'NULL', '', '', '',
+ 'startdate', @date_type, '', '',
+ 'answerdate', @date_type, '', '',
+ 'enddate', @date_type, '', '',
+ #
+
+ 'duration', 'int', '', '', 0, '',
+ 'billsec', 'int', '', '', 0, '',
+ 'disposition', 'varchar', '', 45, \"''", '',
+ 'amaflags', 'int', '', '', 0, '',
+ 'accountcode', 'varchar', '', 20, \"''", '',
+ 'uniqueid', 'varchar', '', 32, \"''", '',
+ 'userfield', 'varchar', '', 255, \"''", '',
+
+ ###
+ # fields for unitel/RSLCOM/convergent that don't map well to asterisk
+ # defaults
+ # though these are now used elsewhere:
+ # charged_party, upstream_price, rated_price, carrierid
+ ###
+
+ #cdr_type: Usage = 1, S&E = 7, OC&C = 8
+ 'cdrtypenum', 'int', 'NULL', '', '', '',
+
+ 'charged_party', 'varchar', 'NULL', $char_d, '', '',
+
+ 'upstream_currency', 'char', 'NULL', 3, '', '',
+ 'upstream_price', 'decimal', 'NULL', '10,2', '', '',
+ 'upstream_rateplanid', 'int', 'NULL', '', '', '', #?
+
+ # how it was rated internally...
+ 'ratedetailnum', 'int', 'NULL', '', '', '',
+ 'rated_price', 'decimal', 'NULL', '10,4', '', '',
+
+ 'distance', 'decimal', 'NULL', '', '', '',
+ 'islocal', 'int', 'NULL', '', '', '', # '', '', 0, '' instead?
+
+ #cdr_calltype: the big list in appendix 2
+ 'calltypenum', 'int', 'NULL', '', '', '',
+
+ 'description', 'varchar', 'NULL', $char_d, '', '',
+ 'quantity', 'int', 'NULL', '', '', '',
+
+ #cdr_carrier: Telstra =1, Optus = 2, RSL COM = 3
+ 'carrierid', 'int', 'NULL', '', '', '',
+
+ 'upstream_rateid', 'int', 'NULL', '', '', '',
+
+ ###
+ #and now for our own fields
+ ###
+
+ # a svcnum... right..?
+ 'svcnum', 'int', 'NULL', '', '', '',
+
+ #NULL, done (or something)
+ 'freesidestatus', 'varchar', 'NULL', 32, '', '',
+
+ #NULL, done (or something)
+ 'freesiderewritestatus', 'varchar', 'NULL', 32, '', '',
+
+ #an indexed place to put big numbers
+ 'cdrid', 'bigint', 'NULL', '', '', '',
+
+ #old
+ 'cdrbatch', 'varchar', 'NULL', 255, '', '',
+ #new
+ 'cdrbatchnum', 'int', 'NULL', '', '', '',
+
+ ],
+ 'primary_key' => 'acctid',
+ 'unique' => [],
+ 'index' => [ [ 'calldate' ],
+ [ 'src' ], [ 'dst' ], [ 'dcontext' ], [ 'charged_party' ],
+ [ 'accountcode' ], [ 'carrierid' ], [ 'cdrid' ],
+ [ 'freesidestatus' ], [ 'freesiderewritestatus' ],
+ [ 'cdrbatch' ], [ 'cdrbatchnum' ],
+ ],
+ },
+
+ 'cdr_batch' => {
+ 'columns' => [
+ 'cdrbatchnum', 'serial', '', '', '', '',
+ 'cdrbatch', 'varchar', 'NULL', 255, '', '',
+ '_date', @date_type, '', '',
+ ],
+ 'primary_key' => 'cdrbatchnum',
+ 'unique' => [ [ 'cdrbatch' ] ],
+ 'index' => [],
+ },
+
+ 'cdr_termination' => {
+ 'columns' => [
+ 'cdrtermnum', 'bigserial', '', '', '', '',
+ 'acctid', 'bigint', '', '', '', '',
+ 'termpart', 'int', '', '', '', '',#future use see below
+ 'rated_price', 'decimal', 'NULL', '10,4', '', '',
+ 'status', 'varchar', 'NULL', 32, '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'cdrtermnum',
+ 'unique' => [ [ 'acctid', 'termpart' ] ],
+ 'index' => [ [ 'acctid' ], [ 'status' ], ],
+ },
+
+ #to handle multiple termination/settlement passes...
+ # 'part_termination' => {
+ # 'columns' => [
+ # 'termpart', 'int', '', '', '', '',
+ # 'termname', 'varchar', '', $char_d, '', '',
+ # 'cdr_column', 'varchar', '', $char_d, '', '', #maybe set it here instead of in the price plan?
+ # ],
+ # 'primary_key' => 'termpart',
+ # 'unique' => [],
+ # 'index' => [],
+ # },
+
+ #the remaining cdr_ tables are not really used
+ 'cdr_calltype' => {
+ 'columns' => [
+ 'calltypenum', 'serial', '', '', '', '',
+ 'calltypename', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'calltypenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cdr_type' => {
+ 'columns' => [
+ 'cdrtypenum' => 'serial', '', '', '', '',
+ 'cdrtypename' => 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'cdrtypenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cdr_carrier' => {
+ 'columns' => [
+ 'carrierid' => 'serial', '', '', '', '',
+ 'carriername' => 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'carrierid',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ #'cdr_file' => {
+ # 'columns' => [
+ # 'filenum', 'serial', '', '', '', '',
+ # 'filename', 'varchar', '', '', '', '',
+ # 'status', 'varchar', 'NULL', '', '', '',
+ # ],
+ # 'primary_key' => 'filenum',
+ # 'unique' => [ [ 'filename' ], ], #just change the index if we need to
+ # # agent-virtualize or have a customer
+ # # with dup-filename needs or something
+ # # (only used by cdr.http_and_import for
+ # # chrissakes)
+ # 'index' => [],
+ #},
+
+ 'inventory_item' => {
+ 'columns' => [
+ 'itemnum', 'serial', '', '', '', '',
+ 'classnum', 'int', '', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ 'item', 'varchar', '', $char_d, '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'itemnum',
+ 'unique' => [ [ 'classnum', 'item' ] ],
+ 'index' => [ [ 'classnum' ], [ 'agentnum' ], [ 'svcnum' ] ],
+ },
+
+ 'inventory_class' => {
+ 'columns' => [
+ 'classnum', 'serial', '', '', '', '',
+ 'classname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'access_user' => {
+ 'columns' => [
+ 'usernum', 'serial', '', '', '', '',
+ 'username', 'varchar', '', $char_d, '', '',
+ '_password', 'varchar', '', $char_d, '', '',
+ 'last', 'varchar', '', $char_d, '', '',
+ 'first', 'varchar', '', $char_d, '', '',
+ 'user_custnum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'usernum',
+ 'unique' => [ [ 'username' ] ],
+ 'index' => [ [ 'user_custnum' ] ],
+ },
+
+ 'access_user_pref' => {
+ 'columns' => [
+ 'prefnum', 'serial', '', '', '', '',
+ 'usernum', 'int', '', '', '', '',
+ 'prefname', 'varchar', '', $char_d, '', '',
+ 'prefvalue', 'text', 'NULL', '', '', '',
+ 'expiration', @date_type, '', '',
+ ],
+ 'primary_key' => 'prefnum',
+ 'unique' => [],
+ 'index' => [ [ 'usernum' ] ],
+ },
+
+ 'access_group' => {
+ 'columns' => [
+ 'groupnum', 'serial', '', '', '', '',
+ 'groupname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'groupnum',
+ 'unique' => [ [ 'groupname' ] ],
+ 'index' => [],
+ },
+
+ 'access_usergroup' => {
+ 'columns' => [
+ 'usergroupnum', 'serial', '', '', '', '',
+ 'usernum', 'int', '', '', '', '',
+ 'groupnum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'usergroupnum',
+ 'unique' => [ [ 'usernum', 'groupnum' ] ],
+ 'index' => [ [ 'usernum' ] ],
+ },
+
+ 'access_groupagent' => {
+ 'columns' => [
+ 'groupagentnum', 'serial', '', '', '', '',
+ 'groupnum', 'int', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'groupagentnum',
+ 'unique' => [ [ 'groupnum', 'agentnum' ] ],
+ 'index' => [ [ 'groupnum' ] ],
+ },
+
+ 'access_right' => {
+ 'columns' => [
+ 'rightnum', 'serial', '', '', '', '',
+ 'righttype', 'varchar', '', $char_d, '', '',
+ 'rightobjnum', 'int', '', '', '', '',
+ 'rightname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'rightnum',
+ 'unique' => [ [ 'righttype', 'rightobjnum', 'rightname' ] ],
+ 'index' => [],
+ },
+
+ 'svc_phone' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'countrycode', 'varchar', '', 3, '', '',
+ 'phonenum', 'varchar', '', 15, '', '', #12 ?
+ 'pin', 'varchar', 'NULL', $char_d, '', '',
+ 'sip_password', 'varchar', 'NULL', $char_d, '', '',
+ 'phone_name', 'varchar', 'NULL', $char_d, '', '',
+ 'pbxsvc', 'int', 'NULL', '', '', '',
+ 'domsvc', 'int', 'NULL', '', '', '',
+ 'locationnum', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['countrycode', 'phonenum'], ['pbxsvc'], ['domsvc'],
+ ['locationnum'],
+ ],
+ },
+
+ 'phone_device' => {
+ 'columns' => [
+ 'devicenum', 'serial', '', '', '', '',
+ 'devicepart', 'int', '', '', '', '',
+ 'svcnum', 'int', '', '', '', '',
+ 'mac_addr', 'varchar', 'NULL', 12, '', '',
+ ],
+ 'primary_key' => 'devicenum',
+ 'unique' => [ [ 'mac_addr' ], ],
+ 'index' => [ [ 'devicepart' ], [ 'svcnum' ], ],
+ },
+
+ 'part_device' => {
+ 'columns' => [
+ 'devicepart', 'serial', '', '', '', '',
+ 'devicename', 'varchar', '', $char_d, '', '',
+ #'classnum', #tie to an inventory class?
+ ],
+ 'primary_key' => 'devicepart',
+ 'unique' => [ [ 'devicename' ] ], #?
+ 'index' => [],
+ },
+
+ 'phone_avail' => {
+ 'columns' => [
+ 'availnum', 'serial', '', '', '', '',
+ 'exportnum', 'int', '', '', '', '',
+ 'countrycode', 'varchar', '', 3, '', '',
+ 'state', 'char', 'NULL', 2, '', '',
+ 'npa', 'char', '', 3, '', '',
+ 'nxx', 'char', 'NULL', 3, '', '',
+ 'station', 'char', 'NULL', 4, '', '',
+ 'name', 'varchar', 'NULL', $char_d, '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ 'availbatch', 'varchar', 'NULL', $char_d, '', '',
+ ],
+ 'primary_key' => 'availnum',
+ 'unique' => [],
+ 'index' => [ [ 'exportnum', 'countrycode', 'state' ], #npa search
+ [ 'exportnum', 'countrycode', 'npa' ], #nxx search
+ [ 'exportnum', 'countrycode', 'npa', 'nxx' ],#station search
+ [ 'exportnum', 'countrycode', 'npa', 'nxx', 'station' ], # #
+ [ 'svcnum' ],
+ [ 'availbatch' ],
+ ],
+ },
+
+ 'reason_type' => {
+ 'columns' => [
+ 'typenum', 'serial', '', '', '', '',
+ 'class', 'char', '', 1, '', '',
+ 'type', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'typenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'reason' => {
+ 'columns' => [
+ 'reasonnum', 'serial', '', '', '', '',
+ 'reason_type', 'int', '', '', '', '',
+ 'reason', 'text', '', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'reasonnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'conf' => {
+ 'columns' => [
+ 'confnum', 'serial', '', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
+ 'name', 'varchar', '', $char_d, '', '',
+ 'value', 'text', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'confnum',
+ 'unique' => [ [ 'agentnum', 'name' ]],
+ 'index' => [],
+ },
+
+ 'pkg_referral' => {
+ 'columns' => [
+ 'pkgrefnum', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'refnum', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'pkgrefnum',
+ 'unique' => [ [ 'pkgnum', 'refnum' ] ],
+ 'index' => [ [ 'pkgnum' ], [ 'refnum' ] ],
+ },
+
+ 'svc_pbx' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'id', 'int', 'NULL', '', '', '',
+ 'title', 'varchar', 'NULL', $char_d, '', '',
+ 'max_extensions', 'int', 'NULL', '', '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ [ 'id' ] ],
+ },
+
+ 'svc_mailinglist' => { #svc_group?
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'username', 'varchar', '', $username_len, '', '',
+ 'domsvc', 'int', '', '', '', '',
+ 'listnum', 'int', '', '', '', '',
+ 'reply_to', 'char', 'NULL', 1, '', '',#SetReplyTo
+ 'remove_from', 'char', 'NULL', 1, '', '',#RemoveAuthor
+ 'reject_auto', 'char', 'NULL', 1, '', '',#RejectAuto
+ 'remove_to_and_cc', 'char', 'NULL', 1, '', '',#RemoveToAndCc
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['username'], ['domsvc'], ['listnum'] ],
+ },
+
+ 'mailinglist' => {
+ 'columns' => [
+ 'listnum', 'serial', '', '', '', '',
+ 'listname', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'listnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'mailinglistmember' => {
+ 'columns' => [
+ 'membernum', 'serial', '', '', '', '',
+ 'listnum', 'int', '', '', '', '',
+ 'svcnum', 'int', 'NULL', '', '', '',
+ 'contactemailnum', 'int', 'NULL', '', '', '',
+ 'email', 'varchar', 'NULL', 255, '', '',
+ ],
+ 'primary_key' => 'membernum',
+ 'unique' => [],
+ 'index' => [['listnum'],['svcnum'],['contactemailnum'],['email']],
+ },
+
+
+ # name type nullability length default local
+
+ #'new_table' => {
+ # 'columns' => [
+ # 'num', 'serial', '', '', '', '',
+ # ],
+ # 'primary_key' => 'num',
+ # 'unique' => [],
+ # 'index' => [],
+ #},
+
+ };
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>
+
+=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/Setup.pm b/FS/FS/Setup.pm
new file mode 100644
index 0000000..29ca9a8
--- /dev/null
+++ b/FS/FS/Setup.pm
@@ -0,0 +1,552 @@
+package FS::Setup;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+#use Tie::DxHash;
+use Tie::IxHash;
+use FS::UID qw( dbh driver_name );
+use FS::Record;
+
+use FS::svc_domain;
+$FS::svc_domain::whois_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( create_initial_data );
+
+=head1 NAME
+
+FS::Setup - Database setup
+
+=head1 SYNOPSIS
+
+ use FS::Setup;
+
+=head1 DESCRIPTION
+
+Currently this module simply provides a place to store common subroutines for
+database setup.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item
+
+=cut
+
+sub create_initial_data {
+ my %opt = @_;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ $FS::UID::AutoCommit = 0;
+
+ populate_locales();
+
+ populate_duplock();
+
+ #initial_data data
+ populate_initial_data(%opt);
+
+ populate_access();
+
+ populate_msgcat();
+
+ if ( $oldAutoCommit ) {
+ dbh->commit or die dbh->errstr;
+ }
+
+}
+
+sub populate_locales {
+
+ use Locale::Country;
+ use FS::cust_main_county;
+
+ #cust_main_county
+ foreach my $country ( sort map uc($_), all_country_codes ) {
+ _add_country($country);
+ }
+
+}
+
+sub populate_addl_locales {
+
+ my %addl = (
+ 'US' => {
+ 'FM' => 'Federated States of Micronesia',
+ 'MH' => 'Marshall Islands',
+ 'PW' => 'Palau',
+ 'AA' => "Armed Forces Americas (except Canada)",
+ 'AE' => "Armed Forces Europe / Canada / Middle East / Africa",
+ 'AP' => "Armed Forces Pacific",
+ },
+ );
+
+ foreach my $country ( keys %addl ) {
+ foreach my $state ( keys %{ $addl{$country} } ) {
+ # $longname = $addl{$country}{$state};
+ _add_locale( 'country'=>$country, 'state'=>$state);
+ }
+ }
+
+}
+
+sub _add_country {
+
+ use Locale::SubCountry;
+
+ my( $country ) = shift;
+
+ my $subcountry = eval { new Locale::SubCountry($country) };
+ my @states = $subcountry ? $subcountry->all_codes : undef;
+
+ if ( !scalar(@states) || ( scalar(@states)==1 && !defined($states[0]) ) ) {
+
+ _add_locale( 'country'=>$country );
+
+ } else {
+
+ if ( $states[0] =~ /^(\d+|\w)$/ ) {
+ @states = map $subcountry->full_name($_), @states
+ }
+
+ foreach my $state ( @states ) {
+ _add_locale( 'country'=>$country, 'state'=>$state);
+ }
+
+ }
+
+}
+
+sub _add_locale {
+ my $cust_main_county = new FS::cust_main_county( { 'tax'=>0, @_ });
+ my $error = $cust_main_county->insert;
+ die $error if $error;
+}
+
+sub populate_duplock {
+
+ return unless driver_name =~ /^mysql/i;
+
+ my $sth = dbh->prepare(
+ "INSERT INTO duplicate_lock ( lockname ) VALUES ( 'svc_acct' )"
+ ) or die dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+}
+
+sub populate_initial_data {
+ my %opt = @_;
+
+ my $data = initial_data(%opt);
+
+ foreach my $table ( keys %$data ) {
+
+ #warn "popuilating $table\n";
+
+ my $class = "FS::$table";
+ eval "use $class;";
+ die $@ if $@;
+
+ $class->_populate_initial_data(%opt)
+ if $class->can('_populate_initial_data');
+
+ my @records = @{ $data->{$table} };
+
+ foreach my $record ( @records ) {
+
+ my $args = delete($record->{'_insert_args'}) || [];
+ my $object = $class->new( $record );
+ my $error = $object->insert( @$args );
+ die "error inserting record into $table: $error\n"
+ if $error;
+
+ #my $pkey = $object->primary_key;
+ #my $pkeyvalue = $object->$pkey();
+ #warn " inserted $pkeyvalue\n";
+
+ }
+
+ }
+
+}
+
+sub initial_data {
+ my %opt = @_;
+
+ #tie my %hash, 'Tie::DxHash',
+ tie my %hash, 'Tie::IxHash',
+
+ #bootstrap user
+ 'access_user' => [
+ { 'username' => 'fs_bootstrap',
+ '_password' => 'changeme', #will trigger warning if you try to enable
+ 'last' => 'User',
+ 'first' => 'Bootstrap',
+ 'disabled' => 'Y',
+ },
+ ],
+
+ #superuser group
+ 'access_group' => [
+ { 'groupname' => 'Superuser' },
+ ],
+
+ #reason types
+ 'reason_type' => [],
+
+#XXX need default new-style billing events
+# #billing events
+# 'part_bill_event' => [
+# { 'payby' => 'CARD',
+# 'event' => 'Batch card',
+# 'seconds' => 0,
+# 'eventcode' => '$cust_bill->batch_card(%options);',
+# 'weight' => 40,
+# 'plan' => 'batch-card',
+# },
+# { 'payby' => 'BILL',
+# 'event' => 'Send invoice',
+# 'seconds' => 0,
+# 'eventcode' => '$cust_bill->send();',
+# 'weight' => 50,
+# 'plan' => 'send',
+# },
+# { 'payby' => 'DCRD',
+# 'event' => 'Send invoice',
+# 'seconds' => 0,
+# 'eventcode' => '$cust_bill->send();',
+# 'weight' => 50,
+# 'plan' => 'send',
+# },
+# { 'payby' => 'DCHK',
+# 'event' => 'Send invoice',
+# 'seconds' => 0,
+# 'eventcode' => '$cust_bill->send();',
+# 'weight' => 50,
+# 'plan' => 'send',
+# },
+# { 'payby' => 'DCLN',
+# 'event' => 'Suspend',
+# 'seconds' => 0,
+# 'eventcode' => '$cust_bill->suspend();',
+# 'weight' => 40,
+# 'plan' => 'suspend',
+# },
+# #{ 'payby' => 'DCLN',
+# # 'event' => 'Retriable',
+# # 'seconds' => 0,
+# # 'eventcode' => '$cust_bill_event->retriable();',
+# # 'weight' => 60,
+# # 'plan' => 'retriable',
+# #},
+# ],
+
+ #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 View/Edit service definitions and Add a new
+ #service definition with Table svc_domain (and no modifiers).
+ 'part_svc' => [
+ { 'svc' => 'Domain',
+ 'svcdb' => 'svc_domain',
+ }
+ ],
+
+ #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 View/Edit package
+ #definitions and Add a new package definition which includes quantity 1 of
+ #the svc_domain service you created above.
+ 'part_pkg' => [
+ { 'pkg' => 'System Domain',
+ 'comment' => '(NOT FOR CUSTOMERS)',
+ 'freq' => '0',
+ 'plan' => 'flat',
+ '_insert_args' => [
+ 'pkg_svc' => { 1 => 1 }, # XXX
+ 'primary_svc' => 1, #XXX
+ 'options' => {
+ 'setup_fee' => '0',
+ 'recur_fee' => '0',
+ },
+ ],
+ },
+ ],
+
+ #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 View/Edit
+ #agent types and Add a new agent type.
+ 'agent_type' => [
+ { 'atype' => 'Internal' },
+ ],
+
+ #Allow this agent type to sell the package you created above.
+ 'type_pkgs' => [
+ { 'typenum' => 1, #XXX
+ 'pkgpart' => 1, #XXX
+ },
+ ],
+
+ #After creating a new agent type, you must create an agent. Click on
+ #View/Edit agents and Add a new agent.
+ 'agent' => [
+ { 'agent' => 'Internal',
+ 'typenum' => 1, # XXX
+ },
+ ],
+
+ #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 View/Edit advertising
+ #sources and Add a new advertising source.
+ 'part_referral' => [
+ { 'referral' => 'Internal', },
+ ],
+
+ #Click on New Customer and create a new customer for your system accounts
+ #with billing type Complimentary. Leave the First package dropdown set to
+ #(none).
+ 'cust_main' => [
+ { 'agentnum' => 1, #XXX
+ 'refnum' => 1, #XXX
+ 'first' => 'System',
+ 'last' => 'Accounts',
+ 'address1' => '1234 System Lane',
+ 'city' => 'Systemtown',
+ 'state' => 'CA',
+ 'zip' => '54321',
+ 'country' => 'US',
+ 'payby' => 'COMP',
+ 'payinfo' => 'system', #or something
+ 'paydate' => '1/2037',
+ },
+ ],
+
+ #From the Customer View screen of the newly created customer, order the
+ #package you defined above.
+ 'cust_pkg' => [
+ { 'custnum' => 1, #XXX
+ 'pkgpart' => 1, #XXX
+ },
+ ],
+
+ #From the Package View screen of the newly created package, choose
+ #(Provision) to add the customer's service for this new package.
+ #Add your own domain.
+ 'svc_domain' => [
+ { 'domain' => $opt{'domain'},
+ 'pkgnum' => 1, #XXX
+ 'svcpart' => 1, #XXX
+ 'action' => 'N', #pseudo-field
+ },
+ ],
+
+ #Go back to View/Edit service definitions on the main menu, and Add a new
+ #service definition with Table svc_acct. Select your domain in the domsvc
+ #Modifier. Set Fixed to define a service locked-in to this domain, or
+ #Default to define a service which may select from among this domain and
+ #the customer's domains.
+
+ #not yet....
+
+ #usage classes
+ 'usage_class' => [],
+
+ #phone types
+ 'phone_type' => [],
+
+ ;
+
+ \%hash;
+
+}
+
+sub populate_access {
+
+ use FS::AccessRight;
+ use FS::access_right;
+
+ foreach my $rightname ( FS::AccessRight->default_superuser_rights ) {
+ my $access_right = new FS::access_right {
+ 'righttype' => 'FS::access_group',
+ 'rightobjnum' => 1, #$supergroup->groupnum,
+ 'rightname' => $rightname,
+ };
+ my $ar_error = $access_right->insert;
+ die $ar_error if $ar_error;
+ }
+
+ #foreach my $agent ( qsearch('agent', {} ) ) {
+ my $access_groupagent = new FS::access_groupagent {
+ 'groupnum' => 1, #$supergroup->groupnum,
+ 'agentnum' => 1, #$agent->agentnum,
+ };
+ my $aga_error = $access_groupagent->insert;
+ die $aga_error if $aga_error;
+ #}
+
+}
+
+sub populate_msgcat {
+
+ use FS::Record qw(qsearch);
+ use FS::msgcat;
+
+ foreach my $del_msgcat ( qsearch('msgcat', {}) ) {
+ my $error = $del_msgcat->delete;
+ die $error if $error;
+ }
+
+ my %messages = msgcat_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;
+ }
+ }
+
+}
+
+sub msgcat_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',
+ },
+
+ 'phonenum_in_use' => {
+ 'en_US' => 'Phone number 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',
+ },
+
+ 'stateid' => {
+ 'en_US' => 'Driver\'s License',
+ },
+
+ 'stateid_state' => {
+ 'en_US' => 'Driver\'s License State',
+ },
+
+ 'invalid_domain' => {
+ 'en_US' => 'Invalid domain',
+ },
+
+ );
+}
+
+=back
+
+=head1 BUGS
+
+Sure.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/FS/TicketSystem.pm b/FS/FS/TicketSystem.pm
new file mode 100644
index 0000000..a80a827
--- /dev/null
+++ b/FS/FS/TicketSystem.pm
@@ -0,0 +1,30 @@
+package FS::TicketSystem;
+
+use strict;
+use vars qw( $conf $system $AUTOLOAD );
+use FS::Conf;
+use FS::UID;
+
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $system = $conf->config('ticket_system');
+} );
+
+sub AUTOLOAD {
+ my $self = shift;
+
+ my($sub)=$AUTOLOAD;
+ $sub =~ s/.*://;
+
+ my $conf = new FS::Conf;
+ die "FS::TicketSystem::$AUTOLOAD called, but no ticket system configured\n"
+ unless $system;
+
+ eval "use FS::TicketSystem::$system;";
+ die $@ if $@;
+
+ $self .= "::$system";
+ $self->$sub(@_);
+}
+
+1;
diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm
new file mode 100644
index 0000000..2995b88
--- /dev/null
+++ b/FS/FS/TicketSystem/RT_External.pm
@@ -0,0 +1,401 @@
+package FS::TicketSystem::RT_External;
+
+use strict;
+use vars qw( $DEBUG $me $conf $dbh $default_queueid $external_url
+ $priority_reverse
+ $priority_field $priority_field_queue $field
+ );
+use URI::Escape;
+use FS::UID qw(dbh);
+use FS::Record qw(qsearchs);
+use FS::cust_main;
+use Carp qw(cluck);
+
+$me = '[FS::TicketSystem::RT_External]';
+$DEBUG = 0;
+
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $default_queueid = $conf->config('ticket_system-default_queueid');
+ $priority_reverse = $conf->exists('ticket_system-priority_reverse');
+ $priority_field =
+ $conf->config('ticket_system-custom_priority_field');
+ if ( $priority_field ) {
+ $priority_field_queue =
+ $conf->config('ticket_system-custom_priority_field_queue');
+
+ $field = $priority_field_queue
+ ? $priority_field_queue. '.%7B'. $priority_field. '%7D'
+ : $priority_field;
+ } else {
+ $priority_field_queue = '';
+ $field = '';
+ }
+
+ $external_url = '';
+ $dbh = dbh;
+ if ($conf->config('ticket_system') eq 'RT_External') {
+ my ($datasrc, $user, $pass) = $conf->config('ticket_system-rt_external_datasrc');
+ $dbh = DBI->connect($datasrc, $user, $pass, { 'ChopBlanks' => 1 })
+ or die "RT_External DBI->connect error: $DBI::errstr\n";
+
+ $external_url = $conf->config('ticket_system-rt_external_url');
+ }
+
+ #kludge... should *use* the id... but good enough for now
+ if ( $priority_field_queue =~ /^(\d+)$/ ) {
+ my $id = $1;
+ my $sql = 'SELECT Name FROM Queues WHERE Id = ?';
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
+ $sth->execute($id) or die $sth->errstr. " executing $sql";
+
+ $priority_field_queue = $sth->fetchrow_arrayref->[0];
+
+ }
+
+} );
+
+sub num_customer_tickets {
+ my( $self, $custnum, $priority ) = @_;
+
+ my( $from_sql, @param) = $self->_from_customer( $custnum, $priority );
+
+ my $sql = "SELECT COUNT(*) $from_sql";
+ warn "$me $sql (@param)" if $DEBUG;
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
+ $sth->execute(@param) or die $sth->errstr. " executing $sql";
+
+ $sth->fetchrow_arrayref->[0];
+
+}
+
+sub customer_tickets {
+ my( $self, $custnum, $limit, $priority ) = @_;
+ $limit ||= 0;
+
+ my( $from_sql, @param) = $self->_from_customer( $custnum, $priority );
+ my $sql = "
+ SELECT Tickets.*,
+ Queues.Name AS Queue,
+ Users.Name AS Owner,
+ position(Tickets.Status in 'newopenstalledresolvedrejecteddeleted')
+ AS svalue
+ ". ( length($priority) ? ", ObjectCustomFieldValues.Content" : '' )."
+ $from_sql
+ ORDER BY svalue,
+ Priority ". ( $priority_reverse ? 'ASC' : 'DESC' ). ",
+ id DESC
+ LIMIT $limit
+ ";
+ warn "$me $sql (@param)" if $DEBUG;
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql";
+ $sth->execute(@param) or die $sth->errstr. "executing $sql";
+
+ #munge column names??? #httemplate/view/cust_main/tickets.html has column
+ #names that might not make sense now...
+ $sth->fetchall_arrayref({});
+
+}
+
+sub comments_on_tickets {
+ my ($self, $custnum, $limit, $time ) = @_;
+ $limit ||= 0;
+
+ my( $from_sql, @param) = $self->_from_customer( $custnum );
+ my $sql = qq{
+ SELECT transactions.*, Attachments.content, Tickets.subject
+ FROM transactions
+ JOIN Attachments ON( Attachments.transactionid = transactions.id )
+ JOIN Tickets ON ( Tickets.id = transactions.objectid )
+ JOIN Links ON ( Tickets.id = Links.LocalBase
+ AND Links.Base LIKE '%/ticket/' || Tickets.id )
+
+
+ WHERE ( Status = 'new' OR Status = 'open' OR Status = 'stalled' )
+ AND Target = 'freeside://freeside/cust_main/$custnum'
+ AND transactions.type = 'Comment'
+ AND transactions.created >= (SELECT TIMESTAMP WITH TIME ZONE 'epoch' + $time * INTERVAL '1 second')
+ LIMIT $limit
+ };
+ cluck $sql if $DEBUG > 0;
+ #AND created >
+ $dbh->selectall_arrayref( $sql, { Slice => {} } ) or die $dbh->errstr . " $sql";
+}
+
+sub _from_customer {
+ my( $self, $custnum, $priority ) = @_;
+
+ my @param = ();
+ my $join = '';
+ my $where = '';
+ if ( defined($priority) ) {
+
+ my $queue_sql = " ObjectCustomFields.ObjectId = ( SELECT id FROM Queues
+ WHERE Queues.Name = ? )
+ OR ( ? = '' AND ObjectCustomFields.ObjectId = 0 )";
+
+ my $customfield_sql =
+ "customfield = (
+ SELECT CustomFields.Id FROM CustomFields
+ JOIN ObjectCustomFields
+ ON ( CustomFields.id = ObjectCustomFields.CustomField )
+ WHERE LookupType = 'RT::Queue-RT::Ticket'
+ AND Name = ?
+ AND ( $queue_sql )
+ )";
+
+ push @param, $priority_field,
+ $priority_field_queue,
+ $priority_field_queue;
+
+ if ( length($priority) ) {
+ #$where = "
+ # and ? = ( select content from TicketCustomFieldValues
+ # where ticket = tickets.id
+ # and customfield = ( select id from customfields
+ # where name = ?
+ # and ( $queue_sql )
+ # )
+ # )
+ #";
+ unshift @param, $priority;
+
+ $join = "JOIN ObjectCustomFieldValues
+ ON ( Tickets.id = ObjectCustomFieldValues.ObjectId )";
+
+ $where = " AND Content = ?
+ AND ObjectCustomFieldValues.Disabled != 1
+ AND ObjectType = 'RT::Ticket'
+ AND $customfield_sql";
+
+ } else {
+
+ $where =
+ "AND 0 = ( SELECT COUNT(*) FROM ObjectCustomFieldValues
+ WHERE ObjectId = Tickets.id
+ AND ObjectType = 'RT::Ticket'
+ AND $customfield_sql
+ )
+ ";
+ }
+
+ }
+
+ my $sql = "
+ FROM Tickets
+ JOIN Queues ON ( Tickets.Queue = Queues.id )
+ JOIN Users ON ( Tickets.Owner = Users.id )
+ JOIN Links ON ( Tickets.id = Links.LocalBase
+ AND Links.Base LIKE '%/ticket/' || Tickets.id )
+ $join
+
+ WHERE ( ". join(' OR ', map "Status = '$_'", $self->statuses ). " )
+ AND Target = 'freeside://freeside/cust_main/$custnum'
+ $where
+ ";
+
+ ( $sql, @param );
+
+}
+
+sub statuses {
+ #my $self = shift;
+ my @statuses = grep { ! /^\s*$/ } $conf->config('cust_main-ticket_statuses');
+ @statuses = (qw( new open stalled )) unless scalar(@statuses);
+ @statuses;
+}
+
+sub href_customer_tickets {
+ my( $self, $custnum ) = ( shift, shift );
+ my( $priority, @statuses);
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $priority = $opt->{'priority'};
+ @statuses = $opt->{'statuses'} ? @{$opt->{'statuses'}} : $self->statuses;
+ } else {
+ $priority = shift;
+ @statuses = $self->statuses;
+ }
+
+ #my $href = $self->baseurl;
+
+ #i snarfed this from an RT bookmarked search, then unescaped (some of) it with
+ #perl -npe 's/%([0-9A-F]{2})/pack('C', hex($1))/eg;'
+
+ #$href .=
+ my $href =
+ "Search/Results.html?Order=ASC&".
+ "Query= MemberOf = 'freeside://freeside/cust_main/$custnum' ".
+ #" AND ( Status = 'open' OR Status = 'new' OR Status = 'stalled' )"
+ " AND ( ". join(' OR ', map "Status = '$_'", @statuses ). " ) "
+ ;
+
+ if ( defined($priority) && $field && $priority_field_queue ) {
+ $href .= " AND Queue = '$priority_field_queue' ";
+ }
+ if ( defined($priority) && $field ) {
+ $href .= " AND 'CF.$field' ";
+ if ( $priority ) {
+ $href .= "= '$priority' ";
+ } else {
+ $href .= "IS 'NULL' "; #this is "RTQL", not SQL
+ }
+ }
+
+ #$href =
+ uri_escape($href);
+ #eventually should unescape all of it...
+
+ $href .= '&Rows=100'.
+ '&OrderBy=id&Page=1'.
+ '&Format=%27%20%20%20%3Cb%3E%3Ca%20href%3D%22'.
+ $self->baseurl.
+ 'Ticket%2FDisplay.html%3Fid%3D__id__%22%3E__id__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3A%23%27%2C%20%0A%27%3Cb%3E%3Ca%20href%3D%22'.
+ $self->baseurl.
+ 'Ticket%2FDisplay.html%3Fid%3D__id__%22%3E__Subject__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3ASubject%27%2C%20%0A%27__Status__%27%2C%20';
+
+ if ( defined($priority) && $field ) {
+ $href .= '%0A%27__CustomField.'. $field. '__%2FTITLE%3ASeverity%27%2C%20';
+ }
+
+ $href .= '%0A%27__QueueName__%27%2C%20%0A%27__OwnerName__%27%2C%20%0A%27__Priority__%27%2C%20%0A%27__NEWLINE__%27%2C%20%0A%27%27%2C%20%0A%27%3Csmall%3E__Requestors__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__CreatedRelative__%3C%2Fsmall%3E%27%2C';
+
+ if ( defined($priority) && $field ) {
+ $href .= '%20%0A%27__-__%27%2C';
+ }
+
+ $href .= '%20%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27';
+
+ #$href =
+ #uri_escape($href);
+
+ $self->baseurl. $href;
+
+}
+
+sub href_params_new_ticket {
+ my( $self, $custnum_or_cust_main, $requestors ) = @_;
+
+ my( $custnum, $cust_main );
+ if ( ref($custnum_or_cust_main) ) {
+ $cust_main = $custnum_or_cust_main;
+ $custnum = $cust_main->custnum;
+ } else {
+ $custnum = $custnum_or_cust_main;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ }
+
+ my %param = (
+ 'Queue' => ($cust_main->agent->ticketing_queueid || $default_queueid),
+ 'new-MemberOf'=> "freeside://freeside/cust_main/$custnum",
+ 'Requestors' => $requestors,
+ );
+
+ ( $self->baseurl.'Ticket/Create.html', %param );
+}
+
+sub href_new_ticket {
+ my $self = shift;
+
+ my( $base, %param ) = $self->href_params_new_ticket(@_);
+
+ my $uri = new URI $base;
+ $uri->query_form(%param);
+ $uri;
+
+}
+
+sub href_ticket {
+ my($self, $ticketnum) = @_;
+ $self->baseurl. 'Ticket/Display.html?id='.$ticketnum;
+}
+
+sub queues {
+ my($self) = @_;
+
+ my $sql = "SELECT id, Name FROM Queues WHERE Disabled = 0";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
+ $sth->execute() or die $sth->errstr. " executing $sql";
+
+ map { $_->[0] => $_->[1] } @{ $sth->fetchall_arrayref([]) };
+
+}
+
+sub queue {
+ my($self, $queueid) = @_;
+
+ return '' unless $queueid;
+
+ my $sql = "SELECT Name FROM Queues WHERE id = ?";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. " preparing $sql";
+ $sth->execute($queueid) or die $sth->errstr. " executing $sql";
+
+ my $rows = $sth->fetchrow_arrayref;
+ $rows ? $rows->[0] : '';
+
+}
+
+sub baseurl {
+ #my $self = shift;
+ $external_url. '/';
+}
+
+sub _retrieve_single_value {
+ my( $self, $sql ) = @_;
+
+ warn "$me $sql" if $DEBUG;
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql";
+ $sth->execute or die $sth->errstr. "executing $sql";
+
+ my $arrayref = $sth->fetchrow_arrayref;
+ $arrayref ? $arrayref->[0] : $arrayref;
+}
+
+sub transaction_creator {
+ my( $self, $transaction_id ) = @_;
+
+ my $sql = "SELECT Name FROM Transactions JOIN Users ON ".
+ "Transactions.Creator=Users.id WHERE Transactions.id = ".
+ $transaction_id;
+
+ $self->_retrieve_single_value($sql);
+}
+
+sub transaction_ticketid {
+ my( $self, $transaction_id ) = @_;
+
+ my $sql = "SELECT ObjectId FROM Transactions WHERE Transactions.id = ".
+ $transaction_id;
+
+ $self->_retrieve_single_value($sql);
+}
+
+sub transaction_subject {
+ my( $self, $transaction_id ) = @_;
+
+ my $sql = "SELECT Subject FROM Transactions JOIN Tickets ON ObjectId=".
+ "Tickets.id WHERE Transactions.id = ". $transaction_id;
+
+ $self->_retrieve_single_value($sql);
+}
+
+sub transaction_status {
+ my( $self, $transaction_id ) = @_;
+
+ my $sql = "SELECT Status FROM Transactions JOIN Tickets ON ObjectId=".
+ "Tickets.id WHERE Transactions.id = ". $transaction_id;
+
+ $self->_retrieve_single_value($sql);
+}
+
+sub access_right {
+ warn "WARNING: no access rights available w/ external RT";
+ 0;
+}
+
+sub create_ticket {
+ return 'create_ticket unimplemented w/external RT (write something w/RT::Client::REST?)';
+}
+
+1;
+
diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm
new file mode 100644
index 0000000..f47648e
--- /dev/null
+++ b/FS/FS/TicketSystem/RT_Internal.pm
@@ -0,0 +1,314 @@
+package FS::TicketSystem::RT_Internal;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use Data::Dumper;
+use MIME::Entity;
+use FS::UID qw(dbh);
+use FS::CGI qw(popurl);
+use FS::TicketSystem::RT_Libs;
+use RT::CurrentUser;
+
+@ISA = qw( FS::TicketSystem::RT_Libs );
+
+$DEBUG = 0;
+$me = '[FS::TicketSystem::RT_Internal]';
+
+sub sql_num_customer_tickets {
+ "( select count(*) from Tickets
+ join Links on ( Tickets.id = Links.LocalBase )
+ where ( Status = 'new' or Status = 'open' or Status = 'stalled' )
+ and Target = 'freeside://freeside/cust_main/' || custnum
+ )";
+}
+
+sub baseurl {
+ #my $self = shift;
+ if ( $RT::URI::freeside::URL ) {
+ $RT::URI::freeside::URL. '/rt/';
+ } else {
+ 'http://you_need_to_set_RT_URI_freeside_URL_in_SiteConfig.pm/';
+ }
+}
+
+#mapping/genericize??
+#ShowConfigTab ModifySelf
+sub access_right {
+ my( $self, $session, $right ) = @_;
+
+ #return '' unless $conf->config('ticket_system');
+ return '' unless FS::Conf->new->config('ticket_system');
+
+ $session = $self->session($session);
+
+ #warn "$me access_right: CurrentUser ". $session->{'CurrentUser'}. ":\n".
+ # ( $DEBUG>1 ? Dumper($session->{'CurrentUser'}) : '' )
+ # if $DEBUG > 1;
+
+ $session->{'CurrentUser'}->HasRight( Right => $right,
+ Object => $RT::System );
+}
+
+sub session {
+ my( $self, $session ) = @_;
+
+ if ( $session && $session->{'Current_User'} ) {
+ warn "$me session: using existing session and CurrentUser: \n".
+ Dumper($session->{'CurrentUser'})
+ if $DEBUG;
+ } else {
+ warn "$me session: loading session and CurrentUser\n" if $DEBUG > 1;
+ $session = $self->_web_external_auth($session);
+ }
+
+ $session;
+}
+
+sub init {
+ my $self = shift;
+
+ warn "$me init: loading RT libraries\n" if $DEBUG;
+ eval '
+ use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
+ use RT;
+ #it looks like the rest are taken care of these days in RT::InitClasses
+ #use RT::Ticket;
+ #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::ObjectCustomFieldValues;
+
+ #for web external auth...
+ use RT::Interface::Web;
+ ';
+ die $@ if $@;
+
+ warn "$me init: loading RT config\n" if $DEBUG;
+ {
+ local $SIG{__DIE__};
+ eval 'RT::LoadConfig();';
+ }
+ die $@ if $@;
+
+ warn "$me init: initializing RT\n" if $DEBUG;
+ {
+ local $SIG{__DIE__};
+ eval 'RT::Init("NoSignalHandlers"=>1);';
+ }
+ die $@ if $@;
+
+ warn "$me init: complete" if $DEBUG;
+}
+
+=item create_ticket SESSION_HASHREF, OPTION => VALUE ...
+
+Class method. Creates a ticket. If there is an error, returns the scalar
+error, otherwise returns the newly created RT::Ticket object.
+
+Accepts the following options:
+
+=over 4
+
+=item queue
+
+Queue name or Id
+
+=item subject
+
+Ticket subject
+
+=item requestor
+
+Requestor email address or arrayref of addresses
+
+=item cc
+
+Cc: email address or arrayref of addresses
+
+=item message
+
+Ticket message
+
+=item mime_type
+
+MIME type to use for message. Defaults to text/plain. Specifying text/html
+can be useful to use HTML markup in message.
+
+=item custnum
+
+Customer number (see L<FS::cust_main>) to associate with ticket.
+
+=item svcnum
+
+Service number (see L<FS::cust_svc>) to associate with ticket. Will also
+associate the customer who has this service (unless the service is unlinked).
+
+=back
+
+=cut
+
+sub create_ticket {
+ my($self, $session, %param) = @_;
+
+ $session = $self->session($session);
+
+ my $Queue = RT::Queue->new($session->{'CurrentUser'});
+ $Queue->Load( $param{'queue'} );
+
+ my $req = ref($param{'requestor'})
+ ? $param{'requestor'}
+ : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
+
+ my $cc = ref($param{'cc'})
+ ? $param{'cc'}
+ : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
+
+ my $mimeobj = MIME::Entity->build(
+ 'Data' => $param{'message'},
+ 'Type' => ( $param{'mime_type'} || 'text/plain' ),
+ );
+
+ my %ticket = (
+ 'Queue' => $Queue->Id,
+ 'Subject' => $param{'subject'},
+ 'Requestor' => $req,
+ 'Cc' => $cc,
+ 'MIMEObj' => $mimeobj,
+ );
+ warn Dumper(\%ticket) if $DEBUG > 1;
+
+ my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
+ my( $id, $Transaction, $ErrStr );
+ {
+ local $SIG{__DIE__};
+ ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
+ }
+ return $ErrStr if $id == 0;
+
+ warn "ticket got id $id\n" if $DEBUG;
+
+ #XXX check errors adding custnum/svcnum links (put it in a transaction)...
+ # but we do already know they're good
+
+ if ( $param{'custnum'} ) {
+ my( $val, $msg ) = $Ticket->_AddLink(
+ 'Type' => 'MemberOf',
+ 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
+ );
+ }
+
+ if ( $param{'svcnum'} ) {
+ my( $val, $msg ) = $Ticket->_AddLink(
+ 'Type' => 'MemberOf',
+ 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
+ );
+ }
+
+ $Ticket;
+}
+
+#shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
+# to get logged into RT from afar
+sub _web_external_auth {
+ my( $self, $session ) = @_;
+
+ my $user = $FS::CurrentUser::CurrentUser->username;
+
+ $session ||= {};
+ $session->{'CurrentUser'} = RT::CurrentUser->new();
+
+ warn "$me _web_external_auth loading RT user for $user\n"
+ if $DEBUG > 1;
+
+ $session->{'CurrentUser'}->Load($user);
+
+ if ( ! $session->{'CurrentUser'}->Id() ) {
+
+ # Create users on-the-fly
+
+ warn "can't load RT user for $user; auto-creating\n"
+ if $DEBUG;
+
+ my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
+
+ 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'
+ )
+ {
+ #uhh, wrong root
+ #$m->comp( '/Elements/Callback', %ARGS,
+ # _CallbackName => 'NewUser' );
+
+ 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'};
+
+ die "can't auto-create RT user"; #an error message would be nice :/
+ #$m->abort() unless $RT::WebFallbackToInternalAuth;
+ #$m->comp( '/Elements/Login', %ARGS,
+ # Error => loc( 'Cannot create user: [_1]', $msg ) );
+ }
+ }
+
+ unless ( $session->{'CurrentUser'}->Id() ) {
+ delete $session->{'CurrentUser'};
+
+ die "can't auto-create RT user";
+ #$user = $orig_user;
+ #
+ #if ($RT::WebExternalOnly) {
+ # $m->comp( '/Elements/Login', %ARGS,
+ # Error => loc('You are not an authorized user') );
+ # $m->abort();
+ #}
+ }
+
+ $session;
+
+}
+
+1;
+
diff --git a/FS/FS/TicketSystem/RT_Libs.pm b/FS/FS/TicketSystem/RT_Libs.pm
new file mode 100644
index 0000000..aebe8c5
--- /dev/null
+++ b/FS/FS/TicketSystem/RT_Libs.pm
@@ -0,0 +1,10 @@
+package FS::TicketSystem::RT_Libs;
+
+use strict;
+use vars qw( @ISA );
+use FS::TicketSystem::RT_External;
+
+@ISA = qw( FS::TicketSystem::RT_External );
+
+1;
+
diff --git a/FS/FS/Tron.pm b/FS/FS/Tron.pm
new file mode 100644
index 0000000..78af0fe
--- /dev/null
+++ b/FS/FS/Tron.pm
@@ -0,0 +1,123 @@
+package FS::Tron;
+# a program to monitor outside systems
+
+use strict;
+use warnings;
+use base 'Exporter';
+use Net::SSH qw( sshopen2 ); #sshopen3 );
+use FS::Record qw( qsearchs );
+use FS::svc_external;
+use FS::cust_svc_option;
+
+our @EXPORT_OK = qw( tron_ping tron_scan tron_lint);
+
+our %desired = (
+ #less lenient, we need to make sure we upgrade deb 4 & pg 7.4
+ 'freeside_version' => qr/^1\.(7\.3|9\.0)/,
+ 'debian_version' => qr/^5/, #qr/^5.0.[2-9]$/ #qr/^4/,
+ 'apache_mpm' => qw/^(Prefork|$)/,
+ 'pg_version' => qr/^8\.[1-9]/,
+ 'apache_version' => qr/^2/,
+
+ #payment gateway survey
+# 'payment_gateway' => qw/^authorizenet$/,
+
+ #stuff to add/replace later
+ #'apache_mpm' => qw/^Prefork/,
+ #'pg_version' => qr/^8\.[3-9]/,
+);
+
+sub _cust_svc_external {
+ my $cust_svc_or_svcnum = shift;
+
+ my ( $cust_svc, $svc_external );
+ if ( ref($cust_svc_or_svcnum) ) {
+ $cust_svc = $cust_svc_or_svcnum;
+ $svc_external = $cust_svc->svc_x;
+ } else {
+ $svc_external = qsearchs('svc_external', { svcnum=>$cust_svc_or_svcnum } );
+ $cust_svc = $svc_external->cust_svc;
+ }
+
+ ( $cust_svc, $svc_external );
+
+}
+
+sub tron_ping {
+ my( $cust_svc, $svc_external ) = _cust_svc_external(shift);
+
+ my %hash = ();
+ my $machine = $svc_external->title; # or better as a cust_svc_option??
+ sshopen2($machine, *READER, *WRITER, '/bin/echo pong');
+ my $pong = scalar(<READER>);
+ close READER;
+ close WRITER;
+
+ $pong =~ /pong/;
+}
+
+sub tron_scan {
+ my( $cust_svc, $svc_external ) = _cust_svc_external(shift);
+
+ #don't scan again if things are okay
+ my $bad = 0;
+ foreach my $option ( keys %desired ) {
+ my $current = $cust_svc->option($option);
+ $bad++ unless $current =~ $desired{$option};
+ }
+ return '' unless $bad;
+
+ #do the scan
+ my %hash = ();
+ my $machine = $svc_external->title; # or better as a cust_svc_option??
+ #sshopen2($machine, *READER, *WRITER, '/usr/local/bin/freeside-yori all');
+ #fix freeside users' patch if necessary, since packages put this in /usr/bin
+ sshopen2($machine, *READER, *WRITER, 'freeside-yori all');
+ while (<READER>) {
+ chomp;
+ my($option, $value) = split(/: ?/);
+ next unless defined($option) && exists($desired{$option});
+ $hash{$option} = $value;
+ }
+ close READER;
+ close WRITER;
+
+ unless ( keys %hash ) {
+ return "error scanning $machine\n";
+ }
+
+ # store the results
+ foreach my $option ( keys %hash ) {
+ my %opthash = ( 'optionname' => $option,
+ 'svcnum' => $cust_svc->svcnum,
+ );
+ my $cust_svc_option = qsearchs('cust_svc_option', \%opthash )
+ || new FS::cust_svc_option \%opthash;
+ next if $cust_svc_option->optionvalue eq $hash{$option};
+ $cust_svc_option->optionvalue( $hash{$option} );
+ my $error = $cust_svc_option->optionnum
+ ? $cust_svc_option->replace
+ : $cust_svc_option->insert;
+ return $error if $error;
+ }
+
+ '';
+
+}
+
+sub tron_lint {
+ my $cust_svc = shift;
+
+ my @lint;
+ foreach my $option ( keys %desired ) {
+ my $current = $cust_svc->option($option);
+ push @lint, "$option is $current" unless $current =~ $desired{$option};
+ }
+
+ push @lint, 'unchecked' unless scalar($cust_svc->options);
+
+ @lint;
+
+}
+
+1;
diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm
new file mode 100644
index 0000000..821b191
--- /dev/null
+++ b/FS/FS/UI/Web.pm
@@ -0,0 +1,640 @@
+package FS::UI::Web;
+
+use strict;
+use vars qw($DEBUG @ISA @EXPORT_OK $me);
+use Exporter;
+use FS::Conf;
+use FS::Misc::DateTime qw( parse_datetime );
+use FS::Record qw(dbdef);
+use FS::cust_main; # are sql_balance and sql_date_balance in the right module?
+
+#use vars qw(@ISA);
+#use FS::UI
+#@ISA = qw( FS::UI );
+@ISA = qw( Exporter );
+
+@EXPORT_OK = qw( svc_url );
+
+$DEBUG = 0;
+$me = '[FS::UID::Web]';
+
+###
+# date parsing
+###
+
+use Date::Parse;
+sub parse_beginning_ending {
+ my($cgi, $prefix) = @_;
+ $prefix .= '_' if $prefix;
+
+ my $beginning = 0;
+ if ( $cgi->param($prefix.'begin') =~ /^(\d+)$/ ) {
+ $beginning = $1;
+ } elsif ( $cgi->param($prefix.'beginning') =~ /^([ 0-9\-\/]{1,64})$/ ) {
+ $beginning = parse_datetime($1) || 0;
+ }
+
+ my $ending = 4294967295; #2^32-1
+ if ( $cgi->param($prefix.'end') =~ /^(\d+)$/ ) {
+ $ending = $1 - 1;
+ } elsif ( $cgi->param($prefix.'ending') =~ /^([ 0-9\-\/]{1,64})$/ ) {
+ #probably need an option to turn off the + 86399
+ $ending = parse_datetime($1) + 86399;
+ }
+
+ ( $beginning, $ending );
+}
+
+=item svc_url
+
+Returns a service URL, first checking to see if there is a service-specific
+page to link to, otherwise to a generic service handling page. Options are
+passed as a list of name-value pairs, and include:
+
+=over 4
+
+=item * m - Mason request object ($m)
+
+=item * action - The action for which to construct "edit", "view", or "search"
+
+=item ** part_svc - Service definition (see L<FS::part_svc>)
+
+=item ** svcdb - Service table
+
+=item *** query - Query string
+
+=item *** svc - FS::cust_svc or FS::svc_* object
+
+=item ahref - Optional flag, if set true returns <A HREF="$url"> instead of just the URL.
+
+=back
+
+* Required fields
+
+** part_svc OR svcdb is required
+
+*** query OR svc is required
+
+=cut
+
+ # ##
+ # #required
+ # ##
+ # 'm' => $m, #mason request object
+ # 'action' => 'edit', #or 'view'
+ #
+ # 'part_svc' => $part_svc, #usual
+ # #OR
+ # 'svcdb' => 'svc_table',
+ #
+ # 'query' => #optional query string
+ # # (pass a blank string if you want a "raw" URL to add your
+ # # own svcnum to)
+ # #OR
+ # 'svc' => $svc_x, #or $cust_svc, it just needs a svcnum
+ #
+ # ##
+ # #optional
+ # ##
+ # 'ahref' => 1, # if set true, returns <A HREF="$url">
+
+use FS::CGI qw(rooturl);
+sub svc_url {
+ my %opt = @_;
+
+ #? return '' unless ref($opt{part_svc});
+
+ my $svcdb = $opt{svcdb} || $opt{part_svc}->svcdb;
+ my $query = exists($opt{query}) ? $opt{query} : $opt{svc}->svcnum;
+ my $url;
+ warn "$me [svc_url] checking for /$opt{action}/$svcdb.cgi component"
+ if $DEBUG;
+ if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
+ $url = "$svcdb.cgi?";
+ } else {
+
+ my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
+
+ $url = "$generic.html?svcdb=$svcdb;";
+ $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
+ }
+
+ import FS::CGI 'rooturl'; #WTF! why is this necessary
+ my $return = rooturl(). "$opt{action}/$url$query";
+
+ $return = qq!<A HREF="$return">! if $opt{ahref};
+
+ $return;
+}
+
+sub svc_link {
+ my($m, $part_svc, $cust_svc) = @_ or return '';
+ svc_X_link( $part_svc->svc, @_ );
+}
+
+sub svc_label_link {
+ my($m, $part_svc, $cust_svc) = @_ or return '';
+ svc_X_link( ($cust_svc->label)[1], @_ );
+}
+
+sub svc_X_link {
+ my ($x, $m, $part_svc, $cust_svc) = @_ or return '';
+
+ return $x
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+ my $ahref = svc_url(
+ 'ahref' => 1,
+ 'm' => $m,
+ 'action' => 'view',
+ 'part_svc' => $part_svc,
+ 'svc' => $cust_svc,
+ );
+
+ "$ahref$x</A>";
+}
+
+#this probably needs an ACL too...
+sub svc_export_links {
+ my ($m, $part_svc, $cust_svc) = @_ or return '';
+
+ my $ahref = $cust_svc->export_links;
+
+ join('', @$ahref);
+}
+
+sub parse_lt_gt {
+ my($cgi, $field) = @_;
+
+ my @search = ();
+
+ my %op = (
+ 'lt' => '<',
+ 'gt' => '>',
+ );
+
+ foreach my $op (keys %op) {
+
+ warn "checking for ${field}_$op field\n"
+ if $DEBUG;
+
+ if ( $cgi->param($field."_$op") =~ /^\s*\$?\s*(-?[\d\,\s]+(\.\d\d)?)\s*$/ ) {
+
+ my $num = $1;
+ $num =~ s/[\,\s]+//g;
+ my $search = "$field $op{$op} $num";
+ push @search, $search;
+
+ warn "found ${field}_$op field; adding search element $search\n"
+ if $DEBUG;
+ }
+
+ }
+
+ @search;
+
+}
+
+###
+# cust_main report subroutines
+###
+
+
+=item cust_header [ CUST_FIELDS_VALUE ]
+
+Returns an array of customer information headers according to the supplied
+customer fields value, or if no value is supplied, the B<cust-fields>
+configuration value.
+
+=cut
+
+use vars qw( @cust_fields @cust_colors @cust_styles @cust_aligns );
+
+sub cust_header {
+
+ warn "FS::UI:Web::cust_header called"
+ if $DEBUG;
+
+ my $conf = new FS::Conf;
+
+ my %header2method = (
+ 'Customer' => 'name',
+ 'Cust. Status' => 'ucfirst_cust_status',
+ 'Cust#' => 'custnum',
+ 'Name' => 'contact',
+ 'Company' => 'company',
+ '(bill) Customer' => 'name',
+ '(service) Customer' => 'ship_name',
+ '(bill) Name' => 'contact',
+ '(service) Name' => 'ship_contact',
+ '(bill) Company' => 'company',
+ '(service) Company' => 'ship_company',
+ 'Address 1' => 'address1',
+ 'Address 2' => 'address2',
+ 'City' => 'city',
+ 'State' => 'state',
+ 'Zip' => 'zip',
+ 'Country' => 'country_full',
+ 'Day phone' => 'daytime', # XXX should use msgcat, but how?
+ 'Night phone' => 'night', # XXX should use msgcat, but how?
+ 'Fax number' => 'fax',
+ '(bill) Address 1' => 'address1',
+ '(bill) Address 2' => 'address2',
+ '(bill) City' => 'city',
+ '(bill) State' => 'state',
+ '(bill) Zip' => 'zip',
+ '(bill) Country' => 'country_full',
+ '(bill) Day phone' => 'daytime', # XXX should use msgcat, but how?
+ '(bill) Night phone' => 'night', # XXX should use msgcat, but how?
+ '(bill) Fax number' => 'fax',
+ '(service) Address 1' => 'ship_address1',
+ '(service) Address 2' => 'ship_address2',
+ '(service) City' => 'ship_city',
+ '(service) State' => 'ship_state',
+ '(service) Zip' => 'ship_zip',
+ '(service) Country' => 'ship_country_full',
+ '(service) Day phone' => 'ship_daytime', # XXX should use msgcat, how?
+ '(service) Night phone' => 'ship_night', # XXX should use msgcat, how?
+ '(service) Fax number' => 'ship_fax',
+ 'Invoicing email(s)' => 'invoicing_list_emailonly_scalar',
+ 'Payment Type' => 'payby',
+ 'Current Balance' => 'current_balance',
+ );
+ $header2method{'Cust#'} = 'display_custnum'
+ if $conf->exists('cust_main-default_agent_custid');
+
+ my %header2colormethod = (
+ 'Cust. Status' => 'cust_statuscolor',
+ );
+ my %header2style = (
+ 'Cust. Status' => 'b',
+ );
+ my %header2align = (
+ 'Cust. Status' => 'c',
+ 'Cust#' => 'r',
+ );
+
+ my $cust_fields;
+ my @cust_header;
+ if ( @_ && $_[0] ) {
+
+ warn " using supplied cust-fields override".
+ " (ignoring cust-fields config file)"
+ if $DEBUG;
+ $cust_fields = shift;
+
+ } else {
+
+ if ( $conf->exists('cust-fields')
+ && $conf->config('cust-fields') =~ /^([\w\. \|\#\(\)]+):?/
+ )
+ {
+ warn " found cust-fields configuration value"
+ if $DEBUG;
+ $cust_fields = $1;
+ } else {
+ warn " no cust-fields configuration value found; using default 'Cust. Status | Customer'"
+ if $DEBUG;
+ $cust_fields = 'Cust. Status | Customer';
+ }
+
+ }
+
+ @cust_header = split(/ \| /, $cust_fields);
+ @cust_fields = map { $header2method{$_} || $_ } @cust_header;
+ @cust_colors = map { exists $header2colormethod{$_}
+ ? $header2colormethod{$_}
+ : ''
+ }
+ @cust_header;
+ @cust_styles = map { exists $header2style{$_} ? $header2style{$_} : '' }
+ @cust_header;
+ @cust_aligns = map { exists $header2align{$_} ? $header2align{$_} : 'l' }
+ @cust_header;
+
+ #my $svc_x = shift;
+ @cust_header;
+}
+
+=item cust_sql_fields [ CUST_FIELDS_VALUE ]
+
+Returns a list of fields for the SELECT portion of an SQL query.
+
+As with L<the cust_header subroutine|/cust_header>, the fields returned are
+defined by the supplied customer fields setting, or if no customer fields
+setting is supplied, the <B>cust-fields</B> configuration value.
+
+=cut
+
+sub cust_sql_fields {
+
+ my @fields = qw( last first company );
+ push @fields, map "ship_$_", @fields;
+ push @fields, 'country';
+
+ cust_header(@_);
+ #inefficientish, but tiny lists and only run once per page
+
+ my @add_fields = qw( address1 address2 city state zip daytime night fax );
+ push @fields,
+ grep { my $field = $_; grep { $_ eq $field } @cust_fields }
+ ( @add_fields, ( map "ship_$_", @add_fields ), 'payby' );
+
+ push @fields, 'agent_custid';
+
+ my @extra_fields = ();
+ if (grep { $_ eq 'current_balance' } @cust_fields) {
+ push @extra_fields, FS::cust_main->balance_sql . " AS current_balance";
+ }
+
+ map("cust_main.$_", @fields), @extra_fields;
+}
+
+=item cust_fields OBJECT [ CUST_FIELDS_VALUE ]
+
+Given an object that contains fields from cust_main (say, from a
+JOINed search. See httemplate/search/svc_* for examples), returns an array
+of customer information, or "(unlinked)" if this service is not linked to a
+customer.
+
+As with L<the cust_header subroutine|/cust_header>, the fields returned are
+defined by the supplied customer fields setting, or if no customer fields
+setting is supplied, the <B>cust-fields</B> configuration value.
+
+=cut
+
+
+sub cust_fields {
+ my $record = shift;
+ warn "FS::UI::Web::cust_fields called for $record ".
+ "(cust_fields: @cust_fields)"
+ if $DEBUG > 1;
+
+ #cust_header(@_) unless @cust_fields; #now need to cache to keep cust_fields
+ # #override incase we were passed as a sub
+
+ my $seen_unlinked = 0;
+
+ map {
+ if ( $record->custnum ) {
+ warn " $record -> $_" if $DEBUG > 1;
+ $record->$_(@_);
+ } else {
+ warn " ($record unlinked)" if $DEBUG > 1;
+ $seen_unlinked++ ? '' : '(unlinked)';
+ }
+ } @cust_fields;
+}
+
+=item cust_fields_subs
+
+Returns an array of subroutine references for returning customer field values.
+This is similar to cust_fields, but returns each field's sub as a distinct
+element.
+
+=cut
+
+sub cust_fields_subs {
+ my $unlinked_warn = 0;
+ return map {
+ my $f = $_;
+ if( $unlinked_warn++ ) {
+ sub {
+ my $record = shift;
+ if( $record->custnum ) {
+ $record->$f(@_);
+ }
+ else {
+ '(unlinked)'
+ };
+ }
+ }
+ else {
+ sub {
+ my $record = shift;
+ $record->$f(@_) if $record->custnum;
+ }
+ }
+ } @cust_fields;
+}
+
+=item cust_colors
+
+Returns an array of subroutine references (or empty strings) for returning
+customer information colors.
+
+As with L<the cust_header subroutine|/cust_header>, the fields returned are
+defined by the supplied customer fields setting, or if no customer fields
+setting is supplied, the <B>cust-fields</B> configuration value.
+
+=cut
+
+sub cust_colors {
+ map {
+ my $method = $_;
+ if ( $method ) {
+ sub { shift->$method(@_) };
+ } else {
+ '';
+ }
+ } @cust_colors;
+}
+
+=item cust_styles
+
+Returns an array of customer information styles.
+
+As with L<the cust_header subroutine|/cust_header>, the fields returned are
+defined by the supplied customer fields setting, or if no customer fields
+setting is supplied, the <B>cust-fields</B> configuration value.
+
+=cut
+
+sub cust_styles {
+ map {
+ if ( $_ ) {
+ $_;
+ } else {
+ '';
+ }
+ } @cust_styles;
+}
+
+=item cust_aligns
+
+Returns an array or scalar (depending on context) of customer information
+alignments.
+
+As with L<the cust_header subroutine|/cust_header>, the fields returned are
+defined by the supplied customer fields setting, or if no customer fields
+setting is supplied, the <B>cust-fields</B> configuration value.
+
+=cut
+
+sub cust_aligns {
+ if ( wantarray ) {
+ @cust_aligns;
+ } else {
+ join('', @cust_aligns);
+ }
+}
+
+###
+# begin JSRPC code...
+###
+
+package FS::UI::Web::JSRPC;
+
+use strict;
+use vars qw($DEBUG);
+use Carp;
+use Storable qw(nfreeze);
+use MIME::Base64;
+use JSON;
+use FS::UID qw(getotaker);
+use FS::Record qw(qsearchs);
+use FS::queue;
+
+$DEBUG = 0;
+
+sub new {
+ my $class = shift;
+ my $self = {
+ env => {},
+ job => shift,
+ cgi => shift,
+ };
+
+ bless $self, $class;
+
+ croak "CGI object required as second argument" unless $self->{'cgi'};
+
+ return $self;
+}
+
+sub process {
+
+ my $self = shift;
+
+ my $cgi = $self->{'cgi'};
+
+ # XXX this should parse JSON foo and build a proper data structure
+ my @args = $cgi->param('arg');
+
+ #work around konqueror bug!
+ @args = map { s/\x00$//; $_; } @args;
+
+ my $sub = $cgi->param('sub'); #????
+
+ warn "FS::UI::Web::JSRPC::process:\n".
+ " cgi=$cgi\n".
+ " sub=$sub\n".
+ " args=".join(', ',@args)."\n"
+ if $DEBUG;
+
+ if ( $sub eq 'start_job' ) {
+
+ $self->start_job(@args);
+
+ } elsif ( $sub eq 'job_status' ) {
+
+ $self->job_status(@args);
+
+ } else {
+
+ die "unknown sub $sub";
+
+ }
+
+}
+
+sub start_job {
+ my $self = shift;
+
+ warn "FS::UI::Web::start_job: ". join(', ', @_) if $DEBUG;
+# my %param = @_;
+ my %param = ();
+ while ( @_ ) {
+ my( $field, $value ) = splice(@_, 0, 2);
+ unless ( exists( $param{$field} ) ) {
+ $param{$field} = $value;
+ } elsif ( ! ref($param{$field}) ) {
+ $param{$field} = [ $param{$field}, $value ];
+ } else {
+ push @{$param{$field}}, $value;
+ }
+ }
+ $param{CurrentUser} = getotaker();
+ warn "FS::UI::Web::start_job\n".
+ join('', map {
+ if ( ref($param{$_}) ) {
+ " $_ => [ ". join(', ', @{$param{$_}}). " ]\n";
+ } else {
+ " $_ => $param{$_}\n";
+ }
+ } keys %param )
+ if $DEBUG;
+
+ #first get the CGI params shipped off to a job ASAP so an id can be returned
+ #to the caller
+
+ my $job = new FS::queue { 'job' => $self->{'job'} };
+
+ #too slow to insert all the cgi params as individual args..,?
+ #my $error = $queue->insert('_JOB', $cgi->Vars);
+
+ #warn 'froze string of size '. length(nfreeze(\%param)). " for job args\n"
+ # if $DEBUG;
+
+ my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) );
+
+ if ( $error ) {
+
+ warn "job not inserted: $error\n"
+ if $DEBUG;
+
+ $error; #this doesn't seem to be handled well,
+ # will trigger "illegal jobnum" below?
+ # (should never be an error inserting the job, though, only thing
+ # would be Pg f%*kage)
+ } else {
+
+ warn "job inserted successfully with jobnum ". $job->jobnum. "\n"
+ if $DEBUG;
+
+ $job->jobnum;
+ }
+
+}
+
+sub job_status {
+ my( $self, $jobnum ) = @_; #$url ???
+
+ sleep 1; # XXX could use something better...
+
+ my $job;
+ if ( $jobnum =~ /^(\d+)$/ ) {
+ $job = qsearchs('queue', { 'jobnum' => $jobnum } );
+ } else {
+ die "FS::UI::Web::job_status: illegal jobnum $jobnum\n";
+ }
+
+ my @return;
+ if ( $job && $job->status ne 'failed' ) {
+ my ($progress, $action) = split ',', $job->statustext, 2;
+ $action ||= 'Server processing job';
+ @return = ( 'progress', $progress, $action );
+ } elsif ( !$job ) { #handle job gone case : job successful
+ # so close popup, redirect parent window...
+ @return = ( 'complete' );
+ } else {
+ @return = ( 'error', $job ? $job->statustext : $jobnum );
+ }
+
+ #to_json(\@return); #waiting on deb 5.0 for new JSON.pm?
+ objToJson(\@return);
+
+}
+
+1;
+
diff --git a/FS/FS/UI/Web/small_custview.pm b/FS/FS/UI/Web/small_custview.pm
new file mode 100644
index 0000000..f8e2020
--- /dev/null
+++ b/FS/FS/UI/Web/small_custview.pm
@@ -0,0 +1,129 @@
+package FS::UI::Web::small_custview;
+
+use strict;
+use vars qw(@EXPORT_OK @ISA);
+use Exporter;
+use FS::Msgcat;
+use FS::Record qw(qsearchs);
+use FS::cust_main;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( small_custview );
+
+=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT, NOBALANCE_FLAG, URL
+
+Sheesh. I did switch to mason, but this is still hanging around. Figure out
+some better way to sling mason components to self-service & RT.
+
+=cut
+
+sub small_custview {
+
+ my $arg = shift;
+ my $countrydefault = shift || 'US';
+ my $nobalance = shift;
+ my $url = shift;
+
+ my $cust_main = ref($arg) ? $arg
+ : qsearchs('cust_main', { 'custnum' => $arg } )
+ or die "unknown custnum $arg";
+
+ my $html;
+
+ $html = qq!View <A HREF="$url?! . $cust_main->custnum . '">'
+ if $url;
+
+ $html .= 'Customer #<B>'. $cust_main->display_custnum. '</B></A>'.
+ ' - <B><FONT COLOR="#'. $cust_main->statuscolor. '">'.
+ ucfirst($cust_main->status). '</FONT></B>'.
+ ntable('#e8e8e8'). '<TR><TD VALIGN="top">'. 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 ) {
+ $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 VALIGN="top">'. 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}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>'
+ unless $nobalance;
+
+ # last payment might be good here too?
+
+ $html;
+}
+
+#bah. don't want to pull in all of FS::CGI, that's the whole problem in the
+#first place
+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">';
+ }
+
+}
+
+1;
+
diff --git a/FS/FS/UI/bytecount.pm b/FS/FS/UI/bytecount.pm
new file mode 100644
index 0000000..7e78bf5
--- /dev/null
+++ b/FS/FS/UI/bytecount.pm
@@ -0,0 +1,101 @@
+package FS::UI::bytecount;
+
+use strict;
+use vars qw($DEBUG $me @ISA @EXPORT_OK);
+use Exporter;
+use FS::Conf;
+use Number::Format 1.50;
+
+@ISA = qw( Exporter );
+
+@EXPORT_OK = qw( bytecount_unexact parse_bytecount display_bytecount );
+
+$DEBUG = 0;
+$me = '[FS::UID::bytecount]';
+
+=head1 NAME
+
+FS::UI::bytecount - Subroutines for parsing and displaying byte counters
+
+=head1 SYNOPSIS
+
+ use FS::UI::bytecount;
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item bytecount_unexact COUNT
+
+Returns a two decimal place value for COUNT followed by bytes, Kbytes, Mbytes,
+or GBytes as appropriate.
+
+=cut
+
+sub bytecount_unexact {
+ my $bc = shift;
+ return("$bc bytes")
+ if ($bc < 1000);
+ return(sprintf("%.2f Kbytes", $bc/1024))
+ if ($bc < 1048576);
+ return(sprintf("%.2f Mbytes", $bc/1048576))
+ if ($bc < 1073741824);
+ return(sprintf("%.2f Gbytes", $bc/1073741824));
+}
+
+=item parse_bytecount AMOUNT
+
+Accepts a number (digits and a decimal point) possibly followed by k, m, g, or
+t (and an optional 'b') in either case. Returns a pure number representing
+the input or the input itself if unparsable. Discards commas as noise.
+
+=cut
+
+sub parse_bytecount {
+ my $bc = shift;
+ return $bc if (($bc =~ tr/.//) > 1);
+ $bc =~ /^\s*([,\d.]*)\s*([kKmMgGtT]?)[bB]?\s*$/ or return $bc;
+ my $base = $1;
+ $base =~ tr/,//d;
+ return $bc unless length $base;
+ my $exponent = index ' kmgt', lc($2);
+ return $bc if ($exponent < 0 && $2);
+ $exponent = 0 if ($exponent < 0);
+ return int($base * 1024 ** $exponent); #bytecounts are integer values
+}
+
+=item display_bytecount AMOUNT
+
+Converts a pure number to a value followed possibly followed by k, m, g, or
+t via Number::Format
+
+=cut
+
+sub display_bytecount {
+ my $bc = shift;
+ return $bc unless ($bc =~ /^(\d+)$/);
+ my $conf = new FS::Conf;
+ my $f = new Number::Format;
+ my $precision = ( $conf->exists('datavolume-significantdigits') &&
+ $conf->config('datavolume-significantdigits') =~ /^\s*\d+\s*$/ )
+ ? $conf->config('datavolume-significantdigits')
+ : 3;
+ my $unit = $conf->exists('datavolume-forcemegabytes') ? 'M' : 'A';
+
+ return $f->format_bytes($bc, precision => $precision, unit => $unit);
+}
+
+=back
+
+=head1 BUGS
+
+Fly
+
+=head1 SEE ALSO
+
+L<Number::Format>
+
+=cut
+
+1;
+
diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm
new file mode 100644
index 0000000..e042c05
--- /dev/null
+++ b/FS/FS/UID.pm
@@ -0,0 +1,405 @@
+package FS::UID;
+
+use strict;
+use vars qw(
+ @ISA @EXPORT_OK $DEBUG $me $cgi $freeside_uid $user $conf_dir $cache_dir
+ $secrets $datasrc $db_user $db_pass $schema $dbh $driver_name
+ $AutoCommit %callback @callback $callback_hack $use_confcompat
+);
+use subs qw(
+ getsecrets cgisetotaker
+);
+use Exporter;
+use Carp qw(carp croak cluck confess);
+use DBI;
+use IO::File;
+use FS::CurrentUser;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup
+ getotaker dbh datasrc getsecrets driver_name myconnect
+ use_confcompat);
+
+$DEBUG = 0;
+$me = '[FS::UID]';
+
+$freeside_uid = scalar(getpwnam('freeside'));
+
+$conf_dir = "%%%FREESIDE_CONF%%%";
+$cache_dir = "%%%FREESIDE_CACHE%%%";
+
+$AutoCommit = 1; #ours, not DBI
+$use_confcompat = 1;
+$callback_hack = 0;
+
+=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;
+ my $olduser = $user;
+ warn "$me forksuidsetup starting for $user\n" if $DEBUG;
+
+ if ( $FS::CurrentUser::upgrade_hack ) {
+ $user = 'fs_bootstrap';
+ } else {
+ 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 (\$>=$>, \$<=$<)\n" unless checkeuid();
+
+ warn "$me forksuidsetup connecting to database\n" if $DEBUG;
+ if ( $FS::CurrentUser::upgrade_hack && $olduser ) {
+ $dbh = &myconnect($olduser);
+ } else {
+ $dbh = &myconnect();
+ }
+ warn "$me forksuidsetup connected to database with handle $dbh\n" if $DEBUG;
+
+ warn "$me forksuidsetup loading schema\n" if $DEBUG;
+ use FS::Schema qw(reload_dbdef dbdef);
+ reload_dbdef("$conf_dir/dbdef.$datasrc")
+ unless $FS::Schema::setup_hack;
+
+ warn "$me forksuidsetup deciding upon config system to use\n" if $DEBUG;
+
+ if ( ! $FS::Schema::setup_hack && dbdef->table('conf') ) {
+
+ my $sth = $dbh->prepare("SELECT COUNT(*) FROM conf") or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $confcount = $sth->fetchrow_arrayref->[0];
+
+ if ($confcount) {
+ $use_confcompat = 0;
+ }else{
+ warn "NO CONFIGURATION RECORDS FOUND";
+ }
+
+ } else {
+ warn "NO CONFIGURATION TABLE FOUND" unless $FS::Schema::setup_hack;
+ }
+
+ unless ( $callback_hack ) {
+ warn "$me calling callbacks\n" if $DEBUG;
+ foreach ( keys %callback ) {
+ &{$callback{$_}};
+ # breaks multi-database installs # delete $callback{$_}; #run once
+ }
+
+ &{$_} foreach @callback;
+ } else {
+ warn "$me skipping callbacks (callback_hack set)\n" if $DEBUG;
+ }
+
+ warn "$me forksuidsetup loading user\n" if $DEBUG;
+ FS::CurrentUser->load_user($user);
+
+ $dbh;
+}
+
+sub myconnect {
+ my $handle = DBI->connect( getsecrets(@_), { 'AutoCommit' => 0,
+ 'ChopBlanks' => 1,
+ 'ShowErrorStatement' => 1,
+ }
+ )
+ or die "DBI->connect error: $DBI::errstr\n";
+
+ if ( $schema ) {
+ use DBIx::DBSchema::_util qw(_load_driver ); #quelle hack
+ my $driver = _load_driver($handle);
+ if ( $driver =~ /^Pg/ ) {
+ no warnings 'redefine';
+ eval "sub DBIx::DBSchema::DBD::${driver}::default_db_schema {'$schema'}";
+ die $@ if $@;
+ }
+ }
+
+ $handle;
+}
+
+=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 unless $>; #huh. mpm-itk hack
+ ( $> == $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;
+
+ if ( -e "$conf_dir/mapsecrets" ) {
+ die "No user!" unless $user;
+ my($line) = grep /^\s*($user|\*)\s/,
+ map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/mapsecrets");
+ confess "User $user not found in mapsecrets!" unless $line;
+ $line =~ /^\s*($user|\*)\s+(.*)$/;
+ $secrets = $2;
+ die "Illegal mapsecrets line for user?!" unless $secrets;
+ } else {
+ # no mapsecrets file at all, so do the default thing
+ $secrets = 'secrets';
+ }
+
+ ($datasrc, $db_user, $db_pass, $schema) =
+ map { /^(.*)$/; $1 } readline(new IO::File "$conf_dir/$secrets")
+ or die "Can't get secrets: $conf_dir/$secrets: $!\n";
+ undef $driver_name;
+
+ ($datasrc, $db_user, $db_pass);
+}
+
+=item use_confcompat
+
+Returns true whenever we should use 1.7 configuration compatibility.
+
+=cut
+
+sub use_confcompat {
+ $use_confcompat;
+}
+
+=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/Upgrade.pm b/FS/FS/Upgrade.pm
new file mode 100644
index 0000000..576676f
--- /dev/null
+++ b/FS/FS/Upgrade.pm
@@ -0,0 +1,276 @@
+package FS::Upgrade;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use Tie::IxHash;
+use FS::UID qw( dbh driver_name );
+use FS::Conf;
+use FS::Record qw(qsearchs str2time_sql);
+
+use FS::svc_domain;
+$FS::svc_domain::whois_hack = 1;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( upgrade upgrade_sqlradius );
+
+$DEBUG = 1;
+
+=head1 NAME
+
+FS::Upgrade - Database upgrade routines
+
+=head1 SYNOPSIS
+
+ use FS::Upgrade;
+
+=head1 DESCRIPTION
+
+Currently this module simply provides a place to store common subroutines for
+database upgrades.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item
+
+=cut
+
+sub upgrade {
+ my %opt = @_;
+
+ my $data = upgrade_data(%opt);
+
+ foreach my $table ( keys %$data ) {
+
+ my $class = "FS::$table";
+ eval "use $class;";
+ die $@ if $@;
+
+ if ( $class->can('_upgrade_data') ) {
+ warn "Upgrading $table...\n";
+
+ my $start = time;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ $FS::UID::AutoCommit = 0;
+
+ $class->_upgrade_data(%opt);
+
+ if ( $oldAutoCommit ) {
+ dbh->commit or die dbh->errstr;
+ }
+
+ #warn "\e[1K\rUpgrading $table... done in ". (time-$start). " seconds\n";
+ warn " done in ". (time-$start). " seconds\n";
+
+ } else {
+ warn "WARNING: asked for upgrade of $table,".
+ " but FS::$table has no _upgrade_data method\n";
+ }
+
+# my @records = @{ $data->{$table} };
+#
+# foreach my $record ( @records ) {
+# my $args = delete($record->{'_upgrade_args'}) || [];
+# my $object = $class->new( $record );
+# my $error = $object->insert( @$args );
+# die "error inserting record into $table: $error\n"
+# if $error;
+# }
+
+ }
+
+}
+
+
+sub upgrade_data {
+ my %opt = @_;
+
+ tie my %hash, 'Tie::IxHash',
+
+ #cust_main (remove paycvv from history)
+ 'cust_main' => [],
+
+ #msgcat
+ 'msgcat' => [],
+
+ #reason type and reasons
+ 'reason_type' => [],
+ 'cust_pkg_reason' => [],
+
+ #need part_pkg before cust_credit...
+ 'part_pkg' => [],
+
+ #customer credits
+ 'cust_credit' => [],
+
+ #duplicate history records
+ 'h_cust_svc' => [],
+
+ #populate cust_pay.otaker
+ 'cust_pay' => [],
+
+ #populate part_pkg_taxclass for starters
+ 'part_pkg_taxclass' => [],
+
+ #remove bad pending records
+ 'cust_pay_pending' => [],
+
+ #replace invnum and pkgnum with billpkgnum
+ 'cust_bill_pkg_detail' => [],
+
+ #usage_classes if we have none
+ 'usage_class' => [],
+
+ #phone_type if we have none
+ 'phone_type' => [],
+
+ #fixup access rights
+ 'access_right' => [],
+
+ #change recur_flat and enable_prorate
+ 'part_pkg_option' => [],
+
+ #add weights to pkg_category
+ 'pkg_category' => [],
+
+ #cdrbatch fixes
+ 'cdr' => [],
+
+ #otaker->usernum
+ 'cust_attachment' => [],
+ #'cust_credit' => [],
+ #'cust_main' => [],
+ 'cust_main_note' => [],
+ #'cust_pay' => [],
+ 'cust_pay_void' => [],
+ 'cust_pkg' => [],
+ #'cust_pkg_reason' => [],
+ 'cust_pkg_discount' => [],
+ 'cust_refund' => [],
+ 'banned_pay' => [],
+
+ ;
+
+ \%hash;
+
+}
+
+sub upgrade_sqlradius {
+ #my %opt = @_;
+
+ my $conf = new FS::Conf;
+
+ my @part_export = FS::part_export::sqlradius->all_sqlradius_withaccounting();
+
+ foreach my $part_export ( @part_export ) {
+
+ my $errmsg = 'Error adding FreesideStatus to '.
+ $part_export->option('datasrc'). ': ';
+
+ my $dbh = DBI->connect(
+ ( map $part_export->option($_), qw ( datasrc username password ) ),
+ { PrintError => 0, PrintWarn => 0 }
+ ) or do {
+ warn $errmsg.$DBI::errstr;
+ next;
+ };
+
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $group = "UserName";
+ $group .= ",Realm"
+ if ( ref($part_export) =~ /withdomain/ );
+
+ my $sth_alter = $dbh->prepare(
+ "ALTER TABLE radacct ADD COLUMN FreesideStatus varchar(32) NULL"
+ );
+ if ( $sth_alter ) {
+ if ( $sth_alter->execute ) {
+ my $sth_update = $dbh->prepare(
+ "UPDATE radacct SET FreesideStatus = 'done' WHERE FreesideStatus IS NULL"
+ ) or die $errmsg.$dbh->errstr;
+ $sth_update->execute or die $errmsg.$sth_update->errstr;
+ } else {
+ my $error = $sth_alter->errstr;
+ warn $errmsg.$error unless $error =~ /Duplicate column name/i;
+ }
+ } else {
+ my $error = $dbh->errstr;
+ warn $errmsg.$error; #unless $error =~ /exists/i;
+ }
+
+ my $sth_index = $dbh->prepare(
+ "CREATE INDEX FreesideStatus ON radacct ( FreesideStatus )"
+ );
+ if ( $sth_index ) {
+ unless ( $sth_index->execute ) {
+ my $error = $sth_index->errstr;
+ warn $errmsg.$error unless $error =~ /Duplicate key name/i;
+ }
+ } else {
+ my $error = $dbh->errstr;
+ warn $errmsg.$error; #unless $error =~ /exists/i;
+ }
+
+ my $sth = $dbh->prepare("SELECT UserName,
+ Realm,
+ $str2time max(AcctStartTime)),
+ $str2time max(AcctStopTime))
+ FROM radacct
+ WHERE FreesideStatus = 'done'
+ AND AcctStartTime != 0
+ AND AcctStopTime != 0
+ GROUP BY $group
+ ")
+ or die $errmsg.$dbh->errstr;
+ $sth->execute() or die $errmsg.$sth->errstr;
+
+ while (my $row = $sth->fetchrow_arrayref ) {
+ my ($username, $realm, $start, $stop) = @$row;
+
+ $username = lc($username) unless $conf->exists('username-uppercase');
+
+ my $exportnum = $part_export->exportnum;
+ my $extra_sql = " AND exportnum = $exportnum ".
+ " AND exportsvcnum IS NOT NULL ";
+
+ if ( ref($part_export) =~ /withdomain/ ) {
+ $extra_sql = " AND '$realm' = ( SELECT domain FROM svc_domain
+ WHERE svc_domain.svcnum = svc_acct.domsvc ) ";
+ }
+
+ my $svc_acct = qsearchs({
+ 'select' => 'svc_acct.*',
+ 'table' => 'svc_acct',
+ 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )'.
+ 'LEFT JOIN export_svc USING ( svcpart )',
+ 'hashref' => { 'username' => $username },
+ 'extra_sql' => $extra_sql,
+ });
+
+ if ($svc_acct) {
+ $svc_acct->last_login($start)
+ if $start && (!$svc_acct->last_login || $start > $svc_acct->last_login);
+ $svc_acct->last_logout($stop)
+ if $stop && (!$svc_acct->last_logout || $stop > $svc_acct->last_logout);
+ }
+ }
+ }
+
+}
+
+=back
+
+=head1 BUGS
+
+Sure.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/FS/XMLRPC.pm b/FS/FS/XMLRPC.pm
new file mode 100644
index 0000000..fb0e5ac
--- /dev/null
+++ b/FS/FS/XMLRPC.pm
@@ -0,0 +1,166 @@
+package FS::XMLRPC;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Frontier::RPC2;
+
+# Instead of 'use'ing freeside modules on the fly below, just preload them now.
+use FS;
+use FS::CGI;
+use FS::Conf;
+use FS::Record;
+use FS::cust_main;
+
+use Data::Dumper;
+
+@ISA = qw( );
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::XMLRPC - Object methods for handling XMLRPC requests
+
+=head1 SYNOPSIS
+
+ use FS::XMLRPC;
+
+ $xmlrpc = new FS::XMLRPC;
+
+ ($error, $response_xml) = $xmlrpc->serve($request_xml);
+
+=head1 DESCRIPTION
+
+The FS::XMLRPC object is a mechanisim to access read-only data from freeside's subroutines. It does not, at least not at this point, give you the ability to access methods of freeside objects remotely. It can, however, be used to call subroutines such as FS::cust_main::smart_search and FS::Record::qsearch.
+
+See the serve method below for calling syntax.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+Provides a FS::XMLRPC object used to handle incoming XMLRPC requests.
+
+=cut
+
+sub new {
+
+ my $class = shift;
+ my $self = {};
+ bless($self, $class);
+
+ $self->{_coder} = new Frontier::RPC2;
+
+ return $self;
+
+}
+
+=item serve REQUEST_XML_SCALAR
+
+The serve method takes a scalar containg an XMLRPC request for one of freeside's subroutines (not object methods). Parameters passed in the 'methodCall' will be passed as a list to the subroutine untouched. The return value of the called subroutine _must_ be a freeside object reference (eg. qsearchs) or a list of freeside object references (eg. qsearch, smart_search), _and_, the object(s) returned must support the hashref method. This will be checked first by calling UNIVERSAL::can('FS::class::subroutine', 'hashref').
+
+Return value is an XMLRPC methodResponse containing the results of the call. The result of the subroutine call itself will be coded in the methodResponse as an array of structs, regardless of whether there was many or a single object returned. In other words, after you decode the response, you'll always have an array.
+
+=cut
+
+sub serve {
+
+ my ($self, $request_xml) = (shift, shift);
+ my $response_xml;
+
+ my $coder = $self->{_coder};
+ my $call = $coder->decode($request_xml);
+
+ warn "Got methodCall with method_name='" . $call->{method_name} . "'"
+ if $DEBUG;
+
+ $response_xml = $coder->encode_response(&_serve($call->{method_name}, $call->{value}));
+
+ return ('', $response_xml);
+
+}
+
+sub _serve { #Subroutine, not method
+
+ my ($method_name, $params) = (shift, shift);
+
+
+ #die 'Called _serve without parameters' unless ref($params) eq 'ARRAY';
+ $params = [] unless (ref($params) eq 'ARRAY');
+
+ if ($method_name =~ /^(\w+)\.(\w+)/) {
+
+ #my ($class, $sub) = split(/\./, $method_name);
+ my ($class, $sub) = ($1, $2);
+ my $fssub = "FS::${class}::${sub}";
+ warn "fssub: ${fssub}" if $DEBUG;
+ warn "params: " . Dumper($params) if $DEBUG;
+
+ my @result;
+
+ if ($class eq 'Conf') { #Special case for FS::Conf because we need an obj.
+
+ if ($sub eq 'config') {
+ my $conf = new FS::Conf;
+ @result = ($conf->config(@$params));
+ } else {
+ warn "FS::XMLRPC: Can't call undefined subroutine '${fssub}'";
+ }
+
+ } else {
+
+ unless (UNIVERSAL::can("FS::${class}", $sub)) {
+ warn "FS::XMLRPC: Can't call undefined subroutine '${fssub}'";
+ # Should we encode an error in the response,
+ # or just break silently to the remote caller and complain locally?
+ return [];
+ }
+
+ eval {
+ no strict 'refs';
+ my $fssub = "FS::${class}::${sub}";
+ @result = (&$fssub(@$params));
+ };
+
+ if ($@) {
+ warn "FS::XMLRPC: Error while calling '${fssub}': $@";
+ return [];
+ }
+
+ }
+
+ warn Dumper(@result) if $DEBUG;
+
+ if (grep { UNIVERSAL::can($_, 'hashref') ? 0 : 1 } @result) {
+ #warn "FS::XMLRPC: One or more objects returned from '${fssub}' doesn't " .
+ # "support the 'hashref' method.";
+
+ # If they're not FS::Record decendants, just return the results unmap'd?
+ # This is more flexible, but possibly more error-prone.
+ return [ @result ];
+ } else {
+ return [ map { $_->hashref } @result ];
+ }
+ } elsif ($method_name eq 'version') {
+ return [ $FS::VERSION ];
+ } # else...
+
+ warn "Unhandle XMLRPC request '${method_name}'";
+ return [];
+
+}
+
+=head1 BUGS
+
+Probably lots.
+
+=head1 SEE ALSO
+
+L<Frontier::RPC2>.
+
+=cut
+
+1;
+
diff --git a/FS/FS/Yori.pm b/FS/FS/Yori.pm
new file mode 100644
index 0000000..b5bdc0c
--- /dev/null
+++ b/FS/FS/Yori.pm
@@ -0,0 +1,94 @@
+package FS::Yori;
+# a reporting program, to report information to the MCP
+
+use strict;
+use base 'Exporter';
+
+our @EXPORT_OK = qw( reports report );
+
+sub reports { #should be autogenerated i guess
+ qw( freeside_version debian_version pg_version
+ apache_version apache_mpm
+ payment_gateways
+ );
+ #ssh_vulnkey
+}
+
+sub report {
+ my $report = shift;
+ $report =~ /^(\w+)$/ or die;
+ eval "report_$report();";
+}
+
+sub report_all {
+ foreach my $report ( reports() ) {
+ print "$report: ". report($report). "\n";
+ }
+}
+
+sub report_freeside_version {
+ chomp( my $fs_version =
+ `grep '^VERSION=' /home/ivan/freeside/Makefile | cut -d= -f2`
+ );
+ $fs_version;
+}
+
+sub report_debian_version {
+ chomp( my $deb_version = `cat /etc/debian_version` );
+ $deb_version;
+}
+
+sub report_pg_version {
+ chomp( my $pg_version = `echo 'show server_version' | psql -t freeside` );
+ chomp($pg_version); #two?
+ $pg_version =~ s/^ +//;
+ $pg_version;
+}
+
+sub report_apache_version {
+ chomp( my $apache_version =
+ `/usr/sbin/apache2 -v | head -1 | cut -d: -f2 | cut -d/ -f2 | cut -d' ' -f1`
+ );
+ $apache_version;
+}
+
+sub report_apache_mpm {
+ chomp( my $apache_mpm =
+ `/usr/sbin/apache2 -V | grep '^Server MPM' | cut -d: -f2`
+ );
+ $apache_mpm =~ s/^ +//;
+ $apache_mpm;
+}
+
+sub report_payment_gateways {
+ my @gateways = split(/\n/,
+ `aptitude -F '%c %p' search 'libbusiness-onlinepayment-.*' | grep '^i ' | grep -v '^i libbusiness-onlinepayment-perl' | cut -c29- | cut -d- -f1`
+ );
+ join(', ', @gateways);
+}
+
+#sub report_ssh_vulnkey{
+# my $ssh_vulnkey = `ssh-vulnkey -a | grep COMPROMISED`;
+# $ssh_vulnkey;
+#}
+
+sub report_load {
+ open LOAD, "</proc/loadavg" || return;
+ my($one, $five, $fifteen) = split ' ', <LOAD>;
+ close LOAD;
+ ($one, $five, $fifteen);
+}
+
+sub report_freememory {
+ open MEM, "</proc/meminfo" || return;
+ my $free = 0;
+ my @interesting = qw( MemFree Cached SwapFree );
+ while (<MEM>) {
+ /^(\w*):\s*(\d*) kB$/ || next;
+ next unless grep { $_ eq $1 } @interesting;
+ $free += $2;
+ }
+ close MEM;
+ $free;
+}
+
diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm
new file mode 100644
index 0000000..b5b693a
--- /dev/null
+++ b/FS/FS/access_group.pm
@@ -0,0 +1,162 @@
+package FS::access_group;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::m2name_Common;
+use FS::access_groupagent;
+use FS::access_right;
+
+@ISA = qw(FS::m2m_Common FS::m2name_Common FS::Record);
+
+=head1 NAME
+
+FS::access_group - Object methods for access_group records
+
+=head1 SYNOPSIS
+
+ use FS::access_group;
+
+ $record = new FS::access_group \%hash;
+ $record = new FS::access_group { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_group object represents an access group. FS::access_group inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item groupnum - primary key
+
+=item groupname - Access group name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new access group. To add the access group 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 { 'access_group'; }
+
+=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 access group. 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('groupnum')
+ || $self->ut_text('groupname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item access_groupagent
+
+Returns all associated FS::access_groupagent records.
+
+=cut
+
+sub access_groupagent {
+ my $self = shift;
+ qsearch('access_groupagent', { 'groupnum' => $self->groupnum } );
+}
+
+=item access_rights
+
+Returns all associated FS::access_right records.
+
+=cut
+
+sub access_rights {
+ my $self = shift;
+ qsearch('access_right', { 'righttype' => 'FS::access_group',
+ 'rightobjnum' => $self->groupnum
+ }
+ );
+}
+
+=item access_right RIGHTNAME
+
+Returns the specified FS::access_right record. Can be used as a boolean, to
+test if this group has the given RIGHTNAME.
+
+=cut
+
+sub access_right {
+ my( $self, $name ) = @_;
+ qsearchs('access_right', { 'righttype' => 'FS::access_group',
+ 'rightobjnum' => $self->groupnum,
+ 'rightname' => $name,
+ }
+ );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/access_groupagent.pm b/FS/FS/access_groupagent.pm
new file mode 100644
index 0000000..bacc013
--- /dev/null
+++ b/FS/FS/access_groupagent.pm
@@ -0,0 +1,146 @@
+package FS::access_groupagent;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::agent;
+use FS::access_group;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::access_groupagent - Object methods for access_groupagent records
+
+=head1 SYNOPSIS
+
+ use FS::access_groupagent;
+
+ $record = new FS::access_groupagent \%hash;
+ $record = new FS::access_groupagent { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_groupagent object represents an group reseller virtualization. FS::access_groupagent inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item groupagentnum - primary key
+
+=item groupnum -
+
+=item agentnum -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new group reseller virtualization. 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 { 'access_groupagent'; }
+
+=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 group reseller virtualization. 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('groupagentnum')
+ || $self->ut_foreign_key('groupnum', 'access_group', 'groupnum')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item agent
+
+Returns the associated FS::agent object.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=item access_group
+
+Returns the associated FS::access_group object.
+
+=cut
+
+sub access_group {
+ my $self = shift;
+ qsearchs('access_group', { 'groupnum' => $self->groupnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm
new file mode 100644
index 0000000..bc6dd5d
--- /dev/null
+++ b/FS/FS/access_right.pm
@@ -0,0 +1,163 @@
+package FS::access_right;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::access_right - Object methods for access_right records
+
+=head1 SYNOPSIS
+
+ use FS::access_right;
+
+ $record = new FS::access_right \%hash;
+ $record = new FS::access_right { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_right object represents a granted access right. FS::access_right
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item rightnum - primary key
+
+=item righttype -
+
+=item rightobjnum -
+
+=item rightname -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new right. To add the right 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 { 'access_right'; }
+
+=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 right. 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('rightnum')
+ || $self->ut_text('righttype')
+ || $self->ut_text('rightobjnum')
+ || $self->ut_text('rightname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ my @unmigrated = ( qsearch( 'access_right',
+ { 'righttype'=>'FS::access_group',
+ 'rightname'=>'Engineering configuration',
+ }
+ ),
+ qsearch( 'access_right',
+ { 'righttype'=>'FS::access_group',
+ 'rightname'=>'Engineering global configuration',
+ }
+ )
+ );
+ foreach ( @unmigrated ) {
+ my $rightname = $_->rightname;
+ $rightname =~ s/Engineering/Dialup/;
+ $_->rightname($rightname);
+ my $error = $_->replace;
+ die "Failed to update access right: $error"
+ if $error;
+ my $broadband = new FS::access_right { $_->hash };
+ $rightname =~ s/Dialup/Broadband/;
+ $broadband->rightnum('');
+ $broadband->rightname($rightname);
+ $error = $broadband->insert;
+ die "Failed to insert access right: $error"
+ if $error;
+ }
+
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
new file mode 100644
index 0000000..1bf6e93
--- /dev/null
+++ b/FS/FS/access_user.pm
@@ -0,0 +1,518 @@
+package FS::access_user;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $conf $htpasswd_file );
+use FS::UID;
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::m2m_Common;
+use FS::option_Common;
+use FS::access_user_pref;
+use FS::access_usergroup;
+use FS::agent;
+use FS::cust_main;
+
+@ISA = qw( FS::m2m_Common FS::option_Common FS::Record );
+#@ISA = qw( FS::m2m_Common FS::option_Common );
+
+$DEBUG = 0;
+$me = '[FS::access_user]';
+
+#kludge htpasswd for now (i hope this bootstraps okay)
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $htpasswd_file = $conf->base_dir. '/htpasswd';
+} );
+
+=head1 NAME
+
+FS::access_user - Object methods for access_user records
+
+=head1 SYNOPSIS
+
+ use FS::access_user;
+
+ $record = new FS::access_user \%hash;
+ $record = new FS::access_user { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_user object represents an internal access user. FS::access_user
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item usernum - primary key
+
+=item username -
+
+=item _password -
+
+=item last -
+
+=item first -
+
+=item disabled - empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new internal access user. To add the user 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 { 'access_user'; }
+
+sub _option_table { 'access_user_pref'; }
+sub _option_namecol { 'prefname'; }
+sub _option_valuecol { 'prefvalue'; }
+
+=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->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;
+
+ $error = $self->htpasswd_kludge();
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ }
+
+ $error = $self->SUPER::insert(@_);
+
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+
+ #make sure it isn't a dup username? or you could nuke people's passwords
+ #blah. really just should do our own login w/cookies
+ #and auth out of the db in the first place
+ #my $hterror = $self->htpasswd_kludge('-D');
+ #$error .= " - additionally received error cleaning up htpasswd file: $hterror"
+ return $error;
+
+ } else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+ }
+
+}
+
+sub htpasswd_kludge {
+ my $self = shift;
+
+ #awful kludge to skip setting htpasswd for fs_* users
+ return '' if $self->username =~ /^fs_/;
+
+ unshift @_, '-c' unless -e $htpasswd_file;
+ if (
+ system('htpasswd', '-b', @_,
+ $htpasswd_file,
+ $self->username,
+ $self->_password,
+ ) == 0
+ )
+ {
+ return '';
+ } else {
+ return 'htpasswd exited unsucessfully';
+ }
+}
+
+=item delete
+
+Delete this record from the database.
+
+=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(@_)
+ || $self->htpasswd_kludge('-D')
+ ;
+
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ } else {
+ $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 $new = shift;
+
+ my $old = ( ref($_[0]) eq ref($new) )
+ ? shift
+ : $new->replace_old;
+
+ 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->_password ne $old->_password ) {
+ my $error = $new->htpasswd_kludge();
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ }
+ } elsif ( $old->disabled && !$new->disabled
+ && $new->_password =~ /changeme/i ) {
+ return "Must change password when enabling this account";
+ }
+
+ my $error = $new->SUPER::replace($old, @_);
+
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ } else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+ }
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid internal access user. 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('usernum')
+ || $self->ut_alpha_lower('username')
+ || $self->ut_text('_password')
+ || $self->ut_text('last')
+ || $self->ut_text('first')
+ || $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item name
+
+Returns a name string for this user: "Last, First".
+
+=cut
+
+sub name {
+ my $self = shift;
+ $self->get('last'). ', '. $self->first;
+}
+
+=item user_cust_main
+
+Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
+user.
+
+=cut
+
+sub user_cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
+}
+
+=item access_usergroup
+
+Returns links to the the groups this user is a part of, as FS::access_usergroup
+objects (see L<FS::access_usergroup>).
+
+=cut
+
+sub access_usergroup {
+ my $self = shift;
+ qsearch( 'access_usergroup', { 'usernum' => $self->usernum } );
+}
+
+#=item access_groups
+#
+#=cut
+#
+#sub access_groups {
+#
+#}
+#
+#=item access_groupnames
+#
+#=cut
+#
+#sub access_groupnames {
+#
+#}
+
+=item agentnums
+
+Returns a list of agentnums this user can view (via group membership).
+
+=cut
+
+sub agentnums {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT DISTINCT agentnum FROM access_usergroup
+ JOIN access_groupagent USING ( groupnum )
+ WHERE usernum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->usernum) or die $sth->errstr;
+ map { $_->[0] } @{ $sth->fetchall_arrayref };
+}
+
+=item agentnums_href
+
+Returns a hashref of agentnums this user can view.
+
+=cut
+
+sub agentnums_href {
+ my $self = shift;
+ scalar( { map { $_ => 1 } $self->agentnums } );
+}
+
+=item agentnums_sql [ HASHREF | OPTION => VALUE ... ]
+
+Returns an sql fragement to select only agentnums this user can view.
+
+Options are passed as a hashref or a list. Available options are:
+
+=over 4
+
+=item null
+
+The frament will also allow the selection of null agentnums.
+
+=item null_right
+
+The fragment will also allow the selection of null agentnums if the current
+user has the provided access right
+
+=item table
+
+Optional table name in which agentnum is being checked. Sometimes required to
+resolve 'column reference "agentnum" is ambiguous' errors.
+
+=back
+
+=cut
+
+sub agentnums_sql {
+ my( $self ) = shift;
+ my %opt = ref($_[0]) ? %{$_[0]} : @_;
+
+ my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
+
+# my @agentnums = map { "$agentnum = $_" } $self->agentnums;
+ my @agentnums = ();
+ push @agentnums, "$agentnum IN (". join(',', $self->agentnums). ')';
+
+ push @agentnums, "$agentnum IS NULL"
+ if $opt{'null'}
+ || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
+
+ return ' 1 = 0 ' unless scalar(@agentnums);
+ '( '. join( ' OR ', @agentnums ). ' )';
+
+}
+
+=item agentnum
+
+Returns true if the user can view the specified agent.
+
+=cut
+
+sub agentnum {
+ my( $self, $agentnum ) = @_;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM access_usergroup
+ JOIN access_groupagent USING ( groupnum )
+ WHERE usernum = ? AND agentnum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->usernum, $agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item agents
+
+Returns the list of agents this user can view (via group membership), as
+FS::agent objects.
+
+=cut
+
+sub agents {
+ my $self = shift;
+ qsearch({
+ 'table' => 'agent',
+ 'hashref' => { disabled=>'' },
+ 'extra_sql' => ' AND '. $self->agentnums_sql,
+ });
+}
+
+=item access_right RIGHTNAME | LISTREF
+
+Given a right name or a list reference of right names, returns true if this
+user has this right, or, for a list, one of the rights (currently via group
+membership, eventually also via user overrides).
+
+=cut
+
+sub access_right {
+ my( $self, $rightname ) = @_;
+
+ $rightname = [ $rightname ] unless ref($rightname);
+
+ warn "$me access_right called on ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+
+ #some caching of ACL requests for low-hanging fruit perf improvement
+ #since we get a new $CurrentUser object each page view there shouldn't be any
+ #issues with stickiness
+ if ( $self->{_ACLcache} ) {
+
+ unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
+ warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+ return grep $self->{_ACLcache}{$_}, @$rightname
+ }
+
+ warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
+ if $DEBUG;
+
+ } else {
+
+ warn "initializing ACL cache\n"
+ if $DEBUG;
+ $self->{_ACLcache} = {};
+
+ }
+
+ my $has_right = ' rightname IN ('. join(',', map '?', @$rightname ). ') ';
+
+ my $sth = dbh->prepare("
+ SELECT groupnum FROM access_usergroup
+ LEFT JOIN access_group USING ( groupnum )
+ LEFT JOIN access_right
+ ON ( access_group.groupnum = access_right.rightobjnum )
+ WHERE usernum = ?
+ AND righttype = 'FS::access_group'
+ AND $has_right
+ LIMIT 1
+ ") or die dbh->errstr;
+ $sth->execute($self->usernum, @$rightname) or die $sth->errstr;
+ my $row = $sth->fetchrow_arrayref;
+
+ my $return = $row ? $row->[0] : '';
+
+ #just caching the single-rightname hits should be enough of a win for now
+ if ( scalar(@$rightname) == 1 ) {
+ $self->{_ACLcache}{${$rightname}[0]} = $return;
+ }
+
+ $return;
+
+}
+
+=item default_customer_view
+
+Returns the default customer view for this user, from the
+"default_customer_view" user preference, the "cust_main-default_view" config,
+or the hardcoded default, "jumbo" (may change to "basics" in the near future).
+
+=cut
+
+sub default_customer_view {
+ my $self = shift;
+
+ $self->option('default_customer_view')
+ || $conf->config('cust_main-default_view')
+ || 'jumbo'; #'basics' in 1.9.1?
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/access_user_pref.pm b/FS/FS/access_user_pref.pm
new file mode 100644
index 0000000..a445d31
--- /dev/null
+++ b/FS/FS/access_user_pref.pm
@@ -0,0 +1,129 @@
+package FS::access_user_pref;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::access_user_pref - Object methods for access_user_pref records
+
+=head1 SYNOPSIS
+
+ use FS::access_user_pref;
+
+ $record = new FS::access_user_pref \%hash;
+ $record = new FS::access_user_pref { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_user_pref object represents an per-user preference. Preferenaces
+are also used to store transient state information (server-side "cookies").
+FS::access_user_pref inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item prefnum - primary key
+
+=item usernum - Internal access user (see L<FS::access_user>)
+
+=item prefname -
+
+=item prefvalue -
+
+=item expiration -
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new preference. To add the preference 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 { 'access_user_pref'; }
+
+=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 preference. 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('prefnum')
+ || $self->ut_number('usernum')
+ || $self->ut_text('prefname')
+ #|| $self->ut_textn('prefvalue')
+ || $self->ut_anything('prefvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::access_user>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/access_usergroup.pm b/FS/FS/access_usergroup.pm
new file mode 100644
index 0000000..8511fe5
--- /dev/null
+++ b/FS/FS/access_usergroup.pm
@@ -0,0 +1,143 @@
+package FS::access_usergroup;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::access_user;
+use FS::access_group;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::access_usergroup - Object methods for access_usergroup records
+
+=head1 SYNOPSIS
+
+ use FS::access_usergroup;
+
+ $record = new FS::access_usergroup \%hash;
+ $record = new FS::access_usergroup { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_usergroup object represents an internal access user's membership
+in a group. FS::access_usergroup inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item usergroupnum - primary key
+
+=item usernum -
+
+=item groupnum -
+
+
+=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 { 'access_usergroup'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('usergroupnum')
+ || $self->ut_number('usernum')
+ || $self->ut_number('groupnum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item access_user
+
+=cut
+
+sub access_user {
+ my $self = shift;
+ qsearchs( 'access_user', { 'usernum' => $self->usernum } );
+}
+
+=item access_group
+
+=cut
+
+sub access_group {
+ my $self = shift;
+ qsearchs( 'access_group', { 'groupnum' => $self->groupnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/acct_rt_transaction.pm b/FS/FS/acct_rt_transaction.pm
new file mode 100644
index 0000000..ef0a275
--- /dev/null
+++ b/FS/FS/acct_rt_transaction.pm
@@ -0,0 +1,316 @@
+package FS::acct_rt_transaction;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::acct_rt_transaction - Object methods for acct_rt_transaction records
+
+=head1 SYNOPSIS
+
+ use FS::acct_rt_transaction;
+
+ $record = new FS::acct_rt_transaction \%hash;
+ $record = new FS::acct_rt_transaction { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::acct_rt_transaction object represents an application of time
+from a rt transaction to a svc_acct. FS::acct_rt_transaction inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item svcrtid
+
+Primary key
+
+=item svcnum
+
+The svcnum of the svc_acct to which the time applies
+
+=item transaction_id
+
+The id of the rt transtaction from which the time applies
+
+=item seconds
+
+The amount of time applied from tickets
+
+=item support
+
+The amount of time applied to support services
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new acct_rt_transaction. 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 { 'acct_rt_transaction'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my( $self, %options ) = @_;
+
+ 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($options{options} ? %{$options{options}} : ());
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $svc_acct = qsearchs('svc_acct', {'svcnum' => $self->svcnum});
+ unless ($svc_acct) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't find svc_acct " . $self->svcnum;
+ }
+
+ $error = $svc_acct->decrement_seconds($self->support);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error incrementing service seconds: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+
+=item delete
+
+Delete this record from the database.
+
+=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;
+ }
+
+ my $svc_acct = qsearchs('svc_acct', {'svcnum' => $self->svcnum});
+ unless ($svc_acct) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't find svc_acct " . $self->svcnum;
+ }
+
+ $error = $svc_acct->increment_seconds($self->support);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error incrementing service seconds: $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
+
+=item check
+
+Checks all fields to make sure this is a valid acct_rt_transaction. 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 ($selfref) = $self->hashref;
+
+ my $error =
+ $self->ut_numbern('svcrtid')
+ || $self->ut_numbern('svcnum')
+ || $self->ut_number('transaction_id')
+ || $self->ut_numbern('_date')
+ || $self->ut_snumber('seconds')
+ || $self->ut_snumber('support')
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ if ($selfref->{custnum}) {
+ my $conf = new FS::Conf;
+ my %packages = map { $_ => 1 } $conf->config('support_packages');
+ my $cust_main = qsearchs('cust_main',{ 'custnum' => $selfref->{custnum} } );
+ return "Invalid custnum: " . $selfref->{custnum} unless $cust_main;
+
+ my (@svcs) = map { $_->svcnum } $cust_main->support_services;
+ return "svcnum ". $self->svcnum. " invalid for custnum ".$selfref->{custnum}
+ unless (!$self->svcnum || scalar(grep { $_ == $self->svcnum } @svcs));
+
+ $self->svcnum($svcs[0]) unless $self->svcnum;
+ return "Can't find support service for custnum ".$selfref->{custnum}
+ unless $self->svcnum;
+ }
+
+ $self->SUPER::check;
+}
+
+=item creator
+
+Returns the creator of the RT transaction associated with this object.
+
+=cut
+
+sub creator {
+ my $self = shift;
+ FS::TicketSystem->transaction_creator($self->transaction_id);
+}
+
+=item ticketid
+
+Returns the number of the RT ticket associated with this object.
+
+=cut
+
+sub ticketid {
+ my $self = shift;
+ FS::TicketSystem->transaction_ticketid($self->transaction_id);
+}
+
+=item subject
+
+Returns the subject of the RT ticket associated with this object.
+
+=cut
+
+sub subject {
+ my $self = shift;
+ FS::TicketSystem->transaction_subject($self->transaction_id);
+}
+
+=item status
+
+Returns the status of the RT ticket associated with this object.
+
+=cut
+
+sub status {
+ my $self = shift;
+ FS::TicketSystem->transaction_status($self->transaction_id);
+}
+
+=item batch_insert SVC_ACCT_RT_TRANSACTION_OBJECT, ...
+
+Class method which inserts multiple time applications. Takes a list of
+FS::acct_rt_transaction objects. If there is an error inserting any
+application, the entire transaction is rolled back, i.e. all time is applied
+or none is.
+
+For example:
+
+ my $errors = FS::acct_rt_transaction->batch_insert(@transactions);
+ if ( $error ) {
+ #success; all payments were inserted
+ } else {
+ #failure; no payments were inserted.
+ }
+
+=cut
+
+sub batch_insert {
+ my $self = shift; #class method
+
+ 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;
+ foreach (@_) {
+ $error = $_->insert;
+ last if $error;
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ } else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+
+ $error;
+
+}
+
+=back
+
+=head1 BUGS
+
+Possibly the delete method or others.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.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..0fe2476
--- /dev/null
+++ b/FS/FS/addr_block.pm
@@ -0,0 +1,385 @@
+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;
+use Carp qw( carp );
+
+@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.
+
+=item manual_flag - prohibit automatic ip assignment from this block when true.
+
+=item agentnum - optional agent number (see L<FS::agent>)
+
+=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.
+
+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.
+
+sub replace_check {
+ my ( $new, $old ) = ( shift, shift );
+
+ unless($new->routernum == $old->routernum) {
+ my @svc = $self->svc_broadband;
+ if (@svc) {
+ return 'Block has assigned addresses: '.
+ join ', ', map {$_->ip_addr} @svc;
+ }
+
+ return 'Block is already allocated'
+ if($new->routernum && $old->routernum);
+
+ }
+
+ '';
+}
+
+=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')
+ || $self->ut_enum('manual_flag', [ '', 'Y' ])
+ || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration')
+ ;
+ 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;
+ new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
+}
+
+=item cidr
+
+Returns a CIDR string for this block's address and netmask, i.e. 10.4.20.0/24
+
+=cut
+
+sub cidr {
+ my $self = shift;
+ $self->NetAddr->cidr;
+}
+
+=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. There are never free addresses
+when manual_flag is true.
+
+=cut
+
+sub next_free_addr {
+ my $self = shift;
+
+ return '' if $self->manual_flag;
+
+ 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 -- deprecated
+
+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) = @_;
+ carp "deallocate deprecated -- use replace";
+
+ return 'Block must be allocated to a router'
+ unless(ref $router eq 'FS::router');
+
+ my $new = new FS::addr_block {$self->hash};
+ $new->routernum($router->routernum);
+ return $new->replace($self);
+
+}
+
+=item deallocate -- deprecated
+
+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 {
+ carp "deallocate deprecated -- use replace";
+ my $self = shift;
+
+ 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.
+
+=item agent
+
+Returns the agent (see L<FS::agent>) for this address block, if one exists.
+
+=cut
+
+sub agent {
+ qsearchs('agent', { 'agentnum' => shift->agentnum } );
+}
+
+=item label
+
+Returns text including the router name, gateway ip, and netmask for this
+block.
+
+=cut
+
+sub label {
+ my $self = shift;
+ my $router = $self->router;
+ ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
+}
+
+=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..f174275
--- /dev/null
+++ b/FS/FS/agent.pm
@@ -0,0 +1,579 @@
+package FS::agent;
+
+use strict;
+use vars qw( @ISA );
+#use Crypt::YAPassGen;
+use Business::CreditCard 0.28;
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::agent_type;
+use FS::reg_code;
+use FS::TicketSystem;
+use FS::Conf;
+
+@ISA = qw( FS::m2m_Common 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 ticketing_queueid - Ticketing Queue
+
+=item invoice_template - Invoice template name
+
+=item agent_custnum - Optional agent customer (see L<FS::cust_main>)
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=item prog - Deprecated (never used)
+
+=item freq - Deprecated (never used)
+
+=item username - (Deprecated) Username for the Agent interface
+
+=item _password - (Deprecated) 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')
+ || $self->ut_textn('invoice_template')
+ || $self->ut_foreign_keyn('agent_custnum', 'cust_main', 'custnum' )
+ ;
+ 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 agent_cust_main
+
+Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
+agent.
+
+=cut
+
+sub agent_cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->agent_custnum } );
+}
+
+=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 ticketing_queue
+
+Returns the queue name corresponding with the id from the I<ticketing_queueid>
+field, or the empty string.
+
+=cut
+
+sub ticketing_queue {
+ my $self = shift;
+ FS::TicketSystem->queue($self->ticketing_queueid);
+};
+
+=item payment_gateway [ OPTION => VALUE, ... ]
+
+Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
+
+Currently available options are I<nofatal>, I<invnum>, I<method>, and I<payinfo>.
+
+If I<nofatal> is set, and no gateway is available, then the empty string
+will be returned instead of throwing a fatal exception.
+
+If I<invnum> is set to the number of an invoice (see L<FS::cust_bill>) then
+an attempt will be made to select a gateway suited for the taxes paid on
+the invoice.
+
+The I<method> and I<payinfo> options can be used to influence the choice
+as well. Presently only 'CC' and 'ECHECK' methods are meaningful.
+
+When the I<method> is 'CC' then the card number in I<payinfo> can direct
+this routine to route to a gateway suited for that type of card.
+
+=cut
+
+sub payment_gateway {
+ my ( $self, %options ) = @_;
+
+ my $taxclass = '';
+ if ( $options{invnum} ) {
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } );
+ die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
+
+ my @part_pkg =
+ map { $_->part_pkg }
+ grep { $_ }
+ map { $_->cust_pkg }
+ $cust_bill->cust_bill_pkg;
+
+ my @taxclasses = map $_->taxclass, @part_pkg;
+
+ $taxclass = $taxclasses[0]
+ unless grep { $taxclasses[0] ne $_ } @taxclasses; #unless there are
+ #different taxclasses
+ }
+
+ #look for an agent gateway override first
+ my $cardtype;
+ if ( $options{method} && $options{method} eq 'CC' && $options{payinfo} ) {
+ $cardtype = cardtype($options{payinfo});
+ } elsif ( $options{method} && $options{method} eq 'ECHECK' ) {
+ $cardtype = 'ACH';
+ } else {
+ $cardtype = $options{method} || '';
+ }
+
+ my $override =
+ qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => $cardtype,
+ taxclass => $taxclass, } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => '',
+ taxclass => $taxclass, } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => $cardtype,
+ taxclass => '', } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => '',
+ taxclass => '', } );
+
+ my $payment_gateway = new FS::payment_gateway;
+ if ( $override ) { #use a payment gateway override
+
+ $payment_gateway = $override->payment_gateway;
+
+ } else { #use the standard settings from the config
+ # the standard settings from the config could be moved to a null agent
+ # agent_payment_gateway referenced payment_gateway
+
+ my $conf = new FS::Conf;
+ unless ( $conf->exists('business-onlinepayment') ) {
+ if ( $options{'nofatal'} ) {
+ return '';
+ } else {
+ die "Real-time processing not enabled\n";
+ }
+ }
+
+ #load up config
+ my $bop_config = 'business-onlinepayment';
+ $bop_config .= '-ach'
+ if ( $options{method}
+ && $options{method} =~ /^(ECHECK|CHEK)$/
+ && $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;
+
+ $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
+ 'Business::OnlinePayment');
+ $payment_gateway->gateway_module($processor);
+ $payment_gateway->gateway_username($login);
+ $payment_gateway->gateway_password($password);
+ $payment_gateway->gateway_action($action);
+ $payment_gateway->set('options', [ @bop_options ]);
+
+ }
+
+ $payment_gateway;
+}
+
+=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 $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement";
+ $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 (customers with active
+recurring packages).
+
+=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_inactive_cust_main
+
+Returns the number of inactive customers for this agent (customers with no
+active recurring packages, but otherwise unsuspended/uncancelled).
+
+=cut
+
+sub num_inactive_cust_main {
+ shift->num_sql(FS::cust_main->inactive_sql);
+}
+
+=item inactive_cust_main
+
+Returns the inactive customers for this agent, as cust_main objects.
+
+=cut
+
+sub inactive_cust_main {
+ shift->cust_main_sql(FS::cust_main->inactive_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);
+}
+
+=item num_active_cust_pkg
+
+Returns the number of active customer packages for this agent.
+
+=cut
+
+sub num_active_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->active_sql);
+}
+
+sub num_pkg_sql {
+ my( $self, $sql ) = @_;
+ my $statement =
+ "SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )".
+ " WHERE agentnum = ? AND $sql";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_inactive_cust_pkg
+
+Returns the number of inactive customer packages (one-time packages otherwise
+unsuspended/uncancelled) for this agent.
+
+=cut
+
+sub num_inactive_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->inactive_sql);
+}
+
+=item num_susp_cust_pkg
+
+Returns the number of suspended customer packages for this agent.
+
+=cut
+
+sub num_susp_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->susp_sql);
+}
+
+=item num_cancel_cust_pkg
+
+Returns the number of cancelled customer packages for this agent.
+
+=cut
+
+sub num_cancel_cust_pkg {
+ shift->num_pkg_sql(FS::cust_pkg->cancel_sql);
+}
+
+=item generate_reg_codes NUM PKGPART_ARRAYREF
+
+Generates the specified number of registration codes, allowing purchase of the
+specified package definitions. Returns an array reference of the newly
+generated codes, or a scalar error message.
+
+=cut
+
+#false laziness w/prepay_credit::generate
+sub generate_reg_codes {
+ my( $self, $num, $pkgparts ) = @_;
+
+ my @codeset = ( 'A'..'Z' );
+
+ 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 @codes = ();
+ for ( 1 ... $num ) {
+ my $reg_code = new FS::reg_code {
+ 'agentnum' => $self->agentnum,
+ 'code' => join('', map($codeset[int(rand $#codeset)], (0..7) ) ),
+ };
+ my $error = $reg_code->insert($pkgparts);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ push @codes, $reg_code->code;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ \@codes;
+
+}
+
+=item num_reg_code
+
+Returns the number of unused registration codes for this agent.
+
+=cut
+
+sub num_reg_code {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM reg_code WHERE agentnum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_prepay_credit
+
+Returns the number of unused prepaid cards for this agent.
+
+=cut
+
+sub num_prepay_credit {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM prepay_credit WHERE agentnum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+
+=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_payment_gateway.pm b/FS/FS/agent_payment_gateway.pm
new file mode 100644
index 0000000..bd99d0c
--- /dev/null
+++ b/FS/FS/agent_payment_gateway.pm
@@ -0,0 +1,139 @@
+package FS::agent_payment_gateway;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::payment_gateway;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::agent_payment_gateway - Object methods for agent_payment_gateway records
+
+=head1 SYNOPSIS
+
+ use FS::agent_payment_gateway;
+
+ $record = new FS::agent_payment_gateway \%hash;
+ $record = new FS::agent_payment_gateway { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::agent_payment_gateway object represents a payment gateway override for
+a specific agent. FS::agent_payment_gateway inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item agentgatewaynum - primary key
+
+=item agentnum -
+
+=item gatewaynum -
+
+=item cardtype -
+
+=item taxclass -
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new override. To add the override 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 { 'agent_payment_gateway'; }
+
+=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 override. 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('agentgatewaynum')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
+ || $self->ut_foreign_key('gatewaynum', 'payment_gateway', 'gatewaynum' )
+ || $self->ut_textn('cardtype')
+ || $self->ut_textn('taxclass')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item payment_gateway
+
+=cut
+
+sub payment_gateway {
+ my $self = shift;
+ qsearchs('payment_gateway', { 'gatewaynum' => $self->gatewaynum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::payment_gateway>, L<FS::agent>, L<FS::Record>, 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..2660bb4
--- /dev/null
+++ b/FS/FS/agent_type.pm
@@ -0,0 +1,191 @@
+package FS::agent_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch );
+use FS::m2m_Common;
+use FS::agent;
+use FS::type_pkgs;
+
+@ISA = qw( FS::m2m_Common 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 type_pkgs_enabled
+
+Returns all FS::type_pkg objects (see L<FS::type_pkgs>) that link to enabled
+package definitions (see L<FS::part_pkg>).
+
+An additional strange feature is that the returned type_pkg objects also have
+all fields of the associated part_pkg object.
+
+=cut
+
+sub type_pkgs_enabled {
+ my $self = shift;
+ qsearch({
+ 'table' => 'type_pkgs',
+ 'addl_from' => 'JOIN part_pkg USING ( pkgpart )',
+ 'hashref' => { 'typenum' => $self->typenum },
+ 'extra_sql' => " AND ( disabled = '' OR disabled IS NULL )".
+ " ORDER BY pkg",
+ });
+}
+
+=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 BUGS
+
+type_pkgs_enabled should order itself by something (pkg?)
+
+type_pkgs_enabled should populate something that caches for the part_pkg method
+rather than add fields to this object, right? In fact we need a "poop" object
+framework that does that automatically for any joined search at some point....
+right?
+
+=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/banned_pay.pm b/FS/FS/banned_pay.pm
new file mode 100644
index 0000000..a862028
--- /dev/null
+++ b/FS/FS/banned_pay.pm
@@ -0,0 +1,140 @@
+package FS::banned_pay;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::UID qw( getotaker );
+
+=head1 NAME
+
+FS::banned_pay - Object methods for banned_pay records
+
+=head1 SYNOPSIS
+
+ use FS::banned_pay;
+
+ $record = new FS::banned_pay \%hash;
+ $record = new FS::banned_pay { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::banned_pay object represents an banned credit card or ACH account.
+FS::banned_pay inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item bannum - primary key
+
+=item payby - I<CARD> or I<CHEK>
+
+=item payinfo - fingerprint of banned card (base64-encoded MD5 digest)
+
+=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 - reason (text)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new ban. To add the ban 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 { 'banned_pay'; }
+
+=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 ban. 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('bannum')
+ || $self->ut_enum('payby', [ 'CARD', 'CHEK' ] )
+ || $self->ut_text('payinfo')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('reason')
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ $self->otaker(getotaker) unless $self->otaker;
+
+ $self->SUPER::check;
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/category_Common.pm b/FS/FS/category_Common.pm
new file mode 100644
index 0000000..c239a78
--- /dev/null
+++ b/FS/FS/category_Common.pm
@@ -0,0 +1,87 @@
+package FS::category_Common;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch );
+
+=head1 NAME
+
+FS::category_Common - Base class for category (group of classifications) classes
+
+=head1 SYNOPSIS
+
+use base qw( FS::category_Common );
+use FS::class_table; #should use this
+
+#optional for non-standard names
+sub _class_table { 'table_name'; } #default is to replace s/category/class/
+
+=head1 DESCRIPTION
+
+FS::category_Common is a base class for classes which provide a categorization
+(group of classifications) for other classes, such as pkg_category or
+cust_category.
+
+=item delete
+
+Deletes this category from the database. Only categories with no associated
+classifications can be deleted. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a ". $self->table.
+ " with ". $self->_class_table. " records!"
+ if qsearch( $self->_class_table, { 'categorynum' => $self->categorynum } );
+
+ $self->SUPER::delete;
+}
+
+=item check
+
+Checks all fields to make sure this is a valid package category. 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('categorynum')
+ or $self->ut_text('categoryname')
+ or $self->ut_snumbern('weight')
+ or $self->ut_enum('disabled', [ '', 'Y' ])
+ or $self->SUPER::check;
+
+}
+
+=back
+
+=cut
+
+#defaults
+
+use vars qw( $_class_table );
+sub _class_table {
+ return $_class_table if $_class_table;
+ my $self = shift;
+ $_class_table = $self->table;
+ $_class_table =~ s/category/cclass/ # s/_category$/_class/
+ or die "can't determine an automatic class table for $_class_table";
+ $_class_table;
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
new file mode 100644
index 0000000..2426f41
--- /dev/null
+++ b/FS/FS/cdr.pm
@@ -0,0 +1,894 @@
+package FS::cdr;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $me );
+use Exporter;
+use Tie::IxHash;
+use Date::Parse;
+use Date::Format;
+use Time::Local;
+use FS::UID qw( dbh );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs );
+use FS::cdr_type;
+use FS::cdr_calltype;
+use FS::cdr_carrier;
+use FS::cdr_batch;
+use FS::cdr_termination;
+
+@ISA = qw(FS::Record);
+@EXPORT_OK = qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+$DEBUG = 0;
+$me = '[FS::cdr]';
+
+=head1 NAME
+
+FS::cdr - Object methods for cdr records
+
+=head1 SYNOPSIS
+
+ use FS::cdr;
+
+ $record = new FS::cdr \%hash;
+ $record = new FS::cdr { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr object represents an Call Data Record, typically from a telephony
+system or provider of some sort. FS::cdr inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item acctid - primary key
+
+=item calldate - Call timestamp (SQL timestamp)
+
+=item clid - Caller*ID with text
+
+=item src - Caller*ID number / Source number
+
+=item dst - Destination extension
+
+=item dcontext - Destination context
+
+=item channel - Channel used
+
+=item dstchannel - Destination channel if appropriate
+
+=item lastapp - Last application if appropriate
+
+=item lastdata - Last application data
+
+=item startdate - Start of call (UNIX-style integer timestamp)
+
+=item answerdate - Answer time of call (UNIX-style integer timestamp)
+
+=item enddate - End time of call (UNIX-style integer timestamp)
+
+=item duration - Total time in system, in seconds
+
+=item billsec - Total time call is up, in seconds
+
+=item disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+
+=item amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
+
+=cut
+
+ #ignore the "omit" and "documentation" AMAs??
+ #AMA = Automated Message Accounting.
+ #default: Sets the system default.
+ #omit: Do not record calls.
+ #billing: Mark the entry for billing
+ #documentation: Mark the entry for documentation.
+
+=item accountcode - CDR account number to use: account
+
+=item uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+
+=item userfield - CDR user-defined field
+
+=item cdr_type - CDR type - see L<FS::cdr_type> (Usage = 1, S&E = 7, OC&C = 8)
+
+=item charged_party - Service number to be billed
+
+=item upstream_currency - Wholesale currency from upstream
+
+=item upstream_price - Wholesale price from upstream
+
+=item upstream_rateplanid - Upstream rate plan ID
+
+=item rated_price - Rated (or re-rated) price
+
+=item distance - km (need units field?)
+
+=item islocal - Local - 1, Non Local = 0
+
+=item calltypenum - Type of call - see L<FS::cdr_calltype>
+
+=item description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
+
+=item quantity - Number of items (cdr_type 7&8 only)
+
+=item carrierid - Upstream Carrier ID (see L<FS::cdr_carrier>)
+
+=cut
+
+#Telstra =1, Optus = 2, RSL COM = 3
+
+=item upstream_rateid - Upstream Rate ID
+
+=item svcnum - Link to customer service (see L<FS::cust_svc>)
+
+=item freesidestatus - NULL, done (or something)
+
+=item freesiderewritestatus - NULL, done (or something)
+
+=item cdrbatch
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new CDR. To add the CDR 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 { 'cdr'; }
+
+sub table_info {
+ {
+ 'fields' => {
+#XXX fill in some (more) nice names
+ #'acctid' => '',
+ 'calldate' => 'Call date',
+ 'clid' => 'Caller ID',
+ 'src' => 'Source',
+ 'dst' => 'Destination',
+ 'dcontext' => 'Dest. context',
+ 'channel' => 'Channel',
+ 'dstchannel' => 'Destination channel',
+ #'lastapp' => '',
+ #'lastdata' => '',
+ 'startdate' => 'Start date',
+ 'answerdate' => 'Answer date',
+ 'enddate' => 'End date',
+ 'duration' => 'Duration',
+ 'billsec' => 'Billable seconds',
+ 'disposition' => 'Disposition',
+ 'amaflags' => 'AMA flags',
+ 'accountcode' => 'Account code',
+ #'uniqueid' => '',
+ 'userfield' => 'User field',
+ #'cdrtypenum' => '',
+ 'charged_party' => 'Charged party',
+ #'upstream_currency' => '',
+ 'upstream_price' => 'Upstream price',
+ #'upstream_rateplanid' => '',
+ #'ratedetailnum' => '',
+ 'rated_price' => 'Rated price',
+ #'distance' => '',
+ #'islocal' => '',
+ #'calltypenum' => '',
+ #'description' => '',
+ #'quantity' => '',
+ 'carrierid' => 'Carrier ID',
+ #'upstream_rateid' => '',
+ 'svcnum' => 'Freeside service',
+ 'freesidestatus' => 'Freeside status',
+ 'freesiderewritestatus' => 'Freeside rewrite status',
+ 'cdrbatch' => 'Legacy batch',
+ 'cdrbatchnum' => 'Batch',
+ },
+
+ };
+
+}
+
+=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 CDR. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+Note: Unlike most types of records, we don't want to "reject" a CDR and we want
+to process them as quickly as possible, so we allow the database to check most
+of the data.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+# we don't want to "reject" a CDR like other sorts of input...
+# my $error =
+# $self->ut_numbern('acctid')
+## || $self->ut_('calldate')
+# || $self->ut_text('clid')
+# || $self->ut_text('src')
+# || $self->ut_text('dst')
+# || $self->ut_text('dcontext')
+# || $self->ut_text('channel')
+# || $self->ut_text('dstchannel')
+# || $self->ut_text('lastapp')
+# || $self->ut_text('lastdata')
+# || $self->ut_numbern('startdate')
+# || $self->ut_numbern('answerdate')
+# || $self->ut_numbern('enddate')
+# || $self->ut_number('duration')
+# || $self->ut_number('billsec')
+# || $self->ut_text('disposition')
+# || $self->ut_number('amaflags')
+# || $self->ut_text('accountcode')
+# || $self->ut_text('uniqueid')
+# || $self->ut_text('userfield')
+# || $self->ut_numbern('cdrtypenum')
+# || $self->ut_textn('charged_party')
+## || $self->ut_n('upstream_currency')
+## || $self->ut_n('upstream_price')
+# || $self->ut_numbern('upstream_rateplanid')
+## || $self->ut_n('distance')
+# || $self->ut_numbern('islocal')
+# || $self->ut_numbern('calltypenum')
+# || $self->ut_textn('description')
+# || $self->ut_numbern('quantity')
+# || $self->ut_numbern('carrierid')
+# || $self->ut_numbern('upstream_rateid')
+# || $self->ut_numbern('svcnum')
+# || $self->ut_textn('freesidestatus')
+# || $self->ut_textn('freesiderewritestatus')
+# ;
+# return $error if $error;
+
+ $self->calldate( $self->startdate_sql )
+ if !$self->calldate && $self->startdate;
+
+ #was just for $format eq 'taqua' but can't see the harm... add something to
+ #disable if it becomes a problem
+ if ( $self->duration eq '' && $self->enddate && $self->startdate ) {
+ $self->duration( $self->enddate - $self->startdate );
+ }
+ if ( $self->billsec eq '' && $self->enddate && $self->answerdate ) {
+ $self->billsec( $self->enddate - $self->answerdate );
+ }
+
+ $self->set_charged_party;
+
+ #check the foreign keys even?
+ #do we want to outright *reject* the CDR?
+ my $error =
+ $self->ut_numbern('acctid')
+
+ #add a config option to turn these back on if someone needs 'em
+ #
+ # #Usage = 1, S&E = 7, OC&C = 8
+ # || $self->ut_foreign_keyn('cdrtypenum', 'cdr_type', 'cdrtypenum' )
+ #
+ # #the big list in appendix 2
+ # || $self->ut_foreign_keyn('calltypenum', 'cdr_calltype', 'calltypenum' )
+ #
+ # # Telstra =1, Optus = 2, RSL COM = 3
+ # || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item is_tollfree
+
+ Returns true when the cdr represents a toll free number and false otherwise.
+
+=cut
+
+sub is_tollfree {
+ my $self = shift;
+ ( $self->dst =~ /^(\+?1)?8(8|([02-7])\3)/ ) ? 1 : 0;
+}
+
+=item set_charged_party
+
+If the charged_party field is already set, does nothing. Otherwise:
+
+If the cdr-charged_party-accountcode config option is enabled, sets the
+charged_party to the accountcode.
+
+Otherwise sets the charged_party normally: to the src field in most cases,
+or to the dst field if it is a toll free number.
+
+=cut
+
+sub set_charged_party {
+ my $self = shift;
+
+ my $conf = new FS::Conf;
+
+ unless ( $self->charged_party ) {
+
+ if ( $conf->exists('cdr-charged_party-accountcode') && $self->accountcode ){
+
+ my $charged_party = $self->accountcode;
+ $charged_party =~ s/^0+//
+ if $conf->exists('cdr-charged_party-accountcode-trim_leading_0s');
+ $self->charged_party( $charged_party );
+
+ } else {
+
+ if ( $self->is_tollfree ) {
+ $self->charged_party($self->dst);
+ } else {
+ $self->charged_party($self->src);
+ }
+
+ }
+
+ }
+
+# my $prefix = $conf->config('cdr-charged_party-truncate_prefix');
+# my $prefix_len = length($prefix);
+# my $trunc_len = $conf->config('cdr-charged_party-truncate_length');
+#
+# $self->charged_party( substr($self->charged_party, 0, $trunc_len) )
+# if $prefix_len && $trunc_len
+# && substr($self->charged_party, 0, $prefix_len) eq $prefix;
+
+}
+
+=item set_status_and_rated_price STATUS [ RATED_PRICE [ SVCNUM ] ]
+
+Sets the status to the provided string. If there is an error, returns the
+error, otherwise returns false.
+
+=cut
+
+sub set_status_and_rated_price {
+ my($self, $status, $rated_price, $svcnum, %opt) = @_;
+ if($opt{'inbound'}) {
+ my $term = qsearchs('cdr_termination', {
+ acctid => $self->acctid,
+ termpart => 1 # inbound
+ });
+ my $error;
+ if($term) {
+ warn "replacing existing cdr status (".$self->acctid.")\n" if $term;
+ $error = $term->delete;
+ return $error if $error;
+ }
+ $term = FS::cdr_termination->new({
+ acctid => $self->acctid,
+ termpart => 1,
+ rated_price => $rated_price,
+ status => $status,
+ svcnum => $svcnum,
+ });
+ return $term->insert;
+ }
+ else {
+ $self->freesidestatus($status);
+ $self->rated_price($rated_price);
+ $self->svcnum($svcnum) if $svcnum;
+ return $self->replace();
+ }
+}
+
+=item calldate_unix
+
+Parses the calldate in SQL string format and returns a UNIX timestamp.
+
+=cut
+
+sub calldate_unix {
+ str2time(shift->calldate);
+}
+
+=item startdate_sql
+
+Parses the startdate in UNIX timestamp format and returns a string in SQL
+format.
+
+=cut
+
+sub startdate_sql {
+ my($sec,$min,$hour,$mday,$mon,$year) = localtime(shift->startdate);
+ $mon++;
+ $year += 1900;
+ "$year-$mon-$mday $hour:$min:$sec";
+}
+
+=item cdr_carrier
+
+Returns the FS::cdr_carrier object associated with this CDR, or false if no
+carrierid is defined.
+
+=cut
+
+my %carrier_cache = ();
+
+sub cdr_carrier {
+ my $self = shift;
+ return '' unless $self->carrierid;
+ $carrier_cache{$self->carrierid} ||=
+ qsearchs('cdr_carrier', { 'carrierid' => $self->carrierid } );
+}
+
+=item carriername
+
+Returns the carrier name (see L<FS::cdr_carrier>), or the empty string if
+no FS::cdr_carrier object is assocated with this CDR.
+
+=cut
+
+sub carriername {
+ my $self = shift;
+ my $cdr_carrier = $self->cdr_carrier;
+ $cdr_carrier ? $cdr_carrier->carriername : '';
+}
+
+=item cdr_calltype
+
+Returns the FS::cdr_calltype object associated with this CDR, or false if no
+calltypenum is defined.
+
+=cut
+
+my %calltype_cache = ();
+
+sub cdr_calltype {
+ my $self = shift;
+ return '' unless $self->calltypenum;
+ $calltype_cache{$self->calltypenum} ||=
+ qsearchs('cdr_calltype', { 'calltypenum' => $self->calltypenum } );
+}
+
+=item calltypename
+
+Returns the call type name (see L<FS::cdr_calltype>), or the empty string if
+no FS::cdr_calltype object is assocated with this CDR.
+
+=cut
+
+sub calltypename {
+ my $self = shift;
+ my $cdr_calltype = $self->cdr_calltype;
+ $cdr_calltype ? $cdr_calltype->calltypename : '';
+}
+
+=item downstream_csv [ OPTION => VALUE, ... ]
+
+=cut
+
+my %export_names = (
+ 'simple' => {
+ 'name' => 'Simple',
+ 'invoice_header' => "Date,Time,Name,Destination,Duration,Price",
+ },
+ 'simple2' => {
+ 'name' => 'Simple with source',
+ 'invoice_header' => "Date,Time,Called From,Destination,Duration,Price",
+ #"Date,Time,Name,Called From,Destination,Duration,Price",
+ },
+ 'default' => {
+ 'name' => 'Default',
+ 'invoice_header' => 'Date,Time,Number,Destination,Duration,Price',
+ },
+ 'source_default' => {
+ 'name' => 'Default with source',
+ 'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
+ },
+ 'accountcode_default' => {
+ 'name' => 'Default plus accountcode',
+ 'invoice_header' => 'Date,Time,Account,Number,Destination,Duration,Price',
+ },
+);
+
+my $duration_sub = sub {
+ my($cdr, %opt) = @_;
+ if ( $opt{minutes} ) {
+ $opt{minutes}. ( $opt{granularity} ? 'm' : ' call' );
+ } else {
+ #config if anyone really wants decimal minutes back
+ #sprintf('%.2fm', $cdr->billsec / 60 );
+ int($cdr->billsec / 60).'m '. ($cdr->billsec % 60).'s';
+ }
+};
+
+my %export_formats = (
+ 'simple' => [
+ sub { time2str('%D', shift->calldate_unix ) }, #DATE
+ sub { time2str('%r', shift->calldate_unix ) }, #TIME
+ 'userfield', #USER
+ 'dst', #NUMBER_DIALED
+ $duration_sub, #DURATION
+ #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+ sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
+ ],
+ 'simple2' => [
+ sub { time2str('%D', shift->calldate_unix ) }, #DATE
+ sub { time2str('%r', shift->calldate_unix ) }, #TIME
+ #'userfield', #USER
+ 'src', #called from
+ 'dst', #NUMBER_DIALED
+ $duration_sub, #DURATION
+ #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+ sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
+ ],
+ 'default' => [
+
+ #DATE
+ sub { time2str('%D', shift->calldate_unix ) },
+ # #time2str("%Y %b %d - %r", $cdr->calldate_unix ),
+
+ #TIME
+ sub { time2str('%r', shift->calldate_unix ) },
+ # time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot
+
+ #DEST ("Number")
+ sub { my($cdr, %opt) = @_; $opt{pretty_dst} || $cdr->dst; },
+
+ #REGIONNAME ("Destination")
+ sub { my($cdr, %opt) = @_; $opt{dst_regionname}; },
+
+ #DURATION
+ $duration_sub,
+
+ #PRICE
+ sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; },
+
+ ],
+);
+$export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ];
+$export_formats{'accountcode_default'} =
+ [ @{ $export_formats{'default'} }[0,1],
+ 'accountcode',
+ @{ $export_formats{'default'} }[2..5],
+ ];
+
+sub downstream_csv {
+ my( $self, %opt ) = @_;
+
+ my $format = $opt{'format'};
+ return "Unknown format $format" unless exists $export_formats{$format};
+
+ #my $conf = new FS::Conf;
+ #$opt{'money_char'} ||= $conf->config('money_char') || '$';
+ $opt{'money_char'} ||= FS::Conf->new->config('money_char') || '$';
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ my @columns =
+ map {
+ ref($_) ? &{$_}($self, %opt) : $self->$_();
+ }
+ @{ $export_formats{$format} };
+
+ my $status = $csv->combine(@columns);
+ die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV"
+ unless $status;
+
+ $csv->string;
+
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item invoice_formats
+
+Returns an ordered list of key value pairs containing invoice format names
+as keys (for use with part_pkg::voip_cdr) and "pretty" format names as values.
+
+=cut
+
+sub invoice_formats {
+ map { ($_ => $export_names{$_}->{'name'}) }
+ grep { $export_names{$_}->{'invoice_header'} }
+ keys %export_names;
+}
+
+=item invoice_header FORMAT
+
+Returns a scalar containing the CSV column header for invoice format FORMAT.
+
+=cut
+
+sub invoice_header {
+ my $format = shift;
+ $export_names{$format}->{'invoice_header'};
+}
+
+=item import_formats
+
+Returns an ordered list of key value pairs containing import format names
+as keys (for use with batch_import) and "pretty" format names as values.
+
+=cut
+
+#false laziness w/part_pkg & part_export
+
+my %cdr_info;
+foreach my $INC ( @INC ) {
+ warn "globbing $INC/FS/cdr/*.pm\n" if $DEBUG;
+ foreach my $file ( glob("$INC/FS/cdr/*.pm") ) {
+ warn "attempting to load CDR format info from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/cdr/: $file\n";
+ next;
+ };
+ my $mod = $1;
+ my $info = eval "use FS::cdr::$mod; ".
+ "\\%FS::cdr::$mod\::info;";
+ if ( $@ ) {
+ die "error using FS::cdr::$mod (skipping): $@\n" if $@;
+ next;
+ }
+ unless ( keys %$info ) {
+ warn "no %info hash found in FS::cdr::$mod, skipping\n";
+ next;
+ }
+ warn "got CDR format info from FS::cdr::$mod: $info\n" if $DEBUG;
+ if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
+ warn "skipping disabled CDR format FS::cdr::$mod" if $DEBUG;
+ next;
+ }
+ $cdr_info{$mod} = $info;
+ }
+}
+
+tie my %import_formats, 'Tie::IxHash',
+ map { $_ => $cdr_info{$_}->{'name'} }
+ sort { $cdr_info{$a}->{'weight'} <=> $cdr_info{$b}->{'weight'} }
+ grep { exists($cdr_info{$_}->{'import_fields'}) }
+ keys %cdr_info;
+
+sub import_formats {
+ %import_formats;
+}
+
+sub _cdr_min_parser_maker {
+ my $field = shift;
+ my @fields = ref($field) ? @$field : ($field);
+ @fields = qw( billsec duration ) unless scalar(@fields) && $fields[0];
+ return sub {
+ my( $cdr, $min ) = @_;
+ my $sec = eval { _cdr_min_parse($min) };
+ die "error parsing seconds for @fields from $min minutes: $@\n" if $@;
+ $cdr->$_($sec) foreach @fields;
+ };
+}
+
+sub _cdr_min_parse {
+ my $min = shift;
+ sprintf('%.0f', $min * 60 );
+}
+
+sub _cdr_date_parser_maker {
+ my $field = shift;
+ my %options = @_;
+ my @fields = ref($field) ? @$field : ($field);
+ return sub {
+ my( $cdr, $datestring ) = @_;
+ my $unixdate = eval { _cdr_date_parse($datestring, %options) };
+ die "error parsing date for @fields from $datestring: $@\n" if $@;
+ $cdr->$_($unixdate) foreach @fields;
+ };
+}
+
+sub _cdr_date_parse {
+ my $date = shift;
+ my %options = @_;
+
+ return '' unless length($date); #that's okay, it becomes NULL
+ return '' if $date eq 'NA'; #sansay
+
+ if ( $date =~ /^([a-z]{3})\s+([a-z]{3})\s+(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s+(\d{4})$/i && $7 > 1970 ) {
+ my $time = str2time($date);
+ return $time if $time > 100000; #just in case
+ }
+
+ my($year, $mon, $day, $hour, $min, $sec);
+
+ #$date =~ /^\s*(\d{4})[\-\/]\(\d{1,2})[\-\/](\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/
+ #taqua #2007-10-31 08:57:24.113000000
+
+ if ( $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\D+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/ ) {
+ ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+ } elsif ( $date =~ /^\s*(\d{1,2})\D(\d{1,2})\D(\d{4})\s+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/ ) {
+ ($mon, $day, $year, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+ } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d+\.\d+)(\D|$)/ ) {
+ # broadsoft: 20081223201938.314
+ ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+ } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ ) {
+ # WIP: 20100329121420
+ ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+ } else {
+ die "unparsable date: $date"; #maybe we shouldn't die...
+ }
+
+ return '' if ( $year == 1900 || $year == 1970 ) && $mon == 1 && $day == 1
+ && $hour == 0 && $min == 0 && $sec == 0;
+
+ if ($options{gmt}) {
+ timegm($sec, $min, $hour, $day, $mon-1, $year);
+ } else {
+ timelocal($sec, $min, $hour, $day, $mon-1, $year);
+ }
+}
+
+=item batch_import HASHREF
+
+Imports CDR records. Available options are:
+
+=over 4
+
+=item file
+
+Filename
+
+=item format
+
+=item params
+
+Hash reference of preset fields, typically cdrbatch
+
+=item empty_ok
+
+Set true to prevent throwing an error on empty imports
+
+=back
+
+=cut
+
+my %import_options = (
+ 'table' => 'cdr',
+
+ 'batch_keycol' => 'cdrbatchnum',
+ 'batch_table' => 'cdr_batch',
+ 'batch_namecol' => 'cdrbatch',
+
+ 'formats' => { map { $_ => $cdr_info{$_}->{'import_fields'}; }
+ keys %cdr_info
+ },
+
+ #drop the || 'csv' to allow auto xls for csv types?
+ 'format_types' => { map { $_ => ( lc($cdr_info{$_}->{'type'}) || 'csv' ); }
+ keys %cdr_info
+ },
+
+ 'format_headers' => { map { $_ => ( $cdr_info{$_}->{'header'} || 0 ); }
+ keys %cdr_info
+ },
+
+ 'format_sep_chars' => { map { $_ => $cdr_info{$_}->{'sep_char'}; }
+ keys %cdr_info
+ },
+
+ 'format_fixedlength_formats' =>
+ { map { $_ => $cdr_info{$_}->{'fixedlength_format'}; }
+ keys %cdr_info
+ },
+);
+
+sub _import_options {
+ \%import_options;
+}
+
+sub batch_import {
+ my $opt = shift;
+
+ my $iopt = _import_options;
+ $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
+
+ FS::Record::batch_import( $opt );
+
+}
+
+=item process_batch_import
+
+=cut
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $opt = _import_options;
+# $opt->{'params'} = [ 'format', 'cdrbatch' ];
+
+ FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+# if ( $format eq 'simple' ) { #should be a callback or opt in FS::cdr::simple
+# @columns = map { s/^ +//; $_; } @columns;
+# }
+
+# _ upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data {
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ my $sth = dbh->prepare(
+ 'SELECT DISTINCT(cdrbatch) FROM cdr WHERE cdrbatch IS NOT NULL'
+ ) or die dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+ my %cdrbatchnum = ();
+ while (my $row = $sth->fetchrow_arrayref) {
+ my $cdr_batch = new FS::cdr_batch { 'cdrbatch' => $row->[0] };
+ my $error = $cdr_batch->insert;
+ die $error if $error;
+ $cdrbatchnum{$row->[0]} = $cdr_batch->cdrbatchnum;
+ }
+
+ $sth = dbh->prepare('UPDATE cdr SET cdrbatch = NULL, cdrbatchnum = ? WHERE cdrbatch IS NOT NULL AND cdrbatch = ?') or die dbh->errstr;
+
+ foreach my $cdrbatch (keys %cdrbatchnum) {
+ $sth->execute($cdrbatchnum{$cdrbatch}, $cdrbatch) or die $sth->errstr;
+ }
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr/asterisk.pm b/FS/FS/cdr/asterisk.pm
new file mode 100644
index 0000000..8b29642
--- /dev/null
+++ b/FS/FS/cdr/asterisk.pm
@@ -0,0 +1,45 @@
+package FS::cdr::asterisk;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+#http://www.the-asterisk-book.com/unstable/funktionen-cdr.html
+my %amaflags = (
+ DEFAULT => 0,
+ OMIT => 1, #asterisk 1.4+
+ IGNORE => 1, #asterisk 1.2
+ BILLING => 2, #asterisk 1.4+
+ BILL => 2, #asterisk 1.2
+ DOCUMENTATION => 3,
+ #? '' => 0,
+);
+
+%info = (
+ 'name' => 'Asterisk',
+ 'weight' => 10,
+ 'import_fields' => [
+ 'accountcode',
+ 'src',
+ 'dst',
+ 'dcontext',
+ 'clid',
+ 'channel',
+ 'dstchannel',
+ 'lastapp',
+ 'lastdata',
+ _cdr_date_parser_maker('startdate'),
+ _cdr_date_parser_maker('answerdate'),
+ _cdr_date_parser_maker('enddate'),
+ 'duration',
+ 'billsec',
+ 'disposition',
+ sub { my($cdr, $amaflags) = @_; $cdr->amaflags($amaflags{$amaflags}); },
+ 'uniqueid',
+ 'userfield',
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/bell_west.pm b/FS/FS/cdr/bell_west.pm
new file mode 100644
index 0000000..f745bb1
--- /dev/null
+++ b/FS/FS/cdr/bell_west.pm
@@ -0,0 +1,122 @@
+package FS::cdr::bell_west;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info $tmp_mon $tmp_mday $tmp_year );
+use Time::Local;
+#use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Bell West',
+ 'weight' => 210,
+ 'header' => 1,
+ 'type' => 'xls',
+
+ 'import_fields' => [
+
+ # CDR FIELD / REQUIRED / Notes
+
+ # CHG TYPE / No / Internal Code only (no need to import)
+ sub {},
+
+ # ACCOUNT # / No / Internal Number only (no need to import)
+ sub {},
+
+ # DATE / Yes / "DATE" Excel date format MM/DD/YYYY
+ # XXX false laziness w/troop.pm
+ sub { my($cdr, $date) = @_;
+
+ my $datetime = DateTime::Format::Excel->parse_datetime( $date );
+ $tmp_mon = $datetime->mon_0;
+ $tmp_mday = $datetime->mday;
+ $tmp_year = $datetime->year;
+ },
+
+ # CUST NO / Yes / "TIME" "075959" Text based time
+ # Note: This is really the start time but Bell header says "Cust No" which
+ # is wrong
+ sub { my($cdr, $time) = @_;
+ #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
+ $time =~ /^(\d{2})(\d{2})(\d{2})$/
+ or die "unparsable time: $time"; #maybe we shouldn't die...
+ #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
+ $cdr->startdate(
+ timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)
+ );
+ },
+
+ # BTN / Yes / Main billing number but not DID or real number
+ # (put in SRC field)
+ 'src',
+
+ # ORIG CITY / No / We will use your Freeside rating and description name
+ 'channel',
+
+ # TERM / YES / All calls should be billed, however all calls are
+ # missing "1+" and "011+" & DIR ASST = "411"
+ 'dst',
+
+ # TERM CITY / No / We will use your Freeside rating and description name
+ 'dstchannel',
+
+ # WTN / Yes / Bill to number (put in "charged_party")
+ 'charged_party',
+
+ # CODE / Yes / Account Code (security) and we need on invoice
+ 'accountcode',
+
+ # PROV/COUNTRY / No / We will use your Freeside rating and description name
+ # (but use this to add "011" for "International" calls)
+ sub { my( $cdr, $prov ) = @_;
+ my $pre = ( $prov =~ /^\s*International\s*/i ) ? '011' : '1';
+ $cdr->dst( $pre. $cdr->dst ) unless $cdr->dst =~ /^$pre/;
+ },
+
+ # CALL TYPE / Possibly / Not sure if you need this to determine correct
+ # billing method ?
+ # DDD normal call (Direct Dial Dsomething? ="LD"?)
+ # TF Toll Free
+ # (toll free dst# should be sufficient to rate)
+ # DAT Directory AssisTance
+ # (dst# 411 "area code" should be sufficient to rate)
+ # DNS (Another sort of directory assistance?... only one record with
+ # "8195551212" in the dst#)
+ 'dcontext', #probably don't need... map to cdr_type? calltypenum?
+
+ # DURATION Yes Units = seconds
+ 'billsec', #need to trim .00 ?
+
+ # AMOUNT CHARGED No Will use Freeside rating and description name
+ sub { my( $cdr, $amount) = @_;
+ $amount =~ s/^\$//;
+ $cdr->upstream_price( $amount );
+ },
+
+ ],
+
+);
+
+1;
+
+__END__
+
+CHG TYPE (unused)
+ACCOUNT # (unused)
+
+DATE startdate (+ CUST NO)
+CUST NO (startdate time)
+ - Start of call (UNIX-style integer timestamp)
+
+BTN *src - Caller*ID number / Source number
+ORIG CITY channel - Channel used
+TERM # *dst - Destination extension
+TERM CITY dstchannel - Destination channel if appropriate
+WTN *charged_party - Service number to be billed
+CODE *accountcode - CDR account number to use: account
+
+PROV/COUNTRY (used to prefix TERM # w/ 1 or 011)
+
+CALL TYPE dcontext - Destination context
+DURATION *billsec - Total time call is up, in seconds
+AMOUNT CHARGED *upstream_price - Wholesale price from upstream
+
diff --git a/FS/FS/cdr/broadsoft.pm b/FS/FS/cdr/broadsoft.pm
new file mode 100644
index 0000000..423e96f
--- /dev/null
+++ b/FS/FS/cdr/broadsoft.pm
@@ -0,0 +1,108 @@
+package FS::cdr::broadsoft;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Broadsoft',
+ 'weight' => 500,
+ 'header' => 1, #0 default, set to 1 to ignore the first line, or
+ # to higher numbers to ignore that number of lines
+ 'type' => 'csv', #csv (default), fixedlength or xls
+ 'sep_char' => ',', #for csv, defaults to ,
+ 'disabled' => 0, #0 default, set to 1 to disable
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+
+ skip(2),
+ sub { my($cdr, $data, $conf, $param) = @_;
+ $param->{skiprow} = 1 if lc($data) ne 'normal';
+ '' }, # 3: type
+
+ trim('accountcode'), # 4: userNumber
+ skip(2),
+ trim('src'), # 7: callingNumber
+ skip(1),
+ trim('dst'), # 9: calledNumber
+
+ _cdr_date_parser_maker('startdate'), # 10: startTime
+ skip(1),
+ sub { my($cdr, $data) = @_;
+ $cdr->disposition(
+ lc($data) eq 'yes' ?
+ 'ANSWERED' : 'NO ANSWER') }, # 12: answerIndicator
+ _cdr_date_parser_maker('answerdate'), # 13: answerTime
+ _cdr_date_parser_maker('enddate'), # 14: releaseTime
+
+ ],
+
+);
+
+sub trim {
+ my $fieldname = shift;
+ return sub {
+ my($cdr, $data) = @_;
+ $data =~ s/^\+1//;
+ $cdr->$fieldname($data);
+ ''
+ }
+}
+
+sub skip {
+ map { undef } (1..$_[0]);
+}
+
+1;
+
+__END__
+
+list of freeside CDR fields, useful ones marked with *
+
+ acctid - primary key
+ *[1] calldate - Call timestamp (SQL timestamp)
+ clid - Caller*ID with text
+7 * src - Caller*ID number / Source number
+9 * dst - Destination extension
+ dcontext - Destination context
+ channel - Channel used
+ dstchannel - Destination channel if appropriate
+ lastapp - Last application if appropriate
+ lastdata - Last application data
+10 * startdate - Start of call (UNIX-style integer timestamp)
+13 answerdate - Answer time of call (UNIX-style integer timestamp)
+14 * enddate - End time of call (UNIX-style integer timestamp)
+ * duration - Total time in system, in seconds
+ * billsec - Total time call is up, in seconds
+12 *[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+ amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+ channel basis like accountcode.
+4 *[3] accountcode - CDR account number to use: account
+ uniqueid - Unique channel identifier
+ userfield - CDR user-defined field
+ cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+ *[4] charged_party - Service number to be billed
+ upstream_currency - Wholesale currency from upstream
+ *[5] upstream_price - Wholesale price from upstream
+ upstream_rateplanid - Upstream rate plan ID
+ rated_price - Rated (or re-rated) price
+ distance - km (need units field?)
+ islocal - Local - 1, Non Local = 0
+ *[6] calltypenum - Type of call - see FS::cdr_calltype
+ description - Description (cdr_type 7&8 only) (used for
+ cust_bill_pkg.itemdesc)
+ quantity - Number of items (cdr_type 7&8 only)
+ carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+ upstream_rateid - Upstream Rate ID
+ svcnum - Link to customer service (see FS::cust_svc)
+ freesidestatus - NULL, done (or something)
+
+[1] Auto-populated from startdate if not present
+[2] Package options available to ignore calls without a specific disposition
+[3] When using 'cdr-charged_party-accountcode' config
+[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present
+[5] When using 'upstream_simple' rating method.
+[6] Set to usage class classnum when using pre-rated CDRs and usage class-based
+ taxation (local/intrastate/interstate/international)
diff --git a/FS/FS/cdr/genband.pm b/FS/FS/cdr/genband.pm
new file mode 100644
index 0000000..619d908
--- /dev/null
+++ b/FS/FS/cdr/genband.pm
@@ -0,0 +1,120 @@
+package FS::cdr::genband;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'GenBand (Tekelec)', #'Genband G6 (Tekelec T6000)',
+ 'weight' => 140,
+ 'type' => 'fixedlength',
+ 'fixedlength_format' => [qw(
+ Type:2:1:2
+ Sequence:4:3:6
+ OIDCall:30:7:36
+ StartTime:19:37:55
+ AnswerTime:19:56:74
+ EndTime:19:75:93
+ SourceName:30:94:123
+ SourceEndName:30:124:153
+ SourceCallerID:20:154:173
+ SourceCallerName:30:174:203
+ DestinationName:30:204:233
+ DestinationEndName:30:234:263
+ DestCallerID:20:264:283
+ DestCallerIDInfo:30:284:313
+ DialedDigits:30:314:343
+ Billing:30:344:373
+ AuthCode:30:374:403
+ CallDirection:1:404:404
+ ExtendedCall:1:405:405
+ ExternalCall:1:406:406
+ Duration:9:407:415
+ SIPCallID:64:416:479
+ IncomingDigits:30:480:509
+ OutpulsedDigits:30:510:539
+ CarrierIdentificationCode:4:540:543
+ CompletionReason:4:544:547
+ OriginationPartition:30:548:577
+ DestinationPartition:30:578:607
+ BilledSourceDID:20:608:627
+ OriginalCall:30:628:657
+ VideoCall:1:658:658
+ )],
+ 'import_fields' => [
+ sub {}, #Type:2:1:2
+ sub {}, #Sequence:4:3:6
+ 'uniqueid', #OIDCall:30:7:36
+ _cdr_date_parser_maker('startdate'), #StartTime:19:37:55
+ _cdr_date_parser_maker('answerdate'), #AnswerTime:19:56:74
+ _cdr_date_parser_maker('enddate'), #EndTime:19:75:93
+ sub {}, #SourceName:30:94:123
+ 'channel', #SourceEndName:30:124:153
+ 'src', #SourceCallerID:20:154:173
+ 'clid', #SourceCallerName:30:174:203
+ sub {}, #DestinationName:30:204:233
+ 'dstchannel', #DestinationEndName:30:234:263
+ 'dst', #DestCallerID:20:264:283
+ sub {}, #DestCallerIDInfo:30:284:313
+ sub {}, #DialedDigits:30:314:343
+ sub {}, #Billing:30:344:373
+ sub {}, #AuthCode:30:374:403
+ sub {}, #CallDirection:1:404:404
+ sub {}, #ExtendedCall:1:405:405
+ sub {}, #ExternalCall:1:406:406
+ sub { my( $cdr, $duration ) = @_;
+ $cdr->duration($duration);
+ $cdr->billsec($duration); }, #'duration', #Duration:9:407:415
+ sub {}, #SIPCallID:64:416:479
+ sub {}, #IncomingDigits:30:480:509
+ sub {}, #OutpulsedDigits:30:510:539
+ sub {}, #CarrierIdentificationCode:4:540:543
+ sub {}, #CompletionReason:4:544:547
+ sub {}, #OriginationPartition:30:548:577
+ sub {}, #DestinationPartition:30:578:607
+ sub {}, #BilledSourceDID:20:608:627
+ sub {}, #OriginalCall:30:628:657
+ sub {}, #VideoCall:1:658:658
+ ],
+);
+# acctid - primary key
+# calldate - Call timestamp (SQL timestamp)
+# clid - Caller*ID with text
+# src - Caller*ID number / Source number
+# dst - Destination extension
+# dcontext - Destination context
+# channel - Channel used
+# dstchannel - Destination channel if appropriate
+# lastapp - Last application if appropriate
+# lastdata - Last application data
+# startdate - Start of call (UNIX-style integer timestamp)
+# answerdate - Answer time of call (UNIX-style integer timestamp)
+# enddate - End time of call (UNIX-style integer timestamp)
+# duration - Total time in system, in seconds
+# billsec - Total time call is up, in seconds
+# disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+# amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+# channel basis like accountcode.
+# accountcode - CDR account number to use: account
+# uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+# userfield - CDR user-defined field
+# cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+# charged_party - Service number to be billed
+# upstream_currency - Wholesale currency from upstream
+# upstream_price - Wholesale price from upstream
+# upstream_rateplanid - Upstream rate plan ID
+# rated_price - Rated (or re-rated) price
+# distance - km (need units field?)
+# islocal - Local - 1, Non Local = 0
+# calltypenum - Type of call - see FS::cdr_calltype
+# description - Description (cdr_type 7&8 only) (used for
+# cust_bill_pkg.itemdesc)
+# quantity - Number of items (cdr_type 7&8 only)
+# carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+# upstream_rateid - Upstream Rate ID
+# svcnum - Link to customer service (see FS::cust_svc)
+# freesidestatus - NULL, done (or something)
+
+1;
diff --git a/FS/FS/cdr/genband_meetme.pm b/FS/FS/cdr/genband_meetme.pm
new file mode 100644
index 0000000..d87dd8f
--- /dev/null
+++ b/FS/FS/cdr/genband_meetme.pm
@@ -0,0 +1,17 @@
+package FS::cdr::genband_meetme;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Genband (Tekelec) Meet-Me Conference', #'Genband G6 (Tekelec T6000) Meet-Me Conference Log Records',
+ 'weight' => 145,
+ 'disabled' => 1,
+ 'import_fields' => [
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/indosoft.pm b/FS/FS/cdr/indosoft.pm
new file mode 100644
index 0000000..cb25089
--- /dev/null
+++ b/FS/FS/cdr/indosoft.pm
@@ -0,0 +1,71 @@
+package FS::cdr::indosoft;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Indosoft Conference Bridge',
+ 'weight' => 300,
+ 'header' => 1,
+ 'type' => 'csv',
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+
+ #cdr_id
+ 'uniqueid',
+
+ #connect_time
+ _cdr_date_parser_maker( ['startdate', 'answerdate' ] ),
+
+ #disconnect_time
+ _cdr_date_parser_maker('enddate'),
+
+ #account_id
+ 'accountcode',
+
+ #conference_id
+ 'userfield',
+
+ #client_id
+ 'charged_party',
+
+ #pin_used
+ 'dcontext',
+
+ #channel
+ 'channel',
+
+ #clid
+ #'src',
+ sub { my($cdr, $clid) = @_;
+ $cdr->clid( $clid ); #because they called it 'clid' explicitly
+ $cdr->src( $clid );
+ },
+
+ #dnis
+ 'dst',
+
+ #call_status
+ 'disposition',
+
+ #conf_billing_code
+ 'lastapp', #arbitrary
+
+ #participant_id
+ 'lastdata', #arbitrary
+
+ #codr_id
+ 'dstchannel', #arbitrary
+
+ #call_type
+ 'description',
+
+ ],
+
+);
+
+1;
+
diff --git a/FS/FS/cdr/netcentrex.pm b/FS/FS/cdr/netcentrex.pm
new file mode 100644
index 0000000..a434d5d
--- /dev/null
+++ b/FS/FS/cdr/netcentrex.pm
@@ -0,0 +1,783 @@
+package FS::cdr::netcentrex;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+#close enough http://wiki.freeswitch.org/wiki/Hangup_causes
+#my %disposition = (
+# 16 => 'ANSWERED',
+# 17 => 'BUSY',
+# 18 => 'NO USER RESPONSE',
+# 19 => 'NO ANSWER',
+# 156 => '??' #???
+#);
+
+%info = (
+ 'name' => 'NetCentrex',
+ 'weight' => 150,
+ 'type' => 'csv',
+ 'sep_char' => ';',
+ 'import_fields' => [
+ '', #00 SU Identifier
+ '', #01 SU IP Address
+ '', #02 Conference ID
+ '', #03 Call ID
+ '', #04 Leg number (all 0)
+ _cdr_date_parser_maker('startdate'), #05 Authorize timestamp
+ _cdr_date_parser_maker('answerdate'), #06 Start timestamp
+ sub { my( $cdr, $duration ) = @_; #07 Duration
+ $cdr->duration($duration);
+ $cdr->billsec( $duration);
+ },
+ _e164_parser_maker('src', 'charged_party'), #08 Caller
+ _e164_parser_maker('dcontext', 'dst', 'norewrite_pivotonly'=>1) ,#09 Callee
+ 'channel', #10 Source IP
+ 'dstchannel', #11 Destination IP
+ 'userfield', #12 selector Tag
+ '', #13 *service Tag
+ '', #14 *announcement Tag
+ '', #15 *route Table Tag
+ '', #16 vTrunkGroup Tag
+ '', #17 vTrunk Tag XXX ? another userfield?
+ '', #18 *termination Tag
+ '', #19 *location group Tag
+ '', #20 *GK Originating IP
+ '', #21 *GK Terminating IP
+ '', #22 *GK Originating Domain
+ '', #23 *GK Terminating Domain
+ '', #24 Malicious Call (all 0)
+ '', #25 Service (all 0)
+ 'disposition', #26 Termination Cause 16/17/18/156
+ '', #27 Simulation Call (all 0) supposedly don't bill 1
+ '', #28 Type (all C)
+ _cdr_date_parser_maker('enddate'), #29 ReleaseTimeStamp
+ #seems empty from here in sampes...
+ '', #30
+ '', #31
+ '', #32
+ '', #33
+ '', #34
+ '', #35
+ '', #36
+ '', #37
+ '', #38
+ '', #39
+ '', #40
+ '', #41
+ '', #42
+ '', #43
+ '', #44
+ '', #45
+ '', #46
+ '', #47
+ '', #48
+ '', #49
+ '', #50
+
+ # * empty
+ ],
+
+);
+
+sub _e164_parser_maker {
+ my( $field, $pivot_field, %opt ) = @_;
+ return sub {
+ my( $cdr, $e164 ) = @_;
+ my( $pivot, $number ) = _e164_parse($e164);
+ if ( $opt{'norewrite_pivotonly'} && ! $pivot ) {
+ $cdr->$pivot_field( $number );
+ } else {
+ $cdr->$field( $number );
+ $cdr->$pivot_field( $pivot );
+ }
+ };
+}
+
+sub _e164_parse {
+ my $e164 = shift;
+
+ $e164 =~ s/^e164://;
+
+ my ($pivot, $number);
+ if ( $e164 =~ /^O(\d+)$/ ) {
+ $pivot = ''; #?
+ $number = $1;
+ } elsif ( $e164 =~ /^000000(\d+)$/ ) {
+ $pivot = '';
+ $number = $1;
+ } elsif ( $e164 =~ /^(1\d{5})(\d+)$/ ) {
+ $pivot = $1;
+ $number = $2;
+ } else {
+ $pivot = '';
+ $number = $e164; #unparsable...
+ }
+
+ ( $pivot, $number );
+}
+
+1;
+
+=pod
+
+ calldate - Call timestamp (SQL timestamp)
+ clid - Caller*ID with text
+ src - Caller*ID number / Source number
+ dst - Destination extension
+ dcontext - Destination context
+ channel - Channel used
+ dstchannel - Destination channel if appropriate
+ lastapp - Last application if appropriate
+ lastdata - Last application data
+ startdate - Start of call (UNIX-style integer timestamp)
+ answerdate - Answer time of call (UNIX-style integer timestamp)
+ enddate - End time of call (UNIX-style integer timestamp)
+ duration - Total time in system, in seconds
+ billsec - Total time call is up, in seconds
+ disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+ amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+ channel basis like accountcode.
+ accountcode - CDR account number to use: account
+ uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+ userfield - CDR user-defined field
+ cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+ charged_party - Service number to be billed
+ upstream_currency - Wholesale currency from upstream
+ upstream_price - Wholesale price from upstream
+ upstream_rateplanid - Upstream rate plan ID
+ rated_price - Rated (or re-rated) price
+ distance - km (need units field?)
+ islocal - Local - 1, Non Local = 0
+ calltypenum - Type of call - see FS::cdr_calltype
+ description - Description (cdr_type 7&8 only) (used for
+ cust_bill_pkg.itemdesc)
+ quantity - Number of items (cdr_type 7&8 only)
+ carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+ upstream_rateid - Upstream Rate ID
+ svcnum - Link to customer service (see FS::cust_svc)
+ freesidestatus - NULL, done (or something)
+ cdrbatch
+
+No. Field Type/Length Format / Remarks Description Example
+00 SU Identifier String This field is never empty. SU Identifier (as defined by su- su01
+ <= 16 chars core.ini/[SU]/SUInstance key at SU
+ 192.168.121.1
+ initialization).
+ By default, the SUInstance is set to
+ a string that represents the SU
+ private IP address.
+01 SU IP address String ipv4:xx.xx.xx.xx<:port> SU IP address (and ASM port) as ipv4:213.56.136.29: 2518
+ <= 26 chars provided by su-
+ This field is never empty.
+ crouting.ini/[crRouting]/localASMa
+ ddress key.
+02 Conference ID String When [CDR_FIELDS] Unique call session identifier Advised format
+ <= 64 chars ReadlIDFormat is set to 1 in provided by the SU, as received in (ReadlIDFormat=1):
+ ncx-cdr-wrapper.ini (advised call initiation message (H.225 910a4b12 cd67d93f
+ format): conferenceID field in Setup or 4300abd2 cc10a0a0
+ ARQ).
+ 4x4 bytes as an hexadecimal RealIDFormat=0:
+ string; double words are
+ 12.123.54.125.67.235.255.2
+ space-separated
+ 31.9.12.4.3.7.19.245.65
+ When [CDR_FIELDS]
+ ReadlIDFormat is set to 0 in
+ ncx-cdr-wrapper.ini:
+ 16xdecimal notation of a 1-
+ byte number (0..255), dot-
+ separated.
+ This field is never empty.
+03 Call ID String When [CDR_FIELDS] Call identifier provided by the ASM Advised format
+ <= 64 chars ReadlIDFormat is set to 1 in in the SU (it can be the CallID or (ReadlIDFormat=1):
+ ncx-cdr-wrapper.ini (advised the RealCallID according to what is 910a4b12 cd67d93f
+ format): set in the ncx-cdr-wrapper.ini 4300abd2 cc10a0a0
+ UseRealCallID field). It is received
+ 4x4 bytes as an hexadecimal RealIDFormat=0:
+ in call initiation message (H.225
+ string; double words are
+ callID field in Setup or ARQ). 12.123.54.125.67.235.255.2
+ space-separated
+ 31.9.12.4.3.7.19.245.65
+ When [CDR_FIELDS]
+ ReadlIDFormat is set to 0 in
+ ncx-cdr-wrapper.ini:
+ 16xdecimal notation of a 1-
+ byte number (0..255), dot-
+ separated.
+ This field may be empty if no
+ H.225 callID is present in
+ ARQ.
+04 Leg number Integer Always set to 0 when the call Call attempt index, starting at 0. 0
+ ~ 1 char is not deflected. Incremented whenever a call leg
+ to a new destination is created.
+ This field is never empty.
+ A single call without any call
+ forward service will only have 1
+ CDR line, whose Leg number is set
+ to 0.
+ If a call is redirected (on
+ CFU/CFB/CNFR), it will generate a
+ second CDR line, leg number 1.
+ The leg number is then
+ incremented on each subsequent
+ redirection.
+
+05 Authorize Long It can have two formats as Authorize date and time of the call 1039189431
+ timestamp 10 chars given in the ncx-cdr- leg => enable to have a date and
+ wrapper.ini by the time if a call is not connected.
+ TimestampFormat field. UTC.
+ If TimestampFormat is set to This is the ARQ or SETUP or
+ 0, the result string INVITE reception timestamp for
+ corresponds to the "epoch" the first call leg. For next tickets,
+ time, the number of elapsed this is the call deflection processing
+ seconds since 1970/01/01 start time. Thus, this value may
+ 00:00:00 (UTC) vary in tickets related to a
+ complete call.
+ If TimestampFormat is set to
+ 1, the result string is 20 chars
+ in length (format: YYYY-MM-
+ DD HH:MM:SS)
+ NOTE: if you choose
+ TimestampFormat = 0 you
+ can have the tenth of second
+ (UseTenthOfSecond = 1) or
+ the micro second
+ (UseMicroSecond = 1)
+ NOTE: you can hide
+ timestamp equal to 0 (or
+ 1970/01/01 00:00:00) with
+ the key HideNullTimestamp
+ set to 1.
+ This field is never empty.
+06 Start timestamp Long It can have two formats as Starting date and time of the call 1039189431
+ 10 chars given in the ncx-cdr- leg. UTC.
+ wrapper.ini by the
+ This is the CONNECT or OK (after
+ TimestampFormat field.
+ INVITE) reception timestamp. It is
+ If TimestampFormat is set to set to the same value for all tickets
+ 0, the result string related to a call.
+ corresponds to the "epoch"
+ time, the number of elapsed
+ seconds since 1970/01/01
+ 00:00:00 (UTC)
+ If TimestampFormat is set to
+ 1, the result string is 20 chars
+ in length (format: YYYY-MM-
+ DD HH:MM:SS)
+ 0 (or 1970/01/01 00:00:00)
+ means the connection was not
+ established for this call leg.
+ NOTE: if you choose
+ TimestampFormat = 0 you
+ can have the tenth of second
+ (UseTenthOfSecond = 1) or
+ the micro second
+ (UseMicroSecond = 1)
+ NOTE: you can hide
+ timestamp equal to 0 (or
+ 1970/01/01 00:00:00) with
+ the key HideNullTimestamp
+ set to 1.
+ This field may be empty if the
+ call is not connected.
+07 Duration Long In seconds (0 means the Duration of the call leg (in 6
+ <= 10 chars connection was not seconds), after the connection was
+ established for this call leg). established.
+ NOTE: you can have the tenth Set to 0 for SIP NOTIFICATION
+ of second (UseTenthOfSecond and SIP MESSAGE reports.
+ = 1) or the micro second
+ (UseMicroSecond = 1)
+ This field is never empty.
+08 Caller String e164:[number] or h323:[alias] Main Source Alias in pivot format e164:0010033575
+ or email:[alias] (provided by the ASM)
+ <= 128 chars
+ This field may be empty if the If pivot format cannot be
+ Caller pivot alias cannot be computed then the main source
+ computed. alias is presented in originating
+ format and the "O" char is inserted
+ See Use Cases section for
+ at the beginning of the alias or
+ possible cases.
+ number.
+ NOTE: the phone-context and
+ trunk-context are set if present.
+09 Callee String e164:[number] or h323:[alias] E.164 Called Party Number alias or e164:0010033762
+ or email:[alias] H323 destination ID in pivot
+ <= 128 chars
+ format (provided by the ASM)
+ This field may be empty if the
+ Callee pivot alias cannot be If pivot format cannot be
+ computed. computed then the originating
+ format is presented and the "O"
+ char is inserted at the beginning of
+ the alias or number.
+ NOTE: the phone-context and
+ trunk-context are set if present.
+10 Source IP String ipv4:xx.xx.xx.xx<:port> If ncx-cdr-wrapper.ini/useFullIP = ipv4:192.168.1.2:34123
+ 0:
+ <= 26 chars This field may be empty if the
+ Source IP cannot be retrieved Source IP address of the caller, as
+ in IP message mode. used for IP filtering (thus, may be
+ either Packet IP address or
+ CallSignalAddress, depending on
+ su-
+ crouting.ini/[defaultH323Parameter
+ s]/ipFiltering key
+ It can also be changed by the
+ selector "extended actions"
+ parameter. See "selector extended
+ actions" dedicated documentation
+ for further information.
+ If ncx-cdr-wrapper.ini/useFullIP =
+ 1:
+ Source IP packet address for the
+ call leg
+11 Destination IP String ipv4:xx.xx.xx.xx<:port> If ncx-cdr-wrapper.ini/useFullIP = ipv4:213.56.162.17
+ 0:
+ <= 26 chars This field may be empty if
+ destination IP cannot be Destination IP signaling address
+ resolved. for the call leg
+ If ncx-cdr-wrapper.ini/useFullIP =
+ 1:
+ Destination IP packet address for
+ the call leg
+ NOTE: Can be different from the
+ signaling address when routing
+ through a proxy group. This field
+ refers to the proxy IP address.
+ Otherwise IP signaling address and
+ IP packet address are the same.
+12 selector Tag String This field is empty for non Extensible tag. See extension tag in=33231412345,vp=165,si
+ <= 199 chars Business Services managed format below. =123 tz=Europe/Berlin,
+ sources and for Sites with no
+ Selector Tag placed on the selector
+ PSTN ranges allocated.
+ for this call
+ See [ref: 2] and [ref: 3] for further
+ 2 2
+ information.
+13 service Tag Full alphanumeric This field is empty for now. Service Tag placed on the selector
+ string or on the vTrunkGroup for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2
+ information.
+14 announcement Full alphanumeric This field is empty for now. Announcement Tag placed on the
+ Tag string selector, routeTable or
+ vTrunkGroup for this call.
+ See [ref: 2]and [ref: 3] for further
+ 2
+ information.
+15 route Table Tag Full alphanumeric This field is empty for now. Route table Tag placed on the
+ string route table for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2 2
+ information.
+16 vTrunkGroup Full alphanumeric This field is empty for now. vTrunkGroupTag placed on the
+ Tag string vTrunkGroup for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2
+ information.
+17 vTrunk Tag String This field is empty for non Extensible tag. See extension tag in=33156341289,vp=4232,s
+ <= 199 chars Business Services managed format below. i=132,tz=Europe/Paris
+ destinations and for Sites with
+ vTrunk Tag placed on the vTrunk
+ no PSTN ranges allocated.
+ for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2 2
+ information.
+18 termination Tag Full alphanumeric This field is empty for now. Termination Tag placed on the
+ string Termination for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2 2
+ information.
+19 location group Full alphanumeric This field is empty for now. location group Tag placed on the
+ Tag string selector for this call.
+ See [ref: 2] and [ref: 3] for further
+ 2 2
+ information.
+20 GK Originating Full alphanumeric This field is empty for now. Parameter provided by the ASM in
+ IP string the SU (reserved for future usage).
+21 GK Terminating Full alphanumeric This field is empty for now. Parameter provided by the ASM in
+ IP string the SU (reserved for future usage).
+22 GK Originating Full alphanumeric This field is empty for now. Parameter provided by the ASM in
+ Domain string the SU (reserved for future usage).
+23 GK Terminating Full alphanumeric This field is empty for now. Parameter provided by the ASM in
+ Domain string the SU (reserved for future usage).
+24 Malicious Call Boolean 0/1 Indicate if a call is malicious or 0
+ not. All calls to a specific called
+ 1 char
+ party will be tagged as malicious
+ when the malicious feature has
+ been activated.
+25 Service Long 0..31 Bit mask for activated services for 6: at least one
+ <= 3 chars this call. TECHNOLOGY and one
+ This field is never empty.
+ REMOVE service objects
+ This is a combination between the
+ have been used during
+ following values:
+ routing process
+ 1: if at least one CLIR service
+ 10: at least one BASIC-
+ object has been used during
+ XACTION and one REMOVE
+ routing process
+ service objects have been
+ 2: if at least one REMOVE service used during routing process
+ object has been used during
+ routing process
+ 4: if at least one TECHNOLOGY
+ service object has been used
+ during routing process
+ 8: if at least one BASIC-XACTION
+ service object has been used
+ during routing process
+ 16: if at least one SUBSTITUTION
+ service object has been used
+ during routing process
+ This is independent from the su-
+ crouting.ini configuration file and
+ in particular from the SPE
+ activation.
+26 Termination Long Causes in the range [1-127] Cause of the call termination. 16
+ Cause <= 3 chars are standard Q.850 causes
+ Causes >= 128 are specific
+ Comverse extension causes.
+ See [ref. 5] for possible values
+ and meanings.
+ This field is never empty.
+27 Simulation Call Boolean 0/1 Indicates if a call is a simulation 0
+ 1 char call or not.
+ This field is never empty.
+ SIMULATION CALLS MUST NOT BE
+ BILLED.
+ Simulation calls can only be
+ generated through the Telnet
+ interface (tests and diagnostic
+ only).
+28 Type One character Optional field depending on Type of CDR: C
+ the UseType entry in ncx-cdr-
+ 1 char - Call ('C'): for INVITE and SETUP
+ wrapper.ini. If set to 1, a
+ value in this field will be - Notification ('N') for SIP
+ always printed: 'C' by default. NOTIFICATION
+ 'C', 'N' or 'M'. - Message ('M') for SIP MESSAGE
+ This field is never empty.
+29 ReleaseTimeSta Long Optional field depending of Release date of the leg. 1039189431
+ mp 10 chars the UseReleaseTimeStamp
+ entry in ncx-cdr-wrapper.ini.
+ It can have two formats as
+ given in the ncx-cdr-
+ wrapper.ini by the
+ TimestampFormat field.
+ If TimestampFormat is set to
+ 0, the result string
+ corresponds to the "epoch"
+ time, the number of elapsed
+ seconds since 1970/01/01
+ 00:00:00 (UTC)
+ If TimestampFormat is set to
+ 1, the result string is 20 chars
+ in length (format: YYYY-MM-
+ DD HH:MM:SS)
+ NOTE: if you choose
+ TimestampFormat = 0 you
+ can have the tenth of second
+ (UseTenthOfSecond = 1) or
+ the micro second
+ (UseMicroSecond = 1)
+ NOTE: you can hide
+ timestamp equal to 0 (or
+ 1970/01/01 00:00:00) with
+ the key HideNullTimestamp
+ set to 1.
+ This field is empty when no
+ CRR message is received and
+ therefore it will be empty for
+ the CDR describing presence
+ message (SIP NOTIFY and SIP
+ MESSAGE). It is also empty
+ when the CDR is closed by the
+ AMU (e.g. if the SU is
+ detected as DOWN).
+ In all other cases, this field is
+ never empty
+30 cgIdentity Tag Full alphanumeric Optional: this field is filled if Extensible tag for Calling Party. pu=33231345123,pr=23
+ string usecgidentitytag is set to 1 in See extension tag format below.
+ <= 132 chars ncx-cdr-wrapper.ini.
+ This field is empty for non
+ Business Services/class V
+ managed sources.
+ The content of this field differs
+ between BS and MyCall
+ solutions.
+31 cdIdentity Tag Full alphanumeric Optional: this field is filled if Extensible tag for Called Party. See pr=1111,bi=ADMIN
+ string usecdidentitytag is set to 1 in extension tag format below.
+ <= 132 chars ncx-cdr-wrapper.ini
+ This field is empty for non
+ Business Services/class V
+ managed destinations.
+ The content of this field differs
+ between BS and MyCall
+ solutions.
+32 Originating String Optional: this field is filled if E.164 Main Source alias or H323 e164:0010033575
+ Caller <= 128 chars useoriginatingcaller is set to 1 source ID in originating format (as
+ in ncx-cdr-wrapper.ini received from the network)
+ e164:[number] or h323:[alias] The Main Source alias is computed
+ or email:[alias] according to su-core.ini
+ configuration.
+ NOTE: the phone-context and
+ trunk-context are set if present.
+33 Originating String Optional: this field is filled if E.164 Main Destination alias or e164:0010033762
+ Callee <= 128 chars useoriginatingcallee is set to 1 H323 destination ID in originating
+ in ncx-cdr-wrapper.ini format (as received from the
+ network)
+ e164:[number] or h323:[alias]
+ or email:[alias] The Main Destination alias is
+ computed according to su-core.ini
+ configuration.
+ NOTE: the phone-context and
+ trunk-context are set if present.
+34 Terminating String Optional: this field is filled if E.164 Calling Party Number alias or e164:0010033575
+ Caller <= 128 chars useterminatingcaller is set to 1 H323 source ID in terminating
+ in ncx-cdr-wrapper.ini format (as provided to the
+ network).
+ e164:[number] or h323:[alias]
+ or email:[alias] NOTE: the phone-context and
+ trunk-context are set if present.
+35 Terminating String Optional: this field is filled if E.164 Called Party Number alias or e164:0010033762
+ Callee <= 128 chars useterminatingcallee is set to H323 destination ID in terminating
+ 1 in ncx-cdr-wrapper.ini. format (as provided to the
+ network).
+ e164:[number] or h323:[alias]
+ or email:[alias] NOTE: the phone-context and
+ trunk-context are set if present.
+ This field may be empty if no
+ terminating destination aliases
+ can be computed by the CRE
+ (missing vtrunk transformation
+ or unable to found a vtrunk
+ for whatever routing reason),
+ or if the pivot to terminating
+ destination alias
+ transformation leads to an
+ empty alias.
+36 Network Long Optional: this field is filled if For H.323 the network timestamp 1039189431
+ Timestamp 10 chars usenetworkcompletiontimesta is measured at the first Progress or
+ mp is set to 1 in ncx-cdr- ALERT or CONNECT received by
+ wrapper.ini. the CCS for direct call.
+ For redirected call, the network
+ It can have two formats as
+ timestamp is measured by the
+ given in the ncx-cdr-
+ CCS at the redirection decision
+ wrapper.ini by the
+ point,
+ TimestampFormat field.
+ NOTE: For H.323 calls, the tcp-ack
+ If TimestampFormat is set to
+ of the outgoing TCP connection is
+ 0, the result string
+ not considered in the measure of
+ corresponds to the "epoch"
+ network timestamp
+ time, the number of elapsed
+ seconds since 1970/01/01 For SIP the network timestamp is
+ 00:00:00 (UTC) measured at the first SESSION
+ PROGRESS or RINGING or OK
+ If TimestampFormat is set to
+ received by the CCS for direct call.
+ 1, the result string is 20 chars
+ in length (format: YYYY-MM- The network timestamp is
+ DD HH:MM:SS) measured at the redirection
+ decision point for redirected call.
+ NOTE: if you choose
+ TimestampFormat = 0 you
+ can have the tenth of second
+ (UseTenthOfSecond = 1) or
+ the micro second
+ (UseMicroSecond = 1)
+ NOTE: you can hide
+ timestamp equal to 0 (or
+ 1970/01/01 00:00:00) with
+ the key HideNullTimestamp
+ set to 1.
+ This field may be empty if the
+ callee does not answer.
+37 Targeted Integer Optional: this field is filled if Provides information on the 12
+ adaptor UseTargetedAdaptors is set to adaptor that has been used: "1"
+ <= 2 chars
+ 1 in ncx-cdr-wrapper.ini. for adaptor1, "2" for adaptor2 and
+ "12" for adaptor1 and adaptor2
+ "1", "2" or "12"
+ See the amu-core.ini file section
+ for further details on adaptors
+ definition.
+38 Adaptor1 errors String Optional: this field is filled if Report errors on adaptor1 at the cra,crr
+ UseAdaptor1Errors is set to 1 adaptor API level.
+ <= 15 chars
+ in ncx-cdr-wrapper.ini.
+ "nca" (error on the new call
+ authorize)
+ "cra" (error on the call re-
+ authorize)
+ "ncr" (error on the new call
+ report)
+ "crr" (error on the call release
+ report)
+ When several errors occurred,
+ comma separated notation will
+ be used.
+ Empty when no error has
+ been detected.
+39 Source signaling String Optional: this field is filled in Source IP signaling address for the ipv4:192.168.1.2:34123
+ IP only if useFullIP is set to 1 in call leg.
+ <= 26 chars
+ the ncx-cdr-wrapper.ini file.
+ It can be changed by the selector
+ ipv4:xx.xx.xx.xx<:port> "extended actions" parameter. See
+ "selector extended actions"
+ This field may be empty if the
+ dedicated documentation for
+ Source IP cannot be retrieved
+ further information.
+ in IP message mode.
+40 Destination String Optional: this fields is filled in Destination IP signaling address ipv4:213.56.162.17
+ signaling IP only if useFullIP is set to 1 in for the call leg
+ <= 26 chars
+ ncx-cdr-wrapper.ini file.
+ ipv4:xx.xx.xx.xx<:port>, can
+ be empty if destination IP
+ cannot be resolved.
+41 Source point Unsigned integer Optional: this field is filled in SS7 point code, node identifier 1234
+ code only if usePC is set to 1 in the
+ <= 5 chars
+ ncx-cdr-wrapper.ini file.
+ SIP: FROM header [TG-TEL]:
+ PC is Encoded in the trunk-
+ group part of a "tel" URI
+ extension (see also RFC
+ 3966).
+ H.323: H.225/circuitInfo:
+ Encoded in an
+ sourceCircuitID.cic.pointCode.
+42 Destination point Unsigned integer Optional: this field is filled in SS7 point code, node identifier 1234
+ code only if usePC is set to 1 in the
+ <= 5 chars
+ ncx-cdr-wrapper.ini file.
+ SIP: TO header [TG-TEL]: PC
+ is encoded in the trunk-group
+ part of a "tel" URI extension
+ (see also RFC 3966).
+ H.323: H.225/circuitInfo:
+ Encoded in a
+ destinationCircuitID.cic.pointC
+ ode.
+43 Origination tag Full alphanumeric Optional: this field is filled in Origination tag placed on the crr=...,poi=...
+ string only if useOriginationTag is origination for this call.
+ set to 1 in the ncx-cdr-
+ wrapper.ini file.
+44 Proxy group tag Full alphanumeric Optional: this field is filled in Proxy group Tag placed on the
+ string only if useProxyGroupTag is proxy group for this call.
+ set to 1 in the ncx-cdr-
+ wrapper.ini file.
+ This field is empty for now.
+45 Advice of Charge String Optional: this field only is filled AOC received. rend=10.2,unit=EURO
+ in if UseAoc is set to 1 in ncx-
+ <= 50 chars cdr-wrapper.ini file. Available with CCS 3.8.4.
+ This field may be empty if
+ AOC service is not used or if
+ no AOC value is available.
+ <aocType>=<amount>,unit=
+ <string> with:
+ 1. <aocType> (max length:
+ 7 chars):
+ Received AOC-D: 'rduring'
+ Received AOC-E, 'rend'
+ Other AOC types are not yet
+ supported by the su-core and
+ therefore are ignored.
+ 2. <amount> (max length:
+ 14 chars):
+ The amount is decoded from
+ the received AOC-D or AOC-E.
+ This value is mandatory in an
+ AOC.
+ 3. unit=<string> (max length:
+ 15 chars):
+ The unit string is the decoded
+ unit value in the received
+ AOC-D or AOC-E. This value is
+ mandatory in an AOC.
+46 Routing Context String Optional Routing context of the leg. basic
+ <= 5 chars 3 possible values: For IMS calls, routing context has
+ the value "orig" or "term".
+ - basic Otherwise, it is set to "basic".
+ - orig
+ Dependencies:
+ - term
+ - amu-core-4.8.0
+ - adaptor-generic-cdr-
+ 1.8.0
+ - ncx-cdr-wrapper-1.8.0
+47 Originating String Optional: this field is filled if E164 Main Source alias or H323 e164:33762
+ Original Caller <= 128 chars useoriginatingoriginalcaller is source ID in originating format (as
+ set to 1 in ncx-cdr- received from the network) of the
+ wrapper.ini. original caller.
+ e164:[number] or h323:[alias] The main source alias is computed
+ or email:[alias] according to su-core.ini
+ configuration.
+ NOTE: the phone-context and
+ trunk-context are set if present.
+ Dependencies:
+ - amu-core-4.10.0
+ - adaptor-generic-cdr-
+ 1.10.0
+ - ncx-cdr-wrapper-1.10.0
+48 Pivot Original String Optional: this field is filled if E164 Main Source alias or H323 E164:0010033762
+ Caller <= 128 chars usepivotoriginalcaller is set to source ID in pivot format (as
+ 1 in ncx-cdr-wrapper.ini. received from the network) of the
+ original caller
+ e164:[number] or h323:[alias]
+ or email:[alias] They are sent if present by SU if
+ su-
+ crouting.ini/[compatibility]/aliasRe
+ porting is 5_0_0 or greater
+ NOTE: the phone-context and
+ trunk-context are set if present.
+ Dependencies:
+ - amu-core-4.10.0
+ - adaptor-generic-cdr-
+ 1.10.0
+ - ncx-cdr-wrapper-1.10.0
+49 Terminating String Optional: this field is filled if E164 Main Source alias or H323 E164:0010033762
+ Original Caller <= 128 chars useterminatingoriginalcaller is source ID in terminating format (as
+ set to 1 in ncx-cdr- received from the network) of the
+ wrapper.ini. original caller.
+ e164:[number] or h323:[alias] They are sent if present by SU if
+ or email:[alias] su-
+ crouting.ini/[compatibility]/aliasRe
+ porting is 5_0_0 or greater
+ NOTE: the phone-context and
+ trunk-context are set if present.
+ Dependencies:
+ - amu-core-4.10.0
+ - adaptor-generic-cdr-
+ 1.10.0
+ - ncx-cdr-wrapper-1.10.0
+50 Pivotclir Boolean Optional: this field is filled if Pivot CLIR calculated with caller clir=0
+ UsePivotClir is set to 1 in ncx- information.
+ 6 chars cdr-wrapper.ini.
+ Dependencies:
+ 0 means that Calling Line
+ Identification is showed. - amu-core-4.12.0
+ 1 means that Calling Line - adaptor-generic-cdr-
+ Identification is hidden. 1.12.0
+ - ncx-cdr-wrapper-1.12.0
+
diff --git a/FS/FS/cdr/nextone.pm b/FS/FS/cdr/nextone.pm
new file mode 100644
index 0000000..22e6e86
--- /dev/null
+++ b/FS/FS/cdr/nextone.pm
@@ -0,0 +1,26 @@
+package FS::cdr::nextone;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Nextone',
+ 'weight' => 200,
+ 'header' => 1,
+ 'import_fields' => [
+ 'userfield', #CallZoneData ???userfield
+ 'channel', #OrigGw
+ 'dstchannel', #TermGw
+ sub { my( $cdr, $duration ) = @_;
+ $cdr->duration($duration);
+ $cdr->billsec($duration); }, #Duration
+ 'dst', #CallDTMF
+ 'src', #Ani
+ 'startdate', #DateTimeInt
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/openser.pm b/FS/FS/cdr/openser.pm
new file mode 100644
index 0000000..87fb822
--- /dev/null
+++ b/FS/FS/cdr/openser.pm
@@ -0,0 +1,24 @@
+package FS::cdr::openser;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'OpenSER',
+ 'weight' => 15,
+ 'header' => 1,
+ 'import_fields' => [
+ _cdr_date_parser_maker('startdate'),
+ _cdr_date_parser_maker('enddate'),
+ 'src',
+ 'dst',
+ 'duration',
+ 'channel',
+ 'dstchannel',
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/sansay.pm b/FS/FS/cdr/sansay.pm
new file mode 100644
index 0000000..8087c57
--- /dev/null
+++ b/FS/FS/cdr/sansay.pm
@@ -0,0 +1,408 @@
+package FS::cdr::sansay;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Sansay VSX',
+ 'weight' => 135,
+ 'header' => 0, #0 default, set to 1 to ignore the first line, or
+ # to higher numbers to ignore that number of lines
+ 'type' => 'csv', #csv (default), fixedlength or xls
+ 'sep_char' => ';', #for csv, defaults to ,
+ 'disabled' => 0, #0 default, set to 1 to disable
+
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+
+ # "Header" (I do not think this means what you think it means)
+ #002452502;V1.10;R;
+
+ # Record Sequence Number 9 Unique identification of this record
+ 'uniqueid',
+
+ '', #Version Number 5 Format version number of records to follow
+ # "V1.10"
+ '', #Record Type 1 Type of CDR being generated
+ # R ­ Normal CDR record, A - Audit
+
+ # "Body"
+ #WithMedia;181-1071459514@192.188.0.28;0001;Mon Dec 15 11:38:34 2003;Mon Dec 15 11:38:41 2003;Mon Dec 15 11:38:48 2003;480;EndedByRemoteUser;3;T;000200;H323;;192.188.0.38;9001;192.188.0.28;f0faff54-2e6c-11d8-8c4b-bd4d562c2265;192.188.0.38;18044;192.188.0.28;10756;G.729b;240;460;6066;14060;0;0;0;000200;H323;;192.188.0.28;8811;192.188.0.38;e83af3d3-1d2d-d811-9f98-003048424934;192.188.0.38;19236;192.188.0.28;10758;G.729b;460;240;14060;6066;0;0;0;F;9001;305;2;15;305000;00000011 44934567 45231267 2300BCC0;8587542200;
+
+ '', #ConnectionType 16 Type of connection : Media or No Media
+ '', #SessionID 32 Unique ID assigned to the call by
+ # SSM subsystem
+ '', #XXX #Release Cause 4 2.4 Internal process Release Cause
+
+ #Cause Code Descriptions
+ #01 Normal answered call
+ #02 No Answer, tear down by originator
+ #03 No answer, tear down by the termination
+ #04 NORMAL_NO_ANSWER, tear down by
+ # system
+ #402 Service Not Available
+ #403 Termination capability un-compatible
+ #404 Outbound digit translation failed
+ #405 Termination reject for some other reasons
+ #406 Termination Route is blocked
+ #500 Originator is not in the Authorized list
+ # (source verification failed)
+ #501 Origination digit translation failed
+ #502 Origination direction is not bi-directional or
+ # inbound
+ #503 Origination is not in service state
+ #600 Max system call handling reached
+ #601 System reject call
+ #602 System outbound digit translation error
+ # (maybe invalid configuration)
+ #603 System inbound digit translation error
+ # (Maybe invalid configuration)
+
+
+ #Start Time of Date 32 Indicates Time of Date when the call
+ # entered the system
+ _cdr_date_parser_maker('startdate'),
+
+ #Answer Time of Date 32 Indicates TOD when the call was
+ # answered
+ _cdr_date_parser_maker('answerdate'),
+
+ #Release TOD 32 Indicates the TOD when the call was
+ # disconnected
+ _cdr_date_parser_maker('enddate'),
+
+ #Minutes West of 32 Minutes West of Greenwich Mean
+ #Greenwich Mean Time Time. Used to calculate the time
+ # zone.
+ '', #XXX use this
+
+ #Release Cause from 32 Release cause string from either H323
+ #Protocol Stack or SIP protocol stack
+ #4. Release Cause String (Field #8 in CDR)
+ #- a string of text further identifying the teardown circumstance from terminating protocol message.
+ '',
+
+ #Binary Value of Release 4 Binary value of the protocol release
+ #Cause from Protocol cause
+ #stack
+ #
+ #3. Release Cause from Stack ( Field # 9 in CDR)
+ #- an integer value based on the releasing dialogues protocol.
+ # a. For a H.323 call leg originated release it will be the real Q.931 value received from the far
+ # side.
+ #Some of the Q.931 release causes;
+ #3: No route to destination
+ #16; Normal Clearing
+ #17: User Busy
+ #19: NO Answer from User
+ #21; Call Rejected
+ #28: Address Incomplete
+ #34: No Circuit Channel Available
+ #....
+ # b. For a SIP call leg originated release, it's a RFC 3261 release cause value received from the
+ # far side.
+ #The following is the list that VSX generated if certain event happen:
+ #"400 Parse Failed" - Malformed Message
+ #"405 Method Not Allowed" - Unsupported Method
+ #"480 Temporarily Unavailable" - Overload Throttle Rejection, Max Sessions
+ #Exceeded, Demo License Expired, Capacity Exceeded on Route, Radius Server Timeout
+ #"415 No valid codec" - No valid codec could be supported between origination and
+ #term call legs.
+ #"481 Transaction Does Not Exist" - Unknown Transaction or Dialog
+ #"487 Transaction Terminated" - Origination Cancel
+ #"488 ReInvite Rejected" - Relay of ReInvite was Rejected
+ #"504 Server Time-out" - Internal VSX Failure
+ #"500 Sequence Out of Order" - CSeq counter violation
+ # c. For a VSX system originated release, it an internal release cause for teardown.
+ #If the VSX initiates a call teardown, the following cause values and strings are written into the CDR:
+ #999, "Demo Licence Expired!"
+ #999, "VSX Capacity Exceeded"
+ #999, "VSX Operator Reset"
+ #999, "Route Rejected"
+ #999, "Radius Rejected"
+ #999, "Radius Access Timeout"
+ #999, "Gatekeeper Reject"
+ #999, "Enum Server Reject"
+ #999, "Enum Server Timeout"
+ #999, "DNS Server Reject"
+ #999, "DNS/GK Timeout"
+ #999, "Could not allocate media"
+ #999, "No Response to INVITE"
+ #999, "Ring No Answer Timeout"
+ #999, "200 OK Timeout"
+ #999, "Maximum Duration Exceeded"
+ #987, "Termination Capacity Exceeded"
+ #987, "Origination Capacity Exceeded"
+ #987, "Term CPS Capacity Exceeded"
+ #987, "Orig CPS Capacity Exceeded"
+ #987, "Max H323 Legs Exceeded"
+ '',
+
+ #1st release dialogue 1 O: origination, T: termination
+ #2. 1st Release Dialogue ( Field #10 in CDR)
+ #- one character value identifying the side of the call that i
+ # ,,O ­ origination initiated the teardown.
+ # ,,T ­ termination initiated the teardown.
+ # ,,N ­ the VSX internally initiated the teardown.
+ '',
+
+ #Trunk ID -- Origination 6 TrunkID for origination GW(resources)
+ 'accountcode', # right? # use cdr-charged_party-accountcode
+
+ #VoIP Protocol - Origination 6 VoIP protocol for origination dialogue
+ '',
+
+ #Origination Source Number 128 Source Number in Origination Dialogue
+ 'src',
+
+ #Origination Source Host Name 128 FQDN or IP address for Source GW in Origination Dialogue
+ 'channel',
+
+ #Origination Destination Number 128 Destination Number in Origination
+ #Dialogue
+ 'dst',
+
+ #Origination Destination Host Name 128 FQDN or IP address for Destination
+ #GW in Origination Dialogue
+ 'dstchannel',
+
+ #Origination Call ID 128 Unique ID for the origination dialogue(leg)
+ '', #'clid', #? that's not really the same call ID
+
+ #Origination Remote 16 Remote Payload IP address for
+ # Payload IP origination dialogue
+ # Address
+ '',
+
+ #Origination Remote 6 Remote Payload UDP address for
+ # Payload UDP origination dialogue
+ # Address
+ '',
+
+ #Origination Local 16 Local(SG) Payload IP address for
+ # Payload IP origination dialogue
+ # Address
+ '',
+
+ #Origination Local 6 Local(SG) Payload UDP address for
+ # Payload UDP origination dialogue
+ # Address
+ '',
+
+ #Origination Codec List 128 Supported Codec list( separated by
+ # comma) for origination dialogue
+ '',
+
+ #Origination Ingress 10 Number of Ingress( into Sansay
+ # Packets system) payload packets in
+ # origination dialogue
+ '',
+
+ #Origination Egress 10 Number of Egress( out from Sansay
+ # Packets system) payload packets in
+ # origination dialogue
+ '',
+
+ #Origination Ingress 10 Number of Ingress( into Sansay
+ # Octets system) payload octets in origination
+ # dialogue
+ '',
+
+ #Origination Egress 10 Number of Egress( out from Sansay
+ # Octets system) payload octets in origination
+ # dialogue
+ '',
+
+ #Origination Ingress 10 Number of Ingress( into Sansay
+ # Packet Loss system) payload packet loss in
+ # origination dialogue
+ '',
+
+ #Origination Ingress 10 Average Ingress( into Sansay system)
+ # Delay payload packets delay ( in ms) in
+ # origination dialogue
+ '',
+
+ #Origination Ingress 10 Average of Ingress( into Sansay
+ # Packet Jitter system) payload packet Jitter ( in ms)
+ # in origination dialogue
+ '',
+
+ #Trunk ID -- Termination 6 Trunk ID for termination GW(resources)
+ 'carrierid',
+
+ #VoIP Protocol - 6 VoIP protocol from termination GW
+ # Termination
+ '',
+
+ #Termination Source 128 Source Number in Termination
+ # Number Dialogue
+ '',
+
+ #Termination Source Host 128 FQDN or IP address for Source GW
+ # Name in Termination Dialogue
+ '',
+
+ #Termination Destination 128 Destination Number in Termination
+ # Number Dialogue
+ '',
+
+ #Termination Destination 128 FQDN or IP address for Destination
+ # Host Name GW in Termination Dialogue
+ '',
+
+ #Termination Call ID 128 Unique ID for the termination
+ # dialogue(leg)
+ '',
+
+ #Termination Remote 16 Remote Payload IP address for
+ # Payload IP termination dialogue
+ # Address
+ '',
+
+ #Termination Remote 6 Remote Payload UDP address for
+ # Payload UDP termination dialogue
+ # Address
+ '',
+
+ #Termination Local 16 Local(SG) Payload IP address for
+ # Payload IP termination dialogue
+ # Address
+ '',
+
+ #Termination Local 6 Local(SG) Payload UDP address for
+ # Payload UDP termination dialogue
+ # Address
+ '',
+
+ #Termination Codec List 128 Supported Codec list( separated by
+ # comma) for termination dialogue
+ '',
+
+ #Termination Ingress 10 Number of Ingress( into Sansay
+ # Packets system) payload packets in
+ # termination dialogue
+ '',
+
+ #Termination Egress 10 Number of Egress( out from Sansay
+ # Packets system) payload packets in
+ # termination dialogue
+ '',
+
+ #Termination Ingress 10 Number of Ingress( into Sansay
+ # Octets system) payload octets in
+ # termination dialogue
+ '',
+
+ #Termination Egress 10 Number of Egress( out from Sansay
+ # Octets system) payload octets in
+ # termination dialogue
+ '',
+
+ #Termination Ingress 10 Number of Ingress( into Sansay
+ # Packet Loss system) payload packet loss in
+ # termination dialogue
+ '',
+
+ #Termination Ingress 10 Average Ingress( into Sansay system)
+ # Delay payload packets delay ( in ms) in
+ # termination dialogue
+ '',
+
+ #Termination Ingress 10 Average of Ingress( into Sansay
+ # Packet Jitter system) payload packet Jitter ( in ms)
+ # in termination dialogue
+ '',
+
+ #Final Route Indication 1 F: Final Route Selection,
+ # I: Intermediate Route Attempts
+ '',
+
+ #Routing Digits 64 Routing Digit (Digit after Inbound
+ # translation, before Outbound
+ # Translation). This may also be the
+ # LRN if LNP feature is enabled
+ '',
+
+ #Call Duration in Second 6 Call Duration in Seconds. 0 if this is
+ # failed call
+ 'billsec',
+
+ #Post Dial Delay in 6 Post dial delay (from call attempt to
+ # Seconds ring). 0 if this is failed call
+ '',
+
+ #Ring Time in Second 6 Ring Time in Seconds. 0 if this is
+ # failed call
+ '',
+
+ #Duration in milliseconds 10 Call duration in milliseconds.
+ '',
+
+ #Conf ID 36 Unique Conference ID for this call in
+ # Cisco format
+ '',
+
+ #RPID/ANI 32 Inbound Remote Party ID line or
+ # Proxy Asserted Identity if provided
+ 'clid', #?
+
+ ],
+
+);
+
+1;
+
+__END__
+
+list of freeside CDR fields, useful ones marked with *
+
+N/A acctid - primary key
+FILLED_IN *[1] calldate - Call timestamp (SQL timestamp)
+DONE clid - Caller*ID with text
+DONE * src - Caller*ID number / Source number
+DONE * dst - Destination extension
+ dcontext - Destination context
+DONE channel - Channel used
+DONE dstchannel - Destination channel if appropriate
+ lastapp - Last application if appropriate
+ lastdata - Last application data
+DONE * startdate - Start of call (UNIX-style integer timestamp)
+DONE answerdate - Answer time of call (UNIX-style integer timestamp)
+DONE * enddate - End time of call (UNIX-style integer timestamp)
+* duration - Total time in system, in seconds
+DONE * billsec - Total time call is up, in seconds
+*[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+ amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+ channel basis like accountcode.
+DONE *[3] accountcode - CDR account number to use: account
+ uniqueid - Unique channel identifier
+ userfield - CDR user-defined field
+ cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+FILLED_IN *[4] charged_party - Service number to be billed
+ upstream_currency - Wholesale currency from upstream
+*[5] upstream_price - Wholesale price from upstream
+ upstream_rateplanid - Upstream rate plan ID
+ rated_price - Rated (or re-rated) price
+ distance - km (need units field?)
+ islocal - Local - 1, Non Local = 0
+*[6] calltypenum - Type of call - see FS::cdr_calltype
+ description - Description (cdr_type 7&8 only) (used for
+ cust_bill_pkg.itemdesc)
+ quantity - Number of items (cdr_type 7&8 only)
+DONE carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+ upstream_rateid - Upstream Rate ID
+ svcnum - Link to customer service (see FS::cust_svc)
+ freesidestatus - NULL, done (or something)
+
+[1] Auto-populated from startdate if not present
+[2] Package options available to ignore calls without a specific disposition
+[3] When using 'cdr-charged_party-accountcode' config
+[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present
+[5] When using 'upstream_simple' rating method.
+[6] Set to usage class classnum when using pre-rated CDRs and usage class-based
+ taxation (local/intrastate/interstate/international)
+
diff --git a/FS/FS/cdr/simple.pm b/FS/FS/cdr/simple.pm
new file mode 100644
index 0000000..197b0eb
--- /dev/null
+++ b/FS/FS/cdr/simple.pm
@@ -0,0 +1,52 @@
+package FS::cdr::simple;
+
+use strict;
+use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year );
+use Time::Local;
+use FS::cdr qw(_cdr_min_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Simple',
+ 'weight' => 20,
+ 'header' => 1,
+ 'import_fields' => [
+
+ # Date (MM/DD/YY)
+ sub { my($cdr, $date) = @_;
+ $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/
+ or die "unparsable date: $date"; #maybe we shouldn't die...
+ #$cdr->startdate( timelocal(0, 0, 0 ,$2, $1-1, $3) );
+ ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 );
+ },
+
+ # Time
+ sub { my($cdr, $time) = @_;
+ #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
+ $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
+ or die "unparsable time: $time"; #maybe we shouldn't die...
+ #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
+ $cdr->startdate(
+ timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)
+ );
+ },
+
+ # Source_Number
+ 'src',
+
+ # Terminating_Number
+ 'dst',
+
+ # Duration
+ _cdr_min_parser_maker, #( [qw( billsec duration)] ),
+ #sub { my($cdr, $min) = @_;
+ # my $sec = sprintf('%.0f', $min * 60 );
+ # $cdr->billsec( $sec );
+ # $cdr->duration( $sec );
+ # },
+
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/simple2.pm b/FS/FS/cdr/simple2.pm
new file mode 100644
index 0000000..2e4fb90
--- /dev/null
+++ b/FS/FS/cdr/simple2.pm
@@ -0,0 +1,51 @@
+package FS::cdr::simple2;
+
+use strict;
+use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year );
+use Time::Local;
+use FS::cdr qw(_cdr_min_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Simple (Prerated)',
+ 'weight' => 25,
+ 'header' => 1,
+ 'import_fields' => [
+ sub {}, #TEXT_TIME (redundant w/Time)
+ sub {}, #Blank
+ 'src', #Calling.
+
+ #Date (YY/MM/DD)
+ sub { my($cdr, $date) = @_;
+ $date =~ /^(\d\d(\d\d)?)\/(\d{1,2})\/(\d{1,2})$/
+ or die "unparsable date: $date"; #maybe we shouldn't die...
+ #$cdr->startdate( timelocal(0, 0, 0 ,$3, $2-1, $1) );
+ ($tmp_mday, $tmp_mon, $tmp_year) = ( $4, $3-1, $1 );
+ },
+
+ #Time
+ sub { my($cdr, $time) = @_;
+ $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
+ or die "unparsable time: $time"; #maybe we shouldn't die...
+ #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
+ $cdr->startdate(
+ timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)
+ );
+ },
+
+ 'dst', #Dest
+ 'userfield', #? #DestinationDesc
+
+ #Min
+ _cdr_min_parser_maker, #( [qw( billsec duration)] ),
+
+ sub {}, #Rate XXX do something w/this, informationally???
+ 'upstream_price', #Total
+
+ 'accountcode', #ServCode
+ 'description', #Service_Type
+ ],
+);
+
+
diff --git a/FS/FS/cdr/taqua.pm b/FS/FS/cdr/taqua.pm
new file mode 100644
index 0000000..26c0bda
--- /dev/null
+++ b/FS/FS/cdr/taqua.pm
@@ -0,0 +1,190 @@
+package FS::cdr::taqua;
+
+use strict;
+use vars qw(@ISA %info $da_rewrite);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Taqua',
+ 'weight' => 130,
+ 'header' => 1,
+ 'import_fields' => [ #some of these are kind arbitrary...
+
+ #0
+ #RecordType
+ sub {
+ my($cdr, $field, $conf, $hashref) = @_;
+ $hashref->{skiprow} = 1 unless ($field == 0 && $cdr->disposition == 100);
+ $cdr->cdrtypenum($field);
+ },
+
+ sub { my($cdr, $field) = @_; }, #all10#RecordVersion
+ sub { my($cdr, $field) = @_; }, #OrigShelfNumber
+ sub { my($cdr, $field) = @_; }, #OrigCardNumber
+ sub { my($cdr, $field) = @_; }, #OrigCircuit
+ sub { my($cdr, $field) = @_; }, #OrigCircuitType
+ 'uniqueid', #SequenceNumber
+ 'accountcode', #SessionNumber
+ 'src', #CallingPartyNumber
+ #'dst', #CalledPartyNumber
+ #CalledPartyNumber
+ sub {
+ my( $cdr, $field, $conf ) = @_;
+ if ( $cdr->calltypenum == 6 && $cdr->cdrtypenum == 0 ) {
+ $cdr->dst("+$field");
+ } else {
+ $cdr->dst($field);
+ }
+ },
+
+ #10
+ _cdr_date_parser_maker('startdate', 'gmt' => 1), #CallArrivalTime
+ _cdr_date_parser_maker('enddate', 'gmt' => 1), #CallCompletionTime
+
+ #Disposition
+ #sub { my($cdr, $d ) = @_; $cdr->disposition( $disposition{$d}): },
+ 'disposition',
+ # -1 => '',
+ # 0 => '',
+ # 100 => '',
+ # 101 => '',
+ # 102 => '',
+ # 103 => '',
+ # 104 => '',
+ # 105 => '',
+ # 201 => '',
+ # 203 => '',
+
+ _cdr_date_parser_maker('answerdate', 'gmt' => 1), #DispositionTime
+ sub { my($cdr, $field) = @_; }, #TCAP
+ sub { my($cdr, $field) = @_; }, #OutboundCarrierConnectTime
+ sub { my($cdr, $field) = @_; }, #OutboundCarrierDisconnectTime
+
+ #TermTrunkGroup
+ #it appears channels are actually part of trunk groups, but this data
+ #is interesting and we need a source and destination place to put it
+ 'dstchannel', #TermTrunkGroup
+
+
+ sub { my($cdr, $field) = @_; }, #TermShelfNumber
+ sub { my($cdr, $field) = @_; }, #TermCardNumber
+
+ #20
+ sub { my($cdr, $field) = @_; }, #TermCircuit
+ sub { my($cdr, $field) = @_; }, #TermCircuitType
+ 'carrierid', #OutboundCarrierId
+
+ #BillingNumber
+ #'charged_party',
+ sub {
+ my( $cdr, $field, $conf ) = @_;
+
+ #could be more efficient for the no config case, if anyone ever needs that
+ $da_rewrite ||= $conf->config('cdr-taqua-da_rewrite');
+
+ if ( $da_rewrite && $field =~ /\d/ ) {
+ my $rewrite = $da_rewrite;
+ $rewrite =~ s/\s//g;
+ my @rewrite = split(',', $conf->config('cdr-taqua-da_rewrite') );
+ if ( grep { $field eq $_ } @rewrite ) {
+ $cdr->charged_party( $cdr->src() );
+ $cdr->calltypenum(12);
+ return;
+ }
+ }
+ if ( $cdr->is_tollfree ) { # thankfully this is already available
+ $cdr->charged_party($cdr->dst); # and this
+ } else {
+ $cdr->charged_party($field);
+ }
+ },
+
+ sub { my($cdr, $field) = @_; }, #SubscriberNumber
+ 'lastapp', #ServiceName
+ sub { my($cdr, $field) = @_; }, #some weirdness #ChargeTime
+ 'lastdata', #ServiceInformation
+ sub { my($cdr, $field) = @_; }, #FacilityInfo
+ sub { my($cdr, $field) = @_; }, #all 1900-01-01 0#CallTraceTime
+
+ #30
+ sub { my($cdr, $field) = @_; }, #all-1#UniqueIndicator
+ sub { my($cdr, $field) = @_; }, #all-1#PresentationIndicator
+ sub { my($cdr, $field) = @_; }, #empty#Pin
+ 'calltypenum', #CallType
+
+ #nothing below is used by QIS...
+
+ sub { my($cdr, $field) = @_; }, #Balt/empty #OrigRateCenter
+ sub { my($cdr, $field) = @_; }, #Balt/empty #TermRateCenter
+
+ #OrigTrunkGroup
+ #it appears channels are actually part of trunk groups, but this data
+ #is interesting and we need a source and destination place to put it
+ 'channel', #OrigTrunkGroup
+
+ 'userfield', #empty#UserDefined
+ sub { my($cdr, $field) = @_; }, #empty#PseudoDestinationNumber
+ sub { my($cdr, $field) = @_; }, #all-1#PseudoCarrierCode
+
+ #40
+ sub { my($cdr, $field) = @_; }, #empty#PseudoANI
+ sub { my($cdr, $field) = @_; }, #all-1#PseudoFacilityInfo
+ sub { my($cdr, $field) = @_; }, #OrigDialedDigits
+ sub { my($cdr, $field) = @_; }, #all-1#OrigOutboundCarrier
+ sub { my($cdr, $field) = @_; }, #IncomingCarrierID
+ 'dcontext', #JurisdictionInfo
+ sub { my($cdr, $field) = @_; }, #OrigDestDigits
+ sub { my($cdr, $field) = @_; }, #huh?#InsertTime
+ sub { my($cdr, $field) = @_; }, #key
+ sub { my($cdr, $field) = @_; }, #empty#AMALineNumber
+
+ #50
+ sub { my($cdr, $field) = @_; }, #empty#AMAslpID
+ sub { my($cdr, $field) = @_; }, #empty#AMADigitsDialedWC
+ sub { my($cdr, $field) = @_; }, #OpxOffHook
+ sub { my($cdr, $field) = @_; }, #OpxOnHook
+
+ #acctid - primary key
+ #AUTO #calldate - Call timestamp (SQL timestamp)
+#clid - Caller*ID with text
+ #XXX src - Caller*ID number / Source number
+ #XXX dst - Destination extension
+ #dcontext - Destination context
+ #channel - Channel used
+ #dstchannel - Destination channel if appropriate
+ #lastapp - Last application if appropriate
+ #lastdata - Last application data
+ #startdate - Start of call (UNIX-style integer timestamp)
+ #answerdate - Answer time of call (UNIX-style integer timestamp)
+ #enddate - End time of call (UNIX-style integer timestamp)
+ #HACK#duration - Total time in system, in seconds
+ #HACK#XXX billsec - Total time call is up, in seconds
+ #disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+#INT amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
+ #accountcode - CDR account number to use: account
+
+ #uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+ #userfield - CDR user-defined field
+
+ #X cdrtypenum - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+ #XXX charged_party - Service number to be billed
+#upstream_currency - Wholesale currency from upstream
+#X upstream_price - Wholesale price from upstream
+#upstream_rateplanid - Upstream rate plan ID
+#rated_price - Rated (or re-rated) price
+#distance - km (need units field?)
+#islocal - Local - 1, Non Local = 0
+#calltypenum - Type of call - see FS::cdr_calltype
+#X description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
+#quantity - Number of items (cdr_type 7&8 only)
+#carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+#upstream_rateid - Upstream Rate ID
+
+ #svcnum - Link to customer service (see FS::cust_svc)
+ #freesidestatus - NULL, done (or something)
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/transnexus.pm b/FS/FS/cdr/transnexus.pm
new file mode 100644
index 0000000..0ed7ad4
--- /dev/null
+++ b/FS/FS/cdr/transnexus.pm
@@ -0,0 +1,66 @@
+package FS::cdr::transnexus;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info );
+use MIME::Base64;
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Transnexus',
+ 'weight' => 18,
+ 'type' => 'csv',
+ 'sep_char' => "\t",
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+
+ _cdr_date_parser_maker('startddate'), #O_CallStartTime
+ 'src', #CallingNumberReported
+ 'dst', #CalledNumberReported
+ 'channel', #SourceDeviceName / O_ReportingDeviceName
+ 'dstchannel', #O_ReportingDeviceName / DestinationDeviceName
+ sub { $_[0]->clid( decode_base64($_[1]) ); }, #CallId
+ 'uniqueid', #TransactionId
+ 'duration', #RatedDuration
+ 'billsec', #O_BillingDuration
+ 'upstream_price', #O_BillingAmountCustCurr
+ ],
+);
+
+1;
+
+__END__
+
+O_CallStartTime - Date and time stamp of the call setup as reported in the CDR from the source device.
+
+CallingNumberReported - Calling number from the source device reported in authorization request to the OSPrey server.
+
+CalledNumberReported - Called number from the source device reported in authorization request to the OSPrey server.
+
+----
+1.1.1 Customer CDR Archive File
+
+SourceDeviceName - The IP address or Domain Name of the device which is the call source.
+
+O_ReportingDeviceName - IP address or Domain Name of the source (Originating) device reporting the CDR to the OSPrey Server. If a proxy is used, (such as SIP proxy for signaling or FreeRADIUS for CDR reporting) this field is the IP address of the proxy device, not the actual source device.
+
+---
+or 1.1.2 Provider CDR Archive File
+
+O_ReportingDeviceName - IP address or Domain Name of the source (Originating) device reporting the CDR to the OSPrey Server. If a proxy is used, (such as SIP proxy for signaling or FreeRADIUS for CDR reporting) this field is the IP address of the proxy device, not the actual source device.
+
+DestinationDeviceName - The IP address or Domain Name of the destination device.
+
+----
+
+CallId - The Call Identifier generated by the source VoIP device.
+
+TransactionId - The unique Transaction Identification number created by the OSPrey server for each call
+
+RatedDuration - The rateable duration calculated by NexOSS.
+
+O_BillingDuration - The duration used to calculate the billable amount for a call from the source (Originating) network. This value is derived from RatedDuration and rounded up based on the ¿First Increment¿ or ¿Next Increment¿ rules defined in the Product or Customer Rate Plan used to rate the call.
+
+O_BillingAmountCustCurr - Amount billable to the source (Originating) Customer. Provided in the currency of the Product or Customer Rate Plan.
+
diff --git a/FS/FS/cdr/troop.pm b/FS/FS/cdr/troop.pm
new file mode 100644
index 0000000..020af2b
--- /dev/null
+++ b/FS/FS/cdr/troop.pm
@@ -0,0 +1,128 @@
+package FS::cdr::troop;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info $tmp_mon $tmp_mday $tmp_year );
+use Time::Local;
+#use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Troop',
+ 'weight' => 220,
+ 'header' => 2,
+ 'type' => 'xls',
+
+ 'import_fields' => [
+
+ # CDR FIELD / REQUIRED / Notes
+
+ # / No / CDR sequence number
+ sub {},
+
+ # WTN / Yes
+ 'charged_party',
+
+ # Account Code / Yes / Account Code (security) and we need on invoice
+ 'accountcode',
+
+ # DT / Yes / "DATE" Excel
+ # XXX false laziness w/bell_west.pm
+ sub { my($cdr, $date) = @_;
+
+ my $datetime = DateTime::Format::Excel->parse_datetime( $date );
+ $tmp_mon = $datetime->mon_0;
+ $tmp_mday = $datetime->mday;
+ $tmp_year = $datetime->year;
+ },
+
+ # Time / Yes / "TIME" excel
+ sub { my($cdr, $time) = @_;
+ #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
+
+ #$sec = $time * 86400;
+ my $sec = int( $time * 86400 + .5);
+
+ #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
+ $cdr->startdate(
+ timelocal(0, 0, 0, $tmp_mday, $tmp_mon, $tmp_year) + $sec
+ );
+ },
+
+
+ # Dur. / Yes / Units = seconds
+ 'billsec',
+
+ # OVS Type / Maybe / add "011" to international calls
+ # N = DOM LD / normal
+ # Z = INTL LD
+ # O = INTL LD
+ # others...?
+ sub { my($cdr, $ovs) = @_;
+ my $pre = ( $ovs =~ /^\s*[OZ]\s*$/i ) ? '011' : '1';
+ $cdr->dst( $pre. $cdr->dst ) unless $cdr->dst =~ /^$pre/;
+ },
+
+ # Number / YES
+ 'src',
+
+ # City / No
+ 'channel',
+
+ # Prov/State / No / We will use your Freeside rating and description name
+ sub { my($cdr, $state) = @_;
+ $cdr->channel( $cdr->channel. ", $state" )
+ if $state;
+ },
+
+ # Number / Yes
+ 'dst',
+
+ # City / No
+ 'dstchannel',
+
+ # Prov/State / No / We will use your Freeside rating and description name
+ sub { my($cdr, $state) = @_;
+ $cdr->dstchannel( $cdr->dstchannel. ", $state" )
+ if $state;
+ },
+
+ # OVS / Maybe
+ # Would help to add "011" to international calls (if you are willing)
+ # (using ovs above)
+ sub { my($cdr, $ovs) = @_;
+ my @ignore = ( 'BELL', 'CANADA', 'UNITED STATES', );
+ $cdr->dstchannel( $cdr->dstchannel. ", $ovs" )
+ if $ovs && ! grep { $ovs =~ /^\s*$_\s*$/ } @ignore;
+ },
+
+ # CC Ind. / No / Does show if Calling card but should not be required
+ #'N' or 'E'
+ sub {},
+
+ # Call Charge / No / Bell billing info and is not required
+ 'upstream_price',
+
+ # Account # / No / Bell billing info and is not required
+ sub {},
+
+ # Net Charge / No / Bell billing info and is not required
+ sub {},
+
+ # Surcharge / No / Taxes and is not required
+ sub {},
+
+ # GST / No / Taxes and is not required
+ sub {},
+
+ # PST / No / Taxes and is not required
+ sub {},
+
+ # HST / No / Taxes and is not required
+ sub {},
+
+ ],
+
+);
+
+1;
+
diff --git a/FS/FS/cdr/unitel.pm b/FS/FS/cdr/unitel.pm
new file mode 100644
index 0000000..df34a57
--- /dev/null
+++ b/FS/FS/cdr/unitel.pm
@@ -0,0 +1,39 @@
+package FS::cdr::unitel;
+
+use strict;
+use vars qw(@ISA %info);
+use FS::cdr;
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Unitel/RSLCOM',
+ 'weight' => 500,
+ 'import_fields' => [
+ 'uniqueid',
+ #'cdr_type',
+ 'cdrtypenum',
+ 'calldate', # may need massaging? huh maybe not...
+ #'billsec', #XXX duration and billsec?
+ sub { $_[0]->billsec( $_[1] );
+ $_[0]->duration( $_[1] );
+ },
+ 'src',
+ 'dst', # XXX needs to have "+61" prepended unless /^\+/ ???
+ 'charged_party',
+ 'upstream_currency',
+ 'upstream_price',
+ 'upstream_rateplanid',
+ 'distance',
+ 'islocal',
+ 'calltypenum',
+ 'startdate', #XXX needs massaging
+ 'enddate', #XXX same
+ 'description',
+ 'quantity',
+ 'carrierid',
+ 'upstream_rateid',
+ ]
+);
+
+1;
diff --git a/FS/FS/cdr/vitelity.pm b/FS/FS/cdr/vitelity.pm
new file mode 100644
index 0000000..97ed0c3
--- /dev/null
+++ b/FS/FS/cdr/vitelity.pm
@@ -0,0 +1,25 @@
+package FS::cdr::vitelity;
+
+use strict;
+use vars qw( @ISA %info );
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Vitelity',
+ 'weight' => 100,
+ 'header' => 1,
+ 'import_fields' => [
+ # Cheers to Vitelity for their concise, readable CDR format.
+ _cdr_date_parser_maker('startdate'),
+ 'src',
+ 'dst',
+ 'duration',
+ 'clid',
+ 'disposition',
+ 'upstream_price',
+ ],
+);
+
+1;
diff --git a/FS/FS/cdr/wip.pm b/FS/FS/cdr/wip.pm
new file mode 100644
index 0000000..070e253
--- /dev/null
+++ b/FS/FS/cdr/wip.pm
@@ -0,0 +1,49 @@
+package FS::cdr::wip;
+
+use strict;
+use vars qw( @ISA %info );
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'WIP',
+ 'weight' => 100,
+ 'header' => 1,
+ 'type' => 'csv',
+ 'sep_char' => ':',
+ 'import_fields' => [
+# All of these are based on the January 2010 version of the spec,
+# except that we assume that before all the fields mentioned in the
+# spec, there's a counter field.
+ skip(4), # counter, id, APCSJursID, RecordType
+ 'unique_id', # CDRID
+ skip(1), # AccountNumber; empty
+ 'charged_party', # ServiceNumber
+ skip(1), # ServiceNumberType
+ 'src', # PointOrigin
+ 'dst', # PointTarget
+ 'calltypenum', # Jurisdiction: need to remap
+ _cdr_date_parser_maker('startdate'), #TransactionDate
+ skip(3), # BillClass, TypeIDUsage, ElementID
+ 'duration', # PrimaryUnits
+ skip(6), # CompletionStatus, Latitude, Longitude,
+ # OriginDescription, TargetDescription, RatePeriod
+ 'billsec', # RatedUnits; seems to always be equal to PrimaryUnits
+ skip(6), #SecondsUnits, ThirdUnits, FileID, OriginalExtractSequenceNumber,
+ #RateClass, #ProviderClass
+ skip(8), #ProviderID, CurrencyCode, EquipmentTypeCode, ClassOfServiceCode,
+ #RateUnitsType, DistanceBandID, ZoneClass, CDRStatus
+ 'upstream_price', # ISPBuy
+ skip(2), # EUBuy, CDRFromCarrier
+ ],
+# Need clarification on:
+# Values for RecordType, Jurisdiction, CompletionStatus, and ProviderClass
+# Do we care about the following:
+# AccountNumber, ServiceNumberType, CDRStatus
+
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
diff --git a/FS/FS/cdr_batch.pm b/FS/FS/cdr_batch.pm
new file mode 100644
index 0000000..59cfd2c
--- /dev/null
+++ b/FS/FS/cdr_batch.pm
@@ -0,0 +1,128 @@
+package FS::cdr_batch;
+
+use strict;
+use base qw( FS::Record );
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cdr_batch - Object methods for cdr_batch records
+
+=head1 SYNOPSIS
+
+ use FS::cdr_batch;
+
+ $record = new FS::cdr_batch \%hash;
+ $record = new FS::cdr_batch { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_batch object represents a CDR batch. FS::cdr_batch inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item cdrbatchnum
+
+primary key
+
+=item cdrbatch
+
+cdrbatch
+
+=item _date
+
+_date
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new batch. To add the batch 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 { 'cdr_batch'; }
+
+=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 batch. 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('cdrbatchnum')
+ || $self->ut_textn('cdrbatch')
+ || $self->ut_numbern('_date')
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cdr>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr_calltype.pm b/FS/FS/cdr_calltype.pm
new file mode 100644
index 0000000..fe45608
--- /dev/null
+++ b/FS/FS/cdr_calltype.pm
@@ -0,0 +1,115 @@
+package FS::cdr_calltype;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cdr_calltype - Object methods for cdr_calltype records
+
+=head1 SYNOPSIS
+
+ use FS::cdr_calltype;
+
+ $record = new FS::cdr_calltype \%hash;
+ $record = new FS::cdr_calltype { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_calltype object represents an CDR call type. FS::cdr_calltype
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item calltypenum - primary key
+
+=item calltypename - CDR call type name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new call type. To add the call type 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 { 'cdr_calltype'; }
+
+=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 call 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;
+
+ my $error =
+ $self->ut_numbern('calltypenum')
+ || $self->ut_text('calltypename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr_carrier.pm b/FS/FS/cdr_carrier.pm
new file mode 100644
index 0000000..609c939
--- /dev/null
+++ b/FS/FS/cdr_carrier.pm
@@ -0,0 +1,116 @@
+package FS::cdr_carrier;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cdr_carrier - Object methods for cdr_carrier records
+
+=head1 SYNOPSIS
+
+ use FS::cdr_carrier;
+
+ $record = new FS::cdr_carrier \%hash;
+ $record = new FS::cdr_carrier { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_carrier object represents an CDR carrier or upstream.
+FS::cdr_carrier inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item carrierid - primary key
+
+=item carriername - Carrier name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new carrier. To add the carrier 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 { 'cdr_carrier'; }
+
+=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 carrier. 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('carrierid')
+ || $self->ut_text('carriername')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr_termination.pm b/FS/FS/cdr_termination.pm
new file mode 100644
index 0000000..5e30805
--- /dev/null
+++ b/FS/FS/cdr_termination.pm
@@ -0,0 +1,155 @@
+package FS::cdr_termination;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cdr_termination - Object methods for cdr_termination records
+
+=head1 SYNOPSIS
+
+ use FS::cdr_termination;
+
+ $record = new FS::cdr_termination \%hash;
+ $record = new FS::cdr_termination { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_termination object represents an CDR termination status.
+FS::cdr_termination inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item cdrtermnum
+
+primary key
+
+=item acctid
+
+acctid
+
+=item termpart
+
+termpart
+
+=item rated_price
+
+rated_price
+
+=item status
+
+status
+
+=item svcnum
+
+svc_phone record associated with this transaction, if there is one.
+
+=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 { 'cdr_termination'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('cdrtermnum')
+ || $self->ut_foreign_key('acctid', 'cdr', 'acctid')
+ #|| $self->ut_foreign_key('termpart', 'part_termination', 'termpart')
+ || $self->ut_number('termpart')
+ || $self->ut_float('rated_price')
+ || $self->ut_enum('status', [ '', 'done' ] ) # , 'skipped' ] )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+#=item set_status_and_rated_price STATUS [ RATED_PRICE ]
+#
+#Sets the status to the provided string. If there is an error, returns the
+#error, otherwise returns false.
+#
+#=cut
+#
+#sub set_status_and_rated_price {
+# my($self, $status, $rated_price) = @_;
+# $self->status($status);
+# $self->rated_price($rated_price);
+# $self->replace();
+#}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cdr>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cdr_type.pm b/FS/FS/cdr_type.pm
new file mode 100644
index 0000000..e258bf8
--- /dev/null
+++ b/FS/FS/cdr_type.pm
@@ -0,0 +1,119 @@
+package FS::cdr_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cdr_type - Object methods for cdr_type records
+
+=head1 SYNOPSIS
+
+ use FS::cdr_type;
+
+ $record = new FS::cdr_type \%hash;
+ $record = new FS::cdr_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_type object represents an CDR type. FS::cdr_type inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item cdrtypenum - primary key
+
+=item typename - CDR type name
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new CDR type. To add the CDR type 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 { 'cdr_type'; }
+
+=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 CDR type. 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('cdrtypenum')
+ || $self->ut_text('typename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cgp_rule.pm b/FS/FS/cgp_rule.pm
new file mode 100644
index 0000000..ad5ab1e
--- /dev/null
+++ b/FS/FS/cgp_rule.pm
@@ -0,0 +1,320 @@
+package FS::cgp_rule;
+
+use strict;
+use base qw( FS::o2m_Common FS::Record );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_svc;
+use FS::cgp_rule_condition;
+use FS::cgp_rule_action;
+
+=head1 NAME
+
+FS::cgp_rule - Object methods for cgp_rule records
+
+=head1 SYNOPSIS
+
+ use FS::cgp_rule;
+
+ $record = new FS::cgp_rule \%hash;
+ $record = new FS::cgp_rule { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cgp_rule object represents a mail filtering rule. FS::cgp_rule
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item rulenum
+
+primary key
+
+=item name
+
+name
+
+=item comment
+
+comment
+
+=item svcnum
+
+svcnum
+
+=item priority
+
+priority
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new rule. To add the rule 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 { 'cgp_rule'; }
+
+=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;
+
+ my $error = $self->SUPER::insert(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $error = $self->svc_export;
+ 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;
+
+ 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 = $self->cgp_rule_condition;
+ push @del, $self->cgp_rule_action;
+
+ foreach my $del (@del) {
+ my $error = $del->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $error = $self->svc_export;
+ 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
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( ref($_[0]) eq ref($new) )
+ ? shift
+ : $new->replace_old;
+
+ 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 or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ }
+
+ $error = $new->svc_export;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item svc_export
+
+Calls the replace export for any communigate exports attached to this rule's
+service.
+
+=cut
+
+sub svc_export {
+ my $self = shift;
+
+ my $cust_svc = $self->cust_svc;
+ my $svc_x = $cust_svc->svc_x;
+
+ #_singledomain too
+ my @exports = $cust_svc->part_svc->part_export('communigate_pro');
+ my @errors = map $_->export_replace($svc_x, $svc_x), @exports;
+
+ @errors ? join(' / ', @errors) : '';
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid rule. 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('rulenum')
+ || $self->ut_text('name')
+ || $self->ut_textn('comment')
+ || $self->ut_foreign_key('svcnum', 'cust_svc', 'svcnum')
+ || $self->ut_number('priority')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_svc
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item cgp_rule_condition
+
+Returns the conditions associated with this rule, as FS::cgp_rule_condition
+objects.
+
+=cut
+
+sub cgp_rule_condition {
+ my $self = shift;
+ qsearch('cgp_rule_condition', { 'rulenum' => $self->rulenum } );
+}
+
+=item cgp_rule_action
+
+Returns the actions associated with this rule, as FS::cgp_rule_action
+objects.
+
+=cut
+
+sub cgp_rule_action {
+ my $self = shift;
+ qsearch('cgp_rule_action', { 'rulenum' => $self->rulenum } );
+}
+
+=item arrayref
+
+Returns an arraref representing this rule, suitable for Communigate Pro API
+commands:
+
+The first element specifies the rule priority.
+
+The second element specifies the rule name.
+
+The third element specifies the rule conditions.
+
+The fourth element specifies the rule actions.
+
+The fifth element specifies the rule comment.
+
+=cut
+
+sub arrayref {
+ my $self = shift;
+ [ $self->priority,
+ $self->name,
+ [ map $_->arrayref, $self->cgp_rule_condition ],
+ [ map $_->arrayref, $self->cgp_rule_action ],
+ $self->comment,
+ ],
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cgp_rule_action.pm b/FS/FS/cgp_rule_action.pm
new file mode 100644
index 0000000..71605a9
--- /dev/null
+++ b/FS/FS/cgp_rule_action.pm
@@ -0,0 +1,141 @@
+package FS::cgp_rule_action;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cgp_rule;
+
+=head1 NAME
+
+FS::cgp_rule_action - Object methods for cgp_rule_action records
+
+=head1 SYNOPSIS
+
+ use FS::cgp_rule_action;
+
+ $record = new FS::cgp_rule_action \%hash;
+ $record = new FS::cgp_rule_action { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cgp_rule_action object represents a mail filtering action.
+FS::cgp_rule_action inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item ruleactionnum
+
+primary key
+
+=item action
+
+action
+
+=item params
+
+params
+
+=item rulenum
+
+rulenum
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new action. To add the action 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 { 'cgp_rule_action'; }
+
+=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 action. 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('ruleactionnum')
+ || $self->ut_text('action')
+ || $self->ut_textn('params')
+ || $self->ut_foreign_key('rulenum', 'cgp_rule', 'rulenum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item arrayref
+
+=cut
+
+sub arrayref {
+ my $self = shift;
+ [ $self->action, $self->params ];
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cgp_rule_condition.pm b/FS/FS/cgp_rule_condition.pm
new file mode 100644
index 0000000..02ea172
--- /dev/null
+++ b/FS/FS/cgp_rule_condition.pm
@@ -0,0 +1,148 @@
+package FS::cgp_rule_condition;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cgp_rule;
+
+=head1 NAME
+
+FS::cgp_rule_condition - Object methods for cgp_rule_condition records
+
+=head1 SYNOPSIS
+
+ use FS::cgp_rule_condition;
+
+ $record = new FS::cgp_rule_condition \%hash;
+ $record = new FS::cgp_rule_condition { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cgp_rule_condition object represents a mail filtering condition.
+FS::cgp_rule_condition inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item ruleconditionnum
+
+primary key
+
+=item condition
+
+condition
+
+=item op
+
+op
+
+=item params
+
+params
+
+=item rulenum
+
+rulenum
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new condition. To add the condition 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 { 'cgp_rule_condition'; }
+
+=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 condition. 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('ruleconditionnum')
+ || $self->ut_text('condition')
+ || $self->ut_textn('op')
+ || $self->ut_textn('params')
+ || $self->ut_foreign_key('rulenum', 'cgp_rule', 'rulenum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item arrayref
+
+Returns an array reference of the condition, op and params fields.
+
+=cut
+
+sub arrayref {
+ my $self = shift;
+ [ map $self->$_, qw( condition op params ) ];
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/class_Common.pm b/FS/FS/class_Common.pm
new file mode 100644
index 0000000..5ee8208
--- /dev/null
+++ b/FS/FS/class_Common.pm
@@ -0,0 +1,143 @@
+package FS::class_Common;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::class_Common - Base class for classification classes
+
+=head1 SYNOPSIS
+
+use base qw( FS::class_Common );
+use FS::category_table; #should use this
+
+#required
+sub _target_table { 'table_name'; }
+
+#optional for non-standard names
+sub _target_column { 'classnum'; } #default is classnum
+sub _category_table { 'table_name'; } #default is to replace s/class/category/
+
+=head1 DESCRIPTION
+
+FS::class_Common is a base class for classes which provide a classification for
+other classes, such as pkg_class or cust_class.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new classification. To add the classfication to the database, see
+L<"insert">.
+
+=cut
+
+=item insert
+
+Adds this classification to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Deletes this classification from the database. Only classifications with no
+associated target objects can be deleted. If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a ". $self->table.
+ " with ". $self->_target_table. " records!"
+ if qsearch( $self->_target_table,
+ { $self->_target_column => $self->classnum }
+ );
+
+ $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 package classification. 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('classnum')
+ or $self->ut_text('classname')
+ or $self->ut_foreign_keyn( 'categorynum',
+ $self->_category_table,
+ 'categorynum',
+ )
+ or $self->ut_enum('disabled', [ '', 'Y' ] )
+ or $self->SUPER::check;
+
+}
+
+=item category
+
+Returns the category record associated with this class, or false if there is
+none.
+
+=cut
+
+sub category {
+ my $self = shift;
+ qsearchs($self->_category_table, { 'categorynum' => $self->categorynum } );
+}
+
+=item categoryname
+
+Returns the category name associated with this class, or false if there
+is none.
+
+=cut
+
+sub categoryname {
+ my $category = shift->category;
+ $category ? $category->categoryname : '';
+}
+
+#required
+sub _target_table {
+ my $self = shift;
+ die "_target_table unspecified for $self";
+}
+
+#defaults
+
+sub _target_column { 'classnum'; }
+
+use vars qw( $_category_table );
+sub _category_table {
+ return $_category_table if $_category_table;
+ my $self = shift;
+ $_category_table = $self->table;
+ $_category_table =~ s/class/category/ # s/_class$/_category/
+ or die "can't determine an automatic category table for $_category_table";
+ $_category_table;
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::category_Common>, L<FS::pkg_class>, L<FS::cust_class>
+
+=cut
+
+1;
diff --git a/FS/FS/clientapi_session.pm b/FS/FS/clientapi_session.pm
new file mode 100644
index 0000000..f71a126
--- /dev/null
+++ b/FS/FS/clientapi_session.pm
@@ -0,0 +1,121 @@
+package FS::clientapi_session;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::clientapi_session - Object methods for clientapi_session records
+
+=head1 SYNOPSIS
+
+ use FS::clientapi_session;
+
+ $record = new FS::clientapi_session \%hash;
+ $record = new FS::clientapi_session { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::clientapi_session object represents an FS::ClientAPI session.
+FS::clientapi_session inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item sessionnum - primary key
+
+=item sessionid - session ID
+
+=item namespace - session namespace
+
+=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 { 'clientapi_session'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('primary_key')
+ || $self->ut_number('validate_other_fields')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::ClientAPI>, <FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/clientapi_session_field.pm b/FS/FS/clientapi_session_field.pm
new file mode 100644
index 0000000..085e956
--- /dev/null
+++ b/FS/FS/clientapi_session_field.pm
@@ -0,0 +1,124 @@
+package FS::clientapi_session_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::clientapi_session_field - Object methods for clientapi_session_field records
+
+=head1 SYNOPSIS
+
+ use FS::clientapi_session_field;
+
+ $record = new FS::clientapi_session_field \%hash;
+ $record = new FS::clientapi_session_field { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::clientapi_session_field object represents a FS::ClientAPI session data
+field. FS::clientapi_session_field inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item fieldnum - primary key
+
+=item sessionnum - Base ClientAPI sesison (see L<FS::clientapi_session>)
+
+=item fieldname
+
+=item fieldvalie
+
+=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 { 'clientapi_session_field'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('primary_key')
+ || $self->ut_number('validate_other_fields')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::clientapi_session>, L<FS::ClientAPI>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/conf.pm b/FS/FS/conf.pm
new file mode 100644
index 0000000..3faab14
--- /dev/null
+++ b/FS/FS/conf.pm
@@ -0,0 +1,114 @@
+package FS::conf;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::conf - Object methods for conf records
+
+=head1 SYNOPSIS
+
+ use FS::conf;
+
+ $record = new FS::conf \%hash;
+ $record = new FS::conf { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::conf object represents a configuration value. FS::conf inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item confnum - primary key
+
+=item agentnum - the agent to which this configuration value applies
+
+=item name - the name of the configuration value
+
+=item value - the configuration value
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new configuration value. 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 { 'conf'; }
+
+=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 configuration value. 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('confnum')
+ || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
+ || $self->ut_text('name')
+ || $self->ut_anything('value')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
new file mode 100644
index 0000000..d3ab411
--- /dev/null
+++ b/FS/FS/contact.pm
@@ -0,0 +1,177 @@
+package FS::contact;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::prospect_main;
+use FS::cust_main;
+use FS::cust_location;
+
+=head1 NAME
+
+FS::contact - Object methods for contact records
+
+=head1 SYNOPSIS
+
+ use FS::contact;
+
+ $record = new FS::contact \%hash;
+ $record = new FS::contact { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact object represents an example. FS::contact inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactnum
+
+primary key
+
+=item prospectnum
+
+prospectnum
+
+=item custnum
+
+custnum
+
+=item locationnum
+
+locationnum
+
+=item last
+
+last
+
+=item first
+
+first
+
+=item title
+
+title
+
+=item comment
+
+comment
+
+=item disabled
+
+disabled
+
+
+=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 { 'contact'; }
+
+=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('contactnum')
+ || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
+ || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
+ || $self->ut_textn('last')
+ || $self->ut_textn('first')
+ || $self->ut_textn('title')
+ || $self->ut_textn('comment')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
+ return "Prospect and customer!" if $self->prospectnum && $self->custnum;
+
+ return "One of first name, last name, or title must have a value"
+ if ! grep $self->$_(), qw( first last title);
+
+ $self->SUPER::check;
+}
+
+sub line {
+ my $self = shift;
+ my $data = $self->first. ' '. $self->last;
+ $data .= ', '. $self->title
+ if $self->title;
+ $data .= ' ('. $self->comment. ')'
+ if $self->comment;
+ $data;
+}
+
+=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/FS/FS/contact_email.pm b/FS/FS/contact_email.pm
new file mode 100644
index 0000000..1276d8d
--- /dev/null
+++ b/FS/FS/contact_email.pm
@@ -0,0 +1,128 @@
+package FS::contact_email;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::contact_email - Object methods for contact_email records
+
+=head1 SYNOPSIS
+
+ use FS::contact_email;
+
+ $record = new FS::contact_email \%hash;
+ $record = new FS::contact_email { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact_email object represents an example. FS::contact_email inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactemailnum
+
+primary key
+
+=item contactnum
+
+contactnum
+
+=item emailaddress
+
+emailaddress
+
+
+=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 { 'contact_email'; }
+
+=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('contactemailnum')
+ || $self->ut_number('contactnum')
+ || $self->ut_text('emailaddress')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=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/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm
new file mode 100644
index 0000000..bb9cf03
--- /dev/null
+++ b/FS/FS/contact_phone.pm
@@ -0,0 +1,143 @@
+package FS::contact_phone;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::contact_phone - Object methods for contact_phone records
+
+=head1 SYNOPSIS
+
+ use FS::contact_phone;
+
+ $record = new FS::contact_phone \%hash;
+ $record = new FS::contact_phone { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact_phone object represents an example. FS::contact_phone inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactphonenum
+
+primary key
+
+=item contactnum
+
+contactnum
+
+=item phonetypenum
+
+phonetypenum
+
+=item countrycode
+
+countrycode
+
+=item phonenum
+
+phonenum
+
+=item extension
+
+extension
+
+
+=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 { 'contact_phone'; }
+
+=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('contactphonenum')
+ || $self->ut_number('contactnum')
+ || $self->ut_number('phonetypenum')
+ || $self->ut_text('countrycode')
+ || $self->ut_text('phonenum')
+ || $self->ut_text('extension')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=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/FS/FS/cust_attachment.pm b/FS/FS/cust_attachment.pm
new file mode 100644
index 0000000..5e5e076
--- /dev/null
+++ b/FS/FS/cust_attachment.pm
@@ -0,0 +1,199 @@
+package FS::cust_attachment;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::Record );
+use Carp;
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+
+=head1 NAME
+
+FS::cust_attachment - Object methods for cust_attachment records
+
+=head1 SYNOPSIS
+
+ use FS::cust_attachment;
+
+ $record = new FS::cust_attachment \%hash;
+ $record = new FS::cust_attachment { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_attachment object represents a file attached to a L<FS::cust_main>
+object. FS::cust_attachment inherits from FS::Record. The following fields
+are currently supported:
+
+=over 4
+
+=item attachnum
+
+Primary key (assigned automatically).
+
+=item custnum
+
+Customer number (see L<FS::cust_main>).
+
+=item _date
+
+The date the record was last updated.
+
+=item usernum
+
+Order taker (see L<FS::access_user>)
+
+=item filename
+
+The file's name.
+
+=item mime_type
+
+The Content-Type of the file.
+
+=item body
+
+The contents of the file.
+
+=item disabled
+
+If the attachment was disabled, this contains the date it was disabled.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new attachment object.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_attachment'; }
+
+sub nohistory_fields { 'body'; }
+
+=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
+
+# 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 $conf = new FS::Conf;
+ my $error;
+ if($conf->config('disable_cust_attachment') ) {
+ $error = 'Attachments disabled (see configuration)';
+ }
+
+ $error =
+ $self->ut_numbern('attachnum')
+ || $self->ut_number('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('otaker')
+ || $self->ut_text('filename')
+ || $self->ut_text('mime_type')
+ || $self->ut_numbern('disabled')
+ || $self->ut_anything('body')
+ ;
+ if($conf->config('max_attachment_size')
+ and $self->size > $conf->config('max_attachment_size') ) {
+ $error = 'Attachment too large'
+ }
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item size
+
+Returns the size of the attachment in bytes.
+
+=cut
+
+sub size {
+ my $self = shift;
+ return length($self->body);
+}
+
+#false laziness w/otaker_Mixin & cust_main_note
+sub otaker {
+ my $self = shift;
+ if ( scalar(@_) ) { #set
+ my $otaker = shift;
+ my($l,$f) = (split(', ', $otaker));
+ my $access_user = qsearchs('access_user', { 'username'=>$otaker } )
+ || qsearchs('access_user', { 'first'=>$f, 'last'=>$l } )
+ or croak "can't set otaker: $otaker not found!"; #confess?
+ $self->usernum( $access_user->usernum );
+ $otaker; #not sure return is used anywhere, but just in case
+ } else { #get
+ if ( $self->usernum ) {
+ $self->access_user->username;
+ } elsif ( length($self->get('otaker')) ) {
+ $self->get('otaker');
+ } else {
+ '';
+ }
+ }
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+Doesn't work on non-Postgres systems.
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..1270530
--- /dev/null
+++ b/FS/FS/cust_bill.pm
@@ -0,0 +1,4557 @@
+package FS::cust_bill;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $conf $money_char $date_format $rdate_format );
+use vars qw( $invoice_lines @buf ); #yuck
+use Fcntl qw(:flock); #for spool_csv
+use List::Util qw(min max);
+use Date::Format;
+use Text::Template 1.20;
+use File::Temp 0.14;
+use String::ShellQuote;
+use HTML::Entities;
+use Locale::Country;
+use Storable qw( freeze thaw );
+use FS::UID qw( datasrc );
+use FS::Misc qw( send_email send_fax generate_ps generate_pdf do_print );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_main_Mixin;
+use FS::cust_main;
+use FS::cust_statement;
+use FS::cust_bill_pkg;
+use FS::cust_bill_pkg_display;
+use FS::cust_bill_pkg_detail;
+use FS::cust_credit;
+use FS::cust_pay;
+use FS::cust_pkg;
+use FS::cust_credit_bill;
+use FS::pay_batch;
+use FS::cust_pay_batch;
+use FS::cust_bill_event;
+use FS::cust_event;
+use FS::part_pkg;
+use FS::cust_bill_pay;
+use FS::cust_bill_pay_batch;
+use FS::part_bill_event;
+use FS::payby;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::cust_bill]';
+
+#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') || '$';
+ $date_format = $conf->config('date_format') || '%x';
+ $rdate_format = $conf->config('date_format') || '%m/%d/%Y';
+} );
+
+=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:
+
+Regular fields
+
+=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 invoice_terms - optional terms override for this specific invoice
+
+=back
+
+Customer info at invoice generation time
+
+=over 4
+
+=item previous_balance
+
+=item billing_balance
+
+=back
+
+Deprecated
+
+=over 4
+
+=item printed - deprecated
+
+=back
+
+Specific use cases
+
+=over 4
+
+=item closed - books closed flag, empty or `Y'
+
+=item statementnum - invoice aggregation (see L<FS::cust_statement>)
+
+=item agent_invid - legacy invoice number
+
+=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'; }
+
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum.
+ ' (cust_bill.invnum '. $self->invnum. ')';
+}
+
+=item insert
+
+Adds this invoice to the database ("Posts" the invoice). If there is an error,
+returns the error, otherwise returns false.
+
+=item delete
+
+This method now works but you probably shouldn't use it. Instead, apply a
+credit against the invoice.
+
+Using this method to delete invoices outright is really, really bad. There
+would be no record you ever posted this invoice, and there are no check to
+make sure charged = 0 or that there are no associated cust_bill_pkg records.
+
+Really, don't use it.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed invoice" 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 $table (qw(
+ cust_bill_event
+ cust_event
+ cust_credit_bill
+ cust_bill_pay
+ cust_bill_pay
+ cust_credit_bill
+ cust_pay_batch
+ cust_bill_pay_batch
+ cust_bill_pkg
+ )) {
+
+ foreach my $linked ( $self->$table() ) {
+ my $error = $linked->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 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
+
+#replace can be inherited from Record.pm
+
+# replace_check is now the preferred way to #implement replace data checks
+# (so $object->replace() works without an argument)
+
+sub replace_check {
+ 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
+ || $old->charged == 0;
+
+ '';
+}
+
+=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_foreign_key('custnum', 'cust_main', 'custnum' )
+ || $self->ut_numbern('_date')
+ || $self->ut_money('charged')
+ || $self->ut_numbern('printed')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ || $self->ut_foreign_keyn('statementnum', 'cust_statement', 'statementnum' )
+ || $self->ut_numbern('agent_invid') #varchar?
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ $self->printed(0) if $self->printed eq '';
+
+ $self->SUPER::check;
+}
+
+=item display_invnum
+
+Returns the displayed invoice number for this invoice: agent_invid if
+cust_bill-default_agent_invid is set and it has a value, invnum otherwise.
+
+=cut
+
+sub display_invnum {
+ my $self = shift;
+ if ( $conf->exists('cust_bill-default_agent_invid') && $self->agent_invid ){
+ return $self->agent_invid;
+ } else {
+ return $self->invnum;
+ }
+}
+
+=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(
+ { 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'invnum' => $self->invnum },
+ 'order_by' => 'ORDER BY billpkgnum',
+ }
+ );
+}
+
+=item cust_bill_pkg_pkgnum PKGNUM
+
+Returns the line items (see L<FS::cust_bill_pkg>) for this invoice and
+specified pkgnum.
+
+=cut
+
+sub cust_bill_pkg_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ qsearch(
+ { 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'invnum' => $self->invnum,
+ 'pkgnum' => $pkgnum,
+ },
+ 'order_by' => 'ORDER BY billpkgnum',
+ }
+ );
+}
+
+=item cust_pkg
+
+Returns the packages (see L<FS::cust_pkg>) corresponding to the line items for
+this invoice.
+
+=cut
+
+sub cust_pkg {
+ my $self = shift;
+ my @cust_pkg = map { $_->pkgnum > 0 ? $_->cust_pkg : () }
+ $self->cust_bill_pkg;
+ my %saw = ();
+ grep { ! $saw{$_->pkgnum}++ } @cust_pkg;
+}
+
+=item no_auto
+
+Returns true if any of the packages (or their definitions) corresponding to the
+line items for this invoice have the no_auto flag set.
+
+=cut
+
+sub no_auto {
+ my $self = shift;
+ grep { $_->no_auto || $_->part_pkg->no_auto } $self->cust_pkg;
+}
+
+=item open_cust_bill_pkg
+
+Returns the open line items for this invoice.
+
+Note that cust_bill_pkg with both setup and recur fees are returned as two
+separate line items, each with only one fee.
+
+=cut
+
+# modeled after cust_main::open_cust_bill
+sub open_cust_bill_pkg {
+ my $self = shift;
+
+ # grep { $_->owed > 0 } $self->cust_bill_pkg
+
+ my %other = ( 'recur' => 'setup',
+ 'setup' => 'recur', );
+ my @open = ();
+ foreach my $field ( qw( recur setup )) {
+ push @open, map { $_->set( $other{$field}, 0 ); $_; }
+ grep { $_->owed($field) > 0 }
+ $self->cust_bill_pkg;
+ }
+
+ @open;
+}
+
+=item cust_bill_event
+
+Returns the completed invoice events (deprecated, old-style 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 num_cust_bill_event
+
+Returns the number of completed invoice events (deprecated, old-style events - see L<FS::cust_bill_event>) for this invoice.
+
+=cut
+
+sub num_cust_bill_event {
+ my $self = shift;
+ my $sql =
+ "SELECT COUNT(*) FROM cust_bill_event WHERE invnum = ?";
+ my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute($self->invnum) or die $sth->errstr. " executing $sql";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item cust_event
+
+Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice.
+
+=cut
+
+#false laziness w/cust_pkg.pm
+sub cust_event {
+ my $self = shift;
+ qsearch({
+ 'table' => 'cust_event',
+ 'addl_from' => 'JOIN part_event USING ( eventpart )',
+ 'hashref' => { 'tablenum' => $self->invnum },
+ 'extra_sql' => " AND eventtable = 'cust_bill' ",
+ });
+}
+
+=item num_cust_event
+
+Returns the number of new-style customer billing events (see L<FS::cust_event>) for this invoice.
+
+=cut
+
+#false laziness w/cust_pkg.pm
+sub num_cust_event {
+ my $self = shift;
+ my $sql =
+ "SELECT COUNT(*) FROM cust_event JOIN part_event USING ( eventpart ) ".
+ " WHERE tablenum = ? AND eventtable = 'cust_bill'";
+ my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute($self->invnum) or die $sth->errstr. " executing $sql";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=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_suspend_if_balance_over AMOUNT
+
+Suspends the customer associated with this invoice if the total amount owed on
+this invoice and all older invoices is greater than the specified amount.
+
+Returns a list: an empty list on success or a list of errors.
+
+=cut
+
+sub cust_suspend_if_balance_over {
+ my( $self, $amount ) = ( shift, shift );
+ my $cust_main = $self->cust_main;
+ if ( $cust_main->total_owed_date($self->_date) < $amount ) {
+ return ();
+ } else {
+ $cust_main->suspend(@_);
+ }
+}
+
+=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 } )
+ #;
+}
+
+sub cust_pay_batch {
+ my $self = shift;
+ qsearch('cust_pay_batch', { 'invnum' => $self->invnum } );
+}
+
+sub cust_bill_pay_batch {
+ my $self = shift;
+ qsearch('cust_bill_pay_batch', { '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;
+ map { $_ } #return $self->num_cust_bill_pay unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
+}
+
+=item cust_credited
+
+=item cust_credit_bill
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
+
+=cut
+
+sub cust_credited {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_credit_bill unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
+ ;
+}
+
+sub cust_credit_bill {
+ shift->cust_credited(@_);
+}
+
+=item cust_bill_pay_pkgnum PKGNUM
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice
+with matching pkgnum.
+
+=cut
+
+sub cust_bill_pay_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ map { $_ } #return $self->num_cust_bill_pay_pkgnum($pkgnum) unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum,
+ 'pkgnum' => $pkgnum,
+ }
+ );
+}
+
+=item cust_credited_pkgnum PKGNUM
+
+=item cust_credit_bill_pkgnum PKGNUM
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice
+with matching pkgnum.
+
+=cut
+
+sub cust_credited_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ map { $_ } #return $self->num_cust_credit_bill_pkgnum($pkgnum) unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum,
+ 'pkgnum' => $pkgnum,
+ }
+ );
+}
+
+sub cust_credit_bill_pkgnum {
+ shift->cust_credited_pkgnum(@_);
+}
+
+=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;
+}
+
+sub owed_pkgnum {
+ my( $self, $pkgnum ) = @_;
+
+ #my $balance = $self->charged;
+ my $balance = 0;
+ $balance += $_->setup + $_->recur for $self->cust_bill_pkg_pkgnum($pkgnum);
+
+ $balance -= $_->amount for $self->cust_bill_pay_pkgnum($pkgnum);
+ $balance -= $_->amount for $self->cust_credited_pkgnum($pkgnum);
+
+ $balance = sprintf( "%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+=item apply_payments_and_credits [ OPTION => VALUE ... ]
+
+Applies unapplied payments and credits to this invoice.
+
+A hash of optional arguments may be passed. Currently "manual" is supported.
+If true, a payment receipt is sent instead of a statement when
+'payment_receipt_email' configuration option is set.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub apply_payments_and_credits {
+ my( $self, %options ) = @_;
+
+ 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 @payments = grep { $_->unapplied > 0 } $self->cust_main->cust_pay;
+ my @credits = grep { $_->credited > 0 } $self->cust_main->cust_credit;
+
+ if ( $conf->exists('pkg-balances') ) {
+ # limit @payments & @credits to those w/ a pkgnum grepped from $self
+ my %pkgnums = map { $_ => 1 } map $_->pkgnum, $self->cust_bill_pkg;
+ @payments = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @payments;
+ @credits = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @credits;
+ }
+
+ while ( $self->owed > 0 and ( @payments || @credits ) ) {
+
+ my $app = '';
+ if ( @payments && @credits ) {
+
+ #decide which goes first by weight of top (unapplied) line item
+
+ my @open_lineitems = $self->open_cust_bill_pkg;
+
+ my $max_pay_weight =
+ max( map { $_->part_pkg->pay_weight || 0 }
+ grep { $_ }
+ map { $_->cust_pkg }
+ @open_lineitems
+ );
+ my $max_credit_weight =
+ max( map { $_->part_pkg->credit_weight || 0 }
+ grep { $_ }
+ map { $_->cust_pkg }
+ @open_lineitems
+ );
+
+ #if both are the same... payments first? it has to be something
+ if ( $max_pay_weight >= $max_credit_weight ) {
+ $app = 'pay';
+ } else {
+ $app = 'credit';
+ }
+
+ } elsif ( @payments ) {
+ $app = 'pay';
+ } elsif ( @credits ) {
+ $app = 'credit';
+ } else {
+ die "guru meditation #12 and 35";
+ }
+
+ my $unapp_amount;
+ if ( $app eq 'pay' ) {
+
+ my $payment = shift @payments;
+ $unapp_amount = $payment->unapplied;
+ $app = new FS::cust_bill_pay { 'paynum' => $payment->paynum };
+ $app->pkgnum( $payment->pkgnum )
+ if $conf->exists('pkg-balances') && $payment->pkgnum;
+
+ } elsif ( $app eq 'credit' ) {
+
+ my $credit = shift @credits;
+ $unapp_amount = $credit->credited;
+ $app = new FS::cust_credit_bill { 'crednum' => $credit->crednum };
+ $app->pkgnum( $credit->pkgnum )
+ if $conf->exists('pkg-balances') && $credit->pkgnum;
+
+ } else {
+ die "guru meditation #12 and 35";
+ }
+
+ my $owed;
+ if ( $conf->exists('pkg-balances') && $app->pkgnum ) {
+ warn "owed_pkgnum ". $app->pkgnum;
+ $owed = $self->owed_pkgnum($app->pkgnum);
+ } else {
+ $owed = $self->owed;
+ }
+ next unless $owed > 0;
+
+ warn "min ( $unapp_amount, $owed )\n" if $DEBUG;
+ $app->amount( sprintf('%.2f', min( $unapp_amount, $owed ) ) );
+
+ $app->invnum( $self->invnum );
+
+ my $error = $app->insert(%options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error inserting ". $app->table. " record: $error";
+ }
+ die $error if $error;
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+=item generate_email OPTION => VALUE ...
+
+Options:
+
+=over 4
+
+=item from
+
+sender address, required
+
+=item tempate
+
+alternate template name, optional
+
+=item print_text
+
+text attachment arrayref, optional
+
+=item subject
+
+email subject, optional
+
+=item notice_name
+
+notice name instead of "Invoice", optional
+
+=back
+
+Returns an argument list to be passed to L<FS::Misc::send_email>.
+
+=cut
+
+use MIME::Entity;
+
+sub generate_email {
+
+ my $self = shift;
+ my %args = @_;
+
+ my $me = '[FS::cust_bill::generate_email]';
+
+ my %return = (
+ 'from' => $args{'from'},
+ 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'),
+ );
+
+ my %opt = (
+ 'unsquelch_cdr' => $conf->exists('voip-cdr_email'),
+ 'template' => $args{'template'},
+ 'notice_name' => ( $args{'notice_name'} || 'Invoice' ),
+ );
+
+ my $cust_main = $self->cust_main;
+
+ if (ref($args{'to'}) eq 'ARRAY') {
+ $return{'to'} = $args{'to'};
+ } else {
+ $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
+ $cust_main->invoicing_list
+ ];
+ }
+
+ if ( $conf->exists('invoice_html') ) {
+
+ warn "$me creating HTML/text multipart message"
+ if $DEBUG;
+
+ $return{'nobody'} = 1;
+
+ my $alternative = build MIME::Entity
+ 'Type' => 'multipart/alternative',
+ 'Encoding' => '7bit',
+ 'Disposition' => 'inline'
+ ;
+
+ my $data;
+ if ( $conf->exists('invoice_email_pdf')
+ and scalar($conf->config('invoice_email_pdf_note')) ) {
+
+ warn "$me using 'invoice_email_pdf_note' in multipart message"
+ if $DEBUG;
+ $data = [ map { $_ . "\n" }
+ $conf->config('invoice_email_pdf_note')
+ ];
+
+ } else {
+
+ warn "$me not using 'invoice_email_pdf_note' in multipart message"
+ if $DEBUG;
+ if ( ref($args{'print_text'}) eq 'ARRAY' ) {
+ $data = $args{'print_text'};
+ } else {
+ $data = [ $self->print_text(\%opt) ];
+ }
+
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/plain',
+ #'Encoding' => 'quoted-printable',
+ 'Encoding' => '7bit',
+ 'Data' => $data,
+ 'Disposition' => 'inline',
+ );
+
+ $args{'from'} =~ /\@([\w\.\-]+)/;
+ my $from = $1 || 'example.com';
+ my $content_id = join('.', rand()*(2**32), $$, time). "\@$from";
+
+ my $logo;
+ my $agentnum = $cust_main->agentnum;
+ if ( defined($args{'template'}) && length($args{'template'})
+ && $conf->exists( 'logo_'. $args{'template'}. '.png', $agentnum )
+ )
+ {
+ $logo = 'logo_'. $args{'template'}. '.png';
+ } else {
+ $logo = "logo.png";
+ }
+ my $image_data = $conf->config_binary( $logo, $agentnum);
+
+ my $image = build MIME::Entity
+ 'Type' => 'image/png',
+ 'Encoding' => 'base64',
+ 'Data' => $image_data,
+ 'Filename' => 'logo.png',
+ 'Content-ID' => "<$content_id>",
+ ;
+
+ $alternative->attach(
+ 'Type' => 'text/html',
+ 'Encoding' => 'quoted-printable',
+ 'Data' => [ '<html>',
+ ' <head>',
+ ' <title>',
+ ' '. encode_entities($return{'subject'}),
+ ' </title>',
+ ' </head>',
+ ' <body bgcolor="#e8e8e8">',
+ $self->print_html({ 'cid'=>$content_id, %opt }),
+ ' </body>',
+ '</html>',
+ ],
+ 'Disposition' => 'inline',
+ #'Filename' => 'invoice.pdf',
+ );
+
+ my @otherparts = ();
+ if ( $cust_main->email_csv_cdr ) {
+
+ push @otherparts, build MIME::Entity
+ 'Type' => 'text/csv',
+ 'Encoding' => '7bit',
+ 'Data' => [ map { "$_\n" }
+ $self->call_details('prepend_billed_number' => 1)
+ ],
+ 'Disposition' => 'attachment',
+ 'Filename' => 'usage-'. $self->invnum. '.csv',
+ ;
+
+ }
+
+ if ( $conf->exists('invoice_email_pdf') ) {
+
+ #attaching pdf too:
+ # multipart/mixed
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+ # image/png
+ # application/pdf
+
+ my $related = build MIME::Entity 'Type' => 'multipart/related',
+ 'Encoding' => '7bit';
+
+ #false laziness w/Misc::send_email
+ $related->head->replace('Content-type',
+ $related->mime_type.
+ '; boundary="'. $related->head->multipart_boundary. '"'.
+ '; type=multipart/alternative'
+ );
+
+ $related->add_part($alternative);
+
+ $related->add_part($image);
+
+ my $pdf = build MIME::Entity $self->mimebuild_pdf(\%opt);
+
+ $return{'mimeparts'} = [ $related, $pdf, @otherparts ];
+
+ } else {
+
+ #no other attachment:
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+ # image/png
+
+ $return{'content-type'} = 'multipart/related';
+ $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+ $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
+ #$return{'disposition'} = 'inline';
+
+ }
+
+ } else {
+
+ if ( $conf->exists('invoice_email_pdf') ) {
+ warn "$me creating PDF attachment"
+ if $DEBUG;
+
+ #mime parts arguments a la MIME::Entity->build().
+ $return{'mimeparts'} = [
+ { $self->mimebuild_pdf(\%opt) }
+ ];
+ }
+
+ if ( $conf->exists('invoice_email_pdf')
+ and scalar($conf->config('invoice_email_pdf_note')) ) {
+
+ warn "$me using 'invoice_email_pdf_note'"
+ if $DEBUG;
+ $return{'body'} = [ map { $_ . "\n" }
+ $conf->config('invoice_email_pdf_note')
+ ];
+
+ } else {
+
+ warn "$me not using 'invoice_email_pdf_note'"
+ if $DEBUG;
+ if ( ref($args{'print_text'}) eq 'ARRAY' ) {
+ $return{'body'} = $args{'print_text'};
+ } else {
+ $return{'body'} = [ $self->print_text(\%opt) ];
+ }
+
+ }
+
+ }
+
+ %return;
+
+}
+
+=item mimebuild_pdf
+
+Returns a list suitable for passing to MIME::Entity->build(), representing
+this invoice as PDF attachment.
+
+=cut
+
+sub mimebuild_pdf {
+ my $self = shift;
+ (
+ 'Type' => 'application/pdf',
+ 'Encoding' => 'base64',
+ 'Data' => [ $self->print_pdf(@_) ],
+ 'Disposition' => 'attachment',
+ 'Filename' => 'invoice-'. $self->invnum. '.pdf',
+ );
+}
+
+=item send HASHREF | [ TEMPLATE [ , AGENTNUM [ , INVOICE_FROM [ , AMOUNT ] ] ] ]
+
+Sends this invoice to the destinations configured for this customer: sends
+email, prints and/or faxes. See L<FS::cust_main_invoice>.
+
+Options can be passed as a hashref (recommended) or as a list of up to
+four values for templatename, agentnum, invoice_from and amount.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<agentnum>, if specified, means that this invoice will only be sent for customers
+of the specified agent or agent(s). AGENTNUM can be a scalar agentnum (for a
+single agent) or an arrayref of agentnums.
+
+I<invoice_from>, if specified, overrides the default email invoice From: address.
+
+I<amount>, if specified, only sends the invoice if the total amount owed on this
+invoice and all older invoices is greater than the specified amount.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub queueable_send {
+ my %opt = @_;
+
+ my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
+ or die "invalid invoice number: " . $opt{invnum};
+
+ my @args = ( $opt{template}, $opt{agentnum} );
+ push @args, $opt{invoice_from}
+ if exists($opt{invoice_from}) && $opt{invoice_from};
+
+ my $error = $self->send( @args );
+ die $error if $error;
+
+}
+
+sub send {
+ my $self = shift;
+
+ my( $template, $invoice_from, $notice_name );
+ my $agentnums = '';
+ my $balance_over = 0;
+
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $template = $opt->{'template'} || '';
+ if ( $agentnums = $opt->{'agentnum'} ) {
+ $agentnums = [ $agentnums ] unless ref($agentnums);
+ }
+ $invoice_from = $opt->{'invoice_from'};
+ $balance_over = $opt->{'balance_over'} if $opt->{'balance_over'};
+ $notice_name = $opt->{'notice_name'};
+ } else {
+ $template = scalar(@_) ? shift : '';
+ if ( scalar(@_) && $_[0] ) {
+ $agentnums = ref($_[0]) ? shift : [ shift ];
+ }
+ $invoice_from = shift if scalar(@_);
+ $balance_over = shift if scalar(@_) && $_[0] !~ /^\s*$/;
+ }
+
+ return 'N/A' unless ! $agentnums
+ or grep { $_ == $self->cust_main->agentnum } @$agentnums;
+
+ return ''
+ unless $self->cust_main->total_owed_date($self->_date) > $balance_over;
+
+ $invoice_from ||= $self->_agent_invoice_from || #XXX should go away
+ $conf->config('invoice_from', $self->cust_main->agentnum );
+
+ my %opt = (
+ 'template' => $template,
+ 'invoice_from' => $invoice_from,
+ 'notice_name' => ( $notice_name || 'Invoice' ),
+ );
+
+ my @invoicing_list = $self->cust_main->invoicing_list;
+
+ #$self->email_invoice(\%opt)
+ $self->email(\%opt)
+ if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
+
+ #$self->print_invoice(\%opt)
+ $self->print(\%opt)
+ if grep { $_ eq 'POST' } @invoicing_list; #postal
+
+ $self->fax_invoice(\%opt)
+ if grep { $_ eq 'FAX' } @invoicing_list; #fax
+
+ '';
+
+}
+
+=item email HASHREF | [ TEMPLATE [ , INVOICE_FROM ] ]
+
+Emails this invoice.
+
+Options can be passed as a hashref (recommended) or as a list of up to
+two values for templatename and invoice_from.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<invoice_from>, if specified, overrides the default email invoice From: address.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub queueable_email {
+ my %opt = @_;
+
+ my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
+ or die "invalid invoice number: " . $opt{invnum};
+
+ my @args = ( $opt{template} );
+ push @args, $opt{invoice_from}
+ if exists($opt{invoice_from}) && $opt{invoice_from};
+
+ my $error = $self->email( @args );
+ die $error if $error;
+
+}
+
+#sub email_invoice {
+sub email {
+ my $self = shift;
+
+ my( $template, $invoice_from, $notice_name );
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $template = $opt->{'template'} || '';
+ $invoice_from = $opt->{'invoice_from'};
+ $notice_name = $opt->{'notice_name'} || 'Invoice';
+ } else {
+ $template = scalar(@_) ? shift : '';
+ $invoice_from = shift if scalar(@_);
+ $notice_name = 'Invoice';
+ }
+
+ $invoice_from ||= $self->_agent_invoice_from || #XXX should go away
+ $conf->config('invoice_from', $self->cust_main->agentnum );
+
+ my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
+ $self->cust_main->invoicing_list;
+
+ #better to notify this person than silence
+ @invoicing_list = ($invoice_from) unless @invoicing_list;
+
+ my $subject = $self->email_subject($template);
+
+ my $error = send_email(
+ $self->generate_email(
+ 'from' => $invoice_from,
+ 'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
+ 'subject' => $subject,
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+ )
+ );
+ die "can't email invoice: $error\n" if $error;
+ #die "$error\n" if $error;
+
+}
+
+sub email_subject {
+ my $self = shift;
+
+ #my $template = scalar(@_) ? shift : '';
+ #per-template?
+
+ my $subject = $conf->config('invoice_subject', $self->cust_main->agentnum)
+ || 'Invoice';
+
+ my $cust_main = $self->cust_main;
+ my $name = $cust_main->name;
+ my $name_short = $cust_main->name_short;
+ my $invoice_number = $self->invnum;
+ my $invoice_date = $self->_date_pretty;
+
+ eval qq("$subject");
+}
+
+=item lpr_data HASHREF | [ TEMPLATE ]
+
+Returns the postscript or plaintext for this invoice as an arrayref.
+
+Options can be passed as a hashref (recommended) or as a single optional value
+for template.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub lpr_data {
+ my $self = shift;
+ my( $template, $notice_name );
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $template = $opt->{'template'} || '';
+ $notice_name = $opt->{'notice_name'} || 'Invoice';
+ } else {
+ $template = scalar(@_) ? shift : '';
+ $notice_name = 'Invoice';
+ }
+
+ my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+ );
+
+ my $method = $conf->exists('invoice_latex') ? 'print_ps' : 'print_text';
+ [ $self->$method( \%opt ) ];
+}
+
+=item print HASHREF | [ TEMPLATE ]
+
+Prints this invoice.
+
+Options can be passed as a hashref (recommended) or as a single optional
+value for template.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+#sub print_invoice {
+sub print {
+ my $self = shift;
+ my( $template, $notice_name );
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $template = $opt->{'template'} || '';
+ $notice_name = $opt->{'notice_name'} || 'Invoice';
+ } else {
+ $template = scalar(@_) ? shift : '';
+ $notice_name = 'Invoice';
+ }
+
+ my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+ );
+
+ do_print $self->lpr_data(\%opt);
+}
+
+=item fax_invoice HASHREF | [ TEMPLATE ]
+
+Faxes this invoice.
+
+Options can be passed as a hashref (recommended) or as a single optional
+value for template.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub fax_invoice {
+ my $self = shift;
+ my( $template, $notice_name );
+ if ( ref($_[0]) ) {
+ my $opt = shift;
+ $template = $opt->{'template'} || '';
+ $notice_name = $opt->{'notice_name'} || 'Invoice';
+ } else {
+ $template = scalar(@_) ? shift : '';
+ $notice_name = 'Invoice';
+ }
+
+ die 'FAX invoice destination not (yet?) supported with plain text invoices.'
+ unless $conf->exists('invoice_latex');
+
+ my $dialstring = $self->cust_main->getfield('fax');
+ #Check $dialstring?
+
+ my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+ );
+
+ my $error = send_fax( 'docdata' => $self->lpr_data(\%opt),
+ 'dialstring' => $dialstring,
+ );
+ die $error if $error;
+
+}
+
+=item ftp_invoice [ TEMPLATENAME ]
+
+Sends this invoice data via FTP.
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub ftp_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->send_csv(
+ 'protocol' => 'ftp',
+ 'server' => $conf->config('cust_bill-ftpserver'),
+ 'username' => $conf->config('cust_bill-ftpusername'),
+ 'password' => $conf->config('cust_bill-ftppassword'),
+ 'dir' => $conf->config('cust_bill-ftpdir'),
+ 'format' => $conf->config('cust_bill-ftpformat'),
+ );
+}
+
+=item spool_invoice [ TEMPLATENAME ]
+
+Spools this invoice data (see L<FS::spool_csv>)
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub spool_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->spool_csv(
+ 'format' => $conf->config('cust_bill-spoolformat'),
+ 'agent_spools' => $conf->exists('cust_bill-spoolagent'),
+ );
+}
+
+=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
+
+Like B<send>, but only sends the invoice if it is the newest open invoice for
+this customer.
+
+=cut
+
+sub send_if_newest {
+ my $self = shift;
+
+ return ''
+ if scalar(
+ grep { $_->owed > 0 }
+ qsearch('cust_bill', {
+ 'custnum' => $self->custnum,
+ #'_date' => { op=>'>', value=>$self->_date },
+ 'invnum' => { op=>'>', value=>$self->invnum },
+ } )
+ );
+
+ $self->send(@_);
+}
+
+=item send_csv OPTION => VALUE, ...
+
+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.
+
+See L</print_csv> for a description of the output format.
+
+=cut
+
+sub send_csv {
+ my($self, %opt) = @_;
+
+ #create file(s)
+
+ my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
+ mkdir $spooldir, 0700 unless -d $spooldir;
+
+ my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
+ my $file = "$spooldir/$tracctnum.csv";
+
+ my ( $header, $detail ) = $self->print_csv(%opt, 'tracctnum' => $tracctnum );
+
+ open(CSV, ">$file") or die "can't open $file: $!";
+ print CSV $header;
+
+ print CSV $detail;
+
+ close CSV;
+
+ 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 spool_csv
+
+Spools CSV invoice data.
+
+Options are:
+
+=over 4
+
+=item format - 'default' or 'billco'
+
+=item dest - if set (to POST, EMAIL or FAX), only sends spools invoices if the customer has the corresponding invoice destinations set (see L<FS::cust_main_invoice>).
+
+=item agent_spools - if set to a true value, will spool to per-agent files rather than a single global file
+
+=item balanceover - if set, only spools the invoice if the total amount owed on this invoice and all older invoices is greater than the specified amount.
+
+=back
+
+=cut
+
+sub spool_csv {
+ my($self, %opt) = @_;
+
+ my $cust_main = $self->cust_main;
+
+ if ( $opt{'dest'} ) {
+ my %invoicing_list = map { /^(POST|FAX)$/ or 'EMAIL' =~ /^(.*)$/; $1 => 1 }
+ $cust_main->invoicing_list;
+ return 'N/A' unless $invoicing_list{$opt{'dest'}}
+ || ! keys %invoicing_list;
+ }
+
+ if ( $opt{'balanceover'} ) {
+ return 'N/A'
+ if $cust_main->total_owed_date($self->_date) < $opt{'balanceover'};
+ }
+
+ my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
+ mkdir $spooldir, 0700 unless -d $spooldir;
+
+ my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
+
+ my $file =
+ "$spooldir/".
+ ( $opt{'agent_spools'} ? 'agentnum'.$cust_main->agentnum : 'spool' ).
+ ( lc($opt{'format'}) eq 'billco' ? '-header' : '' ) .
+ '.csv';
+
+ my ( $header, $detail ) = $self->print_csv(%opt, 'tracctnum' => $tracctnum );
+
+ open(CSV, ">>$file") or die "can't open $file: $!";
+ flock(CSV, LOCK_EX);
+ seek(CSV, 0, 2);
+
+ print CSV $header;
+
+ if ( lc($opt{'format'}) eq 'billco' ) {
+
+ flock(CSV, LOCK_UN);
+ close CSV;
+
+ $file =
+ "$spooldir/".
+ ( $opt{'agent_spools'} ? 'agentnum'.$cust_main->agentnum : 'spool' ).
+ '-detail.csv';
+
+ open(CSV,">>$file") or die "can't open $file: $!";
+ flock(CSV, LOCK_EX);
+ seek(CSV, 0, 2);
+ }
+
+ print CSV $detail;
+
+ flock(CSV, LOCK_UN);
+ close CSV;
+
+ return '';
+
+}
+
+=item print_csv OPTION => VALUE, ...
+
+Returns CSV data for this invoice.
+
+Options are:
+
+format - 'default' or 'billco'
+
+Returns a list consisting of two scalars. The first is a single line of CSV
+header information for this invoice. The second is one or more lines of CSV
+detail information for this invoice.
+
+If I<format> is not specified or "default", the fields of the CSV file are 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>
+
+B<record_type> is C<cust_bill> for the initial header line only. The
+last five fields (B<pkg> through B<edate>) are irrelevant, and all other
+fields are filled in.
+
+B<record_type> is C<cust_bill_pkg> for detail lines. 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
+
+If I<format> is "billco", the fields of the header CSV file are as follows:
+
+ +-------------------------------------------------------------------+
+ | FORMAT HEADER FILE |
+ |-------------------------------------------------------------------|
+ | Field | Description | Name | Type | Width |
+ | 1 | N/A-Leave Empty | RC | CHAR | 2 |
+ | 2 | N/A-Leave Empty | CUSTID | CHAR | 15 |
+ | 3 | Transaction Account No | TRACCTNUM | CHAR | 15 |
+ | 4 | Transaction Invoice No | TRINVOICE | CHAR | 15 |
+ | 5 | Transaction Zip Code | TRZIP | CHAR | 5 |
+ | 6 | Transaction Company Bill To | TRCOMPANY | CHAR | 30 |
+ | 7 | Transaction Contact Bill To | TRNAME | CHAR | 30 |
+ | 8 | Additional Address Unit Info | TRADDR1 | CHAR | 30 |
+ | 9 | Bill To Street Address | TRADDR2 | CHAR | 30 |
+ | 10 | Ancillary Billing Information | TRADDR3 | CHAR | 30 |
+ | 11 | Transaction City Bill To | TRCITY | CHAR | 20 |
+ | 12 | Transaction State Bill To | TRSTATE | CHAR | 2 |
+ | 13 | Bill Cycle Close Date | CLOSEDATE | CHAR | 10 |
+ | 14 | Bill Due Date | DUEDATE | CHAR | 10 |
+ | 15 | Previous Balance | BALFWD | NUM* | 9 |
+ | 16 | Pmt/CR Applied | CREDAPPLY | NUM* | 9 |
+ | 17 | Total Current Charges | CURRENTCHG | NUM* | 9 |
+ | 18 | Total Amt Due | TOTALDUE | NUM* | 9 |
+ | 19 | Total Amt Due | AMTDUE | NUM* | 9 |
+ | 20 | 30 Day Aging | AMT30 | NUM* | 9 |
+ | 21 | 60 Day Aging | AMT60 | NUM* | 9 |
+ | 22 | 90 Day Aging | AMT90 | NUM* | 9 |
+ | 23 | Y/N | AGESWITCH | CHAR | 1 |
+ | 24 | Remittance automation | SCANLINE | CHAR | 100 |
+ | 25 | Total Taxes & Fees | TAXTOT | NUM* | 9 |
+ | 26 | Customer Reference Number | CUSTREF | CHAR | 15 |
+ | 27 | Federal Tax*** | FEDTAX | NUM* | 9 |
+ | 28 | State Tax*** | STATETAX | NUM* | 9 |
+ | 29 | Other Taxes & Fees*** | OTHERTAX | NUM* | 9 |
+ +-------+-------------------------------+------------+------+-------+
+
+If I<format> is "billco", the fields of the detail CSV file are as follows:
+
+ FORMAT FOR DETAIL FILE
+ | | | |
+ Field | Description | Name | Type | Width
+ 1 | N/A-Leave Empty | RC | CHAR | 2
+ 2 | N/A-Leave Empty | CUSTID | CHAR | 15
+ 3 | Account Number | TRACCTNUM | CHAR | 15
+ 4 | Invoice Number | TRINVOICE | CHAR | 15
+ 5 | Line Sequence (sort order) | LINESEQ | NUM | 6
+ 6 | Transaction Detail | DETAILS | CHAR | 100
+ 7 | Amount | AMT | NUM* | 9
+ 8 | Line Format Control** | LNCTRL | CHAR | 2
+ 9 | Grouping Code | GROUP | CHAR | 2
+ 10 | User Defined | ACCT CODE | CHAR | 15
+
+=cut
+
+sub print_csv {
+ my($self, %opt) = @_;
+
+ eval "use Text::CSV_XS";
+ die $@ if $@;
+
+ my $cust_main = $self->cust_main;
+
+ my $csv = Text::CSV_XS->new({'always_quote'=>1});
+
+ if ( lc($opt{'format'}) eq 'billco' ) {
+
+ my $taxtotal = 0;
+ $taxtotal += $_->{'amount'} foreach $self->_items_tax;
+
+ my $duedate = $self->due_date2str('%m/%d/%Y'); #date_format?
+
+ my( $previous_balance, @unused ) = $self->previous; #previous balance
+
+ my $pmt_cr_applied = 0;
+ $pmt_cr_applied += $_->{'amount'}
+ foreach ( $self->_items_payments, $self->_items_credits ) ;
+
+ my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
+
+ $csv->combine(
+ '', # 1 | N/A-Leave Empty CHAR 2
+ '', # 2 | N/A-Leave Empty CHAR 15
+ $opt{'tracctnum'}, # 3 | Transaction Account No CHAR 15
+ $self->invnum, # 4 | Transaction Invoice No CHAR 15
+ $cust_main->zip, # 5 | Transaction Zip Code CHAR 5
+ $cust_main->company, # 6 | Transaction Company Bill To CHAR 30
+ #$cust_main->payname, # 7 | Transaction Contact Bill To CHAR 30
+ $cust_main->contact, # 7 | Transaction Contact Bill To CHAR 30
+ $cust_main->address2, # 8 | Additional Address Unit Info CHAR 30
+ $cust_main->address1, # 9 | Bill To Street Address CHAR 30
+ '', # 10 | Ancillary Billing Information CHAR 30
+ $cust_main->city, # 11 | Transaction City Bill To CHAR 20
+ $cust_main->state, # 12 | Transaction State Bill To CHAR 2
+
+ # XXX ?
+ time2str("%m/%d/%Y", $self->_date), # 13 | Bill Cycle Close Date CHAR 10
+
+ # XXX ?
+ $duedate, # 14 | Bill Due Date CHAR 10
+
+ $previous_balance, # 15 | Previous Balance NUM* 9
+ $pmt_cr_applied, # 16 | Pmt/CR Applied NUM* 9
+ sprintf("%.2f", $self->charged), # 17 | Total Current Charges NUM* 9
+ $totaldue, # 18 | Total Amt Due NUM* 9
+ $totaldue, # 19 | Total Amt Due NUM* 9
+ '', # 20 | 30 Day Aging NUM* 9
+ '', # 21 | 60 Day Aging NUM* 9
+ '', # 22 | 90 Day Aging NUM* 9
+ 'N', # 23 | Y/N CHAR 1
+ '', # 24 | Remittance automation CHAR 100
+ $taxtotal, # 25 | Total Taxes & Fees NUM* 9
+ $self->custnum, # 26 | Customer Reference Number CHAR 15
+ '0', # 27 | Federal Tax*** NUM* 9
+ sprintf("%.2f", $taxtotal), # 28 | State Tax*** NUM* 9
+ '0', # 29 | Other Taxes & Fees*** NUM* 9
+ );
+
+ } else {
+
+ $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";
+ }
+
+ my $header = $csv->string. "\n";
+
+ my $detail = '';
+ if ( lc($opt{'format'}) eq 'billco' ) {
+
+ my $lineseq = 0;
+ foreach my $item ( $self->_items_pkg ) {
+
+ $csv->combine(
+ '', # 1 | N/A-Leave Empty CHAR 2
+ '', # 2 | N/A-Leave Empty CHAR 15
+ $opt{'tracctnum'}, # 3 | Account Number CHAR 15
+ $self->invnum, # 4 | Invoice Number CHAR 15
+ $lineseq++, # 5 | Line Sequence (sort order) NUM 6
+ $item->{'description'}, # 6 | Transaction Detail CHAR 100
+ $item->{'amount'}, # 7 | Amount NUM* 9
+ '', # 8 | Line Format Control** CHAR 2
+ '', # 9 | Grouping Code CHAR 2
+ '', # 10 | User Defined CHAR 15
+ );
+
+ $detail .= $csv->string. "\n";
+
+ }
+
+ } else {
+
+ 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->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 )
+ : '' ),
+ ( $cust_bill_pkg->sdate
+ ? time2str("%x", $cust_bill_pkg->sdate)
+ : '' ),
+ ($cust_bill_pkg->edate
+ ?time2str("%x", $cust_bill_pkg->edate)
+ : '' ),
+ );
+
+ } else { #pkgnum tax
+ next unless $cust_bill_pkg->setup != 0;
+ $pkg = $cust_bill_pkg->desc;
+ $setup = sprintf('%10.2f', $cust_bill_pkg->setup );
+ ( $sdate, $edate ) = ( '', '' );
+ }
+
+ $csv->combine(
+ 'cust_bill_pkg',
+ $self->invnum,
+ ( map { '' } (1..11) ),
+ ($pkg, $setup, $recur, $sdate, $edate)
+ ) or die "can't create csv";
+
+ $detail .= $csv->string. "\n";
+
+ }
+
+ }
+
+ ( $header, $detail );
+
+}
+
+=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 { $_->part_pkg->pkg }
+ grep { $_->pkgnum } $self->cust_bill_pkg
+ );
+ $description = eval qq("$dtempl");
+ }
+
+ $cust_main->realtime_bop($method, $amount,
+ 'description' => $description,
+ 'invnum' => $self->invnum,
+#this didn't do what we want, it just calls apply_payments_and_credits
+# 'apply' => 1,
+ 'apply_to_invoice' => 1,
+ #what we want:
+ #this changes application behavior: auto payments
+ #triggered against a specific invoice are now applied
+ #to that invoice instead of oldest open.
+ #seem okay to me...
+ );
+
+}
+
+=item batch_card OPTION => VALUE...
+
+Adds a payment for this invoice to the pending credit card batch (see
+L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value,
+runs the payment using a realtime gateway.
+
+=cut
+
+sub batch_card {
+ my ($self, %options) = @_;
+ my $cust_main = $self->cust_main;
+
+ $options{invnum} = $self->invnum;
+
+ $cust_main->batch_card(%options);
+}
+
+sub _agent_template {
+ my $self = shift;
+ $self->cust_main->agent_template;
+}
+
+sub _agent_invoice_from {
+ my $self = shift;
+ $self->cust_main->agent_invoice_from;
+}
+
+=item print_text HASHREF | [ TIME [ , TEMPLATE [ , OPTION => VALUE ... ] ] ]
+
+Returns an text invoice, as a list of lines.
+
+Options can be passed as a hashref (recommended) or as a list of time, template
+and then any key/value pairs for any other options.
+
+I<time>, if specified, is 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.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub print_text {
+ my $self = shift;
+ my( $today, $template, %opt );
+ if ( ref($_[0]) ) {
+ %opt = %{ shift() };
+ $today = delete($opt{'time'}) || '';
+ $template = delete($opt{template}) || '';
+ } else {
+ ( $today, $template, %opt ) = @_;
+ }
+
+ my %params = ( 'format' => 'template' );
+ $params{'time'} = $today if $today;
+ $params{'template'} = $template if $template;
+ $params{$_} = $opt{$_}
+ foreach grep $opt{$_}, qw( unsquealch_cdr notice_name );
+
+ $self->print_generic( %params );
+}
+
+=item print_latex HASHREF | [ TIME [ , TEMPLATE [ , OPTION => VALUE ... ] ] ]
+
+Internal method - returns a filename of a filled-in LaTeX template for this
+invoice (Note: add ".tex" to get the actual filename), and a filename of
+an associated logo (with the .eps extension included).
+
+See print_ps and print_pdf for methods that return PostScript and PDF output.
+
+Options can be passed as a hashref (recommended) or as a list of time, template
+and then any key/value pairs for any other options.
+
+I<time>, if specified, is 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.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub print_latex {
+ my $self = shift;
+ my( $today, $template, %opt );
+ if ( ref($_[0]) ) {
+ %opt = %{ shift() };
+ $today = delete($opt{'time'}) || '';
+ $template = delete($opt{template}) || '';
+ } else {
+ ( $today, $template, %opt ) = @_;
+ }
+
+ my %params = ( 'format' => 'latex' );
+ $params{'time'} = $today if $today;
+ $params{'template'} = $template if $template;
+ $params{$_} = $opt{$_}
+ foreach grep $opt{$_}, qw( unsquealch_cdr notice_name );
+
+ $template ||= $self->_agent_template;
+
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $lh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.eps',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ my $agentnum = $self->cust_main->agentnum;
+
+ if ( $template && $conf->exists("logo_${template}.eps", $agentnum) ) {
+ print $lh $conf->config_binary("logo_${template}.eps", $agentnum)
+ or die "can't write temp file: $!\n";
+ } else {
+ print $lh $conf->config_binary('logo.eps', $agentnum)
+ or die "can't write temp file: $!\n";
+ }
+ close $lh;
+ $params{'logo_file'} = $lh->filename;
+
+ my @filled_in = $self->print_generic( %params );
+
+ 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('', @filled_in );
+ close $fh;
+
+ $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
+ return ($1, $params{'logo_file'});
+
+}
+
+=item print_generic OPTION => VALUE ...
+
+Internal method - returns a filled-in template for this invoice as a scalar.
+
+See print_ps and print_pdf for methods that return PostScript and PDF output.
+
+Non optional options include
+ format - latex, html, template
+
+Optional options include
+
+template - a value used as a suffix for a configuration template
+
+time - a 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.
+
+cid -
+
+unsquelch_cdr - overrides any per customer cdr squelching when true
+
+notice_name - overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+#what's with all the sprintf('%10.2f')'s in here? will it cause any
+# (alignment in text invoice?) problems to change them all to '%.2f' ?
+# yes: fixed width (dot matrix) text printing will be borked
+sub print_generic {
+
+ my( $self, %params ) = @_;
+ my $today = $params{today} ? $params{today} : time;
+ warn "$me print_generic called on $self with suffix $params{template}\n"
+ if $DEBUG;
+
+ my $format = $params{format};
+ die "Unknown format: $format"
+ unless $format =~ /^(latex|html|template)$/;
+
+ my $cust_main = $self->cust_main;
+ $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
+ unless $cust_main->payname
+ && $cust_main->payby !~ /^(CARD|DCRD|CHEK|DCHK)$/;
+
+ my %delimiters = ( 'latex' => [ '[@--', '--@]' ],
+ 'html' => [ '<%=', '%>' ],
+ 'template' => [ '{', '}' ],
+ );
+
+ #create the template
+ my $template = $params{template} ? $params{template} : $self->_agent_template;
+ my $templatefile = "invoice_$format";
+ $templatefile .= "_$template"
+ if length($template);
+ my @invoice_template = map "$_\n", $conf->config($templatefile)
+ or die "cannot load config data $templatefile";
+
+ my $old_latex = '';
+ if ( $format eq 'latex' && grep { /^%%Detail/ } @invoice_template ) {
+ #change this to a die when the old code is removed
+ warn "old-style invoice template $templatefile; ".
+ "patch with conf/invoice_latex.diff or use new conf/invoice_latex*\n";
+ $old_latex = 'true';
+ @invoice_template = _translate_old_latex_format(@invoice_template);
+ }
+
+ my $text_template = new Text::Template(
+ TYPE => 'ARRAY',
+ SOURCE => \@invoice_template,
+ DELIMITERS => $delimiters{$format},
+ );
+
+ $text_template->compile()
+ or die "Can't compile $templatefile: $Text::Template::ERROR\n";
+
+
+ # additional substitution could possibly cause breakage in existing templates
+ my %convert_maps = (
+ 'latex' => {
+ 'notes' => sub { map "$_", @_ },
+ 'footer' => sub { map "$_", @_ },
+ 'smallfooter' => sub { map "$_", @_ },
+ 'returnaddress' => sub { map "$_", @_ },
+ 'coupon' => sub { map "$_", @_ },
+ 'summary' => sub { map "$_", @_ },
+ },
+ 'html' => {
+ 'notes' =>
+ sub {
+ map {
+ s/%%(.*)$/<!-- $1 -->/g;
+ s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/g;
+ s/\\begin\{enumerate\}/<ol>/g;
+ s/\\item / <li>/g;
+ s/\\end\{enumerate\}/<\/ol>/g;
+ s/\\textbf\{(.*)\}/<b>$1<\/b>/g;
+ s/\\\\\*/<br>/g;
+ s/\\dollar ?/\$/g;
+ s/\\#/#/g;
+ s/~/&nbsp;/g;
+ $_;
+ } @_
+ },
+ 'footer' =>
+ sub { map { s/~/&nbsp;/g; s/\\\\\*?\s*$/<BR>/; $_; } @_ },
+ 'smallfooter' =>
+ sub { map { s/~/&nbsp;/g; s/\\\\\*?\s*$/<BR>/; $_; } @_ },
+ 'returnaddress' =>
+ sub {
+ map {
+ s/~/&nbsp;/g;
+ s/\\\\\*?\s*$/<BR>/;
+ s/\\hyphenation\{[\w\s\-]+}//;
+ s/\\([&])/$1/g;
+ $_;
+ } @_
+ },
+ 'coupon' => sub { "" },
+ 'summary' => sub { "" },
+ },
+ 'template' => {
+ 'notes' =>
+ sub {
+ map {
+ s/%%.*$//g;
+ s/\\section\*\{\\textsc\{(.*)\}\}/\U$1/g;
+ s/\\begin\{enumerate\}//g;
+ s/\\item / * /g;
+ s/\\end\{enumerate\}//g;
+ s/\\textbf\{(.*)\}/$1/g;
+ s/\\\\\*/ /;
+ s/\\dollar ?/\$/g;
+ $_;
+ } @_
+ },
+ 'footer' =>
+ sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ },
+ 'smallfooter' =>
+ sub { map { s/~/ /g; s/\\\\\*?\s*$/\n/; $_; } @_ },
+ 'returnaddress' =>
+ sub {
+ map {
+ s/~/ /g;
+ s/\\\\\*?\s*$/\n/; # dubious
+ s/\\hyphenation\{[\w\s\-]+}//;
+ $_;
+ } @_
+ },
+ 'coupon' => sub { "" },
+ 'summary' => sub { "" },
+ },
+ );
+
+
+ # hashes for differing output formats
+ my %nbsps = ( 'latex' => '~',
+ 'html' => '', # '&nbps;' would be nice
+ 'template' => '', # not used
+ );
+ my $nbsp = $nbsps{$format};
+
+ my %escape_functions = ( 'latex' => \&_latex_escape,
+ 'html' => \&encode_entities,
+ 'template' => sub { shift },
+ );
+ my $escape_function = $escape_functions{$format};
+
+ my %date_formats = ( 'latex' => '%b %o, %Y',
+ 'html' => '%b&nbsp;%o,&nbsp;%Y',
+ 'template' => '%s',
+ );
+ my $date_format = $date_formats{$format};
+
+ my %embolden_functions = ( 'latex' => sub { return '\textbf{'. shift(). '}'
+ },
+ 'html' => sub { return '<b>'. shift(). '</b>'
+ },
+ 'template' => sub { shift },
+ );
+ my $embolden_function = $embolden_functions{$format};
+
+
+ # generate template variables
+ my $returnaddress;
+ if (
+ defined( $conf->config_orbase( "invoice_${format}returnaddress",
+ $template
+ )
+ )
+ && length( $conf->config_orbase( "invoice_${format}returnaddress",
+ $template
+ )
+ )
+ ) {
+
+ $returnaddress = join("\n",
+ $conf->config_orbase("invoice_${format}returnaddress", $template)
+ );
+
+ } elsif ( grep /\S/,
+ $conf->config_orbase('invoice_latexreturnaddress', $template) ) {
+
+ my $convert_map = $convert_maps{$format}{'returnaddress'};
+ $returnaddress =
+ join( "\n",
+ &$convert_map( $conf->config_orbase( "invoice_latexreturnaddress",
+ $template
+ )
+ )
+ );
+ } elsif ( grep /\S/, $conf->config('company_address', $self->cust_main->agentnum) ) {
+
+ my $convert_map = $convert_maps{$format}{'returnaddress'};
+ $returnaddress = join( "\n", &$convert_map(
+ map { s/( {2,})/'~' x length($1)/eg;
+ s/$/\\\\\*/;
+ $_
+ }
+ ( $conf->config('company_name', $self->cust_main->agentnum),
+ $conf->config('company_address', $self->cust_main->agentnum),
+ )
+ )
+ );
+
+ } else {
+
+ my $warning = "Couldn't find a return address; ".
+ "do you need to set the company_address configuration value?";
+ warn "$warning\n";
+ $returnaddress = $nbsp;
+ #$returnaddress = $warning;
+
+ }
+
+ my %invoice_data = (
+
+ #invoice from info
+ 'company_name' => scalar( $conf->config('company_name', $self->cust_main->agentnum) ),
+ 'company_address' => join("\n", $conf->config('company_address', $self->cust_main->agentnum) ). "\n",
+ 'returnaddress' => $returnaddress,
+ 'agent' => &$escape_function($cust_main->agent->agent),
+
+ #invoice info
+ 'invnum' => $self->invnum,
+ 'date' => time2str($date_format, $self->_date),
+ 'today' => time2str('%b %o, %Y', $today),
+ 'terms' => $self->terms,
+ 'template' => $template, #params{'template'},
+ 'notice_name' => ($params{'notice_name'} || 'Invoice'),#escape_function?
+ 'current_charges' => sprintf("%.2f", $self->charged),
+ 'duedate' => $self->due_date2str($rdate_format), #date_format?
+
+ #customer info
+ 'custnum' => $cust_main->display_custnum,
+ 'agent_custid' => &$escape_function($cust_main->agent_custid),
+ ( map { $_ => &$escape_function($cust_main->$_()) } qw(
+ payname company address1 address2 city state zip fax
+ )),
+
+ #global config
+ 'ship_enable' => $conf->exists('invoice-ship_address'),
+ 'unitprices' => $conf->exists('invoice-unitprice'),
+ 'smallernotes' => $conf->exists('invoice-smallernotes'),
+ 'smallerfooter' => $conf->exists('invoice-smallerfooter'),
+ 'balance_due_below_line' => $conf->exists('balance_due_below_line'),
+
+ # better hang on to conf_dir for a while (for old templates)
+ 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
+
+ #these are only used when doing paged plaintext
+ 'page' => 1,
+ 'total_pages' => 1,
+
+ );
+
+ $invoice_data{finance_section} = '';
+ if ( $conf->config('finance_pkgclass') ) {
+ my $pkg_class =
+ qsearchs('pkg_class', { classnum => $conf->config('finance_pkgclass') });
+ $invoice_data{finance_section} = $pkg_class->categoryname;
+ }
+ $invoice_data{finance_amount} = '0.00';
+
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ my $prefix = $cust_main->has_ship_address ? 'ship_' : '';
+ foreach ( qw( contact company address1 address2 city state zip country fax) ){
+ my $method = $prefix.$_;
+ $invoice_data{"ship_$_"} = _latex_escape($cust_main->$method);
+ }
+ $invoice_data{'ship_country'} = ''
+ if ( $invoice_data{'ship_country'} eq $countrydefault );
+
+ $invoice_data{'cid'} = $params{'cid'}
+ if $params{'cid'};
+
+ if ( $cust_main->country eq $countrydefault ) {
+ $invoice_data{'country'} = '';
+ } else {
+ $invoice_data{'country'} = &$escape_function(code2country($cust_main->country));
+ }
+
+ my @address = ();
+ $invoice_data{'address'} = \@address;
+ push @address,
+ $cust_main->payname.
+ ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
+ ? " (P.O. #". $cust_main->payinfo. ")"
+ : ''
+ )
+ ;
+ push @address, $cust_main->company
+ if $cust_main->company;
+ push @address, $cust_main->address1;
+ push @address, $cust_main->address2
+ if $cust_main->address2;
+ push @address,
+ $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
+ push @address, $invoice_data{'country'}
+ if $invoice_data{'country'};
+ push @address, ''
+ while (scalar(@address) < 5);
+
+ $invoice_data{'logo_file'} = $params{'logo_file'}
+ if $params{'logo_file'};
+
+ 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;
+ $invoice_data{'true_previous_balance'} = sprintf("%.2f", ($self->previous_balance || 0) );
+ $invoice_data{'balance_adjustments'} = sprintf("%.2f", ($self->previous_balance || 0) - ($self->billing_balance || 0) );
+ $invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
+ $invoice_data{'balance'} = sprintf("%.2f", $balance_due);
+
+ my $agentnum = $self->cust_main->agentnum;
+
+ my $summarypage = '';
+ if ( $conf->exists('invoice_usesummary', $agentnum) ) {
+ $summarypage = 1;
+ }
+ $invoice_data{'summarypage'} = $summarypage;
+
+ #do variable substitution in notes, footer, smallfooter
+ foreach my $include (qw( notes footer smallfooter coupon )) {
+
+ my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
+ my @inc_src;
+
+ if ( $conf->exists($inc_file, $agentnum)
+ && length( $conf->config($inc_file, $agentnum) ) ) {
+
+ @inc_src = $conf->config($inc_file, $agentnum);
+
+ } else {
+
+ $inc_file = $conf->key_orbase("invoice_latex$include", $template);
+
+ my $convert_map = $convert_maps{$format}{$include};
+
+ @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
+ s/--\@\]/$delimiters{$format}[1]/g;
+ $_;
+ }
+ &$convert_map( $conf->config($inc_file, $agentnum) );
+
+ }
+
+ my $inc_tt = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @inc_src ],
+ DELIMITERS => $delimiters{$format},
+ ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
+
+ unless ( $inc_tt->compile() ) {
+ my $error = "Can't compile $inc_file template: $Text::Template::ERROR\n";
+ warn $error. "Template:\n". join('', map "$_\n", @inc_src);
+ die $error;
+ }
+
+ $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
+
+ $invoice_data{$include} =~ s/\n+$//
+ if ($format eq 'latex');
+ }
+
+ $invoice_data{'po_line'} =
+ ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
+ ? &$escape_function("Purchase Order #". $cust_main->payinfo)
+ : $nbsp;
+
+ my %money_chars = ( 'latex' => '',
+ 'html' => $conf->config('money_char') || '$',
+ 'template' => '',
+ );
+ my $money_char = $money_chars{$format};
+
+ my %other_money_chars = ( 'latex' => '\dollar ',#XXX should be a config too
+ 'html' => $conf->config('money_char') || '$',
+ 'template' => '',
+ );
+ my $other_money_char = $other_money_chars{$format};
+ $invoice_data{'dollar'} = $other_money_char;
+
+ my @detail_items = ();
+ my @total_items = ();
+ my @buf = ();
+ my @sections = ();
+
+ $invoice_data{'detail_items'} = \@detail_items;
+ $invoice_data{'total_items'} = \@total_items;
+ $invoice_data{'buf'} = \@buf;
+ $invoice_data{'sections'} = \@sections;
+
+ my $previous_section = { 'description' => 'Previous Charges',
+ 'subtotal' => $other_money_char.
+ sprintf('%.2f', $pr_total),
+ 'summarized' => $summarypage ? 'Y' : '',
+ };
+ $previous_section->{posttotal} = '0 / 30 / 60/ 90 days overdue '.
+ join(' / ', map { $cust_main->balance_date_range(@$_) }
+ $self->_prior_month30s
+ )
+ if $conf->exists('invoice_include_aging');
+
+ my $taxtotal = 0;
+ my $tax_section = { 'description' => 'Taxes, Surcharges, and Fees',
+ 'subtotal' => $taxtotal, # adjusted below
+ 'summarized' => $summarypage ? 'Y' : '',
+ };
+ my $tax_weight = _pkg_category($tax_section->{description})
+ ? _pkg_category($tax_section->{description})->weight
+ : 0;
+ $tax_section->{'summarized'} = $summarypage && !$tax_weight ? 'Y' : '';
+ $tax_section->{'sort_weight'} = $tax_weight;
+
+
+ my $adjusttotal = 0;
+ my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments',
+ 'subtotal' => 0, # adjusted below
+ 'summarized' => $summarypage ? 'Y' : '',
+ };
+ my $adjust_weight = _pkg_category($adjust_section->{description})
+ ? _pkg_category($adjust_section->{description})->weight
+ : 0;
+ $adjust_section->{'summarized'} = $summarypage && !$adjust_weight ? 'Y' : '';
+ $adjust_section->{'sort_weight'} = $adjust_weight;
+
+ my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
+ my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
+ $invoice_data{'multisection'} = $multisection;
+ my $late_sections = [];
+ my $extra_sections = [];
+ my $extra_lines = ();
+ if ( $multisection ) {
+ ($extra_sections, $extra_lines) =
+ $self->_items_extra_usage_sections($escape_function, $format)
+ if $conf->exists('usage_class_as_a_section', $cust_main->agentnum);
+
+ push @$extra_sections, $adjust_section if $adjust_section->{sort_weight};
+
+ push @detail_items, @$extra_lines if $extra_lines;
+ push @sections,
+ $self->_items_sections( $late_sections, # this could stand a refactor
+ $summarypage,
+ $escape_function,
+ $extra_sections,
+ $format, #bah
+ );
+ if ($conf->exists('svc_phone_sections')) {
+ my ($phone_sections, $phone_lines) =
+ $self->_items_svc_phone_sections($escape_function, $format);
+ push @{$late_sections}, @$phone_sections;
+ push @detail_items, @$phone_lines;
+ }
+ }else{
+ push @sections, { 'description' => '', 'subtotal' => '' };
+ }
+
+ unless ( $conf->exists('disable_previous_balance')
+ || $conf->exists('previous_balance-summary_only')
+ )
+ {
+
+ foreach my $line_item ( $self->_items_previous ) {
+
+ my $detail = {
+ ext_description => [],
+ };
+ $detail->{'ref'} = $line_item->{'pkgnum'};
+ $detail->{'quantity'} = 1;
+ $detail->{'section'} = $previous_section;
+ $detail->{'description'} = &$escape_function($line_item->{'description'});
+ if ( exists $line_item->{'ext_description'} ) {
+ @{$detail->{'ext_description'}} = map {
+ &$escape_function($_);
+ } @{$line_item->{'ext_description'}};
+ }
+ $detail->{'amount'} = ( $old_latex ? '' : $money_char).
+ $line_item->{'amount'};
+ $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
+
+ push @detail_items, $detail;
+ push @buf, [ $detail->{'description'},
+ $money_char. sprintf("%10.2f", $line_item->{'amount'}),
+ ];
+ }
+
+ }
+
+ if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {
+ push @buf, ['','-----------'];
+ push @buf, [ 'Total Previous Balance',
+ $money_char. sprintf("%10.2f", $pr_total) ];
+ push @buf, ['',''];
+ }
+
+ foreach my $section (@sections, @$late_sections) {
+
+ $invoice_data{finance_amount} = sprintf('%.2f', $section->{'subtotal'} )
+ if ( $invoice_data{finance_section} &&
+ $section->{'description'} eq $invoice_data{finance_section} );
+
+ $section->{'subtotal'} = $other_money_char.
+ sprintf('%.2f', $section->{'subtotal'})
+ if $multisection;
+
+ # begin some normalization
+ $section->{'amount'} = $section->{'subtotal'}
+ if $multisection;
+
+
+ if ( $section->{'description'} ) {
+ push @buf, ( [ &$escape_function($section->{'description'}), '' ],
+ [ '', '' ],
+ );
+ }
+
+ my $multilocation = scalar($cust_main->cust_location); #too expensive?
+ my %options = ();
+ $options{'section'} = $section if $multisection;
+ $options{'format'} = $format;
+ $options{'escape_function'} = $escape_function;
+ $options{'format_function'} = sub { () } unless $unsquelched;
+ $options{'unsquelched'} = $unsquelched;
+ $options{'summary_page'} = $summarypage;
+ $options{'skip_usage'} =
+ scalar(@$extra_sections) && !grep{$section == $_} @$extra_sections;
+ $options{'multilocation'} = $multilocation;
+
+ foreach my $line_item ( $self->_items_pkg(%options) ) {
+ my $detail = {
+ ext_description => [],
+ };
+ $detail->{'ref'} = $line_item->{'pkgnum'};
+ $detail->{'quantity'} = $line_item->{'quantity'};
+ $detail->{'section'} = $section;
+ $detail->{'description'} = &$escape_function($line_item->{'description'});
+ if ( exists $line_item->{'ext_description'} ) {
+ @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
+ }
+ $detail->{'amount'} = ( $old_latex ? '' : $money_char ).
+ $line_item->{'amount'};
+ $detail->{'unit_amount'} = ( $old_latex ? '' : $money_char ).
+ $line_item->{'unit_amount'};
+ $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
+
+ push @detail_items, $detail;
+ push @buf, ( [ $detail->{'description'},
+ $money_char. sprintf("%10.2f", $line_item->{'amount'}),
+ ],
+ map { [ " ". $_, '' ] } @{$detail->{'ext_description'}},
+ );
+ }
+
+ if ( $section->{'description'} ) {
+ push @buf, ( ['','-----------'],
+ [ $section->{'description'}. ' sub-total',
+ $money_char. sprintf("%10.2f", $section->{'subtotal'})
+ ],
+ [ '', '' ],
+ [ '', '' ],
+ );
+ }
+
+ }
+
+ $invoice_data{current_less_finance} =
+ sprintf('%.2f', $self->charged - $invoice_data{finance_amount} );
+
+ if ( $multisection && !$conf->exists('disable_previous_balance')
+ || $conf->exists('previous_balance-summary_only') )
+ {
+ unshift @sections, $previous_section if $pr_total;
+ }
+
+ foreach my $tax ( $self->_items_tax ) {
+
+ $taxtotal += $tax->{'amount'};
+
+ my $description = &$escape_function( $tax->{'description'} );
+ my $amount = sprintf( '%.2f', $tax->{'amount'} );
+
+ if ( $multisection ) {
+
+ my $money = $old_latex ? '' : $money_char;
+ push @detail_items, {
+ ext_description => [],
+ ref => '',
+ quantity => '',
+ description => $description,
+ amount => $money. $amount,
+ product_code => '',
+ section => $tax_section,
+ };
+
+ } else {
+
+ push @total_items, {
+ 'total_item' => $description,
+ 'total_amount' => $other_money_char. $amount,
+ };
+
+ }
+
+ push @buf,[ $description,
+ $money_char. $amount,
+ ];
+
+ }
+
+ if ( $taxtotal ) {
+ my $total = {};
+ $total->{'total_item'} = 'Sub-total';
+ $total->{'total_amount'} =
+ $other_money_char. sprintf('%.2f', $self->charged - $taxtotal );
+
+ if ( $multisection ) {
+ $tax_section->{'subtotal'} = $other_money_char.
+ sprintf('%.2f', $taxtotal);
+ $tax_section->{'pretotal'} = 'New charges sub-total '.
+ $total->{'total_amount'};
+ push @sections, $tax_section if $taxtotal;
+ }else{
+ unshift @total_items, $total;
+ }
+ }
+ $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
+
+ push @buf,['','-----------'];
+ push @buf,[( $conf->exists('disable_previous_balance')
+ ? 'Total Charges'
+ : 'Total New Charges'
+ ),
+ $money_char. sprintf("%10.2f",$self->charged) ];
+ push @buf,['',''];
+
+ {
+ my $total = {};
+ my $item = 'Total';
+ $item = $conf->config('previous_balance-exclude_from_total')
+ || 'Total New Charges'
+ if $conf->exists('previous_balance-exclude_from_total');
+ my $amount = $self->charged +
+ ( $conf->exists('disable_previous_balance') ||
+ $conf->exists('previous_balance-exclude_from_total')
+ ? 0
+ : $pr_total
+ );
+ $total->{'total_item'} = &$embolden_function($item);
+ $total->{'total_amount'} =
+ &$embolden_function( $other_money_char. sprintf( '%.2f', $amount ) );
+ if ( $multisection ) {
+ if ( $adjust_section->{'sort_weight'} ) {
+ $adjust_section->{'posttotal'} = 'Balance Forward '. $other_money_char.
+ sprintf("%.2f", ($self->billing_balance || 0) );
+ } else {
+ $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char.
+ sprintf('%.2f', $self->charged );
+ }
+ }else{
+ push @total_items, $total;
+ }
+ push @buf,['','-----------'];
+ push @buf,[$item,
+ $money_char.
+ sprintf( '%10.2f', $amount )
+ ];
+ push @buf,['',''];
+ }
+
+ unless ( $conf->exists('disable_previous_balance') ) {
+ #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+
+ # credits
+ my $credittotal = 0;
+ foreach my $credit ( $self->_items_credits('trim_len'=>60) ) {
+
+ my $total;
+ $total->{'total_item'} = &$escape_function($credit->{'description'});
+ $credittotal += $credit->{'amount'};
+ $total->{'total_amount'} = '-'. $other_money_char. $credit->{'amount'};
+ $adjusttotal += $credit->{'amount'};
+ if ( $multisection ) {
+ my $money = $old_latex ? '' : $money_char;
+ push @detail_items, {
+ ext_description => [],
+ ref => '',
+ quantity => '',
+ description => &$escape_function($credit->{'description'}),
+ amount => $money. $credit->{'amount'},
+ product_code => '',
+ section => $adjust_section,
+ };
+ } else {
+ push @total_items, $total;
+ }
+
+ }
+ $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
+
+ #credits (again)
+ foreach my $credit ( $self->_items_credits('trim_len'=>32) ) {
+ push @buf, [ $credit->{'description'}, $money_char.$credit->{'amount'} ];
+ }
+
+ # payments
+ my $paymenttotal = 0;
+ foreach my $payment ( $self->_items_payments ) {
+ my $total = {};
+ $total->{'total_item'} = &$escape_function($payment->{'description'});
+ $paymenttotal += $payment->{'amount'};
+ $total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'};
+ $adjusttotal += $payment->{'amount'};
+ if ( $multisection ) {
+ my $money = $old_latex ? '' : $money_char;
+ push @detail_items, {
+ ext_description => [],
+ ref => '',
+ quantity => '',
+ description => &$escape_function($payment->{'description'}),
+ amount => $money. $payment->{'amount'},
+ product_code => '',
+ section => $adjust_section,
+ };
+ }else{
+ push @total_items, $total;
+ }
+ push @buf, [ $payment->{'description'},
+ $money_char. sprintf("%10.2f", $payment->{'amount'}),
+ ];
+ }
+ $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
+
+ if ( $multisection ) {
+ $adjust_section->{'subtotal'} = $other_money_char.
+ sprintf('%.2f', $adjusttotal);
+ push @sections, $adjust_section
+ unless $adjust_section->{sort_weight};
+ }
+
+ {
+ my $total;
+ $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
+ $total->{'total_amount'} =
+ &$embolden_function(
+ $other_money_char. sprintf('%.2f', $summarypage
+ ? $self->charged +
+ $self->billing_balance
+ : $self->owed + $pr_total
+ )
+ );
+ if ( $multisection && !$adjust_section->{sort_weight} ) {
+ $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '.
+ $total->{'total_amount'};
+ }else{
+ push @total_items, $total;
+ }
+ push @buf,['','-----------'];
+ push @buf,[$self->balance_due_msg, $money_char.
+ sprintf("%10.2f", $balance_due ) ];
+ }
+ }
+
+ if ( $multisection ) {
+ if ($conf->exists('svc_phone_sections')) {
+ my $total;
+ $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
+ $total->{'total_amount'} =
+ &$embolden_function(
+ $other_money_char. sprintf('%.2f', $self->owed + $pr_total)
+ );
+ my $last_section = pop @sections;
+ $last_section->{'posttotal'} = $total->{'total_item'}. ' '.
+ $total->{'total_amount'};
+ push @sections, $last_section;
+ }
+ push @sections, @$late_sections
+ if $unsquelched;
+ }
+
+ my @includelist = ();
+ push @includelist, 'summary' if $summarypage;
+ foreach my $include ( @includelist ) {
+
+ my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
+ my @inc_src;
+
+ if ( length( $conf->config($inc_file, $agentnum) ) ) {
+
+ @inc_src = $conf->config($inc_file, $agentnum);
+
+ } else {
+
+ $inc_file = $conf->key_orbase("invoice_latex$include", $template);
+
+ my $convert_map = $convert_maps{$format}{$include};
+
+ @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
+ s/--\@\]/$delimiters{$format}[1]/g;
+ $_;
+ }
+ &$convert_map( $conf->config($inc_file, $agentnum) );
+
+ }
+
+ my $inc_tt = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @inc_src ],
+ DELIMITERS => $delimiters{$format},
+ ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
+
+ unless ( $inc_tt->compile() ) {
+ my $error = "Can't compile $inc_file template: $Text::Template::ERROR\n";
+ warn $error. "Template:\n". join('', map "$_\n", @inc_src);
+ die $error;
+ }
+
+ $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
+
+ $invoice_data{$include} =~ s/\n+$//
+ if ($format eq 'latex');
+ }
+
+ $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?"
+ if ( $format eq 'template' && !$wasfunc );
+
+ if ($format eq 'template') {
+
+ if ( $invoice_lines ) {
+ $invoice_data{'total_pages'} = int( scalar(@buf) / $invoice_lines );
+ $invoice_data{'total_pages'}++
+ if scalar(@buf) % $invoice_lines;
+ }
+
+ #setup subroutine for the template
+ sub FS::cust_bill::_template::invoice_lines {
+ my $lines = shift || scalar(@FS::cust_bill::_template::buf);
+ map {
+ scalar(@FS::cust_bill::_template::buf)
+ ? shift @FS::cust_bill::_template::buf
+ : [ '', '' ];
+ }
+ ( 1 .. $lines );
+ }
+
+ my $lines;
+ my @collect;
+ while (@buf) {
+ push @collect, split("\n",
+ $text_template->fill_in( HASH => \%invoice_data,
+ PACKAGE => 'FS::cust_bill::_template'
+ )
+ );
+ $FS::cust_bill::_template::page++;
+ }
+ map "$_\n", @collect;
+ }else{
+ warn "filling in template for invoice ". $self->invnum. "\n"
+ if $DEBUG;
+ warn join("\n", map " $_ => ". $invoice_data{$_}, keys %invoice_data). "\n"
+ if $DEBUG > 1;
+
+ $text_template->fill_in(HASH => \%invoice_data);
+ }
+}
+
+# helper routine for generating date ranges
+sub _prior_month30s {
+ my $self = shift;
+ my @ranges = (
+ [ 1, 2592000 ], # 0-30 days ago
+ [ 2592000, 5184000 ], # 30-60 days ago
+ [ 5184000, 7776000 ], # 60-90 days ago
+ [ 7776000, 0 ], # 90+ days ago
+ );
+
+ map { [ $_->[0] ? $self->_date - $_->[0] - 1 : '',
+ $_->[1] ? $self->_date - $_->[1] - 1 : '',
+ ] }
+ @ranges;
+}
+
+=item print_ps HASHREF | [ TIME [ , TEMPLATE ] ]
+
+Returns an postscript invoice, as a scalar.
+
+Options can be passed as a hashref (recommended) or as a list of time, template
+and then any key/value pairs for any other options.
+
+I<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.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub print_ps {
+ my $self = shift;
+
+ my ($file, $lfile) = $self->print_latex(@_);
+ my $ps = generate_ps($file);
+ unlink($lfile);
+
+ $ps;
+}
+
+=item print_pdf HASHREF | [ TIME [ , TEMPLATE ] ]
+
+Returns an PDF invoice, as a scalar.
+
+Options can be passed as a hashref (recommended) or as a list of time, template
+and then any key/value pairs for any other options.
+
+I<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.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+=cut
+
+sub print_pdf {
+ my $self = shift;
+
+ my ($file, $lfile) = $self->print_latex(@_);
+ my $pdf = generate_pdf($file);
+ unlink($lfile);
+
+ $pdf;
+}
+
+=item print_html HASHREF | [ TIME [ , TEMPLATE [ , CID ] ] ]
+
+Returns an HTML invoice, as a scalar.
+
+I<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.
+
+I<template>, if specified, is the name of a suffix for alternate invoices.
+
+I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+
+I<cid> is a MIME Content-ID used to create a "cid:" URL for the logo image, used
+when emailing the invoice as part of a multipart/related MIME email.
+
+=cut
+
+sub print_html {
+ my $self = shift;
+ my %params;
+ if ( ref($_[0]) ) {
+ %params = %{ shift() };
+ }else{
+ $params{'time'} = shift;
+ $params{'template'} = shift;
+ $params{'cid'} = shift;
+ }
+
+ $params{'format'} = 'html';
+
+ $self->print_generic( %params );
+}
+
+# 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". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
+ $value =~ s/([<>])/\$$1\$/g;
+ $value;
+}
+
+#utility methods for print_*
+
+sub _translate_old_latex_format {
+ warn "_translate_old_latex_format called\n"
+ if $DEBUG;
+
+ my @template = ();
+ while ( @_ ) {
+ my $line = shift;
+
+ if ( $line =~ /^%%Detail\s*$/ ) {
+
+ push @template, q![@--!,
+ q! foreach my $_tr_line (@detail_items) {!,
+ q! if ( scalar ($_tr_item->{'ext_description'} ) ) {!,
+ q! $_tr_line->{'description'} .= !,
+ q! "\\tabularnewline\n~~".!,
+ q! join( "\\tabularnewline\n~~",!,
+ q! @{$_tr_line->{'ext_description'}}!,
+ q! );!,
+ q! }!;
+
+ while ( ( my $line_item_line = shift )
+ !~ /^%%EndDetail\s*$/ ) {
+ $line_item_line =~ s/'/\\'/g; # nice LTS
+ $line_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes
+ $line_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g;
+ push @template, " \$OUT .= '$line_item_line';";
+ }
+
+ push @template, '}',
+ '--@]';
+ #' doh, gvim
+ } elsif ( $line =~ /^%%TotalDetails\s*$/ ) {
+
+ push @template, '[@--',
+ ' foreach my $_tr_line (@total_items) {';
+
+ while ( ( my $total_item_line = shift )
+ !~ /^%%EndTotalDetails\s*$/ ) {
+ $total_item_line =~ s/'/\\'/g; # nice LTS
+ $total_item_line =~ s/\\/\\\\/g; # escape quotes and backslashes
+ $total_item_line =~ s/\$(\w+)/'. \$_tr_line->{$1}. '/g;
+ push @template, " \$OUT .= '$total_item_line';";
+ }
+
+ push @template, '}',
+ '--@]';
+
+ } else {
+ $line =~ s/\$(\w+)/[\@-- \$$1 --\@]/g;
+ push @template, $line;
+ }
+
+ }
+
+ if ($DEBUG) {
+ warn "$_\n" foreach @template;
+ }
+
+ (@template);
+}
+
+sub terms {
+ my $self = shift;
+
+ #check for an invoice-specific override
+ return $self->invoice_terms if $self->invoice_terms;
+
+ #check for a customer- specific override
+ my $cust_main = $self->cust_main;
+ return $cust_main->invoice_terms if $cust_main->invoice_terms;
+
+ #use configured default
+ $conf->config('invoice_default_terms') || '';
+}
+
+sub due_date {
+ my $self = shift;
+ my $duedate = '';
+ if ( $self->terms =~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $duedate = $self->_date() + ( $1 * 86400 );
+ }
+ $duedate;
+}
+
+sub due_date2str {
+ my $self = shift;
+ $self->due_date ? time2str(shift, $self->due_date) : '';
+}
+
+sub balance_due_msg {
+ my $self = shift;
+ my $msg = 'Balance Due';
+ return $msg unless $self->terms;
+ if ( $self->due_date ) {
+ $msg .= ' - Please pay by '. $self->due_date2str($date_format);
+ } elsif ( $self->terms ) {
+ $msg .= ' - '. $self->terms;
+ }
+ $msg;
+}
+
+sub balance_due_date {
+ my $self = shift;
+ my $duedate = '';
+ if ( $conf->exists('invoice_default_terms')
+ && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $duedate = time2str($rdate_format, $self->_date + ($1*86400) );
+ }
+ $duedate;
+}
+
+=item invnum_date_pretty
+
+Returns a string with the invoice number and date, for example:
+"Invoice #54 (3/20/2008)"
+
+=cut
+
+sub invnum_date_pretty {
+ my $self = shift;
+ 'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')';
+}
+
+=item _date_pretty
+
+Returns a string with the date, for example: "3/20/2008"
+
+=cut
+
+sub _date_pretty {
+ my $self = shift;
+ time2str($date_format, $self->_date);
+}
+
+use vars qw(%pkg_category_cache);
+sub _items_sections {
+ my $self = shift;
+ my $late = shift;
+ my $summarypage = shift;
+ my $escape = shift;
+ my $extra_sections = shift;
+ my $format = shift;
+
+ my %subtotal = ();
+ my %late_subtotal = ();
+ my %not_tax = ();
+
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
+ {
+
+ my $usage = $cust_bill_pkg->usage;
+
+ foreach my $display ($cust_bill_pkg->cust_bill_pkg_display) {
+ next if ( $display->summary && $summarypage );
+
+ my $section = $display->section;
+ my $type = $display->type;
+
+ $not_tax{$section} = 1
+ unless $cust_bill_pkg->pkgnum == 0;
+
+ if ( $display->post_total && !$summarypage ) {
+ if (! $type || $type eq 'S') {
+ $late_subtotal{$section} += $cust_bill_pkg->setup
+ if $cust_bill_pkg->setup != 0;
+ }
+
+ if (! $type) {
+ $late_subtotal{$section} += $cust_bill_pkg->recur
+ if $cust_bill_pkg->recur != 0;
+ }
+
+ if ($type && $type eq 'R') {
+ $late_subtotal{$section} += $cust_bill_pkg->recur - $usage
+ if $cust_bill_pkg->recur != 0;
+ }
+
+ if ($type && $type eq 'U') {
+ $late_subtotal{$section} += $usage
+ unless scalar(@$extra_sections);
+ }
+
+ } else {
+
+ next if $cust_bill_pkg->pkgnum == 0 && ! $section;
+
+ if (! $type || $type eq 'S') {
+ $subtotal{$section} += $cust_bill_pkg->setup
+ if $cust_bill_pkg->setup != 0;
+ }
+
+ if (! $type) {
+ $subtotal{$section} += $cust_bill_pkg->recur
+ if $cust_bill_pkg->recur != 0;
+ }
+
+ if ($type && $type eq 'R') {
+ $subtotal{$section} += $cust_bill_pkg->recur - $usage
+ if $cust_bill_pkg->recur != 0;
+ }
+
+ if ($type && $type eq 'U') {
+ $subtotal{$section} += $usage
+ unless scalar(@$extra_sections);
+ }
+
+ }
+
+ }
+
+ }
+
+ %pkg_category_cache = ();
+
+ push @$late, map { { 'description' => &{$escape}($_),
+ 'subtotal' => $late_subtotal{$_},
+ 'post_total' => 1,
+ 'sort_weight' => ( _pkg_category($_)
+ ? _pkg_category($_)->weight
+ : 0
+ ),
+ ((_pkg_category($_) && _pkg_category($_)->condense)
+ ? $self->_condense_section($format)
+ : ()
+ ),
+ } }
+ sort _sectionsort keys %late_subtotal;
+
+ my @sections;
+ if ( $summarypage ) {
+ @sections = grep { exists($subtotal{$_}) || ! _pkg_category($_)->disabled }
+ map { $_->categoryname } qsearch('pkg_category', {});
+ } else {
+ @sections = keys %subtotal;
+ }
+
+ my @early = map { { 'description' => &{$escape}($_),
+ 'subtotal' => $subtotal{$_},
+ 'summarized' => $not_tax{$_} ? '' : 'Y',
+ 'tax_section' => $not_tax{$_} ? '' : 'Y',
+ 'sort_weight' => ( _pkg_category($_)
+ ? _pkg_category($_)->weight
+ : 0
+ ),
+ ((_pkg_category($_) && _pkg_category($_)->condense)
+ ? $self->_condense_section($format)
+ : ()
+ ),
+ }
+ } @sections;
+ push @early, @$extra_sections if $extra_sections;
+
+ sort { $a->{sort_weight} <=> $b->{sort_weight} } @early;
+
+}
+
+#helper subs for above
+
+sub _sectionsort {
+ _pkg_category($a)->weight <=> _pkg_category($b)->weight;
+}
+
+sub _pkg_category {
+ my $categoryname = shift;
+ $pkg_category_cache{$categoryname} ||=
+ qsearchs( 'pkg_category', { 'categoryname' => $categoryname } );
+}
+
+my %condensed_format = (
+ 'label' => [ qw( Description Qty Amount ) ],
+ 'fields' => [
+ sub { shift->{description} },
+ sub { shift->{quantity} },
+ sub { shift->{amount} },
+ ],
+ 'align' => [ qw( l r r ) ],
+ 'span' => [ qw( 5 1 1 ) ], # unitprices?
+ 'width' => [ qw( 10.7cm 1.4cm 1.6cm ) ], # don't like this
+);
+
+sub _condense_section {
+ my ( $self, $format ) = ( shift, shift );
+ ( 'condensed' => 1,
+ map { my $method = "_condensed_$_"; $_ => $self->$method($format) }
+ qw( description_generator
+ header_generator
+ total_generator
+ total_line_generator
+ )
+ );
+}
+
+sub _condensed_generator_defaults {
+ my ( $self, $format ) = ( shift, shift );
+ return ( \%condensed_format, ' ', ' ', ' ', sub { shift } );
+}
+
+my %html_align = (
+ 'c' => 'center',
+ 'l' => 'left',
+ 'r' => 'right',
+);
+
+sub _condensed_header_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+
+ if ($format eq 'latex') {
+ $prefix = "\\hline\n\\rule{0pt}{2.5ex}\n\\makebox[1.4cm]{}&\n";
+ $suffix = "\\\\\n\\hline";
+ $separator = "&\n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
+ };
+ } elsif ( $format eq 'html' ) {
+ $prefix = '<th></th>';
+ $suffix = '';
+ $separator = '';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<th align="$html_align{$a}">$d</th>!;
+ };
+ }
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( map { $f->{$_}->[$i] } qw(label align span width) );
+ }
+
+ $prefix. join($separator, @result). $suffix;
+ };
+
+}
+
+sub _condensed_description_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+
+ if ($format eq 'latex') {
+ $prefix = "\\hline\n\\multicolumn{1}{c}{\\rule{0pt}{2.5ex}~} &\n";
+ $suffix = '\\\\';
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '"><td align="center"></td>';
+ $suffix = '';
+ $separator = '';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}">$d</td>!;
+ };
+ }
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result, &{$column}( &{$f->{fields}->[$i]}(@args),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+sub _condensed_total_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ # my $r = &{$f->{fields}->[$i]}(@args);
+ # $r .= ' Total' unless $i;
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args). ($i ? '' : ' Total'),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+=item total_line_generator FORMAT
+
+Returns a coderef used for generation of invoice total line items for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+# should not be used: will have issues with hash element names (description vs
+# total_item and amount vs total_amount -- another array of functions?
+
+sub _condensed_total_line_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+#sub _items_extra_usage_sections {
+# my $self = shift;
+# my $escape = shift;
+#
+# my %sections = ();
+#
+# my %usage_class = map{ $_->classname, $_ } qsearch('usage_class', {});
+# foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
+# {
+# next unless $cust_bill_pkg->pkgnum > 0;
+#
+# foreach my $section ( keys %usage_class ) {
+#
+# my $usage = $cust_bill_pkg->usage($section);
+#
+# next unless $usage && $usage > 0;
+#
+# $sections{$section} ||= 0;
+# $sections{$section} += $usage;
+#
+# }
+#
+# }
+#
+# map { { 'description' => &{$escape}($_),
+# 'subtotal' => $sections{$_},
+# 'summarized' => '',
+# 'tax_section' => '',
+# }
+# }
+# sort {$usage_class{$a}->weight <=> $usage_class{$b}->weight} keys %sections;
+#
+#}
+
+sub _items_extra_usage_sections {
+ my $self = shift;
+ my $escape = shift;
+ my $format = shift;
+
+ my %sections = ();
+ my %classnums = ();
+ my %lines = ();
+
+ my %usage_class = map { $_->classnum => $_ } qsearch( 'usage_class', {} );
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+ next unless $cust_bill_pkg->pkgnum > 0;
+
+ foreach my $classnum ( keys %usage_class ) {
+ my $section = $usage_class{$classnum}->classname;
+ $classnums{$section} = $classnum;
+
+ foreach my $detail ( $cust_bill_pkg->cust_bill_pkg_detail($classnum) ) {
+ my $amount = $detail->amount;
+ next unless $amount && $amount > 0;
+
+ $sections{$section} ||= { 'subtotal'=>0, 'calls'=>0, 'duration'=>0 };
+ $sections{$section}{amount} += $amount; #subtotal
+ $sections{$section}{calls}++;
+ $sections{$section}{duration} += $detail->duration;
+
+ my $desc = $detail->regionname;
+ my $description = $desc;
+ $description = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
+
+ $lines{$section}{$desc} ||= {
+ description => &{$escape}($description),
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ ref => '',
+ amount => 0,
+ calls => 0,
+ duration => 0,
+ #unit_amount => $cust_bill_pkg->unitrecur,
+ quantity => $cust_bill_pkg->quantity,
+ product_code => 'N/A',
+ ext_description => [],
+ };
+
+ $lines{$section}{$desc}{amount} += $amount;
+ $lines{$section}{$desc}{calls}++;
+ $lines{$section}{$desc}{duration} += $detail->duration;
+
+ }
+ }
+ }
+
+ my %sectionmap = ();
+ foreach (keys %sections) {
+ my $usage_class = $usage_class{$classnums{$_}};
+ $sectionmap{$_} = { 'description' => &{$escape}($_),
+ 'amount' => $sections{$_}{amount}, #subtotal
+ 'calls' => $sections{$_}{calls},
+ 'duration' => $sections{$_}{duration},
+ 'summarized' => '',
+ 'tax_section' => '',
+ 'sort_weight' => $usage_class->weight,
+ ( $usage_class->format
+ ? ( map { $_ => $usage_class->$_($format) }
+ qw( description_generator header_generator total_generator total_line_generator )
+ )
+ : ()
+ ),
+ };
+ }
+
+ my @sections = sort { $a->{sort_weight} <=> $b->{sort_weight} }
+ values %sectionmap;
+
+ my @lines = ();
+ foreach my $section ( keys %lines ) {
+ foreach my $line ( keys %{$lines{$section}} ) {
+ my $l = $lines{$section}{$line};
+ $l->{section} = $sectionmap{$section};
+ $l->{amount} = sprintf( "%.2f", $l->{amount} );
+ #$l->{unit_amount} = sprintf( "%.2f", $l->{unit_amount} );
+ push @lines, $l;
+ }
+ }
+
+ return(\@sections, \@lines);
+
+}
+
+sub _items_svc_phone_sections {
+ my $self = shift;
+ my $escape = shift;
+ my $format = shift;
+
+ my %sections = ();
+ my %classnums = ();
+ my %lines = ();
+
+ my %usage_class = map { $_->classnum => $_ } qsearch( 'usage_class', {} );
+
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+ next unless $cust_bill_pkg->pkgnum > 0;
+
+ foreach my $detail ( $cust_bill_pkg->cust_bill_pkg_detail ) {
+
+ my $phonenum = $detail->phonenum;
+ next unless $phonenum;
+
+ my $amount = $detail->amount;
+ next unless $amount && $amount > 0;
+
+ $sections{$phonenum} ||= { 'amount' => 0,
+ 'calls' => 0,
+ 'duration' => 0,
+ 'sort_weight' => -1,
+ 'phonenum' => $phonenum,
+ };
+ $sections{$phonenum}{amount} += $amount; #subtotal
+ $sections{$phonenum}{calls}++;
+ $sections{$phonenum}{duration} += $detail->duration;
+
+ my $desc = $detail->regionname;
+ my $description = $desc;
+ $description = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
+
+ $lines{$phonenum}{$desc} ||= {
+ description => &{$escape}($description),
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => '',
+ ref => '',
+ amount => 0,
+ calls => 0,
+ duration => 0,
+ #unit_amount => '',
+ quantity => '',
+ product_code => 'N/A',
+ ext_description => [],
+ };
+
+ $lines{$phonenum}{$desc}{amount} += $amount;
+ $lines{$phonenum}{$desc}{calls}++;
+ $lines{$phonenum}{$desc}{duration} += $detail->duration;
+
+ my $line = $usage_class{$detail->classnum}->classname;
+ $sections{"$phonenum $line"} ||=
+ { 'amount' => 0,
+ 'calls' => 0,
+ 'duration' => 0,
+ 'sort_weight' => $usage_class{$detail->classnum}->weight,
+ 'phonenum' => $phonenum,
+ };
+ $sections{"$phonenum $line"}{amount} += $amount; #subtotal
+ $sections{"$phonenum $line"}{calls}++;
+ $sections{"$phonenum $line"}{duration} += $detail->duration;
+
+ $lines{"$phonenum $line"}{$desc} ||= {
+ description => &{$escape}($description),
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => '',
+ ref => '',
+ amount => 0,
+ calls => 0,
+ duration => 0,
+ #unit_amount => '',
+ quantity => '',
+ product_code => 'N/A',
+ ext_description => [],
+ };
+
+ $lines{"$phonenum $line"}{$desc}{amount} += $amount;
+ $lines{"$phonenum $line"}{$desc}{calls}++;
+ $lines{"$phonenum $line"}{$desc}{duration} += $detail->duration;
+ push @{$lines{"$phonenum $line"}{$desc}{ext_description}},
+ $detail->formatted('format' => $format);
+
+ }
+ }
+
+ my %sectionmap = ();
+ my $simple = new FS::usage_class { format => 'simple' }; #bleh
+ my $usage_simple = new FS::usage_class { format => 'usage_simple' }; #bleh
+ foreach ( keys %sections ) {
+ my $summary = $sections{$_}{sort_weight} < 0 ? 1 : 0;
+ my $usage_class = $summary ? $simple : $usage_simple;
+ my $ending = $summary ? ' usage charges' : '';
+ $sectionmap{$_} = { 'description' => &{$escape}($_. $ending),
+ 'amount' => $sections{$_}{amount}, #subtotal
+ 'calls' => $sections{$_}{calls},
+ 'duration' => $sections{$_}{duration},
+ 'summarized' => '',
+ 'tax_section' => '',
+ 'phonenum' => $sections{$_}{phonenum},
+ 'sort_weight' => $sections{$_}{sort_weight},
+ 'post_total' => $summary, #inspire pagebreak
+ (
+ ( map { $_ => $usage_class->$_($format) }
+ qw( description_generator
+ header_generator
+ total_generator
+ total_line_generator
+ )
+ )
+ ),
+ };
+ }
+
+ my @sections = sort { $a->{phonenum} cmp $b->{phonenum} ||
+ $a->{sort_weight} <=> $b->{sort_weight}
+ }
+ values %sectionmap;
+
+ my @lines = ();
+ foreach my $section ( keys %lines ) {
+ foreach my $line ( keys %{$lines{$section}} ) {
+ my $l = $lines{$section}{$line};
+ $l->{section} = $sectionmap{$section};
+ $l->{amount} = sprintf( "%.2f", $l->{amount} );
+ #$l->{unit_amount} = sprintf( "%.2f", $l->{unit_amount} );
+ push @lines, $l;
+ }
+ }
+
+ return(\@sections, \@lines);
+
+}
+
+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 @display = qw( _items_previous _items_pkg );
+
+ 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 ) {
+ my $date = $conf->exists('invoice_show_prior_due_date')
+ ? 'due '. $_->due_date2str($date_format)
+ : time2str($date_format, $_->_date);
+ push @b, {
+ 'description' => 'Previous Balance, Invoice #'. $_->invnum. " ($date)",
+ #'pkgpart' => 'N/A',
+ 'pkgnum' => 'N/A',
+ 'amount' => sprintf("%.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 %options = @_;
+ my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg;
+ my @items = $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+ if ($options{section} && $options{section}->{condensed}) {
+ my %itemshash = ();
+ local $Storable::canonical = 1;
+ foreach ( @items ) {
+ my $item = { %$_ };
+ delete $item->{ref};
+ delete $item->{ext_description};
+ my $key = freeze($item);
+ $itemshash{$key} ||= 0;
+ $itemshash{$key} ++; # += $item->{quantity};
+ }
+ @items = sort { $a->{description} cmp $b->{description} }
+ map { my $i = thaw($_);
+ $i->{quantity} = $itemshash{$_};
+ $i->{amount} =
+ sprintf( "%.2f", $i->{quantity} * $i->{amount} );#unit_amount
+ $i;
+ }
+ keys %itemshash;
+ }
+ @items;
+}
+
+sub _taxsort {
+ return 0 unless $a cmp $b;
+ return -1 if $b eq 'Tax';
+ return 1 if $a eq 'Tax';
+ return -1 if $b eq 'Other surcharges';
+ return 1 if $a eq 'Other surcharges';
+ $a cmp $b;
+}
+
+sub _items_tax {
+ my $self = shift;
+ my @cust_bill_pkg = sort _taxsort 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 %opt = @_;
+
+ my $format = $opt{format} || '';
+ my $escape_function = $opt{escape_function} || sub { shift };
+ my $format_function = $opt{format_function} || '';
+ my $unsquelched = $opt{unsquelched} || '';
+ my $section = $opt{section}->{description} if $opt{section};
+ my $summary_page = $opt{summary_page} || '';
+ my $multilocation = $opt{multilocation} || '';
+
+ my @b = ();
+ my ($s, $r, $u) = ( undef, undef, undef );
+ foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+ {
+
+ foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
+ if ( $_ && !$cust_bill_pkg->hidden ) {
+ $_->{amount} = sprintf( "%.2f", $_->{amount} ),
+ $_->{amount} =~ s/^\-0\.00$/0.00/;
+ $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
+ push @b, { %$_ }
+ unless $_->{amount} == 0;
+ $_ = undef;
+ }
+ }
+
+ foreach my $display ( grep { defined($section)
+ ? $_->section eq $section
+ : 1
+ }
+ grep { !$_->summary || !$summary_page }
+ $cust_bill_pkg->cust_bill_pkg_display
+ )
+ {
+
+ my $type = $display->type;
+
+ my $desc = $cust_bill_pkg->desc;
+ $desc = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
+
+ my %details_opt = ( 'format' => $format,
+ 'escape_function' => $escape_function,
+ 'format_function' => $format_function,
+ );
+
+ if ( $cust_bill_pkg->pkgnum > 0 ) {
+
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+
+ if ( $cust_bill_pkg->setup != 0 && (!$type || $type eq 'S') ) {
+
+ my $description = $desc;
+ $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+
+ my @d = ();
+ unless ( $cust_pkg->part_pkg->hide_svc_detail
+ || $cust_bill_pkg->hidden )
+ {
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date);
+ if ( $multilocation ) {
+ my $loc = $cust_pkg->location_label;
+ $loc = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($loc) > 50;
+ push @d, &{$escape_function}($loc);
+ }
+ }
+ push @d, $cust_bill_pkg->details(%details_opt)
+ if $cust_bill_pkg->recur == 0;
+
+ if ( $cust_bill_pkg->hidden ) {
+ $s->{amount} += $cust_bill_pkg->setup;
+ $s->{unit_amount} += $cust_bill_pkg->unitsetup;
+ push @{ $s->{ext_description} }, @d;
+ } else {
+ $s = {
+ description => $description,
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ amount => $cust_bill_pkg->setup,
+ unit_amount => $cust_bill_pkg->unitsetup,
+ quantity => $cust_bill_pkg->quantity,
+ ext_description => \@d,
+ };
+ };
+
+ }
+
+ if ( $cust_bill_pkg->recur != 0 &&
+ ( !$type || $type eq 'R' || $type eq 'U' )
+ )
+ {
+
+ my $is_summary = $display->summary;
+ my $description = ($is_summary && $type && $type eq 'U')
+ ? "Usage charges" : $desc;
+
+ unless ( $conf->exists('disable_line_item_date_ranges') ) {
+ $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
+ " - ". time2str($date_format, $cust_bill_pkg->edate). ")";
+ }
+
+ my @d = ();
+
+ #at least until cust_bill_pkg has "past" ranges in addition to
+ #the "future" sdate/edate ones... see #3032
+ my @dates = ( $self->_date );
+ my $prev = $cust_bill_pkg->previous_cust_bill_pkg;
+ push @dates, $prev->sdate if $prev;
+
+ unless ( $cust_pkg->part_pkg->hide_svc_detail
+ || $cust_bill_pkg->itemdesc
+ || $cust_bill_pkg->hidden
+ || $is_summary && $type && $type eq 'U' )
+ {
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short(@dates)
+ #$cust_bill_pkg->edate,
+ #$cust_bill_pkg->sdate)
+ ;
+ if ( $multilocation ) {
+ my $loc = $cust_pkg->location_label;
+ $loc = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($loc) > 50;
+ push @d, &{$escape_function}($loc);
+ }
+ }
+
+ push @d, $cust_bill_pkg->details(%details_opt)
+ unless ($is_summary || $type && $type eq 'R');
+
+ my $amount = 0;
+ if (!$type) {
+ $amount = $cust_bill_pkg->recur;
+ }elsif($type eq 'R') {
+ $amount = $cust_bill_pkg->recur - $cust_bill_pkg->usage;
+ }elsif($type eq 'U') {
+ $amount = $cust_bill_pkg->usage;
+ }
+
+ if ( !$type || $type eq 'R' ) {
+
+ if ( $cust_bill_pkg->hidden ) {
+ $r->{amount} += $amount;
+ $r->{unit_amount} += $cust_bill_pkg->unitrecur;
+ push @{ $r->{ext_description} }, @d;
+ } else {
+ $r = {
+ description => $description,
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ amount => $amount,
+ unit_amount => $cust_bill_pkg->unitrecur,
+ quantity => $cust_bill_pkg->quantity,
+ ext_description => \@d,
+ };
+ }
+
+ } elsif ( $amount ) { # && $type eq 'U'
+
+ if ( $cust_bill_pkg->hidden ) {
+ $u->{amount} += $amount;
+ $u->{unit_amount} += $cust_bill_pkg->unitrecur;
+ push @{ $u->{ext_description} }, @d;
+ } else {
+ $u = {
+ description => $description,
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ amount => $amount,
+ unit_amount => $cust_bill_pkg->unitrecur,
+ quantity => $cust_bill_pkg->quantity,
+ ext_description => \@d,
+ };
+ }
+
+ }
+
+ } # recurring or usage with recurring charge
+
+ } else { #pkgnum tax or one-shot line item (??)
+
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @b, {
+ 'description' => $desc,
+ 'amount' => sprintf("%.2f", $cust_bill_pkg->setup),
+ };
+ }
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @b, {
+ 'description' => "$desc (".
+ time2str($date_format, $cust_bill_pkg->sdate). ' - '.
+ time2str($date_format, $cust_bill_pkg->edate). ')',
+ 'amount' => sprintf("%.2f", $cust_bill_pkg->recur),
+ };
+ }
+
+ }
+
+ }
+
+ }
+
+ foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
+ if ( $_ ) {
+ $_->{amount} = sprintf( "%.2f", $_->{amount} ),
+ $_->{amount} =~ s/^\-0\.00$/0.00/;
+ $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
+ push @b, { %$_ }
+ unless $_->{amount} == 0;
+ }
+ }
+
+ @b;
+
+}
+
+sub _items_credits {
+ my( $self, %opt ) = @_;
+ my $trim_len = $opt{'trim_len'} || 60;
+
+ my @b;
+ #credits
+ foreach ( $self->cust_credited ) {
+
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+ my $reason = substr($_->cust_credit->reason, 0, $trim_len);
+ $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($date_format,$_->cust_credit->_date). $reason,
+ 'amount' => sprintf("%.2f",$_->amount),
+ };
+ }
+
+ @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($date_format,$_->cust_pay->_date ),
+ 'amount' => sprintf("%.2f", $_->amount )
+ };
+ }
+
+ @b;
+
+}
+
+=item call_details [ OPTION => VALUE ... ]
+
+Returns an array of CSV strings representing the call details for this invoice
+The only option available is the boolean prepend_billed_number
+
+=cut
+
+sub call_details {
+ my ($self, %opt) = @_;
+
+ my $format_function = sub { shift };
+
+ if ($opt{prepend_billed_number}) {
+ $format_function = sub {
+ my $detail = shift;
+ my $row = shift;
+
+ $row->amount ? $row->phonenum. ",". $detail : '"Billed number",'. $detail;
+
+ };
+ }
+
+ my @details = map { $_->details( 'format_function' => $format_function,
+ 'escape_function' => sub{ return() },
+ )
+ }
+ grep { $_->pkgnum }
+ $self->cust_bill_pkg;
+ my $header = $details[0];
+ ( $header, grep { $_ ne $header } @details );
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item process_reprint
+
+=cut
+
+sub process_reprint {
+ process_re_X('print', @_);
+}
+
+=item process_reemail
+
+=cut
+
+sub process_reemail {
+ process_re_X('email', @_);
+}
+
+=item process_refax
+
+=cut
+
+sub process_refax {
+ process_re_X('fax', @_);
+}
+
+=item process_reftp
+
+=cut
+
+sub process_reftp {
+ process_re_X('ftp', @_);
+}
+
+=item respool
+
+=cut
+
+sub process_respool {
+ process_re_X('spool', @_);
+}
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_re_X {
+ my( $method, $job ) = ( shift, shift );
+ warn "$me process_re_X $method for job $job\n" if $DEBUG;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ re_X(
+ $method,
+ $job,
+ %$param,
+ );
+
+}
+
+sub re_X {
+ my($method, $job, %param ) = @_;
+ if ( $DEBUG ) {
+ warn "re_X $method for job $job with param:\n".
+ join( '', map { " $_ => ". $param{$_}. "\n" } keys %param );
+ }
+
+ #some false laziness w/search/cust_bill.html
+ my $distinct = '';
+ my $orderby = 'ORDER BY cust_bill._date';
+
+ my $extra_sql = ' WHERE '. FS::cust_bill->search_sql_where(\%param);
+
+ my $addl_from = 'LEFT JOIN cust_main USING ( custnum )';
+
+ my @cust_bill = qsearch( {
+ #'select' => "cust_bill.*",
+ 'table' => 'cust_bill',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'debug' => 1,
+ } );
+
+ $method .= '_invoice' unless $method eq 'email' || $method eq 'print';
+
+ warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n"
+ if $DEBUG;
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+ foreach my $cust_bill ( @cust_bill ) {
+ $cust_bill->$method();
+
+ if ( $job ) { #progressbar foo
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / scalar(@cust_bill) )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ }
+
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item owed_sql
+
+Returns an SQL fragment to retreive the amount owed (charged minus credited and paid).
+
+=cut
+
+sub owed_sql {
+ my ($class, $start, $end) = @_;
+ 'charged - '.
+ $class->paid_sql($start, $end). ' - '.
+ $class->credited_sql($start, $end);
+}
+
+=item net_sql
+
+Returns an SQL fragment to retreive the net amount (charged minus credited).
+
+=cut
+
+sub net_sql {
+ my ($class, $start, $end) = @_;
+ 'charged - '. $class->credited_sql($start, $end);
+}
+
+=item paid_sql
+
+Returns an SQL fragment to retreive the amount paid against this invoice.
+
+=cut
+
+sub paid_sql {
+ my ($class, $start, $end) = @_;
+ $start &&= "AND cust_bill_pay._date <= $start";
+ $end &&= "AND cust_bill_pay._date > $end";
+ $start = '' unless defined($start);
+ $end = '' unless defined($end);
+ "( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum $start $end )";
+}
+
+=item credited_sql
+
+Returns an SQL fragment to retreive the amount credited against this invoice.
+
+=cut
+
+sub credited_sql {
+ my ($class, $start, $end) = @_;
+ $start &&= "AND cust_credit_bill._date <= $start";
+ $end &&= "AND cust_credit_bill._date > $end";
+ $start = '' unless defined($start);
+ $end = '' unless defined($end);
+ "( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum $start $end )";
+}
+
+=item search_sql_where HASHREF
+
+Class method which returns an SQL WHERE fragment to search for parameters
+specified in HASHREF. Valid parameters are
+
+=over 4
+
+=item _date
+
+List reference of start date, end date, as UNIX timestamps.
+
+=item invnum_min
+
+=item invnum_max
+
+=item agentnum
+
+=item charged
+
+List reference of charged limits (exclusive).
+
+=item owed
+
+List reference of charged limits (exclusive).
+
+=item open
+
+flag, return open invoices only
+
+=item net
+
+flag, return net invoices only
+
+=item days
+
+=item newest_percust
+
+=back
+
+Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
+
+=cut
+
+sub search_sql_where {
+ my($class, $param) = @_;
+ if ( $DEBUG ) {
+ warn "$me search_sql_where called with params: \n".
+ join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
+ }
+
+ my @search = ();
+
+ #agentnum
+ if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
+ push @search, "cust_main.agentnum = $1";
+ }
+
+ #_date
+ if ( $param->{_date} ) {
+ my($beginning, $ending) = @{$param->{_date}};
+
+ push @search, "cust_bill._date >= $beginning",
+ "cust_bill._date < $ending";
+ }
+
+ #invnum
+ if ( $param->{'invnum_min'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill.invnum >= $1";
+ }
+ if ( $param->{'invnum_max'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill.invnum <= $1";
+ }
+
+ #charged
+ if ( $param->{charged} ) {
+ my @charged = ref($param->{charged})
+ ? @{ $param->{charged} }
+ : ($param->{charged});
+
+ push @search, map { s/^charged/cust_bill.charged/; $_; }
+ @charged;
+ }
+
+ my $owed_sql = FS::cust_bill->owed_sql;
+
+ #owed
+ if ( $param->{owed} ) {
+ my @owed = ref($param->{owed})
+ ? @{ $param->{owed} }
+ : ($param->{owed});
+ push @search, map { s/^owed/$owed_sql/; $_; }
+ @owed;
+ }
+
+ #open/net flags
+ push @search, "0 != $owed_sql"
+ if $param->{'open'};
+ push @search, '0 != '. FS::cust_bill->net_sql
+ if $param->{'net'};
+
+ #days
+ push @search, "cust_bill._date < ". (time-86400*$param->{'days'})
+ if $param->{'days'};
+
+ #newest_percust
+ if ( $param->{'newest_percust'} ) {
+
+ #$distinct = 'DISTINCT ON ( cust_bill.custnum )';
+ #$orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC';
+
+ my @newest_where = map { my $x = $_;
+ $x =~ s/\bcust_bill\./newest_cust_bill./g;
+ $x;
+ }
+ grep ! /^cust_main./, @search;
+ my $newest_where = scalar(@newest_where)
+ ? ' AND '. join(' AND ', @newest_where)
+ : '';
+
+
+ push @search, "cust_bill._date = (
+ SELECT(MAX(newest_cust_bill._date)) FROM cust_bill AS newest_cust_bill
+ WHERE newest_cust_bill.custnum = cust_bill.custnum
+ $newest_where
+ )";
+
+ }
+
+ #agent virtualization
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ if ( $curuser->username eq 'fs_queue'
+ && $param->{'CurrentUser'} =~ /^(\w+)$/ ) {
+ my $username = $1;
+ my $newuser = qsearchs('access_user', {
+ 'username' => $username,
+ 'disabled' => '',
+ } );
+ if ( $newuser ) {
+ $curuser = $newuser;
+ } else {
+ warn "$me WARNING: (fs_queue) can't find CurrentUser $username\n";
+ }
+ }
+ push @search, $curuser->agentnums_sql;
+
+ join(' AND ', @search );
+
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+=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_ApplicationCommon.pm b/FS/FS/cust_bill_ApplicationCommon.pm
new file mode 100644
index 0000000..322728f
--- /dev/null
+++ b/FS/FS/cust_bill_ApplicationCommon.pm
@@ -0,0 +1,518 @@
+package FS::cust_bill_ApplicationCommon;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $skip_apply_to_lineitems_hack );
+use List::Util qw(min);
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::cust_bill_pkg;
+use FS::part_svc;
+use FS::part_export;
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::cust_bill_ApplicationCommon]';
+
+$skip_apply_to_lineitems_hack = 0;
+
+=head1 NAME
+
+FS::cust_bill_ApplicationCommon - Base class for bill application classes
+
+=head1 SYNOPSIS
+
+use FS::cust_bill_ApplicationCommon;
+
+@ISA = qw( FS::cust_bill_ApplicationCommon );
+
+sub _app_source_name { 'payment'; }
+sub _app_source_table { 'cust_pay'; }
+sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
+
+=head1 DESCRIPTION
+
+FS::cust_bill_ApplicationCommon is intended as a base class for classes which
+represent application of things to invoices, currently payments
+(see L<FS::cust_bill_pay>) or credits (see L<FS::cust_credit_bill>).
+
+=head1 METHODS
+
+=over 4
+
+=item insert
+
+=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(@_)
+ || $self->apply_to_lineitems(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+=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;
+
+ foreach my $app ( $self->lineitem_applications ) {
+ 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;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item apply_to_lineitems
+
+Auto-applies this invoice application to specific line items, if possible.
+
+=cut
+
+sub calculate_applications {
+ my( $self, %options ) = @_;
+
+ return '' if $skip_apply_to_lineitems_hack;
+
+ my @apply = ();
+
+ my $conf = new FS::Conf;
+
+ my @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
+
+ if ( exists($options{subitems}) ) {
+ my $i = 0;
+ my %open = ();
+ $open{$_->billpkgnum} = $i++ foreach @open;
+
+ foreach my $listref ( @{$options{subitems}} ) {
+ my ($billpkgnum, $itemamount, $taxlocationnum) = @$listref;
+ return "Can't apply a ". $self->_app_source_name. ' of $'. $listref->[1].
+ " to line item $billpkgnum which is not open"
+ unless exists($open{$billpkgnum});
+ my $itemindex = $open{$billpkgnum};
+ my %taxhash = ();
+ if ($taxlocationnum) {
+ %taxhash = map { ($_->primary_key => $_->get($_->primary_key)) }
+ grep { $_->get($_->primary_key) == $taxlocationnum }
+ $open[$itemindex]->cust_bill_pkg_tax_Xlocation;
+
+ return "No tax line item with a key value of $taxlocationnum exists"
+ unless scalar(%taxhash);
+ }
+ push @apply, [ $open[$itemindex], $itemamount, { %taxhash } ];
+ }
+ return \@apply;
+ }
+
+ @open = grep { $_->pkgnum == $self->pkgnum } @open
+ if $conf->exists('pkg-balances') && $self->pkgnum;
+ warn "$me ". scalar(@open). " open line items for invoice ".
+ $self->cust_bill->invnum. ": ". join(', ', @open). "\n"
+ if $DEBUG;
+ my $total = 0;
+ foreach (@open) {
+ $total += $_->owed_setup if $_->setup;
+ $total += $_->owed_recur if $_->recur;
+ }
+ $total = sprintf('%.2f', $total);
+
+ if ( $self->amount > $total ) {
+ return "Can't apply a ". $self->_app_source_name. ' of $'. $self->amount.
+ " greater than the remaining owed on line items (\$$total)";
+ }
+
+ #easy cases:
+ # - one lineitem (a simple special case of:)
+ # - amount is for whole invoice (well, all of remaining lineitem links)
+ if ( $self->amount == $total ) {
+
+ warn "$me application amount covers remaining balance of invoice in full;".
+ "applying to those lineitems\n"
+ if $DEBUG;
+
+ #@apply = map { [ $_, $_->amount ]; } @open;
+ #@apply = map { [ $_, $_->owed_setup + 0 || $_->owed_recur + 0 ]; } @open;
+ @apply = map { [ $_, $_->setup ? $_->owed_setup : $_->owed_recur ]; } @open;
+
+ } else {
+
+ #slightly magic case:
+ # - amount exactly and uniquely matches a single open lineitem
+ # (you must be trying to pay or credit that item, then)
+
+ my @same = grep { ( $_->setup && $_->owed_setup == $self->amount )
+ || ( $_->recur && $_->owed_recur == $self->amount )
+ }
+ @open;
+ if ( scalar(@same) == 1 ) {
+ warn "$me application amount exactly and uniquely matches one lineitem;".
+ " applying to that lineitem\n"
+ if $DEBUG;
+ @apply = map { [ $_, $self->amount ]; } @same
+ }
+
+ }
+
+ unless ( @apply ) {
+
+ warn "$me applying amount based on package weights\n"
+ if $DEBUG;
+
+ #and the rest:
+ # - apply based on weights...
+
+ my $weight_col = $self->_app_part_pkg_weight_column;
+ my @openweight = map {
+ my $open = $_;
+ my $cust_pkg = $open->cust_pkg;
+ my $weight =
+ $cust_pkg
+ ? ( $cust_pkg->part_pkg->$weight_col() || 0 )
+ : -1; #default or per-tax weight?
+ [ $open, $weight ]
+ }
+ @open;
+
+ my %saw = ();
+ my @weights = sort { $b <=> $a } # highest weight first
+ grep { ! $saw{$_}++ } # want a list of unique weights
+ map { $_->[1] }
+ @openweight;
+
+ my $remaining_amount = $self->amount;
+ foreach my $weight ( @weights ) {
+
+ #i hate it when my schwartz gets tangled
+ my @items = map { $_->[0] } grep { $weight == $_->[1] } @openweight;
+
+ my $itemtotal = 0;
+ foreach my $item (@items) {
+ $itemtotal += $item->owed_setup if $item->setup;
+ $itemtotal += $item->owed_recur if $item->recur;
+ }
+ my $applytotal = min( $itemtotal, $remaining_amount );
+ $remaining_amount -= $applytotal;
+
+ warn "$me applying $applytotal ($remaining_amount remaining)".
+ " to ". scalar(@items). " lineitems with weight $weight\n"
+ if $DEBUG;
+
+ #if some items are less than applytotal/num_items, then apply then in full
+ my $lessflag;
+ do {
+ $lessflag = 0;
+
+ #no, not sprintf("%.2f",
+ # we want this rounded DOWN for purposes of checking for line items
+ # less than it, we don't want .66666 becoming .67 and causing this
+ # to trigger when it shouldn't
+ my $applyeach = int( 100 * $applytotal / scalar(@items) ) / 100;
+
+ my @newitems = ();
+ foreach my $item ( @items ) {
+ my $itemamount = $item->setup ? $item->owed_setup : $item->owed_recur;
+ if ( $itemamount < $applyeach ) {
+ warn "$me applying full $itemamount".
+ " to small line item (cust_bill_pkg ". $item->billpkgnum. ")\n"
+ if $DEBUG;
+ push @apply, [ $item, $itemamount ];
+ $applytotal -= $itemamount;
+ $lessflag=1;
+ } else {
+ push @newitems, $item;
+ }
+ }
+ @items = @newitems;
+
+ } while ( $lessflag && @items );
+
+ if ( @items ) {
+
+ #and now that we've fallen out of the loop, distribute the rest equally
+
+ # should cust_bill_pay_pkg and cust_credit_bill_pkg amount columns
+ # become real instead of numeric(10,2) ??? no..
+ my $applyeach = sprintf("%.2f", $applytotal / scalar(@items) );
+
+ my @equi_apply = map { [ $_, $applyeach ] } @items;
+
+ # or should we futz with pennies instead? yes, bah!
+ my $diff =
+ sprintf('%.0f', 100 * ( $applytotal - $applyeach * scalar(@items) ) );
+ $diff = 0 if $diff eq '-0'; #yay ieee fp
+ if ( abs($diff) > scalar(@items) ) {
+ #we must have done something really wrong, the difference is more than
+ #a penny an item
+ return 'Error distributing pennies applying '.$self->_app_source_name.
+ " - can't distribute difference of $diff pennies".
+ ' among '. scalar(@items). ' line items';
+ }
+
+ warn "$me futzing with $diff pennies difference\n"
+ if $DEBUG && $diff;
+
+ my $futz = 0;
+ while ( $diff != 0 && $futz < scalar(@equi_apply) ) {
+ if ( $diff > 0 ) {
+ $equi_apply[$futz++]->[1] += .01;
+ $diff -= 1;
+ } elsif ( $diff < 0 ) {
+ $equi_apply[$futz++]->[1] -= .01;
+ $diff += 1;
+ } else {
+ die "guru exception #5 (in fortran tongue the answer)";
+ }
+ }
+
+ if ( sprintf('%.0f', $diff ) ) {
+ return "couldn't futz with pennies enough: still $diff left";
+ }
+
+ if ( $DEBUG ) {
+ warn "$me applying ". $_->[1].
+ " to line item (cust_bill_pkg ". $_->[0]->billpkgnum. ")\n"
+ foreach @equi_apply;
+ }
+ push @apply, @equi_apply;
+
+ }
+
+ #$remaining_amount -= $applytotal;
+ last unless $remaining_amount;
+
+ }
+
+ }
+
+ # break down lineitem amounts for tax lines
+ # could expand @open above, instead, for a slightly different magic effect
+ my @result = ();
+ foreach my $apply ( @apply ) {
+ my @sub_lines = $apply->[0]->cust_bill_pkg_tax_Xlocation;
+ my $amount = $apply->[1];
+ warn "applying ". $apply->[1]. " to ". $apply->[0]->desc
+ if $DEBUG;
+
+ foreach my $subline ( @sub_lines ) {
+ my $owed = $subline->owed;
+ push @result, [ $apply->[0],
+ sprintf('%.2f', min($amount, $owed) ),
+ { $subline->primary_key => $subline->get($subline->primary_key) },
+ ];
+ $amount -= $owed;
+ $amount = 0 if $amount < 0;
+ last unless $amount;
+ }
+ if ( $amount > 0 ) {
+ push @result, [ $apply->[0], sprintf('%.2f', $amount), {} ];
+ }
+ }
+
+ \@result;
+
+}
+
+sub apply_to_lineitems {
+ #my $self = shift;
+ my( $self, %options ) = @_;
+
+ return '' if $skip_apply_to_lineitems_hack;
+
+ my $conf = new FS::Conf;
+
+ 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 $listref_or_error = $self->calculate_applications(%options);
+ unless (ref($listref_or_error)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $listref_or_error;
+ }
+
+ my @apply = @$listref_or_error;
+
+ # do the applicaiton(s)
+ my $table = $self->lineitem_breakdown_table;
+ my $source_key = dbdef->table($self->table)->primary_key;
+ my $applied = 0;
+ foreach my $apply ( @apply ) {
+ my ( $cust_bill_pkg, $amount, $taxcreditref ) = @$apply;
+ $applied += $amount;
+ my $application = "FS::$table"->new( {
+ $source_key => $self->$source_key(),
+ 'billpkgnum' => $cust_bill_pkg->billpkgnum,
+ 'amount' => sprintf('%.2f', $amount),
+ 'setuprecur' => ( $cust_bill_pkg->setup > 0 ? 'setup' : 'recur' ),
+ 'sdate' => $cust_bill_pkg->sdate,
+ 'edate' => $cust_bill_pkg->edate,
+ %$taxcreditref,
+ });
+ my $error = $application->insert(%options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ # trigger export_insert_on_payment
+ if ( $conf->exists('trigger_export_insert_on_payment')
+ && $cust_bill_pkg->pkgnum > 0 )
+ {
+ if ( my $cust_pkg = $cust_bill_pkg->cust_pkg ) {
+
+ foreach my $cust_svc ( $cust_pkg->cust_svc ) {
+ my $svc_x = $cust_svc->svc_x;
+ my @part_export = grep { $_->can('export_insert_on_payment') }
+ $cust_svc->part_svc->part_export;
+
+ foreach my $part_export ( $cust_svc->part_svc->part_export ) {
+ $error = $part_export->_export_insert_on_payment($svc_x);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+ }
+ }
+ # done trigger export_insert_on_payment
+
+ }
+
+ #everything should always be applied to line items in full now... sanity check
+ $applied = sprintf('%.2f', $applied);
+ unless ( $applied == $self->amount ) {
+ $dbh->rollback if $oldAutoCommit;
+ return 'Error applying '. $self->_app_source_name. ' of $'. $self->amount.
+ ' to line items - only $'. $applied. ' was applied.';
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item lineitem_applications
+
+Returns all the specific line item applications for this invoice application.
+
+=cut
+
+sub lineitem_applications {
+ my $self = shift;
+ my $primary_key = dbdef->table($self->table)->primary_key;
+ qsearch({
+ 'table' => $self->lineitem_breakdown_table,
+ 'hashref' => { $primary_key => $self->$primary_key() },
+ });
+
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>)
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=item applied_to_invoice
+
+Returns a string representing the invoice (see L<FS::cust_bill>), for example:
+"applied to Invoice #54 (3/20/2008)"
+
+=cut
+
+sub applied_to_invoice {
+ my $self = shift;
+ 'applied to '. $self->cust_bill->invnum_date_pretty;
+}
+
+=item lineitem_breakdown_table
+
+=cut
+
+sub lineitem_breakdown_table {
+ my $self = shift;
+ $self->_load_table($self->_app_lineitem_breakdown_table);
+}
+
+sub _load_table {
+ my( $self, $table ) = @_;
+ eval "use FS::$table";
+ die $@ if $@;
+ $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill_pay> and L<FS::cust_bill_pay_pkg>,
+L<FS::cust_credit_bill> and L<FS::cust_credit_bill_pkg>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_event.pm b/FS/FS/cust_bill_event.pm
new file mode 100644
index 0000000..36afed0
--- /dev/null
+++ b/FS/FS/cust_bill_event.pm
@@ -0,0 +1,380 @@
+package FS::cust_bill_event;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_Mixin;
+use FS::cust_bill;
+use FS::part_bill_event;
+
+@ISA = qw(FS::cust_main_Mixin FS::Record);
+
+$DEBUG = 0;
+
+=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'; }
+
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum.
+ ' (cust_bill.invnum '. $self->invnum. ')';
+}
+
+=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_anything('statustext')
+ ;
+
+ return "Unknown eventpart ". $self->eventpart
+ unless my $part_bill_event =
+ qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
+
+ return "Unknown invnum ". $self->invnum
+ unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
+
+ $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);
+}
+
+=item retryable
+
+Changes the statustext of this event to B<retriable>, rendering it
+retriable (should retry be called).
+
+=cut
+
+sub retriable {
+ my $self = shift;
+ return '' unless $self->status eq 'done';
+ my $old = ref($self)->new( { $self->hash } );
+ $self->statustext('retriable');
+ $self->replace($old);
+}
+
+=item search_sql_where HASHREF
+
+Class method which returns an SQL WHERE fragment to search for parameters
+specified in HASHREF. Valid parameters are
+
+=over 4
+
+=item agentnum
+
+=item beginning
+
+An epoch date setting a lower bound for _date values
+
+=item ending
+
+An epoch date setting a upper bound for _date values
+
+=item failed
+
+Limits the search to failed events if true
+
+=item payby
+
+Requires that the search be JOIN'd to part_bill_event # Bug?
+
+=item invnum
+
+=item currentuser
+
+Specifies the user for agent virtualization
+
+=back
+
+=cut
+
+sub search_sql_where {
+ my ($class, $params) = @_;
+ my @search = ();
+
+ push @search, "agentnum = ". $params->{agentnum} if $params->{agentnum};
+
+ push @search, "cust_bill_event._date >= ". $params->{beginning}
+ if $params->{beginning};
+ push @search, "cust_bill_event._date <= ". $params->{ending}
+ if $params->{ending};
+
+ push @search, "statustext != ''",
+ "statustext IS NOT NULL",
+ "statustext != 'N/A'"
+ if $params->{failed};
+
+ push @search, "part_bill_event.payby = '". $params->{payby}. "'"
+ if $params->{payby};
+
+ push @search, "cust_bill_event.invnum = '". $params->{invnum}. "'"
+ if $params->{invnum};
+
+ my $currentuser = $params->{currentuser} || $params->{CurrentUser};
+ if ($currentuser) {
+ my $access_user = qsearchs('access_user', { username => $currentuser });
+ if ($access_user) {
+ push @search, $access_user->agentnums_sql;
+ }else{
+ push @search, "1=0";
+ }
+ }else{
+ push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+ }
+
+ join(' AND ', @search );
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item reprint
+
+=cut
+
+sub process_reprint {
+ process_re_X('print', @_);
+}
+
+=item reemail
+
+=cut
+
+sub process_reemail {
+ process_re_X('email', @_);
+}
+
+=item refax
+
+=cut
+
+sub process_refax {
+ process_re_X('fax', @_);
+}
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_re_X {
+ my( $method, $job ) = ( shift, shift );
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ re_X(
+ $method,
+ $param,
+ $job,
+ );
+
+}
+
+sub re_X {
+ my($method, $param, $job) = @_;
+
+ my $where = FS::cust_bill_event->search_sql_where($param);
+ $where = " WHERE plan LIKE 'send%'". ( $where ? " AND $where" : "" );
+
+ my $from = 'LEFT JOIN part_bill_event USING ( eventpart )'.
+ 'LEFT JOIN cust_bill USING ( invnum )'.
+ 'LEFT JOIN cust_main USING ( custnum )';
+
+ my @cust_bill_event = qsearch( 'cust_bill_event', {}, '', $where, '', $from );
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+ foreach my $cust_bill_event ( @cust_bill_event ) {
+
+ $cust_bill_event->cust_bill->$method(
+ $cust_bill_event->part_bill_event->templatename
+ );
+
+ if ( $job ) { #progressbar foo
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / scalar(@cust_bill_event) )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ }
+
+ #this doesn't work, but it would be nice
+ #if ( $job ) { #progressbar foo
+ # my $error = $job->update_statustext(
+ # scalar(@cust_bill_event). " invoices re-${method}ed"
+ # );
+ # die $error if $error;
+ #}
+
+}
+
+=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..831d7f2
--- /dev/null
+++ b/FS/FS/cust_bill_pay.pm
@@ -0,0 +1,186 @@
+package FS::cust_bill_pay;
+
+use strict;
+use vars qw( @ISA $conf );
+use FS::Record qw( qsearchs );
+use FS::cust_main_Mixin;
+use FS::cust_bill_ApplicationCommon;
+use FS::cust_bill;
+use FS::cust_pay;
+use FS::cust_pkg;
+
+@ISA = qw( FS::cust_main_Mixin FS::cust_bill_ApplicationCommon );
+
+#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::cust_bill_ApplicationCommon and 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'; }
+
+sub _app_source_name { 'payment'; }
+sub _app_source_table { 'cust_pay'; }
+sub _app_lineitem_breakdown_table { 'cust_bill_pay_pkg'; }
+sub _app_part_pkg_weight_column { 'pay_weight'; }
+
+=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;
+ return "Can't delete application for closed invoice"
+ if $self->cust_bill->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub replace {
+ return "Can't modify application of payment!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid payment 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('billpaynum')
+ || $self->ut_foreign_key('paynum', 'cust_pay', 'paynum' )
+ || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ $self->_date(time) unless $self->_date;
+
+ return "Cannot apply more than remaining value of invoice"
+ unless $self->amount <= $self->cust_bill->owed;
+
+ return "Cannot apply more than remaining value of payment"
+ unless $self->amount <= $self->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 send_receipt HASHREF | OPTION => VALUE ...
+
+
+Sends a payment receipt for the associated payment, against this specific
+invoice. If there is an error, returns the error, otherwise returns false.
+
+See L<FS::cust_pay/send_receipt>.
+
+=cut
+
+sub send_receipt {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+ $self->cust_pay->send_receipt(
+ 'cust_bill' => $self->cust_bill,
+ %$opt,
+ );
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods.
+
+=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_pay_batch.pm b/FS/FS/cust_bill_pay_batch.pm
new file mode 100644
index 0000000..30fb744
--- /dev/null
+++ b/FS/FS/cust_bill_pay_batch.pm
@@ -0,0 +1,120 @@
+package FS::cust_bill_pay_batch;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pay_batch - Object methods for cust_bill_pay_batch records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pay_batch;
+
+ $record = new FS::cust_bill_pay_batch \%hash;
+ $record = new FS::cust_bill_pay_batch { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pay_batch object represents a relationship between a
+customer's bill and a batch. FS::cust_bill_pay_batch inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item billpaynum - primary key
+
+=item invnum - customer's bill (invoice)
+
+=item paybatchnum - entry in cust_pay_batch table
+
+=item amount -
+
+=item _date -
+
+
+=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_bill_pay_batch'; }
+
+=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 example. 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('billpaynum')
+ || $self->ut_number('invnum')
+ || $self->ut_number('paybatchnum')
+ || $self->ut_money('amount')
+ || $self->ut_numbern('_date')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Just hangs there.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm
new file mode 100644
index 0000000..eb2e80c
--- /dev/null
+++ b/FS/FS/cust_bill_pay_pkg.pm
@@ -0,0 +1,224 @@
+package FS::cust_bill_pay_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pay;
+use FS::cust_bill_pkg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pay_pkg;
+
+ $record = new FS::cust_bill_pay_pkg \%hash;
+ $record = new FS::cust_bill_pay_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pay_pkg object represents application of a payment (see
+L<FS::cust_bill_pay>) to a specific line item within an invoice (see
+L<FS::cust_bill_pkg>). FS::cust_bill_pay_pkg inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item billpaypkgnum - primary key
+
+=item billpaynum - Payment application to the overall invoice (see L<FS::cust_bill_pay>)
+
+=item billpkgnum - Line item to which payment is applied (see L<FS::cust_bill_pkg>)
+
+=item amount - Amount of the payment applied to this line item.
+
+=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
+
+=item sdate - starting date of recurring fee
+
+=item edate - ending date of recurring fee
+
+=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 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 { 'cust_bill_pay_pkg'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my($self, %options) = @_;
+
+ #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 inserting $self: $error";
+ }
+
+ #payment receipt
+ my $conf = new FS::Conf;
+ my $trigger = $conf->config('payment_receipt-trigger') || 'cust_pay';
+ if ( $trigger eq 'cust_bill_pay_pkg' ) {
+ my $error = $self->send_receipt(
+ 'manual' => $options{'manual'},
+ );
+ warn "can't send payment receipt/statement: $error" if $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 payment application. 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('billpaypkgnum')
+ || $self->ut_foreign_key('billpaynum', 'cust_bill_pay', 'billpaynum' )
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_keyn('billpkgtaxlocationnum',
+ 'cust_bill_pkg_tax_location',
+ 'billpkgtaxlocationnum')
+ || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
+ 'cust_bill_pkg_tax_rate_location',
+ 'billpkgtaxratelocationnum')
+ || $self->ut_money('amount')
+ || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+ || $self->ut_numbern('sdate')
+ || $self->ut_numbern('edate')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pay
+
+Returns the FS::cust_bill_pay object (payment application to the overall
+invoice).
+
+=cut
+
+sub cust_bill_pay {
+ my $self = shift;
+ qsearchs('cust_bill_pay', { 'billpaynum' => $self->billpaynum } );
+}
+
+=item cust_bill_pkg
+
+Returns the FS::cust_bill_pkg object (line item to which payment is applied).
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
+}
+
+=item send_receipt
+
+Sends a payment receipt for the associated payment, against this specific
+invoice and packages. If there is an error, returns the error, otherwise
+returns false.
+
+=cut
+
+sub send_receipt {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+ $self->cust_bill_pay->send_receipt(
+ 'cust_pkg' => scalar($self->cust_bill_pkg->cust_pkg),
+ %$opt,
+ );
+}
+
+
+=back
+
+=head1 BUGS
+
+B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
+setup and recur fields. It should be removed once that's fixed.
+
+=head1 SEE ALSO
+
+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..c825c15
--- /dev/null
+++ b/FS/FS/cust_bill_pkg.pm
@@ -0,0 +1,873 @@
+package FS::cust_bill_pkg;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use Carp;
+use FS::Record qw( qsearch qsearchs dbdef dbh );
+use FS::cust_main_Mixin;
+use FS::cust_pkg;
+use FS::part_pkg;
+use FS::cust_bill;
+use FS::cust_bill_pkg_detail;
+use FS::cust_bill_pkg_display;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
+use FS::cust_tax_exempt_pkg;
+use FS::cust_bill_pkg_tax_location;
+use FS::cust_bill_pkg_tax_rate_location;
+use FS::cust_tax_adjustment;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::cust_bill_pkg]';
+
+=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 = $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 billpkgnum
+
+primary key
+
+=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, or -1 for the virtual line item (itemdesc is used for the line)
+
+=item pkgpart_override
+
+optional package definition (see L<FS::part_pkg>) override
+
+=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 (overrides normal package description)
+
+=item quantity
+
+If not set, defaults to 1
+
+=item unitsetup
+
+If not set, defaults to setup
+
+=item unitrecur
+
+If not set, defaults to recur
+
+=item hidden
+
+If set to Y, indicates data should not appear as separate line item on invoice
+
+=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;
+ }
+
+ if ( $self->get('details') ) {
+ foreach my $detail ( @{$self->get('details')} ) {
+ my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
+ 'billpkgnum' => $self->billpkgnum,
+ 'format' => (ref($detail) ? $detail->[0] : '' ),
+ 'detail' => (ref($detail) ? $detail->[1] : $detail ),
+ 'amount' => (ref($detail) ? $detail->[2] : '' ),
+ 'classnum' => (ref($detail) ? $detail->[3] : '' ),
+ 'phonenum' => (ref($detail) ? $detail->[4] : '' ),
+ 'duration' => (ref($detail) ? $detail->[5] : '' ),
+ 'regionname' => (ref($detail) ? $detail->[6] : '' ),
+ };
+ $error = $cust_bill_pkg_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_bill_pkg_detail: $error";
+ }
+ }
+ }
+
+ if ( $self->get('display') ) {
+ foreach my $cust_bill_pkg_display ( @{ $self->get('display') } ) {
+ $cust_bill_pkg_display->billpkgnum($self->billpkgnum);
+ $error = $cust_bill_pkg_display->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_bill_pkg_display: $error";
+ }
+ }
+ }
+
+ if ( $self->get('discounts') ) {
+ foreach my $cust_bill_pkg_discount ( @{$self->get('discounts')} ) {
+ $cust_bill_pkg_discount->billpkgnum($self->billpkgnum);
+ $error = $cust_bill_pkg_discount->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_bill_pkg_discount: $error";
+ }
+ }
+ }
+
+ if ( $self->_cust_tax_exempt_pkg ) {
+ foreach my $cust_tax_exempt_pkg ( @{$self->_cust_tax_exempt_pkg} ) {
+ $cust_tax_exempt_pkg->billpkgnum($self->billpkgnum);
+ $error = $cust_tax_exempt_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_tax_exempt_pkg: $error";
+ }
+ }
+ }
+
+ my $tax_location = $self->get('cust_bill_pkg_tax_location');
+ if ( $tax_location ) {
+ foreach my $cust_bill_pkg_tax_location ( @$tax_location ) {
+ $cust_bill_pkg_tax_location->billpkgnum($self->billpkgnum);
+ $error = $cust_bill_pkg_tax_location->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_bill_pkg_tax_location: $error";
+ }
+ }
+ }
+
+ my $tax_rate_location = $self->get('cust_bill_pkg_tax_rate_location');
+ if ( $tax_rate_location ) {
+ foreach my $cust_bill_pkg_tax_rate_location ( @$tax_rate_location ) {
+ $cust_bill_pkg_tax_rate_location->billpkgnum($self->billpkgnum);
+ $error = $cust_bill_pkg_tax_rate_location->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_bill_pkg_tax_rate_location: $error";
+ }
+ }
+ }
+
+ my $cust_tax_adjustment = $self->get('cust_tax_adjustment');
+ if ( $cust_tax_adjustment ) {
+ $cust_tax_adjustment->billpkgnum($self->billpkgnum);
+ $error = $cust_tax_adjustment->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error replacing cust_tax_adjustment: $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item delete
+
+Not recommended.
+
+=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;
+
+ foreach my $table (qw(
+ cust_bill_pkg_detail
+ cust_bill_pkg_display
+ cust_bill_pkg_tax_location
+ cust_bill_pkg_tax_rate_location
+ cust_tax_exempt_pkg
+ cust_bill_pay_pkg
+ cust_credit_bill_pkg
+ )) {
+
+ foreach my $linked ( qsearch($table, { billpkgnum=>$self->billpkgnum }) ) {
+ my $error = $linked->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ foreach my $cust_tax_adjustment (
+ qsearch('cust_tax_adjustment', { billpkgnum=>$self->billpkgnum })
+ ) {
+ $cust_tax_adjustment->billpkgnum(''); #NULL
+ my $error = $cust_tax_adjustment->replace;
+ 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;
+
+ '';
+
+}
+
+#alas, bin/follow-tax-rename
+#
+#=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_numbern('billpkgnum')
+ || $self->ut_snumber('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')
+ || $self->ut_textn('itemcomment')
+ || $self->ut_enum('hidden', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ #if ( $self->pkgnum != 0 ) { #allow unchecked pkgnum 0 for tax! (add to part_pkg?)
+ if ( $self->pkgnum > 0 ) { #allow -1 for non-pkg line items and 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;
+ carp "$me $self -> cust_pkg" if $DEBUG;
+ qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item part_pkg
+
+Returns the package definition for this invoice line item.
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ if ( $self->pkgpart_override ) {
+ qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } );
+ } else {
+ my $part_pkg;
+ my $cust_pkg = $self->cust_pkg;
+ $part_pkg = $cust_pkg->part_pkg if $cust_pkg;
+ $part_pkg;
+ }
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>) for this invoice line item.
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=item previous_cust_bill_pkg
+
+Returns the previous cust_bill_pkg for this package, if any.
+
+=cut
+
+sub previous_cust_bill_pkg {
+ my $self = shift;
+ return unless $self->sdate;
+ qsearchs({
+ 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'pkgnum' => $self->pkgnum,
+ 'sdate' => { op=>'<', value=>$self->sdate },
+ },
+ 'order_by' => 'ORDER BY sdate DESC LIMIT 1',
+ });
+}
+
+=item details [ OPTION => VALUE ... ]
+
+Returns an array of detail information for the invoice line item.
+
+Currently available options are: I<format> I<escape_function>
+
+If I<format> is set to html or latex then the array members are improved
+for tabular appearance in those environments if possible.
+
+If I<escape_function> is set then the array members are processed by this
+function before being returned.
+
+=cut
+
+sub details {
+ my ( $self, %opt ) = @_;
+ my $format = $opt{format} || '';
+ my $escape_function = $opt{escape_function} || sub { shift };
+ return () unless defined dbdef->table('cust_bill_pkg_detail');
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ my $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ join(' - ', map { &$escape_function($_) }
+ $csv->fields
+ );
+ };
+
+ $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ join('</TD><TD>', map { &$escape_function($_) }
+ $csv->fields
+ );
+ }
+ if $format eq 'html';
+
+ $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ #join(' & ', map { '\small{'. &$escape_function($_). '}' }
+ # $csv->fields );
+ my $result = '';
+ my $column = 1;
+ foreach ($csv->fields) {
+ $result .= ' & ' if $column > 1;
+ if ($column > 6) { # KLUDGE ALERT!
+ $result .= '\multicolumn{1}{l}{\scriptsize{'.
+ &$escape_function($_). '}}';
+ }else{
+ $result .= '\scriptsize{'. &$escape_function($_). '}';
+ }
+ $column++;
+ }
+ $result;
+ }
+ if $format eq 'latex';
+
+ $format_sub = $opt{format_function} if $opt{format_function};
+
+ map { ( $_->format eq 'C'
+ ? &{$format_sub}( $_->detail, $_ )
+ : &{$escape_function}( $_->detail )
+ )
+ }
+ qsearch ({ 'table' => 'cust_bill_pkg_detail',
+ 'hashref' => { 'billpkgnum' => $self->billpkgnum },
+ 'order_by' => 'ORDER BY detailnum',
+ });
+ #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
+}
+
+=item desc
+
+Returns a description for this line item. For typical line items, this is the
+I<pkg> field of the corresponding B<FS::part_pkg> object (see L<FS::part_pkg>).
+For one-shot line items and named taxes, it is the I<itemdesc> field of this
+line item, and for generic taxes, simply returns "Tax".
+
+=cut
+
+sub desc {
+ my $self = shift;
+
+ if ( $self->pkgnum > 0 ) {
+ $self->itemdesc || $self->part_pkg->pkg;
+ } else {
+ my $desc = $self->itemdesc || 'Tax';
+ $desc .= ' '. $self->itemcomment if $self->itemcomment =~ /\S/;
+ $desc;
+ }
+}
+
+=item owed_setup
+
+Returns the amount owed (still outstanding) on this line item's setup fee,
+which is the amount of the line item minus all payment applications (see
+L<FS::cust_bill_pay_pkg> and credit applications (see
+L<FS::cust_credit_bill_pkg>).
+
+=cut
+
+sub owed_setup {
+ my $self = shift;
+ $self->owed('setup', @_);
+}
+
+=item owed_recur
+
+Returns the amount owed (still outstanding) on this line item's recurring fee,
+which is the amount of the line item minus all payment applications (see
+L<FS::cust_bill_pay_pkg> and credit applications (see
+L<FS::cust_credit_bill_pkg>).
+
+=cut
+
+sub owed_recur {
+ my $self = shift;
+ $self->owed('recur', @_);
+}
+
+# modeled after cust_bill::owed...
+sub owed {
+ my( $self, $field ) = @_;
+ my $balance = $self->$field();
+ $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg($field) );
+ $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) );
+ $balance = sprintf( '%.2f', $balance );
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+#modeled after owed
+sub payable {
+ my( $self, $field ) = @_;
+ my $balance = $self->$field();
+ $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg($field) );
+ $balance = sprintf( '%.2f', $balance );
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+sub cust_bill_pay_pkg {
+ my( $self, $field ) = @_;
+ qsearch( 'cust_bill_pay_pkg', { 'billpkgnum' => $self->billpkgnum,
+ 'setuprecur' => $field,
+ }
+ );
+}
+
+sub cust_credit_bill_pkg {
+ my( $self, $field ) = @_;
+ qsearch( 'cust_credit_bill_pkg', { 'billpkgnum' => $self->billpkgnum,
+ 'setuprecur' => $field,
+ }
+ );
+}
+
+=item units
+
+Returns the number of billing units (for tax purposes) represented by this,
+line item.
+
+=cut
+
+sub units {
+ my $self = shift;
+ $self->pkgnum ? $self->part_pkg->calc_units($self->cust_pkg) : 0; # 1?
+}
+
+=item quantity
+
+=cut
+
+sub quantity {
+ my( $self, $value ) = @_;
+ if ( defined($value) ) {
+ $self->setfield('quantity', $value);
+ }
+ $self->getfield('quantity') || 1;
+}
+
+=item unitsetup
+
+=cut
+
+sub unitsetup {
+ my( $self, $value ) = @_;
+ if ( defined($value) ) {
+ $self->setfield('unitsetup', $value);
+ }
+ $self->getfield('unitsetup') eq ''
+ ? $self->getfield('setup')
+ : $self->getfield('unitsetup');
+}
+
+=item unitrecur
+
+=cut
+
+sub unitrecur {
+ my( $self, $value ) = @_;
+ if ( defined($value) ) {
+ $self->setfield('unitrecur', $value);
+ }
+ $self->getfield('unitrecur') eq ''
+ ? $self->getfield('recur')
+ : $self->getfield('unitrecur');
+}
+
+=item disintegrate
+
+Returns a list of cust_bill_pkg objects each with no more than a single class
+(including setup or recur) of charge.
+
+=cut
+
+sub disintegrate {
+ my $self = shift;
+ # XXX this goes away with cust_bill_pkg refactor
+
+ my $cust_bill_pkg = new FS::cust_bill_pkg { $self->hash };
+ my %cust_bill_pkg = ();
+
+ $cust_bill_pkg{setup} = $cust_bill_pkg if $cust_bill_pkg->setup;
+ $cust_bill_pkg{recur} = $cust_bill_pkg if $cust_bill_pkg->recur;
+
+
+ #split setup and recur
+ if ($cust_bill_pkg->setup && $cust_bill_pkg->recur) {
+ my $cust_bill_pkg_recur = new FS::cust_bill_pkg { $cust_bill_pkg->hash };
+ $cust_bill_pkg->set('details', []);
+ $cust_bill_pkg->recur(0);
+ $cust_bill_pkg->unitrecur(0);
+ $cust_bill_pkg->type('');
+ $cust_bill_pkg_recur->setup(0);
+ $cust_bill_pkg_recur->unitsetup(0);
+ $cust_bill_pkg{recur} = $cust_bill_pkg_recur;
+
+ }
+
+ #split usage from recur
+ my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage )
+ if exists($cust_bill_pkg{recur});
+ warn "usage is $usage\n" if $DEBUG > 1;
+ if ($usage) {
+ my $cust_bill_pkg_usage =
+ new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash };
+ $cust_bill_pkg_usage->recur( $usage );
+ $cust_bill_pkg_usage->type( 'U' );
+ my $recur = sprintf( "%.2f", $cust_bill_pkg{recur}->recur - $usage );
+ $cust_bill_pkg{recur}->recur( $recur );
+ $cust_bill_pkg{recur}->type( '' );
+ $cust_bill_pkg{recur}->set('details', []);
+ $cust_bill_pkg{''} = $cust_bill_pkg_usage;
+ }
+
+ #subdivide usage by usage_class
+ if (exists($cust_bill_pkg{''})) {
+ foreach my $class (grep { $_ } $self->usage_classes) {
+ my $usage = sprintf( "%.2f", $cust_bill_pkg{''}->usage($class) );
+ my $cust_bill_pkg_usage =
+ new FS::cust_bill_pkg { $cust_bill_pkg{''}->hash };
+ $cust_bill_pkg_usage->recur( $usage );
+ $cust_bill_pkg_usage->set('details', []);
+ my $classless = sprintf( "%.2f", $cust_bill_pkg{''}->recur - $usage );
+ $cust_bill_pkg{''}->recur( $classless );
+ $cust_bill_pkg{$class} = $cust_bill_pkg_usage;
+ }
+ warn "Unexpected classless usage value: ". $cust_bill_pkg{''}->recur
+ if ($cust_bill_pkg{''}->recur && $cust_bill_pkg{''}->recur < 0);
+ delete $cust_bill_pkg{''}
+ unless ($cust_bill_pkg{''}->recur && $cust_bill_pkg{''}->recur > 0);
+ }
+
+# # sort setup,recur,'', and the rest numeric && return
+# my @result = map { $cust_bill_pkg{$_} }
+# sort { my $ad = ($a=~/^\d+$/); my $bd = ($b=~/^\d+$/);
+# ( $ad cmp $bd ) || ( $ad ? $a<=>$b : $b cmp $a )
+# }
+# keys %cust_bill_pkg;
+#
+# return (@result);
+
+ %cust_bill_pkg;
+}
+
+=item usage CLASSNUM
+
+Returns the amount of the charge associated with usage class CLASSNUM if
+CLASSNUM is defined. Otherwise returns the total charge associated with
+usage.
+
+=cut
+
+sub usage {
+ my( $self, $classnum ) = @_;
+ my $sum = 0;
+ my @values = ();
+
+ if ( $self->get('details') ) {
+
+ @values =
+ map { $_->[2] }
+ grep { ref($_) && ( defined($classnum) ? $_->[3] eq $classnum : 1 ) }
+ @{ $self->get('details') };
+
+ }else{
+
+ my $hashref = { 'billpkgnum' => $self->billpkgnum };
+ $hashref->{ 'classnum' } = $classnum if defined($classnum);
+ @values = map { $_->amount } qsearch('cust_bill_pkg_detail', $hashref);
+
+ }
+
+ foreach ( @values ) {
+ $sum += $_ if $_;
+ }
+ $sum;
+}
+
+=item usage_classes
+
+Returns a list of usage classnums associated with this invoice line's
+details.
+
+=cut
+
+sub usage_classes {
+ my( $self ) = @_;
+
+ if ( $self->get('details') ) {
+
+ my %seen = ();
+ foreach my $detail ( grep { ref($_) } @{$self->get('details')} ) {
+ $seen{ $detail->[3] } = 1;
+ }
+ keys %seen;
+
+ }else{
+
+ map { $_->classnum }
+ qsearch({ table => 'cust_bill_pkg_detail',
+ hashref => { billpkgnum => $self->billpkgnum },
+ select => 'DISTINCT classnum',
+ });
+
+ }
+
+}
+
+=item cust_bill_pkg_display [ type => TYPE ]
+
+Returns an array of display information for the invoice line item optionally
+limited to 'TYPE'.
+
+=cut
+
+sub cust_bill_pkg_display {
+ my ( $self, %opt ) = @_;
+
+ my $default =
+ new FS::cust_bill_pkg_display { billpkgnum =>$self->billpkgnum };
+
+ return ( $default ) unless defined dbdef->table('cust_bill_pkg_display');#hmmm
+
+ my $type = $opt{type} if exists $opt{type};
+ my @result;
+
+ if ( $self->get('display') ) {
+ @result = grep { defined($type) ? ($type eq $_->type) : 1 }
+ @{ $self->get('display') };
+ } else {
+ my $hashref = { 'billpkgnum' => $self->billpkgnum };
+ $hashref->{type} = $type if defined($type);
+
+ @result = qsearch ({ 'table' => 'cust_bill_pkg_display',
+ 'hashref' => { 'billpkgnum' => $self->billpkgnum },
+ 'order_by' => 'ORDER BY billpkgdisplaynum',
+ });
+ }
+
+ push @result, $default unless ( scalar(@result) || $type );
+
+ @result;
+
+}
+
+# reserving this name for my friends FS::{tax_rate|cust_main_county}::taxline
+# and FS::cust_main::bill
+
+sub _cust_tax_exempt_pkg {
+ my ( $self ) = @_;
+
+ $self->{Hash}->{_cust_tax_exempt_pkg} or
+ $self->{Hash}->{_cust_tax_exempt_pkg} = [];
+
+}
+
+=item cust_bill_pkg_tax_Xlocation
+
+Returns the list of associated cust_bill_pkg_tax_location and/or
+cust_bill_pkg_tax_rate_location objects
+
+=cut
+
+sub cust_bill_pkg_tax_Xlocation {
+ my $self = shift;
+
+ my %hash = ( 'billpkgnum' => $self->billpkgnum );
+
+ (
+ qsearch ( 'cust_bill_pkg_tax_location', { %hash } ),
+ qsearch ( 'cust_bill_pkg_tax_rate_location', { %hash } )
+ );
+
+}
+
+=item cust_bill_pkg_detail [ CLASSNUM ]
+
+Returns the list of associated cust_bill_pkg_detail objects
+The optional CLASSNUM argument will limit the details to the specified usage
+class.
+
+=cut
+
+sub cust_bill_pkg_detail {
+ my $self = shift;
+ my $classnum = shift || '';
+
+ my %hash = ( 'billpkgnum' => $self->billpkgnum );
+ $hash{classnum} = $classnum if $classnum;
+
+ qsearch ( 'cust_bill_pkg_detail', { %hash } ),
+
+}
+
+=back
+
+=head1 BUGS
+
+setup and recur shouldn't be separate fields. There should be one "amount"
+field and a flag to tell you if it is a setup/one-time fee or a recurring fee.
+
+A line item with both should really be two separate records (preserving
+sdate and edate for setup fees for recurring packages - that information may
+be valuable later). Invoice generation (cust_main::bill), invoice printing
+(cust_bill), tax reports (report_tax.cgi) and line item reports
+(cust_bill_pkg.cgi) would need to be updated.
+
+owed_setup and owed_recur could then be repaced by just owed, and
+cust_bill::open_cust_bill_pkg and
+cust_bill_ApplicationCommon::apply_to_lineitems could be simplified.
+
+=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..4d9ee81
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -0,0 +1,377 @@
+package FS::cust_bill_pkg_detail;
+
+use strict;
+use vars qw( @ISA $me $DEBUG %GetInfoType );
+use HTML::Entities;
+use FS::Record qw( qsearch qsearchs dbdef dbh );
+use FS::cust_bill_pkg;
+use FS::usage_class;
+use FS::Conf;
+
+@ISA = qw(FS::Record);
+$me = '[ FS::cust_bill_pkg_detail ]';
+$DEBUG = 0;
+
+=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 billpkgnum - link to cust_bill_pkg
+
+=item amount - price of this line item detail
+
+=item format - '' for straight text and 'C' for CSV in detail
+
+=item classnum - link to usage_class
+
+=item duration - granularized number of seconds for this call
+
+=item regionname -
+
+=item phonenum -
+
+=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;
+
+ my $conf = new FS::Conf;
+
+ my $phonenum = $self->phonenum;
+ my $phonenum_check_method;
+ if ( $conf->exists('svc_phone-allow_alpha_phonenum') ) {
+ $phonenum =~ s/\W//g;
+ $phonenum_check_method = 'ut_alphan';
+ } else {
+ $phonenum =~ s/\D//g;
+ $phonenum_check_method = 'ut_numbern';
+ }
+ $self->phonenum($phonenum);
+
+ $self->ut_numbern('detailnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum')
+ #|| $self->ut_moneyn('amount')
+ || $self->ut_floatn('amount')
+ || $self->ut_enum('format', [ '', 'C' ] )
+ || $self->ut_numbern('duration')
+ || $self->ut_textn('regionname')
+ || $self->ut_text('detail')
+ || $self->ut_foreign_keyn('classnum', 'usage_class', 'classnum')
+ || $self->$phonenum_check_method('phonenum')
+ || $self->SUPER::check
+ ;
+
+}
+
+=item formatted [ OPTION => VALUE ... ]
+
+Returns detail information for the invoice line item detail formatted for
+display.
+
+Currently available options are: I<format> I<escape_function>
+
+If I<format> is set to html or latex then the format is improved
+for tabular appearance in those environments if possible.
+
+If I<escape_function> is set then the format is processed by this
+function before being returned.
+
+If I<format_function> is set then the detail is handed to this callback
+for processing.
+
+=cut
+
+sub formatted {
+ my ( $self, %opt ) = @_;
+ my $format = $opt{format} || '';
+ return () unless defined dbdef->table('cust_bill_pkg_detail');
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ my $escape_function = sub { shift };
+
+ $escape_function = \&encode_entities
+ if $format eq 'html';
+
+ $escape_function =
+ sub {
+ my $value = shift;
+ $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
+ $value =~ s/([<>])/\$$1\$/g;
+ $value;
+ }
+ if $format eq 'latex';
+
+ $escape_function = $opt{escape_function} if $opt{escape_function};
+
+ my $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ join(' - ', map { &$escape_function($_) }
+ $csv->fields
+ );
+ };
+
+ $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ join('</TD><TD>', map { &$escape_function($_) }
+ $csv->fields
+ );
+ }
+ if $format eq 'html';
+
+ $format_sub = sub { my $detail = shift;
+ $csv->parse($detail) or return "can't parse $detail";
+ #join(' & ', map { '\small{'. &$escape_function($_). '}' } # $csv->fields );
+ my $result = '';
+ my $column = 1;
+ foreach ($csv->fields) {
+ $result .= ' & ' if $column > 1;
+ if ($column > 6) { # KLUDGE ALERT!
+ $result .= '\multicolumn{1}{l}{\scriptsize{'.
+ &$escape_function($_). '}}';
+ }else{
+ $result .= '\scriptsize{'. &$escape_function($_). '}';
+ }
+ $column++;
+ }
+ $result;
+ }
+ if $format eq 'latex';
+
+ $format_sub = $opt{format_function} if $opt{format_function};
+
+ $self->format eq 'C'
+ ? &{$format_sub}( $self->detail, $self )
+ : &{$escape_function}( $self->detail )
+ ;
+}
+
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ my $type = dbdef->table($class->table)->column('classnum')->type;
+ unless ( $type =~ /^int/i || $type =~ /int$/i ) {
+
+ my $dbh = dbh;
+ if ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+
+ eval "use DBI::Const::GetInfoType;";
+ die $@ if $@;
+
+ my $major_version = 0;
+ $dbh->get_info( $GetInfoType{SQL_DBMS_VER} ) =~ /^(\d{2})/
+ && ( $major_version = sprintf("%d", $1) );
+
+ if ( $major_version > 7 ) {
+
+ # ideally this would be supported in DBIx-DBSchema and friends
+
+ foreach my $table ( qw( cust_bill_pkg_detail h_cust_bill_pkg_detail ) ){
+
+ warn "updating $table column classnum to integer\n" if $DEBUG;
+ my $sql = "ALTER TABLE $table ALTER classnum TYPE int USING ".
+ "int4(classnum)";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ }
+
+ } elsif ( $dbh->{pg_server_version} =~ /^704/ ) { # earlier?
+
+ # ideally this would be supported in DBIx-DBSchema and friends
+
+ # XXX_FIXME better locking
+
+ foreach my $table ( qw( cust_bill_pkg_detail h_cust_bill_pkg_detail ) ){
+
+ warn "updating $table column classnum to integer\n" if $DEBUG;
+
+ my $sql = "ALTER TABLE $table RENAME classnum TO old_classnum";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ my $def = dbdef->table($table)->column('classnum');
+ $def->type('integer');
+ $def->length('');
+ $sql = "ALTER TABLE $table ADD COLUMN ". $def->line($dbh);
+ $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ $sql = "UPDATE $table SET classnum = int4( text( old_classnum ) )";
+ $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ $sql = "ALTER TABLE $table DROP old_classnum";
+ $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ }
+
+ } else {
+
+ die "cust_bill_pkg_detail classnum upgrade unsupported for this Pg version\n";
+
+ }
+
+ } else {
+
+ die "cust_bill_pkg_detail classnum upgrade only supported for Pg 8+\n";
+
+ }
+
+ }
+
+
+ if ( defined( dbdef->table($class->table)->column('billpkgnum') ) &&
+ defined( dbdef->table($class->table)->column('invnum') ) &&
+ defined( dbdef->table($class->table)->column('pkgnum') )
+ ) {
+
+ warn "$me Checking for unmigrated invoice line item details\n" if $DEBUG;
+
+ my @cbpd = qsearch({ 'table' => $class->table,
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE invnum IS NOT NULL AND '.
+ 'pkgnum IS NOT NULL',
+ });
+
+ if (scalar(@cbpd)) {
+ warn "$me Found unmigrated invoice line item details\n" if $DEBUG;
+
+ foreach my $cbpd ( @cbpd ) {
+ my $detailnum = $cbpd->detailnum;
+ warn "$me Contemplating detail $detailnum\n" if $DEBUG > 1;
+ my $cust_bill_pkg =
+ qsearchs({ 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'invnum' => $cbpd->invnum,
+ 'pkgnum' => $cbpd->pkgnum,
+ },
+ 'order_by' => 'ORDER BY billpkgnum LIMIT 1',
+ });
+ if ($cust_bill_pkg) {
+ $cbpd->billpkgnum($cust_bill_pkg->billpkgnum);
+ $cbpd->invnum('');
+ $cbpd->pkgnum('');
+ my $error = $cbpd->replace;
+
+ warn "*** WARNING: error replacing line item detail ".
+ "(cust_bill_pkg_detail) $detailnum: $error ***\n"
+ if $error;
+ } else {
+ warn "Found orphaned line item detail $detailnum during upgrade.\n";
+ }
+
+ } # foreach $cbpd
+
+ } # if @cbpd
+
+ } # if billpkgnum, invnum, and pkgnum columns defined
+
+ '';
+
+}
+
+=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_bill_pkg_discount.pm b/FS/FS/cust_bill_pkg_discount.pm
new file mode 100644
index 0000000..e7dd5f2
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_discount.pm
@@ -0,0 +1,158 @@
+package FS::cust_bill_pkg_discount;
+
+use strict;
+use base qw( FS::cust_main_Mixin FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pkg;
+use FS::cust_pkg_discount;
+
+=head1 NAME
+
+FS::cust_bill_pkg_discount - Object methods for cust_bill_pkg_discount records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_discount;
+
+ $record = new FS::cust_bill_pkg_discount \%hash;
+ $record = new FS::cust_bill_pkg_discount { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_discount object represents the slice of a customer
+applied to a line item. FS::cust_bill_pkg_discount inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item billpkgdiscountnum
+
+primary key
+
+=item billpkgnum
+
+Line item (see L<FS::cust_bill_pkg>)
+
+=item pkgdiscountnum
+
+Customer discount (see L<FS::cust_pkg_discount>)
+
+=item amount
+
+Amount discounted from the line itme.
+
+=item months
+
+Number of months of discount this represents.
+
+=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 { 'cust_bill_pkg_discount'; }
+
+=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 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('billpkgdiscountnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_foreign_key('pkgdiscountnum', 'cust_pkg_discount', 'pkgdiscountnum' )
+ || $self->ut_money('amount')
+ || $self->ut_float('months')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pkg
+
+Returns the associated line item (see L<FS::cust_bill_pkg>).
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } ) ;
+}
+
+=item cust_pkg_discount
+
+Returns the associated customer discount (see L<FS::cust_pkg_discount>).
+
+=cut
+
+sub cust_pkg_discount {
+ my $self = shift;
+ qsearchs( 'cust_pkg_discount', { 'pkgdiscountnum' => $self->pkgdiscountnum });
+}
+
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pkg_display.pm b/FS/FS/cust_bill_pkg_display.pm
new file mode 100644
index 0000000..e9da18d
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_display.pm
@@ -0,0 +1,166 @@
+package FS::cust_bill_pkg_display;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pkg_display - Object methods for cust_bill_pkg_display records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_display;
+
+ $record = new FS::cust_bill_pkg_display \%hash;
+ $record = new FS::cust_bill_pkg_display { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_display object represents line item display information.
+FS::cust_bill_pkg_display inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item billpkgdisplaynum
+
+primary key
+
+=item billpkgnum
+
+billpkgnum
+
+=item section
+
+section
+
+=cut
+
+sub section {
+ my ( $self, $value ) = @_;
+ if ( defined($value) ) {
+ $self->setfield('section', $value);
+ } else {
+ my $section = $self->getfield('section');
+ unless ($section) {
+ my $cust_bill_pkg = $self->cust_bill_pkg;
+ if ( $cust_bill_pkg->pkgnum > 0 ) {
+ my $part_pkg = $cust_bill_pkg->part_pkg;
+ $section = $part_pkg->categoryname if $part_pkg;
+ }
+ }
+ $section;
+ }
+}
+
+=item post_total
+
+post_total
+
+=item type
+
+type
+
+=item summary
+
+summary
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new line item display object. 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_bill_pkg_display'; }
+
+=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 line item display object.
+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('billpkgdisplaynum')
+ || $self->ut_number('billpkgnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum')
+ || $self->ut_textn('section')
+ || $self->ut_enum('post_total', [ '', 'Y' ])
+ || $self->ut_enum('type', [ '', 'S', 'R', 'U' ])
+ || $self->ut_enum('summary', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pkg
+
+Returns the associated cust_bill_pkg (see L<FS::cust_bill_pkg>) for this
+line item display object.
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } ) ;
+}
+
+=back
+
+=head1 BUGS
+
+
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_bill_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pkg_tax_location.pm b/FS/FS/cust_bill_pkg_tax_location.pm
new file mode 100644
index 0000000..44dd6e3
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_tax_location.pm
@@ -0,0 +1,225 @@
+package FS::cust_bill_pkg_tax_location;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pkg;
+use FS::cust_pkg;
+use FS::cust_location;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
+use FS::cust_main_county;
+
+=head1 NAME
+
+FS::cust_bill_pkg_tax_location - Object methods for cust_bill_pkg_tax_location records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_tax_location;
+
+ $record = new FS::cust_bill_pkg_tax_location \%hash;
+ $record = new FS::cust_bill_pkg_tax_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_tax_location object represents an record of taxation
+based on package location. FS::cust_bill_pkg_tax_location inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item billpkgtaxlocationnum
+
+billpkgtaxlocationnum
+
+=item billpkgnum
+
+billpkgnum
+
+=item taxnum
+
+taxnum
+
+=item taxtype
+
+taxtype
+
+=item pkgnum
+
+pkgnum
+
+=item locationnum
+
+locationnum
+
+=item amount
+
+amount
+
+
+=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_bill_pkg_tax_location'; }
+
+=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.
+
+=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;
+
+ my $error =
+ $self->ut_numbern('billpkgtaxlocationnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_number('taxnum') #cust_bill_pkg/tax_rate key, based on taxtype
+ || $self->ut_enum('taxtype', [ qw( FS::cust_main_county FS::tax_rate ) ] )
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
+ || $self->ut_foreign_key('locationnum', 'cust_location', 'locationnum' )
+ || $self->ut_money('amount')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pkg
+
+Returns the associated cust_bill_pkg object
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
+}
+
+=item cust_location
+
+Returns the associated cust_location object
+
+=cut
+
+sub cust_location {
+ my $self = shift;
+ qsearchs( 'cust_location', { 'locationnum' => $self->locationnum } );
+}
+
+=item desc
+
+Returns a description for this tax line item constituent. Currently this
+is the desc of the associated line item followed by the state/county/city
+for the location in parentheses.
+
+=cut
+
+sub desc {
+ my $self = shift;
+ my $cust_location = $self->cust_location;
+ my $location = join('/', grep { $_ } # leave in?
+ map { $cust_location->$_ }
+ qw( state county city ) # country?
+ );
+ my $cust_bill_pkg_desc = $self->billpkgnum
+ ? $self->cust_bill_pkg->desc
+ : $self->cust_bill_pkg_desc;
+ "$cust_bill_pkg_desc ($location)";
+}
+
+=item owed
+
+Returns the amount owed (still outstanding) on this tax line item which is
+the amount of this record minus all payment applications and credit
+applications.
+
+=cut
+
+sub owed {
+ my $self = shift;
+ my $balance = $self->amount;
+ $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg('setup') );
+ $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg('setup') );
+ $balance = sprintf( '%.2f', $balance );
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+sub cust_bill_pay_pkg {
+ my $self = shift;
+ qsearch( 'cust_bill_pay_pkg',
+ { map { $_ => $self->$_ } qw( billpkgtaxlocationnum billpkgnum ) }
+ );
+}
+
+sub cust_credit_bill_pkg {
+ my $self = shift;
+ qsearch( 'cust_credit_bill_pkg',
+ { map { $_ => $self->$_ } qw( billpkgtaxlocationnum billpkgnum ) }
+ );
+}
+
+sub cust_main_county {
+ my $self = shift;
+ my $result;
+ if ( $self->taxtype eq 'FS::cust_main_county' ) {
+ $result = qsearchs( 'cust_main_county', { 'taxnum' => $self->taxnum } );
+ }
+}
+
+=back
+
+=head1 BUGS
+
+The presense of FS::cust_main_county::delete makes the cust_main_county method
+unreliable
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pkg_tax_rate_location.pm b/FS/FS/cust_bill_pkg_tax_rate_location.pm
new file mode 100644
index 0000000..39b2bb9
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_tax_rate_location.pm
@@ -0,0 +1,221 @@
+package FS::cust_bill_pkg_tax_rate_location;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pkg;
+use FS::cust_pkg;
+use FS::tax_rate_location;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
+
+=head1 NAME
+
+FS::cust_bill_pkg_tax_rate_location - Object methods for cust_bill_pkg_tax_rate_location records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_tax_rate_location;
+
+ $record = new FS::cust_bill_pkg_tax_rate_location \%hash;
+ $record = new FS::cust_bill_pkg_tax_rate_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_tax_rate_location object represents an record of taxation
+based on package location. FS::cust_bill_pkg_tax_rate_location inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item billpkgtaxratelocationnum
+
+billpkgtaxratelocationnum
+
+=item billpkgnum
+
+billpkgnum
+
+=item taxnum
+
+taxnum
+
+=item taxtype
+
+taxtype
+
+=item locationtaxid
+
+locationtaxid
+
+=item taxratelocationnum
+
+taxratelocationnum
+
+=item amount
+
+amount
+
+
+=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_bill_pkg_tax_rate_location'; }
+
+=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.
+
+=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;
+
+ my $error =
+ $self->ut_numbern('billpkgtaxratelocationnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_number('taxnum') #cust_bill_pkg/tax_rate key, based on taxtype
+ || $self->ut_enum('taxtype', [ qw( FS::cust_main_county FS::tax_rate ) ] )
+ || $self->ut_textn('locationtaxid')
+ || $self->ut_foreign_key('taxratelocationnum', 'tax_rate_location', 'taxratelocationnum' )
+ || $self->ut_money('amount')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pkg
+
+Returns the associated cust_bill_pkg object
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
+}
+
+=item tax_rate_location
+
+Returns the associated tax_rate_location object
+
+=cut
+
+sub tax_rate_location {
+ my $self = shift;
+ qsearchs( 'tax_rate_location',
+ { 'taxratelocationnum' => $self->taxratelocationnum }
+ );
+}
+
+=item desc
+
+Returns a description for this tax line item constituent. Currently this
+is the desc of the associated line item followed by the
+state,county,city,locationtaxid for the location in parentheses.
+
+=cut
+
+sub desc {
+ my $self = shift;
+ my $tax_rate_location = $self->tax_rate_location;
+ my $location = join(', ', grep { $_ }
+ map { $tax_rate_location->$_ }
+ qw( state county city )
+ );
+ $location .= ( $location && $self->locationtaxid ) ? ', ' : '';
+ $location .= $self->locationtaxid;
+ my $cust_bill_pkg_desc = $self->billpkgnum
+ ? $self->cust_bill_pkg->desc
+ : $self->cust_bill_pkg_desc;
+ "$cust_bill_pkg_desc ($location)";
+
+}
+
+
+=item owed
+
+Returns the amount owed (still outstanding) on this tax line item which is
+the amount of this record minus all payment applications and credit
+applications.
+
+=cut
+
+sub owed {
+ my $self = shift;
+ my $balance = $self->amount;
+ $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg('setup') );
+ $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg('setup') );
+ $balance = sprintf( '%.2f', $balance );
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+sub cust_bill_pay_pkg {
+ my $self = shift;
+ qsearch( 'cust_bill_pay_pkg', { map { $_ => $self->$_ }
+ qw( billpkgtaxratelocationnum billpkgnum )
+ }
+ );
+}
+
+sub cust_credit_bill_pkg {
+ my $self = shift;
+ qsearch( 'cust_credit_bill_pkg', { map { $_ => $self->$_ }
+ qw( billpkgtaxratelocationnum billpkgnum )
+ }
+ );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_category.pm b/FS/FS/cust_category.pm
new file mode 100644
index 0000000..636b1d3
--- /dev/null
+++ b/FS/FS/cust_category.pm
@@ -0,0 +1,97 @@
+package FS::cust_category;
+
+use strict;
+use base qw( FS::category_Common );
+use FS::cust_class;
+
+=head1 NAME
+
+FS::cust_category - Object methods for cust_category records
+
+=head1 SYNOPSIS
+
+ use FS::cust_category;
+
+ $record = new FS::cust_category \%hash;
+ $record = new FS::cust_category { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_category object represents a customer category. Every customer
+class (see L<FS::cust_class>) has, optionally, a customer category.
+FS::cust_category inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item categorynum
+
+primary key
+
+=item categoryname
+
+Text name of this package category
+
+=item weight
+
+Weight
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer category. To add the customer category to the database,
+see L<"insert">.
+
+=cut
+
+sub table { 'cust_category'; }
+
+=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.
+
+=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.
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_class>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_class.pm b/FS/FS/cust_class.pm
new file mode 100644
index 0000000..a811be7
--- /dev/null
+++ b/FS/FS/cust_class.pm
@@ -0,0 +1,120 @@
+package FS::cust_class;
+
+use strict;
+use base qw( FS::class_Common );
+use FS::cust_main;
+use FS::cust_category;
+
+=head1 NAME
+
+FS::cust_class - Object methods for cust_class records
+
+=head1 SYNOPSIS
+
+ use FS::cust_class;
+
+ $record = new FS::cust_class \%hash;
+ $record = new FS::cust_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pkg_class object represents an customer class. Every customer (see
+L<FS::cust_main>) has, optionally, a customer class. FS::cust_class inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+primary key
+
+=item classname
+
+Text name of this customer class
+
+=item categorynum
+
+Number of associated cust_category (see L<FS::cust_category>)
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer class. To add the customer class to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'cust_class'; }
+sub _target_table { 'cust_main'; }
+
+=item insert
+
+Adds this customer class to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Delete this customer class from the database. Only customer classes with no
+associated customers can be deleted. 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 customer class. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=item cust_category
+
+=item category
+
+Returns the cust_category record associated with this class, or false if there
+is none.
+
+=cut
+
+sub cust_category {
+ my $self = shift;
+ $self->category;
+}
+
+=item categoryname
+
+Returns the category name associated with this class, or false if there
+is none.
+
+=cut
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::Record>
+
+=cut
+
+1;
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
new file mode 100644
index 0000000..674bc10
--- /dev/null
+++ b/FS/FS/cust_credit.pm
@@ -0,0 +1,625 @@
+package FS::cust_credit;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::Record );
+use vars qw( $conf $unsuspendauto $me $DEBUG );
+use Date::Format;
+use FS::UID qw( dbh getotaker );
+use FS::Misc qw(send_email);
+use FS::Record qw( qsearch qsearchs dbdef );
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_refund;
+use FS::cust_credit_bill;
+use FS::part_pkg;
+use FS::reason_type;
+use FS::reason;
+use FS::cust_event;
+
+$me = '[ FS::cust_credit ]';
+$DEBUG = 0;
+
+#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');
+
+};
+
+our %reasontype_map = ( 'referral_credit_type' => 'Referral Credit',
+ 'cancel_credit_type' => 'Cancellation Credit',
+ 'signup_credit_type' => 'Self-Service Credit',
+ );
+
+=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 usernum
+
+Order taker (see L<FS::access_user>)
+
+=item reason
+
+Text ( deprecated )
+
+=item reasonnum
+
+Reason (see L<FS::reason>)
+
+=item addlinfo
+
+Text
+
+=item closed
+
+Books closed flag, empty or `Y'
+
+=item pkgnum
+
+Desired pkgnum when using experimental package balances.
+
+=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'; }
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum.
+ ' (cust_credit.crednum '. $self->crednum. ')';
+}
+
+=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, %options) = @_;
+
+ 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;
+
+ unless ($self->reasonnum) {
+ my $result = $self->reason( $self->getfield('reason'),
+ exists($options{ 'reason_type' })
+ ? ('reason_type' => $options{ 'reason_type' })
+ : (),
+ );
+ unless($result) {
+ $dbh->rollback if $oldAutoCommit;
+ return "failed to set reason for $me"; #: ". $dbh->errstr;
+ }
+ }
+
+ $self->setfield('reason', '');
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting $self: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ #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
+
+Unless the closed flag is set, deletes this credit and all associated
+applications (see L<FS::cust_credit_bill>). In most cases, you want to use
+the void method instead to leave a record of the deleted credit.
+
+=cut
+
+# very similar to FS::cust_pay::delete
+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;
+ }
+ }
+
+ foreach my $cust_credit_refund ( $self->cust_credit_refund ) {
+ my $error = $cust_credit_refund->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 = $self->cust_main;
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
+ #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
+
+You can, but probably shouldn't modify credits...
+
+=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;
+
+ $self->otaker(getotaker) unless ($self->otaker);
+
+ my $error =
+ $self->ut_numbern('crednum')
+ || $self->ut_number('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ || $self->ut_alphan('otaker')
+ || $self->ut_textn('reason')
+ || $self->ut_foreign_key('reasonnum', 'reason', 'reasonnum')
+ || $self->ut_textn('addlinfo')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_keyn('eventnum', 'cust_event', 'eventnum')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0 " if $self->amount <= 0;
+
+ return "amount must be greater or equal to amount applied"
+ if $self->unapplied < 0;
+
+ return "Unknown customer"
+ unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ $self->SUPER::check;
+}
+
+=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;
+ map { $_ } #return $self->num_cust_credit_refund unless wantarray;
+ 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;
+ map { $_ } #return $self->num_cust_credit_bill unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'crednum' => $self->crednum } )
+ ;
+}
+
+=item unapplied
+
+Returns the amount of this credit that is still unapplied/outstanding;
+amount minus all refund applications (see L<FS::cust_credit_refund>) and
+applications to invoices (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub unapplied {
+ 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 credited
+
+Deprecated name for the unapplied method.
+
+=cut
+
+sub credited {
+ my $self = shift;
+ #carp "cust_credit->credited deprecated; use ->unapplied";
+ $self->unapplied(@_);
+}
+
+=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 } );
+}
+
+
+=item reason
+
+Returns the text of the associated reason (see L<FS::reason>) for this credit.
+
+=cut
+
+sub reason {
+ my ($self, $value, %options) = @_;
+ my $dbh = dbh;
+ my $reason;
+ my $typenum = $options{'reason_type'};
+
+ my $oldAutoCommit = $FS::UID::AutoCommit; # this should already be in
+ local $FS::UID::AutoCommit = 0; # a transaction if it matters
+
+ if ( defined( $value ) ) {
+ my $hashref = { 'reason' => $value };
+ $hashref->{'reason_type'} = $typenum if $typenum;
+ my $addl_from = "LEFT JOIN reason_type ON ( reason_type = typenum ) ";
+ my $extra_sql = " AND reason_type.class='R'";
+
+ $reason = qsearchs( { 'table' => 'reason',
+ 'hashref' => $hashref,
+ 'addl_from' => $addl_from,
+ 'extra_sql' => $extra_sql,
+ } );
+
+ if (!$reason && $typenum) {
+ $reason = new FS::reason( { 'reason_type' => $typenum,
+ 'reason' => $value,
+ 'disabled' => 'Y',
+ } );
+ my $error = $reason->insert;
+ if ( $error ) {
+ warn "error inserting reason: $error\n";
+ $reason = undef;
+ }
+ }
+
+ $self->reasonnum($reason ? $reason->reasonnum : '') ;
+ warn "$me reason used in set mode with non-existant reason -- clearing"
+ unless $reason;
+ }
+ $reason = qsearchs( 'reason', { 'reasonnum' => $self->reasonnum } );
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ( $reason ? $reason->reason : '' ).
+ ( $self->addlinfo ? ' '.$self->addlinfo : '' );
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ if (defined dbdef->table($class->table)->column('reason')) {
+
+ warn "$me Checking for unmigrated reasons\n" if $DEBUG;
+
+ my @cust_credits = qsearch({ 'table' => $class->table,
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE reason IS NOT NULL',
+ });
+
+ if (scalar(grep { $_->getfield('reason') =~ /\S/ } @cust_credits)) {
+ warn "$me Found unmigrated reasons\n" if $DEBUG;
+ my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
+ my $reason_type = qsearchs( 'reason_type', $hashref );
+ unless ($reason_type) {
+ $reason_type = new FS::reason_type( $hashref );
+ my $error = $reason_type->insert();
+ die "$class had error inserting FS::reason_type into database: $error\n"
+ if $error;
+ }
+
+ $hashref = { 'reason_type' => $reason_type->typenum,
+ 'reason' => '(none)'
+ };
+ my $noreason = qsearchs( 'reason', $hashref );
+ unless ($noreason) {
+ $hashref->{'disabled'} = 'Y';
+ $noreason = new FS::reason( $hashref );
+ my $error = $noreason->insert();
+ die "can't insert legacy reason '(none)' into database: $error\n"
+ if $error;
+ }
+
+ foreach my $cust_credit ( @cust_credits ) {
+ my $reason = $cust_credit->getfield('reason');
+ warn "Contemplating reason $reason\n" if $DEBUG > 1;
+ if ($reason =~ /\S/) {
+ $cust_credit->reason($reason, 'reason_type' => $reason_type->typenum)
+ or die "can't insert legacy reason $reason into database\n";
+ }else{
+ $cust_credit->reasonnum($noreason->reasonnum);
+ }
+
+ $cust_credit->setfield('reason', '');
+ my $error = $cust_credit->replace;
+
+ warn "*** WARNING: error replacing reason in $class ".
+ $cust_credit->crednum. ": $error ***\n"
+ if $error;
+ }
+ }
+
+ warn "$me Ensuring existance of auto reasons\n" if $DEBUG;
+
+ foreach ( keys %reasontype_map ) {
+ unless ($conf->config($_)) { # hmmmm
+# warn "$me Found $_ reason type lacking\n" if $DEBUG;
+# my $hashref = { 'class' => 'R', 'type' => $reasontype_map{$_} };
+ my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
+ my $reason_type = qsearchs( 'reason_type', $hashref );
+ unless ($reason_type) {
+ $reason_type = new FS::reason_type( $hashref );
+ my $error = $reason_type->insert();
+ die "$class had error inserting FS::reason_type into database: $error\n"
+ if $error;
+ }
+ $conf->set($_, $reason_type->typenum);
+ }
+ }
+
+ warn "$me Ensuring commission packages have a reason type\n" if $DEBUG;
+
+ my $hashref = { 'class' => 'R', 'type' => 'Legacy' };
+ my $reason_type = qsearchs( 'reason_type', $hashref );
+ unless ($reason_type) {
+ $reason_type = new FS::reason_type( $hashref );
+ my $error = $reason_type->insert();
+ die "$class had error inserting FS::reason_type into database: $error\n"
+ if $error;
+ }
+
+ my @plans = qw( flat_comission flat_comission_cust flat_comission_pkg );
+ foreach my $plan ( @plans ) {
+ foreach my $pkg ( qsearch('part_pkg', { 'plan' => $plan } ) ) {
+ unless ($pkg->option('reason_type', 1) ) {
+ my $plandata = $pkg->plandata.
+ "reason_type=". $reason_type->typenum. "\n";
+ $pkg->plandata($plandata);
+ my $error =
+ $pkg->replace( undef,
+ 'pkg_svc' => { map { $_->svcpart => $_->quantity }
+ $pkg->pkg_svc
+ },
+ 'primary_svc' => $pkg->svcpart,
+ );
+ die "failed setting reason_type option: $error"
+ if $error;
+ }
+ }
+ }
+ }
+
+ $class->_upgrade_otaker(%opts);
+
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut
+
+sub unapplied_sql {
+ my ($class, $start, $end) = @_;
+
+ my $bill_start = $start ? "AND cust_credit_bill._date <= $start" : '';
+ my $bill_end = $end ? "AND cust_credit_bill._date > $end" : '';
+ my $refund_start = $start ? "AND cust_credit_refund._date <= $start" : '';
+ my $refund_end = $end ? "AND cust_credit_refund._date > $end" : '';
+
+ "amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum
+ $refund_start $refund_end )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum
+ $bill_start $bill_end )
+ ,0
+ )
+ ";
+
+}
+
+=item credited_sql
+
+Deprecated name for the unapplied_sql method.
+
+=cut
+
+sub credited_sql {
+ #my $class = shift;
+
+ #carp "cust_credit->credited_sql deprecated; use ->unapplied_sql";
+
+ #$class->unapplied_sql(@_);
+ unapplied_sql();
+}
+
+=back
+
+=head1 BUGS
+
+The delete method. The replace method.
+
+B<credited> and B<credited_sql> are now called B<unapplied> and
+B<unapplied_sql>. The old method names should start to give warnings.
+
+=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..900a5c0
--- /dev/null
+++ b/FS/FS/cust_credit_bill.pm
@@ -0,0 +1,170 @@
+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_Mixin;
+use FS::cust_bill_ApplicationCommon;
+use FS::cust_bill;
+use FS::cust_credit;
+use FS::cust_pkg;
+
+@ISA = qw( FS::cust_main_Mixin FS::cust_bill_ApplicationCommon );
+
+#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::cust_bill_ApplicationCommon and 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'; }
+
+sub _app_source_name { 'credit'; }
+sub _app_source_table { 'cust_credit'; }
+sub _app_lineitem_breakdown_table { 'cust_credit_bill_pkg'; }
+sub _app_part_pkg_weight_column { 'credit_weight'; }
+
+=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;
+ return "Can't delete application for closed invoice"
+ if $self->cust_bill->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_foreign_key('crednum', 'cust_credit', 'crednum')
+ || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum' )
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ $self->_date(time) unless $self->_date;
+
+ return "Cannot apply more than remaining value of credit"
+ unless $self->amount <= $self->cust_credit->credited;
+
+ return "Cannot apply more than remaining value of invoice"
+ unless $self->amount <= $self->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 } );
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+This probably should have been called cust_bill_credit.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_bill>, L<FS::cust_credit>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit_bill_pkg.pm b/FS/FS/cust_credit_bill_pkg.pm
new file mode 100644
index 0000000..158fc73
--- /dev/null
+++ b/FS/FS/cust_credit_bill_pkg.pm
@@ -0,0 +1,352 @@
+package FS::cust_credit_bill_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_main_Mixin;
+use FS::cust_credit_bill;
+use FS::cust_bill_pkg;
+use FS::cust_bill_pkg_tax_location;
+use FS::cust_bill_pkg_tax_rate_location;
+use FS::cust_tax_exempt_pkg;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+=head1 NAME
+
+FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
+
+=head1 SYNOPSIS
+
+ use FS::cust_credit_bill_pkg;
+
+ $record = new FS::cust_credit_bill_pkg \%hash;
+ $record = new FS::cust_credit_bill_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit_bill_pkg object represents application of a credit (see
+L<FS::cust_credit_bill>) to a specific line item within an invoice
+(see L<FS::cust_bill_pkg>). FS::cust_credit_bill_pkg inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item creditbillpkgnum - primary key
+
+=item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>)
+
+=item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>)
+
+=item amount - Amount of the credit applied to this line item.
+
+=item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
+
+=item sdate - starting date of recurring fee
+
+=item edate - ending date of recurring fee
+
+=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 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 { 'cust_credit_bill_pkg'; }
+
+=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;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $payable = $self->cust_bill_pkg->payable($self->setuprecur);
+ my $taxable = $self->_is_taxable ? $payable : 0;
+ my $part_pkg = $self->cust_bill_pkg->part_pkg;
+ my $freq = $part_pkg ? $part_pkg->freq || 1 : 1;# assume unchanged
+ my $taxable_per_month = sprintf("%.2f", $taxable / $freq );
+ my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
+
+ if ($taxable_per_month >= 0) { #panic if its subzero?
+ my $groupby = 'taxnum,year,month';
+ my $sum = 'SUM(amount)';
+ my @exemptions = qsearch(
+ {
+ 'select' => "$groupby, $sum AS amount",
+ 'table' => 'cust_tax_exempt_pkg',
+ 'hashref' => { billpkgnum => $self->billpkgnum },
+ 'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
+ }
+ );
+ foreach my $exemption ( @exemptions ) {
+ next if $taxable_per_month >= $exemption->amount;
+ my $amount = $exemption->amount - $taxable_per_month;
+ if ($amount > $credit_per_month) {
+ "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
+ $amount = $credit_per_month;
+ }
+ my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
+ 'billpkgnum' => $self->billpkgnum,
+ 'creditbillpkgnum' => $self->creditbillpkgnum,
+ 'amount' => 0-$amount,
+ map { $_ => $exemption->$_ } split(',', $groupby)
+ };
+ my $error = $cust_tax_exempt_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_tax_exempt_pkg: $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+#helper functions for above
+sub _is_taxable {
+ my $self = shift;
+ my $part_pkg = $self->cust_bill_pkg->part_pkg;
+
+ return 0 unless $part_pkg; #XXX fails for tax on tax
+
+ my $method = $self->setuprecur. 'tax';
+ return 0 if $part_pkg->$method =~ /^Y$/i;
+
+ if ($self->billpkgtaxlocationnum) {
+ my $location_object = $self->cust_bill_pkg_tax_Xlocation;
+ my $tax_object = $location_object->cust_main_county;
+ return 0 if $tax_object && $self->tax_object->$method =~ /^Y$/i;
+ } #elsif ($self->billpkgtaxratelocationnum) { ... }
+
+ 1;
+}
+
+=item delete
+
+Delete this record from the database.
+
+=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 $original_cust_bill_pkg = $self->cust_bill_pkg;
+ my $cust_bill = $original_cust_bill_pkg->cust_bill;
+
+ my %hash = $original_cust_bill_pkg->hash;
+ delete $hash{$_} for qw( billpkgnum setup recur );
+ $hash{$self->setuprecur} = $self->amount;
+ my $cust_bill_pkg = new FS::cust_bill_pkg { %hash };
+
+ use Data::Dumper;
+ my @exemptions = qsearch( 'cust_tax_exempt_pkg',
+ { creditbillpkgnum => $self->creditbillpkgnum }
+ );
+ my %seen = ();
+ my @generated_exemptions = ();
+ my @unseen_exemptions = ();
+ foreach my $exemption ( @exemptions ) {
+ my $error = $exemption->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error deleting cust_tax_exempt_pkg: $error";
+ }
+
+ next if $seen{$exemption->taxnum};
+ $seen{$exemption->taxnum} = 1;
+ push @unseen_exemptions, $exemption;
+ }
+
+ foreach my $exemption ( @unseen_exemptions ) {
+ my $tax_object = $exemption->cust_main_county;
+ unless ($tax_object) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find exempted tax";
+ }
+
+ my $hashref_or_error =
+ $tax_object->taxline( [ $cust_bill_pkg ],
+ 'custnum' => $cust_bill->custnum,
+ 'invoice_time' => $cust_bill->_date,
+ );
+ unless (ref($hashref_or_error)) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error calculating taxes: $hashref_or_error";
+ }
+
+ push @generated_exemptions, @{ $cust_bill_pkg->_cust_tax_exempt_pkg || [] };
+ }
+
+ foreach my $taxnum ( keys %seen ) {
+ my $sum = 0;
+ $sum += $_->amount for grep {$_->taxnum == $taxnum} @exemptions;
+ $sum -= $_->amount for grep {$_->taxnum == $taxnum} @generated_exemptions;
+ $sum = sprintf("%.2f", $sum);
+ unless ($sum eq '0.00' || $sum eq '-0.00') {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't unapply credit without charging tax";
+ }
+ }
+
+ 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 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 credit applicaiton. 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('creditbillpkgnum')
+ || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_foreign_keyn('billpkgtaxlocationnum',
+ 'cust_bill_pkg_tax_location',
+ 'billpkgtaxlocationnum')
+ || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
+ 'cust_bill_pkg_tax_rate_location',
+ 'billpkgtaxratelocationnum')
+ || $self->ut_money('amount')
+ || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+ || $self->ut_numbern('sdate')
+ || $self->ut_numbern('edate')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+sub cust_credit_bill {
+ my $self = shift;
+ qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
+}
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
+}
+
+sub cust_bill_pkg_tax_Xlocation {
+ my $self = shift;
+ if ($self->billpkg_tax_locationnum) {
+ return qsearchs(
+ 'cust_bill_pkg_tax_location',
+ { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
+ );
+
+ } elsif ($self->billpkg_tax_rate_locationnum) {
+ return qsearchs(
+ 'cust_bill_pkg_tax_rate_location',
+ { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
+ );
+ } else {
+ return undef;
+ }
+}
+
+=back
+
+=head1 BUGS
+
+B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
+setup and recur fields. It should be removed once that's fixed.
+
+B<insert> method assumes that the frequency of the package associated with the
+associated line item remains unchanged during the lifetime of the system.
+It may get the tax exemption adjustments wrong if package definitions change
+frequency. The presense of delete methods in FS::cust_main_county and
+FS::tax_rate makes crediting of old "texas tax" unreliable in the presense of
+changing taxes. Explicit tax credit requests? Carry 'taxable' onto line
+items?
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..9fc03f2
--- /dev/null
+++ b/FS/FS/cust_credit_refund.pm
@@ -0,0 +1,186 @@
+package FS::cust_credit_refund;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_main_Mixin;
+use FS::cust_credit;
+use FS::cust_refund;
+
+@ISA = qw( FS::cust_main_Mixin 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;
+ return "Can't apply refund to closed credit"
+ if $self->cust_credit->closed =~ /^Y/i;
+ return "Can't apply credit to closed refund"
+ if $self->cust_refund->closed =~ /^Y/i;
+ $self->SUPER::insert(@_);
+}
+
+=item delete
+
+Remove this cust_credit_refund from the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't remove refund from closed credit"
+ if $self->cust_credit->closed =~ /^Y/i;
+ return "Can't remove credit from closed refund"
+ if $self->cust_refund->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=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 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_event.pm b/FS/FS/cust_event.pm
new file mode 100644
index 0000000..52b5911
--- /dev/null
+++ b/FS/FS/cust_event.pm
@@ -0,0 +1,511 @@
+package FS::cust_event;
+
+use strict;
+use base qw( FS::cust_main_Mixin FS::Record );
+use vars qw( @ISA $DEBUG $me );
+use Carp qw( croak confess );
+use FS::Record qw( qsearch qsearchs dbdef );
+use FS::part_event;
+#for cust_X
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_bill;
+
+$DEBUG = 0;
+$me = '[FS::cust_event]';
+
+=head1 NAME
+
+FS::cust_event - Object methods for cust_event records
+
+=head1 SYNOPSIS
+
+ use FS::cust_event;
+
+ $record = new FS::cust_event \%hash;
+ $record = new FS::cust_event { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_event object represents an completed event. FS::cust_event
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item eventnum - primary key
+
+=item eventpart - event definition (see L<FS::part_event>)
+
+=item tablenum - customer, package or invoice, depending on the value of part_event.eventtable (see L<FS::cust_main>, L<FS::cust_pkg>, and L<FS::cust_bill>)
+
+=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<new>, B<locked>, B<done> or B<failed>. Note: B<done> indicates the event is complete and should not be retried (statustext may still be set to an optional message), while B<failed> indicates the event failed and should be retried.
+
+=item statustext - additional status detail (i.e. error or progress 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_event'; }
+
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum;
+ #' (cust_bill.invnum '. $self->invnum. ')';
+}
+sub custnum {
+ my $self = shift;
+ $self->cust_main_custnum(@_) || $self->SUPER::custnum(@_);
+}
+
+=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_foreign_key('eventpart', 'part_event', 'eventpart')
+ ;
+ return $error if $error;
+
+ my $eventtable = $self->part_event->eventtable;
+ my $dbdef_eventtable = dbdef->table( $eventtable );
+
+ $error =
+ $self->ut_foreign_key( 'tablenum',
+ $eventtable,
+ $dbdef_eventtable->primary_key
+ )
+ || $self->ut_number('_date')
+ || $self->ut_enum('status', [qw( new locked done failed )])
+ || $self->ut_anything('statustext')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item part_event
+
+Returns the event definition (see L<FS::part_event>) for this completed event.
+
+=cut
+
+sub part_event {
+ my $self = shift;
+ qsearchs( 'part_event', { 'eventpart' => $self->eventpart } );
+}
+
+=item cust_X
+
+Returns the customer, package, invoice or batched payment (see
+L<FS::cust_main>, L<FS::cust_pkg>, L<FS::cust_bill> or L<FS::cust_pay_batch>)
+for this completed invoice event.
+
+=cut
+
+sub cust_bill {
+ croak "FS::cust_event::cust_bill called";
+}
+
+sub cust_X {
+ my $self = shift;
+ my $eventtable = $self->part_event->eventtable;
+ my $dbdef_table = dbdef->table( $eventtable );
+ my $primary_key = $dbdef_table->primary_key;
+ qsearchs( $eventtable, { $primary_key => $self->tablenum } );
+}
+
+=item test_conditions [ OPTION => VALUE ... ]
+
+Tests conditions for this event, returns true if all conditions are satisfied,
+false otherwise.
+
+=cut
+
+sub test_conditions {
+ my( $self, %opt ) = @_;
+ my $part_event = $self->part_event;
+ my $object = $self->cust_X;
+ my @conditions = $part_event->part_event_condition;
+ $opt{'cust_event'} = $self;
+
+ #no unsatisfied conditions
+ #! grep ! $_->condition( $object, %opt ), @conditions;
+ my @unsatisfied = grep ! $_->condition( $object, %opt ), @conditions;
+
+ if ( $opt{'stats_hashref'} ) {
+ foreach my $unsat (@unsatisfied) {
+ $opt{'stats_hashref'}->{$unsat->conditionname}++;
+ }
+ }
+
+ ! @unsatisfied;
+}
+
+=item do_event
+
+Runs the event action.
+
+=cut
+
+sub do_event {
+ my $self = shift;
+
+ my $part_event = $self->part_event;
+
+ my $object = $self->cust_X;
+ my $obj_pkey = $object->primary_key;
+ my $for = "for ". $object->table. " ". $object->$obj_pkey();
+ warn "running cust_event ". $self->eventnum.
+ " (". $part_event->action. ") $for\n"
+ if $DEBUG;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ my $error;
+ {
+ local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
+ $error = eval { $part_event->do_action($object, $self); };
+ }
+
+ my $status = '';
+ my $statustext = '';
+ if ( $@ ) {
+ $status = 'failed';
+ #$statustext = $@;
+ $statustext = "Error running ". $part_event->action. " action: $@";
+ } elsif ( $error ) {
+ $status = 'done';
+ $statustext = $error;
+ } else {
+ $status = 'done';
+ }
+
+ #replace or add myself
+ $self->_date(time);
+ $self->status($status);
+ $self->statustext($statustext);
+
+ $error = $self->eventnum ? $self->replace : $self->insert;
+ if ( $error ) {
+ #this is why we need that locked state...
+ my $e = 'WARNING: Event run but database not updated - '.
+ 'error replacing or inserting cust_event '. $self->eventnum.
+ " $for: $error\n";
+ warn $e;
+ return $e;
+ }
+
+ '';
+
+}
+
+=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);
+}
+
+#=item retryable
+#
+#Changes the statustext of this event to B<retriable>, rendering it
+#retriable (should retry be called).
+#
+#=cut
+
+sub retriable {
+ confess "cust_event->retriable called";
+ my $self = shift;
+ return '' unless $self->status eq 'done';
+ my $old = ref($self)->new( { $self->hash } );
+ $self->statustext('retriable');
+ $self->replace($old);
+}
+
+=item join_cust_sql
+
+=cut
+
+sub join_sql {
+ #my $class = shift;
+
+ "
+ JOIN part_event USING ( eventpart )
+ LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = invnum )
+ LEFT JOIN cust_pkg ON ( eventtable = 'cust_pkg' AND tablenum = pkgnum )
+ LEFT JOIN cust_main ON ( ( eventtable = 'cust_main' AND tablenum = cust_main.custnum )
+ OR ( eventtable = 'cust_bill' AND cust_bill.custnum = cust_main.custnum )
+ OR ( eventtable = 'cust_pkg' AND cust_pkg.custnum = cust_main.custnum )
+ )
+ ";
+
+}
+
+=item search_sql_where HASHREF
+
+Class method which returns an SQL WHERE fragment to search for parameters
+specified in HASHREF. Valid parameters are
+
+=over 4
+
+=item agentnum
+
+=item custnum
+
+=item invnum
+
+=item pkgnum
+
+=item failed
+
+=item beginning
+
+=item ending
+
+=item payby
+
+=item
+
+=back
+
+=cut
+
+#Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
+#sub
+
+sub search_sql_where {
+ my($class, $param) = @_;
+ if ( $DEBUG ) {
+ warn "$me search_sql_where called with params: \n".
+ join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
+ }
+
+ my @search = $class->cust_search_sql($param);
+
+ #eventpart
+ my @eventpart = ref($param->{'eventpart'})
+ ? @{ $param->{'eventpart'} }
+ : split(',', $param->{'eventpart'});
+ @eventpart = grep /^(\d+)$/, @eventpart;
+ if ( @eventpart ) {
+ push @search, 'eventpart IN ('. join(',', @eventpart). ')';
+ }
+
+ if ( $param->{'beginning'} =~ /^(\d+)$/ ) {
+ push @search, "cust_event._date >= $1";
+ }
+ if ( $param->{'ending'} =~ /^(\d+)$/ ) {
+ push @search, "cust_event._date <= $1";
+ }
+
+ if ( $param->{'failed'} ) {
+ push @search, "statustext != ''",
+ "statustext IS NOT NULL",
+ "statustext != 'N/A'";
+ }
+
+ if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
+ push @search, "cust_main.custnum = '$1'";
+ }
+
+ if ( $param->{'invnum'} =~ /^(\d+)$/ ) {
+ push @search, "part_event.eventtable = 'cust_bill'",
+ "tablenum = '$1'";
+ }
+
+ if ( $param->{'pkgnum'} =~ /^(\d+)$/ ) {
+ push @search, "part_event.eventtable = 'cust_pkg'",
+ "tablenum = '$1'";
+ }
+
+ my $where = 'WHERE '. join(' AND ', @search );
+
+ join(' AND ', @search );
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item reprint
+
+=cut
+
+sub process_reprint {
+ process_re_X('print', @_);
+}
+
+=item reemail
+
+=cut
+
+sub process_reemail {
+ process_re_X('email', @_);
+}
+
+=item refax
+
+=cut
+
+sub process_refax {
+ process_re_X('fax', @_);
+}
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_re_X {
+ my( $method, $job ) = ( shift, shift );
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ re_X(
+ $method,
+ $param,
+ $job,
+ );
+
+}
+
+sub re_X {
+ my($method, $param, $job) = @_;
+
+ my $search_sql = FS::cust_event->search_sql_where($param);
+
+ #maybe not...? we do want the "re-" action to match the search more closely
+ # # yuck! hardcoded *AND* sequential scans!
+ #my $where = " WHERE action LIKE 'cust_bill_send%' ".
+ # ( $search_sql ? " AND $search_sql" : "" );
+
+ my $where = ( $search_sql ? " WHERE $search_sql" : "" );
+
+ my @cust_event = qsearch({
+ 'table' => 'cust_event',
+ 'addl_from' => FS::cust_event->join_sql(),
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ });
+
+ warn "$me re_X found ". scalar(@cust_event). " events\n"
+ if $DEBUG;
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+ foreach my $cust_event ( @cust_event ) {
+
+ my $cust_X = $cust_event->cust_X; # cust_bill
+ next unless $cust_X->can($method);
+
+ $cust_X->$method( $cust_event->part_event->templatename
+ || $cust_X->agent_template
+ );
+
+ if ( $job ) { #progressbar foo
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / scalar(@cust_event) )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ }
+
+ #this doesn't work, but it would be nice
+ #if ( $job ) { #progressbar foo
+ # my $error = $job->update_statustext(
+ # scalar(@cust_event). " invoices re-${method}ed"
+ # );
+ # die $error if $error;
+ #}
+
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::part_event>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
new file mode 100644
index 0000000..0c5c023
--- /dev/null
+++ b/FS/FS/cust_location.pm
@@ -0,0 +1,265 @@
+package FS::cust_location;
+
+use strict;
+use base qw( FS::Record );
+use Locale::Country;
+use FS::Record qw( qsearch ); #qsearchs );
+use FS::prospect_main;
+use FS::cust_main;
+use FS::cust_main_county;
+
+=head1 NAME
+
+FS::cust_location - Object methods for cust_location records
+
+=head1 SYNOPSIS
+
+ use FS::cust_location;
+
+ $record = new FS::cust_location \%hash;
+ $record = new FS::cust_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_location object represents a customer location. FS::cust_location
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item locationnum
+
+primary key
+
+=item custnum
+
+custnum
+
+=item address1
+
+Address line one (required)
+
+=item address2
+
+Address line two (optional)
+
+=item city
+
+City
+
+=item county
+
+County (optional, see L<FS::cust_main_county>)
+
+=item state
+
+State (see L<FS::cust_main_county>)
+
+=item zip
+
+Zip
+
+=item country
+
+Country (see L<FS::cust_main_county>)
+
+=item geocode
+
+Geocode
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new location. To add the location 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_location'; }
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid location. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+#some false laziness w/cust_main, but since it should eventually lose these
+#fields anyway...
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('locationnum')
+ || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
+ || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
+ || $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_zip('zip', $self->country)
+ || $self->ut_alphan('geocode')
+ ;
+ return $error if $error;
+
+ return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
+ return "Prospect and customer!" if $self->prospectnum && $self->custnum;
+
+ 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,
+ } );
+ }
+
+ $self->SUPER::check;
+}
+
+=item country_full
+
+Returns this locations's full country name
+
+=cut
+
+sub country_full {
+ my $self = shift;
+ code2country($self->country);
+}
+
+=item location_label [ OPTION => VALUE ... ]
+
+Returns the label of the service location for this customer.
+
+Options are
+
+=over 4
+
+=item join_string
+
+used to separate the address elements (defaults to ', ')
+
+=item escape_function
+
+
+a callback used for escaping the text of the address elements
+
+=back
+
+=cut
+
+# false laziness with FS::cust_main::location_label
+
+sub location_label {
+ my $self = shift;
+ my %opt = @_;
+
+ my $separator = $opt{join_string} || ', ';
+ my $escape = $opt{escape_function} || sub{ shift };
+ my $ds = $opt{double_space} || ' ';
+ my $line = '';
+ my $cydefault =
+ $opt{'countrydefault'} || FS::Conf->new->config('countrydefault') || 'US';
+ my $prefix = '';
+
+ my $notfirst = 0;
+ foreach (qw ( address1 address2 ) ) {
+ my $method = "$prefix$_";
+ $line .= ($notfirst ? $separator : ''). &$escape($self->$method)
+ if $self->$method;
+ $notfirst++;
+ }
+ $notfirst = 0;
+ foreach (qw ( city county state zip ) ) {
+ my $method = "$prefix$_";
+ if ( $self->$method ) {
+ $line .= ($notfirst ? ($method eq 'zip' ? $ds : ' ') : $separator);
+ $line .= '(' if $method eq 'county';
+ $line .= &$escape($self->$method);
+ $line .= ')' if $method eq 'county';
+ $notfirst++;
+ }
+ $line .= ',' if $method eq 'county';
+ }
+ $line .= $separator. &$escape(code2country($self->country))
+ if $self->country ne $cydefault;
+
+ $line;
+}
+
+=item line
+
+Synonym for location_label
+
+=cut
+
+sub line {
+ my $self = shift;
+ $self->location_label;
+}
+
+=item location_hash
+
+Returns a list of key/value pairs, with the following keys: address1, adddress2,
+city, county, state, zip, country.
+
+=cut
+
+#geocode? not yet set
+
+sub location_hash {
+ my $self = shift;
+ map { $_ => $self->$_ } qw( address1 address2 city county state zip country );
+}
+
+=back
+
+=head1 BUGS
+
+Not yet used for cust_main billing and shipping addresses.
+
+=head1 SEE ALSO
+
+L<FS::cust_main_county>, L<FS::cust_pkg>, 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..c1a8aaf
--- /dev/null
+++ b/FS/FS/cust_main.pm
@@ -0,0 +1,8858 @@
+package FS::cust_main;
+
+require 5.006;
+use strict;
+use base qw( FS::otaker_Mixin FS::payinfo_Mixin FS::Record );
+use vars qw( @EXPORT_OK $DEBUG $me $conf
+ @encrypted_fields
+ $import $ignore_expired_card
+ $skip_fuzzyfiles @fuzzyfields
+ @paytypes
+ );
+use vars qw( $realtime_bop_decline_quiet ); #ugh
+use Safe;
+use Carp;
+use Exporter;
+use Scalar::Util qw( blessed );
+use List::Util qw( min );
+use Time::Local qw(timelocal);
+use Data::Dumper;
+use Tie::IxHash;
+use Digest::MD5 qw(md5_base64);
+use Date::Format;
+#use Date::Manip;
+use File::Temp qw( tempfile );
+use String::Approx qw(amatch);
+use Business::CreditCard 0.28;
+use Locale::Country;
+use FS::UID qw( getotaker dbh driver_name );
+use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
+use FS::Misc qw( generate_email send_email generate_ps do_print );
+use FS::Msgcat qw(gettext);
+use FS::payby;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::cust_bill;
+use FS::cust_bill_pkg;
+use FS::cust_bill_pkg_display;
+use FS::cust_bill_pkg_tax_location;
+use FS::cust_bill_pkg_tax_rate_location;
+use FS::cust_pay;
+use FS::cust_pay_pending;
+use FS::cust_pay_void;
+use FS::cust_pay_batch;
+use FS::cust_credit;
+use FS::cust_refund;
+use FS::part_referral;
+use FS::cust_main_county;
+use FS::cust_location;
+use FS::cust_class;
+use FS::cust_main_exemption;
+use FS::cust_tax_adjustment;
+use FS::tax_rate;
+use FS::tax_rate_location;
+use FS::cust_tax_location;
+use FS::part_pkg_taxrate;
+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_event;
+use FS::part_event_condition;
+#use FS::cust_event;
+use FS::type_pkgs;
+use FS::payment_gateway;
+use FS::agent_payment_gateway;
+use FS::banned_pay;
+use FS::TicketSystem;
+
+@EXPORT_OK = qw( smart_search );
+
+$realtime_bop_decline_quiet = 0;
+
+# 1 is mostly method/subroutine entry and options
+# 2 traces progress of some operations
+# 3 is even more information including possibly sensitive data
+$DEBUG = 0;
+$me = '[FS::cust_main]';
+
+$import = 0;
+$ignore_expired_card = 0;
+
+$skip_fuzzyfiles = 0;
+@fuzzyfields = ( 'first', 'last', 'company', 'address1' );
+
+@encrypted_fields = ('payinfo', 'paycvv');
+sub nohistory_fields { ('paycvv'); }
+
+@paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
+
+#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,
+ ;
+
+=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
+
+First name
+
+=item last
+
+Last name
+
+=item ss
+
+Cocial 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
+
+Shipping first name
+
+=item ship_last
+
+Shipping 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
+
+Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
+
+=item payinfo
+
+Payment Information (See L<FS::payinfo_Mixin> for data format)
+
+=item paymask
+
+Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
+
+=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 paystart_month
+
+Start date month (maestro/solo cards only)
+
+=item paystart_year
+
+Start date year (maestro/solo cards only)
+
+=item payissue
+
+Issue number (maestro/solo cards only)
+
+=item payname
+
+Name on card or billing name
+
+=item payip
+
+IP address from which payment information was received
+
+=item tax
+
+Tax exempt, empty or `Y'
+
+=item usernum
+
+Order taker (see L<FS::access_user>)
+
+=item comments
+
+Comments (optional)
+
+=item referral_custnum
+
+Referring customer number
+
+=item spool_cdr
+
+Enable individual CDR spooling, empty or `Y'
+
+=item dundate
+
+A suggestion to events (see L<FS::part_bill_event">) to delay until this unix timestamp
+
+=item squelch_cdr
+
+Discourage individual CDR printing, empty or `Y'
+
+=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>, I<noexport> and I<tax_exemption>.
+
+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 successfully).
+
+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.)
+
+The I<tax_exemption> option can be set to an arrayref of tax names.
+FS::cust_main_exemption records will be created and inserted.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $cust_pkgs = @_ ? shift : {};
+ my $invoicing_list = @_ ? shift : '';
+ my %options = @_;
+ warn "$me 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 $prepay_identifier = '';
+ my( $amount, $seconds, $upbytes, $downbytes, $totalbytes ) = (0, 0, 0, 0, 0);
+ my $payby = '';
+ if ( $self->payby eq 'PREPAY' ) {
+
+ $self->payby('BILL');
+ $prepay_identifier = $self->payinfo;
+ $self->payinfo('');
+
+ warn " looking up prepaid card $prepay_identifier\n"
+ if $DEBUG > 1;
+
+ my $error = $self->get_prepay( $prepay_identifier,
+ 'amount_ref' => \$amount,
+ 'seconds_ref' => \$seconds,
+ 'upbytes_ref' => \$upbytes,
+ 'downbytes_ref' => \$downbytes,
+ 'totalbytes_ref' => \$totalbytes,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "error applying prepaid card (transaction rolled back): $error";
+ return $error;
+ }
+
+ $payby = 'PREP' if $amount;
+
+ } elsif ( $self->payby =~ /^(CASH|WEST|MCRD)$/ ) {
+
+ $payby = $1;
+ $self->payby('BILL');
+ $amount = $self->paid;
+
+ }
+
+ warn " inserting $self\n"
+ if $DEBUG > 1;
+
+ $self->signupdate(time) unless $self->signupdate;
+
+ $self->auto_agent_custid()
+ if $conf->config('cust_main-auto_agent_custid') && ! $self->agent_custid;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "inserting cust_main record (transaction rolled back): $error";
+ return $error;
+ }
+
+ warn " setting invoicing list\n"
+ if $DEBUG > 1;
+
+ if ( $invoicing_list ) {
+ $error = $self->check_invoicing_list( $invoicing_list );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "checking invoicing_list (transaction rolled back): $error";
+ return $error;
+ }
+ $self->invoicing_list( $invoicing_list );
+ }
+
+ warn " setting cust_main_exemption\n"
+ if $DEBUG > 1;
+
+ my $tax_exemption = delete $options{'tax_exemption'};
+ if ( $tax_exemption ) {
+ foreach my $taxname ( @$tax_exemption ) {
+ my $cust_main_exemption = new FS::cust_main_exemption {
+ 'custnum' => $self->custnum,
+ 'taxname' => $taxname,
+ };
+ my $error = $cust_main_exemption->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_main_exemption (transaction rolled back): $error";
+ }
+ }
+ }
+
+ if ( $conf->config('cust_main-skeleton_tables')
+ && $conf->config('cust_main-skeleton_custnum') ) {
+
+ warn " inserting skeleton records\n"
+ if $DEBUG > 1;
+
+ my $error = $self->start_copy_skel;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ warn " ordering packages\n"
+ if $DEBUG > 1;
+
+ $error = $self->order_pkgs( $cust_pkgs,
+ %options,
+ 'seconds_ref' => \$seconds,
+ 'upbytes_ref' => \$upbytes,
+ 'downbytes_ref' => \$downbytes,
+ 'totalbytes_ref' => \$totalbytes,
+ );
+ 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 ( $upbytes || $downbytes || $totalbytes ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "No svc_acct record to apply pre-paid data";
+ }
+
+ if ( $amount ) {
+ warn " inserting initial $payby payment of $amount\n"
+ if $DEBUG > 1;
+ $error = $self->insert_cust_pay($payby, $amount, $prepay_identifier);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting payment (transaction rolled back): $error";
+ }
+ }
+
+ unless ( $import || $skip_fuzzyfiles ) {
+ warn " queueing fuzzyfiles update\n"
+ if $DEBUG > 1;
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+ }
+
+ warn " insert complete; committing transaction\n"
+ if $DEBUG > 1;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+use File::CounterFile;
+sub auto_agent_custid {
+ my $self = shift;
+
+ my $format = $conf->config('cust_main-auto_agent_custid');
+ my $agent_custid;
+ if ( $format eq '1YMMXXXXXXXX' ) {
+
+ my $counter = new File::CounterFile 'cust_main.agent_custid';
+ $counter->lock;
+
+ my $ym = 100000000000 + time2str('%y%m00000000', time);
+ if ( $ym > $counter->value ) {
+ $counter->{'value'} = $agent_custid = $ym;
+ $counter->{'updated'} = 1;
+ } else {
+ $agent_custid = $counter->inc;
+ }
+
+ $counter->unlock;
+
+ } else {
+ die "Unknown cust_main-auto_agent_custid format: $format";
+ }
+
+ $self->agent_custid($agent_custid);
+
+}
+
+sub start_copy_skel {
+ my $self = shift;
+
+ #'mg_user_preference' => {},
+ #'mg_user_indicator_profile.user_indicator_profile_id' => { 'mg_profile_indicator.profile_indicator_id' => { 'mg_profile_details.profile_detail_id' }, },
+ #'mg_watchlist_header.watchlist_header_id' => { 'mg_watchlist_details.watchlist_details_id' },
+ #'mg_user_grid_header.grid_header_id' => { 'mg_user_grid_details.user_grid_details_id' },
+ #'mg_portfolio_header.portfolio_header_id' => { 'mg_portfolio_trades.portfolio_trades_id' => { 'mg_portfolio_trades_positions.portfolio_trades_positions_id' } },
+ my @tables = eval(join('\n',$conf->config('cust_main-skeleton_tables')));
+ die $@ if $@;
+
+ _copy_skel( 'cust_main', #tablename
+ $conf->config('cust_main-skeleton_custnum'), #sourceid
+ $self->custnum, #destid
+ @tables, #child tables
+ );
+}
+
+#recursive subroutine, not a method
+sub _copy_skel {
+ my( $table, $sourceid, $destid, %child_tables ) = @_;
+
+ my $primary_key;
+ if ( $table =~ /^(\w+)\.(\w+)$/ ) {
+ ( $table, $primary_key ) = ( $1, $2 );
+ } else {
+ my $dbdef_table = dbdef->table($table);
+ $primary_key = $dbdef_table->primary_key
+ or return "$table has no primary key".
+ " (or do you need to run dbdef-create?)";
+ }
+
+ warn " _copy_skel: $table.$primary_key $sourceid to $destid for ".
+ join (', ', keys %child_tables). "\n"
+ if $DEBUG > 2;
+
+ foreach my $child_table_def ( keys %child_tables ) {
+
+ my $child_table;
+ my $child_pkey = '';
+ if ( $child_table_def =~ /^(\w+)\.(\w+)$/ ) {
+ ( $child_table, $child_pkey ) = ( $1, $2 );
+ } else {
+ $child_table = $child_table_def;
+
+ $child_pkey = dbdef->table($child_table)->primary_key;
+ # or return "$table has no primary key".
+ # " (or do you need to run dbdef-create?)\n";
+ }
+
+ my $sequence = '';
+ if ( keys %{ $child_tables{$child_table_def} } ) {
+
+ return "$child_table has no primary key".
+ " (run dbdef-create or try specifying it?)\n"
+ unless $child_pkey;
+
+ #false laziness w/Record::insert and only works on Pg
+ #refactor the proper last-inserted-id stuff out of Record::insert if this
+ # ever gets use for anything besides a quick kludge for one customer
+ my $default = dbdef->table($child_table)->column($child_pkey)->default;
+ $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i
+ or return "can't parse $child_table.$child_pkey default value ".
+ " for sequence name: $default";
+ $sequence = $1;
+
+ }
+
+ my @sel_columns = grep { $_ ne $primary_key }
+ dbdef->table($child_table)->columns;
+ my $sel_columns = join(', ', @sel_columns );
+
+ my @ins_columns = grep { $_ ne $child_pkey } @sel_columns;
+ my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) ';
+ my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) ';
+
+ my $sel_st = "SELECT $sel_columns FROM $child_table".
+ " WHERE $primary_key = $sourceid";
+ warn " $sel_st\n"
+ if $DEBUG > 2;
+ my $sel_sth = dbh->prepare( $sel_st )
+ or return dbh->errstr;
+
+ $sel_sth->execute or return $sel_sth->errstr;
+
+ while ( my $row = $sel_sth->fetchrow_hashref ) {
+
+ warn " selected row: ".
+ join(', ', map { "$_=".$row->{$_} } keys %$row ). "\n"
+ if $DEBUG > 2;
+
+ my $statement =
+ "INSERT INTO $child_table $ins_columns VALUES $placeholders";
+ my $ins_sth =dbh->prepare($statement)
+ or return dbh->errstr;
+ my @param = ( $destid, map $row->{$_}, @ins_columns );
+ warn " $statement: [ ". join(', ', @param). " ]\n"
+ if $DEBUG > 2;
+ $ins_sth->execute( @param )
+ or return $ins_sth->errstr;
+
+ #next unless keys %{ $child_tables{$child_table} };
+ next unless $sequence;
+
+ #another section of that laziness
+ my $seq_sql = "SELECT currval('$sequence')";
+ my $seq_sth = dbh->prepare($seq_sql) or return dbh->errstr;
+ $seq_sth->execute or return $seq_sth->errstr;
+ my $insertid = $seq_sth->fetchrow_arrayref->[0];
+
+ # don't drink soap! recurse! recurse! okay!
+ my $error =
+ _copy_skel( $child_table_def,
+ $row->{$child_pkey}, #sourceid
+ $insertid, #destid
+ %{ $child_tables{$child_table_def} },
+ );
+ return $error if $error;
+
+ }
+
+ }
+
+ return '';
+
+}
+
+=item order_pkg HASHREF | OPTION => VALUE ...
+
+Orders a single package.
+
+Options may be passed as a list of key/value pairs or as a hash reference.
+Options are:
+
+=over 4
+
+=item cust_pkg
+
+FS::cust_pkg object
+
+=item cust_location
+
+Optional FS::cust_location object
+
+=item svcs
+
+Optional arryaref of FS::svc_* service objects.
+
+=item depend_jobnum
+
+If this option is set to a job queue jobnum (see L<FS::queue>), all provisioning
+jobs will have a dependancy on the supplied job (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 successfully).
+
+=item ticket_subject
+
+Optional subject for a ticket created and attached to this customer
+
+=item ticket_subject
+
+Optional queue name for ticket additions
+
+=back
+
+=cut
+
+sub order_pkg {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+
+ warn "$me order_pkg called with options ".
+ join(', ', map { "$_: $opt->{$_}" } keys %$opt ). "\n"
+ if $DEBUG;
+
+ my $cust_pkg = $opt->{'cust_pkg'};
+ my $svcs = $opt->{'svcs'} || [];
+
+ my %svc_options = ();
+ $svc_options{'depend_jobnum'} = $opt->{'depend_jobnum'}
+ if exists($opt->{'depend_jobnum'}) && $opt->{'depend_jobnum'};
+
+ my %insert_params = map { $opt->{$_} ? ( $_ => $opt->{$_} ) : () }
+ qw( ticket_subject ticket_queue );
+
+ 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 ( $opt->{'cust_location'} &&
+ ( ! $cust_pkg->locationnum || $cust_pkg->locationnum == -1 ) ) {
+ my $error = $opt->{'cust_location'}->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_location (transaction rolled back): $error";
+ }
+ $cust_pkg->locationnum($opt->{'cust_location'}->locationnum);
+ }
+
+ $cust_pkg->custnum( $self->custnum );
+
+ my $error = $cust_pkg->insert( %insert_params );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_pkg (transaction rolled back): $error";
+ }
+
+ foreach my $svc_something ( @{ $opt->{'svcs'} } ) {
+ if ( $svc_something->svcnum ) {
+ my $old_cust_svc = $svc_something->cust_svc;
+ my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
+ $new_cust_svc->pkgnum( $cust_pkg->pkgnum);
+ $error = $new_cust_svc->replace($old_cust_svc);
+ } else {
+ $svc_something->pkgnum( $cust_pkg->pkgnum );
+ if ( $svc_something->isa('FS::svc_acct') ) {
+ foreach ( grep { $opt->{$_.'_ref'} && ${ $opt->{$_.'_ref'} } }
+ qw( seconds upbytes downbytes totalbytes ) ) {
+ $svc_something->$_( $svc_something->$_() + ${ $opt->{$_.'_ref'} } );
+ ${ $opt->{$_.'_ref'} } = 0;
+ }
+ }
+ $error = $svc_something->insert(%svc_options);
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting svc_ (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+#deprecated #=item order_pkgs HASHREF [ , SECONDSREF ] [ , OPTION => VALUE ... ]
+=item order_pkgs HASHREF [ , OPTION => VALUE ... ]
+
+Like the insert method on an existing record, this method orders multiple
+packages 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, 'noexport'=>1 );
+
+Services can be new, in which case they are inserted, or existing unaudited
+services, in which case they are linked to the newly-created package.
+
+Currently available options are: I<depend_jobnum>, I<noexport>, I<seconds_ref>,
+I<upbytes_ref>, I<downbytes_ref>, and I<totalbytes_ref>.
+
+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 successfully).
+
+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.)
+
+If I<seconds_ref>, I<upbytes_ref>, I<downbytes_ref>, or I<totalbytes_ref> is
+provided, the scalars (provided by references) will be incremented by the
+values of the prepaid card.`
+
+=cut
+
+sub order_pkgs {
+ my $self = shift;
+ my $cust_pkgs = shift;
+ my $seconds_ref = ref($_[0]) ? shift : ''; #deprecated
+ my %options = @_;
+ $seconds_ref ||= $options{'seconds_ref'};
+
+ warn "$me 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 ) {
+
+ my $error = $self->order_pkg(
+ 'cust_pkg' => $cust_pkg,
+ 'svcs' => $cust_pkgs->{$cust_pkg},
+ 'seconds_ref' => $seconds_ref,
+ map { $_ => $options{$_} } qw( upbytes_ref downbytes_ref totalbytes_ref
+ depend_jobnum
+ )
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF, UPBYTEREF, DOWNBYTEREF ]
+
+Recharges this (existing) customer with the specified prepaid card (see
+L<FS::prepay_credit>), specified either by I<identifier> or as an
+FS::prepay_credit object. If there is an error, returns the error, otherwise
+returns false.
+
+Optionally, five scalar references can be passed as well. They will have their
+values filled in with the amount, number of seconds, and number of upload,
+download, and total bytes applied by this prepaid card.
+
+=cut
+
+#the ref bullshit here should be refactored like get_prepay. MyAccount.pm is
+#the only place that uses these args
+sub recharge_prepay {
+ my( $self, $prepay_credit, $amountref, $secondsref,
+ $upbytesref, $downbytesref, $totalbytesref ) = @_;
+
+ 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, $seconds, $upbytes, $downbytes, $totalbytes) = ( 0, 0, 0, 0, 0 );
+
+ my $error = $self->get_prepay( $prepay_credit,
+ 'amount_ref' => \$amount,
+ 'seconds_ref' => \$seconds,
+ 'upbytes_ref' => \$upbytes,
+ 'downbytes_ref' => \$downbytes,
+ 'totalbytes_ref' => \$totalbytes,
+ )
+ || $self->increment_seconds($seconds)
+ || $self->increment_upbytes($upbytes)
+ || $self->increment_downbytes($downbytes)
+ || $self->increment_totalbytes($totalbytes)
+ || $self->insert_cust_pay_prepay( $amount,
+ ref($prepay_credit)
+ ? $prepay_credit->identifier
+ : $prepay_credit
+ );
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( defined($amountref) ) { $$amountref = $amount; }
+ if ( defined($secondsref) ) { $$secondsref = $seconds; }
+ if ( defined($upbytesref) ) { $$upbytesref = $upbytes; }
+ if ( defined($downbytesref) ) { $$downbytesref = $downbytes; }
+ if ( defined($totalbytesref) ) { $$totalbytesref = $totalbytes; }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item get_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , OPTION => VALUE ... ]
+
+Looks up and deletes a prepaid card (see L<FS::prepay_credit>),
+specified either by I<identifier> or as an FS::prepay_credit object.
+
+Available options are: I<amount_ref>, I<seconds_ref>, I<upbytes_ref>, I<downbytes_ref>, and I<totalbytes_ref>. The scalars (provided by references) will be
+incremented by the values of the prepaid card.
+
+If the prepaid card specifies an I<agentnum> (see L<FS::agent>), it is used to
+check or set this customer's I<agentnum>.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+
+sub get_prepay {
+ my( $self, $prepay_credit, %opt ) = @_;
+
+ 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;
+
+ unless ( ref($prepay_credit) ) {
+
+ my $identifier = $prepay_credit;
+
+ $prepay_credit = qsearchs(
+ 'prepay_credit',
+ { 'identifier' => $prepay_credit },
+ '',
+ 'FOR UPDATE'
+ );
+
+ unless ( $prepay_credit ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Invalid prepaid card: ". $identifier;
+ }
+
+ }
+
+ if ( $prepay_credit->agentnum ) {
+ if ( $self->agentnum && $self->agentnum != $prepay_credit->agentnum ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "prepaid card not valid for agent ". $self->agentnum;
+ }
+ $self->agentnum($prepay_credit->agentnum);
+ }
+
+ my $error = $prepay_credit->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "removing prepay_credit (transaction rolled back): $error";
+ }
+
+ ${ $opt{$_.'_ref'} } += $prepay_credit->$_()
+ for grep $opt{$_.'_ref'}, qw( amount seconds upbytes downbytes totalbytes );
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item increment_upbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of upbytes. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_upbytes {
+ _increment_column( shift, 'upbytes', @_);
+}
+
+=item increment_downbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of downbytes. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_downbytes {
+ _increment_column( shift, 'downbytes', @_);
+}
+
+=item increment_totalbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of totalbytes. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_totalbytes {
+ _increment_column( shift, 'totalbytes', @_);
+}
+
+=item increment_seconds SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of seconds. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_seconds {
+ _increment_column( shift, 'seconds', @_);
+}
+
+=item _increment_column AMOUNT
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of seconds or bytes. If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub _increment_column {
+ my( $self, $column, $amount ) = @_;
+ warn "$me increment_column called: $column, $amount\n"
+ if $DEBUG;
+
+ return '' unless $amount;
+
+ my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') }
+ $self->ncancelled_pkgs;
+
+ if ( ! @cust_pkg ) {
+ return 'No packages with primary or single services found'.
+ ' to apply pre-paid time';
+ } elsif ( scalar(@cust_pkg) > 1 ) {
+ #maybe have a way to specify the package/account?
+ return 'Multiple packages found to apply pre-paid time';
+ }
+
+ my $cust_pkg = $cust_pkg[0];
+ warn " found package pkgnum ". $cust_pkg->pkgnum. "\n"
+ if $DEBUG > 1;
+
+ my @cust_svc =
+ $cust_pkg->cust_svc( $cust_pkg->part_pkg->svcpart('svc_acct') );
+
+ if ( ! @cust_svc ) {
+ return 'No account found to apply pre-paid time';
+ } elsif ( scalar(@cust_svc) > 1 ) {
+ return 'Multiple accounts found to apply pre-paid time';
+ }
+
+ my $svc_acct = $cust_svc[0]->svc_x;
+ warn " found service svcnum ". $svc_acct->pkgnum.
+ ' ('. $svc_acct->email. ")\n"
+ if $DEBUG > 1;
+
+ $column = "increment_$column";
+ $svc_acct->$column($amount);
+
+}
+
+=item insert_cust_pay_prepay AMOUNT [ PAYINFO ]
+
+Inserts a prepayment in the specified amount for this customer. An optional
+second argument can specify the prepayment identifier for tracking purposes.
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert_cust_pay_prepay {
+ shift->insert_cust_pay('PREP', @_);
+}
+
+=item insert_cust_pay_cash AMOUNT [ PAYINFO ]
+
+Inserts a cash payment in the specified amount for this customer. An optional
+second argument can specify the payment identifier for tracking purposes.
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert_cust_pay_cash {
+ shift->insert_cust_pay('CASH', @_);
+}
+
+=item insert_cust_pay_west AMOUNT [ PAYINFO ]
+
+Inserts a Western Union payment in the specified amount for this customer. An
+optional second argument can specify the prepayment identifier for tracking
+purposes. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert_cust_pay_west {
+ shift->insert_cust_pay('WEST', @_);
+}
+
+sub insert_cust_pay {
+ my( $self, $payby, $amount ) = splice(@_, 0, 3);
+ my $payinfo = scalar(@_) ? shift : '';
+
+ my $cust_pay = new FS::cust_pay {
+ 'custnum' => $self->custnum,
+ 'paid' => sprintf('%.2f', $amount),
+ #'_date' => #date the prepaid card was purchased???
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ };
+ $cust_pay->insert;
+
+}
+
+=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,
+ options => { $cust_pkg->options },
+ );
+ 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;
+ }
+ }
+
+ foreach my $cust_main_exemption (
+ qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } )
+ ) {
+ my $error = $cust_main_exemption->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 ] [ , OPTION => VALUE ... ] ]
+
+
+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' ] );
+
+Currently available options are: I<tax_exemption>.
+
+The I<tax_exemption> option can be set to an arrayref of tax names.
+FS::cust_main_exemption records will be deleted and inserted as appropriate.
+
+=cut
+
+sub replace {
+ my $self = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
+ my @param = @_;
+
+ warn "$me replace called\n"
+ if $DEBUG;
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ if ( $self->payby eq 'COMP'
+ && $self->payby ne $old->payby
+ && ! $curuser->access_right('Complimentary customer')
+ )
+ {
+ return "You are not permitted to create complimentary accounts.";
+ }
+
+ local($ignore_expired_card) = 1
+ if $old->payby =~ /^(CARD|DCRD)$/
+ && $self->payby =~ /^(CARD|DCRD)$/
+ && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+ 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;
+ }
+
+ if ( @param && ref($param[0]) eq 'ARRAY' ) { # 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 );
+ }
+
+ my %options = @param;
+
+ my $tax_exemption = delete $options{'tax_exemption'};
+ if ( $tax_exemption ) {
+
+ my %cust_main_exemption =
+ map { $_->taxname => $_ }
+ qsearch('cust_main_exemption', { 'custnum' => $old->custnum } );
+
+ foreach my $taxname ( @$tax_exemption ) {
+
+ next if delete $cust_main_exemption{$taxname};
+
+ my $cust_main_exemption = new FS::cust_main_exemption {
+ 'custnum' => $self->custnum,
+ 'taxname' => $taxname,
+ };
+ my $error = $cust_main_exemption->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_main_exemption (transaction rolled back): $error";
+ }
+ }
+
+ foreach my $cust_main_exemption ( values %cust_main_exemption ) {
+ my $error = $cust_main_exemption->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "deleting cust_main_exemption (transaction rolled back): $error";
+ }
+ }
+
+ }
+
+ 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;
+ }
+ }
+
+ unless ( $import || $skip_fuzzyfiles ) {
+ $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( map $self->getfield($_), @fuzzyfields );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+
+ if ( $self->ship_last ) {
+ $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
+ $error = $queue->insert( map $self->getfield("ship_$_"), @fuzzyfields );
+ 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 replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ warn "$me check BEFORE: \n". $self->_dump
+ if $DEBUG > 2;
+
+ my $error =
+ $self->ut_numbern('custnum')
+ || $self->ut_number('agentnum')
+ || $self->ut_textn('agent_custid')
+ || $self->ut_number('refnum')
+ || $self->ut_foreign_keyn('classnum', 'cust_class', 'classnum')
+ || $self->ut_textn('custbatch')
+ || $self->ut_name('last')
+ || $self->ut_name('first')
+ || $self->ut_snumbern('birthdate')
+ || $self->ut_snumbern('signupdate')
+ || $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')
+ || $self->ut_textn('stateid')
+ || $self->ut_textn('stateid_state')
+ || $self->ut_textn('invoice_terms')
+ || $self->ut_alphan('geocode')
+ || $self->ut_floatn('cdr_termination_percentage')
+ ;
+
+ #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->censustract ne '' ) {
+ $self->censustract =~ /^\s*(\d{9})\.?(\d{2})\s*$/
+ or return "Illegal census tract: ". $self->censustract;
+
+ $self->censustract("$1.$2");
+ }
+
+ 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;
+
+ if ( $conf->exists('cust_main-require_phone')
+ && ! length($self->daytime) && ! length($self->night)
+ ) {
+
+ my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day Phone'
+ : FS::Msgcat::_gettext('daytime');
+ my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/
+ ? 'Night Phone'
+ : FS::Msgcat::_gettext('night');
+
+ return "$daytime_label or $night_label is required"
+
+ }
+
+ if ( $self->has_ship_address
+ && scalar ( grep { $self->getfield($_) ne $self->getfield("ship_$_") }
+ $self->addr_fields )
+ )
+ {
+ 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 qsearch('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;
+
+ return "Unit # is required."
+ if $self->ship_address2 =~ /^\s*$/
+ && $conf->exists('cust_main-require_address2');
+
+ } else { # ship_ info eq billing info, so don't store dup info in database
+
+ $self->setfield("ship_$_", '')
+ foreach $self->addr_fields;
+
+ return "Unit # is required."
+ if $self->address2 =~ /^\s*$/
+ && $conf->exists('cust_main-require_address2');
+
+ }
+
+ #$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
+ # or return "Illegal payby: ". $self->payby;
+ #$self->payby($1);
+ FS::payby->can_payby($self->table, $self->payby)
+ or return "Illegal payby: ". $self->payby;
+
+ $error = $self->ut_numbern('paystart_month')
+ || $self->ut_numbern('paystart_year')
+ || $self->ut_numbern('payissue')
+ || $self->ut_textn('paytype')
+ ;
+ return $error if $error;
+
+ if ( $self->payip eq '' ) {
+ $self->payip('');
+ } else {
+ $error = $self->ut_ip('payip');
+ return $error if $error;
+ }
+
+ # If it is encrypted and the private key is not availaible then we can't
+ # check the credit card.
+
+ my $check_payinfo = 1;
+
+ if ($self->is_encrypted($self->payinfo)) {
+ $check_payinfo = 0;
+ }
+
+ if ( $check_payinfo && $self->payby =~ /^(CARD|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";
+
+ my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
+ if ( $ban ) {
+ return 'Banned credit card: banned on '.
+ time2str('%a %h %o at %r', $ban->_date).
+ ' by '. $ban->otaker.
+ ' (ban# '. $ban->bannum. ')';
+ }
+
+ if (length($self->paycvv) && !$self->is_encrypted($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('');
+ }
+
+ my $cardtype = cardtype($payinfo);
+ if ( $cardtype =~ /^(Switch|Solo)$/i ) {
+
+ return "Start date or issue number is required for $cardtype cards"
+ unless $self->paystart_month && $self->paystart_year or $self->payissue;
+
+ return "Start month must be between 1 and 12"
+ if $self->paystart_month
+ and $self->paystart_month < 1 || $self->paystart_month > 12;
+
+ return "Start year must be 1990 or later"
+ if $self->paystart_year
+ and $self->paystart_year < 1990;
+
+ return "Issue number must be beween 1 and 99"
+ if $self->payissue
+ and $self->payissue < 1 || $self->payissue > 99;
+
+ } else {
+ $self->paystart_month('');
+ $self->paystart_year('');
+ $self->payissue('');
+ }
+
+ } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/[^\d\@]//g;
+ if ( $conf->exists('echeck-nonus') ) {
+ $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba';
+ $payinfo = "$1\@$2";
+ } else {
+ $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba';
+ $payinfo = "$1\@$2";
+ }
+ $self->payinfo($payinfo);
+ $self->paycvv('');
+
+ my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref);
+ if ( $ban ) {
+ return 'Banned ACH account: banned on '.
+ time2str('%a %h %o at %r', $ban->_date).
+ ' by '. $ban->otaker.
+ ' (ban# '. $ban->bannum. ')';
+ }
+
+ } 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('');
+
+ } elsif ( $self->payby eq 'BILL' ) {
+
+ $error = $self->ut_textn('payinfo');
+ return "Illegal P.O. number: ". $self->payinfo if $error;
+ $self->paycvv('');
+
+ } elsif ( $self->payby eq 'COMP' ) {
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ if ( ! $self->custnum
+ && ! $curuser->access_right('Complimentary customer')
+ )
+ {
+ return "You are not permitted to create complimentary accounts."
+ }
+
+ $error = $self->ut_textn('payinfo');
+ return "Illegal comp account issuer: ". $self->payinfo if $error;
+ $self->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->paydate eq '' || $self->paydate eq '-' ) {
+ return "Expiration date required"
+ unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD)$/;
+ $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 =~ /^19(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) {
+ ( $m, $y ) = ( $2, "19$1" );
+ } 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
+ && !$ignore_expired_card
+ && ( $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);
+ }
+
+ foreach my $flag (qw( tax spool_cdr squelch_cdr archived email_csv_cdr )) {
+ $self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag();
+ $self->$flag($1);
+ }
+
+ $self->otaker(getotaker) unless $self->otaker;
+
+ warn "$me check AFTER: \n". $self->_dump
+ if $DEBUG > 2;
+
+ $self->SUPER::check;
+}
+
+=item addr_fields
+
+Returns a list of fields which have ship_ duplicates.
+
+=cut
+
+sub addr_fields {
+ qw( last first company
+ address1 address2 city county state zip country
+ daytime night fax
+ );
+}
+
+=item has_ship_address
+
+Returns true if this customer record has a separate shipping address.
+
+=cut
+
+sub has_ship_address {
+ my $self = shift;
+ scalar( grep { $self->getfield("ship_$_") ne '' } $self->addr_fields );
+}
+
+=item location_hash
+
+Returns a list of key/value pairs, with the following keys: address1, adddress2,
+city, county, state, zip, country. The shipping address is used if present.
+
+=cut
+
+#geocode? dependent on tax-ship_address config, not available in cust_location
+#mostly. not yet then.
+
+sub location_hash {
+ my $self = shift;
+ my $prefix = $self->has_ship_address ? 'ship_' : '';
+
+ map { $_ => $self->get($prefix.$_) }
+ qw( address1 address2 city county state zip country geocode );
+ #fields that cust_location has
+}
+
+=item all_pkgs [ EXTRA_QSEARCH_PARAMS_HASHREF ]
+
+Returns all packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub all_pkgs {
+ my $self = shift;
+ my $extra_qsearch = ref($_[0]) ? shift : {};
+
+ return $self->num_pkgs unless wantarray || keys(%$extra_qsearch);
+
+ my @cust_pkg = ();
+ if ( $self->{'_pkgnum'} ) {
+ @cust_pkg = values %{ $self->{'_pkgnum'}->cache };
+ } else {
+ @cust_pkg = $self->_cust_pkg($extra_qsearch);
+ }
+
+ sort sort_packages @cust_pkg;
+}
+
+=item cust_pkg
+
+Synonym for B<all_pkgs>.
+
+=cut
+
+sub cust_pkg {
+ shift->all_pkgs(@_);
+}
+
+=item cust_location
+
+Returns all locations (see L<FS::cust_location>) for this customer.
+
+=cut
+
+sub cust_location {
+ my $self = shift;
+ qsearch('cust_location', { 'custnum' => $self->custnum } );
+}
+
+=item location_label [ OPTION => VALUE ... ]
+
+Returns the label of the service location (see analog in L<FS::cust_location>) for this customer.
+
+Options are
+
+=over 4
+
+=item join_string
+
+used to separate the address elements (defaults to ', ')
+
+=item escape_function
+
+a callback used for escaping the text of the address elements
+
+=back
+
+=cut
+
+# false laziness with FS::cust_location::line
+
+sub location_label {
+ my $self = shift;
+ my %opt = @_;
+
+ my $separator = $opt{join_string} || ', ';
+ my $escape = $opt{escape_function} || sub{ shift };
+ my $line = '';
+ my $cydefault = FS::conf->new->config('countrydefault') || 'US';
+ my $prefix = length($self->ship_last) ? 'ship_' : '';
+
+ my $notfirst = 0;
+ foreach (qw ( address1 address2 ) ) {
+ my $method = "$prefix$_";
+ $line .= ($notfirst ? $separator : ''). &$escape($self->$method)
+ if $self->$method;
+ $notfirst++;
+ }
+ $notfirst = 0;
+ foreach (qw ( city county state zip ) ) {
+ my $method = "$prefix$_";
+ if ( $self->$method ) {
+ $line .= ' (' if $method eq 'county';
+ $line .= ($notfirst ? ' ' : $separator). &$escape($self->$method);
+ $line .= ' )' if $method eq 'county';
+ $notfirst++;
+ }
+ }
+ $line .= $separator. &$escape(code2country($self->country))
+ if $self->country ne $cydefault;
+
+ $line;
+}
+
+=item ncancelled_pkgs [ EXTRA_QSEARCH_PARAMS_HASHREF ]
+
+Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub ncancelled_pkgs {
+ my $self = shift;
+ my $extra_qsearch = ref($_[0]) ? shift : {};
+
+ return $self->num_ncancelled_pkgs unless wantarray;
+
+ my @cust_pkg = ();
+ if ( $self->{'_pkgnum'} ) {
+
+ warn "$me ncancelled_pkgs: returning cached objects"
+ if $DEBUG > 1;
+
+ @cust_pkg = grep { ! $_->getfield('cancel') }
+ values %{ $self->{'_pkgnum'}->cache };
+
+ } else {
+
+ warn "$me ncancelled_pkgs: searching for packages with custnum ".
+ $self->custnum. "\n"
+ if $DEBUG > 1;
+
+ $extra_qsearch->{'extra_sql'} .= ' AND ( cancel IS NULL OR cancel = 0 ) ';
+
+ @cust_pkg = $self->_cust_pkg($extra_qsearch);
+
+ }
+
+ sort sort_packages @cust_pkg;
+
+}
+
+sub _cust_pkg {
+ my $self = shift;
+ my $extra_qsearch = ref($_[0]) ? shift : {};
+
+ $extra_qsearch->{'select'} ||= '*';
+ $extra_qsearch->{'select'} .=
+ ',( SELECT COUNT(*) FROM cust_svc WHERE cust_pkg.pkgnum = cust_svc.pkgnum )
+ AS _num_cust_svc';
+
+ map {
+ $_->{'_num_cust_svc'} = $_->get('_num_cust_svc');
+ $_;
+ }
+ qsearch({
+ %$extra_qsearch,
+ 'table' => 'cust_pkg',
+ 'hashref' => { 'custnum' => $self->custnum },
+ });
+
+}
+
+# This should be generalized to use config options to determine order.
+sub sort_packages {
+
+ my $locationsort = ( $a->locationnum || 0 ) <=> ( $b->locationnum || 0 );
+ return $locationsort if $locationsort;
+
+ if ( $a->get('cancel') xor $b->get('cancel') ) {
+ return -1 if $b->get('cancel');
+ return 1 if $a->get('cancel');
+ #shouldn't get here...
+ return 0;
+ } else {
+ my $a_num_cust_svc = $a->num_cust_svc;
+ my $b_num_cust_svc = $b->num_cust_svc;
+ return 0 if !$a_num_cust_svc && !$b_num_cust_svc;
+ return -1 if $a_num_cust_svc && !$b_num_cust_svc;
+ return 1 if !$a_num_cust_svc && $b_num_cust_svc;
+ my @a_cust_svc = $a->cust_svc;
+ my @b_cust_svc = $b->cust_svc;
+ $a_cust_svc[0]->svc_x->label cmp $b_cust_svc[0]->svc_x->label;
+ }
+
+}
+
+=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 next_bill_date
+
+Returns the next date this customer will be billed, as a UNIX timestamp, or
+undef if no active package has a next bill date.
+
+=cut
+
+sub next_bill_date {
+ my $self = shift;
+ min( map $_->get('bill'), grep $_->get('bill'), $self->unsuspended_pkgs );
+}
+
+=item num_cancelled_pkgs
+
+Returns the number of cancelled packages (see L<FS::cust_pkg>) for this
+customer.
+
+=cut
+
+sub num_cancelled_pkgs {
+ shift->num_pkgs("cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0");
+}
+
+sub num_ncancelled_pkgs {
+ shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )");
+}
+
+sub num_pkgs {
+ my( $self ) = shift;
+ my $sql = scalar(@_) ? shift : '';
+ $sql = "AND $sql" if $sql && $sql !~ /^\s*$/ && $sql !~ /^\s*AND/i;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM cust_pkg WHERE custnum = ? $sql"
+ ) or die dbh->errstr;
+ $sth->execute($self->custnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=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.
+
+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 HASHREF | PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) matching the listed
+PKGPARTs (see L<FS::part_pkg>). Preferred usage is to pass a hashref instead
+of a list of pkgparts; the hashref has the following keys:
+
+=over 4
+
+=item pkgparts - listref of pkgparts
+
+=item (other options are passed to the suspend method)
+
+=back
+
+
+Returns a list: an empty list on success or a list of errors.
+
+=cut
+
+sub suspend_if_pkgpart {
+ my $self = shift;
+ my (@pkgparts, %opt);
+ if (ref($_[0]) eq 'HASH'){
+ @pkgparts = @{$_[0]{pkgparts}};
+ %opt = %{$_[0]};
+ }else{
+ @pkgparts = @_;
+ }
+ grep { $_->suspend(%opt) }
+ grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts }
+ $self->unsuspended_pkgs;
+}
+
+=item suspend_unless_pkgpart HASHREF | PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) unless they match the
+given PKGPARTs (see L<FS::part_pkg>). Preferred usage is to pass a hashref
+instead of a list of pkgparts; the hashref has the following keys:
+
+=over 4
+
+=item pkgparts - listref of pkgparts
+
+=item (other options are passed to the suspend method)
+
+=back
+
+Returns a list: an empty list on success or a list of errors.
+
+=cut
+
+sub suspend_unless_pkgpart {
+ my $self = shift;
+ my (@pkgparts, %opt);
+ if (ref($_[0]) eq 'HASH'){
+ @pkgparts = @{$_[0]{pkgparts}};
+ %opt = %{$_[0]};
+ }else{
+ @pkgparts = @_;
+ }
+ grep { $_->suspend(%opt) }
+ 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:
+
+=over 4
+
+=item quiet - can be set true to supress email cancellation notices.
+
+=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item ban - can be set true to ban this customer's credit card or ACH information, if present.
+
+=item nobill - can be set true to skip billing if it might otherwise be done.
+
+=back
+
+Always returns a list: an empty list on success or a list of errors.
+
+=cut
+
+# nb that dates are not specified as valid options to this method
+
+sub cancel {
+ my( $self, %opt ) = @_;
+
+ warn "$me cancel called on customer ". $self->custnum. " with options ".
+ join(', ', map { "$_: $opt{$_}" } keys %opt ). "\n"
+ if $DEBUG;
+
+ return ( 'access denied' )
+ unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer');
+
+ if ( $opt{'ban'} && $self->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) {
+
+ #should try decryption (we might have the private key)
+ # and if not maybe queue a job for the server that does?
+ return ( "Can't (yet) ban encrypted credit cards" )
+ if $self->is_encrypted($self->payinfo);
+
+ my $ban = new FS::banned_pay $self->_banned_pay_hashref;
+ my $error = $ban->insert;
+ return ( $error ) if $error;
+
+ }
+
+ my @pkgs = $self->ncancelled_pkgs;
+
+ if ( !$opt{nobill} && $conf->exists('bill_usage_on_cancel') ) {
+ $opt{nobill} = 1;
+ my $error = $self->bill( pkg_list => [ @pkgs ], cancel => 1 );
+ warn "Error billing during cancel, custnum ". $self->custnum. ": $error"
+ if $error;
+ }
+
+ warn "$me cancelling ". scalar($self->ncancelled_pkgs). "/".
+ scalar(@pkgs). " packages for customer ". $self->custnum. "\n"
+ if $DEBUG;
+
+ grep { $_ } map { $_->cancel(%opt) } $self->ncancelled_pkgs;
+}
+
+sub _banned_pay_hashref {
+ my $self = shift;
+
+ my %payby2ban = (
+ 'CARD' => 'CARD',
+ 'DCRD' => 'CARD',
+ 'CHEK' => 'CHEK',
+ 'DCHK' => 'CHEK'
+ );
+
+ {
+ 'payby' => $payby2ban{$self->payby},
+ 'payinfo' => md5_base64($self->payinfo),
+ #don't ever *search* on reason! #'reason' =>
+ };
+}
+
+=item notes
+
+Returns all notes (see L<FS::cust_main_note>) for this customer.
+
+=cut
+
+sub notes {
+ my $self = shift;
+ #order by?
+ qsearch( 'cust_main_note',
+ { 'custnum' => $self->custnum },
+ '',
+ 'ORDER BY _DATE DESC'
+ );
+}
+
+=item agent
+
+Returns the agent (see L<FS::agent>) for this customer.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
+}
+
+=item cust_class
+
+Returns the customer class, as an FS::cust_class object, or the empty string
+if there is no customer class.
+
+=cut
+
+sub cust_class {
+ my $self = shift;
+ if ( $self->classnum ) {
+ qsearchs('cust_class', { 'classnum' => $self->classnum } );
+ } else {
+ return '';
+ }
+}
+
+=item categoryname
+
+Returns the customer category name, or the empty string if there is no customer
+category.
+
+=cut
+
+sub categoryname {
+ my $self = shift;
+ my $cust_class = $self->cust_class;
+ $cust_class
+ ? $cust_class->categoryname
+ : '';
+}
+
+=item classname
+
+Returns the customer class name, or the empty string if there is no customer
+class.
+
+=cut
+
+sub classname {
+ my $self = shift;
+ my $cust_class = $self->cust_class;
+ $cust_class
+ ? $cust_class->classname
+ : '';
+}
+
+
+=item bill_and_collect
+
+Cancels and suspends any packages due, generates bills, applies payments and
+credits, and applies collection events to run cards, send bills and notices,
+etc.
+
+By default, warns on errors and continues with the next operation (but see the
+"fatal" flag below).
+
+Options are passed as name-value pairs. Currently available options are:
+
+=over 4
+
+=item 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') );
+
+=item invoice_time
+
+Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
+
+=item check_freq
+
+"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
+
+=item resetup
+
+If set true, re-charges setup fees.
+
+=item fatal
+
+If set any errors prevent subsequent operations from continusing. If set
+specifically to "return", returns the error (or false, if there is no error).
+Any other true value causes errors to die.
+
+=item debug
+
+Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
+
+=back
+
+Options are passed to the B<bill> and B<collect> methods verbatim, so all
+options of those methods are also available.
+
+=cut
+
+sub bill_and_collect {
+ my( $self, %options ) = @_;
+
+ my $error;
+
+ #$options{actual_time} not $options{time} because freeside-daily -d is for
+ #pre-printing invoices
+
+ $options{'actual_time'} ||= time;
+
+ $error = $self->cancel_expired_pkgs( $options{actual_time} );
+ if ( $error ) {
+ $error = "Error expiring custnum ". $self->custnum. ": $error";
+ if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; }
+ elsif ( $options{fatal} ) { die $error; }
+ else { warn $error; }
+ }
+
+ $error = $self->suspend_adjourned_pkgs( $options{actual_time} );
+ if ( $error ) {
+ $error = "Error adjourning custnum ". $self->custnum. ": $error";
+ if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; }
+ elsif ( $options{fatal} ) { die $error; }
+ else { warn $error; }
+ }
+
+ $error = $self->bill( %options );
+ if ( $error ) {
+ $error = "Error billing custnum ". $self->custnum. ": $error";
+ if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; }
+ elsif ( $options{fatal} ) { die $error; }
+ else { warn $error; }
+ }
+
+ $error = $self->apply_payments_and_credits;
+ if ( $error ) {
+ $error = "Error applying custnum ". $self->custnum. ": $error";
+ if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; }
+ elsif ( $options{fatal} ) { die $error; }
+ else { warn $error; }
+ }
+
+ unless ( $conf->exists('cancelled_cust-noevents')
+ && ! $self->num_ncancelled_pkgs
+ ) {
+ $error = $self->collect( %options );
+ if ( $error ) {
+ $error = "Error collecting custnum ". $self->custnum. ": $error";
+ if ($options{fatal} && $options{fatal} eq 'return') { return $error; }
+ elsif ($options{fatal} ) { die $error; }
+ else { warn $error; }
+ }
+ }
+
+ '';
+
+}
+
+sub cancel_expired_pkgs {
+ my ( $self, $time, %options ) = @_;
+
+ my @cancel_pkgs = $self->ncancelled_pkgs( {
+ 'extra_sql' => " AND expire IS NOT NULL AND expire > 0 AND expire <= $time "
+ } );
+
+ my @errors = ();
+
+ foreach my $cust_pkg ( @cancel_pkgs ) {
+ my $cpr = $cust_pkg->last_cust_pkg_reason('expire');
+ my $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum,
+ 'reason_otaker' => $cpr->otaker
+ )
+ : ()
+ );
+ push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error;
+ }
+
+ scalar(@errors) ? join(' / ', @errors) : '';
+
+}
+
+sub suspend_adjourned_pkgs {
+ my ( $self, $time, %options ) = @_;
+
+ my @susp_pkgs = $self->ncancelled_pkgs( {
+ 'extra_sql' =>
+ " AND ( susp IS NULL OR susp = 0 )
+ AND ( ( bill IS NOT NULL AND bill != 0 AND bill < $time )
+ OR ( adjourn IS NOT NULL AND adjourn != 0 AND adjourn <= $time )
+ )
+ ",
+ } );
+
+ #only because there's no SQL test for is_prepaid :/
+ @susp_pkgs =
+ grep { ( $_->part_pkg->is_prepaid
+ && $_->bill
+ && $_->bill < $time
+ )
+ || ( $_->adjourn
+ && $_->adjourn <= $time
+ )
+
+ }
+ @susp_pkgs;
+
+ my @errors = ();
+
+ foreach my $cust_pkg ( @susp_pkgs ) {
+ my $cpr = $cust_pkg->last_cust_pkg_reason('adjourn')
+ if ($cust_pkg->adjourn && $cust_pkg->adjourn < $^T);
+ my $error = $cust_pkg->suspend($cpr ? ( 'reason' => $cpr->reasonnum,
+ 'reason_otaker' => $cpr->otaker
+ )
+ : ()
+ );
+ push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error;
+ }
+
+ scalar(@errors) ? join(' / ', @errors) : '';
+
+}
+
+=item bill OPTIONS
+
+Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
+conjunction with the collect method by calling B<bill_and_collect>.
+
+If there is an error, returns the error, otherwise returns false.
+
+Options are passed as name-value pairs. Currently available options are:
+
+=over 4
+
+=item resetup
+
+If set true, re-charges setup fees.
+
+=item 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') );
+
+=item pkg_list
+
+An array ref of specific packages (objects) to attempt billing, instead trying all of them.
+
+ $cust_main->bill( pkg_list => [$pkg1, $pkg2] );
+
+=item not_pkgpart
+
+A hashref of pkgparts to exclude from this billing run (can also be specified as a comma-separated scalar).
+
+=item invoice_time
+
+Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
+
+=item cancel
+
+This boolean value informs the us that the package is being cancelled. This
+typically might mean not charging the normal recurring fee but only usage
+fees since the last billing. Setup charges may be charged. Not all package
+plans support this feature (they tend to charge 0).
+
+=item invoice_terms
+
+Optional terms to be printed on this invoice. Otherwise, customer-specific
+terms or the default terms are used.
+
+=back
+
+=cut
+
+sub bill {
+ my( $self, %options ) = @_;
+ return '' if $self->payby eq 'COMP';
+ warn "$me bill customer ". $self->custnum. "\n"
+ if $DEBUG;
+
+ my $time = $options{'time'} || time;
+ my $invoice_time = $options{'invoice_time'} || $time;
+
+ $options{'not_pkgpart'} ||= {};
+ $options{'not_pkgpart'} = { map { $_ => 1 }
+ split(/\s*,\s*/, $options{'not_pkgpart'})
+ }
+ unless ref($options{'not_pkgpart'});
+
+ 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 $error = $self->do_cust_event(
+ 'debug' => ( $options{'debug'} || 0 ),
+ 'time' => $invoice_time,
+ 'check_freq' => $options{'check_freq'},
+ 'stage' => 'pre-bill',
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ #keep auto-charge and non-auto-charge line items separate
+ my @passes = ( '', 'no_auto' );
+
+ my %cust_bill_pkg = map { $_ => [] } @passes;
+
+ ###
+ # find the packages which are due for billing, find out how much they are
+ # & generate invoice database.
+ ###
+
+ my %total_setup = map { my $z = 0; $_ => \$z; } @passes;
+ my %total_recur = map { my $z = 0; $_ => \$z; } @passes;
+
+ my %taxlisthash = map { $_ => {} } @passes;
+
+ my @precommit_hooks = ();
+
+ $options{'pkg_list'} ||= [ $self->ncancelled_pkgs ]; #param checks?
+ foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {
+
+ next if $options{'not_pkgpart'}->{$cust_pkg->pkgpart};
+
+ warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1;
+
+ #? to avoid use of uninitialized value errors... ?
+ $cust_pkg->setfield('bill', '')
+ unless defined($cust_pkg->bill);
+
+ #my $part_pkg = $cust_pkg->part_pkg;
+
+ my $real_pkgpart = $cust_pkg->pkgpart;
+ my %hash = $cust_pkg->hash;
+
+ foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
+
+ $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
+
+ my $pass = ($cust_pkg->no_auto || $part_pkg->no_auto) ? 'no_auto' : '';
+
+ my $error =
+ $self->_make_lines( 'part_pkg' => $part_pkg,
+ 'cust_pkg' => $cust_pkg,
+ 'precommit_hooks' => \@precommit_hooks,
+ 'line_items' => $cust_bill_pkg{$pass},
+ 'setup' => $total_setup{$pass},
+ 'recur' => $total_recur{$pass},
+ 'tax_matrix' => $taxlisthash{$pass},
+ 'time' => $time,
+ 'real_pkgpart' => $real_pkgpart,
+ 'options' => \%options,
+ );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ } #foreach my $part_pkg
+
+ } #foreach my $cust_pkg
+
+ #if the customer isn't on an automatic payby, everything can go on a single
+ #invoice anyway?
+ #if ( $cust_main->payby !~ /^(CARD|CHEK)$/ ) {
+ #merge everything into one list
+ #}
+
+ foreach my $pass (@passes) { # keys %cust_bill_pkg ) {
+
+ my @cust_bill_pkg = @{ $cust_bill_pkg{$pass} };
+
+ next unless @cust_bill_pkg; #don't create an invoice w/o line items
+
+ if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
+ !$conf->exists('postal_invoice-recurring_only')
+ )
+ {
+
+ my $postal_pkg = $self->charge_postal_fee();
+ if ( $postal_pkg && !ref( $postal_pkg ) ) {
+
+ $dbh->rollback if $oldAutoCommit;
+ return "can't charge postal invoice fee for customer ".
+ $self->custnum. ": $postal_pkg";
+
+ } elsif ( $postal_pkg ) {
+
+ my $real_pkgpart = $postal_pkg->pkgpart;
+ foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
+ my %postal_options = %options;
+ delete $postal_options{cancel};
+ my $error =
+ $self->_make_lines( 'part_pkg' => $part_pkg,
+ 'cust_pkg' => $postal_pkg,
+ 'precommit_hooks' => \@precommit_hooks,
+ 'line_items' => \@cust_bill_pkg,
+ 'setup' => $total_setup{$pass},
+ 'recur' => $total_recur{$pass},
+ 'tax_matrix' => $taxlisthash{$pass},
+ 'time' => $time,
+ 'real_pkgpart' => $real_pkgpart,
+ 'options' => \%postal_options,
+ );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ }
+
+ my $listref_or_error =
+ $self->calculate_taxes( \@cust_bill_pkg, $taxlisthash{$pass}, $invoice_time);
+
+ unless ( ref( $listref_or_error ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $listref_or_error;
+ }
+
+ foreach my $taxline ( @$listref_or_error ) {
+ ${ $total_setup{$pass} } =
+ sprintf('%.2f', ${ $total_setup{$pass} } + $taxline->setup );
+ push @cust_bill_pkg, $taxline;
+ }
+
+ #add tax adjustments
+ warn "adding tax adjustments...\n" if $DEBUG > 2;
+ foreach my $cust_tax_adjustment (
+ qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum,
+ 'billpkgnum' => '',
+ }
+ )
+ ) {
+
+ my $tax = sprintf('%.2f', $cust_tax_adjustment->amount );
+
+ my $itemdesc = $cust_tax_adjustment->taxname;
+ $itemdesc = '' if $itemdesc eq 'Tax';
+
+ push @cust_bill_pkg, new FS::cust_bill_pkg {
+ 'pkgnum' => 0,
+ 'setup' => $tax,
+ 'recur' => 0,
+ 'sdate' => '',
+ 'edate' => '',
+ 'itemdesc' => $itemdesc,
+ 'itemcomment' => $cust_tax_adjustment->comment,
+ 'cust_tax_adjustment' => $cust_tax_adjustment,
+ #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
+ };
+
+ }
+
+ my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );
+
+ my @cust_bill = $self->cust_bill;
+ my $balance = $self->balance;
+ my $previous_balance = scalar(@cust_bill)
+ ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
+ : 0;
+
+ $previous_balance += $cust_bill[$#cust_bill]->charged
+ if scalar(@cust_bill);
+ #my $balance_adjustments =
+ # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged);
+
+ #create the new invoice
+ my $cust_bill = new FS::cust_bill ( {
+ 'custnum' => $self->custnum,
+ '_date' => ( $invoice_time ),
+ 'charged' => $charged,
+ 'billing_balance' => $balance,
+ 'previous_balance' => $previous_balance,
+ 'invoice_terms' => $options{'invoice_terms'},
+ } );
+ $error = $cust_bill->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice for customer #". $self->custnum. ": $error";
+ }
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+ $cust_bill_pkg->invnum($cust_bill->invnum);
+ my $error = $cust_bill_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice line item: $error";
+ }
+ }
+
+ } #foreach my $pass ( keys %cust_bill_pkg )
+
+ foreach my $hook ( @precommit_hooks ) {
+ eval {
+ &{$hook}; #($self) ?
+ };
+ if ( $@ ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "$@ running precommit hook $hook\n";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME
+
+This is a weird one. Perhaps it should not even be exposed.
+
+Generates tax line items (see L<FS::cust_bill_pkg>) for this customer.
+Usually used internally by bill method B<bill>.
+
+If there is an error, returns the error, otherwise returns reference to a
+list of line items suitable for insertion.
+
+=over 4
+
+=item LINEITEMREF
+
+An array ref of the line items being billed.
+
+=item TAXHASHREF
+
+A strange beast. The keys to this hash are internal identifiers consisting
+of the name of the tax object type, a space, and its unique identifier ( e.g.
+ 'cust_main_county 23' ). The values of the hash are listrefs. The first
+item in the list is the tax object. The remaining items are either line
+items or floating point values (currency amounts).
+
+The taxes are calculated on this entity. Calculated exemption records are
+transferred to the LINEITEMREF items on the assumption that they are related.
+
+Read the source.
+
+=item INVOICE_TIME
+
+This specifies the date appearing on the associated invoice. Some
+jurisdictions (i.e. Texas) have tax exemptions which are date sensitive.
+
+=back
+
+=cut
+sub calculate_taxes {
+ my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_;
+
+ my @tax_line_items = ();
+
+ warn "having a look at the taxes we found...\n" if $DEBUG > 2;
+
+ # keys are tax names (as printed on invoices / itemdesc )
+ # values are listrefs of taxlisthash keys (internal identifiers)
+ my %taxname = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are (cumulative) amounts
+ my %tax = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are listrefs of cust_bill_pkg_tax_location hashrefs
+ my %tax_location = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are listrefs of cust_bill_pkg_tax_rate_location hashrefs
+ my %tax_rate_location = ();
+
+ foreach my $tax ( keys %$taxlisthash ) {
+ my $tax_object = shift @{ $taxlisthash->{$tax} };
+ warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
+ warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2;
+ my $hashref_or_error =
+ $tax_object->taxline( $taxlisthash->{$tax},
+ 'custnum' => $self->custnum,
+ 'invoice_time' => $invoice_time
+ );
+ return $hashref_or_error unless ref($hashref_or_error);
+
+ unshift @{ $taxlisthash->{$tax} }, $tax_object;
+
+ my $name = $hashref_or_error->{'name'};
+ my $amount = $hashref_or_error->{'amount'};
+
+ #warn "adding $amount as $name\n";
+ $taxname{ $name } ||= [];
+ push @{ $taxname{ $name } }, $tax;
+
+ $tax{ $tax } += $amount;
+
+ $tax_location{ $tax } ||= [];
+ if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
+ push @{ $tax_location{ $tax } },
+ {
+ 'taxnum' => $tax_object->taxnum,
+ 'taxtype' => ref($tax_object),
+ 'pkgnum' => $tax_object->get('pkgnum'),
+ 'locationnum' => $tax_object->get('locationnum'),
+ 'amount' => sprintf('%.2f', $amount ),
+ };
+ }
+
+ $tax_rate_location{ $tax } ||= [];
+ if ( ref($tax_object) eq 'FS::tax_rate' ) {
+ my $taxratelocationnum =
+ $tax_object->tax_rate_location->taxratelocationnum;
+ push @{ $tax_rate_location{ $tax } },
+ {
+ 'taxnum' => $tax_object->taxnum,
+ 'taxtype' => ref($tax_object),
+ 'amount' => sprintf('%.2f', $amount ),
+ 'locationtaxid' => $tax_object->location,
+ 'taxratelocationnum' => $taxratelocationnum,
+ };
+ }
+
+ }
+
+ #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
+ my %packagemap = map { $_->pkgnum => $_ } @$cust_bill_pkg;
+ foreach my $tax ( keys %$taxlisthash ) {
+ foreach ( @{ $taxlisthash->{$tax} }[1 ... scalar(@{ $taxlisthash->{$tax} })] ) {
+ next unless ref($_) eq 'FS::cust_bill_pkg';
+
+ push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg },
+ splice( @{ $_->_cust_tax_exempt_pkg } );
+ }
+ }
+
+ #consolidate and create tax line items
+ warn "consolidating and generating...\n" if $DEBUG > 2;
+ foreach my $taxname ( keys %taxname ) {
+ my $tax = 0;
+ my %seen = ();
+ my @cust_bill_pkg_tax_location = ();
+ my @cust_bill_pkg_tax_rate_location = ();
+ warn "adding $taxname\n" if $DEBUG > 1;
+ foreach my $taxitem ( @{ $taxname{$taxname} } ) {
+ next if $seen{$taxitem}++;
+ warn "adding $tax{$taxitem}\n" if $DEBUG > 1;
+ $tax += $tax{$taxitem};
+ push @cust_bill_pkg_tax_location,
+ map { new FS::cust_bill_pkg_tax_location $_ }
+ @{ $tax_location{ $taxitem } };
+ push @cust_bill_pkg_tax_rate_location,
+ map { new FS::cust_bill_pkg_tax_rate_location $_ }
+ @{ $tax_rate_location{ $taxitem } };
+ }
+ next unless $tax;
+
+ $tax = sprintf('%.2f', $tax );
+
+ my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
+ 'disabled' => '',
+ },
+ );
+
+ my @display = ();
+ if ( $pkg_category and
+ $conf->config('invoice_latexsummary') ||
+ $conf->config('invoice_htmlsummary')
+ )
+ {
+
+ my %hash = ( 'section' => $pkg_category->categoryname );
+ push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
+
+ }
+
+ push @tax_line_items, new FS::cust_bill_pkg {
+ 'pkgnum' => 0,
+ 'setup' => $tax,
+ 'recur' => 0,
+ 'sdate' => '',
+ 'edate' => '',
+ 'itemdesc' => $taxname,
+ 'display' => \@display,
+ 'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
+ 'cust_bill_pkg_tax_rate_location' => \@cust_bill_pkg_tax_rate_location,
+ };
+
+ }
+
+ \@tax_line_items;
+}
+
+sub _make_lines {
+ my ($self, %params) = @_;
+
+ my $part_pkg = $params{part_pkg} or die "no part_pkg specified";
+ my $cust_pkg = $params{cust_pkg} or die "no cust_pkg specified";
+ my $precommit_hooks = $params{precommit_hooks} or die "no package specified";
+ my $cust_bill_pkgs = $params{line_items} or die "no line buffer specified";
+ my $total_setup = $params{setup} or die "no setup accumulator specified";
+ my $total_recur = $params{recur} or die "no recur accumulator specified";
+ my $taxlisthash = $params{tax_matrix} or die "no tax accumulator specified";
+ my $time = $params{'time'} or die "no time specified";
+ my (%options) = %{$params{options}};
+
+ my $dbh = dbh;
+ my $real_pkgpart = $params{real_pkgpart};
+ my %hash = $cust_pkg->hash;
+ my $old_cust_pkg = new FS::cust_pkg \%hash;
+
+ my @details = ();
+ my @discounts = ();
+ my $lineitems = 0;
+
+ $cust_pkg->pkgpart($part_pkg->pkgpart);
+
+ ###
+ # bill setup
+ ###
+
+ my $setup = 0;
+ my $unitsetup = 0;
+ if ( $options{'resetup'}
+ || ( ! $cust_pkg->setup
+ && ( ! $cust_pkg->start_date
+ || $cust_pkg->start_date <= $time
+ )
+ && ( ! $conf->exists('disable_setup_suspended_pkgs')
+ || ( $conf->exists('disable_setup_suspended_pkgs') &&
+ ! $cust_pkg->getfield('susp')
+ )
+ )
+ )
+ )
+ {
+
+ warn " bill setup\n" if $DEBUG > 1;
+ $lineitems++;
+
+ $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
+ return "$@ running calc_setup for $cust_pkg\n"
+ if $@;
+
+ $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh
+
+ $cust_pkg->setfield('setup', $time)
+ unless $cust_pkg->setup;
+ #do need it, but it won't get written to the db
+ #|| $cust_pkg->pkgpart != $real_pkgpart;
+
+ $cust_pkg->setfield('start_date', '')
+ if $cust_pkg->start_date;
+
+ }
+
+ ###
+ # bill recurring fee
+ ###
+
+ #XXX unit stuff here too
+ my $recur = 0;
+ my $unitrecur = 0;
+ my $sdate;
+ if ( ! $cust_pkg->get('susp')
+ and ! $cust_pkg->get('start_date')
+ and ( $part_pkg->getfield('freq') ne '0'
+ && ( $cust_pkg->getfield('bill') || 0 ) <= $time
+ )
+ || ( $part_pkg->plan eq 'voip_cdr'
+ && $part_pkg->option('bill_every_call')
+ )
+ || ( $options{cancel} )
+ ) {
+
+ # XXX should this be a package event? probably. events are called
+ # at collection time at the moment, though...
+ $part_pkg->reset_usage($cust_pkg, 'debug'=>$DEBUG)
+ if $part_pkg->can('reset_usage');
+ #don't want to reset usage just cause we want a line item??
+ #&& $part_pkg->pkgpart == $real_pkgpart;
+
+ warn " bill recur\n" if $DEBUG > 1;
+ $lineitems++;
+
+ # XXX shared with $recur_prog
+ $sdate = ( $options{cancel} ? $cust_pkg->last_bill : $cust_pkg->bill )
+ || $cust_pkg->setup
+ || $time;
+
+ #over two params! lets at least switch to a hashref for the rest...
+ my $increment_next_bill = ( $part_pkg->freq ne '0'
+ && ( $cust_pkg->getfield('bill') || 0 ) <= $time
+ && !$options{cancel}
+ );
+ my %param = ( 'precommit_hooks' => $precommit_hooks,
+ 'increment_next_bill' => $increment_next_bill,
+ 'discounts' => \@discounts,
+ );
+
+ my $method = $options{cancel} ? 'calc_cancel' : 'calc_recur';
+ $recur = eval { $cust_pkg->$method( \$sdate, \@details, \%param ) };
+ return "$@ running $method for $cust_pkg\n"
+ if ( $@ );
+
+ if ( $increment_next_bill ) {
+
+ my $next_bill = $part_pkg->add_freq($sdate);
+ return "unparsable frequency: ". $part_pkg->freq
+ if $next_bill == -1;
+
+ #pro-rating magic - if $recur_prog fiddled $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;
+ #no need, its in $hash{last_bill}# my $last_bill = $cust_pkg->last_bill;
+ $cust_pkg->last_bill($sdate);
+
+ $cust_pkg->setfield('bill', $next_bill );
+
+ }
+
+ }
+
+ 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 there's line items, create em cust_bill_pkg records
+ # If $cust_pkg has been modified, update it (if we're a real pkgpart)
+ ###
+
+ if ( $lineitems ) {
+
+ if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
+ # hmm.. and if just the options are modified in some weird price plan?
+
+ warn " package ". $cust_pkg->pkgnum. " modified; updating\n"
+ if $DEBUG >1;
+
+ my $error = $cust_pkg->replace( $old_cust_pkg,
+ 'options' => { $cust_pkg->options },
+ );
+ return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error"
+ if $error; #just in case
+ }
+
+ $setup = sprintf( "%.2f", $setup );
+ $recur = sprintf( "%.2f", $recur );
+ if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
+ return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
+ }
+ if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
+ return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
+ }
+
+ if ( $setup != 0 || $recur != 0 ) {
+
+ warn " charges (setup=$setup, recur=$recur); adding line items\n"
+ if $DEBUG > 1;
+
+ my @cust_pkg_detail = map { $_->detail } $cust_pkg->cust_pkg_detail('I');
+ if ( $DEBUG > 1 ) {
+ warn " adding customer package invoice detail: $_\n"
+ foreach @cust_pkg_detail;
+ }
+ push @details, @cust_pkg_detail;
+
+ my $cust_bill_pkg = new FS::cust_bill_pkg {
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'setup' => $setup,
+ 'unitsetup' => $unitsetup,
+ 'recur' => $recur,
+ 'unitrecur' => $unitrecur,
+ 'quantity' => $cust_pkg->quantity,
+ 'details' => \@details,
+ 'discounts' => \@discounts,
+ 'hidden' => $part_pkg->hidden,
+ };
+
+ if ( $part_pkg->option('recur_temporality', 1) eq 'preceding' ) {
+ $cust_bill_pkg->sdate( $hash{last_bill} );
+ $cust_bill_pkg->edate( $sdate - 86399 ); #60s*60m*24h-1
+ $cust_bill_pkg->edate( $time ) if $options{cancel};
+ } else { #if ( $part_pkg->option('recur_temporality', 1) eq 'upcoming' ) {
+ $cust_bill_pkg->sdate( $sdate );
+ $cust_bill_pkg->edate( $cust_pkg->bill );
+ #$cust_bill_pkg->edate( $time ) if $options{cancel};
+ }
+
+ $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
+ unless $part_pkg->pkgpart == $real_pkgpart;
+
+ $$total_setup += $setup;
+ $$total_recur += $recur;
+
+ ###
+ # handle taxes
+ ###
+
+ my $error =
+ $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time}, $real_pkgpart, \%options);
+ return $error if $error;
+
+ push @$cust_bill_pkgs, $cust_bill_pkg;
+
+ } #if $setup != 0 || $recur != 0
+
+ } #if $line_items
+
+ '';
+
+}
+
+sub _handle_taxes {
+ my $self = shift;
+ my $part_pkg = shift;
+ my $taxlisthash = shift;
+ my $cust_bill_pkg = shift;
+ my $cust_pkg = shift;
+ my $invoice_time = shift;
+ my $real_pkgpart = shift;
+ my $options = shift;
+
+ my %cust_bill_pkg = ();
+ my %taxes = ();
+
+ my @classes;
+ #push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U';
+ push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->usage;
+ push @classes, 'setup' if ($cust_bill_pkg->setup && !$options->{cancel});
+ push @classes, 'recur' if ($cust_bill_pkg->recur && !$options->{cancel});
+
+ if ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
+
+ if ( $conf->exists('enable_taxproducts')
+ && ( scalar($part_pkg->part_pkg_taxoverride)
+ || $part_pkg->has_taxproduct
+ )
+ )
+ {
+
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ return "fatal: Can't (yet) use tax-pkg_address with taxproducts";
+ }
+
+ foreach my $class (@classes) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, $class );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{$class} = $err_or_ref;
+ }
+
+ unless (exists $taxes{''}) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, '' );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{''} = $err_or_ref;
+ }
+
+ } else {
+
+ my @loc_keys = qw( city county state country );
+ my %taxhash;
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ my $cust_location = $cust_pkg->cust_location;
+ %taxhash = map { $_ => $cust_location->$_() } @loc_keys;
+ } else {
+ my $prefix =
+ ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+ ? 'ship_'
+ : '';
+ %taxhash = map { $_ => $self->get("$prefix$_") } @loc_keys;
+ }
+
+ $taxhash{'taxclass'} = $part_pkg->taxclass;
+
+ my @taxes = ();
+ my %taxhash_elim = %taxhash;
+ my @elim = qw( city county state );
+ do {
+
+ #first try a match with taxclass
+ @taxes = qsearch( 'cust_main_county', \%taxhash_elim );
+
+ if ( !scalar(@taxes) && $taxhash_elim{'taxclass'} ) {
+ #then try a match without taxclass
+ my %no_taxclass = %taxhash_elim;
+ $no_taxclass{ 'taxclass' } = '';
+ @taxes = qsearch( 'cust_main_county', \%no_taxclass );
+ }
+
+ $taxhash_elim{ shift(@elim) } = '';
+
+ } while ( !scalar(@taxes) && scalar(@elim) );
+
+ @taxes = grep { ! $_->taxname or ! $self->tax_exemption($_->taxname) }
+ @taxes
+ if $self->cust_main_exemption; #just to be safe
+
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ foreach (@taxes) {
+ $_->set('pkgnum', $cust_pkg->pkgnum );
+ $_->set('locationnum', $cust_pkg->locationnum );
+ }
+ }
+
+ $taxes{''} = [ @taxes ];
+ $taxes{'setup'} = [ @taxes ];
+ $taxes{'recur'} = [ @taxes ];
+ $taxes{$_} = [ @taxes ] foreach (@classes);
+
+ # # maybe eliminate this entirely, along with all the 0% records
+ # unless ( @taxes ) {
+ # return
+ # "fatal: can't find tax rate for state/county/country/taxclass ".
+ # join('/', map $taxhash{$_}, qw(state county country taxclass) );
+ # }
+
+ } #if $conf->exists('enable_taxproducts') ...
+
+ }
+
+ my @display = ();
+ my $separate = $conf->exists('separate_usage');
+ my $usage_mandate = $cust_pkg->part_pkg->option('usage_mandate', 'Hush!');
+ if ( $separate || $cust_bill_pkg->hidden || $usage_mandate ) {
+
+ my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart };
+ my %hash = $cust_bill_pkg->hidden # maybe for all bill linked?
+ ? ( 'section' => $temp_pkg->part_pkg->categoryname )
+ : ();
+
+ my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!');
+ my $summary = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!');
+ if ( $separate ) {
+ push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
+ push @display, new FS::cust_bill_pkg_display { type => 'R', %hash };
+ } else {
+ push @display, new FS::cust_bill_pkg_display
+ { type => '',
+ %hash,
+ ( ( $usage_mandate ) ? ( 'summary' => 'Y' ) : () ),
+ };
+ }
+
+ if ($separate && $section && $summary) {
+ push @display, new FS::cust_bill_pkg_display { type => 'U',
+ summary => 'Y',
+ %hash,
+ };
+ }
+ if ($usage_mandate || $section && $summary) {
+ $hash{post_total} = 'Y';
+ }
+
+ $hash{section} = $section if ($separate || $usage_mandate);
+ push @display, new FS::cust_bill_pkg_display { type => 'U', %hash };
+
+ }
+ $cust_bill_pkg->set('display', \@display);
+
+ my %tax_cust_bill_pkg = $cust_bill_pkg->disintegrate;
+ foreach my $key (keys %tax_cust_bill_pkg) {
+ my @taxes = @{ $taxes{$key} || [] };
+ my $tax_cust_bill_pkg = $tax_cust_bill_pkg{$key};
+
+ my %localtaxlisthash = ();
+ foreach my $tax ( @taxes ) {
+
+ my $taxname = ref( $tax ). ' '. $tax->taxnum;
+# $taxname .= ' pkgnum'. $cust_pkg->pkgnum.
+# ' locationnum'. $cust_pkg->locationnum
+# if $conf->exists('tax-pkg_address') && $cust_pkg->locationnum;
+
+ $taxlisthash->{ $taxname } ||= [ $tax ];
+ push @{ $taxlisthash->{ $taxname } }, $tax_cust_bill_pkg;
+
+ $localtaxlisthash{ $taxname } ||= [ $tax ];
+ push @{ $localtaxlisthash{ $taxname } }, $tax_cust_bill_pkg;
+
+ }
+
+ warn "finding taxed taxes...\n" if $DEBUG > 2;
+ foreach my $tax ( keys %localtaxlisthash ) {
+ my $tax_object = shift @{ $localtaxlisthash{$tax} };
+ warn "found possible taxed tax ". $tax_object->taxname. " we call $tax\n"
+ if $DEBUG > 2;
+ next unless $tax_object->can('tax_on_tax');
+
+ foreach my $tot ( $tax_object->tax_on_tax( $self ) ) {
+ my $totname = ref( $tot ). ' '. $tot->taxnum;
+
+ warn "checking $totname which we call ". $tot->taxname. " as applicable\n"
+ if $DEBUG > 2;
+ next unless exists( $localtaxlisthash{ $totname } ); # only increase
+ # existing taxes
+ warn "adding $totname to taxed taxes\n" if $DEBUG > 2;
+ my $hashref_or_error =
+ $tax_object->taxline( $localtaxlisthash{$tax},
+ 'custnum' => $self->custnum,
+ 'invoice_time' => $invoice_time,
+ );
+ return $hashref_or_error
+ unless ref($hashref_or_error);
+
+ $taxlisthash->{ $totname } ||= [ $tot ];
+ push @{ $taxlisthash->{ $totname } }, $hashref_or_error->{amount};
+
+ }
+ }
+
+ }
+
+ '';
+}
+
+sub _gather_taxes {
+ my $self = shift;
+ my $part_pkg = shift;
+ my $class = shift;
+
+ my @taxes = ();
+ my $geocode = $self->geocode('cch');
+
+ my @taxclassnums = map { $_->taxclassnum }
+ $part_pkg->part_pkg_taxoverride($class);
+
+ unless (@taxclassnums) {
+ @taxclassnums = map { $_->taxclassnum }
+ grep { $_->taxable eq 'Y' }
+ $part_pkg->part_pkg_taxrate('cch', $geocode, $class);
+ }
+ warn "Found taxclassnum values of ". join(',', @taxclassnums)
+ if $DEBUG;
+
+ my $extra_sql =
+ "AND (".
+ join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")";
+
+ @taxes = qsearch({ 'table' => 'tax_rate',
+ 'hashref' => { 'geocode' => $geocode, },
+ 'extra_sql' => $extra_sql,
+ })
+ if scalar(@taxclassnums);
+
+ warn "Found taxes ".
+ join(',', map{ ref($_). " ". $_->get($_->primary_key) } @taxes). "\n"
+ if $DEBUG;
+
+ [ @taxes ];
+
+}
+
+=item collect [ HASHREF | OPTION => VALUE ... ]
+
+(Attempt to) collect money for this customer's outstanding invoices (see
+L<FS::cust_bill>). Usually used after the bill method.
+
+Actions are now triggered by billing events; see L<FS::part_event> and the
+billing events web interface. Old-style invoice events (see
+L<FS::part_bill_event>) have been deprecated.
+
+If there is an error, returns the error, otherwise returns false.
+
+Options are passed as name-value pairs.
+
+Currently available options are:
+
+=over 4
+
+=item 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.
+
+=item retry
+
+Retry card/echeck/LEC transactions even when not scheduled by invoice events.
+
+=item check_freq
+
+"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
+
+=item quiet
+
+set true to surpress email card/ACH decline notices.
+
+=item debug
+
+Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
+
+=back
+
+# =item payby
+#
+# allows for one time override of normal customer billing method
+
+=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
+
+ if ( $DEBUG ) {
+ my $balance = $self->balance;
+ warn "$me collect customer ". $self->custnum. ": balance $balance\n"
+ }
+
+ 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;
+ }
+ }
+
+ my $error = $self->do_cust_event(
+ 'debug' => ( $options{'debug'} || 0 ),
+ 'time' => $invoice_time,
+ 'check_freq' => $options{'check_freq'},
+ 'stage' => 'collect',
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item do_cust_event [ HASHREF | OPTION => VALUE ... ]
+
+Runs billing events; see L<FS::part_event> and the billing 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:
+
+=over 4
+
+=item 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.
+
+=item check_freq
+
+"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
+
+=item stage
+
+"collect" (the default) or "pre-bill"
+
+=item quiet
+
+set true to surpress email card/ACH decline notices.
+
+=item debug
+
+Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
+
+=cut
+
+# =item payby
+#
+# allows for one time override of normal customer billing method
+
+# =item retry
+#
+# Retry card/echeck/LEC transactions even when not scheduled by invoice events.
+
+sub do_cust_event {
+ my( $self, %options ) = @_;
+ my $time = $options{'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
+
+ if ( $DEBUG ) {
+ my $balance = $self->balance;
+ warn "$me do_cust_event customer ". $self->custnum. ": balance $balance\n"
+ }
+
+# 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;
+# }
+# }
+
+ # false laziness w/pay_batch::import_results
+
+ my $due_cust_event = $self->due_cust_event(
+ 'debug' => ( $options{'debug'} || 0 ),
+ 'time' => $time,
+ 'check_freq' => $options{'check_freq'},
+ 'stage' => ( $options{'stage'} || 'collect' ),
+ );
+ unless( ref($due_cust_event) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $due_cust_event;
+ }
+
+ foreach my $cust_event ( @$due_cust_event ) {
+
+ #XXX lock event
+
+ #re-eval event conditions (a previous event could have changed things)
+ unless ( $cust_event->test_conditions( 'time' => $time ) ) {
+ #don't leave stray "new/locked" records around
+ my $error = $cust_event->delete;
+ if ( $error ) {
+ #gah, even with transactions
+ $dbh->commit if $oldAutoCommit; #well.
+ return $error;
+ }
+ next;
+ }
+
+ {
+ local $realtime_bop_decline_quiet = 1 if $options{'quiet'};
+ warn " running cust_event ". $cust_event->eventnum. "\n"
+ if $DEBUG > 1;
+
+
+ #if ( my $error = $cust_event->do_event(%options) ) { #XXX %options?
+ if ( my $error = $cust_event->do_event() ) {
+ #XXX wtf is this? figure out a proper dealio with return value
+ #from do_event
+ # gah, even with transactions.
+ $dbh->commit if $oldAutoCommit; #well.
+ return $error;
+ }
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item due_cust_event [ HASHREF | OPTION => VALUE ... ]
+
+Inserts database records for and returns an ordered listref of new events due
+for this customer, as FS::cust_event objects (see L<FS::cust_event>). If no
+events are due, an empty listref is returned. If there is an error, returns a
+scalar error message.
+
+To actually run the events, call each event's test_condition method, and if
+still true, call the event's do_event method.
+
+Options are passed as a hashref or as a list of name-value pairs. Available
+options are:
+
+=over 4
+
+=item check_freq
+
+Search only for events of this check frequency (how often events of this type are checked); currently "1d" (daily, the default) and "1m" (monthly) are recognized.
+
+=item stage
+
+"collect" (the default) or "pre-bill"
+
+=item time
+
+"Current time" for the events.
+
+=item debug
+
+Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
+
+=item eventtable
+
+Only return events for the specified eventtable (by default, events of all eventtables are returned)
+
+=item objects
+
+Explicitly pass the objects to be tested (typically used with eventtable).
+
+=item testonly
+
+Set to true to return the objects, but not actually insert them into the
+database.
+
+=back
+
+=cut
+
+sub due_cust_event {
+ my $self = shift;
+ my %opt = ref($_[0]) ? %{ $_[0] } : @_;
+
+ #???
+ #my $DEBUG = $opt{'debug'}
+ local($DEBUG) = $opt{'debug'}
+ if defined($opt{'debug'}) && $opt{'debug'} > $DEBUG;
+
+ warn "$me due_cust_event called with options ".
+ join(', ', map { "$_: $opt{$_}" } keys %opt). "\n"
+ if $DEBUG;
+
+ $opt{'time'} ||= time;
+
+ 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
+ unless $opt{testonly};
+
+ ###
+ # find possible events (initial search)
+ ###
+
+ my @cust_event = ();
+
+ my @eventtable = $opt{'eventtable'}
+ ? ( $opt{'eventtable'} )
+ : FS::part_event->eventtables_runorder;
+
+ foreach my $eventtable ( @eventtable ) {
+
+ my @objects;
+ if ( $opt{'objects'} ) {
+
+ @objects = @{ $opt{'objects'} };
+
+ } else {
+
+ #my @objects = $self->eventtable(); # sub cust_main { @{ [ $self ] }; }
+ @objects = ( $eventtable eq 'cust_main' )
+ ? ( $self )
+ : ( $self->$eventtable() );
+
+ }
+
+ my @e_cust_event = ();
+
+ my $cross = "CROSS JOIN $eventtable";
+ $cross .= ' LEFT JOIN cust_main USING ( custnum )'
+ unless $eventtable eq 'cust_main';
+
+ foreach my $object ( @objects ) {
+
+ #this first search uses the condition_sql magic for optimization.
+ #the more possible events we can eliminate in this step the better
+
+ my $cross_where = '';
+ my $pkey = $object->primary_key;
+ $cross_where = "$eventtable.$pkey = ". $object->$pkey();
+
+ my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
+ my $extra_sql =
+ FS::part_event_condition->where_conditions_sql( $eventtable,
+ 'time'=>$opt{'time'}
+ );
+ my $order = FS::part_event_condition->order_conditions_sql( $eventtable );
+
+ $extra_sql = "AND $extra_sql" if $extra_sql;
+
+ #here is the agent virtualization
+ $extra_sql .= " AND ( part_event.agentnum IS NULL
+ OR part_event.agentnum = ". $self->agentnum. ' )';
+
+ $extra_sql .= " $order";
+
+ warn "searching for events for $eventtable ". $object->$pkey. "\n"
+ if $opt{'debug'} > 2;
+ my @part_event = qsearch( {
+ 'debug' => ( $opt{'debug'} > 3 ? 1 : 0 ),
+ 'select' => 'part_event.*',
+ 'table' => 'part_event',
+ 'addl_from' => "$cross $join",
+ 'hashref' => { 'check_freq' => ( $opt{'check_freq'} || '1d' ),
+ 'eventtable' => $eventtable,
+ 'disabled' => '',
+ },
+ 'extra_sql' => "AND $cross_where $extra_sql",
+ } );
+
+ if ( $DEBUG > 2 ) {
+ my $pkey = $object->primary_key;
+ warn " ". scalar(@part_event).
+ " possible events found for $eventtable ". $object->$pkey(). "\n";
+ }
+
+ push @e_cust_event, map { $_->new_cust_event($object) } @part_event;
+
+ }
+
+ warn " ". scalar(@e_cust_event).
+ " subtotal possible cust events found for $eventtable\n"
+ if $DEBUG > 1;
+
+ push @cust_event, @e_cust_event;
+
+ }
+
+ warn " ". scalar(@cust_event).
+ " total possible cust events found in initial search\n"
+ if $DEBUG; # > 1;
+
+
+ ##
+ # test stage
+ ##
+
+ $opt{stage} ||= 'collect';
+ @cust_event =
+ grep { my $stage = $_->part_event->event_stage;
+ $opt{stage} eq $stage or ( ! $stage && $opt{stage} eq 'collect' )
+ }
+ @cust_event;
+
+ ##
+ # test conditions
+ ##
+
+ my %unsat = ();
+
+ @cust_event = grep $_->test_conditions( 'time' => $opt{'time'},
+ 'stats_hashref' => \%unsat ),
+ @cust_event;
+
+ warn " ". scalar(@cust_event). " cust events left satisfying conditions\n"
+ if $DEBUG; # > 1;
+
+ warn " invalid conditions not eliminated with condition_sql:\n".
+ join('', map " $_: ".$unsat{$_}."\n", keys %unsat )
+ if keys %unsat && $DEBUG; # > 1;
+
+ ##
+ # insert
+ ##
+
+ unless( $opt{testonly} ) {
+ foreach my $cust_event ( @cust_event ) {
+
+ my $error = $cust_event->insert();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ##
+ # return
+ ##
+
+ warn " returning events: ". Dumper(@cust_event). "\n"
+ if $DEBUG > 2;
+
+ \@cust_event;
+
+}
+
+=item retry_realtime
+
+Schedules realtime / batch 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 either this customer, or 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;
+
+ #a little false laziness w/due_cust_event (not too bad, really)
+
+ my $join = FS::part_event_condition->join_conditions_sql;
+ my $order = FS::part_event_condition->order_conditions_sql;
+ my $mine =
+ '( '
+ . join ( ' OR ' , map {
+ "( part_event.eventtable = " . dbh->quote($_)
+ . " AND tablenum IN( SELECT " . dbdef->table($_)->primary_key . " from $_ where custnum = " . dbh->quote( $self->custnum ) . "))" ;
+ } FS::part_event->eventtables)
+ . ') ';
+
+ #here is the agent virtualization
+ my $agent_virt = " ( part_event.agentnum IS NULL
+ OR part_event.agentnum = ". $self->agentnum. ' )';
+
+ #XXX this shouldn't be hardcoded, actions should declare it...
+ my @realtime_events = qw(
+ cust_bill_realtime_card
+ cust_bill_realtime_check
+ cust_bill_realtime_lec
+ cust_bill_batch
+ );
+
+ my $is_realtime_event = ' ( '. join(' OR ', map "part_event.action = '$_'",
+ @realtime_events
+ ).
+ ' ) ';
+
+ my @cust_event = qsearchs({
+ 'table' => 'cust_event',
+ 'select' => 'cust_event.*',
+ 'addl_from' => "LEFT JOIN part_event USING ( eventpart ) $join",
+ 'hashref' => { 'status' => 'done' },
+ 'extra_sql' => " AND statustext IS NOT NULL AND statustext != '' ".
+ " AND $mine AND $is_realtime_event AND $agent_virt $order" # LIMIT 1"
+ });
+
+ my %seen_invnum = ();
+ foreach my $cust_event (@cust_event) {
+
+ #max one for the customer, one for each open invoice
+ my $cust_X = $cust_event->cust_X;
+ next if $seen_invnum{ $cust_event->part_event->eventtable eq 'cust_bill'
+ ? $cust_X->invnum
+ : 0
+ }++
+ or $cust_event->part_event->eventtable eq 'cust_bill'
+ && ! $cust_X->owed;
+
+ my $error = $cust_event->retry;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error scheduling event for retry: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+
+=cut
+
+=item realtime_collect [ OPTION => VALUE ... ]
+
+Runs a realtime credit card, ACH (electronic check) or phone bill transaction
+via a Business::OnlinePayment or Business::OnlineThirdPartyPayment realtime
+gateway. See L<http://420.am/business-onlinepayment> and
+L<http://420.am/business-onlinethirdpartypayment> for supported gateways.
+
+On failure returns an error message.
+
+Returns false or a hashref upon success. The hashref contains keys popup_url reference, and collectitems. The first is a URL to which a browser should be redirected for completion of collection. The second is a reference id for the transaction suitable for the end user. The collectitems is a reference to a list of name value pairs suitable for assigning to a html form and posted to popup_url.
+
+Available options are: I<method>, I<amount>, I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>, I<pkgnum>
+
+I<method> is one of: I<CC>, I<ECHECK> and I<LEC>. If none is specified
+then it is deduced from the customer record.
+
+If no I<amount> is specified, then the customer balance is used.
+
+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
+the value defined by the business-onlinepayment-description configuration
+option, or "Internet services" if that is unset.
+
+If an I<invnum> is specified, this payment (if successful) is applied to the
+specified invoice. If you don't specify an I<invnum> you might want to
+call the B<apply_payments> method or set the I<apply> option.
+
+I<apply> can be set to true to apply a resulting payment.
+
+I<quiet> can be set true to surpress email decline notices.
+
+I<paynum_ref> can be set to a scalar reference. It will be filled in with the
+resulting paynum, if any.
+
+I<payunique> is a unique identifier for this payment.
+
+I<session_id> is a session identifier associated with this payment.
+
+I<depend_jobnum> allows payment capture to unlock export jobs
+
+=cut
+
+sub realtime_collect {
+ my( $self, %options ) = @_;
+
+ if ( $DEBUG ) {
+ warn "$me realtime_collect:\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ $options{amount} = $self->balance unless exists( $options{amount} );
+ $options{method} = FS::payby->payby2bop($self->payby)
+ unless exists( $options{method} );
+
+ return $self->realtime_bop({%options});
+
+}
+
+=item realtime_bop { [ ARG => 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.
+
+Required arguments in the hashref are I<method>, and I<amount>
+
+Available methods are: I<CC>, I<ECHECK> and I<LEC>
+
+Available optional arguments are: I<description>, I<invnum>, I<apply>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
+
+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
+the value defined by the business-onlinepayment-description configuration
+option, or "Internet services" if that is unset.
+
+If an I<invnum> is specified, this payment (if successful) is applied to the
+specified invoice. If you don't specify an I<invnum> you might want to
+call the B<apply_payments> method or set the I<apply> option.
+
+I<apply> can be set to true to apply a resulting payment.
+
+I<quiet> can be set true to surpress email decline notices.
+
+I<paynum_ref> can be set to a scalar reference. It will be filled in with the
+resulting paynum, if any.
+
+I<payunique> is a unique identifier for this payment.
+
+I<session_id> is a session identifier associated with this payment.
+
+I<depend_jobnum> allows payment capture to unlock export jobs
+
+(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
+
+=cut
+
+# some helper routines
+sub _bop_recurring_billing {
+ my( $self, %opt ) = @_;
+
+ my $method = scalar($conf->config('credit_card-recurring_billing_flag'));
+
+ if ( defined($method) && $method eq 'transaction_is_recur' ) {
+
+ return 1 if $opt{'trans_is_recur'};
+
+ } else {
+
+ my %hash = ( 'custnum' => $self->custnum,
+ 'payby' => 'CARD',
+ );
+
+ return 1
+ if qsearch('cust_pay', { %hash, 'payinfo' => $opt{'payinfo'} } )
+ || qsearch('cust_pay', { %hash, 'paymask' => $self->mask_payinfo('CARD',
+ $opt{'payinfo'} )
+ } );
+
+ }
+
+ return 0;
+
+}
+
+sub _payment_gateway {
+ my ($self, $options) = @_;
+
+ $options->{payment_gateway} = $self->agent->payment_gateway( %$options )
+ unless exists($options->{payment_gateway});
+
+ $options->{payment_gateway};
+}
+
+sub _bop_auth {
+ my ($self, $options) = @_;
+
+ (
+ 'login' => $options->{payment_gateway}->gateway_username,
+ 'password' => $options->{payment_gateway}->gateway_password,
+ );
+}
+
+sub _bop_options {
+ my ($self, $options) = @_;
+
+ $options->{payment_gateway}->gatewaynum
+ ? $options->{payment_gateway}->options
+ : @{ $options->{payment_gateway}->get('options') };
+}
+
+sub _bop_defaults {
+ my ($self, $options) = @_;
+
+ unless ( $options->{'description'} ) {
+ if ( $conf->exists('business-onlinepayment-description') ) {
+ my $dtempl = $conf->config('business-onlinepayment-description');
+
+ my $agent = $self->agent->agent;
+ #$pkgs... not here
+ $options->{'description'} = eval qq("$dtempl");
+ } else {
+ $options->{'description'} = 'Internet services';
+ }
+ }
+
+ $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} );
+ $options->{invnum} ||= '';
+ $options->{payname} = $self->payname unless exists( $options->{payname} );
+}
+
+sub _bop_content {
+ my ($self, $options) = @_;
+ my %content = ();
+
+ $content{address} = exists($options->{'address1'})
+ ? $options->{'address1'}
+ : $self->address1;
+ my $address2 = exists($options->{'address2'})
+ ? $options->{'address2'}
+ : $self->address2;
+ $content{address} .= ", ". $address2 if length($address2);
+
+ my $payip = exists($options->{'payip'}) ? $options->{'payip'} : $self->payip;
+ $content{customer_ip} = $payip if length($payip);
+
+ $content{invoice_number} = $options->{'invnum'}
+ if exists($options->{'invnum'}) && length($options->{'invnum'});
+
+ $content{email_customer} =
+ ( $conf->exists('business-onlinepayment-email_customer')
+ || $conf->exists('business-onlinepayment-email-override') );
+
+ $content{payfirst} = $self->getfield('first');
+ $content{paylast} = $self->getfield('last');
+
+ $content{account_name} = "$content{payfirst} $content{paylast}"
+ if $options->{method} eq 'ECHECK';
+
+ $content{name} = $options->{payname};
+ $content{name} = $content{account_name} if exists($content{account_name});
+
+ $content{city} = exists($options->{city})
+ ? $options->{city}
+ : $self->city;
+ $content{state} = exists($options->{state})
+ ? $options->{state}
+ : $self->state;
+ $content{zip} = exists($options->{zip})
+ ? $options->{'zip'}
+ : $self->zip;
+ $content{country} = exists($options->{country})
+ ? $options->{country}
+ : $self->country;
+ $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/
+ $content{phone} = $self->daytime || $self->night;
+
+ (%content);
+}
+
+my %bop_method2payby = (
+ 'CC' => 'CARD',
+ 'ECHECK' => 'CHEK',
+ 'LEC' => 'LECB',
+);
+
+sub realtime_bop {
+ my $self = shift;
+
+ my %options = ();
+ if (ref($_[0]) eq 'HASH') {
+ %options = %{$_[0]};
+ } else {
+ my ( $method, $amount ) = ( shift, shift );
+ %options = @_;
+ $options{method} = $method;
+ $options{amount} = $amount;
+ }
+
+ if ( $DEBUG ) {
+ warn "$me realtime_bop (new): $options{method} $options{amount}\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ return $self->fake_bop(%options) if $options{'fake'};
+
+ $self->_bop_defaults(\%options);
+
+ ###
+ # set trans_is_recur based on invnum if there is one
+ ###
+
+ my $trans_is_recur = 0;
+ if ( $options{'invnum'} ) {
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{'invnum'} } );
+ die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
+
+ my @part_pkg =
+ map { $_->part_pkg }
+ grep { $_ }
+ map { $_->cust_pkg }
+ $cust_bill->cust_bill_pkg;
+
+ $trans_is_recur = 1
+ if grep { $_->freq ne '0' } @part_pkg;
+
+ }
+
+ ###
+ # select a gateway
+ ###
+
+ my $payment_gateway = $self->_payment_gateway( \%options );
+ my $namespace = $payment_gateway->gateway_namespace;
+
+ eval "use $namespace";
+ die $@ if $@;
+
+ ###
+ # check for banned credit card/ACH
+ ###
+
+ my $ban = qsearchs('banned_pay', {
+ 'payby' => $bop_method2payby{$options{method}},
+ 'payinfo' => md5_base64($options{payinfo}),
+ } );
+ return "Banned credit card" if $ban;
+
+ ###
+ # massage data
+ ###
+
+ my (%bop_content) = $self->_bop_content(\%options);
+
+ if ( $options{method} ne 'ECHECK' ) {
+ $options{payname} =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+ or return "Illegal payname $options{payname}";
+ ($bop_content{payfirst}, $bop_content{paylast}) = ($1, $2);
+ }
+
+ my @invoicing_list = $self->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $self->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $paydate = '';
+ my %content = ();
+ if ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'CC' ) {
+
+ $content{card_number} = $options{payinfo};
+ $paydate = exists($options{'paydate'})
+ ? $options{'paydate'}
+ : $self->paydate;
+ $paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ $content{expiration} = "$2/$1";
+
+ my $paycvv = exists($options{'paycvv'})
+ ? $options{'paycvv'}
+ : $self->paycvv;
+ $content{cvv2} = $paycvv
+ if length($paycvv);
+
+ my $paystart_month = exists($options{'paystart_month'})
+ ? $options{'paystart_month'}
+ : $self->paystart_month;
+
+ my $paystart_year = exists($options{'paystart_year'})
+ ? $options{'paystart_year'}
+ : $self->paystart_year;
+
+ $content{card_start} = "$paystart_month/$paystart_year"
+ if $paystart_month && $paystart_year;
+
+ my $payissue = exists($options{'payissue'})
+ ? $options{'payissue'}
+ : $self->payissue;
+ $content{issue_number} = $payissue if $payissue;
+
+ if ( $self->_bop_recurring_billing( 'payinfo' => $options{'payinfo'},
+ 'trans_is_recur' => $trans_is_recur,
+ )
+ )
+ {
+ $content{recurring_billing} = 'YES';
+ $content{acct_code} = 'rebill'
+ if $conf->exists('credit_card-recurring_billing_acct_code');
+ }
+
+ } elsif ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'ECHECK' ){
+ ( $content{account_number}, $content{routing_code} ) =
+ split('@', $options{payinfo});
+ $content{bank_name} = $options{payname};
+ $content{bank_state} = exists($options{'paystate'})
+ ? $options{'paystate'}
+ : $self->getfield('paystate');
+ $content{account_type} = exists($options{'paytype'})
+ ? uc($options{'paytype'}) || 'CHECKING'
+ : uc($self->getfield('paytype')) || 'CHECKING';
+ $content{customer_org} = $self->company ? 'B' : 'I';
+ $content{state_id} = exists($options{'stateid'})
+ ? $options{'stateid'}
+ : $self->getfield('stateid');
+ $content{state_id_state} = exists($options{'stateid_state'})
+ ? $options{'stateid_state'}
+ : $self->getfield('stateid_state');
+ $content{customer_ssn} = exists($options{'ss'})
+ ? $options{'ss'}
+ : $self->ss;
+ } elsif ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'LEC' ) {
+ $content{phone} = $options{payinfo};
+ } elsif ( $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+ #move along
+ } else {
+ #die an evil death
+ }
+
+ ###
+ # run transaction(s)
+ ###
+
+ my $balance = exists( $options{'balance'} )
+ ? $options{'balance'}
+ : $self->balance;
+
+ $self->select_for_update; #mutex ... just until we get our pending record in
+
+ #the checks here are intended to catch concurrent payments
+ #double-form-submission prevention is taken care of in cust_pay_pending::check
+
+ #check the balance
+ return "The customer's balance has changed; $options{method} transaction aborted."
+ if $self->balance < $balance;
+ #&& $self->balance < $options{amount}; #might as well anyway?
+
+ #also check and make sure there aren't *other* pending payments for this cust
+
+ my @pending = qsearch('cust_pay_pending', {
+ 'custnum' => $self->custnum,
+ 'status' => { op=>'!=', value=>'done' }
+ });
+ return "A payment is already being processed for this customer (".
+ join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ).
+ "); $options{method} transaction aborted."
+ if scalar(@pending);
+
+ #okay, good to go, if we're a duplicate, cust_pay_pending will kick us out
+
+ my $cust_pay_pending = new FS::cust_pay_pending {
+ 'custnum' => $self->custnum,
+ #'invnum' => $options{'invnum'},
+ 'paid' => $options{amount},
+ '_date' => '',
+ 'payby' => $bop_method2payby{$options{method}},
+ 'payinfo' => $options{payinfo},
+ 'paydate' => $paydate,
+ 'recurring_billing' => $content{recurring_billing},
+ 'pkgnum' => $options{'pkgnum'},
+ 'status' => 'new',
+ 'gatewaynum' => $payment_gateway->gatewaynum || '',
+ 'session_id' => $options{session_id} || '',
+ 'jobnum' => $options{depend_jobnum} || '',
+ };
+ $cust_pay_pending->payunique( $options{payunique} )
+ if defined($options{payunique}) && length($options{payunique});
+ my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted
+ return $cpp_new_err if $cpp_new_err;
+
+ my( $action1, $action2 ) =
+ split( /\s*\,\s*/, $payment_gateway->gateway_action );
+
+ my $transaction = new $namespace( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
+ $transaction->content(
+ 'type' => $options{method},
+ $self->_bop_auth(\%options),
+ 'action' => $action1,
+ 'description' => $options{'description'},
+ 'amount' => $options{amount},
+ #'invoice_number' => $options{'invnum'},
+ 'customer_id' => $self->custnum,
+ %bop_content,
+ 'reference' => $cust_pay_pending->paypendingnum, #for now
+ 'email' => $email,
+ %content, #after
+ );
+
+ $cust_pay_pending->status('pending');
+ my $cpp_pending_err = $cust_pay_pending->replace;
+ return $cpp_pending_err if $cpp_pending_err;
+
+ #config?
+ my $BOP_TESTING = 0;
+ my $BOP_TESTING_SUCCESS = 1;
+
+ unless ( $BOP_TESTING ) {
+ $transaction->submit();
+ } else {
+ if ( $BOP_TESTING_SUCCESS ) {
+ $transaction->is_success(1);
+ $transaction->authorization('fake auth');
+ } else {
+ $transaction->is_success(0);
+ $transaction->error_message('fake failure');
+ }
+ }
+
+ if ( $transaction->is_success() && $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+
+ return { reference => $cust_pay_pending->paypendingnum,
+ map { $_ => $transaction->$_ } qw ( popup_url collectitems ) };
+
+ } elsif ( $transaction->is_success() && $action2 ) {
+
+ $cust_pay_pending->status('authorized');
+ my $cpp_authorized_err = $cust_pay_pending->replace;
+ return $cpp_authorized_err if $cpp_authorized_err;
+
+ my $auth = $transaction->authorization;
+ my $ordernum = $transaction->can('order_number')
+ ? $transaction->order_number
+ : '';
+
+ my $capture =
+ new Business::OnlinePayment( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
+ my %capture = (
+ %content,
+ type => $options{method},
+ action => $action2,
+ $self->_bop_auth(\%options),
+ order_number => $ordernum,
+ amount => $options{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 successful 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($options{payinfo}) } $conf->config('cvv-save')
+ ) {
+ my $error = $self->remove_cvv;
+ if ( $error ) {
+ warn "WARNING: error removing cvv: $error\n";
+ }
+ }
+
+ ###
+ # result handling
+ ###
+
+ $self->_realtime_bop_result( $cust_pay_pending, $transaction, %options );
+
+}
+
+=item fake_bop
+
+=cut
+
+sub fake_bop {
+ my $self = shift;
+
+ my %options = ();
+ if (ref($_[0]) eq 'HASH') {
+ %options = %{$_[0]};
+ } else {
+ my ( $method, $amount ) = ( shift, shift );
+ %options = @_;
+ $options{method} = $method;
+ $options{amount} = $amount;
+ }
+
+ if ( $options{'fake_failure'} ) {
+ return "Error: No error; test failure requested with fake_failure";
+ }
+
+ #my $paybatch = '';
+ #if ( $payment_gateway->gatewaynum ) { # agent override
+ # $paybatch = $payment_gateway->gatewaynum. '-';
+ #}
+ #
+ #$paybatch .= "$processor:". $transaction->authorization;
+ #
+ #$paybatch .= ':'. $transaction->order_number
+ # if $transaction->can('order_number')
+ # && length($transaction->order_number);
+
+ my $paybatch = 'FakeProcessor:54:32';
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'custnum' => $self->custnum,
+ 'invnum' => $options{'invnum'},
+ 'paid' => $options{amount},
+ '_date' => '',
+ 'payby' => $bop_method2payby{$options{method}},
+ #'payinfo' => $payinfo,
+ 'payinfo' => '4111111111111111',
+ 'paybatch' => $paybatch,
+ #'paydate' => $paydate,
+ 'paydate' => '2012-05-01',
+ } );
+ $cust_pay->payunique( $options{payunique} ) if length($options{payunique});
+
+ my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
+
+ if ( $error ) {
+ $cust_pay->invnum(''); #try again with no specific invnum
+ my $error2 = $cust_pay->insert( $options{'manual'} ?
+ ( 'manual' => 1 ) : ()
+ );
+ if ( $error2 ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH debited but database not updated - '.
+ "error inserting (fake!) payment: $error2".
+ " (previously tried insert with invnum #$options{'invnum'}" .
+ ": $error )";
+ warn $e;
+ return $e;
+ }
+ }
+
+ if ( $options{'paynum_ref'} ) {
+ ${ $options{'paynum_ref'} } = $cust_pay->paynum;
+ }
+
+ return ''; #no error
+
+}
+
+
+# item _realtime_bop_result CUST_PAY_PENDING, BOP_OBJECT [ OPTION => VALUE ... ]
+#
+# Wraps up processing of a realtime credit card, ACH (electronic check) or
+# phone bill transaction.
+
+sub _realtime_bop_result {
+ my( $self, $cust_pay_pending, $transaction, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$me _realtime_bop_result: pending transaction ".
+ $cust_pay_pending->paypendingnum. "\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ my $payment_gateway = $options{payment_gateway}
+ or return "no payment gateway in arguments to _realtime_bop_result";
+
+ $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
+ my $cpp_captured_err = $cust_pay_pending->replace;
+ return $cpp_captured_err if $cpp_captured_err;
+
+ if ( $transaction->is_success() ) {
+
+ my $paybatch = '';
+ if ( $payment_gateway->gatewaynum ) { # agent override
+ $paybatch = $payment_gateway->gatewaynum. '-';
+ }
+
+ $paybatch .= $payment_gateway->gateway_module. ":".
+ $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' => $cust_pay_pending->paid,
+ '_date' => '',
+ 'payby' => $cust_pay_pending->payby,
+ #'payinfo' => $payinfo,
+ 'paybatch' => $paybatch,
+ 'paydate' => $cust_pay_pending->paydate,
+ 'pkgnum' => $cust_pay_pending->pkgnum,
+ } );
+ #doesn't hurt to know, even though the dup check is in cust_pay_pending now
+ $cust_pay->payunique( $options{payunique} )
+ if defined($options{payunique}) && length($options{payunique});
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
+
+ my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
+
+ if ( $error ) {
+ $cust_pay->invnum(''); #try again with no specific invnum
+ my $error2 = $cust_pay->insert( $options{'manual'} ?
+ ( 'manual' => 1 ) : ()
+ );
+ if ( $error2 ) {
+ # gah. but at least we have a record of the state we had to abort in
+ # from cust_pay_pending now.
+ my $e = "WARNING: $options{method} captured but payment not recorded -".
+ " error inserting payment (". $payment_gateway->gateway_module.
+ "): $error2".
+ " (previously tried insert with invnum #$options{'invnum'}" .
+ ": $error ) - pending payment saved as paypendingnum ".
+ $cust_pay_pending->paypendingnum. "\n";
+ warn $e;
+ return $e;
+ }
+ }
+
+ my $jobnum = $cust_pay_pending->jobnum;
+ if ( $jobnum ) {
+ my $placeholder = qsearchs( 'queue', { 'jobnum' => $jobnum } );
+
+ unless ( $placeholder ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: $options{method} captured but job $jobnum not ".
+ "found for paypendingnum ". $cust_pay_pending->paypendingnum. "\n";
+ warn $e;
+ return $e;
+ }
+
+ $error = $placeholder->delete;
+
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: $options{method} captured but could not delete ".
+ "job $jobnum for paypendingnum ".
+ $cust_pay_pending->paypendingnum. ": $error\n";
+ warn $e;
+ return $e;
+ }
+
+ }
+
+ if ( $options{'paynum_ref'} ) {
+ ${ $options{'paynum_ref'} } = $cust_pay->paynum;
+ }
+
+ $cust_pay_pending->status('done');
+ $cust_pay_pending->statustext('captured');
+ $cust_pay_pending->paynum($cust_pay->paynum);
+ my $cpp_done_err = $cust_pay_pending->replace;
+
+ if ( $cpp_done_err ) {
+
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: $options{method} captured but payment not recorded - ".
+ "error updating status for paypendingnum ".
+ $cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
+ warn $e;
+ return $e;
+
+ } else {
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ if ( $options{'apply'} ) {
+ my $apply_error = $self->apply_payments_and_credits;
+ if ( $apply_error ) {
+ warn "WARNING: error applying payment: $apply_error\n";
+ #but we still should return no error cause the payment otherwise went
+ #through...
+ }
+ }
+
+ return ''; #no error
+
+ }
+
+ } else {
+
+ my $perror = $payment_gateway->gateway_module. " error: ".
+ $transaction->error_message;
+
+ my $jobnum = $cust_pay_pending->jobnum;
+ if ( $jobnum ) {
+ my $placeholder = qsearchs( 'queue', { 'jobnum' => $jobnum } );
+
+ if ( $placeholder ) {
+ my $error = $placeholder->depended_delete;
+ $error ||= $placeholder->delete;
+ warn "error removing provisioning jobs after declined paypendingnum ".
+ $cust_pay_pending->paypendingnum. "\n";
+ } else {
+ my $e = "error finding job $jobnum for declined paypendingnum ".
+ $cust_pay_pending->paypendingnum. "\n";
+ warn $e;
+ }
+
+ }
+
+ unless ( $transaction->error_message ) {
+
+ my $t_response;
+ if ( $transaction->can('response_page') ) {
+ $t_response = {
+ 'page' => ( $transaction->can('response_page')
+ ? $transaction->response_page
+ : ''
+ ),
+ 'code' => ( $transaction->can('response_code')
+ ? $transaction->response_code
+ : ''
+ ),
+ 'headers' => ( $transaction->can('response_headers')
+ ? $transaction->response_headers
+ : ''
+ ),
+ };
+ } else {
+ $t_response .=
+ "No additional debugging information available for ".
+ $payment_gateway->gateway_module;
+ }
+
+ $perror .= "No error_message returned from ".
+ $payment_gateway->gateway_module. " -- ".
+ ( ref($t_response) ? Dumper($t_response) : $t_response );
+
+ }
+
+ 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 = {
+ 'company_name' =>
+ scalar( $conf->config('company_name', $self->agentnum ) ),
+ 'company_address' =>
+ join("\n", $conf->config('company_address', $self->agentnum ) ),
+ 'error' => $transaction->error_message,
+ };
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->agentnum ),
+ '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;
+
+ }
+
+ $cust_pay_pending->status('done');
+ $cust_pay_pending->statustext("declined: $perror");
+ my $cpp_done_err = $cust_pay_pending->replace;
+ if ( $cpp_done_err ) {
+ my $e = "WARNING: $options{method} declined but pending payment not ".
+ "resolved - error updating status for paypendingnum ".
+ $cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
+ warn $e;
+ $perror = "$e ($perror)";
+ }
+
+ return $perror;
+ }
+
+}
+
+=item realtime_botpp_capture CUST_PAY_PENDING [ OPTION => VALUE ... ]
+
+Verifies successful third party processing of a realtime credit card,
+ACH (electronic check) or phone bill transaction via a
+Business::OnlineThirdPartyPayment realtime gateway. See
+L<http://420.am/business-onlinethirdpartypayment> for supported gateways.
+
+Available options are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>
+
+The additional options I<payname>, 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 successful) 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.
+
+I<paynum_ref> can be set to a scalar reference. It will be filled in with the
+resulting paynum, if any.
+
+I<payunique> is a unique identifier for this payment.
+
+Returns a hashref containing elements bill_error (which will be undefined
+upon success) and session_id of any associated session.
+
+=cut
+
+sub realtime_botpp_capture {
+ my( $self, $cust_pay_pending, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$me realtime_botpp_capture: pending transaction $cust_pay_pending\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ eval "use Business::OnlineThirdPartyPayment";
+ die $@ if $@;
+
+ ###
+ # select the gateway
+ ###
+
+ my $method = FS::payby->payby2bop($cust_pay_pending->payby);
+
+ my $payment_gateway = $cust_pay_pending->gatewaynum
+ ? qsearchs( 'payment_gateway',
+ { gatewaynum => $cust_pay_pending->gatewaynum }
+ )
+ : $self->agent->payment_gateway( 'method' => $method,
+ # 'invnum' => $cust_pay_pending->invnum,
+ # 'payinfo' => $cust_pay_pending->payinfo,
+ );
+
+ $options{payment_gateway} = $payment_gateway; # for the helper subs
+
+ ###
+ # massage data
+ ###
+
+ my @invoicing_list = $self->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $self->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my %content = ();
+
+ $content{email_customer} =
+ ( $conf->exists('business-onlinepayment-email_customer')
+ || $conf->exists('business-onlinepayment-email-override') );
+
+ ###
+ # run transaction(s)
+ ###
+
+ my $transaction =
+ new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
+ $transaction->reference({ %options });
+
+ $transaction->content(
+ 'type' => $method,
+ $self->_bop_auth(\%options),
+ 'action' => 'Post Authorization',
+ 'description' => $options{'description'},
+ 'amount' => $cust_pay_pending->paid,
+ #'invoice_number' => $options{'invnum'},
+ 'customer_id' => $self->custnum,
+ 'referer' => 'http://cleanwhisker.420.am/',
+ 'reference' => $cust_pay_pending->paypendingnum,
+ 'email' => $email,
+ 'phone' => $self->daytime || $self->night,
+ %content, #after
+ # plus whatever is required for bogus capture avoidance
+ );
+
+ $transaction->submit();
+
+ my $error =
+ $self->_realtime_bop_result( $cust_pay_pending, $transaction, %options );
+
+ {
+ bill_error => $error,
+ session_id => $cust_pay_pending->session_id,
+ }
+
+}
+
+=item default_payment_gateway DEPRECATED -- use agent->payment_gateway
+
+=cut
+
+sub default_payment_gateway {
+ my( $self, $method ) = @_;
+
+ die "Real-time processing not enabled\n"
+ unless $conf->exists('business-onlinepayment');
+
+ #warn "default_payment_gateway deprecated -- use agent->payment_gateway\n";
+
+ #load up config
+ my $bop_config = 'business-onlinepayment';
+ $bop_config .= '-ach'
+ if $method =~ /^(ECHECK|CHEK)$/ && $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;
+
+ ( $processor, $login, $password, $action, @bop_options )
+}
+
+=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>, I<paydate>
+
+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.
+
+I<paydate> specifies the expiration date for a credit card overriding the
+value from the customer record or the payment record. Specified as yyyy-mm-dd
+
+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 successful) 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 = shift;
+
+ my %options = ();
+ if (ref($_[0]) ne 'HASH') {
+ %options = %{$_[0]};
+ } else {
+ my $method = shift;
+ %options = @_;
+ $options{method} = $method;
+ }
+
+ if ( $DEBUG ) {
+ warn "$me realtime_refund_bop (new): $options{method} refund\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ ###
+ # look up the original payment and optionally a gateway for that payment
+ ###
+
+ my $cust_pay = '';
+ my $amount = $options{'amount'};
+
+ my( $processor, $login, $password, @bop_options, $namespace ) ;
+ my( $auth, $order_number ) = ( '', '', '' );
+
+ if ( $options{'paynum'} ) {
+
+ warn " paynum: $options{paynum}\n" if $DEBUG > 1;
+ $cust_pay = qsearchs('cust_pay', { paynum=>$options{'paynum'} } )
+ or return "Unknown paynum $options{'paynum'}";
+ $amount ||= $cust_pay->paid;
+
+ $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
+ or return "Can't parse paybatch for paynum $options{'paynum'}: ".
+ $cust_pay->paybatch;
+ my $gatewaynum = '';
+ ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
+
+ if ( $gatewaynum ) { #gateway for the payment to be refunded
+
+ my $payment_gateway =
+ qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
+ die "payment gateway $gatewaynum not found"
+ unless $payment_gateway;
+
+ $processor = $payment_gateway->gateway_module;
+ $login = $payment_gateway->gateway_username;
+ $password = $payment_gateway->gateway_password;
+ $namespace = $payment_gateway->gateway_namespace;
+ @bop_options = $payment_gateway->options;
+
+ } else { #try the default gateway
+
+ my $conf_processor;
+ my $payment_gateway =
+ $self->agent->payment_gateway('method' => $options{method});
+
+ ( $conf_processor, $login, $password, $namespace ) =
+ map { my $method = "gateway_$_"; $payment_gateway->$method }
+ qw( module username password namespace );
+
+ @bop_options = $payment_gateway->gatewaynum
+ ? $payment_gateway->options
+ : @{ $payment_gateway->get('options') };
+
+ return "processor of payment $options{'paynum'} $processor does not".
+ " match default processor $conf_processor"
+ unless $processor eq $conf_processor;
+
+ }
+
+
+ } else { # didn't specify a paynum, so look for agent gateway overrides
+ # like a normal transaction
+
+ my $payment_gateway =
+ $self->agent->payment_gateway( 'method' => $options{method},
+ #'payinfo' => $payinfo,
+ );
+ my( $processor, $login, $password, $namespace ) =
+ map { my $method = "gateway_$_"; $payment_gateway->$method }
+ qw( module username password namespace );
+
+ my @bop_options = $payment_gateway->gatewaynum
+ ? $payment_gateway->options
+ : @{ $payment_gateway->get('options') };
+
+ }
+ return "neither amount nor paynum specified" unless $amount;
+
+ eval "use $namespace";
+ die $@ if $@;
+
+ my %content = (
+ 'type' => $options{method},
+ 'login' => $login,
+ 'password' => $password,
+ 'order_number' => $order_number,
+ 'amount' => $amount,
+ 'referer' => 'http://cleanwhisker.420.am/', #XXX fix referer :/
+ );
+ $content{authorization} = $auth
+ if length($auth); #echeck/ACH transactions have an order # but no auth
+ #(at least with authorize.net)
+
+ my $disable_void_after;
+ if ($conf->exists('disable_void_after')
+ && $conf->config('disable_void_after') =~ /^(\d+)$/) {
+ $disable_void_after = $1;
+ }
+
+ #first try void if applicable
+ if ( $cust_pay && $cust_pay->paid == $amount
+ && (
+ ( not defined($disable_void_after) )
+ || ( time < ($cust_pay->_date + $disable_void_after ) )
+ )
+ ) {
+ warn " attempting void\n" if $DEBUG > 1;
+ my $void = new Business::OnlinePayment( $processor, @bop_options );
+ if ( $void->can('info') ) {
+ if ( $cust_pay->payby eq 'CARD'
+ && $void->info('CC_void_requires_card') )
+ {
+ $content{'card_number'} = $cust_pay->payinfo;
+ } elsif ( $cust_pay->payby eq 'CHEK'
+ && $void->info('ECHECK_void_requires_account') )
+ {
+ ( $content{'account_number'}, $content{'routing_code'} ) =
+ split('@', $cust_pay->payinfo);
+ $content{'name'} = $self->get('first'). ' '. $self->get('last');
+ }
+ }
+ $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;
+ }
+ warn " void successful\n" if $DEBUG > 1;
+ return '';
+ }
+ }
+
+ warn " void unsuccessful, trying refund\n"
+ if $DEBUG > 1;
+
+ #massage data
+ my $address = $self->address1;
+ $address .= ", ". $self->address2 if $self->address2;
+
+ my($payname, $payfirst, $paylast);
+ if ( $self->payname && $options{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";
+ }
+
+ my @invoicing_list = $self->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $self->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $payip = exists($options{'payip'})
+ ? $options{'payip'}
+ : $self->payip;
+ $content{customer_ip} = $payip
+ if length($payip);
+
+ my $payinfo = '';
+ if ( $options{method} eq 'CC' ) {
+
+ if ( $cust_pay ) {
+ $content{card_number} = $payinfo = $cust_pay->payinfo;
+ (exists($options{'paydate'}) ? $options{'paydate'} : $cust_pay->paydate)
+ =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/ &&
+ ($content{expiration} = "$2/$1"); # where available
+ } else {
+ $content{card_number} = $payinfo = $self->payinfo;
+ (exists($options{'paydate'}) ? $options{'paydate'} : $self->paydate)
+ =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ $content{expiration} = "$2/$1";
+ }
+
+ } elsif ( $options{method} eq 'ECHECK' ) {
+
+ if ( $cust_pay ) {
+ $payinfo = $cust_pay->payinfo;
+ } else {
+ $payinfo = $self->payinfo;
+ }
+ ( $content{account_number}, $content{routing_code} )= split('@', $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 ( $options{method} eq 'LEC' ) {
+ $content{phone} = $payinfo = $self->payinfo;
+ }
+
+ #then try refund
+ my $refund = new Business::OnlinePayment( $processor, @bop_options );
+ my %sub_content = $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,
+ 'email' => $email,
+ 'phone' => $self->daytime || $self->night,
+ %content, #after
+ );
+ warn join('', map { " $_ => $sub_content{$_}\n" } keys %sub_content )
+ if $DEBUG > 1;
+ $refund->submit();
+
+ return "$processor error: ". $refund->error_message
+ unless $refund->is_success();
+
+ my $paybatch = "$processor:". $refund->authorization;
+ $paybatch .= ':'. $refund->order_number
+ if $refund->can('order_number') && $refund->order_number;
+
+ while ( $cust_pay && $cust_pay->unapplied < $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' => $bop_method2payby{$options{method}},
+ 'payinfo' => $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 batch_card OPTION => VALUE...
+
+Adds a payment for this invoice to the pending credit card batch (see
+L<FS::cust_pay_batch>), or, if the B<realtime> option is set to a true value,
+runs the payment using a realtime gateway.
+
+=cut
+
+sub batch_card {
+ my ($self, %options) = @_;
+
+ my $amount;
+ if (exists($options{amount})) {
+ $amount = $options{amount};
+ }else{
+ $amount = sprintf("%.2f", $self->balance - $self->in_transit_payments);
+ }
+ return '' unless $amount > 0;
+
+ my $invnum = delete $options{invnum};
+ my $payby = $options{invnum} || $self->payby; #dubious
+
+ if ($options{'realtime'}) {
+ return $self->realtime_bop( FS::payby->payby2bop($self->payby),
+ $amount,
+ %options,
+ );
+ }
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #this needs to handle mysql as well as Pg, like svc_acct.pm
+ #(make it into a common function if folks need to do batching with mysql)
+ $dbh->do("LOCK TABLE pay_batch IN SHARE ROW EXCLUSIVE MODE")
+ or return "Cannot lock pay_batch: " . $dbh->errstr;
+
+ my %pay_batch = (
+ 'status' => 'O',
+ 'payby' => FS::payby->payby2payment($payby),
+ );
+
+ my $pay_batch = qsearchs( 'pay_batch', \%pay_batch );
+
+ unless ( $pay_batch ) {
+ $pay_batch = new FS::pay_batch \%pay_batch;
+ my $error = $pay_batch->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die "error creating new batch: $error\n";
+ }
+ }
+
+ my $old_cust_pay_batch = qsearchs('cust_pay_batch', {
+ 'batchnum' => $pay_batch->batchnum,
+ 'custnum' => $self->custnum,
+ } );
+
+ foreach (qw( address1 address2 city state zip country payby payinfo paydate
+ payname )) {
+ $options{$_} = '' unless exists($options{$_});
+ }
+
+ my $cust_pay_batch = new FS::cust_pay_batch ( {
+ 'batchnum' => $pay_batch->batchnum,
+ 'invnum' => $invnum || 0, # is there a better value?
+ # this field should be
+ # removed...
+ # cust_bill_pay_batch now
+ 'custnum' => $self->custnum,
+ 'last' => $self->getfield('last'),
+ 'first' => $self->getfield('first'),
+ 'address1' => $options{address1} || $self->address1,
+ 'address2' => $options{address2} || $self->address2,
+ 'city' => $options{city} || $self->city,
+ 'state' => $options{state} || $self->state,
+ 'zip' => $options{zip} || $self->zip,
+ 'country' => $options{country} || $self->country,
+ 'payby' => $options{payby} || $self->payby,
+ 'payinfo' => $options{payinfo} || $self->payinfo,
+ 'exp' => $options{paydate} || $self->paydate,
+ 'payname' => $options{payname} || $self->payname,
+ 'amount' => $amount, # consolidating
+ } );
+
+ $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum)
+ if $old_cust_pay_batch;
+
+ my $error;
+ if ($old_cust_pay_batch) {
+ $error = $cust_pay_batch->replace($old_cust_pay_batch)
+ } else {
+ $error = $cust_pay_batch->insert;
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die $error;
+ }
+
+ my $unapplied = $self->total_unapplied_credits
+ + $self->total_unapplied_payments
+ + $self->in_transit_payments;
+ foreach my $cust_bill ($self->open_cust_bill) {
+ #$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ my $cust_bill_pay_batch = new FS::cust_bill_pay_batch {
+ 'invnum' => $cust_bill->invnum,
+ 'paybatchnum' => $cust_pay_batch->paybatchnum,
+ 'amount' => $cust_bill->owed,
+ '_date' => time,
+ };
+ if ($unapplied >= $cust_bill_pay_batch->amount){
+ $unapplied -= $cust_bill_pay_batch->amount;
+ next;
+ }else{
+ $cust_bill_pay_batch->amount(sprintf ( "%.2f",
+ $cust_bill_pay_batch->amount - $unapplied )); $unapplied = 0;
+ }
+ $error = $cust_bill_pay_batch->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item apply_payments_and_credits [ OPTION => VALUE ... ]
+
+Applies unapplied payments and credits.
+
+In most cases, this new method should be used in place of sequential
+apply_payments and apply_credits methods.
+
+A hash of optional arguments may be passed. Currently "manual" is supported.
+If true, a payment receipt is sent instead of a statement when
+'payment_receipt_email' configuration option is set.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub apply_payments_and_credits {
+ my( $self, %options ) = @_;
+
+ 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
+
+ foreach my $cust_bill ( $self->open_cust_bill ) {
+ my $error = $cust_bill->apply_payments_and_credits(%options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error applying: $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+=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>).
+
+Dies if there is an error.
+
+=cut
+
+sub apply_credits {
+ my $self = shift;
+ my %opt = @_;
+
+ 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
+
+ unless ( $self->total_unapplied_credits ) {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return 0;
+ }
+
+ 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';
+
+ if ( $conf->exists('pkg-balances') ) {
+ # limit @credits to those w/ a pkgnum grepped from $self
+ my %pkgnums = ();
+ foreach my $i (@invoices) {
+ foreach my $li ( $i->cust_bill_pkg ) {
+ $pkgnums{$li->pkgnum} = 1;
+ }
+ }
+ @credits = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @credits;
+ }
+
+ my $credit;
+
+ foreach my $cust_bill ( @invoices ) {
+
+ if ( !defined($credit) || $credit->credited == 0) {
+ $credit = pop @credits or last;
+ }
+
+ my $owed;
+ if ( $conf->exists('pkg-balances') && $credit->pkgnum ) {
+ $owed = $cust_bill->owed_pkgnum($credit->pkgnum);
+ } else {
+ $owed = $cust_bill->owed;
+ }
+ unless ( $owed > 0 ) {
+ push @credits, $credit;
+ next;
+ }
+
+ my $amount = min( $credit->credited, $owed );
+
+ my $cust_credit_bill = new FS::cust_credit_bill ( {
+ 'crednum' => $credit->crednum,
+ 'invnum' => $cust_bill->invnum,
+ 'amount' => $amount,
+ } );
+ $cust_credit_bill->pkgnum( $credit->pkgnum )
+ if $conf->exists('pkg-balances') && $credit->pkgnum;
+ my $error = $cust_credit_bill->insert;
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+
+ redo if ($cust_bill->owed > 0) && ! $conf->exists('pkg-balances');
+
+ }
+
+ my $total_unapplied_credits = $self->total_unapplied_credits;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return $total_unapplied_credits;
+}
+
+=item apply_payments [ OPTION => VALUE ... ]
+
+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.
+
+A hash of optional arguments may be passed. Currently "manual" is supported.
+If true, a payment receipt is sent instead of a statement when
+'payment_receipt_email' configuration option is set.
+
+Dies if there is an error.
+
+=cut
+
+sub apply_payments {
+ my( $self, %options ) = @_;
+
+ 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
+
+ #return 0 unless
+
+ my @payments = sort { $b->_date <=> $a->_date }
+ grep { $_->unapplied > 0 }
+ $self->cust_pay;
+
+ my @invoices = sort { $a->_date <=> $b->_date}
+ grep { $_->owed > 0 }
+ $self->cust_bill;
+
+ if ( $conf->exists('pkg-balances') ) {
+ # limit @payments to those w/ a pkgnum grepped from $self
+ my %pkgnums = ();
+ foreach my $i (@invoices) {
+ foreach my $li ( $i->cust_bill_pkg ) {
+ $pkgnums{$li->pkgnum} = 1;
+ }
+ }
+ @payments = grep { ! $_->pkgnum || $pkgnums{$_->pkgnum} } @payments;
+ }
+
+ my $payment;
+
+ foreach my $cust_bill ( @invoices ) {
+
+ if ( !defined($payment) || $payment->unapplied == 0 ) {
+ $payment = pop @payments or last;
+ }
+
+ my $owed;
+ if ( $conf->exists('pkg-balances') && $payment->pkgnum ) {
+ $owed = $cust_bill->owed_pkgnum($payment->pkgnum);
+ } else {
+ $owed = $cust_bill->owed;
+ }
+ unless ( $owed > 0 ) {
+ push @payments, $payment;
+ next;
+ }
+
+ my $amount = min( $payment->unapplied, $owed );
+
+ my $cust_bill_pay = new FS::cust_bill_pay ( {
+ 'paynum' => $payment->paynum,
+ 'invnum' => $cust_bill->invnum,
+ 'amount' => $amount,
+ } );
+ $cust_bill_pay->pkgnum( $payment->pkgnum )
+ if $conf->exists('pkg-balances') && $payment->pkgnum;
+ my $error = $cust_bill_pay->insert(%options);
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+
+ redo if ( $cust_bill->owed > 0) && ! $conf->exists('pkg-balances');
+
+ }
+
+ my $total_unapplied_payments = $self->total_unapplied_payments;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return $total_unapplied_payments;
+}
+
+=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 $custnum = $self->custnum;
+#
+# my $owed_sql = FS::cust_bill->owed_sql;
+#
+# my $sql = "
+# SELECT SUM($owed_sql) FROM cust_bill
+# WHERE custnum = $custnum
+# AND _date <= $time
+# ";
+#
+# my $sth = dbh->prepare($sql) or die dbh->errstr;
+# $sth->execute() or die $sth->errstr;
+#
+# return sprintf( '%.2f', $sth->fetchrow_arrayref->[0] );
+
+ 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 total_owed_pkgnum PKGNUM
+
+Returns the total owed on all invoices for this customer's specific package
+when using experimental package balances (see L<FS::cust_bill/owed_pkgnum>).
+
+=cut
+
+sub total_owed_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ $self->total_owed_date_pkgnum(2145859200, $pkgnum); #12/31/2037
+}
+
+=item total_owed_date_pkgnum TIME PKGNUM
+
+Returns the total owed for this customer's specific package when using
+experimental package balances 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_pkgnum {
+ my( $self, $time, $pkgnum ) = @_;
+
+ my $total_bill = 0;
+ foreach my $cust_bill (
+ grep { $_->_date <= $time }
+ qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+ ) {
+ $total_bill += $cust_bill->owed_pkgnum($pkgnum);
+ }
+ sprintf( "%.2f", $total_bill );
+
+}
+
+=item total_paid
+
+Returns the total amount of all payments.
+
+=cut
+
+sub total_paid {
+ my $self = shift;
+ my $total = 0;
+ $total += $_->paid foreach $self->cust_pay;
+ sprintf( "%.2f", $total );
+}
+
+=item total_unapplied_credits
+
+Returns the total outstanding credit (see L<FS::cust_credit>) for this
+customer. See L<FS::cust_credit/credited>.
+
+=item total_credited
+
+Old name for total_unapplied_credits. Don't use.
+
+=cut
+
+sub total_credited {
+ #carp "total_credited deprecated, use total_unapplied_credits";
+ shift->total_unapplied_credits(@_);
+}
+
+sub total_unapplied_credits {
+ my $self = shift;
+ my $total_credit = 0;
+ $total_credit += $_->credited foreach $self->cust_credit;
+ sprintf( "%.2f", $total_credit );
+}
+
+=item total_unapplied_credits_pkgnum PKGNUM
+
+Returns the total outstanding credit (see L<FS::cust_credit>) for this
+customer. See L<FS::cust_credit/credited>.
+
+=cut
+
+sub total_unapplied_credits_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ my $total_credit = 0;
+ $total_credit += $_->credited foreach $self->cust_credit_pkgnum($pkgnum);
+ 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;
+ $total_unapplied += $_->unapplied foreach $self->cust_pay;
+ sprintf( "%.2f", $total_unapplied );
+}
+
+=item total_unapplied_payments_pkgnum PKGNUM
+
+Returns the total unapplied payments (see L<FS::cust_pay>) for this customer's
+specific package when using experimental package balances. See
+L<FS::cust_pay/unapplied>.
+
+=cut
+
+sub total_unapplied_payments_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ my $total_unapplied = 0;
+ $total_unapplied += $_->unapplied foreach $self->cust_pay_pkgnum($pkgnum);
+ sprintf( "%.2f", $total_unapplied );
+}
+
+
+=item total_unapplied_refunds
+
+Returns the total unrefunded refunds (see L<FS::cust_refund>) for this
+customer. See L<FS::cust_refund/unapplied>.
+
+=cut
+
+sub total_unapplied_refunds {
+ my $self = shift;
+ my $total_unapplied = 0;
+ $total_unapplied += $_->unapplied foreach $self->cust_refund;
+ sprintf( "%.2f", $total_unapplied );
+}
+
+=item balance
+
+Returns the balance for this customer (total_owed plus total_unrefunded, minus
+total_unapplied_credits minus total_unapplied_payments).
+
+=cut
+
+sub balance {
+ my $self = shift;
+ sprintf( "%.2f",
+ $self->total_owed
+ + $self->total_unapplied_refunds
+ - $self->total_unapplied_credits
+ - $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_unapplied_refunds
+ - $self->total_unapplied_credits
+ - $self->total_unapplied_payments
+ );
+}
+
+=item balance_date_range START_TIME [ END_TIME [ OPTION => VALUE ... ] ]
+
+Returns the balance for this customer, only considering invoices with date
+earlier than START_TIME, and optionally not later than END_TIME
+(total_owed_date minus total_unapplied_credits minus total_unapplied_payments).
+
+Times are specified as SQL fragments or numeric
+UNIX timestamps; see L<perlfunc/"time">). Also see L<Time::Local> and
+L<Date::Parse> for conversion functions. The empty string can be passed
+to disable that time constraint completely.
+
+Available options are:
+
+=over 4
+
+=item unapplied_date
+
+set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering)
+
+=back
+
+=cut
+
+sub balance_date_range {
+ my $self = shift;
+ my $sql = 'SELECT SUM('. $self->balance_date_sql(@_).
+ ') FROM cust_main WHERE custnum='. $self->custnum;
+ sprintf( "%.2f", $self->scalar_sql($sql) );
+}
+
+=item balance_pkgnum PKGNUM
+
+Returns the balance for this customer's specific package when using
+experimental package balances (total_owed plus total_unrefunded, minus
+total_unapplied_credits minus total_unapplied_payments)
+
+=cut
+
+sub balance_pkgnum {
+ my( $self, $pkgnum ) = @_;
+
+ sprintf( "%.2f",
+ $self->total_owed_pkgnum($pkgnum)
+# n/a - refunds aren't part of pkg-balances since they don't apply to invoices
+# + $self->total_unapplied_refunds_pkgnum($pkgnum)
+ - $self->total_unapplied_credits_pkgnum($pkgnum)
+ - $self->total_unapplied_payments_pkgnum($pkgnum)
+ );
+}
+
+=item in_transit_payments
+
+Returns the total of requests for payments for this customer pending in
+batches in transit to the bank. See L<FS::pay_batch> and L<FS::cust_pay_batch>
+
+=cut
+
+sub in_transit_payments {
+ my $self = shift;
+ my $in_transit_payments = 0;
+ foreach my $pay_batch ( qsearch('pay_batch', {
+ 'status' => 'I',
+ } ) ) {
+ foreach my $cust_pay_batch ( qsearch('cust_pay_batch', {
+ 'batchnum' => $pay_batch->batchnum,
+ 'custnum' => $self->custnum,
+ } ) ) {
+ $in_transit_payments += $cust_pay_batch->amount;
+ }
+ }
+ sprintf( "%.2f", $in_transit_payments );
+}
+
+=item payment_info
+
+Returns a hash of useful information for making a payment.
+
+=over 4
+
+=item balance
+
+Current balance.
+
+=item payby
+
+'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
+'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
+'LECB' (Phone bill billing), 'BILL' (billing), or 'COMP' (free).
+
+=back
+
+For credit card transactions:
+
+=over 4
+
+=item card_type 1
+
+=item payname
+
+Exact name on card
+
+=back
+
+For electronic check transactions:
+
+=over 4
+
+=item stateid_state
+
+=back
+
+=cut
+
+sub payment_info {
+ my $self = shift;
+
+ my %return = ();
+
+ $return{balance} = $self->balance;
+
+ $return{payname} = $self->payname
+ || ( $self->first. ' '. $self->get('last') );
+
+ $return{$_} = $self->get($_) for qw(address1 address2 city state zip);
+
+ $return{payby} = $self->payby;
+ $return{stateid_state} = $self->stateid_state;
+
+ if ( $self->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{card_type} = cardtype($self->payinfo);
+ $return{payinfo} = $self->paymask;
+
+ @return{'month', 'year'} = $self->paydate_monthyear;
+
+ }
+
+ if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
+ my ($payinfo1, $payinfo2) = split '@', $self->paymask;
+ $return{payinfo1} = $payinfo1;
+ $return{payinfo2} = $payinfo2;
+ $return{paytype} = $self->paytype;
+ $return{paystate} = $self->paystate;
+
+ }
+
+ #doubleclick protection
+ my $_date = time;
+ $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
+
+ %return;
+
+}
+
+=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 tax_exemption TAXNAME
+
+=cut
+
+sub tax_exemption {
+ my( $self, $taxname ) = @_;
+
+ qsearchs( 'cust_main_exemption', { 'custnum' => $self->custnum,
+ 'taxname' => $taxname,
+ },
+ );
+}
+
+=item cust_main_exemption
+
+=cut
+
+sub cust_main_exemption {
+ my $self = shift;
+ qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } );
+}
+
+=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 ) {
+
+ if ($address eq 'FAX' and $self->getfield('fax') eq '') {
+ return 'Can\'t add FAX invoice destination with a blank FAX number.';
+ }
+
+ 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;
+
+ }
+
+ return "Email address required"
+ if $conf->exists('cust_main-require_invoicing_list_email')
+ && ! grep { $_ !~ /^([A-Z]+)$/ } @$arrayref;
+
+ '';
+}
+
+=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 invoicing_list_emailonly
+
+Returns the list of email invoice recipients (invoicing_list without non-email
+destinations such as POST and FAX).
+
+=cut
+
+sub invoicing_list_emailonly {
+ my $self = shift;
+ warn "$me invoicing_list_emailonly called"
+ if $DEBUG;
+ grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list;
+}
+
+=item invoicing_list_emailonly_scalar
+
+Returns the list of email invoice recipients (invoicing_list without non-email
+destinations such as POST and FAX) as a comma-separated scalar.
+
+=cut
+
+sub invoicing_list_emailonly_scalar {
+ my $self = shift;
+ warn "$me invoicing_list_emailonly_scalar called"
+ if $DEBUG;
+ join(', ', $self->invoicing_list_emailonly);
+}
+
+=item referral_custnum_cust_main
+
+Returns the customer who referred this customer (or the empty string, if
+this customer was not referred).
+
+Note the difference with referral_cust_main method: This method,
+referral_custnum_cust_main returns the single customer (if any) who referred
+this customer, while referral_cust_main returns an array of customers referred
+BY this customer.
+
+=cut
+
+sub referral_custnum_cust_main {
+ my $self = shift;
+ return '' unless $self->referral_custnum;
+ qsearchs('cust_main', { 'custnum' => $self->referral_custnum } );
+}
+
+=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).
+
+Note the difference with referral_custnum_cust_main method: This method,
+referral_cust_main, returns an array of customers referred BY this customer,
+while referral_custnum_cust_main returns the single customer (if any) who
+referred this customer.
+
+=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 commission 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 referring_cust_main
+
+Returns the single cust_main record for the customer who referred this customer
+(referral_custnum), or false.
+
+=cut
+
+sub referring_cust_main {
+ my $self = shift;
+ return '' unless $self->referral_custnum;
+ qsearchs('cust_main', { 'custnum' => $self->referral_custnum } );
+}
+
+=item credit AMOUNT, REASON [ , OPTION => VALUE ... ]
+
+Applies a credit to this customer. If there is an error, returns the error,
+otherwise returns false.
+
+REASON can be a text string, an FS::reason object, or a scalar reference to
+a reasonnum. If a text string, it will be automatically inserted as a new
+reason, and a 'reason_type' option must be passed to indicate the
+FS::reason_type for the new reason.
+
+An I<addlinfo> option may be passed to set the credit's I<addlinfo> field.
+
+Any other options are passed to FS::cust_credit::insert.
+
+=cut
+
+sub credit {
+ my( $self, $amount, $reason, %options ) = @_;
+
+ my $cust_credit = new FS::cust_credit {
+ 'custnum' => $self->custnum,
+ 'amount' => $amount,
+ };
+
+ if ( ref($reason) ) {
+
+ if ( ref($reason) eq 'SCALAR' ) {
+ $cust_credit->reasonnum( $$reason );
+ } else {
+ $cust_credit->reasonnum( $reason->reasonnum );
+ }
+
+ } else {
+ $cust_credit->set('reason', $reason)
+ }
+
+ for (qw( addlinfo eventnum )) {
+ $cust_credit->$_( delete $options{$_} )
+ if exists($options{$_});
+ }
+
+ $cust_credit->insert(%options);
+
+}
+
+=item charge HASHREF || AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
+
+Creates a one-time charge for this customer. If there is an error, returns
+the error, otherwise returns false.
+
+New-style, with a hashref of options:
+
+ my $error = $cust_main->charge(
+ {
+ 'amount' => 54.32,
+ 'quantity' => 1,
+ 'start_date' => str2time('7/4/2009'),
+ 'pkg' => 'Description',
+ 'comment' => 'Comment',
+ 'additional' => [], #extra invoice detail
+ 'classnum' => 1, #pkg_class
+
+ 'setuptax' => '', # or 'Y' for tax exempt
+
+ #internal taxation
+ 'taxclass' => 'Tax class',
+
+ #vendor taxation
+ 'taxproduct' => 2, #part_pkg_taxproduct
+ 'override' => {}, #XXX describe
+
+ #will be filled in with the new object
+ 'cust_pkg_ref' => \$cust_pkg,
+
+ #generate an invoice immediately
+ 'bill_now' => 0,
+ 'invoice_terms' => '', #with these terms
+ }
+ );
+
+Old-style:
+
+ my $error = $cust_main->charge( 54.32, 'Description', 'Comment', 'Tax class' );
+
+=cut
+
+sub charge {
+ my $self = shift;
+ my ( $amount, $quantity, $start_date, $classnum );
+ my ( $pkg, $comment, $additional );
+ my ( $setuptax, $taxclass ); #internal taxes
+ my ( $taxproduct, $override ); #vendor (CCH) taxes
+ my $no_auto = '';
+ my $cust_pkg_ref = '';
+ my ( $bill_now, $invoice_terms ) = ( 0, '' );
+ if ( ref( $_[0] ) ) {
+ $amount = $_[0]->{amount};
+ $quantity = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1;
+ $start_date = exists($_[0]->{start_date}) ? $_[0]->{start_date} : '';
+ $no_auto = exists($_[0]->{no_auto}) ? $_[0]->{no_auto} : '';
+ $pkg = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge';
+ $comment = exists($_[0]->{comment}) ? $_[0]->{comment}
+ : '$'. sprintf("%.2f",$amount);
+ $setuptax = exists($_[0]->{setuptax}) ? $_[0]->{setuptax} : '';
+ $taxclass = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : '';
+ $classnum = exists($_[0]->{classnum}) ? $_[0]->{classnum} : '';
+ $additional = $_[0]->{additional} || [];
+ $taxproduct = $_[0]->{taxproductnum};
+ $override = { '' => $_[0]->{tax_override} };
+ $cust_pkg_ref = exists($_[0]->{cust_pkg_ref}) ? $_[0]->{cust_pkg_ref} : '';
+ $bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : '';
+ $invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : '';
+ } else {
+ $amount = shift;
+ $quantity = 1;
+ $start_date = '';
+ $pkg = @_ ? shift : 'One-time charge';
+ $comment = @_ ? shift : '$'. sprintf("%.2f",$amount);
+ $setuptax = '';
+ $taxclass = @_ ? shift : '';
+ $additional = [];
+ }
+
+ 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,
+ 'plan' => 'flat',
+ 'freq' => 0,
+ 'disabled' => 'Y',
+ 'classnum' => ( $classnum ? $classnum : '' ),
+ 'setuptax' => $setuptax,
+ 'taxclass' => $taxclass,
+ 'taxproductnum' => $taxproduct,
+ } );
+
+ my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) }
+ ( 0 .. @$additional - 1 )
+ ),
+ 'additional_count' => scalar(@$additional),
+ 'setup_fee' => $amount,
+ );
+
+ my $error = $part_pkg->insert( options => \%options,
+ tax_overrides => $override,
+ );
+ 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,
+ 'quantity' => $quantity,
+ 'start_date' => $start_date,
+ 'no_auto' => $no_auto,
+ } );
+
+ $error = $cust_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ } elsif ( $cust_pkg_ref ) {
+ ${$cust_pkg_ref} = $cust_pkg;
+ }
+
+ if ( $bill_now ) {
+ my $error = $self->bill( 'invoice_terms' => $invoice_terms,
+ 'pkg_list' => [ $cust_pkg ],
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+
+}
+
+#=item charge_postal_fee
+#
+#Applies a one time charge this customer. If there is an error,
+#returns the error, returns the cust_pkg charge object or false
+#if there was no charge.
+#
+#=cut
+#
+# This should be a customer event. For that to work requires that bill
+# also be a customer event.
+
+sub charge_postal_fee {
+ my $self = shift;
+
+ my $pkgpart = $conf->config('postal_invoice-fee_pkgpart');
+ return '' unless ($pkgpart && grep { $_ eq 'POST' } $self->invoicing_list);
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'custnum' => $self->custnum,
+ 'pkgpart' => $pkgpart,
+ 'quantity' => 1,
+ } );
+
+ my $error = $cust_pkg->insert;
+ $error ? $error : $cust_pkg;
+}
+
+=item cust_bill
+
+Returns all the invoices (see L<FS::cust_bill>) for this customer.
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_bill unless wantarray;
+ 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;
+
+ qsearch({
+ 'table' => 'cust_bill',
+ 'hashref' => { 'custnum' => $self->custnum, },
+ 'extra_sql' => ' AND '. FS::cust_bill->owed_sql. ' > 0',
+ 'order_by' => 'ORDER BY _date ASC',
+ });
+
+}
+
+=item cust_statements
+
+Returns all the statements (see L<FS::cust_statement>) for this customer.
+
+=cut
+
+sub cust_statement {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_statement unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch('cust_statement', { 'custnum' => $self->custnum, } )
+}
+
+=item cust_credit
+
+Returns all the credits (see L<FS::cust_credit>) for this customer.
+
+=cut
+
+sub cust_credit {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_credit unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
+}
+
+=item cust_credit_pkgnum
+
+Returns all the credits (see L<FS::cust_credit>) for this customer's specific
+package when using experimental package balances.
+
+=cut
+
+sub cust_credit_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ map { $_ } #return $self->num_cust_credit_pkgnum($pkgnum) unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit', { 'custnum' => $self->custnum,
+ 'pkgnum' => $pkgnum,
+ }
+ );
+}
+
+=item cust_pay
+
+Returns all the payments (see L<FS::cust_pay>) for this customer.
+
+=cut
+
+sub cust_pay {
+ my $self = shift;
+ return $self->num_cust_pay unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
+}
+
+=item num_cust_pay
+
+Returns the number of payments (see L<FS::cust_pay>) for this customer. Also
+called automatically when the cust_pay method is used in a scalar context.
+
+=cut
+
+sub num_cust_pay {
+ my $self = shift;
+ my $sql = "SELECT COUNT(*) FROM cust_pay WHERE custnum = ?";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute($self->custnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item cust_pay_pkgnum
+
+Returns all the payments (see L<FS::cust_pay>) for this customer's specific
+package when using experimental package balances.
+
+=cut
+
+sub cust_pay_pkgnum {
+ my( $self, $pkgnum ) = @_;
+ map { $_ } #return $self->num_cust_pay_pkgnum($pkgnum) unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay', { 'custnum' => $self->custnum,
+ 'pkgnum' => $pkgnum,
+ }
+ );
+}
+
+=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;
+ map { $_ } #return $self->num_cust_pay_void unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay_void', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay_batch
+
+Returns all batched payments (see L<FS::cust_pay_void>) for this customer.
+
+=cut
+
+sub cust_pay_batch {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_pay_batch unless wantarray;
+ sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch( 'cust_pay_batch', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay_pending
+
+Returns all pending payments (see L<FS::cust_pay_pending>) for this customer
+(without status "done").
+
+=cut
+
+sub cust_pay_pending {
+ my $self = shift;
+ return $self->num_cust_pay_pending unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay_pending', {
+ 'custnum' => $self->custnum,
+ 'status' => { op=>'!=', value=>'done' },
+ },
+ );
+}
+
+=item num_cust_pay_pending
+
+Returns the number of pending payments (see L<FS::cust_pay_pending>) for this
+customer (without status "done"). Also called automatically when the
+cust_pay_pending method is used in a scalar context.
+
+=cut
+
+sub num_cust_pay_pending {
+ my $self = shift;
+ my $sql = " SELECT COUNT(*) FROM cust_pay_pending ".
+ " WHERE custnum = ? AND status != 'done' ";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute($self->custnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item cust_refund
+
+Returns all the refunds (see L<FS::cust_refund>) for this customer.
+
+=cut
+
+sub cust_refund {
+ my $self = shift;
+ map { $_ } #return $self->num_cust_refund unless wantarray;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
+}
+
+=item display_custnum
+
+Returns the displayed customer number for this customer: agent_custid if
+cust_main-default_agent_custid is set and it has a value, custnum otherwise.
+
+=cut
+
+sub display_custnum {
+ my $self = shift;
+ if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){
+ return $self->agent_custid;
+ } else {
+ return $self->custnum;
+ }
+}
+
+=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->contact;
+ $name = $self->company. " ($name)" if $self->company;
+ $name;
+}
+
+=item ship_name
+
+Returns a name string for this (service/shipping) contact, either
+"Company (Last, First)" or "Last, First".
+
+=cut
+
+sub ship_name {
+ my $self = shift;
+ if ( $self->get('ship_last') ) {
+ my $name = $self->ship_contact;
+ $name = $self->ship_company. " ($name)" if $self->ship_company;
+ $name;
+ } else {
+ $self->name;
+ }
+}
+
+=item name_short
+
+Returns a name string for this customer, either "Company" or "First Last".
+
+=cut
+
+sub name_short {
+ my $self = shift;
+ $self->company !~ /^\s*$/ ? $self->company : $self->contact_firstlast;
+}
+
+=item ship_name_short
+
+Returns a name string for this (service/shipping) contact, either "Company"
+or "First Last".
+
+=cut
+
+sub ship_name_short {
+ my $self = shift;
+ if ( $self->get('ship_last') ) {
+ $self->ship_company !~ /^\s*$/
+ ? $self->ship_company
+ : $self->ship_contact_firstlast;
+ } else {
+ $self->name_company_or_firstlast;
+ }
+}
+
+=item contact
+
+Returns this customer's full (billing) contact name only, "Last, First"
+
+=cut
+
+sub contact {
+ my $self = shift;
+ $self->get('last'). ', '. $self->first;
+}
+
+=item ship_contact
+
+Returns this customer's full (shipping) contact name only, "Last, First"
+
+=cut
+
+sub ship_contact {
+ my $self = shift;
+ $self->get('ship_last')
+ ? $self->get('ship_last'). ', '. $self->ship_first
+ : $self->contact;
+}
+
+=item contact_firstlast
+
+Returns this customers full (billing) contact name only, "First Last".
+
+=cut
+
+sub contact_firstlast {
+ my $self = shift;
+ $self->first. ' '. $self->get('last');
+}
+
+=item ship_contact_firstlast
+
+Returns this customer's full (shipping) contact name only, "First Last".
+
+=cut
+
+sub ship_contact_firstlast {
+ my $self = shift;
+ $self->get('ship_last')
+ ? $self->first. ' '. $self->get('ship_last')
+ : $self->contact_firstlast;
+}
+
+=item country_full
+
+Returns this customer's full country name
+
+=cut
+
+sub country_full {
+ my $self = shift;
+ code2country($self->country);
+}
+
+=item geocode DATA_VENDOR
+
+Returns a value for the customer location as encoded by DATA_VENDOR.
+Currently this only makes sense for "CCH" as DATA_VENDOR.
+
+=cut
+
+sub geocode {
+ my ($self, $data_vendor) = (shift, shift); #always cch for now
+
+ my $geocode = $self->get('geocode'); #XXX only one data_vendor for geocode
+ return $geocode if $geocode;
+
+ my $prefix = ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+ ? 'ship_'
+ : '';
+
+ my($zip,$plus4) = split /-/, $self->get("${prefix}zip")
+ if $self->country eq 'US';
+
+ $zip ||= '';
+ $plus4 ||= '';
+ #CCH specific location stuff
+ my $extra_sql = "AND plus4lo <= '$plus4' AND plus4hi >= '$plus4'";
+
+ my @cust_tax_location =
+ qsearch( {
+ 'table' => 'cust_tax_location',
+ 'hashref' => { 'zip' => $zip, 'data_vendor' => $data_vendor },
+ 'extra_sql' => $extra_sql,
+ 'order_by' => 'ORDER BY plus4hi',#overlapping with distinct ends
+ }
+ );
+ $geocode = $cust_tax_location[0]->geocode
+ if scalar(@cust_tax_location);
+
+ $geocode;
+}
+
+=item cust_status
+
+=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 inactive - No active recurring packages, but otherwise unsuspended/uncancelled (the inactive status is new - previously inactive customers were mis-identified as cancelled)
+
+=item suspended - All non-cancelled recurring packages are suspended
+
+=item cancelled - All recurring packages are cancelled
+
+=back
+
+=cut
+
+sub status { shift->cust_status(@_); }
+
+sub cust_status {
+ my $self = shift;
+ for my $status (qw( prospect active inactive 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 "Error executing 'SELECT $sql': ". $sth->errstr;
+ return $status if $sth->fetchrow_arrayref->[0];
+ }
+}
+
+=item ucfirst_cust_status
+
+=item ucfirst_status
+
+Returns the status with the first character capitalized.
+
+=cut
+
+sub ucfirst_status { shift->ucfirst_cust_status(@_); }
+
+sub ucfirst_cust_status {
+ my $self = shift;
+ ucfirst($self->cust_status);
+}
+
+=item statuscolor
+
+Returns a hex triplet color string for this customer's status.
+
+=cut
+
+use vars qw(%statuscolor);
+tie %statuscolor, 'Tie::IxHash',
+ 'prospect' => '7e0079', #'000000', #black? naw, purple
+ 'active' => '00CC00', #green
+ 'inactive' => '0000CC', #blue
+ 'suspended' => 'FF9900', #yellow
+ 'cancelled' => 'FF0000', #red
+;
+
+sub statuscolor { shift->cust_statuscolor(@_); }
+
+sub cust_statuscolor {
+ my $self = shift;
+ $statuscolor{$self->cust_status};
+}
+
+=item tickets
+
+Returns an array of hashes representing the customer's RT tickets.
+
+=cut
+
+sub tickets {
+ my $self = shift;
+
+ my $num = $conf->config('cust_main-max_tickets') || 10;
+ my @tickets = ();
+
+ if ( $conf->config('ticket_system') ) {
+ unless ( $conf->config('ticket_system-custom_priority_field') ) {
+
+ @tickets = @{ FS::TicketSystem->customer_tickets($self->custnum, $num) };
+
+ } else {
+
+ foreach my $priority (
+ $conf->config('ticket_system-custom_priority_field-values'), ''
+ ) {
+ last if scalar(@tickets) >= $num;
+ push @tickets,
+ @{ FS::TicketSystem->customer_tickets( $self->custnum,
+ $num - scalar(@tickets),
+ $priority,
+ )
+ };
+ }
+ }
+ }
+ (@tickets);
+}
+
+# Return services representing svc_accts in customer support packages
+sub support_services {
+ my $self = shift;
+ my %packages = map { $_ => 1 } $conf->config('support_packages');
+
+ grep { $_->pkg_svc && $_->pkg_svc->primary_svc eq 'Y' }
+ grep { $_->part_svc->svcdb eq 'svc_acct' }
+ map { $_->cust_svc }
+ grep { exists $packages{ $_->pkgpart } }
+ $self->ncancelled_pkgs;
+
+}
+
+# Return a list of latitude/longitude for one of the services (if any)
+sub service_coordinates {
+ my $self = shift;
+
+ my @svc_X =
+ grep { $_->latitude && $_->longitude }
+ map { $_->svc_x }
+ map { $_->cust_svc }
+ $self->ncancelled_pkgs;
+
+ scalar(@svc_X) ? ( $svc_X[0]->latitude, $svc_X[0]->longitude ) : ()
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item statuses
+
+Class method that returns the list of possible status strings for customers
+(see L<the status method|/status>). For example:
+
+ @statuses = FS::cust_main->statuses();
+
+=cut
+
+sub statuses {
+ #my $self = shift; #could be class...
+ keys %statuscolor;
+}
+
+=item prospect_sql
+
+Returns an SQL expression identifying prospective cust_main records (customers
+with no packages ever ordered)
+
+=cut
+
+use vars qw($select_count_pkgs);
+$select_count_pkgs =
+ "SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum";
+
+sub select_count_pkgs_sql {
+ $select_count_pkgs;
+}
+
+sub prospect_sql { "
+ 0 = ( $select_count_pkgs )
+"; }
+
+=item active_sql
+
+Returns an SQL expression identifying active cust_main records (customers with
+active recurring packages).
+
+=cut
+
+sub active_sql { "
+ 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. "
+ )
+"; }
+
+=item inactive_sql
+
+Returns an SQL expression identifying inactive cust_main records (customers with
+no active recurring packages, but otherwise unsuspended/uncancelled).
+
+=cut
+
+sub inactive_sql { "
+ 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
+ AND
+ 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " )
+"; }
+
+=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_pkgs AND ". FS::cust_pkg->suspended_sql. " )
+ AND
+ 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
+"; }
+
+=item cancel_sql
+=item cancelled_sql
+
+Returns an SQL expression identifying cancelled cust_main records.
+
+=cut
+
+sub cancelled_sql { cancel_sql(@_); }
+sub cancel_sql {
+
+ my $recurring_sql = FS::cust_pkg->recurring_sql;
+ my $cancelled_sql = FS::cust_pkg->cancelled_sql;
+
+ "
+ 0 < ( $select_count_pkgs )
+ AND 0 < ( $select_count_pkgs AND $recurring_sql AND $cancelled_sql )
+ AND 0 = ( $select_count_pkgs AND $recurring_sql
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ )
+ AND 0 = ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " )
+ ";
+
+}
+
+=item uncancel_sql
+=item uncancelled_sql
+
+Returns an SQL expression identifying un-cancelled cust_main records.
+
+=cut
+
+sub uncancelled_sql { uncancel_sql(@_); }
+sub uncancel_sql { "
+ ( 0 < ( $select_count_pkgs
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0
+ )
+ )
+ OR 0 = ( $select_count_pkgs )
+ )
+"; }
+
+=item balance_sql
+
+Returns an SQL fragment to retreive the balance.
+
+=cut
+
+sub balance_sql { "
+ ( SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill
+ WHERE cust_bill.custnum = cust_main.custnum )
+ - ( SELECT COALESCE( SUM(paid), 0 ) FROM cust_pay
+ WHERE cust_pay.custnum = cust_main.custnum )
+ - ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_credit
+ WHERE cust_credit.custnum = cust_main.custnum )
+ + ( SELECT COALESCE( SUM(refund), 0 ) FROM cust_refund
+ WHERE cust_refund.custnum = cust_main.custnum )
+"; }
+
+=item balance_date_sql START_TIME [ END_TIME [ OPTION => VALUE ... ] ]
+
+Returns an SQL fragment to retreive the balance for this customer, only
+considering invoices with date earlier than START_TIME, and optionally not
+later than END_TIME (total_owed_date minus total_unapplied_credits minus
+total_unapplied_payments).
+
+Times are specified as SQL fragments or numeric
+UNIX timestamps; see L<perlfunc/"time">). Also see L<Time::Local> and
+L<Date::Parse> for conversion functions. The empty string can be passed
+to disable that time constraint completely.
+
+Available options are:
+
+=over 4
+
+=item unapplied_date
+
+set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering)
+
+=item total
+
+(unused. obsolete?)
+set to true to remove all customer comparison clauses, for totals
+
+=item where
+
+(unused. obsolete?)
+WHERE clause hashref (elements "AND"ed together) (typically used with the total option)
+
+=item join
+
+(unused. obsolete?)
+JOIN clause (typically used with the total option)
+
+=item cutoff
+
+An absolute cutoff time. Payments, credits, and refunds I<applied> after this
+time will be ignored. Note that START_TIME and END_TIME only limit the date
+range for invoices and I<unapplied> payments, credits, and refunds.
+
+=back
+
+=cut
+
+sub balance_date_sql {
+ my( $class, $start, $end, %opt ) = @_;
+
+ my $cutoff = $opt{'cutoff'};
+
+ my $owed = FS::cust_bill->owed_sql($cutoff);
+ my $unapp_refund = FS::cust_refund->unapplied_sql($cutoff);
+ my $unapp_credit = FS::cust_credit->unapplied_sql($cutoff);
+ my $unapp_pay = FS::cust_pay->unapplied_sql($cutoff);
+
+ my $j = $opt{'join'} || '';
+
+ my $owed_wh = $class->_money_table_where( 'cust_bill', $start,$end,%opt );
+ my $refund_wh = $class->_money_table_where( 'cust_refund', $start,$end,%opt );
+ my $credit_wh = $class->_money_table_where( 'cust_credit', $start,$end,%opt );
+ my $pay_wh = $class->_money_table_where( 'cust_pay', $start,$end,%opt );
+
+ " ( SELECT COALESCE(SUM($owed), 0) FROM cust_bill $j $owed_wh )
+ + ( SELECT COALESCE(SUM($unapp_refund), 0) FROM cust_refund $j $refund_wh )
+ - ( SELECT COALESCE(SUM($unapp_credit), 0) FROM cust_credit $j $credit_wh )
+ - ( SELECT COALESCE(SUM($unapp_pay), 0) FROM cust_pay $j $pay_wh )
+ ";
+
+}
+
+=item unapplied_payments_date_sql START_TIME [ END_TIME ]
+
+Returns an SQL fragment to retreive the total unapplied payments for this
+customer, only considering invoices with date earlier than START_TIME, and
+optionally not later than END_TIME.
+
+Times are specified as SQL fragments or numeric
+UNIX timestamps; see L<perlfunc/"time">). Also see L<Time::Local> and
+L<Date::Parse> for conversion functions. The empty string can be passed
+to disable that time constraint completely.
+
+Available options are:
+
+=cut
+
+sub unapplied_payments_date_sql {
+ my( $class, $start, $end, ) = @_;
+
+ my $unapp_pay = FS::cust_pay->unapplied_sql;
+
+ my $pay_where = $class->_money_table_where( 'cust_pay', $start, $end,
+ 'unapplied_date'=>1 );
+
+ " ( SELECT COALESCE(SUM($unapp_pay), 0) FROM cust_pay $pay_where ) ";
+}
+
+=item _money_table_where TABLE START_TIME [ END_TIME [ OPTION => VALUE ... ] ]
+
+Helper method for balance_date_sql; name (and usage) subject to change
+(suggestions welcome).
+
+Returns a WHERE clause for the specified monetary TABLE (cust_bill,
+cust_refund, cust_credit or cust_pay).
+
+If TABLE is "cust_bill" or the unapplied_date option is true, only
+considers records with date earlier than START_TIME, and optionally not
+later than END_TIME .
+
+=cut
+
+sub _money_table_where {
+ my( $class, $table, $start, $end, %opt ) = @_;
+
+ my @where = ();
+ push @where, "cust_main.custnum = $table.custnum" unless $opt{'total'};
+ if ( $table eq 'cust_bill' || $opt{'unapplied_date'} ) {
+ push @where, "$table._date <= $start" if defined($start) && length($start);
+ push @where, "$table._date > $end" if defined($end) && length($end);
+ }
+ push @where, @{$opt{'where'}} if $opt{'where'};
+ my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where ) : '';
+
+ $where;
+
+}
+
+=item search HASHREF
+
+(Class method)
+
+Returns a qsearch hash expression to search for parameters specified in
+HASHREF. Valid parameters are
+
+=over 4
+
+=item agentnum
+
+=item status
+
+=item cancelled_pkgs
+
+bool
+
+=item signupdate
+
+listref of start date, end date
+
+=item payby
+
+listref
+
+=item paydate_year
+
+=item paydate_month
+
+=item current_balance
+
+listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance'))
+
+=item cust_fields
+
+=item flattened_pkgs
+
+bool
+
+=back
+
+=cut
+
+sub search {
+ my ($class, $params) = @_;
+
+ my $dbh = dbh;
+
+ my @where = ();
+ my $orderby;
+
+ ##
+ # parse agent
+ ##
+
+ if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "cust_main.agentnum = $1";
+ }
+
+ ##
+ # do the same for user
+ ##
+
+ if ( $params->{'usernum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "cust_main.usernum = $1";
+ }
+
+ ##
+ # parse status
+ ##
+
+ #prospect active inactive suspended cancelled
+ if ( grep { $params->{'status'} eq $_ } FS::cust_main->statuses() ) {
+ my $method = $params->{'status'}. '_sql';
+ #push @where, $class->$method();
+ push @where, FS::cust_main->$method();
+ }
+
+ ##
+ # parse cancelled package checkbox
+ ##
+
+ my $pkgwhere = "";
+
+ $pkgwhere .= "AND (cancel = 0 or cancel is null)"
+ unless $params->{'cancelled_pkgs'};
+
+ ##
+ # parse without census tract checkbox
+ ##
+
+ push @where, "(censustract = '' or censustract is null)"
+ if $params->{'no_censustract'};
+
+ ##
+ # dates
+ ##
+
+ foreach my $field (qw( signupdate )) {
+
+ next unless exists($params->{$field});
+
+ my($beginning, $ending, $hour) = @{$params->{$field}};
+
+ push @where,
+ "cust_main.$field IS NOT NULL",
+ "cust_main.$field >= $beginning",
+ "cust_main.$field <= $ending";
+
+ # XXX: do this for mysql and/or pull it out of here
+ if(defined $hour) {
+ if ($dbh->{Driver}->{Name} eq 'Pg') {
+ push @where, "extract(hour from to_timestamp(cust_main.$field)) = $hour";
+ }
+ else {
+ warn "search by time of day not supported on ".$dbh->{Driver}->{Name}." databases";
+ }
+ }
+
+ $orderby ||= "ORDER BY cust_main.$field";
+
+ }
+
+ ###
+ # classnum
+ ###
+
+ if ( $params->{'classnum'} ) {
+
+ my @classnum = ref( $params->{'classnum'} )
+ ? @{ $params->{'classnum'} }
+ : ( $params->{'classnum'} );
+
+ @classnum = grep /^(\d*)$/, @classnum;
+
+ if ( @classnum ) {
+ push @where, '( '. join(' OR ', map {
+ $_ ? "cust_main.classnum = $_"
+ : "cust_main.classnum IS NULL"
+ }
+ @classnum
+ ).
+ ' )';
+ }
+
+ }
+
+ ###
+ # payby
+ ###
+
+ if ( $params->{'payby'} ) {
+
+ my @payby = ref( $params->{'payby'} )
+ ? @{ $params->{'payby'} }
+ : ( $params->{'payby'} );
+
+ @payby = grep /^([A-Z]{4})$/, @{ $params->{'payby'} };
+
+ push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )'
+ if @payby;
+
+ }
+
+ ###
+ # paydate_year / paydate_month
+ ###
+
+ if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) {
+ my $year = $1;
+ $params->{'paydate_month'} =~ /^(\d\d?)$/
+ or die "paydate_year without paydate_month?";
+ my $month = $1;
+
+ push @where,
+ 'paydate IS NOT NULL',
+ "paydate != ''",
+ "CAST(paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )"
+;
+ }
+
+ ###
+ # invoice terms
+ ###
+
+ if ( $params->{'invoice_terms'} =~ /^([\w ]+)$/ ) {
+ my $terms = $1;
+ if ( $1 eq 'NULL' ) {
+ push @where,
+ "( cust_main.invoice_terms IS NULL OR cust_main.invoice_terms = '' )";
+ } else {
+ push @where,
+ "cust_main.invoice_terms IS NOT NULL",
+ "cust_main.invoice_terms = '$1'";
+ }
+ }
+
+ ##
+ # amounts
+ ##
+
+ if ( $params->{'current_balance'} ) {
+
+ #my $balance_sql = $class->balance_sql();
+ my $balance_sql = FS::cust_main->balance_sql();
+
+ my @current_balance =
+ ref( $params->{'current_balance'} )
+ ? @{ $params->{'current_balance'} }
+ : ( $params->{'current_balance'} );
+
+ push @where, map { s/current_balance/$balance_sql/; $_ }
+ @current_balance;
+
+ }
+
+ ##
+ # custbatch
+ ##
+
+ if ( $params->{'custbatch'} =~ /^([\w\/\-\:\.]+)$/ and $1 ) {
+ push @where,
+ "cust_main.custbatch = '$1'";
+ }
+
+ ##
+ # setup queries, subs, etc. for the search
+ ##
+
+ $orderby ||= 'ORDER BY custnum';
+
+ # here is the agent virtualization
+ push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $addl_from = 'LEFT JOIN cust_pkg USING ( custnum ) ';
+
+ my $count_query = "SELECT COUNT(*) FROM cust_main $extra_sql";
+
+ my $select = join(', ',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields($params->{'cust_fields'}),
+ );
+
+ my(@extra_headers) = ();
+ my(@extra_fields) = ();
+
+ if ($params->{'flattened_pkgs'}) {
+
+ if ($dbh->{Driver}->{Name} eq 'Pg') {
+
+ $select .= ", array_to_string(array(select pkg from cust_pkg left join part_pkg using ( pkgpart ) where cust_main.custnum = cust_pkg.custnum $pkgwhere),'|') as magic";
+
+ }elsif ($dbh->{Driver}->{Name} =~ /^mysql/i) {
+ $select .= ", GROUP_CONCAT(pkg SEPARATOR '|') as magic";
+ $addl_from .= " LEFT JOIN part_pkg using ( pkgpart )";
+ }else{
+ warn "warning: unknown database type ". $dbh->{Driver}->{Name}.
+ "omitting packing information from report.";
+ }
+
+ my $header_query = "SELECT COUNT(cust_pkg.custnum = cust_main.custnum) AS count FROM cust_main $addl_from $extra_sql $pkgwhere group by cust_main.custnum order by count desc limit 1";
+
+ my $sth = dbh->prepare($header_query) or die dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+ my $headerrow = $sth->fetchrow_arrayref;
+ my $headercount = $headerrow ? $headerrow->[0] : 0;
+ while($headercount) {
+ unshift @extra_headers, "Package ". $headercount;
+ unshift @extra_fields, eval q!sub {my $c = shift;
+ my @a = split '\|', $c->magic;
+ my $p = $a[!.--$headercount. q!];
+ $p;
+ };!;
+ }
+
+ }
+
+ my $sql_query = {
+ 'table' => 'cust_main',
+ 'select' => $select,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'count_query' => $count_query,
+ 'extra_headers' => \@extra_headers,
+ 'extra_fields' => \@extra_fields,
+ };
+
+}
+
+=item email_search_result HASHREF
+
+(Class method)
+
+Emails a notice to the specified customers.
+
+Valid parameters are those of the L<search> method, plus the following:
+
+=over 4
+
+=item from
+
+From: address
+
+=item subject
+
+Email Subject:
+
+=item html_body
+
+HTML body
+
+=item text_body
+
+Text body
+
+=item job
+
+Optional job queue job for status updates.
+
+=back
+
+Returns an error message, or false for success.
+
+If an error occurs during any email, stops the enture send and returns that
+error. Presumably if you're getting SMTP errors aborting is better than
+retrying everything.
+
+=cut
+
+sub email_search_result {
+ my($class, $params) = @_;
+
+ my $from = delete $params->{from};
+ my $subject = delete $params->{subject};
+ my $html_body = delete $params->{html_body};
+ my $text_body = delete $params->{text_body};
+
+ my $job = delete $params->{'job'};
+
+ $params->{'payby'} = [ split(/\0/, $params->{'payby'}) ]
+ unless ref($params->{'payby'});
+
+ my $sql_query = $class->search($params);
+
+ my $count_query = delete($sql_query->{'count_query'});
+ my $count_sth = dbh->prepare($count_query)
+ or die "Error preparing $count_query: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $count_query: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+ my $num_cust = $count_arrayref->[0];
+
+ #my @extra_headers = @{ delete($sql_query->{'extra_headers'}) };
+ #my @extra_fields = @{ delete($sql_query->{'extra_fields'}) };
+
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+
+ #eventually order+limit magic to reduce memory use?
+ foreach my $cust_main ( qsearch($sql_query) ) {
+
+ my $to = $cust_main->invoicing_list_emailonly_scalar;
+ next unless $to;
+
+ my $error = send_email(
+ generate_email(
+ 'from' => $from,
+ 'to' => $to,
+ 'subject' => $subject,
+ 'html_body' => $html_body,
+ 'text_body' => $text_body,
+ )
+ );
+ return $error if $error;
+
+ if ( $job ) { #progressbar foo
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / $num_cust )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ }
+
+ return '';
+}
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_email_search_result {
+ my $job = shift;
+ #warn "$me process_re_X $method for job $job\n" if $DEBUG;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ $param->{'job'} = $job;
+
+ $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ]
+ unless ref($param->{'payby'});
+
+ my $error = FS::cust_main->email_search_result( $param );
+ die $error if $error;
+
+}
+
+=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, I<first>, I<last>, I<company> and/or I<address1> may be
+specified (the appropriate ship_ field is also searched).
+
+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 $all = $self->all_X($field);
+ next unless scalar(@$all);
+
+ my %match = ();
+ $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, ['i'], @$all ) );
+
+ my @fcust = ();
+ foreach ( keys %match ) {
+ push @fcust, qsearch('cust_main', { %$hash, $field=>$_}, @opt);
+ push @fcust, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt);
+ }
+ my %fsaw = ();
+ push @cust_main, grep { ! $fsaw{$_->custnum}++ } @fcust;
+ }
+
+ # we want the components of $fuzzy ANDed, not ORed, but still don't want dupes
+ my %saw = ();
+ @cust_main = grep { ++$saw{$_->custnum} == scalar(keys %$fuzzy) } @cust_main;
+
+ @cust_main;
+
+}
+
+=item masked FIELD
+
+Returns a masked version of the named field
+
+=cut
+
+sub masked {
+my ($self,$field) = @_;
+
+# Show last four
+
+'x'x(length($self->getfield($field))-4).
+ substr($self->getfield($field), (length($self->getfield($field))-4));
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item smart_search OPTION => VALUE ...
+
+Accepts the following options: I<search>, the string to search for. The string
+will be searched for as a customer number, phone number, name or company name,
+as an exact, or, in some cases, a substring or fuzzy match (see the source code
+for the exact heuristics used); I<no_fuzzy_on_exact>, causes smart_search to
+skip fuzzy matching when an exact match is found.
+
+Any additional options are treated as an additional qualifier on the search
+(i.e. I<agentnum>).
+
+Returns a (possibly empty) array of FS::cust_main objects.
+
+=cut
+
+sub smart_search {
+ my %options = @_;
+
+ #here is the agent virtualization
+ my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my @cust_main = ();
+
+ my $skip_fuzzy = delete $options{'no_fuzzy_on_exact'};
+ my $search = delete $options{'search'};
+ ( my $alphanum_search = $search ) =~ s/\W//g;
+
+ if ( $alphanum_search =~ /^1?(\d{3})(\d{3})(\d{4})(\d*)$/ ) { #phone# search
+
+ #false laziness w/Record::ut_phone
+ my $phonen = "$1-$2-$3";
+ $phonen .= " x$4" if $4;
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { %options },
+ 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ).
+ ' ( '.
+ join(' OR ', map "$_ = '$phonen'",
+ qw( daytime night fax
+ ship_daytime ship_night ship_fax )
+ ).
+ ' ) '.
+ " AND $agentnums_sql", #agent virtualization
+ } );
+
+ unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match
+ #try looking for matches with extensions unless one was specified
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { %options },
+ 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ).
+ ' ( '.
+ join(' OR ', map "$_ LIKE '$phonen\%'",
+ qw( daytime night
+ ship_daytime ship_night )
+ ).
+ ' ) '.
+ " AND $agentnums_sql", #agent virtualization
+ } );
+
+ }
+
+ # custnum search (also try agent_custid), with some tweaking options if your
+ # legacy cust "numbers" have letters
+ }
+
+ if ( $search =~ /^\s*(\d+)\s*$/
+ || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+'
+ && $search =~ /^\s*(\w\w?\d+)\s*$/
+ )
+ || ( $conf->exists('address1-search' )
+ && $search =~ /^\s*(\d+\-?\w*)\s*$/ #i.e. 1234A or 9432-D
+ )
+ )
+ {
+
+ my $num = $1;
+
+ if ( $num =~ /^(\d+)$/ && $num <= 2147483647 ) { #need a bigint custnum? wow
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $num, %options },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } );
+ }
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'agent_custid' => $num, %options },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } );
+
+ if ( $conf->exists('address1-search') ) {
+ my $len = length($num);
+ $num = lc($num);
+ foreach my $prefix ( '', 'ship_' ) {
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { %options, },
+ 'extra_sql' =>
+ ( keys(%options) ? ' AND ' : ' WHERE ' ).
+ " LOWER(SUBSTRING(${prefix}address1 FROM 1 FOR $len)) = '$num' ".
+ " AND $agentnums_sql",
+ } );
+ }
+ }
+
+ } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) {
+
+ my($company, $last, $first) = ( $1, $2, $3 );
+
+ # "Company (Last, First)"
+ #this is probably something a browser remembered,
+ #so just do an exact search (but case-insensitive, so USPS standardization
+ #doesn't throw a wrench in the works)
+
+ foreach my $prefix ( '', 'ship_' ) {
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { %options },
+ 'extra_sql' =>
+ ( keys(%options) ? ' AND ' : ' WHERE ' ).
+ join(' AND ',
+ " LOWER(${prefix}first) = ". dbh->quote(lc($first)),
+ " LOWER(${prefix}last) = ". dbh->quote(lc($last)),
+ " LOWER(${prefix}company) = ". dbh->quote(lc($company)),
+ $agentnums_sql,
+ ),
+ } );
+ }
+
+ } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { # value search
+ # try (ship_){last,company}
+
+ my $value = lc($1);
+
+ # # remove "(Last, First)" in "Company (Last, First)", otherwise the
+ # # full strings the browser remembers won't work
+ # $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name
+
+ use Lingua::EN::NameParse;
+ my $NameParse = new Lingua::EN::NameParse(
+ auto_clean => 1,
+ allow_reversed => 1,
+ );
+
+ my($last, $first) = ( '', '' );
+ #maybe disable this too and just rely on NameParse?
+ if ( $value =~ /^(.+),\s*([^,]+)$/ ) { # Last, First
+
+ ($last, $first) = ( $1, $2 );
+
+ #} elsif ( $value =~ /^(.+)\s+(.+)$/ ) {
+ } elsif ( ! $NameParse->parse($value) ) {
+
+ my %name = $NameParse->components;
+ $first = $name{'given_name_1'};
+ $last = $name{'surname_1'};
+
+ }
+
+ if ( $first && $last ) {
+
+ my($q_last, $q_first) = ( dbh->quote($last), dbh->quote($first) );
+
+ #exact
+ my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
+ $sql .= "
+ ( ( LOWER(last) = $q_last AND LOWER(first) = $q_first )
+ OR ( LOWER(ship_last) = $q_last AND LOWER(ship_first) = $q_first )
+ )";
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => \%options,
+ 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
+ } );
+
+ # or it just be something that was typed in... (try that in a sec)
+
+ }
+
+ my $q_value = dbh->quote($value);
+
+ #exact
+ my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
+ $sql .= " ( LOWER(last) = $q_value
+ OR LOWER(company) = $q_value
+ OR LOWER(ship_last) = $q_value
+ OR LOWER(ship_company) = $q_value
+ ";
+ $sql .= " OR LOWER(address1) = $q_value
+ OR LOWER(ship_address1) = $q_value
+ "
+ if $conf->exists('address1-search');
+ $sql .= " )";
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => \%options,
+ 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
+ } );
+
+ #no exact match, trying substring/fuzzy
+ #always do substring & fuzzy (unless they're explicity config'ed off)
+ #getting complaints searches are not returning enough
+ unless ( @cust_main && $skip_fuzzy || $conf->exists('disable-fuzzy') ) {
+
+ #still some false laziness w/search (was search/cust_main.cgi)
+
+ #substring
+
+ my @hashrefs = (
+ { 'company' => { op=>'ILIKE', value=>"%$value%" }, },
+ { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, },
+ );
+
+ if ( $first && $last ) {
+
+ push @hashrefs,
+ { 'first' => { op=>'ILIKE', value=>"%$first%" },
+ 'last' => { op=>'ILIKE', value=>"%$last%" },
+ },
+ { 'ship_first' => { op=>'ILIKE', value=>"%$first%" },
+ 'ship_last' => { op=>'ILIKE', value=>"%$last%" },
+ },
+ ;
+
+ } else {
+
+ push @hashrefs,
+ { 'last' => { op=>'ILIKE', value=>"%$value%" }, },
+ { 'ship_last' => { op=>'ILIKE', value=>"%$value%" }, },
+ ;
+ }
+
+ if ( $conf->exists('address1-search') ) {
+ push @hashrefs,
+ { 'address1' => { op=>'ILIKE', value=>"%$value%" }, },
+ { 'ship_address1' => { op=>'ILIKE', value=>"%$value%" }, },
+ ;
+ }
+
+ foreach my $hashref ( @hashrefs ) {
+
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { %$hashref,
+ %options,
+ },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton
+ } );
+
+ }
+
+ #fuzzy
+ my @fuzopts = (
+ \%options, #hashref
+ '', #select
+ " AND $agentnums_sql", #extra_sql #agent virtualization
+ );
+
+ if ( $first && $last ) {
+ push @cust_main, FS::cust_main->fuzzy_search(
+ { 'last' => $last, #fuzzy hashref
+ 'first' => $first }, #
+ @fuzopts
+ );
+ }
+ foreach my $field ( 'last', 'company' ) {
+ push @cust_main,
+ FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts );
+ }
+ if ( $conf->exists('address1-search') ) {
+ push @cust_main,
+ FS::cust_main->fuzzy_search( { 'address1' => $value }, @fuzopts );
+ }
+
+ }
+
+ }
+
+ #eliminate duplicates
+ my %saw = ();
+ @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+
+ @cust_main;
+
+}
+
+=item email_search
+
+Accepts the following options: I<email>, the email address to search for. The
+email address will be searched for as an email invoice destination and as an
+svc_acct account.
+
+#Any additional options are treated as an additional qualifier on the search
+#(i.e. I<agentnum>).
+
+Returns a (possibly empty) array of FS::cust_main objects (but usually just
+none or one).
+
+=cut
+
+sub email_search {
+ my %options = @_;
+
+ local($DEBUG) = 1;
+
+ my $email = delete $options{'email'};
+
+ #we're only being used by RT at the moment... no agent virtualization yet
+ #my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my @cust_main = ();
+
+ if ( $email =~ /([^@]+)\@([^@]+)/ ) {
+
+ my ( $user, $domain ) = ( $1, $2 );
+
+ warn "$me smart_search: searching for $user in domain $domain"
+ if $DEBUG;
+
+ push @cust_main,
+ map $_->cust_main,
+ qsearch( {
+ 'table' => 'cust_main_invoice',
+ 'hashref' => { 'dest' => $email },
+ }
+ );
+
+ push @cust_main,
+ map $_->cust_main,
+ grep $_,
+ map $_->cust_svc->cust_pkg,
+ qsearch( {
+ 'table' => 'svc_acct',
+ 'hashref' => { 'username' => $user, },
+ 'extra_sql' =>
+ 'AND ( SELECT domain FROM svc_domain
+ WHERE svc_acct.domsvc = svc_domain.svcnum
+ ) = '. dbh->quote($domain),
+ }
+ );
+ }
+
+ my %saw = ();
+ @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+
+ warn "$me smart_search: found ". scalar(@cust_main). " unique customers"
+ if $DEBUG;
+
+ @cust_main;
+
+}
+
+=item check_and_rebuild_fuzzyfiles
+
+=cut
+
+sub check_and_rebuild_fuzzyfiles {
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields
+}
+
+=item rebuild_fuzzyfiles
+
+=cut
+
+sub rebuild_fuzzyfiles {
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ mkdir $dir, 0700 unless -d $dir;
+
+ foreach my $fuzzy ( @fuzzyfields ) {
+
+ open(LOCK,">>$dir/cust_main.$fuzzy")
+ or die "can't open $dir/cust_main.$fuzzy: $!";
+ flock(LOCK,LOCK_EX)
+ or die "can't lock $dir/cust_main.$fuzzy: $!";
+
+ open (CACHE,">$dir/cust_main.$fuzzy.tmp")
+ or die "can't open $dir/cust_main.$fuzzy.tmp: $!";
+
+ foreach my $field ( $fuzzy, "ship_$fuzzy" ) {
+ my $sth = dbh->prepare("SELECT $field FROM cust_main".
+ " WHERE $field != '' AND $field IS NOT NULL");
+ $sth->execute or die $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_arrayref ) {
+ print CACHE $row->[0]. "\n";
+ }
+
+ }
+
+ close CACHE or die "can't close $dir/cust_main.$fuzzy.tmp: $!";
+
+ rename "$dir/cust_main.$fuzzy.tmp", "$dir/cust_main.$fuzzy";
+ close LOCK;
+ }
+
+}
+
+=item all_X
+
+=cut
+
+sub all_X {
+ my( $self, $field ) = @_;
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ open(CACHE,"<$dir/cust_main.$field")
+ or die "can't open $dir/cust_main.$field: $!";
+ my @array = map { chomp; $_; } <CACHE>;
+ close CACHE;
+ \@array;
+}
+
+=item append_fuzzyfiles FIRSTNAME LASTNAME COMPANY ADDRESS1
+
+=cut
+
+sub append_fuzzyfiles {
+ #my( $first, $last, $company ) = @_;
+
+ &check_and_rebuild_fuzzyfiles;
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+
+ foreach my $field (@fuzzyfields) {
+ my $value = shift;
+
+ if ( $value ) {
+
+ open(CACHE,">>$dir/cust_main.$field")
+ or die "can't open $dir/cust_main.$field: $!";
+ flock(CACHE,LOCK_EX)
+ or die "can't lock $dir/cust_main.$field: $!";
+
+ print CACHE "$value\n";
+
+ flock(CACHE,LOCK_UN)
+ or die "can't unlock $dir/cust_main.$field: $!";
+ close CACHE;
+ }
+
+ }
+
+ 1;
+}
+
+=item batch_charge
+
+=cut
+
+sub batch_charge {
+ my $param = shift;
+ #warn join('-',keys %$param);
+ my $fh = $param->{filehandle};
+ my @fields = @{$param->{fields}};
+
+ 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
+
+}
+
+=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
+
+Sends a templated email notification to the customer (see L<Text::Template>).
+
+OPTIONS is a hash and may include
+
+I<from> - the email sender (default is invoice_from)
+
+I<to> - comma-separated scalar or arrayref of recipients
+ (default is invoicing_list)
+
+I<subject> - The subject line of the sent email notification
+ (default is "Notice from company_name")
+
+I<extra_fields> - a hashref of name/value pairs which will be substituted
+ into the template
+
+The following variables are vavailable in the template.
+
+I<$first> - the customer first name
+I<$last> - the customer last name
+I<$company> - the customer company
+I<$payby> - a description of the method of payment for the customer
+ # would be nice to use FS::payby::shortname
+I<$payinfo> - the account information used to collect for this customer
+I<$expdate> - the expiration of the customer payment in seconds from epoch
+
+=cut
+
+sub notify {
+ my ($self, $template, %options) = @_;
+
+ return unless $conf->exists($template);
+
+ my $from = $conf->config('invoice_from', $self->agentnum)
+ if $conf->exists('invoice_from', $self->agentnum);
+ $from = $options{from} if exists($options{from});
+
+ my $to = join(',', $self->invoicing_list_emailonly);
+ $to = $options{to} if exists($options{to});
+
+ my $subject = "Notice from " . $conf->config('company_name', $self->agentnum)
+ if $conf->exists('company_name', $self->agentnum);
+ $subject = $options{subject} if exists($options{subject});
+
+ my $notify_template = new Text::Template (TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n",
+ $conf->config($template)]
+ )
+ or die "can't create new Text::Template object: Text::Template::ERROR";
+ $notify_template->compile()
+ or die "can't compile template: Text::Template::ERROR";
+
+ $FS::notify_template::_template::company_name =
+ $conf->config('company_name', $self->agentnum);
+ $FS::notify_template::_template::company_address =
+ join("\n", $conf->config('company_address', $self->agentnum) ). "\n";
+
+ my $paydate = $self->paydate || '2037-12-31';
+ $FS::notify_template::_template::first = $self->first;
+ $FS::notify_template::_template::last = $self->last;
+ $FS::notify_template::_template::company = $self->company;
+ $FS::notify_template::_template::payinfo = $self->mask_payinfo;
+ my $payby = $self->payby;
+ 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') {
+ $FS::notify_template::_template::payby = 'credit card';
+ ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+ $expire_time--;
+ }elsif ($payby eq 'COMP') {
+ $FS::notify_template::_template::payby = 'complimentary account';
+ }else{
+ $FS::notify_template::_template::payby = 'current method';
+ }
+ $FS::notify_template::_template::expdate = $expire_time;
+
+ for (keys %{$options{extra_fields}}){
+ no strict "refs";
+ ${"FS::notify_template::_template::$_"} = $options{extra_fields}->{$_};
+ }
+
+ send_email(from => $from,
+ to => $to,
+ subject => $subject,
+ body => $notify_template->fill_in( PACKAGE =>
+ 'FS::notify_template::_template' ),
+ );
+
+}
+
+=item generate_letter CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
+
+Generates a templated notification to the customer (see L<Text::Template>).
+
+OPTIONS is a hash and may include
+
+I<extra_fields> - a hashref of name/value pairs which will be substituted
+ into the template. These values may override values mentioned below
+ and those from the customer record.
+
+The following variables are available in the template instead of or in addition
+to the fields of the customer record.
+
+I<$payby> - a description of the method of payment for the customer
+ # would be nice to use FS::payby::shortname
+I<$payinfo> - the masked account information used to collect for this customer
+I<$expdate> - the expiration of the customer payment method in seconds from epoch
+I<$returnaddress> - the return address defaults to invoice_latexreturnaddress or company_address
+
+=cut
+
+sub generate_letter {
+ my ($self, $template, %options) = @_;
+
+ return unless $conf->exists($template);
+
+ my $letter_template = new Text::Template
+ ( TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config($template)],
+ DELIMITERS => [ '[@--', '--@]' ],
+ )
+ or die "can't create new Text::Template object: Text::Template::ERROR";
+
+ $letter_template->compile()
+ or die "can't compile template: Text::Template::ERROR";
+
+ my %letter_data = map { $_ => $self->$_ } $self->fields;
+ $letter_data{payinfo} = $self->mask_payinfo;
+
+ #my $paydate = $self->paydate || '2037-12-31';
+ my $paydate = $self->paydate =~ /^\S+$/ ? $self->paydate : '2037-12-31';
+
+ my $payby = $self->payby;
+ 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') {
+ $letter_data{payby} = 'credit card';
+ ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+ $expire_time--;
+ }elsif ($payby eq 'COMP') {
+ $letter_data{payby} = 'complimentary account';
+ }else{
+ $letter_data{payby} = 'current method';
+ }
+ $letter_data{expdate} = $expire_time;
+
+ for (keys %{$options{extra_fields}}){
+ $letter_data{$_} = $options{extra_fields}->{$_};
+ }
+
+ unless(exists($letter_data{returnaddress})){
+ my $retadd = join("\n", $conf->config_orbase( 'invoice_latexreturnaddress',
+ $self->agent_template)
+ );
+ if ( length($retadd) ) {
+ $letter_data{returnaddress} = $retadd;
+ } elsif ( grep /\S/, $conf->config('company_address', $self->agentnum) ) {
+ $letter_data{returnaddress} =
+ join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
+ $conf->config('company_address', $self->agentnum)
+ );
+ } else {
+ $letter_data{returnaddress} = '~';
+ }
+ }
+
+ $letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
+
+ $letter_data{company_name} = $conf->config('company_name', $self->agentnum);
+
+ my $dir = $FS::UID::conf_dir."/cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.tex',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+
+ $letter_template->fill_in( OUTPUT => $fh, HASH => \%letter_data );
+ close $fh;
+ $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
+ return $1;
+}
+
+=item print_ps TEMPLATE
+
+Returns an postscript letter filled in from TEMPLATE, as a scalar.
+
+=cut
+
+sub print_ps {
+ my $self = shift;
+ my $file = $self->generate_letter(@_);
+ FS::Misc::generate_ps($file);
+}
+
+=item print TEMPLATE
+
+Prints the filled in template.
+
+TEMPLATE is the name of a L<Text::Template> to fill in and print.
+
+=cut
+
+sub queueable_print {
+ my %opt = @_;
+
+ my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
+ or die "invalid customer number: " . $opt{custvnum};
+
+ my $error = $self->print( $opt{template} );
+ die $error if $error;
+}
+
+sub print {
+ my ($self, $template) = (shift, shift);
+ do_print [ $self->print_ps($template) ];
+}
+
+#these three subs should just go away once agent stuff is all config overrides
+
+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 ) = @_;
+
+ #yuck. this whole thing needs to be reconciled better with 1.9's idea of
+ #agent-specific Conf
+
+ use FS::part_event::Condition;
+
+ my $agentnum = $self->agentnum;
+
+ my $regexp = regexp_sql();
+
+ my $part_event_option =
+ qsearchs({
+ 'select' => 'part_event_option.*',
+ 'table' => 'part_event_option',
+ 'addl_from' => q{
+ LEFT JOIN part_event USING ( eventpart )
+ LEFT JOIN part_event_option AS peo_agentnum
+ ON ( part_event.eventpart = peo_agentnum.eventpart
+ AND peo_agentnum.optionname = 'agentnum'
+ AND peo_agentnum.optionvalue }. $regexp. q{ '(^|,)}. $agentnum. q{(,|$)'
+ )
+ LEFT JOIN part_event_condition
+ ON ( part_event.eventpart = part_event_condition.eventpart
+ AND part_event_condition.conditionname = 'cust_bill_age'
+ )
+ LEFT JOIN part_event_condition_option
+ ON ( part_event_condition.eventconditionnum = part_event_condition_option.eventconditionnum
+ AND part_event_condition_option.optionname = 'age'
+ )
+ },
+ #'hashref' => { 'optionname' => $option },
+ #'hashref' => { 'part_event_option.optionname' => $option },
+ 'extra_sql' =>
+ " WHERE part_event_option.optionname = ". dbh->quote($option).
+ " AND action = 'cust_bill_send_agent' ".
+ " AND ( disabled IS NULL OR disabled != 'Y' ) ".
+ " AND peo_agentnum.optionname = 'agentnum' ".
+ " AND ( agentnum IS NULL OR agentnum = $agentnum ) ".
+ " ORDER BY
+ CASE WHEN part_event_condition_option.optionname IS NULL
+ THEN -1
+ ELSE ". FS::part_event::Condition->age2seconds_sql('part_event_condition_option.optionvalue').
+ " END
+ , part_event.weight".
+ " LIMIT 1"
+ });
+
+ unless ( $part_event_option ) {
+ return $self->agent->invoice_template || ''
+ if $option eq 'agent_templatename';
+ return '';
+ }
+
+ $part_event_option->optionvalue;
+
+}
+
+sub queued_bill {
+ ## actual sub, not a method, designed to be called from the queue.
+ ## sets up the customer, and calls the bill_and_collect
+ my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
+ my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
+ $cust_main->bill_and_collect(
+ %args,
+ );
+}
+
+sub _upgrade_data { #class method
+ my ($class, %opts) = @_;
+
+ my $sql = 'UPDATE h_cust_main SET paycvv = NULL WHERE paycvv IS NOT NULL';
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ local($ignore_expired_card) = 1;
+ $class->_upgrade_otaker(%opts);
+
+}
+
+=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
+
+Birthdates rely on negative epoch values.
+
+The payby for card/check batches is broken. With mixed batching, bad
+things will happen.
+
+B<collect> I<invoice_time> should be renamed I<time>, like B<bill>.
+
+=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/Import.pm b/FS/FS/cust_main/Import.pm
new file mode 100644
index 0000000..901ff18
--- /dev/null
+++ b/FS/FS/cust_main/Import.pm
@@ -0,0 +1,427 @@
+package FS::cust_main::Import;
+
+use strict;
+use vars qw( $DEBUG $conf );
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+use File::Slurp qw( slurp );
+use FS::Misc::DateTime qw( parse_datetime );
+use FS::UID qw( dbh );
+use FS::Record qw( qsearchs );
+use FS::cust_main;
+use FS::svc_acct;
+use FS::svc_external;
+use FS::svc_phone;
+use FS::part_referral;
+
+$DEBUG = 0;
+
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::cust_main::Import - Batch customer importing
+
+=head1 SYNOPSIS
+
+ use FS::cust_main::Import;
+
+ #import
+ FS::cust_main::Import::batch_import( {
+ file => $file, #filename
+ type => $type, #csv or xls
+ format => $format, #extended, extended-plus_company, svc_external,
+ # or svc_external_svc_phone
+ agentnum => $agentnum,
+ refnum => $refnum,
+ pkgpart => $pkgpart,
+ job => $job, #optional job queue job, for progressbar updates
+ custbatch => $custbatch, #optional batch unique identifier
+ } );
+ die $error if $error;
+
+ #ajax helper
+ use FS::UI::Web::JSRPC;
+ my $server =
+ new FS::UI::Web::JSRPC 'FS::cust_main::Import::process_batch_import', $cgi;
+ print $server->process;
+
+=head1 DESCRIPTION
+
+Batch customer importing.
+
+=head1 SUBROUTINES
+
+=item process_batch_import
+
+Load a batch import as a queued JSRPC job
+
+=cut
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $files = $param->{'uploaded_files'}
+ or die "No files provided.\n";
+
+ my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+ my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+ my $file = $dir. $files{'file'};
+
+ my $type;
+ if ( $file =~ /\.(\w+)$/i ) {
+ $type = lc($1);
+ } else {
+ #or error out???
+ warn "can't parse file type from filename $file; defaulting to CSV";
+ $type = 'csv';
+ }
+
+ my $error =
+ FS::cust_main::Import::batch_import( {
+ job => $job,
+ file => $file,
+ type => $type,
+ custbatch => $param->{custbatch},
+ agentnum => $param->{'agentnum'},
+ refnum => $param->{'refnum'},
+ pkgpart => $param->{'pkgpart'},
+ #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2
+ # city state zip comments )],
+ 'format' => $param->{'format'},
+ } );
+
+ unlink $file;
+
+ die "$error\n" if $error;
+
+}
+
+=item batch_import
+
+=cut
+
+
+#some false laziness w/cdr.pm now
+sub batch_import {
+ my $param = shift;
+
+ my $job = $param->{job};
+
+ my $filename = $param->{file};
+ my $type = $param->{type} || 'csv';
+
+ my $custbatch = $param->{custbatch};
+
+ my $agentnum = $param->{agentnum};
+ my $refnum = $param->{refnum};
+ my $pkgpart = $param->{pkgpart};
+
+ my $format = $param->{'format'};
+
+ my @fields;
+ my $payby;
+ if ( $format eq 'simple' ) {
+ @fields = qw( cust_pkg.setup dayphone first last
+ address1 address2 city state zip comments );
+ $payby = 'BILL';
+ } elsif ( $format eq 'extended' ) {
+ @fields = qw( agent_custid refnum
+ last first address1 address2 city state zip country
+ daytime night
+ ship_last ship_first ship_address1 ship_address2
+ ship_city ship_state ship_zip ship_country
+ payinfo paycvv paydate
+ invoicing_list
+ cust_pkg.pkgpart
+ svc_acct.username svc_acct._password
+ );
+ $payby = 'BILL';
+ } elsif ( $format eq 'extended-plus_company' ) {
+ @fields = qw( agent_custid refnum
+ last first company address1 address2 city state zip country
+ daytime night
+ ship_last ship_first ship_company ship_address1 ship_address2
+ ship_city ship_state ship_zip ship_country
+ payinfo paycvv paydate
+ invoicing_list
+ cust_pkg.pkgpart
+ svc_acct.username svc_acct._password
+ );
+ $payby = 'BILL';
+ } elsif ( $format =~ /^svc_external/ ) {
+ @fields = qw( agent_custid refnum
+ last first company address1 address2 city state zip country
+ daytime night
+ ship_last ship_first ship_company ship_address1 ship_address2
+ ship_city ship_state ship_zip ship_country
+ payinfo paycvv paydate
+ invoicing_list
+ cust_pkg.pkgpart cust_pkg.bill
+ svc_external.id svc_external.title
+ );
+ push @fields, map "svc_phone.$_", qw( countrycode phonenum sip_password pin)
+ if $format eq 'svc_external_svc_phone';
+ $payby = 'BILL';
+ } else {
+ die "unknown format $format";
+ }
+
+ my $count;
+ my $parser;
+ my @buffer = ();
+ if ( $type eq 'csv' ) {
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ $parser = new Text::CSV_XS;
+
+ @buffer = split(/\r?\n/, slurp($filename) );
+ $count = scalar(@buffer);
+
+ } elsif ( $type eq 'xls' ) {
+
+ eval "use Spreadsheet::ParseExcel;";
+ die $@ if $@;
+
+ my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($filename);
+ $parser = $excel->{Worksheet}[0]; #first sheet
+
+ $count = $parser->{MaxRow} || $parser->{MinRow};
+ $count++;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ #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;
+
+ my $line;
+ my $row = 0;
+ my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
+ while (1) {
+
+ my @columns = ();
+ if ( $type eq 'csv' ) {
+
+ last unless scalar(@buffer);
+ $line = shift(@buffer);
+
+ $parser->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $parser->error_input();
+ };
+ @columns = $parser->fields();
+
+ } elsif ( $type eq 'xls' ) {
+
+ last if $row > ($parser->{MaxRow} || $parser->{MinRow})
+ || ! $parser->{Cells}[$row];
+
+ my @row = @{ $parser->{Cells}[$row] };
+ @columns = map $_->{Val}, @row;
+
+ #my $z = 'A';
+ #warn $z++. ": $_\n" for @columns;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ #warn join('-',@columns);
+
+ my %cust_main = (
+ custbatch => $custbatch,
+ agentnum => $agentnum,
+ refnum => $refnum,
+ country => $conf->config('countrydefault') || 'US',
+ payby => $payby, #default
+ paydate => '12/2037', #default
+ );
+ my $billtime = time;
+ my %cust_pkg = ( pkgpart => $pkgpart );
+ my %svc_x = ();
+ foreach my $field ( @fields ) {
+
+ if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
+
+ #$cust_pkg{$1} = parse_datetime( shift @$columns );
+ if ( $1 eq 'pkgpart' ) {
+ $cust_pkg{$1} = shift @columns;
+ } elsif ( $1 eq 'setup' ) {
+ $billtime = parse_datetime(shift @columns);
+ } else {
+ $cust_pkg{$1} = parse_datetime( shift @columns );
+ }
+
+ } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) {
+
+ $svc_x{$1} = shift @columns;
+
+ } elsif ( $field =~ /^svc_external\.(id|title)$/ ) {
+
+ $svc_x{$1} = shift @columns;
+
+ } elsif ( $field =~ /^svc_phone\.(countrycode|phonenum|sip_password|pin)$/ ) {
+ $svc_x{$1} = shift @columns;
+
+ } else {
+
+ #refnum interception
+ if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) {
+
+ my $referral = $columns[0];
+ my %hash = ( 'referral' => $referral,
+ 'agentnum' => $agentnum,
+ 'disabled' => '',
+ );
+
+ my $part_referral = qsearchs('part_referral', \%hash )
+ || new FS::part_referral \%hash;
+
+ unless ( $part_referral->refnum ) {
+ my $error = $part_referral->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't auto-insert advertising source: $referral: $error";
+ }
+ }
+
+ $columns[0] = $part_referral->refnum;
+ }
+
+ my $value = shift @columns;
+ $cust_main{$field} = $value if length($value);
+ }
+ }
+
+ $cust_main{'payby'} = 'CARD'
+ if defined $cust_main{'payinfo'}
+ && length $cust_main{'payinfo'};
+
+ my $invoicing_list = $cust_main{'invoicing_list'}
+ ? [ delete $cust_main{'invoicing_list'} ]
+ : [];
+
+ my $cust_main = new FS::cust_main ( \%cust_main );
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash'; #this part is important
+
+ if ( $cust_pkg{'pkgpart'} ) {
+ my $cust_pkg = new FS::cust_pkg ( \%cust_pkg );
+
+ my @svc_x = ();
+ my $svcdb = '';
+ if ( $svc_x{'username'} ) {
+ $svcdb = 'svc_acct';
+ } elsif ( $svc_x{'id'} || $svc_x{'title'} ) {
+ $svcdb = 'svc_external';
+ }
+
+ my $svc_phone = '';
+ if ( $svc_x{'countrycode'} || $svc_x{'phonenum'} ) {
+ $svc_phone = FS::svc_phone->new( {
+ map { $_ => delete($svc_x{$_}) }
+ qw( countrycode phonenum sip_password pin)
+ } );
+ }
+
+ if ( $svcdb || $svc_phone ) {
+ my $part_pkg = $cust_pkg->part_pkg;
+ unless ( $part_pkg ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "unknown pkgpart: ". $cust_pkg{'pkgpart'};
+ }
+ if ( $svcdb ) {
+ $svc_x{svcpart} = $part_pkg->svcpart_unique_svcdb( $svcdb );
+ my $class = "FS::$svcdb";
+ push @svc_x, $class->new( \%svc_x );
+ }
+ if ( $svc_phone ) {
+ $svc_phone->svcpart( $part_pkg->svcpart_unique_svcdb('svc_phone') );
+ push @svc_x, $svc_phone;
+ }
+ }
+
+ $hash{$cust_pkg} = \@svc_x;
+ }
+
+ my $error = $cust_main->insert( \%hash, $invoicing_list );
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert customer". ( $line ? " for $line" : '' ). ": $error";
+ }
+
+ if ( $format eq 'simple' ) {
+
+ #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";
+ }
+
+ $error = $cust_main->apply_payments_and_credits;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't bill customer for $line: $error";
+ }
+
+ $error = $cust_main->collect();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't collect customer for $line: $error";
+ }
+
+ }
+
+ $row++;
+
+ if ( $job && time - $min_sec > $last ) { #progress bar
+ $job->update_statustext( int(100 * $row / $count) );
+ $last = time;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
+
+ return "Empty file!" unless $row;
+
+ ''; #no error
+
+}
+
+=head1 BUGS
+
+Not enough documentation.
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::cust_pkg>,
+L<FS::svc_acct>, L<FS::svc_external>, L<FS::svc_phone>
+
+=cut
+
+1;
diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm
new file mode 100644
index 0000000..3dde95f
--- /dev/null
+++ b/FS/FS/cust_main_Mixin.pm
@@ -0,0 +1,344 @@
+package FS::cust_main_Mixin;
+
+use strict;
+use vars qw( $DEBUG $me );
+use Carp qw( confess );
+use FS::UID qw(dbh);
+use FS::cust_main;
+
+$DEBUG = 0;
+$me = '[FS::cust_main_Mixin]';
+
+=head1 NAME
+
+FS::cust_main_Mixin - Mixin class for records that contain fields from cust_main
+
+=head1 SYNOPSIS
+
+package FS::some_table;
+use vars qw(@ISA);
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+=head1 DESCRIPTION
+
+This is a mixin class for records that contain fields from the cust_main table,
+for example, from a JOINed search. See httemplate/search/ for examples.
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+sub cust_unlinked_msg { '(unlinked)'; }
+sub cust_linked { $_[0]->custnum; }
+
+=item display_custnum
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<name> method, or "(unlinked)" if this object is not linked to
+a customer.
+
+=cut
+
+sub display_custnum {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::display_custnum($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item name
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<name> method, or "(unlinked)" if this object is not linked to
+a customer.
+
+=cut
+
+sub name {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::name($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item ship_name
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<ship_name> method, or "(unlinked)" if this object is not
+linked to a customer.
+
+=cut
+
+sub ship_name {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::ship_name($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item contact
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<contact> method, or "(unlinked)" if this object is not linked
+to a customer.
+
+=cut
+
+sub contact {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::contact($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item ship_contact
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<ship_contact> method, or "(unlinked)" if this object is not
+linked to a customer.
+
+=cut
+
+sub ship_contact {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::ship_contact($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item country_full
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<country_full> method, or "(unlinked)" if this object is not
+linked to a customer.
+
+=cut
+
+sub country_full {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::country_full($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item invoicing_list_emailonly
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<invoicing_list_emailonly> method, or "(unlinked)" if this
+object is not linked to a customer.
+
+=cut
+
+sub invoicing_list_emailonly {
+ my $self = shift;
+ warn "invoicing_list_email only called on $self, ".
+ "custnum ". $self->custnum. "\n"
+ if $DEBUG;
+ $self->cust_linked
+ ? FS::cust_main::invoicing_list_emailonly($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item invoicing_list_emailonly_scalar
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<invoicing_list_emailonly_scalar> method, or "(unlinked)" if
+this object is not linked to a customer.
+
+=cut
+
+sub invoicing_list_emailonly_scalar {
+ my $self = shift;
+ warn "invoicing_list_emailonly called on $self, ".
+ "custnum ". $self->custnum. "\n"
+ if $DEBUG;
+ $self->cust_linked
+ ? FS::cust_main::invoicing_list_emailonly_scalar($self)
+ : $self->cust_unlinked_msg;
+}
+
+=item invoicing_list
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<invoicing_list> method, or "(unlinked)" if this object is not
+linked to a customer.
+
+Note: this method is read-only.
+
+=cut
+
+#read-only
+sub invoicing_list {
+ my $self = shift;
+ $self->cust_linked
+ ? FS::cust_main::invoicing_list($self)
+ : ();
+}
+
+=item status
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<status> method, or "(unlinked)" if this object is not linked to
+a customer.
+
+=cut
+
+sub cust_status {
+ my $self = shift;
+ return $self->cust_unlinked_msg unless $self->cust_linked;
+
+ #FS::cust_main::status($self)
+ #false laziness w/actual cust_main::status
+ # (make sure FS::cust_main methods are called)
+ for my $status (qw( prospect active inactive suspended cancelled )) {
+ my $method = $status.'_sql';
+ my $sql = FS::cust_main->$method();;
+ my $numnum = ( $sql =~ s/cust_main\.custnum/?/g );
+ my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
+ $sth->execute( ($self->custnum) x $numnum )
+ or die "Error executing 'SELECT $sql': ". $sth->errstr;
+ return $status if $sth->fetchrow_arrayref->[0];
+ }
+}
+
+=item ucfirst_cust_status
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<ucfirst_status> method, or "(unlinked)" if this object is not
+linked to a customer.
+
+=cut
+
+sub ucfirst_cust_status {
+ my $self = shift;
+ $self->cust_linked
+ ? ucfirst( $self->cust_status(@_) )
+ : $self->cust_unlinked_msg;
+}
+
+=item cust_statuscolor
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+FS::cust_main I<statuscol> method, or "000000" if this object is not linked to
+a customer.
+
+=cut
+
+sub cust_statuscolor {
+ my $self = shift;
+
+ $self->cust_linked
+ ? FS::cust_main::cust_statuscolor($self)
+ : '000000';
+}
+
+=item prospect_sql
+
+=item active_sql
+
+=item inactive_sql
+
+=item suspended_sql
+
+=item cancelled_sql
+
+Class methods that return SQL framents, equivalent to the corresponding
+FS::cust_main method.
+
+=cut
+
+# my \$self = shift;
+# \$self->cust_linked
+# ? FS::cust_main::${sub}_sql(\$self)
+# : '0';
+
+foreach my $sub (qw( prospect active inactive suspended cancelled )) {
+ eval "
+ sub ${sub}_sql {
+ confess 'cust_main_Mixin ${sub}_sql called with object' if ref(\$_[0]);
+ 'cust_main.custnum IS NOT NULL AND '. FS::cust_main->${sub}_sql();
+ }
+ ";
+ die $@ if $@;
+}
+
+=item cust_search_sql
+
+Returns a list of SQL WHERE fragments to search for parameters specified
+in HASHREF. Valid parameters are:
+
+=over 4
+
+=item agentnum
+
+=item status
+
+=item payby
+
+=back
+
+=cut
+
+sub cust_search_sql {
+ my($class, $param) = @_;
+
+ if ( $DEBUG ) {
+ warn "$me cust_search_sql called with params: \n".
+ join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
+ }
+
+ my @search = ();
+
+ if ( $param->{'agentnum'} && $param->{'agentnum'} =~ /^(\d+)$/ ) {
+ push @search, "cust_main.agentnum = $1";
+ }
+
+ #status (prospect active inactive suspended cancelled)
+ if ( grep { $param->{'status'} eq $_ } FS::cust_main->statuses() ) {
+ my $method = $param->{'status'}. '_sql';
+ push @search, $class->$method();
+ }
+
+ #payby
+ my @payby = ref($param->{'payby'})
+ ? @{ $param->{'payby'} }
+ : split(',', $param->{'payby'});
+ @payby = grep /^([A-Z]{4})$/, @payby;
+ if ( @payby ) {
+ push @search, 'cust_main.payby IN ('. join(',', map "'$_'", @payby). ')';
+ }
+
+ #here is the agent virtualization
+ push @search,
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+ return @search;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
new file mode 100644
index 0000000..ab1ac1e
--- /dev/null
+++ b/FS/FS/cust_main_county.pm
@@ -0,0 +1,503 @@
+package FS::cust_main_county;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $conf
+ @cust_main_county %cust_main_county $countyflag ); # $cityflag );
+use Exporter;
+use FS::Record qw( qsearch dbh );
+use FS::cust_bill_pkg;
+use FS::cust_bill;
+use FS::cust_pkg;
+use FS::part_pkg;
+use FS::cust_tax_exempt;
+use FS::cust_tax_exempt_pkg;
+
+@ISA = qw( FS::Record );
+@EXPORT_OK = qw( regionselector );
+
+@cust_main_county = ();
+$countyflag = '';
+#$cityflag = '';
+
+#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 city
+
+=item county
+
+=item state
+
+=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_textn('city')
+ || $self->ut_textn('county')
+ || $self->ut_anything('state')
+ || $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 '';
+}
+
+=item sql_taxclass_sameregion
+
+Returns an SQL WHERE fragment or the empty string to search for entries
+with different tax classes.
+
+=cut
+
+#hmm, description above could be better...
+
+sub sql_taxclass_sameregion {
+ my $self = shift;
+
+ my $same_query = 'SELECT taxclass FROM cust_main_county '.
+ ' WHERE taxnum != ? AND country = ?';
+ my @same_param = ( 'taxnum', 'country' );
+ foreach my $opt_field (qw( state county )) {
+ if ( $self->$opt_field() ) {
+ $same_query .= " AND $opt_field = ?";
+ push @same_param, $opt_field;
+ } else {
+ $same_query .= " AND $opt_field IS NULL";
+ }
+ }
+
+ my @taxclasses = $self->_list_sql( \@same_param, $same_query );
+
+ return '' unless scalar(@taxclasses);
+
+ '( taxclass IS NULL OR ( '. #only if !$self->taxclass ??
+ join(' AND ', map { 'taxclass != '.dbh->quote($_) } @taxclasses ).
+ ' ) ) ';
+}
+
+sub _list_sql {
+ my( $self, $param, $sql ) = @_;
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute( map $self->$_(), @$param )
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ map $_->[0], @{ $sth->fetchall_arrayref };
+}
+
+=item taxline TAXABLES_ARRAYREF, [ OPTION => VALUE ... ]
+
+Returns a listref of a name and an amount of tax calculated for the list of
+packages or amounts referenced by TAXABLES_ARRAYREF. Returns a scalar error
+message on error.
+
+Options include custnum and invoice_date and are hints to this method
+
+=cut
+
+sub taxline {
+ my( $self, $taxables, %opt ) = @_;
+
+ my @exemptions = ();
+ push @exemptions, @{ $_->_cust_tax_exempt_pkg }
+ for grep { ref($_) } @$taxables;
+
+ 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 $name = $self->taxname || 'Tax';
+ my $amount = 0;
+
+ foreach my $cust_bill_pkg (@$taxables) {
+
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+ my $cust_bill = $cust_pkg->cust_bill if $cust_pkg;
+ my $custnum = $cust_pkg ? $cust_pkg->custnum : $opt{custnum};
+ my $part_pkg = $cust_bill_pkg->part_pkg;
+ my $invoice_date = $cust_bill ? $cust_bill->_date : $opt{invoice_date};
+
+ my $taxable_charged = 0;
+ $taxable_charged += $cust_bill_pkg->setup
+ unless $part_pkg->setuptax =~ /^Y$/i
+ || $self->setuptax =~ /^Y$/i;
+ $taxable_charged += $cust_bill_pkg->recur
+ unless $part_pkg->recurtax =~ /^Y$/i
+ || $self->recurtax =~ /^Y$/i;
+
+ next unless $taxable_charged;
+
+ if ( $self->exempt_amount && $self->exempt_amount > 0 ) {
+ #my ($mon,$year) = (localtime($cust_bill_pkg->sdate) )[4,5];
+ my ($mon,$year) =
+ (localtime( $cust_bill_pkg->sdate || $invoice_date ) )[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 );
+
+ #call the whole thing off if this customer has any old
+ #exemption records...
+ my @cust_tax_exempt =
+ qsearch( 'cust_tax_exempt' => { custnum=> $custnum } );
+ if ( @cust_tax_exempt ) {
+ $dbh->rollback if $oldAutoCommit;
+ return
+ 'this customer still has old-style tax exemption records; '.
+ 'run bin/fs-migrate-cust_tax_exempt?';
+ }
+
+ foreach my $which_month ( 1 .. $freq ) {
+
+ #maintain the new exemption table now
+ my $sql = "
+ SELECT SUM(amount)
+ FROM cust_tax_exempt_pkg
+ LEFT JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_bill USING ( invnum )
+ WHERE custnum = ?
+ AND taxnum = ?
+ AND year = ?
+ AND month = ?
+ ";
+ my $sth = dbh->prepare($sql) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "fatal: can't lookup exising exemption: ". dbh->errstr;
+ };
+ $sth->execute(
+ $custnum,
+ $self->taxnum,
+ 1900+$year,
+ $mon,
+ ) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "fatal: can't lookup exising exemption: ". dbh->errstr;
+ };
+ my $existing_exemption = $sth->fetchrow_arrayref->[0] || 0;
+
+ foreach ( grep { $_->taxnum == $self->taxnum &&
+ $_->month == $mon &&
+ $_->year == 1900+$year
+ } @exemptions
+ )
+ {
+ $existing_exemption += $_->amount;
+ }
+
+ my $remaining_exemption =
+ $self->exempt_amount - $existing_exemption;
+ if ( $remaining_exemption > 0 ) {
+ my $addl = $remaining_exemption > $taxable_per_month
+ ? $taxable_per_month
+ : $remaining_exemption;
+ $taxable_charged -= $addl;
+
+ my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg ( {
+ 'taxnum' => $self->taxnum,
+ 'year' => 1900+$year,
+ 'month' => $mon,
+ 'amount' => sprintf("%.2f", $addl ),
+ } );
+ if ($cust_bill_pkg->billpkgnum) {
+ $cust_tax_exempt_pkg->billpkgnum($cust_bill_pkg->billpkgnum);
+ my $error = $cust_tax_exempt_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "fatal: can't insert cust_tax_exempt_pkg: $error";
+ }
+ }else{
+ push @exemptions, $cust_tax_exempt_pkg;
+ push @{ $cust_bill_pkg->_cust_tax_exempt_pkg }, $cust_tax_exempt_pkg;
+ } # if $cust_bill_pkg->billpkgnum
+ } # if $remaining_exemption > 0
+
+ #++
+ $mon++;
+ #until ( $mon < 12 ) { $mon -= 12; $year++; }
+ until ( $mon < 13 ) { $mon -= 12; $year++; }
+
+ } #foreach $which_month
+
+ } #if $tax->exempt_amount
+
+ $taxable_charged = sprintf( "%.2f", $taxable_charged);
+
+ $amount += $taxable_charged * $self->tax / 100
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return {
+ 'name' => $name,
+ 'amount' => $amount,
+ };
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item regionselector [ COUNTY STATE COUNTRY [ PREFIX [ ONCHANGE [ DISABLED ] ] ] ]
+
+=cut
+
+sub regionselector {
+ my ( $selected_county, $selected_state, $selected_country,
+ $prefix, $onchange, $disabled ) = @_;
+
+ $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 $dstate = $state ) =~ s/[\n\r]//g;
+ my $text = $dstate || '(n/a)';
+ $script_html .= qq!opt(what.form.${prefix}state, "$dstate", "$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" $disabled>!;
+ $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" $disabled>!;
+ foreach my $state ( sort keys %{ $cust_main_county{$selected_country} } ) {
+ my $text = $state || '(n/a)';
+ my $selected = $state eq $selected_state ? 'SELECTED' : '';
+ $state_html .= qq(\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" $disabled>!;
+ 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 .= qq(\n<OPTION$selected VALUE="$country">$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_exemption.pm b/FS/FS/cust_main_exemption.pm
new file mode 100644
index 0000000..06d22b7
--- /dev/null
+++ b/FS/FS/cust_main_exemption.pm
@@ -0,0 +1,128 @@
+package FS::cust_main_exemption;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+
+=head1 NAME
+
+FS::cust_main_exemption - Object methods for cust_main_exemption records
+
+=head1 SYNOPSIS
+
+ use FS::cust_main_exemption;
+
+ $record = new FS::cust_main_exemption \%hash;
+ $record = new FS::cust_main_exemption { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_main_exemption object represents a customer tax exemption from a
+specific tax name (prefix). FS::cust_main_exemption inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item exemptionnum
+
+Primary key
+
+=item custnum
+
+Customer (see L<FS::cust_main>)
+
+=item taxname
+
+taxname
+
+
+=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 { 'cust_main_exemption'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('exemptionnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_text('taxname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::Record>, 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..ec01842
--- /dev/null
+++ b/FS/FS/cust_main_invoice.pm
@@ -0,0 +1,188 @@
+package FS::cust_main_invoice;
+
+use strict;
+use vars qw(@ISA);
+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, `POST' to enable mailing (the default if no cust_main_invoice records exist), or `FAX' to enable faxing via a HylaFAX server.
+
+=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 replace 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;
+
+ my $conf = new FS::Conf;
+
+ if ( $self->dest =~ /^(POST|FAX)$/ ) {
+ #contemplate our navel
+ } elsif ( $self->dest =~ /^(\d+)$/ ) {
+ return "Unknown local account (specified by svcnum: ". $self->dest. ")"
+ unless qsearchs( 'svc_acct', { 'svcnum' => $self->dest } );
+ } elsif ( $conf->exists('emailinvoice-apostrophe')
+ ? $self->dest =~ /^\s*([\w\.\-\&\+\']+)\@(([\w\.\-]+\.)+\w+)\s*$/
+ : $self->dest =~ /^\s*([\w\.\-\&\+]+)\@(([\w\.\-]+\.)+\w+)\s*$/ ){
+ my($user, $domain) = ($1, $2);
+ $self->dest("$1\@$2");
+ } else {
+ return gettext("illegal_email_invoice_address"). ': '. $self->dest;
+ }
+
+ ''; #no error
+}
+
+=item address
+
+Returns the literal email address for this record (or `POST' or `FAX').
+
+=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;
+ }
+}
+
+=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 } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main_note.pm b/FS/FS/cust_main_note.pm
new file mode 100644
index 0000000..0a203a8
--- /dev/null
+++ b/FS/FS/cust_main_note.pm
@@ -0,0 +1,159 @@
+package FS::cust_main_note;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::Record );
+use Carp;
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cust_main_note - Object methods for cust_main_note records
+
+=head1 SYNOPSIS
+
+ use FS::cust_main_note;
+
+ $record = new FS::cust_main_note \%hash;
+ $record = new FS::cust_main_note { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_main_note object represents a note attachted to a customer.
+FS::cust_main_note inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item notenum
+
+primary key
+
+=item custnum
+
+=item _date
+
+=item usernum
+
+=item comments
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer note. To add the note 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_main_note'; }
+
+=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('notenum')
+ || $self->ut_number('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('otaker')
+ || $self->ut_anything('comments')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+#false laziness w/otaker_Mixin & cust_attachment
+sub otaker {
+ my $self = shift;
+ if ( scalar(@_) ) { #set
+ my $otaker = shift;
+ my($l,$f) = (split(', ', $otaker));
+ my $access_user = qsearchs('access_user', { 'username'=>$otaker } )
+ || qsearchs('access_user', { 'first'=>$f, 'last'=>$l } )
+ or croak "can't set otaker: $otaker not found!"; #confess?
+ $self->usernum( $access_user->usernum );
+ $otaker; #not sure return is used anywhere, but just in case
+ } else { #get
+ if ( $self->usernum ) {
+ $self->access_user->username;
+ } elsif ( length($self->get('otaker')) ) {
+ $self->get('otaker');
+ } else {
+ '';
+ }
+ }
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+Lurking in the cracks.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
new file mode 100644
index 0000000..ff8226c
--- /dev/null
+++ b/FS/FS/cust_pay.pm
@@ -0,0 +1,915 @@
+package FS::cust_pay;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::payinfo_transaction_Mixin FS::cust_main_Mixin
+ FS::Record );
+use vars qw( $DEBUG $me $conf @encrypted_fields
+ $unsuspendauto $ignore_noapply
+ );
+use Date::Format;
+use Business::CreditCard;
+use Text::Template;
+use FS::UID qw( getotaker );
+use FS::Misc qw( send_email );
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::payby;
+use FS::cust_main_Mixin;
+use FS::payinfo_transaction_Mixin;
+use FS::cust_bill;
+use FS::cust_bill_pay;
+use FS::cust_pay_refund;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_pay_void;
+
+$DEBUG = 0;
+
+$me = '[FS::cust_pay]';
+
+$ignore_noapply = 0;
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $unsuspendauto = $conf->exists('unsuspendauto');
+} );
+
+@encrypted_fields = ('payinfo');
+
+=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 _date
+
+specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item paid
+
+Amount of this payment
+
+=item usernum
+
+order taker (see L<FS::access_user>)
+
+=item payby
+
+Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
+
+=item payinfo
+
+Payment Information (See L<FS::payinfo_Mixin> for data format)
+
+=item paymask
+
+Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
+
+=item paybatch
+
+text field for tracking card processing or other batch grouping
+
+=item payunique
+
+Optional unique identifer to prevent duplicate transactions.
+
+=item closed
+
+books closed flag, empty or `Y'
+
+=item pkgnum
+
+Desired pkgnum when using experimental package balances.
+
+=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'; }
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum.
+ ' (cust_pay.paynum '. $self->paynum. ')';
+}
+
+=item insert [ OPTION => VALUE ... ]
+
+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.
+
+A hash of optional arguments may be passed. Currently "manual" is supported.
+If true, a payment receipt is sent instead of a statement when
+'payment_receipt_email' configuration option is set.
+
+=cut
+
+sub insert {
+ my($self, %options) = @_;
+
+ 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_bill;
+ if ( $self->invnum ) {
+ $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 $error = $self->check;
+ return $error if $error;
+
+ my $cust_main = $self->cust_main;
+ my $old_balance = $cust_main->balance;
+
+ $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(%options);
+ if ( $error ) {
+ if ( $ignore_noapply ) {
+ warn "warning: error inserting $cust_bill_pay: $error ".
+ "(ignore_noapply flag set; inserting cust_pay record anyway)\n";
+ } else {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting $cust_bill_pay: $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ #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;
+
+ #payment receipt
+ my $trigger = $conf->config('payment_receipt-trigger') || 'cust_pay';
+ if ( $trigger eq 'cust_pay' ) {
+ my $error = $self->send_receipt(
+ 'manual' => $options{'manual'},
+ 'cust_bill' => $cust_bill,
+ 'cust_main' => $cust_main,
+ );
+ warn "can't send payment receipt/statement: $error" if $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
+
+Unless the closed flag is set, deletes this payment and all associated
+applications (see L<FS::cust_bill_pay> and L<FS::cust_pay_refund>). In most
+cases, you want to use the void method instead to leave a record of the
+deleted payment.
+
+=cut
+
+# very similar to FS::cust_credit::delete
+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->exists('deletepayments')
+ && $conf->config('deletepayments') ne '' ) {
+
+ my $cust_main = $self->cust_main;
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
+ #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->paymask. "\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 can, but probably shouldn't modify payments...
+
+=cut
+
+sub replace {
+ #return "Can't modify payment!"
+ my $self = shift;
+ return "Can't modify closed payment" if $self->closed =~ /^Y/i;
+ $self->SUPER::replace(@_);
+}
+
+=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;
+
+ $self->otaker(getotaker) unless ($self->otaker);
+
+ my $error =
+ $self->ut_numbern('paynum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('paid')
+ || $self->ut_alphan('otaker')
+ || $self->ut_textn('paybatch')
+ || $self->ut_textn('payunique')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->payinfo_check()
+ ;
+ 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;
+
+#i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it
+# # UNIQUE index should catch this too, without race conditions, but this
+# # should give a better error message the other 99.9% of the time...
+# if ( length($self->payunique)
+# && qsearchs('cust_pay', { 'payunique' => $self->payunique } ) ) {
+# #well, it *could* be a better error message
+# return "duplicate transaction".
+# " - a payment with unique identifer ". $self->payunique.
+# " already exists";
+# }
+
+ $self->SUPER::check;
+}
+
+=item send_receipt HASHREF | OPTION => VALUE ...
+
+Sends a payment receipt for this payment..
+
+Available options:
+
+=over 4
+
+=item manual
+
+Flag indicating the payment is being made manually.
+
+=item cust_bill
+
+Invoice (FS::cust_bill) object. If not specified, the most recent invoice
+will be assumed.
+
+=item cust_main
+
+Customer (FS::cust_main) object (for efficiency).
+
+=back
+
+=cut
+
+sub send_receipt {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+
+ my $cust_bill = $opt->{'cust_bill'};
+ my $cust_main = $opt->{'cust_main'} || $self->cust_main;
+
+ my $conf = new FS::Conf;
+
+ return ''
+ unless $conf->exists('payment_receipt_email')
+ && grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list;
+
+ $cust_bill ||= ($cust_main->cust_bill)[-1]; #rather inefficient though?
+
+ if ( ( exists($opt->{'manual'}) && $opt->{'manual'} )
+ || ! $conf->exists('invoice_html_statement')
+ || ! $cust_bill
+ ) {
+
+ 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 { $_ !~ /^(POST|FAX)$/ }
+ $cust_main->invoicing_list;
+
+ my $payby = $self->payby;
+ my $payinfo = $self->payinfo;
+ $payby =~ s/^BILL$/Check/ if $payinfo;
+ if ( $payby eq 'CARD' || $payby eq 'CHEK' ) {
+ $payinfo = $self->paymask
+ } else {
+ $payinfo = $self->decrypt($payinfo);
+ }
+ $payby =~ s/^CHEK$/Electronic check/;
+
+ my %fill_in = (
+ '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,
+ 'company_name' => $conf->config('company_name', $cust_main->agentnum),
+ );
+
+ if ( $opt->{'cust_pkg'} ) {
+ $fill_in{'pkg'} = $opt->{'cust_pkg'}->part_pkg->pkg;
+ #setup date, other things?
+ }
+
+ send_email(
+ 'from' => $conf->config('invoice_from', $cust_main->agentnum),
+ #invoice_from??? well as good as any
+ 'to' => \@invoicing_list,
+ 'subject' => 'Payment receipt',
+ 'body' => [ $receipt_template->fill_in( HASH => \%fill_in ) ],
+ );
+
+ } else {
+
+ my $queue = new FS::queue {
+ 'paynum' => $self->paynum,
+ 'job' => 'FS::cust_bill::queueable_email',
+ };
+
+ $queue->insert(
+ 'invnum' => $cust_bill->invnum,
+ 'template' => 'statement',
+ );
+
+ }
+
+}
+
+=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;
+ map { $_ } #return $self->num_cust_bill_pay unless wantarray;
+ 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;
+ map { $_ } #return $self->num_cust_pay_refund unless wantarray;
+ 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 amount
+
+Returns the "paid" field.
+
+=cut
+
+sub amount {
+ my $self = shift;
+ $self->paid();
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item batch_insert CUST_PAY_OBJECT, ...
+
+Class method which inserts multiple payments. Takes a list of FS::cust_pay
+objects. Returns a list, each element representing the status of inserting the
+corresponding payment - empty. If there is an error inserting any payment, the
+entire transaction is rolled back, i.e. all payments are inserted or none are.
+
+For example:
+
+ my @errors = FS::cust_pay->batch_insert(@cust_pay);
+ my $num_errors = scalar(grep $_, @errors);
+ if ( $num_errors == 0 ) {
+ #success; all payments were inserted
+ } else {
+ #failure; no payments were inserted.
+ }
+
+=cut
+
+sub batch_insert {
+ my $self = shift; #class method
+
+ 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 $errors = 0;
+
+ my @errors = map {
+ my $error = $_->insert( 'manual' => 1 );
+ if ( $error ) {
+ $errors++;
+ } else {
+ $_->cust_main->apply_payments;
+ }
+ $error;
+ } @_;
+
+ if ( $errors ) {
+ $dbh->rollback if $oldAutoCommit;
+ } else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+
+ @errors;
+
+}
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut
+
+sub unapplied_sql {
+ my ($class, $start, $end) = shift;
+ my $bill_start = $start ? "AND cust_bill_pay._date <= $start" : '';
+ my $bill_end = $end ? "AND cust_bill_pay._date > $end" : '';
+ my $refund_start = $start ? "AND cust_pay_refund._date <= $start" : '';
+ my $refund_end = $end ? "AND cust_pay_refund._date > $end" : '';
+
+ "paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum
+ $bill_start $bill_end )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum
+ $refund_start $refund_end )
+ ,0
+ )
+ ";
+
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+use FS::h_cust_pay;
+
+sub _upgrade_data { #class method
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ #not the most efficient, but hey, it only has to run once
+
+ my $where = "WHERE ( otaker IS NULL OR otaker = '' OR otaker = 'ivan' ) ".
+ " AND usernum IS NULL ".
+ " AND 0 < ( SELECT COUNT(*) FROM cust_main ".
+ " WHERE cust_main.custnum = cust_pay.custnum ) ";
+
+ my $count_sql = "SELECT COUNT(*) FROM cust_pay $where";
+
+ my $sth = dbh->prepare($count_sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+ #warn "$total cust_pay records to update\n"
+ # if $DEBUG;
+ local($DEBUG) = 2 if $total > 1000; #could be a while, force progress info
+
+ my $count = 0;
+ my $lastprog = 0;
+
+ my @cust_pay = qsearch( {
+ 'table' => 'cust_pay',
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY paynum',
+ } );
+
+ foreach my $cust_pay (@cust_pay) {
+
+ my $h_cust_pay = $cust_pay->h_search('insert');
+ if ( $h_cust_pay ) {
+ next if $cust_pay->otaker eq $h_cust_pay->history_user;
+ $cust_pay->otaker($h_cust_pay->history_user);
+ } else {
+ $cust_pay->otaker('legacy');
+ }
+
+ delete $FS::payby::hash{'COMP'}->{cust_pay}; #quelle kludge
+ my $error = $cust_pay->replace;
+
+ if ( $error ) {
+ warn " *** WARNING: Error updating order taker for payment paynum ".
+ $cust_pay->paynun. ": $error\n";
+ next;
+ }
+
+ $FS::payby::hash{'COMP'}->{cust_pay} = ''; #restore it
+
+ $count++;
+ if ( $DEBUG > 1 && $lastprog + 30 < time ) {
+ warn "$me $count/$total (". sprintf('%.2f',100*$count/$total). '%)'. "\n";
+ $lastprog = time;
+ }
+
+ }
+
+ $class->_upgrade_otaker(%opts);
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item batch_import HASHREF
+
+Inserts new payments.
+
+=cut
+
+sub batch_import {
+ my $param = shift;
+
+ my $fh = $param->{filehandle};
+ my $agentnum = $param->{agentnum};
+ my $format = $param->{'format'};
+ my $paybatch = $param->{'paybatch'};
+
+ # here is the agent virtualization
+ my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my @fields;
+ my $payby;
+ if ( $format eq 'simple' ) {
+ @fields = qw( custnum agent_custid paid payinfo );
+ $payby = 'BILL';
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $payby = 'BILL';
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+
+ my $imported = 0;
+
+ 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 $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();
+
+ my %cust_pay = (
+ payby => $payby,
+ paybatch => $paybatch,
+ );
+
+ my $cust_main;
+ foreach my $field ( @fields ) {
+
+ if ( $field eq 'agent_custid'
+ && $agentnum
+ && $columns[0] =~ /\S+/ )
+ {
+
+ my $agent_custid = $columns[0];
+ my %hash = ( 'agent_custid' => $agent_custid,
+ 'agentnum' => $agentnum,
+ );
+
+ if ( $cust_pay{'custnum'} !~ /^\s*$/ ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't specify custnum with agent_custid $agent_custid";
+ }
+
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ });
+
+ unless ( $cust_main ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find customer with agent_custid $agent_custid";
+ }
+
+ $field = 'custnum';
+ $columns[0] = $cust_main->custnum;
+ }
+
+ $cust_pay{$field} = shift @columns;
+ }
+
+ my $cust_pay = new FS::cust_pay( \%cust_pay );
+ my $error = $cust_pay->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert payment for $line: $error";
+ }
+
+ if ( $format eq 'simple' ) {
+ # include agentnum for less surprise?
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $cust_pay->custnum },
+ 'extra_sql' => $extra_sql,
+ })
+ unless $cust_main;
+
+ unless ( $cust_main ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find customer to which payments apply at line: $line";
+ }
+
+ $error = $cust_main->apply_payments_and_credits;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't apply payments to customer for $line: $error";
+ }
+
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless $imported;
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods.
+
+=head1 SEE ALSO
+
+L<FS::cust_pay_pending>, 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..9ef1e1c
--- /dev/null
+++ b/FS/FS/cust_pay_batch.pm
@@ -0,0 +1,277 @@
+package FS::cust_pay_batch;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp qw( confess );
+use Business::CreditCard 0.28;
+use FS::Record qw(dbh qsearch qsearchs);
+use FS::payinfo_Mixin;
+use FS::cust_main;
+use FS::cust_bill;
+
+@ISA = qw( FS::payinfo_Mixin FS::Record );
+
+# 1 is mostly method/subroutine entry and options
+# 2 traces progress of some operations
+# 3 is even more information including possibly sensitive data
+$DEBUG = 0;
+
+=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;
+
+ #deprecated# $error = $record->retriable;
+
+=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 batchnum - indentifies group in batch
+
+=item payby - CARD/CHEK/LECB/BILL/COMP
+
+=item payinfo
+
+=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
+
+=item status
+
+=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
+
+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 transaction. 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('paybatchnum')
+ || $self->ut_numbern('trancode') #deprecated
+ || $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);
+
+ $error = $self->payinfo_check();
+ return $error if $error;
+
+ if ( $self->exp eq '' ) {
+ return "Expiration date required"
+ unless $self->payby =~ /^(CHEK|DCHK|LECB|WEST)$/;
+ $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);
+ }
+
+ #we have lots of old zips in there... don't hork up batch results cause of em
+ $self->zip =~ /^\s*(\w[\w\-\s]{2,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 } );
+}
+
+#you know what, screw this in the new world of events. we should be able to
+#get the event defs to retry (remove once.pm condition, add every.pm) without
+#mucking about with statuses of previous cust_event records. right?
+#
+#=item retriable
+#
+#Marks the corresponding event (see L<FS::cust_bill_event>) for this batched
+#credit card payment as retriable. Useful if the corresponding financial
+#institution account was declined for temporary reasons and/or a manual
+#retry is desired.
+#
+#Implementation details: For the named customer's invoice, changes the
+#statustext of the 'done' (without statustext) event to 'retriable.'
+#
+#=cut
+
+sub retriable {
+
+ confess "deprecated method cust_pay_batch->retriable called; try removing ".
+ "the once condition and adding an every condition?";
+
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE'; #Hmm
+ 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_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
+ or return "event $self->eventnum references nonexistant invoice $self->invnum";
+
+ warn "cust_pay_batch->retriable working with self of " . $self->paybatchnum . " and invnum of " . $self->invnum;
+ my @cust_bill_event =
+ sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds }
+ grep {
+ $_->part_bill_event->eventcode =~ /\$cust_bill->batch_card/
+ && $_->status eq 'done'
+ && ! $_->statustext
+ }
+ $cust_bill->cust_bill_event;
+ # complain loudly if scalar(@cust_bill_event) > 1 ?
+ my $error = $cust_bill_event[0]->retriable;
+ if ($error ) {
+ # gah, even with transactions.
+ $dbh->commit if $oldAutoCommit; #well.
+ return "error marking invoice event retriable: $error";
+ }
+ '';
+}
+
+=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_pending.pm b/FS/FS/cust_pay_pending.pm
new file mode 100644
index 0000000..f48e1a8
--- /dev/null
+++ b/FS/FS/cust_pay_pending.pm
@@ -0,0 +1,340 @@
+package FS::cust_pay_pending;
+
+use strict;
+use vars qw( @ISA @encrypted_fields );
+use FS::Record qw( qsearch qsearchs dbh ); #dbh for _upgrade_data
+use FS::payinfo_transaction_Mixin;
+use FS::cust_main_Mixin;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_pay;
+
+@ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record );
+
+@encrypted_fields = ('payinfo');
+
+=head1 NAME
+
+FS::cust_pay_pending - Object methods for cust_pay_pending records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pay_pending;
+
+ $record = new FS::cust_pay_pending \%hash;
+ $record = new FS::cust_pay_pending { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay_pending object represents an pending payment. It reflects
+local state through the multiple stages of processing a real-time transaction
+with an external gateway. FS::cust_pay_pending inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item paypendingnum
+
+Primary key
+
+=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
+
+Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
+
+=item payinfo
+
+Payment Information (See L<FS::payinfo_Mixin> for data format)
+
+=item paymask
+
+Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
+
+=item paydate
+
+Expiration date
+
+=item payunique
+
+Unique identifer to prevent duplicate transactions.
+
+=item pkgnum
+
+Desired pkgnum when using experimental package balances.
+
+=item status
+
+Pending transaction status, one of the following:
+
+=over 4
+
+=item new
+
+Aquires basic lock on payunique
+
+=item pending
+
+Transaction is pending with the gateway
+
+=item authorized
+
+Only used for two-stage transactions that require a separate capture step
+
+=item captured
+
+Transaction completed with payment gateway (sucessfully), not yet recorded in
+the database
+
+=item declined
+
+Transaction completed with payment gateway (declined), not yet recorded in
+the database
+
+=item done
+
+Transaction recorded in database
+
+=back
+
+=item statustext
+
+Additional status information.
+
+=cut
+
+#=item cust_balance -
+
+=item paynum -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new pending payment. To add the pending payment 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_pay_pending'; }
+
+=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 pending payment. 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('paypendingnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_money('paid')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('payunique')
+ || $self->ut_text('status')
+ #|| $self->ut_textn('statustext')
+ || $self->ut_anything('statustext')
+ #|| $self->ut_money('cust_balance')
+ || $self->ut_hexn('session_id')
+ || $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
+ || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->payinfo_check() #payby/payinfo/paymask/paydate
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ # UNIQUE index should catch this too, without race conditions, but this
+ # should give a better error message the other 99.9% of the time...
+ if ( length($self->payunique) ) {
+ my $cust_pay_pending = qsearchs('cust_pay_pending', {
+ 'payunique' => $self->payunique,
+ 'paypendingnum' => { op=>'!=', value=>$self->paypendingnum },
+ });
+ if ( $cust_pay_pending ) {
+ #well, it *could* be a better error message
+ return "duplicate transaction - a payment with unique identifer ".
+ $self->payunique. " already exists";
+ }
+ }
+
+ $self->SUPER::check;
+}
+
+=item cust_main
+
+Returns the associated L<FS::cust_main> record if any. Otherwise returns false.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs('cust_main', { custnum => $self->custnum } );
+}
+
+
+#these two are kind-of false laziness w/cust_main::realtime_bop
+#(currently only used when resolving pending payments manually)
+
+=item insert_cust_pay
+
+Sets the status of this pending pament to "done" (with statustext
+"captured (manual)"), and inserts a payment record (see L<FS::cust_pay>).
+
+Currently only used when resolving pending payments manually.
+
+=cut
+
+sub insert_cust_pay {
+ my $self = shift;
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'custnum' => $self->custnum,
+ 'paid' => $self->paid,
+ '_date' => $self->_date, #better than passing '' for now
+ 'payby' => $self->payby,
+ 'payinfo' => $self->payinfo,
+ 'paybatch' => $self->paybatch,
+ 'paydate' => $self->paydate,
+ } );
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
+
+ my $error = $cust_pay->insert;#($options{'manual'} ? ( 'manual' => 1 ) : () );
+
+ if ( $error ) {
+ # gah.
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->status('done');
+ $self->statustext('captured (manual)');
+ $self->paynum($cust_pay->paynum);
+ my $cpp_done_err = $self->replace;
+
+ if ( $cpp_done_err ) {
+
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ return $cpp_done_err;
+
+ } else {
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return ''; #no error
+
+ }
+
+}
+
+=item decline
+
+Sets the status of this pending pament to "done" (with statustext
+"declined (manual)").
+
+Currently only used when resolving pending payments manually.
+
+=cut
+
+sub decline {
+ my $self = shift;
+
+ #could send decline email too? doesn't seem useful in manual resolution
+
+ $self->status('done');
+ $self->statustext("declined (manual)");
+ $self->replace;
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { #class method
+ my ($class, %opts) = @_;
+
+ my $sql =
+ "DELETE FROM cust_pay_pending WHERE status = 'new' AND _date < ".(time-600);
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay_refund.pm b/FS/FS/cust_pay_refund.pm
new file mode 100644
index 0000000..cb9dbce
--- /dev/null
+++ b/FS/FS/cust_pay_refund.pm
@@ -0,0 +1,188 @@
+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.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ return "Can't apply refund to closed payment"
+ if $self->cust_pay->closed =~ /^Y/i;
+ return "Can't apply payment to closed refund"
+ if $self->cust_refund->closed =~ /^Y/i;
+ $self->SUPER::insert(@_);
+}
+
+=item delete
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't remove refund from closed payment"
+ if $self->cust_pay->closed =~ /^Y/i;
+ return "Can't remove payment from 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_pay
+
+Returns the payment (see L<FS::cust_pay>)
+
+=cut
+
+sub cust_pay {
+ my $self = shift;
+ qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
+}
+
+=item cust_refund
+
+Returns the refund (see L<FS::cust_refund>)
+
+=cut
+
+sub cust_refund {
+ my $self = shift;
+ qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
+}
+
+=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..e18a4f6
--- /dev/null
+++ b/FS/FS/cust_pay_void.pm
@@ -0,0 +1,260 @@
+package FS::cust_pay_void;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::payinfo_Mixin FS::Record );
+use vars qw( @encrypted_fields $otaker_upgrade_kludge );
+use Business::CreditCard;
+use FS::UID qw(getotaker);
+use FS::Record qw(qsearchs dbh fields); # qsearch );
+use FS::cust_pay;
+#use FS::cust_bill;
+#use FS::cust_bill_pay;
+#use FS::cust_pay_refund;
+#use FS::cust_main;
+use FS::cust_pkg;
+
+@encrypted_fields = ('payinfo');
+$otaker_upgrade_kludge = 0;
+
+=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 otaker
+
+order taker (see L<FS::access_user>)
+
+=item payby
+
+`CARD' (credit cards), `CHEK' (electronic check/ACH),
+`LECB' (phone bill billing), `BILL' (billing), `CASH' (cash),
+`WEST' (Western Union), `MCRD' (Manual credit card), 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 pkgnum
+
+Desired pkgnum when using experimental package balances.
+
+=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 unvoid
+
+"Un-void"s this payment: Deletes the voided payment from the database and adds
+back a normal payment.
+
+=cut
+
+sub unvoid {
+ 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 = new FS::cust_pay ( {
+ map { $_ => $self->get($_) } fields('cust_pay')
+ } );
+ my $error = $cust_pay->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 voided payment. You probably don't want to use this directly; see
+the B<unvoid> method to add the original payment back.
+
+=item replace OLD_RECORD
+
+Currently unimplemented.
+
+=cut
+
+sub replace {
+ return "Can't modify voided payments!" unless $otaker_upgrade_kludge;
+ shift->SUPER::replace(@_);
+}
+
+=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_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $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|PREP|CASH|WEST|MCRD)$/
+ 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) unless $self->otaker;
+
+ $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 } );
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ local($otaker_upgrade_kludge) = 1;
+ $class->_upgrade_otaker(%opts);
+}
+
+=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..b851ac7
--- /dev/null
+++ b/FS/FS/cust_pkg.pm
@@ -0,0 +1,3212 @@
+package FS::cust_pkg;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::location_Mixin
+ FS::m2m_Common FS::option_Common FS::Record );
+use vars qw($disable_agentcheck $DEBUG $me);
+use Carp qw(cluck);
+use Scalar::Util qw( blessed );
+use List::Util qw(max);
+use Tie::IxHash;
+use Time::Local qw( timelocal_nocheck );
+use MIME::Entity;
+use FS::UID qw( getotaker dbh );
+use FS::Misc qw( send_email );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_svc;
+use FS::part_pkg;
+use FS::cust_main;
+use FS::cust_location;
+use FS::pkg_svc;
+use FS::cust_bill_pkg;
+use FS::cust_pkg_detail;
+use FS::cust_event;
+use FS::h_cust_svc;
+use FS::reg_code;
+use FS::part_svc;
+use FS::cust_pkg_reason;
+use FS::reason;
+use FS::cust_pkg_discount;
+use FS::discount;
+use FS::UI::Web;
+
+# need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend,
+# setup }
+# because they load configuration 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;
+
+$DEBUG = 0;
+$me = '[FS::cust_pkg]';
+
+$disable_agentcheck = 0;
+
+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 locationnum
+
+Optional link to package location (see L<FS::location>)
+
+=item start_date
+
+date
+
+=item setup
+
+date
+
+=item bill
+
+date (next bill date)
+
+=item last_bill
+
+last bill date
+
+=item adjourn
+
+date
+
+=item susp
+
+date
+
+=item expire
+
+date
+
+=item cancel
+
+date
+
+=item usernum
+
+order taker (see L<FS::access_user>)
+
+=item manual_flag
+
+If this field is set to 1, disables the automatic
+unsuspension of this package when using the B<unsuspendauto> config option.
+
+=item quantity
+
+If not set, defaults to 1
+
+=item change_date
+
+Date of change from previous package
+
+=item change_pkgnum
+
+Previous pkgnum
+
+=item change_pkgpart
+
+Previous pkgpart
+
+=item change_locationnum
+
+Previous locationnum
+
+=back
+
+Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date
+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'; }
+sub cust_linked { $_[0]->cust_main_custnum; }
+sub cust_unlinked_msg {
+ my $self = shift;
+ "WARNING: can't find cust_main.custnum ". $self->custnum.
+ ' (cust_pkg.pkgnum '. $self->pkgnum. ')';
+}
+
+=item insert [ OPTION => VALUE ... ]
+
+Adds this billing item to the database ("Orders" the item). If there is an
+error, returns the error, otherwise returns false.
+
+If the additional field I<promo_code> is defined instead of I<pkgpart>, it
+will be used to look up the package definition and agent restrictions will be
+ignored.
+
+If the additional field I<refnum> is defined, an FS::pkg_referral record will
+be created and inserted. Multiple FS::pkg_referral records can be created by
+setting I<refnum> to an array reference of refnums or a hash reference with
+refnums as keys. If no I<refnum> is defined, a default FS::pkg_referral
+record will be created corresponding to cust_main.refnum.
+
+The following options are available:
+
+=over 4
+
+=item change
+
+If set true, supresses any referral credit to a referring customer.
+
+=item options
+
+cust_pkg_option records will be created
+
+=item ticket_subject
+
+a ticket will be added to this customer with this subject
+
+=item ticket_queue
+
+an optional queue name for ticket additions
+
+=back
+
+=cut
+
+sub insert {
+ my( $self, %options ) = @_;
+
+ if ( $self->part_pkg->option('start_1st', 1) && !$self->start_date ) {
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime(time) )[0,1,2,3,4,5];
+ $mon += 1 unless $mday == 1;
+ until ( $mon < 12 ) { $mon -= 12; $year++; }
+ $self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) );
+ }
+
+ my $expire_months = $self->part_pkg->option('expire_months', 1);
+ if ( $expire_months && !$self->expire ) {
+ my $start = $self->start_date || $self->setup || time;
+
+ #false laziness w/part_pkg::add_freq
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($start) )[0,1,2,3,4,5];
+ $mon += $expire_months;
+ until ( $mon < 12 ) { $mon -= 12; $year++; }
+
+ #$self->expire( timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year) );
+ $self->expire( timelocal_nocheck(0,0,0,$mday,$mon,$year) );
+ }
+
+ 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($options{options} ? %{$options{options}} : ());
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->refnum($self->cust_main->refnum) unless $self->refnum;
+ $self->refnum( [ $self->refnum ] ) unless ref($self->refnum);
+ $self->process_m2m( 'link_table' => 'pkg_referral',
+ 'target_table' => 'part_referral',
+ 'params' => $self->refnum,
+ );
+
+ if ( $self->discountnum ) {
+ my $error = $self->insert_discount();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ #if ( $self->reg_code ) {
+ # my $reg_code = qsearchs('reg_code', { 'code' => $self->reg_code } );
+ # $error = $reg_code->delete;
+ # if ( $error ) {
+ # $dbh->rollback if $oldAutoCommit;
+ # return $error;
+ # }
+ #}
+
+ my $conf = new FS::Conf;
+
+ if ( $conf->config('ticket_system') && $options{ticket_subject} ) {
+
+ #eval '
+ # use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
+ # use RT;
+ #';
+ #die $@ if $@;
+ #
+ #RT::LoadConfig();
+ #RT::Init();
+ use FS::TicketSystem;
+ FS::TicketSystem->init();
+
+ my $q = new RT::Queue($RT::SystemUser);
+ $q->Load($options{ticket_queue}) if $options{ticket_queue};
+ my $t = new RT::Ticket($RT::SystemUser);
+ my $mime = new MIME::Entity;
+ $mime->build( Type => 'text/plain', Data => $options{ticket_subject} );
+ $t->Create( $options{ticket_queue} ? (Queue => $q) : (),
+ Subject => $options{ticket_subject},
+ MIMEObj => $mime,
+ );
+ $t->AddLink( Type => 'MemberOf',
+ Target => 'freeside://freeside/cust_main/'. $self->custnum,
+ );
+ }
+
+ if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) {
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_main::queueable_print',
+ };
+ $error = $queue->insert(
+ 'custnum' => $self->custnum,
+ 'template' => 'welcome_letter',
+ );
+
+ if ($error) {
+ warn "can't send welcome letter: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=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 ] [ HASHREF | OPTION => VALUE ... ]
+
+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, adjourn, 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).
+
+Available options are:
+
+=over 4
+
+=item reason
+
+can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item reason_otaker
+
+the access_user (see L<FS::access_user>) providing the reason
+
+=item options
+
+hashref of keys and values - cust_pkg_option records will be created, updated or removed as appopriate
+
+=back
+
+=cut
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? 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?
+
+ local($disable_agentcheck) = 1 if $old->pkgpart == $new->pkgpart;
+
+ 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 $method ( qw(adjourn expire) ) { # How many reasons?
+ if ($options->{'reason'} && $new->$method && $old->$method ne $new->$method) {
+ my $error = $new->insert_reason(
+ 'reason' => $options->{'reason'},
+ 'date' => $new->$method,
+ 'action' => $method,
+ 'reason_otaker' => $options->{'reason_otaker'},
+ );
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Error inserting cust_pkg_reason: $error";
+ }
+ }
+ }
+
+ #save off and freeze RADIUS attributes for any associated svc_acct records
+ my @svc_acct = ();
+ if ( $old->part_pkg->is_prepaid || $new->part_pkg->is_prepaid ) {
+
+ #also check for specific exports?
+ # to avoid spurious modify export events
+ @svc_acct = map { $_->svc_x }
+ grep { $_->part_svc->svcdb eq 'svc_acct' }
+ $old->cust_svc;
+
+ $_->snapshot foreach @svc_acct;
+
+ }
+
+ my $error = $new->SUPER::replace($old,
+ $options->{options} ? $options->{options} : ()
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ #for prepaid packages,
+ #trigger export of new RADIUS Expiration attribute when cust_pkg.bill changes
+ foreach my $old_svc_acct ( @svc_acct ) {
+ my $new_svc_acct = new FS::svc_acct { $old_svc_acct->hash };
+ my $s_error = $new_svc_acct->replace($old_svc_acct);
+ if ( $s_error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $s_error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=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;
+
+ $self->locationnum('') if !$self->locationnum || $self->locationnum == -1;
+
+ my $error =
+ $self->ut_numbern('pkgnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_numbern('pkgpart')
+ || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
+ || $self->ut_numbern('start_date')
+ || $self->ut_numbern('setup')
+ || $self->ut_numbern('bill')
+ || $self->ut_numbern('susp')
+ || $self->ut_numbern('cancel')
+ || $self->ut_numbern('adjourn')
+ || $self->ut_numbern('expire')
+ || $self->ut_enum('no_auto', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ if ( $self->reg_code ) {
+
+ unless ( grep { $self->pkgpart == $_->pkgpart }
+ map { $_->reg_code_pkg }
+ qsearchs( 'reg_code', { 'code' => $self->reg_code,
+ 'agentnum' => $self->cust_main->agentnum })
+ ) {
+ return "Unknown registration code";
+ }
+
+ } elsif ( $self->promo_code ) {
+
+ my $promo_part_pkg =
+ qsearchs('part_pkg', {
+ 'pkgpart' => $self->pkgpart,
+ 'promo_code' => { op=>'ILIKE', value=>$self->promo_code },
+ } );
+ return 'Unknown promotional code' unless $promo_part_pkg;
+
+ } else {
+
+ unless ( $disable_agentcheck ) {
+ my $agent =
+ qsearchs( 'agent', { 'agentnum' => $self->cust_main->agentnum } );
+ return "agent ". $agent->agentnum. ':'. $agent->agent.
+ " can't purchase pkgpart ". $self->pkgpart
+ unless $agent->pkgpart_hashref->{ $self->pkgpart }
+ || $agent->agentnum == $self->part_pkg->agentnum;
+ }
+
+ $error = $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart' );
+ return $error if $error;
+
+ }
+
+ $self->otaker(getotaker) unless $self->otaker;
+
+ 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:
+
+=over 4
+
+=item quiet - can be set true to supress email cancellation notices.
+
+=item time - can be set to cancel the package based on a specific future or historical date. Using time ensures that the remaining amount is calculated correctly. Note however that this is an immediate cancel and just changes the date. You are PROBABLY looking to expire the account instead of using this.
+
+=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item date - can be set to a unix style timestamp to specify when to cancel (expire)
+
+=item nobill - can be set true to skip billing if it might otherwise be done.
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub cancel {
+ my( $self, %options ) = @_;
+ my $error;
+
+ my $conf = new FS::Conf;
+
+ warn "cust_pkg::cancel 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 $old = $self->select_for_update;
+
+ if ( $old->get('cancel') || $self->get('cancel') ) {
+ dbh->rollback if $oldAutoCommit;
+ return ""; # no error
+ }
+
+ my $date = $options{date} if $options{date}; # expire/cancel later
+ $date = '' if ($date && $date <= time); # complain instead?
+
+ #race condition: usage could be ongoing until unprovisioned
+ #resolved by performing a change package instead (which unprovisions) and
+ #later cancelling
+ if ( !$options{nobill} && !$date && $conf->exists('bill_usage_on_cancel') ) {
+ my $copy = $self->new({$self->hash});
+ my $error =
+ $copy->cust_main->bill( pkg_list => [ $copy ], cancel => 1 );
+ warn "Error billing during cancel, custnum ".
+ #$self->cust_main->custnum. ": $error"
+ ": $error"
+ if $error;
+ }
+
+
+ my $cancel_time = $options{'time'} || time;
+
+ if ( $options{'reason'} ) {
+ $error = $self->insert_reason( 'reason' => $options{'reason'},
+ 'action' => $date ? 'expire' : 'cancel',
+ 'date' => $date ? $date : $cancel_time,
+ 'reason_otaker' => $options{'reason_otaker'},
+ );
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Error inserting cust_pkg_reason: $error";
+ }
+ }
+
+ my %svc;
+ unless ( $date ) {
+ foreach my $cust_svc (
+ #schwartz
+ map { $_->[0] }
+ sort { $a->[1] <=> $b->[1] }
+ map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; }
+ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
+ ) {
+
+ my $error = $cust_svc->cancel;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error cancelling cust_svc: $error";
+ }
+ }
+
+ # Add a credit for remaining service
+ my $remaining_value = $self->calc_remain(time=>$cancel_time);
+ if ( $remaining_value > 0 && !$options{'no_credit'} ) {
+ my $error = $self->cust_main->credit(
+ $remaining_value,
+ 'Credit for unused time on '. $self->part_pkg->pkg,
+ 'reason_type' => $conf->config('cancel_credit_type'),
+ );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error crediting customer \$$remaining_value for unused time on".
+ $self->part_pkg->pkg. ": $error";
+ }
+ }
+ }
+
+ my %hash = $self->hash;
+ $date ? ($hash{'expire'} = $date) : ($hash{'cancel'} = $cancel_time);
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace( $self, options => { $self->options } );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '' if $date; #no errors
+
+ my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list;
+ if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) {
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
+ 'to' => \@invoicing_list,
+ 'subject' => ( $conf->config('cancelsubject') || 'Cancellation Notice' ),
+ 'body' => [ map "$_\n", $conf->config('cancelmessage') ],
+ );
+ #should this do something on errors?
+ }
+
+ ''; #no errors
+
+}
+
+=item cancel_if_expired [ NOW_TIMESTAMP ]
+
+Cancels this package if its expire date has been reached.
+
+=cut
+
+sub cancel_if_expired {
+ my $self = shift;
+ my $time = shift || time;
+ return '' unless $self->expire && $self->expire <= $time;
+ my $error = $self->cancel;
+ if ( $error ) {
+ return "Error cancelling expired pkg ". $self->pkgnum. " for custnum ".
+ $self->custnum. ": $error";
+ }
+ '';
+}
+
+=item unexpire
+
+Cancels any pending expiration (sets the expire field to null).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub unexpire {
+ 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 $old = $self->select_for_update;
+
+ my $pkgnum = $old->pkgnum;
+ if ( $old->get('cancel') || $self->get('cancel') ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Can't unexpire cancelled package $pkgnum";
+ # or at least it's pointless
+ }
+
+ unless ( $old->get('expire') && $self->get('expire') ) {
+ dbh->rollback if $oldAutoCommit;
+ return ""; # no error
+ }
+
+ my %hash = $self->hash;
+ $hash{'expire'} = '';
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace( $self, options => { $self->options } );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+
+}
+
+=item suspend [ OPTION => VALUE ... ]
+
+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).
+
+Available options are:
+
+=over 4
+
+=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item date - can be set to a unix style timestamp to specify when to suspend (adjourn)
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub suspend {
+ 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 $old = $self->select_for_update;
+
+ my $pkgnum = $old->pkgnum;
+ if ( $old->get('cancel') || $self->get('cancel') ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Can't suspend cancelled package $pkgnum";
+ }
+
+ if ( $old->get('susp') || $self->get('susp') ) {
+ dbh->rollback if $oldAutoCommit;
+ return ""; # no error # complain on adjourn?
+ }
+
+ my $date = $options{date} if $options{date}; # adjourn/suspend later
+ $date = '' if ($date && $date <= time); # complain instead?
+
+ if ( $date && $old->get('expire') && $old->get('expire') < $date ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Package $pkgnum expires before it would be suspended.";
+ }
+
+ my $suspend_time = $options{'time'} || time;
+
+ if ( $options{'reason'} ) {
+ $error = $self->insert_reason( 'reason' => $options{'reason'},
+ 'action' => $date ? 'adjourn' : 'suspend',
+ 'date' => $date ? $date : $suspend_time,
+ 'reason_otaker' => $options{'reason_otaker'},
+ );
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Error inserting cust_pkg_reason: $error";
+ }
+ }
+
+ unless ( $date ) {
+
+ my @labels = ();
+
+ 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;
+ }
+ my( $label, $value ) = $cust_svc->label;
+ push @labels, "$label: $value";
+ }
+ }
+
+ my $conf = new FS::Conf;
+ if ( $conf->config('suspend_email_admin') ) {
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
+ #invoice_from ??? well as good as any
+ 'to' => $conf->config('suspend_email_admin'),
+ 'subject' => 'FREESIDE NOTIFICATION: Customer package suspended',
+ 'body' => [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you that the following customer package has been suspended:\n",
+ "\n",
+ 'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n",
+ 'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n",
+ ( map { "Service : $_\n" } @labels ),
+ ],
+ );
+
+ if ( $error ) {
+ warn "WARNING: can't send suspension admin email (suspending anyway): ".
+ "$error\n";
+ }
+
+ }
+
+ }
+
+ my %hash = $self->hash;
+ if ( $date ) {
+ $hash{'adjourn'} = $date;
+ } else {
+ $hash{'susp'} = $suspend_time;
+ }
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace( $self, options => { $self->options } );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+}
+
+=item unsuspend [ OPTION => VALUE ... ]
+
+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 and the
+adjourn field if it is in the past).
+
+Available options are:
+
+=over 4
+
+=item adjust_next_bill
+
+Can be set true to adjust the next bill date forward by
+the amount of time the account was inactive. This was set true by default
+since 1.4.2 and 1.5.0pre6; however, starting with 1.7.0 this needs to be
+explicitly requested. Price plans for which this makes sense (anniversary-date
+based than prorate or subscription) could have an option to enable this
+behaviour?
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub unsuspend {
+ my( $self, %opt ) = @_;
+ 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 $old = $self->select_for_update;
+
+ my $pkgnum = $old->pkgnum;
+ if ( $old->get('cancel') || $self->get('cancel') ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Can't unsuspend cancelled package $pkgnum";
+ }
+
+ unless ( $old->get('susp') && $self->get('susp') ) {
+ dbh->rollback if $oldAutoCommit;
+ return ""; # no error # complain instead?
+ }
+
+ 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;
+ }
+ }
+
+ }
+
+ my %hash = $self->hash;
+ my $inactive = time - $hash{'susp'};
+
+ my $conf = new FS::Conf;
+
+ $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
+ if ( $opt{'adjust_next_bill'}
+ || $conf->exists('unsuspend-always_adjust_next_bill_date') )
+ && $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} );
+
+ $hash{'susp'} = '';
+ $hash{'adjourn'} = '' if $hash{'adjourn'} < time;
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace( $self, options => { $self->options } );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+}
+
+=item unadjourn
+
+Cancels any pending suspension (sets the adjourn field to null).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub unadjourn {
+ 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 $old = $self->select_for_update;
+
+ my $pkgnum = $old->pkgnum;
+ if ( $old->get('cancel') || $self->get('cancel') ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Can't unadjourn cancelled package $pkgnum";
+ # or at least it's pointless
+ }
+
+ if ( $old->get('susp') || $self->get('susp') ) {
+ dbh->rollback if $oldAutoCommit;
+ return "Can't unadjourn suspended package $pkgnum";
+ # perhaps this is arbitrary
+ }
+
+ unless ( $old->get('adjourn') && $self->get('adjourn') ) {
+ dbh->rollback if $oldAutoCommit;
+ return ""; # no error
+ }
+
+ my %hash = $self->hash;
+ $hash{'adjourn'} = '';
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace( $self, options => { $self->options } );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+
+}
+
+
+=item change HASHREF | OPTION => VALUE ...
+
+Changes this package: cancels it and creates a new one, with a different
+pkgpart or locationnum or both. All services are transferred to the new
+package (no change will be made if this is not possible).
+
+Options may be passed as a list of key/value pairs or as a hash reference.
+Options are:
+
+=over 4
+
+=item locaitonnum
+
+New locationnum, to change the location for this package.
+
+=item cust_location
+
+New FS::cust_location object, to create a new location and assign it
+to this package.
+
+=item pkgpart
+
+New pkgpart (see L<FS::part_pkg>).
+
+=item refnum
+
+New refnum (see L<FS::part_referral>).
+
+=back
+
+At least one option must be specified (otherwise, what's the point?)
+
+Returns either the new FS::cust_pkg object or a scalar error.
+
+For example:
+
+ my $err_or_new_cust_pkg = $old_cust_pkg->change
+
+=cut
+
+#some false laziness w/order
+sub change {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+
+# my ($custnum, $pkgparts, $remove_pkgnum, $return_cust_pkg, $refnum) = @_;
+#
+
+ 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 %hash = ();
+
+ my $time = time;
+
+ #$hash{$_} = $self->$_() foreach qw( last_bill bill );
+
+ #$hash{$_} = $self->$_() foreach qw( setup );
+
+ $hash{'setup'} = $time if $self->setup;
+
+ $hash{'change_date'} = $time;
+ $hash{"change_$_"} = $self->$_()
+ foreach qw( pkgnum pkgpart locationnum );
+
+ if ( $opt->{'cust_location'} &&
+ ( ! $opt->{'locationnum'} || $opt->{'locationnum'} == -1 ) ) {
+ $error = $opt->{'cust_location'}->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_location (transaction rolled back): $error";
+ }
+ $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum;
+ }
+
+ # Create the new package.
+ my $cust_pkg = new FS::cust_pkg {
+ custnum => $self->custnum,
+ pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ),
+ refnum => ( $opt->{'refnum'} || $self->refnum ),
+ locationnum => ( $opt->{'locationnum'} || $self->locationnum ),
+ %hash,
+ };
+
+ $error = $cust_pkg->insert( 'change' => 1 );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ # Transfer services and cancel old package.
+
+ $error = $self->transfer($cust_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;
+ $error = $self->transfer($cust_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 still had services left on the old
+ # package. We can't change the package under this circumstances, so abort.
+ $dbh->rollback if $oldAutoCommit;
+ return "Unable to transfer all services from package ". $self->pkgnum;
+ }
+
+ #reset usage if changing pkgpart
+ # AND usage rollover is off (otherwise adds twice, now and at package bill)
+ if ($self->pkgpart != $cust_pkg->pkgpart) {
+ my $part_pkg = $cust_pkg->part_pkg;
+ $error = $part_pkg->reset_usage($cust_pkg, $part_pkg->is_prepaid
+ ? ()
+ : ( 'null' => 1 )
+ )
+ if $part_pkg->can('reset_usage') && ! $part_pkg->option('usage_rollover');
+
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error setting usage values: $error";
+ }
+ }
+
+ #Good to go, cancel old package.
+ $error = $self->cancel( quiet=>1 );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $conf->exists('cust_pkg-change_pkgpart-bill_now') ) {
+ #$self->cust_main
+ my $error = $cust_pkg->cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ $cust_pkg;
+
+}
+
+=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;
+ 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 last_cust_pkg_reason ACTION
+
+Returns the most recent ACTION FS::cust_pkg_reason associated with the package.
+Returns false if there is no reason or the package is not currenly ACTION'd
+ACTION is one of adjourn, susp, cancel, or expire.
+
+=cut
+
+sub last_cust_pkg_reason {
+ my ( $self, $action ) = ( shift, shift );
+ my $date = $self->get($action);
+ qsearchs( {
+ 'table' => 'cust_pkg_reason',
+ 'hashref' => { 'pkgnum' => $self->pkgnum,
+ 'action' => substr(uc($action), 0, 1),
+ 'date' => $date,
+ },
+ 'order_by' => 'ORDER BY num DESC LIMIT 1',
+ } );
+}
+
+=item last_reason ACTION
+
+Returns the most recent ACTION FS::reason associated with the package.
+Returns false if there is no reason or the package is not currenly ACTION'd
+ACTION is one of adjourn, susp, cancel, or expire.
+
+=cut
+
+sub last_reason {
+ my $cust_pkg_reason = shift->last_cust_pkg_reason(@_);
+ $cust_pkg_reason->reason
+ if $cust_pkg_reason;
+}
+
+=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;
+ return $self->{'_pkgpart'} if $self->{'_pkgpart'};
+ cluck "cust_pkg->part_pkg called" if $DEBUG > 1;
+ qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item old_cust_pkg
+
+Returns the cancelled package this package was changed from, if any.
+
+=cut
+
+sub old_cust_pkg {
+ my $self = shift;
+ return '' unless $self->change_pkgnum;
+ qsearchs('cust_pkg', { 'pkgnum' => $self->change_pkgnum } );
+}
+
+=item calc_setup
+
+Calls the I<calc_setup> of the FS::part_pkg object associated with this billing
+item.
+
+=cut
+
+sub calc_setup {
+ my $self = shift;
+ $self->part_pkg->calc_setup($self, @_);
+}
+
+=item calc_recur
+
+Calls the I<calc_recur> of the FS::part_pkg object associated with this billing
+item.
+
+=cut
+
+sub calc_recur {
+ my $self = shift;
+ $self->part_pkg->calc_recur($self, @_);
+}
+
+=item calc_remain
+
+Calls the I<calc_remain> of the FS::part_pkg object associated with this
+billing item.
+
+=cut
+
+sub calc_remain {
+ my $self = shift;
+ $self->part_pkg->calc_remain($self, @_);
+}
+
+=item calc_cancel
+
+Calls the I<calc_cancel> of the FS::part_pkg object associated with this
+billing item.
+
+=cut
+
+sub calc_cancel {
+ my $self = shift;
+ $self->part_pkg->calc_cancel($self, @_);
+}
+
+=item cust_bill_pkg
+
+Returns any invoice line items for this package (see L<FS::cust_bill_pkg>).
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item cust_pkg_detail [ DETAILTYPE ]
+
+Returns any customer package details for this package (see
+L<FS::cust_pkg_detail>).
+
+DETAILTYPE can be set to "I" for invoice details or "C" for comments.
+
+=cut
+
+sub cust_pkg_detail {
+ my $self = shift;
+ my %hash = ( 'pkgnum' => $self->pkgnum );
+ $hash{detailtype} = shift if @_;
+ qsearch({
+ 'table' => 'cust_pkg_detail',
+ 'hashref' => \%hash,
+ 'order_by' => 'ORDER BY weight, pkgdetailnum',
+ });
+}
+
+=item set_cust_pkg_detail DETAILTYPE [ DETAIL, DETAIL, ... ]
+
+Sets customer package details for this package (see L<FS::cust_pkg_detail>).
+
+DETAILTYPE can be set to "I" for invoice details or "C" for comments.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub set_cust_pkg_detail {
+ my( $self, $detailtype, @details ) = @_;
+
+ 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 $current ( $self->cust_pkg_detail($detailtype) ) {
+ my $error = $current->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error removing old detail: $error";
+ }
+ }
+
+ foreach my $detail ( @details ) {
+ my $cust_pkg_detail = new FS::cust_pkg_detail {
+ 'pkgnum' => $self->pkgnum,
+ 'detailtype' => $detailtype,
+ 'detail' => $detail,
+ };
+ my $error = $cust_pkg_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error adding new detail: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item cust_event
+
+Returns the new-style customer billing events (see L<FS::cust_event>) for this invoice.
+
+=cut
+
+#false laziness w/cust_bill.pm
+sub cust_event {
+ my $self = shift;
+ qsearch({
+ 'table' => 'cust_event',
+ 'addl_from' => 'JOIN part_event USING ( eventpart )',
+ 'hashref' => { 'tablenum' => $self->pkgnum },
+ 'extra_sql' => " AND eventtable = 'cust_pkg' ",
+ });
+}
+
+=item num_cust_event
+
+Returns the number of new-style customer billing events (see L<FS::cust_event>) for this invoice.
+
+=cut
+
+#false laziness w/cust_bill.pm
+sub num_cust_event {
+ my $self = shift;
+ my $sql =
+ "SELECT COUNT(*) FROM cust_event JOIN part_event USING ( eventpart ) ".
+ " WHERE tablenum = ? AND eventtable = 'cust_pkg'";
+ my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute($self->pkgnum) or die $sth->errstr. " executing $sql";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=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;
+
+ return () unless $self->num_cust_svc(@_);
+
+ if ( @_ ) {
+ return qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum,
+ 'svcpart' => shift, } );
+ }
+
+ cluck "cust_pkg->cust_svc called" if $DEBUG > 2;
+
+ #if ( $self->{'_svcnum'} ) {
+ # values %{ $self->{'_svcnum'}->cache };
+ #} else {
+ $self->_sort_cust_svc(
+ [ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ]
+ );
+ #}
+
+}
+
+=item overlimit [ SVCPART ]
+
+Returns the services for this package which have exceeded their
+usage limit as FS::cust_svc objects (see L<FS::cust_svc>). If a svcpart
+is specified, return only the matching services.
+
+=cut
+
+sub overlimit {
+ my $self = shift;
+ return () unless $self->num_cust_svc(@_);
+ grep { $_->overlimit } $self->cust_svc(@_);
+}
+
+=item h_cust_svc END_TIMESTAMP [ START_TIMESTAMP ]
+
+Returns historical services for this package created before END TIMESTAMP and
+(optionally) not cancelled before START_TIMESTAMP, as FS::h_cust_svc objects
+(see L<FS::h_cust_svc>).
+
+=cut
+
+sub h_cust_svc {
+ my $self = shift;
+
+ $self->_sort_cust_svc(
+ [ qsearch( 'h_cust_svc',
+ { 'pkgnum' => $self->pkgnum, },
+ FS::h_cust_svc->sql_h_search(@_),
+ )
+ ]
+ );
+}
+
+sub _sort_cust_svc {
+ my( $self, $arrayref ) = @_;
+
+ my $sort =
+ sub ($$) { my ($a, $b) = @_; $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] };
+
+ map { $_->[0] }
+ sort $sort
+ map {
+ my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
+ 'svcpart' => $_->svcpart } );
+ [ $_,
+ $pkg_svc ? $pkg_svc->primary_svc : '',
+ $pkg_svc ? $pkg_svc->quantity : 0,
+ ];
+ }
+ @$arrayref;
+
+}
+
+=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;
+
+ return $self->{'_num_cust_svc'}
+ if !scalar(@_)
+ && exists($self->{'_num_cust_svc'})
+ && $self->{'_num_cust_svc'} =~ /\d/;
+
+ cluck "cust_pkg->num_cust_svc called, _num_cust_svc:".$self->{'_num_cust_svc'}
+ if $DEBUG > 2;
+
+ 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 of 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 part_svc
+
+Returns a list of FS::part_svc objects representing provisioned and available
+services included in this package. Each FS::part_svc object also has the
+following extra fields:
+
+=over 4
+
+=item num_cust_svc (count)
+
+=item num_avail (quantity - count)
+
+=item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects
+
+svcnum
+label -> ($cust_svc->label)[1]
+
+=back
+
+=cut
+
+sub part_svc {
+ my $self = shift;
+
+ #XXX some sort of sort order besides numeric by svcpart...
+ my @part_svc = sort { $a->svcpart <=> $b->svcpart } map {
+ my $pkg_svc = $_;
+ my $part_svc = $pkg_svc->part_svc;
+ my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart);
+ $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #more evil
+ $part_svc->{'Hash'}{'num_avail'} =
+ max( 0, $pkg_svc->quantity - $num_cust_svc );
+ $part_svc->{'Hash'}{'cust_pkg_svc'} =
+ $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : [];
+ $part_svc;
+ } $self->part_pkg->pkg_svc;
+
+ #extras
+ push @part_svc, map {
+ my $part_svc = $_;
+ my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart);
+ $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail
+ $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ?
+ $part_svc->{'Hash'}{'cust_pkg_svc'} =
+ $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : [];
+ $part_svc;
+ } $self->extra_part_svc;
+
+ @part_svc;
+
+}
+
+=item extra_part_svc
+
+Returns a list of FS::part_svc objects corresponding to services in this
+package which are still provisioned but not (any longer) available in the
+package definition.
+
+=cut
+
+sub extra_part_svc {
+ my $self = shift;
+
+ my $pkgnum = $self->pkgnum;
+ my $pkgpart = $self->pkgpart;
+
+# qsearch( {
+# 'table' => 'part_svc',
+# 'hashref' => {},
+# 'extra_sql' =>
+# "WHERE 0 = ( SELECT COUNT(*) FROM pkg_svc
+# WHERE pkg_svc.svcpart = part_svc.svcpart
+# AND pkg_svc.pkgpart = ?
+# AND quantity > 0
+# )
+# AND 0 < ( SELECT COUNT(*) FROM cust_svc
+# LEFT JOIN cust_pkg USING ( pkgnum )
+# WHERE cust_svc.svcpart = part_svc.svcpart
+# AND pkgnum = ?
+# )",
+# 'extra_param' => [ [$self->pkgpart=>'int'], [$self->pkgnum=>'int'] ],
+# } );
+
+#seems to benchmark slightly faster...
+ qsearch( {
+ #'select' => 'DISTINCT ON (svcpart) part_svc.*',
+ #MySQL doesn't grok DISINCT ON
+ 'select' => 'DISTINCT part_svc.*',
+ 'table' => 'part_svc',
+ 'addl_from' =>
+ 'LEFT JOIN pkg_svc ON ( pkg_svc.svcpart = part_svc.svcpart
+ AND pkg_svc.pkgpart = ?
+ AND quantity > 0
+ )
+ LEFT JOIN cust_svc ON ( cust_svc.svcpart = part_svc.svcpart )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ ',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE pkgsvcnum IS NULL AND cust_pkg.pkgnum = ? ",
+ 'extra_param' => [ [$self->pkgpart=>'int'], [$self->pkgnum=>'int'] ],
+ } );
+}
+
+=item status
+
+Returns a short status string for this package, currently:
+
+=over 4
+
+=item not yet billed
+
+=item one-time charge
+
+=item active
+
+=item suspended
+
+=item cancelled
+
+=back
+
+=cut
+
+sub status {
+ my $self = shift;
+
+ my $freq = length($self->freq) ? $self->freq : $self->part_pkg->freq;
+
+ return 'cancelled' if $self->get('cancel');
+ return 'suspended' if $self->susp;
+ return 'not yet billed' unless $self->setup;
+ return 'one-time charge' if $freq =~ /^(0|$)/;
+ return 'active';
+}
+
+=item statuses
+
+Class method that returns the list of possible status strings for packages
+(see L<the status method|/status>). For example:
+
+ @statuses = FS::cust_pkg->statuses();
+
+=cut
+
+tie my %statuscolor, 'Tie::IxHash',
+ 'not yet billed' => '000000',
+ 'one-time charge' => '000000',
+ 'active' => '00CC00',
+ 'suspended' => 'FF9900',
+ 'cancelled' => 'FF0000',
+;
+
+sub statuses {
+ my $self = shift; #could be class...
+ #grep { $_ !~ /^(not yet billed)$/ } #this is a dumb status anyway
+ # # mayble split btw one-time vs. recur
+ keys %statuscolor;
+}
+
+=item statuscolor
+
+Returns a hex triplet color string for this package's status.
+
+=cut
+
+sub statuscolor {
+ my $self = shift;
+ $statuscolor{$self->status};
+}
+
+=item pkg_label
+
+Returns a label for this package. (Currently "pkgnum: pkg - comment" or
+"pkg-comment" depending on user preference).
+
+=cut
+
+sub pkg_label {
+ my $self = shift;
+ my $label = $self->part_pkg->pkg_comment( 'nopkgpart' => 1 );
+ $label = $self->pkgnum. ": $label"
+ if $FS::CurrentUser::CurrentUser->option('show_pkgnum');
+ $label;
+}
+
+=item pkg_label_long
+
+Returns a long label for this package, adding the primary service's label to
+pkg_label.
+
+=cut
+
+sub pkg_label_long {
+ my $self = shift;
+ my $label = $self->pkg_label;
+ my $cust_svc = $self->primary_cust_svc;
+ $label .= ' ('. ($cust_svc->label)[1]. ')' if $cust_svc;
+ $label;
+}
+
+=item primary_cust_svc
+
+Returns a primary service (as FS::cust_svc object) if one can be identified.
+
+=cut
+
+#for labeling purposes - might not 100% match up with part_pkg->svcpart's idea
+
+sub primary_cust_svc {
+ my $self = shift;
+
+ my @cust_svc = $self->cust_svc;
+
+ return '' unless @cust_svc; #no serivces - irrelevant then
+
+ return $cust_svc[0] if scalar(@cust_svc) == 1; #always return a single service
+
+ # primary service as specified in the package definition
+ # or exactly one service definition with quantity one
+ my $svcpart = $self->part_pkg->svcpart;
+ @cust_svc = grep { $_->svcpart == $svcpart } @cust_svc;
+ return $cust_svc[0] if scalar(@cust_svc) == 1;
+
+ #couldn't identify one thing..
+ return '';
+}
+
+=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 h_labels END_TIMESTAMP [ START_TIMESTAMP ]
+
+Like the labels method, but returns historical information on services that
+were active as of END_TIMESTAMP and (optionally) not cancelled before
+START_TIMESTAMP.
+
+Returns a list of lists, calling the label method for all (historical) services
+(see L<FS::h_cust_svc>) of this billing item.
+
+=cut
+
+sub h_labels {
+ my $self = shift;
+ map { [ $_->label(@_) ] } $self->h_cust_svc(@_);
+}
+
+=item labels_short
+
+Like labels, except returns a simple flat list, and shortens long
+(currently >5 or the cust_bill-max_same_services configuration value) lists of
+identical services to one line that lists the service label and the number of
+individual services rather than individual items.
+
+=cut
+
+sub labels_short {
+ shift->_labels_short( 'labels', @_ );
+}
+
+=item h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
+
+Like h_labels, except returns a simple flat list, and shortens long
+(currently >5 or the cust_bill-max_same_services configuration value) lists of
+identical services to one line that lists the service label and the number of
+individual services rather than individual items.
+
+=cut
+
+sub h_labels_short {
+ shift->_labels_short( 'h_labels', @_ );
+}
+
+sub _labels_short {
+ my( $self, $method ) = ( shift, shift );
+
+ my $conf = new FS::Conf;
+ my $max_same_services = $conf->config('cust_bill-max_same_services') || 5;
+
+ my %labels;
+ #tie %labels, 'Tie::IxHash';
+ push @{ $labels{$_->[0]} }, $_->[1]
+ foreach $self->h_labels(@_);
+ my @labels;
+ foreach my $label ( keys %labels ) {
+ my %seen = ();
+ my @values = grep { ! $seen{$_}++ } @{ $labels{$label} };
+ my $num = scalar(@values);
+ if ( $num > $max_same_services ) {
+ push @labels, "$label ($num)";
+ } else {
+ if ( $conf->exists('cust_bill-consolidate_services') ) {
+ # push @labels, "$label: ". join(', ', @values);
+ while ( @values ) {
+ my $detail = "$label: ";
+ $detail .= shift(@values). ', '
+ while @values && length($detail.$values[0]) < 78;
+ $detail =~ s/, $//;
+ push @labels, $detail;
+ }
+ } else {
+ push @labels, map { "$label: $_" } @values;
+ }
+ }
+ }
+
+ @labels;
+
+}
+
+=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 } );
+}
+
+#these subs are in location_Mixin.pm now... unfortunately the POD doesn't mixin
+
+=item cust_location
+
+Returns the location object, if any (see L<FS::cust_location>).
+
+=item cust_location_or_main
+
+If this package is associated with a location, returns the locaiton (see
+L<FS::cust_location>), otherwise returns the customer (see L<FS::cust_main>).
+
+=item location_label [ OPTION => VALUE ... ]
+
+Returns the label of the location object (see L<FS::cust_location>).
+
+=cut
+
+#end of subs in location_Mixin.pm now... unfortunately the POD doesn't mixin
+
+=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 quantity
+
+=cut
+
+sub quantity {
+ my( $self, $value ) = @_;
+ if ( defined($value) ) {
+ $self->setfield('quantity', $value);
+ }
+ $self->getfield('quantity') || 1;
+}
+
+=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 { $cust_svc->hash };
+ $new->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 { $cust_svc->hash };
+ $new->svcpart($change_svcpart);
+ $new->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;
+ '';
+
+}
+
+=item insert_reason
+
+Associates this package with a (suspension or cancellation) reason (see
+L<FS::cust_pkg_reason>, possibly inserting a new reason on the fly (see
+L<FS::reason>).
+
+Available options are:
+
+=over 4
+
+=item reason
+
+can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+
+=item reason_otaker
+
+the access_user (see L<FS::access_user>) providing the reason
+
+=item date
+
+a unix timestamp
+
+=item action
+
+the action (cancel, susp, adjourn, expire) associated with the reason
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert_reason {
+ my ($self, %options) = @_;
+
+ my $otaker = $options{reason_otaker} ||
+ $FS::CurrentUser::CurrentUser->username;
+
+ my $reasonnum;
+ if ( $options{'reason'} =~ /^(\d+)$/ ) {
+
+ $reasonnum = $1;
+
+ } elsif ( ref($options{'reason'}) ) {
+
+ return 'Enter a new reason (or select an existing one)'
+ unless $options{'reason'}->{'reason'} !~ /^\s*$/;
+
+ my $reason = new FS::reason({
+ 'reason_type' => $options{'reason'}->{'typenum'},
+ 'reason' => $options{'reason'}->{'reason'},
+ });
+ my $error = $reason->insert;
+ return $error if $error;
+
+ $reasonnum = $reason->reasonnum;
+
+ } else {
+ return "Unparsable reason: ". $options{'reason'};
+ }
+
+ my $cust_pkg_reason =
+ new FS::cust_pkg_reason({ 'pkgnum' => $self->pkgnum,
+ 'reasonnum' => $reasonnum,
+ 'otaker' => $otaker,
+ 'action' => substr(uc($options{'action'}),0,1),
+ 'date' => $options{'date'}
+ ? $options{'date'}
+ : time,
+ });
+
+ $cust_pkg_reason->insert;
+}
+
+=item insert_discount
+
+Associates this package with a discount (see L<FS::cust_pkg_discount>, possibly
+inserting a new discount on the fly (see L<FS::discount>).
+
+Available options are:
+
+=over 4
+
+=item discountnum
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert_discount {
+ #my ($self, %options) = @_;
+ my $self = shift;
+
+ my $cust_pkg_discount = new FS::cust_pkg_discount {
+ 'pkgnum' => $self->pkgnum,
+ 'discountnum' => $self->discountnum,
+ 'months_used' => 0,
+ 'end_date' => '', #XXX
+ 'otaker' => $self->otaker,
+ #for the create a new discount case
+ '_type' => $self->discountnum__type,
+ 'amount' => $self->discountnum_amount,
+ 'percent' => $self->discountnum_percent,
+ 'months' => $self->discountnum_months,
+ #'disabled' => $self->discountnum_disabled,
+ };
+
+ $cust_pkg_discount->insert;
+}
+
+=item set_usage USAGE_VALUE_HASHREF
+
+USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts
+to which they should be set (see L<FS::svc_acct>). Currently seconds,
+upbytes, downbytes, and totalbytes are appropriate keys.
+
+All svc_accts which are part of this package have their values reset.
+
+=cut
+
+sub set_usage {
+ my ($self, $valueref, %opt) = @_;
+
+ foreach my $cust_svc ($self->cust_svc){
+ my $svc_x = $cust_svc->svc_x;
+ $svc_x->set_usage($valueref, %opt)
+ if $svc_x->can("set_usage");
+ }
+}
+
+=item recharge USAGE_VALUE_HASHREF
+
+USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts
+to which they should be set (see L<FS::svc_acct>). Currently seconds,
+upbytes, downbytes, and totalbytes are appropriate keys.
+
+All svc_accts which are part of this package have their values incremented.
+
+=cut
+
+sub recharge {
+ my ($self, $valueref) = @_;
+
+ foreach my $cust_svc ($self->cust_svc){
+ my $svc_x = $cust_svc->svc_x;
+ $svc_x->recharge($valueref)
+ if $svc_x->can("recharge");
+ }
+}
+
+=item cust_pkg_discount
+
+=cut
+
+sub cust_pkg_discount {
+ my $self = shift;
+ qsearch('cust_pkg_discount', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item cust_pkg_discount_active
+
+=cut
+
+sub cust_pkg_discount_active {
+ my $self = shift;
+ grep { $_->status eq 'active' } $self->cust_pkg_discount;
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item recurring_sql
+
+Returns an SQL expression identifying recurring packages.
+
+=cut
+
+sub recurring_sql { "
+ '0' != ( select freq from part_pkg
+ where cust_pkg.pkgpart = part_pkg.pkgpart )
+"; }
+
+=item onetime_sql
+
+Returns an SQL expression identifying one-time packages.
+
+=cut
+
+sub onetime_sql { "
+ '0' = ( select freq from part_pkg
+ where cust_pkg.pkgpart = part_pkg.pkgpart )
+"; }
+
+=item active_sql
+
+Returns an SQL expression identifying active packages.
+
+=cut
+
+sub active_sql { "
+ ". $_[0]->recurring_sql(). "
+ AND cust_pkg.setup IS NOT NULL AND cust_pkg.setup != 0
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
+"; }
+
+=item not_yet_billed_sql
+
+Returns an SQL expression identifying packages which have not yet been billed.
+
+=cut
+
+sub not_yet_billed_sql { "
+ ( cust_pkg.setup IS NULL OR cust_pkg.setup = 0 )
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
+"; }
+
+=item inactive_sql
+
+Returns an SQL expression identifying inactive packages (one-time packages
+that are otherwise unsuspended/uncancelled).
+
+=cut
+
+sub inactive_sql { "
+ ". $_[0]->onetime_sql(). "
+ AND cust_pkg.setup IS NOT NULL AND cust_pkg.setup != 0
+ 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 packages.
+
+=cut
+
+sub suspended_sql { susp_sql(@_); }
+sub susp_sql {
+ #$_[0]->recurring_sql(). ' AND '.
+ "
+ ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0
+ ";
+}
+
+=item cancel_sql
+=item cancelled_sql
+
+Returns an SQL exprression identifying cancelled packages.
+
+=cut
+
+sub cancelled_sql { cancel_sql(@_); }
+sub cancel_sql {
+ #$_[0]->recurring_sql(). ' AND '.
+ "cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0";
+}
+
+=item search HASHREF
+
+(Class method)
+
+Returns a qsearch hash expression to search for parameters specified in HASHREF.
+Valid parameters are
+
+=over 4
+
+=item agentnum
+
+=item magic
+
+active, inactive, suspended, cancel (or cancelled)
+
+=item status
+
+active, inactive, suspended, one-time charge, inactive, cancel (or cancelled)
+
+=item custom
+
+ boolean selects custom packages
+
+=item classnum
+
+=item pkgpart
+
+pkgpart or arrayref or hashref of pkgparts
+
+=item setup
+
+arrayref of beginning and ending epoch date
+
+=item last_bill
+
+arrayref of beginning and ending epoch date
+
+=item bill
+
+arrayref of beginning and ending epoch date
+
+=item adjourn
+
+arrayref of beginning and ending epoch date
+
+=item susp
+
+arrayref of beginning and ending epoch date
+
+=item expire
+
+arrayref of beginning and ending epoch date
+
+=item cancel
+
+arrayref of beginning and ending epoch date
+
+=item query
+
+pkgnum or APKG_pkgnum
+
+=item cust_fields
+
+a value suited to passing to FS::UI::Web::cust_header
+
+=item CurrentUser
+
+specifies the user for agent virtualization
+
+=item fcc_line
+
+ boolean selects packages containing fcc form 477 telco lines
+
+=back
+
+=cut
+
+sub search {
+ my ($class, $params) = @_;
+ my @where = ();
+
+ ##
+ # parse agent
+ ##
+
+ if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "cust_main.agentnum = $1";
+ }
+
+ ##
+ # parse custnum
+ ##
+
+ if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "cust_pkg.custnum = $1";
+ }
+
+ ##
+ # parse status
+ ##
+
+ if ( $params->{'magic'} eq 'active'
+ || $params->{'status'} eq 'active' ) {
+
+ push @where, FS::cust_pkg->active_sql();
+
+ } elsif ( $params->{'magic'} =~ /^not[ _]yet[ _]billed$/
+ || $params->{'status'} =~ /^not[ _]yet[ _]billed$/ ) {
+
+ push @where, FS::cust_pkg->not_yet_billed_sql();
+
+ } elsif ( $params->{'magic'} =~ /^(one-time charge|inactive)/
+ || $params->{'status'} =~ /^(one-time charge|inactive)/ ) {
+
+ push @where, FS::cust_pkg->inactive_sql();
+
+ } elsif ( $params->{'magic'} eq 'suspended'
+ || $params->{'status'} eq 'suspended' ) {
+
+ push @where, FS::cust_pkg->suspended_sql();
+
+ } elsif ( $params->{'magic'} =~ /^cancell?ed$/
+ || $params->{'status'} =~ /^cancell?ed$/ ) {
+
+ push @where, FS::cust_pkg->cancelled_sql();
+
+ }
+
+ ###
+ # parse package class
+ ###
+
+ #false lazinessish w/graph/cust_bill_pkg.cgi
+ my $classnum = 0;
+ my @pkg_class = ();
+ if ( exists($params->{'classnum'})
+ && $params->{'classnum'} =~ /^(\d*)$/
+ )
+ {
+ $classnum = $1;
+ if ( $classnum ) { #a specific class
+ push @where, "part_pkg.classnum = $classnum";
+
+ #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
+ #die "classnum $classnum not found!" unless $pkg_class[0];
+ #$title .= $pkg_class[0]->classname.' ';
+
+ } elsif ( $classnum eq '' ) { #the empty class
+
+ push @where, "part_pkg.classnum IS NULL";
+ #$title .= 'Empty class ';
+ #@pkg_class = ( '(empty class)' );
+ } elsif ( $classnum eq '0' ) {
+ #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } );
+ #push @pkg_class, '(empty class)';
+ } else {
+ die "illegal classnum";
+ }
+ }
+ #eslaf
+
+ ###
+ # parse package report options
+ ###
+
+ my @report_option = ();
+ if ( exists($params->{'report_option'})
+ && $params->{'report_option'} =~ /^([,\d]*)$/
+ )
+ {
+ @report_option = split(',', $1);
+ }
+
+ if (@report_option) {
+ # this will result in the empty set for the dangling comma case as it should
+ push @where,
+ map{ "0 < ( SELECT count(*) FROM part_pkg_option
+ WHERE part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND optionname = 'report_option_$_'
+ AND optionvalue = '1' )"
+ } @report_option;
+ }
+
+ #eslaf
+
+ ###
+ # parse custom
+ ###
+
+ push @where, "part_pkg.custom = 'Y'" if $params->{custom};
+
+ ###
+ # parse fcc_line
+ ###
+
+ push @where, "part_pkg.fcc_ds0s > 0" if $params->{fcc_line};
+
+ ###
+ # parse censustract
+ ###
+
+ if ( exists($params->{'censustract'}) ) {
+ $params->{'censustract'} =~ /^([.\d]*)$/;
+ my $censustract = "cust_main.censustract = '$1'";
+ $censustract .= ' OR cust_main.censustract is NULL' unless $1;
+ push @where, "( $censustract )";
+ }
+
+ ###
+ # parse part_pkg
+ ###
+
+ if ( ref($params->{'pkgpart'}) ) {
+
+ my @pkgpart = ();
+ if ( ref($params->{'pkgpart'}) eq 'HASH' ) {
+ @pkgpart = grep $params->{'pkgpart'}{$_}, keys %{ $params->{'pkgpart'} };
+ } elsif ( ref($params->{'pkgpart'}) eq 'ARRAY' ) {
+ @pkgpart = @{ $params->{'pkgpart'} };
+ } else {
+ die 'unhandled pkgpart ref '. $params->{'pkgpart'};
+ }
+
+ @pkgpart = grep /^(\d+)$/, @pkgpart;
+
+ push @where, 'pkgpart IN ('. join(',', @pkgpart). ')' if scalar(@pkgpart);
+
+ } elsif ( $params->{'pkgpart'} =~ /^(\d+)$/ ) {
+ push @where, "pkgpart = $1";
+ }
+
+ ###
+ # parse dates
+ ###
+
+ my $orderby = '';
+
+ #false laziness w/report_cust_pkg.html
+ my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+ );
+
+ if( exists($params->{'active'} ) ) {
+ # This overrides all the other date-related fields
+ my($beginning, $ending) = @{$params->{'active'}};
+ push @where,
+ "cust_pkg.setup IS NOT NULL",
+ "cust_pkg.setup <= $ending",
+ "(cust_pkg.cancel IS NULL OR cust_pkg.cancel >= $beginning )",
+ "NOT (".FS::cust_pkg->onetime_sql . ")";
+ }
+ else {
+ foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+
+ next unless exists($params->{$field});
+
+ my($beginning, $ending) = @{$params->{$field}};
+
+ next if $beginning == 0 && $ending == 4294967295;
+
+ push @where,
+ "cust_pkg.$field IS NOT NULL",
+ "cust_pkg.$field >= $beginning",
+ "cust_pkg.$field <= $ending";
+
+ $orderby ||= "ORDER BY cust_pkg.$field";
+
+ }
+ }
+
+ $orderby ||= 'ORDER BY bill';
+
+ ###
+ # parse magic, legacy, etc.
+ ###
+
+ if ( $params->{'magic'} &&
+ $params->{'magic'} =~ /^(active|inactive|suspended|cancell?ed)$/
+ ) {
+
+ $orderby = 'ORDER BY pkgnum';
+
+ if ( $params->{'pkgpart'} =~ /^(\d+)$/ ) {
+ push @where, "pkgpart = $1";
+ }
+
+ } elsif ( $params->{'query'} eq 'pkgnum' ) {
+
+ $orderby = 'ORDER BY pkgnum';
+
+ } elsif ( $params->{'query'} eq 'APKG_pkgnum' ) {
+
+ $orderby = 'ORDER BY pkgnum';
+
+ push @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
+ )
+ )';
+
+ }
+
+ ##
+ # setup queries, links, subs, etc. for the search
+ ##
+
+ # here is the agent virtualization
+ if ($params->{CurrentUser}) {
+ my $access_user =
+ qsearchs('access_user', { username => $params->{CurrentUser} });
+
+ if ($access_user) {
+ push @where, $access_user->agentnums_sql('table'=>'cust_main');
+ } else {
+ push @where, "1=0";
+ }
+ } else {
+ push @where, $FS::CurrentUser::CurrentUser->agentnums_sql('table'=>'cust_main');
+ }
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '.
+ 'LEFT JOIN part_pkg USING ( pkgpart ) '.
+ 'LEFT JOIN pkg_class ON ( part_pkg.classnum = pkg_class.classnum ) ';
+
+ my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql";
+
+ my $sql_query = {
+ 'table' => 'cust_pkg',
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'cust_pkg.*',
+ ( map "part_pkg.$_", qw( pkg freq ) ),
+ 'pkg_class.classname',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(
+ $params->{'cust_fields'}
+ ),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+ 'count_query' => $count_query,
+ };
+
+}
+
+=item fcc_477_count
+
+Returns a list of two package counts. The first is a count of packages
+based on the supplied criteria and the second is the count of residential
+packages with those same criteria. Criteria are specified as in the search
+method.
+
+=cut
+
+sub fcc_477_count {
+ my ($class, $params) = @_;
+
+ my $sql_query = $class->search( $params );
+
+ my $count_sql = delete($sql_query->{'count_query'});
+ $count_sql =~ s/ FROM/,count(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN 1 END) FROM/
+ or die "couldn't parse count_sql";
+
+ my $count_sth = dbh->prepare($count_sql)
+ or die "Error preparing $count_sql: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $count_sql: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+
+ return ( @$count_arrayref );
+
+}
+
+
+=item location_sql
+
+Returns a list: the first item is an SQL fragment identifying matching
+packages/customers via location (taking into account shipping and package
+address taxation, if enabled), and subsequent items are the parameters to
+substitute for the placeholders in that fragment.
+
+=cut
+
+sub location_sql {
+ my($class, %opt) = @_;
+ my $ornull = $opt{'ornull'};
+
+ my $conf = new FS::Conf;
+
+ # '?' placeholders in _location_sql_where
+ my $x = $ornull ? 3 : 2;
+ my @bill_param = ( ('city')x3, ('county')x$x, ('state')x$x, 'country' );
+
+ my $main_where;
+ my @main_param;
+ if ( $conf->exists('tax-ship_address') ) {
+
+ $main_where = "(
+ ( ( ship_last IS NULL OR ship_last = '' )
+ AND ". _location_sql_where('cust_main', '', $ornull ). "
+ )
+ OR ( ship_last IS NOT NULL AND ship_last != ''
+ AND ". _location_sql_where('cust_main', 'ship_', $ornull ). "
+ )
+ )";
+ # AND payby != 'COMP'
+
+ @main_param = ( @bill_param, @bill_param );
+
+ } else {
+
+ $main_where = _location_sql_where('cust_main'); # AND payby != 'COMP'
+ @main_param = @bill_param;
+
+ }
+
+ my $where;
+ my @param;
+ if ( $conf->exists('tax-pkg_address') ) {
+
+ my $loc_where = _location_sql_where( 'cust_location', '', $ornull );
+
+ $where = " (
+ ( cust_pkg.locationnum IS NULL AND $main_where )
+ OR ( cust_pkg.locationnum IS NOT NULL AND $loc_where )
+ )
+ ";
+ @param = ( @main_param, @bill_param );
+
+ } else {
+
+ $where = $main_where;
+ @param = @main_param;
+
+ }
+
+ ( $where, @param );
+
+}
+
+#subroutine, helper for location_sql
+sub _location_sql_where {
+ my $table = shift;
+ my $prefix = @_ ? shift : '';
+ my $ornull = @_ ? shift : '';
+
+# $ornull = $ornull ? " OR ( ? IS NULL AND $table.${prefix}county IS NULL ) " : '';
+
+ $ornull = $ornull ? ' OR ? IS NULL ' : '';
+
+ my $or_empty_city = " OR ( ? = '' AND $table.${prefix}city IS NULL ) ";
+ my $or_empty_county = " OR ( ? = '' AND $table.${prefix}county IS NULL ) ";
+ my $or_empty_state = " OR ( ? = '' AND $table.${prefix}state IS NULL ) ";
+
+# ( $table.${prefix}city = ? $or_empty_city $ornull )
+ "
+ ( $table.${prefix}city = ? OR ? = '' OR CAST(? AS text) IS NULL )
+ AND ( $table.${prefix}county = ? $or_empty_county $ornull )
+ AND ( $table.${prefix}state = ? $or_empty_state $ornull )
+ AND $table.${prefix}country = ?
+ ";
+}
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item order CUSTNUM, PKGPARTS_ARYREF, [ REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF [ REFNUM ] ] ]
+
+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.
+
+REFNUM, if specified, will specify the FS::pkg_referral record to be created
+and inserted. Multiple FS::pkg_referral records can be created by
+setting I<refnum> to an array reference of refnums or a hash reference with
+refnums as keys. If no I<refnum> is defined, a default FS::pkg_referral
+record will be created corresponding to cust_main.refnum.
+
+=cut
+
+sub order {
+ my ($custnum, $pkgparts, $remove_pkgnum, $return_cust_pkg, $refnum) = @_;
+
+ 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;
+
+ warn "$me order: pkgnums to remove: ". join(',', @$remove_pkgnum). "\n"
+ if $DEBUG;
+
+ my @old_cust_pkg = map { qsearchs('cust_pkg', { pkgnum => $_ }) }
+ @$remove_pkgnum;
+
+ my $change = scalar(@old_cust_pkg) != 0;
+
+ my %hash = ();
+ if ( scalar(@old_cust_pkg) == 1 && scalar(@$pkgparts) == 1 ) {
+
+ warn "$me order: changing pkgnum ". $old_cust_pkg[0]->pkgnum.
+ " to pkgpart ". $pkgparts->[0]. "\n"
+ if $DEBUG;
+
+ my $err_or_cust_pkg =
+ $old_cust_pkg[0]->change( 'pkgpart' => $pkgparts->[0],
+ 'refnum' => $refnum,
+ );
+
+ unless (ref($err_or_cust_pkg)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_cust_pkg;
+ }
+
+ push @$return_cust_pkg, $err_or_cust_pkg;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+
+ }
+
+ # Create the new packages.
+ foreach my $pkgpart (@$pkgparts) {
+
+ warn "$me order: inserting pkgpart $pkgpart\n" if $DEBUG;
+
+ my $cust_pkg = new FS::cust_pkg { custnum => $custnum,
+ pkgpart => $pkgpart,
+ refnum => $refnum,
+ %hash,
+ };
+ $error = $cust_pkg->insert( 'change' => $change );
+ 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_pkg (@old_cust_pkg) {
+
+ warn "$me order: transferring services from pkgnum ". $old_pkg->pkgnum. "\n"
+ if $DEBUG;
+
+ 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( quiet=>1 );
+ if ($error) {
+ $dbh->rollback;
+ return $error;
+ }
+ }
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item bulk_change PKGPARTS_ARYREF, REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF ]
+
+A bulk change method to change packages for multiple customers.
+
+PKGPARTS is a list of pkgparts specifying the the billing item definitions (see
+L<FS::part_pkg>) to order for each customer. Duplicates are of course
+permitted.
+
+REMOVE_PKGNUMS is an list of pkgnums specifying the billing items to
+replace. 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>).
+
+RETURN_CUST_PKG_ARRAYREF, if specified, will be filled in with the
+newly-created cust_pkg objects.
+
+=cut
+
+sub bulk_change {
+ my ($pkgparts, $remove_pkgnum, $return_cust_pkg) = @_;
+
+ # 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 @errors;
+ my @old_cust_pkg = map { qsearchs('cust_pkg', { pkgnum => $_ }) }
+ @$remove_pkgnum;
+
+ while(scalar(@old_cust_pkg)) {
+ my @return = ();
+ my $custnum = $old_cust_pkg[0]->custnum;
+ my (@remove) = map { $_->pkgnum }
+ grep { $_->custnum == $custnum } @old_cust_pkg;
+ @old_cust_pkg = grep { $_->custnum != $custnum } @old_cust_pkg;
+
+ my $error = order $custnum, $pkgparts, \@remove, \@return;
+
+ push @errors, $error
+ if $error;
+ push @$return_cust_pkg, @return;
+ }
+
+ if (scalar(@errors)) {
+ $dbh->rollback if $oldAutoCommit;
+ return join(' / ', @errors);
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=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_pkg_detail.pm b/FS/FS/cust_pkg_detail.pm
new file mode 100644
index 0000000..e2d8987
--- /dev/null
+++ b/FS/FS/cust_pkg_detail.pm
@@ -0,0 +1,140 @@
+package FS::cust_pkg_detail;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record; # qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_pkg_detail - Object methods for cust_pkg_detail records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg_detail;
+
+ $record = new FS::cust_pkg_detail \%hash;
+ $record = new FS::cust_pkg_detail { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg_detail object represents additional customer package details.
+FS::cust_pkg_detail inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item pkgdetailnum
+
+primary key
+
+=item pkgnum
+
+pkgnum (see L<FS::cust_pkg>)
+
+=item detail
+
+detail
+
+=item detailtype
+
+"I" for Invoice details or "C" for comments
+
+=item weight
+
+Optional display weight
+
+=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 { 'cust_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 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;
+
+ my $error =
+ $self->ut_numbern('pkgdetailnum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_text('detail')
+ || $self->ut_enum('detailtype', [ 'I', 'C' ] )
+ || $self->ut_numbern('weight')
+ ;
+ return $error if $error;
+
+ $self->weight(0) unless $self->weight;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pkg_discount.pm b/FS/FS/cust_pkg_discount.pm
new file mode 100644
index 0000000..3770a2b
--- /dev/null
+++ b/FS/FS/cust_pkg_discount.pm
@@ -0,0 +1,246 @@
+package FS::cust_pkg_discount;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::Record );
+use FS::Record qw( dbh qsearchs ); # qsearch );
+use FS::cust_pkg;
+use FS::discount;
+
+=head1 NAME
+
+FS::cust_pkg_discount - Object methods for cust_pkg_discount records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg_discount;
+
+ $record = new FS::cust_pkg_discount \%hash;
+ $record = new FS::cust_pkg_discount { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg_discount object represents the application of a discount to a
+customer package. FS::cust_pkg_discount inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item pkgdiscountnum
+
+primary key
+
+=item pkgnum
+
+Customer package (see L<FS::cust_pkg>)
+
+=item discountnum
+
+Discount (see L<FS::discount>)
+
+=item months_used
+
+months_used
+
+=item end_date
+
+end_date
+
+=item usernum
+
+order taker, see L<FS::access_user>
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new discount application. 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 { 'cust_pkg_discount'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ #my( $self, %options ) = @_;
+ 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->discountnum == -1 ) {
+ my $discount = new FS::discount {
+ '_type' => $self->_type,
+ 'amount' => $self->amount,
+ 'percent' => $self->percent,
+ 'months' => $self->months,
+ 'disabled' => 'Y',
+ };
+ my $error = $discount->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->discountnum($discount->discountnum);
+ }
+
+ my $error = $self->SUPER::insert; #(@_); #(%options);
+ 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
+
+# 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 discount applciation. 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('pkgdiscountnum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_key('discountnum', 'discount', 'discountnum' )
+ || $self->ut_float('months_used') #actually decimal, but this will do
+ || $self->ut_numbern('end_date')
+ || $self->ut_alphan('otaker')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_pkg
+
+Returns the customer package (see L<FS::cust_pkg>).
+
+=cut
+
+sub cust_pkg {
+ my $self = shift;
+ qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item discount
+
+Returns the discount (see L<FS::discount>).
+
+=cut
+
+sub discount {
+ my $self = shift;
+ qsearchs('discount', { 'discountnum' => $self->discountnum } );
+}
+
+=item increment_months_used
+
+Increments months_used by the given parameter
+
+=cut
+
+sub increment_months_used {
+ my( $self, $used ) = @_;
+ #UPDATE cust_pkg_discount SET months_used = months_used + ?
+ #leaves no history, and billing is mutexed per-customer, so the dum way is ok
+ $self->months_used( $self->months_used + $used );
+ $self->replace();
+}
+
+=item status
+
+=cut
+
+sub status {
+ my $self = shift;
+ my $discount = $self->discount;
+
+ if ( $self->disabled ne 'Y'
+ and ( ! $discount->months || $self->months_used < $discount->months )
+ #XXX also end date
+ ) {
+ 'active';
+ } else {
+ 'expired';
+ }
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::discount>, L<FS::cust_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pkg_option.pm b/FS/FS/cust_pkg_option.pm
new file mode 100644
index 0000000..43a1530
--- /dev/null
+++ b/FS/FS/cust_pkg_option.pm
@@ -0,0 +1,115 @@
+package FS::cust_pkg_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_pkg_option - Object methods for cust_pkg_option records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg_option;
+
+ $record = new FS::cust_pkg_option \%hash;
+ $record = new FS::cust_pkg_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg_option object represents an option key an value for a
+customer package. FS::cust_pkg_option inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item pkgnum -
+
+=item optionname -
+
+=item optionvalue -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new option. To add the 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
+
+sub table { 'cust_pkg_option'; }
+
+=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 option. 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('optionnum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pkg_reason.pm b/FS/FS/cust_pkg_reason.pm
new file mode 100644
index 0000000..1ec5024
--- /dev/null
+++ b/FS/FS/cust_pkg_reason.pm
@@ -0,0 +1,325 @@
+package FS::cust_pkg_reason;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cust_pkg_reason - Object methods for cust_pkg_reason records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg_reason;
+
+ $record = new FS::cust_pkg_reason \%hash;
+ $record = new FS::cust_pkg_reason { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg_reason object represents a relationship between a cust_pkg
+and a reason, for example cancellation or suspension reasons.
+FS::cust_pkg_reason inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item num
+
+primary key
+
+=item pkgnum
+
+=item reasonnum
+
+=item usernum
+
+=item date
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cust_pkg_reason. 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 { 'cust_pkg_reason'; }
+
+=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 cust_pkg_reason. 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('num')
+ || $self->ut_number('pkgnum')
+ || $self->ut_number('reasonnum')
+ || $self->ut_enum('action', [ 'A', 'C', 'E', 'S' ])
+ || $self->ut_alphan('otaker')
+ || $self->ut_numbern('date')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item reason
+
+Returns the reason (see L<FS::reason>) associated with this cust_pkg_reason.
+
+=cut
+
+sub reason {
+ my $self = shift;
+ qsearchs( 'reason', { 'reasonnum' => $self->reasonnum } );
+}
+
+=item reasontext
+
+Returns the text of the reason (see L<FS::reason>) associated with this
+cust_pkg_reason.
+
+=cut
+
+sub reasontext {
+ my $reason = shift->reason;
+ $reason ? $reason->reason : '';
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+use FS::h_cust_pkg;
+use FS::h_cust_pkg_reason;
+use FS::Schema qw(dbdef);
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ return '' unless dbdef->table('cust_pkg_reason')->column('action');
+
+ my $action_replace =
+ " AND ( history_action = 'replace_old' OR history_action = 'replace_new' )";
+
+ my $count = 0;
+ my @unmigrated = qsearch('cust_pkg_reason', { 'action' => '' } );
+ foreach ( @unmigrated ) {
+
+ my @history_cust_pkg_reason = qsearch( 'h_cust_pkg_reason', { $_->hash } );
+
+ next unless scalar(@history_cust_pkg_reason) == 1;
+
+ my $hashref = { pkgnum => $_->pkgnum,
+ history_date => $history_cust_pkg_reason[0]->history_date,
+ };
+
+ my @history = qsearch({ table => 'h_cust_pkg',
+ hashref => $hashref,
+ extra_sql => $action_replace,
+ order_by => 'ORDER BY history_action',
+ });
+
+ my $fuzz = 0;
+ while (scalar(@history) < 2 && $fuzz < 3) {
+ $hashref->{history_date}++;
+ $fuzz++;
+ push @history, qsearch({ table => 'h_cust_pkg',
+ hashref => $hashref,
+ extra_sql => $action_replace,
+ order_by => 'ORDER BY history_action',
+ });
+ }
+
+ next unless scalar(@history) == 2;
+
+ my @new = grep { $_->history_action eq 'replace_new' } @history;
+ my @old = grep { $_->history_action eq 'replace_old' } @history;
+
+ next if (scalar(@new) == 2 || scalar(@old) == 2);
+
+ if ( !$old[0]->get('cancel') && $new[0]->get('cancel') ) {
+ $_->action('C');
+ }elsif( !$old[0]->susp && $new[0]->susp ){
+ $_->action('S');
+ }elsif( $new[0]->expire &&
+ (!$old[0]->expire || !$old[0]->expire != $new[0]->expire )
+ ){
+ $_->action('E');
+ $_->date($new[0]->expire);
+ }elsif( $new[0]->adjourn &&
+ (!$old[0]->adjourn || $old[0]->adjourn != $new[0]->adjourn )
+ ){
+ $_->action('A');
+ $_->date($new[0]->adjourn);
+ }
+
+ my $error = $_->replace
+ if $_->modified;
+
+ die $error if $error;
+
+ $count++;
+ }
+
+ #remove nullability if scalar(@migrated) - $count == 0 && ->column('action');
+
+ #seek expirations/adjourns without reason
+ foreach my $field qw( expire adjourn cancel susp ) {
+ my $addl_from =
+ "LEFT JOIN h_cust_pkg ON ".
+ "(cust_pkg_reason.pkgnum = h_cust_pkg.pkgnum AND".
+ " cust_pkg_reason.date = h_cust_pkg.$field AND".
+ " history_action = 'replace_new')";
+
+ my $extra_sql = 'AND h_cust_pkg.pkgnum IS NULL';
+
+ my @unmigrated = qsearch({ table => 'cust_pkg_reason',
+ hashref => { action => uc(substr($field,0,1)) },
+ addl_from => $addl_from,
+ select => 'cust_pkg_reason.*',
+ extra_sql => $extra_sql,
+ });
+ foreach ( @unmigrated ) {
+
+ my $hashref = { pkgnum => $_->pkgnum,
+ history_date => $_->date,
+ };
+
+ my @history = qsearch({ table => 'h_cust_pkg',
+ hashref => $hashref,
+ extra_sql => $action_replace,
+ order_by => 'ORDER BY history_action',
+ });
+
+ my $fuzz = 0;
+ while (scalar(@history) < 2 && $fuzz < 3) {
+ $hashref->{history_date}++;
+ $fuzz++;
+ push @history, qsearch({ table => 'h_cust_pkg',
+ hashref => $hashref,
+ extra_sql => $action_replace,
+ order_by => 'ORDER BY history_action',
+ });
+ }
+
+ next unless scalar(@history) == 2;
+
+ my @new = grep { $_->history_action eq 'replace_new' } @history;
+ my @old = grep { $_->history_action eq 'replace_old' } @history;
+
+ next if (scalar(@new) == 2 || scalar(@old) == 2);
+
+ $_->date($new[0]->get($field))
+ if ( $new[0]->get($field) &&
+ ( !$old[0]->get($field) ||
+ $old[0]->get($field) != $new[0]->get($field)
+ )
+ );
+
+ my $error = $_->replace
+ if $_->modified;
+
+ die $error if $error;
+ }
+ }
+
+ #seek cancels/suspends without reason, but with expire/adjourn reason
+ foreach my $field qw( cancel susp ) {
+
+ my %precursor_map = ( 'cancel' => 'expire', 'susp' => 'adjourn' );
+ my $precursor = $precursor_map{$field};
+ my $preaction = uc(substr($precursor,0,1));
+ my $action = uc(substr($field,0,1));
+ my $addl_from =
+ "LEFT JOIN cust_pkg_reason ON ".
+ "(cust_pkg.pkgnum = cust_pkg_reason.pkgnum AND".
+ " cust_pkg.$precursor = cust_pkg_reason.date AND".
+ " cust_pkg_reason.action = '$preaction') ".
+ "LEFT JOIN cust_pkg_reason AS target ON ".
+ "(cust_pkg.pkgnum = target.pkgnum AND".
+ " cust_pkg.$field = target.date AND".
+ " target.action = '$action')"
+ ;
+
+ my $extra_sql = "WHERE target.pkgnum IS NULL AND ".
+ "cust_pkg.$field IS NOT NULL AND ".
+ "cust_pkg.$field < cust_pkg.$precursor + 86400 AND ".
+ "cust_pkg_reason.action = '$preaction'";
+
+ my @unmigrated = qsearch({ table => 'cust_pkg',
+ hashref => { },
+ select => 'cust_pkg.*',
+ addl_from => $addl_from,
+ extra_sql => $extra_sql,
+ });
+ foreach ( @unmigrated ) {
+ my $cpr = new FS::cust_pkg_reason { $_->last_cust_pkg_reason($precursor)->hash, 'num' => '' };
+ $cpr->date($_->get($field));
+ $cpr->action($action);
+
+ my $error = $cpr->insert;
+ die $error if $error;
+ }
+ }
+
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+Here be termites. Don't use on wooden computers.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_recon.pm b/FS/FS/cust_recon.pm
new file mode 100644
index 0000000..0a1ca3a
--- /dev/null
+++ b/FS/FS/cust_recon.pm
@@ -0,0 +1,193 @@
+package FS::cust_recon;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cust_recon - Object methods for cust_recon records
+
+=head1 SYNOPSIS
+
+ use FS::cust_recon;
+
+ $record = new FS::cust_recon \%hash;
+ $record = new FS::cust_recon { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_recon object represents a customer reconcilation. FS::cust_recon
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item reconid
+
+primary key
+
+=item recondate
+
+recondate
+
+=item custnum
+
+custnum
+
+=item agentnum
+
+agentnum
+
+=item last
+
+last
+
+=item first
+
+first
+
+=item address1
+
+address1
+
+=item address2
+
+address2
+
+=item city
+
+city
+
+=item state
+
+state
+
+=item zip
+
+zip
+
+=item pkg
+
+pkg
+
+=item adjourn
+
+adjourn
+
+=item status
+
+status
+
+=item agent_custid
+
+agent_custid
+
+=item agent_pkg
+
+agent_pkg
+
+=item agent_adjourn
+
+agent_adjourn
+
+=item comments
+
+comments
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer reconcilation. To add the reconcilation 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_recon'; }
+
+=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 reconcilation. 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('reconid')
+ || $self->ut_numbern('recondate')
+ || $self->ut_number('custnum')
+ || $self->ut_number('agentnum')
+ || $self->ut_text('last')
+ || $self->ut_text('first')
+ || $self->ut_text('address1')
+ || $self->ut_textn('address2')
+ || $self->ut_text('city')
+ || $self->ut_textn('state')
+ || $self->ut_textn('zip')
+ || $self->ut_textn('pkg')
+ || $self->ut_numbern('adjourn')
+ || $self->ut_textn('status')
+ || $self->ut_text('agent_custid')
+ || $self->ut_textn('agent_pkg')
+ || $self->ut_numbern('agent_adjourn')
+ || $self->ut_textn('comments')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Possibly the existance of this module.
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..6d08c86
--- /dev/null
+++ b/FS/FS/cust_refund.pm
@@ -0,0 +1,388 @@
+package FS::cust_refund;
+
+use strict;
+use base qw( FS::otaker_Mixin FS::payinfo_transaction_Mixin FS::cust_main_Mixin
+ FS::Record );
+use vars qw( @encrypted_fields );
+use Business::CreditCard;
+use FS::UID qw(getotaker);
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_credit;
+use FS::cust_credit_refund;
+use FS::cust_pay_refund;
+use FS::cust_main;
+
+@encrypted_fields = ('payinfo');
+
+=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
+
+Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
+
+=item payinfo
+
+Payment Information (See L<FS::payinfo_Mixin> for data format)
+
+=item paymask
+
+Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
+
+=item paybatch
+
+text field for tracking card processing
+
+=item usernum
+
+order taker (see L<FS::access_user>
+
+=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
+
+Unless the closed flag is set, deletes this refund and all associated
+applications (see L<FS::cust_credit_refund> and L<FS::cust_pay_refund>).
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed refund" 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_refund ( $self->cust_credit_refund ) {
+ my $error = $cust_credit_refund->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $cust_pay_refund ( $self->cust_pay_refund ) {
+ my $error = $cust_pay_refund->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
+
+Modifying a refund? Well, don't say I didn't warn you.
+
+=cut
+
+sub replace {
+ my $self = shift;
+ $self->SUPER::replace(@_);
+}
+
+=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;
+
+ $self->otaker(getotaker) unless $self->otaker;
+
+ my $error =
+ $self->ut_numbern('refundnum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_money('refund')
+ || $self->ut_alphan('otaker')
+ || $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 } );
+
+ $error = $self->payinfo_check;
+ return $error if $error;
+
+ $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;
+ map { $_ } #return $self->num_cust_credit_refund unless wantarray;
+ 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;
+ map { $_ } #return $self->num_cust_pay_refund unless wantarray;
+ 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 );
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut
+
+sub unapplied_sql {
+ my ($class, $start, $end) = shift;
+ my $credit_start = $start ? "AND cust_credit_refund._date <= $start" : '';
+ my $credit_end = $end ? "AND cust_credit_refund._date > $end" : '';
+ my $pay_start = $start ? "AND cust_pay_refund._date <= $start" : '';
+ my $pay_end = $end ? "AND cust_pay_refund._date > $end" : '';
+
+ "refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum
+ $credit_start $credit_end )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum
+ $pay_start $pay_end )
+ ,0
+ )
+ ";
+
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+ $class->_upgrade_otaker(%opts);
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_credit>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_statement.pm b/FS/FS/cust_statement.pm
new file mode 100644
index 0000000..83dd5c1
--- /dev/null
+++ b/FS/FS/cust_statement.pm
@@ -0,0 +1,272 @@
+package FS::cust_statement;
+
+use strict;
+use base qw( FS::cust_bill );
+use FS::Record qw( dbh qsearch ); #qsearchs );
+use FS::cust_main;
+use FS::cust_bill;
+
+=head1 NAME
+
+FS::cust_statement - Object methods for cust_statement records
+
+=head1 SYNOPSIS
+
+ use FS::cust_statement;
+
+ $record = new FS::cust_statement \%hash;
+ $record = new FS::cust_statement { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_statement object represents an informational statement which
+aggregates one or more invoices. FS::cust_statement inherits from
+FS::cust_bill.
+
+The following fields are currently supported:
+
+=over 4
+
+=item statementnum
+
+primary key
+
+=item custnum
+
+customer
+
+=item _date
+
+date
+
+=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 new { FS::Record::new(@_); }
+
+sub table { 'cust_statement'; }
+
+=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;
+
+ FS::Record::insert($self);
+
+ foreach my $cust_bill (
+ qsearch({
+ 'table' => 'cust_bill',
+ 'hashref' => { 'custnum' => $self->custnum,
+ 'statementnum' => '',
+ },
+ 'extra_sql' => 'FOR UPDATE' ,
+ })
+ )
+ {
+ $cust_bill->statementnum( $self->statementnum );
+ my $error = $cust_bill->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error associating invoice: $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete { FS::Record::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
+
+sub replace { FS::Record::replace(@_); }
+
+sub replace_check { ''; }
+
+=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('statementnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum' )
+ || $self->ut_numbern('_date')
+ ;
+ return $error if $error;
+
+ $self->_date(time) unless $self->_date;
+
+ #don't want to call cust_bill, and Record just checks virtual fields
+ #$self->SUPER::check;
+ '';
+
+}
+
+=item cust_bill
+
+Returns the associated invoices (cust_bill records) for this statement.
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearch('cust_bill', { 'statementnum' => $self->statementnum } );
+}
+
+sub _aggregate {
+ my( $self, $method ) = ( shift, shift );
+
+ my @agg = ();
+
+ foreach my $cust_bill ( $self->cust_bill ) {
+ push @agg, $cust_bill->$method( @_ );
+ }
+
+ @agg;
+}
+
+sub _total {
+ my( $self, $method ) = ( shift, shift );
+
+ my $total = 0;
+
+ foreach my $cust_bill ( $self->cust_bill ) {
+ $total += $cust_bill->$method( @_ );
+ }
+
+ $total;
+}
+
+=item cust_bill_pkg
+
+Returns the line items (see L<FS::cust_bill_pkg>) for all associated invoices.
+
+=item cust_bill_pkg_pkgnum PKGNUM
+
+Returns the line items (see L<FS::cust_bill_pkg>) for all associated invoices
+and specified pkgnum.
+
+=item cust_bill_pay
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for all associated
+invoices.
+
+=item cust_credited
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for all associated
+invoices.
+
+=item cust_bill_pay_pkgnum PKGNUM
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for all associated
+invoices with matching pkgnum.
+
+=item cust_credited_pkgnum PKGNUM
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for all associated
+invoices with matching pkgnum.
+
+=cut
+
+sub cust_bill_pay { shift->_aggregate('cust_bill_pay', @_); }
+sub cust_credited { shift->_aggregate('cust_credited', @_); }
+sub cust_bill_pay_pkgnum { shift->_aggregate('cust_bill_pay_pkgnum', @_); }
+sub cust_credited_pkgnum { shift->_aggregate('cust_credited_pkgnum', @_); }
+
+sub cust_bill_pkg { shift->_aggregate('cust_bill_pkg', @_); }
+sub cust_bill_pkg_pkgnum { shift->_aggregate('cust_bill_pkg_pkgnum', @_); }
+
+=item tax
+
+Returns the total tax amount for all assoicated invoices.0
+
+=cut
+
+=item charged
+
+Returns the total amount charged for all associated invoices.
+
+=cut
+
+=item owed
+
+Returns the total amount owed for all associated invoices.
+
+=cut
+
+sub tax { shift->_total('tax', @_); }
+sub charged { shift->_total('charged', @_); }
+sub owed { shift->_total('owed', @_); }
+
+#don't show previous info
+sub previous {
+ ( 0 ); # 0, empty list
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill>, L<FS::Record>, 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..c0766e5
--- /dev/null
+++ b/FS/FS/cust_svc.pm
@@ -0,0 +1,735 @@
+package FS::cust_svc;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $ignore_quantity );
+use Carp;
+#use Scalar::Util qw( blessed );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh str2time_sql );
+use FS::cust_pkg;
+use FS::part_pkg;
+use FS::part_svc;
+use FS::pkg_svc;
+use FS::domain_record;
+use FS::part_export;
+use FS::cdr;
+
+#most FS::svc_ classes are autoloaded in svc_x emthod
+use FS::svc_acct; #this one is used in the cache stuff
+
+@ISA = qw( FS::cust_main_Mixin FS::option_Common ); #FS::Record );
+
+$DEBUG = 0;
+$me = '[cust_svc]';
+
+$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>)
+
+=item overlimit - date the service exceeded its usage limit
+
+=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; #this deletes this cust_svc record as well
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error deleting service: $error";
+ }
+
+ } else {
+
+ #huh?
+ warn "WARNING: no svc_ record found for svcnum ". $self->svcnum.
+ "; deleting cust_svc only\n";
+
+ 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 overlimit [ ACTION ]
+
+Retrieves or sets the overlimit date. If ACTION is absent, return
+the present value of overlimit. If ACTION is present, it can
+have the value 'suspend' or 'unsuspend'. In the case of 'suspend' overlimit
+is set to the current time if it is not already set. The 'unsuspend' value
+causes the time to be cleared.
+
+If there is an error on setting, returns the error, otherwise returns false.
+
+=cut
+
+sub overlimit {
+ my $self = shift;
+ my $action = shift or return $self->getfield('overlimit');
+
+ 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 ( $action eq 'suspend' ) {
+ $self->setfield('overlimit', time) unless $self->getfield('overlimit');
+ }elsif ( $action eq 'unsuspend' ) {
+ $self->setfield('overlimit', '');
+ }else{
+ die "unexpected action value: $action";
+ }
+
+ local $ignore_quantity = 1;
+ my $error = $self->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error setting overlimit: $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 = shift;
+#
+# my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+# ? shift
+# : $new->replace_old;
+ my ( $new, $old ) = ( shift, shift );
+ $old = $new->replace_old unless defined($old);
+
+ 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 });
+ local($FS::Record::nowarn_identical) = 1;
+ my $error = $new_svc_x->replace($svc_x);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+ }
+
+# #trigger a re-export on pkgnum changes?
+# # (of prepaid packages), for Expiration RADIUS attribute
+# if ( $new->pkgnum != $old->pkgnum && $new->cust_pkg->part_pkg->is_prepaid ) {
+# my $svc_x = $new->svc_x;
+# local($FS::Record::nowarn_identical) = 1;
+# my $error = $svc_x->export('replace');
+# if ( $error ) {
+# $dbh->rollback if $oldAutoCommit;
+# return $error if $error;
+# }
+# }
+
+ #my $error = $new->SUPER::replace($old, @_);
+ 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, otherwise 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')
+ || $self->ut_numbern('overlimit')
+ ;
+ 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 package this service belongs to, as a FS::cust_pkg object (see
+L<FS::cust_pkg>).
+
+=cut
+
+sub cust_pkg {
+ my $self = shift;
+ qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=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,
+ }
+ );
+}
+
+=item date_inserted
+
+Returns the date this service was inserted.
+
+=cut
+
+sub date_inserted {
+ my $self = shift;
+ $self->h_date('insert');
+}
+
+=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
+- svcnum
+
+Usage example:
+
+ my($label, $value, $svcdb) = $cust_svc->label;
+
+=item label_long
+
+Like the B<label> method, except the second item in the list ("meaningful
+identifier") may be longer - typically, a full name is included.
+
+=cut
+
+sub label { shift->_label('svc_label', @_); }
+sub label_long { shift->_label('svc_label_long', @_); }
+
+sub _label {
+ my $self = shift;
+ my $method = shift;
+ my $svc_x = $self->svc_x
+ or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
+
+ $self->$method($svc_x);
+}
+
+sub svc_label { shift->_svc_label('label', @_); }
+sub svc_label_long { shift->_svc_label('label_long', @_); }
+
+sub _svc_label {
+ my( $self, $method, $svc_x ) = ( shift, shift, shift );
+
+ (
+ $self->part_svc->svc,
+ $svc_x->$method(@_),
+ $self->part_svc->svcdb,
+ $self->svcnum
+ );
+
+}
+
+=item export_links
+
+Returns a listref of html elements associated with this service's exports.
+
+=cut
+
+sub export_links {
+ my $self = shift;
+ my $svc_x = $self->svc_x
+ or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
+
+ $svc_x->export_links;
+}
+
+=item export_getsettings
+
+Returns two hashrefs of settings associated with this service's exports.
+
+=cut
+
+sub export_getsettings {
+ my $self = shift;
+ my $svc_x = $self->svc_x
+ or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
+
+ $svc_x->export_getsettings;
+}
+
+
+=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";
+ warn "$me svc_x: part_svc.svcpart ". $self->part_svc->svcpart.
+ ", so searching for $svcdb.svcnum ". $self->svcnum. "\n"
+ if $DEBUG;
+ 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 $mes = "$me seconds_since_sqlradacct:";
+
+ my $svc_x = $self->svc_x;
+
+ my @part_export = $self->part_svc->part_export_usage;
+ die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
+ " service definition"
+ unless @part_export;
+ #or return undef;
+
+ my $seconds = 0;
+ foreach my $part_export ( @part_export ) {
+
+ next if $part_export->option('ignore_accounting');
+
+ warn "$mes connecting to sqlradius database\n"
+ if $DEBUG;
+
+ my $dbh = DBI->connect( map { $part_export->option($_) }
+ qw(datasrc username password) )
+ or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+ warn "$mes connected to sqlradius database\n"
+ if $DEBUG;
+
+ #select a unix time conversion function based on database type
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+
+ my $username = $part_export->export_username($svc_x);
+
+ my $query;
+
+ warn "$mes finding closed sessions completely within the given range\n"
+ if $DEBUG;
+
+ 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];
+
+ warn "$mes finding open sessions which start in the range\n"
+ if $DEBUG;
+
+ # 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];
+
+ warn "$mes finding closed sessions which start before the range but stop during\n"
+ if $DEBUG;
+
+ #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];
+
+ warn "$mes finding closed sessions which start before the range but stop after\n"
+ if $DEBUG;
+
+ # count range start->range end
+ # don't count open sessions anymore (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;
+
+ warn "$mes done finding sessions\n"
+ if $DEBUG;
+
+ }
+
+ $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 $mes = "$me attribute_since_sqlradacct:";
+
+ my $svc_x = $self->svc_x;
+
+ my @part_export = $self->part_svc->part_export_usage;
+ die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
+ " service definition"
+ unless @part_export;
+ #or return undef;
+
+ my $sum = 0;
+
+ foreach my $part_export ( @part_export ) {
+
+ next if $part_export->option('ignore_accounting');
+
+ warn "$mes connecting to sqlradius database\n"
+ if $DEBUG;
+
+ my $dbh = DBI->connect( map { $part_export->option($_) }
+ qw(datasrc username password) )
+ or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+ warn "$mes connected to sqlradius database\n"
+ if $DEBUG;
+
+ #select a unix time conversion function based on database type
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+
+ my $username = $part_export->export_username($svc_x);
+
+ warn "$mes SUMing $attrib sessions\n"
+ if $DEBUG;
+
+ 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;
+
+ my $row = $sth->fetchrow_arrayref;
+ $sum += $row->[0] if defined($row->[0]);
+
+ warn "$mes done SUMing sessions\n"
+ if $DEBUG;
+
+ }
+
+ $sum;
+
+}
+
+=item get_session_history TIMESTAMP_START TIMESTAMP_END
+
+See L<FS::svc_acct/get_session_history>. Equivalent to
+$cust_svc->svc_x->get_session_history, 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_usage;
+ die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
+ " service definition"
+ unless @part_export;
+ #or return undef;
+
+ my @sessions = ();
+
+ foreach my $part_export ( @part_export ) {
+ push @sessions,
+ @{ $part_export->usage_sessions( $start, $end, $self->svc_x ) };
+ }
+
+ @sessions;
+
+}
+
+=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_svc_option.pm b/FS/FS/cust_svc_option.pm
new file mode 100644
index 0000000..07fec90
--- /dev/null
+++ b/FS/FS/cust_svc_option.pm
@@ -0,0 +1,134 @@
+package FS::cust_svc_option;
+
+use strict;
+use vars qw( @ISA );
+#use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_svc_option - Object methods for cust_svc_option records
+
+=head1 SYNOPSIS
+
+ use FS::cust_svc_option;
+
+ $record = new FS::cust_svc_option \%hash;
+ $record = new FS::cust_svc_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_svc_option object represents an customer service option.
+ FS::cust_svc_option inherits from FS::Record. The following fields are
+ currently supported:
+
+=over 4
+
+=item optionnum
+
+primary key
+
+=item svcnum
+
+svcnum (see L<FS::cust_svc>)
+
+=item optionname
+
+Option Name
+
+=item optionvalue
+
+Option Value
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new option. To add the 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 { 'cust_svc_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 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_foreign_key('svcnum', 'cust_svc', 'svcnum')
+ || $self->ut_alpha('optionname')
+ || $self->ut_anything('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_tax_adjustment.pm b/FS/FS/cust_tax_adjustment.pm
new file mode 100644
index 0000000..5891368
--- /dev/null
+++ b/FS/FS/cust_tax_adjustment.pm
@@ -0,0 +1,149 @@
+package FS::cust_tax_adjustment;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+use FS::cust_bill_pkg;
+
+=head1 NAME
+
+FS::cust_tax_adjustment - Object methods for cust_tax_adjustment records
+
+=head1 SYNOPSIS
+
+ use FS::cust_tax_adjustment;
+
+ $record = new FS::cust_tax_adjustment \%hash;
+ $record = new FS::cust_tax_adjustment { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_tax_adjustment object represents an taxation adjustment.
+FS::cust_tax_adjustment inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item adjustmentnum
+
+primary key
+
+=item custnum
+
+custnum
+
+=item taxname
+
+taxname
+
+=item amount
+
+amount
+
+=item comment
+
+comment
+
+=item billpkgnum
+
+billpkgnum
+
+
+=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 { 'cust_tax_adjustment'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('adjustmentnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum' )
+ || $self->ut_text('taxname')
+ || $self->ut_money('amount')
+ || $self->ut_textn('comment')
+ || $self->ut_foreign_keyn('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..045421c
--- /dev/null
+++ b/FS/FS/cust_tax_exempt.pm
@@ -0,0 +1,152 @@
+package FS::cust_tax_exempt;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_Mixin;
+use FS::cust_main;
+use FS::cust_main_county;
+
+@ISA = qw( FS::cust_main_Mixin 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 record of an old-style 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 NOTE
+
+Old-style customer tax exemptions are only useful for legacy migrations - if
+you are looking for current customer tax exemption data see
+L<FS::cust_tax_exempt_pkg>.
+
+=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
+ ;
+}
+
+=item cust_main_county
+
+Returns the FS::cust_main_county object associated with this tax exemption.
+
+=cut
+
+sub cust_main_county {
+ my $self = shift;
+ qsearchs( 'cust_main_county', { 'taxnum' => $self->taxnum } );
+}
+
+=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/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm
new file mode 100644
index 0000000..e63b84b
--- /dev/null
+++ b/FS/FS/cust_tax_exempt_pkg.pm
@@ -0,0 +1,152 @@
+package FS::cust_tax_exempt_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_Mixin;
+use FS::cust_bill_pkg;
+use FS::cust_main_county;
+use FS::cust_credit_bill_pkg;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+=head1 NAME
+
+FS::cust_tax_exempt_pkg - Object methods for cust_tax_exempt_pkg records
+
+=head1 SYNOPSIS
+
+ use FS::cust_tax_exempt_pkg;
+
+ $record = new FS::cust_tax_exempt_pkg \%hash;
+ $record = new FS::cust_tax_exempt_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_tax_exempt_pkg object represents a 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 exemptpkgnum - primary key
+
+=item billpkgnum - invoice line item (see L<FS::cust_bill_pkg>)
+
+=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 examption 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 { 'cust_tax_exempt_pkg'; }
+
+=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 exemption 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('exemptnum')
+# || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum')
+ || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum')
+ || $self->ut_foreign_keyn('creditbillpkgnum',
+ 'cust_credit_bill_pkg',
+ 'creditbillpkgnum')
+ || $self->ut_number('year') #check better
+ || $self->ut_number('month') #check better
+ || $self->ut_money('amount')
+ || $self->SUPER::check
+ ;
+}
+
+=item cust_main_county
+
+Returns the associated tax definition if it still exists in the database.
+Otherwise returns false.
+
+=cut
+
+sub cust_main_county {
+ my $self = shift;
+ qsearchs( 'cust_main_county', { 'taxnum', $self->taxnum } );
+}
+
+=back
+
+=head1 BUGS
+
+Texas tax is still a royal pain in the ass.
+
+=head1 SEE ALSO
+
+L<FS::cust_main_county>, L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from
+the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_tax_location.pm b/FS/FS/cust_tax_location.pm
new file mode 100644
index 0000000..161a654
--- /dev/null
+++ b/FS/FS/cust_tax_location.pm
@@ -0,0 +1,344 @@
+package FS::cust_tax_location;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::Misc qw ( csv_from_fixed );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_tax_location - Object methods for cust_tax_location records
+
+=head1 SYNOPSIS
+
+ use FS::cust_tax_location;
+
+ $record = new FS::cust_tax_location \%hash;
+ $record = new FS::cust_tax_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_tax_location object represents a mapping between a customer and
+a tax location. FS::cust_tax_location inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item custlocationnum
+
+primary key
+
+=item data_vendor
+
+a tax data vendor
+
+=item zip
+
+=item state
+
+=item plus4hi
+
+the upper bound of the last 4 zip code digits
+
+=item plus4lo
+
+the lower bound of the last 4 zip code digits
+
+=item default_location
+
+'Y' when this record represents the default for zip
+
+=item geocode - the foreign key into FS::part_pkg_tax_rate and FS::tax_rate
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cust_tax_location. To add the cust_tax_location 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_tax_location'; }
+
+=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 cust_tax_location. 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('custlocationnum')
+ || $self->ut_text('data_vendor')
+ || $self->ut_textn('city')
+ || $self->ut_textn('postalcity')
+ || $self->ut_textn('county')
+ || $self->ut_text('state')
+ || $self->ut_numbern('plus4hi')
+ || $self->ut_numbern('plus4lo')
+ || $self->ut_enum('default_location', [ '', 'Y' ] )
+ || $self->ut_enum('cityflag', [ '', 'I', 'O', 'B' ] )
+ || $self->ut_alpha('geocode')
+ ;
+ return $error if $error;
+
+ #ugh! cch canada weirdness and more
+ if ($self->state eq 'CN' && $self->data_vendor eq 'cch-zip' ) {
+ $error = "Illegal cch canadian zip"
+ unless $self->zip =~ /^[A-Z]$/;
+ } elsif ($self->state =~ /^E([B-DFGILNPR-UW])$/ && $self->data_vendor eq 'cch-zip' ) {
+ $error = "Illegal cch european zip"
+ unless $self->zip =~ /^E$1$/;
+ } else {
+ $error = $self->ut_number('zip', $self->state eq 'CN' ? 'CA' : 'US');
+ }
+ return $error if $error;
+
+ #ugh! cch canada weirdness and more
+ return "must specify either city/county or plus4lo/plus4hi"
+ unless ( $self->plus4lo && $self->plus4hi ||
+ ( $self->city ||
+ $self->state eq 'CN' ||
+ $self->state =~ /^E([B-DFGILNPR-UW])$/
+ ) && $self->county
+ );
+
+ $self->SUPER::check;
+}
+
+
+sub batch_import {
+ my ($param, $job) = @_;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my $imported = 0;
+ my @fields;
+ my $hook;
+
+ my @column_lengths = ();
+ my @column_callbacks = ();
+ if ( $format =~ /^cch-fixed/ ) {
+ $format =~ s/-fixed//;
+ my $f = $format;
+ my $update = 0;
+ $f =~ s/-update// && ($update = 1);
+ if ($f eq 'cch') {
+ push @column_lengths, qw( 5 2 4 4 10 1 );
+ } elsif ( $f eq 'cch-zip' ) {
+ push @column_lengths, qw( 5 28 25 2 28 5 1 1 10 1 2 );
+ } else {
+ return "Unknown format: $format";
+ }
+ push @column_lengths, 1 if $update;
+ }
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job || scalar(@column_lengths) ) {
+ my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
+ return $error if $error;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
+ @fields = qw( zip state plus4lo plus4hi geocode default_location );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $imported++ if $format eq 'cch-update'; #empty file ok
+
+ $hook = sub {
+ my $hash = shift;
+
+ $hash->{'data_vendor'} = 'cch';
+ $hash->{'default_location'} =~ s/ //g;
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ delete($hash->{actionflag});
+
+ my $cust_tax_location = qsearchs('cust_tax_location', $hash);
+ return "Can't find cust_tax_location to delete: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ unless $cust_tax_location;
+
+ my $error = $cust_tax_location->delete;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{'actionflag'});
+
+ '';
+
+ };
+
+ } elsif ( $format eq 'cch-zip' || $format eq 'cch-update-zip' ) {
+ @fields = qw( zip city county state postalcity countyfips countydef default_location geocode cityflag unique );
+ push @fields, 'actionflag' if $format eq 'cch-update-zip';
+
+ $imported++ if $format eq 'cch-update'; #empty file ok
+
+ $hook = sub {
+ my $hash = shift;
+
+ $hash->{'data_vendor'} = 'cch-zip';
+ delete($hash->{$_}) foreach qw( countyfips countydef unique );
+
+ $hash->{'cityflag'} =~ s/ //g;
+ $hash->{'default_location'} =~ s/ //g;
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ delete($hash->{actionflag});
+
+ my $cust_tax_location = qsearchs('cust_tax_location', $hash);
+ return "Can't find cust_tax_location to delete: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ unless $cust_tax_location;
+
+ my $error = $cust_tax_location->delete;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{'actionflag'});
+
+ '';
+
+ };
+
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ 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;
+
+ while ( defined($line=<$fh>) ) {
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing locations"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my @columns = $csv->fields();
+
+ my %cust_tax_location = ( 'data_vendor' => $format );;
+ foreach my $field ( @fields ) {
+ $cust_tax_location{$field} = shift @columns;
+ }
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%cust_tax_location);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ next unless scalar(keys %cust_tax_location);
+
+ my $cust_tax_location = new FS::cust_tax_location( \%cust_tax_location );
+ $error = $cust_tax_location->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert cust_tax_location for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless ( $imported || $format =~ /^cch-update/ );
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+The author should be informed of any you find.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/discount.pm b/FS/FS/discount.pm
new file mode 100644
index 0000000..8afeb2e
--- /dev/null
+++ b/FS/FS/discount.pm
@@ -0,0 +1,182 @@
+package FS::discount;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::discount - Object methods for discount records
+
+=head1 SYNOPSIS
+
+ use FS::discount;
+
+ $record = new FS::discount \%hash;
+ $record = new FS::discount { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::discount object represents a discount definition. FS::discount inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item discountnum
+
+primary key
+
+=item name
+
+name
+
+=item amount
+
+amount
+
+=item percent
+
+percent
+
+=item months
+
+months
+
+=item disabled
+
+disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new discount. To add the discount 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 { 'discount'; }
+
+=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 discount. 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;
+
+ if ( $self->_type eq 'Select discount type' ) {
+ return 'Please select a discount type';
+ } elsif ( $self->_type eq 'Amount' ) {
+ $self->percent('0');
+ return 'Amount must be greater than 0' unless $self->amount > 0;
+ } elsif ( $self->_type eq 'Percentage' ) {
+ $self->amount('0.00');
+ return 'Percentage must be greater than 0' unless $self->percent > 0;
+ }
+
+ my $error =
+ $self->ut_numbern('discountnum')
+ || $self->ut_textn('name')
+ || $self->ut_money('amount')
+ || $self->ut_float('percent') #actually decimal, but this will do
+ || $self->ut_floatn('months') #actually decimal, but this will do
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item description_short
+
+=item description
+
+Returns a text description incorporating the amount, percent and months fields.
+
+description_short omits term information
+
+=cut
+
+sub description_short {
+ my $self = shift;
+
+ my $conf = new FS::Conf;
+ my $money_char = $conf->config('money_char') || '$';
+
+ my $desc = $self->name ? $self->name.': ' : '';
+ $desc .= $money_char. sprintf('%.2f/month ', $self->amount)
+ if $self->amount > 0;
+ $desc .= $self->percent. '% '
+ if $self->percent > 0;
+
+ $desc;
+}
+
+sub description {
+ my $self = shift;
+ my $desc = $self->description_short;
+ $desc .= ' for '. $self->months. ' months' if $self->months;
+ $desc;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pkg_discount>, 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..6513abf
--- /dev/null
+++ b/FS/FS/domain_record.pm
@@ -0,0 +1,438 @@
+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 = 0;
+
+=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 entry 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 entry. 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 } );
+
+ my $conf = new FS::Conf;
+
+ if ( $conf->exists('zone-underscore') ) {
+ $self->reczone =~ /^(@|[a-z0-9_\.\-\*]+)$/i
+ or return "Illegal reczone: ". $self->reczone;
+ $self->reczone($1);
+ } else {
+ $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|TXT|_mstr)$/
+ or return "Illegal rectype (only SOA NS MX A PTR CNAME TXT 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' ) {
+ if ( $conf->exists('zone-underscore') ) {
+ $self->recdata =~ /^([a-z0-9_\.\-]+)$/i
+ or return "Illegal data for PTR record: ". $self->recdata;
+ $self->recdata($1);
+ } else {
+ $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 'TXT' ) {
+ if ( $self->recdata =~ /^((?:\S+)|(?:".+"))$/ ) {
+ $self->recdata($1);
+ } else {
+ $self->recdata('"'. $self->recdata. '"'); #?
+ }
+ # or return "Illegal data for TXT record: ". $self->recdata;
+ } 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_device.pm b/FS/FS/export_device.pm
new file mode 100644
index 0000000..69e3826
--- /dev/null
+++ b/FS/FS/export_device.pm
@@ -0,0 +1,136 @@
+package FS::export_device;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::part_export;
+use FS::part_device;
+
+=head1 NAME
+
+FS::export_device - Object methods for export_device records
+
+=head1 SYNOPSIS
+
+ use FS::export_device;
+
+ $record = new FS::export_device \%hash;
+ $record = new FS::export_device { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::export_device object links a device definition (see L<FS::part_device>)
+to an export (see L<FS::part_export>). FS::export_device inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item exportdevicenum - primary key
+
+=item exportnum - export (see L<FS::part_export>)
+
+=item devicepart - device definition (see L<FS::part_device>)
+
+=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 { 'export_device'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# may want to check for duplicates against either services or devices
+# cf FS::export_svc
+
+=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 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('exportdevicenum')
+ || $self->ut_number('exportnum')
+ || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
+ || $self->ut_number('devicepart')
+ || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
+ || $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_device
+
+Returns the FS::part_device object (see L<FS::part_device>).
+
+=cut
+
+sub part_device {
+ my $self = shift;
+ qsearchs( 'part_device', { 'svcpart' => $self->devicepart } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_export>, L<FS::part_device>, 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..0370f5f
--- /dev/null
+++ b/FS/FS/export_svc.pm
@@ -0,0 +1,322 @@
+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 [ JOB, OFFSET, MULTIPLIER ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+TODOC: JOB, OFFSET, MULTIPLIER
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my( $job, $offset, $mult ) = ( '', 0, 100);
+ $job = shift if @_;
+ $offset = shift if @_;
+ $mult = shift if @_;
+
+ 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->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";
+ }
+
+ if ( @checks ) {
+
+ my $done = 0;
+ my $percheck = $mult / scalar(@checks);
+
+ foreach my $check ( @checks ) {
+
+ if ( $job ) {
+ $error = $job->update_statustext(int( $offset + ($done+.33) *$percheck ));
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my @current_svc = $self->part_export->svc_x;
+ #warn "current: ". scalar(@current_svc). " $current_svc[0]\n";
+
+ if ( $job ) {
+ $error = $job->update_statustext(int( $offset + ($done+.67) *$percheck ));
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my @new_svc = $self->part_svc->svc_x;
+ #warn "new: ". scalar(@new_svc). " $new_svc[0]\n";
+
+ if ( $job ) {
+ $error = $job->update_statustext(int( $offset + ($done+1) *$percheck ));
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ 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 )
+ ;
+ }
+
+ $done++;
+ }
+
+ } #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/h_Common.pm b/FS/FS/h_Common.pm
new file mode 100644
index 0000000..ca13e1b
--- /dev/null
+++ b/FS/FS/h_Common.pm
@@ -0,0 +1,124 @@
+package FS::h_Common;
+
+use strict;
+use FS::Record qw(dbdef);
+use Carp qw(confess);
+
+=head1 NAME
+
+FS::h_Common - History table "mixin" common base class
+
+=head1 SYNOPSIS
+
+package FS::h_tablename;
+@ISA = qw( FS::h_Common FS::tablename );
+
+sub table { 'h_table_name'; }
+
+sub insert { return "can't insert history records manually"; }
+sub delete { return "can't delete history records"; }
+sub replace { return "can't modify history records"; }
+
+=head1 DESCRIPTION
+
+FS::h_Common is intended as a "mixin" base class for history table classes to
+inherit from.
+
+=head1 METHODS
+
+=over 4
+
+=item sql_h_search END_TIMESTAMP [ START_TIMESTAMP ]
+
+Returns an a list consisting of the "SELECT", "EXTRA_SQL", SQL fragments, a
+placeholder for "CACHE_OBJ" and an "AS" SQL fragment, to search for the
+appropriate history records created before END_TIMESTAMP and (optionally) not
+deleted before START_TIMESTAMP.
+
+=cut
+
+sub sql_h_search {
+ my( $self, $end ) = ( shift, shift );
+
+ my $table = $self->table;
+ my $real_table = ($table =~ /^h_(.*)$/) ? $1 : $table;
+ my $pkey = dbdef->table($real_table)->primary_key
+ or die "can't (yet) search history table $real_table without a primary key";
+
+ unless ($end) {
+ confess 'Called sql_h_search without END_TIMESTAMP';
+ }
+
+ my( $notdeleted, $notdeleted_mr ) = ( '', '' );
+ if ( scalar(@_) && $_[0] ) {
+ $notdeleted =
+ "AND 0 = ( SELECT COUNT(*) FROM $table as notdel
+ WHERE notdel.$pkey = maintable.$pkey
+ AND notdel.history_action = 'delete'
+ AND notdel.history_date > maintable.history_date
+ AND notdel.history_date <= $_[0]
+ )";
+ $notdeleted_mr =
+ "AND 0 = ( SELECT COUNT(*) FROM $table as notdel_mr
+ WHERE notdel_mr.$pkey = mostrecent.$pkey
+ AND notdel_mr.history_action = 'delete'
+ AND notdel_mr.history_date > mostrecent.history_date
+ AND notdel_mr.history_date <= $_[0]
+ )";
+ }
+
+ (
+ #"DISTINCT ON ( $pkey ) *",
+ "*",
+
+ "AND history_date <= $end
+ AND ( history_action = 'insert'
+ OR history_action = 'replace_new'
+ )
+ $notdeleted
+ AND history_date = ( SELECT MAX(mostrecent.history_date)
+ FROM $table AS mostrecent
+ WHERE mostrecent.$pkey = maintable.$pkey
+ AND mostrecent.history_date <= $end
+ AND ( mostrecent.history_action = 'insert'
+ OR mostrecent.history_action = 'replace_new'
+ )
+ $notdeleted_mr
+ )
+
+ ORDER BY $pkey ASC",
+ #ORDER BY $pkey ASC, history_date DESC",
+
+ '',
+
+ 'AS maintable',
+ );
+
+}
+
+=item sql_h_searchs END_TIMESTAMP [ START_TIMESTAMP ]
+
+Like sql_h_search, but limited to the single most recent record (before
+END_TIMESTAMP)
+
+=cut
+
+sub sql_h_searchs {
+ my $self = shift;
+ my($select, $where, $cacheobj, $as) = $self->sql_h_search(@_);
+ $where .= ' LIMIT 1';
+ ($select, $where, $cacheobj, $as);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_cust_bill.pm b/FS/FS/h_cust_bill.pm
new file mode 100644
index 0000000..7a3d811
--- /dev/null
+++ b/FS/FS/h_cust_bill.pm
@@ -0,0 +1,33 @@
+package FS::h_cust_bill;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_bill;
+
+@ISA = qw( FS::h_Common FS::cust_bill );
+
+sub table { 'h_cust_bill' };
+
+=head1 NAME
+
+FS::h_cust_bill - Historical record of customer tax changes (old-style)
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_bill object represents historical changes to invoices.
+FS::h_cust_bill inherits from FS::h_Common and FS::cust_bill.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_cust_credit.pm b/FS/FS/h_cust_credit.pm
new file mode 100644
index 0000000..1425a26
--- /dev/null
+++ b/FS/FS/h_cust_credit.pm
@@ -0,0 +1,33 @@
+package FS::h_cust_credit;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_credit;
+
+@ISA = qw( FS::h_Common FS::cust_credit );
+
+sub table { 'h_cust_credit' };
+
+=head1 NAME
+
+FS::h_cust_credit - Historical record of customer credit changes
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_credit object represents historical changes to credits.
+FS::h_cust_credit inherits from FS::h_Common and FS::cust_credit.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_credit>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_cust_pay.pm b/FS/FS/h_cust_pay.pm
new file mode 100644
index 0000000..6434b3f
--- /dev/null
+++ b/FS/FS/h_cust_pay.pm
@@ -0,0 +1,33 @@
+package FS::h_cust_pay;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_pay;
+
+@ISA = qw( FS::h_Common FS::cust_pay );
+
+sub table { 'h_cust_pay' };
+
+=head1 NAME
+
+FS::h_cust_pay - Historical record of customer payment changes
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_pay object represents historical changes to payments.
+FS::h_cust_pay inherits from FS::h_Common and FS::cust_pay.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pay>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_cust_pkg.pm b/FS/FS/h_cust_pkg.pm
new file mode 100644
index 0000000..e796f41
--- /dev/null
+++ b/FS/FS/h_cust_pkg.pm
@@ -0,0 +1,34 @@
+package FS::h_cust_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_pkg;
+
+@ISA = qw( FS::h_Common FS::cust_pkg );
+
+sub table { 'h_cust_pkg' };
+
+=head1 NAME
+
+FS::h_cust_pkg - Historical record of customer package changes
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_pkg object represents historical changes to packages.
+FS::h_cust_pkg inherits from FS::h_Common and FS::cust_pkg.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pkg>, L<FS::h_Common>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/h_cust_pkg_reason.pm b/FS/FS/h_cust_pkg_reason.pm
new file mode 100644
index 0000000..dda2009
--- /dev/null
+++ b/FS/FS/h_cust_pkg_reason.pm
@@ -0,0 +1,34 @@
+package FS::h_cust_pkg_reason;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_pkg_reason;
+
+@ISA = qw( FS::h_Common FS::cust_pkg_reason );
+
+sub table { 'h_cust_pkg_reason' };
+
+=head1 NAME
+
+FS::h_cust_pkg_reason - Historical record of customer package changes
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_pkg_reason object represents historical changes to packages.
+FS::h_cust_pkg_reason inherits from FS::h_Common and FS::cust_pkg_reason.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pkg_reason>, L<FS::h_Common>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/h_cust_svc.pm b/FS/FS/h_cust_svc.pm
new file mode 100644
index 0000000..d280d53
--- /dev/null
+++ b/FS/FS/h_cust_svc.pm
@@ -0,0 +1,165 @@
+package FS::h_cust_svc;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp;
+use FS::Record qw(qsearchs);
+use FS::h_Common;
+use FS::cust_svc;
+
+@ISA = qw( FS::h_Common FS::cust_svc );
+
+$DEBUG = 0;
+
+sub table { 'h_cust_svc'; }
+
+=head1 NAME
+
+FS::h_cust_svc - Object method for h_cust_svc objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_svc object represents a historical service. FS::h_cust_svc
+inherits from FS::h_Common and FS::cust_svc.
+
+=head1 METHODS
+
+=over 4
+
+=item date_deleted
+
+Returns the date this service was deleted, if any.
+
+=cut
+
+sub date_deleted {
+ my $self = shift;
+ $self->h_date('delete');
+}
+
+=item label END_TIMESTAMP [ START_TIMESTAMP ]
+
+Returns a label for this historical service, if the service was created before
+END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP. Otherwise,
+returns an empty list.
+
+If a service is found, returns a list consisting of:
+- The name of this historical service (from part_svc)
+- A meaningful identifier (username, domain, or mail alias)
+- The table name (i.e. svc_domain) for this historical service
+
+=cut
+
+sub label { shift->_label('svc_label', @_); }
+sub label_long { shift->_label('svc_label_long', @_); }
+
+sub _label {
+ my $self = shift;
+ my $method = shift;
+
+ #carp "FS::h_cust_svc::_label called on $self" if $DEBUG;
+ warn "FS::h_cust_svc::_label called on $self for $method" if $DEBUG;
+ my $svc_x = $self->h_svc_x(@_);
+ return () unless $svc_x;
+ my $part_svc = $self->part_svc;
+
+ unless ($svc_x) {
+ carp "can't find h_". $self->part_svc->svcdb. '.svcnum '. $self->svcnum if $DEBUG;
+ return $part_svc->svc, 'n/a', $part_svc->svcdb;
+ }
+
+ my @label;
+ eval { @label = $self->$method($svc_x, @_); };
+
+ if ($@) {
+ carp 'while resolving history record for svcdb/svcnum ' .
+ $part_svc->svcdb . '/' . $self->svcnum . ': ' . $@ if $DEBUG;
+ return $part_svc->svc, 'n/a', $part_svc->svcdb;
+ } else {
+ return @label;
+ }
+
+}
+
+=item h_svc_x END_TIMESTAMP [ START_TIMESTAMP ]
+
+Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e. an
+FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally) not
+cancelled before START_TIMESTAMP.
+
+=cut
+
+#false laziness w/cust_pkg::h_cust_svc
+sub h_svc_x {
+ my $self = shift;
+ my $svcdb = $self->part_svc->svcdb;
+
+ warn "requiring FS/h_$svcdb.pm" if $DEBUG;
+ require "FS/h_$svcdb.pm";
+ my $svc_x = qsearchs(
+ "h_$svcdb",
+ { 'svcnum' => $self->svcnum, },
+ "FS::h_$svcdb"->sql_h_searchs(@_),
+ ) || $self->SUPER::svc_x;
+
+ if ($svc_x) {
+ carp "Using $svcdb in place of missing h_${svcdb} record."
+ if ($svc_x->isa('FS::' . $svcdb) and $DEBUG);
+ return $svc_x;
+ } else {
+ return '';
+ }
+
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+use FS::UID qw( driver_name dbh );
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ warn "[FS::h_cust_svc] upgrading $class\n" if $DEBUG;
+
+ return if driver_name =~ /^mysql/; #You can't specify target table 'h_cust_svc' for update in FROM clause
+
+ my $sql = "
+ DELETE FROM h_cust_svc
+ WHERE history_action = 'delete'
+ AND historynum != ( SELECT min(historynum) FROM h_cust_svc AS main
+ WHERE main.history_date = h_cust_svc.history_date
+ AND main.history_user = h_cust_svc.history_user
+ AND main.svcnum = h_cust_svc.svcnum
+ AND main.svcpart = h_cust_svc.svcpart
+ AND ( main.pkgnum = h_cust_svc.pkgnum
+ OR ( main.pkgnum IS NULL AND h_cust_svc.pkgnum IS NULL )
+ )
+ AND ( main.overlimit = h_cust_svc.overlimit
+ OR ( main.overlimit IS NULL AND h_cust_svc.overlimit IS NULL )
+ )
+ )
+ ";
+
+ warn $sql if $DEBUG;
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::cust_svc>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_cust_tax_exempt.pm b/FS/FS/h_cust_tax_exempt.pm
new file mode 100644
index 0000000..9d2318b
--- /dev/null
+++ b/FS/FS/h_cust_tax_exempt.pm
@@ -0,0 +1,40 @@
+package FS::h_cust_tax_exempt;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::cust_tax_exempt;
+
+@ISA = qw( FS::h_Common FS::cust_tax_exempt );
+
+sub table { 'h_cust_tax_exempt' };
+
+=head1 NAME
+
+FS::h_cust_tax_exempt - Historical record of customer tax changes (old-style)
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_cust_tax_exempt object represents historical changes to old-style
+customer tax exemptions. FS::h_cust_tax_exempt inherits from FS::h_Common and
+FS::cust_tax_exempt.
+
+=head1 NOTE
+
+Old-style customer tax exemptions are only useful for legacy migrations - if
+you are looking for current customer tax exemption data see
+L<FS::cust_tax_exempt_pkg>.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_tax_exempt>, L<FS::cust_tax_exempt_pkg>, L<FS::h_Common>,
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_domain_record.pm b/FS/FS/h_domain_record.pm
new file mode 100644
index 0000000..0ab974f
--- /dev/null
+++ b/FS/FS/h_domain_record.pm
@@ -0,0 +1,33 @@
+package FS::h_domain_record;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::domain_record;
+
+@ISA = qw( FS::h_Common FS::domain_record );
+
+sub table { 'h_domain_record' };
+
+=head1 NAME
+
+FS::h_domain_record - Historical DNS entry objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_domain_record object represents a historical entry in a DNS zone.
+FS::h_domain_record inherits from FS::h_Common and FS::domain_record.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_external>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_acct.pm b/FS/FS/h_svc_acct.pm
new file mode 100644
index 0000000..247d20c
--- /dev/null
+++ b/FS/FS/h_svc_acct.pm
@@ -0,0 +1,78 @@
+package FS::h_svc_acct;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp qw(carp);
+use FS::Record qw(qsearchs);
+use FS::h_Common;
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::h_svc_domain;
+
+@ISA = qw( FS::h_Common FS::svc_acct );
+
+$DEBUG = 0;
+
+sub table { 'h_svc_acct' };
+
+=head1 NAME
+
+FS::h_svc_acct - Historical account objects
+
+=head1 SYNOPSIS
+
+=head1 METHODS
+
+=over 4
+
+=item svc_domain
+
+=cut
+
+sub svc_domain {
+ my $self = shift;
+ qsearchs( 'h_svc_domain',
+ { 'svcnum' => $self->domsvc },
+ FS::h_svc_domain->sql_h_searchs(@_),
+ );
+}
+
+=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(@_) || $self->SUPER::svc_domain()
+ or die 'no history svc_domain.svcnum for svc_acct.domsvc ' . $self->domsvc;
+
+ carp 'Using FS::svc_acct record in place of missing FS::h_svc_acct record.'
+ if ($svc_domain->isa('FS::svc_acct') and $DEBUG);
+
+ $svc_domain->domain;
+
+}
+
+
+=back
+
+=head1 DESCRIPTION
+
+An FS::h_svc_acct object represents a historical account. FS::h_svc_acct
+inherits from FS::h_Common and FS::svc_acct.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_acct>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_broadband.pm b/FS/FS/h_svc_broadband.pm
new file mode 100644
index 0000000..d6038fb
--- /dev/null
+++ b/FS/FS/h_svc_broadband.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_broadband;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_broadband;
+
+@ISA = qw( FS::h_Common FS::svc_broadband );
+
+sub table { 'h_svc_broadband' };
+
+=head1 NAME
+
+FS::h_svc_broadband - Historical broadband connection objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_broadband object represents a historical broadband connection.
+FS::h_svc_broadband inherits from FS::h_Common and FS::svc_broadband.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_broadband>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_domain.pm b/FS/FS/h_svc_domain.pm
new file mode 100644
index 0000000..60d54f7
--- /dev/null
+++ b/FS/FS/h_svc_domain.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_domain;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_domain;
+
+@ISA = qw( FS::h_Common FS::svc_domain );
+
+sub table { 'h_svc_domain' };
+
+=head1 NAME
+
+FS::h_svc_domain - Historical domain objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_domain object represents a historical domain. FS::h_svc_domain
+inherits from FS::h_Common and FS::svc_domain.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_domain>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_external.pm b/FS/FS/h_svc_external.pm
new file mode 100644
index 0000000..5eb7064
--- /dev/null
+++ b/FS/FS/h_svc_external.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_external;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_external;
+
+@ISA = qw( FS::h_Common FS::svc_external );
+
+sub table { 'h_svc_external' };
+
+=head1 NAME
+
+FS::h_svc_external - Historical externally tracked service objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_external object represents a historical externally tracked service.
+FS::h_svc_external inherits from FS::h_Common and FS::svc_external.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_external>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_forward.pm b/FS/FS/h_svc_forward.pm
new file mode 100644
index 0000000..25b2039
--- /dev/null
+++ b/FS/FS/h_svc_forward.pm
@@ -0,0 +1,85 @@
+package FS::h_svc_forward;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Record qw(qsearchs);
+use FS::h_Common;
+use FS::svc_forward;
+use FS::svc_acct;
+use FS::h_svc_acct;
+
+use Carp qw(carp);
+
+$DEBUG = 0;
+
+@ISA = qw( FS::h_Common FS::svc_forward );
+
+sub table { 'h_svc_forward' };
+
+=head1 NAME
+
+FS::h_svc_forward - Historical mail forwarding alias objects
+
+=head1 SYNOPSIS
+
+=head1 METHODS
+
+=over 4
+
+=item srcsvc_acct
+
+=cut
+
+sub srcsvc_acct {
+ my $self = shift;
+ my $h_svc_acct = qsearchs(
+ 'h_svc_acct',
+ { 'svcnum' => $self->srcsvc },
+ FS::h_svc_acct->sql_h_searchs(@_),
+ ) || $self->SUPER::srcsvc_acct
+ or die "no history svc_acct.svcnum for svc_forward.srcsvc ". $self->srcsvc;
+
+ carp 'Using svc_acct in place of missing h_svc_acct record.'
+ if ($h_svc_acct->isa('FS::domain_record') and $DEBUG);
+
+ return $h_svc_acct;
+
+}
+
+=item dstsvc_acct
+
+=cut
+
+sub dstsvc_acct {
+ my $self = shift;
+ my $h_svc_acct = qsearchs(
+ 'h_svc_acct',
+ { 'svcnum' => $self->dstsvc },
+ FS::h_svc_acct->sql_h_searchs(@_),
+ ) || $self->SUPER::dstsvc_acct
+ or die "no history svc_acct.svcnum for svc_forward.dstsvc ". $self->dstsvc;
+
+ carp 'Using svc_acct in place of missing h_svc_acct record.'
+ if ($h_svc_acct->isa('FS::domain_record') and $DEBUG);
+
+ return $h_svc_acct;
+}
+
+=back
+
+=head1 DESCRIPTION
+
+An FS::h_svc_forward object represents a historical mail forwarding alias.
+FS::h_svc_forward inherits from FS::h_Common and FS::svc_forward.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_forward>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_mailinglist.pm b/FS/FS/h_svc_mailinglist.pm
new file mode 100644
index 0000000..3d1fd27
--- /dev/null
+++ b/FS/FS/h_svc_mailinglist.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_mailinglist;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_mailinglist;
+
+@ISA = qw( FS::h_Common FS::svc_mailinglist );
+
+sub table { 'h_svc_mailinglist' };
+
+=head1 NAME
+
+FS::h_svc_mailinglist - Historical mailing list objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_mailinglist object represents a historical mailing list.
+FS::h_svc_mailinglist inherits from FS::h_Common and FS::svc_mailinglist.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_mailinglist>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_pbx.pm b/FS/FS/h_svc_pbx.pm
new file mode 100644
index 0000000..db702f3
--- /dev/null
+++ b/FS/FS/h_svc_pbx.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_pbx;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_pbx;
+
+@ISA = qw( FS::h_Common FS::svc_pbx );
+
+sub table { 'h_svc_pbx' };
+
+=head1 NAME
+
+FS::h_svc_pbx - Historical PBX objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_pbx object represents a historical PBX tenant. FS::h_svc_pbx
+inherits from FS::h_Common and FS::svc_pbx.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_pbx>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_phone.pm b/FS/FS/h_svc_phone.pm
new file mode 100644
index 0000000..95898c7
--- /dev/null
+++ b/FS/FS/h_svc_phone.pm
@@ -0,0 +1,33 @@
+package FS::h_svc_phone;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_phone;
+
+@ISA = qw( FS::h_Common FS::svc_phone );
+
+sub table { 'h_svc_phone' };
+
+=head1 NAME
+
+FS::h_svc_phone - Historical phone number objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_phone object represents a historical phone number.
+FS::h_svc_phone inherits from FS::h_Common and FS::svc_phone.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_phone>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_www.pm b/FS/FS/h_svc_www.pm
new file mode 100644
index 0000000..2a3b6dc
--- /dev/null
+++ b/FS/FS/h_svc_www.pm
@@ -0,0 +1,67 @@
+package FS::h_svc_www;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp qw(carp);
+use FS::Record qw(qsearchs);
+use FS::h_Common;
+use FS::svc_www;
+use FS::h_domain_record;
+
+@ISA = qw( FS::h_Common FS::svc_www );
+
+$DEBUG = 0;
+
+sub table { 'h_svc_www' };
+
+=head1 NAME
+
+FS::h_svc_www - Historical web virtual host objects
+
+=head1 SYNOPSIS
+
+=head1 METHODS
+
+=over 4
+
+=item domain_record
+
+=cut
+
+sub domain_record {
+ my $self = shift;
+
+ carp 'Called FS::h_svc_www->domain_record on svcnum ' . $self->svcnum if $DEBUG;
+
+ my $domain_record = qsearchs(
+ 'h_domain_record',
+ { 'recnum' => $self->recnum },
+ FS::h_domain_record->sql_h_searchs(@_),
+ ) || $self->SUPER::domain_record
+ or die "no history domain_record.recnum for svc_www.recnum ". $self->domsvc;
+
+ carp 'Using domain_record in place of missing h_domain_record record.'
+ if ($domain_record->isa('FS::domain_record') and $DEBUG);
+
+ return $domain_record;
+
+}
+
+=back
+
+=head1 DESCRIPTION
+
+An FS::h_svc_www object represents a historical web virtual host.
+FS::h_svc_www inherits from FS::h_Common and FS::svc_www.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_www>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/inventory_class.pm b/FS/FS/inventory_class.pm
new file mode 100644
index 0000000..4747241
--- /dev/null
+++ b/FS/FS/inventory_class.pm
@@ -0,0 +1,264 @@
+package FS::inventory_class;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( dbh qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::inventory_class - Object methods for inventory_class records
+
+=head1 SYNOPSIS
+
+ use FS::inventory_class;
+
+ $record = new FS::inventory_class \%hash;
+ $record = new FS::inventory_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::inventory_class object represents a class of inventory, such as "DID
+numbers" or "physical equipment serials". FS::inventory_class inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item classnum - primary key
+
+=item classname - Name of this class
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new inventory class. To add the class 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 { 'inventory_class'; }
+
+=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 inventory class. 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('classnum')
+ || $self->ut_textn('classname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item num_avail
+
+Returns the number of available (unused/unallocated) inventory items of this
+class (see L<FS::inventory_item>).
+
+=cut
+
+sub num_avail {
+ my( $self, $sql ) = @_;
+ $sql .= ' AND ' if length($sql);
+ $sql .= '( svcnum IS NULL OR svcnum = 0 )';
+ $self->num_sql($sql);
+}
+
+sub num_sql {
+ my( $self, $sql ) = @_;
+ $sql = "AND $sql" if length($sql);
+
+ my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null' => 1,
+ 'table' => 'inventory_item',
+ );
+
+ my $st = "SELECT COUNT(*) FROM inventory_item ".
+ " WHERE classnum = ? AND $agentnums_sql $sql";
+ my $sth = dbh->prepare($st) or die dbh->errstr. " preparing $st";
+ $sth->execute($self->classnum) or die $sth->errstr. " executing $st";
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item num_used
+
+Returns the number of used (allocated) inventory items of this class (see
+L<FS::inventory_class>).
+
+=cut
+
+sub num_used {
+ my( $self, $sql ) = @_;
+ $sql .= ' AND ' if length($sql);
+ $sql .= 'svcnum IS NOT NULL AND svcnum > 0 ';
+ $self->num_sql($sql);
+}
+
+=item num_total
+
+Returns the total number of inventory items of this class (see
+L<FS::inventory_class>).
+
+=cut
+
+sub num_total {
+ my( $self, $sql ) = @_;
+ $self->num_sql($sql);
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item searchcell_factory
+
+=cut
+
+sub countcell_factory {
+ my($class, %opt) = @_;
+
+ my $p = $opt{p};
+
+ my $sql = $opt{'agentnum'} ? 'agentnum = '.$opt{'agentnum'} : '';
+
+ use Tie::IxHash;
+ tie my %labels, 'Tie::IxHash',
+ 'num_avail' => 'Available', # <FONT SIZE="-1"><A HREF="eventually">(upload batch)</A></FONT>',
+ 'num_used' => 'In use', #'Used', #'Allocated',
+ 'num_total' => 'Total',
+ ;
+
+ my %link = (
+ 'num_avail' => ';avail=1',
+ 'num_used' => ';used=1',
+ 'num_total' => '',
+ );
+
+ my %inv_action_link = (
+ 'num_avail' => [ 'upload batch',
+ $p.'misc/inventory_item-import.html?classnum=',
+ 'classnum'
+ ],
+ );
+
+ sub {
+ my $inventory_class = shift;
+
+ my $link =
+ $p. 'search/inventory_item.html?'.
+ 'classnum='. $inventory_class->classnum;
+ $link .= ';agentnum='.$opt{'agentnum'} if $opt{'agentnum'};
+
+ my %actioncol = ();
+ foreach ( keys %inv_action_link ) {
+ my($label, $baseurl, $method) =
+ @{ $inv_action_link{$_} };
+ my $url = $baseurl. $inventory_class->$method();
+ $actioncol{$_} =
+ '<FONT SIZE="-1">'.
+ '('.
+ '<A HREF="'.$url.'">'.
+ $label.
+ '</A>'.
+ ')'.
+ '</FONT>';
+ }
+
+ my %num = map {
+ $_ => $inventory_class->$_($sql);
+ } keys %labels;
+
+ [ map {
+ [
+ {
+ 'data' => '<B>'. $num{$_}. '</B>',
+ 'align' => 'right',
+ },
+ {
+ 'data' => $labels{$_},
+ 'align' => 'left',
+ 'link' => ( $num{$_}
+ ? $link.$link{$_}
+ : ''
+ ),
+ },
+ { 'data' => $actioncol{$_},
+ 'align' => 'left',
+ },
+ ]
+ } keys %labels
+ ];
+ };
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::inventory_item>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/inventory_item.pm b/FS/FS/inventory_item.pm
new file mode 100644
index 0000000..39a0dff
--- /dev/null
+++ b/FS/FS/inventory_item.pm
@@ -0,0 +1,182 @@
+package FS::inventory_item;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::cust_main_Mixin;
+use FS::inventory_class;
+use FS::cust_svc;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+=head1 NAME
+
+FS::inventory_item - Object methods for inventory_item records
+
+=head1 SYNOPSIS
+
+ use FS::inventory_item;
+
+ $record = new FS::inventory_item \%hash;
+ $record = new FS::inventory_item { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::inventory_item object represents a specific piece of (real or virtual)
+inventory, such as a specific DID or serial number. FS::inventory_item
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item itemnum - primary key
+
+=item classnum - Inventory class (see L<FS::inventory_class>)
+
+=item item - Item identifier (unique within its inventory class)
+
+=item svcnum - Customer servcie (see L<FS::cust_svc>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new item. To add the item 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 { 'inventory_item'; }
+
+=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 item. 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('itemnum')
+ || $self->ut_foreign_key('classnum', 'inventory_class', 'classnum' )
+ #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum' )
+ || $self->ut_agentnum_acl('agentnum', ['Configuration',
+ 'Edit global inventory'] )
+ || $self->ut_text('item')
+ || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item cust_svc
+
+Returns the customer service associated with this inventory item, if the
+item has been used (see L<FS::cust_svc>).
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ return '' unless $self->svcnum;
+ qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item agent
+
+Returns the associated agent for this event, if any, as an FS::agent object.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item process_batch_import
+
+=cut
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $opt = { 'table' => 'inventory_item',
+ #'params' => [ 'itembatch', 'classnum', ],
+ 'params' => [ 'classnum', 'agentnum', ],
+ 'formats' => { 'default' => [ 'item' ] },
+ 'default_csv' => 1,
+ };
+
+ FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
+=back
+
+=head1 BUGS
+
+maybe batch_import should be a regular method in FS::inventory_class
+
+=head1 SEE ALSO
+
+L<inventory_class>, L<cust_svc>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/location_Mixin.pm b/FS/FS/location_Mixin.pm
new file mode 100644
index 0000000..d457386
--- /dev/null
+++ b/FS/FS/location_Mixin.pm
@@ -0,0 +1,57 @@
+package FS::location_Mixin;
+
+use strict;
+use FS::Record qw( qsearchs );
+use FS::cust_location;
+
+=item cust_location
+
+Returns the location object, if any (see L<FS::cust_location>).
+
+=cut
+
+sub cust_location {
+ my $self = shift;
+ return '' unless $self->locationnum;
+ qsearchs( 'cust_location', { 'locationnum' => $self->locationnum } );
+}
+
+=item cust_location_or_main
+
+If this package is associated with a location, returns the locaiton (see
+L<FS::cust_location>), otherwise returns the customer (see L<FS::cust_main>).
+
+=cut
+
+sub cust_location_or_main {
+ my $self = shift;
+ $self->cust_location || $self->cust_main;
+}
+
+=item location_label [ OPTION => VALUE ... ]
+
+Returns the label of the location object (see L<FS::cust_location>).
+
+=cut
+
+sub location_label {
+ my $self = shift;
+ my $object = $self->cust_location_or_main;
+ $object->location_label(@_);
+}
+
+=item location_hash
+
+Returns a hash of values for the location, either from the location object,
+the cust_main shipping address, or the cust_main address, whichever is present
+first.
+
+=cut
+
+sub location_hash {
+ my $self = shift;
+ my $object = $self->cust_location_or_main;
+ $object->location_hash(@_);
+}
+
+1;
diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm
new file mode 100644
index 0000000..6774a48
--- /dev/null
+++ b/FS/FS/m2m_Common.pm
@@ -0,0 +1,170 @@
+package FS::m2m_Common;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+
+#hmm. well. we seem to be used as a mixin.
+#@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::m2m_Common]';
+
+=head1 NAME
+
+FS::m2m_Common - Mixin class for classes in a many-to-many relationship
+
+=head1 SYNOPSIS
+
+use FS::m2m_Common;
+
+@ISA = qw( FS::m2m_Common FS::Record );
+
+=head1 DESCRIPTION
+
+FS::m2m_Common is intended as a mixin class for classes which have a
+many-to-many relationship with another table (via a linking table).
+
+It is currently assumed that the link table contains two fields named the same
+as the primary keys of the base and target tables, but you can ovverride this
+assumption if your table is different.
+
+=head1 METHODS
+
+=over 4
+
+=item process_m2m OPTION => VALUE, ...
+
+Available options:
+
+=over 4
+
+=item link_table (required)
+
+=item target_table (required)
+
+=item params (required)
+
+hashref; keys are primary key values in target_table (values are boolean). For convenience, keys may optionally be prefixed with the name
+of the primary key, as in "agentnum54" instead of "54", or passed as an arrayref
+of values.
+
+=item base_field (optional)
+
+base field, defaults to primary key of this base table
+
+=item target_field (optional)
+
+target field, defaults to the primary key of the target table
+
+=item hashref (optional)
+
+static hashref further qualifying the m2m fields
+
+=cut
+
+sub process_m2m {
+ my( $self, %opt ) = @_;
+
+ #use Data::Dumper;
+ #warn "$me process_m2m called on $self with options:\n". Dumper(%opt)
+ warn "$me process_m2m called on $self"
+ if $DEBUG;
+
+ my $self_pkey = $self->dbdef_table->primary_key;
+ my $base_field = $opt{'base_field'} || $self_pkey;
+ my $hashref = $opt{'hashref'} || {};
+ $hashref->{$base_field} = $self->$self_pkey();
+
+ my $link_table = $self->_load_table($opt{'link_table'});
+
+ my $target_table = $self->_load_table($opt{'target_table'});
+ my $target_field = $opt{'target_field'}
+ || dbdef->table($target_table)->primary_key;
+
+ if ( ref($opt{'params'}) eq 'ARRAY' ) {
+ $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} };
+ }
+
+ 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 $del_obj (
+ grep {
+ my $targetnum = $_->$target_field();
+ ( ! $opt{'params'}->{$targetnum}
+ && ! $opt{'params'}->{"$target_field$targetnum"}
+ );
+ }
+ qsearch( $link_table, $hashref )
+ ) {
+ my $error = $del_obj->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $add_targetnum (
+ grep { ! qsearchs( $link_table, { %$hashref, $target_field => $_ } ) }
+ map { /^($target_field)?(\d+)$/; $2; }
+ grep { /^($target_field)?(\d+)$/ }
+ grep { $opt{'params'}->{$_} }
+ keys %{ $opt{'params'} }
+ ) {
+
+ my $add_obj = "FS::$link_table"->new( {
+ %$hashref,
+ $target_field => $add_targetnum,
+ });
+ my $error = $add_obj->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+sub _load_table {
+ my( $self, $table ) = @_;
+ eval "use FS::$table";
+ die $@ if $@;
+ $table;
+}
+
+#=item target_table
+#
+#=cut
+#
+#sub target_table {
+# my $self = shift;
+# my $target_table = $self->_target_table;
+# eval "use FS::$target_table";
+# die $@ if $@;
+# $target_table;
+#}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/m2name_Common.pm b/FS/FS/m2name_Common.pm
new file mode 100644
index 0000000..e9dcee9
--- /dev/null
+++ b/FS/FS/m2name_Common.pm
@@ -0,0 +1,177 @@
+package FS::m2name_Common;
+
+use strict;
+use vars qw( $DEBUG $me );
+use Carp;
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearchs ); #qsearch dbh );
+
+$DEBUG = 0;
+
+$me = '[FS::m2name_Common]';
+
+=head1 NAME
+
+FS::m2name_Common - Mixin class for tables with a related table listing names
+
+=head1 SYNOPSIS
+
+use FS::m2name_Common;
+
+@ISA = qw( FS::m2name_Common FS::Record );
+
+=head1 DESCRIPTION
+
+FS::m2name_Common is intended as a mixin class for classes which have a
+related table that lists names.
+
+=head1 METHODS
+
+=over 4
+
+=item process_m2name OPTION => VALUE, ...
+
+Available options:
+
+link_table (required) - Table into which the records are inserted.
+
+num_col (optional) - Column in link_table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
+
+name_col (required) - Name of the column in link_table that stores the string names.
+
+names_list (required) - List reference of the possible string name values.
+
+params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form. Processing is controlled by the B<param_style param> option.
+
+param_style (required) - Controls processing of B<params>. I<'link_table.value checkboxes'> specifies that parameters keys are in the form C<link_table.name>, and the values are booleans controlling whether or not to insert that name into link_table. I<'name_colN values'> specifies that parameter keys are in the form C<name_col0>, C<name_col1>, and so on, and values are the names inserted into link_table.
+
+args_callback (optional) - Coderef. Optional callback that may modify arguments for insert and replace operations. The callback is run with four arguments: the first argument is object being inserted or replaced (i.e. FS::I<link_table> object), the second argument is a prefix to use when retreiving CGI arguements from the params hashref, the third argument is the params hashref (see above), and the final argument is a listref of arguments that the callback should modify.
+
+=cut
+
+sub process_m2name {
+ my( $self, %opt ) = @_;
+
+ my $self_pkey = $self->dbdef_table->primary_key;
+ my $link_sourcekey = $opt{'num_col'} || $self_pkey;
+
+ my $link_table = $self->_load_table($opt{'link_table'});
+
+ my $link_static = $opt{'link_static'} || {};
+
+ warn "$me processing m2name from ". $self->table. ".$link_sourcekey".
+ " to $link_table\n"
+ if $DEBUG;
+
+ foreach my $name ( @{ $opt{'names_list'} } ) {
+
+ warn "$me checking $name\n" if $DEBUG;
+
+ my $name_col = $opt{'name_col'};
+
+ my $obj = qsearchs( $link_table, {
+ $link_sourcekey => $self->$self_pkey(),
+ $name_col => $name,
+ %$link_static,
+ });
+
+ my $param = '';
+ my $prefix = '';
+ if ( $opt{'param_style'} =~ /link_table.value\s+checkboxes/i ) {
+ #access_group.html style
+ my $paramname = "$link_table.$name";
+ $param = $opt{'params'}->{$paramname};
+ } elsif ( $opt{'param_style'} =~ /name_colN values/i ) {
+ #part_event.html style
+
+ my @fields = grep { /^$name_col\d+$/ }
+ keys %{$opt{'params'}};
+
+ $param = grep { $name eq $opt{'params'}->{$_} } @fields;
+
+ if ( $param ) {
+ #this depends on their being one condition per name...
+ #which needs to be enforced on the edit page...
+ #(it is on part_event and access_group edit)
+ foreach my $field (@fields) {
+ $prefix = "$field." if $name eq $opt{'params'}->{$field};
+ }
+ warn "$me prefix $prefix\n" if $DEBUG;
+ }
+ } else { #??
+ croak "unknown param_style: ". $opt{'param_style'};
+ $param = $opt{'params'}->{$name};
+ }
+
+ if ( $obj && ! $param ) {
+
+ warn "$me deleting $name\n" if $DEBUG;
+
+ my $d_obj = $obj; #need to save $obj for below.
+ my $error = $d_obj->delete;
+ die "error deleting $d_obj for $link_table.$name: $error" if $error;
+
+ } elsif ( $param && ! $obj ) {
+
+ warn "$me inserting $name\n" if $DEBUG;
+
+ #ok to clobber it now (but bad form nonetheless?)
+ #$obj = new "FS::$link_table" ( {
+ $obj = "FS::$link_table"->new( {
+ $link_sourcekey => $self->$self_pkey(),
+ $opt{'name_col'} => $name,
+ %$link_static,
+ });
+
+ my @args = ();
+ if ( $opt{'args_callback'} ) { #edit/process/part_event.html
+ &{ $opt{'args_callback'} }( $obj,
+ $prefix,
+ $opt{'params'},
+ \@args
+ );
+ }
+
+ my $error = $obj->insert( @args );
+ die "error inserting $obj for $link_table.$name: $error" if $error;
+
+ } elsif ( $param && $obj && $opt{'args_callback'} ) {
+
+ my @args = ();
+ if ( $opt{'args_callback'} ) { #edit/process/part_event.html
+ &{ $opt{'args_callback'} }( $obj,
+ $prefix,
+ $opt{'params'},
+ \@args
+ );
+ }
+
+ my $error = $obj->replace( $obj, @args );
+ die "error replacing $obj for $link_table.$name: $error" if $error;
+
+ }
+
+ }
+
+ '';
+}
+
+sub _load_table {
+ my( $self, $table ) = @_;
+ eval "use FS::$table";
+ die $@ if $@;
+ $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/mailinglist.pm b/FS/FS/mailinglist.pm
new file mode 100644
index 0000000..1294610
--- /dev/null
+++ b/FS/FS/mailinglist.pm
@@ -0,0 +1,173 @@
+package FS::mailinglist;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::mailinglistmember;
+use FS::svc_mailinglist;
+
+=head1 NAME
+
+FS::mailinglist - Object methods for mailinglist records
+
+=head1 SYNOPSIS
+
+ use FS::mailinglist;
+
+ $record = new FS::mailinglist \%hash;
+ $record = new FS::mailinglist { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::mailinglist object represents a mailing list FS::mailinglist inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item listnum
+
+primary key
+
+=item listname
+
+Mailing list name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new mailing list. To add the mailing list 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 { 'mailinglist'; }
+
+=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
+
+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;
+
+ foreach my $member ( $self->mailinglistmember ) {
+ my $error = $member->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 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 mailing list. 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('listnum')
+ || $self->ut_text('listname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item mailinglistmember
+
+=cut
+
+sub mailinglistmember {
+ my $self = shift;
+ qsearch('mailinglistmember', { 'listnum' => $self->listnum } );
+}
+
+=item svc_mailinglist
+
+=cut
+
+sub svc_mailinglist {
+ my $self = shift;
+ qsearchs('svc_mailinglist', { 'listnum' => $self->listnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::mailinglistmember>, L<FS::svc_mailinglist>, L<FS::Record>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/mailinglistmember.pm b/FS/FS/mailinglistmember.pm
new file mode 100644
index 0000000..49688d8
--- /dev/null
+++ b/FS/FS/mailinglistmember.pm
@@ -0,0 +1,245 @@
+package FS::mailinglistmember;
+
+use strict;
+use base qw( FS::Record );
+use Scalar::Util qw( blessed );
+use FS::Record qw( dbh qsearchs ); # qsearch );
+use FS::mailinglist;
+use FS::svc_acct;
+use FS::contact_email;
+
+=head1 NAME
+
+FS::mailinglistmember - Object methods for mailinglistmember records
+
+=head1 SYNOPSIS
+
+ use FS::mailinglistmember;
+
+ $record = new FS::mailinglistmember \%hash;
+ $record = new FS::mailinglistmember { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::mailinglistmember object represents a mailing list member.
+FS::mailinglistmember inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item membernum
+
+primary key
+
+=item listnum
+
+listnum
+
+=item svcnum
+
+svcnum
+
+=item contactemailnum
+
+contactemailnum
+
+=item email
+
+email
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new mailing list member. To add the member 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 { 'mailinglistmember'; }
+
+=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;
+
+ my $error = $self->SUPER::insert
+ || $self->export('mailinglistmember_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;
+
+ 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
+ || $self->export('mailinglistmember_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
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ 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)
+ || $new->export('mailinglistmember_replace', $old);
+ 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 member. 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('membernum')
+ || $self->ut_foreign_key('listnum', 'mailinglist', 'listnum')
+ || $self->ut_foreign_keyn('svcnum', 'svc_acct', 'svcnum')
+ || $self->ut_foreign_keyn('contactemailnum', 'contact_email', 'contactemailnum')
+ || $self->ut_textn('email') #XXX ut_email! from svc_forward, cust_main_invoice
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item mailinglist
+
+=cut
+
+sub mailinglist {
+ my $self = shift;
+ qsearchs('mailinglist', { 'listnum' => $self->listnum } );
+}
+
+=item email_address
+
+=cut
+
+sub email_address {
+ my $self = shift;
+ #XXX svcnum, contactemailnum
+ $self->email;
+}
+
+=item export
+
+=cut
+
+sub export {
+ my( $self, $method ) = ( shift, shift );
+ my $svc_mailinglist = $self->mailinglist->svc_mailinglist
+ or return '';
+ $svc_mailinglist->export($method, $self, @_);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+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..d1224f3
--- /dev/null
+++ b/FS/FS/msgcat.pm
@@ -0,0 +1,166 @@
+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 message catalog entry. To add the message catalog entry 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 message catalog entry. 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
+}
+
+
+sub _upgrade_data { #class method
+ my( $class, %opts) = @_;
+
+ eval "use FS::Setup;";
+ die $@ if $@;
+
+ #"repopulate_msgcat", false laziness w/FS::Setup::populate_msgcat
+
+ my %messages = FS::Setup::msgcat_messages();
+
+ foreach my $msgcode ( keys %messages ) {
+ foreach my $locale ( keys %{$messages{$msgcode}} ) {
+ my %msgcat = (
+ 'msgcode' => $msgcode,
+ 'locale' => $locale,
+ #'msg' => $messages{$msgcode}{$locale},
+ );
+ #my $msgcat = qsearchs('msgcat', \%msgcat);
+ my $msgcat = FS::Record::qsearchs('msgcat', \%msgcat); #wtf?
+ next if $msgcat;
+
+ $msgcat = new FS::msgcat( {
+ %msgcat,
+ 'msg' => $messages{$msgcode}{$locale},
+ } );
+ my $error = $msgcat->insert;
+ die $error if $error;
+ }
+ }
+
+}
+
+=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..97b0ea1
--- /dev/null
+++ b/FS/FS/nas.pm
@@ -0,0 +1,150 @@
+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 NAS. 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 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/o2m_Common.pm b/FS/FS/o2m_Common.pm
new file mode 100644
index 0000000..0e03b52
--- /dev/null
+++ b/FS/FS/o2m_Common.pm
@@ -0,0 +1,152 @@
+package FS::o2m_Common;
+
+use strict;
+use vars qw( $DEBUG $me );
+use Carp;
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+
+$DEBUG = 0;
+
+$me = '[FS::o2m_Common]';
+
+=head1 NAME
+
+FS::o2m_Common - Mixin class for tables with a related table
+
+=head1 SYNOPSIS
+
+use FS::o2m_Common;
+
+@ISA = qw( FS::o2m_Common FS::Record );
+
+=head1 DESCRIPTION
+
+FS::o2m_Common is intended as a mixin class for classes which have a
+related table.
+
+=head1 METHODS
+
+=over 4
+
+=item process_o2m OPTION => VALUE, ...
+
+Available options:
+
+table (required) - Table into which the records are inserted.
+
+num_col (optional) - Column in table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
+
+params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form.
+
+fields (required) - Arrayref of field names for each record in table. Pulled from params as "pkeyNN_field" where pkey is table's primary key and NN is the entry's numeric identifier.
+
+=cut
+
+#a little more false laziness w/m2m_Common.pm than m2_name_Common.pm
+# still, far from the worse of it. at least we're a reuable mixin!
+sub process_o2m {
+ my( $self, %opt ) = @_;
+
+ my $self_pkey = $self->dbdef_table->primary_key;
+ my $link_sourcekey = $opt{'num_col'} || $self_pkey;
+
+ my $hashref = {}; #$opt{'hashref'} || {};
+ $hashref->{$link_sourcekey} = $self->$self_pkey();
+
+ my $table = $self->_load_table($opt{'table'});
+ my $table_pkey = dbdef->table($table)->primary_key;
+
+# my $link_static = $opt{'link_static'} || {};
+
+ warn "$me processing o2m from ". $self->table. ".$link_sourcekey".
+ " to $table\n"
+ if $DEBUG;
+
+ #if ( ref($opt{'params'}) eq 'ARRAY' ) {
+ # $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} };
+ #}
+
+ 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 @fields = grep { /^$table_pkey\d+$/ }
+ keys %{ $opt{'params'} };
+
+ my %edits = map { $opt{'params'}->{$_} => $_ }
+ grep { $opt{'params'}->{$_} }
+ @fields;
+
+ foreach my $del_obj (
+ grep { ! $edits{$_->$table_pkey()} }
+ qsearch( $table, $hashref )
+ ) {
+ my $error = $del_obj->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $pkey_value ( keys %edits ) {
+ my $old_obj = qsearchs( $table, { %$hashref, $table_pkey => $pkey_value } ),
+ my $add_param = $edits{$pkey_value};
+ my %hash = ( $table_pkey => $pkey_value,
+ map { $_ => $opt{'params'}->{$add_param."_$_"} }
+ @{ $opt{'fields'} }
+ );
+ #next unless grep { $_ =~ /\S/ } values %hash;
+
+ my $new_obj = "FS::$table"->new( { %$hashref, %hash } );
+ my $error = $new_obj->replace($old_obj);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $add_param ( grep { ! $opt{'params'}->{$_} } @fields ) {
+
+ my %hash = map { $_ => $opt{'params'}->{$add_param."_$_"} }
+ @{ $opt{'fields'} };
+ next unless grep { $_ =~ /\S/ } values %hash;
+
+ my $add_obj = "FS::$table"->new( { %$hashref, %hash } );
+ my $error = $add_obj->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+sub _load_table {
+ my( $self, $table ) = @_;
+ eval "use FS::$table";
+ die $@ if $@;
+ $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm
new file mode 100644
index 0000000..a786ae3
--- /dev/null
+++ b/FS/FS/option_Common.pm
@@ -0,0 +1,345 @@
+package FS::option_Common;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Scalar::Util qw( blessed );
+use FS::Record qw( qsearch qsearchs dbh );
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::option_Common - Base class for option sub-classes
+
+=head1 SYNOPSIS
+
+use FS::option_Common;
+
+@ISA = qw( FS::option_Common );
+
+#optional for non-standard names
+sub _option_table { 'table_name'; } #defaults to ${table}_option
+sub _option_namecol { 'column_name'; } #defaults to optionname
+sub _option_valuecol { 'column_name'; } #defaults to optionvalue
+
+=head1 DESCRIPTION
+
+FS::option_Common is intended as a base class for classes which have a
+simple one-to-many class associated with them, used to store a hash-like data
+structure of keys and values.
+
+=head1 METHODS
+
+=over 4
+
+=item insert [ HASHREF | OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If a list or hash reference of options is supplied, option records are also
+created.
+
+=cut
+
+#false laziness w/queue.pm
+sub insert {
+ my $self = shift;
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
+ warn "FS::option_Common::insert called on $self with options ".
+ join(', ', map "$_ => ".$options->{$_}, keys %$options)
+ 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 $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $pkey = $self->primary_key;
+ my $option_table = $self->option_table;
+
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+
+ foreach my $optionname ( keys %{$options} ) {
+
+ my $optionvalue = $options->{$optionname};
+
+ my $href = {
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
+ $valuecol => ( ref($optionvalue) || $optionvalue ),
+ };
+
+ #my $option_record = eval "new FS::$option_table \$href";
+ #if ( $@ ) {
+ # $dbh->rollback if $oldAutoCommit;
+ # return $@;
+ #}
+ my $option_record = "FS::$option_table"->new($href);
+
+ my @args = ();
+ push @args, $optionvalue if ref($optionvalue); #only hashes supported so far
+
+ $error = $option_record->insert(@args);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database. Any associated option records are also
+deleted.
+
+=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;
+ }
+
+ my $pkey = $self->primary_key;
+ #my $option_table = $self->option_table;
+
+ foreach my $obj ( $self->option_objects ) {
+ my $error = $obj->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+If a list or hash reference of options is supplied, option records are created
+or modified.
+
+=cut
+
+sub replace {
+ my $self = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
+
+ warn "FS::option_Common::replace called on $self with options ".
+ join(', ', map "$_ => ". $options->{$_}, keys %$options)
+ 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 $error = $self->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $pkey = $self->primary_key;
+ my $option_table = $self->option_table;
+
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+
+ foreach my $optionname ( keys %{$options} ) {
+
+ warn "FS::option_Common::replace: inserting or replacing option: $optionname"
+ if $DEBUG > 1;
+
+ my $oldopt = qsearchs( $option_table, {
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
+ } );
+
+ my $optionvalue = $options->{$optionname};
+
+ my %oldhash = $oldopt ? $oldopt->hash : ();
+
+ my $href = {
+ %oldhash,
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
+ $valuecol => ( ref($optionvalue) || $optionvalue ),
+ };
+
+ #my $newopt = eval "new FS::$option_table \$href";
+ #if ( $@ ) {
+ # $dbh->rollback if $oldAutoCommit;
+ # return $@;
+ #}
+ my $newopt = "FS::$option_table"->new($href);
+
+ my $opt_pkey = $newopt->primary_key;
+
+ $newopt->$opt_pkey($oldopt->$opt_pkey) if $oldopt;
+
+ my @args = ();
+ push @args, $optionvalue if ref($optionvalue); #only hashes supported so far
+
+ warn "FS::option_Common::replace: ".
+ ( $oldopt ? "$newopt -> replace($oldopt)" : "$newopt -> insert" )
+ if $DEBUG > 2;
+ my $error = $oldopt ? $newopt->replace($oldopt, @args)
+ : $newopt->insert( @args);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ #remove extraneous old options
+ foreach my $opt (
+ grep { !exists $options->{$_->$namecol()} } $old->option_objects
+ ) {
+ my $error = $opt->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item option_objects
+
+Returns all options as FS::I<tablename>_option objects.
+
+=cut
+
+sub option_objects {
+ my $self = shift;
+ my $pkey = $self->primary_key;
+ my $option_table = $self->option_table;
+ qsearch($option_table, { $pkey => $self->get($pkey) } );
+}
+
+=item options
+
+Returns a list of option names and values suitable for assigning to a hash.
+
+=cut
+
+sub options {
+ my $self = shift;
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+ map { $_->$namecol() => $_->$valuecol() } $self->option_objects;
+}
+
+=item option OPTIONNAME
+
+Returns the option value for the given name, or the empty string.
+
+=cut
+
+sub option {
+ my $self = shift;
+ my $pkey = $self->primary_key;
+ my $option_table = $self->option_table;
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+ my $hashref = {
+ $pkey => $self->get($pkey),
+ $namecol => shift,
+ };
+ warn "$self -> option: searching for ".
+ join(' / ', map { "$_ => ". $hashref->{$_} } keys %$hashref )
+ if $DEBUG;
+ my $obj = qsearchs($option_table, $hashref);
+ $obj ? $obj->$valuecol() : '';
+}
+
+
+sub option_table {
+ my $self = shift;
+ my $option_table = $self->_option_table;
+ eval "use FS::$option_table";
+ die $@ if $@;
+ $option_table;
+}
+
+#defaults
+sub _option_table { shift->table .'_option'; }
+sub _option_namecol { 'optionname'; }
+sub _option_valuecol { 'optionvalue'; }
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/otaker_Mixin.pm b/FS/FS/otaker_Mixin.pm
new file mode 100644
index 0000000..071958c
--- /dev/null
+++ b/FS/FS/otaker_Mixin.pm
@@ -0,0 +1,67 @@
+package FS::otaker_Mixin;
+
+use strict;
+use Carp qw( croak ); #confess );
+use FS::Record qw( qsearch qsearchs );
+use FS::access_user;
+
+sub otaker {
+ my $self = shift;
+ if ( scalar(@_) ) { #set
+ my $otaker = shift;
+ my $access_user = qsearchs('access_user', { 'username' => $otaker } )
+ or croak "can't set otaker: $otaker not found!"; #confess?
+ $self->usernum( $access_user->usernum );
+ $otaker; #not sure return is used anywhere, but just in case
+ } else { #get
+ if ( $self->usernum ) {
+ $self->access_user->username;
+ } elsif ( length($self->get('otaker')) ) {
+ $self->get('otaker');
+ } else {
+ '';
+ }
+ }
+}
+
+sub access_user {
+ my $self = shift;
+ qsearchs('access_user', { 'usernum' => $self->usernum } );
+}
+
+sub _upgrade_otaker {
+ my $class = shift;
+ my $table = $class->table;
+
+ while ( 1 ) {
+ my @records = qsearch({
+ 'table' => $table,
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE otaker IS NOT NULL LIMIT 1000',
+ });
+ last unless @records;
+
+ foreach my $record (@records) {
+ eval { $record->otaker($record->otaker) };
+ if ( $@ ) {
+ my $access_user = new FS::access_user {
+ 'username' => $record->otaker,
+ '_password' => 'CHANGEME',
+ 'first' => 'Legacy',
+ 'last' => 'User',
+ 'disabled' => 'Y',
+ };
+ my $error = $access_user->insert;
+ die $error if $error;
+ $record->otaker($record->otaker);
+ }
+ $record->set('otaker', '');
+ my $error = $record->replace;
+ die $error if $error;
+ }
+
+ }
+
+}
+
+1;
diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm
new file mode 100644
index 0000000..4e7aa52
--- /dev/null
+++ b/FS/FS/part_bill_event.pm
@@ -0,0 +1,368 @@
+package FS::part_bill_event;
+
+use strict;
+use vars qw( @ISA $DEBUG @EXPORT_OK );
+use Carp qw(cluck confess);
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::Conf;
+use FS::cust_main;
+use FS::cust_bill;
+
+@ISA = qw( FS::Record );
+@EXPORT_OK = qw( due_events );
+$DEBUG = 0;
+
+=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;
+
+ $error = $record->do_event( $direct_object );
+
+ @events = due_events ( { 'record' => $event_triggering_record,
+ 'payby' => $payby,
+ 'event_time => $_date,
+ 'extra_sql => $extra } );
+
+=head1 DESCRIPTION
+
+An FS::part_bill_event object represents a deprecated, old-style 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 reason - an associated reason for this event to fire
+
+=item disabled - Disabled flag, empty or `Y'
+
+=back
+
+=head1 NOTE
+
+Old-style invoice events are only useful for legacy migrations - if you are
+looking for current events see L<FS::part_event>.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice event definition. To add the invoice event definition 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;
+
+ #yay, these regexen will go away with the event refactor
+
+ $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
+
+# or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/
+ or $c =~ /^\s*\$cust_main\->apply_payments_and_credits; "";\s*$/
+
+ or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/
+
+ or $c =~ /^\s*\$cust_main\->suspend_(if|unless)_pkgpart\([\d\,\s]*\);\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->cust_suspend_if_balance_over\([\d\.\s]*\);\s*$/
+
+ or do {
+ #log
+ return "illegal eventcode: $c";
+ };
+
+ }
+
+ my $error = $self->ut_numbern('eventpart')
+ || $self->ut_enum('payby', [qw( CARD DCLN 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_numbern('reason')
+ ;
+ #|| $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 latexreturnaddress latexfooter
+ latexsmallfooter
+ html htmlnotes htmlreturnaddress htmlfooter
+ ))
+ {
+ unless ( $conf->exists("invoice_${file}_$name") ) {
+ $conf->set(
+ "invoice_${file}_$name" =>
+ join("\n", $conf->config("invoice_$file") )
+ );
+ }
+ }
+ }
+
+ if ($self->reason){
+ my $reasonr = qsearchs('reason', {'reasonnum' => $self->reason});
+ return "Unknown reason" unless $reasonr;
+ }
+
+ $self->SUPER::check;
+}
+
+=item templatename
+
+Returns the alternate invoice template name, if any, or false if there is
+no alternate template for this invoice event.
+
+=cut
+
+sub templatename {
+ my $self = shift;
+ if ( $self->plan =~ /^send_(alternate|agent)$/
+ && $self->plandata =~ /^(agent_)?templatename (.*)$/m
+ )
+ {
+ $2;
+ } else {
+ '';
+ }
+}
+
+=item due_events
+
+Returns the list of events due, if any, or false if there is none.
+Requires record and payby, but event_time and extra_sql are optional.
+
+=cut
+
+sub due_events {
+ my ($record, $payby, $event_time, $extra_sql) = @_;
+
+ #cluck "DEPRECATED: FS::part_bill_event::due_events called on $record";
+ confess "DEPRECATED: FS::part_bill_event::due_events called on $record";
+
+ my $interval = 0;
+ if ($record->_date){
+ $event_time = time unless $event_time;
+ $interval = $event_time - $record->_date;
+ }
+ sort { $a->seconds <=> $b->seconds
+ || $a->weight <=> $b->weight
+ || $a->eventpart <=> $b->eventpart }
+ grep { ref($record) ne 'FS::cust_bill' || $_->eventcode !~ /honor_dundate/
+ || $event_time > $record->cust_main->dundate
+ }
+ grep { $_->seconds <= ( $interval )
+ && ! qsearch( 'cust_bill_event', {
+ 'invnum' => $record->get($record->dbdef_table->primary_key),
+ 'eventpart' => $_->eventpart,
+ 'status' => 'done',
+ } )
+ }
+ qsearch( {
+ 'table' => 'part_bill_event',
+ 'hashref' => { 'payby' => $payby,
+ 'disabled' => '', },
+ 'extra_sql' => $extra_sql,
+ } );
+
+
+}
+
+=item do_event
+
+Performs the event and returns any errors that occur.
+Requires a record on which to perform the event.
+Should only be performed inside a transaction.
+
+=cut
+
+sub do_event {
+ my ($self, $object, %options) = @_;
+
+ #cluck "DEPRECATED: FS::part_bill_event::do_event called on $self";
+ confess "DEPRECATED: FS::part_bill_event::do_event called on $self";
+
+ warn " calling event (". $self->eventcode. ") for " . $object->table . " " ,
+ $object->get($object->dbdef_table->primary_key) . "\n" if $DEBUG > 1;
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ # for "callback" -- heh
+ my $cust_main = $object->cust_main;
+ my $cust_bill;
+ if ($object->table eq 'cust_bill'){
+ $cust_bill = $object;
+ }
+ my $cust_pay_batch;
+ if ($object->table eq 'cust_pay_batch'){
+ $cust_pay_batch = $object;
+ }
+
+ my $error;
+ {
+ local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
+ $error = eval $self->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' => $object->get($object->dbdef_table->primary_key),
+ 'invnum' => $object->invnum,
+ 'eventpart' => $self->eventpart,
+ '_date' => time,
+ 'status' => $status,
+ 'statustext' => $statustext,
+ };
+ $error = $cust_bill_event->insert;
+ if ( $error ) {
+ my $e = 'WARNING: Event run but database not updated - '.
+ 'error inserting cust_bill_event, invnum #'. $object->invnum .
+ ', eventpart '. $self->eventpart.": $error";
+ warn $e;
+ return $e;
+ }
+ '';
+}
+
+=item reasontext
+
+Returns the text of any reason associated with this event.
+
+=cut
+
+sub reasontext {
+ my $self = shift;
+ my $r = qsearchs('reason', { 'reasonnum' => $self->reason });
+ if ($r){
+ $r->reason;
+ }else{
+ '';
+ }
+}
+
+=back
+
+=head1 BUGS
+
+The whole "eventcode" idea is bunk. This should be refactored with subclasses
+like part_pkg/ and part_export/
+
+=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_device.pm b/FS/FS/part_device.pm
new file mode 100644
index 0000000..4963584
--- /dev/null
+++ b/FS/FS/part_device.pm
@@ -0,0 +1,148 @@
+package FS::part_device;
+
+use strict;
+use base qw( FS::Record FS::m2m_Common );
+use FS::Record qw( qsearch qsearchs );
+use FS::part_export;
+use FS::export_device;
+
+=head1 NAME
+
+FS::part_device - Object methods for part_device records
+
+=head1 SYNOPSIS
+
+ use FS::part_device;
+
+ $record = new FS::part_device \%hash;
+ $record = new FS::part_device { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_device object represents a phone device definition. FS::part_device
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item devicepart
+
+primary key
+
+=item devicename
+
+devicename
+
+
+=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 { 'part_device'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('devicepart')
+ || $self->ut_text('devicename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item part_export
+
+Returns a list of all exports (see L<FS::part_export>) for this device.
+
+=cut
+
+sub part_export {
+ my $self = shift;
+ map { qsearchs( 'part_export', { 'exportnum' => $_->exportnum } ) }
+ qsearch( 'export_device', { 'devicepart' => $self->devicepart } );
+}
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $opt = { 'table' => 'part_device',
+ 'params' => [],
+ 'formats' => { 'default' => [ 'devicename' ] },
+ 'default_csv' => 1,
+ };
+
+ FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm
new file mode 100644
index 0000000..c98c3f8
--- /dev/null
+++ b/FS/FS/part_event.pm
@@ -0,0 +1,444 @@
+package FS::part_event;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp qw(confess);
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::option_Common;
+use FS::m2name_Common;
+use FS::Conf;
+use FS::part_event_option;
+use FS::part_event_condition;
+use FS::cust_event;
+use FS::agent;
+
+@ISA = qw( FS::m2name_Common FS::option_Common ); # FS::Record );
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::part_event - Object methods for part_event records
+
+=head1 SYNOPSIS
+
+ use FS::part_event;
+
+ $record = new FS::part_event \%hash;
+ $record = new FS::part_event { 'column' => 'value' };
+
+ $error = $record->insert( { 'option' => 'value' } );
+ $error = $record->insert( \%options );
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->do_event( $direct_object );
+
+=head1 DESCRIPTION
+
+An FS::part_event object represents an event definition - a billing, collection
+or other callback which is triggered when certain customer, invoice, package or
+other conditions are met. FS::part_event inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item eventpart - primary key
+
+=item agentnum - Optional agentnum (see L<FS::agent>)
+
+=item event - event name
+
+=item eventtable - table name against which this event is triggered; currently "cust_bill" (the traditional invoice events), "cust_main" (customer events) or "cust_pkg (package events) (or "cust_statement")
+
+=item check_freq - how often events of this type are checked; currently "1d" (daily) and "1m" (monthly) are recognized. Note that the apprioriate freeside-daily and/or freeside-monthly cron job needs to be in place.
+
+=item weight - ordering for events
+
+=item action - event action (like part_bill_event.plan - eventcode plan)
+
+=item disabled - Disabled flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice event definition. To add the invoice event definition 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_event'; }
+
+=item insert [ HASHREF ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If a list or hash reference of options is supplied, part_export_option records
+are created (see L<FS::part_event_option>).
+
+=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 [ HASHREF | OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+If a list or hash reference of options is supplied, part_event_option
+records are created or modified (see L<FS::part_event_option>).
+
+=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 $error =
+ $self->ut_numbern('eventpart')
+ || $self->ut_text('event')
+ || $self->ut_enum('eventtable', [ $self->eventtables ] )
+ || $self->ut_enum('check_freq', [ '1d', '1m' ])
+ || $self->ut_number('weight')
+ || $self->ut_alpha('action')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->ut_agentnum_acl('agentnum', 'Edit global billing events')
+ ;
+ return $error if $error;
+
+ #XXX check action to make sure a module exists?
+ # well it'll die in _rebless...
+
+ $self->SUPER::check;
+}
+
+=item _rebless
+
+Reblesses the object into the FS::part_event::Action::ACTION class, where
+ACTION is the object's I<action> field.
+
+=cut
+
+sub _rebless {
+ my $self = shift;
+ my $action = $self->action or return $self;
+ #my $class = ref($self). "::$action";
+ my $class = "FS::part_event::Action::$action";
+ eval "use $class";
+ die $@ if $@;
+ bless($self, $class); # unless $@;
+ $self;
+}
+
+=item part_event_condition
+
+Returns the conditions associated with this event, as FS::part_event_condition
+objects (see L<FS::part_event_condition>)
+
+=cut
+
+sub part_event_condition {
+ my $self = shift;
+ qsearch( 'part_event_condition', { 'eventpart' => $self->eventpart } );
+}
+
+=item new_cust_event OBJECT
+
+Creates a new customer event (see L<FS::cust_event>) for the provided object.
+
+=cut
+
+sub new_cust_event {
+ my( $self, $object ) = @_;
+
+ confess "**** $object is not a ". $self->eventtable
+ if ref($object) ne "FS::". $self->eventtable;
+
+ my $pkey = $object->primary_key;
+
+ new FS::cust_event {
+ 'eventpart' => $self->eventpart,
+ 'tablenum' => $object->$pkey(),
+ '_date' => time, #i think we always want the real "now" here.
+ 'status' => 'new',
+ };
+}
+
+#surely this doesn't work
+sub reasontext { confess "part_event->reasontext deprecated"; }
+#=item reasontext
+#
+#Returns the text of any reason associated with this event.
+#
+#=cut
+#
+#sub reasontext {
+# my $self = shift;
+# my $r = qsearchs('reason', { 'reasonnum' => $self->reason });
+# if ($r){
+# $r->reason;
+# }else{
+# '';
+# }
+#}
+
+=item agent
+
+Returns the associated agent for this event, if any, as an FS::agent object.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=item templatename
+
+Returns the alternate invoice template name, if any, or false if there is
+no alternate template for this event.
+
+=cut
+
+sub templatename {
+
+ my $self = shift;
+ if ( $self->action =~ /^cust_bill_send_(alternate|agent)$/
+ && ( $self->option('agent_templatename')
+ || $self->option('templatename') )
+ )
+ {
+ $self->option('agent_templatename')
+ || $self->option('templatename');
+
+ } else {
+ '';
+ }
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item eventtable_labels
+
+Returns a hash reference of labels for eventtable values,
+i.e. 'cust_main'=>'Customer'
+
+=cut
+
+sub eventtable_labels {
+ #my $class = shift;
+
+ tie my %hash, 'Tie::IxHash',
+ 'cust_pkg' => 'Package',
+ 'cust_bill' => 'Invoice',
+ 'cust_main' => 'Customer',
+ 'cust_pay_batch' => 'Batch payment',
+ 'cust_statement' => 'Statement', #too general a name here? "Invoice group"?
+ ;
+
+ \%hash
+}
+
+=item eventtable_pkey_sql
+
+Returns a hash reference of full SQL primary key names for eventtable values,
+i.e. 'cust_main'=>'cust_main.custnum'
+
+=cut
+
+sub eventtable_pkey_sql {
+ my $class = shift;
+
+ my $hashref = $class->eventtable_pkey;
+
+ my %hash = map { $_ => "$_.". $hashref->{$_} } keys %$hashref;
+
+ \%hash;
+}
+
+=item eventtable_pkey
+
+Returns a hash reference of full SQL primary key names for eventtable values,
+i.e. 'cust_main'=>'custnum'
+
+=cut
+
+sub eventtable_pkey {
+ #my $class = shift;
+
+ {
+ 'cust_main' => 'custnum',
+ 'cust_bill' => 'invnum',
+ 'cust_pkg' => 'pkgnum',
+ 'cust_pay_batch' => 'paybatchnum',
+ 'cust_statement' => 'statementnum',
+ };
+}
+
+=item eventtables
+
+Returns a list of eventtable values (default ordering; suited for display).
+
+=cut
+
+sub eventtables {
+ my $class = shift;
+ my $eventtables = $class->eventtable_labels;
+ keys %$eventtables;
+}
+
+=item eventtables_runorder
+
+Returns a list of eventtable values (run order).
+
+=cut
+
+sub eventtables_runorder {
+ shift->eventtables; #same for now
+}
+
+=item check_freq_labels
+
+Returns a hash reference of labels for check_freq values,
+i.e. '1d'=>'daily'
+
+=cut
+
+sub check_freq_labels {
+ #my $class = shift;
+
+ #Tie::IxHash??
+ {
+ '1d' => 'daily',
+ '1m' => 'monthly',
+ };
+}
+
+=item actions [ EVENTTABLE ]
+
+Return information about the available actions. If an eventtable is specified,
+only return information about actions available for that eventtable.
+
+Information is returned as key-value pairs. Keys are event names. Values are
+hashrefs with the following keys:
+
+=over 4
+
+=item description
+
+=item eventtable_hashref
+
+=item option_fields
+
+=item default_weight
+
+=item deprecated
+
+=back
+
+See L<FS::part_event::Action> for more information.
+
+=cut
+
+#false laziness w/part_event_condition.pm
+#some false laziness w/part_export & part_pkg
+my %actions;
+foreach my $INC ( @INC ) {
+ foreach my $file ( glob("$INC/FS/part_event/Action/*.pm") ) {
+ warn "attempting to load Action from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/part_event/Action/: $file\n";
+ next;
+ };
+ my $mod = $1;
+ eval "use FS::part_event::Action::$mod;";
+ if ( $@ ) {
+ die "error using FS::part_event::Action::$mod (skipping): $@\n" if $@;
+ #warn "error using FS::part_event::Action::$mod (skipping): $@\n" if $@;
+ #next;
+ }
+ $actions{$mod} = {
+ ( map { $_ => "FS::part_event::Action::$mod"->$_() }
+ qw( description eventtable_hashref default_weight deprecated )
+ #option_fields_hashref
+ ),
+ 'option_fields' => [ "FS::part_event::Action::$mod"->option_fields() ],
+ };
+ }
+}
+
+sub actions {
+ my( $class, $eventtable ) = @_;
+ (
+ map { $_ => $actions{$_} }
+ sort { $actions{$a}->{'default_weight'}<=>$actions{$b}->{'default_weight'} }
+ $class->all_actions( $eventtable )
+ );
+
+}
+
+=item all_actions [ EVENTTABLE ]
+
+Returns a list of just the action names
+
+=cut
+
+sub all_actions {
+ my ( $class, $eventtable ) = @_;
+
+ grep { !$eventtable || $actions{$_}->{'eventtable_hashref'}{$eventtable} }
+ keys %actions
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::part_event_option>, L<FS::part_event_condition>, L<FS::cust_main>,
+L<FS::cust_pkg>, 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_event/Action.pm b/FS/FS/part_event/Action.pm
new file mode 100644
index 0000000..45219a3
--- /dev/null
+++ b/FS/FS/part_event/Action.pm
@@ -0,0 +1,240 @@
+package FS::part_event::Action;
+
+use strict;
+use base qw( FS::part_event );
+use Tie::IxHash;
+
+=head1 NAME
+
+FS::part_event::Action - Base class for event actions
+
+=head1 SYNOPSIS
+
+package FS::part_event::Action::myaction;
+
+use base FS::part_event::Action;
+
+=head1 DESCRIPTION
+
+FS::part_event::Action is a base class for event action classes.
+
+=head1 METHODS
+
+These methods are implemented in each action class.
+
+=over 4
+
+=item description
+
+Action classes must define a description method. This method should return a
+scalar description of the action.
+
+=item eventtable_hashref
+
+Action classes must define a eventtable_hashref method if they can only be
+triggered against some kinds of tables. This method should return a hash
+reference of eventtables (values set true indicate the action can be performed):
+
+ sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ 'cust_pay_batch' => 0,
+ };
+ }
+
+=cut
+
+#fallback
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ 'cust_pay_batch' => 1,
+ };
+}
+
+=item event_stage
+
+Action classes may define an event_stage method to indicate a preference
+for being run at a non-standard stage of the billing and collection process.
+
+This method may currently return "collect" (the default) or "pre-bill".
+
+=cut
+
+sub event_stage {
+ 'collect';
+}
+
+=item option_fields
+
+Action classes may define an option_fields method to indicate that they
+accept one or more options.
+
+This method should return a list of option names and option descriptions.
+Each option description can be a scalar description, for simple options, or a
+hashref with the following values:
+
+=over 4
+
+=item label - Description
+
+=item type - Currently text, money, checkbox, checkbox-multiple, select, select-agent, select-pkg_class, select-part_referral, select-table, fixed, hidden, (others can be implemented as httemplate/elements/tr-TYPE.html mason components). Defaults to text.
+
+=item size - Size for text fields
+
+=item options - For checkbox-multiple and select, a list reference of available option values.
+
+=item option_labels - For select, a hash reference of availble option values and labels.
+
+=item value - for checkbox, fixed, hidden
+
+=item table - for select-table
+
+=item name_col - for select-table
+
+=item NOTE: See httemplate/elements/select-table.html for a full list of the optinal options for the select-table type
+
+=back
+
+NOTE: A database connection is B<not> yet available when this subroutine is
+executed.
+
+Example:
+
+ sub option_fields {
+ (
+ 'field' => 'description',
+
+ 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+
+ 'third_field' => { 'label' => 'Types',
+ 'type' => 'select',
+ 'options' => [ 'h', 's' ],
+ 'option_labels' => { 'h' => 'Happy',
+ 's' => 'Sad',
+ },
+ );
+ }
+
+=cut
+
+#fallback
+sub option_fields {
+ ();
+}
+
+=item default_weight
+
+Action classes may define a default weighting. Weights control execution order
+relative to other actions (that are triggered at the same time).
+
+=cut
+
+#fallback
+sub default_weight {
+ 100;
+}
+
+=item deprecated
+
+Action classes may define a deprecated method that returns true, indicating
+that this action is deprecated.
+
+=cut
+
+#default
+sub deprecated {
+ 0;
+}
+
+=item do_action CUSTOMER_EVENT_OBJECT
+
+Action classes must define an action method. This method is triggered if
+all conditions have been met.
+
+The object which triggered the event (an FS::cust_main, FS::cust_bill or
+FS::cust_pkg object) is passed as an argument.
+
+To retreive option values, call the option method on the desired option, i.e.:
+
+ my( $self, $cust_object ) = @_;
+ $value_of_field = $self->option('field');
+
+To indicate sucessful completion, simply return. Optionally, you can return a
+string of information status information about the sucessful completion, or
+simply return the empty string.
+
+To indicate a failure and that this event should retry, die with the desired
+error message.
+
+=back
+
+=head1 BASE METHODS
+
+These methods are defined in the base class for use in action classes.
+
+=over 4
+
+=item cust_main CUST_OBJECT
+
+Return the customer object (see L<FS::cust_main>) associated with the provided
+object (the object itself if it is already a customer object).
+
+=cut
+
+sub cust_main {
+ my( $self, $cust_object ) = @_;
+
+ $cust_object->isa('FS::cust_main') ? $cust_object : $cust_object->cust_main;
+
+}
+
+=item option_label OPTIONNAME
+
+Returns the label for the specified option name.
+
+=cut
+
+sub option_label {
+ my( $self, $optionname ) = @_;
+
+ my %option_fields = $self->option_fields;
+
+ ref( $option_fields{$optionname} )
+ ? $option_fields{$optionname}->{'label'}
+ : $option_fields{$optionname}
+ or $optionname;
+}
+
+=item option_fields_hashref
+
+Returns the option fields as an (ordered) hash reference.
+
+=cut
+
+sub option_fields_hashref {
+ my $self = shift;
+ tie my %hash, 'Tie::IxHash', $self->option_fields;
+ \%hash;
+}
+
+=item option_fields_listref
+
+Returns just the option field names as a list reference.
+
+=cut
+
+sub option_fields_listref {
+ my $self = shift;
+ my $hashref = $self->option_fields_hashref;
+ [ keys %$hashref ];
+}
+
+=back
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_event/Action/Mixin/credit_pkg.pm b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
new file mode 100644
index 0000000..aeda92f
--- /dev/null
+++ b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
@@ -0,0 +1,63 @@
+package FS::part_event::Action::Mixin::credit_pkg;
+
+use strict;
+
+sub eventtable_hashref {
+ { 'cust_pkg' => 1 };
+}
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'percent' => { 'label' => 'Percent',
+ 'type' => 'input-percentage',
+ 'default' => '100',
+ },
+ 'what' => { 'label' => 'Of',
+ 'type' => 'select',
+ #add additional ways to specify in the package def
+ 'options' => [ qw( base_recur_permonth unit_setup recur_cost_permonth setup_cost ) ],
+ 'labels' => { 'base_recur_permonth' => 'Base monthly fee',
+ 'unit_setup' => 'Setup fee',
+ 'recur_cost_permonth' => 'Monthly cost',
+ 'setup_cost' => 'Setup cost',
+ },
+ },
+ );
+
+}
+
+#my %no_cust_pkg = ( 'setup_cost' => 1 );
+
+sub _calc_credit {
+ my( $self, $cust_pkg ) = @_;
+
+ my $cust_main = $self->cust_main($cust_pkg);
+
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ my $what = $self->option('what');
+
+ #false laziness w/Condition/cust_payments_pkg.pm
+ if ( $what =~ /_permonth$/ ) { #huh. yuck.
+ if ( $part_pkg->freq !~ /^\d+$/ ) {
+ die 'WARNING: Not crediting for package '. $cust_pkg->pkgnum.
+ ' ( customer '. $cust_pkg->custnum. ')'.
+ ' - credits not (yet) available for '.
+ ' packages with '. $part_pkg->freq_pretty. ' frequency';
+ }
+ }
+
+ my $percent = $self->option('percent');
+
+ #my @arg = $no_cust_pkg{$what} ? () : ($cust_pkg);
+ my @arg = ($what eq 'setup_cost') ? () : ($cust_pkg);
+
+ sprintf('%.2f', $part_pkg->$what(@arg) * $percent / 100 );
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/addpost.pm b/FS/FS/part_event/Action/addpost.pm
new file mode 100644
index 0000000..f92e72e
--- /dev/null
+++ b/FS/FS/part_event/Action/addpost.pm
@@ -0,0 +1,20 @@
+package FS::part_event::Action::addpost;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Add postal invoicing'; }
+
+sub default_weight { 20; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ $cust_main->invoicing_list_addpost();
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/apply.pm b/FS/FS/part_event/Action/apply.pm
new file mode 100644
index 0000000..823d1e0
--- /dev/null
+++ b/FS/FS/part_event/Action/apply.pm
@@ -0,0 +1,24 @@
+package FS::part_event::Action::apply;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ 'Apply unapplied payments and credits';
+}
+
+sub deprecated { 1; }
+
+sub default_weight { 70; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ $cust_main->apply_payments_and_credits;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/bill.pm b/FS/FS/part_event/Action/bill.pm
new file mode 100644
index 0000000..b96614d
--- /dev/null
+++ b/FS/FS/part_event/Action/bill.pm
@@ -0,0 +1,26 @@
+package FS::part_event::Action::bill;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ #'Generate invoices (normally only used with a <i>Late Fee</i> event)';
+ 'Generate invoices (normally only used with a Late Fee event)';
+}
+
+sub deprecated { 1; }
+
+sub default_weight { 60; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my $error = $cust_main->bill;
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cancel.pm b/FS/FS/part_event/Action/cancel.pm
new file mode 100644
index 0000000..b9d6d29
--- /dev/null
+++ b/FS/FS/part_event/Action/cancel.pm
@@ -0,0 +1,30 @@
+package FS::part_event::Action::cancel;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Cancel'; }
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'C',
+ },
+ );
+}
+
+sub default_weight { 20; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my $error = $cust_main->cancel( 'reason' => $self->option('reasonnum') );
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/collect.pm b/FS/FS/part_event/Action/collect.pm
new file mode 100644
index 0000000..9881440
--- /dev/null
+++ b/FS/FS/part_event/Action/collect.pm
@@ -0,0 +1,26 @@
+package FS::part_event::Action::collect;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ #'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)';
+ 'Collect on invoices (normally only used with a Late Fee and Generate Invoice events)';
+}
+
+sub deprecated { 1; }
+
+sub default_weight { 80; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my $error = $cust_main->collect;
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_batch.pm b/FS/FS/part_event/Action/cust_bill_batch.pm
new file mode 100644
index 0000000..50c306a
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_batch.pm
@@ -0,0 +1,25 @@
+package FS::part_event::Action::cust_bill_batch;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Add card or check to a pending batch'; }
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 40; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->batch_card; # ( %options ); #XXX options??
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_comp.pm b/FS/FS/part_event/Action/cust_bill_comp.pm
new file mode 100644
index 0000000..76fd274
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_comp.pm
@@ -0,0 +1,28 @@
+package FS::part_event::Action::cust_bill_comp;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Pay invoice with a complimentary "payment"'; }
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 30; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ my $error = $cust_bill->comp;
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_email.pm b/FS/FS/part_event/Action/cust_bill_email.pm
new file mode 100644
index 0000000..a5cd861
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_email.pm
@@ -0,0 +1,23 @@
+package FS::part_event::Action::cust_bill_email;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Send invoice (email only)'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 51; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->email;
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_fee_percent.pm b/FS/FS/part_event/Action/cust_bill_fee_percent.pm
new file mode 100644
index 0000000..48daf15
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_fee_percent.pm
@@ -0,0 +1,28 @@
+package FS::part_event::Action::cust_bill_fee_percent;
+
+use strict;
+use base qw( FS::part_event::Action::fee );
+use Tie::IxHash;
+
+sub description { 'Late fee (percentage of invoice)'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ my $class = shift;
+
+ my $t = tie my %option_fields, 'Tie::IxHash', $class->SUPER::option_fields();
+ $t->Shift; #assumes charge is first
+ $t->Unshift( 'percent' => { label=>'Percent', size=>2, } );
+
+ %option_fields;
+}
+
+sub _calc_fee {
+ my( $self, $cust_bill ) = @_;
+ sprintf('%.2f', $cust_bill->owed * $self->option('percent') / 100 );
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_card.pm b/FS/FS/part_event/Action/cust_bill_realtime_card.pm
new file mode 100644
index 0000000..c1fdba9
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_realtime_card.pm
@@ -0,0 +1,28 @@
+package FS::part_event::Action::cust_bill_realtime_card;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ #'Run card with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
+ 'Run card with a Business::OnlinePayment realtime gateway';
+}
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 30; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->realtime_card;
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_check.pm b/FS/FS/part_event/Action/cust_bill_realtime_check.pm
new file mode 100644
index 0000000..11b13a9
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_realtime_check.pm
@@ -0,0 +1,28 @@
+package FS::part_event::Action::cust_bill_realtime_check;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ #'Run check with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
+ 'Run check with a Business::OnlinePayment realtime gateway';
+}
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 30; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->realtime_ach;
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
new file mode 100644
index 0000000..cd03ddc
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
@@ -0,0 +1,28 @@
+package FS::part_event::Action::cust_bill_realtime_lec;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ #'Run phone bill ("LEC") billing with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
+ 'Run phone bill ("LEC") billing with a Business::OnlinePayment realtime gateway';
+}
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 30; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->realtime_lec;
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send.pm b/FS/FS/part_event/Action/cust_bill_send.pm
new file mode 100644
index 0000000..587a7c6
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send.pm
@@ -0,0 +1,20 @@
+package FS::part_event::Action::cust_bill_send;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Send invoice (email/print/fax)'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ $cust_bill->send;
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_agent.pm b/FS/FS/part_event/Action/cust_bill_send_agent.pm
new file mode 100644
index 0000000..670a32c
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send_agent.pm
@@ -0,0 +1,42 @@
+package FS::part_event::Action::cust_bill_send_agent;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ 'Send invoice (email/print/fax) with alternate template, for specific agents';
+}
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'agentnum' => { label => 'Only for agent(s)',
+ type => 'select-agent',
+ multiple => 1
+ },
+ 'agent_templatename' => { label => 'Template',
+ type => 'select-invoice_template',
+ },
+ 'agent_invoice_from' => 'Invoice email From: address',
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->send(
+ $self->option('agent_templatename'),
+ [ split(/\s*,\s*/, $self->option('agentnum') ) ],
+ $self->option('agent_invoice_from'),
+ );
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_alternate.pm b/FS/FS/part_event/Action/cust_bill_send_alternate.pm
new file mode 100644
index 0000000..cfd9264
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send_alternate.pm
@@ -0,0 +1,31 @@
+package FS::part_event::Action::cust_bill_send_alternate;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Send invoice (email/print/fax) with alternate template'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'templatename' => { label => 'Template',
+ type => 'select-invoice_template',
+ },
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->send( $self->option('templatename') );
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
new file mode 100644
index 0000000..bf47268
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
@@ -0,0 +1,50 @@
+package FS::part_event::Action::cust_bill_send_csv_ftp;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Upload CSV invoice data to an FTP server'; }
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'ftpformat' => { label => 'Format',
+ type =>'select',
+ options => ['default', 'billco'],
+ option_labels => { 'default' => 'Default',
+ 'billco' => 'Billco',
+ },
+ },
+ 'ftpserver' => 'FTP server',
+ 'ftpusername' => 'FTP username',
+ 'ftppassword' => 'FTP password',
+ 'ftpdir' => 'FTP directory',
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->send_csv(
+ 'protocol' => 'ftp',
+ 'server' => $self->option('ftpserver'),
+ 'username' => $self->option('ftpusername'),
+ 'password' => $self->option('ftppassword'),
+ 'dir' => $self->option('ftpdir'),
+ 'format' => $self->option('ftpformat'),
+ );
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_if_newest.pm b/FS/FS/part_event/Action/cust_bill_send_if_newest.pm
new file mode 100644
index 0000000..083da8b
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send_if_newest.pm
@@ -0,0 +1,38 @@
+package FS::part_event::Action::cust_bill_send_if_newest;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+ 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)';
+}
+
+# XXX is this handled better by something against customers??
+#sub deprecated {
+# 1;
+#}
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'if_newest_templatename' => { label => 'Template',
+ type => 'select-invoice_template',
+ },
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->send( $self->option('templatename') );
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_send_reminder.pm b/FS/FS/part_event/Action/cust_bill_send_reminder.pm
new file mode 100644
index 0000000..2ba8136
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_send_reminder.pm
@@ -0,0 +1,31 @@
+package FS::part_event::Action::cust_bill_send_reminder;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Send invoice (email/print/fax) reminder'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'notice_name' => 'Reminder name',
+ #'notes' => { 'label' => 'Reminder notes' },
+ #include standard notes? no/prepend/append
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ #my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->send({ 'notice_name' => $self->option('notice_name') });
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm
new file mode 100644
index 0000000..43d2300
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm
@@ -0,0 +1,59 @@
+package FS::part_event::Action::cust_bill_spool_csv;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Spool CSV invoice data'; }
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'spoolformat' => { label => 'Format',
+ type => 'select',
+ options => ['default', 'billco'],
+ option_labels => { 'default' => 'Default',
+ 'billco' => 'Billco',
+ },
+ },
+ 'spooldest' => { label => 'For destination',
+ type => 'select',
+ options => [ '', qw( POST EMAIL FAX ) ],
+ option_labels => { '' => '(all)',
+ 'POST' => 'Postal Mail',
+ 'EMAIL' => 'Email',
+ 'FAX' => 'Fax',
+ },
+ },
+ 'spoolbalanceover' => { label =>
+ 'If balance (this invoice and previous) over',
+ type => 'money',
+ },
+ 'spoolagent_spools' => { label => 'Individual per-agent spools',
+ type => 'checkbox',
+ value => '1',
+ },
+ );
+}
+
+sub default_weight { 50; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ $cust_bill->spool_csv(
+ 'format' => $self->option('spoolformat'),
+ 'dest' => $self->option('spooldest'),
+ 'balanceover' => $self->option('spoolbalanceover'),
+ 'agent_spools' => $self->option('spoolagent_spools'),
+ );
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm b/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm
new file mode 100644
index 0000000..13188ab
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm
@@ -0,0 +1,42 @@
+package FS::part_event::Action::cust_bill_suspend_if_balance;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Suspend if balance (this invoice and previous) over'; }
+
+sub deprecated { 1; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ (
+ 'balanceover' => { label=>'Balance over', type=>'money', }, # size=>7 },
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'S',
+ },
+ );
+}
+
+sub default_weight { 10; }
+
+sub do_action {
+ my( $self, $cust_bill ) = @_;
+
+ #my $cust_main = $self->cust_main($cust_bill);
+ my $cust_main = $cust_bill->cust_main;
+
+ my @err = $cust_bill->cust_suspend_if_balance_over(
+ $self->option('balanceover'),
+ 'reason' => $self->option('reasonnum'),
+ );
+
+ die join(' / ', @err) if scalar(@err);
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_statement.pm b/FS/FS/part_event/Action/cust_statement.pm
new file mode 100644
index 0000000..2d9e877
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_statement.pm
@@ -0,0 +1,39 @@
+package FS::part_event::Action::cust_statement;
+
+use strict;
+
+use base qw( FS::part_event::Action );
+
+use FS::cust_statement;
+
+sub description {
+ 'Group invoices into an informational statement.';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub default_weight {
+ 90;
+}
+
+sub do_action {
+ my( $self, $cust_main ) = @_;
+
+ #my( $self, $object ) = @_;
+ #my $cust_main = $self->cust_main($object);
+
+ my $cust_statement = new FS::cust_statement {
+ 'custnum' => $cust_main->custnum
+ };
+ my $error = $cust_statement->insert;
+ die $error if $error;
+
+ '';
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/cust_statement_send.pm b/FS/FS/part_event/Action/cust_statement_send.pm
new file mode 100644
index 0000000..74cc48c
--- /dev/null
+++ b/FS/FS/part_event/Action/cust_statement_send.pm
@@ -0,0 +1,26 @@
+package FS::part_event::Action::cust_statement_send;
+
+use strict;
+
+use base qw( FS::part_event::Action );
+
+sub description {
+ 'Send statement (email/print/fax)';
+}
+
+sub eventtable_hashref {
+ { 'cust_statement' => 1, };
+}
+
+sub default_weight {
+ 95;
+}
+
+sub do_action {
+ my( $self, $cust_statement ) = @_;
+
+ $cust_statement->send( 'statement' ); #XXX configure
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/fee.pm b/FS/FS/part_event/Action/fee.pm
new file mode 100644
index 0000000..68288d0
--- /dev/null
+++ b/FS/FS/part_event/Action/fee.pm
@@ -0,0 +1,58 @@
+package FS::part_event::Action::fee;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Late fee (flat)'; }
+
+sub event_stage { 'pre-bill'; }
+
+sub option_fields {
+ (
+ 'charge' => { label=>'Amount', type=>'money', }, # size=>7, },
+ 'reason' => 'Reason (invoice line item)',
+ 'classnum' => { label=>'Package class' => type=>'select-pkg_class', },
+ 'taxclass' => { label=>'Tax class', type=>'select-taxclass', },
+ 'setuptax' => { label=>'Late fee is tax exempt',
+ type=>'checkbox', value=>'Y' },
+ 'nextbill' => { label=>'Hold late fee until next invoice',
+ type=>'checkbox', value=>'Y' },
+ );
+}
+
+sub default_weight { 10; }
+
+sub _calc_fee {
+ #my( $self, $cust_object ) = @_;
+ my $self = shift;
+ $self->option('charge');
+}
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my $conf = new FS::Conf;
+
+ my %charge = (
+ 'amount' => $self->_calc_fee($cust_object),
+ 'pkg' => $self->option('reason'),
+ 'taxclass' => $self->option('taxclass'),
+ 'classnum' => ( $self->option('classnum')
+ || scalar($conf->config('finance_pkgclass')) ),
+ 'setuptax' => $self->option('setuptax'),
+ );
+
+ #unless its more than N months away?
+ $charge{'start_date'} = $cust_main->next_bill_date
+ if $self->option('nextbill');
+
+ my $error = $cust_main->charge( \%charge );
+
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_agent_credit.pm b/FS/FS/part_event/Action/pkg_agent_credit.pm
new file mode 100644
index 0000000..4bcee98
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_agent_credit.pm
@@ -0,0 +1,39 @@
+package FS::part_event::Action::pkg_agent_credit;
+
+use strict;
+use base qw( FS::part_event::Action::pkg_referral_credit );
+
+sub description { 'Credit the agent a specific amount'; }
+
+#a little false laziness w/pkg_referral_credit
+sub do_action {
+ my( $self, $cust_pkg, $cust_event ) = @_;
+
+ my $cust_main = $self->cust_main($cust_pkg);
+
+ my $agent = $cust_main->agent;
+ return "No customer record for agent ". $agent->agent
+ unless $agent->agent_custnum;
+
+ my $agent_cust_main = $agent->agent_cust_main;
+ #? or return "No customer record for agent ". $agent->agent;
+
+ my $amount = $self->_calc_credit($cust_pkg);
+ return '' unless $amount > 0;
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $error = $agent_cust_main->credit(
+ $amount,
+ \$reasonnum,
+ 'eventnum' => $cust_event->eventnum,
+ 'addlinfo' => 'for customer #'. $cust_main->display_custnum.
+ ': '.$cust_main->name,
+ );
+ die "Error crediting customer ". $agent_cust_main->custnum.
+ " for agent commission: $error"
+ if $error;
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_agent_credit_pkg.pm b/FS/FS/part_event/Action/pkg_agent_credit_pkg.pm
new file mode 100644
index 0000000..b3e1181
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_agent_credit_pkg.pm
@@ -0,0 +1,9 @@
+package FS::part_event::Action::pkg_agent_credit_pkg;
+
+use strict;
+use base qw( FS::part_event::Action::Mixin::credit_pkg
+ FS::part_event::Action::pkg_agent_credit );
+
+sub description { 'Credit the agent an amount based on the referred package'; }
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_employee_credit.pm b/FS/FS/part_event/Action/pkg_employee_credit.pm
new file mode 100644
index 0000000..64dd8b2
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_employee_credit.pm
@@ -0,0 +1,39 @@
+package FS::part_event::Action::pkg_employee_credit;
+
+use strict;
+use base qw( FS::part_event::Action::pkg_referral_credit );
+
+sub description { 'Credit the ordering employee a specific amount'; }
+
+#a little false laziness w/pkg_referral_credit
+sub do_action {
+ my( $self, $cust_pkg, $cust_event ) = @_;
+
+ my $cust_main = $self->cust_main($cust_pkg);
+
+ my $employee = $cust_pkg->access_user;
+ return "No customer record for employee ". $employee->username
+ unless $employee->user_custnum;
+
+ my $employee_cust_main = $employee->user_cust_main;
+ #? or return "No customer record for employee ". $employee->username;
+
+ my $amount = $self->_calc_credit($cust_pkg);
+ return '' unless $amount > 0;
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $error = $employee_cust_main->credit(
+ $amount,
+ \$reasonnum,
+ 'eventnum' => $cust_event->eventnum,
+ 'addlinfo' => 'for customer #'. $cust_main->display_custnum.
+ ': '.$cust_main->name,
+ );
+ die "Error crediting customer ". $employee_cust_main->custnum.
+ " for employee commission: $error"
+ if $error;
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_employee_credit_pkg.pm b/FS/FS/part_event/Action/pkg_employee_credit_pkg.pm
new file mode 100644
index 0000000..e3b867f
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_employee_credit_pkg.pm
@@ -0,0 +1,9 @@
+package FS::part_event::Action::pkg_employee_credit_pkg;
+
+use strict;
+use base qw( FS::part_event::Action::Mixin::credit_pkg
+ FS::part_event::Action::pkg_employee_credit );
+
+sub description { 'Credit the ordering employee an amount based on the referred package'; }
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_referral_credit.pm b/FS/FS/part_event/Action/pkg_referral_credit.pm
new file mode 100644
index 0000000..e7c92d6
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_referral_credit.pm
@@ -0,0 +1,62 @@
+package FS::part_event::Action::pkg_referral_credit;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Credit the referring customer a specific amount'; }
+
+sub eventtable_hashref {
+ { 'cust_pkg' => 1 };
+}
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'amount' => { 'label' => 'Credit amount',
+ 'type' => 'money',
+ },
+ );
+
+}
+
+sub do_action {
+ my( $self, $cust_pkg, $cust_event ) = @_;
+
+ my $cust_main = $self->cust_main($cust_pkg);
+
+# my $part_pkg = $cust_pkg->part_pkg;
+
+ return 'No referring customer' unless $cust_main->referral_custnum;
+
+ my $referring_cust_main = $cust_main->referring_cust_main;
+ return 'Referring customer is cancelled'
+ if $referring_cust_main->status eq 'cancelled';
+
+ my $amount = $self->_calc_credit($cust_pkg);
+ return '' unless $amount > 0;
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $error = $referring_cust_main->credit(
+ $amount,
+ \$reasonnum,
+ 'eventnum' => $cust_event->eventnum,
+ 'addlinfo' => 'for customer #'. $cust_main->display_custnum.
+ ': '.$cust_main->name,
+ );
+ die "Error crediting customer ". $cust_main->referral_custnum.
+ " for referral: $error"
+ if $error;
+
+}
+
+sub _calc_credit {
+ my( $self, $cust_pkg ) = @_;
+
+ $self->option('amount');
+}
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm b/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm
new file mode 100644
index 0000000..667c4ce
--- /dev/null
+++ b/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm
@@ -0,0 +1,9 @@
+package FS::part_event::Action::pkg_referral_credit_pkg;
+
+use strict;
+use base qw( FS::part_event::Action::Mixin::credit_pkg
+ FS::part_event::Action::pkg_referral_credit );
+
+sub description { 'Credit the referring customer an amount based on the referred package'; }
+
+1;
diff --git a/FS/FS/part_event/Action/suspend.pm b/FS/FS/part_event/Action/suspend.pm
new file mode 100644
index 0000000..c77728e
--- /dev/null
+++ b/FS/FS/part_event/Action/suspend.pm
@@ -0,0 +1,32 @@
+package FS::part_event::Action::suspend;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Suspend'; }
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'S',
+ },
+ );
+}
+
+sub default_weight { 10; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my @err = $cust_main->suspend( 'reason' => $self->option('reasonnum') );
+
+ die join(' / ', @err) if scalar(@err);
+
+ '';
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/suspend_if_pkgpart.pm b/FS/FS/part_event/Action/suspend_if_pkgpart.pm
new file mode 100644
index 0000000..6f2007c
--- /dev/null
+++ b/FS/FS/part_event/Action/suspend_if_pkgpart.pm
@@ -0,0 +1,40 @@
+package FS::part_event::Action::suspend_if_pkgpart;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Suspend packages'; }
+
+#i should be deprecated in favor of using the if_pkgpart condition
+
+sub option_fields {
+ (
+ 'if_pkgpart' => { 'label' => 'Suspend packages:',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'S',
+ },
+ );
+}
+
+sub default_weight { 10; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my @err = $cust_main->suspend_if_pkgpart( {
+ 'pkgparts' => [ split(/\s*,\s*/, $self->option('if_pkgpart') ) ],
+ 'reason' => $self->option('reasonnum'),
+ } );
+
+ die join(' / ', @err) if scalar(@err);
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/suspend_unless_pkgpart.pm b/FS/FS/part_event/Action/suspend_unless_pkgpart.pm
new file mode 100644
index 0000000..efc7a2d
--- /dev/null
+++ b/FS/FS/part_event/Action/suspend_unless_pkgpart.pm
@@ -0,0 +1,40 @@
+package FS::part_event::Action::suspend_unless_pkgpart;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Suspend packages except'; }
+
+#i should be deprecated in favor of using the unless_pkgpart condition
+
+sub option_fields {
+ (
+ 'unless_pkgpart' => { 'label' => 'Suspend packages except:',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'S',
+ },
+ );
+}
+
+sub default_weight { 10; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my @err = $cust_main->suspend_unless_pkgpart( {
+ 'pkgparts' => [ split(/\s*,\s*/, $self->option('unless_pkgpart') ) ],
+ 'reason' => $self->option('reasonnum'),
+ } );
+
+ die join(' / ', @err) if scalar(@err);
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/writeoff.pm b/FS/FS/part_event/Action/writeoff.pm
new file mode 100644
index 0000000..8529d29
--- /dev/null
+++ b/FS/FS/part_event/Action/writeoff.pm
@@ -0,0 +1,33 @@
+package FS::part_event::Action::writeoff;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description { 'Write off bad debt with a credit entry.'; }
+
+sub option_fields {
+ (
+ #'charge' => { label=>'Amount', type=>'money', }, # size=>7, },
+ 'reasonnum' => { 'label' => 'Reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ );
+}
+
+sub default_weight { 65; }
+
+sub do_action {
+ my( $self, $cust_object ) = @_;
+
+ my $cust_main = $self->cust_main($cust_object);
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $error = $cust_main->credit( $cust_main->balance, \$reasonnum );
+ die $error if $error;
+
+ '';
+}
+
+1;
diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm
new file mode 100644
index 0000000..ddd8a61
--- /dev/null
+++ b/FS/FS/part_event/Condition.pm
@@ -0,0 +1,448 @@
+package FS::part_event::Condition;
+
+use strict;
+use base qw( FS::part_event_condition );
+use Time::Local qw(timelocal_nocheck);
+use FS::UID qw( driver_name );
+
+=head1 NAME
+
+FS::part_event::Condition - Base class for event conditions
+
+=head1 SYNOPSIS
+
+package FS::part_event::Condition::mycondition;
+
+use base FS::part_event::Condition;
+
+=head1 DESCRIPTION
+
+FS::part_event::Condition is a base class for event conditions classes.
+
+=head1 METHODS
+
+These methods are implemented in each condition class.
+
+=over 4
+
+=item description
+
+Condition classes must define a description method. This method should return
+a scalar description of the condition.
+
+=item eventtable_hashref
+
+Condition classes must define an eventtable_hashref method if they can only be
+tested against some kinds of tables. This method should return a hash reference
+of eventtables (values set true indicate the condition can be tested):
+
+ sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ 'cust_pay_batch' => 0,
+ 'cust_statement' => 0,
+ };
+ }
+
+=cut
+
+#fallback
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ 'cust_pay_batch' => 1,
+ 'cust_statement' => 1,
+ };
+}
+
+=item option_fields
+
+Condition classes may define an option_fields method to indicate that they
+accept one or more options.
+
+This method should return a list of option names and option descriptions.
+Each option description can be a scalar description, for simple options, or a
+hashref with the following values:
+
+=over 4
+
+=item label - Description
+
+=item type - Currently text, money, checkbox, checkbox-multiple, select, select-agent, select-pkg_class, select-part_referral, select-table, fixed, hidden, (others can be implemented as httemplate/elements/tr-TYPE.html mason components). Defaults to text.
+
+=item options - For checkbox-multiple and select, a list reference of available option values.
+
+=item option_labels - For checkbox-multiple (and select?), a hash reference of availble option values and labels.
+
+=item value - for checkbox, fixed, hidden (also a default for text, money, more?)
+
+=item table - for select-table
+
+=item name_col - for select-table
+
+=item NOTE: See httemplate/elements/select-table.html for a full list of the optinal options for the select-table type
+
+=back
+
+NOTE: A database connection is B<not> yet available when this subroutine is
+executed.
+
+Example:
+
+ sub option_fields {
+ (
+ 'field' => 'description',
+
+ 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+
+ 'third_field' => { 'label' => 'Types',
+ 'type' => 'checkbox-multiple',
+ 'options' => [ 'h', 's' ],
+ 'option_labels' => { 'h' => 'Happy',
+ 's' => 'Sad',
+ },
+ );
+ }
+
+=cut
+
+#fallback
+sub option_fields {
+ ();
+}
+
+=item condition CUSTOMER_EVENT_OBJECT
+
+Condition classes must define a condition method. This method is evaluated
+to determine if the condition has been met. The object which triggered the
+event (an FS::cust_main, FS::cust_bill or FS::cust_pkg object) is passed as
+the first argument. Additional arguments are list of key-value pairs.
+
+To retreive option values, call the option method on the desired option, i.e.:
+
+ my( $self, $cust_object, %opts ) = @_;
+ $value_of_field = $self->option('field');
+
+Available additional arguments:
+
+ $time = $opt{'time'}; #use this instead of time or $^T
+
+ $cust_event = $opt{'cust_event'}; #to retreive the cust_event object being tested
+
+Return a true value if the condition has been met, and a false value if it has
+not.
+
+=item condition_sql EVENTTABLE
+
+Condition classes may optionally define a condition_sql method. This B<class>
+method should return an SQL fragment that tests for this condition. The
+fragment is evaluated and a true value of this expression indicates that the
+condition has been met. The event table (cust_main, cust_bill or cust_pkg) is
+passed as an argument.
+
+This method is used for optimizing event queries. You may want to add indices
+for any columns referenced. It is acceptable to return an SQL fragment which
+partially tests the condition; doing so will still reduce the number of
+records which much be returned and tested with the B<condition> method.
+
+=cut
+
+# fallback.
+sub condition_sql {
+ my( $class, $eventtable ) = @_;
+ #...
+ 'true';
+}
+
+=item disabled
+
+Condition classes may optionally define a disabled method. Returning a true
+value disbles the condition entirely.
+
+=cut
+
+sub disabled {
+ 0;
+}
+
+=item implicit_flag
+
+This is used internally by the I<once> and I<balance> conditions. You probably
+do B<not> want to define this method for new custom conditions, unless you're
+sure you want B<every> new action to start with your condition.
+
+Condition classes may define an implicit_flag method that returns true to
+indicate that all new events should start with this condition. (Currently,
+condition classes which do so should be applicable to all kinds of
+I<eventtable>s.) The numeric value of the flag also defines the ordering of
+implicit conditions.
+
+=cut
+
+#fallback
+sub implicit_flag { 0; }
+
+=item remove_warning
+
+Again, used internally by the I<once> and I<balance> conditions; probably not
+a good idea for new custom conditions.
+
+Condition classes may define a remove_warning method containing a string
+warning message to enable a confirmation dialog triggered when the condition
+is removed from an event.
+
+=cut
+
+#fallback
+sub remove_warning { ''; }
+
+=item order_sql
+
+This is used internally by the I<balance_age> and I<cust_bill_age> conditions
+to declare ordering; probably not of general use for new custom conditions.
+
+=item order_sql_weight
+
+In conjunction with order_sql, this defines which order the ordering fragments
+supplied by different B<order_sql> should be used.
+
+=cut
+
+sub order_sql_weight { ''; }
+
+=back
+
+=head1 BASE METHODS
+
+These methods are defined in the base class for use in condition classes.
+
+=over 4
+
+=item cust_main CUST_OBJECT
+
+Return the customer object (see L<FS::cust_main>) associated with the provided
+object (the object itself if it is already a customer object).
+
+=cut
+
+sub cust_main {
+ my( $self, $cust_object ) = @_;
+
+ $cust_object->isa('FS::cust_main') ? $cust_object : $cust_object->cust_main;
+
+}
+
+=item option_label OPTIONNAME
+
+Returns the label for the specified option name.
+
+=cut
+
+sub option_label {
+ my( $self, $optionname ) = @_;
+
+ my %option_fields = $self->option_fields;
+
+ ref( $option_fields{$optionname} )
+ ? $option_fields{$optionname}->{'label'}
+ : $option_fields{$optionname}
+ or $optionname;
+}
+
+=back
+
+=item option_age_from OPTION FROM_TIMESTAMP
+
+Retreives a condition option, parses it from a frequency (such as "1d", "1w" or
+"12m"), and subtracts that interval from the supplied timestamp. It is
+primarily intended for use in B<condition>.
+
+=cut
+
+sub option_age_from {
+ my( $self, $option, $time ) = @_;
+ my $age = $self->option($option);
+ $age = '0m' unless length($age);
+
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
+
+ if ( $age =~ /^(\d+)m$/i ) {
+ $mon -= $1;
+ until ( $mon >= 0 ) { $mon += 12; $year--; }
+ } elsif ( $age =~ /^(\d+)y$/i ) {
+ $year -= $1;
+ } elsif ( $age =~ /^(\d+)w$/i ) {
+ $mday -= $1 * 7;
+ } elsif ( $age =~ /^(\d+)d$/i ) {
+ $mday -= $1;
+ } elsif ( $age =~ /^(\d+)h$/i ) {
+ $hour -= $hour;
+ } else {
+ die "unparsable age: $age";
+ }
+
+ timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
+
+}
+
+=item condition_sql_option OPTION
+
+This is a class method that returns an SQL fragment for retreiving a condition
+option. It is primarily intended for use in B<condition_sql>.
+
+=cut
+
+sub condition_sql_option {
+ my( $class, $option ) = @_;
+
+ ( my $condname = $class ) =~ s/^.*:://;
+
+ "( SELECT optionvalue FROM part_event_condition_option
+ WHERE part_event_condition_option.eventconditionnum =
+ cond_$condname.eventconditionnum
+ AND part_event_condition_option.optionname = '$option'
+ )";
+}
+
+=item condition_sql_option_age_from OPTION FROM_TIMESTAMP
+
+This is a class method that returns an SQL fragment that will retreive a
+condition option, parse it from a frequency (such as "1d", "1w" or "12m"),
+and subtract that interval from the supplied timestamp. It is primarily
+intended for use in B<condition_sql>.
+
+=cut
+
+sub condition_sql_option_age_from {
+ my( $class, $option, $from ) = @_;
+
+ my $value = $class->condition_sql_option($option);
+
+# my $str2time = str2time_sql;
+
+ if ( driver_name =~ /^Pg/i ) {
+
+ #can we do better with Pg now that we have $from? yes we can, bob
+ "( $from - EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval ) )";
+
+ } elsif ( driver_name =~ /^mysql/i ) {
+
+ #hmm... is there a way we can save $value? we're just an expression, hmm
+ #we might be able to do something like "AS ${option}_value" except we get
+ #used in more complicated expressions and we need some sort of unique
+ #identifer passed down too... yow
+
+ "CASE WHEN $value IS NULL OR $value = ''
+ THEN $from
+ WHEN $value LIKE '%m'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'm', '' ) MONTH
+ )
+ WHEN $value LIKE '%y'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'y', '' ) YEAR
+ )
+ WHEN $value LIKE '%w'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'w', '' ) WEEK
+ )
+ WHEN $value LIKE '%d'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'd', '' ) DAY
+ )
+ WHEN $value LIKE '%h'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'h', '' ) HOUR
+ )
+ END
+ "
+ } else {
+
+ die "FATAL: don't know how to subtract frequencies from dates for ".
+ driver_name. " databases";
+
+ }
+
+}
+
+=item condition_sql_option_age OPTION
+
+This is a class method that returns an SQL fragment for retreiving a condition
+option, and additionaly parsing it from a frequency (such as "1d", "1w" or
+"12m") into an approximate number of seconds.
+
+Note that since months vary in length, the results of this method should B<not>
+be used in computations (use condition_sql_option_age_from for that). They are
+useful for for ordering and comparison to other ages.
+
+This method is primarily intended for use in B<order_sql>.
+
+=cut
+
+sub condition_sql_option_age {
+ my( $class, $option ) = @_;
+ $class->age2seconds_sql( $class->condition_sql_option($option) );
+}
+
+=item age2seconds_sql
+
+Class method returns an SQL fragment for parsing an arbitrary frequeny (such
+as "1d", "1w", "12m", "2y" or "12h") into an approximate number of seconds.
+
+Approximate meaning: months are considered to be 30 days, years to be
+365.25 days. Otherwise the numbers of seconds returned is exact.
+
+=cut
+
+sub age2seconds_sql {
+ my( $class, $value ) = @_;
+
+ if ( driver_name =~ /^Pg/i ) {
+
+ "EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval )";
+
+ } elsif ( driver_name =~ /^mysql/i ) {
+
+ #hmm... is there a way we can save $value? we're just an expression, hmm
+ #we might be able to do something like "AS ${option}_age" except we get
+ #used in more complicated expressions and we need some sort of unique
+ #identifer passed down too... yow
+ # 2592000 = 30d "1 month"
+ # 31557600 = 365.25d "1 year"
+
+ "CASE WHEN $value IS NULL OR $value = ''
+ THEN 0
+ WHEN $value LIKE '%m'
+ THEN REPLACE( $value, 'm', '' ) * 2592000
+ WHEN $value LIKE '%y'
+ THEN REPLACE( $value, 'y', '' ) * 31557600
+ WHEN $value LIKE '%w'
+ THEN REPLACE( $value, 'w', '' ) * 604800
+ WHEN $value LIKE '%d'
+ THEN REPLACE( $value, 'd', '' ) * 86400
+ WHEN $value LIKE '%h'
+ THEN REPLACE( $value, 'h', '' ) * 3600
+ END
+ "
+ } else {
+
+ die "FATAL: don't know how to approximate frequencies for ". driver_name.
+ " databases";
+
+ }
+
+}
+
+=head1 NEW CONDITION CLASSES
+
+A module should be added in FS/FS/part_event/Condition/ which implements the
+methods desribed above in L</METHODS>. An example may be found in the
+eg/part_event-Condition-template.pm file.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/part_event/Condition/agent.pm b/FS/FS/part_event/Condition/agent.pm
new file mode 100644
index 0000000..da428c1
--- /dev/null
+++ b/FS/FS/part_event/Condition/agent.pm
@@ -0,0 +1,37 @@
+package FS::part_event::Condition::agent;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+# see the FS::part_event::Condition manpage for full documentation on each
+# of the required and optional methods.
+
+sub description {
+ 'Agent';
+}
+
+sub option_fields {
+ (
+ 'agentnum' => { label=>'Agent', type=>'select-agent', },
+ );
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $agentnum = $self->option('agentnum');
+
+ $cust_main->agentnum == $agentnum;
+
+}
+
+#sub condition_sql {
+# my( $self, $table ) = @_;
+#
+# 'true';
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/agent_type.pm b/FS/FS/part_event/Condition/agent_type.pm
new file mode 100644
index 0000000..54c8932
--- /dev/null
+++ b/FS/FS/part_event/Condition/agent_type.pm
@@ -0,0 +1,40 @@
+package FS::part_event::Condition::agent_type;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+# see the FS::part_event::Condition manpage for full documentation on each
+# of the required and optional methods.
+
+sub description {
+ 'Agent Type';
+}
+
+sub option_fields {
+ (
+ 'typenum' => { label => 'Agent Type',
+ type => 'select-agent_type',
+ disable_empty => 1,
+ },
+ );
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $typenum = $self->option('typenum');
+
+ $cust_main->agent->typenum == $typenum;
+
+}
+
+#sub condition_sql {
+# my( $self, $table ) = @_;
+#
+# 'true';
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/balance.pm b/FS/FS/part_event/Condition/balance.pm
new file mode 100644
index 0000000..3b8854a
--- /dev/null
+++ b/FS/FS/part_event/Condition/balance.pm
@@ -0,0 +1,48 @@
+package FS::part_event::Condition::balance;
+
+use strict;
+use FS::cust_main;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer balance'; }
+
+sub implicit_flag { 20; }
+
+sub remove_warning {
+ 'Are you sure you want to remove this condition? Doing so will allow this event to run even if the customer has no outstanding balance. Perhaps you want to reset "Balance over" to 0 instead of removing the condition entirely?'; #better error msg?
+}
+
+sub option_fields {
+ (
+ 'balance' => { 'label' => 'Balance over',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ );
+}
+
+sub condition {
+ my($self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $over = $self->option('balance');
+ $over = 0 unless length($over);
+
+ $cust_main->balance > $over;
+}
+
+sub condition_sql {
+ my( $class, $table ) = @_;
+
+ my $over = $class->condition_sql_option('balance');
+
+ my $balance_sql = FS::cust_main->balance_sql;
+
+ "$balance_sql > CAST( $over AS DECIMAL(10,2) )";
+
+}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/balance_age.pm b/FS/FS/part_event/Condition/balance_age.pm
new file mode 100644
index 0000000..fc34612
--- /dev/null
+++ b/FS/FS/part_event/Condition/balance_age.pm
@@ -0,0 +1,54 @@
+package FS::part_event::Condition::balance_age;
+
+use strict;
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer balance age'; }
+
+sub option_fields {
+ (
+ 'balance' => { 'label' => 'Balance over',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ 'age' => { 'label' => 'Age',
+ 'type' => 'freq',
+ },
+ );
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $over = $self->option('balance');
+ $over = 0 unless length($over);
+
+ my $age = $self->option_age_from('age', $opt{'time'} );
+
+ $cust_main->balance_date($age) > $over;
+}
+
+sub condition_sql {
+ my( $class, $table, %opt ) = @_;
+
+ my $over = $class->condition_sql_option('balance');
+ my $age = $class->condition_sql_option_age_from('age', $opt{'time'});
+
+ my $balance_sql = FS::cust_main->balance_date_sql( $age );
+
+ "$balance_sql > CAST( $over AS DECIMAL(10,2) )";
+}
+
+sub order_sql {
+ shift->condition_sql_option_age('age');
+}
+
+use FS::UID qw( driver_name );
+
+sub order_sql_weight {
+ 10;
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/balance_under.pm b/FS/FS/part_event/Condition/balance_under.pm
new file mode 100644
index 0000000..2002c70
--- /dev/null
+++ b/FS/FS/part_event/Condition/balance_under.pm
@@ -0,0 +1,42 @@
+package FS::part_event::Condition::balance_under;
+
+use strict;
+use FS::cust_main;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer balance (under)'; }
+
+sub option_fields {
+ (
+ 'balance' => { 'label' => 'Balance under (or equal to)',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ );
+}
+
+sub condition {
+ my($self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $under = $self->option('balance');
+ $under = 0 unless length($under);
+
+ $cust_main->balance <= $under;
+}
+
+sub condition_sql {
+ my( $class, $table ) = @_;
+
+ my $under = $class->condition_sql_option('balance');
+
+ my $balance_sql = FS::cust_main->balance_sql;
+
+ "$balance_sql <= CAST( $under AS DECIMAL(10,2) )";
+
+}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/cust_bill_age.pm b/FS/FS/part_event/Condition/cust_bill_age.pm
new file mode 100644
index 0000000..f343673
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_age.pm
@@ -0,0 +1,46 @@
+package FS::part_event::Condition::cust_bill_age;
+
+use strict;
+use base qw( FS::part_event::Condition );
+
+sub description { 'Invoice age'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub option_fields {
+ (
+ 'age' => { label=>'Age', type=>'freq', },
+ );
+}
+
+sub condition {
+ my( $self, $cust_bill, %opt ) = @_;
+
+ my $age = $self->option_age_from('age', $opt{'time'} );
+
+ $cust_bill->_date <= $age;
+
+}
+
+sub condition_sql {
+ my( $class, $table, %opt ) = @_;
+
+ my $age = $class->condition_sql_option_age_from('age', $opt{'time'} );
+
+ "cust_bill._date <= $age";
+}
+
+sub order_sql {
+ shift->condition_sql_option_age('age');
+}
+
+sub order_sql_weight {
+ 0;
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_has_noauto.pm b/FS/FS/part_event/Condition/cust_bill_has_noauto.pm
new file mode 100644
index 0000000..6cb94c0
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_has_noauto.pm
@@ -0,0 +1,33 @@
+package FS::part_event::Condition::cust_bill_has_noauto;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Invoice ineligible for automatic collection';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub condition {
+ #my($self, $cust_bill, %opt) = @_;
+ my($self, $cust_bill) = @_;
+
+ $cust_bill->no_auto;
+}
+
+#sub condition_sql {
+# my( $class, $table ) = @_;
+#
+# my $sql = qq| |;
+# return $sql;
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_has_service.pm b/FS/FS/part_event/Condition/cust_bill_has_service.pm
new file mode 100644
index 0000000..d85af26
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_has_service.pm
@@ -0,0 +1,56 @@
+package FS::part_event::Condition::cust_bill_has_service;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Invoice is billing for a certain service type';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+# could not find component for path '/elements/tr-select-part_svc.html'
+# sub disabled { 1; }
+
+sub option_fields {
+ (
+ 'has_service' => { 'label' => 'Has service',
+ 'type' => 'select-part_svc',
+ },
+ );
+}
+
+sub condition {
+ #my($self, $cust_bill, %opt) = @_;
+ my($self, $cust_bill) = @_;
+
+ my $servicenum = $self->option('has_service');
+ grep { $servicenum == $_->svcpart }
+ map { $_->cust_pkg->cust_svc }
+ $cust_bill->cust_bill_pkg ;
+}
+
+sub condition_sql {
+ my( $class, $table, %opt ) = @_;
+
+ my $integer = $opt{'driver_name'} =~ /^mysql/ ? 'UNSIGNED INTEGER' : 'INTEGER';
+
+ my $servicenum = $class->condition_sql_option('has_service');
+ my $sql = qq| 0 < ( SELECT COUNT(cs.svcpart)
+ FROM cust_bill_pkg cbp, cust_svc cs
+ WHERE cbp.invnum = cust_bill.invnum
+ AND cs.pkgnum = cbp.pkgnum
+ AND cs.svcpart = CAST( $servicenum AS $integer )
+ )
+ |;
+ return $sql;
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm
new file mode 100644
index 0000000..78a6d51
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm
@@ -0,0 +1,33 @@
+package FS::part_event::Condition::cust_bill_hasnt_noauto;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Invoice eligible for automatic collection';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub condition {
+ #my($self, $cust_bill, %opt) = @_;
+ my($self, $cust_bill) = @_;
+
+ ! $cust_bill->no_auto;
+}
+
+#sub condition_sql {
+# my( $class, $table ) = @_;
+#
+# my $sql = qq| |;
+# return $sql;
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_owed.pm b/FS/FS/part_event/Condition/cust_bill_owed.pm
new file mode 100644
index 0000000..d8c77c7
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_owed.pm
@@ -0,0 +1,54 @@
+package FS::part_event::Condition::cust_bill_owed;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Amount owed on specific invoice';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub implicit_flag { 30; }
+
+sub remove_warning {
+ 'Are you sure you want to remove this condition? Doing so will allow this event to run even for invoices which have no outstanding balance. Perhaps you want to reset "Amount owed over" to 0 instead of removing the condition entirely?'; #better error msg?
+}
+
+sub option_fields {
+ (
+ 'owed' => { 'label' => 'Amount owed over',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ );
+}
+
+sub condition {
+ #my($self, $cust_bill, %opt) = @_;
+ my($self, $cust_bill) = @_;
+
+ my $over = $self->option('owed');
+ $over = 0 unless length($over);
+
+ $cust_bill->owed > $over;
+}
+
+sub condition_sql {
+ my( $class, $table ) = @_;
+
+ my $over = $class->condition_sql_option('owed');
+
+ my $owed_sql = FS::cust_bill->owed_sql;
+
+ "$owed_sql > CAST( $over AS DECIMAL(10,2) )";
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_owed_under.pm b/FS/FS/part_event/Condition/cust_bill_owed_under.pm
new file mode 100644
index 0000000..4eb6439
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_bill_owed_under.pm
@@ -0,0 +1,49 @@
+package FS::part_event::Condition::cust_bill_owed_under;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Amount owed on specific invoice (under)';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ };
+}
+
+sub option_fields {
+ (
+ 'owed' => { 'label' => 'Amount owed under (or equal to)',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ );
+}
+
+sub condition {
+ #my($self, $cust_bill, %opt) = @_;
+ my($self, $cust_bill) = @_;
+
+ my $under = $self->option('owed');
+ $under = 0 unless length($under);
+
+ $cust_bill->owed <= $under;
+
+}
+
+sub condition_sql {
+ my( $class, $table ) = @_;
+
+ my $under = $class->condition_sql_option('owed');
+
+ my $owed_sql = FS::cust_bill->owed_sql;
+
+ "$owed_sql <= CAST( $under AS DECIMAL(10,2) )";
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_pay_batch_declined.pm b/FS/FS/part_event/Condition/cust_pay_batch_declined.pm
new file mode 100644
index 0000000..b3a8d70
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_pay_batch_declined.pm
@@ -0,0 +1,51 @@
+package FS::part_event::Condition::cust_pay_batch_declined;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Batch payment declined';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 0,
+ 'cust_pay_batch' => 1,
+ };
+}
+
+#sub option_fields {
+# (
+# 'field' => 'description',
+#
+# 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+#
+# 'third_field' => { 'label' => 'Types',
+# 'type' => 'checkbox-multiple',
+# 'options' => [ 'h', 's' ],
+# 'option_labels' => { 'h' => 'Happy',
+# 's' => 'Sad',
+# },
+# );
+#}
+
+sub condition {
+ my($self, $cust_pay_batch, %opt) = @_;
+
+ #my $cust_main = $self->cust_main($object);
+ #my $value_of_field = $self->option('field');
+ #my $time = $opt{'time'}; #use this instead of time or $^T
+
+ $cust_pay_batch->status =~ /Declined/i;
+
+}
+
+#sub condition_sql {
+# my( $class, $table ) = @_;
+# #...
+# 'true';
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_payments.pm b/FS/FS/part_event/Condition/cust_payments.pm
new file mode 100644
index 0000000..477ecdb
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_payments.pm
@@ -0,0 +1,43 @@
+package FS::part_event::Condition::cust_payments;
+
+use strict;
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer total payments (amount)'; }
+
+sub option_fields {
+ (
+ 'over' => { 'label' => 'Customer total payments at least',
+ 'type' => 'money',
+ 'value' => '0.00', #default
+ },
+ );
+}
+
+sub condition {
+ my($self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $over = $self->option('over');
+ $over = 0 unless length($over);
+
+ $cust_main->total_paid >= $over;
+
+}
+
+#XXX add for efficiency. could use cust_main::total_paid_sql
+#use FS::cust_main;
+#sub condition_sql {
+# my( $class, $table ) = @_;
+#
+# my $over = $class->condition_sql_option('balance');
+#
+# my $balance_sql = FS::cust_main->balance_sql;
+#
+# "$balance_sql > $over";
+#
+#}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/cust_payments_pkg.pm b/FS/FS/part_event/Condition/cust_payments_pkg.pm
new file mode 100644
index 0000000..d6c493b
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_payments_pkg.pm
@@ -0,0 +1,68 @@
+package FS::part_event::Condition::cust_payments_pkg;
+
+use strict;
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer total payments (multiplier of package)'; }
+
+sub eventtable_hashref {
+ { 'cust_pkg' => 1 };
+}
+
+sub option_fields {
+ (
+ 'over_times' => { 'label' => 'Customer total payments as least',
+ 'type' => 'text',
+ 'value' => '1', #default
+ },
+ 'what' => { 'label' => 'Times',
+ 'type' => 'select',
+ #also add some way to specify in the package def, no?
+ 'options' => [ qw( base_recur_permonth ) ],
+ 'labels' => { 'base_recur_permonth' => 'Base monthly fee', },
+ },
+ );
+}
+
+sub condition {
+ my($self, $cust_pkg) = @_;
+
+ my $cust_main = $self->cust_main($cust_pkg);
+
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ my $over_times = $self->option('over_times');
+ $over_times = 0 unless length($over_times);
+
+ my $what = $self->option('what');
+
+ #false laziness w/Condition/cust_payments_pkg.pm
+ if ( $what eq 'base_recur_permonth' ) { #huh. yuck.
+ if ( $part_pkg->freq !~ /^\d+$/ ) {
+ die 'WARNING: Not crediting customer '. $cust_main->referral_custnum.
+ ' for package '. $cust_pkg->pkgnum.
+ ' ( customer '. $cust_pkg->custnum. ')'.
+ ' - Referral credits not (yet) available for '.
+ ' packages with '. $part_pkg->freq_pretty. ' frequency';
+ }
+ }
+
+ $cust_main->total_paid >= $over_times * $part_pkg->$what($cust_pkg);
+
+}
+
+#XXX add for efficiency. could use cust_main::total_paid_sql
+#use FS::cust_main;
+#sub condition_sql {
+# my( $class, $table ) = @_;
+#
+# my $over = $class->condition_sql_option('balance');
+#
+# my $balance_sql = FS::cust_main->balance_sql;
+#
+# "$balance_sql > $over";
+#
+#}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/cust_status.pm b/FS/FS/part_event/Condition/cust_status.pm
new file mode 100644
index 0000000..fbdff25
--- /dev/null
+++ b/FS/FS/part_event/Condition/cust_status.pm
@@ -0,0 +1,32 @@
+package FS::part_event::Condition::cust_status;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+
+sub description {
+ 'Customer Status';
+}
+
+#something like this
+sub option_fields {
+ (
+ 'status' => { 'label' => 'Customer Status',
+ 'type' => 'select-cust_main-status',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ #XXX test
+ my $hashref = $self->option('status') || {};
+ $hashref->{ $cust_main->status };
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/dundate.pm b/FS/FS/part_event/Condition/dundate.pm
new file mode 100644
index 0000000..ee2a95f
--- /dev/null
+++ b/FS/FS/part_event/Condition/dundate.pm
@@ -0,0 +1,26 @@
+package FS::part_event::Condition::dundate;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ "Skip until customer dun date is reached";
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ $cust_main->dundate <= $opt{time};
+
+}
+
+#sub condition_sql {
+# my( $self, $table ) = @_;
+#
+# 'true';
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/every.pm b/FS/FS/part_event/Condition/every.pm
new file mode 100644
index 0000000..1910674
--- /dev/null
+++ b/FS/FS/part_event/Condition/every.pm
@@ -0,0 +1,67 @@
+package FS::part_event::Condition::every;
+
+use strict;
+use FS::UID qw( dbh );
+use FS::Record qw( qsearch );
+use FS::cust_event;
+
+use base qw( FS::part_event::Condition );
+
+sub description { "Don't retry failures more often than specified interval"; }
+
+sub option_fields {
+ (
+ 'retry_delay' => { label=>'Retry after', type=>'freq', value=>'1d', },
+ 'max_tries' => { label=>'Maximum # of attempts', type=>'text', size=>3, },
+ );
+}
+
+my %after = (
+ 'h' => 3600,
+ 'd' => 86400,
+ 'w' => 604800,
+ 'm' => 2592000, #well, 30 days... presumably people would mostly use d or w
+ '' => 2592000,
+ 'y' => 31536000, #well, 365 days...
+);
+
+my $sql =
+ "SELECT COUNT(*) FROM cust_event WHERE eventpart = ? AND tablenum = ?";
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $obj_pkey = $object->primary_key;
+ my $tablenum = $object->$obj_pkey();
+
+ if ( $self->option('max_tries') =~ /^\s*(\d+)\s*$/ ) {
+ my $max_tries = $1;
+ my $sth = dbh->prepare($sql)
+ or die dbh->errstr. " preparing: $sql";
+ $sth->execute($self->eventpart, $tablenum)
+ or die $sth->errstr. " executing: $sql";
+ my $tries = $sth->fetchrow_arrayref->[0];
+ return 0 if $tries >= $max_tries;
+ }
+
+ my $time = $opt{'time'};
+ my $retry_delay = $self->option('retry_delay');
+ $retry_delay =~ /^(\d+)([hdwmy]?)$/
+ or die "unparsable retry_delay: $retry_delay";
+ my $date_after = $time - $1 * $after{$2};
+
+ my $sth = dbh->prepare("$sql AND _date > ?") # AND status = 'failed' "
+ or die dbh->errstr. " preparing: $sql";
+ $sth->execute($self->eventpart, $tablenum, $date_after)
+ or die $sth->errstr. " executing: $sql";
+ ! $sth->fetchrow_arrayref->[0];
+
+}
+
+#sub condition_sql {
+# my( $self, $table ) = @_;
+#
+# 'true';
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/has_pkg_class.pm b/FS/FS/part_event/Condition/has_pkg_class.pm
new file mode 100644
index 0000000..59a3675
--- /dev/null
+++ b/FS/FS/part_event/Condition/has_pkg_class.pm
@@ -0,0 +1,40 @@
+package FS::part_event::Condition::has_pkg_class;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+use FS::pkg_class;
+
+sub description {
+ 'Customer has uncancelled package with class';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+#something like this
+sub option_fields {
+ (
+ 'pkgclass' => { 'label' => 'Package Class',
+ 'type' => 'select-pkg_class',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ #XXX test
+ my $hashref = $self->option('pkgclass') || {};
+ grep $hashref->{ $_->part_pkg->classnum }, $cust_main->ncancelled_pkgs;
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/has_pkgpart.pm b/FS/FS/part_event/Condition/has_pkgpart.pm
new file mode 100644
index 0000000..c54b7e2
--- /dev/null
+++ b/FS/FS/part_event/Condition/has_pkgpart.pm
@@ -0,0 +1,41 @@
+package FS::part_event::Condition::has_pkgpart;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer has uncancelled package of specified definitions'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub option_fields {
+ (
+ 'if_pkgpart' => { 'label' => 'Only packages: ',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ #XXX test
+ my $if_pkgpart = $self->option('if_pkgpart') || {};
+ grep $if_pkgpart->{ $_->pkgpart }, $cust_main->ncancelled_pkgs;
+
+}
+
+#XXX
+#sub condition_sql {
+#
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/has_referral_custnum.pm b/FS/FS/part_event/Condition/has_referral_custnum.pm
new file mode 100644
index 0000000..61a8155
--- /dev/null
+++ b/FS/FS/part_event/Condition/has_referral_custnum.pm
@@ -0,0 +1,48 @@
+package FS::part_event::Condition::has_referral_custnum;
+
+use strict;
+use FS::cust_main;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer has a referring customer'; }
+
+sub option_fields {
+ (
+ 'active' => { 'label' => 'Referring customer is active',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ },
+ );
+}
+
+sub condition {
+ my($self, $object) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ if ( $self->option('active') ) {
+
+ return 0 unless $cust_main->referral_custnum;
+
+ #check for no cust_main for referral_custnum? (deleted?)
+
+ $cust_main->referral_custnum_cust_main->status eq 'active';
+
+ } else {
+
+ $cust_main->referral_custnum; # ? 1 : 0;
+
+ }
+
+}
+
+sub condition_sql {
+ #my( $class, $table ) = @_;
+
+ "cust_main.referral_custnum IS NOT NULL";
+
+ #XXX a bit harder to check active status here
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/hasnt_pkgpart.pm b/FS/FS/part_event/Condition/hasnt_pkgpart.pm
new file mode 100644
index 0000000..421d023
--- /dev/null
+++ b/FS/FS/part_event/Condition/hasnt_pkgpart.pm
@@ -0,0 +1,40 @@
+package FS::part_event::Condition::hasnt_pkgpart;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Customer does not have uncancelled package of specified definitions'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub option_fields {
+ (
+ 'unless_pkgpart' => { 'label' => 'Packages: ',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ #XXX test
+ my $unless_pkgpart = $self->option('unless_pkgpart') || {};
+ ! grep $unless_pkgpart->{ $_->pkgpart }, $cust_main->ncancelled_pkgs;
+}
+
+#XXX
+#sub condition_sql {
+#
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/once.pm b/FS/FS/part_event/Condition/once.pm
new file mode 100644
index 0000000..d004814
--- /dev/null
+++ b/FS/FS/part_event/Condition/once.pm
@@ -0,0 +1,55 @@
+package FS::part_event::Condition::once;
+
+use strict;
+use FS::Record qw( qsearch );
+use FS::part_event;
+use FS::cust_event;
+
+use base qw( FS::part_event::Condition );
+
+sub description { "Don't run this event again after it has completed successfully"; }
+
+sub implicit_flag { 10; }
+
+sub remove_warning {
+ 'Are you sure you want to remove this condition? Doing so will allow this event to run every time the other conditions are satisfied, even if it has already run sucessfully.'; #better error msg?
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $obj_pkey = $object->primary_key;
+ my $tablenum = $object->$obj_pkey();
+
+ my @existing = qsearch( {
+ 'table' => 'cust_event',
+ 'hashref' => {
+ 'eventpart' => $self->eventpart,
+ 'tablenum' => $tablenum,
+ 'status' => { op=>'!=', value=>'failed' },
+ },
+ 'extra_sql' => ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/
+ ? " AND eventnum != $1 "
+ : ''
+ ),
+ } );
+
+ ! scalar(@existing);
+
+}
+
+sub condition_sql {
+ my( $self, $table ) = @_;
+
+ my %tablenum = %{ FS::part_event->eventtable_pkey_sql };
+
+ "0 = ( SELECT COUNT(*) FROM cust_event
+ WHERE cust_event.eventpart = part_event.eventpart
+ AND cust_event.tablenum = $tablenum{$table}
+ AND status != 'failed'
+ )
+ ";
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/once_percust.pm b/FS/FS/part_event/Condition/once_percust.pm
new file mode 100644
index 0000000..b8a8fbf
--- /dev/null
+++ b/FS/FS/part_event/Condition/once_percust.pm
@@ -0,0 +1,67 @@
+package FS::part_event::Condition::once_percust;
+
+use strict;
+use FS::Record qw( qsearch );
+use FS::part_event;
+use FS::cust_event;
+
+use base qw( FS::part_event::Condition );
+
+sub description { "Don't run more than once per customer"; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $obj_pkey = $object->primary_key;
+ my $obj_table = $object->table;
+ my $custnum = $object->custnum;
+
+ my @where = (
+ "tablenum IN ( SELECT $obj_pkey FROM $obj_table WHERE custnum = $custnum )"
+ );
+ if ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/ ) {
+ push @where, " eventnum != $1 ";
+ }
+ my $extra_sql = ' AND '. join(' AND ', @where);
+
+ my @existing = qsearch( {
+ 'table' => 'cust_event',
+ 'hashref' => {
+ 'eventpart' => $self->eventpart,
+ #'tablenum' => $tablenum,
+ 'status' => { op=>'!=', value=>'failed' },
+ },
+ 'extra_sql' => $extra_sql,
+ } );
+
+ ! scalar(@existing);
+
+}
+
+#XXX test?
+sub condition_sql {
+ my( $self, $table ) = @_;
+
+ my %pkey = %{ FS::part_event->eventtable_pkey };
+
+ my $pkey = $pkey{$table};
+
+ "0 = ( SELECT COUNT(*) FROM cust_event
+ WHERE cust_event.eventpart = part_event.eventpart
+ AND cust_event.tablenum IN (
+ SELECT $pkey FROM $table AS once_percust
+ WHERE once_percust.custnum = cust_main.custnum )
+ AND status != 'failed'
+ )
+ ";
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/payby.pm b/FS/FS/part_event/Condition/payby.pm
new file mode 100644
index 0000000..d931568
--- /dev/null
+++ b/FS/FS/part_event/Condition/payby.pm
@@ -0,0 +1,50 @@
+package FS::part_event::Condition::payby;
+
+use strict;
+use Tie::IxHash;
+use FS::payby;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ #'customer payment types: ';
+ 'Customer payment type';
+}
+
+#something like this
+tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
+sub option_fields {
+ (
+ 'payby' => {
+ label => 'Customer payment type',
+ #type => 'select-multiple',
+ type => 'checkbox-multiple',
+ options => [ keys %payby ],
+ option_labels => \%payby,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ #uuh.. all right? test this.
+ my $hashref = $self->option('payby') || {};
+ $hashref->{ $cust_main->payby };
+
+}
+
+#sub condition_sql {
+# my( $self, $table ) = @_;
+#
+# #uuh... yeah... something like this. test it for sure.
+#
+# my @payby = keys %{ $self->option('payby') };
+#
+# ' ( '. join(' OR ', map { "cust_main.payby = '$_'" } @payby ). ' ) ';
+#
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/pkg_age.pm b/FS/FS/part_event/Condition/pkg_age.pm
new file mode 100644
index 0000000..8b3b4c9
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_age.pm
@@ -0,0 +1,58 @@
+package FS::part_event::Condition::pkg_age;
+
+use strict;
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+
+sub description {
+ 'Package Age';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+#something like this
+sub option_fields {
+ (
+ 'age' => { 'label' => 'Package date age',
+ 'type' => 'freq',
+ },
+ 'field' => { 'label' => 'Compare date',
+ 'type' => 'select',
+ 'options' =>
+ [qw( setup last_bill bill adjourn susp expire cancel )],
+ 'labels' => {
+ 'setup' => 'Setup date',
+ 'last_bill' => 'Last bill date',
+ 'bill' => 'Next bill date',
+ 'adjourn' => 'Adjournment date',
+ 'susp' => 'Suspension date',
+ 'expire' => 'Expiration date',
+ 'cancel' => 'Cancellation date',
+ },
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_pkg, %opt ) = @_;
+
+ my $age = $self->option_age_from('age', $opt{'time'} );
+
+ my $pkg_date = $cust_pkg->get( $self->option('field') );
+
+ $pkg_date && $pkg_date <= $age;
+
+}
+
+#XXX write me for efficiency
+#sub condition_sql {
+#
+#}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/pkg_class.pm b/FS/FS/part_event/Condition/pkg_class.pm
new file mode 100644
index 0000000..8c9031c
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_class.pm
@@ -0,0 +1,38 @@
+package FS::part_event::Condition::pkg_class;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+use FS::pkg_class;
+
+sub description {
+ 'Package Class';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+#something like this
+sub option_fields {
+ (
+ 'pkgclass' => { 'label' => 'Package Class',
+ 'type' => 'select-pkg_class',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_pkg ) = @_;
+
+ #XXX test
+ my $hashref = $self->option('pkgclass') || {};
+ $hashref->{ $cust_pkg->part_pkg->classnum };
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/pkg_notchange.pm b/FS/FS/part_event/Condition/pkg_notchange.pm
new file mode 100644
index 0000000..4c103c2
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_notchange.pm
@@ -0,0 +1,31 @@
+package FS::part_event::Condition::pkg_notchange;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+
+sub description {
+ 'Package is a new order, not a change';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub condition {
+ my( $self, $cust_pkg ) = @_;
+
+ ! $cust_pkg->change_date;
+
+}
+
+sub condition_sql {
+ '( cust_pkg.change_date IS NULL OR cust_pkg.change_date = 0 )';
+}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/pkg_pkgpart.pm b/FS/FS/part_event/Condition/pkg_pkgpart.pm
new file mode 100644
index 0000000..6adef8e
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_pkgpart.pm
@@ -0,0 +1,39 @@
+package FS::part_event::Condition::pkg_pkgpart;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Package definitions'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub option_fields {
+ (
+ 'if_pkgpart' => { 'label' => 'Only packages: ',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_pkg) = @_;
+
+ #XXX test
+ my $if_pkgpart = $self->option('if_pkgpart') || {};
+ $if_pkgpart->{ $cust_pkg->pkgpart };
+
+}
+
+#XXX
+#sub condition_sql {
+#
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/pkg_recurring.pm b/FS/FS/part_event/Condition/pkg_recurring.pm
new file mode 100644
index 0000000..1b66821
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_recurring.pm
@@ -0,0 +1,31 @@
+package FS::part_event::Condition::pkg_recurring;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Package is recurring'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub condition {
+ my( $self, $cust_pkg ) = @_;
+
+ $cust_pkg->part_pkg->freq !~ /^0+\D?$/; #just in case, probably just != '0'
+
+}
+
+
+#XXX join part_pkg USING (pkgpart)
+# part_pkg.freq != '0'
+#sub condition_sql {
+#
+#}
+
+1;
+
diff --git a/FS/FS/part_event/Condition/pkg_status.pm b/FS/FS/part_event/Condition/pkg_status.pm
new file mode 100644
index 0000000..6c1c9cc
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_status.pm
@@ -0,0 +1,37 @@
+package FS::part_event::Condition::pkg_status;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+use FS::Record qw( qsearch );
+
+sub description {
+ 'Package Status';
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+#something like this
+sub option_fields {
+ (
+ 'status' => { 'label' => 'Package Status',
+ 'type' => 'select-cust_pkg-status',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_pkg ) = @_;
+
+ #XXX test
+ my $hashref = $self->option('status') || {};
+ $hashref->{ $cust_pkg->status };
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm b/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm
new file mode 100644
index 0000000..47fa8c3
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm
@@ -0,0 +1,39 @@
+package FS::part_event::Condition::pkg_unless_pkgpart;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+sub description { 'Except package definitions'; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub option_fields {
+ (
+ 'unless_pkgpart' => { 'label' => 'Except packages: ',
+ 'type' => 'select-part_pkg',
+ 'multiple' => 1,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $cust_pkg) = @_;
+
+ #XXX test
+ my $unless_pkgpart = $self->option('unless_pkgpart') || {};
+ ! $unless_pkgpart->{ $cust_pkg->pkgpart };
+
+}
+
+#XXX
+#sub condition_sql {
+#
+#}
+
+1;
diff --git a/FS/FS/part_event_condition.pm b/FS/FS/part_event_condition.pm
new file mode 100644
index 0000000..32f19a3
--- /dev/null
+++ b/FS/FS/part_event_condition.pm
@@ -0,0 +1,354 @@
+package FS::part_event_condition;
+
+use strict;
+use vars qw( @ISA $DEBUG @SKIP_CONDITION_SQL );
+use FS::UID qw( dbh driver_name );
+use FS::Record qw( qsearch qsearchs );
+use FS::option_Common;
+use FS::part_event; #for order_conditions_sql...
+
+@ISA = qw( FS::option_Common ); # FS::Record );
+$DEBUG = 0;
+
+@SKIP_CONDITION_SQL = ();
+
+=head1 NAME
+
+FS::part_event_condition - Object methods for part_event_condition records
+
+=head1 SYNOPSIS
+
+ use FS::part_event_condition;
+
+ $record = new FS::part_event_condition \%hash;
+ $record = new FS::part_event_condition { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_event_condition object represents an event condition.
+FS::part_event_condition inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item eventconditionnum - primary key
+
+=item eventpart - Event definition (see L<FS::part_event>)
+
+=item conditionname - Condition name - defines which FS::part_event::Condition::I<conditionname> evaluates this condition
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new event. 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_event_condition'; }
+
+=item insert [ HASHREF | OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If a list or hash reference of options is supplied, part_event_condition_option
+records are created (see L<FS::part_event_condition_option>).
+
+=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 [ HASHREF | OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+If a list or hash reference of options is supplied, part_event_condition_option
+records are created or modified (see L<FS::part_event_condition_option>).
+
+=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('eventconditionnum')
+ || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart')
+ || $self->ut_alpha('conditionname')
+ ;
+ return $error if $error;
+
+ #XXX check conditionname to make sure a module exists?
+ # well it'll die in _rebless...
+
+ $self->SUPER::check;
+}
+
+
+=item _rebless
+
+Reblesses the object into the FS::part_event::Condition::CONDITIONNAME class,
+where CONDITIONNAME is the object's I<conditionname> field.
+
+=cut
+
+sub _rebless {
+ my $self = shift;
+ my $conditionname = $self->conditionname;
+ #my $class = ref($self). "::$conditionname";
+ my $class = "FS::part_event::Condition::$conditionname";
+ eval "use $class";
+ die $@ if $@;
+ bless($self, $class); #unless $@;
+ $self;
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item conditions [ EVENTTABLE ]
+
+Return information about the available conditions. If an eventtable is
+specified, only return information about conditions available for that
+eventtable.
+
+Information is returned as key-value pairs. Keys are condition names. Values
+are hashrefs with the following keys:
+
+=over 4
+
+=item description
+
+=item option_fields
+
+# =item default_weight
+
+# =item deprecated
+
+=back
+
+See L<FS::part_event::Condition> for more information.
+
+=cut
+
+#false laziness w/part_event.pm
+#some false laziness w/part_export & part_pkg
+my %conditions;
+foreach my $INC ( @INC ) {
+ foreach my $file ( glob("$INC/FS/part_event/Condition/*.pm") ) {
+ warn "attempting to load Condition from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/part_event/Condition/: $file\n";
+ next;
+ };
+ my $mod = $1;
+ my $fullmod = "FS::part_event::Condition::$mod";
+ eval "use $fullmod;";
+ if ( $@ ) {
+ die "error using $fullmod (skipping): $@\n" if $@;
+ #warn "error using $fullmod (skipping): $@\n" if $@;
+ #next;
+ }
+ if ( $fullmod->disabled ) {
+ warn "$fullmod is disabled; skipping\n";
+ next;
+ }
+ #my $full_condition_sql = $fullmod. '::condition_sql';
+ my $condition_sql_coderef = sub { $fullmod->condition_sql(@_) };
+ my $order_sql_coderef = $fullmod->can('order_sql')
+ ? sub { $fullmod->order_sql(@_) }
+ : '';
+ $conditions{$mod} = {
+ ( map { $_ => $fullmod->$_() }
+ qw( description eventtable_hashref
+ implicit_flag remove_warning
+ order_sql_weight
+ )
+ # deprecated
+ #option_fields_hashref
+ ),
+ 'option_fields' => [ $fullmod->option_fields() ],
+ 'condition_sql' => $condition_sql_coderef,
+ 'order_sql' => $order_sql_coderef,
+ };
+ }
+}
+
+sub conditions {
+ my( $class, $eventtable ) = @_;
+ (
+ map { $_ => $conditions{$_} }
+# sort { $conditions{$a}->{'default_weight'}<=>$conditions{$b}->{'default_weight'} }
+# sort by ?
+ $class->all_conditionnames( $eventtable )
+ );
+
+}
+
+=item all_conditionnames [ EVENTTABLE ]
+
+Returns a list of just the condition names
+
+=cut
+
+sub all_conditionnames {
+ my ( $class, $eventtable ) = @_;
+
+ grep { !$eventtable || $conditions{$_}->{'eventtable_hashref'}{$eventtable} }
+ keys %conditions
+}
+
+=item join_conditions_sql [ EVENTTABLE ]
+
+Returns an SQL fragment selecting joining all condition options for an event as
+tables titled "cond_I<conditionname>". Typically used in conjunction with
+B<where_conditions_sql>.
+
+=cut
+
+sub join_conditions_sql {
+ my ( $class, $eventtable ) = @_;
+ my %conditions = $class->conditions( $eventtable );
+
+ join(' ',
+ map {
+ "LEFT JOIN part_event_condition AS cond_$_".
+ " ON ( part_event.eventpart = cond_$_.eventpart".
+ " AND cond_$_.conditionname = ". dbh->quote($_).
+ " )";
+ }
+ keys %conditions
+ );
+
+}
+
+=item where_conditions_sql [ EVENTTABLE [ , OPTION => VALUE, ... ] ]
+
+Returns an SQL fragment to select events which have unsatisfied conditions.
+Must be used in conjunction with B<join_conditions_sql>.
+
+The only current option is "time", the current time (or "pretend" current time
+as passed to freeside-daily), as a UNIX timestamp.
+
+=cut
+
+sub where_conditions_sql {
+ my ( $class, $eventtable, %options ) = @_;
+
+ my $time = $options{'time'};
+
+ my %conditions = $class->conditions( $eventtable );
+
+ my $where = join(' AND ',
+ map {
+ my $conditionname = $_;
+ my $coderef = $conditions{$conditionname}->{condition_sql};
+ my $sql = &$coderef( $eventtable, 'time' => $time,
+ 'driver_name' => driver_name(),
+ );
+ die "$coderef is not a CODEREF" unless ref($coderef) eq 'CODE';
+ "( cond_$conditionname.conditionname IS NULL OR $sql )";
+ }
+ grep { my $cond = $_;
+ ! grep { $_ eq $cond } @SKIP_CONDITION_SQL
+ }
+ keys %conditions
+ );
+
+ $where;
+}
+
+=item order_conditions_sql [ EVENTTABLE ]
+
+Returns an SQL fragment to order selected events. Must be used in conjunction
+with B<join_conditions_sql>.
+
+=cut
+
+sub order_conditions_sql {
+ my( $class, $eventtable ) = @_;
+
+ my %conditions = $class->conditions( $eventtable );
+
+ my $eventtables = join(' ', FS::part_event->eventtables_runorder);
+
+ my $order_by = join(', ',
+ "position( part_event.eventtable in ' $eventtables ')",
+ ( map {
+ my $conditionname = $_;
+ my $coderef = $conditions{$conditionname}->{order_sql};
+ my $sql = &$coderef( $eventtable );
+ "CASE WHEN cond_$conditionname.conditionname IS NULL
+ THEN -1
+ ELSE $sql
+ END
+ ";
+ }
+ sort { $conditions{$a}->{order_sql_weight}
+ <=> $conditions{$b}->{order_sql_weight}
+ }
+ grep { $conditions{$_}->{order_sql} }
+ keys %conditions
+ ),
+ 'part_event.weight'
+ );
+
+ "ORDER BY $order_by";
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_event::Condition>, L<FS::part_event>, L<FS::Record>, schema.html from
+the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_event_condition_option.pm b/FS/FS/part_event_condition_option.pm
new file mode 100644
index 0000000..3256dc0
--- /dev/null
+++ b/FS/FS/part_event_condition_option.pm
@@ -0,0 +1,151 @@
+package FS::part_event_condition_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::option_Common;
+use FS::part_event_condition;
+
+@ISA = qw( FS::option_Common ); # FS::Record);
+
+=head1 NAME
+
+FS::part_event_condition_option - Object methods for part_event_condition_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_event_condition_option;
+
+ $record = new FS::part_event_condition_option \%hash;
+ $record = new FS::part_event_condition_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_event_condition_option object represents an event condition option.
+FS::part_event_condition_option inherits from FS::Record. The following fields
+are currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item eventconditionnum - Event condition (see L<FS::part_event_condition>)
+
+=item optionname - Option name
+
+=item optionvalue - Option value
+
+=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 { 'part_event_condition_option'; }
+
+=item insert [ HASHREF | OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If a list or hash reference of options is supplied,
+part_event_condition_option_option records are created (see
+L<FS::part_event_condition_option_option>).
+
+=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 [ HASHREF | OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+If a list or hash reference of options is supplied,
+part_event_condition_option_option records are created or modified (see
+L<FS::part_event_condition_option_option>).
+
+=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;
+
+ my $error =
+ $self->ut_numbern('optionnum')
+ || $self->ut_foreign_key('eventconditionnum',
+ 'part_event_condition', 'eventconditionnum')
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+#this makes the nested options magically show up as perl refs
+#move it to a mixin class if we need nested options again
+sub optionvalue {
+ my $self = shift;
+ if ( scalar(@_) ) { #setting, no magic (here, insert takes care of it)
+ $self->set('optionvalue', @_);
+ } else { #getting, magic
+ my $optionvalue = $self->get('optionvalue');
+ if ( $optionvalue eq 'HASH' ) {
+ return { $self->options };
+ } else {
+ $optionvalue;
+ }
+ }
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::part_event_condition>, L<FS::part_event_condition_option_option>,
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_event_condition_option_option.pm b/FS/FS/part_event_condition_option_option.pm
new file mode 100644
index 0000000..7396c22
--- /dev/null
+++ b/FS/FS/part_event_condition_option_option.pm
@@ -0,0 +1,129 @@
+package FS::part_event_condition_option_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::part_event_condition_option;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_event_condition_option_option - Object methods for part_event_condition_option_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_event_condition_option_option;
+
+ $record = new FS::part_event_condition_option_option \%hash;
+ $record = new FS::part_event_condition_option_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_event_condition_option_option object represents a nested event
+condition option. FS::part_event_condition_option_option inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item optionoptionnum - primary key
+
+=item optionnum - Parent option (see L<FS::part_event_option>)
+
+=item optionname - Option name
+
+=item optionvalue - Option value
+
+
+=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 { 'part_event_condition_option_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 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;
+
+ my $error =
+ $self->ut_numbern('optionoptionnum')
+ || $self->ut_foreign_key('optionnum',
+ 'part_event_condition_option', 'optionnum' )
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_event_condition_option>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_event_option.pm b/FS/FS/part_event_option.pm
new file mode 100644
index 0000000..09b7756
--- /dev/null
+++ b/FS/FS/part_event_option.pm
@@ -0,0 +1,214 @@
+package FS::part_event_option;
+
+use strict;
+use vars qw( @ISA );
+use Scalar::Util qw( blessed );
+use FS::UID qw( dbh );
+use FS::Record qw( qsearch qsearchs );
+use FS::part_event;
+use FS::reason;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_event_option - Object methods for part_event_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_event_option;
+
+ $record = new FS::part_event_option \%hash;
+ $record = new FS::part_event_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_event_option object represents an event definition option (action
+option). FS::part_event_option inherits from FS::Record. The following fields
+are currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item eventpart - Event definition (see L<FS::part_event>)
+
+=item optionname - Option name
+
+=item optionvalue - Option value
+
+=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 { 'part_event_option'; }
+
+=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->optionname eq 'reasonnum' && $self->optionvalue eq 'HASH' ) {
+
+ my $error = $self->insert_reason(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ my $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
+
+# 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
+
+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 $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
+ if ( $self->optionname eq 'reasonnum' ) {
+ warn "reasonnum: ". $self->optionvalue;
+ }
+ if ( $self->optionname eq 'reasonnum' && $self->optionvalue eq 'HASH' ) {
+
+ my $error = $self->insert_reason(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ my $error = $self->SUPER::replace($old);
+ 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 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;
+
+ my $error =
+ $self->ut_numbern('optionnum')
+ || $self->ut_foreign_key('eventpart', 'part_event', 'eventpart' )
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+sub insert_reason {
+ my( $self, $reason ) = @_;
+
+ my $reason_obj = new FS::reason({
+ 'reason_type' => $reason->{'typenum'},
+ 'reason' => $reason->{'reason'},
+ });
+
+ $reason_obj->insert or $self->optionvalue( $reason_obj->reasonnum ) and '';
+
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::part_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..588606d
--- /dev/null
+++ b/FS/FS/part_export.pm
@@ -0,0 +1,490 @@
+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::option_Common;
+use FS::part_svc;
+use FS::part_export_option;
+use FS::export_svc;
+
+#for export modules, though they should probably just use it themselves
+use FS::queue;
+
+@ISA = qw( FS::option_Common );
+@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>).
+
+=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 $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 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 export_device
+
+Returns a list of associated FS::export_device records.
+
+=cut
+
+sub export_device {
+ my $self = shift;
+ qsearch('export_device', { '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;
+ $self->option_objects;
+}
+
+=item options
+
+Returns a list of option names and values suitable for assigning to a hash.
+
+=item option OPTIONNAME
+
+Returns the option value for the given name, or the empty string.
+
+=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, 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) unless $@;
+ $self;
+}
+
+#these should probably all go away, just let the subclasses define em
+
+=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 );
+}
+
+=item export_links SVC_OBJECT ARRAYREF
+
+Adds a list of web elements to ARRAYREF specific to this export and SVC_OBJECT.
+The elements are displayed in the UI to lead the the operator to external
+configuration, monitoring, and similar tools.
+
+=item export_getsettings SVC_OBJECT SETTINGS_HASHREF DEFAUTS_HASHREF
+
+Adds a hashref of settings to SETTINGSREF specific to this export and
+SVC_OBJECT. The elements can be displayed in the UI on the service view.
+
+DEFAULTSREF is a hashref with the same keys where true values indicate the
+setting is a default (and thus can be displayed in the UI with less emphasis,
+or hidden by default).
+
+=cut
+
+=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}};
+# }
+# '';
+#}
+
+#false laziness w/part_pkg & cdr
+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_freeside.pm b/FS/FS/part_export/acct_freeside.pm
new file mode 100644
index 0000000..3c287ca
--- /dev/null
+++ b/FS/FS/part_export/acct_freeside.pm
@@ -0,0 +1,139 @@
+package FS::part_export::acct_freeside;
+
+use vars qw( @ISA %info $DEBUG );
+use Data::Dumper;
+use Tie::IxHash;
+use FS::part_export;
+#use FS::Record qw( qsearch qsearchs );
+use Frontier::Client;
+
+@ISA = qw(FS::part_export);
+
+$DEBUG = 1;
+
+tie my %options, 'Tie::IxHash',
+ 'xmlrpc_url' => { label => 'Full URL to target Freeside xmlrpc.cgi', },
+ 'ss_username' => { label => 'Self-service username', },
+ 'ss_domain' => { label => 'Self-service domain', },
+ 'ss_password' => { label => 'Self-service password', },
+ 'domsvc' => { label => 'Domain svcnum on target machine', },
+ 'pkgnum' => { label => 'Customer package pkgnum on target machine', },
+ 'svcpart' => { label => 'Service definition svcpart on target machine', },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to another Freeside server',
+ 'options' => \%options,
+ 'notes' => <<END
+Real-time export to another Freeside server via self-service.
+Requires installation of
+<a href="http://search.cpan.org/dist/Frontier-Client">Frontier::Client</a>
+from CPAN and setup of an appropriate bulk customer on the other Freeside server.
+END
+);
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+
+ my $result = $self->_freeside_command('provision_acct',
+ 'pkgnum' => $self->option('pkgnum'),
+ 'svcpart' => $self->option('svcpart'),
+ 'username' => $svc_acct->username,
+ '_password' => $svc_acct->_password,
+ '_password2' => $svc_acct->_password,
+ 'domsvc' => $self->option('domsvc'),
+ );
+
+ $result->{error} || '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $svcnum = $self->_freeside_find_svc( $old );
+ return $svcnum unless $svcnum =~ /^(\d+)$/;
+
+ #only pw change supported for now...
+ my $result = $self->_freeside_command( 'myaccount_passwd',
+ 'svcnum' => $svcnum,
+ 'new_password' => $new->_password,
+ 'new_password2' => $new->_password,
+ );
+
+ $result->{error} || '';
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ my $svcnum = $self->_freeside_find_svc( $svc_acct );
+ return $svcnum unless $svcnum =~ /^(\d+)$/;
+
+ my $result = $self->_freeside_command( 'unprovision_svc', 'svcnum'=>$svcnum );
+
+ $result->{'error'} || '';
+
+}
+
+sub _freeside_find_svc {
+ my( $self, $svc_acct ) = ( shift, shift );
+
+ my $list_svcs = $self->_freeside_command( 'list_svcs', 'svcdb'=>'svc_acct' );
+ my @svc = grep { $svc_acct->username eq $_->{username}
+ #&& compare domains
+ } @{ $list_svcs->{svcs} };
+
+ return 'multiple services found on target FS' if scalar(@svc) > 1;
+ return 'no service found on target FS' if scalar(@svc) == 0; #shouldn't be fatal?
+
+ $svc[0]->{'svcnum'};
+
+}
+
+sub _freeside_command {
+ my( $self, $method, @args ) = @_;
+
+ my %login = (
+ 'username' => $self->option('ss_username'),
+ 'domain' => $self->option('ss_domain'),
+ 'password' => $self->option('ss_password'),
+ );
+ my $login_result = $self->_do_freeside_command( 'login', %login );
+ return $login_result if $login_result->{error};
+ my $session_id = $login_result->{session_id};
+
+ #could reuse $session id for replace & delete where we have to find then delete..
+
+ my %command = (
+ session_id => $session_id,
+ @args
+ );
+ my $result = $self->_do_freeside_command( $method, %command );
+
+ $result;
+
+}
+
+sub _do_freeside_command {
+ my( $self, $method, %args ) = @_;
+
+ # a questionable choice... but it'll do for now.
+ eval "use Frontier::Client;";
+ die $@ if $@;
+
+ #reuse?
+ my $conn = Frontier::Client->new( url => $self->option('xmlrpc_url') );
+
+ warn "sending FS selfservice $method: ". Dumper(\%args)
+ if $DEBUG;
+ my $result = $conn->call("FS.SelfService.XMLRPC.$method", \%args);
+ warn "FS selfservice $method response: ". Dumper($result)
+ if $DEBUG;
+
+ $result;
+
+}
+
+1;
diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm
new file mode 100644
index 0000000..d8d70a3
--- /dev/null
+++ b/FS/FS/part_export/acct_plesk.pm
@@ -0,0 +1,121 @@
+package FS::part_export::acct_plesk;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'URL' => { label=>'URL' },
+ 'login' => { label=>'Login' },
+ 'password' => { label=>'Password' },
+ 'debug' => { label=>'Enable debugging',
+ type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to Plesk managed mail service',
+ 'options'=> \%options,
+ 'notes' => <<'END'
+Real-time export to
+<a href="http://www.swsoft.com/">Plesk</a> managed server.
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
+from CPAN and proper <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration:acct_plesk.pm">configuration</a>.
+END
+);
+
+sub rebless { shift; }
+
+# experiment: want the status of these right away (don't want account to
+# create or whatever and then get error in the queue from dup username or
+# something), so no queueing
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->_plesk_command( 'mail_add',
+ $svc_acct->domain,
+ $svc_acct->username,
+ $svc_acct->_password,
+ ) ||
+ $self->_export_unsuspend($svc_acct);
+}
+
+sub _plesk_command {
+ my( $self, $method, $domain, @args ) = @_;
+
+ eval "use Net::Plesk;";
+ return $@ if $@;
+
+ local($Net::Plesk::DEBUG) = 1
+ if $self->option('debug');
+
+ my $plesk = new Net::Plesk (
+ 'POST' => $self->option('URL'),
+ ':HTTP_AUTH_LOGIN' => $self->option('login'),
+ ':HTTP_AUTH_PASSWD' => $self->option('password'),
+ );
+
+ my $dresponse = $plesk->domain_get( $domain );
+ return $dresponse->errortext unless $dresponse->is_success;
+ my $domainID = $dresponse->id;
+
+ my $response = $plesk->$method($dresponse->id, @args);
+ return $response->errortext unless $response->is_success;
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ return "can't change domain with Plesk"
+ if $old->domain ne $new->domain;
+ return "can't change username with Plesk"
+ if $old->username ne $new->username;
+ return '' unless $old->_password ne $new->_password;
+
+ $self->_plesk_command( 'mail_set',
+ $new->domain,
+ $new->username,
+ $new->_password,
+ $old->cust_svc->cust_pkg->susp ? 0 : 1,
+ );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->_plesk_command( 'mail_remove',
+ $svc_acct->domain,
+ $svc_acct->username,
+ );
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->_plesk_command( 'mail_set',
+ $svc_acct->domain,
+ $svc_acct->username,
+ $svc_acct->_password,
+ 0,
+ );
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->_plesk_command( 'mail_set',
+ $svc_acct->domain,
+ $svc_acct->username,
+ $svc_acct->_password,
+ 1,
+ );
+}
+
+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..9f1ae7b
--- /dev/null
+++ b/FS/FS/part_export/acct_sql.pm
@@ -0,0 +1,310 @@
+package FS::part_export::acct_sql;
+
+use vars qw(@ISA %info);
+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',
+ },
+ 'static' => { label =>
+ 'Database schema mapping to static values.',
+ type => 'textarea',
+ },
+ 'primary_key' => { label => 'Database primary key' },
+ 'crypt' => { label => 'Password encryption',
+ type=>'select', options=>[qw(crypt md5)],
+ default=>'crypt',
+ },
+;
+
+tie my %vpopmail_map, 'Tie::IxHash',
+ 'pw_name' => 'username',
+ 'pw_domain' => 'domain',
+ 'pw_passwd' => 'crypt_password',
+ 'pw_uid' => 'uid',
+ 'pw_gid' => 'gid',
+ 'pw_gecos' => 'finger',
+ 'pw_dir' => 'dir',
+ #'pw_shell' => 'shell',
+ 'pw_shell' => 'quota',
+;
+my $vpopmail_map = join('\n', map "$_ $vpopmail_map{$_}", keys %vpopmail_map );
+
+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 );
+
+tie my %postfix_native_mailbox_map, 'Tie::IxHash',
+ 'userid' => 'email',
+ 'uid' => 'uid',
+ 'gid' => 'gid',
+ 'password' => 'ldap_password',
+ 'mail' => 'domain_slash_username',
+;
+my $postfix_native_mailbox_map =
+ join('\n', map "$_ $postfix_native_mailbox_map{$_}",
+ keys %postfix_native_mailbox_map );
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export of accounts to SQL databases '.
+ '(vpopmail, Postfix+Courier IMAP, others?)',
+ 'options' => \%options,
+ 'nodomain' => '',
+ 'notes' => <<END
+Export accounts (svc_acct records) to SQL databases. Currently has default
+configurations for vpopmail and Postfix+Courier IMAP but intended to be
+configurable for other schemas as well.
+
+<BR><BR>In contrast to sqlmail, this 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" database schemas rather than
+configure the MTA or POP/IMAP server for a Freeside-specific schema, and
+to be configured for different mail server setups.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <li><INPUT TYPE="button" VALUE="vpopmail" onClick='
+ this.form.table.value = "vpopmail";
+ this.form.schema.value = "$vpopmail_map";
+ this.form.primary_key.value = "pw_name, pw_domain";
+ '>
+ <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";
+ '>
+ <LI><INPUT TYPE="button" VALUE="postfix_native_mailbox" onClick='
+ this.form.table.value = "users";
+ this.form.schema.value = "$postfix_native_mailbox_map";
+ this.form.primary_key.value = "userid";
+ '>
+</UL>
+END
+);
+
+sub _schema_map { shift->_map('schema'); }
+sub _static_map { shift->_map('static'); }
+
+sub _map {
+ my $self = shift;
+ map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) );
+}
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+
+ my %schema = $self->_schema_map;
+ my %static = $self->_static_map;
+
+ my %record = (
+
+ ( map { $_ => $static{$_} } keys %static ),
+
+ ( map { my $value = $schema{$_};
+ my @arg = ();
+ push @arg, $self->option('crypt')
+ if $value eq 'crypt_password' && $self->option('crypt');
+ $_ => $svc_acct->$value(@arg);
+ } keys %schema
+ ),
+
+ );
+
+ 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 {
+ my($self, $new, $old) = (shift, shift, shift);
+
+ my %schema = $self->_schema_map;
+ my %static = $self->_static_map;
+
+ my @primary_key = ();
+ if ( $self->option('primary_key') =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
+ my $keymap = $schema{$key};
+ push @primary_key, $old->$keymap();
+ }
+ } else {
+ my $keymap = $schema{$self->option('primary_key')};
+ push @primary_key, $old->$keymap();
+ }
+
+ my %record = (
+
+ ( map { $_ => $static{$_} } keys %static ),
+
+ ( map { my $value = $schema{$_};
+ my @arg = ();
+ push @arg, $self->option('crypt')
+ if $value eq 'crypt_password' && $self->option('crypt');
+ $_ => $new->$value(@arg);
+ } keys %schema
+ ),
+
+ );
+
+ my $err_or_queue = $self->acct_sql_queue(
+ $new->svcnum,
+ 'replace',
+ $self->option('table'),
+ $self->option('primary_key'), @primary_key,
+ %record,
+ );
+ return $err_or_queue unless ref($err_or_queue);
+ '';
+}
+
+sub _export_delete {
+ my ( $self, $svc_acct ) = (shift, shift);
+
+ my %schema = $self->_schema_map;
+
+ my %primary_key = ();
+ if ( $self->option('primary_key') =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
+ my $keymap = $schema{$key};
+ $primary_key{ $key } = $svc_acct->$keymap();
+ }
+ } else {
+ my $keymap = $schema{$self->option('primary_key')};
+ $primary_key{ $self->option('primary_key') } = $svc_acct->$keymap(),
+ }
+
+ my $err_or_queue = $self->acct_sql_queue(
+ $svc_acct->svcnum,
+ 'delete',
+ $self->option('table'),
+ %primary_key,
+ #$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( values(%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_replace { #subroutine, not method
+ my $dbh = acct_sql_connect(shift, shift, shift);
+
+ my( $table, $pkey ) = ( shift, shift );
+
+ my %primary_key = ();
+ if ( $pkey =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $pkey ) ) {
+ $primary_key{$key} = shift;
+ }
+ } else {
+ $primary_key{$pkey} = shift;
+ }
+
+ my %record = @_;
+
+ my $sth = $dbh->prepare(
+ "UPDATE $table".
+ ' SET '. join(', ', map "$_ = ?", keys %record ).
+ ' WHERE '. join(' AND ', map "$_ = ?", keys %primary_key )
+ ) or die $dbh->errstr;
+
+ $sth->execute( values(%record), values(%primary_key) );
+
+ $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/amazon_ec2.pm b/FS/FS/part_export/amazon_ec2.pm
new file mode 100644
index 0000000..0e65ca0
--- /dev/null
+++ b/FS/FS/part_export/amazon_ec2.pm
@@ -0,0 +1,169 @@
+package FS::part_export::amazon_ec2;
+
+use base qw( FS::part_export );
+
+use vars qw(@ISA %info $replace_ok_kludge);
+use Tie::IxHash;
+use FS::Record qw( qsearchs );
+use FS::svc_external;
+
+tie my %options, 'Tie::IxHash',
+ 'access_key' => { label => 'AWS access key', },
+ 'secret_key' => { label => 'AWS secret key', },
+ 'ami' => { label => 'AMI', 'default' => 'ami-ff46a796', },
+ 'keyname' => { label => 'Keypair name', },
+ #option to turn off (or on) ip address allocation
+;
+
+%info = (
+ 'svc' => 'svc_external',
+ 'desc' =>
+ 'Export to Amazon EC2',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Create instances in the Amazon EC2 (Elastic compute cloud). Install
+Net::Amazon::EC2 perl module. Advisable to set svc_external-skip_manual config
+option.
+END
+);
+
+$replace_ok_kludge = 0;
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_external) = (shift, shift);
+ $err_or_queue = $self->amazon_ec2_queue( $svc_external->svcnum, 'insert',
+ $svc_external->svcnum,
+ $self->option('ami'),
+ $self->option('keyname'),
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return '' if $replace_ok_kludge;
+ return "can't change instance id or IP address";
+ #$err_or_queue = $self->amazon_ec2_queue( $new->svcnum,
+ # 'replace', $new->username, $new->_password );
+ #ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_delete {
+ my( $self, $svc_external ) = (shift, shift);
+ my( $instance_id, $ip ) = split(/:/, $svc_external->title );
+ $err_or_queue = $self->amazon_ec2_queue( $svc_external->svcnum, 'delete',
+ $instance_id,
+ $ip,
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+#these three are optional
+# fallback for svc_acct will change and restore password
+#sub _export_suspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->amazon_ec2_queue( $svc_something->svcnum,
+# 'suspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+#
+#sub _export_unsuspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->amazon_ec2_queue( $svc_something->svcnum,
+# 'unsuspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+
+sub export_links {
+ my($self, $svc_external, $arrayref) = (shift, shift, shift);
+ my( $instance_id, $ip ) = split(/:/, $svc_external->title );
+
+ push @$arrayref, qq!<A HREF="http://$ip/">http://$ip/</A>!;
+ '';
+}
+
+###
+
+#a good idea to queue anything that could fail or take any time
+sub amazon_ec2_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::amazon_ec2::amazon_ec2_$method",
+ };
+ $queue->insert( $self->option('access_key'),
+ $self->option('secret_key'),
+ @_
+ )
+ or $queue;
+}
+
+sub amazon_ec2_new {
+ my( $access_key, $secret_key, @rest ) = @_;
+
+ eval 'use Net::Amazon::EC2;';
+ die $@ if $@;
+
+ my $ec2 = new Net::Amazon::EC2 'AWSAccessKeyId' => $access_key,
+ 'SecretAccessKey' => $secret_key;
+
+ ( $ec2, @rest );
+}
+
+sub amazon_ec2_insert { #subroutine, not method
+ my( $ec2, $svcnum, $ami, $keyname ) = amazon_ec2_new(@_);
+
+ my $reservation_info = $ec2->run_instances( 'ImageId' => $ami,
+ 'KeyName' => $keyname,
+ 'MinCount' => 1,
+ 'MaxCount' => 1,
+ );
+
+ my $instance_id = $reservation_info->instances_set->[0]->instance_id;
+
+ my $ip = $ec2->allocate_address
+ or die "can't allocate address";
+ $ec2->associate_address('InstanceId' => $instance_id,
+ 'PublicIp' => $ip,
+ )
+ or die "can't assocate IP address $ip with instance $instance_id";
+
+ my $svc_external = qsearchs('svc_external', { 'svcnum' => $svcnum } )
+ or die "can't find svc_external.svcnum $svcnum\n";
+
+ $svc_external->title("$instance_id:$ip");
+
+ local($replace_ok_kludge) = 1;
+ my $error = $svc_external->replace;
+ die $error if $error;
+
+}
+
+#sub amazon_ec2_replace { #subroutine, not method
+#}
+
+sub amazon_ec2_delete { #subroutine, not method
+ my( $ec2, $id, $ip ) = amazon_ec2_new(@_);
+
+ my $instance_id = sprintf('i-%x', $id);
+ $ec2->disassociate_address('PublicIp'=>$ip)
+ or die "can't dissassocate $ip";
+
+ $ec2->release_address('PublicIp'=>$ip)
+ or die "can't release $ip";
+
+ my $result = $ec2->terminate_instances('InstanceId'=>$instance_id);
+ #check for instance_id match or something?
+
+}
+
+#sub amazon_ec2_suspend { #subroutine, not method
+#}
+
+#sub amazon_ec2_unsuspend { #subroutine, not method
+#}
+
+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..c006db9
--- /dev/null
+++ b/FS/FS/part_export/artera_turbo.pm
@@ -0,0 +1,181 @@
+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',
+ },
+ 'enable_edit' => { 'label' => 'Enable local editing of Artera serial numbers and key codes (note that the changes will NOT be exported to Artera)',
+ '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 configuration 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(sprintf('%010d', $result->{'ASN'}));
+ $new->title( substr('0000000000'.uc($result->{'AKC'}), -10) );
+ $new->replace($svc_external);
+ } else {
+ $result->{'message'} || 'No response from Artera';
+ }
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return '' if $self->option('enable_edit');
+ 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->queue_statusChange(17, $svc_external);
+}
+
+sub _export_suspend {
+ my( $self, $svc_external ) = (shift, shift);
+ $self->queue_statusChange(16, $svc_external);
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_external ) = (shift, shift);
+ $self->queue_statusChange(15, $svc_external);
+}
+
+sub queue_statusChange {
+ my( $self, $status, $svc_external ) = @_;
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svc_external->svcnum,
+ 'job' => 'FS::part_export::artera_turbo::statusChange',
+ };
+ $queue->insert(
+ ( map { $self->option($_) }
+ qw( rid username password production ) ),
+ $status,
+ $svc_external->id,
+ $svc_external->title,
+ $self->option('debug'),
+ );
+}
+
+sub statusChange {
+ my( $rid, $username, $password, $prod, $status, $id, $title, $debug ) = @_;
+
+ eval "use Net::Artera;";
+ return $@ if $@;
+ $Net::Artera::DEBUG = 1 if $debug;
+
+ my $artera = new Net::Artera (
+ 'rid' => $rid,
+ 'username' => $username,
+ 'password' => $password,
+ 'production' => $prod,
+ );
+
+ my $result = $artera->statusChange(
+ 'asn' => sprintf('%010d', $id),
+ 'akc' => substr("0000000000$title", -10),
+ 'statusid' => $status,
+ );
+
+ die $result->{'message'} unless $result->{'id'} == 1;
+
+}
+
+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..edfd4b1
--- /dev/null
+++ b/FS/FS/part_export/communigate_pro.pm
@@ -0,0 +1,994 @@
+package FS::part_export::communigate_pro;
+
+use strict;
+use vars qw(@ISA %info %options %quotas $DEBUG);
+use Data::Dumper;
+use Tie::IxHash;
+use FS::part_export;
+use FS::queue;
+
+@ISA = qw(FS::part_export);
+
+$DEBUG = 1;
+
+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 (default when not specified in service)',
+ type => 'select',
+ options => [qw(MultiMailbox TextMailbox MailDirMailbox AGrade BGrade CGrade)],
+ default => 'MultiMailbox',
+ },
+ 'externalFlag' => { label => 'Create accounts with an external (visible for legacy mailers) INBOX.',
+ type => 'checkbox',
+ },
+ 'AccessModes' => { label => 'Access modes (default when not specified in service)',
+ default => 'Mail POP IMAP PWD WebMail WebSite',
+ },
+ 'create_domain' => { label => 'Domain creation API call',
+ type => 'select',
+ options => [qw( CreateDomain CreateSharedDomain )],
+ }
+;
+
+%info = (
+ 'svc' => [qw( svc_acct svc_domain svc_forward svc_mailinglist )],
+ 'desc' => 'Real-time export of accounts, domains, mail forwards and mailing lists to a CommuniGate Pro mail server',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Real time export of accounts, domains, mail forwards and mailing lists 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
+);
+
+%quotas = (
+ 'quota' => 'MaxAccountSize',
+ 'file_quota' => 'MaxWebSize',
+ 'file_maxnum' => 'MaxWebFiles',
+ 'file_maxsize' => 'MaxFileSize',
+);
+
+sub rebless { shift; }
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ $svc_acct->email;
+}
+
+sub _export_insert {
+ my( $self, $svc_x ) = (shift, shift);
+
+ my $table = $svc_x->table;
+ my $method = "_export_insert_$table";
+ $self->$method($svc_x, @_);
+}
+
+sub _export_insert_svc_acct {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ my %settings = (
+ 'AccessModes' => [ split(' ', ( $svc_acct->cgp_accessmodes
+ || $self->option('AccessModes') )
+ )
+ ],
+ 'RealName' => $svc_acct->finger,
+ 'Password' => $svc_acct->_password,
+
+ 'PasswordRecovery' => ($svc_acct->password_recover ? 'YES':'NO'),
+
+ 'RulesAllowed' => $svc_acct->cgp_rulesallowed,
+ 'RPOPAllowed' =>($svc_acct->cgp_rpopallowed ?'YES':'NO'),
+ 'MailToAll' =>($svc_acct->cgp_mailtoall ?'YES':'NO'),
+ 'AddMailTrailer' =>($svc_acct->cgp_addmailtrailer ?'YES':'NO'),
+
+ map { $quotas{$_} => $svc_acct->$_() }
+ grep $svc_acct->$_(), keys %quotas
+ );
+ #XXX phase 3: archive messages, mailing lists
+
+ my @options = ( 'CreateAccount',
+ 'accountName' => $self->export_username($svc_acct),
+ 'accountType' => ( $svc_acct->cgp_type
+ || $self->option('accountType') ),
+ 'settings' => \%settings
+ );
+
+ push @options, 'externalFlag' => $self->option('externalFlag')
+ if $self->option('externalFlag');
+
+ #let's do the create realtime too, for much the same reasons, and to avoid
+ #pain of trying to queue w/dep the prefs & aliases
+ eval { $self->communigate_pro_runcommand( @options ) };
+ return $@ if $@;
+
+ #preferences
+ my %prefs = ();
+ $prefs{'DeleteMode'} = $svc_acct->cgp_deletemode if $svc_acct->cgp_deletemode;
+ $prefs{'EmptyTrash'} = $svc_acct->cgp_emptytrash if $svc_acct->cgp_emptytrash;
+ $prefs{'Language'} = $svc_acct->cgp_language if $svc_acct->cgp_language;
+ $prefs{'TimeZone'} = $svc_acct->cgp_timezone if $svc_acct->cgp_timezone;
+ $prefs{'SkinName'} = $svc_acct->cgp_skinname if $svc_acct->cgp_skinname;
+ $prefs{'ProntoSkinName'} = $svc_acct->cgp_prontoskinname if $svc_acct->cgp_prontoskinname;
+ $prefs{'SendMDNMode'} = $svc_acct->cgp_sendmdnmode if $svc_acct->cgp_sendmdnmode;
+ if ( keys %prefs ) {
+ my $pref_err = $self->communigate_pro_queue( $svc_acct->svcnum,
+ 'UpdateAccountPrefs',
+ $self->export_username($svc_acct),
+ %prefs,
+ );
+ warn "WARNING: error queueing UpdateAccountPrefs job: $pref_err"
+ if $pref_err;
+ }
+
+ #aliases
+ if ( $svc_acct->cgp_aliases ) {
+ my $alias_err = $self->communigate_pro_queue( $svc_acct->svcnum,
+ 'SetAccountAliases',
+ $self->export_username($svc_acct),
+ [ split(/\s*[,\s]\s*/, $svc_acct->cgp_aliases) ],
+ );
+ warn "WARNING: error queueing SetAccountAliases job: $alias_err"
+ if $alias_err;
+ }
+
+ my $rule_error = $self->communigate_pro_queue(
+ $svc_acct->svcnum,
+ 'SetAccountMailRules',
+ $self->export_username($svc_acct),
+ $svc_acct->cgp_rule_arrayref,
+ );
+ warn "WARNING: error queueing SetAccountMailRules job: $rule_error"
+ if $rule_error;
+
+ '';
+
+}
+
+sub _export_insert_svc_domain {
+ my( $self, $svc_domain ) = (shift, shift);
+
+ my $create = $self->option('create_domain') || 'CreateDomain';
+
+ my %settings = (
+ 'DomainAccessModes' => [ split(' ', $svc_domain->cgp_accessmodes ) ],
+ );
+ $settings{'AccountsLimit'} = $svc_domain->max_accounts
+ if $svc_domain->max_accounts;
+ $settings{'AdminDomainName'} = $svc_domain->parent_svc_x->domain
+ if $svc_domain->parent_svcnum;
+ $settings{'TrailerText'} = $svc_domain->trailer
+ if $svc_domain->trailer;
+
+ my @options = ( $create, $svc_domain->domain, \%settings );
+
+ eval { $self->communigate_pro_runcommand( @options ) };
+ return $@ if $@;
+
+ #aliases
+ if ( $svc_domain->cgp_aliases ) {
+ my $alias_err = $self->communigate_pro_queue( $svc_domain->svcnum,
+ 'SetDomainAliases',
+ $svc_domain->domain,
+ split(/\s*[,\s]\s*/, $svc_domain->cgp_aliases),
+ );
+ warn "WARNING: error queueing SetDomainAliases job: $alias_err"
+ if $alias_err;
+ }
+
+ #account defaults
+ my $def_err = $self->communigate_pro_queue( $svc_domain->svcnum,
+ 'SetAccountDefaults',
+ $svc_domain->domain,
+ 'PWDAllowed' =>($svc_domain->acct_def_password_selfchange ? 'YES':'NO'),
+ 'PasswordRecovery' => ($svc_domain->acct_def_password_recover ? 'YES':'NO'),
+ 'AccessModes' => $svc_domain->acct_def_cgp_accessmodes,
+ 'MaxAccountSize' => $svc_domain->acct_def_quota,
+ 'MaxWebSize' => $svc_domain->acct_def_file_quota,
+ 'MaxWebFile' => $svc_domain->acct_def_file_maxnum,
+ 'MaxFileSize' => $svc_domain->acct_def_file_maxsize,
+ 'RulesAllowed' => $svc_domain->acct_def_cgp_rulesallowed,
+ 'RPOPAllowed' =>($svc_domain->acct_def_cgp_rpopallowed ?'YES':'NO'),
+ 'MailToAll' =>($svc_domain->acct_def_cgp_mailtoall ?'YES':'NO'),
+ 'AddMailTrailer' =>($svc_domain->acct_def_cgp_addmailtrailer ?'YES':'NO'),
+ );
+ warn "WARNING: error queueing SetAccountDefaults job: $def_err"
+ if $def_err;
+
+ #account defaults prefs
+ my $pref_err = $self->communigate_pro_queue( $svc_domain->svcnum,
+ 'SetAccountDefaultPrefs',
+ $svc_domain->domain,
+ 'DeleteMode' => $svc_domain->acct_def_cgp_deletemode,
+ 'EmptyTrash' => $svc_domain->acct_def_cgp_emptytrash,
+ 'Language' => $svc_domain->acct_def_cgp_language,
+ 'TimeZone' => $svc_domain->acct_def_cgp_timezone,
+ 'SkinName' => $svc_domain->acct_def_cgp_skinname,
+ 'ProntoSkinName' => $svc_domain->acct_def_cgp_prontoskinname,
+ 'SendMDNMode' => $svc_domain->acct_def_cgp_sendmdnmode,
+ );
+ warn "WARNING: error queueing SetAccountDefaultPrefs job: $pref_err"
+ if $pref_err;
+
+ my $rule_error = $self->communigate_pro_queue(
+ $svc_domain->svcnum,
+ 'SetDomainMailRules',
+ $svc_domain->domain,
+ $svc_domain->cgp_rule_arrayref,
+ );
+ warn "WARNING: error queueing SetDomainMailRules job: $rule_error"
+ if $rule_error;
+
+ '';
+
+}
+
+sub _export_insert_svc_forward {
+ my( $self, $svc_forward ) = (shift, shift);
+
+ my $src = $svc_forward->src || $svc_forward->srcsvc_acct->email;
+ my $dst = $svc_forward->dst || $svc_forward->dstsvc_acct->email;
+
+ #real-time here, presuming CGP does some dup detection?
+ eval { $self->communigate_pro_runcommand( 'CreateForwarder', $src, $dst); };
+ return $@ if $@;
+
+ '';
+}
+
+sub _export_insert_svc_mailinglist {
+ my( $self, $svc_mlist ) = (shift, shift);
+
+ my @members = map $_->email_address,
+ $svc_mlist->mailinglist->mailinglistmember;
+
+ #real-time here, presuming CGP does some dup detection
+ eval { $self->communigate_pro_runcommand(
+ 'CreateGroup',
+ $svc_mlist->username.'@'.$svc_mlist->domain,
+ { 'RealName' => $svc_mlist->listname,
+ 'SetReplyTo' => ( $svc_mlist->reply_to ? 'YES' : 'NO' ),
+ 'RemoveAuthor' => ( $svc_mlist->remove_from ? 'YES' : 'NO' ),
+ 'RejectAuto' => ( $svc_mlist->reject_auto ? 'YES' : 'NO' ),
+ 'RemoveToAndCc' => ( $svc_mlist->remove_to_and_cc ? 'YES' : 'NO' ),
+ 'Members' => \@members,
+ }
+ );
+ };
+ return $@ if $@;
+
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $table = $new->table;
+ my $method = "_export_replace_$table";
+ $self->$method($new, $old, @_);
+}
+
+sub _export_replace_svc_acct {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ #let's just do the rename part realtime rather than trying to queue
+ #w/dependencies. we don't want FS winding up out-of-sync with the wrong
+ #username and a queued job anyway. right??
+ if ( $self->export_username($old) ne $self->export_username($new) ) {
+ eval { $self->communigate_pro_runcommand(
+ 'RenameAccount',
+ $self->export_username($old),
+ $self->export_username($new),
+ ) };
+ return $@ if $@;
+ }
+
+ if ( $new->_password ne $old->_password
+ && '*SUSPENDED* '.$old->_password ne $new->_password
+ ) {
+ $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword',
+ $self->export_username($new), $new->_password
+ );
+ }
+
+ my %settings = ();
+
+ $settings{'RealName'} = $new->finger
+ if $old->finger ne $new->finger;
+ $settings{$quotas{$_}} = $new->$_()
+ foreach grep $old->$_() ne $new->$_(), keys %quotas;
+ $settings{'accountType'} = $new->cgp_type
+ if $old->cgp_type ne $new->cgp_type;
+ $settings{'AccessModes'} = $new->cgp_accessmodes
+ if $old->cgp_accessmodes ne $new->cgp_accessmodes
+ || $old->cgp_type ne $new->cgp_type;
+
+ $settings{'PasswordRecovery'} = ( $new->password_recover ? 'YES':'NO' )
+ if $old->password_recover ne $new->password_recover;
+
+ $settings{'RulesAllowed'} = $new->cgp_rulesallowed
+ if $old->cgp_rulesallowed ne $new->cgp_rulesallowed;
+ $settings{'RPOPAllowed'} = ( $new->cgp_rpopallowed ? 'YES':'NO' )
+ if $old->cgp_rpopallowed ne $new->cgp_rpopallowed;
+ $settings{'MailToAll'} = ( $new->cgp_mailtoall ? 'YES':'NO' )
+ if $old->cgp_mailtoall ne $new->cgp_mailtoall;
+ $settings{'AddMailTrailer'} = ( $new->cgp_addmailtrailer ? 'YES':'NO' )
+ if $old->cgp_addmailtrailer ne $new->cgp_addmailtrailer;
+
+ #XXX phase 3: archive messages, mailing lists
+
+ if ( keys %settings ) {
+ my $error = $self->communigate_pro_queue(
+ $new->svcnum,
+ 'UpdateAccountSettings',
+ $self->export_username($new),
+ %settings,
+ );
+ return $error if $error;
+ }
+
+ #preferences
+ my %prefs = ();
+ $prefs{'DeleteMode'} = $new->cgp_deletemode
+ if $old->cgp_deletemode ne $new->cgp_deletemode;
+ $prefs{'EmptyTrash'} = $new->cgp_emptytrash
+ if $old->cgp_emptytrash ne $new->cgp_emptytrash;
+ $prefs{'Language'} = $new->cgp_language
+ if $old->cgp_language ne $new->cgp_language;
+ $prefs{'TimeZone'} = $new->cgp_timezone
+ if $old->cgp_timezone ne $new->cgp_timezone;
+ $prefs{'SkinName'} = $new->cgp_skinname
+ if $old->cgp_skinname ne $new->cgp_skinname;
+ $prefs{'ProntoSkinName'} = $new->cgp_prontoskinname
+ if $old->cgp_prontoskinname ne $new->cgp_prontoskinname;
+ $prefs{'SendMDNMode'} = $new->cgp_sendmdnmode
+ if $old->cgp_sendmdnmode ne $new->cgp_sendmdnmode;
+ if ( keys %prefs ) {
+ my $pref_err = $self->communigate_pro_queue( $new->svcnum,
+ 'UpdateAccountPrefs',
+ $self->export_username($new),
+ %prefs,
+ );
+ warn "WARNING: error queueing UpdateAccountPrefs job: $pref_err"
+ if $pref_err;
+ }
+
+ if ( $old->cgp_aliases ne $new->cgp_aliases ) {
+ my $error = $self->communigate_pro_queue(
+ $new->svcnum,
+ 'SetAccountAliases',
+ $self->export_username($new),
+ [ split(/\s*[,\s]\s*/, $new->cgp_aliases) ],
+ );
+ return $error if $error;
+ }
+
+ my $rule_error = $self->communigate_pro_queue(
+ $new->svcnum,
+ 'SetAccountMailRules',
+ $self->export_username($new),
+ $new->cgp_rule_arrayref,
+ );
+ warn "WARNING: error queueing SetAccountMailRules job: $rule_error"
+ if $rule_error;
+
+ '';
+
+}
+
+sub _export_replace_svc_domain {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ if ( $old->domain ne $new->domain ) {
+ my $error = $self->communigate_pro_queue( $new->svcnum, 'RenameDomain',
+ $old->domain, $new->domain,
+ );
+ return $error if $error;
+ }
+ my %settings = ();
+ $settings{'AccountsLimit'} = $new->max_accounts
+ if $old->max_accounts ne $new->max_accounts;
+ $settings{'TrailerText'} = $new->trailer
+ if $old->trailer ne $new->trailer;
+ $settings{'DomainAccessModes'} = $new->cgp_accessmodes
+ if $old->cgp_accessmodes ne $new->cgp_accessmodes;
+ $settings{'AdminDomainName'} =
+ $new->parent_svcnum ? $new->parent_svc_x->domain : ''
+ if $old->parent_svcnum != $new->parent_svcnum;
+
+ if ( keys %settings ) {
+ my $error = $self->communigate_pro_queue( $new->svcnum,
+ 'UpdateDomainSettings',
+ $new->domain,
+ %settings,
+ );
+ return $error if $error;
+ }
+
+ if ( $old->cgp_aliases ne $new->cgp_aliases ) {
+ my $error = $self->communigate_pro_queue( $new->svcnum,
+ 'SetDomainAliases',
+ $new->domain,
+ split(/\s*[,\s]\s*/, $new->cgp_aliases),
+ );
+ return $error if $error;
+ }
+
+ #below this identical to insert... any value to doing an Update here?
+ #not seeing any big one... i guess it would be nice to avoid the update
+ #when things haven't changed
+
+ #account defaults
+ my $def_err = $self->communigate_pro_queue( $new->svcnum,
+ 'SetAccountDefaults',
+ $new->domain,
+ 'PWDAllowed' => ( $new->acct_def_password_selfchange ? 'YES' : 'NO' ),
+ 'PasswordRecovery' => ( $new->acct_def_password_recover ? 'YES' : 'NO' ),
+ 'AccessModes' => $new->acct_def_cgp_accessmodes,
+ 'MaxAccountSize' => $new->acct_def_quota,
+ 'MaxWebSize' => $new->acct_def_file_quota,
+ 'MaxWebFile' => $new->acct_def_file_maxnum,
+ 'MaxFileSize' => $new->acct_def_file_maxsize,
+ 'RulesAllowed' => $new->acct_def_cgp_rulesallowed,
+ 'RPOPAllowed' => ( $new->acct_def_cgp_rpopallowed ? 'YES' : 'NO' ),
+ 'MailToAll' => ( $new->acct_def_cgp_mailtoall ? 'YES' : 'NO' ),
+ 'AddMailTrailer' => ( $new->acct_def_cgp_addmailtrailer ? 'YES' : 'NO' ),
+ );
+ warn "WARNING: error queueing SetAccountDefaults job: $def_err"
+ if $def_err;
+
+ #account defaults prefs
+ my $pref_err = $self->communigate_pro_queue( $new->svcnum,
+ 'SetAccountDefaultPrefs',
+ $new->domain,
+ 'DeleteMode' => $new->acct_def_cgp_deletemode,
+ 'EmptyTrash' => $new->acct_def_cgp_emptytrash,
+ 'Language' => $new->acct_def_cgp_language,
+ 'TimeZone' => $new->acct_def_cgp_timezone,
+ 'SkinName' => $new->acct_def_cgp_skinname,
+ 'ProntoSkinName' => $new->acct_def_cgp_prontoskinname,
+ 'SendMDNMode' => $new->acct_def_cgp_sendmdnmode,
+ );
+ warn "WARNING: error queueing SetAccountDefaultPrefs job: $pref_err"
+ if $pref_err;
+
+ my $rule_error = $self->communigate_pro_queue(
+ $new->svcnum,
+ 'SetDomainMailRules',
+ $new->domain,
+ $new->cgp_rule_arrayref,
+ );
+ warn "WARNING: error queueing SetDomainMailRules job: $rule_error"
+ if $rule_error;
+
+ '';
+}
+
+sub _export_replace_svc_forward {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $osrc = $old->src || $old->srcsvc_acct->email;
+ my $nsrc = $new->src || $new->srcsvc_acct->email;
+ my $odst = $old->dst || $old->dstsvc_acct->email;
+ my $ndst = $new->dst || $new->dstsvc_acct->email;
+
+ if ( $odst ne $ndst ) {
+
+ #no change command, so delete and create (real-time)
+ eval { $self->communigate_pro_runcommand('DeleteForwarder', $osrc) };
+ return $@ if $@;
+ eval { $self->communigate_pro_runcommand('CreateForwarder', $nsrc, $ndst)};
+ return $@ if $@;
+
+ } elsif ( $osrc ne $nsrc ) {
+
+ #real-time here, presuming CGP does some dup detection?
+ eval { $self->communigate_pro_runcommand( 'RenameForwarder', $osrc, $nsrc)};
+ return $@ if $@;
+
+ } else {
+ warn "communigate replace called for svc_forward with no changes\n";#confess
+ }
+
+ '';
+}
+
+sub _export_replace_svc_mailinglist {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $oldGroupName = $old->username.'@'.$old->domain;
+ my $newGroupName = $new->username.'@'.$new->domain;
+
+ if ( $oldGroupName ne $newGroupName ) {
+ eval { $self->communigate_pro_runcommand(
+ 'RenameGroup', $oldGroupName, $newGroupName ); };
+ return $@ if $@;
+ }
+
+ my @members = map $_->email_address,
+ $new->mailinglist->mailinglistmember;
+
+ #real-time here, presuming CGP does some dup detection
+ eval { $self->communigate_pro_runcommand(
+ 'SetGroup', $newGroupName,
+ { 'RealName' => $new->listname,
+ 'SetReplyTo' => ( $new->reply_to ? 'YES' : 'NO' ),
+ 'RemoveAuthor' => ( $new->remove_from ? 'YES' : 'NO' ),
+ 'RejectAuto' => ( $new->reject_auto ? 'YES' : 'NO' ),
+ 'RemoveToAndCc' => ( $new->remove_to_and_cc ? 'YES' : 'NO' ),
+ 'Members' => \@members,
+ }
+ );
+ };
+ return $@ if $@;
+
+ '';
+
+}
+
+sub _export_delete {
+ my( $self, $svc_x ) = (shift, shift);
+
+ my $table = $svc_x->table;
+ my $method = "_export_delete_$table";
+ $self->$method($svc_x, @_);
+}
+
+sub _export_delete_svc_acct {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount',
+ $self->export_username($svc_acct),
+ );
+}
+
+sub _export_delete_svc_domain {
+ my( $self, $svc_domain ) = (shift, shift);
+
+ $self->communigate_pro_queue( $svc_domain->svcnum, 'DeleteDomain',
+ $svc_domain->domain,
+ #XXX turn on force option for domain deletion?
+ );
+}
+
+sub _export_delete_svc_forward {
+ my( $self, $svc_forward ) = (shift, shift);
+
+ $self->communigate_pro_queue( $svc_forward->svcnum, 'DeleteForwarder',
+ ($svc_forward->src || $svc_forward->srcsvc_acct->email),
+ );
+}
+
+sub _export_delete_svc_mailinglist {
+ my( $self, $svc_mailinglist ) = (shift, shift);
+
+ #real-time here, presuming CGP does some dup detection
+ eval { $self->communigate_pro_runcommand(
+ 'DeleteGroup',
+ $svc_mailinglist->username.'@'.$svc_mailinglist->domain,
+ );
+ };
+ return $@ if $@;
+
+ '';
+
+}
+
+sub _export_suspend {
+ my( $self, $svc_x ) = (shift, shift);
+
+ my $table = $svc_x->table;
+ my $method = "_export_suspend_$table";
+ $self->$method($svc_x, @_);
+
+}
+
+sub _export_suspend_svc_acct {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ #XXX is this the desired suspnsion action?
+
+ $self->communigate_pro_queue(
+ $svc_acct->svcnum,
+ 'UpdateAccountSettings',
+ $self->export_username($svc_acct),
+ 'AccessModes' => 'Mail',
+ );
+
+}
+
+sub _export_suspend_svc_domain {
+ my( $self, $svc_domain) = (shift, shift);
+
+ #XXX domain operations
+ '';
+
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_x ) = (shift, shift);
+
+ my $table = $svc_x->table;
+ my $method = "_export_unsuspend_$table";
+ $self->$method($svc_x, @_);
+
+}
+
+sub _export_unsuspend_svc_acct {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->communigate_pro_queue(
+ $svc_acct->svcnum,
+ 'UpdateAccountSettings',
+ $self->export_username($svc_acct),
+ 'AccessModes' => ( $svc_acct->cgp_accessmodes
+ || $self->option('AccessModes') ),
+ );
+
+}
+
+sub _export_unsuspend_svc_domain {
+ my( $self, $svc_domain) = (shift, shift);
+
+ #XXX domain operations
+ '';
+
+}
+
+sub export_mailinglistmember_insert {
+ my( $self, $svc_mailinglist, $mailinglistmember ) = (shift, shift, shift);
+ $svc_mailinglist->replace();
+}
+
+sub export_mailinglistmember_replace {
+ my( $self, $svc_mailinglist, $new, $old ) = (shift, shift, shift, shift);
+ die "no way to do this from the UI right now";
+}
+
+sub export_mailinglistmember_delete {
+ my( $self, $svc_mailinglist, $mailinglistmember ) = (shift, shift, shift);
+ $svc_mailinglist->replace();
+}
+
+sub export_getsettings {
+ my($self, $svc_x) = (shift, shift);
+
+ my $table = $svc_x->table;
+ my $method = "export_getsettings_$table";
+
+ $self->can($method) ? $self->$method($svc_x, @_) : '';
+
+}
+
+sub export_getsettings_svc_domain {
+ my($self, $svc_domain, $settingsref, $defaultref ) = @_;
+
+ my $settings = eval { $self->communigate_pro_runcommand(
+ 'GetDomainSettings',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ my $effective_settings = eval { $self->communigate_pro_runcommand(
+ 'GetDomainEffectiveSettings',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ my $acct_defaults = eval { $self->communigate_pro_runcommand(
+ 'GetAccountDefaults',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ my $acct_defaultprefs = eval { $self->communigate_pro_runcommand(
+ 'GetAccountDefaultPrefs',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ my $rules = eval { $self->communigate_pro_runcommand(
+ 'GetDomainMailRules',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ #aliases too
+ my $aliases = eval { $self->communigate_pro_runcommand(
+ 'GetDomainAliases',
+ $svc_domain->domain
+ ) };
+ return $@ if $@;
+
+ my %more = (
+ ( map { ("Acct. Default $_" => $acct_defaults->{$_}); }
+ keys(%$acct_defaults)
+ ),
+ ( map { ("Acct. Default $_" => $acct_defaultprefs->{$_}); } #diff label??
+ keys(%$acct_defaultprefs)
+ ),
+ ( map _rule2string($_), @$rules ),
+ 'Aliases' => join(', ', @$aliases),
+ );
+
+ %$effective_settings = ( %$effective_settings, %more );
+ %$settings = ( %$settings, %more );
+
+ #false laziness w/below
+
+ my %defaults = map { $_ => 1 }
+ grep !exists(${$settings}{$_}), keys %$effective_settings;
+
+ foreach my $key ( grep ref($effective_settings->{$_}),
+ keys %$effective_settings )
+ {
+ my $value = $effective_settings->{$key};
+ if ( ref($value) eq 'ARRAY' ) {
+ $effective_settings->{$key} = join(' ', @$value);
+ } else {
+ #XXX
+ warn "serializing ". ref($value). " for table display not yet handled";
+ }
+ }
+
+ %{$settingsref} = %$effective_settings;
+ %{$defaultref} = %defaults;
+
+ '';
+}
+
+sub export_getsettings_svc_acct {
+ my($self, $svc_acct, $settingsref, $defaultref ) = @_;
+
+ my $settings = eval { $self->communigate_pro_runcommand(
+ 'GetAccountSettings',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ delete($settings->{'Password'});
+
+ my $effective_settings = eval { $self->communigate_pro_runcommand(
+ 'GetAccountEffectiveSettings',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ delete($effective_settings->{'Password'});
+
+ #prefs/effectiveprefs too
+
+ my $prefs = eval { $self->communigate_pro_runcommand(
+ 'GetAccountPrefs',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ my $effective_prefs = eval { $self->communigate_pro_runcommand(
+ 'GetAccountEffectivePrefs',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ %$effective_settings = ( %$effective_settings,
+ map { ("Pref $_" => $effective_prefs->{$_}); }
+ keys(%$effective_prefs)
+ );
+ %$settings = ( %$settings,
+ map { ("Pref $_" => $prefs->{$_}); }
+ keys(%$prefs)
+ );
+
+ #mail rules
+ my $rules = eval { $self->communigate_pro_runcommand(
+ 'GetAccountMailRules',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ %$effective_settings = ( %$effective_settings,
+ map _rule2string($_), @$rules
+ );
+ %$settings = ( %$settings,
+ map _rule2string($_), @$rules
+ );
+
+ #aliases too
+ my $aliases = eval { $self->communigate_pro_runcommand(
+ 'GetAccountAliases',
+ $svc_acct->email
+ ) };
+ return $@ if $@;
+
+ $effective_settings->{'Aliases'} = join(', ', @$aliases);
+ $settings->{'Aliases'} = join(', ', @$aliases);
+
+ #false laziness w/above
+
+ my %defaults = map { $_ => 1 }
+ grep !exists(${$settings}{$_}), keys %$effective_settings;
+
+ foreach my $key ( grep ref($effective_settings->{$_}),
+ keys %$effective_settings )
+ {
+ my $value = $effective_settings->{$key};
+ if ( ref($value) eq 'ARRAY' ) {
+ $effective_settings->{$key} = join(' ', @$value);
+ } else {
+ #XXX
+ warn "serializing ". ref($value). " for table display not yet handled";
+ }
+ }
+
+ %{$settingsref} = %$effective_settings;
+ %{$defaultref} = %defaults;
+
+ '';
+
+}
+
+sub _rule2string {
+ my $rule = shift;
+ my($priority, $name, $conditions, $actions, $comment) = @$rule;
+ $conditions = join(', ', map { my $a = $_; join(' ', @$a); } @$conditions);
+ $actions = join(', ', map { my $a = $_; join(' ', @$a); } @$actions);
+ ("Mail rule $name" => "$priority IF $conditions THEN $actions ($comment)");
+}
+
+sub export_getsettings_svc_mailinglist {
+ my($self, $svc_mailinglist, $settingsref, $defaultref ) = @_;
+
+ my $settings = eval { $self->communigate_pro_runcommand(
+ 'GetGroup',
+ $svc_mailinglist->username.'@'.$svc_mailinglist->domain,
+ ) };
+ return $@ if $@;
+
+ $settings->{'Members'} = join(', ', @{ $settings->{'Members'} } );
+
+ %{$settingsref} = %$settings;
+
+ '';
+}
+
+sub communigate_pro_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $jobnum = ''; #don't actually care
+ $self->communigate_pro_queue_dep( \$jobnum, $svcnum, $method, @_);
+}
+
+sub communigate_pro_queue_dep {
+ my( $self, $jobnumref, $svcnum, $method ) = splice(@_,0,4);
+
+ my %kludge_methods = (
+ #'CreateAccount' => 'CreateAccount',
+ 'UpdateAccountSettings' => 'UpdateAccountSettings',
+ 'UpdateAccountPrefs' => 'cp_Scalar_Hash',
+ #'CreateDomain' => 'cp_Scalar_Hash',
+ #'CreateSharedDomain' => 'cp_Scalar_Hash',
+ 'UpdateDomainSettings' => 'cp_Scalar_settingsHash',
+ 'SetDomainAliases' => 'cp_Scalar_Array',
+ 'SetAccountDefaults' => 'cp_Scalar_settingsHash',
+ 'UpdateAccountDefaults' => 'cp_Scalar_settingsHash',
+ 'SetAccountDefaultPrefs' => 'cp_Scalar_settingsHash',
+ 'UpdateAccountDefaultPrefs' => 'cp_Scalar_settingsHash',
+ );
+ my $sub = exists($kludge_methods{$method})
+ ? $kludge_methods{$method}
+ : 'communigate_pro_command';
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::communigate_pro::$sub",
+ };
+ my $error = $queue->insert(
+ $self->machine,
+ $self->option('port'),
+ $self->option('login'),
+ $self->option('password'),
+ $method,
+ @_,
+ );
+ $$jobnumref = $queue->jobnum unless $error;
+
+ return $error;
+}
+
+sub communigate_pro_runcommand {
+ my( $self, $method ) = (shift, shift);
+
+ communigate_pro_command(
+ $self->machine,
+ $self->option('port'),
+ $self->option('login'),
+ $self->option('password'),
+ $method,
+ @_,
+ );
+
+}
+
+#XXX one sub per arg prototype is lame. more magic? i suppose queue needs
+# to store data strctures properly instead of just an arg list. right.
+
+sub cp_Scalar_Hash {
+ my( $machine, $port, $login, $password, $method, $scalar, %hash ) = @_;
+ my @args = ( $scalar, \%hash );
+ communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+}
+
+sub cp_Scalar_Array {
+ my( $machine, $port, $login, $password, $method, $scalar, @array ) = @_;
+ my @args = ( $scalar, \@array );
+ communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+}
+
+#sub cp_Hash {
+# my( $machine, $port, $login, $password, $method, %hash ) = @_;
+# my @args = ( \%hash );
+# communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+#}
+
+sub cp_Scalar_settingsHash {
+ my( $machine, $port, $login, $password, $method, $domain, %settings ) = @_;
+ for (qw( AccessModes DomainAccessModes )) {
+ $settings{$_} = [split(' ',$settings{$_})] if $settings{$_};
+ }
+ my @args = ( 'domain' => $domain, 'settings' => \%settings );
+ communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+}
+
+#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, $accountName, %args ) = @_;
+ $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
+ my @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";
+ die $@ if $@;
+
+ my $cli = new CGP::CLI( {
+ 'PeerAddr' => $machine,
+ 'PeerPort' => $port,
+ 'login' => $login,
+ 'password' => $password,
+ } ) or die "Can't login to CGPro: $CGP::ERR_STRING\n";
+
+ #warn "$method ". Dumper(@args) if $DEBUG;
+
+ my $return = $cli->$method(@args)
+ or die "Communigate Pro error: ". $cli->getErrMessage. "\n";
+
+ $cli->Logout; # or die "Can't logout of CGPro: $CGP::ERR_STRING\n";
+
+ $return;
+
+}
+
+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..e25043f
--- /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 multiple domains,
+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..96fa437
--- /dev/null
+++ b/FS/FS/part_export/cp.pm
@@ -0,0 +1,161 @@
+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 "can't change username with Critical Path" #CP no longer supports this
+ if $old->username ne $new->username;
+ return '' unless $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/cpanel.pm b/FS/FS/part_export/cpanel.pm
new file mode 100644
index 0000000..0ad00df
--- /dev/null
+++ b/FS/FS/part_export/cpanel.pm
@@ -0,0 +1,192 @@
+package FS::part_export::cpanel;
+
+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 access username' },
+ 'accesshash' => { label=>'Remote access key', type=>'textarea' },
+ 'debug' => { label=>'Enable debugging', type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to Cpanel control panel.',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => 'Real time export to a the <a href="http://www.cpanel.net/">Cpanel</a> control panel software. Service definition names are exported as Cpanel packages. Requires installation of the Cpanel::Accounting perl module distributed with Cpanel.',
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+ $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum, 'insert',
+ $svc_acct->domain,
+ $svc_acct->username,
+ $svc_acct->_password,
+ $svc_acct->cust_svc->part_svc->svc,
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change username with cpanel"
+ if $old->username ne $new->username;
+ return "can't change password with cpanel"
+ if $old->_passsword ne $new->_password;
+ return "can't change domain with cpanel"
+ if $old->domain ne $new->domain;
+
+ '';
+
+ ##return '' unless $old->_password ne $new->_password;
+ #$err_or_queue = $self->cpanel_queue( $new->svcnum,
+ # 'replace', $new->username, $new->_password );
+ #ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
+ 'delete', $svc_acct->username
+ );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
+ 'suspend', $svc_acct->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $err_or_queue = $self->cpanel_queue( $svc_acct->svcnum,
+ 'unsuspend', $svc_acct->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+
+sub cpanel_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::cpanel::cpanel_$method",
+ };
+ $queue->insert(
+ $self->machine,
+ $self->option('user'),
+ $self->option('accesshash'),
+ $self->option('debug'),
+ @_
+ ) or $queue;
+}
+
+
+sub cpanel_insert { #subroutine, not method
+ my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
+
+# my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
+# warn " cpanel->createacct ". join(', ', @_). "\n"
+# if $debug;
+# my $response = $whm->createacct(@_);
+# die $whm->{'error'} if $whm->{'error'};
+# warn " cpanel response: $response\n"
+# if $debug;
+
+ warn "cpanel_insert: attempting web interface to add POP"
+ if $debug;
+
+ my($domain, $username, $password, $svc) = @_;
+
+ use LWP::UserAgent;
+ use HTTP::Request::Common qw(POST);
+
+ my $url =
+ "http://$user:$accesshash\@$domain:2082/frontend/x/mail/addpop2.html";
+
+ my $ua = LWP::UserAgent->new();
+
+ #$req->authorization_basic($user, $accesshash);
+
+ my $res = $ua->request(
+ POST( $url,
+ [
+ 'email' => $username,
+ 'domain' => $domain,
+ 'password' => $password,
+ 'quota' => 10, #?
+ ]
+ )
+ );
+
+ die "Error submitting data to $url: ". $res->status_line
+ unless $res->is_success;
+
+ die "Username in use"
+ if $res->content =~ /exists/;
+
+ die "Account not created: ". $res->content
+ if $res->content =~ /failure/;
+
+}
+
+#sub cpanel_replace { #subroutine, not method
+#}
+
+sub cpanel_delete { #subroutine, not method
+ my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
+ my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
+ warn " cpanel->killacct ". join(', ', @_). "\n"
+ if $debug;
+ my $response = $whm->killacct(shift);
+ die $whm->{'error'} if $whm->{'error'};
+ warn " cpanel response: $response\n"
+ if $debug;
+}
+
+sub cpanel_suspend { #subroutine, not method
+ my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
+ my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
+ warn " cpanel->suspend ". join(', ', @_). "\n"
+ if $debug;
+ my $response = $whm->suspend(shift);
+ die $whm->{'error'} if $whm->{'error'};
+ warn " cpanel response: $response\n"
+ if $debug;
+}
+
+sub cpanel_unsuspend { #subroutine, not method
+ my( $machine, $user, $accesshash, $debug ) = splice(@_,0,4);
+ my $whm = cpanel_connect($machine, $user, $accesshash, $debug);
+ warn " cpanel->unsuspend ". join(', ', @_). "\n"
+ if $debug;
+ my $response = $whm->unsuspend(shift);
+ die $whm->{'error'} if $whm->{'error'};
+ warn " cpanel response: $response\n"
+ if $debug;
+}
+
+sub cpanel_connect {
+ my( $host, $user, $accesshash, $debug ) = @_;
+
+ eval "use Cpanel::Accounting;";
+ die $@ if $@;
+
+ warn "creating new Cpanel::Accounting connection to $user@$host\n"
+ if $debug;
+
+ my $whm = new Cpanel::Accounting;
+ $whm->{'host'} = $host;
+ $whm->{'user'} = $user;
+ $whm->{'accesshash'} = $accesshash;
+ $whm->{'usessl'} = 1;
+
+ $whm;
+}
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/dashcs_e911.pm b/FS/FS/part_export/dashcs_e911.pm
new file mode 100644
index 0000000..320d0a6
--- /dev/null
+++ b/FS/FS/part_export/dashcs_e911.pm
@@ -0,0 +1,153 @@
+package FS::part_export::dashcs_e911;
+
+use strict;
+use vars qw(@ISA %info $me $DEBUG);
+use Tie::IxHash;
+use FS::part_export;
+
+$DEBUG = 0;
+$me = '['.__PACKAGE__.']';
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'username' => { label=>'Dash username', },
+ '_password' => { label=>'Dash password', },
+ 'staging' => { label=>'Staging (test mode)', type=>'checkbox', },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Provision e911 services via Dash Carrier Services',
+ 'notes' => 'Provision e911 services via Dash Carrier Services',
+ 'options' => \%options,
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_phone) = (shift, shift);
+ return 'invalid phonenum' unless $svc_phone->phonenum;
+
+ my $opts = { map{ $_ => $self->option($_) } keys %options };
+ $opts->{wantreturn} = 1;
+
+ my %location_hash = $svc_phone->location_hash;
+ my $location = {
+ 'address1' => $location_hash{address1},
+ 'address2' => $location_hash{address2},
+ 'community' => $location_hash{city},
+ 'state' => $location_hash{state},
+ 'postalcode' => $location_hash{zip},
+ };
+
+ my $error_or_ref =
+ dash_command($opts, 'validateLocation', { 'location' => $location } );
+ return $error_or_ref unless ref($error_or_ref);
+
+ my $status = $error_or_ref->get_Location->get_status; # hate
+ return $status->get_description unless $status->get_code eq 'GEOCODED';
+
+ my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
+ my $cust_main = $cust_pkg->cust_main if $cust_pkg;
+ my $caller_name = $cust_main ? $cust_main->name_short : 'unknown';
+
+ my $arg = {
+ 'uri' => {
+ 'uri' => 'tel:'. $svc_phone->countrycode. $svc_phone->phonenum,
+ 'callername' => $caller_name,
+ },
+ 'location' => $location,
+ };
+
+ $error_or_ref = dash_command($opts, 'addLocation', $arg );
+ return $error_or_ref unless ref($error_or_ref);
+
+ my $id = $error_or_ref->get_Location->get_locationid;
+ $self->_export_command('provisionLocation', { 'locationid' => $id });
+}
+
+sub _export_delete {
+ my($self, $svc_phone) = (shift, shift);
+ return '' unless $svc_phone->phonenum;
+
+ my $arg = { 'uri' => 'tel:'. $svc_phone->countrycode. $svc_phone->phonenum };
+ $self->_export_queue('removeURI', $arg);
+}
+
+sub _export_suspend {
+ my($self) = shift;
+ '';
+}
+
+sub _export_unsuspend {
+ my($self) = shift;
+ '';
+}
+
+sub _export_command {
+ my $self = shift;
+
+ my $opts = { map{ $_ => $self->option($_) } keys %options };
+
+ dash_command($opts, @_);
+
+}
+
+sub _export_replace {
+ my($self, $new, $old ) = (shift, shift, shift);
+
+ # this could succeed in unprovision but fail to provision
+ my $arg = { 'uri' => 'tel:'. $old->countrycode. $old->phonenum };
+ $self->_export_command('removeURI', $arg) || $self->_export_insert($new);
+}
+
+#a good idea to queue anything that could fail or take any time
+sub _export_queue {
+ my $self = shift;
+
+ my $opts = { map{ $_ => $self->option($_) } keys %options };
+
+ my $queue = new FS::queue {
+ 'job' => "FS::part_export::dashcs_e911::dash_command",
+ };
+ $queue->insert( $opts, @_ );
+}
+
+sub dash_command {
+ my ( $opt, $method, $arg ) = (shift, shift, shift);
+
+ warn "$me: dash_command called with method $method\n" if $DEBUG;
+
+ my @module = qw(
+ Net::DashCS::Interfaces::EmergencyProvisioningService::EmergencyProvisioningPort
+ SOAP::Lite
+ );
+
+ foreach my $module ( @module ) {
+ eval "use $module;";
+ die $@ if $@;
+ }
+
+ local *SOAP::Transport::HTTP::Client::get_basic_credentials = sub {
+ return ($opt->{'username'}, $opt->{'_password'});
+ };
+
+ my $service = new Net::DashCS::Interfaces::EmergencyProvisioningService::EmergencyProvisioningPort(
+ { deserializer_args => { strict => 0 } }
+ );
+
+ $service->set_proxy('https://staging-service.dashcs.com/dash-api/soap/emergencyprovisioning/v1')
+ if $opt->{'staging'};
+
+ my $result = $service->$method($arg);
+
+ if (not $result) {
+ warn "returning fault: ". $result->get_faultstring if $DEBUG;
+ return ''.$result->get_faultstring;
+ }
+
+ warn "returning ok: $result\n" if $DEBUG;
+ return $result if $opt->{wantreturn};
+ '';
+}
diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm
new file mode 100644
index 0000000..582e292
--- /dev/null
+++ b/FS/FS/part_export/domain_shellcommands.pm
@@ -0,0 +1,165 @@
+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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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);
+ return '' if $command =~ /^\s*$/;
+
+ #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
+
+ {
+ no strict 'refs';
+
+ if ( $old->catchall ) {
+ 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 ) {
+ 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/domain_sql.pm b/FS/FS/part_export/domain_sql.pm
new file mode 100644
index 0000000..0ce1b16
--- /dev/null
+++ b/FS/FS/part_export/domain_sql.pm
@@ -0,0 +1,238 @@
+package FS::part_export::domain_sql;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+#quite a bit of false laziness w/acct_sql - some stuff should be generalized
+#out to a "dababase base class"
+
+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',
+ },
+ 'static' => { label =>
+ 'Database schema mapping to static values.',
+ type => 'textarea',
+ },
+ 'primary_key' => { label => 'Database primary key' },
+;
+
+tie my %postfix_transport_map, 'Tie::IxHash',
+ 'domain' => 'domain'
+;
+my $postfix_transport_map =
+ join('\n', map "$_ $postfix_transport_map{$_}",
+ keys %postfix_transport_map );
+tie my %postfix_transport_static, 'Tie::IxHash',
+ 'transport' => 'virtual:',
+;
+my $postfix_transport_static =
+ join('\n', map "$_ $postfix_transport_static{$_}",
+ keys %postfix_transport_static );
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Real time export of domains to SQL databases '.
+ '(postfix, others?)',
+ 'options' => \%options,
+ 'notes' => <<END
+Export domains (svc_domain records) to SQL databases. Currently this is a
+simple export with a default for Postfix, but it can be extended for other
+uses.
+
+<BR><BR>Use these buttons for useful presets:
+<UL>
+ <LI><INPUT TYPE="button" VALUE="postfix_transport" onClick='
+ this.form.table.value = "transport";
+ this.form.schema.value = "$postfix_transport_map";
+ this.form.static.value = "$postfix_transport_static";
+ this.form.primary_key.value = "domain";
+ '>
+</UL>
+END
+);
+
+sub _schema_map { shift->_map('schema'); }
+sub _static_map { shift->_map('static'); }
+
+sub _map {
+ my $self = shift;
+ map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option(shift) );
+}
+
+sub _export_insert {
+ my($self, $svc_domain) = (shift, shift);
+
+ my %schema = $self->_schema_map;
+ my %static = $self->_static_map;
+
+ my %record = ( ( map { $_ => $static{$_} } keys %static ),
+ ( map { my $method = $schema{$_};
+ $_ => $svc_domain->$method();
+ }
+ keys %schema
+ )
+ );
+
+ my $err_or_queue =
+ $self->domain_sql_queue(
+ $svc_domain->svcnum,
+ 'insert',
+ $self->option('table'),
+ %record
+ );
+ return $err_or_queue unless ref($err_or_queue);
+
+ '';
+}
+
+sub _export_replace {
+ my($self, $new, $old) = (shift, shift, shift);
+
+ my %schema = $self->_schema_map;
+ my %static = $self->_static_map;
+
+ my @primary_key = ();
+ if ( $self->option('primary_key') =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
+ my $keymap = $schema{$key};
+ push @primary_key, $old->$keymap();
+ }
+ } else {
+ my $keymap = $map{$self->option('primary_key')};
+ push @primary_key, $old->$keymap();
+ }
+
+ my %record = ( ( map { $_ => $static{$_} } keys %static ),
+ ( map { my $method = $schema{$_};
+ $_ => $new->$method();
+ }
+ keys %schema
+ )
+ );
+
+ my $err_or_queue = $self->domain_sql_queue(
+ $new->svcnum,
+ 'replace',
+ $self->option('table'),
+ $self->option('primary_key'), @primary_key,
+ %record,
+ );
+ return $err_or_queue unless ref($err_or_queue);
+ '';
+}
+
+sub _export_delete {
+ my ( $self, $svc_domain ) = (shift, shift);
+
+ my %schema = $self->_schema_map;
+ my %static = $self->_static_map;
+
+ my %primary_key = ();
+ if ( $self->option('primary_key') =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $self->option('primary_key') ) ) {
+ my $keymap = $map{$key};
+ $primary_key{ $key } = $svc_domain->$keymap();
+ }
+ } else {
+ my $keymap = $map{$self->option('primary_key')};
+ $primary_key{ $self->option('primary_key') } = $svc_domain->$keymap(),
+ }
+
+ my $err_or_queue = $self->domain_sql_queue(
+ $svc_domain->svcnum,
+ 'delete',
+ $self->option('table'),
+ %primary_key,
+ #$self->option('primary_key') => $svc_domain->$keymap(),
+ );
+ return $err_or_queue unless ref($err_or_queue);
+ '';
+}
+
+sub domain_sql_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::domain_sql::domain_sql_$method",
+ };
+ $queue->insert(
+ $self->option('datasrc'),
+ $self->option('username'),
+ $self->option('password'),
+ @_,
+ ) or $queue;
+}
+
+sub domain_sql_insert { #subroutine, not method
+ my $dbh = domain_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( values(%record) )
+ or die "can't insert into $table table: ". $sth->errstr;
+
+ $dbh->disconnect;
+}
+
+sub domain_sql_delete { #subroutine, not method
+ my $dbh = domain_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 domain_sql_replace { #subroutine, not method
+ my $dbh = domain_sql_connect(shift, shift, shift);
+
+ my( $table, $pkey ) = ( shift, shift );
+
+ my %primary_key = ();
+ if ( $pkey =~ /,/ ) {
+ foreach my $key ( split(/\s*,\s*/, $pkey ) ) {
+ $primary_key{$key} = shift;
+ }
+ } else {
+ $primary_key{$pkey} = shift;
+ }
+
+ my %record = @_;
+
+ my $sth = $dbh->prepare(
+ "UPDATE $table".
+ ' SET '. join(', ', map "$_ = ?", keys %record ).
+ ' WHERE '. join(' AND ', map "$_ = ?", keys %primary_key )
+ ) or die $dbh->errstr;
+
+ $sth->execute( values(%record), values(%primary_key) );
+
+ $dbh->disconnect;
+}
+
+sub domain_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/domreg_net_dri.pm b/FS/FS/part_export/domreg_net_dri.pm
new file mode 100644
index 0000000..bf01602
--- /dev/null
+++ b/FS/FS/part_export/domreg_net_dri.pm
@@ -0,0 +1,614 @@
+package FS::part_export::domreg_net_dri;
+
+use vars qw(@ISA %info %options $conf);
+use Tie::IxHash;
+use FS::part_export::null;
+
+=head1 NAME
+
+FS::part_export::domreg_net_dri - Register or transfer domains with Net::DRI
+
+=head1 DESCRIPTION
+
+This module handles registering and transferring domains with select registrars or registries supported
+by L<Net::DRI>.
+
+As a part_export, this module can be designated for use with svc_domain services. When the svc_domain object
+is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
+on the setting of the svc_domain's action field. Further operations can be performed from the View Domain screen.
+
+Logging information is written to the Freeside log folder.
+
+For correct operation you must add name/value pairs to the protcol and transport options fields. The setttings
+depend on the domain registry driver (DRD) selected.
+
+=over 4
+
+=item N - Register the domain
+
+=item M - Transfer the domain
+
+=item I - Ignore the domain for registration purposes
+
+=back
+
+=cut
+
+@ISA = qw(FS::part_export::null);
+
+my @tldlist = qw/com net org biz info name mobi at be ca cc ch cn de dk es eu fr it mx nl tv uk us/;
+
+my $opensrs_protocol_opts=<<'END';
+username=
+password=
+auto_renew=0
+affiliate_id=
+reseller_id=
+END
+
+my $opensrs_transport_opts=<<'END';
+client_login=
+client_password=
+END
+
+tie %options, 'Tie::IxHash',
+ 'drd' => { label => 'Domain Registry Driver (DRD)',
+ type => 'select',
+ options => [ qw/BookMyName CentralNic Gandi OpenSRS OVH VNDS/ ],
+ default => 'OpenSRS' },
+ 'log_level' => { label => 'Logging',
+ type => 'select',
+ options => [ qw/debug info notice warning error critical alert emergency/ ],
+ default => 'warning' },
+ 'protocol_opts' => {
+ label => 'Protocol Options',
+ type => 'textarea',
+ default => $opensrs_protocol_opts,
+ },
+ 'transport_opts' => {
+ label => 'Transport Options',
+ type => 'textarea',
+ default => $opensrs_transport_opts,
+ },
+# 'register' => { label => 'Use for registration',
+# type => 'checkbox',
+# default => '1' },
+# 'transfer' => { label => 'Use for transfer',
+# type => 'checkbox',
+# default => '1' },
+# 'delete' => { label => 'Use for deletion',
+# type => 'checkbox',
+# default => '1' },
+# 'renew' => { label => 'Use for renewals',
+# type => 'checkbox',
+# default => '1' },
+ 'tlds' => { label => 'Use this export for these top-level domains (TLDs)',
+ type => 'select',
+ multi => 1,
+ size => scalar(@tldlist),
+ options => [ @tldlist ],
+ default => 'com net org' },
+;
+
+my $opensrs_protocol_defaults = $opensrs_protocol_opts;
+$opensrs_protocol_defaults =~ s|\n|\\n|g;
+
+my $opensrs_transport_defaults = $opensrs_transport_opts;
+$opensrs_transport_defaults =~ s|\n|\\n|g;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Domain registration via Net::DRI',
+ 'options' => \%options,
+ 'notes' => <<"END"
+Registers and transfers domains via a Net::DRI registrar or registry.
+<a href="http://search.cpan.org/search?dist=Net-DRI">Net::DRI</a>
+must be installed. You must have an account at the selected registrar/registry.
+<BR />
+Some top-level domains have additional business rules not supported by this export. These TLDs cannot be registered or transfered with this export.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
+ document.dummy.machine.value = "rr-n1-tor.opensrs.net";
+ this.form.machine.value = "rr-n1-tor.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
+ document.dummy.machine.value = "horizon.opensrs.net";
+ this.form.machine.value = "horizon.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS protocol/transport options" onClick='
+ this.form.protocol_opts.value = "$opensrs_protocol_defaults";
+ this.form.transport_opts.value = "$opensrs_transport_defaults";
+ '>
+</UL>
+END
+);
+
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+};
+
+#sub rebless { shift; }
+
+# experiment: want the status of these right away, so no queueing
+
+sub _export_insert {
+ my( $self, $svc_domain ) = ( shift, shift );
+
+ return if $svc_domain->action eq 'I'; # Ignoring registration, just doing DNS
+
+ if ($svc_domain->action eq 'N') {
+ return $self->register( $svc_domain );
+ } elsif ($svc_domain->action eq 'M') {
+ return $self->transfer( $svc_domain );
+ }
+ return "Unknown domain action " . $svc_domain->action;
+}
+
+=item get_portfolio_credentials
+
+Returns, in list context, the user name and password for the domain portfolio.
+
+This is currently specified via the username and password keys in the protocol options.
+
+=cut
+
+sub get_portfolio_credentials {
+ my $self = shift;
+
+ my %opts = $self->get_protocol_options();
+ return ($opts{username}, $opts{password});
+}
+
+=item format_tel
+
+Reformats a phone number according to registry rules. Currently Freeside stores phone numbers
+in NANPA format and most registries prefer "+CCC.NPANPXNNNN"
+
+=cut
+
+sub format_tel {
+ my $tel = shift;
+
+ #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})\s*(x\s*(\d+))?$/) {
+ if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
+ $tel = "+1.$1$2$3"; # TBD: other country codes
+# if $tel .= "$4" if $4;
+ }
+ return $tel;
+}
+
+sub gen_contact_set {
+ my ($self, $dri, $cust_main) = @_;
+
+ my @invoicing_list = $cust_main->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $cust_main->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $cs=$dri->local_object('contactset');
+ my $co=$dri->local_object('contact');
+
+ my ($user, $pass) = $self->get_portfolio_credentials();
+
+ $co->srid($user); # Portfolio user name for OpenSRS?
+ $co->auth($pass); # Portfolio password for OpenSRS?
+
+ $co->firstname($cust_main->first);
+ $co->name($cust_main->last);
+ $co->org($cust_main->company || '-');
+ $co->street([$cust_main->address1, $cust_main->address2]);
+ $co->city($cust_main->city);
+ $co->sp($cust_main->state);
+ $co->pc($cust_main->zip);
+ $co->cc($cust_main->country);
+ $co->voice(format_tel($cust_main->daytime()));
+ $co->email($email);
+
+ $cs->set($co, 'registrant');
+ $cs->set($co, 'admin');
+ $cs->set($co, 'billing');
+
+ return $cs;
+}
+
+=item validate_contact_set
+
+Attempts to validate contact data for the domain based on OpenSRS rules.
+
+Returns undef if the contact data is acceptable, an error message if the contact
+data lacks one or more required fields.
+
+=cut
+
+sub validate_contact_set {
+ my $c = shift;
+
+ my %fields = (
+ firstname => "first name",
+ name => "last name",
+ street => "street address",
+ city => "city",
+ sp => "state",
+ pc => "ZIP/postal code",
+ cc => "country",
+ email => "email address",
+ voice => "phone number",
+ );
+ my @err = ();
+ foreach my $which (qw/registrant admin billing/) {
+ my $co = $c->get($which);
+ foreach (keys %fields) {
+ if (!$co->$_()) {
+ push @err, $fields{$_};
+ }
+ }
+ }
+ if (scalar(@err) > 0) {
+ return "Contact information needs: " . join(', ', @err);
+ }
+ undef;
+}
+
+#sub _export_replace {
+# my( $self, $new, $old ) = (shift, shift, shift);
+#
+# return '';
+#
+#}
+
+## Domain registration exports do nothing on delete. You're just removing the domain from Freeside, not the registry
+#sub _export_delete {
+# my( $self, $www ) = ( shift, shift );
+#
+# return '';
+#}
+
+=item split_textarea_options
+
+Split textarea contents into lines, split lines on =, and then trim the results;
+
+=cut
+
+sub split_textarea_options {
+ my ($self, $optname) = @_;
+ my %opts = map {
+ my ($key, $value) = split /=/, $_;
+ $key =~ s/^\s*//;
+ $key =~ s/\s*$//;
+ $value =~ s/^\s*//;
+ $value =~ s/\s*$//;
+ $key => $value } split /\n/, $self->option($optname);
+ %opts;
+}
+
+=item get_protocol_options
+
+Return a hash of protocol options
+
+=cut
+
+sub get_protocol_options {
+ my $self = shift;
+ my %opts = $self->split_textarea_options('protocol_opts');
+ if ($self->machine =~ /opensrs\.net/) {
+ my %topts = $self->get_transport_options;
+ $opts{reseller_id} = $topts{client_login};
+ }
+ %opts;
+}
+
+=item get_transport_options
+
+Return a hash of transport options
+
+=cut
+
+sub get_transport_options {
+ my $self = shift;
+ my %opts = $self->split_textarea_options('transport_opts');
+ $opts{remote_url} = "https://" . $self->machine . ":55443/resellers" if $self->machine =~ /opensrs\.net/;
+ %opts;
+}
+
+=item is_supported_domain
+
+Return undef if the domain name uses a TLD or SLD that is supported by this registrar.
+Otherwise return an error message explaining what's wrong.
+
+=cut
+
+sub is_supported_domain {
+ my $self = shift;
+ my $svc_domain = shift;
+
+ # Get the TLD of the new domain
+ my @bits = split /\./, $svc_domain->domain;
+
+ return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
+
+ my $tld = pop @bits;
+
+ # See if it's one this export supports
+ my @tlds = split /\s+/, $self->option('tlds');
+ @tlds = map { s/\.//; $_ } @tlds;
+ return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
+ return undef;
+}
+
+=item get_dri
+
+=cut
+
+sub get_dri {
+ my $self = shift;
+ my $dri;
+
+# return $self->{dri} if $self->{dri}; #!!!TBD!!! connection caching.
+
+ eval "use Net::DRI 0.95;";
+ return $@ if $@;
+
+# $dri=Net::DRI->new(...) to create the global object. Save the result,
+
+ eval {
+ #$dri = Net::DRI::TrapExceptions->new(10);
+ $dri = Net::DRI->new({logging => [ 'files', { output_directory => '%%%FREESIDE_LOG%%%' } ]}); #!!!TBD!!!
+ $dri->logging->level( $self->option('log_level') );
+ $dri->add_registry( $self->option('drd') );
+ my $protocol;
+ $protocol = 'xcp' if $self->option('drd') eq 'OpenSRS';
+
+ $dri->target( $self->option('drd') )->add_current_profile($self->option('drd') . '1',
+# 'Net::DRI::Protocol::' . $self->option('protocol_type'),
+# $self->option('protocol_type'),
+# 'xcp', #TBD!!!!
+ $protocol, # Implies transport
+# 'Net::DRI::Transport::' . $self->option('transport_type'),
+ { $self->get_transport_options() },
+# [ $self->get_protocol_options() ]
+ );
+ };
+ return $@ if $@;
+
+ $self->{dri} = $dri;
+ return $dri;
+}
+
+=item get_status
+
+Returns a reference to a hashref containing information on the domain's status. The keys
+defined depend on the status.
+
+'unregistered' means the domain is not registered.
+
+Otherwise, if the domain is in an asynchronous operation such as a transfer, returns the state
+of that operation.
+
+Otherwise returns a value indicating if the domain can be managed through our reseller account.
+
+=cut
+
+sub get_status {
+ my ( $self, $svc_domain ) = @_;
+ my $rc;
+ my $rslt = {};
+
+ my $dri = $self->get_dri;
+
+ if (UNIVERSAL::isa($dri, 'Net::DRI::Exception')) {
+ $rslt->{'message'} = $dri->as_string;
+ return $rslt;
+ }
+ eval {
+ $rc = $dri->domain_check( $svc_domain->domain );
+ if (!$rc->is_success()) {
+ # Problem accessing the registry/registrar
+ $rslt->{'message'} = $rc->message;
+ } elsif (!$dri->get_info('exist')) {
+ # Domain is not registered
+ $rslt->{'unregistered'} = 1;
+ } else {
+ $rc = $dri->domain_transfer_query( $svc_domain->domain );
+ if ($rc->is_success() && $dri->get_info('status')) {
+ # Transfer in progress
+ $rslt->{status} = $dri->get_info('status');
+ $rslt->{contact_email} = $dri->get_info('request_address');
+ $rslt->{last_update_time} = $dri->get_info('unixtime');
+ } elsif ($dri->get_info('reason')) {
+ $rslt->{'reason'} = $dri->get_info('reason');
+ # Domain is not being transferred...
+ $rc = $dri->domain_info( $svc_domain->domain, { $self->get_protocol_options() } );
+ if ($rc->is_success() && $dri->get_info('exDate')) {
+ $rslt->{'expdate'} = $dri->get_info('exDate');
+ }
+ } else {
+ $rslt->{status} = 'Unknown';
+ }
+ }
+ };
+# rslt->{'message'} = $@->as_string if $@;
+ if ($@) {
+ $rslt->{'message'} = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->as_string : $@->message;
+ }
+
+ return $rslt; # Success
+}
+
+=item register
+
+Attempts to register the domain through the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub register {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception.
+
+# Call methods
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $cs = $self->gen_contact_set($dri, $cust_main);
+
+ $err = validate_contact_set($cs);
+ return $err if $err;
+
+# !!!TBD!!! add custom name servers when supported; add ns => $ns to hash passed to domain_create
+
+ $res = $dri->domain_create($svc_domain->domain, { $self->get_protocol_options(), pure_create => 1, contact => $cs, duration => DateTime::Duration->new(years => $years) });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item transfer
+
+Attempts to transfer the domain into the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub transfer {
+ my ( $self, $svc_domain ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+# $dri=Net::DRI->new(...) to create the global object. Save the result,
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+
+# Call methods
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $cs = $self->gen_contact_set($dri, $cust_main);
+
+ $err = validate_contact_set($cs);
+ return $err if $err;
+
+# !!!TBD!!! add custom name servers when supported; add ns => $ns to hash passed to domain_transfer_start
+
+ $res = $dri->domain_transfer_start($svc_domain->domain, { $self->get_protocol_options(), contact => $cs });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item renew
+
+Attempts to renew the domain for the specified number of years.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub renew {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+ my $expdate;
+ my $res = $dri->domain_info( $svc_domain->domain, { $self->get_protocol_options() } );
+ if ($res->is_success() && $dri->get_info('exDate')) {
+ $expdate = $dri->get_info('exDate');
+
+# return "Domain renewal not enabled" if !$self->option('renew');
+ $res = $dri->domain_renew( $svc_domain->domain, { $self->get_protocol_options(), duration => DateTime::Duration->new(years => $years), current_expiration => $expdate });
+ }
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item revoke
+
+Attempts to revoke the domain registration. Only succeeds if invoked during the DRI
+grace period immediately after registration.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub revoke {
+ my ( $self, $svc_domain ) = @_;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $dri = $self->get_dri;
+ return $dri->as_string if (UNIVERSAL::isa($dri, 'Net::DRI::Exception'));
+
+ eval { # All $dri methods can throw an exception
+
+# return "Domain registration revocation not enabled" if !$self->option('revoke');
+ my $res = $dri->domain_delete( $svc_domain->domain, { $self->get_protocol_options(), domain => $svc_domain->domain, pure_delete => 1 });
+ $err = $res->is_success ? '' : $res->message;
+ };
+ if ($@) {
+ $err = (UNIVERSAL::isa($@, 'Net::DRI::Exception')) ? $@->msg : $@->message;
+ }
+
+ return $err;
+}
+
+=item registrar
+
+Should return a full-blown object representing the Net::DRI DRD, but current just returns a hashref
+containing the registrar name.
+
+=cut
+
+sub registrar {
+ my $self = shift;
+ return {
+ name => $self->option('drd'),
+ };
+}
+
+=head1 SEE ALSO
+
+L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/domreg_opensrs.pm b/FS/FS/part_export/domreg_opensrs.pm
new file mode 100644
index 0000000..4d6ea8f
--- /dev/null
+++ b/FS/FS/part_export/domreg_opensrs.pm
@@ -0,0 +1,613 @@
+package FS::part_export::domreg_opensrs;
+
+use vars qw(@ISA %info %options $conf $me $DEBUG);
+use Tie::IxHash;
+use DateTime;
+use FS::Record qw(qsearchs qsearch);
+use FS::Conf;
+use FS::part_export::null;
+use FS::svc_domain;
+use FS::part_pkg;
+
+=head1 NAME
+
+FS::part_export::domreg_opensrs - Register or transfer domains with Tucows OpenSRS
+
+=head1 DESCRIPTION
+
+This module handles registering and transferring domains using a registration service provider (RSP) account
+at Tucows OpenSRS, an ICANN-approved domain registrar.
+
+As a part_export, this module can be designated for use with svc_domain services. When the svc_domain object
+is inserted into the Freeside database, registration or transferring of the domain may be initiated, depending
+on the setting of the svc_domain's action field.
+
+=over 4
+
+=item N - Register the domain
+
+=item M - Transfer the domain
+
+=item I - Ignore the domain for registration purposes
+
+=back
+
+This export uses Net::OpenSRS. Registration and transfer attempts will fail unless Net::OpenSRS is installed
+and LWP::UserAgent is able to make HTTPS posts. You can turn on debugging messages and use the OpenSRS test
+gateway when setting up this export.
+
+=cut
+
+@ISA = qw(FS::part_export::null);
+$me = '[' . __PACKAGE__ . ']';
+$DEBUG = 0;
+
+my @tldlist = qw/com net org biz info name mobi at be ca cc ch cn de dk es eu fr it mx nl tv uk us/;
+
+tie %options, 'Tie::IxHash',
+ 'username' => { label => 'Reseller user name at OpenSRS',
+ },
+ 'privatekey' => { label => 'Private key',
+ },
+ 'password' => { label => 'Password for management account',
+ },
+ 'masterdomain' => { label => 'Master domain at OpenSRS',
+ },
+ 'wait_for_pay' => { label => 'Do not provision until payment is received',
+ type => 'checkbox',
+ default => '0',
+ },
+ 'debug_level' => { label => 'Net::OpenSRS debug level',
+ type => 'select',
+ options => [ 0, 1, 2, 3 ],
+ default => 0 },
+# 'register' => { label => 'Use for registration',
+# type => 'checkbox',
+# default => '1' },
+# 'transfer' => { label => 'Use for transfer',
+# type => 'checkbox',
+# default => '1' },
+ 'tlds' => { label => 'Use this export for these top-level domains (TLDs)',
+ type => 'select',
+ multi => 1,
+ size => scalar(@tldlist),
+ options => [ @tldlist ],
+ default => 'com net org' },
+;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Domain registration via Tucows OpenSRS',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Registers and transfers domains via the <a href="http://opensrs.com/">Tucows OpenSRS</a> registrar (using <a href="http://search.cpan.org/dist/Net-OpenSRS">Net::OpenSRS</a>).
+All of the Net::OpenSRS restrictions apply:
+<UL>
+ <LI>You must have a reseller account with Tucows.
+ <LI>You must add the public IP address of the Freeside server to the 'Script API allow' list in the OpenSRS web interface.
+ <LI>You must generate an API access key in the OpenSRS web interface and enter it below.
+ <LI>All domains are managed using the same user name and password, but you can create sub-accounts for clients.
+ <LI>The user name must be the same as your OpenSRS reseller ID.
+ <LI>You must enter a master domain that all other domains are associated with. That domain must be registered through your OpenSRS account.
+</UL>
+Some top-level domains offered by OpenSRS have additional business rules not supported by this export. These TLDs cannot be registered or transfered with this export.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Live System (rr-n1-tor.opensrs.net)" onClick='
+ document.dummy.machine.value = "rr-n1-tor.opensrs.net";
+ this.form.machine.value = "rr-n1-tor.opensrs.net";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="OpenSRS Test System (horizon.opensrs.net)" onClick='
+ document.dummy.machine.value = "horizon.opensrs.net";
+ this.form.machine.value = "horizon.opensrs.net";
+ '>
+</UL>
+END
+);
+
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+};
+
+=head1 METHODS
+
+=over 4
+
+=item format_tel
+
+Reformats a phone number according to registry rules. Currently Freeside stores phone numbers
+in NANPA format and the registry prefers "+CCC.NPANPXNNNN"
+
+=cut
+
+sub format_tel {
+ my $tel = shift;
+
+ #if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})( x(\d+))?$/) {
+ if ($tel =~ /^(\d{3})-(\d{3})-(\d{4})$/) {
+ $tel = "+1.$1$2$3";
+# if $tel .= "$4" if $4;
+ }
+ return $tel;
+}
+
+=item gen_contact_info
+
+Generates contact data for the domain based on the customer data.
+
+Currently relies on Net::OpenSRS to format the telephone number for OpenSRS.
+
+=cut
+
+sub gen_contact_info
+{
+ my ($co)=@_;
+
+ my @invoicing_list = $co->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $co->all_emails;
+ }
+
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my $c = {
+ firstname => $co->first,
+ lastname => $co->last,
+ company => $co->company,
+ address => $co->address1,
+ city => $co->city(),
+ state => $co->state(),
+ zip => $co->zip(),
+ country => uc($co->country()),
+ email => $email,
+ #phone => format_tel($co->daytime()),
+ phone => $co->daytime() || $co->night,
+ };
+ return $c;
+}
+
+=item validate_contact_info
+
+Attempts to validate contact data for the domain based on OpenSRS rules.
+
+Returns undef if the contact data is acceptable, an error message if the contact
+data lacks one or more required fields.
+
+=cut
+
+sub validate_contact_info {
+ my $c = shift;
+
+ my %fields = (
+ firstname => "first name",
+ lastname => "last name",
+ address => "street address",
+ city => "city",
+ state => "state",
+ zip => "ZIP/postal code",
+ country => "country",
+ email => "email address",
+ phone => "phone number",
+ );
+ my @err = ();
+ foreach (keys %fields) {
+ if (!defined($c->{$_}) || !$c->{$_}) {
+ push @err, $fields{$_};
+ }
+ }
+ if (scalar(@err) > 0) {
+ return "Contact information needs: " . join(', ', @err);
+ }
+ undef;
+}
+
+=item testmode
+
+Returns the Net::OpenSRS-required test mode string based on whether the export
+is configured to use the live or the test gateway.
+
+=cut
+
+sub testmode {
+ my $self = shift;
+
+ return 'live' if $self->machine eq "rr-n1-tor.opensrs.net";
+ return 'test' if $self->machine eq "horizon.opensrs.net";
+ undef;
+
+}
+
+=item _export_insert
+
+Attempts to "export" the domain, i.e. register or transfer it if the user selected
+that option when editing the domain.
+
+Returns an error message on failure or undef on success.
+
+May also return an error message if it cannot load the required Perl module Net::OpenSRS,
+or if the domain is not registerable, or if insufficient data is provided in the customer
+record to generate the required contact information to register or transfer the domain.
+
+=cut
+
+sub _export_insert {
+ my( $self, $svc_domain ) = ( shift, shift );
+
+ return if $svc_domain->action eq 'I'; # Ignoring registration, just doing DNS
+
+ if ($svc_domain->action eq 'N') {
+ return $self->register( $svc_domain );
+ } elsif ($svc_domain->action eq 'M') {
+ return $self->transfer( $svc_domain );
+ }
+ return "Unknown domain action " . $svc_domain->action;
+}
+
+sub _export_insert_on_payment {
+ my( $self, $svc_domain ) = ( shift, shift );
+ warn "$me:_export_insert_on_payment called\n" if $DEBUG;
+ return '' unless $self->option('wait_for_pay');
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svc_domain->svcnum,
+ 'job' => 'FS::part_export::domreg_opensrs::renew_through',
+ };
+ $queue->insert( $self, $svc_domain ); #_export_insert with 'R' action?
+
+ return '';
+}
+
+## Domain registration exports do nothing on replace. Mainly because we haven't decided what they should do.
+#sub _export_replace {
+# my( $self, $new, $old ) = (shift, shift, shift);
+#
+# return '';
+#
+#}
+
+## Domain registration exports do nothing on delete. You're just removing the domain from Freeside, not the registry
+#sub _export_delete {
+# my( $self, $svc_domain ) = ( shift, shift );
+#
+# return '';
+#}
+
+=item is_supported_domain
+
+Return undef if the domain name uses a TLD or SLD that is supported by this registrar.
+Otherwise return an error message explaining what's wrong.
+
+=cut
+
+sub is_supported_domain {
+ my $self = shift;
+ my $svc_domain = shift;
+
+ # Get the TLD of the new domain
+ my @bits = split /\./, $svc_domain->domain;
+
+ return "Can't register subdomains: " . $svc_domain->domain if scalar(@bits) != 2;
+
+ my $tld = pop @bits;
+
+ # See if it's one this export supports
+ my @tlds = split /\s+/, $self->option('tlds');
+ @tlds = map { s/\.//; $_ } @tlds;
+ return "Can't register top-level domain $tld, restricted to: " . $self->option('tlds') if ! grep { $_ eq $tld } @tlds;
+ return undef;
+}
+
+=item get_srs
+
+=cut
+
+sub get_srs {
+ my $self = shift;
+
+ my $srs = Net::OpenSRS->new();
+
+ $srs->debug_level( $self->option('debug_level') ); # Output should be in the Apache error log
+
+ $srs->environment( $self->testmode() );
+ $srs->set_key( $self->option('privatekey') );
+
+ $srs->set_manage_auth( $self->option('username'), $self->option('password') );
+ return $srs;
+}
+
+=item get_status
+
+Returns a reference to a hashref containing information on the domain's status. The keys
+defined depend on the status.
+
+'unregistered' means the domain is not registered.
+
+Otherwise, if the domain is in an asynchronous operation such as a transfer, returns the state
+of that operation.
+
+Otherwise returns a value indicating if the domain can be managed through our reseller account.
+
+=cut
+
+sub get_status {
+ my ( $self, $svc_domain ) = @_;
+ my $rslt = {};
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $srs = $self->get_srs;
+
+ if ($srs->is_available( $svc_domain->domain )) {
+ $rslt->{'unregistered'} = 1;
+ } else {
+ $rslt = $srs->check_transfer( $svc_domain->domain );
+ if (defined($rslt->{'reason'})) {
+ my $rv = $srs->make_request(
+ {
+ action => 'belongs_to_rsp',
+ object => 'domain',
+ attributes => {
+ domain => $svc_domain->domain
+ }
+ }
+ );
+ if ($rv) {
+ $self->_set_response;
+ if ( $rv->{attributes}->{'domain_expdate'} ) {
+ $rslt->{'expdate'} = $rv->{attributes}->{'domain_expdate'};
+ }
+ }
+ }
+ }
+
+ return $rslt; # Success
+}
+
+=item register
+
+Attempts to register the domain through the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub register {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ return "Net::OpenSRS does not support period other than 1 year" if $years != 1;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $c = gen_contact_info($cust_main);
+
+ $err = validate_contact_info($c);
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+# cookie not required for registration
+# my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+# if (!$cookie) {
+# return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+# }
+
+# return "Domain registration not enabled" if !$self->option('register');
+ return $srs->last_response() if !$srs->register_domain( $svc_domain->domain, $c);
+
+ return ''; # Should only get here if register succeeded
+}
+
+=item transfer
+
+Attempts to transfer the domain into the reseller account associated with this export.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub transfer {
+ my ( $self, $svc_domain ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $cust_main = $svc_domain->cust_svc->cust_pkg->cust_main;
+
+ my $c = gen_contact_info($cust_main);
+
+ $err = validate_contact_info($c);
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain transfer not enabled" if !$self->option('transfer');
+ return $srs->last_response() if !$srs->transfer_domain( $svc_domain->domain, $c);
+
+ return ''; # Should only get here if transfer succeeded
+}
+
+=item renew
+
+Attempts to renew the domain for the specified number of years.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub renew {
+ my ( $self, $svc_domain, $years ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain renewal not enabled" if !$self->option('renew');
+ return $srs->last_response() if !$srs->renew_domain( $svc_domain->domain, $years );
+
+ return ''; # Should only get here if renewal succeeded
+}
+
+=item renew_through [ EPOCH_DATE ]
+
+Attempts to renew the domain through the specified date. If no date is
+provided it is gleaned from the associated cust_pkg bill date
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub renew_through {
+ my ( $self, $svc_domain, $date ) = @_;
+
+ warn "$me: renew_through called\n" if $DEBUG;
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ unless ( $date ) {
+ my $cust_pkg = $svc_domain->cust_svc->cust_pkg;
+ return "Can't renew: no date specified and domain is not in a package."
+ unless $cust_pkg;
+ $date = $cust_pkg->bill;
+ }
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ warn "$me: checking status\n" if $DEBUG;
+ my $rv = $self->get_status($svc_domain);
+ return "Domain ". $svc_domain->domain. " is not renewable"
+ unless $rv->{expdate};
+
+ return "Can't parse expiration date for ". $svc_domain->domain
+ unless $rv->{expdate} =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
+
+ my ($year,$month,$day,$hour,$minute,$second) = ($1,$2,$3,$4,$5,$6);
+ my $exp = DateTime->new( year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $minute,
+ second => $second,
+ time_zone => 'America/New_York',#timezone of opensrs
+ );
+
+ my $bill = DateTime->
+ from_epoch( 'epoch' => $date,
+ 'time_zone' => DateTime::TimeZone->new( name => 'local' ),
+ );
+
+ my $years = 0;
+ while ( DateTime->compare( $bill, $exp ) > 0 ) {
+ $years++;
+ $exp->add( 'years' => 1 );
+
+ return "Can't renew ". $svc_domain->domain. " for more than 10 years."
+ if $years > 10; #no infinite loop
+ }
+
+ warn "$me: renewing ". $svc_domain->domain. "for $years years\n" if $DEBUG;
+ my $srs = $self->get_srs;
+ $rv = $srs->make_request(
+ {
+ action => 'renew',
+ object => 'domain',
+ attributes => {
+ domain => $svc_domain->domain,
+ auto_renew => 0,
+ handle => 'process',
+ period => $years,
+ currentexpirationyear => $year,
+ }
+ }
+ );
+ return $rv->{response_text} unless $rv->{is_success};
+
+ return ''; # Should only get here if renewal succeeded
+}
+
+=item revoke
+
+Attempts to revoke the domain registration. Only succeeds if invoked during the OpenSRS
+grace period immediately after registration.
+
+Like most export functions, returns an error message on failure or undef on success.
+
+=cut
+
+sub revoke {
+ my ( $self, $svc_domain ) = @_;
+
+ eval "use Net::OpenSRS;";
+ return $@ if $@;
+
+ my $err = $self->is_supported_domain( $svc_domain );
+ return $err if $err;
+
+ my $srs = $self->get_srs;
+
+ my $cookie = $srs->get_cookie( $self->option('masterdomain') );
+ if (!$cookie) {
+ return "Unable to get cookie at OpenSRS: " . $srs->last_response();
+ }
+
+# return "Domain registration revocation not enabled" if !$self->option('revoke');
+ return $srs->last_response() if !$srs->revoke_domain( $svc_domain->domain);
+
+ return ''; # Should only get here if transfer succeeded
+}
+
+=item registrar
+
+Should return a full-blown object representing OpenSRS, but current just returns a hashref
+containing the registrar name.
+
+=cut
+
+sub registrar {
+ return {
+ name => 'OpenSRS',
+ };
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<Net::OpenSRS>, L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_domain>,
+L<FS::Record>, schema.html from the base documentation.
+
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/everyone_net.pm b/FS/FS/part_export/everyone_net.pm
new file mode 100644
index 0000000..e04318e
--- /dev/null
+++ b/FS/FS/part_export/everyone_net.pm
@@ -0,0 +1,132 @@
+package FS::part_export::everyone_net;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'clientID' => { label=>'clientID' },
+ 'password' => { label=>'Password' },
+ #'workgroup' => { label=>'Default Workgroup' },
+ 'debug' => { label=>'Enable debugging',
+ type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to Everyone.net outsourced mail service',
+ 'options'=> \%options,
+ 'notes' => <<'END'
+Real-time export to
+<a href="http://www.cp.net/">Everyone.net</a> via the XRC Remote API.
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-XRC">Net::XRC</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+# experiement: want the status of these right away (don't want account to
+# create or whatever and then get error in the queue from dup username or
+# something), so no queueing
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ $self->_xrc_command( 'createUser',
+ $svc_acct->domain,
+ [],
+ string($svc_acct->username),
+ string($svc_acct->_password),
+ );
+}
+
+sub _xrc_command {
+ my( $self, $method, $domain, @args ) = @_;
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ local($Net::XRC::DEBUG) = 1
+ if $self->option('debug');
+
+ my $xrc = new Net::XRC (
+ 'clientID' => $self->option('clientID'),
+ 'password' => $self->option('password'),
+ );
+
+ my $dresponse = $xrc->lookupMXReadyClientIDByEmailDomain( string($domain) );
+ return $dresponse->error unless $dresponse->is_success;
+ my $clientID = $dresponse->content;
+ return "clientID for domain $domain not found"
+ if $clientID == -1;
+
+ my $response = $xrc->$method($clientID, @args);
+ return $response->error unless $response->is_success;
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ return "can't change domain with Everyone.net"
+ if $old->domain ne $new->domain;
+ return "can't change username with Everyone.net"
+ if $old->username ne $new->username;
+ return '' unless $old->_password ne $new->_password;
+
+ $self->_xrc_command( 'setUserPassword',
+ $new->domain,
+ string($new->username),
+ string($new->_password),
+ );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ $self->_xrc_command( 'deleteUser',
+ $svc_acct->domain,
+ string($svc_acct->username),
+ );
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ $self->_xrc_command( 'suspendUser',
+ $svc_acct->domain,
+ string($svc_acct->username),
+ );
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ eval "use Net::XRC qw(:types);";
+ return $@ if $@;
+
+ $self->_xrc_command( 'unsuspendUser',
+ $svc_acct->domain,
+ string($svc_acct->username),
+ );
+}
+
+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..0f79ede
--- /dev/null
+++ b/FS/FS/part_export/forward_shellcommands.pm
@@ -0,0 +1,182 @@
+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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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> - username of forward source
+ <LI><code>$domain</code> - domain of forward source
+ <LI><code>$source</code> - forward source ($username@$domain)
+ <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);
+ return '' if $command =~ /^\s*$/;
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_forward->getfield($_) foreach $svc_forward->fields;
+ }
+
+ if ( $svc_forward->srcsvc ) {
+ my $srcsvc_acct = $svc_forward->srcsvc_acct;
+ $username = $srcsvc_acct->username;
+ $domain = $srcsvc_acct->domain;
+ $source = $srcsvc_acct->email;
+ } else {
+ $source = $svc_forward->src;
+ ( $username, $domain ) = split(/\@/, $source);
+ }
+
+ if ($svc_forward->dstsvc) {
+ $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;
+ }
+
+ if ( $old->srcsvc ) {
+ my $srcsvc_acct = $old->srcsvc_acct;
+ $old_username = $srcsvc_acct->username;
+ $old_domain = $srcsvc_acct->domain;
+ $old_source = $srcsvc_acct->email;
+ } else {
+ $old_source = $old->src;
+ ( $old_username, $old_domain ) = split(/\@/, $old_source);
+ }
+
+ if ( $old->dstsvc ) {
+ $old_destination = $old->dstsvc_acct->email;
+ } else {
+ $old_destination = $old->dst;
+ }
+
+ if ( $new->srcsvc ) {
+ my $srcsvc_acct = $new->srcsvc_acct;
+ $new_username = $srcsvc_acct->username;
+ $new_domain = $srcsvc_acct->domain;
+ $new_source = $srcsvc_acct->email;
+ } else {
+ $new_source = $new->src;
+ ( $new_username, $new_domain ) = split(/\@/, $new_source);
+ }
+
+ 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/globalpops_voip.pm b/FS/FS/part_export/globalpops_voip.pm
new file mode 100644
index 0000000..67b48bb
--- /dev/null
+++ b/FS/FS/part_export/globalpops_voip.pm
@@ -0,0 +1,370 @@
+package FS::part_export::globalpops_voip;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::Record qw(qsearch dbh);
+use FS::part_export;
+use FS::phone_avail;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'login' => { label=>'GlobalPOPs Media Services API login' },
+ 'password' => { label=>'GlobalPOPs Media Services API password' },
+ 'endpointgroup' => { label=>'GlobalPOPs endpoint group number' },
+ 'dry_run' => { label=>"Test mode - don't actually provision" },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Provision phone numbers to GlobalPOPs VoIP',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-GlobalPOPs-MediaServicesAPI">Net::GlobalPOPs::MediaServicesAPI</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+sub get_dids {
+ my $self = shift;
+ my %opt = ref($_[0]) ? %{$_[0]} : @_;
+
+ my %getdids = ();
+ # 'orderby' => 'npa', #but it doesn't seem to work :/
+
+ if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
+ %getdids = ( 'npa' => $opt{'areacode'},
+ 'nxx' => $opt{'exchange'},
+ );
+ } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
+ %getdids = ( 'npa' => $opt{'areacode'} );
+ } elsif ( $opt{'state'} ) {
+
+ my @avail = qsearch({
+ 'table' => 'phone_avail',
+ 'hashref' => { 'exportnum' => $self->exportnum,
+ 'countrycode' => '1', #don't hardcode me when gp goes int'l
+ 'state' => $opt{'state'},
+ },
+ 'order_by' => 'ORDER BY npa',
+ });
+
+ return [ map $_->npa, @avail ] if @avail; #return cached area codes instead
+
+ #otherwise, search for em
+ %getdids = ( 'state' => $opt{'state'} );
+
+ }
+
+ my $dids = $self->gp_command('getDIDs', %getdids);
+
+ #use Data::Dumper;
+ #warn Dumper($dids);
+
+ my $search = $dids->{'search'};
+
+ if ( $search->{'statuscode'} == 302200 ) {
+ return [];
+ } elsif ( $search->{'statuscode'} != 100 ) {
+ die "Error running globalpop getDIDs: ".
+ $search->{'statuscode'}. ': '. $search->{'status'}; #die??
+ }
+
+ my @return = ();
+
+ #my $latas = $search->{state}{lata};
+ my %latas;
+ if ( grep $search->{state}{lata}{$_}, qw(name rate_center) ) {
+ %latas = map $search->{state}{lata}{$_},
+ qw(name rate_center);
+ } else {
+ %latas = %{ $search->{state}{lata} };
+ }
+
+ foreach my $lata ( keys %latas ) {
+
+ #warn "LATA $lata";
+
+ #my $l = $latas{$lata};
+ #$l = $l->{rate_center} if exists $l->{rate_center};
+
+ my $lata_dids = $self->gp_command('getDIDs', %getdids, 'lata'=>$lata);
+ my $lata_search = $lata_dids->{'search'};
+ unless ( $lata_search->{'statuscode'} == 100 ) {
+ die "Error running globalpop getDIDs: ". $lata_search->{'status'}; #die??
+ }
+
+ my $l = $lata_search->{state}{lata}{'rate_center'};
+
+ #use Data::Dumper;
+ #warn Dumper($l);
+
+ my %rate_center;
+ if ( grep $l->{$_}, qw(name friendlyname) ) {
+ %rate_center = map $l->{$_},
+ qw(name friendlyname);
+ } else {
+ %rate_center = %$l;
+ }
+
+ foreach my $rate_center ( keys %rate_center ) {
+
+ #warn "rate center $rate_center";
+
+ my $rc = $rate_center{$rate_center};
+ $rc = $rc->{friendlyname} if exists $rc->{friendlyname};
+
+ my @r = ();
+ if ( exists($rc->{npa}) ) {
+ @r = ($rc);
+ } else {
+ @r = map { { 'name'=>$_, %{ $rc->{$_} } }; } keys %$rc
+ }
+
+ foreach my $r (@r) {
+
+ my @npa = ();
+ if ( exists($r->{npa}{name}) ) {
+ @npa = ($r->{npa})
+ } else {
+ @npa = map { { 'name'=>$_, %{ $r->{npa}{$_} } } } keys %{ $r->{npa} };
+ }
+
+ foreach my $npa (@npa) {
+
+ if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
+
+ #warn Dumper($npa);
+
+ my $tn = $npa->{nxx}{tn} || $npa->{nxx}{$opt{'exchange'}}{tn};
+
+ my @tn = ref($tn) ? @$tn : ($tn);
+ #push @return, @tn;
+ push @return, map {
+ if ( /^\s*(\d{3})(\d{3})(\d{4})\s*$/ ) {
+ "$1-$2-$3";
+ } else {
+ $_;
+ }
+ }
+ @tn;
+
+ } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
+
+ if ( $npa->{nxx}{name} ) {
+ @nxx = ( $npa->{nxx}{name} );
+ } else {
+ @nxx = keys %{ $npa->{nxx} };
+ }
+
+ push @return, map { $r->{name}. ' ('. $npa->{name}. "-$_-XXXX)"; }
+ @nxx;
+
+ } elsif ( $opt{'state'} ) { #and not other things, then return areacode
+ #my $ac = $npa->{name};
+ #use Data::Dumper;
+ #warn Dumper($r) unless length($ac) == 3;
+
+ push @return, $npa->{name}
+ unless grep { $_ eq $npa->{name} } @return;
+
+ } else {
+ warn "WARNING: returning nothing for get_dids without known options"; #?
+ }
+
+ } #foreach my $npa
+
+ } #foreach my $r
+
+ } #foreach my $rate_center
+
+ } #foreach my $lata
+
+ if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
+ @return = sort { $a cmp $b } @return; #string comparison actually dwiw
+ } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
+ @return = sort { lc($a) cmp lc($b) } @return;
+ } elsif ( $opt{'state'} ) { #and not other things, then return areacode
+
+ #populate cache
+
+ 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 $errmsg = 'WARNING: error populating phone availability cache: ';
+ my $error = '';
+ foreach my $return (@return) {
+ my $phone_avail = new FS::phone_avail {
+ 'exportnum' => $self->exportnum,
+ 'countrycode' => '1', #don't hardcode me when gp goes int'l
+ 'state' => $opt{'state'},
+ 'npa' => $return,
+ };
+ $error = $phone_avail->insert();
+ if ( $error ) {
+ warn $errmsg.$error;
+ last;
+ }
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ } else {
+ $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
+ }
+
+ #end populate cache
+
+ #@return = sort { (split(' ', $a))[0] <=> (split(' ', $b))[0] } @return;
+ @return = sort { $a <=> $b } @return;
+ } else {
+ warn "WARNING: returning nothing for get_dids without known options"; #?
+ }
+
+ \@return;
+
+}
+
+sub gp_command {
+ my( $self, $command, @args ) = @_;
+
+ eval "use Net::GlobalPOPs::MediaServicesAPI;";
+ die $@ if $@;
+
+ my $gp = Net::GlobalPOPs::MediaServicesAPI->new(
+ 'login' => $self->option('login'),
+ 'password' => $self->option('password'),
+ #'debug' => $debug,
+ );
+
+ $gp->$command(@args);
+}
+
+
+sub _export_insert {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ return '' if $self->option('dry_run');
+
+ #we want to provision and catch errors now, not queue
+
+ my $r = $self->gp_command('reserveDID',
+ 'did' => $svc_phone->phonenum,
+ 'minutes' => 1,
+ 'endpointgroup' => $self->option('endpointgroup'),
+ );
+
+ my $rdid = $r->{did};
+
+ if ( $rdid->{'statuscode'} != 100 ) {
+ return "Error running globalpop reserveDID: ".
+ $rdid->{'statuscode'}. ': '. $rdid->{'status'};
+ }
+
+ my $a = $self->gp_command('assignDID',
+ 'did' => $svc_phone->phonenum,
+ 'endpointgroup' => $self->option('endpointgroup'),
+ #'rewrite'
+ #'cnam'
+ );
+
+ my $adid = $a->{did};
+
+ if ( $adid->{'statuscode'} != 100 ) {
+ return "Error running globalpop assignDID: ".
+ $adid->{'statuscode'}. ': '. $adid->{'status'};
+ }
+
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ #hmm, what's to change?
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ return '' if $self->option('dry_run');
+
+ #probably okay to queue the deletion...?
+ #but hell, let's do it inline anyway, who wants phone numbers hanging around
+
+ my $r = $self->gp_command('releaseDID',
+ 'did' => $svc_phone->phonenum,
+ );
+
+ my $rdid = $r->{did};
+
+ if ( $rdid->{'statuscode'} != 100 ) {
+ return "Error running globalpop releaseDID: ".
+ $rdid->{'statuscode'}. ': '. $rdid->{'status'};
+ }
+
+ '';
+}
+
+sub _export_suspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #nop for now
+ '';
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #nop for now
+ '';
+}
+
+#hmm, might forgo queueing entirely for most things, data is too much of a pita
+#sub globalpops_voip_queue {
+# my( $self, $svcnum, $method ) = (shift, shift, shift);
+# my $queue = new FS::queue {
+# 'svcnum' => $svcnum,
+# 'job' => 'FS::part_export::globalpops_voip::globalpops_voip_command',
+# };
+# $queue->insert(
+# $self->option('login'),
+# $self->option('password'),
+# $method,
+# @_,
+# );
+#}
+
+sub globalpops_voip_command {
+ my($login, $password, $method, @args) = @_;
+
+ eval "use Net::GlobalPOPs::MediaServicesAPI;";
+ die $@ if $@;
+
+ my $gp = new Net::GlobalPOPs
+ 'login' => $login,
+ 'password' => $password,
+ #'debug' => 1,
+ ;
+
+ my $return = $gp->$method( @args );
+
+ #$return->{'status'}
+ #$return->{'statuscode'}
+
+ die $return->{'status'} if $return->{'statuscode'};
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/grandstream.pm b/FS/FS/part_export/grandstream.pm
new file mode 100644
index 0000000..5c6f1ed
--- /dev/null
+++ b/FS/FS/part_export/grandstream.pm
@@ -0,0 +1,257 @@
+package FS::part_export::grandstream;
+
+use base 'FS::part_export';
+use vars qw($DEBUG $me %info $GAPSLITE_HOME $JAVA_HOME);
+use URI;
+use MIME::Base64;
+use Tie::IxHash;
+use IPC::Run qw(run);
+use FS::CGI qw(rooturl);
+
+$DEBUG = 0;
+
+$me = '[' . __PACKAGE__ . ']';
+$GAPSLITE_HOME = '/usr/local/src/GS_CFG_GEN/';
+
+my @java = qw( /usr/lib/jvm/default-java/ /usr/java/default/
+ /usr/lib/jvm/java-6-sun/
+ /usr/lib/jvm/java-1.4.2-gcj-4.1-1.4.2.0/
+ ); #add more common places distros and people put their JREs
+
+$JAVA_HOME = (grep { -e $_ } @java)[0];
+
+tie my %options, 'Tie::IxHash',
+ 'upload' => { label=>'Enable upload to TFTP server via SSH',
+ type=>'checkbox',
+ },
+ 'user' => { label=>'User name for SSH to TFTP server' },
+ 'tftproot' => { label=>'Directory in which to upload configuration' },
+ 'java_home' => { label=>'Path to java to be used',
+ default=>$JAVA_HOME,
+ },
+ 'gapslite_home' => { label=>'Path to grandstream configuration tool',
+ default=>$GAPSLITE_HOME,
+ },
+ 'template' => { label=>'Configuration template',
+ type=>'textarea',
+ notes=>'Type or paste the configuration template here',
+ },
+;
+
+%info = (
+ 'svc' => [ qw( part_device ) ], # svc_phone
+ 'desc' => 'Provision phone numbers to Grandstream Networks phones/ATAs',
+ 'options' => \%options,
+ 'notes' => 'Provision phone numbers to Grandstream Networks phones/ATAs. Requires a Java runtime environment and the Grandstream configuration tool to be installed.',
+);
+
+sub rebless { shift; }
+
+sub gs_create_config {
+ my($self, $mac, %opt) = (@_);
+
+ eval "use Net::SCP;";
+ die $@ if $@;
+
+ warn "gs_create_config called with mac of $mac\n" if $DEBUG;
+ $mac = sprintf('%012s', lc($mac));
+ my $dir = '%%%FREESIDE_CONF%%%/cache.'. $FS::UID::datasrc;
+
+ my $fh = new File::Temp(
+ TEMPLATE => "grandstream.$mac.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0,
+ );
+
+ my $filename = $fh->filename;
+
+ #my $template = new Text::Template (
+ # TYPE => 'ARRAY',
+ # SOURCE => $self->option('template'),
+ # DELIMITERS => $delimiters,
+ # OUTPUT => $fh,
+ #);
+
+ #$template->compile or die "Can't compile template: $Text::Template::ERROR\n";
+
+ #my $config = $template->fill_in( HASH => { mac_addr => $mac } );
+
+ print $fh $self->option('template') or die "print failed: $!";
+ close $fh;
+
+ #system( "export GAPSLITE_HOME=$GAPSLITE_HOME; export JAVA_HOME=$JAVA_HOME; ".
+ # "cd $dir; $GAPSLITE_HOME/bin/encode.sh $mac $filename $dir/cfg$mac"
+ # ) == 0
+ # or die "grandstream encode failed: $!";
+ my $out_and_err = '';
+ my @cmd = ( "$JAVA_HOME/bin/java",
+ '-classpath', "$GAPSLITE_HOME/lib/gapslite.jar:$GAPSLITE_HOME/lib/bcprov-jdk14-124.jar:$GAPSLITE_HOME/config",
+ 'com.grandstream.cmd.TextEncoder',
+ $mac, $filename, "$dir/cfg$mac",
+ );
+ run \@cmd, '>&', \$out_and_err
+ or die "grandstream encode failed: $out_and_err";
+
+ unlink $filename;
+
+ open my $encoded, "$dir/cfg$mac" or die "open cfg$mac failed: $!";
+
+ my $content;
+
+ if ($opt{upload}) {
+ if ($self->option('upload')) {
+ my $scp = new Net::SCP ( {
+ 'host' => $self->machine,
+ 'user' => $self->option('user'),
+ 'cwd' => $self->option('tftproot'),
+ } );
+
+ $scp->put( "$dir/cfg$mac" ) or die "upload failed: ". $scp->errstr;
+ }
+ } else {
+ local $/;
+ $content = <$encoded>;
+ }
+
+ close $encoded;
+ unlink "$dir/cfg$mac";
+
+ $content;
+}
+
+sub gs_create {
+ my($self, $mac) = (shift, shift);
+
+ return unless $mac; # be more alarmed? Or check upstream?
+
+ $self->gs_create_config($mac, 'upload' => 1);
+ '';
+}
+
+sub gs_delete {
+ my($self, $mac) = (shift, shift);
+
+ $mac = sprintf('%012s', lc($mac));
+
+ ssh_cmd( user => $self->option('user'),
+ host => $self->machine,
+ command => 'rm',
+ args => [ '-f', $self->option('tftproot'). "/cfg$mac" ],
+ );
+ '';
+
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+sub _export_insert {
+# my( $self, $svc_phone ) = (shift, shift);
+# $self->gs_create($svc_phone->mac_addr);
+ '';
+}
+
+sub _export_replace {
+# my( $self, $new_svc, $old_svc ) = (shift, shift, shift);
+# $self->gs_delete($old_svc->mac_addr);
+# $self->gs_create($new_svc->mac_addr);
+ '';
+}
+
+sub _export_delete {
+# my( $self, $svc_phone ) = (shift, shift);
+# $self->gs_delete($svc_phone->mac_addr);
+ '';
+}
+
+sub _export_suspend {
+ '';
+}
+
+sub _export_unsuspend {
+ '';
+}
+
+sub export_device_insert {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+ $self->gs_create($phone_device->mac_addr);
+ '';
+}
+
+sub export_device_delete {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+ $self->gs_delete($phone_device->mac_addr);
+ '';
+}
+
+sub export_device_config {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ my $mac;
+# if ($phone_device) {
+ $mac = $phone_device->mac_addr;
+# } else {
+# $mac = $svc_phone->mac_addr;
+# }
+
+ return '' unless $mac; # be more alarmed? Or check upstream?
+
+ $self->gs_create_config($mac);
+}
+
+
+sub export_device_replace {
+ my( $self, $svc_phone, $new_svc_or_device, $old_svc_or_device ) =
+ (shift, shift, shift, shift);
+
+ $self->gs_delete($old_svc_or_device->mac_addr);
+ $self->gs_create($new_svc_or_device->mac_addr);
+ '';
+}
+
+# bad overloading?
+sub export_links {
+ my($self, $svc_phone, $arrayref) = (shift, shift, shift);
+
+ return; # remove if we actually support being an export for svc_phone;
+
+ my @deviceparts = map { $_->devicepart } $self->export_device;
+ my @devices = grep { my $part = $_->devicepart;
+ scalar( grep { $_ == $part } @deviceparts );
+ } $svc_phone->phone_device;
+
+ my $export = $self->exportnum;
+ my $fsurl = rooturl();
+ if (@devices) {
+ foreach my $device ( @devices ) {
+ next unless $device->mac_addr;
+ my $num = $device->devicenum;
+ push @$arrayref,
+ qq!<A HREF="$fsurl/misc/phone_device_config.html?exportnum=$export;devicenum=$num">!.
+ qq! Phone config </A>!;
+ }
+ } elsif ($svc_phone->mac_addr) {
+ my $num = $svc_phone->svcnum;
+ push @$arrayref,
+ qq!<A HREF="$fsurl/misc/phone_device_config.html?exportnum=$export;svcnum=$num">!.
+ qq! Phone config </A>!;
+ } #else
+ '';
+}
+
+sub export_device_links {
+ my($self, $svc_phone, $device, $arrayref) = (shift, shift, shift, shift);
+ warn "export_device_links $self $svc_phone $device $arrayref\n" if $DEBUG;
+ return unless $device && $device->mac_addr;
+ my $export = $self->exportnum;
+ my $fsurl = rooturl();
+ my $num = $device->devicenum;
+ push @$arrayref,
+ qq!<A HREF="$fsurl/misc/phone_device_config.html?exportnum=$export;devicenum=$num">!.
+ qq! Phone config </A>!;
+ '';
+}
+
+1;
diff --git a/FS/FS/part_export/http.pm b/FS/FS/part_export/http.pm
new file mode 100644
index 0000000..55d8329
--- /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 { $_ !~ /^(POST|FAX)$/ } $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/indosoft.pm b/FS/FS/part_export/indosoft.pm
new file mode 100644
index 0000000..b573401
--- /dev/null
+++ b/FS/FS/part_export/indosoft.pm
@@ -0,0 +1,219 @@
+package FS::part_export::indosoft;
+
+use vars qw(@ISA %info $insert_hack);
+use Tie::IxHash;
+use Date::Format;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'url' => { label => 'Voicebridge API URL' },
+ 'account_id' => { label => 'Voicebridge Account ID' },
+;
+
+%info = (
+ 'svc' => 'svc_phone', #svc_bridge? svc_confbridge?
+ 'desc' =>
+ 'Export conferences to the Indosoft Conference Bridge',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Export conferences to the Indosoft conference bridge.
+Net::Indosoft::Voicebridge is required.
+END
+);
+
+$insert_hack = 0;
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_phone) = (shift, shift);
+
+ my $cust_main = $svc_phone->cust_svc->cust_pkg->cust_main;
+
+ my $address = $cust_main->address1;
+ $address .= ' '.$cust_main->address2 if $cust_main->address2;
+
+ my $phone = $cust_main->daytime || $cust_main->night;
+
+ my @email = $cust_main->invoicing_list_emailonly;
+
+ #svc_phone->location_hash stuff? well that was for e911.. this shouldn't
+ # even be svc_phone
+
+ #add client
+ my $client_return = eval {
+ indosoft_runcommand( 'addClient',
+ 'account_id' => $self->option('account_id'),
+
+ 'client_contact_name' => $cust_main->name, #or just first last?
+ 'client_contact_password' => $svc_phone->sip_password, # ?
+
+ 'client_contact_addr' => $address,
+ 'client_contact_city' => $cust_main->city,
+ 'client_contact_state' => $cust_main->state,
+ 'client_contact_country' => $cust_main->country,
+ 'client_contact_zip' => $cust_main->zip,
+
+ 'client_contact_phone' => $phone,
+ 'client_contact_fax' => $cust_main->fax,
+ 'client_contact_email' => $email[0],
+ );
+ };
+ return $@ if $@;
+
+ my $client_id = $client_return->{client_id};
+
+ #add conference
+ my $conf_return = eval {
+ indosoft_runcommand( 'addConference',
+ 'client_id' => $client_id,
+ 'conference_name' => $cust_main->name,
+ 'conference_desc' => $svc_phone->svcnum. ' for '. $cust_main->name,
+ 'start_time' => time2str('%Y-%d-$m %T', time), #now, right?? '2010-20-04 16:20:00',
+ #'moderated_flag' => 0,
+ #'entry_ann_flag' => 0
+ #'record_flag' => 0
+ #'moh_flag' => 0
+ #'talk_detect_flag' => 0
+ #'play_user_cnt_flag' => 0
+ #'wait_for_admin' => 0
+ #'stop_on_admin_exit' => 0
+ #'second_pin' => 0
+ #'secondary_pin' => 0,
+ #'allow_sub-conf' => 0,
+ #'duration' => 0,
+ #'conference_type' => 'reservation', #'reservationless',
+ );
+ };
+ return $@ if $@;
+
+ my $conference_id = $conf_return->{conference_id};
+
+ #put conference_id in svc_phone.phonenum (and client_id in... phone_name???)
+ local($insert_hack) = 1;
+ $svc_phone->phonenum($conference_id);
+ $svc_phone->phone_name($client_id);
+ #my $error = $svc_phone->replace;
+ #return $error if $error;
+ $svc_phone->replace;
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change phone number as conference_id with indosoft"
+ if $old->phonenum ne $new->phonenum && ! $insert_hack;
+ return '';
+
+ #change anything?
+}
+
+sub _export_delete {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ #delete conference
+ my $conf_return = eval {
+ indosoft_runcommand( 'deleteConference',
+ 'conference_id' => $svc_phone->phonenum,
+ );
+ };
+ return $@ if $@;
+
+ #delete client
+ my $client_return = eval {
+ indosoft_runcommand( 'deleteClient',
+ 'client_id' => $svc_phone->phone_name,
+ )
+ };
+ return $@ if $@;
+
+ '';
+
+}
+
+# #these three are optional
+# # fallback for svc_acct will change and restore password
+# sub _export_suspend {
+# my( $self, $svc_phone ) = (shift, shift);
+# $err_or_queue = $self->indosoft_queue( $svc_phone->svcnum,
+# 'suspend', $svc_phone->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+# }
+#
+# sub _export_unsuspend {
+# my( $self, $svc_phone ) = (shift, shift);
+# $err_or_queue = $self->indosoft_queue( $svc_phone->svcnum,
+# 'unsuspend', $svc_phone->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+# }
+#
+# sub export_links {
+# my($self, $svc_phone, $arrayref) = (shift, shift, shift);
+# #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_phone->username.
+# # qq!">!. $svc_phone->username. qq!</A>!;
+# '';
+# }
+
+###
+
+sub indosoft_runcommand {
+ my( $self, $method ) = (shift, shift);
+
+ indosoft_command(
+ $self->option('url'),
+ $method,
+ @_,
+ );
+
+}
+
+sub indosoft_command {
+ my( $url, $method, @args ) = @_;
+
+ eval 'use Net::Indosoft::Voicebridge;';
+ die $@ if $@;
+
+ my $vb = new Net::Indosoft::Voicebridge( 'url' => $url );
+
+ my $return = $vb->$method( @args );
+
+ die "Indosoft error: ". $return->{'error'} if $return->{'error'};
+
+ $return;
+
+}
+
+
+# #a good idea to queue anything that could fail or take any time
+# sub indosoft_queue {
+# my( $self, $svcnum, $method ) = (shift, shift, shift);
+# my $queue = new FS::queue {
+# 'svcnum' => $svcnum,
+# 'job' => "FS::part_export::indosoft::indosoft_$method",
+# };
+# $queue->insert( @_ ) or $queue;
+# }
+#
+# sub indosoft_insert { #subroutine, not method
+# my( $username, $password ) = @_;
+# #do things with $username and $password
+# }
+#
+# sub indosoft_replace { #subroutine, not method
+# }
+#
+# sub indosoft_delete { #subroutine, not method
+# my( $username ) = @_;
+# #do things with $username
+# }
+#
+# sub indosoft_suspend { #subroutine, not method
+# }
+#
+# sub indosoft_unsuspend { #subroutine, not method
+# }
+
+
+1;
diff --git a/FS/FS/part_export/infostreet.pm b/FS/FS/part_export/infostreet.pm
new file mode 100644
index 0000000..ef16c7c
--- /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 { $_ !~ /^(POST|FAX)$/ } $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/internal_diddb.pm b/FS/FS/part_export/internal_diddb.pm
new file mode 100644
index 0000000..a330cb0
--- /dev/null
+++ b/FS/FS/part_export/internal_diddb.pm
@@ -0,0 +1,134 @@
+package FS::part_export::internal_diddb;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::phone_avail;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'countrycode' => { label => 'Country code', 'default' => '1', },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Provision phone numbers from the internal DID database',
+ 'notes' => 'After adding the export, DIDs may be imported under Tools -> Importing -> Import phone numbers (DIDs)',
+ 'options' => \%options,
+);
+
+sub rebless { shift; }
+
+sub get_dids {
+ my $self = shift;
+ my %opt = ref($_[0]) ? %{$_[0]} : @_;
+
+ my %hash = ( 'countrycode' => ( $self->option('countrycode') || '1' ),
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => '',
+ );
+
+ if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
+
+ $hash{npa} = $opt{areacode};
+ $hash{nxx} = $opt{exchange};
+
+ return [ map { $_->npa. '-'. $_->nxx. '-'. $_->station }
+ qsearch({ 'table' => 'phone_avail',
+ 'hashref' => \%hash,
+ 'order_by' => 'ORDER BY station',
+ })
+ ];
+
+ } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
+
+ $hash{npa} = $opt{areacode};
+
+ return [ map { '('. $_->npa. '-'. $_->nxx. '-XXXX)' }
+ qsearch({ 'select' => 'DISTINCT npa, nxx',
+ 'table' => 'phone_avail',
+ 'hashref' => \%hash,
+ 'order_by' => 'ORDER BY nxx',
+ })
+ ];
+
+ } elsif ( $opt{'state'} ) { #return aracodes
+
+ $hash{state} = $opt{state};
+
+ return [ map { $_->npa }
+ qsearch({ 'select' => 'DISTINCT npa',
+ 'table' => 'phone_avail',
+ 'hashref' => \%hash,
+ 'order_by' => 'ORDER BY npa',
+ })
+ ];
+
+ } else {
+ die "FS::part_export::internal_diddb::get_dids called without options\n";
+ }
+
+}
+
+sub _export_insert { #link phone_avail to svcnum
+ my( $self, $svc_phone ) = (shift, shift);
+
+ $svc_phone->phonenum =~ /^(\d{3})(\d{3})(\d+)$/
+ or return "unparsable phone number: ". $svc_phone->phonenum;
+ my( $npa, $nxx, $station ) = ($1, $2, $3);
+
+ my $phone_avail = qsearchs('phone_avail', {
+ 'countrycode' => ( $self->option('countrycode') || '1' ),
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => '',
+ 'npa' => $npa,
+ 'nxx' => $nxx,
+ 'station' => $station,
+ });
+
+ return "number not available: ". $svc_phone->phonenum
+ unless $phone_avail;
+
+ $phone_avail->svcnum($svc_phone->svcnum);
+
+ $phone_avail->replace;
+
+}
+
+sub _export_delete { #unlink phone_avail from svcnum
+ my( $self, $svc_phone ) = (shift, shift);
+
+ $svc_phone->phonenum =~ /^(\d{3})(\d{3})(\d+)$/
+ or return "unparsable phone number: ". $svc_phone->phonenum;
+ my( $npa, $nxx, $station ) = ($1, $2, $3);
+
+ my $phone_avail = qsearchs('phone_avail', {
+ 'countrycode' => ( $self->option('countrycode') || '1'),
+ 'exportnum' => $self->exportnum,
+ 'svcnum' => $svc_phone->svcnum,
+ #these too?
+ 'npa' => $npa,
+ 'nxx' => $nxx,
+ 'station' => $station,
+ });
+
+ unless ( $phone_avail ) {
+ warn "WARNING: can't find number to return to availability: ".
+ $svc_phone->phonenum;
+ return;
+ }
+
+ $phone_avail->svcnum('');
+
+ $phone_avail->replace;
+
+}
+
+sub _export_replace { ''; }
+sub _export_suspend { ''; }
+sub _export_unsuspend { ''; }
+
+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/nas_wrapper.pm b/FS/FS/part_export/nas_wrapper.pm
new file mode 100644
index 0000000..2499ba3
--- /dev/null
+++ b/FS/FS/part_export/nas_wrapper.pm
@@ -0,0 +1,311 @@
+package FS::part_export::nas_wrapper;
+
+=head1 FS::part_export::nas_wrapper
+
+This is a meta-export that triggers other exports for FS::svc_broadband objects
+based on a set of configurable conditions. These conditions are defined by the
+following FS::router virtual fields:
+
+=over 4
+
+=item nas_conf - Per-router meta-export configuration. See L</"nas_conf Syntax">.
+
+=back
+
+=head2 nas_conf Syntax
+
+export_name|routernum[,routernum]|[field,condition[,field,condition]][||...]
+
+=over 4
+
+=item export_name - Name or exportnum of the export to be executed. In order to specify export options you must use the exportnum form. (ex. 'router' for FS::part_export::router).
+
+=item routernum - FS::router routernum corresponding to the desired FS::router for which this export will be run.
+
+=item field - FS::svc_broadband field (real or virtual). The following condition (regex) will be matched against the value of this field.
+
+=item condition - A regular expression to be match against the value of the previously listed FS::svc_broadband field.
+
+=back
+
+If multiple routernum's are specified, then the export will be triggered for each router listed. If multiple field/condition pairs are present, then the results of the matches will be and'd. Note that if a false match is found, the rest of the matches may not be checked.
+
+You can specify multiple export/router/condition sets by concatenating them with '||'.
+
+=cut
+
+use strict;
+use vars qw(@ISA %info $me $DEBUG);
+
+use FS::Record qw(qsearchs);
+use FS::part_export;
+
+use Tie::IxHash;
+use Data::Dumper qw(Dumper);
+
+@ISA = qw(FS::part_export);
+$me = '[' . __PACKAGE__ . ']';
+$DEBUG = 0;
+
+%info = (
+ 'svc' => 'svc_broadband',
+ 'desc' => 'A meta-export that triggers other svc_broadband exports.',
+ 'options' => {},
+ 'notes' => '',
+);
+
+
+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_replace {
+ my($self) = shift;
+ $self->_export_command('replace', @_);
+}
+
+sub _export_command {
+ my ( $self, $action, $svc_broadband) = (shift, shift, shift);
+
+ my ($new, $old);
+ if ($action eq 'replace') {
+ $new = $svc_broadband;
+ $old = shift;
+ }
+
+ my $router = $svc_broadband->addr_block->router;
+
+ return '' unless grep(/^nas_conf$/, $router->fields);
+ my $nas_conf = $router->nas_conf;
+
+ my $child_exports = &_parse_nas_conf($nas_conf);
+
+ my $error = '';
+
+ my $queue_child_exports = {};
+
+ # Similar to FS::svc_Common::replace, calling insert, delete, and replace
+ # exports where necessary depending on which conditions match.
+ if ($action eq 'replace') {
+
+ my @new_child_exports = ();
+ my @old_child_exports = ();
+
+ # Find all the matching "new" child exports.
+ foreach my $child_export (@$child_exports) {
+ my $match = &_test_child_export_conditions(
+ $child_export->{'conditions'},
+ $new,
+ );
+
+ if ($match) {
+ push @new_child_exports, $child_export;
+ }
+ }
+
+ # Find all the matching "old" child exports.
+ foreach my $child_export (@$child_exports) {
+ my $match = &_test_child_export_conditions(
+ $child_export->{'conditions'},
+ $old,
+ );
+
+ if ($match) {
+ push @old_child_exports, $child_export;
+ }
+ }
+
+ # Insert exports for new.
+ push @{$queue_child_exports->{'insert'}}, (
+ map {
+ my $new_child_export = $_;
+ if (! grep { $new_child_export eq $_ } @old_child_exports) {
+ $new_child_export->{'args'} = [ $new ];
+ $new_child_export;
+ } else {
+ ();
+ }
+ } @new_child_exports
+ );
+
+ # Replace exports for new and old.
+ push @{$queue_child_exports->{'replace'}}, (
+ map {
+ my $new_child_export = $_;
+ if (grep { $new_child_export eq $_ } @old_child_exports) {
+ $new_child_export->{'args'} = [ $new, $old ];
+ $new_child_export;
+ } else {
+ ();
+ }
+ } @new_child_exports
+ );
+
+ # Delete exports for old.
+ push @{$queue_child_exports->{'delete'}}, (
+ grep {
+ my $old_child_export = $_;
+ if (! grep { $old_child_export eq $_ } @new_child_exports) {
+ $old_child_export->{'args'} = [ $old ];
+ $old_child_export;
+ } else {
+ ();
+ }
+ } @old_child_exports
+ );
+
+ } else {
+
+ foreach my $child_export (@$child_exports) {
+ my $match = &_test_child_export_conditions(
+ $child_export->{'conditions'},
+ $svc_broadband,
+ );
+
+ if ($match) {
+ $child_export->{'args'} = [ $svc_broadband ];
+ push @{$queue_child_exports->{$action}}, $child_export;
+ }
+ }
+
+ }
+
+ warn "[debug]$me Dispatching child exports... "
+ . &Dumper($queue_child_exports) if $DEBUG;
+
+ # Actually call the child exports now, with their preset action and arguments.
+ foreach my $_action (keys(%$queue_child_exports)) {
+
+ foreach my $_child_export (@{$queue_child_exports->{$_action}}) {
+ $error = &_dispatch_child_export(
+ $_child_export,
+ $_action,
+ @{$_child_export->{'args'}},
+ @_,
+ );
+
+ # Bail if there's an error queueing one of the exports.
+ # This will all get rolled-back.
+ return $error if $error;
+ }
+
+ }
+
+ return '';
+
+}
+
+
+sub _parse_nas_conf {
+
+ my $nas_conf = shift;
+ my @child_exports = ();
+
+ foreach my $cond_set ($nas_conf =~ m/(.*?[^\\])(?:\|\||$)/g) {
+
+ warn "[debug]$me cond_set is '$cond_set'" if $DEBUG;
+
+ my @args = $cond_set =~ m/(.*?[^\\])(?:\||$)/g;
+
+ my %child_export = (
+ 'export' => $args[0],
+ 'routernum' => [ split(/,\s*/, $args[1]) ],
+ 'conditions' => { @args[2..$#args] },
+ );
+
+ warn "[debug]$me " . Dumper(\%child_export) if $DEBUG;
+
+ push @child_exports, { %child_export };
+
+ }
+
+ return \@child_exports;
+
+}
+
+sub _dispatch_child_export {
+
+ my ($child_export, $action, @args) = (shift, shift, @_);
+
+ my $child_export_name = $child_export->{'export'};
+ my @routernums = @{$child_export->{'routernum'}};
+
+ my $error = '';
+
+ # And the real hack begins...
+
+ my $child_part_export;
+ if ($child_export_name =~ /^(\d+)$/) {
+ my $exportnum = $1;
+ $child_part_export = qsearchs('part_export', { exportnum => $exportnum });
+ unless ($child_part_export) {
+ return "No such FS::part_export with exportnum '$exportnum'";
+ }
+
+ $child_export_name = $child_part_export->exporttype;
+ } else {
+ $child_part_export = new FS::part_export {
+ 'exporttype' => $child_export_name,
+ 'machine' => 'bogus',
+ };
+ }
+
+ warn "[debug]$me running export '$child_export_name' for routernum(s) '"
+ . join(',', @routernums) . "'" if $DEBUG;
+
+ my $cmd_method = "_export_$action";
+
+ foreach my $routernum (@routernums) {
+ $error ||= $child_part_export->$cmd_method(
+ @args,
+ 'routernum' => $routernum,
+ );
+ last if $error;
+ }
+
+ warn "[debug]$me export '$child_export_name' returned '$error'"
+ if $DEBUG;
+
+ return $error;
+
+}
+
+sub _test_child_export_conditions {
+
+ my ($conditions, $svc_broadband) = (shift, shift);
+
+ my $match = 1;
+ foreach my $cond_field (keys %$conditions) {
+ my $cond_regex = $conditions->{$cond_field};
+ warn "[debug]$me Condition: $cond_field =~ /$cond_regex/" if $DEBUG;
+ unless ($svc_broadband->get($cond_field) =~ /$cond_regex/) {
+ $match = 0;
+ last;
+ }
+ }
+
+ return $match;
+
+}
+
+
+1;
+
diff --git a/FS/FS/part_export/netsapiens.pm b/FS/FS/part_export/netsapiens.pm
new file mode 100644
index 0000000..83f0f01
--- /dev/null
+++ b/FS/FS/part_export/netsapiens.pm
@@ -0,0 +1,312 @@
+package FS::part_export::netsapiens;
+
+use vars qw(@ISA $me %info);
+use URI;
+use MIME::Base64;
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+$me = '[FS::part_export::netsapiens]';
+
+tie my %options, 'Tie::IxHash',
+ 'login' => { label=>'NetSapiens tac2 User API username' },
+ 'password' => { label=>'NetSapiens tac2 User API password' },
+ 'url' => { label=>'NetSapiens tac2 User URL' },
+ 'device_login' => { label=>'NetSapiens tac2 Device API username' },
+ 'device_password' => { label=>'NetSapiens tac2 Device API password' },
+ 'device_url' => { label=>'NetSapiens tac2 Device URL' },
+ 'domain' => { label=>'NetSapiens Domain' },
+ 'debug' => { label=>'Enable debugging', type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => [ 'svc_phone', ], # 'part_device',
+ 'desc' => 'Provision phone numbers to NetSapiens',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Requires installation of
+<a href="http://search.cpan.org/dist/REST-Client">REST::Client</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+sub ns_command {
+ my $self = shift;
+ $self->_ns_command('', @_);
+}
+
+sub ns_device_command {
+ my $self = shift;
+ $self->_ns_command('device_', @_);
+}
+
+sub _ns_command {
+ my( $self, $prefix, $method, $command ) = splice(@_,0,4);
+
+ eval 'use REST::Client';
+ die $@ if $@;
+
+ my $ns = new REST::Client 'host'=>$self->option($prefix.'url');
+
+ my @args = ( $command );
+
+ if ( $method eq 'PUT' ) {
+ my $content = $ns->buildQuery( { @_ } );
+ $content =~ s/^\?//;
+ push @args, $content;
+ } elsif ( $method eq 'GET' ) {
+ $args[0] .= $ns->buildQuery( { @_ } );
+ }
+
+ warn "$me $method ". $self->option($prefix.'url'). join(', ', @args). "\n"
+ if $self->option('debug');
+
+ my $auth = encode_base64( $self->option($prefix.'login'). ':'.
+ $self->option($prefix.'password') );
+ push @args, { 'Authorization' => "Basic $auth" };
+
+ $ns->$method( @args );
+ $ns;
+}
+
+sub ns_domain {
+ my($self, $svc_phone) = (shift, shift);
+ $svc_phone->domain || $self->option('domain');
+}
+
+sub ns_subscriber {
+ my($self, $svc_phone) = (shift, shift);
+
+ my $domain = $self->ns_domain($svc_phone);
+ my $phonenum = $svc_phone->phonenum;
+
+ "/domains_config/$domain/subscriber_config/$phonenum";
+}
+
+sub ns_registrar {
+ my($self, $svc_phone) = (shift, shift);
+
+ $self->ns_subscriber($svc_phone).
+ '/registrar_config/'. $self->ns_devicename($svc_phone);
+}
+
+sub ns_devicename {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ my $domain = $self->ns_domain($svc_phone);
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ #"sip:$countrycode$phonenum\@$domain";
+ "sip:$phonenum\@$domain";
+}
+
+sub ns_dialplan {
+ my($self, $svc_phone) = (shift, shift);
+
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ #"/dialplans/DID+Table/dialplan_config/sip:$countrycode$phonenum\@*"
+ "/dialplans/DID+Table/dialplan_config/sip:$phonenum\@*"
+}
+
+sub ns_device {
+ my($self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ #my $countrycode = $svc_phone->countrycode;
+ #my $phonenum = $svc_phone->phonenum;
+
+ "/phones_config/". lc($phone_device->mac_addr);
+}
+
+sub ns_create_or_update {
+ my($self, $svc_phone, $dial_policy) = (shift, shift, shift);
+
+ my $domain = $self->ns_domain($svc_phone);
+ #my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ my( $firstname, $lastname );
+ if ( $svc_phone->phone_name =~ /^\s*(\S+)\s+(\S.*\S)\s*$/ ) {
+ $firstname = $1;
+ $lastname = $2;
+ } else {
+ #deal w/unaudited netsapiens services?
+ my $cust_main = $svc_phone->cust_svc->cust_pkg->cust_main;
+ $firstname = $cust_main->get('first');
+ $lastname = $cust_main->get('last');
+ }
+
+ # Piece 1 (already done) - User creation
+
+ my $ns = $self->ns_command( 'PUT', $self->ns_subscriber($svc_phone),
+ 'subscriber_login' => $phonenum.'@'.$domain,
+ 'firstname' => $firstname,
+ 'lastname' => $lastname,
+ 'subscriber_pin' => $svc_phone->pin,
+ 'dial_plan' => 'Default', #config?
+ 'dial_policy' => $dial_policy,
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ #Piece 2 - sip device creation
+
+ my $ns2 = $self->ns_command( 'PUT', $self->ns_registrar($svc_phone),
+ 'termination_match' => $self->ns_devicename($svc_phone)
+ );
+
+ if ( $ns2->responseCode !~ /^2/ ) {
+ return $ns2->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns2->responseContent ) );
+ }
+
+ #Piece 3 - DID mapping to user
+
+ my $ns3 = $self->ns_command( 'PUT', $self->ns_dialplan($svc_phone),
+ 'to_user' => $phonenum,
+ 'to_host' => $domain,
+ );
+
+ if ( $ns3->responseCode !~ /^2/ ) {
+ return $ns3->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns3->responseContent ) );
+ }
+
+ '';
+}
+
+sub ns_delete {
+ my($self, $svc_phone) = (shift, shift);
+
+ my $ns = $self->ns_command( 'DELETE', $self->ns_subscriber($svc_phone) );
+
+ #delete other things?
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+sub ns_parse_response {
+ my( $self, $content ) = ( shift, shift );
+
+ #try to screen-scrape something useful
+ tie my %hash, Tie::IxHash;
+ while ( $content =~ s/^.*?<p>\s*<b>(.+?)<\/b>\s*(.+?)\s*<\/p>//is ) {
+ ( $hash{$1} = $2 ) =~ s/^\s*<(\w+)>(.+?)<\/\1>/$2/is;
+ }
+
+ %hash;
+}
+
+sub _export_insert {
+ my($self, $svc_phone) = (shift, shift);
+ $self->ns_create_or_update($svc_phone, 'Permit All');
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change phonenum with NetSapiens (unprovision and reprovision?)"
+ if $old->phonenum ne $new->phonenum;
+ $self->_export_insert($new);
+}
+
+sub _export_delete {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ $self->ns_delete($svc_phone);
+}
+
+sub _export_suspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ $self->ns_create_or_update($svc_phone, 'Deny');
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #$self->ns_create_or_update($svc_phone, 'Permit All');
+ $self->_export_insert($svc_phone);
+}
+
+sub export_device_insert {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ my $domain = $self->ns_domain($svc_phone);
+ my $countrycode = $svc_phone->countrycode;
+ my $phonenum = $svc_phone->phonenum;
+
+ my $device = $self->ns_devicename($svc_phone);
+
+ my $ns = $self->ns_device_command(
+ 'PUT', $self->ns_device($svc_phone, $phone_device),
+ 'line1_enable' => 'yes',
+ 'device1' => $self->ns_devicename($svc_phone),
+ 'line1_ext' => $phonenum,
+,
+ #'line2_enable' => 'yes',
+ #'device2' =>
+ #'line2_ext' =>
+
+ #'notes' =>
+ 'server' => 'SiPbx',
+ 'domain' => $domain,
+
+ 'brand' => $phone_device->part_device->devicename,
+
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+sub export_device_delete {
+ my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
+
+ my $ns = $self->ns_device_command(
+ 'DELETE', $self->ns_device($svc_phone, $phone_device),
+ );
+
+ if ( $ns->responseCode !~ /^2/ ) {
+ return $ns->responseCode. ' '.
+ join(', ', $self->ns_parse_response( $ns->responseContent ) );
+ }
+
+ '';
+
+}
+
+
+sub export_device_replace {
+ my( $self, $svc_phone, $new_phone_device, $old_phone_device ) =
+ (shift, shift, shift, shift);
+
+ #?
+ $self->export_device_insert( $svc_phone, $new_phone_device );
+
+}
+
+sub export_links {
+ my($self, $svc_phone, $arrayref) = (shift, shift, shift);
+ #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_phone->username.
+ # qq!">!. $svc_phone->username. qq!</A>!;
+ '';
+}
+
+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/phone_shellcommands.pm b/FS/FS/part_export/phone_shellcommands.pm
new file mode 100644
index 0000000..040af27
--- /dev/null
+++ b/FS/FS/part_export/phone_shellcommands.pm
@@ -0,0 +1,140 @@
+package FS::part_export::phone_shellcommands;
+
+use strict;
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use String::ShellQuote;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+#TODO
+#- modify command (get something from freepbx for changing PINs)
+#- suspension/unsuspension
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root', },
+ 'useradd' => { label=>'Insert command', },
+ 'userdel' => { label=>'Delete command', },
+ 'usermod' => { label=>'Modify command', },
+ 'suspend' => { label=>'Suspension command', },
+ 'unsuspend' => { label=>'Unsuspension command', },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Run remote commands via SSH, for phone numbers',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Run remote commands via SSH, for phone numbers. You will need to
+<a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="FreePBX (build_exten CLI module needed)" onClick='
+ this.form.user.value = "root";
+ this.form.useradd.value = "build_exten.php --create --exten $phonenum --directdid 1$phonenum --sip-secret $sip_password --name $cust_name --vm-password $pin && /usr/share/asterisk/bin/module_admin reload";
+ this.form.userdel.value = "build_exten.php --delete --exten $phonenum && /usr/share/asterisk/bin/module_admin reload";
+ this.form.usermod.value = "build_exten.php --modify --exten $new_phonenum --directdid 1$new_phonenum --sip-secret $new_sip_password --name $new_cust_name --vm-password $new_pin && /usr/share/asterisk/bin/module_admin reload";
+ this.form.suspend.value = "";
+ this.form.unsuspend.value = "";
+ '> (Important note: Reduce freeside-queued "max_kids" to 1 when using FreePBX integration)
+ </UL>
+
+The following variables are available for interpolation (prefixed with new_ or
+old_ for replace operations):
+<UL>
+ <LI><code>$countrycode</code> - Country code
+ <LI><code>$phonenum</code> - Phone number
+ <LI><code>$sip_password</code> - SIP secret (quoted for the shell)
+ <LI><code>$pin</code> - Personal identification number
+ <LI><code>$cust_name</code> - Customer name (quoted for the shell)
+</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_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_phone) = (shift, shift, shift);
+ my $command = $self->option($action);
+ return '' if $command =~ /^\s*$/;
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_phone->getfield($_) foreach $svc_phone->fields;
+ }
+ my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
+ my $cust_name = $cust_pkg ? $cust_pkg->cust_main->name : '';
+ $cust_name = shell_quote $cust_name;
+ my $sip_password = shell_quote $svc_phone->sip_password;
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $svc_phone->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 $cust_pkg = $new->cust_svc->cust_pkg;
+ my $new_cust_name = $cust_pkg ? $cust_pkg->cust_main->name : '';
+ $new_cust_name = shell_quote $new_cust_name;
+ #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::phone_shellcommands::ssh_cmd",
+ };
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
diff --git a/FS/FS/part_export/phone_sqlradius.pm b/FS/FS/part_export/phone_sqlradius.pm
new file mode 100644
index 0000000..24f7845
--- /dev/null
+++ b/FS/FS/part_export/phone_sqlradius.pm
@@ -0,0 +1,158 @@
+package FS::part_export::phone_sqlradius;
+
+use vars qw(@ISA $DEBUG %info );
+use Tie::IxHash;
+use FS::Record qw( dbh str2time_sql ); #qsearch qsearchs );
+#use FS::part_export;
+use FS::part_export::sqlradius qw(sqlradius_connect);
+#use FS::svc_phone;
+#use FS::export_svc;
+#use Carp qw( cluck );
+
+@ISA = qw(FS::part_export::sqlradius);
+
+$DEBUG = 0;
+
+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'
+ },
+ 'hide_ip' => {
+ type => 'checkbox',
+ label => 'Hide IP address information on session reports',
+ },
+ 'hide_data' => {
+ type => 'checkbox',
+ label => 'Hide download/upload information on session reports',
+ },
+
+ #should be default for this one, right?
+ #'show_called_station' => {
+ # type => 'checkbox',
+ # label => 'Show the Called-Station-ID on session reports',
+ #},
+
+ #N/A
+ #'overlimit_groups' => { label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit', } ,
+ #'groups_susp_reason' => { label =>
+ # 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)',
+ # type => 'textarea',
+ # },
+
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS) for phone provisioning and rating',
+ 'options' => \%options,
+ 'notes' => <<END,
+Real-time export of <b>radcheck</b> table
+<!--, <b>radreply</b> and <b>usergroup</b>-- tables>
+to any SQL database for <a href="http://www.freeradius.org/">FreeRADIUS</a>
+or <a href="http://radius.innercite.com/">ICRADIUS</a>.
+<br><br>
+
+This export is for phone/VoIP provisioning and rating. For a regular RADIUS
+export, see sqlradius.
+<br><br>
+
+<!--An existing RADIUS database will be updated in realtime, but you can use
+<a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Developer/bin/freeside-phone_sqlradius-reset">freeside-phone_sqlradius-reset</a>
+to delete the entire RADIUS database and repopulate the tables from the
+Freeside database.
+<br><br>
+-->
+
+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.
+
+END
+);
+
+sub rebless { shift; }
+
+sub export_username {
+ my($self, $svc_phone) = (shift, shift);
+ $svc_phone->countrycode. $svc_phone->phonenum;
+}
+
+sub _export_suspend {}
+sub _export_unsuspend {}
+
+#probably harmless that we ->can('usage_sessions').... ?
+
+#we want to feed these into CDRs, not update svc_acct records
+sub update_svc {
+ my $self = shift;
+
+ my $fdbh = dbh;
+ my $dbh = sqlradius_connect( map $self->option($_),
+ qw( datasrc username password ) );
+
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+
+ my @fields = qw( radacctid username realm acctsessiontime );
+
+ my @param = ();
+ my $where = '';
+
+ my $sth = $dbh->prepare("
+ SELECT RadAcctId, UserName, AcctSessionTime,
+ $str2time AcctStartTime), $str2time AcctStopTime),
+ CallingStationID, CalledStationID
+ FROM radacct
+ WHERE FreesideStatus IS NULL
+ AND AcctStopTime != 0
+ ") or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_arrayref ) {
+ my( $RadAcctId, $UserName, $AcctSessionTime,
+ $AcctStartTime, $AcctStopTime,
+ $CallingStationID, $CalledStationID,
+ )= @$row;
+ warn "processing record: ".
+ "$RadAcctId ($UserName for ${AcctSessionTime}s"
+ if $DEBUG;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit; # can't undo side effects, but at
+ local $FS::UID::AutoCommit = 0; # least we can avoid over counting
+
+ my $cdr = new FS::cdr {
+ 'src' => $CallingStationID,
+ 'charged_party' => $UserName,
+ 'dst' => $CalledStationID,
+ 'startdate' => $AcctStartTime,
+ 'enddate' => $AcctStopTime,
+ 'duration' => $AcctStopTime - $AcctStartTime,
+ 'billsec' => $AcctSessionTime,
+ };
+
+ my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ".
+ "(UserName $UserName)";
+
+ my $error = $cdr->insert;
+ my $status = $error ? 'skipped' : 'done';
+
+ warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG;
+ my $psth = $dbh->prepare("UPDATE radacct
+ SET FreesideStatus = ?
+ WHERE RadAcctId = ?"
+ ) or die $dbh->errstr;
+ $psth->execute($status, $RadAcctId) or die $psth->errstr;
+
+ $fdbh->commit or die $fdbh->errstr if $oldAutoCommit;
+
+ }
+
+}
+
+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/prizm.pm b/FS/FS/part_export/prizm.pm
new file mode 100644
index 0000000..02e89c6
--- /dev/null
+++ b/FS/FS/part_export/prizm.pm
@@ -0,0 +1,591 @@
+package FS::part_export::prizm;
+
+use vars qw(@ISA %info %options $DEBUG $me);
+use Tie::IxHash;
+use FS::Record qw(fields dbh);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+$DEBUG = 0;
+$me = '[' . __PACKAGE__ . ']';
+
+tie %options, 'Tie::IxHash',
+ 'url' => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' },
+ 'user' => { label => 'Northbound username', default=>'nbi' },
+ 'password' => { label => 'Password', default => '' },
+ 'ems' => { label => 'Full EMS', type => 'checkbox' },
+ 'always_bam' => { label => 'Always activate/suspend authentication', type => 'checkbox' },
+ 'element_name_length' => { label => 'Size of siteName (best left blank)' },
+;
+
+my $notes = <<'EOT';
+Real-time export of <b>svc_broadband</b>, <b>cust_pkg</b>, and <b>cust_main</b>
+record data to Motorola
+<a href="http://motorola.canopywireless.com/products/prizm/">Canopy Prizm
+software</a> via the Northbound interface.<br><br>
+
+Freeside will attempt to create an element in an existing network with the
+values provided in svc_broadband. Of particular interest are
+<ul>
+ <li> mac address - used to identify the element
+ <li> vlan profile - an exact match for a vlan profiles defined in prizm
+ <li> ip address - defines the management ip address of the prizm element
+ <li> latitude - GPS latitude
+ <li> longitude - GPS longitude
+ <li> altitude - GPS altitude
+</ul>
+
+In addition freeside attempts to set the service plan name in prizm to the
+name of the package in which the service resides.
+
+The service is associated with a customer in prizm as well, and freeside
+will create the customer should none already exist with import id matching
+the freeside customer number. The following fields are set.
+
+<ul>
+ <li> importId - the freeside customer number
+ <li> customerType - freeside
+ <li> customerName - the name associated with the freeside shipping address
+ <li> address1 - the shipping address
+ <li> address2
+ <li> city
+ <li> state
+ <li> zipCode
+ <li> country
+ <li> workPhone - the daytime phone number
+ <li> homePhone - the night phone number
+ <li> freesideId - the freeside customer number
+</ul>
+
+ Additionally set on the element are
+<ul>
+ <li> Site Name - The shipping name followed by the service broadband description field
+ <li> Site Location - the shipping address
+ <li> Site Contact - the daytime and night phone numbers
+</ul>
+
+Freeside provisions, suspends, and unsuspends elements BAM only unless the
+'Full EMS' checkbox is checked.<br><br>
+
+When freeside provisions an element the siteName is copied internally by
+prizm in such a manner that it is possible for the value to exceed the size
+of the column used in the prizm database. Therefore freeside truncates
+by default this value to 50 characters. It is thought that this
+column is the account_name column of the element_user_account table. It
+may be possible to lift this limit by modifying the prizm database and
+setting a new appropriate value on this export. This is untested and
+possibly harmful.
+
+EOT
+
+%info = (
+ 'svc' => 'svc_broadband',
+ 'desc' => 'Real-time export to Northbound Interface',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => $notes,
+);
+
+sub prizm_command {
+ my ($self,$namespace,$method) = (shift,shift,shift);
+
+ eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
+ die $@ if $@;
+
+ my $prizm = new Net::Prizm (
+ namespace => $namespace,
+ url => $self->option('url'),
+ user => $self->option('user'),
+ password => $self->option('password'),
+ );
+
+ $prizm->$method(@_);
+}
+
+sub queued_prizm_command { # subroutine
+ my( $url, $user, $password, $namespace, $method, @args ) = @_;
+
+ eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
+ die $@ if $@;
+
+ my $prizm = new Net::Prizm (
+ namespace => $namespace,
+ url => $url,
+ user => $user,
+ password => $password,
+ );
+
+ $err_or_som = $prizm->$method( @args);
+
+ die $err_or_som
+ unless ref($err_or_som);
+
+ '';
+
+}
+
+sub _export_insert {
+ my( $self, $svc ) = ( shift, shift );
+ warn "$me: _export_insert called for export ". $self->exportnum.
+ " on service ". $svc->svcnum. "\n"
+ if $DEBUG;
+
+ my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
+
+ my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
+ ['import_id'],
+ [$cust_main->custnum],
+ ['='],
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+
+ my $pre = '';
+ if ( defined $cust_main->dbdef_table->column('ship_last') ) {
+ $pre = $cust_main->ship_last ? 'ship_' : '';
+ }
+ my $name = $pre ? $cust_main->ship_name : $cust_main->name;
+ my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
+ qw (address1 address2 city state zip)
+ );
+ my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
+ qw (daytime night)
+ );
+
+ my $pcustomer;
+ if ($err_or_som->result->[0]) {
+ $pcustomer = $err_or_som->result->[0]->customerId;
+ warn "$me: found customer $pcustomer in prizm\n" if $DEBUG;
+ }else{
+ my $chashref = $cust_main->hashref;
+ my $customerinfo = {
+ importId => $cust_main->custnum,
+ customerName => $name,
+ customerType => 'freeside',
+ address1 => $chashref->{"${pre}address1"},
+ address2 => $chashref->{"${pre}address2"},
+ city => $chashref->{"${pre}city"},
+ state => $chashref->{"${pre}state"},
+ zipCode => $chashref->{"${pre}zip"},
+ workPhone => $chashref->{"${pre}daytime"},
+ homePhone => $chashref->{"${pre}night"},
+ email => @{[$cust_main->invoicing_list_emailonly]}[0],
+ extraFieldNames => [ 'country', 'freesideId',
+ ],
+ extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
+ ],
+ };
+
+ $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
+ $customerinfo);
+ return $err_or_som
+ unless ref($err_or_som);
+
+ $pcustomer = $err_or_som->result;
+ warn "$me: added customer $pcustomer to prizm\n" if $DEBUG;
+ }
+ warn "multiple prizm customers found for $cust_main->custnum"
+ if scalar(@$pcustomer) > 1;
+
+# #kinda big question/expensive
+# $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
+# ['Network Default Gateway Address'],
+# [$svc->addr_block->ip_gateway],
+# ['='],
+# );
+# return $err_or_som
+# unless ref($err_or_som);
+#
+# return "No elements in network" unless exists $err_or_som->result->[0];
+
+ my $networkid = 0;
+# for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
+# if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
+# $networkid = $err_or_som->result->[0]->attributeValues->[$i];
+# last;
+# }
+# }
+
+# here we cope with a problem of prizm failing to insert for reason
+# of duplicate mac addr, but doing so inconsistently... a race in prizm?
+
+ $self->prizm_command( 'CustomerIfService', 'removeElementFromCustomer',
+ 0,
+ $cust_main->custnum,
+ 0,
+ $svc->mac_addr,
+ );
+
+ $err_or_som = $self->prizm_command( 'NetworkIfService', 'getPrizmElements',
+ [ 'MAC Address' ],
+ [ $svc->mac_addr ],
+ [ '=' ],
+ );
+ if ( ref($err_or_som) && $err_or_som->result->[0] ) { # ignore errors
+ $self->prizm_command( 'NetworkIfService', 'deleteElement',
+ $err_or_som->result->[0],
+ 1,
+ );
+ }
+# end of coping
+
+ my $performance_profile = $svc->performance_profile;
+ $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
+
+ my $element_name_length = 50;
+ $element_name_length = $1
+ if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
+ $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
+ $networkid,
+ $svc->mac_addr,
+ substr($name . " " . $svc->description,
+ 0, $element_name_length),
+ $location,
+ $contact,
+ sprintf("%032X", $svc->authkey || 0),
+ $performance_profile,
+ $svc->vlan_profile,
+ ($self->option('ems') ? 1 : 0 ),
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: added provisioned element to prizm\n" if $DEBUG;
+
+ my (@names) = ('Management IP',
+ 'GPS Latitude',
+ 'GPS Longitude',
+ 'GPS Altitude',
+ 'Site Name',
+ 'Site Location',
+ 'Site Contact',
+ );
+ my (@values) = ($svc->ip_addr,
+ $svc->latitude,
+ $svc->longitude,
+ $svc->altitude,
+ $name . " " . $svc->description,
+ $location,
+ $contact,
+ );
+ $element = $err_or_som->result->elementId;
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
+ [ $element ],
+ \@names,
+ \@values,
+ 0,
+ 1,
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: set element configuration\n" if $DEBUG;
+
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+ [ $element ],
+ $svc->vlan_profile,
+ 0,
+ 1,
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: set element vlan profile\n" if $DEBUG;
+
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+ [ $element ],
+ $performance_profile,
+ 0,
+ 1,
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: set element configset (performance profile)\n" if $DEBUG;
+
+ $err_or_som = $self->prizm_command('NetworkIfService',
+ 'activateNetworkElements',
+ [ $element ],
+ 1,
+ ( $self->option('ems') ? 1 : 0 ),
+ );
+
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: activated element\n" if $DEBUG;
+
+ $err_or_som = $self->prizm_command('CustomerIfService',
+ 'addElementToCustomer',
+ 0,
+ $cust_main->custnum,
+ 0,
+ $svc->mac_addr,
+ );
+
+ return $err_or_som
+ unless ref($err_or_som);
+ warn "$me: added element to customer\n" if $DEBUG;
+
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc ) = ( shift, shift );
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $cust_pkg = $svc->cust_svc->cust_pkg;
+
+ my $depend = [];
+
+ if ($cust_pkg) {
+ my $queue = new FS::queue {
+ 'svcnum' => $svc->svcnum,
+ 'job' => 'FS::part_export::prizm::queued_prizm_command',
+ };
+ my $error = $queue->insert(
+ ( map { $self->option($_) }
+ qw( url user password ) ),
+ 'CustomerIfService',
+ 'removeElementFromCustomer',
+ 0,
+ $cust_pkg->custnum,
+ 0,
+ $svc->mac_addr,
+ );
+
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ push @$depend, $queue->jobnum;
+ }
+
+ my $err_or_queue =
+ $self->queue_statuschange('deleteElement', $depend, $svc, 1);
+
+ unless (ref($err_or_queue)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = ( shift, shift, shift );
+
+ my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
+ [ 'MAC Address' ],
+ [ $old->mac_addr ],
+ [ '=' ],
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+
+ return "Can't find prizm element for " . $old->mac_addr
+ unless $err_or_som->result->[0];
+
+ my %freeside2prizm = ( mac_addr => 'MAC Address',
+ ip_addr => 'Management IP',
+ latitude => 'GPS Latitude',
+ longitude => 'GPS Longitude',
+ altitude => 'GPS Altitude',
+ authkey => 'Authentication Key',
+ );
+
+ my (@values);
+ my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
+ grep { $old->$_ ne $new->$_ }
+ grep { exists($freeside2prizm{$_}) }
+ fields( 'svc_broadband' );
+
+ if ($old->description ne $new->description) {
+ my $cust_main = $old->cust_svc->cust_pkg->cust_main;
+ my $name = defined($cust_main->dbdef_table->column('ship_last'))
+ ? $cust_main->ship_name
+ : $cust_main->name;
+ push @values, $name . " " . $new->description;
+ push @names, "Site Name";
+ }
+
+ my $element = $err_or_som->result->[0]->elementId;
+
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
+ [ $element ],
+ \@names,
+ \@values,
+ 0,
+ 1,
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+ [ $element ],
+ $new->vlan_profile,
+ 0,
+ 1,
+ )
+ if $old->vlan_profile ne $new->vlan_profile;
+
+ return $err_or_som
+ unless ref($err_or_som);
+
+ my $performance_profile = $new->performance_profile;
+ $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
+
+ $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+ [ $element ],
+ $performance_profile,
+ 0,
+ 1,
+ );
+ return $err_or_som
+ unless ref($err_or_som);
+
+ '';
+
+}
+
+sub _export_suspend {
+ my( $self, $svc ) = ( shift, shift );
+ my $depend = [];
+ my $ems = $self->option('ems') ? 1 : 0;
+ my $err_or_queue = '';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $err_or_queue =
+ $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
+ unless (ref($err_or_queue)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ push @$depend, $err_or_queue->jobnum;
+
+ if ($ems && $self->option('always_bam')) {
+ $err_or_queue =
+ $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
+ unless (ref($err_or_queue)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_unsuspend {
+ my( $self, $svc ) = ( shift, shift );
+ my $depend = [];
+ my $ems = $self->option('ems') ? 1 : 0;
+ my $err_or_queue = '';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ($ems && $self->option('always_bam')) {
+ $err_or_queue =
+ $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
+ unless (ref($err_or_queue)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ push @$depend, $err_or_queue->jobnum;
+ }
+
+ $err_or_queue =
+ $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
+ unless (ref($err_or_queue)) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub export_links {
+ my( $self, $svc, $arrayref ) = ( shift, shift, shift );
+
+ push @$arrayref,
+ '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
+
+ '';
+}
+
+sub queue_statuschange {
+ my( $self, $method, $jobs, $svc, @args ) = @_;
+
+ # already in a transaction and can't die here
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svc->svcnum,
+ 'job' => 'FS::part_export::prizm::statuschange',
+ };
+ my $error = $queue->insert(
+ ( map { $self->option($_) }
+ qw( url user password ) ),
+ $method,
+ $svc->mac_addr,
+ @args,
+ );
+
+ unless ($error) { # successful insertion
+ foreach my $job ( @$jobs ) {
+ $error ||= $queue->depend_insert($job);
+ }
+ }
+
+ $error or $queue;
+}
+
+sub statuschange { # subroutine
+ my( $url, $user, $password, $method, $mac_addr, @args) = @_;
+
+ eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
+ die $@ if $@;
+
+ my $prizm = new Net::Prizm (
+ namespace => 'NetworkIfService',
+ url => $url,
+ user => $user,
+ password => $password,
+ );
+
+ my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
+ [ $mac_addr ],
+ [ '=' ],
+ );
+ die $err_or_som
+ unless ref($err_or_som);
+
+ die "Can't find prizm element for " . $mac_addr
+ unless $err_or_som->result->[0];
+
+ my $arg1;
+ # yuck!
+ if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
+ $arg1 = [ $err_or_som->result->[0]->elementId ];
+ }else{
+ $arg1 = $err_or_som->result->[0]->elementId;
+ }
+ $err_or_som = $prizm->$method( $arg1, @args );
+
+ die $err_or_som
+ unless ref($err_or_som);
+
+ '';
+
+}
+
+
+1;
diff --git a/FS/FS/part_export/radiator.pm b/FS/FS/part_export/radiator.pm
new file mode 100644
index 0000000..2ac3edb
--- /dev/null
+++ b/FS/FS/part_export/radiator.pm
@@ -0,0 +1,167 @@
+package FS::part_export::radiator;
+
+use vars qw(@ISA %info $radusers);
+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 RADIATOR',
+ 'options' => \%options,
+ 'nodomain' => '',
+ 'notes' => <<'END',
+Real-time export of the <b>radusers</b> table to any SQL database in
+<a href="http://www.open.com.au/radiator/">Radiator</a>-native format.
+To setup accounting, see the RADIATOR documentation for hooks to update
+a standard <b>radacct</b> table.
+END
+);
+
+@ISA = qw(FS::part_export::sqlradius); #for regular sqlradius accounting
+
+$radusers = 'RADUSERS'; #MySQL is case sensitive about table names! huh
+
+#sub export_username {
+# my($self, $svc_acct) = (shift, shift);
+# $svc_acct->email;
+#}
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->radiator_queue(
+ $svc_acct->svcnum,
+ 'insert',
+ $self->_radiator_hash($svc_acct),
+ );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+# return "can't (yet) change domain with radiator export"
+# if $old->domain ne $new->domain;
+# return "can't (yet) change username with radiator export"
+# if $old->username ne $new->username;
+
+ $self->radiator_queue(
+ $new->svcnum,
+ 'replace',
+ $self->export_username($old),
+ $self->_radiator_hash($new),
+ );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ $self->radiator_queue(
+ $svc_acct->svcnum,
+ 'delete',
+ $self->export_username($svc_acct),
+ );
+}
+
+sub _radiator_hash {
+ my( $self, $svc_acct ) = @_;
+ my %hash = (
+ 'username' => $self->export_username($svc_acct),
+ 'pass_word' => $svc_acct->crypt_password,
+ 'fullname' => $svc_acct->finger,
+ map { my $method = "radius_$_"; $_ => $svc_acct->$method(); }
+ qw( framed_filter_id framed_mtu framed_netmask framed_protocol
+ framed_routing login_host login_service login_tcp_port )
+ );
+ $hash{'timeleft'} = $svc_acct->seconds
+ if $svc_acct->seconds =~ /^\d+$/;
+ $hash{'staticaddress'} = $svc_acct->slipip
+ if $svc_acct->slipip =~ /^[\d\.]+$/; # and $self->slipip ne '0.0.0.0';
+
+ $hash{'servicename'} = ( $svc_acct->radius_groups )[0];
+
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ $hash{'validto'} = $cust_pkg->bill
+ if $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill;
+
+ #some other random stuff, should probably be attributes or virtual fields
+ #$hash{'state'} = 0; #only inserts
+ #$hash{'badlogins'} = 0; #only inserts
+ $hash{'maxlogins'} = 1;
+ $hash{'addeddate'} = $cust_pkg->setup
+ if $cust_pkg && $cust_pkg->setup;
+ $hash{'validfrom'} = $cust_pkg->last_bill || $cust_pkg->setup
+ if $cust_pkg && ( $cust_pkg->last_bill || $cust_pkg->setup );
+ $hash{'state'} = $cust_pkg->susp ? 1 : 0
+ if $cust_pkg;
+
+ %hash;
+}
+
+sub radiator_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::radiator::radiator_$method",
+ };
+ $queue->insert(
+ $self->option('datasrc'),
+ $self->option('username'),
+ $self->option('password'),
+ @_,
+ ); # or $queue;
+}
+
+sub radiator_insert { #subroutine, not method
+ my $dbh = radiator_connect(shift, shift, shift);
+ my %hash = @_;
+ $hash{'state'} = 0; #see "random stuff" above
+ $hash{'badlogins'} = 0; #see "random stuff" above
+
+ my $sth = $dbh->prepare(
+ "INSERT INTO $radusers ( ". join(', ', keys %hash ). ' ) '.
+ 'VALUES ( '. join(', ', map '?', keys %hash ). ' ) '
+ ) or die $dbh->errstr;
+ $sth->execute( values %hash )
+ or die $sth->errstr;
+
+ $dbh->disconnect;
+
+}
+
+sub radiator_replace { #subroutine, not method
+ my $dbh = radiator_connect(shift, shift, shift);
+ my ( $old_username, %hash ) = @_;
+
+ my $sth = $dbh->prepare(
+ "UPDATE $radusers SET ". join(', ', map " $_ = ?", keys %hash ).
+ ' WHERE username = ?'
+ ) or die $dbh->errstr;
+ $sth->execute( values(%hash), $old_username )
+ or die $sth->errstr;
+
+ $dbh->disconnect;
+}
+
+sub radiator_delete { #subroutine, not method
+ my $dbh = radiator_connect(shift, shift, shift);
+ my ( $username ) = @_;
+
+ my $sth = $dbh->prepare(
+ "DELETE FROM $radusers WHERE username = ?"
+ ) or die $dbh->errstr;
+ $sth->execute( $username )
+ or die $sth->errstr;
+
+ $dbh->disconnect;
+}
+
+
+sub radiator_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/router.pm b/FS/FS/part_export/router.pm
new file mode 100644
index 0000000..42aa51c
--- /dev/null
+++ b/FS/FS/part_export/router.pm
@@ -0,0 +1,375 @@
+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:
+
+=head1 Required custom fields
+
+=over 4
+
+=item admin_address - IP address (or hostname) to connect.
+
+=item admin_user - Username for the router.
+
+=item admin_password - Password for the router.
+
+=item admin_protocol - Protocol to use for the router. 'telnet' or 'ssh'. The ssh protocol only support password-less (ie. RSA key) authentication. As such, the admin_password field isn't used if ssh is specified.
+
+=item admin_timeout - Time in seconds to wait for a connection.
+
+=item admin_prompt - A regular expression matching the router's prompt. See Net::Telnet for details. Only applies to the 'telnet' protocol.
+
+=item admin_cmd_insert - Insert export command.
+
+=item admin_cmd_insert_error - Insert export command error pattern.
+
+=item admin_cmd_delete - Delete export command.
+
+=item admin_cmd_delete_error - Delete export command error pattern.
+
+=item admin_cmd_replace - Replace export command.
+
+=item admin_cmd_replace_error - Replace export command error pattern.
+
+=item admin_cmd_suspend - Suspend export command.
+
+=item admin_cmd_suspend_error - Support export command error pattern.
+
+=item admin_cmd_unsuspend - Unsuspend export command.
+
+=item admin_cmd_unsuspend_error - Unsuspend export command error pattern.
+
+The admin_cmd_* virtual fields, if set, will be processed in one of two ways. After being expanded, they will be run on the router specified by admin_address using the protocol specified by admin_protocol.
+
+=over 4
+
+=item Text::Template
+
+If the export command contains the string [@--, then it will be processed with Text::Template using [@-- and --@] as delimeters.
+
+=item eval
+
+If the export command does not contain [@--, it will be double quoted and eval'd.
+
+=back
+
+The admin_cmd_*_error virtual fields, if set, define a regular expression that will be matched against the output of the command being run. If the pattern matches, an error will be raised using the output as the error.
+
+If any of the required router virtual fields are not defined, then the export silently declines.
+
+=back
+
+The export itself takes no options.
+
+=cut
+
+use strict;
+use vars qw(@ISA %info $me $DEBUG);
+use Tie::IxHash;
+use Text::Template;
+
+use FS::Record qw(qsearchs);
+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'},
+;
+
+%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. This export will execute if the following virtual fields are set on the router: admin_user, admin_password, admin_address, admin_timeout, admin_prompt. Option virtual fields are: admin_cmd_insert, admin_cmd_replace, admin_cmd_delete, admin_cmd_suspend, admin_cmd_unsuspend. See the module documentation for a full list of required/supported router virtual fields.',
+);
+
+$me = '[' . __PACKAGE__ . ']';
+$DEBUG = 1;
+
+
+sub rebless { shift; }
+
+sub _field_prefix { 'admin'; }
+
+sub _req_router_fields {
+ map {
+ $_[0]->_field_prefix . '_' . $_
+ } (qw(address prompt user));
+}
+
+sub _export_insert {
+ my($self) = shift;
+ warn "Running insert for " . ref($self);
+ $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_replace {
+ my($self) = shift;
+ $self->_export_command('replace', @_);
+}
+
+sub _export_command {
+ my ($self, $action, $svc_broadband) = (shift, shift, shift);
+ my ($error, $old);
+
+ if ($action eq 'replace') {
+ $old = shift;
+ }
+
+ warn "[debug]$me Processing action '$action'" if $DEBUG;
+
+ # fetch router info
+ my $router = $self->_get_router($svc_broadband, @_);
+ unless ($router) {
+ return "Unable to lookup router for $action export";
+ }
+
+ unless ($self->_check_router_fields($router)) {
+ # Virtual fields aren't defined. Exit silently.
+ warn "[debug]$me Required router virtual fields not defined. Returning..."
+ if $DEBUG;
+ return '';
+ }
+
+ my $args;
+ ($error, $args) = $self->_prepare_args(
+ $action,
+ $router,
+ $svc_broadband,
+ ($old ? $old : ()),
+ @_
+ );
+
+ if ($error) {
+ # Error occured while preparing args.
+ return $error;
+ } elsif (not defined $args) {
+ # Silently decline.
+ warn "[debug]$me Declining '$action' export" if $DEBUG;
+ return '';
+ } # else ... queue the export.
+
+ warn "[debug]$me Queueing with args: " . join(', ', @$args) if $DEBUG;
+
+ return(
+ $self->_queue(
+ $svc_broadband->svcnum,
+ $self->_get_cmd_sub($svc_broadband, $router),
+ @$args
+ )
+ );
+
+}
+
+sub _prepare_args {
+
+ my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
+ my $old = shift if ($action eq 'replace');
+ my $error = '';
+
+ my $field_prefix = $self->_field_prefix;
+ my $command = $router->getfield("${field_prefix}_cmd_${action}");
+ unless ($command) {
+ warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
+ . "is not defined." if $DEBUG;
+ return '';
+ }
+
+ if ($command =~ /\[\@--/) { # Use Text::Template
+
+ my $template_data = {};
+
+ if ($action eq 'replace') {
+ $template_data->{"old_$_"} = $old->getfield($_) foreach $old->fields;
+ $template_data->{"new_$_"} = $svc_broadband->getfield($_)
+ foreach $svc_broadband->fields;
+ } else {
+ $template_data->{$_} = $svc_broadband->getfield($_)
+ foreach $svc_broadband->fields;
+ }
+
+ my $template = new Text::Template (
+ TYPE => 'STRING',
+ SOURCE => $command,
+ DELIMITERS => [ '[@--', '--@]' ],
+ ) or return "Unable to construct template for router command: "
+ . $Text::Template::ERROR;
+
+ $command = $template->fill_in(
+ HASH => $template_data,
+ BROKEN_ARG => \$error,
+ BROKEN => sub {
+ my %bargs = @_;
+ my $err = $bargs{'arg'};
+ $$err = $bargs{'error'};
+ return undef;
+ },
+ );
+
+ if (not defined $command or $error) {
+ $error ||= $Text::Template::ERROR;
+ return "Unable to fill-in template for router command: $error";
+ }
+
+ } else { # Use eval
+ no strict 'vars';
+ no strict 'refs';
+
+ if ($action eq 'replace') {
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
+ $command = eval(qq("$command"));
+ } else {
+ ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
+ $command = eval(qq("$command"));
+ }
+ return $@ if $@;
+ }
+
+ my $args = [
+ 'user' => $router->getfield($field_prefix . '_user'),
+ 'password' => $router->getfield($field_prefix . '_password'),
+ 'host' => $router->getfield($field_prefix . '_address'),
+ 'Timeout' => $router->getfield($field_prefix . '_timeout'),
+ 'Prompt' => $router->getfield($field_prefix . '_prompt'),
+ 'command' => $command,
+ ];
+
+ my $error_check = $router->getfield("${field_prefix}_cmd_${action}_error");
+ push(@$args, ('error_check' => $error_check)) if ($error_check);
+
+ return('', $args);
+
+}
+
+sub _get_cmd_sub {
+
+ my ($self, $svc_broadband, $router) = (shift, shift, shift);
+
+ my $protocol = (
+ $router->getfield($self->_field_prefix . '_protocol') =~ /^(telnet|ssh)$/
+ ) ? $1 : 'telnet';
+
+ return(ref($self)."::".$protocol."_cmd");
+
+}
+
+sub _check_router_fields {
+
+ my ($self, $router, $action) = (shift, shift, shift);
+ my @check_fields = $self->_req_router_fields;
+
+ foreach (@check_fields) {
+ if ($router->getfield($_) eq '') {
+ warn "[debug]$me Required field '$_' is unset" if $DEBUG;
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+}
+
+sub _queue {
+ my( $self, $svcnum, $cmd_sub ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ };
+ $queue->job($cmd_sub);
+ $queue->insert(@_);
+}
+
+sub _get_router {
+ my ($self, $svc_broadband, %args) = (shift, shift, shift, @_);
+
+ my $router;
+ if ($args{'routernum'}) {
+ $router = qsearchs('router', { routernum => $args{'routernum'}});
+ } else {
+ $router = $svc_broadband->addr_block->router;
+ }
+
+ return($router);
+
+}
+
+
+# Subroutines
+sub ssh_cmd {
+ my %arg = @_;
+
+ eval 'use Net::SSH \'0.08\'';
+ die $@ if $@;
+
+ my @out = &Net::SSH::ssh_cmd( { @_ } );
+ my $error = &_cmd_error_check(\%arg, \@out);
+
+ die ("Error while processing ssh command: $error") if $error;
+
+ return '';
+
+}
+
+sub telnet_cmd {
+ my %arg = @_;
+
+ eval 'use Net::Telnet';
+ die $@ if $@;
+
+ my $t = new Net::Telnet (Timeout => $arg{'Timeout'},
+ Prompt => $arg{'Prompt'});
+ $t->open($arg{'host'});
+ $t->login($arg{'user'}, $arg{'password'});
+ my @out = $t->cmd($arg{'command'});
+ my $error = &_cmd_error_check(\%arg, \@out);
+
+ die ("Error while processing telnet command: $error") if $error;
+
+ return '';
+
+}
+
+sub _cmd_error_check {
+ my ($arg, $out) = (shift, shift);
+
+ die "_cmd_error_check called without proper arguments"
+ unless (ref($arg) eq 'HASH' and ref($out) eq 'ARRAY');
+
+ unless (exists($arg->{'error_check'}) and $arg->{'error_check'} ne '') {
+ #Preserve default behaviour and return output if a check isn't defined.
+ warn "Output from router command: " . join('', @$out) if $DEBUG;
+ return '';
+ }
+
+ my $error_check = $arg->{'error_check'};
+ foreach (@$out) {
+ return $_ if /$error_check/;
+ }
+
+ return '';
+
+}
+
+1;
diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm
new file mode 100644
index 0000000..ec861d3
--- /dev/null
+++ b/FS/FS/part_export/shellcommands.pm
@@ -0,0 +1,474 @@
+package FS::part_export::shellcommands;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use String::ShellQuote;
+use FS::part_export;
+use FS::Record qw( qsearch qsearchs );
+
+@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_no_queue' => { label=>'Run immediately',
+ type => 'checkbox',
+ },
+ 'useradd_stdin' => { label=>'Insert command STDIN',
+ type =>'textarea',
+ default=>'',
+ },
+ 'userdel' => { label=>'Delete command',
+ default=>'userdel -r $username',
+ #default=>'rm -rf $dir',
+ },
+ 'userdel_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
+ '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 -g $new_gid -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_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
+ 'usermod_stdin' => { label=>'Modify command STDIN',
+ type =>'textarea',
+ default=>'',
+ },
+ 'usermod_pwonly' => { label=>'Disallow username, domain, uid, gid, and dir changes', #and RADIUS group changes',
+ type =>'checkbox',
+ },
+ 'usermod_nousername' => { label=>'Disallow just username changes',
+ type =>'checkbox',
+ },
+ 'suspend' => { label=>'Suspension command',
+ default=>'usermod -L $username',
+ },
+ 'suspend_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
+ 'suspend_stdin' => { label=>'Suspension command STDIN',
+ default=>'',
+ },
+ 'unsuspend' => { label=>'Unsuspension command',
+ default=>'usermod -U $username',
+ },
+ 'unsuspend_no_queue' => { label=>'Run immediately',
+ type =>'checkbox',
+ },
+ 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
+ default=>'',
+ },
+ 'crypt' => { label => 'Default password encryption',
+ type=>'select', options=>[qw(crypt md5)],
+ default => 'crypt',
+ },
+ 'groups_susp_reason' => { label =>
+ 'Radius group mapping to reason (via template user)',
+ type => 'textarea',
+ },
+# 'no_queue' => { label => 'Run command immediately',
+# type => 'checkbox',
+# },
+;
+
+%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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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 -g $new_gid -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 -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 -g $new_gid -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 -g $new_gid -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 -g $new_gid -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, already quoted for the shell (do not add additional quotes).
+ <LI><code>$crypt_password</code> - encrypted password. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
+ <LI><code>$ldap_password</code> - Password in LDAP/RFC2307 format (for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or "{MD5}5426824942db4253f87a1009fd5d2d4"). When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
+ <LI><code>$uid</code>
+ <LI><code>$gid</code>
+ <LI><code>$finger</code> - GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
+ <LI><code>$first</code> - First name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (do not add additional quotes).
+ <LI><code>$last</code> - Last name of GECOS. When used on the command line (rather than STDIN), it will be quoted for the shell already (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><code>$reasonnum (when suspending)</code>
+ <LI><code>$reasontext (when suspending)</code>
+ <LI><code>$reasontypenum (when suspending)</code>
+ <LI><code>$reasontypetext (when suspending)</code>
+ <LI><code>$pkgnum</code>
+ <LI><code>$custnum</code>
+ <LI>All other fields in <b>svc_acct</b> are also available.
+ <LI>The following fields from <b>cust_main</b> are also available (except during replace): company, address1, address2, city, state, zip, county, daytime, night, fax, otaker. When used on the command line (rather than STDIN), they will be quoted for the shell already (do not add additional quotes).
+</UL>
+END
+);
+
+sub _groups_susp_reason_map { shift->_map('groups_susp_reason'); }
+
+sub _map {
+ my $self = shift;
+ map { reverse(/^\s*(\S+)\s*(.*)\s*$/) } split("\n", $self->option(shift) );
+}
+
+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;
+
+ # snarfs are unused at this point?
+ 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 ) {
+ no strict 'vars';
+ {
+ no strict 'refs';
+ foreach my $custf (qw( company address1 address2 city state zip country
+ daytime night fax otaker
+ ))
+ {
+ ${$custf} = $cust_pkg->cust_main->$custf();
+ }
+ }
+ $email = ( grep { $_ !~ /^(POST|FAX)$/ } $cust_pkg->cust_main->invoicing_list )[0];
+ } else {
+ $email = '';
+ }
+
+ $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
+ ($first, $last ) = ( $1, $2 );
+ $domain = $svc_acct->domain;
+
+ $quoted_password = shell_quote $_password;
+
+ $crypt_password = $svc_acct->crypt_password( $self->option('crypt') );
+ $ldap_password = $svc_acct->ldap_password( $self->option('crypt') );
+
+ @radius_groups = $svc_acct->radius_groups;
+
+ my ($reasonnum, $reasontext, $reasontypenum, $reasontypetext);
+ if ( $cust_pkg && $action eq 'suspend' &&
+ (my $r = $cust_pkg->last_reason('susp')) )
+ {
+ $reasonnum = $r->reasonnum;
+ $reasontext = $r->reason;
+ $reasontypenum = $r->reason_type;
+ $reasontypetext = $r->reasontype->type;
+
+ my %reasonmap = $self->_groups_susp_reason_map;
+ my $userspec = '';
+ $userspec = $reasonmap{$reasonnum}
+ if exists($reasonmap{$reasonnum});
+ $userspec = $reasonmap{$reasontext}
+ if (!$userspec && exists($reasonmap{$reasontext}));
+
+ my $suspend_user;
+ if ( $userspec =~ /^\d+$/ ) {
+ $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } );
+ } elsif ( $userspec =~ /^\S+\@\S+$/ ) {
+ my ($username,$domain) = split(/\@/, $userspec);
+ for my $user (qsearch( 'svc_acct', { 'username' => $username } )){
+ $suspend_user = $user if $userspec eq $user->email;
+ }
+ } elsif ($userspec) {
+ $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } );
+ }
+
+ @radius_groups = $suspend_user->radius_groups
+ if $suspend_user;
+
+ } else {
+ $reasonnum = $reasontext = $reasontypenum = $reasontypetext = '';
+ }
+
+ $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+ $custnum = $cust_pkg ? $cust_pkg->custnum : '';
+
+ my $stdin_string = eval(qq("$stdin"));
+
+ $first = shell_quote $first;
+ $last = shell_quote $last;
+ $finger = shell_quote $finger;
+ $crypt_password = shell_quote $crypt_password;
+ $ldap_password = shell_quote $ldap_password;
+
+ $company = shell_quote $company;
+ $address1 = shell_quote $address1;
+ $address2 = shell_quote $address2;
+ $city = shell_quote $city;
+ $state = shell_quote $state;
+ $zip = shell_quote $zip;
+ $country = shell_quote $country;
+ $daytime = shell_quote $daytime;
+ $night = shell_quote $night;
+ $fax = shell_quote $fax;
+ $otaker = shell_quote $otaker;
+
+ my $command_string = eval(qq("$command"));
+ my @ssh_cmd_args = (
+ user => $self->option('user') || 'root',
+ host => $self->machine,
+ command => $command_string,
+ stdin_string => $stdin_string,
+ );
+
+ if($self->option($action . '_no_queue')) {
+ # discard return value just like freeside-queued.
+ eval { ssh_cmd(@ssh_cmd_args) };
+ $error = $@;
+ return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
+ if $error;
+ }
+ else {
+ $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args );
+ }
+}
+
+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;
+ }
+ my $old_cust_pkg = $old->cust_svc->cust_pkg;
+ my $new_cust_pkg = $new->cust_svc->cust_pkg;
+ $new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/;
+ ($new_first, $new_last ) = ( $1, $2 );
+ $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;
+
+ $new_crypt_password = $new->crypt_password( $self->option('crypt') );
+ $new_ldap_password = $new->ldap_password( $self->option('crypt') );
+
+ @old_radius_groups = $old->radius_groups;
+ @new_radius_groups = $new->radius_groups;
+
+ my $error = '';
+ if ( $self->option('usermod_pwonly') || $self->option('usermod_nousername') ){
+ if ( $old_username ne $new_username ) {
+ $error ||= "can't change username";
+ }
+ }
+ if ( $self->option('usermod_pwonly') ) {
+ if ( $old_domain ne $new_domain ) {
+ $error ||= "can't change domain";
+ }
+ if ( $old_uid != $new_uid ) {
+ $error ||= "can't change uid";
+ }
+ if ( $old_gid != $new_gid ) {
+ $error ||= "can't change gid";
+ }
+ 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;
+
+ my $stdin_string = eval(qq("$stdin"));
+
+ $new_first = shell_quote $new_first;
+ $new_last = shell_quote $new_last;
+ $new_finger = shell_quote $new_finger;
+ $new_crypt_password = shell_quote $new_crypt_password;
+ $new_ldap_password = shell_quote $new_ldap_password;
+ $old_pkgnum = $old_cust_pkg ? $old_cust_pkg->pkgnum : '';
+ $old_custnum = $old_cust_pkg ? $old_cust_pkg->custnum : '';
+ $new_pkgnum = $new_cust_pkg ? $new_cust_pkg->pkgnum : '';
+ $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : '';
+
+ my $command_string = eval(qq("$command"));
+
+ my @ssh_cmd_args = (
+ user => $self->option('user') || 'root',
+ host => $self->machine,
+ command => $command_string,
+ stdin_string => $stdin_string,
+ );
+
+ if($self->option('usermod_no_queue')) {
+ # discard return value just like freeside-queued.
+ eval { ssh_cmd(@ssh_cmd_args) };
+ $error = $@;
+ return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
+ if $error;
+ }
+ else {
+ $self->shellcommands_queue( $new->svcnum, @ssh_cmd_args );
+ }
+}
+
+#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..d5a6187
--- /dev/null
+++ b/FS/FS/part_export/shellcommands_withdomain.pm
@@ -0,0 +1,138 @@
+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",
+ },
+ 'useradd_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
+ 'userdel' => { label=>'Delete command',
+ #default=>'',
+ },
+ 'userdel_stdin' => { label=>'Delete command STDIN',
+ type =>'textarea',
+ #default=>'',
+ },
+ 'userdel_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'',
+ },
+ 'usermod_stdin' => { label=>'Modify command STDIN',
+ type =>'textarea',
+ #default=>"$_password\n$_password\n",
+ },
+ 'usermod_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
+ 'usermod_pwonly' => { label=>'Disallow username, domain, uid, dir and RADIUS group changes',
+ type =>'checkbox',
+ },
+ 'usermod_nousername' => { label=>'Disallow just username changes',
+ type =>'checkbox',
+ },
+ 'suspend' => { label=>'Suspension command',
+ default=>'',
+ },
+ 'suspend_stdin' => { label=>'Suspension command STDIN',
+ default=>'',
+ },
+ 'suspend_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
+ 'unsuspend' => { label=>'Unsuspension command',
+ default=>'',
+ },
+ 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
+ default=>'',
+ },
+ 'unsuspend_no_queue' => { label => 'Run immediately',
+ type => 'checkbox',
+ },
+ 'crypt' => { label => 'Default password encryption',
+ type=>'select', options=>[qw(crypt md5)],
+ default => 'crypt',
+ },
+;
+
+%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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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;
+ '>
+ <LI><INPUT TYPE="button" VALUE="MagicMail" onClick='
+ this.form.useradd.value = "/usr/bin/mm_create_email_service -e $svcnum -d $domain -u $username -p $quoted_password -f $first -l $last -m $svcnum -g EMAIL";
+ this.form.useradd_stdin.value = "";
+ this.form.useradd_no_queue.checked = 1;
+ this.form.userdel.value = "/usr/bin/mm_delete_user -e ${username}\\\@${domain}";
+ this.form.userdel_stdin.value = "";
+ this.form.suspend.value = "/usr/bin/mm_suspend_user -e ${username}\\\@${domain}";
+ this.form.suspend_stdin.value = "";
+ this.form.unsuspend.value = "/usr/bin/mm_activate_user -e ${username}\\\@${domain}";
+ this.form.unsuspend_stdin.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>$_password</code>
+ <LI><code>$quoted_password</code> - unencrypted password, already quoted for the shell (do not add additional quotes)
+ <LI><code>$crypt_password</code> - encrypted password, already quoted for the shell (do not add additional quotes)
+ <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/snmp.pm b/FS/FS/part_export/snmp.pm
new file mode 100644
index 0000000..81b3c7e
--- /dev/null
+++ b/FS/FS/part_export/snmp.pm
@@ -0,0 +1,256 @@
+package FS::part_export::snmp;
+
+=head1 FS::part_export::snmp
+
+This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly.
+
+=head1 Required custom fields
+
+=over 4
+
+=item snmp_address - IP address (or hostname) of the router/agent
+
+=item snmp_comm - R/W SNMP community of the router/agent
+
+=item snmp_version - SNMP version of the router/agent
+
+=back
+
+=head1 Optional custom fields
+
+=over 4
+
+=item snmp_cmd_insert - SNMP SETs to perform on insert. See L</Formatting>
+
+=item snmp_cmd_replace - SNMP SETs to perform on replace. See L</Formatting>
+
+=item snmp_cmd_delete - SNMP SETs to perform on delete. See L</Formatting>
+
+=item snmp_cmd_suspend - SNMP SETs to perform on suspend. See L</Formatting>
+
+=item snmp_cmd_unsuspend - SNMP SETs to perform on unsuspend. See L</Formatting>
+
+=back
+
+=head1 Formatting
+
+The values for the snmp_cmd_* fields should be formatted as follows:
+
+<OID>|<Data Type>|<expr>[||<OID>|<Data Type>|<expr>[...]]
+
+=over 4
+
+=item OID - SNMP object ID (ex. 1.3.6.1.4.1.1.20). If the OID string starts with a '.', then the Private Enterprise OID (1.3.6.1.4.1) is prepended.
+
+=item Data Type - SNMP data types understood by L<Net::SNMP>, as well as HEX_STRING for convenience. ex. INTEGER, OCTET_STRING, IPADDRESS, ...
+
+=item expr - Expression to be eval'd by freeside. By default, the expression is double quoted and eval'd with all FS::svc_broadband fields available as scalars (ex. $svcnum, $ip_addr, $speed_up). However, if the expression contains a non-escaped double quote, the expression is eval'd without being double quoted. In this case, the expression must be a block of valid perl code that returns the desired value.
+
+You must escape non-delimiter pipes ("|") with a backslash.
+
+=back
+
+=head1 Examples
+
+This is an example for exporting to a Trango Access5830 AP. Newlines inserted for clarity.
+
+=over 4
+
+=item snmp_cmd_delete -
+
+1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
+1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1|
+
+=item snmp_cmd_insert -
+
+1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
+1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$radio_addr =~ /[0-9a-fA-F]{2}/g)||
+1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|
+
+=item snmp_cmd_replace -
+
+1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
+1.3.6.1.4.1.5454.1.20.3.5.8|INTEGER|1||1.3.6.1.4.1.5454.1.20.3.5.1|INTEGER|50||
+1.3.6.1.4.1.5454.1.20.3.5.2|HEX_STRING|join("",$new_radio_addr =~ /[0-9a-fA-F]{2}/g)||
+1.3.6.1.4.1.5454.1.20.3.5.7|INTEGER|1|
+
+=back
+
+=cut
+
+
+use strict;
+use vars qw(@ISA %info $me $DEBUG);
+use Tie::IxHash;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::part_export::router;
+
+@ISA = qw(FS::part_export::router);
+
+tie my %options, 'Tie::IxHash', ();
+
+%info = (
+ 'svc' => 'svc_broadband',
+ 'desc' => 'Sends SNMP SETs to an SNMP agent.',
+ 'options' => \%options,
+ 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::snmp for required virtual fields and usage information.',
+);
+
+$me= '[' . __PACKAGE__ . ']';
+$DEBUG = 1;
+
+
+sub _field_prefix { 'snmp'; }
+
+sub _req_router_fields {
+ map {
+ $_[0]->_field_prefix . '_' . $_
+ } (qw(address comm version));
+}
+
+sub _get_cmd_sub {
+
+ my ($self, $svc_broadband, $router) = (shift, shift, shift);
+
+ return(ref($self) . '::snmp_cmd');
+
+}
+
+sub _prepare_args {
+
+ my ($self, $action, $router) = (shift, shift, shift);
+ my ($svc_broadband) = shift;
+ my $old;
+ my $field_prefix = $self->_field_prefix;
+
+ if ($action eq 'replace') { $old = shift; }
+
+ my $raw_cmd = $router->getfield("${field_prefix}_cmd_${action}");
+ unless ($raw_cmd) {
+ warn "[debug]$me router custom field '${field_prefix}_cmd_$action' "
+ . "is not defined." if $DEBUG;
+ return '';
+ }
+
+ my $args = [
+ '-hostname' => $router->getfield($field_prefix.'_address'),
+ '-version' => $router->getfield($field_prefix.'_version'),
+ '-community' => $router->getfield($field_prefix.'_comm'),
+ ];
+
+ my @varbindlist = ();
+
+ foreach my $snmp_cmd ($raw_cmd =~ m/(.*?[^\\])(?:\|\||$)/g) {
+
+ warn "[debug]$me snmp_cmd is '$snmp_cmd'" if $DEBUG;
+
+ my ($oid, $type, $expr) = $snmp_cmd =~ m/(.*?[^\\])(?:\||$)/g;
+
+ if ($oid =~ /^([\d\.]+)$/) {
+ $oid = $1;
+ $oid = ($oid =~ /^\./) ? '1.3.6.1.4.1' . $oid : $oid;
+ } else {
+ return "Invalid SNMP OID '$oid'";
+ }
+
+ if ($type =~ /^([A-Z_\d]+)$/) {
+ $type = $1;
+ } else {
+ return "Invalid SNMP ASN.1 type '$type'";
+ }
+
+ if ($expr =~ /^(.*)$/) {
+ $expr = $1;
+ } else {
+ return "Invalid expression '$expr'";
+ }
+
+ {
+ no strict 'vars';
+ no strict 'refs';
+
+ if ($action eq 'replace') {
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
+ $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
+ } else {
+ ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
+ $expr = ($expr =~/[^\\]"/) ? eval($expr) : eval(qq("$expr"));
+ }
+ return $@ if $@;
+ }
+
+ push @varbindlist, ($oid, $type, $expr);
+
+ }
+
+ push @$args, ('-varbindlist', @varbindlist);
+
+ return('', $args);
+
+}
+
+sub snmp_cmd {
+ eval "use Net::SNMP;";
+ die $@ if $@;
+
+ my %args = ();
+ my @varbindlist = ();
+ while (scalar(@_)) {
+ my $key = shift;
+ if ($key eq '-varbindlist') {
+ push @varbindlist, @_;
+ last;
+ } else {
+ $args{$key} = shift;
+ }
+ }
+
+ my $i = 0;
+ while ($i*3 < scalar(@varbindlist)) {
+ my $type_index = ($i*3)+1;
+ my $type_name = $varbindlist[$type_index];
+
+ # Implementing HEX_STRING outselves since Net::SNMP doesn't. Ewwww!
+ if ($type_name eq 'HEX_STRING') {
+ my $value_index = $type_index + 1;
+ $type_name = 'OCTET_STRING';
+ $varbindlist[$value_index] = pack('H*', $varbindlist[$value_index]);
+ }
+
+ my $type = eval "Net::SNMP::$type_name";
+ if ($@ or not defined $type) {
+ warn $@ if $DEBUG;
+ die "snmp_cmd error: Unable to lookup type '$type_name'";
+ }
+
+ $varbindlist[$type_index] = $type;
+ } continue {
+ $i++;
+ }
+
+ my ($snmp, $error) = Net::SNMP->session(%args);
+ die "snmp_cmd error: $error" unless($snmp);
+
+ my $res = $snmp->set_request('-varbindlist' => \@varbindlist);
+ unless($res) {
+ $error = $snmp->error;
+ $snmp->close;
+ die "snmp_cmd error: " . $error;
+ }
+
+ $snmp->close;
+
+ return '';
+
+}
+
+
+=head1 BUGS
+
+Plenty, I'm sure.
+
+=cut
+
+1;
diff --git a/FS/FS/part_export/soma.pm b/FS/FS/part_export/soma.pm
new file mode 100644
index 0000000..c73d9f9
--- /dev/null
+++ b/FS/FS/part_export/soma.pm
@@ -0,0 +1,412 @@
+package FS::part_export::soma;
+
+use vars qw(@ISA %info %options $DEBUG);
+use Tie::IxHash;
+use FS::Record qw(fields dbh);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+$DEBUG = 1;
+
+tie %options, 'Tie::IxHash',
+ 'url' => { label => 'Soma OSS-API url', default=>'https://localhost:8088/ossapi/services' },
+ 'data_app_id' => { label => 'SOMA Data Application Id', default => '' },
+;
+
+my $notes = <<'EOT';
+Real-time export of <b>svc_external</b> and <b>svc_broadband</b> record data
+to SOMA Networks <a href="http://www.somanetworks.com">platform</a> via the
+OSS-API.<br><br>
+
+Freeside will attempt to create/delete a cpe for the ESN provided in
+svc_external. If a data application id is provided then freeside will
+use the values provided in svc_broadband to manage the attributes and
+features of that cpe.
+
+EOT
+
+%info = (
+ 'svc' => [ qw ( svc_broadband svc_external ) ],
+ 'desc' => 'Real-time export to SOMA platform',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => $notes,
+);
+
+sub _export_insert {
+ my( $self, $svc ) = ( shift, shift );
+
+ warn "_export_insert called for service ". $svc->svcnum
+ if $DEBUG;
+
+ my %args = ( url => $self->option('url'), method => '_queueable_insert' );
+
+ $args{esn} = $self->esn($svc) or return 'No ESN found!';
+
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+ $args{svcdb} = $svcdb;
+ if ( $svcdb eq 'svc_external' ) {
+ #do nothing
+ } elsif ( $svcdb eq 'svc_broadband' ){
+ $args{data_app_id} = $self->option('data_app_id')
+ } else {
+ return "Don't know how to provision $svcdb";
+ }
+
+ warn "dispatching statuschange" if $DEBUG;
+
+ eval { statuschange(%args) };
+ return $@ if $@;
+
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc ) = ( shift, shift );
+
+ my %args = ( url => $self->option('url'), method => '_queueable_delete' );
+
+ $args{esn} = $self->esn($svc) or return 'No ESN found!';
+
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+ $args{svcdb} = $svcdb;
+ if ( $svcdb eq 'svc_external' ) {
+ #do nothing
+ } elsif ( $svcdb eq 'svc_broadband' ){
+ $args{data_app_id} = $self->option('data_app_id')
+ } else {
+ return "Don't know how to provision $svcdb";
+ }
+
+ eval { statuschange(%args) };
+ return $@ if $@;
+
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = ( shift, shift, shift );
+
+ my %args = ( url => $self->option('url'), method => '_queueable_replace' );
+
+ $args{esn} = $self->esn($old) or return 'No old ESN found!';
+ $args{new_esn} = $self->esn($new) or return 'No new ESN found!';
+
+ my $svcdb = $old->cust_svc->part_svc->svcdb;
+ $args{svcdb} = $svcdb;
+ if ( $svcdb eq 'svc_external' ) {
+ #do nothing
+ } elsif ( $svcdb eq 'svc_broadband' ){
+ $args{data_app_id} = $self->option('data_app_id')
+ } else {
+ return "Don't know how to provision $svcdb";
+ }
+
+ eval { statuschange(%args) };
+ return $@ if $@;
+
+ '';
+}
+
+sub _export_suspend {
+ my( $self, $svc ) = ( shift, shift );
+
+ $self->queue_statuschange('_queueable_suspend', $svc);
+}
+
+sub _export_unsuspend {
+ my( $self, $svc ) = ( shift, shift );
+
+ $self->queue_statuschange('_queueable_unsuspend', $svc);
+}
+
+sub queue_statuschange {
+ my( $self, $method, $svc ) = @_;
+
+ my %args = ( url => $self->option('url'), method => $method );
+
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+ $args{svcdb} = $svcdb;
+ if ( $svcdb eq 'svc_external' ) {
+ #do absolutely nothing
+ return '';
+ } elsif ( $svcdb eq 'svc_broadband' ){
+ $args{data_app_id} = $self->option('data_app_id')
+ } else {
+ return "Don't know how to provision $svcdb";
+ }
+
+ $args{esn} = $self->esn($svc);
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svc->svcnum,
+ 'job' => 'FS::part_export::soma::statuschange',
+ };
+ my $error = $queue->insert( %args );
+
+ return $error if $error;
+
+ '';
+
+}
+
+sub statuschange { # subroutine
+ my( %options ) = @_;
+
+ warn "statuschange called with options ".
+ join (', ', map { "$_ => $options{$_}" } keys(%options))
+ if $DEBUG;
+
+ my $method = $options{method};
+
+ eval "use Net::Soma 0.01 qw(ApplicationDef ApplicationInstance
+ AttributeDef AttributeInstance);";
+ die $@ if $@;
+
+ my %soma_objects = ();
+ foreach my $service ( qw ( CPECollection CPEAccess AppCatalog Applications ) )
+ {
+ $soma_objects{$service} = new Net::Soma ( namespace => $service."Service",
+ url => $options{'url'},
+ die_on_fault => 1,
+ );
+ }
+
+ my $cpeid = eval {$soma_objects{CPECollection}->getCPEByESN( $options{esn} )};
+ warn "failed to find CPE with ESN $options{esn}"
+ if ($DEBUG && !$cpeid);
+
+ if ( $method eq '_queueable_insert' && $options{svcdb} eq 'svc_external' ) {
+ if ( !$cpeid ) {
+ # only type 1 is used at this time
+ $cpeid = $soma_objects{CPECollection}->createCPE( $options{esn}, 1 );
+ } else {
+ $soma_objects{CPECollection}->releaseCPE( $cpeid );
+ die "Soma element for $options{esn} already exists";
+ }
+ }
+
+ die "Can't find soma element for $options{esn}"
+ unless $cpeid;
+
+ warn "dispatching $method from statuschange" if $DEBUG;
+ &{$method}( \%soma_objects, $cpeid, %options );
+
+}
+
+sub _queueable_insert {
+ my( $soma_objects, $cpeid, %options ) = @_;
+
+ warn "_queueable_insert called for $cpeid with options ".
+ join (', ', map { "$_ => $options{$_}" } keys(%options))
+ if $DEBUG;
+
+ my $appid = $options{data_app_id};
+ if ($appid) {
+ my $application =
+ $soma_objects->{AppCatalog}
+ ->getDefaultApplicationInstance($appid, $cpeid);
+
+ my $attribute =
+ $soma_objects->{AppCatalog}
+ ->getDefaultApplicationAttributeInstance(2, 1, $cpeid);
+ $attribute->value('G');
+
+ my $i = 0;
+ foreach my $instance (@{$application->attributes}) {
+ unless ($instance->definitionId == $attribute->definitionId) {
+ $i++; next;
+ }
+ $application->attributes->[$i] = $attribute;
+ last;
+ }
+
+ $soma_objects->{Applications}->subscribeApp( $cpeid, $application );
+ }
+
+ $soma_objects->{CPECollection}->releaseCPE( $cpeid );
+
+ '';
+}
+
+sub _queueable_delete {
+ my( $soma_objects, $cpeid, %options ) = @_;
+
+ my $appid = $options{data_app_id};
+ my $norelease;
+
+ if ($appid) {
+ my $applications =
+ $soma_objects->{Applications}->getSubscribedApplications( $cpeid );
+
+ my $instance_id;
+ foreach $application (@$applications) {
+ next unless $application->definitionId == $appid;
+ $instance_id = $application->instanceId;
+ }
+
+ $soma_objects->{Applications}->unsubscribeApp( $cpeid, $instance_id );
+
+ } else {
+
+ $soma_objects->{CPECollection}->deleteCPE($cpeid);
+ $norelease = 1;
+
+ }
+
+ $soma_objects->{CPECollection}->releaseCPE( $cpeid ) unless $norelease;
+
+ '';
+}
+
+sub _queueable_replace {
+ my( $soma_objects, $cpeid, %options ) = @_;
+
+ my $appid = $options{data_app_id} || '';
+
+ if (exists($options{data_app_id})) {
+ my $applications =
+ $soma_objects->{Applications}->getSubscribedApplications( $cpeid );
+
+ my $instance_id;
+ foreach $application (@$applications) {
+ next unless $application->internalName eq 'dataApplication';
+ if ($application->definitionId != $options{data_app_id}) {
+ $instance_id = $application->instanceId;
+ $soma_objects->{Applications}->unsubscribeApp( $cpeid, $instance_id );
+ }
+ }
+
+ if ($appid && !$instance_id ) {
+ my $application =
+ $soma_objects->{AppCatalog}
+ ->getDefaultApplicationInstance($appid, $cpeid);
+
+ $soma_objects->{Applications}->subscribeApp( $cpeid, $application );
+ }
+
+ } else {
+
+ $soma_objects->{CPEAccess}->switchCPE($cpeid, $options{new_esn})
+ unless( $options{new_esn} eq $options{esn});
+
+ }
+
+ $soma_objects->{CPECollection}->releaseCPE( $cpeid );
+
+ '';
+}
+
+sub _queueable_suspend {
+ my( $soma_objects, $cpeid, %options ) = @_;
+
+ my $appid = $options{data_app_id};
+
+ if ($appid) {
+ my $applications =
+ $soma_objects->{Applications}->getSubscribedApplications( $cpeid );
+
+ my $instance_id;
+ foreach $application (@$applications) {
+ next unless $application->definitionId == $appid;
+
+ $instance_id = $application->instanceId;
+ my $app_def =
+ $soma_objects->{AppCatalog}->getApplicationDef($appid, $cpeid);
+ my @attr_def = grep { $_->internalName eq 'status' }
+ @{$app_def->attributes};
+
+ foreach my $attribute ( @{$application->attributes} ) {
+ next unless $attribute->definitionId == $attr_def[0]->definitionId;
+ $attribute->{value} = 'S';
+
+ $soma_objects->{Applications}->setAppAttribute( $cpeid,
+ $instance_id,
+ $attribute
+ );
+ }
+
+ }
+
+ } else {
+
+ #do nothing
+
+ }
+
+ $soma_objects->{CPECollection}->releaseCPE( $cpeid );
+
+ '';
+}
+
+sub _queueable_unsuspend {
+ my( $soma_objects, $cpeid, %options ) = @_;
+
+ my $appid = $options{data_app_id};
+
+ if ($appid) {
+ my $applications =
+ $soma_objects->{Applications}->getSubscribedApplications( $cpeid );
+
+ my $instance_id;
+ foreach $application (@$applications) {
+ next unless $application->definitionId == $appid;
+
+ $instance_id = $application->instanceId;
+ my $app_def =
+ $soma_objects->{AppCatalog}->getApplicationDef($appid, $cpeid);
+ my @attr_def = grep { $_->internalName eq 'status' }
+ @{$app_def->attributes};
+
+ foreach my $attribute ( @{$application->attributes} ) {
+ next unless $attribute->definitionId == $attr_def[0]->definitionId;
+ $attribute->{value} = 'E';
+
+ $soma_objects->{Applications}->setAppAttribute( $cpeid,
+ $instance_id,
+ $attribute
+ );
+ }
+
+ }
+
+ } else {
+
+ #do nothing
+
+ }
+
+ $soma_objects->{CPECollection}->releaseCPE( $cpeid );
+
+ '';
+}
+
+sub esn {
+ my ( $self, $svc ) = @_;
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+
+ if ($svcdb eq 'svc_external') {
+ my $esn = $svc->title;
+ $esn =~ /^\s*([\da-fA-F]{1,16})\s*$/ && ($esn = $1);
+ return sprintf( '%016s', $esn );
+ }
+
+ my $cust_pkg = $svc->cust_svc->cust_pkg;
+ return '' unless $cust_pkg;
+
+ my @cust_svc = grep { $_->part_svc->svcdb eq 'svc_external' &&
+ scalar( $_->part_svc->part_export('soma') )
+ }
+ $cust_pkg->cust_svc;
+ return '' unless scalar(@cust_svc);
+ warn "part_export::soma found multiple ESNs for cust_svc ". $svc->svcnum
+ if scalar( @cust_svc ) > 1;
+
+ my $esn = $cust_svc[0]->svc_x->title;
+ $esn =~ /^\s*([\da-fA-F]{1,16})\s*$/ && ($esn = $1);
+
+ sprintf( '%016s', $esn );
+}
+
+
+1;
diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm
new file mode 100644
index 0000000..cbdaf7f
--- /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 => 'srcsvc dstsvc dst' },
+ 'svc_domain_fields' => { label => 'svc_domain Export Fields',
+ default => 'domain svcnum catchall' },
+ '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..4f67ac6
--- /dev/null
+++ b/FS/FS/part_export/sqlradius.pm
@@ -0,0 +1,814 @@
+package FS::part_export::sqlradius;
+
+use vars qw(@ISA @EXPORT_OK $DEBUG %info %options $notes1 $notes2);
+use Exporter;
+use Tie::IxHash;
+use FS::Record qw( dbh qsearch qsearchs str2time_sql );
+use FS::part_export;
+use FS::svc_acct;
+use FS::export_svc;
+use Carp qw( cluck );
+
+@ISA = qw(FS::part_export);
+@EXPORT_OK = qw( sqlradius_connect );
+
+$DEBUG = 0;
+
+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'
+ },
+ 'hide_ip' => {
+ type => 'checkbox',
+ label => 'Hide IP address information on session reports',
+ },
+ 'hide_data' => {
+ type => 'checkbox',
+ label => 'Hide download/upload information on session reports',
+ },
+ 'show_called_station' => {
+ type => 'checkbox',
+ label => 'Show the Called-Station-ID on session reports',
+ },
+ 'overlimit_groups' => { label => 'Radius groups to assign to svc_acct which has exceeded its bandwidth or time limit (if not overridden by overlimit_groups global or per-agent config)', } ,
+ 'groups_susp_reason' => { label =>
+ 'Radius group mapping to reason (via template user) (svcnum|username|username@domain reasonnum|reason)',
+ type => 'textarea',
+ },
+
+;
+
+$notes1 = <<'END';
+Real-time export of <b>radcheck</b>, <b>radreply</b> and <b>usergroup</b>
+tables to any SQL database for
+<a href="http://www.freeradius.org/">FreeRADIUS</a>
+or <a href="http://radius.innercite.com/">ICRADIUS</a>.
+END
+
+$notes2 = <<'END';
+An existing RADIUS database will be updated in realtime, but you can use
+<a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Developer/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)',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => $notes1.
+ 'This export does not export RADIUS realms (see also '.
+ 'sqlradius_withdomain). '.
+ $notes2
+);
+
+sub _groups_susp_reason_map { map { reverse( /^\s*(\S+)\s*(.*)$/ ) }
+ split( "\n", shift->option('groups_susp_reason'));
+}
+
+sub rebless { shift; }
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ warn "export_username called on $self with arg $svc_acct" if $DEBUG > 1;
+ $svc_acct->username;
+}
+
+sub _export_insert {
+ my($self, $svc_x) = (shift, shift);
+
+ foreach my $table (qw(reply check)) {
+ my $method = "radius_$table";
+ my %attrib = $svc_x->$method();
+ next unless keys %attrib;
+ my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'insert',
+ $table, $self->export_username($svc_x), %attrib );
+ return $err_or_queue unless ref($err_or_queue);
+ }
+ my @groups = $svc_x->radius_groups;
+ if ( @groups ) {
+ cluck localtime(). ": queuing usergroup_insert for ". $svc_x->svcnum.
+ " (". $self->export_username($svc_x). " with ". join(", ", @groups)
+ if $DEBUG;
+ my $err_or_queue = $self->sqlradius_queue(
+ $svc_x->svcnum, 'usergroup_insert',
+ $self->export_username($svc_x), @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;
+ }
+ }
+ }
+ }
+
+ my $error;
+ my (@oldgroups) = $old->radius_groups;
+ my (@newgroups) = $new->radius_groups;
+ $error = $self->sqlreplace_usergroups( $new->svcnum,
+ $self->export_username($new),
+ $jobnum ? $jobnum : '',
+ \@oldgroups,
+ \@newgroups,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+
+ my $new = $svc_acct->clone_suspended;
+
+ 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->sqlradius_queue( $new->svcnum, 'insert',
+ 'check', $self->export_username($new), $new->radius_check );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+
+ my $error;
+ my (@newgroups) = $self->suspended_usergroups($svc_acct);
+ $error =
+ $self->sqlreplace_usergroups( $new->svcnum,
+ $self->export_username($new),
+ '',
+ $svc_acct->usergroup,
+ \@newgroups,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (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 $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
+ 'check', $self->export_username($svc_acct), $svc_acct->radius_check );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+
+ my $error;
+ my (@oldgroups) = $self->suspended_usergroups($svc_acct);
+ $error = $self->sqlreplace_usergroups( $svc_acct->svcnum,
+ $self->export_username($svc_acct),
+ '',
+ \@oldgroups,
+ $svc_acct->usergroup,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_x ) = (shift, shift);
+ my $err_or_queue = $self->sqlradius_queue( $svc_x->svcnum, 'delete',
+ $self->export_username($svc_x) );
+ 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 suspended_usergroups {
+ my ($self, $svc_acct) = (shift, shift);
+
+ return () unless $svc_acct;
+
+ #false laziness with FS::part_export::shellcommands
+ #subclass part_export?
+
+ my $r = $svc_acct->cust_svc->cust_pkg->last_reason('susp');
+ my %reasonmap = $self->_groups_susp_reason_map;
+ my $userspec = '';
+ if ($r) {
+ $userspec = $reasonmap{$r->reasonnum}
+ if exists($reasonmap{$r->reasonnum});
+ $userspec = $reasonmap{$r->reason}
+ if (!$userspec && exists($reasonmap{$r->reason}));
+ }
+ my $suspend_user;
+ if ($userspec =~ /^d+$/ ){
+ $suspend_user = qsearchs( 'svc_acct', { 'svcnum' => $userspec } );
+ }elsif ($userspec =~ /^\S+\@\S+$/){
+ my ($username,$domain) = split(/\@/, $userspec);
+ for my $user (qsearch( 'svc_acct', { 'username' => $username } )){
+ $suspend_user = $user if $userspec eq $user->email;
+ }
+ }elsif ($userspec){
+ $suspend_user = qsearchs( 'svc_acct', { 'username' => $userspec } );
+ }
+ #esalf
+ return $suspend_user->radius_groups if $suspend_user;
+ ();
+}
+
+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 eq 'Password' ? '==' : ':=' ),
+ $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 $s_sth = $dbh->prepare(
+ "SELECT COUNT(*) FROM usergroup WHERE UserName = ? AND GroupName = ?"
+ ) or die $dbh->errstr;
+
+ my $sth = $dbh->prepare(
+ "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )"
+ ) or die $dbh->errstr;
+
+ foreach my $group ( @groups ) {
+ $s_sth->execute( $username, $group ) or die $s_sth->errstr;
+ if ($s_sth->fetchrow_arrayref->[0]) {
+ warn localtime() . ": sqlradius_usergroup_insert attempted to reinsert " .
+ "$group for $username\n"
+ if $DEBUG;
+ next;
+ }
+ $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;
+}
+
+sub sqlreplace_usergroups {
+ my ($self, $svcnum, $username, $jobnum, $old, $new) = @_;
+
+ # (sorta) false laziness with FS::svc_acct::replace
+ my @oldgroups = @$old;
+ my @newgroups = @$new;
+ 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( $svcnum, 'usergroup_delete',
+ $username, @delgroups );
+ return $err_or_queue
+ unless ref($err_or_queue);
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ return $error if $error;
+ }
+ }
+
+ if ( @newgroups ) {
+ cluck localtime(). ": queuing usergroup_insert for $svcnum ($username) ".
+ "with ". join(", ", @newgroups)
+ if $DEBUG;
+ my $err_or_queue = $self->sqlradius_queue( $svcnum, 'usergroup_insert',
+ $username, @newgroups );
+ return $err_or_queue
+ unless ref($err_or_queue);
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ return $error if $error;
+ }
+ }
+ '';
+}
+
+
+#--
+
+=item usage_sessions HASHREF
+
+=item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ]
+
+New-style: pass a hashref with the following keys:
+
+=over 4
+
+=item stoptime_start - Lower bound for AcctStopTime, as a UNIX timestamp
+
+=item stoptime_end - Upper bound for AcctStopTime, as a UNIX timestamp
+
+=item open_sessions - Only show records with no AcctStopTime (typically used without stoptime_* options and with starttime_* options instead)
+
+=item starttime_start - Lower bound for AcctStartTime, as a UNIX timestamp
+
+=item starttime_end - Upper bound for AcctStartTime, as a UNIX timestamp
+
+=item svc_acct
+
+=item ip
+
+=item prefix
+
+=back
+
+Old-style:
+
+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.
+
+PREFIX, if specified, limits the results to records with a matching
+Called-Station-ID.
+
+#SQL_SELECT defaults to * if unspecified. It can be useful to set it to
+#SUM(acctsessiontime) or SUM(AcctInputOctets), etc.
+
+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
+
+=item calledstationid
+
+=back
+
+=cut
+
+#some false laziness w/cust_svc::seconds_since_sqlradacct
+
+sub usage_sessions {
+ my( $self ) = shift;
+
+ my $opt = {};
+ my($start, $end, $svc_acct, $ip, $prefix) = ( '', '', '', '', '');
+ if ( ref($_[0]) ) {
+ $opt = shift;
+ $start = $opt->{stoptime_start};
+ $end = $opt->{stoptime_end};
+ $svc_acct = $opt->{svc_acct};
+ $ip = $opt->{ip};
+ $prefix = $opt->{prefix};
+ } else {
+ ( $start, $end ) = splice(@_, 0, 2);
+ $svc_acct = @_ ? shift : '';
+ $ip = @_ ? shift : '';
+ $prefix = @_ ? 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 = str2time_sql( $dbh->{Driver}->{Name} );
+
+ my @fields = (
+ qw( username realm framedipaddress
+ acctsessiontime acctinputoctets acctoutputoctets
+ calledstationid
+ ),
+ "$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 =~ /^([^@]+)\@([^@]+)$/ ) {
+ push @where, '( UserName = ? OR ( UserName = ? AND Realm = ? ) )';
+ push @param, $username, $1, $2;
+ } else {
+ push @where, 'UserName = ?';
+ push @param, $username;
+ }
+ }
+
+ if ( length($ip) ) {
+ push @where, ' FramedIPAddress = ?';
+ push @param, $ip;
+ }
+
+ if ( length($prefix) ) {
+ #assume sip: for now, else things get ugly trying to match /^\w+:$prefix/
+ push @where, " CalledStationID LIKE 'sip:$prefix\%'";
+ }
+
+ if ( $start ) {
+ push @where, "$str2time AcctStopTime ) >= ?";
+ push @param, $start;
+ }
+ if ( $end ) {
+ push @where, "$str2time AcctStopTime ) <= ?";
+ push @param, $end;
+ }
+ if ( $opt->{open_sessions} ) {
+ push @where, 'AcctStopTime IS NULL';
+ }
+ if ( $opt->{starttime_start} ) {
+ push @where, "$str2time AcctStartTime ) >= ?";
+ push @param, $opt->{starttime_start};
+ }
+ if ( $opt->{starttime_end} ) {
+ push @where, "$str2time AcctStartTime ) <= ?";
+ push @param, $opt->{starttime_end};
+ }
+
+ my $where = join(' AND ', @where);
+ $where = "WHERE $where" if $where;
+
+ my $sth = $dbh->prepare('SELECT '. join(', ', @fields).
+ " FROM radacct
+ $where
+ ORDER BY AcctStartTime DESC
+ ") or die $dbh->errstr;
+ $sth->execute(@param) or die $sth->errstr;
+
+ [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ];
+
+}
+
+=item update_svc_acct
+
+=cut
+
+sub update_svc {
+ my $self = shift;
+
+ my $conf = new FS::Conf;
+
+ my $fdbh = dbh;
+ my $dbh = sqlradius_connect( map $self->option($_),
+ qw( datasrc username password ) );
+
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my @fields = qw( radacctid username realm acctsessiontime );
+
+ my @param = ();
+ my $where = '';
+
+ my $sth = $dbh->prepare("
+ SELECT RadAcctId, UserName, Realm, AcctSessionTime,
+ $str2time AcctStartTime), $str2time AcctStopTime),
+ AcctInputOctets, AcctOutputOctets
+ FROM radacct
+ WHERE FreesideStatus IS NULL
+ AND AcctStopTime != 0
+ ") or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_arrayref ) {
+ my($RadAcctId, $UserName, $Realm, $AcctSessionTime, $AcctStartTime,
+ $AcctStopTime, $AcctInputOctets, $AcctOutputOctets) = @$row;
+ warn "processing record: ".
+ "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s"
+ if $DEBUG;
+
+ $UserName = lc($UserName) unless $conf->exists('username-uppercase');
+
+ #my %search = ( 'username' => $UserName );
+
+ my $extra_sql = '';
+ if ( ref($self) =~ /withdomain/ ) { #well...
+ $extra_sql = " AND '$Realm' = ( SELECT domain FROM svc_domain
+ WHERE svc_domain.svcnum = svc_acct.domsvc ) ";
+ }
+
+ my $oldAutoCommit = $FS::UID::AutoCommit; # can't undo side effects, but at
+ local $FS::UID::AutoCommit = 0; # least we can avoid over counting
+
+ my @svc_acct =
+ grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum,
+ 'svcpart' => $_->cust_svc->svcpart, } )
+ }
+ qsearch( 'svc_acct',
+ { 'username' => $UserName },
+ '',
+ $extra_sql
+ );
+
+ my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ".
+ "(UserName $UserName, Realm $Realm)";
+ my $status = 'skipped';
+ if ( !@svc_acct ) {
+ warn "WARNING: no svc_acct record found $errinfo - skipping\n";
+ } elsif ( scalar(@svc_acct) > 1 ) {
+ warn "WARNING: multiple svc_acct records found $errinfo - skipping\n";
+ } else {
+
+ my $svc_acct = $svc_acct[0];
+ warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG;
+
+ $svc_acct->last_login($AcctStartTime);
+ $svc_acct->last_logout($AcctStopTime);
+
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ if ( $cust_pkg && $AcctStopTime < ( $cust_pkg->last_bill
+ || $cust_pkg->setup ) ) {
+ $status = 'skipped (too old)';
+ } else {
+ my @st;
+ push @st, _try_decrement($svc_acct, 'seconds', $AcctSessionTime );
+ push @st, _try_decrement($svc_acct, 'upbytes', $AcctInputOctets );
+ push @st, _try_decrement($svc_acct, 'downbytes', $AcctOutputOctets );
+ push @st, _try_decrement($svc_acct, 'totalbytes', $AcctInputOctets
+ + $AcctOutputOctets);
+ $status=join(' ', @st);
+ }
+ }
+
+ warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG;
+ my $psth = $dbh->prepare("UPDATE radacct
+ SET FreesideStatus = ?
+ WHERE RadAcctId = ?"
+ ) or die $dbh->errstr;
+ $psth->execute($status, $RadAcctId) or die $psth->errstr;
+
+ $fdbh->commit or die $fdbh->errstr if $oldAutoCommit;
+
+ }
+
+}
+
+sub _try_decrement {
+ my ($svc_acct, $column, $amount) = @_;
+ if ( $svc_acct->$column !~ /^$/ ) {
+ warn " svc_acct.$column found (". $svc_acct->$column.
+ ") - decrementing\n"
+ if $DEBUG;
+ my $method = 'decrement_' . $column;
+ my $error = $svc_acct->$method($amount);
+ die $error if $error;
+ return 'done';
+ } else {
+ warn " no existing $column value for svc_acct - skipping\n" if $DEBUG;
+ }
+ return 'skipped';
+}
+
+###
+#class methods
+###
+
+sub all_sqlradius {
+ #my $class = shift;
+
+ #don't just look for ->can('usage_sessions'), we're sqlradius-specific
+ # (radiator is supposed to be setup with a radacct table)
+ #i suppose it would be more slick to look for things that inherit from us..
+
+ my @part_export = ();
+ push @part_export, qsearch('part_export', { 'exporttype' => $_ } )
+ foreach qw( sqlradius sqlradius_withdomain radiator phone_sqlradius );
+ @part_export;
+}
+
+sub all_sqlradius_withaccounting {
+ my $class = shift;
+ grep { ! $_->option('ignore_accounting') } $class->all_sqlradius;
+}
+
+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..e5a7151
--- /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) 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..869c7c7
--- /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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">SSH is setup for unattended
+operation</a>.
+END
+);
+
+$prefix = "%%%FREESIDE_CONF%%%/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/thirdlane.pm b/FS/FS/part_export/thirdlane.pm
new file mode 100644
index 0000000..60c0997
--- /dev/null
+++ b/FS/FS/part_export/thirdlane.pm
@@ -0,0 +1,348 @@
+package FS::part_export::thirdlane;
+
+use base qw( FS::part_export );
+
+use vars qw(%info $me);
+use Tie::IxHash;
+use URI::Escape;
+use Frontier::Client;
+
+$me = '['.__PACKAGE__.']';
+
+tie my %options, 'Tie::IxHash',
+ #'server' => { label => 'Thirdlane server name or IP address', },
+ 'username' => { label => 'Thirdlane username', },
+ 'password' => { label => 'Thirdlane password', },
+ 'ssl' => { label => 'Enable HTTPS (SSL) connection',
+ type => 'checkbox',
+ },
+ 'port' => { label => 'Port number if not 80 or 443', },
+ 'prototype_tenant' => { label => 'Prototype tenant name', },
+ 'omit_countrycode' => { label => 'Omit country code', type => 'checkbox' },
+ 'debug' => { label => 'Checkbox label', type => 'checkbox' },
+# '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.',
+# },
+;
+
+%info = (
+ 'svc' => [qw( svc_pbx svc_phone svc_acct )],
+ 'desc' =>
+ 'Export tenants, DIDs and admins to Thirdlane PBX manager',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Exports tenants, DIDs and admins to Thirdlane PBX manager using the XML-RPC API.
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_x) = (shift, shift);
+
+ if ( $svc_x->isa('FS::svc_pbx') ) {
+
+ return 'Name must be 19 characters or less (thirdlane restriction?)'
+ if length($svc_x->title) > 19;
+
+ return 'Name must consist of alphanumerics and spaces only (thirdlane restriction?)'
+ unless $svc_x->title =~ /^[\w\s]+$/;
+
+ my $tenant = {
+ 'tenant' => $svc_x->title,
+ 'maxusers' => $svc_x->max_extensions,
+ #others? will they not clone?
+ };
+
+ @what_to_clone = qw(routes schedules menus queues voiceprompts moh);
+
+ my $result = $self->_thirdlane_command( 'asterisk::rpc_tenant_create',
+ $tenant,
+ $self->option('prototype_tenant'),
+ \@what_to_clone,
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ $result eq '0' ? '' : 'Thirdlane API failure (rpc_tenant_create)';
+
+ } elsif ( $svc_x->isa('FS::svc_phone') ) {
+
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_create',
+ $self->_thirdlane_did($svc_x)
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ $result eq '0' or return 'Thirdlane API failure (rpc_did_create)';
+
+ return '' unless $svc_x->pbxsvc;
+
+ $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_assign',
+ $self->_thirdlane_did($svc_x),
+ $svc_x->pbx_title,
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ $result eq '0' ? '' : 'Thirdlane API failure (rpc_did_assign)';
+
+ } elsif ( $svc_x->isa('FS::svc_acct') ) {
+
+ return 'Must select a PBX' unless $svc_x->pbxsvc;
+
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_admin_create',
+ $svc_x->username,
+ $svc_x->_password,
+ $svc_x->pbx_title,
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ $result eq '0' ? '' : 'Thirdlane API failure (rpc_admin_create)';
+
+ } else {
+ die "guru meditation #10: $svc_x is not FS::svc_pbx, FS::svc_phone or FS::svc_acct";
+ }
+
+}
+
+sub _export_replace {
+ my($self, $new, $old) = (shift, shift, shift);
+
+# #return "can't change username with thirdlane"
+# # if $old->username ne $new->username;
+# #return '' unless $old->_password ne $new->_password;
+# $err_or_queue = $self->thirdlane_queue( $new->svcnum,
+# 'replace', $new->username, $new->_password );
+# ref($err_or_queue) ? '' : $err_or_queue;
+
+ if ( $new->isa('FS::svc_pbx') ) {
+
+ #need more info on how the API works for changing names.. can it?
+ return "can't change PBX name with thirdlane (yet?)"
+ if $old->title ne $new->title;
+
+ my $tenant = {
+ 'tenant' => $old->title,
+ 'maxusers' => $new->max_extensions,
+ #others? will they not clone?
+ };
+
+ my $result = $self->_thirdlane_command( 'asterisk::rpc_tenant_update',
+ $tenant
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ $result eq '0' ? '' : 'Thirdlane API failure (rpc_tenant_update)';
+
+ } elsif ( $new->isa('FS::svc_phone') ) {
+
+ return "can't change DID countrycode with thirdlane"
+ if $old->countrycode ne $new->countrycode;
+ return "can't change DID number with thirdlane"
+ if $old->phonenum ne $new->phonenum;
+
+ if ( $old->pbxsvc != $new->pbxsvc ) {
+
+ if ( $old->pbxsvc ) {
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_unassign',
+ $self->_thirdlane_did($old),
+ );
+ $result eq '0' or return 'Thirdlane API failure (rpc_did_unassign)';
+ }
+
+ if ( $new->pbxsvc ) {
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_assign',
+ $self->_thirdlane_did($new),
+ $new->pbx_title,
+ );
+ $result eq '0' or return 'Thirdlane API failure (rpc_did_assign)';
+ }
+
+
+ }
+
+ '';
+
+ } elsif ( $new->isa('FS::svc_acct') ) {
+
+ return "can't change uesrname with thirdlane"
+ if $old->username ne $new->username;
+
+ return "can't change password with thirdlane"
+ if $old->_password ne $new->_password;
+
+ return "can't change PBX for user with thirdlane"
+ if $old->pbxsvc != $new->pbxsvc;
+
+ ''; #we don't care then
+
+ } else {
+ die "guru meditation #11: $new is not FS::svc_pbx, FS::svc_phone or FS::svc_acct";
+ }
+
+}
+
+sub _export_delete {
+ my($self, $svc_x) = (shift, shift);
+ #my( $self, $svc_something ) = (shift, shift);
+ #$err_or_queue = $self->thirdlane_queue( $svc_something->svcnum,
+ # 'delete', $svc_something->username );
+ #ref($err_or_queue) ? '' : $err_or_queue;
+
+ if ( $svc_x->isa('FS::svc_pbx') ) {
+
+ my $result = $self->_thirdlane_command( 'asterisk::rpc_tenant_delete',
+ $svc_x->title,
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ #$result eq '0' ? '' : 'Thirdlane API failure (rpc_tenant_delete)';
+ warn "Thirdlane API failure (rpc_tenant_delete); deleting anyway\n"
+ if $result ne '0';
+ '';
+
+ } elsif ( $svc_x->isa('FS::svc_phone') ) {
+
+ if ( $svc_x->pbxsvc ) {
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_unassign',
+ $self->_thirdlane_did($svc_x),
+ );
+ $result eq '0' or return 'Thirdlane API failure (rpc_did_unassign)';
+ }
+
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_did_delete',
+ $self->_thirdlane_did($svc_x),
+ );
+ $result eq '0' ? '' : 'Thirdlane API failure (rpc_did_delete)';
+
+ } elsif ( $svc_x->isa('FS::svc_acct') ) {
+
+ return '' unless $svc_x->pbxsvc; #error out? nah
+
+ my $result = $self->_thirdlane_command(
+ 'asterisk::rpc_admin_delete',
+ $svc_x->username,
+ $svc_x->pbx_title,
+ );
+
+ #use Data::Dumper;
+ #warn Dumper(\$result);
+ #$result eq '0' ? '' : 'Thirdlane API failure (rpc_admin_delete)';
+ warn "Thirdlane API failure (rpc_admin_delete); deleting anyway\n"
+ if $result ne '0';
+ '';
+
+ } else {
+ die "guru meditation #12: $svc_x is not FS::svc_pbx, FS::svc_phone or FS::svc_acct";
+ }
+
+}
+
+sub _thirdlane_command {
+ my($self, @param) = @_;
+
+ my $url = $self->option('ssl') ? 'https://' : 'http://';
+ $url .= uri_escape($self->option('username')). ':'.
+ uri_escape($self->option('password')). '@'.
+ $self->machine;
+ $url .= ':'. $self->option('port') if $self->option('port');
+ $url .= '/xmlrpc.cgi';
+
+ warn "$me connecting to $url\n"
+ if $self->option('debug');
+ my $conn = Frontier::Client->new( 'url' => $url,
+ #no, spews output to browser
+ #'debug' => $self->option('debug'),
+ );
+
+ warn "$me sending command: ". join(' ', @param). "\n"
+ if $self->option('debug');
+ $conn->call(@param);
+
+}
+
+sub _thirdlane_did {
+ my($self, $svc_phone) = @_;
+ if ( $self->option('omit_countrycode') ) {
+ $svc_phone->phonenum;
+ } else {
+ $svc_phone->countrycode. $svc_phone->phonenum;
+ }
+}
+
+ #my( $self, $svc_something ) = (shift, shift);
+ #$err_or_queue = $self->thirdlane_queue( $svc_something->svcnum,
+ # 'delete', $svc_something->username );
+ #ref($err_or_queue) ? '' : $err_or_queue;
+
+#these three are optional
+## fallback for svc_acct will change and restore password
+#sub _export_suspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->thirdlane_queue( $svc_something->svcnum,
+# 'suspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+#
+#sub _export_unsuspend {
+# my( $self, $svc_something ) = (shift, shift);
+# $err_or_queue = $self->thirdlane_queue( $svc_something->svcnum,
+# 'unsuspend', $svc_something->username );
+# ref($err_or_queue) ? '' : $err_or_queue;
+#}
+#
+#sub export_links {
+# my($self, $svc_something, $arrayref) = (shift, shift, shift);
+# #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_something->username.
+# # qq!">!. $svc_something->username. qq!</A>!;
+# '';
+#}
+
+####
+#
+##a good idea to queue anything that could fail or take any time
+#sub thirdlane_queue {
+# my( $self, $svcnum, $method ) = (shift, shift, shift);
+# my $queue = new FS::queue {
+# 'svcnum' => $svcnum,
+# 'job' => "FS::part_export::thirdlane::thirdlane_$method",
+# };
+# $queue->insert( @_ ) or $queue;
+#}
+#
+#sub thirdlane_insert { #subroutine, not method
+# my( $username, $password ) = @_;
+# #do things with $username and $password
+#}
+#
+#sub thirdlane_replace { #subroutine, not method
+#}
+#
+#sub thirdlane_delete { #subroutine, not method
+# my( $username ) = @_;
+# #do things with $username
+#}
+#
+#sub thirdlane_suspend { #subroutine, not method
+#}
+#
+#sub thirdlane_unsuspend { #subroutine, not method
+#}
+
+1;
diff --git a/FS/FS/part_export/trango.pm b/FS/FS/part_export/trango.pm
new file mode 100644
index 0000000..e7f1126
--- /dev/null
+++ b/FS/FS/part_export/trango.pm
@@ -0,0 +1,434 @@
+package FS::part_export::trango;
+
+=head1 FS::part_export::trango
+
+This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly.
+
+=head1 Required custom fields
+
+=over 4
+
+=item trango_address - IP address (or hostname) of the Trango AP.
+
+=item trango_comm - R/W SNMP community of the Trango AP.
+
+=item trango_ap_type - Trango AP Model. Currently 'access5830' is the only supported option.
+
+=back
+
+=head1 Optional custom fields
+
+=over 4
+
+=item trango_baseid - Base ID of the Trango AP. See L</"Generating SU IDs">.
+
+=item trango_apid - AP ID of the Trango AP. See L</"Generating SU IDs">.
+
+=back
+
+=head1 Generating SU IDs
+
+This export will/must generate a unique SU ID for each service exported to a Trango AP. It can be done such that SU IDs are globally unique, unique per Base ID, or unique per Base ID/AP ID pair. This is accomplished by setting neither trango_baseid and trango_apid, only trango_baseid, or both trango_baseid and trango_apid, respectively. An SU ID will be generated if the FS::svc_broadband virtual field specified by suid_field export option is unset, otherwise the existing value will be used.
+
+=head1 Device Support
+
+This export has been tested with the Trango Access5830 AP.
+
+
+=cut
+
+
+use strict;
+use vars qw(@ISA %info $me $DEBUG $trango_mib $counter_dir);
+
+use FS::UID qw(dbh datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export::snmp;
+
+use Tie::IxHash;
+use File::CounterFile;
+use Data::Dumper qw(Dumper);
+
+@ISA = qw(FS::part_export::snmp);
+
+tie my %options, 'Tie::IxHash', (
+ 'suid_field' => {
+ 'label' => 'Trango SU ID field',
+ 'default' => 'trango_suid',
+ 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU ID.',
+ },
+ 'mac_field' => {
+ 'label' => 'Trango MAC address field',
+ 'default' => '',
+ 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU\'s MAC address.',
+ },
+);
+
+%info = (
+ 'svc' => 'svc_broadband',
+ 'desc' => 'Sends SNMP SETs to a Trango AP.',
+ 'options' => \%options,
+ 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.',
+);
+
+$me= '[' . __PACKAGE__ . ']';
+$DEBUG = 1;
+
+$trango_mib = {
+ 'access5830' => {
+ 'snmpversion' => 'snmpv1',
+ 'varbinds' => {
+ 'insert' => [
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbAddMac
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
+ 'type' => 'HEX_STRING',
+ 'value' => \&_trango_access5830_sudbAddMac,
+ },
+ { # sudbAddSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ ],
+ 'delete' => [
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbDeleteSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ ],
+ 'replace' => [
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbDeleteSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbAddMac
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
+ 'type' => 'HEX_STRING',
+ 'value' => \&_trango_access5830_sudbAddMac,
+ },
+ { # sudbAddSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ ],
+ 'suspend' => [
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbDeleteSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ ],
+ 'unsuspend' => [
+ { # sudbDeleteOrAddID
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
+ 'type' => 'INTEGER',
+ 'value' => \&_trango_access5830_sudbDeleteOrAddId,
+ },
+ { # sudbAddMac
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
+ 'type' => 'HEX_STRING',
+ 'value' => \&_trango_access5830_sudbAddMac,
+ },
+ { # sudbAddSU
+ 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
+ 'type' => 'INTEGER',
+ 'value' => 1,
+ },
+ ],
+ },
+ },
+};
+
+
+sub _field_prefix { 'trango'; }
+
+sub _req_router_fields {
+ map {
+ $_[0]->_field_prefix . '_' . $_
+ } (qw(address comm ap_type suid_field));
+}
+
+sub _get_cmd_sub {
+
+ return('FS::part_export::snmp::snmp_cmd');
+
+}
+
+sub _prepare_args {
+
+ my ($self, $action, $router) = (shift, shift, shift);
+ my ($svc_broadband) = shift;
+ my $old = shift if $action eq 'replace';
+ my $field_prefix = $self->_field_prefix;
+ my $error;
+
+ my $ap_type = $router->getfield($field_prefix . '_ap_type');
+
+ unless (exists $trango_mib->{$ap_type}) {
+ return "Unsupported Trango AP type '$ap_type'";
+ }
+
+ $error = $self->_check_suid(
+ $action, $router, $svc_broadband, ($old) ? $old : ()
+ );
+ return $error if $error;
+
+ $error = $self->_check_mac(
+ $action, $router, $svc_broadband, ($old) ? $old : ()
+ );
+ return $error if $error;
+
+ my $ap_mib = $trango_mib->{$ap_type};
+
+ my $args = [
+ '-hostname' => $router->getfield($field_prefix.'_address'),
+ '-version' => $ap_mib->{'snmpversion'},
+ '-community' => $router->getfield($field_prefix.'_comm'),
+ ];
+
+ my @varbindlist = ();
+
+ foreach my $oid (@{$ap_mib->{'varbinds'}->{$action}}) {
+ warn "[debug]$me Processing OID '" . $oid->{'oid'} . "'" if $DEBUG;
+ my $value;
+ if (ref($oid->{'value'}) eq 'CODE') {
+ eval {
+ $value = &{$oid->{'value'}}(
+ $self, $action, $router, $svc_broadband,
+ (($old) ? $old : ()),
+ );
+ };
+ return "While processing OID '" . $oid->{'oid'} . "':" . $@
+ if $@;
+ } else {
+ $value = $oid->{'value'};
+ }
+
+ warn "[debug]$me Value for OID '" . $oid->{'oid'} . "': " if $DEBUG;
+
+ if (defined $value) { # Skip OIDs with undefined values.
+ push @varbindlist, ($oid->{'oid'}, $oid->{'type'}, $value);
+ }
+ }
+
+
+ push @$args, ('-varbindlist', @varbindlist);
+
+ return('', $args);
+
+}
+
+sub _check_suid {
+
+ my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
+ my $old = shift if $action eq 'replace';
+ my $error;
+
+ my $suid_field = $self->option('suid_field');
+ unless (grep {$_ eq $suid_field} $svc_broadband->fields) {
+ return "Missing Trango SU ID field. "
+ . "See the trango export options for more info.";
+ }
+
+ my $suid = $svc_broadband->getfield($suid_field);
+ if ($action eq 'replace') {
+ my $old_suid = $old->getfield($suid_field);
+
+ if ($old_suid ne '' and $old_suid ne $suid) {
+ return 'Cannot change Trango SU ID';
+ }
+ }
+
+ if (not $suid =~ /^\d+$/ and $action ne 'delete') {
+ my $new_suid = eval { $self->_get_next_suid($router); };
+ return "Error while getting next Trango SU ID: $@" if ($@);
+
+ warn "[debug]$me Got new SU ID: $new_suid" if $DEBUG;
+ $svc_broadband->set($suid_field, $new_suid);
+
+ #FIXME: Probably a bad hack.
+ # We need to update the SU ID field in the database.
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::svc_Common::noexport_hack = 1;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $svcnum = $svc_broadband->svcnum;
+
+ my $old_svc = qsearchs('svc_broadband', { svcnum => $svcnum });
+ unless ($old_svc) {
+ return "Unable to retrieve svc_broadband with svcnum '$svcnum";
+ }
+
+ my $svcpart = $svc_broadband->svcpart
+ ? $svc_broadband->svcpart
+ : $svc_broadband->cust_svc->svcpart;
+
+ my $new_svc = new FS::svc_broadband {
+ $old_svc->hash,
+ $suid_field => $new_suid,
+ svcpart => $svcpart,
+ };
+
+ $error = $new_svc->check;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error while updating the Trango SU ID: $error" if $error;
+ }
+
+ warn "[debug]$me Updating svc_broadband with SU ID '$new_suid'...\n" .
+ &Dumper($new_svc) if $DEBUG;
+
+ $error = eval { $new_svc->replace($old_svc); };
+
+ if ($@ or $error) {
+ $error ||= $@;
+ $dbh->rollback if $oldAutoCommit;
+ return "Error while updating the Trango SU ID: $error" if $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ }
+
+ return '';
+
+}
+
+sub _check_mac {
+
+ my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
+ my $old = shift if $action eq 'replace';
+
+ my $mac_field = $self->option('mac_field');
+ unless (grep {$_ eq $mac_field} $svc_broadband->fields) {
+ return "Missing Trango MAC address field. "
+ . "See the trango export options for more info.";
+ }
+
+ my $mac_addr = $svc_broadband->getfield($mac_field);
+ unless (length(join('', $mac_addr =~ /[0-9a-fA-F]/g)) == 12) {
+ return "Invalid Trango MAC address: $mac_addr";
+ }
+
+ return('');
+
+}
+
+sub _get_next_suid {
+
+ my ($self, $router) = (shift, shift);
+
+ my $counter_dir = '/usr/local/etc/freeside/export.'. datasrc . '/trango';
+ my $baseid = $router->getfield('trango_baseid');
+ my $apid = $router->getfield('trango_apid');
+
+ my $counter_file_suffix = '';
+ if ($baseid ne '') {
+ $counter_file_suffix .= "_B$baseid";
+ if ($apid ne '') {
+ $counter_file_suffix .= "_A$apid";
+ }
+ }
+
+ my $counter_file = $counter_dir . '/SUID' . $counter_file_suffix;
+
+ warn "[debug]$me Using SUID counter file '$counter_file'";
+
+ my $suid = eval {
+ mkdir $counter_dir, 0700 unless -d $counter_dir;
+
+ my $cf = new File::CounterFile($counter_file, 0);
+ $cf->inc;
+ };
+
+ die "Error generating next Trango SU ID: $@" if (not $suid or $@);
+
+ return($suid);
+
+}
+
+
+
+# Trango-specific subroutines for generating varbind values.
+#
+# All subs should die on error, and return undef to decline. OIDs that
+# decline will not be added to varbinds.
+
+sub _trango_access5830_sudbDeleteOrAddId {
+
+ my ($self, $action, $router) = (shift, shift, shift);
+ my ($svc_broadband) = shift;
+ my $old = shift if $action eq 'replace';
+
+ my $suid = $svc_broadband->getfield($self->option('suid_field'));
+
+ # Sanity check.
+ unless ($suid =~ /^\d+$/) {
+ if ($action eq 'delete') {
+ # Silently ignore. If we don't have a valid SU ID now, we probably
+ # never did.
+ return undef;
+ } else {
+ die "Invalid Trango SU ID '$suid'";
+ }
+ }
+
+ return ($suid);
+
+}
+
+sub _trango_access5830_sudbAddMac {
+
+ my ($self, $action, $router) = (shift, shift, shift);
+ my ($svc_broadband) = shift;
+ my $old = shift if $action eq 'replace';
+
+ my $mac_addr = $svc_broadband->getfield($self->option('mac_field'));
+ $mac_addr = join('', $mac_addr =~ /[0-9a-fA-F]/g);
+
+ # Sanity check.
+ die "Invalid Trango MAC address '$mac_addr'" unless (length($mac_addr)==12);
+
+ return($mac_addr);
+
+}
+
+
+=head1 BUGS
+
+Plenty, I'm sure.
+
+=cut
+
+
+1;
diff --git a/FS/FS/part_export/vitelity.pm b/FS/FS/part_export/vitelity.pm
new file mode 100644
index 0000000..bec3837
--- /dev/null
+++ b/FS/FS/part_export/vitelity.pm
@@ -0,0 +1,239 @@
+package FS::part_export::vitelity;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::Record qw(qsearch dbh);
+use FS::part_export;
+use FS::phone_avail;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'login' => { label=>'Vitelity API login' },
+ 'pass' => { label=>'Vitelity API password' },
+ 'dry_run' => { label=>"Test mode - don't actually provision" },
+;
+
+%info = (
+ 'svc' => 'svc_phone',
+ 'desc' => 'Provision phone numbers to Vitelity',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-Vitelity">Net::Vitelity</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+sub get_dids {
+ my $self = shift;
+ my %opt = ref($_[0]) ? %{$_[0]} : @_;
+
+ my %search = ();
+ # 'orderby' => 'npa', #but it doesn't seem to work :/
+
+ my $method = '';
+
+ if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
+
+ return [
+ map { join('-', $_->npx, $_->nxx, $_->station ) }
+ qsearch({
+ 'table' => 'phone_avail',
+ 'hashref' => { 'exportnum' => $self->exportnum,
+ 'countrycode' => '1',
+ 'state' => $opt{'state'},
+ 'npa' => $opt{'areacode'},
+ 'nxx' => $opt{'exchange'},
+ },
+ 'order_by' => 'ORDER BY name', #?
+ })
+ ];
+
+ } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
+
+ return [
+ map { $_->name. ' ('. $_->npa. '-'. $_->nxx. '-XXXX)' }
+ qsearch({
+ 'select' => 'DISTINCT ON ( name, npa, nxx ) *',
+ 'table' => 'phone_avail',
+ 'hashref' => { 'exportnum' => $self->exportnum,
+ 'countrycode' => '1',
+ 'state' => $opt{'state'},
+ 'npa' => $opt{'areacode'},
+ },
+ 'order_by' => 'ORDER BY name', #?
+ })
+ ];
+
+ } elsif ( $opt{'state'} ) { #and not other things, then return areacode
+
+ #XXX need to flush the cache at some point :/
+
+ my @avail = qsearch({
+ 'select' => 'DISTINCT npa',
+ 'table' => 'phone_avail',
+ 'hashref' => { 'exportnum' => $self->exportnum,
+ 'countrycode' => '1', #don't hardcode me when gp goes intl
+ 'state' => $opt{'state'},
+ },
+ 'order_by' => 'ORDER BY npa',
+ });
+
+ return [ map $_->npa, @avail ] if @avail; #return cached area codes instead
+
+ #otherwise, search for em
+
+ my @ratecenters = $self->vitelity_command( 'listavailratecenters',
+ 'state' => $opt{'state'},
+ );
+
+ if ( $ratecenters[0] eq 'unavailable' ) {
+ return [];
+ } elsif ( $ratecenters[0] eq 'missingdata' ) {
+ die "missingdata error running Vitelity API"; #die?
+ }
+
+ 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 $errmsg = 'WARNING: error populating phone availability cache: ';
+
+ my %npa = ();
+ foreach my $ratecenter (@ratecenters) {
+
+ my @dids = $self->vitelity_command( 'listlocal',
+ 'state' => $opt{'state'},
+ 'ratecenter' => $ratecenter,
+ );
+
+ if ( $dids[0] eq 'unavailable' ) {
+ next;
+ } elsif ( $dids[0] eq 'missingdata' ) {
+ die "missingdata error running Vitelity API"; #die?
+ }
+
+ foreach my $did ( @dids ) {
+ $did =~ /^(\d{3})(\d{3})(\d{4})$/ or die "unparsable did $did\n";
+ my($npa, $nxx, $station) = ($1, $2, $3);
+ $npa{$npa}++;
+
+ my $phone_avail = new FS::phone_avail {
+ 'exportnum' => $self->exportnum,
+ 'countrycode' => '1', #don't hardcode me when vitelity goes int'l
+ 'state' => $opt{'state'},
+ 'npa' => $npa,
+ 'nxx' => $nxx,
+ 'station' => $station,
+ 'name' => $ratecenter,
+ };
+
+ $error = $phone_avail->insert();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die $errmsg.$error;
+ }
+
+ }
+
+ }
+
+ $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
+
+ my @return = sort { $a <=> $b } keys %npa;
+ #@return = sort { (split(' ', $a))[0] <=> (split(' ', $b))[0] } @return;
+
+ return \@return;
+
+ } else {
+ die "get_dids called without state or areacode options";
+ }
+
+}
+
+sub vitelity_command {
+ my( $self, $command, @args ) = @_;
+
+ eval "use Net::Vitelity;";
+ die $@ if $@;
+
+ my $vitelity = Net::Vitelity->new(
+ 'login' => $self->option('login'),
+ 'pass' => $self->option('pass'),
+ #'debug' => $debug,
+ );
+
+ $vitelity->$command(@args);
+}
+
+sub _export_insert {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ return '' if $self->option('dry_run');
+
+ #we want to provision and catch errors now, not queue
+
+ my $result = $self->vitelity_command('getlocaldid',
+ 'did' => $svc_phone->phonenum,
+#XXX
+#Options: type=perminute OR type=unlimited OR type=your-pri OR
+# routesip=route_to_this_subaccount
+ );
+
+ if ( $result ne 'success' ) {
+ return "Error running Vitelity getlocaldid: $result";
+ }
+
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ #hmm, what's to change?
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_phone ) = (shift, shift);
+
+ return '' if $self->option('dry_run');
+
+ #probably okay to queue the deletion...?
+ #but hell, let's do it inline anyway, who wants phone numbers hanging around
+
+ my $result = $self->vitelity_command('removedid',
+ 'did' => $svc_phone->phonenum,
+ );
+
+ if ( $result ne 'success' ) {
+ return "Error running Vitelity getlocaldid: $result";
+ }
+
+ '';
+}
+
+sub _export_suspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #nop for now
+ '';
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_phone ) = (shift, shift);
+ #nop for now
+ '';
+}
+
+1;
+
diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm
new file mode 100644
index 0000000..799a8e1
--- /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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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 = "%%%FREESIDE_EXPORT%%%/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_plesk.pm b/FS/FS/part_export/www_plesk.pm
new file mode 100644
index 0000000..ccf9b3e
--- /dev/null
+++ b/FS/FS/part_export/www_plesk.pm
@@ -0,0 +1,138 @@
+package FS::part_export::www_plesk;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'URL' => { label=>'URL' },
+ 'login' => { label=>'Login' },
+ 'password' => { label=>'Password' },
+ 'template' => { label=>'Domain Template' },
+ 'web' => { label=>'Host Website',
+ type=>'checkbox' },
+ 'debug' => { label=>'Enable debugging',
+ type=>'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_www',
+ 'desc' => 'Real-time export to Plesk managed hosting service',
+ 'options'=> \%options,
+ 'notes' => <<'END'
+Real-time export to
+<a href="http://www.swsoft.com/">Plesk</a> managed server.
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-Plesk">Net::Plesk</a>
+from CPAN and proper <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration:www_plesk.pm">configuration</a>.
+END
+);
+
+sub rebless { shift; }
+
+# experiment: want the status of these right away (don't want account to
+# create or whatever and then get error in the queue from dup username or
+# something), so no queueing
+
+sub _export_insert {
+ my( $self, $www ) = ( shift, shift );
+
+ eval "use Net::Plesk;";
+ return $@ if $@;
+
+ my $plesk = new Net::Plesk (
+ 'POST' => $self->option('URL'),
+ ':HTTP_AUTH_LOGIN' => $self->option('login'),
+ ':HTTP_AUTH_PASSWD' => $self->option('password'),
+ );
+
+ my $gcresp = $plesk->client_get( $www->svc_acct->username );
+ return $gcresp->errortext
+ unless $gcresp->is_success;
+
+ unless ($gcresp->id) {
+ my $cust_main = $www->cust_svc->cust_pkg->cust_main;
+ $gcresp = $plesk->client_add( $cust_main->name,
+ $www->svc_acct->username,
+ $www->svc_acct->_password,
+ $cust_main->daytime,
+ $cust_main->fax,
+ $cust_main->invoicing_list->[0],
+ $cust_main->address1 . $cust_main->address2,
+ $cust_main->city,
+ $cust_main->state,
+ $cust_main->zip,
+ $cust_main->country,
+ );
+ return $gcresp->errortext
+ unless $gcresp->is_success;
+ }
+
+ $plesk->client_ippool_add_ip ( $gcresp->id,
+ $www->domain_record->recdata,
+ );
+
+ if ($self->option('web')) {
+ $self->_plesk_command( 'domain_add',
+ $www->domain_record->svc_domain->domain,
+ $gcresp->id,
+ $www->domain_record->recdata,
+ $self->option('template')?$self->option('template'):'',
+ $www->svc_acct->username,
+ $www->svc_acct->_password,
+ );
+ }else{
+ $self->_plesk_command( 'domain_add',
+ $www->domain_record->svc_domain->domain,
+ $gcresp->id,
+ $www->domain_record->recdata,
+ $self->option('template')?$self->option('template'):'',
+ );
+ }
+}
+
+sub _plesk_command {
+ my( $self, $method, @args ) = @_;
+
+ eval "use Net::Plesk;";
+ return $@ if $@;
+
+ local($Net::Plesk::DEBUG) = 1
+ if $self->option('debug');
+
+ my $plesk = new Net::Plesk (
+ 'POST' => $self->option('URL'),
+ ':HTTP_AUTH_LOGIN' => $self->option('login'),
+ ':HTTP_AUTH_PASSWD' => $self->option('password'),
+ );
+
+ my $response = $plesk->$method(@args);
+ return $response->errortext unless $response->is_success;
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ return "can't change domain with Plesk"
+ if $old->domain_record->svc_domain->domain ne
+ $new->domain_record->svc_domain->domain;
+
+ return "can't change client with Plesk"
+ if $old->svc_acct->username ne
+ $new->svc_acct->username;
+
+ return '';
+
+}
+
+sub _export_delete {
+ my( $self, $www ) = ( shift, shift );
+ $self->_plesk_command( 'domain_del', $www->domain_record->svc_domain->domain);
+}
+
+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..91b294e
--- /dev/null
+++ b/FS/FS/part_export/www_shellcommands.pm
@@ -0,0 +1,190 @@
+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',
+ },
+ 'suspend' => { label=>'Suspension command',
+ default=>'[ -n "$zone" ] && chmod 0 /var/www/$zone',
+ },
+ 'unsuspend'=> { label=>'Unsuspension command',
+ default=>'[ -n "$zone" ] && chmod 755 /var/www/$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="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">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";
+ this.form.suspend.value = "[ -n \"$zone\" ] && chmod 0 /var/www/$zone";
+ this.form.unsuspend.value = "[ -n \"$zone\" ] && chmod 755 /var/www/$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 = "";
+ this.form.suspend.value = "";
+ this.form.unsuspend.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 = "";
+ this.form.suspend.value = "";
+ this.form.unsuspend.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>$_password</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_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_www) = (shift, shift, shift);
+ my $command = $self->option($action);
+ return '' if $command =~ /^\s*$/;
+
+ #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..e759404
--- /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_foreign_key('exportnum', 'part_export', '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..ef7f557
--- /dev/null
+++ b/FS/FS/part_pkg.pm
@@ -0,0 +1,1494 @@
+package FS::part_pkg;
+
+use strict;
+use vars qw( @ISA %plans $DEBUG $setup_hack $skip_pkg_svc_hack );
+use Carp qw(carp cluck confess);
+use Scalar::Util qw( blessed );
+use Time::Local qw( timelocal_nocheck );
+use Tie::IxHash;
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh dbdef );
+use FS::pkg_svc;
+use FS::part_svc;
+use FS::cust_pkg;
+use FS::agent_type;
+use FS::type_pkgs;
+use FS::part_pkg_option;
+use FS::pkg_class;
+use FS::agent;
+use FS::part_pkg_taxoverride;
+use FS::part_pkg_taxproduct;
+use FS::part_pkg_link;
+
+@ISA = qw( FS::m2m_Common FS::option_Common );
+$DEBUG = 0;
+$setup_hack = 0;
+$skip_pkg_svc_hack = 0;
+
+=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 package definition. FS::part_pkg
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgpart - primary key (assigned automatically for new package definitions)
+
+=item pkg - Text name of this package definition (customer-viewable)
+
+=item comment - Text name of this package definition (non-customer-viewable)
+
+=item classnum - Optional package class (see L<FS::pkg_class>)
+
+=item promo_code - Promotional code
+
+=item setup - Setup fee expression (deprecated)
+
+=item freq - Frequency of recurring fee
+
+=item recur - Recurring fee expression (deprecated)
+
+=item setuptax - Setup fee tax exempt flag, empty or `Y'
+
+=item recurtax - Recurring fee tax exempt flag, empty or `Y'
+
+=item taxclass - Tax class
+
+=item plan - Price plan
+
+=item plandata - Price plan data (deprecated - see L<FS::part_pkg_option> instead)
+
+=item disabled - Disabled flag, empty or `Y'
+
+=item custom - Custom flag, empty or `Y'
+
+=item setup_cost - for cost tracking
+
+=item recur_cost - for cost tracking
+
+=item pay_weight - Weight (relative to credit_weight and other package definitions) that controls payment application to specific line items.
+
+=item credit_weight - Weight (relative to other package definitions) that controls credit application to specific line items.
+
+=item agentnum - Optional agentnum (see L<FS::agent>)
+
+=item fcc_ds0s - Optional DS0 equivalency number for FCC form 477
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new package definition. To add the package definition to
+the database, see L<"insert">.
+
+=cut
+
+sub table { 'part_pkg'; }
+
+=item clone
+
+An alternate constructor. Creates a new package definition by duplicating
+an existing definition. A new pkgpart is assigned and the custom flag is
+set to Y. To add the package definition to the database, see L<"insert">.
+
+=cut
+
+sub clone {
+ my $self = shift;
+ my $class = ref($self);
+ my %hash = $self->hash;
+ $hash{'pkgpart'} = '';
+ $hash{'custom'} = 'Y';
+ #new FS::part_pkg ( \%hash ); # ?
+ new $class ( \%hash ); # ?
+}
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this package definition to the database. If there is an error,
+returns the error, otherwise returns false.
+
+Currently available options are: I<pkg_svc>, I<primary_svc>, I<cust_pkg>,
+I<custnum_ref> and I<options>.
+
+If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
+values, appropriate FS::pkg_svc records will be inserted.
+
+If I<primary_svc> is set to the svcpart of the primary service, the appropriate
+FS::pkg_svc record will be updated.
+
+If I<cust_pkg> is set to a pkgnum of a FS::cust_pkg record (or the FS::cust_pkg
+record itself), the object will be updated to point to this package definition.
+
+In conjunction with I<cust_pkg>, if I<custnum_ref> is set to a scalar reference,
+the scalar will be updated with the custnum value from the cust_pkg record.
+
+If I<tax_overrides> is set to a hashref with usage classes as keys and comma
+separated tax class numbers as values, appropriate FS::part_pkg_taxoverride
+records will be inserted.
+
+If I<options> is set to a hashref of options, appropriate FS::part_pkg_option
+records will be inserted.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+ warn "FS::part_pkg::insert called on $self with options ".
+ join(', ', map "$_=>$options{$_}", keys %options)
+ 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;
+
+ warn " inserting part_pkg record" if $DEBUG;
+ my $error = $self->SUPER::insert( $options{options} );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('agent_defaultpkg') ) {
+ warn " agent_defaultpkg set; allowing all agents to purchase package"
+ if $DEBUG;
+ 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;
+ }
+ }
+ }
+
+ warn " inserting part_pkg_taxoverride records" if $DEBUG;
+ my %overrides = %{ $options{'tax_overrides'} || {} };
+ foreach my $usage_class ( keys %overrides ) {
+ my $override =
+ ( exists($overrides{$usage_class}) && defined($overrides{$usage_class}) )
+ ? $overrides{$usage_class}
+ : '';
+ my @overrides = (grep "$_", split(',', $override) );
+ my $error = $self->process_m2m (
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'hashref' => { 'usage_class' => $usage_class },
+ 'params' => \@overrides,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ unless ( $skip_pkg_svc_hack ) {
+
+ warn " inserting pkg_svc records" if $DEBUG;
+ my $pkg_svc = $options{'pkg_svc'} || {};
+ foreach my $part_svc ( qsearch('part_svc', {} ) ) {
+ my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
+ my $primary_svc =
+ ( $options{'primary_svc'} && $options{'primary_svc'}==$part_svc->svcpart )
+ ? 'Y'
+ : '';
+
+ my $pkg_svc = new FS::pkg_svc( {
+ 'pkgpart' => $self->pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ 'quantity' => $quantity,
+ 'primary_svc' => $primary_svc,
+ } );
+ my $error = $pkg_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ if ( $options{'cust_pkg'} ) {
+ warn " updating cust_pkg record " if $DEBUG;
+ my $old_cust_pkg =
+ ref($options{'cust_pkg'})
+ ? $options{'cust_pkg'}
+ : qsearchs('cust_pkg', { pkgnum => $options{'cust_pkg'} } );
+ ${ $options{'custnum_ref'} } = $old_cust_pkg->custnum
+ if $options{'custnum_ref'};
+ my %hash = $old_cust_pkg->hash;
+ $hash{'pkgpart'} = $self->pkgpart,
+ my $new_cust_pkg = new FS::cust_pkg \%hash;
+ local($FS::cust_pkg::disable_agentcheck) = 1;
+ my $error = $new_cust_pkg->replace($old_cust_pkg);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error modifying cust_pkg record: $error";
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $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 [ , OPTION => VALUE ... ]
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+Currently available options are: I<pkg_svc>, I<primary_svc> and I<options>
+
+If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
+values, the appropriate FS::pkg_svc records will be replaced.
+
+If I<primary_svc> is set to the svcpart of the primary service, the appropriate
+FS::pkg_svc record will be updated.
+
+If I<options> is set to a hashref, the appropriate FS::part_pkg_option records
+will be replaced.
+
+=cut
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
+
+ $options->{options} = {} unless defined($options->{options});
+
+ warn "FS::part_pkg::replace called on $new to replace $old with options".
+ join(', ', map "$_ => ". $options->{$_}, keys %$options)
+ 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;
+
+ #plandata shit stays in replace for upgrades until after 2.0 (or edit
+ #_upgrade_data)
+ warn " saving legacy plandata" if $DEBUG;
+ my $plandata = $new->get('plandata');
+ $new->set('plandata', '');
+
+ warn " deleting old part_pkg_option records" if $DEBUG;
+ foreach my $part_pkg_option ( $old->part_pkg_option ) {
+ my $error = $part_pkg_option->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " replacing part_pkg record" if $DEBUG;
+ my $error = $new->SUPER::replace($old, $options->{options} );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ warn " inserting part_pkg_option records for plandata: $plandata|" if $DEBUG;
+ foreach my $part_pkg_option (
+ map { /^(\w+)=(.*)$/ or do { $dbh->rollback if $oldAutoCommit;
+ return "illegal plandata: $plandata";
+ };
+ new FS::part_pkg_option {
+ 'pkgpart' => $new->pkgpart,
+ 'optionname' => $1,
+ 'optionvalue' => $2,
+ };
+ }
+ split("\n", $plandata)
+ ) {
+ my $error = $part_pkg_option->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " replacing pkg_svc records" if $DEBUG;
+ my $pkg_svc = $options->{'pkg_svc'} || {};
+ foreach my $part_svc ( qsearch('part_svc', {} ) ) {
+ my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
+ my $primary_svc =
+ ( defined($options->{'primary_svc'}) && $options->{'primary_svc'}
+ && $options->{'primary_svc'} == $part_svc->svcpart
+ )
+ ? 'Y'
+ : '';
+
+
+ my $old_pkg_svc = qsearchs('pkg_svc', {
+ 'pkgpart' => $old->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( {
+ 'pkgsvcnum' => ( $old_pkg_svc ? $old_pkg_svc->pkgsvcnum : '' ),
+ 'pkgpart' => $new->pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ 'quantity' => $quantity,
+ 'primary_svc' => $primary_svc,
+ } );
+ my $error = $old_pkg_svc
+ ? $new_pkg_svc->replace($old_pkg_svc)
+ : $new_pkg_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item check
+
+Checks all fields to make sure this is a valid package 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;
+ warn "FS::part_pkg::check called on $self" if $DEBUG;
+
+ for (qw(setup recur plandata)) {
+ #$self->set($_=>0) if $self->get($_) =~ /^\s*$/; }
+ return "Use of $_ field is deprecated; set a plan and options: ".
+ $self->get($_)
+ if length($self->get($_));
+ $self->set($_, '');
+ }
+
+ if ( $self->dbdef_table->column('freq')->type =~ /(int)/i ) {
+ my $error = $self->ut_number('freq');
+ return $error if $error;
+ } else {
+ $self->freq =~ /^(\d+[hdw]?)$/
+ or return "Illegal or empty freq: ". $self->freq;
+ $self->freq($1);
+ }
+
+ my @null_agentnum_right = ( 'Edit global package definitions' );
+ push @null_agentnum_right, 'One-time charge'
+ if $self->freq =~ /^0/;
+ push @null_agentnum_right, 'Customize customer package'
+ if $self->disabled eq 'Y'; #good enough
+
+ my $error = $self->ut_numbern('pkgpart')
+ || $self->ut_text('pkg')
+ || $self->ut_text('comment')
+ || $self->ut_textn('promo_code')
+ || $self->ut_alphan('plan')
+ || $self->ut_enum('setuptax', [ '', 'Y' ] )
+ || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->ut_textn('taxclass')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->ut_enum('custom', [ '', 'Y' ] )
+ || $self->ut_enum('no_auto', [ '', 'Y' ])
+ #|| $self->ut_moneyn('setup_cost')
+ #|| $self->ut_moneyn('recur_cost')
+ || $self->ut_floatn('setup_cost')
+ || $self->ut_floatn('recur_cost')
+ || $self->ut_floatn('pay_weight')
+ || $self->ut_floatn('credit_weight')
+ || $self->ut_numbern('taxproductnum')
+ || $self->ut_foreign_keyn('classnum', 'pkg_class', 'classnum')
+ || $self->ut_foreign_keyn('addon_classnum', 'pkg_class', 'classnum')
+ || $self->ut_foreign_keyn('taxproductnum',
+ 'part_pkg_taxproduct',
+ 'taxproductnum'
+ )
+ || ( $setup_hack
+ ? $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum' )
+ : $self->ut_agentnum_acl('agentnum', \@null_agentnum_right)
+ )
+ || $self->ut_numbern('fcc_ds0s')
+ || $self->SUPER::check
+ ;
+ return $error if $error;
+
+ return 'Unknown plan '. $self->plan
+ unless exists($plans{$self->plan});
+
+ my $conf = new FS::Conf;
+ return 'Taxclass is required'
+ if ! $self->taxclass && $conf->exists('require_taxclasses');
+
+ '';
+}
+
+=item pkg_comment [ OPTION => VALUE... ]
+
+Returns an (internal) string representing this package. Currently,
+"pkgpart: pkg - comment", is returned. "pkg - comment" may be returned in the
+future, omitting pkgpart. The comment will have '(CUSTOM) ' prepended if
+custom is Y.
+
+If the option nopkgpart is true then the "pkgpart: ' is omitted.
+
+=cut
+
+sub pkg_comment {
+ my $self = shift;
+ my %opt = @_;
+
+ #$self->pkg. ' - '. $self->comment;
+ #$self->pkg. ' ('. $self->comment. ')';
+ my $pre = $opt{nopkgpart} ? '' : $self->pkgpart. ': ';
+ $pre. $self->pkg. ' - '. $self->custom_comment;
+}
+
+sub custom_comment {
+ my $self = shift;
+ ( $self->custom ? '(CUSTOM) ' : '' ). $self->comment;
+}
+
+=item pkg_class
+
+Returns the package class, as an FS::pkg_class object, or the empty string
+if there is no package class.
+
+=cut
+
+sub pkg_class {
+ my $self = shift;
+ if ( $self->classnum ) {
+ qsearchs('pkg_class', { 'classnum' => $self->classnum } );
+ } else {
+ return '';
+ }
+}
+
+=item addon_pkg_class
+
+Returns the add-on package class, as an FS::pkg_class object, or the empty
+string if there is no add-on package class.
+
+=cut
+
+sub addon_pkg_class {
+ my $self = shift;
+ if ( $self->addon_classnum ) {
+ qsearchs('pkg_class', { 'classnum' => $self->addon_classnum } );
+ } else {
+ return '';
+ }
+}
+
+=item categoryname
+
+Returns the package category name, or the empty string if there is no package
+category.
+
+=cut
+
+sub categoryname {
+ my $self = shift;
+ my $pkg_class = $self->pkg_class;
+ $pkg_class
+ ? $pkg_class->categoryname
+ : '';
+}
+
+=item classname
+
+Returns the package class name, or the empty string if there is no package
+class.
+
+=cut
+
+sub classname {
+ my $self = shift;
+ my $pkg_class = $self->pkg_class;
+ $pkg_class
+ ? $pkg_class->classname
+ : '';
+}
+
+=item addon_classname
+
+Returns the add-on package class name, or the empty string if there is no
+add-on package class.
+
+=cut
+
+sub addon_classname {
+ my $self = shift;
+ my $pkg_class = $self->addon_pkg_class;
+ $pkg_class
+ ? $pkg_class->classname
+ : '';
+}
+
+=item agent
+
+Returns the associated agent for this event, if any, as an FS::agent object.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=item pkg_svc [ HASHREF | OPTION => VALUE ]
+
+Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package
+definition (with non-zero quantity).
+
+One option is available, I<disable_linked>. If set true it will return the
+services for this package definition alone, omitting services from any add-on
+packages.
+
+=cut
+
+=item type_pkgs
+
+Returns all FS::type_pkgs objects (see L<FS::type_pkgs>) for this package
+definition.
+
+=cut
+
+sub type_pkgs {
+ my $self = shift;
+ qsearch('type_pkgs', { 'pkgpart' => $self->pkgpart } );
+}
+
+sub pkg_svc {
+ my $self = shift;
+
+# #sort { $b->primary cmp $a->primary }
+# grep { $_->quantity }
+# qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+
+ my $opt = ref($_[0]) ? $_[0] : { @_ };
+ my %pkg_svc = map { $_->svcpart => $_ }
+ grep { $_->quantity }
+ qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+
+ unless ( $opt->{disable_linked} ) {
+ foreach my $dst_pkg ( map $_->dst_pkg, $self->svc_part_pkg_link ) {
+ my @pkg_svc = grep { $_->quantity }
+ qsearch( 'pkg_svc', { pkgpart=>$dst_pkg->pkgpart } );
+ foreach my $pkg_svc ( @pkg_svc ) {
+ if ( $pkg_svc{$pkg_svc->svcpart} ) {
+ my $quantity = $pkg_svc{$pkg_svc->svcpart}->quantity;
+ $pkg_svc{$pkg_svc->svcpart}->quantity($quantity + $pkg_svc->quantity);
+ } else {
+ $pkg_svc{$pkg_svc->svcpart} = $pkg_svc;
+ }
+ }
+ }
+ }
+
+ values(%pkg_svc);
+
+}
+
+=item svcpart [ SVCDB ]
+
+Returns the svcpart of the primary service definition (see L<FS::part_svc>)
+associated with this package 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. SVCDB can be specified as a scalar table
+name, such as 'svc_acct', or as an arrayref of possible table names.
+
+=cut
+
+sub svcpart {
+ my $pkg_svc = shift->_primary_pkg_svc(@_);
+ $pkg_svc ? $pkg_svc->svcpart : '';
+}
+
+=item part_svc [ SVCDB ]
+
+Like the B<svcpart> method, but returns the FS::part_svc object (see
+L<FS::part_svc>).
+
+=cut
+
+sub part_svc {
+ my $pkg_svc = shift->_primary_pkg_svc(@_);
+ $pkg_svc ? $pkg_svc->part_svc : '';
+}
+
+sub _primary_pkg_svc {
+ my $self = shift;
+
+ my $svcdb = scalar(@_) ? shift : [];
+ $svcdb = ref($svcdb) ? $svcdb : [ $svcdb ];
+ my %svcdb = map { $_=>1 } @$svcdb;
+
+ my @svcdb_pkg_svc =
+ grep { !scalar(@$svcdb) || $svcdb{ $_->part_svc->svcdb } }
+ $self->pkg_svc;
+
+ my @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc;
+ @pkg_svc = grep {$_->quantity == 1 } @svcdb_pkg_svc
+ unless @pkg_svc;
+ return '' if scalar(@pkg_svc) != 1;
+ $pkg_svc[0];
+}
+
+=item svcpart_unique_svcdb SVCDB
+
+Returns the svcpart of a service definition (see L<FS::part_svc>) matching
+SVCDB associated with this package definition (see L<FS::pkg_svc>). Returns
+false if there not a primary service definition for SVCDB or there are multiple
+service definitions for SVCDB.
+
+=cut
+
+sub svcpart_unique_svcdb {
+ my( $self, $svcdb ) = @_;
+ my @svcdb_pkg_svc = grep { ( $svcdb eq $_->part_svc->svcdb ) } $self->pkg_svc;
+ return '' if scalar(@svcdb_pkg_svc) != 1;
+ $svcdb_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 is free, 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->is_free ) {
+ ( 'BILL' );
+ } else {
+ ( 'CARD' );
+ }
+}
+
+=item is_free
+
+Returns true if this package is free.
+
+=cut
+
+sub is_free {
+ my $self = shift;
+ unless ( $self->plan ) {
+ $self->setup =~ /^\s*0+(\.0*)?\s*$/
+ && $self->recur =~ /^\s*0+(\.0*)?\s*$/;
+ } elsif ( $self->can('is_free_options') ) {
+ not grep { $_ !~ /^\s*0*(\.0*)?\s*$/ }
+ map { $self->option($_) }
+ $self->is_free_options;
+ } else {
+ warn "FS::part_pkg::is_free: FS::part_pkg::". $self->plan. " subclass ".
+ "provides neither is_free_options nor is_free method; returning false";
+ 0;
+ }
+}
+
+sub can_discount { 0; }
+
+sub freqs_href {
+ #method, class method or sub? #my $self = shift;
+
+ tie my %freq, 'Tie::IxHash',
+ '0' => '(no recurring fee)',
+ '1h' => 'hourly',
+ '1d' => 'daily',
+ '2d' => 'every two days',
+ '3d' => 'every three days',
+ '1w' => 'weekly',
+ '2w' => 'biweekly (every 2 weeks)',
+ '1' => 'monthly',
+ '45d' => 'every 45 days',
+ '2' => 'bimonthly (every 2 months)',
+ '3' => 'quarterly (every 3 months)',
+ '4' => 'every 4 months',
+ '137d' => 'every 4 1/2 months (137 days)',
+ '6' => 'semiannually (every 6 months)',
+ '12' => 'annually',
+ '13' => 'every 13 months (annually +1 month)',
+ '24' => 'biannually (every 2 years)',
+ '36' => 'triannually (every 3 years)',
+ '48' => '(every 4 years)',
+ '60' => '(every 5 years)',
+ '120' => '(every 10 years)',
+ ;
+
+ \%freq;
+
+}
+
+=item freq_pretty
+
+Returns an english representation of the I<freq> field, such as "monthly",
+"weekly", "semi-annually", etc.
+
+=cut
+
+sub freq_pretty {
+ my $self = shift;
+ my $freq = $self->freq;
+
+ #my $freqs_href = $self->freqs_href;
+ my $freqs_href = freqs_href();
+
+ if ( exists($freqs_href->{$freq}) ) {
+ $freqs_href->{$freq};
+ } else {
+ my $interval = 'month';
+ if ( $freq =~ /^(\d+)([hdw])$/ ) {
+ my %interval = ( 'h' => 'hour', 'd'=>'day', 'w'=>'week' );
+ $interval = $interval{$2};
+ }
+ if ( $1 == 1 ) {
+ "every $interval";
+ } else {
+ "every $freq ${interval}s";
+ }
+ }
+}
+
+=item add_freq TIMESTAMP
+
+Adds the frequency of this package to the provided timestamp and returns
+the resulting timestamp, or -1 if the frequency of this package could not be
+parsed (shouldn't happen).
+
+=cut
+
+sub add_freq {
+ my( $self, $date ) = @_;
+ my $freq = $self->freq;
+
+ #change this bit to use Date::Manip? CAREFUL with timezones (see
+ # mailing list archive)
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($date) )[0,1,2,3,4,5];
+
+ if ( $self->freq =~ /^\d+$/ ) {
+ $mon += $self->freq;
+ until ( $mon < 12 ) { $mon -= 12; $year++; }
+ } elsif ( $self->freq =~ /^(\d+)w$/ ) {
+ my $weeks = $1;
+ $mday += $weeks * 7;
+ } elsif ( $self->freq =~ /^(\d+)d$/ ) {
+ my $days = $1;
+ $mday += $days;
+ } elsif ( $self->freq =~ /^(\d+)h$/ ) {
+ my $hours = $1;
+ $hour += $hours;
+ } else {
+ return -1;
+ }
+
+ timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
+}
+
+=item plandata
+
+For backwards compatibility, returns the plandata field as well as all options
+from FS::part_pkg_option.
+
+=cut
+
+sub plandata {
+ my $self = shift;
+ carp "plandata is deprecated";
+ if ( @_ ) {
+ $self->SUPER::plandata(@_);
+ } else {
+ my $plandata = $self->get('plandata');
+ my %options = $self->options;
+ $plandata .= join('', map { "$_=$options{$_}\n" } keys %options );
+ $plandata;
+ }
+}
+
+=item part_pkg_option
+
+Returns all options as FS::part_pkg_option objects (see
+L<FS::part_pkg_option>).
+
+=cut
+
+sub part_pkg_option {
+ my $self = shift;
+ qsearch('part_pkg_option', { 'pkgpart' => $self->pkgpart } );
+}
+
+=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_pkg_option;
+}
+
+=item option OPTIONNAME [ QUIET ]
+
+Returns the option value for the given name, or the empty string. If a true
+value is passed as the second argument, warnings about missing the option
+will be suppressed.
+
+=cut
+
+sub option {
+ my( $self, $opt, $ornull ) = @_;
+ my $part_pkg_option =
+ qsearchs('part_pkg_option', {
+ pkgpart => $self->pkgpart,
+ optionname => $opt,
+ } );
+ return $part_pkg_option->optionvalue if $part_pkg_option;
+ my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+ split("\n", $self->get('plandata') );
+ return $plandata{$opt} if exists $plandata{$opt};
+ cluck "WARNING: (pkgpart ". $self->pkgpart. ") Package def option $opt ".
+ "not found in options or plandata!\n"
+ unless $ornull;
+ '';
+}
+
+=item bill_part_pkg_link
+
+Returns the associated part_pkg_link records (see L<FS::part_pkg_link>).
+
+=cut
+
+sub bill_part_pkg_link {
+ shift->_part_pkg_link('bill', @_);
+}
+
+=item svc_part_pkg_link
+
+Returns the associated part_pkg_link records (see L<FS::part_pkg_link>).
+
+=cut
+
+sub svc_part_pkg_link {
+ shift->_part_pkg_link('svc', @_);
+}
+
+sub _part_pkg_link {
+ my( $self, $type ) = @_;
+ qsearch({ table => 'part_pkg_link',
+ hashref => { 'src_pkgpart' => $self->pkgpart,
+ 'link_type' => $type,
+ },
+ order_by => "ORDER BY hidden",
+ });
+}
+
+sub self_and_bill_linked {
+ shift->_self_and_linked('bill', @_);
+}
+
+sub _self_and_linked {
+ my( $self, $type, $hidden ) = @_;
+ $hidden ||= '';
+
+ my @result = ();
+ foreach ( ( $self, map { $_->dst_pkg->_self_and_linked($type, $_->hidden) }
+ $self->_part_pkg_link($type) ) )
+ {
+ $_->hidden($hidden) if $hidden;
+ push @result, $_;
+ }
+
+ (@result);
+}
+
+=item part_pkg_taxoverride [ CLASS ]
+
+Returns all associated FS::part_pkg_taxoverride objects (see
+L<FS::part_pkg_taxoverride>). Limits the returned set to those
+of class CLASS if defined. Class may be one of 'setup', 'recur',
+the empty string (default), or a usage class number (see L<FS::usage_class>).
+When a class is specified, the empty string class (default) is returned
+if no more specific values exist.
+
+=cut
+
+sub part_pkg_taxoverride {
+ my $self = shift;
+ my $class = shift;
+
+ my $hashref = { 'pkgpart' => $self->pkgpart };
+ $hashref->{'usage_class'} = $class if defined($class);
+ my @overrides = qsearch('part_pkg_taxoverride', $hashref );
+
+ unless ( scalar(@overrides) || !defined($class) || !$class ){
+ $hashref->{'usage_class'} = '';
+ @overrides = qsearch('part_pkg_taxoverride', $hashref );
+ }
+
+ @overrides;
+}
+
+=item has_taxproduct
+
+Returns true if this package has any taxproduct associated with it.
+
+=cut
+
+sub has_taxproduct {
+ my $self = shift;
+
+ $self->taxproductnum ||
+ scalar( grep { $_ =~/^usage_taxproductnum_/ && $self->option($_) }
+ keys %{ {$self->options} }
+ )
+
+}
+
+
+=item taxproduct [ CLASS ]
+
+Returns the associated tax product for this package definition (see
+L<FS::part_pkg_taxproduct>). CLASS may be one of 'setup', 'recur' or
+the usage classnum (see L<FS::usage_class>). Returns the default
+tax product for this record if the more specific CLASS value does
+not exist.
+
+=cut
+
+sub taxproduct {
+ my $self = shift;
+ my $class = shift;
+
+ my $part_pkg_taxproduct;
+
+ my $taxproductnum = $self->taxproductnum;
+ if ($class) {
+ my $class_taxproductnum = $self->option("usage_taxproductnum_$class", 1);
+ $taxproductnum = $class_taxproductnum
+ if $class_taxproductnum
+ }
+
+ $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum } );
+
+ unless ($part_pkg_taxproduct || $taxproductnum eq $self->taxproductnum ) {
+ $taxproductnum = $self->taxproductnum;
+ $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum } );
+ }
+
+ $part_pkg_taxproduct;
+}
+
+=item taxproduct_description [ CLASS ]
+
+Returns the description of the associated tax product for this package
+definition (see L<FS::part_pkg_taxproduct>).
+
+=cut
+
+sub taxproduct_description {
+ my $self = shift;
+ my $part_pkg_taxproduct = $self->taxproduct(@_);
+ $part_pkg_taxproduct ? $part_pkg_taxproduct->description : '';
+}
+
+=item part_pkg_taxrate DATA_PROVIDER, GEOCODE, [ CLASS ]
+
+Returns the package to taxrate m2m records for this package in the location
+specified by GEOCODE (see L<FS::part_pkg_taxrate>) and usage class CLASS.
+CLASS may be one of 'setup', 'recur', or one of the usage classes numbers
+(see L<FS::usage_class>).
+
+=cut
+
+sub _expand_cch_taxproductnum {
+ my $self = shift;
+ my $class = shift;
+ my $part_pkg_taxproduct = $self->taxproduct($class);
+
+ my ($a,$b,$c,$d) = ( $part_pkg_taxproduct
+ ? ( split ':', $part_pkg_taxproduct->taxproduct )
+ : ()
+ );
+ $a = '' unless $a; $b = '' unless $b; $c = '' unless $c; $d = '' unless $d;
+ my $extra_sql = "AND ( taxproduct = '$a:$b:$c:$d'
+ OR taxproduct = '$a:$b:$c:'
+ OR taxproduct = '$a:$b:".":$d'
+ OR taxproduct = '$a:$b:".":' )";
+ map { $_->taxproductnum } qsearch( { 'table' => 'part_pkg_taxproduct',
+ 'hashref' => { 'data_vendor'=>'cch' },
+ 'extra_sql' => $extra_sql,
+ } );
+
+}
+
+sub part_pkg_taxrate {
+ my $self = shift;
+ my ($data_vendor, $geocode, $class) = @_;
+
+ my $dbh = dbh;
+ my $extra_sql = 'WHERE part_pkg_taxproduct.data_vendor = '.
+ dbh->quote($data_vendor);
+
+ # CCH oddness in m2m
+ $extra_sql .= ' AND ('.
+ join(' OR ', map{ 'geocode = '. $dbh->quote(substr($geocode, 0, $_)) }
+ qw(10 5 2)
+ ).
+ ')';
+ # much more CCH oddness in m2m -- this is kludgy
+ my @tpnums = $self->_expand_cch_taxproductnum($class);
+ if (scalar(@tpnums)) {
+ $extra_sql .= ' AND ('.
+ join(' OR ', map{ "taxproductnum = $_" } @tpnums ).
+ ')';
+ } else {
+ $extra_sql .= ' AND ( 0 = 1 )';
+ }
+
+ my $addl_from = 'LEFT JOIN part_pkg_taxproduct USING ( taxproductnum )';
+ my $order_by = 'ORDER BY taxclassnum, length(geocode) desc, length(taxproduct) desc';
+ my $select = 'DISTINCT ON(taxclassnum) *, taxproduct';
+
+ # should qsearch preface columns with the table to facilitate joins?
+ qsearch( { 'table' => 'part_pkg_taxrate',
+ 'select' => $select,
+ 'hashref' => { # 'data_vendor' => $data_vendor,
+ # 'taxproductnum' => $self->taxproductnum,
+ },
+ 'addl_from' => $addl_from,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $order_by,
+ } );
+}
+
+=item _rebless
+
+Reblesses the object into the FS::part_pkg::PLAN class (if available), where
+PLAN is the object's I<plan> field. There should be better docs
+on how to create new price plans, but until then, see L</NEW PLAN CLASSES>.
+
+=cut
+
+sub _rebless {
+ my $self = shift;
+ my $plan = $self->plan;
+ unless ( $plan ) {
+ cluck "no price plan found for pkgpart ". $self->pkgpart. "\n"
+ if $DEBUG;
+ return $self;
+ }
+ return $self if ref($self) =~ /::$plan$/; #already blessed into plan subclass
+ my $class = ref($self). "::$plan";
+ warn "reblessing $self into $class" if $DEBUG;
+ eval "use $class;";
+ die $@ if $@;
+ bless($self, $class) unless $@;
+ $self;
+}
+
+#fallbacks that eval the setup and recur fields, for backwards compat
+
+sub calc_setup {
+ my $self = shift;
+ warn 'no price plan class for '. $self->plan. ", eval-ing setup\n";
+ $self->_calc_eval('setup', @_);
+}
+
+sub calc_recur {
+ my $self = shift;
+ warn 'no price plan class for '. $self->plan. ", eval-ing recur\n";
+ $self->_calc_eval('recur', @_);
+}
+
+use vars qw( $sdate @details );
+sub _calc_eval {
+ #my( $self, $field, $cust_pkg ) = @_;
+ my( $self, $field, $cust_pkg, $sdateref, $detailsref ) = @_;
+ *sdate = $sdateref;
+ *details = $detailsref;
+ $self->$field() =~ /^(.*)$/
+ or die "Illegal $field (pkgpart ". $self->pkgpart. '): '.
+ $self->$field(). "\n";
+ my $prog = $1;
+ return 0 if $prog =~ /^\s*$/;
+ my $value = eval $prog;
+ die $@ if $@;
+ $value;
+}
+
+#fallback that return 0 for old legacy packages with no plan
+
+sub calc_remain { 0; }
+sub calc_cancel { 0; }
+sub calc_units { 0; }
+
+#fallback for everything except bulk.pm
+sub hide_svc_detail { 0; }
+
+=item recur_cost_permonth CUST_PKG
+
+recur_cost divided by freq (only supported for monthly and longer frequencies)
+
+=cut
+
+sub recur_cost_permonth {
+ my($self, $cust_pkg) = @_;
+ return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0;
+ sprintf('%.2f', $self->recur_cost / $self->freq );
+}
+
+=item format OPTION DATA
+
+Returns data formatted according to the function 'format' described
+in the plan info. Returns DATA if no such function exists.
+
+=cut
+
+sub format {
+ my ($self, $option, $data) = (shift, shift, shift);
+ if (exists($plans{$self->plan}->{fields}->{$option}{format})) {
+ &{$plans{$self->plan}->{fields}->{$option}{format}}($data);
+ }else{
+ $data;
+ }
+}
+
+=item parse OPTION DATA
+
+Returns data parsed according to the function 'parse' described
+in the plan info. Returns DATA if no such function exists.
+
+=cut
+
+sub parse {
+ my ($self, $option, $data) = (shift, shift, shift);
+ if (exists($plans{$self->plan}->{fields}->{$option}{parse})) {
+ &{$plans{$self->plan}->{fields}->{$option}{parse}}($data);
+ }else{
+ $data;
+ }
+}
+
+=back
+
+=cut
+
+=head1 CLASS METHODS
+
+=over 4
+
+=cut
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+ my($class, %opts) = @_;
+
+ warn "[FS::part_pkg] upgrading $class\n" if $DEBUG;
+
+ my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'extra_sql' => "WHERE ". join(' OR ',
+ ( map "($_ IS NOT NULL AND $_ != '' )",
+ qw( plandata setup recur ) ),
+ 'plan IS NULL', "plan = '' ",
+ ),
+ });
+
+ foreach my $part_pkg (@part_pkg) {
+
+ unless ( $part_pkg->plan ) {
+ $part_pkg->plan('flat');
+ }
+
+ if ( length($part_pkg->option('setup_fee')) == 0
+ && $part_pkg->setup =~ /^\s*([\d\.]+)\s*$/ ) {
+
+ my $opt = new FS::part_pkg_option {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'optionname' => 'setup_fee',
+ 'optionvalue' => $1,
+ };
+ my $error = $opt->insert;
+ die $error if $error;
+
+
+ #} else {
+ # die "Can't parse part_pkg.setup for fee; convert pkgnum ".
+ # $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n";
+ }
+ $part_pkg->setup('');
+
+ if ( length($part_pkg->option('recur_fee')) == 0
+ && $part_pkg->recur =~ /^\s*([\d\.]+)\s*$/ ) {
+
+ my $opt = new FS::part_pkg_option {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'optionname' => 'recur_fee',
+ 'optionvalue' => $1,
+ };
+ my $error = $opt->insert;
+ die $error if $error;
+
+
+ #} else {
+ # die "Can't parse part_pkg.setup for fee; convert pkgnum ".
+ # $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n";
+ }
+ $part_pkg->recur('');
+
+ $part_pkg->replace; #this should take care of plandata, right?
+
+ }
+
+ # now upgrade to the explicit custom flag
+
+ @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'hashref' => { disabled => 'Y', custom => '' },
+ 'extra_sql' => "AND comment LIKE '(CUSTOM) %'",
+ });
+
+ foreach my $part_pkg (@part_pkg) {
+ my $new = new FS::part_pkg { $part_pkg->hash };
+ $new->custom('Y');
+ my $comment = $part_pkg->comment;
+ $comment =~ s/^\(CUSTOM\) //;
+ $comment = '(none)' unless $comment =~ /\S/;
+ $new->comment($comment);
+
+ my $pkg_svc = { map { $_->svcpart => $_->quantity } $part_pkg->pkg_svc };
+ my $primary = $part_pkg->svcpart;
+ my $options = { $part_pkg->options };
+
+ my $error = $new->replace( $part_pkg,
+ 'pkg_svc' => $pkg_svc,
+ 'primary_svc' => $primary,
+ 'options' => $options,
+ );
+ die $error if $error;
+ }
+
+}
+
+=item curuser_pkgs_sql
+
+Returns an SQL fragment for searching for packages the current user can
+use, either via part_pkg.agentnum directly, or via agent type (see
+L<FS::type_pkgs>).
+
+=cut
+
+sub curuser_pkgs_sql {
+ my $class = shift;
+
+ $class->_pkgs_sql( $FS::CurrentUser::CurrentUser->agentnums );
+
+}
+
+=item agent_pkgs_sql AGENT | AGENTNUM, ...
+
+Returns an SQL fragment for searching for packages the provided agent or agents
+can use, either via part_pkg.agentnum directly, or via agent type (see
+L<FS::type_pkgs>).
+
+=cut
+
+sub agent_pkgs_sql {
+ my $class = shift; #i'm a class method, not a sub (the question is... why??)
+ my @agentnums = map { ref($_) ? $_->agentnum : $_ } @_;
+
+ $class->_pkgs_sql(@agentnums); #is this why
+
+}
+
+sub _pkgs_sql {
+ my( $class, @agentnums ) = @_;
+ my $agentnums = join(',', @agentnums);
+
+ "
+ (
+ ( agentnum IS NOT NULL AND agentnum IN ($agentnums) )
+ OR ( agentnum IS NULL
+ AND EXISTS ( SELECT 1
+ FROM type_pkgs
+ LEFT JOIN agent_type USING ( typenum )
+ LEFT JOIN agent AS typeagent USING ( typenum )
+ WHERE type_pkgs.pkgpart = part_pkg.pkgpart
+ AND typeagent.agentnum IN ($agentnums)
+ )
+ )
+ )
+ ";
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item plan_info
+
+=cut
+
+#false laziness w/part_export & cdr
+my %info;
+foreach my $INC ( @INC ) {
+ warn "globbing $INC/FS/part_pkg/*.pm\n" if $DEBUG;
+ foreach my $file ( glob("$INC/FS/part_pkg/*.pm") ) {
+ warn "attempting to load plan info from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/part_pkg/: $file\n";
+ next;
+ };
+ my $mod = $1;
+ my $info = eval "use FS::part_pkg::$mod; ".
+ "\\%FS::part_pkg::$mod\::info;";
+ if ( $@ ) {
+ die "error using FS::part_pkg::$mod (skipping): $@\n" if $@;
+ next;
+ }
+ unless ( keys %$info ) {
+ warn "no %info hash found in FS::part_pkg::$mod, skipping\n";
+ next;
+ }
+ warn "got plan info from FS::part_pkg::$mod: $info\n" if $DEBUG;
+ if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
+ warn "skipping disabled plan FS::part_pkg::$mod" if $DEBUG;
+ next;
+ }
+ $info{$mod} = $info;
+ }
+}
+
+tie %plans, 'Tie::IxHash',
+ map { $_ => $info{$_} }
+ sort { $info{$a}->{'weight'} <=> $info{$b}->{'weight'} }
+ keys %info;
+
+sub plan_info {
+ \%plans;
+}
+
+
+=back
+
+=head1 NEW PLAN CLASSES
+
+A module should be added in FS/FS/part_pkg/ Eventually, an example may be
+found in eg/plan_template.pm. Until then, it is suggested that you use the
+other modules in FS/FS/part_pkg/ as a guide.
+
+=head1 BUGS
+
+The delete method is unimplemented.
+
+setup and recur semantics are not yet defined (and are implemented in
+FS::cust_bill. hmm.). now they're deprecated and need to go.
+
+plandata should go
+
+part_pkg_taxrate is Pg specific
+
+replace should be smarter about managing the related tables (options, pkg_svc)
+
+=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_pkg/agent.pm b/FS/FS/part_pkg/agent.pm
new file mode 100644
index 0000000..e5bd163
--- /dev/null
+++ b/FS/FS/part_pkg/agent.pm
@@ -0,0 +1,177 @@
+package FS::part_pkg::agent;
+
+use strict;
+use vars qw(@ISA $DEBUG $me %info);
+use Date::Format;
+use FS::Record qw( qsearch );
+use FS::agent;
+use FS::cust_main;
+
+#use FS::part_pkg::recur_Common;;
+#@ISA = qw(FS::part_pkg::recur_Common);
+use FS::part_pkg::prorate;
+@ISA = qw(FS::part_pkg::prorate);
+
+$DEBUG = 0;
+
+$me = '[FS::part_pkg::agent]';
+
+%info = (
+ 'name' => 'Wholesale bulk billing, for master customers of an agent.',
+ 'shortname' => 'Wholesale bulk billing for agent.',
+
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+
+
+ #'recur_method' => { 'name' => 'Recurring fee method',
+ # #'type' => 'radio',
+ # #'options' => \%recur_method,
+ # 'type' => 'select',
+ # 'select_options' => \%recur_Common::recur_method,
+ # },
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28)',
+ 'default' => '1',
+ },
+
+ 'no_pkg_prorate' => { 'name' => 'Disable prorating bulk packages (charge full price for packages active only a portion of the month)',
+ 'type' => 'checkbox',
+ },
+
+ },
+
+ #'fieldorder' => [qw( setup_fee recur_fee recur_method cutoff_day ) ],
+ 'fieldorder' => [qw( setup_fee recur_fee cutoff_day no_pkg_prorate ) ],
+
+ 'weight' => 51,
+
+);
+
+#some false laziness-ish w/bulk.pm... not a lot
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $last_bill = $cust_pkg->last_bill;
+
+ return sprintf("%.2f", $self->SUPER::calc_recur(@_) )
+ unless $$sdate > $last_bill;
+
+ my $conf = new FS::Conf;
+ my $money_char = $conf->config('money_char') || '$';
+
+ my $total_agent_charge = 0;
+
+ warn "$me billing for agent packages from ". time2str('%x', $last_bill).
+ " to ". time2str('%x', $$sdate). "\n"
+ if $DEBUG;
+
+ my $prorate_ratio = ( $$sdate - $last_bill )
+ / ( $self->add_freq($last_bill) - $last_bill );
+
+ #almost always just one,
+ #unless you have multiple agents with same master customer0
+ my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } );
+
+ foreach my $agent (@agents) {
+
+ warn "$me billing for agent ". $agent->agent. "\n"
+ if $DEBUG;
+
+ #not the most efficient to load them all into memory,
+ #but good enough for our current needs
+ my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } );
+
+ foreach my $cust_main (@cust_main) {
+
+ warn "$me billing agent charges for ". $cust_main->name_short. "\n"
+ if $DEBUG;
+
+ #make sure setup dates are filled in
+ my $error = $cust_main->bill; #options don't propogate from freeside-daily
+ die "Error pre-billing agent customer: $error" if $error;
+
+ my @cust_pkg = grep { my $setup = $_->get('setup');
+ my $cancel = $_->get('cancel');
+
+ $setup < $$sdate # END
+ && ( ! $cancel || $cancel > $last_bill ) #START
+ }
+ $cust_main->all_pkgs;
+
+ foreach my $cust_pkg ( @cust_pkg ) {
+
+ warn "$me billing agent charges for pkgnum ". $cust_pkg->pkgnum. "\n"
+ if $DEBUG;
+
+ my $pkg_details = $cust_main->name_short. ': '; #name?
+ # + something to identify package... primary service probably
+
+ my $pkg_charge = 0;
+
+ my $part_pkg = $cust_pkg->part_pkg;
+ #option to not fallback? via options above
+ my $pkg_setup_fee =
+ $part_pkg->setup_cost || $part_pkg->option('setup_fee');
+ my $pkg_base_recur =
+ $part_pkg->recur_cost || $part_pkg->base_recur_permonth($cust_pkg);
+
+ my $pkg_start = $cust_pkg->get('setup');
+ if ( $pkg_start < $last_bill ) {
+ $pkg_start = $last_bill;
+ } elsif ( $pkg_setup_fee ) {
+ $pkg_charge += $pkg_setup_fee;
+ $pkg_details .= $money_char. sprintf('%.2f setup, ', $pkg_setup_fee );
+ }
+
+ my $pkg_end = $cust_pkg->get('cancel');
+ $pkg_end = ( !$pkg_end || $pkg_end > $$sdate ) ? $$sdate : $pkg_end;
+
+
+ my $pkg_recur_charge = $prorate_ratio * $pkg_base_recur;
+ $pkg_recur_charge *= ( $pkg_end - $pkg_start )
+ / ( $$sdate - $last_bill )
+ unless $self->option('no_pkg_prorate');
+
+ my $recur_charge += $pkg_recur_charge;
+
+ $pkg_details .= $money_char. sprintf('%.2f', $recur_charge ).
+ ' ('. time2str('%x', $pkg_start).
+ ' - '. time2str('%x', $pkg_end ). ')'
+ if $recur_charge;
+
+ $pkg_charge += $recur_charge;
+
+ push @$details, $pkg_details
+ if $pkg_charge;
+ $total_agent_charge += $pkg_charge;
+
+ } #foreach $cust_pkg
+
+ } #foreach $cust_main
+
+ } #foreach $agent;
+
+ my $charges = $total_agent_charge + $self->SUPER::calc_recur(@_); #prorate
+
+ sprintf('%.2f', $charges );
+
+}
+
+sub can_discount { 0; }
+
+sub hide_svc_detail {
+ 1;
+}
+
+sub is_free {
+ 0;
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/base_delayed.pm b/FS/FS/part_pkg/base_delayed.pm
new file mode 100644
index 0000000..df50376
--- /dev/null
+++ b/FS/FS/part_pkg/base_delayed.pm
@@ -0,0 +1,52 @@
+package FS::part_pkg::base_delayed;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::base_rate;
+
+@ISA = qw(FS::part_pkg::base_rate);
+
+%info = (
+ 'name' => 'Free (or setup fee) for X days, then base rate'.
+ ' (anniversary billing)',
+ 'shortname' => 'Bulk (manual from "units" option), w/intro period',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'free_days' => { 'name' => 'Initial free days',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring base fee for this package',
+ 'default' => 0,
+ },
+ 'recur_notify' => { 'name' => 'Number of days before recurring billing'.
+ ' commences to notify customer. (0 means'.
+ ' no warning)',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ },
+ 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify',
+ 'unused_credit'
+ ],
+ #'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',
+ 'weight' => 54, #&g!
+);
+
+sub calc_setup {
+ my($self, $cust_pkg, $time ) = @_;
+
+ my $d = $cust_pkg->bill || $time;
+ $d += 86400 * $self->option('free_days');
+ $cust_pkg->bill($d);
+
+ $self->option('setup_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/base_rate.pm b/FS/FS/part_pkg/base_rate.pm
new file mode 100644
index 0000000..440e985
--- /dev/null
+++ b/FS/FS/part_pkg/base_rate.pm
@@ -0,0 +1,96 @@
+package FS::part_pkg::base_rate;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch);
+use FS::part_pkg;
+
+@ISA = qw(FS::part_pkg);
+
+%info = (
+ 'name' => 'Base rate (anniversary billing, Times units ordered)',
+ # XXX it multiplies recurring fee by cust_pkg option "units", how to
+ # express that
+ 'shortname' => 'Bulk (manual from "units" option)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring Base fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'externalid' => { 'name' => 'Optional External ID',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit',
+ 'externalid' ],
+ 'weight' => 52,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ my $i = 0;
+ my $count = $self->option( 'additional_count', 'quiet' ) || 0;
+ while ($i < $count) {
+ push @$details, $self->option( 'additional_info' . $i++ );
+ }
+
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my($self, $cust_pkg) = @_;
+ $self->base_recur($cust_pkg);
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ my $units = $cust_pkg->option('units') ? $cust_pkg->option('units') : 1 ;
+ # default to 1 if not found
+ sprintf("%.2f",
+ ($self->option('recur_fee') * $units )
+ );
+}
+
+sub calc_remain {
+ my ($self, $cust_pkg) = @_;
+ my $time = time; #should be able to pass this in for credit calculation
+ my $next_bill = $cust_pkg->getfield('bill') || 0;
+ my $last_bill = $cust_pkg->last_bill || 0;
+ return 0 if ! $self->base_recur($cust_pkg)
+ || ! $self->option('unused_credit', 1)
+ || ! $last_bill
+ || ! $next_bill
+ || $next_bill < $time;
+
+ my %sec = (
+ 'h' => 3600, # 60 * 60
+ 'd' => 86400, # 60 * 60 * 24
+ 'w' => 604800, # 60 * 60 * 24 * 7
+ 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12
+ );
+
+ $self->freq =~ /^(\d+)([hdwm]?)$/
+ or die 'unparsable frequency: '. $self->freq;
+ my $freq_sec = $1 * $sec{$2||'m'};
+ return 0 unless $freq_sec;
+
+ sprintf("%.2f", $self->base_recur($cust_pkg) * ( $next_bill - $time ) / $freq_sec );
+
+}
+
+sub is_free_options {
+ qw( setup_fee recur_fee );
+}
+
+sub is_prepaid {
+ 0; #no, we're postpaid
+}
+
+1;
diff --git a/FS/FS/part_pkg/bulk.pm b/FS/FS/part_pkg/bulk.pm
new file mode 100644
index 0000000..69fe98e
--- /dev/null
+++ b/FS/FS/part_pkg/bulk.pm
@@ -0,0 +1,108 @@
+package FS::part_pkg::bulk;
+
+use strict;
+use vars qw(@ISA $DEBUG $me %info);
+use Date::Format;
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+$DEBUG = 1;
+$me = '[FS::part_pkg::bulk]';
+
+%info = (
+ 'name' => 'Bulk billing based on number of active services',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for the entire bulk package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for the entire bulk package',
+ 'default' => 0,
+ },
+ 'svc_setup_fee' => { 'name' => 'Setup fee for each new service',
+ 'default' => 0,
+ },
+ 'svc_recur_fee' => { 'name' => 'Recurring fee for each service',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'svc_setup_fee', 'svc_recur_fee',
+ 'unused_credit', ],
+ 'weight' => 50,
+);
+
+#some false laziness-ish w/agent.pm... not a lot
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ my $conf = new FS::Conf;
+ my $money_char = $conf->config('money_char') || '$';
+
+ my $svc_setup_fee = $self->option('svc_setup_fee');
+
+ my $last_bill = $cust_pkg->last_bill;
+
+ return sprintf("%.2f", $self->base_recur($cust_pkg) )
+ unless $$sdate > $last_bill;
+
+ my $total_svc_charge = 0;
+
+ warn "$me billing for bulk services from ". time2str('%x', $last_bill).
+ " to ". time2str('%x', $$sdate). "\n"
+ if $DEBUG;
+
+ # END START
+ foreach my $h_cust_svc ( $cust_pkg->h_cust_svc( $$sdate, $last_bill ) ) {
+
+ my @label = $h_cust_svc->label_long( $$sdate, $last_bill );
+ die "fatal: no historical label found, wtf?" unless scalar(@label); #?
+ my $svc_details = $label[0]. ': '. $label[1]. ': ';
+
+ my $svc_charge = 0;
+
+ my $svc_start = $h_cust_svc->date_inserted;
+ if ( $svc_start < $last_bill ) {
+ $svc_start = $last_bill;
+ } elsif ( $svc_setup_fee ) {
+ $svc_charge += $svc_setup_fee;
+ $svc_details .= $money_char. sprintf('%.2f setup, ', $svc_setup_fee);
+ }
+
+ my $svc_end = $h_cust_svc->date_deleted;
+ $svc_end = ( !$svc_end || $svc_end > $$sdate ) ? $$sdate : $svc_end;
+
+ my $recur_charge =
+ $self->option('svc_recur_fee') * ( $svc_end - $svc_start )
+ / ( $$sdate - $last_bill );
+
+ $svc_details .= $money_char. sprintf('%.2f', $recur_charge ).
+ ' ('. time2str('%x', $svc_start).
+ ' - '. time2str('%x', $svc_end ). ')'
+ if $recur_charge;
+
+ $svc_charge += $recur_charge;
+
+ push @$details, $svc_details;
+ $total_svc_charge += $svc_charge;
+
+ }
+
+ sprintf('%.2f', $self->base_recur($cust_pkg) + $total_svc_charge );
+}
+
+sub can_discount { 0; }
+
+sub hide_svc_detail {
+ 1;
+}
+
+sub is_free_options {
+ qw( setup_fee recur_fee svc_setup_fee svc_recur_fee );
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/cdr_termination.pm b/FS/FS/part_pkg/cdr_termination.pm
new file mode 100644
index 0000000..cfd6b4e
--- /dev/null
+++ b/FS/FS/part_pkg/cdr_termination.pm
@@ -0,0 +1,212 @@
+package FS::part_pkg::cdr_termination;
+
+use strict;
+use base qw( FS::part_pkg::recur_Common );
+use vars qw( $DEBUG %info );
+use Tie::IxHash;
+use FS::Record qw( qsearch ); #qsearchs );
+use FS::cdr;
+use FS::cdr_termination;
+
+tie my %temporalities, 'Tie::IxHash',
+ 'upcoming' => "Upcoming (future)",
+ 'preceding' => "Preceding (past)",
+;
+
+%info = (
+ 'name' => 'VoIP rating of CDR records for termination partners.',
+ 'shortname' => 'VoIP/telco CDR termination',
+ 'fields' => {
+
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+
+ #'cdr_column' => { 'name' => 'Column from CDR records',
+ # 'type' => 'select',
+ # 'select_enum' => [qw(
+ # dcontext
+ # channel
+ # dstchannel
+ # lastapp
+ # lastdata
+ # accountcode
+ # userfield
+ # cdrtypenum
+ # calltypenum
+ # description
+ # carrierid
+ # upstream_rateid
+ # )],
+ # },
+
+ #false laziness w/flat.pm
+ 'recur_temporality' => { 'name' => 'Charge recurring fee for period',
+ 'type' => 'select',
+ 'select_options' => \%temporalities,
+ },
+
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '.
+ 'subscription',
+ 'default' => '1',
+ },
+
+ 'recur_method' => { 'name' => 'Recurring fee method',
+ #'type' => 'radio',
+ #'options' => \%recur_method,
+ 'type' => 'select',
+ 'select_options' => \%FS::part_pkg::recur_Common::recur_method,
+ },
+
+ #false laziness w/voip_cdr.pm
+ 'output_format' => { 'name' => 'CDR invoice display format',
+ 'type' => 'select',
+ 'select_options' => { FS::cdr::invoice_formats() },
+ 'default' => 'simple2', #XXX test
+ },
+
+ 'usage_section' => { 'name' => 'Section in which to place separate usage charges',
+ },
+
+ 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section',
+ 'type' => 'checkbox',
+ },
+
+ 'usage_mandate' => { 'name' => 'Always put usage details in separate section',
+ 'type' => 'checkbox',
+ },
+ #eofalse
+
+ },
+ #cdr_column
+ 'fieldorder' => [qw(
+ setup_fee recur_fee
+ recur_temporality unused_credit recur_method cutoff_day
+ output_format usage_section summarize_usage usage_mandate
+ )
+ ],
+
+ 'weight' => 48,
+
+);
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ #my $last_bill = $cust_pkg->last_bill;
+ my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
+
+ return 0
+ if $self->option('recur_temporality', 1) eq 'preceding'
+ && ( $last_bill eq '' || $last_bill == 0 );
+
+ # termination calculations
+
+ my $term_percent = $cust_pkg->cust_main->cdr_termination_percentage;
+ die "no customer termination percentage" unless $term_percent;
+
+ my $output_format = $self->option('output_format', 'Hush!') || 'simple2';
+
+ my $charges = 0;
+
+ #find an svc_external record
+ my @svc_external = map { $_->svc_x }
+ grep { $_->part_svc->svcdb eq 'svc_external' }
+ $cust_pkg->cust_svc;
+
+ die "cdr_termination package has no svc_external service"
+ unless @svc_external;
+ die "cdr_termination package has multiple svc_external services"
+ if scalar(@svc_external) > 1;
+
+ my $svc_external = $svc_external[0];
+
+ # find CDRs:
+ # - matching our customer via svc_external.id/title? (and via what field?)
+
+ #let's try carrierid for now, can always make it configurable or rewrite
+ my $cdr_column = 'carrierid';
+
+ my %hashref = ( 'freesidestatus' => 'done' );
+
+ # try matching on svc_external.id for now... (or title? if ints don't cut it)
+ $hashref{$cdr_column} = $svc_external[0]->id;
+
+ # - with no cdr_termination.status
+
+ my $termpart = 1; #or from an option
+
+ #false lazienss w/search/cdr.html (i should be a part_termination method)
+ my $where_term =
+ "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart ) ";
+ #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
+ my $extra_sql =
+ "AND NOT EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
+
+ #may need to process in batches if there's waaay too many
+ my @cdrs = qsearch({
+ 'table' => 'cdr',
+ #'addl_from' => $join_term,
+ 'hashref' => \%hashref,
+ 'extra_sql' => "$extra_sql FOR UPDATE",
+ });
+
+ foreach my $cdr (@cdrs) {
+
+ #add a cdr_termination record and the charges
+
+ # XXX config?
+ #my $term_price = sprintf('%.2f', $cdr->rated_price * $term_percent / 100 );
+ my $term_price = sprintf('%.4f', $cdr->rated_price * $term_percent / 100 );
+
+ my $cdr_termination = new FS::cdr_termination {
+ 'acctid' => $cdr->acctid,
+ 'termpart' => $termpart,
+ 'rated_price' => $term_price,
+ 'status' => 'done',
+ };
+
+ my $error = $cdr_termination->insert;
+ die $error if $error; #next if $error; #or just skip this one??? why?
+
+ $charges += $term_price;
+
+ # and add a line to the invoice
+
+ my $call_details = $cdr->downstream_csv( 'format' => $output_format,
+ 'charge' => $term_price,
+ );
+
+ my $classnum = ''; #usage class?
+
+ #option to turn off? or just use squelch_cdr for the customer probably
+ push @$details, [ 'C', $call_details, $term_price, $classnum ];
+
+ }
+
+ # eotermiation calculation
+
+ $charges += $self->calc_recur_Common(@_);
+
+ $charges;
+}
+
+sub is_free {
+ 0;
+}
+
+1;
diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm
new file mode 100644
index 0000000..6db6eee
--- /dev/null
+++ b/FS/FS/part_pkg/flat.pm
@@ -0,0 +1,312 @@
+package FS::part_pkg::flat;
+
+use strict;
+use vars qw( @ISA %info
+ %usage_fields %usage_recharge_fields
+ @usage_fieldorder @usage_recharge_fieldorder
+ );
+use Tie::IxHash;
+use List::Util qw(min); # max);
+#use FS::Record qw(qsearch);
+use FS::UI::bytecount;
+use FS::Conf;
+use FS::part_pkg;
+use FS::cust_bill_pkg_discount;
+
+@ISA = qw(FS::part_pkg);
+
+tie my %temporalities, 'Tie::IxHash',
+ 'upcoming' => "Upcoming (future)",
+ 'preceding' => "Preceding (past)",
+;
+
+%usage_fields = (
+
+ 'seconds' => { 'name' => 'Time limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'upbytes' => { 'name' => 'Upload limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'downbytes' => { 'name' => 'Download limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'totalbytes' => { 'name' => 'Transfer limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+);
+
+%usage_recharge_fields = (
+
+ 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
+ },
+ 'recharge_seconds' => { 'name' => 'Recharge time for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_downbytes' => { 'name' => 'Recharge download for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
+ ' over into current period',
+ 'type' => 'checkbox',
+ },
+ 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
+ 'package recharge',
+ 'type' => 'checkbox',
+ },
+);
+
+@usage_fieldorder = qw( seconds upbytes downbytes totalbytes );
+@usage_recharge_fieldorder = qw(
+ recharge_amount recharge_seconds recharge_upbytes
+ recharge_downbytes recharge_totalbytes
+ usage_rollover recharge_reset
+);
+
+%info = (
+ 'name' => 'Flat rate (anniversary billing)',
+ 'shortname' => 'Anniversary',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+
+ #false laziness w/voip_cdr.pm
+ 'recur_temporality' => { 'name' => 'Charge recurring fee for period',
+ 'type' => 'select',
+ 'select_options' => \%temporalities,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+
+ #used in cust_pkg.pm so could add to any price plan
+ 'expire_months' => { 'name' => 'Auto-add an expiration date this number of months out',
+ },
+ #used in cust_pkg.pm so could add to any price plan where it made sense
+ 'start_1st' => { 'name' => 'Auto-add a start date to the 1st, ignoring the current month.',
+ 'type' => 'checkbox',
+ },
+
+ %usage_fields,
+ %usage_recharge_fields,
+
+ 'externalid' => { 'name' => 'Optional External ID',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [ qw( setup_fee recur_fee
+ recur_temporality unused_credit
+ expire_months start_1st
+ ),
+ @usage_fieldorder, @usage_recharge_fieldorder,
+ qw( externalid ),
+ ],
+ 'weight' => 10,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ my $i = 0;
+ my $count = $self->option( 'additional_count', 'quiet' ) || 0;
+ while ($i < $count) {
+ push @$details, $self->option( 'additional_info' . $i++ );
+ }
+
+ my $quantity = $cust_pkg->quantity || 1;
+
+ sprintf("%.2f", $quantity * $self->unit_setup($cust_pkg, $sdate, $details) );
+}
+
+sub unit_setup {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+
+ #my $last_bill = $cust_pkg->last_bill;
+ my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
+
+ return 0
+ if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0;
+
+ my $br = $self->base_recur($cust_pkg);
+
+ my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
+
+ sprintf('%.2f', $br - $discount);
+}
+
+sub calc_discount {
+ my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $br = $self->base_recur($cust_pkg);
+
+ my $tot_discount = 0;
+ #UI enforces just 1 for now, will need ordering when they can be stacked
+ my @cust_pkg_discount = $cust_pkg->cust_pkg_discount_active;
+ foreach my $cust_pkg_discount ( @cust_pkg_discount ) {
+ my $discount = $cust_pkg_discount->discount;
+ #UI enforces one or the other (for now? probably for good)
+ my $amount = 0;
+ $amount += $discount->amount;
+ $amount += sprintf('%.2f', $discount->percent * $br / 100 );
+
+ my $chg_months = $param->{'months'} || $cust_pkg->part_pkg->freq;
+
+ my $months = $discount->months
+ ? min( $chg_months,
+ $discount->months - $cust_pkg->months_used )
+ : $chg_months;
+
+ my $error = $cust_pkg_discount->increment_months_used($months);
+ die "error discounting: $error" if $error;
+
+ $amount *= $months;
+ $amount = sprintf('%.2f', $amount);
+
+ next unless $amount > 0;
+
+ #record details in cust_bill_pkg_discount
+ my $cust_bill_pkg_discount = new FS::cust_bill_pkg_discount {
+ 'pkgdiscountnum' => $cust_pkg_discount->pkgdiscountnum,
+ 'amount' => $amount,
+ 'months' => $months,
+ };
+ push @{ $param->{'discounts'} }, $cust_bill_pkg_discount;
+
+ #add details on discount to invoice
+ my $conf = new FS::Conf;
+ my $money_char = $conf->config('money_char') || '$';
+ $months = sprintf('%.2f', $months) if $months =~ /\./;
+
+ my $d = 'Includes ';
+ $d .= $discount->name. ' ' if $discount->name;
+ $d .= 'discount of '. $discount->description_short;
+ $d .= " for $months month". ( $months!=1 ? 's' : '' );
+ $d .= ": $money_char$amount" if $months != 1 || $discount->percent;
+ push @$details, $d;
+
+ $tot_discount += $amount;
+ }
+
+ sprintf('%.2f', $tot_discount);
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee', 1) || 0;
+}
+
+sub base_recur_permonth {
+ my($self, $cust_pkg) = @_;
+
+ return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0;
+
+ sprintf('%.2f', $self->base_recur($cust_pkg) / $self->freq );
+}
+
+sub calc_remain {
+ my ($self, $cust_pkg, %options) = @_;
+
+ my $time;
+ if ($options{'time'}) {
+ $time = $options{'time'};
+ } else {
+ $time = time;
+ }
+
+ my $next_bill = $cust_pkg->getfield('bill') || 0;
+
+ #my $last_bill = $cust_pkg->last_bill || 0;
+ my $last_bill = $cust_pkg->get('last_bill') || 0; #->last_bill falls back to setup
+
+ return 0 if ! $self->base_recur($cust_pkg)
+ || ! $self->option('unused_credit', 1)
+ || ! $last_bill
+ || ! $next_bill
+ || $next_bill < $time;
+
+ my %sec = (
+ 'h' => 3600, # 60 * 60
+ 'd' => 86400, # 60 * 60 * 24
+ 'w' => 604800, # 60 * 60 * 24 * 7
+ 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12
+ );
+
+ $self->freq =~ /^(\d+)([hdwm]?)$/
+ or die 'unparsable frequency: '. $self->freq;
+ my $freq_sec = $1 * $sec{$2||'m'};
+ return 0 unless $freq_sec;
+
+ sprintf("%.2f", $self->base_recur($cust_pkg) * ( $next_bill - $time ) / $freq_sec );
+
+}
+
+sub is_free_options {
+ qw( setup_fee recur_fee );
+}
+
+sub is_prepaid { 0; } #no, we're postpaid
+
+#XXX discounts only on recurring fees for now (no setup/one-time or usage)
+sub can_discount {
+ my $self = shift;
+ $self->freq =~ /^\d+$/ && $self->freq > 0;
+}
+
+sub usage_valuehash {
+ my $self = shift;
+ map { $_, $self->option($_) }
+ grep { $self->option($_, 'hush') }
+ qw(seconds upbytes downbytes totalbytes);
+}
+
+sub reset_usage {
+ my($self, $cust_pkg, %opt) = @_;
+ warn " resetting usage counters" if defined($opt{debug}) && $opt{debug} > 1;
+ my %values = $self->usage_valuehash;
+ if ($self->option('usage_rollover', 1)) {
+ $cust_pkg->recharge(\%values);
+ }else{
+ $cust_pkg->set_usage(\%values, %opt);
+ }
+}
+
+1;
diff --git a/FS/FS/part_pkg/flat_comission.pm b/FS/FS/part_pkg/flat_comission.pm
new file mode 100644
index 0000000..c05e455
--- /dev/null
+++ b/FS/FS/part_pkg/flat_comission.pm
@@ -0,0 +1,69 @@
+package FS::part_pkg::flat_comission;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Flat rate with recurring commission per (any) active package',
+ 'shortname' => '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,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'comission_amount' => { 'name' => 'Commission amount per month (per active package)',
+ 'default' => 0,
+ },
+ 'comission_depth' => { 'name' => 'Number of layers',
+ 'default' => 1,
+ },
+ 'reason_type' => { 'name' => 'Reason type for commission credits',
+ 'type' => 'select',
+ 'select_table' => 'reason_type',
+ 'select_hash' => { 'class' => 'R' },
+ 'select_key' => 'typenum',
+ 'select_label' => 'type',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'reason_type' ],
+ #'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 + \';\'',
+ 'weight' => 62,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg ) = @_;
+
+ my $amount = $self->option('comission_amount');
+ my $num_active = scalar(
+ $cust_pkg->cust_main->referral_cust_pkg( $self->option('comission_depth') )
+ );
+
+ my $commission = sprintf('%.2f', $amount*$num_active);
+
+ if ( $commission > 0 ) {
+
+ my $error =
+ $cust_pkg->cust_main->credit( $commission, "commission",
+ 'reason_type'=>$self->option('reason_type'),
+ );
+ die $error if $error;
+
+ }
+
+ $self->option('recur_fee');
+}
+
+sub can_discount { 0; }
+
+1;
diff --git a/FS/FS/part_pkg/flat_comission_cust.pm b/FS/FS/part_pkg/flat_comission_cust.pm
new file mode 100644
index 0000000..5db4658
--- /dev/null
+++ b/FS/FS/part_pkg/flat_comission_cust.pm
@@ -0,0 +1,67 @@
+package FS::part_pkg::flat_comission_cust;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Flat rate with recurring commission per active customer',
+ 'shortname' => '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,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'comission_amount' => { 'name' => 'Commission amount per month (per active customer)',
+ 'default' => 0,
+ },
+ 'comission_depth' => { 'name' => 'Number of layers',
+ 'default' => 1,
+ },
+ 'reason_type' => { 'name' => 'Reason type for commission credits',
+ 'type' => 'select_table',
+ 'select_table' => 'reason_type',
+ 'select_hash' => { 'class' => 'R' },
+ 'select_key' => 'typenum',
+ 'select_label' => 'type',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'reason_type' ],
+ #'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 + \';\'',
+ 'weight' => '60',
+);
+
+sub calc_recur {
+ my($self, $cust_pkg ) = @_;
+
+ my $amount = $self->option('comission_amount');
+ my $num_active = scalar(
+ $cust_pkg->cust_main->referral_cust_main_ncancelled(
+ $self->option('comission_depth')
+ )
+ );
+
+ if ( $amount && $num_active ) {
+ my $error =
+ $cust_pkg->cust_main->credit( $amount*$num_active, "commission",
+ 'reason_type'=>$self->option('reason_type'),
+ );
+ die $error if $error;
+ }
+
+ $self->option('recur_fee');
+}
+
+sub can_discount { 0; }
+
+1;
diff --git a/FS/FS/part_pkg/flat_comission_pkg.pm b/FS/FS/part_pkg/flat_comission_pkg.pm
new file mode 100644
index 0000000..6f5ee69
--- /dev/null
+++ b/FS/FS/part_pkg/flat_comission_pkg.pm
@@ -0,0 +1,60 @@
+package FS::part_pkg::flat_comission_pkg;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Flat rate with recurring commission per (selected) active package',
+ 'shortname' => '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,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ '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',
+ },
+ 'reason_type' => { 'name' => 'Reason type for commission credits',
+ 'type' => 'select',
+ 'select_table' => 'reason_type',
+ 'select_hash' => { 'class' => 'R' } ,
+ 'select_key' => 'typenum',
+ 'select_label' => 'type',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'comission_depth', 'comission_amount', 'comission_pkgpart', 'reason_type' ],
+ #'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 + \';\'',
+ #'disabled' => 1,
+ 'weight' => '64',
+);
+
+# XXX this needs to be fixed!!!
+sub calc_recur {
+ my($self, $cust_pkg ) = @_;
+ $self->option('recur_fee');
+}
+
+sub can_discount { 0; }
+
+1;
diff --git a/FS/FS/part_pkg/flat_delayed.pm b/FS/FS/part_pkg/flat_delayed.pm
new file mode 100644
index 0000000..33f9dd8
--- /dev/null
+++ b/FS/FS/part_pkg/flat_delayed.pm
@@ -0,0 +1,69 @@
+package FS::part_pkg::flat_delayed;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Free (or setup fee) for X days, then flat rate'.
+ ' (anniversary billing)',
+ 'shortname' => 'Anniversary, with intro period',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'free_days' => { 'name' => 'Initial free days',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'recur_notify' => { 'name' => 'Number of days before recurring billing'.
+ ' commences to notify customer. (0 means'.
+ ' no warning)',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ },
+ 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'recur_notify',
+ 'unused_credit'
+ ],
+ #'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',
+ 'weight' => 12,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg, $time ) = @_;
+
+ my $d = $cust_pkg->bill || $time;
+ $d += 86400 * $self->option('free_days');
+ $cust_pkg->bill($d);
+
+ $self->option('setup_fee');
+}
+
+sub calc_remain {
+ my ($self, $cust_pkg, %options) = @_;
+ my $next_bill = $cust_pkg->getfield('bill') || 0;
+ my $last_bill = $cust_pkg->last_bill || 0;
+ my $free_days = $self->option('free_days');
+
+ return 0 if $last_bill + (86400 * $free_days) == $next_bill
+ && $last_bill == $cust_pkg->setup;
+
+ return 0 if ! $self->base_recur($cust_pkg)
+ || ! $self->option('unused_credit', 1)
+ || ! $last_bill
+ || ! $next_bill;
+
+ return $self->SUPER::calc_remain($cust_pkg, %options);
+}
+
+1;
diff --git a/FS/FS/part_pkg/flat_introrate.pm b/FS/FS/part_pkg/flat_introrate.pm
new file mode 100644
index 0000000..2d551f1
--- /dev/null
+++ b/FS/FS/part_pkg/flat_introrate.pm
@@ -0,0 +1,60 @@
+package FS::part_pkg::flat_introrate;
+
+use strict;
+use vars qw(@ISA %info $DEBUG $me);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+use Date::Manip qw(DateCalc UnixDate ParseDate);
+
+@ISA = qw(FS::part_pkg::flat);
+$me = '[' . __PACKAGE__ . ']';
+$DEBUG = 0;
+
+(%info) = (%FS::part_pkg::flat::info);
+$info{name} = 'Introductory price for X months, then flat rate,'.
+ 'relative to setup date (anniversary billing)';
+$info{shortname} = 'Anniversary, with intro price';
+$info{fields} = { %{$info{fields}} };
+$info{fields}{intro_fee} =
+ { 'name' => 'Introductory recurring fee for this package',
+ 'default' => 0,
+ };
+$info{fields}{intro_duration} =
+ { 'name' => 'Duration of the introductory period, in number of months',
+ 'default' => 0,
+ };
+$info{fieldorder} = [ @{ $info{fieldorder} } ];
+splice @{$info{fieldorder}}, 1, 0, qw( intro_duration intro_fee );
+$info{weight} = 14;
+
+sub base_recur {
+ my($self, $cust_pkg, $time ) = @_;
+
+ my $now = $time ? $$time : time;
+
+ my ($duration) = ($self->option('intro_duration') =~ /^(\d+)$/);
+ unless ($duration) {
+ die "Invalid intro_duration: " . $self->option('intro_duration');
+ }
+
+ my $setup = &ParseDate('epoch ' . $cust_pkg->getfield('setup'));
+ my $intro_end = &DateCalc($setup, "+${duration} month");
+ my $recur;
+
+ warn "$me: \$duration = ${duration}" if $DEBUG;
+ warn "$me: \$intro_end = ${intro_end}" if $DEBUG;
+ warn "$me: $now < " . &UnixDate($intro_end, '%s') if $DEBUG;
+
+ if ($now < &UnixDate($intro_end, '%s')) {
+ $recur = $self->option('intro_fee');
+ } else {
+ $recur = $self->option('recur_fee');
+ }
+
+ $recur;
+
+}
+
+
+1;
diff --git a/FS/FS/part_pkg/incomplete/billoneday.pm b/FS/FS/part_pkg/incomplete/billoneday.pm
new file mode 100644
index 0000000..8740547
--- /dev/null
+++ b/FS/FS/part_pkg/incomplete/billoneday.pm
@@ -0,0 +1,48 @@
+package FS::part_pkg::billoneday;
+
+use strict;
+use vars qw(@ISA %info);
+use Time::Local qw(timelocal);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'charge a full month every (selectable) billing day',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'cutoff_day' => { 'name' => 'billing day',
+ 'default' => 1,
+ },
+
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'],
+ #'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,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value',
+ 'freq' => 'm',
+ 'weight' => 30,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg, $sdate ) = @_;
+
+ my $mnow = $$sdate;
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
+ my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year);
+ my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11));
+
+ if($mday > $self->option('cutoff_date') and $mstart != $mnow ) {
+ $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11));
+ }
+ else{
+ $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year);
+ }
+ $self->option('recur_fee');
+}
+1;
diff --git a/FS/FS/part_pkg/prepaid.pm b/FS/FS/part_pkg/prepaid.pm
new file mode 100644
index 0000000..cff165a
--- /dev/null
+++ b/FS/FS/part_pkg/prepaid.pm
@@ -0,0 +1,54 @@
+package FS::part_pkg::prepaid;
+
+use strict;
+use vars qw(@ISA %info %recur_action);
+use Tie::IxHash;
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+tie %recur_action, 'Tie::IxHash',
+ 'suspend' => 'suspend',
+ 'cancel' => 'cancel',
+;
+
+tie my %overlimit_action, 'Tie::IxHash',
+ 'overlimit' => 'Default overlimit processing',
+ 'cancel' => 'Cancel',
+;
+
+%info = (
+ 'name' => 'Prepaid, flat rate',
+ #'name' => 'Prepaid (no automatic recurring)', #maybe use it here too
+ 'shortname' => 'Prepaid, no automatic cycle',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'One-time setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Initial and recharge fee for this package',
+ 'default' => 0,
+ },
+ 'recur_action' => { 'name' => 'Action to take upon reaching end of prepaid preiod',
+ 'type' => 'select',
+ 'select_options' => \%recur_action,
+ },
+ %FS::part_pkg::flat::usage_fields,
+ 'overlimit_action' => { 'name' => 'Action to take upon reaching a usage limit.',
+ 'type' => 'select',
+ 'select_options' => \%overlimit_action,
+ },
+ #XXX if you set overlimit_action to 'cancel', should also have the ability
+ # to select a reason
+ },
+ 'fieldorder' => [ qw( setup_fee recur_fee recur_action ),
+ @FS::part_pkg::flat::usage_fieldorder, 'overlimit_action',
+ ],
+ 'weight' => 25,
+);
+
+sub is_prepaid {
+ 1;
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm
new file mode 100644
index 0000000..09561cf
--- /dev/null
+++ b/FS/FS/part_pkg/prorate.pm
@@ -0,0 +1,128 @@
+package FS::part_pkg::prorate;
+
+use strict;
+use vars qw(@ISA %info);
+use Time::Local qw(timelocal);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'First partial month pro-rated, then flat-rate (selectable billing day)',
+ 'shortname' => 'Prorate (Nth of month billing)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28)',
+ 'default' => 1,
+ },
+ 'seconds' => { 'name' => 'Time limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'upbytes' => { 'name' => 'Upload limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'downbytes' => { 'name' => 'Download limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'totalbytes' => { 'name' => 'Transfer limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
+ },
+ 'recharge_seconds' => { 'name' => 'Recharge time for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
+ 'over into current period',
+ 'type' => 'checkbox',
+ },
+ 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
+ 'package recharge',
+ 'type' => 'checkbox',
+ },
+
+ #it would be better if this had to be turned on, its confusing
+ 'externalid' => { 'name' => 'Optional External ID',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'cutoff_day',
+ 'seconds', 'upbytes', 'downbytes', 'totalbytes',
+ 'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
+ 'recharge_downbytes', 'recharge_totalbytes',
+ 'usage_rollover', 'recharge_reset', 'externalid', ],
+ 'freq' => 'm',
+ 'weight' => 20,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+ my $cutoff_day = $self->option('cutoff_day', 1) || 1;
+ my $mnow = $$sdate;
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
+ my $mend;
+ my $mstart;
+
+ if ( $mday >= $cutoff_day ) {
+ $mend =
+ timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11));
+ $mstart =
+ timelocal(0,0,0,$cutoff_day,$mon,$year);
+
+ } else {
+ $mend = timelocal(0,0,0,$cutoff_day, $mon, $year);
+ if ($mon==0) {$mon=11;$year--;} else {$mon--;}
+ $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year);
+ }
+
+ $$sdate = $mstart;
+ my $permonth = $self->option('recur_fee') / $self->freq;
+
+ my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
+
+ $param->{'months'} = $months;
+ my $discount = $self->calc_discount( $cust_pkg, $sdate, $details, $param);
+
+ sprintf('%.2f', $permonth * $months - $discount);
+}
+
+1;
diff --git a/FS/FS/part_pkg/prorate_delayed.pm b/FS/FS/part_pkg/prorate_delayed.pm
new file mode 100644
index 0000000..0073493
--- /dev/null
+++ b/FS/FS/part_pkg/prorate_delayed.pm
@@ -0,0 +1,67 @@
+package FS::part_pkg::prorate_delayed;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg;
+
+@ISA = qw(FS::part_pkg::prorate);
+
+%info = (
+ 'name' => 'Free (or setup fee) for X days, then prorate, then flat-rate ' .
+ '(1st of month billing)',
+ 'shortname' => 'Prorate (Nth of month billing), with intro period', #??
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'free_days' => { 'name' => 'Initial free days',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'recur_notify' => { 'name' => 'Number of days before recurring billing'.
+ ' commences to notify customer. (0 means'.
+ ' no warning)',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ },
+ 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee', 'unused_credit' ],
+ #'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',
+ 'weight' => 22,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg, $time ) = @_;
+
+ my $d = $cust_pkg->bill || $time;
+ $d += 86400 * $self->option('free_days');
+ $cust_pkg->bill($d);
+
+ $self->option('setup_fee');
+}
+
+sub calc_remain {
+ my ($self, $cust_pkg, %options) = @_;
+ my $next_bill = $cust_pkg->getfield('bill') || 0;
+ my $last_bill = $cust_pkg->last_bill || 0;
+ my $free_days = $self->option('free_days');
+
+ return 0 if $last_bill + (86400 * $free_days) == $next_bill
+ && $last_bill == $cust_pkg->setup;
+
+ return 0 if ! $self->base_recur($cust_pkg)
+ || ! $self->option('unused_credit', 1)
+ || ! $last_bill
+ || ! $next_bill;
+
+ return $self->SUPER::calc_remain($cust_pkg, %options);
+}
+
+1;
diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm
new file mode 100644
index 0000000..8ed9eb6
--- /dev/null
+++ b/FS/FS/part_pkg/recur_Common.pm
@@ -0,0 +1,61 @@
+package FS::part_pkg::recur_Common;
+
+use strict;
+use vars qw( @ISA %info %recur_method );
+use Tie::IxHash;
+use Time::Local;
+use FS::part_pkg::prorate;
+
+@ISA = qw(FS::part_pkg::prorate);
+
+%info = ( 'disabled' => 1 ); #recur_Common not a usable price plan directly
+
+tie %recur_method, 'Tie::IxHash',
+ 'anniversary' => 'Charge the recurring fee at the frequency specified above',
+ 'prorate' => 'Charge a prorated fee the first time (selectable billing date)',
+ 'subscription' => 'Charge the full fee for the first partial period (selectable billing date)',
+;
+
+sub calc_recur_Common {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_; #only need $sdate & $param
+
+ my $charges = 0;
+
+ if ( $param->{'increment_next_bill'} ) {
+
+ my $recur_method = $self->option('recur_method', 1) || 'anniversary';
+
+ if ( $recur_method eq 'prorate' ) {
+
+ $charges = $self->SUPER::calc_recur(@_);
+
+ } else {
+
+ $charges = $self->option('recur_fee');
+
+ if ( $recur_method eq 'subscription' ) {
+
+ my $cutoff_day = $self->option('cutoff_day', 1) || 1;
+ my ($day, $mon, $year) = ( localtime($$sdate) )[ 3..5 ];
+
+ if ( $day < $cutoff_day ) {
+ if ( $mon == 0 ) { $mon=11; $year--; }
+ else { $mon--; }
+ }
+
+ $$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year);
+
+ }#$recur_method eq 'subscription'
+
+ $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param );
+
+ }#$recur_method eq 'prorate'
+
+ }#increment_next_bill
+
+ $charges;
+
+}
+
+1;
diff --git a/FS/FS/part_pkg/rt_time.pm b/FS/FS/part_pkg/rt_time.pm
new file mode 100644
index 0000000..9452d44
--- /dev/null
+++ b/FS/FS/part_pkg/rt_time.pm
@@ -0,0 +1,70 @@
+package FS::part_pkg::rt_time;
+
+use strict;
+use FS::Conf;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::recur_Common;
+use Carp qw(cluck);
+
+our @ISA = qw(FS::part_pkg::recur_Common);
+
+our $DEBUG = 0;
+
+our %info = (
+ 'name' => 'Bill from Time Worked on tickets in RT',
+ 'shortname' => 'Project Billing (RT)',
+ 'weight' => 55,
+ 'fields' => {
+ 'base_rate' => { 'name' => 'Rate (per minute)',
+ 'default' => 0,
+ },
+ }
+);
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $charges = 0;
+
+ $charges += $self->calc_usage(@_);
+ $charges += $self->calc_recur_Common(@_);
+
+ $charges;
+
+}
+
+sub can_discount { 0; }
+
+sub calc_cancel {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ $self->calc_usage(@_);
+}
+
+sub calc_usage {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $last_bill = $cust_pkg->get('last_bill') || $cust_pkg->get('setup');
+ my @tickets = @{ FS::TicketSystem->comments_on_tickets( $cust_pkg->custnum, 100, $last_bill ) };
+
+ my $charges = 0;
+
+ my $rate = $self->option('base_rate');
+
+ foreach my $ding ( @tickets) {
+ $charges += sprintf('%.2f', $ding->{'timetaken'} * $rate);
+ push @$details, join( ", ", ("($ding->{timetaken}) Minutes", substr($ding->{'content'},0,255)));
+ }
+ cluck $rate, $charges, @$details if $DEBUG > 0;
+ return $charges;
+}
+
+1;
diff --git a/FS/FS/part_pkg/sesmon_hour.pm b/FS/FS/part_pkg/sesmon_hour.pm
new file mode 100644
index 0000000..e608d55
--- /dev/null
+++ b/FS/FS/part_pkg/sesmon_hour.pm
@@ -0,0 +1,59 @@
+package FS::part_pkg::sesmon_hour;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Base charge plus charge per-hour from the session monitor',
+ 'shortname' => 'Session monitor (per-hour)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'recur_included_hours' => { 'name' => 'Hours included',
+ 'default' => 0,
+ },
+ 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', '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_fee.value + \' + \' + what.recur_hourly_charge.value + \' * $hours;\'',
+ 'weight' => 80,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg ) = @_;
+
+ my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600;
+ $hours -= $self->option('recur_included_hours');
+ $hours = 0 if $hours < 0;
+
+ $self->option('recur_fee') + $hours * $self->option('recur_hourly_charge');
+
+}
+
+sub can_discount { 0; }
+
+sub is_free_options {
+ qw( setup_fee recur_fee recur_hourly_charge );
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sesmon_minute.pm b/FS/FS/part_pkg/sesmon_minute.pm
new file mode 100644
index 0000000..654fdf5
--- /dev/null
+++ b/FS/FS/part_pkg/sesmon_minute.pm
@@ -0,0 +1,58 @@
+package FS::part_pkg::sesmon_minute;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Base charge plus charge per-minute from the session monitor',
+ 'shortname' => 'Session monitor (per-minute)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'recur_included_min' => { 'name' => 'Minutes included',
+ 'default' => 0,
+ },
+ 'recur_minly_charge' => { 'name' => 'Additional charge per minute',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', '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_fee.value + \' + \' + what.recur_minly_charge.value + \' * $min;\'',
+ 'weight' => 80,
+);
+
+
+sub calc_recur {
+ my( $self, $cust_pkg ) = @);
+ my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60;
+ $min -= $self->option('recur_included_min');
+ $min = 0 if $min < 0;
+
+ $self->option('recur_fee') + $min * $self->option('recur_minly_charge');
+}
+
+sub can_discount { 0; }
+
+sub is_free_options {
+ qw( setup_fee recur_fee recur_minly_charge );
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
new file mode 100644
index 0000000..3cf2064
--- /dev/null
+++ b/FS/FS/part_pkg/sql_external.pm
@@ -0,0 +1,82 @@
+package FS::part_pkg::sql_external;
+
+use strict;
+use base qw( FS::part_pkg::recur_Common );
+use vars qw( %info );
+use DBI;
+#use FS::Record qw(qsearch qsearchs);
+
+%info = (
+ 'name' => 'Base charge plus additional fees for external services from a configurable SQL query',
+ 'shortname' => 'External SQL query',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '.
+ 'subscription',
+ 'default' => '1',
+ },
+
+ 'recur_method' => { 'name' => 'Recurring fee method',
+ #'type' => 'radio',
+ #'options' => \%recur_method,
+ 'type' => 'select',
+ 'select_options' => \%FS::part_pkg::recur_Common::recur_method,
+ },
+ '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_fee unused_credit recur_method cutoff_day
+ datasrc db_username db_password query
+ )],
+ 'weight' => '58',
+);
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg) = @_; #, $sdate, $details, $param ) = @_;
+
+ my $price = $self->calc_recur_Common(@_);
+
+ my $dbh = DBI->connect( map { $self->option($_) }
+ qw( datasrc db_username db_password )
+ )
+ or die $DBI::errstr;
+
+ my $sth = $dbh->prepare( $self->option('query') )
+ or die $dbh->errstr;
+
+ 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;
+}
+
+sub can_discount { 0; }
+
+sub is_free { 0; }
+
+1;
diff --git a/FS/FS/part_pkg/sql_generic.pm b/FS/FS/part_pkg/sql_generic.pm
new file mode 100644
index 0000000..eb80044
--- /dev/null
+++ b/FS/FS/part_pkg/sql_generic.pm
@@ -0,0 +1,90 @@
+package FS::part_pkg::sql_generic;
+
+use strict;
+use vars qw(@ISA %info);
+use DBI;
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Base charge plus a per-domain metered rate from a configurable SQL query',
+ 'shortname' => 'Bulk (per-domain from SQL query)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ '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_fee unused_credit 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_fee.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_fee.value + \' + $units * \' + what.recur_unit_charge + \';\'',
+ 'weight' => '56',
+);
+
+sub calc_recur {
+ my($self, $cust_pkg ) = @_;
+
+ my $dbh = DBI->connect( map { $self->option($_) }
+ qw( datasrc db_username db_password )
+ )
+ or die $DBI::errstr;
+
+ my $sth = $dbh->prepare( $self->option('query') )
+ 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 -= $self->option('recur_included');
+ $units = 0 if $units < 0;
+
+ $self->option('recur_fee') + $units * $self->option('recur_unit_charge');
+}
+
+sub can_discount { 0; }
+
+sub is_free_options {
+ qw( setup_fee recur_fee recur_unit_charge );
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sqlradacct_hour.pm b/FS/FS/part_pkg/sqlradacct_hour.pm
new file mode 100644
index 0000000..15f678f
--- /dev/null
+++ b/FS/FS/part_pkg/sqlradacct_hour.pm
@@ -0,0 +1,173 @@
+package FS::part_pkg::sqlradacct_hour;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'Base charge plus per-hour (and for data) from an SQL RADIUS radacct table',
+ 'shortname' => 'Usage charges from RADIUS',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+
+ 'recur_included_hours' => { 'name' => 'Hours included',
+ 'default' => 0,
+ },
+ 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
+ 'default' => 0,
+ },
+ 'recur_hourly_cap' => { 'name' => 'Maximum overage charge for hours'.
+ ' (0 means no cap)',
+
+ 'default' => 0,
+ },
+
+ 'recur_included_input' => { 'name' => 'Upload megabytes included',
+ 'default' => 0,
+ },
+ 'recur_input_charge' => { 'name' =>
+ 'Additional charge per megabyte upload',
+ 'default' => 0,
+ },
+ 'recur_input_cap' => { 'name' => 'Maximum overage charge for upload'.
+ ' (0 means no cap)',
+ 'default' => 0,
+ },
+
+ 'recur_included_output' => { 'name' => 'Download megabytes included',
+ 'default' => 0,
+ },
+ 'recur_output_charge' => { 'name' =>
+ 'Additional charge per megabyte download',
+ 'default' => 0,
+ },
+ 'recur_output_cap' => { 'name' => 'Maximum overage charge for download'.
+ ' (0 means no cap)',
+ 'default' => 0,
+ },
+
+ 'recur_included_total' => { 'name' =>
+ 'Total megabytes included',
+ 'default' => 0,
+ },
+ 'recur_total_charge' => { 'name' =>
+ 'Additional charge per megabyte total',
+ 'default' => 0,
+ },
+ 'recur_total_cap' => { 'name' => 'Maximum overage charge for total'.
+ ' megabytes (0 means no cap)',
+ 'default' => 0,
+ },
+
+ 'global_cap' => { 'name' => 'Global cap on all overage charges'.
+ ' (0 means no cap)',
+ 'default' => 0,
+ },
+
+ },
+ 'fieldorder' => [qw( setup_fee recur_fee unused_credit recur_included_hours recur_hourly_charge recur_hourly_cap recur_included_input recur_input_charge recur_input_cap recur_included_output recur_output_charge recur_output_cap recur_included_total recur_total_charge recur_total_cap global_cap )],
+ #'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_fee.value + \' + $hourscharge + $inputcharge + $outputcharge + $totalcharge ;\'',
+ 'weight' => 40,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ my $last_bill = $cust_pkg->last_bill;
+ my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $$sdate ) / 3600;
+ $hours -= $self->option('recur_included_hours');
+ $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 - $self->option('recur_included_total');
+ $total = 0 if $total < 0;
+ $input = $input - $self->option('recur_included_input');
+ $input = 0 if $input < 0;
+ $output = $output - $self->option('recur_included_output');
+ $output = 0 if $output < 0;
+
+ my $totalcharge =
+ $total * sprintf('%.2f', $self->option('recur_total_charge'));
+ $totalcharge = $self->option('recur_total_cap')
+ if $self->option('recur_total_cap')
+ && $totalcharge > $self->option('recur_total_cap');
+
+ my $inputcharge =
+ $input * sprintf('%.2f', $self->option('recur_input_charge'));
+ $inputcharge = $self->option('recur_input_cap')
+ if $self->option('recur_input_cap')
+ && $inputcharge > $self->option('recur_input_cap');
+
+ my $outputcharge =
+ $output * sprintf('%.2f', $self->option('recur_output_charge'));
+ $outputcharge = $self->option('recur_output_cap')
+ if $self->option('recur_output_cap')
+ && $outputcharge > $self->option('recur_output_cap');
+
+ my $hourscharge =
+ $hours * sprintf('%.2f', $self->option('recur_hourly_charge'));
+ $hourscharge = $self->option('recur_hourly_cap')
+ if $self->option('recur_hourly_cap')
+ && $hourscharge > $self->option('recur_hourly_cap');
+
+ if ( $self->option('recur_total_charge') > 0 ) {
+ push @$details, "Last month's data ".
+ sprintf('%.1f', $total). " megs: $totalcharge";
+ }
+ if ( $self->option('recur_input_charge') > 0 ) {
+ push @$details, "Last month's download ".
+ sprintf('%.1f', $input). " megs: $inputcharge";
+ }
+ if ( $self->option('recur_output_charge') > 0 ) {
+ push @$details, "Last month's upload ".
+ sprintf('%.1f', $output). " megs: $outputcharge";
+ }
+ if ( $self->option('recur_hourly_charge') > 0 ) {
+ push @$details, "Last month\'s time ".
+ sprintf('%.1f', $hours). " hours: $hourscharge";
+ }
+
+ my $charges = $hourscharge + $inputcharge + $outputcharge + $totalcharge;
+ if ( $self->option('global_cap') && $charges > $self->option('global_cap') ) {
+ $charges = $self->option('global_cap');
+ push @$details, "Usage charges capped at: $charges";
+ }
+
+ $self->option('recur_fee') + $charges;
+}
+
+sub can_discount { 0; }
+
+sub is_free_options {
+ qw( setup_fee recur_fee recur_hourly_charge
+ recur_input_charge recur_output_charge recur_total_charge );
+}
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm
new file mode 100644
index 0000000..a5e0262
--- /dev/null
+++ b/FS/FS/part_pkg/subscription.pm
@@ -0,0 +1,113 @@
+package FS::part_pkg::subscription;
+
+use strict;
+use vars qw(@ISA %info);
+use Time::Local qw(timelocal);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+@ISA = qw(FS::part_pkg::flat);
+
+%info = (
+ 'name' => 'First partial month full charge, then flat-rate (selectable billing day)',
+ 'shortname' => 'Subscription (Nth of month, full charge for first)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'cutoff_day' => { 'name' => 'billing day',
+ 'default' => 1,
+ },
+ 'seconds' => { 'name' => 'Time limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'upbytes' => { 'name' => 'Upload limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'downbytes' => { 'name' => 'Download limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'totalbytes' => { 'name' => 'Transfer limit for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_amount' => { 'name' => 'Cost of recharge for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ },
+ },
+ 'recharge_seconds' => { 'name' => 'Recharge time for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ },
+ 'recharge_upbytes' => { 'name' => 'Recharge upload for this package',
+ 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '',
+ 'check' => sub { shift =~ /^\d*$/ },
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ },
+ 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '.
+ 'over into current period',
+ 'type' => 'checkbox',
+ },
+ 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '.
+ 'package recharge',
+ 'type' => 'checkbox',
+ },
+
+ #it would be better if this had to be turned on, its confusing
+ 'externalid' => { 'name' => 'Optional External ID',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'cutoff_day', 'seconds',
+ 'upbytes', 'downbytes', 'totalbytes',
+ 'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
+ 'recharge_downbytes', 'recharge_totalbytes',
+ 'usage_rollover', 'recharge_reset', 'externalid' ],
+ 'freq' => 'm',
+ 'weight' => 30,
+);
+
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+ my $cutoff_day = $self->option('cutoff_day', 1) || 1;
+ my $mnow = $$sdate;
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
+
+ if ( $mday < $cutoff_day ) {
+ if ($mon==0) {$mon=11;$year--;}
+ else {$mon--;}
+ }
+
+ $$sdate = timelocal(0,0,0,$cutoff_day,$mon,$year);
+
+ my $br = $self->base_recur($cust_pkg);
+
+ my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
+
+ sprintf('%.2f', $br - $discount);
+}
+
+1;
diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm
new file mode 100644
index 0000000..77b9af5
--- /dev/null
+++ b/FS/FS/part_pkg/voip_cdr.pm
@@ -0,0 +1,760 @@
+package FS::part_pkg::voip_cdr;
+
+use strict;
+use vars qw(@ISA $DEBUG %info);
+use Date::Format;
+use Tie::IxHash;
+use FS::Conf;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::recur_Common;
+use FS::cdr;
+use FS::rate;
+use FS::rate_prefix;
+use FS::rate_detail;
+use FS::part_pkg::recur_Common;
+
+@ISA = qw(FS::part_pkg::recur_Common);
+
+$DEBUG = 0;
+
+tie my %rating_method, 'Tie::IxHash',
+ 'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables',
+# 'upstream' => 'Rate calls based on upstream data: If the call type is "1", map the upstream rate ID directly to an internal rate (rate_detail), otherwise, pass the upstream price through directly.',
+ 'upstream_simple' => 'Simply pass through and charge the "upstream_price" amount.',
+ 'single_price' => 'A single price per minute for all calls.',
+;
+
+#tie my %cdr_location, 'Tie::IxHash',
+# 'internal' => 'Internal: CDR records imported into the internal CDR table',
+# 'external' => 'External: CDR records queried directly from an external '.
+# 'Asterisk (or other?) CDR table',
+#;
+
+tie my %temporalities, 'Tie::IxHash',
+ 'upcoming' => "Upcoming (future)",
+ 'preceding' => "Preceding (past)",
+;
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+
+%info = (
+ 'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table',
+ 'shortname' => 'VoIP/telco CDR rating (standard)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+
+ #false laziness w/flat.pm
+ 'recur_temporality' => { 'name' => 'Charge recurring fee for period',
+ 'type' => 'select',
+ 'select_options' => \%temporalities,
+ },
+
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '.
+ 'subscription',
+ 'default' => '1',
+ },
+
+ 'recur_method' => { 'name' => 'Recurring fee method',
+ #'type' => 'radio',
+ #'options' => \%recur_method,
+ 'type' => 'select',
+ 'select_options' => \%FS::part_pkg::recur_Common::recur_method,
+ },
+
+ 'rating_method' => { 'name' => 'Rating method',
+ 'type' => 'radio',
+ 'options' => \%rating_method,
+ },
+
+ 'ratenum' => { 'name' => 'Rate plan',
+ 'type' => 'select',
+ 'select_table' => 'rate',
+ 'select_key' => 'ratenum',
+ 'select_label' => 'ratename',
+ },
+
+ 'min_charge' => { 'name' => 'Charge per minute when using "single price per minute" rating method',
+ },
+
+ 'sec_granularity' => { 'name' => 'Granularity when using "single price per minute" rating method',
+ 'type' => 'select',
+ 'select_options' => \%granularity,
+ },
+
+ 'ignore_unrateable' => { 'name' => 'Ignore calls without a rate in the rate tables. By default, the system will throw a fatal error upon encountering unrateable calls.',
+ 'type' => 'checkbox',
+ },
+
+ 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records',
+ 'default' => '+1',
+ },
+
+ 'disable_src' => { 'name' => 'Disable rating of CDR records based on the "src" field in addition to "charged_party"',
+ 'type' => 'checkbox'
+ },
+
+ 'domestic_prefix' => { 'name' => 'Destination prefix for domestic CDR records',
+ 'default' => '1',
+ },
+
+# 'domestic_prefix_required' => { 'name' => 'Require explicit destination prefix for domestic CDR records',
+# 'type' => 'checkbox',
+# },
+
+ 'international_prefix' => { 'name' => 'Destination prefix for international CDR records',
+ 'default' => '011',
+ },
+
+ 'disable_tollfree' => { 'name' => 'Disable automatic toll-free processing',
+ 'type' => 'checkbox',
+ },
+
+ 'use_amaflags' => { 'name' => 'Do not charge for CDRs where the amaflags field is not set to "2" ("BILL"/"BILLING").',
+ 'type' => 'checkbox',
+ },
+
+ 'use_disposition' => { 'name' => 'Do not charge for CDRs where the disposition flag is not set to "ANSWERED".',
+ 'type' => 'checkbox',
+ },
+
+ 'use_disposition_taqua' => { 'name' => 'Do not charge for CDRs where the disposition is not set to "100" (Taqua).',
+ 'type' => 'checkbox',
+ },
+
+ 'use_carrierid' => { 'name' => 'Do not charge for CDRs where the Carrier ID is not set to: ',
+ },
+
+ 'use_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is not set to: ',
+ },
+
+ 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values:',
+ },
+
+ 'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:',
+ },
+
+ 'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:',
+ },
+
+ 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value',
+ },
+
+ 'use_duration' => { 'name' => 'Calculate usage based on the duration field instead of the billsec field',
+ 'type' => 'checkbox',
+ },
+
+ '411_rewrite' => { 'name' => 'Rewrite these (comma-separated) destination numbers to 411 for rating purposes (also ignore any carrierid check): ',
+ },
+
+ #false laziness w/cdr_termination.pm
+ 'output_format' => { 'name' => 'CDR invoice display format',
+ 'type' => 'select',
+ 'select_options' => { FS::cdr::invoice_formats() },
+ 'default' => 'default', #XXX test
+ },
+
+ 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not)',
+ },
+
+ 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section',
+ 'type' => 'checkbox',
+ },
+
+ 'usage_mandate' => { 'name' => 'Always put usage details in separate section',
+ 'type' => 'checkbox',
+ },
+ #eofalse
+
+ 'bill_every_call' => { 'name' => 'Generate an invoice immediately for every call. Useful for prepaid.',
+ 'type' => 'checkbox',
+ },
+
+ 'count_available_phones' => { 'name' => 'Consider for tax purposes the number of lines to be svc_phones that may be provisioned rather than those that actually are.',
+ 'type' => 'checkbox',
+ },
+
+ #XXX also have option for an external db
+# 'cdr_location' => { 'name' => 'CDR database location'
+# 'type' => 'select',
+# 'select_options' => \%cdr_location,
+# 'select_callback' => {
+# 'external' => {
+# 'enable' => [ 'datasrc', 'username', 'password' ],
+# },
+# 'internal' => {
+# 'disable' => [ 'datasrc', 'username', 'password' ],
+# }
+# },
+# },
+# 'datasrc' => { 'name' => 'DBI data source for external CDR table',
+# 'disabled' => 'Y',
+# },
+# 'username' => { 'name' => 'External database username',
+# 'disabled' => 'Y',
+# },
+# 'password' => { 'name' => 'External database password',
+# 'disabled' => 'Y',
+# },
+
+ },
+ 'fieldorder' => [qw(
+ setup_fee recur_fee recur_temporality unused_credit
+ recur_method cutoff_day
+ rating_method ratenum min_charge sec_granularity
+ ignore_unrateable
+ default_prefix
+ disable_src
+ domestic_prefix international_prefix
+ disable_tollfree
+ use_amaflags use_disposition
+ use_disposition_taqua use_carrierid use_cdrtypenum
+ skip_dcontext skip_dstchannel_prefix
+ skip_dst_length_less skip_lastapp
+ use_duration
+ 411_rewrite
+ output_format usage_mandate summarize_usage usage_section
+ bill_every_call
+ count_available_phones
+ )
+ ],
+ 'weight' => 40,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $charges = 0;
+
+ $charges += $self->calc_usage(@_);
+ $charges += $self->calc_recur_Common(@_);
+
+ $charges;
+
+}
+
+sub calc_cancel {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ $self->calc_usage(@_);
+}
+
+#false laziness w/voip_sqlradacct calc_recur resolve it if that one ever gets used again
+
+sub calc_usage {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ #my $last_bill = $cust_pkg->last_bill;
+ my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
+
+ return 0
+ if $self->option('recur_temporality', 1) eq 'preceding'
+ && ( $last_bill eq '' || $last_bill == 0 );
+
+ my $ratenum = $cust_pkg->part_pkg->option('ratenum');
+
+ my $spool_cdr = $cust_pkg->cust_main->spool_cdr;
+
+ my %included_min = ();
+
+ my $charges = 0;
+
+# my $downstream_cdr = '';
+
+ my $rating_method = $self->option('rating_method') || 'prefix';
+ my $intl = $self->option('international_prefix') || '011';
+ my $domestic_prefix = $self->option('domestic_prefix');
+ my $disable_tollfree = $self->option('disable_tollfree');
+ my $ignore_unrateable = $self->option('ignore_unrateable', 'Hush!');
+ my $use_duration = $self->option('use_duration');
+
+ my $output_format = $self->option('output_format', 'Hush!')
+ || ( $rating_method eq 'upstream_simple'
+ ? 'simple'
+ : 'default'
+ );
+
+ my @dirass = ();
+ if ( $self->option('411_rewrite') ) {
+ my $dirass = $self->option('411_rewrite');
+ $dirass =~ s/\s//g;
+ @dirass = split(',', $dirass);
+ }
+
+ #for check_chargable, so we don't keep looking up options inside the loop
+ my %opt_cache = ();
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ foreach my $cust_svc (
+ grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc
+ ) {
+
+ my $svc_phone = $cust_svc->svc_x;
+ foreach my $cdr (
+ $svc_phone->get_cdrs(
+ 'disable_src' => $self->option('disable_src'),
+ 'default_prefix' => $self->option('default_prefix'),
+ 'status' => '',
+ 'for_update' => 1,
+ ) # $last_bill, $$sdate )
+ ) {
+ if ( $DEBUG > 1 ) {
+ warn "rating CDR $cdr\n".
+ join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr );
+ }
+
+ my $rate_detail;
+ my( $rate_region, $regionnum );
+ my $pretty_destnum;
+ my $charge = '';
+ my $seconds = '';
+ my $regionname = '';
+ my $classnum = '';
+ my @call_details = ();
+ if ( $rating_method eq 'prefix' ) {
+
+ my $da_rewrote = 0;
+ if ( length($cdr->dst) && grep { $cdr->dst eq $_ } @dirass ){
+ $cdr->dst('411');
+ $da_rewrote = 1;
+ }
+
+ my $reason = $self->check_chargable( $cdr,
+ 'da_rewrote' => $da_rewrote,
+ 'option_cache' => \%opt_cache,
+ );
+
+ if ( $reason ) {
+
+ warn "not charging for CDR ($reason)\n" if $DEBUG;
+ $charge = 0;
+
+ } else {
+
+ ###
+ # look up rate details based on called station id
+ # (or calling station id for toll free calls)
+ ###
+
+ my( $to_or_from, $number );
+ if ( $cdr->is_tollfree && ! $disable_tollfree )
+ { #tollfree call
+ $to_or_from = 'from';
+ $number = $cdr->src;
+ } else { #regular call
+ $to_or_from = 'to';
+ $number = $cdr->dst;
+ }
+
+ warn "parsing call $to_or_from $number\n" if $DEBUG;
+
+ #remove non-phone# stuff and whitespace
+ $number =~ s/\s//g;
+# my $proto = '';
+# $dest =~ s/^(\w+):// and $proto = $1; #sip:
+# my $siphost = '';
+# $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com
+
+ #determine the country code
+ my $countrycode;
+ if ( $number =~ /^$intl(((\d)(\d))(\d))(\d+)$/
+ || $number =~ /^\+(((\d)(\d))(\d))(\d+)$/
+ )
+ {
+
+ my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 );
+ #first look for 1 digit country code
+ if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) {
+ $countrycode = $one;
+ $number = $u1.$u2.$rest;
+ } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2
+ $countrycode = $two;
+ $number = $u2.$rest;
+ } else { #3 digit country code
+ $countrycode = $three;
+ $number = $rest;
+ }
+
+ } else {
+ $countrycode = $domestic_prefix || '1';
+ $number =~ s/^$countrycode//;# if length($number) > 10;
+ }
+
+ warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG;
+ $pretty_destnum = "+$countrycode $number";
+ #asterisks here causes inserting the detail to barf, so:
+ $pretty_destnum =~ s/\*//g;
+
+ my $rate = qsearchs('rate', { 'ratenum' => $ratenum })
+ or die "ratenum $ratenum not found!";
+
+ $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode,
+ 'phonenum' => $number,
+ });
+
+ if ( $rate_detail ) {
+
+ $rate_region = $rate_detail->dest_region;
+ $regionnum = $rate_region->regionnum;
+ $regionname = $rate_region->regionname;
+ warn " found rate for regionnum $regionnum ".
+ "and rate detail $rate_detail\n"
+ if $DEBUG;
+
+ } elsif ( $ignore_unrateable ) {
+
+ $rate_region = '';
+ $regionnum = '';
+ #code below will throw a warning & skip
+
+ } else {
+
+ die "FATAL: no rate_detail found in ".
+ $rate->ratenum. ":". $rate->ratename. " rate plan ".
+ "for +$countrycode $number (CDR acctid ". $cdr->acctid. "); ".
+ "add a rate or set ignore_unrateable flag on the package def\n";
+ }
+
+ }
+
+# } elsif ( $rating_method eq 'upstream' ) { #XXX this was convergent, not currently used. very much becoming the odd one out. remove?
+#
+# if ( $cdr->cdrtypenum == 1 ) { #rate based on upstream rateid
+#
+# $rate_detail = $cdr->cdr_upstream_rate->rate_detail;
+#
+# $regionnum = $rate_detail->dest_regionnum;
+# $rate_region = $rate_detail->dest_region;
+#
+# $pretty_destnum = $cdr->dst;
+#
+# warn " found rate for regionnum $regionnum and ".
+# "rate detail $rate_detail\n"
+# if $DEBUG;
+#
+# } else { #pass upstream price through
+#
+# $charge = sprintf('%.2f', $cdr->upstream_price);
+# $charges += $charge;
+#
+# @call_details = (
+# #time2str("%Y %b %d - %r", $cdr->calldate_unix ),
+# time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot
+# 'N/A', #minutes...
+# '$'.$charge,
+# #$pretty_destnum,
+# $cdr->description, #$rate_region->regionname,
+# );
+#
+# }
+
+ } elsif ( $rating_method eq 'upstream_simple' ) {
+
+ #XXX $charge = sprintf('%.2f', $cdr->upstream_price);
+ $charge = sprintf('%.3f', $cdr->upstream_price);
+ $charges += $charge;
+
+ @call_details = ($cdr->downstream_csv( 'format' => $output_format,
+ 'charge' => $charge,
+ )
+ );
+ $classnum = $cdr->calltypenum;
+
+ } elsif ( $rating_method eq 'single_price' ) {
+
+ # a little false laziness w/below
+
+ my $granularity = length($self->option('sec_granularity'))
+ ? $self->option('sec_granularity')
+ : 60;
+
+ # length($cdr->billsec) ? $cdr->billsec : $cdr->duration;
+ $seconds = $use_duration ? $cdr->duration : $cdr->billsec;
+
+ $seconds += $granularity - ( $seconds % $granularity )
+ if $seconds # don't granular-ize 0 billsec calls (bills them)
+ && $granularity; # 0 is per call
+ my $minutes = $seconds / 60; # sprintf("%.1f",
+ #$minutes =~ s/\.0$// if $granularity == 60;
+
+ # XXX config?
+ #$charge = sprintf('%.2f', ( $self->option('min_charge') * $minutes )
+ #+ 0.00000001 ); #so 1.005 rounds to 1.01
+ $charge = sprintf('%.4f', ( $self->option('min_charge') * $minutes )
+ + 0.0000000001 ); #so 1.00005 rounds to 1.0001
+
+ $charges += $charge;
+
+ @call_details = ($cdr->downstream_csv( 'format' => $output_format,
+ 'charge' => $charge,
+ )
+ );
+
+ } else {
+ die "don't know how to rate CDRs using method: $rating_method\n";
+ }
+
+ ###
+ # find the price and add detail to the invoice
+ ###
+
+ # if $rate_detail is not found, skip this CDR... i.e.
+ # don't add it to invoice, don't set its status to done,
+ # don't call downstream_csv or something on it...
+ # but DO emit a warning...
+ #if ( ! $rate_detail && ! scalar(@call_details) ) {}
+ if ( ! $rate_detail && $charge eq '' ) {
+
+ warn "no rate_detail found for CDR.acctid: ". $cdr->acctid.
+ "; skipping\n"
+
+ } else { # there *is* a rate_detail (or call_details), proceed...
+
+ unless ( @call_details || ( $charge ne '' && $charge == 0 ) ) {
+
+ $included_min{$regionnum} = $rate_detail->min_included
+ unless exists $included_min{$regionnum};
+
+ my $granularity = $rate_detail->sec_granularity;
+
+ # length($cdr->billsec) ? $cdr->billsec : $cdr->duration;
+ $seconds = $use_duration ? $cdr->duration : $cdr->billsec;
+
+ $seconds -= $rate_detail->conn_sec;
+ $seconds = 0 if $seconds < 0;
+
+ $seconds += $granularity - ( $seconds % $granularity )
+ if $seconds # don't granular-ize 0 billsec calls (bills them)
+ && $granularity; # 0 is per call
+ my $minutes = sprintf("%.1f", $seconds / 60);
+ $minutes =~ s/\.0$// if $granularity == 60;
+
+ # per call rather than per minute
+ $minutes = 1 unless $granularity;
+
+ $included_min{$regionnum} -= $minutes;
+
+ $charge = sprintf('%.2f', $rate_detail->conn_charge);
+
+ if ( $included_min{$regionnum} < 0 ) {
+ my $charge_min = 0 - $included_min{$regionnum}; #XXX should preserve
+ #(display?) this
+ $included_min{$regionnum} = 0;
+ $charge += sprintf('%.2f', ($rate_detail->min_charge * $charge_min)
+ + 0.00000001 ); #so 1.005 rounds to 1.01
+ $charge = sprintf('%.2f', $charge);
+ $charges += $charge;
+ }
+
+ # this is why we need regionnum/rate_region....
+ warn " (rate region $rate_region)\n" if $DEBUG;
+
+ @call_details = (
+ $cdr->downstream_csv( 'format' => $output_format,
+ 'granularity' => $granularity,
+ 'minutes' => $minutes,
+ 'charge' => $charge,
+ 'pretty_dst' => $pretty_destnum,
+ 'dst_regionname' => $regionname,
+ )
+ );
+
+ $classnum = $rate_detail->classnum;
+
+ }
+
+ if ( $charge > 0 ) {
+ #just use FS::cust_bill_pkg_detail objects?
+ my $call_details;
+ my $phonenum = $cust_svc->svc_x->phonenum;
+
+ #if ( $self->option('rating_method') eq 'upstream_simple' ) {
+ if ( scalar(@call_details) == 1 ) {
+ $call_details =
+ [ 'C',
+ $call_details[0],
+ $charge,
+ $classnum,
+ $phonenum,
+ $seconds,
+ $regionname,
+ ];
+ } else { #only used for $rating_method eq 'upstream' now
+ $csv->combine(@call_details);
+ $call_details =
+ [ 'C',
+ $csv->string,
+ $charge,
+ $classnum,
+ $phonenum,
+ $seconds,
+ $regionname,
+ ];
+ }
+ warn " adding details on charge to invoice: [ ".
+ join(', ', @{$call_details} ). " ]"
+ if ( $DEBUG && ref($call_details) );
+ push @$details, $call_details; #\@call_details,
+ }
+
+ # if the customer flag is on, call "downstream_csv" or something
+ # like it to export the call downstream!
+ # XXX price plan option to pick format, or something...
+ #$downstream_cdr .= $cdr->downstream_csv( 'format' => 'XXX format' )
+ # if $spool_cdr;
+
+ my $error = $cdr->set_status_and_rated_price( 'done',
+ $charge,
+ $cust_svc->svcnum,
+ );
+ die $error if $error;
+
+ }
+
+ } # $cdr
+
+ } # $cust_svc
+
+ unshift @$details, [ 'C',
+ FS::cdr::invoice_header($output_format),
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]
+ if @$details && $rating_method ne 'upstream';
+
+# if ( $spool_cdr && length($downstream_cdr) ) {
+#
+# use FS::UID qw(datasrc);
+# my $dir = '/usr/local/etc/freeside/export.'. datasrc. '/cdr';
+# mkdir $dir, 0700 unless -d $dir;
+# $dir .= '/'. $cust_pkg->custnum.
+# mkdir $dir, 0700 unless -d $dir;
+# my $filename = time2str("$dir/CDR%Y%m%d-spool.CSV", time); #XXX invoice date instead? would require changing the order things are generated in cust_main::bill insert cust_bill first - with transactions it could be done though
+#
+# push @{ $param->{'precommit_hooks'} },
+# sub {
+# #lock the downstream spool file and append the records
+# use Fcntl qw(:flock);
+# use IO::File;
+# my $spool = new IO::File ">>$filename"
+# or die "can't open $filename: $!\n";
+# flock( $spool, LOCK_EX)
+# or die "can't lock $filename: $!\n";
+# seek($spool, 0, 2)
+# or die "can't seek to end of $filename: $!\n";
+# print $spool $downstream_cdr;
+# flock( $spool, LOCK_UN );
+# close $spool;
+# };
+#
+# } #if ( $spool_cdr && length($downstream_cdr) )
+
+ $charges;
+}
+
+#returns a reason why not to rate this CDR, or false if the CDR is chargeable
+sub check_chargable {
+ my( $self, $cdr, %flags ) = @_;
+
+ #should have some better way of checking these options from a hash
+ #or something
+
+ my @opt = qw(
+ use_amaflags
+ use_disposition
+ use_disposition_taqua
+ use_carrierid
+ use_cdrtypenum
+ skip_dcontext
+ skip_dstchannel_prefix
+ skip_dst_length_less
+ skip_lastapp
+ );
+ foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) {
+ $flags{option_cache}->{$opt} = $self->option($opt, 1);
+ }
+ my %opt = %{ $flags{option_cache} };
+
+ return 'amaflags != 2'
+ if $opt{'use_amaflags'} && $cdr->amaflags != 2;
+
+ return 'disposition != ANSWERED'
+ if $opt{'use_disposition'} && $cdr->disposition ne 'ANSWERED';
+
+ return "disposition != 100"
+ if $opt{'use_disposition_taqua'} && $cdr->disposition != 100;
+
+ return "carrierid != $opt{'use_carrierid'}"
+ if length($opt{'use_carrierid'})
+ && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches ''
+ && ! $flags{'da_rewrote'};
+
+ return "cdrtypenum != $opt{'use_cdrtypenum'}"
+ if length($opt{'use_cdrtypenum'})
+ && $cdr->cdrtypenum ne $opt{'use_cdrtypenum'}; #ne otherwise 0 matches ''
+
+ return "dcontext IN ( $opt{'skip_dcontext'} )"
+ if $opt{'skip_dcontext'} =~ /\S/
+ && grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $opt{'skip_dcontext'});
+
+ my $len_prefix = length($opt{'skip_dstchannel_prefix'});
+ return "dstchannel starts with $opt{'skip_dstchannel_prefix'}"
+ if $len_prefix
+ && substr($cdr->dstchannel,0,$len_prefix) eq $opt{'skip_dstchannel_prefix'};
+
+ my $dst_length = $opt{'skip_dst_length_less'};
+ return "destination less than $dst_length digits"
+ if $dst_length && length($cdr->dst) < $dst_length;
+
+ return "lastapp is $opt{'skip_lastapp'}"
+ if length($opt{'skip_lastapp'}) && $cdr->lastapp eq $opt{'skip_lastapp'};
+
+ #all right then, rate it
+ '';
+}
+
+sub is_free {
+ 0;
+}
+
+# This equates svc_phone records; perhaps svc_phone should have a field
+# to indicate it represents a line
+sub calc_units {
+ my($self, $cust_pkg ) = @_;
+ my $count = 0;
+ if ( $self->option('count_available_phones', 1)) {
+ map { $count += ( $_->quantity || 0 ) }
+ grep { $_->part_svc->svcdb eq 'svc_phone' }
+ $cust_pkg->part_pkg->pkg_svc;
+ } else {
+ $count =
+ scalar(grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc);
+ }
+ $count;
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/voip_inbound.pm b/FS/FS/part_pkg/voip_inbound.pm
new file mode 100644
index 0000000..4937bb8
--- /dev/null
+++ b/FS/FS/part_pkg/voip_inbound.pm
@@ -0,0 +1,374 @@
+package FS::part_pkg::voip_inbound;
+
+use strict;
+use vars qw(@ISA $DEBUG %info);
+use Date::Format;
+use Tie::IxHash;
+use FS::Conf;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::recur_Common;
+use FS::cdr;
+use FS::part_pkg::recur_Common;
+
+@ISA = qw(FS::part_pkg::recur_Common);
+
+$DEBUG = 0;
+
+tie my %temporalities, 'Tie::IxHash',
+ 'upcoming' => "Upcoming (future)",
+ 'preceding' => "Preceding (past)",
+;
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+
+%info = (
+ 'name' => 'VoIP flat rate pricing of CDRs for inbound calls',
+ 'shortname' => 'VoIP/telco CDR rating (inbound)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+
+ #false laziness w/flat.pm
+ 'recur_temporality' => { 'name' => 'Charge recurring fee for period',
+ 'type' => 'select',
+ 'select_options' => \%temporalities,
+ },
+
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+
+ 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '.
+ 'subscription',
+ 'default' => '1',
+ },
+
+ 'recur_method' => { 'name' => 'Recurring fee method',
+ 'type' => 'select',
+ 'select_options' => \%FS::part_pkg::recur_Common::recur_method,
+ },
+
+ 'min_charge' => { 'name' => 'Charge per minute',
+ },
+
+ 'sec_granularity' => { 'name' => 'Granularity',
+ 'type' => 'select',
+ 'select_options' => \%granularity,
+ },
+
+ 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records',
+ 'default' => '+1',
+ },
+
+ 'disable_tollfree' => { 'name' => 'Disable automatic toll-free processing',
+ 'type' => 'checkbox',
+ },
+
+ 'use_amaflags' => { 'name' => 'Do not charge for CDRs where the amaflags field is not set to "2" ("BILL"/"BILLING").',
+ 'type' => 'checkbox',
+ },
+
+ 'use_disposition' => { 'name' => 'Do not charge for CDRs where the disposition flag is not set to "ANSWERED".',
+ 'type' => 'checkbox',
+ },
+
+ 'use_disposition_taqua' => { 'name' => 'Do not charge for CDRs where the disposition is not set to "100" (Taqua).',
+ 'type' => 'checkbox',
+ },
+
+ 'use_carrierid' => { 'name' => 'Do not charge for CDRs where the Carrier ID is not set to: ',
+ },
+
+ 'use_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is not set to: ',
+ },
+
+ 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values:',
+ },
+
+ 'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:',
+ },
+
+ 'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:',
+ },
+
+ 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value',
+ },
+
+ 'use_duration' => { 'name' => 'Calculate usage based on the duration field instead of the billsec field',
+ 'type' => 'checkbox',
+ },
+
+ #false laziness w/cdr_termination.pm
+ 'output_format' => { 'name' => 'CDR invoice display format',
+ 'type' => 'select',
+ 'select_options' => { FS::cdr::invoice_formats() },
+ 'default' => 'default', #XXX test
+ },
+
+ 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not)',
+ },
+
+ 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section',
+ 'type' => 'checkbox',
+ },
+
+ 'usage_mandate' => { 'name' => 'Always put usage details in separate section',
+ 'type' => 'checkbox',
+ },
+ #eofalse
+
+ 'bill_every_call' => { 'name' => 'Generate an invoice immediately for every call. Useful for prepaid.',
+ 'type' => 'checkbox',
+ },
+
+ #XXX also have option for an external db
+# 'cdr_location' => { 'name' => 'CDR database location'
+# 'type' => 'select',
+# 'select_options' => \%cdr_location,
+# 'select_callback' => {
+# 'external' => {
+# 'enable' => [ 'datasrc', 'username', 'password' ],
+# },
+# 'internal' => {
+# 'disable' => [ 'datasrc', 'username', 'password' ],
+# }
+# },
+# },
+# 'datasrc' => { 'name' => 'DBI data source for external CDR table',
+# 'disabled' => 'Y',
+# },
+# 'username' => { 'name' => 'External database username',
+# 'disabled' => 'Y',
+# },
+# 'password' => { 'name' => 'External database password',
+# 'disabled' => 'Y',
+# },
+
+ },
+ 'fieldorder' => [qw(
+ setup_fee recur_fee recur_temporality unused_credit
+ recur_method cutoff_day
+ min_charge sec_granularity
+ default_prefix
+ disable_tollfree
+ use_amaflags use_disposition
+ use_disposition_taqua use_carrierid use_cdrtypenum
+ skip_dcontext skip_dstchannel_prefix
+ skip_dst_length_less skip_lastapp
+ use_duration
+ output_format usage_mandate summarize_usage usage_section
+ bill_every_call
+ )
+ ],
+ 'weight' => 40,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $charges = 0;
+
+ $charges += $self->calc_usage(@_);
+ $charges += $self->calc_recur_Common(@_);
+
+ $charges;
+
+}
+
+sub calc_cancel {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ $self->calc_usage(@_);
+}
+
+#false laziness w/voip_sqlradacct calc_recur resolve it if that one ever gets used again
+
+sub calc_usage {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ #my $last_bill = $cust_pkg->last_bill;
+ my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
+
+ return 0
+ if $self->option('recur_temporality', 1) eq 'preceding'
+ && ( $last_bill eq '' || $last_bill == 0 );
+
+ my $spool_cdr = $cust_pkg->cust_main->spool_cdr;
+
+ my %included_min = ();
+
+ my $charges = 0;
+
+# my $downstream_cdr = '';
+
+ my $disable_tollfree = $self->option('disable_tollfree');
+ my $ignore_unrateable = $self->option('ignore_unrateable', 'Hush!');
+ my $use_duration = $self->option('use_duration');
+
+ my $output_format = $self->option('output_format', 'Hush!') || 'default';
+
+ #for check_chargable, so we don't keep looking up options inside the loop
+ my %opt_cache = ();
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ foreach my $cust_svc (
+ grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc
+ ) {
+ my $svc_phone = $cust_svc->svc_x;
+
+ foreach my $cdr ( $svc_phone->get_cdrs(
+ 'for_update' => 1,
+ 'status' => '', # unprocessed only
+ 'default_prefix' => $self->option('default_prefix'),
+ 'inbound' => 1,
+ )
+ ) {
+ if ( $DEBUG > 1 ) {
+ warn "rating inbound CDR $cdr\n".
+ join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr );
+ }
+ my $granularity = length($self->option('sec_granularity'))
+ ? $self->option('sec_granularity')
+ : 60;
+
+ my $seconds = $use_duration ? $cdr->duration : $cdr->billsec;
+
+ $seconds += $granularity - ( $seconds % $granularity )
+ if $seconds # don't granular-ize 0 billsec calls (bills them)
+ && $granularity; # 0 is per call
+ my $minutes = sprintf("%.1f",$seconds / 60);
+ $minutes =~ s/\.0$// if $granularity == 60; # count whole minutes, convert to integer
+ $minutes = 1 unless $granularity; # per call
+ my $charge = sprintf('%.2f', ( $self->option('min_charge') * $minutes )
+ + 0.00000001 ); #so 1.00005 rounds to 1.0001
+ next if !$charge;
+ $charges += $charge;
+ my @call_details = ($cdr->downstream_csv( 'format' => $output_format,
+ 'charge' => $charge,
+ 'minutes' => $minutes,
+ 'granularity' => $granularity,
+ )
+ );
+ push @$details,
+ [ 'C',
+ $call_details[0],
+ $charge,
+ $cdr->calltypenum, #classnum
+ $self->phonenum,
+ $seconds,
+ '', #regionname, not set for inbound calls
+ ];
+
+ my $error = $cdr->set_status_and_rated_price( 'done',
+ $charge,
+ $cust_svc->svcnum,
+ 'inbound' => 1 );
+ die $error if $error;
+
+ } #$cdr
+ } # $cust_svc
+ unshift @$details, [ 'C',
+ FS::cdr::invoice_header($output_format),
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]
+ if @$details;
+
+ $charges;
+}
+
+#returns a reason why not to rate this CDR, or false if the CDR is chargeable
+sub check_chargable {
+ my( $self, $cdr, %flags ) = @_;
+
+ #should have some better way of checking these options from a hash
+ #or something
+
+ my @opt = qw(
+ use_amaflags
+ use_disposition
+ use_disposition_taqua
+ use_carrierid
+ use_cdrtypenum
+ skip_dcontext
+ skip_dstchannel_prefix
+ skip_dst_length_less
+ skip_lastapp
+ );
+ foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) {
+ $flags{option_cache}->{$opt} = $self->option($opt, 1);
+ }
+ my %opt = %{ $flags{option_cache} };
+
+ return 'amaflags != 2'
+ if $opt{'use_amaflags'} && $cdr->amaflags != 2;
+
+ return 'disposition != ANSWERED'
+ if $opt{'use_disposition'} && $cdr->disposition ne 'ANSWERED';
+
+ return "disposition != 100"
+ if $opt{'use_disposition_taqua'} && $cdr->disposition != 100;
+
+ return "carrierid != $opt{'use_carrierid'}"
+ if length($opt{'use_carrierid'})
+ && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches ''
+ && ! $flags{'da_rewrote'};
+
+ return "cdrtypenum != $opt{'use_cdrtypenum'}"
+ if length($opt{'use_cdrtypenum'})
+ && $cdr->cdrtypenum ne $opt{'use_cdrtypenum'}; #ne otherwise 0 matches ''
+
+ return "dcontext IN ( $opt{'skip_dcontext'} )"
+ if $opt{'skip_dcontext'} =~ /\S/
+ && grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $opt{'skip_dcontext'});
+
+ my $len_prefix = length($opt{'skip_dstchannel_prefix'});
+ return "dstchannel starts with $opt{'skip_dstchannel_prefix'}"
+ if $len_prefix
+ && substr($cdr->dstchannel,0,$len_prefix) eq $opt{'skip_dstchannel_prefix'};
+
+ my $dst_length = $opt{'skip_dst_length_less'};
+ return "destination less than $dst_length digits"
+ if $dst_length && length($cdr->dst) < $dst_length;
+
+ return "lastapp is $opt{'skip_lastapp'}"
+ if length($opt{'skip_lastapp'}) && $cdr->lastapp eq $opt{'skip_lastapp'};
+
+ #all right then, rate it
+ '';
+}
+
+sub is_free {
+ 0;
+}
+
+# This equates svc_phone records; perhaps svc_phone should have a field
+# to indicate it represents a line
+sub calc_units {
+ my($self, $cust_pkg ) = @_;
+ my $count =
+ scalar(grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc);
+ $count;
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm
new file mode 100644
index 0000000..441df58
--- /dev/null
+++ b/FS/FS/part_pkg/voip_sqlradacct.pm
@@ -0,0 +1,194 @@
+package FS::part_pkg::voip_sqlradacct;
+
+use strict;
+use vars qw(@ISA $DEBUG %info);
+use Date::Format;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::flat;
+#use FS::rate;
+use FS::rate_prefix;
+
+@ISA = qw(FS::part_pkg::flat);
+
+$DEBUG = 1;
+
+%info = (
+ 'disabled' => 1, #they're sucked into our CDR table now instead
+ 'name' => 'VoIP rating by plan of CDR records in an SQL RADIUS radacct table',
+ 'shortname' => 'VoIP/telco CDR rating (external RADIUS)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Base recurring fee for this package',
+ 'default' => 0,
+ },
+ 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+ ' of service at cancellation',
+ 'type' => 'checkbox',
+ },
+ 'ratenum' => { 'name' => 'Rate plan',
+ 'type' => 'select',
+ 'select_table' => 'rate',
+ 'select_key' => 'ratenum',
+ 'select_label' => 'ratename',
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_fee unused_credit ratenum ignore_unrateable )],
+ 'weight' => 40,
+);
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+#false laziness w/voip_cdr... resolve it if this one ever gets used again
+sub calc_recur {
+ my($self, $cust_pkg, $sdate, $details ) = @_;
+
+ my $last_bill = $cust_pkg->last_bill;
+
+ my $ratenum = $cust_pkg->part_pkg->option('ratenum');
+
+ my %included_min = ();
+
+ my $charges = 0;
+
+ foreach my $cust_svc (
+ grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc
+ ) {
+
+ foreach my $session (
+ $cust_svc->get_session_history( $last_bill, $$sdate )
+ ) {
+ if ( $DEBUG > 1 ) {
+ warn "rating session $session\n".
+ join('', map { " $_ => ". $session->{$_}. "\n" } keys %$session );
+ }
+
+ ###
+ # look up rate details based on called station id
+ ###
+
+ my $dest = $session->{'calledstationid'};
+
+ #remove non-phone# stuff and whitespace
+ $dest =~ s/\s//g;
+ my $proto = '';
+ $dest =~ s/^(\w+):// and $proto = $1; #sip:
+ my $siphost = '';
+ $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com
+
+ #determine the country code
+ my $countrycode;
+ if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ ) {
+
+ my( $three, $two, $one, $u1, $u2, $rest ) = ( $1, $2, $3, $4, $5, $6 );
+ #first look for 1 digit country code
+ if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) {
+ $countrycode = $one;
+ $dest = $u1.$u2.$rest;
+ } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2
+ $countrycode = $two;
+ $dest = $u2.$rest;
+ } else { #3 digit country code
+ $countrycode = $three;
+ $dest = $rest;
+ }
+
+ } else {
+ $countrycode = '1';
+ $dest =~ s/^1//;# if length($dest) > 10;
+ }
+
+ warn "rating call to +$countrycode $dest\n" if $DEBUG;
+
+ #find a rate prefix, first look at most specific (4 digits) then 3, etc.,
+ # finally trying the country code only
+ my $rate_prefix = '';
+ for my $len ( reverse(1..6) ) {
+ $rate_prefix = qsearchs('rate_prefix', {
+ 'countrycode' => $countrycode,
+ #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) }
+ 'npa' => substr($dest, 0, $len),
+ } ) and last;
+ }
+ $rate_prefix ||= qsearchs('rate_prefix', {
+ 'countrycode' => $countrycode,
+ 'npa' => '',
+ });
+
+ die "Can't find rate for call to +$countrycode $dest\n"
+ unless $rate_prefix;
+
+ my $regionnum = $rate_prefix->regionnum;
+ my $rate_detail = qsearchs('rate_detail', {
+ 'ratenum' => $ratenum,
+ 'dest_regionnum' => $regionnum,
+ } );
+
+ warn " found rate for regionnum $regionnum ".
+ "and rate detail $rate_detail\n"
+ if $DEBUG;
+
+ ###
+ # find the price and add detail to the invoice
+ ###
+
+ $included_min{$regionnum} = $rate_detail->min_included
+ unless exists $included_min{$regionnum};
+
+ my $granularity = $rate_detail->sec_granularity;
+ my $seconds = $session->{'acctsessiontime'};
+ $seconds += $granularity - ( $seconds % $granularity );
+ my $minutes = sprintf("%.1f", $seconds / 60);
+ $minutes =~ s/\.0$// if $granularity == 60;
+
+ $included_min{$regionnum} -= $minutes;
+
+ my $charge = 0;
+ if ( $included_min{$regionnum} < 0 ) {
+ my $charge_min = 0 - $included_min{$regionnum};
+ $included_min{$regionnum} = 0;
+ $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min );
+ $charges += $charge;
+ }
+
+ my $rate_region = $rate_prefix->rate_region;
+ warn " (rate region $rate_region)\n" if $DEBUG;
+
+ my @call_details = (
+ #time2str("%Y %b %d - %r", $session->{'acctstarttime'}),
+ time2str("%c", $session->{'acctstarttime'}),
+ $minutes.'m',
+ '$'.$charge,
+ "+$countrycode $dest",
+ $rate_region->regionname,
+ );
+
+ warn " adding details on charge to invoice: ".
+ join(' - ', @call_details )
+ if $DEBUG;
+
+ push @$details, join(' - ', @call_details); #\@call_details,
+
+ } # $session
+
+ } # $cust_svc
+
+ $self->option('recur_fee') + $charges;
+
+}
+
+sub can_discount { 0; }
+
+sub is_free { 0; }
+
+sub base_recur {
+ my($self, $cust_pkg) = @_;
+ $self->option('recur_fee');
+}
+
+1;
+
diff --git a/FS/FS/part_pkg_link.pm b/FS/FS/part_pkg_link.pm
new file mode 100644
index 0000000..fb7a8d3
--- /dev/null
+++ b/FS/FS/part_pkg_link.pm
@@ -0,0 +1,163 @@
+package FS::part_pkg_link;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::part_pkg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_pkg_link - Object methods for part_pkg_link records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_link;
+
+ $record = new FS::part_pkg_link \%hash;
+ $record = new FS::part_pkg_link { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_link object represents an link from one package definition to
+another. FS::part_pkg_link inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item pkglinknum
+
+primary key
+
+=item src_pkgpart
+
+Source package (see L<FS::part_pkg>)
+
+=item dst_pkgpart
+
+Destination package (see L<FS::part_pkg>)
+
+=item link_type
+
+Link type - currently, "bill" (source package bills a line item from target
+package), or "svc" (source package includes services from target package).
+
+=item hidden
+
+Flag indicating that this subpackage should be felt, but not seen as an invoice
+line item when set to 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new link. To add the link 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_pkg_link'; }
+
+=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 link. 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('pkglinknum')
+ || $self->ut_foreign_key('src_pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_foreign_key('dst_pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_enum('link_type', [ 'bill', 'svc' ] )
+ || $self->ut_enum('hidden', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item src_pkg
+
+Returns the source part_pkg object (see L<FS::part_pkg>).
+
+=cut
+
+sub src_pkg {
+ my $self = shift;
+ qsearchs('part_pkg', { 'pkgpart' => $self->src_pkgpart } );
+}
+
+=item dst_pkg
+
+Returns the source part_pkg object (see L<FS::part_pkg>).
+
+=cut
+
+sub dst_pkg {
+ my $self = shift;
+ qsearchs('part_pkg', { 'pkgpart' => $self->dst_pkgpart } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_option.pm b/FS/FS/part_pkg_option.pm
new file mode 100644
index 0000000..3cb330b
--- /dev/null
+++ b/FS/FS/part_pkg_option.pm
@@ -0,0 +1,155 @@
+package FS::part_pkg_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::part_pkg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_pkg_option - Object methods for part_pkg_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_option;
+
+ $record = new FS::part_pkg_option \%hash;
+ $record = new FS::part_pkg_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_option object represents an package definition option.
+FS::part_pkg_option inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item pkgpart - package definition (see L<FS::part_pkg>)
+
+=item optionname - option name
+
+=item optionvalue - option value
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new package definition option. To add the package definition 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_pkg_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 package definition 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_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_alpha('optionname')
+ || $self->ut_anything('optionvalue')
+ ;
+ return $error if $error;
+
+ #check options & values?
+
+ $self->SUPER::check;
+}
+
+=back
+
+=cut
+
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ my $sql = "UPDATE part_pkg_option SET optionname = 'recur_fee'".
+ " WHERE optionname = 'recur_flat'";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ $sql = "UPDATE part_pkg_option SET optionname = 'recur_method',".
+ "optionvalue = 'prorate' WHERE optionname = 'enable_prorate'";
+ $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ '';
+
+}
+
+=head1 BUGS
+
+Possibly.
+
+=head1 SEE ALSO
+
+L<FS::part_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_report_option.pm b/FS/FS/part_pkg_report_option.pm
new file mode 100644
index 0000000..16a4c98
--- /dev/null
+++ b/FS/FS/part_pkg_report_option.pm
@@ -0,0 +1,125 @@
+package FS::part_pkg_report_option;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::part_pkg_report_option - Object methods for part_pkg_report_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_report_option;
+
+ $record = new FS::part_pkg_report_option \%hash;
+ $record = new FS::part_pkg_report_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_report_option object represents a package definition optional
+reporting classification. FS::part_pkg_report_option inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item num
+
+primary key
+
+=item name
+
+name - The name associated with the reporting option
+
+=item disabled
+
+disabled - set to 'Y' to prevent addition to new packages
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new report option. To add the 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
+
+sub table { 'part_pkg_report_option'; }
+
+=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
+
+sub delete {
+ return "Can't delete part_pkg_report_option 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.
+
+=cut
+
+=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('num')
+ || $self->ut_text('name')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Overlaps somewhat with pkg_class and pkg_category
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_taxclass.pm b/FS/FS/part_pkg_taxclass.pm
new file mode 100644
index 0000000..824fd17
--- /dev/null
+++ b/FS/FS/part_pkg_taxclass.pm
@@ -0,0 +1,226 @@
+package FS::part_pkg_taxclass;
+
+use strict;
+use vars qw( @ISA );
+use Scalar::Util qw( blessed );
+use FS::UID qw( dbh );
+use FS::Record; # qw( qsearch qsearchs );
+use FS::cust_main_county;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_pkg_taxclass - Object methods for part_pkg_taxclass records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_taxclass;
+
+ $record = new FS::part_pkg_taxclass \%hash;
+ $record = new FS::part_pkg_taxclass { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_taxclass object represents a tax class. FS::part_pkg_taxclass
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item taxclassnum
+
+Primary key
+
+=item taxclass
+
+Tax class
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax class. To add the tax class 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_pkg_taxclass'; }
+
+=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;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $sth = dbh->prepare("
+ SELECT country, state, county FROM cust_main_county
+ WHERE taxclass IS NOT NULL AND taxclass != ''
+ GROUP BY country, state, county
+ ") or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_hashref ) {
+ #warn "inserting for $row";
+ my $cust_main_county = new FS::cust_main_county {
+ 'country' => $row->{country},
+ 'state' => $row->{state},
+ 'county' => $row->{county},
+ 'tax' => 0,
+ 'taxclass' => $self->taxclass,
+ #exempt_amount
+ #taxname
+ #setuptax
+ #recurtax
+ };
+ $error = $cust_main_county->insert;
+ #last if $error;
+ 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
+
+# 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
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ return "Can't change tax class name (disable and create anew)"
+ if $old->taxclass ne $new->taxclass;
+
+ $new->SUPER::replace(@_);
+}
+
+=item check
+
+Checks all fields to make sure this is a valid tax class. 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('taxclassnum')
+ || $self->ut_text('taxclass')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=cut
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ my $sth = dbh->prepare('
+ SELECT DISTINCT taxclass
+ FROM cust_main_county
+ LEFT JOIN part_pkg_taxclass USING ( taxclass )
+ WHERE taxclassnum IS NULL
+ AND taxclass IS NOT NULL
+ ') or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my %taxclass = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
+ my @taxclass = grep $_, keys %taxclass;
+
+ foreach my $taxclass ( @taxclass ) {
+
+ my $part_pkg_taxclass = new FS::part_pkg_taxclass ( {
+ 'taxclass' => $taxclass,
+ } );
+ my $error = $part_pkg_taxclass->insert;
+ die $error if $error;
+
+ }
+
+}
+
+=head1 BUGS
+
+Other tables (cust_main_county, part_pkg, agent_payment_gateway) have a text
+taxclass instead of a key to this table.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_taxoverride.pm b/FS/FS/part_pkg_taxoverride.pm
new file mode 100644
index 0000000..0fdfa50
--- /dev/null
+++ b/FS/FS/part_pkg_taxoverride.pm
@@ -0,0 +1,119 @@
+package FS::part_pkg_taxoverride;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_pkg_taxoverride - Object methods for part_pkg_taxoverride records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_taxoverride;
+
+ $record = new FS::part_pkg_taxoverride \%hash;
+ $record = new FS::part_pkg_taxoverride { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_taxoverride object represents a manual mapping of a
+package to tax rates. FS::part_pkg_taxoverride inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item taxoverridenum
+
+Primary key
+
+=item pkgpart
+
+The package definition id
+
+=item taxclassnum
+
+The tax class id
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax override. To add the tax product 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 { 'part_pkg_taxoverride'; }
+
+=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 tax product. 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('taxoverridenum')
+ || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=cut
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_taxproduct.pm b/FS/FS/part_pkg_taxproduct.pm
new file mode 100644
index 0000000..56e63b6
--- /dev/null
+++ b/FS/FS/part_pkg_taxproduct.pm
@@ -0,0 +1,139 @@
+package FS::part_pkg_taxproduct;
+
+use strict;
+use vars qw( @ISA $delete_kludge );
+use FS::Record qw( qsearch );
+
+@ISA = qw(FS::Record);
+$delete_kludge = 0;
+
+=head1 NAME
+
+FS::part_pkg_taxproduct - Object methods for part_pkg_taxproduct records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_taxproduct;
+
+ $record = new FS::part_pkg_taxproduct \%hash;
+ $record = new FS::part_pkg_taxproduct { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_taxproduct object represents a tax product.
+FS::part_pkg_taxproduct inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item taxproductnum
+
+Primary key
+
+=item data_vendor
+
+Tax data vendor
+
+=item taxproduct
+
+Tax product id from the vendor
+
+=item description
+
+A human readable description of the id in taxproduct
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax product. To add the tax product 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 { 'part_pkg_taxproduct'; }
+
+=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
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a tax product which has attached package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxproductnum' => $self->taxproductnum } );
+
+ unless ( $delete_kludge ) {
+ return "Can't delete a tax product which has attached packages!"
+ if qsearch( 'part_pkg', { 'taxproductnum' => $self->taxproductnum } );
+ }
+
+ $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.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid tax product. 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('taxproductnum')
+ || $self->ut_textn('data_vendor')
+ || $self->ut_text('taxproduct')
+ || $self->ut_textn('description')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=cut
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg_taxrate.pm b/FS/FS/part_pkg_taxrate.pm
new file mode 100644
index 0000000..5a1e7ba
--- /dev/null
+++ b/FS/FS/part_pkg_taxrate.pm
@@ -0,0 +1,420 @@
+package FS::part_pkg_taxrate;
+
+use strict;
+use vars qw( @ISA );
+use Date::Parse;
+use DateTime;
+use DateTime::Format::Strptime;
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+use FS::part_pkg_taxproduct;
+use FS::Misc qw(csv_from_fixed);
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_pkg_taxrate - Object methods for part_pkg_taxrate records
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg_taxrate;
+
+ $record = new FS::part_pkg_taxrate \%hash;
+ $record = new FS::part_pkg_taxrate { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_taxrate object maps packages onto tax rates.
+FS::part_pkg_taxrate inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item pkgtaxratenum
+
+Primary key
+
+=item data_vendor
+
+Tax data vendor
+
+=item geocode
+
+Tax vendor location code
+
+=item taxproductnum
+
+Class of package for tax purposes, Index into FS::part_pkg_taxproduct
+
+=item city
+
+city
+
+=item county
+
+county
+
+=item state
+
+state
+
+=item local
+
+local
+
+=item country
+
+country
+
+=item taxclassnum
+
+Class of tax index into FS::tax_taxclass and FS::tax_rate
+
+=item taxclassnumtaxed
+
+Class of tax taxed by this entry.
+
+=item taxable
+
+taxable
+
+=item effdate
+
+effdate
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer (location), package, tax rate mapping. To add the
+mapping 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 { 'part_pkg_taxrate'; }
+
+=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 tax rate mapping. 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('pkgtaxratenum')
+ || $self->ut_textn('data_vendor')
+ || $self->ut_textn('geocode')
+ || $self->
+ ut_foreign_key('taxproductnum', 'part_pkg_taxproduct', 'taxproductnum')
+ || $self->ut_textn('city')
+ || $self->ut_textn('county')
+ || $self->ut_textn('state')
+ || $self->ut_textn('local')
+ || $self->ut_text('country')
+ || $self->ut_foreign_keyn('taxclassnumtaxed', 'tax_class', 'taxclassnum')
+ || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
+ || $self->ut_snumbern('effdate')
+ || $self->ut_enum('taxable', [ 'Y', '' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item batch_import
+
+Loads part_pkg_taxrate records from an external CSV file. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub batch_import {
+ my ($param, $job) = @_;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my $imported = 0;
+ my @fields;
+ my $hook;
+
+ my @column_lengths = ();
+ my @column_callbacks = ();
+ if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
+ $format =~ s/-fixed//;
+ my $date_format = sub { my $r='';
+ /^(\d{4})(\d{2})(\d{2})$/ && ($r="$3/$2/$1");
+ $r;
+ };
+ $column_callbacks[16] = $date_format;
+ push @column_lengths, qw( 28 25 2 1 10 4 30 3 100 2 2 2 2 1 2 2 8 1 );
+ push @column_lengths, 1 if $format eq 'cch-update';
+ }
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job || scalar(@column_callbacks) ) {
+ my $error =
+ csv_from_fixed(\$fh, \$count, \@column_lengths, \@column_callbacks);
+ return $error if $error;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
+ @fields = qw( city county state local geocode group groupdesc item
+ itemdesc provider customer taxtypetaxed taxcattaxed
+ taxable taxtype taxcat effdate rectype );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $imported++ if $format eq 'cch-update'; #empty file ok
+
+ $hook = sub {
+ my $hash = shift;
+
+ unless ( $hash->{'rectype'} eq 'R' or $hash->{'rectype'} eq 'T' ) {
+ delete($hash->{$_}) for (keys %$hash);
+ return;
+ }
+
+ $hash->{'data_vendor'} = 'cch';
+
+ my %providers = ( '00' => 'Regulated LEC',
+ '01' => 'Regulated IXC',
+ '02' => 'Unregulated LEC',
+ '03' => 'Unregulated IXC',
+ '04' => 'ISP',
+ '05' => 'Wireless',
+ );
+
+ my %customers = ( '00' => 'Residential',
+ '01' => 'Commercial',
+ '02' => 'Industrial',
+ '09' => 'Lifeline',
+ '10' => 'Senior Citizen',
+ );
+
+ my $taxproduct =
+ join(':', map{ $hash->{$_} } qw(group item provider customer ) );
+
+ my %part_pkg_taxproduct = ( 'data_vendor' => 'cch',
+ 'taxproduct' => $taxproduct,
+ );
+
+ my $part_pkg_taxproduct = qsearchs( 'part_pkg_taxproduct',
+ { %part_pkg_taxproduct }
+ );
+
+ unless ($part_pkg_taxproduct) {
+ return "Can't find part_pkg_taxproduct for txmatrix deletion: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ if ($hash->{'actionfield'} && $hash->{'actionflag'} eq 'D');
+
+ $part_pkg_taxproduct{'description'} =
+ join(' : ', (map{ $hash->{$_} } qw(groupdesc itemdesc)),
+ $providers{$hash->{'provider'}} || '',
+ $customers{$hash->{'customer'}} || '',
+ );
+ $part_pkg_taxproduct = new FS::part_pkg_taxproduct \%part_pkg_taxproduct;
+ my $error = $part_pkg_taxproduct->insert;
+ return "Error inserting tax product (part_pkg_taxproduct): $error"
+ if $error;
+
+ }
+ $hash->{'taxproductnum'} = $part_pkg_taxproduct->taxproductnum;
+
+ delete($hash->{$_})
+ for qw(group groupdesc item itemdesc provider customer rectype );
+
+ my %map = ( 'taxclassnum' => [ 'taxtype', 'taxcat' ],
+ 'taxclassnumtaxed' => [ 'taxtypetaxed', 'taxcattaxed' ],
+ );
+
+ for my $item (keys %map) {
+ my $class = join(':', map($hash->{$_}, @{$map{$item}}));
+ my $tax_class =
+ qsearchs( 'tax_class',
+ { data_vendor => 'cch',
+ 'taxclass' => $class,
+ }
+ );
+ $hash->{$item} = $tax_class->taxclassnum
+ if $tax_class;
+
+ return "Can't find tax class for txmatrix deletion: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ if ( $hash->{'actionflag'} && $hash->{'actionflag'} eq 'D' &&
+ !$tax_class && $class ne ':'
+ );
+
+ delete($hash->{$_}) foreach @{$map{$item}};
+ }
+
+ my $parser = new DateTime::Format::Strptime( pattern => "%m/%d/%Y",
+ time_zone => 'floating',
+ );
+ my $dt = $parser->parse_datetime( $hash->{'effdate'} );
+ $hash->{'effdate'} = $dt ? $dt->epoch : '';
+
+ $hash->{'country'} = 'US'; # CA is available
+
+ delete($hash->{'taxable'}) if ($hash->{'taxable'} eq 'N');
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ delete($hash->{actionflag});
+
+ my $part_pkg_taxrate = qsearchs('part_pkg_taxrate', $hash);
+ unless ( $part_pkg_taxrate ) {
+ if ( $hash->{taxproductnum} ) {
+ my $taxproduct =
+ qsearchs( 'part_pkg_taxproduct',
+ { 'taxproductnum' => $hash->{taxproductnum} }
+ );
+ $hash->{taxproductnum} .= ' ( '. $taxproduct->taxproduct. ' )'
+ if $taxproduct;
+ }
+ return "Can't find part_pkg_taxrate to delete: ".
+ join(" ", map { "$_ => *". $hash->{$_}. '*' } keys(%$hash) );
+ }
+
+ my $error = $part_pkg_taxrate->delete;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{actionflag});
+
+ '';
+ };
+
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $hook = sub {};
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ 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;
+
+ while ( defined($line=<$fh>) ) {
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax matrix"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my @columns = $csv->fields();
+
+ my %part_pkg_taxrate = ( 'data_vendor' => $format );
+ foreach my $field ( @fields ) {
+ $part_pkg_taxrate{$field} = shift @columns;
+ }
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%part_pkg_taxrate);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ next unless scalar(keys %part_pkg_taxrate);
+
+
+ my $part_pkg_taxrate = new FS::part_pkg_taxrate( \%part_pkg_taxrate );
+ $error = $part_pkg_taxrate->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert part_pkg_taxrate for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless ( $imported || $format eq 'cch-update' );
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..01c59df
--- /dev/null
+++ b/FS/FS/part_pop_local.pm
@@ -0,0 +1,113 @@
+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 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..c94c57e
--- /dev/null
+++ b/FS/FS/part_referral.pm
@@ -0,0 +1,208 @@
+package FS::part_referral;
+
+use strict;
+use vars qw( @ISA $setup_hack );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::agent;
+
+@ISA = qw( FS::Record );
+$setup_hack = 0;
+
+=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'
+
+=item agentnum - Optional agentnum (see L<FS::agent>)
+
+=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')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
+ || ( $setup_hack
+ ? $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum' )
+ : $self->ut_agentnum_acl('agentnum', 'Edit global advertising sources')
+ )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item agent
+
+Returns the associated agent for this referral, if any, as an FS::agent object.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item acl_agentnum_sql [ INCLUDE_GLOBAL_BOOL ]
+
+Returns an SQL fragment for searching for part_referral records allowed by the
+current users's agent ACLs (and "Edit global advertising sources" right).
+
+Pass a true value to include global advertising sources (for example, when
+simply using rather than editing advertising sources).
+
+=cut
+
+sub acl_agentnum_sql {
+ my $self = shift;
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ my $sql = $curuser->agentnums_sql;
+ $sql = " ( $sql OR agentnum IS NULL ) "
+ if $curuser->access_right('Edit global advertising sources')
+ or defined($_[0]) && $_[0];
+
+ $sql;
+
+}
+
+=item all_part_referral [ INCLUDE_GLOBAL_BOOL ]
+
+Returns all part_referral records allowed by the current users's agent ACLs
+(and "Edit global advertising sources" right).
+
+Pass a true value to include global advertising sources (for example, when
+simply using rather than editing advertising sources).
+
+=cut
+
+sub all_part_referral {
+ my $self = shift;
+
+ qsearch({
+ 'table' => 'part_referral',
+ 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql(@_). ' ORDER BY refnum ',
+ });
+
+}
+
+=item num_part_referral [ INCLUDE_GLOBAL_BOOL ]
+
+Returns the number of part_referral records allowed by the current users's
+agent ACLs (and "Edit global advertising sources" right).
+
+=cut
+
+sub num_part_referral {
+ my $self = shift;
+
+ my $sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql(@_)
+ ) or die dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=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..3ed153e
--- /dev/null
+++ b/FS/FS/part_svc.pm
@@ -0,0 +1,870 @@
+package FS::part_svc;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Tie::IxHash;
+use FS::Record qw( qsearch qsearchs fields dbh );
+use FS::Schema qw( dbdef );
+use FS::part_svc_column;
+use FS::part_export;
+use FS::export_svc;
+use FS::cust_svc;
+
+@ISA = qw(FS::Record);
+
+$DEBUG = 0;
+
+=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 [ , JOB ] ] ]
+
+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 or empty (no default), `D' for default, `F' for fixed (unchangeable), `M' for manual selection from inventory, or `A' for automatic selection from inventory. 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.
+
+TODOC: JOB
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my @fields = ();
+ my @exportnums = ();
+ @fields = @{shift(@_)} if @_;
+ if ( @_ ) {
+ my $exportnums = shift;
+ @exportnums = grep $exportnums->{$_}, keys %$exportnums;
+ }
+ my $job = '';
+ $job = shift if @_;
+
+ 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') )
+ || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
+ } (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');
+ my $label = $self->getfield($svcdb.'__'.$field.'_label');
+ if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
+
+ if ( uc($flag) =~ /^([A-Z])$/ ) {
+ my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
+ || sub { shift };
+ $part_svc_column->setfield('columnflag', $1);
+ $part_svc_column->setfield('columnvalue',
+ &$parser($self->getfield($svcdb.'__'.$field))
+ );
+ }
+
+ $part_svc_column->setfield('columnlabel', $label)
+ if $label !~ /^\s*$/;
+
+ 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
+ my $slice = 100/scalar(@exportnums) if @exportnums;
+ my $done = 0;
+ foreach my $exportnum ( @exportnums ) {
+ my $export_svc = new FS::export_svc ( {
+ 'exportnum' => $exportnum,
+ 'svcpart' => $self->svcpart,
+ } );
+ $error = $export_svc->insert($job, $slice*$done++, $slice);
+ 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 [ , JOB ] ] ] ]
+
+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)
+
+TODOC: JOB
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+ my $compat = '';
+ my @fields = ();
+ my $exportnums;
+ my $job = '';
+ if ( @_ && $_[0] eq '1.3-COMPAT' ) {
+ shift;
+ $compat = '1.3';
+ @fields = @{shift(@_)} if @_;
+ $exportnums = @_ ? shift : '';
+ $job = shift if @_;
+ } else {
+ return 'non-1.3-COMPAT interface not yet written';
+ #not yet implemented
+ }
+
+ 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 ( $compat eq '1.3' ) {
+
+ # maintain part_svc_column records
+
+ my $svcdb = $new->svcdb;
+ foreach my $field (
+ grep { $_ ne 'svcnum'
+ && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') )
+ || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
+ } (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');
+ my $label = $new->getfield($svcdb.'__'.$field.'_label');
+
+ if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
+
+ if ( uc($flag) =~ /^([A-Z])$/ ) {
+ $part_svc_column->setfield('columnflag', $1);
+ my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
+ || sub { shift };
+ $part_svc_column->setfield('columnvalue',
+ &$parser($new->getfield($svcdb.'__'.$field))
+ );
+ } else {
+ $part_svc_column->setfield('columnflag', '');
+ $part_svc_column->setfield('columnvalue', '');
+ }
+
+ $part_svc_column->setfield('columnlabel', $label)
+ if $label !~ /^\s*$/;
+
+ 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
+ my @new_export_svc = ();
+ 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} ) {
+ push @new_export_svc, new FS::export_svc ( $hashref );
+ }
+
+ }
+
+ my $slice = 100/scalar(@new_export_svc) if @new_export_svc;
+ my $done = 0;
+ foreach my $export_svc (@new_export_svc) {
+ $error = $export_svc->insert($job, $slice*$done++, $slice);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ if ( $job ) {
+ $error = $job->update_statustext( int( $slice * $done ) );
+ 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 $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( $self->svcdb ) }; #might die
+ return "Unknown svcdb: ". $self->svcdb. " (Error: $@)"
+ 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 a list of 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 part_export_usage
+
+Returns a list of any exports (see L<FS::part_export>) for this service that
+are capable of reporting usage information.
+
+=cut
+
+sub part_export_usage {
+ my $self = shift;
+ grep $_->can('usage_sessions'), $self->part_export;
+}
+
+=item part_export_did
+
+Returns a list of any exports (see L<FS::part_export>) for this service that
+are capable of returing available DID (phone number) information.
+
+=cut
+
+sub part_export_did {
+ my $self = shift;
+ grep $_->can('get_dids'), $self->part_export;
+}
+
+
+=item cust_svc [ PKGPART ]
+
+Returns a list of associated customer services (FS::cust_svc records).
+
+If a PKGPART is specified, returns the customer services which are contained
+within packages of that type (see L<FS::part_pkg>). If PKGPARTis specified as
+B<0>, returns unlinked customer services.
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+
+ my $hashref = { 'svcpart' => $self->svcpart };
+
+ my( $addl_from, $extra_sql ) = ( '', '' );
+ if ( @_ ) {
+ my $pkgpart = shift;
+ if ( $pkgpart =~ /^(\d+)$/ ) {
+ $addl_from = 'LEFT JOIN cust_pkg USING ( pkgnum )';
+ $extra_sql = "AND pkgpart = $1";
+ } elsif ( $pkgpart eq '0' ) {
+ $hashref->{'pkgnum'} = '';
+ }
+ }
+
+ qsearch({
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+ });
+}
+
+=item num_cust_svc [ PKGPART ]
+
+Returns the number of associated customer services (FS::cust_svc records).
+
+If a PKGPART is specified, returns the number of customer services which are
+contained within packages of that type (see L<FS::part_pkg>). If PKGPART
+is specified as B<0>, returns the number of unlinked customer services.
+
+=cut
+
+sub num_cust_svc {
+ my $self = shift;
+
+ my @param = ( $self->svcpart );
+
+ my( $join, $and ) = ( '', '' );
+ if ( @_ ) {
+ my $pkgpart = shift;
+ if ( $pkgpart ) {
+ $join = 'LEFT JOIN cust_pkg USING ( pkgnum )';
+ $and = 'AND pkgpart = ?';
+ push @param, $pkgpart;
+ } elsif ( $pkgpart eq '0' ) {
+ $and = 'AND pkgnum IS NULL';
+ }
+ }
+
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM cust_svc $join WHERE svcpart = ? $and"
+ ) or die dbh->errstr;
+ $sth->execute(@param)
+ or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=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 CLASS METHODS
+
+=over 4
+
+=cut
+
+my $svc_defs;
+sub _svc_defs {
+
+ return $svc_defs if $svc_defs; #cache
+
+ my $conf = new FS::Conf;
+
+ #false laziness w/part_pkg.pm::plan_info
+
+ my %info;
+ foreach my $INC ( @INC ) {
+ warn "globbing $INC/FS/svc_*.pm\n" if $DEBUG;
+ foreach my $file ( glob("$INC/FS/svc_*.pm") ) {
+
+ warn "attempting to load service table info from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/: $file\n";
+ next;
+ };
+ my $mod = $1;
+
+ if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^svc_acct_pop$/ ) {
+ warn "skipping FS::$mod" if $DEBUG;
+ next;
+ }
+
+ eval "use FS::$mod;";
+ if ( $@ ) {
+ die "error using FS::$mod (skipping): $@\n" if $@;
+ next;
+ }
+ unless ( UNIVERSAL::can("FS::$mod", 'table_info') ) {
+ warn "FS::$mod has no table_info method; skipping";
+ next;
+ }
+
+ my $info = "FS::$mod"->table_info;
+ unless ( keys %$info ) {
+ warn "FS::$mod->table_info doesn't return info, skipping\n";
+ next;
+ }
+ warn "got info from FS::$mod: $info\n" if $DEBUG;
+ if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
+ warn "skipping disabled service FS::$mod" if $DEBUG;
+ next;
+ }
+ $info{$mod} = $info;
+ }
+ }
+
+ tie my %svc_defs, 'Tie::IxHash',
+ map { $_ => $info{$_}->{'fields'} }
+ sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} }
+ keys %info,
+ ;
+
+ # yuck. maybe this won't be so bad when virtual fields become real fields
+ my %vfields;
+ foreach my $svcdb (grep dbdef->table($_), keys %svc_defs ) {
+ eval "use FS::$svcdb;";
+ 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) {
+ $svc_defs{$svcdb}->{$field} = { desc => $pvf->label,
+ type => 'select',
+ select_list => \@list };
+ } else {
+ $svc_defs{$svcdb}->{$field} = $pvf->label;
+ } #endif
+ $vfields{$svcdb}->{$field} = $pvf;
+ warn "\$vfields{$svcdb}->{$field} = $pvf"
+ if $DEBUG;
+ } #next $field
+ } #next $svcdb
+
+ $svc_defs = \%svc_defs; #cache
+
+}
+
+=item svc_tables
+
+Returns a list of all svc_ tables.
+
+=cut
+
+sub svc_tables {
+ my $class = shift;
+ my $svc_defs = $class->_svc_defs;
+ grep { defined( dbdef->table($_) ) } keys %$svc_defs;
+}
+
+=item svc_table_fields TABLE
+
+Given a table name, returns a hashref of field names. The field names
+returned are those with additional (service-definition related) information,
+not necessarily all database fields of the table. Pseudo-fields may also
+be returned (i.e. svc_acct.usergroup).
+
+Each value of the hashref is another hashref, which can have one or more of
+the following keys:
+
+=over 4
+
+=item label - Description of the field
+
+=item def_label - Optional description of the field in the context of service definitions
+
+=item type - Currently "text", "select", "disabled", or "radius_usergroup_selector"
+
+=item disable_default - This field should not allow a default value in service definitions
+
+=item disable_fixed - This field should not allow a fixed value in service definitions
+
+=item disable_inventory - This field should not allow inventory values in service definitions
+
+=item select_list - If type is "text", this can be a listref of possible values.
+
+=item select_table - An alternative to select_list, this defines a database table with the possible choices.
+
+=item select_key - Used with select_table, this is the field name of keys
+
+=item select_label - Used with select_table, this is the field name of labels
+
+=back
+
+=cut
+
+#maybe this should move and be a class method in svc_Common.pm
+sub svc_table_fields {
+ my($class, $table) = @_;
+ my $svc_defs = $class->_svc_defs;
+ my $def = $svc_defs->{$table};
+
+ foreach ( grep !ref($def->{$_}), keys %$def ) {
+
+ #normalize the shortcut in %info hash
+ $def->{$_} = { 'label' => $def->{$_} };
+
+ $def->{$_}{'type'} ||= 'text';
+
+ }
+
+ $def;
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item process
+
+Job-queue processor for web interface adds/edits
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $old = qsearchs('part_svc', { 'svcpart' => $param->{'svcpart'} })
+ if $param->{'svcpart'};
+
+ $param->{'svc_acct__usergroup'} =
+ ref($param->{'svc_acct__usergroup'})
+ ? join(',', @{$param->{'svc_acct__usergroup'}} )
+ : $param->{'svc_acct__usergroup'};
+
+ #unmunge cgp_accessmodes (falze laziness-ish w/edit/process/svc_acct.cgi)
+ $param->{'svc_acct__cgp_accessmodes'} ||=
+ join(' ', sort
+ grep { $_ !~ /^(flag|label)$/ }
+ map { /^svc_acct__cgp_accessmodes_([\w\/]+)$/ or die "no way"; $1; }
+ grep $param->{$_},
+ grep /^svc_acct__cgp_accessmodes_([\w\/]+)$/,
+ keys %$param
+ );
+
+
+ my $new = new FS::part_svc ( {
+ map {
+ $_ => $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 {
+ my $f = $svcdb.'__'.$_;
+ if ( $param->{ $f.'_flag' } =~ /^[MA]$/ ) {
+ $param->{ $f } = delete( $param->{ $f.'_classnum' } );
+ }
+ if ( $param->{ $f.'_flag' } =~ /^S$/ ) {
+ $param->{ $f } = ref($param->{ $f })
+ ? join(',', @{$param->{ $f }} )
+ : $param->{ $f };
+ }
+ ( $f, $f.'_flag', $f.'_label' );
+ }
+ @fields;
+
+ } FS::part_svc->svc_tables()
+ )
+ } );
+
+ my %exportnums =
+ map { $_->exportnum => ( $param->{'exportnum'.$_->exportnum} || '') }
+ qsearch('part_export', {} );
+
+ my $error;
+ if ( $param->{'svcpart'} ) {
+ $error = $new->replace( $old,
+ '1.3-COMPAT', #totally bunk, as jeff noted
+ [ 'usergroup' ],
+ \%exportnums,
+ $job
+ );
+ } else {
+ $error = $new->insert( [ 'usergroup' ],
+ \%exportnums,
+ $job,
+ );
+ $param->{'svcpart'} = $new->getfield('svcpart');
+ }
+
+ die "$error\n" if $error;
+}
+
+=item process_bulk_cust_svc
+
+Job-queue processor for web interface bulk customer service changes
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_bulk_cust_svc {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $old_part_svc =
+ qsearchs('part_svc', { 'svcpart' => $param->{'old_svcpart'} } );
+
+ die "Must select a new service definition\n" unless $param->{'new_svcpart'};
+
+ #the rest should be abstracted out to to its own subroutine?
+
+ 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::cust_svc::ignore_quantity ) = 1;
+
+ my $total = $old_part_svc->num_cust_svc( $param->{'pkgpart'} );
+
+ my $n = 0;
+ foreach my $old_cust_svc ( $old_part_svc->cust_svc( $param->{'pkgpart'} ) ) {
+
+ my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
+
+ $new_cust_svc->svcpart( $param->{'new_svcpart'} );
+ my $error = $new_cust_svc->replace($old_cust_svc);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die "$error\n" if $error;
+ }
+
+ $error = $job->update_statustext( int( 100 * ++$n / $total ) );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die $error if $error;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=head1 BUGS
+
+Delete is unimplemented.
+
+The list of svc_* tables is no longer hardcoded, but svc_acct_pop is skipped
+as a special case until it is renamed.
+
+all_part_svc_column methods 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..f5b39c0
--- /dev/null
+++ b/FS/FS/part_svc_column.pm
@@ -0,0 +1,123 @@
+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 columnlabel - label for the column
+
+=item columnvalue - default or fixed value for the column
+
+=item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded.
+
+=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_textn('columnlabel')
+ || $self->ut_anything('columnvalue')
+ ;
+ return $error if $error;
+
+ $self->columnflag =~ /^([DFSMAX]?)$/
+ or return "illegal columnflag ". $self->columnflag;
+ $self->columnflag(uc($1));
+
+ if ( $self->columnflag =~ /^[MA]$/ ) {
+ $error =
+ $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' );
+ return $error if $error;
+ }
+
+ $self->SUPER::check;
+}
+
+=back
+
+=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..df04cc9
--- /dev/null
+++ b/FS/FS/part_svc_router.pm
@@ -0,0 +1,33 @@
+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_numbern('svcrouternum')
+ || $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..f5a4161
--- /dev/null
+++ b/FS/FS/part_virtual_field.pm
@@ -0,0 +1,301 @@
+package FS::part_virtual_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record;
+use FS::Schema qw( dbdef );
+use CGI qw(escapeHTML);
+
+@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 = 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="! . escapeHTML($value) . q!"!;
+ if ($self->length) {
+ $text .= q! SIZE="! . $self->length . q!"!;
+ }
+ $text .= '>';
+ }
+ $text .= q!</TD></TR>! . "\n";
+ } else {
+ return '';
+ }
+ } else {
+ return '';
+ }
+ return $text;
+}
+
+=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/pay_batch.pm b/FS/FS/pay_batch.pm
new file mode 100644
index 0000000..6a27554
--- /dev/null
+++ b/FS/FS/pay_batch.pm
@@ -0,0 +1,505 @@
+package FS::pay_batch;
+
+use strict;
+use vars qw( @ISA $DEBUG %import_info %export_info $conf );
+use Time::Local;
+use Text::CSV_XS;
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::cust_pay;
+use FS::Conf;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::pay_batch - Object methods for pay_batch records
+
+=head1 SYNOPSIS
+
+ use FS::pay_batch;
+
+ $record = new FS::pay_batch \%hash;
+ $record = new FS::pay_batch { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pay_batch object represents an payment batch. FS::pay_batch inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item batchnum - primary key
+
+=item payby - CARD or CHEK
+
+=item status - O (Open), I (In-transit), or R (Resolved)
+
+=item download -
+
+=item upload -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new batch. To add the batch 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 { 'pay_batch'; }
+
+=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 batch. 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('batchnum')
+ || $self->ut_enum('payby', [ 'CARD', 'CHEK' ])
+ || $self->ut_enum('status', [ 'O', 'I', 'R' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item rebalance
+
+=cut
+
+sub rebalance {
+ my $self = shift;
+}
+
+=item set_status
+
+=cut
+
+sub set_status {
+ my $self = shift;
+ $self->status(shift);
+ $self->download(time)
+ if $self->status eq 'I' && ! $self->download;
+ $self->upload(time)
+ if $self->status eq 'R' && ! $self->upload;
+ $self->replace();
+}
+
+# further false laziness
+
+%import_info = %export_info = ();
+foreach my $INC (@INC) {
+ warn "globbing $INC/FS/pay_batch/*.pm\n" if $DEBUG;
+ foreach my $file ( glob("$INC/FS/pay_batch/*.pm")) {
+ warn "attempting to load batch format from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/;
+ next if !$1;
+ my $mod = $1;
+ my ($import, $export, $name) =
+ eval "use FS::pay_batch::$mod;
+ ( \\%FS::pay_batch::$mod\::import_info,
+ \\%FS::pay_batch::$mod\::export_info,
+ \$FS::pay_batch::$mod\::name)";
+ $name ||= $mod; # in case it's not defined
+ if( $@) {
+ # in FS::cdr this is a die, not a warn. That's probably a bug.
+ warn "error using FS::pay_batch::$mod (skipping): $@\n";
+ next;
+ }
+ if(!keys(%$import)) {
+ warn "no \%import_info found in FS::pay_batch::$mod (skipping)\n";
+ }
+ else {
+ $import_info{$name} = $import;
+ }
+ if(!keys(%$export)) {
+ warn "no \%export_info found in FS::pay_batch::$mod (skipping)\n";
+ }
+ else {
+ $export_info{$name} = $export;
+ }
+ }
+}
+
+=item import_results OPTION => VALUE, ...
+
+Import batch results.
+
+Options are:
+
+I<filehandle> - open filehandle of results file.
+
+I<format> - "csv-td_canada_trust-merchant_pc_batch", "csv-chase_canada-E-xactBatch", "ach-spiritone", or "PAP"
+
+=cut
+
+sub import_results {
+ my $self = shift;
+
+ my $param = ref($_[0]) ? shift : { @_ };
+ my $fh = $param->{'filehandle'};
+ my $format = $param->{'format'};
+ my $info = $import_info{$format}
+ or die "unknown format $format";
+
+ my $filetype = $info->{'filetype'}; # CSV or fixed
+ my @fields = @{ $info->{'fields'}};
+ my $formatre = $info->{'formatre'}; # for fixed
+ my @all_values;
+ my $begin_condition = $info->{'begin_condition'};
+ my $end_condition = $info->{'end_condition'};
+ my $end_hook = $info->{'end_hook'};
+ my $skip_condition = $info->{'skip_condition'};
+ my $hook = $info->{'hook'};
+ my $approved_condition = $info->{'approved'};
+ my $declined_condition = $info->{'declined'};
+
+ 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 $reself = $self->select_for_update;
+
+ unless ( $reself->status eq 'I' ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "batchnum ". $self->batchnum. "no longer in transit";
+ }
+
+ my $error = $self->set_status('R');
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $total = 0;
+ my $line;
+
+ # Order of operations has been changed here.
+ # We now slurp everything into @all_values, then
+ # process one line at a time.
+
+ if ($filetype eq 'XML') {
+ eval "use XML::Simple";
+ die $@ if $@;
+ my @xmlkeys = @{ $info->{'xmlkeys'} }; # for XML
+ my $xmlrow = $info->{'xmlrow'}; # also for XML
+
+ # Do everything differently.
+ my $data = XML::Simple::XMLin($fh, KeepRoot => 1);
+ my $rows = $data;
+ # $xmlrow = [ RootKey, FirstLevelKey, SecondLevelKey... ]
+ $rows = $rows->{$_} foreach( @$xmlrow );
+ if(!defined($rows)) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find rows in XML file";
+ }
+ $rows = [ $rows ] if ref($rows) ne 'ARRAY';
+ foreach my $row (@$rows) {
+ push @all_values, [ @{$row}{@xmlkeys}, $row ];
+ }
+ }
+ else {
+ while ( defined($line=<$fh>) ) {
+
+ next if $line =~ /^\s*$/; #skip blank lines
+
+ if ($filetype eq "CSV") {
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+ push @all_values, [ $csv->fields(), $line ];
+ }elsif ($filetype eq 'fixed'){
+ my @values = ( $line =~ /$formatre/ );
+ unless (@values) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $line;
+ };
+ push @values, $line;
+ push @all_values, \@values;
+ }else{
+ $dbh->rollback if $oldAutoCommit;
+ return "Unknown file type $filetype";
+ }
+ }
+ }
+
+ foreach (@all_values) {
+ my @values = @$_;
+
+ my %hash;
+ my $line = pop @values;
+ foreach my $field ( @fields ) {
+ my $value = shift @values;
+ next unless $field;
+ $hash{$field} = $value;
+ }
+
+ if ( defined($begin_condition) ) {
+ if ( &{$begin_condition}(\%hash, $line) ) {
+ undef $begin_condition;
+ }
+ else {
+ next;
+ }
+ }
+
+ if ( defined($end_condition) and &{$end_condition}(\%hash, $line) ) {
+ my $error;
+ $error = &{$end_hook}(\%hash, $total, $line) if defined($end_hook);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ last;
+ }
+
+ if ( defined($skip_condition) and &{$skip_condition}(\%hash, $line) ) {
+ next;
+ }
+
+ my $cust_pay_batch =
+ qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'}+0 } );
+ unless ( $cust_pay_batch ) {
+ return "unknown paybatchnum $hash{'paybatchnum'}\n";
+ }
+ my $custnum = $cust_pay_batch->custnum,
+ my $payby = $cust_pay_batch->payby,
+
+ my $new_cust_pay_batch = new FS::cust_pay_batch { $cust_pay_batch->hash };
+
+ &{$hook}(\%hash, $cust_pay_batch->hashref);
+
+ if ( &{$approved_condition}(\%hash) ) {
+
+ $new_cust_pay_batch->status('Approved');
+
+ } elsif ( &{$declined_condition}(\%hash) ) {
+
+ $new_cust_pay_batch->status('Declined');
+
+ }
+
+ my $error = $new_cust_pay_batch->replace($cust_pay_batch);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n";
+ }
+
+ if ( $new_cust_pay_batch->status =~ /Approved/i ) {
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'custnum' => $custnum,
+ 'payby' => $payby,
+ 'paybatch' => $self->batchnum,
+ 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 ( $new_cust_pay_batch->status =~ /Declined/i ) {
+
+ #false laziness w/cust_main::collect
+
+ my $due_cust_event = $new_cust_pay_batch->cust_main->due_cust_event(
+ #'check_freq' => '1d', #?
+ 'eventtable' => 'cust_pay_batch',
+ 'objects' => [ $new_cust_pay_batch ],
+ );
+ unless( ref($due_cust_event) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $due_cust_event;
+ }
+
+ foreach my $cust_event ( @$due_cust_event ) {
+
+ #XXX lock event
+
+ #re-eval event conditions (a previous event could have changed things)
+ next unless $cust_event->test_conditions;
+
+ if ( my $error = $cust_event->do_event() ) {
+ # gah, even with transactions.
+ #$dbh->commit if $oldAutoCommit; #well.
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+# Formerly httemplate/misc/download-batch.cgi
+sub export_batch {
+ my $self = shift;
+ my $conf = new FS::Conf;
+ my $format = shift || $conf->config('batch-default_format')
+ or die "No batch format configured\n";
+ my $info = $export_info{$format} or die "Format not found: '$format'\n";
+ &{$info->{'init'}}($conf) if exists($info->{'init'});
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $first_download;
+ my $status = $self->status;
+ if ($status eq 'O') {
+ $first_download = 1;
+ my $error = $self->set_status('I');
+ die "error updating pay_batch status: $error\n" if $error;
+ } elsif ($status eq 'I' && $curuser->access_right('Reprocess batches')) {
+ $first_download = 0;
+ } else {
+ die "No pending batch.\n";
+ }
+
+ my $batch = '';
+ my $batchtotal = 0;
+ my $batchcount = 0;
+
+ my @cust_pay_batch = sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch('cust_pay_batch', { batchnum => $self->batchnum } );
+
+ my $h = $info->{'header'};
+ if(ref($h) eq 'CODE') {
+ $batch .= &$h($self, \@cust_pay_batch) . "\n";
+ }
+ else {
+ $batch .= $h . "\n";
+ }
+ foreach my $cust_pay_batch (@cust_pay_batch) {
+ if($first_download) {
+ my $balance = $cust_pay_batch->cust_main->balance;
+ my $error = '';
+ if($balance <= 0) { # then don't charge this customer
+ $error = $cust_pay_batch->delete;
+ undef $cust_pay_batch;
+ }
+ elsif($balance < $cust_pay_batch->amount) { # then reduce the charge to the remaining balance
+ $cust_pay_batch->amount($balance);
+ $error = $cust_pay_batch->replace;
+ }
+ # else $balance >= $cust_pay_batch->amount
+ if($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ }
+ if($cust_pay_batch) { # that is, it wasn't deleted
+ $batchcount++;
+ $batchtotal += $cust_pay_batch->amount;
+ $batch .= &{$info->{'row'}}($cust_pay_batch, $self, $batchcount, $batchtotal) . "\n";
+ }
+ }
+ my $f = $info->{'footer'};
+ if(ref($f) eq 'CODE') {
+ $batch .= &$f($self, $batchcount, $batchtotal) . "\n";
+ }
+ else {
+ $batch .= $f . "\n";
+ }
+
+ if ($info->{'autopost'}) {
+ my $error = &{$info->{'autopost'}}($self, $batch);
+ if($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return $batch;
+}
+
+=back
+
+=head1 BUGS
+
+status is somewhat redundant now that download and upload exist
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/pay_batch/BoM.pm b/FS/FS/pay_batch/BoM.pm
new file mode 100644
index 0000000..7bfc22a
--- /dev/null
+++ b/FS/FS/pay_batch/BoM.pm
@@ -0,0 +1,73 @@
+package FS::pay_batch::BoM;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'BoM';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [],
+ 'hook' => sub { die "Can't import BoM" },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid,
+ $datacenter,
+ $typecode,
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct) = $conf->config("batchconfig-BoM");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( "A%10s%04u%06u%05u%54s\n",
+ $origid,
+ $pay_batch->batchnum,
+ jdate($pay_batch->download),
+ $datacenter,
+ "") .
+ sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",
+ $typecode,
+ jdate($pay_batch->download),
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct);
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",
+ $cust_pay_batch->amount * 100,
+ $aba,
+ $account,
+ $cust_pay_batch->payname,
+ $cust_pay_batch->paybatchnum
+ );
+ },
+ footer => sub {
+ my ($pay_batch, $batchcount, $batchtotal) = @_;
+ sprintf( "YD%08u%014.0f%56s\n", $batchcount, $batchtotal*100, "").
+ sprintf( "Z%014u%04u%014u%05u%41s\n",
+ $batchtotal*100, $batchcount, "0", "0", "");
+ },
+);
+
+sub jdate {
+ my (@date) = localtime(shift);
+ sprintf("%03d%03d", $date[5] % 100, $date[7] + 1);
+}
+
+1;
+
diff --git a/FS/FS/pay_batch/PAP.pm b/FS/FS/pay_batch/PAP.pm
new file mode 100644
index 0000000..432ef07
--- /dev/null
+++ b/FS/FS/pay_batch/PAP.pm
@@ -0,0 +1,103 @@
+package FS::pay_batch::PAP;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'PAP';
+
+%import_info = (
+ 'filetype' => 'fixed',
+ 'formatre' => '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$',
+ 'fields' => [
+ 'recordtype',
+ 'batchnum',
+ 'datacenter',
+ 'paid',
+ '_date',
+ 'bank',
+ 'payinfo',
+ 'paybatchnum',
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
+ my $tmpdate = timelocal( 0,0,1,1,0,substr($hash->{'_date'}, 0, 3)+2000);
+ $tmpdate += 86400*(substr($hash->{'_date'}, 3, 3)-1) ;
+ $hash->{'_date'} = $tmpdate;
+ $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'};
+ },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+# Why does pay_batch.pm have approved_condition and declined_condition?
+# It doesn't even try to handle the case of neither condition being met.
+ 'end_hook' => sub {
+ my( $hash, $total) = @_;
+ $total = sprintf("%.2f", $total);
+ my $batch_total = $hash->{'datacenter'}.$hash->{'paid'}.
+ substr($hash->{'_date'},0,1); # YUCK!
+ $batch_total = sprintf("%.2f", $batch_total / 100 );
+ return "Our total $total does not match bank total $batch_total!"
+ if $total != $batch_total;
+ '';
+ },
+ 'end_condition' => sub {
+ my $hash = shift;
+ $hash->{recordtype} eq 'W';
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid,
+ $datacenter,
+ $typecode,
+ $shortname,
+ $longname,
+ $mybank,
+ $myacct) = $conf->config("batchconfig-PAP");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",
+ $origid,
+ $typecode,
+ cdate($pay_batch->download),
+ $shortname,
+ $mybank,
+ $myacct,
+ $pay_batch->batchnum,
+ "" )
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",
+ $cust_pay_batch->payname,
+ cdate($pay_batch->download),
+ $cust_pay_batch->paybatchnum,
+ $aba,
+ $account,
+ $cust_pay_batch->amount*100 );
+ },
+ footer => sub {
+ my ($pay_batch, $batchcount, $batchtotal) = @_;
+ sprintf( "T%08u%014.0f%57s\n",
+ $batchcount,
+ $batchtotal*100,
+ "" );
+ },
+);
+
+sub cdate {
+ my (@date) = localtime(shift);
+ sprintf("%02d%02d%02d", $date[3], $date[4] + 1, $date[5] % 100);
+}
+
+1;
+
diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm
new file mode 100644
index 0000000..6ee5771
--- /dev/null
+++ b/FS/FS/pay_batch/RBC.pm
@@ -0,0 +1,143 @@
+package FS::pay_batch::RBC;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Date::Format 'time2str';
+use FS::Conf;
+
+my $conf;
+my ($client_num, $shortname, $longname, $trans_code, $i);
+
+$name = 'RBC';
+# Royal Bank of Canada ACH Direct Payments Service
+
+%import_info = (
+ 'filetype' => 'fixed',
+ 'formatre' =>
+ '^(.).{18}(.{4}).{3}(.).{11}(.{19}).{6}(.{30}).{17}(.{9})(.{18}).{6}(.{14}).{23}(.).{9}\r?$',
+ 'fields' => [ qw(
+ recordtype
+ batchnum
+ subtype
+ paybatchnum
+ custname
+ bank
+ payinfo
+ paid
+ status
+ ) ],
+ 'hook' => sub {
+ my $hash = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
+ $hash->{'_date'} = time;
+ $hash->{'payinfo'} =~ s/^(\S+).*/$1/; # these often have trailing spaces
+ $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'};
+ },
+ 'approved' => sub {
+ my $hash = shift;
+ $hash->{'status'} eq ' '
+ },
+ 'declined' => sub {
+ my $hash = shift;
+ grep { $hash->{'status'} eq $_ } ('E', 'R', 'U', 'T');
+ },
+ 'begin_condition' => sub {
+ my $hash = shift;
+ $hash->{recordtype} eq '1'; # Detail Record
+ },
+ 'end_hook' => sub {
+ my( $hash, $total, $line ) = @_;
+ $total = sprintf("%.2f", $total);
+ # We assume here that this is an 'All Records' or 'Input Records'
+ # report.
+ my $batch_total = sprintf("%.2f", substr($line, 59, 18) / 100);
+ return "Our total $total does not match bank total $batch_total!"
+ if $total != $batch_total;
+ '';
+ },
+ 'end_condition' => sub {
+ my $hash = shift;
+ $hash->{recordtype} eq '4'; # Client Trailer Record
+ },
+ 'skip_condition' => sub {
+ my $hash = shift;
+ $hash->{'subtype'} ne '0';
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($client_num,
+ $shortname,
+ $longname,
+ $trans_code,
+ ) = $conf->config("batchconfig-RBC");
+ $i = 1;
+ },
+ header => sub {
+ my $pay_batch = shift;
+ '$$AAPASTD0152[PROD[NL$$'."\n".
+ '000001'.
+ 'A'.
+ 'HDR'.
+ sprintf("%10s", $client_num).
+ sprintf("%-30s", $longname).
+ sprintf("%04u", $pay_batch->batchnum).
+ time2str("%Y%j", $pay_batch->download).
+ 'CAD'.
+ '1'.
+ ' ' x 87 # filler/reserved fields
+ ;
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ $i++;
+ sprintf("%06u", $i).
+ 'D'.
+ sprintf("%3s",$trans_code).
+ sprintf("%10s",$client_num).
+ ' '.
+ sprintf("%-19s", $cust_pay_batch->paybatchnum).
+ '00'.
+ sprintf("%09u", $aba).
+ sprintf("%-18s", $account).
+ ' '.
+ sprintf("%010.0f",$cust_pay_batch->amount*100).
+ ' '.
+ time2str("%Y%j", $pay_batch->download).
+ sprintf("%-30s", $cust_pay_batch->cust_main->first . ' ' .
+ $cust_pay_batch->cust_main->last).
+ 'E'. # English
+ ' '.
+ sprintf("%-15s", $shortname).
+ 'CAD'.
+ ' '.
+ 'CAN'.
+ ' '.
+ 'N' # no customer optional information follows
+ ;
+# Note: IAT Address Information and Remittance records are not
+# supported. This means you probably can't process payments
+# destined to U.S. bank accounts. If you need this feature, contact
+# Freeside Internet Services.
+ },
+ footer => sub {
+ my ($pay_batch, $batchcount, $batchtotal) = @_;
+ sprintf("%06u", $i + 1).
+ 'Z'.
+ 'TRL'.
+ sprintf("%10s", $client_num).
+ ' ' x 20 .
+ sprintf("%06u", $batchcount).
+ sprintf("%014.0f", $batchtotal*100).
+ '00' .
+ '000000' . # total number of customer information records
+ ' ' x 84
+ ;
+ },
+);
+
+1;
+
diff --git a/FS/FS/pay_batch/ach_spiritone.pm b/FS/FS/pay_batch/ach_spiritone.pm
new file mode 100644
index 0000000..bd3bb14
--- /dev/null
+++ b/FS/FS/pay_batch/ach_spiritone.pm
@@ -0,0 +1,65 @@
+package FS::pay_batch::ach_spiritone;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+use File::Temp;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'ach-spiritone'; # note spelling
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ '', #name
+ 'paybatchnum',
+ 'aba',
+ 'payinfo',
+ '', #transaction type
+ 'paid',
+ '', #default transaction type
+ '', #default amount
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ $hash->{'_date'} = time;
+ $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'aba'};
+ },
+ 'approved' => sub { 1 },
+ 'declined' => sub { 0 },
+);
+
+%export_info = (
+# This is the simplest case.
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my ($account, $aba) = split('@', $cust_pay_batch->payinfo);
+ my $payname = $cust_pay_batch->first . ' ' . $cust_pay_batch->last;
+ $payname =~ tr/",/ /;
+ qq!"$payname","!.$cust_pay_batch->paybatchnum.
+ qq!","$aba","$account","27","!.$cust_pay_batch->amount.
+ qq!","27","0.00"!; #"
+ },
+ autopost => sub {
+ my ($pay_batch, $batch) = @_;
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp(
+ TEMPLATE => 'paybatch.'. $pay_batch->batchnum .'.XXXXXXXX',
+ DIR => $dir,
+ ) or return "can't open temp file: $!\n";
+
+ print $fh $batch;
+ seek $fh, 0, 0;
+
+ my $error = $pay_batch->import_results( 'filehandle' => $fh,
+ 'format' => $name,
+ );
+ return $error if $error;
+ },
+);
+
+1;
+
diff --git a/FS/FS/pay_batch/chase_canada.pm b/FS/FS/pay_batch/chase_canada.pm
new file mode 100644
index 0000000..909e4ae
--- /dev/null
+++ b/FS/FS/pay_batch/chase_canada.pm
@@ -0,0 +1,104 @@
+package FS::pay_batch::chase_canada;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my $origid;
+
+$name = 'csv-chase_canada-E-xactBatch';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ '',
+ '',
+ '',
+ 'paid',
+ 'auth',
+ 'payinfo',
+ '',
+ '',
+ 'bankcode',
+ 'bankmess',
+ 'etgcode',
+ 'etgmess',
+ '',
+ 'paybatchnum',
+ '',
+ 'result',
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ my $cpb = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} );
+ $hash->{'_date'} = time;
+ $hash->{'payinfo'} = $cpb->{'payinfo'}
+ if( substr($hash->{'payinfo'}, -4) eq substr($cpb->{'payinfo'}, -4) );
+ },
+ 'approved' => sub {
+ my $hash = shift;
+ $hash->{'etgcode'} eq '00' && $hash->{'result'} eq 'Approved';
+ },
+ 'declined' => sub {
+ my $hash = shift;
+ $hash->{'etgcode'} ne '00' || $hash->{'result'} eq 'Declined';
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ ($origid) = $conf->config("batchconfig-$name");
+ },
+ header => sub {
+ my $pay_batch = shift;
+ sprintf( '$$E-xactBatchFileV1.0$$%s:%03u$$%s',
+ sdate($pay_batch->download),
+ $pay_batch->batchnum,
+ $origid );
+ },
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+ my $payname = $cust_pay_batch->payname;
+ $payname =~ tr/",/ /;
+
+ join(',',
+ $cust_pay_batch->paybatchnum,
+ $cust_pay_batch->custnum,
+ $cust_pay_batch->invnum,
+ qq!"$payname"!,
+ '00',
+ $cust_pay_batch->payinfo,
+ $cust_pay_batch->amount,
+ expdate($cust_pay_batch->exp),
+ '',
+ ''
+ );
+ },
+ # no footer
+);
+
+sub sdate {
+ my (@date) = localtime(shift);
+ sprintf('%02d/%02d/%02d', $date[5] % 100, $date[4] + 1, $date[3]);
+}
+
+sub expdate {
+ my $exp = shift;
+ $exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my ($mon, $y) = ($2, $1);
+ if($conf->exists('batch-increment_expiration')) {
+ my ($curmon, $curyear) = (localtime(time))[4,5];
+ $curmon++;
+ $curyear -= 100;
+ $y++ while $y < $curyear || ($y == $curyear && $mon < $curmon);
+ }
+ $mon = "0$mon" if $mon =~ /^\d$/;
+ $y = "0$y" if $y =~ /^\d$/;
+ return "$mon$y";
+}
+
+1;
diff --git a/FS/FS/pay_batch/paymentech.pm b/FS/FS/pay_batch/paymentech.pm
new file mode 100644
index 0000000..89f0c77
--- /dev/null
+++ b/FS/FS/pay_batch/paymentech.pm
@@ -0,0 +1,117 @@
+package FS::pay_batch::paymentech;
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local;
+use Date::Format 'time2str';
+use Date::Parse 'str2time';
+use Tie::IxHash;
+use FS::Conf;
+
+my $conf;
+my ($bin, $merchantID, $terminalID, $username);
+$name = 'paymentech';
+
+%import_info = (
+ filetype => 'XML',
+ xmlrow => [ qw(transResponse newOrderResp) ],
+ fields => [
+ 'paybatchnum',
+ '_date',
+ 'approvalStatus',
+ ],
+ xmlkeys => [
+ 'orderID',
+ 'respDateTime',
+ 'approvalStatus',
+ ],
+ 'hook' => sub {
+ my ($hash, $oldhash) = @_;
+ my ($mon, $day, $year, $hour, $min, $sec) =
+ $hash->{'_date'} =~ /^(..)(..)(....)(..)(..)(..)$/;
+ $hash->{'_date'} = timelocal($sec, $min, $hour, $day, $mon-1, $year);
+ $hash->{'paid'} = $oldhash->{'amount'};
+ },
+ 'approved' => sub { my $hash = shift;
+ $hash->{'approvalStatus'}
+ },
+ 'declined' => sub { my $hash = shift;
+ ! $hash->{'approvalStatus'}
+ },
+);
+
+my %paytype = (
+ 'personal checking' => 'C',
+ 'personal savings' => 'S',
+ 'business checking' => 'X',
+ 'business savings' => 'X',
+ );
+
+%export_info = (
+ init => sub {
+# Load this at run time
+ eval "use XML::Writer";
+ die $@ if $@;
+ my $conf = shift;
+ ($bin, $terminalID, $merchantID, $username) =
+ $conf->config('batchconfig-paymentech');
+ },
+# Here we do all the work in the header function.
+ header => sub {
+ my $pay_batch = shift;
+ my @cust_pay_batch = @{(shift)};
+ my $count = 1;
+ my $output;
+ my $xml = new XML::Writer(OUTPUT => \$output, DATA_MODE => 1, DATA_INDENT => 2);
+ $xml->startTag('transRequest', RequestCount => scalar(@cust_pay_batch) + 1);
+ $xml->startTag('batchFileID');
+ $xml->dataElement(userID => $username);
+ $xml->dataElement(fileDateTime => time2str('%Y%m%d%H%M%S', time));
+ $xml->dataElement(fileID => 'FILEID');
+ $xml->endTag('batchFileID');
+
+ foreach (@cust_pay_batch) {
+ $xml->startTag('newOrder', BatchRequestNo => $count++);
+ tie my %order, 'Tie::IxHash', (
+ industryType => 'EC',
+ transType => 'AC',
+ bin => $bin,
+ merchantID => $merchantID,
+ terminalID => $terminalID,
+ ($_->payby eq 'CARD') ? (
+ ccAccountNum => $_->payinfo,
+ ccExp => time2str('%m%y', str2time($_->exp))
+ ) : (
+ ecpCheckRT => ($_->payinfo =~ /@(\d+)/),
+ ecpCheckDDA => ($_->payinfo =~ /(\d+)@/),
+ ecpBankAcctType => $paytype{lc($_->cust_main->paytype)},
+ ecpDelvMethod => 'A',
+ ),
+ avsZip => substr($_->zip, 0, 10),
+ avsAddress1 => substr($_->address1, 0, 30),
+ avsAddress2 => substr($_->address2, 0, 30),
+ avsCity => substr($_->city, 0, 20),
+ avsState => $_->state,
+ avsName => substr($_->first . ' ' . $_->last, 0, 30),
+ avsCountryCode => $_->country,
+ orderID => $_->paybatchnum,
+ amount => $_->amount * 100,
+ );
+ foreach my $key (keys %order) {
+ $xml->dataElement($key, $order{$key})
+ }
+ $xml->endTag('newOrder');
+ }
+ $xml->startTag('endOfDay', BatchRequestNo => $count);
+ $xml->dataElement(bin => $bin);
+ $xml->dataElement(merchantID => $merchantID);
+ $xml->dataElement(terminalID => $terminalID);
+ $xml->endTag('endOfDay');
+ $xml->endTag('transRequest');
+ return $output;
+ },
+ row => sub {},
+);
+
+1;
+
diff --git a/FS/FS/pay_batch/td_canada_trust.pm b/FS/FS/pay_batch/td_canada_trust.pm
new file mode 100644
index 0000000..43b9237
--- /dev/null
+++ b/FS/FS/pay_batch/td_canada_trust.pm
@@ -0,0 +1,104 @@
+package FS::pay_batch::td_canada_trust;
+
+# Formerly known as csv-td_canada_trust-merchant_pc_batch,
+# which I'm sure we can all agree is both a terrible name
+# and an illegal Perl identifier.
+
+use strict;
+use vars qw(@ISA %import_info %export_info $name);
+use Time::Local 'timelocal';
+use FS::Conf;
+
+my $conf;
+my ($origid, $datacenter, $typecode, $shortname, $longname, $mybank, $myacct);
+
+$name = 'csv-td_canada_trust-merchant_pc_batch';
+
+%import_info = (
+ 'filetype' => 'CSV',
+ 'fields' => [
+ 'paybatchnum',
+ 'paid',
+ '', # card type
+ '_date',
+ 'time',
+ 'payinfo',
+ '', # expiry date
+ '', # auth number
+ 'type', # transaction type
+ 'result', # processing result
+ '', # terminal ID
+ ],
+ 'hook' => sub {
+ my $hash = shift;
+ my $date = $hash->{'_date'};
+ my $time = $hash->{'time'};
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100);
+ $hash->{'_date'} = timelocal( substr($time, 4, 2),
+ substr($time, 2, 2),
+ substr($time, 0, 2),
+ substr($date, 6, 2),
+ substr($date, 4, 2)-1,
+ substr($date, 0, 4)-1900 );
+ },
+ 'approved' => sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && $hash->{'result'} == 3
+ },
+ 'declined' => sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && ( $hash->{'result'} == 4
+ || $hash->{'result'} == 5 )
+ },
+ '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;
+ },
+);
+
+%export_info = (
+ init => sub {
+ $conf = shift;
+ },
+ # no header
+ row => sub {
+ my ($cust_pay_batch, $pay_batch) = @_;
+
+ return join(',',
+ '',
+ '',
+ '',
+ '',
+ $cust_pay_batch->payinfo,
+ expdate($cust_pay_batch->exp),
+ $cust_pay_batch->amount,
+ $cust_pay_batch->paybatchnum
+ );
+ },
+# no footer
+);
+
+sub expdate {
+ my $exp = shift;
+ $exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my ($mon, $y) = ($2, $1);
+ if($conf->exists('batch-increment_expiration')) {
+ my ($curmon, $curyear) = (localtime(time))[4,5];
+ $curmon++;
+ $curyear -= 100;
+ $y++ while $y < $curyear || ($y == $curyear && $mon < $curmon);
+ }
+ $mon = "0$mon" if $mon =~ /^\d$/;
+ $y = "0$y" if $y =~ /^\d$/;
+ return "$mon$y";
+}
+
+1;
+
diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm
new file mode 100644
index 0000000..30a03dd
--- /dev/null
+++ b/FS/FS/payby.pm
@@ -0,0 +1,209 @@
+package FS::payby;
+
+use strict;
+use vars qw(%hash %payby2bop);
+use Tie::IxHash;
+use Business::CreditCard;
+
+
+=head1 NAME
+
+FS::payby - Object methods for payment type records
+
+=head1 SYNOPSIS
+
+ use FS::payby;
+
+ #for now...
+
+ my @payby = FS::payby->payby;
+
+ my $bool = FS::payby->can_payby('cust_main', 'CARD');
+
+ tie my %payby, 'Tie::IxHash', FS::payby->payby2longname
+
+ my @cust_payby = FS::payby->cust_payby;
+
+ tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname
+
+=head1 DESCRIPTION
+
+Payment types.
+
+=head1 METHODS
+
+=over 4
+
+=item
+
+=cut
+
+# paybys can be any/all of:
+# - a customer payment type (cust_main.payby)
+# - a payment or refund type (cust_pay.payby, cust_pay_batch.payby, cust_refund.payby)
+# - an event type (part_bill_event.payby)
+
+tie %hash, 'Tie::IxHash',
+ 'CARD' => {
+ tinyname => 'card',
+ shortname => 'Credit card',
+ longname => 'Credit card (automatic)',
+ realtime => 1,
+ },
+ 'DCRD' => {
+ tinyname => 'card',
+ shortname => 'Credit card',
+ longname => 'Credit card (on-demand)',
+ cust_pay => 'CARD', #this is a customer type only, payments are CARD...
+ realtime => 1,
+ },
+ 'CHEK' => {
+ tinyname => 'check',
+ shortname => 'Electronic check',
+ longname => 'Electronic check (automatic)',
+ realtime => 1,
+ },
+ 'DCHK' => {
+ tinyname => 'check',
+ shortname => 'Electronic check',
+ longname => 'Electronic check (on-demand)',
+ cust_pay => 'CHEK', #this is a customer type only, payments are CHEK...
+ realtime => 1,
+ },
+ 'LECB' => {
+ tinyname => 'phone bill',
+ shortname => 'Phone bill billing',
+ longname => 'Phone bill billing',
+ realtime => 1,
+ },
+ 'BILL' => {
+ tinyname => 'billing',
+ shortname => 'Billing',
+ payname => 'Check',
+ longname => 'Billing',
+ },
+ 'PREP' => {
+ tinyname => 'prepaid card',
+ shortname => 'Prepaid card',
+ longname => 'Prepaid card',
+ cust_main => 'BILL', #this is a payment type only, customers go to BILL...
+ },
+ 'CASH' => {
+ tinyname => 'cash',
+ shortname => 'Cash', # initial payment, then billing
+ longname => 'Cash',
+ cust_main => 'BILL', #this is a payment type only, customers go to BILL...
+ },
+ 'WEST' => {
+ tinyname => 'western union',
+ shortname => 'Western Union', # initial payment, then billing
+ longname => 'Western Union',
+ cust_main => 'BILL', #this is a payment type only, customers go to BILL...
+ },
+ 'MCRD' => { #not the same as DCRD
+ tinyname => 'card',
+ shortname => 'Manual credit card', # initial payment, then billing
+ longname => 'Manual credit card',
+ cust_main => 'BILL', #this is a payment type only, customers go to BILL...
+ },
+ 'COMP' => {
+ tinyname => 'comp',
+ shortname => 'Complimentary',
+ longname => 'Complimentary',
+ cust_pay => '', # (free) is depricated as a payment type in cust_pay
+ },
+ 'CBAK' => {
+ tinyname => 'chargeback',
+ shortname => 'Chargeback',
+ longname => 'Chargeback',
+ cust_main => '', # not a customer type
+ },
+;
+
+sub payby {
+ keys %hash;
+}
+
+sub can_payby {
+ my( $self, $table, $payby ) = @_;
+
+ #return "Illegal payby" unless $hash{$payby};
+ return 0 unless $hash{$payby};
+
+ $table = 'cust_pay' if $table =~ /^cust_(pay_pending|pay_batch|pay_void|refund)$/;
+ return 0 if exists( $hash{$payby}->{$table} );
+
+ return 1;
+}
+
+sub realtime { # can use realtime payment facilities
+ my( $self, $payby ) = @_;
+
+ return 0 unless $hash{$payby};
+ return 0 unless exists( $hash{$payby}->{realtime} );
+
+ return $hash{$payby}->{realtime};
+}
+
+sub payby2longname {
+ my $self = shift;
+ map { $_ => $hash{$_}->{longname} } $self->payby;
+}
+
+sub shortname {
+ my( $self, $payby ) = @_;
+ $hash{$payby}->{shortname};
+}
+
+sub payname {
+ my( $self, $payby ) = @_;
+ #$hash{$payby}->{payname} || $hash{$payby}->{shortname};
+ exists($hash{$payby}->{payname})
+ ? $hash{$payby}->{payname}
+ : $hash{$payby}->{shortname};
+}
+
+sub longname {
+ my( $self, $payby ) = @_;
+ $hash{$payby}->{longname};
+}
+
+%payby2bop = (
+ 'CARD' => 'CC',
+ 'CHEK' => 'ECHECK',
+ 'MCRD' => 'CC',
+);
+
+sub payby2bop {
+ my( $self, $payby ) = @_;
+ $payby2bop{ $self->payby2payment($payby) };
+}
+
+sub payby2payment {
+ my( $self, $payby ) = @_;
+ $hash{$payby}{'cust_pay'} || $payby;
+}
+
+sub cust_payby {
+ my $self = shift;
+ grep { ! exists $hash{$_}->{cust_main} } $self->payby;
+}
+
+sub cust_payby2longname {
+ my $self = shift;
+ map { $_ => $hash{$_}->{longname} } $self->cust_payby;
+}
+
+=back
+
+=head1 BUGS
+
+This should eventually be an actual database table, and all tables that
+currently have a char payby field should have a foreign key into here instead.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
new file mode 100644
index 0000000..99cca6a
--- /dev/null
+++ b/FS/FS/payinfo_Mixin.pm
@@ -0,0 +1,290 @@
+package FS::payinfo_Mixin;
+
+use strict;
+use Business::CreditCard;
+use FS::payby;
+
+=head1 NAME
+
+FS::payinfo_Mixin - Mixin class for records in tables that contain payinfo.
+
+=head1 SYNOPSIS
+
+package FS::some_table;
+use vars qw(@ISA);
+@ISA = qw( FS::payinfo_Mixin FS::Record );
+
+=head1 DESCRIPTION
+
+This is a mixin class for records that contain payinfo.
+
+This class handles the following functions for payinfo...
+
+Payment Mask (Generation and Storage)
+Data Validation (parent checks need to be sure to call this)
+Pretty printing
+
+=head1 FIELDS
+
+=over 4
+
+=item payby
+
+The following payment types (payby) are supported:
+
+For Customers (cust_main):
+'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
+'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
+'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or
+'PREPAY' (special billing type: applies a credit and sets billing type to I<BILL> - see L<FS::prepay_credit>)
+
+For Refunds (cust_refund):
+'CARD' (credit cards), 'CHEK' (electronic check/ACH),
+'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
+'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' Chargeback, or 'COMP' (free)
+
+
+For Payments (cust_pay):
+'CARD' (credit cards), 'CHEK' (electronic check/ACH),
+'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
+'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
+'COMP' (free) is depricated as a payment type in cust_pay
+
+=cut
+
+# was this supposed to do something?
+
+#sub payby {
+# my($self,$payby) = @_;
+# if ( defined($payby) ) {
+# $self->setfield('payby', $payby);
+# }
+# return $self->getfield('payby')
+#}
+
+=item payinfo
+
+Payment information (payinfo) can be one of the following types:
+
+Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
+
+=cut
+
+sub payinfo {
+ my($self,$payinfo) = @_;
+ if ( defined($payinfo) ) {
+ $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter'
+ $self->paymask($self->mask_payinfo());
+ } else {
+ $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter'
+ return $payinfo;
+ }
+}
+
+=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
+
+=cut
+
+sub paycvv {
+ my($self,$paycvv) = @_;
+ # This is only allowed in cust_main... Even then it really shouldn't be stored...
+ if ($self->table eq 'cust_main') {
+ if ( defined($paycvv) ) {
+ $self->setfield('paycvv', $paycvv); # This is okay since we are the 'setter'
+ } else {
+ $paycvv = $self->getfield('paycvv'); # This is okay since we are the 'getter'
+ return $paycvv;
+ }
+ } else {
+# warn "This doesn't work for other tables besides cust_main
+ '';
+ }
+}
+
+=item paymask
+
+=cut
+
+sub paymask {
+ my($self, $paymask) = @_;
+
+ if ( defined($paymask) && $paymask ne '' ) {
+ # I hate this little bit of magic... I don't expect it to cause a problem,
+ # but who knows... If the payinfo is passed in masked then ignore it and
+ # set it based on the payinfo. The only guy that should call this in this
+ # way is... $self->payinfo
+ $self->setfield('paymask', $self->mask_payinfo());
+
+ } else {
+
+ $paymask=$self->getfield('paymask');
+ if (!defined($paymask) || $paymask eq '') {
+ # Generate it if it's blank - Note that we're not going to set it - just
+ # generate
+ $paymask = $self->mask_payinfo();
+ }
+
+ }
+
+ return $paymask;
+}
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item mask_payinfo [ PAYBY, PAYINFO ]
+
+This method converts the payment info (credit card, bank account, etc.) into a
+masked string.
+
+Optionally, an arbitrary payby and payinfo can be passed.
+
+=cut
+
+sub mask_payinfo {
+ my $self = shift;
+ my $payby = scalar(@_) ? shift : $self->payby;
+ my $payinfo = scalar(@_) ? shift : $self->payinfo;
+
+ # Check to see if it's encrypted...
+ my $paymask;
+ if ( $self->is_encrypted($payinfo) ) {
+ $paymask = 'N/A';
+ } else {
+ # if not, mask it...
+ if ($payby eq 'CARD' || $payby eq 'DCRD' || $payby eq 'MCRD') {
+ # Credit Cards
+ my $conf = new FS::Conf;
+ my $mask_method = $conf->config('card_masking_method') || 'first6last4';
+ $mask_method =~ /^first(\d+)last(\d+)$/
+ or die "can't parse card_masking_method $mask_method";
+ my($first, $last) = ($1, $2);
+
+ $paymask = substr($payinfo,0,$first).
+ 'x'x(length($payinfo)-$first-$last).
+ substr($payinfo,(length($payinfo)-$last));
+ } elsif ($payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ # Checks (Show last 2 @ bank)
+ my( $account, $aba ) = split('@', $payinfo );
+ $paymask = 'x'x(length($account)-2).
+ substr($account,(length($account)-2))."@".$aba;
+ } else { # Tie up loose ends
+ $paymask = $payinfo;
+ }
+ }
+ return $paymask;
+}
+
+=item payinfo_check
+
+Checks payby and payinfo.
+
+For Customers (cust_main):
+'CARD' (credit card - automatic), 'DCRD' (credit card - on-demand),
+'CHEK' (electronic check - automatic), 'DCHK' (electronic check - on-demand),
+'LECB' (Phone bill billing), 'BILL' (billing), 'COMP' (free), or
+'PREPAY' (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>)
+
+For Refunds (cust_refund):
+'CARD' (credit cards), 'CHEK' (electronic check/ACH),
+'LECB' (Phone bill billing), 'BILL' (billing), 'CASH' (cash),
+'WEST' (Western Union), 'MCRD' (Manual credit card), 'CBAK' (Chargeback), or 'COMP' (free)
+
+For Payments (cust_pay):
+'CARD' (credit cards), 'CHEK' (electronic check/ACH),
+'LECB' (phone bill billing), 'BILL' (billing), 'PREP' (prepaid card),
+'CASH' (cash), 'WEST' (Western Union), or 'MCRD' (Manual credit card)
+'COMP' (free) is depricated as a payment type in cust_pay
+
+=cut
+
+sub payinfo_check {
+ my $self = shift;
+
+ FS::payby->can_payby($self->table, $self->payby)
+ or return "Illegal payby: ". $self->payby;
+
+ if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
+ 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 {
+ if ( $self->is_encrypted($self->payinfo) ) {
+ #something better? all it would cause is a decryption error anyway?
+ my $error = $self->ut_anything('payinfo');
+ return $error if $error;
+ } else {
+ my $error = $self->ut_textn('payinfo');
+ return $error if $error;
+ }
+ }
+
+ '';
+
+}
+
+=item payby_payinfo_pretty
+
+Returns payment method and information (suitably masked, if applicable) as
+a human-readable string, such as:
+
+ Card #54xxxxxxxxxxxx32
+
+or
+
+ Check #119006
+
+=cut
+
+sub payby_payinfo_pretty {
+ my $self = shift;
+ if ( $self->payby eq 'CARD' ) {
+ 'Card #'. $self->paymask;
+ } elsif ( $self->payby eq 'CHEK' ) {
+ 'E-check acct#'. $self->payinfo;
+ } elsif ( $self->payby eq 'BILL' ) {
+ 'Check #'. $self->payinfo;
+ } elsif ( $self->payby eq 'PREP' ) {
+ 'Prepaid card #'. $self->payinfo;
+ } elsif ( $self->payby eq 'CASH' ) {
+ 'Cash '. $self->payinfo;
+ } elsif ( $self->payby eq 'WEST' ) {
+ 'Western Union'; #. $self->payinfo;
+ } elsif ( $self->payby eq 'MCRD' ) {
+ 'Manual credit card'; #. $self->payinfo;
+ } else {
+ $self->payby. ' '. $self->payinfo;
+ }
+}
+
+=back
+
+=head1 BUGS
+
+Future items?
+ Encryption - In the Future (Pull from Record.pm)
+ Bad Card Stuff - In the Future (Integrate Banned Pay)
+ Currency - In the Future
+
+=head1 SEE ALSO
+
+L<FS::payby>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/payinfo_transaction_Mixin.pm b/FS/FS/payinfo_transaction_Mixin.pm
new file mode 100644
index 0000000..19419de
--- /dev/null
+++ b/FS/FS/payinfo_transaction_Mixin.pm
@@ -0,0 +1,123 @@
+package FS::payinfo_transaction_Mixin;
+
+use strict;
+use vars qw( @ISA );
+use FS::payby;
+use FS::payinfo_Mixin;
+use FS::Record qw(qsearchs);
+use FS::cust_main;
+use FS::payment_gateway;
+
+@ISA = qw( FS::payinfo_Mixin );
+
+=head1 NAME
+
+FS::payinfo_transaction_Mixin - Mixin class for records in tables that represent transactions.
+
+=head1 SYNOPSIS
+
+package FS::some_table;
+use vars qw(@ISA);
+@ISA = qw( FS::payinfo_transaction_Mixin FS::Record );
+
+=head1 DESCRIPTION
+
+This is a mixin class for records that represent transactions: that contain
+payinfo and paybatch. Currently FS::cust_pay and FS::cust_refund
+
+=head1 METHODS
+
+=over 4
+
+=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 payby_name
+
+Returns a name for the payby field.
+
+=cut
+
+sub payby_name {
+ my $self = shift;
+ if ( $self->payby eq 'BILL' ) { #kludge
+ 'Check';
+ } else {
+ FS::payby->shortname( $self->payby );
+ }
+}
+
+=item gatewaynum
+
+Returns a gatewaynum for the processing gateway.
+
+=item processor
+
+Returns a name for the processing gateway.
+
+=item authorization
+
+Returns a name for the processing gateway.
+
+=item order_number
+
+Returns a name for the processing gateway.
+
+=cut
+
+sub gatewaynum { shift->_parse_paybatch->{'gatewaynum'}; }
+sub processor { shift->_parse_paybatch->{'processor'}; }
+sub authorization { shift->_parse_paybatch->{'authorization'}; }
+sub order_number { shift->_parse_paybatch->{'order_number'}; }
+
+#sucks that this stuff is in paybatch like this in the first place,
+#but at least other code can start to use new field names
+#(code nicked from FS::cust_main::realtime_refund_bop)
+sub _parse_paybatch {
+ my $self = shift;
+
+ $self->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
+ or return {};
+ #"Can't parse paybatch for paynum $options{'paynum'}: ".
+ # $cust_pay->paybatch;
+
+ my( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
+
+ if ( $gatewaynum ) { #gateway for the payment to be refunded
+
+ my $payment_gateway =
+ qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
+
+ die "payment gateway $gatewaynum not found" #?
+ unless $payment_gateway;
+
+ $processor = $payment_gateway->gateway_module;
+
+ }
+
+ {
+ 'gatewaynum' => $gatewaynum,
+ 'processor' => $processor,
+ 'authorization' => $auth,
+ 'order_number' => $order_number,
+ };
+
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::payinfo_Mixin>
+
+=cut
+
+1;
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
new file mode 100644
index 0000000..bc8b875
--- /dev/null
+++ b/FS/FS/payment_gateway.pm
@@ -0,0 +1,247 @@
+package FS::payment_gateway;
+
+use strict;
+use vars qw( @ISA $me $DEBUG );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::option_Common;
+use FS::agent_payment_gateway;
+
+@ISA = qw( FS::option_Common );
+$me = '[ FS::payment_gateway ]';
+$DEBUG=0;
+
+=head1 NAME
+
+FS::payment_gateway - Object methods for payment_gateway records
+
+=head1 SYNOPSIS
+
+ use FS::payment_gateway;
+
+ $record = new FS::payment_gateway \%hash;
+ $record = new FS::payment_gateway { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::payment_gateway object represents an payment gateway.
+FS::payment_gateway inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item gatewaynum - primary key
+
+=item gateway_namespace - Business::OnlinePayment or Business::OnlineThirdPartyPayment
+
+=item gateway_module - Business::OnlinePayment:: module name
+
+=item gateway_username - payment gateway username
+
+=item gateway_password - payment gateway password
+
+=item gateway_action - optional action or actions (multiple actions are separated with `,': for example: `Authorization Only, Post Authorization'). Defaults to `Normal Authorization'.
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new payment gateway. To add the payment gateway 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 { 'payment_gateway'; }
+
+=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 payment gateway. 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('gatewaynum')
+ || $self->ut_alpha('gateway_module')
+ || $self->ut_enum('gateway_namespace', ['Business::OnlinePayment',
+ 'Business::OnlineThirdPartyPayment',
+ ] )
+ || $self->ut_textn('gateway_username')
+ || $self->ut_anything('gateway_password')
+ || $self->ut_textn('gateway_callback_url') # a bit too permissive
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ #|| $self->ut_textn('gateway_action')
+ ;
+ return $error if $error;
+
+ if ( $self->gateway_action ) {
+ my @actions = split(/,\s*/, $self->gateway_action);
+ $self->gateway_action(
+ join( ',', map { /^(Normal Authorization|Authorization Only|Credit|Post Authorization)$/
+ or return "Unknown action $_";
+ $1
+ }
+ @actions
+ )
+ );
+ } else {
+ $self->gateway_action('Normal Authorization');
+ }
+
+ # this little kludge mimics FS::CGI::popurl
+ $self->gateway_callback_url($self->gateway_callback_url. '/')
+ if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ );
+
+ $self->SUPER::check;
+}
+
+=item agent_payment_gateway
+
+Returns any agent overrides for this payment gateway.
+
+=cut
+
+sub agent_payment_gateway {
+ my $self = shift;
+ qsearch('agent_payment_gateway', { 'gatewaynum' => $self->gatewaynum } );
+}
+
+=item disable
+
+Disables this payment gateway: deletes all associated agent_payment_gateway
+overrides and sets the I<disabled> field to "B<Y>".
+
+=cut
+
+sub disable {
+ 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 $agent_payment_gateway ( $self->agent_payment_gateway ) {
+ my $error = $agent_payment_gateway->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error deleting agent_payment_gateway override: $error";
+ }
+ }
+
+ $self->disabled('Y');
+ my $error = $self->replace();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error disabling payment_gateway: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item namespace_description
+
+returns a friendly name for the namespace
+
+=cut
+
+my %namespace2description = (
+ '' => 'Direct',
+ 'Business::OnlinePayment' => 'Direct',
+ 'Business::OnlineThirdPartyPayment' => 'Hosted',
+);
+
+sub namespace_description {
+ $namespace2description{shift->gateway_namespace} || 'Unknown';
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data {
+ my ($class, %opts) = @_;
+ my $dbh = dbh;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ foreach ( qsearch( 'payment_gateway', { 'gateway_namespace' => '' } ) ) {
+ $_->gateway_namespace('Business::OnlinePayment'); #defaulting
+ my $error = $_->replace;
+ die "$class had error during upgrade replacement: $error" if $error;
+ }
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/payment_gateway_option.pm b/FS/FS/payment_gateway_option.pm
new file mode 100644
index 0000000..0576022
--- /dev/null
+++ b/FS/FS/payment_gateway_option.pm
@@ -0,0 +1,126 @@
+package FS::payment_gateway_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::payment_gateway_option - Object methods for payment_gateway_option records
+
+=head1 SYNOPSIS
+
+ use FS::payment_gateway_option;
+
+ $record = new FS::payment_gateway_option \%hash;
+ $record = new FS::payment_gateway_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::payment_gateway_option object represents an option key and value for
+a payment gateway. FS::payment_gateway_option inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item gatewaynum -
+
+=item optionname -
+
+=item optionvalue -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new option. To add the 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 { 'payment_gateway_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 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_foreign_key('gatewaynum', 'payment_gateway', 'gatewaynum')
+ || $self->ut_text('optionname')
+ || $self->ut_textn('optionvalue')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/phone_avail.pm b/FS/FS/phone_avail.pm
new file mode 100644
index 0000000..55b44ec
--- /dev/null
+++ b/FS/FS/phone_avail.pm
@@ -0,0 +1,186 @@
+package FS::phone_avail;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::phone_avail - Phone number availability cache
+
+=head1 SYNOPSIS
+
+ use FS::phone_avail;
+
+ $record = new FS::phone_avail \%hash;
+ $record = new FS::phone_avail { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::phone_avail object represents availability of phone service.
+FS::phone_avail inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item availnum
+
+primary key
+
+=item exportnum
+
+exportnum
+
+=item countrycode
+
+countrycode
+
+=item state
+
+state
+
+=item npa
+
+npa
+
+=item nxx
+
+nxx
+
+=item station
+
+station
+
+=item name
+
+Optional name
+
+=item svcnum
+
+svcnum
+
+=item availbatch
+
+availbatch
+
+=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 { 'phone_avail'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('availnum')
+ || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum' )
+ || $self->ut_number('countrycode')
+ || $self->ut_alphan('state')
+ || $self->ut_number('npa')
+ || $self->ut_numbern('nxx')
+ || $self->ut_numbern('station')
+ || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' )
+ || $self->ut_textn('availbatch')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $numsub = sub {
+ my( $phone_avail, $value ) = @_;
+ $value =~ s/\D//g;
+ $value =~ /^(\d{3})(\d{3})(\d+)$/ or die "unparsable number $value\n";
+ #( $hash->{npa}, $hash->{nxx}, $hash->{station} ) = ( $1, $2, $3 );
+ $phone_avail->npa($1);
+ $phone_avail->nxx($2);
+ $phone_avail->station($3);
+ };
+
+ my $opt = { 'table' => 'phone_avail',
+ 'params' => [ 'availbatch', 'exportnum', 'countrycode' ],
+ 'formats' => { 'default' => [ 'state', $numsub ] },
+ };
+
+ FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
+=back
+
+=head1 BUGS
+
+Sparse documentation.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/phone_device.pm b/FS/FS/phone_device.pm
new file mode 100644
index 0000000..ba765e0
--- /dev/null
+++ b/FS/FS/phone_device.pm
@@ -0,0 +1,299 @@
+package FS::phone_device;
+
+use strict;
+use base qw( FS::Record );
+use Scalar::Util qw( blessed );
+use FS::Record qw( dbh qsearchs ); # qsearch );
+use FS::part_device;
+use FS::svc_phone;
+
+=head1 NAME
+
+FS::phone_device - Object methods for phone_device records
+
+=head1 SYNOPSIS
+
+ use FS::phone_device;
+
+ $record = new FS::phone_device \%hash;
+ $record = new FS::phone_device { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::phone_device object represents a specific customer phone device, such as
+a SIP phone or ATA. FS::phone_device inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item devicenum
+
+primary key
+
+=item devicepart
+
+devicepart
+
+=item svcnum
+
+svcnum
+
+=item mac_addr
+
+mac_addr
+
+
+=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 { 'phone_device'; }
+
+=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;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->export('device_insert');
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+
+=item delete
+
+Delete this record from the database.
+
+=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;
+
+ $self->export('device_delete');
+
+ 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 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 = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ 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->export('device_replace', $old);
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=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;
+
+ my $mac = $self->mac_addr;
+ $mac =~ s/\s+//g;
+ $mac =~ s/://g;
+ $self->mac_addr($mac);
+
+ my $error =
+ $self->ut_numbern('devicenum')
+ || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
+ || $self->ut_foreign_key('svcnum', 'svc_phone', 'svcnum' ) #cust_svc?
+ || $self->ut_hexn('mac_addr')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item part_device
+
+Returns the device type record (see L<FS::part_device>) associated with this
+customer device.
+
+=cut
+
+sub part_device {
+ my $self = shift;
+ qsearchs( 'part_device', { 'devicepart' => $self->devicepart } );
+}
+
+=item svc_phone
+
+Returns the phone number (see L<FS::svc_phone>) associated with this customer
+device.
+
+=cut
+
+sub svc_phone {
+ my $self = shift;
+ qsearchs( 'svc_phone', { 'svcnum' => $self->svcnum } );
+}
+
+=item export HOOK [ EXPORT_ARGS ]
+
+Runs the provided export hook (i.e. "device_insert") for this service.
+
+=cut
+
+sub export {
+ my( $self, $method ) = ( 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 $svc_phone = $self->svc_phone;
+ my $error = $svc_phone->export($method, $self, @_); #call device export
+ if ( $error ) { #netsapiens at least
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting $method event to svc_phone ". $svc_phone->svcnum.
+ " (transaction rolled back): $error";
+ }
+
+ $method = "export_$method" unless $method =~ /^export_/;
+
+ foreach my $part_export ( $self->part_device->part_export ) {
+ next unless $part_export->can($method);
+ my $error = $part_export->$method($svc_phone, $self, @_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting $method event to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item export_links
+
+Returns a list of html elements associated with this device's exports.
+
+=cut
+
+sub export_links {
+ my $self = shift;
+ my $return = [];
+ $self->export('export_device_links', $return);
+ $return;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/phone_type.pm b/FS/FS/phone_type.pm
new file mode 100644
index 0000000..d2ef465
--- /dev/null
+++ b/FS/FS/phone_type.pm
@@ -0,0 +1,137 @@
+package FS::phone_type;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch ); # qsearchs );
+
+=head1 NAME
+
+FS::phone_type - Object methods for phone_type records
+
+=head1 SYNOPSIS
+
+ use FS::phone_type;
+
+ $record = new FS::phone_type \%hash;
+ $record = new FS::phone_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::phone_type object represents an phone number type (for example: Work,
+Home, Mobile, Fax). FS::phone_type inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item phonetypenum
+
+Primary key
+
+=item typename
+
+Type name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new type. To add the type 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 { 'phone_type'; }
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid 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;
+
+ my $error =
+ $self->ut_numbern('phonetypenum')
+ || $self->ut_number('weight')
+ || $self->ut_text('typename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+# Used by FS::Setup to initialize a new database.
+sub _populate_initial_data {
+ my ($class, %opts) = @_;
+
+ my $weight = 10;
+
+ foreach ("Work", "Home", "Mobile", "Fax") {
+ my $object = $class->new({ 'typename' => $_,
+ 'weight' => $weight,
+ });
+ my $error = $object->insert;
+ die "error inserting $class into database: $error\n"
+ if $error;
+
+ $weight += 10;
+ }
+
+ '';
+
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data {
+ my $class = shift;
+
+ return $class->_populate_initial_data(@_)
+ unless scalar( qsearch( 'phone_type', {} ) );
+
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::contact_phone>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/pkg_category.pm b/FS/FS/pkg_category.pm
new file mode 100644
index 0000000..cd875d1
--- /dev/null
+++ b/FS/FS/pkg_category.pm
@@ -0,0 +1,132 @@
+package FS::pkg_category;
+
+use strict;
+use base qw( FS::category_Common );
+use vars qw( @ISA $me $DEBUG );
+use FS::Record qw( qsearch dbh );
+use FS::pkg_class;
+use FS::part_pkg;
+
+$DEBUG = 0;
+$me = '[FS::pkg_category]';
+
+=head1 NAME
+
+FS::pkg_category - Object methods for pkg_category records
+
+=head1 SYNOPSIS
+
+ use FS::pkg_category;
+
+ $record = new FS::pkg_category \%hash;
+ $record = new FS::pkg_category { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pkg_category object represents an package category. Every package class
+(see L<FS::pkg_class>) has, optionally, a package category. FS::pkg_category
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item categorynum
+
+primary key (assigned automatically for new package categoryes)
+
+=item categoryname
+
+Text name of this package category
+
+=item weight
+
+Weight
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new package category. To add the package category to the database,
+see L<"insert">.
+
+=cut
+
+sub table { 'pkg_category'; }
+
+=item insert
+
+Adds this package category to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Deletes this package category from the database. Only package categoryes with
+no associated package definitions can be deleted. 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 package category. If there is an
+error, returns the error, otherwise returns false. Called by the insert and
+replace methods.
+
+=cut
+
+# _ upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data {
+ my ($class, %opts) = @_;
+ my $dbh = dbh;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ my @pkg_category =
+ qsearch('pkg_category', { weight => { op => '!=', value => '' } } );
+
+ unless( scalar(@pkg_category) ) {
+ my @pkg_category = qsearch('pkg_category', {} );
+ my $weight = 0;
+ foreach ( sort { $a->description cmp $b->description } @pkg_category ) {
+ $_->weight($weight);
+ my $error = $_->replace;
+ die "error setting pkg_category weight: $error\n" if $error;
+ $weight += 10;
+ }
+ }
+ '';
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::category_Common>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/pkg_class.pm b/FS/FS/pkg_class.pm
new file mode 100644
index 0000000..51d0455
--- /dev/null
+++ b/FS/FS/pkg_class.pm
@@ -0,0 +1,119 @@
+package FS::pkg_class;
+
+use strict;
+use FS::class_Common;
+use base qw( FS::class_Common );
+use FS::part_pkg;
+use FS::pkg_category;
+
+=head1 NAME
+
+FS::pkg_class - Object methods for pkg_class records
+
+=head1 SYNOPSIS
+
+ use FS::pkg_class;
+
+ $record = new FS::pkg_class \%hash;
+ $record = new FS::pkg_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pkg_class object represents an package class. Every package definition
+(see L<FS::part_pkg>) has, optionally, a package class. FS::pkg_class inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+primary key (assigned automatically for new package classes)
+
+=item classname
+
+Text name of this package class
+
+=item categorynum
+
+Number of associated pkg_category (see L<FS::pkg_category>)
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new package class. To add the package class to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'pkg_class'; }
+sub _target_table { 'part_pkg'; }
+
+=item insert
+
+Adds this package class to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Deletes this package class from the database. Only package classes with no
+associated package definitions can be deleted. 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 package class. If there is an
+error, returns the error, otherwise returns false. Called by the insert and
+replace methods.
+
+=item pkg_category
+
+=item category
+
+Returns the pkg_category record associated with this class, or false if there
+is none.
+
+=cut
+
+sub pkg_category {
+ my $self = shift;
+ $self->category;
+}
+
+=item categoryname
+
+Returns the category name associated with this class, or false if there
+is none.
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_pkg>, L<FS::Record>
+
+=cut
+
+1;
diff --git a/FS/FS/pkg_referral.pm b/FS/FS/pkg_referral.pm
new file mode 100644
index 0000000..333c2bf
--- /dev/null
+++ b/FS/FS/pkg_referral.pm
@@ -0,0 +1,126 @@
+package FS::pkg_referral;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::pkg_referral - Object methods for pkg_referral records
+
+=head1 SYNOPSIS
+
+ use FS::pkg_referral;
+
+ $record = new FS::pkg_referral \%hash;
+ $record = new FS::pkg_referral { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pkg_referral object represents the association of an advertising source
+with a specific customer package (purchase). FS::pkg_referral inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgrefnum - primary key
+
+=item pkgnum - Customer package. See L<FS::cust_pkg>
+
+=item refnum - Advertising source. See L<FS::part_referral>
+
+=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 { 'pkg_referral'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('pkgrefnum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
+ || $self->ut_foreign_key('refnum', 'part_referral', 'refnum' )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Multiple pkg_referral records for a single package (configured off by default)
+still seems weird.
+
+=head1 SEE ALSO
+
+L<FS::part_referral>, L<FS::cust_pkg>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm
new file mode 100644
index 0000000..9f3a4a1
--- /dev/null
+++ b/FS/FS/pkg_svc.pm
@@ -0,0 +1,160 @@
+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 pkgsvcnum - primary key
+
+=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 );
+
+ $old = $new->replace_old unless defined($old);
+
+ 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_numbern('pkgsvcnum')
+ || $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..c26ca85
--- /dev/null
+++ b/FS/FS/port.pm
@@ -0,0 +1,154 @@
+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 port 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 port. 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 BUGS
+
+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..e5773ae
--- /dev/null
+++ b/FS/FS/prepay_credit.pm
@@ -0,0 +1,203 @@
+package FS::prepay_credit;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(qsearchs dbh);
+use FS::agent;
+
+@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::prepay_credit object represents a pre-paid 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>)
+
+=item agentnum - optional agent (see L<FS::agent>) for this prepaid card
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new pre-paid credit. To add the pre-paid credit 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->ut_numbern('seconds')
+ || $self->ut_numbern('upbytes')
+ || $self->ut_numbern('downbytes')
+ || $self->ut_numbern('totalbytes')
+ || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
+ || $self->SUPER::check
+ ;
+
+}
+
+=item agent
+
+Returns the agent (see L<FS::agent>) for this prepaid card, if any.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item generate NUM TYPE LENGTH HASHREF
+
+Generates the specified number of prepaid cards. Returns an array reference of
+the newly generated card identifiers, or a scalar error message.
+
+=cut
+
+#false laziness w/agent::generate_reg_codes
+sub generate {
+ my( $num, $type, $length, $hashref ) = @_;
+
+ my @codeset = ();
+ push @codeset, ( 'A'..'Z' ) if $type =~ /alpha/;
+ push @codeset, ( '1'..'9' ) if $type =~ /numeric/;
+ $length ||= 8;
+
+ 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 $condup = 0; #don't retry forever
+
+ my @cards = ();
+ for ( 1 ... $num ) {
+
+ my $identifier = join('', map($codeset[int(rand $#codeset)], (1..$length) ) );
+
+ redo if qsearchs('prepay_credit',{identifier=>$identifier}) && $condup++<23;
+ $condup = 0;
+
+ my $prepay_credit = new FS::prepay_credit {
+ 'identifier' => $identifier,
+ %$hashref,
+ };
+ my $error = $prepay_credit->check || $prepay_credit->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "(inserting prepay_credit) $error";
+ }
+ push @cards, $prepay_credit->identifier;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ \@cards;
+
+}
+
+=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/prospect_main.pm b/FS/FS/prospect_main.pm
new file mode 100644
index 0000000..369029b
--- /dev/null
+++ b/FS/FS/prospect_main.pm
@@ -0,0 +1,292 @@
+package FS::prospect_main;
+
+use strict;
+use base qw( FS::o2m_Common FS::Record );
+use vars qw( $DEBUG );
+use Scalar::Util qw( blessed );
+use FS::Record qw( dbh qsearch ); #qsearchs );
+use FS::agent;
+use FS::cust_location;
+use FS::contact;
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::prospect_main - Object methods for prospect_main records
+
+=head1 SYNOPSIS
+
+ use FS::prospect_main;
+
+ $record = new FS::prospect_main \%hash;
+ $record = new FS::prospect_main { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::prospect_main object represents a prospect. FS::prospect_main inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item prospectnum
+
+primary key
+
+=item company
+
+company
+
+=item locationnum
+
+locationnum
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new prospect. To add the prospect 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 { 'prospect_main'; }
+
+=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 %options = @_;
+ warn "FS::prospect_main::insert called on $self with options ".
+ join(', ', map "$_=>$options{$_}", keys %options)
+ 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;
+
+ warn " inserting prospect_main record" if $DEBUG;
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'cust_location'} ) {
+ warn " inserting cust_location record" if $DEBUG;
+ my $cust_location = $options{'cust_location'};
+ $cust_location->prospectnum($self->prospectnum);
+ $error = $cust_location->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+#delete dangling locations?
+
+=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 = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my %options = @_;
+
+ warn "FS::prospect_main::replace called on $new to replace $old with options".
+ " ". join(', ', map "$_ => ". $options{$_}, keys %options)
+ 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;
+
+ warn " replacing prospect_main record" if $DEBUG;
+ my $error = $new->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'cust_location'} ) {
+ my $cust_location = $options{'cust_location'};
+ $cust_location->prospectnum($new->prospectnum);
+ my $method = $cust_location->locationnum ? 'replace' : 'insert';
+ warn " ${method}ing cust_location record" if $DEBUG;
+ $error = $cust_location->$method();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ } elsif ( exists($options{'cust_location'}) ) {
+ foreach my $cust_location (
+ qsearch('cust_location', { 'prospectnum' => $new->prospectnum } )
+ ) {
+ $error = $cust_location->delete();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item check
+
+Checks all fields to make sure this is a valid prospect. 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('prospectnum')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum' )
+ || $self->ut_text('company')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item contact
+
+Returns the contacts (see L<FS::contact>) associated with this prospect.
+
+=cut
+
+sub contact {
+ my $self = shift;
+ qsearch( 'contact', { 'prospectnum' => $self->prospectnum } );
+}
+
+=item search HASHREF
+
+(Class method)
+
+Returns a qsearch hash expression to search for the parameters specified in
+HASHREF. Valid parameters are:
+
+=over 4
+
+=item agentnum
+
+=back
+
+=cut
+
+sub search {
+ my( $class, $params ) = @_;
+
+ my @where = ();
+ my $orderby;
+
+ ##
+ # parse agent
+ ##
+
+ if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "prospect_main.agentnum = $1";
+ }
+
+ ##
+ # setup queries, subs, etc. for the search
+ ##
+
+ $orderby ||= 'ORDER BY prospectnum';
+
+ # here is the agent virtualization
+ push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $count_query = "SELECT COUNT(*) FROM prospect_main $extra_sql";
+
+ my $sql_query = {
+ 'table' => 'prospect_main',
+ #'select' => $select,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'count_query' => $count_query,
+ #'extra_headers' => \@extra_headers,
+ #'extra_fields' => \@extra_fields,
+ };
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+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..99e349c
--- /dev/null
+++ b/FS/FS/queue.pm
@@ -0,0 +1,512 @@
+package FS::queue;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $conf $jobnums);
+use Exporter;
+use MIME::Base64;
+use Storable qw( nfreeze thaw );
+use FS::UID qw(myconnect);
+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;
+use FS::CGI qw (rooturl);
+
+@ISA = qw(FS::Record);
+@EXPORT_OK = qw( joblisting );
+
+$DEBUG = 0;
+
+$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 (new, locked, or failed)
+
+=item statustext
+
+Freeform text status message
+
+=item _date
+
+UNIX timestamp
+
+=item svcnum
+
+Optional link to service (see L<FS::cust_svc>).
+
+=item custnum
+
+Optional link to customer (see L<FS::cust_main>).
+
+=item secure
+
+Secure flag, 'Y' indicates that when using encryption, the job needs to be
+run on a machine with the private key.
+
+=cut
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new job. To add the job 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, @args ) = @_;
+
+ 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 %args = ();
+ {
+ no warnings "misc";
+ %args = @args;
+ }
+
+ $self->custnum( $args{'custnum'} ) if $args{'custnum'};
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $arg ( @args ) {
+ my $freeze = ref($arg) ? 'Y' : '';
+ my $queue_arg = new FS::queue_arg ( {
+ 'jobnum' => $self->jobnum,
+ 'frozen' => $freeze,
+ 'arg' => $freeze ? encode_base64(nfreeze($arg)) : $arg,# always freeze?
+ } );
+ $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 $reportname = '';
+ if ( $self->status =~/^done/ ) {
+ my $dropstring = rooturl(). '/misc/queued_report\?report=';
+ if ($self->statustext =~ /.*$dropstring([.\w]+)\>/) {
+ $reportname = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/report.$1";
+ }
+ }
+
+ 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;
+
+ unlink $reportname if $reportname;
+
+ '';
+
+}
+
+=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 done )])
+ || $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 { $_->frozen ? thaw(decode_base64($_->arg)) : $_->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
+ }
+}
+
+=item update_statustext VALUE
+
+Updates the statustext value of this job to supplied value, in the database.
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+use vars qw($_update_statustext_dbh);
+sub update_statustext {
+ my( $self, $statustext ) = @_;
+ return '' if $statustext eq $self->statustext;
+ warn "updating statustext for $self to $statustext" if $DEBUG;
+
+ $_update_statustext_dbh ||= myconnect;
+
+ my $sth = $_update_statustext_dbh->prepare(
+ 'UPDATE queue set statustext = ? WHERE jobnum = ?'
+ ) or return $_update_statustext_dbh->errstr;
+
+ $sth->execute($statustext, $self->jobnum) or return $sth->errstr;
+ $_update_statustext_dbh->commit or die $_update_statustext_dbh->errstr;
+ $self->statustext($statustext);
+ '';
+
+ #my $new = new FS::queue { $self->hash };
+ #$new->statustext($statustext);
+ #my $error = $new->replace($self);
+ #return $error if $error;
+ #$self->statustext($statustext);
+ #'';
+}
+
+=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 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..8e9a10d
--- /dev/null
+++ b/FS/FS/queue_arg.pm
@@ -0,0 +1,120 @@
+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 frozen - argument is frozen with Storable
+
+=item arg - argument
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new argument. To add the argument 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_enum('frozen', [ '', 'Y' ])
+ || $self->ut_anything('arg')
+ ;
+ return $error if $error;
+
+ $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/queue_depend.pm b/FS/FS/queue_depend.pm
new file mode 100644
index 0000000..99a22c5
--- /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
+successfully (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..506b325
--- /dev/null
+++ b/FS/FS/raddb.pm
@@ -0,0 +1,1912 @@
+package FS::raddb;
+use vars qw(%attrib);
+
+%attrib = (
+ '3com_user_access_level' => '3Com-User-Access-Level',
+ '3gpp2_accounting_contain' => '3GPP2-Accounting-Container',
+ '3gpp2_acct_stop_trigger' => '3GPP2-Acct-Stop-Trigger',
+ '3gpp2_active_time' => '3GPP2-Active-Time',
+ '3gpp2_airlink_priority' => '3GPP2-Airlink-Priority',
+ '3gpp2_airlink_record_typ' => '3GPP2-Airlink-Record-Type',
+ '3gpp2_airlink_sequence_n' => '3GPP2-Airlink-Sequence-Number',
+ '3gpp2_allowed_diffserv_m' => '3GPP2-Allowed-Diffserv-Marking',
+ '3gpp2_allowed_persistent' => '3GPP2-Allowed-Persistent-TFTs',
+ '3gpp2_bad_ppp_frame_coun' => '3GPP2-Bad-PPP-Frame-Count',
+ '3gpp2_begin_session' => '3GPP2-Begin-Session',
+ '3gpp2_bsid' => '3GPP2-BSID',
+ '3gpp2_compulsory_tunnel_' => '3GPP2-Compulsory-Tunnel-Indicator',
+ '3gpp2_correlation_id' => '3GPP2-Correlation-Id',
+ '3gpp2_dcch_frame_size' => '3GPP2-DCCH-Frame-Size',
+ '3gpp2_diffserv_class_opt' => '3GPP2-Diffserv-Class-Option',
+ '3gpp2_disconnect_reason' => '3GPP2-Disconnect-Reason',
+ '3gpp2_dns_update_capabil' => '3GPP2-DNS-Update-Capability',
+ '3gpp2_dns_update_require' => '3GPP2-DNS-Update-Required',
+ '3gpp2_esn' => '3GPP2-ESN',
+ '3gpp2_fch_frame_size' => '3GPP2-FCH-Frame-Size',
+ '3gpp2_foreign_agent_addr' => '3GPP2-Foreign-Agent-Address',
+ '3gpp2_forward_dcch_mux_o' => '3GPP2-Forward-DCCH-Mux-Option',
+ '3gpp2_forward_dcch_rc' => '3GPP2-Forward-DCCH-RC',
+ '3gpp2_forward_fch_mux_op' => '3GPP2-Forward-FCH-Mux-Option',
+ '3gpp2_forward_fch_rc' => '3GPP2-Forward-FCH-RC',
+ '3gpp2_forward_pdch_rc' => '3GPP2-Forward-PDCH-RC',
+ '3gpp2_forward_traffic_ty' => '3GPP2-Forward-Traffic-Type',
+ '3gpp2_home_agent_ip_addr' => '3GPP2-Home-Agent-IP-Address',
+ '3gpp2_ike_preshared_secr' => '3GPP2-Ike-Preshared-Secret-Request',
+ '3gpp2_inbound_mobile_ip_' => '3GPP2-Inbound-Mobile-IP-Sig-Octets',
+ '3gpp2_ip_qos' => '3GPP2-IP-QoS',
+ '3gpp2_ip_technology' => '3GPP2-IP-Technology',
+ '3gpp2_keyid' => '3GPP2-KeyID',
+ '3gpp2_last_user_activity' => '3GPP2-Last-User-Activity-Time',
+ '3gpp2_mip_lifetime' => '3GPP2-MIP-Lifetime',
+ '3gpp2_mn_aaa_removal_ind' => '3GPP2-MN-AAA-Removal-Indication',
+ '3gpp2_mn_ha_shared_key' => '3GPP2-MN-HA-Shared-Key',
+ '3gpp2_mn_ha_spi' => '3GPP2-MN-HA-SPI',
+ '3gpp2_module_orig_term_i' => '3GPP2-Module-Orig-Term-Indicator',
+ '3gpp2_number_active_tran' => '3GPP2-Number-Active-Transitions',
+ '3gpp2_originating_number' => '3GPP2-Originating-Number-SDBs',
+ '3gpp2_originating_sdb_oc' => '3GPP2-Originating-SDB-OCtet-Count',
+ '3gpp2_outbound_mobile_ip' => '3GPP2-Outbound-Mobile-IP-Sig-Octets',
+ '3gpp2_pcf_ip_address' => '3GPP2-PCF-IP-Address',
+ '3gpp2_pre_shared_secret' => '3GPP2-Pre-Shared-Secret',
+ '3gpp2_prepaid_acct_capab' => '3GPP2-Prepaid-acct-Capability',
+ '3gpp2_prepaid_acct_quota' => '3GPP2-Prepaid-Acct-Quota',
+ '3gpp2_prepaid_tariff_swi' => '3GPP2-PrePaid-Tariff-Switching',
+ '3gpp2_received_hdlc_octe' => '3GPP2-Received-HDLC-Octets',
+ '3gpp2_release_indicator' => '3GPP2-Release-Indicator',
+ '3gpp2_remote_address_tab' => '3GPP2-Remote-Address-Table-Index',
+ '3gpp2_remote_ip_address' => '3GPP2-Remote-IP-Address',
+ '3gpp2_remote_ipv4_addr_o' => '3GPP2-Remote-IPv4-Addr-Octet-Count',
+ '3gpp2_remote_ipv6_addres' => '3GPP2-Remote-IPv6-Address',
+ '3gpp2_remote_ipv6_octet_' => '3GPP2-Remote-IPv6-Octet-Count',
+ '3gpp2_reverse_dcch_mux_o' => '3GPP2-Reverse-DCCH-Mux-Option',
+ '3gpp2_reverse_dhhc_rc' => '3GPP2-Reverse-DHHC-RC',
+ '3gpp2_reverse_fch_mux_op' => '3GPP2-Reverse-FCH-Mux-Option',
+ '3gpp2_reverse_fch_rc' => '3GPP2-Reverse-FCH-RC',
+ '3gpp2_reverse_traffic_ty' => '3GPP2-Reverse-Traffic-Type',
+ '3gpp2_reverse_tunnel_spe' => '3GPP2-Reverse-Tunnel-Spec',
+ '3gpp2_rn_packet_data_ina' => '3GPP2-RN-Packet-Data-Inactivity-Timer',
+ '3gpp2_s_key' => '3GPP2-S-Key',
+ '3gpp2_s_lifetime' => '3GPP2-S-Lifetime',
+ '3gpp2_s_request' => '3GPP2-S-Request',
+ '3gpp2_security_level' => '3GPP2-Security-Level',
+ '3gpp2_service_option' => '3GPP2-Service-Option',
+ '3gpp2_service_option_pro' => '3GPP2-Service-Option-Profile',
+ '3gpp2_service_reference_' => '3GPP2-Service-Reference-Id',
+ '3gpp2_session_continue' => '3GPP2-Session-Continue',
+ '3gpp2_session_terminatio' => '3GPP2-Session-Termination-Capability',
+ '3gpp2_terminating_number' => '3GPP2-Terminating-Number-SDBs',
+ '3gpp2_terminating_sdb_oc' => '3GPP2-Terminating-SDB-Octet-Count',
+ '3gpp2_user_id' => '3GPP2-User-Id',
+ '3gpp_charging_characteri' => '3GPP-Charging-Characteristics',
+ '3gpp_charging_gateway_ad' => '3GPP-Charging-Gateway-Address',
+ '3gpp_charging_gateway_ip' => '3GPP-Charging-Gateway-IPv6-Address',
+ '3gpp_charging_id' => '3GPP-Charging-ID',
+ '3gpp_ggsn_address' => '3GPP-GGSN-Address',
+ '3gpp_ggsn_ipv6_address' => '3GPP-GGSN-IPv6-Address',
+ '3gpp_ggsn_mcc_mnc' => '3GPP-GGSN-MCC-MNC',
+ '3gpp_gprs_negotiated_qos' => '3GPP-GPRS-Negotiated-QoS-profile',
+ '3gpp_imsi' => '3GPP-IMSI',
+ '3gpp_imsi_mcc_mnc' => '3GPP-IMSI-MCC-MNC',
+ '3gpp_ipv6_dns_servers' => '3GPP-IPv6-DNS-Servers',
+ '3gpp_nsapi' => '3GPP-NSAPI',
+ '3gpp_pdp_type' => '3GPP-PDP-Type',
+ '3gpp_selection_mode' => '3GPP-Selection-Mode',
+ '3gpp_session_stop_indica' => '3GPP-Session-Stop-Indicator',
+ '3gpp_sgsn_address' => '3GPP-SGSN-Address',
+ '3gpp_sgsn_ipv6_address' => '3GPP-SGSN-IPv6-Address',
+ 'aat_assign_ip_pool' => 'AAT-Assign-IP-Pool',
+ 'aat_atm_direct' => 'AAT-ATM-Direct',
+ 'aat_atm_traffic_profile' => 'AAT-ATM-Traffic-Profile',
+ 'aat_atm_vci' => 'AAT-ATM-VCI',
+ 'aat_atm_vpi' => 'AAT-ATM-VPI',
+ 'aat_client_primary_dns' => 'AAT-Client-Primary-DNS',
+ 'aat_client_primary_wins_' => 'AAT-Client-Primary-WINS-NBNS',
+ 'aat_client_secondary_win' => 'AAT-Client-Secondary-WINS-NBNS',
+ 'aat_data_filter' => 'AAT-Data-Filter',
+ 'aat_input_octets_diff' => 'AAT-Input-Octets-Diff',
+ 'aat_ip_pool_definition' => 'AAT-IP-Pool-Definition',
+ 'aat_ip_tos' => 'AAT-IP-TOS',
+ 'aat_ip_tos_apply_to' => 'AAT-IP-TOS-Apply-To',
+ 'aat_ip_tos_precedence' => 'AAT-IP-TOS-Precedence',
+ 'aat_mcast_client' => 'AAT-MCast-Client',
+ 'aat_output_octets_diff' => 'AAT-Output-Octets-Diff',
+ 'aat_ppp_address' => 'AAT-PPP-Address',
+ 'aat_require_auth' => 'AAT-Require-Auth',
+ 'aat_source_ip_check' => 'AAT-Source-IP-Check',
+ 'aat_user_mac_address' => 'AAT-User-MAC-Address',
+ 'aat_vrouter_name' => 'AAT-Vrouter-Name',
+ 'acc_access_community' => 'Acc-Access-Community',
+ 'acc_access_partition' => 'Acc-Access-Partition',
+ 'acc_acct_on_off_reason' => 'Acc-Acct-On-Off-Reason',
+ 'acc_ace_token' => 'Acc-Ace-Token',
+ 'acc_ace_token_ttl' => 'Acc-Ace-Token-Ttl',
+ 'acc_apsm_oversubscribed' => 'Acc-Apsm-Oversubscribed',
+ 'acc_bridging_support' => 'Acc-Bridging-Support',
+ 'acc_callback_cbcp_type' => 'Acc-Callback-CBCP-Type',
+ 'acc_callback_delay' => 'Acc-Callback-Delay',
+ 'acc_callback_mode' => 'Acc-Callback-Mode',
+ 'acc_callback_num_valid' => 'Acc-Callback-Num-Valid',
+ 'acc_ccp_option' => 'Acc-Ccp-Option',
+ 'acc_clearing_cause' => 'Acc-Clearing-Cause',
+ 'acc_clearing_location' => 'Acc-Clearing-Location',
+ 'acc_connect_rx_speed' => 'Acc-Connect-Rx-Speed',
+ 'acc_connect_tx_speed' => 'Acc-Connect-Tx-Speed',
+ 'acc_customer_id' => 'Acc-Customer-Id',
+ 'acc_dial_port_index' => 'Acc-Dial-Port-Index',
+ 'acc_dialout_auth_mode' => 'Acc-Dialout-Auth-Mode',
+ 'acc_dialout_auth_passwor' => 'Acc-Dialout-Auth-Password',
+ 'acc_dialout_auth_usernam' => 'Acc-Dialout-Auth-Username',
+ 'acc_dns_server_pri' => 'Acc-Dns-Server-Pri',
+ 'acc_dns_server_sec' => 'Acc-Dns-Server-Sec',
+ 'acc_igmp_admin_state' => 'Acc-Igmp-Admin-State',
+ 'acc_igmp_version' => 'Acc-Igmp-Version',
+ 'acc_input_errors' => 'Acc-Input-Errors',
+ 'acc_ip_compression' => 'Acc-Ip-Compression',
+ 'acc_ip_gateway_pri' => 'Acc-Ip-Gateway-Pri',
+ 'acc_ip_gateway_sec' => 'Acc-Ip-Gateway-Sec',
+ 'acc_ip_pool_name' => 'Acc-Ip-Pool-Name',
+ 'acc_ipx_compression' => 'Acc-Ipx-Compression',
+ 'acc_ml_call_threshold' => 'Acc-ML-Call-Threshold',
+ 'acc_ml_clear_threshold' => 'Acc-ML-Clear-Threshold',
+ 'acc_ml_damping_factor' => 'Acc-ML-Damping-Factor',
+ 'acc_ml_mlx_admin_state' => 'Acc-ML-MLX-Admin-State',
+ 'acc_modem_error_protocol' => 'Acc-Modem-Error-Protocol',
+ 'acc_modem_modulation_typ' => 'Acc-Modem-Modulation-Type',
+ 'acc_nbns_server_pri' => 'Acc-Nbns-Server-Pri',
+ 'acc_nbns_server_sec' => 'Acc-Nbns-Server-Sec',
+ 'acc_output_errors' => 'Acc-Output-Errors',
+ 'acc_reason_code' => 'Acc-Reason-Code',
+ 'acc_request_type' => 'Acc-Request-Type',
+ 'acc_route_policy' => 'Acc-Route-Policy',
+ 'acc_service_profile' => 'Acc-Service-Profile',
+ 'acc_tunnel_port' => 'Acc-Tunnel-Port',
+ 'acc_tunnel_secret' => 'Acc-Tunnel-Secret',
+ 'acc_vpsm_reject_cause' => 'Acc-Vpsm-Reject-Cause',
+ 'acct_authentic' => 'Acct-Authentic',
+ 'acct_delay_time' => 'Acct-Delay-Time',
+ 'acct_dyn_ac_ent' => 'Acct_Dyn_Ac_Ent',
+ 'acct_dyn_ac_enu' => 'Acct-Dyn-Ac-Ent',
+ 'acct_input_gigawords' => 'Acct-Input-Gigawords',
+ 'acct_input_octets' => 'Acct-Input-Octets',
+ 'acct_input_octets_64' => 'Acct_Input_Octets_64',
+ 'acct_input_octets_65' => 'Acct-Input-Octets-64',
+ 'acct_input_packets' => 'Acct-Input-Packets',
+ 'acct_input_packets_64' => 'Acct_Input_Packets_64',
+ 'acct_input_packets_65' => 'Acct-Input-Packets-64',
+ 'acct_interim_interval' => 'Acct-Interim-Interval',
+ 'acct_link_count' => 'Acct-Link-Count',
+ 'acct_mcast_in_octets' => 'Acct_Mcast_In_Octets',
+ 'acct_mcast_in_octett' => 'Acct-Mcast-In-Octets',
+ 'acct_mcast_in_packets' => 'Acct_Mcast_In_Packets',
+ 'acct_mcast_in_packett' => 'Acct-Mcast-In-Packets',
+ 'acct_mcast_out_octets' => 'Acct_Mcast_Out_Octets',
+ 'acct_mcast_out_octett' => 'Acct-Mcast-Out-Octets',
+ 'acct_mcast_out_packets' => 'Acct_Mcast_Out_Packets',
+ 'acct_mcast_out_packett' => 'Acct-Mcast-Out-Packets',
+ 'acct_multi_session_id' => 'Acct-Multi-Session-Id',
+ 'acct_output_gigawords' => 'Acct-Output-Gigawords',
+ 'acct_output_octets' => 'Acct-Output-Octets',
+ 'acct_output_octets_64' => 'Acct_Output_Octets_64',
+ 'acct_output_octets_65' => 'Acct-Output-Octets-64',
+ 'acct_output_packets' => 'Acct-Output-Packets',
+ 'acct_output_packets_64' => 'Acct_Output_Packets_64',
+ 'acct_output_packets_65' => 'Acct-Output-Packets-64',
+ 'acct_session_gigawords' => 'Acct-Session-Gigawords',
+ 'acct_session_id' => 'Acct-Session-Id',
+ 'acct_session_input_gigaw' => 'Acct-Session-Input-Gigawords',
+ 'acct_session_input_octet' => 'Acct-Session-Input-Octets',
+ 'acct_session_octets' => 'Acct-Session-Octets',
+ 'acct_session_output_giga' => 'Acct-Session-Output-Gigawords',
+ 'acct_session_output_octe' => 'Acct-Session-Output-Octets',
+ 'acct_session_start_time' => 'Acct-Session-Start-Time',
+ 'acct_session_time' => 'Acct-Session-Time',
+ 'acct_status_type' => 'Acct-Status-Type',
+ 'acct_terminate_cause' => 'Acct-Terminate-Cause',
+ 'acct_tunnel_connection' => 'Acct-Tunnel-Connection',
+ 'acct_tunnel_packets_lost' => 'Acct-Tunnel-Packets-Lost',
+ 'acct_type' => 'Acct-Type',
+ 'acct_unique_session_id' => 'Acct-Unique-Session-Id',
+ 'add_prefix' => 'Add-Prefix',
+ 'add_suffix' => 'Add-Suffix',
+ 'alteon_service_type' => 'Alteon-Service-Type',
+ 'altiga_access_hours_g_u' => 'Altiga-Access-Hours-G/U',
+ 'altiga_allow_alpha_only_' => 'Altiga-Allow-Alpha-Only-Passwords-G',
+ 'altiga_ipsec_allow_passw' => 'Altiga-IPSec-Allow-Passwd-Store-G/U',
+ 'altiga_ipsec_authenticat' => 'Altiga-IPSec-Authentication-G',
+ 'altiga_ipsec_banner_g' => 'Altiga-IPSec-Banner-G',
+ 'altiga_ipsec_default_dom' => 'Altiga-IPSec-Default-Domain-G',
+ 'altiga_ipsec_l2l_keepali' => 'Altiga-IPSec-L2L-Keepalives-G',
+ 'altiga_ipsec_mode_config' => 'Altiga-IPSec-Mode-Config-G',
+ 'altiga_ipsec_over_nat_g' => 'Altiga-IPSec-Over-NAT-G',
+ 'altiga_ipsec_over_nat_po' => 'Altiga-IPSec-Over-NAT-Port-Num-G',
+ 'altiga_ipsec_sec_associa' => 'Altiga-IPSec-Sec-Association-G/U',
+ 'altiga_ipsec_secondary_d' => 'Altiga-IPSec-Secondary-Domains-G',
+ 'altiga_ipsec_split_tunne' => 'Altiga-IPSec-Split-Tunnel-List-G',
+ 'altiga_ipsec_tunnel_type' => 'Altiga-IPSec-Tunnel-Type-G',
+ 'altiga_ipsec_user_group_' => 'Altiga-IPSec-User-Group-Lock-G',
+ 'altiga_l2tp_encryption_g' => 'Altiga-L2TP-Encryption-G',
+ 'altiga_l2tp_min_authenti' => 'Altiga-L2TP-Min-Authentication-G/U',
+ 'altiga_min_password_leng' => 'Altiga-Min-Password-Length-G',
+ 'altiga_pptp_encryption_g' => 'Altiga-PPTP-Encryption-G',
+ 'altiga_pptp_min_authenti' => 'Altiga-PPTP-Min-Authentication-G/U',
+ 'altiga_primary_dns_g' => 'Altiga-Primary-DNS-G',
+ 'altiga_primary_wins_g' => 'Altiga-Primary-WINS-G',
+ 'altiga_priority_on_sep_g' => 'Altiga-Priority-on-SEP-G/U',
+ 'altiga_secondary_dns_g' => 'Altiga-Secondary-DNS-G',
+ 'altiga_secondary_wins_g' => 'Altiga-Secondary-WINS-G',
+ 'altiga_sep_card_assignme' => 'Altiga-SEP-Card-Assignment-G/U',
+ 'altiga_simultaneous_logi' => 'Altiga-Simultaneous-Logins-G/U',
+ 'altiga_tunneling_protoco' => 'Altiga-Tunneling-Protocols-G/U',
+ 'altiga_use_client_addres' => 'Altiga-Use-Client-Address-G/U',
+ 'annex_acct_servers' => 'Annex-Acct-Servers',
+ 'annex_addr_resolution_pr' => 'Annex-Addr-Resolution-Protocol',
+ 'annex_addr_resolution_se' => 'Annex-Addr-Resolution-Servers',
+ 'annex_audit_level' => 'Annex-Audit-Level',
+ 'annex_authen_servers' => 'Annex-Authen-Servers',
+ 'annex_begin_modulation' => 'Annex-Begin-Modulation',
+ 'annex_begin_receive_line' => 'Annex-Begin-Receive-Line-Level',
+ 'annex_callback_portlist' => 'Annex-Callback-Portlist',
+ 'annex_cli_command' => 'Annex-CLI-Command',
+ 'annex_cli_filter' => 'Annex-CLI-Filter',
+ 'annex_compression_protoc' => 'Annex-Compression-Protocol',
+ 'annex_connect_progress' => 'Annex-Connect-Progress',
+ 'annex_disconnect_reason' => 'Annex-Disconnect-Reason',
+ 'annex_domain_name' => 'Annex-Domain-Name',
+ 'annex_edo' => 'Annex-EDO',
+ 'annex_end_modulation' => 'Annex-End-Modulation',
+ 'annex_end_receive_line_l' => 'Annex-End-Receive-Line-Level',
+ 'annex_error_correction_p' => 'Annex-Error-Correction-Prot',
+ 'annex_filter' => 'Annex-Filter',
+ 'annex_host_allow' => 'Annex-Host-Allow',
+ 'annex_host_restrict' => 'Annex-Host-Restrict',
+ 'annex_input_filter' => 'Annex-Input-Filter',
+ 'annex_keypress_timeout' => 'Annex-Keypress-Timeout',
+ 'annex_local_ip_address' => 'Annex-Local-IP-Address',
+ 'annex_local_username' => 'Annex-Local-Username',
+ 'annex_logical_channel_nu' => 'Annex-Logical-Channel-Number',
+ 'annex_maximum_call_durat' => 'Annex-Maximum-Call-Duration',
+ 'annex_modem_disc_reason' => 'Annex-Modem-Disc-Reason',
+ 'annex_mrru' => 'Annex-MRRU',
+ 'annex_multicast_rate_lim' => 'Annex-Multicast-Rate-Limit',
+ 'annex_multilink_id' => 'Annex-Multilink-Id',
+ 'annex_num_in_multilink' => 'Annex-Num-In-Multilink',
+ 'annex_output_filter' => 'Annex-Output-Filter',
+ 'annex_pool_id' => 'Annex-Pool-Id',
+ 'annex_port' => 'Annex-Port',
+ 'annex_ppp_trace_level' => 'Annex-PPP-Trace-Level',
+ 'annex_pre_input_octets' => 'Annex-Pre-Input-Octets',
+ 'annex_pre_input_packets' => 'Annex-Pre-Input-Packets',
+ 'annex_pre_output_octets' => 'Annex-Pre-Output-Octets',
+ 'annex_pre_output_packets' => 'Annex-Pre-Output-Packets',
+ 'annex_primary_dns_server' => 'Annex-Primary-DNS-Server',
+ 'annex_primary_nbns_serve' => 'Annex-Primary-NBNS-Server',
+ 'annex_product_name' => 'Annex-Product-Name',
+ 'annex_rate_reneg_req_rcv' => 'Annex-Rate-Reneg-Req-Rcvd',
+ 'annex_rate_reneg_req_sen' => 'Annex-Rate-Reneg-Req-Sent',
+ 'annex_re_chap_timeout' => 'Annex-Re-CHAP-Timeout',
+ 'annex_receive_speed' => 'Annex-Receive-Speed',
+ 'annex_retrain_requests_r' => 'Annex-Retrain-Requests-Rcvd',
+ 'annex_retrain_requests_s' => 'Annex-Retrain-Requests-Sent',
+ 'annex_retransmitted_pack' => 'Annex-Retransmitted-Packets',
+ 'annex_sec_profile_index' => 'Annex-Sec-Profile-Index',
+ 'annex_secondary_dns_serv' => 'Annex-Secondary-DNS-Server',
+ 'annex_secondary_nbns_ser' => 'Annex-Secondary-NBNS-Server',
+ 'annex_signal_to_noise_ra' => 'Annex-Signal-to-Noise-Ratio',
+ 'annex_sw_version' => 'Annex-SW-Version',
+ 'annex_syslog_tap' => 'Annex-Syslog-Tap',
+ 'annex_system_disc_reason' => 'Annex-System-Disc-Reason',
+ 'annex_transmit_speed' => 'Annex-Transmit-Speed',
+ 'annex_transmitted_packet' => 'Annex-Transmitted-Packets',
+ 'annex_tunnel_authen_mode' => 'Annex-Tunnel-Authen-Mode',
+ 'annex_tunnel_authen_type' => 'Annex-Tunnel-Authen-Type',
+ 'annex_unauthenticated_ti' => 'Annex-Unauthenticated-Time',
+ 'annex_user_level' => 'Annex-User-Level',
+ 'annex_user_server_locati' => 'Annex-User-Server-Location',
+ 'annex_wan_number' => 'Annex-Wan-Number',
+ 'arap_challenge_response' => 'ARAP-Challenge-Response',
+ 'arap_features' => 'ARAP-Features',
+ 'arap_password' => 'ARAP-Password',
+ 'arap_security' => 'ARAP-Security',
+ 'arap_security_data' => 'ARAP-Security-Data',
+ 'arap_zone_access' => 'ARAP-Zone-Access',
+ 'ascend_access_intercept_' => 'Ascend-Access-Intercept-LEA',
+ 'ascend_access_intercepta' => 'Ascend-Access-Intercept-Log',
+ 'ascend_add_seconds' => 'Ascend-Add-Seconds',
+ 'ascend_appletalk_peer_mo' => 'Ascend-Appletalk-Peer-Mode',
+ 'ascend_appletalk_route' => 'Ascend-Appletalk-Route',
+ 'ascend_ara_pw' => 'Ascend-Ara-PW',
+ 'ascend_assign_ip_client' => 'Ascend-Assign-IP-Client',
+ 'ascend_assign_ip_global_' => 'Ascend-Assign-IP-Global-Pool',
+ 'ascend_assign_ip_pool' => 'Ascend-Assign-IP-Pool',
+ 'ascend_assign_ip_server' => 'Ascend-Assign-IP-Server',
+ 'ascend_atm_connect_group' => 'Ascend-ATM-Connect-Group',
+ 'ascend_atm_connect_vci' => 'Ascend-ATM-Connect-Vci',
+ 'ascend_atm_connect_vpi' => 'Ascend-ATM-Connect-Vpi',
+ 'ascend_atm_direct' => 'Ascend-ATM-Direct',
+ 'ascend_atm_direct_profil' => 'Ascend-ATM-Direct-Profile',
+ 'ascend_atm_fault_managem' => 'Ascend-ATM-Fault-Management',
+ 'ascend_atm_group' => 'Ascend-ATM-Group',
+ 'ascend_atm_loopback_cell' => 'Ascend-ATM-Loopback-Cell-Loss',
+ 'ascend_atm_vci' => 'Ascend-ATM-Vci',
+ 'ascend_atm_vpi' => 'Ascend-ATM-Vpi',
+ 'ascend_auth_delay' => 'Ascend-Auth-Delay',
+ 'ascend_auth_type' => 'Ascend-Auth-Type',
+ 'ascend_authen_alias' => 'Ascend-Authen-Alias',
+ 'ascend_backup' => 'Ascend-Backup',
+ 'ascend_bacp_enable' => 'Ascend-BACP-Enable',
+ 'ascend_base_channel_coun' => 'Ascend-Base-Channel-Count',
+ 'ascend_bi_directional_au' => 'Ascend-Bi-Directional-Auth',
+ 'ascend_billing_number' => 'Ascend-Billing-Number',
+ 'ascend_bir_bridge_group' => 'Ascend-BIR-Bridge-Group',
+ 'ascend_bir_enable' => 'Ascend-BIR-Enable',
+ 'ascend_bir_proxy' => 'Ascend-BIR-Proxy',
+ 'ascend_bridge' => 'Ascend-Bridge',
+ 'ascend_bridge_address' => 'Ascend-Bridge-Address',
+ 'ascend_bridge_non_pppoe' => 'Ascend-Bridge-Non-PPPoE',
+ 'ascend_cache_refresh' => 'Ascend-Cache-Refresh',
+ 'ascend_cache_time' => 'Ascend-Cache-Time',
+ 'ascend_call_attempt_limi' => 'Ascend-Call-Attempt-Limit',
+ 'ascend_call_block_durati' => 'Ascend-Call-Block-Duration',
+ 'ascend_call_by_call' => 'Ascend-Call-By-Call',
+ 'ascend_call_direction' => 'Ascend-Call-Direction',
+ 'ascend_call_filter' => 'Ascend-Call-Filter',
+ 'ascend_call_type' => 'Ascend-Call-Type',
+ 'ascend_callback' => 'Ascend-Callback',
+ 'ascend_callback_delay' => 'Ascend-Callback-Delay',
+ 'ascend_calling_id_number' => 'Ascend-Calling-Id-Number-Plan',
+ 'ascend_calling_id_presen' => 'Ascend-Calling-Id-Presentatn',
+ 'ascend_calling_id_screen' => 'Ascend-Calling-Id-Screening',
+ 'ascend_calling_id_type_o' => 'Ascend-Calling-Id-Type-Of-Num',
+ 'ascend_calling_subaddres' => 'Ascend-Calling-Subaddress',
+ 'ascend_cbcp_delay' => 'Ascend-CBCP-Delay',
+ 'ascend_cbcp_enable' => 'Ascend-CBCP-Enable',
+ 'ascend_cbcp_mode' => 'Ascend-CBCP-Mode',
+ 'ascend_cbcp_trunk_group' => 'Ascend-CBCP-Trunk-Group',
+ 'ascend_cir_timer' => 'Ascend-CIR-Timer',
+ 'ascend_ckt_type' => 'Ascend-Ckt-Type',
+ 'ascend_client_assign_dns' => 'Ascend-Client-Assign-DNS',
+ 'ascend_client_assign_win' => 'Ascend-Client-Assign-WINS',
+ 'ascend_client_gateway' => 'Ascend-Client-Gateway',
+ 'ascend_client_primary_dn' => 'Ascend-Client-Primary-DNS',
+ 'ascend_client_primary_wi' => 'Ascend-Client-Primary-WINS',
+ 'ascend_client_secondary_' => 'Ascend-Client-Secondary-WINS',
+ 'ascend_client_secondarya' => 'Ascend-Client-Secondary-DNS',
+ 'ascend_connect_progress' => 'Ascend-Connect-Progress',
+ 'ascend_data_filter' => 'Ascend-Data-Filter',
+ 'ascend_data_rate' => 'Ascend-Data-Rate',
+ 'ascend_data_svc' => 'Ascend-Data-Svc',
+ 'ascend_dba_monitor' => 'Ascend-DBA-Monitor',
+ 'ascend_dec_channel_count' => 'Ascend-Dec-Channel-Count',
+ 'ascend_destination_nas_p' => 'Ascend-Destination-Nas-Port',
+ 'ascend_dhcp_maximum_leas' => 'Ascend-DHCP-Maximum-Leases',
+ 'ascend_dhcp_pool_number' => 'Ascend-DHCP-Pool-Number',
+ 'ascend_dhcp_reply' => 'Ascend-DHCP-Reply',
+ 'ascend_dial_number' => 'Ascend-Dial-Number',
+ 'ascend_dialed_number' => 'Ascend-Dialed-Number',
+ 'ascend_dialout_allowed' => 'Ascend-Dialout-Allowed',
+ 'ascend_disconnect_cause' => 'Ascend-Disconnect-Cause',
+ 'ascend_dropped_octets' => 'Ascend-Dropped-Octets',
+ 'ascend_dropped_packets' => 'Ascend-Dropped-Packets',
+ 'ascend_dsl_cir_recv_limi' => 'Ascend-Dsl-CIR-Recv-Limit',
+ 'ascend_dsl_cir_xmit_limi' => 'Ascend-Dsl-CIR-Xmit-Limit',
+ 'ascend_dsl_downstream_li' => 'Ascend-Dsl-Downstream-Limit',
+ 'ascend_dsl_rate_mode' => 'Ascend-Dsl-Rate-Mode',
+ 'ascend_dsl_rate_type' => 'Ascend-Dsl-Rate-Type',
+ 'ascend_dsl_upstream_limi' => 'Ascend-Dsl-Upstream-Limit',
+ 'ascend_egress_enabled' => 'Ascend-Egress-Enabled',
+ 'ascend_endpoint_disc' => 'Ascend-Endpoint-Disc',
+ 'ascend_event_type' => 'Ascend-Event-Type',
+ 'ascend_expect_callback' => 'Ascend-Expect-Callback',
+ 'ascend_fcp_parameter' => 'Ascend-FCP-Parameter',
+ 'ascend_filter' => 'Ascend-Filter',
+ 'ascend_filter_required' => 'Ascend-Filter-Required',
+ 'ascend_first_dest' => 'Ascend-First-Dest',
+ 'ascend_force_56' => 'Ascend-Force-56',
+ 'ascend_fr_08_mode' => 'Ascend-FR-08-Mode',
+ 'ascend_fr_circuit_name' => 'Ascend-FR-Circuit-Name',
+ 'ascend_fr_dce_n392' => 'Ascend-FR-DCE-N392',
+ 'ascend_fr_dce_n393' => 'Ascend-FR-DCE-N393',
+ 'ascend_fr_direct' => 'Ascend-FR-Direct',
+ 'ascend_fr_direct_dlci' => 'Ascend-FR-Direct-DLCI',
+ 'ascend_fr_direct_profile' => 'Ascend-FR-Direct-Profile',
+ 'ascend_fr_dlci' => 'Ascend-FR-DLCI',
+ 'ascend_fr_dte_n392' => 'Ascend-FR-DTE-N392',
+ 'ascend_fr_dte_n393' => 'Ascend-FR-DTE-N393',
+ 'ascend_fr_link_mgt' => 'Ascend-FR-Link-Mgt',
+ 'ascend_fr_link_status_dl' => 'Ascend-FR-Link-Status-DLCI',
+ 'ascend_fr_linkup' => 'Ascend-FR-LinkUp',
+ 'ascend_fr_n391' => 'Ascend-FR-N391',
+ 'ascend_fr_nailed_grp' => 'Ascend-FR-Nailed-Grp',
+ 'ascend_fr_profile_name' => 'Ascend-FR-Profile-Name',
+ 'ascend_fr_svc_addr' => 'Ascend-FR-SVC-Addr',
+ 'ascend_fr_t391' => 'Ascend-FR-T391',
+ 'ascend_fr_t392' => 'Ascend-FR-T392',
+ 'ascend_fr_type' => 'Ascend-FR-Type',
+ 'ascend_ft1_caller' => 'Ascend-FT1-Caller',
+ 'ascend_global_call_id' => 'Ascend-Global-Call-Id',
+ 'ascend_group' => 'Ascend-Group',
+ 'ascend_h323_conference_i' => 'Ascend-H323-Conference-Id',
+ 'ascend_h323_dialed_time' => 'Ascend-H323-Dialed-Time',
+ 'ascend_h323_fegw_address' => 'Ascend-H323-Fegw-Address',
+ 'ascend_h323_gatekeeper' => 'Ascend-H323-Gatekeeper',
+ 'ascend_handle_ipx' => 'Ascend-Handle-IPX',
+ 'ascend_history_weigh_typ' => 'Ascend-History-Weigh-Type',
+ 'ascend_home_agent_ip_add' => 'Ascend-Home-Agent-IP-Addr',
+ 'ascend_home_agent_passwo' => 'Ascend-Home-Agent-Password',
+ 'ascend_home_agent_udp_po' => 'Ascend-Home-Agent-UDP-Port',
+ 'ascend_home_network_name' => 'Ascend-Home-Network-Name',
+ 'ascend_host_info' => 'Ascend-Host-Info',
+ 'ascend_idle_limit' => 'Ascend-Idle-Limit',
+ 'ascend_if_netmask' => 'Ascend-IF-Netmask',
+ 'ascend_inc_channel_count' => 'Ascend-Inc-Channel-Count',
+ 'ascend_inter_arrival_jit' => 'Ascend-Inter-Arrival-Jitter',
+ 'ascend_ip_direct' => 'Ascend-IP-Direct',
+ 'ascend_ip_pool_chaining' => 'Ascend-IP-Pool-Chaining',
+ 'ascend_ip_pool_definitio' => 'Ascend-IP-Pool-Definition',
+ 'ascend_ip_tos' => 'Ascend-IP-TOS',
+ 'ascend_ip_tos_apply_to' => 'Ascend-IP-TOS-Apply-To',
+ 'ascend_ip_tos_precedence' => 'Ascend-IP-TOS-Precedence',
+ 'ascend_ipsec_profile' => 'Ascend-IPSEC-Profile',
+ 'ascend_ipx_alias' => 'Ascend-IPX-Alias',
+ 'ascend_ipx_header_compre' => 'Ascend-IPX-Header-Compression',
+ 'ascend_ipx_node_addr' => 'Ascend-IPX-Node-Addr',
+ 'ascend_ipx_peer_mode' => 'Ascend-IPX-Peer-Mode',
+ 'ascend_ipx_route' => 'Ascend-IPX-Route',
+ 'ascend_link_compression' => 'Ascend-Link-Compression',
+ 'ascend_max_shared_users' => 'Ascend-Max-Shared-Users',
+ 'ascend_maximum_call_dura' => 'Ascend-Maximum-Call-Duration',
+ 'ascend_maximum_channels' => 'Ascend-Maximum-Channels',
+ 'ascend_maximum_time' => 'Ascend-Maximum-Time',
+ 'ascend_menu_item' => 'Ascend-Menu-Item',
+ 'ascend_menu_selector' => 'Ascend-Menu-Selector',
+ 'ascend_metric' => 'Ascend-Metric',
+ 'ascend_minimum_channels' => 'Ascend-Minimum-Channels',
+ 'ascend_modem_portno' => 'Ascend-Modem-PortNo',
+ 'ascend_modem_shelfno' => 'Ascend-Modem-ShelfNo',
+ 'ascend_modem_slotno' => 'Ascend-Modem-SlotNo',
+ 'ascend_mpp_idle_percent' => 'Ascend-MPP-Idle-Percent',
+ 'ascend_mtu' => 'Ascend-MTU',
+ 'ascend_multicast_client' => 'Ascend-Multicast-Client',
+ 'ascend_multicast_gleave_' => 'Ascend-Multicast-GLeave-Delay',
+ 'ascend_multicast_rate_li' => 'Ascend-Multicast-Rate-Limit',
+ 'ascend_multilink_id' => 'Ascend-Multilink-ID',
+ 'ascend_nas_port_format' => 'Ascend-NAS-Port-Format',
+ 'ascend_netware_timeout' => 'Ascend-Netware-timeout',
+ 'ascend_num_in_multilink' => 'Ascend-Num-In-Multilink',
+ 'ascend_number_sessions' => 'Ascend-Number-Sessions',
+ 'ascend_numbering_plan_id' => 'Ascend-Numbering-Plan-ID',
+ 'ascend_owner_ip_addr' => 'Ascend-Owner-IP-Addr',
+ 'ascend_port_redir_portnu' => 'Ascend-Port-Redir-Portnum',
+ 'ascend_port_redir_protoc' => 'Ascend-Port-Redir-Protocol',
+ 'ascend_port_redir_server' => 'Ascend-Port-Redir-Server',
+ 'ascend_ppp_address' => 'Ascend-PPP-Address',
+ 'ascend_ppp_async_map' => 'Ascend-PPP-Async-Map',
+ 'ascend_ppp_vj_1172' => 'Ascend-PPP-VJ-1172',
+ 'ascend_ppp_vj_slot_comp' => 'Ascend-PPP-VJ-Slot-Comp',
+ 'ascend_pppoe_enable' => 'Ascend-PPPoE-Enable',
+ 'ascend_pre_input_octets' => 'Ascend-Pre-Input-Octets',
+ 'ascend_pre_input_packets' => 'Ascend-Pre-Input-Packets',
+ 'ascend_pre_output_octets' => 'Ascend-Pre-Output-Octets',
+ 'ascend_pre_output_packet' => 'Ascend-Pre-Output-Packets',
+ 'ascend_preempt_limit' => 'Ascend-Preempt-Limit',
+ 'ascend_presession_time' => 'Ascend-PreSession-Time',
+ 'ascend_pri_number_type' => 'Ascend-PRI-Number-Type',
+ 'ascend_primary_home_agen' => 'Ascend-Primary-Home-Agent',
+ 'ascend_private_route' => 'Ascend-Private-Route',
+ 'ascend_private_route_req' => 'Ascend-Private-Route-Required',
+ 'ascend_private_route_tab' => 'Ascend-Private-Route-Table-ID',
+ 'ascend_pw_lifetime' => 'Ascend-PW-Lifetime',
+ 'ascend_pw_warntime' => 'Ascend-PW-Warntime',
+ 'ascend_qos_downstream' => 'Ascend-QOS-Downstream',
+ 'ascend_qos_upstream' => 'Ascend-QOS-Upstream',
+ 'ascend_receive_secret' => 'Ascend-Receive-Secret',
+ 'ascend_recv_name' => 'Ascend-Recv-Name',
+ 'ascend_redirect_number' => 'Ascend-Redirect-Number',
+ 'ascend_remote_addr' => 'Ascend-Remote-Addr',
+ 'ascend_remote_fw' => 'Ascend-Remote-FW',
+ 'ascend_remove_seconds' => 'Ascend-Remove-Seconds',
+ 'ascend_require_auth' => 'Ascend-Require-Auth',
+ 'ascend_route_appletalk' => 'Ascend-Route-Appletalk',
+ 'ascend_route_ip' => 'Ascend-Route-IP',
+ 'ascend_route_ipx' => 'Ascend-Route-IPX',
+ 'ascend_secondary_home_ag' => 'Ascend-Secondary-Home-Agent',
+ 'ascend_seconds_of_histor' => 'Ascend-Seconds-Of-History',
+ 'ascend_send_auth' => 'Ascend-Send-Auth',
+ 'ascend_send_passwd' => 'Ascend-Send-Passwd',
+ 'ascend_send_secret' => 'Ascend-Send-Secret',
+ 'ascend_service_type' => 'Ascend-Service-Type',
+ 'ascend_session_svr_key' => 'Ascend-Session-Svr-Key',
+ 'ascend_session_type' => 'Ascend-Session-Type',
+ 'ascend_shared_profile_en' => 'Ascend-Shared-Profile-Enable',
+ 'ascend_source_auth' => 'Ascend-Source-Auth',
+ 'ascend_source_ip_check' => 'Ascend-Source-IP-Check',
+ 'ascend_svc_enabled' => 'Ascend-SVC-Enabled',
+ 'ascend_target_util' => 'Ascend-Target-Util',
+ 'ascend_telnet_profile' => 'Ascend-Telnet-Profile',
+ 'ascend_temporary_rtes' => 'Ascend-Temporary-Rtes',
+ 'ascend_third_prompt' => 'Ascend-Third-Prompt',
+ 'ascend_token_expiry' => 'Ascend-Token-Expiry',
+ 'ascend_token_idle' => 'Ascend-Token-Idle',
+ 'ascend_token_immediate' => 'Ascend-Token-Immediate',
+ 'ascend_traffic_shaper' => 'Ascend-Traffic-Shaper',
+ 'ascend_transit_number' => 'Ascend-Transit-Number',
+ 'ascend_ts_idle_limit' => 'Ascend-TS-Idle-Limit',
+ 'ascend_ts_idle_mode' => 'Ascend-TS-Idle-Mode',
+ 'ascend_tunnel_vrouter_na' => 'Ascend-Tunnel-VRouter-Name',
+ 'ascend_tunneling_protoco' => 'Ascend-Tunneling-Protocol',
+ 'ascend_user_acct_base' => 'Ascend-User-Acct-Base',
+ 'ascend_user_acct_host' => 'Ascend-User-Acct-Host',
+ 'ascend_user_acct_key' => 'Ascend-User-Acct-Key',
+ 'ascend_user_acct_port' => 'Ascend-User-Acct-Port',
+ 'ascend_user_acct_time' => 'Ascend-User-Acct-Time',
+ 'ascend_user_acct_type' => 'Ascend-User-Acct-Type',
+ 'ascend_uu_info' => 'Ascend-UU-Info',
+ 'ascend_vrouter_name' => 'Ascend-VRouter-Name',
+ 'ascend_x25_cug' => 'Ascend-X25-Cug',
+ 'ascend_x25_nui' => 'Ascend-X25-Nui',
+ 'ascend_x25_nui_password_' => 'Ascend-X25-Nui-Password-Prompt',
+ 'ascend_x25_nui_prompt' => 'Ascend-X25-Nui-Prompt',
+ 'ascend_x25_pad_alias_1' => 'Ascend-X25-Pad-Alias-1',
+ 'ascend_x25_pad_alias_2' => 'Ascend-X25-Pad-Alias-2',
+ 'ascend_x25_pad_alias_3' => 'Ascend-X25-Pad-Alias-3',
+ 'ascend_x25_pad_banner' => 'Ascend-X25-Pad-Banner',
+ 'ascend_x25_pad_prompt' => 'Ascend-X25-Pad-Prompt',
+ 'ascend_x25_pad_x3_parame' => 'Ascend-X25-Pad-X3-Parameters',
+ 'ascend_x25_pad_x3_profil' => 'Ascend-X25-Pad-X3-Profile',
+ 'ascend_x25_profile_name' => 'Ascend-X25-Profile-Name',
+ 'ascend_x25_reverse_charg' => 'Ascend-X25-Reverse-Charging',
+ 'ascend_x25_rpoa' => 'Ascend-X25-Rpoa',
+ 'ascend_x25_x121_address' => 'Ascend-X25-X121-Address',
+ 'ascend_xmit_rate' => 'Ascend-Xmit-Rate',
+ 'assigned_ip_address' => 'Assigned_IP_Address',
+ 'assigned_ip_addrest' => 'Assigned-IP-Address',
+ 'auth_type' => 'Auth-Type',
+ 'autz_type' => 'Autz-Type',
+ 'bg_aging_time' => 'BG_Aging_Time',
+ 'bg_aging_timf' => 'BG-Aging-Time',
+ 'bg_path_cost' => 'BG_Path_Cost',
+ 'bg_path_cosu' => 'BG-Path-Cost',
+ 'bg_span_dis' => 'BG_Span_Dis',
+ 'bg_span_dit' => 'BG-Span-Dis',
+ 'bg_trans_bpdu' => 'BG_Trans_BPDU',
+ 'bg_trans_bpdv' => 'BG-Trans-BPDU',
+ 'bind_auth_context' => 'Bind_Auth_Context',
+ 'bind_auth_contexu' => 'Bind-Auth-Context',
+ 'bind_auth_max_sessions' => 'Bind_Auth_Max_Sessions',
+ 'bind_auth_max_sessiont' => 'Bind-Auth-Max-Sessions',
+ 'bind_auth_protocol' => 'Bind_Auth_Protocol',
+ 'bind_auth_protocom' => 'Bind-Auth-Protocol',
+ 'bind_auth_service_grp' => 'Bind_Auth_Service_Grp',
+ 'bind_auth_service_grq' => 'Bind-Auth-Service-Grp',
+ 'bind_bypass_bypass' => 'Bind_Bypass_Bypass',
+ 'bind_bypass_bypast' => 'Bind-Bypass-Bypass',
+ 'bind_bypass_context' => 'Bind_Bypass_Context',
+ 'bind_bypass_contexu' => 'Bind-Bypass-Context',
+ 'bind_dot1q_port' => 'Bind_Dot1q_Port',
+ 'bind_dot1q_poru' => 'Bind-Dot1q-Port',
+ 'bind_dot1q_slot' => 'Bind_Dot1q_Slot',
+ 'bind_dot1q_slou' => 'Bind-Dot1q-Slot',
+ 'bind_dot1q_vlan_tag_id' => 'Bind_Dot1q_Vlan_Tag_Id',
+ 'bind_dot1q_vlan_tag_ie' => 'Bind-Dot1q-Vlan-Tag-Id',
+ 'bind_int_context' => 'Bind_Int_Context',
+ 'bind_int_contexu' => 'Bind-Int-Context',
+ 'bind_int_interface_name' => 'Bind_Int_Interface_Name',
+ 'bind_int_interface_namf' => 'Bind-Int-Interface-Name',
+ 'bind_l2tp_flow_control' => 'Bind_L2TP_Flow_Control',
+ 'bind_l2tp_flow_controm' => 'Bind-L2TP-Flow-Control',
+ 'bind_l2tp_tunnel_name' => 'Bind_L2TP_Tunnel_Name',
+ 'bind_l2tp_tunnel_namf' => 'Bind-L2TP-Tunnel-Name',
+ 'bind_ses_context' => 'Bind_Ses_Context',
+ 'bind_ses_contexu' => 'Bind-Ses-Context',
+ 'bind_sub_password' => 'Bind_Sub_Password',
+ 'bind_sub_passwore' => 'Bind-Sub-Password',
+ 'bind_sub_user_at_context' => 'Bind_Sub_User_At_Context',
+ 'bind_sub_user_at_contexu' => 'Bind-Sub-User-At-Context',
+ 'bind_tun_context' => 'Bind_Tun_Context',
+ 'bind_tun_contexu' => 'Bind-Tun-Context',
+ 'bind_type' => 'Bind_Type',
+ 'bind_typf' => 'Bind-Type',
+ 'bintec_bibodialtable' => 'BinTec-biboDialTable',
+ 'bintec_biboppptable' => 'BinTec-biboPPPTable',
+ 'bintec_ipextiftable' => 'BinTec-ipExtIfTable',
+ 'bintec_ipextrttable' => 'BinTec-ipExtRtTable',
+ 'bintec_ipfiltertable' => 'BinTec-ipFilterTable',
+ 'bintec_ipnatpresettable' => 'BinTec-ipNatPresetTable',
+ 'bintec_ipqostable' => 'BinTec-ipQoSTable',
+ 'bintec_iproutetable' => 'BinTec-ipRouteTable',
+ 'bintec_ipxcirctable' => 'BinTec-ipxCircTable',
+ 'bintec_ipxstaticroutetab' => 'BinTec-ipxStaticRouteTable',
+ 'bintec_ipxstaticservtabl' => 'BinTec-ipxStaticServTable',
+ 'bintec_ospfiftable' => 'BinTec-ospfIfTable',
+ 'bintec_pppextiftable' => 'BinTec-pppExtIfTable',
+ 'bintec_qosiftable' => 'BinTec-qosIfTable',
+ 'bintec_qospolicytable' => 'BinTec-qosPolicyTable',
+ 'bintec_ripcirctable' => 'BinTec-ripCircTable',
+ 'bintec_sapcirctable' => 'BinTec-sapCircTable',
+ 'bridge_group' => 'Bridge_Group',
+ 'bridge_grouq' => 'Bridge-Group',
+ 'cabletron_protocol_calla' => 'Cabletron-Protocol-Callable',
+ 'cabletron_protocol_enabl' => 'Cabletron-Protocol-Enable',
+ 'call_id' => 'call-id',
+ 'callback_id' => 'Callback-Id',
+ 'callback_number' => 'Callback-Number',
+ 'called_station_id' => 'Called-Station-Id',
+ 'caller_id' => 'Caller-ID',
+ 'calling_station_id' => 'Calling-Station-Id',
+ 'cbbsm_bandwidth' => 'CBBSM-Bandwidth',
+ 'challenge_state' => 'Challenge-State',
+ 'chap_challenge' => 'CHAP-Challenge',
+ 'chap_password' => 'CHAP-Password',
+ 'char_noecho' => 'Char-Noecho',
+ 'cisco_abort_cause' => 'Cisco-Abort-Cause',
+ 'cisco_account_info' => 'Cisco-Account-Info',
+ 'cisco_assign_ip_pool' => 'Cisco-Assign-IP-Pool',
+ 'cisco_avpair' => 'Cisco-AVPair',
+ 'cisco_call_filter' => 'Cisco-Call-Filter',
+ 'cisco_call_type' => 'Cisco-Call-Type',
+ 'cisco_command_code' => 'Cisco-Command-Code',
+ 'cisco_control_info' => 'Cisco-Control-Info',
+ 'cisco_data_filter' => 'Cisco-Data-Filter',
+ 'cisco_data_rate' => 'Cisco-Data-Rate',
+ 'cisco_disconnect_cause' => 'Cisco-Disconnect-Cause',
+ 'cisco_email_server_ack_f' => 'Cisco-Email-Server-Ack-Flag',
+ 'cisco_email_server_addre' => 'Cisco-Email-Server-Address',
+ 'cisco_fax_account_id_ori' => 'Cisco-Fax-Account-Id-Origin',
+ 'cisco_fax_auth_status' => 'Cisco-Fax-Auth-Status',
+ 'cisco_fax_connect_speed' => 'Cisco-Fax-Connect-Speed',
+ 'cisco_fax_coverpage_flag' => 'Cisco-Fax-Coverpage-Flag',
+ 'cisco_fax_dsn_address' => 'Cisco-Fax-Dsn-Address',
+ 'cisco_fax_dsn_flag' => 'Cisco-Fax-Dsn-Flag',
+ 'cisco_fax_mdn_address' => 'Cisco-Fax-Mdn-Address',
+ 'cisco_fax_mdn_flag' => 'Cisco-Fax-Mdn-Flag',
+ 'cisco_fax_modem_time' => 'Cisco-Fax-Modem-Time',
+ 'cisco_fax_msg_id' => 'Cisco-Fax-Msg-Id',
+ 'cisco_fax_pages' => 'Cisco-Fax-Pages',
+ 'cisco_fax_process_abort_' => 'Cisco-Fax-Process-Abort-Flag',
+ 'cisco_fax_recipient_coun' => 'Cisco-Fax-Recipient-Count',
+ 'cisco_gateway_id' => 'Cisco-Gateway-Id',
+ 'cisco_idle_limit' => 'Cisco-Idle-Limit',
+ 'cisco_ip_direct' => 'Cisco-IP-Direct',
+ 'cisco_ip_pool_definition' => 'Cisco-IP-Pool-Definition',
+ 'cisco_link_compression' => 'Cisco-Link-Compression',
+ 'cisco_maximum_channels' => 'Cisco-Maximum-Channels',
+ 'cisco_maximum_time' => 'Cisco-Maximum-Time',
+ 'cisco_multilink_id' => 'Cisco-Multilink-ID',
+ 'cisco_nas_port' => 'Cisco-NAS-Port',
+ 'cisco_num_in_multilink' => 'Cisco-Num-In-Multilink',
+ 'cisco_port_used' => 'Cisco-Port-Used',
+ 'cisco_ppp_async_map' => 'Cisco-PPP-Async-Map',
+ 'cisco_ppp_vj_slot_comp' => 'Cisco-PPP-VJ-Slot-Comp',
+ 'cisco_pre_input_octets' => 'Cisco-Pre-Input-Octets',
+ 'cisco_pre_input_packets' => 'Cisco-Pre-Input-Packets',
+ 'cisco_pre_output_octets' => 'Cisco-Pre-Output-Octets',
+ 'cisco_pre_output_packets' => 'Cisco-Pre-Output-Packets',
+ 'cisco_presession_time' => 'Cisco-PreSession-Time',
+ 'cisco_pw_lifetime' => 'Cisco-PW-Lifetime',
+ 'cisco_route_ip' => 'Cisco-Route-IP',
+ 'cisco_service_info' => 'Cisco-Service-Info',
+ 'cisco_target_util' => 'Cisco-Target-Util',
+ 'cisco_xmit_rate' => 'Cisco-Xmit-Rate',
+ 'class' => 'Class',
+ 'client_dns_pri' => 'Client_DNS_Pri',
+ 'client_dns_prj' => 'Client-DNS-Pri',
+ 'client_dns_sec' => 'Client_DNS_Sec',
+ 'client_dns_sed' => 'Client-DNS-Sec',
+ 'client_id' => 'Client-Id',
+ 'client_ip_address' => 'Client-IP-Address',
+ 'client_port_dnis' => 'Client-Port-DNIS',
+ 'client_port_id' => 'Client-Port-Id',
+ 'colubris_avpair' => 'Colubris-AVPair',
+ 'configuration_token' => 'Configuration-Token',
+ 'connect_info' => 'Connect-Info',
+ 'connect_rate' => 'Connect-Rate',
+ 'context_name' => 'Context_Name',
+ 'context_namf' => 'Context-Name',
+ 'crypt_password' => 'Crypt-Password',
+ 'current_time' => 'Current-Time',
+ 'cvpn3000_access_hours' => 'CVPN3000-Access-Hours',
+ 'cvpn3000_allow_network_e' => 'CVPN3000-Allow-Network-Extension-Mode',
+ 'cvpn3000_auth_server_pas' => 'CVPN3000-Auth-Server-Password',
+ 'cvpn3000_auth_server_pri' => 'CVPN3000-Auth-Server-Priority',
+ 'cvpn3000_auth_server_typ' => 'CVPN3000-Auth-Server-Type',
+ 'cvpn3000_authd_user_idle' => 'CVPN3000-Authd-User-Idle-Timeout',
+ 'cvpn3000_cisco_ip_phone_' => 'CVPN3000-Cisco-IP-Phone-Bypass',
+ 'cvpn3000_dhcp_network_sc' => 'CVPN3000-DHCP-Network-Scope',
+ 'cvpn3000_ike_keep_alives' => 'CVPN3000-IKE-Keep-Alives',
+ 'cvpn3000_ipsec_allow_pas' => 'CVPN3000-IPSec-Allow-Passwd-Store',
+ 'cvpn3000_ipsec_auth_on_r' => 'CVPN3000-IPSec-Auth-On-Rekey',
+ 'cvpn3000_ipsec_authentic' => 'CVPN3000-IPSec-Authentication',
+ 'cvpn3000_ipsec_authoriza' => 'CVPN3000-IPSec-Authorization-Type',
+ 'cvpn3000_ipsec_authorizb' => 'CVPN3000-IPSec-Authorization-Required',
+ 'cvpn3000_ipsec_backup_se' => 'CVPN3000-IPSec-Backup-Servers',
+ 'cvpn3000_ipsec_backup_sf' => 'CVPN3000-IPSec-Backup-Server-List',
+ 'cvpn3000_ipsec_banner1' => 'CVPN3000-IPSec-Banner1',
+ 'cvpn3000_ipsec_banner2' => 'CVPN3000-IPSec-Banner2',
+ 'cvpn3000_ipsec_client_fw' => 'CVPN3000-IPSec-Client-Fw-Filter-Name',
+ 'cvpn3000_ipsec_client_fx' => 'CVPN3000-IPSec-Client-Fw-Filter-Opt',
+ 'cvpn3000_ipsec_confidenc' => 'CVPN3000-IPSec-Confidence-Level',
+ 'cvpn3000_ipsec_default_d' => 'CVPN3000-IPSec-Default-Domain',
+ 'cvpn3000_ipsec_dn_field' => 'CVPN3000-IPSec-DN-Field',
+ 'cvpn3000_ipsec_group_nam' => 'CVPN3000-IPSec-Group-Name',
+ 'cvpn3000_ipsec_ike_peer_' => 'CVPN3000-IPSec-IKE-Peer-ID-Check',
+ 'cvpn3000_ipsec_ip_compre' => 'CVPN3000-IPSec-IP-Compression',
+ 'cvpn3000_ipsec_ltl_keepa' => 'CVPN3000-IPSec-LTL-Keepalives',
+ 'cvpn3000_ipsec_mode_conf' => 'CVPN3000-IPSec-Mode-Config',
+ 'cvpn3000_ipsec_over_udp' => 'CVPN3000-IPSec-Over-UDP',
+ 'cvpn3000_ipsec_over_udp_' => 'CVPN3000-IPSec-Over-UDP-Port',
+ 'cvpn3000_ipsec_reqrd_cli' => 'CVPN3000-IPSec-Reqrd-Client-Fw-Cap',
+ 'cvpn3000_ipsec_sec_assoc' => 'CVPN3000-IPSec-Sec-Association',
+ 'cvpn3000_ipsec_split_dns' => 'CVPN3000-IPSec-Split-DNS-Names',
+ 'cvpn3000_ipsec_split_tun' => 'CVPN3000-IPSec-Split-Tunnel-List',
+ 'cvpn3000_ipsec_split_tuo' => 'CVPN3000-IPSec-Split-Tunneling-Policy',
+ 'cvpn3000_ipsec_tunnel_ty' => 'CVPN3000-IPSec-Tunnel-Type',
+ 'cvpn3000_ipsec_user_grou' => 'CVPN3000-IPSec-User-Group-Lock',
+ 'cvpn3000_l2tp_encryption' => 'CVPN3000-L2TP-Encryption',
+ 'cvpn3000_l2tp_min_auth_p' => 'CVPN3000-L2TP-Min-Auth-Protocol',
+ 'cvpn3000_l2tp_mppc_compr' => 'CVPN3000-L2TP-MPPC-Compression',
+ 'cvpn3000_leap_bypass' => 'CVPN3000-LEAP-Bypass',
+ 'cvpn3000_ms_client_icpt_' => 'CVPN3000-MS-Client-Icpt-DHCP-Conf-Msg',
+ 'cvpn3000_ms_client_subne' => 'CVPN3000-MS-Client-Subnet-Mask',
+ 'cvpn3000_partition_max_s' => 'CVPN3000-Partition-Max-Sessions',
+ 'cvpn3000_partition_mobil' => 'CVPN3000-Partition-Mobile-IP-Key',
+ 'cvpn3000_partition_mobim' => 'CVPN3000-Partition-Mobile-IP-Address',
+ 'cvpn3000_partition_mobin' => 'CVPN3000-Partition-Mobile-IP-SPI',
+ 'cvpn3000_partition_premi' => 'CVPN3000-Partition-Premise-Router',
+ 'cvpn3000_partition_prima' => 'CVPN3000-Partition-Primary-DHCP',
+ 'cvpn3000_partition_secon' => 'CVPN3000-Partition-Secondary-DHCP',
+ 'cvpn3000_pptp_encryption' => 'CVPN3000-PPTP-Encryption',
+ 'cvpn3000_pptp_min_auth_p' => 'CVPN3000-PPTP-Min-Auth-Protocol',
+ 'cvpn3000_pptp_mppc_compr' => 'CVPN3000-PPTP-MPPC-Compression',
+ 'cvpn3000_primary_dns' => 'CVPN3000-Primary-DNS',
+ 'cvpn3000_primary_wins' => 'CVPN3000-Primary-WINS',
+ 'cvpn3000_priority_on_sep' => 'CVPN3000-Priority-On-SEP',
+ 'cvpn3000_reqrd_client_fw' => 'CVPN3000-Reqrd-Client-Fw-Vendor-Code',
+ 'cvpn3000_reqrd_client_fx' => 'CVPN3000-Reqrd-Client-Fw-Product-Code',
+ 'cvpn3000_reqrd_client_fy' => 'CVPN3000-Reqrd-Client-Fw-Description',
+ 'cvpn3000_request_auth_ve' => 'CVPN3000-Request-Auth-Vector',
+ 'cvpn3000_require_hw_clie' => 'CVPN3000-Require-HW-Client-Auth',
+ 'cvpn3000_require_individ' => 'CVPN3000-Require-Individual-User-Auth',
+ 'cvpn3000_secondary_dns' => 'CVPN3000-Secondary-DNS',
+ 'cvpn3000_secondary_wins' => 'CVPN3000-Secondary-WINS',
+ 'cvpn3000_sep_card_assign' => 'CVPN3000-SEP-Card-Assignment',
+ 'cvpn3000_simultaneous_lo' => 'CVPN3000-Simultaneous-Logins',
+ 'cvpn3000_strip_realm' => 'CVPN3000-Strip-Realm',
+ 'cvpn3000_tunneling_proto' => 'CVPN3000-Tunneling-Protocols',
+ 'cvpn3000_use_client_addr' => 'CVPN3000-Use-Client-Address',
+ 'cvpn3000_user_auth_serve' => 'CVPN3000-User-Auth-Server-Name',
+ 'cvpn3000_user_auth_servf' => 'CVPN3000-User-Auth-Server-Port',
+ 'cvpn3000_user_auth_servg' => 'CVPN3000-User-Auth-Server-Secret',
+ 'cvpn5000_client_assigned' => 'CVPN5000-Client-Assigned-IP',
+ 'cvpn5000_client_assignee' => 'CVPN5000-Client-Assigned-IPX',
+ 'cvpn5000_client_real_ip' => 'CVPN5000-Client-Real-IP',
+ 'cvpn5000_echo' => 'CVPN5000-Echo',
+ 'cvpn5000_tunnel_throughp' => 'CVPN5000-Tunnel-Throughput',
+ 'cvpn5000_vpn_groupinfo' => 'CVPN5000-VPN-GroupInfo',
+ 'cvpn5000_vpn_password' => 'CVPN5000-VPN-Password',
+ 'cvx_assign_ip_pool' => 'CVX-Assign-IP-Pool',
+ 'cvx_client_assign_dns' => 'CVX-Client-Assign-DNS',
+ 'cvx_data_filter' => 'CVX-Data-Filter',
+ 'cvx_data_rate' => 'CVX-Data-Rate',
+ 'cvx_disconnect_cause' => 'CVX-Disconnect-Cause',
+ 'cvx_identification' => 'CVX-Identification',
+ 'cvx_idle_limit' => 'CVX-Idle-Limit',
+ 'cvx_ipsvc_aznlvl' => 'CVX-IPSVC-AZNLVL',
+ 'cvx_ipsvc_mask' => 'CVX-IPSVC-Mask',
+ 'cvx_maximum_channels' => 'CVX-Maximum-Channels',
+ 'cvx_modem_begin_modulati' => 'CVX-Modem-Begin-Modulation',
+ 'cvx_modem_begin_recv_lin' => 'CVX-Modem-Begin-Recv-Line-Lvl',
+ 'cvx_modem_data_compressi' => 'CVX-Modem-Data-Compression',
+ 'cvx_modem_end_modulation' => 'CVX-Modem-End-Modulation',
+ 'cvx_modem_end_recv_line_' => 'CVX-Modem-End-Recv-Line-Lvl',
+ 'cvx_modem_error_correcti' => 'CVX-Modem-Error-Correction',
+ 'cvx_modem_local_rate_neg' => 'CVX-Modem-Local-Rate-Negs',
+ 'cvx_modem_local_retrains' => 'CVX-Modem-Local-Retrains',
+ 'cvx_modem_remote_rate_ne' => 'CVX-Modem-Remote-Rate-Negs',
+ 'cvx_modem_remote_retrain' => 'CVX-Modem-Remote-Retrains',
+ 'cvx_modem_retx_packets' => 'CVX-Modem-ReTx-Packets',
+ 'cvx_modem_snr' => 'CVX-Modem-SNR',
+ 'cvx_modem_tx_packets' => 'CVX-Modem-Tx-Packets',
+ 'cvx_multicast_client' => 'CVX-Multicast-Client',
+ 'cvx_multicast_rate_limit' => 'CVX-Multicast-Rate-Limit',
+ 'cvx_multilink_group_numb' => 'CVX-Multilink-Group-Number',
+ 'cvx_multilink_match_info' => 'CVX-Multilink-Match-Info',
+ 'cvx_ppp_address' => 'CVX-PPP-Address',
+ 'cvx_ppp_log_mask' => 'CVX-PPP-Log-Mask',
+ 'cvx_presession_time' => 'CVX-PreSession-Time',
+ 'cvx_primary_dns' => 'CVX-Primary-DNS',
+ 'cvx_radius_redirect' => 'CVX-Radius-Redirect',
+ 'cvx_secondary_dns' => 'CVX-Secondary-DNS',
+ 'cvx_ss7_session_id_type' => 'CVX-SS7-Session-ID-Type',
+ 'cvx_vpop_id' => 'CVX-VPOP-ID',
+ 'cvx_xmit_rate' => 'CVX-Xmit-Rate',
+ 'dhcp_max_leases' => 'DHCP_Max_Leases',
+ 'dhcp_max_leaset' => 'DHCP-Max-Leases',
+ 'dialback_name' => 'Dialback-Name',
+ 'dialback_no' => 'Dialback-No',
+ 'digest_algorithm' => 'Digest-Algorithm',
+ 'digest_attributes' => 'Digest-Attributes',
+ 'digest_body_digest' => 'Digest-Body-Digest',
+ 'digest_cnonce' => 'Digest-CNonce',
+ 'digest_method' => 'Digest-Method',
+ 'digest_nonce' => 'Digest-Nonce',
+ 'digest_nonce_count' => 'Digest-Nonce-Count',
+ 'digest_qop' => 'Digest-QOP',
+ 'digest_realm' => 'Digest-Realm',
+ 'digest_response' => 'Digest-Response',
+ 'digest_uri' => 'Digest-URI',
+ 'digest_user_name' => 'Digest-User-Name',
+ 'eap_code' => 'EAP-Code',
+ 'eap_id' => 'EAP-Id',
+ 'eap_md5_password' => 'EAP-MD5-Password',
+ 'eap_message' => 'EAP-Message',
+ 'eap_sim_any_id_req' => 'EAP-Sim-ANY_ID_REQ',
+ 'eap_sim_checkcode' => 'EAP-Sim-CHECKCODE',
+ 'eap_sim_counter' => 'EAP-Sim-COUNTER',
+ 'eap_sim_counter_too_smal' => 'EAP-Sim-COUNTER_TOO_SMALL',
+ 'eap_sim_encr_data' => 'EAP-Sim-ENCR_DATA',
+ 'eap_sim_extra' => 'EAP-Sim-EXTRA',
+ 'eap_sim_fullauth_id_req' => 'EAP-Sim-FULLAUTH_ID_REQ',
+ 'eap_sim_hmac' => 'EAP-Sim-HMAC',
+ 'eap_sim_identity' => 'EAP-Sim-IDENTITY',
+ 'eap_sim_imsi' => 'EAP-Sim-IMSI',
+ 'eap_sim_iv' => 'EAP-Sim-IV',
+ 'eap_sim_kc1' => 'EAP-Sim-KC1',
+ 'eap_sim_kc2' => 'EAP-Sim-KC2',
+ 'eap_sim_kc3' => 'EAP-Sim-KC3',
+ 'eap_sim_key' => 'EAP-Sim-KEY',
+ 'eap_sim_mac' => 'EAP-Sim-MAC',
+ 'eap_sim_next_pseudonum' => 'EAP-Sim-NEXT_PSEUDONUM',
+ 'eap_sim_next_reauth_id' => 'EAP-Sim-NEXT_REAUTH_ID',
+ 'eap_sim_nonce_mt' => 'EAP-Sim-NONCE_MT',
+ 'eap_sim_nonce_s' => 'EAP-Sim-NONCE_S',
+ 'eap_sim_notification' => 'EAP-Sim-NOTIFICATION',
+ 'eap_sim_padding' => 'EAP-Sim-PADDING',
+ 'eap_sim_permanent_id_req' => 'EAP-Sim-PERMANENT_ID_REQ',
+ 'eap_sim_rand' => 'EAP-Sim-RAND',
+ 'eap_sim_rand1' => 'EAP-Sim-Rand1',
+ 'eap_sim_rand2' => 'EAP-Sim-Rand2',
+ 'eap_sim_rand3' => 'EAP-Sim-Rand3',
+ 'eap_sim_selected_version' => 'EAP-Sim-SELECTED_VERSION',
+ 'eap_sim_sres1' => 'EAP-Sim-SRES1',
+ 'eap_sim_sres2' => 'EAP-Sim-SRES2',
+ 'eap_sim_sres3' => 'EAP-Sim-SRES3',
+ 'eap_sim_state' => 'EAP-Sim-State',
+ 'eap_sim_subtype' => 'EAP-Sim-Subtype',
+ 'eap_sim_version_list' => 'EAP-Sim-VERSION_LIST',
+ 'eap_tls_require_client_c' => 'EAP-TLS-Require-Client-Cert',
+ 'eap_type' => 'EAP-Type',
+ 'eap_type_gtc' => 'EAP-Type-GTC',
+ 'eap_type_identity' => 'EAP-Type-Identity',
+ 'eap_type_leap' => 'EAP-Type-LEAP',
+ 'eap_type_md5' => 'EAP-Type-MD5',
+ 'eap_type_nak' => 'EAP-Type-NAK',
+ 'eap_type_notification' => 'EAP-Type-Notification',
+ 'eap_type_otp' => 'EAP-Type-OTP',
+ 'eap_type_peap' => 'EAP-Type-PEAP',
+ 'eap_type_sim' => 'EAP-Type-SIM',
+ 'eap_type_sim2' => 'EAP-Type-SIM2',
+ 'eap_type_tls' => 'EAP-Type-TLS',
+ 'eap_type_ttls' => 'EAP-Type-TTLS',
+ 'error_cause' => 'Error-Cause',
+ 'erx_address_pool_name' => 'ERX-Address-Pool-Name',
+ 'erx_alternate_cli_access' => 'ERX-Alternate-Cli-Access-Level',
+ 'erx_alternate_cli_vroute' => 'ERX-Alternate-Cli-Vrouter-Name',
+ 'erx_atm_mbs' => 'ERX-Atm-MBS',
+ 'erx_atm_pcr' => 'ERX-Atm-PCR',
+ 'erx_atm_scr' => 'ERX-Atm-SCR',
+ 'erx_atm_service_category' => 'ERX-Atm-Service-Category',
+ 'erx_bearer_type' => 'ERX-Bearer-Type',
+ 'erx_cli_allow_all_vr_acc' => 'ERX-Cli-Allow-All-VR-Access',
+ 'erx_cli_initial_access_l' => 'ERX-Cli-Initial-Access-Level',
+ 'erx_dial_out_number' => 'ERX-Dial-Out-Number',
+ 'erx_egress_policy_name' => 'ERX-Egress-Policy-Name',
+ 'erx_egress_statistics' => 'ERX-Egress-Statistics',
+ 'erx_framed_ip_route_tag' => 'ERX-Framed-Ip-Route-Tag',
+ 'erx_igmp_enable' => 'ERX-Igmp-Enable',
+ 'erx_ingress_policy_name' => 'ERX-Ingress-Policy-Name',
+ 'erx_ingress_statistics' => 'ERX-Ingress-Statistics',
+ 'erx_input_gigapkts' => 'ERX-Input-Gigapkts',
+ 'erx_ipv6_local_interface' => 'ERX-IpV6-Local-Interface',
+ 'erx_ipv6_primary_dns' => 'ERX-Ipv6-Primary-Dns',
+ 'erx_ipv6_secondary_dns' => 'ERX-Ipv6-Secondary-Dns',
+ 'erx_ipv6_virtual_router' => 'ERX-IpV6-Virtual-Router',
+ 'erx_local_loopback_inter' => 'ERX-Local-Loopback-Interface',
+ 'erx_maximum_bps' => 'ERX-Maximum-BPS',
+ 'erx_minimum_bps' => 'ERX-Minimum-BPS',
+ 'erx_output_gigapkts' => 'ERX-Output-Gigapkts',
+ 'erx_ppp_auth_protocol' => 'ERX-PPP-Auth-Protocol',
+ 'erx_ppp_password' => 'ERX-PPP-Password',
+ 'erx_ppp_username' => 'ERX-PPP-Username',
+ 'erx_pppoe_description' => 'ERX-Pppoe-Description',
+ 'erx_pppoe_max_sessions' => 'ERX-Pppoe-Max-Sessions',
+ 'erx_pppoe_url' => 'ERX-Pppoe-Url',
+ 'erx_primary_dns' => 'ERX-Primary-Dns',
+ 'erx_primary_wins' => 'ERX-Primary-Wins',
+ 'erx_qos_profile_interfac' => 'ERX-Qos-Profile-Interface-Type',
+ 'erx_qos_profile_name' => 'ERX-Qos-Profile-Name',
+ 'erx_redirect_vr_name' => 'ERX-Redirect-VR-Name',
+ 'erx_sa_validate' => 'ERX-Sa-Validate',
+ 'erx_secondary_dns' => 'ERX-Secondary-Dns',
+ 'erx_secondary_wins' => 'ERX-Secondary-Wins',
+ 'erx_service_bundle' => 'ERX-Service-Bundle',
+ 'erx_tunnel_interface_id' => 'ERX-Tunnel-Interface-Id',
+ 'erx_tunnel_maximum_sessi' => 'ERX-Tunnel-Maximum-Sessions',
+ 'erx_tunnel_nas_port_meth' => 'ERX-Tunnel-Nas-Port-Method',
+ 'erx_tunnel_password' => 'ERX-Tunnel-Password',
+ 'erx_tunnel_tos' => 'ERX-Tunnel-Tos',
+ 'erx_tunnel_virtual_route' => 'ERX-Tunnel-Virtual-Router',
+ 'erx_virtual_router_name' => 'ERX-Virtual-Router-Name',
+ 'event_timestamp' => 'Event-Timestamp',
+ 'exec_program' => 'Exec-Program',
+ 'exec_program_wait' => 'Exec-Program-Wait',
+ 'expiration' => 'Expiration',
+ 'extreme_netlogin_only' => 'Extreme-Netlogin-Only',
+ 'extreme_netlogin_url' => 'Extreme-Netlogin-Url',
+ 'extreme_netlogin_url_des' => 'Extreme-Netlogin-Url-Desc',
+ 'extreme_netlogin_vlan' => 'Extreme-Netlogin-Vlan',
+ 'fall_through' => 'Fall-Through',
+ 'filter_id' => 'Filter-Id',
+ 'foundry_command_exceptio' => 'Foundry-Command-Exception-Flag',
+ 'foundry_command_string' => 'Foundry-Command-String',
+ 'foundry_inm_privilege' => 'Foundry-INM-Privilege',
+ 'foundry_privilege_level' => 'Foundry-Privilege-Level',
+ 'framed_address' => 'Framed-Address',
+ 'framed_appletalk_link' => 'Framed-AppleTalk-Link',
+ 'framed_appletalk_network' => 'Framed-AppleTalk-Network',
+ 'framed_appletalk_zone' => 'Framed-AppleTalk-Zone',
+ 'framed_callback_id' => 'Framed-Callback-Id',
+ 'framed_compression' => 'Framed-Compression',
+ 'framed_filter_id' => 'Framed-Filter-Id',
+ 'framed_interface_id' => 'Framed-Interface-Id',
+ 'framed_ip_address' => 'Framed-IP-Address',
+ 'framed_ip_netmask' => 'Framed-IP-Netmask',
+ 'framed_ipv6_pool' => 'Framed-IPv6-Pool',
+ 'framed_ipv6_prefix' => 'Framed-IPv6-Prefix',
+ 'framed_ipv6_route' => 'Framed-IPv6-Route',
+ 'framed_ipx_network' => 'Framed-IPX-Network',
+ 'framed_mtu' => 'Framed-MTU',
+ 'framed_netmask' => 'Framed-Netmask',
+ 'framed_pool' => 'Framed-Pool',
+ 'framed_protocol' => 'Framed-Protocol',
+ 'framed_route' => 'Framed-Route',
+ 'framed_routing' => 'Framed-Routing',
+ 'freeradius_proxied_to' => 'FreeRADIUS-Proxied-To',
+ 'gandalf_around_the_corne' => 'Gandalf-Around-The-Corner',
+ 'gandalf_authentication_s' => 'Gandalf-Authentication-String',
+ 'gandalf_calling_line_id_' => 'Gandalf-Calling-Line-ID-1',
+ 'gandalf_calling_line_ida' => 'Gandalf-Calling-Line-ID-2',
+ 'gandalf_channel_group_na' => 'Gandalf-Channel-Group-Name-1',
+ 'gandalf_channel_group_nb' => 'Gandalf-Channel-Group-Name-2',
+ 'gandalf_compression_stat' => 'Gandalf-Compression-Status',
+ 'gandalf_dial_prefix_name' => 'Gandalf-Dial-Prefix-Name-1',
+ 'gandalf_dial_prefix_namf' => 'Gandalf-Dial-Prefix-Name-2',
+ 'gandalf_fwd_broadcast_in' => 'Gandalf-Fwd-Broadcast-In',
+ 'gandalf_fwd_broadcast_ou' => 'Gandalf-Fwd-Broadcast-Out',
+ 'gandalf_fwd_multicast_in' => 'Gandalf-Fwd-Multicast-In',
+ 'gandalf_fwd_multicast_ou' => 'Gandalf-Fwd-Multicast-Out',
+ 'gandalf_fwd_unicast_in' => 'Gandalf-Fwd-Unicast-In',
+ 'gandalf_fwd_unicast_out' => 'Gandalf-Fwd-Unicast-Out',
+ 'gandalf_hunt_group' => 'Gandalf-Hunt-Group',
+ 'gandalf_ipx_spoofing_sta' => 'Gandalf-IPX-Spoofing-State',
+ 'gandalf_ipx_watchdog_spo' => 'Gandalf-IPX-Watchdog-Spoof',
+ 'gandalf_min_outgoing_bea' => 'Gandalf-Min-Outgoing-Bearer',
+ 'gandalf_modem_mode' => 'Gandalf-Modem-Mode',
+ 'gandalf_modem_required_1' => 'Gandalf-Modem-Required-1',
+ 'gandalf_modem_required_2' => 'Gandalf-Modem-Required-2',
+ 'gandalf_operational_mode' => 'Gandalf-Operational-Modes',
+ 'gandalf_phone_number_1' => 'Gandalf-Phone-Number-1',
+ 'gandalf_phone_number_2' => 'Gandalf-Phone-Number-2',
+ 'gandalf_ppp_authenticati' => 'Gandalf-PPP-Authentication',
+ 'gandalf_ppp_ncp_type' => 'Gandalf-PPP-NCP-Type',
+ 'gandalf_remote_lan_name' => 'Gandalf-Remote-LAN-Name',
+ 'gandalf_sap_group_name_1' => 'Gandalf-SAP-Group-Name-1',
+ 'gandalf_sap_group_name_2' => 'Gandalf-SAP-Group-Name-2',
+ 'gandalf_sap_group_name_3' => 'Gandalf-SAP-Group-Name-3',
+ 'gandalf_sap_group_name_4' => 'Gandalf-SAP-Group-Name-4',
+ 'gandalf_sap_group_name_5' => 'Gandalf-SAP-Group-Name-5',
+ 'garderos_location_name' => 'Garderos-Location-Name',
+ 'garderos_service_name' => 'Garderos-Service-Name',
+ 'group' => 'Group',
+ 'group_name' => 'Group-Name',
+ 'gw_final_xlated_cdn' => 'gw-final-xlated-cdn',
+ 'gw_rxd_cdn' => 'gw-rxd-cdn',
+ 'h323_billing_model' => 'h323-billing-model',
+ 'h323_call_origin' => 'h323-call-origin',
+ 'h323_call_type' => 'h323-call-type',
+ 'h323_conf_id' => 'h323-conf-id',
+ 'h323_connect_time' => 'h323-connect-time',
+ 'h323_credit_amount' => 'h323-credit-amount',
+ 'h323_credit_time' => 'h323-credit-time',
+ 'h323_currency' => 'h323-currency',
+ 'h323_disconnect_cause' => 'h323-disconnect-cause',
+ 'h323_disconnect_time' => 'h323-disconnect-time',
+ 'h323_gw_id' => 'h323-gw-id',
+ 'h323_incoming_conf_id' => 'h323-incoming-conf-id',
+ 'h323_preferred_lang' => 'h323-preferred-lang',
+ 'h323_prompt_id' => 'h323-prompt-id',
+ 'h323_redirect_ip_address' => 'h323-redirect-ip-address',
+ 'h323_redirect_number' => 'h323-redirect-number',
+ 'h323_remote_address' => 'h323-remote-address',
+ 'h323_return_code' => 'h323-return-code',
+ 'h323_setup_time' => 'h323-setup-time',
+ 'h323_time_and_day' => 'h323-time-and-day',
+ 'h323_voice_quality' => 'h323-voice-quality',
+ 'hint' => 'Hint',
+ 'huntgroup_name' => 'Huntgroup-Name',
+ 'idle_timeout' => 'Idle-Timeout',
+ 'incoming_req_uri' => 'incoming-req-uri',
+ 'initial_modulation_type' => 'Initial-Modulation-Type',
+ 'ip3_ip_option' => 'IP3-IP-Option',
+ 'ip3_rdata_rate' => 'IP3-RData-Rate',
+ 'ip3_xdata_rate' => 'IP3-XData-Rate',
+ 'ip_address_pool_name' => 'Ip_Address_Pool_Name',
+ 'ip_address_pool_namf' => 'Ip-Address-Pool-Name',
+ 'ip_host_addr' => 'Ip_Host_Addr',
+ 'ip_host_adds' => 'Ip-Host-Addr',
+ 'ip_tos_field' => 'IP_TOS_Field',
+ 'ip_tos_fiele' => 'IP-TOS-Field',
+ 'itk_acct_serv_ip' => 'ITK-Acct-Serv-IP',
+ 'itk_acct_serv_prot' => 'ITK-Acct-Serv-Prot',
+ 'itk_auth_req_type' => 'ITK-Auth-Req-Type',
+ 'itk_auth_serv_ip' => 'ITK-Auth-Serv-IP',
+ 'itk_auth_serv_prot' => 'ITK-Auth-Serv-Prot',
+ 'itk_banner' => 'ITK-Banner',
+ 'itk_channel_binding' => 'ITK-Channel-Binding',
+ 'itk_ddi' => 'ITK-DDI',
+ 'itk_dest_no' => 'ITK-Dest-No',
+ 'itk_dialout_type' => 'ITK-Dialout-Type',
+ 'itk_filter_rule' => 'ITK-Filter-Rule',
+ 'itk_ftp_auth_ip' => 'ITK-Ftp-Auth-IP',
+ 'itk_ip_pool' => 'ITK-IP-Pool',
+ 'itk_isdn_prot' => 'ITK-ISDN-Prot',
+ 'itk_modem_init_string' => 'ITK-Modem-Init-String',
+ 'itk_modem_pool_id' => 'ITK-Modem-Pool-Id',
+ 'itk_nas_name' => 'ITK-NAS-Name',
+ 'itk_password_prompt' => 'ITK-Password-Prompt',
+ 'itk_ppp_auth_type' => 'ITK-PPP-Auth-Type',
+ 'itk_ppp_client_server_mo' => 'ITK-PPP-Client-Server-Mode',
+ 'itk_ppp_compression_prot' => 'ITK-PPP-Compression-Prot',
+ 'itk_prompt' => 'ITK-Prompt',
+ 'itk_provider_id' => 'ITK-Provider-Id',
+ 'itk_start_delay' => 'ITK-Start-Delay',
+ 'itk_tunnel_ip' => 'ITK-Tunnel-IP',
+ 'itk_tunnel_prot' => 'ITK-Tunnel-Prot',
+ 'itk_usergroup' => 'ITK-Usergroup',
+ 'itk_username' => 'ITK-Username',
+ 'itk_username_prompt' => 'ITK-Username-Prompt',
+ 'itk_users_default_entry' => 'ITK-Users-Default-Entry',
+ 'itk_users_default_pw' => 'ITK-Users-Default-Pw',
+ 'itk_welcome_message' => 'ITK-Welcome-Message',
+ 'juniper_allow_commands' => 'Juniper-Allow-Commands',
+ 'juniper_allow_configurat' => 'Juniper-Allow-Configuration',
+ 'juniper_deny_commands' => 'Juniper-Deny-Commands',
+ 'juniper_deny_configurati' => 'Juniper-Deny-Configuration',
+ 'juniper_local_user_name' => 'Juniper-Local-User-Name',
+ 'karlnet_turbocell_name' => 'KarlNet-TurboCell-Name',
+ 'karlnet_turbocell_opmode' => 'KarlNet-TurboCell-OpMode',
+ 'karlnet_turbocell_opstat' => 'KarlNet-TurboCell-OpState',
+ 'karlnet_turbocell_txrate' => 'KarlNet-TurboCell-TxRate',
+ 'lac_port' => 'LAC_Port',
+ 'lac_port_type' => 'LAC_Port_Type',
+ 'lac_port_typf' => 'LAC-Port-Type',
+ 'lac_poru' => 'LAC-Port',
+ 'lac_real_port' => 'LAC_Real_Port',
+ 'lac_real_port_type' => 'LAC_Real_Port_Type',
+ 'lac_real_port_typf' => 'LAC-Real-Port-Type',
+ 'lac_real_poru' => 'LAC-Real-Port',
+ 'ldap_group' => 'Ldap-Group',
+ 'ldap_userdn' => 'Ldap-UserDn',
+ 'le_admin_group' => 'LE-Admin-Group',
+ 'le_advice_of_charge' => 'LE-Advice-of-Charge',
+ 'le_connect_detail' => 'LE-Connect-Detail',
+ 'le_ip_gateway' => 'LE-IP-Gateway',
+ 'le_ip_pool' => 'LE-IP-Pool',
+ 'le_ipsec_active_profile' => 'LE-IPSec-Active-Profile',
+ 'le_ipsec_deny_action' => 'LE-IPSec-Deny-Action',
+ 'le_ipsec_log_options' => 'LE-IPSec-Log-Options',
+ 'le_ipsec_outsource_profi' => 'LE-IPSec-Outsource-Profile',
+ 'le_ipsec_passive_profile' => 'LE-IPSec-Passive-Profile',
+ 'le_modem_info' => 'LE-Modem-Info',
+ 'le_multicast_client' => 'LE-Multicast-Client',
+ 'le_nat_inmap' => 'LE-NAT-Inmap',
+ 'le_nat_log_options' => 'LE-NAT-Log-Options',
+ 'le_nat_other_session_tim' => 'LE-NAT-Other-Session-Timeout',
+ 'le_nat_outmap' => 'LE-NAT-Outmap',
+ 'le_nat_outsource_inmap' => 'LE-NAT-Outsource-Inmap',
+ 'le_nat_outsource_outmap' => 'LE-NAT-Outsource-Outmap',
+ 'le_nat_sess_dir_fail_act' => 'LE-NAT-Sess-Dir-Fail-Action',
+ 'le_nat_tcp_session_timeo' => 'LE-NAT-TCP-Session-Timeout',
+ 'le_terminate_detail' => 'LE-Terminate-Detail',
+ 'lm_password' => 'LM-Password',
+ 'local_web_acct_duration' => 'Local-Web-Acct-Duration',
+ 'local_web_acct_interim_r' => 'Local-Web-Acct-Interim-Rx-Bytes',
+ 'local_web_acct_interim_s' => 'Local-Web-Acct-Interim-Rx-Gigawords',
+ 'local_web_acct_interim_t' => 'Local-Web-Acct-Interim-Tx-Bytes',
+ 'local_web_acct_interim_u' => 'Local-Web-Acct-Interim-Tx-Gigawords',
+ 'local_web_acct_interim_v' => 'Local-Web-Acct-Interim-Tx-Mgmt',
+ 'local_web_acct_interim_w' => 'Local-Web-Acct-Interim-Rx-Mgmt',
+ 'local_web_acct_rx_mgmt' => 'Local-Web-Acct-Rx-Mgmt',
+ 'local_web_acct_time' => 'Local-Web-Acct-Time',
+ 'local_web_acct_tx_mgmt' => 'Local-Web-Acct-Tx-Mgmt',
+ 'local_web_border_router' => 'Local-Web-Border-Router',
+ 'local_web_client_ip' => 'Local-Web-Client-Ip',
+ 'local_web_reauth_counter' => 'Local-Web-Reauth-Counter',
+ 'local_web_rx_limit' => 'Local-Web-Rx-Limit',
+ 'local_web_tx_limit' => 'Local-Web-Tx-Limit',
+ 'login_callback_number' => 'Login-Callback-Number',
+ 'login_host' => 'Login-Host',
+ 'login_ip_host' => 'Login-IP-Host',
+ 'login_ipv6_host' => 'Login-IPv6-Host',
+ 'login_lat_group' => 'Login-LAT-Group',
+ 'login_lat_node' => 'Login-LAT-Node',
+ 'login_lat_port' => 'Login-LAT-Port',
+ 'login_lat_service' => 'Login-LAT-Service',
+ 'login_port' => 'Login-Port',
+ 'login_service' => 'Login-Service',
+ 'login_tcp_port' => 'Login-TCP-Port',
+ 'login_time' => 'Login-Time',
+ 'mcast_maxgroups' => 'Mcast_MaxGroups',
+ 'mcast_maxgroupt' => 'Mcast-MaxGroups',
+ 'mcast_receive' => 'Mcast_Receive',
+ 'mcast_receivf' => 'Mcast-Receive',
+ 'mcast_send' => 'Mcast_Send',
+ 'mcast_sene' => 'Mcast-Send',
+ 'medium_type' => 'Medium_Type',
+ 'medium_typf' => 'Medium-Type',
+ 'menu' => 'Menu',
+ 'merit_proxy_action' => 'Merit-Proxy-Action',
+ 'merit_user_id' => 'Merit-User-Id',
+ 'merit_user_realm' => 'Merit-User-Realm',
+ 'message_authenticator' => 'Message-Authenticator',
+ 'method' => 'method',
+ 'mikrotik_group' => 'Mikrotik-Group',
+ 'mikrotik_recv_limit' => 'Mikrotik-Recv-Limit',
+ 'mikrotik_xmit_limit' => 'Mikrotik-Xmit-Limit',
+ 'module_failure_message' => 'Module-Failure-Message',
+ 'module_success_message' => 'Module-Success-Message',
+ 'motorola_canopy_cirenabl' => 'Motorola-Canopy-CIRENABLE',
+ 'motorola_canopy_dlba' => 'Motorola-Canopy-DLBA',
+ 'motorola_canopy_enable' => 'Motorola-Canopy-Enable',
+ 'motorola_canopy_higherbw' => 'Motorola-Canopy-HIGHERBW',
+ 'motorola_canopy_hpcenabl' => 'Motorola-Canopy-HPCENABLE',
+ 'motorola_canopy_hpsdldr' => 'Motorola-Canopy-HPSDLDR',
+ 'motorola_canopy_hpsuldr' => 'Motorola-Canopy-HPSULDR',
+ 'motorola_canopy_lpsdldr' => 'Motorola-Canopy-LPSDLDR',
+ 'motorola_canopy_lpsuldr' => 'Motorola-Canopy-LPSULDR',
+ 'motorola_canopy_sdldr' => 'Motorola-Canopy-SDLDR',
+ 'motorola_canopy_shared_s' => 'Motorola-Canopy-Shared-Secret',
+ 'motorola_canopy_suldr' => 'Motorola-Canopy-SULDR',
+ 'motorola_canopy_ulba' => 'Motorola-Canopy-ULBA',
+ 'ms_acct_auth_type' => 'MS-Acct-Auth-Type',
+ 'ms_acct_eap_type' => 'MS-Acct-EAP-Type',
+ 'ms_arap_pw_change_reason' => 'MS-ARAP-PW-Change-Reason',
+ 'ms_bap_usage' => 'MS-BAP-Usage',
+ 'ms_chap2_cpw' => 'MS-CHAP2-CPW',
+ 'ms_chap2_response' => 'MS-CHAP2-Response',
+ 'ms_chap2_success' => 'MS-CHAP2-Success',
+ 'ms_chap_challenge' => 'MS-CHAP-Challenge',
+ 'ms_chap_cpw_1' => 'MS-CHAP-CPW-1',
+ 'ms_chap_cpw_2' => 'MS-CHAP-CPW-2',
+ 'ms_chap_domain' => 'MS-CHAP-Domain',
+ 'ms_chap_error' => 'MS-CHAP-Error',
+ 'ms_chap_lm_enc_pw' => 'MS-CHAP-LM-Enc-PW',
+ 'ms_chap_mppe_keys' => 'MS-CHAP-MPPE-Keys',
+ 'ms_chap_nt_enc_pw' => 'MS-CHAP-NT-Enc-PW',
+ 'ms_chap_response' => 'MS-CHAP-Response',
+ 'ms_chap_use_ntlm_auth' => 'MS-CHAP-Use-NTLM-Auth',
+ 'ms_filter' => 'MS-Filter',
+ 'ms_link_drop_time_limit' => 'MS-Link-Drop-Time-Limit',
+ 'ms_link_utilization_thre' => 'MS-Link-Utilization-Threshold',
+ 'ms_mppe_encryption_polic' => 'MS-MPPE-Encryption-Policy',
+ 'ms_mppe_encryption_type' => 'MS-MPPE-Encryption-Type',
+ 'ms_mppe_encryption_types' => 'MS-MPPE-Encryption-Types',
+ 'ms_mppe_recv_key' => 'MS-MPPE-Recv-Key',
+ 'ms_mppe_send_key' => 'MS-MPPE-Send-Key',
+ 'ms_new_arap_password' => 'MS-New-ARAP-Password',
+ 'ms_old_arap_password' => 'MS-Old-ARAP-Password',
+ 'ms_primary_dns_server' => 'MS-Primary-DNS-Server',
+ 'ms_primary_nbns_server' => 'MS-Primary-NBNS-Server',
+ 'ms_ras_vendor' => 'MS-RAS-Vendor',
+ 'ms_ras_version' => 'MS-RAS-Version',
+ 'ms_secondary_dns_server' => 'MS-Secondary-DNS-Server',
+ 'ms_secondary_nbns_server' => 'MS-Secondary-NBNS-Server',
+ 'multi_link_flag' => 'Multi-Link-Flag',
+ 'nas_identifier' => 'NAS-Identifier',
+ 'nas_ip_address' => 'NAS-IP-Address',
+ 'nas_ipv6_address' => 'NAS-IPv6-Address',
+ 'nas_port' => 'NAS-Port',
+ 'nas_port_id' => 'NAS-Port-Id',
+ 'nas_port_type' => 'NAS-Port-Type',
+ 'nas_real_port' => 'NAS_Real_Port',
+ 'nas_real_poru' => 'NAS-Real-Port',
+ 'navini_avpair' => 'Navini-AVPair',
+ 'next_hop_dn' => 'next-hop-dn',
+ 'next_hop_ip' => 'next-hop-ip',
+ 'nn_data_rate' => 'NN-Data-Rate',
+ 'nn_data_rate_ceiling' => 'NN-Data-Rate-Ceiling',
+ 'nn_homenode' => 'NN-Homenode',
+ 'nn_homeservice' => 'NN-Homeservice',
+ 'nn_homeservice_name' => 'NN-Homeservice-Name',
+ 'no_such_attribute' => 'No-Such-Attribute',
+ 'nokia_charging_id' => 'Nokia-Charging-Id',
+ 'nokia_ggsn_ip_address' => 'Nokia-GGSN-IP-Address',
+ 'nokia_imsi' => 'Nokia-IMSI',
+ 'nokia_prepaid_ind' => 'Nokia-Prepaid-Ind',
+ 'nokia_sgsn_ip_address' => 'Nokia-SGSN-IP-Address',
+ 'nomadix_bw_down' => 'Nomadix-Bw-Down',
+ 'nomadix_bw_up' => 'Nomadix-Bw-Up',
+ 'nomadix_config_url' => 'Nomadix-Config-URL',
+ 'nomadix_endofsession' => 'Nomadix-EndofSession',
+ 'nomadix_expiration' => 'Nomadix-Expiration',
+ 'nomadix_goodbye_url' => 'Nomadix-Goodbye-URL',
+ 'nomadix_ip_upsell' => 'Nomadix-IP-Upsell',
+ 'nomadix_logoff_url' => 'Nomadix-Logoff-URL',
+ 'nomadix_maxbytesdown' => 'Nomadix-MaxBytesDown',
+ 'nomadix_maxbytesup' => 'Nomadix-MaxBytesUp',
+ 'nomadix_net_vlan' => 'Nomadix-Net-VLAN',
+ 'nomadix_subnet' => 'Nomadix-Subnet',
+ 'nomadix_url_redirection' => 'Nomadix-URL-Redirection',
+ 'ns_admin_privilege' => 'NS-Admin-Privilege',
+ 'ns_mta_md5_password' => 'NS-MTA-MD5-Password',
+ 'ns_primary_dns' => 'NS-Primary-DNS',
+ 'ns_primary_wins' => 'NS-Primary-WINS',
+ 'ns_secondary_dns' => 'NS-Secondary-DNS',
+ 'ns_secondary_wins' => 'NS-Secondary-WINS',
+ 'ns_user_group' => 'NS-User-Group',
+ 'ns_vsys_name' => 'NS-VSYS-Name',
+ 'nt_password' => 'NT-Password',
+ 'ntlm_user_name' => 'NTLM-User-Name',
+ 'old_password' => 'Old-Password',
+ 'outgoing_req_uri' => 'outgoing-req-uri',
+ 'packet_dst_port' => 'Packet-Dst-Port',
+ 'packet_type' => 'Packet-Type',
+ 'pam_auth' => 'Pam-Auth',
+ 'password' => 'Password',
+ 'password_retry' => 'Password-Retry',
+ 'police_burst' => 'Police_Burst',
+ 'police_bursu' => 'Police-Burst',
+ 'police_rate' => 'Police_Rate',
+ 'police_ratf' => 'Police-Rate',
+ 'pool_name' => 'Pool-Name',
+ 'port_limit' => 'Port-Limit',
+ 'port_message' => 'Port-Message',
+ 'post_auth_type' => 'Post-Auth-Type',
+ 'post_proxy_type' => 'Post-Proxy-Type',
+ 'postauth_type' => 'PostAuth-Type',
+ 'pppoe_motm' => 'PPPOE_MOTM',
+ 'pppoe_motn' => 'PPPOE-MOTM',
+ 'pppoe_url' => 'PPPOE_URL',
+ 'pppoe_urm' => 'PPPOE-URL',
+ 'pre_acct_type' => 'Pre-Acct-Type',
+ 'pre_proxy_type' => 'Pre-Proxy-Type',
+ 'prefix' => 'Prefix',
+ 'prev_hop_ip' => 'prev-hop-ip',
+ 'prev_hop_via' => 'prev-hop-via',
+ 'prompt' => 'Prompt',
+ 'propel_accelerate' => 'Propel-Accelerate',
+ 'propel_client_ip_address' => 'Propel-Client-IP-Address',
+ 'propel_client_nas_ip_add' => 'Propel-Client-NAS-IP-Address',
+ 'propel_client_source_id' => 'Propel-Client-Source-ID',
+ 'propel_dialed_digits' => 'Propel-Dialed-Digits',
+ 'proxy_state' => 'Proxy-State',
+ 'proxy_to_realm' => 'Proxy-To-Realm',
+ 'pvc_circuit_padding' => 'PVC_Circuit_Padding',
+ 'pvc_circuit_paddinh' => 'PVC-Circuit-Padding',
+ 'pvc_encapsulation_type' => 'PVC_Encapsulation_Type',
+ 'pvc_encapsulation_typf' => 'PVC-Encapsulation-Type',
+ 'pvc_profile_name' => 'PVC_Profile_Name',
+ 'pvc_profile_namf' => 'PVC-Profile-Name',
+ 'quintum_avpair' => 'Quintum-AVPair',
+ 'quintum_h323_billing_mod' => 'Quintum-h323-billing-model',
+ 'quintum_h323_call_origin' => 'Quintum-h323-call-origin',
+ 'quintum_h323_call_type' => 'Quintum-h323-call-type',
+ 'quintum_h323_conf_id' => 'Quintum-h323-conf-id',
+ 'quintum_h323_connect_tim' => 'Quintum-h323-connect-time',
+ 'quintum_h323_credit_amou' => 'Quintum-h323-credit-amount',
+ 'quintum_h323_credit_time' => 'Quintum-h323-credit-time',
+ 'quintum_h323_currency_ty' => 'Quintum-h323-currency-type',
+ 'quintum_h323_disconnect_' => 'Quintum-h323-disconnect-time',
+ 'quintum_h323_disconnecta' => 'Quintum-h323-disconnect-cause',
+ 'quintum_h323_gw_id' => 'Quintum-h323-gw-id',
+ 'quintum_h323_incoming_co' => 'Quintum-h323-incoming-conf-id',
+ 'quintum_h323_preferred_l' => 'Quintum-h323-preferred-lang',
+ 'quintum_h323_prompt_id' => 'Quintum-h323-prompt-id',
+ 'quintum_h323_redirect_ip' => 'Quintum-h323-redirect-ip-address',
+ 'quintum_h323_redirect_nu' => 'Quintum-h323-redirect-number',
+ 'quintum_h323_remote_addr' => 'Quintum-h323-remote-address',
+ 'quintum_h323_return_code' => 'Quintum-h323-return-code',
+ 'quintum_h323_setup_time' => 'Quintum-h323-setup-time',
+ 'quintum_h323_time_and_da' => 'Quintum-h323-time-and-day',
+ 'quintum_h323_voice_quali' => 'Quintum-h323-voice-quality',
+ 'quintum_nas_port' => 'Quintum-NAS-Port',
+ 'rate_limit_burst' => 'Rate_Limit_Burst',
+ 'rate_limit_bursu' => 'Rate-Limit-Burst',
+ 'rate_limit_rate' => 'Rate_Limit_Rate',
+ 'rate_limit_ratf' => 'Rate-Limit-Rate',
+ 'realm' => 'Realm',
+ 'redcreek_tunneled_dns_se' => 'RedCreek-Tunneled-DNS-Server',
+ 'redcreek_tunneled_domain' => 'RedCreek-Tunneled-DomainName',
+ 'redcreek_tunneled_gatewa' => 'RedCreek-Tunneled-Gateway',
+ 'redcreek_tunneled_hostna' => 'RedCreek-Tunneled-HostName',
+ 'redcreek_tunneled_ip_add' => 'RedCreek-Tunneled-IP-Addr',
+ 'redcreek_tunneled_ip_net' => 'RedCreek-Tunneled-IP-Netmask',
+ 'redcreek_tunneled_search' => 'RedCreek-Tunneled-Search-List',
+ 'redcreek_tunneled_wins_s' => 'RedCreek-Tunneled-WINS-Server1',
+ 'redcreek_tunneled_wins_t' => 'RedCreek-Tunneled-WINS-Server2',
+ 'replicate_to_realm' => 'Replicate-To-Realm',
+ 'reply_message' => 'Reply-Message',
+ 'response_packet_type' => 'Response-Packet-Type',
+ 'rewrite_rule' => 'Rewrite-Rule',
+ 'sdx_service_name' => 'Sdx-Service-Name',
+ 'sdx_session_volume_quota' => 'Sdx-Session-Volume-Quota',
+ 'sdx_tunnel_disconnect_ca' => 'Sdx-Tunnel-Disconnect-Cause-Info',
+ 'service_type' => 'Service-Type',
+ 'session' => 'Session',
+ 'session_error_code' => 'Session_Error_Code',
+ 'session_error_codf' => 'Session-Error-Code',
+ 'session_error_msg' => 'Session_Error_Msg',
+ 'session_error_msh' => 'Session-Error-Msg',
+ 'session_protocol' => 'session-protocol',
+ 'session_timeout' => 'Session-Timeout',
+ 'session_type' => 'Session-Type',
+ 'shasta_service_profile' => 'Shasta-Service-Profile',
+ 'shasta_user_privilege' => 'Shasta-User-Privilege',
+ 'shasta_vpn_name' => 'Shasta-VPN-Name',
+ 'shiva_acct_serv_switch' => 'Shiva-Acct-Serv-Switch',
+ 'shiva_called_number' => 'Shiva-Called-Number',
+ 'shiva_calling_number' => 'Shiva-Calling-Number',
+ 'shiva_compression_type' => 'Shiva-Compression-Type',
+ 'shiva_connect_reason' => 'Shiva-Connect-Reason',
+ 'shiva_customer_id' => 'Shiva-Customer-Id',
+ 'shiva_disconnect_reason' => 'Shiva-Disconnect-Reason',
+ 'shiva_event_flags' => 'Shiva-Event-Flags',
+ 'shiva_function' => 'Shiva-Function',
+ 'shiva_link_protocol' => 'Shiva-Link-Protocol',
+ 'shiva_link_speed' => 'Shiva-Link-Speed',
+ 'shiva_links_in_bundle' => 'Shiva-Links-In-Bundle',
+ 'shiva_network_protocols' => 'Shiva-Network-Protocols',
+ 'shiva_session_id' => 'Shiva-Session-Id',
+ 'shiva_type_of_service' => 'Shiva-Type-Of-Service',
+ 'shiva_user_attributes' => 'Shiva-User-Attributes',
+ 'simultaneous_use' => 'Simultaneous-Use',
+ 'sip_from' => 'Sip-From',
+ 'sip_hdr' => 'sip-hdr',
+ 'sip_method' => 'Sip-Method',
+ 'sip_to' => 'Sip-To',
+ 'sip_translated_request_u' => 'Sip-Translated-Request-URI',
+ 'smb_account_ctrl' => 'SMB-Account-CTRL',
+ 'smb_account_ctrl_text' => 'SMB-Account-CTRL-TEXT',
+ 'sonicwall_user_group' => 'SonicWall-User-Group',
+ 'sonicwall_user_privilege' => 'SonicWall-User-Privilege',
+ 'source_validation' => 'Source_Validation',
+ 'source_validatioo' => 'Source-Validation',
+ 'sql_group' => 'Sql-Group',
+ 'sql_user_name' => 'SQL-User-Name',
+ 'ss3_firewall_user_privil' => 'SS3-Firewall-User-Privilege',
+ 'st_acct_vc_connection_id' => 'ST-Acct-VC-Connection-Id',
+ 'st_policy_name' => 'ST-Policy-Name',
+ 'st_primary_dns_server' => 'ST-Primary-DNS-Server',
+ 'st_primary_nbns_server' => 'ST-Primary-NBNS-Server',
+ 'st_secondary_dns_server' => 'ST-Secondary-DNS-Server',
+ 'st_secondary_nbns_server' => 'ST-Secondary-NBNS-Server',
+ 'st_service_domain' => 'ST-Service-Domain',
+ 'st_service_name' => 'ST-Service-Name',
+ 'state' => 'State',
+ 'strip_user_name' => 'Strip-User-Name',
+ 'stripped_user_name' => 'Stripped-User-Name',
+ 'subscriber' => 'subscriber',
+ 'suffix' => 'Suffix',
+ 'telebit_accounting_info' => 'Telebit-Accounting-Info',
+ 'telebit_activate_command' => 'Telebit-Activate-Command',
+ 'telebit_login_command' => 'Telebit-Login-Command',
+ 'telebit_port_name' => 'Telebit-Port-Name',
+ 'termination_action' => 'Termination-Action',
+ 'termination_menu' => 'Termination-Menu',
+ 'trapeze_encryption_type' => 'Trapeze-Encryption-Type',
+ 'trapeze_end_date' => 'Trapeze-End-Date',
+ 'trapeze_mobility_profile' => 'Trapeze-Mobility-Profile',
+ 'trapeze_ssid' => 'Trapeze-SSID',
+ 'trapeze_start_date' => 'Trapeze-Start-Date',
+ 'trapeze_time_of_day' => 'Trapeze-Time-Of-Day',
+ 'trapeze_url' => 'Trapeze-URL',
+ 'trapeze_vlan_name' => 'Trapeze-VLAN-Name',
+ 'tty_level_max' => 'TTY_Level_Max',
+ 'tty_level_may' => 'TTY-Level-Max',
+ 'tty_level_start' => 'TTY_Level_Start',
+ 'tty_level_staru' => 'TTY-Level-Start',
+ 'tunnel_algorithm' => 'Tunnel_Algorithm',
+ 'tunnel_algorithn' => 'Tunnel-Algorithm',
+ 'tunnel_assignment_id' => 'Tunnel-Assignment-Id',
+ 'tunnel_client_auth_id' => 'Tunnel-Client-Auth-Id',
+ 'tunnel_client_endpoint' => 'Tunnel-Client-Endpoint',
+ 'tunnel_cmd_timeout' => 'Tunnel_Cmd_Timeout',
+ 'tunnel_cmd_timeouu' => 'Tunnel-Cmd-Timeout',
+ 'tunnel_connection_id' => 'Tunnel-Connection-Id',
+ 'tunnel_context' => 'Tunnel_Context',
+ 'tunnel_contexu' => 'Tunnel-Context',
+ 'tunnel_deadtime' => 'Tunnel_Deadtime',
+ 'tunnel_deadtimf' => 'Tunnel-Deadtime',
+ 'tunnel_dnis' => 'Tunnel_DNIS',
+ 'tunnel_dnit' => 'Tunnel-DNIS',
+ 'tunnel_domain' => 'Tunnel_Domain',
+ 'tunnel_domaio' => 'Tunnel-Domain',
+ 'tunnel_function' => 'Tunnel_Function',
+ 'tunnel_functioo' => 'Tunnel-Function',
+ 'tunnel_group' => 'Tunnel_Group',
+ 'tunnel_grouq' => 'Tunnel-Group',
+ 'tunnel_l2f_second_passwo' => 'Tunnel_L2F_Second_Password',
+ 'tunnel_l2f_second_passwp' => 'Tunnel-L2F-Second-Password',
+ 'tunnel_local_name' => 'Tunnel_Local_Name',
+ 'tunnel_local_namf' => 'Tunnel-Local-Name',
+ 'tunnel_max_sessions' => 'Tunnel_Max_Sessions',
+ 'tunnel_max_sessiont' => 'Tunnel-Max-Sessions',
+ 'tunnel_max_tunnels' => 'Tunnel_Max_Tunnels',
+ 'tunnel_max_tunnelt' => 'Tunnel-Max-Tunnels',
+ 'tunnel_medium_type' => 'Tunnel-Medium-Type',
+ 'tunnel_password' => 'Tunnel-Password',
+ 'tunnel_police_burst' => 'Tunnel_Police_Burst',
+ 'tunnel_police_bursu' => 'Tunnel-Police-Burst',
+ 'tunnel_police_rate' => 'Tunnel_Police_Rate',
+ 'tunnel_police_ratf' => 'Tunnel-Police-Rate',
+ 'tunnel_preference' => 'Tunnel-Preference',
+ 'tunnel_private_group_id' => 'Tunnel-Private-Group-Id',
+ 'tunnel_rate_limit_burst' => 'Tunnel_Rate_Limit_Burst',
+ 'tunnel_rate_limit_bursu' => 'Tunnel-Rate-Limit-Burst',
+ 'tunnel_rate_limit_rate' => 'Tunnel_Rate_Limit_Rate',
+ 'tunnel_rate_limit_ratf' => 'Tunnel-Rate-Limit-Rate',
+ 'tunnel_remote_name' => 'Tunnel_Remote_Name',
+ 'tunnel_remote_namf' => 'Tunnel-Remote-Name',
+ 'tunnel_retransmit' => 'Tunnel_Retransmit',
+ 'tunnel_retransmiu' => 'Tunnel-Retransmit',
+ 'tunnel_server_auth_id' => 'Tunnel-Server-Auth-Id',
+ 'tunnel_server_endpoint' => 'Tunnel-Server-Endpoint',
+ 'tunnel_session_auth' => 'Tunnel_Session_Auth',
+ 'tunnel_session_auth_ctx' => 'Tunnel_Session_Auth_Ctx',
+ 'tunnel_session_auth_cty' => 'Tunnel-Session-Auth-Ctx',
+ 'tunnel_session_auth_serv' => 'Tunnel_Session_Auth_Service_Grp',
+ 'tunnel_session_auth_serw' => 'Tunnel-Session-Auth-Service-Grp',
+ 'tunnel_session_auti' => 'Tunnel-Session-Auth',
+ 'tunnel_type' => 'Tunnel-Type',
+ 'tunnel_window' => 'Tunnel_Window',
+ 'tunnel_windox' => 'Tunnel-Window',
+ 'unix_ftp_gid' => 'Unix-FTP-GID',
+ 'unix_ftp_group_ids' => 'Unix-FTP-Group-Ids',
+ 'unix_ftp_group_names' => 'Unix-FTP-Group-Names',
+ 'unix_ftp_home' => 'Unix-FTP-Home',
+ 'unix_ftp_shell' => 'Unix-FTP-Shell',
+ 'unix_ftp_uid' => 'Unix-FTP-UID',
+ 'user_category' => 'User-Category',
+ 'user_name' => 'User-Name',
+ 'user_name_is_star' => 'User-Name-Is-Star',
+ 'user_password' => 'User-Password',
+ 'user_profile' => 'User-Profile',
+ 'user_service_type' => 'User-Service-Type',
+ 'usr_accm_type' => 'USR-ACCM-Type',
+ 'usr_acct_reason_code' => 'USR-Acct-Reason-Code',
+ 'usr_actual_voltage' => 'USR-Actual-Voltage',
+ 'usr_appletalk' => 'USR-Appletalk',
+ 'usr_appletalk_network_ra' => 'USR-Appletalk-Network-Range',
+ 'usr_at_call_input_filter' => 'USR-AT-Call-Input-Filter',
+ 'usr_at_call_output_filte' => 'USR-AT-Call-Output-Filter',
+ 'usr_at_input_filter' => 'USR-AT-Input-Filter',
+ 'usr_at_output_filter' => 'USR-AT-Output-Filter',
+ 'usr_at_rtmp_input_filter' => 'USR-AT-RTMP-Input-Filter',
+ 'usr_at_rtmp_output_filte' => 'USR-AT-RTMP-Output-Filter',
+ 'usr_at_zip_input_filter' => 'USR-AT-Zip-Input-Filter',
+ 'usr_at_zip_output_filter' => 'USR-AT-Zip-Output-Filter',
+ 'usr_auth_mode' => 'USR-Auth-Mode',
+ 'usr_back_channel_data_ra' => 'USR-Back-Channel-Data-Rate',
+ 'usr_bearer_capabilities' => 'USR-Bearer-Capabilities',
+ 'usr_block_error_count_li' => 'USR-Block-Error-Count-Limit',
+ 'usr_blocks_received' => 'USR-Blocks-Received',
+ 'usr_blocks_resent' => 'USR-Blocks-Resent',
+ 'usr_blocks_sent' => 'USR-Blocks-Sent',
+ 'usr_bridging' => 'USR-Bridging',
+ 'usr_call_arrival_in_gmt' => 'USR-Call-Arrival-in-GMT',
+ 'usr_call_arrival_time' => 'USR-Call-Arrival-Time',
+ 'usr_call_connect_in_gmt' => 'USR-Call-Connect-in-GMT',
+ 'usr_call_connecting_time' => 'USR-Call-Connecting-Time',
+ 'usr_call_end_date_time' => 'USR-Call-End-Date-Time',
+ 'usr_call_end_time' => 'USR-Call-End-Time',
+ 'usr_call_error_code' => 'USR-Call-Error-Code',
+ 'usr_call_event_code' => 'USR-Call-Event-Code',
+ 'usr_call_reference_numbe' => 'USR-Call-Reference-Number',
+ 'usr_call_start_date_time' => 'USR-Call-Start-Date-Time',
+ 'usr_call_terminate_in_gm' => 'USR-Call-Terminate-in-GMT',
+ 'usr_call_type' => 'USR-Call-Type',
+ 'usr_callback_type' => 'USR-Callback-Type',
+ 'usr_called_party_number' => 'USR-Called-Party-Number',
+ 'usr_calling_party_number' => 'USR-Calling-Party-Number',
+ 'usr_card_type' => 'USR-Card-Type',
+ 'usr_ccp_algorithm' => 'USR-CCP-Algorithm',
+ 'usr_cdma_call_reference_' => 'USR-CDMA-Call-Reference-Number',
+ 'usr_channel' => 'USR-Channel',
+ 'usr_channel_connected_to' => 'USR-Channel-Connected-To',
+ 'usr_channel_decrement' => 'USR-Channel-Decrement',
+ 'usr_channel_expansion' => 'USR-Channel-Expansion',
+ 'usr_characters_received' => 'USR-Characters-Received',
+ 'usr_characters_sent' => 'USR-Characters-Sent',
+ 'usr_chassis_call_channel' => 'USR-Chassis-Call-Channel',
+ 'usr_chassis_call_slot' => 'USR-Chassis-Call-Slot',
+ 'usr_chassis_call_span' => 'USR-Chassis-Call-Span',
+ 'usr_chassis_slot' => 'USR-Chassis-Slot',
+ 'usr_chassis_temp_thresho' => 'USR-Chassis-Temp-Threshold',
+ 'usr_chassis_temperature' => 'USR-Chassis-Temperature',
+ 'usr_chat_script_name' => 'USR-Chat-Script-Name',
+ 'usr_compression_algorith' => 'USR-Compression-Algorithm',
+ 'usr_compression_reset_mo' => 'USR-Compression-Reset-Mode',
+ 'usr_compression_type' => 'USR-Compression-Type',
+ 'usr_connect_speed' => 'USR-Connect-Speed',
+ 'usr_connect_term_reason' => 'USR-Connect-Term-Reason',
+ 'usr_connect_time' => 'USR-Connect-Time',
+ 'usr_connect_time_limit' => 'USR-Connect-Time-Limit',
+ 'usr_cusr_hat_script_rule' => 'USR-CUSR-hat-Script-Rules',
+ 'usr_default_dte_data_rat' => 'USR-Default-DTE-Data-Rate',
+ 'usr_device_connected_to' => 'USR-Device-Connected-To',
+ 'usr_disconnect_cause_ind' => 'USR-Disconnect-Cause-Indicator',
+ 'usr_dnis_reauthenticatio' => 'USR-DNIS-ReAuthentication',
+ 'usr_ds0' => 'USR-DS0',
+ 'usr_ds0s' => 'USR-DS0s',
+ 'usr_dte_data_idle_timout' => 'USR-DTE-Data-Idle-Timout',
+ 'usr_dte_ring_no_answer_l' => 'USR-DTE-Ring-No-Answer-Limit',
+ 'usr_dtr_false_timeout' => 'USR-DTR-False-Timeout',
+ 'usr_dtr_true_timeout' => 'USR-DTR-True-Timeout',
+ 'usr_end_time' => 'USR-End-Time',
+ 'usr_equalization_type' => 'USR-Equalization-Type',
+ 'usr_esn' => 'USR-ESN',
+ 'usr_et_bridge_call_outpu' => 'USR-ET-Bridge-Call-Output-Filte',
+ 'usr_et_bridge_input_filt' => 'USR-ET-Bridge-Input-Filter',
+ 'usr_et_bridge_output_fil' => 'USR-ET-Bridge-Output-Filter',
+ 'usr_event_date_time' => 'USR-Event-Date-Time',
+ 'usr_event_id' => 'USR-Event-Id',
+ 'usr_expansion_algorithm' => 'USR-Expansion-Algorithm',
+ 'usr_expected_voltage' => 'USR-Expected-Voltage',
+ 'usr_failure_to_connect_r' => 'USR-Failure-to-Connect-Reason',
+ 'usr_fallback_enabled' => 'USR-Fallback-Enabled',
+ 'usr_fallback_limit' => 'USR-Fallback-Limit',
+ 'usr_filter_zones' => 'USR-Filter-Zones',
+ 'usr_final_rx_link_data_r' => 'USR-Final-Rx-Link-Data-Rate',
+ 'usr_final_tx_link_data_r' => 'USR-Final-Tx-Link-Data-Rate',
+ 'usr_framed_ip_address_po' => 'USR-Framed_IP_Address_Pool_Name',
+ 'usr_framed_ipx_route' => 'USR-Framed-IPX-Route',
+ 'usr_gateway_ip_address' => 'USR-Gateway-IP-Address',
+ 'usr_harc_disconnect_code' => 'USR-HARC-Disconnect-Code',
+ 'usr_host_type' => 'USR-Host-Type',
+ 'usr_ids0_call_type' => 'USR-IDS0-Call-Type',
+ 'usr_igmp_maximum_respons' => 'USR-IGMP-Maximum-Response-Time',
+ 'usr_igmp_query_interval' => 'USR-IGMP-Query-Interval',
+ 'usr_igmp_robustness' => 'USR-IGMP-Robustness',
+ 'usr_igmp_routing' => 'USR-IGMP-Routing',
+ 'usr_igmp_version' => 'USR-IGMP-Version',
+ 'usr_imsi' => 'USR-IMSI',
+ 'usr_initial_rx_link_data' => 'USR-Initial-Rx-Link-Data-Rate',
+ 'usr_initial_tx_link_data' => 'USR-Initial-Tx-Link-Data-Rate',
+ 'usr_interface_index' => 'USR-Interface-Index',
+ 'usr_ip' => 'USR-IP',
+ 'usr_ip_call_input_filter' => 'USR-IP-Call-Input-Filter',
+ 'usr_ip_call_output_filte' => 'USR-IP-Call-Output-Filter',
+ 'usr_ip_default_route_opt' => 'USR-IP-Default-Route-Option',
+ 'usr_ip_rip_input_filter' => 'USR-IP-RIP-Input-Filter',
+ 'usr_ip_rip_output_filter' => 'USR-IP-RIP-Output-Filter',
+ 'usr_ip_rip_policies' => 'USR-IP-RIP-Policies',
+ 'usr_ip_rip_simple_auth_p' => 'USR-IP-RIP-Simple-Auth-Password',
+ 'usr_ip_saa_filter' => 'USR-IP-SAA-Filter',
+ 'usr_ipx' => 'USR-IPX',
+ 'usr_ipx_call_input_filte' => 'USR-IPX-Call-Input-Filter',
+ 'usr_ipx_call_output_filt' => 'USR-IPX-Call-Output-Filter',
+ 'usr_ipx_rip_input_filter' => 'USR-IPX-RIP-Input-Filter',
+ 'usr_ipx_rip_output_filte' => 'USR-IPX-RIP-Output-Filter',
+ 'usr_ipx_routing' => 'USR-IPX-Routing',
+ 'usr_ipx_wan' => 'USR-IPX-WAN',
+ 'usr_iwf_call_identifier' => 'USR-IWF-Call-Identifier',
+ 'usr_iwf_ip_address' => 'USR-IWF-IP-Address',
+ 'usr_keypress_timeout' => 'USR-Keypress-Timeout',
+ 'usr_last_callers_number_' => 'USR-Last-Callers-Number-ANI',
+ 'usr_last_number_dialed_i' => 'USR-Last-Number-Dialed-In-DNIS',
+ 'usr_last_number_dialed_o' => 'USR-Last-Number-Dialed-Out',
+ 'usr_line_reversals' => 'USR-Line-Reversals',
+ 'usr_local_framed_ip_addr' => 'USR-Local-Framed-IP-Addr',
+ 'usr_local_ip_address' => 'USR-Local-IP-Address',
+ 'usr_log_filter_packets' => 'USR-Log-Filter-Packets',
+ 'usr_max_channels' => 'USR-Max-Channels',
+ 'usr_mbi_ct_bchannel_used' => 'USR-Mbi_Ct_BChannel_Used',
+ 'usr_mbi_ct_pri_card_slot' => 'USR-Mbi_Ct_PRI_Card_Slot',
+ 'usr_mbi_ct_pri_card_span' => 'USR-Mbi_Ct_PRI_Card_Span_Line',
+ 'usr_mbi_ct_tdm_time_slot' => 'USR-Mbi_Ct_TDM_Time_Slot',
+ 'usr_mic' => 'USR-MIC',
+ 'usr_min_compression_size' => 'USR-Min-Compression-Size',
+ 'usr_mobile_ip_address' => 'USR-Mobile-IP-Address',
+ 'usr_mobile_numbytes_rxed' => 'USR-Mobile-NumBytes-Rxed',
+ 'usr_mobile_numbytes_txed' => 'USR-Mobile-NumBytes-Txed',
+ 'usr_mobileip_home_agent_' => 'USR-MobileIP-Home-Agent-Address',
+ 'usr_modem_group' => 'USR-Modem-Group',
+ 'usr_modem_setup_time' => 'USR-Modem-Setup-Time',
+ 'usr_modem_training_time' => 'USR-Modem-Training-Time',
+ 'usr_modulation_type' => 'USR-Modulation-Type',
+ 'usr_mp_edo' => 'USR-MP-EDO',
+ 'usr_mp_edo_hiper' => 'USR-MP-EDO-HIPER',
+ 'usr_mp_mrru' => 'USR-MP-MRRU',
+ 'usr_mpip_tunnel_originat' => 'USR-MPIP-Tunnel-Originator',
+ 'usr_multicast_forwarding' => 'USR-Multicast-Forwarding',
+ 'usr_multicast_proxy' => 'USR-Multicast-Proxy',
+ 'usr_multicast_receive' => 'USR-Multicast-Receive',
+ 'usr_nas_type' => 'USR-NAS-Type',
+ 'usr_nfas_id' => 'USR-NFAS-ID',
+ 'usr_num_fax_pages_proces' => 'USR-Num-Fax-Pages-Processed',
+ 'usr_number_of_blers' => 'USR-Number-of-Blers',
+ 'usr_number_of_characters' => 'USR-Number-Of-Characters-Lost',
+ 'usr_number_of_fallbacks' => 'USR-Number-of-Fallbacks',
+ 'usr_number_of_link_naks' => 'USR-Number-of-Link-NAKs',
+ 'usr_number_of_link_timeo' => 'USR-Number-of-Link-Timeouts',
+ 'usr_number_of_rings_limi' => 'USR-Number-of-Rings-Limit',
+ 'usr_number_of_upshifts' => 'USR-Number-of-Upshifts',
+ 'usr_orig_nas_type' => 'USR-Orig-NAS-Type',
+ 'usr_originate_answer_mod' => 'USR-Originate-Answer-Mode',
+ 'usr_ospf_addressless_ind' => 'USR-OSPF-Addressless-Index',
+ 'usr_packet_bus_session' => 'USR-Packet-Bus-Session',
+ 'usr_physical_state' => 'USR-Physical-State',
+ 'usr_port_tap' => 'USR-Port-Tap',
+ 'usr_port_tap_address' => 'USR-Port-Tap-Address',
+ 'usr_port_tap_facility' => 'USR-Port-Tap-Facility',
+ 'usr_port_tap_format' => 'USR-Port-Tap-Format',
+ 'usr_port_tap_output' => 'USR-Port-Tap-Output',
+ 'usr_port_tap_priority' => 'USR-Port-Tap-Priority',
+ 'usr_power_supply_number' => 'USR-Power-Supply-Number',
+ 'usr_primary_dns_server' => 'USR-Primary_DNS_Server',
+ 'usr_primary_nbns_server' => 'USR-Primary_NBNS_Server',
+ 'usr_pw_cutoff' => 'USR-PW_Cutoff',
+ 'usr_pw_framed_routing_v2' => 'USR-PW_Framed_Routing_V2',
+ 'usr_pw_index' => 'USR-PW_Index',
+ 'usr_pw_packet' => 'USR-PW_Packet',
+ 'usr_pw_tunnel_authentica' => 'USR-PW_Tunnel_Authentication',
+ 'usr_pw_usr_ifilter_ip' => 'USR-PW_USR_IFilter_IP',
+ 'usr_pw_usr_ifilter_ipx' => 'USR-PW_USR_IFilter_IPX',
+ 'usr_pw_usr_ofilter_ip' => 'USR-PW_USR_OFilter_IP',
+ 'usr_pw_usr_ofilter_ipx' => 'USR-PW_USR_OFilter_IPX',
+ 'usr_pw_usr_ofilter_sap' => 'USR-PW_USR_OFilter_SAP',
+ 'usr_pw_vpn_gateway' => 'USR-PW_VPN_Gateway',
+ 'usr_pw_vpn_id' => 'USR-PW_VPN_ID',
+ 'usr_pw_vpn_name' => 'USR-PW_VPN_Name',
+ 'usr_pw_vpn_neighbor' => 'USR-PW_VPN_Neighbor',
+ 'usr_q931_call_reference_' => 'USR-Q931-Call-Reference-Value',
+ 'usr_rad_dvmrp_metric' => 'USR-Rad-Dvmrp-Metric',
+ 'usr_rad_location_type' => 'USR-Rad-Location-Type',
+ 'usr_rad_multicast_routin' => 'USR-Rad-Multicast-Routing-Ttl',
+ 'usr_rad_multicast_routio' => 'USR-Rad-Multicast-Routing-RtLim',
+ 'usr_rad_multicast_routip' => 'USR-Rad-Multicast-Routing-Proto',
+ 'usr_rad_multicast_routiq' => 'USR-Rad-Multicast-Routing-Bound',
+ 'usr_re_chap_timeout' => 'USR-Re-Chap-Timeout',
+ 'usr_receive_acc_map' => 'USR-Receive-Acc-Map',
+ 'usr_reply_script1' => 'USR-Reply-Script1',
+ 'usr_reply_script2' => 'USR-Reply-Script2',
+ 'usr_reply_script3' => 'USR-Reply-Script3',
+ 'usr_reply_script4' => 'USR-Reply-Script4',
+ 'usr_reply_script5' => 'USR-Reply-Script5',
+ 'usr_reply_script6' => 'USR-Reply-Script6',
+ 'usr_request_type' => 'USR-Request-Type',
+ 'usr_retrains_granted' => 'USR-Retrains-Granted',
+ 'usr_retrains_requested' => 'USR-Retrains-Requested',
+ 'usr_rmmie_firmware_build' => 'USR-RMMIE-Firmware-Build-Date',
+ 'usr_rmmie_firmware_versi' => 'USR-RMMIE-Firmware-Version',
+ 'usr_rmmie_last_update_ev' => 'USR-RMMIE-Last-Update-Event',
+ 'usr_rmmie_last_update_ti' => 'USR-RMMIE-Last-Update-Time',
+ 'usr_rmmie_manufacturer_i' => 'USR-RMMIE-Manufacturer-ID',
+ 'usr_rmmie_num_of_updates' => 'USR-RMMIE-Num-Of-Updates',
+ 'usr_rmmie_planned_discon' => 'USR-RMMIE-Planned-Disconnect',
+ 'usr_rmmie_product_code' => 'USR-RMMIE-Product-Code',
+ 'usr_rmmie_pwrlvl_farecho' => 'USR-RMMIE-PwrLvl-FarEcho-Canc',
+ 'usr_rmmie_pwrlvl_nearech' => 'USR-RMMIE-PwrLvl-NearEcho-Canc',
+ 'usr_rmmie_pwrlvl_noise_l' => 'USR-RMMIE-PwrLvl-Noise-Lvl',
+ 'usr_rmmie_pwrlvl_xmit_lv' => 'USR-RMMIE-PwrLvl-Xmit-Lvl',
+ 'usr_rmmie_rcv_pwrlvl_330' => 'USR-RMMIE-Rcv-PwrLvl-3300Hz',
+ 'usr_rmmie_rcv_pwrlvl_375' => 'USR-RMMIE-Rcv-PwrLvl-3750Hz',
+ 'usr_rmmie_rcv_tot_pwrlvl' => 'USR-RMMIE-Rcv-Tot-PwrLvl',
+ 'usr_rmmie_serial_number' => 'USR-RMMIE-Serial-Number',
+ 'usr_rmmie_status' => 'USR-RMMIE-Status',
+ 'usr_rmmie_x2_status' => 'USR-RMMIE-x2-Status',
+ 'usr_routing_protocol' => 'USR-Routing-Protocol',
+ 'usr_sap_filter_in' => 'USR-SAP-Filter-In',
+ 'usr_secondary_dns_server' => 'USR-Secondary_DNS_Server',
+ 'usr_secondary_nbns_serve' => 'USR-Secondary_NBNS_Server',
+ 'usr_security_login_limit' => 'USR-Security-Login-Limit',
+ 'usr_security_resp_limit' => 'USR-Security-Resp-Limit',
+ 'usr_send_name' => 'USR-Send-Name',
+ 'usr_send_password' => 'USR-Send-Password',
+ 'usr_send_script1' => 'USR-Send-Script1',
+ 'usr_send_script2' => 'USR-Send-Script2',
+ 'usr_send_script3' => 'USR-Send-Script3',
+ 'usr_send_script4' => 'USR-Send-Script4',
+ 'usr_send_script5' => 'USR-Send-Script5',
+ 'usr_send_script6' => 'USR-Send-Script6',
+ 'usr_server_time' => 'USR-Server-Time',
+ 'usr_service_option' => 'USR-Service-Option',
+ 'usr_simplified_mnp_level' => 'USR-Simplified-MNP-Levels',
+ 'usr_simplified_v42bis_us' => 'USR-Simplified-V42bis-Usage',
+ 'usr_slot_connected_to' => 'USR-Slot-Connected-To',
+ 'usr_speed_of_connection' => 'USR-Speed-Of-Connection',
+ 'usr_spoofing' => 'USR-Spoofing',
+ 'usr_start_time' => 'USR-Start-Time',
+ 'usr_supports_tags' => 'USR-Supports-Tags',
+ 'usr_sync_async_mode' => 'USR-Sync-Async-Mode',
+ 'usr_syslog_tap' => 'USR-Syslog-Tap',
+ 'usr_terminal_type' => 'USR-Terminal-Type',
+ 'usr_transmit_acc_map' => 'USR-Transmit-Acc-Map',
+ 'usr_tunnel_auth_hostname' => 'USR-Tunnel-Auth-Hostname',
+ 'usr_tunnel_security' => 'USR-Tunnel-Security',
+ 'usr_tunnel_switch_endpoi' => 'USR-Tunnel-Switch-Endpoint',
+ 'usr_tunneled_mlpp' => 'USR-Tunneled-MLPP',
+ 'usr_unauthenticated_time' => 'USR-Unauthenticated-Time',
+ 'usr_vpn_encrypter' => 'USR-VPN-Encrypter',
+ 'usr_vpn_gw_location_id' => 'USR-VPN-GW-Location-Id',
+ 'usr_vts_session_key' => 'USR-VTS-Session-Key',
+ 'vendor_specific' => 'Vendor-Specific',
+ 'versanet_termination_cau' => 'Versanet-Termination-Cause',
+ 'vnc_pppoe_cbq_rx' => 'VNC-PPPoE-CBQ-RX',
+ 'vnc_pppoe_cbq_rx_fallbac' => 'VNC-PPPoE-CBQ-RX-Fallback',
+ 'vnc_pppoe_cbq_tx' => 'VNC-PPPoE-CBQ-TX',
+ 'vnc_pppoe_cbq_tx_fallbac' => 'VNC-PPPoE-CBQ-TX-Fallback',
+ 'vnc_splash' => 'VNC-Splash',
+ 'wispr_bandwidth_max_down' => 'WISPr-Bandwidth-Max-Down',
+ 'wispr_bandwidth_max_up' => 'WISPr-Bandwidth-Max-Up',
+ 'wispr_bandwidth_min_down' => 'WISPr-Bandwidth-Min-Down',
+ 'wispr_bandwidth_min_up' => 'WISPr-Bandwidth-Min-Up',
+ 'wispr_billing_class_of_s' => 'WISPr-Billing-Class-Of-Service',
+ 'wispr_location_id' => 'WISPr-Location-ID',
+ 'wispr_location_name' => 'WISPr-Location-Name',
+ 'wispr_logoff_url' => 'WISPr-Logoff-URL',
+ 'wispr_redirection_url' => 'WISPr-Redirection-URL',
+ 'wispr_session_terminate_' => 'WISPr-Session-Terminate-Time',
+ 'wispr_session_terminatea' => 'WISPr-Session-Terminate-End-Of-Day',
+ 'x_ascend_add_seconds' => 'X-Ascend-Add-Seconds',
+ 'x_ascend_ara_pw' => 'X-Ascend-Ara-PW',
+ 'x_ascend_assign_ip_clien' => 'X-Ascend-Assign-IP-Client',
+ 'x_ascend_assign_ip_globa' => 'X-Ascend-Assign-IP-Global-Pool',
+ 'x_ascend_assign_ip_pool' => 'X-Ascend-Assign-IP-Pool',
+ 'x_ascend_assign_ip_serve' => 'X-Ascend-Assign-IP-Server',
+ 'x_ascend_authen_alias' => 'X-Ascend-Authen-Alias',
+ 'x_ascend_backup' => 'X-Ascend-Backup',
+ 'x_ascend_bacp_enable' => 'X-Ascend-BACP-Enable',
+ 'x_ascend_base_channel_co' => 'X-Ascend-Base-Channel-Count',
+ 'x_ascend_billing_number' => 'X-Ascend-Billing-Number',
+ 'x_ascend_bridge' => 'X-Ascend-Bridge',
+ 'x_ascend_bridge_address' => 'X-Ascend-Bridge-Address',
+ 'x_ascend_call_attempt_li' => 'X-Ascend-Call-Attempt-Limit',
+ 'x_ascend_call_block_dura' => 'X-Ascend-Call-Block-Duration',
+ 'x_ascend_call_by_call' => 'X-Ascend-Call-By-Call',
+ 'x_ascend_call_filter' => 'X-Ascend-Call-Filter',
+ 'x_ascend_call_type' => 'X-Ascend-Call-Type',
+ 'x_ascend_callback' => 'X-Ascend-Callback',
+ 'x_ascend_client_assign_d' => 'X-Ascend-Client-Assign-DNS',
+ 'x_ascend_client_gateway' => 'X-Ascend-Client-Gateway',
+ 'x_ascend_client_primary_' => 'X-Ascend-Client-Primary-DNS',
+ 'x_ascend_client_secondar' => 'X-Ascend-Client-Secondary-DNS',
+ 'x_ascend_connect_progres' => 'X-Ascend-Connect-Progress',
+ 'x_ascend_data_filter' => 'X-Ascend-Data-Filter',
+ 'x_ascend_data_rate' => 'X-Ascend-Data-Rate',
+ 'x_ascend_data_svc' => 'X-Ascend-Data-Svc',
+ 'x_ascend_dba_monitor' => 'X-Ascend-DBA-Monitor',
+ 'x_ascend_dec_channel_cou' => 'X-Ascend-Dec-Channel-Count',
+ 'x_ascend_dhcp_maximum_le' => 'X-Ascend-DHCP-Maximum-Leases',
+ 'x_ascend_dhcp_pool_numbe' => 'X-Ascend-DHCP-Pool-Number',
+ 'x_ascend_dhcp_reply' => 'X-Ascend-DHCP-Reply',
+ 'x_ascend_dial_number' => 'X-Ascend-Dial-Number',
+ 'x_ascend_dialout_allowed' => 'X-Ascend-Dialout-Allowed',
+ 'x_ascend_disconnect_caus' => 'X-Ascend-Disconnect-Cause',
+ 'x_ascend_event_type' => 'X-Ascend-Event-Type',
+ 'x_ascend_expect_callback' => 'X-Ascend-Expect-Callback',
+ 'x_ascend_fcp_parameter' => 'X-Ascend-FCP-Parameter',
+ 'x_ascend_first_dest' => 'X-Ascend-First-Dest',
+ 'x_ascend_force_56' => 'X-Ascend-Force-56',
+ 'x_ascend_fr_circuit_name' => 'X-Ascend-FR-Circuit-Name',
+ 'x_ascend_fr_dce_n392' => 'X-Ascend-FR-DCE-N392',
+ 'x_ascend_fr_dce_n393' => 'X-Ascend-FR-DCE-N393',
+ 'x_ascend_fr_direct' => 'X-Ascend-FR-Direct',
+ 'x_ascend_fr_direct_dlci' => 'X-Ascend-FR-Direct-DLCI',
+ 'x_ascend_fr_direct_profi' => 'X-Ascend-FR-Direct-Profile',
+ 'x_ascend_fr_dlci' => 'X-Ascend-FR-DLCI',
+ 'x_ascend_fr_dte_n392' => 'X-Ascend-FR-DTE-N392',
+ 'x_ascend_fr_dte_n393' => 'X-Ascend-FR-DTE-N393',
+ 'x_ascend_fr_link_mgt' => 'X-Ascend-FR-Link-Mgt',
+ 'x_ascend_fr_linkup' => 'X-Ascend-FR-LinkUp',
+ 'x_ascend_fr_n391' => 'X-Ascend-FR-N391',
+ 'x_ascend_fr_nailed_grp' => 'X-Ascend-FR-Nailed-Grp',
+ 'x_ascend_fr_profile_name' => 'X-Ascend-FR-Profile-Name',
+ 'x_ascend_fr_t391' => 'X-Ascend-FR-T391',
+ 'x_ascend_fr_t392' => 'X-Ascend-FR-T392',
+ 'x_ascend_fr_type' => 'X-Ascend-FR-Type',
+ 'x_ascend_ft1_caller' => 'X-Ascend-FT1-Caller',
+ 'x_ascend_group' => 'X-Ascend-Group',
+ 'x_ascend_handle_ipx' => 'X-Ascend-Handle-IPX',
+ 'x_ascend_history_weigh_t' => 'X-Ascend-History-Weigh-Type',
+ 'x_ascend_home_agent_ip_a' => 'X-Ascend-Home-Agent-IP-Addr',
+ 'x_ascend_home_agent_pass' => 'X-Ascend-Home-Agent-Password',
+ 'x_ascend_home_agent_udp_' => 'X-Ascend-Home-Agent-UDP-Port',
+ 'x_ascend_home_network_na' => 'X-Ascend-Home-Network-Name',
+ 'x_ascend_host_info' => 'X-Ascend-Host-Info',
+ 'x_ascend_idle_limit' => 'X-Ascend-Idle-Limit',
+ 'x_ascend_if_netmask' => 'X-Ascend-IF-Netmask',
+ 'x_ascend_inc_channel_cou' => 'X-Ascend-Inc-Channel-Count',
+ 'x_ascend_ip_direct' => 'X-Ascend-IP-Direct',
+ 'x_ascend_ip_pool_definit' => 'X-Ascend-IP-Pool-Definition',
+ 'x_ascend_ipx_alias' => 'X-Ascend-IPX-Alias',
+ 'x_ascend_ipx_node_addr' => 'X-Ascend-IPX-Node-Addr',
+ 'x_ascend_ipx_peer_mode' => 'X-Ascend-IPX-Peer-Mode',
+ 'x_ascend_ipx_route' => 'X-Ascend-IPX-Route',
+ 'x_ascend_link_compressio' => 'X-Ascend-Link-Compression',
+ 'x_ascend_maximum_call_du' => 'X-Ascend-Maximum-Call-Duration',
+ 'x_ascend_maximum_channel' => 'X-Ascend-Maximum-Channels',
+ 'x_ascend_maximum_time' => 'X-Ascend-Maximum-Time',
+ 'x_ascend_menu_item' => 'X-Ascend-Menu-Item',
+ 'x_ascend_menu_selector' => 'X-Ascend-Menu-Selector',
+ 'x_ascend_metric' => 'X-Ascend-Metric',
+ 'x_ascend_minimum_channel' => 'X-Ascend-Minimum-Channels',
+ 'x_ascend_modem_portno' => 'X-Ascend-Modem-PortNo',
+ 'x_ascend_modem_shelfno' => 'X-Ascend-Modem-ShelfNo',
+ 'x_ascend_modem_slotno' => 'X-Ascend-Modem-SlotNo',
+ 'x_ascend_mpp_idle_percen' => 'X-Ascend-MPP-Idle-Percent',
+ 'x_ascend_multicast_clien' => 'X-Ascend-Multicast-Client',
+ 'x_ascend_multicast_rate_' => 'X-Ascend-Multicast-Rate-Limit',
+ 'x_ascend_multilink_id' => 'X-Ascend-Multilink-ID',
+ 'x_ascend_netware_timeout' => 'X-Ascend-Netware-timeout',
+ 'x_ascend_num_in_multilin' => 'X-Ascend-Num-In-Multilink',
+ 'x_ascend_number_sessions' => 'X-Ascend-Number-Sessions',
+ 'x_ascend_ppp_address' => 'X-Ascend-PPP-Address',
+ 'x_ascend_ppp_async_map' => 'X-Ascend-PPP-Async-Map',
+ 'x_ascend_ppp_vj_1172' => 'X-Ascend-PPP-VJ-1172',
+ 'x_ascend_ppp_vj_slot_com' => 'X-Ascend-PPP-VJ-Slot-Comp',
+ 'x_ascend_pre_input_octet' => 'X-Ascend-Pre-Input-Octets',
+ 'x_ascend_pre_input_packe' => 'X-Ascend-Pre-Input-Packets',
+ 'x_ascend_pre_output_octe' => 'X-Ascend-Pre-Output-Octets',
+ 'x_ascend_pre_output_pack' => 'X-Ascend-Pre-Output-Packets',
+ 'x_ascend_preempt_limit' => 'X-Ascend-Preempt-Limit',
+ 'x_ascend_presession_time' => 'X-Ascend-PreSession-Time',
+ 'x_ascend_pri_number_type' => 'X-Ascend-PRI-Number-Type',
+ 'x_ascend_primary_home_ag' => 'X-Ascend-Primary-Home-Agent',
+ 'x_ascend_pw_lifetime' => 'X-Ascend-PW-Lifetime',
+ 'x_ascend_pw_warntime' => 'X-Ascend-PW-Warntime',
+ 'x_ascend_receive_secret' => 'X-Ascend-Receive-Secret',
+ 'x_ascend_remote_addr' => 'X-Ascend-Remote-Addr',
+ 'x_ascend_remove_seconds' => 'X-Ascend-Remove-Seconds',
+ 'x_ascend_require_auth' => 'X-Ascend-Require-Auth',
+ 'x_ascend_route_ip' => 'X-Ascend-Route-IP',
+ 'x_ascend_route_ipx' => 'X-Ascend-Route-IPX',
+ 'x_ascend_secondary_home_' => 'X-Ascend-Secondary-Home-Agent',
+ 'x_ascend_seconds_of_hist' => 'X-Ascend-Seconds-Of-History',
+ 'x_ascend_send_auth' => 'X-Ascend-Send-Auth',
+ 'x_ascend_send_passwd' => 'X-Ascend-Send-Passwd',
+ 'x_ascend_send_secret' => 'X-Ascend-Send-Secret',
+ 'x_ascend_session_svr_key' => 'X-Ascend-Session-Svr-Key',
+ 'x_ascend_shared_profile_' => 'X-Ascend-Shared-Profile-Enable',
+ 'x_ascend_target_util' => 'X-Ascend-Target-Util',
+ 'x_ascend_temporary_rtes' => 'X-Ascend-Temporary-Rtes',
+ 'x_ascend_third_prompt' => 'X-Ascend-Third-Prompt',
+ 'x_ascend_token_expiry' => 'X-Ascend-Token-Expiry',
+ 'x_ascend_token_idle' => 'X-Ascend-Token-Idle',
+ 'x_ascend_token_immediate' => 'X-Ascend-Token-Immediate',
+ 'x_ascend_transit_number' => 'X-Ascend-Transit-Number',
+ 'x_ascend_ts_idle_limit' => 'X-Ascend-TS-Idle-Limit',
+ 'x_ascend_ts_idle_mode' => 'X-Ascend-TS-Idle-Mode',
+ 'x_ascend_tunneling_proto' => 'X-Ascend-Tunneling-Protocol',
+ 'x_ascend_user_acct_base' => 'X-Ascend-User-Acct-Base',
+ 'x_ascend_user_acct_host' => 'X-Ascend-User-Acct-Host',
+ 'x_ascend_user_acct_key' => 'X-Ascend-User-Acct-Key',
+ 'x_ascend_user_acct_port' => 'X-Ascend-User-Acct-Port',
+ 'x_ascend_user_acct_time' => 'X-Ascend-User-Acct-Time',
+ 'x_ascend_user_acct_type' => 'X-Ascend-User-Acct-Type',
+ 'x_ascend_xmit_rate' => 'X-Ascend-Xmit-Rate',
+ 'xedia_address_pool' => 'Xedia-Address-Pool',
+ 'xedia_client_access_netw' => 'Xedia-Client-Access-Network',
+ 'xedia_dns_server' => 'Xedia-DNS-Server',
+ 'xedia_netbios_server' => 'Xedia-NetBios-Server',
+ 'xedia_ppp_echo_interval' => 'Xedia-PPP-Echo-Interval',
+ 'xedia_ssh_privileges' => 'Xedia-SSH-Privileges',
+
+ #NETC.NET.AU (RADIATOR?)
+ 'authentication_type' => 'Authentication-Type',
+
+ #wtxs (dunno)
+ #'radius_operator' => 'Radius-Operator',
+
+);
+
+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/rate.pm b/FS/FS/rate.pm
new file mode 100644
index 0000000..793846a
--- /dev/null
+++ b/FS/FS/rate.pm
@@ -0,0 +1,422 @@
+package FS::rate;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Record qw( qsearch qsearchs dbh fields );
+use FS::rate_detail;
+
+@ISA = qw(FS::Record);
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::rate - Object methods for rate records
+
+=head1 SYNOPSIS
+
+ use FS::rate;
+
+ $record = new FS::rate \%hash;
+ $record = new FS::rate { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rate object represents an rate plan. FS::rate inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item ratenum - primary key
+
+=item ratename
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new rate plan. To add the rate plan 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 { 'rate'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+Currently available options are: I<rate_detail>
+
+If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
+objects will have their ratenum field set and will be inserted after this
+record.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+
+ 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->check;
+ return $error if $error;
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'rate_detail'} ) {
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progressbar foo
+
+ foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
+
+ $rate_detail->ratenum($self->ratenum);
+ $error = $rate_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'job'} ) {
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $options{'job'}->update_statustext(
+ int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $last = time;
+ }
+ }
+
+ }
+ }
+
+ $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 [ , OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+Currently available options are: I<rate_detail>
+
+If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
+objects will have their ratenum field set and will be inserted after this
+record. Any existing rate_detail records associated with this record will be
+deleted.
+
+=cut
+
+sub replace {
+ my ($new, $old) = (shift, shift);
+ my %options = @_;
+
+ 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 @old_rate_detail = ();
+# @old_rate_detail = $old->rate_detail if $options{'rate_detail'};
+
+ my $error = $new->SUPER::replace($old);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+# foreach my $old_rate_detail ( @old_rate_detail ) {
+#
+# my $error = $old_rate_detail->delete;
+# if ($error) {
+# $dbh->rollback if $oldAutoCommit;
+# return $error;
+# }
+#
+# if ( $options{'job'} ) {
+# $num++;
+# if ( time - $min_sec > $last ) {
+# my $error = $options{'job'}->update_statustext(
+# int( 50 * $num / scalar( @old_rate_detail ) )
+# );
+# if ( $error ) {
+# $dbh->rollback if $oldAutoCommit;
+# return $error;
+# }
+# $last = time;
+# }
+# }
+#
+# }
+ if ( $options{'rate_detail'} ) {
+ my $sth = $dbh->prepare('DELETE FROM rate_detail WHERE ratenum = ?') or do {
+ $dbh->rollback if $oldAutoCommit;
+ return $dbh->errstr;
+ };
+
+ $sth->execute($old->ratenum) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return $sth->errstr;
+ };
+
+ my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+# $num = 0;
+ foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
+
+ $rate_detail->ratenum($new->ratenum);
+ $error = $rate_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'job'} ) {
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $options{'job'}->update_statustext(
+ int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $last = time;
+ }
+ }
+
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid rate plan. 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('ratenum')
+ || $self->ut_text('ratename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF
+
+Returns the rate detail (see L<FS::rate_detail>) for this rate to the
+specificed destination, or the empty string if no rate can be found for
+the given destination.
+
+Destination can be specified as an FS::rate_detail object or regionnum
+(see L<FS::rate_detail>), or as a hashref with two keys: I<countrycode>
+and I<phonenum>.
+
+=cut
+
+sub dest_detail {
+ my $self = shift;
+
+ my $regionnum;
+ if ( ref($_[0]) eq 'HASH' ) {
+
+ my $countrycode = $_[0]->{'countrycode'};
+ my $phonenum = $_[0]->{'phonenum'};
+
+ #find a rate prefix, first look at most specific, then fewer digits,
+ # finally trying the country code only
+ my $rate_prefix = '';
+ for my $len ( reverse(1..10) ) {
+ $rate_prefix = qsearchs('rate_prefix', {
+ 'countrycode' => $countrycode,
+ #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
+ 'npa' => substr($phonenum, 0, $len),
+ } ) and last;
+ }
+ $rate_prefix ||= qsearchs('rate_prefix', {
+ 'countrycode' => $countrycode,
+ 'npa' => '',
+ });
+
+ return '' unless $rate_prefix;
+
+ $regionnum = $rate_prefix->regionnum;
+
+ #$rate_region = $rate_prefix->rate_region;
+
+ } else {
+ $regionnum = ref($_[0]) ? shift->regionnum : shift;
+ }
+
+ qsearchs( 'rate_detail', { 'ratenum' => $self->ratenum,
+ 'dest_regionnum' => $regionnum, } );
+}
+
+=item rate_detail
+
+Returns all region-specific details (see L<FS::rate_detail>) for this rate.
+
+=cut
+
+sub rate_detail {
+ my $self = shift;
+ qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item process
+
+Experimental job-queue processor for web interface adds/edits
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
+ if $param->{'ratenum'};
+
+ my @rate_detail = map {
+
+ my $regionnum = $_->regionnum;
+ if ( $param->{"sec_granularity$regionnum"} ) {
+
+ new FS::rate_detail {
+ 'dest_regionnum' => $regionnum,
+ map { $_ => $param->{"$_$regionnum"} }
+ qw( min_included min_charge sec_granularity )
+ #qw( min_included conn_charge conn_sec min_charge sec_granularity )
+ };
+
+ } else {
+
+ new FS::rate_detail {
+ 'dest_regionnum' => $regionnum,
+ 'min_included' => 0,
+ 'conn_charge' => 0,
+ 'conn_sec' => 0,
+ 'conn_charge' => 0,
+ 'min_charge' => 0,
+ 'sec_granularity' => '60'
+ };
+
+ }
+
+ } qsearch('rate_region', {} );
+
+ my $rate = new FS::rate {
+ map { $_ => $param->{$_} }
+ fields('rate')
+ };
+
+ my $error = '';
+ if ( $param->{'ratenum'} ) {
+ warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
+
+ my @param = ( 'job'=>$job );
+ push @param, 'rate_detail'=>\@rate_detail
+ unless $param->{'preserve_rate_detail'};
+
+ $error = $rate->replace( $old, @param );
+
+ } else {
+ warn "inserting $rate\n" if $DEBUG;
+ $error = $rate->insert( 'rate_detail' => \@rate_detail,
+ 'job' => $job,
+ );
+ #$ratenum = $rate->getfield('ratenum');
+ }
+
+ die "$error\n" if $error;
+
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm
new file mode 100644
index 0000000..f6cdedf
--- /dev/null
+++ b/FS/FS/rate_detail.pm
@@ -0,0 +1,613 @@
+package FS::rate_detail;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::rate;
+use FS::rate_region;
+use Tie::IxHash;
+
+@ISA = qw(FS::Record);
+
+$DEBUG = 0;
+$me = '[FS::rate_detail]';
+
+=head1 NAME
+
+FS::rate_detail - Object methods for rate_detail records
+
+=head1 SYNOPSIS
+
+ use FS::rate_detail;
+
+ $record = new FS::rate_detail \%hash;
+ $record = new FS::rate_detail { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rate_detail object represents an call plan rate. FS::rate_detail
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item ratedetailnum - primary key
+
+=item ratenum - rate plan (see L<FS::rate>)
+
+=item orig_regionnum - call origination region
+
+=item dest_regionnum - call destination region
+
+=item min_included - included minutes
+
+=item min_charge - charge per minute
+
+=item sec_granularity - granularity in seconds, i.e. 6 or 60; 0 for per-call
+
+=item classnum - usage class (see L<FS::usage_class>) if any for this rate
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new call plan rate. To add the call plan rate 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 { 'rate_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 call plan rate. 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('ratedetailnum')
+ || $self->ut_foreign_key('ratenum', 'rate', 'ratenum')
+ || $self->ut_foreign_keyn('orig_regionnum', 'rate_region', 'regionnum' )
+ || $self->ut_foreign_key('dest_regionnum', 'rate_region', 'regionnum' )
+ || $self->ut_number('min_included')
+
+ #|| $self->ut_money('min_charge')
+ #good enough for now...
+ || $self->ut_float('min_charge')
+
+ || $self->ut_number('sec_granularity')
+
+ || $self->ut_foreign_keyn('classnum', 'usage_class', 'classnum' )
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item rate
+
+Returns the parent call plan (see L<FS::rate>) associated with this call plan
+rate.
+
+=cut
+
+sub rate {
+ my $self = shift;
+ qsearchs('rate', { 'ratenum' => $self->ratenum } );
+}
+
+=item orig_region
+
+Returns the origination region (see L<FS::rate_region>) associated with this
+call plan rate.
+
+=cut
+
+sub orig_region {
+ my $self = shift;
+ qsearchs('rate_region', { 'regionnum' => $self->orig_regionnum } );
+}
+
+=item dest_region
+
+Returns the destination region (see L<FS::rate_region>) associated with this
+call plan rate.
+
+=cut
+
+sub dest_region {
+ my $self = shift;
+ qsearchs('rate_region', { 'regionnum' => $self->dest_regionnum } );
+}
+
+=item dest_regionname
+
+Returns the name of the destination region (see L<FS::rate_region>) associated
+with this call plan rate.
+
+=cut
+
+sub dest_regionname {
+ my $self = shift;
+ $self->dest_region->regionname;
+}
+
+=item dest_regionname
+
+Returns a short list of the prefixes for the destination region
+(see L<FS::rate_region>) associated with this call plan rate.
+
+=cut
+
+sub dest_prefixes_short {
+ my $self = shift;
+ $self->dest_region->prefixes_short;
+}
+
+=item classname
+
+Returns the name of the usage class (see L<FS::usage_class>) associated with
+this call plan rate.
+
+=cut
+
+sub classname {
+ my $self = shift;
+ my $usage_class = qsearchs('usage_class', { classnum => $self->classnum });
+ $usage_class ? $usage_class->classname : '';
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item granularities
+
+ Returns an (ordered) hash of granularity => name pairs
+
+=cut
+
+tie my %granularities, 'Tie::IxHash',
+ '1', => '1 second',
+ '6' => '6 second',
+ '30' => '30 second', # '1/2 minute',
+ '60' => 'minute',
+ '0' => 'call',
+;
+
+sub granularities {
+ %granularities;
+}
+
+=item conn_secs
+
+ Returns an (ordered) hash of conn_sec => name pairs
+
+=cut
+
+tie my %conn_secs, 'Tie::IxHash',
+ '0' => 'connection',
+ '1' => 'first second',
+ '6' => 'first 6 seconds',
+ '30' => 'first 30 seconds', # '1/2 minute',
+ '60' => 'first minute',
+ '120' => 'first 2 minutes',
+ '180' => 'first 3 minutes',
+ '300' => 'first 5 minutes',
+;
+
+sub conn_secs {
+ %conn_secs;
+}
+
+=item process_edit_import
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_edit_import {
+ my $job = shift;
+
+ #do we actually belong in rate_detail, like 'table' says? even though we
+ # can possible create new rate records, that's a side effect, mostly we
+ # do edit rate_detail records in batch...
+
+ my $opt = { 'table' => 'rate_detail',
+ 'params' => [], #required, apparantly
+ 'formats' => { 'default' => [
+ 'dest_regionnum',
+ '', #regionname
+ '', #country
+ '', #prefixes
+ #loop these
+ 'min_included',
+ 'min_charge',
+ sub {
+ my( $rate_detail, $g ) = @_;
+ $g = 0 if $g =~ /^\s*(per-)?call\s*$/i;
+ $g = 60 if $g =~ /^\s*minute\s*$/i;
+ $g =~ /^(\d+)/ or die "can't parse granularity: $g".
+ " for record ". Dumper($rate_detail);
+ $rate_detail->sec_granularity($1);
+ },
+ 'classnum',
+ ] },
+ 'format_headers' => { 'default' => 1, },
+ 'format_types' => { 'default' => 'xls' },
+ };
+
+ #false laziness w/
+ #FS::Record::process_batch_import( $job, $opt, @_ );
+
+ my $table = $opt->{table};
+ my @pass_params = @{ $opt->{params} };
+ my %formats = %{ $opt->{formats} };
+
+ my $param = thaw(decode_base64(shift));
+ warn Dumper($param) if $DEBUG;
+
+ my $files = $param->{'uploaded_files'}
+ or die "No files provided.\n";
+
+ my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+ my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+ my $file = $dir. $files{'file'};
+
+ my $error =
+ #false laziness w/
+ #FS::Record::batch_import( {
+ FS::rate_detail::edit_import( {
+ #class-static
+ table => $table,
+ formats => \%formats,
+ format_types => $opt->{format_types},
+ format_headers => $opt->{format_headers},
+ format_sep_chars => $opt->{format_sep_chars},
+ format_fixedlength_formats => $opt->{format_fixedlength_formats},
+ #per-import
+ job => $job,
+ file => $file,
+ #type => $type,
+ format => $param->{format},
+ params => { map { $_ => $param->{$_} } @pass_params },
+ #?
+ default_csv => $opt->{default_csv},
+ } );
+
+ unlink $file;
+
+ die "$error\n" if $error;
+
+}
+
+=item edit_import
+
+=cut
+
+#false laziness w/ #FS::Record::batch_import, grep "edit_import" for differences
+#could be turned into callbacks or something
+use Text::CSV_XS;
+sub edit_import {
+ my $param = shift;
+
+ warn "$me edit_import call with params: \n". Dumper($param)
+ if $DEBUG;
+
+ my $table = $param->{table};
+ my $formats = $param->{formats};
+
+ my $job = $param->{job};
+ my $file = $param->{file};
+ my $format = $param->{'format'};
+ my $params = $param->{params} || {};
+
+ die "unknown format $format" unless exists $formats->{ $format };
+
+ my $type = $param->{'format_types'}
+ ? $param->{'format_types'}{ $format }
+ : $param->{type} || 'csv';
+
+ unless ( $type ) {
+ if ( $file =~ /\.(\w+)$/i ) {
+ $type = lc($1);
+ } else {
+ #or error out???
+ warn "can't parse file type from filename $file; defaulting to CSV";
+ $type = 'csv';
+ }
+ $type = 'csv'
+ if $param->{'default_csv'} && $type ne 'xls';
+ }
+
+ my $header = $param->{'format_headers'}
+ ? $param->{'format_headers'}{ $param->{'format'} }
+ : 0;
+
+ my $sep_char = $param->{'format_sep_chars'}
+ ? $param->{'format_sep_chars'}{ $param->{'format'} }
+ : ',';
+
+ my $fixedlength_format =
+ $param->{'format_fixedlength_formats'}
+ ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
+ : '';
+
+ my @fields = @{ $formats->{ $format } };
+
+ my $row = 0;
+ my $count;
+ my $parser;
+ my @buffer = ();
+ my @header = (); #edit_import
+ if ( $type eq 'csv' || $type eq 'fixedlength' ) {
+
+ if ( $type eq 'csv' ) {
+
+ my %attr = ();
+ $attr{sep_char} = $sep_char if $sep_char;
+ $parser = new Text::CSV_XS \%attr;
+
+ } elsif ( $type eq 'fixedlength' ) {
+
+ eval "use Parse::FixedLength;";
+ die $@ if $@;
+ $parser = new Parse::FixedLength $fixedlength_format;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ @buffer = split(/\r?\n/, slurp($file) );
+ splice(@buffer, 0, ($header || 0) );
+ $count = scalar(@buffer);
+
+ } elsif ( $type eq 'xls' ) {
+
+ eval "use Spreadsheet::ParseExcel;";
+ die $@ if $@;
+
+ eval "use DateTime::Format::Excel;";
+ #for now, just let the error be thrown if it is used, since only CDR
+ # formats bill_west and troop use it, not other excel-parsing things
+ #die $@ if $@;
+
+ my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
+
+ $parser = $excel->{Worksheet}[0]; #first sheet
+
+ $count = $parser->{MaxRow} || $parser->{MinRow};
+ $count++;
+
+ $row = $header || 0;
+
+ #edit_import - need some magic to parse the header
+ if ( $header ) {
+ my @header_row = @{ $parser->{Cells}[$0] };
+ @header = map $_->{Val}, @header_row;
+ }
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ #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;
+
+ #edit_import - use the header to setup looping over different rates
+ my @rate = ();
+ if ( @header ) {
+ splice(@header,0,4); # # Region Country Prefixes
+ while ( my @next = splice(@header,0,4) ) {
+ my $rate;
+ if ( $next[0] =~ /^(\d+):\s*([^:]+):/ ) {
+ $rate = qsearchs('rate', { 'ratenum' => $1 } )
+ or die "unknown ratenum $1";
+ } elsif ( $next[0] =~ /^(NEW:)?\s*([^:]+)/i ) {
+ $rate = new FS::rate { 'ratename' => $2 };
+ my $error = $rate->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting new rate: $error\n";
+ }
+ }
+ push @rate, $rate;
+ }
+ }
+ die unless @rate;
+
+ my $line;
+ my $imported = 0;
+ my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
+ while (1) {
+
+ my @columns = ();
+ if ( $type eq 'csv' ) {
+
+ last unless scalar(@buffer);
+ $line = shift(@buffer);
+
+ $parser->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $parser->error_input();
+ };
+ @columns = $parser->fields();
+
+ } elsif ( $type eq 'fixedlength' ) {
+
+ @columns = $parser->parse($line);
+
+ } elsif ( $type eq 'xls' ) {
+
+ last if $row > ($parser->{MaxRow} || $parser->{MinRow})
+ || ! $parser->{Cells}[$row];
+
+ my @row = @{ $parser->{Cells}[$row] };
+ @columns = map $_->{Val}, @row;
+
+ #my $z = 'A';
+ #warn $z++. ": $_\n" for @columns;
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ #edit_import loop
+
+ my @repeat = @columns[0..3];
+
+ foreach my $rate ( @rate ) {
+
+ my @later = ();
+ my %hash = %$params;
+
+ foreach my $field ( @fields ) {
+
+ my $value = shift @columns;
+
+ if ( ref($field) eq 'CODE' ) {
+ #&{$field}(\%hash, $value);
+ push @later, $field, $value;
+ #} else {
+ } elsif ($field) { #edit_import
+ #??? $hash{$field} = $value if length($value);
+ $hash{$field} = $value if defined($value) && length($value);
+ }
+
+ }
+
+ unshift @columns, @repeat; #edit_import put these back on for next time
+
+ my $class = "FS::$table";
+
+ my $record = $class->new( \%hash );
+
+ $record->ratenum($rate->ratenum); #edit_import
+
+ #edit_improt n/a my $param = {};
+ while ( scalar(@later) ) {
+ my $sub = shift @later;
+ my $data = shift @later;
+ #&{$sub}($record, $data, $conf, $param);# $record->&{$sub}($data, $conf);
+ &{$sub}($record, $data); #edit_import - don't have $conf
+ #edit_import wrong loop last if exists( $param->{skiprow} );
+ }
+ #edit_import wrong loop next if exists( $param->{skiprow} );
+
+ #edit_import update or insert, not just insert
+ my $old = qsearchs({
+ 'table' => $table,
+ 'hashref' => { map { $_ => $record->$_() } qw(ratenum dest_regionnum) },
+ });
+
+ my $error;
+ if ( $old ) {
+ $record->ratedetailnum($old->ratedetailnum);
+ $error = $record->replace($old)
+ } else {
+ $record->insert;
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert record". ( $line ? " for $line" : '' ). ": $error";
+ }
+
+ }
+
+ $row++;
+ $imported++;
+
+ if ( $job && time - $min_sec > $last ) { #progress bar
+ $job->update_statustext( int(100 * $imported / $count) );
+ $last = time;
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
+
+ return "Empty file!" unless $imported || $param->{empty_ok};
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::rate>, L<FS::rate_region>, L<FS::Record>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/rate_prefix.pm b/FS/FS/rate_prefix.pm
new file mode 100644
index 0000000..ce780fe
--- /dev/null
+++ b/FS/FS/rate_prefix.pm
@@ -0,0 +1,160 @@
+package FS::rate_prefix;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::rate_region;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::rate_prefix - Object methods for rate_prefix records
+
+=head1 SYNOPSIS
+
+ use FS::rate_prefix;
+
+ $record = new FS::rate_prefix \%hash;
+ $record = new FS::rate_prefix { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rate_prefix object represents an call rating prefix. FS::rate_prefix
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item prefixnum - primary key
+
+=item regionnum - call ration region (see L<FS::rate_region>)
+
+=item countrycode
+
+=item npa
+
+=item nxx
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new prefix. To add the prefix 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 { 'rate_prefix'; }
+
+=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 prefix. 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('prefixnum')
+ || $self->ut_foreign_key('regionnum', 'rate_region', 'regionnum' )
+ || $self->ut_number('countrycode')
+ || $self->ut_numbern('npa')
+ || $self->ut_numbern('nxx')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item rate_region
+
+Returns the rate region (see L<FS::rate_region>) for this prefix.
+
+=cut
+
+sub rate_region {
+ my $self = shift;
+ qsearchs('rate_region', { 'regionnum' => $self->regionnum } );
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item all_countrycodes
+
+Returns a list of all countrycodes listed in rate_prefix
+
+=cut
+
+sub all_countrycodes {
+ #my $class = shift;
+ my $sql =
+ "SELECT DISTINCT(countrycode) FROM rate_prefix ORDER BY countrycode";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ map $_->[0], @{ $sth->fetchall_arrayref };
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::rate_region>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/rate_region.pm b/FS/FS/rate_region.pm
new file mode 100644
index 0000000..0e65223
--- /dev/null
+++ b/FS/FS/rate_region.pm
@@ -0,0 +1,315 @@
+package FS::rate_region;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::rate_prefix;
+use FS::rate_detail;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::rate_region - Object methods for rate_region records
+
+=head1 SYNOPSIS
+
+ use FS::rate_region;
+
+ $record = new FS::rate_region \%hash;
+ $record = new FS::rate_region { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rate_region object represents an call rating region. FS::rate_region
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item regionnum - primary key
+
+=item regionname
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new region. To add the region 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 { 'rate_region'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+Currently available options are: I<rate_prefix> and I<dest_detail>
+
+If I<rate_prefix> is set to an array reference of FS::rate_prefix objects, the
+objects will have their regionnum field set and will be inserted after this
+record.
+
+If I<dest_detail> is set to an array reference of FS::rate_detail objects, the
+objects will have their dest_regionnum field set and will be inserted after
+this record.
+
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+
+ 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->check;
+ return $error if $error;
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'rate_prefix'} ) {
+ foreach my $rate_prefix ( @{$options{'rate_prefix'}} ) {
+ $rate_prefix->regionnum($self->regionnum);
+ $error = $rate_prefix->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ if ( $options{'dest_detail'} ) {
+ foreach my $rate_detail ( @{$options{'dest_detail'}} ) {
+ $rate_detail->dest_regionnum($self->regionnum);
+ $error = $rate_detail->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
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD [ , OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+Currently available options are: I<rate_prefix> and I<dest_detail>
+
+If I<rate_prefix> is set to an array reference of FS::rate_prefix objects, the
+objects will have their regionnum field set and will be inserted after this
+record. Any existing rate_prefix records associated with this record will be
+deleted.
+
+If I<dest_detail> is set to an array reference of FS::rate_detail objects, the
+objects will have their dest_regionnum field set and will be inserted after
+this record. Any existing rate_detail records associated with this record will
+be deleted.
+
+=cut
+
+sub replace {
+ my ($new, $old) = (shift, shift);
+ my %options = @_;
+
+ 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 @old_rate_prefix = ();
+ @old_rate_prefix = $old->rate_prefix if $options{'rate_prefix'};
+ my @old_dest_detail = ();
+ @old_dest_detail = $old->dest_detail if $options{'dest_detail'};
+
+ my $error = $new->SUPER::replace($old);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $old_rate_prefix ( @old_rate_prefix ) {
+ my $error = $old_rate_prefix->delete;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ foreach my $old_dest_detail ( @old_dest_detail ) {
+ my $error = $old_dest_detail->delete;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $rate_prefix ( @{$options{'rate_prefix'}} ) {
+ $rate_prefix->regionnum($new->regionnum);
+ $error = $rate_prefix->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ foreach my $rate_detail ( @{$options{'dest_detail'}} ) {
+ $rate_detail->dest_regionnum($new->regionnum);
+ $error = $rate_detail->insert;
+ 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 region. 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('regionnum')
+ || $self->ut_text('regionname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item rate_prefix
+
+Returns all prefixes (see L<FS::rate_prefix>) for this region.
+
+=cut
+
+sub rate_prefix {
+ my $self = shift;
+
+ sort { $a->countrycode cmp $b->countrycode
+ or $a->npa cmp $b->npa
+ or $a->nxx cmp $b->nxx
+ }
+ qsearch( 'rate_prefix', { 'regionnum' => $self->regionnum } );
+}
+
+=item dest_detail
+
+Returns all rate details (see L<FS::rate_detail>) for this region as a
+destionation.
+
+=cut
+
+sub dest_detail {
+ my $self = shift;
+ qsearch( 'rate_detail', { 'dest_regionnum' => $self->regionnum, } );
+}
+
+=item prefixes_short
+
+Returns a string representing all the prefixes for this region.
+
+=cut
+
+sub prefixes_short {
+ my $self = shift;
+
+ my $countrycode = '';
+ my $out = '';
+
+ foreach my $rate_prefix ( $self->rate_prefix ) {
+ if ( $countrycode ne $rate_prefix->countrycode ) {
+ $out =~ s/, $//;
+ $countrycode = $rate_prefix->countrycode;
+ $out.= " +$countrycode ";
+ }
+ my $npa = $rate_prefix->npa;
+ if ( $countrycode eq '1' ) {
+ #$out .= '('. substr( $npa, 0, 3 ). ')';
+ $out .= substr( $npa, 0, 3 );
+ $out .= ' '. substr( $npa, 3 ) if length($npa) > 3;
+ } else {
+ $out .= $rate_prefix->npa;
+ }
+ $out .= '-'. $rate_prefix->nxx if $rate_prefix->nxx;
+ $out .= ', ';
+ }
+ $out =~ s/, $//;
+
+ $out;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/reason.pm b/FS/FS/reason.pm
new file mode 100644
index 0000000..377da49
--- /dev/null
+++ b/FS/FS/reason.pm
@@ -0,0 +1,130 @@
+package FS::reason;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use FS::Record qw( qsearch qsearchs dbh dbdef );
+use FS::reason_type;
+
+@ISA = qw(FS::Record);
+$DEBUG = 0;
+$me = '[FS::reason]';
+
+=head1 NAME
+
+FS::reason - Object methods for reason records
+
+=head1 SYNOPSIS
+
+ use FS::reason;
+
+ $record = new FS::reason \%hash;
+ $record = new FS::reason { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::reason object represents a reason message. FS::reason inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item reasonnum - primary key
+
+=item reason_type - index into FS::reason_type
+
+=item reason - text of the reason
+
+=item disabled - 'Y' or ''
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new reason. 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 { 'reason'; }
+
+=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 reason. 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('reasonnum')
+ || $self->ut_text('reason')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item reasontype
+
+Returns the reason_type (see <I>FS::reason_type</I>) associated with this reason.
+
+=cut
+
+sub reasontype {
+ qsearchs( 'reason_type', { 'typenum' => shift->reason_type } );
+}
+
+=back
+
+=head1 BUGS
+
+Here be termintes. Don't use on wooden computers.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm
new file mode 100644
index 0000000..4425c64
--- /dev/null
+++ b/FS/FS/reason_type.pm
@@ -0,0 +1,209 @@
+package FS::reason_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+our %class_name = (
+ 'C' => 'cancel',
+ 'R' => 'credit',
+ 'S' => 'suspend',
+);
+
+our %class_purpose = (
+ 'C' => 'explain why a customer package was cancelled',
+ 'R' => 'explain why a customer was credited',
+ 'S' => 'explain why a customer package was suspended',
+);
+
+=head1 NAME
+
+FS::reason_type - Object methods for reason_type records
+
+=head1 SYNOPSIS
+
+ use FS::reason_type;
+
+ $record = new FS::reason_type \%hash;
+ $record = new FS::reason_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::reason_type object represents a grouping of reasons. FS::reason_type
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item typenum - primary key
+
+=item class - currently 'C', 'R', or 'S' for cancel, credit, or suspend
+
+=item type - name of the type of reason
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new reason_type. 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 { 'reason_type'; }
+
+=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 reason_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;
+
+ my $error =
+ $self->ut_numbern('typenum')
+ || $self->ut_enum('class', [ keys %class_name ] )
+ || $self->ut_text('type')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item reasons
+
+Returns a list of all reasons associated with this type.
+
+=cut
+
+sub reasons {
+ qsearch( 'reason', { 'reason_type' => shift->typenum } );
+}
+
+=item enabled_reasons
+
+Returns a list of enabled reasons associated with this type.
+
+=cut
+
+sub enabled_reasons {
+ qsearch( 'reason', { 'reason_type' => shift->typenum,
+ 'enabled' => '',
+ } );
+}
+
+# _populate_initial_data
+#
+# Used by FS::Setup to initialize a new database.
+#
+#
+
+sub _populate_initial_data { # class method
+ my ($self, %opts) = @_;
+
+ my $conf = new FS::Conf;
+
+ foreach ( keys %class_name ) {
+ my $object = $self->new( {'class' => $_,
+ 'type' => ucfirst($class_name{$_}). ' Reason',
+ } );
+ my $error = $object->insert();
+ die "error inserting $self into database: $error\n"
+ if $error;
+ }
+
+ my $object = qsearchs('reason_type', { 'class' => 'R' });
+ die "can't find credit reason type just inserted!\n"
+ unless $object;
+
+ foreach ( keys %FS::cust_credit::reasontype_map ) {
+# my $object = $self->new( {'class' => 'R',
+# 'type' => $FS::cust_credit::reasontype_map{$_},
+# } );
+# my $error = $object->insert();
+# die "error inserting $self into database: $error\n"
+# if $error;
+ $conf->set($_, $object->typenum);
+ }
+
+ '';
+
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data { # class method
+ my ($self, %opts) = @_;
+
+ foreach ( keys %class_name ) {
+ unless (scalar(qsearch('reason_type', { 'class' => $_ }))) {
+ my $object = $self->new( {'class' => $_,
+ 'type' => ucfirst($class_name{$_}),
+ } );
+ my $error = $object->insert();
+ die "error inserting $self into database: $error\n"
+ if $error;
+ }
+ }
+
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+Here be termintes. Don't use on wooden computers.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/reg_code.pm b/FS/FS/reg_code.pm
new file mode 100644
index 0000000..f48ccf0
--- /dev/null
+++ b/FS/FS/reg_code.pm
@@ -0,0 +1,223 @@
+package FS::reg_code;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(qsearch dbh);
+use FS::agent;
+use FS::reg_code_pkg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::reg_code - One-time registration codes
+
+=head1 SYNOPSIS
+
+ use FS::reg_code;
+
+ $record = new FS::reg_code \%hash;
+ $record = new FS::reg_code { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::reg_code object is a one-time registration code. FS::reg_code inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item codenum - primary key
+
+=item code - registration code string
+
+=item agentnum - Agent (see L<FS::agent>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new registration code. To add the code 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 { 'reg_code'; }
+
+=item insert [ PKGPART_ARRAYREF ]
+
+Adds this record to the database. If an arrayref of pkgparts
+(see L<FS::part_pkg>) is specified, the appropriate reg_code_pkg records
+(see L<FS::reg_code_pkg>) will be inserted.
+
+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;
+ }
+
+ if ( @_ ) {
+ my $pkgparts = shift;
+ foreach my $pkgpart ( @$pkgparts ) {
+ my $reg_code_pkg = new FS::reg_code_pkg ( {
+ 'codenum' => $self->codenum,
+ 'pkgpart' => $pkgpart,
+ } );
+ $error = $reg_code_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item delete
+
+Delete this record (and all associated reg_code_pkg records) from the database.
+
+=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;
+
+ foreach my $reg_code_pkg ( $self->reg_code_pkg ) {
+ my $error = $reg_code_pkg->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 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 registration code. 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('codenum')
+ || $self->ut_alpha('code')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item part_pkg
+
+Returns all package definitions (see L<FS::part_pkg> for this registration
+code.
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ map { $_->part_pkg } $self->reg_code_pkg;
+}
+
+=item reg_code_pkg
+
+Returns all FS::reg_code_pkg records for this registration code.
+
+=cut
+
+sub reg_code_pkg {
+ my $self = shift;
+ qsearch('reg_code_pkg', { 'codenum' => $self->codenum } );
+}
+
+
+=back
+
+=head1 BUGS
+
+Feeping creaturitis.
+
+=head1 SEE ALSO
+
+L<FS::reg_code_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/reg_code_pkg.pm b/FS/FS/reg_code_pkg.pm
new file mode 100644
index 0000000..837b755
--- /dev/null
+++ b/FS/FS/reg_code_pkg.pm
@@ -0,0 +1,139 @@
+package FS::reg_code_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(qsearchs);
+use FS::reg_code;
+use FS::part_pkg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::reg_code_pkg - Class linking registration codes (see L<FS::reg_code>) with package definitions (see L<FS::part_pkg>)
+
+=head1 SYNOPSIS
+
+ use FS::reg_code_pkg;
+
+ $record = new FS::reg_code_pkg \%hash;
+ $record = new FS::reg_code_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::reg_code_pkg object links a registration code to a package definition.
+FS::table_name inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item codepkgnum - primary key
+
+=item codenum - registration code (see L<FS::reg_code>)
+
+=item pkgpart - package definition (see L<FS::part_pkg>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new registration code. To add the registration code 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 { 'reg_code_pkg'; }
+
+=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 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;
+
+ my $error =
+ $self->ut_numbern('codepkgnum')
+ || $self->ut_foreign_key('codenum', 'reg_code', 'codenum')
+ || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item part_pkg
+
+Returns the package definition (see L<FS::part_pkg>)
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=back
+
+=head1 BUGS
+
+Feeping creaturitis.
+
+=head1 SEE ALSO
+
+L<FS::reg_code_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/registrar.pm b/FS/FS/registrar.pm
new file mode 100644
index 0000000..cf5dc49
--- /dev/null
+++ b/FS/FS/registrar.pm
@@ -0,0 +1,119 @@
+package FS::registrar;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::registrar - Object methods for registrar records
+
+=head1 SYNOPSIS
+
+ use FS::registrar;
+
+ $record = new FS::registrar \%hash;
+ $record = new FS::registrar { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::registrar object represents a registrar. FS::registrar inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item registrarnum - primary key
+
+=item registrarname -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new registrar. To add the registrar 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 { 'registrar'; }
+
+=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 registrar. 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('registrarnum')
+ || $self->ut_text('registrarname')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+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..7a9fda3
--- /dev/null
+++ b/FS/FS/router.pm
@@ -0,0 +1,152 @@
+package FS::router;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+use FS::addr_block;
+
+@ISA = qw( FS::Record FS::m2m_Common );
+
+=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')
+ || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration')
+ ;
+ 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;
+}
+
+=item agent
+
+Returns the agent associated with this router, if any.
+
+=cut
+
+sub agent {
+ qsearchs('agent', { 'agentnum' => shift->agentnum });
+}
+
+=back
+
+=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..615c8ae
--- /dev/null
+++ b/FS/FS/session.pm
@@ -0,0 +1,265 @@
+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 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_CGPRule_Mixin.pm b/FS/FS/svc_CGPRule_Mixin.pm
new file mode 100644
index 0000000..4501580
--- /dev/null
+++ b/FS/FS/svc_CGPRule_Mixin.pm
@@ -0,0 +1,61 @@
+package FS::svc_CGPRule_Mixin;
+
+use strict;
+use FS::Record qw(qsearch);
+use FS::cgp_rule;
+
+=head1 NAME
+
+FS::svc_CGPRule_Mixin - Mixin class for svc_classes which can be related to cgp_rule
+
+=head1 SYNOPSIS
+
+package FS::svc_table;
+use base qw( FS::svc_CGPRule_Mixin FS::svc_Common );
+
+=head1 DESCRIPTION
+
+This is a mixin class for svc_ classes that can have Communigate Pro rules
+(currently, domains and accounts).
+
+=head1 METHODS
+
+=over 4
+
+=item
+
+Returns the rules associated with this service, as FS::cgp_rule objects.
+
+=cut
+
+sub cgp_rule {
+ my $self = shift;
+ qsearch({
+ 'table' => 'cgp_rule',
+ 'hashref' => { 'svcnum' => $self->svcnum },
+ 'order_by' => 'ORDER BY priority ASC',
+ });
+}
+
+=item cgp_rule_arrayref
+
+Returns an arrayref of rules suitable for Communigate Pro API commands.
+
+=cut
+
+sub cgp_rule_arrayref {
+ my $self = shift;
+ [ map $_->arrayref, $self->cgp_rule ];
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cgp_rule>
+
+=cut
+
+1;
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
new file mode 100644
index 0000000..71290f4
--- /dev/null
+++ b/FS/FS/svc_Common.pm
@@ -0,0 +1,1032 @@
+package FS::svc_Common;
+
+use strict;
+use vars qw( @ISA $noexport_hack $DEBUG $me
+ $overlimit_missing_cust_svc_nonfatal_kludge );
+use Carp qw( cluck carp croak confess ); #specify cluck have to specify them all
+use Scalar::Util qw( blessed );
+use FS::Record qw( qsearch qsearchs fields dbh );
+use FS::cust_main_Mixin;
+use FS::cust_svc;
+use FS::part_svc;
+use FS::queue;
+use FS::cust_main;
+use FS::inventory_item;
+use FS::inventory_class;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+$me = '[FS::svc_Common]';
+$DEBUG = 0;
+
+$overlimit_missing_cust_svc_nonfatal_kludge = 0;
+
+=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
+
+=item search_sql_field FIELD STRING
+
+Class method which returns an SQL fragment to search for STRING in FIELD.
+
+It is now case-insensitive by default.
+
+=cut
+
+sub search_sql_field {
+ my( $class, $field, $string ) = @_;
+ my $table = $class->table;
+ my $q_string = dbh->quote($string);
+ "LOWER($table.$field) = LOWER($q_string)";
+}
+
+#fallback for services that don't provide a search...
+sub search_sql {
+ #my( $class, $string ) = @_;
+ '1 = 0'; #false
+}
+
+=item new
+
+=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'};
+ }
+
+ #$self->{'Hash'} = shift;
+ my $newhash = shift;
+ $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
+
+ $self->setdefault( $self->_fieldhandlers )
+ unless $self->svcnum;
+
+ $self->{'Hash'}{$_} = $newhash->{$_}
+ foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
+ keys %$newhash;
+
+ foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
+ $self->{'Hash'}{$field}='';
+ }
+
+ $self->_rebless if $self->can('_rebless');
+
+ $self->{'modified'} = 0;
+
+ $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
+
+ $self;
+}
+
+#empty default
+sub _fieldhandlers { {}; }
+
+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 ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields;
+ } else { # Case 3
+ return @vfields;
+ }
+ return ();
+}
+
+=item label
+
+svc_Common provides a fallback label subroutine that just returns the svcnum.
+
+=cut
+
+sub label {
+ my $self = shift;
+ cluck "warning: ". ref($self). " not loaded or missing label method; ".
+ "using svcnum";
+ $self->svcnum;
+}
+
+sub label_long {
+ my $self = shift;
+ $self->label(@_);
+}
+
+=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 field set and
+will be inserted after this record, but before any exports are run. Each
+element of the array can also optionally be a two-element array reference
+containing the child object and the name of an alternate field to be filled in
+with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
+
+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)).
+
+If I<export_args> is set to an array reference, the referenced list will be
+passed to export commands.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+ warn "[$me] insert called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
+
+ my @jobnums = ();
+ local $FS::queue::jobnums = \@jobnums;
+ warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
+ if $DEBUG;
+ my $objects = $options{'child_objects'} || [];
+ my $depend_jobnums = $options{'depend_jobnum'} || [];
+ $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
+
+ 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 $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,
+ } );
+ my $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);
+ }
+
+ my $error = $self->preinsert_hook_first
+ || $self->set_auto_inventory
+ || $self->check
+ || $self->_check_duplicate
+ || $self->preinsert_hook
+ || $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $object ( @$objects ) {
+ my($field, $obj);
+ if ( ref($object) eq 'ARRAY' ) {
+ ($obj, $field) = @$object;
+ } else {
+ $obj = $object;
+ $field = 'svcnum';
+ }
+ $obj->$field($self->svcnum);
+ $error = $obj->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+
+ warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
+ if $DEBUG;
+
+ my $export_args = $options{'export_args'} || [];
+
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_insert($self, @$export_args);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "exporting to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ foreach my $depend_jobnum ( @$depend_jobnums ) {
+ warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
+ if $DEBUG;
+ foreach my $jobnum ( @jobnums ) {
+ my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
+ warn "[$me] 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;
+
+ '';
+}
+
+#fallbacks
+sub preinsert_hook_first { ''; }
+sub _check_duplcate { ''; }
+sub preinsert_hook { ''; }
+sub table_dupcheck_fields { (); }
+
+=item delete [ , OPTION => VALUE ... ]
+
+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 %options = @_;
+ my $export_args = $options{'export_args'} || [];
+
+ 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
+ || $self->export('delete', @$export_args)
+ || $self->return_inventory
+ || $self->cust_svc->delete
+ ;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
+
+Replaces OLD_RECORD with this one. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? 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->set_auto_inventory($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ #redundant, but so any duplicate fields are maniuplated as appropriate
+ # (svc_phone.phonenum)
+ $error = $new->check;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
+ if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) {
+
+ $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;
+ }
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+
+ my $export_args = $options->{'export_args'} || [];
+
+ #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, @$export_args);
+ 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, @$export_args);
+ 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, @$export_args );
+ 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', @_ );
+}
+
+=item set_default_and_fixed
+
+=cut
+
+sub set_default_and_fixed {
+ my $self = shift;
+ $self->setx( [ 'D', 'F' ], @_ );
+}
+
+=item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
+
+Sets fields according to the passed in flag or arrayref of flags.
+
+Optionally, a hashref of field names and callback coderefs can be passed.
+If a coderef exists for a given field name, instead of setting the field,
+the coderef is called with the column value (part_svc_column.columnvalue)
+as the single parameter.
+
+=cut
+
+sub setx {
+ my $self = shift;
+ my $x = shift;
+ my @x = ref($x) ? @$x : ($x);
+ my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ ;
+ return $error if $error;
+
+ my $part_svc = $self->part_svc;
+ return "Unknown svcpart" unless $part_svc;
+
+ #set default/fixed/whatever fields from part_svc
+
+ foreach my $part_svc_column (
+ grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
+ $part_svc->all_part_svc_column
+ ) {
+
+ my $columnname = $part_svc_column->columnname;
+ my $columnvalue = $part_svc_column->columnvalue;
+
+ $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
+ if exists( $coderef->{$columnname} );
+ $self->setfield( $columnname, $columnvalue );
+
+ }
+
+ $part_svc;
+
+}
+
+sub part_svc {
+ my $self = shift;
+
+ #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;
+ }
+
+ qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+
+}
+
+=item svc_pbx
+
+Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
+
+Only makes sense if the service has a pbxsvc field (currently, svc_phone and
+svc_acct).
+
+=cut
+
+# XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override
+
+sub svc_pbx {
+ my $self = shift;
+ return '' unless $self->pbxsvc;
+ qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } );
+}
+
+=item pbx_title
+
+Returns the title of the FS::svc_pbx record associated with this service, if
+any.
+
+Only makes sense if the service has a pbxsvc field (currently, svc_phone and
+svc_acct).
+
+=cut
+
+sub pbx_title {
+ my $self = shift;
+ my $svc_pbx = $self->svc_pbx or return '';
+ $svc_pbx->title;
+}
+
+=item pbx_select_hash %OPTIONS
+
+Can be called as an object method or a class method.
+
+Returns a hash SVCNUM => TITLE ... representing the PBXes this customer
+that may be associated with this service.
+
+Currently available options are: I<pkgnum> I<svcpart>
+
+Only makes sense if the service has a pbxsvc field (currently, svc_phone and
+svc_acct).
+
+=cut
+
+#false laziness w/svc_acct::domain_select_hash
+sub pbx_select_hash {
+ my ($self, %options) = @_;
+ my %pbxes = ();
+ my $part_svc;
+ my $cust_pkg;
+
+ if (ref($self)) {
+ $part_svc = $self->part_svc;
+ $cust_pkg = $self->cust_svc->cust_pkg
+ if $self->cust_svc;
+ }
+
+ $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
+ if $options{'svcpart'};
+
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
+ if $options{'pkgnum'};
+
+ if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S'
+ || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) {
+ %pbxes = map { $_->svcnum => $_->title }
+ map { qsearchs('svc_pbx', { 'svcnum' => $_ }) }
+ split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue);
+ } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) {
+ %pbxes = map { $_->svcnum => $_->title }
+ map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) }
+ map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
+ qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
+ } else {
+ #XXX agent-virt
+ %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} );
+ }
+
+ if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') {
+ my $svc_pbx = qsearchs('svc_pbx',
+ { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } );
+ if ( $svc_pbx ) {
+ $pbxes{$svc_pbx->svcnum} = $svc_pbx->title;
+ } else {
+ warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ".
+ $part_svc->part_svc_column('pbxsvc')->columnvalue;
+
+ }
+ }
+
+ (%pbxes);
+
+}
+
+=item set_auto_inventory
+
+Sets any fields which auto-populate from inventory (see L<FS::part_svc>), and
+also check any manually populated inventory fields.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub set_auto_inventory {
+ my $self = shift;
+ my $old = @_ ? shift : '';
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ ;
+ return $error if $error;
+
+ my $part_svc = $self->part_svc;
+ return "Unkonwn svcpart" unless $part_svc;
+
+ 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;
+
+ #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);
+ my $columnflag = $part_svc_column->columnflag;
+ next unless $columnflag =~ /^[AM]$/;
+
+ next if $columnflag eq 'A' && $self->$field() ne '';
+
+ my $classnum = $part_svc_column->columnvalue;
+ my %hash = ( 'classnum' => $classnum );
+
+ if ( $columnflag eq 'A' && $self->$field() eq '' ) {
+ $hash{'svcnum'} = '';
+ } elsif ( $columnflag eq 'M' ) {
+ return "Select inventory item for $field" unless $self->getfield($field);
+ $hash{'item'} = $self->getfield($field);
+ }
+
+ my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null' => 1,
+ 'table' => 'inventory_item',
+ );
+
+ my $inventory_item = qsearchs({
+ 'table' => 'inventory_item',
+ 'hashref' => \%hash,
+ 'extra_sql' => "AND $agentnums_sql",
+ 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
+ ' LIMIT 1 FOR UPDATE',
+ });
+
+ unless ( $inventory_item ) {
+ $dbh->rollback if $oldAutoCommit;
+ my $inventory_class =
+ qsearchs('inventory_class', { 'classnum' => $classnum } );
+ return "Can't find inventory_class.classnum $classnum"
+ unless $inventory_class;
+ return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
+ #for pluralizing
+ }
+
+ next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
+
+ $self->setfield( $field, $inventory_item->item );
+ #if $columnflag eq 'A' && $self->$field() eq '';
+
+ $inventory_item->svcnum( $self->svcnum );
+ my $ierror = $inventory_item->replace();
+ if ( $ierror ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error provisioning inventory: $ierror";
+ }
+
+ if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
+ my $old_inv = qsearchs({
+ 'table' => 'inventory_item',
+ 'hashref' => { 'classnum' => $classnum,
+ 'svcnum' => $old->svcnum,
+ 'item' => $old->$field(),
+ },
+ });
+ if ( $old_inv ) {
+ $old_inv->svcnum('');
+ my $oerror = $old_inv->replace;
+ if ( $oerror ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error unprovisioning inventory: $oerror";
+ }
+ }
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item return_inventory
+
+=cut
+
+sub return_inventory {
+ 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 $inventory_item ( $self->inventory_item ) {
+ $inventory_item->svcnum('');
+ my $error = $inventory_item->replace();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error returning inventory: $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item inventory_item
+
+Returns the inventory items associated with this svc_ record, as
+FS::inventory_item objects (see L<FS::inventory_item>.
+
+=cut
+
+sub inventory_item {
+ my $self = shift;
+ qsearch({
+ 'table' => 'inventory_item',
+ 'hashref' => { 'svcnum' => $self->svcnum, },
+ });
+}
+
+=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;
+ my %options = @_;
+ my $export_args = $options{'export_args'} || [];
+ $self->export('suspend', @$export_args);
+}
+
+=item unsuspend
+
+Runs export_unsuspend callbacks.
+
+=cut
+
+sub unsuspend {
+ my $self = shift;
+ my %options = @_;
+ my $export_args = $options{'export_args'} || [];
+ $self->export('unsuspend', @$export_args);
+}
+
+=item export_links
+
+Runs export_links callbacks and returns the links.
+
+=cut
+
+sub export_links {
+ my $self = shift;
+ my $return = [];
+ $self->export('links', $return);
+ $return;
+}
+
+=item export_getsettings
+
+Runs export_getsettings callbacks and returns the two hashrefs.
+
+=cut
+
+sub export_getsettings {
+ my $self = shift;
+ my %settings = ();
+ my %defaults = ();
+ my $error = $self->export('getsettings', \%settings, \%defaults);
+ if ( $error ) {
+ #XXX bubble this up better
+ warn "error running export_getsetings: $error";
+ return ( {}, {} );
+ }
+ ( \%settings, \%defaults );
+}
+
+=item export HOOK [ EXPORT_ARGS ]
+
+Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
+
+=cut
+
+sub export {
+ my( $self, $method ) = ( shift, shift );
+
+ $method = "export_$method" unless $method =~ /^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;
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ next unless $part_export->can($method);
+ my $error = $part_export->$method($self, @_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting $method event to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item overlimit
+
+Sets or retrieves overlimit date.
+
+=cut
+
+sub overlimit {
+ my $self = shift;
+ #$self->cust_svc->overlimit(@_);
+ my $cust_svc = $self->cust_svc;
+ unless ( $cust_svc ) { #wtf?
+ my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ".
+ $self->svcnum;
+ if ( $overlimit_missing_cust_svc_nonfatal_kludge ) {
+ cluck "$error; continuing anyway as requested";
+ return '';
+ } else {
+ confess $error;
+ }
+ }
+ $cust_svc->overlimit(@_);
+}
+
+=item cancel
+
+Stub - returns false (no error) so derived classes don't need to define this
+methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+This method is called *before* the deletion step which actually deletes the
+services. This method should therefore only be used for "pre-deletion"
+cancellation steps, if necessary.
+
+=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.
+
+B<export> method isn't used by insert and replace methods yet.
+
+=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_Domain_Mixin.pm b/FS/FS/svc_Domain_Mixin.pm
new file mode 100644
index 0000000..202899c
--- /dev/null
+++ b/FS/FS/svc_Domain_Mixin.pm
@@ -0,0 +1,134 @@
+package FS::svc_Domain_Mixin;
+
+use strict;
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs);
+use FS::part_svc;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_domain;
+
+=head1 NAME
+
+FS::svc_Domain_Mixin - Mixin class for svc_classes with a domsvc field
+
+=head1 SYNOPSIS
+
+package FS::svc_table;
+use base qw( FS::svc_Domain_Mixin FS::svc_Common );
+
+=head1 DESCRIPTION
+
+This is a mixin class for svc_ classes that contain a domsvc field linking to
+a domain (see L<FS::svc_domain>).
+
+=head1 METHODS
+
+=over 4
+
+=item domain [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns the domain associated with this account.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=cut
+
+sub domain {
+ my $self = shift;
+ #die "svc_acct.domsvc is null for svcnum ". $self->svcnum unless $self->domsvc;
+ return '' unless $self->domsvc;
+ my $svc_domain = $self->svc_domain(@_)
+ or die "no svc_domain.svcnum for 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
+
+# FS::h_svc_acct has a history-aware svc_domain override
+
+sub svc_domain {
+ my $self = shift;
+ $self->{'_domsvc'}
+ ? $self->{'_domsvc'}
+ : qsearchs( 'svc_domain', { 'svcnum' => $self->domsvc } );
+}
+
+=item domain_select_hash %OPTIONS
+
+Object or class method.
+
+Returns a hash SVCNUM => DOMAIN ... representing the domains this customer
+may at present purchase.
+
+Currently available options are: I<pkgnum> and I<svcpart>.
+
+=cut
+
+sub domain_select_hash {
+ my ($self, %options) = @_;
+ my %domains = ();
+
+ my $conf = new FS::Conf;
+
+ my $part_svc;
+ my $cust_pkg;
+
+ if (ref($self)) {
+ $part_svc = $self->part_svc;
+ $cust_pkg = $self->cust_svc->cust_pkg
+ if $self->cust_svc;
+ }
+
+ $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
+ if $options{'svcpart'};
+
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
+ if $options{'pkgnum'};
+
+ if ($part_svc && ( $part_svc->part_svc_column('domsvc')->columnflag eq 'S'
+ || $part_svc->part_svc_column('domsvc')->columnflag eq 'F')) {
+ %domains = map { $_->svcnum => $_->domain }
+ map { qsearchs('svc_domain', { 'svcnum' => $_ }) }
+ split(',', $part_svc->part_svc_column('domsvc')->columnvalue);
+ }elsif ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) {
+ %domains = map { $_->svcnum => $_->domain }
+ map { qsearchs('svc_domain', { 'svcnum' => $_->svcnum }) }
+ map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
+ qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
+ }else{
+ %domains = map { $_->svcnum => $_->domain } qsearch('svc_domain', {} );
+ }
+
+ if ($part_svc && $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 ) {
+ $domains{$svc_domain->svcnum} = $svc_domain->domain;
+ }else{
+ warn "unknown svc_domain.svcnum for part_svc_column domsvc: ".
+ $part_svc->part_svc_column('domsvc')->columnvalue;
+
+ }
+ }
+
+ (%domains);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>
+
+=cut
+
+1;
diff --git a/FS/FS/svc_External_Common.pm b/FS/FS/svc_External_Common.pm
new file mode 100644
index 0000000..a5805aa
--- /dev/null
+++ b/FS/FS/svc_External_Common.pm
@@ -0,0 +1,199 @@
+package FS::svc_External_Common;
+
+use strict;
+use vars qw(@ISA);
+use FS::svc_Common;
+
+@ISA = qw( FS::svc_Common );
+
+=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
+
+FS::svc_External_Common is intended as a base class for table-specific classes
+to inherit from. FS::svc_External_Common is used for services which connect
+to externally tracked services via "id" and "table" fields.
+
+FS::svc_External_Common 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 search_sql
+
+Provides a default search_sql method which returns an SQL fragment to search
+the B<title> field.
+
+=cut
+
+sub search_sql {
+ my($class, $string) = @_;
+ $class->search_sql_field('title', $string);
+}
+
+=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
+
+=item label
+
+Returns a string identifying this external service in the form "id:title"
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->id. ':'. $self->title;
+}
+
+=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 replace 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_Parent_Mixin.pm b/FS/FS/svc_Parent_Mixin.pm
new file mode 100644
index 0000000..4501baf
--- /dev/null
+++ b/FS/FS/svc_Parent_Mixin.pm
@@ -0,0 +1,103 @@
+package FS::svc_Parent_Mixin;
+
+use strict;
+use NEXT;
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_svc;
+
+=head1 NAME
+
+FS::svc_Parent_Mixin - Mixin class for svc_ classes with a parent_svcnum field
+
+=head1 SYNOPSIS
+
+package FS::svc_table;
+use vars qw(@ISA);
+@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common );
+
+=head1 DESCRIPTION
+
+This is a mixin class for svc_ classes that contain a parent_svcnum field.
+
+=cut
+
+=head1 METHODS
+
+=over 4
+
+=item parent_cust_svc
+
+Returns the parent FS::cust_svc object.
+
+=cut
+
+sub parent_cust_svc {
+ my $self = shift;
+ qsearchs('cust_svc', { 'svcnum' => $self->parent_svcnum } );
+}
+
+=item parent_svc_x
+
+Returns the corresponding parent FS::svc_ object.
+
+=cut
+
+sub parent_svc_x {
+ my $self = shift;
+ $self->parent_cust_svc->svc_x;
+}
+
+=item children_cust_svc
+
+Returns a list of any child FS::cust_svc objects.
+
+Note: This is not recursive; it only returns direct children.
+
+=cut
+
+sub children_cust_svc {
+ my $self = shift;
+ qsearch('cust_svc', { 'parent_svcnum' => $self->svcnum } );
+}
+
+=item children_svc_x
+
+Returns the corresponding list of child FS::svc_ objects.
+
+=cut
+
+sub children_svc_x {
+ my $self = shift;
+ map { $_->svc_x } $self->children_cust_svc;
+}
+
+=item check
+
+This class provides a check subroutine which takes care of checking the
+parent_svcnum field. The svc_ class which uses it will call SUPER::check at
+the end of its own checks, and this class will call NEXT::check to pass
+the check "up the chain" (see L<NEXT>).
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_foreign_keyn('parent_svcnum', 'cust_svc', 'svcnum')
+ || $self->NEXT::check;
+
+}
+
+=back
+
+=head1 BUGS
+
+Do we need a recursive child finder for multi-layered children?
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>
+
+=cut
+
+1;
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
new file mode 100644
index 0000000..9236067
--- /dev/null
+++ b/FS/FS/svc_acct.pm
@@ -0,0 +1,3257 @@
+package FS::svc_acct;
+
+use strict;
+use base qw( FS::svc_Domain_Mixin FS::svc_CGPRule_Mixin FS::svc_Common );
+use vars qw( $DEBUG $me $conf $skip_fuzzyfiles
+ $dir_prefix @shells $usernamemin
+ $usernamemax $passwordmin $passwordmax
+ $username_ampersand $username_letter $username_letterfirst
+ $username_noperiod $username_nounderscore $username_nodash
+ $username_uppercase $username_percent $username_colon
+ $password_noampersand $password_noexclamation
+ $warning_template $warning_from $warning_subject $warning_mimetype
+ $warning_cc
+ $smtpmachine
+ $radius_password $radius_ip
+ $dirhash
+ @saltset @pw_set );
+use Scalar::Util qw( blessed );
+use Math::BigInt;
+use Carp;
+use Fcntl qw(:flock);
+use Date::Format;
+use Crypt::PasswdMD5 1.2;
+use Digest::SHA1 'sha1_base64';
+use Digest::MD5 'md5_base64';
+use Data::Dumper;
+use Text::Template;
+use Authen::Passphrase;
+use FS::UID qw( datasrc driver_name );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs fields dbh dbdef );
+use FS::Msgcat qw(gettext);
+use FS::UI::bytecount;
+use FS::UI::Web;
+use FS::part_pkg;
+use FS::part_svc;
+use FS::svc_acct_pop;
+use FS::cust_main_invoice;
+use FS::svc_domain;
+use FS::svc_pbx;
+use FS::raddb;
+use FS::queue;
+use FS::radius_usergroup;
+use FS::export_svc;
+use FS::part_export;
+use FS::svc_forward;
+use FS::svc_www;
+use FS::cdr;
+
+$DEBUG = 0;
+$me = '[FS::svc_acct]';
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( 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;
+ #blank->6, keep 0
+ $passwordmin = ( defined($passwordmin) && $passwordmin =~ /\d+/ )
+ ? $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');
+ $username_percent = $conf->exists('username-percent');
+ $username_colon = $conf->exists('username-colon');
+ $password_noampersand = $conf->exists('password-noexclamation');
+ $password_noexclamation = $conf->exists('password-noexclamation');
+ $dirhash = $conf->config('dirhash') || 0;
+ if ( $conf->exists('warning_email') ) {
+ $warning_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config('warning_email') ]
+ ) or warn "can't create warning email template: $Text::Template::ERROR";
+ $warning_from = $conf->config('warning_email-from'); # || 'your-isp-is-dum'
+ $warning_subject = $conf->config('warning_email-subject') || 'Warning';
+ $warning_mimetype = $conf->config('warning_email-mimetype') || 'text/plain';
+ $warning_cc = $conf->config('warning_email-cc');
+ } else {
+ $warning_template = '';
+ $warning_from = '';
+ $warning_subject = '';
+ $warning_mimetype = '';
+ $warning_cc = '';
+ }
+ $smtpmachine = $conf->config('smtpmachine');
+ $radius_password = $conf->config('radius-password') || 'Password';
+ $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
+ @pw_set = ( 'A'..'Z' ) if $conf->exists('password-generated-allcaps');
+}
+);
+
+@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 _password_encoding
+
+plain, crypt, ldap (or empty for autodetection)
+
+=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
+
+=item slipip
+
+IP address
+
+=item seconds
+
+=item upbytes
+
+=item downbyte
+
+=item totalbytes
+
+=item domsvc
+
+svcnum from svc_domain
+
+=item pbxsvc
+
+Optional svcnum from svc_pbx
+
+=item radius_I<Radius_Attribute>
+
+I<Radius-Attribute> (reply)
+
+=item rc_I<Radius_Attribute>
+
+I<Radius-Attribute> (check)
+
+=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_info {
+ {
+ 'name' => 'Account',
+ 'longname_plural' => 'Access accounts and mailboxes',
+ 'sorts' => [ 'username', 'uid', 'seconds', 'last_login' ],
+ 'display_weight' => 10,
+ 'cancel_weight' => 50,
+ 'fields' => {
+ 'dir' => 'Home directory',
+ 'uid' => {
+ label => 'UID',
+ def_info => 'set to fixed and blank for no UIDs',
+ type => 'text',
+ },
+ 'slipip' => 'IP address',
+ # 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!,
+ 'popnum' => {
+ label => 'Access number',
+ type => 'select',
+ select_table => 'svc_acct_pop',
+ select_key => 'popnum',
+ select_label => 'city',
+ disable_select => 1,
+ },
+ 'username' => {
+ label => 'Username',
+ type => 'text',
+ disable_default => 1,
+ disable_fixed => 1,
+ disable_select => 1,
+ },
+ 'password_selfchange' => { label => 'Password modification',
+ type => 'checkbox',
+ },
+ 'password_recover' => { label => 'Password recovery',
+ type => 'checkbox',
+ },
+ 'quota' => {
+ label => 'Quota', #Mail storage limit
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'file_quota'=> {
+ label => 'File storage limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'file_maxnum'=> {
+ label => 'Number of files limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'file_maxsize'=> {
+ label => 'File size limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ '_password' => 'Password',
+ 'gid' => {
+ label => 'GID',
+ def_info => 'when blank, defaults to UID',
+ type => 'text',
+ },
+ 'shell' => {
+ label => 'Shell',
+ def_info => 'set to blank for no shell tracking',
+ type => 'select',
+ #select_list => [ $conf->config('shells') ],
+ select_list => [ $conf ? $conf->config('shells') : () ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'finger' => 'Real name', # (GECOS)',
+ 'domsvc' => {
+ label => 'Domain',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain',
+ disable_inventory => 1,
+ },
+ 'pbxsvc' => { label => 'PBX',
+ type => 'select-svc_pbx.html',
+ disable_inventory => 1,
+ disable_select => 1, #UI wonky, pry works otherwise
+ },
+ 'usergroup' => {
+ label => 'RADIUS groups',
+ type => 'radius_usergroup_selector',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'seconds' => { label => 'Seconds',
+ label_sort => 'with Time Remaining',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ disable_part_svc_column => 1,
+ },
+ 'upbytes' => { label => 'Upload',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'downbytes' => { label => 'Download',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'totalbytes'=> { label => 'Total up and download',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'seconds_threshold' => { label => 'Seconds threshold',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ disable_part_svc_column => 1,
+ },
+ 'upbytes_threshold' => { label => 'Upload threshold',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'downbytes_threshold' => { label => 'Download threshold',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'totalbytes_threshold'=> { label => 'Total up and download threshold',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ 'format' => \&FS::UI::bytecount::display_bytecount,
+ 'parse' => \&FS::UI::bytecount::parse_bytecount,
+ disable_part_svc_column => 1,
+ },
+ 'last_login'=> {
+ label => 'Last login',
+ type => 'disabled',
+ },
+ 'last_logout'=> {
+ label => 'Last logout',
+ type => 'disabled',
+ },
+
+ 'cgp_aliases' => {
+ label => 'Communigate aliases',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ #settings
+ 'cgp_type'=> {
+ label => 'Communigate account type',
+ type => 'select',
+ select_list => [qw( MultiMailbox TextMailbox MailDirMailbox AGrade BGrade CGrade )],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_accessmodes' => {
+ label => 'Communigate enabled services',
+ type => 'communigate_pro-accessmodes',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_rulesallowed' => {
+ label => 'Allowed mail rules',
+ type => 'select',
+ select_list => [ '', 'No', 'Filter Only', 'All But Exec', 'Any' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_rpopallowed' => { label => 'RPOP modifications',
+ type => 'checkbox',
+ },
+ 'cgp_mailtoall' => { label => 'Accepts mail to "all"',
+ type => 'checkbox',
+ },
+ 'cgp_addmailtrailer' => { label => 'Add trailer to sent mail',
+ type => 'checkbox',
+ },
+ #XXX archive messages, mailing lists
+
+ #preferences
+ 'cgp_deletemode' => {
+ label => 'Communigate message delete method',
+ type => 'select',
+ select_list => [ 'Move To Trash', 'Immediately', 'Mark' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_emptytrash' => {
+ label => 'Communigate on logout remove trash',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_language' => {
+ label => 'Communigate language',
+ type => 'select',
+ select_list => [ '', qw( English Arabic Chinese Dutch French German Hebrew Italian Japanese Portuguese Russian Slovak Spanish Thai ) ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_timezone' => {
+ label => 'Communigate time zone',
+ type => 'select',
+ select_list => [ '',
+ 'HostOS',
+ '(+0100) Algeria/Congo',
+ '(+0200) Egypt/South Africa',
+ '(+0300) Saudi Arabia',
+ '(+0400) Oman',
+ '(+0500) Pakistan',
+ '(+0600) Bangladesh',
+ '(+0700) Thailand/Vietnam',
+ '(+0800) China/Malaysia',
+ '(+0900) Japan/Korea',
+ '(+1000) Queensland',
+ '(+1100) Micronesia',
+ '(+1200) Fiji',
+ '(+1300) Tonga/Kiribati',
+ '(+1400) Christmas Islands',
+ '(-0100) Azores/Cape Verde',
+ '(-0200) Fernando de Noronha',
+ '(-0300) Argentina/Uruguay',
+ '(-0400) Venezuela/Guyana',
+ '(-0500) Haiti/Peru',
+ '(-0600) Central America',
+ '(-0700) Arisona',
+ '(-0800) Adamstown',
+ '(-0900) Marquesas Islands',
+ '(-1000) Hawaii/Tahiti',
+ '(-1100) Samoa',
+ 'Asia/Afghanistan',
+ 'Asia/India',
+ 'Asia/Iran',
+ 'Asia/Iraq',
+ 'Asia/Israel',
+ 'Asia/Jordan',
+ 'Asia/Lebanon',
+ 'Asia/Syria',
+ 'Australia/Adelaide',
+ 'Australia/East',
+ 'Australia/NorthernTerritory',
+ 'Europe/Central',
+ 'Europe/Eastern',
+ 'Europe/Moscow',
+ 'Europe/Western',
+ 'GMT (+0000)',
+ 'Newfoundland',
+ 'NewZealand/Auckland',
+ 'NorthAmerica/Alaska',
+ 'NorthAmerica/Atlantic',
+ 'NorthAmerica/Central',
+ 'NorthAmerica/Eastern',
+ 'NorthAmerica/Mountain',
+ 'NorthAmerica/Pacific',
+ 'Russia/Ekaterinburg',
+ 'Russia/Irkutsk',
+ 'Russia/Kamchatka',
+ 'Russia/Krasnoyarsk',
+ 'Russia/Magadan',
+ 'Russia/Novosibirsk',
+ 'Russia/Vladivostok',
+ 'Russia/Yakutsk',
+ 'SouthAmerica/Brasil',
+ 'SouthAmerica/Chile',
+ 'SouthAmerica/Paraguay',
+ ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_skinname' => {
+ label => 'Communigate layout',
+ type => 'select',
+ select_list => [ '', '***', 'GoldFleece', 'Skin2' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_prontoskinname' => {
+ label => 'Communigate Pronto style',
+ type => 'select',
+ select_list => [ '', 'Pronto', 'Pronto-darkflame', 'Pronto-steel', 'Pronto-twilight', ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_sendmdnmode' => {
+ label => 'Communigate send read receipts',
+ type => 'select',
+ select_list => [ '', 'Never', 'Manually', 'Automatically' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+
+ #mail
+ #XXX vacation message, redirect all mail, mail rules
+ #XXX RPOP settings
+
+ },
+ };
+}
+
+sub table { 'svc_acct'; }
+
+sub table_dupcheck_fields { ( 'username', 'domsvc' ); }
+
+sub _fieldhandlers {
+ {
+ #false laziness with edit/svc_acct.cgi
+ 'usergroup' => sub {
+ my( $self, $groups ) = @_;
+ if ( ref($groups) eq 'ARRAY' ) {
+ $groups;
+ } elsif ( length($groups) ) {
+ [ split(/\s*,\s*/, $groups) ];
+ } else {
+ [];
+ }
+ },
+ };
+}
+
+sub last_login {
+ shift->_lastlog('in', @_);
+}
+
+sub last_logout {
+ shift->_lastlog('out', @_);
+}
+
+sub _lastlog {
+ my( $self, $op, $time ) = @_;
+
+ if ( defined($time) ) {
+ warn "$me last_log$op called on svcnum ". $self->svcnum.
+ ' ('. $self->email. "): $time\n"
+ if $DEBUG;
+
+ my $dbh = dbh;
+
+ my $sql = "UPDATE svc_acct SET last_log$op = ? WHERE svcnum = ?";
+ warn "$me $sql\n"
+ if $DEBUG;
+
+ my $sth = $dbh->prepare( $sql )
+ or die "Error preparing $sql: ". $dbh->errstr;
+ my $rv = $sth->execute($time, $self->svcnum);
+ die "Error executing $sql: ". $sth->errstr
+ unless defined($rv);
+ die "Can't update last_log$op for svcnum". $self->svcnum
+ if $rv == 0;
+
+ $self->{'Hash'}->{"last_log$op"} = $time;
+ }else{
+ $self->getfield("last_log$op");
+ }
+}
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+sub search_sql {
+ my( $class, $string ) = @_;
+ if ( $string =~ /^([^@]+)@([^@]+)$/ ) {
+ my( $username, $domain ) = ( $1, $2 );
+ my $q_username = dbh->quote($username);
+ my @svc_domain = qsearch('svc_domain', { 'domain' => $domain } );
+ if ( @svc_domain ) {
+ "svc_acct.username = $q_username AND ( ".
+ join( ' OR ', map { "svc_acct.domsvc = ". $_->svcnum; } @svc_domain ).
+ " )";
+ } else {
+ '1 = 0'; #false
+ }
+ } elsif ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
+ ' ( '.
+ $class->search_sql_field('slipip', $string ).
+ ' OR '.
+ $class->search_sql_field('username', $string ).
+ ' ) ';
+ } else {
+ $class->search_sql_field('username', $string);
+ }
+}
+
+=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns the "username@domain" string for this account.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->email(@_);
+}
+
+=item label_long [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns a longer string label for this acccount ("Real Name <username@domain>"
+if available, or "username@domain").
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=cut
+
+sub label_long {
+ my $self = shift;
+ my $label = $self->label(@_);
+ my $finger = $self->finger;
+ return $label unless $finger =~ /\S/;
+ my $maxlen = 40 - length($label) - length($self->cust_svc->part_svc->svc);
+ $finger = substr($finger, 0, $maxlen-3).'...' if length($finger) > $maxlen;
+ "$finger <$label>";
+}
+
+=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. Each element of the array can also optionally be a
+two-element array reference containing the child object and the name of an
+alternate field to be filled in with the newly-inserted svcnum, for example
+C<[ $svc_forward, 'srcsvc' ]>
+
+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 = @_;
+
+ if ( $DEBUG ) {
+ warn "[$me] insert called on $self: ". Dumper($self).
+ "\nwith options: ". Dumper(%options);
+ }
+
+ 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 @jobnums;
+ my $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;
+ }
+ }
+ }
+
+ unless ( $skip_fuzzyfiles ) {
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+ }
+
+ my $cust_pkg = $self->cust_svc->cust_pkg;
+
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+ my $agentnum = $cust_main->agentnum;
+
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto')
+ && ! $cust_main->invoicing_list_emailonly
+ ) {
+ my @invoicing_list = $cust_main->invoicing_list;
+ push @invoicing_list, $self->email;
+ $cust_main->invoicing_list(\@invoicing_list);
+ }
+
+ #welcome email
+ my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype)
+ = ('','','','','','');
+
+ if ( $conf->exists('welcome_email', $agentnum) ) {
+ $welcome_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ]
+ ) or warn "can't create welcome email template: $Text::Template::ERROR";
+ $welcome_from = $conf->config('welcome_email-from', $agentnum);
+ # || 'your-isp-is-dum'
+ $welcome_subject = $conf->config('welcome_email-subject', $agentnum)
+ || 'Welcome';
+ $welcome_subject_template = new Text::Template (
+ TYPE => 'STRING',
+ SOURCE => $welcome_subject,
+ ) or warn "can't create welcome email subject template: $Text::Template::ERROR";
+ $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum)
+ || 'text/plain';
+ }
+ if ( $welcome_template && $cust_pkg ) {
+ my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
+ if ( $to ) {
+
+ my %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,
+ );
+ 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_template->fill_in( HASH => \%hash, ),
+ 'mimetype' => $welcome_mimetype,
+ 'body' => $welcome_template->fill_in( HASH => \%hash, ),
+ );
+ 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
+}
+
+# set usage fields and thresholds if unset but set in a package def
+# AND the package already has a last bill date (otherwise they get double added)
+sub preinsert_hook_first {
+ my $self = shift;
+
+ return '' unless $self->pkgnum;
+
+ my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+ return '' unless $cust_pkg && $cust_pkg->last_bill;
+
+ my $part_pkg = $cust_pkg->part_pkg;
+ return '' unless $part_pkg && $part_pkg->can('usage_valuehash');
+
+ my %values = $part_pkg->usage_valuehash;
+ my $multiplier = $conf->exists('svc_acct-usage_threshold')
+ ? 1 - $conf->config('svc_acct-usage_threshold')/100
+ : 0.20; #doesn't matter
+
+ foreach ( keys %values ) {
+ next if $self->getfield($_);
+ $self->setfield( $_, $values{$_} );
+ $self->setfield( $_. '_threshold', int( $values{$_} * $multiplier ) )
+ if $conf->exists('svc_acct-usage_threshold');
+ }
+
+ ''; #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;
+ }
+ }
+
+ my $error = $self->SUPER::delete;
+ 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;
+ }
+ }
+
+ $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 = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ warn "$me replacing $old with $new\n" if $DEBUG;
+
+ my $error;
+
+ return "can't modify system account" if $old->_check_system;
+
+ {
+ #no warnings 'numeric'; #alas, a 5.006-ism
+ local($^W) = 0;
+
+ foreach my $xid (qw( uid gid )) {
+
+ return "Can't change $xid!"
+ if ! $conf->exists("svc_acct-edit_$xid")
+ && $old->$xid() != $new->$xid()
+ && $new->cust_svc->part_svc->part_svc_column($xid)->columnflag ne 'F'
+ }
+
+ }
+
+ #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 ] );
+ if ( $DEBUG ) {
+ warn $old->email. " old groups: ". join(' ',@{$old->usergroup}). "\n";
+ warn $new->email. "new groups: ". join(' ',@{$new->usergroup}). "\n";
+ }
+ 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";
+ }
+ }
+
+ }
+
+ $error = $new->SUPER::replace($old, @_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+
+ if ( $new->username ne $old->username && ! $skip_fuzzyfiles ) {
+ $error = $new->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=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 {
+ 'svcnum' => $self->svcnum,
+ 'job' => 'FS::svc_acct::append_fuzzyfiles'
+ };
+ my $error = $queue->insert($self->username);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+
+=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( $self->_fieldhandlers );
+ 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_foreign_keyn('pbxsvc', 'svc_pbx', 'svcnum' )
+ || $self->ut_textn('sec_phrase')
+ || $self->ut_snumbern('seconds')
+ || $self->ut_snumbern('upbytes')
+ || $self->ut_snumbern('downbytes')
+ || $self->ut_snumbern('totalbytes')
+ || $self->ut_enum('_password_encoding', ['',qw(plain crypt ldap)])
+ || $self->ut_enum('password_selfchange', [ '', 'Y' ])
+ || $self->ut_enum('password_recover', [ '', 'Y' ])
+ || $self->ut_textn('cgp_accessmodes')
+ || $self->ut_alphan('cgp_type')
+ || $self->ut_textn('cgp_aliases' ) #well
+ #settings
+ || $self->ut_alphasn('cgp_rulesallowed')
+ || $self->ut_enum('cgp_rpopallowed', [ '', 'Y' ])
+ || $self->ut_enum('cgp_mailtoall', [ '', 'Y' ])
+ || $self->ut_enum('cgp_addmailtrailer', [ '', 'Y' ])
+ #preferences
+ || $self->ut_alphasn('cgp_deletemode')
+ || $self->ut_alphan('cgp_emptytrash')
+ || $self->ut_alphan('cgp_language')
+ || $self->ut_textn('cgp_timezone')
+ || $self->ut_textn('cgp_skinname')
+ || $self->ut_textn('cgp_prontoskinname')
+ || $self->ut_alphan('cgp_sendmdnmode')
+ #XXX vacation message, redirect all mail, mail rules
+ #XXX RPOP settings
+ ;
+ return $error if $error;
+
+ my $cust_pkg;
+ local $username_letter = $username_letter;
+ if ($self->svcnum) {
+ my $cust_svc = $self->cust_svc
+ or return "no cust_svc record found for svcnum ". $self->svcnum;
+ my $cust_pkg = $cust_svc->cust_pkg;
+ }
+ if ($self->pkgnum) {
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );#complain?
+ }
+ if ($cust_pkg) {
+ $username_letter =
+ $conf->exists('username-letter', $cust_pkg->cust_main->agentnum);
+ }
+
+ 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');
+ }
+ unless ( $username_percent ) {
+ $recref->{username} =~ /\%/ and return gettext('illegal_username');
+ }
+ unless ( $username_colon ) {
+ $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} !~ /^(root|toor|smtp)$/;
+
+ 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. "\'; ".
+ "shells configuration value 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}='' );
+ }
+
+ unless ( $part_svc->part_svc_column('dir')->columnflag eq 'F' ) {
+
+ $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};
+ ;
+ }
+
+ }
+
+ # $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);
+
+ for (qw( quota file_quota file_maxsize )) {
+ $recref->{$_} =~ /^(\w*)$/ or return "Illegal $_";
+ $recref->{$_} = $1;
+ }
+ $recref->{file_maxnum} =~ /^\s*(\d*)\s*$/ or return "Illegal file_maxnum";
+ $recref->{file_maxnum} = $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($_);
+ }
+
+ # First, if _password is blank, generate one and set default encoding.
+ if ( ! $recref->{_password} ) {
+ $error = $self->set_password('');
+ }
+ # But if there's a _password but no encoding, assume it's plaintext and
+ # set it to default encoding.
+ elsif ( ! $recref->{_password_encoding} ) {
+ $error = $self->set_password($recref->{_password});
+ }
+ return $error if $error;
+
+ # Next, check _password to ensure compliance with the encoding.
+ if ( $recref->{_password_encoding} eq 'ldap' ) {
+
+ if ( $recref->{_password} =~ /^(\{[\w\-]+\})(!?.{0,64})$/ ) {
+ $recref->{_password} = uc($1).$2;
+ } else {
+ return 'Illegal (ldap-encoded) password: '. $recref->{_password};
+ }
+
+ } elsif ( $recref->{_password_encoding} eq 'crypt' ) {
+
+ if ( $recref->{_password} =~
+ #/^(\$\w+\$.*|[\w\+\/]{13}|_[\w\+\/]{19}|\*)$/
+ /^(!!?)?(\$\w+\$.*|[\w\+\/\.]{13}|_[\w\+\/\.]{19}|\*)$/
+ ) {
+
+ $recref->{_password} = ( defined($1) ? $1 : '' ). $2;
+
+ } else {
+ return 'Illegal (crypt-encoded) password: '. $recref->{_password};
+ }
+
+ } elsif ( $recref->{_password_encoding} eq 'plain' ) {
+ # Password randomization is now in set_password.
+ # Strip whitespace characters, check length requirements, etc.
+ if ( $recref->{_password} =~ /^([^\t\n]{$passwordmin,$passwordmax})$/ ) {
+ $recref->{_password} = $1;
+ } else {
+ return gettext('illegal_password'). " $passwordmin-$passwordmax ".
+ FS::Msgcat::_gettext('illegal_password_characters').
+ ": ". $recref->{_password};
+ }
+
+ if ( $password_noampersand ) {
+ $recref->{_password} =~ /\&/ and return gettext('illegal_password');
+ }
+ if ( $password_noexclamation ) {
+ $recref->{_password} =~ /\!/ and return gettext('illegal_password');
+ }
+ }
+ else {
+ return "invalid password encoding ('".$recref->{_password_encoding}."'";
+ }
+ $self->SUPER::check;
+
+}
+
+
+sub _password_encryption {
+ my $self = shift;
+ my $encoding = lc($self->_password_encoding);
+ return if !$encoding;
+ return 'plain' if $encoding eq 'plain';
+ if($encoding eq 'crypt') {
+ my $pass = $self->_password;
+ $pass =~ s/^\*SUSPENDED\* //;
+ $pass =~ s/^!!?//;
+ return 'md5' if $pass =~ /^\$1\$/;
+ #return 'blowfish' if $self->_password =~ /^\$2\$/;
+ return 'des' if length($pass) == 13;
+ return;
+ }
+ if($encoding eq 'ldap') {
+ uc($self->_password) =~ /^\{([\w-]+)\}/;
+ return 'crypt' if $1 eq 'CRYPT' or $1 eq 'DES';
+ return 'plain' if $1 eq 'PLAIN' or $1 eq 'CLEARTEXT';
+ return 'md5' if $1 eq 'MD5';
+ return 'sha1' if $1 eq 'SHA' or $1 eq 'SHA-1';
+
+ return;
+ }
+ return;
+}
+
+sub get_cleartext_password {
+ my $self = shift;
+ if($self->_password_encryption eq 'plain') {
+ if($self->_password_encoding eq 'ldap') {
+ $self->_password =~ /\{\w+\}(.*)$/;
+ return $1;
+ }
+ else {
+ return $self->_password;
+ }
+ }
+ return;
+}
+
+
+=item set_password
+
+Set the cleartext password for the account. If _password_encoding is set, the
+new password will be encoded according to the existing method (including
+encryption mode, if it can be determined). Otherwise,
+config('default-password-encoding') is used.
+
+If no password is supplied (or a zero-length password when minimum password length
+is >0), one will be generated randomly.
+
+=cut
+
+sub set_password {
+ my( $self, $pass ) = ( shift, shift );
+
+ warn "[$me] set_password (to $pass) called on $self: ". Dumper($self)
+ if $DEBUG;
+
+ my $failure = gettext('illegal_password'). " $passwordmin-$passwordmax ".
+ FS::Msgcat::_gettext('illegal_password_characters').
+ ": ". $pass;
+
+ my( $encoding, $encryption ) = ('', '');
+
+ if ( $self->_password_encoding ) {
+ $encoding = $self->_password_encoding;
+ # identify existing encryption method, try to use it.
+ $encryption = $self->_password_encryption;
+ if (!$encryption) {
+ # use the system default
+ undef $encoding;
+ }
+ }
+
+ if ( !$encoding ) {
+ # set encoding to system default
+ ($encoding, $encryption) =
+ split(/-/, lc($conf->config('default-password-encoding')));
+ $encoding ||= 'legacy';
+ $self->_password_encoding($encoding);
+ }
+
+ if ( $encoding eq 'legacy' ) {
+
+ # The legacy behavior from check():
+ # If the password is blank, randomize it and set encoding to 'plain'.
+ if(!defined($pass) or (length($pass) == 0 and $passwordmin)) {
+ $pass = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+ $self->_password_encoding('plain');
+ } else {
+ # Prefix + valid-length password
+ if ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) {
+ $pass = $1.$3;
+ $self->_password_encoding('plain');
+ # Prefix + crypt string
+ } elsif ( $pass =~ /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/ ) {
+ $pass = $1.$3;
+ $self->_password_encoding('crypt');
+ # Various disabled crypt passwords
+ } elsif ( $pass eq '*' || $pass eq '!' || $pass eq '!!' ) {
+ $self->_password_encoding('crypt');
+ } else {
+ return $failure;
+ }
+ }
+
+ $self->_password($pass);
+ return;
+
+ }
+
+ return $failure
+ if $passwordmin && length($pass) < $passwordmin
+ or $passwordmax && length($pass) > $passwordmax;
+
+ if ( $encoding eq 'crypt' ) {
+ if ($encryption eq 'md5') {
+ $pass = unix_md5_crypt($pass);
+ } elsif ($encryption eq 'des') {
+ $pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]);
+ }
+
+ } elsif ( $encoding eq 'ldap' ) {
+ if ($encryption eq 'md5') {
+ $pass = md5_base64($pass);
+ } elsif ($encryption eq 'sha1') {
+ $pass = sha1_base64($pass);
+ } elsif ($encryption eq 'crypt') {
+ $pass = crypt($pass, $saltset[int(rand(64))].$saltset[int(rand(64))]);
+ }
+ # else $encryption eq 'plain', do nothing
+ $pass = '{'.uc($encryption).'}'.$pass;
+ }
+ # else encoding eq 'plain'
+
+ $self->_password($pass);
+ return;
+}
+
+=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 method 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;
+
+ my $global_unique = $conf->config('global_unique-username') || 'none';
+ return '' if $global_unique eq 'disabled';
+
+ $self->lock_table;
+
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } );
+ unless ( $part_svc ) {
+ return 'unknown svcpart '. $self->svcpart;
+ }
+
+ my @dup_user = grep { !$self->svcnum || $_->svcnum != $self->svcnum }
+ qsearch( 'svc_acct', { 'username' => $self->username } );
+ return gettext('username_in_use')
+ if $global_unique eq 'username' && @dup_user;
+
+ my @dup_userdomain = grep { !$self->svcnum || $_->svcnum != $self->svcnum }
+ 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 { !$self->svcnum || $_->svcnum != $self->svcnum }
+ 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 ". $self->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 ". $self->email.
+ ": 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 ". $self->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;
+
+ return %{ $self->{'radius_reply'} }
+ if exists $self->{'radius_reply'};
+
+ 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;
+ }
+
+ if ( $self->seconds !~ /^$/ ) {
+ $reply{'Session-Timeout'} = $self->seconds;
+ }
+
+ if ( $conf->exists('radius-chillispot-max') ) {
+ #http://dev.coova.org/svn/coova-chilli/doc/dictionary.chillispot
+
+ #hmm. just because sqlradius.pm says so?
+ my %whatis = (
+ 'input' => 'up',
+ 'output' => 'down',
+ 'total' => 'total',
+ );
+
+ foreach my $what (qw( input output total )) {
+ my $is = $whatis{$what}.'bytes';
+ if ( $self->$is() =~ /\d/ ) {
+ my $big = new Math::BigInt $self->$is();
+ $big = new Math::BigInt '0' if $big->is_neg();
+ my $att = "Chillispot-Max-\u$what";
+ $reply{"$att-Octets"} = $big->copy->band(0xffffffff)->bstr;
+ $reply{"$att-Gigawords"} = $big->copy->brsft(32)->bstr;
+ }
+ }
+
+ }
+
+ %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;
+
+ return %{ $self->{'radius_check'} }
+ if exists $self->{'radius_check'};
+
+ my %check =
+ 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 );
+
+
+ my($pw_attrib, $password) = $self->radius_password;
+ $check{$pw_attrib} = $password;
+
+ my $cust_svc = $self->cust_svc;
+ if ( $cust_svc ) {
+ my $cust_pkg = $cust_svc->cust_pkg;
+ if ( $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill ) {
+ $check{'Expiration'} = time2str('%B %e %Y %T', $cust_pkg->bill ); #http://lists.cistron.nl/pipermail/freeradius-users/2005-January/040184.html
+ }
+ } else {
+ warn "WARNING: no cust_svc record for svc_acct.svcnum ". $self->svcnum.
+ "; can't set Expiration\n"
+ unless $cust_svc;
+ }
+
+ %check;
+
+}
+
+=item radius_password
+
+Returns a key/value pair containing the RADIUS attribute name and value
+for the password.
+
+=cut
+
+sub radius_password {
+ my $self = shift;
+
+ my $pw_attrib;
+ if ( $self->_password_encoding eq 'ldap' ) {
+ $pw_attrib = 'Password-With-Header';
+ } elsif ( $self->_password_encoding eq 'crypt' ) {
+ $pw_attrib = 'Crypt-Password';
+ } elsif ( $self->_password_encoding eq 'plain' ) {
+ $pw_attrib = $radius_password;
+ } else {
+ $pw_attrib = length($self->_password) <= 12
+ ? $radius_password
+ : 'Crypt-Password';
+ }
+
+ ($pw_attrib, $self->_password);
+
+}
+
+=item snapshot
+
+This method instructs the object to "snapshot" or freeze RADIUS check and
+reply attributes to the current values.
+
+=cut
+
+#bah, my english is too broken this morning
+#Of note is the "Expiration" attribute, which, for accounts in prepaid packages, is typically defined on-the-fly as the associated packages cust_pkg.bill. (This is used by
+#the FS::cust_pkg's replace method to trigger the correct export updates when
+#package dates change)
+
+sub snapshot {
+ my $self = shift;
+
+ $self->{$_} = { $self->$_() }
+ foreach qw( radius_reply radius_check );
+
+}
+
+=item forget_snapshot
+
+This methos instructs the object to forget any previously snapshotted
+RADIUS check and reply attributes.
+
+=cut
+
+sub forget_snapshot {
+ my $self = shift;
+
+ delete $self->{$_}
+ foreach qw( radius_reply radius_check );
+
+}
+
+=item domain [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns the domain associated with this account.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=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 cust_svc
+
+Returns the FS::cust_svc record for this account (see L<FS::cust_svc>).
+
+=cut
+
+#inherited from svc_Common
+
+=item email [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns an email address associated with the account.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=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 decrement_upbytes OCTETS
+
+Decrements the I<upbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_upbytes {
+ shift->_op_usage('-', 'upbytes', @_);
+}
+
+=item increment_upbytes OCTETS
+
+Increments the I<upbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_upbytes {
+ shift->_op_usage('+', 'upbytes', @_);
+}
+
+=item decrement_downbytes OCTETS
+
+Decrements the I<downbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_downbytes {
+ shift->_op_usage('-', 'downbytes', @_);
+}
+
+=item increment_downbytes OCTETS
+
+Increments the I<downbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_downbytes {
+ shift->_op_usage('+', 'downbytes', @_);
+}
+
+=item decrement_totalbytes OCTETS
+
+Decrements the I<totalbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_totalbytes {
+ shift->_op_usage('-', 'totalbytes', @_);
+}
+
+=item increment_totalbytes OCTETS
+
+Increments the I<totalbytes> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_totalbytes {
+ shift->_op_usage('+', 'totalbytes', @_);
+}
+
+=item decrement_seconds SECONDS
+
+Decrements the I<seconds> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_seconds {
+ shift->_op_usage('-', 'seconds', @_);
+}
+
+=item increment_seconds SECONDS
+
+Increments the I<seconds> field of this record by the given amount. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_seconds {
+ shift->_op_usage('+', 'seconds', @_);
+}
+
+
+my %op2action = (
+ '-' => 'suspend',
+ '+' => 'unsuspend',
+);
+my %op2condition = (
+ '-' => sub { my($self, $column, $amount) = @_;
+ $self->$column - $amount <= 0;
+ },
+ '+' => sub { my($self, $column, $amount) = @_;
+ ($self->$column || 0) + $amount > 0;
+ },
+);
+my %op2warncondition = (
+ '-' => sub { my($self, $column, $amount) = @_;
+ my $threshold = $column . '_threshold';
+ $self->$column - $amount <= $self->$threshold + 0;
+ },
+ '+' => sub { my($self, $column, $amount) = @_;
+ ($self->$column || 0) + $amount > 0;
+ },
+);
+
+sub _op_usage {
+ my( $self, $op, $column, $amount ) = @_;
+
+ warn "$me _op_usage called for $column on svcnum ". $self->svcnum.
+ ' ('. $self->email. "): $op $amount\n"
+ if $DEBUG;
+
+ return '' unless $amount;
+
+ 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 $sql = "UPDATE svc_acct SET $column = ".
+ " CASE WHEN $column IS NULL THEN 0 ELSE $column END ". #$column||0
+ " $op ? WHERE svcnum = ?";
+ warn "$me $sql\n"
+ if $DEBUG;
+
+ my $sth = $dbh->prepare( $sql )
+ or die "Error preparing $sql: ". $dbh->errstr;
+ my $rv = $sth->execute($amount, $self->svcnum);
+ die "Error executing $sql: ". $sth->errstr
+ unless defined($rv);
+ die "Can't update $column for svcnum". $self->svcnum
+ if $rv == 0;
+
+ #$self->snapshot; #not necessary, we retain the old values
+ #create an object with the updated usage values
+ my $new = qsearchs('svc_acct', { 'svcnum' => $self->svcnum });
+ #call exports
+ my $error = $new->replace($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error replacing: $error";
+ }
+
+ #overlimit_action eq 'cancel' handling
+ my $cust_pkg = $self->cust_svc->cust_pkg;
+ if ( $cust_pkg
+ && $cust_pkg->part_pkg->option('overlimit_action', 1) eq 'cancel'
+ && $op eq '-' && &{$op2condition{$op}}($self, $column, $amount)
+ )
+ {
+
+ my $error = $cust_pkg->cancel; #XXX should have a reason
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error cancelling: $error";
+ }
+
+ #nothing else is relevant if we're cancelling, so commit & return success
+ warn "$me update successful; committing\n"
+ if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+
+ }
+
+ my $action = $op2action{$op};
+
+ if ( &{$op2condition{$op}}($self, $column, $amount) &&
+ ( $action eq 'suspend' && !$self->overlimit
+ || $action eq 'unsuspend' && $self->overlimit )
+ ) {
+
+ my $error = $self->_op_overlimit($action);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ if ( $conf->exists("svc_acct-usage_$action")
+ && &{$op2condition{$op}}($self, $column, $amount) ) {
+ #my $error = $self->$action();
+ my $error = $self->cust_svc->cust_pkg->$action();
+ # $error ||= $self->overlimit($action);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error ${action}ing: $error";
+ }
+ }
+
+ if ($warning_template && &{$op2warncondition{$op}}($self, $column, $amount)) {
+ my $wqueue = new FS::queue {
+ 'svcnum' => $self->svcnum,
+ 'job' => 'FS::svc_acct::reached_threshold',
+ };
+
+ my $to = '';
+ if ($op eq '-'){
+ $to = $warning_cc if &{$op2condition{$op}}($self, $column, $amount);
+ }
+
+ # x_threshold race
+ my $error = $wqueue->insert(
+ 'svcnum' => $self->svcnum,
+ 'op' => $op,
+ 'column' => $column,
+ 'to' => $to,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error queuing threshold activity: $error";
+ }
+ }
+
+ warn "$me update successful; committing\n"
+ if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+sub _op_overlimit {
+ my( $self, $action ) = @_;
+
+ 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_pkg = $self->cust_svc->cust_pkg;
+
+ my $conf_overlimit =
+ $cust_pkg
+ ? $conf->config('overlimit_groups', $cust_pkg->cust_main->agentnum )
+ : $conf->config('overlimit_groups');
+
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+
+ my $groups = $conf_overlimit || $part_export->option('overlimit_groups');
+ next unless $groups;
+
+ my $gref = &{ $self->_fieldhandlers->{'usergroup'} }( $self, $groups );
+
+ my $other = new FS::svc_acct $self->hashref;
+ $other->usergroup( $gref );
+
+ my($new,$old);
+ if ($action eq 'suspend') {
+ $new = $other;
+ $old = $self;
+ } else { # $action eq 'unsuspend'
+ $new = $self;
+ $old = $other;
+ }
+
+ my $error = $part_export->export_replace($new, $old)
+ || $self->overlimit($action);
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error replacing radius groups: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+sub set_usage {
+ my( $self, $valueref, %options ) = @_;
+
+ warn "$me set_usage called for svcnum ". $self->svcnum.
+ ' ('. $self->email. "): ".
+ join(', ', map { "$_ => " . $valueref->{$_}} keys %$valueref) . "\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';
+
+ local $FS::svc_Common::noexport_hack = 1;
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $reset = 0;
+ my %handyhash = ();
+ if ( $options{null} ) {
+ %handyhash = ( map { ( $_ => 'NULL', $_."_threshold" => 'NULL' ) }
+ qw( seconds upbytes downbytes totalbytes )
+ );
+ }
+ foreach my $field (keys %$valueref){
+ $reset = 1 if $valueref->{$field};
+ $self->setfield($field, $valueref->{$field});
+ $self->setfield( $field.'_threshold',
+ int($self->getfield($field)
+ * ( $conf->exists('svc_acct-usage_threshold')
+ ? 1 - $conf->config('svc_acct-usage_threshold')/100
+ : 0.20
+ )
+ )
+ );
+ $handyhash{$field} = $self->getfield($field);
+ $handyhash{$field.'_threshold'} = $self->getfield($field.'_threshold');
+ }
+ #my $error = $self->replace; #NO! we avoid the call to ->check for
+ #die $error if $error; #services not explicity changed via the UI
+
+ my $sql = "UPDATE svc_acct SET " .
+ join (',', map { "$_ = $handyhash{$_}" } (keys %handyhash) ).
+ " WHERE svcnum = ". $self->svcnum;
+
+ warn "$me $sql\n"
+ if $DEBUG;
+
+ if (scalar(keys %handyhash)) {
+ my $sth = $dbh->prepare( $sql )
+ or die "Error preparing $sql: ". $dbh->errstr;
+ my $rv = $sth->execute();
+ die "Error executing $sql: ". $sth->errstr
+ unless defined($rv);
+ die "Can't update usage for svcnum ". $self->svcnum
+ if $rv == 0;
+ }
+
+ #$self->snapshot; #not necessary, we retain the old values
+ #create an object with the updated usage values
+ my $new = qsearchs('svc_acct', { 'svcnum' => $self->svcnum });
+ local($FS::Record::nowarn_identical) = 1;
+ my $error = $new->replace($self); #call exports
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error replacing: $error";
+ }
+
+ if ( $reset ) {
+
+ my $error = '';
+
+ $error = $self->_op_overlimit('unsuspend')
+ if $self->overlimit;;
+
+ $error ||= $self->cust_svc->cust_pkg->unsuspend
+ if $conf->exists("svc_acct-usage_unsuspend");
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error unsuspending: $error";
+ }
+
+ }
+
+ warn "$me update successful; committing\n"
+ if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+
+=item recharge HASHREF
+
+ Increments usage columns by the amount specified in HASHREF as
+ column=>amount pairs.
+
+=cut
+
+sub recharge {
+ my ($self, $vhash) = @_;
+
+ if ( $DEBUG ) {
+ warn "[$me] recharge called on $self: ". Dumper($self).
+ "\nwith vhash: ". Dumper($vhash);
+ }
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $error = '';
+
+ foreach my $column (keys %$vhash){
+ $error ||= $self->_op_usage('+', $column, $vhash->{$column});
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ }else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+ return $error;
+}
+
+=item is_rechargeable
+
+Returns true if this svc_account can be "recharged" and false otherwise.
+
+=cut
+
+sub is_rechargable {
+ my $self = shift;
+ $self->seconds ne ''
+ || $self->upbytes ne ''
+ || $self->downbytes ne ''
+ || $self->totalbytes ne '';
+}
+
+=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 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 {
+ my $self = shift;
+ $self->cust_svc->get_session_history(@_);
+}
+
+=item last_login_text
+
+Returns text describing the time of last login.
+
+=cut
+
+sub last_login_text {
+ my $self = shift;
+ $self->last_login ? ctime($self->last_login) : 'unknown';
+}
+
+=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ]
+
+=cut
+
+sub get_cdrs {
+ my($self, $start, $end, %opt ) = @_;
+
+ my $did = $self->username; #yup
+
+ my $prefix = $opt{'default_prefix'}; #convergent.au '+61'
+
+ my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : '';
+
+ #SELECT $for_update * FROM cdr
+ # WHERE calldate >= $start #need a conversion
+ # AND calldate < $end #ditto
+ # AND ( charged_party = "$did"
+ # OR charged_party = "$prefix$did" #if length($prefix);
+ # OR ( ( charged_party IS NULL OR charged_party = '' )
+ # AND
+ # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix)
+ # )
+ # )
+ # AND ( freesidestatus IS NULL OR freesidestatus = '' )
+
+ my $charged_or_src;
+ if ( length($prefix) ) {
+ $charged_or_src =
+ " AND ( charged_party = '$did'
+ OR charged_party = '$prefix$did'
+ OR ( ( charged_party IS NULL OR charged_party = '' )
+ AND
+ ( src = '$did' OR src = '$prefix$did' )
+ )
+ )
+ ";
+ } else {
+ $charged_or_src =
+ " AND ( charged_party = '$did'
+ OR ( ( charged_party IS NULL OR charged_party = '' )
+ AND
+ src = '$did'
+ )
+ )
+ ";
+
+ }
+
+ qsearch(
+ 'select' => "$for_update *",
+ 'table' => 'cdr',
+ 'hashref' => {
+ #( freesidestatus IS NULL OR freesidestatus = '' )
+ 'freesidestatus' => '',
+ },
+ 'extra_sql' => $charged_or_src,
+
+ );
+
+}
+
+=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 ) {
+ confess "explicitly specified usergroup not an arrayref: ". $self->usergroup
+ unless ref($self->usergroup) eq 'ARRAY';
+ #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 successful 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\* //;
+
+ if ( $self->_password_encoding eq 'ldap' ) {
+
+ my $auth = from_rfc2307 Authen::Passphrase $self->_password;
+ return $auth->match($check_password);
+
+ } elsif ( $self->_password_encoding eq 'crypt' ) {
+
+ my $auth = from_crypt Authen::Passphrase $self->_password;
+ return $auth->match($check_password);
+
+ } elsif ( $self->_password_encoding eq 'plain' ) {
+
+ return $check_password eq $password;
+
+ } else {
+
+ #XXX this could be replaced with Authen::Passphrase stuff
+
+ 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 [ DEFAULT_ENCRYPTION_TYPE ]
+
+Returns an encrypted password, either by passing through an encrypted password
+in the database or by encrypting a plaintext password from the database.
+
+The optional DEFAULT_ENCRYPTION_TYPE parameter can be set to I<crypt> (classic
+UNIX DES crypt), I<md5> (md5 crypt supported by most modern Linux and BSD
+distrubtions), or (eventually) I<blowfish> (blowfish hashing supported by
+OpenBSD, SuSE, other Linux distibutions with pam_unix2, etc.). The default
+encryption type is only used if the password is not already encrypted in the
+database.
+
+=cut
+
+sub crypt_password {
+ my $self = shift;
+
+ if ( $self->_password_encoding eq 'ldap' ) {
+
+ if ( $self->_password =~ /^\{(PLAIN|CLEARTEXT)\}(.+)$/ ) {
+ my $plain = $2;
+
+ #XXX this could be replaced with Authen::Passphrase stuff
+
+ my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
+ if ( $encryption eq 'crypt' ) {
+ crypt(
+ $self->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ } elsif ( $encryption eq 'md5' ) {
+ unix_md5_crypt( $self->_password );
+ } elsif ( $encryption eq 'blowfish' ) {
+ croak "unknown encryption method $encryption";
+ } else {
+ croak "unknown encryption method $encryption";
+ }
+
+ } elsif ( $self->_password =~ /^\{CRYPT\}(.+)$/ ) {
+ $1;
+ }
+
+ } elsif ( $self->_password_encoding eq 'crypt' ) {
+
+ return $self->_password;
+
+ } elsif ( $self->_password_encoding eq 'plain' ) {
+
+ #XXX this could be replaced with Authen::Passphrase stuff
+
+ my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
+ if ( $encryption eq 'crypt' ) {
+ crypt(
+ $self->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ } elsif ( $encryption eq 'md5' ) {
+ unix_md5_crypt( $self->_password );
+ } elsif ( $encryption eq 'blowfish' ) {
+ croak "unknown encryption method $encryption";
+ } else {
+ croak "unknown encryption method $encryption";
+ }
+
+ } else {
+
+ if ( length($self->_password) == 13
+ || $self->_password =~ /^\$(1|2a?)\$/
+ || $self->_password =~ /^(\*|NP|\*LK\*|!!?)$/
+ )
+ {
+ $self->_password;
+ } else {
+
+ #XXX this could be replaced with Authen::Passphrase stuff
+
+ my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
+ if ( $encryption eq 'crypt' ) {
+ crypt(
+ $self->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ } elsif ( $encryption eq 'md5' ) {
+ unix_md5_crypt( $self->_password );
+ } elsif ( $encryption eq 'blowfish' ) {
+ croak "unknown encryption method $encryption";
+ } else {
+ croak "unknown encryption method $encryption";
+ }
+
+ }
+
+ }
+
+}
+
+=item ldap_password [ DEFAULT_ENCRYPTION_TYPE ]
+
+Returns an encrypted password in "LDAP" format, with a curly-bracked prefix
+describing the format, for example, "{PLAIN}himom", "{CRYPT}94pAVyK/4oIBk" or
+"{MD5}5426824942db4253f87a1009fd5d2d4".
+
+The optional DEFAULT_ENCRYPTION_TYPE is not yet used, but the idea is for it
+to work the same as the B</crypt_password> method.
+
+=cut
+
+sub ldap_password {
+ my $self = shift;
+ #eventually should check a "password-encoding" field
+
+ if ( $self->_password_encoding eq 'ldap' ) {
+
+ return $self->_password;
+
+ } elsif ( $self->_password_encoding eq 'crypt' ) {
+
+ if ( length($self->_password) == 13 ) { #crypt
+ return '{CRYPT}'. $self->_password;
+ } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5
+ return '{MD5}'. $1;
+ #} elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish
+ # die "Blowfish encryption not supported in this context, svcnum ".
+ # $self->svcnum. "\n";
+ } else {
+ warn "encryption method not (yet?) supported in LDAP context";
+ return '{CRYPT}*'; #unsupported, should not auth
+ }
+
+ } elsif ( $self->_password_encoding eq 'plain' ) {
+
+ return '{PLAIN}'. $self->_password;
+
+ #return '{CLEARTEXT}'. $self->_password; #?
+
+ } else {
+
+ if ( length($self->_password) == 13 ) { #crypt
+ return '{CRYPT}'. $self->_password;
+ } elsif ( $self->_password =~ /^\$1\$(.*)$/ && length($1) == 31 ) { #passwdMD5
+ return '{MD5}'. $1;
+ } elsif ( $self->_password =~ /^\$2a?\$(.*)$/ ) { #Blowfish
+ warn "Blowfish encryption not supported in this context, svcnum ".
+ $self->svcnum. "\n";
+ return '{CRYPT}*';
+
+ #are these two necessary anymore?
+ } elsif ( $self->_password =~ /^(\w{48})$/ ) { #LDAP SSHA
+ return '{SSHA}'. $1;
+ } elsif ( $self->_password =~ /^(\w{64})$/ ) { #LDAP NS-MTA-MD5
+ return '{NS-MTA-MD5}'. $1;
+
+ } else { #plaintext
+ return '{PLAIN}'. $self->_password;
+
+ #return '{CLEARTEXT}'. $self->_password; #?
+
+ #XXX this could be replaced with Authen::Passphrase stuff if it gets used
+ #my $encryption = ( scalar(@_) && $_[0] ) ? shift : 'crypt';
+ #if ( $encryption eq 'crypt' ) {
+ # return '{CRYPT}'. crypt(
+ # $self->_password,
+ # $saltset[int(rand(64))].$saltset[int(rand(64))]
+ # );
+ #} elsif ( $encryption eq 'md5' ) {
+ # unix_md5_crypt( $self->_password );
+ #} elsif ( $encryption eq 'blowfish' ) {
+ # croak "unknown encryption method $encryption";
+ #} else {
+ # croak "unknown encryption method $encryption";
+ #}
+ }
+
+ }
+
+}
+
+=item domain_slash_username
+
+Returns $domain/$username/
+
+=cut
+
+sub domain_slash_username {
+ my $self = shift;
+ $self->domain. '/'. $self->username. '/';
+}
+
+=item virtual_maildir
+
+Returns $domain/maildirs/$username/
+
+=cut
+
+sub virtual_maildir {
+ my $self = shift;
+ $self->domain. '/maildirs/'. $self->username. '/';
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item search HASHREF
+
+Class method which returns a qsearch hash expression to search for parameters
+specified in HASHREF. Valid parameters are
+
+=over 4
+
+=item domain
+
+=item domsvc
+
+=item unlinked
+
+=item agentnum
+
+=item pkgpart
+
+Arrayref of pkgparts
+
+=item pkgpart
+
+=item where
+
+Arrayref of additional WHERE clauses, will be ANDed together.
+
+=item order_by
+
+=item cust_fields
+
+=back
+
+=cut
+
+sub search {
+ my ($class, $params) = @_;
+
+ my @where = ();
+
+ # domain
+ if ( $params->{'domain'} ) {
+ my $svc_domain = qsearchs('svc_domain', { 'domain'=>$params->{'domain'} } );
+ #preserve previous behavior & bubble up an error if $svc_domain not found?
+ push @where, 'domsvc = '. $svc_domain->svcnum if $svc_domain;
+ }
+
+ # domsvc
+ if ( $params->{'domsvc'} =~ /^(\d+)$/ ) {
+ push @where, "domsvc = $1";
+ }
+
+ #unlinked
+ push @where, 'pkgnum IS NULL' if $params->{'unlinked'};
+
+ #agentnum
+ if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where, "agentnum = $1";
+ }
+
+ #custnum
+ if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where, "custnum = $1";
+ }
+
+ #pkgpart
+ if ( $params->{'pkgpart'} && scalar(@{ $params->{'pkgpart'} }) ) {
+ #XXX untaint or sql quote
+ push @where,
+ 'cust_pkg.pkgpart IN ('. join(',', @{ $params->{'pkgpart'} } ). ')';
+ }
+
+ # popnum
+ if ( $params->{'popnum'} =~ /^(\d+)$/ ) {
+ push @where, "popnum = $1";
+ }
+
+ # svcpart
+ if ( $params->{'svcpart'} =~ /^(\d+)$/ ) {
+ push @where, "svcpart = $1";
+ }
+
+
+ # here is the agent virtualization
+ #if ($params->{CurrentUser}) {
+ # my $access_user =
+ # qsearchs('access_user', { username => $params->{CurrentUser} });
+ #
+ # if ($access_user) {
+ # push @where, $access_user->agentnums_sql('table'=>'cust_main');
+ # }else{
+ # push @where, "1=0";
+ # }
+ #} else {
+ push @where, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'table' => 'cust_main',
+ 'null_right' => 'View/link unlinked services',
+ );
+ #}
+
+ push @where, @{ $params->{'where'} } if $params->{'where'};
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+ my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql";
+ #if ( keys %svc_acct ) {
+ # $count_query .= ' WHERE '.
+ # join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}),
+ # keys %svc_acct
+ # );
+ #}
+
+ my $sql_query = {
+ 'table' => 'svc_acct',
+ 'hashref' => {}, # \%svc_acct,
+ 'select' => join(', ',
+ 'svc_acct.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields($params->{'cust_fields'}),
+ ),
+ 'addl_from' => $addl_from,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $params->{'order_by'},
+ 'count_query' => $count_query,
+ };
+
+}
+
+=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 .= qq(<OPTION VALUE="$group");
+ if ( $sel_groups{$group} ) {
+ $html .= ' SELECTED';
+ $sel_groups{$group} = 0;
+ }
+ $html .= ">$group</OPTION>\n";
+ }
+ foreach my $group ( grep { $sel_groups{$_} } keys %sel_groups ) {
+ $html .= qq(<OPTION VALUE="$group" 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;
+}
+
+=item reached_threshold
+
+Performs some activities when svc_acct thresholds (such as number of seconds
+remaining) are reached.
+
+=cut
+
+sub reached_threshold {
+ my %opt = @_;
+
+ my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $opt{'svcnum'} } );
+ die "Cannot find svc_acct with svcnum " . $opt{'svcnum'} unless $svc_acct;
+
+ if ( $opt{'op'} eq '+' ){
+ $svc_acct->setfield( $opt{'column'}.'_threshold',
+ int($svc_acct->getfield($opt{'column'})
+ * ( $conf->exists('svc_acct-usage_threshold')
+ ? $conf->config('svc_acct-usage_threshold')/100
+ : 0.80
+ )
+ )
+ );
+ my $error = $svc_acct->replace;
+ die $error if $error;
+ }elsif ( $opt{'op'} eq '-' ){
+
+ my $threshold = $svc_acct->getfield( $opt{'column'}.'_threshold' );
+ return '' if ($threshold eq '' );
+
+ $svc_acct->setfield( $opt{'column'}.'_threshold', 0 );
+ my $error = $svc_acct->replace;
+ die $error if $error; # email next time, i guess
+
+ if ( $warning_template ) {
+ eval "use FS::Misc qw(send_email)";
+ die $@ if $@;
+
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ my $cust_main = $cust_pkg->cust_main;
+
+ my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ }
+ $cust_main->invoicing_list,
+ ($opt{'to'} ? $opt{'to'} : ())
+ );
+
+ my $mimetype = $warning_mimetype;
+ $mimetype .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
+
+ my $body = $warning_template->fill_in( HASH => {
+ 'custnum' => $cust_main->custnum,
+ 'username' => $svc_acct->username,
+ 'password' => $svc_acct->_password,
+ 'first' => $cust_main->first,
+ 'last' => $cust_main->getfield('last'),
+ 'pkg' => $cust_pkg->part_pkg->pkg,
+ 'column' => $opt{'column'},
+ 'amount' => $opt{'column'} =~/bytes/
+ ? FS::UI::bytecount::display_bytecount($svc_acct->getfield($opt{'column'}))
+ : $svc_acct->getfield($opt{'column'}),
+ 'threshold' => $opt{'column'} =~/bytes/
+ ? FS::UI::bytecount::display_bytecount($threshold)
+ : $threshold,
+ } );
+
+
+ my $error = send_email(
+ 'from' => $warning_from,
+ 'to' => $to,
+ 'subject' => $warning_subject,
+ 'content-type' => $mimetype,
+ 'body' => [ map "$_\n", split("\n", $body) ],
+ );
+ die $error if $error;
+ }
+ }else{
+ die "unknown op: " . $opt{'op'};
+ }
+}
+
+=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)
+
+_op_usage and set_usage bypass the history... maybe they shouldn't
+
+=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..de41f5b
--- /dev/null
+++ b/FS/FS/svc_acct_pop.pm
@@ -0,0 +1,206 @@
+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 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..74cedfc
--- /dev/null
+++ b/FS/FS/svc_broadband.pm
@@ -0,0 +1,370 @@
+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 FS::part_svc_router;
+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_info {
+ {
+ 'name' => 'Broadband',
+ 'name_plural' => 'Broadband services',
+ 'longname_plural' => 'Fixed (username-less) broadband services',
+ 'display_weight' => 50,
+ 'cancel_weight' => 70,
+ 'fields' => {
+ 'description' => 'Descriptive label for this particular device.',
+ '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' => { 'label' => 'Address block',
+ 'type' => 'select',
+ 'select_table' => 'addr_block',
+ 'select_key' => 'blocknum',
+ 'select_label' => 'cidr',
+ 'disable_inventory' => 1,
+ },
+ },
+ };
+}
+
+sub table { 'svc_broadband'; }
+
+sub table_dupcheck_fields { ( 'mac_addr' ); }
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+sub search_sql {
+ my( $class, $string ) = @_;
+ if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
+ $class->search_sql_field('ip_addr', $string );
+ }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
+ $class->search_sql_field('mac_addr', uc($string));
+ }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
+ $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
+ } else {
+ '1 = 0'; #false
+ }
+}
+
+=item label
+
+Returns the IP address.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->ip_addr;
+}
+
+=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_numbern('blocknum')
+ || $self->ut_textn('description')
+ || $self->ut_number('speed_up')
+ || $self->ut_number('speed_down')
+ || $self->ut_ipn('ip_addr')
+ || $self->ut_hexn('mac_addr')
+ || $self->ut_hexn('auth_key')
+ || $self->ut_coordn('latitude', -90, 90)
+ || $self->ut_coordn('longitude', -180, 180)
+ || $self->ut_sfloatn('altitude')
+ || $self->ut_textn('vlan_profile')
+ ;
+ 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'; }
+
+ my $cust_svc = $self->svcnum
+ ? qsearchs('cust_svc', { 'svcnum' => $self->svcnum } )
+ : '';
+ my $cust_pkg;
+ if ($cust_svc) {
+ $cust_pkg = $cust_svc->cust_pkg;
+ }else{
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
+ return "Invalid pkgnum" unless $cust_pkg;
+ }
+
+ if ($self->blocknum) {
+ $error = $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum');
+ return $error if $error;
+ }
+
+ if ($cust_pkg && $self->blocknum) {
+ my $addr_agentnum = $self->addr_block->agentnum;
+ if ($addr_agentnum && $addr_agentnum != $cust_pkg->cust_main->agentnum) {
+ return "Address block does not service this customer";
+ }
+ }
+
+ $error = $self->_check_ip_addr;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+sub _check_ip_addr {
+ my $self = shift;
+
+ if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
+
+ return '' if $conf->exists('svc_broadband-allow_null_ip_addr'); #&& !$self->blocknum
+
+ return "Must supply either address or block"
+ unless $self->blocknum;
+ 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.")";
+ }
+
+ }
+
+ if (not($self->blocknum)) {
+ return "Must supply either address or block"
+ unless ($self->ip_addr and $self->ip_addr ne '0.0.0.0');
+ my @block = grep { $_->NetAddr->contains($self->NetAddr) }
+ map { $_->addr_block }
+ $self->allowed_routers;
+ if (scalar(@block)) {
+ $self->blocknum($block[0]->blocknum);
+ }else{
+ return "Address not with available block.";
+ }
+ }
+
+ # 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;
+ }
+
+ '';
+}
+
+sub _check_duplicate {
+ my $self = shift;
+
+ return "MAC already in use"
+ if ( $self->mac_addr &&
+ scalar( qsearch( 'svc_broadband', { 'mac_addr', $self->mac_addr } ) )
+ );
+
+ '';
+}
+
+
+=item NetAddr
+
+Returns a NetAddr::IP object containing the IP address of this service. The netmask
+is /32.
+
+=cut
+
+sub NetAddr {
+ my $self = shift;
+ 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;
+ 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;
+ map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
+}
+
+=head1 BUGS
+
+The business with sb_field has been 'fixed', in a manner of speaking.
+
+allowed_routers isn't agent virtualized because part_svc isn't agent
+virtualized
+
+=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..8823c77
--- /dev/null
+++ b/FS/FS/svc_domain.pm
@@ -0,0 +1,711 @@
+package FS::svc_domain;
+
+use strict;
+use base qw( FS::svc_Parent_Mixin FS::svc_CGPRule_Mixin FS::svc_Common );
+use vars qw( $whois_hack $conf
+ @defaultrecords $soadefaultttl $soaemail $soaexpire $soamachine
+ $soarefresh $soaretry
+);
+use Carp;
+use Scalar::Util qw( blessed );
+use Date::Format;
+#use Net::Whois::Raw;
+use Net::Domain::TLD qw(tld_exists);
+use FS::Record qw(fields qsearch qsearchs dbh);
+use FS::Conf;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::cust_pkg;
+use FS::cust_main;
+use FS::domain_record;
+use FS::queue;
+
+#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.
+
+=item suffix -
+
+=item parent_svcnum -
+
+=item registrarnum - Registrar (see L<FS::registrar>)
+
+=item registrarkey - Registrar key or password for this domain
+
+=item setup_date - UNIX timestamp
+
+=item renewal_interval - Number of days before expiration date to start renewal
+
+=item expiration_date - UNIX timestamp
+
+=item max_accounts
+
+=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_info {
+ {
+ 'name' => 'Domain',
+ 'sorts' => 'domain',
+ 'display_weight' => 20,
+ 'cancel_weight' => 60,
+ 'fields' => {
+ 'domain' => 'Domain',
+ 'parent_svcnum' => {
+ label => 'Parent domain / Communigate administrator domain',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'max_accounts' => { label => 'Maximum number of accounts',
+ 'disable_inventory' => 1,
+ },
+ 'cgp_aliases' => {
+ label => 'Communigate aliases',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'cgp_accessmodes' => {
+ label => 'Communigate enabled services',
+ type => 'communigate_pro-accessmodes',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+
+ 'acct_def_cgp_accessmodes' => {
+ label => 'Acct. default Communigate enabled services',
+ type => 'communigate_pro-accessmodes',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_password_selfchange' => { label => 'Acct. default Password modification',
+ type => 'checkbox',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_password_recover' => { label => 'Acct. default Password recovery',
+ type => 'checkbox',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_deletemode' => {
+ label => 'Acct. default Communigate message delete method',
+ type => 'select',
+ select_list => [ 'Move To Trash', 'Immediately', 'Mark' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_emptytrash' => {
+ label => 'Acct. default Communigate on logout remove trash',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_quota' => {
+ label => 'Acct. default Quota', #Mail storage limit
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_file_quota'=> {
+ label => 'Acct. default File storage limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_file_maxnum'=> {
+ label => 'Acct. default Number of files limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_file_maxsize'=> {
+ label => 'Acct. default File size limit',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_rulesallowed' => {
+ label => 'Acct. default Allowed mail rules',
+ type => 'select',
+ select_list => [ '', 'No', 'Filter Only', 'All But Exec', 'Any' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_rpopallowed' => {
+ label => 'Acct. default RPOP modifications',
+ type => 'checkbox',
+ },
+ 'acct_def_cgp_mailtoall' => {
+ label => 'Acct. default Accepts mail to "all"',
+ type => 'checkbox',
+ },
+ 'acct_def_cgp_addmailtrailer' => {
+ label => 'Acct. default Add trailer to sent mail',
+ type => 'checkbox',
+ },
+ 'trailer' => {
+ label => 'Mail trailer',
+ type => 'textarea',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_language' => {
+ label => 'Acct. default language',
+ type => 'select',
+ select_list => [ '', qw( English Arabic Chinese Dutch French German Hebrew Italian Japanese Portuguese Russian Slovak Spanish Thai ) ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_timezone' => {
+ label => 'Acct. default time zone',
+ type => 'select',
+ select_list => [ '',
+ 'HostOS',
+ '(+0100) Algeria/Congo',
+ '(+0200) Egypt/South Africa',
+ '(+0300) Saudi Arabia',
+ '(+0400) Oman',
+ '(+0500) Pakistan',
+ '(+0600) Bangladesh',
+ '(+0700) Thailand/Vietnam',
+ '(+0800) China/Malaysia',
+ '(+0900) Japan/Korea',
+ '(+1000) Queensland',
+ '(+1100) Micronesia',
+ '(+1200) Fiji',
+ '(+1300) Tonga/Kiribati',
+ '(+1400) Christmas Islands',
+ '(-0100) Azores/Cape Verde',
+ '(-0200) Fernando de Noronha',
+ '(-0300) Argentina/Uruguay',
+ '(-0400) Venezuela/Guyana',
+ '(-0500) Haiti/Peru',
+ '(-0600) Central America',
+ '(-0700) Arisona',
+ '(-0800) Adamstown',
+ '(-0900) Marquesas Islands',
+ '(-1000) Hawaii/Tahiti',
+ '(-1100) Samoa',
+ 'Asia/Afghanistan',
+ 'Asia/India',
+ 'Asia/Iran',
+ 'Asia/Iraq',
+ 'Asia/Israel',
+ 'Asia/Jordan',
+ 'Asia/Lebanon',
+ 'Asia/Syria',
+ 'Australia/Adelaide',
+ 'Australia/East',
+ 'Australia/NorthernTerritory',
+ 'Europe/Central',
+ 'Europe/Eastern',
+ 'Europe/Moscow',
+ 'Europe/Western',
+ 'GMT (+0000)',
+ 'Newfoundland',
+ 'NewZealand/Auckland',
+ 'NorthAmerica/Alaska',
+ 'NorthAmerica/Atlantic',
+ 'NorthAmerica/Central',
+ 'NorthAmerica/Eastern',
+ 'NorthAmerica/Mountain',
+ 'NorthAmerica/Pacific',
+ 'Russia/Ekaterinburg',
+ 'Russia/Irkutsk',
+ 'Russia/Kamchatka',
+ 'Russia/Krasnoyarsk',
+ 'Russia/Magadan',
+ 'Russia/Novosibirsk',
+ 'Russia/Vladivostok',
+ 'Russia/Yakutsk',
+ 'SouthAmerica/Brasil',
+ 'SouthAmerica/Chile',
+ 'SouthAmerica/Paraguay',
+ ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_skinname' => {
+ label => 'Acct. default layout',
+ type => 'select',
+ select_list => [ '', '***', 'GoldFleece', 'Skin2' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_prontoskinname' => {
+ label => 'Acct. default Pronto style',
+ type => 'select',
+ select_list => [ '', 'Pronto', 'Pronto-darkflame', 'Pronto-steel', 'Pronto-twilight', ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'acct_def_cgp_sendmdnmode' => {
+ label => 'Acct. default send read receipts',
+ type => 'select',
+ select_list => [ '', 'Never', 'Manually', 'Automatically' ],
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ },
+ };
+}
+
+sub table { 'svc_domain'; }
+
+sub search_sql {
+ my($class, $string) = @_;
+ $class->search_sql_field('domain', $string);
+}
+
+
+=item label
+
+Returns the domain.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->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, I<M>
+for transfers, or I<I> for no action (registered elsewhere).
+
+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->SUPER::insert(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ 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 "can't delete DNS entry: ".
+ join(' ', map $domain_record->$_(),
+ qw( reczone recaf rectype recdata )
+ ).
+ ":$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 = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ return "Can't change domain - reorder."
+ if $old->getfield('domain') ne $new->getfield('domain')
+ && ! $conf->exists('svc_domain-edit_domain');
+
+ # Better to do it here than to force the caller to remember that svc_domain is weird.
+ $new->setfield(action => 'I');
+ 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')
+ || $self->ut_numbern('max_accounts')
+ || $self->ut_anything('trailer') #well
+ || $self->ut_textn('cgp_aliases') #well
+ || $self->ut_enum('acct_def_password_selfchange', [ '', 'Y' ])
+ || $self->ut_enum('acct_def_password_recover', [ '', 'Y' ])
+ || $self->ut_textn('acct_def_cgp_accessmodes')
+ || $self->ut_alphan('acct_def_quota')
+ || $self->ut_alphan('acct_def_file_quota')
+ || $self->ut_alphan('acct_def_maxnum')
+ || $self->ut_alphan('acct_def_maxsize')
+ #settings
+ || $self->ut_alphasn('acct_def_cgp_rulesallowed')
+ || $self->ut_enum('acct_def_cgp_rpopallowed', [ '', 'Y' ])
+ || $self->ut_enum('acct_def_cgp_mailtoall', [ '', 'Y' ])
+ || $self->ut_enum('acct_def_cgp_addmailtrailer', [ '', 'Y' ])
+ #XXX archive messages
+ #preferences
+ || $self->ut_alphasn('acct_def_cgp_deletemode')
+ || $self->ut_alphan('acct_def_cgp_emptytrash')
+ || $self->ut_alphan('acct_def_cgp_language')
+ || $self->ut_textn('acct_def_cgp_timezone')
+ || $self->ut_textn('acct_def_cgp_skinname')
+ || $self->ut_textn('acct_def_cgp_prontoskinname')
+ || $self->ut_alphan('acct_def_cgp_sendmdnmode')
+ #mail
+ #XXX rules, archive rule, spam foldering rule(s)
+ ;
+ 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;
+
+ #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) {
+ if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu|tv|info|biz)$/ ) {
+ $recref->{domain} = "$1.$2";
+ $recref->{suffix} ||= $2;
+ # hmmmmmmmm.
+ } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)\.(\w+)$/ ) {
+ $recref->{domain} = "$1.$2";
+ # need to match a list of suffixes - no guarantee they're top-level..
+ # http://wiki.mozilla.org/TLD_List
+ # but this will have to do for now...
+ $recref->{suffix} ||= $2;
+ } else {
+ return "Illegal domain ". $recref->{domain}.
+ " (or unknown registry - try \$whois_hack)";
+ }
+
+ $self->suffix =~ /(^|\.)(\w+)$/
+ or return "can't parse suffix for TLD: ". $self->suffix;
+ my $tld = $2;
+ return "No such TLD: .$tld" unless tld_exists($tld);
+
+ if ( $recref->{catchall} ne '' ) {
+ my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
+ return "Unknown catchall" unless $svc_acct;
+ }
+
+ $self->ut_alphan('suffix')
+ or $self->ut_foreign_keyn('registrarnum', 'registrar', 'registrarnum')
+ or $self->ut_textn('registrarkey')
+ or $self->ut_numbern('setup_date')
+ or $self->ut_numbern('renewal_interval')
+ or $self->ut_numbern('expiration_date')
+ or $self->SUPER::check;
+
+}
+
+sub _check_duplicate {
+ my $self = shift;
+
+ $self->lock_table;
+
+ if ( qsearchs( 'svc_domain', { 'domain' => $self->domain } ) ) {
+ return "Domain in use (here)";
+ } else {
+ return '';
+ }
+}
+
+=item domain_record
+
+=cut
+
+sub domain_record {
+ my $self = shift;
+
+ my %order = (
+ 'SOA' => 1,
+ 'NS' => 2,
+ 'MX' => 3,
+ 'CNAME' => 4,
+ 'A' => 5,
+ 'TXT' => 6,
+ 'PTR' => 7,
+ );
+
+ my %sort = (
+ #'SOA' => sub { $_[0]->recdata cmp $_[1]->recdata }, #sure hope not though
+# 'SOA' => sub { 0; },
+# 'NS' => sub { 0; },
+ 'MX' => sub { my( $a_weight, $a_name ) = split(/\s+/, $_[0]->recdata);
+ my( $b_weight, $b_name ) = split(/\s+/, $_[1]->recdata);
+ $a_weight <=> $b_weight or $a_name cmp $b_name;
+ },
+ 'CNAME' => sub { $_[0]->reczone cmp $_[1]->reczone },
+ 'A' => sub { $_[0]->reczone cmp $_[1]->reczone },
+
+# 'TXT' => sub { 0; },
+ 'PTR' => sub { $_[0]->reczone <=> $_[1]->reczone },
+ );
+
+ map { $_ } #return $self->num_domain_record( PARAMS ) unless wantarray;
+ sort { $order{$a->rectype} <=> $order{$b->rectype}
+ or &{ $sort{$a->rectype} || sub { 0; } }($a, $b)
+ }
+ 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";
+}
+
+=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..338fdbc
--- /dev/null
+++ b/FS/FS/svc_external.pm
@@ -0,0 +1,205 @@
+package FS::svc_external;
+
+use strict;
+use vars qw(@ISA);
+use FS::Conf;
+use FS::svc_External_Common;
+
+@ISA = qw( FS::svc_External_Common );
+
+=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 generic externally tracked service.
+FS::svc_external inherits from FS::svc_External_Common (and 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_info {
+ {
+ 'name' => 'External service',
+ 'sorts' => 'id',
+ 'display_weight' => 90,
+ 'cancel_weight' => 10,
+ 'fields' => {
+ 'id' => { label => 'Unique number of external record',
+ type => 'text',
+ disable_default => 1,
+ disable_fixed => 1,
+ },
+ 'title' => { label => 'Printed on invoice line items',
+ type => 'text',
+ #disable_inventory => 1,
+ },
+ },
+ };
+}
+
+sub table { 'svc_external'; }
+
+# oh! this should be moved to svc_artera_turbo or something now
+sub label {
+ my $self = shift;
+ my $conf = new FS::Conf;
+ if ( $conf->exists('svc_external-display_type')
+ && $conf->config('svc_external-display_type') eq 'artera_turbo' )
+ {
+ sprintf('%010d', $self->id). '-'.
+ substr('0000000000'.uc($self->title), -10);
+ } else {
+ #$self->SUPER::label;
+ return $self->id unless $self->title =~ /\S/;
+ $self->id. ' - '. $self->title;
+ }
+}
+
+=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 replace methods.
+
+=cut
+
+#sub check {
+# my $self = shift;
+# my $error;
+#
+# $error = $self->SUPER::delete;
+# return $error if $error;
+#
+# '';
+#}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_External_Common>, 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..9e27a32
--- /dev/null
+++ b/FS/FS/svc_forward.pm
@@ -0,0 +1,368 @@
+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_info {
+ {
+ 'name' => 'Forward',
+ 'name_plural' => 'Mail forwards',
+ 'display_weight' => 30,
+ 'cancel_weight' => 30,
+ 'fields' => {
+ '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',
+ },
+ };
+}
+
+sub table { 'svc_forward'; }
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+sub search_sql {
+ my( $class, $string ) = @_;
+ $class->search_sql_field('src', $string);
+}
+
+=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns a text string representing this forward.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=cut
+
+sub label {
+ my $self = shift;
+ my $tag = '';
+
+ if ( $self->srcsvc ) {
+ my $svc_acct = $self->srcsvc_acct(@_);
+ $tag = $svc_acct->email(@_);
+ } else {
+ $tag = $self->src;
+ }
+
+ $tag .= ' -> ';
+
+ if ( $self->dstsvc ) {
+ my $svc_acct = $self->dstsvc_acct(@_);
+ $tag .= $svc_acct->email(@_);
+ } else {
+ $tag .= $self->dst;
+ }
+
+ $tag;
+}
+
+
+=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->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->src;
+ $self->src("$1$2");
+ } else {
+ $self->src('');
+ }
+
+ if ( $self->dst ) {
+ my $conf = new FS::Conf;
+ if ( $conf->exists('svc_forward-arbitrary_dst') ) {
+ my $error = $self->ut_textn('dst');
+ return $error if $error;
+ } else {
+ $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_mailinglist.pm b/FS/FS/svc_mailinglist.pm
new file mode 100644
index 0000000..ba297ee
--- /dev/null
+++ b/FS/FS/svc_mailinglist.pm
@@ -0,0 +1,330 @@
+package FS::svc_mailinglist;
+
+use strict;
+use base qw( FS::svc_Domain_Mixin FS::svc_Common );
+use Scalar::Util qw( blessed );
+use FS::Record qw( qsearchs dbh ); # qsearch );
+use FS::svc_domain;
+use FS::mailinglist;
+
+=head1 NAME
+
+FS::svc_mailinglist - Object methods for svc_mailinglist records
+
+=head1 SYNOPSIS
+
+ use FS::svc_mailinglist;
+
+ $record = new FS::svc_mailinglist \%hash;
+ $record = new FS::svc_mailinglist { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::svc_mailinglist object represents a mailing list customer service.
+FS::svc_mailinglist inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum
+
+primary key
+
+=item username
+
+username
+
+=item domsvc
+
+domsvc
+
+=item listnum
+
+listnum
+
+=item reply_to_group
+
+reply_to_group
+
+=item remove_author
+
+remove_author
+
+=item reject_auto
+
+reject_auto
+
+=item remove_to_and_cc
+
+remove_to_and_cc
+
+=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 { 'svc_mailinglist'; }
+
+sub table_info {
+ {
+ 'name' => 'Mailing list',
+ 'display_weight' => 80,
+ 'cancel_weight' => 55,
+ 'fields' => {
+ 'username' => { 'label' => 'List address',
+ 'disable_default' => 1,
+ 'disable_fixed' => 1,
+ 'disable_inventory' => 1,
+ },
+ 'domsvc' => { 'label' => 'List address domain',
+ 'disable_inventory' => 1,
+ },
+ 'domain' => 'List address domain',
+ 'listnum' => { 'label' => 'List name',
+ 'disable_inventory' => 1,
+ },
+ 'listname' => 'List name', #actually mailinglist.listname
+ 'reply_to' => { 'label' => 'Reply-To list',
+ 'type' => 'checkbox',
+ 'disable_inventory' => 1,
+ 'disable_select' => 1,
+ },
+ 'remove_from' => { 'label' => 'Remove From: from messages',
+ 'type' => 'checkbox',
+ 'disable_inventory' => 1,
+ 'disable_select' => 1,
+ },
+ 'reject_auto' => { 'label' => 'Reject automatic messages',
+ 'type' => 'checkbox',
+ 'disable_inventory' => 1,
+ 'disable_select' => 1,
+ },
+ 'remove_to_and_cc' => { 'label' => 'Remove To: and Cc: from messages',
+ 'type' => 'checkbox',
+ 'disable_inventory' => 1,
+ 'disable_select' => 1,
+ },
+ },
+ };
+}
+
+=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;
+
+ my $error;
+
+ #attach to existing lists? sound scary
+ #unless ( $self->listnum ) {
+ my $mailinglist = new FS::mailinglist {
+ 'listname' => $self->get('listname'),
+ };
+ $error = $mailinglist->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->listnum($mailinglist->listnum);
+ #}
+
+ $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;
+
+ 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->mailinglist->delete || $self->SUPER::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
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ return "can't change listnum" if $old->listnum != $new->listnum; #?
+
+ my %options = @_;
+
+ 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->get('listname') && $new->get('listname') ne $old->listname ) {
+ my $mailinglist = $old->mailinglist;
+ $mailinglist->listname($new->get('listname'));
+ my $error = $mailinglist->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+ }
+
+ my $error = $new->SUPER::replace($old, %options);
+ 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 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;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_text('username')
+ || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum')
+ #|| $self->ut_foreign_key('listnum', 'mailinglist', 'listnum')
+ || $self->ut_foreign_keyn('listnum', 'mailinglist', 'listnum')
+ || $self->ut_enum('reply_to_group', [ '', 'Y' ] )
+ || $self->ut_enum('remove_author', [ '', 'Y' ] )
+ || $self->ut_enum('reject_auto', [ '', 'Y' ] )
+ || $self->ut_enum('remove_to_and_cc', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ return "Can't remove listnum" if $self->svcnum && ! $self->listnum;
+
+ $self->SUPER::check;
+}
+
+=item mailinglist
+
+=cut
+
+sub mailinglist {
+ my $self = shift;
+ qsearchs('mailinglist', { 'listnum' => $self->listnum } );
+}
+
+=item listname
+
+=cut
+
+sub listname {
+ my $self = shift;
+ my $mailinglist = $self->mailinglist;
+ $mailinglist ? $mailinglist->listname : '';
+}
+
+=item label
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->listname. ' <'. $self->username. '@'. $self->domain. '>';
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_pbx.pm b/FS/FS/svc_pbx.pm
new file mode 100644
index 0000000..6ae0418
--- /dev/null
+++ b/FS/FS/svc_pbx.pm
@@ -0,0 +1,277 @@
+package FS::svc_pbx;
+
+use strict;
+use base qw( FS::svc_External_Common );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_svc;
+use FS::svc_phone;
+use FS::svc_acct;
+
+=head1 NAME
+
+FS::svc_pbx - Object methods for svc_pbx records
+
+=head1 SYNOPSIS
+
+ use FS::svc_pbx;
+
+ $record = new FS::svc_pbx \%hash;
+ $record = new FS::svc_pbx { '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_pbx object represents a PBX tenant. FS::svc_pbx inherits from
+FS::svc_Common. The following fields are currently supported:
+
+=over 4
+
+=item svcnum
+
+Primary key (assigned automatcially for new accounts)
+
+=item id
+
+(Unique?) number of external record
+
+=item title
+
+PBX name
+
+=item max_extensions
+
+Maximum number of extensions
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new PBX tenant. To add the PBX tenant 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_pbx'; }
+
+sub table_info {
+ {
+ 'name' => 'PBX',
+ 'name_plural' => 'PBXs', #optional,
+ 'longname_plural' => 'PBXs', #optional
+ 'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
+ 'display_weight' => 70,
+ 'cancel_weight' => 90,
+ 'fields' => {
+ 'id' => 'ID',
+ 'title' => 'Name',
+ 'max_extensions' => 'Maximum number of User Extensions',
+# 'field' => 'Description',
+# 'another_field' => {
+# 'label' => 'Description',
+# 'def_label' => 'Description for service definitions',
+# 'type' => 'text',
+# 'disable_default' => 1, #disable switches
+# 'disable_fixed' => 1, #
+# 'disable_inventory' => 1, #
+# },
+# 'foreign_key' => {
+# 'label' => 'Description',
+# 'def_label' => 'Description for service defs',
+# 'type' => 'select',
+# 'select_table' => 'foreign_table',
+# 'select_key' => 'key_field_in_table',
+# 'select_label' => 'label_field_in_table',
+# },
+
+ },
+ };
+}
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+#XXX
+#or something more complicated if necessary
+#sub search_sql {
+# my($class, $string) = @_;
+# $class->search_sql_field('title', $string);
+#}
+
+=item label
+
+Returns the title field for this PBX tenant.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->title;
+}
+
+=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;
+
+ 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 $svc_phone (qsearch('svc_phone', { 'pbxsvc' => $self->svcnum } )) {
+ $svc_phone->pbxsvc('');
+ my $error = $svc_phone->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $svc_acct (qsearch('svc_acct', { 'pbxsvc' => $self->svcnum } )) {
+ my $error = $svc_acct->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 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 PBX tenant. 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;
+}
+
+#XXX this is a way-too simplistic implementation
+# at the very least, title should be unique across exports that need that or
+# controlled by a conf setting or something
+sub _check_duplicate {
+ my $self = shift;
+
+ $self->lock_table;
+
+ if ( qsearchs( 'svc_pbx', { 'title' => $self->title } ) ) {
+ return "Name in use";
+ } else {
+ return '';
+ }
+}
+
+=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_phone.pm b/FS/FS/svc_phone.pm
new file mode 100644
index 0000000..a0a2c3e
--- /dev/null
+++ b/FS/FS/svc_phone.pm
@@ -0,0 +1,633 @@
+package FS::svc_phone;
+
+use strict;
+use base qw( FS::svc_Domain_Mixin FS::location_Mixin FS::svc_Common );
+use vars qw( $DEBUG $me @pw_set $conf $phone_name_max );
+use Data::Dumper;
+use Scalar::Util qw( blessed );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::Msgcat qw(gettext);
+use FS::part_svc;
+use FS::phone_device;
+use FS::svc_pbx;
+use FS::svc_domain;
+use FS::cust_location;
+
+$me = '[' . __PACKAGE__ . ']';
+$DEBUG = 0;
+
+#avoid l 1 and o O 0
+@pw_set = ( 'a'..'k', 'm','n', 'p-z', 'A'..'N', 'P'..'Z' , '2'..'9' );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::svc_acct'} = sub {
+ $conf = new FS::Conf;
+ $phone_name_max = $conf->config('svc_phone-phone_name-max_length');
+};
+
+=head1 NAME
+
+FS::svc_phone - Object methods for svc_phone records
+
+=head1 SYNOPSIS
+
+ use FS::svc_phone;
+
+ $record = new FS::svc_phone \%hash;
+ $record = new FS::svc_phone { '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_phone object represents a phone number. FS::svc_phone inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item svcnum
+
+primary key
+
+=item countrycode
+
+=item phonenum
+
+=item sip_password
+
+=item pin
+
+Voicemail PIN
+
+=item phone_name
+
+=item pbxsvc
+
+Optional svcnum from svc_pbx
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new phone number. To add the number 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_info {
+ {
+ 'name' => 'Phone number',
+ 'sorts' => 'phonenum',
+ 'display_weight' => 60,
+ 'cancel_weight' => 80,
+ 'fields' => {
+ 'countrycode' => { label => 'Country code',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'phonenum' => 'Phone number',
+ 'pin' => { label => 'Personal Identification Number',
+ type => 'text',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ 'sip_password' => 'SIP password',
+ 'phone_name' => 'Name',
+ 'pbxsvc' => { label => 'PBX',
+ type => 'select-svc_pbx.html',
+ disable_inventory => 1,
+ disable_select => 1, #UI wonky, pry works otherwise
+ },
+ 'domsvc' => {
+ label => 'Domain',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain',
+ disable_inventory => 1,
+ },
+ 'locationnum' => {
+ label => 'E911 location',
+ disable_inventory => 1,
+ disable_select => 1,
+ },
+ },
+ };
+}
+
+sub table { 'svc_phone'; }
+
+sub table_dupcheck_fields { ( 'countrycode', 'phonenum' ); }
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+sub search_sql {
+ my( $class, $string ) = @_;
+
+ if ( $conf->exists('svc_phone-allow_alpha_phonenum') ) {
+ $string =~ s/\W//g;
+ } else {
+ $string =~ s/\D//g;
+ }
+
+ my $conf = new FS::Conf;
+ my $ccode = ( $conf->exists('default_phone_countrycode')
+ && $conf->config('default_phone_countrycode')
+ )
+ ? $conf->config('default_phone_countrycode')
+ : '1';
+
+ $string =~ s/^$ccode//;
+
+ $class->search_sql_field('phonenum', $string );
+}
+
+=item label
+
+Returns the phone number.
+
+=cut
+
+sub label {
+ my $self = shift;
+ my $phonenum = $self->phonenum; #XXX format it better
+ my $label = $phonenum;
+ $label .= '@'.$self->domain if $self->domsvc;
+ $label .= ' ('.$self->phone_name.')' if $self->phone_name;
+ $label;
+}
+
+=item insert
+
+Adds this phone number to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+
+ if ( $DEBUG ) {
+ warn "[$me] insert called on $self: ". Dumper($self).
+ "\nwith options: ". Dumper(%options);
+ }
+
+ 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;
+
+ #false laziness w/cust_pkg.pm... move this to location_Mixin? that would
+ #make it more of a base class than a mixin... :)
+ if ( $options{'cust_location'}
+ && ( ! $self->locationnum || $self->locationnum == -1 ) ) {
+ my $error = $options{'cust_location'}->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_location (transaction rolled back): $error";
+ }
+ $self->locationnum( $options{'cust_location'}->locationnum );
+ }
+ #what about on-the-fly edits? if the ui supports it?
+
+ my $error = $self->SUPER::insert(%options);
+ 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;
+
+ 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 $phone_device ( $self->phone_device ) {
+ my $error = $phone_device->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;
+ '';
+
+}
+
+# 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
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my %options = @_;
+
+ if ( $DEBUG ) {
+ warn "[$me] replacing $old with $new\n".
+ "\nwith options: ". Dumper(%options);
+ }
+
+ 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;
+
+ #false laziness w/cust_pkg.pm... move this to location_Mixin? that would
+ #make it more of a base class than a mixin... :)
+ if ( $options{'cust_location'}
+ && ( ! $new->locationnum || $new->locationnum == -1 ) ) {
+ my $error = $options{'cust_location'}->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_location (transaction rolled back): $error";
+ }
+ $new->locationnum( $options{'cust_location'}->locationnum );
+ }
+ #what about on-the-fly edits? if the ui supports it?
+
+ my $error = $new->SUPER::replace($old, %options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no 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 phone number. 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 $conf = new FS::Conf;
+
+ my $phonenum = $self->phonenum;
+ my $phonenum_check_method;
+ if ( $conf->exists('svc_phone-allow_alpha_phonenum') ) {
+ $phonenum =~ s/\W//g;
+ $phonenum_check_method = 'ut_alpha';
+ } else {
+ $phonenum =~ s/\D//g;
+ $phonenum_check_method = 'ut_number';
+ }
+ $self->phonenum($phonenum);
+
+ $self->locationnum('') if !$self->locationnum || $self->locationnum == -1;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_numbern('countrycode')
+ || $self->$phonenum_check_method('phonenum')
+ || $self->ut_anything('sip_password')
+ || $self->ut_numbern('pin')
+ || $self->ut_textn('phone_name')
+ || $self->ut_foreign_keyn('pbxsvc', 'svc_pbx', 'svcnum' )
+ || $self->ut_foreign_keyn('domsvc', 'svc_domain', 'svcnum' )
+ || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
+ ;
+ return $error if $error;
+
+ return 'Name ('. $self->phone_name.
+ ") is longer than $phone_name_max characters"
+ if $phone_name_max && length($self->phone_name) > $phone_name_max;
+
+ $self->countrycode(1) unless $self->countrycode;
+
+ unless ( length($self->sip_password) ) {
+
+ $self->sip_password(
+ join('', map $pw_set[ int(rand $#pw_set) ], (0..16) )
+ );
+
+ }
+
+ $self->SUPER::check;
+}
+
+=item _check duplicate
+
+Internal method to check for duplicate phone numers.
+
+=cut
+
+#false laziness w/svc_acct.pm's _check_duplicate.
+sub _check_duplicate {
+ my $self = shift;
+
+ my $global_unique = $conf->config('global_unique-phonenum') || 'none';
+ return '' if $global_unique eq 'disabled';
+
+ $self->lock_table;
+
+ my @dup_ccphonenum =
+ grep { !$self->svcnum || $_->svcnum != $self->svcnum }
+ qsearch( 'svc_phone', {
+ 'countrycode' => $self->countrycode,
+ 'phonenum' => $self->phonenum,
+ });
+
+ return gettext('phonenum_in_use')
+ if $global_unique eq 'countrycode+phonenum' && @dup_ccphonenum;
+
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } );
+ unless ( $part_svc ) {
+ return 'unknown svcpart '. $self->svcpart;
+ }
+
+ if ( @dup_ccphonenum ) {
+
+ my $exports = FS::part_export::export_info('svc_phone');
+ my %conflict_ccphonenum_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;
+
+ $conflict_ccphonenum_svcpart{$_} = $part_export->exportnum
+ foreach @svcparts;
+
+ }
+
+ foreach my $dup_ccphonenum ( @dup_ccphonenum ) {
+ my $dup_svcpart = $dup_ccphonenum->cust_svc->svcpart;
+ if ( exists($conflict_ccphonenum_svcpart{$dup_svcpart}) ) {
+ return "duplicate phone number ".
+ $self->countrycode. ' '. $self->phonenum.
+ ": conflicts with svcnum ". $dup_ccphonenum->svcnum.
+ " via exportnum ". $conflict_ccphonenum_svcpart{$dup_svcpart};
+ }
+ }
+
+ }
+
+ return '';
+
+}
+
+=item check_pin
+
+Checks the supplied PIN against the PIN in the database. Returns true for a
+sucessful authentication, false if no match.
+
+=cut
+
+sub check_pin {
+ my($self, $check_pin) = @_;
+ length($self->pin) && $check_pin eq $self->pin;
+}
+
+=item radius_reply
+
+=cut
+
+sub radius_reply {
+ my $self = shift;
+ #XXX Session-Timeout! holy shit, need rlm_perl to ask for this in realtime
+ ();
+}
+
+=item radius_check
+
+=cut
+
+sub radius_check {
+ my $self = shift;
+ my %check = ();
+
+ my $conf = new FS::Conf;
+
+ $check{'User-Password'} = $conf->config('svc_phone-radius-default_password');
+
+ %check;
+}
+
+sub radius_groups {
+ ();
+}
+
+=item phone_device
+
+Returns any FS::phone_device records associated with this service.
+
+=cut
+
+sub phone_device {
+ my $self = shift;
+ qsearch('phone_device', { 'svcnum' => $self->svcnum } );
+}
+
+#override location_Mixin version cause we want to try the cust_pkg location
+#in between us and cust_main
+# XXX what to do in the unlinked case??? return a pseudo-object that returns
+# empty fields?
+sub cust_location_or_main {
+ my $self = shift;
+ return $self->cust_location if $self->locationnum;
+ my $cust_pkg = $self->cust_svc->cust_pkg;
+ $cust_pkg ? $cust_pkg->cust_location_or_main : '';
+}
+
+=item get_cdrs
+
+Returns a set of Call Detail Records (see L<FS::cdr>) associated with this
+service. By default, "associated with" means that either the "src" or the
+"charged_party" field of the CDR matches the "phonenum" field of the service.
+
+=over 2
+
+Accepts the following options:
+
+=item for_update => 1: SELECT the CDRs "FOR UPDATE".
+
+=item status => "" (or "done"): Return only CDRs with that processing status.
+
+=item inbound => 1: Return CDRs for inbound calls. With "status", will filter
+on inbound processing status.
+
+=item default_prefix => "XXX": Also accept the phone number of the service prepended
+with the chosen prefix.
+
+=item disable_src => 1: Only match on "charged_party", not "src".
+
+=back
+
+=cut
+
+sub get_cdrs {
+ my($self, %options) = @_;
+ my @fields;
+ my %hash;
+ my @where;
+
+ if ( $options{'inbound'} ) {
+ @fields = ( 'dst' );
+ if ( exists($options{'status'}) ) {
+ # must be 'done' or ''
+ my $sq = 'EXISTS ( SELECT 1 FROM cdr_termination '.
+ 'WHERE cdr.acctid = cdr_termination.acctid '.
+ 'AND cdr_termination.status = \'done\' '.
+ 'AND cdr_termination.termpart = 1 )';
+ if ( $options{'status'} eq 'done' ) {
+ push @where, $sq;
+ }
+ elsif ($options{'status'} eq '' ) {
+ push @where, "NOT $sq";
+ }
+ else {
+ warn "invalid status: $options{'status'} (ignored)\n";
+ }
+ }
+ }
+ else {
+ @fields = ( 'charged_party' );
+ push @fields, 'src' if !$options{'disable_src'};
+ $hash{'freesidestatus'} = $options{'status'}
+ if exists($options{'status'});
+ }
+
+ my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
+
+ my $number = $self->phonenum;
+
+ my $prefix = $options{'default_prefix'};
+
+ my @orwhere = map " $_ = '$number' ", @fields;
+ push @orwhere, map " $_ = '$prefix$number' ", @fields
+ if length($prefix);
+ if ( $prefix =~ /^\+(\d+)$/ ) {
+ push @orwhere, map " $_ = '$1$number' ", @fields
+ }
+
+ push @where, ' ( '. join(' OR ', @orwhere ). ' ) ';
+
+ if ( $options{'begin'} ) {
+ push @where, 'startdate >= '. $options{'begin'};
+ }
+ if ( $options{'end'} ) {
+ push @where, 'startdate < '. $options{'end'};
+ }
+
+ my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where );
+
+ my @cdrs =
+ qsearch( {
+ 'table' => 'cdr',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY startdate $for_update",
+ } );
+
+ @cdrs;
+}
+
+=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_www.pm b/FS/FS/svc_www.pm
new file mode 100644
index 0000000..7e02d81
--- /dev/null
+++ b/FS/FS/svc_www.pm
@@ -0,0 +1,286 @@
+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_info {
+ {
+ 'name' => 'Hosting',
+ 'name_plural' => 'Virtual hosting services',
+ 'display_weight' => 40,
+ 'cancel_weight' => 20,
+ 'fields' => {
+ },
+ };
+};
+
+sub table { 'svc_www'; }
+
+=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ]
+
+Returns the zone name for this virtual host.
+
+END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with
+history records.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->domain_record(@_)->zone;
+}
+
+=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 preinsert_hook {
+ my $self = shift;
+
+ #return '' unless $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/;
+ return '' unless $self->recnum =~ /^([\w\-]+|\@)\.(\d+)$/;
+
+ my( $reczone, $domain_svcnum ) = ( $1, $2 );
+ unless ( $apacheip ) {
+ 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,
+ };
+ my $error = $domain_record->insert;
+ return $error if $error;
+
+ $self->recnum($domain_record->recnum);
+ return '';
+}
+
+=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 replace 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_numbern('usersvc')
+ || $self->ut_anything('config')
+ ;
+ 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;
+ }
+
+ if ( $self->usersvc ) {
+ return "Unknown usersvc0 (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/tax_class.pm b/FS/FS/tax_class.pm
new file mode 100644
index 0000000..4f03969
--- /dev/null
+++ b/FS/FS/tax_class.pm
@@ -0,0 +1,392 @@
+package FS::tax_class;
+
+use strict;
+use vars qw( @ISA );
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw( csv_from_fixed );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::tax_class - Object methods for tax_class records
+
+=head1 SYNOPSIS
+
+ use FS::tax_class;
+
+ $record = new FS::tax_class \%hash;
+ $record = new FS::tax_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::tax_class object represents a tax class. FS::tax_class
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item taxclassnum
+
+Primary key
+
+=item data_vendor
+
+Vendor of the tax data
+
+=item taxclass
+
+Tax class
+
+=item description
+
+Human readable description of the tax class
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax class. To add the tax class 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 { 'tax_class'; }
+
+=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
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a tax class which has tax rates!"
+ if qsearch( 'tax_rate', { 'taxclassnum' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxclassnum' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax overrides!"
+ if qsearch( 'part_pkg_taxoverride', { 'taxclassnum' => $self->taxclassnum } );
+
+ $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.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid tax class. 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('taxclassnum')
+ || $self->ut_text('taxclass')
+ || $self->ut_textn('data_vendor')
+ || $self->ut_textn('description')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item batch_import
+
+Loads part_pkg_taxrate records from an external CSV file. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub batch_import {
+ my ($param, $job) = @_;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my @fields;
+ my $hook;
+ my $endhook;
+ my $data = {};
+ my $imported = 0;
+ my $dbh = dbh;
+
+ my @column_lengths = ();
+ my @column_callbacks = ();
+ if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
+ $format =~ s/-fixed//;
+ push @column_lengths, qw( 8 10 3 2 2 10 100 );
+ push @column_lengths, 1 if $format eq 'cch-update';
+ }
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job || scalar(@column_lengths) ) {
+ my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
+ return $error if $error;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
+ @fields = qw( table name pos length number value description );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $hook = sub {
+ my $hash = shift;
+
+ if ($hash->{'table'} eq 'DETAIL') {
+ push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
+ if ($hash->{'name'} eq 'TAXCAT' &&
+ (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
+
+ push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
+ if ($hash->{'name'} eq 'TAXTYPE' &&
+ (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ my $name = $hash->{'name'};
+ my $value = $hash->{'value'};
+ return "Bad value for $name: $value"
+ unless $value =~ /^\d+$/;
+
+ if ($name eq 'TAXCAT' || $name eq 'TAXTYPE') {
+ my @tax_class = qsearch( 'tax_class',
+ { 'data_vendor' => 'cch' },
+ '',
+ "AND taxclass LIKE '".
+ ($name eq 'TAXTYPE' ? $value : '%').":".
+ ($name eq 'TAXCAT' ? $value : '%')."'",
+ );
+ foreach (@tax_class) {
+ my $error = $_->delete;
+ return $error if $error;
+ }
+ }
+ }
+
+ }
+
+ delete($hash->{$_})
+ for qw( data_vendor table name pos length number value description );
+ delete($hash->{actionflag}) if exists($hash->{actionflag});
+
+ '';
+
+ };
+
+ $endhook = sub {
+
+ my $sql = "SELECT DISTINCT ".
+ "substring(taxclass from 1 for position(':' in taxclass)-1),".
+ "substring(description from 1 for position(':' in description)-1) ".
+ "FROM tax_class WHERE data_vendor='cch'";
+
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my @old_types = @{$sth->fetchall_arrayref};
+
+ $sql = "SELECT DISTINCT ".
+ "substring(taxclass from position(':' in taxclass)+1),".
+ "substring(description from position(':' in description)+1) ".
+ "FROM tax_class WHERE data_vendor='cch'";
+
+ $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my @old_cats = @{$sth->fetchall_arrayref};
+
+ my $catcount = exists($data->{'taxcat'}) ? scalar(@{$data->{'taxcat'}})
+ : 0;
+ my $typecount = exists($data->{'taxtype'}) ? scalar(@{$data->{'taxtype'}})
+ : 0;
+
+ my $count = scalar(@old_types) * $catcount
+ + $typecount * (scalar(@old_cats) + $catcount);
+
+ $imported = 1 if $format eq 'cch-update'; #empty file ok
+
+ foreach my $type (@old_types) {
+ foreach my $cat (@{$data->{'taxcat'}}) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax classes"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $tax_class =
+ new FS::tax_class( { 'data_vendor' => 'cch',
+ 'taxclass' => $type->[0].':'.$cat->[0],
+ 'description' => $type->[1].':'.$cat->[1],
+ } );
+ my $error = $tax_class->insert;
+ return $error if $error;
+ $imported++;
+ }
+ }
+
+ foreach my $type (@{$data->{'taxtype'}}) {
+ foreach my $cat (@old_cats, @{$data->{'taxcat'}}) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax classes"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $tax_class =
+ new FS::tax_class( { 'data_vendor' => 'cch',
+ 'taxclass' => $type->[0].':'.$cat->[0],
+ 'description' => $type->[1].':'.$cat->[1],
+ } );
+ my $error = $tax_class->insert;
+ return $error if $error;
+ $imported++;
+ }
+ }
+
+ '';
+ };
+
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $hook = sub {};
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ 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;
+
+ while ( defined($line=<$fh>) ) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax classes"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ my @columns = $csv->fields();
+
+ my %tax_class = ( 'data_vendor' => $format );
+ foreach my $field ( @fields ) {
+ $tax_class{$field} = shift @columns;
+ }
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%tax_class);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ next unless scalar(keys %tax_class);
+
+ my $tax_class = new FS::tax_class( \%tax_class );
+ $error = $tax_class->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_class for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ my $error = &{$endhook}();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_class for $line: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty File!" unless ($imported || $format eq 'cch-update');
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+ batch_import does not handle mixed I and D records in the same file for
+ format cch-update
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm
new file mode 100644
index 0000000..87f2014
--- /dev/null
+++ b/FS/FS/tax_rate.pm
@@ -0,0 +1,2039 @@
+package FS::tax_rate;
+
+use strict;
+use vars qw( @ISA $DEBUG $me
+ %tax_unittypes %tax_maxtypes %tax_basetypes %tax_authorities
+ %tax_passtypes %GetInfoType $keep_cch_files );
+use Date::Parse;
+use DateTime;
+use DateTime::Format::Strptime;
+use Storable qw( thaw nfreeze );
+use IO::File;
+use File::Temp;
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Response;
+use MIME::Base64;
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use FS::Record qw( qsearch qsearchs dbh dbdef );
+use FS::tax_class;
+use FS::cust_bill_pkg;
+use FS::cust_tax_location;
+use FS::tax_rate_location;
+use FS::part_pkg_taxrate;
+use FS::part_pkg_taxproduct;
+use FS::cust_main;
+use FS::Misc qw( csv_from_fixed );
+
+#i'd like to dump these
+use FS::CGI qw(rooturl popurl);
+use URI::Escape;
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::tax_rate]';
+$keep_cch_files = 0;
+
+=head1 NAME
+
+FS::tax_rate - Object methods for tax_rate objects
+
+=head1 SYNOPSIS
+
+ use FS::tax_rate;
+
+ $record = new FS::tax_rate \%hash;
+ $record = new FS::tax_rate { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::tax_rate object represents a tax rate, defined by locale.
+FS::tax_rate inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item taxnum
+
+primary key (assigned automatically for new tax rates)
+
+=item geocode
+
+a geographic location code provided by a tax data vendor
+
+=item data_vendor
+
+the tax data vendor
+
+=item location
+
+a location code provided by a tax authority
+
+=item taxclassnum
+
+a foreign key into FS::tax_class - the type of tax
+referenced but FS::part_pkg_taxrate
+eitem effective_date
+
+the time after which the tax applies
+
+=item tax
+
+percentage
+
+=item excessrate
+
+second bracket percentage
+
+=item taxbase
+
+the amount to which the tax applies (first bracket)
+
+=item taxmax
+
+a cap on the amount of tax if a cap exists
+
+=item usetax
+
+percentage on out of jurisdiction purchases
+
+=item useexcessrate
+
+second bracket percentage on out of jurisdiction purchases
+
+=item unittype
+
+one of the values in %tax_unittypes
+
+=item fee
+
+amount of tax per unit
+
+=item excessfee
+
+second bracket amount of tax per unit
+
+=item feebase
+
+the number of units to which the fee applies (first bracket)
+
+=item feemax
+
+the most units to which fees apply (first and second brackets)
+
+=item maxtype
+
+a value from %tax_maxtypes indicating how brackets accumulate (i.e. monthly, per invoice, etc)
+
+=item taxname
+
+if defined, printed on invoices instead of "Tax"
+
+=item taxauth
+
+a value from %tax_authorities
+
+=item basetype
+
+a value from %tax_basetypes indicating the tax basis
+
+=item passtype
+
+a value from %tax_passtypes indicating how the tax should displayed to the customer
+
+=item passflag
+
+'Y', 'N', or blank indicating the tax can be passed to the customer
+
+=item setuptax
+
+if 'Y', this tax does not apply to setup fees
+
+=item recurtax
+
+if 'Y', this tax does not apply to recurring fees
+
+=item manual
+
+if 'Y', has been manually edited
+
+=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 { 'tax_rate'; }
+
+=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;
+
+ foreach (qw( taxbase taxmax )) {
+ $self->$_(0) unless $self->$_;
+ }
+
+ $self->ut_numbern('taxnum')
+ || $self->ut_text('geocode')
+ || $self->ut_textn('data_vendor')
+ || $self->ut_textn('location')
+ || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
+ || $self->ut_snumbern('effective_date')
+ || $self->ut_float('tax')
+ || $self->ut_floatn('excessrate')
+ || $self->ut_money('taxbase')
+ || $self->ut_money('taxmax')
+ || $self->ut_floatn('usetax')
+ || $self->ut_floatn('useexcessrate')
+ || $self->ut_numbern('unittype')
+ || $self->ut_floatn('fee')
+ || $self->ut_floatn('excessfee')
+ || $self->ut_floatn('feemax')
+ || $self->ut_numbern('maxtype')
+ || $self->ut_textn('taxname')
+ || $self->ut_numbern('taxauth')
+ || $self->ut_numbern('basetype')
+ || $self->ut_numbern('passtype')
+ || $self->ut_enum('passflag', [ '', 'Y', 'N' ])
+ || $self->ut_enum('setuptax', [ '', 'Y' ] )
+ || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->ut_enum('inoutcity', [ '', 'I', 'O' ] )
+ || $self->ut_enum('inoutlocal', [ '', 'I', 'O' ] )
+ || $self->ut_enum('manual', [ '', 'Y' ] )
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->SUPER::check
+ ;
+
+}
+
+=item taxclass_description
+
+Returns the human understandable value associated with the related
+FS::tax_class.
+
+=cut
+
+sub taxclass_description {
+ my $self = shift;
+ my $tax_class = qsearchs('tax_class', {'taxclassnum' => $self->taxclassnum });
+ $tax_class ? $tax_class->description : '';
+}
+
+=item unittype_name
+
+Returns the human understandable value associated with the unittype column
+
+=cut
+
+%tax_unittypes = ( '0' => 'access line',
+ '1' => 'minute',
+ '2' => 'account',
+);
+
+sub unittype_name {
+ my $self = shift;
+ $tax_unittypes{$self->unittype};
+}
+
+=item maxtype_name
+
+Returns the human understandable value associated with the maxtype column
+
+=cut
+
+%tax_maxtypes = ( '0' => 'receipts per invoice',
+ '1' => 'receipts per item',
+ '2' => 'total utility charges per utility tax year',
+ '3' => 'total charges per utility tax year',
+ '4' => 'receipts per access line',
+ '9' => 'monthly receipts per location',
+);
+
+sub maxtype_name {
+ my $self = shift;
+ $tax_maxtypes{$self->maxtype};
+}
+
+=item basetype_name
+
+Returns the human understandable value associated with the basetype column
+
+=cut
+
+%tax_basetypes = ( '0' => 'sale price',
+ '1' => 'gross receipts',
+ '2' => 'sales taxable telecom revenue',
+ '3' => 'minutes carried',
+ '4' => 'minutes billed',
+ '5' => 'gross operating revenue',
+ '6' => 'access line',
+ '7' => 'account',
+ '8' => 'gross revenue',
+ '9' => 'portion gross receipts attributable to interstate service',
+ '10' => 'access line',
+ '11' => 'gross profits',
+ '12' => 'tariff rate',
+ '14' => 'account',
+ '15' => 'prior year gross receipts',
+);
+
+sub basetype_name {
+ my $self = shift;
+ $tax_basetypes{$self->basetype};
+}
+
+=item taxauth_name
+
+Returns the human understandable value associated with the taxauth column
+
+=cut
+
+%tax_authorities = ( '0' => 'federal',
+ '1' => 'state',
+ '2' => 'county',
+ '3' => 'city',
+ '4' => 'local',
+ '5' => 'county administered by state',
+ '6' => 'city administered by state',
+ '7' => 'city administered by county',
+ '8' => 'local administered by state',
+ '9' => 'local administered by county',
+);
+
+sub taxauth_name {
+ my $self = shift;
+ $tax_authorities{$self->taxauth};
+}
+
+=item passtype_name
+
+Returns the human understandable value associated with the passtype column
+
+=cut
+
+%tax_passtypes = ( '0' => 'separate tax line',
+ '1' => 'separate surcharge line',
+ '2' => 'surcharge not separated',
+ '3' => 'included in base rate',
+);
+
+sub passtype_name {
+ my $self = shift;
+ $tax_passtypes{$self->passtype};
+}
+
+=item taxline TAXABLES, [ OPTIONSHASH ]
+
+Returns a listref of a name and an amount of tax calculated for the list
+of packages/amounts referenced by TAXABLES. If an error occurs, a message
+is returned as a scalar.
+
+=cut
+
+sub taxline {
+ my $self = shift;
+
+ my $taxables;
+ my %opt = ();
+
+ if (ref($_[0]) eq 'ARRAY') {
+ $taxables = shift;
+ %opt = @_;
+ }else{
+ $taxables = [ @_ ];
+ #exemptions would be broken in this case
+ }
+
+ my $name = $self->taxname;
+ $name = 'Other surcharges'
+ if ($self->passtype == 2);
+ my $amount = 0;
+
+ if ( $self->disabled ) { # we always know how to handle disabled taxes
+ return {
+ 'name' => $name,
+ 'amount' => $amount,
+ };
+ }
+
+ my $taxable_charged = 0;
+ my @cust_bill_pkg = grep { $taxable_charged += $_ unless ref; ref; }
+ @$taxables;
+
+ warn "calculating taxes for ". $self->taxnum. " on ".
+ join (",", map { $_->pkgnum } @cust_bill_pkg)
+ if $DEBUG;
+
+ if ($self->passflag eq 'N') {
+ # return "fatal: can't (yet) handle taxes not passed to the customer";
+ # until someone needs to track these in freeside
+ return {
+ 'name' => $name,
+ 'amount' => 0,
+ };
+ }
+
+ my $maxtype = $self->maxtype || 0;
+ if ($maxtype != 0 && $maxtype != 9) {
+ return $self->_fatal_or_null( 'tax with "'.
+ $self->maxtype_name. '" threshold'
+ );
+ }
+
+ if ($maxtype == 9) {
+ return
+ $self->_fatal_or_null( 'tax with "'. $self->maxtype_name. '" threshold' );
+ # "texas" tax
+ }
+
+ # we treat gross revenue as gross receipts and expect the tax data
+ # to DTRT (i.e. tax on tax rules)
+ if ($self->basetype != 0 && $self->basetype != 1 &&
+ $self->basetype != 5 && $self->basetype != 6 &&
+ $self->basetype != 7 && $self->basetype != 8 &&
+ $self->basetype != 14
+ ) {
+ return
+ $self->_fatal_or_null( 'tax with "'. $self->basetype_name. '" basis' );
+ }
+
+ unless ($self->setuptax =~ /^Y$/i) {
+ $taxable_charged += $_->setup foreach @cust_bill_pkg;
+ }
+ unless ($self->recurtax =~ /^Y$/i) {
+ $taxable_charged += $_->recur foreach @cust_bill_pkg;
+ }
+
+ my $taxable_units = 0;
+ unless ($self->recurtax =~ /^Y$/i) {
+ if (( $self->unittype || 0 ) == 0) {
+ my %seen = ();
+ foreach (@cust_bill_pkg) {
+ $taxable_units += $_->units
+ unless $seen{$_->pkgnum};
+ $seen{$_->pkgnum}++;
+ }
+ }elsif ($self->unittype == 1) {
+ return $self->_fatal_or_null( 'fee with minute unit type' );
+ }elsif ($self->unittype == 2) {
+ $taxable_units = 1;
+ }else {
+ return $self->_fatal_or_null( 'unknown unit type in tax'. $self->taxnum );
+ }
+ }
+
+ #
+ # XXX insert exemption handling here
+ #
+ # the tax or fee is applied to taxbase or feebase and then
+ # the excessrate or excess fee is applied to taxmax or feemax
+ #
+
+ $amount += $taxable_charged * $self->tax;
+ $amount += $taxable_units * $self->fee;
+
+ warn "calculated taxes as [ $name, $amount ]\n"
+ if $DEBUG;
+
+ return {
+ 'name' => $name,
+ 'amount' => $amount,
+ };
+
+}
+
+sub _fatal_or_null {
+ my ($self, $error) = @_;
+
+ my $conf = new FS::Conf;
+
+ $error = "can't yet handle ". $error;
+ my $name = $self->taxname;
+ $name = 'Other surcharges'
+ if ($self->passtype == 2);
+
+ if ($conf->exists('ignore_incalculable_taxes')) {
+ warn "WARNING: $error; billing anyway per ignore_incalculable_taxes conf\n";
+ return { name => $name, amount => 0 };
+ } else {
+ return "fatal: $error";
+ }
+}
+
+=item tax_on_tax CUST_MAIN
+
+Returns a list of taxes which are candidates for taxing taxes for the
+given customer (see L<FS::cust_main>)
+
+=cut
+
+ #hot
+sub tax_on_tax {
+ #akshun
+ my $self = shift;
+ my $cust_main = shift;
+
+ warn "looking up taxes on tax ". $self->taxnum. " for customer ".
+ $cust_main->custnum
+ if $DEBUG;
+
+ my $geocode = $cust_main->geocode($self->data_vendor);
+
+ # CCH oddness in m2m
+ my $dbh = dbh;
+ my $extra_sql = ' AND ('.
+ join(' OR ', map{ 'geocode = '. $dbh->quote(substr($geocode, 0, $_)) }
+ qw(10 5 2)
+ ).
+ ')';
+
+ my $order_by = 'ORDER BY taxclassnum, length(geocode) desc';
+ my $select = 'DISTINCT ON(taxclassnum) *';
+
+ # should qsearch preface columns with the table to facilitate joins?
+ my @taxclassnums = map { $_->taxclassnum }
+ qsearch( { 'table' => 'part_pkg_taxrate',
+ 'select' => $select,
+ 'hashref' => { 'data_vendor' => $self->data_vendor,
+ 'taxclassnumtaxed' => $self->taxclassnum,
+ },
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $order_by,
+ } );
+
+ return () unless @taxclassnums;
+
+ $extra_sql =
+ "AND (". join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")";
+
+ qsearch({ 'table' => 'tax_rate',
+ 'hashref' => { 'geocode' => $geocode, },
+ 'extra_sql' => $extra_sql,
+ })
+
+}
+
+=item tax_rate_location
+
+Returns an object representing the location associated with this tax
+(see L<FS::tax_rate_location>)
+
+=cut
+
+sub tax_rate_location {
+ my $self = shift;
+
+ qsearchs({ 'table' => 'tax_rate_location',
+ 'hashref' => { 'data_vendor' => $self->data_vendor,
+ 'geocode' => $self->geocode,
+ 'disabled' => '',
+ },
+ }) ||
+ new FS::tax_rate_location;
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item batch_import
+
+=cut
+
+sub _progressbar_foo {
+ return (0, time, 5);
+}
+
+sub batch_import {
+ my ($param, $job) = @_;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my %insert = ();
+ my %delete = ();
+
+ my @fields;
+ my $hook;
+
+ my @column_lengths = ();
+ my @column_callbacks = ();
+ if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
+ $format =~ s/-fixed//;
+ my $date_format = sub { my $r='';
+ /^(\d{4})(\d{2})(\d{2})$/ && ($r="$3/$2/$1");
+ $r;
+ };
+ my $trim = sub { my $r = shift; $r =~ s/^\s*//; $r =~ s/\s*$//; $r };
+ push @column_lengths, qw( 10 1 1 8 8 5 8 8 8 1 2 2 30 8 8 10 2 8 2 1 2 2 );
+ push @column_lengths, 1 if $format eq 'cch-update';
+ push @column_callbacks, $trim foreach (@column_lengths); # 5, 6, 15, 17 esp
+ $column_callbacks[8] = $date_format;
+ }
+
+ my $line;
+ my ( $count, $last, $min_sec ) = _progressbar_foo();
+ if ( $job || scalar(@column_callbacks) ) {
+ my $error =
+ csv_from_fixed(\$fh, \$count, \@column_lengths, \@column_callbacks);
+ return $error if $error;
+ }
+ $count *=2;
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
+ @fields = qw( geocode inoutcity inoutlocal tax location taxbase taxmax
+ excessrate effective_date taxauth taxtype taxcat taxname
+ usetax useexcessrate fee unittype feemax maxtype passflag
+ passtype basetype );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $hook = sub {
+ my $hash = shift;
+
+ $hash->{'actionflag'} ='I' if ($hash->{'data_vendor'} eq 'cch');
+ $hash->{'data_vendor'} ='cch';
+ my $parser = new DateTime::Format::Strptime( pattern => "%m/%d/%Y",
+ time_zone => 'floating',
+ );
+ my $dt = $parser->parse_datetime( $hash->{'effective_date'} );
+ $hash->{'effective_date'} = $dt ? $dt->epoch : '';
+
+ $hash->{$_} =~ s/\s//g foreach qw( inoutcity inoutlocal ) ;
+ $hash->{$_} = sprintf("%.2f", $hash->{$_}) foreach qw( taxbase taxmax );
+
+ my $taxclassid =
+ join(':', map{ $hash->{$_} } qw(taxtype taxcat) );
+
+ my %tax_class = ( 'data_vendor' => 'cch',
+ 'taxclass' => $taxclassid,
+ );
+
+ my $tax_class = qsearchs( 'tax_class', \%tax_class );
+ return "Error updating tax rate: no tax class $taxclassid"
+ unless $tax_class;
+
+ $hash->{'taxclassnum'} = $tax_class->taxclassnum;
+
+ foreach (qw( taxtype taxcat )) {
+ delete($hash->{$_});
+ }
+
+ my %passflagmap = ( '0' => '',
+ '1' => 'Y',
+ '2' => 'N',
+ );
+ $hash->{'passflag'} = $passflagmap{$hash->{'passflag'}}
+ if exists $passflagmap{$hash->{'passflag'}};
+
+ foreach (keys %$hash) {
+ $hash->{$_} = substr($hash->{$_}, 0, 80)
+ if length($hash->{$_}) > 80;
+ }
+
+ my $actionflag = delete($hash->{'actionflag'});
+
+ $hash->{'taxname'} =~ s/`/'/g;
+ $hash->{'taxname'} =~ s|\\|/|g;
+
+ return '' if $format eq 'cch'; # but not cch-update
+
+ if ($actionflag eq 'I') {
+ $insert{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = { %$hash };
+ }elsif ($actionflag eq 'D') {
+ $delete{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = { %$hash };
+ }else{
+ return "Unexpected action flag: ". $hash->{'actionflag'};
+ }
+
+ delete($hash->{$_}) for keys %$hash;
+
+ '';
+
+ };
+
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $hook = sub {};
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+
+ my $imported = 0;
+
+ 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 ( defined($line=<$fh>) ) {
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax rates"
+ );
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ $last = time;
+ }
+ }
+
+ my @columns = $csv->fields();
+
+ my %tax_rate = ( 'data_vendor' => $format );
+ foreach my $field ( @fields ) {
+ $tax_rate{$field} = shift @columns;
+ }
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%tax_rate);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if (scalar(keys %tax_rate)) { #inserts only, not updates for cch
+
+ my $tax_rate = new FS::tax_rate( \%tax_rate );
+ $error = $tax_rate->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_rate for $line: $error";
+ }
+
+ }
+
+ $imported++;
+
+ }
+
+ for (grep { !exists($delete{$_}) } keys %insert) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax rates"
+ );
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ $last = time;
+ }
+ }
+
+ my $tax_rate = new FS::tax_rate( $insert{$_} );
+ my $error = $tax_rate->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ my $hashref = $insert{$_};
+ $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) );
+ return "can't insert tax_rate for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ for (grep { exists($delete{$_}) } keys %insert) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax rates"
+ );
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ $last = time;
+ }
+ }
+
+ my $old = qsearchs( 'tax_rate', $delete{$_} );
+ unless ($old) {
+ $dbh->rollback if $oldAutoCommit;
+ $old = $delete{$_};
+ return "can't find tax_rate to replace for: ".
+ #join(" ", map { "$_ => ". $old->{$_} } @fields);
+ join(" ", map { "$_ => ". $old->{$_} } keys(%$old) );
+ }
+ my $new = new FS::tax_rate({ $old->hash, %{$insert{$_}}, 'manual' => '' });
+ $new->taxnum($old->taxnum);
+ my $error = $new->replace($old);
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ my $hashref = $insert{$_};
+ $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) );
+ return "can't replace tax_rate for $line: $error";
+ }
+
+ $imported++;
+ $imported++;
+ }
+
+ for (grep { !exists($insert{$_}) } keys %delete) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count ). ",Importing tax rates"
+ );
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+ $last = time;
+ }
+ }
+
+ my $tax_rate = qsearchs( 'tax_rate', $delete{$_} );
+ unless ($tax_rate) {
+ $dbh->rollback if $oldAutoCommit;
+ $tax_rate = $delete{$_};
+ return "can't find tax_rate to delete for: ".
+ #join(" ", map { "$_ => ". $tax_rate->{$_} } @fields);
+ join(" ", map { "$_ => ". $tax_rate->{$_} } keys(%$tax_rate) );
+ }
+ my $error = $tax_rate->delete;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ my $hashref = $delete{$_};
+ $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) );
+ return "can't delete tax_rate for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless ($imported || $format eq 'cch-update');
+
+ ''; #no error
+
+}
+
+=item process_batch_import
+
+Load a batch import as a queued JSRPC job
+
+=cut
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $param = thaw(decode_base64(shift));
+ my $args = '$job, encode_base64( nfreeze( $param ) )';
+
+ my $method = '_perform_batch_import';
+ if ( $param->{reload} ) {
+ $method = 'process_batch_reload';
+ }
+
+ eval "$method($args);";
+ if ($@) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $@;
+ }
+
+ #success!
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+sub _perform_batch_import {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ my $format = $param->{'format'}; #well... this is all cch specific
+
+ my $files = $param->{'uploaded_files'}
+ or die "No files provided.";
+
+ my (%files) = map { /^(\w+):((taxdata\/\w+\.\w+\/)?[\.\w]+)$/ ? ($1,$2):() }
+ split /,/, $files;
+
+ if ( $format eq 'cch' || $format eq 'cch-fixed'
+ || $format eq 'cch-update' || $format eq 'cch-fixed-update' )
+ {
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $error = '';
+ my @insert_list = ();
+ my @delete_list = ();
+ my @predelete_list = ();
+ my $insertname = '';
+ my $deletename = '';
+ my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc;
+
+ my @list = ( 'GEOCODE', \&FS::tax_rate_location::batch_import,
+ 'CODE', \&FS::tax_class::batch_import,
+ 'PLUS4', \&FS::cust_tax_location::batch_import,
+ 'ZIP', \&FS::cust_tax_location::batch_import,
+ 'TXMATRIX', \&FS::part_pkg_taxrate::batch_import,
+ 'DETAIL', \&FS::tax_rate::batch_import,
+ );
+ while( scalar(@list) ) {
+ my ( $name, $import_sub ) = splice( @list, 0, 2 );
+ my $file = lc($name). 'file';
+
+ unless ($files{$file}) {
+ $error = "No $name supplied";
+ next;
+ }
+ next if $name eq 'DETAIL' && $format =~ /update/;
+
+ my $filename = "$dir/". $files{$file};
+
+ if ( $format =~ /update/ ) {
+
+ ( $error, $insertname, $deletename ) =
+ _perform_cch_insert_delete_split( $name, $filename, $dir, $format )
+ unless $error;
+ last if $error;
+
+ unlink $filename or warn "Can't delete $filename: $!"
+ unless $keep_cch_files;
+ push @insert_list, $name, $insertname, $import_sub, $format;
+ if ( $name eq 'GEOCODE' ) { #handle this whole ordering issue better
+ unshift @predelete_list, $name, $deletename, $import_sub, $format;
+ } else {
+ unshift @delete_list, $name, $deletename, $import_sub, $format;
+ }
+
+ } else {
+
+ push @insert_list, $name, $filename, $import_sub, $format;
+
+ }
+
+ }
+
+ push @insert_list,
+ 'DETAIL', "$dir/".$files{detail}, \&FS::tax_rate::batch_import, $format
+ if $format =~ /update/;
+
+ $error ||= _perform_cch_tax_import( $job,
+ [ @predelete_list ],
+ [ @insert_list ],
+ [ @delete_list ],
+ );
+
+
+ @list = ( @predelete_list, @insert_list, @delete_list );
+ while( !$keep_cch_files && scalar(@list) ) {
+ my ( undef, $file, undef, undef ) = splice( @list, 0, 4 );
+ unlink $file or warn "Can't delete $file: $!";
+ }
+
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+
+ }else{
+ die "Unknown format: $format";
+ }
+
+}
+
+
+sub _perform_cch_tax_import {
+ my ( $job, $predelete_list, $insert_list, $delete_list ) = @_;
+
+ my $error = '';
+ foreach my $list ($predelete_list, $insert_list, $delete_list) {
+ while( scalar(@$list) ) {
+ my ( $name, $file, $method, $format ) = splice( @$list, 0, 4 );
+ my $fmt = "$format-update";
+ $fmt = $format. ( lc($name) eq 'zip' ? '-zip' : '' );
+ open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!";
+ $error ||= &{$method}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
+ close $fh;
+ }
+ }
+
+ return $error;
+}
+
+sub _perform_cch_insert_delete_split {
+ my ($name, $filename, $dir, $format) = @_;
+
+ my $error = '';
+
+ open my $fh, "< $filename"
+ or $error ||= "Can't open $name file $filename: $!";
+
+ my $ifh = new File::Temp( TEMPLATE => "$name.insert.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0, #meh
+ ) or die "can't open temp file: $!\n";
+ my $insertname = $ifh->filename;
+
+ my $dfh = new File::Temp( TEMPLATE => "$name.delete.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0, #meh
+ ) or die "can't open temp file: $!\n";
+ my $deletename = $dfh->filename;
+
+ my $insert_pattern = ($format eq 'cch-update') ? qr/"I"\s*$/ : qr/I\s*$/;
+ my $delete_pattern = ($format eq 'cch-update') ? qr/"D"\s*$/ : qr/D\s*$/;
+ while(<$fh>) {
+ my $handle = '';
+ $handle = $ifh if $_ =~ /$insert_pattern/;
+ $handle = $dfh if $_ =~ /$delete_pattern/;
+ unless ($handle) {
+ $error = "bad input line: $_" unless $handle;
+ last;
+ }
+ print $handle $_;
+ }
+ close $fh;
+ close $ifh;
+ close $dfh;
+
+ return ($error, $insertname, $deletename);
+}
+
+sub _perform_cch_diff {
+ my ($name, $newdir, $olddir) = @_;
+
+ my %oldlines = ();
+
+ if ($olddir) {
+ open my $oldcsvfh, "$olddir/$name.txt"
+ or die "failed to open $olddir/$name.txt: $!\n";
+
+ while(<$oldcsvfh>) {
+ chomp;
+ $oldlines{$_} = 1;
+ }
+ close $oldcsvfh;
+ }
+
+ open my $newcsvfh, "$newdir/$name.txt"
+ or die "failed to open $newdir/$name.txt: $!\n";
+
+ my $dfh = new File::Temp( TEMPLATE => "$name.diff.XXXXXXXX",
+ DIR => "$newdir",
+ UNLINK => 0, #meh
+ ) or die "can't open temp file: $!\n";
+ my $diffname = $dfh->filename;
+
+ while(<$newcsvfh>) {
+ chomp;
+ if (exists($oldlines{$_})) {
+ $oldlines{$_} = 0;
+ } else {
+ print $dfh $_, ',"I"', "\n";
+ }
+ }
+ close $newcsvfh;
+
+ for (keys %oldlines) {
+ print $dfh $_, ',"D"', "\n" if $oldlines{$_};
+ }
+
+ close $dfh;
+
+ return $diffname;
+}
+
+sub _cch_fetch_and_unzip {
+ my ( $job, $urls, $secret, $dir ) = @_;
+
+ my $ua = new LWP::UserAgent;
+ foreach my $url (split ',', $urls) {
+ my @name = split '/', $url; #somewhat restrictive
+ my $name = pop @name;
+ $name =~ /([\w.]+)/; # untaint that which we don't trust so much any more
+ $name = $1;
+
+ open my $taxfh, ">$dir/$name" or die "Can't open $dir/$name: $!\n";
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $res = $ua->request(
+ new HTTP::Request( GET => $url ),
+ sub {
+ print $taxfh $_[0] or die "Can't write to $dir/$name: $!\n";
+ my $content_length = $_[1]->content_length;
+ $imported += length($_[0]);
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ ($content_length ? int(100 * $imported/$content_length) : 0 ).
+ ",Downloading data from CCH"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ },
+ );
+ die "download of $url failed: ". $res->status_line
+ unless $res->is_success;
+
+ close $taxfh;
+ my $error = $job->update_statustext( "0,Unpacking data" );
+ die $error if $error;
+ $secret =~ /([\w.]+)/; # untaint that which we don't trust so much any more
+ $secret = $1;
+ system('unzip', "-P", $secret, "-d", "$dir", "$dir/$name") == 0
+ or die "unzip -P $secret -d $dir $dir/$name failed";
+ #unlink "$dir/$name";
+ }
+}
+
+sub _cch_extract_csv_from_dbf {
+ my ( $job, $dir, $name ) = @_;
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ eval "use XBase;";
+ die $@ if $@;
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $error = $job->update_statustext( "0,Unpacking $name" );
+ die $error if $error;
+ warn "opening $dir.new/$name.dbf\n" if $DEBUG;
+ my $table = new XBase 'name' => "$dir.new/$name.dbf";
+ die "failed to access $dir.new/$name.dbf: ". XBase->errstr
+ unless defined($table);
+ my $count = $table->last_record; # approximately;
+ open my $csvfh, ">$dir.new/$name.txt"
+ or die "failed to open $dir.new/$name.txt: $!\n";
+
+ my $csv = new Text::CSV_XS { 'always_quote' => 1 };
+ my @fields = $table->field_names;
+ my $cursor = $table->prepare_select;
+ my $format_date =
+ sub { my $date = shift;
+ $date =~ /^(\d{4})(\d{2})(\d{2})$/ && ($date = "$2/$3/$1");
+ $date;
+ };
+ while (my $row = $cursor->fetch_hashref) {
+ $csv->combine( map { ($table->field_type($_) eq 'D')
+ ? &{$format_date}($row->{$_})
+ : $row->{$_}
+ }
+ @fields
+ );
+ print $csvfh $csv->string, "\n";
+ $imported++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int(100 * $imported/$count). ",Unpacking $name"
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+ $table->close;
+ close $csvfh;
+}
+
+sub _remember_disabled_taxes {
+ my ( $job, $format, $disabled_tax_rate ) = @_;
+
+ # cch specific hash
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+
+ my @items = qsearch( { table => 'tax_rate',
+ hashref => { disabled => 'Y',
+ data_vendor => $format,
+ },
+ select => 'geocode, taxclassnum',
+ }
+ );
+ my $count = scalar(@items);
+ foreach my $tax_rate ( @items ) {
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Remembering disabled taxes"
+ );
+ $last = time;
+ }
+ $imported++;
+ my $tax_class =
+ qsearchs( 'tax_class', { taxclassnum => $tax_rate->taxclassnum } );
+ unless ( $tax_class ) {
+ warn "failed to find tax_class ". $tax_rate->taxclassnum;
+ next;
+ }
+ $disabled_tax_rate->{$tax_rate->geocode. ':'. $tax_class->taxclass} = 1;
+ }
+}
+
+sub _remember_tax_products {
+ my ( $job, $format, $taxproduct ) = @_;
+
+ # XXX FIXME this loop only works when cch is the only data provider
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+
+ my $extra_sql = "WHERE taxproductnum IS NOT NULL OR ".
+ "0 < ( SELECT count(*) from part_pkg_option WHERE ".
+ " part_pkg_option.pkgpart = part_pkg.pkgpart AND ".
+ " optionname LIKE 'usage_taxproductnum_%' AND ".
+ " optionvalue != '' )";
+ my @items = qsearch( { table => 'part_pkg',
+ select => 'DISTINCT pkgpart,taxproductnum',
+ hashref => {},
+ extra_sql => $extra_sql,
+ }
+ );
+ my $count = scalar(@items);
+ foreach my $part_pkg ( @items ) {
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Remembering tax products"
+ );
+ $last = time;
+ }
+ $imported++;
+ warn "working with package part ". $part_pkg->pkgpart.
+ "which has a taxproductnum of ". $part_pkg->taxproductnum. "\n" if $DEBUG;
+ my $part_pkg_taxproduct = $part_pkg->taxproduct('');
+ $taxproduct->{$part_pkg->pkgpart}->{''} = $part_pkg_taxproduct->taxproduct
+ if $part_pkg_taxproduct && $part_pkg_taxproduct->data_vendor eq $format;
+
+ foreach my $option ( $part_pkg->part_pkg_option ) {
+ next unless $option->optionname =~ /^usage_taxproductnum_(\w+)$/;
+ my $class = $1;
+
+ $part_pkg_taxproduct = $part_pkg->taxproduct($class);
+ $taxproduct->{$part_pkg->pkgpart}->{$class} =
+ $part_pkg_taxproduct->taxproduct
+ if $part_pkg_taxproduct && $part_pkg_taxproduct->data_vendor eq $format;
+ }
+ }
+}
+
+sub _restore_remembered_tax_products {
+ my ( $job, $format, $taxproduct ) = @_;
+
+ # cch specific
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $count = scalar(keys %$taxproduct);
+ foreach my $pkgpart ( keys %$taxproduct ) {
+ warn "restoring taxproductnums on pkgpart $pkgpart\n" if $DEBUG;
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Restoring tax products"
+ );
+ $last = time;
+ }
+ $imported++;
+
+ my $part_pkg = qsearchs('part_pkg', { pkgpart => $pkgpart } );
+ unless ( $part_pkg ) {
+ return "somehow failed to find part_pkg with pkgpart $pkgpart!\n";
+ }
+
+ my %options = $part_pkg->options;
+ my %pkg_svc = map { $_->svcpart => $_->quantity } $part_pkg->pkg_svc;
+ my $primary_svc = $part_pkg->svcpart;
+ my $new = new FS::part_pkg { $part_pkg->hash };
+
+ foreach my $class ( keys %{ $taxproduct->{$pkgpart} } ) {
+ warn "working with class '$class'\n" if $DEBUG;
+ my $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct',
+ { taxproduct => $taxproduct->{$pkgpart}->{$class},
+ data_vendor => $format,
+ }
+ );
+
+ unless ( $part_pkg_taxproduct ) {
+ return "failed to find part_pkg_taxproduct (".
+ $taxproduct->{pkgpart}->{$class}. ") for pkgpart $pkgpart\n";
+ }
+
+ if ( $class eq '' ) {
+ $new->taxproductnum($part_pkg_taxproduct->taxproductnum);
+ next;
+ }
+
+ $options{"usage_taxproductnum_$class"} =
+ $part_pkg_taxproduct->taxproductnum;
+
+ }
+
+ my $error = $new->replace( $part_pkg,
+ 'pkg_svc' => \%pkg_svc,
+ 'primary_svc' => $primary_svc,
+ 'options' => \%options,
+ );
+
+ return $error if $error;
+
+ }
+
+ '';
+}
+
+sub _restore_remembered_disabled_taxes {
+ my ( $job, $format, $disabled_tax_rate ) = @_;
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $count = scalar(keys %$disabled_tax_rate);
+ foreach my $key (keys %$disabled_tax_rate) {
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Disabling tax rates"
+ );
+ $last = time;
+ }
+ $imported++;
+ my ($geocode,$taxclass) = split /:/, $key, 2;
+ my @tax_class = qsearch( 'tax_class', { data_vendor => $format,
+ taxclass => $taxclass,
+ } );
+ return "found multiple tax_class records for format $format class $taxclass"
+ if scalar(@tax_class) > 1;
+
+ unless (scalar(@tax_class)) {
+ warn "no tax_class for format $format class $taxclass\n";
+ next;
+ }
+
+ my @tax_rate =
+ qsearch('tax_rate', { data_vendor => $format,
+ geocode => $geocode,
+ taxclassnum => $tax_class[0]->taxclassnum,
+ }
+ );
+
+ if (scalar(@tax_rate) > 1) {
+ return "found multiple tax_rate records for format $format geocode ".
+ "$geocode and taxclass $taxclass ( taxclassnum ".
+ $tax_class[0]->taxclassnum. " )";
+ }
+
+ if (scalar(@tax_rate)) {
+ $tax_rate[0]->disabled('Y');
+ my $error = $tax_rate[0]->replace;
+ return $error if $error;
+ }
+ }
+}
+
+sub _remove_old_tax_data {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Removing old tax data" );
+ die $error if $error;
+
+ my $sql = "UPDATE public.tax_rate_location SET disabled='Y' ".
+ "WHERE data_vendor = ". $dbh->quote($format);
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+
+ my @table = qw(
+ tax_rate part_pkg_taxrate part_pkg_taxproduct tax_class cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ $sql = "DELETE FROM public.$table WHERE data_vendor = ".
+ $dbh->quote($format);
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ if ( $format eq 'cch' ) {
+ $sql = "DELETE FROM public.cust_tax_location WHERE data_vendor = ".
+ $dbh->quote("$format-zip");
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ '';
+}
+
+sub _create_temporary_tables {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Creating temporary tables" );
+ die $error if $error;
+
+ my @table = qw( tax_rate
+ tax_rate_location
+ part_pkg_taxrate
+ part_pkg_taxproduct
+ tax_class
+ cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ my $sql =
+ "CREATE TEMPORARY TABLE $table ( LIKE $table INCLUDING DEFAULTS )";
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ '';
+}
+
+sub _copy_from_temp {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Making permanent" );
+ die $error if $error;
+
+ my @table = qw( tax_rate
+ tax_rate_location
+ part_pkg_taxrate
+ part_pkg_taxproduct
+ tax_class
+ cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ my $sql =
+ "INSERT INTO public.$table SELECT * from $table";
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ '';
+}
+
+=item process_download_and_reload
+
+Download and process a tax update as a queued JSRPC job after wiping the
+existing wipable tax data.
+
+=cut
+
+sub process_download_and_reload {
+ _process_reload('process_download_and_update', @_);
+}
+
+
+=item process_batch_reload
+
+Load and process a tax update from the provided files as a queued JSRPC job
+after wiping the existing wipable tax data.
+
+=cut
+
+sub process_batch_reload {
+ _process_reload('_perform_batch_import', @_);
+}
+
+
+sub _process_reload {
+ my ( $method, $job ) = ( shift, shift );
+
+ my $param = thaw(decode_base64($_[0]));
+ my $format = $param->{'format'}; #well... this is all cch specific
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+
+ if ( $job ) { # progress bar
+ my $error = $job->update_statustext( 0 );
+ die $error if $error;
+ }
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $error = '';
+
+ my $sql =
+ "SELECT count(*) FROM part_pkg_taxoverride JOIN tax_class ".
+ "USING (taxclassnum) WHERE data_vendor = '$format'";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ die "Don't (yet) know how to handle part_pkg_taxoverride records."
+ if $sth->fetchrow_arrayref->[0];
+
+ # really should get a table EXCLUSIVE lock here
+
+ #remember disabled taxes
+ my %disabled_tax_rate = ();
+ $error ||= _remember_disabled_taxes( $job, $format, \%disabled_tax_rate );
+
+ #remember tax products
+ my %taxproduct = ();
+ $error ||= _remember_tax_products( $job, $format, \%taxproduct );
+
+ #create temp tables
+ $error ||= _create_temporary_tables( $job, $format );
+
+ #import new data
+ unless ($error) {
+ my $args = '$job, @_';
+ eval "$method($args);";
+ $error = $@ if $@;
+ }
+
+ #restore taxproducts
+ $error ||= _restore_remembered_tax_products( $job, $format, \%taxproduct );
+
+ #disable tax_rates
+ $error ||=
+ _restore_remembered_disabled_taxes( $job, $format, \%disabled_tax_rate );
+
+ #wipe out the old data
+ $error ||= _remove_old_tax_data( $job, $format );
+
+ #untemporize
+ $error ||= _copy_from_temp( $job, $format );
+
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }
+
+ #success!
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+
+=item process_download_and_update
+
+Download and process a tax update as a queued JSRPC job
+
+=cut
+
+sub process_download_and_update {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ my $format = $param->{'format'}; #well... this is all cch specific
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+
+ if ( $job ) { # progress bar
+ my $error = $job->update_statustext( 0);
+ die $error if $error;
+ }
+
+ my $cache_dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
+ my $dir = $cache_dir. 'taxdata';
+ unless (-d $dir) {
+ mkdir $dir or die "can't create $dir: $!\n";
+ }
+
+ if ($format eq 'cch') {
+
+ my @namelist = qw( code detail geocode plus4 txmatrix zip );
+
+ my $conf = new FS::Conf;
+ die "direct download of tax data not enabled\n"
+ unless $conf->exists('taxdatadirectdownload');
+ my ( $urls, $username, $secret, $states ) =
+ $conf->config('taxdatadirectdownload');
+ die "No tax download URL provided. ".
+ "Did you set the taxdatadirectdownload configuration value?\n"
+ unless $urls;
+
+ $dir .= '/cch';
+
+ my $dbh = dbh;
+ my $error = '';
+
+ # really should get a table EXCLUSIVE lock here
+ # check if initial import or update
+ #
+ # relying on mkdir "$dir.new" as a mutex
+
+ my $sql = "SELECT count(*) from tax_rate WHERE data_vendor='$format'";
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+ my $update = $sth->fetchrow_arrayref->[0];
+
+ # create cache and/or rotate old tax data
+
+ if (-d $dir) {
+
+ if (-d "$dir.4") {
+ opendir(my $dirh, "$dir.4") or die "failed to open $dir.4: $!\n";
+ foreach my $file (readdir($dirh)) {
+ unlink "$dir.4/$file" if (-f "$dir.4/$file");
+ }
+ closedir($dirh);
+ rmdir "$dir.4";
+ }
+
+ for (3, 2, 1) {
+ if ( -e "$dir.$_" ) {
+ rename "$dir.$_", "$dir.". ($_+1) or die "can't rename $dir.$_: $!\n";
+ }
+ }
+ rename "$dir", "$dir.1" or die "can't rename $dir: $!\n";
+
+ } else {
+
+ die "can't find previous tax data\n" if $update;
+
+ }
+
+ mkdir "$dir.new" or die "can't create $dir.new: $!\n";
+
+ # fetch and unpack the zip files
+
+ _cch_fetch_and_unzip( $job, $urls, $secret, "$dir.new" );
+
+ # extract csv files from the dbf files
+
+ foreach my $name ( @namelist ) {
+ _cch_extract_csv_from_dbf( $job, $dir, $name );
+ }
+
+ # generate the diff files
+
+ my @list = ();
+ foreach my $name ( @namelist ) {
+ my $difffile = "$dir.new/$name.txt";
+ if ($update) {
+ my $error = $job->update_statustext( "0,Comparing to previous $name" );
+ die $error if $error;
+ warn "processing $dir.new/$name.txt\n" if $DEBUG;
+ my $olddir = $update ? "$dir.1" : "";
+ $difffile = _perform_cch_diff( $name, "$dir.new", $olddir );
+ }
+ $difffile =~ s/^$cache_dir//;
+ push @list, "${name}file:$difffile";
+ }
+
+ # perform the import
+ local $keep_cch_files = 1;
+ $param->{uploaded_files} = join( ',', @list );
+ $param->{format} .= '-update' if $update;
+ $error ||=
+ _perform_batch_import( $job, encode_base64( nfreeze( $param ) ) );
+
+ rename "$dir.new", "$dir"
+ or die "cch tax update processed, but can't rename $dir.new: $!\n";
+
+ }else{
+ die "Unknown format: $format";
+ }
+}
+
+=item browse_queries PARAMS
+
+Returns a list consisting of a hashref suited for use as the argument
+to qsearch, and sql query string. Each is based on the PARAMS hashref
+of keys and values which frequently would be passed as C<scalar($cgi->Vars)>
+from a form. This conveniently creates the query hashref and count_query
+string required by the browse and search elements. As a side effect,
+the PARAMS hashref is untainted and keys with unexpected values are removed.
+
+=cut
+
+sub browse_queries {
+ my $params = shift;
+
+ my $query = {
+ 'table' => 'tax_rate',
+ 'hashref' => {},
+ 'order_by' => 'ORDER BY geocode, taxclassnum',
+ },
+
+ my $extra_sql = '';
+
+ if ( $params->{data_vendor} =~ /^(\w+)$/ ) {
+ $extra_sql .= ' WHERE data_vendor = '. dbh->quote($1);
+ } else {
+ delete $params->{data_vendor};
+ }
+
+ if ( $params->{geocode} =~ /^(\w+)$/ ) {
+ $extra_sql .= ( $extra_sql =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
+ 'geocode LIKE '. dbh->quote($1.'%');
+ } else {
+ delete $params->{geocode};
+ }
+
+ if ( $params->{taxclassnum} =~ /^(\d+)$/ &&
+ qsearchs( 'tax_class', {'taxclassnum' => $1} )
+ )
+ {
+ $extra_sql .= ( $extra_sql =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
+ ' taxclassnum = '. dbh->quote($1)
+ } else {
+ delete $params->{taxclassnun};
+ }
+
+ my $tax_type = $1
+ if ( $params->{tax_type} =~ /^(\d+)$/ );
+ delete $params->{tax_type}
+ unless $tax_type;
+
+ my $tax_cat = $1
+ if ( $params->{tax_cat} =~ /^(\d+)$/ );
+ delete $params->{tax_cat}
+ unless $tax_cat;
+
+ my @taxclassnum = ();
+ if ($tax_type || $tax_cat ) {
+ my $compare = "LIKE '". ( $tax_type || "%" ). ":". ( $tax_cat || "%" ). "'";
+ $compare = "= '$tax_type:$tax_cat'" if ($tax_type && $tax_cat);
+ @taxclassnum = map { $_->taxclassnum }
+ qsearch({ 'table' => 'tax_class',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE taxclass $compare",
+ });
+ }
+
+ $extra_sql .= ( $extra_sql =~ /WHERE/i ? ' AND ' : ' WHERE ' ). '( '.
+ join(' OR ', map { " taxclassnum = $_ " } @taxclassnum ). ' )'
+ if ( @taxclassnum );
+
+ unless ($params->{'showdisabled'}) {
+ $extra_sql .= ( $extra_sql =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
+ "( disabled = '' OR disabled IS NULL )";
+ }
+
+ $query->{extra_sql} = $extra_sql;
+
+ return ($query, "SELECT COUNT(*) FROM tax_rate $extra_sql");
+}
+
+=item queue_liability_report PARAMS
+
+Launches a tax liability report.
+=cut
+
+sub queue_liability_report {
+ my $cgi = shift;
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+ my $agentnum = $cgi->param('agentnum');
+ $agentnum =~ /^(\d+)$/ ? $agentnum = $1 : $agentnum = '';
+ my $job = new FS::queue { job => 'FS::tax_rate::generate_liability_report' };
+ $job->insert(
+ 'beginning' => $beginning,
+ 'ending' => $ending,
+ 'agentnum' => $agentnum,
+ 'p' => popurl(2),
+ 'rooturl' => rooturl,
+ );
+}
+
+=item generate_liability_report PARAMS
+
+Generates a tax liability report. Provide a hash including desired
+agentnum, beginning, and ending
+
+=cut
+
+sub generate_liability_report {
+ my %args = @_;
+
+ #let us open the temp file early
+ my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc;
+ my $report = new File::Temp( TEMPLATE => 'report.tax.liability.XXXXXXXX',
+ DIR => $dir,
+ UNLINK => 0, # not so temp
+ ) or die "can't open report file: $!\n";
+
+ my $conf = new FS::Conf;
+ my $money_char = $conf->config('money_char') || '$';
+
+ my $join_cust = "
+ JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ ";
+
+ my $join_loc =
+ "LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum )";
+ my $join_tax_loc = "LEFT JOIN tax_rate_location USING ( taxratelocationnum )";
+
+ my $addl_from = " $join_cust $join_loc $join_tax_loc ";
+
+ my $where = "WHERE _date >= $args{beginning} AND _date <= $args{ending} ";
+
+ my $agentname = '';
+ if ( $args{agentnum} =~ /^(\d+)$/ ) {
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "agent not found" unless $agent;
+ $agentname = $agent->agent;
+ $where .= ' AND cust_main.agentnum = '. $agent->agentnum;
+ }
+
+ # my ( $location_sql, @location_param ) = FS::cust_pkg->location_sql;
+ # $where .= " AND $location_sql";
+ #my @taxparam = ( 'itemdesc', @location_param );
+ # now something along the lines of geocode matching ?
+ #$where .= FS::cust_pkg->_location_sql_where('cust_tax_location');;
+ my @taxparam = ( 'itemdesc', 'tax_rate_location.state', 'tax_rate_location.county', 'tax_rate_location.city', 'cust_bill_pkg_tax_rate_location.locationtaxid' );
+
+ my $select = 'DISTINCT itemdesc,locationtaxid,tax_rate_location.state,tax_rate_location.county,tax_rate_location.city';
+
+ #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)
+ my $scalar_sql = sub {
+ my( $r, $param, $sql ) = @_;
+ 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;
+ };
+
+ my $tax = 0;
+ my $credit = 0;
+ my %taxes = ();
+ my %basetaxes = ();
+ foreach my $t (qsearch({ table => 'cust_bill_pkg',
+ select => $select,
+ hashref => { pkgpart => 0 },
+ addl_from => $addl_from,
+ extra_sql => $where,
+ })
+ )
+ {
+ my @params = map { my $f = $_; $f =~ s/.*\.//; $f } @taxparam;
+ my $label = join('~', map { $t->$_ } @params);
+ $label = 'Tax'. $label if $label =~ /^~/;
+ unless ( exists( $taxes{$label} ) ) {
+ my ($baselabel, @trash) = split /~/, $label;
+
+ $taxes{$label}->{'label'} = join(', ', split(/~/, $label) );
+ $taxes{$label}->{'url_param'} =
+ join(';', map { "$_=". uri_escape($t->$_) } @params);
+
+ my $taxwhere = "FROM cust_bill_pkg $addl_from $where AND payby != 'COMP' ".
+ "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+
+ my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
+ " $taxwhere AND cust_bill_pkg.pkgnum = 0";
+
+ my $x = &{$scalar_sql}($t, [ map { $_, $_ } @params ], $sql );
+ $tax += $x;
+ $taxes{$label}->{'tax'} += $x;
+
+ my $creditfrom = " JOIN cust_credit_bill_pkg USING (billpkgnum,billpkgtaxratelocationnum) ";
+ my $creditwhere = "FROM cust_bill_pkg $addl_from $creditfrom $where ".
+ "AND payby != 'COMP' ".
+ "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+
+ $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
+ " $creditwhere AND cust_bill_pkg.pkgnum = 0";
+
+ my $y = &{$scalar_sql}($t, [ map { $_, $_ } @params ], $sql );
+ $credit += $y;
+ $taxes{$label}->{'credit'} += $y;
+
+ unless ( exists( $taxes{$baselabel} ) ) {
+
+ $basetaxes{$baselabel}->{'label'} = $baselabel;
+ $basetaxes{$baselabel}->{'url_param'} = "itemdesc=$baselabel";
+ $basetaxes{$baselabel}->{'base'} = 1;
+
+ }
+
+ $basetaxes{$baselabel}->{'tax'} += $x;
+ $basetaxes{$baselabel}->{'credit'} += $y;
+
+ }
+
+ # calculate customer-exemption for this tax
+ # calculate package-exemption for this tax
+ # calculate monthly exemption (texas tax) for this tax
+ # count up all the cust_tax_exempt_pkg records associated with
+ # the actual line items.
+ }
+
+
+ #ordering
+ my @taxes = ();
+
+ foreach my $tax ( sort { $a cmp $b } keys %taxes ) {
+ my ($base, @trash) = split '~', $tax;
+ my $basetax = delete( $basetaxes{$base} );
+ if ($basetax) {
+ if ( $basetax->{tax} == $taxes{$tax}->{tax} ) {
+ $taxes{$tax}->{base} = 1;
+ } else {
+ push @taxes, $basetax;
+ }
+ }
+ push @taxes, $taxes{$tax};
+ }
+
+ push @taxes, {
+ 'label' => 'Total',
+ 'url_param' => '',
+ 'tax' => $tax,
+ 'credit' => $credit,
+ 'base' => 1,
+ };
+
+
+ my $dateagentlink = "begin=$args{beginning};end=$args{ending}";
+ $dateagentlink .= ';agentnum='. $args{agentnum}
+ if length($agentname);
+ my $baselink = $args{p}. "search/cust_bill_pkg.cgi?$dateagentlink";
+
+
+ print $report <<EOF;
+
+ <% include("/elements/header.html", "$agentname Tax Report - ".
+ ( $args{beginning}
+ ? time2str('%h %o %Y ', $args{beginning} )
+ : ''
+ ).
+ 'through '.
+ ( $args{ending} == 4294967295
+ ? 'now'
+ : time2str('%h %o %Y', $args{ending} )
+ )
+ )
+ %>
+
+ <% include('/elements/table-grid.html') %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">&nbsp;&nbsp;&nbsp;&nbsp;</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
+ </TR>
+EOF
+
+ my $bgcolor1 = '#eeeeee';
+ my $bgcolor2 = '#ffffff';
+ my $bgcolor = '';
+
+ foreach my $tax ( @taxes ) {
+
+ if ( $bgcolor eq $bgcolor1 ) {
+ $bgcolor = $bgcolor2;
+ } else {
+ $bgcolor = $bgcolor1;
+ }
+
+ my $link = '';
+ if ( $tax->{'label'} ne 'Total' ) {
+ $link = ';'. $tax->{'url_param'};
+ }
+
+ print $report <<EOF;
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% '$bgcolor' %>"><% '$tax->{label}' %></TD>
+ <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% '$bgcolor' %>" ALIGN="right">
+ <A HREF="<% '$baselink$link' %>;istax=1"><% '$money_char' %><% sprintf('%.2f', $tax->{'tax'} ) %></A>
+ </TD>
+ <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% '$bgcolor' %>"></TD>
+ <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% '$bgcolor' %>" ALIGN="right">
+ <A HREF="<% '$baselink$link' %>;istax=1;iscredit=rate"><% '$money_char' %><% sprintf('%.2f', $tax->{'credit'} ) %></A>
+ </TD>
+ <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ </TR>
+EOF
+ }
+
+ print $report <<EOF;
+ </TABLE>
+
+ </BODY>
+ </HTML>
+EOF
+
+ my $reportname = $report->filename;
+ close $report;
+
+ my $dropstring = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/report.';
+ $reportname =~ s/^$dropstring//;
+
+ my $reporturl = $args{rooturl}. "/misc/queued_report?report=$reportname";
+ die "<a href=$reporturl>view</a>\n";
+
+}
+
+
+
+=back
+
+=head1 BUGS
+
+ Mixing automatic and manual editing works poorly at present.
+
+ Tax liability calculations take too long and arguably don't belong here.
+ Tax liability report generation not entirely safe (escaped).
+
+=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/tax_rate_location.pm b/FS/FS/tax_rate_location.pm
new file mode 100644
index 0000000..218ed97
--- /dev/null
+++ b/FS/FS/tax_rate_location.pm
@@ -0,0 +1,317 @@
+package FS::tax_rate_location;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::Misc qw( csv_from_fixed );
+
+=head1 NAME
+
+FS::tax_rate_location - Object methods for tax_rate_location records
+
+=head1 SYNOPSIS
+
+ use FS::tax_rate_location;
+
+ $record = new FS::tax_rate_location \%hash;
+ $record = new FS::tax_rate_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::tax_rate_location object represents an example. FS::tax_rate_location inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item taxratelocationnum
+
+Primary key (assigned automatically for new tax_rate_locations)
+
+=item data_vendor
+
+The tax data vendor
+
+=item geocode
+
+A unique geographic location code provided by the data vendor
+
+=item city
+
+City
+
+=item county
+
+County
+
+=item state
+
+State
+
+=item disabled
+
+If 'Y' this record is no longer active.
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax rate location. 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 { 'tax_rate_location'; }
+
+=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
+
+sub delete {
+ return "Can't delete tax rate locations. Set disable to 'Y' instead.";
+ # check that it is unused in any cust_bill_pkg_tax_location records instead?
+}
+
+=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 tax rate location. 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('taxratelocationnum')
+ || $self->ut_textn('data_vendor')
+ || $self->ut_alpha('geocode')
+ || $self->ut_textn('city')
+ || $self->ut_textn('county')
+ || $self->ut_textn('state')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ my $t;
+ $t = qsearchs( 'tax_rate_location',
+ { disabled => '',
+ ( map { $_ => $self->$_ } qw( data_vendor geocode ) ),
+ },
+ )
+ unless $self->disabled;
+
+ $t = $self->by_key( $self->taxratelocationnum )
+ if ( !$t && $self->taxratelocationnum );
+
+ return "geocode ". $self->geocode. " already in use for this vendor"
+ if ( $t && $t->taxratelocationnum != $self->taxratelocationnum );
+
+ return "may only be disabled"
+ if ( $t && scalar( grep { $t->$_ ne $self->$_ }
+ grep { $_ ne 'disabled' }
+ $self->fields
+ )
+ );
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item batch_import
+
+=cut
+
+sub batch_import {
+ my ($param, $job) = @_;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my %insert = ();
+ my %delete = ();
+
+ my @fields;
+ my $hook;
+
+ my @column_lengths = ();
+ my @column_callbacks = ();
+ if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
+ $format =~ s/-fixed//;
+ my $trim = sub { my $r = shift; $r =~ s/^\s*//; $r =~ s/\s*$//; $r };
+ push @column_lengths, qw( 28 25 2 10 );
+ push @column_lengths, 1 if $format eq 'cch-update';
+ push @column_callbacks, $trim foreach (@column_lengths);
+ }
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job || scalar(@column_callbacks) ) {
+ my $error =
+ csv_from_fixed(\$fh, \$count, \@column_lengths, \@column_callbacks);
+ return $error if $error;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
+ @fields = qw( city county state geocode );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $hook = sub {
+ my $hash = shift;
+
+ $hash->{'data_vendor'} ='cch';
+
+ if (exists($hash->{'actionflag'}) && $hash->{'actionflag'} eq 'D') {
+ delete($hash->{actionflag});
+
+ $hash->{disabled} = '';
+ my $tax_rate_location = qsearchs('tax_rate_location', $hash);
+ return "Can't find tax_rate_location to delete: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ unless $tax_rate_location;
+
+ $tax_rate_location->disabled('Y');
+ my $error = $tax_rate_location->replace;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{'actionflag'});
+
+ '';
+
+ };
+
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $hook = sub {};
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+
+ my $imported = 0;
+
+ 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 ( defined($line=<$fh>) ) {
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my @columns = $csv->fields();
+
+ my %tax_rate_location = ();
+ foreach my $field ( @fields ) {
+ $tax_rate_location{$field} = shift @columns;
+ }
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%tax_rate_location);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if (scalar(keys %tax_rate_location)) { #inserts only
+
+ my $tax_rate_location = new FS::tax_rate_location( \%tax_rate_location );
+ $error = $tax_rate_location->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_rate_location for $line: $error";
+ }
+
+ }
+
+ $imported++;
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless ($imported || $format eq 'cch-update');
+
+ ''; #no error
+
+}
+
+=head1 BUGS
+
+Currently somewhat specific to CCH supplied data.
+
+=head1 SEE ALSO
+
+L<FS::Record>, 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..6503755
--- /dev/null
+++ b/FS/FS/type_pkgs.pm
@@ -0,0 +1,130 @@
+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 typepkgnum - primary key
+
+=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_numbern('typepkgnum')
+ || $self->ut_foreign_key('typenum', 'agent_type', 'typenum' )
+ || $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart' )
+ ;
+ return $error if $error;
+
+ $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 } );
+}
+
+=item agent_type
+
+Returns the FS::agent_type object associated with this record.
+
+=cut
+
+sub agent_type {
+ my $self = shift;
+ qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+}
+
+=cut
+
+=back
+
+=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/FS/usage_class.pm b/FS/FS/usage_class.pm
new file mode 100644
index 0000000..b64d26a
--- /dev/null
+++ b/FS/FS/usage_class.pm
@@ -0,0 +1,413 @@
+package FS::usage_class;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::usage_class - Object methods for usage_class records
+
+=head1 SYNOPSIS
+
+ use FS::usage_class;
+
+ $record = new FS::usage_class \%hash;
+ $record = new FS::usage_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::usage_class object represents a usage class. Every rate detail
+(see L<FS::rate_detail>) has, optionally, a usage class. FS::usage_class
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+Primary key (assigned automatically for new usage classes)
+
+=item classname
+
+Text name of this usage class
+
+=item disabled
+
+Disabled flag, empty or 'Y'
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new usage class. To add the usage class 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 { 'usage_class'; }
+
+=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 usage class. 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('classnum')
+ || $self->ut_numbern('weight')
+ || $self->ut_text('classname')
+ || $self->ut_textn('format')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item summary_formats_labelhash
+
+Returns a list of line item format descriptions suitable for assigning to
+a hash.
+
+=cut
+
+# transform hashes of arrays to arrays of hashes for false laziness removal?
+my %summary_formats = (
+ 'simple' => {
+ 'label' => [ qw( Description Calls Minutes Amount ) ],
+ 'fields' => [
+ sub { shift->{description} },
+ sub { shift->{calls} },
+ sub { sprintf( '%.1f', shift->{duration}/60 ) },
+ sub { shift->{amount} },
+ ],
+ 'align' => [ qw( l r r r ) ],
+ 'span' => [ qw( 4 1 1 1 ) ], # unitprices?
+ 'width' => [ qw( 8.2cm 2.5cm 1.4cm 1.6cm ) ], # don't like this
+ },
+ 'simpler' => {
+ 'label' => [ qw( Description Calls Amount ) ],
+ 'fields' => [
+ sub { shift->{description} },
+ sub { shift->{calls} },
+ sub { shift->{amount} },
+ ],
+ 'align' => [ qw( l r r ) ],
+ 'span' => [ qw( 5 1 1 ) ],
+ 'width' => [ qw( 10.7cm 1.4cm 1.6cm ) ], # don't like this
+ },
+ 'usage_simple' => {
+ 'label' => [ qw( Date Time Number Destination Duration Amount ) ],
+ 'fields' => [
+ sub { ' ' },
+ sub { ' ' },
+ sub { ' ' },
+ sub { ' ' },
+ sub { ' ' },
+ sub { ' ' },
+ ],
+ 'align' => [ qw( l l l l r r ) ],
+ 'span' => [ qw( 2 1 1 1 1 1 ) ], # unitprices?
+ 'width' => [ qw( 4.3cm 1.4cm 2.5cm 2.5cm 1.4cm 1.6cm ) ],# don't like this
+ },
+);
+
+sub summary_formats_labelhash {
+ map { $_ => join(',', @{$summary_formats{$_}{label}}) } keys %summary_formats;
+}
+
+=item header_generator FORMAT
+
+Returns a coderef used for generation of an invoice line item header for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+my %html_align = (
+ 'c' => 'center',
+ 'l' => 'left',
+ 'r' => 'right',
+);
+
+sub _generator_defaults {
+ my ( $self, $format ) = ( shift, shift );
+ return ( $summary_formats{$self->format}, ' ', ' ', ' ', sub { shift } );
+}
+
+sub header_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ $self->_generator_defaults($format);
+
+ if ($format eq 'latex') {
+ $prefix = "\\hline\n\\rule{0pt}{2.5ex}\n\\makebox[1.4cm]{}&\n";
+ $suffix = "\\\\\n\\hline";
+ $separator = "&\n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
+ };
+ } elsif ( $format eq 'html' ) {
+ $prefix = '<th></th>';
+ $suffix = '';
+ $separator = '';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<th align="$html_align{$a}">$d</th>!;
+ };
+ }
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( map { $f->{$_}->[$i] } qw(label align span width) );
+ }
+
+ $prefix. join($separator, @result). $suffix;
+ };
+
+}
+
+=item description_generator FORMAT
+
+Returns a coderef used for generation of invoice line items for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+sub description_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ $self->_generator_defaults($format);
+
+ if ($format eq 'latex') {
+ $prefix = "\\hline\n\\multicolumn{1}{c}{\\rule{0pt}{2.5ex}~} &\n";
+ $suffix = '\\\\';
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '"><td align="center"></td>';
+ $suffix = '';
+ $separator = '';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}">$d</td>!;
+ };
+ }
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result, &{$column}( &{$f->{fields}->[$i]}(@args),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+=item total_generator FORMAT
+
+Returns a coderef used for generation of invoice total lines for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+sub total_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+# $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .
+# '{' . $section->{'subtotal'} . '}' . "\n";
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ $self->_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ # my $r = &{$f->{fields}->[$i]}(@args);
+ # $r .= ' Total' unless $i;
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args). ($i ? '' : ' Total'),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+=item total_line_generator FORMAT
+
+Returns a coderef used for generation of invoice total line items for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+# not used: will have issues with hash element names (description vs
+# total_item and amount vs total_amount -- another array of functions?
+
+sub total_line_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+# $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
+# '{' . $line->{'total_amount'} . '}' . "\n";
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ $self->_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+
+
+sub _populate_initial_data {
+ my ($class, %opts) = @_;
+
+ foreach ("Intrastate", "Interstate", "International") {
+ my $object = $class->new( { 'classname' => $_ } );
+ my $error = $object->insert;
+ die "error inserting $class into database: $error\n"
+ if $error;
+ }
+
+ '';
+
+}
+
+sub _upgrade_data {
+ my $class = shift;
+
+ return $class->_populate_initial_data(@_)
+ unless scalar( qsearch( 'usage_class', {} ) );
+
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
new file mode 100644
index 0000000..5537c2f
--- /dev/null
+++ b/FS/MANIFEST
@@ -0,0 +1,513 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+bin/freeside-addoutsource
+bin/freeside-addoutsourceuser
+bin/freeside-addgroup
+bin/freeside-adduser
+bin/freeside-apply-credits
+bin/freeside-count-active-customers
+bin/freeside-daily
+bin/freeside-deloutsource
+bin/freeside-deloutsourceuser
+bin/freeside-deluser
+bin/freeside-email
+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/AccessRight.pm
+FS/CGI.pm
+FS/InitHandler.pm
+FS/ClientAPI.pm
+FS/ClientAPI_SessionCache.pm
+FS/ClientAPI/passwd.pm
+FS/ClientAPI/MyAccount.pm
+FS/Conf.pm
+FS/ConfItem.pm
+FS/Cron/backup.pm
+FS/Cron/bill.pm
+FS/Cron/vacuum.pm
+FS/Daemon.pm
+FS/Misc.pm
+FS/Record.pm
+FS/Report.pm
+FS/Report/FCC_477.pm
+FS/Report/Table.pm
+FS/Report/Table/Monthly.pm
+FS/SearchCache.pm
+FS/UI/Web.pm
+FS/UID.pm
+FS/Mason.pm
+FS/Mason/Request.pm
+FS/Msgcat.pm
+FS/Pony.pm
+FS/acct_snarf.pm
+FS/addr_block.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/Import.pm
+FS/cust_main_Mixin.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/h_Common.pm
+FS/h_cust_bill.pm
+FS/h_cust_pkg.pm
+FS/h_cust_pkg_reason.pm
+FS/h_cust_svc.pm
+FS/h_cust_tax_exempt.pm
+FS/h_domain_record.pm
+FS/h_svc_acct.pm
+FS/h_svc_broadband.pm
+FS/h_svc_domain.pm
+FS/h_svc_external.pm
+FS/h_svc_forward.pm
+FS/h_svc_www.pm
+FS/part_bill_event.pm
+FS/payinfo_Mixin.pm
+FS/export_svc.pm
+FS/export_device.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/dashcs_e911.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/radiator.pm
+FS/part_export/router.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_pkg_option.pm
+FS/part_pkg/flat.pm
+FS/part_pkg/flat_comission.pm
+FS/part_pkg/flat_comission_cust.pm
+FS/part_pkg/flat_comission_pkg.pm
+FS/part_pkg/flat_delayed.pm
+FS/part_pkg/prorate.pm
+FS/part_pkg/sesmon_hour.pm
+FS/part_pkg/sesmon_minute.pm
+FS/part_pkg/sql_external.pm
+FS/part_pkg/sql_generic.pm
+FS/part_pkg/sqlradacct_hour.pm
+FS/part_pkg/subscription.pm
+FS/part_pkg/voip_sqlradacct.pm
+FS/part_pkg/voip_cdr.pm
+FS/part_pkg/base_rate.pm
+FS/part_pkg/base_delayed.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/payby.pm
+FS/pkg_class.pm
+FS/pkg_svc.pm
+FS/rate.pm
+FS/rate_detail.pm
+FS/rate_region.pm
+FS/rate_prefix.pm
+FS/reg_code.pm
+FS/reg_code_pkg.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
+FS/cust_tax_exempt_pkg.pm
+FS/clientapi_session.pm
+FS/clientapi_session_field.pm
+t/addr_block.t
+t/agent.t
+t/agent_type.t
+t/AccessRight.t
+t/CGI.t
+t/InitHandler.t
+t/ClientAPI.t
+t/ClientAPI_SessionCache.t
+t/Conf.t
+t/ConfItem.t
+t/Cron-backup.t
+t/Cron-bill.t
+t/Cron-vacuum.t
+t/Daemon.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_Mixin.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/h_cust_bill.t
+t/h_cust_pkg.t
+t/h_cust_pkg_reason.t
+t/h_cust_svc.t
+t/h_cust_tax_exempt.t
+t/h_Common.t
+t/h_domain_record.t
+t/h_svc_acct.t
+t/h_svc_broadband.t
+t/h_svc_domain.t
+t/h_svc_external.t
+t/h_svc_forward.t
+t/h_svc_www.t
+t/cust_tax_exempt.t
+t/cust_tax_exempt_pkg.t
+t/domain_record.t
+t/nas.t
+t/part_bill_event.t
+t/export_svc.t
+t/export_device.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-radiator.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_pkg_option.t
+t/part_pkg-flat.t
+t/part_pkg-flat_comission.t
+t/part_pkg-flat_comission_cust.t
+t/part_pkg-flat_comission_pkg.t
+t/part_pkg-flat_delayed.t
+t/part_pkg-prorate.t
+t/part_pkg-sesmon_hour.t
+t/part_pkg-sesmon_minute.t
+t/part_pkg-sql_external.t
+t/part_pkg-sql_generic.t
+t/part_pkg-sqlradacct_hour.t
+t/part_pkg-subscription.t
+t/part_pkg-voip_sqlradacct.t
+t/part_pkg-voip_cdr.t
+t/part_pop_local.t
+t/part_referral.t
+t/part_svc.t
+t/part_svc_column.t
+t/payby.t
+t/payinfo_Mixin.t
+t/pkg_class.t
+t/pkg_svc.t
+t/port.t
+t/prepay_credit.t
+t/rate.t
+t/rate_detail.t
+t/rate_region.t
+t/rate_prefix.t
+t/radius_usergroup.t
+t/reg_code.t
+t/reg_code_pkg.t
+t/router.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
+t/clientapi_session.t
+t/clientapi_session_field.t
+FS/payment_gateway.pm
+t/payment_gateway.t
+FS/payment_gateway_option.pm
+t/payment_gateway_option.t
+FS/option_Common.pm
+t/option_Common.t
+FS/agent_payment_gateway.pm
+t/agent_payment_gateway.t
+FS/banned_pay.pm
+t/banned_pay.t
+bin/freeside-prepaidd
+FS/cdr.pm
+t/cdr.t
+FS/cdr_calltype.pm
+t/cdr_calltype.t
+FS/cdr_type.pm
+t/cdr_type.t
+FS/cdr_carrier.pm
+t/cdr_carrier.t
+FS/inventory_class.pm
+t/inventory_class.t
+FS/inventory_item.pm
+t/inventory_item.t
+FS/access_user.pm
+t/access_user.t
+FS/access_user_pref.pm
+t/access_user_pref.t
+FS/access_group.pm
+t/access_group.t
+FS/access_usergroup.pm
+t/access_usergroup.t
+FS/access_groupagent.pm
+t/access_groupagent.t
+FS/access_right.pm
+t/access_right.t
+FS/m2m_Common.pm
+FS/pay_batch.pm
+t/pay_batch.t
+FS/ConfDefaults.pm
+t/ConfDefaults.t
+FS/m2name_Common.pm
+FS/CurrentUser.pm
+FS/svc_phone.pm
+t/svc_phone.t
+FS/h_svc_phone.pm
+FS/cust_bill_pay_batch.pm
+t/cust_bill_pay_batch.t
+FS/cust_bill_pay_pkg.pm
+t/cust_bill_pay_pkg.t
+FS/cust_credit_bill_pkg.pm
+t/cust_credit_bill_pkg.t
+FS/registrar.pm
+t/registrar.t
+FS/svc_Domain_Mixin.pm
+t/svc_Domain_Mixin.t
+FS/svc_External_Common.pm
+t/svc_External_Common.t
+FS/svc_Parent_Mixin.pm
+t/svc_Parent_Mixin.t
+FS/cust_main_note.pm
+t/cust_main_note.t
+FS/cust_pkg_reason.pm
+t/cust_pkg_reason.t
+FS/reason.pm
+t/reason.t
+FS/reason_type.pm
+t/reason_type.t
+FS/pkg_referral.pm
+t/pkg_referral.t
+FS/part_event_option.pm
+t/part_event_option.t
+FS/part_event_condition.pm
+t/part_event_condition.t
+FS/part_event_condition_option.pm
+t/part_event_condition_option.t
+FS/part_event.pm
+t/part_event.t
+FS/cust_event.pm
+t/cust_event.t
+FS/part_event_condition_option_option.pm
+t/part_event_condition_option_option.t
+FS/cust_pkg_option.pm
+t/cust_pkg_option.t
+FS/conf.pm
+t/conf.t
+FS/acct_rt_transaction.pm
+t/acct_rt_transaction.t
+FS/cust_pay_pending.pm
+t/cust_pay_pending.t
+FS/part_pkg_taxclass.pm
+t/part_pkg_taxclass.t
+FS/tax_rate.pm
+t/tax_rate.t
+FS/tax_class.pm
+t/tax_class.t
+FS/cust_tax_location.pm
+t/cust_tax_location.t
+FS/part_pkg_taxproduct.pm
+t/part_pkg_taxproduct.t
+FS/part_pkg_taxoverride.pm
+t/part_pkg_taxoverride.t
+FS/part_pkg_taxrate.pm
+t/part_pkg_taxrate.t
+FS/part_pkg_link.pm
+t/part_pkg_link.t
+FS/pkg_category.pm
+t/pkg_category.t
+FS/phone_avail.pm
+t/phone_avail.t
+FS/Yori.pm
+FS/cust_svc_option.pm
+t/cust_svc_option.t
+FS/usage_class.pm
+t/usage_class.t
+FS/cust_bill_pkg_display.pm
+t/cust_bill_pkg_display.t
+FS/cust_pkg_detail.pm
+t/cust_pkg_detail.t
+FS/cust_location.pm
+t/cust_location.t
+FS/cust_bill_pkg_tax_location.pm
+t/cust_bill_pkg_tax_location.t
+FS/tax_rate_location.pm
+t/tax_rate_location.t
+FS/cust_bill_pkg_tax_rate_location.pm
+t/cust_bill_pkg_tax_rate_location.t
+FS/cust_recon.pm
+t/cust_recon.t
+FS/part_pkg_report_option.pm
+t/part_pkg_report_option.t
+FS/cust_main_exemption.pm
+t/cust_main_exemption.t
+FS/cust_tax_adjustment.pm
+t/cust_tax_adjustment.t
+FS/phone_device.pm
+t/phone_device.t
+FS/part_device.pm
+t/part_device.t
+FS/cdr_termination.pm
+t/cdr_termination.t
+FS/cust_attachment.pm
+t/cust_attachment.t
+FS/cust_statement.pm
+t/cust_statement.t
+FS/cdr_batch.pm
+t/cdr_batch.t
+FS/cust_class.pm
+t/cust_class.t
+FS/cust_category.pm
+t/cust_category.t
+FS/class_Common.pm
+t/class_Common.t
+FS/category_Common.pm
+t/category_Common.t
+FS/contact.pm
+t/contact.t
+FS/contact_phone.pm
+t/contact_phone.t
+FS/phone_type.pm
+t/phone_type.t
+FS/contact_email.pm
+t/contact_email.t
+FS/prospect_main.pm
+t/prospect_main.t
+FS/o2m_Common.pm
+FS/svc_pbx.pm
+t/svc_pbx.t
+FS/h_svc_www.pm
+t/h_svc_www.t
+FS/discount.pm
+t/discount.t
+FS/cust_pkg_discount.pm
+t/cust_pkg_discount.t
+FS/cust_bill_pkg_discount.pm
+t/cust_bill_pkg_discount.t
+FS/location_Mixin.pm
+t/location_Mixin.t
+FS/svc_mailinglist.pm
+t/svc_mailinglist.t
+FS/mailinglist.pm
+t/mailinglist.t
+FS/mailinglistmember.pm
+t/mailinglistmember.t
+FS/otaker_Mixin.pm
+FS/part_event/Action/Mixin/credit_pkg.pm
+FS/part_event/Action/pkg_agent_credit.pm
+FS/part_event/Action/pkg_agent_credit_pkg.pm
+FS/part_event/Action/pkg_employee_credit.pm
+FS/part_event/Action/pkg_employee_credit_pkg.pm
+FS/Misc/DateTime.pm
+FS/cgp_rule.pm
+t/cgp_rule.t
+FS/cgp_rule_condition.pm
+t/cgp_rule_condition.t
+FS/cgp_rule_action.pm
+t/cgp_rule_action.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/bin/freeside-addgroup b/FS/bin/freeside-addgroup
new file mode 100755
index 0000000..25c2345
--- /dev/null
+++ b/FS/bin/freeside-addgroup
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+use strict;
+use vars qw($opt_s);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::CurrentUser;
+use FS::AccessRight;
+use FS::access_group;
+use FS::access_right;
+use FS::access_groupagent;
+
+getopts("s");
+my $user = shift or die &usage; #just for adminsuidsetup
+my $group = shift or die &usage;
+
+$FS::CurrentUser::upgrade_hack = 1;
+#adminsuidsetup $rootuser;
+adminsuidsetup $user;
+
+my $access_group = new FS::access_group { 'groupname' => $group };
+my $error = $access_group->insert;
+die $error if $error;
+
+if ( $opt_s ) {
+ foreach my $rightname ( FS::AccessRight->default_superuser_rights ) {
+ my $access_right = new FS::access_right {
+ 'righttype' => 'FS::access_group',
+ 'rightobjnum' => $access_group->groupnum,
+ 'rightname' => $rightname,
+ };
+ my $ar_error = $access_right->insert;
+ die $ar_error if $ar_error;
+ }
+
+ foreach my $agent ( qsearch('agent', {} ) ) {
+ my $access_groupagent = new FS::access_groupagent {
+ 'groupnum' => $access_group->groupnum,
+ 'agentnum' => $agent->agentnum,
+ };
+ my $aga_error = $access_groupagent->insert;
+ die $aga_error if $aga_error;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-addgroup [ -s ] username groupname"
+}
+
diff --git a/FS/bin/freeside-addoutsource b/FS/bin/freeside-addoutsource
new file mode 100644
index 0000000..9cb1219
--- /dev/null
+++ b/FS/bin/freeside-addoutsource
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+domain=$1
+
+FREESIDE_CONF=%%%FREESIDE_CONF%%%
+FREESIDE_CACHE=%%%FREESIDE_CACHE%%%
+FREESIDE_EXPORT=%%%FREESIDE_EXPORT%%%
+
+#without this, [a-z]* matches CVS/, the copy doesn't return a sucessful error
+# status, and the rest of the commands aren't run
+export LANG=C
+
+createdb $domain && \
+\
+mkdir $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \
+\
+chown freeside $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \
+\
+cp /home/ivan/freeside/conf/[a-z]* $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain && \
+\
+touch $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \
+\
+chown freeside $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \
+\
+chmod 600 $FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \
+\
+echo -e "DBI:Pg:dbname=$domain\nfreeside\n" >$FREESIDE_CONF/conf.DBI:Pg:dbname=$domain/secrets && \
+\
+mkdir $FREESIDE_CACHE/counters.DBI:Pg:dbname=$domain && \
+mkdir $FREESIDE_CACHE/cache.DBI:Pg:dbname=$domain && \
+mkdir $FREESIDE_EXPORT/export.DBI:Pg:dbname=$domain
+
diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser
new file mode 100644
index 0000000..cbe792a
--- /dev/null
+++ b/FS/bin/freeside-addoutsourceuser
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+username=$1
+domain=$2
+password=$3
+realdomain=$4
+FREESIDE_CONF=%%%FREESIDE_CONF%%%
+
+freeside-adduser -s conf.DBI:Pg:dbname=$domain/secrets \
+ -n \
+ $username #2>/dev/null
+
+[ -e $FREESIDE_CONF/dbdef.DBI:Pg:dbname=$domain ] \
+ || ( freeside-setup -d $realdomain -u $username )
+
+freeside-adduser -g 1 $username
+
+htpasswd -b $FREESIDE_CONF/htpasswd $username $password
diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser
new file mode 100644
index 0000000..5304813
--- /dev/null
+++ b/FS/bin/freeside-adduser
@@ -0,0 +1,119 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_s $opt_g $opt_n);
+use Fcntl qw(:flock);
+use Getopt::Std;
+
+my $FREESIDE_CONF = "%%%FREESIDE_CONF%%%";
+
+getopts("s:g:n");
+my $user = shift or die &usage;
+
+if ( $opt_s ) {
+
+ #if ( -e "$FREESIDE_CONF/mapsecrets" ) {
+ # open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets")
+ # or die "can't open $FREESIDE_CONF/mapsecrets: $!";
+ # while (<MAPSECRETS>) {
+ # /^(\S+) / or die "unparsable line in mapsecrets: $_";
+ # die "user $user already exists\n" if $user eq $1;
+ # }
+ # close MAPSECRETS;
+ #}
+
+ #insert new entry before a wildcard...
+ open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets")
+ and flock(MAPSECRETS,LOCK_EX)
+ or die "can't open $FREESIDE_CONF/mapsecrets: $!";
+ open(NEW,">$FREESIDE_CONF/mapsecrets.new")
+ or die "can't open $FREESIDE_CONF/mapsecrets.new: $!";
+ while(<MAPSECRETS>) {
+ if ( /^\*\s/ ) {
+ print NEW "$user $opt_s\n";
+ }
+ print NEW $_;
+ }
+ close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!";
+ close NEW or die "can't close $FREESIDE_CONF/mapsecrets.new: $!";
+ rename("$FREESIDE_CONF/mapsecrets.new", "$FREESIDE_CONF/mapsecrets")
+ or die "can't move mapsecrets.new into place: $!";
+
+}
+
+###
+
+exit if $opt_n;
+
+###
+
+use FS::UID qw(adminsuidsetup);
+use FS::CurrentUser;
+use FS::access_user;
+use FS::access_usergroup;
+
+$FS::CurrentUser::upgrade_hack = 1;
+#adminsuidsetup $rootuser;
+adminsuidsetup $user;
+
+my $access_user = new FS::access_user {
+ 'username' => $user,
+ '_password' => 'notyet',
+ 'first' => 'Firstname', # $opt_f ||
+ 'last' => 'Lastname', # $opt_l ||
+};
+my $au_error = $access_user->insert;
+die $au_error if $au_error;
+
+if ( $opt_g ) {
+
+ my $access_usergroup = new FS::access_usergroup {
+ 'usernum' => $access_user->usernum,
+ 'groupnum' => $opt_g,
+ };
+ my $aug_error = $access_usergroup->insert;
+ die $aug_error if $aug_error;
+
+}
+
+###
+
+sub usage {
+ die "Usage:\n\n freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ]"
+}
+
+=head1 NAME
+
+freeside-adduser - Command line interface to add (freeside) users.
+
+=head1 SYNOPSIS
+
+ freeside-adduser [ -n ] [ -s ] [ -g groupnum ] username [ password ]
+
+=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.
+
+This functionality is now available in the web interface as well, under
+B<Configuration | Employees | View/Edit employees>.
+
+ -g: initial groupnum
+
+ Development/multi-DB options:
+
+ -s: alternate secrets file
+
+ -n: no ACL added, for bootstrapping
+
+=head1 NOTE
+
+No explicit htpasswd options are available in 1.7 - passwords are now
+maintained automatically.
+
+=head1 SEE ALSO
+
+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-apply_payments_and_credits b/FS/bin/freeside-apply_payments_and_credits
new file mode 100755
index 0000000..d789c6c
--- /dev/null
+++ b/FS/bin/freeside-apply_payments_and_credits
@@ -0,0 +1,79 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $DEBUG );
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_main;
+use DBI;
+
+$DEBUG = 1;
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my $unapplied_payments_sql = <<EOF;
+SELECT custnum FROM cust_pay WHERE paid >
+ ( ( SELECT coalesce(sum(amount),0) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ + ( SELECT coalesce(sum(amount),0) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum)
+ )
+EOF
+
+my $unapplied_credits_sql = <<EOF;
+SELECT custnum FROM cust_credit WHERE cust_credit.amount >
+ ( ( SELECT coalesce(sum(cust_credit_bill.amount),0) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ + ( SELECT coalesce(sum(cust_Credit_refund.amount),0) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum)
+ )
+EOF
+
+my %custnum = ();
+
+my $sth = $dbh->prepare($unapplied_payments_sql) or die $dbh->errstr;
+$sth->execute or die "unapplied payment search failed: ". $sth->errstr;
+
+map { $custnum{$_->[0]} = 1 } @{ $sth->fetchall_arrayref };
+
+$sth = $dbh->prepare($unapplied_credits_sql) or die $dbh->errstr;
+$sth->execute or die "unapplied credit search failed: ". $sth->errstr;
+
+map { $custnum{$_->[0]} = 1 } @{ $sth->fetchall_arrayref };
+
+foreach my $custnum ( keys %custnum ) {
+
+ warn "processing customer $custnum\n" if $DEBUG;
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "customer $custnum no longer exists!\n";
+
+ my $error = $cust_main->apply_payments_and_credits;
+ die $error if $error;
+
+}
+
+sub usage {
+ die "Usage:\n\n freeside-apply_payments_and_credits user\n";
+}
+
+=head1 NAME
+
+freeside-apply_payments_and_credits - Command line interface to apply payments and credits to invoice
+
+=head1 SYNOPSIS
+
+ freeside-apply_payments_and_credits username
+
+=head1 DESCRIPTION
+
+Finds unapplied payment and credit amounts and applies them to any outstanding
+uncovered invoice amounts.
+
+B<username> is a username added by freeside-adduser.
+
+=cut
+
+
+
diff --git a/FS/bin/freeside-cdr-sftp_and_import b/FS/bin/freeside-cdr-sftp_and_import
new file mode 100755
index 0000000..d90695f
--- /dev/null
+++ b/FS/bin/freeside-cdr-sftp_and_import
@@ -0,0 +1,187 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Net::SFTP::Foreign::Compat;
+use Net::FTP;
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::cdr;
+
+###
+# parse command line
+###
+
+use vars qw( $opt_m $opt_p $opt_r $opt_e $opt_d $opt_v );
+getopts('m:p:r:e:d:v');
+
+$opt_e ||= 'csv';
+#$opt_e = ".$opt_e" unless $opt_e =~ /^\./;
+$opt_e =~ s/^\.//;
+
+$opt_p ||= '';
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# %%%FREESIDE_CACHE%%%
+my $cachedir = '/usr/local/etc/freeside/cache.'. datasrc. '/cdrs';
+mkdir $cachedir unless -d $cachedir;
+
+my $format = shift or die &usage;
+
+use vars qw( $servername );
+$servername = shift or die &usage;
+
+###
+# get the file list
+###
+
+warn "Retrieving directory listing\n" if $opt_v;
+
+$opt_m = 'sftp' if !defined($opt_m);
+$opt_m = lc($opt_m);
+
+my $ls;
+
+if($opt_m eq 'ftp') {
+ my $ls_ftp = ftp();
+
+ $ls = [ grep { /^$opt_p.*\.$opt_e$/i } $ls_ftp->ls ];
+}
+elsif($opt_m eq 'sftp') {
+ my $ls_sftp = sftp();
+
+ $ls_sftp->setcwd($opt_r) or die "can't chdir to $opt_r\n"
+ if $opt_r;
+
+ $ls = $ls_sftp->ls('.', wanted => qr/^$opt_p.*\.$opt_e$/i,
+ names_only => 1 );
+}
+else {
+ die "Method '$opt_m' not supported; must be ftp or sftp\n";
+}
+
+###
+# import each file
+###
+
+foreach my $filename ( @$ls ) {
+
+ warn "Downloading $filename\n" if $opt_v;
+
+ #get the file
+ if($opt_m eq 'ftp') {
+ my $ftp = ftp();
+ $ftp->get($filename, "$cachedir/$filename")
+ or die "Can't get $filename: ". $ftp->message . "\n";
+ }
+ else {
+ my $sftp = sftp();
+ $sftp->get($filename, "$cachedir/$filename")
+ or die "Can't get $filename: ". $sftp->error . "\n";
+ }
+
+ warn "Processing $filename\n" if $opt_v;
+
+ my $error = FS::cdr::batch_import( {
+ 'file' => "$cachedir/$filename",
+ 'format' => $format,
+ 'batch_namevalue' => $filename,
+ 'empty_ok' => 1,
+ } );
+ die $error if $error;
+
+ if ( $opt_d ) {
+ if($opt_m eq 'ftp') {
+ my $ftp = ftp();
+ $ftp->rename($filename, "$opt_d/$filename")
+ or die "Can't move $filename to $opt_d: ".$ftp->message . "\n";
+ }
+ else {
+ my $sftp = sftp();
+ $sftp->rename($filename, "$opt_d/$filename")
+ or die "can't move $filename to $opt_d: ". $sftp->error . "\n";
+ }
+ }
+
+ unlink "$cachedir/$filename";
+
+}
+
+###
+# subs
+###
+
+sub usage {
+ "Usage: \n cdr.import user format servername\n";
+}
+
+use vars qw( $sftp $ftp );
+
+sub ftp {
+ return $ftp if $ftp && $ftp->pwd;
+
+ my ($hostname, $user) = reverse split('@', $servername);
+ my ($user, $pass) = split(':', $user);
+
+ my $ftp = Net::FTP->new($hostname) or die "FTP connection to '$hostname' failed.";
+ $ftp->login($user, $pass) or die "FTP login failed: ".$ftp->message;
+ $ftp->cwd($opt_r) or die "can't chdir to $opt_r\n" if $opt_r;
+ return $ftp;
+}
+
+sub sftp {
+
+ #reuse connections
+ return $sftp if $sftp && $sftp->cwd;
+
+ my %sftp = ( host => $servername );
+
+ $sftp = Net::SFTP::Foreign->new(%sftp);
+ $sftp->error and die "SFTP connection failed: ". $sftp->error;
+
+ $sftp;
+}
+
+=head1 NAME
+
+cdr.sftp_and_import - Download CDR files from a remote server via SFTP
+
+=head1 SYNOPSIS
+
+ cdr.sftp_and_import [ -m method ][ -p prefix ] [ -e extension ] [ -r remotefolder ] [ -d donefolder ] [ -v ] user format [sftpuser@]servername
+
+=head1 DESCRIPTION
+
+Command line tool to download CDR files from a remote server via SFTP or FTP and then
+import them into the database.
+
+-m: transfer method (sftp or ftp), defaults to sftp
+
+-p: file prefix, if specified
+
+-e: file extension, defaults to .csv
+
+-r: if specified, changes into this remote folder before starting
+
+-d: if specified, moves files to the specified folder when done
+
+-v: verbose
+
+user: freeside username
+
+format: CDR format name
+
+[sftpuser@]servername: remote server
+(or ftpuser:ftppass@servername)
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cdr>
+
+=cut
+
+1;
+
diff --git a/FS/bin/freeside-cdrd b/FS/bin/freeside-cdrd
new file mode 100644
index 0000000..2cf75f3
--- /dev/null
+++ b/FS/bin/freeside-cdrd
@@ -0,0 +1,160 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::Daemon ':all'; #daemonize1 drop_root daemonize2 myexit logfile sig*
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch ); #qsearchs);
+#use FS::cdr;
+use FS::cust_pkg;
+use FS::queue;
+
+my $user = shift or die &usage;
+
+#daemonize1('freeside-sprepaidd', $user); #keep unique pid files w/multi installs
+daemonize1('freeside-cdrd');
+
+drop_root();
+
+adminsuidsetup($user);
+
+logfile( "%%%FREESIDE_LOG%%%/cdrd-log.". $FS::UID::datasrc );
+
+daemonize2();
+
+die "not running; no voip_cdr package defs w/ bill_every_call and customer pkgs"
+ unless _shouldrun();
+
+#--
+
+my $addl_from =
+ 'LEFT JOIN part_pkg USING ( pkgpart ) '.
+ "LEFT JOIN part_pkg_option
+ ON ( cust_pkg.pkgpart = part_pkg_option.pkgpart
+ AND part_pkg_option.optionname = 'bill_every_call' )";
+
+#XXX should pay attention to disable_src for efficiency
+
+my $extra_sql =
+ "WHERE plan = 'voip_cdr' ".
+ " AND optionvalue = '1' ".
+ " AND ( susp IS NULL OR susp = 0)".
+ " AND ( cancel IS NULL OR cancel = 0)".
+ " AND 0 < (
+ SELECT COUNT(*) FROM svc_phone LEFT JOIN cust_svc USING (svcnum)
+ WHERE cust_pkg.pkgnum = cust_svc.pkgnum
+ AND 0 < ( SELECT COUNT(*) FROM cdr
+ WHERE ( freesidestatus IS NULL OR freesidestatus = '' )
+ AND ( charged_party = svc_phone.phonenum
+ OR charged_party = svc_phone.countrycode
+ || svc_phone.phonenum
+ OR src = svc_phone.phonenum
+ OR src = svc_phone.countrycode
+ || svc_phone.phonenum
+ )
+ )
+ )
+ AND 0 = (
+ SELECT COUNT(*) FROM queue
+ WHERE queue.job = 'FS::cust_main::queued_bill'
+ AND queue.custnum = cust_pkg.custnum
+ )
+
+ ";
+# don't repeatedly queue failures
+# AND status != 'failed'
+
+while (1) {
+
+ my $found = 0;
+ foreach my $cust_pkg (
+ qsearch( {
+ 'select' => 'cust_pkg.*, part_pkg.plan',
+ 'table' => 'cust_pkg',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ } )
+ ) {
+
+ $found = 1;
+
+ #my $work_cust_pkg = $cust_pkg;
+
+ #my $cust_main = $cust_pkg->cust_main;
+
+ my $time = time;
+
+ my $job = new FS::queue {
+ 'job' => 'FS::cust_main::queued_bill',
+ 'secure' => 'Y',
+ 'custnum' => $cust_pkg->custnum,
+ };
+ my $error = $job->insert(
+ 'custnum' => $cust_pkg->custnum,
+ 'time' => $time,
+ 'invoice_time' => $time,
+ 'actual_time' => $time,
+ 'check_freq' => '1d', #well
+ #'debug' => 1,
+ );
+
+ if ( $error ) {
+ #die "FATAL: error inserting billing job: $error\n";
+ warn "WARNING: error inserting billing job (will retry in 30 seconds):".
+ " $error\n";
+ sleep 30; #i dunno, wait and see if the database comes back?
+ }
+
+ }
+
+ myexit() if sigterm() || sigint();
+ sleep 1 unless $found;
+
+}
+
+#--
+
+sub _shouldrun {
+
+ my $extra_sql =
+ ' AND 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ )
+ ';
+
+ my @part_pkg =
+ grep $_->option('bill_every_call', 'hush'),
+ qsearch({
+ 'table' => 'part_pkg',
+ 'hashref' => { 'plan' => 'voip_cdr' },
+ 'extra_sql' => $extra_sql,
+ })
+ ;
+
+ scalar(@part_pkg);
+
+}
+
+sub usage {
+ die "Usage:\n\n freeside-cdrd user\n";
+}
+
+=head1 NAME
+
+freeside-cdrd - Real-time daemon for CDRs
+
+=head1 SYNOPSIS
+
+ freeside-cdrd
+
+=head1 DESCRIPTION
+
+Runs continuously, searches for CDRs and bills customers who have VoIP
+price plands with the B<bill_every_call> option set.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-cdrrewrited b/FS/bin/freeside-cdrrewrited
new file mode 100644
index 0000000..0b7f688
--- /dev/null
+++ b/FS/bin/freeside-cdrrewrited
@@ -0,0 +1,129 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $conf );
+use FS::Daemon ':all'; #daemonize1 drop_root daemonize2 myexit logfile sig*
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch ); #qsearchs);
+#use FS::cdr;
+#use FS::cust_pkg;
+#use FS::queue;
+
+my $user = shift or die &usage;
+
+#daemonize1('freeside-sprepaidd', $user); #keep unique pid files w/multi installs
+daemonize1('freeside-cdrrewrited');
+
+drop_root();
+
+adminsuidsetup($user);
+
+logfile( "%%%FREESIDE_LOG%%%/cdrrewrited-log.". $FS::UID::datasrc );
+
+daemonize2();
+
+$conf = new FS::Conf;
+
+die "not running; cdr-asterisk_forward_rewrite and cdr-charged_party_rewrite ".
+ " conf options are both off\n"
+ unless _shouldrun();
+
+#--
+
+while (1) {
+
+ #hmm... don't want to do an expensive search with an ever-growing bunch
+ # of unprocessed CDRs during the month... better to mark them all as
+ # rewritten "skipped", i.e. why we're a daemon in the first place
+ # instead of just doing this search like normal CDRs
+
+ my $found = 0;
+ foreach my $cdr (
+ qsearch( {
+ 'table' => 'cdr',
+ 'extra_sql' => 'FOR UPDATE',
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE freesidestatus IS NULL'.
+ ' AND freesiderewritestatus IS NULL'.
+ ' LIMIT 1024', #arbitrary, but don't eat too much memory
+ } )
+ ) {
+
+ $found = 1;
+ my @status = ();
+
+ if ( $conf->exists('cdr-asterisk_forward_rewrite')
+ && $cdr->dstchannel =~ /^Local\/(\d+)/i && $1 ne $cdr->dst
+ )
+ {
+
+ my $dst = $1;
+
+ warn "dst ". $cdr->dst. " does not match dstchannel $dst ".
+ "(". $cdr->dstchannel. "); rewriting CDR as a forwarded call";
+
+ $cdr->charged_party($cdr->dst);
+ $cdr->dst($dst);
+ $cdr->amaflags(2);
+
+ push @status, 'asterisk_forward';
+
+ }
+
+ if ( $conf->exists('cdr-charged_party_rewrite') && ! $cdr->charged_party ) {
+
+ $cdr->set_charged_party;
+ push @status, 'charged_party';
+
+ }
+
+ $cdr->freesiderewritestatus(
+ scalar(@status) ? join('/', @status) : 'skipped'
+ );
+
+ my $error = $cdr->replace;
+
+ if ( $error ) {
+ warn "WARNING: error rewriting CDR (will retry in 30 seconds):".
+ " $error\n";
+ sleep 30; #i dunno, wait and see if the database comes back?
+ }
+
+ }
+
+ myexit() if sigterm() || sigint();
+ #sleep 1 unless $found;
+ sleep 5 unless $found;
+
+}
+
+#--
+
+sub _shouldrun {
+ $conf->exists('cdr-asterisk_forward_rewrite')
+ || $conf->exists('cdr-charged_party_rewrite');
+}
+
+sub usage {
+ die "Usage:\n\n freeside-cdrrewrited user\n";
+}
+
+=head1 NAME
+
+freeside-cdrrewrited - Real-time daemon for CDR rewriting
+
+=head1 SYNOPSIS
+
+ freeside-cdrrewrited
+
+=head1 DESCRIPTION
+
+Runs continuously, searches for CDRs and does forwarded-call rewriting if the
+"cdr-asterisk_forward_rewrite" or "cdr-charged_party_rewrite" config option is
+enabled.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-check b/FS/bin/freeside-check
new file mode 100644
index 0000000..9930aae
--- /dev/null
+++ b/FS/bin/freeside-check
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw( adminsuidsetup );
+use FS::Cron::check qw(
+ check_queued check_selfservice check_apache check_bop_failures
+ check_sg check_sg_login check_sgng
+ alert error_msg
+);
+
+my $user = shift or die &usage;
+my @emails = @ARGV;
+#die "no notification email given" unless @emails;
+
+eval { adminsuidsetup $user };
+
+if ( $@ ) { alert("Database down: $@", @emails); exit; }
+
+check_queued or alert('Queue daemon not running', @emails);
+check_selfservice or alert(error_msg(), @emails);
+check_apache or alert('Apache not running: '. error_msg(), @emails);
+
+#no-ops unless you are sg
+my $sg = 'FS::ClientAPI::SG';
+check_sg or alert("$sg not responding: ". error_msg(), @emails);
+check_sg_login or alert("$sg login errort: ". error_msg(), @emails);
+check_sgng or alert("${sg}NG not responding: ". error_msg(), @emails);
+
+check_bop_failures or alert(error_msg(), @emails);
+
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..e16cc5c
--- /dev/null
+++ b/FS/bin/freeside-daily
@@ -0,0 +1,135 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw(%opt);
+getopts("p:a:d:vl:sy:nmrkg:u", \%opt);
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#you can skip this by setting the disable_cron_billing config
+use FS::Cron::bill qw(bill);
+bill(%opt);
+
+#you can skip this just by not having the config
+use FS::Cron::breakage qw(reconcile_breakage);
+reconcile_breakage(%opt);
+
+#you can skip this just by not having the config
+use FS::Cron::upload qw(upload);
+upload(%opt);
+
+# Send alerts about upcoming credit card expiration.
+use FS::Cron::alert_expiration qw(alert_expiration);
+my $conf = new FS::Conf;
+alert_expiration(%opt) if($conf->exists('alert_expiration'));
+
+#what to do about the below when using -m? that is the question.
+
+#you don't want to skip this, besides, it should be cheap
+use FS::Cron::expire_user_pref qw(expire_user_pref);
+expire_user_pref();
+
+unless ( $opt{k} ) {
+ use FS::Cron::notify qw(notify_flat_delay);
+ notify_flat_delay(%opt);
+}
+
+#debian Pg 8.1+ auto-vaccums, 7.4 w/postgresql-contrib
+if ( $opt{u} ) {
+ use FS::Cron::vacuum qw(vacuum);
+ vacuum();
+}
+
+#you can skip this just by not having the config
+use FS::Cron::backup qw(backup_scp);
+backup_scp();
+
+###
+# 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' ] [ -y days ] [ -p 'payby' ] [ -a agentnum,agentnum,... ] [ -s ] [ -v ] [ -l level ] [ -m ] [ -k ] user [ custnum custnum ... ]\n";
+}
+
+###
+# documentation
+###
+
+=head1 NAME
+
+freeside-daily - Run daily billing and invoice collection events.
+
+=head1 SYNOPSIS
+
+ freeside-daily [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum,agentnum,... ] [ -s ] [ -v ] [ -l level ] [ -m ] [ -r ] [ -k ] user [ custnum custnum ... ]
+
+=head1 DESCRIPTION
+
+Bills customers and runs invoice collection events. Should be run from
+crontab daily.
+
+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).
+
+ -n: When used with "-d" and/or "-y", specifies that invoices should be dated
+ with today's date, irregardless of the pretend date used to pre-generate
+ the invoices.
+
+ -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
+
+ -a: Only process customers with the specified agentnum. Multiple agentnums can be specified, separated with commas.
+
+ -g: Don't process the provided pkgpart (or pkgparts, specified as a comma-
+ separated list).
+
+ -s: re-charge setup fees
+
+ -v: enable debugging
+
+ -l: debugging level
+
+ -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
+
+ -r: Multi-process mode dry run option
+
+ -k: skip notify_flat_delay
+
+ -u: Do a vacuum (starting with version 1.9, this is not run by default).
+
+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-dbdef-create b/FS/bin/freeside-dbdef-create
new file mode 100755
index 0000000..a04f425
--- /dev/null
+++ b/FS/bin/freeside-dbdef-create
@@ -0,0 +1,47 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use DBI;
+use DBIx::DBSchema 0.26;
+use FS::UID qw(adminsuidsetup datasrc driver_name);
+use FS::Schema;
+
+my $user = shift or die &usage;
+
+$FS::Schema::setup_hack = 1;
+$FS::CurrentUser::upgrade_hack = 1;
+my($dbh)=adminsuidsetup $user;
+
+#needs to match FS::Record
+my($dbdef_file) = "%%%FREESIDE_CONF%%%/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";
+}
+
+=head1 NAME
+
+freeside-dbdef-create - Recreate database schema cache
+
+=head1 SYNOPSIS
+
+ freeside-dbdef-create user
+
+=head1 DESCRIPTION
+
+Reverse engineers the database schema and recreates the dbdef cache file.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-dedup-cust_bill_pkg_detail-header b/FS/bin/freeside-dedup-cust_bill_pkg_detail-header
new file mode 100755
index 0000000..d887f21
--- /dev/null
+++ b/FS/bin/freeside-dedup-cust_bill_pkg_detail-header
@@ -0,0 +1,57 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( %seen $opt_d );
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_bill_pkg_detail;
+
+getopts('d');
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $extra_sql = "AND detail LIKE 'Date,Time%'";
+my @cust_bill_pkg_detail = qsearch( { 'table' => 'cust_bill_pkg_detail',
+ 'hashref' => {format => 'C'},
+ 'extra_sql' => $extra_sql,
+ } );
+for my $detail (@cust_bill_pkg_detail) {
+ if ( $seen{$detail->billpkgnum} ) {
+ if ($opt_d) { # dry run
+ print "DELETE cust_bill_pkg_detail WHERE detailnum=". $detail->detailnum.
+ "\n";
+ } else {
+ $detail->delete;
+ }
+ } else {
+ $seen{$detail->billpkgnum} = 1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-dedup-group [-d] user\n";
+}
+
+=head1 NAME
+
+freeside-dedup-cust_bill_pkg_detail-header - Command line tool to eliminate duplicate headers from cdr details on invoices
+
+=head1 SYNOPSIS
+
+ freeside-dedup-cust_bill_pkg_detail-header user
+
+=head1 DESCRIPTION
+
+ Removes all but one header when duplicate entries exist on invoice
+ cdr details.
+
+ -d: dry run
+
+=head1 SEE ALSO
+
+L<FS::part_pkg::voip_cdr>
+
+=cut
+
diff --git a/FS/bin/freeside-delete-addr_blocks b/FS/bin/freeside-delete-addr_blocks
new file mode 100755
index 0000000..a7e9976
--- /dev/null
+++ b/FS/bin/freeside-delete-addr_blocks
@@ -0,0 +1,31 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( $user $block @blocks );
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::addr_block;
+use FS::svc_broadband;
+
+$user = shift or die &usage;
+&adminsuidsetup( $user );
+
+@blocks = qsearch('addr_block', {} );
+die "No address blocks" unless (scalar(@blocks) > 0);
+
+foreach $block (@blocks) {
+ my @devices = qsearch('svc_broadband', { 'blocknum' => $block->blocknum } );
+ if (@devices) {
+ print "Skipping block " . $block->ip_gateway . " / " . $block->ip_netmask;
+ print "\n";
+ }else{
+ print "Deleting block " . $block->ip_gateway . " / " . $block->ip_netmask;
+ print "\n";
+ $block->delete;
+ }
+}
+
+
+sub usage {
+ "Usage:\n freeside-delete-addr_blocks user \n";
+}
diff --git a/FS/bin/freeside-deloutsource b/FS/bin/freeside-deloutsource
new file mode 100644
index 0000000..afc3a01
--- /dev/null
+++ b/FS/bin/freeside-deloutsource
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+domain=$1
+FREESIDE_CONF=%%%FREESIDE_CONF%%%
+FREESIDE_CACHE=%%%FREESIDE_CACHE%%%
+FREESIDE_EXPORT=%%%FREESIDE_EXPORT%%%
+
+dropdb $domain && \
+rm -rf $FREESIDE_CONF/conf.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf $FREESIDE_CACHE/counters.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf $FREESIDE_CACHE/cache.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf $FREESIDE_EXPORT/export.DBI:Pg:host=localhost\;dbname=$domain && \
+rm $FREESIDE_CONF/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..dc4ff9c
--- /dev/null
+++ b/FS/bin/freeside-deloutsourceuser
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+username=$1
+
+freeside-deluser -h %%%FREESIDE_CONF%%%/htpasswd $username 2>/dev/null
+
diff --git a/FS/bin/freeside-deluser b/FS/bin/freeside-deluser
new file mode 100644
index 0000000..a2a361a
--- /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 = "%%%FREESIDE_CONF%%%";
+
+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-disable-reasons b/FS/bin/freeside-disable-reasons
new file mode 100755
index 0000000..0af4609
--- /dev/null
+++ b/FS/bin/freeside-disable-reasons
@@ -0,0 +1,64 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_t $opt_e);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::reason_type;
+use FS::reason;
+
+getopts('t:e');
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+die &usage
+ unless ($opt_t);
+
+$FS::Record::nowarn_identical = 1;
+
+my @reason = ();
+if ( $opt_t ) {
+ $opt_t =~ /^(\d+)$/ or die "invalid reason_type";
+ @reason = qsearch('reason', { reason_type => $1 } );
+ die "no reasons found\n" unless @reason;
+} else {
+ die "no reason_type specified\n";
+}
+
+foreach my $reason ( @reason ) {
+ if ( $opt_e ) {
+ $reason->disabled('');
+ }else{
+ $reason->disabled('Y');
+ }
+ my $error = $reason->replace
+ if $reason->modified;
+ die $error if $error;
+}
+
+
+sub usage {
+ die "Usage:\n\n freeside-disable-reasons -t reason_type [ -e ] user\n";
+}
+
+=head1 NAME
+
+freeside-disable-reasons - Command line tool to set the disabled column for reasons
+
+=head1 SYNOPSIS
+
+ freeside-disable-reasons -t reason_type [ -e ] user
+
+=head1 DESCRIPTION
+
+ Disables the reasons of the specified reason type.
+ Enables instead if -e is specified.
+
+=head1 SEE ALSO
+
+L<FS::reason>, L<FS::reason_type>
+
+=cut
+
diff --git a/FS/bin/freeside-email b/FS/bin/freeside-email
new file mode 100755
index 0000000..7a93f78
--- /dev/null
+++ b/FS/bin/freeside-email
@@ -0,0 +1,55 @@
+#!/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 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-fetch b/FS/bin/freeside-fetch
new file mode 100755
index 0000000..7b674ed
--- /dev/null
+++ b/FS/bin/freeside-fetch
@@ -0,0 +1,93 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use LWP::UserAgent;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::Misc qw(send_email);
+
+my $user = shift or die &usage;
+my $employeelist = shift or die &usage;
+my $url = shift or die &usage;
+adminsuidsetup $user;
+
+my @employees = split ',', $employeelist;
+
+foreach my $employee (@employees) {
+
+ $employee =~ /^(\w+)$/;
+
+ my $access_user = qsearchs( 'access_user', { 'username' => $1 } );
+ unless ($access_user) {
+ warn "Can't find employee $employee... skipping";
+ next;
+ }
+
+ my $email_address = $access_user->option('email_address');
+ unless ($email_address) {
+ warn "No email address for $employee... skipping";
+ next;
+ }
+
+ no warnings 'redefine';
+ local *LWP::UserAgent::get_basic_credentials = sub {
+ return ($access_user->username, $access_user->_password);
+ };
+
+ my $ua = new LWP::UserAgent;
+ $ua->timeout(1800); #30m, some reports can take a while
+ $ua->agent("FreesideFetcher/0.1 " . $ua->agent);
+
+ my $req = new HTTP::Request GET => $url;
+ my $res = $ua->request($req);
+
+ my $conf = new FS::Conf;
+ my $subject = $conf->config('email_report-subject') || 'Freeside report';
+
+ my %options = ( 'from' => $email_address,
+ 'to' => $email_address,
+ 'subject' => $subject,
+ 'body' => $res->content,
+ );
+
+ $options{'content-type'} = $res->content_type
+ if $res->content_type;
+ $options{'content-encoding'} = $res->content_encoding
+ if $res->content_encoding;
+
+ if ($res->is_success) {
+ send_email %options;
+ }else{
+ warn "fetching $url failed for $employee: " . $res->status_line;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-fetch user employee[,employee ...] url\n\n";
+}
+
+=head1 NAME
+
+freeside-fetch - Send a freeside page to a list of employees.
+
+=head1 SYNOPSIS
+
+ freeside-fetch user employee[,employee ...] url
+
+=head1 DESCRIPTION
+
+ Fetches a web page specified by url as if employee and emails it to
+ employee. Useful when run out of cron to send freeside web pages.
+
+ user: From the mapsecrets file - a user with access to the freeside database
+
+ employee: the username of an employee to receive the emailed page. May be a comma separated list
+
+ url: the web page to be received
+
+=head1 BUGS
+
+ Can leak employee usernames and passwords if requested to access inappropriate urls.
+
+=cut
+
diff --git a/FS/bin/freeside-history-requeue b/FS/bin/freeside-history-requeue
new file mode 100755
index 0000000..77a4332
--- /dev/null
+++ b/FS/bin/freeside-history-requeue
@@ -0,0 +1,100 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_j $opt_d);
+use Getopt::Std;
+use Date::Parse;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::queue;
+
+getopts('j:d');
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $start = shift or die &usage;
+my $end = shift or die &usage;
+
+$start = str2time($start) unless $start =~ /^(\d+)$/;
+$end = str2time($end) unless $end =~ /^(\d+)$/;
+
+my $extra_sql = "AND history_date >= $start AND history_date <= $end";
+
+my $hashref = { 'history_action' => 'insert' };
+
+$hashref->{'job'} = $opt_j if $opt_j;
+
+my @h_queue = qsearch({
+ 'table' => 'h_queue',
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+});
+
+my $num = 0;
+
+foreach my $h_queue (@h_queue) {
+
+ my @queue_args = qsearch({
+ 'table' => 'h_queue_arg',
+ 'hashref' => { 'history_action' => 'insert',
+ 'jobnum' => $h_queue->jobnum,
+ },
+ 'order_by' => 'argnum',
+ });
+
+ my @args = map {
+ my $arg = $_->arg;
+ $arg =~ s/^db\.suicidegirls\.com$/sg-account/;
+ $arg;
+ } @queue_args;
+
+ my $queue = new FS::queue {
+ map { $_ => $h_queue->$_() }
+ qw( job _date status statustext svcnum )
+ };
+
+ if ( $opt_d ) { #dry run
+ print "requeueing job: ". join(' ', @args). "\n";
+ my $error = $queue->check;
+ die "error requeueing job ". $h_queue->jobnum. ": $error" if $error;
+ } else {
+ print "requeueing job: ". join(' ', @args). "\n";
+ my $error = $queue->insert(@args);
+ #warn "error requeueing job ". $h_queue->jobnum. ": $error\n" if $error;
+ print "error requeueing job ". $h_queue->jobnum. ": $error\n" if $error;
+ }
+
+ $num++;
+
+}
+
+print "requeued $num jobs\n";
+
+sub usage {
+ die "Usage:\n\n freeside-history-requeue user start_timestamp end_timestamp\n";
+}
+
+=head1 NAME
+
+freeside-history-requeue - Command line tool to re-trigger export jobs for existing services
+
+=head1 SYNOPSIS
+
+ freeside-history-requeue [ -j job ] [ -d ] user start_timestamp end_timestamp
+
+=head1 DESCRIPTION
+
+ Re-queues all queued jobs for the specified time period.
+
+ -j: specifies that only jobs with this job string are re-queued.
+
+ -d: dry run
+
+=head1 SEE ALSO
+
+L<freeside-reexport>, L<freeside-sqlradius-reset>, L<FS::part_export>
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-init-config b/FS/bin/freeside-init-config
new file mode 100755
index 0000000..fe4729c
--- /dev/null
+++ b/FS/bin/freeside-init-config
@@ -0,0 +1,45 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($opt_u $opt_f $opt_v);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup checkeuid dbh);
+use FS::CurrentUser;
+use FS::Record qw(qsearch);
+use FS::Conf;
+
+
+die "Not running uid freeside!" unless checkeuid();
+
+getopts("u:vf");
+my $dir = shift or die &usage;
+
+$FS::CurrentUser::upgrade_hack = 1;
+$FS::UID::AutoCommit = 0;
+$FS::UID::callback_hack = 1;
+adminsuidsetup $opt_u; #$user;
+
+$|=1;
+
+if (!scalar(qsearch('conf', {})) || $opt_f) {
+ my $error = FS::Conf::init_config($dir);
+ if ($error) {
+ warn "CONFIGURATION INITIALIZATION FAILED\n";
+ dbh->rollback or die dbh->errstr;
+ die $error if $error;
+ }
+}
+
+warn "Freeside database initialized - committing transaction\n" if $opt_v;
+
+dbh->commit or die dbh->errstr;
+dbh->disconnect or die dbh->errstr;
+
+warn "Configuration initialization committed successfully\n" if $opt_v;
+
+sub usage {
+ die "Usage:\n freeside-init-config [ -v ] [ -f ] directory\n"
+ # [ -u user ] for devel/multi-db installs
+}
+
+1;
diff --git a/FS/bin/freeside-monthly b/FS/bin/freeside-monthly
new file mode 100755
index 0000000..a81e3e9
--- /dev/null
+++ b/FS/bin/freeside-monthly
@@ -0,0 +1,94 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+
+&untaint_argv; #what it sounds like (eww)
+#use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y);
+use vars qw(%opt);
+getopts("p:a:d:vsy:", \%opt);
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+use FS::Cron::bill qw(bill);
+bill(%opt, 'check_freq'=>'1m' );
+
+use FS::Cron::upload qw(upload);
+upload(%opt);
+
+###
+# 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-monthly [ -d 'date' ] user [ custnum custnum ... ]\n";
+}
+
+###
+# documentation
+###
+
+=head1 NAME
+
+freeside-monthly - Run monthly billing and invoice collection events.
+
+=head1 SYNOPSIS
+
+ freeside-monthly [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] [ -v ] user [ custnum custnum ... ]
+
+=head1 DESCRIPTION
+
+Bills customers and runs invoice collection events, for the alternate monthly
+event chain. If you have defined monthly event checks, should be run from
+crontab monthly.
+
+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>)
+
+ -a: Only process customers with the specified agentnum
+
+ -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 NOTE
+
+In most cases, you would use freeside-daily only and not freeside-monthly.
+freeside-monthly would only be used in cases where you have events that can
+only be run once each month, for example, batching invoices to a third-party
+print/mail provider.
+
+=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-paymentech-download b/FS/bin/freeside-paymentech-download
new file mode 100755
index 0000000..16ac3c2
--- /dev/null
+++ b/FS/bin/freeside-paymentech-download
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Date::Format qw(time2str);
+use File::Temp qw(tempdir); #0.19 for ->newdir() interface, not in 5.10.0
+use Net::SFTP::Foreign;
+use Expect;
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::pay_batch;
+use FS::cust_pay_batch;
+use FS::Conf;
+
+use vars qw( $opt_t $opt_v $opt_a );
+getopts('vta:');
+
+#$Net::SFTP::Foreign::debug = -1;
+sub usage { "
+ Usage:
+ freeside-paymentech-download [ -v ] [ -t ] [ -a archivedir ] user\n
+" }
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+if ( $opt_a ) {
+ die "no such directory: $opt_a\n"
+ unless -d $opt_a;
+ die "archive directory $opt_a is not writable by the freeside user\n"
+ unless -w $opt_a;
+}
+
+my $unzip_check = `which unzip` or die "can't find unzip executable\n";
+
+#my $tmpdir = File::Temp->newdir();
+my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
+
+my $conf = new FS::Conf;
+my @batchconf = $conf->config('batchconfig-paymentech');
+# BIN, terminalID, merchantID, username, password
+my $username = $batchconf[3] or die "no Paymentech batch username configured\n";
+my $password = $batchconf[4] or die "no Paymentech batch password configured\n";
+
+my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
+ : 'orbitalbatch.paymentech.net');
+print STDERR "Connecting to $username\@$host...\n" if $opt_v;
+
+my $sftp = Net::SFTP::Foreign->new( host => $host,
+ user => $username,
+ password => $password,
+ timeout => 30,
+ );
+die "failed to connect to '$username\@$host'\n(".$sftp->error.")\n" if $sftp->error;
+
+my @files = map { $_->{filename} } @{ $sftp->ls('.', wanted => qr/_resp\.zip$/) };
+die "no response files found\n" if !@files;
+
+BATCH: foreach my $filename (@files) {
+
+ #get file
+ $filename =~ s/_resp\.zip$//;
+ print STDERR "Retrieving $filename\n" if $opt_v;
+ $sftp->get("$filename\_resp.zip", "$tmpdir/${filename}_resp.zip");
+ if($sftp->error) {
+ warn "failed to download $filename\n";
+ next BATCH;
+ }
+
+ #unzip file
+ system('unzip', '-P', $password, '-q',
+ "$tmpdir/${filename}_resp.zip", '-d', $tmpdir);
+ if(! -f "$tmpdir/${filename}_resp.xml") {
+ warn "failed to extract ${filename}_resp.xml from ${filename}_resp.zip\n";
+ next BATCH;
+ }
+
+ #copy to archive dir
+ if ( $opt_a ) {
+ print STDERR "Copying $tmpdir/${filename}_resp.xml to archive dir $opt_a\n"
+ if $opt_v;
+ system 'cp', "$tmpdir/${filename}_resp.xml", $opt_a;
+ warn "failed to copy $tmpdir/${filename}_resp.xml to $opt_a: $@" if $@;
+ }
+
+ #get batchnum & retrieve pending batch
+ open my $fh, "<$tmpdir/${filename}_resp.xml";
+ my ($batchnum) = split ('-', $filename);
+ $batchnum = sprintf("%d", $batchnum); # remove leading zeroes
+ my $batch = qsearchs('pay_batch', { batchnum => $batchnum });
+ if(! $batch) {
+ warn "batch '$batchnum' not found\n";
+ next BATCH;
+ }
+
+ #and import results
+ print STDERR "Importing batch #$batchnum\n" if $opt_v;
+ my $error = $batch->import_results( filehandle => $fh,
+ format => 'paymentech' );
+ warn "error: $error\n" if $error;
+
+}
+
+print STDERR "Finished!\n" if $opt_v;
+
+=head1 NAME
+
+freeside-paymentech-download - Retrieve payment batch responses from Chase Paymentech.
+
+=head1 SYNOPSIS
+
+ paymentech-download [ -v ] [ -t ] [ -a archivedir ] user
+
+=head1 DESCRIPTION
+
+Command line tool to download payment batch responses from the Chase Paymentech
+gateway. These are XML files packaged in ZIP files. This script downloads them
+by SFTP, extracts the contents, and passes them to L<FS::pay_batch::import_result>.
+
+-v: Be verbose.
+
+-t: Use the test server.
+
+-a directory: Archive response files in the provided directory.
+
+user: freeside username
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::pay_batch>
+
+=cut
+
+1;
+
diff --git a/FS/bin/freeside-paymentech-upload b/FS/bin/freeside-paymentech-upload
new file mode 100755
index 0000000..3f8abc0
--- /dev/null
+++ b/FS/bin/freeside-paymentech-upload
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Date::Format qw(time2str);
+use File::Temp qw(tempdir); #0.19 for ->newdir() interface, not in 5.10.0
+use Net::SFTP::Foreign;
+use Expect;
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::pay_batch;
+use FS::cust_pay_batch;
+use FS::Conf;
+
+use vars qw( $opt_a $opt_t $opt_v $opt_p );
+getopts('avtp:');
+
+#$Net::SFTP::Foreign::debug = -1;
+
+sub usage { "
+ Usage:
+ freeside-paymentech-upload [ -v ] [ -t ] user batchnum
+ freeside-paymentech-upload -a [ -p payby ] [ -v ] [ -t ] user\n
+" }
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $zip_check = `which zip` or die "can't find zip executable\n";
+
+my @batches;
+
+if($opt_a) {
+ my %criteria = (status => 'O');
+ $criteria{'payby'} = uc($opt_p) if $opt_p;
+ @batches = qsearch('pay_batch', \%criteria);
+ die "No open batches found".($opt_p ? " of type '$opt_p'" : '').".\n"
+ if !@batches;
+}
+else {
+ my $batchnum = shift;
+ die &usage if !$batchnum;
+ @batches = qsearchs('pay_batch', { batchnum => $batchnum } );
+ die "Can't find payment batch '$batchnum'\n" if !@batches;
+}
+
+my $conf = new FS::Conf;
+my @batchconf = $conf->config('batchconfig-paymentech');
+# BIN, terminalID, merchantID, username, password
+my $username = $batchconf[3] or die "no Paymentech batch username configured\n";
+my $password = $batchconf[4] or die "no Paymentech batch password configured\n";
+
+#my $tmpdir = File::Temp->newdir();
+my $tmpdir = tempdir( CLEANUP => 1 ); #DIR=>somewhere?
+
+my @filenames;
+
+foreach my $pay_batch (@batches) {
+ my $batchnum = $pay_batch->batchnum;
+ my $filename = sprintf('%06d',$batchnum) . '-' .time2str('%Y%m%d%H%M%S', time);
+ print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
+ my $text = $pay_batch->export_batch('paymentech');
+ $text =~ s!<fileID>FILEID</fileID>!<fileID>$filename</fileID>!
+ or die "couldn't find FILEID tag\n";
+ open OUT, ">$tmpdir/$filename.xml";
+ print OUT $text;
+ close OUT;
+
+ system('zip', '-P', $password, '-q', '-j',
+ "$tmpdir/$filename.zip", "$tmpdir/$filename.xml");
+
+ die "failed to create zip file\n" if (! -f "$tmpdir/$filename.zip" );
+ push @filenames, $filename;
+}
+
+my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
+ : 'orbitalbatch.paymentech.net');
+print STDERR "Connecting to $username\@$host...\n" if $opt_v;
+
+my $sftp = Net::SFTP::Foreign->new( host => $host,
+ user => $username,
+ password => $password,
+ timeout => 30,
+ );
+die "failed to connect to '$username\@$host'\n(".$sftp->error.")\n"
+ if $sftp->error;
+
+foreach my $filename (@filenames) {
+ $sftp->put("$tmpdir/$filename.zip", "$filename.zip")
+ or die "failed to upload file (".$sftp->error.")\n";
+}
+
+print STDERR "Finished!\n" if $opt_v;
+
+=head1 NAME
+
+freeside-paymentech-upload - Transmit a payment batch to Chase Paymentech via SFTP.
+
+=head1 SYNOPSIS
+
+ freeside-paymentech-upload [ -a [ -p PAYBY ] ] [ -v ] [ -t ] user batchnum
+
+=head1 DESCRIPTION
+
+Command line tool to upload a payment batch to the Chase Paymentech gateway.
+The batch will be exported to the Paymentech XML format, packaged in a ZIP
+file, and transmitted via SFTP. Use L<paymentech-download> to retrieve the
+response file.
+
+-a: Send all open batches, instead of specifying a batchnum.
+
+-p PAYBY: With -a, limit to batches of that payment type, e.g. -p CARD.
+
+-v: Be verbose.
+
+-t: Send the transaction to the test server.
+
+user: freeside username
+
+batchnum: pay_batch primary key
+
+=head1 BUGS
+
+Passing the zip password on the command line is slightly risky.
+
+=head1 SEE ALSO
+
+L<FS::pay_batch>
+
+=cut
+
+1;
+
diff --git a/FS/bin/freeside-prepaidd b/FS/bin/freeside-prepaidd
new file mode 100644
index 0000000..86bfe87
--- /dev/null
+++ b/FS/bin/freeside-prepaidd
@@ -0,0 +1,106 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_pkg;
+
+my $user = shift or die &usage;
+
+#daemonize1('freeside-sprepaidd', $user); #keep unique pid files w/multi installs
+daemonize1('freeside-prepaidd');
+
+drop_root();
+
+adminsuidsetup($user);
+
+logfile( "%%%FREESIDE_LOG%%%/prepaidd-log.". $FS::UID::datasrc );
+
+daemonize2();
+
+#--
+
+while (1) {
+
+ foreach my $cust_pkg (
+ qsearch( {
+ 'select' => 'cust_pkg.*, part_pkg.plan',
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+ #'hashref' => { 'plan' => 'prepaid' },#should check part_pkg::is_prepaid
+ #'extra_sql' => "AND bill < ". time.
+ 'hashref' => {},
+ 'extra_sql' => "WHERE plan = 'prepaid' AND bill < ". time.
+ " AND bill IS NOT NULL".
+ " AND ( susp IS NULL OR susp = 0)".
+ " AND ( cancel IS NULL OR cancel = 0)"
+ } )
+ ) {
+
+ my $work_cust_pkg = $cust_pkg;
+
+ my $cust_main = $cust_pkg->cust_main;
+ if ( $cust_main->total_unapplied_payments > 0
+ or $cust_main->total_credited > 0
+ )
+ {
+ #this needs a flag to say only do the prepaid packages...
+ # and only try em if the renewal price matches.. but this will do for now
+ my $b_error = $cust_main->bill;
+ if ( $b_error ) {
+ warn "Error billing customer #". $cust_main->custnum;
+ next;
+ }
+ $b_error = $cust_main->apply_payments_and_credits;
+ if ( $b_error ) {
+ warn "Error applying payments&credits, customer #". $cust_main->custnum;
+ next;
+ }
+
+ $work_cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $work_cust_pkg->pkgnum } );
+
+ next
+ if $cust_main->balance <= 0
+ and $work_cust_pkg->bill >= time;
+ }
+
+ my $action = $work_cust_pkg->part_pkg->option('recur_action') || 'suspend';
+
+ my $error = $work_cust_pkg->$action();
+
+ warn "Error ${action}ing package ". $work_cust_pkg->pkgnum.
+ " for custnum ". $work_cust_pkg->custnum.
+ ": $error\n"
+ if $error;
+ }
+
+ die "exiting" if sigterm() || sigint();
+ sleep 5;
+
+}
+
+#--
+
+sub usage {
+ die "Usage:\n\n freeside-prepaidd user\n";
+}
+
+=head1 NAME
+
+freeside-prepaidd - Real-time daemon for prepaid packages
+
+=head1 SYNOPSIS
+
+ freeside-prepaidd
+
+=head1 DESCRIPTION
+
+Runs continuously and suspends or cancels any prepaid customer packages which
+have passed their renewal date (next bill date).
+
+=head1 SEE ALSO
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-prune-applications b/FS/bin/freeside-prune-applications
new file mode 100755
index 0000000..d2b6efe
--- /dev/null
+++ b/FS/bin/freeside-prune-applications
@@ -0,0 +1,63 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_d $opt_q $opt_v); # $opt_n instead of $opt_d?
+use vars qw($DEBUG $DRY_RUN);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup checkeuid);
+use FS::Misc::prune qw(prune_applications);
+
+die "Not running uid freeside!" unless checkeuid();
+
+getopts("dq");
+
+$DEBUG = !$opt_q;
+#$DEBUG = $opt_v;
+
+$DRY_RUN = $opt_d;
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup($user);
+
+my $hashref = {};
+
+$hashref->{dry_run} = 1 if $DRY_RUN;
+$hashref->{debug} = 1 if $DEBUG;
+
+print join "\n", prune_applications($hashref);
+print "\n" if $DRY_RUN;
+
+$dbh->commit or die $dbh->errstr;
+
+###
+
+sub usage {
+ die "Usage:\n freeside-prune-applications [ -d ] [ -q | -v ] user\n";
+}
+
+=head1 NAME
+
+freeside-prune-applications - Removes stray applications of credit, payment to
+ bills, refunds, etc.
+
+=head1 SYNOPSIS
+
+ freeside-prune-applications [ -d ] [ -q | -v ]
+
+=head1 DESCRIPTION
+
+Reads your existing database schema and updates it to match the current schema,
+adding any columns or tables necessary.
+
+ [ -d ]: Dry run; display affected records (to STDOUT) only, but do not
+ remove them.
+
+ [ -q ]: Run quietly. This may become the default at some point.
+
+ [ -v ]: Run verbosely, sending debugging information to STDERR. This is the
+ current default.
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued
new file mode 100644
index 0000000..c9b0edb
--- /dev/null
+++ b/FS/bin/freeside-queued
@@ -0,0 +1,298 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $DEBUG $kids $max_kids $sleep_time %kids );
+use POSIX qw(:sys_wait_h);
+use IO::File;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup forksuidsetup driver_name dbh myconnect);
+use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::queue;
+use FS::queue_depend;
+
+# no autoloading for non-FS classes...
+use Net::SSH 0.07;
+
+$DEBUG = 0;
+
+$kids = 0;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw(%opt);
+getopts('sn', \%opt );
+
+my $user = shift or die &usage;
+
+warn "starting daemonization (forking)\n" if $DEBUG;
+#daemonize1('freeside-queued',$user); #to keep pid files unique w/multi installs
+daemonize1('freeside-queued');
+
+warn "dropping privledges\n" if $DEBUG;
+drop_root();
+
+$ENV{HOME} = (getpwuid($>))[7]; #for ssh
+
+warn "connecting to database\n" if $DEBUG;
+$@ = 'not connected';
+while ( $@ ) {
+ eval { adminsuidsetup $user; };
+ if ( $@ ) {
+ warn $@;
+ warn "sleeping for reconnect...\n";
+ sleep 5;
+ }
+}
+
+logfile( "%%%FREESIDE_LOG%%%/queuelog.". $FS::UID::datasrc );
+
+warn "completing daemonization (detaching))\n" if $DEBUG;
+daemonize2();
+
+#--
+
+my $conf = new FS::Conf;
+$max_kids = $conf->config('queued-max_kids') || 10;
+$sleep_time = $conf->config('queued-sleep_time') || 10;
+
+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 { $FS::UID::dbh = 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;
+
+ my $nodepend = 'AND NOT EXISTS( SELECT 1 FROM queue_depend'.
+ ' WHERE queue_depend.jobnum = queue.jobnum )';
+
+ #anything with a priority goes after stuff without one
+ my $order_by = ' ORDER BY COALESCE(priority,0) ASC, jobnum ASC ';
+
+ my $limit = $max_kids - $kids;
+
+ $order_by .= ( driver_name eq 'mysql'
+ ? " LIMIT $limit FOR UPDATE "
+ : " FOR UPDATE LIMIT $limit " );
+
+ my $hashref = { 'status' => 'new' };
+ if ( $opt{'s'} ) {
+ $hashref->{'secure'} = 'Y';
+ } elsif ( $opt{'n'} ) {
+ $hashref->{'secure'} = '';
+ }
+
+ #qsearch dies when the db goes away
+ my @jobs = eval {
+ qsearch({
+ 'table' => 'queue',
+ 'hashref' => $hashref,
+ 'extra_sql' => $nodepend,
+ 'order_by' => $order_by,
+ });
+ };
+ if ( $@ ) {
+ warn "WARNING: error searching for jobs, closing connection: $@";
+ undef $FS::UID::dbh;
+ next;
+ }
+
+ unless ( @jobs ) {
+ dbh->commit or do {
+ warn "WARNING: database error, closing connection: ". dbh->errstr;
+ undef $FS::UID::dbh;
+ next;
+ };
+ sleep $sleep_time;
+ next;
+ }
+
+ foreach my $job ( @jobs ) {
+
+ 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;
+ }
+
+ dbh->commit or do {
+ warn "WARNING: database error, closing connection: ". dbh->errstr;
+ undef $FS::UID::dbh;
+ next;
+ };
+
+ $FS::UID::AutoCommit = 1;
+
+ my @args = eval { $ljob->args; };
+ if ( $@ ) {
+ warn "WARNING: error retrieving job arguments, closing connection: $@";
+ undef $FS::UID::dbh;
+ next;
+ }
+ splice @args, 0, 1, $ljob if $args[0] eq '_JOB';
+
+ 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; #XXX still dying if we can't fork AND we can't connect to the db
+ 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);
+
+ dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile');
+
+ #auto-use classes...
+ if ( $ljob->job =~ /(FS::(part_export|cust_main)::\w+)::/
+ || $ljob->job =~ /(FS::\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 "&'. $ljob->job. '('. join(', ', @args). ")\n" if $DEBUG;
+ eval $eval; #throw away return value? suppose so
+ if ( $@ ) {
+ my %hash = $ljob->hash;
+ $hash{'statustext'} = $@;
+ if ( $hash{'statustext'} =~ /\/misc\/queued_report/ ) { #use return?
+ $hash{'status'} = 'done';
+ } else {
+ $hash{'status'} = 'failed';
+ warn "job $eval failed";
+ }
+ my $fjob = new FS::queue( \%hash );
+ my $error = $fjob->replace($ljob);
+ die $error if $error;
+ } else {
+ $ljob->delete;
+ }
+
+ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) {
+ open(PROFILE,">%%%FREESIDE_LOG%%%/queueprofile.$$.".time)
+ or die "can't open profile file: $!";
+ print PROFILE dbh->sprintProfile();
+ close PROFILE or die "can't close profile file: $!";
+ }
+
+ exit;
+ #end-of-kid
+ }
+
+ } #foreach my $job
+
+} continue {
+ if ( sigterm() ) {
+ warn "received TERM signal; exiting\n";
+ exit;
+ }
+ if ( sigint() ) {
+ warn "received INT signal; exiting\n";
+ exit;
+ }
+}
+
+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-queued user\n";
+}
+
+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 [ -s | -n ] user
+
+=head1 DESCRIPTION
+
+Job queue daemon. Should be running at all times.
+
+-s: "secure" jobs only (queued billing jobs)
+
+-n: non-"secure" jobs only (other jobs)
+
+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-reset-fixed b/FS/bin/freeside-reset-fixed
new file mode 100755
index 0000000..5829d44
--- /dev/null
+++ b/FS/bin/freeside-reset-fixed
@@ -0,0 +1,69 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_p $opt_s $opt_r);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_svc;
+use FS::svc_Common;
+
+getopts('p:s:r');
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+die &usage
+ if ($opt_p && $opt_s);
+
+$FS::Record::nowarn_identical = 1;
+$FS::svc_Common::noexport_hack = 1
+ unless $opt_r;
+
+my @svc_x = ();
+if ( $opt_s ) {
+ $opt_s =~ /^(\d+)$/ or die "invalid svcnum";
+ my $cust_svc = qsearchs('cust_svc', { svcnum => $1 } )
+ or die "svcnum $opt_s not found\n";
+ push @svc_x, $cust_svc->svc_x;
+} elsif ( $opt_p ) {
+ $opt_p =~ /^(\d+)$/ or die "invalid svcpart";
+ push @svc_x, map { $_->svc_x } qsearch('cust_svc', { svcpart => $1 } );
+ die "no services with svcpart $opt_p found\n" unless @svc_x;
+} else {
+ push @svc_x, map { $_->svc_x } qsearch('cust_svc', {} );
+ die "no services found\n" unless @svc_x;
+}
+
+foreach my $svc_x ( @svc_x ) {
+ my $result = $svc_x->setfixed;
+ die $result unless ref($result);
+ my $error = $svc_x->replace
+ if $svc_x->modified;
+ die $error if $error;
+}
+
+
+sub usage {
+ die "Usage:\n\n freeside-reset-fixed user [ -s svcnum | -p svcpart ] [ -r ]\n";
+}
+
+=head1 NAME
+
+freeside-reset-fixed - Command line tool to set the fixed columns for existing services
+
+=head1 SYNOPSIS
+
+ freeside-reset-fixed user [ -s svcnum | -p svcpart ] [ -r ]
+
+=head1 DESCRIPTION
+
+ Resets the fixed columns for the specified service part or service number.
+ Re-exports the service if -r is specified.
+
+=head1 SEE ALSO
+
+L<freeside-reexport>, L<FS::part_svc>
+
+=cut
+
diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server
new file mode 100644
index 0000000..1c2086e
--- /dev/null
+++ b/FS/bin/freeside-selfservice-server
@@ -0,0 +1,272 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $FREESIDE_LOG $FREESIDE_LOCK );
+use vars qw( $Debug %kids $kids $max_kids $ssh_pid %old_ssh_pid $keepalives );
+use subs qw( lock_write unlock_write myshutdown usage );
+use Fcntl qw(:flock);
+use POSIX qw(:sys_wait_h);
+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::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
+use FS::UID qw(adminsuidsetup forksuidsetup);
+use FS::ClientAPI qw( load_clientapi_modules );
+use FS::ClientAPI_SessionCache;
+use FS::Record qw( qsearch qsearchs );
+
+use FS::Conf;
+use FS::cust_svc;
+use FS::agent;
+
+$FREESIDE_LOG = "%%%FREESIDE_LOG%%%";
+$FREESIDE_LOCK = "%%%FREESIDE_LOCK%%%";
+
+$Debug = 1; # 2 will turn on more logging
+ # 3 will log packet contents, including passwords
+
+$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 : '';
+
+my $lock_file = "$FREESIDE_LOCK/selfservice.$machine.writelock";
+
+# to keep pid files unique w/multi machines (and installs!)
+# $FS::UID::datasrc not posible
+daemonize1("freeside-selfservice-server","$user.$machine");
+
+#false laziness w/Daemon::drop_root
+my $freeside_gid = scalar(getgrnam('freeside'))
+ or die "can't find freeside group\n";
+
+open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+chown $FS::UID::freeside_uid, $freeside_gid, $lock_file;
+
+drop_root();
+
+$ENV{HOME} = (getpwuid($>))[7]; #for ssh
+
+load_clientapi_modules;
+
+adminsuidsetup $user;
+
+#logfile("/usr/local/etc/freeside/selfservice.". $FS::UID::datasrc); #MACHINE
+logfile("$FREESIDE_LOG/selfservice.$machine.log");
+
+daemonize2();
+
+my $conf = new FS::Conf;
+if ( $conf->exists('selfservice-ignore_quantity') ) {
+ $FS::cust_svc::ignore_quantity = 1;
+ $FS::cust_svc::ignore_quantity = 1; #now it is used twice.
+}
+
+#clear the signup info cache so an "/etc/init.d/freeside restart" will pick
+#up new info... (better as a callback in Signup.pm?)
+my $cache = new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::Signup',
+} );
+$cache->remove('signup_info_cache');
+
+#and also clear the selfservice skin info cache, for the same reason
+my $ss_cache = new FS::ClientAPI_SessionCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+} );
+$ss_cache->remove($_)
+ foreach grep /^skin_info_cache_agent/, $ss_cache->get_keys();
+
+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 ) {
+ myshutdown() if sigint() || sigterm();
+ if ( $keepalives && $keepalive_count++ > 10 ) {
+ $keepalive_count = 0;
+ lock_write;
+
+ nstore_fd( { _token => '_keepalive' }, $writer );
+ foreach my $agent ( qsearch( 'agent', { disabled => '' } ) ) {
+ my $config = qsearchs( 'conf', { name => 'selfservice-bulk_ftp_dir',
+ agentnum => $agent->agentnum,
+ } )
+ or next;
+
+ my $session =
+ FS::ClientAPI->dispatch( 'Agent/agent_login',
+ { username => $agent->username,
+ password => $agent->_password,
+ }
+ );
+
+ nstore_fd( { _token => '_ftp_scan',
+ dir => $config->value,
+ session_id => $session->{session_id},
+ },
+ $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;
+ $old_ssh_pid{$ssh_pid} = 1;
+ $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
+ }
+
+ }
+
+ myshutdown if sigint() || sigterm();
+ 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};
+ }
+ }
+
+ foreach my $pid ( keys %old_ssh_pid ) {
+ waitpid($pid, WNOHANG) and delete $old_ssh_pid{$pid};
+ }
+ #warn "done reaping\n";
+}
+
+sub myshutdown {
+ &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 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..ddff81e
--- /dev/null
+++ b/FS/bin/freeside-setup
@@ -0,0 +1,165 @@
+#!/usr/bin/perl -Tw
+
+#to delay loading dbdef until we're ready
+BEGIN { $FS::Schema::setup_hack = 1; }
+
+#to allow initial insert
+use FS::part_pkg;
+$FS::part_pkg::setup_hack = 1;
+$FS::part_pkg::setup_hack = 1;
+
+use strict;
+use vars qw($opt_u $opt_d $opt_v);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets);
+use FS::CurrentUser;
+use FS::Schema qw( dbdef_dist reload_dbdef );
+use FS::Record qw( qsearch );
+#use FS::raddb;
+use FS::Setup qw(create_initial_data);
+use FS::Conf;
+
+die "Not running uid freeside!" unless checkeuid();
+
+#my %attrib2db =
+# map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
+
+getopts("u:vd:");
+my $config_dir = shift || '%%%DIST_CONF%%%' ;
+$config_dir =~ /^([\w.:=\/]+)$/
+ or die "unacceptable configuration directory name";
+$config_dir = $1;
+
+getsecrets($opt_u);
+
+#needs to match FS::Record
+my($dbdef_file) = "%%%FREESIDE_CONF%%%/dbdef.". datasrc;
+
+###
+
+my $username_len = 32;
+
+#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;
+
+###
+# create a dbdef object from the old data structure
+###
+
+my $dbdef = dbdef_dist(datasrc);
+
+#important
+$dbdef->save($dbdef_file);
+&FS::Schema::reload_dbdef($dbdef_file);
+
+###
+# create 'em
+###
+
+$FS::CurrentUser::upgrade_hack = 1;
+$FS::UID::callback_hack = 1;
+my $dbh = adminsuidsetup $opt_u; #$user;
+$FS::UID::callback_hack = 0;
+
+#create tables
+$|=1;
+
+foreach my $statement ( $dbdef->sql($dbh) ) {
+ $dbh->do( $statement )
+ or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement";
+}
+
+#now go back and reverse engineer the db
+#so we pick up the correct column DEFAULTs for #oidless inserts
+dbdef_create($dbh, $dbdef_file);
+delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
+reload_dbdef($dbdef_file);
+
+warn "Freeside schema initialized - commiting transaction\n" if $opt_v;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+warn "Database schema committed successfully\n" if $opt_v;
+
+warn "Initializing freeside configuration\n" if $opt_v;
+$FS::UID::callback_hack = 1;
+$dbh = adminsuidsetup $opt_u;
+$FS::UID::callback_hack = 0;
+if (!scalar(qsearch('conf', {}))) {
+ my $error = FS::Conf::init_config($config_dir);
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr;
+ die $error;
+ }
+}
+
+warn "Freeside configuration initialized - commiting transaction\n" if $opt_v;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+warn "Freeside configuration committed successfully\n" if $opt_v;
+
+$dbh = adminsuidsetup $opt_u;
+create_initial_data('domain' => $opt_d);
+
+warn "Freeside database initialized - commiting transaction\n" if $opt_v;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+warn "Database initialization committed successfully\n" if $opt_v;
+
+sub dbdef_create { # reverse engineer the schema from the DB and save to file
+ my( $dbh, $file ) = @_;
+ my $dbdef = new_native DBIx::DBSchema $dbh;
+ $dbdef->save($file);
+}
+
+sub usage {
+ die "Usage:\n freeside-setup -d domain.name [ -v ] [ config/dir ]\n"
+ # [ -u user ] for devel/multi-db installs
+}
+
+1;
+
+
diff --git a/FS/bin/freeside-sqlradius-dedup-group b/FS/bin/freeside-sqlradius-dedup-group
new file mode 100755
index 0000000..441d50f
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-dedup-group
@@ -0,0 +1,82 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( %seen @dups );
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+
+my %allowed_types = map { $_ => 1 } qw ( sqlradius sqlradius_withdomain );
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $export_x = shift;
+my @part_export;
+if ( !defined($export_x) ) {
+ @part_export = qsearch('part_export', {} );
+} elsif ( $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";
+}
+
+@part_export = grep { $allowed_types{$_->exporttype} } @part_export
+ or die "No sqlradius exports specified.";
+
+foreach my $part_export ( @part_export ) {
+ my $dbh = DBI->connect( map $part_export->option($_),
+ qw ( datasrc username password ) );
+
+ my $sth = $dbh->prepare("SELECT id,username,groupname
+ FROM usergroup ORDER By username,groupname,id")
+ or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+
+ @dups = (); %seen = ();
+ while (my $row = $sth->fetchrow_arrayref ) {
+ my ($userid, $username, $groupname) = @$row;
+ unless ( exists($seen{$username}{$groupname}) ) {
+ $seen{$username}{$groupname} = $userid;
+ next;
+ }
+ push @dups, $userid;
+ }
+
+ $sth = $dbh->prepare("DELETE FROM usergroup WHERE id = ?")
+ or die $dbh->errstr;
+
+ foreach (@dups) {
+ $sth->execute($_) or die $sth->errstr;
+ }
+
+}
+
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-dedup-group user [ exportnum|exporttype ]\n";
+}
+
+=head1 NAME
+
+freeside-sqlradius-dedup-group - Command line tool to eliminate duplicate usergroup entries from radius tables
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-dedup-group user [ exportnum|exporttype ]
+
+=head1 DESCRIPTION
+
+ Removes all but one username groupname pair when duplicate entries exist
+ for the specified export (selected by exportnum or exporttype) or all
+ exports if none are specified.
+
+=head1 SEE ALSO
+
+L<freeside-reexport>, L<freeside-sqlradius-reset>, L<FS::part_export>
+
+=cut
+
diff --git a/FS/bin/freeside-sqlradius-radacctd b/FS/bin/freeside-sqlradius-radacctd
new file mode 100644
index 0000000..7b2d04d
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-radacctd
@@ -0,0 +1,145 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( @part_export );
+use subs qw(myshutdown);
+use POSIX qw(:sys_wait_h);
+#use IO::File;
+use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
+use FS::UID qw(adminsuidsetup); #forksuidsetup driver_name dbh myconnect);
+use FS::Record qw(qsearch); # qsearchs);
+use FS::part_export;
+use FS::part_export::sqlradius;
+#use FS::svc_acct;
+#use FS::cust_svc;
+
+my $user = shift or die &usage;
+
+#daemonize1('freeside-sqlradius-radacctd', $user); #keep unique pid files w/multi installs
+daemonize1('freeside-sqlradius-radacctd');
+
+drop_root();
+
+#$ENV{HOME} = (getpwuid($>))[7]; #for ssh
+
+adminsuidsetup $user;
+
+logfile( "%%%FREESIDE_LOG%%%/sqlradius-radacctd-log.". $FS::UID::datasrc );
+
+daemonize2();
+
+#--
+
+my @part_export = FS::part_export::sqlradius->all_sqlradius_withaccounting();
+
+die "no sqlradius, sqlradius_withdomain, radiator or phone_sqlradius exports".
+ " without ignore_accounting"
+ unless @part_export;
+
+while (1) {
+
+ #fork off one kid per export (machine)
+ # _>{'_radacct_kid'} is an evil kludge
+ foreach my $part_export ( grep ! $_->{'_radacct_kid'}, @part_export ) {
+
+ defined( my $pid = fork ) or do {
+ warn "WARNING: can't fork to spawn child for ". $part_export->machine;
+ next;
+ };
+
+ if ( $pid ) {
+ $part_export->{'_radacct_kid'} = $pid;
+ warn "child $pid spawned for ". $part_export->machine;
+ } else { #kid time
+
+ adminsuidsetup($user); #get our own db handle
+
+ until ( sigint || sigterm ) {
+ $part_export->update_svc();
+ sleep 1;
+ }
+
+ warn "child for ". $part_export->machine. " done";
+ exit;
+
+ } #eo kid
+
+ }
+
+ #reap up any kids that died...
+ &reap_kids;
+
+ myshutdown() if sigterm() || sigint();
+
+ sleep 5;
+}
+
+#--
+
+sub myshutdown {
+ &reap_kids;
+
+ #kill all the kids
+ kill 'TERM', $_ foreach grep $_, map $_->{'_radacct_kid'}, @part_export;
+
+ my $wait = 12; #wait up to 1 minute
+ while ( ( grep $_->{'_radacct_kid'}, @part_export ) && $wait-- ) {
+ warn "waiting for children to terminate";
+ sleep 5;
+ &reap_kids;
+ }
+ warn "abandoning children" if grep $_->{'_radacct_kid'}, @part_export;
+ die "exiting";
+}
+
+sub reap_kids {
+ #warn "reaping kids\n";
+ foreach my $part_export ( grep $_->{'_radacct_kid'}, @part_export ) {
+ my $pid = $part_export->{'_radacct_kid'};
+ my $kid = waitpid($pid, WNOHANG);
+ if ( $kid > 0 ) {
+ $part_export->{'_radacct_kid'} = '';
+ }
+ }
+ #warn "done reaping\n";
+}
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-radacctd user\n";
+}
+
+=head1 NAME
+
+freeside-sqlradius-radacctd - Real-time radacct import daemon
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-radacctd username
+
+=head1 DESCRIPTION
+
+Imports records from an the SQL radacct tables of all sqlradius,
+sqlradius_withdomain and radiator exports (except those with the
+ignore_accounting flag) and updates the following fields in svc_acct (see
+L<FS::svc_acct>) for each account: last_login, last_logout, seconds,
+upbytes, downbytes, totalbytes. Runs as a daemon and updates the database
+in real-time.
+
+B<username> is a username added by freeside-adduser.
+
+=head1 RADIUS DATABASE CHANGES
+
+In 1.7.4+, freeside-upgrade should have taken care of these changes already.
+
+ALTER TABLE radacct ADD COLUMN FreesideStatus varchar(32) NULL;
+
+If you want to ignore the existing accountg records, also do:
+
+UPDATE radacct SET FreesideStatus = 'done' WHERE FreesideStatus IS NULL;
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/bin/freeside-sqlradius-reset b/FS/bin/freeside-sqlradius-reset
new file mode 100755
index 0000000..a77bad6
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-reset
@@ -0,0 +1,118 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $opt_n );
+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;
+
+getopts("n");
+
+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 phone_sqlradius )) {
+ push @exports, qsearch('part_export', { exportnum => $exportnum,
+ exporttype => $exporttype, } );
+ }
+ }
+ } else {
+ @exports = qsearch('part_export', { exporttype=>'sqlradius' } );
+ push @exports, qsearch('part_export', { exporttype=>'sqlradius_withdomain' } );
+}
+
+unless ( $opt_n ) {
+ 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;
+ }
+}
+
+use FS::svc_Common;
+$FS::svc_Common::overlimit_missing_cust_svc_nonfatal_kludge = 1;
+$FS::svc_Common::overlimit_missing_cust_svc_nonfatal_kludge = 1;
+
+foreach my $export ( @exports ) {
+
+ #my @svcparts = map { $_->svcpart } $export->export_svc;
+ my $overlimit_groups = $export->option('overlimit_groups');
+
+ my @svc_x =
+ map { $_->svc_x }
+ #map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ #grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ # $export->export_svc;
+ map { @{ $_->[1] } }
+ grep { scalar( @{ $_->[1] } ) }
+ map { [ $_, [ qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) ] ] }
+ $export->export_svc;
+
+
+ foreach my $svc_x ( @svc_x ) {
+
+ #$svc_x->check; #set any fixed usergroup so it'll export even if all
+ # #svc_acct records don't have the group yet
+ #more efficient?
+ my $x = $svc_x->setfixed( $svc_x->_fieldhandlers);
+ unless ( ref($x) ) {
+ warn "WARNING: can't set fixed usergroups for svcnum ". $svc_x->svcnum.
+ "\n";
+ }
+
+ if ($overlimit_groups && $svc_x->overlimit) {
+ $svc_x->usergroup( &{ $svc_x->_fieldhandlers->{'usergroup'} }
+ ($svc_x, $overlimit_groups)
+ );
+ }
+
+ #false laziness with FS::svc_acct::insert (like it matters)
+ my $error = $export->export_insert($svc_x);
+ 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 [ -n ] 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.
+
+The B<-n> option, if supplied, supresses the deletion of the existing data in
+the tables.
+
+=head1 SEE ALSO
+
+L<freeside-reexport>, L<FS::part_export>, L<FS::part_export::sqlradius>
+
+=cut
+
+1;
diff --git a/FS/bin/freeside-sqlradius-seconds b/FS/bin/freeside-sqlradius-seconds
new file mode 100644
index 0000000..9999cbb
--- /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 - Command line time-online tool
+
+=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/bin/freeside-sqlradius-set-lastlog b/FS/bin/freeside-sqlradius-set-lastlog
new file mode 100755
index 0000000..ad85630
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-set-lastlog
@@ -0,0 +1,102 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs str2time_sql);
+use FS::Conf;
+use FS::part_export;
+use FS::svc_acct;
+
+my %allowed_types = map { $_ => 1 } qw ( sqlradius sqlradius_withdomain );
+my $conf = new FS::Conf;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $export_x = shift;
+my @part_export;
+if ( !defined($export_x) ) {
+ @part_export = qsearch('part_export', {} );
+} elsif ( $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";
+}
+
+# gross almost false laziness with FS::part_export::sqlradius::update_svc_acct
+@part_export = grep { ! $_->option('ignore_accounting') }
+ grep { $allowed_types{$_->exporttype} }
+ @part_export
+ or die "No sqlradius exports specified.";
+
+
+foreach my $part_export ( @part_export ) {
+ my $dbh = DBI->connect( map $part_export->option($_),
+ qw ( datasrc username password ) );
+
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+ my $group = "UserName";
+ $group .= ",Realm"
+ if ( ref($part_export) =~ /withdomain/ );
+
+ my $sth = $dbh->prepare("SELECT UserName, Realm,
+ $str2time max(AcctStartTime)),
+ $str2time max(AcctStopTime))
+ FROM radacct
+ WHERE AcctStartTime != 0 AND AcctStopTime != 0
+ GROUP BY $group")
+ or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+
+ while (my $row = $sth->fetchrow_arrayref ) {
+ my ($username, $realm, $start, $stop) = @$row;
+
+ $username = lc($username) unless $conf->exists('username-uppercase');
+ my $extra_sql = '';
+ if ( ref($part_export) =~ /withdomain/ ) {
+ $extra_sql = " And '$realm' = ( SELECT domain FROM svc_domain
+ WHERE svc_domain.svcnum = svc_acct.domsvc ) ";
+ }
+
+ my $svc_acct = qsearchs( 'svc_acct',
+ { 'username' => $username },
+ '',
+ $extra_sql,
+ );
+ if ($svc_acct) {
+ $svc_acct->last_login($start)
+ if $start && (!$svc_acct->last_login || $start > $svc_acct->last_login);
+ $svc_acct->last_logout($stop)
+ if $stop && (!$svc_acct->last_logout || $stop > $svc_acct->last_logout);
+ }
+ }
+}
+
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-set_lastlog user [ exportnum|exporttype ]\n";
+}
+
+=head1 NAME
+
+freeside-sqlradius-set-lastlog - Command line tool to set last_login and last_logout values from radius tables
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-set-lastlog user [ exportnum|exporttype ]
+
+=head1 DESCRIPTION
+
+ Sets the last_login and last_logout columns of each svc_acct based on
+ data in the radacct table for the specified export (selected by exportnum
+ or exporttype) or all exports if none are specified.
+
+=head1 SEE ALSO
+
+L<freeside-sqlradius-radacctd>, L<FS::part_export>
+
+=cut
+
diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade
new file mode 100755
index 0000000..f4ff1c2
--- /dev/null
+++ b/FS/bin/freeside-upgrade
@@ -0,0 +1,242 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_d $opt_s $opt_q $opt_v $opt_r);
+use vars qw($DEBUG $DRY_RUN);
+use Getopt::Std;
+use DBIx::DBSchema 0.31; #0.39
+use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name); #getsecrets);
+use FS::CurrentUser;
+use FS::Schema qw( dbdef dbdef_dist reload_dbdef );
+use FS::Misc::prune qw(prune_applications);
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::Upgrade qw(upgrade upgrade_sqlradius);
+
+my $start = time;
+
+die "Not running uid freeside!" unless checkeuid();
+
+getopts("dqrs");
+
+$DEBUG = !$opt_q;
+#$DEBUG = $opt_v;
+
+$DRY_RUN = $opt_d;
+
+my $user = shift or die &usage;
+$FS::CurrentUser::upgrade_hack = 1;
+$FS::UID::callback_hack = 1;
+my $dbh = adminsuidsetup($user);
+$FS::UID::callback_hack = 0;
+
+if ( driver_name =~ /^mysql/i ) { #until 0.39 is required above
+ eval "use DBIx::DBSchema 0.39;";
+ die $@ if $@;
+}
+
+#needs to match FS::Schema...
+my $dbdef_file = "%%%FREESIDE_CONF%%%/dbdef.". datasrc;
+
+dbdef_create($dbh, $dbdef_file);
+
+delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
+reload_dbdef($dbdef_file);
+
+warn "Upgrade startup completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+#$DBIx::DBSchema::DEBUG = $DEBUG;
+#$DBIx::DBSchema::Table::DEBUG = $DEBUG;
+#$DBIx::DBSchema::Index::DEBUG = $DEBUG;
+
+my @bugfix = ();
+
+if (dbdef->table('cust_main')->column('agent_custid') && ! $opt_s) {
+ push @bugfix,
+ "UPDATE cust_main SET agent_custid = NULL where agent_custid = ''";
+
+ push @bugfix,
+ "UPDATE h_cust_main SET agent_custid = NULL where agent_custid = ''"
+ if (dbdef->table('h_cust_main'));
+}
+
+#you should have run fs-migrate-part_svc ages ago, when you upgraded
+#from 1.3 to 1.4... if not, it needs to be hooked into -upgrade here or
+#you'll lose all the part_svc settings it migrates to part_svc_column
+
+my @statements = dbdef->sql_update_schema( dbdef_dist(datasrc),
+ $dbh,
+ { 'nullify_default' => 1, },
+ );
+
+@statements =
+ grep { $_ !~ /^CREATE +INDEX +h_queue/i } #useless, holds up queue insertion
+ @statements;
+
+unless ( driver_name =~ /^mysql/i ) {
+ #not necessary under non-mysql, takes forever on big db
+ @statements =
+ grep { $_ !~ /^ *ALTER +TABLE +h_queue +ALTER +COLUMN +job +TYPE +varchar\(512\) *$/i }
+ @statements;
+}
+
+if ( $DRY_RUN ) {
+ print
+ join(";\n", @bugfix, @statements ). ";\n";
+ exit;
+} else {
+ foreach my $statement ( @bugfix, @statements ) {
+ warn "$statement\n";
+ $dbh->do( $statement )
+ or die "Error: ". $dbh->errstr. "\n executing: $statement";
+ }
+
+# warn "Pre-schema change upgrades completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+# $start = time;
+
+# dbdef->update_schema( dbdef_dist(datasrc), $dbh );
+}
+
+warn "Schema upgrade completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+my $hashref = {};
+$hashref->{dry_run} = 1 if $DRY_RUN;
+$hashref->{debug} = 1 if $DEBUG && $DRY_RUN;
+prune_applications($hashref) unless $opt_s;
+
+warn "Application pruning completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+print "\n" if $DRY_RUN;
+
+if ( $dbh->{Driver}->{Name} =~ /^mysql/i && ! $opt_s ) {
+
+ foreach my $table (qw( svc_acct svc_phone )) {
+
+ my $sth = $dbh->prepare(
+ "SELECT COUNT(*) FROM duplicate_lock WHERE lockname = '$table'"
+ ) or die $dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+ unless ( $sth->fetchrow_arrayref->[0] ) {
+
+ $sth = $dbh->prepare(
+ "INSERT INTO duplicate_lock ( lockname ) VALUES ( '$table' )"
+ ) or die $dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+ }
+
+ }
+
+ warn "Duplication lock creation completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+ $start = time;
+
+}
+
+$dbh->commit or die $dbh->errstr;
+
+dbdef_create($dbh, $dbdef_file);
+
+$dbh->disconnect or die $dbh->errstr;
+
+delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
+$FS::UID::AutoCommit = 0;
+$FS::UID::callback_hack = 1;
+$dbh = adminsuidsetup($user);
+$FS::UID::callback_hack = 0;
+unless ( $DRY_RUN || $opt_s ) {
+ my $dir = "%%%FREESIDE_CONF%%%/conf.". datasrc;
+ if (!scalar(qsearch('conf', {}))) {
+ my $error = FS::Conf::init_config($dir);
+ if ($error) {
+ warn "CONFIGURATION UPGRADE FAILED\n";
+ $dbh->rollback or die $dbh->errstr;
+ die $error;
+ }
+ }
+}
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+$dbh = adminsuidsetup($user);
+
+warn "Re-initialization with updated schema completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+upgrade()
+ unless $DRY_RUN || $opt_s;
+
+$dbh->commit or die $dbh->errstr;
+
+warn "Table updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+upgrade_sqlradius()
+ unless $DRY_RUN || $opt_s || $opt_r;
+
+warn "SQL RADIUS updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
+$start = time;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+warn "Final commit and disconnection completed in ". (time-$start). " seconds; upgrade done!\n"; # if $DEBUG;
+
+###
+
+sub dbdef_create { # reverse engineer the schema from the DB and save to file
+ my( $dbh, $file ) = @_;
+ my $dbdef = new_native DBIx::DBSchema $dbh;
+ $dbdef->save($file);
+}
+
+sub usage {
+ die "Usage:\n freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ] user\n";
+}
+
+=head1 NAME
+
+freeside-upgrade - Upgrades database schema for new freeside verisons.
+
+=head1 SYNOPSIS
+
+ freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ]
+
+=head1 DESCRIPTION
+
+Reads your existing database schema and updates it to match the current schema,
+adding any columns or tables necessary.
+
+Also performs other upgrade functions:
+
+=over 4
+
+=item Calls FS:: Misc::prune::prune_applications (probably unnecessary every upgrade, but simply won't find any records to change)
+
+=item If necessary, moves your configuration information from the filesystem in /usr/local/etc/freeside/conf.<datasrc> to the database.
+
+=back
+
+ [ -d ]: Dry run; output SQL statements (to STDOUT) only, but do not execute
+ them.
+
+ [ -q ]: Run quietly. This may become the default at some point.
+
+ [ -r ]: Skip sqlradius updates. Useful for occassions where the sqlradius
+ databases may be inaccessible.
+
+ [ -v ]: Run verbosely, sending debugging information to STDERR. This is the
+ current default.
+
+ [ -s ]: Schema changes only. Useful for Pg/slony slaves where the data
+ changes will be replicated from the Pg/slony master.
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-void-payments b/FS/bin/freeside-void-payments
new file mode 100755
index 0000000..8c1f3db
--- /dev/null
+++ b/FS/bin/freeside-void-payments
@@ -0,0 +1,239 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $user $cust_main @customers );
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::Conf;
+use FS::cust_main;
+use FS::cust_pay;
+use FS::cust_pay_void;
+use Business::OnlinePayment; # For retrieving the void list only.
+use Time::Local;
+use Date::Parse 'str2time';
+use Date::Format 'time2str';
+
+my %opt;
+getopts("r:f:ca:g:s:e:vnX:", \%opt);
+
+$user = shift or die &usage;
+&adminsuidsetup( $user );
+
+# The -g and -a options need to override this.
+my $method = $opt{'c'} ? 'ECHECK' : 'CARD';
+my $gateway;
+if($opt{'g'}) {
+ $gateway = FS::payment_gateway->by_key($opt{'g'})
+ or die "Payment gateway not found: '".$opt{'g'}."'.";
+}
+elsif($opt{'a'}) {
+ my $agent = FS::agent->by_key($opt{'a'})
+ or die "Agent not found: '".$opt{'a'}."'.";
+ $gateway = $agent->payment_gateway(method => $method)
+ or die "Agent has no payment gateway for method '$method'.";
+}
+
+if(defined($opt{'X'}) and !qsearchs('reason', { reasonnum => opt{'X'} })) {
+ die "Cancellation reason not found: '".$opt{'X'}."'";
+}
+
+my ($processor, $login, $password, $action, @bop_options) =
+ FS::cust_main->default_payment_gateway($method);
+my $gatewaynum = '';
+
+if($gateway) {
+# override the default gateway
+ $gatewaynum = $gateway->gatewaynum . '-' if $gateway->gatewaynum;
+ $processor = $gateway->gateway_module;
+ $login = $gateway->gateway_username;
+ $password = $gateway->gateway_password;
+ $action = $gateway->gateway_action;
+ @bop_options = $gateway->options;
+}
+
+my @auths;
+if($opt{'f'}) {
+# Read the list of authorization numbers from a file.
+ my $in;
+ open($in, '< '. $opt{'f'}) or die "Unable to open file: '".$opt{'f'}."'.";
+ @auths = grep /^\d+$/, <$in>;
+ chomp @auths;
+}
+else {
+# Get the list from the processor. This requires the processor module to
+# support get_returns.
+ my $transaction = new Business::OnlinePayment ( $processor, @bop_options );
+ if(! $transaction->can('get_returns')) {
+ die "'$processor' does not provide an automated void list.";
+ }
+ my @local = localtime;
+# Start and end dates for this can be set via -s and -e. If they're not,
+# end defaults to midnight today and start defaults to one day before end.
+ my $end = defined($opt{'e'}) ?
+ str2time($opt{'e'}) : timelocal(0, 0, 0, @local[3,4,5]);
+ my $start = defined($opt{'s'}) ?
+ str2time($opt{'s'}) : $end - 86400;
+ die "Invalid date range: '$start'-'$end'" if not ($start and $end);
+ $transaction->content (
+ login => $login,
+ password => $password,
+ start => time2str("%Y-%m-%d",$start),
+ end => time2str("%Y-%m-%d",$end),
+ );
+ @auths = $transaction->get_returns;
+}
+
+$opt{'r'} ||= 'freeside-void-payments';
+my $success = 0;
+my $notfound = 0;
+my $canceled = 0;
+print "Voiding ".scalar(@auths)." transactions:\n" if $opt{'v'};
+foreach my $authnum (@auths) {
+ my $paybatch = $gatewaynum . $processor . ':' . $authnum;
+ my $cust_pay = qsearchs('cust_pay', { paybatch => $paybatch } );
+ my $error;
+ my $cancel_error;
+ if($cust_pay) {
+ $error = $cust_pay->void($opt{'r'});
+ $success++ if not $error;
+ if($opt{'X'} and not $error) {
+ $cancel_error = join(';',$cust_pay->cust_main->cancel('reason' => $opt{'X'}));
+ $canceled++ if !$cancel_error;
+ }
+ }
+ else {
+ my $cpv = qsearchs('cust_pay_void', { paybatch => $paybatch });
+ if($cpv) {
+ $error = 'already voided '.time2str('%Y-%m-%d', $cpv->void_date) .
+ ' by ' . $cpv->otaker;
+ }
+ else {
+ $error = 'not found';
+ $notfound++;
+ }
+ }
+ if($opt{'v'}) {
+ print $authnum;
+ if($error) {
+ print "\t($error)";
+ }
+ elsif($opt{'X'}) {
+ print "\t(canceled service)" if !$cancel_error;
+ print "\n\t(cancellation failed: $cancel_error)" if $cancel_error;
+ }
+ print "\n";
+ }
+}
+
+if($opt{'v'}) {
+ print scalar(@auths)." transactions: $success voided, $notfound not found\n";
+ print "$canceled customer".($canceled == 1 ? '' : 's')." canceled\n" if $opt{'X'};
+}
+
+sub usage {
+ die "Usage:\n\n freeside-void-payments [ options ] user
+
+ options:
+ -a agentnum use agentnum's gateway information
+ -g gatewaynum use gatewaynum
+ -f file read transaction numbers from file
+ -c use ECHECK gateway instead of CARD
+ -r reason specify void reason (as a string)
+ -v be verbose
+ -s start-date
+ -e end-date limit by payment return date
+ -X reasonnum cancel customers whose payments are voided
+ (specify cancellation reason number)
+
+";
+}
+
+__END__
+
+# Documentation
+
+=head1 NAME
+
+freeside-void-payments - Automatically void a list of returned payments.
+
+=head1 SYNOPSIS
+
+ freeside-void-payments [ -f file | [ -s start-date ] [ -e end-date ] ]
+ [ -r 'reason' ]
+ [ -g gatewaynum | -a agentnum ]
+ [ -c ] [ -v ]
+ [ -X reasonnum ]
+ user
+
+=head1 DESCRIPTION
+
+=pod
+
+Voids payments that were returned by the payment processor. Can be
+run periodically from crontab or manually after receiving a list of
+returned payments. Normally this is a meaningful operation only for
+electronic checks.
+
+This script voids payments based on the combination of gateway (see
+L<FS::payment_gateway>) and authorization number, since this is
+generally how the processor will identify them later.
+
+ -f: Read the list of authorization numbers from the specified file.
+ If they are not from the default payment gateway, -g or -a
+ must be given to identify the gateway.
+
+ If -f is not given, the script will attempt to contact the gateway
+ and download a list of returned transactions. To support this,
+ the Business::OnlinePayment module for the processor must implement
+ the get_returns() method. For an example, see
+ Business::OnlinePayment::WesternACH.
+
+ -s, -e: Specify the starting and ending dates for the void list.
+ This has no effect if -f is given. The end date defaults to
+ today, and the start date defaults to one day before the end date.
+
+ -r: The reason for voiding the payments, to be stored in the database.
+
+ -g: The FS::payment_gateway number for the gateway that handled
+ these payments. If -f is not given, this determines which
+ gateway will be contacted. This overrides -a.
+
+ -a: The agentnum whose default gateway will be used. If neither -a
+ nor -g is given, the system default gateway will be used.
+
+ -c: Use the default gateway for check transactions rather than
+ credit cards.
+
+ -v: Be verbose.
+
+ -X: Automatically cancel all packages belonging to customers whose
+ payments were returned. Requires a cancellation reasonnum
+ (from FS::reason).
+
+=head1 EXAMPLE
+
+Given 'returns.txt', which contains one authorization number on each
+line, provided by your default e-check processor:
+
+ freeside-void-payments -f returns.txt -c -r 'Returned check'
+
+If your default processor is Western ACH, which supports automated
+returns processing, this voids all returned payments since 2009-06-01:
+
+ freeside-void-payments -r 'Returned check' -s 2009-06-01
+
+This, in your crontab, will void returned payments for the last
+day at 8:30 every morning:
+
+ 30 8 * * * /usr/local/bin/freeside-void-payments -r 'Returned check'
+
+=head1 BUGS
+
+Most payment gateways don't support it.
+
+=head1 SEE ALSO
+
+L<Business::OnlinePayment>, L<FS::cust_pay>
+
+=cut
diff --git a/FS/bin/freeside-yori b/FS/bin/freeside-yori
new file mode 100644
index 0000000..d113799
--- /dev/null
+++ b/FS/bin/freeside-yori
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use FS::Yori qw(reports report);
+
+if ( @ARGV ) {
+ while ( my $report = shift ) {
+ print report($report). "\n";
+ }
+} else {
+ print join("\n", reports() ). "\n";
+}
+
+
+1;
diff --git a/FS/t/AccessRight.t b/FS/t/AccessRight.t
new file mode 100644
index 0000000..a966842
--- /dev/null
+++ b/FS/t/AccessRight.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::AccessRight;
+$loaded=1;
+print "ok 1\n";
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/ClientAPI_SessionCache.t b/FS/t/ClientAPI_SessionCache.t
new file mode 100644
index 0000000..605803e
--- /dev/null
+++ b/FS/t/ClientAPI_SessionCache.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::ClientAPI_SessionCache;
+$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/ConfDefaults.t b/FS/t/ConfDefaults.t
new file mode 100644
index 0000000..433555a
--- /dev/null
+++ b/FS/t/ConfDefaults.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::ConfDefaults;
+$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/Cron-backup.t b/FS/t/Cron-backup.t
new file mode 100644
index 0000000..847d41a
--- /dev/null
+++ b/FS/t/Cron-backup.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Cron::backup;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Cron-bill.t b/FS/t/Cron-bill.t
new file mode 100644
index 0000000..42c7b4f
--- /dev/null
+++ b/FS/t/Cron-bill.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Cron::bill;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Cron-vacuum.t b/FS/t/Cron-vacuum.t
new file mode 100644
index 0000000..eaa6b76
--- /dev/null
+++ b/FS/t/Cron-vacuum.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Cron::vacuum;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Daemon.t b/FS/t/Daemon.t
new file mode 100644
index 0000000..24893fd
--- /dev/null
+++ b/FS/t/Daemon.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Daemon;
+$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-FCC_477.t b/FS/t/Report-FCC_477.t
new file mode 100644
index 0000000..b7359aa
--- /dev/null
+++ b/FS/t/Report-FCC_477.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Report::FCC_477;
+$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/access_group.t b/FS/t/access_group.t
new file mode 100644
index 0000000..be14109
--- /dev/null
+++ b/FS/t/access_group.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_group;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/access_groupagent.t b/FS/t/access_groupagent.t
new file mode 100644
index 0000000..aff1f25
--- /dev/null
+++ b/FS/t/access_groupagent.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_groupagent;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/access_right.t b/FS/t/access_right.t
new file mode 100644
index 0000000..66cd362
--- /dev/null
+++ b/FS/t/access_right.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_right;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/access_user.t b/FS/t/access_user.t
new file mode 100644
index 0000000..cab679d
--- /dev/null
+++ b/FS/t/access_user.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_user;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/access_user_pref.t b/FS/t/access_user_pref.t
new file mode 100644
index 0000000..2822098
--- /dev/null
+++ b/FS/t/access_user_pref.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_user_pref;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/access_usergroup.t b/FS/t/access_usergroup.t
new file mode 100644
index 0000000..383a7cf
--- /dev/null
+++ b/FS/t/access_usergroup.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_usergroup;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/acct_rt_transaction.t b/FS/t/acct_rt_transaction.t
new file mode 100644
index 0000000..552bdc8
--- /dev/null
+++ b/FS/t/acct_rt_transaction.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::acct_rt_transaction;
+$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/addr_block.t b/FS/t/addr_block.t
new file mode 100644
index 0000000..4f49a44
--- /dev/null
+++ b/FS/t/addr_block.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::addr_block;
+$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_payment_gateway.t b/FS/t/agent_payment_gateway.t
new file mode 100644
index 0000000..af78a9a
--- /dev/null
+++ b/FS/t/agent_payment_gateway.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::agent_payment_gateway;
+$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/banned_pay.t b/FS/t/banned_pay.t
new file mode 100644
index 0000000..bef1ff2
--- /dev/null
+++ b/FS/t/banned_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::banned_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/category_Common.t b/FS/t/category_Common.t
new file mode 100644
index 0000000..b8bd9e2
--- /dev/null
+++ b/FS/t/category_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::category_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr.t b/FS/t/cdr.t
new file mode 100644
index 0000000..1d1f3eb
--- /dev/null
+++ b/FS/t/cdr.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr_batch.t b/FS/t/cdr_batch.t
new file mode 100644
index 0000000..673a7bd
--- /dev/null
+++ b/FS/t/cdr_batch.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_batch;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr_calltype.t b/FS/t/cdr_calltype.t
new file mode 100644
index 0000000..d4e1394
--- /dev/null
+++ b/FS/t/cdr_calltype.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_calltype;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr_carrier.t b/FS/t/cdr_carrier.t
new file mode 100644
index 0000000..1e21615
--- /dev/null
+++ b/FS/t/cdr_carrier.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_carrier;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr_termination.t b/FS/t/cdr_termination.t
new file mode 100644
index 0000000..7167bf2
--- /dev/null
+++ b/FS/t/cdr_termination.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_termination;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cdr_type.t b/FS/t/cdr_type.t
new file mode 100644
index 0000000..9dff15a
--- /dev/null
+++ b/FS/t/cdr_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cgp_rule.t b/FS/t/cgp_rule.t
new file mode 100644
index 0000000..421b922
--- /dev/null
+++ b/FS/t/cgp_rule.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cgp_rule;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cgp_rule_action.t b/FS/t/cgp_rule_action.t
new file mode 100644
index 0000000..f0548dc
--- /dev/null
+++ b/FS/t/cgp_rule_action.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cgp_rule_action;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cgp_rule_condition.t b/FS/t/cgp_rule_condition.t
new file mode 100644
index 0000000..49d8c35
--- /dev/null
+++ b/FS/t/cgp_rule_condition.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cgp_rule_condition;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/class_Common.t b/FS/t/class_Common.t
new file mode 100644
index 0000000..9ac809b
--- /dev/null
+++ b/FS/t/class_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::class_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/clientapi_session.t b/FS/t/clientapi_session.t
new file mode 100644
index 0000000..a6414c3
--- /dev/null
+++ b/FS/t/clientapi_session.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::clientapi_session;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/clientapi_session_field.t b/FS/t/clientapi_session_field.t
new file mode 100644
index 0000000..a9d4fa9
--- /dev/null
+++ b/FS/t/clientapi_session_field.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::clientapi_session_field;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/conf.t b/FS/t/conf.t
new file mode 100644
index 0000000..5e52079
--- /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/contact.t b/FS/t/contact.t
new file mode 100644
index 0000000..bf22bd1
--- /dev/null
+++ b/FS/t/contact.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/contact_email.t b/FS/t/contact_email.t
new file mode 100644
index 0000000..1cd13b6
--- /dev/null
+++ b/FS/t/contact_email.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact_email;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/contact_phone.t b/FS/t/contact_phone.t
new file mode 100644
index 0000000..493ced7
--- /dev/null
+++ b/FS/t/contact_phone.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact_phone;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_attachment.t b/FS/t/cust_attachment.t
new file mode 100644
index 0000000..5986204
--- /dev/null
+++ b/FS/t/cust_attachment.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_attachment;
+$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_ApplicationCommon.t b/FS/t/cust_bill_ApplicationCommon.t
new file mode 100644
index 0000000..fa03d34
--- /dev/null
+++ b/FS/t/cust_bill_ApplicationCommon.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_ApplicationCommon;
+$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_pay_batch.t b/FS/t/cust_bill_pay_batch.t
new file mode 100644
index 0000000..bc3a827
--- /dev/null
+++ b/FS/t/cust_bill_pay_batch.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pay_batch;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pay_pkg.t b/FS/t/cust_bill_pay_pkg.t
new file mode 100644
index 0000000..b8fcddb
--- /dev/null
+++ b/FS/t/cust_bill_pay_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pay_pkg;
+$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_bill_pkg_discount.t b/FS/t/cust_bill_pkg_discount.t
new file mode 100644
index 0000000..74923e1
--- /dev/null
+++ b/FS/t/cust_bill_pkg_discount.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_discount;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pkg_display.t b/FS/t/cust_bill_pkg_display.t
new file mode 100644
index 0000000..d84dbdf
--- /dev/null
+++ b/FS/t/cust_bill_pkg_display.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_display;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pkg_tax_location.t b/FS/t/cust_bill_pkg_tax_location.t
new file mode 100644
index 0000000..087b59a
--- /dev/null
+++ b/FS/t/cust_bill_pkg_tax_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_tax_location;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pkg_tax_rate_location.t b/FS/t/cust_bill_pkg_tax_rate_location.t
new file mode 100644
index 0000000..3250db9
--- /dev/null
+++ b/FS/t/cust_bill_pkg_tax_rate_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_tax_rate_location;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_category.t b/FS/t/cust_category.t
new file mode 100644
index 0000000..8cb0cd0
--- /dev/null
+++ b/FS/t/cust_category.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_category;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_class.t b/FS/t/cust_class.t
new file mode 100644
index 0000000..ef7e82f
--- /dev/null
+++ b/FS/t/cust_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_class;
+$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_bill_pkg.t b/FS/t/cust_credit_bill_pkg.t
new file mode 100644
index 0000000..4eb84c3
--- /dev/null
+++ b/FS/t/cust_credit_bill_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_credit_bill_pkg;
+$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_event.t b/FS/t/cust_event.t
new file mode 100644
index 0000000..7812c5b
--- /dev/null
+++ b/FS/t/cust_event.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_event;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_location.t b/FS/t/cust_location.t
new file mode 100644
index 0000000..e98372d
--- /dev/null
+++ b/FS/t/cust_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_location;
+$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_Mixin.t b/FS/t/cust_main_Mixin.t
new file mode 100644
index 0000000..c8b9291
--- /dev/null
+++ b/FS/t/cust_main_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_Mixin;
+$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_exemption.t b/FS/t/cust_main_exemption.t
new file mode 100644
index 0000000..fec6d19
--- /dev/null
+++ b/FS/t/cust_main_exemption.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_exemption;
+$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_main_note.t b/FS/t/cust_main_note.t
new file mode 100644
index 0000000..41a7bac
--- /dev/null
+++ b/FS/t/cust_main_note.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_note;
+$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_pending.t b/FS/t/cust_pay_pending.t
new file mode 100644
index 0000000..9ab2b5e
--- /dev/null
+++ b/FS/t/cust_pay_pending.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pay_pending;
+$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_pkg_detail.t b/FS/t/cust_pkg_detail.t
new file mode 100644
index 0000000..15dec00
--- /dev/null
+++ b/FS/t/cust_pkg_detail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg_detail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pkg_discount.t b/FS/t/cust_pkg_discount.t
new file mode 100644
index 0000000..51108a7
--- /dev/null
+++ b/FS/t/cust_pkg_discount.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg_discount;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pkg_option.t b/FS/t/cust_pkg_option.t
new file mode 100644
index 0000000..12314bf
--- /dev/null
+++ b/FS/t/cust_pkg_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pkg_reason.t b/FS/t/cust_pkg_reason.t
new file mode 100644
index 0000000..2f0a4fa
--- /dev/null
+++ b/FS/t/cust_pkg_reason.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg_reason;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_recon.t b/FS/t/cust_recon.t
new file mode 100644
index 0000000..3724736
--- /dev/null
+++ b/FS/t/cust_recon.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_recon;
+$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_statement.t b/FS/t/cust_statement.t
new file mode 100644
index 0000000..c57d94c
--- /dev/null
+++ b/FS/t/cust_statement.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_statement;
+$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_svc_option.t b/FS/t/cust_svc_option.t
new file mode 100644
index 0000000..eeaa170
--- /dev/null
+++ b/FS/t/cust_svc_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_svc_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_tax_adjustment.t b/FS/t/cust_tax_adjustment.t
new file mode 100644
index 0000000..cc5719a
--- /dev/null
+++ b/FS/t/cust_tax_adjustment.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_tax_adjustment;
+$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/cust_tax_exempt_pkg.t b/FS/t/cust_tax_exempt_pkg.t
new file mode 100644
index 0000000..099a0ce
--- /dev/null
+++ b/FS/t/cust_tax_exempt_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_tax_exempt_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_tax_location.t b/FS/t/cust_tax_location.t
new file mode 100644
index 0000000..83a1362
--- /dev/null
+++ b/FS/t/cust_tax_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_tax_location;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/discount.t b/FS/t/discount.t
new file mode 100644
index 0000000..d221e8e
--- /dev/null
+++ b/FS/t/discount.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::discount;
+$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_device.t b/FS/t/export_device.t
new file mode 100644
index 0000000..4688326
--- /dev/null
+++ b/FS/t/export_device.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::export_device;
+$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/h_Common.t b/FS/t/h_Common.t
new file mode 100644
index 0000000..174bb99
--- /dev/null
+++ b/FS/t/h_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_bill.t b/FS/t/h_cust_bill.t
new file mode 100644
index 0000000..ceccb2a
--- /dev/null
+++ b/FS/t/h_cust_bill.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_bill;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_credit.t b/FS/t/h_cust_credit.t
new file mode 100644
index 0000000..e20f476
--- /dev/null
+++ b/FS/t/h_cust_credit.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_credit;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_pay.t b/FS/t/h_cust_pay.t
new file mode 100644
index 0000000..6a3fe95
--- /dev/null
+++ b/FS/t/h_cust_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_pkg.t b/FS/t/h_cust_pkg.t
new file mode 100644
index 0000000..16a8a5d
--- /dev/null
+++ b/FS/t/h_cust_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_pkg_reason.t b/FS/t/h_cust_pkg_reason.t
new file mode 100644
index 0000000..b8dae92
--- /dev/null
+++ b/FS/t/h_cust_pkg_reason.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_pkg_reason;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_svc.t b/FS/t/h_cust_svc.t
new file mode 100644
index 0000000..a7dabbe
--- /dev/null
+++ b/FS/t/h_cust_svc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_cust_tax_exempt.t b/FS/t/h_cust_tax_exempt.t
new file mode 100644
index 0000000..432238a
--- /dev/null
+++ b/FS/t/h_cust_tax_exempt.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_cust_tax_exempt;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_domain_record.t b/FS/t/h_domain_record.t
new file mode 100644
index 0000000..f48e72e
--- /dev/null
+++ b/FS/t/h_domain_record.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_domain_record;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_acct.t b/FS/t/h_svc_acct.t
new file mode 100644
index 0000000..9c94d08
--- /dev/null
+++ b/FS/t/h_svc_acct.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_acct;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_broadband.t b/FS/t/h_svc_broadband.t
new file mode 100644
index 0000000..b8e5c7c
--- /dev/null
+++ b/FS/t/h_svc_broadband.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_broadband;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_domain.t b/FS/t/h_svc_domain.t
new file mode 100644
index 0000000..87d2a09
--- /dev/null
+++ b/FS/t/h_svc_domain.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_domain;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_external.t b/FS/t/h_svc_external.t
new file mode 100644
index 0000000..5248f87
--- /dev/null
+++ b/FS/t/h_svc_external.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_external;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_forward.t b/FS/t/h_svc_forward.t
new file mode 100644
index 0000000..64731d5
--- /dev/null
+++ b/FS/t/h_svc_forward.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_forward;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_mailinglist.t b/FS/t/h_svc_mailinglist.t
new file mode 100644
index 0000000..d75575a
--- /dev/null
+++ b/FS/t/h_svc_mailinglist.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_mailinglist;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_pbx.t b/FS/t/h_svc_pbx.t
new file mode 100644
index 0000000..8b30f52
--- /dev/null
+++ b/FS/t/h_svc_pbx.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_pbx;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/h_svc_www.t b/FS/t/h_svc_www.t
new file mode 100644
index 0000000..07558ce
--- /dev/null
+++ b/FS/t/h_svc_www.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_www;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/inventory_class.t b/FS/t/inventory_class.t
new file mode 100644
index 0000000..80b2fa2
--- /dev/null
+++ b/FS/t/inventory_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::inventory_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/inventory_item.t b/FS/t/inventory_item.t
new file mode 100644
index 0000000..8ce9d67
--- /dev/null
+++ b/FS/t/inventory_item.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::inventory_item;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/location_Mixin.t b/FS/t/location_Mixin.t
new file mode 100644
index 0000000..b6a9bf2
--- /dev/null
+++ b/FS/t/location_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::location_Mixin;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/mailinglist.t b/FS/t/mailinglist.t
new file mode 100644
index 0000000..45b7dd5
--- /dev/null
+++ b/FS/t/mailinglist.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::mailinglist;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/mailinglistmember.t b/FS/t/mailinglistmember.t
new file mode 100644
index 0000000..1ceb2f5
--- /dev/null
+++ b/FS/t/mailinglistmember.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::mailinglistmember;
+$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/option_Common.t b/FS/t/option_Common.t
new file mode 100644
index 0000000..ad26141
--- /dev/null
+++ b/FS/t/option_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::option_Common;
+$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_device.t b/FS/t/part_device.t
new file mode 100644
index 0000000..5696868
--- /dev/null
+++ b/FS/t/part_device.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event-Action.t b/FS/t/part_event-Action.t
new file mode 100644
index 0000000..a665277
--- /dev/null
+++ b/FS/t/part_event-Action.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event::Action;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event-Condition.t b/FS/t/part_event-Condition.t
new file mode 100644
index 0000000..c44a438
--- /dev/null
+++ b/FS/t/part_event-Condition.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event::Condition;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event.t b/FS/t/part_event.t
new file mode 100644
index 0000000..027b20c
--- /dev/null
+++ b/FS/t/part_event.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event_condition.t b/FS/t/part_event_condition.t
new file mode 100644
index 0000000..fa5a05c
--- /dev/null
+++ b/FS/t/part_event_condition.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event_condition;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event_condition_option.t b/FS/t/part_event_condition_option.t
new file mode 100644
index 0000000..492fc82
--- /dev/null
+++ b/FS/t/part_event_condition_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event_condition_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event_condition_option_option.t b/FS/t/part_event_condition_option_option.t
new file mode 100644
index 0000000..f714011
--- /dev/null
+++ b/FS/t/part_event_condition_option_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event_condition_option_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_event_option.t b/FS/t/part_event_option.t
new file mode 100644
index 0000000..546a78f
--- /dev/null
+++ b/FS/t/part_event_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_event_option;
+$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-radiator.t b/FS/t/part_export-radiator.t
new file mode 100644
index 0000000..546e9de
--- /dev/null
+++ b/FS/t/part_export-radiator.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::radiator;
+$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-flat.t b/FS/t/part_pkg-flat.t
new file mode 100644
index 0000000..3eee7a7
--- /dev/null
+++ b/FS/t/part_pkg-flat.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::flat;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-flat_comission.t b/FS/t/part_pkg-flat_comission.t
new file mode 100644
index 0000000..fefa57e
--- /dev/null
+++ b/FS/t/part_pkg-flat_comission.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::flat_comission;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-flat_comission_cust.t b/FS/t/part_pkg-flat_comission_cust.t
new file mode 100644
index 0000000..05d3ac4
--- /dev/null
+++ b/FS/t/part_pkg-flat_comission_cust.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::flat_comission_cust;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-flat_comission_pkg.t b/FS/t/part_pkg-flat_comission_pkg.t
new file mode 100644
index 0000000..851b58d
--- /dev/null
+++ b/FS/t/part_pkg-flat_comission_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::flat_comission_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-flat_delayed.t b/FS/t/part_pkg-flat_delayed.t
new file mode 100644
index 0000000..ed63846
--- /dev/null
+++ b/FS/t/part_pkg-flat_delayed.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::flat_delayed;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-prorate.t b/FS/t/part_pkg-prorate.t
new file mode 100644
index 0000000..d32b1c0
--- /dev/null
+++ b/FS/t/part_pkg-prorate.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::prorate;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-sesmon_hour.t b/FS/t/part_pkg-sesmon_hour.t
new file mode 100644
index 0000000..4f02cfc
--- /dev/null
+++ b/FS/t/part_pkg-sesmon_hour.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::sesmon_hour;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-sesmon_minute.t b/FS/t/part_pkg-sesmon_minute.t
new file mode 100644
index 0000000..6ceaa3c
--- /dev/null
+++ b/FS/t/part_pkg-sesmon_minute.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::sesmon_minute;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-sql_external.t b/FS/t/part_pkg-sql_external.t
new file mode 100644
index 0000000..366ed01
--- /dev/null
+++ b/FS/t/part_pkg-sql_external.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::sql_external;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-sql_generic.t b/FS/t/part_pkg-sql_generic.t
new file mode 100644
index 0000000..299a7c6
--- /dev/null
+++ b/FS/t/part_pkg-sql_generic.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::sql_generic;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-sqlradacct_hour.t b/FS/t/part_pkg-sqlradacct_hour.t
new file mode 100644
index 0000000..2a4ed79
--- /dev/null
+++ b/FS/t/part_pkg-sqlradacct_hour.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::sqlradacct_hour;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-subscription.t b/FS/t/part_pkg-subscription.t
new file mode 100644
index 0000000..10b4479
--- /dev/null
+++ b/FS/t/part_pkg-subscription.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::subscription;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-voip_cdr.t b/FS/t/part_pkg-voip_cdr.t
new file mode 100644
index 0000000..2d988a3
--- /dev/null
+++ b/FS/t/part_pkg-voip_cdr.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::voip_cdr;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg-voip_sqlradacct.t b/FS/t/part_pkg-voip_sqlradacct.t
new file mode 100644
index 0000000..8d54204
--- /dev/null
+++ b/FS/t/part_pkg-voip_sqlradacct.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg::voip_sqlradacct;
+$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_pkg_link.t b/FS/t/part_pkg_link.t
new file mode 100644
index 0000000..f5ada88
--- /dev/null
+++ b/FS/t/part_pkg_link.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_link;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_option.t b/FS/t/part_pkg_option.t
new file mode 100644
index 0000000..6239b2d
--- /dev/null
+++ b/FS/t/part_pkg_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_report_option.t b/FS/t/part_pkg_report_option.t
new file mode 100644
index 0000000..622bb38
--- /dev/null
+++ b/FS/t/part_pkg_report_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_report_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_taxclass.t b/FS/t/part_pkg_taxclass.t
new file mode 100644
index 0000000..bbe4073
--- /dev/null
+++ b/FS/t/part_pkg_taxclass.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_taxclass;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_taxoverride.t b/FS/t/part_pkg_taxoverride.t
new file mode 100644
index 0000000..d3b385d
--- /dev/null
+++ b/FS/t/part_pkg_taxoverride.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_taxoverride;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_taxproduct.t b/FS/t/part_pkg_taxproduct.t
new file mode 100644
index 0000000..a0aaa1d
--- /dev/null
+++ b/FS/t/part_pkg_taxproduct.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_taxproduct;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg_taxrate.t b/FS/t/part_pkg_taxrate.t
new file mode 100644
index 0000000..6e5bee0
--- /dev/null
+++ b/FS/t/part_pkg_taxrate.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_taxrate;
+$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/pay_batch.t b/FS/t/pay_batch.t
new file mode 100644
index 0000000..c43133d
--- /dev/null
+++ b/FS/t/pay_batch.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pay_batch;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/payby.t b/FS/t/payby.t
new file mode 100644
index 0000000..7430bc8
--- /dev/null
+++ b/FS/t/payby.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::payby;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/payinfo_Mixin.t b/FS/t/payinfo_Mixin.t
new file mode 100644
index 0000000..3567c8e
--- /dev/null
+++ b/FS/t/payinfo_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::payinfo_Mixin;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/payment_gateway.t b/FS/t/payment_gateway.t
new file mode 100644
index 0000000..4bcc781
--- /dev/null
+++ b/FS/t/payment_gateway.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::payment_gateway;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/payment_gateway_option.t b/FS/t/payment_gateway_option.t
new file mode 100644
index 0000000..19e6451
--- /dev/null
+++ b/FS/t/payment_gateway_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::payment_gateway_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/phone_avail.t b/FS/t/phone_avail.t
new file mode 100644
index 0000000..67f7e9a
--- /dev/null
+++ b/FS/t/phone_avail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::phone_avail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/phone_device.t b/FS/t/phone_device.t
new file mode 100644
index 0000000..3070314
--- /dev/null
+++ b/FS/t/phone_device.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::phone_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/phone_type.t b/FS/t/phone_type.t
new file mode 100644
index 0000000..b6bc793
--- /dev/null
+++ b/FS/t/phone_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::phone_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/pkg_category.t b/FS/t/pkg_category.t
new file mode 100644
index 0000000..ee256d5
--- /dev/null
+++ b/FS/t/pkg_category.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pkg_category;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/pkg_class.t b/FS/t/pkg_class.t
new file mode 100644
index 0000000..fb3774f
--- /dev/null
+++ b/FS/t/pkg_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pkg_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/pkg_referral.t b/FS/t/pkg_referral.t
new file mode 100644
index 0000000..ff047ba
--- /dev/null
+++ b/FS/t/pkg_referral.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pkg_referral;
+$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/prospect_main.t b/FS/t/prospect_main.t
new file mode 100644
index 0000000..045c2f0
--- /dev/null
+++ b/FS/t/prospect_main.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::prospect_main;
+$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/rate.t b/FS/t/rate.t
new file mode 100644
index 0000000..ae9c8bb
--- /dev/null
+++ b/FS/t/rate.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::rate;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/rate_detail.t b/FS/t/rate_detail.t
new file mode 100644
index 0000000..163972e
--- /dev/null
+++ b/FS/t/rate_detail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::rate_detail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/rate_prefix.t b/FS/t/rate_prefix.t
new file mode 100644
index 0000000..d4bd513
--- /dev/null
+++ b/FS/t/rate_prefix.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::rate_prefix;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/rate_region.t b/FS/t/rate_region.t
new file mode 100644
index 0000000..6e0db8f
--- /dev/null
+++ b/FS/t/rate_region.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::rate_region;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/reason.t b/FS/t/reason.t
new file mode 100644
index 0000000..d5e4dc9
--- /dev/null
+++ b/FS/t/reason.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::reason;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/reason_type.t b/FS/t/reason_type.t
new file mode 100644
index 0000000..279d5b9
--- /dev/null
+++ b/FS/t/reason_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::reason_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/reg_code.t b/FS/t/reg_code.t
new file mode 100644
index 0000000..4b95990
--- /dev/null
+++ b/FS/t/reg_code.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::reg_code;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/reg_code_pkg.t b/FS/t/reg_code_pkg.t
new file mode 100644
index 0000000..7f89ffa
--- /dev/null
+++ b/FS/t/reg_code_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::reg_code_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/registrar.t b/FS/t/registrar.t
new file mode 100644
index 0000000..a6ba134
--- /dev/null
+++ b/FS/t/registrar.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::registrar;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/router.t b/FS/t/router.t
new file mode 100644
index 0000000..fe171b3
--- /dev/null
+++ b/FS/t/router.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::router;
+$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_CGPRule_Mixin.t b/FS/t/svc_CGPRule_Mixin.t
new file mode 100644
index 0000000..fcccd12
--- /dev/null
+++ b/FS/t/svc_CGPRule_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_CGPRule_Mixin;
+$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_Domain_Mixin.t b/FS/t/svc_Domain_Mixin.t
new file mode 100644
index 0000000..261af75
--- /dev/null
+++ b/FS/t/svc_Domain_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_Domain_Mixin;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_External_Common.t b/FS/t/svc_External_Common.t
new file mode 100644
index 0000000..a0b2ea2
--- /dev/null
+++ b/FS/t/svc_External_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_External_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_Parent_Mixin.t b/FS/t/svc_Parent_Mixin.t
new file mode 100644
index 0000000..ed9923f
--- /dev/null
+++ b/FS/t/svc_Parent_Mixin.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_Parent_Mixin;
+$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_mailinglist.t b/FS/t/svc_mailinglist.t
new file mode 100644
index 0000000..73896da
--- /dev/null
+++ b/FS/t/svc_mailinglist.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_mailinglist;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_pbx.t b/FS/t/svc_pbx.t
new file mode 100644
index 0000000..2a41372
--- /dev/null
+++ b/FS/t/svc_pbx.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_pbx;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_phone.t b/FS/t/svc_phone.t
new file mode 100644
index 0000000..15b9ca2
--- /dev/null
+++ b/FS/t/svc_phone.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_phone;
+$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/tax_class.t b/FS/t/tax_class.t
new file mode 100644
index 0000000..ddd8d9f
--- /dev/null
+++ b/FS/t/tax_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::tax_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/tax_rate.t b/FS/t/tax_rate.t
new file mode 100644
index 0000000..d498812
--- /dev/null
+++ b/FS/t/tax_rate.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::tax_rate;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/tax_rate_location.t b/FS/t/tax_rate_location.t
new file mode 100644
index 0000000..f4ee910
--- /dev/null
+++ b/FS/t/tax_rate_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::tax_rate_location;
+$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/FS/t/usage_class.t b/FS/t/usage_class.t
new file mode 100644
index 0000000..64fe98e
--- /dev/null
+++ b/FS/t/usage_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::usage_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..4ea1678
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,3 @@
+See:
+
+http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation#Installation_and_upgrades
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a06e0f3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,433 @@
+#!/usr/bin/make
+
+#solaris and perhaps other very weirdass /bin/sh
+#SHELL="/bin/ksh"
+
+DB_TYPE = Pg
+#DB_TYPE = mysql
+
+DB_USER = freeside
+DB_PASSWORD=
+
+DATASOURCE = DBI:${DB_TYPE}:dbname=freeside
+
+#changable now (some things which should go to the others still go to CONF)
+FREESIDE_CONF = /usr/local/etc/freeside
+FREESIDE_LOG = /usr/local/etc/freeside
+FREESIDE_LOCK = /usr/local/etc/freeside
+FREESIDE_CACHE = /usr/local/etc/freeside
+FREESIDE_EXPORT = /usr/local/etc/freeside
+
+MASON_HANDLER = ${FREESIDE_CONF}/handler.pl
+MASONDATA = ${FREESIDE_CACHE}/masondata
+
+#where to put the default configuraiton used by freeside-setup to initialize
+#a new database (not used after that). primarily of interest to distro
+#package maintainers
+DIST_CONF = ${FREESIDE_CONF}/default_conf
+
+#mod_perl v1
+#APACHE_VERSION = 1
+#mod_perl v2 prereleases up to and including 1.999_21
+#APACHE_VERSON = 1.99
+#mod_perl v2 proper and prereleases 1.999_22 and after
+APACHE_VERSION = 2
+
+#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
+INIT_INSTALL = /usr/sbin/update-rc.d freeside defaults 21 20
+#redhat, fedora
+#INIT_INSTALL = /sbin/chkconfig freeside on
+#not necessary (freebsd)
+#INIT_INSTALL = /usr/bin/true
+
+#deb, suse
+#HTTPD_RESTART = /etc/init.d/apache restart
+#deb w/apache2
+HTTPD_RESTART = /etc/init.d/apache2 restart
+#redhat, fedora, mandrake
+#HTTPD_RESTART = /etc/init.d/httpd restart
+#freebsd
+#HTTPD_RESTART = /usr/local/etc/rc.d/apache.sh stop || true; 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
+
+#(an include directory, not a file, "Include /etc/apache/conf.d" in httpd.conf)
+#deb (3.1+), apache2
+APACHE_CONF = /etc/apache2/conf.d
+
+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
+
+SELFSERVICE_USER = fs_selfservice
+#never run on the same machine in production!!!
+SELFSERVICE_MACHINES =
+# 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
+FREESIDE_URL = "http://localhost/freeside/"
+
+#for now, same db as specified in DATASOURCE... eventually, otherwise?
+RT_DB_DATABASE = freeside
+
+# for cvs-upgrade-deploy target, the username who checked out the CVS copy.
+CVS_USER = ivan
+
+# for auto-version updates, so we can "make release" more things automatically
+RPM_SPECFILE = rpm/freeside.spec
+
+#---
+
+#rt/config.layout.in
+RT_PATH = /opt/rt3
+
+#only used for dev kludge now, not a big deal
+FREESIDE_PATH = `pwd`
+PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.10.1/
+
+VERSION=2.1.0
+TAG=freeside_2_1_0
+
+DEBVERSION = `echo ${VERSION} | perl -pe 's/(\d)([a-z])/\1~\2/'`-1
+
+TEXMFHOME := "\$$TEXMFHOME"
+
+help:
+ @echo "supported targets:"
+ @echo " create-database create-config"
+ @echo " install deploy"
+ @echo " cvs-upgrade-deploy"
+ @echo " configure-rt create-rt"
+ @echo " clean help"
+ @echo
+ @echo " install-docs install-perl-modules"
+ @echo " install-init install-apache"
+ @echo " install-rt install-texmf"
+ @echo " install-selfservice update-selfservice"
+ @echo
+ @echo " dev dev-docs dev-perl-modules"
+ @echo
+ @echo " masondocs alldocs docs"
+ @echo " wikiman"
+ @echo " perl-modules"
+ #@echo
+ #@echo " upload-docs release"
+
+
+masondocs: httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/*
+ rm -rf masondocs
+ cp -pr httemplate masondocs
+ touch masondocs
+
+alldocs: masondocs
+
+docs:
+ make masondocs
+
+wikiman:
+ chmod a+rx ./bin/pod2x
+ ./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 masondocs ${FREESIDE_DOCUMENT_ROOT}
+ chown -R freeside:freeside ${FREESIDE_DOCUMENT_ROOT}
+ cp htetc/handler.pl ${MASON_HANDLER}
+ cp htetc/htpasswd.logout ${FREESIDE_CONF}
+ [ ! -e ${MASONDATA} ] && mkdir ${MASONDATA} || true
+ chown -R freeside ${MASONDATA}
+
+dev-docs:
+ [ -e ${FREESIDE_DOCUMENT_ROOT} ] && mv ${FREESIDE_DOCUMENT_ROOT} ${FREESIDE_DOCUMENT_ROOT}.`date +%Y%m%d%H%M%S` || true
+ ln -s ${FREESIDE_PATH}/httemplate ${FREESIDE_DOCUMENT_ROOT}
+ cp htetc/handler.pl ${MASON_HANDLER}
+ perl -p -i -e "\
+ s'###use Module::Refresh;###'use Module::Refresh;'; \
+ s'###Module::Refresh->refresh;###'Module::Refresh->refresh;'; \
+ " ${MASON_HANDLER} || true
+
+perl-modules:
+ cd FS; \
+ [ -e Makefile ] || perl Makefile.PL; \
+ make; \
+ perl -p -i -e "\
+ s/%%%VERSION%%%/${VERSION}/g;\
+ " blib/lib/FS.pm;\
+ perl -p -i -e "\
+ s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\
+ s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+ s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \
+ s'%%%MASONDATA%%%'${MASONDATA}'g;\
+ " blib/lib/FS/*.pm;\
+ perl -p -i -e "\
+ s/%%%SELFSERVICE_USER%%%/${SELFSERVICE_USER}/g;\
+ s/%%%SELFSERVICE_MACHINES%%%/${SELFSERVICE_MACHINES}/g;\
+ s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
+ " blib/lib/FS/Cron/*.pm;\
+ perl -p -i -e "\
+ s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\
+ s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
+ s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\
+ " blib/lib/FS/part_export/*.pm;\
+ perl -p -i -e "\
+ s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\
+ " blib/lib/FS/cust_main/*.pm;\
+ perl -p -i -e "\
+ s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\
+ s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\
+ s|%%%FREESIDE_LOCK%%%|${FREESIDE_LOCK}|g;\
+ s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\
+ s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
+ s|%%%DIST_CONF%%%|${DIST_CONF}|g;\
+ " blib/script/*
+
+install-perl-modules: perl-modules
+ [ -L ${PERL_INC_DEV_KLUDGE}/FS ] \
+ && rm ${PERL_INC_DEV_KLUDGE}/FS \
+ && mv ${PERL_INC_DEV_KLUDGE}/FS.old ${PERL_INC_DEV_KLUDGE}/FS \
+ || true
+ cd FS; \
+ make install UNINST=1
+ #install this for freeside-setup
+ install -d $(DIST_CONF)
+ #install conf/[a-z]* $(DEFAULT_CONF)
+ #CVS is not [a-z]
+ install `ls -d conf/[a-z]* | grep -v CVS | grep -v '^conf/registries'` $(DIST_CONF)
+
+dev-perl-modules: perl-modules
+ [ -d ${PERL_INC_DEV_KLUDGE}/FS -a ! -L ${PERL_INC_DEV_KLUDGE}/FS ] \
+ && mv ${PERL_INC_DEV_KLUDGE}/FS ${PERL_INC_DEV_KLUDGE}/FS.old \
+ || true
+
+ rm -rf ${PERL_INC_DEV_KLUDGE}/FS
+ ln -sf ${FREESIDE_PATH}/FS/blib/lib/FS ${PERL_INC_DEV_KLUDGE}/FS
+
+install-texmf:
+ install -D -o freeside -m 444 etc/fslongtable.sty \
+ `kpsewhich -expand-var \\\$$TEXMFLOCAL`/tex/generic/fslongtable.sty
+ texhash `kpsewhich -expand-var \\\$$TEXMFLOCAL`
+
+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/%%%SELFSERVICE_USER%%%/${SELFSERVICE_USER}/g;\
+ s/%%%SELFSERVICE_MACHINES%%%/${SELFSERVICE_MACHINES}/g;\
+ " ${INIT_FILE}
+ ${INIT_INSTALL}
+
+install-apache:
+ [ -e ${APACHE_CONF}/freeside-base.conf ] && rm ${APACHE_CONF}/freeside-base.conf || true
+ [ -d ${APACHE_CONF} ] && \
+ ( install -o root -m 755 htetc/freeside-base${APACHE_VERSION}.conf ${APACHE_CONF} && \
+ ( [ ${RT_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-rt.conf ${APACHE_CONF} || true ) && \
+ perl -p -i -e "\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+ s'%%%FREESIDE_CONF%%%'${FREESIDE_CONF}'g; \
+ s'%%%MASON_HANDLER%%%'${MASON_HANDLER}'g; \
+ " ${APACHE_CONF}/freeside-*.conf \
+ ) || true
+
+install-selfservice:
+ [ -e ~freeside ] || cp -pr /etc/skel ~freeside && chown -R freeside ~freeside
+ [ -e ~freeside/.ssh/id_dsa.pub ] || [ -e ~freeside/.ssh/id_rsa.pub ] || su - freeside -c 'ssh-keygen -t dsa'
+ 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 ~freeside/.ssh/; sudo install -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; make clean; 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-apache install-rt install-texmf
+
+deploy: install
+ ${HTTPD_RESTART}
+ ${FREESIDE_RESTART}
+
+cvs-upgrade-deploy:
+ su ${CVS_USER} -c 'cvs update -d -P'
+ make install-perl-modules
+ su freeside -c "freeside-upgrade ${CVS_USER}" #not really the same user
+ make deploy
+
+dev: dev-perl-modules dev-docs
+
+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
+
+ /bin/echo -e "${DATASOURCE}\n${DB_USER}\n${DB_PASSWORD}" >${FREESIDE_CONF}/secrets
+ chmod 600 ${FREESIDE_CONF}/secrets
+ chown freeside ${FREESIDE_CONF}/secrets
+
+ mkdir "${FREESIDE_CACHE}/counters.${DATASOURCE}"
+ chown freeside "${FREESIDE_CACHE}/counters.${DATASOURCE}"
+
+ mkdir "${FREESIDE_CACHE}/cache.${DATASOURCE}"
+ chown freeside "${FREESIDE_CACHE}/cache.${DATASOURCE}"
+
+ mkdir "${FREESIDE_EXPORT}/export.${DATASOURCE}"
+ chown freeside "${FREESIDE_EXPORT}/export.${DATASOURCE}"
+
+ #install this for freeside-setup
+ install -d $(DIST_CONF)
+ #install conf/[a-z]* $(DEFAULT_CONF)
+ #CVS is not [a-z]
+ install `ls -d conf/[a-z]* | grep -v CVS | grep -v '^conf/registries'` $(DIST_CONF)
+
+
+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=${DB_TYPE} \
+ --with-db-dba=${DB_USER} \
+ --with-db-database=${RT_DB_DATABASE} \
+ --with-db-rt-user=${DB_USER} \
+ --with-db-rt-pass=${DB_PASSWORD} \
+ --with-web-user=freeside \
+ --with-web-group=freeside \
+ --with-rt-group=freeside \
+ --with-web-handler=modperl2
+
+create-rt: configure-rt
+ [ -d /opt ] || mkdir /opt #doh
+ [ -d /opt/rt3 ] || mkdir /opt/rt3 #
+ [ -d /opt/rt3/share ] || mkdir /opt/rt3/share #
+ cd rt; make install
+ rt/sbin/rt-setup-database --dba '${DB_USER}' \
+ --dba-password '${DB_PASSWORD}' \
+ --action schema \
+ || true
+ rt/sbin/rt-setup-database --dba-password '${DB_PASSWORD}' \
+ --action coredata \
+ && rt/sbin/rt-setup-database --dba-password '${DB_PASSWORD}' \
+ --action insert \
+ --datafile ${RT_PATH}/etc/initialdata \
+ || true
+
+install-rt:
+ if [ ${RT_ENABLED} -eq 1 ]; then ( cd rt; make install ); fi
+ if [ ${RT_ENABLED} -eq 1 ]; then perl -p -i -e "\
+ s'%%%RT_DOMAIN%%%'${RT_DOMAIN}'g;\
+ s'%%%RT_TIMEZONE%%%'${RT_TIMEZONE}'g;\
+ s'%%%FREESIDE_URL%%%'${FREESIDE_URL}'g;\
+ " ${RT_PATH}/etc/RT_SiteConfig.pm; fi
+
+clean:
+ rm -rf masondocs
+ rm -rf httemplate/docs/man
+ rm -rf pod2htmi.tmp
+ rm -rf pod2htmd.tmp
+ -cd FS; \
+ make clean
+ -cd fs_selfservice/FS-SelfService; \
+ make clean
+
+#these are probably only useful if you're me...
+
+#release: upload-docs
+.PHONY: release
+release:
+ # Update the changelog
+ ./bin/cvs2cl
+ cvs commit -m "Updated for ${VERSION}" ChangeLog
+
+ # Update the RPM specfile
+ cvs edit ${RPM_SPECFILE}
+ perl -p -i -e "s/\d+[^\}]+/${VERSION}/ if /%define\s+version\s+(\d+[^\}]+)\}/;" ${RPM_SPECFILE}
+ perl -p -i -e "s/\d+[^\}]+/1/ if /%define\s+release\s+(\d+[^\}]+)\}/;" ${RPM_SPECFILE}
+ cvs commit -m "Updated for ${VERSION}" ${RPM_SPECFILE}
+
+ # Update the Debian changelog
+ cvs edit debian/changelog
+ dch -v ${DEBVERSION} -p "New upstream release"
+ cvs commit -m "Updated for ${VERSION}" debian/changelog
+
+ # Make sure other people's changes are pulled in!
+ cvs update -d -P || true #it exits 1...
+
+ # Tag the release
+ #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@420.am:/var/www/www.sisd.com/freeside/
+ mv freeside-${VERSION} freeside-${VERSION}.tar.gz ..
+
+ #these things failing should not make release target fail, so: "|| true"
+
+ #kick off vmware update
+ #./BUILD_VMWARE_APPLIANCE ${$TAG} || true
+
+ #kick off deb package update
+
+ #kick off rpm package update too?
+
+ #update web demo?
+
+ #update web demo self-service?
+
diff --git a/README b/README
new file mode 100644
index 0000000..41ae52d
--- /dev/null
+++ b/README
@@ -0,0 +1,36 @@
+Freeside is a billing and administration package for Internet Service
+Providers, VoIP providers and other online businesses.
+
+Copyright (C) 2005-2008 Freeside Internet Services, Inc.
+Copyright (C) 2000-2005 Ivan Kohler
+Copyright (C) 1999 Silicon Interactive Software Design
+Additional copyright holders may be found in the docs/license.html file.
+All rights reserved
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
+ License along with this program, in the file `AGPL'; if not,
+ see <http://www.fsf.org/licensing/licenses/agpl-3.0.html>.
+
+The Freeside home page is at http://www.freeside.biz/freeside
+
+The documentation is at http://www.freeside.biz/mediawiki
+
+Community support resources are located at
+http://www.freeside.biz/freeside/developers.html
+
+Preconfigured appliances, installation, customization, training and support
+services are available from Freeside Internet Services, Inc.
+
+Products: http://www.freeside.biz/freeside/products.html
+Services: http://www.freeside.biz/freeside/services.html
+Contact: http://www.freeside.biz/freeside/contact.html
diff --git a/bin/add-history-records.pl b/bin/add-history-records.pl
new file mode 100755
index 0000000..fbf9d09
--- /dev/null
+++ b/bin/add-history-records.pl
@@ -0,0 +1,139 @@
+#!/usr/bin/perl
+
+die "This is broken. Don't use it!\n";
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs qsearch);
+
+use Data::Dumper;
+
+my @tables = qw(svc_acct svc_broadband svc_domain svc_external svc_forward svc_www cust_svc domain_record);
+#my @tables = qw(svc_www);
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup($user);
+
+my $dbdef = FS::Record::dbdef;
+
+foreach my $table (@tables) {
+
+ my $h_table = 'h_' . $table;
+ my $cnt = 0;
+ my $t_cnt = 0;
+
+ eval "use FS::${table}";
+ die $@ if $@;
+ eval "use FS::${h_table}";
+ die $@ if $@;
+
+ print "Adding history records for ${table}...\n";
+
+ my $dbdef_table = $dbdef->table($table);
+ my $pkey = $dbdef_table->primary_key;
+
+ foreach my $rec (qsearch($table, {})) {
+
+ #my $h_rec = qsearchs(
+ # $h_table,
+ # { $pkey => $rec->getfield($pkey) },
+ # eval "FS::${h_table}->sql_h_searchs(time)",
+ #);
+
+ my $h_rec = qsearchs(
+ $h_table,
+ { $pkey => $rec->getfield($pkey) },
+ "DISTINCT ON ( $pkey ) *",
+ "AND history_action = 'insert' ORDER BY $pkey ASC, history_date DESC",
+ '',
+ 'AS maintable',
+ );
+
+ unless ($h_rec) {
+ my $h_insert_rec = $rec->_h_statement('insert', 1);
+ #print $h_insert_rec . "\n";
+ $dbh->do($h_insert_rec);
+ die $dbh->errstr if $dbh->err;
+ $dbh->commit or die $dbh->errstr;
+ $cnt++;
+ }
+
+
+ $t_cnt++;
+
+ }
+
+ print "History records inserted into $h_table: $cnt\n";
+ print " Total records in $table: $t_cnt\n";
+
+ print "\n";
+
+}
+
+foreach my $table (@tables) {
+
+ my $h_table = 'h_' . $table;
+ my $cnt = 0;
+
+ eval "use FS::${table}";
+ die $@ if $@;
+ eval "use FS::${h_table}";
+ die $@ if $@;
+
+ print "Adding insert records for unmatched delete records on ${table}...\n";
+
+ my $dbdef_table = $dbdef->table($table);
+ my $pkey = $dbdef_table->primary_key;
+
+ #SELECT * FROM h_svc_www
+ #DISTINCT ON ( $pkey ) ?
+ my $where = "
+ WHERE ${pkey} in (
+ SELECT ${h_table}1.${pkey}
+ FROM ${h_table} as ${h_table}1
+ WHERE (
+ SELECT count(${h_table}2.${pkey})
+ FROM ${h_table} as ${h_table}2
+ WHERE ${h_table}2.${pkey} = ${h_table}1.${pkey}
+ AND ${h_table}2.history_action = 'delete'
+ ) > 0
+ AND (
+ SELECT count(${h_table}3.${pkey})
+ FROM ${h_table} as ${h_table}3
+ WHERE ${h_table}3.${pkey} = ${h_table}1.${pkey}
+ AND ( ${h_table}3.history_action = 'insert'
+ OR ${h_table}3.history_action = 'replace_new' )
+ ) = 0
+ GROUP BY ${h_table}1.${pkey})";
+
+
+ my @h_recs = qsearch(
+ $h_table, { },
+ "DISTINCT ON ( $pkey ) *",
+ $where,
+ '',
+ ''
+ );
+
+ foreach my $h_rec (@h_recs) {
+ #print "Adding insert record for deleted record with pkey='" . $h_rec->getfield($pkey) . "'...\n";
+ my $class = 'FS::' . $table;
+ my $rec = $class->new({ $h_rec->hash });
+ my $h_insert_rec = $rec->_h_statement('insert', 1);
+ #print $h_insert_rec . "\n";
+ $dbh->do($h_insert_rec);
+ die $dbh->errstr if $dbh->err;
+ $dbh->commit or die $dbh->errstr;
+ $cnt++;
+ }
+
+ print "History records inserted into $h_table: $cnt\n";
+
+}
+
+
+
+sub usage {
+ die "Usage:\n add-history-records.pl user\n";
+}
+
diff --git a/bin/all-postal-no-email b/bin/all-postal-no-email
new file mode 100755
index 0000000..ef5dff6
--- /dev/null
+++ b/bin/all-postal-no-email
@@ -0,0 +1,22 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_main;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+foreach my $cust_main ( qsearch( 'cust_main', {} ) ) {
+
+ print $cust_main->custnum. "\n";
+
+ $cust_main->invoicing_list( [ 'POST' ] );
+
+}
+
+sub usage {
+ die "Usage:\n\n all-postal-no-email user\n";
+}
+
diff --git a/bin/apache.export b/bin/apache.export
new file mode 100755
index 0000000..82eb6d6
--- /dev/null
+++ b/bin/apache.export
@@ -0,0 +1,94 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+#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;
+
+use vars qw(%opt);
+getopts("d", \%opt);
+
+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 $exportnum = $export->exportnum;
+ my $file = "$spooldir/$machine.exportnum$exportnum.conf";
+
+ warn "exporting apache configuration for $machine to $file\n"
+ if $opt{d};
+
+ 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 $dir $email $config);
+ $zone = $svc_www->domain_record->zone;
+ $config = $svc_www->config;
+ if ( $svc_www->svc_acct ) {
+ $username = $svc_www->svc_acct->username;
+ $dir = $svc_www->svc_acct->dir;
+ $email = $svc_www->svc_acct->email;
+ } else {
+ $username = '';
+ $dir = '';
+ $email = '';
+ }
+
+ warn " adding configuration section for $zone\n"
+ if $opt{d};
+
+ print HTTPD_CONF eval(qq("$template")). "\n\n";
+ }
+
+ my $user = $export->option('user');
+ my $httpd_conf = $export->option('httpd_conf');
+
+ warn "syncing $file to $httpd_conf on $machine\n"
+ if $opt{d};
+
+ $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';
+
+ warn "running restart command $restart on $machine\n"
+ if $opt{d};
+
+ ssh("root\@$machine", $restart);
+
+}
+
+close HTTPD_CONF;
+
+# -----
+
+sub usage {
+ die "Usage:\n apache.export [ -d ] 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/backup-dvd b/bin/backup-dvd
new file mode 100644
index 0000000..d0314b4
--- /dev/null
+++ b/bin/backup-dvd
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+database="freeside"
+DEVICE="/dev/hda"
+
+su freeside -c "pg_dump $database" >/var/backups/$database.sql
+
+DATE=$(date +%Y-%m-%d)
+
+#NOTE: These two paths must end in a / in
+#order to correctly build up the other paths
+#BACKUP_DIR="/backup/directory/"
+BACKUP_DIR="/backup/"
+ #TEMP_BACKUP_FILES_DIR="/backup/temp/"
+
+BACKUP_FILE=$BACKUP_DIR"backup-"$DATE".tar.bz2"
+ #DATABASE_FILE=$TEMP_BACKUP_FILES_DIR"foo-"$DATE".sql"
+
+ #These directories shouldn't end in a / although
+ #I don't think it will cause any problems if
+ #they do. There should be a space at the end though
+ #to ensure the database file gets concatenated correctly.
+ #SOURCE="/a/location /other/locations " $DATABASE_FILE
+
+#echo Removing old backup directories
+rm -rf $BACKUP_DIR
+ #rm -rf $TEMP_BACKUP_FILES_DIR
+
+#echo Creating new backup directories
+mkdir $BACKUP_DIR
+ #mkdir $TEMP_BACKUP_FILES_DIR
+
+ #echo Creating database backup
+ #pg_dump -U username -f $DATABASE_FILE databaseName
+
+#echo Backing up $SOURCE to file $BACKUP_FILE
+#tar -cvpl -f $BACKUP_FILE --anchored --exclude /backup /
+tar -cjpl -f $BACKUP_FILE --anchored --exclude /backup /
+
+ ##This is not necessary and possibly harmful for DVD+RW media
+ #echo Quick blanking media
+ #dvd+rw-format -blank /dev/hdc
+
+#echo Burning backup
+growisofs -dvd-compat -Z $DEVICE -quiet -r -J $BACKUP_FILE
diff --git a/bin/bill-as-nextmonth b/bin/bill-as-nextmonth
new file mode 100755
index 0000000..813e841
--- /dev/null
+++ b/bin/bill-as-nextmonth
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+month=`date +%m`
+nextmonth=`expr $month + 1`
+/usr/local/bin/freeside-daily -d $nextmonth/1/`date +%Y` fs_daily
diff --git a/bin/bill-as-nextmonth-BILL b/bin/bill-as-nextmonth-BILL
new file mode 100755
index 0000000..91e9431
--- /dev/null
+++ b/bin/bill-as-nextmonth-BILL
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+month=`date +%m`
+nextmonth=`expr $month + 1`
+/usr/local/bin/freeside-daily -d $nextmonth/1/`date +%Y` -p BILL fs_daily
diff --git a/bin/bill-as-nextyear b/bin/bill-as-nextyear
new file mode 100755
index 0000000..63c4ad2
--- /dev/null
+++ b/bin/bill-as-nextyear
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+year=`date +%Y`
+nextyear=`expr $year + 1`
+/usr/local/bin/freeside-daily -d 1/1/$nextyear fs_daily
diff --git a/bin/bill-as-nextyear-BILL b/bin/bill-as-nextyear-BILL
new file mode 100755
index 0000000..0d77dd0
--- /dev/null
+++ b/bin/bill-as-nextyear-BILL
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+year=`date +%Y`
+nextyear=`expr $year + 1`
+/usr/local/bin/freeside-daily -d 1/1/$nextyear -p BILL fs_daily
diff --git a/bin/bill-for-nextmonth b/bin/bill-for-nextmonth
new file mode 100755
index 0000000..e1a3376
--- /dev/null
+++ b/bin/bill-for-nextmonth
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+month=`date +%m`
+nextmonth=`expr $month + 1`
+/usr/local/bin/freeside-daily -d $nextmonth/1/`date +%Y` -n fs_daily
diff --git a/bin/bill-for-nextyear b/bin/bill-for-nextyear
new file mode 100755
index 0000000..1430a58
--- /dev/null
+++ b/bin/bill-for-nextyear
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+year=`date +%Y`
+nextyear=`expr $year + 1`
+/usr/local/bin/freeside-daily -d 1/1/$nextyear -n fs_daily
diff --git a/bin/bill-nextmonth b/bin/bill-nextmonth
new file mode 100755
index 0000000..813e841
--- /dev/null
+++ b/bin/bill-nextmonth
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+month=`date +%m`
+nextmonth=`expr $month + 1`
+/usr/local/bin/freeside-daily -d $nextmonth/1/`date +%Y` fs_daily
diff --git a/bin/bill-nextyear b/bin/bill-nextyear
new file mode 100755
index 0000000..63c4ad2
--- /dev/null
+++ b/bin/bill-nextyear
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+year=`date +%Y`
+nextyear=`expr $year + 1`
+/usr/local/bin/freeside-daily -d 1/1/$nextyear fs_daily
diff --git a/bin/bind.export b/bin/bind.export
new file mode 100755
index 0000000..286e43a
--- /dev/null
+++ b/bin/bind.export
@@ -0,0 +1,195 @@
+#!/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: $!";
+
+ if ( -e "$prefix/named.conf.HEADER" ) {
+ 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: $!";
+
+ if ( -e "$prefix/named.conf.HEADER" ) {
+ 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..45db2e2
--- /dev/null
+++ b/bin/bind.import
@@ -0,0 +1,235 @@
+#!/usr/bin/perl -w
+#
+# REQUIRED:
+# -p: part number for domains
+#
+# -n: named.conf file (or an include file with zones you want to import),
+# for example root@ns.isp.com:/var/named/named.conf
+#
+# OPTIONAL:
+# -d: dry-run, debug: don't insert any records, just dump debugging output
+# -e: use existing domains records in Freeside
+# -s: import slave zones as master. useful if you need to recreate your
+# primary nameserver from a secondary
+# -c dir: override patch for downloading zone files (for example, when
+# downloading zone files from chrooted bind)
+#
+# need to manually put header in
+# /usr/local/etc/freeside/export.<datasrc./bind/<machine>/named.conf.HEADER
+# (or, nowadays, better just to include the file freeside exports)
+
+use strict;
+
+use vars qw($domain_svcpart);
+
+use Getopt::Std;
+use Data::Dumper;
+#use BIND::Conf_Parser;
+#use DNS::ZoneParse 0.81;
+
+use Net::SCP qw(scp 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 vars qw($opt_p $opt_n $opt_s $opt_c $opt_d $opt_e);
+getopts("p:n:sc:de");
+
+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;
+
+$domain_svcpart = $opt_p;
+
+my $named_conf = $opt_n;
+
+use vars qw($named_machine $prefix);
+$named_machine = (split(/:/, $named_conf))[0];
+my $pnamed_machine = $named_machine;
+$pnamed_machine =~ s/^[\w\-]+\@//;
+$prefix = "$spooldir/$pnamed_machine";
+mkdir $prefix unless -d $prefix;
+
+#iscp("$named_conf","$prefix/named.conf.import");
+scp("$named_conf","$prefix/named.conf.import");
+
+##
+
+$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 -p partnum -n \"user\@machine:/path/to/named.conf\" [ -s ] [ -c chroot_dir ] [ -d ] [ -e ] user\n";
+}
+
+########
+BEGIN {
+
+ package Parser;
+ use BIND::Conf_Parser;
+ use vars qw(@ISA $named_dir);
+ @ISA = qw(BIND::Conf_Parser);
+
+ $named_dir = 'COULD_NOT_FIND_NAMED_DIRECTORY_TRY_SETTING_-C_OPTION';
+ sub handle_option {
+ my($self, $option, $argument) = @_;
+ return unless $option eq "directory";
+ $named_dir = $argument;
+ #warn "found named dir: $named_dir\n";
+ }
+
+ 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 ) {
+
+ if ( $main::opt_d ) {
+
+ use Data::Dumper;
+ print "$name: ". Dumper($options);
+
+ } else {
+
+ 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;
+ if ( $main::opt_c ) {
+ $sourcefile = "$main::opt_c/$sourcefile" if $main::opt_c;
+ } else {
+ $sourcefile = "$named_dir/$sourcefile" unless $file =~ /^\//;
+ }
+
+ use Net::SCP qw(iscp scp);
+ #iscp("$main::named_machine:$sourcefile",
+ # "$main::prefix/$basefile.import");
+ scp("$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;
+
+ if ( $main::opt_d ) {
+
+ use Data::Dumper;
+ print "$name: ". Dumper($dump);
+
+ } else {
+
+ foreach my $rectype ( keys %$dump ) {
+ if ( $rectype =~ /^SOA$/i ) {
+ my $rec = $dump->{$rectype};
+ $rec->{email} =~ s/\@/\./;
+ 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};
+
+ my $datasub;
+ if ( $rectype =~ /^MX$/i ) {
+ $datasub = sub { $_[0]->{priority}. ' '. $_[0]->{host}; };
+ } elsif ( $rectype =~ /^TXT$/i ) {
+ $datasub = sub { $_[0]->{text}; };
+ } else {
+ $datasub = sub { $_[0]->{host}; };
+ }
+
+ foreach my $rec ( @{ $dump->{$rectype} } ) {
+ my $domain_record = new FS::domain_record( {
+ 'svcnum' => $domain->svcnum,
+ 'reczone' => $rec->{name},
+ 'recaf' => $rec->{class} || 'IN',
+ 'rectype' => $rectype,
+ 'recdata' => &{$datasub}($rec),
+ } );
+ my $error = $domain_record->insert;
+ if ( $error ) {
+ warn "$error inserting ".
+ $rec->{name}. ' . '. $domain->domain. "\n";
+ warn Dumper($rec);
+ #system('cat',"$main::prefix/$basefile.import");
+ die;
+ }
+ }
+ }
+ }
+
+ }
+
+ #} else {
+ # die "unrecognized type $type\n";
+ }
+
+ }
+
+}
+#########
+
diff --git a/bin/breakdown-bill-applications b/bin/breakdown-bill-applications
new file mode 100644
index 0000000..44c3e36
--- /dev/null
+++ b/bin/breakdown-bill-applications
@@ -0,0 +1,25 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record qw( qsearch );
+use FS::cust_bill_pay;
+use FS::cust_credit_bill;
+
+$FS::CurrentUser::upgrade_hack = 1;
+adminsuidsetup(shift) or die "Usage: breakdown-bill-applications username\n";
+
+#quick and dirty conversion script if you have enough memory to throw at it
+
+my @tables = qw( cust_bill_pay cust_credit_bill );
+
+my @apps = ();
+foreach my $table {
+ push @apps, qsearch($table,
+
+
+) {
+
+}
+
+foreach my $cust_bill_
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/build_exten.php b/bin/build_exten.php
new file mode 100755
index 0000000..e55df4b
--- /dev/null
+++ b/bin/build_exten.php
@@ -0,0 +1,790 @@
+#!/usr/bin/php -q
+<?php /* $Id: build_exten.php,v 1.1 2010-03-26 02:19:16 ivan Exp $ */
+//Copyright (C) 2008 Astrogen LLC
+//
+//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.
+
+function out($text) {
+ echo $text."\n";
+}
+
+function outn($text) {
+ echo $text;
+}
+
+function error($text) {
+ echo "[ERROR] ".$text."\n";
+}
+
+function warning($text) {
+ echo "[WARNING] ".$text."\n";
+}
+
+function fatal($text) {
+ echo "[FATAL] ".$text."\n";
+ exit(1);
+}
+
+function debug($text) {
+ global $param_debug;
+
+ if ($param_debug) echo "[DEBUG] ".$text."\n";
+}
+
+if (! @ include("Console/Getopt.php")) {
+ fatal("PEAR must be installed (requires Console/Getopt.php). Include path: ".ini_get("include_path"));
+ exit(12);
+}
+
+ini_set('error_reporting', E_ALL & ~E_NOTICE);
+
+function showHelp() {
+ global $argv;
+ out("USAGE:");
+ out(" ".$argv[0]." --create|delete --exten <extension> [optional parameters]");
+ out("");
+
+ out("OPERATIONS (exactly one must be specified):");
+ out(" --create, -c");
+ out(" Create a new extension");
+ out(" --modify, -m");
+ out(" Modify an existing extension, the extension must exist and all values execept");
+ out(" those specified will remain the same");
+ out(" --delete, -d");
+ out(" Delete an extension");
+
+ out("PARAMETERS:");
+ out(" --exten extension_number");
+ out(" Extension number to create or delete. Must be specified.");
+
+ out("OPTIONAL PARAMETERS:");
+ out(" --name name");
+ out(" Display Name, defaults to specified extension number.");
+ out(" --outboundcid cid_number");
+ out(" Outbound CID Number, defaults to specified extension number.");
+ out(" --directdid did_number");
+ out(" Direct DID Number, defaults to extension number.");
+ out(" --vm-password password");
+ out(" Voicemail Password, defaults to specified extension number.");
+ out(" --sip-secret secret");
+ out(" SIP Secret, defaults to md5 hash of specified extension number.");
+ out(" --debug");
+ out(" Display debug messages.");
+ out(" --no-warnings");
+ out(" Do Not display warning messages.");
+
+ out(" --help, -h, -? Show this help");
+}
+
+// **** Parse out command-line options
+$shortopts = "cmdh?";
+$longopts = array(
+ "help",
+ "debug",
+ "no-warnings",
+ "create",
+ "modify",
+ "delete",
+ "exten=",
+ "outboundcid=",
+ "directdid=",
+ "name=",
+ "sip-secret=",
+ "vm-password=",
+);
+
+$args = Console_Getopt::getopt(Console_Getopt::readPHPArgv(), $shortopts, $longopts);
+if (is_object($args)) {
+ // assume it's PEAR_ERROR
+ fatal($args->message);
+ exit(255);
+}
+
+$no_params = true;
+
+$param_debug = false;
+$param_warnings = true;
+$param_create = false;
+$param_modify = false;
+$param_delete = false;
+$param_exten = false;
+$param_name = false;
+$param_outboundcid = false;
+$param_directdid = false;
+$param_sip_secret = false;
+$param_vm_password = false;
+
+foreach ($args[0] as $arg) {
+ $no_params = false;
+ switch ($arg[0]) {
+
+ case "--help":
+ case "h":
+ case "?":
+ showHelp();
+ exit(10);
+ break;
+
+ case "--debug":
+ $param_debug = true;
+ debug("debug mode is enabled");
+ break;
+
+ case "--no-warnings":
+ $param_warnings = false;
+ break;
+
+ case "--create":
+ case "c":
+ $param_create = true;
+ break;
+
+ case "--modify":
+ case "m":
+ $param_modify = true;
+ break;
+
+ case "--delete":
+ case "d":
+ $param_delete = true;
+ break;
+
+ case "--exten":
+ $param_exten = true;
+ $new_exten = $arg[1];
+ break;
+
+ case "--outboundcid":
+ $param_outboundcid = true;
+ $new_outboundcid = $arg[1];
+ break;
+
+ case "--directdid":
+ $param_directdid = true;
+ $new_directdid = $arg[1];
+ break;
+
+ case "--name":
+ $param_name = true;
+ $new_name = $arg[1];
+ break;
+
+ case "--sip-secret":
+ $param_sip_secret = true;
+ $new_sip_secret = $arg[1];
+ break;
+
+ case "--vm-password":
+ $param_vm_password = true;
+ $new_vm_password = $arg[1];
+ break;
+
+ default:
+ error("unhandled argument supplied: ".$arg[0].", aborting");
+ exit (1);
+ }
+}
+
+if ($no_params) {
+ showHelp();
+ exit(10);
+}
+if ($param_create && $param_modify) {
+ error("Incompatible combination of options, create and modify");
+ exit (1);
+}
+if (!(($param_create || $param_modify) XOR $param_delete)) {
+ error("Invalid Parameter combination, you must include create or delete and can not do both in one call");
+ exit (1);
+}
+if (!$param_exten) {
+ error("You must provide an extension number to create or delete an extension");
+ exit (1);
+}
+
+if ($param_warnings && $param_create) {
+ if (!$param_outboundcid) {
+ $new_outboundcid = $new_exten;
+ warning("WARNING: No outboundcid specified for extenion, using $new_outboundcid as outboundcid");
+ }
+ if (!$param_directdid) {
+ $new_directdid = $new_exten;
+ warning("WARNING: No outboundcid specified for extenion, using $new_outboundcid as outboundcid");
+ }
+ if (!$param_name) {
+ $new_name = $new_exten;
+ warning("WARNING: No name specified for extenion, using $new_name as name");
+ }
+ if (!$param_sip_secret) {
+ $new_sip_secret = md5($new_exten);
+ warning("WARNING: No sip-secret specified for extenion, using $new_sip_secret as secret");
+ }
+ if (!$param_vm_password) {
+ $new_vm_password = $new_exten;
+ warning("WARNING: No vm-password specified for extenion, using $new_vm_password as password");
+ }
+}
+
+// Now setup actions and exten how leveraged code expected it
+//
+$exten = $new_exten;
+if ($param_create) {
+ $actions = "addext/addvm";
+} else if ($param_modify) {
+ $actions = "modext";
+} else if ($param_delete) {
+ $actions = "remext";
+}
+
+/* I don't think I need these but ???
+*/
+$type = 'setup';
+$display = '';
+$extdisplay = null;
+
+// determine module type to show, default to 'setup'
+$type_names = array(
+ 'tool'=>'Tools',
+ 'setup'=>'Setup',
+ 'cdrcost'=>'Call Cost',
+);
+
+define("AMP_CONF", "/etc/amportal.conf");
+$amportalconf = AMP_CONF;
+
+// bootstrap retrieve_conf by getting the AMPWEBROOT since that is currently where the necessary
+// functions.inc.php resides, and then use that parser to properly parse the file and get all
+// the defaults as needed.
+//
+function parse_amportal_conf_bootstrap($filename) {
+ $file = file($filename);
+ foreach ($file as $line) {
+ if (preg_match("/^\s*([\w]+)\s*=\s*\"?([\w\/\:\.\*\%-]*)\"?\s*([;#].*)?/",$line,$matches)) {
+ $conf[ $matches[1] ] = $matches[2];
+ }
+ }
+ if ( !isset($conf["AMPWEBROOT"]) || ($conf["AMPWEBROOT"] == "")) {
+ $conf["AMPWEBROOT"] = "/var/www/html";
+ } else {
+ $conf["AMPWEBROOT"] = rtrim($conf["AMPWEBROOT"],'/');
+ }
+
+ return $conf;
+}
+
+$amp_conf = parse_amportal_conf_bootstrap($amportalconf);
+if (count($amp_conf) == 0) {
+ exit (1);
+}
+
+
+// Emulate gettext extension functions if gettext is not available
+if (!function_exists('_')) {
+ function _($str) {
+ return $str;
+ }
+}
+if (!function_exists('gettext')) {
+ function gettext($message) {
+ return $message;
+ }
+}
+if (!function_exists('dgettext')) {
+ function dgettext($domain, $message) {
+ return $message;
+ }
+}
+
+// setup locale
+function set_language() {
+ if (extension_loaded('gettext')) {
+ if (isset($_COOKIE['lang'])) {
+ setlocale(LC_ALL, $_COOKIE['lang']);
+ putenv("LANGUAGE=".$_COOKIE['lang']);
+ } else {
+ setlocale(LC_ALL, 'en_US');
+ }
+ bindtextdomain('amp','./i18n');
+ bind_textdomain_codeset('amp', 'utf8');
+ textdomain('amp');
+ }
+}
+set_language();
+
+// systems running on sqlite3 (or pgsql) this function is not available
+// instead of changing the whole code, lets hack our own version of this function.
+// according to the documentation found here: http://il2.php.net/mysql_real_escape_string
+// this shold be enough.
+// Fixes ticket: http://freepbx.org/trac/ticket/1963
+if (!function_exists('mysql_real_escape_string')) {
+ function mysql_real_escape_string($str) {
+ $str = str_replace( "\x00", "\\" . "\x00", $str );
+ $str = str_replace( "\x1a", "\\" . "\x1a", $str );
+ $str = str_replace( "\n" , "\\". "\n" , $str );
+ $str = str_replace( "\r" , "\\". "\r" , $str );
+ $str = str_replace( "\\" , "\\". "\\" , $str );
+ $str = str_replace( "'" , "''" , $str );
+ $str = str_replace( '"' , '""' , $str );
+ return $str;
+ }
+}
+
+// include base functions
+
+require_once($amp_conf['AMPWEBROOT']."/admin/functions.inc.php");
+require_once($amp_conf['AMPWEBROOT']."/admin/common/php-asmanager.php");
+$amp_conf = parse_amportal_conf($amportalconf);
+if (count($amp_conf) == 0) {
+ exit (1);
+}
+$asterisk_conf_file = $amp_conf["ASTETCDIR"]."/asterisk.conf";
+$asterisk_conf = parse_asterisk_conf($asterisk_conf_file);
+
+ini_set('include_path',ini_get('include_path').':'.$amp_conf['AMPWEBROOT'].'/admin/:');
+
+$astman = new AGI_AsteriskManager();
+
+// attempt to connect to asterisk manager proxy
+if (!isset($amp_conf["ASTMANAGERPROXYPORT"]) || !$res = $astman->connect("127.0.0.1:".$amp_conf["ASTMANAGERPROXYPORT"], $amp_conf["AMPMGRUSER"] , $amp_conf["AMPMGRPASS"])) {
+ // attempt to connect directly to asterisk, if no proxy or if proxy failed
+ if (!$res = $astman->connect("127.0.0.1:".$amp_conf["ASTMANAGERPORT"], $amp_conf["AMPMGRUSER"] , $amp_conf["AMPMGRPASS"])) {
+ // couldn't connect at all
+ unset( $astman );
+ }
+}
+// connect to database
+require_once($amp_conf['AMPWEBROOT']."/admin/common/db_connect.php");
+
+$nt = notifications::create($db);
+
+$framework_asterisk_running = checkAstMan();
+
+// get all enabled modules
+// active_modules array used below and in drawselects function and genConf function
+$active_modules = module_getinfo(false, MODULE_STATUS_ENABLED);
+
+$fpbx_menu = array();
+
+// pointer to current item in $fpbx_menu, if applicable
+$cur_menuitem = null;
+
+// add module sections to $fpbx_menu
+$types = array();
+if(is_array($active_modules)){
+ foreach($active_modules as $key => $module) {
+ //include module functions
+ if (is_file($amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php")) {
+ require_once($amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php");
+ }
+
+ // create an array of module sections to display
+ // stored as [items][$type][$category][$name] = $displayvalue
+ if (isset($module['items']) && is_array($module['items'])) {
+ // loop through the types
+ foreach($module['items'] as $itemKey => $item) {
+
+ if (!$framework_asterisk_running &&
+ ((isset($item['needsenginedb']) && strtolower($item['needsenginedb'] == 'yes')) ||
+ (isset($item['needsenginerunning']) && strtolower($item['needsenginerunning'] == 'yes')))
+ )
+ {
+ $item['disabled'] = true;
+ } else {
+ $item['disabled'] = false;
+ }
+
+ if (!in_array($item['type'], $types)) {
+ $types[] = $item['type'];
+ }
+
+ if (!isset($item['display'])) {
+ $item['display'] = $itemKey;
+ }
+
+ // reference to the actual module
+ $item['module'] =& $active_modules[$key];
+
+ // item is an assoc array, with at least array(module=> name=>, category=>, type=>, display=>)
+ $fpbx_menu[$itemKey] = $item;
+
+ // allow a module to replace our main index page
+ if (($item['display'] == 'index') && ($display == '')) {
+ $display = 'index';
+ }
+
+ // check current item
+ if ($display == $item['display']) {
+ // found current menuitem, make a reference to it
+ $cur_menuitem =& $fpbx_menu[$itemKey];
+ }
+ }
+ }
+ }
+}
+sort($types);
+
+// new gui hooks
+if(is_array($active_modules)){
+ foreach($active_modules as $key => $module) {
+ if (isset($module['items']) && is_array($module['items'])) {
+ foreach($module['items'] as $itemKey => $itemName) {
+ //list of potential _configpageinit functions
+ $initfuncname = $key . '_' . $itemKey . '_configpageinit';
+ if ( function_exists($initfuncname) ) {
+ $configpageinits[] = $initfuncname;
+ }
+ }
+ }
+ //check for module level (rather than item as above) _configpageinit function
+ $initfuncname = $key . '_configpageinit';
+ if ( function_exists($initfuncname) ) {
+ $configpageinits[] = $initfuncname;
+ }
+ }
+}
+
+// extensions vs device/users ... this is a bad design, but hey, it works
+if (isset($amp_conf["AMPEXTENSIONS"]) && ($amp_conf["AMPEXTENSIONS"] == "deviceanduser")) {
+ unset($fpbx_menu["extensions"]);
+} else {
+ unset($fpbx_menu["devices"]);
+ unset($fpbx_menu["users"]);
+}
+
+
+// Here we process the action and create the exten, mailbox or delete it.
+//
+
+$EXTEN_REQUEST = array (
+ 'actions' => $actions,
+ 'ext' => $exten,
+ 'displayname' => $new_name,
+ 'emergencycid' => '',
+ 'outboundcid' => $new_outboundcid,
+ 'accountcode' => '',
+ 'dtmfmode' => 'auto',
+ 'devicesecret' => $new_sip_secret,
+ 'directdid' => $new_directdid,
+ );
+
+$actions = explode('/',$EXTEN_REQUEST['actions']);
+
+ $actions_taken = false;
+
+ $ext = '';
+ $pass = '';
+ $displayname = '';
+ $emergencycid = '';
+ $outboundcid = '';
+ $directdid = '';
+ $mailbox = '';
+ $tech = 'sip';
+ $dcontext = 'from-internal';
+ $dtmfmode = 'auto';
+
+ foreach ($EXTEN_REQUEST as $key => $value) {
+ switch ($key) {
+ case 'ext':
+ case 'displayname':
+ case 'emergencycid':
+ case 'outboundcid':
+ case 'accountcode':
+ case 'dtmfmode':
+ case 'devicesecret':
+ case 'directdid':
+ case 'mailbox':
+ case 'dcontext':
+ $$key = $value;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /*
+ echo "\nDumping core_users_get:";
+ $user_list = core_users_get($ext);
+ var_dump($user_list);
+
+ echo "\nDumping core_devices_get:";
+ $device_list = core_devices_get($ext);
+ var_dump($device_list);
+
+ echo "\nDumping voicemail_mailbox_get:";
+ $vm_list = voicemail_mailbox_get($ext);
+ var_dump($vm_list);
+
+ exit;
+ */
+
+ if ($ext == '') {
+ fatal("No Extension provided (this should have been caught above, may be a bug");
+ exit (10);
+ }
+
+ /* DEFAULTS:
+ displayname: ext
+ devicesecret: ext
+ */
+
+ if (in_array('addext', $actions) || in_array('addvm',$actions)) {
+ if ($displayname == '') {
+ $displayname = $ext;
+ }
+ if (isset($accountcode)) {
+ $_REQUEST['devinfo_accountcode'] = $accountcode;
+ }
+ if (!isset($devicesecret)) {
+ $devicesecret = $ext;
+ }
+ if ($mailbox == '') {
+ $mailbox = $ext.'@default';
+ }
+ $user_add_arr = array(
+ 'extension' => $ext,
+ 'device' => $ext,
+ 'name' => $displayname,
+ 'directdid' => $directdid,
+ 'outboundcid' => $outboundcid,
+ 'sipname' => '',
+ 'record_out' => 'Never',
+ 'record_in' => 'Never',
+ 'callwaiting' => 'enabled',
+
+ 'vm' => 'enabled',
+ 'vmcontext' => 'default',
+ 'options' => '',
+ 'vmpwd' => $new_vm_password,
+ 'email' => '',
+ 'pager' => '',
+ 'attach' => 'attach=no',
+ 'saycid' => 'saycid=no',
+ 'envelope' => 'envelope=no',
+ 'delete' => 'delete=no',
+ );
+
+ // archaic code expects these in the REQUEST array ...
+ //
+ $_REQUEST['devinfo_secret'] = $devicesecret;
+ $_REQUEST['devinfo_dtmfmode'] = $dtmfmode;
+ $_REQUEST['devinfo_canreinvite'] = 'no';
+ $_REQUEST['devinfo_context'] = $dcontext;
+ $_REQUEST['devinfo_host'] = 'dynamic';
+ $_REQUEST['devinfo_type'] = 'friend';
+ $_REQUEST['devinfo_nat'] = 'yes';
+ $_REQUEST['devinfo_port'] = '5060';
+ $_REQUEST['devinfo_dial'] = 'SIP/'.$ext;
+ $_REQUEST['devinfo_mailbox'] = $mailbox;
+
+ } else if (in_array('modext', $actions)) {
+ $user_list = core_users_get($ext);
+ //var_dump($user_list);
+ if (!isset($user_list['extension'])) {
+ error("No such extension found: $ext");
+ exit (10);
+ }
+ $device_list = core_devices_get($ext);
+ //var_dump($device_list);
+ if (count($device_list) == 0) {
+ error("No such device found: $ext");
+ exit (10);
+ }
+ $vm_list = voicemail_mailbox_get($ext);
+ //var_dump($vm_list);
+ if (count($vm_list) == 0) {
+ error("No voicemail found for: $ext");
+ exit (10);
+ }
+
+ if ($param_name) {
+ $user_list['name'] = $new_name;
+ $device_list['description'] = $new_name;
+ $vm_list['name'] = $new_name;
+ }
+ if ($param_sip_secret) {
+ $device_list['secret'] = $new_sip_secret;
+ }
+ if ($param_vm_password) {
+ $vm_list['pwd'] = $new_vm_password;
+ }
+ if ($param_directdid) {
+ $user_list['directdid'] = $new_directdid;
+ }
+ if ($param_outboundcid) {
+ $user_list['outboundcid'] = $new_outboundcid;
+ }
+ $user_mod_arr = array(
+ 'extension' => $ext,
+ 'device' => $ext,
+ 'name' => $user_list['name'],
+ 'directdid' => $user_list['directdid'],
+ 'outboundcid' => $user_list['outboundcid'],
+ 'sipname' => $user_list['sipname'],
+ 'record_out' => $user_list['record_out'],
+ 'record_in' => $user_list['record_in'],
+ 'callwaiting' => $user_list['callwaiting'],
+
+ 'vm' => 'enabled',
+ 'vmcontext' => $vm_list['vmcontext'],
+ 'vmpwd' => $vm_list['pwd'],
+ 'email' => $vm_list['email'],
+ 'pager' => $vm_list['pager'],
+ 'options' => '',
+ 'attach' => $vm_list['options']['attach'],
+ 'saycid' => $vm_list['options']['saycid'],
+ 'envelope' => $vm_list['options']['envelope'],
+ 'delete' => $vm_list['options']['delete'],
+ );
+
+ // archaic code expects these in the REQUEST array ...
+ //
+ $_REQUEST['devinfo_secret'] = $device_list['secret'];
+ $_REQUEST['devinfo_dtmfmode'] = $device_list['dtmfmode'];
+ $_REQUEST['devinfo_canreinvite'] = $device_list['canreinvite'];
+ $_REQUEST['devinfo_context'] = $device_list['context'];
+ $_REQUEST['devinfo_host'] = $device_list['host'];
+ $_REQUEST['devinfo_type'] = $device_list['type'];
+ $_REQUEST['devinfo_nat'] = $device_list['nat'];
+ $_REQUEST['devinfo_port'] = $device_list['port'];
+ $_REQUEST['devinfo_dial'] = $device_list['dial'];
+ $_REQUEST['devinfo_mailbox'] = $device_list['mailbox'];
+ $_REQUEST['devinfo_accountcode'] = $device_list['accountcode'];
+ $_REQUEST['devinfo_username'] = $ext;
+ //$_REQUEST['devinfo_callerid'] = $device_list['callerid'];
+ //$_REQUEST['devinfo_record_in'] = $device_list['record_in'];
+ //$_REQUEST['devinfo_record_out'] = $device_list['record_out'];
+
+ if (isset($device_list['qualify'])) {
+ $_REQUEST['devinfo_qualify'] = $device_list['qualify'];
+ }
+ if (isset($device_list['callgroup'])) {
+ $_REQUEST['devinfo_callgroup'] = $device_list['callgroup'];
+ }
+ if (isset($device_list['pickupgroup'])) {
+ $_REQUEST['devinfo_pickupgroup'] = $device_list['pickupgroup'];
+ }
+ if (isset($device_list['allow'])) {
+ $_REQUEST['devinfo_allow'] = $device_list['allow'];
+ }
+ if (isset($device_list['disallow'])) {
+ $_REQUEST['devinfo_disallow'] = $device_list['disallow'];
+ }
+
+ $actions_taken = true;
+ debug("core_users_edit($ext, $user_add_arr)");
+ core_users_edit($ext, $user_mod_arr);
+ // doesn't return a return code, so hope it worked:-)
+
+ debug("core_devices_del($ext, true)");
+ debug("core_devices_add($ext,'sip',".$device_list['dial'].",'fixed',$ext,".$device_list['description'].",".$device_list['emergency_cid'].",true)");
+ core_devices_del($ext,true);
+ core_devices_add($ext,'sip',$device_list['dial'],'fixed',$ext,$device_list['description'],$device_list['emergency_cid'],true);
+ // doesn't return a return code, so hope it worked:-)
+
+ debug("voicemail_mailbox_del($ext)");
+ debug("voicemail_mailbox_add($ext, $user_mod_arr)");
+ voicemail_mailbox_del($ext);
+ voicemail_mailbox_add($ext, $user_mod_arr);
+ }
+
+ if (in_array('addvm', $actions)) {
+ $actions_taken = true;
+ if (($existing_vmbox = voicemail_mailbox_get($ext)) == null ) {
+ debug("voicemail_mailbox_add($ext, $user_add_arr)");
+ voicemail_mailbox_add($ext, $user_add_arr);
+ } else {
+ debug(print_r($existing_vmbox,true));
+ fatal("voicemail_mailbox_get($ext) indicates the box already exists, aborting");
+ exit (1);
+ }
+
+ // check if we need to create symlink if if addext is not being called
+ if (!in_array('addext', $actions)) {
+
+ $thisUser = core_users_get($ext);
+
+ // This is a bit kludgey, the other way is to reformat the core_users_get() info and do a core_users_add() in edit mode
+ //
+ if (!empty($thisUser)) {
+ $this_vmcontext = $user_add_arr['vmcontext'];
+ sql("UPDATE `users` SET `voicemail` = '$this_vmcontext' WHERE `extension` = '$ext'");
+
+ if ($astman) {
+ $astman->database_put("AMPUSER",$ext."/voicemail","\"".isset($this_vmcontext)?$this_vmcontext:''."\"");
+ }
+ }
+
+ if(isset($this_vmcontext) && $this_vmcontext != "novm") {
+ if(empty($this_vmcontext)) {
+ $vmcontext = "default";
+ } else {
+ $vmcontext = $this_vmcontext;
+ }
+ //voicemail symlink
+ //
+ exec("rm -f /var/spool/asterisk/voicemail/device/".$ext,$output,$return_val);
+ exec("/bin/ln -s /var/spool/asterisk/voicemail/".$vmcontext."/".$ext."/ /var/spool/asterisk/voicemail/device/".$ext,$output,$return_val);
+ if ($return_val != 0) {
+ error("Error code $return_val when sym-linking vmail context $vmcontext to device directory for $ext. Trying to carry on but you should investigate.");
+ }
+ }
+ }
+ }
+
+ if (in_array('addext', $actions)) {
+ $actions_taken = true;
+ $any_users = core_users_get($ext);
+ debug("core_users_add($user_add_arr)");
+ if (isset($any_users['extension']) || !core_users_add($user_add_arr)) {
+ var_dump($any_users);
+ fatal("Attempt to add user failed, aborting");
+ exit (1);
+ }
+ }
+
+ if (in_array('addext', $actions)) {
+ $actions_taken = true;
+ debug("core_devices_add($ext, $tech, '', 'fixed', $ext, $displayname, $emergencycid)");
+ $any_devices = core_devices_get($ext);
+ if (count($any_devices) > 0 || !core_devices_add($ext, $tech, '', 'fixed', $ext, $displayname, $emergencycid)) {
+ var_dump($any_devices);
+ fatal("Attempt to add device failed, aborting");
+ exit (1);
+ }
+ }
+
+ if (in_array('remext', $actions)) {
+ $actions_taken = true;
+ if (core_users_get($ext) != null) {
+ debug("removing user $ext");
+ core_users_del($ext);
+ core_devices_del($ext);
+ } else {
+ debug("not removing user $ext");
+ }
+ if (voicemail_mailbox_get($ext) != null) {
+ debug("removing vm $ext");
+ voicemail_mailbox_del($ext);
+ } else {
+ debug("not removing vm $ext");
+ }
+ }
+
+ if ($actions_taken) {
+ debug("Request completed successfully");
+ exit (0);
+ } else {
+ warning("No actions were performed");
+ exit (10);
+ }
+ exit;
+?>
diff --git a/bin/cch_tax_tool b/bin/cch_tax_tool
new file mode 100755
index 0000000..6261363
--- /dev/null
+++ b/bin/cch_tax_tool
@@ -0,0 +1,59 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# this tool manipulates fixed length cch tax files by comparing the
+# update files in the $update_dir to the initial install files
+# in the $init_dir
+#
+# it produces .DOIT files in $update_dir which are suitable for
+# syncing a database initialzed with the files in $init_dir to
+# the state represented by the files in $update_dir
+#
+# how one acquires update files from cch that overlap with initial
+# full install remains a mystery
+
+my $init_dir = "cchinit/";
+my $update_dir = "cchupdate/";
+
+foreach my $file (qw (CODE DETAIL PLUS4 GEOCODE TXMATRIX ZIP)) {
+ my $tfile = $update_dir. $file. "T";
+ $tfile = $update_dir. "TXMATRIT" if $tfile =~ /TXMATRIXT$/;
+ open FILE, "$tfile.TXT" or die "Can't open $tfile.TXT\n";
+ open INSERT, ">$tfile.INS" or die "Can't open $tfile.INS\n";
+ open DELETE, ">$tfile.DEL" or die "Can't open $tfile.DEL\n";
+ while(<FILE>){
+ chomp;
+ print INSERT "$_\n" if s/I$//;
+ print DELETE "$_\n" if s/D$//;
+ }
+ close FILE;
+ close INSERT;
+ close DELETE;
+ system "sort $tfile.INS > $tfile.INSSORT";
+ system "sort $tfile.DEL > $tfile.DELSORT";
+ system "sort $init_dir$file.txt > $tfile.ORGINSSORT";
+ system "comm -12 $tfile.INSSORT $tfile.ORGINSSORT > $tfile.PREINS";
+ system "comm -23 $tfile.INSSORT $tfile.ORGINSSORT > $tfile.2BEINS";
+ system "comm -23 $tfile.DELSORT $tfile.ORGINSSORT > $tfile.PREDEL";
+ system "comm -12 $tfile.DELSORT $tfile.ORGINSSORT > $tfile.2BEDEL";
+}
+
+foreach my $file (qw (CODET DETAILT PLUS4T GEOCODET TXMATRIT ZIPT)) {
+ my $tfile = $update_dir. $file;
+ $tfile = "TXMATRIT" if $tfile eq "TXMATRIXT";
+ open INSERT, "$tfile.2BEINS" or die "Can't open $tfile.2BEINS\n";
+ open DELETE, "$tfile.2BEDEL" or die "Can't open $tfile.2BEDEL\n";
+ open FILE, ">$tfile.DOIT" or die "Can't open $tfile.DOIT\n";
+ while(<INSERT>){
+ chomp;
+ print FILE $_, "I\n";
+ }
+ while(<DELETE>){
+ chomp;
+ print FILE $_, "D\n";
+ }
+ close FILE;
+ close INSERT;
+ close DELETE;
+}
diff --git a/bin/cdr-netsapiens.import b/bin/cdr-netsapiens.import
new file mode 100755
index 0000000..8aa4ac0
--- /dev/null
+++ b/bin/cdr-netsapiens.import
@@ -0,0 +1,237 @@
+#!/usr/bin/perl
+#
+# */5 * * * /home/ivan/freeside/bin/cdr-netsapiens.import ivan exportnum
+
+use strict;
+use vars qw( $DEBUG );
+use Date::Format;
+use REST::Client;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record qw(qsearchs);
+use FS::part_export;
+use FS::cdr;
+
+$DEBUG = 1;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $exportnum = shift or die &usage;
+my $part_export = qsearchs('part_export', { 'exportnum' => $exportnum } )
+ or die "unknown exportnum $exportnum\n";
+
+my $cdrbatch = 'NetSapiens import '. time2str('%Y-%m-%d %x', $^T);
+
+my $cdrs = 0;
+
+do {
+
+ #find max time_release
+ my $sth = dbh->prepare('SELECT MAX(enddate) FROM cdr') #XXX and imported from this netsapens switch
+ or die dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+ my $time_release = time2str('%Y-%m-%d %X', $sth->fetchrow_arrayref->[0]);
+ #retreive CDRs >= this time
+
+ my $ns = $part_export->ns_command( 'GET', '/cdr/',
+ 'time_release' => "$time_release,",
+ '_sort' => '+time_release',
+ );
+
+ #loop over them, double check duplicates, insert the rest
+
+ my $content = $ns->responseContent;
+
+ $cdrs = 0;
+
+ #<a href="/tac2/cdr/20090219201719000016%40SkyNet360.Com">20090219201719000016@SkyNet360.Com</a>
+ # <form method="post" action="/tac2/cdr/20090219201719000016%40SkyNet360.Com">
+ while ( $content =~
+ s/^.*?<form class="" method="post" action="\/tac2\/cdr\/(\d{4})(\d+)\%40[^"]*">//is )
+ {
+
+ my $cdrid = ($1-1900). $2; #2009 -> 109 so we fit in a bigint
+
+ unless ( $cdrs ) { #inefficient
+ my $dsth = dbh->prepare(
+ 'SELECT cdrid FROM cdr WHERE cdrid IS NOT NULL AND cdrid = ?'
+ ) or die dbh->errstr;
+ $dsth->execute($cdrid) or die $dsth->errstr;
+ my $row = $dsth->fetchrow_arrayref;
+ if ( $row && $row->[0] eq $cdrid ) { # == w/ 8 byte int?
+ warn "$cdrid (dup)\n" if $DEBUG > 1;
+ next;
+ }
+ }
+ warn "$cdrid\n" if $DEBUG > 1;
+
+ $content =~ s/(.*?)<\/form>//is;
+ my $cdr_content = $1;
+
+ my %cdr = ();
+ while ( $cdr_content =~
+ s/.*?<input name="(\w+)" type="\w+" value="([^"]+)" \/>//is )
+ {
+ warn " $1 => $2\n" if $DEBUG > 2;
+ $cdr{$1} = $2;
+ }
+
+ $cdrs++;
+
+ my $cdr = new FS::cdr {
+ 'src' => $cdr{'orig_from_user'}, #orig_sub
+ 'dst' => $cdr{'orig_to_user'}, #term_sub?
+ 'startdate' => FS::cdr::_cdr_date_parse($cdr{'time_start'}),
+ 'enddate' => FS::cdr::_cdr_date_parse($cdr{'time_release'}),
+ 'duration' => $cdr{'duration'},
+ 'billsec' => $cdr{'time_talking'},
+ #'disposition' =>
+ #'accountcode' =>
+ #'charged_party'
+ 'cdrid' => $cdrid,
+ 'cdrbatch' => $cdrbatch,
+ };
+
+ my $error = $cdr->insert;
+ die $error if $error;
+
+ }
+
+} while $cdrs;
+
+sub usage {
+ "Usage: \n cdr-netsapiens.import user exportnum\n";
+}
+
+__END__
+
+ rly_prt_0 => 23946
+ orig_req_host => residential.skynet360.com
+ batch_dura => 0
+ orig_from_host => 63.251.149.5
+ batch_tim_beg => 2009-02-19 20:17:19
+ term_match => sip:7865457300@residential.skynet360.com
+ term_domain => residential.skynet360.com
+ term_sub => 7865457300
+ orig_req_user => 7865457300
+ orig_callid => 5D1164E6-44E011D6-8C84C368-EA5A0BC4@63.251.149.5
+ term_ip => 63.251.148.137:1453
+ term_to_uri => sip:7865457300@residential.skynet360.com
+ release_code => end
+ time_start => 2009-02-19 20:17:19.0
+ batch_hold => 0
+ orig_from_user => 9046384544
+ time_holding => 0
+ term_logi_uri => sip:7865457300@residential.skynet360.com
+ time_talking => 0
+ orig_from_uri => sip:9046384544@63.251.149.5
+ duration => 0
+ orig_logi_uri => sip:9046384544@63.251.149.5
+ rly_cnt_b => 0
+ time_insert => 2009-02-19 15:17:38.0
+ orig_to_user => 7865457300
+ rly_prt_a => 63.251.149.18:21972
+ cdr_index => 0
+ orig_to_host => 63.251.149.18
+ orig_match => sip:*@63.251.149.5
+ time_release => 2009-02-19 20:17:37
+ codec => G.711 u-law
+ orig_req_uri => sip:7865457300@residential.skynet360.com
+ orig_to_uri => sip:7865457300@63.251.149.18
+ rly_cnt_a => 13
+ orig_ip => 63.251.149.5:57326
+ release_text => Orig: Cancel
+ time_disp => 0
+ time_ringing => 2009-02-19 20:17:19
+ _method => put
+prt_0 => 23946
+ orig_req_host => residential.skynet360.com
+ batch_dura => 0
+ orig_from_host => 63.251.149.5
+ batch_tim_beg => 2009-02-19 20:17:19
+ term_match => sip:7865457300@residential.skynet360.com
+ term_domain => residential.skynet360.com
+ time_start => 2009-02-19 20:17:19.0
+ term_sub => 7865457300
+ orig_req_user => 7865457300
+ orig_callid => 5D1164E6-44E011D6-8C84C368-EA5A0BC4@63.251.149.5
+ term_ip => 63.251.148.137:1453
+ term_to_uri => sip:7865457300@residential.skynet360.com
+ release_code => end
+ time_start => 2009-02-19 20:17:19.0
+ batch_hold => 0
+ orig_from_user => 9046384544
+ time_holding => 0
+ term_logi_uri => sip:7865457300@residential.skynet360.com
+ time_talking => 0
+ orig_from_uri => sip:9046384544@63.251.149.5
+ duration => 0
+ orig_logi_uri => sip:9046384544@63.251.149.5
+ rly_cnt_b => 0
+ time_insert => 2009-02-19 15:17:38.0
+ orig_to_user => 7865457300
+ rly_prt_a => 63.251.149.18:21972
+ cdr_index => 0
+ orig_to_host => 63.251.149.18
+ orig_match => sip:*@63.251.149.5
+ time_release => 2009-02-19 20:17:37
+ codec => G.711 u-law
+ orig_req_uri => sip:7865457300@residential.skynet360.com
+ orig_to_uri => sip:7865457300@63.251.149.18
+ rly_cnt_a => 13
+ orig_ip => 63.251.149.5:57326
+ release_text => Orig: Cancel
+ time_disp => 0
+ time_ringing => 2009-02-19 20:17:19
+ _method => put
+
+list of freeside CDR fields, useful ones marked with *
+
+ acctid - primary key
+*[1] calldate - Call timestamp (SQL timestamp)
+ clid - Caller*ID with text
+* src - Caller*ID number / Source number
+* dst - Destination extension
+ dcontext - Destination context
+ channel - Channel used
+ dstchannel - Destination channel if appropriate
+ lastapp - Last application if appropriate
+ lastdata - Last application data
+* startdate - Start of call (UNIX-style integer timestamp)
+ answerdate - Answer time of call (UNIX-style integer timestamp)
+* enddate - End time of call (UNIX-style integer timestamp)
+* duration - Total time in system, in seconds
+* billsec - Total time call is up, in seconds
+*[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+ amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+ channel basis like accountcode.
+*[3] accountcode - CDR account number to use: account
+ uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+ userfield - CDR user-defined field
+ cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+*[4] charged_party - Service number to be billed
+ upstream_currency - Wholesale currency from upstream
+*[5] upstream_price - Wholesale price from upstream
+ upstream_rateplanid - Upstream rate plan ID
+ rated_price - Rated (or re-rated) price
+ distance - km (need units field?)
+ islocal - Local - 1, Non Local = 0
+*[6] calltypenum - Type of call - see FS::cdr_calltype
+ description - Description (cdr_type 7&8 only) (used for
+ cust_bill_pkg.itemdesc)
+ quantity - Number of items (cdr_type 7&8 only)
+ carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+ upstream_rateid - Upstream Rate ID
+ svcnum - Link to customer service (see FS::cust_svc)
+ freesidestatus - NULL, done (or something)
+
+[1] Auto-populated from startdate if not present
+[2] Package options available to ignore calls without a specific disposition
+[3] When using 'cdr-charged_party-accountcode' config
+[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present
+[5] When using 'upstream_simple' rating method.
+[6] Set to usage class classnum when using pre-rated CDRs and usage class-based
+ taxation (local/intrastate/interstate/international)
+
+
diff --git a/bin/cdr-transnexus.import b/bin/cdr-transnexus.import
new file mode 100755
index 0000000..b9fe41a
--- /dev/null
+++ b/bin/cdr-transnexus.import
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Net::SFTP::Foreign;
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::cdr;
+
+###
+# parse command line
+###
+
+use vars qw( $opt_p $opt_d $opt_v );
+getopts('v');
+
+$opt_p = 'last';
+$opt_d = 'done';
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# %%%FREESIDE_CACHE%%%
+my $cachedir = '/usr/local/etc/freeside/cache.'. datasrc. '/cdrs';
+mkdir $cachedir unless -d $cachedir;
+
+#my $format = shift or die &usage;
+my $format = 'transnexus';
+
+use vars qw( $servername );
+$servername = shift or die &usage;
+
+my $DIR = '/home/ossadmin/OSS/nexoss/CDR_ARCHIVE_BY_ACCOUNT';
+
+###
+# get the file list
+###
+
+warn "Retreiving directory listing\n" if $opt_v;
+
+my $ls_sftp = sftp();
+
+my $lsdir = $ls_sftp->ls($DIR);
+
+###
+# import each file in each dir
+###
+
+foreach my $dir ( @$lsdir ) {
+
+ my $dirname = $dir->{filename};
+ warn "Scanning dir $dirname\n" if $opt_v;
+
+ #my $ls = $ls_sftp->ls("$DIR/$dirname", wanted => qr/^$opt_p.*-CDRs$/i );
+ my $ls = $ls_sftp->ls("$DIR/$dirname", wanted => qr/^$opt_p.*Customer-CDRs$/i );
+
+ foreach my $file ( @$ls ) {
+
+ my $filename = $file->{filename};
+ warn "Downloading $filename\n" if $opt_v;
+
+ #get the file
+ my $get_sftp = sftp();
+ $get_sftp->get("$DIR/$dirname/$filename", "$cachedir/$filename")
+ or die "Can't get $filename: ". $get_sftp->error;
+
+ warn "Processing $filename\n" if $opt_v;
+
+ my $error = FS::cdr::batch_import( {
+ 'file' => "$cachedir/$filename",
+ 'format' => $format,
+ 'batch_namevalue' => $filename,
+ 'empty_ok' => 1,
+ } );
+ die $error if $error;
+
+ if ( $opt_d ) {
+ my $mv_sftp = sftp();
+ $mv_sftp->mkdir("$DIR/$dirname/$opt_d");
+ $mv_sftp->rename( "$DIR/$dirname/$filename",
+ "$DIR/$dirname/$opt_d/$filename" )
+ or die "can't move $filename to $opt_d: ". $mv_sftp->error;
+ }
+
+ unlink "$cachedir/$filename";
+
+ }
+
+}
+
+###
+# subs
+###
+
+sub usage {
+ "Usage: \n cdr-transnexus.import [ -v ] user [sftpuser@]servername\n";
+}
+
+use vars qw( $sftp );
+
+sub sftp {
+
+ #reuse connections
+ return $sftp if $sftp && $sftp->cwd;
+
+ my %sftp = ( host => $servername );
+
+ $sftp = Net::SFTP::Foreign->new(%sftp);
+ $sftp->error and die "SFTP connection failed: ". $sftp->error;
+
+ $sftp;
+}
+
+=head1 NAME
+
+cdr.sftp_and_import - Download CDR files from a remote server via SFTP
+
+=head1 SYNOPSIS
+
+ cdr-transnexus.import [ -v ] user [sftpuser@]servername
+
+=head1 DESCRIPTION
+
+Command line tool to download CDR files from a remote server via SFTP and then
+import them into the database.
+
+-v: verbose
+
+user: freeside username
+
+[sftpuser@]servername: remote server
+
+=head1 BUGS
+
+Hacked up copy of freeside-cdr-sftp_and_import
+
+=head1 SEE ALSO
+
+L<FS::cdr>
+
+=cut
+
+1;
+
diff --git a/bin/cdr.http_and_import b/bin/cdr.http_and_import
new file mode 100755
index 0000000..8910eec
--- /dev/null
+++ b/bin/cdr.http_and_import
@@ -0,0 +1,108 @@
+#!/usr/bin/perl
+#
+# Usage:
+# cdr.http_and_import [ -p prefix ] [ -e extension ] [ -v ] user format URL
+#
+# -e: file extension, defaults to .csv
+# -d: if specified, moves files to the specified folder when done
+
+use strict;
+use Getopt::Std;
+use WWW::IndexParser;
+#use LWP::UserAgent;
+use FS::UID qw(adminsuidsetup datasrc dbh);
+use FS::cdr;
+
+###
+# parse command line
+###
+
+use vars qw( $opt_p $opt_e $opt_v );
+getopts('p:e:v');
+
+$opt_e ||= 'csv';
+#$opt_e = ".$opt_e" unless $opt_e =~ /^\./;
+$opt_e =~ s/^\.//;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# %%%FREESIDE_CACHE%%%
+my $cachedir = '/usr/local/etc/freeside/cache.'. datasrc. '/cdrs';
+mkdir $cachedir unless -d $cachedir;
+
+my $format = shift or die &usage;
+
+use vars qw( $URL );
+$URL = shift or die &usage;
+
+###
+# get the file list
+###
+
+warn "Retreiving directory listing\n" if $opt_v;
+
+my @files = WWW::IndexParser->new(url => $URL);
+
+###
+# import each file
+###
+
+foreach my $file ( @files ) {
+
+ my $filename = $file->{filename};
+
+ if ( $opt_p ) { next unless $filename =~ /^$opt_p/ };
+ if ( $opt_e ) { next unless $filename =~ /\.$opt_e$/i };
+
+ #check and see if we've gotten this file already!!!
+ #just going to cheat with filenames in the cache for now
+ if ( -e "$cachedir/$filename" ) {
+ warn "Already have unprocessed $cachedir/$filename; skipping\n"; # if $opt_v;
+ next;
+ }
+ if ( -e "$cachedir/$filename.DONE" ) {
+ warn "Already processed $cachedir/$filename; skipping\n" if $opt_v;
+ next;
+ }
+
+ warn "Downloading $filename\n" if $opt_v;
+
+ #get the file
+
+ my $ua = LWP::UserAgent->new;
+ my $response = $ua->get("$URL/$filename");
+
+ unless ( $response->is_success ) {
+ die "Error retreiving $URL/$filename: ". $response->status_line;
+ }
+
+ open(FILE, ">$cachedir/$filename")
+ or die "can't open $cachedir/$filename: $!";
+ print FILE $response->content;
+ close FILE or die "can't close $cachedir/$filename: $!";
+
+ warn "Processing $filename\n" if $opt_v;
+
+ my $error = FS::cdr::batch_import( {
+ 'file' => "$cachedir/$filename",
+ 'format' => $format,
+ 'batch_namevalue' => $filename,
+ 'empty_ok' => 1,
+ } );
+ die $error if $error;
+
+ close FILE;
+
+ rename("$cachedir/$filename", "$cachedir/$filename.DONE");
+
+}
+
+###
+# sub
+###
+
+sub usage {
+ "Usage: \n cdr.http_and_import [ -p prefix ] [ -e extension ] [ -v ] user format URL\n";
+}
+
diff --git a/bin/cdr.import b/bin/cdr.import
new file mode 100644
index 0000000..36266ef
--- /dev/null
+++ b/bin/cdr.import
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+#
+# Usage:
+# cdr.import user format filename
+#
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::cdr;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $format = shift or die &usage;
+
+my $file = shift;
+
+my $error = FS::cdr::batch_import( {
+ 'file' => $file,
+ 'format' => $format,
+ 'batch_namevalue' => $file,
+} );
+die $error if $error;
+
+sub usage {
+ "Usage: \n cdr.import user format filename\n";
+}
+
diff --git a/bin/cdr_calltype.import b/bin/cdr_calltype.import
new file mode 100755
index 0000000..a998284
--- /dev/null
+++ b/bin/cdr_calltype.import
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -w
+#
+# bin/cdr_calltype.import ivan ~ivan/convergent/newspecs/fixed_inbound/calltypes.csv
+
+use strict;
+use FS::UID qw(dbh adminsuidsetup);
+use FS::cdr_calltype;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+while (<>) {
+
+ chomp;
+ my $line = $_;
+
+ #$line =~ /^(\d+),"([^"]+)"$/ or do {
+ $line =~ /^(\d+),"([^"]+)"/ or do {
+ warn "unparsable line: $line\n";
+ next;
+ };
+
+ my $cdr_calltype = new FS::cdr_calltype {
+ 'calltypenum' => $1,
+ 'calltypename' => $2,
+ };
+
+ #my $error = $cdr_calltype->check;
+ my $error = $cdr_calltype->insert;
+ if ( $error ) {
+ warn "********** $error FOR LINE: $line\n";
+ dbh->commit;
+ #my $wait = scalar(<STDIN>);
+ }
+
+}
+
+sub usage {
+ "Usage:\n\ncdr_calltype.import username filename ...\n";
+}
+
diff --git a/bin/cdr_upstream_rate.import b/bin/cdr_upstream_rate.import
new file mode 100755
index 0000000..fda3883
--- /dev/null
+++ b/bin/cdr_upstream_rate.import
@@ -0,0 +1,142 @@
+#!/usr/bin/perl -w
+#
+# Usage: bin/cdr_upstream_rate.import username ratenum filename
+#
+# records will be imported into cdr_upstream_rate, rate_detail and rate_region
+#
+# Example: bin/cdr_upstream_rate.import ivan 1 ~ivan/convergent/sample_rate_table.csv
+#
+# username: a freeside login (from /usr/local/etc/freeside/mapsecrets)
+# ratenum: rate plan (FS::rate) created with the web UI
+# filename: CSV file
+#
+# the following fields are currently used:
+# - Class Code => cdr_upstream_rate.rateid
+# - Description => rate_region.regionname
+# (rate_detail->dest_region)
+# - 1_rate => ( * 60 / 1_rate_seconds ) => rate_detail.min_charge
+# - 1_rate_seconds => (used above)
+# - 1_second_increment => rate_detail.sec_granularity
+#
+# the following fields are not (yet) used:
+# - Flagfall => what's this for?
+#
+# - 1_cap_time => freeside doesn't have voip time caps yet...
+# - 1_cap_cost => freeside doesn't have voip cost caps yet...
+# - 1_repeat => not sure what this is for, sample data is all 0
+#
+# - 2_rate => \
+# - 2_rate_seconds => |
+# - 2_second_increment => | not sure what the second set of rate data
+# - 2_cap_time => | is supposed to be for...
+# - 2_cap_cost => |
+# - 2_repeat => /
+#
+# - Carrier => probably not needed?
+# - Start Date => not necessary?
+
+use strict;
+use vars qw( $DEBUG );
+use Text::CSV_XS;
+use FS::UID qw(dbh adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::rate;
+use FS::cdr_upstream_rate;
+use FS::rate_detail;
+use FS::rate_region;
+
+$DEBUG = 1;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $ratenum = shift or die &usage;
+
+my $rate = qsearchs( 'rate', { 'ratenum' => $ratenum } );
+die "rate plan $ratenum not found in rate table\n"
+ unless $rate;
+
+my $csv = new Text::CSV_XS;
+my $hline = scalar(<>);
+chomp($hline);
+$csv->parse($hline) or die "can't parse header: $hline\n";
+my @header = $csv->fields();
+
+$FS::UID::AutoCommit = 0;
+
+while (<>) {
+
+ chomp;
+ my $line = $_;
+
+# #$line =~ /^(\d+),"([^"]+)"$/ or do {
+# #}
+# $line =~ /^(\d+),"([^"]+)"/ or do {
+# warn "unparsable line: $line\n";
+# next;
+# };
+
+ $csv->parse($line) or die "can't parse line: $line\n";
+ my @line = $csv->fields();
+
+ my %hash = map { $_ => shift(@line) } @header;
+
+ warn join('', map { "$_ => $hash{$_}\n" } keys %hash )
+ if $DEBUG > 1;
+
+ my $rate_region = new FS::rate_region {
+ 'regionname' => $hash{'Description'}
+ };
+
+ my $error = $rate_region->insert;
+ if ( $error ) {
+ dbh->rollback;
+ die "error inserting into rate_region: $error\n";
+ }
+ my $dest_regionnum = $rate_region->regionnum;
+ warn "rate_region $dest_regionnum inserted\n"
+ if $DEBUG;
+
+ my $rate_detail = new FS::rate_detail {
+ 'ratenum' => $ratenum,
+ 'dest_regionnum' => $dest_regionnum,
+ 'min_included' => 0,
+ #'min_charge', => sprintf('%.5f', 60 * $hash{'1_rate'} / $hash{'1_rate_seconds'} ),
+ 'min_charge', => sprintf('%.5f', $hash{'1_rate'} /
+ ( $hash{'1_rate_seconds'} / 60 )
+ ),
+ 'sec_granularity' => $hash{'1_second_increment'},
+ };
+ $error = $rate_detail->insert;
+ if ( $error ) {
+ dbh->rollback;
+ die "error inserting into rate_detail: $error\n";
+ }
+ my $ratedetailnum = $rate_detail->ratedetailnum;
+ warn "rate_detail $ratedetailnum inserted\n"
+ if $DEBUG;
+
+ my $cdr_upstream_rate = new FS::cdr_upstream_rate {
+ 'upstream_rateid' => $hash{'Class Code'},
+ 'ratedetailnum' => $rate_detail->ratedetailnum,
+ };
+ $error = $cdr_upstream_rate->insert;
+ if ( $error ) {
+ dbh->rollback;
+ die "error inserting into cdr_upstream_rate: $error\n";
+ }
+ warn "cdr_upstream_rate ". $cdr_upstream_rate->upstreamratenum. " inserted\n"
+ if $DEBUG;
+
+ dbh->commit or die "can't commit: ". dbh->errstr;
+
+ warn "\n" if $DEBUG;
+
+}
+
+dbh->commit or die "can't commit: ". dbh->errstr;
+
+sub usage {
+ "Usage:\n\ncdr_upstream_rate.import username ratenum filename\n";
+}
+
diff --git a/bin/confdiff b/bin/confdiff
new file mode 100755
index 0000000..5b6af85
--- /dev/null
+++ b/bin/confdiff
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+
+adminsuidsetup('ivan');
+
+my $conf = new FS::Conf;
+
+my $file2 = pop @ARGV;
+my $file1 = pop @ARGV;
+
+open(FILE1, ">/tmp/$file1") or die "can't open /tmp/$file1: $!";
+print FILE1 $conf->config($file1);
+print FILE1 "\n";
+close FILE1 or die $!;
+
+open(FILE2, ">/tmp/$file2") or die "can't open /tmp/$file2: $!";
+print FILE2 $conf->config($file2);
+print FILE2 "\n";
+close FILE2 or die $!;
+
+my @opt = @ARGV;
+
+system('diff', @opt, "/tmp/$file1", "/tmp/$file2");
+
+#unlink("/tmp/$file1', "/tmp/$file2");
diff --git a/bin/countdeclines b/bin/countdeclines
new file mode 100755
index 0000000..bbc3925
--- /dev/null
+++ b/bin/countdeclines
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use Date::Parse;
+
+my $e = 'PlugnPay error: 97: Declined for CVV failure';
+my @y = (2008,2009);
+
+my $p = 0;
+
+foreach my $y (@y) {
+ foreach my $m (1..12) {
+ my $d = "$m/1/$y";
+ my $t = str2time($d);
+
+ #print "$pd-$d: SELECT count(*) from cust_bill_event where statustext = '$e' and _date >= $p and _date < $t;\n"
+ print "SELECT count(*) from cust_bill_event where statustext = '$e' and _date >= $p and _date < $t;\n"
+ if $p;
+
+ $p = $t;
+ $pd = $d;
+ }
+}
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/cust_main_special.pm b/bin/cust_main_special.pm
new file mode 100644
index 0000000..967b6be
--- /dev/null
+++ b/bin/cust_main_special.pm
@@ -0,0 +1,608 @@
+package cust_main_special;
+
+require 5.006;
+use strict;
+use vars qw( @ISA $DEBUG $me $conf );
+use Safe;
+use Carp;
+use Data::Dumper;
+use Date::Format;
+use FS::UID qw( dbh );
+use FS::Record qw( qsearchs qsearch );
+use FS::payby;
+use FS::cust_pkg;
+use FS::cust_bill;
+use FS::cust_bill_pkg;
+use FS::cust_bill_pkg_display;
+use FS::cust_bill_pkg_tax_location;
+use FS::cust_main_county;
+use FS::cust_location;
+use FS::tax_rate;
+use FS::cust_tax_location;
+use FS::part_pkg_taxrate;
+use FS::queue;
+use FS::part_pkg;
+
+@ISA = qw ( FS::cust_main );
+
+$DEBUG = 0;
+$me = '[emergency billing program]';
+
+$conf = new FS::Conf;
+
+=head1 METHODS
+
+=over 4
+
+=item bill OPTIONS
+
+Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
+conjunction with the collect method by calling B<bill_and_collect>.
+
+If there is an error, returns the error, otherwise returns false.
+
+Options are passed as name-value pairs. Currently available options are:
+
+=over 4
+
+=item resetup
+
+If set true, re-charges setup fees.
+
+=item 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') );
+
+=item pkg_list
+
+An array ref of specific packages (objects) to attempt billing, instead trying all of them.
+
+ $cust_main->bill( pkg_list => [$pkg1, $pkg2] );
+
+=item invoice_time
+
+Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
+
+=item backbill
+
+Used to specify the period starting date and preventing normal billing. Instead all outstanding cdrs/usage are processed as if from the unix timestamp in backbill and without changing the dates in the customer packages. Useful in those situations when cdrs were not imported before a billing run
+
+=back
+
+=cut
+
+sub bill {
+ my( $self, %options ) = @_;
+
+ bless $self, 'cust_main_special';
+ return '' if $self->payby eq 'COMP';
+ warn "$me backbill usage for customer ". $self->custnum. "\n"
+ if $DEBUG;
+
+ my $time = $options{'time'} || time;
+ 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 @cust_bill_pkg = ();
+
+ ###
+ # find the packages which are due for billing, find out how much they are
+ # & generate invoice database.
+ ###
+
+ my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 );
+ my %taxlisthash;
+ my @precommit_hooks = ();
+
+ my @cust_pkgs = qsearch('cust_pkg', { 'custnum' => $self->custnum } );
+ foreach my $cust_pkg (@cust_pkgs) {
+
+ #NO!! next if $cust_pkg->cancel;
+ next if $cust_pkg->getfield('cancel');
+
+ warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1;
+
+ #? to avoid use of uninitialized value errors... ?
+ $cust_pkg->setfield('bill', '')
+ unless defined($cust_pkg->bill);
+
+ #my $part_pkg = $cust_pkg->part_pkg;
+
+ my $real_pkgpart = $cust_pkg->pkgpart;
+ my %hash = $cust_pkg->hash;
+
+ foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
+
+ $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
+
+ my $error =
+ $self->_make_lines( 'part_pkg' => $part_pkg,
+ 'cust_pkg' => $cust_pkg,
+ 'precommit_hooks' => \@precommit_hooks,
+ 'line_items' => \@cust_bill_pkg,
+ 'setup' => \$total_setup,
+ 'recur' => \$total_recur,
+ 'tax_matrix' => \%taxlisthash,
+ 'time' => $time,
+ 'options' => \%options,
+ );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ } #foreach my $part_pkg
+
+ } #foreach my $cust_pkg
+
+ unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items
+ unless ( $options{backbill} ) {
+ #but do commit any package date cycling that happened
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ } else {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ }
+ return '';
+ }
+
+ my $postal_pkg = $self->charge_postal_fee();
+ if ( $postal_pkg && !ref( $postal_pkg ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't charge postal invoice fee for customer ".
+ $self->custnum. ": $postal_pkg";
+ }
+ if ( !$options{backbill} && $postal_pkg &&
+ ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
+ !$conf->exists('postal_invoice-recurring_only')
+ )
+ )
+ {
+ foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
+ my $error =
+ $self->_make_lines( 'part_pkg' => $part_pkg,
+ 'cust_pkg' => $postal_pkg,
+ 'precommit_hooks' => \@precommit_hooks,
+ 'line_items' => \@cust_bill_pkg,
+ 'setup' => \$total_setup,
+ 'recur' => \$total_recur,
+ 'tax_matrix' => \%taxlisthash,
+ 'time' => $time,
+ 'options' => \%options,
+ );
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ warn "having a look at the taxes we found...\n" if $DEBUG > 2;
+
+ # keys are tax names (as printed on invoices / itemdesc )
+ # values are listrefs of taxlisthash keys (internal identifiers)
+ my %taxname = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are (cumulative) amounts
+ my %tax = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are listrefs of cust_bill_pkg_tax_location hashrefs
+ my %tax_location = ();
+
+ foreach my $tax ( keys %taxlisthash ) {
+ my $tax_object = shift @{ $taxlisthash{$tax} };
+ warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
+ warn " ". join('/', @{ $taxlisthash{$tax} } ). "\n" if $DEBUG > 2;
+ my $hashref_or_error =
+ $tax_object->taxline( $taxlisthash{$tax},
+ 'custnum' => $self->custnum,
+ 'invoice_time' => $invoice_time
+ );
+ unless ( ref($hashref_or_error) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $hashref_or_error;
+ }
+ unshift @{ $taxlisthash{$tax} }, $tax_object;
+
+ my $name = $hashref_or_error->{'name'};
+ my $amount = $hashref_or_error->{'amount'};
+
+ #warn "adding $amount as $name\n";
+ $taxname{ $name } ||= [];
+ push @{ $taxname{ $name } }, $tax;
+
+ $tax{ $tax } += $amount;
+
+ $tax_location{ $tax } ||= [];
+ if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
+ push @{ $tax_location{ $tax } },
+ {
+ 'taxnum' => $tax_object->taxnum,
+ 'taxtype' => ref($tax_object),
+ 'pkgnum' => $tax_object->get('pkgnum'),
+ 'locationnum' => $tax_object->get('locationnum'),
+ 'amount' => sprintf('%.2f', $amount ),
+ };
+ }
+
+ }
+
+ #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
+ my %packagemap = map { $_->pkgnum => $_ } @cust_bill_pkg;
+ foreach my $tax ( keys %taxlisthash ) {
+ foreach ( @{ $taxlisthash{$tax} }[1 ... scalar(@{ $taxlisthash{$tax} })] ) {
+ next unless ref($_) eq 'FS::cust_bill_pkg';
+
+ push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg },
+ splice( @{ $_->_cust_tax_exempt_pkg } );
+ }
+ }
+
+ #consolidate and create tax line items
+ warn "consolidating and generating...\n" if $DEBUG > 2;
+ foreach my $taxname ( keys %taxname ) {
+ my $tax = 0;
+ my %seen = ();
+ my @cust_bill_pkg_tax_location = ();
+ warn "adding $taxname\n" if $DEBUG > 1;
+ foreach my $taxitem ( @{ $taxname{$taxname} } ) {
+ next if $seen{$taxitem}++;
+ warn "adding $tax{$taxitem}\n" if $DEBUG > 1;
+ $tax += $tax{$taxitem};
+ push @cust_bill_pkg_tax_location,
+ map { new FS::cust_bill_pkg_tax_location $_ }
+ @{ $tax_location{ $taxitem } };
+ }
+ next unless $tax;
+
+ $tax = sprintf('%.2f', $tax );
+ $total_setup = sprintf('%.2f', $total_setup+$tax );
+
+ push @cust_bill_pkg, new FS::cust_bill_pkg {
+ 'pkgnum' => 0,
+ 'setup' => $tax,
+ 'recur' => 0,
+ 'sdate' => '',
+ 'edate' => '',
+ 'itemdesc' => $taxname,
+ 'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
+ };
+
+ }
+
+ my $charged = sprintf('%.2f', $total_setup + $total_recur );
+
+ #create the new invoice
+ my $cust_bill = new FS::cust_bill ( {
+ 'custnum' => $self->custnum,
+ '_date' => ( $invoice_time ),
+ 'charged' => $charged,
+ } );
+ my $error = $cust_bill->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice for customer #". $self->custnum. ": $error";
+ }
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+ $cust_bill_pkg->invnum($cust_bill->invnum);
+ my $error = $cust_bill_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice line item: $error";
+ }
+ }
+
+
+ #foreach my $hook ( @precommit_hooks ) {
+ # eval {
+ # &{$hook}; #($self) ?
+ # };
+ # if ( $@ ) {
+ # $dbh->rollback if $oldAutoCommit;
+ # return "$@ running precommit hook $hook\n";
+ # }
+ #}
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+
+sub _make_lines {
+ my ($self, %params) = @_;
+
+ warn " making lines\n" if $DEBUG > 1;
+ my $part_pkg = $params{part_pkg} or die "no part_pkg specified";
+ my $cust_pkg = $params{cust_pkg} or die "no cust_pkg specified";
+ my $precommit_hooks = $params{precommit_hooks} or die "no package specified";
+ my $cust_bill_pkgs = $params{line_items} or die "no line buffer specified";
+ my $total_setup = $params{setup} or die "no setup accumulator specified";
+ my $total_recur = $params{recur} or die "no recur accumulator specified";
+ my $taxlisthash = $params{tax_matrix} or die "no tax accumulator specified";
+ my $time = $params{'time'} or die "no time specified";
+ my (%options) = %{$params{options}};
+
+ my $dbh = dbh;
+ my $real_pkgpart = $cust_pkg->pkgpart;
+ my %hash = $cust_pkg->hash;
+ my $old_cust_pkg = new FS::cust_pkg \%hash;
+ my $backbill = $options{backbill} || 0;
+
+ my @details = ();
+
+ my $lineitems = 0;
+
+ $cust_pkg->pkgpart($part_pkg->pkgpart);
+
+ ###
+ # bill setup
+ ###
+
+ my $setup = 0;
+ my $unitsetup = 0;
+ if ( ! $cust_pkg->setup &&
+ (
+ ( $conf->exists('disable_setup_suspended_pkgs') &&
+ ! $cust_pkg->getfield('susp')
+ ) || ! $conf->exists('disable_setup_suspended_pkgs')
+ )
+ || $options{'resetup'}
+ ) {
+
+ warn " bill setup\n" if $DEBUG > 1;
+ $lineitems++;
+
+ $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
+ return "$@ running calc_setup for $cust_pkg\n"
+ if $@;
+
+ $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh
+
+ $cust_pkg->setfield('setup', $time)
+ unless $cust_pkg->setup;
+ #do need it, but it won't get written to the db
+ #|| $cust_pkg->pkgpart != $real_pkgpart;
+
+ }
+
+ ###
+ # bill recurring fee
+ ###
+
+ #XXX unit stuff here too
+ my $recur = 0;
+ my $unitrecur = 0;
+ my $sdate;
+ if ( ! $cust_pkg->getfield('susp') and
+ ( $part_pkg->getfield('freq') ne '0' &&
+ ( $cust_pkg->getfield('bill') || 0 ) <= $time
+ )
+ || ( $part_pkg->plan eq 'voip_cdr'
+ && $part_pkg->option('bill_every_call')
+ )
+ || $backbill
+ ) {
+
+ # XXX should this be a package event? probably. events are called
+ # at collection time at the moment, though...
+ $part_pkg->reset_usage($cust_pkg, 'debug'=>$DEBUG)
+ if $part_pkg->can('reset_usage');
+ #don't want to reset usage just cause we want a line item??
+ #&& $part_pkg->pkgpart == $real_pkgpart;
+
+ warn " bill recur\n" if $DEBUG > 1;
+ $lineitems++;
+
+ # XXX shared with $recur_prog
+ $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+ $sdate = $cust_pkg->lastbill || $backbill if $backbill;
+
+ #over two params! lets at least switch to a hashref for the rest...
+ my $increment_next_bill = ( $part_pkg->freq ne '0'
+ && ( $cust_pkg->getfield('bill') || 0 ) <= $time
+ );
+ my %param = ( 'precommit_hooks' => $precommit_hooks,
+ 'increment_next_bill' => $increment_next_bill,
+ );
+
+ $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
+ return "$@ running calc_recur for $cust_pkg\n"
+ if ( $@ );
+
+
+ warn "details is now: \n" if $DEBUG > 2;
+ warn Dumper(\@details) if $DEBUG > 2;
+
+ if ( $increment_next_bill ) {
+
+ my $next_bill = $part_pkg->add_freq($sdate);
+ return "unparsable frequency: ". $part_pkg->freq
+ if $next_bill == -1;
+
+ #pro-rating magic - if $recur_prog fiddled $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;
+ $sdate = $cust_pkg->lastbill || $backbill if $backbill;
+ #no need, its in $hash{last_bill}# my $last_bill = $cust_pkg->last_bill;
+ $cust_pkg->last_bill($sdate);
+
+ $cust_pkg->setfield('bill', $next_bill );
+
+ }
+
+ }
+
+ 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 there's line items, create em cust_bill_pkg records
+ # If $cust_pkg has been modified, update it (if we're a real pkgpart)
+ ###
+
+ if ( $lineitems ) {
+
+ if ( !$backbill && $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
+ # hmm.. and if just the options are modified in some weird price plan?
+
+ warn " package ". $cust_pkg->pkgnum. " modified; updating\n"
+ if $DEBUG >1;
+
+ my $error = $cust_pkg->replace( $old_cust_pkg,
+ 'options' => { $cust_pkg->options },
+ );
+ return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error"
+ if $error; #just in case
+ }
+
+ my @cust_pkg_detail = map { $_->detail } $cust_pkg->cust_pkg_detail('I');
+ if ( $DEBUG > 1 ) {
+ warn " tentatively adding customer package invoice detail: $_\n"
+ foreach @cust_pkg_detail;
+ }
+ push @details, @cust_pkg_detail;
+
+ $setup = sprintf( "%.2f", $setup );
+ $recur = sprintf( "%.2f", $recur );
+ my $cust_bill_pkg = new FS::cust_bill_pkg {
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'setup' => $setup,
+ 'unitsetup' => $unitsetup,
+ 'recur' => $recur,
+ 'unitrecur' => $unitrecur,
+ 'quantity' => $cust_pkg->quantity,
+ 'details' => \@details,
+ };
+
+ warn "created cust_bill_pkg which looks like:\n" if $DEBUG > 2;
+ warn Dumper($cust_bill_pkg) if $DEBUG > 2;
+ if ($backbill) {
+ my %usage_cust_bill_pkg = $cust_bill_pkg->disintegrate;
+ $recur = 0;
+ foreach my $key (keys %usage_cust_bill_pkg) {
+ next if ($key eq 'setup' || $key eq 'recur');
+ $recur += $usage_cust_bill_pkg{$key}->recur;
+ }
+ $setup = 0;
+ }
+
+ $setup = sprintf( "%.2f", $setup );
+ $recur = sprintf( "%.2f", $recur );
+ if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
+ return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
+ }
+ if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
+ return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
+ }
+
+
+ if ( $setup != 0 || $recur != 0 ) {
+
+ warn " charges (setup=$setup, recur=$recur); adding line items\n"
+ if $DEBUG > 1;
+
+ $cust_bill_pkg->setup($setup);
+ $cust_bill_pkg->recur($recur);
+
+ warn "cust_bill_pkg now looks like:\n" if $DEBUG > 2;
+ warn Dumper($cust_bill_pkg) if $DEBUG > 2;
+
+ if ( $part_pkg->option('recur_temporality', 1) eq 'preceding' ) {
+ $cust_bill_pkg->sdate( $hash{last_bill} );
+ $cust_bill_pkg->edate( $sdate - 86399 ); #60s*60m*24h-1
+ } else { #if ( $part_pkg->option('recur_temporality', 1) eq 'upcoming' ) {
+ $cust_bill_pkg->sdate( $sdate );
+ $cust_bill_pkg->edate( $cust_pkg->bill );
+ }
+
+ $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
+ unless $part_pkg->pkgpart == $real_pkgpart;
+
+ $$total_setup += $setup;
+ $$total_recur += $recur;
+
+ ###
+ # handle taxes
+ ###
+
+ my $error =
+ $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time});
+ return $error if $error;
+
+ push @$cust_bill_pkgs, $cust_bill_pkg;
+
+ } #if $setup != 0 || $recur != 0
+
+ } #if $line_items
+
+ '';
+
+}
+
+
+sub _gather_taxes {
+ my $self = shift;
+ my $part_pkg = shift;
+ my $class = shift;
+
+ my @taxes = ();
+ my $geocode = $self->geocode('cch');
+
+ my @taxclassnums = map { $_->taxclassnum }
+ $part_pkg->part_pkg_taxoverride($class);
+
+ unless (@taxclassnums) {
+ @taxclassnums = map { $_->taxclassnum }
+ $part_pkg->part_pkg_taxrate('cch', $geocode, $class);
+ }
+ warn "Found taxclassnum values of ". join(',', @taxclassnums)
+ if $DEBUG;
+
+ my $extra_sql =
+ "AND (".
+ join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")";
+
+ @taxes = grep { ($_->fee || 0 ) == 0 } #ignore unit based taxes
+ qsearch({ 'table' => 'tax_rate',
+ 'hashref' => { 'geocode' => $geocode, },
+ 'extra_sql' => $extra_sql,
+ })
+ if scalar(@taxclassnums);
+
+ warn "Found taxes ".
+ join(',', map{ ref($_). " ". $_->get($_->primary_key) } @taxes). "\n"
+ if $DEBUG;
+
+ [ @taxes ];
+
+}
+
+
+=back
+
+
+=cut
+
+1;
+
diff --git a/bin/cust_pay_histogram b/bin/cust_pay_histogram
new file mode 100755
index 0000000..714b321
--- /dev/null
+++ b/bin/cust_pay_histogram
@@ -0,0 +1,115 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use Date::Parse;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw( qsearch );
+use FS::cust_pay;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw(%opt);
+getopts("p:a:b:e:", \%opt);
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my @where = ();
+
+push @where, 'agentnum = '. $dbh->quote($opt{a}) if $opt{a};
+push @where, 'cust_pay.payby = '. $dbh->quote($opt{p}) if $opt{p};
+push @where, 'cust_pay._date > '. $dbh->quote(str2time($opt{b})) if $opt{b};
+push @where, 'cust_pay._date < '. $dbh->quote(str2time($opt{e})) if $opt{e};
+
+my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : '';
+my $addl_from = 'LEFT JOIN cust_main USING( custnum )';
+
+my @payrow = qsearch( { table => 'cust_pay',
+ hashref => {},
+ select => 'count(*) AS quantity, paid',
+ addl_from => $addl_from,
+ extra_sql => $extra_sql,
+ order_by => 'GROUP BY paid',
+ }
+ );
+
+my $max = 0;
+my $sum = 0;
+foreach (@payrow) {
+ $sum += $_->quantity;
+ $max = $_->quantity if $_->quantity > $max;
+}
+my $scale = int($max/60) + 1;
+
+print "\n PAYMENTS RECEIVED";
+print " AFTER $opt{b}" if $opt{b};
+print " UNTIL $opt{e}" if $opt{e};
+print " VIA $opt{p}" if $opt{p};
+print " BY AGENT $opt{a}" if $opt{a};
+print "\n\n";
+print "Total number of payments: $sum\n\n";
+print "(each * represents $scale)\n\n" if $scale > 1;
+
+foreach my $payrow ( @payrow ) {
+ print sprintf("%10.2f", $payrow->paid),
+ ": ",
+ sprintf("%6d", $payrow->quantity),
+ "| ",
+ '*' x($payrow->quantity/$scale),
+ "\n";
+}
+
+print "\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 cust_pay_histogram [ -b 'begin_date' ] [ -e 'end_date' ] [ -p 'payby' ] [ -a agentnum ] user\n";
+}
+
+###
+# documentation
+###
+
+=head1 NAME
+
+cust_pay_histogram - Show a histogram of payments made for a date range.
+
+=head1 SYNOPSIS
+
+ freeside-daily [ -b 'begin_date' ] [ -e 'end_date'] [ -p 'payby' ] [ -a agentnum ] user
+
+=head1 DESCRIPTION
+
+Displays a histogram of cust_pay records in the database.
+
+ -b: Include only payments since 'begin_date'. Date is in any format Date::Parse is happy with, but be careful.
+
+ -e: Include only payments before 'end_date'. Date is in any format Date::Parse is happy with, but be careful.
+
+ -p: Only process payments with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
+
+ -a: Only process payments of customers with the specified agentnum
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_pay>
+
+=cut
+
diff --git a/bin/customer-faker b/bin/customer-faker
new file mode 100755
index 0000000..236a412
--- /dev/null
+++ b/bin/customer-faker
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Data::Faker;
+use Business::CreditCard;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::svc_acct;
+
+my $refnum = 1;
+
+#my @pkgs = ( 4, 5, 6 );
+my $svcpart = 2;
+
+use vars qw( $opt_p $opt_a $opt_k );
+getopts('p:a:k:');
+
+my $agentnum = $opt_a || 1;
+
+my @pkgs = $opt_k ? split(/,\s*/, $opt_k) : ( 2, 3, 4 );
+
+my $user = shift or die &usage;
+my $num = shift or die &usage;
+adminsuidsetup($user);
+
+my $onum = $num;
+my $start = time;
+
+my @states = qw( AL AK AS AZ AR CA CO CT DE DC FL GA GU HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND MP OH OK OR PA PR RI SC SD TN TX UT VT VI VA WA WV WI WY );
+#FM MH
+
+until ( $num-- <= 0 ) {
+
+ my $faker = new Data::Faker;
+
+ my $cust_main = new FS::cust_main {
+ 'agentnum' => $agentnum,
+ 'refnum' => $refnum,
+ 'first' => $faker->first_name,
+ 'last' => $faker->last_name,
+ 'company' => ( $num % 2 ? $faker->company. ', '. $faker->company_suffix : '' ), #half with companies..
+ 'address1' => $faker->street_address,
+ 'city' => 'Tofutown', #missing, so everyone is from tofutown# $faker->city,
+ #'state' => $faker->us_state_abbr,
+ 'state' => $states[ int(rand($#states)) ],
+ 'zip' => $faker->us_zip_code,
+ 'country' => 'US',
+ 'daytime' => $faker->phone_number,
+ 'night' => $faker->phone_number,
+ #forget it, these can have extensions# 'fax' => ( $num % 2 ? $faker->phone_number : '' ), #ditto
+ #bah, forget shipping addresses
+ 'payby' => 'BILL',
+ 'payip' => $faker->ip_address,
+ };
+
+ if ( $opt_p eq 'CARD' || ( !$opt_p && rand() > .33 ) ) {
+ $cust_main->payby('CARD');
+ my $cardnum = '4123'. sprintf('%011u', int(rand(100000000000)) );
+ $cust_main->payinfo( $cardnum. generate_last_digit($cardnum) );
+ $cust_main->paydate( '2009-05-01' );
+ } elsif ( $opt_p eq 'CHEK' || ( !$opt_p && rand() > .66 ) ) {
+ $cust_main->payby('CHEK');
+ my $payinfo = sprintf('%7u@%09u', int(rand(10000000)), int(rand(1000000000)) );
+ $cust_main->payinfo($payinfo);
+ $cust_main->payname( 'First International Bank of Testing' );
+ }
+
+ # could insert invoicing_list and other stuff too.. hell, could insert
+ # packages, services, more
+ # but i just wanted 10k customers to test the pager and this was good enough
+ # not anymore, here's some services and packages
+
+ my $now = time;
+ my $year = 31556736; #60*60*24*365.24
+ my $setup = $now - int(rand($year));
+
+ my $cust_pkg = new FS::cust_pkg {
+ 'pkgpart' => $pkgs[ int(rand(scalar(@pkgs))) ],
+
+ #some dates in here would be nice
+ 'setup' => $setup,
+ #'last_bill'
+ #'bill'
+ #'susp'
+ #'expire'
+ #'cancel'
+ };
+
+ my $svc_acct = new FS::svc_acct {
+ 'svcpart' => $svcpart,
+ 'username' => $faker->username,
+ };
+
+ while ( qsearch( 'svc_acct', { 'username' => $svc_acct->username } ) ) {
+ my $username = $svc_acct->username;
+ $username++;
+ $svc_acct->username($username);
+ }
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash',
+ $cust_pkg => [ $svc_acct ],
+ ;
+
+ my $error = $cust_main->insert( \%hash );
+ die $error if $error;
+
+}
+
+my $end = time;
+
+my $sec = $end-$start;
+$sec=1 if $sec==0;
+my $persec = $onum / $sec;
+print "$onum customers inserted in $sec seconds ($persec customers/sec)\n";
+
+#---
+
+sub usage {
+ die "Usage:\n\n customer-faker [ -p payby ] [ -a agentnum ] [ -k pkgpart,pkgpart,pkgpart... ] user num_fakes\n";
+}
diff --git a/bin/cvs2cl b/bin/cvs2cl
new file mode 100755
index 0000000..1c1bfb0
--- /dev/null
+++ b/bin/cvs2cl
@@ -0,0 +1,2 @@
+#!/bin/sh
+cvs2cl -F trunk
diff --git a/bin/del-old-history b/bin/del-old-history
new file mode 100755
index 0000000..5c9412a
--- /dev/null
+++ b/bin/del-old-history
@@ -0,0 +1,30 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record; #why is this necessary
+
+#WARNING: not all tables are safe to remove history!
+# these are, and seem to take the most space in a typical install with queued
+# exports
+my @tables = qw( h_queue h_queue_arg );
+
+my $years = 2;
+my $seconds = $years * 31556926; #60*60*24*365.2422 is close enough
+my $before = int( time - $seconds );
+
+adminsuidsetup shift or die "usage: del-old-history user\n";
+
+foreach my $table ( @tables ) {
+
+ unless ( $table =~ /^h_/ ) {
+ warn "$table is not a history table, skipping\n";
+ next;
+ }
+
+ my $sql = "DELETE FROM $table WHERE history_date < $before";
+ warn "$sql\n";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+}
diff --git a/bin/drop_slony.slonik b/bin/drop_slony.slonik
new file mode 100644
index 0000000..04ffaca
--- /dev/null
+++ b/bin/drop_slony.slonik
@@ -0,0 +1,9 @@
+cluster name = freeside;
+
+node 1 admin conninfo = 'dbname=freeside host=XXX user=postgres';
+node 2 admin conninfo = 'dbname=freeside host=XXX user=postgres';
+
+drop set (id=1, origin=1);
+
+uninstall node ( id=1 );
+
diff --git a/bin/expand-country b/bin/expand-country
new file mode 100755
index 0000000..c6f2a1f
--- /dev/null
+++ b/bin/expand-country
@@ -0,0 +1,29 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Locale::SubCountry;
+use FS::UID qw(adminsuidsetup);
+use FS::Setup;
+use FS::Record qw(qsearch);
+use FS::cust_main_county;
+
+my $user = shift or die &usage;
+my $country = shift or die &usage;
+
+adminsuidsetup($user);
+
+my @country = qsearch('cust_main_county', { 'country' => $country } );
+die "unknown country $country" unless (@country);
+#die "$country already expanded" if scalar(@country) > 1;
+
+foreach my $cust_main_county ( @country ) {
+ my $error = $cust_main_county->delete;
+ die $error if $error;
+}
+
+FS::Setup::_add_country($country);
+
+sub usage {
+ die "Usage:\n\n expand-country user countrycode\n";
+}
+
diff --git a/bin/explain-ar-total.sql b/bin/explain-ar-total.sql
new file mode 100644
index 0000000..f154430
--- /dev/null
+++ b/bin/explain-ar-total.sql
@@ -0,0 +1,976 @@
+EXPLAIN SELECT ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill LEFT JOIN cust_main USING ( custnum ) WHERE cust_bill._date > ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund LEFT JOIN cust_main USING ( custnum ) WHERE cust_refund._date > ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit LEFT JOIN cust_main USING ( custnum ) WHERE cust_credit._date > ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay LEFT JOIN cust_main USING ( custnum ) WHERE cust_pay._date > ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ AS balance_0_30, ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill LEFT JOIN cust_main USING ( custnum ) WHERE cust_bill._date <= ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND cust_bill._date > ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund LEFT JOIN cust_main USING ( custnum ) WHERE cust_refund._date <= ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND cust_refund._date > ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit LEFT JOIN cust_main USING ( custnum ) WHERE cust_credit._date <= ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND cust_credit._date > ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay LEFT JOIN cust_main USING ( custnum ) WHERE cust_pay._date <= ( EXTRACT( EPOCH FROM now() ) - 2592000 ) AND cust_pay._date > ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ AS balance_30_60, ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill LEFT JOIN cust_main USING ( custnum ) WHERE cust_bill._date <= ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND cust_bill._date > ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund LEFT JOIN cust_main USING ( custnum ) WHERE cust_refund._date <= ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND cust_refund._date > ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit LEFT JOIN cust_main USING ( custnum ) WHERE cust_credit._date <= ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND cust_credit._date > ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay LEFT JOIN cust_main USING ( custnum ) WHERE cust_pay._date <= ( EXTRACT( EPOCH FROM now() ) - 5184000 ) AND cust_pay._date > ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ AS balance_60_90, ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill LEFT JOIN cust_main USING ( custnum ) WHERE cust_bill._date <= ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund LEFT JOIN cust_main USING ( custnum ) WHERE cust_refund._date <= ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit LEFT JOIN cust_main USING ( custnum ) WHERE cust_credit._date <= ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay LEFT JOIN cust_main USING ( custnum ) WHERE cust_pay._date <= ( EXTRACT( EPOCH FROM now() ) - 7776000 ) AND ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ AS balance_90_0, ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill LEFT JOIN cust_main USING ( custnum ) WHERE ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund LEFT JOIN cust_main USING ( custnum ) WHERE ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit LEFT JOIN cust_main USING ( custnum ) WHERE ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay LEFT JOIN cust_main USING ( custnum ) WHERE ( SELECT COALESCE(SUM(charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum ) - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )), 0) FROM cust_bill WHERE cust_main.custnum = cust_bill.custnum )
+ + ( SELECT COALESCE(SUM(refund
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
+ ,0
+ )
+ ), 0) FROM cust_refund WHERE cust_main.custnum = cust_refund.custnum )
+ - ( SELECT COALESCE(SUM(amount
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_refund
+ WHERE cust_credit.crednum = cust_credit_refund.crednum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_credit_bill
+ WHERE cust_credit.crednum = cust_credit_bill.crednum )
+ ,0
+ )
+ ), 0) FROM cust_credit WHERE cust_main.custnum = cust_credit.custnum )
+ - ( SELECT COALESCE(SUM(paid
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_bill_pay
+ WHERE cust_pay.paynum = cust_bill_pay.paynum )
+ ,0
+ )
+ - COALESCE(
+ ( SELECT SUM(amount) FROM cust_pay_refund
+ WHERE cust_pay.paynum = cust_pay_refund.paynum )
+ ,0
+ )
+ ), 0) FROM cust_pay WHERE cust_main.custnum = cust_pay.custnum )
+ > 0 AND ( agentnum = 1 OR agentnum = 2 OR agentnum = 3 OR agentnum = 4 OR agentnum IS NULL ) )
+ AS balance_0_0
diff --git a/bin/explain-bill-query b/bin/explain-bill-query
new file mode 100644
index 0000000..e3f6978
--- /dev/null
+++ b/bin/explain-bill-query
@@ -0,0 +1,34 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Cron::bill qw(bill_where);
+
+my $user = 'fs_daily';
+
+#&untaint_argv; #what it sounds like (eww)
+use vars qw(%opt);
+getopts("p:a:d:vl:sy:nmrk", \%opt);
+
+adminsuidsetup $user;
+
+ #we're at now now (and later).
+ $opt{'time'} = $opt{'d'} ? str2time($opt{'d'}) : $^T;
+ $opt{'time'} += $opt{'y'} * 86400 if $opt{'y'};
+
+ $opt{'invoice_time'} = $opt{'n'} ? $^T : $opt{'time'};
+
+
+my $sql = 'EXPLAIN SELECT custnum FROM cust_main WHERE '. bill_where(%opt);
+
+my $sth = dbh->prepare($sql) or die dbh->errstr;
+
+$sth->execute or die $sth->errstr;
+
+while ( my $row = $sth->fetchrow_arrayref ) {
+
+ print join(' / ', @$row ). "\n";
+
+}
+
diff --git a/bin/fetch_and_expand_taxes b/bin/fetch_and_expand_taxes
new file mode 100755
index 0000000..186d6df
--- /dev/null
+++ b/bin/fetch_and_expand_taxes
@@ -0,0 +1,55 @@
+#!/usr/bin/perl -w
+
+use strict;
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Response;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+
+my $user = shift or die &usage;
+my $dir = shift or die &usage;
+
+
+adminsuidsetup $user;
+
+my $conf = new FS::Conf;
+
+chdir $dir or die "can't change to $dir: $!\n";
+
+die "direct download of tax data not enabled\n"
+ unless $conf->exists('taxdatadirectdownload');
+my ( $urls, $username, $secret, $states ) =
+ $conf->config('taxdatadirectdownload');
+die "No tax download URL provided. ".
+ "Did you set the taxdatadirectdownload configuration value?\n"
+ unless $urls;
+
+my $ua = new LWP::UserAgent;
+ foreach my $url (split ',', $urls) {
+ my @name = split '/', $url; #somewhat restrictive
+ my $name = pop @name;
+ $name =~ /(.*)/; # untaint that which we trust;
+ $name = $1;
+
+ open my $taxfh, ">$name" or die "Can't open $name: $!\n";
+
+ my $res = $ua->request(
+ new HTTP::Request( GET => $url),
+ sub { #my ($data, $response_object) = @_;
+ print $taxfh $_[0] or die "Can't write to $dir.new/$name: $!\n";
+ },
+ );
+ die "download of $url failed: ". $res->status_line
+ unless $res->is_success;
+ close $taxfh;
+ $secret =~ /(.*)/; # untaint that which we trust;
+ $secret = $1;
+ system('unzip', "-P", $secret, $name) == 0
+ or die "unzip -P $secret $name failed";
+}
+
+sub usage {
+ die "Usage:\n\n fetch_and_expand_taxes user dir\n";
+}
+
diff --git a/bin/find-overapplied b/bin/find-overapplied
new file mode 100644
index 0000000..7973cef
--- /dev/null
+++ b/bin/find-overapplied
@@ -0,0 +1,27 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Data::Dumper;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_credit;
+use FS::cust_pay;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my @credits = grep { $_->credited < 0 } qsearch('cust_credit', {});
+my @payments = grep { $_->unapplied < 0 } qsearch('cust_pay', {});
+
+if ( @credits ) {
+ print scalar(@credits). " overapplied credits:\n". Dumper(@credits). "\n";
+}
+
+if ( @payments ) {
+ print scalar(@payments). " overapplied payments:\n". Dumper(@payments). "\n";
+}
+
+sub usage {
+ die "Usage:\n\n find-overapplied user\n";
+}
+
diff --git a/bin/fix-sequences b/bin/fix-sequences
new file mode 100755
index 0000000..dc4abd7
--- /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.26;
+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/follow-tax-rename b/bin/follow-tax-rename
new file mode 100644
index 0000000..b7536e8
--- /dev/null
+++ b/bin/follow-tax-rename
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pkg;
+
+$FS::Record::nowarn_classload = 1;
+$FS::Record::nowarn_classload = 1;
+
+adminsuidsetup shift;
+
+my $begin = 1231876106;
+
+my @old = qsearch('h_cust_main_county', {
+ 'history_action' => 'replace_old',
+ 'history_date' => { op=>'>=', value=>$begin, },
+} );
+
+foreach my $old (@old) {
+
+ my $new = qsearchs('h_cust_main_county', {
+ 'history_action' => 'replace_new',
+ 'history_date' => $old->history_date,
+ });
+
+ unless ( $new ) {
+ warn "huh? no corresponding new record found?";
+ next;
+ }
+
+ my $old_taxname = $old->taxname;
+ my $new_taxname = $new->taxname;
+
+ my @cust_bill_pkg = qsearch('cust_bill_pkg', {
+ 'pkgnum' => 0,
+ 'itemdesc' => $old->taxname,
+ });
+
+ next unless @cust_bill_pkg;
+
+ warn 'fixing '. scalar(@cust_bill_pkg).
+ " dangling line items for rename $old_taxname -> $new_taxname\n";
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+
+ $cust_bill_pkg->itemdesc( $new->taxname );
+ my $error = $cust_bill_pkg->replace;
+ die $error if $error;
+
+ }
+
+}
diff --git a/bin/freeside-create-initial-data b/bin/freeside-create-initial-data
new file mode 100755
index 0000000..4102089
--- /dev/null
+++ b/bin/freeside-create-initial-data
@@ -0,0 +1,31 @@
+#!/usr/bin/perl -Tw
+
+#to allow initial insert
+use FS::part_pkg;
+$FS::part_pkg::setup_hack = 1;
+$FS::part_pkg::setup_hack = 1;
+
+use strict;
+use vars qw($opt_d $opt_v);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Setup qw(create_initial_data);
+
+getopts("d:");
+
+my $dbh = adminsuidsetup shift;
+create_initial_data('domain' => $opt_d);
+
+warn "Freeside initial data inserted - commiting transaction\n" if $opt_v;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+warn "Database initialization committed successfully\n" if $opt_v;
+
+sub usage {
+ die "Usage:\n freeside-create-initial-data -d domain.name [ -v ] user\n"
+}
+
+1;
+
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-migrate-events b/bin/freeside-migrate-events
new file mode 100644
index 0000000..3e8a6b2
--- /dev/null
+++ b/bin/freeside-migrate-events
@@ -0,0 +1,266 @@
+#!/usr/bin/perl -w
+
+use strict;
+#use Getopt::Std;
+use FS::UID qw( adminsuidsetup dbh );
+use FS::Record qw( qsearch );
+use FS::part_bill_event;
+use FS::part_event;
+use FS::cust_bill_event;
+use FS::cust_event;
+
+#use vars qw( $opt_m );
+#getopts('m');
+
+my $user = shift or die &usage;
+adminsuidsetup($user);
+
+my %plan2action = (
+ 'fee' => 'fee',
+ 'fee_percent' => 'NOTYET', #XXX need fee_percent action
+ 'suspend' => 'suspend',
+ 'suspend-if-balance' => 'suspend', #"if balance" becomes the balance cond
+ 'suspend-if-pkgpart' => 'suspend_if_pkgpart',
+ 'suspend-unless-pkgpart' => 'suspend_unless_pkgpart',
+ 'cancel' => 'cancel',
+ 'addpost' => 'addpost',
+ 'comp' => 'NOTYET', #XXX or N/A or something
+ 'credit' => 'writeoff',
+ 'realtime-card' => 'cust_bill_realtime_card',
+ 'realtime-check' => 'cust_bill_realtime_check',
+ 'realtime-lec' => 'cust_bill_realtime_lec',
+ 'batch-card' => 'cust_bill_batch',
+ #?'retriable' =>
+ 'send' => 'cust_bill_send',
+ 'send_email' => 'cust_bill_email',
+ 'send_alternate' => 'cust_bill_send_alternate',
+ 'send_if_newest' => 'cust_bill_send_if_newest',
+ 'send_agent' => 'cust_bill_send_agent',
+ 'send_csv_ftp' => 'cust_bill_send_csv_ftp',
+ 'spool_csv', => 'cust_bill_spool_csv',
+ 'bill' => 'bill',
+ 'apply' => 'apply',
+ 'collect' => 'collect',
+);
+
+
+foreach my $part_bill_event (
+ qsearch({
+ 'table' => 'part_bill_event',
+ })
+) {
+
+ print $part_bill_event->event;
+
+ my $action = $plan2action{ $part_bill_event->plan };
+
+ if ( $action eq 'NOTYET' ) {
+ warn "not migrating part_bill_event.eventpart ".$part_bill_event->eventpart.
+ "; ". $part_bill_event->plan. " plan not (yet) handled";
+ next;
+ } elsif ( ! $action ) {
+ warn "not migrating part_bill_event.eventpart ".$part_bill_event->eventpart.
+ "; unknown plan ". $part_bill_event->plan;
+ next;
+ }
+
+ my %plandata = map { /^(\w+) (.*)$/; ($1, $2); }
+ split(/\n/, $part_bill_event->plandata);
+
+ #XXX may need to fudge some other plandata2option names
+
+ my $balanceover = 0;
+ my $honor_dundate = 0;
+
+ if ( $part_bill_event->plan eq 'suspend-if-balance' ) {
+ $balanceover = delete $plandata{'balanceover'};
+ $honor_dundate = ( (delete $plandata{'balance_honor_dundate'}) =~ /1/ );
+ }
+
+ my $part_event = new FS::part_event {
+ 'event' => $part_bill_event->event,
+ 'eventtable' => 'cust_bill',
+ 'check_freq' => $part_bill_event->freq || '1d',
+ 'weight' => $part_bill_event->weight,
+ 'action' => $action,
+ 'disabled' => $part_bill_event->disabled,
+ };
+
+ my $error = $part_event->insert(\%plandata);
+ die "error inserting part_event: $error\n" if $error;
+
+ print ' '. $part_event->eventpart;
+
+ my $once = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'once'
+ };
+ $error = $once->insert;
+ die $error if $error;
+
+ my $balance = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'balance'
+ };
+ $error = $balance->insert( 'balance' => $balanceover );
+ die $error if $error;
+
+ my $cust_bill_owed = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'cust_bill_owed'
+ };
+ $error = $cust_bill_owed->insert( 'owed' => 0 );
+ die $error if $error;
+
+ my $payby = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'payby'
+ };
+ $error = $payby->insert( 'payby' => { $part_bill_event->payby => 1 } );
+ die $error if $error;
+
+ if ( $part_bill_event->seconds ) {
+
+ my $age = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'cust_bill_age'
+ };
+ $error = $age->insert( 'age' => ($part_bill_event->seconds/86400 ).'d' );
+ die $error if $error;
+
+ }
+
+ if ( $honor_dundate ) {
+ my $dundate = new FS::part_event_condition {
+ 'eventpart' => $part_event->eventpart,
+ 'conditionname' => 'dundate'
+ };
+ $error = $dundate->insert();
+ die $error if $error;
+ }
+
+ #my $derror = $part_bill_event->delete;
+ #die "error removing part_bill_event: $derror\n" if $derror;
+
+# if ( $opt_m ) {
+
+ my $sth = dbh->prepare('
+ INSERT INTO cust_event ( eventpart, tablenum, _date, status, statustext )
+ SELECT ? , invnum , _date, status, statustext
+ FROM cust_bill_event WHERE eventpart = ?
+ ') or die dbh->errstr;
+
+ $sth->execute( $part_event->eventpart, $part_bill_event->eventpart )
+ or die $sth->errstr;
+
+# } else {
+#
+# foreach my $cust_bill_event (
+# qsearch({
+# 'table' => 'cust_bill_event',
+# 'hashref' => { 'eventpart' => $part_bill_event->eventpart, },
+# })
+# ) {
+#
+# my $cust_event = new FS::cust_event {
+# 'eventpart' => $part_event->eventpart,
+# 'tablenum' => $cust_bill_event->invnum,
+# '_date' => $cust_bill_event->_date,
+# 'status' => $cust_bill_event->status,
+# 'statustext' => $cust_bill_event->statustext,
+# };
+#
+# my $cerror = $cust_event->insert;
+# #die "error inserting cust_event: $cerror\n" if $cerror;
+# warn "error inserting cust_event: $cerror\n" if $cerror;
+#
+# #my $dcerror = $cust_bill_event->delete;
+# #die "error removing cust_bill_event: $dcerror\n" if $dcerror;
+#
+# print ".";
+#
+# }
+#
+# }
+
+ print "\n";
+
+}
+
+sub usage {
+ die "Usage:\n freeside-migrate-events user\n";
+}
+
+=head1 NAME
+
+freeside-migrate-events - Migrates 1.7/1.8-style invoice events to
+ 1.9/2.0-style billing events
+
+=head1 SYNOPSIS
+
+ freeside-migrate-events
+
+=head1 DESCRIPTION
+
+Migrates events from L<FS::part_bill_event> to L<FS::part_event> and friends,
+and from L<FS::cust_bill_event> records to L<FS::cust_event>
+
+=head1 BUGS
+
+Doesn't migrate any action options yet.
+
+Doesn't translate option names that changed.
+
+Doesn't migrate reasons.
+
+Doesn't delete the old events (which is not a big deal, since the new code
+won't run them...)
+
+Can take lots of memory for large databases.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
+__END__
+
+#part_bill_event part_event
+#
+#eventpart n/a
+#event event
+#freq check_freq
+#payby part_event_condition.conditionname = payby
+#eventcode PARSE_WITH_REGEX (probably can just get from plandata)
+#seconds part_event_condition.conditionname = cust_bill_age
+#plandata PARSE_WITH_REGEX (along with eventcode, yuck)
+#reason part_event_option.optionname = reason
+#disabled disabled
+#
+
+ #these might help parse existing eventcode
+
+ $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
+
+# or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/
+ or $c =~ /^\s*\$cust_main\->apply_payments_and_credits; "";\s*$/
+
+ or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/
+
+ or $c =~ /^\s*\$cust_main\->suspend_(if|unless)_pkgpart\([\d\,\s]*\);\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->cust_suspend_if_balance_over\([\d\.\s]*\);\s*$/
+
+ or do {
+ #log
+ return "illegal eventcode: $c";
+ };
+
+ }
+
+
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-upgrade-unicode b/bin/freeside-upgrade-unicode
new file mode 100755
index 0000000..c603365
--- /dev/null
+++ b/bin/freeside-upgrade-unicode
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+# based on example code from
+# http://blog.larik.nl:80/articles/2006/03/13/upgrade-your-postgresql-databases-to-unicode
+# by frodo larik / blog.larik.nl
+
+db=freeside
+
+# This script updates all dbs to use unicode
+
+dbhost='localhost'
+username='freeside'
+#odir=${HOME}/freeside_unicode_upgrade
+odir=/home/ivan/FREESIDE_unicode_upgrade
+
+if [ "${db}X" == "X" ]
+then
+ echo "I need a db for host ${dbhost} and username ${username} $db"
+ exit
+fi
+
+if [ ! -d $odir ]
+then
+ mkdir $odir || exit "Exit at mkdir"
+fi
+
+#echo -n "Enter a comma-separated list of country codes to keep [US,CA]:"
+#countries=`line`
+#if [ "${countries}X" == "X" ]
+#then
+# countries='US,CA'
+#fi
+
+echo "delete from cust_main_county where 0 = ( select count(*) from cust_main where cust_main_county.country = cust_main.country );" | su freeside -c 'psql freeside'
+
+dump_sql=${odir}/${db}_out.sql
+conv_sql=${odir}/${db}_conv.sql
+result_sql=${odir}/${db}_result.txt
+sql_diff=${odir}/${db}.diff
+
+# 0. stop
+
+/etc/init.d/freeside stop || die "can't stop freeside"
+/etc/init.d/apache stop || die "can't stop apache"
+/etc/init.d/apache2 stop || die "can't stop apache"
+
+echo "Dumping $db database to $dump_sql"
+
+su $username -c "pg_dump --host=$dbhost --username=$username -D --format=p $db" >$dump_sql || exit "exit at pg_dump"
+
+echo "Removing invalid characters from the dump"
+
+iconv -c -f UTF-8 -t UTF-8 ${dump_sql} > ${conv_sql} || exit "exit at iconv"
+
+echo "*** Making a diff from the dump: check $sql_diff ***"
+
+diff $dump_sql $conv_sql > $sql_diff
+
+echo "Removing current database"
+
+su $freeside -c "dropdb --host=$dbhost --username=$username $db" || exit "exit at dropdb"
+
+echo "Creating a new databse"
+
+su freeside -c "createdb --encoding='unicode' --host=$dbhost --username=$username $db" || exit "exit at createdb"
+
+echo "Loading data into new database"
+su freeside -c "psql -f $conv_sql -o $result_sql -h $dbhost -U $username $db" || exit "exit at psql ${extra_string}"
+
+# 99.
+/etc/init.d/freeside start || die "oh no, can't start freeside"
+/etc/init.d/apache start || die "oh no, can't start apache"
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-cust_tax_exempt b/bin/fs-migrate-cust_tax_exempt
new file mode 100755
index 0000000..35c74ff
--- /dev/null
+++ b/bin/fs-migrate-cust_tax_exempt
@@ -0,0 +1,323 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Time::Local;
+use Date::Format;
+use Time::Duration;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw( qsearch dbh );
+use FS::cust_tax_exempt;
+#use FS::cust_bill;
+use FS::h_cust_bill;
+use FS::h_cust_tax_exempt;
+use FS::cust_bill_pkg;
+use FS::cust_tax_exempt_pkg;
+#use Data::Dumper;
+
+my $start = time;
+
+adminsuidsetup shift;
+
+my $fuz = 7; #seconds
+
+ #site-specific rewrites
+my %rewrite = (
+ #cust_tax_exempt.exemptnum => { 'field' => 'newvalue', ... },
+# '23' => { month=>10, year=>2005, invnum=>1640 },
+
+ #etc.
+);
+
+my @cust_tax_exempt = qsearch('cust_tax_exempt', {} );
+my $num_cust_tax_exempt = scalar(@cust_tax_exempt);
+my $num_cust_tax_exempt_migrated = 0;
+my $total_cust_tax_exempt_migrated = 0;
+my $num_cust_tax_exempt_pkg_migrated = 0;
+my $total_cust_tax_exempt_pkg_migrated = 0;
+
+$FS::UID::AutoCommit = 0;
+
+foreach my $cust_tax_exempt ( @cust_tax_exempt ) {
+
+ if ( exists $rewrite{ $cust_tax_exempt->exemptnum } ) {
+ my $hashref = $rewrite{ $cust_tax_exempt->exemptnum };
+ $cust_tax_exempt->setfield($_, $hashref->{$_})
+ foreach keys %$hashref;
+ }
+
+ if ( $cust_tax_exempt->year < 1990 ) {
+ warn "exemption year is ". $cust_tax_exempt->year.
+ "; not migrating exemption ". $cust_tax_exempt->exemptnum.
+ ' for custnum '. $cust_tax_exempt->custnum. "\n\n";
+ next;
+ }
+
+ # also make sure cust_bill_pkg record dates contain the month/year
+# my $mon = $cust_tax_exempt->month;
+# my $year = $cust_tax_exempt->year;
+# $mon--;
+# my $edate_after = timelocal(0,0,0,1,$mon,$year);
+# $mon++;
+# if ( $mon >= 12 ) { $mon-=12; $year++ };
+# my $sdate_before = timelocal(0,0,0,1,$mon,$year);
+
+ my $mon = $cust_tax_exempt->month;
+ my $year = $cust_tax_exempt->year;
+ if ( $mon >= 12 ) { $mon-=12; $year++ };
+ my $sdate_before = timelocal(0,0,0,1,$mon,$year);
+ #$mon++;
+ #if ( $mon >= 12 ) { $mon-=12; $year++ };
+ my $edate_after = timelocal(0,0,0,1,$mon,$year);
+
+ # !! start a transaction? (yes, its started)
+
+ my @h_cust_tax_exempt = qsearch({
+ 'table' => 'h_cust_tax_exempt',
+ 'hashref' => { 'exemptnum' => $cust_tax_exempt->exemptnum },
+ 'extra_sql' => " AND ( history_action = 'insert'
+ OR history_action = 'replace_new' )
+ ORDER BY history_date ASC
+ ",
+ });
+
+ my $amount_so_far = 0;
+ my $num_cust_tax_exempt_pkg = 0;
+ my $total_cust_tax_exempt_pkg = 0;
+ H_CUST_TAX_EXEMPT: foreach my $h_cust_tax_exempt ( @h_cust_tax_exempt ) {
+
+ my $amount = sprintf('%.2f', $h_cust_tax_exempt->amount - $amount_so_far );
+ $amount_so_far += $amount;
+
+# print Dumper($h_cust_tax_exempt), "\n";
+
+ #find a matching cust_bill record
+ # (print time differences and choose a meaningful threshold, should work)
+
+ my @h_cust_bill = ();
+ if ( $cust_tax_exempt->invnum ) {
+ #warn "following invnum ". $cust_tax_exempt->invnum.
+ # " kludge for cust_tax_exempt ". $cust_tax_exempt->exemptnum. "\n";
+
+ @h_cust_bill = qsearch({
+ #'table' => 'cust_bill',
+ 'table' => 'h_cust_bill',
+ 'hashref' => { 'custnum' => $h_cust_tax_exempt->custnum,
+ 'invnum' => $cust_tax_exempt->invnum,
+ 'history_action' => 'insert',
+ },
+ #'extra_sql' =>
+ # ' AND history_date <= '. ( $h_cust_tax_exempt->history_date + $fuz ).
+ # ' AND history_date > '. ( $h_cust_tax_exempt->history_date - $fuz ),
+ });
+
+ } else {
+
+ @h_cust_bill = qsearch({
+ #'table' => 'cust_bill',
+ 'table' => 'h_cust_bill',
+ 'hashref' => { 'custnum' => $h_cust_tax_exempt->custnum,
+ 'history_action' => 'insert',
+ },
+ 'extra_sql' =>
+ ' AND history_date <= '. ( $h_cust_tax_exempt->history_date + $fuz ).
+ ' AND history_date > '. ( $h_cust_tax_exempt->history_date - $fuz ),
+ });
+
+ }
+
+ if ( scalar(@h_cust_bill) != 1 ) {
+ warn ' '. scalar(@h_cust_bill). ' h_cust_bill records matching '.
+ 'h_cust_tax_exempt.historynum '. $h_cust_tax_exempt->historynum.
+ "; not migrating (adjust fuz factor?)\n";
+ next;
+ }
+
+ my $h_cust_bill = $h_cust_bill[0];
+
+# print Dumper(@cust_bill), "\n\n";
+
+ # then find a matching cust_bill_pkg record with part_pkg.taxclass record
+ # that matches the one pointed to by cust_tax_exempt.taxnum
+ # (hopefully just one, see how many we can match automatically)
+
+ my $cust_main_county = $cust_tax_exempt->cust_main_county;
+ my $taxclass = $cust_main_county->taxclass;
+
+ my $hashref = {
+ 'custnum' => $cust_tax_exempt->custnum,
+ 'invnum' => $h_cust_bill->invnum,
+ 'pkgnum' => { op=>'>', value=>0, },
+ };
+ unless ( $cust_tax_exempt->invnum ) {
+ # also make sure cust_bill_pkg record dates contain the month/year
+
+ #$hashref->{'sdate'} = { op=>'<', value=>$sdate_before };
+ $hashref->{'sdate'} = { op=>'<=', value=>$sdate_before };
+
+ #$hashref->{'edate'} = { op=>'>', value=>$edate_after };
+ $hashref->{'edate'} = { op=>'>=', value=>$edate_after };
+ }
+
+ if ( $cust_tax_exempt->billpkgnum ) {
+ $hashref->{'billpkgnum'} = $cust_tax_exempt->billpkgnum;
+ }
+
+ my $extra_sql = 'ORDER BY billpkgnum';
+
+ $extra_sql = "AND taxclass = '$taxclass' $extra_sql"
+ unless $cust_tax_exempt->ignore_current_taxclass;
+
+ my @cust_bill_pkg = qsearch({
+ 'select' => 'cust_bill_pkg.*, part_pkg.freq',
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => 'LEFT JOIN cust_pkg using ( pkgnum ) '.
+ 'LEFT JOIN part_pkg using ( pkgpart ) ',
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+ });
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+ $cust_bill_pkg->exemptable_per_month(
+ sprintf('%.2f',
+ ( $cust_bill_pkg->setup + $cust_bill_pkg->recur )
+ /
+ ( $cust_bill_pkg[0]->freq || 1 )
+ )
+ );
+ }
+
+ my(@cust_tax_exempt_pkg) = ();
+ if ( scalar(@cust_bill_pkg) == 1
+ && $cust_bill_pkg[0]->exemptable_per_month >= $amount
+ )
+ {
+
+ my $cust_bill_pkg = $cust_bill_pkg[0];
+
+ # finally, create an appropriate cust_tax_exempt_pkg record
+
+ push @cust_tax_exempt_pkg, new FS::cust_tax_exempt_pkg {
+ 'billpkgnum' => $cust_bill_pkg->billpkgnum,
+ 'taxnum' => $cust_tax_exempt->taxnum,
+ 'year' => $cust_tax_exempt->year,
+ 'month' => $cust_tax_exempt->month,
+ 'amount' => $amount,
+ };
+
+ } else {
+
+# warn ' '. scalar(@cust_bill_pkg). ' cust_bill_pkg records for invoice '.
+# $h_cust_bill->invnum.
+# "; not migrating h_cust_tax_exempt historynum ".
+# $h_cust_tax_exempt->historynum. " for \$$amount\n";
+# warn " *** DIFFERENT DATES ***\n"
+# if grep { $_->sdate != $cust_bill_pkg[0]->sdate
+# || $_->edate != $cust_bill_pkg[0]->edate
+# } @cust_bill_pkg;
+# foreach ( @cust_bill_pkg ) {
+# warn ' '. $_->billpkgnum. ': '. $_->setup. 's/'. $_->recur.'r'.
+# ' '. time2str('%D', $_->sdate). '-'. time2str('%D', $_->edate).
+# "\n";
+# }
+#
+# next;
+
+ my $remaining = $amount;
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+ last unless $remaining;
+ my $this_amount =sprintf('%.2f',
+ $remaining <= $cust_bill_pkg->exemptable_per_month
+ ? $remaining
+ : $cust_bill_pkg->exemptable_per_month
+ );;
+
+ push @cust_tax_exempt_pkg, new FS::cust_tax_exempt_pkg {
+ 'billpkgnum' => $cust_bill_pkg->billpkgnum,
+ 'taxnum' => $cust_tax_exempt->taxnum,
+ 'year' => $cust_tax_exempt->year,
+ 'month' => $cust_tax_exempt->month,
+ 'amount' => $this_amount,
+ };
+
+ $remaining -= $this_amount;
+
+ }
+
+ }
+
+ foreach my $cust_tax_exempt_pkg ( @cust_tax_exempt_pkg ) {
+ my $error = $cust_tax_exempt_pkg->insert;
+ #my $error = $cust_tax_exempt_pkg->check;
+ if ( $error ) {
+ warn "*** error inserting cust_tax_exempt_pkg record: $error\n";
+ next; #not necessary.. H_CUST_TAX_EXEMPT;
+
+ #not necessary, incorrect $total_cust_tax_exempt_pkg will error it out
+ # roll back at least the entire cust_tax_exempt transaction
+ # next CUST_TAX_EXEMPT;
+ }
+
+ $num_cust_tax_exempt_pkg++;
+
+ $total_cust_tax_exempt_pkg += $cust_tax_exempt_pkg->amount;
+
+ }
+
+ }
+
+ $total_cust_tax_exempt_pkg = sprintf('%.2f', $total_cust_tax_exempt_pkg );
+
+ unless ( $total_cust_tax_exempt_pkg == $cust_tax_exempt->amount ) {
+ warn "total h_ amount $total_cust_tax_exempt_pkg != cust_tax_exempt.amount ".
+ $cust_tax_exempt->amount.
+ ";\n not migrating exemption ". $cust_tax_exempt->exemptnum. " for ".
+ $cust_tax_exempt->month. '/'. $cust_tax_exempt->year.
+ ' (custnum '. $cust_tax_exempt->custnum. ") ".
+ #"\n (sdate < ". time2str('%D', $sdate_before ).
+ "\n (sdate <= ". time2str('%D', $sdate_before ). " [$sdate_before]".
+ #' / edate > '. time2str('%D', $edate_after ). ')'.
+ ' / edate >= '. time2str('%D', $edate_after ). " [$edate_after])".
+ "\n\n";
+
+ # roll back at least the entire cust_tax_exempt transaction
+ dbh->rollback;
+
+ # next CUST_TAX_EXEMPT;
+ next;
+ }
+
+ # remove the cust_tax_exempt record
+ my $error = $cust_tax_exempt->delete;
+ if ( $error ) {
+ #roll back at least the entire cust_tax_exempt transaction
+ dbh->rollback;
+
+ #next CUST_TAX_EXEMPT;
+ next;
+ }
+
+ $num_cust_tax_exempt_migrated++;
+ $total_cust_tax_exempt_migrated += $cust_tax_exempt->amount;
+
+ $num_cust_tax_exempt_pkg_migrated += $num_cust_tax_exempt_pkg;
+ $total_cust_tax_exempt_pkg_migrated += $total_cust_tax_exempt_pkg;
+
+ # commit the transaction
+ dbh->commit;
+
+}
+
+$total_cust_tax_exempt_migrated =
+ sprintf('%.2f', $total_cust_tax_exempt_migrated );
+$total_cust_tax_exempt_pkg_migrated =
+ sprintf('%.2f', $total_cust_tax_exempt_pkg_migrated );
+
+warn
+ "$num_cust_tax_exempt_migrated / $num_cust_tax_exempt (".
+ sprintf('%.2f', 100 * $num_cust_tax_exempt_migrated / $num_cust_tax_exempt).
+ '%) cust_tax_exempt records migrated ($'. $total_cust_tax_exempt_migrated.
+ ")\n to $num_cust_tax_exempt_pkg_migrated cust_tax_exempt_pkg records".
+ ' ($'. $total_cust_tax_exempt_pkg_migrated. ')'.
+ "\n in ". duration(time-$start). "\n"
+;
+
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..07f7b61
--- /dev/null
+++ b/bin/fs-migrate-svc_acct_sm
@@ -0,0 +1,227 @@
+#!/usr/bin/perl -Tw
+#
+# 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..af21c05
--- /dev/null
+++ b/bin/generate-raddb
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+
+# usage: generate-raddb radius-server/raddb/dictionary* >raddb.pm
+# i.e.: generate-raddb ~/freeradius/freeradius-1.0.5/share/dictionary* ~/wirelessoceans/dictionary.ip3networks ~/wtxs/dictionary.mot.canopy >raddb.pm.new
+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 ( sort 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 " '$_' ". ( " " x (24-length($_) ) ). "=> '$hash{$_}',\n";
+}
+
+print <<END;
+
+ #NETC.NET.AU (RADIATOR?)
+ 'authentication_type' => 'Authentication-Type',
+
+ #wtxs (dunno)
+ #'radius_operator' => 'Radius-Operator',
+
+);
+
+1;
+END
+
diff --git a/bin/generate-table-module b/bin/generate-table-module
new file mode 100755
index 0000000..16a23e5
--- /dev/null
+++ b/bin/generate-table-module
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+use FS::Schema qw( dbdef_dist );
+
+my $table = shift;
+
+###
+# add a new FS/FS/table.pm
+###
+
+my %ut = ( #just guesses
+ 'int' => 'number',
+ 'number' => 'float',
+ 'varchar' => 'text',
+ 'text' => 'text',
+ 'serial' => 'number',
+);
+
+my $dbdef_table = dbdef_dist->table($table)
+ or die "define table in Schema.pm first";
+my $primary_key = $dbdef_table->primary_key;
+
+open(SRC,"<eg/table_template.pm") or die $!;
+-e "FS/FS/$table.pm" and die "FS/FS/$table.pm already exists!";
+open(DEST,">FS/FS/$table.pm") or die $!;
+
+while (my $line = <SRC>) {
+
+ $line =~ s/table_name/$table/g;
+
+ if ( $line =~ /^=item\s+field\s+-\s+description\s*$/ ) {
+
+ foreach my $column ( $dbdef_table->columns ) {
+ print DEST "=item $column\n\n";
+ if ( $column eq $primary_key ) {
+ print DEST "primary key\n\n";
+ } else {
+ print DEST "$column\n\n";
+ }
+ }
+ next;
+
+ } elsif ( $line=~ /^(\s*)\$self->ut_numbern\('primary_key'\)\s*/ ) {
+
+ print DEST "$1\$self->ut_numbern('$primary_key')\n"
+ if $primary_key;
+ next;
+
+ } elsif (
+ $line =~ /^(\s*)\|\|\s+\$self->ut_number\('validate_other_fields'\)\s*/
+ ) {
+
+ foreach my $column ( grep { $_ ne $primary_key } $dbdef_table->columns ) {
+ my $ut = $ut{$dbdef_table->column($column)->type};
+ $ut .= 'n' if $dbdef_table->column($column)->null;
+ print DEST "$1|| \$self->ut_$ut('$column')\n";
+ }
+ next;
+
+ }
+
+ print DEST $line;
+}
+
+close SRC;
+close DEST;
+
+###
+# add to FS/FS/Mason.pm
+###
+
+# it needs to be:
+# 1. disablable (have an option to turn it off)
+# 2. documented in the schema change docs
+#
+#my $magic = '# Sammath Naur';
+#system("perl -pi -e 's/$magic/use FS::$table;\n $magic/' FS/FS/Mason.pm");
+
+###
+# add FS/t/table.t
+###
+
+open(TEST,">FS/t/$table.t") or die $!;
+print TEST <<ENDTEST;
+BEGIN { \$| = 1; print "1..1\\n" }
+END {print "not ok 1\\n" unless \$loaded;}
+use FS::$table;
+\$loaded=1;
+print "ok 1\\n";
+ENDTEST
+close TEST;
+
+###
+# add them to MANIFEST
+###
+
+system('cvs edit FS/MANIFEST');
+
+open(MANIFEST,">>FS/MANIFEST") or die $!;
+print MANIFEST "FS/$table.pm\n",
+ "t/$table.t\n";
+close MANIFEST;
+
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/h_cust_main-wipe_paycvv b/bin/h_cust_main-wipe_paycvv
new file mode 100755
index 0000000..d34c15f
--- /dev/null
+++ b/bin/h_cust_main-wipe_paycvv
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record; #buh?
+
+my $user = shift or die 'usage';
+adminsuidsetup $user;
+
+while (1) {
+
+ my $sql = ' UPDATE h_cust_main SET paycvv = NULL
+ WHERE historynum IN ( SELECT historynum FROM h_cust_main
+ WHERE paycvv IS NOT NULL LIMIT 8192 )';
+# WHERE paycvv IS NOT NULL LIMIT 1 )';
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+
+ print '.'; $|=1;
+
+ my $rv = $sth->execute;
+
+ dbh->commit or die dbh->errstr;
+
+ last if $rv == 0;
+
+}
+
+print "\n";
+
diff --git a/bin/import-county-tax-rates b/bin/import-county-tax-rates
new file mode 100755
index 0000000..05798c9
--- /dev/null
+++ b/bin/import-county-tax-rates
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+#
+# import-county-tax-rates username state country <filename.csv
+# example: import-county-tax-rates ivan CA US <taxes.csv
+#
+# rates.csv: taxrate,county
+
+use FS::UID qw(adminsuidsetup);
+use FS::cust_main_county;
+
+my $user = shift;
+adminsuidsetup $user;
+
+my($state, $country) = (shift, shift);
+
+while (<>) {
+ my($tax, $county) = split(','); #half-ass CSV parser
+
+ my $cust_main_county = new FS::cust_main_county {
+ 'county' => $county,
+ 'state' => $state,
+ 'country' => $country,
+ 'tax' => $tax,
+ };
+
+ my $error = $cust_main_county->insert;
+ #my $error = $cust_main_county->check;
+ die $error if $error;
+
+}
diff --git a/bin/import-optigold.pl b/bin/import-optigold.pl
new file mode 100755
index 0000000..d32a2a1
--- /dev/null
+++ b/bin/import-optigold.pl
@@ -0,0 +1,1077 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use DBI;
+use HTML::TableParser;
+use Date::Parse;
+use Text::CSV_XS;
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_credit;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::part_referral;
+use FS::part_pkg;
+use FS::UID qw(adminsuidsetup);
+
+my $DEBUG = 0;
+
+my $dry_run = '0';
+
+my $s_dbname = 'DBI:Pg:dbname=optigoldimport';
+my $s_dbuser = 'freeside';
+my $s_dbpass = '';
+my $extension = '.htm';
+
+#my $d_dbuser = 'freeside';
+my $d_dbuser = 'enet';
+#my $d_dbuser = 'ivan';
+#my $d_dbuser = 'freesideimport';
+
+my $radius_file = 'radius.csv';
+my $email_file = 'email.csv';
+
+#my $agentnum = 1;
+my $agentnum = 13;
+my $legacy_domain_svcnum = 1;
+my $legacy_ppp_svcpart = 2;
+my $legacy_email_svcpart = 3;
+#my $legacy_broadband_svcpart = 4;
+#my $legacy_broadband_svcpart = 14;
+#my $previous_credit_reasonnum = 1;
+my $previous_credit_reasonnum = 1220;
+
+
+my $state = ''; #statemachine-ish
+my $sourcefile;
+my $s_dbh;
+my $columncount;
+my $rowcount;
+
+my @args = (
+ {
+ id => 1,
+ hdr => \&header,
+ row => \&row,
+ start => \&start,
+ end => \&end,
+ },
+ );
+
+
+$s_dbh = DBI->connect($s_dbname, $s_dbuser, $s_dbpass,
+ { 'AutoCommit' => 0,
+ 'ChopBlanks' => 1,
+ 'ShowErrorStatement' => 1
+ }
+ );
+
+foreach ( qw ( billcycle cust email product ) ) {
+ $sourcefile = $_;
+
+ print "parsing $sourcefile\n";
+
+ die "bad file name" unless $sourcefile =~ /^\w+$/;
+
+ $columncount = 0;
+ $rowcount = 0;
+
+ my $c_sth = '';
+ if ( $c_sth = $s_dbh->prepare("SELECT COUNT(*) FROM $sourcefile") ) {
+ if ( $c_sth->execute ) {
+ if ( $c_sth->fetchrow_arrayref->[0] ) {
+ warn "already have data in $sourcefile table; skipping";
+ next;
+ }
+ }
+ }
+
+ my $tp = new HTML::TableParser( \@args, { Decode => 1, Trim => 1, Chomp => 1 });
+ $tp->parse_file($sourcefile.$extension) or die "failed";
+ $s_dbh->commit or die $s_dbh->errstr;
+# $s_dbh->disconnect;
+}
+
+
+sub start {
+ warn "start\n" if $DEBUG;
+ my $table_id = shift;
+ die "unexpected state change" unless $state eq '';
+ die "unexpected table" unless $table_id eq '1';
+ $state = 'table';
+}
+
+sub end {
+ warn "end\n" if $DEBUG;
+ my ($tbl_id, $line, $udata) = @_;
+ die "unexpected state change in header" unless $state eq 'rows';
+ die "unexpected table" unless $tbl_id eq '1';
+ $state = '';
+}
+
+sub header {
+ warn "header\n" if $DEBUG;
+ my ($tbl_id, $line, $cols, $udata) = @_;
+ die "unexpected state change in header" unless $state eq 'table';
+ die "unexpected table" unless $tbl_id eq '1';
+ $state = 'rows';
+
+ die "invalid column ". join (', ', grep { !/^[ \w\r]+$/ } @$cols)
+ if scalar(grep { !/^[ \w\r]+$/ } @$cols);
+
+ my $sql = "CREATE TABLE $sourcefile ( ".
+ join(', ', map { s/[ \r]/_/g; "$_ varchar NULL" } @$cols). " )";
+ $s_dbh->do($sql) or die "create table failed: ". $s_dbh->errstr;
+ $columncount = scalar( @$cols );
+}
+
+sub row {
+ warn "row\n" if $DEBUG;
+ my ($tbl_id, $line, $cols, $udata) = @_;
+ die "unexpected state change in row" unless $state eq 'rows';
+ die "unexpected table" unless $tbl_id eq '1';
+
+ die "invalid number of columns: ". join(', ', @$cols)
+ unless (scalar(@$cols) == $columncount);
+
+ my $sql = "INSERT INTO $sourcefile VALUES(".
+ join(', ', map { s/\s*(\S[\S ]*?)\s*$/$1/; $s_dbh->quote($_) } @$cols). ")";
+ $s_dbh->do($sql) or die "insert failed: ". $s_dbh->errstr;
+ $rowcount++;
+ warn "row $rowcount\n" unless ($rowcount % 1000);
+}
+
+## now svc_acct from CSV files
+
+$FS::cust_main::import=1;
+$FS::cust_pkg::disable_agentcheck = 1;
+$FS::cust_svc::ignore_quantity = 1;
+
+my (%master_map) = ();
+my (%referrals) = ();
+my (%custid) = ();
+my (%cancel) = ();
+my (%susp) = ();
+my (%adjo) = ();
+my (%bill) = ();
+my (%cust_pkg_map) = ();
+my (%object_map) = ();
+my (%package_cache) = ();
+my $count = 0;
+
+my $d_dbh = adminsuidsetup $d_dbuser;
+local $FS::UID::AutoCommit = 0;
+
+my @import = ( { 'file' => $radius_file,
+ 'sep_char' => ';',
+ 'fields' => [ qw( garbage1 username garbage2 garbage3 _password ) ],
+ 'fixup' => sub {
+ my $hash = shift;
+ delete $hash->{$_}
+ foreach qw (garbage1 garbage2 garbage3);
+ $hash->{'svcpart'} = $legacy_ppp_svcpart;
+ $hash->{'domsvc'} = $legacy_domain_svcnum;
+ '';
+ },
+ 'mapkey' => 'legacy_ppp',
+ 'skey' => 'username',
+ },
+ { 'file' => $email_file,
+ 'sep_char' => ';',
+ 'fields' => [ qw( username null finger _password status garbage ) ],
+ 'fixup' => sub {
+ my $hash = shift;
+ #return 1
+ # if $object_map{'legacy_ppp'}{$hash->{'username'}};
+ delete $hash->{$_}
+ foreach qw (null status garbage);
+ $hash->{'svcpart'} = $legacy_email_svcpart;
+ $hash->{'domsvc'} = $legacy_domain_svcnum;
+ '';
+ },
+ 'mapkey' => 'legacy_email',
+ 'skey' => 'username',
+ },
+);
+
+while ( @import ) {
+ my $href = shift @import;
+ my $file = $href->{'file'} or die "No file specified";
+ my (@fields) = @{$href->{'fields'}};
+ my ($sep_char) = $href->{'sep_char'} || ';';
+ my ($fixup) = $href->{'fixup'};
+ my ($mapkey) = $href->{'mapkey'};
+ my ($skey) = $href->{'skey'};
+ my $line;
+
+ my $csv = new Text::CSV_XS({'sep_char' => $sep_char});
+ open(FH, $file) or die "cannot open $file: $!";
+ $count = 0;
+
+ while ( defined($line=<FH>) ) {
+ chomp $line;
+
+ $line &= "\177" x length($line); # i hope this isn't really necessary
+ $csv->parse($line)
+ or die "cannot parse: " . $csv->error_input();
+
+ my @values = $csv->fields();
+ my %hash;
+ foreach my $field (@fields) {
+ $hash{$field} = shift @values;
+ }
+
+ if (@values) {
+ warn "skipping malformed line: $line\n";
+ next;
+ }
+
+ my $skip = &{$fixup}(\%hash)
+ if $fixup;
+
+ unless ($skip) {
+ my $svc_acct = new FS::svc_acct { %hash };
+ my $error = $svc_acct->insert;
+ if ($error) {
+ warn $error;
+ next;
+ }
+
+ if ($skey && $mapkey) {
+ my $key = (ref($skey) eq 'CODE') ? &{$skey}($svc_acct) : $hash{$skey};
+ $object_map{$mapkey}{$key} = $svc_acct->svcnum;
+ }
+
+ $count++
+ }
+ }
+ print "Imported $count service records\n";
+
+}
+
+
+
+sub pkg_freq {
+ my ( $href ) = ( shift );
+ my $once;
+ $href->{'one_time_list'} =~ /^\s*(\S[\S ]*?)\s*$/ && ($once = $1);
+ $once
+ ? 0
+ : int(eval "$href->{'months_credit'} + 0");
+# int(eval "$href->{'month_credit'} + 0");
+}
+
+sub account_id {
+ my $href = shift;
+ if ($href->{'slave_account_id'} =~ /^\s*(\S[\S ]*?)\s*$/) {
+ "slave:$1";
+ }else{
+ my $l = $href->{cbilling_cycle_login};
+ $l =~ /^\s*(\S[\S ]*?)\s*$/ && ($l = $1);
+ $l;
+ }
+}
+
+sub b_or {
+ my ( $field, $hash ) = ( shift, shift );
+ $field = 'billing_'. $field
+ if $hash->{'billing_use'} eq 'Billing Address';
+ $hash->{$field};
+}
+
+sub p_or {
+ my ( $field, $hash ) = ( shift, shift );
+ $field = 'billing_'. $field
+ if $hash->{'billing_use'} eq 'Billing Address';
+ my $ac = ( $hash->{$field. '_area_code'}
+ && $hash->{$field. '_area_code'} =~ /^\d{3}$/ )
+ ? $hash->{$field. '_area_code'}. '-'
+ : '903-' # wtf?
+ ;
+ ( $hash->{$field} && $hash->{$field} =~ /^\d{3}-\d{4}$/)
+ ? $ac. $hash->{$field}
+ : '';
+}
+
+sub or_b {
+ my ( $field, $hash ) = ( shift, shift );
+ $hash->{'billing_use'} eq 'Billing Address' ? $hash->{$field} : '';
+}
+
+sub or_p {
+ my ( $field, $hash ) = ( shift, shift );
+ $hash->{'billing_use'} eq 'Billing Address' && $hash->{$field} =~ /^\d{3}-\d{4}$/
+ ? ( $hash->{$field. '_area_code'} =~ /^\d{3}$/
+ ? $hash->{$field. '_area_code'}. '-'
+ : '903-' # wtf?
+ ). $hash->{$field}
+ : '';
+}
+
+my %payby_map = ( '' => 'BILL',
+ 'None' => 'BILL',
+ 'Credit Card' => 'CARD',
+ 'Bank Debit' => 'CHEK',
+ 'Virtual Check' => 'CHEK',
+);
+sub payby {
+ $payby_map{ shift->{billing_type} };
+}
+
+sub payinfo {
+ my $hash = shift;
+ my $payby = payby($hash);
+ my $info;
+ my $cc =
+ $hash->{'credit_card_number_1'}.
+ $hash->{'credit_card_number_2'}.
+ $hash->{'credit_card_number_3'}.
+ $hash->{'credit_card_number_4'};
+ my $bank =
+ $hash->{'bank_account_number'}.
+ '@'.
+ $hash->{'bank_transit_number'};
+ if ($payby eq 'CARD') {
+ $info = $cc;
+ }elsif ($payby eq 'CHEK') {
+ $info = $bank;
+ }elsif ($payby eq 'BILL') {
+ $info = $hash->{'blanket_purchase_order_number'};
+ $bank =~ s/[^\d\@]//g;
+ $cc =~ s/\D//g;
+ if ( $bank =~ /^\d+\@\d{9}/) {
+ $info = $bank;
+ $payby = 'DCHK';
+ }
+ if ( $cc =~ /^\d{13,16}/ ) {
+ $info = $cc;
+ $payby = 'DCRD';
+ }
+ }else{
+ die "unexpected payby";
+ }
+ ($info, $payby);
+}
+
+sub ut_name_fixup {
+ my ($object, $field) = (shift, shift);
+ my $value = $object->getfield($field);
+ $value =~ s/[^\w \,\.\-\']/ /g;
+ $object->setfield($field, $value);
+}
+
+sub ut_text_fixup {
+ my ($object, $field) = (shift, shift);
+ my $value = $object->getfield($field);
+ $value =~ s/[^\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]/ /g;
+ $object->setfield($field, $value);
+}
+
+sub ut_state_fixup {
+ my ($object, $field) = (shift, shift);
+ my $value = $object->getfield($field);
+ $value = 'TX' if $value eq 'TTX';
+ $object->setfield($field, $value);
+}
+
+sub ut_zip_fixup {
+ my ($object, $field) = (shift, shift);
+ my $value = $object->getfield($field);
+ $value =~ s/[^-\d]//g;
+ $object->setfield($field, $value);
+}
+
+my @tables = (
+part_pkg => { 'stable' => 'product',
+#part_pkg => { 'stable' => 'billcycle',
+ 'mapping' =>
+ { 'pkg' => sub { my $href = shift;
+ $href->{'description'}
+ ? $href->{'description'}
+ : $href->{'product_id'};
+ },
+ 'comment' => 'product_id',
+ 'freq' => sub { pkg_freq(shift) },
+ 'recur_fee'=> sub { my $href = shift;
+ my $price = ( pkg_freq($href)
+ ? $href->{'unit_price'}
+ : 0
+ );
+ $price =~ s/[^\d.]//g;
+ $price = 0 unless $price;
+ sprintf("%.2f", $price);
+ },
+ 'setuptax' => sub { my $href = shift;
+ $href->{'taxable'} ? '' : 'Y';
+ },
+ 'recurtax' => sub { my $href = shift;
+ $href->{'taxable'} ? '' : 'Y';
+ },
+ 'plan' => sub { 'flat' },
+ 'disabled' => sub { 'Y' },
+ 'pkg_svc' => sub { my $href = shift;
+ my $result = {};
+ if (pkg_freq($href)){
+ $result->{$legacy_ppp_svcpart} = 1;
+ $result->{$legacy_email_svcpart} =
+ $href->{emails_allowed}
+ if $href->{emails_allowed};
+ }
+ },
+ 'primary_svc'=> sub { pkg_freq(shift)
+ ? $legacy_ppp_svcpart
+ : ''
+ ;
+ },
+ },
+ 'fixup' => sub { my $part_pkg = shift;
+ my $row = shift;
+ unless ($part_pkg->pkg =~ /^\s*(\S[\S ]*?)\s*$/) {
+ warn "no pkg: ". $part_pkg->pkg. " for ". $row->{product_id};
+ return 1;
+ }
+
+ unless ($part_pkg->comment =~ /^\s*(\S[\S ]*?)\s*$/) {
+ warn "no comment: ". $part_pkg->comment. " for ". $row->{product_id};
+ return 1;
+ }
+
+ return 1 if exists($package_cache{$1});
+ $package_cache{$1} = $part_pkg;
+ 1;
+ },
+ 'wrapup' => sub { foreach (keys %package_cache) {
+ my $part_pkg = $package_cache{$_};
+ my $options =
+ { map { my $v = $part_pkg->$_;
+ $part_pkg->$_('');
+ ($_ => $v);
+ }
+ qw (setup_fee recur_fee)
+ };
+ my $error =
+ $part_pkg->insert(options=>$options);
+ die "Error inserting package: $error"
+ if $error;
+ $count++ unless $error;
+ }
+ },
+ },
+part_referral => { 'stable' => 'cust',
+ 'mapping' =>
+ { 'agentnum' => sub { $agentnum },
+ 'referral' => sub { my $r = shift->{'referred_from'};
+ $referrals{$r} = 1;
+ },
+ },
+ 'fixup' => sub { 1 },
+ 'wrapup' => sub { foreach (keys %referrals) {
+ my $part_referral =
+ new FS::part_referral( {
+ 'agentnum' => $agentnum,
+ 'referral' => $referrals{$_},
+ } );
+ my $error = $part_referral->insert;
+ die "Error inserting referral: $error"
+ if $error;
+ $count++ unless $error;
+ $referrals{$_} = $part_referral->refnum;
+ }
+ },
+ },
+#svc_acct => { 'stable' => 'cust',
+# 'mapping' =>
+# { 'username' => 'login',
+# '_password' => 'password',
+# 'svcpart' => sub{ $legacy_ppp_svcpart },
+# 'domsvc' => sub{ $legacy_domain_svcnum },
+# 'status' => 'status',
+# },
+# 'fixup' => sub { my $svc_acct = shift;
+# my $row = shift;
+# my $id = $row->{'master_account'}
+# ? 'slave:'. $row->{'customer_id'}
+# : $row->{'login'};
+# my $status = $svc_acct->status;
+# if ( $status ne 'Current'
+# && $status ne 'On Hold' )
+# {
+# $cancel{$id} =
+# str2time($row->{termination_date});
+# warn "not creating (cancelled) svc_acct for " .
+# $svc_acct->username. "\n";
+# return 1
+# }
+# $susp{$id} = str2time($row->{hold_date})
+# if $status eq 'On Hold';
+# $adjo{$id} = str2time($row->{hold_date})
+# if ( $status eq 'Current' &&
+# $row->{hold_date} );
+# $bill{$id} =
+# str2time($row->{expiration_date});
+# '';
+# },
+# 'skey' => sub { my $svc_acct = shift;
+# my $row = shift;
+# my $id = $row->{'master_account'}
+# ? 'slave:'. $row->{'customer_id'}
+# : $row->{'login'};
+# },
+# },
+cust_main => { 'stable' => 'cust',
+ 'mapping' =>
+ { 'agentnum' => sub { $agentnum },
+ 'agent_custid' => sub { my $id = shift->{'customer_number'};
+ if (exists($custid{$id})) {
+ $custid{$id}++;
+ $id. chr(64 + $custid{$id});
+ }else{
+ $custid{$id} = 0;
+ $id;
+ }
+ },
+ 'last' => sub { b_or('last_name', shift) || ' ' },
+ 'first' => sub { b_or('first_name', shift) || ' ' },
+ 'stateid' => 'drivers_license_number',
+ 'signupdate' => sub { str2time(shift->{'creation_date'}) },
+ 'company' => sub { b_or('company_name', shift) },
+ 'address1' => sub { b_or('address', shift) || ' ' },
+ 'city' => sub { b_or('city', shift) || 'Paris' },
+ 'state' => sub { uc(b_or('state', shift)) || 'TX' },
+ 'zip' => sub { b_or('zip_code', shift) || '75460' },
+ 'country' => sub { 'US' },
+ 'daytime' => sub { p_or('phone', shift) },
+ 'night' => sub { p_or('phone_alternate_1', shift) },
+ 'fax' => sub { p_or('fax', shift) },
+ 'ship_last' => sub { or_b('last_name', shift) },
+ 'ship_first' => sub { or_b('first_name', shift) },
+ 'ship_company' => sub { or_b('company_name', shift) },
+ 'ship_address1'=> sub { or_b('address', shift) },
+ 'ship_city' => sub { or_b('city', shift) },
+ 'ship_state' => sub { uc(or_b('state', shift)) },
+ 'ship_zip' => sub { or_b('zip_code', shift) },
+ 'ship_daytime' => sub { or_p('phone', shift) },
+ 'ship_fax' => sub { or_p('fax', shift) },
+ 'tax' => sub { shift->{taxable} eq '' ? 'Y' : '' },
+ 'refnum' => sub { $referrals{shift->{'referred_from'}}
+ || 1
+ },
+ },
+ 'fixup' => sub { my $cust_main = shift;
+ my $row = shift;
+
+ my ($master_account, $customer_id, $login) =
+ ('', '', '');
+ $row->{'master_account'} =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($master_account = $1);
+ $row->{'customer_id'} =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($customer_id = $1);
+ $row->{'login'} =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($login = $1);
+
+ my ($first, $last, $company) =
+ ('', '', '');
+ $cust_main->first =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($first = $1);
+ $cust_main->last =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($last = $1);
+ $cust_main->company =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($company = $1);
+
+ unless ($first || $last || $company) {
+ warn "bogus entry: ". $row->{'login'};
+ return 1;
+ }
+
+ my $id = $master_account
+ ? 'slave:'. $customer_id
+ : $login;
+ #my $id = $login;
+ my $status = $row->{status};
+
+ my $cancelled = 0;
+ if ( $status ne 'Current'
+ && $status ne 'current'
+ && $status ne 'On Hold' )
+ {
+ $cancelled = 1;
+ $cancel{$login} =
+ str2time($row->{termination_date});
+ }
+ $susp{$id} = str2time($row->{hold_date})
+ if ($status eq 'On Hold' && !$cancelled);
+ $adjo{$id} = str2time($row->{hold_date})
+ if ( $status eq 'Current' && !$cancelled &&
+ $row->{hold_date} );
+ $bill{$id} =
+ str2time($row->{expiration_date})
+ if (!$cancelled);
+
+ my $svcnum =
+ $object_map{legacy_ppp}{$row->{'login'} };
+ unless( $cancelled || $svcnum || $status eq 'Pn Hold' ) {
+ warn "can't find svc_acct for legacy ppp ".
+ $row->{'login'}, "\n";
+ }
+
+ $object_map{svc_acct}{$id} = $svcnum
+ unless $cancelled;
+
+ $master_map{$login} = $master_account
+ if $master_account;
+ return 1 if $master_account;
+ $cust_main->ship_country('US')
+ if $cust_main->has_ship_address;
+ ut_name_fixup($cust_main, 'first');
+ ut_name_fixup($cust_main, 'company');
+ ut_name_fixup($cust_main, 'last');
+
+ my ($info, $payby) = payinfo($row);
+ $cust_main->payby($payby);
+ $cust_main->payinfo($info);
+
+ $cust_main->paycvv(
+ $row->{'credit_card_cvv_number'}
+ )
+ if ($payby eq 'CARD' or $payby eq 'DCRD');
+
+ $cust_main->paydate('20'.
+ $row->{'credit_card_exp_date_2'}. '-'.
+ substr(
+ $row->{'credit_card_exp_date_1'},
+ 0,
+ 2,
+ ).
+ '-01'
+ )
+ if ($payby eq 'CARD' or $payby eq 'DCRD');
+
+ my $payname = '';
+ $payname = $row->{'credit_card_name'}
+ if ($payby eq 'CARD' or $payby eq 'DCRD');
+ $payname = $row->{'bank_name'}
+ if ($payby eq 'CHEK' or $payby eq 'DCHK');
+ $cust_main->payname($payname);
+
+ $cust_main->paytype(
+ $row->{'bank_account_to_debit'}
+ ? 'Personal '.
+ $row->{bank_account_to_debit}
+ : ''
+ )
+ if ($payby eq 'CHEK' or $payby eq 'DCHK');
+
+ $cust_main->payby('BILL')
+ if ($cust_main->payby eq 'CHEK' &&
+ $cust_main->payinfo !~ /^\d+\@\d{9}$/);
+ $cust_main->payby('BILL')
+ if ($cust_main->payby eq 'CARD' &&
+ $cust_main->payinfo =~ /^\s*$/);
+ $cust_main->paydate('2037-12-01')
+ if ($cust_main->payby eq 'BILL');
+ ut_text_fixup($cust_main, 'address1');
+ ut_state_fixup($cust_main, 'state');
+ ut_zip_fixup($cust_main, 'zip');
+
+
+ '';
+ },
+ 'skey' => sub { my $object = shift;
+ my $href = shift;
+ my $balance = sprintf("%.2f",
+ $href->{balance_due});
+ if ($balance < 0) {
+ my $cust_credit = new FS::cust_credit({
+ 'custnum' => $object->custnum,
+ 'amount' => sprintf("%.2f", -$balance),
+ 'reasonnum' => $previous_credit_reasonnum,
+ });
+ my $error = $cust_credit->insert;
+ warn "Error inserting credit for ",
+ $href->{'login'}, " : $error\n"
+ if $error;
+
+ }elsif($balance > 0) {
+ my $error = $object->charge(
+ $balance, "Prior balance",
+ );
+ warn "Error inserting balance charge for ",
+ $href->{'login'}, " : $error\n"
+ if $error;
+
+ }
+ $href->{'login'};
+ },
+ },
+#cust_main => { 'stable' => 'cust',
+# 'mapping' =>
+# { 'referred_by' => sub { my $href = shift;
+# my $u = shift->{'login'};
+# my $cn = $href->{'customer_number'};
+#
+# my $c = qsearch( 'cust_main',
+# { 'custnum' => $cn }
+# ) or die "can't fine customer $cn";
+#
+# my $s = qsearch( 'svc_acct',
+# { 'username' => $u }
+# ) or return '';
+#
+# my $n = $s->cust_svc
+# ->cust_pkg
+# ->cust_main
+# ->custnum;
+#
+# $c->referral_custnum($n);
+# my $error = $c->replace;
+# die "error setting referral: $error"
+# if $error;
+# '';
+# },
+# };
+# 'fixup' => sub { 1 },
+# },
+cust_pkg => { 'stable' => 'billcycle',
+ 'mapping' =>
+ { 'custnum' => sub { my $l = shift->{cbilling_cycle_login};
+ $l =~ /^\s*(\S[\S ]*?)\s*$/ && ($l = $1);
+ my $r = $object_map{'cust_main'}{$l};
+ unless ($r) {
+ my $m = $master_map{$l};
+ $r = $object_map{'cust_main'}{$m}
+ if $m;
+ }
+ $r;
+ },
+ 'pkgpart' => sub { my $href = shift;
+ my $p = $href->{product_id};
+ $p =~ /^\s*(\S[\S ]*?)\s*$/ && ($p = $1);
+ my $pkg = $package_cache{$p}
+ if $package_cache{$p};
+
+ my $month = '';
+ $href->{month_credit} =~ /\s*(\S[\S ]*?)\s*$/ && ($month = $1);
+ $month = int(eval "$month + 0");
+
+ my $price = 0;
+ $href->{unit_price} =~ /\s*(\S[\S ]*?)\s*$/ && ($price = $1);
+ $price = eval "$price + 0";
+
+ if ($pkg) {
+ $pkg = ''
+ unless $pkg->freq + 0 == $month;
+
+ if ($pkg && ($pkg->freq + 0)) {
+ my $recur = 0;
+ $pkg->recur_fee =~ /\s*(\S[\S ]*?)\s*$/ && ($recur = $1);
+ $recur = eval "$recur + 0";
+ $pkg = ''
+ unless $recur == $price;
+ }
+
+ if ($pkg) {
+ $pkg = ''
+ unless $pkg->setuptax
+ eq ($href->{taxable} ? '' : 'Y');
+ }
+
+ }
+
+ unless ($pkg) {
+ my $pkghref = { 'pkg' => ($href->{description} ? $href->{description} : $href->{product_id} ),
+ 'comment' => $href->{product_id},
+ 'freq' => $month,
+ 'setuptax' => ($href->{'taxable'} ? '' : 'Y'),
+ 'recurtax' => ($href->{'taxable'} ? '' : 'Y'),
+ 'plan' => 'flat',
+ 'disabled' => 'Y',
+ };
+
+ my @pkgs = qsearch('part_pkg', $pkghref);
+ my $recur = sprintf("%.2f", ($month ? $price : 0));
+ for (@pkgs) {
+ my %options = $_->options;
+ if ($options{recur_fee} eq $recur) {
+ $pkg = $_;
+ last;
+ }
+ }
+
+ $pkghref->{recur_fee} = $recur
+ unless $pkg;
+
+ my $pkg_svc = {};
+
+ if ($month){
+ $pkg_svc->{$legacy_ppp_svcpart} = 1;
+ $pkg_svc->{$legacy_email_svcpart} =
+ $href->{emails_allowed}
+ if $href->{emails_allowed};
+ }
+ $pkghref->{pkg_svc} = $pkg_svc;
+ $pkghref->{primary_svc}
+ = ( $month
+ ? $legacy_ppp_svcpart
+ : '');
+ unless ($pkg) {
+ $pkg = new FS::part_pkg $pkghref;
+ my $options =
+ { map { my $v = $pkg->$_;
+ $pkg->$_('');
+ ($_ => $v);
+ }
+ qw (setup_fee recur_fee)
+ };
+ my $error =
+ $pkg->insert(options=>$options);
+ if ($error) {
+ warn "Error inserting pkg ".
+ join(", ", map{"$_ => ". $pkg->get($_)} fields $pkg).
+ ": $error\n";
+ $pkg = '';
+ }
+ }
+ }
+ $pkg ? $pkg->pkgpart : '';
+ },
+ 'setup' => sub { str2time(shift->{creation_date}) },
+ 'bill' => sub { $bill{account_id(shift)}
+ #$bill{$href->{cbilling_cycle_login}};
+ },
+ 'susp' => sub { $susp{account_id(shift)}
+ #$susp{$href->{cbilling_cycle_login}};
+ },
+ 'adjo' => sub { $adjo{account_id(shift)}
+ #$adjo{$href->{cbilling_cycle_login}};
+ },
+ 'cancel' => sub { $cancel{account_id(shift)}
+ #$cancel{$href->{cbilling_cycle_login}};
+ },
+ },
+ 'fixup' => sub { my ($object, $row) = (shift,shift);
+ unless ($object->custnum) {
+ warn "can't find customer for ".
+ $row->{cbilling_cycle_login}. "\n";
+ return 1;
+ }
+ unless ($object->pkgpart) {
+ warn "can't find package for ".
+ $row->{product_id}. "\n";
+ return 1;
+ }
+ '';
+ },
+ 'skey' => sub { my $object = shift;
+ my $href = shift;
+ my $id = $href->{'billing_cycle_item_id'};
+ $id =~ /^\s*(\S[\S ]*?)\s*$/ && ($id = $1);
+ $cust_pkg_map{$id} = $object->pkgnum;
+ account_id($href);
+ },
+ 'wrapup' => sub { for my $id (keys %{$object_map{'cust_pkg'}}){
+ my $cust_svc =
+ qsearchs( 'cust_svc', { 'svcnum' =>
+ $object_map{'svc_acct'}{$id} }
+ );
+ unless ($cust_svc) {
+ warn "can't find legacy ppp $id\n";
+ next;
+ }
+ $cust_svc->
+ pkgnum($object_map{'cust_pkg'}{$id});
+ my $error = $cust_svc->replace;
+ warn "error linking legacy ppp $id: $error\n"
+ if $error;
+ }
+ },
+ },
+svc_acct => { 'stable' => 'email',
+ 'mapping' =>
+ { 'username' => 'email_name',
+ '_password' => 'password',
+ 'svcpart' => sub{ $legacy_email_svcpart },
+ 'domsvc' => sub{ $legacy_domain_svcnum },
+ },
+# 'fixup' => sub { my ($object, $row) = (shift,shift);
+# my ($sd,$sm,$sy) = split '/',
+# $row->{shut_off_date}
+# if $row->{shut_off_date};
+# if ($sd && $sm && $sy) {
+# my ($cd, $cm, $cy) = (localtime)[3,4,5];
+# $cy += 1900; $cm++;
+# return 1 if $sy < $cy;
+# return 1 if ($sy == $cy && $sm < $cm);
+# return 1 if ($sy == $cy && $sm == $cm && $sd <= $cd);
+# }
+# return 1 if $object_map{'cust_main'}{$object->username};
+# '';
+# },
+ 'fixup' => sub { my ($object, $row) = (shift,shift);
+ my ($sd,$sm,$sy) = split '/',
+ $row->{shut_off_date}
+ if $row->{shut_off_date};
+ if ($sd && $sm && $sy) {
+ my ($cd, $cm, $cy) = (localtime)[3,4,5];
+ $cy += 1900; $cm++;
+ return 1 if $sy < $cy;
+ return 1 if ($sy == $cy && $sm < $cm);
+ return 1 if ($sy == $cy && $sm == $cm && $sd <= $cd);
+ }
+ #return 1 if $object_map{'cust_main'}{$object->username};
+
+ my $email_name;
+ $row->{email_name} =~ /^\s*(\S[\S ]*?)\s*$/
+ && ($email_name = $1);
+
+ my $svcnum =
+ $object_map{legacy_email}{$email_name}
+ if $email_name;
+ unless( $svcnum ) {
+ warn "can't find svc_acct for legacy email ".
+ $row->{'email_name'}, "\n";
+ return 1;
+ }
+
+ $object_map{svc_acct}{'email:'.$row->{'email_customer_id'}} = $svcnum;
+ return 1;
+ },
+# 'skey' => sub { my $object = shift;
+# my $href = shift;
+# 'email:'. $href->{'email_customer_id'};
+# },
+ 'wrapup' => sub { for my $id (keys %{$object_map{'svc_acct'}}){
+ next unless $id =~ /^email:(\d+)/;
+ my $custid = $1;
+ my $cust_svc =
+ qsearchs( 'cust_svc', { 'svcnum' =>
+ $object_map{'svc_acct'}{$id} }
+ );
+ unless ($cust_svc) {
+ warn "can't find legacy email $id\n";
+ next;
+ }
+
+ if ($cust_svc->pkgnum) {
+ warn "service already linked for $id\n";
+ next;
+ }
+
+ $cust_svc->
+ pkgnum($cust_pkg_map{$custid});
+ if ($cust_svc->pkgnum){
+ my $error = $cust_svc->replace;
+ warn "error linking legacy email $id: $error\n"
+ if $error;
+ }else{
+ warn "can't find package for $id\n"
+ }
+ }
+ },
+ },
+);
+
+#my $s_dbh = DBI->connect($s_datasrc, $s_dbuser, $s_dbpass) or die $DBI::errstr;
+
+while ( @tables ) {
+ my ($table, $href) = (shift @tables, shift @tables);
+ my $stable = $href->{'stable'} or die "No source table"; # good enough for now
+ my (%mapping) = %{$href->{'mapping'}};
+ my ($fixup) = $href->{'fixup'};
+ my ($wrapup) = $href->{'wrapup'};
+ my ($id) = $href->{'id'};
+ my ($skey) = $href->{'skey'};
+
+ #$d_dbh->do("delete from $table");
+
+ my $s_sth = $s_dbh->prepare("select count(*) from $stable");
+ $s_sth->execute or die $s_sth->errstr;
+ my $rowcount = $s_sth->fetchrow_arrayref->[0];
+
+ $s_sth = $s_dbh->prepare("select * from $stable");
+ $s_sth->execute or die $s_sth->errstr;
+
+ my $row;
+ $count = 0;
+ while ( $row = $s_sth->fetchrow_hashref ) {
+ my $class = "FS::$table";
+
+ warn sprintf("%.2f", 100*$count/$rowcount). "% of $table processed\n"
+ unless( !$count || $count % 100 );
+
+ my $object = new $class ( {
+ map { $_ => ( ref($mapping{$_}) eq 'CODE'
+ ? &{$mapping{$_}}($row)
+ : $row->{$mapping{$_}}
+ )
+ }
+ keys(%mapping)
+ } );
+ my $skip = &{$fixup}($object, $row)
+ if $fixup;
+
+ unless ($skip) {
+ my $error = $object->insert;
+ if ($error) {
+ warn "Error inserting $table ".
+ join(", ", map{"$_ => ". $object->get($_)} fields $object).
+ ": $error\n";
+ next;
+ }
+ if ($skey) {
+ my $key = (ref($skey) eq 'CODE') ? &{$skey}($object, $row)
+ : $row->{$skey};
+ $object_map{$table}{$key} = $object->get($object->primary_key)
+ }
+ $count++;
+ }
+ }
+
+ &{$wrapup}()
+ if $wrapup;
+
+ print "$count/$rowcount of $table SUCCESSFULLY processed\n";
+
+}
+
+# link to any uncancelled package on customer
+foreach my $username ( keys %{$object_map{'legacy_email'}} ) {
+ my $cust_svc = qsearchs( 'cust_svc',
+ { 'svcnum' => $object_map{legacy_email}{$username} }
+ );
+ next unless $cust_svc;
+ next if $cust_svc->pkgnum;
+
+ my $custnum = $object_map{cust_main}{$username};
+ unless ($custnum) {
+ my $master = $master_map{$username};
+ $custnum = $object_map{'cust_main'}{$master}
+ if $master;
+ next unless $custnum;
+ }
+
+ #my $extra_sql = ' AND 0 != (select freq from part_pkg where '.
+ # 'cust_pkg.pkgpart = part_pkg.pkgpart )';
+ my $extra_sql = " AND 'Prior balance' != (select pkg from part_pkg where ".
+ "cust_pkg.pkgpart = part_pkg.pkgpart )";
+
+ my @cust_pkg = qsearch( {
+ 'table' => 'cust_pkg',
+ 'hashref' => { 'custnum' => $custnum,
+ 'cancel' => '',
+ },
+ 'extra_sql' => $extra_sql,
+ } );
+ next unless scalar(@cust_pkg);
+
+ $cust_svc->pkgnum($cust_pkg[0]->pkgnum);
+ $cust_svc->replace;
+}
+
+
+if ($dry_run) {
+ $d_dbh->rollback;
+}else{
+ $d_dbh->commit or die $d_dbh->errstr;
+}
+
diff --git a/bin/import-tax-rates b/bin/import-tax-rates
new file mode 100755
index 0000000..1cb76e0
--- /dev/null
+++ b/bin/import-tax-rates
@@ -0,0 +1,56 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($opt_c $opt_p $opt_t $opt_d $opt_z $opt_f);
+use vars qw($DEBUG);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::tax_rate;
+use FS::cust_tax_location;
+
+getopts('c:p:t:d:z:f:');
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my ($format) = $opt_f =~ /^([-\w]+)$/;
+
+my @list = (
+ 'CODE', $opt_c, \&FS::tax_class::batch_import,
+ 'PLUS4', $opt_p, \&FS::cust_tax_location::batch_import,
+ 'ZIP', $opt_z, \&FS::cust_tax_location::batch_import,
+ 'TXMATRIX', $opt_t, \&FS::part_pkg_taxrate::batch_import,
+ 'DETAIL', $opt_d, \&FS::tax_rate::batch_import,
+);
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+my $error = '';
+
+while(@list) {
+ my ($name, $file, $method) = splice(@list, 0, 3);
+
+ my $fh;
+
+ $file =~ /^([\s\d\w.]+)$/ or die "Illegal filename: $file\n";
+ $file = $1;
+
+ my $f = $format;
+ $f .= '-zip' if $name eq 'ZIP';
+
+ open $fh, '<', $file or die "can't open $name file: $!\n";
+ $error ||= &{$method}( { filehandle => $fh, 'format' => $f, } );
+
+ die "error while processing $file: $error" if $error;
+ close $fh;
+}
+
+if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+}else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+sub usage { die "Usage:\nimport-tax-rates f FORMAT -c CODEFILE -p PLUS4FILE -z ZIPFILE -t TXMATRIXFILE -d DETAILFILE user\n\n"; }
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/japan.pl b/bin/japan.pl
new file mode 100755
index 0000000..14e44e4
--- /dev/null
+++ b/bin/japan.pl
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch );
+use FS::cust_main_county;
+
+adminsuidsetup shift;
+
+my $country = 'JP';
+
+foreach my $cust_main_county (
+ qsearch('cust_main_county', { 'country' => $country } )
+) {
+
+ if ( $cust_main_county->state =~ /\[([\w ]+)\]\s*$/ ) {
+ $cust_main_county->state($1);
+ my $error = $cust_main_county->replace;
+ die $error if $error;
+ }
+
+}
+
+
+#use Locale::SubCountry;
+#
+##my $state = 'Tôkyô [Tokyo]';
+#my $state = 'Tottori';
+#
+#my $lsc = new Locale::SubCountry 'JP';
+#
+#print $lsc->code($state)."\n";
+
diff --git a/bin/make-pkg-fruit b/bin/make-pkg-fruit
new file mode 100755
index 0000000..61d707f
--- /dev/null
+++ b/bin/make-pkg-fruit
@@ -0,0 +1,172 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch qsearchs );
+use FS::part_export;
+use FS::export_svc;
+use FS::pkg_svc;
+use FS::part_svc;
+use FS::part_pkg;
+use FS::cust_svc;
+use FS::svc_Common;
+use FS::svc_broadband;
+use FS::part_svc_router;
+
+my $exporttype = 'prizm';
+my $pkg_property = 'pkg';
+my $svc_property = 'performance_profile';
+
+my $user = shift or die &usage;
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::cust_svc::ignore_quantity = 1;
+$FS::UID::AutoCommit = 0;
+
+my $DEBUG = 0;
+
+my $dbh = adminsuidsetup($user);
+
+my @exportnum = map { $_->exportnum }
+ qsearch( 'part_export', { 'exporttype' => $exporttype } );
+
+die "no $exporttype exports found\n" unless scalar(@exportnum);
+
+my %pkg_svc_map = ();
+
+my @old_svcpart = ();
+push @old_svcpart, map { $_->svcpart }
+ qsearch ( 'export_svc', { 'exportnum' => $_ } )
+ foreach @exportnum;
+
+die "no svcparts found\n" unless scalar(@old_svcpart);
+
+foreach (@old_svcpart) {
+ foreach my $pkg_svc ( qsearch( 'pkg_svc',
+ { 'svcpart' => $_,
+ 'quantity' => { 'op' => '>',
+ 'value' => '0',
+ },
+ }
+ )
+ )
+ {
+ warn "updating package ". $pkg_svc->pkgpart. "\n" if $DEBUG;
+ my $pkg_from = $pkg_svc->part_pkg->$pkg_property;
+ unless ( $pkg_svc_map{ $pkg_from }{ $pkg_svc->svcpart } ) {
+ my $old_part_svc = $pkg_svc->part_svc;
+ my $part_svc = new FS::part_svc( { $old_part_svc->hash } );
+ $part_svc->svcpart('');
+
+ my $svcdb = $part_svc->svcdb;
+ foreach ( $old_part_svc->all_part_svc_column ) {
+ my $formatter = FS::part_svc->svc_table_fields($svcdb)->{$_}->{format}
+ || sub { shift };
+
+ $part_svc->setfield( $svcdb.'__'.$_->columnname.'_flag', $_->columnflag);
+ $part_svc->setfield( $svcdb.'__'.$_->columnname,
+ &$formatter($_->columnvalue)
+ );
+ }
+
+ my $formatter =
+ FS::part_svc->svc_table_fields($svcdb)->{$svc_property}->{format}
+ || sub { shift };
+ $part_svc->setfield( $svcdb.'__'.$svc_property.'_flag', 'F');
+ $part_svc->setfield( $svcdb.'__'.$svc_property,
+ &$formatter($pkg_svc->part_pkg->$pkg_property)
+ );
+ my $error = $part_svc->insert( [],
+ { map { $_->exportnum => 1 }
+ $old_part_svc->part_export
+ },
+ );
+ die "error inserting service: $error\n" if $error;
+
+ # this part is specific to svc_broadband
+ foreach (qsearch( 'part_svc_router', { 'svcpart' => $pkg_svc->svcpart } ))
+ {
+ my $part_svc_router = new FS::part_svc_router( { $_->hash } );
+ $part_svc_router->svcrouternum( '' );
+ $part_svc_router->svcpart( $part_svc->svcpart );
+ my $error = $part_svc_router->insert;
+ die "error associating service with router: $error\n" if $error;
+ }
+
+ $pkg_svc_map{ $pkg_from }{ $pkg_svc->svcpart } = $part_svc->svcpart;
+
+ }
+
+ my $new_pkg_svc = new FS::pkg_svc( { $pkg_svc->hash } );
+ $new_pkg_svc->svcpart( $pkg_svc_map{ $pkg_from }{ $pkg_svc->svcpart } );
+ my $error = $pkg_svc->delete;
+ die "error removing old service from package: $error\n" if $error;
+ $error = $new_pkg_svc->insert;
+ die "error adding new service to package: $error\n" if $error;
+
+ }
+}
+warn "done with packages\n" if $DEBUG;
+
+foreach my $svcpart ( @old_svcpart ) {
+ foreach my $cust_svc ( qsearch( 'cust_svc', { 'svcpart' => $svcpart } ) ) {
+ my $svc_x = $cust_svc->svc_x;
+ my $cust_pkg = $cust_svc->cust_pkg;
+ die "can't handle unattached service ". $cust_svc->svcnum unless $cust_pkg;
+ my $pkg_from = $cust_pkg->part_pkg->$pkg_property;
+ $svc_x->setfield( $svc_property, $pkg_from );
+ $svc_x->setfield( 'svcpart', $pkg_svc_map{ $pkg_from }{ $svcpart } );
+ my $error = $svc_x->replace;
+ die "error replacing service ". $svc_x->svcnum. ": $error\n" if $error;
+
+ $cust_svc->svcpart( $pkg_svc_map{ $pkg_from }{ $svcpart } );
+ $error = $cust_svc->replace;
+ die "error replacing customer service ". $cust_svc->svcnum. ": $error\n"
+ if $error;
+ }
+
+ my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "can't find old part_svc!" unless $part_svc;
+
+ my $new_part_svc = new FS::part_svc( { $part_svc->hash } );
+ $new_part_svc->disabled('Y');
+ my $svcdb = $part_svc->svcdb;
+ foreach ( $part_svc->all_part_svc_column ) {
+ my $formatter = FS::part_svc->svc_table_fields($svcdb)->{$_}->{format}
+ || sub { shift };
+
+ $part_svc->setfield( $svcdb.'__'.$_->columnname.'_flag', $_->columnflag);
+ $part_svc->setfield( $svcdb.'__'.$_->columnname,
+ &$formatter($_->columnvalue)
+ );
+ }
+ my $error = $new_part_svc->replace($part_svc, '1.3-COMPAT');
+ die "error disabling service: $error\n" if $error;
+}
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+
+sub usage {
+ die "Usage:\n\n make-pkg-fruit user\n";
+}
+
+=head1 NAME
+
+make-pkg-fruit - Tool to migrate package properties to services
+
+=head1 SYNOPSIS
+
+ make-pkg-fruit
+
+=head1 DESCRIPTION
+
+Multiplies out services with package properties and migrates package
+definitions and customer services to the new services. Read the source.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
diff --git a/bin/mapsecrets2access_user b/bin/mapsecrets2access_user
new file mode 100755
index 0000000..d632360
--- /dev/null
+++ b/bin/mapsecrets2access_user
@@ -0,0 +1,87 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Copy "cp";
+use FS::UID qw(adminsuidsetup);
+use FS::CurrentUser;
+use FS::AccessRight;
+use FS::Record qw(qsearchs qsearch);
+use FS::access_group;
+use FS::access_user;
+use FS::access_usergroup;
+use FS::access_right;
+use FS::access_groupagent;
+use FS::agent;
+
+$FS::CurrentUser::upgrade_hack = 1;
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $supergroup = qsearchs('access_group', { 'groupname' => 'Superuser' } );
+unless ( $supergroup ) {
+
+ $supergroup = new FS::access_group { 'groupname' => 'Superuser' };
+ my $error = $supergroup->insert;
+ die $error if $error;
+
+ foreach my $rightname ( FS::AccessRight->default_superuser_rights ) {
+ my $access_right = new FS::access_right {
+ 'righttype' => 'FS::access_group',
+ 'rightobjnum' => $supergroup->groupnum,
+ 'rightname' => $rightname,
+ };
+ my $ar_error = $access_right->insert;
+ die $ar_error if $ar_error;
+ }
+
+ foreach my $agent ( qsearch('agent', {} ) ) {
+ my $access_groupagent = new FS::access_groupagent {
+ 'groupnum' => $supergroup->groupnum,
+ 'agentnum' => $agent->agentnum,
+ };
+ my $aga_error = $access_groupagent->insert;
+ die $aga_error if $aga_error;
+ }
+
+}
+my $supergroupnum = $supergroup->groupnum;
+
+my $conf = new FS::Conf;
+my $dir = $conf->base_dir;
+my $mapsecrets = "$dir/mapsecrets";
+open(MAPSECRETS, "<$mapsecrets") or die "Can't open $mapsecrets: $!";
+while (<MAPSECRETS>) {
+ /([\w]+)\s+secrets\s*$/ or die "unparsable line in mapsecrets: $_";
+ my $username = $1;
+
+ next if qsearchs('access_user', { 'username' => $username } );
+
+ my $access_user = new FS::access_user {
+ 'username' => $username,
+ '_password' => 'notyet',
+ 'first' => 'Legacy',
+ 'last' => 'User',
+ };
+ my $au_error = $access_user->insert;
+ die $au_error if $au_error;
+
+ my $access_usergroup = new FS::access_usergroup {
+ 'usernum' => $access_user->usernum,
+ 'groupnum' => $supergroupnum,
+ };
+ my $aug_error = $access_usergroup->insert;
+ die $aug_error if $aug_error;
+
+}
+close MAPSECRETS;
+
+# okay to clobber mapsecrets now i guess
+cp $mapsecrets, "$mapsecrets.bak$$";
+open(MAPSECRETS, ">$mapsecrets") or die $!;
+print MAPSECRETS '* secrets'. "\n";
+close MAPSECRETS or die $!;
+
+sub usage {
+ die "Usage:\n mapsecrets2access_user user\n";
+}
+
diff --git a/bin/masonize b/bin/masonize
new file mode 100755
index 0000000..509ef3e
--- /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 <%= ??? (in $file):";
+
+ } 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/monitor b/bin/monitor
new file mode 100755
index 0000000..8dac700
--- /dev/null
+++ b/bin/monitor
@@ -0,0 +1,127 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $DEBUG );
+use Getopt::Std;
+use FS::Daemon qw(daemonize1 daemonize2 logfile sigint sigterm);
+use FS::Yori qw(report);
+use Email::Send;
+
+$DEBUG = 0;
+
+&untaint_argv; #what it sounds like (eww)
+
+use vars qw(%opt);
+getopts('m:p:', \%opt );
+
+my ($machine, @emails) = @ARGV;
+die &usage unless @emails;
+
+warn "starting daemonization (forking)\n" if $DEBUG;
+daemonize1('freeside-monitor');
+#logfile( "%%%FREESIDE_LOG%%%/monitorlog.$machine" );
+logfile( "/usr/local/etc/freeside/monitorlog.$machine" );
+
+warn "completing daemonization (detaching))\n" if $DEBUG;
+daemonize2();
+
+my $wantfree = $opt{m} || 1048576;
+my $wantload = $opt{p} || 5;
+
+die 'bogus memory requirement: $wantfree'
+ unless $wantfree && $wantfree =~ /^\d+$/;
+
+die 'bogus load requirement: $wantload'
+ unless $wantload && $wantload =~ /^[\d.]+$/;
+
+my $alerts = 0;
+my $last = time();
+while (1) {
+
+ my(undef, $load, undef) = report('load');
+ my($free) = report('freememory');
+
+ warn "free is $free and wantfree is $wantfree\n" if $DEBUG > 1;
+ warn "load is $load and wantload is $wantload\n" if $DEBUG > 1;
+ warn "last is $last\n" if $DEBUG > 1;
+
+ unless( defined($load) && $load < $wantload
+ && defined($free) && $free > $wantfree
+ || ( time() < $last + 1800 && $alerts > 2 ) )
+ {
+ warn localtime(). ": $machine has load of $load and $free kB free memory\n";
+ $alerts++;
+ $alerts = 0 if time() > $last + 1800;
+ $last = time();
+ foreach my $email ( @emails ) {
+
+ my $message = <<"__MESSAGE__";
+From: support\@freeside.biz
+To: $email
+Subject: ALERT - $machine
+
+ALERT: $machine has a load of $load and only $free kB free..
+
+__MESSAGE__
+
+ my $sender = Email::Send->new({mailer => 'SMTP'});
+ $sender->mailer_args([Host => 'mail.freeside.biz']);
+ $sender->send($message);
+
+ }
+
+ }
+
+
+
+ if ( sigterm() ) {
+ warn "received TERM signal; exiting\n";
+ exit;
+ }
+ if ( sigint() ) {
+ warn "received INT signal; exiting\n";
+ exit;
+ }
+
+ sleep 30; #too long? too short?
+
+}
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ $ARGV[$_] =~ /^([\w\-\/\@\.]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-monitor [ -pm ] machine email\n";
+}
+
+=head1 NAME
+
+freeside-monitor - Perform some basic load monitoring
+
+=head1 SYNOPSIS
+
+ freeside-monitor [ -p MAXLOAD ] [ -m REQUIRED_FRERMEM ] machine email [ ... ]
+
+=head1 DESCRIPTION
+
+Load monitoring daemon. Should be running at all times.
+
+-p: maximum permitted 5 minute load
+
+-m: minimum free vmem in kB
+
+machine: a unique name to be used in alert messages
+email: address(es) to which alerts should be sent
+
+=head1 VERSION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/bin/move-customers b/bin/move-customers
new file mode 100755
index 0000000..a7ea197
--- /dev/null
+++ b/bin/move-customers
@@ -0,0 +1,678 @@
+#!/usr/bin/perl -w
+
+#script to move customers from one installation to another
+# source is remote, destination is local
+# script is kinda-specific to a somewhat old source installation (1.7? older?)
+# target installation has to be 1.9 (after 9/2009)
+
+use strict;
+use vars qw( $sdbh );
+use DBI;
+use FS::UID qw( adminsuidsetup dbh );
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearchs );
+use FS::agent;
+use FS::cust_main;
+use FS::part_pkg;
+use FS::part_svc;
+use FS::cust_bill_ApplicationCommon;
+use FS::svc_Common;
+use FS::cust_event;
+use FS::svc_domain;
+use FS::cust_pkg;
+
+my $DANGEROUS = 0;
+my $DRY = 0;
+
+#ssh -p 2222 -L 1080:66.209.32.4:7219 -L 5454:localhost:5432 66.209.32.4
+
+#my $source_datasrc = 'DBI:Pg:host=66.209.32.4;dbname=freeside;sslmode=require';
+my $source_datasrc = 'DBI:Pg:host=localhost;port=5454;dbname=freeside';
+my $source_user = 'readonly';
+my $source_pw = '';
+
+#my @source_agents = ( 2, 7, 3, 4, 5, 1 );
+my @source_agents = ( 1, 2, 3, 4, 5, 7 );
+
+my $dest_agent_typenum = 12;
+
+my $dest_refnum = 60;
+
+my $dest_legacy_credit_reasontype = 5;
+
+my $dest_pkg_classnum = 6;
+
+my %domsvc_map = (
+ 1 => 20450,
+ 3653 => 20162,
+ 7634 => 20451,
+);
+
+#testing
+#my %eventparts = (
+# 'CARD' => [ 1, ],
+# 'CHEK' => [],
+# 'BILL' => [],
+# 'DCHK' => [],
+# 'DCRD' => [],
+# 'COMP' => [],
+#);
+#production
+my %eventparts = (
+ 'CARD' => [ 1, ],
+ 'CHEK' => [ 2, ],
+ 'BILL' => [ 5, ],
+ 'DCHK' => [ 12, ],
+ 'DCRD' => [ 15, ],
+ 'COMP' => [],
+);
+
+#--
+
+# target(local) setup
+
+my $user = shift
+ or die "Usage:\n (edit variables at top of script and then)\n".
+ " move-customers user\n";
+adminsuidsetup $user;
+
+$FS::cust_main::ignore_expired_card = 1;
+$FS::cust_main::ignore_expired_card = 1;
+$FS::part_pkg::skip_pkg_svc_hack = 1;
+$FS::part_pkg::skip_pkg_svc_hack = 1;
+$FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
+$FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+$FS::cust_pkg::disable_agentcheck = 1;
+$FS::cust_pkg::disable_agentcheck = 1;
+
+my $void_paynum = 2147483646; #top of int range
+
+# --
+
+# source(remote) setup
+
+$sdbh = DBI->connect($source_datasrc, $source_user, $source_pw)
+ or die $DBI::errstr;
+
+$sdbh->{ChopBlanks} = 1;
+
+# --
+
+my %map = ();
+$map{'_DOMSVC'} = \%domsvc_map;
+
+import_table('pkg_class', 'nomap' => 1);
+import_table('svc_acct_pop', 'nomap' => 1);
+
+#XXX
+#import_table('reason_type', 'nomap' => 1);
+#foreach my $src_typenum ( keys %{ $map{'reason_type'} } ) {
+# import_table('reason', 'reason_type' => $src_typenum,
+# 'search' => 'reason_type',
+# 'map' => 'reason_type',
+# );
+#}
+
+my $agent_sth = $sdbh->prepare(
+ 'SELECT * FROM agent WHERE agentnum IN ( '. join(',', @source_agents ). ')'
+) or die $sdbh->errstr;
+
+$agent_sth->execute or die $agent_sth->errstr;
+
+
+while ( my $agentrow = $agent_sth->fetchrow_hashref ) {
+
+ my $src_agent = $agentrow->{'agent'};
+
+ warn "importing customers for $src_agent\n";
+
+ my $agent = qsearchs('agent', { 'agent' => $src_agent, 'disabled' => '' } );
+
+ if ( $agent ) {
+
+ warn " using existing agentnum ". $agent->agentnum. "\n";
+
+ if ( $DANGEROUS ) {
+ warn "DELETING ALL CUSTOMERS OF $src_agent locally \n";
+
+ foreach my $statement (
+ 'DELETE FROM cust_main WHERE agentnum = '. $agent->agentnum,
+ ( map { "DELETE FROM $_
+ WHERE 0 = ( SELECT COUNT(*) FROM cust_main
+ WHERE cust_main.custnum = $_.custnum )
+ "
+ }
+ qw(
+ cust_credit
+ cust_main_invoice
+ cust_main_note
+ cust_pay
+ cust_refund
+ )
+ )
+ #pkg_class, part_pkg_pop
+ #part_pkg, pkg_svc, part_svc, part_svc_column
+ #XXX more... does it matter?
+ ) {
+
+ #warn $statement;
+ my $sth = dbh->prepare($statement) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ }
+
+ dbh->commit or die dbh->errstr;
+
+ }
+
+ } else {
+
+ warn " creating new agent...\n";
+
+ $agent = new FS::agent { 'agent' => $src_agent,
+ 'typenum' => $dest_agent_typenum };
+ my $error = $agent->insert;
+ die $error if $error;
+
+ warn " agentnum ". $agent->agentnum. "\n";
+
+ }
+
+ $map{'agent'}->{ $agentrow->{'agentnum'} } = $agent->agentnum;
+
+}
+
+ #my $customer_sth = $sdbh->prepare(
+ # 'SELECT * FROM cust_main WHERE agentnum = '. $agentrow->{'agentnum'}
+ #) or die $sdbh->errstr;
+my $customer_sth = $sdbh->prepare(
+ 'SELECT * FROM cust_main WHERE agentnum IN ( '. join(',', @source_agents ). ')
+ ORDER BY custnum'
+) or die $sdbh->errstr;
+
+$customer_sth->execute or die $customer_sth->errstr;
+
+while ( my $customerrow = $customer_sth->fetchrow_hashref ) {
+
+ #use Data::Dumper;
+ # warn Dumper($customerrow);
+ my $src_custnum = $customerrow->{'custnum'};
+
+ warn " $src_custnum has referral_custnum ". $customerrow->{'referral_custnum'}
+ if $customerrow->{'referral_custnum'};
+
+ my $cust_main = new FS::cust_main {
+ %{ $customerrow },
+ 'custnum' => '',
+ 'referral_custnum' => '', #restore afterwords?
+ 'refnum' => $dest_refnum,
+ 'agentnum' => $map{'agent'}->{ $customerrow->{'agentnum'} },
+ 'agent_custid' => $src_custnum,
+ };
+
+ #$cust_main->ship_country('') if $cust_main->ship_country eq ' ';
+ #$cust_main->tax('') if $cust_main->tax =~ /^\s+$/;
+
+ my $error = $cust_main->insert;
+ if ( $error ) {
+ warn "*** WARNING: error importing customer src custnum $src_custnum: $error";
+ use Data::Dumper;
+ warn Dumper($cust_main) if $src_custnum == 6854;
+ next;
+ }
+
+ warn "inserting dest customer ". $cust_main->custnum. " for $src_custnum\n";
+
+ $map{'cust_main'}->{$src_custnum} = $cust_main->custnum;
+
+ #now import the relations, easy and hard:
+
+ import_table( 'cust_main_note', 'custnum' => $src_custnum );
+
+ import_table( 'cust_pay', 'custnum' => $src_custnum,
+ #ivan showing up as cust_pay otaker
+ # old db doesn't have cust_pay.otaker, pull it from history
+ 'preinsert_callback' => sub {
+ my($row, $cust_pay) = @_;
+
+ my $sth = $sdbh->prepare(
+ "SELECT history_user FROM h_cust_pay WHERE history_action = 'insert'
+ AND paynum = ". $row->{'paynum'}
+ ) or die $sdbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $otaker = $sth->fetchrow_arrayref->[0];
+
+ $cust_pay->otaker($otaker);
+ },
+ );
+
+ # crap, cust_credit.reason is text in old db
+#*** WARNING: error importing cust_credit src crednum 2200: failed to set reason for [ FS::cust_credit ]: at ./move-customers line 232.
+ import_table( 'cust_credit', 'custnum' => $src_custnum,
+ 'insert_opts' => [ 'reason_type' => $dest_legacy_credit_reasontype ],
+ 'preinsert_callback' => sub {
+ my($row, $object) = @_;
+ $object->set('reason', '(none)') if $object->get('reason') =~ /^\s*$/;
+ },
+ );
+
+ import_table( 'cust_refund', 'custnum' => $src_custnum,
+ 'post_callback' => sub {
+ #my( $src_refundnum, $dst_refundnum ) = @_;
+ my $src_refundnum = shift;
+
+ # cust_credit_refund (map refundnum and crednum...)
+ import_table( 'cust_credit_refund',
+ 'refundnum' => $src_refundnum,
+ 'search' => 'refundnum',
+ 'map' => 'cust_refund',
+ 'map2' => 'cust_credit',
+ 'map2key' => 'crednum',
+ );
+
+ # cust_pay_refund (map refundnum and paynum...)
+ import_table( 'cust_pay_refund',
+ 'refundnum' => $src_refundnum,
+ 'search' => 'refundnum',
+ 'map' => 'cust_refund',
+ 'map2' => 'cust_pay',
+ 'map2key' => 'paynum',
+ );
+
+ },
+ );
+
+ # dunno what's up with this (ship_country ' ', fixed)
+#*** WARNING: error importing customer src custnum 6854: Illegal (name) (error code illegal_name) ship_last: at ./move-customers line 129.
+
+ # cust_pay_void
+ import_table( 'cust_pay_void', 'custnum' => $src_custnum,
+ 'preinsert_callback' => sub {
+ my($row, $object) = @_;
+ $object->paynum( $void_paynum-- );
+ },
+ );
+
+ # (not in old db: cust_attachment, cust_statement, cust_location,
+ # cust_main_exemption, cust_pay_pending )
+ # (not used in old db: cust_pay_batch, cust_tax_exempt)
+ # (not useful to migrate: queue)
+
+ #werid direct cust_main relations:
+
+ # cust_pkg (part_pkg, part_svc, etc.)
+ import_table( 'cust_pkg', 'custnum' => $src_custnum,
+ 'preinsert_callback' => sub {
+ my($row, $object) = @_;
+ my $src_pkgpart = $row->{'pkgpart'} or die "wtf";
+ my $dest_pkgpart = $map{'part_pkg'}->{$src_pkgpart};
+ if ( $dest_pkgpart ) {
+ $object->pkgpart($dest_pkgpart);
+ return;
+ }
+
+ my $sth = $sdbh->prepare(
+ "SELECT * FROM part_pkg WHERE pkgpart = $src_pkgpart"
+ ) or die $sdbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+ my $part_pkg_row = $sth->fetchrow_hashref
+ or die "cust_pkg.pkgpart missing in part_pkg?!";
+
+ my $hashref = {
+ %{ $part_pkg_row },
+ 'pkgpart' => '',
+ };
+ my $src_classnum = $part_pkg_row->{'classnum'};
+ $hashref->{'classnum'} = $map{'pkg_class'}->{ $src_classnum }
+ if $src_classnum;
+
+ my $part_pkg = new FS::part_pkg $hashref;
+
+ #$part_pkg->setuptax('') if $part_pkg->setuptax =~ /^\s+$/;
+ #$part_pkg->recurtax('') if $part_pkg->recurtax =~ /^\s+$/;
+
+ my $error = $part_pkg->insert( 'options' => {} );
+ die "*** FATAL: error importing part_pkg src pkgpart $src_pkgpart ".
+ ": $error"
+ if $error;
+
+ $map{ 'part_pkg' }->{ $part_pkg_row->{'pkgpart'} } = $part_pkg->pkgpart;
+
+ # part_pkg_option
+ import_table( 'part_pkg_option',
+ 'pkgpart' => $src_pkgpart,
+ 'search' => 'pkgpart',
+ 'map' => 'part_pkg',
+ );
+
+ my $osth = $sdbh->prepare(
+ "SELECT * FROM part_pkg_option WHERE pkgpart = $src_pkgpart"
+ ) or die $sdbh->errstr;
+
+ # pkg_svc, part_svc, part_svc_column
+ import_table( 'pkg_svc',
+ 'pkgpart' => $src_pkgpart,
+ 'search' => 'pkgpart',
+ 'map' => 'part_pkg',
+ 'preinsert_callback' => sub {
+
+ my($row, $object) = @_;
+ my $src_svcpart = $row->{'svcpart'} or die "wtf2";
+ my $dest_svcpart = $map{'part_svc'}->{$src_svcpart};
+ if ( $dest_svcpart ) {
+ $object->svcpart($dest_svcpart);
+ return;
+ }
+
+ my $sth = $sdbh->prepare(
+ "SELECT * FROM part_svc WHERE svcpart = $src_svcpart"
+ ) or die $sdbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+
+ my $part_svc_row = $sth->fetchrow_hashref
+ or die "svcpart missing in part_svc?!";
+
+ my $hashref = {
+ %{ $part_svc_row },
+ 'svcpart' => '',
+ };
+
+ my $part_svc = new FS::part_svc $hashref;
+ $part_svc->disabled('') if $part_svc->disabled =~ /^\s+$/;
+ my $error = $part_svc->insert;
+ die "*** FATAL: error importing part_svc src svcpart $src_svcpart ".
+ ": $error"
+ if $error;
+
+ $map{ 'part_svc' }->{ $part_svc_row->{'svcpart'} } = $part_svc->svcpart;
+
+ # part_svc_column
+ import_table( 'part_svc_column',
+ 'svcpart' => $src_svcpart,
+ 'search' => 'svcpart',
+ 'map' => 'part_svc',
+ 'preinsert_callback' => sub {
+ my($row, $object) = @_;
+ if ( $object->columnname eq 'domsvc' ) {
+ $object->columnvalue( $map{'_DOMSVC'}->{ $object->columnvalue } );
+ }
+ },
+ );
+
+ #what we came here for in the first place
+ $object->svcpart( $part_svc->svcpart );
+
+ }
+ );
+
+ #what we came here for in the first place
+ $object->pkgpart( $part_pkg->pkgpart );
+
+ },
+
+ 'post_callback' => sub {
+ #my( $src_pkgnum, $dst_pkgnum ) = @_;
+ my $src_pkgnum = shift;
+
+ #XXX grr... action makes this very hard...
+ ## cust_pkg_reason (shit, and bring in/remap reasons)
+ #import_table( 'cust_pkg_reason',
+ # 'pkgnum' => $src_pkgnum,
+ # 'search' => 'pkgnum',
+ # 'map' => 'cust_pkg',
+ # 'map2' => 'reason',
+ # 'map2key' => 'reasonnum',
+ # );
+
+ #cust_svc
+ import_table( 'cust_svc',
+ 'pkgnum' => $src_pkgnum,
+ 'search' => 'pkgnum',
+ 'map' => 'cust_pkg',
+ 'map2' => 'part_svc',
+ 'map2key' => 'svcpart',
+ 'post_callback' => sub {
+ #my( $src_svcnum, $dst_svcnum ) = @_;
+ my $src_svcnum = shift;
+
+ #svc_domain
+ import_table( 'svc_domain',
+ 'svcnum' => $src_svcnum,
+ 'search' => 'svcnum',
+ 'map' => 'cust_svc',
+ 'noblank_primary' => 1,
+ );
+
+ #svc_acct
+ import_table( 'svc_acct',
+ 'svcnum' => $src_svcnum,
+ 'search' => 'svcnum',
+ 'map' => 'cust_svc',
+ 'noblank_primary' => 1,
+ 'map2' => 'svc_acct_pop',
+ 'map2key' => 'popnum',
+ #'map3' => 'svc_domain',
+ 'map3' => '_DOMSVC',
+ 'map3key' => 'domsvc',
+ );
+
+ #radius_usergroup
+ import_table( 'radius_usergroup',
+ 'svcnum' => $src_svcnum,
+ 'search' => 'svcnum',
+ 'map' => 'cust_svc',
+ );
+
+ #other svc_ tables not in old db
+
+ },
+ );
+
+ },
+
+
+
+
+ );
+ # end of cust_pkg (part_pkg, part_svc, etc.)
+
+ # cust_bill (invnum move)
+ import_table( 'cust_bill', 'custnum' => $src_custnum,
+ 'preinsert_callback' => sub {
+ my($row, $object) = @_;
+ $object->agent_invid( $row->{'invnum'} );
+ },
+ 'post_callback' => sub {
+ my( $src_invnum, $dst_invnum ) = @_;
+ #my $src_invnum = shift;
+
+ # cust_bill_pkg ( map invnum and pkgnum... )
+ import_table( 'cust_bill_pkg',
+ 'invnum' => $src_invnum,
+ 'search' => 'invnum',
+ 'map' => 'cust_bill',
+ 'map2' => 'cust_pkg',
+ 'map2key' => 'pkgnum',
+ 'post_callback' => sub {
+ my $src_billpkgnum = shift;
+
+ import_table( 'cust_bill_pkg_detail',
+ 'billpkgnum' => $src_billpkgnum,
+ 'search' => 'billpkgnum',
+ 'map' => 'cust_bill_pkg',
+ 'addl_from' => 'left join cust_bill_pkg using ( invnum, pkgnum )',
+ );
+
+ },
+ );
+
+ # cust_credit_bill (map invnum and crednum... )
+ import_table( 'cust_credit_bill',
+ 'invnum' => $src_invnum,
+ 'search' => 'invnum',
+ 'map' => 'cust_bill',
+ 'map2' => 'cust_credit',
+ 'map2key' => 'crednum',
+ 'post_callback' => sub {
+ my $src_creditbillnum = shift;
+ #map creditbillnum and billpkgnum
+ import_table( 'cust_credit_bill_pkg',
+ 'creditbillnum' => $src_creditbillnum,
+ 'search' => 'creditbillnum',
+ 'map' => 'cust_credit_bill',
+ 'map2' => 'cust_bill_pkg',
+ 'map2key' => 'billpkgnum',
+ );
+
+ },
+ );
+
+ # cust_bill_pay (map invnum and paynum...)
+ import_table( 'cust_bill_pay',
+ 'invnum' => $src_invnum,
+ 'search' => 'invnum',
+ 'map' => 'cust_bill',
+ 'map2' => 'cust_pay',
+ 'map2key' => 'paynum',
+ 'post_callback' => sub {
+ my $src_billpaynum = shift;
+ #map billpaynum and billpkgnum
+ import_table( 'cust_bill_pay_pkg',
+ 'billpaynum' => $src_billpaynum,
+ 'search' => 'billpaynum',
+ 'map' => 'cust_bill_pay',
+ 'map2' => 'cust_bill_pkg',
+ 'map2key' => 'billpkgnum',
+ );
+ },
+ );
+
+ #need to do something about events. mark initial stuff as done
+ foreach my $eventpart ( @{ $eventparts{$cust_main->payby} } ) {
+
+ my $cust_event = new FS::cust_event {
+ 'eventpart' => $eventpart,
+ 'tablenum' => $dst_invnum,
+ '_date' => time, # XXX something? probably not
+ 'status' => 'done',
+ };
+
+ my $error = $cust_event->insert;
+ die "*** FATAL: error inserting cust_event for eventpart $eventpart,".
+ " tablenum (invnum) $dst_invnum: $error"
+ if $error;
+
+ }
+
+ },
+ );
+
+ # ---
+
+ # (not in old db: cust_pkg_detail)
+ # (not used in old db: cust_bill_pay_batch, cust_pkg_option)
+
+ # ---
+
+ # (not in old db: cust_bill_pkg_display, cust_bill_pkg_tax_location,
+ # cust_bill_pkg_tax_rate_location, cust_tax_adjustment, cust_svc_option, )
+ # (not used in old db: cust_tax_exempt_pkg)
+
+ #do this last, so no notices go out
+ import_table( 'cust_main_invoice', 'custnum' => $src_custnum );
+
+ #dbh->commit or die dbh->errstr;
+ warn "customer ". $cust_main->custnum. " inserted\n";
+ #exit;
+
+}
+
+
+warn "import successful!\n";
+if ( $DRY ) {
+ warn "rolling back (dry run)\n";
+ dbh->rollback or die dbh->errstr;
+ warn "rolled back\n"
+} else {
+ warn "commiting\n";
+ dbh->commit or die dbh->errstr;
+ warn "committed\n";
+}
+
+sub import_table {
+ my( $table, %opt ) = @_;
+
+ eval "use FS::$table;";
+ die $@ if $@;
+
+ my $map = $opt{'map'} || 'cust_main';
+ my $search = $opt{'search'} || 'custnum';
+
+ $opt{'insert_opts'} ||= [];
+
+ my $primary_key = dbdef->table($table)->primary_key;
+
+ my $addl_from = defined($opt{'addl_from'}) ? $opt{'addl_from'} : '';
+
+ my $sth = $sdbh->prepare(
+ "SELECT * FROM $table $addl_from ".
+ ( $opt{'nomap'} ? '' : " WHERE $search = ". $opt{$search} )
+ ) or die $sdbh->errstr;
+
+ $sth->execute or die "(searching $table): ". $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_hashref ) {
+ #my $src_custnum = $customerrow->{'custnum'};
+
+ my $hashref = { %$row };
+ $hashref->{$primary_key} = ''
+ unless $opt{'noblank_primary'};
+ $hashref->{ $search } = $map{$map}->{ $row->{$search} }
+ unless $opt{'nomap'};
+
+ if ( $opt{'map2'} ) {
+ my $key2 = $opt{'map2key'};
+ $hashref->{$key2} = $map{ $opt{'map2'} }->{ $row->{$key2} }
+ unless $opt{map2key} eq 'pkgnum' && ( $row->{$key2} eq '0'
+ || $row->{$key2} eq '-1'
+ )
+ or ! defined($row->{$key2})
+ or $row->{$key2} eq '';
+ #warn "map $opt{map2}.$opt{map2key}: ". $row->{$key2}. " to ". $map{ $opt{'map2'} }->{ $row->{$key2} };
+ }
+
+ if ( $opt{'map3'} ) {
+ my $key3 = $opt{'map3key'};
+ $hashref->{$key3} = $map{ $opt{'map3'} }->{ $row->{$key3} };
+ }
+
+ my $object = eval "new FS::$table \$hashref;";
+ die $@ if $@;
+
+ &{ $opt{preinsert_callback} }( $row, $object )
+ if $opt{preinsert_callback};
+
+ my $error = $object->insert( @{ $opt{'insert_opts'} } );
+ if ( $error ) {
+ warn "*** WARNING: error importing $table src $primary_key ". $row->{$primary_key}. ": $error";
+ next;
+ }
+
+ $map{ $table }->{ $row->{$primary_key} } = $object->get($primary_key);
+
+ &{ $opt{post_callback} }( $row->{$primary_key}, $object->get($primary_key) )
+ if $opt{post_callback};
+
+ }
+
+}
+
+1;
+
diff --git a/bin/move-unlinked b/bin/move-unlinked
new file mode 100755
index 0000000..0d31a49
--- /dev/null
+++ b/bin/move-unlinked
@@ -0,0 +1,99 @@
+#!/usr/bin/perl -w
+
+#script to move unlinked accounts from one installation to another
+# source is remote, destination is local
+
+use strict;
+use vars qw( $sdbh );
+use DBI;
+use FS::UID qw( adminsuidsetup dbh );
+use FS::Schema qw( dbdef );
+use DBI;
+use FS::Record qw( qsearchs );
+use FS::svc_acct;
+
+#my $DANGEROUS = 0;
+#my $DRY = 0;
+
+#ssh -p 2222 -L 1080:66.209.32.4:7219 -L 5454:localhost:5432 66.209.32.4
+
+#my $source_datasrc = 'DBI:Pg:host=66.209.32.4;dbname=freeside;sslmode=require';
+my $source_datasrc = 'DBI:Pg:host=localhost;port=5454;dbname=freeside';
+my $source_user = 'readonly';
+my $source_pw = '';
+
+
+my %domsvc_map = (
+ 1 => 108, #nothinbut.net
+ 3653 => 109, #ewol.com
+ #7634 => 20451,
+);
+#my %domsvc_map = (
+# 1 => 20450,
+# 3653 => 20162,
+## 7634 => 20451,
+#);
+
+my %svcpart_map = (
+ 2 => 23, # NBN-DIALUP
+ 3 => 29, # NBN-EMAIL
+ 8 => 30, # EWOL-EMAIL
+);
+#my %svcpart_map = (
+# 2 => , # NBN-DIALUP
+# 3 => , # NBN-EMAIL
+# 8 => , # EWOL-EMAIL
+#);
+
+
+#--
+
+# target(local) setup
+
+my $user = shift
+ or die "Usage:\n (edit variables at top of script and then)\n".
+ " move-customers user\n";
+adminsuidsetup $user;
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_Common::noexport_hack = 1;
+
+# --
+
+# source(remote) setup
+
+$sdbh = DBI->connect($source_datasrc, $source_user, $source_pw)
+ or die $DBI::errstr;
+
+$sdbh->{ChopBlanks} = 1;
+
+# --
+
+my $sth = $sdbh->prepare(
+ 'select * from svc_acct left join cust_svc using ( svcnum ) where pkgnum is null'
+) or die $sdbh->errstr;
+
+$sth->execute or die $sth->errstr;
+
+while ( my $hashref = $sth->fetchrow_hashref ) {
+
+ my %hash = %$hashref;
+
+ $hash{'svcnum'} = '';
+
+ $hash{'domsvc'} = $domsvc_map{ $hash{'domsvc'}};
+ $hash{'svcpart'} = $svcpart_map{$hash{'svcpart'}};
+
+ my $svc_acct = new FS::svc_acct \%hash;
+
+ #my $error = $svc_acct->check;
+ my $error = $svc_acct->insert;
+
+ if ( $error ) {
+ use Data::Dumper;
+ warn Dumper($svc_acct);
+ die $error;
+ }
+}
+
+1;
diff --git a/bin/opensrs_domain_pkgs b/bin/opensrs_domain_pkgs
new file mode 100755
index 0000000..ae14761
--- /dev/null
+++ b/bin/opensrs_domain_pkgs
@@ -0,0 +1,126 @@
+#!/usr/bin/perl -w
+
+use strict;
+use DateTime;
+use Date::Format;
+use Date::Parse;
+use Net::OpenSRS;
+use Net::Whois::Raw;
+use Data::Dumper;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs qsearch);
+use FS::Conf;
+use FS::svc_domain;
+use FS::part_pkg;
+use FS::part_export;
+
+my $exportnum = 1;
+my $pkgpart = 631;
+my $user = 'qis';
+
+adminsuidsetup $user;
+
+my $part_export = qsearchs('part_export' => { exportnum => $exportnum })
+ or die "can't find export $exportnum\n";
+
+my $srs = $part_export->get_srs;
+
+my $rv = $srs->make_request(
+ {
+ action => 'get_domains_by_expiredate',
+ object => 'domain',
+ attributes => {
+ exp_from => time2str('%Y-%m-%d', time() - 4*24*60*60),
+ exp_to => time2str('%Y-%m-%d', time() + 10*366*24*60*60),
+ limit => 10000,
+ }
+ }
+);
+
+die $rv->{response_text} unless $rv->{is_success};
+
+my %domains = map { $_->{name}, $_ } @{ $rv->{attributes}->{exp_domains} };
+
+# each is of form
+# {
+# 'f_let_expire' => 'N',
+# 'name' => 'wolfecpa.com',
+# 'f_auto_renew' => 'N',
+# 'expiredate' => '2017-09-16 04:00:00'
+# },
+
+foreach my $svc_domain ( $part_export->svc_x ) {
+ unless ( exists($domains{$svc_domain->domain}) ) {
+ warn $svc_domain->domain. " not at registrar. No action taken.\n";
+ next;
+ }
+
+ $domains{$svc_domain->domain}{seen} = 1;
+
+ unless ( $domains{$svc_domain->domain}{expiredate} =~
+ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/ )
+ {
+ warn "Can't parse expiration date for ". $svc_domain->domain. " skipping\n";
+ next;
+ }
+
+ my ($year,$month,$day,$hour,$minute,$second) = ($1,$2,$3,$4,$5,$6);
+ my $exp = DateTime->new( year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $minute,
+ second => $second,
+ time_zone => 'America/New_York',#timezone of opensrs
+ );
+ my $expiretime = $exp->epoch;
+
+ my $error = $part_export->is_supported_domain($svc_domain);
+ warn $error if $error;
+ $error = undef;
+
+ my $create = '';
+ my $whois = whois($svc_domain->domain);
+ $whois =~ /Record created on (\d{1,2}-\w{3}-\d{4})\./ && ($create = $1);
+ my $createtime = str2time($create);
+
+ unless ($createtime) {
+ $exp->subtract( 'years' => 1 );
+ $createtime = $exp->epoch;
+ }
+
+ my $new;
+ my $cust_svc = $svc_domain->cust_svc;
+ my $cust_pkg = $cust_svc->cust_pkg;
+ unless ($cust_pkg) {
+ warn $svc_domain->domain. " not linked to package. No action taken.\n";
+ next;
+ }
+
+ foreach my $pkg ( grep { $_->pkgpart == $pkgpart } $cust_pkg->cust_main->ncancelled_pkgs ) {
+ next if $pkg->cust_svc; # only handles simple 1 domain/package case
+ $cust_svc->pkgnum($pkg->pkgnum);
+ $error = $cust_svc->replace;
+ die "error linking to empty package: $error\n" if $error;
+ $cust_pkg = $pkg;
+ last;
+ }
+
+ unless ($cust_pkg->pkgpart == $pkgpart) {
+ $new = new FS::cust_pkg
+ { custnum => $cust_pkg->custnum, pkgpart => $pkgpart };
+ my $error = $new->insert;
+ die "error inserting package: $error\n" if $error;
+ $cust_svc->pkgnum($new->pkgnum);
+ $error = $cust_svc->replace;
+ die "error linking to new package: $error\n" if $error;
+ $cust_pkg = $new;
+ }
+
+ # set dates on package if it was empty?
+ $cust_pkg->bill($expiretime);
+ $cust_pkg->setup($createtime);
+ $error = $cust_pkg->replace;
+ die $error if $error;
+}
+
diff --git a/bin/passwd.import b/bin/passwd.import
new file mode 100755
index 0000000..8ab9e2a
--- /dev/null
+++ b/bin/passwd.import
@@ -0,0 +1,121 @@
+#!/usr/bin/perl -Tw
+
+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 =~ s/^NP$/\*/;
+ $password =~ s/^\*LK\*$/\*/;
+ $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/payment-faker b/bin/payment-faker
new file mode 100755
index 0000000..03316e1
--- /dev/null
+++ b/bin/payment-faker
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+use Date::Parse;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_pay;
+use FS::cust_credit;
+
+my $user;
+$user = shift or die "usage: payment-faker $user";
+adminsuidsetup($user);
+
+for $month ( 1 .. 11 ) {
+
+ print "month $month\n";
+
+ system(qq!freeside-daily -d "$month/1/2006" $user!);
+
+ foreach my $cust_main ( qsearch('cust_main', {} ) ) {
+ next unless $cust_main->balance > 0;
+ my $item = '';
+ if ( rand() > .95 ) {
+ $item = new FS::cust_credit {
+ 'amount' => $cust_main->balance,
+ '_date' => str2time("$month/1/2006"),
+ 'reason' => 'testing',
+ };
+ } else {
+
+ if ( rand() > .5 ) {
+ $payby = 'BILL';
+ $payinfo = int(rand(10000));
+ } else {
+ $payby = 'CARD';
+ $payinfo = '4111111111111111';
+ }
+
+ $item = new FS::cust_pay {
+ 'paid' => $cust_main->balance,
+ '_date' => str2time("$month/1/2006"),
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ };
+ }
+
+ $item->custnum($cust_main->custnum);
+ my $error = $item->insert;
+ die $error if $error;
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ }
+
+}
diff --git a/bin/pg-readonly b/bin/pg-readonly
new file mode 100644
index 0000000..ad69fbd
--- /dev/null
+++ b/bin/pg-readonly
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+#
+# hack to update/add read-only permissions for a user on the db
+#
+# usage: pg-readonly freesideuser readonlyuser
+
+use strict;
+use DBI;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(dbdef);
+
+my $user = shift or die &usage;
+my $rouser = shift or die &usage;
+
+my $dbh = adminsuidsetup $user;
+
+foreach my $table ( dbdef->tables ) {
+ $dbh->do("GRANT SELECT ON $table TO $rouser");
+ $dbh->commit();
+ if ( my $pkey = dbdef->table($table)->primary_key ) {
+ $dbh->do("GRANT SELECT ON ${table}_${pkey}_seq TO $rouser");
+ $dbh->commit();
+ }
+}
diff --git a/bin/pg-sizer b/bin/pg-sizer
new file mode 100755
index 0000000..3af0286
--- /dev/null
+++ b/bin/pg-sizer
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Schema qw(dbdef);
+use FS::Record; #why is this necessary
+
+adminsuidsetup shift or die "usage: pg-sizer user";
+
+my $verbose = 1;
+
+my %size = ();
+my %prettysize = ();
+
+foreach my $table ( dbdef->tables ) {
+ warn "sizing $table...\n" if $verbose;
+ my $sth = dbh->prepare("SELECT pg_total_relation_size('$table')")
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $size = $sth->fetchrow_arrayref->[0];
+ $size{$table} = $size;
+
+ my $psth = dbh->prepare("SELECT pg_size_pretty( $size )")
+ or die dbh->errstr;
+ $psth->execute or die $psth->errstr;
+ my $prettysize = $psth->fetchrow_arrayref->[0];
+ $prettysize{$table} = $prettysize;
+
+ warn "$table: $prettysize{$table}\n" if $verbose;
+}
+
+foreach my $table ( reverse sort { $size{$a} <=> $size{$b} } keys %size ) {
+ #print "$table: $size{$table}\n";
+ print "$table: $prettysize{$table}\n";
+}
+
diff --git a/bin/pg-version b/bin/pg-version
new file mode 100755
index 0000000..b6cddb6
--- /dev/null
+++ b/bin/pg-version
@@ -0,0 +1,13 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+
+my $user = shift or die &usage;
+adminsuidsetup($user);
+
+print "pg_server_version: ". dbh->{'pg_server_version'}. "\n";
+
+sub usage {
+ "\n\nUsage: pg-version username\n";
+};
diff --git a/bin/ping b/bin/ping
new file mode 100755
index 0000000..605a204
--- /dev/null
+++ b/bin/ping
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use Net::Ping;
+use Net::SSH qw( ssh_cmd );
+use Email::Send;
+
+my @other_hosts = ( 'freeside.biz', 'saturn5.com' );
+
+my( $machine, @emails ) = @ARGV;
+die "no notification email given" unless @emails;
+
+my $ping = new Net::Ping; # 'icmp'; #requires root
+
+my $pong = '';
+# can't tcp ping... $ping->ping($machine) and
+$pong = eval { ssh_cmd('freeside@'.$machine, 'echo pong') };
+#(command ignored if authorized_keys setup w/command=)
+
+if ( $@ || $pong !~ /pong/ ) { #houston, we may have a problem
+
+ #warn "can't reach $machine, checking @other_hosts\n";
+
+ #let's do a sanity check, can we see some other hosts?
+ exit unless grep $ping->ping($_), @other_hosts;
+
+ #uh-oh, this is bad.
+
+ #warn "checking to see if we've alerted on this recently\n";
+
+ #but we don't want to be too noisy, have we alerted on this in the last 24h?
+ my $file = "/tmp/alert-$machine";
+ exit if -e $file && -M $file < 1;
+
+ open(FILE, ">>$file");
+ print FILE "emailing\n";
+ close FILE;
+
+ #warn "emailing alerts\n";
+
+ foreach my $email ( @emails ) {
+
+ my $message = <<"__MESSAGE__";
+From: support\@freeside.biz
+To: $email
+Subject: ALERT - $machine
+
+ALERT: $machine appears to be down.
+
+__MESSAGE__
+
+ my $sender = Email::Send->new({mailer => 'SMTP'});
+ $sender->mailer_args([Host => 'mail.freeside.biz']);
+ $sender->send($message);
+
+ }
+
+}
+
diff --git a/bin/pod2x b/bin/pod2x
new file mode 100755
index 0000000..ecb7f91
--- /dev/null
+++ b/bin/pod2x
@@ -0,0 +1,145 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my $mw_username = 'ivan';
+chomp( my $mw_password = `cat .mw-password` );
+
+my $site_perl = "./FS";
+#my $html = "Freeside:1.7:Documentation:Developer";
+my $html = "Freeside:1.9:Documentation:Developer";
+
+foreach my $dir (
+ $html,
+ map "$html/$_", qw( bin FS FS/UI FS/part_export FS/part_pkg
+ FS/part_event FS/part_event/Condition FS/part_event/Action
+ FS/ClientAPI FS/Cron FS/Misc FS/Report FS/Report/Table
+ FS/TicketSystem FS/UI
+ FS/SelfService
+ )
+) {
+ -d $dir or mkdir $dir;
+}
+
+$|=1;
+
+die "Can't find $site_perl" unless -d $site_perl;
+#die "Can't find $catman" unless -d $catman;
+-d $html or mkdir $html;
+
+my $count = 0;
+
+#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");
+ -e "$file.pod" or system("cp $file $file.pod");
+}
+
+#just for filename_to_pagename for now
+use WWW::Mediawiki::Client;
+my $mvs = WWW::Mediawiki::Client->new(
+ 'host' => 'www.freeside.biz',
+ 'wiki_path' => 'mediawiki/index.php',
+ 'username' => $mw_username,
+ 'password' => $mw_password,
+ #'commit_message' => 'import from POD'
+ );
+#$mvs->do_login;
+
+use MediaWiki;
+
+my $c = MediaWiki->new;
+# $is_ok = $c->setup("config.ini");
+$c->setup({
+ 'bot' => { 'user' => $mw_username, 'pass' => $mw_password },
+ 'wiki' => {
+ 'host' => 'www.freeside.biz',
+ 'path' => 'mediawiki',
+ #'has_query' => 1,
+
+ }
+}) or die "Mediawiki->setup failed";
+
+my @files;
+if ( @ARGV ) {
+ @files = @ARGV;
+} else {
+ @files = (
+ glob("$site_perl/*.pm"),
+ glob("$site_perl/*/*.pm"),
+ glob("$site_perl/*/*/*.pm"),
+ glob("$site_perl/*/*/*/*.pm"),
+ glob("$site_perl/bin/*.pod"),
+ glob("./fs_selfservice/FS-SelfService/*.pm"),
+ glob("./fs_selfservice/FS-SelfService/*/*.pm"),
+ );
+
+}
+
+foreach my $file (@files) {
+ next if $file =~ /(^|\/)blib\//;
+ next if $file =~ /(^|\/)CVS\//;
+ #$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";
+ }
+
+ #exit if $count++ == 10;
+
+ my $htmlroot = join('/', map '..',1..(scalar($file =~ tr/\///)-2)) || '.';
+
+ system "pod2wiki --style mediawiki $file >$html/$name.rawwiki";
+
+ if ( -e "$html/$name.rawwiki" ) {
+ print "processing $name\n";
+ } else {
+ print "skipping $name\n";
+ next;
+ };
+
+# $mvs->do_update("$html/$name.wiki");
+
+
+ my $text = '';
+ open(RAW, "<$html/$name.rawwiki") or die $!;
+ while (<RAW>) {
+ s/\[\[([^#p][^\]]*)\]\]/"[[$html\/". w_e($1). "|$1]]"/ge;
+ $text .= $_;
+ }
+ close RAW;
+
+ my $pagename = $mvs->filename_to_pagename("$html/$name.wiki");
+ #print " uploading to $pagename\n";
+
+ $c->text( $pagename, $text );
+
+}
+
+sub w_e {
+ my $s = shift;
+ $s =~ s/_/ /g;
+ $s =~ s/::/\//g;
+ $s =~ s/^freeside-/bin\/freeside-/g;
+ $s;
+}
+
+
+## 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/postfix.export b/bin/postfix.export
new file mode 100755
index 0000000..61380da
--- /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/print-directory_assist b/bin/print-directory_assist
new file mode 100755
index 0000000..4c5e4a8
--- /dev/null
+++ b/bin/print-directory_assist
@@ -0,0 +1,12 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my $acs = `cut -c1-3 ../etc/areacodes.txt`;
+
+my $plus = '';
+foreach my $npa ( split(/\n/, $acs ) ) {
+ warn $npa;
+ $plus .= $npa. '5551212,';
+}
+print "$plus\n";
diff --git a/bin/print-schema b/bin/print-schema
new file mode 100755
index 0000000..886e325
--- /dev/null
+++ b/bin/print-schema
@@ -0,0 +1,7 @@
+#!/usr/bin/perl
+
+use DBIx::DBSchema;
+
+$l = load DBIx::DBSchema "/usr/local/etc/freeside/dbdef.DBI:Pg:dbname=freeside";
+
+print $l->pretty_print, "\n";
diff --git a/bin/rate-us.import b/bin/rate-us.import
new file mode 100755
index 0000000..66ac5de
--- /dev/null
+++ b/bin/rate-us.import
@@ -0,0 +1,109 @@
+#!/usr/bin/perl -w
+
+use strict;
+#use Spreadsheet::ParseExcel;
+use DBI;
+use FS::UID qw(adminsuidsetup);
+use FS::rate_region;
+use FS::rate_prefix;
+use FS::rate_region;
+
+my $ratenum = 1;
+
+my $user = shift or usage();
+adminsuidsetup $user;
+
+sub usage {
+ #die "Usage:\n\n rate.import user rates.xls worksheet_name";
+ die "Usage:\n\n rate.import user";
+}
+
+my %rate_region;
+
+foreach my $file ( 'areas and rates US.xls',
+ 'areas and rates US2.xls',
+ 'areas and rates US3.xls',
+ )
+{
+
+ my $dbh = DBI->connect("DBI:Excel:file=$file")
+ or die "can't connect: $DBI::errstr";
+
+ #my $table = shift or usage();
+ my $table = 'Sheet1';
+ my $sth = $dbh->prepare("select * from $table")
+ or die "can't prepare: ". $dbh->errstr;
+ $sth->execute
+ or die "can't execute: ". $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_hashref ) {
+
+ #print join(' - ', map $row->{$_}, qw( rate_center Code Area_Prefix Rate ) ). "\n";
+
+ my $regionname = $row->{'rate_center'};
+ $regionname =~ s/\xA0//g;
+ #$regionname =~ s/\xE9/e/g; #e with accent aigu
+ $regionname =~ s/(^\s+|\s+$)//;
+ $regionname .= ', USA';
+
+ my $prefix = $row->{'area_prefix'};
+ $prefix =~ s/\xA0//g;
+ $prefix =~ s/\s$//;
+ #my $prefixprefix = '';
+ #if ( $prefix =~ /^\s*(\d+)\s*\((.*)\)\s*$/ ) {
+ # $prefixprefix = $1;
+ # $prefix = $2;
+ #} elsif ( $prefix =~ /^\s*\((\d{3})\)\s*(.*)$/ ) {
+ # $prefixprefix = $1;
+ # $prefix = $2;
+ #}
+
+ my @rate_prefix = map {
+ #warn $row->{'rate_center'}. ": $prefixprefix$_\n";
+ new FS::rate_prefix {
+ 'countrycode' => '1', # $row->{'Country'}
+ #'npa' => $prefixprefix.$_,
+ 'npa' => $_,
+ };
+ }
+ split(/\s*[;,]\s*/, $prefix);
+
+
+ my $dest_detail = new FS::rate_detail {
+ 'ratenum' => $ratenum,
+ 'min_included' => 0,
+ 'min_charge' =>
+ sprintf('%.2f', $row->{'rate'} ),
+ 'sec_granularity' => 60,
+ };
+
+ unless ( exists $rate_region{$regionname} ) {
+
+ my $rate_region = new FS::rate_region {
+ 'regionname' => $regionname,
+ };
+
+ my $error = $rate_region->insert( 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => [ $dest_detail ],
+ );
+ die $error if $error;
+
+ $rate_region{$regionname} = $rate_region->regionnum;
+
+ } else {
+
+ foreach my $rate_prefix ( @rate_prefix ) {
+ $rate_prefix->regionnum($rate_region{$regionname});
+ my $error = $rate_prefix->insert;
+ die $error if $error;
+ }
+
+ #$rate_detail->dest_regionnum($rate_region{$regionname});
+ #$error = $rate_detail->insert;
+ #die $error if $error;
+
+ }
+
+ }
+
+}
diff --git a/bin/rate.delete b/bin/rate.delete
new file mode 100644
index 0000000..7b7e4bc
--- /dev/null
+++ b/bin/rate.delete
@@ -0,0 +1,3 @@
+#delete from rate_detail where ratenum = 18;
+#delete from rate_region where 0 = ( select count(*) from rate_detail where rate_region.regionnum = rate_detail.dest_regionnum );
+#delete from rate_prefix where 0 = ( select count(*) from rate_region where rate_prefix.regionnum = rate_region.regionnum );
diff --git a/bin/rate.import b/bin/rate.import
new file mode 100755
index 0000000..fdd756d
--- /dev/null
+++ b/bin/rate.import
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+
+use strict;
+#use Spreadsheet::ParseExcel;
+use DBI;
+use FS::UID qw(adminsuidsetup);
+use FS::rate_region;
+use FS::rate_prefix;
+use FS::rate_region;
+
+my $ratenum = 1;
+
+my $user = shift or usage();
+adminsuidsetup $user;
+
+#my $file = shift or usage();
+my $file = 'areas and rates.xls';
+my $dbh = DBI->connect("DBI:Excel:file=$file")
+ or die "can't connect: $DBI::errstr";
+
+#my $table = shift or usage();
+my $table = 'areas_and_rates';
+my $sth = $dbh->prepare("select * from $table")
+ or die "can't prepare: ". $dbh->errstr;
+$sth->execute
+ or die "can't execute: ". $sth->errstr;
+
+sub usage {
+ #die "Usage:\n\n rate.import user rates.xls worksheet_name";
+ die "Usage:\n\n rate.import user";
+}
+
+##
+
+while ( my $row = $sth->fetchrow_hashref ) {
+
+ #print join(' - ', map $row->{$_}, qw( Country Code Area_Prefix Rate ) ). "\n";
+
+ my $regionname = $row->{'Country'};
+ $regionname =~ s/\xA0//g;
+ $regionname =~ s/\xE9/e/g; #e with accent aigu
+ $regionname =~ s/(^\s+|\s+$)//;
+
+ #next if $regionname =~ /Sweden Telia Mobile/;
+
+ my $rate_region = new FS::rate_region {
+ 'regionname' => $regionname,
+ };
+
+ my $prefix = $row->{'Area_Prefix'};
+ $prefix =~ s/\xA0//g;
+ $prefix =~ s/\s$//;
+ my $prefixprefix = '';
+ if ( $prefix =~ /^\s*(\d+)\s*\((.*)\)\s*$/ ) {
+ $prefixprefix = $1;
+ $prefix = $2;
+ } elsif ( $prefix =~ /^\s*\((\d{3})\)\s*(.*)$/ ) {
+ $prefixprefix = $1;
+ $prefix = $2;
+ }
+
+ my @rate_prefix = ();
+ if ( $prefix =~ /\d/ ) {
+
+ @rate_prefix = map {
+ #warn $row->{'Country'}. ": $prefixprefix$_\n";
+ new FS::rate_prefix {
+ 'countrycode' => $row->{'Code'},
+ 'npa' => $prefixprefix.$_,
+ };
+ }
+ split(/\s*[;,]\s*/, $prefix);
+
+ } else {
+ @rate_prefix = ( new FS::rate_prefix {
+ 'countycode' => $row->{'Code'},
+ 'npa' => '',
+ };
+ );
+ }
+
+ my $dest_detail = new FS::rate_detail {
+ 'ratenum' => $ratenum,
+ 'min_included' => 0,
+ 'min_charge' =>
+ sprintf('%.2f', $row->{'Rate'} ),
+ 'sec_granularity' => 60,
+ };
+
+ my $error = $rate_region->insert( 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => [ $dest_detail ],
+ );
+ die $error if $error;
+
+}
diff --git a/bin/reassemble_taxes b/bin/reassemble_taxes
new file mode 100755
index 0000000..001240b
--- /dev/null
+++ b/bin/reassemble_taxes
@@ -0,0 +1,35 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+
+my $user = shift or die &usage;
+my $dir = shift or die &usage;
+
+
+adminsuidsetup $user;
+
+my $conf = new FS::Conf;
+
+chdir $dir or die "can't change to $dir: $!\n";
+die "pmzclfull.zip already exists\n" if -f 'pmzclfull.zip';
+
+die "direct download of tax data not enabled\n"
+ unless $conf->exists('taxdatadirectdownload');
+my ( $urls, $username, $secret, $states ) =
+ $conf->config('taxdatadirectdownload');
+die "No tax download URL provided. ".
+ "Did you set the taxdatadirectdownload configuration value?\n"
+ unless $urls;
+
+my @filelist = qw( code.dbf detail.dbf geocode.dbf npanxx.dbf plus4.dbf
+ txmatrix.dbf zip.dbf );
+
+system('zip', "-P", $secret, 'pmzclfull.zip', @filelist) == 0
+ or die "zip failed\n";
+
+sub usage {
+ die "Usage:\n\n reassemble_taxes user dir\n";
+}
+
diff --git a/bin/rebill b/bin/rebill
new file mode 100755
index 0000000..4f05238
--- /dev/null
+++ b/bin/rebill
@@ -0,0 +1,132 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use Date::Parse;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw( qsearch );
+use cust_main_special;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw(%opt);
+getopts("p:a:d:sy:n", \%opt);
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my (@custnums) = @ARGV;
+
+my $time = $opt{d} ? str2time($opt{d}) : $^T;
+$time += $opt{y} * 86400 if $opt{y};
+my $invoice_time = $opt{n} ? $^T : $time;
+
+my %args = (
+ 'time' => $time,
+ 'invoice_time' => $invoice_time,
+ 'actual_time' => $^T, #when freeside-bill was started
+ #(not, when using -m, freeside-queued)
+ 'resetup' => ( $opt{'s'} ? $opt{'s'} : 0 ),
+ 'backbill' => $time,
+);
+
+my $extra_sql = ( $opt{a} || $opt{p} ) ? ' AND ' : ' WHERE ';
+$extra_sql .= "( ". join( ' OR ', map{ "custnum = $_" } @custnums ). " )";
+$extra_sql = '' unless scalar @custnums;
+
+my @cust = qsearch( { table => 'cust_main',
+ hashref => { $opt{a} ? ( 'agentnum' => $opt{a} ) : (),
+ $opt{p} ? ( 'payby' => $opt{p} ) : (),
+ },
+ extra_sql => $extra_sql,
+ }
+ );
+
+foreach my $cust ( @cust ) {
+ my $balance = $cust->balance;
+ cust_main_special::bill($cust, %args);
+ if ($balance != $cust->balance){
+ $cust->apply_payments_and_credits;
+ my $error = $cust->collect(%args);
+ warn "Error collecting, custnum ". $cust->custnum. ": $error" if $error;
+ }
+}
+
+
+###
+# 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' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] user [ custnum custnum ... ]\n";
+}
+
+###
+# documentation
+###
+
+=head1 NAME
+
+freeside-daily - Run daily billing and invoice collection events.
+
+=head1 SYNOPSIS
+
+ freeside-daily [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] user [ custnum custnum ... ]
+
+=head1 DESCRIPTION
+
+Bills customers and runs invoice collection events. Should be run from
+crontab daily.
+
+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).
+
+ -n: When used with "-d" and/or "-y", specifies that invoices should be dated
+ with today's date, irregardless of the pretend date used to pre-generate
+ the invoices.
+
+ -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
+
+ -a: Only process customers with the specified agentnum
+
+ -s: re-charge setup fees
+
+ -v: enable debugging
+
+ -l: debugging level
+
+ -m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
+
+ -r: Multi-process mode dry run option
+
+ -k: skip notify_flat_delay and vacuum
+
+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/bin/reset-cust_credit-otaker b/bin/reset-cust_credit-otaker
new file mode 100755
index 0000000..93002d0
--- /dev/null
+++ b/bin/reset-cust_credit-otaker
@@ -0,0 +1,88 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_d);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_credit;
+use FS::h_cust_credit;
+
+getopts('d:');
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+die &usage
+ unless ($opt_d);
+
+$FS::Record::nowarn_identical = 1;
+
+if ( $opt_d ) {
+ $opt_d =~ /^(\d+)$/ or die "invalid date";
+} else {
+ die "no date specified\n";
+}
+
+my @cust_credit = qsearch('cust_credit', { otaker => $user } );
+die "no credits found\n" unless @cust_credit;
+
+my $cust_credit = new FS::cust_credit;
+my @fields = grep { $_ !~ /^otaker|reason|reasonnum$/ } $cust_credit->fields;
+
+foreach my $cust_credit ( @cust_credit ) {
+ my %hash = $cust_credit->hash;
+ foreach (qw(otaker reason reasonnum)) {
+ delete $hash{$_};
+ }
+ $hash{'history_action'} = 'replace_old';
+ my $h_cust_credit =
+ qsearchs({ 'table' => 'h_cust_credit',
+ 'hashref' => \%hash,
+ 'select' => '*',
+ 'extra_sql' => " AND history_date <= $opt_d",
+ 'order_by' => 'ORDER BY history_date DESC LIMIT 1',
+ });
+ if ($h_cust_credit) {
+ $cust_credit->otaker($h_cust_credit->otaker);
+ my $reason = $h_cust_credit->getfield('reason');
+ if ($reason =~ /^\s*$/) {
+ $reason = '(none)';
+ }
+ $cust_credit->otaker($h_cust_credit->otaker);
+ $cust_credit->reason($reason);
+ my $error = $cust_credit->replace
+ if $cust_credit->modified;
+ die "error replacing cust_credit: $error\n"
+ if $error;
+ }else{
+ warn "Skipping credit.crednum ". $cust_credit->crednum;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n reset-cust_credit-otaker -d epoch_date user\n";
+}
+
+=head1 NAME
+
+reset-cust_credit-otaker - Command line tool to reset the otaker column for cust_credits to a previous value
+
+=head1 SYNOPSIS
+
+ reset-cust_credit-otaker -d epoch_date user
+
+=head1 DESCRIPTION
+
+ Sets the otaker column of the cust_credit records specified by user and
+ datespec to the value just prior to datespec.
+
+ The reasonnum of the cust_credit record is also set to reason record
+ which matches the reason specified in the history.
+
+=head1 SEE ALSO
+
+L<FS::cust_credit>, L<FS::h_cust_credit>;
+
+=cut
+
diff --git a/bin/rollback b/bin/rollback
new file mode 100755
index 0000000..7f83ef4
--- /dev/null
+++ b/bin/rollback
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs fields);
+
+use FS::svc_acct;
+
+#cust_pkg pkgnum 240133 241206 replace_old
+#cust_svc svcnum 31102 32083 delete
+#svc_acct svcnum 37162 37652 delete
+my($user, $table, $pkey, $start, $end, $action) = @ARGV;
+
+adminsuidsetup $user or die;
+
+#eval "use FS::h_$table;";
+#die $@ if $@;
+eval "use FS::$table;";
+die $@ if $@;
+
+my @history = grep { $_->historynum <= $end } qsearch("h_$table", { 'historynum' => { op=>'>=', value=>$start }, history_action => $action } );
+
+my %seen;
+foreach my $h (@history) {
+ my $error;
+ if ( $action eq 'replace_old' ) {
+ my $old = qsearchs($table, { $pkey => $h->get($pkey) } );
+ unless ( $old ) { die "can't find $table $pkey ". $h->get($pkey). "\n"; }
+ my $new = "FS::$table"->new( { map { $_ => $h->get($_) } fields($table) } );
+ $error = $new->replace($old);
+ } elsif ( $action eq 'delete' ) {
+ next if $seen{$h->get($pkey)}++;
+ my $new = "FS::$table"->new( { map { $_ => $h->get($_) } fields($table) } );
+ $error = $new->insert;
+ } else {
+ die "unknown action $action\n";
+ }
+ die $error if $error;
+}
diff --git a/bin/rotate-cdrs b/bin/rotate-cdrs
new file mode 100755
index 0000000..7bef0bb
--- /dev/null
+++ b/bin/rotate-cdrs
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Fcntl qw(:flock);
+use IO::File;
+
+my $dir = '/usr/local/etc/freeside/export/cdr';
+#chdir $dir;
+
+#XXX glob might not handle lots of args at some point...
+foreach my $file ( glob("$dir/*/CDR*-spool.CSV") ) {
+
+ $file =~ m{(\d+)/CDR(\d+)-spool.CSV$}
+ or die "guru meditation #54: can't parse filename: $file\n";
+ my($custnum, $date) = ($1, $2);
+
+
+ my $alpha = 'A';
+ while ( -e "$dir/$custnum/CDR$date$alpha.CSV" ) {
+ $alpha++; # A -> Z -> AA etc.
+ }
+ my $newfile = "$dir/$custnum/CDR$date$alpha.CSV";
+
+ rename $file, $newfile
+ or die "$! moving $file to $newfile\n";
+
+ use IO::File;
+ my $lock = new IO::File ">>$newfile"
+ or die "can't open $newfile: $!\n";
+ sleep 1; #just in case. i guess there's still a *remotely* possible
+ #race condition, but i'm not losing any sleep over it... (rimshot)
+ flock($lock, LOCK_EX)
+ or die "can't lock $newfile: $!\n";
+ #okay we've got the lock, any pending write should be done...
+
+ print "$custnum: $newfile\n";
+
+}
diff --git a/bin/rt-drop-tables b/bin/rt-drop-tables
new file mode 100755
index 0000000..b027542
--- /dev/null
+++ b/bin/rt-drop-tables
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+my @tables = qw(
+Attachments
+Queues
+Links
+Principals
+Groups
+ScripConditions
+Transactions
+Scrips
+ACL
+GroupMembers
+CachedGroupMembers
+Users
+Tickets
+ScripActions
+Templates
+TicketCustomFieldValues
+CustomFields
+CustomFieldValues
+sessions
+);
+
+foreach my $table ( @tables ) {
+ print "drop table $table;\n";
+ print "drop sequence ${table}_id_seq;\n";
+}
+
diff --git a/bin/rt-update-links b/bin/rt-update-links
new file mode 100644
index 0000000..75d554f
--- /dev/null
+++ b/bin/rt-update-links
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+
+my( $olddb, $newdb ) = ( shift, shift );
+
+$FS::CurrentUser::upgrade_hack = 1;
+my $dbh = adminsuidsetup;
+
+my $statement = "select * from links where base like 'fsck.com-rt://$olddb/%' OR target like 'fsck.com-rt://$olddb/%'";
+
+my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+
+while ( my $row = $sth->fetchrow_hashref ) {
+
+ ( my $base = $row->{'base'} )
+ =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/);
+
+ ( my $target = $row->{'target'} )
+ =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/);
+
+ if ( $row->{'base'} ne $base || $row->{'target'} ne $target ) {
+
+ my $update = 'UPDATE links SET base = ?, target = ? where id = ?';
+ my @param = ( $base, $target, $row->{'id'} );
+
+ warn "$update : ". join(', ', @param). "\n";
+ $dbh->do($update, {}, @param );
+
+ }
+
+}
+
+$dbh->commit;
+
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/slony-setup b/bin/slony-setup
new file mode 100755
index 0000000..0798c1a
--- /dev/null
+++ b/bin/slony-setup
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+#
+# slony replication setup
+#
+# usage: slony-setup freesideuser
+
+use strict;
+use DBI;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(dbdef);
+
+my $user = shift or die "usage: slony-setup username\n";
+adminsuidsetup($user);
+
+#---
+
+my $MASTERHOST = '192.168.20.10';
+my $SLAVEHOST = '192.168.20.50';
+#my $REPLICATIONUSER='pgsql';
+my $REPLICATIONUSER='postgres';
+
+#--------
+
+print <<END;
+
+#on slave:
+useradd freeside
+cp -pr /etc/skel /home/freeside
+chown -R freeside /home/freeside
+
+su postgres -c 'createuser freeside' #n y n
+su freeside -c 'createdb freeside'
+
+#on master:
+su postgres -c 'createlang plpgsql freeside'
+
+pg_dump -s -U $REPLICATIONUSER -h $MASTERHOST freeside | psql -U $REPLICATIONUSER -h $SLAVEHOST freeside
+
+END
+
+#--------
+
+#drop set ( id = 1, origin = 1);
+
+print <<END;
+#on master:
+slonik <<_EOF_
+
+cluster name = freeside;
+node 1 admin conninfo = 'dbname=freeside host=$MASTERHOST user=$REPLICATIONUSER';
+node 2 admin conninfo = 'dbname=freeside host=$SLAVEHOST user=$REPLICATIONUSER';
+init cluster ( id=1, comment = 'Master Node');
+
+create set (id=1, origin=1, comment='All freeside tables');
+
+END
+
+my $id = 1;
+
+foreach my $table ( dbdef->tables ) {
+ #next if $table =~ /^sql_/i;
+ print "set add table (set id=1, origin=1, id=". $id++. ", fully qualified name = 'public.$table' );\n";
+
+}
+
+print <<END;
+
+store node (id=2, comment = 'Slave node');
+store path (server = 1, client = 2, conninfo='dbname=freeside host=$MASTERHOST user=$REPLICATIONUSER');
+store path (server = 2, client = 1, conninfo='dbname=freeside host=$SLAVEHOST user=$REPLICATIONUSER');
+store listen (origin=1, provider = 1, receiver =2);
+store listen (origin=2, provider = 2, receiver =1);
+
+_EOF_
+END
+
+print <<END;
+
+### start slon processes (both machines) (this is debian-specific)
+mkdir /etc/slony1/freeside
+
+cat >/etc/slony1/freeside/slon.conf <<_EOF_
+# Set the cluster name that this instance of slon is running against
+# default is to read it off the command line
+cluster_name='freeside'
+
+# Set slon's connection info, default is to read it off the command line
+conn_info='host=localhost port=5432 dbname=freeside user=postgres'
+_EOF_
+
+/etc/init.d/slony1 start
+
+END
+
+
+print <<END;
+#on master:
+slonik <<_EOF_
+
+cluster name = freeside;
+
+node 1 admin conninfo = 'dbname=freeside host=$MASTERHOST user=$REPLICATIONUSER';
+node 2 admin conninfo = 'dbname=freeside host=$SLAVEHOST user=$REPLICATIONUSER';
+
+subscribe set ( id = 1, provider = 1, receiver = 2, forward = no);
+
+_EOF_
+END
+
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/strip-eps b/bin/strip-eps
new file mode 100755
index 0000000..2c2d124
--- /dev/null
+++ b/bin/strip-eps
@@ -0,0 +1,20 @@
+#!/usr/bin/perl -w
+
+# Author: Andy Turner <andrew.turner@acadia.net>
+
+use strict;
+
+# The first line has some binary magic for file identification
+# purposes. GhostScript doesn't like it. Strip it.
+scalar <>;
+
+# Add a header so that we can use magic to determine the file type.
+print "%!PS-Adobe-3.0 EPSF-3.0\n";
+
+while (<>) {
+ print;
+
+ # Illustrator Version 7 format EPS files have a bunch of binary gook
+ # after the "%%EOF" line. (% is a comment in PostScript, right?)
+ last if /^%%EOF/;
+}
diff --git a/bin/svc_acct-recalculate_usage b/bin/svc_acct-recalculate_usage
new file mode 100644
index 0000000..1b3955b
--- /dev/null
+++ b/bin/svc_acct-recalculate_usage
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_s $opt_u $opt_p $opt_k);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::cust_svc;
+
+my %field2sub = (
+ 'seconds' => sub {
+ my($svc_acct, $cust_pkg) = @_;
+ $svc_acct->seconds_since_sqlradacct( $cust_pkg->last_bill, time );
+ },
+ 'upbytes' => sub {
+ my($svc_acct, $cust_pkg) = @_;
+ $svc_acct->attribute_since_sqlradacct(
+ $cust_pkg->last_bill, time, 'AcctInputOctets' );
+ },
+ 'downbytes' => sub {
+ my($svc_acct, $cust_pkg) = @_;
+ $svc_acct->attribute_since_sqlradacct(
+ $cust_pkg->last_bill, time, 'AcctOutputOctets' );
+ },
+ 'totalbytes' => sub {
+ my($svc_acct, $cust_pkg) = @_;
+ $svc_acct->attribute_since_sqlradacct(
+ $cust_pkg->last_bill, time, 'AcctInputOctets' )
+ +
+ $svc_acct->attribute_since_sqlradacct(
+ $cust_pkg->last_bill, time, 'AcctOutputOctets' )
+ ;
+ },
+);
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $field = shift;
+die "can only reset seconds, upbytes, downbytes or totalbytes"
+ unless $field2sub{$field};
+
+my $value = shift;
+
+#false laziness w/freeside-reexport
+getopts('s:u:p:k:');
+
+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;
+} elsif ( $opt_k ) {
+ push @svc_x,
+ map { $_->svc_x }
+ qsearch({
+ table => 'cust_svc',
+ addl_from => 'LEFT JOIN cust_pkg USING ( pkgnum )',
+ extra_sql => "WHERE pkgpart = $opt_k",
+ });
+ die "no services with pkgpart $opt_k found\n" unless @svc_x;
+}
+
+warn "setting $field to $value before usage\n";
+foreach my $svc_x ( @svc_x ) {
+ my $cust_pkg = $svc_x->cust_svc->cust_pkg;
+ my $cust_usage = $value - &{ $field2sub{$field} }( $svc_x, $cust_pkg );
+# warn "resetting ". $svc_x->svcnum.':'.$svc_x->username. " to $cust_usage\n";
+ warn "$field for ". $svc_x->svcnum.':'.$svc_x->username. " reached limit\n"
+ if $cust_usage <= 0;
+ $svc_x->$field($cust_usage);
+
+ my $error = $svc_x->replace;
+ die $error if $error;
+}
+
+sub usage {
+ die "Usage:\n\n svc_acct-recalculate_usage user [ -s svcnum | -u username | -p svcpart ]\n";
+}
+
+=head1 NAME
+
+svc-acct-recalculate_usage - Command line tool to recalculate usage for existing services
+
+=head1 SYNOPSIS
+
+ svc_acct-recalculate_usage user usagefield initialvalue [ -s svcnum | -u username | -p svcpart ]
+
+ #recalculate a 1gb totalbytes limit for pkgpart 2
+ svc_acct-recalculate_usage ivan totalbytes 1073741824 -k 2
+
+=head1 DESCRIPTION
+
+Re-calculates the specified usage field for the specified service(s) (selected
+by svcnum, username or svcpart).
+
+=head1 SEE ALSO
+
+L<FS::svc_acct>, L<freeside-reexport>, L<FS::part_export>
+
+=cut
+
diff --git a/bin/svc_acct.import b/bin/svc_acct.import
new file mode 100755
index 0000000..aff26b9
--- /dev/null
+++ b/bin/svc_acct.import
@@ -0,0 +1,237 @@
+#!/usr/bin/perl -Tw
+
+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_acct_pop.import b/bin/svc_acct_pop.import
new file mode 100755
index 0000000..9e3d38b
--- /dev/null
+++ b/bin/svc_acct_pop.import
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+
+use strict;
+use Text::CSV_XS;
+use FS::UID qw(adminsuidsetup);
+use FS::svc_acct_pop;
+
+my @fields = qw( ac loc state city exch );
+my $fixup = sub {
+ my $hash = shift;
+ $hash->{ac} =~ /^\s*(\d{3})\s*$/;
+ $hash->{ac} = $1;
+ $hash->{loc} =~ /^\s*(\d{3})(\d{4})\s*$/;
+ $hash->{exch} = $1;
+ $hash->{loc} = $2;
+ $hash->{state} =~ /^\s*(\S{0,2})\s*$/;
+ $hash->{state} = $1;
+ $hash->{city} =~ /^\s*(.*?)\s*$/;
+ $hash->{city} = $1;
+
+ };
+
+my $user = shift or usage();
+adminsuidsetup $user;
+
+my $file = shift or usage();
+my $csv = new Text::CSV_XS;
+
+open(FH, $file) or die "cannot open $file: $!";
+
+sub usage {
+ die "Usage:\n\n svc_acct_pop.import user popfile.csv\n\n";
+}
+
+###
+
+my $line;
+while ( defined($line=<FH>) ) {
+ chomp $line;
+
+ $line &= "\177" x length($line); # i hope this isn't really necessary
+ $csv->parse($line)
+ or die "cannot parse: " . $csv->error_input();
+
+ my @values = $csv->fields();
+ my %hash;
+ foreach my $field (@fields) {
+ $hash{$field} = shift @values;
+ }
+
+ &{$fixup}(\%hash);
+
+ my $svc_acct_pop = new FS::svc_acct_pop { %hash };
+
+ #my $error = $svc_acct_pop->check;
+ my $error = $svc_acct_pop->insert;
+ die $error if $error;
+
+}
diff --git a/bin/svc_broadband.renumber b/bin/svc_broadband.renumber
new file mode 100755
index 0000000..980fa00
--- /dev/null
+++ b/bin/svc_broadband.renumber
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use strict;
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_Common;
+use FS::part_svc_router;
+use FS::svc_broadband;
+use FS::router;
+use FS::addr_block;
+
+$FS::svc_Common::noexport_hack = 1; #Disable exports!
+
+my $user = shift if $ARGV[0] or die &usage;
+adminsuidsetup($user);
+
+my $remapfile = shift if $ARGV[0] or die &usage;
+my $old_blocknum = shift if $ARGV[0] or die &usage;
+my $new_blocknum = shift if $ARGV[0] or die &usage;
+my $old_svcnum = shift if $ARGV[0];
+
+my %ipmap;
+
+open(REMAP, "<$remapfile") or die $!;
+while (<REMAP>) {
+ next unless (/^([0-9\.]+)\s+([0-9\.]+)$/);
+ my ($old_ip, $new_ip) = ($1, $2);
+ $ipmap{$old_ip} = $new_ip;
+}
+close(REMAP);
+
+my @svcs;
+if ($old_svcnum) {
+ @svcs = ( qsearchs('svc_broadband', { svcnum => $old_svcnum,
+ blocknum => $old_blocknum }) );
+} else {
+ @svcs = qsearch('svc_broadband', { blocknum => $old_blocknum });
+}
+
+foreach my $old_sb (@svcs) {
+
+ my $old_ip = $old_sb->ip_addr;
+ my $new_ip = $ipmap{$old_ip};
+ print "Renumbering ${old_ip} (${old_blocknum}) => ${new_ip} (${new_blocknum})...\n";
+
+
+ my $new_sb = new FS::svc_broadband
+ { $old_sb->hash,
+ ip_addr => $new_ip,
+ blocknum => $new_blocknum,
+ svcpart => $old_sb->cust_svc->svcpart,
+ };
+
+ my $error = $new_sb->replace($old_sb);
+ die $error if $error;
+
+}
+
+
+
+exit(0);
+
+sub usage {
+
+ my $usage = <<EOT;
+Usage:
+ svc_broadband.renumber user remapfile old_blocknum new_blocknum [ svcnum ]
+
+remapfile format:
+old_ip_address new_ip_address
+...
+
+Example remapfile:
+10.0.0.5 192.168.0.5
+10.0.0.20 192.168.0.20
+10.0.0.32 192.168.0.3
+
+Warning: This assumes your routers have already been reconfigured with the
+ new addresses. Exports will not be run!
+
+EOT
+
+}
diff --git a/bin/svc_domain.erase b/bin/svc_domain.erase
new file mode 100755
index 0000000..435dd5f
--- /dev/null
+++ b/bin/svc_domain.erase
@@ -0,0 +1,15 @@
+#!/usr/bin/perl -w
+
+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/bin/tax_rate_location.import b/bin/tax_rate_location.import
new file mode 100755
index 0000000..439d27c
--- /dev/null
+++ b/bin/tax_rate_location.import
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($opt_g $opt_f);
+use vars qw($DEBUG);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::tax_rate_location;
+
+getopts('f:g:');
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my ($format) = $opt_f =~ /^([-\w]+)$/;
+
+my @list = (
+ 'GEOCODE', $opt_g, \&FS::tax_rate_location::batch_import,
+);
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+my $error = '';
+
+while(@list) {
+ my ($name, $file, $method) = splice(@list, 0, 3);
+
+ my $fh;
+
+ $file =~ /^([\s\d\w.]+)$/ or die "Illegal filename: $file\n";
+ $file = $1;
+
+ open $fh, '<', $file or die "can't open $name file: $!\n";
+ $error ||= &{$method}( { filehandle => $fh, 'format' => $format, } );
+
+ die "error while processing $file: $error" if $error;
+ close $fh;
+}
+
+if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+}else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+sub usage { die "Usage:\ntax_rates_location.import -f FORMAT -g GEOCODEFILE user\n\n"; }
diff --git a/bin/test_scrub b/bin/test_scrub
new file mode 100644
index 0000000..5766925
--- /dev/null
+++ b/bin/test_scrub
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -w
+
+#This drops anything from the database that could cause live things to happen.
+#You'd want to do this on a test copy of your live database but NEVER on the
+#live database itself.
+
+#-all exports (all records in part_export, part_export_option export_svc)
+#-all non-POST invoice destinations (cust_main_invoice)
+#-all payment gateways and agent payment gw overrides (payment_gateway,
+# payment_gateway_option, agent_payment_gateway)
+#-everything in the job queue (queue and queue_arg)
+#-business-onlinepayment and business-onlinepayment-ach config
+
+use strict;
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Conf;
+
+adminsuidsetup shift;
+
+foreach my $table (qw(
+ part_export
+ part_export_option
+ export_svc
+ payment_gateway
+ payment_gateway_option
+ agent_payment_gateway
+ queue
+ queue_arg
+)) {
+
+ my $sth = dbh->prepare("DELETE FROM $table") or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+}
+
+my $dsth = dbh->prepare("DELETE FROM cust_main_invoice WHERE dest != 'POST'")
+ or die dbh->errstr;
+$dsth->execute or die $dsth->errstr;
+
+my $conf = new FS::Conf;
+foreach my $item (qw(
+ business-onlinepayment
+ business-onlinepayment-ach
+)) {
+ $conf->delete($item);
+}
+
+dbh->commit or die dbh->errstr;
diff --git a/bin/tron-scan b/bin/tron-scan
new file mode 100755
index 0000000..914d6d4
--- /dev/null
+++ b/bin/tron-scan
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::Tron qw(tron_scan tron_lint);
+use FS::cust_svc;
+
+adminsuidsetup shift;
+
+my $conf = new FS::Conf;
+my $mcp_svcpart = $conf->config('mcp_svcpart') or die "no mcp_svcpart";
+
+#tron_scan($_) foreach qsearch('cust_svc', { 'svcpart' => $mcp_svcpart } );
+foreach my $svc ( qsearch('cust_svc', { 'svcpart' => $mcp_svcpart } ) ) {
+ my $error = tron_scan($svc);
+ warn $error if $error;
+
+ my @lint = tron_lint($svc);
+ print $svc->svc_x->title. "\n". join('', map " $_\n", @lint )
+ if @lint;
+}
+
+1;
diff --git a/rt/html/NoAuth/js/scriptaculous/controls.js b/conf/agent_defaultpkg
index e69de29..e69de29 100644
--- a/rt/html/NoAuth/js/scriptaculous/controls.js
+++ b/conf/agent_defaultpkg
diff --git a/conf/alerter_template b/conf/alerter_template
new file mode 100644
index 0000000..6fb66b7
--- /dev/null
+++ b/conf/alerter_template
@@ -0,0 +1,18 @@
+
+
+{ $company_name; }
+{ $company_address; }
+
+
+{ $first; } { $last; }:
+
+ We thank you for your continuing patronage. This notice is to remind you
+that your { $payby } used to pay { $company_name; } for Internet
+service will expire on { use Date::Format; time2str("%B %o, %Y", $expdate); }. Please provide us with new
+billing information so that we may continue your service uninterrupted.
+
+Very Truly Yours,
+
+ { $company_name; } Service Team
+
+
diff --git a/conf/blank_logo.eps b/conf/blank_logo.eps
new file mode 100644
index 0000000..e7e3bab
--- /dev/null
+++ b/conf/blank_logo.eps
@@ -0,0 +1,22 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%BoundingBox: 0 0 1 1
+%%HiResBoundingBox: 0 0 0 0
+%%Creator: Karbon14 EPS Exportfilter 0.5
+%%CreationDate: (01/03/2007 11:23:26 PM)
+%%For: (ivan) ()
+%%Title: ()
+
+/N {newpath} def
+/C {closepath} def
+/m {moveto} def
+/c {curveto} def
+/l {lineto} def
+/s {stroke} def
+/f {fill} def
+/w {setlinewidth} def
+/d {setdash} def
+/r {setrgbcolor} def
+/S {gsave} def
+/R {grestore} def
+
+%%EOF
diff --git a/conf/company_address b/conf/company_address
new file mode 100644
index 0000000..3824862
--- /dev/null
+++ b/conf/company_address
@@ -0,0 +1,2 @@
+1234 Example Lane
+Exampleton, CA 54321
diff --git a/conf/company_name b/conf/company_name
new file mode 100644
index 0000000..2cd5323
--- /dev/null
+++ b/conf/company_name
@@ -0,0 +1 @@
+ExampleCo
diff --git a/rt/html/NoAuth/js/scriptaculous/effects.js b/conf/cust_pkg-change_svcpart
index e69de29..e69de29 100644
--- a/rt/html/NoAuth/js/scriptaculous/effects.js
+++ 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/impending_recur_template b/conf/impending_recur_template
new file mode 100644
index 0000000..deb396a
--- /dev/null
+++ b/conf/impending_recur_template
@@ -0,0 +1,20 @@
+
+
+{ $company_name; }
+{ $company_address; }
+
+
+{ $first; } { $last; }:
+
+ We thank you for your continuing patronage. This notice is to remind you
+that your { $packages->[0] } Internet service
+will expire on { use Date::Format; time2str("%B %o, %Y", $recurdates->[0]); }.
+At that time we will begin charging you on a recurring basis so that we may
+continue your service uninterrupted.
+
+Very Truly Yours,
+
+ { $company_name; } Service Team
+
+
+
diff --git a/conf/invoice_from b/conf/invoice_from
new file mode 100644
index 0000000..e20d96b
--- /dev/null
+++ b/conf/invoice_from
@@ -0,0 +1 @@
+ivan-unconfigured-freeside-installation@freeside.biz
diff --git a/conf/invoice_html b/conf/invoice_html
new file mode 100644
index 0000000..fe2a9a9
--- /dev/null
+++ b/conf/invoice_html
@@ -0,0 +1,254 @@
+<STYLE TYPE="text/css">
+.invoice { font-family: sans-serif; font-size: 10pt }
+.invoice_header { font-size: 10pt }
+.invoice_headerright TH { border-top: 2px solid #000000; border-bottom: 2px solid #000000 }
+.invoice_headerright TD { font-size: 10pt; empty-cells: show }
+.invoice_summary TH { border-bottom: 2px solid #000000 }
+.invoice_summary TD { font-size: 10pt; empty-cells: show }
+.invoice_longtable table { cellspacing: none }
+.invoice_longtable TH { border-top: 2px solid #000000; border-bottom: 1px solid #000000; padding-left: none; padding-right: none; font-size: 10pt }
+.invoice_desc TD { border-top: 2px solid #000000; font-weight: bold; font-size: 10pt }
+.invoice_desc_more TD { font-weight: bold; font-size: 10pt }
+.invoice_extdesc TD { font-size: 8pt }
+.invoice_totaldesc TD { font-size: 10pt; empty-cells: show }
+</STYLE>
+
+<table class="invoice" bgcolor="#ffffff" WIDTH=625 CELLSPACING=8><tr><td>
+
+ <table class="invoice_header" width="100%">
+ <tr>
+ <td><img src="<%= $cid ? "cid:$cid" : "cust_bill-logo.cgi?invnum=$invnum;template=$template" %>"></td>
+ <td align="left"><%= $returnaddress %></td>
+ <td align="right">
+ <table CLASS="invoice_headerright" cellspacing=0>
+ <tr>
+ <td align="center">
+ Invoice&nbsp;date<BR>
+ <B><%= $date %></B>
+ </td>
+ <td>
+ </td>
+ <td align="center">
+ Invoice&nbsp;#<BR>
+ <B><%= $invnum %></B>
+ </td>
+ <td>
+ </td>
+ <td align="center">
+ Customer #<BR>
+ <B><%= $custnum %></B>
+ </td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <th colspan=3 align="center">
+ <FONT SIZE="+3"><%= $notice_name ? substr($notice_name, 0, 1) : 'I' %></FONT><FONT SIZE="+2"><%= $notice_name ? uc(substr($notice_name, 1)) : 'NVOICE' %></FONT>
+ </th>
+ <th>&nbsp;</th>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ </td>
+ <td align="left">
+ <b><%= $payname %></b><BR>
+ <%= join('<BR>', grep length($_), $company,
+ $address1,
+ $address2,
+ "$city,&nbsp;$state&nbsp;&nbsp;$zip",
+ $country,
+ )
+ %>
+ </td>
+ <%= $ship_enable ? ('<td align="left">'.
+ join('<BR>',grep length($_), '<b>Service Address</b>',
+ $ship_company,
+ $ship_address1,
+ $ship_address2,
+ "$ship_city,&nbsp;$ship_state&nbsp;$ship_zip",
+ $ship_country,
+ ' ',
+ ' ',
+ ).
+ '</td><tr><td></td><td></td>'
+ )
+ : ''
+ %>
+ <td align="right">
+ Terms: <%= $terms %><BR>
+ <%= $po_line %>
+ </td>
+ </tr>
+
+ </table>
+ <%= $summary %>
+ <%=
+ my $notfirst = 0;
+ foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {
+ if ($section->{'pretotal'} && !$summary) {
+ $OUT .= '</table>' if $notfirst;
+ $OUT .=
+ '<table width="100%"><tr><td>'.
+ '<p align="right"><b><font size="+1">'.
+ uc(substr($section->{'pretotal'},0,1)).
+ '</font><font size="+0">'. uc(substr($section->{'pretotal'},1)).
+ '</font></b>'.
+ '<p>'.
+ '</td></tr>';
+ }
+ unless ($section->{'summarized'}) {
+ $OUT .= '</table>' if ( $notfirst || $section->{'pretotal'} && !$summary );
+ $OUT .= '<table><tr><td>';
+ if ($section->{'description'}) {
+ $OUT .=
+ '<p><b><font size="+1">'. uc(substr($section->{'description'},0,1)).
+ '</font><font size="+0">'. uc(substr($section->{'description'},1)).
+ '</font></b>'.
+ '<p>';
+ }else{
+ $OUT .=
+ '<p><b><font size="+1">C</font><font size="+0">HARGES</font></b>'.
+ '<p>';
+ }
+ $OUT .= '</td></tr></table>';
+
+ $OUT .=
+ '<table class="invoice_longtable" CELLSPACING=0 WIDTH="100%">'.
+ '<tr>';
+
+ if ($section->{header_generator}) {
+ $OUT .= &{$section->{header_generator}}();
+ } else {
+ $OUT .= '<th align="center">Ref</th>'.
+ '<th align="left">Description</th>'.
+ ( $unitprices
+ ? '<th align="left">Unit Price</th>'.
+ '<th align="left">Quantity</th>'
+ : ''
+ ).
+ '<th align="right">Amount</th>';
+ }
+ $OUT .= '</tr>';
+
+ my $lastref = 0;
+ foreach my $line (
+ grep { ( scalar(@sections) > 1
+ ? $section->{'description'} eq $_->{'section'}->{'description'}
+ : 1
+ ) }
+ @detail_items )
+ {
+ $OUT .=
+ '<tr class="invoice_desc';
+ if ( $section->{description_generator} ) {
+ $OUT .= &{$section->{description_generator}}($line);
+ } else {
+ $OUT .= ( ($line->{'ref'} && $line->{'ref'} ne $lastref) ? '' : '_more' ).
+ '">'.
+ '<td align="center">'.
+ ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ). '</td>'.
+ '<td align="left">'. $line->{'description'}. '</td>'.
+ ( $unitprices
+ ? '<td align="left">'. $line->{'unit_amount'}. '</td>'.
+ '<td align="left">'. $line->{'quantity'}. '</td>'
+ : ''
+ ).
+
+ '<td align="right">'. $line->{'amount'}. '</td>';
+ }
+ $OUT .= '</tr>';
+ $lastref = $line->{'ref'};
+ if ( @{$line->{'ext_description'} } ) {
+ $OUT .= '<tr class="invoice_extdesc"><td></td><td';
+ $OUT .= $unitprices ? ' colspan=3>' : '>';
+ $OUT .= '<table width="100%">';
+ foreach my $ext_desc ( @{$line->{'ext_description'} } ) {
+ $OUT .=
+ '<tr class="invoice_extdesc">'.
+ '<td align="left" '.
+ ( $ext_desc =~ /<\/?TD>/i ? '' : 'colspan=99' ). '>'.
+ '&nbsp;&nbsp;'. $ext_desc.
+ '</td>'.
+ '</tr>'
+ }
+ $OUT .= '</table></td><td></td></tr>';
+ }
+ }
+
+
+ if ($section->{'description'} || $multisection) {
+ my $style = 'border-top: 3px solid #000000;'.
+ 'border-bottom: 3px solid #000000;';
+ $OUT .=
+ '<tr class="invoice_totaldesc">'.
+ qq(<td style="$style">&nbsp;</td>);
+ if ($section->{total_generator}) {
+ $OUT .= &{$section->{total_generator}}($section);
+ } else {
+ $OUT .= qq(<td align="left" style="$style").
+ ( $unitprices ? ' colspan=3>' : '>' ).
+ $section->{'description'}. ' Total </td>'.
+ qq(<td align="right" style="$style">).
+ $section->{'subtotal'}. '</td>';
+ }
+ $OUT .= '</tr>';
+ }
+ }
+ if ($section->{'posttotal'}) {
+ $OUT .= '<tr><td align="right" colspan=5>';
+ $OUT .=
+ '<p><font size="+1">'. $section->{'posttotal'}.
+ '</font>'.
+ '<p>';
+ $OUT .= '</td></tr>';
+ }
+
+ $notfirst++;
+
+ }
+
+ my $style = 'border-top: 3px solid #000000;';
+ my $linenum = 0;
+
+ foreach my $line ( @total_items ) {
+
+ $style .= 'border-bottom: 3px solid #000000;'
+ if ++$linenum == scalar(@total_items) - ( $balance_due_below_line ? 1 : 0 );
+
+ $OUT .=
+ '<tr class="invoice_totaldesc">';
+ if ($section->{total_line_generator}) {
+ $OUT .= &{$section->{total_line_generator}}($line);
+ } else {
+ $OUT .= qq(<td style="$style">&nbsp;</td>).
+ qq(<td align="left" style="$style").
+ ( $unitprices ? ' colspan=3>' : '>' ).
+ $line->{'total_item'}. '</td>'.
+ qq(<td align="right" style="$style">).
+ $line->{'total_amount'}. '</td>';
+ }
+ $OUT .= '</tr>';
+
+ $style='';
+
+ }
+
+ %>
+ </table>
+ <br><br>
+
+<%= length($summary)
+ ? ''
+ : ( $smallernotes
+ ? '<FONT SIZE="-1">'.$notes.'</FONT>'
+ : $notes
+ )
+%>
+
+ <hr NOSHADE SIZE=2 COLOR="#000000">
+ <p align="center" <%= $smallerfooter ? 'STYLE="font-size:75%;"' : '' %>><%= $footer %>
+
+</td></tr></table>
diff --git a/conf/invoice_html_statement b/conf/invoice_html_statement
new file mode 100644
index 0000000..0595602
--- /dev/null
+++ b/conf/invoice_html_statement
@@ -0,0 +1,124 @@
+<STYLE TYPE="text/css">
+.invoice { font-family: sans-serif; font-size: 10pt }
+.invoice_header { font-size: 10pt }
+.invoice_headerright TH { border-top: 2px solid #000000; border-bottom: 2px solid #000000 }
+.invoice_headerright TD { font-size: 10pt; empty-cells: show }
+.invoice_longtable table { cellspacing: none }
+.invoice_longtable TH { border-top: 2px solid #000000; border-bottom: 1px solid #000000; padding-left: none; padding-right: none; font-size: 10pt }
+.invoice_desc TD { border-top: 2px solid #000000; font-weight: bold; font-size: 10pt }
+.invoice_extdesc TD { font-size: 8pt }
+.invoice_totaldesc TD { font-size: 10pt; empty-cells: show }
+</STYLE>
+
+<table class="invoice" bgcolor="#ffffff" WIDTH=768 CELLSPACING=8><tr><td>
+
+ <table class="invoice_header" width="100%">
+ <tr>
+ <td><img src="<%= $cid ? "cid:$cid" : "cust_bill-logo.cgi?invnum=$invnum;template=$template" %>"></td>
+ <td align="left"><%= $returnaddress %></td>
+ <td align="right">
+ <table CLASS="invoice_headerright" cellspacing=0>
+ <tr>
+ <td align="right">
+ Invoice&nbsp;date<BR>
+ <B><%= $date %></B>
+ </td>
+ <td>
+ </td>
+ <td align="left">
+ Invoice&nbsp;number<BR>
+ <B><%= $invnum %></B>
+ </td>
+ </tr>
+ <tr>
+ <th>&nbsp;</th>
+ <th colspan=1 align="center">
+ <FONT SIZE="+3">S</FONT><FONT SIZE="+2">TATEMENT</FONT>
+ </th>
+ <th>&nbsp;</th>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ </td>
+ <td align="left">
+ <b><%= $payname %></b><BR>
+ <%= join('<BR>', grep length($_), $company,
+ $address1,
+ $address2,
+ "$city,&nbsp;$state&nbsp;&nbsp;$zip",
+ $country,
+ )
+ %>
+ </td>
+ <td align="right">
+ Terms: <%= $terms %><BR>
+ <%= $po_line %>
+ </td>
+ </tr>
+
+ </table>
+
+ <p><b><font size="+1">C</font><font size="+0">HARGES</font></b>
+ <p>
+ <table class="invoice_longtable" CELLSPACING=0 WIDTH="100%">
+ <tr>
+ <th align="center">Ref</th>
+ <th align="left">Description</th>
+ <th align="right">Amount</th>
+ </tr>
+ <%=
+
+ foreach my $line ( @detail_items ) {
+ $OUT .=
+ '<tr class="invoice_desc">'.
+ '<td align="center">'. $line->{'ref'}. '</td>'.
+ '<td align="left">'. $line->{'description'}. '</td>'.
+ '<td align="right">'. $line->{'amount'}. '</td>'.
+ '</tr>'
+ ;
+ foreach my $ext_desc ( @{$line->{'ext_description'} } ) {
+ $OUT .=
+ '<tr class="invoice_extdesc">'.
+ '<td></td>'.
+ '<td align="left">-&nbsp;'. $ext_desc. '</td>'.
+ '<td></td>'.
+ '</tr>'
+ }
+ }
+
+ my $style = 'border-top: 3px solid #000000;';
+ my $linenum = 0;
+
+ foreach my $line ( @total_items ) {
+
+ $style .= 'border-bottom: 3px solid #000000;'
+ if ++$linenum == scalar(@total_items);
+
+ $OUT .=
+ '<tr class="invoice_totaldesc">'.
+ qq(<td style="$style">&nbsp;</td>).
+ qq(<td align="left" style="$style">).
+ $line->{'total_item'}. '</td>'.
+ qq(<td align="right" style="$style">).
+ $line->{'total_amount'}. '</td>'.
+ '</tr>'
+ ;
+
+ $style='';
+
+ }
+
+ %>
+ </table>
+ <br><br>
+
+<%= $notes %>
+
+ <hr NOSHADE SIZE=2 COLOR="#000000">
+ <p align="center"><%= $footer %>
+
+</td></tr></table>
diff --git a/conf/invoice_htmlsummary b/conf/invoice_htmlsummary
new file mode 100644
index 0000000..b158478
--- /dev/null
+++ b/conf/invoice_htmlsummary
@@ -0,0 +1,74 @@
+<table>
+ <tr>
+ <td>
+ <table>
+ <tr><td><%= $notes %></td></tr>
+ </table>
+ </td>
+ <td>
+ <table class="invoice_summary">
+ <tr><th colspan=2><br></th></tr>
+ <tr>
+ <td><b><u><br>Summary of Previous Balance and Payments<br></u></b></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td><b>Previous Balance</b></td>
+ <td align="right"><b><%= $dollar.$true_previous_balance %></b></td>
+ </tr>
+ <tr>
+ <td><b>Payments</b></td>
+ <th align="right"><b><%= $dollar.$balance_adjustments %></b></th>
+ </tr>
+ <tr>
+ <td><b>Balance Outstanding</b></td>
+ <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance - $balance_adjustments) %></b></td>
+ </tr>
+ <tr><th colspan=2><br></th></tr>
+ <tr><td colspan=2><br></td></tr>
+ <tr>
+ <td><b><u>Summary of New Charges</u></b></td>
+ <td></td>
+ </tr>
+ <tr><td colspan=2><br></td></tr>
+ <%=
+ my ($last) = grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} reverse @sections;
+
+ foreach my $section ( grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} @sections ) {
+ $OUT .= '<tr><td><b>'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '</b></td>';
+ my $celltype = ($last == $section) ? 'th' : 'td';
+ $OUT .= qq(<$celltype align="right"><b>). $section->{'subtotal'}. "</b></$celltype></tr>";
+ }
+ %>
+ <tr>
+ <td><b>New Charges Total</b></td>
+ <td align="right"><b><%= $dollar.$current_less_finance %></b></td>
+ </tr>
+ <tr><th colspan=2><br></th></tr>
+ <tr><td colspan=2><br></td></tr>
+ <tr>
+ <td><b><u>Invoice Summary</u></b></td>
+ <td></td>
+ </tr>
+ <tr><td colspan=2><br></td></tr>
+ <tr>
+ <td><b>Previous Past Due Charges</b></td>
+ <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance - $balance_adjustments) %></b></td>
+ </tr>
+ <tr>
+ <td><b>Finance charges on overdue amount</b></td>
+ <td align="right"><b><%= $dollar.$finance_amount %></b></td>
+ </tr>
+ <tr>
+ <td><b>New Charges</b></td>
+ <th align="right"><b><%= $dollar.$current_less_finance %></b></th>
+ </tr>
+ <tr>
+ <td><b>Total Amount Due</b></td>
+ <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance + $current_charges - $balance_adjustments) %></b></td>
+ </tr>
+ <tr><th colspan=2><br></th></tr>
+ </table>
+ </td>
+ </tr>
+</table>
diff --git a/conf/invoice_latex b/conf/invoice_latex
new file mode 100644
index 0000000..d37eeba
--- /dev/null
+++ b/conf/invoice_latex
@@ -0,0 +1,358 @@
+%% 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 Kristian Hoffman
+%%
+%% 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 forked for Freeside; checked into CVS
+%%
+
+\documentclass[letterpaper]{article}
+
+\usepackage{fancyhdr,lastpage,ifthen,array,fslongtable,afterpage,caption,multirow,bigstrut}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0cm} % top margin to top of header
+\addtolength{\hoffset}{-0.6cm} % left margin on page
+\addtolength{\topmargin}{-1.25cm} % top margin to top of header
+\setlength{\headheight}{2.0cm} % height of header
+\setlength{\headsep}{1.0cm} % between header and text
+\setlength{\footskip}{1.0cm} % bottom of footer from bottom of text
+
+%\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\textwidth}{19.5cm}
+\setlength{\textheight}{19.5cm}
+\setlength{\oddsidemargin}{-0.9cm} % odd page left margin
+\setlength{\evensidemargin}{-0.9cm} % even page left margin
+
+\LTchunksize=40
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{1pt}
+
+\renewcommand{\footrule}{
+[@--
+ $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : '';
+--@]
+ {
+ }
+ {
+ \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}
+ }
+}
+
+\newcommand{\extracouponspace}{3.6cm}
+
+% Adjust the inset of the mailing address
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
+% Adjust the inset of the return address and logo
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
+% New command for address lines i.e. skip them if blank
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}
+
+% Inserts dollar symbol
+\newcommand{\dollar}[1][]{\symbol{36}}
+
+% Remove plain style header/footer
+\fancypagestyle{plain}{
+ \fancyhead{}
+}
+\fancyhf{}
+
+% Define fancy header/footer for first and subsequent pages
+\fancyfoot[C]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+[@--
+ if ($coupon) {
+ $OUT .= '\vspace{-\extracouponspace}';
+ $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';
+ $OUT .= $coupon;
+ }
+ '';
+--@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
+[@-- $footer --@]
+ }[@-- $coupon ? '\vspace{\extracouponspace}' : '' --@]
+ }
+ { % ... pages
+ [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
+[@-- $smallfooter --@]
+ }
+ }
+}
+
+\fancyfoot[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ }
+ { % ... pages
+ \small{\thepage\ of \pageref{LastPage}}
+ }
+}
+
+\fancyhead[L]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \returninset
+ \makebox{
+ \begin{tabular}{ll}
+ \includegraphics{[@-- $logo_file --@]} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+ \end{tabular}
+ }
+ }
+ { % ... pages
+ %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages.
+ }
+}
+
+\fancyhead[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \begin{tabular}{ccc}
+ Invoice date & Invoice \#& Customer\#\\
+ \vspace{0.2cm}
+ \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]} \\\hline
+ \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- $notice_name || 'Invoice' --@]}} & \\
+ \vspace{-0.2cm}
+ & & \\\hline
+ \end{tabular}
+ }
+ { % ... pages
+ \small{
+ \begin{tabular}{lll}
+ Invoice date & Invoice \#& Customer\#\\
+ \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]}\\
+ \end{tabular}
+ }
+ }
+}
+
+\pagestyle{fancy}
+
+
+%% Font options are:
+%% bch Bitsream Charter
+%% put Utopia
+%% phv Adobe Helvetica
+%% pnc New Century Schoolbook
+%% ptm Times
+%% pcr Courier
+
+\renewcommand{\familydefault}{phv}
+
+
+% Commands for freeside table header...
+
+\newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }
+\newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }
+\newcommand{\FSunitcolumns}{ [@-- $unitprices ? '\makebox[2.5cm][l]{\textbf{~~Unit Price}}&\makebox[1.4cm]{\textbf{~Quantity}}&' : '' --@] }
+
+\newcommand{\FShead}{
+ \hline
+ \rule{0pt}{2.5ex}
+ \makebox[1.4cm]{\textbf{Ref}} &
+% \makebox[2.9cm][l]{\textbf{Description}}&
+% \makebox[1.4cm][l]{}&
+% \makebox[1.4cm][l]{}&
+% \makebox[2.5cm][l]{}&
+ \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{Description}}}&
+ \FSunitcolumns
+ \makebox[1.6cm][r]{\textbf{Amount}} \\
+ \hline
+}
+
+% ...description...
+\newcommand{\FSdesc}[5]{
+ \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &
+ \multicolumn{4}{l}{\textbf{#2}} &
+ \multicolumn{1}{l}{\textbf{#3}} &
+ \multicolumn{1}{r}{\textbf{#4}} &
+ \multicolumn{1}{r}{\textbf{\dollar #5}}\\
+}
+% ...extended description...
+\newcommand{\FSextdesc}[1]{
+ \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
+%% \multicolumn{2}{l}{\small{~-~#1}}\\
+#1\\
+}
+% ...and total line items.
+\newcommand{\FStotaldesc}[2]{
+ & \multicolumn{6}{l}{#1} & #2\\
+}
+
+
+\begin{document}
+% Headers and footers defined for the first page
+\addressinset \rule{0.5cm}{0cm}
+\makebox{
+\begin{minipage}[t]{7.0cm}
+\vspace{0.25cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}
+\hfill
+\makebox{
+\begin{minipage}[t]{6.4cm}
+[@--
+ if ($ship_enable) {
+ $OUT .= '\textbf{Service Address}\\\\';
+ $OUT .= "\\addressline{$ship_company}";
+ $OUT .= "\\addressline{$ship_address1}";
+ $OUT .= "\\addressline{$ship_address2}";
+ $OUT .= "\\addressline{$ship_city, $ship_state~~$ship_zip}";
+ $OUT .= "\\addressline{$ship_country}";
+ $OUT .= '~\\\\';
+ }else{
+ $OUT .= '';
+ }
+--@]
+\begin{flushright}
+Terms: [@-- $terms --@]\\
+[@-- $po_line --@]\\
+\end{flushright}
+\end{minipage}}
+\vspace{1.5cm}
+%
+[@-- $summary --@]
+%
+\section*{}
+[@--
+ foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {
+ if ($section->{'pretotal'} && !$summary) {
+ $OUT .= '\begin{flushright}';
+ $OUT .= '\large\textsc{'. $section->{'pretotal'}. '}\\\\';
+ $OUT .= '\\end{flushright}';
+ }
+ $OUT .= '\pagebreak' if $section->{'post_total'};
+ unless ($section->{'summarized'} ) {
+ $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';
+ $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'
+ if $coupon;
+ $OUT .= '\begin{longtable}{cllllllr}';
+ $OUT .= '\caption*{ ';
+ $OUT .= ($section->{'description'}) ? $section->{'description'}: 'Charges';
+ $OUT .= '}\\\\';
+ if ($section->{header_generator}) {
+ $OUT .= &{$section->{header_generator}}();
+ } else {
+ $OUT .= '\FShead';
+ }
+ $OUT .= '\endfirsthead';
+ $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';
+ if ($section->{header_generator}) {
+ $OUT .= &{$section->{header_generator}}();
+ } else {
+ $OUT .= '\FShead';
+ }
+ $OUT .= '\endhead';
+ $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';
+ $OUT .= '\endfoot';
+ $OUT .= '\hline';
+
+ if (scalar(@sections) > 1) {
+ if ($section->{total_generator}) {
+ $OUT .= &{$section->{total_generator}}($section);
+ } else {
+ $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .
+ '{' . $section->{'subtotal'} . '}' . "\n";
+ }
+ }
+
+ #if ($section == $sections[$#sections]) {
+ foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
+ if ($section->{total_line_generator}) {
+ $OUT .= &{$section->{total_line_generator}}($line);
+ } else {
+ $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
+ '{' . $line->{'total_amount'} . '}' . "\n";
+ }
+ }
+ #}
+
+ $OUT .= '\hline';
+ $OUT .= '\endlastfoot';
+
+ my $lastref = 0;
+ foreach my $line (
+ grep { ( scalar( @sections ) > 1
+ ? $section->{'description'} eq $_->{'section'}->{'description'}
+ : 1
+ ) }
+ @detail_items )
+ {
+ my $ext_description = $line->{'ext_description'};
+
+ # Don't break-up small packages.
+ my $rowbreak = @$ext_description < 5 ? '*' : '';
+
+ $OUT .= "\\hline\n" if ($line->{'ref'} && $line->{'ref'} ne $lastref);
+ if ($section->{description_generator}) {
+ $OUT .= &{$section->{description_generator}}($line);
+ } else {
+ $OUT .= '\FSdesc'.
+ '{' . ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ) . '}'.
+ '{' . $line->{'description'} . '}' .
+ '{' . ( $unitprices ? $line->{'unit_amount'} : '' ) . '}'.
+ '{' . ( $unitprices ? $line->{'quantity'} : '' ) . '}' .
+ '{' . $line->{'amount'} . "}${rowbreak}\n";
+ }
+ $lastref = $line->{'ref'};
+
+ foreach my $ext_desc (@$ext_description) {
+ if ($section->{extended_description_generator}) {
+ $OUT .= &{$section->{extended_description_generator}}($ext_desc);
+ } else {
+ if ( $ext_desc !~ /[^\\]&/ ) {
+ $ext_desc = substr($ext_desc, 0, 80) . '...'
+ if (length($ext_desc) > 80);
+ $ext_desc = '\multicolumn{6}{l}{\small{~~~'. $ext_desc. '}}';
+ }else{
+ $ext_desc = "~~~$ext_desc";
+ }
+ $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";
+ }
+ }
+
+ }
+
+ $OUT .= '\end{longtable}';
+ }
+ if ($section->{'posttotal'}) {
+ $OUT .= '\begin{flushright}';
+ $OUT .= '\normalfont\large\bfseries\textsc{'. $section->{'posttotal'}. '}\\\\';
+ $OUT .= '\\end{flushright}';
+ }
+ }
+
+--@]
+\vfill
+\begin{minipage}[t]{\textwidth}
+ [@-- length($summary)
+ ? ''
+ : ( $smallernotes
+ ? '\scriptsize{ '.$notes.' }'
+ : $notes
+ )
+ --@]
+ [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : '' --@]
+\end{minipage}
+\end{document}
diff --git a/conf/invoice_latex.diff b/conf/invoice_latex.diff
new file mode 100644
index 0000000..b66a522
--- /dev/null
+++ b/conf/invoice_latex.diff
@@ -0,0 +1,138 @@
+--- invoice_latex.old 2005-04-14 01:52:02.000000000 -0700
++++ invoice_latex 2005-04-14 02:33:26.000000000 -0700
+@@ -5,7 +5,7 @@
+ %% Asplen Management Ltd
+ %% www.asplen.co.uk
+ %%
+-%% Modified for Freeside by Ivan Kohler
++%% Modified for Freeside by Ivan Kohler and Kristian Hoffman
+ %%
+ %% Changes
+ %% 0.1 4/12/00 Created
+@@ -61,7 +61,7 @@
+ %% Headers and footers defined for the first page
+ \fancyfoot[CO,CE]{\small{
+ \begin{tabular}{c}
+-$footer
++[@-- $footer --@]
+ \end{tabular}}}
+ %
+ %% The LH Heading comprising logo
+@@ -76,7 +76,7 @@
+ \begin{tabular}{rcl}
+ Invoice date & & Invoice number \\
+ \vspace{0.2cm}
+-\textbf{$date} & & \textbf{$invnum} \\\hline
++\textbf{[@-- $date --@]} & & \textbf{[@-- $invnum --@]} \\\hline
+ \rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}}& \\
+ \vspace{-0.2cm}
+ & & \\\hline
+@@ -85,71 +85,76 @@
+ %% Header & footer changes for subsequent pages
+ %
+ \afterpage{ \fancyfoot[RO,RE]{\small{\thepage\ of \pageref{LastPage}}} }
+-\afterpage{ \fancyfoot[CO,CE]{\small{$smallfooter}} }
++\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}\\
++\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}
++\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\\
++Terms: [@-- $terms --@]\\
++[@-- $po_line --@]\\
+ \end{flushright}
+ \end{minipage}}
+ \vspace{0.5cm}
+ %
+ \section*{\textsc{Charges}}
+-\begin{longtable}{|c|l|c|r|r|}
++\begin{longtable}{|c|l|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}} \\
++\makebox[13cm][l]{\textbf{Description}} &
++\makebox[2cm][r]{\textbf{Amount}} \\
+ \hline
+ \endfirsthead
+-\multicolumn{5}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\
++\multicolumn{3}{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}} \\
++\makebox[13cm][l]{\textbf{Description}} &
++\makebox[2cm][r]{\textbf{Amount}} \\
+ \hline
+ \endhead
+-\multicolumn{5}{r}{\rule{0pt}{2.5ex}/cont...}\\
++\multicolumn{3}{r}{\rule{0pt}{2.5ex}/cont...}\\
+ \endfoot
+-%%TotalDetails
+- & \multicolumn{3}{l}{$total_item} & $total_amount\\
+-%%EndTotalDetails
++[@--
++
++ foreach my $line (@total_items) {
++ $OUT .= ' & \multicolumn{1}{l}{' . $line->{'total_item'} . '} & ' .
++ $line->{'total_amount'} . '\\\\' . "\n";
++ }
++
++--@]
+ \hline
+ \endlastfoot
+-%%Detail
+-\rule{0pt}{2.5ex}$ref &
+-\begin{tabular}{l}
+-$description\tabularnewline
+-\end{tabular}
+-& $quantity & \dollar $amount & \dollar $amount\\\hline
+-%%EndDetail
++[@--
++
++ foreach my $line (@detail_items) {
++ $OUT .= '\rule{0pt}{2.5ex}' . $line->{'ref'} . ' &' . "\n".
++ '\begin{tabular}{l}' . "\n".
++ $line->{'description'} . '\tabularnewline' . "\n".
++ '\end{tabular}' . "\n".
++ '& \dollar ' . $line->{'amount'} . '\\\\\\hline' . "\n";
++ }
++
++--@]
+ \end{longtable}
+ \vfill
+-$notes
++[@-- $notes --@]
+ \end{document}
diff --git a/conf/invoice_latex_statement b/conf/invoice_latex_statement
new file mode 100644
index 0000000..302306a
--- /dev/null
+++ b/conf/invoice_latex_statement
@@ -0,0 +1,244 @@
+%% 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 Kristian Hoffman
+%%
+%% 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 forked for Freeside; checked into CVS
+%%
+
+\documentclass[letterpaper]{article}
+
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0cm} % top margin to top of header
+\addtolength{\hoffset}{-0.6cm} % left margin on page
+\addtolength{\topmargin}{-1.25cm} % top margin to top of header
+\setlength{\headheight}{2.0cm} % height of header
+\setlength{\headsep}{1.0cm} % between header and text
+\setlength{\footskip}{1.0cm} % bottom of footer from bottom of text
+
+%\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\textwidth}{19.5cm}
+\setlength{\textheight}{19.5cm}
+\setlength{\oddsidemargin}{-0.9cm} % odd page left margin
+\setlength{\evensidemargin}{-0.9cm} % even page left margin
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{1pt}
+
+% Adjust the inset of the mailing address
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
+% Adjust the inset of the return address and logo
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
+% New command for address lines i.e. skip them if blank
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}
+
+% Inserts dollar symbol
+\newcommand{\dollar}[1][]{\symbol{36}}
+
+% Remove plain style header/footer
+\fancypagestyle{plain}{
+ \fancyhead{}
+}
+\fancyhf{}
+
+% Define fancy header/footer for first and subsequent pages
+\fancyfoot[C]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \small{
+[@-- $footer --@]
+ }
+ }
+ { % ... pages
+ \small{
+[@-- $smallfooter --@]
+ }
+ }
+}
+
+\fancyfoot[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ }
+ { % ... pages
+ \small{\thepage\ of \pageref{LastPage}}
+ }
+}
+
+\fancyhead[L]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \returninset
+ \makebox{
+ \begin{tabular}{ll}
+ \includegraphics{[@-- $conf_dir --@]/logo.eps} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+ \end{tabular}
+ }
+ }
+ { % ... pages
+ %\includegraphics{[@-- $conf_dir --@]/logo.eps} % Uncomment if you want the logo on all pages.
+ }
+}
+
+\fancyhead[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \begin{tabular}{rcl}
+ Invoice date & & Invoice number \\
+ \vspace{0.2cm}
+ \textbf{[@-- $date --@]} & & \textbf{[@-- $invnum --@]} \\\hline
+ \rule{0pt}{5ex} &~~ \huge{\textsc{Statement}} & \\
+ \vspace{-0.2cm}
+ & & \\\hline
+ \end{tabular}
+ }
+ { % ... pages
+ \small{
+ \begin{tabular}{ll}
+ Invoice date & Invoice number\\
+ \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]}\\
+ \end{tabular}
+ }
+ }
+}
+
+\pagestyle{fancy}
+
+
+%% Font options are:
+%% bch Bitsream Charter
+%% put Utopia
+%% phv Adobe Helvetica
+%% pnc New Century Schoolbook
+%% ptm Times
+%% pcr Courier
+
+\renewcommand{\familydefault}{phv}
+
+
+% Commands for freeside description...
+\newcommand{\FSdesc}[3]{
+ \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &
+ \textbf{#2} &
+ \multicolumn{1}{r}{\textbf{\dollar #3}}\\
+}
+% ...extended description...
+\newcommand{\FSextdesc}[1]{
+ \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
+ \multicolumn{2}{l}{\small{~-~#1}}\\
+}
+% ...and total line items.
+\newcommand{\FStotaldesc}[2]{
+ & \multicolumn{1}{l}{#1} & #2\\
+}
+
+
+\begin{document}
+%
+%% Headers and footers defined for the first page
+%
+%% The LH Heading comprising logo
+%% UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo
+%
+%% The Heading comprising isue date, customer ref & INVOICE name
+%
+%% Header & footer changes for subsequent pages
+%
+%
+%
+\begin{tabular}{ll}
+\addressinset \rule{0cm}{0cm} &
+\makebox{
+\begin{minipage}[t]{5.0cm}
+\vspace{0.25cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}
+\end{tabular}
+\hfill
+\makebox{
+\begin{minipage}[t]{6.4cm}
+\begin{flushright}
+Terms: [@-- $terms --@]\\
+[@-- $po_line --@]\\
+\end{flushright}
+\end{minipage}}
+\vspace{1.5cm}
+%
+\section*{\textsc{Charges}}
+\begin{longtable}{clr}
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[12.8cm][l]{\textbf{Description}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endfirsthead
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[12.8cm][l]{\textbf{Description}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endhead
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\
+\endfoot
+\hline
+[@--
+
+ foreach my $line (@total_items) {
+ $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
+ '{' . $line->{'total_amount'} . '}' . "\n";
+ }
+
+--@]
+\hline
+\endlastfoot
+[@--
+
+ foreach my $line (@detail_items) {
+ my $ext_description = $line->{'ext_description'};
+
+ # Don't break-up small packages.
+ my $rowbreak = @$ext_description < 5 ? '*' : '';
+
+ $OUT .= "\\hline\n";
+ $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' .
+ '{' . $line->{'amount'} . "}${rowbreak}\n";
+
+ foreach my $ext_desc (@$ext_description) {
+ $ext_desc = substr($ext_desc, 0, 80) . '...'
+ if (length($ext_desc) > 80);
+ $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";
+ }
+
+ }
+
+--@]
+\end{longtable}
+\vfill
+[@-- $notes --@]
+\end{document}
diff --git a/conf/invoice_latexcoupon b/conf/invoice_latexcoupon
new file mode 100644
index 0000000..327c121
--- /dev/null
+++ b/conf/invoice_latexcoupon
@@ -0,0 +1,36 @@
+Detach and return this remittance form with your your payment.\\
+\begin{tabular}{ll}
+\begin{tabular}{ll}
+\returninset
+\begin{tabular}{ll}
+ \makebox{ \includegraphics{[@-- $logo_file --@]}} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+\end{tabular}&
+\begin{tabular}{r@{: }lr}
+Invoice date & \textbf{[@-- $date --@]} & \multirow{4}*{
+\makebox{
+\begin{minipage}[t]{7.0cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}}\\
+Customer\#& \textbf{[@-- $custnum --@]} & \\
+Total Due & \textbf{[@-- $balance --@]} & \\
+\rule{0pt}{2.25em}Amount Enclosed & \rule{2cm}{1pt}& \\
+\end{tabular}\\
+\rule{0pt}{1cm} &\\
+\end{tabular}\\
+\begin{tabular}{ll}
+\addressinset \rule{0.5cm}{0cm} &
+\makebox{
+\begin{minipage}[t]{7.0cm}
+[@-- $returnaddress --@]
+\end{minipage}}
+\hfill
+\end{tabular}\\
+\end{tabular}\\
diff --git a/conf/invoice_latexfooter b/conf/invoice_latexfooter
new file mode 100644
index 0000000..2e32123
--- /dev/null
+++ b/conf/invoice_latexfooter
@@ -0,0 +1 @@
+[@-- $company_name --@]
diff --git a/conf/invoice_latexnotes b/conf/invoice_latexnotes
new file mode 100644
index 0000000..5303d3c
--- /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{[@-- $company_name --@]}.
+\item If you have any questions please email or telephone.
+\end{enumerate}
diff --git a/conf/invoice_latexnotes_statement b/conf/invoice_latexnotes_statement
new file mode 100644
index 0000000..0836d27
--- /dev/null
+++ b/conf/invoice_latexnotes_statement
@@ -0,0 +1,8 @@
+%%
+%% Add any customer specific notes in here
+%%
+\section*{\textsc{Notes}}
+\begin{enumerate}
+\item This statement reflects current charges and payments.
+\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..2e32123
--- /dev/null
+++ b/conf/invoice_latexsmallfooter
@@ -0,0 +1 @@
+[@-- $company_name --@]
diff --git a/conf/invoice_latexsummary b/conf/invoice_latexsummary
new file mode 100644
index 0000000..a181ee4
--- /dev/null
+++ b/conf/invoice_latexsummary
@@ -0,0 +1,45 @@
+\begin{tabular}{ll}
+\begin{minipage}{6.4cm}
+\begin{tabular}{m{0cm}m{6.4cm}}
+\rule{0cm}{10cm}&\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\
+\end{tabular}
+\end{minipage} &
+\rule{2cm}{0cm}
+\begin{minipage}{12.8cm}
+\begin{tabular}{lr}
+\hline
+&\\
+\textbf{\underline{Summary of Previous Balance and Payments}} & \\
+&\\
+\textbf{Previous Balance}&\textbf{\dollar[@-- $true_previous_balance --@]}\\
+\textbf{Payments}&\textbf{\dollar[@-- $balance_adjustments --@]}\\
+\cline{2-2}
+\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\
+&\\
+\hline
+&\\
+\textbf{\underline{Summary of New Charges}} & \\
+&\\
+[@--
+ foreach my $section ( grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} @sections ) {
+ $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';
+ $OUT .= '&\textbf{'. $section->{'subtotal'}. '}\\\\';
+ }
+ $OUT .= '\cline{2-2}';
+--@]
+\textbf{New Charges Total}&\textbf{\dollar[@-- $current_less_finance --@]}\\
+&\\
+\hline
+&\\
+\textbf{\underline{Invoice Summary}} & \\
+& \\
+\textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\
+\textbf{Finance charges on overdue amount}&\textbf{\dollar[@-- $finance_amount --@]}\\
+\textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\
+\cline{2-2}
+\textbf{Total Amount Due}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance + $current_charges - $balance_adjustments) --@]}\\
+&\\
+\hline
+\end{tabular}
+\end{minipage} \\
+\end{tabular}
diff --git a/conf/invoice_template b/conf/invoice_template
new file mode 100644
index 0000000..ebf8ef7
--- /dev/null
+++ b/conf/invoice_template
@@ -0,0 +1,26 @@
+
+ { $notice_name || 'Invoice'; }
+ { substr("Page $page of $total_pages ", 0, 19); } { use Date::Format; time2str("%x", $date); } Invoice #{ $invnum; }
+
+
+{ $company_name; }
+{ $company_address; }
+
+
+{ $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)
+ );
+}
+
+ -=> { $company_name; } <=-
diff --git a/conf/invoice_template_statement b/conf/invoice_template_statement
new file mode 100644
index 0000000..db02915
--- /dev/null
+++ b/conf/invoice_template_statement
@@ -0,0 +1,26 @@
+
+ Statement
+ { substr("Page $page of $total_pages ", 0, 19); } { use Date::Format; time2str("%x", $date); } Invoice #{ $invnum; }
+
+
+{ $company_name; }
+{ $company_address; }
+
+
+{ $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)
+ );
+}
+
+ -=> { $company_name; } <=-
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..ff25dd4
--- /dev/null
+++ b/conf/logo.eps
@@ -0,0 +1,13510 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%HiResBoundingBox: 261.500000 345.500000 418.500000 446.500000
+%%Creator: xpdf/pdftops 3.00
+%%LanguageLevel: 2
+%%DocumentMedia: plain 612 792 0 () ()
+%%BoundingBox: 19 0 70 33
+%%EndComments
+%%BeginProcSet: epsffit 1 0
+gsave
+-65.000 -111.618 translate
+0.324 0.324 scale
+%%EndProcSet
+
+% 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
+grestore
diff --git a/conf/logo.png b/conf/logo.png
new file mode 100644
index 0000000..1e415e6
--- /dev/null
+++ b/conf/logo.png
Binary files differ
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/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/rt/html/NoAuth/js/scriptaculous/prototype.js b/conf/show-msgcat-codes
index e69de29..e69de29 100644
--- a/rt/html/NoAuth/js/scriptaculous/prototype.js
+++ 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/conf/ticket_system b/conf/ticket_system
new file mode 100644
index 0000000..631f98a
--- /dev/null
+++ b/conf/ticket_system
@@ -0,0 +1 @@
+RT_Internal
diff --git a/conf/ticket_system-default_queueid b/conf/ticket_system-default_queueid
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/conf/ticket_system-default_queueid
@@ -0,0 +1 @@
+1
diff --git a/conf/welcome_letter b/conf/welcome_letter
new file mode 100644
index 0000000..be7b484
--- /dev/null
+++ b/conf/welcome_letter
@@ -0,0 +1,121 @@
+%% file: random_latex
+%% Purpose: Multipage template for welcome letters
+%%
+%% Based on work by
+%%
+%% Mark Asplen-Taylor
+%% Asplen Management Ltd
+%% www.asplen.co.uk
+%%
+%% Kristian Hoffman
+%%
+%% Changes
+%% 0.1 6/19/07 Created
+
+\documentclass[letterpaper]{article}
+
+\hyphenpenalty=5000
+\usepackage{fancyhdr,lastpage,ifthen,afterpage}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0cm} % top margin to top of header
+\addtolength{\hoffset}{-0.6cm} % left margin on page
+\addtolength{\topmargin}{-1.25cm} % top margin to top of header
+\setlength{\headheight}{2.0cm} % height of header
+\setlength{\headsep}{1.0cm} % between header and text
+\setlength{\footskip}{1.0cm} % bottom of footer from bottom of text
+
+%\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\textwidth}{19.5cm}
+\setlength{\textheight}{19.5cm}
+\setlength{\oddsidemargin}{-0.9cm} % odd page left margin
+\setlength{\evensidemargin}{-0.9cm} % even page left margin
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{0pt}
+
+% Adjust the inset of the mailing address
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
+% Adjust the inset of the return address and logo
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
+% New command for address lines i.e. skip them if blank
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}
+
+% Remove plain style header/footer
+\fancypagestyle{plain}{
+ \fancyhead{}
+}
+\fancyhf{}
+
+% Define fancy header/footer for first and subsequent pages
+
+\fancyfoot[R]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ ~
+ }
+ { % ... pages
+ \small{\thepage\ of \pageref{LastPage}}
+ }
+}
+
+\fancyhead[L]{
+ \ifthenelse{\equal{\thepage}{1}}
+ { % First page
+ \returninset
+ \makebox{
+ \begin{tabular}{ll}
+ \includegraphics{[@-- $conf_dir --@]/logo.eps} &
+ \begin{minipage}[b]{5.5cm}
+[@-- $returnaddress --@]
+ \end{minipage}
+ \end{tabular}
+ }
+ }
+ { % ... pages
+ %\includegraphics{[@-- $conf_dir --@]/logo.eps} % Uncomment if you want the logo on all pages.
+ }
+}
+
+\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}
+%
+\begin{tabular}{ll}
+\addressinset \rule{0cm}{0cm} &
+\makebox{
+\begin{minipage}[t]{5.0cm}
+\vspace{0.25cm}
+\textbf{[@-- $payname --@]}\\
+\addressline{[@-- $company --@]}
+\addressline{[@-- $address1 --@]}
+\addressline{[@-- $address2 --@]}
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
+\addressline{[@-- $country --@]}
+\end{minipage}}
+\end{tabular}
+\vspace{1.5cm}
+\\
+% Your content goes here
+Dear [@-- $first --@] [@-- $last --@]:\\
+\\
+ Thank you for choosing [@-- $company_name --@]. We aim to meet all of your
+ needs. Please do not hesitate to contact us for any additional
+ services or assistance.\\
+
+\end{document}
+
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 0000000..829b543
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,25 @@
+Freeside for Debian
+-------------------
+
+1.
+Edit /etc/apache2/envvars or /etc/apache2/apache2.conf and set User and Group
+to freeside
+
+2.
+/etc/init.d/apache2 restart
+
+3.
+Create one or more Freeside users (your internal sales/tech folks, not customer accounts):
+$ su
+# su freeside
+$ freeside-adduser -g 1 desired_username
+$ htpasswd /etc/freeside/htpasswd username
+(enter password)
+
+4.
+Go to http://your.host.name/freeside and log in.
+
+Optional but recommended.
+(Hopefully) get an SSL certificate setup and change that to https://
+
+ -- Ivan Kohler <ivan-debian@420.am> Wed, 02 Apr 2008 01:46:20 -0700
diff --git a/debian/TODO b/debian/TODO
new file mode 100644
index 0000000..15fed69
--- /dev/null
+++ b/debian/TODO
@@ -0,0 +1,38 @@
+
+test) freeside-webui /etc/apache/conf.d/freeside.conf
+ AuthUserFile is wrong (just fucked)
+
+test its working) somes sort of Alias /freeside /usr/share/freeside/www is needed
+
+test in postinst) freeside package var/cache/freeside/cache.<datasrc is missing>
+
+test RT is missing. doh. get it working.
+
+test actually installing!
+
+--- rc2... right? ---
+
+freeside-selfservice-client doesn't install at all
+
+start freeside-sqlradius-radacctd from /etc/default/freeside too
+
+Added to README.Debian... do something else?
+Ensure apache is set to run as User freeside.
+
+init script doesn't need to add /usr/local/bin. could start over from
+init.d.ex or init.d.lsb.ex
+
+finish
+
+RT install locations (or for now: disable for unstable, enable for
+experiemental. but try to get it finished off in time for lenny)
+
+debian/copyright administrivia
+
+AGPL drama
+
+upload
+
+AGPL drama or silent waiting for days or years
+
+profit! err
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..b708740
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,26 @@
+freeside (2.1.0-1) UNRELEASED; urgency=low
+
+ * New upstream release
+ * New upstream release
+ * New upstream release
+
+ -- Ivan Kohler <ivan-debian@420.am> Tue, 25 May 2010 05:43:49 -0700
+
+freeside (1.9.1-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Ivan Kohler <ivan-debian@420.am> Sat, 10 Oct 2009 19:41:01 -0700
+
+freeside (1.9.1-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Ivan Kohler <ivan-debian@420.am> Sat, 10 Oct 2009 18:57:01 -0700
+
+freeside (1.9.0~cvs0-1) unstable; urgency=low
+
+ * Initial release
+
+ -- Ivan Kohler <ivan-debian@420.am> Wed, 02 Apr 2008 01:46:20 -0700
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7ed6ff8
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+5
diff --git a/debian/config b/debian/config
new file mode 100644
index 0000000..4ffa236
--- /dev/null
+++ b/debian/config
@@ -0,0 +1,19 @@
+#!/bin/sh
+# config script for freeside
+
+set -e
+
+# source debconf stuff
+. /usr/share/debconf/confmodule
+
+# source dbconfig-common shell library, and call the hook function
+if [ -f /usr/share/dbconfig-common/dpkg/config ]; then
+ # we support mysql and pgsql
+ dbc_dbtypes="pgsql, mysql"
+
+ # source dbconfig-common stuff
+ . /usr/share/dbconfig-common/dpkg/config
+ dbc_go freeside $@
+fi
+
+# ... rest of your code ...
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..4ea4815
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,59 @@
+Source: freeside
+Section: misc
+Priority: extra
+Maintainer: Ivan Kohler <ivan-debian@420.am>
+Build-Depends: debhelper (>= 5), perl (>= 5.8)
+Standards-Version: 3.7.2
+Homepage: http://www.freeside.biz/freeside
+Vcs-Browser: http://www.freeside.biz/cgi-bin/viewvc.cgi/freeside/
+Vcs-Cvs: :pserver:anonymous:anonymous@cvs.420.am:/home/cvs/cvsroot freeside
+
+Package: freeside
+Architecture: all
+Pre-Depends: freeside-lib, dbconfig-common
+Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends}, freeside-webui, debconf, adduser (>= 3.11)
+Recommends: cron
+Suggests: gnupg
+Description: Billing and trouble ticketing for service providers
+ Freeside is a web-based billing and trouble ticketing application. It
+ includes features for ISPs, hosting providers, and VoIP providers, but can
+ also be used as a generic customer database, invoicing and membership
+ application. If you like buzzwords, call it an "BSS/OSS and CRM solution".
+
+Package: freeside-lib
+Architecture: all
+Depends: ghostscript | gs-gpl, gsfonts, tetex-base, tetex-bin, libauthen-passphrase-perl, libbusiness-creditcard-perl, libcache-cache-perl, libcache-simple-timedexpiry-perl, libclass-returnvalue-perl, libcrypt-passwdmd5-perl, libdate-manip-perl, libdbd-pg-perl | libdbd-mysql-perl, libdbi-perl, libdbix-dbschema-perl (>= 0.35), libdbix-searchbuilder-perl, libdigest-sha1-perl, libfile-counterfile-perl, libfile-rsync-perl, libfrontier-rpc-perl, libhtml-format-perl, libhtml-tree-perl, libipc-run3-perl, libipc-sharelite-perl, liblingua-en-nameparse-perl, liblocale-maketext-fuzzy-perl, liblocale-maketext-lexicon-perl, liblocale-subcountry-perl, liblog-dispatch-perl, libmailtools-perl (>= 2), libmime-perl (>= 5.424) | libmime-perl (< 5.421), libnet-domain-tld-perl, libnet-scp-perl, libnet-ssh-perl, libnet-whois-raw-perl, libnetaddr-ip-perl, libnumber-format-perl, libregexp-common-perl, libstring-approx-perl, libstring-shellquote-perl, libterm-readkey-perl, libtest-inline-perl, libtext-autoformat-perl, libtext-csv-perl, libtext-template-perl, libtext-wrapper-perl, libtie-ixhash-perl, libtime-duration-perl, libtime-modules-perl, libtimedate-perl, libuniversal-require-perl, liburi-perl, libwant-perl, libwww-perl, libemail-sender-perl, libemail-sender-transport-smtp-tls-perl
+Recommends: libdbd-pg-perl, libdbd-mysql-perl, rsync
+Suggests: libbusiness-onlinepayment-perl
+Description: Libraries for Freeside billing and trouble ticketing
+ Freeside is a web-based billing and trouble ticketing application.
+ .
+ This package provides the perl libraries and command line utilities.
+
+#Package: freeside-bin
+#Architecture: all
+#Depends: freeside-lib
+#Description: Command line tools for Freeside billing and trouble ticketing
+# Freeside is a web-based billing and trouble ticketing application.
+# .
+# This package provides the command-line utilities.
+
+Package: freeside-webui
+Architecture: all
+Depends: freeside-lib, apache2, libapache2-mod-perl2, libapache2-request-perl, libapache-session-perl, libchart-perl, libcolor-scheme-perl, libdatetime-perl, libdatetime-format-strptime-perl, libgd-gd2-noxpm-perl | libgd-gd2-perl, libgd-graph-perl, libhtml-mason-perl, libhtml-scrubber-perl, libhtml-widgets-selectlayers-perl, libio-stringy-perl, libjson-perl, liblingua-en-inflect-perl, libmodule-versions-report-perl, libspreadsheet-writeexcel-perl, libtree-simple-perl, libyaml-perl
+Recommends: libapache-dbi-perl
+Description: Web interface for Freeside billing and trouble ticketing
+ Freeside is a web-based billing and trouble ticketing application.
+ .
+ This package provides the web interface for employees.
+
+#Package: freeside-selfservice-client
+#Architecture: all
+#Description: End-customer interface to Freeside billing and trouble ticketing
+# Freeside is a web-based billing and trouble ticketing application.
+# .
+# This package provides customer signup and self-service web interfaces and
+# XML-RPC, PHP and Perl APIs.
+# .
+# In production use, this package is typically installed on a public web server,
+# separate from the rest of the freeside-* packages.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..c409cb9
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,45 @@
+This package was debianized by Ivan Kohler <ivan-debian@420.am> on
+Wed, 02 Apr 2008 01:46:20 -0700.
+
+It was downloaded from <http://www.freeside.biz/freeside>
+
+Upstream Author(s):
+
+ Freeside Internet Services, Inc.
+
+Copyright:
+
+Copyright (C) 2005-2008 Freeside Internet Services, Inc.
+Copyright (C) 2000-2005 Ivan Kohler
+Copyright (C) 1999 Silicon Interactive Software Design
+All rights reserved
+
+before uploading to debian proper: <likewise for all other copyrights from httemplate/docs/license.html>
+
+License:
+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this package; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+On Debian systems, the complete text of the GNU Affero General
+Public License may someday be found in `/usr/share/common-licenses/AGPL'.
+Until then, you can find it in `/usr/share/doc/freeside/AGPL'.
+
+The Debian packaging is (C) 2008, Ivan Kohler <ivan-debian@420.am> and
+is licensed under the AGPL, see above.
+
+before uploading to debian proper, from httemplate/docs/license.html:
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
+
diff --git a/debian/cron.d b/debian/cron.d
new file mode 100644
index 0000000..f86db1b
--- /dev/null
+++ b/debian/cron.d
@@ -0,0 +1,4 @@
+#
+# Regular cron jobs for the freeside package
+#
+0 0 * * * freeside /usr/bin/freeside-daily fs_daily
diff --git a/debian/dbconfig-common.install b/debian/dbconfig-common.install
new file mode 100644
index 0000000..31b5d14
--- /dev/null
+++ b/debian/dbconfig-common.install
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+. /etc/dbconfig-common/freeside.conf
+
+DB_USER=$dbc_dbuser
+DB_PASSWORD=$dbc_dbpass
+
+# -- can't find a better place to hook this in. dammit.
+
+[ "$dbc_dbtype" = "pgsql" ] && DB_TYPE=Pg
+[ "$dbc_dbtype" = "mysql" ] && DB_TYPE=mysql
+#XXX ask dbc about a remote database etc.
+DATASOURCE=DBI:${DB_TYPE}:dbname=${dbc_dbname}
+
+#debian/rules
+FREESIDE_CONF=/etc/freeside
+FREESIDE_CACHE=/var/cache/freeside
+#XXX huh?
+FREESIDE_EXPORT=/var/spool/freeside
+DEFAULT_CONF=/usr/share/freeside/default_conf
+
+#XXX this rather seriously needs proper debian-style config file handling.
+
+#shamelessly lifted from Makefile create-config target
+[ -e ${FREESIDE_CONF} ] || install -d -o freeside ${FREESIDE_CONF}
+
+touch ${FREESIDE_CONF}/secrets
+chown freeside ${FREESIDE_CONF}/secrets
+chmod 600 ${FREESIDE_CONF}/secrets
+
+[ -s ${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
+
+#XXX yuck! this too!
+[ -e /var/opt/freeside/rt/etc/RT_Config.pm.dbc ] || cp /var/opt/freeside/rt/etc/RT_Config.pm.dbc.generic /var/opt/freeside/rt/etc/RT_Config.pm.dbc
+perl -pi.generic -e "s/^\\s*Set\\s*\\(\s*\\\$DatabaseType.*\$/Set(\\\$DatabaseType, '$DB_TYPE');/" /var/opt/freeside/rt/etc/RT_Config.pm.dbc
+mv /var/opt/freeside/rt/etc/RT_Config.pm.dbc /var/opt/freeside/rt/etc/RT_Config.pm
+perl -pi -e "\
+ s'_DBC_DBUSER_'${dbc_dbuser}'g;\
+ s'_DBC_DBPASS_'${dbc_dbpass}'g;\
+ s'_DBC_DBNAME_'${dbc_dbname}'g;\
+" /var/opt/freeside/rt/etc/RT_Config.pm
+
+#dunno how to hook this in where i need it...
+#dbc_generate_include="template:/var/opt/freeside/rt/etc/RT_Config.pm"
+#dbc_generate_include_args="-o template_infile=/var/opt/freeside/rt/etc/RT_Config.pm.dbc"
+
+install -o freeside -d "${FREESIDE_CACHE}/counters.${DATASOURCE}"
+install -o freeside -d "${FREESIDE_CACHE}/cache.${DATASOURCE}"
+install -o freeside -d "${FREESIDE_EXPORT}/export.${DATASOURCE}"
+
+if [ ! -d "${FREESIDE_CONF}/conf.${DATASOURCE}" ] ; then #don't clobber conf
+install -o freeside -d "${FREESIDE_CONF}/conf.${DATASOURCE}"
+#cp conf/[a-z]* "${FREESIDE_CONF}/conf.${DATASOURCE}"
+cp -i `ls -d ${DEFAULT_CONF}/[a-z]* | grep -v CVS` "${FREESIDE_CONF}/conf.${DATASOURCE}" #-i just in case
+chown -R freeside "${FREESIDE_CONF}/conf.${DATASOURCE}"
+fi
+
+# -- back to your regularly schedule program... go ahead, create the db
+
+DOMAIN=`dnsdomainname`
+if [ "$DOMAIN" = "localdomain" ]; then #freeside needs a valid domain
+ DOMAIN='example.com'
+fi
+
+# XXX this should probably be handled by the _install_...
+# dpkg-statoverride or something
+chown freeside /etc/freeside
+
+su freeside -c "/usr/bin/freeside-setup -d $DOMAIN"
+su freeside -c '/usr/bin/freeside-adduser -g 1 fs_queue'
+su freeside -c '/usr/bin/freeside-adduser -g 1 fs_daily'
+su freeside -c '/usr/bin/freeside-adduser -g 1 fs_selfservice'
+su freeside -c '/usr/bin/freeside-adduser -g 1 fs_upgrade'
+
+#RT paths are bunk for deb proper
+
+chown freeside /var/opt/freeside/rt/etc/RT_Config.pm
+
+su freeside -c "/var/opt/freeside/rt/sbin/rt-setup-database --dba '$DB_USER' --dba-password '$DB_PASSWORD' --action schema"
+
+su freeside -c '/var/opt/freeside/rt/sbin/rt-setup-database --action insert_initial'
+
+su freeside -c '/var/opt/freeside/rt/sbin/rt-setup-database --action insert --datafile /var/opt/freeside/rt/etc/initialdata'
+
+#XXX this totally doesn't belong here, but what the hey
+chown -R freeside /var/cache/freeside/masondata
+
+exit 0
diff --git a/debian/dbconfig-common.upgrade b/debian/dbconfig-common.upgrade
new file mode 100644
index 0000000..cae9adb
--- /dev/null
+++ b/debian/dbconfig-common.upgrade
@@ -0,0 +1,3 @@
+#!/bin/sh
+su freeside -c '/usr/bin/freeside-upgrade fs_upgrade'
+#RT upgrade
diff --git a/debian/freeside-webui.links b/debian/freeside-webui.links
new file mode 100644
index 0000000..7ca4030
--- /dev/null
+++ b/debian/freeside-webui.links
@@ -0,0 +1,4 @@
+etc/freeside/apache2/freeside-alias.conf etc/apache2/conf.d/freeside-alias.conf
+etc/freeside/apache2/freeside-base2.conf etc/apache2/conf.d/freeside-base2.conf
+etc/freeside/apache2/freeside-rt.conf etc/apache2/conf.d/freeside-rt.conf
+
diff --git a/debian/freeside.apache-alias.conf b/debian/freeside.apache-alias.conf
new file mode 100644
index 0000000..fdd4340
--- /dev/null
+++ b/debian/freeside.apache-alias.conf
@@ -0,0 +1 @@
+Alias /freeside/ /usr/share/freeside/www/
diff --git a/debian/freeside.default b/debian/freeside.default
new file mode 100644
index 0000000..eca0306
--- /dev/null
+++ b/debian/freeside.default
@@ -0,0 +1,12 @@
+# Defaults for freeside initscript
+# sourced by /etc/init.d/freeside
+# installed at /etc/default/freeside by the maintainer scripts
+
+#
+# This is a POSIX shell fragment
+#
+
+# Additional options that are passed to the Freeside startup scripts.
+SELFSERVICE_MACHINES=""
+
+#start freeside-sqlradius-radacctd from here too, etc.
diff --git a/debian/freeside.docs b/debian/freeside.docs
new file mode 100644
index 0000000..e845566
--- /dev/null
+++ b/debian/freeside.docs
@@ -0,0 +1 @@
+README
diff --git a/debian/init.d.ex b/debian/init.d.ex
new file mode 100644
index 0000000..2480f51
--- /dev/null
+++ b/debian/init.d.ex
@@ -0,0 +1,157 @@
+#! /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
+# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+# Further changes by Javier Fernandez-Sanguino <jfs@debian.org>
+#
+# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl
+#
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/freeside
+NAME=freeside
+DESC=freeside
+
+test -x $DAEMON || exit 0
+
+LOGDIR=/var/log/freeside
+PIDFILE=/var/run/$NAME.pid
+DODTIME=1 # Time to wait for the server to die, in seconds
+ # If this value is set too low you might not
+ # let some servers to die gracefully and
+ # 'restart' will not work
+
+# Include freeside defaults if available
+if [ -f /etc/default/freeside ] ; then
+ . /etc/default/freeside
+fi
+
+set -e
+
+running_pid()
+{
+ # Check if a given process pid's cmdline matches a given name
+ pid=$1
+ name=$2
+ [ -z "$pid" ] && return 1
+ [ ! -d /proc/$pid ] && return 1
+ cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
+ # Is this the expected child?
+ [ "$cmd" != "$name" ] && return 1
+ return 0
+}
+
+running()
+{
+# Check if the process is running looking at /proc
+# (works for all users)
+
+ # No pidfile, probably no daemon present
+ [ ! -f "$PIDFILE" ] && return 1
+ # Obtain the pid and check it against the binary name
+ pid=`cat $PIDFILE`
+ running_pid $pid $NAME || return 1
+ return 0
+}
+
+force_stop() {
+# Forcefully kill the process
+ [ ! -f "$PIDFILE" ] && return
+ if running ; then
+ kill -15 $pid
+ # Is it really dead?
+ [ -n "$DODTIME" ] && sleep "$DODTIME"s
+ if running ; then
+ kill -9 $pid
+ [ -n "$DODTIME" ] && sleep "$DODTIME"s
+ if running ; then
+ echo "Cannot kill $LABEL (pid=$pid)!"
+ exit 1
+ fi
+ fi
+ fi
+ rm -f $PIDFILE
+ return 0
+}
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ start-stop-daemon --start --quiet --pidfile $PIDFILE \
+ --exec $DAEMON -- $DAEMON_OPTS
+ if running then
+ echo "$NAME."
+ else
+ echo " ERROR."
+ fi
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ start-stop-daemon --stop --quiet --pidfile $PIDFILE \
+ --exec $DAEMON
+ echo "$NAME."
+ ;;
+ force-stop)
+ echo -n "Forcefully stopping $DESC: "
+ force_stop
+ if ! running then
+ echo "$NAME."
+ else
+ echo " ERROR."
+ fi
+ ;;
+ #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
+ #;;
+ 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" except that it does nothing if the
+ # daemon isn't already running.
+ # check wether $DAEMON is running. If so, restart
+ start-stop-daemon --stop --test --quiet --pidfile \
+ /var/run/$NAME.pid --exec $DAEMON \
+ && $0 restart \
+ || exit 0
+ ;;
+ restart)
+ echo -n "Restarting $DESC: "
+ start-stop-daemon --stop --quiet --pidfile \
+ /var/run/$NAME.pid --exec $DAEMON
+ [ -n "$DODTIME" ] && sleep $DODTIME
+ start-stop-daemon --start --quiet --pidfile \
+ /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
+ echo "$NAME."
+ ;;
+ status)
+ echo -n "$LABEL is "
+ if running ; then
+ echo "running"
+ else
+ echo " not running."
+ exit 1
+ fi
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/init.d.lsb.ex b/debian/init.d.lsb.ex
new file mode 100644
index 0000000..1223129
--- /dev/null
+++ b/debian/init.d.lsb.ex
@@ -0,0 +1,281 @@
+#!/bin/sh
+#
+# Example init.d script with LSB support.
+#
+# Please read this init.d carefully and modify the sections to
+# adjust it to the program you want to run.
+#
+# Copyright (c) 2007 Javier Fernandez-Sanguino <jfs@debian.org>
+#
+# This is free software; you may 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,
+# or (at your option) any later version.
+#
+# This is distributed in the hope that it will be useful, but
+# WITHOUT 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 with
+# the Debian operating system, in /usr/share/common-licenses/GPL; if
+# not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+### BEGIN INIT INFO
+# Provides: freeside
+# Required-Start: $network $local_fs
+# Required-Stop:
+# Should-Start: $named
+# Should-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: <Enter a short description of the sortware>
+# Description: <Enter a long description of the software>
+# <...>
+# <...>
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+DAEMON=/usr/sbin/freeside # Introduce the server's location here
+NAME=#PACKAGE # Introduce the short server's name here
+DESC=#PACKAGE # Introduce a short description here
+LOGDIR=/var/log/freeside # Log directory to use
+
+PIDFILE=/var/run/$NAME.pid
+
+test -x $DAEMON || exit 0
+test -x $DAEMON_WRAPPER || exit 0
+
+. /lib/lsb/init-functions
+
+# Default options, these can be overriden by the information
+# at /etc/default/$NAME
+DAEMON_OPTS="" # Additional options given to the server
+
+DODTIME=10 # Time to wait for the server to die, in seconds
+ # If this value is set too low you might not
+ # let some servers to die gracefully and
+ # 'restart' will not work
+
+LOGFILE=$LOGDIR/$NAME.log # Server logfile
+#DAEMONUSER=freeside # Users to run the daemons as. If this value
+ # is set start-stop-daemon will chuid the server
+
+# Include defaults if available
+if [ -f /etc/default/$NAME ] ; then
+ . /etc/default/$NAME
+fi
+
+# Use this if you want the user to explicitly set 'RUN' in
+# /etc/default/
+#if [ "x$RUN" != "xyes" ] ; then
+# log_failure_msg "$NAME disabled, please adjust the configuration to your needs "
+# log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it."
+# exit 1
+#fi
+
+# Check that the user exists (if we set a user)
+# Does the user exist?
+if [ -n "$DAEMONUSER" ] ; then
+ if getent passwd | grep -q "^$DAEMONUSER:"; then
+ # Obtain the uid and gid
+ DAEMONUID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $3}'`
+ DAEMONGID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $4}'`
+ else
+ log_failure_msg "The user $DAEMONUSER, required to run $NAME does not exist."
+ exit 1
+ fi
+fi
+
+
+set -e
+
+running_pid() {
+# Check if a given process pid's cmdline matches a given name
+ pid=$1
+ name=$2
+ [ -z "$pid" ] && return 1
+ [ ! -d /proc/$pid ] && return 1
+ cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
+ # Is this the expected server
+ [ "$cmd" != "$name" ] && return 1
+ return 0
+}
+
+running() {
+# Check if the process is running looking at /proc
+# (works for all users)
+
+ # No pidfile, probably no daemon present
+ [ ! -f "$PIDFILE" ] && return 1
+ pid=`cat $PIDFILE`
+ running_pid $pid $DAEMON_WRAPPER || return 1
+ return 0
+}
+
+start_server() {
+# Start the process using the wrapper
+ if [ -z "$DAEMONUSER" ] ; then
+ start-stop-daemon --start --quiet --pidfile $PIDFILE \
+ --exec $DAEMON -- $DAEMON_OPTS
+ errcode=$?
+ else
+# if we are using a daemonuser then change the user id
+ start-stop-daemon --start --quiet --pidfile $PIDFILE \
+ --chuid $DAEMONUSER \
+ --exec $DAEMON -- $DAEMON_OPTS
+ errcode=$?
+ fi
+ return $errcode
+}
+
+stop_server() {
+# Stop the process using the wrapper
+ if [ -z "$DAEMONUSER" ] ; then
+ start-stop-daemon --stop --quiet --pidfile $PIDFILE \
+ --exec $DAEMON
+ errcode=$
+ else
+# if we are using a daemonuser then look for process that match
+ start-stop-daemon --stop --quiet --pidfile $PIDFILE \
+ --user $DAEMONUSER \
+ --exec $DAEMON
+ errcode=$
+ fi
+
+ return $errcode
+}
+
+reload_server() {
+ [ ! -f "$PIDFILE" ] && return 1
+ pid=`cat $PIDFILE` # This is the daemon's pid
+ # Send a SIGHUP
+ kill -1 $pid
+ return $?
+}
+
+force_stop() {
+# Force the process to die killing it manually
+ [ ! -e "$PIDFILE" ] && return
+ if running ; then
+ kill -15 $pid
+ # Is it really dead?
+ sleep "$DIETIME"s
+ if running ; then
+ kill -9 $pid
+ sleep "$DIETIME"s
+ if running ; then
+ echo "Cannot kill $NAME (pid=$pid)!"
+ exit 1
+ fi
+ fi
+ fi
+ rm -f $PIDFILE
+}
+
+
+case "$1" in
+ start)
+ log_daemon_msg "Starting $DESC " "$NAME"
+ # Check if it's running first
+ if running ; then
+ log_progress_msg "apparently already running"
+ log_end_msg 0
+ exit 0
+ fi
+ if start_server && running ; then
+ # It's ok, the server started and is running
+ log_end_msg 0
+ else
+ # Either we could not start it or it is not running
+ # after we did
+ # NOTE: Some servers might die some time after they start,
+ # this code does not try to detect this and might give
+ # a false positive (use 'status' for that)
+ log_end_msg 1
+ fi
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESC" "$NAME"
+ if running ; then
+ # Only stop the server if we see it running
+ stop_server
+ log_end_msg $?
+ else
+ # If it's not running don't do anything
+ log_progress_msg "apparently not running"
+ log_end_msg 0
+ exit 0
+ fi
+ ;;
+ force-stop)
+ # First try to stop gracefully the program
+ $0 stop
+ if running; then
+ # If it's still running try to kill it more forcefully
+ log_daemon_msg "Stopping (force) $DESC" "$NAME"
+ force_stop
+ log_end_msg $?
+ fi
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ stop_server
+ # Wait some sensible amount, some server need this
+ [ -n "$DIETIME" ] && sleep $DIETIME
+ start_server
+ running
+ log_end_msg $?
+ ;;
+ status)
+
+ log_daemon_msg "Checking status of $DESC" "$NAME"
+ if running ; then
+ log_progress_msg "running"
+ log_end_msg 0
+ else
+ log_progress_msg "apparently not running"
+ log_end_msg 1
+ exit 1
+ fi
+ ;;
+ # Use this if the daemon cannot reload
+ reload)
+ log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon"
+ log_warning_msg "cannot re-read the config file (use restart)."
+ ;;
+ # And this if it cann
+ #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.
+ #
+ # log_daemon_msg "Reloading $DESC configuration files" "$NAME"
+ # if running ; then
+ # reload_server
+ # if ! running ; then
+ # Process died after we tried to reload
+ # log_progress_msg "died on reload"
+ # log_end_msg 1
+ # exit 1
+ # fi
+ # else
+ # log_progress_msg "server is not running"
+ # log_end_msg 1
+ # exit 1
+ # fi
+ #;;
+
+ *)
+ N=/etc/init.d/$NAME
+ echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/postinst b/debian/postinst
new file mode 100644
index 0000000..5d04550
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,54 @@
+#!/bin/sh
+# postinst script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# source debconf stuff
+. /usr/share/debconf/confmodule
+
+# source dbconfig-common stuff
+. /usr/share/dbconfig-common/dpkg/postinst
+
+dbc_pgsql_createdb_encoding='sql_ascii'
+
+#echo "i should create the db here"
+dbc_go freeside $@
+#echo "db should be craeted now"
+
+# 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>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+
+ a2enmod perl
+
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
diff --git a/debian/postrm b/debian/postrm
new file mode 100644
index 0000000..c008445
--- /dev/null
+++ b/debian/postrm
@@ -0,0 +1,48 @@
+#!/bin/sh
+# postrm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# source debconf stuff
+. /usr/share/debconf/confmodule
+
+# source dbconfig-common stuff
+if [ -f /usr/share/dbconfig-common/dpkg/postrm ]; then
+ . /usr/share/dbconfig-common/dpkg/postrm
+ dbc_go freeside $@
+fi
+
+# 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' <overwriter>
+# <overwriter-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/preinst b/debian/preinst
new file mode 100644
index 0000000..50c89e1
--- /dev/null
+++ b/debian/preinst
@@ -0,0 +1,100 @@
+#!/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 http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+
+ # If the package has default file it could be sourced, so that
+ # the local admin can overwrite the defaults
+
+ [ -f "/etc/default/freeside" ] && . /etc/default/freeside
+
+ # Sane defaults:
+
+ [ -z "$FREESIDE_HOME" ] && FREESIDE_HOME=/home/freeside
+ [ -z "$FREESIDE_USER" ] && FREESIDE_USER=freeside
+ [ -z "$FREESIDE_NAME" ] && FREESIDE_NAME="Freeside"
+ [ -z "$FREESIDE_GROUP" ] && FREESIDE_GROUP=freeside
+
+ [ -z "$RT_GROUP" ] && RT_GROUP=rt
+
+ # Groups that the user will be added to, if undefined, then none.
+ ADDGROUP="rt"
+
+ # create user to avoid running server as root
+ # 1. create group if not existing
+ if ! getent group | grep -q "^$FREESIDE_GROUP:" -; then
+ echo -n "Adding group $FREESIDE_GROUP.."
+ addgroup --quiet --system $FREESIDE_GROUP 2>/dev/null ||true
+ echo "..done"
+ fi
+ if ! getent group | grep -q "^$RT_GROUP:" -; then
+ echo -n "Adding group $RT_GROUP.."
+ addgroup --quiet --system $RT_GROUP 2>/dev/null ||true
+ echo "..done"
+ fi
+ # 2. create homedir if not existing
+ test -d $FREESIDE_HOME || mkdir $FREESIDE_HOME
+ # 3. create user if not existing
+ if ! getent passwd | grep -q "^$FREESIDE_USER:" -; then
+ echo -n "Adding system user $FREESIDE_USER.."
+ adduser --quiet \
+ --system \
+ --ingroup $FREESIDE_GROUP \
+ --shell /bin/sh \
+ --no-create-home \
+ --disabled-password \
+ $FREESIDE_USER 2>/dev/null || true
+ echo "..done"
+ fi
+ # 4. adjust passwd entry
+ usermod -c "$FREESIDE_NAME" \
+ -d $FREESIDE_HOME \
+ -g $FREESIDE_GROUP \
+ $FREESIDE_USER
+ # 5. adjust file and directory permissions
+ if ! dpkg-statoverride --list $FREESIDE_HOME >/dev/null
+ then
+ chown -R $FREESIDE_USER:adm $FREESIDE_HOME
+ chmod u=rwx,g=rxs,o= $FREESIDE_HOME
+ fi
+ # 6. Add the user to the ADDGROUP group
+ if test -n $ADDGROUP
+ then
+ if ! groups $FREESIDE_USER | cut -d: -f2 | \
+ grep -qw $ADDGROUP -; then
+ adduser $FREESIDE_USER $ADDGROUP
+ fi
+ fi
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/prerm b/debian/prerm
new file mode 100644
index 0000000..4c17489
--- /dev/null
+++ b/debian/prerm
@@ -0,0 +1,46 @@
+#!/bin/sh
+# prerm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# source debconf stuff
+. /usr/share/debconf/confmodule
+# source dbconfig-common stuff
+. /usr/share/dbconfig-common/dpkg/prerm
+dbc_go freeside $@
+
+# 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 http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ remove|upgrade|deconfigure)
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+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..d37dfd1
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,230 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# If set to a true value then MakeMaker's prompt function will
+# always return the default without waiting for user input.
+#export PERL_MM_USE_DEFAULT=1
+
+PERL ?= /usr/bin/perl
+#PACKAGE = $(shell dh_listpackages)
+PACKAGE = freeside
+TMP = $(CURDIR)/debian/$(PACKAGE)
+DBC_SCRIPTS = $(TMP)/usr/share/dbconfig-common/scripts/freeside
+
+#this is gotten from dbconfig-common
+DB_TYPE = db_type_is_configured_during_pkg_install_by_dbconfig-common_not_at_build_time
+
+#no chance, it doesn't get backslash-interpolted now...
+#DEBVERSION = `head -1 debian/changelog | cut -d')' -f1 | cut -c11-`
+DEBVERSION = 1.7.3~rc2-1
+export VERSION = $(DEBVERSION) (Debian)
+
+export FREESIDE_CONF = /etc/freeside
+export FREESIDE_LOG = /var/log/freeside
+export FREESIDE_LOCK = /var/lock/freeside
+export FREESIDE_CACHE = $(TMP)/var/cache/freeside
+FREESIDE_CACHE = $(TMP)/var/cache/freeside
+
+#XXX huh?
+export FREESIDE_EXPORT = /var/spool/freeside
+
+#XXX own subdir?
+export MASON_HANDLER = $(TMP)-webui/usr/share/freeside/handler.pl
+
+export APACHE_VERSION = 2
+export FREESIDE_DOCUMENT_ROOT = $(TMP)-webui/usr/share/freeside/www
+export INIT_FILE = $(TMP).init
+export INIT_INSTALL = /bin/true
+export HTTPD_RESTART = /bin/true
+#export APACHE_CONF = $(TMP)-webui/etc/apache2/conf.d
+export APACHE_CONF = $(TMP)-webui/etc/freeside/apache2
+export FREESIDE_RESTART = /bin/true
+
+#XXX root?
+export INSTALLGROUP = adm
+
+export SELFSERVICE_MACHINES =
+
+#prompt ? XXX these are runtime, not buildtime :/
+export RT_DOMAIN = `dnsdomainname`
+export RT_TIMEZONE = `cat /etc/timezone`
+
+export HOSTNAME = `hostname -f`
+export FREESIDE_URL = http://$(HOSTNAME)/freeside/
+
+#specific to deb pkg, for purposes of saving off a permanent copy of default
+#config for postinst and that sort of thing
+export DIST_CONF = $(TMP)/usr/share/freeside/default_conf
+
+#XXX yuck. proper RT layout is entirely necessary
+#this seems to infect way to much of RT with the build location, requiring
+# a kludge to hack it out afterwords. look into using fakeroot (didn't
+# realize it would need to be explicit argh)
+# (but leaving it for now, otherwise can't get RT to put files where we need em)
+export RT_PATH = $(TMP)/var/opt/freeside/rt
+
+# This has to be exported to make some magic below work.
+export DH_OPTIONS
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+ touch configure-stamp
+
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ # Add commands to compile the package here
+
+ ( cd FS/ && $(PERL) Makefile.PL INSTALLDIRS=vendor )
+
+ $(MAKE) -e perl-modules
+
+ #TEST#
+
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ dh_clean build-stamp install-stamp
+
+ # Add here commands to clean up after the build process.
+ $(MAKE) -e clean
+ #|| true #XXX freeside clean target fucked
+
+ dh_clean
+
+install: install-stamp
+install-stamp: build-stamp
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install package into
+ # debian/<package>-whatever.
+ ( cd FS/ && $(MAKE) -e DESTDIR=$(TMP)-lib install )
+
+ #false laziness w/install-perl-modules now
+ #install this for postinst later (no create-config)
+ install -d $(DIST_CONF)
+ #install conf/[a-z]* $(DEFAULT_CONF)
+ #CVS is not [a-z]
+ install `ls -d conf/[a-z]* | grep -v CVS` $(DIST_CONF)
+
+ install -d $(FREESIDE_DOCUMENT_ROOT)
+ install -d $(FREESIDE_CACHE)/masondata #MASONDATA
+ $(MAKE) -e install-docs
+
+ #hack the build dir out of Freeside too. oh yeah, sucky.
+ perl -p -i -e "\
+ s'${TMP}(-webui)?''g;\
+ " ${TMP}-webui/usr/share/freeside/handler.pl \
+ ${TMP}/usr/share/perl5/FS/* \
+ ${TMP}/usr/share/perl5/FS/*/* \
+ ${TMP}/usr/bin/*
+
+ rm -r $(FREESIDE_DOCUMENT_ROOT).*
+
+ install -d $(APACHE_CONF)
+ install debian/freeside.apache-alias.conf $(APACHE_CONF)/freeside-alias.conf
+ FREESIDE_DOCUMENT_ROOT=/usr/share/freeside/www MASON_HANDLER=/usr/share/freeside/handler.pl FREESIDE_CONF=/etc/freeside $(MAKE) -e install-apache
+
+ $(MAKE) -e install-init
+
+ #RT
+ #(configure-rt)
+
+ # XXX need to adjust db-type, db-database, db-rt-user, db-rt-pass
+ # based on info from dbc
+ ( cd rt; \
+ cp config.layout.in config.layout; \
+ perl -p -i -e "\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g;\
+ s'%%%MASONDATA%%%'${FREESIDE_CACHE}/masondata'g;\
+ " config.layout; \
+ ./configure --prefix=${RT_PATH} \
+ --enable-layout=Freeside \
+ --with-db-type=Pg \
+ --with-db-dba=freeside \
+ --with-db-database=_DBC_DBNAME_ \
+ --with-db-rt-user=_DBC_DBUSER_ \
+ --with-db-rt-pass=_DBC_DBPASS_ \
+ --with-web-user=freeside \
+ --with-web-group=freeside \
+ --with-rt-group=freeside \
+ )
+
+ #(create-rt)
+ install -d $(RT_PATH)
+ ( cd rt; make install )
+ #hack the build dir out of RT. yeah, sucky.
+ perl -p -i -e "\
+ s'${TMP}''g;\
+ " ${RT_PATH}/etc/RT_Config.pm \
+ ${RT_PATH}/lib/RT.pm \
+ ${RT_PATH}/bin/mason_handler.fcgi \
+ ${RT_PATH}/bin/mason_handler.scgi \
+ ${RT_PATH}/bin/standalone_httpd \
+ ${RT_PATH}/bin/webmux.pl \
+ ${RT_PATH}/bin/rt-crontool \
+ ${RT_PATH}/sbin/rt-dump-database \
+ ${RT_PATH}/sbin/rt-setup-database
+
+ #hack @INC dir out of RT (well, handler.pl) too.
+ perl -p -i -e "\
+ s'/opt/rt3/'/var/opt/freeside/rt/'g;\
+ " ${TMP}-webui/usr/share/freeside/handler.pl
+
+ mv ${RT_PATH}/etc/RT_Config.pm ${RT_PATH}/etc/RT_Config.pm.dbc
+
+ perl -p -i -e "\
+ s'%%%RT_DOMAIN%%%'${RT_DOMAIN}'g;\
+ s'%%%RT_TIMEZONE%%%'${RT_TIMEZONE}'g;\
+ s'%%%FREESIDE_URL%%%'${FREESIDE_URL}'g;\
+ " ${RT_PATH}/etc/RT_SiteConfig.pm
+
+ install -D debian/dbconfig-common.install $(DBC_SCRIPTS)/install/pgsql
+ install -D debian/dbconfig-common.install $(DBC_SCRIPTS)/install/mysql
+
+ install -D debian/dbconfig-common.upgrade $(DBC_SCRIPTS)/upgrade/pgsql/$(DEBVERSION)
+ install -D debian/dbconfig-common.upgrade $(DBC_SCRIPTS)/upgrade/mysql/$(DEBVERSION)
+
+ dh_install
+
+ touch $@
+
+binary-arch:
+# We have nothing to do here for an architecture-independent package
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs ChangeLog
+ dh_installdocs #freeside.docs README AGPL
+ dh_installexamples eg/*
+# dh_installmenu
+ dh_installdebconf
+# dh_installlogrotate
+ dh_installinit
+ dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_perl
+ dh_link
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install
diff --git a/rt/html/NoAuth/js/scriptaculous/scriptaculous.js b/debian/templates
index e69de29..e69de29 100644
--- a/rt/html/NoAuth/js/scriptaculous/scriptaculous.js
+++ b/debian/templates
diff --git a/eg/TEMPLATE_cust_main.import b/eg/TEMPLATE_cust_main.import
new file mode 100755
index 0000000..f6d88c7
--- /dev/null
+++ b/eg/TEMPLATE_cust_main.import
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+#
+# Template for importing legacy customer data
+
+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/cdr_template.pm b/eg/cdr_template.pm
new file mode 100644
index 0000000..4e55c65
--- /dev/null
+++ b/eg/cdr_template.pm
@@ -0,0 +1,101 @@
+package FS::cdr::cdr_template;
+
+use strict;
+use base qw( FS::cdr );
+use vars qw( %info );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+ 'name' => 'Example CDR format',
+ 'weight' => 500,
+ 'header' => 0, #0 default, set to 1 to ignore the first line, or
+ # to higher numbers to ignore that number of lines
+ 'type' => 'csv', #csv (default), fixedlength or xls
+ 'sep_char' => ',', #for csv, defaults to ,
+ 'disabled' => 0, #0 default, set to 1 to disable
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+
+ #place data directly in the specified field
+ 'freeside_cdr_fieldname',
+
+ #subroutine reference
+ sub { my($cdr, $field_data) = @_;
+ #do something to $field_data
+ $cdr->fieldname($field_data);
+ },
+
+ #premade subref factory for date+time parsing, understands dates like:
+ # 10/31/2007 08:57:24
+ # 2007-10-31 08:57:24.113000000
+ # Mon Dec 15 11:38:34 2003
+ _cdr_date_parser_maker('startddate'), #for example
+
+ #premade subref factory for decimal minute parsing
+ _cdr_min_parser_maker, #defaults to billsec and duration
+ _cdr_min_parser_maker('fieldname'), #one field
+ _cdr_min_parser_maker(['billsec', 'duration']), #listref for multiple fields
+
+ ],
+
+ #Parse::FixedLength field descriptions & lengths, for type=>'fixedlength' only
+ 'fixedlength_format' => [qw(
+ Type:2:1:2
+ Sequence:4:3:6
+ )],
+
+);
+
+1;
+
+__END__
+
+list of freeside CDR fields, useful ones marked with *
+
+ acctid - primary key
+*[1] calldate - Call timestamp (SQL timestamp)
+ clid - Caller*ID with text
+* src - Caller*ID number / Source number
+* dst - Destination extension
+ dcontext - Destination context
+ channel - Channel used
+ dstchannel - Destination channel if appropriate
+ lastapp - Last application if appropriate
+ lastdata - Last application data
+* startdate - Start of call (UNIX-style integer timestamp)
+ answerdate - Answer time of call (UNIX-style integer timestamp)
+* enddate - End time of call (UNIX-style integer timestamp)
+* duration - Total time in system, in seconds
+* billsec - Total time call is up, in seconds
+*[2] disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+ amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+ channel basis like accountcode.
+*[3] accountcode - CDR account number to use: account
+ uniqueid - Unique channel identifier
+ userfield - CDR user-defined field
+ cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+*[4] charged_party - Service number to be billed
+ upstream_currency - Wholesale currency from upstream
+*[5] upstream_price - Wholesale price from upstream
+ upstream_rateplanid - Upstream rate plan ID
+ rated_price - Rated (or re-rated) price
+ distance - km (need units field?)
+ islocal - Local - 1, Non Local = 0
+*[6] calltypenum - Type of call - see FS::cdr_calltype
+ description - Description (cdr_type 7&8 only) (used for
+ cust_bill_pkg.itemdesc)
+ quantity - Number of items (cdr_type 7&8 only)
+*[7] carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+ upstream_rateid - Upstream Rate ID
+ svcnum - Link to customer service (see FS::cust_svc)
+ freesidestatus - NULL, done (or something)
+
+[1] Auto-populated from startdate if not present
+[2] Package options available to ignore calls without a specific disposition
+[3] When using 'cdr-charged_party-accountcode' config
+[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present
+[5] When using 'upstream_simple' rating method.
+[6] Set to usage class classnum when using pre-rated CDRs and usage class-based
+ taxation (local/intrastate/interstate/international)
+[7] If doing settlement charging
diff --git a/eg/export_template.pm b/eg/export_template.pm
new file mode 100644
index 0000000..22eb36a
--- /dev/null
+++ b/eg/export_template.pm
@@ -0,0 +1,113 @@
+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;
+}
+
+#these three are optional
+# fallback for svc_acct will change and restore password
+sub _export_suspend {
+ my( $self, $svc_something ) = (shift, shift);
+ $err_or_queue = $self->myexport_queue( $svc_something->svcnum,
+ 'suspend', $svc_something->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_something ) = (shift, shift);
+ $err_or_queue = $self->myexport_queue( $svc_something->svcnum,
+ 'unsuspend', $svc_something->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub export_links {
+ my($self, $svc_something, $arrayref) = (shift, shift, shift);
+ #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_something->username.
+ # qq!">!. $svc_something->username. qq!</A>!;
+ '';
+}
+
+###
+
+#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
+}
+
+sub myexport_suspend { #subroutine, not method
+}
+
+sub myexport_unsuspend { #subroutine, not method
+}
+
+
diff --git a/eg/part_event-Action-template.pm b/eg/part_event-Action-template.pm
new file mode 100644
index 0000000..c2f5ba5
--- /dev/null
+++ b/eg/part_event-Action-template.pm
@@ -0,0 +1,55 @@
+package FS::part_event::Action::myaction;
+
+use strict;
+
+use base qw( FS::part_event::Action );
+
+# see the FS::part_event::Action manpage for full documentation on each
+# of the required and optional methods.
+
+sub description {
+ 'New action (the author forgot to change this description)';
+}
+
+#sub eventtable_hashref {
+# { 'cust_main' => 1,
+# 'cust_bill' => 1,
+# 'cust_pkg' => 1,
+# };
+#}
+
+#sub option_fields {
+# (
+# 'field' => 'description',
+#
+# 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+#
+# 'third_field' => { 'label' => 'Types',
+# 'type' => 'select',
+# 'options' => [ 'h', 's' ],
+# 'option_labels' => { 'h' => 'Happy',
+# 's' => 'Sad',
+# },
+# );
+#}
+
+#sub default_weight {
+# 100;
+#}
+
+
+sub do_action {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $value_of_field = $self->option('field');
+
+ #do your action
+
+ #die "Error: $error";
+ return 'Null example action completed sucessfully.';
+
+}
+
+1;
diff --git a/eg/part_event-Condition-template.pm b/eg/part_event-Condition-template.pm
new file mode 100644
index 0000000..cc05843
--- /dev/null
+++ b/eg/part_event-Condition-template.pm
@@ -0,0 +1,57 @@
+package FS::part_event::Condition::mycondition;
+
+use strict;
+
+use base qw( FS::part_event::Condition );
+
+# see the FS::part_event::Condition manpage for full documentation on each
+# of the required and optional methods.
+
+sub description {
+ 'New condition (the author forgot to change this description)';
+}
+
+#sub eventtable_hashref {
+# { 'cust_main' => 1,
+# 'cust_bill' => 1,
+# 'cust_pkg' => 1,
+# 'cust_pay_batch' => 1,
+# };
+#}
+
+#sub option_fields {
+# (
+# 'field' => 'description',
+#
+# 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+#
+# 'third_field' => { 'label' => 'Types',
+# 'type' => 'checkbox-multiple',
+# 'options' => [ 'h', 's' ],
+# 'option_labels' => { 'h' => 'Happy',
+# 's' => 'Sad',
+# },
+# );
+#}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ my $value_of_field = $self->option('field');
+
+ my $time = $opt{'time'}; #use this instead of time or $^T
+
+ #test your condition
+ 1;
+
+}
+
+#sub condition_sql {
+# my( $class, $table ) = @_;
+# #...
+# 'true';
+#}
+
+1;
diff --git a/eg/table_template-svc.pm b/eg/table_template-svc.pm
new file mode 100644
index 0000000..7e10275
--- /dev/null
+++ b/eg/table_template-svc.pm
@@ -0,0 +1,212 @@
+package FS::svc_table;
+
+use strict;
+use base qw( FS::svc_Common );
+#use FS::Record qw( qsearch qsearchs );
+use FS::cust_svc;
+
+=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'; }
+
+sub table_info {
+ {
+ 'name' => 'Example',
+ 'name_plural' => 'Example services', #optional,
+ 'longname_plural' => 'Example services', #optional
+ 'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
+ 'display_weight' => 100,
+ 'cancel_weight' => 100,
+ 'fields' => {
+ 'field' => 'Description',
+ 'another_field' => {
+ 'label' => 'Description',
+ 'def_label' => 'Description for service definitions',
+ 'type' => 'text',
+ 'disable_default' => 1, #disable switches
+ 'disable_fixed' => 1, #
+ 'disable_inventory' => 1, #
+ },
+ 'foreign_key' => {
+ 'label' => 'Description',
+ 'def_label' => 'Description for service defs',
+ 'type' => 'select',
+ 'select_table' => 'foreign_table',
+ 'select_key' => 'key_field_in_table',
+ 'select_label' => 'label_field_in_table',
+ },
+
+ },
+ };
+}
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+
+=cut
+
+#or something more complicated if necessary
+sub search_sql {
+ my($class, $string) = @_;
+ $class->search_sql_field('search_field', $string);
+}
+
+=item label
+
+Returns a meaningful identifier for this example
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->label_field; #or something more complicated if necessary
+}
+
+=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..9c71b3a
--- /dev/null
+++ b/eg/table_template.pm
@@ -0,0 +1,116 @@
+package FS::table_name;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=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;
+
+ my $error =
+ $self->ut_numbern('primary_key')
+ || $self->ut_number('validate_other_fields')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=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/eg/xmlrpc-example.pl b/eg/xmlrpc-example.pl
new file mode 100755
index 0000000..7a2a0a6
--- /dev/null
+++ b/eg/xmlrpc-example.pl
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;
+
+my $server = new Frontier::Client (
+ url => 'http://user:pass@freesidehost/misc/xmlrpc.cgi',
+);
+
+#my $method = 'cust_main.smart_search';
+#my @args = (search => '1');
+
+my $method = 'Record.qsearch';
+my @args = (cust_main => { });
+
+my $result = $server->call($method, @args);
+
+if (ref($result) eq 'ARRAY') {
+ print "Result:\n";
+ print Dumper(@$result);
+}
+
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/areacodes.txt b/etc/areacodes.txt
new file mode 100644
index 0000000..e214db1
--- /dev/null
+++ b/etc/areacodes.txt
@@ -0,0 +1,353 @@
+201 NJ New Jersey (Hackensack, Jersey City, Hoboken and northeast New Jersey, overlays with 551)<Eastern Time Zone>
+202 DC District of Columbia (all of Washington, DC)<Eastern Time Zone>
+203 CT Connecticut (Bridgeport, New Haven, Waterbury and southwestern Connecticut, overlays with 475)<Eastern Time Zone>
+204 MB Manitoba (all of Manitoba)<Central Time Zone>
+205 AL Alabama (Birmingham, Tuscaloosa and central and western Alabama)<Central Time Zone>
+206 WA Washington (Seattle, Sea-Tac, Ballard, Madison, Capitol Hill and central Seattle neighborhoods)<Pacific Time Zone>
+207 ME Maine (all of Maine)<Eastern Time Zone>
+208 ID Idaho (all of Idaho)<Mountain & Pacific Time Zones>
+209 CA California (Stockton, Merced, Modesto, San Andreas and central California)<Pacific Time Zone>
+210 TX Texas (San Antonio area)<Central Time Zone>
+212 NY New York (New York City Manhattan area, overlays with 646 and 917)<Eastern Time Zone>
+213 CA California (Downtown Los Angeles area only)<Pacific Time Zone>
+214 TX Texas (Dallas area, overlays with 469 and 972)<Central Time Zone>
+215 PA Pennsylvania (Philadelphia area, overlays with 267 and 445)<Eastern Time Zone>
+216 OH Ohio (Cleveland area)<Eastern Time Zone>
+217 IL Illinois (Champaign, Decatur, Urbana, Springfield and central Illinois)<Central Time Zone>
+218 MN Minnesota (Duluth, Thief River Falls, Brainerd, International Falls and northern Minnesota)<Central Time Zone>
+219 IN Indiana (Gary, Valparaiso, Michigan City, Goodland, Fowler and northwestern Indiana)<Central Time Zone>
+224 IL Illinois (Waukegan, Des Plaines, northwest Chicago suburbs and northeastern Illinois, overlays with 847)<Central Time Zone>
+225 LA Louisiana (Baton Rouge, New Roads, White Castle and central eastern Louisiana)<Central Time Zone>
+226 ON Ontario (London, Windsor and southwestern Ontario, overlays with 519)<Eastern Time Zone>
+228 MS Mississippi (Gulfport, Pascagoula, Biloxi, Bay St Louis and southern Mississippi gulf coast)<Central Time Zone>
+229 GA Georgia (Albany, Valdosta, Bainbridge, Americus, Fitzgerald and southwestern Georgia)<Eastern Time Zone>
+231 MI Michigan (Traverse City, Ludington, Muskegon, Petoskey and northwestern Michigan)<Eastern Time Zone>
+234 OH Ohio (Youngstown, Warren, Akron, Canton and northeastern Ohio, overlays with 330)<Eastern Time Zone>
+239 FL Florida (Fort Meyers, Naples, Everglades and southwestern Florida)<Eastern Time Zone>
+240 MD Maryland (Hagerstown, Rockville, Cumberland and western Maryland, overlays with 301)<Eastern Time Zone>
+242 BAHAMAS (all of Bahamas)<Atlantic Time Zone (one hour later than Eastern)>
+246 BARBADOS (all of Barbados)<Atlantic Time Zone (one hour later than Eastern)>
+248 MI Michigan (Troy, Oakland County, Pontiac, Southfield, Rochester Hills and northwestern Detroit suburbs, overlays with 947)<Eastern Time Zone>
+250 BC British Columbia (all except Vancouver area)<Mountain & Pacific Time Zones>
+251 AL Alabama (Mobile, Jackson and southwestern Alabama)<Central Time Zone>
+252 NC North Carolina (Greenville, Kitty Hawk, Rocky Mount and northeastern North Carolina)<Eastern Time Zone>
+253 WA Washington (Tacoma, Auburn, Puyallup, Enumclaw, Spanaway and south Seattle suburbs)<Pacific Time Zone>
+254 TX Texas (Waco, Killeen, Belton, Stephenville and north central Texas)<Central Time Zone>
+256 AL Alabama (Florence, Huntsville, Gadsden, Anniston and northern and eastern Alabama)<Central Time Zone>
+260 IN Indiana (Fort Wayne, Decatur, Angola, Wabash and northeastern Indiana)<Eastern Time Zone>
+262 WI Wisconsin (Menomonee Falls, Waukesha, Racine and southeastern Wisconsin excluding Milwaukee area)<Central Time Zone>
+264 ANGUILLA (all of Anguilla)<Atlantic Time Zone (one hour later than Eastern)>
+267 PA Pennsylvania (Philadelphia area, overlays with 215 and 445)<Eastern Time Zone>
+268 ANTIGUA and BARBUDA (all of Antigua and Barbuda)<Atlantic Time Zone (one hour later than Eastern)>
+269 MI Michigan (Battle Creek, Benton Harbor, Allegan, Hastings, Kalamazoo, St Joseph, and southwestern Michigan)<Eastern Time Zone>
+270 KY Kentucky (Paducah, Bowling Green, Hopkinsville, Owensboro and western Kentucky)<Central Time Zone>
+276 VA Virginia (Abingdon, Wytheville, Martinsville, Bluefield and western Virginia)<Eastern Time Zone>
+281 TX Texas (Spring, Katy, Houston area, overlays with 713 and 832)<Central Time Zone>
+284 BRITISH VIRGIN ISLANDS (all of the British Virgin Islands)<Atlantic Time Zone (one hour later than Eastern)>
+289 ON Ontario (Hamilton, Toronto suburbs and central southeastern Ontario, overlays with 905)<Eastern Time Zone>
+301 MD Maryland (Hagerstown, Rockville, Cumberland and western Maryland, overlays with 240)<Eastern Time Zone>
+302 DE Delaware (all of Delaware)<Eastern Time Zone>
+303 CO Colorado (Boulder, Longmont, Aurora, Denver and central Colorado, overlays with 720)<Mountain Time Zone>
+304 WV West Virginia (all of West Virginia)<Eastern Time Zone>
+305 FL Florida (Miami, Homestead, Coral Gables, Key West and southeastern Florida, overlays with 786)<Eastern Time Zone>
+306 SK Saskatchewan (all of Saskatchewan)<Central & Mountain Time Zones>
+307 WY Wyoming (all of Wyoming)<Mountain Time Zone>
+308 NE Nebraska (North Platte, Scottsbluff, McCook, Grand Island and western Nebraska)<Central & Mountain Time Zones>
+309 IL Illinois (Peoria, Moline, Rock Island, Galesburg and central western Illinois)<Central Time Zone>
+310 CA California (Malibu, Torrance, Beverley Hills, Santa Monica, Catalina and western Los Angeles suburbs, overlays with 424)<Pacific Time Zone>
+311 NON-EMERGENCY ACCESS
+312 IL Illinois (downtown central Chicago area)<Central Time Zone>
+313 MI Michigan (Dearborn, Detroit and inner Detroit suburbs)<Eastern Time Zone>
+314 MO Missouri (St Louis, Florissant, Crestwood, Affton and surrounding suburbs)<Central Time Zone>
+315 NY New York (Watertown, Utica, Syracuse and north central New York)<Eastern Time Zone>
+316 KS Kansas ( Wichita, Augusta, El Dorado, Mulvane and the Wichita surrounding area)<Central Time Zone>
+317 IN Indiana (Indianapolis, Greenwood, Mooresville, Beech Grove and central Indiana)<Eastern Time Zone>
+318 LA Louisiana (Shreveport, Monroe, Alexandria, Fisher, Tallulah and northern Louisiana)<Central Time Zone>
+319 IA Iowa (Burlington, Iowa City, Cedar Rapids, Waterloo and east central and southeastern Iowa)<Central Time Zone>
+320 MN Minnesota (St Cloud, Morris, Hutchinson, Sandstone, Appleton and central Minnesota)<Central Time Zone>
+321 FL Florida (Orlando, Cocoa Beach, St Cloud and central eastern Florida, overlays with 407)<Eastern Time Zone>
+323 CA California (Florence and Los Angeles excluding downtown Los Angeles)<Pacific Time Zone>
+325 TX Texas (Abilene, San Angelo, Albany, Comanche, Snyder, Ozona and west central Texas)<Central Time Zone>
+330 OH Ohio (Youngstown, Warren, Akron, Canton and northeastern Ohio, overlays with 234)<Eastern Time Zone>
+331 IL Illinois (Aurora, Batavia, Geneva and western Chicago suburbs, overlays with 630)<Central Time Zone>
+334 AL Alabama ( Montgomery, Auburn, Dothan, Selma and southeastern Alabama)<Central Time Zone>
+336 NC North Carolina (Winston-Salem, Greensboro, North Wilkesboro and northwest North Carolina)<Eastern Time Zone>
+337 LA Louisiana (Leesville, Lake Charles, Lafayette, De Ridder and southwestern Louisiana)<Central Time Zone>
+339 MA Massachusetts (Saugus, Norwood and east central Massachusetts, overlays with 781)<Eastern Time Zone>
+340 US VIRGIN ISLANDS (all of the US Virgin Islands)<Atlantic Time Zone>
+345 CAYMAN ISLANDS (all of the Cayman Islands)<Eastern Time Zone>
+347 NY New York (Flushing, Jamaica, Brooklyn, Staten Island, Bronx and Queens, overlays with 718 and 917)<Eastern Time Zone>
+351 MA Massachusetts (Fitchburg, Peabody and northeastern Massachusetts, overlays with 978)<Eastern Time Zone>
+352 FL Florida (Gainesville, Ocala, Inverness, Dunnellon and central Florida)<Eastern Time Zone>
+360 WA Washington (Bellingham, Vancouver, Aberdeen, Olympia and western Washington except Seattle area)<Pacific Time Zone>
+361 TX Texas (Corpus Christi, Victoria, George West and southeastern Texas)<Central Time Zone>
+385 UT Utah (North and south of Salt Lake City including Ogden, Provo, Bountiful and Spanish Fork)<Mountain Time Zone>
+386 FL Florida (Daytona Beach, Lake City, Live Oak, Crescent City and northern and eastern Florida)<Eastern Time Zone>
+401 RI Rhode Island (all of Rhode Island)<Eastern Time Zone>
+402 NE Nebraska (Valentine, Lincoln, Norfolk, Omaha, Superior, Crofton and eastern Nebraska)<Central Time Zone>
+403 AB Alberta (Calgary, Banff, Red Deer, Medicine Hat, Lethbridge and southern Alberta)<Mountain Time Zone>
+404 GA Georgia (central Atlanta area, overlays with 470 and 678)<Eastern Time Zone>
+405 OK Oklahoma (Oklahoma City, Edmond, Norman, Shawnee, Chickasha and central Oklahoma)<Central Time Zone>
+406 MT Montana (all of Montana)<Mountain Time Zone>
+407 FL Florida (Orlando, Cocoa Beach, St Cloud and central eastern Florida, overlays with 321)<Eastern Time Zone>
+408 CA California (Los Gatos, Milpitas, Sunnyvale, Cupertino and San Jose area)<Pacific Time Zone>
+409 TX Texas (Beaumont, Galveston, Port Arthur, Jasper and southeastern Texas)<Central Time Zone>
+410 MD Maryland (Annapolis, Baltimore, Salisbury and eastern Maryland, overlays with 443)<Eastern Time Zone>
+411 LOCAL DIRECTORY ASSISTANCE
+412 PA Pennsylvania (Pittsburgh area, McKeesport, Braddock, Duquesne, overlays with 878)<Eastern Time Zone>
+413 MA Massachusetts (Pittsfield, Springfield, Holyoke, Greenfield and western Massachusetts)<Eastern Time Zone>
+414 WI Wisconsin (Milwaukee, Greenfield, Oak Creek and Milwaukee suburbs)<Central Time Zone>
+415 CA California (Sausalito, San Rafael, Novato, San Quentin, San Francisco and bay area)<Pacific Time Zone>
+416 ON Ontario (Toronto area, overlays with 647)<Eastern Time Zone>
+417 MO Missouri (Joplin, Springfield, West Plains, Lamar, Lebanon and southwestern Missouri)<Central Time Zone>
+418 QC Quebec (Quebec and eastern Quebec)<Eastern Time Zone>
+419 OH Ohio (Toledo, Mansfield, Lima, Bryan, Sandusky, Bowling Green and northwestern Ohio, overlays with 567)<Eastern Time Zone>
+423 TN Tennessee (Bristol, Sweetwater, Chattanooga, and southeastern and north eastern Tennessee)<Central & Eastern Time Zones>
+424 CA California (Malibu, Torrance, Beverley Hills, Santa Monica, Catalina and western Los Angeles suburbs, overlays with 310)<Pacific Time Zone>
+425 WA Washington (north and east Seattle suburbs including Everett, Bellevue, Redmond, Renton and Issaquah)<Pacific Time Zone>
+430 TX Texas (Tyler, Sherman, Longview, Palestine and northeastern Texas, overlays with 903)<Central Time Zone>
+432 TX Texas (Alpine, Midland, Odessa, Big Bend, Seminole, Comstock and west central Texas)< Central Time Zone>
+434 VA Virginia (Lynchburg, Danville, South Hill, Charlottesville and south central Virginia)<Eastern Time Zone>
+435 UT Utah (Logan, St George, Moab and all of Utah excluding Salt Lake City, Ogden, Provo and central Utah)<Mountain Time Zone>
+438 QC Quebec (Montreal area, overlays with 514)<Eastern Time Zone>
+440 OH Ohio (Elyria, Lorain, Oberlin, Wellington and north central Ohio)<Eastern Time Zone>
+441 BERMUDA (all of Bermuda)<Atlantic Time Zone>
+443 MD Maryland (Annapolis, Baltimore, Salisbury and eastern Maryland, overlays with 410)<Eastern Time Zone>
+450 QC Quebec (central southern Quebec excluding Montreal)<Eastern Time Zone>
+456 INTERNATIONAL INBOUND
+469 TX Texas (Dallas area, overlays with 214 and 972)<Central Time Zone>
+470 GA Georgia (Atlanta, Gainesville and north central Georgia, overlays with 404 and 678 and 770)<Eastern Time Zone>
+473 GRENADA (all of Grenada)<Atlantic Time Zone (one hour later than Eastern)>
+475 CT Connecticut ( Bridgeport, New Haven, Waterbury and southwestern Connecticut, overlays with 203)<Eastern Time Zone>
+478 GA Georgia (Macon, Warner Robins, Swainsboro, Wadley, Milledgeville, Perry and central Georgia)<Eastern Time Zone>
+479 AR Arkansas (Fort Smith, Fayetteville and northwestern Arkansas)<Central Time Zone>
+480 AZ Arizona (Chandler, eastern Phoenix area and eastern Phoenix suburbs only)<Mountain & Pacific Time Zones>
+484 PA Pennsylvania (Reading, Allentown, Chester and southeastern Pennsylvania, overlays with 610 and 835)<Eastern Time Zone>
+500 PERSONAL COMMUNICATIONS SERVICES
+501 AR Arkansas (Little Rock, Hot Springs and central Arkansas)<Central Time Zone>
+502 KY Kentucky (Louisville, Frankfort, Shelbyville and north central Kentucky)<Eastern Time Zone>
+503 OR Oregon (Portland, Salem, Tillamook, Astoria and northwestern Oregon, overlays with 971)<Pacific Time Zone>
+504 LA Louisiana (New Orleans, Kenner, Metairie and surrounding areas)<Central Time Zone>
+505 NM New Mexico (Albuquerque, Farmington, Gallup, Santa Fe and northwestern New Mexico)<Mountain Time Zone>
+506 NB New Brunswick (all of New Brunswick)<Atlantic Time Zone>
+507 MN Minnesota (Rochester, Mankato, Worthington, Marshall and southern Minnesota)<Central Time Zone>
+508 MA Massachusetts (Worcester, New Bedford and southeastern Massachusetts, overlays with 774)<Eastern Time Zone>
+509 WA Washington (Spokane, Yakima, Walla Walla, Moses Lake, Ellensburg and eastern Washington)<Pacific Time Zone>
+510 CA California (Hayward, Berkeley, Oakland, Richmond and Fremont areas)<Pacific Time Zone>
+512 TX Texas (Austin, Lampasas, Bastrop, Milam and central Texas)<Central Time Zone>
+513 OH Ohio (Cincinnati, Middletown, Hamilton, Norwood, Lebanon and southwestern Ohio)<Eastern Time Zone>
+514 QC Quebec (Montreal area, overlays with 438)<Eastern Time Zone>
+515 IA Iowa (Des Moines, Ames, Fort Dodge, Jefferson, Algona, Indianola and north central Iowa)<Central Time Zone>
+516 NY New York (Nassau County, Levittown, Hicksville, Massapequa and western Long Island)<Eastern Time Zone>
+517 MI Michigan (Jackson, Lansing, Howell, Deerfield, Addison and south central Michigan)<Eastern Time Zone>
+518 NY New York (Plattsburgh, Saranac Lake, Albany and northeastern New York)<Eastern Time Zone>
+519 ON Ontario (London, Windsor and southwestern Ontario, overlays with 226)<Eastern Time Zone>
+520 AZ Arizona (Tucson and southeastern Arizona)<Mountain & Pacific Time Zones>
+530 CA California (Alturas, Chico, Redding, Placerville, Truckee and northeastern California)<Pacific Time Zone>
+540 VA Virginia (Roanoke, Harrisonburg, Winchester, Fredericksburg and northern Virginia)<Eastern Time Zone>
+541 OR Oregon (Medford, Eugene, Ontario, Burns and all of Oregon except northwestern Oregon)<Mountain & Pacific Time Zones>
+551 NJ New Jersey (Hackensack, Jersey City, and northeast New Jersey, overlays with 201)<Eastern Time Zone>
+559 CA California (Fresno, Madera, Hanford, Visalia and central California)<Pacific Time Zone>
+561 FL Florida (West Palm Beach, Boca Raton, Boynton Beach, Delray Beach, Belleglade and central eastern Florida)<Eastern Time Zone>
+562 CA California (Long Beach, Lakewood, Bellflower and southwestern Los Angeles suburbs)<Pacific Time Zone>
+563 IA Iowa (Decorah, Dubuque, Clinton, Davenport and eastern and northeastern Iowa)<Central Time Zone>
+567 OH Ohio (Toledo, Mansfield, Lima, Bryan, Sandusky and northwestern Ohio, overlays with 419)<Eastern Time Zone>
+570 PA Pennsylvania (Scranton, Williamsport, Wilkes-Barre, Susquehanna and northeastern Pennsylvania)<Eastern Time Zone>
+571 VA Virginia (Alexandria, Washington DC suburbs, Arlington and northeastern Virginia, overlays with 703)<Eastern Time Zone>
+573 MO Missouri (Jefferson City, Hannibal, Poplar Bluff and eastern Missouri excluding St Louis)<Central Time Zone>
+574 IN Indiana (South Bend, Logansport, Elkhart, Nappanee and north central Indiana)<Central & Eastern Time Zones>
+575 NM New Mexico (Carlsbad, Las Cruces, Roswell, Silver City, Taos and southern and eastern New Mexico)<Mountain Time Zone>
+580 OK Oklahoma (Guymon, Hugo, Enid, Lawton, Ardmore, Elk City and southern and western Oklahoma)<Central Time Zone>
+585 NY New York (Rochester, Wellsville, Batavia, Olean and western New York)<Eastern Time Zone>
+586 MI Michigan (Port Huron, Flint, Flushing, Warren and eastern Michigan, overlays with 810)<Eastern Time Zone>
+600 CANADA/SERVICES
+601 MS Mississippi (Jackson, Meridian, Natchez, McComb, Hattiesburg and central Mississippi, overlays with 769)<Central Time Zone>
+602 AZ Arizona (Central Phoenix only)<Mountain & Pacific Time Zones>
+603 NH New Hampshire (all of New Hampshire)<Eastern Time Zone>
+604 BC British Columbia (Vancouver, Richmond, Abbotsford, Whistler and southwestern BC, overlays with 778)<Pacific Time Zone>
+605 SD South Dakota (all of South Dakota)<Central & Mountain Time Zones>
+606 KY Kentucky (Ashland, Hazard, Somerset, London, Corbin, Pikeville, Maysville and eastern Kentucky)<Central & Eastern Time Zones>
+607 NY New York (Binghamton, Elmira, Bath, Norwich and south central New York)<Eastern Time Zone>
+608 WI Wisconsin (Madison, La Crosse, Platteville, Janesville and southwestern Wisconsin)<Central Time Zone>
+609 NJ New Jersey (Atlantic City, Brown Mills, Trenton, and central & southeastern New Jersey)<Eastern Time Zone>
+610 PA Pennsylvania (Reading, Allentown, Chester and southeastern Pennsylvania, overlays with 484 and 835)<Eastern Time Zone>
+611 REPAIR SERVICE
+612 MN Minnesota (Central Minneapolis, Fort Snelling, St Anthony and Richfield)<Central Time Zone>
+613 ON Ontario (Ottawa and southeastern Ontario)<Eastern Time Zone>
+614 OH Ohio (Columbus area)<Eastern Time Zone>
+615 TN Tennessee (Nashville, Murfreesboro, Springfield, Lebanon, Dickson and north central Tennessee)<Central Time Zone>
+616 MI Michigan ( Grand Rapids, Holland, Greenville, Grandhaven, Zeeland, and southwestern Michigan)<Eastern Time Zone>
+617 MA Massachusetts (Boston, Cambridge and east central Massachusetts, overlays with 857)<Eastern Time Zone>
+618 IL Illinois (Carbondale, Alton, Centralia, Mount Vernon and southern Illinois)<Central Time Zone>
+619 CA California (National City, Chula Vista, Imperial Beach, Otay and the San Diego area)<Pacific Time Zone>
+620 KS Kansas (Dodge City, Great Bend, Parsons, Liberal and southern Kansas)<Central & Mountain Time Zone>
+623 AZ Arizona (Buckeye, Peoria, western Phoenix area and western Phoenix suburbs only)<Mountain & Pacific Time Zones>
+626 CA California (Arcadia, Temple City, Covina, Pasadena and eastern Los Angeles suburbs)<Pacific Time Zone>
+630 IL Illinois (Aurora, Batavia, Geneva and western Chicago suburbs, overlays with 331)<Central Time Zone>
+631 NY New York (Manorville, Huntington, Lindenhurst, Islip, Deer Park and Eastern Long Island)<Eastern Time Zone>
+636 MO Missouri (Chesterfield, Union, De Soto, Troy and east central Missouri)<Central Time Zone>
+641 IA Iowa (Mason City, Oskaloosa, Creston, Pella, Ottumwa, Britt, Clear Lake, Fairfield and central Iowa)<Central Time Zone>
+646 NY New York (New York City Manhattan area, overlays with 212 and 917)<Eastern Time Zone>
+647 ON Ontario (Toronto area, overlays with 416)<Eastern Time Zone>
+649 TURKS & CAICOS (all of Turks and Caicos)<Eastern Time Zone>
+650 CA California (San Mateo, Palo Alto, Redwood City, Menlo Park and southern San Francisco suburbs)<Pacific Time Zone>
+651 MN Minnesota (St Paul, Lindstrom, Red Wing, Hastings and east central Minnesota)<Central Time Zone>
+660 MO Missouri (Marshall, Sedalia, Macon, Trenton, Maryville and north central Missouri)<Central Time Zone>
+661 CA California (Bakersfield, Mojave, Santa Clarita, Palmdale and south central California)<Pacific Time Zone>
+662 MS Mississippi (Greenville, Tupelo, Winona, Columbus, Holly Springs and northern Mississippi)<Central Time Zone>
+664 MONTSERRAT (all of Montserrat)<Atlantic Time Zone (one hour later than Eastern)>
+670 COMMONWEALTH OF THE NORTHERN MARIANA ISLANDS (all of CNMI including Saipan)<Pacific Guam Time Zone (6 or 7 hours earlier than Pacific Time, does not observe daylight savings)>
+671 GUAM (all of Guam)<Pacific Guam Time Zone (6 or 7 hours earlier than Pacific Time, does not observe daylight savings)>
+678 GA Georgia (Atlanta, Gainesville, Griffin and north central Georgia, overlays 404 and and 470 and 770)<Eastern Time Zone>
+682 TX Texas (Fort Worth, Arlington, Grandview, Weatherford, Rhome, overlays with 817)<Central Time Zone>
+684 AMERICAN SAMOA (all of American Samoa including Pago Pago)<Samoan Standard Time Zone (3 or 4 hours earlier than Pacific Time, does not observe daylight savings)>
+700 INTEREXCHANGE CARRIER SERVICES
+701 ND North Dakota (all of North Dakota)<Mountain & Central Time Zones>
+702 NV Nevada (Las Vegas, Henderson and extreme southern Nevada)<Pacific Time Zone>
+703 VA Virginia (Alexandria, Washington DC suburbs, Arlington and northeastern Virginia, overlays with 571)<Eastern Time Zone>
+704 NC North Carolina (Charlotte, Kingstown and south central North Carolina, overlays with 980)<Eastern Time Zone>
+705 ON Ontario (North Bay and northeastern Ontario)<Eastern Time Zone>
+706 GA Georgia (Augusta, Columbus, Lagrange, Rome, Dalton and northern and west central Georgia, overlays with 762)<Eastern Time Zone>
+707 CA California (Santa Rosa, Fort Bragg, Crescent City, Eureka, Ukiah and northwestern California)<Pacific Time Zone>
+708 IL Illinois (Chicago Heights, Tinley Park and southern Chicago suburbs)<Central Time Zone>
+709 NF Newfoundland (all of Newfoundland)<Newfoundland Time Zone (one and a half hours later than Eastern)>
+710 US GOVERNMENT
+711 TRS ACCESS
+712 IA Iowa (Estherville, Council Bluffs, Sioux City, Sheldon, Denison and western Iowa)<Central Time Zone>
+713 TX Texas (Spring, Katy, Houston area, overlays with 281 and 832)<Central Time Zone>
+714 CA California (Huntington Beach, Orange, Garden Grove, Tustin, Anaheim and northern Orange County)<Pacific Time Zone>
+715 WI Wisconsin (Rhinelander, Wausau, Eau Claire, Rice Lake, Ashland and northern Wisconsin)<Central Time Zone>
+716 NY New York (Buffalo, Jamestown, Niagara Falls, Tonawanda and western New York)<Eastern Time Zone>
+717 PA Pennsylvania (Harrisburg, Gettysburg, Lancaster, York and south central Pennsylvania)<Eastern Time Zone>
+718 NY New York (Flushing, Jamaica, Brooklyn, Staten Island, Bronx and Queens, overlays with 347 and 917)<Eastern Time Zone>
+719 CO Colorado (Leadville, Pueblo, Colorado Springs, Trinidad and southeastern Colorado)<Mountain Time Zone>
+720 CO Colorado (Boulder, Longmont, Aurora, Denver and central Colorado, overlays with 303)<Mountain Time Zone>
+724 PA Pennsylvania (New Castle, Washington, Uniontown and south western Pennsylvania, overlays with 878)<Eastern Time Zone>
+727 FL Florida (Clearwater, St Petersburg, Dunedin and the west central Florida gulf coast)<Eastern Time Zone>
+731 TN Tennessee (Union City, Jackson, Dyersburg, Paris, Bolivar and western Tennessee)<Central Time Zone>
+732 NJ New Jersey (New Brunswick, Neptune, Lakewood, and east central New Jersey, overlays with 848)<Eastern Time Zone>
+734 MI Michigan (Ann Arbor, Monroe, Wayne, Ypsilanti and southwestern Detroit suburbs)<Eastern Time Zone>
+740 OH Ohio (Jackson, Lancaster, Marietta, Cambridge, Zanesville, New Castle and southeastern Ohio)<Eastern Time Zone>
+754 FL Florida (Fort Lauderdale, Pompano Beach, overlays with 954)<Eastern Time Zone>
+757 VA Virginia (Hampton, Norfolk, Williamsburg, Newport News, Virginia Beach and southeastern Virginia)<Eastern Time Zone>
+758 ST LUCIA (all of St Lucia)<Atlantic Time Zone (one hour later than Eastern)>
+760 CA California (Bishop, Ridgecrest, Indio, Barstow, El Centro, Palm Springs and southeastern California)<Pacific Time Zone>
+762 GA Georgia (Augusta, Columbus, Lagrange, Rome, Dalton and northern and west central Georgia, overlays with 706)<Eastern Time Zone>
+763 MN Minnesota (Maple Grove, Monticello, Elk River, Fridley, Blaine, and northwest Minneapolis area)<Central Time Zone>
+765 IN Indiana (Lafayette, Marion, Muncie, Richmond and central Indiana excluding Indianapolis)<Eastern Time Zone>
+767 DOMINICA (all of Dominica)<Atlantic Time Zone (one hour later than Eastern)>
+769 MS Mississippi (Jackson, Meridian, Natchez, McComb, Hattiesburg and central Mississippi, overlays with 601)<Central Time Zone>
+770 GA Georgia (Marietta, Cedartown, and north central Georgia, overlays with 470 and 678)<Eastern Time Zone>
+772 FL Florida (Vero Beach, Port Saint Lucie, Fort Pierce, Sebastian, Stuart and central eastern Florida)<Eastern Time Zone>
+773 IL Illinois (Chicago excluding downtown Chicago)<Central Time Zone>
+774 MA Massachusetts (Worcester and southeastern Massachusetts, overlays with 508)<Eastern Time Zone>
+775 NV Nevada (Reno, Elko, Ely and all of Nevada excluding Las Vegas and extreme southern Nevada)<Pacific Time Zone>
+778 BC British Columbia (Vancouver, Richmond, Abbotsford and southwestern BC overlays with 604)<Pacific Time Zone>
+779 IL Illinois (La Salle, De Kalb, Rockford, Freeport and northern Illinois excluding Chicago area, overlays with 815)<Central Time Zone>
+780 AB Alberta (Edmonton, Jasper, Grande Prairie, Peace River and northern Alberta)<Mountain Time Zone>
+781 MA Massachusetts (Saugus, Norwood and east central Massachusetts, overlays with 339)<Eastern Time Zone>
+784 ST VINCENT AND THE GRENADINES (all of St Vincent and the Grenadines)<Atlantic Time Zone (one hour later than Eastern)>
+785 KS Kansas (Colby, Topeka, Salina, Manhattan, Lawrence and northern Kansas)<Mountain & Central Time Zones>
+786 FL Florida (Miami, Homestead, Coral Gables, Key West and southeastern Florida, overlays with 305)<Eastern Time Zone>
+787 PUERTO RICO (all of Puerto Rico, overlays with 939)<Eastern & Atlantic Time Zone (does not observe daylight savings time)>
+800 TOLL FREE SERVICES
+801 UT Utah (Salt Lake City, Salt Lake County, Midvale, Alta, Magna, Kearns Holladay, and central Utah)<Mountain Time Zone>
+802 VT Vermont (all of Vermont)<Eastern Time Zone>
+803 SC South Carolina (Columbia, Rock Hill, Sumter, Aiken and central South Carolina)<Eastern Time Zone>
+804 VA Virginia (Richmond, Petersburg, West Point, Chester and east central Virginia)<Eastern Time Zone>
+805 CA California (Santa Barbara, San Luis Obispo, Lompoc and central western coastal California)<Pacific Time Zone>
+806 TX Texas (Amarillo, Lubbock, Canadian, Perryton, Shamrock, Dalhart and Texas panhandle)<Central Time Zone>
+807 ON Ontario (Thunder Bay and western Ontario)<Central & Eastern Time Zones>
+808 HI Hawaii (all of Hawaii)<Hawaiian Time Zone (2 or 3 hours earlier than Pacific Time, does not observe daylight savings)>
+809 DOMINICAN REPUBLIC (all of the Dominican Republic, overlays with 829)<Atlantic Time Zone>
+810 MI Michigan (Port Huron, Flint, Flushing, Warren and eastern Michigan, overlays with 586)<Eastern Time Zone>
+811 BUSINESS OFFICE
+812 IN Indiana (Evansville, New Albany, Terre Haute, Bloomington and southern Indiana)<Central & Eastern Time Zones>
+813 FL Florida (Tampa, Hillsborough, Plant City, Port Tampa and central western Florida)<Eastern Time Zone>
+814 PA Pennsylvania (Erie, Warren, Altoona, Johnstown, Meyersdale and central and northwestern Pennsylvania)<Eastern Time Zone>
+815 IL Illinois (La Salle, De Kalb, Rockford, Freeport and northern Illinois excluding Chicago area, overlays with 779)<Central Time Zone>
+816 MO Missouri (Kansas City, St Joseph, Independence, Harrisonville and west central Missouri)<Central Time Zone>
+817 TX Texas (Fort Worth, Arlington, Grandview, Weatherford, Rhome, overlays with 682)<Central Time Zone>
+818 CA California ( Glendale, San Fernando, Burbank and northern Los Angeles suburbs)<Pacific Time Zone>
+819 QC Quebec (Western Quebec)<Eastern Time Zone>
+822 TOLL FREE SERVICES
+828 NC North Carolina (Asheville, Brevard, Morganton, Murphy and western North Carolina)<Eastern Time Zone>
+829 DOMINICAN REPUBLIC (all of the Dominican Republic, overlays with 809)<Atlantic Time Zone>
+830 TX Texas (Uvalde, New Braunfels, Kerrville, Eagle Pass and southwest Texas)<Central Time Zone>
+831 CA California (Salinas, Hollister, Monterey, Santa Cruz and central western coastal California)<Pacific Time Zone>
+832 TX Texas (Spring, Katy, Houston area, overlays with 281 and 713)<Central Time Zone>
+833 TOLL FREE SERVICES
+835 PA Pennsylvania (Reading, Allentown, and southeastern Pennsylvania, overlays with 484 and 610)<Eastern Time Zone>
+843 SC South Carolina (Florence, Myrtle Beach, Charleston, Hilton Head Island and eastern South Carolina)<Eastern Time Zone>
+844 TOLL FREE SERVICES
+845 NY New York ( Poughkeepsie, Middletown, West Point, Newburgh and southeastern New York)<Eastern Time Zone>
+847 IL Illinois (Waukegan, Des Plaines, northwest Chicago suburbs and northeastern Illinois, overlays with 224)<Central Time Zone>
+848 NJ New Jersey (New Brunswick, Neptune, and east central New Jersey, overlays with 732)<Eastern Time Zone>
+850 FL Florida ( Pensacola, Tallahassee, Panama City and the Florida panhandle)<Central & Eastern Time Zones>
+855 TOLL FREE SERVICES
+856 NJ New Jersey (Vineland, Cherry Hill, Camden, Millville, and southwestern New Jersey)<Eastern Time Zone>
+857 MA Massachusetts (Boston, Cambridge and east central Massachusetts, overlays with 617)<Eastern Time Zone>
+858 CA California (Del Mar, La Jolla and northern San Diego suburbs)<Pacific Time Zone>
+859 KY Kentucky (Lexington, Richmond, Danville, Covington, Mount Sterling and north central Kentucky)<Eastern Time Zone>
+860 CT Connecticut (Bristol, Hartford, Norwich and northern and eastern Connecticut, overlays with 959)<Eastern Time Zone>
+862 NJ New Jersey (Newark, Paterson and northwestern New Jersey, overlays with 973)<Eastern Time Zone>
+863 FL Florida (Avon Park, Clewiston, Lakeland, Bartow, Sebring, Winter Haven and south central Florida)<Eastern Time Zone>
+864 SC South Carolina (Greenville, Spartanburg, Anderson and western South Carolina)<Eastern Time Zone>
+865 TN Tennessee (Knoxville, Newport, Jefferson City, Oak Ridge and east central Tennessee)<Eastern Time Zone>
+866 TOLL FREE SERVICES
+867 YK Yukon, Northwest Territories, and Nunavut (Yukon, Northwest Territories and Nunavut)<Pacific, Mountain, Central, Eastern, and Atlantic Time Zones>
+868 TRINIDAD AND TOBAGO (all of Trinidad and Tobago)<Atlantic Time Zone (one hour later than Eastern)>
+869 ST KITTS AND NEVIS (all of St Kitts and Nevis)<Atlantic Time Zone (one hour later than Eastern)>
+870 AR Arkansas (Texarkana, Mountain Home, Pine Bluff and southern, eastern and northeastern Arkansas)<Central Time Zone>
+876 JAMAICA (all of Jamaica)<Eastern Time Zone>
+877 TOLL FREE SERVICES
+878 PA Pennsylvania (Pittsburgh, New Castle, and southwestern Pennsylvania, overlays with 412 and 724)<Eastern Time Zone>
+880 PAID 800 SERVICE FROM CARIBBEAN TO THE US AND CANADA OR FROM CANADA TO THE US
+881 PAID 888 SERVICE FROM CARIBBEAN TO THE US AND CANADA OR FROM CANADA TO THE US
+882 PAID 877 SERVICE FROM CARIBBEAN TO THE US AND CANADA OR FROM CANADA TO THE US
+888 TOLL FREE SERVICES
+900 900 NUMBER PAY PER CALL SERVICES
+901 TN Tennessee (Memphis, Covington, Somerville and south western Tennessee)<Central Time Zone>
+902 NS Nova Scotia (all of Nova Scotia and Prince Edward Island)<Atlantic Time Zone>
+903 TX Texas (Tyler, Sherman, Longview, Palestine and northeastern Texas, overlays with 430)<Central Time Zone>
+904 FL Florida (Jacksonville, St Augustine, Starke, Green Cove Springs and northeastern Florida)<Eastern Time Zone>
+905 ON Ontario (Hamilton, Toronto suburbs and central southeastern Ontario, overlays with 289)<Eastern Time Zone>
+906 MI Michigan (Ironwood, Marquette, Sault Ste Marie, Escanaba and upper Michigan)<Central & Eastern Time Zones>
+907 AK Alaska (all of Alaska)<Alaskan (1 hour earlier than Pacific) & Aleutian (2 hours earlier than Pacific) Time Zones>
+908 NJ New Jersey (Washington, Elizabeth, Warren, Plainfield and west central New Jersey)<Eastern Time Zone>
+909 CA California (San Bernardino, Ontario, Pomona, Chino, Arrowhead and Big Bear Lake areas)<Pacific Time Zone>
+910 NC North Carolina (Fayetteville, Wilmington, Lumberton and southeastern North Carolina)<Eastern Time Zone>
+911 EMERGENCY
+912 GA Georgia ( Savannah, Vidalia, Waycross, Brunswick, Douglas and southeastern Georgia)<Eastern Time Zone>
+913 KS Kansas (Kansas City, Overland Park, Paola Leavenworth and extreme eastern Kansas)<Central Time Zone>
+914 NY New York (White Plains, Yonkers, Pelham, Westchester, Peekskill and southeastern New York)<Eastern Time Zone>
+915 TX Texas (El Paso, Dell City, Guadalupe Peak, Sierra Blanca, and far western Texas)<Mountain & Central Time Zones>
+916 CA California (Sacramento area)<Pacific Time Zone>
+917 NY New York (New York City Manhattan area, overlays with 212 and 646)<Eastern Time Zone>
+918 OK Oklahoma (Tulsa, Bartlesville, McAlester, Muskogee, Henrietta and northeastern Oklahoma)<Central Time Zone>
+919 NC North Carolina (Raleigh, Durham, Chapel Hill, Oxford and north central North Carolina)<Eastern Time Zone>
+920 WI Wisconsin (Sheboygan, Oshkosh, Green Bay, Manitowoc, Fond Du Lac and eastern Wisconsin)<Central Time Zone>
+925 CA California (Pleasanton, Martinez, Concord, Livermore, Walnut Creek and Dublin areas)<Pacific Time Zone>
+928 AZ Arizona (Flagstaff, Kingman, Prescott, Yuma and northern and western Arizona)<Mountain & Pacific Time Zones>
+931 TN Tennessee (Clarksville, Columbia, Manchester, Cookeville and central Tennessee)<Central & Eastern Time Zones>
+936 TX Texas (Nacogdoches, Lufkin, Conroe, Huntsville, Center and southeastern Texas)<Central Time Zone>
+937 OH Ohio (Marysville, Springfield, Dayton, Hillsboro and southwestern Ohio excluding Cincinnati area)<Eastern Time Zone>
+939 PUERTO RICO (all of Puerto Rico, overlays with 787)<Eastern & Atlantic Time Zone (does not observe daylight savings time)>
+940 TX Texas (Vernon, Wichita Falls, Denton, Gainesville, Decatur and north central Texas)<Central Time Zone>
+941 FL Florida ( Bradenton, Port Charlotte, Sarasota, Punta Gorda and the west central Florida coast)<Eastern Time Zone>
+947 MI Michigan (Troy, Oakland County, Pontiac, Southfield, Rochester Hills and northwestern Detroit suburbs, overlays with 248)<Eastern Time Zone>
+949 CA California (Laguna Niguel, Irvine, El Toro, Newport Beach, Corona Del Mar and southern Orange County)<Pacific Time Zone>
+951 CA California (Riverside, Corona, Murrieta, Perris, San Jacinto and Temecula areas)<Pacific Time Zone>
+952 MN Minnesota (Bloomington, Minnetonka, Chaska and southwest Minneapolis area)<Central Time Zone>
+954 FL Florida (Fort Lauderdale, Pompano Beach, overlays with 754)<Eastern Time Zone>
+956 TX Texas (Laredo, Brownsville, McAllen, Harlingen and southern Texas)<Central Time Zone>
+959 CT Connecticut (Bristol, Hartford, Norwich and northern and eastern Connecticut, overlays with 860)<Eastern Time Zone>
+970 CO Colorado (Aspen, Durango, Grand Junction, Fort Collins and northern and western Colorado)<Mountain Time Zone>
+971 OR Oregon (Portland, Salem, Hillsboro, Beaverton and northwestern Oregon, overlays with 503)<Pacific Time Zone>
+972 TX Texas (Dallas area, overlays with 214 and 469)<Central Time Zone>
+973 NJ New Jersey (Newark, Paterson and northwestern New Jersey, overlays with 862)<Eastern Time Zone>
+978 MA Massachusetts (Fitchburg, Peabody and northeastern Massachusetts, overlays with 351)<Eastern Time Zone>
+979 TX Texas (Wharton, Bryan, Bay City, College Station, Lake Jackson and southeastern Texas)<Central Time Zone>
+980 NC North Carolina (Charlotte, Kingstown and south central North Carolina, overlays with 704)<Eastern Time Zone>
+985 LA Louisiana (Houma, Slidell and southeastern Louisiana excluding New Orleans)<Central Time Zone>
+989 MI Michigan (Alpena, Mt Pleasant, Bay City, Saginaw, Midland, Owosso and central Michigan)<Eastern Time Zone>
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/fslongtable.sty b/etc/fslongtable.sty
new file mode 100644
index 0000000..e322b55
--- /dev/null
+++ b/etc/fslongtable.sty
@@ -0,0 +1,438 @@
+%%
+%% This is file `fslongtable.sty',
+%%
+%% Copyright 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003
+%% The LaTeX3 Project and any individual authors listed elsewhere
+%% in this file.
+%%
+%% This file was forked from file(s) of the Standard LaTeX `Tools Bundle'.
+%% This file includes a new length LTextracouponspace which modifies
+%% the behavior of the package at the end of a page. This feature
+%% and package is not supported or acknowledged by Dave Carlisle.
+%% Do not contact him for such support.
+%% --------------------------------------------------------------------------
+%%
+%% It may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either version 1.3
+%% of this license or (at your option) any later version.
+%% The latest version of this license is in
+%% http://www.latex-project.org/lppl.txt
+%% and version 1.3 or later is part of all distributions of LaTeX
+%% version 2003/12/01 or later.
+%%
+%% File: longtable.dtx Copyright (C) 1990-2001 David Carlisle
+%% File: fslongtable.sty Copyright (C) 2008 Jeff Finucane
+\NeedsTeXFormat{LaTeX2e}[1995/06/01]
+\ProvidesPackage{longtable}
+ [2004/02/01 v4.11 Multi-page Table package (DPC)]
+\def\LT@err{\PackageError{longtable}}
+\def\LT@warn{\PackageWarning{longtable}}
+\def\LT@final@warn{%
+ \AtEndDocument{%
+ \LT@warn{Table \@width s have changed. Rerun LaTeX.\@gobbletwo}}%
+ \global\let\LT@final@warn\relax}
+\DeclareOption{errorshow}{%
+ \def\LT@warn{\PackageInfo{longtable}}}
+\DeclareOption{pausing}{%
+ \def\LT@warn#1{%
+ \LT@err{#1}{This is not really an error}}}
+\DeclareOption{set}{}
+\DeclareOption{final}{}
+\ProcessOptions
+\newskip\LTleft \LTleft=\fill
+\newskip\LTright \LTright=\fill
+\newskip\LTpre \LTpre=\bigskipamount
+\newskip\LTpost \LTpost=\bigskipamount
+\newcount\LTchunksize \LTchunksize=20
+\let\c@LTchunksize\LTchunksize
+\newdimen\LTcapwidth \LTcapwidth=4in
+\newlength\LTextracouponspace
+\newbox\LT@head
+\newbox\LT@firsthead
+\newbox\LT@foot
+\newbox\LT@lastfoot
+\newcount\LT@cols
+\newcount\LT@rows
+\newcounter{LT@tables}
+\newcounter{LT@chunks}[LT@tables]
+\ifx\c@table\undefined
+ \newcounter{table}
+ \def\fnum@table{\tablename~\thetable}
+\fi
+\ifx\tablename\undefined
+ \def\tablename{Table}
+\fi
+\newtoks\LT@p@ftn
+\mathchardef\LT@end@pen=30000
+\def\longtable{%
+ \par
+ \ifx\multicols\@undefined
+ \else
+ \ifnum\col@number>\@ne
+ \@twocolumntrue
+ \fi
+ \fi
+ \if@twocolumn
+ \LT@err{longtable not in 1-column mode}\@ehc
+ \fi
+ \begingroup
+ \@ifnextchar[\LT@array{\LT@array[x]}}
+\def\LT@array[#1]#2{%
+ \refstepcounter{table}\stepcounter{LT@tables}%
+ \if l#1%
+ \LTleft\z@ \LTright\fill
+ \else\if r#1%
+ \LTleft\fill \LTright\z@
+ \else\if c#1%
+ \LTleft\fill \LTright\fill
+ \fi\fi\fi
+ \let\LT@mcol\multicolumn
+ \let\LT@@tabarray\@tabarray
+ \let\LT@@hl\hline
+ \def\@tabarray{%
+ \let\hline\LT@@hl
+ \LT@@tabarray}%
+ \let\\\LT@tabularcr\let\tabularnewline\\%
+ \def\newpage{\noalign{\break}}%
+ \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}%
+ \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}%
+ \let\hline\LT@hline \let\kill\LT@kill\let\caption\LT@caption
+ \@tempdima\ht\strutbox
+ \let\@endpbox\LT@endpbox
+ \ifx\extrarowheight\@undefined
+ \let\@acol\@tabacol
+ \let\@classz\@tabclassz \let\@classiv\@tabclassiv
+ \def\@startpbox{\vtop\LT@startpbox}%
+ \let\@@startpbox\@startpbox
+ \let\@@endpbox\@endpbox
+ \let\LT@LL@FM@cr\@tabularcr
+ \else
+ \advance\@tempdima\extrarowheight
+ \col@sep\tabcolsep
+ \let\@startpbox\LT@startpbox\let\LT@LL@FM@cr\@arraycr
+ \fi
+ \setbox\@arstrutbox\hbox{\vrule
+ \@height \arraystretch \@tempdima
+ \@depth \arraystretch \dp \strutbox
+ \@width \z@}%
+ \let\@sharp##\let\protect\relax
+ \begingroup
+ \@mkpream{#2}%
+ \xdef\LT@bchunk{%
+ \global\advance\c@LT@chunks\@ne
+ \global\LT@rows\z@\setbox\z@\vbox\bgroup
+ \LT@setprevdepth
+ \tabskip\LTleft \noexpand\halign to\hsize\bgroup
+ \tabskip\z@ \@arstrut \@preamble \tabskip\LTright \cr}%
+ \endgroup
+ \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols
+ \LT@make@row
+ \m@th\let\par\@empty
+ \everycr{}\lineskip\z@\baselineskip\z@
+ \LT@bchunk}
+\def\LT@no@pgbk#1[#2]{\penalty #1\@getpen{#2}\ifnum`{=0\fi}}
+\def\LT@start{%
+ \let\LT@start\endgraf
+ \endgraf\penalty\z@\vskip\LTpre
+ \dimen@\pagetotal
+ \advance\dimen@ \ht\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi
+ \advance\dimen@ \dp\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi
+ \advance\dimen@ \ht\LT@foot
+ \dimen@ii\vfuzz
+ \vfuzz\maxdimen
+ \setbox\tw@\copy\z@
+ \setbox\tw@\vsplit\tw@ to \ht\@arstrutbox
+ \setbox\tw@\vbox{\unvbox\tw@}%
+ \vfuzz\dimen@ii
+ \advance\dimen@ \ht
+ \ifdim\ht\@arstrutbox>\ht\tw@\@arstrutbox\else\tw@\fi
+ \advance\dimen@\dp
+ \ifdim\dp\@arstrutbox>\dp\tw@\@arstrutbox\else\tw@\fi
+ \advance\dimen@ -\pagegoal
+ \ifdim \dimen@>\z@\vfil\break\fi
+ \global\@colroom\@colht
+ \ifnum\thepage=1
+ \advance\vsize-\LTextracouponspace
+ \dimen@\pagegoal\advance\dimen@-\LTextracouponspace\pagegoal\dimen@
+ \fi
+ \ifvoid\LT@foot\else
+ \advance\vsize-\ht\LT@foot
+ \global\advance\@colroom-\ht\LT@foot
+ \dimen@\pagegoal\advance\dimen@-\ht\LT@foot\pagegoal\dimen@
+ \maxdepth\z@
+ \fi
+ \ifvoid\LT@firsthead\copy\LT@head\else\box\LT@firsthead\fi\nobreak
+ \output{\LT@output}}
+\def\endlongtable{%
+ \crcr
+ \noalign{%
+ \let\LT@entry\LT@entry@chop
+ \xdef\LT@save@row{\LT@save@row}}%
+ \LT@echunk
+ \LT@start
+ \unvbox\z@
+ \LT@get@widths
+ \if@filesw
+ {\let\LT@entry\LT@entry@write\immediate\write\@auxout{%
+ \gdef\expandafter\noexpand
+ \csname LT@\romannumeral\c@LT@tables\endcsname
+ {\LT@save@row}}}%
+ \fi
+ \ifx\LT@save@row\LT@@save@row
+ \else
+ \LT@warn{Column \@width s have changed\MessageBreak
+ in table \thetable}%
+ \LT@final@warn
+ \fi
+ \endgraf\penalty -\LT@end@pen
+ \endgroup
+ \global\@mparbottom\z@
+ \pagegoal\vsize
+ \endgraf\penalty\z@\addvspace\LTpost
+ \ifvoid\footins\else\insert\footins{}\fi}
+\def\LT@nofcols#1&{%
+ \futurelet\@let@token\LT@n@fcols}
+\def\LT@n@fcols{%
+ \advance\LT@cols\@ne
+ \ifx\@let@token\LT@nofcols
+ \expandafter\@gobble
+ \else
+ \expandafter\LT@nofcols
+ \fi}
+\def\LT@tabularcr{%
+ \relax\iffalse{\fi\ifnum0=`}\fi
+ \@ifstar
+ {\def\crcr{\LT@crcr\noalign{\nobreak}}\let\cr\crcr
+ \LT@t@bularcr}%
+ {\LT@t@bularcr}}
+\let\LT@crcr\crcr
+\let\LT@setprevdepth\relax
+\def\LT@t@bularcr{%
+ \global\advance\LT@rows\@ne
+ \ifnum\LT@rows=\LTchunksize
+ \gdef\LT@setprevdepth{%
+ \prevdepth\z@\global
+ \global\let\LT@setprevdepth\relax}%
+ \expandafter\LT@xtabularcr
+ \else
+ \ifnum0=`{}\fi
+ \expandafter\LT@LL@FM@cr
+ \fi}
+\def\LT@xtabularcr{%
+ \@ifnextchar[\LT@argtabularcr\LT@ntabularcr}
+\def\LT@ntabularcr{%
+ \ifnum0=`{}\fi
+ \LT@echunk
+ \LT@start
+ \unvbox\z@
+ \LT@get@widths
+ \LT@bchunk}
+\def\LT@argtabularcr[#1]{%
+ \ifnum0=`{}\fi
+ \ifdim #1>\z@
+ \unskip\@xargarraycr{#1}%
+ \else
+ \@yargarraycr{#1}%
+ \fi
+ \LT@echunk
+ \LT@start
+ \unvbox\z@
+ \LT@get@widths
+ \LT@bchunk}
+\def\LT@echunk{%
+ \crcr\LT@save@row\cr\egroup
+ \global\setbox\@ne\lastbox
+ \unskip
+ \egroup}
+\def\LT@entry#1#2{%
+ \ifhmode\@firstofone{&}\fi\omit
+ \ifnum#1=\c@LT@chunks
+ \else
+ \kern#2\relax
+ \fi}
+\def\LT@entry@chop#1#2{%
+ \noexpand\LT@entry
+ {\ifnum#1>\c@LT@chunks
+ 1}{0pt%
+ \else
+ #1}{#2%
+ \fi}}
+\def\LT@entry@write{%
+ \noexpand\LT@entry^^J%
+ \@spaces}
+\def\LT@kill{%
+ \LT@echunk
+ \LT@get@widths
+ \expandafter\LT@rebox\LT@bchunk}
+\def\LT@rebox#1\bgroup{%
+ #1\bgroup
+ \unvbox\z@
+ \unskip
+ \setbox\z@\lastbox}
+\def\LT@blank@row{%
+ \xdef\LT@save@row{\expandafter\LT@build@blank
+ \romannumeral\number\LT@cols 001 }}
+\def\LT@build@blank#1{%
+ \if#1m%
+ \noexpand\LT@entry{1}{0pt}%
+ \expandafter\LT@build@blank
+ \fi}
+\def\LT@make@row{%
+ \global\expandafter\let\expandafter\LT@save@row
+ \csname LT@\romannumeral\c@LT@tables\endcsname
+ \ifx\LT@save@row\relax
+ \LT@blank@row
+ \else
+ {\let\LT@entry\or
+ \if!%
+ \ifcase\expandafter\expandafter\expandafter\LT@cols
+ \expandafter\@gobble\LT@save@row
+ \or
+ \else
+ \relax
+ \fi
+ !%
+ \else
+ \aftergroup\LT@blank@row
+ \fi}%
+ \fi}
+\let\setlongtables\relax
+\def\LT@get@widths{%
+ \setbox\tw@\hbox{%
+ \unhbox\@ne
+ \let\LT@old@row\LT@save@row
+ \global\let\LT@save@row\@empty
+ \count@\LT@cols
+ \loop
+ \unskip
+ \setbox\tw@\lastbox
+ \ifhbox\tw@
+ \LT@def@row
+ \advance\count@\m@ne
+ \repeat}%
+ \ifx\LT@@save@row\@undefined
+ \let\LT@@save@row\LT@save@row
+ \fi}
+\def\LT@def@row{%
+ \let\LT@entry\or
+ \edef\@tempa{%
+ \ifcase\expandafter\count@\LT@old@row
+ \else
+ {1}{0pt}%
+ \fi}%
+ \let\LT@entry\relax
+ \xdef\LT@save@row{%
+ \LT@entry
+ \expandafter\LT@max@sel\@tempa
+ \LT@save@row}}
+\def\LT@max@sel#1#2{%
+ {\ifdim#2=\wd\tw@
+ #1%
+ \else
+ \number\c@LT@chunks
+ \fi}%
+ {\the\wd\tw@}}
+\def\LT@hline{%
+ \noalign{\ifnum0=`}\fi
+ \penalty\@M
+ \futurelet\@let@token\LT@@hline}
+\def\LT@@hline{%
+ \ifx\@let@token\hline
+ \global\let\@gtempa\@gobble
+ \gdef\LT@sep{\penalty-\@medpenalty\vskip\doublerulesep}%
+ \else
+ \global\let\@gtempa\@empty
+ \gdef\LT@sep{\penalty-\@lowpenalty\vskip-\arrayrulewidth}%
+ \fi
+ \ifnum0=`{\fi}%
+ \multispan\LT@cols
+ \unskip\leaders\hrule\@height\arrayrulewidth\hfill\cr
+ \noalign{\LT@sep}%
+ \multispan\LT@cols
+ \unskip\leaders\hrule\@height\arrayrulewidth\hfill\cr
+ \noalign{\penalty\@M}%
+ \@gtempa}
+\def\LT@caption{%
+ \noalign\bgroup
+ \@ifnextchar[{\egroup\LT@c@ption\@firstofone}\LT@capti@n}
+\def\LT@c@ption#1[#2]#3{%
+ \LT@makecaption#1\fnum@table{#3}%
+ \def\@tempa{#2}%
+ \ifx\@tempa\@empty\else
+ {\let\\\space
+ \addcontentsline{lot}{table}{\protect\numberline{\thetable}{#2}}}%
+ \fi}
+\def\LT@capti@n{%
+ \@ifstar
+ {\egroup\LT@c@ption\@gobble[]}%
+ {\egroup\@xdblarg{\LT@c@ption\@firstofone}}}
+\def\LT@makecaption#1#2#3{%
+ \LT@mcol\LT@cols c{\hbox to\z@{\hss\parbox[t]\LTcapwidth{%
+ \sbox\@tempboxa{#1{#2: }#3}%
+ \ifdim\wd\@tempboxa>\hsize
+ #1{#2: }#3%
+ \else
+ \hbox to\hsize{\hfil\box\@tempboxa\hfil}%
+ \fi
+ \endgraf\vskip\baselineskip}%
+ \hss}}}
+\def\LT@output{%
+ \ifnum\outputpenalty <-\@Mi
+ \ifnum\outputpenalty > -\LT@end@pen
+ \LT@err{floats and marginpars not allowed in a longtable}\@ehc
+ \else
+ \setbox\z@\vbox{\unvbox\@cclv}%
+ \ifdim \ht\LT@lastfoot>\ht\LT@foot
+ \dimen@\pagegoal
+ \advance\dimen@-\ht\LT@lastfoot
+ \ifdim\dimen@<\ht\z@
+ \setbox\@cclv\vbox{\unvbox\z@\copy\LT@foot\vss}%
+ \@makecol
+ \@outputpage
+ \setbox\z@\vbox{\box\LT@head}%
+ \fi
+ \fi
+ \global\@colroom\@colht
+ \global\vsize\@colht
+ \vbox
+ {\unvbox\z@\box\ifvoid\LT@lastfoot\LT@foot\else\LT@lastfoot\fi}%
+ \fi
+ \else
+ \setbox\@cclv\vbox{\unvbox\@cclv\copy\LT@foot\vss}%
+ \@makecol
+ \@outputpage
+ \global\vsize\@colroom
+ \copy\LT@head\nobreak
+ \fi}
+\def\LT@end@hd@ft#1{%
+ \LT@echunk
+ \ifx\LT@start\endgraf
+ \LT@err
+ {Longtable head or foot not at start of table}%
+ {Increase LTchunksize}%
+ \fi
+ \setbox#1\box\z@
+ \LT@get@widths
+ \LT@bchunk}
+\def\endfirsthead{\LT@end@hd@ft\LT@firsthead}
+\def\endhead{\LT@end@hd@ft\LT@head}
+\def\endfoot{\LT@end@hd@ft\LT@foot}
+\def\endlastfoot{\LT@end@hd@ft\LT@lastfoot}
+\def\LT@startpbox#1{%
+ \bgroup
+ \let\@footnotetext\LT@p@ftntext
+ \setlength\hsize{#1}%
+ \@arrayparboxrestore
+ \vrule \@height \ht\@arstrutbox \@width \z@}
+\def\LT@endpbox{%
+ \@finalstrut\@arstrutbox
+ \egroup
+ \the\LT@p@ftn
+ \global\LT@p@ftn{}%
+ \hfil}
+\def\LT@p@ftntext#1{%
+ \edef\@tempa{\the\LT@p@ftn\noexpand\footnotetext[\the\c@footnote]}%
+ \global\LT@p@ftn\expandafter{\@tempa{#1}}}%
+\endinput
+%%
+%% End of file `longtable.sty'.
diff --git a/etc/megapop.pl b/etc/megapop.pl
new file mode 100755
index 0000000..e2930fb
--- /dev/null
+++ b/etc/megapop.pl
@@ -0,0 +1,114 @@
+#!/usr/bin/perl -Tw
+#
+# 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_selfservice/DEPLOY b/fs_selfservice/DEPLOY
new file mode 100755
index 0000000..e73012f
--- /dev/null
+++ b/fs_selfservice/DEPLOY
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#this is a quick hack for my dev machine. do not use it.
+# see the "make install-selfservice" and "make update-selfservice" makefile
+# targets to properly install this stuff.
+
+#kill `cat /var/run/freeside-selfservice-server.fs_selfservice.pid`
+
+cd FS-SelfService
+perl Makefile.PL && make && make install
+cd ..
+
+#( cd ..; make deploy; cd fs_selfservice )
+( cd ..; make clean; make install-perl-modules; /etc/init.d/freeside restart; cd fs_selfservice )
+
+#cp /home/ivan/freeside/fs_selfservice/FS-SelfService/cgi/* /var/www/MyAccount
+#chown freeside /var/www/MyAccount/*.cgi
+#chmod 755 /var/www/MyAccount/*.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 #!!!!!
+ ##mv /var/www/signup/signup-billaddress.html /var/www/signup/signup.html #!!!!!
+ ##mv /var/www/signup/signup-freeoption.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
+
+
+chmod 755 /var/www/selfservice/*.cgi
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..2e4d3fe
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/MANIFEST
@@ -0,0 +1,9 @@
+Changes
+Makefile.PL
+MANIFEST
+SelfService.pm
+SelfService/XMLRPC.pm
+test.pl
+freeside-selfservice-clientd
+freeside-selfservice-soap-server
+freeside-selfservice-xmlrpc-server
diff --git a/fs_selfservice/FS-SelfService/Makefile.PL b/fs_selfservice/FS-SelfService/Makefile.PL
new file mode 100644
index 0000000..600c9d5
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/Makefile.PL
@@ -0,0 +1,21 @@
+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-soap-server',
+ 'freeside-selfservice-xmlrpc-server',
+ ],
+ 'INSTALLSCRIPT' => '/usr/local/sbin',
+ 'INSTALLSITEBIN' => '/usr/local/sbin',
+ 'INSTALLSITESCRIPT' => '/usr/local/sbin', #recent deb users this...
+ '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..7e6821b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/SelfService.pm
@@ -0,0 +1,1832 @@
+package FS::SelfService;
+
+use strict;
+use vars qw( $VERSION @ISA @EXPORT_OK $DEBUG
+ $skip_uid_check $dir $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 );
+
+$DEBUG = 0;
+
+$dir = "/usr/local/freeside";
+$socket = "$dir/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_info' => 'MyAccount/login_info',
+ 'login' => 'MyAccount/login',
+ 'logout' => 'MyAccount/logout',
+ 'customer_info' => 'MyAccount/customer_info',
+ 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
+ 'invoice' => 'MyAccount/invoice',
+ 'invoice_logo' => 'MyAccount/invoice_logo',
+ 'list_invoices' => 'MyAccount/list_invoices', #?
+ 'cancel' => 'MyAccount/cancel', #add to ss cgi!
+ 'payment_info' => 'MyAccount/payment_info',
+ 'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
+ 'process_payment' => 'MyAccount/process_payment',
+ 'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
+ 'process_payment_change_pkg' => 'MyAccount/process_payment_change_pkg',
+ 'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
+ 'process_prepay' => 'MyAccount/process_prepay',
+ 'realtime_collect' => 'MyAccount/realtime_collect',
+ 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
+ 'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
+ 'list_svc_usage' => 'MyAccount/list_svc_usage',
+ 'list_cdr_usage' => 'MyAccount/list_cdr_usage',
+ 'list_support_usage' => 'MyAccount/list_support_usage',
+ 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
+ 'change_pkg' => 'MyAccount/change_pkg',
+ 'order_recharge' => 'MyAccount/order_recharge',
+ 'renew_info' => 'MyAccount/renew_info',
+ 'order_renew' => 'MyAccount/order_renew',
+ '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',
+ 'myaccount_passwd' => 'MyAccount/myaccount_passwd',
+ 'create_ticket' => 'MyAccount/create_ticket',
+ 'signup_info' => 'Signup/signup_info',
+ 'skin_info' => 'MyAccount/skin_info',
+ 'access_info' => 'MyAccount/access_info',
+ 'domain_select_hash' => 'Signup/domain_select_hash', # expose?
+ 'new_customer' => 'Signup/new_customer',
+ 'capture_payment' => 'Signup/capture_payment',
+ 'agent_login' => 'Agent/agent_login',
+ 'agent_logout' => 'Agent/agent_logout',
+ 'agent_info' => 'Agent/agent_info',
+ 'agent_list_customers' => 'Agent/agent_list_customers',
+ 'mason_comp' => 'MasonComponent/mason_comp',
+ 'call_time' => 'PrepaidPhone/call_time',
+ 'call_time_nanpa' => 'PrepaidPhone/call_time_nanpa',
+ 'phonenum_balance' => 'PrepaidPhone/phonenum_balance',
+ 'bulk_processrow' => 'Bulk/processrow',
+ 'check_username' => 'Bulk/check_username',
+ #sg
+ 'ping' => 'SGNG/ping',
+ 'decompify_pkgs' => 'SGNG/decompify_pkgs',
+ 'previous_payment_info' => 'SGNG/previous_payment_info',
+ 'previous_payment_info_renew_info'
+ => 'SGNG/previous_payment_info_renew_info',
+ 'previous_process_payment' => 'SGNG/previous_process_payment',
+ 'previous_process_payment_order_pkg'
+ => 'SGNG/previous_process_payment_order_pkg',
+ 'previous_process_payment_change_pkg'
+ => 'SGNG/previous_process_payment_change_pkg',
+ 'previous_process_payment_order_renew'
+ => 'SGNG/previous_process_payment_order_renew',
+);
+@EXPORT_OK = (
+ keys(%autoload),
+ qw( regionselector regionselector_hashref location_form
+ expselect popselector domainselector didselector
+ )
+);
+
+$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+#you can add BEGIN { $FS::SelfService::skip_uid_check = 1; }
+#if you grant appropriate permissions to whatever user
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n"
+ if $> != $freeside_uid && ! $skip_uid_check;
+
+-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 as freeside user!";
+-x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";
+
+foreach my $autoload ( keys %autoload ) {
+
+ my $eval =
+ "sub $autoload { ". '
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ #warn scalar(@_). ": ". join(" / ", @_);
+ $param = { @_ };
+ }
+
+ $param->{_packet} = \''. $autoload{$autoload}. '\';
+
+ simple_packet($param);
+ }';
+
+ eval $eval;
+ die $@ if $@;
+
+}
+
+sub simple_packet {
+ my $packet = shift;
+ warn "sending ". $packet->{_packet}. " to server"
+ if $DEBUG;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
+ 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;
+
+ warn "reading message from server"
+ if $DEBUG;
+
+ my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
+ die $return->{'_error'} if defined $return->{_error} && $return->{_error};
+
+ warn "returning message to client"
+ if $DEBUG;
+
+ $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,
+ 'paystart_month' => $paystart_month
+ 'paystart_year' => $paystart_year,
+ 'payissue' => $payissue,
+ 'payip' => $payip
+ 'paydate' => $paydate,
+ 'payname' => $payname,
+ 'invoicing_list' => $invoicing_list,
+ 'referral_custnum' => $referral_custnum,
+ 'agentnum' => $agentnum,
+ 'pkgpart' => $pkgpart,
+
+ 'username' => $username,
+ '_password' => $password,
+ 'popnum' => $popnum,
+ #OR
+ 'countrycode' => 1,
+ 'phonenum' => $phonenum,
+ 'pin' => $pin,
+ }
+ );
+
+ 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
+
+Username
+
+=item domain
+
+Domain
+
+=item password
+
+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
+
+Address line one
+
+=item address2
+
+Address line two
+
+=item city
+
+City
+
+=item state
+
+State
+
+=item zip
+
+Zip or postal code
+
+=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
+
+=cut
+
+#this doesn't actually work yet
+#
+#=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
+
+Session identifier
+
+=item amount
+
+Amount
+
+=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
+
+Name on card
+
+=item address1
+
+Address line one
+
+=item address2
+
+Address line two
+
+=item city
+
+City
+
+=item state
+
+State
+
+=item zip
+
+Zip or postal code
+
+=item country
+
+Two-letter country code
+
+=item payinfo
+
+Card number
+
+=item month
+
+Card expiration month
+
+=item year
+
+Card expiration year
+
+=cut
+
+#this doesn't actually work yet
+#
+#=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 process_payment_order_pkg
+
+Combines the B<process_payment> and B<order_pkg> functions in one step. If the
+payment processes sucessfully, the package is ordered. Takes a hash reference
+as parameter with the keys of both methods.
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=item process_payment_change_pkg
+
+Combines the B<process_payment> and B<change_pkg> functions in one step. If the
+payment processes sucessfully, the package is ordered. Takes a hash reference
+as parameter with the keys of both methods.
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+
+=item process_payment_order_renew
+
+Combines the B<process_payment> and B<order_renew> functions in one step. If
+the payment processes sucessfully, the renewal is processed. Takes a hash
+reference as parameter with the keys of both methods.
+
+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. For more detail on services,
+see L</list_svcs>.
+
+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 custnum
+
+Customer number
+
+=item error
+
+Empty on success, or an error message on errors.
+
+=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.
+
+=over 4
+
+=item part_pkg fields
+
+All fields of part_pkg for this specific cust_pkg (be careful with this
+information - it may reveal more about your available packages than you would
+like users to know in aggregate)
+
+=cut
+
+#XXX pare part_pkg fields down to a more secure subset
+
+=item part_svc
+
+An array of hash references indicating information on unprovisioned services
+available for provisioning for this specific cust_pkg. Each has the following
+keys:
+
+=over 4
+
+=item part_svc fields
+
+All fields of part_svc (be careful with this information - it may reveal more
+about your available packages than you would like users to know in aggregate)
+
+=cut
+
+#XXX pare part_svc fields down to a more secure subset
+
+=back
+
+=item cust_svc
+
+An array of hash references indicating information on the customer services
+already provisioned for this specific cust_pkg. Each has the following keys:
+
+=over 4
+
+=item label
+
+Array reference with three elements: The first element is the name of this service. The second element is a meaningful user-specific identifier for the service (i.e. username, domain or mail alias). The last element is the table name of this service.
+
+=back
+
+=item svcnum
+
+Primary key for this service
+
+=item svcpart
+
+Service definition (see L<FS::part_svc>)
+
+=item pkgnum
+
+Customer package (see L<FS::cust_pkg>)
+
+=item overlimit
+
+Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).
+
+=back
+
+=back
+
+=item list_svcs
+
+Returns service 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 custnum
+
+Customer number
+
+=item svcs
+
+An array of hash references indicating information on all of this customer's
+services. Each has the following keys:
+
+=over 4
+
+=item svcnum
+
+Primary key for this service
+
+=item label
+
+Name of this service
+
+=item value
+
+Meaningful user-specific identifier for the service (i.e. username, domain, or
+mail alias).
+
+=back
+
+Account (svc_acct) services also have the following keys:
+
+=over 4
+
+=item username
+
+Username
+
+=item email
+
+username@domain
+
+=item seconds
+
+Seconds remaining
+
+=item upbytes
+
+Upload bytes remaining
+
+=item downbytes
+
+Download bytes remaining
+
+=item totalbytes
+
+Total bytes remaining
+
+=item recharge_amount
+
+Cost of a recharge
+
+=item recharge_seconds
+
+Number of seconds gained by recharge
+
+=item recharge_upbytes
+
+Number of upload bytes gained by recharge
+
+=item recharge_downbytes
+
+Number of download bytes gained by recharge
+
+=item recharge_totalbytes
+
+Number of total bytes gained by recharge
+
+=back
+
+=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
+
+Session identifier
+
+=item pkgpart
+
+Package to order (see L<FS::part_pkg>).
+
+=item svcpart
+
+Service to order (see L<FS::part_svc>).
+
+Normally optional; required only to provision a non-svc_acct service, or if the
+package definition does not contain one svc_acct service definition with
+quantity 1 (it may contain others with quantity >1). A svcpart of "none" can
+also be specified to indicate that no initial service should be provisioned.
+
+=back
+
+Fields used when provisioning an svc_acct service:
+
+=over 4
+
+=item username
+
+Username
+
+=item _password
+
+Password
+
+=item sec_phrase
+
+Optional security phrase
+
+=item popnum
+
+Optional Access number number
+
+=back
+
+Fields used when provisioning an svc_domain service:
+
+=over 4
+
+=item domain
+
+Domain
+
+=back
+
+Fields used when provisioning an svc_phone service:
+
+=over 4
+
+=item phonenum
+
+Phone number
+
+=item pin
+
+Voicemail PIN
+
+=item sip_password
+
+SIP password
+
+=back
+
+Fields used when provisioning an svc_external service:
+
+=over 4
+
+=item id
+
+External numeric ID.
+
+=item title
+
+External text title.
+
+=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 change_pkg
+
+Changes a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=item pkgnum
+
+Existing customer package.
+
+=item pkgpart
+
+New package to order (see L<FS::part_pkg>).
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=item renew_info
+
+Provides useful info for early renewals.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=back
+
+Returns a hash reference. On errors, it contains a single key, B<error>, with
+the error message. Otherwise, contains a single key, B<dates>, pointing to
+an array refernce of hash references. Each hash reference contains the
+following keys:
+
+=over 4
+
+=item bill_date
+
+(Future) Bill date. Indicates a future date for which billing could be run.
+Specified as a integer UNIX timestamp. Pass this value to the B<order_renew>
+function.
+
+=item bill_date_pretty
+
+(Future) Bill date as a human-readable string. (Convenience for display;
+subject to change, so best not to parse for the date.)
+
+=item amount
+
+Base amount which will be charged if renewed early as of this date.
+
+=item renew_date
+
+Renewal date; i.e. even-futher future date at which the customer will be paid
+through if the early renewal is completed with the given B<bill-date>.
+Specified as a integer UNIX timestamp.
+
+=item renew_date_pretty
+
+Renewal date as a human-readable string. (Convenience for display;
+subject to change, so best not to parse for the date.)
+
+=item pkgnum
+
+Package that will be renewed.
+
+=item expire_date
+
+Expiration date of the package that will be renewed.
+
+=item expire_date_pretty
+
+Expiration date of the package that will be renewed, as a human-readable
+string. (Convenience for display; subject to change, so best not to parse for
+the date.)
+
+=back
+
+=item order_renew
+
+Renews this customer early; i.e. runs billing for this customer in advance.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=item date
+
+Integer date as returned by the B<renew_info> function, indicating the advance
+date for which to run billing.
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=item cancel_pkg
+
+Cancels a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+Session identifier
+
+=item pkgpart
+
+pkgpart of package to cancel
+
+=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, definitely 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
+
+Company name
+
+=item address1 (required)
+
+Address line one
+
+=item address2
+
+Address line two
+
+=item city (required)
+
+City
+
+=item county
+
+County
+
+=item state (required)
+
+State
+
+=item zip (required)
+
+Zip or postal code
+
+=item daytime
+
+Daytime phone number
+
+=item night
+
+Evening phone number
+
+=item fax
+
+Fax number
+
+=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 agentnum
+
+Agent number
+
+=item pkgpart
+
+pkgpart of initial package
+
+=item username
+
+Username
+
+=item _password
+
+Password
+
+=item sec_phrase
+
+Security phrase
+
+=item popnum
+
+Access number (index, not the literal number)
+
+=item countrycode
+
+Country code (to be provisioned as a service)
+
+=item phonenum
+
+Phone number (to be provisioned as a service)
+
+=item pin
+
+Voicemail PIN
+
+=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 Configuration | 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
+
+Currently selected county
+
+=item selected_state
+
+Currently selected state
+
+=item selected_country
+
+Currently 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
+
+Default state
+
+=item default_country
+
+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>';
+
+ my $country_html = '';
+ if ( scalar( keys %cust_main_county ) > 1 ) {
+
+ $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>';
+ } else {
+
+ $country_html = qq(<INPUT TYPE="hidden" NAME="${prefix}country" ).
+ ' VALUE="'. (keys %cust_main_county )[0]. '">';
+
+ }
+
+ ($county_html, $state_html, $country_html);
+
+}
+
+sub regionselector_hashref {
+ my ($county_html, $state_html, $country_html) = regionselector(@_);
+ {
+ 'county_html' => $county_html,
+ 'state_html' => $state_html,
+ 'country_html' => $country_html,
+ };
+}
+
+=item location_form HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item session_id
+
+Current customer session_id
+
+=item no_asterisks
+
+Omit red asterisks from required fields.
+
+=item address1_label
+
+Label for first address line.
+
+=back
+
+Returns an HTML fragment for a location form (address, city, state, zip,
+country)
+
+=cut
+
+sub location_form {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+
+ my $session_id = delete $param->{'session_id'};
+
+ my $rv = mason_comp( 'session_id' => $session_id,
+ 'comp' => '/elements/location.html',
+ 'args' => [ %$param ],
+ );
+
+ #hmm.
+ $rv->{'error'} || $rv->{'output'};
+
+}
+
+
+#=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 .= qq!<OPTION VALUE="$_"!;
+ $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) .. ($thisYear+10) ) {
+ $return .= qq!<OPTION VALUE="$_"!;
+ $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
+
+Access number number
+
+=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";
+
+ $param->{'acstate'} = '' unless defined($param->{'acstate'});
+
+ $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;
+
+}
+
+=item domainselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item pkgnum
+
+Package number
+
+=item domsvc
+
+Service number of the selected item.
+
+=back
+
+Returns an HTML fragment for domain selection.
+
+=cut
+
+sub domainselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ my $domsvc= $param->{'domsvc'};
+ my $rv =
+ domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
+ my $domains = $rv->{'domains'};
+ $domsvc = $rv->{'domsvc'} unless $domsvc;
+
+ return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
+ unless scalar(keys %$domains);
+
+ if (scalar(keys %$domains) == 1) {
+ my $key;
+ foreach(keys %$domains) {
+ $key = $_;
+ }
+ return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
+ '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
+ }
+
+ my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;
+
+
+ foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
+ $text .= qq!<OPTION VALUE="!. $domain. '"'.
+ ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
+ $domains->{$domain};
+ }
+
+ $text .= qq!</SELECT></TD></TR>!;
+
+ $text;
+
+}
+
+=item didselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item field
+
+Field name for the returned HTML fragment.
+
+=item svcpart
+
+Service definition (see L<FS::part_svc>)
+
+=back
+
+Returns an HTML fragment for DID selection.
+
+=cut
+
+sub didselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+
+ my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
+ 'args'=>[ %$param ],
+ );
+
+ #hmm.
+ $rv->{'error'} || $rv->{'output'};
+
+}
+
+=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 additional I<custnum> parameter.
+
+For the most part, development of the reseller web interface has been
+superceded by agent-virtualized access to the backend.
+
+=over 4
+
+=item agent_login
+
+Agent login
+
+=item agent_info
+
+Agent info
+
+=item agent_list_customers
+
+List agent's customers.
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
+
+=cut
+
+1;
+
diff --git a/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm b/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm
new file mode 100644
index 0000000..0df24f7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm
@@ -0,0 +1,61 @@
+#Add this to the modules section of radiusd.conf
+# perl {
+# #path to this module
+# module=/usr/local/share/perl/5.8.8/FS/SelfService/FreeRadiusVoip.pm
+# func_authorize = authorize;
+# }
+#
+#In the Authorize section
+#Make sure that you have 'files' uncommented. Then add a line containing 'perl'
+# after it.
+#
+# #N/A# Add a line containing 'perl' to the Accounting section.
+#
+# and on debian systems, add this to /etc/init.d/freeradius, with the
+# correct path (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=416266)
+# LD_PRELOAD=/usr/lib/libperl.so.5.8.8
+# export LD_PRELOAD
+
+BEGIN { $FS::SelfService::skip_uid_check = 1; }
+
+use strict;
+use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK);
+#use Data::Dumper;
+use FS::SelfService qw(call_time);
+
+use constant RLM_MODULE_REJECT=> 0; #immediately reject the request
+use constant RLM_MODULE_FAIL=> 1; #module failed, don't reply
+use constant RLM_MODULE_OK=> 2; #the module is OK, continue
+use constant RLM_MODULE_HANDLED=> 3; #the module handled the request, so stop
+use constant RLM_MODULE_INVALID=> 4; #the module considers the request invalid
+use constant RLM_MODULE_USERLOCK=> 5; #reject the request (user is locked out)
+use constant RLM_MODULE_NOTFOUND=> 6; #user not found
+use constant RLM_MODULE_NOOP=> 7; #module succeeded without doing anything
+use constant RLM_MODULE_UPDATED=> 8; #OK (pairs modified)
+use constant RLM_MODULE_NUMCODES=> 9; #How many return codes there are
+
+sub authorize {
+
+ #&log_request_attributes();
+
+ my $response = call_time( 'src' => $RAD_REQUEST{'Calling-Station-Id'},
+ 'dst' => $RAD_REQUEST{'Called-Station-Id'}, );
+
+ if ( $response->{'error'} ) {
+ $RAD_REPLY{'Reply-Message'} = $response->{'error'};
+ return RLM_MODULE_REJECT;
+ } else {
+ $RAD_REPLY{'Session-Timeout'} = $response->{'seconds'};
+ return RLM_MODULE_OK;
+ }
+
+}
+
+sub log_request_attributes {
+ # This shouldn't be done in production environments!
+ # This is only meant for debugging!
+ for (keys %RAD_REQUEST) {
+ &radiusd::radlog(1, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
+ }
+}
+
diff --git a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
new file mode 100644
index 0000000..4e0d3e9
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
@@ -0,0 +1,88 @@
+package FS::SelfService::XMLRPC;
+
+=head1 NAME
+
+FS::SelfService::XMLRPC - Freeside XMLRPC accessible self-service API
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+Use this API to implement your own client "self-service" module vi XMLRPC.
+
+Each routine described in L<FS::SelfService> is available vi XMLRPC as the
+method FS.SelfService.XMLRPC.B<method>. All values are passed to the
+selfservice-server in a struct of strings. The return values are in a
+struct as strings, arrays, or structs as appropriate for the values
+described in L<FS::SelfService>.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-selfservice-clientd>, L<freeside-selfservice-server>,L<FS::SelfService>
+
+=cut
+
+use strict;
+use vars qw($DEBUG $AUTOLOAD);
+use FS::SelfService;
+
+$DEBUG = 0;
+$FS::SelfService::DEBUG = $DEBUG;
+
+sub AUTOLOAD {
+ my $call = $AUTOLOAD;
+ $call =~ s/^FS::SelfService::XMLRPC:://;
+ if (exists($FS::SelfService::autoload{$call})) {
+ shift; #discard package name;
+ $call = "FS::SelfService::$call";
+ no strict 'refs';
+ &{$call}(@_);
+ }else{
+ die "No such procedure: $call";
+ }
+}
+
+package SOAP::Transport::HTTP::Daemon; # yuck
+
+use POSIX qw(:sys_wait_h);
+
+no warnings 'redefine';
+
+sub handle {
+ my $self = shift->new;
+
+ local $SIG{CHLD} = 'IGNORE';
+
+ACCEPT:
+ while (my $c = $self->accept) {
+
+ my $kid = 0;
+ do {
+ $kid = waitpid(-1, WNOHANG);
+ warn "found kid $kid";
+ } while $kid > 0;
+
+ my $pid = fork;
+ next ACCEPT if $pid;
+
+ if ( not defined $pid ) {
+ warn "fork() failed: $!";
+ $c = undef;
+ } else {
+ while (my $r = $c->get_request) {
+ $self->request($r);
+ $self->SUPER::handle;
+ $c->send_response($self->response);
+ }
+ # replaced ->close, thanks to Sean Meisner <Sean.Meisner@VerizonWireless.com>
+ # shutdown() doesn't work on AIX. close() is used in this case. Thanks to Jos Clijmans <jos.clijmans@recyfin.be>
+ UNIVERSAL::isa($c, 'shutdown') ? $c->shutdown(2) : $c->close();
+ $c->close;
+ }
+ exit;
+ }
+}
+
+1;
diff --git a/fs_selfservice/FS-SelfService/cgi/ach_payment_results.html b/fs_selfservice/FS-SelfService/cgi/ach_payment_results.html
new file mode 100644
index 0000000..9cdb65e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/ach_payment_results.html
@@ -0,0 +1,10 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Payment results') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!;
+} else {
+ $OUT .= 'Your payment was processed successfully. Thank you.';
+} %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent.cgi b/fs_selfservice/FS-SelfService/cgi/agent.cgi
new file mode 100644
index 0000000..6e8de61
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent.cgi
@@ -0,0 +1,458 @@
+#!/usr/bin/perl -T
+#!/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 = '';
+
+ #false laziness w/signup.cgi, identical except for agentnum vs session_id
+ 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 { $_ => scalar($cgi->param($_)) }
+ qw( last first ss company
+ address1 address2 city county state zip country
+ daytime night fax
+
+ ship_last ship_first ship_company
+ ship_address1 ship_address2 ship_city ship_county ship_state
+ ship_zip ship_country
+ ship_daytime ship_night ship_fax
+
+ payby payinfo paycvv paydate payname invoicing_list
+ referral_custnum promo_code reg_code
+ pkgpart username sec_phrase _password popnum refnum
+ ),
+ grep { /^snarf_/ } $cgi->param
+ } );
+ $error = $rv->{'error'};
+ }
+ #eslaf
+
+ 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 successful';
+ $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 successfully.',
+ };
+ }
+
+}
+
+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 successfully'.
+ ': 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 successful.',
+ };
+ }
+
+}
+
+#--
+
+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; #OLD
+ $fill_in->{'self_url'} = $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..63fa127
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_delete_svc.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="; ''; %>
+<%= 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>
+
+<%= include('footer') %>
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..3aefd61
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_main.html
@@ -0,0 +1,33 @@
+<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>
+
+<%= include('footer') %>
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..18a37e8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html
@@ -0,0 +1,18 @@
+<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>
+
+<%= include('footer') %>
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..f7f39b5
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_provision.html
@@ -0,0 +1,23 @@
+<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>
+<%= include('footer') %>
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..a867edb
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html
@@ -0,0 +1,16 @@
+<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>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/bill.html b/fs_selfservice/FS-SelfService/cgi/bill.html
new file mode 100644
index 0000000..a3884e0
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/bill.html
@@ -0,0 +1,15 @@
+<TR>
+ <TD ALIGN="right">P.O.&nbsp;number</TD>
+ <TD><INPUT TYPE="text" NAME="payinfo" SIZE=10 MAXLENGTH=20 VALUE="<%=$payinfo%>"></TD>
+</TR><TR>
+ <TD ALIGN="right">Attention</TD>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+</TR><TR>
+ <TD ALIGN="right">Postal mail invoice</TD>
+ <TD><INPUT TYPE="checkbox" NAME="postal_invoicing" VALUE="POST" <%=
+ $postal_invoicing ? 'CHECKED' : ''
+ %>></TD>
+</TR><TR>
+ <TD>Email address(es)</TD>
+ <TD><INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(',', $invoicing_list ) %>"></TD>
+</TR>
diff --git a/fs_selfservice/FS-SelfService/cgi/card.html b/fs_selfservice/FS-SelfService/cgi/card.html
new file mode 100644
index 0000000..c7db2b3
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/card.html
@@ -0,0 +1,47 @@
+<TR>
+ <TH ALIGN="right">Card&nbsp;number</TH>
+ <TD COLSPAN=6>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%=$payinfo%>"> </TD>
+ <TH>Exp.</TH>
+ <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>
+<%=
+ if ( $withcvv ) {
+ $OUT .= qq!<TR>!;
+ $OUT .= qq!<TD ALIGN="right">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>)</TD>!;
+ $OUT .= qq!<TD><INPUT TYPE="text" NAME="paycvv" VALUE="" SIZE=4 MAXLENGTH=4></TD>!;
+ $OUT .= qq!</TR>!;
+ }
+ '';
+%>
+<TR>
+ <TH ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TH>
+ <TD COLSPAN=6><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+</TR>
+
+<%= location_form( 'session_id' => $session_id,
+ 'no_asterisks' => 1,
+ #'address1_label' => 'Card billing address',
+ 'address1_label' => 'Card&nbsp;billing&nbsp;address',
+ )
+%>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_bill.html b/fs_selfservice/FS-SelfService/cgi/change_bill.html
new file mode 100755
index 0000000..7941971
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/change_bill.html
@@ -0,0 +1,19 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Edit billing address') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT><BR><BR>!;
+} ''; %>
+
+<FORM NAME="ChangeBillForm" ACTION="<%= $selfurl %>" METHOD=POST onSubmit="document.bottomform.submit.disabled=true;">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_change_bill">
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<%= $r=qq!<font color="#ff0000">*</font>&nbsp;!; include('contact') %>
+
+<INPUT TYPE="submit" NAME="submit" VALUE="<%= $custnum ? "Apply Changes" : "Add Customer" %>">
+<BR>
+</FORM>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_password.html b/fs_selfservice/FS-SelfService/cgi/change_password.html
new file mode 100644
index 0000000..68b6fd8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/change_password.html
@@ -0,0 +1,46 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Change password') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<FORM ACTION="<%= $selfurl %>" METHOD="POST">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_change_password">
+
+<TABLE BGCOLOR="#cccccc">
+
+ <TR>
+ <TH ALIGN="right">Change password for account: </TH>
+ <TD>
+ <SELECT NAME="svcnum">
+ <%= foreach my $svc ( @svcs ) {
+ $OUT .= '<OPTION VALUE="'. $svc->{'svcnum'}. '"'.
+ ( $svc->{'svcnum'} eq $svcnum ? ' SELECTED' : '' ). '>'.
+ $svc->{'label'}. ': '. $svc->{'value'}. "\n";
+ }
+ %>
+ </SELECT>
+ </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">
+
+</FORM>
+
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_pay.html b/fs_selfservice/FS-SelfService/cgi/change_pay.html
new file mode 100644
index 0000000..9633e89
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/change_pay.html
@@ -0,0 +1,69 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Change payment information') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT><BR><BR>!;
+ } ''; %>
+
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="<%=$selfurl%>" onSubmit="document.OneTrueForm.process.disabled=true">
+<%=
+ use Tie::IxHash;
+ use HTML::Widgets::SelectLayers;
+
+ my $preauto = '<TR><TD COLSPAN=3><INPUT TYPE="checkbox" NAME="auto" VALUE="1"';
+ my $postauto = '>Charge future payments to this card automatically</TD></TR>';
+
+ my $tail = qq(</TABLE><INPUT TYPE="hidden" NAME="session" VALUE="$session_id">).
+ qq(<INPUT TYPE="hidden" NAME="action" VALUE="process_change_pay">).
+ qq(<BR>).
+ qq(<INPUT TYPE="submit" NAME="process" ).
+ qq(VALUE="Save payment information"> ).
+ qq(<!-- onClick="this.disabled=true"> -->);
+
+
+ my %paybychecked = (
+ 'BILL' => include('bill'),
+ 'CARD' => include('card')."$preauto CHECKED $postauto",
+ 'DCRD' => include('card')."$preauto $postauto",
+ 'CHEK' => include('check')."$preauto CHECKED $postauto",
+ 'DCHK' => include('check')."$preauto $postauto",
+ );
+ my %payby_index = ( 'CARD' => qq/Credit Card/,
+ 'DCRD' => qq/Credit Card/,
+ 'CHEK' => qq/Check/,
+ 'DCHK' => qq/Check/,
+ 'LECB' => qq/Phone Bill Billing/,
+ 'BILL' => qq/Billing/,
+ 'COMP' => qq/Complimentary/,
+ 'PREP' => qq/Prepaid Card/,
+ 'PREPAY' => qq/Prepaid Card/,
+ );
+ tie my %options, 'Tie::IxHash', ();
+ foreach my $payby_option ( grep { exists( $payby_index{$_} ) } @paybys ) {
+ $options{$payby_option} = $payby_index{$payby_option};
+ }
+ $options{$payby} = $payby_index{$payby}
+ unless exists($options{$payby});
+
+ #don't want to show multiple "Credit card" or "Check" options
+ my %paybyremove = (
+ 'CARD' => 'DCRD',
+ 'DCRD' => 'CARD',
+ 'CHEK' => 'DCHK',
+ 'DCHK' => 'CHEK',
+ );
+ delete( $options{ $paybyremove{$payby} } );
+ delete $options{'DCRD'} unless $payby eq 'DCRD' || ! exists $options{'CARD'};
+ delete $options{'DCHK'} unless $payby eq 'DCHK' || ! exists $options{'CHEK'};
+
+ HTML::Widgets::SelectLayers->new(
+ options => \%options,
+ selected_layer => $payby,
+# form_name => 'dummy',
+# form_action => 'dummy.cgi',
+ layer_callback => sub { my $layer = shift; return '<TABLE BGCOLOR="#cccccc">'.$paybychecked{$layer}.qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$layer">$tail!; },
+ )->html;
+
+%>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_pkg.html b/fs_selfservice/FS-SelfService/cgi/change_pkg.html
new file mode 100644
index 0000000..a841308
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/change_pkg.html
@@ -0,0 +1,37 @@
+<SCRIPT TYPE="text/javascript">
+function enable_change_pkg () {
+ if ( document.ChangePkgForm.pkgpart.selectedIndex > 0 ) {
+ document.ChangePkgForm.submit.disabled = false;
+ } else {
+ document.ChangePkgForm.submit.disabled = true;
+ }
+}
+</SCRIPT>
+<FONT SIZE=4>Purchase replacement package for "<%= $pkg; %>"</FONT><BR><BR>
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+<FORM NAME="ChangePkgForm" ACTION="<%= $selfurl %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_change_pkg">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+<INPUT TYPE="hidden" NAME="pkg" VALUE="<%= $pkg %>">
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart" onChange="enable_change_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>
+</TABLE>
+<INPUT NAME="submit" TYPE="submit" VALUE="Purchase" disabled>
+</FORM>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/change_ship.html b/fs_selfservice/FS-SelfService/cgi/change_ship.html
new file mode 100755
index 0000000..59f9176
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/change_ship.html
@@ -0,0 +1,98 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Edit service address') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT><BR><BR>!;
+} ''; %>
+
+<FORM NAME="OneTrueForm" ACTION="<%= $selfurl %>" METHOD=POST onSubmit="document.bottomform.submit.disabled=true;">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_change_ship">
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<%=
+ foreach (
+ qw( last first company address1 address2 city county state zip country
+ daytime night fax )
+ ) {
+ $OUT .= qq!<INPUT TYPE="hidden" NAME="$_" VALUE="${$_}">!;
+ };
+ '';
+%>
+<SCRIPT>
+function bill_changed(what) {
+ if ( what.form.same.checked ) {
+<%=
+ for (qw( last first company address1 address2 city zip daytime night fax )) {
+ $OUT .= "what.form.ship_$_.value = what.form.$_.value;";
+ }
+ '';
+%>
+ what.form.ship_country.selectedIndex = what.form.country.selectedIndex;
+
+ function fix_ship_county() {
+ what.form.ship_county.selectedIndex = what.form.county.selectedIndex;
+ }
+
+ function fix_ship_state() {
+ what.form.ship_state.selectedIndex = what.form.state.selectedIndex;
+ ship_state_changed(what.form.ship_state, fix_ship_county );
+ }
+
+ ship_country_changed(what.form.ship_country, fix_ship_state );
+
+ }
+}
+function samechanged(what) {
+ if ( what.checked ) {
+ bill_changed(what);
+
+<%=
+ for (qw( last first company address1 address2 city county state zip country daytime night fax )) {
+ $OUT .= "what.form.ship_$_.disabled = true;";
+ $OUT .= "what.form.ship_$_.style.backgroundColor = '#dddddd';";
+ }
+ if ( $require_address2 ) {
+ $OUT .= "document.getElementById('ship_address2_required').style.visibility = 'hidden';";
+ $OUT .= "document.getElementById('ship_address2_label').style.visibility = 'hidden';";
+ }
+%>
+
+ } else {
+
+<%=
+ for (qw( last first company address1 address2 city county state zip country daytime night fax )) {
+ $OUT .= "what.form.ship_$_.disabled = false;";
+ $OUT .= "what.form.ship_$_.style.backgroundColor = '#ffffff';";
+ }
+ if ( $require_address2 ) {
+ $OUT .= "document.getElementById('ship_address2_required').style.visibility = '';";
+ $OUT .= "document.getElementById('ship_address2_label').style.visibility = '';";
+ }
+%>
+ }
+}
+</SCRIPT>
+(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)"
+ <%= (!$ship_last || $cgi->param('same') eq 'Y') ? 'CHECKED' : '' %>
+ >same as billing address)
+<%= $r=qq!<font color="#ff0000">*</font>&nbsp;!;
+ if (!$ship_last || $cgi->param('same') eq 'Y') {
+ $disabled = 'DISABLED STYLE="background-color: #dddddd"';
+ foreach ( qw( last first company address1 address2 city county state
+ zip country daytime night fax )
+ ) {
+ ${"ship_$_"} = ${$_};
+ }
+ }else{
+ $disabled = '';
+ }
+ $pre = 'ship_';
+ include('contact');
+%>
+
+<INPUT TYPE="submit" NAME="submit" VALUE="<%= $custnum ? "Apply Changes" : "Add Customer" %>">
+<BR>
+</FORM>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/check.html b/fs_selfservice/FS-SelfService/cgi/check.html
new file mode 100644
index 0000000..68753fe
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/check.html
@@ -0,0 +1,54 @@
+<TR>
+ <TD ALIGN="right">Account&nbsp;type</TD>
+ <TD>
+ <SELECT NAME="paytype">
+ <%= foreach ( @paytypes ) {
+ $selected = $paytype eq $_ ? ' SELECTED' : '';
+ $OUT .= qq(<OPTION$selected VALUE="$_">$_\n);
+ } %>
+ </SELECT>
+ </TD>
+</TD><TR>
+ <TD ALIGN="right">Account&nbsp;number</TD>
+ <TD><INPUT TYPE="text" NAME="payinfo1" SIZE=10 MAXLENGTH=20 VALUE="<%=$payinfo1%>"></TD>
+</TD><TR>
+ <TD ALIGN="right">ABA/Routing&nbsp;number</TD>
+ <TD><INPUT TYPE="text" NAME="payinfo2" SIZE=10 MAXLENGTH=9 VALUE="<%=$payinfo2%>"></TD>
+</TR><TR>
+ <TD ALIGN="right">Bank&nbsp;name</TD>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+</TR><TR>
+ <%=
+ $OUT = '';
+ if ($show_paystate) {
+ $OUT .= qq!<TD ALIGN="right">Bank state</TD><TD><SELECT NAME="paystate">!;
+ for ( @states ) {
+ $OUT .= '<OPTION'. ($_ eq $paystate ? ' SELECTED' : '' ). ">$_\n";
+ }
+ $OUT .= '</SELECT></TD></TR><TR>';
+ }
+ %>
+ <%=
+ $OUT = '';
+ if ($show_ss) {
+ $OUT .= '<TD ALIGN="right">Account&nbsp;holder<BR>Social&nbsp;';
+ $OUT .= 'security&nbsp;or&nbsp;tax&nbsp;ID&nbsp;#</TD><TD>';
+ $OUT .= qq!<INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="ss" VALUE="$ss">!;
+ $OUT .= '</TD></TR><TR>';
+ }
+ %>
+ <%=
+ $OUT = '';
+ if ($show_stateid) {
+ $OUT .= '<TD ALIGN="right">';
+ $OUT .= qq!Account&nbsp;holder<BR>$stateid_label</TD><TD>!;
+ $OUT .= qq!<INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="stateid" VALUE="$stateid"></TD>!;
+ $OUT .= qq!<TD ALIGN="right">$stateid_state_label</TD>!;
+ $OUT .= '<TD><SELECT NAME="stateid_state">';
+ for ( @states ) {
+ $OUT .= '<OPTION'. ($_ eq $stateid_state ? ' SELECTED' : '' ). ">$_\n";
+ }
+ $OUT .='</SELECT></TD></TR><TR>';
+ }
+ %>
+</TR>
diff --git a/fs_selfservice/FS-SelfService/cgi/contact.html b/fs_selfservice/FS-SelfService/cgi/contact.html
new file mode 100644
index 0000000..20c15df
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/contact.html
@@ -0,0 +1,135 @@
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<TR>
+ <TH ALIGN="right"><%=$r%>Contact&nbsp;name<BR>(last,&nbsp;first)</TH>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%=$pre%>last" VALUE="<%= ${$pre.'last'} %>" onChange="<%= $onchange %>" <%=$disabled%>> ,
+ <INPUT TYPE="text" NAME="<%=$pre%>first" VALUE="<%= ${$pre.'first'} %>" onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%=$pre%>company" VALUE="<%= ${$pre.'company'} %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right"><%=$r%>Address</TH>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%=$pre%>address1" VALUE="<%= ${$pre.'address1'} %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">
+ <%=
+ my $style =
+ ( $disabled
+ || !$require_address2
+ || ( !$pre && $ship_last )
+ )
+ ? 'visibility:hidden'
+ : '';
+
+ $OUT .= qq!<FONT ID="${pre}address2_required" color="#ff0000" STYLE="$style">*</FONT>&nbsp;<FONT ID="${pre}address2_label" STYLE="$style"><B>Unit&nbsp;#</B></FONT>!;
+ %>
+ </TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%=$pre%>address2" VALUE="<%= ${$pre.'address2'} %>" SIZE=70 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right"><%=$r%>City</TH>
+ <TD>
+ <INPUT TYPE="text" ID="<%=$pre%>city" NAME="<%=$pre%>city" VALUE="<%= ${$pre.'city'} %>" onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ FS::SelfService::regionselector( {
+ prefix => $pre,
+ selected_county => ${$pre.'county'},
+ selected_state => ${$pre.'state'},
+ selected_country => ${$pre.'country'},
+ default_state => $statedefault,
+ default_country => $countrydefault,
+ locales => \@cust_main_county,
+ } );
+
+ $OUT .= qq!<TH ALIGN="right">${r}State/County</TH>!;
+ $OUT .= qq!<TD>$county_html $state_html</TD>!;
+ $OUT .= qq!<TH>${r}Zip</TH>!;
+ $OUT .= qq!<TD><INPUT TYPE="text" NAME="${pre}zip" VALUE="${$pre.'zip'}" SIZE=10 onChange="$onchange" $disabled></TD>!;
+ $OUT .= qq!</TR>!;
+ $OUT .= qq!<TR>!;
+ $OUT .= qq!<TH ALIGN="right">${r}Country</TH>!;
+ $OUT .= qq!<TD COLSPAN=5>$country_html</TD>!;
+ %>
+</TR>
+
+<SCRIPT>
+ <%=
+ if ( $disabled ) {
+ $OUT .= qq!var what = document.getElementById("${pre}city");!;
+ for (qw( county state country ) ) {
+ $OUT .= "what.form.$pre$_.disabled = true;";
+ $OUT .= "what.form.$pre$_.style.backgroundColor = '#dddddd';";
+ }
+ }else{
+ '';
+ }
+ %>
+</SCRIPT>
+
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%=$pre%>daytime" VALUE="<%= ${$pre.'daytime'} %>" SIZE=18 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%=$pre%>night" VALUE="<%= ${$pre.'night'} %>" SIZE=18 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%=$pre%>fax" VALUE="<%= ${$pre.'fax'} %>" SIZE=12 onChange="<%= $onchange %>" <%=$disabled%>>
+ </TD>
+</TR>
+
+</TABLE>
+<%=$r%>required fields<BR>
+
+<!--
+#my($county_html, $state_html, $country_html) =
+# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'),
+# $cust_main->get($pre.'state'),
+# $cust_main->get($pre.'country'),
+# $pre,
+# $onchange,
+# $disabled,
+# );
+
+my %select_hash = (
+ 'county' => ${$pre.'county'},
+ 'state' => ${$pre.'state'},
+ 'country' => ${$pre.'country'},
+ 'prefix' => $pre,
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+);
+
+my @counties = counties( ${$pre.'state'},
+ ${$pre.'country'},
+ );
+my $county_style = scalar(@counties) > 1 ? '' : 'STYLE="visibility:hidden"';
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+-->
diff --git a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi
new file mode 100644
index 0000000..253f853
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi
@@ -0,0 +1,25 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use CGI;
+use FS::SelfService qw( invoice_logo );
+
+my $cgi = new CGI;
+
+my %hash = ();
+if ( $cgi->param('invnum') ) {
+ $hash{$_} = scalar($cgi->param($_)) foreach qw( invnum template );
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^([^\.\/]*)$/ or '' =~ /^()$/;
+ $hash{'template'} = $1;
+}
+
+my $hashref = invoice_logo(%hash);
+
+print $cgi->header( '-type' => $hashref->{'content_type'},
+ '-expires' => 'now',
+ ).
+ $hashref->{'logo'};
+
diff --git a/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html
new file mode 100644
index 0000000..37dccaa
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Change package') %>
+
+<%= include('change_pkg') %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
new file mode 100755
index 0000000..192c29f
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Purchase additional package') %>
+
+<%= include('order_pkg') %>
+
+<%= include('footer') %>
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/decline.html b/fs_selfservice/FS-SelfService/cgi/decline.html
new file mode 100644
index 0000000..c50081e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/decline.html
@@ -0,0 +1,14 @@
+<HTML>
+ <HEAD>
+ <TITLE>Processing error</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+
+<FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account. Please contact customer
+support.
+
+<%= $body_footer %>
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..80a14f8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/delete_svc.html
@@ -0,0 +1,11 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Remove service') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!;
+} else {
+ $OUT .= "<FONT SIZE=4>$svc removed.</FONT>";
+} %>
+
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/footer.html b/fs_selfservice/FS-SelfService/cgi/footer.html
new file mode 100644
index 0000000..4889b74
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/footer.html
@@ -0,0 +1,3 @@
+</TD></TR></TABLE>
+<%= $body_footer %>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/header.html b/fs_selfservice/FS-SelfService/cgi/header.html
new file mode 100644
index 0000000..692348f
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/header.html
@@ -0,0 +1,62 @@
+<HTML>
+ <HEAD>
+ <TITLE><%= $title || 'MyAccount' %></TITLE>
+ <%= $head %>
+ </HEAD>
+ <STYLE TYPE="text/css">
+ body {
+ color: <%= $text_color || '#000000' %>;
+ <%= $font ? "font: $font;" : '' %>
+ }
+ a {
+ color: <%= $link_color || 'blue' %>;
+ <%= $menu_nounderline ? 'text-decoration: none' : '' %>
+ }
+ a:visited {
+ color: <%= $vlink_color || 'purple' %>;
+ <%= $menu_nounderline ? 'text-decoration: none' : '' %>
+ }
+ a:active {
+ color: <%= $alink_color || 'blue' %>;
+ <%= $menu_nounderline ? 'text-decoration: none' : '' %>
+ }
+ a:hover {
+ color: <%= $hlink_color || '' %>;
+ <%= $menu_nounderline ? 'text-decoration: none' : '' %>
+ }
+ </STYLE>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <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>
+ <%= $body_header %>
+
+ <TABLE BORDER=0 WIDTH="100%" CELLPADDING=0 CELLSPACING=0>
+ <TR STYLE="padding:0px">
+ <TD><IMG SRC="image.cgi?logo"></TD>
+ <TD WIDTH = "29%"
+ STYLE = "background: url(image.cgi?title_left_image) no-repeat left center; padding:0px">
+ </TD>
+ <TD WIDTH = "49%"
+ ALIGN="<%= $title_align || 'left' %>"
+ STYLE = "background: url(image.cgi?title_right_image) no-repeat right center; padding:0px">
+ <FONT SIZE = "<%= $title_size || 5 %>"
+ COLOR = "<%= $title_color %>"
+ ><%= $INCLUDE_ARGS[0] %>&nbsp;&nbsp;&nbsp;</FONT>
+ </DIV>
+ </TD>
+ </TR>
+ </TABLE>
+
+ <%= include('myaccount_menu') %>
+ <TD VALIGN="top">
+
diff --git a/fs_selfservice/FS-SelfService/cgi/iframecontentmws.js b/fs_selfservice/FS-SelfService/cgi/iframecontentmws.js
new file mode 100644
index 0000000..f2a91d2
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/iframecontentmws.js
@@ -0,0 +1,59 @@
+/*
+ iframecontentmws.js - Foteos Macrides (author and copyright holder)
+ Initial: October 10, 2004 - Last Revised: January 26, 2008
+ Scripts for using HTML documents as iframe content in overlibmws popups.
+
+ See http://www.macridesweb.com/oltest/IFRAME.html
+ and http://www.macridesweb.com/oltest/AJAX.html#ajaxex3
+ for more information.
+*/
+
+/*
+ Use as lead argument in overlib or overlb2 calls. Include WRAP and
+ TEXTPADDING,0 in the call to ensure that the width arg is respected (unless
+ the CAPTION plus CLOSETEXT widths add up to more than the width arg, in which
+ case you should increase the width arg). The name arg should be a unique
+ string for each popup with iframe content in the document. The frameborder
+ arg should be 1 (browser default if omitted) or 0. The scrolling arg should
+ be 'auto' (default if omitted), 'yes' or 'no'.
+*/
+function OLiframeContent(src, width, height, name, frameborder, scrolling) {
+
+ /* stupid safari iframe location caching... */
+ var d = new Date();
+ var unique = d.getTime() + '' + Math.floor(1000 * Math.random());
+ name = name + '' + unique;
+
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+ +(name!=null?' name="'+name+'" id="'+name+'"':'')
+ +(frameborder!=null?' frameborder="'+frameborder+'"':'')
+ +' scrolling="'+(scrolling!=null?scrolling:'auto')
+ +'"><div>[iframe not supported]</div></iframe>');
+}
+
+/*
+ Swap the src if we are iframe content. The name arg should be the same
+ string as in the OLiframeContent function for the popup. The src arg is
+ a partial, relative, or complete URL for the document to be swapped in.
+*/
+function OLswapIframeSrc(name, src){
+ if(parent==self){
+ alert(src+'\n\n is only for iframe content');
+ return;
+ }
+ var o=parent.OLgetRef(name);
+ if(o)o.src=src;
+ else alert(src+'\n\n is not available');
+}
+
+/*
+ Emulate the Back button if we are iframe content. Use only in documents
+ which are swapped in by using the OLswapIframeSrc function.
+*/
+function OLiframeBack(){
+ if(parent==self){
+ alert('This feature is only for iframe content');
+ return;
+ }
+ history.back();
+}
diff --git a/fs_selfservice/FS-SelfService/cgi/image.cgi b/fs_selfservice/FS-SelfService/cgi/image.cgi
new file mode 100755
index 0000000..e951dcd
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/image.cgi
@@ -0,0 +1,20 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use CGI;
+use FS::SelfService qw( skin_info );
+
+my $cgi = new CGI;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\w+)$/ or '' =~ /^()$/;
+my $name = $1;
+
+my $info = skin_info();
+
+print $cgi->header( '-type' => 'image/png', #for now
+ #'-expires' => 'now',
+ ).
+ $info->{$name};
+
diff --git a/fs_selfservice/FS-SelfService/cgi/images/cross.png b/fs_selfservice/FS-SelfService/cgi/images/cross.png
new file mode 100644
index 0000000..1514d51
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/images/cross.png
Binary files differ
diff --git a/fs_selfservice/FS-SelfService/cgi/images/wait-orange.gif b/fs_selfservice/FS-SelfService/cgi/images/wait-orange.gif
new file mode 100644
index 0000000..92c7f34
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/images/wait-orange.gif
Binary files differ
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..7fe7fa4
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/list_customers.html
@@ -0,0 +1,36 @@
+<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>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/login.html b/fs_selfservice/FS-SelfService/cgi/login.html
new file mode 100644
index 0000000..f7473b1
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/login.html
@@ -0,0 +1,95 @@
+<HTML>
+ <HEAD>
+ <TITLE>Login</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<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">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+
+<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+
+<TR>
+ <TH ALIGN="right">Username </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"><%= $single_domain ? '@'.$single_domain : '' %>
+ </TD>
+</TR>
+
+<%=
+if ( $single_domain ) {
+
+ $OUT .= qq(<INPUT TYPE="hidden" NAME="domain" VALUE="$single_domain">);
+
+} else {
+
+ $OUT .= qq(
+ <TR>
+ <TH ALIGN="right">Domain </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="domain" VALUE="$domain">
+ </TD>
+ </TR>
+ );
+
+}
+
+%>
+
+<TR>
+ <TH ALIGN="right">Password </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+</TR>
+<TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Login"></TD>
+</TR>
+</TABLE>
+</FORM>
+
+<%=
+
+if ( $phone_login ) {
+
+ $box_bgcolor ||= '#c0c0c0';
+
+ $OUT .= qq(
+
+ <B>OR</B><BR><BR>
+
+ <FORM ACTION="$self_url" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="session" VALUE="login">
+ <TABLE BGCOLOR="$box_bgcolor" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+ <TR>
+ <TH ALIGN="right">Phone number </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="$username">
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="domain" VALUE="svc_phone">
+ <TR>
+ <TH ALIGN="right">PIN </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Login"></TD>
+ </TR>
+ </TABLE>
+ </FORM>
+
+ );
+
+}
+
+%>
+
+<%= $body_footer %>
diff --git a/fs_selfservice/FS-SelfService/cgi/logout.html b/fs_selfservice/FS-SelfService/cgi/logout.html
new file mode 100644
index 0000000..5e22ad8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/logout.html
@@ -0,0 +1,13 @@
+<HTML>
+ <HEAD>
+ <TITLE>MyAccount</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+ <FONT SIZE=5>MyAccount</FONT><BR><BR>
+You have been logged out.
+
+<%= $body_footer %>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/make_ach_payment.html b/fs_selfservice/FS-SelfService/cgi/make_ach_payment.html
new file mode 100644
index 0000000..09391e7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/make_ach_payment.html
@@ -0,0 +1,42 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Make a payment') %>
+
+<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="ach_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>
+<%= include('check') %>
+<TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox" <%= $save_unchecked ? '' : 'CHECKED' %> NAME="save" VALUE="1">
+ Remember this information
+ </TD>
+</TR><TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox"<%= $payby eq 'CHEK' ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
+ Charge future payments to this account 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>
+
+<%= include('footer') %>
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..e454647
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/make_payment.html
@@ -0,0 +1,52 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Make a payment') %>
+
+<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>
+ <TH ALIGN="right">Amount&nbsp;Due</TH>
+ <TD COLSPAN=7>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<%=sprintf("%.2f",$balance)%>
+ </TD></TR></TABLE>
+ </TD>
+</TR>
+<TR>
+ <TH ALIGN="right">Payment&nbsp;amount</TH>
+ <TD COLSPAN=7>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<%=sprintf("%.2f",$balance)%>">
+ </TD></TR></TABLE>
+ </TD>
+</TR><TR>
+ <TH ALIGN="right">Card&nbsp;type</TH>
+ <TD COLSPAN=7>
+ <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>
+</TR>
+<%= include('card') %>
+<TR>
+ <TD COLSPAN=8>
+ <INPUT TYPE="checkbox" <%= $save_unchecked ? '' : 'CHECKED' %> NAME="save" VALUE="1">
+ Remember this card and billing address
+ </TD>
+</TR><TR>
+ <TD COLSPAN=8>
+ <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>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html b/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html
new file mode 100755
index 0000000..4055ed0
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/make_thirdparty_payment.html
@@ -0,0 +1,35 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Pay now') %>
+
+<SCRIPT TYPE="text/javascript">
+ function popcollect() {
+ overlib( OLiframeContent('<%= $popup_url %>', 336, 550, 'Secure Payment Area', 0, 'auto' ), CAPTION, 'Pay now', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close' );
+ return false;
+ }
+</SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_draggable.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_crossframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="iframecontentmws.js"></SCRIPT>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+}else{
+ $OUT .= <<EOF;
+ You are about to contact our payment processor to pay $amount.<BR><BR>
+ Your transaction reference number is $reference <BR><BR>
+ <FORM NAME="collect_popper" method="post" action="javascript:void(0)" onSubmit="popcollect()">
+EOF
+
+ my %itemhash = @collectitems;
+ foreach my $input (keys %itemhash) {
+ $OUT .= qq!<INPUT NAME="$input" TYPE="hidden" VALUE="$itemhash{$input}">!;
+ }
+
+ $OUT .= qq!<INPUT NAME="submit" type="submit" value="Pay now">!;
+ $OUT .= qq!</FORM>!;
+}
+%>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/map.gif b/fs_selfservice/FS-SelfService/cgi/map.gif
new file mode 100644
index 0000000..ef884d8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/map.gif
Binary files differ
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/areacodes.cgi b/fs_selfservice/FS-SelfService/cgi/misc/areacodes.cgi
new file mode 100755
index 0000000..b33e58c
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/areacodes.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/areacodes.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/counties.cgi b/fs_selfservice/FS-SelfService/cgi/misc/counties.cgi
new file mode 100755
index 0000000..476fe09
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/counties.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/counties.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/exchanges.cgi b/fs_selfservice/FS-SelfService/cgi/misc/exchanges.cgi
new file mode 100755
index 0000000..d8df970
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/exchanges.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/exchanges.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/part_svc-columns.cgi b/fs_selfservice/FS-SelfService/cgi/misc/part_svc-columns.cgi
new file mode 100755
index 0000000..4ee83ca
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/part_svc-columns.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/part_svc-columns.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/phonenums.cgi b/fs_selfservice/FS-SelfService/cgi/misc/phonenums.cgi
new file mode 100755
index 0000000..e7d695d
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/phonenums.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/phonenums.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/states.cgi b/fs_selfservice/FS-SelfService/cgi/misc/states.cgi
new file mode 100755
index 0000000..f75f2ae
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/states.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/states.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/misc/svc_acct-domains.cgi b/fs_selfservice/FS-SelfService/cgi/misc/svc_acct-domains.cgi
new file mode 100755
index 0000000..c5413ba
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/misc/svc_acct-domains.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+use strict;
+use CGI;
+use FS::SelfService qw( mason_comp );
+
+my $cgi = new CGI;
+
+my $rv = mason_comp( 'comp' => '/misc/svc_acct-domains.cgi',
+ 'query_string' => $cgi->query_string, #pass CGI params...
+ );
+
+#hmm.
+my $output = $rv->{'error'} || $rv->{'output'};
+
+print $cgi->header( '-expires' => 'now' ).
+ $output;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount.html b/fs_selfservice/FS-SelfService/cgi/myaccount.html
new file mode 100644
index 0000000..0de7385
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/myaccount.html
@@ -0,0 +1,100 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'My Account') %>
+
+Hello <%= $name %>!<BR><BR>
+<%= $small_custview %>
+<BR>
+<%= if ( $access_pkgnum ) {
+ $OUT .= qq!Balance: <B>\$$balance</B><BR><BR>!;
+ }
+ '';
+%>
+
+<%= if ( $balance > 0 ) {
+ if (scalar(grep $_, @hide_payment_fields)) {
+ $OUT .= qq! <B><A HREF="${url}make_thirdparty_payment&payby_method=CC">Make a payment</A></B><BR><BR>!;
+ } else {
+ $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><BR>';
+ } else {
+ $OUT .= 'You have no outstanding invoices.<BR><BR>';
+ }
+%>
+
+<%=
+ if ( @support_services ) {
+ $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#ff6666" COLSPAN="3">Support Time Remaining</TH>'.
+ '</TR><TR><TH ALIGN="left">#</TH><TH>Package</TH>'.
+ '<TH>Time Remaining</TH></TR>';
+ my $col1 = "ffffff";
+ my $col2 = "dddddd";
+ my $col = $col1;
+
+ foreach my $support ( @support_services ) {
+ my $td = qq!<TD BGCOLOR="#$col">!;
+ my $a = qq!<A HREF="${url}view_support_details;svcnum=!.
+ $support->{'svcnum'}. '">';
+ $OUT .=
+ "<TR>$td$a". $support->{'pkgnum'}. "</A></TD>".
+ $td.$a. $support->{'pkg'}. "</A></TD>".
+ $td.$a. $support->{'time'}. "</A></TD>".
+ '</TR>';
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+ $OUT .= '</TABLE><BR>';
+ } else {
+ $OUT .= '';
+ }
+%>
+
+<%=
+ if ( @tickets ) {
+ $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Tickets</TH></TR>'.
+ '<TR><TH>#</TH><TH>Subject</TH><TH>Priority</TH><TH>Queue</TH>'.
+ '<TH>Status</TH></TR>';
+ my $col1 = "ffffff";
+ my $col2 = "dddddd";
+ my $col = $col1;
+
+ foreach my $ticket ( @tickets ) {
+ my $td = qq!<TD BGCOLOR="#$col">!;
+ $OUT .=
+ "<TR>$td". $ticket->{'id'}. "</TD>".
+ $td. $ticket->{'subject'}. "</TD>".
+ $td. ($ticket->{'content'} || $ticket->{'priority'}). "</TD>".
+ $td. $ticket->{'queue'}. "</TD>".
+ $td. $ticket->{'status'}. "</TD>".
+ '</TR>';
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+ $OUT .= '</TABLE>';
+ } else {
+ $OUT .= '';
+ }
+%>
+
+<%= include('footer') %>
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..9d33036
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
@@ -0,0 +1,153 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0><TR>
+<TD VALIGN="top" BGCOLOR="<%= $menu_bgcolor || $box_bgcolor || '#c0c0c0' %>">
+
+<TABLE CELLSPACING=0 BORDER=0 HEIGHT="100%">
+
+<%=
+
+if ( $menu_top_image ) {
+ $OUT .= '<TR><TD STYLE="padding:0px"><IMG SRC="image.cgi?menu_top_image"></TD></TR>';
+}
+
+my @menu = (
+ { title=>' ' },
+ { title=>'Overview', url=>'myaccount', size=>'+1', },
+ { title=>' ' },
+ { title=>'Purchase', size=>'+1', },
+);
+
+unless ( $access_pkgnum ) {
+ push @menu,
+ { title=>'Purchase additional package',
+ url=>'customer_order_pkg', 'indent'=>2 };
+}
+
+if ( 1 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
+
+ #XXXFIXME still a bit sloppy for multi-gateway of differing namespace
+ my $i = 0;
+ while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CARD/; $i++ }
+ if ( $cust_paybys[$i] =~ /^CARD/ ) {
+ push @menu, { title => 'Recharge my account with a credit card',
+ url => $hide_payment_fields[$i]
+ ? 'make_thirdparty_payment&payby_method=CC'
+ : 'make_payment',
+ indent => 2,
+ }
+ }
+
+ $i = 0;
+ while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CHEK/; $i++ }
+ if ( $cust_paybys[$i] =~ /^CHEK/ ) {
+ push @menu, { title => 'Recharge my account with a check',
+ url => $hide_payment_fields[$i]
+ ? 'make_thirdparty_payment&payby_method=ECHECK'
+ : 'make_ach_payment',
+ indent => 2,
+ }
+ }
+
+ push @menu, { title => 'Recharge my account with a prepaid card',
+ url => 'recharge_prepay',
+ indent => 2,
+ }
+ if grep(/^PREP/, @cust_paybys);
+
+}
+
+push @menu,
+ { title=>' ' },
+ { title=>'View my usage', url=>'view_usage', size=>'+1', },
+;
+
+unless ( $access_pkgnum ) {
+ push @menu,
+ { title=>'Setup my services', url=>'provision', size=>'+1', },
+ ;
+}
+
+push @menu,
+ { title=>' ' };
+
+push @menu,
+ { title=>'Change my information', size=>'+1', };
+
+unless ( $access_pkgnum ) {
+ push @menu,
+ { title=>'Change billing address', url=>'change_bill', indent=>2 },
+ { title=>'Change service address', url=>'change_ship', indent=>2 },
+ { title=>'Change payment information', url=>'change_pay', indent=>2 },
+ ;
+}
+
+push @menu,
+ { title=>'Change password(s)', url=>'change_password', indent=>2 },
+ { title=>' ' },
+ { title=>'Logout', url=>'logout', size=>'+1', },
+;
+
+foreach my $item ( @menu ) {
+
+ next if $menu_skipblanks && $item->{'title'} =~ /^\s*$/;
+ next if $menu_skipheadings && ! $item->{'url'};
+
+ $OUT .= '<TR><TD';
+ if ( $menu_body_image ) {
+ if ( exists $item->{'url'} && $action eq $item->{'url'} ) {
+ $OUT .= #' BGCOLOR="'. ( $body_bgcolor || '#eeeeee' ). '" '.
+ ' STYLE="background: url(image.cgi?menu_body_image) 0 bottom; '.
+ ' color:#3366CC"; '. #XXX config
+ ' " ';
+ } else {
+ $OUT .= ' STYLE="background: url(image.cgi?menu_body_image) 0 bottom" ';
+ }
+ } else {
+ if ( exists $item->{'url'} && $action eq $item->{'url'} ) {
+ $OUT .= ' BGCOLOR="'. ( $body_bgcolor || '#eeeeee' ). '" '.
+ ' STYLE="border-top: 1px solid black;'.
+ ' border-left: 1px solid black;'.
+ ' border-bottom: 1px solid black"';
+ } else {
+ $OUT .= ' STYLE="border-right: 1px solid black"';
+ }
+ }
+ $OUT.='>';
+
+ if ( $menu_skipheadings ) {
+ $OUT .= '&nbsp;&nbsp;';
+ } else {
+ $OUT .= '&nbsp;' x $item->{'indent'}
+ if exists $item->{'indent'};
+ }
+
+ $OUT .= '<A HREF="'. $url. $item->{'url'}. '">'
+ if exists $item->{'url'} && $action ne $item->{'url'};
+
+ $OUT .= '<FONT SIZE="'. ( $menu_fontsize || $item->{'size'} ). '">'
+ if $menu_fontsize || exists($item->{'size'});
+
+ $item->{'title'} =~ s/ /&nbsp;/g;
+ $OUT .= $item->{'title'};
+
+ $OUT .= '</FONT>'
+ if exists $item->{'size'};
+
+ $OUT .= '</A>'
+ if exists $item->{'url'} && $action ne $item->{'url'};
+
+ $OUT .= '</TD></TR>';
+
+}
+
+if ( $menu_bottom_image ) {
+ $OUT .= '<TR><TD STYLE="padding:0px"><IMG SRC="image.cgi?menu_bottom_image"></TD></TR>';
+} else {
+ $OUT .= '<TR><TD STYLE="border-right: 1px solid black" HEIGHT="100%"><BR><BR><BR><BR></TD></TR>';
+}
+
+%>
+
+</TABLE>
+
+</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..79335a0
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/order_pkg.html
@@ -0,0 +1,47 @@
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+ if ( document.OrderPkgForm.pkgpart_svcpart.selectedIndex > 0 ) {
+ document.OrderPkgForm.submit.disabled = false;
+ } else {
+ document.OrderPkgForm.submit.disabled = true;
+ }
+}
+</SCRIPT>
+
+<%= 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>
+ <TABLE><TR><TD> <%= $pkg_selector %>
+
+ </TD>
+</TR>
+
+<%=
+ if ( 0 ) {
+ 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/overlibmws.js b/fs_selfservice/FS-SelfService/cgi/overlibmws.js
new file mode 100644
index 0000000..df2bd1d
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/overlibmws.js
@@ -0,0 +1,620 @@
+/*
+ Do not remove or change this notice.
+ overlibmws.js core module - Copyright Foteos Macrides 2002-2008. All rights reserved.
+ Initial: August 18, 2002 - Last Revised: March 22, 2008
+ This module is subject to the same terms of usage as for Erik Bosrup's overLIB,
+ though only a minority of the code and API now correspond with Erik's version.
+ See the overlibmws Change History and Command Reference via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+ Give credit on sites that use overlibmws and submit changes so others can use them as well.
+ You can get Erik's version via: http://www.bosrup.com/web/overlib/
+*/
+
+// PRE-INIT -- Ignore these lines, configuration is below.
+var OLloaded=0,OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0,
+OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLmodalPI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0,OLprintPI=0,
+pmCnt=1,pMtr=new Array(),OLcmdLine=new Array(),OLrunTime=new Array(),OLv,OLudf,OLrefXY,
+OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%");if(typeof OLgateOK=='undefined')var OLgateOK=1;
+var OLp1or2c='inarray,caparray,caption,closetext,right,left,center,autostatuscap,padx,pady,below,above,vcenter,donothing',
+OLp1or2co='nofollow,background,offsetx,offsety,fgcolor,bgcolor,cgcolor,textcolor,capcolor,width,wrap,wrapmax,height,border,'
++'base,status,autostatus,snapx,snapy,fixx,fixy,relx,rely,midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,'
++'cgbackground,fullhtml,capicon,textfont,captionfont,textsize,captionsize,timeout,delay,hauto,vauto,nojustx,nojusty,fgclass,'
++'bgclass,cgclass,capbelow,textpadding,textfontclass,captionpadding,captionfontclass,sticky,noclose,mouseoff,offdelay,'
++'closecolor,closefont,closesize,closeclick,closetitle,closefontclass,decode',OLp1or2o='text,cap,close,hpos,vpos,padxl,'
++'padxr,padyt,padyb',OLp1co='label',OLp1or2=OLp1or2co+','+OLp1or2o,OLp1=OLp1co+','+'frame';
+OLregCmds(OLp1or2c+','+OLp1or2co+','+OLp1co);
+function OLud(v){return eval('typeof ol_'+v+'=="undefined"')?1:0;}
+
+// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions
+if(OLud('fgcolor'))var ol_fgcolor="#ccccff";
+if(OLud('bgcolor'))var ol_bgcolor="#333399";
+if(OLud('cgcolor'))var ol_cgcolor="#333399";
+if(OLud('textcolor'))var ol_textcolor="#000000";
+if(OLud('capcolor'))var ol_capcolor="#ffffff";
+if(OLud('closecolor'))var ol_closecolor="#eeeeff";
+if(OLud('textfont'))var ol_textfont="Verdana,Arial,Helvetica";
+if(OLud('captionfont'))var ol_captionfont="Verdana,Arial,Helvetica";
+if(OLud('closefont'))var ol_closefont="Verdana,Arial,Helvetica";
+if(OLud('textsize'))var ol_textsize=1;
+if(OLud('captionsize'))var ol_captionsize=1;
+if(OLud('closesize'))var ol_closesize=1;
+if(OLud('fgclass'))var ol_fgclass="";
+if(OLud('bgclass'))var ol_bgclass="";
+if(OLud('cgclass'))var ol_cgclass="";
+if(OLud('textpadding'))var ol_textpadding=2;
+if(OLud('textfontclass'))var ol_textfontclass="";
+if(OLud('captionpadding'))var ol_captionpadding=2;
+if(OLud('captionfontclass'))var ol_captionfontclass="";
+if(OLud('closefontclass'))var ol_closefontclass="";
+if(OLud('close'))var ol_close="Close";
+if(OLud('closeclick'))var ol_closeclick=0;
+if(OLud('closetitle'))var ol_closetitle="Click to Close";
+if(OLud('text'))var ol_text="Default Text";
+if(OLud('cap'))var ol_cap="";
+if(OLud('capbelow'))var ol_capbelow=0;
+if(OLud('background'))var ol_background="";
+if(OLud('width'))var ol_width=200;
+if(OLud('wrap'))var ol_wrap=0;
+if(OLud('wrapmax'))var ol_wrapmax=0;
+if(OLud('height'))var ol_height= -1;
+if(OLud('border'))var ol_border=1;
+if(OLud('base'))var ol_base=0;
+if(OLud('offsetx'))var ol_offsetx=10;
+if(OLud('offsety'))var ol_offsety=10;
+if(OLud('sticky'))var ol_sticky=0;
+if(OLud('nofollow'))var ol_nofollow=0;
+if(OLud('noclose'))var ol_noclose=0;
+if(OLud('mouseoff'))var ol_mouseoff=0;
+if(OLud('offdelay'))var ol_offdelay=300;
+if(OLud('hpos'))var ol_hpos=RIGHT;
+if(OLud('vpos'))var ol_vpos=BELOW;
+if(OLud('status'))var ol_status="";
+if(OLud('autostatus'))var ol_autostatus=0;
+if(OLud('snapx'))var ol_snapx=0;
+if(OLud('snapy'))var ol_snapy=0;
+if(OLud('fixx'))var ol_fixx= -1;
+if(OLud('fixy'))var ol_fixy= -1;
+if(OLud('relx'))var ol_relx=null;
+if(OLud('rely'))var ol_rely=null;
+if(OLud('midx'))var ol_midx=null;
+if(OLud('midy'))var ol_midy=null;
+if(OLud('ref'))var ol_ref="";
+if(OLud('refc'))var ol_refc='UL';
+if(OLud('refp'))var ol_refp='UL';
+if(OLud('refx'))var ol_refx=0;
+if(OLud('refy'))var ol_refy=0;
+if(OLud('fgbackground'))var ol_fgbackground="";
+if(OLud('bgbackground'))var ol_bgbackground="";
+if(OLud('cgbackground'))var ol_cgbackground="";
+if(OLud('padxl'))var ol_padxl=1;
+if(OLud('padxr'))var ol_padxr=1;
+if(OLud('padyt'))var ol_padyt=1;
+if(OLud('padyb'))var ol_padyb=1;
+if(OLud('fullhtml'))var ol_fullhtml=0;
+if(OLud('capicon'))var ol_capicon="";
+if(OLud('frame'))var ol_frame=self;
+if(OLud('timeout'))var ol_timeout=0;
+if(OLud('delay'))var ol_delay=0;
+if(OLud('hauto'))var ol_hauto=0;
+if(OLud('vauto'))var ol_vauto=0;
+if(OLud('nojustx'))var ol_nojustx=0;
+if(OLud('nojusty'))var ol_nojusty=0;
+if(OLud('label'))var ol_label="";
+if(OLud('decode'))var ol_decode=0;
+// ARRAY CONFIGURATION - See overlibConfig.txt for descriptions.
+if(OLud('texts'))var ol_texts=new Array("Text 0","Text 1");
+if(OLud('caps'))var ol_caps=new Array("Caption 0","Caption 1");
+// END CONFIGURATION -- Don't change anything below, all configuration is above.
+
+// INIT -- Runtime variables.
+var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0,o3_offdelay=300,o3_hpos=RIGHT,
+o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="",o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",
+o3_width=200,o3_wrap=0,o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0,o3_snapy=0,
+o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null,o3_ref="",o3_refc='UL',o3_refp='UL',o3_refx=0,
+o3_refy=0,o3_fgbackground="",o3_bgbackground="",o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0,o3_fullhtml=0,
+o3_vpos=BELOW,o3_capicon="",o3_textfont="Verdana,Arial,Helvetica",o3_captionfont="",o3_closefont="",o3_textsize=1,OLcC=null,
+o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0,o3_hauto=0,o3_vauto=0,o3_nojustx=0,o3_nojusty=0,
+o3_close="",o3_closeclick=0,o3_closetitle="",o3_fgclass="",o3_bgclass="",o3_cgclass="",o3_textpadding=2,o3_textfontclass="",
+o3_captionpadding=2,o3_captionfontclass="",o3_closefontclass="",o3_capbelow=0,o3_label="",o3_decode=0,
+CSSOFF=DONOTHING,CSSCLASS=DONOTHING,over=null,OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0,OLfnRef="",OLhover=0,OLx=0,OLy=0,
+OLshowingsticky=0,OLallowmove=0,OLoverHTML="",OLover2HTML="",OLifRef="",OLo2Ref="",OLifX=0,OLifY=0,
+OLua=(OLv=navigator.userAgent)?OLv.toLowerCase():'',
+OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4)?1:0,
+OLns6=(document.getElementById)?1:0,
+OLie4=(document.all)?1:0,
+OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0,
+OLmac=(OLua.indexOf('mac')>=0)?1:0,
+OLsaf=(OLua.indexOf('safari')>=0)?1:0,
+OLkon=(OLua.indexOf('konqueror')>=0)?1:0,
+OLkht=(OLsaf||OLkon)?1:0,
+OLopr=(OLua.indexOf('opera')>=0)?1:0,
+OLop7=(OLopr&&document.createTextNode)?1:0;
+if(OLopr){OLns4=OLns6=OLgek=0;OLie4=(OLop7)?1:0;}
+var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0,
+OLie5=0,OLie55=0;OLie7=0;if(OLie4&&!OLop7){
+if((OLv=OLua.match(/msie (\d\.\d+)\.*/i))&&(OLv=parseFloat(OLv[1]))>=5.0){
+OLie5=1;OLns6=0;if(OLv>=5.5)OLie55=1;if(OLv>=7.0)OLie7=1;}if(OLns6)OLie4=0;}
+if(OLns4)window.onresize=function(){location.reload();};var OLchkMh=1,OLdw;
+if(OLns4||OLie4||OLns6){OLmh();if(window.addEventListener)window.addEventListener("unload",
+OLulCl,false);}else{overlib=nd=cClick=OLpageDefaults=no_overlib;}
+function OLulCl(){if(over)cClick();window.removeEventListener("unload",OLulCl,false);}
+
+/*
+ PUBLIC FUNCTIONS
+*/
+// Loads defaults then args into runtime variables.
+function overlib(){
+if(!(OLloaded&&OLgateOK))return;if((OLexclusivePI)&&OLisExclusive(arguments))return true;if(OLchkMh)OLmh();
+if(OLndt&&!OLtimerid)OLndt=0;if(over)cClick();if(parent!=self){if(parent.OLo2Ref){parent.OLeval(parent.OLo2Ref);
+parent.OLo2Ref="";}if(parent.OLifRef){parent.OLeval(parent.OLifRef);parent.OLifRef="";}}if(OLo2Ref){eval(OLo2Ref);
+OLo2Ref="";}if(OLifRef){eval(OLifRef);OLifRef="";}OLload(OLp1or2);OLload(OLp1);OLfnRef="";OLifX=0;OLifY=0;OLhover=0;
+OLsetRunTimeVar();OLparseTokens('o3_',arguments);if(!(over=OLmkLyr()))return false;if(o3_decode)OLdecode();if(OLprintPI)
+OLchkPrint();if(OLbubblePI)OLchkForBubbleEffect();if(OLdebugPI)OLsetDebugCanShow();if(OLshadowPI)OLinitShadow();
+if(OLiframePI)OLinitIfs();if(OLfilterPI)OLinitFilterLyr();if(OLexclusivePI&&o3_exclusive&&o3_exclusivestatus!="")
+o3_status=o3_exclusivestatus;else if(o3_autostatus==2&&o3_cap!="")o3_status=o3_cap;else if(o3_autostatus==1&&o3_text!="")
+o3_status=o3_text;if(!o3_delay){return OLmain();}else{OLdelayid=setTimeout("OLmain()",o3_delay);if(o3_status!=""){
+self.status=o3_status;return true;}else if(!(OLop7&&event&&event.type=='mouseover'))return false;}
+}
+function OLeval(s){eval(s);}
+
+// Clears popups if appropriate
+function nd(time){
+if(OLloaded&&OLgateOK){if(!((OLexclusivePI)&&OLisExclusive())){if(time&&over&&!o3_delay){
+if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=(OLhover&&o3_frame==self&&!OLcursorOff())?0:
+setTimeout("cClick()",(o3_timeout=OLndt=time));}else{if(!OLshowingsticky){OLallowmove=0;
+if(over)OLhideObject(over);}}}}return false;
+}
+
+// Close function for stickies
+function cClick(){
+if(OLloaded&&OLgateOK){OLhover=0;if(over){if(OLo2Ref){eval(OLo2Ref);OLo2Ref="";}if(OLovertwoPI&&over==over2)cClick2();
+OLhideObject(over);OLshowingsticky=0;OLallowmove=0;}if(OLmodalPI)OLclearModal();}return false;
+}
+
+// Sets page-specific defaults.
+function OLpageDefaults(){
+OLparseTokens('ol_',arguments);
+}
+
+// Gets object referenced by its id or name
+function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);}
+
+// For unsupported browsers.
+function no_overlib(){return false;}
+
+/*
+ OVERLIB MAIN FUNCTION SET
+*/
+function OLmain(){
+o3_delay=0;if(parent!=self&&o3_frame==parent&&parent.OLscrollPI&&parent.over)parent.OLclearScroll();if(o3_frame==self){
+if(o3_noclose)OLoptMOUSEOFF(0);else if(o3_mouseoff)OLoptMOUSEOFF(1);}if(o3_sticky){OLshowingsticky=1;if(OLfnRef&&
+parent!=self&&o3_frame==parent&&parent.overlib){parent.OLifRef=OLfnRef+'cClick()';}}OLdoLyr();OLallowmove=0;if(o3_timeout>0){
+if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_timeout);}OLchkRef();OLdisp(o3_status);
+if(OLdraggablePI)OLcheckDrag();if(o3_status!="")return true;else if(!(OLop7&&event&&event.type=='mouseover'))return false;
+}
+function OLchkRef(){
+if(o3_ref){OLrefXY=OLgetRefXY(o3_ref);if(OLrefXY[0]==null&&OLcrossframePI)OLchkIfRef();
+if(OLrefXY[0]==null){o3_ref="";o3_midx=0;o3_midy=0;}}
+}
+
+// Loads o3_ variables
+function OLload(c){var i,m=c.split(',');for(i=0;i<m.length;i++)eval('o3_'+m[i]+'=ol_'+m[i]);}
+
+// Chooses LGF
+function OLdoLGF(){
+return (o3_background!=''||o3_fullhtml)?OLcontentBackground(o3_text,o3_background,o3_fullhtml):(o3_cap=="")?
+OLcontentSimple(o3_text):(o3_sticky)?OLcontentCaption(o3_text,o3_cap,o3_close):OLcontentCaption(o3_text,o3_cap,'');
+}
+
+// Makes Layer
+function OLmkLyr(id,f,z){
+id=(id||'overDiv');f=(f||o3_frame);z=(z||1000);var fd=f.document,d=OLgetRefById(id,fd);
+if(!d){if(OLns4)d=fd.layers[id]=new Layer(1024,f);else if(OLie4&&!OLop7){
+fd.body.insertAdjacentHTML('AfterBegin','<div id="'+id+'"></div>');d=fd.all[id];}else{d=fd.createElement('div');
+if(d){d.id=id;fd.body.appendChild(d);}}if(!d)return null;if(OLns4)d.zIndex=z;else{var o=d.style;o.position='absolute';
+o.visibility='hidden';o.zIndex=z;}}return d;
+}
+
+// Creates and writes layer content
+function OLdoLyr(){
+if(o3_sticky&&OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}if(o3_background==''&&!o3_fullhtml){
+if(o3_fgbackground!='')o3_fgbackground=' background="'+o3_fgbackground+'"';
+if(o3_bgbackground!='')o3_bgbackground=' background="'+o3_bgbackground+'"';
+if(o3_cgbackground!='')o3_cgbackground=' background="'+o3_cgbackground+'"';
+if(o3_fgcolor!='')o3_fgcolor=' bgcolor="'+o3_fgcolor+'"';if(o3_bgcolor!='')o3_bgcolor=' bgcolor="'+o3_bgcolor+'"';
+if(o3_cgcolor!='')o3_cgcolor=' bgcolor="'+o3_cgcolor+'"';if(o3_height>0)o3_height=' height="'+o3_height+'"';
+else o3_height='';}if(!OLns4)OLrepositionTo(over,(OLns6?20:0),0);var lyrHtml=OLdoLGF();
+if(o3_wrap&&!o3_fullhtml){OLlayerWrite(lyrHtml);o3_width=(OLns4?over.clip.width:over.offsetWidth);if(OLie4){
+var w=OLfd().clientWidth;if(o3_width>=w){if(OLop7){if(OLovertwoPI&&over==over2){var z=over2.style.zIndex;
+o3_frame.document.body.removeChild(over);over2=OLmkLyr('overDiv2',o3_frame,z);over=over2;}else{
+o3_frame.document.body.removeChild(over);over=OLmkLyr();}}o3_width=w-20;}}
+if(o3_wrapmax<1&&o3_frame.innerWidth)o3_wrapmax=o3_frame.innerWidth-40;
+if(o3_wrapmax>0&&o3_width>o3_wrapmax)o3_width=o3_wrapmax;o3_wrap=0;lyrHtml=OLdoLGF();}OLlayerWrite(lyrHtml);
+o3_width=(OLns4?over.clip.width:over.offsetWidth);if(OLbubblePI)OLgenerateBubble(lyrHtml);
+}
+
+/*
+ LAYER GENERATION FUNCTIONS
+*/
+// Makes simple table without caption
+function OLcontentSimple(txt){
+var t=OLbgLGF()+OLfgLGF(txt)+OLbaseLGF();OLsetBackground('');return t;
+}
+
+// Makes table with caption and optional close link
+function OLcontentCaption(txt,title,close){
+var closing=(OLprintPI?OLprintCapLGF():''),closeevent='onmouseover',caption,t,cC='javascript:return '+OLfnRef
++(OLovertwoPI&&over==over2?'cClick2();':'cClick();');if(o3_closeclick)closeevent=(o3_closetitle?'title="'
++o3_closetitle+'" ':'')+'onclick';if(o3_capicon!=''&&o3_capicon.indexOf('<img')!=0)o3_capicon='<img src="'+o3_capicon
++'" /> ';if(close){closing+='<td align="right"><a href="'+cC+'" '+closeevent+'="'+cC+'"'+(o3_closefontclass?' class="'
++o3_closefontclass+'">':(OLns4?'><':'')+OLlgfUtil(0,1,'','a',o3_closecolor,o3_closefont,o3_closesize))+close+
+(o3_closefontclass?'':(OLns4?OLlgfUtil(1,1,'','a'):''))+'</a></td>';}caption='<table id="overCap'
++(OLovertwoPI&&over==over2?'2':'')+'"'+OLwd(0)+' border="0" cellpadding="'+o3_captionpadding+'" cellspacing="0"'
++(o3_cgclass?' class="'+o3_cgclass+'"':o3_cgcolor+o3_cgbackground)+'><tr><td'+OLwd(0)+(o3_cgclass?' class="'
++o3_cgclass+'">':'>')+(o3_captionfontclass?'<div'+OLhL(1)+' class="'+o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',
+o3_capcolor,o3_captionfont,o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+'</td>'+closing+'</tr></table>';
+t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF();OLsetBackground('');return t;
+}
+
+// For BACKGROUND and FULLHTML commands
+function OLcontentBackground(txt,image,hasfullhtml){
+var t;if(hasfullhtml){t=txt;}else{t='<table'+OLwd(1)+' border="0" cellpadding="0" '+'cellspacing="0" '+'height="'
++o3_height+'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'+o3_padxl+'"></td><td valign="top"'
++OLwd(2)+'>'+OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+OLlgfUtil(1,0,'','div')
++'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'+o3_padyb+'"></td></tr></table>';}
+OLsetBackground(image);return t;
+}
+
+// LGF utilities
+function OLbgLGF(){
+return '<table'+OLwd(1)+o3_height+' border="0" cellpadding="'+o3_border+'" cellspacing="0"'+(o3_bgclass?' class="'
++o3_bgclass+'"':o3_bgcolor+o3_bgbackground)+'><tr><td>';
+}
+function OLfgLGF(t){
+return '<table'+OLwd(0)+o3_height+' border="0" cellpadding="'+o3_textpadding+'" cellspacing="0"'+(o3_fgclass?' class="'
++o3_fgclass+'"':o3_fgcolor+o3_fgbackground)+'><tr><td valign="top"'+(o3_fgclass?' class="'+o3_fgclass+'"':'')+'>'
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t+(OLprintPI?OLprintFgLGF():'')
++OLlgfUtil(1,0,'','div')+'</td></tr></table>';
+}
+function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){
+if(end)return('</'+(OLns4?'font'+(stg?'></strong':''):ele)+'>');else return(tfc?'<div'+OLhL(1)+' class="'+tfc+'">':
+((ele=='a'?'':'<')+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="'+OLquoteMultiNameFonts(fac)+'" size="'
++siz:(ele=='a'?'':ele)+' style="'+((ele=='div')?OLhL(0):'')+'color:'+col+(stg?';font-weight:bold':'')+';font-family:'
++OLquoteMultiNameFonts(fac)+';font-size:'+siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">'));
+}
+function OLquoteMultiNameFonts(f){
+var i,v,pM=f.split(',');for(i=0;i<pM.length;i++){v=pM[i];v=v.replace(/^\s+/,'').replace(/\s+$/,'');
+if(/\s/.test(v) && !/['"]/.test(v)){v="\'"+v+"\'";pM[i]=v;}}return pM.join();
+}
+function OLbaseLGF(){
+return ((o3_base>0&&!o3_wrap)?('<table width="100%" border="0" cellpadding="0" cellspacing="0"'+(o3_bgclass?' class="'
++o3_bgclass+'"':'')+'><tr><td height="'+o3_base+'"></td></tr></table>'):'')+'</td></tr></table>';
+}
+function OLwd(a){return(o3_wrap?'':' width="'+(!a?'100%':(a==1?o3_width:(o3_width-o3_padxl-o3_padxr)))+'"');}
+function OLhL(s){return(s?' style="width:100%;"':'width:100%;');}
+
+// Loads image into the div.
+function OLsetBackground(i){
+if(i==''){if(OLns4)over.background.src=null;else{if(OLns6)over.style.width='';over.style.backgroundImage='none';}}
+else{if(OLns4)over.background.src=i;else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i+')';}}
+}
+
+/*
+ HANDLING FUNCTIONS
+*/
+// Displays layer
+function OLdisp(s){
+if(OLmodalPI&&!o3_modalscroll)OLchkModal();if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();
+OLplaceLayer();if(OLmodalPI&&o3_modalscroll)OLchkModal();if(OLndt)OLshowObject(over);
+else OLshowid=setTimeout("OLshowObject(over)",1);OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s;
+}
+
+// Decides placement of layer.
+function OLplaceLayer(){
+var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0,o=OLfd(),
+nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;
+if(!OLkht&&o&&o.clientWidth)iWd=o.clientWidth;
+else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth));
+if(SB>20)SB=20;iWd=o3_frame.innerWidth;}
+pgLeft=(OLie4)?o.scrollLeft:o3_frame.pageXOffset;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0);
+LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);}
+if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){
+if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='UR'||o3_refp=='LR')X-=5;}
+else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){
+if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else
+if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;}
+}else{if(o3_midx!=null){
+X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx);
+}else{if(o3_relx!=null){
+if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB;
+}else{X=o3_fixx+LM;}}}
+}else{
+if(o3_hauto){
+if(o3_hpos==LEFT&&OLx-pgLeft+OLifX<iWd/2&&OLx-pWd-o3_offsetx+OLifX<pgLeft+LM)o3_hpos=RIGHT;else
+if(o3_hpos==RIGHT&&OLx-pgLeft+OLifX>iWd/2&&OLx+pWd+o3_offsetx+OLifX>pgLeft+iWd-SB)o3_hpos=LEFT;}
+X=(o3_hpos==CENTER)?parseInt(OLx-((pWd+CX)/2)+o3_offsetx):
+(o3_hpos==LEFT)?OLx-o3_offsetx-pWd:OLx+o3_offsetx;
+if(o3_snapx>1){
+snp=X % o3_snapx;
+if(o3_hpos==LEFT){X=X-(o3_snapx+snp);}else{X=X+(o3_snapx-snp);}}X+=OLifX;}
+if(!o3_nojustx&&X+pWd>pgLeft+iWd-SB)
+X=iWd+pgLeft-pWd-SB;if(!o3_nojustx&&X-LM<pgLeft)X=pgLeft+LM;
+pgTop=OLie4?o.scrollTop:o3_frame.pageYOffset;
+if(!OLkht&&!nsb&&o&&o.clientHeight)iHt=o.clientHeight;
+else if(o3_frame.innerHeight)iHt=o3_frame.innerHeight;
+if(OLbubblePI&&o3_bubble)pHt=OLbubbleHt;else pHt=OLns4?over.clip.height:over.offsetHeight;
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){TM=(o3_shadowy<0)?Math.abs(o3_shadowy):0;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)BM=CY=5;else
+BM=(o3_shadowy>0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);}
+if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){
+if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){
+if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else
+if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;}
+}else{if(o3_midy!=null){
+Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy);
+}else{if(o3_rely!=null){
+if(o3_rely>=0)Y=pgTop+o3_rely+TM;else Y=pgTop+o3_rely+iHt-pHt-BM;}else{
+Y=o3_fixy+TM;}}}
+}else{
+if(o3_vauto){
+if(o3_vpos==ABOVE&&OLy-pgTop+OLifY<iHt/2&&OLy-pHt-o3_offsety+OLifY<pgTop)o3_vpos=BELOW;else
+if(o3_vpos==BELOW&&OLy-pgTop+OLifY>iHt/2&&OLy+pHt+o3_offsety+((OLns4||OLkht)?17:0)+OLifY>pgTop+iHt-BM)
+o3_vpos=ABOVE;}Y=(o3_vpos==VCENTER)?parseInt(OLy-((pHt+CY)/2)+o3_offsety):
+(o3_vpos==ABOVE)?OLy-(pHt+o3_offsety+BM):OLy+o3_offsety+TM;
+if(o3_snapy>1){
+snp=Y % o3_snapy;
+if(pHt>0&&o3_vpos==ABOVE){Y=Y-(o3_snapy+snp);}else{Y=Y+(o3_snapy-snp);}}Y+=OLifY;}
+if(!o3_nojusty&&Y+pHt+BM>pgTop+iHt)Y=pgTop+iHt-pHt-BM;if(!o3_nojusty&&Y-TM<pgTop)Y=pgTop+TM;
+OLrepositionTo(over,X,Y);
+if(OLshadowPI)OLrepositionShadow(X,Y);if(OLiframePI)OLrepositionIfs(X,Y);
+if(OLns6&&o3_frame.innerHeight){iHt=o3_frame.innerHeight;OLrepositionTo(over,X,Y);}
+if(OLscrollPI)OLchkScroll(X-pgLeft,Y-pgTop);
+}
+
+// Chooses body or documentElement
+function OLfd(f){
+var fd=((f)?f:o3_frame).document,fdc=fd.compatMode,fdd=fd.documentElement;
+return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement:fd.body;
+}
+
+// Gets location of REFerence object
+function OLgetRefXY(r,d){
+var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of;if(!o)return [null,null];if(OLns4){
+if(typeof o.length!='undefined'&&o.length>1){ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY;}else{
+if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){rXY[0]+=o.x;rXY[1]+=o.y;}
+else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}}}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');}
+of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1];return rXY;
+}
+
+// Seeks REFerence by id
+function OLgetRefById(l,d){
+l=(l||'overDiv');d=(d||o3_frame.document);var j,r;if(d.getElementById)return d.getElementById(l);
+if(OLie4&&d.all)return d.all[l];if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];
+for(j=0;j<d.layers.length;j++){r=OLgetRefById(l,d.layers[j].document);if(r)return r;}}return null;
+}
+
+// Seeks REFerence by name
+function OLgetRefByName(l,d){
+d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'):OLns6?d.getElementsByTagName('iframe'):null;
+if(typeof d.images!='undefined'&&d.images[l])return d.images[l];
+if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l];
+if(v)for(j=0;j<v.length;j++)if(v[j].name==l)return v[j];if(d.layers&&d.layers.length>0)for(j=0;j<d.layers.length;j++){
+r=OLgetRefByName(l,d.layers[j].document);if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}return null;
+}
+
+// Gets layer vs REFerence offsets
+function OLgetRefOffsets(o){
+var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0];pW=(OLbubblePI&&o3_bubble)?
+o3_width:OLns4?over.clip.width:over.offsetWidth;pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?
+over.clip.height:over.offsetHeight;if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height;}
+else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{W=(OLns4)?o.clip.width:o.offsetWidth;
+H=(OLns4)?o.clip.height:o.offsetHeight;}if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);
+H+=2*parseInt(o.border);}if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0];}else if(c=='UR'){
+of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0];}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:
+(p=='LR')?[-pW,H-pH]:[0,H];}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]:[W,H];}return of;
+}
+
+// Gets x or y location of object
+function OLpageLoc(o,t){
+var l=0,s=o;while(o.offsetParent&&o.offsetParent.tagName.toLowerCase()!='html'){l+=o['offset'+t];o=o.offsetParent;}
+l+=o['offset'+t];while(s=s.parentNode){if((s['scroll'+t]>0)&&s.tagName.toLowerCase()=='div')l-=s['scroll'+t];}return l;
+}
+
+// Moves layer
+function OLmouseMove(e){
+var e=(e||event);OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick);OLx=(e.pageX||e.clientX+OLfd().scrollLeft);
+OLy=(e.pageY||e.clientY+OLfd().scrollTop);if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById()||(OLovertwoPI&&
+over2==over&&over==OLgetRefById('overDiv2')))){OLplaceLayer();if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}if(OLhover&&over&&
+o3_frame==self&&OLcursorOff())if(o3_offdelay<1)OLcC();else{if(OLtimerid>0)clearTimeout(OLtimerid);
+OLtimerid=setTimeout("OLcC()",o3_offdelay);}
+}
+
+// Capture mouse and chain other scripts.
+function OLmh(){
+var fN,f,j,k,s,mh=OLmouseMove,w=(OLns4&&window.onmousemove),re=/function[ ]*(\w*)\(/;OLdw=document;if(document.onmousemove||
+w){if(w)OLdw=window;f=OLdw.onmousemove.toString();fN=f.match(re);if(!fN||fN[1]=='anonymous'||fN[1]=='OLmouseMove'){OLchkMh=0;
+return;}if(fN[1])s=fN[1]+'(e)';else{j=f.indexOf('{');k=f.lastIndexOf('}')+1;s=f.substring(j,k);}s+=';OLmouseMove(e);';
+mh=new Function('e',s);}OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE);
+}
+
+/*
+ PARSING
+*/
+function OLparseTokens(pf,ar){
+var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);
+for(i=0;i<ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;}
+else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0;}else{
+if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[++i]+']');continue;}
+if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[++i]+']');continue;}
+if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;}
+if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;}
+if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;}
+if(ar[i]==BACKGROUND){q(ar[++i],pf+'background');continue;}
+if(Math.abs(ar[i])==NOCLOSE){t(ar[i],pf+'noclose');continue;}
+if(Math.abs(ar[i])==MOUSEOFF){t(ar[i],pf+'mouseoff');continue;}
+if(ar[i]==OFFDELAY){p(ar[++i],pf+'offdelay');continue;}
+if(ar[i]==RIGHT||ar[i]==LEFT||ar[i]==CENTER){p(ar[i],pf+'hpos');continue;}
+if(ar[i]==OFFSETX){p(ar[++i],pf+'offsetx');continue;}
+if(ar[i]==OFFSETY){p(ar[++i],pf+'offsety');continue;}
+if(ar[i]==FGCOLOR){q(ar[++i],pf+'fgcolor');continue;}
+if(ar[i]==BGCOLOR){q(ar[++i],pf+'bgcolor');continue;}
+if(ar[i]==CGCOLOR){q(ar[++i],pf+'cgcolor');continue;}
+if(ar[i]==TEXTCOLOR){q(ar[++i],pf+'textcolor');continue;}
+if(ar[i]==CAPCOLOR){q(ar[++i],pf+'capcolor');continue;}
+if(ar[i]==CLOSECOLOR){q(ar[++i],pf+'closecolor');continue;}
+if(ar[i]==WIDTH){p(ar[++i],pf+'width');continue;}
+if(Math.abs(ar[i])==WRAP){t(ar[i],pf+'wrap');continue;}
+if(ar[i]==WRAPMAX){p(ar[++i],pf+'wrapmax');continue;}
+if(ar[i]==HEIGHT){p(ar[++i],pf+'height');continue;}
+if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;}
+if(ar[i]==BASE){p(ar[++i],pf+'base');continue;}
+if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;}
+if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}
+if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}
+if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;}
+if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;}
+if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;}
+if(ar[i]==FIXX){p(ar[++i],pf+'fixx');continue;}
+if(ar[i]==FIXY){p(ar[++i],pf+'fixy');continue;}
+if(ar[i]==RELX){p(ar[++i],pf+'relx');continue;}
+if(ar[i]==RELY){p(ar[++i],pf+'rely');continue;}
+if(ar[i]==MIDX){p(ar[++i],pf+'midx');continue;}
+if(ar[i]==MIDY){p(ar[++i],pf+'midy');continue;}
+if(ar[i]==REF){q(ar[++i],pf+'ref');continue;}
+if(ar[i]==REFC){q(ar[++i],pf+'refc');continue;}
+if(ar[i]==REFP){q(ar[++i],pf+'refp');continue;}
+if(ar[i]==REFX){p(ar[++i],pf+'refx');continue;}
+if(ar[i]==REFY){p(ar[++i],pf+'refy');continue;}
+if(ar[i]==FGBACKGROUND){q(ar[++i],pf+'fgbackground');continue;}
+if(ar[i]==BGBACKGROUND){q(ar[++i],pf+'bgbackground');continue;}
+if(ar[i]==CGBACKGROUND){q(ar[++i],pf+'cgbackground');continue;}
+if(ar[i]==PADX){p(ar[++i],pf+'padxl');p(ar[++i],pf+'padxr');continue;}
+if(ar[i]==PADY){p(ar[++i],pf+'padyt');p(ar[++i],pf+'padyb');continue;}
+if(Math.abs(ar[i])==FULLHTML){t(ar[i],pf+'fullhtml');continue;}
+if(ar[i]==BELOW||ar[i]==ABOVE||ar[i]==VCENTER){p(ar[i],pf+'vpos');continue;}
+if(ar[i]==CAPICON){q(ar[++i],pf+'capicon');continue;}
+if(ar[i]==TEXTFONT){q(ar[++i],pf+'textfont');continue;}
+if(ar[i]==CAPTIONFONT){q(ar[++i],pf+'captionfont');continue;}
+if(ar[i]==CLOSEFONT){q(ar[++i],pf+'closefont');continue;}
+if(ar[i]==TEXTSIZE){q(ar[++i],pf+'textsize');continue;}
+if(ar[i]==CAPTIONSIZE){q(ar[++i],pf+'captionsize');continue;}
+if(ar[i]==CLOSESIZE){q(ar[++i],pf+'closesize');continue;}
+if(ar[i]==TIMEOUT){p(ar[++i],pf+'timeout');continue;}
+if(ar[i]==DELAY){p(ar[++i],pf+'delay');continue;}
+if(Math.abs(ar[i])==HAUTO){t(ar[i],pf+'hauto');continue;}
+if(Math.abs(ar[i])==VAUTO){t(ar[i],pf+'vauto');continue;}
+if(Math.abs(ar[i])==NOJUSTX){t(ar[i],pf+'nojustx');continue;}
+if(Math.abs(ar[i])==NOJUSTY){t(ar[i],pf+'nojusty');continue;}
+if(Math.abs(ar[i])==CLOSECLICK){t(ar[i],pf+'closeclick');continue;}
+if(ar[i]==CLOSETITLE){q(ar[++i],pf+'closetitle');continue;}
+if(ar[i]==FGCLASS){q(ar[++i],pf+'fgclass');continue;}
+if(ar[i]==BGCLASS){q(ar[++i],pf+'bgclass');continue;}
+if(ar[i]==CGCLASS){q(ar[++i],pf+'cgclass');continue;}
+if(ar[i]==TEXTPADDING){p(ar[++i],pf+'textpadding');continue;}
+if(ar[i]==TEXTFONTCLASS){q(ar[++i],pf+'textfontclass');continue;}
+if(ar[i]==CAPTIONPADDING){p(ar[++i],pf+'captionpadding');continue;}
+if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;}
+if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;}
+if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;}
+if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;}
+if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;}
+if(ar[i]==DONOTHING){continue;}
+i=OLparseCmdLine(pf,i,ar);}}
+if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function();
+if(pf=='o3_')OLfontSize();
+}
+function OLpar(a,v){eval(v+'='+a);}
+function OLparQuo(a,v){eval(v+"='"+OLescSglQt(a)+"'");}
+function OLescSglQt(s){return s.toString().replace(/\\/g,"\\\\").replace(/'/g,"\\'");}
+function OLtoggle(a,v){eval(v+'=('+v+'==0&&'+a+'>=0)?1:0');}
+function OLhasDims(s){return /[%\-a-z]+$/.test(s);}
+function OLfontSize(){
+var i;if(OLhasDims(o3_textsize)){if(OLns4)o3_textsize="2";}else
+if(!OLns4){i=parseInt(o3_textsize);o3_textsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else
+if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else
+if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLprintPI)OLprintDims();
+}
+function OLdecode(){
+var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826)&&typeof decodeURIComponent?
+decodeURIComponent:u;if(typeof(window.TypeError)=='function'){if(re.test(t)){eval(new Array('try{','o3_text=d(t);',
+'}catch(e){','o3_text=u(t);','}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){',
+'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);}
+}
+
+/*
+ LAYER FUNCTIONS
+*/
+// Writes to layer
+function OLlayerWrite(t){
+t+="\n";if(OLns4){over.document.write(t);over.document.close();}else if(typeof over.innerHTML!='undefined'){
+if(OLieM)over.innerHTML='';over.innerHTML=t;}else{var range=o3_frame.document.createRange();range.setStartAfter(over);
+var domfrag=range.createContextualFragment(t);while(over.hasChildNodes()){over.removeChild(over.lastChild);}
+over.appendChild(domfrag);}if(OLovertwoPI&&over==over2)OLover2HTML=t;else OLoverHTML=t;
+if(OLprintPI)over.print=o3_print?t:null;
+}
+
+// Makes object visible
+function OLshowObject(o){
+OLshowid=0;o=(OLns4)?o:o.style;if(((OLfilterPI)&&!OLchkFilter(o))||!OLfilterPI)o.visibility="visible";
+if(OLshadowPI)OLshowShadow();if(OLiframePI)OLshowIfs();if(OLhidePI)OLhideUtil(1,1,0);
+}
+
+// Hides object
+function OLhideObject(o){
+if(OLshowid>0){clearTimeout(OLshowid);OLshowid=0;}if(OLtimerid>0)clearTimeout(OLtimerid);
+if(OLdelayid>0)clearTimeout(OLdelayid);OLtimerid=0;OLdelayid=0;self.status="";o3_label=ol_label;
+if(o3_frame!=self)o=OLgetRefById();if(o){if(o.onmouseover)o.onmouseover=null;if(OLscrollPI&&o==over)OLclearScroll();
+if(OLdraggablePI)OLclearDrag();if(OLfilterPI)OLcleanupFilter(o);if(OLshadowPI)OLhideShadow();var os=(OLns4)?o:o.style;
+if(((OLfilterPI)&&!OLchkFadeOut(os))||!OLfilterPI){os.visibility="hidden";if(!OLie55||!OLfilterPI||!o3_filter||
+o3_fadeout<0)o.innerHTML='';}if(OLhidePI&&o==over)OLhideUtil(0,0,1);if(OLiframePI)OLhideIfs(o);}
+}
+
+// Moves layer
+function OLrepositionTo(o,xL,yL){
+o=(OLns4)?o:o.style;o.left=(OLns4?xL:xL+'px');o.top=(OLns4?yL:yL+'px');
+}
+
+// Handle NOCLOSE-MOUSEOFF
+function OLoptMOUSEOFF(c){
+if(!c)o3_close="";
+over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}}
+}
+function OLcursorOff(){
+var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight,left=parseInt(o.left),top=parseInt(o.top),
+right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);
+if(OLx<left||OLx>right||OLy<top||OLy>bottom)return true;return false;
+}
+
+/*
+ REGISTRATION
+*/
+function OLsetRunTimeVar(){
+if(OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();
+}
+function OLparseCmdLine(pf,i,ar){
+if(OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){var j=OLcmdLine[k](pf,i,ar);if(j>-1){i=j;break;}}}return i;
+}
+function OLregCmds(c){
+if(typeof c!='string')return;var pM=c.split(',');pMtr=pMtr.concat(pM);
+for(var i=0;i<pM.length;i++)eval(pM[i].toUpperCase()+'='+pmCnt++);
+}
+function OLregRunTimeFunc(f){
+if(typeof f=='object')OLrunTime=OLrunTime.concat(f);else OLrunTime[OLrunTime.length++]=f;
+}
+function OLregCmdLineFunc(f){
+if(typeof f=='object')OLcmdLine=OLcmdLine.concat(f);else OLcmdLine[OLcmdLine.length++]=f;
+}
+
+OLloaded=1;
diff --git a/fs_selfservice/FS-SelfService/cgi/overlibmws_crossframe.js b/fs_selfservice/FS-SelfService/cgi/overlibmws_crossframe.js
new file mode 100644
index 0000000..dd64223
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/overlibmws_crossframe.js
@@ -0,0 +1,53 @@
+/*
+ overlibmws_crossframe.js plug-in module - Copyright Foteos Macrides 2003-2008. All rights reserved.
+ For support of FRAME.
+ Initial: August 3, 2003 - Last Revised: January 16, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+OLregCmds('frame');
+
+function OLparseCrossframe(pf,i,ar){
+var k=i,v;
+if(k<ar.length){
+if(ar[k]==FRAME){v=ar[++k];if(pf=='ol_')ol_frame=v;else OLoptFRAME(v);return k;}}
+return -1;
+}
+
+function OLgetFrameRef(thisFrame,ofrm){
+var i,v,retVal='';for(i=0;i<thisFrame.length;i++){if((((thisFrame[i].length>0)))&&(((OLns4))||
+((OLie4)&&(v=thisFrame[i].document.all.tags('iframe'))!=null&&v.length==0)||
+((OLns6)&&(v=thisFrame[i].document.getElementsByTagName('iframe'))!=null&&v.length==0))){
+retVal=OLgetFrameRef(thisFrame[i],ofrm);if(retVal=='')continue;}
+else if(thisFrame[i]!=ofrm)continue;retVal='['+i+']'+retVal;break;}
+return retVal;
+}
+
+function OLoptFRAME(frm){
+o3_frame=OLmkLyr('overDiv',frm)?frm:self;if(o3_frame!=self){var l,tFrm=OLgetFrameRef(top.frames,o3_frame),
+sFrm=OLgetFrameRef(top.frames,ol_frame);if(sFrm.length==tFrm.length) {l=tFrm.lastIndexOf('[');if(l){
+while(sFrm.substring(0,l)!=tFrm.substring(0,l))l=tFrm.lastIndexOf('[',l-1);tFrm=tFrm.substr(l);sFrm=sFrm.substr(l);}}
+var i,k,cnt=0,p='',str=tFrm;while((k=str.lastIndexOf('['))!= -1){cnt++;str=str.substring(0,k);}
+for(i=0;i<cnt;i++)p=p+'parent.';OLfnRef=p+'frames'+sFrm+'.';var n=window.name,o;
+if((n&&parent!=self&&o3_frame==parent)&&(o=OLgetRef(n,parent.document))){if(OLie4&&!OLop7){
+OLx=event.clientX+OLfd().scrollLeft;OLy=event.clientY+OLfd().scrollTop;}
+OLifX=OLpageLoc(o,'Left')-(OLie4&&!OLop7?OLfd().scrollLeft:self.pageXOffset);
+OLifY=OLpageLoc(o,'Top')-(OLie4&&!OLop7?OLfd().scrollTop:self.pageYOffset);}}
+}
+
+function OLchkIfRef(){
+var n=(parent!=self&&o3_frame==parent)?window.name:'',o=n?OLgetRef(n):null;
+if(o){var oR=OLgetRef(o3_ref,document);if(oR){OLrefXY=OLgetRefXY(o3_ref,document);
+OLrefXY[0]+=(OLpageLoc(o,'Left')-(OLie4&&!OLop7?OLfd(self).scrollLeft:self.pageXOffset));
+OLrefXY[1]+=(OLpageLoc(o,'Top')-(OLie4&&!OLop7?OLfd(self).scrollTop:self.pageYOffset));}}
+}
+
+OLregCmdLineFunc(OLparseCrossframe);
+
+OLcrossframePI=1;
+OLloaded=1;
diff --git a/fs_selfservice/FS-SelfService/cgi/overlibmws_draggable.js b/fs_selfservice/FS-SelfService/cgi/overlibmws_draggable.js
new file mode 100644
index 0000000..1bf0ecf
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/overlibmws_draggable.js
@@ -0,0 +1,85 @@
+/*
+ overlibmws_draggable.js plug-in module - Copyright Foteos Macrides 2002-2008. All rights reserved.
+ For support of the DRAGGABLE feature.
+ Initial: August 24, 2002 - Last Revised: January 26, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+var OLdraggableCmds='draggable,dragcap,dragid';
+OLregCmds(OLdraggableCmds);
+
+// DEFAULT CONFIGURATION
+if(OLud('draggable'))var ol_draggable=0;
+if(OLud('dragcap'))var ol_dragcap=0;
+if(OLud('dragid'))var ol_dragid='';
+// END CONFIGURATION
+
+var o3_draggable=0,o3_dragcap=0,o3_dragid='',o3_dragging=0,OLdrg=null,OLmMv,
+OLcX,OLcY,OLcbX,OLcbY;function OLloadDraggable(){OLload(OLdraggableCmds);}
+function OLparseDraggable(pf,i,ar){var t=OLtoggle,k=i;if(k<ar.length){
+if(Math.abs(ar[k])==DRAGGABLE){t(ar[k],pf+'draggable');return k;}
+if(Math.abs(ar[k])==DRAGCAP){t(ar[k],pf+'dragcap');return k;}
+if(ar[k]==DRAGID){OLparQuo(ar[++k],pf+'dragid');return k;}}return -1;
+}
+
+function OLcheckDrag(){
+if(o3_draggable){if(o3_sticky&&(o3_frame==self))OLinitDrag();else o3_draggable=0;}
+}
+function OLinitDrag(){
+OLmMv=OLdw.onmousemove;o3_dragging=0;
+if(OLns4){document.captureEvents(Event.MOUSEDOWN|Event.CLICK);
+document.onmousedown=OLgrabEl;document.onclick=function(e){return routeEvent(e);}}
+else{var dvido=(o3_dragid)?OLgetRef(o3_dragid):null,capid=(OLovertwoPI&&over==over2?
+'overCap2':'overCap');if(dvido)dvido.onscroll=function(){OLdw.onmousemove=OLmMv;
+OLinitDrag();};OLdrg=(o3_cap&&o3_dragcap)?OLgetRef(capid):over;
+if(!OLdrg||!OLdrg.style)OLdrg=over;OLdrg.onmousedown=OLgrabEl;OLsetDrgCur(1);}
+}
+function OLsetDrgCur(d){if(!OLns4&&OLdrg)OLdrg.style.cursor=(d?'move':'auto');}
+
+function OLgrabEl(e){
+var e=(e||event);
+var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(e.altKey||(OLop7&&e.ctrlKey)));o3_dragging=1;
+if(cKy){OLsetDrgCur(0);document.onmouseup=function(){OLsetDrgCur(1);o3_dragging=0;}
+return(OLns4?routeEvent(e):true);}
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(OLie4)over.onselectstart=function(){return false;}
+if(OLns4){OLcX=OLx;OLcY=OLy;document.captureEvents(Event.MOUSEUP)}else{
+OLcX=OLx-(OLns4?over.left:parseInt(over.style.left));
+OLcY=OLy-(OLns4?over.top:parseInt(over.style.top));
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLcbX=OLx-(parseInt(bkdrop.style.left));
+OLcbY=OLy-(parseInt(bkdrop.style.top));}}OLdw.onmousemove=OLmoveEl;
+document.onmouseup=function(){
+if(OLie4)over.onselectstart=null;o3_dragging=0;OLdw.onmousemove=OLmMv;}
+return(OLns4?routeEvent(e):false);
+}
+
+function OLmoveEl(e){
+var e=(e||event);
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(o3_dragging){if(OLns4){over.moveBy(OLx-OLcX,OLy-OLcY);
+if(OLshadowPI&&bkdrop&&o3_shadow)bkdrop.moveBy(OLx-OLcX,OLy-OLcY);}
+else{OLrepositionTo(over,OLx-OLcX,OLy-OLcY);
+if((OLiframePI)&&OLie55&&OLifsP1)OLrepositionTo(OLifsP1,OLx-OLcX,OLy-OLcY);
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLrepositionTo(bkdrop,OLx-OLcbX,OLy-OLcbY);
+if((OLiframePI)&&OLie55&&OLifsSh)OLrepositionTo(OLifsSh,OLx-OLcbX,OLy-OLcbY);}}
+if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}if(OLns4){OLcX=OLx;OLcY=OLy;}
+return false;
+}
+
+function OLclearDrag(){
+if(OLns4){document.releaseEvents(Event.MOUSEDOWN|Event.MOUSEUP|Event.CLICK);
+document.onmousedown=document.onclick=null;}else{
+if(OLdrg)OLdrg.onmousedown=null;over.onmousedown=null;OLsetDrgCur(0);}
+document.onmouseup=null;o3_dragging=0;
+}
+
+OLregRunTimeFunc(OLloadDraggable);
+OLregCmdLineFunc(OLparseDraggable);
+
+OLdraggablePI=1;
+OLloaded=1;
diff --git a/fs_selfservice/FS-SelfService/cgi/overlibmws_iframe.js b/fs_selfservice/FS-SelfService/cgi/overlibmws_iframe.js
new file mode 100644
index 0000000..4c937d3
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/overlibmws_iframe.js
@@ -0,0 +1,93 @@
+/*
+ overlibmws_iframe.js plug-in module - Copyright Foteos Macrides 2003-2008. All rights reserved.
+ Masks system controls to prevent obscuring of popops for IE v5.5 or higher.
+ Initial: October 19, 2003 - Last Revised: January 26, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+
+var OLifsP1=null,OLifsSh=null,OLifsP2=null;
+
+// IFRAME SHIM SUPPORT FUNCTIONS
+function OLinitIfs(){
+if(!OLie55)return;
+if((OLovertwoPI)&&over2&&over==over2){
+var o=o3_frame.document.all['overIframeOvertwo'];
+if(!o||OLifsP2!=o){OLifsP2=null;OLgetIfsP2Ref();}return;}
+o=o3_frame.document.all['overIframe'];
+if(!o||OLifsP1!=o){OLifsP1=null;OLgetIfsRef();}
+if((OLshadowPI)&&o3_shadow){o=o3_frame.document.all['overIframeShadow'];
+if(!o||OLifsSh!=o){OLifsSh=null;OLgetIfsShRef();}}
+}
+
+function OLsetIfsRef(o,i,z){
+o.id=i;o.src='javascript:false;';o.scrolling='no';var os=o.style;os.position='absolute';
+os.top='0px';os.left='0px';os.width='1px';os.height='1px';os.visibility='hidden';
+os.zIndex=over.style.zIndex-z;os.filter='Alpha(style=0,opacity=0)';
+}
+
+function OLgetIfsRef(){
+if(OLifsP1||!OLie55)return;
+OLifsP1=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP1,'overIframe',2);
+o3_frame.document.body.appendChild(OLifsP1);
+}
+
+function OLgetIfsShRef(){
+if(OLifsSh||!OLie55)return;
+OLifsSh=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsSh,'overIframeShadow',3);
+o3_frame.document.body.appendChild(OLifsSh);
+}
+
+function OLgetIfsP2Ref(){
+if(OLifsP2||!OLie55)return;
+OLifsP2=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP2,'overIframeOvertwo',1);
+o3_frame.document.body.appendChild(OLifsP2);
+}
+
+function OLsetDispIfs(o,w,h){
+var os=o.style;
+os.width=w+'px';os.height=h+'px';os.clip='rect(0px '+w+'px '+h+'px 0px)';
+o.filters.alpha.enabled=true;
+}
+
+function OLdispIfs(){
+if(!OLie55)return;
+var wd=over.offsetWidth,ht=over.offsetHeight;
+if(OLfilterPI&&o3_filter&&o3_filtershadow){wd+=5;ht+=5;}
+if((OLovertwoPI)&&over2&&over==over2){
+if(!OLifsP2)return;
+OLsetDispIfs(OLifsP2,wd,ht);return;}
+if(!OLifsP1)return;
+OLsetDispIfs(OLifsP1,wd,ht);
+if((!OLshadowPI)||!o3_shadow||!OLifsSh)return;
+OLsetDispIfs(OLifsSh,wd,ht);
+}
+
+function OLshowIfs(){
+if(OLifsP1){OLifsP1.style.visibility="visible";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="visible";}
+}
+
+function OLhideIfs(o){
+if(!OLie55||o!=over)return;
+if(OLifsP1)OLifsP1.style.visibility="hidden";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="hidden";
+}
+
+function OLrepositionIfs(X,Y){
+if(OLie55){if((OLovertwoPI)&&over2&&over==over2){
+if(OLifsP2)OLrepositionTo(OLifsP2,X,Y);}
+else{if(OLifsP1){OLrepositionTo(OLifsP1,X,Y);if((OLshadowPI)&&o3_shadow&&OLifsSh)
+OLrepositionTo(OLifsSh,X+o3_shadowx,Y+o3_shadowy);}}}
+}
+
+OLiframePI=1;
+OLloaded=1;
diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.cgi b/fs_selfservice/FS-SelfService/cgi/passwd.cgi
new file mode 100755
index 0000000..1e6e2e5
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/passwd.cgi
@@ -0,0 +1,60 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+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..9cdb65e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/payment_results.html
@@ -0,0 +1,10 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Payment results') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!;
+} else {
+ $OUT .= 'Your payment was processed successfully. Thank you.';
+} %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_bill.html b/fs_selfservice/FS-SelfService/cgi/process_change_bill.html
new file mode 100644
index 0000000..bf7ad77
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_change_bill.html
@@ -0,0 +1,4 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Information updated successfully') %>
+<FONT SIZE=4>Information updated successfully.</FONT>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_password.html b/fs_selfservice/FS-SelfService/cgi/process_change_password.html
new file mode 100644
index 0000000..4eca91f
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_change_password.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', "Password changed" ) %>
+
+<FONT SIZE=4>Password changed for <%= $value %> <%= $label %>.</FONT>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_pay.html b/fs_selfservice/FS-SelfService/cgi/process_change_pay.html
new file mode 100644
index 0000000..e399aea
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_change_pay.html
@@ -0,0 +1,4 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Information updated successfully' ) %>
+<FONT SIZE=4>Information updated successfully.</FONT>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html
new file mode 100644
index 0000000..bf15b6e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html
@@ -0,0 +1,4 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Package change successful') %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_ship.html b/fs_selfservice/FS-SelfService/cgi/process_change_ship.html
new file mode 100644
index 0000000..bf7ad77
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_change_ship.html
@@ -0,0 +1,4 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Information updated successfully') %>
+<FONT SIZE=4>Information updated successfully.</FONT>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html
new file mode 100755
index 0000000..649d920
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Package order successful') %>
+
+<FONT SIZE=4>Package order successful.</FONT>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html b/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html
new file mode 100644
index 0000000..4a16ec5
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', "$svc recharged successfully") %>
+
+<FONT SIZE=4><%= $svc %> recharged successfully.</FONT>
+
+<%= include('footer') %>
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..d6515e7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', "$svc setup successfully") %>
+
+<FONT SIZE=4><%= $svc %> setup successfully.</FONT>
+
+<%= include('footer') %>
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..c20aae0
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html
@@ -0,0 +1,8 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', "$svc setup successfully") %>
+
+<FONT SIZE=4><%= $svc %> setup successfully.</FONT>
+
+<BR><BR>Your serial number is <%= sprintf("%010d-$title", $id) %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/promocode.html b/fs_selfservice/FS-SelfService/cgi/promocode.html
new file mode 100644
index 0000000..f8ee7f6
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/promocode.html
@@ -0,0 +1,14 @@
+<HTML><HEAD><TITLE>ISP Signup</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>ISP Signup - promotional code</FONT><BR><BR>
+<SCRIPT>
+function gotoURL(object) {
+ window.location.href = 'signup.cgi?promo_code=' + object.promo_code.value;
+}
+</SCRIPT>
+<FORM>
+Enter promotional code <INPUT TYPE="text" NAME="promo_code">
+<INPUT type="submit" VALUE="Signup" onClick="gotoURL(this.form)">
+
+</FORM>
+</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..7c0f861
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Setup my services') %>
+
+<%= include('provision_list') %>
+
+<%= include('footer') %>
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..88d1c84
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision_list.html
@@ -0,0 +1,92 @@
+<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=2>'.
+ $pkg->{'pkg'}. '</TH><TH BGCOLOR="#6666ff" >' .
+ qq!(<A style="font-size: smaller;color: #000000" HREF="! .
+ qq!${url}customer_change_pkg;pkgnum=$pkg->{'pkgnum'};pkg=$pkg->{'pkg'}">! .
+ 'change</A>)</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 permanently 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>'
+ #self-service only supports these services so far
+ if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external );
+
+ $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..bae5730
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Setup account') %>
+
+<%= include('svc_acct') %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_prepay.html b/fs_selfservice/FS-SelfService/cgi/recharge_prepay.html
new file mode 100644
index 0000000..c716e82
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/recharge_prepay.html
@@ -0,0 +1,30 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Recharge with prepaid card') %>
+
+<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="recharge_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">Prepaid&nbsp;card&nbsp;number</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="prepaid_cardnum" SIZE=20 MAXLENGTH=19 VALUE="<%=$prepaid_cardnum%>">
+ </TD>
+</TR>
+</TABLE>
+<BR>
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%=$paybatch%>">
+<INPUT TYPE="submit" NAME="process" VALUE="Recharge"> <!-- onClick="this.disabled=true"> -->
+</FORM>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_results.html b/fs_selfservice/FS-SelfService/cgi/recharge_results.html
new file mode 100644
index 0000000..147b66b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/recharge_results.html
@@ -0,0 +1,18 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Recharge results') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your prepaid card: $error</FONT>!;
+} else {
+ $OUT .= 'Prepaid card recharge successful!<BR><BR>';
+
+ $OUT .= '$'. sprintf('%.2f', $amount). ' added to your account.<BR><BR>'
+ if $amount;
+
+ $OUT .= $duration. ' added to your account.<BR><BR>'
+ if $seconds;
+
+ $OUT .= 'Thank you.';
+} %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/regcode.html b/fs_selfservice/FS-SelfService/cgi/regcode.html
new file mode 100644
index 0000000..e639b9b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/regcode.html
@@ -0,0 +1,14 @@
+<HTML><HEAD><TITLE>ISP Signup</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>ISP Signup - registration code</FONT><BR><BR>
+<SCRIPT>
+function gotoURL(object) {
+ window.location.href = 'signup.cgi?reg_code=' + object.reg_code.value;
+}
+</SCRIPT>
+<FORM>
+Enter registration code <INPUT TYPE="text" NAME="reg_code">
+<INPUT type="submit" VALUE="Signup" onClick="gotoURL(this.form)">
+
+</FORM>
+</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..2252852
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
@@ -0,0 +1,743 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($DEBUG $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 Date::Format;
+use Number::Format 1.50;
+use FS::SelfService qw(
+ access_info login_info login customer_info edit_info invoice
+ payment_info process_payment realtime_collect process_prepay
+ list_pkgs order_pkg signup_info order_recharge
+ part_svc_info provision_acct provision_external
+ unprovision_svc change_pkg domainselector
+ list_svcs list_svc_usage list_cdr_usage list_support_usage
+ myaccount_passwd
+ mason_comp
+);
+
+$template_dir = '.';
+
+$DEBUG = 1;
+
+$form_max = 255;
+
+$cgi = new CGI;
+
+unless ( defined $cgi->param('session') ) {
+ my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+
+ do_template('login', $login_info );
+ 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} ) {
+ my $login_info = login_info( 'agentnum' => $cgi->param('agentnum') );
+ do_template('login', {
+ 'error' => $rv->{error},
+ 'username' => $username,
+ 'domain' => $domain,
+ %$login_info,
+ } );
+ 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|make_ach_payment|make_thirdparty_payment|payment_results|ach_payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|change_pay|process_change_bill|process_change_ship|process_change_pay|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|view_cdr_details|view_support_details|change_password|process_change_password)$/
+ or die "unknown action ". $cgi->param('action');
+my $action = $1;
+
+warn "calling $action sub\n"
+ if $DEBUG;
+$FS::SelfService::DEBUG = $DEBUG;
+my $result = eval "&$action();";
+die $@ if $@;
+
+if ( $result->{error} eq "Can't resume session"
+ || $result->{error} eq "Expired session" ) { #ick
+
+ my $login_info = login_info();
+ do_template('login', $login_info);
+ exit;
+}
+
+#warn $result->{'open_invoices'};
+#warn scalar(@{$result->{'open_invoices'}});
+
+warn "processing template $action\n"
+ if $DEBUG;
+do_template($action, {
+ 'session_id' => $session_id,
+ 'action' => $action, #so the menu knows what tab we're on...
+ #%{ payment_info( 'session_id' => $session_id ) }, # cust_paybys for the menu
+ %{$result}
+});
+
+#--
+
+sub myaccount { customer_info( 'session_id' => $session_id ); }
+
+sub change_bill { my $payment_info =
+ payment_info( 'session_id' => $session_id );
+ return $payment_info if ( $payment_info->{'error'} );
+ my $customer_info =
+ customer_info( 'session_id' => $session_id );
+ return {
+ %$payment_info,
+ %$customer_info,
+ };
+ }
+sub change_ship { change_bill(@_); }
+sub change_pay { change_bill(@_); }
+
+sub _process_change_info {
+ my ($erroraction, @fields) = @_;
+
+ my $results = '';
+
+ $results ||= edit_info (
+ 'session_id' => $session_id,
+ map { ($_ => $cgi->param($_)) } grep { defined($cgi->param($_)) } @fields,
+ );
+
+
+ if ( $results->{'error'} ) {
+ no strict 'refs';
+ $action = $erroraction;
+ return {
+ $cgi->Vars,
+ %{&$action()},
+ 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ };
+ } else {
+ return $results;
+ }
+}
+
+sub process_change_bill {
+ _process_change_info( 'change_bill',
+ qw( first last company address1 address2 city state
+ county zip country daytime night fax )
+ );
+}
+
+sub process_change_ship {
+ my @list = map { "ship_$_" }
+ qw( first last company address1 address2 city state
+ county zip country daytime night fax
+ );
+ if ($cgi->param('same') eq 'Y') {
+ foreach (@list) { $cgi->param($_, '') }
+ }
+
+ _process_change_info( 'change_ship', @list );
+}
+
+sub process_change_pay {
+ my $postal = $cgi->param( 'postal_invoicing' );
+ my $payby = $cgi->param( 'payby' );
+ my @list =
+ qw( payby payinfo payinfo1 payinfo2 month year payname
+ address1 address2 city county state zip country auto paytype
+ paystate ss stateid stateid_state invoicing_list
+ );
+ push @list, 'postal_invoicing' if $postal;
+ unless ( $payby ne 'BILL'
+ || $postal
+ || $cgi->param( 'invoicing_list' )
+ )
+ {
+ $action = 'change_pay';
+ return {
+ %{&change_pay()},
+ $cgi->Vars,
+ 'error' => '<FONT COLOR="#FF0000">Postal or email required.</FONT>',
+ };
+ }
+ _process_change_info( 'change_pay', @list );
+}
+
+sub view_invoice {
+
+ $cgi->param('invnum') =~ /^(\d+)$/ or die "illegal invnum";
+ my $invnum = $1;
+
+ invoice( 'session_id' => $session_id,
+ 'invnum' => $invnum,
+ );
+
+}
+
+sub customer_order_pkg {
+ my $init_data = signup_info( 'customer_session_id' => $session_id );
+ return $init_data if ( $init_data->{'error'} );
+
+ my $customer_info = customer_info( 'session_id' => $session_id );
+ return $customer_info if ( $customer_info->{'error'} );
+
+ my $pkgselect = mason_comp(
+ 'session_id' => $session_id,
+ 'comp' => '/edit/cust_main/first_pkg/select-part_pkg.html',
+ 'args' => [ 'password_verify' => 1,
+ 'onchange' => 'enable_order_pkg()',
+ 'relurls' => 1,
+ 'empty_label' => 'Select package',
+ ],
+ );
+
+ $pkgselect = $pkgselect->{'error'} || $pkgselect->{'output'};
+
+ return {
+ ( map { $_ => $init_data->{$_} }
+ qw( part_pkg security_phrase svc_acct_pop ),
+ ),
+ %$customer_info,
+ 'pkg_selector' => $pkgselect,
+ };
+}
+
+sub customer_change_pkg {
+ my $init_data = signup_info( 'customer_session_id' => $session_id );
+ return $init_data if ( $init_data->{'error'} );
+
+ my $customer_info = customer_info( 'session_id' => $session_id );
+ return $customer_info if ( $customer_info->{'error'} );
+
+ return {
+ ( map { $_ => $init_data->{$_} }
+ qw( part_pkg security_phrase svc_acct_pop ),
+ ),
+ ( map { $_ => $cgi->param($_) }
+ qw( pkgnum pkg )
+ ),
+ %$customer_info,
+ };
+}
+
+sub process_order_pkg {
+
+ my $results = '';
+
+ my @params = (qw( custnum pkgpart ));
+ my $svcdb = '';
+ if ( $cgi->param('pkgpart_svcpart') =~ /^(\d+)_(\d+)$/ ) {
+ $cgi->param('pkgpart', $1);
+ $cgi->param('svcpart', $2);
+ push @params, 'svcpart';
+ $svcdb = $cgi->param('svcdb');
+ push @params, 'domsvc' if $svcdb eq 'svc_acct';
+ } else {
+ $svcdb = 'svc_acct';
+ }
+
+ if ( $svcdb eq 'svc_acct' ) {
+
+ push @params, qw( username _password _password2 sec_phrase popnum );
+
+ unless ( length($cgi->param('_password')) ) {
+ my $init_data = signup_info( 'customer_session_id' => $session_id );
+ $results = { 'error' => $init_data->{msgcat}{empty_password} };
+ $results = { 'error' => $init_data->{error} } if($init_data->{error});
+ }
+ if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+ my $init_data = signup_info( 'customer_session_id' => $session_id );
+ $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
+ $results = { 'error' => $init_data->{error} } if($init_data->{error});
+ $cgi->param('_password', '');
+ $cgi->param('_password2', '');
+ }
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+
+ push @params, qw( phonenum sip_password pin phone_name );
+
+ } else {
+ die "$svcdb not handled on process_order_pkg yet";
+ }
+
+ $results ||= order_pkg (
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } @params
+ );
+
+
+ if ( $results->{'error'} ) {
+ $action = 'customer_order_pkg';
+ return {
+ $cgi->Vars,
+ %{customer_order_pkg()},
+ 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ };
+ } else {
+ return $results;
+ }
+
+}
+
+sub process_change_pkg {
+
+ my $results = '';
+
+ $results ||= change_pkg (
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) }
+ qw( pkgpart pkgnum )
+ );
+
+
+ if ( $results->{'error'} ) {
+ $action = 'customer_change_pkg';
+ return {
+ $cgi->Vars,
+ %{customer_change_pkg()},
+ 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ };
+ } else {
+ return $results;
+ }
+
+}
+
+sub process_order_recharge {
+
+ my $results = '';
+
+ $results ||= order_recharge (
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) }
+ qw( svcnum )
+ );
+
+
+ if ( $results->{'error'} ) {
+ $action = 'view_usage';
+ if ($results->{'error'} eq '_decline') {
+ $results->{'error'} = "There has been an error processing your account. Please contact customer support."
+ }
+ return {
+ $cgi->Vars,
+ %{view_usage()},
+ 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ };
+ } else {
+ return $results;
+ }
+
+}
+
+sub make_payment {
+ payment_info( 'session_id' => $session_id );
+}
+
+sub payment_results {
+
+ use Business::CreditCard 0.30;
+
+ #we should only do basic checking here for DoS attacks and things
+ #that couldn't be constructed by the web form... let process_payment() do
+ #the rest, it gives better error messages
+
+ $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
+ or die "Illegal amount: ". $cgi->param('amount'); #!!!
+ my $amount = $1;
+
+ my $payinfo = $cgi->param('payinfo');
+ $payinfo =~ s/[^\dx]//g;
+ $payinfo =~ /^([\dx]{13,16})$/
+ #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ or die "illegal card"; #!!!
+ $payinfo = $1;
+ unless ( $payinfo =~ /x/ ) {
+ validate($payinfo)
+ #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ or die "invalid card"; #!!!
+ }
+
+ if ( $cgi->param('card_type') ) {
+ 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('paycvv') =~ /^\s*(.{0,4})\s*$/ or die "illegal CVV2";
+ my $paycvv = $1;
+
+ $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') =~ /^(.{0,80})$/ or die "illegal state";
+ my $state = $1;
+
+ $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
+ my $zip = $1;
+
+ $cgi->param('country') =~ /^(.{0,2})$/ or die "illegal country";
+ my $country = $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,
+ 'payby' => 'CARD',
+ 'amount' => $amount,
+ 'payinfo' => $payinfo,
+ 'paycvv' => $paycvv,
+ 'month' => $month,
+ 'year' => $year,
+ 'payname' => $payname,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'country' => $country,
+ 'save' => $save,
+ 'auto' => $auto,
+ 'paybatch' => $paybatch,
+ );
+
+}
+
+sub make_ach_payment {
+ payment_info( 'session_id' => $session_id );
+}
+
+sub ach_payment_results {
+
+ #we should only do basic checking here for DoS attacks and things
+ #that couldn't be constructed by the web form... let process_payment() do
+ #the rest, it gives better error messages
+
+ $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
+ or die "illegal amount"; #!!!
+ my $amount = $1;
+
+ my $payinfo1 = $cgi->param('payinfo1');
+ $payinfo1 =~ s/[^\dx]//g;
+ $payinfo1 =~ /^([\dx]+)$/
+ or die "illegal account"; #!!!
+ $payinfo1 = $1;
+
+ my $payinfo2 = $cgi->param('payinfo2');
+ $payinfo2 =~ s/[^\dx]//g;
+ $payinfo2 =~ /^([\dx]+)$/
+ or die "illegal ABA/routing code"; #!!!
+ $payinfo2 = $1;
+
+ $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
+ my $payname = $1;
+
+ $cgi->param('paystate') =~ /^(.{0,2})$/ or die "illegal paystate";
+ my $paystate = $1;
+
+ $cgi->param('paytype') =~ /^(.{0,80})$/ or die "illegal paytype";
+ my $paytype = $1;
+
+ $cgi->param('ss') =~ /^(.{0,80})$/ or die "illegal ss";
+ my $ss = $1;
+
+ $cgi->param('stateid') =~ /^(.{0,80})$/ or die "illegal stateid";
+ my $stateid = $1;
+
+ $cgi->param('stateid_state') =~ /^(.{0,2})$/ or die "illegal stateid_state";
+ my $stateid_state = $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,
+ 'payby' => 'CHEK',
+ 'amount' => $amount,
+ 'payinfo1' => $payinfo1,
+ 'payinfo2' => $payinfo2,
+ 'month' => '12',
+ 'year' => '2037',
+ 'payname' => $payname,
+ 'paytype' => $paytype,
+ 'paystate' => $paystate,
+ 'ss' => $ss,
+ 'stateid' => $stateid,
+ 'stateid_state' => $stateid_state,
+ 'save' => $save,
+ 'auto' => $auto,
+ 'paybatch' => $paybatch,
+ );
+
+}
+
+sub make_thirdparty_payment {
+ $cgi->param('payby_method') =~ /^(CC|ECHECK)$/
+ or die "illegal payby method";
+ realtime_collect( 'session_id' => $session_id, 'method' => $1 );
+}
+
+sub recharge_prepay {
+ customer_info( 'session_id' => $session_id );
+}
+
+sub recharge_results {
+
+ my $prepaid_cardnum = $cgi->param('prepaid_cardnum');
+ $prepaid_cardnum =~ s/\W//g;
+ $prepaid_cardnum =~ /^(\w*)$/ or die "illegal prepaid card number";
+ $prepaid_cardnum = $1;
+
+ process_prepay ( 'session_id' => $session_id,
+ 'prepaid_cardnum' => $prepaid_cardnum,
+ );
+}
+
+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 domsvc _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 view_usage {
+ list_svcs(
+ 'session_id' => $session_id,
+ 'svcdb' => [ 'svc_acct', 'svc_phone' ],
+ 'ncancelled' => 1,
+ );
+}
+
+sub view_usage_details {
+ list_svc_usage(
+ 'session_id' => $session_id,
+ 'svcnum' => $cgi->param('svcnum'),
+ 'beginning' => $cgi->param('beginning') || '',
+ 'ending' => $cgi->param('ending') || '',
+ );
+}
+
+sub view_cdr_details {
+ list_cdr_usage(
+ 'session_id' => $session_id,
+ 'svcnum' => $cgi->param('svcnum'),
+ 'beginning' => $cgi->param('beginning') || '',
+ 'ending' => $cgi->param('ending') || '',
+ );
+}
+
+sub view_support_details {
+ list_support_usage(
+ 'session_id' => $session_id,
+ 'svcnum' => $cgi->param('svcnum'),
+ 'beginning' => $cgi->param('beginning') || '',
+ 'ending' => $cgi->param('ending') || '',
+ );
+}
+
+sub change_password {
+ list_svcs(
+ 'session_id' => $session_id,
+ 'svcdb' => 'svc_acct',
+ );
+};
+
+sub process_change_password {
+
+ my $result = myaccount_passwd(
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( svcnum new_password new_password2 )
+ );
+
+ if ( exists $result->{'error'} && $result->{'error'} ) {
+
+ $action = 'change_password';
+ return {
+ $cgi->Vars,
+ %{ list_svcs( 'session_id' => $session_id,
+ 'svcdb' => 'svc_acct',
+ )
+ },
+ #'svcnum' => $cgi->param('svcnum'),
+ 'error' => $result->{'error'}
+ };
+
+ } else {
+
+ return $result;
+
+ }
+
+}
+
+#--
+
+sub do_template {
+ my $name = shift;
+ my $fill_in = shift;
+
+ $cgi->delete_all();
+ $fill_in->{'selfurl'} = $cgi->self_url;
+ $fill_in->{'cgi'} = \$cgi;
+
+ my $access_info = $session_id
+ ? access_info( 'session_id' => $session_id )
+ : {};
+ $fill_in->{$_} = $access_info->{$_} foreach keys %$access_info;
+
+ my $source = "$template_dir/$name.html";
+ #warn "creating template for $source\n";
+ my $template = new Text::Template( TYPE => 'FILE',
+ SOURCE => $source,
+ DELIMITERS => [ '<%=', '%>' ],
+ UNTAINT => 1,
+ )
+ or die $Text::Template::ERROR;
+
+ #warn "filling in $template with $fill_in\n";
+ 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(regionselector popselector domainselector location_form);
+
+#false laziness w/agent.cgi
+use vars qw(@INCLUDE_ARGS);
+sub include {
+ my $name = shift;
+
+ @INCLUDE_ARGS = @_;
+
+ 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
+ );
+
+}
+
+1;
diff --git a/fs_selfservice/FS-SelfService/cgi/signup-agentselect.html b/fs_selfservice/FS-SelfService/cgi/signup-agentselect.html
new file mode 100755
index 0000000..7851c56
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/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_selfservice/FS-SelfService/cgi/signup-alternate.html b/fs_selfservice/FS-SelfService/cgi/signup-alternate.html
new file mode 100755
index 0000000..490cefa
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/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_selfservice/FS-SelfService/cgi/signup-billaddress.html b/fs_selfservice/FS-SelfService/cgi/signup-billaddress.html
new file mode 100755
index 0000000..3cf9d25
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/signup-billaddress.html
@@ -0,0 +1,307 @@
+<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>
+Billing Address (where credit card statement is sent)
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Exact name on card<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>" onChange="changed(this)">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>" onChange="changed(this)"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ regionselector( $county, $state, $country, '', 'changed(this)' );
+
+ "$county_html $state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>" onChange="changed(this)"></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 onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18 onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12 onChange="changed(this)"></TD>
+</TR>
+</TABLE>
+
+<SCRIPT>
+function changed(what) {
+ what.form.same.checked = false;
+}
+function samechanged(what) {
+ if ( what.checked ) {
+
+ <%= foreach (qw(
+ last first company address1 address2 city zip daytime night fax
+ )) {
+ $OUT .= "what.form.ship_$_.value = what.form.$_.value;\n";
+ }
+ %>
+
+ 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>
+
+<BR><BR>
+Service Address
+(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%= $same eq 'Y' ? 'CHECKED' : '' %>>same as billing address)<BR>
+<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="ship_last" VALUE="<%= $ship_last %>" onChange="changed(this)">,
+ <INPUT TYPE="text" NAME="ship_first" VALUE="<%= $ship_first %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="ship_company" SIZE=70 VALUE="<%= $ship_company %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address1" SIZE=70 VALUE="<%= $ship_address1 %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address2" SIZE=70 VALUE="<%= $ship_address2 %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="ship_city" VALUE="<%= $ship_city %>" onChange="changed(this)"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($ship_county_html, $ship_state_html, $ship_country_html) =
+ regionselector( $ship_county,
+ $ship_state,
+ $ship_country,
+ 'ship_',
+ 'changed(this)',
+ );
+
+ "$ship_county_html $ship_state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="ship_zip" SIZE=10 VALUE="<%= $ship_zip %>" onChange="changed(this)"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+ <TD><%= $ship_country_html %></TD>
+<TR>
+ <TD ALIGN="right">Day Phone</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 Phone</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>
+</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
+<INPUT TYPE="hidden" NAME="promo_code" VALUE="<%= $cgi->param('promo_code') %>">
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart">
+
+ <%=
+ $OUT .= '<OPTION VALUE="">(none)' unless scalar(@$packages) == 1;
+ foreach my $package ( @{$packages} ) {
+ $OUT .= '<OPTION VALUE="'. $package->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED'
+ if ( $pkgpart && $package->{'pkgpart'} == $pkgpart )
+ || scalar(@$packages) == 1;
+ $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_selfservice/FS-SelfService/cgi/signup-freeoption.html b/fs_selfservice/FS-SelfService/cgi/signup-freeoption.html
new file mode 100755
index 0000000..40ad03c
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/signup-freeoption.html
@@ -0,0 +1,262 @@
+<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>
+<%=
+ my $first_payby = $packages->[0]{'payby'}[0];
+ unless ( grep { scalar( @{$_->{'payby'}} ) > 1
+ || $_->{'payby'}->[0] ne $first_payby
+ } @$packages
+ ) {
+ @payby = ( $first_payby );
+ }
+
+ unless ( scalar(@payby) == 1 && $payby[0] eq 'BILL' ) {
+
+ $OUT .= ' Billing information
+ <TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+ <TR><TD>
+ <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>';
+
+ $OUT .= '<TR><TD>Billing type</TD></TR>'
+ if scalar(@payby) > 1;
+
+ $OUT .= '</TABLE>';
+
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="invoicing_list" VALUE="">
+ <INPUT TYPE="hidden" NAME="invoicing_list_POST" VALUE="">';
+ }
+
+%>
+
+<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' => <<'END',
+<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="">
+END
+ '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' => <<'END',
+<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="">
+END
+
+ '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>
+<%= unless ( scalar(@payby) == 1 && $payby[0] eq 'BILL' ) {
+ $OUT .= '<font color="#ff0000">*</font> required fields for each billing type';
+ }
+ '';
+%>
+<BR><BR>First package
+<INPUT TYPE="hidden" NAME="promo_code" VALUE="<%= $cgi->param('promo_code') %>"><TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart">
+
+ <%=
+ $OUT .= '<OPTION VALUE="">(none)' unless scalar(@$packages) == 1;
+ foreach my $package ( @{$packages} ) {
+ $OUT .= '<OPTION VALUE="'. $package->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED'
+ if ( $pkgpart && $package->{'pkgpart'} == $pkgpart )
+ || scalar(@$packages) == 1;
+ $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_selfservice/FS-SelfService/cgi/signup-snarf.html b/fs_selfservice/FS-SelfService/cgi/signup-snarf.html
new file mode 100755
index 0000000..d167efb
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/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_selfservice/FS-SelfService/cgi/signup.cgi b/fs_selfservice/FS-SelfService/cgi/signup.cgi
new file mode 100755
index 0000000..7d1679b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/signup.cgi
@@ -0,0 +1,487 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( @payby $cgi $init_data
+ $self_url $error $agentnum
+
+ $ieak_file $ieak_template
+ $signup_html $signup_template
+ $success_html $success_template
+ $collect_html $collect_template
+ $decline_html $decline_template
+ );
+
+use subs qw( print_form print_okay print_decline
+ success_default collect_default decline_default
+ );
+use CGI;
+#use CGI::Carp qw(fatalsToBrowser);
+use Tie::IxHash;
+use Text::Template;
+use Business::CreditCard;
+use HTTP::BrowserDetect;
+use HTML::Widgets::SelectLayers;
+use FS::SelfService qw( signup_info new_customer );
+
+#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';
+$collect_html = -e 'collect.html'
+ ? 'collect.html'
+ : '/usr/local/freeside/collect.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*\/?\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 $collect_html ) {
+ my $collect_txt = Text::Template::_load_text($collect_html)
+ or die $Text::Template::ERROR;
+ $collect_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $collect_txt = $1;
+ $collect_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $collect_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $collect_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &collect_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;
+}
+
+$cgi = new CGI;
+
+$init_data = signup_info( 'agentnum' => $agentnum || scalar($cgi->param('agentnum')),
+ 'promo_code' => scalar($cgi->param('promo_code')),
+ 'reg_code' => uc(scalar($cgi->param('reg_code'))),
+ );
+
+my $magic = $cgi->param('magic') || '';
+my $action = $cgi->param('action') || '';
+
+if ( $magic eq 'process' || $action eq 'process_signup' ) {
+
+ $error = '';
+
+ $cgi->param('agentnum', $agentnum) if $agentnum;
+ $cgi->param('reg_code', uc(scalar($cgi->param('reg_code'))) );
+
+ #false laziness w/agent.cgi, identical except for agentnum
+ 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' )
+ : ''
+ );
+ $cgi->param('paytype' => defined $cgi->param( $payby. '_paytype' )
+ ? $cgi->param( $payby. '_paytype' )
+ : ''
+ );
+ $cgi->param('paystate' => defined $cgi->param( $payby. '_paystate' )
+ ? $cgi->param( $payby. '_paystate' )
+ : ''
+ );
+
+ 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 ( $svc_x eq 'svc_acct' ) {
+ 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');
+ }
+
+ if ($init_data->{emailinvoiceonly} && (length $cgi->param('invoicing_list') < 1)) {
+ $error ||= $init_data->{msgcat}{illegal_or_empty_text};
+ }
+
+ my $rv = '';
+ unless ( $error ) {
+ $rv = new_customer( {
+ ( map { $_ => scalar($cgi->param($_)) }
+ qw( last first ss company
+ address1 address2 city county state zip country
+ daytime night fax stateid stateid_state
+
+ ship_last ship_first ship_company
+ ship_address1 ship_address2 ship_city ship_county ship_state
+ ship_zip ship_country
+ ship_daytime ship_night ship_fax
+
+ payby payinfo paycvv paydate payname paystate paytype
+ invoicing_list referral_custnum promo_code reg_code
+ pkgpart refnum agentnum
+ username sec_phrase _password popnum
+ mac_addr
+ countrycode phonenum sip_password pin
+ ),
+ grep { /^snarf_/ } $cgi->param
+ ),
+ 'payip' => $cgi->remote_host(),
+ } );
+ $error = $rv->{'error'};
+ }
+ #eslaf
+
+ if ( $error eq '_decline' ) {
+ print_decline();
+ } elsif ( $error eq '_collect' ) {
+ map { $cgi->param($_, $rv->{$_}) }
+ qw( popup_url reference collectitems amount );
+ print_collect();
+ } elsif ( $error ) {
+ #fudge the snarf info
+ no strict 'refs';
+ ${$_} = $cgi->param($_) foreach grep { /^snarf_/ } $cgi->param;
+ print_form();
+ } else {
+ print_okay(
+ 'pkgpart' => scalar($cgi->param('pkgpart')),
+ %$rv,
+ );
+ }
+
+} elsif ( $magic eq 'success' || $action eq 'success' ) {
+
+ $cgi->param('username', 'username'); #hmmm temp kludge
+ $cgi->param('_password', 'password');
+ print_okay( map { /^([\w ]+)$/ ? ( $_ => $1 ) : () } $cgi->param ); #hmmm
+
+} elsif ( $magic eq 'decline' || $action eq 'decline' ) {
+
+ print_decline();
+
+} else {
+ $error = '';
+ print_form;
+}
+
+sub print_form {
+
+ $error = "Error: $error" if $error;
+
+ my $r = {
+ $cgi->Vars,
+ %{$init_data},
+ 'error' => $error,
+ };
+
+ $r->{pkgpart} ||= $r->{default_pkgpart};
+
+ $r->{referral_custnum} = $r->{'ref'};
+ #$cgi->delete('ref');
+ #$cgi->delete('init_popstate');
+ $r->{self_url} = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $signup_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
+sub print_collect {
+
+ $error = "Error: $error" if $error;
+
+ my $r = {
+ $cgi->Vars,
+ %{$init_data},
+ 'error' => $error,
+ };
+
+ $r->{pkgpart} ||= $r->{default_pkgpart};
+
+ $r->{referral_custnum} = $r->{'ref'};
+ $r->{self_url} = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $collect_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
+sub print_decline {
+ my $r = {
+ %{$init_data},
+ };
+
+ print $cgi->header( '-expires' => 'now' ),
+ $decline_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
+sub print_okay {
+ my %param = @_;
+ my $user_agent = new HTTP::BrowserDetect $ENV{HTTP_USER_AGENT};
+
+ my( $username, $password ) = ( '', '' );
+ my( $countrycode, $phonenum, $sip_password, $pin ) = ( '', '', '', '' );
+
+ my $svc_x = $param{signup_service} || 'svc_acct'; #just in case
+ if ( $svc_x eq 'svc_acct' ) {
+
+ $cgi->param('username') =~ /^(.+)$/
+ or die "fatal: invalid username got past FS::SelfService::new_customer";
+ $username = $1;
+ $cgi->param('_password') =~ /^(.+)$/
+ or die "fatal: invalid password got past FS::SelfService::new_customer";
+ $password = $1;
+
+ } elsif ( $svc_x eq 'svc_phone' ) {
+
+ $countrycode = $param{countrycode};
+ $phonenum = $param{phonenum};
+ $sip_password = $param{sip_password};
+ $pin = $param{pin};
+
+ } else {
+ die "unknown signup service $svc_x";
+ }
+
+ ( $cgi->param('first'). ' '. $cgi->param('last') ) =~ /^(.*)$/
+ or die "fatal: invalid email_name got past FS::SelfService::new_customer";
+ my $email_name = $1; #global for template
+
+ #my %pop = ();
+ my %popnum2pop = ();
+ foreach ( @{ $init_data->{'svc_acct_pop'} } ) {
+ #push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
+ $popnum2pop{$_->{popnum}} = $_;
+ }
+
+ my( $ac, $exch, $loc);
+ my $pop = $popnum2pop{$cgi->param('popnum')};
+ #or die "fatal: invalid popnum got past FS::SelfService::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
+ my $part_pkg = ( grep { $_->{'pkgpart'} eq $param{'pkgpart'} }
+ @{ $init_data->{'part_pkg'} }
+ )[0];
+ my $pkg = $part_pkg->{'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( HASH => {
+
+ %{$init_data},
+
+ email_name => $email_name,
+ pkg => $pkg,
+ part_pkg => \$part_pkg,
+
+ signup_service => $svc_x,
+
+ #for svc_acct
+ username => $username,
+ password => $password,
+ _password => $password,
+ ac => $ac, #for dialup POP
+ exch => $exch, #
+ loc => $loc, #
+
+ #for svc_phone
+ countrycode => $countrycode,
+ phonenum => $phonenum,
+ sip_password => $sip_password,
+ pin => $pin,
+
+ });
+ }
+
+}
+
+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 collect_default { #html to use if there is a collect phase
+ <<'END';
+<HTML><HEAD><TITLE>Pay now</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Pay now</FONT><BR><BR>
+<SCRIPT TYPE="text/javascript">
+ function popcollect() {
+ overlib( OLiframeContent('<%= $popup_url %>', 336, 550, 'Secure Payment Area', 0, 'auto' ), CAPTION, 'Pay now', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close' );
+ return false;
+ }
+</SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_draggable.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_crossframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="iframecontentmws.js"></SCRIPT>
+You are about to contact our payment processor to pay <%= $amount %> for
+<%= $pkg %>.<BR><BR>
+Your transaction reference number is <%= $reference %><BR><BR>
+<FORM NAME="collect_popper" method="post" action="javascript:void(0)" onSubmit="popcollect()">
+<%=
+ my %itemhash = @collectitems;
+ foreach my $input (keys %itemhash) {
+ $OUT .= qq!<INPUT NAME="$input" TYPE="hidden" VALUE="$itemhash{$input}">!;
+ }
+%>
+<INPUT NAME="submit" type="submit" value="Pay now">
+</FORM>
+</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
+}
+
+# subs for the templates...
+
+package FS::SelfService::_signupcgi;
+use HTML::Entities;
+use FS::SelfService qw(regionselector expselect popselector didselector);
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup.html b/fs_selfservice/FS-SelfService/cgi/signup.html
new file mode 100755
index 0000000..375137b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/signup.html
@@ -0,0 +1,435 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+ <TITLE><%= $agent || ( $signup_service eq 'svc_phone' ? 'ITSP' : 'ISP' ) %> Signup form</TITLE>
+ <%= $head %>
+</HEAD>
+<BODY BGCOLOR="<%= $body_bgcolor || '#e8e8e8' %>" onUnload="myclose()">
+
+<script type="text/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>
+
+<%= $OUT .= $body_header
+ || '<FONT SIZE=7>'.
+ ( $agent || ( $signup_service eq 'svc_phone' ? 'ITSP' : '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="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_signup">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+<input type="hidden" name="payby">
+<%=
+ $OUT = join("\n",map { my $method = $_ ; map { qq|<input type="hidden" name="${method}_$_" />| } qw / payinfo payinfo1 payinfo2 payname paystate paytype paycvv month year type / } @payby);
+%>
+
+<%=
+ $OUT = join("\n", map { qq|<input type="hidden" name="$_" />| } qw / promo_code reg_code pkgpart username _password _password2 sec_phrase popnum mac_addr countrycode phonenum sip_password pin / );
+%>
+
+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'} == $refnum;
+ $OUT .= '>'. $part_referral->{'referral'};
+ }
+%>
+</SELECT><BR><BR>
+Contact Information
+<TABLE BGCOLOR="<%= $box_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>
+<%=
+ $OUT = '';
+ if ( $stateid_enabled ) {
+ my ($county_html, $state_html, $country_html) =
+ regionselector( {
+ prefix => 'stateid_',
+ default_state => $statedefault,
+ default_country => $countrydefault,
+ locales => \@cust_main_county,
+ } );
+ $OUT .= qq!<TR><TD ALIGN="right">!. $label{stateid}.'</TD>';
+ $OUT .= qq!<TD><INPUT TYPE="text" NAME="stateid" VALUE="$stateid" SIZE=12></TD>!;
+ $OUT .= qq!<TD ALIGN="right">!. $label{stateid_state} .'</TD>';
+ $OUT .="<TD COLSPAN=3>$county_html $state_html</TD></TR>";
+ }
+%>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+<BR>Billing information<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR><TD>
+
+ <%=
+ $OUT ='';
+ unless ( $emailinvoiceonly ) {
+ $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><%= $OUT = ( $emailinvoiceonly ? q|<font color="#ff0000">*</font>| : q|| ) %> Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>">
+</TD></TR>
+<%= ( scalar(@payby) > 1 or 1 ) ? '<TR><TD>Billing type ' : '' %>
+<!--</TABLE>
+<TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%">
+<TR>-->
+
+ <%=
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ foreach ( keys %card_types ) {
+ $selected = $CARD_type eq $card_types{$_} ? 'SELECTED' : '';
+ $cardselect .= qq!<OPTION $selected VALUE="$card_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> Type <SELECT NAME="CHEK_paytype">!. join('', map {qq!<OPTION VALUE="$_">$_</OPTION>!} @paytypes). qq!</SELECT><BR>{$r}Bank State <INPUT TYPE="text" NAME="CHEK_paystate" VALUE="" SIZE=5 MAXLENGTH=4><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> Type <SELECT NAME="DCHK_paytype">!. join('', map {qq!<OPTION VALUE="$_">$_</OPTION>!} @paytypes). qq!</SELECT><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><BR>{$r}Bank State <INPUT TYPE="text" NAME="DCHK_paystate" VALUE="" SIZE=5 MAXLENGTH=4><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" NAME="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<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!<TR><TD ALIGN="right">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>)</TD><TD><INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4></TD></TR>!;
+ }
+ }
+ if ( $paystate_enabled ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CHEK DCHK) ) {
+ my ($county_html, $state_html, $country_html) =
+ regionselector( {
+ prefix => "${payby}_pay",
+ default_state => $statedefault,
+ default_country => $countrydefault,
+ locales => \@cust_main_county,
+ } );
+ $payby{$payby} .= "<BR>${r}Bank state $county_html $state_html";
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+ my %paybychecked = (
+ 'CARD' => '<TABLE BGCOLOR="'. ( $box_bgcolor || '#c0c0c0' ). qq!" BORDER=0 CELLSPACING=0 WIDTH="100%"><TR><TD ALIGN="right"><font color="#ff0000">*</font> Card type</TD><TD>$cardselect</TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Card number</TD><TD><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19></TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Expration</TD><TD>!. expselect("CARD", $paydate). qq!</TD></TR><TR><TD ALIGN="right"><font color="#ff0000">*</font> Name on card</TD><TD><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname"></TD></TR>!,
+ '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> Type <SELECT NAME="CHEK_paytype">!. join('', map {qq!<OPTION VALUE="$_"!.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>"} @paytypes). qq!</SELECT><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> Type <SELECT NAME="DCHK_paytype">!. join('', map {qq!<OPTION VALUE="$_"!.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>"} @paytypes). qq!</SELECT><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="">!,
+ '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<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!<TR><TD ALIGN="right">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>)</TD><TD><INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4></TD></TR>!;
+ }
+ }
+ if ( $paystate_enabled ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CHEK DCHK) ) {
+ my ($county_html, $state_html, $country_html) =
+ regionselector( {
+ prefix => "${payby}_pay",
+ selected_county => $county,
+ selected_state => $state,
+ selected_country => $country,
+ default_state => $statedefault,
+ default_country => $countrydefault,
+ locales => \@cust_main_county,
+ } );
+ $paybychecked{$payby} .= "<BR>${r}Bank state $county_html $state_html";
+ }
+ }
+
+ my %payby_index = ( 'CARD' => qq/Credit Card/,
+ 'DCRD' => qq/Credit Card/,
+ 'CHEK' => qq/Check/,
+ 'DCHK' => qq/Check/,
+ 'LECB' => qq/Phone Bill Billing/,
+ 'BILL' => qq/Billing/,
+ 'COMP' => qq/Complimentary/,
+ 'PREPAY' => qq/Prepaid Card/,
+ );
+
+
+tie my %options, 'Tie::IxHash', ();
+
+foreach my $payby_option ( @payby ) {
+ $options{$payby_option} = $payby_index{$payby_option};
+}
+
+my $selected_layer = ( grep { $_ eq 'CARD' } @payby ) ? 'CARD' : $payby[0];
+
+HTML::Widgets::SelectLayers->new(
+ options => \%options,
+ selected_layer => $selected_layer,
+ form_name => 'dummy',
+ html_between => '</td></tr></table>',
+ form_action => 'dummy.cgi',
+ layer_callback => sub { my $layer = shift; return ( shift @hide_payment_fields ? '' : $paybychecked{$layer} ) . '</TABLE>'; },
+)->html;
+
+
+ %>
+
+</TR></TABLE><font color="#ff0000">*</font> required fields
+<FORM name="signup_form" action="<%= $self_url %>" METHOD="POST" onsubmit="return fixup_form();"><BR><BR>First package
+<INPUT TYPE="hidden" NAME="promo_code" VALUE="<%= $promo_code %>">
+<INPUT TYPE="hidden" NAME="reg_code" VALUE="<%= $reg_code %>">
+<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart">
+
+ <%=
+ $OUT .= '<OPTION VALUE="">(none)'
+ unless scalar(@part_pkg) == 1 or $default_pkgpart;
+ 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>
+<%=
+ if ( $signup_service eq 'svc_phone' ) {
+
+ $OUT .= '<TR><TD ALIGN="right">Phone number</TD><TD>'.
+ didselector( 'field' => 'phonenum',
+ 'svcpart' => $default_svcpart,
+ ).
+ '</TD></TR>';
+
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Voicemail PIN</TD>
+ <TD><INPUT TYPE="pin" NAME="pin" VALUE="$pin"></TD>
+</TR>
+ENDOUT
+
+ } else {
+
+ $OUT .= <<ENDOUT;
+<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>
+ENDOUT
+
+ if ( $security_phrase ) {
+ $OUT .= <<SECPHRASE;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+SECPHRASE
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+
+ if ( $nomadix ) {
+
+ warn $mac_addr;
+ $mac_addr ||= $MA;
+ warn $mac_addr;
+
+ $OUT .= <<NOMADIX;
+ <INPUT TYPE="hidden" NAME="mac_addr" VALUE="$mac_addr">
+NOMADIX
+
+ }
+
+ }
+
+ 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>
+
+<%=
+if ( @optional_packages ) {
+ my @html;
+ foreach my $ii ( 0 .. $#optional_packages) {
+ my $friendly_index = $ii + 1;
+ if ($optional_packages[$ii]) {
+ push @html, qq|<BR>Optional Package #$friendly_index <br />|,'<table bgcolor="#c0c0c0"><tr><td>';
+
+ push @html, qq|<select name="optional_package${ii}">|;
+ push @html, qq|<option value="none"></option>|;
+ push @html, map { qq|<option value="$_->{pkgpart}">$_->{pkg}</option>| } @{$optional_packages[$ii]};
+ push @html, q|</select>|;
+
+ push @html, '</td></tr></table>';
+ }
+ $OUT = join("\n", @html);
+ }
+} else {
+$OUT = ''
+}
+%>
+
+<BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup">
+<script language="JavaScript">
+
+function fixup_form() {
+
+ // copy payment method data up to OneTrueForm
+
+ var payment_method_elements = new Array( 'payinfo', 'payinfo1', 'payinfo2', 'payname', 'paycvv' , 'paystate', 'paytype', 'month', 'year','type' );
+ var payment_method_form_name = document.OneTrueForm.select.options[document.OneTrueForm.select.selectedIndex].value;
+ document.OneTrueForm.elements['payby'].value = payment_method_form_name;
+ var payment_method_form = document.forms[payment_method_form_name];
+
+ for ( ii = 0 ; ii < payment_method_elements.length ; ii++ ) {
+ var true_element_name = payment_method_form_name + '_' + payment_method_elements[ii];
+ copyelement ( payment_method_form.elements[true_element_name],
+ document.OneTrueForm.elements[true_element_name] );
+ }
+
+ // Copy signup details to OneTrueForm
+
+ var signup_elements = new Array (
+ 'promo_code', 'reg_code', 'pkgpart',
+ 'username', '_password', '_password2', 'sec_phrase', 'popnum',
+ 'mac_addr',
+ 'countrycode', 'phonenum', 'sip_password', 'pin'
+ );
+
+ for ( ii = 0 ; ii < signup_elements.length ; ii ++ ) {
+ copyelement ( document.signup_form.elements[signup_elements[ii]],
+ document.OneTrueForm.elements[signup_elements[ii]]);
+ }
+
+ document.OneTrueForm.submit();
+ return false;
+}
+
+function copyelement(from, to) {
+// alert ( from + ' ' + to );
+
+ if ( from == undefined ) {
+ to.value = '';
+ } else {
+ if ( from.type == 'select-one' ) {
+ to.value = from.options[from.selectedIndex].value;
+ } else if ( from.type == 'checkbox' ) {
+ if ( from.checked ) {
+ to.value = from.value;
+ } else {
+ to.value = '';
+ }
+ } else {
+ if ( from.value == undefined ) {
+ to.value = '';
+ } else {
+ to.value = from.value;
+ }
+ }
+// alert(from.name + " (" + from.type + "): " + to.name + " => " + to.value);
+ }
+}
+
+</script>
+</FORM>
+<%= $OUT .= $body_footer %>
+</BODY>
+</HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/stateselect.html b/fs_selfservice/FS-SelfService/cgi/stateselect.html
new file mode 100644
index 0000000..ba55bff
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/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_selfservice/FS-SelfService/cgi/success-delayed.html b/fs_selfservice/FS-SelfService/cgi/success-delayed.html
new file mode 100644
index 0000000..5eeed59
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/success-delayed.html
@@ -0,0 +1,16 @@
+<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>
+Charge: <%= sprintf('$%.2f', $part_pkg->{'options'}->{'setup_fee'}) %><BR>
+In <%= $part_pkg->{'options'}->{'free_days'} %> days you will be charged
+ <%= sprintf('$%.2f', $part_pkg->{'options'}->{'recur_fee'}) %>
+and <%= $part_pkg->{'freq_pretty'} %> thereafter.<BR>
+
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/success.html b/fs_selfservice/FS-SelfService/cgi/success.html
new file mode 100644
index 0000000..ccbcc62
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/success.html
@@ -0,0 +1,40 @@
+<HTML>
+ <HEAD>
+ <TITLE>Signup successful</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<FONT SIZE=7>Signup successful</FONT><BR><BR>
+
+Thanks for signing up! Save this information for future reference.
+<BR><BR>
+
+Signup information for <%= $email_name %>:
+<BR><BR>
+
+<%=
+ if ($signup_service eq 'svc_acct' || !$signup_service ) { #just in case
+ $OUT .= <<END
+ Username: $username<BR>
+ Password: $password<BR>
+ Access number: ($ac) / $exch - $local <BR>
+END
+ } elsif ( $signup_service eq 'svc_phone' ) {
+ $OUT .= <<END
+ <!-- Countrycode: $countrycode <BR>-->
+ Phone number: $phonenum<BR>
+ SIP Server: itsp.sip.server.name<BR>
+ SIP Login: $phonenum<BR>
+ SIP Password: $sip_password<BR>
+ Voicemail PIN: $pin<BR>
+END
+ } else {
+ die "unknown signup service $signup_service";
+ }
+%>
+
+ Package: <%= $pkg %><BR>
+
+<%= $body_footer %>
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..0024438
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/svc_acct.html
@@ -0,0 +1,58 @@
+<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>
+<%=
+ $OUT .= domainselector(pkgnum=>$pkgnum, svcpart=>$svcpart);
+%>
+<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/verify.cgi b/fs_selfservice/FS-SelfService/cgi/verify.cgi
new file mode 100755
index 0000000..d9346b8
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/verify.cgi
@@ -0,0 +1,177 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( $cgi $self_url $error
+ $verify_html $verify_template
+ $success_html $success_template
+ $decline_html $decline_template
+ );
+
+use subs qw( print_verify print_okay print_decline
+ verify_default success_default decline_default
+ );
+use CGI;
+use Text::Template;
+use FS::SelfService qw( capture_payment );
+
+$verify_html = -e 'verify.html'
+ ? 'verify.html'
+ : '/usr/local/freeside/verify.html';
+$success_html = -e 'verify_success.html'
+ ? 'success.html'
+ : '/usr/local/freeside/success.html';
+$decline_html = -e 'verify_decline.html'
+ ? 'decline.html'
+ : '/usr/local/freeside/decline.html';
+
+
+if ( -e $verify_html ) {
+ my $verify_txt = Text::Template::_load_text($verify_html)
+ or die $Text::Template::ERROR;
+ $verify_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $verify_txt = $1;
+ $verify_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $verify_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $verify_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &verify_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;
+}
+
+$cgi = new CGI;
+
+my $rv = capture_payment(
+ data => { 'manual' => 1,
+ map { $_ => scalar($cgi->param($_)) } $cgi->param
+ },
+ url => $cgi->self_url,
+);
+
+$error = $rv->{error};
+
+if ( $error eq '_decline' ) {
+ print_decline();
+} elsif ( $error ) {
+ print_verify();
+} else {
+ print_okay(%$rv);
+}
+
+
+sub print_verify {
+
+ $error = "Error: $error" if $error;
+
+ my $r = { $cgi->Vars, 'error' => $error };
+
+ $r->{self_url} = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $verify_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
+sub print_decline {
+ print $cgi->header( '-expires' => 'now' ),
+ $decline_template->fill_in();
+}
+
+sub print_okay {
+ my %param = @_;
+
+ my @success_url = split '/', $cgi->url(-path);
+ pop @success_url;
+
+ my $success_url = join '/', @success_url;
+ if ($param{session_id}) {
+ my $session_id = lc($param{session_id});
+ $success_url .= "/selfservice.cgi?action=myaccount&session=$session_id";
+ } else {
+ $success_url .= '/signup.cgi?action=success';
+ }
+
+ print $cgi->header( '-expires' => 'now' ),
+ $success_template->fill_in( HASH => { success_url => $success_url } );
+}
+
+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>
+<SCRIPT TYPE="text/javascript">
+ window.top.location="<%= $success_url %>";
+</SCRIPT>
+</BODY></HTML>
+END
+}
+
+sub verify_default { #html to use for verification response
+ <<'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
+}
+
+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
+}
+
+# subs for the templates...
+
+package FS::SelfService::_signupcgi;
+use HTML::Entities;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html
new file mode 100644
index 0000000..6d4d847
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html
@@ -0,0 +1,54 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Call usage for '.
+ Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $beginning).
+ ' - '.
+ Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $ending)
+ )
+%>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<TABLE WIDTH="100%">
+ <TR>
+ <TD WIDTH="50%">
+<%= if ($previous < $beginning) {
+ $OUT .= qq!<A HREF="${url}view_cdr_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$previous;ending=$beginning">Previous period</A>!;
+ }else{
+ '';
+ } %>
+ </TD>
+ <TD WIDTH="50%" ALIGN="right">
+<%= if ($next > $ending) {
+ $OUT .= qq!<A HREF="${url}view_cdr_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$ending;ending=$next">Next period</A>!;
+ }else{
+ '';
+ }%>
+ </TD>
+ </TR>
+</TABLE>
+<TABLE BGCOLOR="#cccccc">
+ <TR>
+<%= foreach my $header (@header) {
+ $OUT .= qq(<TH ALIGN="right">$header</TH>);
+ }
+%>
+ </TR>
+<%= my $total = 0;
+ my $utotal = 0;
+ my $dtotal = 0;
+ foreach my $usage ( @usage ) {
+ $OUT .= '<TR>';
+ $OUT .= qq(<TD>$_</TD>) foreach @{$usage};
+ $OUT .= '</TR>';
+ }
+%>
+
+</TABLE>
+<BR>
+
+</TD></TR></TABLE>
+<%= include('footer') %>
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..5bfb9b6
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_customer.html
@@ -0,0 +1,24 @@
+<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>
+<%= include('footer') %>
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..072a414
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_invoice.html
@@ -0,0 +1,6 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Invoice') %>
+
+<%= $invoice_html %>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_support_details.html b/fs_selfservice/FS-SelfService/cgi/view_support_details.html
new file mode 100644
index 0000000..ea21874
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_support_details.html
@@ -0,0 +1,78 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE=4>Support usage details for
+<%= Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $beginning) %> -
+<%= Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $ending) %>
+</FONT><BR><BR>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<TABLE WIDTH="100%">
+ <TR>
+ <TD WIDTH="50%">
+<%= if ($previous < $beginning) {
+ $OUT .= qq!<A HREF="${url}view_support_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$previous;ending=$beginning">Previous period</A>!;
+ }else{
+ '';
+ } %>
+ </TD>
+ <TD WIDTH="50%" ALIGN="right">
+<%= if ($next > $ending) {
+ $OUT .= qq!<A HREF="${url}view_support_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$ending;ending=$next">Next period</A>!;
+ }else{
+ '';
+ }%>
+ </TD>
+ </TR>
+</TABLE>
+<TABLE BGCOLOR="#cccccc">
+ <TR>
+ <TH ALIGN="left">Ticket</TH>
+ <TH ALIGN="center">Subject</TH>
+ <TH ALIGN="center">Staff</TH>
+ <TH ALIGN="center">Date</TH>
+ <TH ALIGN="center">Status</TH>
+ <TH ALIGN="right">Time</TH>
+ </TR>
+<%= my $total = 0;
+ foreach my $usage ( @usage ) {
+ $OUT .= '<TR><TD ALIGN="left">';
+ $OUT .= $usage->{'ticketid'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $usage->{'subject'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $usage->{'creator'};
+ $OUT .= '</TD><TD ALIGN="left">';
+ $OUT .= Date::Format::time2str('%T%P %a&nbsp;%b&nbsp;%o&nbsp;%Y', $usage->{'_date'});
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $usage->{'status'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ my $duration = $usage->{'support'};
+ $total += $usage->{'support'};
+ my $h = int($duration/3600);
+ my $m = sprintf("%02d", int(($duration % 3600) / 60));
+ my $s = sprintf("%02d", $duration % 60);
+ $OUT .= $usage->{'support'} < 0 ? '-' : '';
+ $OUT .= "$h:$m:$s";
+ $OUT .= '</TD></TR>';
+ }
+ my $h = int($total/3600);
+ my $m = sprintf("%02d", int(($total % 3600) / 60));
+ my $s = sprintf("%02d", $total % 60);
+ $OUT .= qq!<TR><TD COLSPAN="5"></TD><TD ALIGN="right"><HR></TD></TR>!;
+ $OUT .= qq!<TR><TD COLSPAN="5"></TD><TD ALIGN="right">$h:$m:$s</TD></TR>!;
+ %>
+
+</TABLE>
+<BR>
+
+</TD></TR></TABLE>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage.html b/fs_selfservice/FS-SelfService/cgi/view_usage.html
new file mode 100644
index 0000000..6943763
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_usage.html
@@ -0,0 +1,85 @@
+<%= $url = "$selfurl?session=$session_id;action=";
+ @svc_acct = grep { $_->{svcdb} eq 'svc_acct' } @svcs;
+ @svc_phone = grep { $_->{svcdb} eq 'svc_phone' } @svcs;
+ '';
+%>
+<%= include('header', 'Account usage') %>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<%= if ( @svc_acct ) {
+ $OUT.= '<TABLE BGCOLOR="#cccccc">
+ <TR>
+ <TH ALIGN="left">Account</TH>
+ <TH ALIGN="right">Time remaining</TH>
+ <TH ALIGN="right">Upload remaining</TH>
+ <TH ALIGN="right">Download remaining</TH>
+ <TH ALIGN="right">Total remaining</TH>
+ </TR>';
+ } else {
+ $OUT .= '';
+ }
+%>
+
+<%= foreach my $svc ( @svc_acct ) {
+ my $link = "${url}view_usage_details;".
+ "svcnum=$svc->{'svcnum'};beginning=0;ending=0";
+ $OUT .= '<TR><TD>';
+ $OUT .= qq!<A HREF="$link">!. $svc->{'label'}. ': '. $svc->{'value'}.'</A>';
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'seconds'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'upbytes'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'downbytes'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'totalbytes'};
+ $OUT .= '</TD></TR>';
+ if ( $svc->{'recharge_amount'} ) {
+ my $link = "${url}process_order_recharge;".
+ "svcnum=$svc->{'svcnum'}";
+ $OUT .= '<TR><TD ALIGN="right">';
+ $OUT .= qq!<A HREF="$link">!.'Recharge for $';
+ $OUT .= $svc->{'recharge_amount'} . '</A> with';
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'recharge_seconds'} if $svc->{'recharge_seconds'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'recharge_upbytes'} if $svc->{'recharge_upbytes'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'recharge_downbytes'} if $svc->{'recharge_downbytes'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= $svc->{'recharge_totalbytes'} if $svc->{'recharge_totalbytes'};
+ $OUT .= '</TD></TR>';
+ }
+ }
+%>
+
+<%= scalar(@svc_acct) ? '</TABLE><BR><BR>' : '' %>
+
+<%= if ( @svc_phone ) {
+ $OUT.= '<FONT SIZE="4">Call usage</FONT><BR><BR>
+ <TABLE BGCOLOR="#cccccc">
+ <TR>
+ <TH ALIGN="left">Number</TH>'; #"Account" ?
+ #what else?
+ $OUT .= '</TR>';
+ } else {
+ $OUT .= '';
+ }
+%>
+
+<%= foreach my $svc_phone ( @svc_phone ) {
+ my $link = "${url}view_cdr_details;".
+ "svcnum=$svc_phone->{'svcnum'};beginning=0;ending=0";
+ $OUT .= '<TR><TD>';
+ $OUT .= qq!<A HREF="$link">!. $svc_phone->{'label'}. ': '. $svc_phone->{'value'}.'</A>';
+ $OUT .= '</TD></TR>';
+ }
+%>
+
+<%= scalar(@svc_phone) ? '</TABLE><BR><BR>' : '' %>
+
+</TD></TR></TABLE>
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage_details.html b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html
new file mode 100644
index 0000000..c4cc177
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html
@@ -0,0 +1,80 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('header', 'Service usage details for '.
+ Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $beginning).
+ ' - '.
+ Date::Format::time2str('%b&nbsp;%o&nbsp;%Y', $ending)
+ )
+%>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<TABLE WIDTH="100%">
+ <TR>
+ <TD WIDTH="50%">
+<%= if ($previous < $beginning) {
+ $OUT .= qq!<A HREF="${url}view_usage_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$previous;ending=$beginning">Previous period</A>!;
+ }else{
+ '';
+ } %>
+ </TD>
+ <TD WIDTH="50%" ALIGN="right">
+<%= if ($next > $ending) {
+ $OUT .= qq!<A HREF="${url}view_usage_details;svcnum=$svcnum;beginning=!;
+ $OUT .= qq!$ending;ending=$next">Next period</A>!;
+ }else{
+ '';
+ }%>
+ </TD>
+ </TR>
+</TABLE>
+<TABLE BGCOLOR="#cccccc">
+ <TR>
+ <TH ALIGN="left">Account</TH>
+ <TH ALIGN="right">Start Time</TH>
+ <TH ALIGN="right">Duration</TH>
+ <TH ALIGN="right">Upload</TH>
+ <TH ALIGN="right">Download</TH>
+ </TR>
+<%= my $total = 0;
+ my $utotal = 0;
+ my $dtotal = 0;
+ foreach my $usage ( @usage ) {
+ $OUT .= '<TR><TD>';
+ $OUT .= $usage->{'username'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= Date::Format::time2str('%T%P %a&nbsp;%b&nbsp;%o&nbsp;%Y', $usage->{'acctstarttime'});
+ $OUT .= '</TD><TD ALIGN="right">';
+ my $duration = $usage->{'acctstoptime'} - $usage->{'acctstarttime'};
+ $total += $duration;
+ my $h = int($duration/3600);
+ my $m = sprintf("%02d", int(($duration % 3600) / 60));
+ my $s = sprintf("%02d", $duration % 60);
+ $OUT .= "$h:$m:$s";
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= Number::Format::format_bytes($usage->{'acctinputoctets'}, precision => 2);
+ $utotal += $usage->{'acctinputoctets'};
+ $OUT .= '</TD><TD ALIGN="right">';
+ $OUT .= Number::Format::format_bytes($usage->{'acctoutputoctets'}, precision => 2);
+ $dtotal += $usage->{'acctoutputoctets'};
+ $OUT .= '</TD></TR>';
+ }
+ my $h = int($total/3600);
+ my $m = sprintf("%02d", int(($total % 3600) / 60));
+ my $s = sprintf("%02d", $total % 60);
+ $OUT .= qq!<TR><TD></TD><TD></TD>!;
+ $OUT .= qq!<TD ALIGN="right"><HR></TD>! x 3;
+ $OUT .= qq!</TR>!;
+ $OUT .= qq!<TR><TD></TD><TD></TD><TD ALIGN="right">$h:$m:$s</TD>!;
+ $OUT .= qq!<TD ALIGN="right">!;
+ $OUT .= Number::Format::format_bytes($utotal, precision => 2). qq!</TD>!;
+ $OUT .= qq!<TD ALIGN="right">!;
+ $OUT .= Number::Format::format_bytes($dtotal, precision => 2). qq!</TD>!;
+ $OUT .= qq!</TR>!; %>
+
+</TABLE>
+<BR>
+
+<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi b/fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi
new file mode 100644
index 0000000..559ae04
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use XMLRPC::Transport::HTTP;
+use XMLRPC::Lite; # for XMLRPC::Serializer
+use FS::SelfService::XMLRPC;
+
+my %typelookup = (
+ base64 => [10, sub {$_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/}, 'as_base64'],
+ dateTime => [35, sub {$_[0] =~ /^\d{8}T\d\d:\d\d:\d\d$/}, 'as_dateTime'],
+ string => [40, sub {1}, 'as_string'],
+);
+my $serializer = new XMLRPC::Serializer(typelookup => \%typelookup);
+
+XMLRPC::Transport::HTTP::CGI->dispatch_to('FS::SelfService::XMLRPC')
+ ->serializer($serializer)
+ ->handle;
+
diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd
new file mode 100644
index 0000000..0819d9d
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd
@@ -0,0 +1,377 @@
+#!/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;
+use Text::CSV_XS;
+
+#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;
+#$SIG{__DIE__} = sub { &_logmsg(@_); exit };
+
+#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;
+ if ( $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 %ftp_scan_dir;
+my %ftp_scan_map;
+
+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 ( $token eq '_ftp_scan' ) {
+ if ( $ftp_scan_dir{$packet->{dir}} ) {
+ warn "already processing ". $packet->{dir}. "\n" if $Debug;
+ } else {
+ $ftp_scan_dir{$packet->{dir}} = 1;
+ spawn \&ftp_scan, $packet;
+ }
+ $undisp = 1;
+ next;
+ }
+
+ 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}=$$;
+
+ my $rv = send_and_wait( $packet );
+
+ warn "[child-$$] closing write stream\n" if $Debug > 1;
+ close STDOUT or die "FATAL: can't close write stream: $!"; #??!
+
+ #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};
+ if ( $ftp_scan_map{$kid} ) {
+ delete($ftp_scan_dir{$ftp_scan_map{$kid}});
+ delete($ftp_scan_map{$kid});
+ }
+ }
+ }
+ #warn "done reaping\n";
+}
+
+sub spawn {
+ my ( $coderef, $packet ) = ( shift, 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;
+ $ftp_scan_map{$pid} = $packet->{dir} if $coderef == \&ftp_scan;
+ $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($packet);
+}
+
+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 send_and_wait {
+ my $packet = shift;
+
+ 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-$$] 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";
+ }
+
+ fd_retrieve(\*STDIN);
+}
+
+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: $!";
+}
+
+sub ftp_scan {
+ my $packet = shift;
+
+ warn "[child-$$] performing ftp scan" if $Debug > 1;
+
+ warn "[child-$$] packet received:\n".
+ join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+ if $Debug > 2;
+
+ $packet->{_token}=$$;
+
+ my $dir;
+ $packet->{dir} =~ /^(.*)$/ && ($dir = $1); # we trust ourselves
+ opendir(DIR, $dir) or die "failed to open directory $dir: $!\n";
+ my @files = grep(/\.csv$/, readdir(DIR));
+ closedir(DIR);
+
+ foreach my $file ( @files ) {
+ warn "Processing $file ...\n";
+ my $csv = Text::CSV_XS->new();
+ my $err = "";
+ my @records = ();
+ open(CSV, "<$dir/$file") or die "can't open input file for $file: $!\n";
+ open(RESULT, ">$dir/result/$file")
+ or die "can't open result file for $file: $!\n";
+
+ while (<CSV>) {
+ if ( $csv->parse($_) ) {
+ my @columns = $csv->fields();
+ push(@records, \@columns);
+ } else {
+ $err = $csv->error_input;
+ last;
+ }
+ }
+ close(CSV);
+ if ( $err ) {
+ rename("$dir/$file", "$dir/rejected/$file");
+ } else {
+ foreach my $record ( @records ) {
+
+ $packet->{row} = $record;
+ $packet->{_packet} = 'Bulk/processrow';
+ my $result = send_and_wait( $packet );
+
+ if ( $result->{error} ) {
+ my $name;
+ $record->[1] =~ /^(\w+)$/ && ( $name = $1 );
+
+ if ($name) {
+ my $filename = "$dir/rejected/$name";
+ open(REC, ">$filename") or die "can't open $filename: $!\n";
+ print REC join(',', @$record);
+ close REC or die $!;
+ open(ERR, ">$filename.err") or die "can't open $filename.err: $!\n";
+ print ERR $result->{error};
+ close ERR or die $!;
+ }else{
+ warn "bad agent_custid";
+ }
+
+ }
+ print RESULT $result->{message}, "\n";
+ }
+
+ rename("$dir/$file", "$dir/processed/$file");
+ warn "$file processed.\n" if $Debug;
+ }
+ close(RESULT);
+ }
+
+ close STDOUT or die "FATAL: can't close write stream: $!"; #??!
+
+ warn "[child-$$] child exiting" if $Debug > 1;
+ exit;
+
+}
diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-soap-server b/fs_selfservice/FS-SelfService/freeside-selfservice-soap-server
new file mode 100644
index 0000000..869a8ae
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/freeside-selfservice-soap-server
@@ -0,0 +1,53 @@
+#!/usr/bin/perl -w
+#
+# freeside-selfservice-soap-server
+#
+
+use strict;
+use Fcntl qw(:flock);
+use POSIX;
+use Getopt::Std;
+use SOAP::Transport::HTTP;
+use FS::SelfService;
+
+use vars qw( $opt_p $opt_d $opt_s );
+use vars qw( $DEBUG );
+
+getopts("s:p:d");
+$DEBUG = $opt_d;
+my $tag = $opt_s ? $opt_s : '';
+$tag = ($opt_s ? ':' : '') . $opt_p ? ':'.$opt_p : '';
+
+my $log_file = "/usr/local/freeside/selfservice.soap$tag.log";
+
+my $pid = fork;
+defined($pid) or die "Can't fork to start: $!";
+print "Started daemon with pid $pid\n" if $pid;
+exit if $pid;
+
+POSIX::setsid();
+open STDIN, "/dev/null" or die "Can't get rid of STDIN";
+open STDOUT, ">/dev/null" or die "Can't get rid of STDOUT";
+open STDERR, ">&STDOUT" or die "Can't get rid of STDERR";
+
+$SIG{__WARN__} = \&_logmsg;
+$SIG{__DIE__} = sub { &_logmsg(@_); exit };
+
+my $daemon = SOAP::Transport::HTTP::Daemon
+ ->new($opt_s ? (LocalAddr => $opt_s) : (), LocalPort => $opt_p ? $opt_p : 8080)
+ ->dispatch_to('/usr/local/freeside/SOAP/') #, 'FS::SelfService'
+ ->objects_by_reference('iZoomOnlineProvisionService')
+ ->handle;
+
+warn "Handling request at ", $daemon->url, "\n";
+$daemon->handle;
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[". scalar(localtime). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server b/fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server
new file mode 100644
index 0000000..bd4f83b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server
@@ -0,0 +1,59 @@
+#!/usr/bin/perl -w
+#
+# freeside-selfservice-xmlrpc-server
+#
+
+use strict;
+use Fcntl qw(:flock);
+use POSIX;
+use Getopt::Std;
+use XMLRPC::Transport::HTTP;
+use XMLRPC::Lite; # for XMLRPC::Serializer;
+use FS::SelfService::XMLRPC;
+
+use vars qw( $opt_p $opt_d );
+use vars qw( $DEBUG );
+
+getopts("p:d");
+$DEBUG = $opt_d;
+my $tag = $opt_p ? ':'.$opt_p : '';
+
+my %typelookup = (
+ base64 => [10, sub {$_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/}, 'as_base64'],
+ dateTime => [35, sub {$_[0] =~ /^\d{8}T\d\d:\d\d:\d\d$/}, 'as_dateTime'],
+ string => [40, sub {1}, 'as_string'],
+);
+my $serializer = new XMLRPC::Serializer(typelookup => \%typelookup);
+
+my $log_file = "/usr/local/freeside/selfservice.xmlrpc$tag.log";
+
+my $pid = fork;
+defined($pid) or die "Can't fork to start: $!";
+print "Started daemon with pid $pid\n" if $pid;
+exit if $pid;
+
+POSIX::setsid();
+open STDIN, "/dev/null" or die "Can't get rid of STDIN";
+open STDOUT, ">/dev/null" or die "Can't get rid of STDOUT";
+open STDERR, ">&STDOUT" or die "Can't get rid of STDERR";
+
+$SIG{__WARN__} = \&_logmsg;
+$SIG{__DIE__} = sub { &_logmsg(@_); exit };
+
+my $daemon = XMLRPC::Transport::HTTP::Daemon
+ ->new(LocalPort => $opt_p ? $opt_p : 8080)
+ ->dispatch_to('FS::SelfService::XMLRPC')
+ ->serializer($serializer);
+
+warn "Handling request at ", $daemon->url, "\n";
+$daemon->handle;
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[". scalar(localtime). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
diff --git a/fs_selfservice/FS-SelfService/iZoomOnlineProvisionService.pm b/fs_selfservice/FS-SelfService/iZoomOnlineProvisionService.pm
new file mode 100644
index 0000000..f4c5869
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/iZoomOnlineProvisionService.pm
@@ -0,0 +1,75 @@
+package iZoomOnlineProvisionService;
+
+use strict;
+
+#BEGIN { push @INC, '/usr/lib/perl/5.8.8/' };
+use FS::SelfService qw( bulk_processrow check_username agent_login );
+
+=begin WSDL
+
+_IN agent_username $string agent username
+_IN agent_password $string agent password
+_IN agent_custid $string customer id in agent system
+_IN username $string customer service username
+_IN password $string customer service password
+_IN daytime $string phone number
+_IN first $string first name
+_IN last $string last name
+_IN address1 $string address line 1
+_IN address2 $string address line 2
+_IN city $string city
+_IN state $string state
+_IN zip $string zip
+_IN pkg $string package name
+_IN action $string one of (R|P|D|S)(reconcile, provision, provision with disk, send disk)
+_IN adjourn $string day to terminate service
+_IN mobile $string mobile phone
+_IN sms $string (T|F) acceptable to send SMS messages to mobile?
+_IN ship_addr1 $string shipping address line 1
+_IN ship_addr2 $string shipping address line 2
+_IN ship_city $string shipping address city
+_IN ship_state $string shipping address state
+_IN ship_zip $string shipping address zip
+_RETURN @string array [status, message]. status is one of OK, ERR
+
+=cut
+
+my $DEBUG = 0;
+
+sub Provision {
+ my $class = shift;
+
+ my $session = agent_login( map { $_ => shift @_ } qw( username password ) );
+ return [ 'ERR', $session->{error} ] if $session->{error};
+
+ my $result =
+ bulk_processrow( session_id => $session->{session_id}, row => [ @_ ] );
+
+ return $result->{error} ? [ 'ERR', $result->{error} ]
+ : [ 'OK', $result->{message} ];
+}
+
+=begin WSDL
+
+_IN agent_username $string agent username
+_IN agent_password $string agent password
+_IN username $string customer service username
+_IN domain $string user domain name
+_RETURN @string [OK|ERR]
+
+=cut
+sub CheckUserName {
+ my $class = shift;
+
+ my $session = agent_login( map { $_ => shift @_ } qw( username password ) );
+ return [ 'ERR', $session->{error} ] if $session->{error};
+
+ my $result = check_username( session_id => $session->{session_id},
+ map { $_ => shift @_ } qw( user domain )
+ );
+
+ return $result->{error} ? [ 'ERR', $result->{error} ]
+ : [ 'OK', $result->{message} ];
+}
+
+1;
diff --git a/fs_selfservice/FS-SelfService/ieak.template b/fs_selfservice/FS-SelfService/ieak.template
new file mode 100755
index 0000000..52edaa9
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/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_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/fri/CHANGE.log b/fs_selfservice/fri/CHANGE.log
new file mode 100644
index 0000000..f25712b
--- /dev/null
+++ b/fs_selfservice/fri/CHANGE.log
@@ -0,0 +1,271 @@
+
+
+Change log - 05/02/2006
+
+ * update of french translation (submitted by Xavier Ourcière)
+
+Change log - 04/28/2006
+
+ * changed PEAR portability flags to try and fix a bug a user is having (maybe a buggy or old version of PEAR on users machine)
+ * fixed no voicemail message to be more intuitive
+ * fixed ajax bug
+ * fixed German i18n translation bug (requested by Wanninger)
+ * fixed settings recording format bug
+ * fixed settings call forward bug
+
+Change log - 04/10/2006
+
+ * added autoplay of recordings (requested by Robert LaPoint)
+ * refactored the response from the asterisk manager interface so do not always have to strip off "value:" from the response
+
+Change log - 04/04/2006
+
+ * abstracted the doc_root (PHP_SELF) to a variable to handle cases where it is not set properly (requested by Diego Iastrubni)
+ * removed error message about user voicemail directory (submitted by Diego Iastrubni)
+ * added feature to login to allow voicemail include files with wildcards (submitted by Diego Iastrubni)
+ * made voicemail password length message more accurate and descriptive on settings page (submitte by Robert Colbert)
+ * added outbound caller id record matching for call monitor page for results returned to individual users (requested by Robert LaPoint)
+ * fixed AJAX bug that kept giving javascript errors. Now form, pass, and parse a full xml doc
+ * fixed bug in description of dial code in help settings page (submitte by Robert Colbert)
+ * fixed bug to disable AJAX if using a browser that does not support AJAX
+ * updated Italian Translation (contributed by Francesco Romano: alteclab.it)
+
+Change log - 03/31/2006
+
+ * updated Spanish Translation (contributed by Antonio Cano damas: igestec.com)
+
+Change log - 03/29/2006
+
+ * added support for voicemail.conf include files (requested by Diego Iastrubni)
+ * updated database connection to support sqlite (and other databases using a connect file) (requested by Diego Iastrubni)
+
+Change log - 03/28/2006
+
+ * updated for PHP5 support
+ * fixed bug in AJAX javascript (fix submitted by Mahmud Fatafta - voicemetro.com)
+
+Change log - 03/23/2006
+
+ * remove variable references in function calls for PHP5 support (PHP4 supports, PHP5 does not, go figure)
+
+Change log - 03/18/2006
+
+ * fixed setting page voicemail options bug (submitted by Dave Vaughn: techcompinc.com)
+ * fixed settings page record settings FreePBX version bug (submitted by Luca Pandolfini)
+
+Change log - 03/13/2006
+
+ * added navigation menus to ajax update
+ * changed voicemail password on settings page so it can be variable length (submitted by vgster)
+ * fixed bug with settings page check boxes
+
+Change log - 03/09/2006
+
+ * fixed bug in error reporting for asterisk config files or recording file directories missing
+ * fixed bug for voicemail message move to perserve permissions, group, and user
+ * fixed bug in .inc and .conf file security (submitted by Diego Iastrubni, François Harvey: securiweb.net, and Adam Gray: novacoast.com)
+
+Change log - 03/07/2006
+
+ * added ajax seemless page refresh to callmonitor and voicemail
+ * added recording playback encryption (requested by François Harvey: securiweb.net)
+ * added ajax page refresh for voicemail and callmonitor (will seemlessly update page realtime)
+ * fixed bug in file permissions when a voicemail was moved (submitted by ?)
+
+Change log - 02/22/2006
+
+ * added filter to not load code not needed if a module is not loaded (submitted by Diego Iastrubni)
+ * refactored asterisk manager interface class to not require password lookup in common and asi files
+ * fixed module admin bug (submitted by serger)
+
+Change log - 02/14/2006
+
+ * added callmonitor duration filter to filter out short length calls (sponsored by John Cardner, Phonoscope, Inc)
+
+Change log - 02/09/2006
+
+ * added voicemail email and pager settings
+ * more rework of callmonitor recording match to handle large volumes of recordings (sponsored by John Cardner, Phonoscope, Inc)
+
+Change log - 02/07/2006
+
+ * added check for PHP PEAR installation
+ * added check for proper communication with the Asterisk Manager
+ * fixed class coding standard (ie ClassName)
+ * fixed method coding standard (ie methodName)
+ * fixed variable coding standard (ie variable_name)
+ * fixed constant coding standard (ie CONSTANT_NAME)
+ * added config option for voicemail password length (submitted by Chuck Bunn)
+ - set with $SETTINGS_VOICEMAIL_PASSWORD_LENGTH in /includes/main.conf
+ * added voicemail audio format admin option in settings page (submitted by Chuck Bunn)
+ - set with $ARI_VOICEMAIL_AUDIO_FORMAT_DEFAULT in /includes/main.conf
+ * fixed bug to separate voicemail password set in settings page (submitted by Chuck Bunn)
+
+Change log - 02/05/2006
+
+ * added call forward setting
+ * added Hebrew Translation (submitted by Diego Iastrubni)
+ * fixed i18n translation best practices and bugs (submitted by Diego Iastrubni)
+ * fixed voicemail message move bug (submitted by Steve Davies)
+ * fixed voicemail folder creation permissions issue (submitted by Steve Davies)
+
+Change log - 01/31/2006
+
+ * added help page
+ * added file lookup limiting code to prevent hanging when extremely large numbers of files are found in a directory
+ * added database type global variable
+
+Change log - 01/26/2006
+
+ * added php 4 or later version checking
+ * fixed php pre 4.3 version compatability
+ * fixed buy in call manager file matching recursively searching directories (submitted by Adrian Carter)
+
+Change log - 01/20/2006
+
+ * added call monitor aggressive matching option
+
+Change log - 01/18/2006
+
+ * added Hungarian Translation (submitted by Diego Imre Csaba Varasdy)
+ * fixed bug for Asterisk Manager change in Asterisk 1.2
+
+Change log - 01/12/2006
+
+ * added column sort to voicemail page (requested by Diego Elias Sofronas)
+ * added column sort to call monitor page (requested by Elias Sofronas)
+ * added i18n lang select to login page (requested by Diego Iastrubni)
+
+Change log - 12/09/2005
+
+ * another fix to the on-demand call monitor recordings (submitted by Blake Krone)
+
+Change log - 12/09/2005
+
+ * fix to recognize on-demand call monitor recordings (identified as auto-...) (submitted by Francesco Romano, Antonio Cano Damas, and Jason P. Meyer)
+ * added German Translation (submitted by Till Stoermer)
+
+Change log - 12/07/2005
+
+ * fixed search bug (submitted by Francesco Romano)
+ * fixed formating bugs
+
+Change log - 12/01/2005
+
+ * fix delete, move_to, and forward_to voicemail buttons for i18n translations
+ * fix delete call monitor button for i18n translations
+ * fix call monitor file matching problem if call time is a second or two later than time recorded in database log (submitted by Will Prater, Steve D, and others)
+ * changed to get call recording settings from asterisk and not the mysql database to support ARI standalone
+ * fix i18n for recording popup (submitted by Antonio Cano Damas)
+ * added search for voicemail
+ * added class to handle Asterisk Manager Interface (phpagi-asmanager.php would need error handling added)
+ * moved i18n language functions to own file so can support i18n in recording popup
+ * added Italian (submitted by Francesco Romano)
+ * updated Spanish translation (submitted by Antonio Cano Damas)
+ * fixed bugs in standalone code (sponsored by Hugh Buitano and also submitted by John Biundo)
+ * fixed logo (submitted by John Biundo)
+ * cleaned up css for misc/audio.php
+
+Change log - 11/17/2005
+
+ * added protocol multi-config_file (iax,sip,zap) support (sponsored by Hugh Buitano, Infosecure Systems)
+ * add global variables for asterisk and asteriskcdr database hosts and names (sponsored by Hugh Buitano, Infosecure Systems)
+ * added French translation (submitted by Joachim Buron-Pilatre, Phileas Com)
+ * fixed bug (submitted by Joachim Buron-Pilatre, Phileas Com)
+
+Change log - 11/13/2005
+
+ * refactored login context support
+ * added voicemail context support (submitted by Todd Courtnage)
+ * fixed voicemail sub nav folders to allow i18n translation (submitted by Elias Sofronas)
+ * fixed voicemail finding messages in different contexts (sponsored by Brian Connelly, Connelly Management)
+
+Change log - 11/09/2005
+
+ * fixed utf-8 translation in Greek (submitted by Elias Sofronas)
+ * added admin only access to specific modules (submitted by Julian J. M.)
+ * rework handler module code so that each module is only build one time
+ * added download message link on recording playback popup (sponsored by John Cardner, Phonoscope, Inc)
+ * converted i18n translation to utf-8 (submitted by Niklas Larsson and Elias Sofronas)
+ * fix more bugs in i18n translation (submitted by Niklas Larsson)
+ * fixed security bug that allowed access to all files (Edwin Eefting, syn-3.nl)
+
+Change log - 11/04/2005
+
+ * fixed bug to reload asterisk voicemail after voicemail password setting change (submitted by Jason Becker)
+
+Change log - 11/03/2005
+
+ * Highlight which voicemail sub-folder in use (submitted by Elias Sofronas)
+ * set default i18n page (suggested by Niklas Larsson)
+ * admin only account for call monitor (submitted by Julian J. M.)
+ * enhanced pattern matching call monitor unique id from database (submitted by Julian J. M.)
+ * updated Spanish translation (submitted by Diego Iastrubni)
+ * added Swedish translation (submitted by Niklas Larsson)
+ * added Greek translation (submitted by Elias Sofronas)
+ * fixed bug in call recording settings method (changed in AMP 1.10.009)
+ * fix bugs in i18n translation (submitted by Niklas Larsson)
+ - buttons, left menus, select all | none, Call Monitor (heading), Login page.
+
+Change log - 10/21/2005
+
+ * fixed bug in voicemail navigation (submitted by Elias Sofronas)
+ * added version cleanup
+ * added Spanish translation (submitted by Susana Castillo)
+ * added Portuguese translation (submitted by Alejandro Duplat)
+ * added admin setting for call recording
+
+Change log - 09/30/2005
+
+ * added i18n language support
+ * fixed bug if no folder or extension was selected and "move_to" or
+ "forward_to" clicked (bug submitted by Elias Sofronas)
+ * converted modules to a OO plugin architecture
+ * added version to footer
+ * add theme customization
+ * added recording type support (.WAV, .GSM) on settings page
+ * fixed bug to find call recording files better (patch submitted by Mark Voevodin)
+ * fixed bug for navigation and search controls to link to correct folder (bug submitted by Elias Sofronas)
+ * added voicemail password change to settings page
+ * added call monitor delete recording functionality (does not delete database entry)
+ * added call recording settings on settings page
+
+Change log - 09/15/2005
+
+ * added settings page
+ * added call monitor record options on settings page
+ * fixed bug to view src and dst calls in call monitor when restricted (submitted by Elias Sofronas and Thomas Stalder)
+
+Change log - 08/25/2005
+
+ * added SIP authentication login (this does not allow voicemail access)
+ * added persistent passwords (cookies)
+ * added encryption for cookies
+
+Change log - 08/23/2005
+
+ * Fixed $_SESSION['user'] bug conflict with AMP
+ -> changed to $_SESSION['ari_user']
+ * Fixed recording file lookup bug.
+
+Change log - 08/16/2005
+
+ * Fixed formating bug in css
+ * Added multipath to call monitor recordings
+ - set with $asterisk_callmonitor_path in /includes/main.conf
+ * added authentication
+ - use voicemail password
+ - access mailbox voicemail
+ - access call monitor for mailbox
+ - use AMP password
+ - access call monitor for all users
+ - config to allow voicemail to have call monitor access to all users
+ * voicemail access
+ - search of mailbox
+ - easy to delete voicemail interface
+ - move voicemail interface
+ - forward voicemail interface
+
+
+
+ \ No newline at end of file
diff --git a/fs_selfservice/fri/LICENSE.txt b/fs_selfservice/fri/LICENSE.txt
new file mode 100644
index 0000000..c09b19c
--- /dev/null
+++ b/fs_selfservice/fri/LICENSE.txt
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
+ERCHANTABILITY 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) <year> <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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) year 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/fs_selfservice/fri/README.txt b/fs_selfservice/fri/README.txt
new file mode 100644
index 0000000..2e3b908
--- /dev/null
+++ b/fs_selfservice/fri/README.txt
@@ -0,0 +1,123 @@
+Developed by Dan Littlejohn of Littlejohn Consulting.
+ www.littlejohnconsulting.com
+
+Released under the GPL.
+
+Send bug reports, requests to dan@littlejohnconsulting.com
+
++++
+
+Misc notes
+
+ARI Project Page
+ www.littlejohnconsulting.com?q=ari
+
+Coding standard
+ * class - CamelCase (ie ClassName)
+ * method camelCase (ie methodName)
+ * variable underscore (ie variable_name)
+ * constant UNDERSCORE (ie CONSTANT_NAME)
+
+Requirements
+ PHP4 (but PHP5 is not yet supported)
+ PHP PEAR
+ asterisk 1.2 or later
+ apache or apache2
+ asterisk manager - at a mininum need command access
+
+security
+ for security all the files in ./recordings/include should be locked down in the web browser
+ so they cannot be viewed.
+
+voicemail email links - For those who would like to include a link to ARI in the voicemail email and set the correct login (mailbox) you can do so as:
+
+ http://< ip address >/recordings/index.php?login=< login >
+
+ replace
+ < ip address > with the server dns or ip
+ < login > with the login or mailbox
+
++++
+
+Module API
+
+odules can be added or removed from ARI.
+
+API
+
+must include these methods.
+
+rank - weights were the module menu item will appear in the navigation window
+init - initialize the module. Database access should first appear here and not in the constructor
+navMenu - side navigation menu item
+display - main module page content
+
+example
+
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for new_module
+ */
+class NewModule {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 50;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ // put if statement in return string, because do not know $logout until page is built
+ $ret .= "
+ <?php if ($logout !='') { ?>
+ <p><small><small><a href='" . $_SERVER['PHP_SELF'] . "?m=NewModule&f=display'>" . _("new_module") . "</a></small></small></p>
+ <?php } ?>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText("new_module");
+ $ret .= $display->displayLine();
+
+ return $ret;
+ }
+
+}
+
+
+?>
+
+
diff --git a/fs_selfservice/fri/includes/ajax.php b/fs_selfservice/fri/includes/ajax.php
new file mode 100644
index 0000000..fc7961b
--- /dev/null
+++ b/fs_selfservice/fri/includes/ajax.php
@@ -0,0 +1,132 @@
+<?php
+
+/*
+ * AJAX page update script
+ */
+function ajaxRefreshScript($args) {
+
+ global $AJAX_PAGE_REFRESH_TIME;
+
+ $url_args = "?ajax_refresh=1&";
+ foreach($args as $key => $value) {
+ $url_args .= $key . "=" . $value . "&";
+ }
+ $url_args = substr($url_args, 0,strlen($url_args)-1);
+
+ $ret = "
+ <script type='text/javascript' language='javascript'>
+
+ var http_request = false;
+
+ function makeRequest(url, parameters) {
+
+ http_request = false;
+
+ if (window.XMLHttpRequest) { // Mozilla, Safari,...
+ http_request = new XMLHttpRequest();
+ if (http_request.overrideMimeType) {
+ http_request.overrideMimeType('text/xml');
+ }
+ }
+ else if (window.ActiveXObject) { // IE
+ try {
+ http_request = new ActiveXObject('Msxml2.XMLHTTP');
+ }
+ catch (e) {
+ try {
+ http_request = new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ catch (e) {}
+ }
+ }
+ if (!http_request) {
+ return false;
+ }
+ http_request.onreadystatechange = alertContents;
+ http_request.open('GET', url + parameters, true);
+ http_request.send(null);
+ }
+
+ function alertContents() {
+
+ if (!http_request) {
+ return;
+ }
+
+ if (http_request.readyState == 4) {
+ if (http_request.status == 200) {
+
+ var result = http_request.responseXML;
+ if (!result.documentElement && http_request.responseStream) {
+ result.load(http_request.responseStream);
+ }
+
+ var response = http_request.responseXML.documentElement;
+
+ var nav_menu = '';
+ if (response.getElementsByTagName('nav_menu')[0]) {
+ nav_menu = response.getElementsByTagName('nav_menu')[0].firstChild.data;
+ }
+ var nav_submenu = '';
+ if (response.getElementsByTagName('nav_submenu')[0]) {
+ nav_submenu = response.getElementsByTagName('nav_submenu')[0].firstChild.data;
+ }
+ var content = '';
+ if (response.getElementsByTagName('content')[0]) {
+ content = response.getElementsByTagName('content')[0].firstChild.data;
+ }
+
+ if (nav_menu) {
+ document.getElementById('nav_menu').innerHTML = '';
+ document.getElementById('nav_menu').innerHTML = nav_menu;
+ }
+ if (nav_submenu) {
+ document.getElementById('nav_submenu').innerHTML = '';
+ document.getElementById('nav_submenu').innerHTML = nav_submenu;
+ }
+ if (content) {
+ document.getElementById('content').innerHTML = '';
+ document.getElementById('content').innerHTML = content;
+ }
+ }
+ }
+ }
+
+ function updatePage() {
+ makeRequest('" . $_SESSION['ARI_ROOT'] . "', '" . $url_args . "');
+ }
+
+ // refresh time in 'minutes:seconds' (0 to inifinity) : (0 to 59)
+ var refresh_time='" . $AJAX_PAGE_REFRESH_TIME . "';
+
+ if (document.images){
+ var limit=refresh_time.split(\":\");
+ limit=limit[0]*60+limit[1]*1;
+ var current = limit;
+ }
+
+ function beginRefresh(){
+
+ if (!document.images) {
+ return;
+ }
+ if (current==1) {
+ updatePage();
+ current = limit;
+ }
+ else {
+ current-=1;
+ }
+
+ setTimeout(\"beginRefresh()\",1000);
+ }
+
+ window.onload=beginRefresh;
+
+ </script>";
+
+ return $ret;
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/includes/asi.php b/fs_selfservice/fri/includes/asi.php
new file mode 100644
index 0000000..62f221e
--- /dev/null
+++ b/fs_selfservice/fri/includes/asi.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Asterisk manager interface for access to asterisk api (astdb)
+ */
+
+/**
+ * Asterisk Manager Interface
+ */
+class AsteriskManagerInterface {
+
+ var $socket;
+
+ /**
+ * constructor
+ */
+ function AsteriskManagerInterface() {
+ }
+
+ /*
+ * Reloads Asterisk Configuration
+ *
+ * @param $username
+ * asterisk manager interface username
+ * @param $password
+ * asterisk manager interface password
+ */
+ function connect($host,$username,$password) {
+
+ // connect
+ $fp = fsockopen($host, 5038, $errno, $errstr, 10);
+ if (!$fp) {
+ return FALSE;
+ }
+ else {
+ $buffer='';
+ if(version_compare(phpversion(), '4.3', '>=')) {
+ stream_set_timeout($fp, 5);
+ }
+ else {
+ socket_set_timeout($fp, 5);
+ }
+ $buffer = fgets($fp);
+ if (!preg_match('/Asterisk Call Manager/i', $buffer)) {
+ $_SESSION['ari_error'] = _("Asterisk Call Manager not responding") . "<br />\n";
+ return FALSE;
+ }
+ else {
+ $out="Action: Login\r\nUsername: ".$username."\r\nSecret: ".$password."\r\n\r\n";
+ fwrite($fp,$out);
+ $buffer=fgets($fp);
+ if ($buffer!="Response: Success\r\n") {
+ $_SESSION['ari_error'] = _("Asterisk authentication failed:") . "<br />" . $buffer . "<br />\n";
+ return FALSE;
+ }
+ else {
+ $buffers=fgets($fp); // get rid of Message: Authentication accepted
+
+ // connected
+ $this->socket = $fp;
+ }
+ }
+ }
+ return TRUE;
+ }
+
+ /*
+ * Reloads Asterisk Configuration
+ */
+ function disconnect() {
+
+ if ($this->socket) {
+ fclose($this->socket);
+ }
+ }
+
+ /*
+ * Reloads Asterisk Configuration
+ *
+ * @param $command
+ * Command to be sent to the asterisk manager interface
+ * @return $ret
+ * response from asterisk manager interface
+ */
+ function command($command) {
+
+ $response = '';
+
+ fwrite($this->socket,$command);
+
+ $count = 0;
+ while (($buffer = fgets($this->socket)) && (!preg_match('/Response: Follows/i', $buffer))) {
+
+ if ($count>100) {
+ $_SESSION['ari_error'] = _("Asterisk command not understood") . "<br />" . $buffer . "<br />\n";
+ return FALSE;
+ }
+ $count++;
+ }
+
+ $count = 0;
+ while (($buffer = fgets($this->socket)) && (!preg_match('/END COMMAND/i', $buffer))) {
+
+ if (preg_match('/Value/',$buffer)) {
+ $parts = split(' ',trim($buffer));
+ $response = $parts[1];
+ }
+
+ if ($count>100) {
+ $_SESSION['ari_error'] = _("Asterisk command not understood") . "<br />" . $buffer . "<br />\n";
+ return;
+ }
+ $count++;
+ }
+
+ return $response;
+ }
+
+ function command2($command) {
+
+ $response = '';
+
+ fwrite($this->socket,$command);
+
+ $count = 0;
+ while (($buffer = fgets($this->socket)) && (!preg_match('/Response: Follows/i', $buffer))) {
+
+ if ($count>100) {
+ $_SESSION['ari_error'] = _("Asterisk command not understood") . "<br />" . $buffer . "<br />\n";
+ return FALSE;
+ }
+ $count++;
+ }
+
+ $count = 0;
+ while (($buffer = fgets($this->socket)) && (!preg_match('/END COMMAND/i', $buffer))) {
+
+ if (preg_match('/Value:/',$buffer)) {
+ $parts = split('Value:',trim($buffer));
+ $response = $parts[1];
+ }
+ if ($count>100) {
+ $_SESSION['ari_error'] = _("Asterisk command not understood") . "<br />" . $buffer . "<br />\n";
+ return;
+ }
+ $count++;
+ }
+
+ return $response;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/includes/bootstrap.php b/fs_selfservice/fri/includes/bootstrap.php
new file mode 100644
index 0000000..a01a2f5
--- /dev/null
+++ b/fs_selfservice/fri/includes/bootstrap.php
@@ -0,0 +1,315 @@
+<?php
+
+/**
+ * @file
+ * Functions that need to be loaded on every request.
+ */
+
+/**
+ * Sets doc root
+ */
+function setARIRoot() {
+
+ $found = 0;
+ if (isset($_SERVER['PHP_SELF'])) {
+ if ($_SERVER['PHP_SELF']!='') {
+ $_SESSION['ARI_ROOT'] = $_SERVER['PHP_SELF'];
+ }
+ }
+
+ if (!$found) {
+ $_SESSION['ARI_ROOT'] = "index.php";
+ }
+}
+
+/**
+ * Return a arguments.
+ *
+ * @param $args
+ * The name of the array being acted upon.
+ * @param $name
+ * The name of the variable to return.
+ * @return
+ * The value of the variable.
+ */
+function getArgument($args, $name) {
+
+ return isset($args[$name]) ? $args[$name] : '';
+}
+
+/*
+ * Gets top level directory names
+ *
+ * @param $path
+ * directory to search
+ * @param $filter
+ * string to use as a filter to match files to return
+ * @return $directories
+ * directories found
+ */
+function getDirectories($path,$filter) {
+
+ $directories = array();
+
+ if (is_dir($path)) {
+
+ $dh = opendir($path);
+ while (false!== ($item = readdir($dh))) {
+ if($item!="." && $item!="..") {
+
+ $path = fixPathSlash($path);
+ $directory = $path;
+ $directory = appendPath($directory,$item);
+
+ if (is_dir($directory)) {
+
+ $found = 0;
+ if ($filter) {
+ if (strpos($directory,$filter)) {
+ $found = 1;
+ }
+ } else {
+ $found = 1;
+ }
+ if ($found) {
+ $directories[count($directories) + 1] = $directory;
+ }
+ }
+ }
+ }
+ }
+
+ return $directories;
+}
+
+/*
+ * Gets file names recursively 6 folders deep
+ *
+ * @param $path
+ * directory to search
+ * @param $filter
+ * string to use as a filter to match files to return
+ * @param $recursive_max
+ * max number of sub folders to search
+ * @param $recursive_count
+ * current sub folder count
+ * @return $files
+ * files found
+ */
+function getFiles($path,$filter,$recursive_max,$recursive_count) {
+
+ $files = array();
+
+ if (@is_dir($path) && @is_readable($path)) {
+ $dh = opendir($path);
+ while (false!== ($item = readdir($dh))) {
+ if($item[0]!=".") {
+
+ $path = fixPathSlash($path);
+ $msg_path = appendPath($path,$item);
+
+ $fileCount++;
+ if ($fileCount>3000) {
+ $_SESSION['ari_error']
+ .= _("To many files in $msg_path Not all files processed") . "<br>";
+ return;
+ }
+
+ if ($recursive_count<$recursive_max && is_dir($msg_path)) {
+
+ $dirCount++;
+ if ($dirCount>10) {
+ $_SESSION['ari_error']
+ .= sprintf(_("To many directories in %s Not all files processed"),$msg_path) . "<br>";
+ return;
+ }
+
+ $count = $recursive_count + 1;
+ $path_files = getFiles($msg_path,$filter,$recursive_max,$count);
+ $files = array_merge($files,$path_files);
+ }
+ else {
+ $found = 0;
+ if ($filter) {
+ if (strpos($msg_path,$filter)) {
+ $found = 1;
+ }
+ } else {
+ $found = 1;
+ }
+ if ($found) {
+ $files[count($files) + 1] = $msg_path;
+ }
+ }
+ }
+ }
+ }
+
+ return $files;
+}
+
+/* Utilities */
+
+/**
+ * Fixes the path for a trailing slash
+ *
+ * @param $path
+ * path to append
+ * @return $ret
+ * path to returned
+ */
+function fixPathSlash($path) {
+
+ $ret = $path;
+
+ $slash = '';
+ if (!preg_match('/\/$/',$path)) {
+ $slash = '/';
+ }
+ $ret .= $slash;
+
+ return $ret;
+}
+
+/**
+ * Appends folder to end of path
+ *
+ * @param $path
+ * path to append
+ * @param $folder
+ * folder to append to path
+ * @return $ret
+ * path to returned
+ */
+function appendPath($path,$folder) {
+
+ $ret = $path;
+
+ $m = '';
+ if (!preg_match('/\/$/',$path)) {
+ $m = '/';
+ }
+ $ret .= $m . $folder;
+
+ return $ret;
+}
+
+/**
+ * Get Date format
+ *
+ * @param $timestamp
+ * timestamp to be converted
+ */
+function getDateFormat($timestamp) {
+ return date('Y-m-d', $timestamp);
+}
+
+/**
+ * Get time format
+ *
+ * @param $timestamp
+ * timestamp to be converted
+ */
+function getTimeFormat($timestamp) {
+ return date('G:i:s', $timestamp);
+}
+
+/* */
+
+/**
+ * Checks ARI dependencies
+ */
+function checkDependencies() {
+
+ // check for PHP
+ if (!version_compare(phpversion(), '4.3', '>=')) {
+ echo _("ARI requires a version of PHP 4.3 or later");
+ exit();
+ }
+
+ // check for PEAR
+ $include_path = ini_get('include_path');
+ $buf = split(':|,',$include_path);
+
+ $found = 0;
+ foreach ($buf as $path) {
+ $path = fixPathSlash($path);
+ $pear_check_path = $path . "DB.php";
+ if (is_file($pear_check_path)) {
+ $found = 1;
+ break;
+ }
+ }
+
+ if (!$found) {
+ echo _("PHP PEAR must be installed. Visit http://pear.php.net for help with installation.");
+ exit();
+ }
+}
+
+/**
+ * Starts the session
+ */
+function startARISession() {
+
+ if (!isset($_SESSION['ari_user']) ) {
+
+ // start a new session for the user
+ ini_set('session.name', 'ARI'); // prevent session name clashes
+ ini_set('session.gc_maxlifetime', '3900'); // make the session timeout a long time
+ set_time_limit(360);
+ session_start();
+ }
+}
+
+/**
+ * Bootstrap
+ *
+ * Loads critical variables needed for every page request
+ *
+ */
+function bootstrap() {
+
+ // set error reporting
+ error_reporting (E_ALL & ~ E_NOTICE);
+}
+
+/**
+ * Set HTTP headers in preparation for a page response.
+ *
+ * TODO: Figure out caching
+ */
+function ariPageHeader() {
+
+ bootstrap();
+}
+
+/**
+ * Perform end-of-request tasks.
+ *
+ * This function sets the page cache if appropriate, and allows modules to
+ * react to the closing of the page by calling hook_exit().
+ */
+function ariPageFooter() {
+
+}
+
+/**
+ * Includes and run functions
+ */
+
+include_once("./includes/lang.php");
+$language = new Language();
+$language->set();
+
+checkDependencies();
+startARISession();
+setARIRoot();
+
+include_once("./includes/main.conf.php");
+include_once("./version.php");
+include_once("./includes/crypt.php");
+include_once("./includes/login.php");
+
+
+?>
diff --git a/fs_selfservice/fri/includes/common.php b/fs_selfservice/fri/includes/common.php
new file mode 100644
index 0000000..87f2026
--- /dev/null
+++ b/fs_selfservice/fri/includes/common.php
@@ -0,0 +1,434 @@
+<?php
+
+/**
+ * @file
+ * common functions - core handler
+ */
+
+/*
+ * Checks if user is set and sets
+ */
+function checkErrorMessage() {
+
+ if ($_SESSION['ari_error']) {
+ $ret .= "<div class='error'>
+ " . $_SESSION['ari_error'] . "
+ </div>
+ <br>";
+ unset($_SESSION['ari_error']);
+ }
+
+ return $ret;
+}
+
+/*
+ * Checks modules directory, and configuration, and loaded modules
+ */
+function loadModules() {
+
+ global $ARI_ADMIN_MODULES;
+ global $ARI_DISABLED_MODULES;
+
+ global $loaded_modules;
+
+ $modules_path = "./modules";
+ if (is_dir($modules_path)) {
+
+ $filter = ".module";
+ $recursive_max = 1;
+ $recursive_count = 0;
+ $files = getFiles($modules_path,$filter,$recursive_max,$recursive_count);
+
+ foreach($files as $key => $path) {
+
+ // build module object
+ include_once($path);
+ $path_parts = pathinfo($path);
+ list($name,$ext) = split("\.",$path_parts['basename']);
+
+ // check for module and get rank
+ if (class_exists($name)) {
+
+ $module = new $name();
+
+ // check if admin module
+ $found = 0;
+ if ($ARI_ADMIN_MODULES) {
+ $admin_modules = split(',',$ARI_ADMIN_MODULES);
+ foreach ($admin_modules as $key => $value) {
+ if ($name==$value) {
+ $found = 1;
+ break;
+ }
+ }
+ }
+
+ // check if disabled module
+ $disabled = 0;
+ if ($ARI_DISABLED_MODULES) {
+ $disabled_modules = split(',',$ARI_DISABLED_MODULES);
+ foreach ($disabled_modules as $key => $value) {
+ if ($name==$value) {
+ $disabled = 1;
+ break;
+ }
+ }
+ }
+
+ // if not admin module or admin user add to module name to array
+ if (!$disabled && (!$found || $_SESSION['ari_user']['admin'])) {
+ $loaded_modules[$name] = $module;
+ }
+ }
+ }
+ }
+ else {
+ $_SESSION['ari_error'] = _("$path not a directory or not readable");
+ }
+}
+
+/**
+ * Builds database connections
+ */
+function databaseLogon() {
+
+ global $STANDALONE;
+
+ global $ASTERISKMGR_DBHOST;
+
+ global $AMP_FUNCTIONS_FILES;
+ global $AMPORTAL_CONF_FILE;
+
+ global $LEGACY_AMP_DBENGINE;
+ global $LEGACY_AMP_DBFILE;
+ global $LEGACY_AMP_DBHOST;
+ global $LEGACY_AMP_DBNAME;
+
+ global $ASTERISKCDR_DBENGINE;
+ global $ASTERISKCDR_DBFILE;
+ global $ASTERISKCDR_DBHOST;
+ global $ASTERISKCDR_DBNAME;
+
+ global $ARI_DISABLED_MODULES;
+
+ global $loaded_modules;
+
+ // This variable is a global in the FreePBX function.inc.php but needs to be
+ // declared here or the is not seen when parse_amprotaconf() is eventually called
+ // ?php bug?
+ //
+ global $amp_conf_defaults;
+
+ // get user
+ if ($STANDALONE['use']) {
+
+ $mgrhost = $ASTERISKMGR_DBHOST;
+ $mgruser = $STANDALONE['asterisk_mgruser'];
+ $mgrpass = $STANDALONE['asterisk_mgrpass'];
+
+ $asteriskcdr_dbengine = $ASTERISKCDR_DBENGINE;
+ $asteriskcdr_dbfile = $ASTERISKCDR_DBFILE;
+ $asteriskcdr_dbuser = $STANDALONE['asteriskcdr_dbuser'];
+ $asteriskcdr_dbpass = $STANDALONE['asteriskcdr_dbpass'];
+ $asteriskcdr_dbhost = $ASTERISKCDR_DBHOST;
+ $asteriskcdr_dbname = $ASTERISKCDR_DBNAME;
+ }
+ else {
+
+ $include = 0;
+ $files = split(';',$AMP_FUNCTIONS_FILES);
+ foreach ($files as $file) {
+ if (is_file($file)) {
+ include_once($file);
+ $include = 1;
+ }
+ }
+
+ if ($include) {
+ $amp_conf = parse_amportal_conf($AMPORTAL_CONF_FILE);
+
+ $mgrhost = $ASTERISKMGR_DBHOST;
+ $mgruser = $amp_conf['AMPMGRUSER'];
+ $mgrpass = $amp_conf['AMPMGRPASS'];
+
+ $amp_dbengine = isset($amp_conf["AMPDBENGINE"]) ? $amp_conf["AMPDBENGINE"] : $LEGACY_AMP_DBENGINE;
+ $amp_dbfile = isset($amp_conf["AMPDBFILE"]) ? $amp_conf["AMPDBFILE"] : $LEGACY_AMP_DBFILE;
+ $amp_dbuser = $amp_conf["AMPDBUSER"];
+ $amp_dbpass = $amp_conf["AMPDBPASS"];
+ $amp_dbhost = isset($amp_conf["AMPDBHOST"]) ? $amp_conf["AMPDBHOST"] : $LEGACY_AMP_DBHOST;
+ $amp_dbname = isset($amp_conf["AMPDBNAME"]) ? $amp_conf["AMPDBNAME"] : $LEGACY_AMP_DBNAME;
+
+ $asteriskcdr_dbengine = $ASTERISKCDR_DBENGINE;
+ $asteriskcdr_dbfile = $ASTERISKCDR_DBFILE;
+ $asteriskcdr_dbuser = $amp_conf["AMPDBUSER"];
+ $asteriskcdr_dbpass = $amp_conf["AMPDBPASS"];
+ $asteriskcdr_dbhost = $ASTERISKCDR_DBHOST;
+ $asteriskcdr_dbhost = isset($amp_conf["AMPDBHOST"]) ? $amp_conf["AMPDBHOST"] : $ASTERISKCDR_DBHOST;
+ $asteriskcdr_dbname = $ASTERISKCDR_DBNAME;
+
+ unset($amp_conf);
+ }
+ }
+
+ // asterisk manager interface (berkeley database I think)
+ global $asterisk_manager_interface;
+ $asterisk_manager_interface = new AsteriskManagerInterface();
+
+ $success = $asterisk_manager_interface->Connect($mgrhost,$mgruser,$mgrpass);
+ if (!$success) {
+ $_SESSION['ari_error'] =
+ _("ARI does not appear to have access to the Asterisk Manager.") . " ($errno)<br>" .
+ _("Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager Account.") . "<br>" .
+ _("Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account") . "<br>" .
+ _("make sure [general] enabled = yes and a 'permit=' line for localhost or the webserver.");
+ return FALSE;
+ }
+
+ // pear interface databases
+ $db = new Database();
+
+ // AMP asterisk database
+ if (!$STANDALONE['use']) {
+ $_SESSION['dbh_asterisk'] = $db->logon($amp_dbengine,
+ $amp_dbfile,
+ $amp_dbuser,
+ $amp_dbpass,
+ $amp_dbhost,
+ $amp_dbname);
+ if (!isset($_SESSION['dbh_asterisk'])) {
+ $_SESSION['ari_error'] .= _("Cannot connect to the $amp_dbname database") . "<br>" .
+ _("Check AMP installation, asterisk, and ARI main.conf");
+ return FALSE;
+ }
+ }
+
+ // cdr database
+ if (in_array('callmonitor',array_keys($loaded_modules))) {
+ $_SESSION['dbh_cdr'] = $db->logon($asteriskcdr_dbengine,
+ $asteriskcdr_dbfile,
+ $asteriskcdr_dbuser,
+ $asteriskcdr_dbpass,
+ $asteriskcdr_dbhost,
+ $asteriskcdr_dbname);
+ if (!isset($_SESSION['dbh_cdr'])) {
+ $_SESSION['ari_error'] .= sprintf(_("Cannot connect to the $asteriskcdr_dbname database"),$asteriskcdr_dbname) . "<br>" .
+ _("Check AMP installation, asterisk, and ARI main.conf");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Logout if needed for any databases
+ */
+function databaseLogoff() {
+
+ global $asterisk_manager_interface;
+
+ $asterisk_manager_interface->Disconnect();
+}
+
+/*
+ * Checks if user is set and sets
+ */
+function loginBlock() {
+
+ $login = new Login();
+
+ if (isset($_REQUEST['logout'])) {
+ $login->Unauth();
+ }
+
+ if (!isset($_SESSION['ari_user'])) {
+ $login->Auth();
+
+ }
+
+ if (!isset($_SESSION['ari_user'])) {
+
+ // login form
+ $ret .= $login->GetForm();
+
+ return $ret;
+ }
+}
+
+/*
+ * Main handler for website
+ */
+function handleBlock() {
+
+ global $ARI_NO_LOGIN;
+
+ global $loaded_modules;
+
+ // check errors here and in login block
+ $content .= checkErrorMessage();
+
+ // check logout
+ if ($_SESSION['ari_user'] && !$ARI_NO_LOGIN) {
+ $logout = 1;
+ }
+
+ // if nothing set goto user default page
+ if (!isset($_REQUEST['m'])) {
+ $_REQUEST['m'] = $_SESSION['ari_user']['default_page'];
+ }
+ // if not function specified then use display page function
+ if (!isset($_REQUEST['f'])) {
+ $_REQUEST['f'] = 'display';
+ }
+
+ $m = $_REQUEST['m']; // module
+ $f = $_REQUEST['f']; // function
+ $a = $_REQUEST['a']; // action
+
+ // set arguments
+ $args = array();
+ foreach($_REQUEST as $key => $value) {
+ $args[$key] = $value;
+ }
+
+ // set rank
+ $ranked_modules = array();
+ foreach ($loaded_modules as $module) {
+
+ $module_methods = get_class_methods($module); // note that PHP4 returns all lowercase
+ while (list($index, $value) = each($module_methods)) {
+ $module_methods[strtolower($index)] = strtolower($value);
+ }
+ reset($module_methods);
+
+ $rank = 99999;
+ $rank_function = "rank";
+ if (in_array(strtolower($rank_function), $module_methods)) {
+ $rank = $module->$rank_function();
+ }
+
+ $ranked_modules[$rank] = $module;
+ }
+ ksort($ranked_modules);
+
+ // process modules
+ foreach ($ranked_modules as $module) {
+
+ // process module
+ $name = get_class($module); // note PHP4 returns all lowercase
+ $module_methods = get_class_methods($module); // note PHP4 returns all lowercase
+ while (list($index, $value) = each($module_methods)) {
+ $module_methods[strtolower($index)] = strtolower($value);
+ }
+ reset($module_methods);
+
+ // init module
+ $module->init();
+
+ // add nav menu items
+ $nav_menu_function = "navMenu";
+ if (in_array(strtolower($nav_menu_function), $module_methods)) {
+ $nav_menu .= $module->$nav_menu_function($args);
+ }
+
+ if (strtolower($m)==strtolower($name)) {
+
+ // build sub menu
+ $subnav_menu_function = "navSubMenu";
+ if (in_array(strtolower($subnav_menu_function), $module_methods)) {
+ $subnav_menu .= $module->$subnav_menu_function($args);
+ }
+
+ // execute function (usually to build content)
+ if (in_array(strtolower($f), $module_methods)) {
+ $content .= $module->$f($args);
+ }
+ }
+ }
+
+ // add logout link
+ if ($logout != '') {
+ $nav_menu .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?logout=1'>" . _("Logout") . "</a></small></small></p>";
+ }
+
+ // error message if no content
+ if (!$content) {
+ $content .= _("Page Not Found.");
+ }
+
+ return array($nav_menu,$subnav_menu,$content);
+}
+
+/*
+ * Main handler for website
+ */
+function handler() {
+
+ global $ARI_VERSION;
+
+ // version
+ $ari_version = $ARI_VERSION;
+
+ // check error
+ $error = $_SESSION['ari_error'];
+
+ // load modules
+ loadModules();
+
+ // login to database
+ $success = databaseLogon();
+ if ($success) {
+
+ // check if login is needed
+ $content = loginBlock();
+ if (!isset($content)) {
+ list($nav_menu,$subnav_menu,$content) = handleBlock();
+ }
+ }
+ else {
+
+ $display = new Display();
+
+ $content .= $display->displayHeaderText("ARI");
+ $content .= $display->displayLine();
+ $content .= checkErrorMessage();
+ }
+
+ // log off any databases needed
+ databaseLogoff();
+
+ // check for ajax request and refresh or if not build the page
+ if (isset($_REQUEST['ajax_refresh']) ) {
+
+ echo "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+ <response>
+ <nav_menu><![CDATA[" . $nav_menu . "]]></nav_menu>
+ <subnav_menu><![CDATA[" . $subnav_menu . "]]></subnav_menu>
+ <content><![CDATA[" . $content . "]]></content>
+ </response>";
+ }
+ else {
+
+ // build the page
+ include_once("./theme/page.tpl.php");
+ }
+}
+
+/**
+ * Includes and run functions
+ */
+
+// create asterisk manager interface singleton
+$asterisk_manager_interface = '';
+
+// array to keep track of loaded modules
+$loaded_modules = array();
+
+include_once("./includes/asi.php");
+include_once("./includes/database.php");
+include_once("./includes/display.php");
+include_once("./includes/ajax.php");
+
+include_once("./includes/freeside.class.php");
+
+?>
diff --git a/fs_selfservice/fri/includes/crypt.php b/fs_selfservice/fri/includes/crypt.php
new file mode 100644
index 0000000..301d8a8
--- /dev/null
+++ b/fs_selfservice/fri/includes/crypt.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * Allows encrypt and decrypt
+ */
+class Crypt {
+
+ /**
+ * Gets a random value for encryption
+ * - From php.net docs
+ *
+ * @param $iv_len
+ * length of random variable
+ */
+ function getRndIV($iv_len) {
+
+ $iv = '';
+ while ($iv_len-- > 0) {
+ $iv .= chr(mt_rand() & 0xff);
+ }
+ return $iv;
+ }
+
+ /**
+ * Encrypts string
+ * - From php.net docs
+ *
+ * @param $str
+ * string to encrypt
+ * @param $salt
+ * password to use for encryption
+ * @param $iv_len
+ * length of random number
+ */
+ function encrypt($str, $salt, $iv_len = 16) {
+
+ $str .= "\x13";
+ $n = strlen($str);
+ if ($n % 16) $str .= str_repeat("\0", 16 - ($n % 16));
+ $i = 0;
+ $enc_text = $this->getRndIV($iv_len);
+ $iv = substr($salt ^ $enc_text, 0, 512);
+ while ($i < $n) {
+ $block = substr($str, $i, 16) ^ pack('H*', md5($iv));
+ $enc_text .= $block;
+ $iv = substr($block . $iv, 0, 512) ^ $salt;
+ $i += 16;
+ }
+ return urlencode(base64_encode($enc_text));
+ }
+
+ /**
+ * Decrypts string
+ * - From php.net docs
+ *
+ * @param $enc
+ * encrypted string to decrypt
+ * @param $salt
+ * password to use for encryption
+ * @param $iv_len
+ * length of random number
+ */
+ function decrypt($enc, $salt, $iv_len = 16) {
+
+ $enc = urldecode(base64_decode($enc));
+ $n = strlen($enc);
+ $i = $iv_len;
+ $str = '';
+ $iv = substr($salt ^ substr($enc, 0, $iv_len), 0, 512);
+ while ($i < $n) {
+ $block = substr($enc, $i, 16);
+ $str .= $block ^ pack('H*', md5($iv));
+ $iv = substr($block . $iv, 0, 512) ^ $salt;
+ $i += 16;
+ }
+ return preg_replace('/\\x13\\x00*$/', '', $str);
+ }
+}
+
+
+?>
diff --git a/fs_selfservice/fri/includes/database.php b/fs_selfservice/fri/includes/database.php
new file mode 100644
index 0000000..ff3d199
--- /dev/null
+++ b/fs_selfservice/fri/includes/database.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Functions for the database
+ */
+
+/*
+ * Database Class
+ */
+class Database {
+
+ /*
+ * Constructor
+ */
+ function Database() {
+
+ // PEAR must be installed
+ require_once('DB.php');
+ }
+
+ /*
+ * Logs into database and returns database handle
+ *
+
+ * @param $engine
+ * database engine
+ * @param $dbfile
+ * database file
+ * @param $username
+ * username for database
+ * @param $password
+ * password for database
+ * @param $host
+ * database host
+ * @param $name
+ * database name
+ * @return $dbh
+ * variable to hold the returned database handle
+ */
+ function logon($engine,$dbfile,$username,$password,$host,$name) {
+
+ // connect string
+ if ($dbfile) {
+ // datasource mostly to support sqlite: dbengine://dbfile?mode=xxxx
+ $dsn = $engine . '://' . $dbfile . '?mode=0666';
+ }
+ else {
+ // datasource in in this style: dbengine://username:password@host/database
+ $datasource = $engine . '://' . $username . ':' . $password . '@' . $host . '/' . $name;
+ }
+
+ // options
+ $options = array(
+ 'debug' => 2,
+ 'portability' => DB_PORTABILITY_LOWERCASE|DB_PORTABILITY_RTRIM|DB_PORTABILITY_DELETE_COUNT|DB_PORTABILITY_NUMROWS|DB_PORTABILITY_ERRORS|DB_PORTABILITY_NULL_TO_EMPTY,
+ );
+
+ // attempt connection
+ $dbh = DB::connect($datasource,$options);
+
+ // if connection failed show error
+ if(DB::isError($dbh)) {
+ $_SESSION['ari_error'] .= $dbh->getMessage() . "<br><br>";
+ return;
+ }
+ return $dbh;
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/includes/display.php b/fs_selfservice/fri/includes/display.php
new file mode 100644
index 0000000..41d8dc5
--- /dev/null
+++ b/fs_selfservice/fri/includes/display.php
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * Functions common to display
+ */
+
+/**
+ * Display
+ */
+class Display {
+
+ /**
+ * display constructor
+ */
+ function Display() {
+ }
+
+ /**
+ * display text header
+ *
+ * @param $text
+ * Header text to display
+ */
+ function displayHeaderText($text) {
+
+ $ret = "<h2>" . $text . "</h2>
+ <br>";
+
+ return $ret;
+ }
+
+ /**
+ * displays header line
+ */
+ function displayLine() {
+
+ $ret = "
+ <div id='line'>
+ <div class='spacer'></div>
+ <div class='spacer'></div>
+ </div>
+ <br>";
+
+ return $ret;
+ }
+}
+
+/**
+ * DisplaySearch
+ */
+class DisplaySearch extends Display {
+
+ /**
+ * Constructor
+ */
+ function DisplaySearch() {
+ }
+
+ /**
+ * displays search controls
+ *
+ * @param $align
+ * where to align the control
+ * @param $q
+ * search query
+ * @param $focus
+ * whether to focus control on this block
+ */
+ function displaySearchBlock($align,$m,$q,$url_opts,$focus) {
+
+ // align
+ if ($align=='center') {
+ $alignText = "class='bar_center'";
+ }
+ else {
+ $alignText = "class='bar_left'";
+ }
+
+ // url options
+ foreach ($url_opts as $key => $value) {
+ $option_text .= "<input type=hidden name=" . $key . " value=" . $value . ">";
+ }
+
+ // build
+ $ret .= "<div " . $alignText . ">
+ <form class='bar' action='" . $_SESSION['ARI_ROOT'] . "' method='GET' name='search'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=text name=q size=40 value='" . $q . "' maxlength=256>
+ " . $option_text . "
+ <input type=hidden name=start value=0>
+ <input type=submit name=btnS value='" . _("Search") . "'>
+ </form>
+ </div>";
+
+ if ($focus=="true") { // search block loaded twice usually so only allow javascript to be loaded on the top block
+ $ret .= "<script type='text/javascript'>
+ <!--
+ if (document.search) {
+ document.search.q.focus();
+ }
+ // -->
+ </script>";
+ }
+
+ return $ret;
+ }
+
+ /**
+ * displays info bar
+ *
+ * @param $controls
+ * controls for the page on the bar
+ * @param $q
+ * search query
+ * @param $start
+ * start number of current page
+ * @param $span
+ * number of items on current page
+ * @param $total
+ * total number of records found by current search
+ */
+ function displayInfoBarBlock($controls,$q,$start,$span,$total) {
+
+ if ($total<$span) {
+ $span = $total;
+ }
+ $start_count = ($total>0)?$start+1:$start;
+ $span_count = ($start+$span>$total)?$total:$start+$span;
+
+ if ($controls) {
+ $left_text = $controls;
+ }
+ elseif ($q != NULL) {
+ $left_text = "<small><small>" . _("Searched for") . " <u>" . $q . "</u></small></small>";
+ }
+
+ if ($span<$total) {
+ $right_text = "<small><small>" . sprintf(_("Results %d - %d of %d"),$start_count,$span_count,$total) . "</small></small>";
+ } else {
+ $right_text = "<small><small>" . sprintf(_("Results %d"),$total) . "</small></small>";
+ }
+
+ $ret .= "
+ <table id='navbar' width='100%'>
+ <tr>
+ <td>
+ " . $left_text . "
+ </td>
+ <td align='right'>
+ " . $right_text ."
+ </td>
+ </tr>
+ </table>";
+
+ return $ret;
+ }
+
+ /**
+ * displays navigation bar
+ *
+ * @param $q
+ * search query
+ * @param $start
+ * start number of current page
+ * @param $span
+ * number of items on current page
+ * @param $total
+ * total number of records found by current search
+ */
+ function displayNavigationBlock($m,$q,$url_opts,$start,$span,$total) {
+
+ $start = $start=='' ? 0 : $start ;
+ $span = $span=='' ? 15 : $span ;
+
+ $total_pages = ceil($total/$span);
+ $start_page = floor($start/$span);
+
+ // if more than ten pages start at this page minus ten otherwise start at zero
+ $begin = ($start_page>10)?($start_page-10):0;
+ // if more than ten pages then stop at this page plus ten otherwise stop at last page
+ $end = ($start_page>8)?($start_page+10):10;
+
+ // url
+ $unicode_q = urlencode($q); // encode search string
+
+ foreach ($url_opts as $key => $value) {
+ $option_text .= "&" . $key . "=" . $value;
+ }
+
+ $url = $_SESSION['ARI_ROOT'] . "?m=" . $m . "&q=" . $unicode_q . $option_text;
+
+ // build
+ if ($start_page!=0) {
+ $start_page_text = "<a href='" . $url . "&start=0'><small>" . _("First") . "</a>&nbsp;</small>
+ <a href=" . $url . "&start=" . ($start-$span) . "><small><</a>&nbsp;</small>";
+ }
+
+ for($next_page=$begin;($next_page<$total_pages)&&($next_page<$end);$next_page++) {
+ if ($next_page == $start_page) {
+ $middle_page_text .= "<small>" . ($next_page+1) . "&nbsp;</small>";
+ } else {
+ $middle_page_text .= "<a href='" . $url . "&start=" . ($next_page*$span) . "'><small>" . ($next_page+1) . "</a>&nbsp;</small>";
+ }
+ }
+ if ( ($start_page != $total_pages-1) && ($total != 0) ) {
+ $end_page_text = "<a href='" . $url . "&start=" . ($start+$span) . "'><small>></a>&nbsp;</small>
+ <a href='" . $url . "&start=" . ($total_pages-1)*$span . "'><small>" . _("Last") . "</a>&nbsp;</small>";
+ }
+
+ $ret .= "<div class='bar_center'>
+ " . $start_page_text . "
+ " . $middle_page_text . "
+ " . $end_page_text . "
+ </div>";
+
+ return $ret;
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/includes/freeside.class.php b/fs_selfservice/fri/includes/freeside.class.php
new file mode 100644
index 0000000..a441398
--- /dev/null
+++ b/fs_selfservice/fri/includes/freeside.class.php
@@ -0,0 +1,38 @@
+<?php
+class FreesideSelfService {
+
+ //Change this to match the location of your selfservice xmlrpc.cgi or daemon
+ //var $URL = 'https://www.example.com/selfservice/xmlrpc.cgi';
+ var $URL = 'http://localhost/selfservice/xmlrpc.cgi';
+
+ function FreesideSelfService() {
+ $this;
+ }
+
+ public function __call($name, $arguments) {
+
+ error_log("[FreesideSelfService] $name called, sending to ". $this->URL);
+
+ $request = xmlrpc_encode_request("FS.SelfService.XMLRPC.$name", $arguments);
+ $context = stream_context_create( array( 'http' => array(
+ 'method' => "POST",
+ 'header' => "Content-Type: text/xml",
+ 'content' => $request
+ )));
+ $file = file_get_contents($this->URL, false, $context);
+ if (!$file) {
+ trigger_error("[FreesideSelfService] XML-RPC communication error: file_get_contents did not return");
+ } else {
+ $response = xmlrpc_decode($file);
+ if (xmlrpc_is_fault($response)) {
+ trigger_error("[FreesideSelfService] XML-RPC communication error: $response[faultString] ($response[faultCode])");
+ } else {
+ //error_log("[FreesideSelfService] $response");
+ return $response;
+ }
+ }
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/includes/lang.php b/fs_selfservice/fri/includes/lang.php
new file mode 100644
index 0000000..b27b8e3
--- /dev/null
+++ b/fs_selfservice/fri/includes/lang.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * i18n language functions
+ */
+
+/**
+ * Class for login
+ */
+class Language {
+
+ var $error;
+
+ /**
+ * Sets i18n locale language
+ *
+ * sets the language for i18n php gettext module
+ * (gettext has to be enabled in the php.ini)
+ *
+ */
+ function set() {
+
+ if (extension_loaded('gettext')) {
+
+ // try and find the default locale
+ $default_lang = preg_replace('/-/','_',$_SERVER['HTTP_ACCEPT_LANGUAGE']);
+
+ $locale = 'en_US';
+ $locale_dir = "./locale";
+ $directories = getdirectories($locale_dir,"");
+ foreach($directories as $directory) {
+ $buf = substr($directory,strlen($locale_dir)+1,strlen($directory) - strlen($locale_dir));
+ if (preg_match("/" . $buf . "/i",$default_lang)) {
+ $locale = $buf;
+ break;
+ }
+ }
+
+ // set locale
+ $language = isset($_COOKIE['ari_lang']) ? $_COOKIE['ari_lang'] : $locale;
+ putenv("LANG=$language");
+ putenv("LANGUAGE=$language");
+ setlocale(LC_MESSAGES,$language);
+ bindtextdomain('ari','./locale');
+ bind_textdomain_codeset('ari', 'UTF-8');
+ textdomain('ari');
+
+ } else {
+ function _($str) {
+ return $str;
+ }
+ }
+ }
+
+ /**
+ * Sets the i18n language in a cookie
+ *
+ * @param $lang_code
+ * length of random number
+ */
+ function setCookie($lang_code) {
+
+ if (extension_loaded('gettext')) {
+ setcookie("ari_lang", $lang_code, time()+365*24*60*60);
+ }
+ }
+
+ /**
+ * Sets the i18n language in a cookie
+ *
+ * @param $lang_code
+ * length of random number
+ */
+ function getForm() {
+
+ // lang setting options
+ if (extension_loaded('gettext')) {
+
+ $langOptions = "
+ <script>
+ function setCookie(name,value) {
+ var t = new Date();
+ var e = new Date();
+ e.setTime(t.getTime() + 365*24*60*60);
+ document.cookie = name+\"=\"+escape(value) + \";expires=\"+e.toGMTString();
+ }
+ </script>
+ <form class='lang' name='lang' action=" . $_SESSION['ARI_ROOT'] . " method='POST'>
+ <select class='lang_code' name='lang_code' onChange=\"setCookie('ari_lang',document.lang.lang_code.value); window.location.reload();\">
+ <option value='en_US' " . ($_COOKIE['ari_lang']=='en_US' ? 'selected' : '') . ">English</option>
+ <option value='es_ES' " . ($_COOKIE['ari_lang']=='es_ES' ? 'selected' : '') . ">Espa&ntilde;ol</option>
+ <option value='fr_FR' " . ($_COOKIE['ari_lang']=='fr_FR' ? 'selected' : '') . ">French</option>
+ <option value='de_DE' " . ($_COOKIE['ari_lang']=='de_DE' ? 'selected' : '') . ">German</option>
+ <option value='el_GR' " . ($_COOKIE['ari_lang']=='el_GR' ? 'selected' : '') . ">Greek</option>
+ <option value='he_IL' " . ($_COOKIE['ari_lang']=='he_IL' ? 'selected' : '') . ">Hebrew</option>
+ <option value='hu_HU' " . ($_COOKIE['ari_lang']=='hu_HU' ? 'selected' : '') . ">Hungarian</option>
+ <option value='it_IT' " . ($_COOKIE['ari_lang']=='it_IT' ? 'selected' : '') . ">Italian</option>
+ <option value='pt_BR' " . ($_COOKIE['ari_lang']=='pt_BR' ? 'selected' : '') . ">Portuguese</option>
+ <option value='sv_SE' " . ($_COOKIE['ari_lang']=='sv_SE' ? 'selected' : '') . ">Swedish</option>
+ </select>
+ </form>";
+ }
+
+ return $langOptions;
+ }
+
+
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/includes/login.php b/fs_selfservice/fri/includes/login.php
new file mode 100644
index 0000000..41bb7a6
--- /dev/null
+++ b/fs_selfservice/fri/includes/login.php
@@ -0,0 +1,515 @@
+<?php
+
+/**
+ * @file
+ * login functions
+ */
+
+/**
+ * Class for login
+ */
+class Login {
+
+ var $error;
+
+ /**
+ * Authenticate user and register user information into a session
+ */
+ function Auth() {
+
+ global $ARI_ADMIN_USERNAME;
+ global $ARI_ADMIN_PASSWORD;
+ global $ARI_ADMIN_EXTENSIONS;
+ global $ARI_CRYPT_PASSWORD;
+ global $ASTERISK_VOICEMAIL_CONF;
+ global $ASTERISK_VOICEMAIL_CONTEXT;
+ global $ASTERISK_VOICEMAIL_PATH;
+ global $ASTERISK_PROTOCOLS;
+ global $CALLMONITOR_ADMIN_EXTENSIONS;
+ global $ARI_NO_LOGIN;
+ global $ARI_DEFAULT_ADMIN_PAGE;
+ global $ARI_DEFAULT_USER_PAGE;
+
+ $crypt = new Crypt();
+
+ // init variables
+ $extension = '';
+ $displayname = '';
+ $vm_password = '';
+ $category = '';
+ $context = '';
+ $voicemail_enabled = '';
+ $voicemail_email_address = '';
+ $voicemail_pager_address = '';
+ $voicemail_email_enable = '';
+ $admin = '';
+ $admin_callmonitor = '';
+ $default_page = '';
+
+ $username = '';
+ $password = '';
+
+ // get the ari authentication cookie
+ $data = '';
+ $chksum = '';
+ if (isset($_COOKIE['ari_auth'])) {
+ $buf = unserialize($_COOKIE['ari_auth']);
+ list($data,$chksum) = $buf;
+ }
+ if (md5($data) == $chksum) {
+ $data = unserialize($crypt->decrypt($data,$ARI_CRYPT_PASSWORD));
+ $username = $data['username'];
+ $password = $data['password'];
+ }
+
+ if (isset($_POST['username']) &&
+ isset($_POST['password'])) {
+ $username = $_POST['username'];
+ $password = $_POST['password'];
+ }
+
+ // init email options array
+ $voicemail_email = array();
+
+ // when login, make a new session
+ if ($username && !$ARI_NO_LOGIN) {
+
+ $auth = false;
+
+ // check admin
+ if (!$auth) {
+ if ($username==$ARI_ADMIN_USERNAME &&
+ $password==$ARI_ADMIN_PASSWORD) {
+
+ // authenticated
+ $auth = true;
+
+ $extension = 'admin';
+ $name = 'Administrator';
+ $admin = 1;
+ $admin_callmonitor = 1;
+
+ $default_page = $ARI_DEFAULT_ADMIN_PAGE;
+ }
+ }
+
+ // check voicemail login
+ if (!$auth) {
+
+ if (is_readable($ASTERISK_VOICEMAIL_CONF)) {
+
+ $lines = file($ASTERISK_VOICEMAIL_CONF);
+
+ // look for include files and tack their lines to end of array
+ foreach ($lines as $key => $line) {
+
+ if (preg_match("/include/i",$line)) {
+
+ $include_filename = '';
+ $parts = split(' ',$line);
+ if (isset($parts[1])) {
+ $include_filename = trim($parts[1]);
+ }
+
+ if ($include_filename) {
+ $path_parts = pathinfo($ASTERISK_VOICEMAIL_CONF);
+ $include_path = fixPathSlash($path_parts['dirname']) . $include_filename;
+ foreach (glob($include_path) as $include_file) {
+ $include_lines = file($include_file);
+ $lines = array_merge($include_lines,$lines);
+ }
+ }
+ }
+ }
+
+ // process
+ foreach ($lines as $key => $line) {
+
+ // check for current context and process
+ if (preg_match("/\[.*\]/i",$line)) {
+ $currentContext = trim(preg_replace('/\[|\]/', '', $line));
+ }
+ if ($ASTERISK_VOICEMAIL_CONTEXT &&
+ $currentContext!=$ASTERISK_VOICEMAIL_CONTEXT) {
+ continue;
+ }
+
+ // check for user and process
+ unset($value);
+ $parts = split('=>',$line);
+ if (isset($parts[0])) {
+ $var = $parts[0];
+ }
+ if (isset($parts[1])) {
+ $value = $parts[1];
+ }
+ $var = trim($var);
+ if ($var==$username && $value) {
+ $buf = split(',',$value);
+ if ($buf[0]==$password) {
+
+ // authenticated
+ $auth = true;
+ $extension = $username;
+ $displayname = $buf[1];
+ $vm_password = $buf[0];
+ $default_page = $ARI_DEFAULT_USER_PAGE;
+ $context = $currentContext;
+ $voicemail_enabled = 1;
+ $voicemail_email_address = $buf[2];
+ $voicemail_pager_address = $buf[3];
+
+ if ($voicemail_email_address || $voicemail_pager_address) {
+ $voicemail_email_enable = 1;
+ }
+
+ $options = split('\|',$buf[4]);
+ foreach ($options as $option) {
+ $opt_buf = split('=',$option);
+ $voicemail_email[$opt_buf[0]] = trim($opt_buf[1]);
+ }
+
+ $admin = 0;
+ if ($ARI_ADMIN_EXTENSIONS) {
+ $extensions = split(',',$ARI_ADMIN_EXTENSIONS);
+ foreach ($extensions as $key => $value) {
+ if ($extension==$value) {
+ $admin = 1;
+ break 2;
+ }
+ }
+ }
+
+ $admin_callmonitor = 0;
+ if ($CALLMONITOR_ADMIN_EXTENSIONS) {
+ $extensions = split(',',$CALLMONITOR_ADMIN_EXTENSIONS);
+ foreach ($extensions as $key => $value) {
+ if ($value=='all' || $extension==$value) {
+ $admin_callmonitor = 1;
+ break 2;
+ }
+ }
+ }
+ }
+ else {
+ $_SESSION['ari_error'] = "Incorrect Password";
+ return;
+ }
+ }
+ }
+ }
+ else {
+ $_SESSION['ari_error'] = "File not readable: " . $ASTERISK_VOICEMAIL_CONF;
+ return;
+ }
+ }
+
+ // check sip login
+ if (!$auth) {
+
+ foreach($ASTERISK_PROTOCOLS as $protocol => $value) {
+
+ $config_files = split(';',$value['config_files']);
+ foreach ($config_files as $config_file) {
+
+ if (is_readable($config_file)) {
+
+ $lines = file($config_file);
+ foreach ($lines as $key => $line) {
+
+ unset($value);
+ $parts = split('=',$line);
+ if (isset($parts[0])) {
+ $var = trim($parts[0]);
+ }
+ if (isset($parts[1])) {
+ $value = trim($parts[1]);
+ }
+ if ($var=="username") {
+ $protocol_username = $value;
+ }
+ if ($var=="secret") {
+
+ $protocol_password = $value;
+ if ($protocol_username==$username &&
+ $protocol_password==$password) {
+
+ // authenticated
+ $auth = true;
+ $extension = $username ;
+ $displayname = $username;
+ $default_page = $ARI_DEFAULT_ADMIN_PAGE;
+
+ $admin = 0;
+ if ($ARI_ADMIN_EXTENSIONS) {
+ $extensions = split(',',$ARI_ADMIN_EXTENSIONS);
+ foreach ($extensions as $key => $value) {
+ if ($extension==$value) {
+ $admin = 1;
+ break 2;
+ }
+ }
+ }
+
+ $admin_callmonitor = 0;
+ if ($CALLMONITOR_ADMIN_EXTENSIONS) {
+ $extensions = split(',',$CALLMONITOR_ADMIN_EXTENSIONS);
+ foreach ($extensions as $key => $value) {
+ if ($value=='all' || $extension==$value) {
+ $admin_callmonitor = 1;
+ break 2;
+ }
+ }
+ }
+ }
+ else if ($protocol_username==$username &&
+ $protocol_password!=$password) {
+ $_SESSION['ari_error'] = _("Incorrect Password");
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // let user know bad login
+ if (!$auth) {
+ $_SESSION['ari_error'] = _("Incorrect Username or Password");
+ }
+
+ // freeside login
+ $freeside = new FreesideSelfService();
+ $domain = 'svc_phone';
+ $response = $freeside->login( array(
+ 'username' => strtolower($username),
+ 'domain' => $domain,
+ 'password' => strtolower($password),
+ ) );
+ error_log("[login] received response from freeside: $response");
+ $error = $response['error'];
+
+ if ( ! $error && $response['session_id'] ) {
+
+ // sucessful freeside login
+ error_log("[login] logged into freeside with session_id=$session_id");
+
+ // store session id in your session store, to be used for other calls
+ //$fs_session_id = $response['session_id'];
+ $_SESSION['freeside_session_id'] = $response['session_id'];
+
+ $customer_info = $freeside->customer_info( array(
+ 'session_id' => $_SESSION['freeside_session_id'] ,
+ ) );
+ //XXX error checking here too
+ $displayname = $customer_info['name'];
+
+ } else {
+
+ // unsucessful login
+ error_log("[login] error logging into freeside: $error");
+ $auth = false;
+ $extension = '';
+
+ // display error message to user
+ $_SESSION['ari_error'] = _("Incorrect Username or Password");
+
+ }
+
+ // if authenticated and user wants to be remembered, set cookie
+ $remember = '';
+ if (isset($_POST['remember'])) {
+ $remember = $_POST['remember'];
+ }
+ if ($auth && $remember) {
+
+ $data = array('username' => $username, 'password' => $password);
+ $data = $crypt->encrypt(serialize($data),$ARI_CRYPT_PASSWORD);
+
+ $chksum = md5($data);
+
+ $buf = serialize(array($data,$chksum));
+ setcookie('ari_auth',$buf,time()+365*24*60*60,'/');
+ }
+
+ // set category
+ if (!$category) {
+ $category = "general";
+ }
+
+ // set context
+ if (!$context) {
+ $context = "default";
+ }
+
+ // no login user
+ if ($ARI_NO_LOGIN) {
+ $extension = 'admin';
+ $name = 'Administrator';
+ $admin_callmonitor = 1;
+ $default_page = $ARI_DEFAULT_ADMIN_PAGE;
+ }
+
+ // get outboundCID if it exists
+ $outboundCID = $this->getOutboundCID($extension);
+
+ // set
+ if ($extension) {
+ $_SESSION['ari_user']['extension'] = $extension;
+ $_SESSION['ari_user']['outboundCID'] = $outboundCID;
+ $_SESSION['ari_user']['displayname'] = $displayname;
+ $_SESSION['ari_user']['voicemail_password'] = $vm_password;
+ $_SESSION['ari_user']['category'] = $category;
+ $_SESSION['ari_user']['context'] = $context;
+ $_SESSION['ari_user']['voicemail_enabled'] = $voicemail_enabled;
+ $_SESSION['ari_user']['voicemail_email_address'] = $voicemail_email_address;
+ $_SESSION['ari_user']['voicemail_pager_address'] = $voicemail_pager_address;
+ $_SESSION['ari_user']['voicemail_email_enable'] = $voicemail_email_enable;
+ foreach ($voicemail_email as $key => $value) {
+ $_SESSION['ari_user']['voicemail_email'][$key] = $value;
+ }
+ $_SESSION['ari_user']['admin'] = $admin;
+ $_SESSION['ari_user']['admin_callmonitor'] = $admin_callmonitor;
+ $_SESSION['ari_user']['default_page'] = $default_page;
+
+ // force the session data saved
+ session_write_close();
+ }
+ }
+ }
+
+ /*
+ * Gets user outbound caller id
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $ret
+ * outbound caller id
+ */
+ function getOutboundCID($extension) {
+
+ global $asterisk_manager_interface;
+
+ $ret = '';
+ $response = $asterisk_manager_interface->Command2("Action: Command\r\nCommand: database get AMPUSER $extension/outboundcid\r\n\r\n");
+ if ($response) {
+
+ $posLeft = strpos( $response, "<")+strlen("<");
+ $posRight = strpos( $response, ">", $posLeft);
+ $ret = substr( $response,$posLeft,$posRight-$posLeft);
+ }
+ return $ret;
+ }
+
+ /**
+ * logout
+ */
+ function Unauth() {
+ unset($_COOKIE["ari_auth"]);
+ setcookie('ari_auth',"",time(),'/');
+ unset($_SESSION['ari_user']);
+ }
+
+ /**
+ * Provide a login form for user
+ *
+ * @param $request
+ * Variable to hold data entered into form
+ */
+ function GetForm() {
+
+ global $ARI_NO_LOGIN;
+
+ if ($ARI_NO_LOGIN) {
+ $ret = '';
+ return;
+ }
+
+ if (isset($_GET['login'])) {
+ $login = $_GET['login'];
+ }
+
+ // if user name and password were given, but there was a problem report the error
+ if ($this->error!='') {
+ $ret = $this->error;
+ }
+
+ $language = new Language();
+ $display = new Display(NULL);
+
+ // new header
+ $ret .= $display->DisplayHeaderText(_("Login"));
+ $ret .= $display->DisplayLine();
+ $ret .= checkErrorMessage();
+
+ $ret .= "
+ <table id='login'>
+ <form id='login' name='login' action=" . $_SESSION['ARI_ROOT'] . " method='POST'>
+ <tr>
+ <td class='right'>
+ <small><small>" . _("Login") . ":&nbsp;&nbsp;</small></small>
+ </td>
+ <td>
+ <input type='text' name='username' value='" . $login . "' maxlength=20 tabindex=1>
+ </td>
+ </tr>
+ <tr>
+ <td class='right'>
+ <small><small>" . _("Password") . ":&nbsp;&nbsp;</small></small>
+ </td>
+ <td colspan=1>
+ <input type='password' name='password' maxlength=20 tabindex=2>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>
+ <input type='submit' name='btnSubmit' value='" . _("Submit") . "' tabindex=3></small></small></p>
+ </td>
+ </tr>
+ <tr>
+ <td class='right'>
+ <input type='checkbox' name='remember'>
+ </td>
+ <td class='left'>
+ <p class='small'>" . _("Remember Password") . "</p>
+ </td>
+ </tr>
+ </form>
+ <tr>
+ <td></td>
+ <td>
+ " . $language->getForm() . "
+ </td>
+ </tr>
+ <tr><td>&nbsp;</td></tr>
+ </table>
+ <table id='login_text'>
+ <tr>
+ <td>" .
+ _("Use your <b>Voicemail Mailbox and Password</b>") . "<br>" .
+ _("This is the same password used for the phone") . "<br>" .
+ "<br>" .
+ _("For password maintenance or assistance, contact your Phone System Administrator.") . "<br>" . "
+ </td>
+ </tr>
+ </table>";
+
+ $ret .= "
+ <script type='text/javascript'>
+ <!--
+ if (document.login) {
+ document.login.username.focus();
+ }
+ // -->
+ </script>";
+
+ return $ret;
+ }
+
+
+}
+
+
+?>
diff --git a/fs_selfservice/fri/includes/main.conf.php b/fs_selfservice/fri/includes/main.conf.php
new file mode 100644
index 0000000..cedf60c
--- /dev/null
+++ b/fs_selfservice/fri/includes/main.conf.php
@@ -0,0 +1,331 @@
+<?php
+
+/**
+ * @file
+ * site-specific configuration file.
+ */
+
+###############################
+# AMP or standalone settings
+###############################
+#
+# From AMP. Used for logon to database.
+#
+$AMP_FUNCTIONS_FILES = "../admin/functions.php;../admin/functions.inc.php";
+$AMPORTAL_CONF_FILE = "/etc/amportal.conf";
+
+#
+# Host for Asterisk Manager Interface
+#
+$ASTERISKMGR_DBHOST = "localhost";
+
+#
+# Database options for older legacy AMP installations (pre-FreePBX)
+# - $LEGACY_AMP_DBFILE only needs to be set if using a database like sqlite
+#
+$LEGACY_AMP_DBHOST = "localhost";
+$LEGACY_AMP_DBENGINE = "mysql";
+$LEGACY_AMP_DBFILE = "";
+$LEGACY_AMP_DBNAME = "asterisk";
+
+#
+# Database cdr settings
+# - Only need to update these settings if standalone or an older AMP version (pre-FreePBX) is used
+# - $ASTERISKCDR_DBFILE only needs to be set if using a database like sqlite
+# Options: supported database types (others are supported, but not listed)
+# 'mysql' - MySQL
+# 'pgsql' - PostgreSQL
+# 'oci8' - Oracle
+# 'odbc' - ODBC
+#
+$ASTERISKCDR_DBHOST = "localhost";
+$ASTERISKCDR_DBENGINE = "mysql";
+$ASTERISKCDR_DBFILE = "";
+$ASTERISKCDR_DBNAME = "asteriskcdrdb";
+$ASTERISKCDR_DBTABLE = "cdr";
+
+#
+# Standalone, for use without AMP
+# set use = true;
+# set asterisk_mgruser to Asterisk Call Manager username
+# set asterisk_mgrpass to Asterisk Call Manager password
+#
+$STANDALONE['use'] = false;
+$STANDALONE['asterisk_mgruser'] = "";
+$STANDALONE['asterisk_mgrpass'] = "";
+$STANDALONE['asteriskcdr_dbuser'] = "";
+$STANDALONE['asteriskcdr_dbpass'] = "";
+
+###############################
+# authentication settings
+###############################
+#
+# For using the Call Monitor only
+# option: 0 - use Authentication, Voicemail, and Call Monitor
+# 1 - use only the Call Monitor
+#
+$ARI_NO_LOGIN = 0;
+
+#
+# Admin only account
+#
+$ARI_ADMIN_USERNAME = "admin";
+$ARI_ADMIN_PASSWORD ="ari_password";
+#
+# Admin extensions
+# option: Comma delimited list of extensions
+#
+$ARI_ADMIN_EXTENSIONS = "";
+
+#
+# Authentication password to unlock cookie password
+# This must be all continuous and only letters and numbers
+#
+$ARI_CRYPT_PASSWORD = "z1Mc6KRxA7Nw90dGjY5qLXhtrPgJOfeCaUmHvQT3yW8nDsI2VkEpiS4blFoBuZ";
+
+###############################
+# modules settings
+###############################
+#
+# modules with admin only status (they will not be displayed for regular users)
+# option: Comma delimited list of module names (ie voicemail,callmonitor,help,settings)
+#
+$ARI_ADMIN_MODULES = "";
+
+#
+# disable modules (you can also just delete them from /recordings/modules without problems)
+# option: Comma delimited list of module names (ie voicemail,callmonitor,help,settings)
+#
+$ARI_DISABLED_MODULES = "";
+
+#
+# sets the default admin page
+# option: Comma delimited list of module names (ie voicemail,callmonitor,help,settings)
+#
+$ARI_DEFAULT_ADMIN_PAGE = "callmonitor";
+
+#
+# sets the default user page
+# option: Comma delimited list of module names (ie voicemail,callmonitor,help,settings)
+#
+#$ARI_DEFAULT_USER_PAGE = "voicemail";
+$ARI_DEFAULT_USER_PAGE = "dashboard";
+
+#
+# enables ajax page refresh
+# option: 0 - disable ajax page refresh
+# 1 - enable ajax page refresh
+#
+$AJAX_PAGE_REFRESH_ENABLE = 1;
+
+#
+# sets the default user page
+# option: refresh time in 'minutes:seconds' (0 to inifinity) : (0 to 59)
+#
+$AJAX_PAGE_REFRESH_TIME ="01:00";
+###############################
+# voicemail settings
+###############################
+#
+# voicemail config.
+#
+$ASTERISK_VOICEMAIL_CONF = "/etc/asterisk/voicemail.conf";
+
+#
+# To set to a specific context.
+# If using default or more than one context then leave blank
+#
+$ASTERISK_VOICEMAIL_CONTEXT = "";
+
+#
+# Location of asterisk voicemail recordings on server
+# Use semi-colon for multiple paths
+#
+$ASTERISK_VOICEMAIL_PATH = "/var/spool/asterisk/voicemail";
+
+#
+# valid mailbox folders
+#
+$ASTERISK_VOICEMAIL_FOLDERS = array();
+$ASTERISK_VOICEMAIL_FOLDERS[0]['folder'] = "INBOX";
+$ASTERISK_VOICEMAIL_FOLDERS[0]['name'] = _("INBOX");
+$ASTERISK_VOICEMAIL_FOLDERS[1]['folder'] = "Family";
+$ASTERISK_VOICEMAIL_FOLDERS[1]['name'] = _("Family");
+$ASTERISK_VOICEMAIL_FOLDERS[2]['folder'] = "Friends";
+$ASTERISK_VOICEMAIL_FOLDERS[2]['name'] = _("Friends");
+$ASTERISK_VOICEMAIL_FOLDERS[3]['folder'] = "Old";
+$ASTERISK_VOICEMAIL_FOLDERS[3]['name'] = _("Old");
+$ASTERISK_VOICEMAIL_FOLDERS[4]['folder'] = "Work";
+$ASTERISK_VOICEMAIL_FOLDERS[4]['name'] = _("Work");
+
+###############################
+# call monitor settings
+###############################
+#
+# Location of asterisk call monitor recordings on server
+#
+$ASTERISK_CALLMONITOR_PATH = "/var/spool/asterisk/monitor";
+
+#
+# Extensions with access to all call monitor recordings
+# option: Comma delimited list of extensions or "all"
+#
+$CALLMONITOR_ADMIN_EXTENSIONS ="";
+#
+# Allow call monitor users to delete monitored calls
+# option: 0 - do not show controls
+# 1 - show controls
+#
+$CALLMONITOR_ALLOW_DELETE = 1;
+
+#
+# Allow for aggressive matching of recording files to database records
+# will match recordings that are marked several seconds off
+# option: 0 - do not aggressively match
+# 1 - aggressively match
+#
+$CALLMONITOR_AGGRESSIVE_MATCHING = 1;
+
+#
+# Limits log/recording file matching to exact matching
+# will not try to look through all the recordings and make a best match
+# even if there is not uniqueid
+# requires that the MYSQL_UNIQUEID flag be compiled in asterisk-addons
+# (in the asterisk-addon Makefile add the following "CFLAGS+=-DMYSQL_LOGUNIQUEID")
+#
+# * use if there are or will be more than 2500 recording files
+#
+# option: 0 - do not exact match
+# 1 - only exact match
+#
+$CALLMONITOR_ONLY_EXACT_MATCHING = 0;
+
+###############################
+# conference page settings
+###############################
+#
+# Meetme extension prefix
+# for this module to function, the user has to have
+# a meetme conference room {prefix}{extension}
+#
+$CONFERENCE_WEBMEETME_PREFIX = "";
+
+#
+# url to web meetme conference room
+# example: "http://example.mycompany.com/webmeetme"
+#
+$CONFERENCE_WEBMEETME_URL = "";
+
+###############################
+# help page settings
+###############################
+#
+# help feature codes
+# list of handset options and their function
+#
+$ARI_HELP_FEATURE_CODES = array();
+//$ARI_HELP_FEATURE_CODES['*411'] = _("Directory");
+//$ARI_HELP_FEATURE_CODES['*43'] = _("Echo Test");
+//$ARI_HELP_FEATURE_CODES['*60'] = _("Time");
+//$ARI_HELP_FEATURE_CODES['*61'] = _("Weather");
+//$ARI_HELP_FEATURE_CODES['*62'] = _("Schedule wakeup call");
+//$ARI_HELP_FEATURE_CODES['*65'] = _("festival test (your extension is XXX)");
+//$ARI_HELP_FEATURE_CODES['*77'] = _("IVR Recording");
+//$ARI_HELP_FEATURE_CODES['*99'] = _("Playback IVR Recording");
+//$ARI_HELP_FEATURE_CODES['666'] = _("Test Fax");
+//$ARI_HELP_FEATURE_CODES['7777'] = _("Simulate incoming call");
+
+$ARI_HELP_FEATURE_CODES['*72'] = _("Call Forward All Activate");
+$ARI_HELP_FEATURE_CODES['*73'] = _("Call Forward All Deactivate");
+$ARI_HELP_FEATURE_CODES['*74'] = _("Call Forward All Prompting Deactivate");
+$ARI_HELP_FEATURE_CODES['*90'] = _("Call Forward Busy Activate");
+$ARI_HELP_FEATURE_CODES['*91'] = _("Call Forward Busy Deactivate");
+$ARI_HELP_FEATURE_CODES['*92'] = _("Call Forward Busy Prompting Deactivate");
+$ARI_HELP_FEATURE_CODES['*52'] = _("Call Forward No Answer/Unavailable Activate");
+$ARI_HELP_FEATURE_CODES['*53'] = _("Call Forward No Answer/Unavailable Deactivate");
+$ARI_HELP_FEATURE_CODES['*70'] = _("Call Waiting - Activate");
+$ARI_HELP_FEATURE_CODES['*71'] = _("Call Waiting - Deactivate");
+$ARI_HELP_FEATURE_CODES['*78'] = _("Do-Not-Disturb Activate");
+$ARI_HELP_FEATURE_CODES['*79'] = _("Do-Not-Disturb Deactivate");
+$ARI_HELP_FEATURE_CODES['*97'] = _("My Voicemail");
+$ARI_HELP_FEATURE_CODES['*98'] = _("Dial Voicemail");
+
+###############################
+# settings page settings
+###############################
+#
+# protocol config.
+# config_file options: semi-colon delimited list of extensions
+#
+$ASTERISK_PROTOCOLS = array();
+$ASTERISK_PROTOCOLS['iax']['table'] = "iax";
+$ASTERISK_PROTOCOLS['iax']['config_files'] = "/etc/asterisk/iax.conf;/etc/asterisk/iax_additional.conf";
+$ASTERISK_PROTOCOLS['sip']['table'] = "sip";
+$ASTERISK_PROTOCOLS['sip']['config_files'] = "/etc/asterisk/sip.conf;/etc/asterisk/sip_additional.conf";
+$ASTERISK_PROTOCOLS['zap']['table'] = "zap";
+$ASTERISK_PROTOCOLS['zap']['config_files'] = "/etc/asterisk/zapata.conf;/etc/asterisk/zapata_additional.conf";
+
+# Settings for Follow-Me Select Boxes in seconds
+#
+
+$SETTINGS_PRERING_LOW = 4;
+$SETTINGS_PRERING_HIGH = 30;
+$SETTINGS_LISTRING_LOW = 6;
+$SETTINGS_LISTRING_HIGH = 60;
+
+$SETTINGS_FOLLOW_ME_LIST_MAX = 5;
+$SETTINGS_ALLOW_VMX_SETTINGS = true;
+#
+# For setting
+# option: 0 - do not show controls
+# 1 - show controls
+#
+$SETTINGS_ALLOW_CALLFORWARD_SETTINGS = 1;
+$SETTINGS_ALLOW_VOICEMAIL_SETTINGS = 1;
+$SETTINGS_ALLOW_VOICEMAIL_PASSWORD_SET = 1;
+
+#
+# password length
+# setting: number of characters required for changing voicemail password
+#
+$SETTINGS_VOICEMAIL_PASSWORD_LENGTH = 3;
+
+#
+# password exact length
+# option: 0 - do not require exact length when setting the password
+# 1 - require exact length when setting the password
+#
+$SETTINGS_VOICEMAIL_PASSWORD_EXACT = 0;
+
+#
+# voicemail email option descriptions
+#
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS = array();
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['attach'] = _("Email voicemail as attachment");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['saycid'] = _("Say caller id in recording emailed");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['envelope'] = _("Say envelop (date/time) in recording emailed");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['delete'] = _("Delete voicemail when emailed");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['nextaftercmd'] = _("Play next message after deleting current message");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['review'] = _("Ask caller to review their voicemail before sending");
+$SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS['maxmessage'] = _("Maximum time in seconds a voicemail will record");
+
+#
+# Default
+# option: ".wav" - wav format
+# ".gsm" - gsm format
+#
+$ARI_VOICEMAIL_AUDIO_FORMAT_DEFAULT = ".wav";
+
+#
+# For setting
+# option: 0 - do not show controls
+# 1 - show controls
+#
+$SETTINGS_ALLOW_CALL_RECORDING_SET = 1;
+
+
+$SETTINGS_ALLOW_PHONE_SETTINGS = 1;
+
+
+
+?>
diff --git a/fs_selfservice/fri/index.php b/fs_selfservice/fri/index.php
new file mode 100644
index 0000000..0fe6149
--- /dev/null
+++ b/fs_selfservice/fri/index.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * main
+ */
+
+include_once("includes/bootstrap.php");
+ariPageHeader();
+include_once("includes/common.php");
+
+handler();
+
+ariPageFooter();
+
+
+?>
+
+
+
diff --git a/fs_selfservice/fri/locale/ari.po b/fs_selfservice/fri/locale/ari.po
new file mode 100644
index 0000000..4e3493e
--- /dev/null
+++ b/fs_selfservice/fri/locale/ari.po
@@ -0,0 +1,590 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr ""
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr ""
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+msgid "Asterisk command not understood"
+msgstr ""
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr ""
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr ""
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr ""
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr ""
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr ""
+
+#: ../includes/display.php:139
+#, php-format
+msgid "Results %d - %d of %d"
+msgstr ""
+
+#: ../includes/display.php:141
+#, php-format
+msgid "Results %d"
+msgstr ""
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr ""
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr ""
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr ""
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr ""
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr ""
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr ""
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr ""
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr ""
+
+#: ../includes/login.php:451
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr ""
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr ""
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr ""
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr ""
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr ""
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr ""
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr ""
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr ""
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+msgid "IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr ""
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr ""
+
+#: ../modules/callmonitor.module:147
+msgid "duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:150
+msgid "ignore"
+msgstr ""
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr ""
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr ""
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr ""
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr ""
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr ""
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr ""
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr ""
+
+#: ../modules/callmonitor.module:259
+#, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Call Monitor for %s (%s)"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr ""
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr ""
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr ""
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr ""
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, php-format
+msgid "Conference for %s (%s%s)"
+msgstr ""
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr ""
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr ""
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr ""
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr ""
+
+#: ../modules/settings.module:157
+#, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr ""
+
+#: ../modules/settings.module:162
+#, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr ""
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr ""
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, php-format
+msgid "%s does not exist or is not writable"
+msgstr ""
+
+#: ../modules/settings.module:223
+msgid "Voicemail email and pager address not changed"
+msgstr ""
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+msgid "Voicemail email settings not changed"
+msgstr ""
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr ""
+
+#: ../modules/settings.module:408
+msgid "Call Routing"
+msgstr ""
+
+#: ../modules/settings.module:411
+msgid "Call Forwarding:"
+msgstr ""
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+msgid "Enable"
+msgstr ""
+
+#: ../modules/settings.module:431
+#, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr ""
+
+#: ../modules/settings.module:434
+#, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr ""
+
+#: ../modules/settings.module:439
+msgid "Voicemail Password:"
+msgstr ""
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr ""
+
+#: ../modules/settings.module:492
+msgid "Email Voicemail To:"
+msgstr ""
+
+#: ../modules/settings.module:498
+msgid "Pager Voicemail To:"
+msgstr ""
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr ""
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr ""
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr ""
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr ""
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Call Monitor Settings"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr ""
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr ""
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr ""
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr ""
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr ""
+
+#: ../modules/settings.module:669
+#, php-format
+msgid "Settings for %s (%s)"
+msgstr ""
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr ""
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr ""
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr ""
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr ""
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr ""
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr ""
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr ""
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr ""
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr ""
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr ""
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+msgid "Voicemail Login not found."
+msgstr ""
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr ""
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr ""
+
+#: ../modules/voicemail.module:428
+#, php-format
+msgid "Voicemail for %s (%s)"
+msgstr ""
+
+#: ../modules/voicemail.module:678
+#, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr ""
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr ""
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr ""
diff --git a/fs_selfservice/fri/locale/ari.utf-8.po b/fs_selfservice/fri/locale/ari.utf-8.po
new file mode 100644
index 0000000..aff5a75
--- /dev/null
+++ b/fs_selfservice/fri/locale/ari.utf-8.po
@@ -0,0 +1,590 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr ""
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr ""
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+msgid "Asterisk command not understood"
+msgstr ""
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr ""
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr ""
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr ""
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr ""
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr ""
+
+#: ../includes/display.php:139
+#, php-format
+msgid "Results %d - %d of %d"
+msgstr ""
+
+#: ../includes/display.php:141
+#, php-format
+msgid "Results %d"
+msgstr ""
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr ""
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr ""
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr ""
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr ""
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr ""
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr ""
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr ""
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr ""
+
+#: ../includes/login.php:451
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr ""
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr ""
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr ""
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr ""
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr ""
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr ""
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr ""
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr ""
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+msgid "IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr ""
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr ""
+
+#: ../modules/callmonitor.module:147
+msgid "duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:150
+msgid "ignore"
+msgstr ""
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr ""
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr ""
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr ""
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr ""
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr ""
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr ""
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr ""
+
+#: ../modules/callmonitor.module:259
+#, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr ""
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr ""
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr ""
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr ""
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr ""
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, php-format
+msgid "Conference for %s (%s%s)"
+msgstr ""
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr ""
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr ""
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr ""
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr ""
+
+#: ../modules/settings.module:157
+#, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr ""
+
+#: ../modules/settings.module:162
+#, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr ""
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr ""
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, php-format
+msgid "%s does not exist or is not writable"
+msgstr ""
+
+#: ../modules/settings.module:223
+msgid "Voicemail email and pager address not changed"
+msgstr ""
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+msgid "Voicemail email settings not changed"
+msgstr ""
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr ""
+
+#: ../modules/settings.module:408
+msgid "Call Routing"
+msgstr ""
+
+#: ../modules/settings.module:411
+msgid "Call Forwarding:"
+msgstr ""
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+msgid "Enable"
+msgstr ""
+
+#: ../modules/settings.module:431
+#, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr ""
+
+#: ../modules/settings.module:434
+#, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr ""
+
+#: ../modules/settings.module:439
+msgid "Voicemail Password:"
+msgstr ""
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr ""
+
+#: ../modules/settings.module:492
+msgid "Email Voicemail To:"
+msgstr ""
+
+#: ../modules/settings.module:498
+msgid "Pager Voicemail To:"
+msgstr ""
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr ""
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr ""
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr ""
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr ""
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr ""
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr ""
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr ""
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr ""
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr ""
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr ""
+
+#: ../modules/settings.module:669
+#, php-format
+msgid "Settings for %s (%s)"
+msgstr ""
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr ""
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr ""
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr ""
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr ""
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr ""
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr ""
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr ""
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr ""
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr ""
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr ""
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+msgid "Voicemail Login not found."
+msgstr ""
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr ""
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr ""
+
+#: ../modules/voicemail.module:428
+#, php-format
+msgid "Voicemail for %s (%s)"
+msgstr ""
+
+#: ../modules/voicemail.module:678
+#, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr ""
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr ""
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr ""
diff --git a/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..b94eba2
--- /dev/null
+++ b/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..b89b612
--- /dev/null
+++ b/fs_selfservice/fri/locale/de_DE/LC_MESSAGES/ari.po
@@ -0,0 +1,631 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2005 AsteriskPBX.de
+# This file is distributed under the same license as the PACKAGE package.
+# Till Stoemer <ts@AsteriskPBX.de>, 2005.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: ari-de\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-04-03 08:26-0400\n"
+"PO-Revision-Date: 2005-12-10 19:50+0100\n"
+"Last-Translator: Till Stoermer <ts@AsteriskPBX.de>\n"
+"Language-Team: German <ts@AsteriskPBX.de>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:48
+msgid "Asterisk Call Manager not responding"
+msgstr "Der Asterisk Call-Manager reagiert nicht."
+
+#: ../includes/asi.php:56
+msgid "Asterisk authentication failed:"
+msgstr "Anmeldung am Asterisk gescheitert."
+
+#: ../includes/asi.php:98 ../includes/asi.php:112
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "Asterisk reload command not understood"
+
+#: ../includes/bootstrap.php:106
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:209
+msgid "ARI requires a version of PHP 4.0 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:228
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:167
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Kann nicht zum Asterisk-Manager verbinden"
+
+#: ../includes/common.php:168
+msgid ""
+"Check the ARI 'main.conf' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:169
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:170
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:187 ../includes/common.php:202
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"&Uuml;berpr&uuml;fe die AMP-Installation, Asterisk-Datenbank, oder die ARI "
+"main.conf"
+
+#: ../includes/common.php:332
+msgid "Logout"
+msgstr "Abmelden"
+
+#: ../includes/common.php:337
+msgid "Page Not Found."
+msgstr "Seite nicht gefunden"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "Suchen"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Suchen nach"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d of %d"
+msgstr "Ergebnis"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "Ergebnis"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Erste"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Letzte"
+
+#: ../includes/login.php:239
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Vicemail-Login nicht gefunden"
+
+#: ../includes/login.php:240
+msgid "No access to voicemail"
+msgstr "Kein Zugriff auf Voicemail"
+
+#: ../includes/login.php:266
+msgid "Incorrect Password"
+msgstr "Falsches Passwort"
+
+#: ../includes/login.php:278
+msgid "Incorrect Username or Password"
+msgstr "Falscher Benutzer oder Passwort"
+
+#: ../includes/login.php:381 ../includes/login.php:391
+msgid "Login"
+msgstr "Anmeldung"
+
+#: ../includes/login.php:399
+msgid "Password"
+msgstr "Passwort"
+
+#: ../includes/login.php:408
+msgid "Submit"
+msgstr "Anmelden"
+
+#: ../includes/login.php:416
+msgid "Remember Password"
+msgstr "Passwort merken"
+
+#: ../includes/login.php:431
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "Voicemail Mailbox und Password"
+
+#: ../includes/login.php:432
+msgid "This is the same password used for the phone"
+msgstr "Dieses ist das selbe Passwort, das beim Telefon genutzt wird."
+
+#: ../includes/login.php:434
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"F&uuml;r Passwort-&Auml;nderungen, kontaktieren Sie Ihren Voicemail-Admin"
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "Eingang"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "Familie"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "Freunde"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "Alt"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "Arbeit"
+
+#: ../includes/main.conf.php:213
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:214
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:215 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:326
+msgid "Time"
+msgstr "Uhrzeit"
+
+#: ../includes/main.conf.php:216
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:217
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:218
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:219
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:220
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:221
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:222
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:223
+#, fuzzy
+msgid "IVR Recording"
+msgstr "Aufnahmen"
+
+#: ../includes/main.conf.php:224
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:225
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:226
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:227
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:228
+msgid "Message Center (does no ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:229
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:231
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:232
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:273
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:274
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:275
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:276
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:277
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:278
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:279
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "Anrufliste"
+
+#: ../modules/callmonitor.module:132 ../modules/voicemail.module:117
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:303
+#, fuzzy
+msgid "delete"
+msgstr "Ausw&auml;hlen"
+
+#: ../modules/callmonitor.module:147
+#, fuzzy
+msgid "duration"
+msgstr "Dauer"
+
+#: ../modules/callmonitor.module:150
+#, fuzzy
+msgid "ignore"
+msgstr "Keine"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:324
+msgid "Date"
+msgstr "Datum"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:328
+msgid "Caller ID"
+msgstr "Anrufer-Nummer"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "Anrufer"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "Angerufener"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "Kontext"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:334
+msgid "Duration"
+msgstr "Dauer"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "Monitor"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:375
+msgid "play"
+msgstr "Abspielen"
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Anrufliste"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:459
+msgid "select"
+msgstr "Ausw&auml;hlen"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:460
+msgid "all"
+msgstr "Alle"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:461
+msgid "none"
+msgstr "Keine"
+
+#: ../modules/callmonitor.module:543
+msgid "Only deletes recording files, not cdr log"
+msgstr "Nur die Aufnahme-Datei wird gel&ouml;scht (In der CDR nicht)"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr ""
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:647
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: ../modules/settings.module:122
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:123
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:143 ../modules/settings.module:148
+#: ../modules/settings.module:153 ../modules/settings.module:158
+#: ../modules/settings.module:168 ../modules/settings.module:173
+msgid "Voicemail password not changed"
+msgstr "Voicemail-Passwort nicht ge&auml;ndert"
+
+#: ../modules/settings.module:144
+msgid "Password and password confirm must not be blank"
+msgstr "Passwort und Passwort-Wiederholen-Feld darf nicht leer sein"
+
+#: ../modules/settings.module:149
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "Das Passwort muss aus mindestens 4 Ziffern bestehen."
+
+#: ../modules/settings.module:154
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "Das Passwort muss aus mindestens 4 Ziffern bestehen."
+
+#: ../modules/settings.module:159
+msgid "Password and password confirm do not match"
+msgstr "Die Passwort stimmen nicht &uuml;berein."
+
+#: ../modules/settings.module:169 ../modules/settings.module:174
+#: ../modules/settings.module:226 ../modules/settings.module:231
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "existiert nicht, oder ist nicht lesbar."
+
+#: ../modules/settings.module:215
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "Voicemail-Passwort nicht ge&auml;ndert"
+
+#: ../modules/settings.module:225 ../modules/settings.module:230
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "Voicemail-Passwort nicht ge&auml;ndert"
+
+#: ../modules/settings.module:375
+msgid "Language:"
+msgstr "Sprache"
+
+#: ../modules/settings.module:396
+#, fuzzy
+msgid "Call Routing"
+msgstr "Call Monitor Einstellungen"
+
+#: ../modules/settings.module:399
+msgid "Call Forwarding:"
+msgstr ""
+
+#: ../modules/settings.module:407 ../modules/settings.module:486
+#, fuzzy
+msgid "Enable"
+msgstr "in Tabelle"
+
+#: ../modules/settings.module:418
+msgid "Voicemail Password:"
+msgstr "Voicemail-Passwort"
+
+#: ../modules/settings.module:424
+msgid "Enter again to confirm:"
+msgstr "Erneute Eingabe zum best&auml;tigen"
+
+#: ../modules/settings.module:430
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "Das Passwort muss aus mindestens 4 Ziffern bestehen."
+
+#: ../modules/settings.module:471
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "Voicemail"
+
+#: ../modules/settings.module:477
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "Voicemail"
+
+#: ../modules/settings.module:539
+msgid "Audio Format:"
+msgstr "Audio-Format"
+
+#: ../modules/settings.module:542
+msgid "Best Quality"
+msgstr "Beste Qualit&auml;t"
+
+#: ../modules/settings.module:543
+msgid "Smallest Download"
+msgstr "F&uuml;r geringen Download"
+
+#: ../modules/settings.module:551
+msgid "Voicemail Settings"
+msgstr "Voicemail-Einstellungen"
+
+#: ../modules/settings.module:591
+msgid "Call Monitor Settings"
+msgstr "Call Monitor Einstellungen"
+
+#: ../modules/settings.module:594
+msgid "Record INCOMING:"
+msgstr "Aufnahme eingehender Telefonate:"
+
+#: ../modules/settings.module:596 ../modules/settings.module:604
+msgid "Always"
+msgstr "Immer"
+
+#: ../modules/settings.module:597 ../modules/settings.module:605
+msgid "Never"
+msgstr "Nie"
+
+#: ../modules/settings.module:598 ../modules/settings.module:606
+msgid "On-Demand"
+msgstr "Bei Bedarf"
+
+#: ../modules/settings.module:602
+msgid "Record OUTGOING:"
+msgstr "Aufnahme abgehende Telefonate"
+
+#: ../modules/settings.module:649
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Einstellungen f&uuml;r"
+
+#: ../modules/settings.module:685
+msgid "Update"
+msgstr "Erneuern"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "Voicemail"
+
+#: ../modules/voicemail.module:161
+msgid "A folder must be selected before the message can be moved."
+msgstr ""
+"Ein Ordner muss gew&auml;hlt werden, bevor die Nachricht verschoben werden "
+"kann."
+
+#: ../modules/voicemail.module:175
+msgid "An extension must be selected before the message can be forwarded."
+msgstr ""
+"Ein Anschluss muss gew&auml;hlt werden, bevor die Nachricht weitergeleitet "
+"werden kann."
+
+#: ../modules/voicemail.module:239
+msgid "No Voicemail Recordings for Admin"
+msgstr "No Voicemail Recordings for Admin"
+
+#: ../modules/voicemail.module:306
+msgid "move_to"
+msgstr ""
+
+#: ../modules/voicemail.module:309
+msgid "Folder"
+msgstr "Ordner"
+
+#: ../modules/voicemail.module:313
+msgid "forward_to"
+msgstr ""
+
+#: ../modules/voicemail.module:330
+msgid "Priority"
+msgstr "Prirorit&auml;t"
+
+#: ../modules/voicemail.module:332
+msgid "Orig Mailbox"
+msgstr "Orig Mailbox"
+
+#: ../modules/voicemail.module:364
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:379
+msgid "Voicemail recording(s) was not found."
+msgstr "Sprachnachricht(en) nicht gefunden"
+
+#: ../modules/voicemail.module:380
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:412
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Voicemail"
+
+#: ../modules/voicemail.module:662
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "Konnte Mailbox-Ordner nicht erstellen"
+
+#: ../modules/voicemail.module:702
+#, fuzzy, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "Zugriff verweigert auf Ordner"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "Download"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "Kein Verzeichnis, oder nicht lesbar"
+
+#~ msgid "No database connection"
+#~ msgstr "Keine Verbindung zur Datenbank"
+
+#~ msgid "of"
+#~ msgstr "von"
+
+#~ msgid "Login used"
+#~ msgstr "Login genutzt"
+
+#~ msgid "Use your"
+#~ msgstr "Nutze Deine"
+
+#~ msgid "for"
+#~ msgstr "f&uuml;r"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "Das Passwort muss aus mindestens 4 Ziffern bestehen."
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr "Check voicemail audio format on settings page to change from"
+
+#~ msgid "Searching of voicemail is not yet implemented"
+#~ msgstr "Searching of voicemail is not yet implemented"
+
+#~ msgid "on the server"
+#~ msgstr "auf dem Server"
+
+#~ msgid "Folders"
+#~ msgstr "Ordner"
diff --git a/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..6b00b14
--- /dev/null
+++ b/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..2566494
--- /dev/null
+++ b/fs_selfservice/fri/locale/el_GR/LC_MESSAGES/ari.po
@@ -0,0 +1,648 @@
+# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Elias Sofronas <esofronas@gmail.com>, 2005.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: 2005-11-14 10:06+0200\n"
+"Last-Translator: Elias Sofronas <esofronas@gmail.com>\n"
+"Language-Team: English <en@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Ο διαχειÏιστής κλήσεων Asterisk δεν αποκÏίνεται"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Η πιστοποίηση στο Asterisk απέτυχε:"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "Η εντολή Asterisk επαναφόÏτωσης δεν αναγνωÏίστηκε"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "ΑδÏνατη η σÏνδεση στον Asterisk Manager"
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"Ελέγχτε την εγκατάσταση του AMP, την βάση δεδομένων του asterisk, ή το ARI "
+"main.conf"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "ΑποσÏνδεση"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Η σελίδα δεν βÏέθηκε"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "ΕÏÏεση"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "ΕÏÏεση για"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Αποτελέσματα"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "Αποτελέσματα"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "ΠÏώτο"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Τελευταίο"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Λάθος Κωδικός"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Λάθος όνομα χÏήστη ή κωδικός"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "ΘυÏίδα"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "Κωδικός"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "Είσοδος"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "Απομνημόνευση ΚωδικοÏ"
+
+#: ../includes/login.php:451
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "ΘυÏίδα Τηλεφωνητή και Κωδικό"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "Αυτό είναι ο ίδιος κωδικός που χÏησιμοποιήθηκε για το τηλέφωνο"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Για αλλαγή ÎºÏ‰Î´Î¹ÎºÎ¿Ï Î® υποστήÏιξη, επικοινωνήστε με τον ΔιαχειÏιστή του "
+"συστήματος"
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "ΕΣΕΡΧΟΜΕÎΑ"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "ΟΙΚΟΓΕÎΕΙΑ"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "ΦΙΛΟΙ"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "ΠΑΛΙΑ"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "ΔΟΥΛΕΙΑ"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "ÎÏα"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+#, fuzzy
+msgid "IVR Recording"
+msgstr "Μυνήματα ΘυÏίδας"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "ΠαÏακολοÏθηση Κλήσεων"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr "διαγÏαφή"
+
+#: ../modules/callmonitor.module:147
+#, fuzzy
+msgid "duration"
+msgstr "ΔιάÏκεια"
+
+#: ../modules/callmonitor.module:150
+#, fuzzy
+msgid "ignore"
+msgstr "κανένα"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "ΗμεÏομηνία"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "Ταυτότητα ΚαλοÏντος"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "Πηγή"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "ΠÏοοÏισμός"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "ΠεÏιεχόμενο"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "ΔιάÏκεια"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "ΠαÏακολοÏθηση"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "άκουσε"
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "ΠαÏακολοÏθηση Κλήσεων"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "επιλογή"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "όλα"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "κανένα"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr ""
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "Τηλεφωνητής"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, fuzzy, php-format
+msgid "Help for %s (%s)"
+msgstr "Ρυθμίσεις για"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Ρυθμίσεις"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "Ο κωδικός του τηλεφωνητή δεν άλλαξε"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr "Ο κωδικός και η επιβεβαίωση ÎºÏ‰Î´Î¹ÎºÎ¿Ï Î´ÎµÎ½ Ï€Ïέπει να είναι κενά"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "Οι κωδικοί Ï€Ïέπει να είναι μόνο 4 αÏιθμοί"
+
+#: ../modules/settings.module:162
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "Οι κωδικοί Ï€Ïέπει να είναι μόνο 4 αÏιθμοί"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "Ο κωδικός και η επιβεβαίωση ÎºÏ‰Î´Î¹ÎºÎ¿Ï Î´ÎµÎ½ συμφωνοÏν"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "Δεν υπάÏχει ή δεν είναι εγγÏάψιμο"
+
+#: ../modules/settings.module:223
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "Ο κωδικός του τηλεφωνητή δεν άλλαξε"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "Ο κωδικός του τηλεφωνητή δεν άλλαξε"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "Γλώσσα:"
+
+#: ../modules/settings.module:408
+#, fuzzy
+msgid "Call Routing"
+msgstr "Ρυθμίσεις ΠαÏακολοÏθησης Κλήσεων"
+
+#: ../modules/settings.module:411
+#, fuzzy
+msgid "Call Forwarding:"
+msgstr "Ρυθμίσεις ΠαÏακολοÏθησης Κλήσεων"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+#, fuzzy
+msgid "Enable"
+msgstr "στο πεδίο"
+
+#: ../modules/settings.module:431
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "Οι κωδικοί Ï€Ïέπει να είναι μόνο 4 αÏιθμοί"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "Οι κωδικοί Ï€Ïέπει να είναι μόνο 4 αÏιθμοί"
+
+#: ../modules/settings.module:439
+#, fuzzy
+msgid "Voicemail Password:"
+msgstr "Κωδικός Τηλεφωνητή"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "Εισάγετε ξανά για επιβεβαίωση:"
+
+#: ../modules/settings.module:492
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "Τηλεφωνητής"
+
+#: ../modules/settings.module:498
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "Τηλεφωνητής"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "Ποιότητα Ήχου:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "Μέγιστη Ποιότητα"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "ΜικÏότεÏο Download"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "Ρυθμίσεις Τηλεφωνητή"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Ρυθμίσεις ΠαÏακολοÏθησης Κλήσεων"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "ΗχογÏάφηση ΕΙΣΕΡΧΟΜΕÎΟΥ:"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "Πάντα"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "Ποτέ"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "Επιτόπου"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "ΗχογÏάφηση ΕΞΕΡΧΟΜΕÎΟΥ:"
+
+#: ../modules/settings.module:669
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Ρυθμίσεις για"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "Ανανέωση"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "Τηλεφωνητής"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "ΠÏέπει να επιλεχθεί ένας κατάλογος Ï€Ïίν μεταφεÏεθεί το μÏνημα."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "ΠÏέπει να επιλεχθεί ΘυÏίδα παÏαλήπτη Ï€Ïίν Ï€Ïοωθηθεί το μÏνημα."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr "μετακίνηση"
+
+#: ../modules/voicemail.module:307
+#, fuzzy
+msgid "Folder"
+msgstr "Κατάλογοι"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr "Ï€Ïοώθηση"
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "ΠÏοτεÏαιότητα"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "ΑÏχικός Κατάλογος Μυνημάτων"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "Δεν βÏέθηκαν εγγÏαφή(ές) στον τηλεφωνητή."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Δεν βÏέθηκε Ï€Ïόσβαση για θυÏίδα μυνημάτων"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "Καμία Ï€Ïόσβαση στον τηλεφωνητή"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "Δεν ΥπάÏχουν ΕγγÏαφές Μυνημάτων για τον ΔιαχειÏιστή"
+
+#: ../modules/voicemail.module:428
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Τηλεφωνητής"
+
+#: ../modules/voicemail.module:678
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "ΑδÏνατη η δημιουÏγία καταλόγου μυνημάτων"
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr ""
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "κατέβασμα"
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "Οι κωδικοί Ï€Ïέπει να είναι μόνο 4 αÏιθμοί"
+
+#~ msgid "Folders"
+#~ msgstr "Κατάλογοι"
+
+#~ msgid "Login used"
+#~ msgstr "Όνομα χÏήστη που χÏησιμοποιήθηκε"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Ο διαχειÏιστής κλήσεων Asterisk δεν αποκÏίνεται"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "δεν είναι κατάλογος ή δεν είναι αναγνώσιμος"
+
+#~ msgid "of"
+#~ msgstr "από"
+
+#~ msgid "Use your"
+#~ msgstr "ΧÏησιμοποίησε την δικιά σου"
+
+#~ msgid "for"
+#~ msgstr "για"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "Ο κωδικός Ï€Ïέπει να έιναι 4 αÏιθμοί"
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr ""
+#~ "Ελέγχτε το audio format του μυνήματος στην σελίδα Ïυθμίσεων για αλλαγή"
+
+#~ msgid "on the server"
+#~ msgstr "στον server"
+
+#~ msgid "No database connection"
+#~ msgstr "Δεν υπάÏχει σÏνδεση με την βάση δεδομένων"
diff --git a/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..e0fbdd9
--- /dev/null
+++ b/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..0518573
--- /dev/null
+++ b/fs_selfservice/fri/locale/es_ES/LC_MESSAGES/ari.po
@@ -0,0 +1,616 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# Grupo Ikusnet, Antonio F. Cano <antonio@igestec.com>, 2006.
+# Grupo Ikusnet, Agustin Vericat <agustin@igestec.com>, 2006.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: 2006-03-31 13:00\n"
+"Last-Translator: Antonio F. Cano <antonio@igestec.com>\n"
+"Language-Team: Espanol <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "La Centralita no responde"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Fallo la Autenticacion con la Centralita"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+msgid "Asterisk command not understood"
+msgstr "La recarga no funcino"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr "Demasiados directorios en %s, notodos los archivos han sido procesados"
+
+#: ../includes/bootstrap.php:226
+#, fuzzy
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr "Necesita una versi&oacute; de PHP 4.0 o superior"
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+"PHP PEAR debe estar instalado. Visite http://pear.php.net para obtener ayuda"
+
+#: ../includes/common.php:173
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "No es posible conectar con la Centralita"
+
+#: ../includes/common.php:174
+#, fuzzy
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+"Compruebe el archivo 'main.conf' para configuar la conexi&oacute;n con "
+"Asterisk Manager"
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+"Compruebe /etc/asterisk/manager.conf para crear una cuenta Asterisk Manager"
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr "Compruebe la instalacion de FreePBX, DDBB o ARI en main.conf"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "Salir"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Pagina No encontrada"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "Buscar"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Buscado para"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Resultados %d de %d"
+
+#: ../includes/display.php:141
+#, php-format
+msgid "Results %d"
+msgstr "Resultados %d"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Primero"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Ultimo"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Contrase&ntilde;a Incorrecta"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Contrase&ntilde; Incorrecta"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "Usuario"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "Contrase&ntilde;a"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "Enviar"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "Recordar Contrase&ntilde;a"
+
+#: ../includes/login.php:451
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "Use su Buz&oacute;n de Voz (Usuario) y contrase&ntilde;a"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "Esta es es la misma contrase&ntilde;a usada para el telefono"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Para mantenimiento de contrase&ntilde;as o asistencia, pongase en contacto "
+"con el Administrador."
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "Entrada"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "Familiares"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "Amigos"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "Antiguos"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "Trabajo"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr "Directorio"
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr "Test Eco"
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "Hora"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr "Tiempo"
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr "Programar llamada despertador"
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr "Test festival tts (su extension es XXX)"
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr "Activar Llamada en Espera (Desactivada por defecto)"
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr "Desactivar Llamada en Espera"
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr "Desv&iacute;o de llamada"
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr "Desactivar el Desv&iacute;o de Llamada"
+
+#: ../includes/main.conf.php:239
+msgid "IVR Recording"
+msgstr "Grabaciones"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr "Activar No-Molestar"
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr "Desactivar No-Molestar"
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr "Desv&iacute;o de llamada cuando est&eacute; Ocupado"
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr "Desactivar el Desv&iacute;o de llamada cuando est&eacute; Ocupado"
+
+#: ../includes/main.conf.php:244
+#, fuzzy
+msgid "Message Center (does not ask for extension)"
+msgstr "Centro de Mensajes (no pregunta la extens&iacute;n)"
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr "Entrar en el Centro de Mensajes"
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr "Escuchar la grabaci&oacute;n realizada"
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr "Probar Fax"
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr "Simular una llamada entrante"
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr "Adjuntar el mensaje de voz en el correo electr&oacute;nico"
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+"Indica el CallerID en la grabaci&oacute;n enviada por correo electr&oacute;"
+"nico"
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+"Indica la etiqueta (tiempo/hora) en la grabaci&oacute;n enviada por correo "
+"electr&oacute;nico"
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+"Eliminar el mensaje de voz una vez enviado por correo electr&oacute;nico"
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr "Reproducir el siguiente mensaje una vez eliminado el actual"
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "Registro de Llamadas"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr "La ruta no es un directorio: %s"
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr "Eliminar"
+
+#: ../modules/callmonitor.module:147
+msgid "duration"
+msgstr "Duraci&oacute;n"
+
+#: ../modules/callmonitor.module:150
+msgid "ignore"
+msgstr "ninguno"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "Fecha"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "Caller ID"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "Origen"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "Destino"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "Contexto"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "Duraci&oacute;n"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "Monitor para"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "escuchar"
+
+#: ../modules/callmonitor.module:259
+#, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Registro de Llamadas de %s (%s)"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "Selecionar"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "todos"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "ninguno"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr "Solo elimina los archivos grabados, no el log en el CDR"
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "Buz&oacute;n de Voz de %s (%s)"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr "Ayuda"
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr "Ayuda para %s (%s)"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr "Teclas de Marcaci&oacute;n"
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr "Acci&oacute;n"
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Opciones"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr "El n&uacute;mero del desv&iacute;o no ha cambiado"
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+"El n&uacute;mero %s debe contener n&uacte;meros marcables (caracteres como "
+"'(', '-', y ')' son v&aacute;lidos)"
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "La Contrase&ntilde;a del Buz&oacute;n de Voz no ha cambiado"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr ""
+"Contrase&ntilde;a y la confirmacion de esta no deben de estar en blanco"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "Contrase&ntilde;a ha de ser numerica y de longitud %d digitos"
+
+#: ../modules/settings.module:162
+#, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "Contrase&ntilde;a ha de ser numerica y de longitud %d digitos"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "Contrase&ntilde;a y conformacion no corresponden"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, php-format
+msgid "%s does not exist or is not writable"
+msgstr "%s No existe o no se puede escribir"
+
+#: ../modules/settings.module:223
+msgid "Voicemail email and pager address not changed"
+msgstr "La Contrase&ntilde;a del Buz&oacute;n de Voz no ha cambiado"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+msgid "Voicemail email settings not changed"
+msgstr "La Contrase&ntilde;a del Buz&oacute;n de Voz no ha cambiado"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "Idioma:"
+
+#: ../modules/settings.module:408
+msgid "Call Routing"
+msgstr "Enrutado de llamadas"
+
+#: ../modules/settings.module:411
+msgid "Call Forwarding:"
+msgstr "Desviar llamadas a:"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+msgid "Enable"
+msgstr "Activar"
+
+#: ../modules/settings.module:431
+#, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "Contrase&ntilde;a ha de ser numerica y de longitud %s digitos"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "Contrase&ntilde;a ha de ser numerica y de longitud %s digitos"
+
+#: ../modules/settings.module:439
+msgid "Voicemail Password:"
+msgstr "Contrase&ntilde;a del Buz&oacute;n de Voz"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "Introduzca otra vez para confirmar:"
+
+#: ../modules/settings.module:492
+msgid "Email Voicemail To:"
+msgstr "Buz&oacute;n de Voz para"
+
+#: ../modules/settings.module:498
+msgid "Pager Voicemail To:"
+msgstr "Buz&oacute;n de Voz para"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "Formato del Audio:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "Mejor Calidad"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "Descarga rapida"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "Propiedades del Buz&oacute;n de Voz"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Propiedades del Registro de Llamadas"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "Grabaciones Entrantes:"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "Siempre"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "Nunca"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "Bajo demanda"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "Grabaciones Salientes:"
+
+#: ../modules/settings.module:669
+#, php-format
+msgid "Settings for %s (%s)"
+msgstr "Ajustes para %s (%s)"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "Actualizar"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "Buz&oacute;n de Voz"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "Debe elegir primero una carpeta antes de mover el mensaje."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "Debe de seleccionar una extension antes de reenviar el mensaje"
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr "Mover a"
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr "Carpetas"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr "Enviar a"
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "Prioridad"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "Buz&oacute;n de Voz Orig"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr "Mensaje"
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "No se ha encontrado grabaciones en el Buz&oacute;n de Voz."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+msgid "Voicemail Login not found."
+msgstr ""
+"No se encontro el usuario del Buz&oacute;n de Voz, se usa el usuario de la "
+"extension"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "No tiene permiso para acceder al Buz&oacute;n de Voz"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "No hay grabaciones en el Buz&oacute;n de Voz de Admin"
+
+#: ../modules/voicemail.module:428
+#, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Buz&oacute;n de Voz de %s (%s)"
+
+#: ../modules/voicemail.module:678
+#, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "No puedo crear la carpeta %s en el buz&oacute;n de voz"
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "Permiso denegado en el directorio %s o %s"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "Descargar"
+
+#~ msgid "Settings for"
+#~ msgstr "Configuracion de"
+
+#~ msgid "Folders"
+#~ msgstr "Carpetas"
diff --git a/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..78d4733
--- /dev/null
+++ b/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..7f15c7a
--- /dev/null
+++ b/fs_selfservice/fri/locale/fr_FR/LC_MESSAGES/ari.po
@@ -0,0 +1,635 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <jbp@phileas-com.net>, 15/11/2005.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: 2006-04-29 11:30+0100\n"
+"Last-Translator: Xavier Ourcière <xourciere@propolys.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Asterisk Call Manager ne répond pas"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Authentification Asterisk échoue :"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+msgid "Asterisk command not understood"
+msgstr "Asterisk: commande non comprise"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Connexion impossible à Asterisk Manager"
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"Vérifiez l'installation d'AMP, de Asterisk, ou le fichier ARI main.conf"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "Déconexion"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Fichier introuvable"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "Rechercher"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Rechercher pour"
+
+#: ../includes/display.php:139
+#, php-format
+msgid "Results %d - %d of %d"
+msgstr "Résultats %d à %s sur %d"
+
+#: ../includes/display.php:141
+#, php-format
+msgid "Results %d"
+msgstr "Résultats %d"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Premier"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Dernier"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Mot de Passe eronné"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Login ou Mot de Passe erroné"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "Authentification"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "Mot de Passe"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "Valider"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "Se souvenir du mot de passe"
+
+#: ../includes/login.php:451
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "Utilisez votre <b>numéro de la boîte vocale et votre mot de passe</b>"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "C'est le même Mot de Passe que sur le téléphone"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr "Pour de l'assistance contactez votre administrateur de téléphonie."
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "NOUVEAUX"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "Famille"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "Amis"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "Anciens"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "Travail"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr "Annuaire local"
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr "Test d'echo"
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "Heure"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr "Météo"
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr "Programmation de réveil"
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr "test de festival (votre numéro de téléphone est le XXXX)"
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+#, fuzzy
+msgid "IVR Recording"
+msgstr "Enregistrement"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr "Active ne pas déranger"
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr "Désactive ne pas déranger"
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+#, fuzzy
+msgid "Message Center (does not ask for extension)"
+msgstr "Boite vocale personnelle"
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr "Centre de messageries"
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr "Simulation d'appel entrant"
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "Journal d'Appels"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr "Supprimer"
+
+#: ../modules/callmonitor.module:147
+msgid "duration"
+msgstr "Durée supérieure à"
+
+#: ../modules/callmonitor.module:150
+msgid "ignore"
+msgstr "Filtrer"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "Date"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "ID Appelant"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr ""
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr ""
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "Contexte"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "Durée"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "Enregistrement"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "Ecouter"
+
+#: ../modules/callmonitor.module:259
+#, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Journal d'Appels de %s (%s)"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "Sélection"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "Tous"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "Aucun"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr "Supprime seulement les fichiers des enregistrements mais pas les CDRs"
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "Boîte Vocale de %s (%s)"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr "Aide"
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr "Aide: %s (%s)"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Paramètres"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "Mot de passe de boite vocale non changé"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr "Le mot de passe et sa confirmation ne peuvent pas être vides"
+
+#: ../modules/settings.module:157
+#, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr ""
+"Le mot de passe doit comporter uniquement des chiffres et doit avoir une "
+"longueur supérieure à %d"
+
+#: ../modules/settings.module:162
+#, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr ""
+"Le mot de passe doit comporter uniquement des chiffres et doit avoir une "
+"longueur de %d"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr ""
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, php-format
+msgid "%s does not exist or is not writable"
+msgstr "%s n'existe pas ou n'a pas l'autorisation en écriture"
+
+#: ../modules/settings.module:223
+msgid "Voicemail email and pager address not changed"
+msgstr "Email voicemail et adresse de pager inchangés"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+msgid "Voicemail email settings not changed"
+msgstr "Paramètres de la boite vocale inchangés"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "Langue"
+
+#: ../modules/settings.module:408
+msgid "Call Routing"
+msgstr "Routage d'appels"
+
+#: ../modules/settings.module:411
+msgid "Call Forwarding:"
+msgstr "Transfert vers:"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+msgid "Enable"
+msgstr "Activer"
+
+#: ../modules/settings.module:431
+#, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr ""
+"Le mot de passe doit comporter uniquement des chiffres et seulement 4 "
+"chiffres"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr ""
+"Le mot de passe doit comporter uniquement des chiffres et seulement 4 "
+"chiffres"
+
+#: ../modules/settings.module:439
+msgid "Voicemail Password:"
+msgstr "Mot de passe de la boîte vocale"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "Repetez le mot de passe:"
+
+#: ../modules/settings.module:492
+msgid "Email Voicemail To:"
+msgstr "Adresse émail pour le Voicemail:"
+
+#: ../modules/settings.module:498
+msgid "Pager Voicemail To:"
+msgstr ""
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "Format audio:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "Meilleure Qualité"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "Taille réduite"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "Paramètres boîte vocale"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Enregistrements d'appels"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "Enregistrements ENTRANTS"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "Toujours"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "Jamais"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "Sur demande"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "Enregistrements SORTANTS"
+
+#: ../modules/settings.module:669
+#, php-format
+msgid "Settings for %s (%s)"
+msgstr "Paramètres de %s (%s)"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "Mettre à jour"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "Boîte Vocale"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "Sélection un dossier avant de déplacer le message."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "Sélectionnez d'abord une extension pour le transfert du message."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr "Déplacer vers"
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr "Dossier"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr "Transmettre à"
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "Priorité"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "Boîte Source"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "Enregistrement audio non trouvé"
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Enregistrement audio non trouvé"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "Aucun accès à la boîte vocale"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "Pas d'enregistrement pour Admin"
+
+#: ../modules/voicemail.module:428
+#, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Boîte Vocale de %s (%s)"
+
+#: ../modules/voicemail.module:678
+#, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "N'a pas pu créer le dossier %s"
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr ""
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr ""
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr ""
+#~ "Le mot de passe doit comporter que des chiffres et 4 chiffres maximum"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Asterisk Call Manager ne répond pas"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "pas un répertoire ou non lisible"
+
+#~ msgid "of"
+#~ msgstr "de"
+
+#~ msgid "Use your"
+#~ msgstr "Utilisez votre"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr ""
+#~ "Le mot de passe doit comporter que des chiffres et 4 chiffres maximum"
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr "Vérifiez le format audio à la page paramètres"
+
+#~ msgid "on the server"
+#~ msgstr "sur le serveur"
+
+#~ msgid "No database connection"
+#~ msgstr "Pas de connexion à la base de données"
+
+#~ msgid "Format Audio:"
+#~ msgstr "Format Audio :"
diff --git a/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..3b00bd1
--- /dev/null
+++ b/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..7c9ae97
--- /dev/null
+++ b/fs_selfservice/fri/locale/he_IL/LC_MESSAGES/ari.po
@@ -0,0 +1,646 @@
+# translation of ari-he.po to Hebrew
+# This file is distributed under the same license as the PACKAGE package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER.
+# Diego Iastrubni <diego.iastrubni@xorcom.com>, 2006.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: ari-he\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: 2006-02-05 11:48+0200\n"
+"Last-Translator: Diego Iastrubni <diego.iastrubni@xorcom.com>\n"
+"Language-Team: Hebrew <xorcom-users@xorcom.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.9.1\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "מנהל השיחות של Asterisk ×œ× ×ž×’×™×‘"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "×”×ימות מול Asterisk נכשל:"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "פקודת reload של Asterisk ×œ× ×ž×•×‘× ×ª"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "×ין ×פשרות להתחבר למנהל של Asterisk"
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"בדוק ×ת ההתקנה של AMP, בסיס ×”× ×ª×•× ×™× ×©×œ asterisk ×ו הקובץ main.conf של ARI"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "יצי××”"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "דף ×œ× × ×ž×¦×"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "חפש"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "חיפוש של"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "תוצ×ות"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "תוצ×ות"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "ר×שון"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "×חרון"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "ססמה ×œ× × ×›×•× ×”"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "×©× ×ž×©×ª×ž×© ×œ× × ×›×•×Ÿ ×ו ססמה ×œ× × ×›×•× ×”"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "×©× ×ž×©×ª×ž×©"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "ססמה"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "שלח"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "זכור ססמה"
+
+#: ../includes/login.php:451
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "תיבה קולית וססמה"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "זוהי ×ותה ססמה שבשימוש בטלפון שלך"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr "עבור ססמה התחזוקה, ×× × ×¤× ×” ×ל מנהל הטלפוניה שלך."
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "נכנסות"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "משפחה"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "חברי×"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "ישני×"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "עבודה"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "שעה"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+#, fuzzy
+msgid "IVR Recording"
+msgstr "הקלטות"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "צג שיחות"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+#, fuzzy
+msgid "delete"
+msgstr "בחר"
+
+#: ../modules/callmonitor.module:147
+#, fuzzy
+msgid "duration"
+msgstr "משך"
+
+#: ../modules/callmonitor.module:150
+#, fuzzy
+msgid "ignore"
+msgstr "כלו×"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "ת×ריך"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "שיחה מזוהה"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "מקור"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "יעד"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "הקשר"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "משך"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "ניטור"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "נגן"
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "צג שיחות"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "בחר"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "הכל"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "כלו×"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr "מחק הקלטות בלבד, ×œ× ×ת ×¨×™×©×•× ×”Ö¾cdr"
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "תיבה קולית"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, fuzzy, php-format
+msgid "Help for %s (%s)"
+msgstr "הגדרות עבור"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "הגדרות"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "ססמת התיבה הקולית ×œ× ×©×•× ×ª×”"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr "הסממה וה×ימות של הססמה ×œ× ×™×›×•×œ×™× ×œ×”×™×•×ª רקי×"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "הסממ×ות חייבת להכיל 4 ספרות בלבד"
+
+#: ../modules/settings.module:162
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "הסממ×ות חייבת להכיל 4 ספרות בלבד"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "הסממה וה×ימות של הססמה ×œ× ×ª×•×מי×"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "×œ× ×§×™×™× ×ו ×ין ×פשרות לכתוב עליו"
+
+#: ../modules/settings.module:223
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "ססמת התיבה הקולית ×œ× ×©×•× ×ª×”"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "ססמת התיבה הקולית ×œ× ×©×•× ×ª×”"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "שפה:"
+
+#: ../modules/settings.module:408
+#, fuzzy
+msgid "Call Routing"
+msgstr "הגדרות ניתור שיחות"
+
+#: ../modules/settings.module:411
+#, fuzzy
+msgid "Call Forwarding:"
+msgstr "הגדרות ניתור שיחות"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+#, fuzzy
+msgid "Enable"
+msgstr "בטבלה"
+
+#: ../modules/settings.module:431
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "הסממ×ות חייבת להכיל 4 ספרות בלבד"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "הסממ×ות חייבת להכיל 4 ספרות בלבד"
+
+#: ../modules/settings.module:439
+#, fuzzy
+msgid "Voicemail Password:"
+msgstr "ססמת תיבה קולית:"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "הכנס שוב ל×ימות:"
+
+#: ../modules/settings.module:492
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "תיבה קולית"
+
+#: ../modules/settings.module:498
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "תיבה קולית"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "תבנית שמע:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "×יכות ×”×›×™ טובה"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "הורדה הכי קטנה"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "הגדרות תיבה קולית"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "הגדרות ניתור שיחות"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "הקלטת שיחות נכנסות:"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "תמיד"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "××£ פע×"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "לפי דרישה"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "הקלטה שיחות יוצ×ות:"
+
+#: ../modules/settings.module:669
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "הגדרות עבור"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "עדכן"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "תיבה קולית"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "יש לבחור תיקייה לפני ש×פשר להעביר ×ת ההודעה."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "יש לבחור שלוחה לפני ש×פשר העביר ×ת השיחה הל××”."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr ""
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr "תיקייה"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr ""
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "עדיפות"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "תיבת דו×ר מקורית"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "הקלטת תיבה קולית ×œ× × ×ž×¦××”."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "×©× ×”×ž×©×ª×ž×© של תיבת הקול"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "×ין גישה לתיבת הקול"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "×ין הקלטות בתיבת הקול של המנהל"
+
+#: ../modules/voicemail.module:428
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "תיבה קולית"
+
+#: ../modules/voicemail.module:678
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "×ין ×פשרות ליצור ×ת תיקיית הדו×ר"
+
+#: ../modules/voicemail.module:718
+#, fuzzy, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "הגישה נדחתה בתיקייה"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "הורדה"
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "הסממ×ות חייבת להכיל 4 ספרות בלבד"
+
+#~ msgid "Folders"
+#~ msgstr "תיקיות"
+
+#~ msgid "Login used"
+#~ msgstr "×©× ×”×©×ž×©×ª×©"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "מנהל השיחות של Asterisk ×œ× ×ž×’×™×‘"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "×œ× ×¡×¤×¨×™×™×”, ×ו ×ין ×פשרות לקר×"
+
+#~ msgid "of"
+#~ msgstr "של "
+
+#~ msgid "Use your"
+#~ msgstr "השתמש בשלך"
+
+#~ msgid "for"
+#~ msgstr "עבור"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "הסממה חייבת להכיל 4 ספרות בלבד"
+
+#, fuzzy
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr "בחר ×ת תבנית השמע של התיבה הקולית בחלון ההגדרות "
+
+#~ msgid "on the server"
+#~ msgstr "ברשת"
+
+#~ msgid "No database connection"
+#~ msgstr "×ין חיבור לבסיס נתוני×"
diff --git a/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..ff5a922
--- /dev/null
+++ b/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..c9d9e44
--- /dev/null
+++ b/fs_selfservice/fri/locale/hu_HU/LC_MESSAGES/ari.po
@@ -0,0 +1,645 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Varasdy Imre <csvarasdy@softpbx.hu>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Asterisk Call Manager nem válaszol"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Asterisk bejelentkezés elutasítva:"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "Asterisk frissítés parancs ismeretlen"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Nem tudok csatlakozni az Asterisk Managerhez"
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"Ellen&otilde;rizze az AMP telepítést, asterisk adatbázist, vagy az ARI main."
+"conf filet"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "Kilépés"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Nincs ilyen oldal."
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "Keres"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Keresés"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Eredmény"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "Eredmény"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Els&otilde;"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Utolsó"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Hibás jelszó"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Rossz Felhasználonév vagy jelszó"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "Azonosító"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "Jelszó"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "Rögzít"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "Jelszó megjegyzése"
+
+#: ../includes/login.php:451
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "Hangposta és Jelszó"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "A jelszó ugyanaz, mint a telefonhoz"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Om du har problem med lösenord eller behöver hjälp ska du kontakta din vÃ"
+"¤xel ansvarig"
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "Bejövõ"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "Család"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "Barátok"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "Régi"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "Munka"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "Idõ"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+#, fuzzy
+msgid "IVR Recording"
+msgstr "Felvétel"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "Hangrögzítés"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr "Töröl"
+
+#: ../modules/callmonitor.module:147
+#, fuzzy
+msgid "duration"
+msgstr "Hossz"
+
+#: ../modules/callmonitor.module:150
+#, fuzzy
+msgid "ignore"
+msgstr "semmi"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "Dátum"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "Hivószám"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "Hívó"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "Hívott"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "Csoport"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "Hossz"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "Rögzítés"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "Lejátszás"
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Hangrögzítés"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "Választ"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "Mind"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "semmi"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr "Csak a hangfileokat törli, a CDR-t nem"
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "Hangposta"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, fuzzy, php-format
+msgid "Help for %s (%s)"
+msgstr "Beállítások"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Beállítások"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "A jelszót nem változtattam meg"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr "A jelszavakat nem hagyhatja üresen"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "A jelszó csak számból állhat és csak 4 karakteres lehet"
+
+#: ../modules/settings.module:162
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "A jelszó csak számból állhat és csak 4 karakteres lehet"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "A két jelszó nem egyezik"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "nem létezik vagy nem írható"
+
+#: ../modules/settings.module:223
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "A jelszót nem változtattam meg"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "A jelszót nem változtattam meg"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "Nyelv"
+
+#: ../modules/settings.module:408
+#, fuzzy
+msgid "Call Routing"
+msgstr "Hangrögzítés beállításai"
+
+#: ../modules/settings.module:411
+#, fuzzy
+msgid "Call Forwarding:"
+msgstr "Hangrögzítés beállításai"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+#, fuzzy
+msgid "Enable"
+msgstr "táblában"
+
+#: ../modules/settings.module:431
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "A jelszó csak számból állhat és csak 4 karakteres lehet"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "A jelszó csak számból állhat és csak 4 karakteres lehet"
+
+#: ../modules/settings.module:439
+#, fuzzy
+msgid "Voicemail Password:"
+msgstr "Hangposta jelszó:"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "Irja be újra:"
+
+#: ../modules/settings.module:492
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "Hangposta"
+
+#: ../modules/settings.module:498
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "Hangposta"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "Hangformátum:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "Legjobb minõség"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "Legkisebb méret"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "Hangposta beállítások"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Hangrögzítés beállításai"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "Hangrögzítés - Bejövõ:"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "Mindíg"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "Soha"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "Igény esetén"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "Hangrögzítés - Bejövõ:"
+
+#: ../modules/settings.module:669
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Beállítások"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "Frissít"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "Hangposta"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "Ãthelyezés elõtt ki kell jelölni egy mappát."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "Üzenet áthelyezése elõtt ki kell jelölni egy melléket."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr "Ãthelyez"
+
+#: ../modules/voicemail.module:307
+#, fuzzy
+msgid "Folder"
+msgstr "Mappa"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr "Ãtirányít"
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "Prioritás"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "Eredeti Postafiók"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "Nem találok rögzítés(eke)t."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Hangposta Azonosító nem található"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "Nincs hozzáférés a hangpostához"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "Nincs hangfelvétel az Admin részére"
+
+#: ../modules/voicemail.module:428
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Hangposta"
+
+#: ../modules/voicemail.module:678
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "Nem tudom létrehozni a hangposta mappát"
+
+#: ../modules/voicemail.module:718
+#, fuzzy, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "Hozzáférés elutasítva"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "letöltés"
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "A jelszó csak számból állhat és csak 4 karakteres lehet"
+
+#~ msgid "Folders"
+#~ msgstr "Mappák"
+
+#~ msgid "Login used"
+#~ msgstr "Azonosító használt"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Asterisk Call Manager nem válaszol"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "nem k&ouml;nyvtár vagy nem olvasható"
+
+#~ msgid "of"
+#~ msgstr "av"
+
+#~ msgid "Use your"
+#~ msgstr "Használja "
+
+#~ msgid "for"
+#~ msgstr " - "
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "A jelszó csak számból állhat és 4 karakteres lehet"
+
+#~ msgid "on the server"
+#~ msgstr "a serveren"
+
+#~ msgid "No database connection"
+#~ msgstr "Nincs adatbázis kapcsolat"
diff --git a/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..d5a7da8
--- /dev/null
+++ b/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..db245f9
--- /dev/null
+++ b/fs_selfservice/fri/locale/it_IT/LC_MESSAGES/ari.po
@@ -0,0 +1,999 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-08-24 12:33+0200\n"
+"PO-Revision-Date: 2007-08-25 22:41-0600\n"
+"Last-Translator: Francesco Romano\n"
+"Language-Team: Italian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Il Call Manager di Asterisk non risponde"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Autenticazione Asterisk fallita:"
+
+#
+#: ../includes/asi.php:96 ../includes/asi.php:111 ../includes/asi.php:130
+#: ../includes/asi.php:144
+msgid "Asterisk command not understood"
+msgstr "comando reload di Asterisk non eseguito"
+
+#
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr "Troppe directory in %s Non tutti i files sono stati processati"
+
+#: ../includes/bootstrap.php:226
+#, fuzzy
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr "ARI richiede PHP 4.0 o superiore"
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+"PHP PEAR deve essere installato. Visitare http://pear.php.net per aiuto "
+"nell'installazione."
+
+#
+#: ../includes/common.php:180
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Impossibile connettersi all'Asterisk Manager"
+
+#: ../includes/common.php:181
+##, fuzzy
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+"Controllare il file di configurazione main.conf di ARI per l'impostazione "
+"sull'account dell'Asterisk Manager."
+
+#: ../includes/common.php:182
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+"Controllare /etc/asterisk/manager.conf per un valido account Asterisk Manager"
+
+#: ../includes/common.php:183
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+"assicurarsi che in [general] sia presente enable = yes e la riga 'permit=' "
+"con l'indirizzo localhost o il webserver."
+
+#
+#: ../includes/common.php:200 ../includes/common.php:215
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"Controllare l'installazione di AMP, il database di asterisk o il file main."
+"conf di ARI"
+
+#: ../includes/common.php:351
+msgid "Logout"
+msgstr "Esci"
+
+#: ../includes/common.php:356
+msgid "Page Not Found."
+msgstr "Pagina Non Trovata"
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "Cerca"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Ricerca per"
+
+#
+#: ../includes/display.php:139
+##, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Risultati %d - %d di %d"
+
+#
+#: ../includes/display.php:141
+#, php-format
+msgid "Results %d"
+msgstr "Risultati %d"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Prima"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Ultima"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Password sbagliata"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Nome Utente o Password sbagliati"
+
+#: ../includes/login.php:404 ../includes/login.php:413
+msgid "Login"
+msgstr "Login"
+
+#: ../includes/login.php:421
+msgid "Password"
+msgstr "Password"
+
+#: ../includes/login.php:430
+msgid "Submit"
+msgstr "Invia"
+
+#: ../includes/login.php:438
+msgid "Remember Password"
+msgstr "Ricorda Password"
+
+#
+#: ../includes/login.php:453
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr ""
+"Utilizzare come login il numero della <b>Casella Vocale e relativa "
+"Password</b>"
+
+#: ../includes/login.php:454
+msgid "This is the same password used for the phone"
+msgstr "Sono gli stessi utilizzati dal proprio telefono"
+
+#: ../includes/login.php:456
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Per assistenza o manutenzione, contattare l'amministratore del Centralino."
+
+#: ../includes/main.conf.php:150
+msgid "INBOX"
+msgstr "NUOVI"
+
+#: ../includes/main.conf.php:152
+msgid "Family"
+msgstr "Personali"
+
+#: ../includes/main.conf.php:154
+msgid "Friends"
+msgstr "Amici"
+
+#: ../includes/main.conf.php:156
+msgid "Old"
+msgstr "Vecchi"
+
+#: ../includes/main.conf.php:158
+msgid "Work"
+msgstr "Lavoro"
+
+#: ../includes/main.conf.php:237
+msgid "Call Forward All Activate"
+msgstr "Attivazione Trasferimento di Chiamata Incondizionato"
+
+#: ../includes/main.conf.php:238
+msgid "Call Forward All Deactivate"
+msgstr "Disattivazione Trasferimento di Chiamata Incondizionato"
+
+#: ../includes/main.conf.php:239
+msgid "Call Forward All Prompting Deactivate"
+msgstr "Disattivazione Trasferimento di Chiamata Incondizionato (chiede dettagli)"
+
+#: ../includes/main.conf.php:240
+msgid "Call Forward Busy Activate"
+msgstr "Attivazione Trasferimento di Chiamata su Occupato"
+
+#: ../includes/main.conf.php:241
+msgid "Call Forward Busy Deactivate"
+msgstr "Disattivazione Trasferimento di Chiamata su Occupato"
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward Busy Prompting Deactivate"
+msgstr "Disattivazione Trasferimento di Chiamata su Occupato (chiede dettagli)"
+
+#: ../includes/main.conf.php:243
+msgid "Call Forward No Answer/Unavailable Activate"
+msgstr "Attivazione Trasferimento di Chiamata su nessuna risposta"
+
+#: ../includes/main.conf.php:244
+msgid "Call Forward No Answer/Unavailable Deactivate"
+msgstr "Disattivazione Trasferimento di Chiamata su nessuna risposta"
+
+#: ../includes/main.conf.php:245
+msgid "Call Waiting - Activate"
+msgstr "Attivazione Avviso di chiamata"
+
+#: ../includes/main.conf.php:247
+msgid "Do-Not-Disturb Activate"
+msgstr "Attivazione Non-Disturbare"
+
+#: ../includes/main.conf.php:248
+msgid "Do-Not-Disturb Deactivate"
+msgstr "Disattivazione Non-Disturbare"
+
+#: ../includes/main.conf.php:249
+msgid "My Voicemail"
+msgstr "Propria Casella Vocale"
+
+#: ../includes/main.conf.php:250
+msgid "Dial Voicemail"
+msgstr "Casella Vocale"
+
+#: ../includes/main.conf.php:303
+msgid "Email voicemail as attachment"
+msgstr "Invia messaggio vocale come allegato email"
+
+#: ../includes/main.conf.php:304
+msgid "Say caller id in recording emailed"
+msgstr "Riproduci ID chiamante nella registrazione inviata"
+
+#: ../includes/main.conf.php:305
+msgid "Say envelop (date/time) in recording emailed"
+msgstr "Riproduci data/ora nella registrazione inviata"
+
+#: ../includes/main.conf.php:306
+msgid "Delete voicemail when emailed"
+msgstr "Elimina messaggio vocale dopo aver spedito l'email"
+
+#: ../includes/main.conf.php:307
+msgid "Play next message after deleting current message"
+msgstr ""
+"Riproduci il messaggio seguente dopo aver eliminato il messaggio corrente"
+
+#: ../includes/main.conf.php:308
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:309
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/VmX.module:58
+msgid "VmX&#8482 Locator"
+msgstr "VmX&#8482 Locator"
+
+#: ../modules/VmX.module:115
+msgid ""
+"Your Premium VmX Locator service has been disabled, REFRESH your browser to "
+"remove this message"
+msgstr ""
+"Il proprio VmX Locator è stato disabilitato, AGGIORNARE la pagina per "
+"rimuovere questo messaggio"
+
+#: ../modules/VmX.module:116 ../modules/followme.module:101
+#, php-format
+msgid ""
+"Check with your Telephone System Administrator if you think there is a "
+"problem"
+msgstr ""
+"Contattare l'amministratore del Sistema Telefonico se ci sono dei problemi"
+
+#: ../modules/VmX.module:147
+msgid "Option 0 not changed"
+msgstr "Opzione 0 non cambiata"
+
+#
+#: ../modules/VmX.module:148 ../modules/VmX.module:181
+#: ../modules/VmX.module:201 ../modules/phonefeatures.module:302
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+"Il numero %s deve contenere cifre valide (vanno benne caratteri come '(', "
+"'-' e ')')"
+
+#: ../modules/VmX.module:180
+msgid "Option 1 not changed"
+msgstr "Opzione 1 non cambiata"
+
+#: ../modules/VmX.module:200
+msgid "Option 2 not changed"
+msgstr "opzione 2 non cambiata"
+
+#: ../modules/VmX.module:300
+msgid "Use When:"
+msgstr "Utilizzare quando:"
+
+#: ../modules/VmX.module:300
+msgid ""
+"Menu options below are available during your personal voicemail greeting "
+"playback. <br/><br/>Check both to use at all times."
+msgstr ""
+"Le opzioni del menu disponibili qui sotto sono proposte durante il messaggio "
+"di benvenuto della casella vocale. <br/><br/>"
+
+#: ../modules/VmX.module:302
+msgid "unavailable"
+msgstr "non disponibile"
+
+#: ../modules/VmX.module:306
+msgid "busy"
+msgstr "occupato"
+
+#: ../modules/VmX.module:310
+msgid "Voicemail Instructions:"
+msgstr "Istruzioni Casella Vocale:"
+
+#: ../modules/VmX.module:310
+msgid "Uncheck to play a beep after your personal voicemail greeting."
+msgstr "Deselezionare per riprodurre un tono dopo il messaggio di benvenuto."
+
+#: ../modules/VmX.module:313
+msgid "Standard voicemail prompts."
+msgstr "Messaggi standard Casella Vocale"
+
+#: ../modules/VmX.module:321
+msgid "Press 0:"
+msgstr "Premere 0:"
+
+#: ../modules/VmX.module:321
+msgid ""
+"Pressing 0 during your personal voicemail greeing goes to the Operator. \n"
+"\t\t\t\t\tUncheck to enter another destination here."
+msgstr ""
+"Premendo 0 durante il messaggio di benvenuto della Casella Vocale, la "
+"chiamata sarà reindirizzata all'operatore. \n"
+"\t\t\t\t\tDeselezionare per inserire un'altra destinazione."
+
+#: ../modules/VmX.module:329
+msgid "Go To Operator"
+msgstr "Per andare all'Operatore"
+
+#: ../modules/VmX.module:333
+msgid "Press 1:"
+msgstr "Premere 1:"
+
+#: ../modules/VmX.module:336
+msgid ""
+"The remaining options can have internal extensions, ringgroups, queues and "
+"external numbers that may be rung. It is often used to include your cell "
+"phone. You should run a test to make sure that the number is functional any "
+"time a change is made so you don't leave a caller stranded or receiving "
+"invalid number messages."
+msgstr ""
+
+#: ../modules/VmX.module:338
+msgid ""
+"Enter an alternate number here, then change your personal voicemail greeting "
+"to let callers know to press 1 to reach that number. <br/><br/>If you'd like "
+"to use your Follow Me List, check \"Send to Follow Me\" and disable Follow "
+"Me above."
+msgstr ""
+"Immettere una destinazione alternativa, dopo, cambiare il messaggio di "
+"benvenuto per permettere ai chiamanti di premere 1 per raggiungere quella "
+"numerazione. <br/><br/> Se si vuole utilizzare la Lista Seguimi, selezionare "
+"\"Invia al Seguimi\" e disattivare sopra il Seguimi."
+
+#: ../modules/VmX.module:351
+msgid "Send to Follow-Me"
+msgstr "Invia al Seguimi"
+
+#: ../modules/VmX.module:359
+msgid "Press 2:"
+msgstr "Premere 2:"
+
+#: ../modules/VmX.module:359
+msgid ""
+"Use any extensions, ringgroups, queues or external numbers. <br/><br/"
+">Remember to re-record your personal voicemail greeting and include "
+"instructions. Run a test to make sure that the number is functional."
+msgstr ""
+"Utilizzare qualsiasi interno, gruppo di chiamata, coda o numero esterno. <br/"
+"><br/>Ricordarsi di ri-registrare il proprio messaggio di benvenuto e "
+"includere delle istruzioni. Fare dei test per assicurarsi che tutto funzioni."
+
+#
+#: ../modules/VmX.module:373
+##, fuzzy, php-format
+msgid "VmX Locator&#8482; Settings for %s (%s)"
+msgstr "Impostazioni di %s (%s)"
+
+#: ../modules/VmX.module:415 ../modules/followme.module:384
+#: ../modules/phonefeatures.module:180 ../modules/settings.module:625
+msgid "Update"
+msgstr "Aggiorna"
+
+#: ../modules/callmonitor.module:36 ../modules/callmonitor.module:256
+msgid "Call Monitor"
+msgstr "Registrazioni Chiamate"
+
+#
+#: ../modules/callmonitor.module:131
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr "Il percorso non è una directory: %s"
+
+#: ../modules/callmonitor.module:140 ../modules/voicemail.module:318
+msgid "delete"
+msgstr "elimina"
+
+#
+#: ../modules/callmonitor.module:146
+msgid "duration"
+msgstr "durata"
+
+#
+#: ../modules/callmonitor.module:149
+msgid "ignore"
+msgstr "niente"
+
+#: ../modules/callmonitor.module:158 ../modules/voicemail.module:339
+msgid "Date"
+msgstr "Data"
+
+#: ../modules/callmonitor.module:160 ../modules/voicemail.module:341
+msgid "Time"
+msgstr "Ora"
+
+#: ../modules/callmonitor.module:162 ../modules/voicemail.module:343
+msgid "Caller ID"
+msgstr "ID Chiamante"
+
+#: ../modules/callmonitor.module:164
+msgid "Source"
+msgstr "Sorgente"
+
+#: ../modules/callmonitor.module:166
+msgid "Destination"
+msgstr "Destinazione"
+
+#: ../modules/callmonitor.module:168
+msgid "Context"
+msgstr "Contesto"
+
+#: ../modules/callmonitor.module:170 ../modules/voicemail.module:349
+msgid "Duration"
+msgstr "Durata"
+
+#: ../modules/callmonitor.module:201
+msgid "Monitor"
+msgstr "Registrazione"
+
+#: ../modules/callmonitor.module:221 ../modules/voicemail.module:390
+msgid "play"
+msgstr "riproduci"
+
+#
+#: ../modules/callmonitor.module:258
+#, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Registrazioni Chiamate di %s (%s)"
+
+#: ../modules/callmonitor.module:310 ../modules/voicemail.module:492
+msgid "select"
+msgstr "seleziona"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:493
+msgid "all"
+msgstr "tutto"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:494
+msgid "none"
+msgstr "niente"
+
+#: ../modules/callmonitor.module:532
+msgid "Only deletes recording files, not cdr log"
+msgstr "Eliminati solo i file di registrazione, non i log delle chiamate"
+
+#: ../modules/featurecodes.module:36 ../modules/featurecodes.module:63
+##, fuzzy
+msgid "Feature Codes"
+msgstr "Codici Servizi"
+
+#
+#: ../modules/featurecodes.module:65
+##, fuzzy, php-format
+msgid " for %s (%s)"
+msgstr " per %s (%s)"
+
+#: ../modules/featurecodes.module:72
+msgid "Handset Feature Code"
+msgstr "Codice"
+
+#: ../modules/featurecodes.module:75
+msgid "Action"
+msgstr "Azione"
+
+#: ../modules/followme.module:43
+msgid "Follow Me"
+msgstr "Seguimi"
+
+#: ../modules/followme.module:100
+msgid ""
+"Your Follow-Me has been disabled, REFRESH your browser to remove this message"
+msgstr ""
+"Il Seguimi è disattivato, AGGIORNA la pagina per rimuovere questo messaggio"
+
+#: ../modules/followme.module:118
+msgid "Follow-Me pre-ring time not changed"
+msgstr "Tempo di pre-squillo per il Seguimi non cambiato"
+
+#: ../modules/followme.module:119 ../modules/followme.module:142
+#, php-format
+msgid "Number %s must be an interger number of seconds"
+msgstr "Il numero %s deve contenere numeri interi"
+
+#: ../modules/followme.module:141
+msgid "Follow-Me list ring time not changed"
+msgstr "Tempo di squillo Lista Seguimi non cambiato"
+
+#: ../modules/followme.module:185
+msgid "Follow-Me list must contain at least one valid number"
+msgstr "Il Seguimi deve contenere almeno un numero valido"
+
+#: ../modules/followme.module:186
+#, php-format
+msgid "The following: %s is not valid"
+msgstr "Il seguente: %s non è valido"
+
+#
+#: ../modules/followme.module:291 ../modules/followme.module:344
+#: ../modules/phonefeatures.module:335 ../modules/settings.module:420
+msgid "Enable"
+msgstr "Attiva"
+
+#: ../modules/followme.module:292
+msgid ""
+"Dial-by-name Directory, IVR, and internal \n"
+"\t\t\t\t\t\t\t\t\t\t\t\t\tcalls will ring the numbers in the FollowMe \n"
+"\t\t\t\t\t\t\t\t\t\t\t\t\tList. Any FreePBX routes that directly \n"
+"\t\t\t\t\t\t\t\t\t\t\t\t\treference a FollowMe are unaffected by this \n"
+"\t\t\t\t\t\t\t\t\t\t\t\t\tenable/disable setting."
+msgstr "L'Elenco Telefonico, l'IVR e le chiamate interne chiameranno i numeri definiti nella Lista Seguimi. Qualsiasi rotta che ha come referenza un Seguimi non sarà affetto dall'attivazione o dalla disattivazione di questa impostazione."
+
+#: ../modules/followme.module:304
+msgid "Follow Me List:"
+msgstr "Lista Seguimi:"
+
+#: ../modules/followme.module:305
+#, php-format
+msgid "Extensions and outside numbers to ring next."
+msgstr "Interni e numeri esterni da chiamare dopo."
+
+#: ../modules/followme.module:306
+#, php-format
+msgid "Include %s to keep it ringing."
+msgstr "Immettere %s per lasciar squillare."
+
+#: ../modules/followme.module:312
+#, php-format
+msgid "Ring %s First For:"
+msgstr "Chiama prima %s per:"
+
+#: ../modules/followme.module:313
+#, php-format
+msgid "Time to ring extension %s before ringing the %s Follow Me List %s"
+msgstr ""
+"Il tempo di chiamata per l'interno %s prima di far squillare la %s Lista "
+"Seguimi %s"
+
+#: ../modules/followme.module:323 ../modules/followme.module:336
+msgid "seconds"
+msgstr "secondi"
+
+#: ../modules/followme.module:326
+msgid "Ring Followme List for:"
+msgstr "Chiama la Lista Seguimi per:"
+
+#: ../modules/followme.module:326
+msgid "Time to ring the Follow Me List."
+msgstr "Il tempo di chiamata per la Lista Seguimi."
+
+#: ../modules/followme.module:341
+msgid "Use Confirmation:"
+msgstr "Utilizza Conferma:"
+
+#: ../modules/followme.module:341
+msgid ""
+"Outside lines that are part of the Follow Me List will be called and offered "
+"a menu:<br/><br/> \"You have an incoming call. Press 1 to accept or 2 to "
+"decline.\"<br/><br/> This keeps calls from ending up in external voicemail. "
+"Make sure that the List Ring Time is long enough to allow for you to hear "
+"and react to this message."
+msgstr ""
+"Ai Numeri esterni che fanno parte della Lista Seguimi sarà proposto un menu:"
+"<br/><br/> \"Hai una chiamata in arrivo. Premere 1 per accettare o 2 per "
+"rifiutare.\" Questo evita alle chiamate esterne di finire in una segreteria. "
+"Assicurarsi che il tempo di chiamata sia abbastanza lungo per rispondere a "
+"questo messaggio."
+
+#: ../modules/followme.module:356
+##, fuzzy
+msgid "Followme Settings"
+msgstr "Impostazioni Seguimi"
+
+#
+#: ../modules/followme.module:358
+##, fuzzy, php-format
+msgid "Followme Settings for %s (%s)"
+msgstr "Impostazioni Seguimi per %s (%s)"
+
+#: ../modules/phonefeatures.module:25 ../modules/phonefeatures.module:96
+#: ../modules/phonefeatures.module:163
+msgid "Phone Features"
+msgstr "Servizi Telefonici"
+
+#
+#: ../modules/phonefeatures.module:149
+##, fuzzy
+msgid "Call Forwarding"
+msgstr "Trasferimento di Chiamata"
+
+#
+#: ../modules/phonefeatures.module:165
+##, fuzzy, php-format
+msgid "Features for %s (%s)"
+msgstr "Impostazioni per %s (%s)"
+
+#: ../modules/phonefeatures.module:301
+msgid "Call forward number not changed"
+msgstr "Numero per il trasferimento di chiamata non cambiato"
+
+#: ../modules/settings.module:56
+msgid "Settings"
+msgstr "Impostazioni"
+
+#: ../modules/settings.module:118 ../modules/settings.module:123
+#: ../modules/settings.module:128 ../modules/settings.module:133
+#: ../modules/settings.module:143 ../modules/settings.module:148
+msgid "Voicemail password not changed"
+msgstr "Password Casella Vocale non cambiata"
+
+#: ../modules/settings.module:119
+msgid "Password and password confirm must not be blank"
+msgstr "Password e conferma password non possono essere vuoti"
+
+#
+#: ../modules/settings.module:124
+##, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "La Password deve essere minimo di %d numeri"
+
+#
+#: ../modules/settings.module:129
+##, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "La Password deve essere di %d numeri"
+
+#: ../modules/settings.module:134
+msgid "Password and password confirm do not match"
+msgstr "Password e Conferma password non corrispondono"
+
+#
+#: ../modules/settings.module:144 ../modules/settings.module:149
+#: ../modules/settings.module:200 ../modules/settings.module:205
+##, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "%s non esiste o non è scrivile"
+
+#
+#: ../modules/settings.module:189
+msgid "Voicemail email and pager address not changed"
+msgstr "Password Casella Vocale non cambiata"
+
+#
+#: ../modules/settings.module:199 ../modules/settings.module:204
+msgid "Voicemail email settings not changed"
+msgstr "Password Casella Vocale non cambiata"
+
+#: ../modules/settings.module:347
+msgid "Language:"
+msgstr "Lingua:"
+
+#
+#: ../modules/settings.module:357
+#, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "La Password deve essere di solo numeri e %s cifre"
+
+#
+#: ../modules/settings.module:360
+##, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "La Password deve essere di solo numeri e minimo %s cifre"
+
+#
+#: ../modules/settings.module:365
+msgid "Voicemail Password:"
+msgstr "Password Casella Vocale:"
+
+#: ../modules/settings.module:371
+msgid "Enter again to confirm:"
+msgstr "Conferma password:"
+
+#
+#: ../modules/settings.module:419
+msgid "Email Notification"
+msgstr "Notifica Email"
+
+#
+#: ../modules/settings.module:423
+msgid "Email Voicemail To:"
+msgstr "Notifica Email a:"
+
+#
+#: ../modules/settings.module:429
+msgid "Pager Email Notification To:"
+msgstr "Invia Notifica Email al Pager:"
+
+#: ../modules/settings.module:485
+msgid "Audio Format:"
+msgstr "Formato Audio:"
+
+#: ../modules/settings.module:488
+msgid "Best Quality"
+msgstr "Migliore Qualità"
+
+#: ../modules/settings.module:489
+msgid "Smallest Download"
+msgstr "Download Veloci"
+
+#: ../modules/settings.module:497
+msgid "Voicemail Settings"
+msgstr "Impostazioni Casella Vocale"
+
+#: ../modules/settings.module:538
+msgid "Call Monitor Settings"
+msgstr "Impostazioni Registrazioni Chiamate"
+
+#: ../modules/settings.module:541
+msgid "Record INCOMING:"
+msgstr "Registra ENTRANTI:"
+
+#: ../modules/settings.module:543 ../modules/settings.module:551
+msgid "Always"
+msgstr "Sempre"
+
+#: ../modules/settings.module:544 ../modules/settings.module:552
+msgid "Never"
+msgstr "Mai"
+
+#: ../modules/settings.module:545 ../modules/settings.module:553
+msgid "On-Demand"
+msgstr "Su richiesta"
+
+#: ../modules/settings.module:549
+msgid "Record OUTGOING:"
+msgstr "Registra USCENTI:"
+
+#
+#: ../modules/settings.module:592
+##, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Impostazioni per %s (%s)"
+
+#: ../modules/voicemail.module:44
+msgid "Voicemail"
+msgstr "Casella Vocale"
+
+#: ../modules/voicemail.module:163
+msgid "A folder must be selected before the message can be moved."
+msgstr ""
+"Prima di spostare un messaggio, selezionare una cartella di destinazione"
+
+#: ../modules/voicemail.module:177
+msgid "An extension must be selected before the message can be forwarded."
+msgstr "Prima di inoltrare un messaggio, selezionare l'interno di destinazione"
+
+#: ../modules/voicemail.module:321
+msgid "move_to"
+msgstr "sposta_verso"
+
+#: ../modules/voicemail.module:324
+msgid "Folder"
+msgstr "Cartella"
+
+#: ../modules/voicemail.module:328
+msgid "forward_to"
+msgstr "inoltra_a"
+
+#: ../modules/voicemail.module:345
+msgid "Priority"
+msgstr "Priorità"
+
+#: ../modules/voicemail.module:347
+msgid "Orig Mailbox"
+msgstr "Casella Orig"
+
+#: ../modules/voicemail.module:379
+msgid "Message"
+msgstr "Messaggio"
+
+#: ../modules/voicemail.module:394
+msgid "Voicemail recording(s) was not found."
+msgstr "Registrazioni Casella Vocale non trovate."
+
+#
+#: ../modules/voicemail.module:395
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+"Nella pagina delle impostazioni, cambiare il formato dei messaggi vocali. "
+"Adesso è impostato su %s"
+
+#
+#: ../modules/voicemail.module:422
+msgid "Voicemail Login not found."
+msgstr "Login Casella Vocale non trovato"
+
+#: ../modules/voicemail.module:423
+msgid "No access to voicemail"
+msgstr "Accesso alla Casella Vocale disabilitato"
+
+#: ../modules/voicemail.module:429
+msgid "No Voicemail Recordings for Admin"
+msgstr "Nessuna Casella Vocale per Admin"
+
+#
+#: ../modules/voicemail.module:445
+#, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Casella Vocale di %s (%s)"
+
+#
+#: ../modules/voicemail.module:695
+##, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "Non posso creare la cartella %s sul server"
+
+#
+#: ../modules/voicemail.module:735
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "Permessi negati nella cartella %s o %s"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "scarica"
+
+msgid "Unconditional:"
+msgstr "Incondizionato:"
+
+msgid "Unavailable:"
+msgstr "Non disponibile:"
+
+msgid "Busy:"
+msgstr "Occupato:"
+
+#
+##, fuzzy
+msgid "Call Waiting"
+msgstr "Avviso di Chiamata"
+
+##, fuzzy
+msgid "Do Not Disturb"
+msgstr " Non-Disturbare"
+
+#
+##, fuzzy
+msgid "Passwords must be all numbers and at least 3 digits"
+msgstr "La Password deve essere di solo numeri e minimo di 3 cifre"
+
+#~ msgid "Directory"
+#~ msgstr "Directory"
+
+#~ msgid "Echo Test"
+#~ msgstr "Test Echo"
+
+#~ msgid "Weather"
+#~ msgstr "Meteo"
+
+#~ msgid "Schedule wakeup call"
+#~ msgstr "Sveglia"
+
+#~ msgid "festival test (your extension is XXX)"
+#~ msgstr "Test Festival (il tuo interno è XXX)"
+
+#~ msgid "Deactivate Call Waiting"
+#~ msgstr "Disattiva Avviso di Chiamata"
+
+#~ msgid "Disable Call Forwarding"
+#~ msgstr "Disattiva Inoltro di Chiamata"
+
+#
+#~ msgid "IVR Recording"
+#~ msgstr "Registrazione IVR"
+
+#~ msgid "Disable Do-Not-Disturb"
+#~ msgstr "Disattiva Non-Disturbare"
+
+#~ msgid "Disable Call Forward on Busy"
+#~ msgstr "Disattiva Inoltro di Chiamata su Occupato"
+
+##, fuzzy
+#~ msgid "Message Center (does not ask for extension)"
+#~ msgstr "Centro Messaggi (non chiede l'interno)"
+
+#~ msgid "Enter Message Center"
+#~ msgstr "Centro Messaggi"
+
+#~ msgid "Playback IVR Recording"
+#~ msgstr "Riproduce Registrazione IVR"
+
+#~ msgid "Test Fax"
+#~ msgstr "Test Fax"
+
+#~ msgid "Simulate incoming call"
+#~ msgstr "Simula chiamata entrante"
+
+#
+##, fuzzy
+#~ msgid "Conference for %s (%s%s)"
+#~ msgstr "Conferenza per %s (%s%s)"
+
+#~ msgid "Help"
+#~ msgstr "Aiuto"
+
+#
+#~ msgid "Pager Voicemail To:"
+#~ msgstr "Casella Vocale"
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "La Password deve essere di solo numeri e 4 cifre"
+
+msgid "Folders"
+msgstr "Cartelle"
+
+#~ msgid "Login used"
+#~ msgstr "Login utilizzato"
+
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Impossibile connettersi all'Asterisk Manager Interface"
+
+#~ msgid "Cannot connect to the"
+#~ msgstr "Impossibile connettersi al"
+
+#~ msgid "database"
+#~ msgstr "database"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "non è una directory o non è leggibile"
+
+#~ msgid "of"
+#~ msgstr "di"
+
+#~ msgid "Use your"
+#~ msgstr "Utilizzare il "
+
+#~ msgid "for"
+#~ msgstr "di"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "La Password deve essere di 4 numeri"
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr "Controllare il formato audio nella pagina delle impostazioni"
+
+#~ msgid "on the server"
+#~ msgstr "nel server"
+
+#~ msgid "No database connection"
+#~ msgstr "Connessione al database fallita"
+
+msgid "Email a notification, including audio file if indicated below. "
+msgstr "Invia una notifica per posta elettronica, incluso il file audio se impostato sotto."
+
+msgid "Email a short notification "
+msgstr "Invia una breve notifica"
+
+msgid "Phone Features for %s (%s)"
+msgstr "Servizi Telefonici per %s (%s)"
+
+msgid "User Portal"
+msgstr "Portale Utente" \ No newline at end of file
diff --git a/fs_selfservice/fri/locale/locale.txt b/fs_selfservice/fri/locale/locale.txt
new file mode 100644
index 0000000..6b93e2e
--- /dev/null
+++ b/fs_selfservice/fri/locale/locale.txt
@@ -0,0 +1,37 @@
+// To create the .po (write your translations to this file):
+$ find *.php ../includes/* ../modules/*.module ../misc/*.php ../theme/* | xargs xgettext -L PHP -o ari.po --keyword=_ -
+
+// To create the utf-8 .po
+$ iconv -f iso-8859-1 -t utf-8 -o ari.utf-8.po ari.po
+
+// To create the .mo:
+$ msgfmt -v ari.utf-8.po -o ari.mo
+
+// To update (assume both files to be merged are utf-8)
+$ msgmerge es_ES/LC_MESSAGES/ari.po ari.utf-8.po --output-file=es_ES/LC_MESSAGES/ari.po
+$ msgfmt -v es_ES/LC_MESSAGES/ari.po -o es_ES/LC_MESSAGES/ari.mo
+
+
+// script
+// for this to work all translated files need to be converted to utf-8 (use iconv)
+//
+find ../*.php ../includes/* ../modules/*.module ../misc/*.php ../theme/*.css | xargs xgettext -L PHP -o ari.po --keyword=_ -
+iconv -f iso-8859-1 -t utf-8 -o ari.utf-8.po ari.po
+msgmerge el_GR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=el_GR/LC_MESSAGES/ari.po
+msgfmt -v el_GR/LC_MESSAGES/ari.po -o el_GR/LC_MESSAGES/ari.mo
+msgmerge es_ES/LC_MESSAGES/ari.po ari.utf-8.po --output-file=es_ES/LC_MESSAGES/ari.po
+msgfmt -v es_ES/LC_MESSAGES/ari.po -o es_ES/LC_MESSAGES/ari.mo
+msgmerge fr_FR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=fr_FR/LC_MESSAGES/ari.po
+msgfmt -v fr_FR/LC_MESSAGES/ari.po -o fr_FR/LC_MESSAGES/ari.mo
+msgmerge he_IL/LC_MESSAGES/ari.po ari.utf-8.po --output-file=he_IL/LC_MESSAGES/ari.po
+msgfmt -v he_IL/LC_MESSAGES/ari.po -o he_IL/LC_MESSAGES/ari.mo
+msgmerge hu_HU/LC_MESSAGES/ari.po ari.utf-8.po --output-file=hu_HU/LC_MESSAGES/ari.po
+msgfmt -v hu_HU/LC_MESSAGES/ari.po -o hu_HU/LC_MESSAGES/ari.mo
+msgmerge it_IT/LC_MESSAGES/ari.po ari.utf-8.po --output-file=it_IT/LC_MESSAGES/ari.po
+msgfmt -v ot_IT/LC_MESSAGES/ari.po -o it_IT/LC_MESSAGES/ari.mo
+msgmerge pt_BR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=pt_BR/LC_MESSAGES/ari.po
+msgfmt -v pt_BR/LC_MESSAGES/ari.po -o pt_BR/LC_MESSAGES/ari.mo
+msgmerge sv_SE/LC_MESSAGES/ari.po ari.po --output-file=sv_SE/LC_MESSAGES/ari.po
+msgfmt -v sv_SE/LC_MESSAGES/ari.po -o sv_SE/LC_MESSAGES/ari.mo
+
+
diff --git a/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..baa1a11
--- /dev/null
+++ b/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..0ab45fa
--- /dev/null
+++ b/fs_selfservice/fri/locale/pt_BR/LC_MESSAGES/ari.po
@@ -0,0 +1,647 @@
+# Brazilian portuguese translation
+# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Arnaldo M. Pereira <arnaldo@ansi-c.org>, 2005.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Arnaldo M. Pereira <arnaldo@ansi-c.org>\n"
+"Language-Team: Brazilian Portuguese <arnaldo@ansi-c.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Asterisk Call Manager não responde"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Autenticação no Asterisk falhou:"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "Comando reload do Asterisk não compreendido"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr ""
+
+#: ../includes/bootstrap.php:226
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr ""
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+
+#: ../includes/common.php:173
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Não foi possível conectar ao Asterisk Manager"
+
+#: ../includes/common.php:174
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+
+#: ../includes/common.php:175
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+
+#: ../includes/common.php:176
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+
+#: ../includes/common.php:193 ../includes/common.php:208
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr ""
+"Verifique a instalação do AMP, do banco de dados do asterisk ou do main.conf "
+"do ARI"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr ""
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Página não encontrada."
+
+#: ../includes/display.php:92
+#, fuzzy
+msgid "Search"
+msgstr "Procurado"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "Procurado"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Resultados"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "Resultados"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "Primeiro"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr ""
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Senha incorreta"
+
+#: ../includes/login.php:279
+#, fuzzy
+msgid "Incorrect Username or Password"
+msgstr "Senha incorreta"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr ""
+
+#: ../includes/login.php:419
+#, fuzzy
+msgid "Password"
+msgstr "Senha incorreta"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr ""
+
+#: ../includes/login.php:436
+#, fuzzy
+msgid "Remember Password"
+msgstr "Voicemail para"
+
+#: ../includes/login.php:451
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr "Mailbox e senha do Voicemail"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "Esta é a mesma senha utilizada para o telefone"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Para manutenção e assistência, entre em contato com o Administrador de seu "
+"Sistema de Telefonia"
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr ""
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr ""
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr ""
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr ""
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr ""
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr ""
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr ""
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr ""
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr ""
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr ""
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr ""
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr ""
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr ""
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr ""
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr ""
+
+#: ../includes/main.conf.php:239
+msgid "IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr ""
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr ""
+
+#: ../includes/main.conf.php:244
+msgid "Message Center (does not ask for extension)"
+msgstr ""
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr ""
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr ""
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr ""
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr ""
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr ""
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:291
+msgid "Say envelop (date/time) in recording emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr ""
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr ""
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+#, fuzzy
+msgid "Call Monitor"
+msgstr "Monitor de ligações para"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr ""
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr ""
+
+#: ../modules/callmonitor.module:147
+msgid "duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:150
+msgid "ignore"
+msgstr ""
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr ""
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr ""
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr ""
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr ""
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr ""
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr ""
+
+#: ../modules/callmonitor.module:202
+#, fuzzy
+msgid "Monitor"
+msgstr "Monitor de ligações para"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr ""
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Monitor de ligações para"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr ""
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr ""
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr ""
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr ""
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "Voicemail para"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr ""
+
+#: ../modules/help.module:70
+#, fuzzy, php-format
+msgid "Help for %s (%s)"
+msgstr "Configurações para"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr ""
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr ""
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Configurações"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr ""
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "Senha do Voicemail não alterada"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr "Senha e confirmação de senha não pode ser não pode estar em branco"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#: ../modules/settings.module:162
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "Senha e confirmação de senha não batem"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "não existe ou não tem permissão de escrita"
+
+#: ../modules/settings.module:223
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "Senha do Voicemail não alterada"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "Senha do Voicemail não alterada"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr ""
+
+#: ../modules/settings.module:408
+#, fuzzy
+msgid "Call Routing"
+msgstr "Monitor de ligações para"
+
+#: ../modules/settings.module:411
+#, fuzzy
+msgid "Call Forwarding:"
+msgstr "Monitor de ligações para"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+msgid "Enable"
+msgstr ""
+
+#: ../modules/settings.module:431
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#: ../modules/settings.module:439
+#, fuzzy
+msgid "Voicemail Password:"
+msgstr "Voicemail para"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr ""
+
+#: ../modules/settings.module:492
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "Voicemail para"
+
+#: ../modules/settings.module:498
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "Voicemail para"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr ""
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr ""
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr ""
+
+#: ../modules/settings.module:570
+#, fuzzy
+msgid "Voicemail Settings"
+msgstr "Voicemail para"
+
+#: ../modules/settings.module:611
+#, fuzzy
+msgid "Call Monitor Settings"
+msgstr "Monitor de ligações para"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr ""
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr ""
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr ""
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr ""
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr ""
+
+#: ../modules/settings.module:669
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Configurações para"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr ""
+
+#: ../modules/voicemail.module:45
+#, fuzzy
+msgid "Voicemail"
+msgstr "Voicemail para"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "Uma pasta deve ser selecionada antes que a mensagem possa ser movida."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr ""
+"Uma extensão deve ser selecionada antes que a mensagem possa ser repassada."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr ""
+
+#: ../modules/voicemail.module:307
+msgid "Folder"
+msgstr ""
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr ""
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr ""
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr ""
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr ""
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "Gravação do(s) Voicemail(s) não encontrada."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Login do Voicemail não encontrado, utilizado login SIP"
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "Sem acesso ao voicemail"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "Sem gravações para Admin"
+
+#: ../modules/voicemail.module:428
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "Voicemail para"
+
+#: ../modules/voicemail.module:678
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "Não foi possível criar caixa de mensagens"
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr ""
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr ""
+
+#, fuzzy
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Asterisk Call Manager não responde"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "não é um diretório ou não pode ser lido"
+
+#~ msgid "of"
+#~ msgstr "de"
+
+#~ msgid "Use your"
+#~ msgstr "Use seu"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "A senha deve conter apenas números e apenas 4 dígitos"
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr ""
+#~ "Verifique o formato do audio do voicemail na página de configurações para "
+#~ "mudar de"
+
+#~ msgid "on the server"
+#~ msgstr "no servidor"
+
+#~ msgid "No database connection"
+#~ msgstr "Sem conexão com o banco de dados"
diff --git a/fs_selfservice/fri/locale/readme.txt b/fs_selfservice/fri/locale/readme.txt
new file mode 100644
index 0000000..2491865
--- /dev/null
+++ b/fs_selfservice/fri/locale/readme.txt
@@ -0,0 +1,37 @@
+// To create the .po (write your translations to this file):
+$ find *.php ../includes/* ../modules/*.module ../misc/*.php ../theme/* | xargs xgettext -L PHP -o ari.po --keyword=_ -
+
+// To create the utf-8 .po
+$ iconv -f iso-8859-1 -t utf-8 -o ari.utf-8.po ari.po
+
+// To create the .mo:
+$ msgfmt -v ari.utf-8.po -o ari.mo
+
+// To update (assume both files to be merged are utf-8)
+$ msgmerge es_ES/LC_MESSAGES/ari.po ari.utf-8.po --output-file=es_ES/LC_MESSAGES/ari.po
+$ msgfmt -v es_ES/LC_MESSAGES/ari.po -o es_ES/LC_MESSAGES/ari.mo
+
+
+// script
+// for this to work all translated files need to be converted to utf-8 (use iconv)
+//
+find *.php ../includes/* ../modules/*.module ../misc/*.php ../theme/* | xargs xgettext -L PHP -o ari.po --keyword=_ -
+iconv -f iso-8859-1 -t utf-8 -o ari.utf-8.po ari.po
+msgmerge el_GR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=el_GR/LC_MESSAGES/ari.po
+msgfmt -v el_GR/LC_MESSAGES/ari.po -o el_GR/LC_MESSAGES/ari.mo
+msgmerge es_ES/LC_MESSAGES/ari.po ari.utf-8.po --output-file=es_ES/LC_MESSAGES/ari.po
+msgfmt -v es_ES/LC_MESSAGES/ari.po -o es_ES/LC_MESSAGES/ari.mo
+msgmerge fr_FR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=fr_FR/LC_MESSAGES/ari.po
+msgfmt -v fr_FR/LC_MESSAGES/ari.po -o fr_FR/LC_MESSAGES/ari.mo
+msgmerge he_IL/LC_MESSAGES/ari.po ari.utf-8.po --output-file=he_IL/LC_MESSAGES/ari.po
+msgfmt -v he_IL/LC_MESSAGES/ari.po -o he_IL/LC_MESSAGES/ari.mo
+msgmerge hu_HU/LC_MESSAGES/ari.po ari.utf-8.po --output-file=hu_HU/LC_MESSAGES/ari.po
+msgfmt -v hu_HU/LC_MESSAGES/ari.po -o hu_HU/LC_MESSAGES/ari.mo
+msgmerge it_IT/LC_MESSAGES/ari.po ari.utf-8.po --output-file=it_IT/LC_MESSAGES/ari.po
+msgfmt -v ot_IT/LC_MESSAGES/ari.po -o it_IT/LC_MESSAGES/ari.mo
+msgmerge pt_BR/LC_MESSAGES/ari.po ari.utf-8.po --output-file=pt_BR/LC_MESSAGES/ari.po
+msgfmt -v pt_BR/LC_MESSAGES/ari.po -o pt_BR/LC_MESSAGES/ari.mo
+msgmerge sv_SE/LC_MESSAGES/ari.po ari.utf-8.po --output-file=sv_SE/LC_MESSAGES/ari.po
+msgfmt -v sv_SE/LC_MESSAGES/ari.po -o sv_SE/LC_MESSAGES/ari.mo
+
+
diff --git a/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.mo b/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.mo
new file mode 100644
index 0000000..c8ea152
--- /dev/null
+++ b/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.mo
Binary files differ
diff --git a/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.po b/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.po
new file mode 100644
index 0000000..f8f0ad3
--- /dev/null
+++ b/fs_selfservice/fri/locale/sv_SE/LC_MESSAGES/ari.po
@@ -0,0 +1,678 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2006-05-03 08:32-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Niklas Larsson <pnsystem@comhem.se>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../includes/asi.php:46
+msgid "Asterisk Call Manager not responding"
+msgstr "Asterisk Call Manager svara ej"
+
+#: ../includes/asi.php:54
+msgid "Asterisk authentication failed:"
+msgstr "Ej godk&auml;nd autentisering mot Asterisk:"
+
+#: ../includes/asi.php:96 ../includes/asi.php:111
+#, fuzzy
+msgid "Asterisk command not understood"
+msgstr "Asterisk f&ouml;rstod ej omladdningskommandot"
+
+#: ../includes/bootstrap.php:123
+#, php-format
+msgid "To many directories in %s Not all files processed"
+msgstr "F&ouml;r m&aring;nga mappar i %s Alla filer har inte behandlats"
+
+#: ../includes/bootstrap.php:226
+#, fuzzy
+msgid "ARI requires a version of PHP 4.3 or later"
+msgstr "ARI kr&auml;ver version 4.0 eller h&ouml;gre av PHP"
+
+#: ../includes/bootstrap.php:245
+msgid ""
+"PHP PEAR must be installed. Visit http://pear.php.net for help with "
+"installation."
+msgstr ""
+"PHP PEAR m&aring;ste installeras. G&aring; till http://pear.php.net, och "
+"installera."
+
+#: ../includes/common.php:173
+#, fuzzy
+msgid "ARI does not appear to have access to the Asterisk Manager."
+msgstr "Kan ej ansluta till Asterisk Manager"
+
+#: ../includes/common.php:174
+#, fuzzy
+msgid ""
+"Check the ARI 'main.conf.php' configuration file to set the Asterisk Manager "
+"Account."
+msgstr ""
+"Kontrollera ARI 'main.conf' filen och inst&auml;llningarna f&ouml;r Asterisk "
+"Manager kontot."
+
+#: ../includes/common.php:175
+#, fuzzy
+msgid "Check /etc/asterisk/manager.conf for a proper Asterisk Manager Account"
+msgstr ""
+"Kontrollera /etc/asterisk/manager.conf, se till att det finns ett korrekt "
+"Asterisk Manager konto"
+
+#: ../includes/common.php:176
+#, fuzzy
+msgid ""
+"make sure [general] enabled = yes and a 'permit=' line for localhost or the "
+"webserver."
+msgstr ""
+" som bla har [general] enabled = yes och en 'permit=' f&ouml;r localhost "
+"eller ip nummret f&ouml;r webservern"
+
+#: ../includes/common.php:193 ../includes/common.php:208
+#, fuzzy
+msgid "Check AMP installation, asterisk, and ARI main.conf"
+msgstr "Kontrollera AMP installationen, asterisk databas eller ARI main.conf"
+
+#: ../includes/common.php:344
+msgid "Logout"
+msgstr "Logga ut"
+
+#: ../includes/common.php:349
+msgid "Page Not Found."
+msgstr "Sidan hittas ej."
+
+#: ../includes/display.php:92
+msgid "Search"
+msgstr "S&ouml;k"
+
+#: ../includes/display.php:135
+msgid "Searched for"
+msgstr "S&ouml;kte efter"
+
+#: ../includes/display.php:139
+#, fuzzy, php-format
+msgid "Results %d - %d of %d"
+msgstr "Resultat %d av %d"
+
+#: ../includes/display.php:141
+#, fuzzy, php-format
+msgid "Results %d"
+msgstr "Resultat %d"
+
+#: ../includes/display.php:195
+msgid "First"
+msgstr "F&ouml;rst"
+
+#: ../includes/display.php:208
+msgid "Last"
+msgstr "Sist"
+
+#: ../includes/login.php:267
+msgid "Incorrect Password"
+msgstr "Felaktigt l&ouml;senord"
+
+#: ../includes/login.php:279
+msgid "Incorrect Username or Password"
+msgstr "Felaktigt l&ouml;senord"
+
+#: ../includes/login.php:402 ../includes/login.php:411
+msgid "Login"
+msgstr "Anv&auml;ndarnamn"
+
+#: ../includes/login.php:419
+msgid "Password"
+msgstr "L&ouml;senord"
+
+#: ../includes/login.php:428
+msgid "Submit"
+msgstr "Logga in"
+
+#: ../includes/login.php:436
+msgid "Remember Password"
+msgstr "Kom ih&aring;g l&ouml;senord"
+
+#: ../includes/login.php:451
+#, fuzzy
+msgid "Use your <b>Voicemail Mailbox and Password</b>"
+msgstr ""
+"Anv&auml;nd din <b>R&ouml;stbrevl&aring;das nummer och l&ouml;senord</b>"
+
+#: ../includes/login.php:452
+msgid "This is the same password used for the phone"
+msgstr "Det &auml;r samma l&ouml;senord som till din telefon"
+
+#: ../includes/login.php:454
+msgid ""
+"For password maintenance or assistance, contact your Phone System "
+"Administrator."
+msgstr ""
+"Om du har problem med l&ouml;senord eller beh&ouml;ver hj&auml;lp ska du "
+"kontakta din v&auml;xel ansvarig"
+
+#: ../includes/main.conf.php:152
+msgid "INBOX"
+msgstr "Inbox"
+
+#: ../includes/main.conf.php:154
+msgid "Family"
+msgstr "Familj"
+
+#: ../includes/main.conf.php:156
+msgid "Friends"
+msgstr "V&auml;nner"
+
+#: ../includes/main.conf.php:158
+msgid "Old"
+msgstr "Gamla"
+
+#: ../includes/main.conf.php:160
+msgid "Work"
+msgstr "Arbete"
+
+#: ../includes/main.conf.php:229
+msgid "Directory"
+msgstr "Katalog"
+
+#: ../includes/main.conf.php:230
+msgid "Echo Test"
+msgstr "Eko test"
+
+#: ../includes/main.conf.php:231 ../modules/callmonitor.module:161
+#: ../modules/voicemail.module:324
+msgid "Time"
+msgstr "Tid"
+
+#: ../includes/main.conf.php:232
+msgid "Weather"
+msgstr "V&auml;der"
+
+#: ../includes/main.conf.php:233
+msgid "Schedule wakeup call"
+msgstr "Schemal&auml;gg v&auml;ckningssamtal"
+
+#: ../includes/main.conf.php:234
+msgid "festival test (your extension is XXX)"
+msgstr "Festival test (din anknytning &auml;r XXX)"
+
+#: ../includes/main.conf.php:235
+msgid "Activate Call Waiting (deactivated by default)"
+msgstr "Aktivera Samtal V&auml;ntar"
+
+#: ../includes/main.conf.php:236
+msgid "Deactivate Call Waiting"
+msgstr "Avaktivera Samtal V&auml;ntar"
+
+#: ../includes/main.conf.php:237
+msgid "Call Forwarding System"
+msgstr "Vidarekoppla"
+
+#: ../includes/main.conf.php:238
+msgid "Disable Call Forwarding"
+msgstr "Avaktivera vidarekoppling"
+
+#: ../includes/main.conf.php:239
+#, fuzzy
+msgid "IVR Recording"
+msgstr "R&ouml;stmeny inspelning"
+
+#: ../includes/main.conf.php:240
+msgid "Enable Do-Not-Disturb"
+msgstr "Aktivera St&ouml;r Ej"
+
+#: ../includes/main.conf.php:241
+msgid "Disable Do-Not-Disturb"
+msgstr "Avaktivera St&ouml;r Ej"
+
+#: ../includes/main.conf.php:242
+msgid "Call Forward on Busy"
+msgstr "Vidarekoppla vid upptaget"
+
+#: ../includes/main.conf.php:243
+msgid "Disable Call Forward on Busy"
+msgstr "Avaktivera vidarekoppla vid upptaget"
+
+#: ../includes/main.conf.php:244
+#, fuzzy
+msgid "Message Center (does not ask for extension)"
+msgstr "R&ouml;stbrevl&aring;da (fr&aring;ga ej efter anknytning)"
+
+#: ../includes/main.conf.php:245
+msgid "Enter Message Center"
+msgstr "G&aring; till r&ouml;stbrevl&aring;dan"
+
+#: ../includes/main.conf.php:246
+msgid "Playback IVR Recording"
+msgstr "Spela upp r&ouml;stmeny"
+
+#: ../includes/main.conf.php:247
+msgid "Test Fax"
+msgstr "Fax test"
+
+#: ../includes/main.conf.php:248
+msgid "Simulate incoming call"
+msgstr "Simulera inkommande samtal"
+
+#: ../includes/main.conf.php:289
+msgid "Email voicemail as attachment"
+msgstr "Bifoga meddeladen i E-Post"
+
+#: ../includes/main.conf.php:290
+msgid "Say caller id in recording emailed"
+msgstr "L&auml;ser upp nummret i meddelandet"
+
+#: ../includes/main.conf.php:291
+#, fuzzy
+msgid "Say envelop (date/time) in recording emailed"
+msgstr "L&auml;ser upp informationen i meddelandet"
+
+#: ../includes/main.conf.php:292
+msgid "Delete voicemail when emailed"
+msgstr "Radera meddelandet n&auml;r det e-postats"
+
+#: ../includes/main.conf.php:293
+msgid "Play next message after deleting current message"
+msgstr "Spelar upp n&auml;sta eftera att ha raderat nuvarande"
+
+#: ../includes/main.conf.php:294
+msgid "Ask caller to review their voicemail before sending"
+msgstr ""
+
+#: ../includes/main.conf.php:295
+msgid "Maximum time in seconds a voicemail will record"
+msgstr ""
+
+#: ../modules/callmonitor.module:37 ../modules/callmonitor.module:257
+msgid "Call Monitor"
+msgstr "Samtalsregister"
+
+#: ../modules/callmonitor.module:132
+#, php-format
+msgid "Path is not a directory: %s"
+msgstr "S&oulm;kv&auml;gen leder ej till en mapp: %s"
+
+#: ../modules/callmonitor.module:141 ../modules/voicemail.module:301
+msgid "delete"
+msgstr "Radera"
+
+#: ../modules/callmonitor.module:147
+#, fuzzy
+msgid "duration"
+msgstr "L&auml;ngd"
+
+#: ../modules/callmonitor.module:150
+#, fuzzy
+msgid "ignore"
+msgstr "ignorera"
+
+#: ../modules/callmonitor.module:159 ../modules/voicemail.module:322
+msgid "Date"
+msgstr "Datum"
+
+#: ../modules/callmonitor.module:163 ../modules/voicemail.module:326
+msgid "Caller ID"
+msgstr "Nummerpresentation"
+
+#: ../modules/callmonitor.module:165
+msgid "Source"
+msgstr "K&auml;lla"
+
+#: ../modules/callmonitor.module:167
+msgid "Destination"
+msgstr "M&aring;l"
+
+#: ../modules/callmonitor.module:169
+msgid "Context"
+msgstr "Sammanhang"
+
+#: ../modules/callmonitor.module:171 ../modules/voicemail.module:332
+msgid "Duration"
+msgstr "L&auml;ngd"
+
+#: ../modules/callmonitor.module:202
+msgid "Monitor"
+msgstr "Inspelning"
+
+#: ../modules/callmonitor.module:222 ../modules/voicemail.module:373
+msgid "play"
+msgstr "spela"
+
+#: ../modules/callmonitor.module:259
+#, fuzzy, php-format
+msgid "Call Monitor for %s (%s)"
+msgstr "Samtalsregister f&ouml;r %s (%s)"
+
+#: ../modules/callmonitor.module:311 ../modules/voicemail.module:475
+msgid "select"
+msgstr "Val"
+
+#: ../modules/callmonitor.module:312 ../modules/voicemail.module:476
+msgid "all"
+msgstr "alla"
+
+#: ../modules/callmonitor.module:313 ../modules/voicemail.module:477
+msgid "none"
+msgstr "inga"
+
+#: ../modules/callmonitor.module:533
+msgid "Only deletes recording files, not cdr log"
+msgstr "Raderar endast inspelade filer, inte samtalsloggen"
+
+#: ../modules/conference.module:55
+msgid "My Conference room"
+msgstr ""
+
+#: ../modules/conference.module:78
+#, fuzzy, php-format
+msgid "Conference for %s (%s%s)"
+msgstr "R&ouml;stbrevl&aring;da f&ouml;r %s (%s)"
+
+#: ../modules/help.module:39 ../modules/help.module:68
+msgid "Help"
+msgstr "Hj&auml;lp"
+
+#: ../modules/help.module:70
+#, php-format
+msgid "Help for %s (%s)"
+msgstr "Hj&auml;lp f&ouml;r %s (%s)"
+
+#: ../modules/help.module:77
+msgid "Handset Feature Code"
+msgstr "Kortkoder"
+
+#: ../modules/help.module:80
+msgid "Action"
+msgstr "Utf&ouml;r"
+
+#: ../modules/settings.module:61 ../modules/settings.module:667
+msgid "Settings"
+msgstr "Inst&auml;llningar"
+
+#: ../modules/settings.module:125
+msgid "Call forward number not changed"
+msgstr "Vidarekopplingsnummret ej &auml;ndrat"
+
+#: ../modules/settings.module:126
+#, php-format
+msgid ""
+"Number %s must contain dial numbers (characters like '(', '-', and ')' are "
+"ok)"
+msgstr ""
+"Nummer %s ska inneh&aring;lla nummer (tecknen; '(', '-' och ')' &auml;r "
+"till&aring;tna"
+
+#: ../modules/settings.module:151 ../modules/settings.module:156
+#: ../modules/settings.module:161 ../modules/settings.module:166
+#: ../modules/settings.module:176 ../modules/settings.module:181
+msgid "Voicemail password not changed"
+msgstr "L&ouml;senord f&ouml;r r&ouml;stbrevl&aring;dan har inte &auml;ndrats"
+
+#: ../modules/settings.module:152
+msgid "Password and password confirm must not be blank"
+msgstr ""
+"L&ouml;senord och bekr&auml;fta l&ouml;senord f&aring;r inte vara tomma"
+
+#: ../modules/settings.module:157
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and greater than %d digits"
+msgstr "L&ouml;senordet m&aring;ste vara %d siffror"
+
+#: ../modules/settings.module:162
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %d digits"
+msgstr "L&ouml;senordet m&aring;ste vara %d siffror"
+
+#: ../modules/settings.module:167
+msgid "Password and password confirm do not match"
+msgstr "L&ouml;senord och bekr&auml;ftat l&ouml;senord st&auml;mmer inte"
+
+#: ../modules/settings.module:177 ../modules/settings.module:182
+#: ../modules/settings.module:234 ../modules/settings.module:239
+#, fuzzy, php-format
+msgid "%s does not exist or is not writable"
+msgstr "%s finns ej eller &auml;r ej l&auml;sbar"
+
+#: ../modules/settings.module:223
+#, fuzzy
+msgid "Voicemail email and pager address not changed"
+msgstr "L&ouml;senord f&ouml;r r&ouml;stbrevl&aring;dan har inte &auml;ndrats"
+
+#: ../modules/settings.module:233 ../modules/settings.module:238
+#, fuzzy
+msgid "Voicemail email settings not changed"
+msgstr "L&ouml;senord f&ouml;r r&ouml;stbrevl&aring;dan har inte &auml;ndrats"
+
+#: ../modules/settings.module:385
+msgid "Language:"
+msgstr "Spr&aring;k:"
+
+#: ../modules/settings.module:408
+#, fuzzy
+msgid "Call Routing"
+msgstr "Inst&auml;llningar f&ouml;r Vidarekoppling"
+
+#: ../modules/settings.module:411
+#, fuzzy
+msgid "Call Forwarding:"
+msgstr "Vidarekoppling"
+
+#: ../modules/settings.module:419 ../modules/settings.module:507
+#, fuzzy
+msgid "Enable"
+msgstr "Aktivera"
+
+#: ../modules/settings.module:431
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and only %s digits"
+msgstr "L&ouml;senordet m&aring;ste vara %s siffror"
+
+#: ../modules/settings.module:434
+#, fuzzy, php-format
+msgid "Passwords must be all numbers and at least %s digits"
+msgstr "L&ouml;senordet m&aring;ste vara %s siffror"
+
+#: ../modules/settings.module:439
+#, fuzzy
+msgid "Voicemail Password:"
+msgstr "L&ouml;senord f&ouml;r r&ouml;stbrevl&aring;da"
+
+#: ../modules/settings.module:445
+msgid "Enter again to confirm:"
+msgstr "Bekr&auml;fta:"
+
+#: ../modules/settings.module:492
+#, fuzzy
+msgid "Email Voicemail To:"
+msgstr "R&ouml;stbrevl&aring;da"
+
+#: ../modules/settings.module:498
+#, fuzzy
+msgid "Pager Voicemail To:"
+msgstr "R&ouml;stbrevl&aring;da"
+
+#: ../modules/settings.module:558
+msgid "Audio Format:"
+msgstr "Ljud format:"
+
+#: ../modules/settings.module:561
+msgid "Best Quality"
+msgstr "B&auml;sta kvaliten"
+
+#: ../modules/settings.module:562
+msgid "Smallest Download"
+msgstr "Minsta storlek"
+
+#: ../modules/settings.module:570
+msgid "Voicemail Settings"
+msgstr "Inst&auml;llningar f&ouml;r R&ouml;stbrevl&aring;da"
+
+#: ../modules/settings.module:611
+msgid "Call Monitor Settings"
+msgstr "Inst&auml;llningar f&ouml;r Samtalsregister"
+
+#: ../modules/settings.module:614
+msgid "Record INCOMING:"
+msgstr "Spela in inkommande samtal:"
+
+#: ../modules/settings.module:616 ../modules/settings.module:624
+msgid "Always"
+msgstr "Alltid"
+
+#: ../modules/settings.module:617 ../modules/settings.module:625
+msgid "Never"
+msgstr "Aldrig"
+
+#: ../modules/settings.module:618 ../modules/settings.module:626
+msgid "On-Demand"
+msgstr "Vid behov"
+
+#: ../modules/settings.module:622
+msgid "Record OUTGOING:"
+msgstr "Spela in utg&aring;ende samtal:"
+
+#: ../modules/settings.module:669
+#, fuzzy, php-format
+msgid "Settings for %s (%s)"
+msgstr "Inst&auml;llningar f&ouml;r %s (%s)"
+
+#: ../modules/settings.module:705
+msgid "Update"
+msgstr "Uppdatera"
+
+#: ../modules/voicemail.module:45
+msgid "Voicemail"
+msgstr "R&ouml;stbrevl&aring;da"
+
+#: ../modules/voicemail.module:164
+msgid "A folder must be selected before the message can be moved."
+msgstr "En mapp m&aring;sta v&auml;ljas innan meddelandet kan flyttas."
+
+#: ../modules/voicemail.module:178
+msgid "An extension must be selected before the message can be forwarded."
+msgstr ""
+"En anknytning m&aring;ste v&auml;ljas innan meddelandet kan vidarebefodras."
+
+#: ../modules/voicemail.module:304
+msgid "move_to"
+msgstr "Flytta till"
+
+#: ../modules/voicemail.module:307
+#, fuzzy
+msgid "Folder"
+msgstr "Mappar"
+
+#: ../modules/voicemail.module:311
+msgid "forward_to"
+msgstr "Vidarebefodra till"
+
+#: ../modules/voicemail.module:328
+msgid "Priority"
+msgstr "Prioritet"
+
+#: ../modules/voicemail.module:330
+msgid "Orig Mailbox"
+msgstr "Ursprunglig r&ouml;stbrevl&aring;da"
+
+#: ../modules/voicemail.module:362
+msgid "Message"
+msgstr "Meddelande"
+
+#: ../modules/voicemail.module:377
+msgid "Voicemail recording(s) was not found."
+msgstr "R&ouml;stmeddelande hittades inte."
+
+#: ../modules/voicemail.module:378
+#, php-format
+msgid ""
+"On settings page, change voicemail audio format. It is currently set to %s"
+msgstr ""
+"P&aring; inst&auml;llningssidan, &auml;ndra r&ouml;stbrevl&aring;dans "
+"ljudformat. Det &auml;r nu %s"
+
+#: ../modules/voicemail.module:405
+#, fuzzy
+msgid "Voicemail Login not found."
+msgstr "Hittar inte r&ouml;stbrevl&aring;da."
+
+#: ../modules/voicemail.module:406
+msgid "No access to voicemail"
+msgstr "Inget tilltr&auml;de till r&ouml;stbrevl&aring;dan"
+
+#: ../modules/voicemail.module:412
+msgid "No Voicemail Recordings for Admin"
+msgstr "Inga r&ouml;stmeddelande f&ouml;r Admin"
+
+#: ../modules/voicemail.module:428
+#, fuzzy, php-format
+msgid "Voicemail for %s (%s)"
+msgstr "R&ouml;stbrevl&aring;da f&ouml;r %s (%s)"
+
+#: ../modules/voicemail.module:678
+#, fuzzy, php-format
+msgid "Could not create mailbox folder %s on the server"
+msgstr "Kan inte skapa mapp f&ouml;r r&ouml;stbrevl&aring;da"
+
+#: ../modules/voicemail.module:718
+#, php-format
+msgid "Permission denied on folder %s or %s"
+msgstr "Saknar r&auml;ttigheter f&ouml;r mappen %s eller %s"
+
+#: ../misc/recording_popup.php:39
+msgid "download"
+msgstr "ladda ner"
+
+#~ msgid "Folders"
+#~ msgstr "Mappar"
+
+#~ msgid "Version"
+#~ msgstr "Version"
+
+#~ msgid "Passwords must be all numbers and only 4 digits"
+#~ msgstr "L&ouml;senordet m&aring;ste vara 4 siffror"
+
+#~ msgid "Unable to connect to Asterisk Manager"
+#~ msgstr "Kan ej ansluta till Asterisk Manager"
+
+#, fuzzy
+#~ msgid "No Asterisk Manager Interface connection"
+#~ msgstr "Asterisk Call Manager svara ej"
+
+#~ msgid "of"
+#~ msgstr "av"
+
+#~ msgid "Login used"
+#~ msgstr "Anv&auml;nd Login"
+
+#~ msgid "help"
+#~ msgstr "hj&auml;lp"
+
+#~ msgid "not a directory or not readable"
+#~ msgstr "inte en mapp eller ej l&auml;sbar"
+
+#~ msgid "Use your"
+#~ msgstr "Anv&auml;nd din"
+
+#~ msgid "for"
+#~ msgstr "f&ouml;r"
+
+#~ msgid "Password must be all numbers and 4 digits"
+#~ msgstr "L&ouml;senordet m&aring;ste vara 4 siffror"
+
+#~ msgid "Check voicemail audio format on settings page to change from"
+#~ msgstr ""
+#~ "&Auml;ndra inst&auml;llningar f&ouml;r r&ouml;stbrevl&aring;dans ljud "
+#~ "format f&ouml;r att &auml;ndra fr&aring;n"
+
+#~ msgid "on the server"
+#~ msgstr "p&aring; servern"
+
+#~ msgid "No database connection"
+#~ msgstr "Ingen kontakt med databasen"
diff --git a/fs_selfservice/fri/misc/audio.php b/fs_selfservice/fri/misc/audio.php
new file mode 100644
index 0000000..2dc355c
--- /dev/null
+++ b/fs_selfservice/fri/misc/audio.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * plays recording file
+ */
+
+
+
+if (isset($_GET['recording'])) {
+
+ chdir("..");
+ include_once("./includes/bootstrap.php");
+
+ global $ARI_CRYPT_PASSWORD;
+
+ $crypt = new Crypt();
+
+ $path = $crypt->decrypt($_GET['recording'],$ARI_CRYPT_PASSWORD);
+
+ // strip ".." from path for security
+ $path = preg_replace('/\.\./','',$path);
+
+ // See if the file exists
+ if (!is_file($path)) { die("<b>404 File not found!</b>"); }
+
+ // Gather relevent info about file
+ $size = filesize($path);
+ $name = basename($path);
+ $extension = strtolower(substr(strrchr($name,"."),1));
+
+ // This will set the Content-Type to the appropriate setting for the file
+ $ctype ='';
+ switch( $extension ) {
+ case "mp3": $ctype="audio/mpeg"; break;
+ case "wav": $ctype="audio/x-wav"; break;
+ case "Wav": $ctype="audio/x-wav"; break;
+ case "WAV": $ctype="audio/x-wav"; break;
+ case "gsm": $ctype="audio/x-gsm"; break;
+
+ // not downloadable
+ default: die("<b>404 File not found!</b>"); break ;
+ }
+
+ // need to check if file is mislabeled or a liar.
+ $fp=fopen($path, "rb");
+ if ($size && $ctype && $fp) {
+ header("Pragma: public");
+ header("Expires: 0");
+ header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+ header("Cache-Control: public");
+ header("Content-Description: wav file");
+ header("Content-Type: " . $ctype);
+ header("Content-Disposition: attachment; filename=" . $name);
+ header("Content-Transfer-Encoding: binary");
+ header("Content-length: " . $size);
+ fpassthru($fp);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/misc/popup.css b/fs_selfservice/fri/misc/popup.css
new file mode 100644
index 0000000..7a53528
--- /dev/null
+++ b/fs_selfservice/fri/misc/popup.css
@@ -0,0 +1,10 @@
+/*
+ * popup
+ */
+
+.popup_download {
+ color: #105D90;
+ margin: 250px;
+ font-size: 12px;
+ text-align: right;
+} \ No newline at end of file
diff --git a/fs_selfservice/fri/misc/recording_popup.php b/fs_selfservice/fri/misc/recording_popup.php
new file mode 100644
index 0000000..1546adc
--- /dev/null
+++ b/fs_selfservice/fri/misc/recording_popup.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * popup window for playing recording
+ */
+
+chdir("..");
+include_once("./includes/bootstrap.php");
+
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <TITLE>ARI</TITLE>
+ <link rel="stylesheet" href="popup.css" type="text/css">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ </head>
+ <body>
+
+<?php
+
+ global $ARI_CRYPT_PASSWORD;
+
+ $crypt = new Crypt();
+
+ $path = $crypt->encrypt($_GET['recording'],$ARI_CRYPT_PASSWORD);
+
+ if (isset($path)) {
+ if (isset($_GET['date'])) {
+ echo($_GET['date'] . "<br>");
+ }
+ if (isset($_GET['time'])) {
+ echo($_GET['time'] . "<br>");
+ }
+ echo("<br>");
+ echo("<embed src='audio.php?recording=" . $path . "' width=300, height=20 autoplay=true loop=false></embed><br>");
+ echo("<a class='popup_download' href=/recordings/misc/audio.php?recording=" . $path . ">" . _("download") . "</a><br>");
+ }
+
+?>
+
+ </body>
+</html>
+
diff --git a/fs_selfservice/fri/modules.template/blank.module b/fs_selfservice/fri/modules.template/blank.module
new file mode 100644
index 0000000..a3676c4
--- /dev/null
+++ b/fs_selfservice/fri/modules.template/blank.module
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for help
+ */
+class blank {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 8;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=blank&f=display'>" . _("Blank") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $ARI_HELP_FEATURE_CODES;
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Blank");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+ $ret .= 'Blank goes here';
+
+ return $ret;
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/modules/VmX.module b/fs_selfservice/fri/modules/VmX.module
new file mode 100644
index 0000000..61ef653
--- /dev/null
+++ b/fs_selfservice/fri/modules/VmX.module
@@ -0,0 +1,661 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the call monitor recordings
+ */
+
+/**
+ * Class for Followme
+ */
+class VmX {
+
+ var $protocol_table;
+ var $protocol_config_files;
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 6;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ global $SETTINGS_ALLOW_VMX_SETTINGS;
+ global $ARI_ADMIN_USERNAME;
+
+ $ret = "";
+
+ // We are only going to show the menu
+ // if VmX is allowed
+ if ($SETTINGS_ALLOW_VMX_SETTINGS) {
+
+ $exten = $_SESSION['ari_user']['extension'];
+
+ // and we are not logged in as admin
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+
+ $vmx_enabled = $this->getVmxState($exten,'unavail');
+
+ // and vmx is enabled for this user
+ if ($vmx_enabled !== false)
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=VmX&f=display'>" . _("VmX&#8482 Locator") . "</a></small></small></p>";
+ }
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Acts on the user settings
+ *
+ * @param $args
+ * Common arguments
+ * @param $a
+ * action
+ */
+ function action($args) {
+
+ global $STANDALONE;
+ global $ARI_ADMIN_USERNAME;
+ global $SETTINGS_ALLOW_VMX_SETTINGS;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+
+ $follow_me_disabled = getArgument($args,'follow_me_disabled');
+
+ $vmx_option_0_number = getArgument($args, 'vmx_option_0_number');
+ $vmx_option_0_system_default = getArgument($args, 'vmx_option_0_system_default');
+ $vmx_option_1_number = getArgument($args, 'vmx_option_1_number');
+ $vmx_option_1_system_default = getArgument($args, 'vmx_option_1_system_default');
+ $vmx_option_2_number = getArgument($args, 'vmx_option_2_number');
+ $vmx_unavail_enabled = getArgument($args, 'vmx_unavail_enabled');
+ $vmx_busy_enabled = getArgument($args, 'vmx_busy_enabled');
+ $vmx_play_instructions = getArgument($args, 'vmx_play_instructions');
+ $vmx_disabled = getArgument($args, 'vmx_disabled');
+
+ $exten = $_SESSION['ari_user']['extension'];
+
+ // The action is 'update
+ if ($a=='update') {
+
+ $follow_me_disabled = ($this->getFollowMeListRingTime($exten) > 0)?0:1;
+
+
+ $vmx_disabled = $this->getVmxState($exten,'unavail');
+ if ($vmx_disabled === false) {
+ $vmx_disabled = true;
+ $SETTINGS_ALLOW_VMX_SETTINGS=false;
+ } else {
+ $vmx_disabled = false;
+ }
+ if ($vmx_disabled) {
+
+ setcookie("ari_vmx_disabled", $vmx_disabled, time()+365*24*60*60);
+ $vmx_disabled_delayed = $vmx_disabled;
+ $_SESSION['ari_error'] =
+ _("Your Premium VmX Locator service has been disabled, REFRESH your browser to remove this message") . "<br>" .
+ sprintf(_("Check with your Telephone System Administrator if you think there is a problem"));
+ }
+
+ if (! $vmx_disabled) {
+
+ // set database
+ $this->setVmxState($exten,'unavail',$vmx_unavail_enabled);
+ $this->setVmxState($exten,'busy',$vmx_busy_enabled);
+ $this->setVmxPlayInstructions($exten,'unavail',$vmx_play_instructions);
+ $this->setVmxPlayInstructions($exten,'busy',$vmx_play_instructions);
+
+ // store cookie
+ setcookie("ari_vmx_unavail_enabled", $vmx_unavail_enabled, time()+365*24*60*60);
+ setcookie("ari_vmx_busy_enabled", $vmx_busy_enabled, time()+365*24*60*60);
+ setcookie("ari_vmx_play_instructions", $vmx_play_instructions, time()+365*24*60*60);
+
+ $stripped_vmx_option_0_number = preg_replace('/-|\(|\)|\s/','',$vmx_option_0_number);
+
+ if ($vmx_option_0_system_default) {
+ $this->setVmxOptionNumber($exten,'0','unavail',"");
+ $this->setVmxOptionNumber($exten,'0','busy',"");
+ setcookie("ari_vmx_option_0_system_default", $vmx_option_0_system_default, time()+365*24*60*60);
+ if (is_numeric($stripped_vmx_option_0_number) || !$stripped_vmx_option_0_number) {
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_vmx_option_0_number']);
+ if ($vmx_option_0_number && $stripped!=$stripped_vmx_option_0_number) {
+ setcookie("ari_vmx_option_0_number", $call_vmx_option_0_number, time()+365*24*60*60);
+ }
+ }
+ } else {
+ if (!is_numeric($stripped_vmx_option_0_number) && $stripped_vmx_option_0_number) {
+ $_SESSION['ari_error'] =
+ _("Option 0 not changed") . "<br>" .
+ sprintf(_("Number %s must contain dial numbers (characters like '(', '-', and ')' are ok)"),$vmx_option_0_number);
+ }
+ else {
+
+ // set database
+ $this->setVmxOptionNumber($exten,'0','unavail',$stripped_vmx_option_0_number);
+ $this->setVmxOptionNumber($exten,'0','busy',$stripped_vmx_option_0_number);
+
+ // store cookie
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_vmx_option_0_number']);
+ if ($vmx_option_0_number && $stripped!=$stripped_vmx_option_0_number) {
+ setcookie("ari_vmx_option_0_number", $call_vmx_option_0_number, time()+365*24*60*60);
+ }
+ }
+ }
+
+ $stripped_vmx_option_1_number = preg_replace('/-|\(|\)|\s/','',$vmx_option_1_number);
+ if ($vmx_option_1_system_default && !$follow_me_disabled) {
+ $this->setVmxOptionFollowMe($exten,'1','unavail');
+ $this->setVmxOptionFollowMe($exten,'1','busy');
+ setcookie("ari_vmx_option_1_system_default", $vmx_option_1_system_default, time()+365*24*60*60);
+ if (is_numeric($stripped_vmx_option_1_number) || !$stripped_vmx_option_1_number) {
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_vmx_option_1_number']);
+ if ($vmx_option_1_number && $stripped!=$stripped_vmx_option_1_number) {
+ setcookie("ari_vmx_option_1_number", $call_vmx_option_1_number, time()+365*24*60*60);
+ }
+ }
+ }
+ else {
+
+ if (!is_numeric($stripped_vmx_option_1_number) && $stripped_vmx_option_1_number) {
+ $_SESSION['ari_error'] =
+ _("Option 1 not changed") . "<br>" .
+ sprintf(_("Number %s must contain dial numbers (characters like '(', '-', and ')' are ok)"),$vmx_option_1_number);
+ }
+ else {
+
+ // set database
+ $this->setVmxOptionNumber($exten,'1','unavail',$stripped_vmx_option_1_number);
+ $this->setVmxOptionNumber($exten,'1','busy',$stripped_vmx_option_1_number);
+
+ // store cookie
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_vmx_option_1_number']);
+ if ($vmx_option_1_number && $stripped!=$stripped_vmx_option_1_number) {
+ setcookie("ari_vmx_option_1_number", $call_vmx_option_1_number, time()+365*24*60*60);
+ }
+ }
+ }
+
+ $stripped_vmx_option_2_number = preg_replace('/-|\(|\)|\s/','',$vmx_option_2_number);
+ if (!is_numeric($stripped_vmx_option_2_number) && $stripped_vmx_option_2_number) {
+ $_SESSION['ari_error'] =
+ _("Option 2 not changed") . "<br>" .
+ sprintf(_("Number %s must contain dial numbers (characters like '(', '-', and ')' are ok)"),$vmx_option_2_number);
+ }
+ else {
+
+ // set database
+ $this->setVmxOptionNumber($exten,'2','unavail',$stripped_vmx_option_2_number);
+ $this->setVmxOptionNumber($exten,'2','busy',$stripped_vmx_option_2_number);
+
+ // store cookie
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_vmx_option_2_number']);
+ if ($vmx_option_2_number && $stripped!=$stripped_vmx_option_2_number) {
+ setcookie("ari_vmx_option_2_number", $call_vmx_option_2_number, time()+365*24*60*60);
+ }
+ }
+ } // vmx_disabled false
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+ global $SETTINGS_ALLOW_VMX_SETTINGS;
+
+ global $loaded_modules;
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $exten = $_SESSION['ari_user']['extension'];
+
+ $display = new DisplaySearch();
+
+ $follow_me_listring_time = $this->getFollowMeListRingTime($exten);
+
+ //TODO: Set this better than this?
+ $follow_me_disabled = ($follow_me_listring_time > 0)?0:1;
+ setcookie("ari_follow_me_disabled", $follow_me_disabled, time()+365*24*60*60);
+
+
+ $vmx_unavail_enabled=$this->getVmxState($exten,'unavail');
+ if ($vmx_unavail_enabled === false) {
+ $vmx_disabled = true;
+ setcookie("ari_vmx_disabled", $vmx_disabled, time()+365*24*60*60);
+ $SETTINGS_ALLOW_VMX_SETTINGS=false;
+ } else {
+ $vmx_disabled = false;
+ setcookie("ari_vmx_disabled", false, time()+365*24*60*60);
+ $vmx_busy_enabled=$this->getVmxState($exten,'busy');
+ $vmx_play_instructions=$this->getVmxPlayInstructions($exten);
+ $vmx_option_0_number=$this->getVmxOptionNumber($exten,'0');;
+ $vmx_option_1_number=$this->getVmxOptionNumber($exten,'1');;
+ $vmx_option_2_number=$this->getVmxOptionNumber($exten,'2');;
+
+ if (is_numeric($vmx_option_0_number)) {
+ $vmx_option_0_system_default='';
+ $vmx_option_0_number_text_box_options='';
+ } else {
+ $vmx_option_0_system_default='checked';
+ $vmx_option_0_number_text_box_options="disabled style='background: #DDD;'";
+ }
+
+ // if follow-me is enabled then the options are a numberic value (dial a phone number)
+ // or a followme target (FMnnn) which should not be displayed but means the box is checked
+ // or otherwise blank (or garbage in which case blank it)
+ //
+ if (!$follow_me_disabled) {
+ $vmx_option_1_system_default=$this->getVmxOptionFollowMe($exten,'1');
+ if ($vmx_option_1_system_default) {
+ $vmx_option_1_number = '';
+ $vmx_option_1_number_text_box_options="disabled style='background: #DDD;'";
+ }
+ }
+ }
+
+ $set_vmx_text .=
+ "
+ <br>
+ <table class='settings'>
+ <tr>
+ <td><a href='#' class='info'>" . _("Use When:") . "<span>" . _("Menu options below are available during your personal voicemail greeting playback. <br/><br/>Check both to use at all times.") . "<br></span></a></td> <td>
+ <input " . $vmx_unavail_enabled . " type=checkbox name='vmx_unavail_enabled' value='checked'>
+ <small>" . _("unavailable") . "</small>
+ </td>
+ <td>
+ <input " . $vmx_busy_enabled . " type=checkbox name='vmx_busy_enabled' value='checked'>
+ <small>" . _("busy") . "</small>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='#' class='info'>" . _("Voicemail Instructions:") ."<span>" . _("Uncheck to play a beep after your personal voicemail greeting.") . "<br></span></a></td>
+ <td>
+ <input " . $vmx_play_instructions . " type=checkbox name='vmx_play_instructions' value='checked'>
+ <small>" . _("Standard voicemail prompts.") . "</small>
+ </td>
+ </tr>
+ </table>
+ <br>
+ <br>
+ <table class='settings'>
+ <tr>
+ <td><a href='#' class='info'>" . _("Press 0:") . "<span>" . _("Pressing 0 during your personal voicemail greeing goes to the Operator.
+ Uncheck to enter another destination here.") . "<br></span></a>
+ </td>
+ <td>
+ <input " . $vmx_option_0_number_text_box_options . " name='vmx_option_0_number' type='text' size=24 value='" . $vmx_option_0_number . "'>
+ </td>
+ <td>
+ <input " . $vmx_option_0_system_default . " type=checkbox name='vmx_option_0_system_default' value='checked' OnClick=\"disable_fields(); return true;\">
+ <small>" . _("Go To Operator") . "</small>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='#' class='info'>" . _("Press 1:") . "<span>";
+
+ if ($follow_me_disabled)
+ $set_vmx_text .= _("The remaining options can have internal extensions, ringgroups, queues and external numbers that may be rung. It is often used to include your cell phone. You should run a test to make sure that the number is functional any time a change is made so you don't leave a caller stranded or receiving invalid number messages.");
+ else
+ $set_vmx_text .= _("Enter an alternate number here, then change your personal voicemail greeting to let callers know to press 1 to reach that number. <br/><br/>If you'd like to use your Follow Me List, check \"Send to Follow Me\" and disable Follow Me above.");
+
+
+ $set_vmx_text .=
+ " <br></span></a>
+ </td>
+ <td>
+ <input " . $vmx_option_1_number_text_box_options . " name='vmx_option_1_number' type='text' size=24 value='" . $vmx_option_1_number . "'>
+ </td>
+ <td>";
+
+
+ if (!$follow_me_disabled)
+ $set_vmx_text .= "<input " . $vmx_option_1_system_default . " type=checkbox name='vmx_option_1_system_default' value='checked' OnClick=\"disable_fields(); return true;\"><small>" . _("Send to Follow-Me") . "</small>";
+
+
+ $set_vmx_text .=
+ "
+ </td>
+ </tr>
+ <tr>
+ <td><a href='#' class='info'>" . _("Press 2:") . "<span>" . _("Use any extensions, ringgroups, queues or external numbers. <br/><br/>Remember to re-record your personal voicemail greeting and include instructions. Run a test to make sure that the number is functional.") . "<br></span></a></td>
+ <td>
+ <input " . $vmx_option_2_number_text_box_options . " name='vmx_option_2_number' type='text' size=24 value='" . $vmx_option_2_number . "'>
+ </td>
+ </tr>
+ </table>
+ <br>
+ <br>
+ ";
+
+
+ // Now we should be ready to build the page
+ $ret .= checkErrorMessage();
+
+ $headerText = sprintf(_("VmX Locator&#8482; Settings for %s (%s)"),$displayname,$exten);
+
+ $ret .= $display->displayHeaderText($headerText);
+ $ret .= $display->displayLine();
+
+ $ret .=
+ "<SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function disable_fields() {
+
+ if (document.ari_settings.vmx_option_0_system_default.checked) {
+ document.ari_settings.vmx_option_0_number.style.backgroundColor = '#DDD';
+ document.ari_settings.vmx_option_0_number.disabled = true;
+ }
+ else {
+ document.ari_settings.vmx_option_0_number.style.backgroundColor = '#FFF';
+ document.ari_settings.vmx_option_0_number.disabled = false;
+ }";
+
+ if (!$follow_me_disabled) {
+ $ret .= "
+ if (document.ari_settings.vmx_option_1_system_default.checked) {
+ document.ari_settings.vmx_option_1_number.style.backgroundColor = '#DDD';
+ document.ari_settings.vmx_option_1_number.disabled = true;
+ }
+ else {
+ document.ari_settings.vmx_option_1_number.style.backgroundColor = '#FFF';
+ document.ari_settings.vmx_option_1_number.disabled = false;
+ }";
+ }
+ $ret .=
+ "}
+ // End -->
+ </script>";
+
+ $ret .=
+ "<form class='settings' name='ari_settings' action='' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value='action'>
+ <input type=hidden name=a value='update'>
+ " . $set_vmx_text . "
+ <br>
+ <input name='submit' type='submit' value='" . _("Update") . "'>
+ </form>";
+
+ return $ret;
+ }
+
+ /*
+ * Gets VMX option FollowMe
+ *
+ * @param $exten
+ * Extension to get information about
+ * @param $digit
+ * Option number to get
+ * @param $mode
+ * Mode to get (unavail/busy)
+ * @return $response
+ * checked if set to got to extesion's follow-me on this option
+ */
+ function getVmxOptionFollowMe($exten, $digit, $mode='unavail') {
+
+ global $asterisk_manager_interface;
+
+ $digit = trim($digit);
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/vmx/$mode/$digit/ext\r\n\r\n");
+ return (($response == 'FM'.$exten) ? 'checked':'');
+ }
+
+ /*
+ * Sets VMX option FollowMe
+ *
+ * @param $exten
+ * Extension to set information about
+ * @param $digit
+ * Option number to set
+ * @param $mode
+ * Mode to set (unavail/busy)
+ * @param $context
+ * Context to set ext to (default from-findmefollow)
+ * @param $priority
+ * Priority to set ext to (default 1)
+ */
+ function setVmxOptionFollowMe($exten, $digit, $mode, $context='ext-findmefollow', $priority='1') {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = "FM$exten";
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/ext $value_opt\r\n\r\n");
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/context $context\r\n\r\n");
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/pri $priority\r\n\r\n");
+ }
+
+ /*
+ * Gets VMX option number
+ *
+ * @param $exten
+ * Extension to get information about
+ * @param $digit
+ * Option number to get
+ * @param $mode
+ * Mode to get (unavail/busy)
+ * @return $number
+ * Number to use or blank if disabled
+ */
+ function getVmxOptionNumber($exten, $digit, $mode='unavail') {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+ $digit = trim($digit);
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/vmx/$mode/$digit/ext\r\n\r\n");
+ if (is_numeric($response)) {
+ $number = $response;
+ }
+
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE["ari_vmx_option_${digit}_number"]);
+ if ($stripped==$number) {
+ $number = $_COOKIE["ari_vmx_option_${digit}_number"];
+ }
+
+ return $number;
+ }
+
+ /*
+ * Sets VMX option number
+ *
+ * @param $exten
+ * Extension to set information about
+ * @param $digit
+ * Option number to set
+ * @param $mode
+ * Mode to set (unavail/busy)
+ * @param $number
+ * Number to set ext to (blank will delete it)
+ * @param $context
+ * Context to set ext to (default from-internal)
+ * @param $priority
+ * Priority to set ext to (default 1)
+ */
+ function setVmxOptionNumber($exten, $digit, $mode, $number, $context='from-internal', $priority='1') {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = trim($number);
+
+ if (is_numeric($value_opt)) {
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/ext $value_opt\r\n\r\n");
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/context $context\r\n\r\n");
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/$digit/pri $priority\r\n\r\n");
+ } else {
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database deltree AMPUSER $exten/vmx/$mode/$digit\r\n\r\n");
+ }
+ }
+
+ /*
+ * Sets VMX State
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $mode
+ * Mode to set (unavail/busy)
+ * @param $vmx_state
+ * enabled/disabled state based on check box value
+ */
+ function setVmxState($exten,$mode,$vmx_state) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = ($vmx_state)?'enabled':'disabled';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/state $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets VMX State
+ *
+ * @param $exten
+ * Extension to get information about
+ * @param $mode
+ * Mode to get (unavail/busy)
+ * @return $data
+ * state of variable (checked/blank) or false if no poper value
+ */
+ function getVmxState($exten, $mode='unavail') {
+
+ global $asterisk_manager_interface;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/vmx/$mode/state\r\n\r\n");
+
+ if (preg_match("/enabled/",$response)) {
+ $response='checked';
+ }
+ elseif (preg_match("/disabled/",$response)) {
+ $response='';
+ }
+ else {
+ $response = false;
+ }
+
+ //TODO: really need to check for a bogus response, see how other side does it
+ //
+ return $response;
+
+ }
+
+ /*
+ * Sets VMX Play Instructions
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $vmx_play_instructions
+ * play instructions or just beep (checked, blank)
+ * @param $mode
+ * Mode to set (unavail/busy)
+ */
+ function setVmxPlayInstructions($exten,$mode,$vmx_play_instructions) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = ($vmx_play_instructions)?'""':'s';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/vmx/$mode/vmxopts/timeout $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Get VMX Play Instructions
+ *
+ * @param $exten
+ * Extension to get information about
+ * @param $mode
+ * Mode to get (unavail/busy)
+ * @return $data
+ * state of variable (checked/blank) or false if no poper value
+ */
+ function getVmxPlayInstructions($exten, $mode='unavail') {
+
+ global $asterisk_manager_interface;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/vmx/$mode/vmxopts/timeout\r\n\r\n");
+
+ if (preg_match("/s/",$response)) {
+ $response='';
+ }
+ else {
+ $response='checked';
+ }
+
+ //TODO: really need to check for a bogus response, see how other side does it
+ //
+ return $response;
+
+ }
+
+
+ /*
+ * Gets Follow Me List-Ring Time if set
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $number
+ * follow me list-ring time returned if set
+ */
+ function getFollowMeListRingTime($exten) {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/grptime\r\n\r\n");
+ if (is_numeric($response)) {
+ $number = $response;
+ }
+
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_follow_me_listring_time']);
+ if ($stripped==$number) {
+ $number = $_COOKIE['ari_follow_me_listring_time'];
+ }
+
+ return $number;
+ }
+
+
+} // class
+
+?>
diff --git a/fs_selfservice/fri/modules/billing.module b/fs_selfservice/fri/modules/billing.module
new file mode 100644
index 0000000..6ef16e5
--- /dev/null
+++ b/fs_selfservice/fri/modules/billing.module
@@ -0,0 +1,250 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for help
+ */
+class billing {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = -2;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=billing&f=display'>" . _("Billing") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Billing");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+
+ $freeside = new FreesideSelfService();
+
+ $fs_info = $freeside->customer_info( array(
+ 'session_id' => $_SESSION['freeside_session_id'],
+ ) );
+ $error = $fs_info['error'];
+ if ( $error ) {
+ //$_SESSION['ari_error'] = _("Incorrect Username or Password");
+ $_SESSION['ari_error'] = $error; #// XXX report as ari_error???!
+ }
+
+ //$ret .= $fs_info['small_custview'];
+ //$ret .= '<BR>';
+
+ $ret .= 'Balance: <b>$'. $fs_info['balance']. '</b><BR><BR>';
+
+ if ( $fs_info['balance'] > 0 ) {
+
+ #$ret .= '<B><A HREF="'. $_SESSION['ARI_ROOT'].
+ # '?m=billing&f=make_payment">Make a payment</A></B><BR><BR>';
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=make_payment">Make a payment</A></B><BR><BR>';
+
+ }
+
+ // XXX count() ???
+ if ( count($fs_info['open_invoices']) ) {
+
+ $ret .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>';
+ $link = '<A HREF="'. $_SESSION['ARI_ROOT'].
+ '?m=billing&f=view_invoice&invnum=';
+
+ $col1 = "eeeeee";
+ $col2 = "cccccc";
+ $col = $col1;
+
+ while ( $i = each($fs_info['open_invoices']) ) {
+
+ $invoice = $i[value];
+
+ $td = '<TD BGCOLOR="#'. $col. '">';
+ $a = $link. $invoice['invnum']. '">';
+ $ret .=
+ "<TR>$td$a". 'Invoice #'. $invoice['invnum']. "</A></TD>$td</TD>".
+ "$td$a". $invoice['date']. "</A></TD>$td</TD>".
+ '<TD BGCOLOR="#'. $col. '" ALIGN="right">'. $a. '$'. $invoice['owed'].
+ '</A></TD>'.
+ '</TR>';
+
+ if ( $col == $col1 ) {
+ $col = $col2;
+ } else {
+ $col = $col1;
+ }
+
+ }
+
+ $ret .= '</TABLE><BR>';
+ } else {
+ $ret .= 'You have no outstanding invoices.<BR><BR>';
+ }
+
+ #$fs_info = $freeside->customer_info( array(
+ # 'session_id' => $_SESSION['freeside_session_id'],
+ #) );
+ #$error = $fs_info['error'];
+ #if ( $error ) {
+ # //$_SESSION['ari_error'] = _("Incorrect Username or Password");
+ # $_SESSION['ari_error'] = $error; #// XXX report as ari_error???!
+ #}
+
+ // $ret .= 'Billing goes here';
+ // XXX navigate to make payment, view invoice,
+ // & myaccount change payment info
+
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=make_payment">Make a credit card payment</A></B><BR><BR>';
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=make_payment">Make an electronic check payment</A></B><BR><BR>';
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=make_payment">Use a prepaid card</A></B><BR><BR>';
+
+ return $ret;
+
+ }
+
+ function make_payment($args) {
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Billing");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+
+ #$freeside = new FreesideSelfService();
+
+ $ret .= 'Make payment goes here';
+
+ return $ret;
+
+ }
+
+ function view_invoice($args) {
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Billing");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ #$ret .= $display->displayLine();
+
+ $invnum = getArgument($args, 'invnum');
+
+ $freeside = new FreesideSelfService();
+ $invoice = $freeside->invoice( array(
+ 'session_id' => $_SESSION['freeside_session_id'],
+ 'invnum' => $invnum,
+ ) );
+ $error = $invoice['error'];
+ if ( $error ) {
+ //$_SESSION['ari_error'] = _("Incorrect Username or Password");
+ $_SESSION['ari_error'] = $error; // XXX report as ari_error???!
+ }
+
+ $html = $invoice['invoice_html']->scalar;
+ $html = str_replace( "\xA0", '&nbsp;', $html); // XX doh
+ error_log($html);
+
+ $ret .= '<TABLE BGCOLOR="#000000" BORDER=0><TR><TD>'.
+ $html.
+ '</TD></TR></TABLE>';
+
+ return $ret;
+
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/modules/callmonitor.module b/fs_selfservice/fri/modules/callmonitor.module
new file mode 100644
index 0000000..36f5f28
--- /dev/null
+++ b/fs_selfservice/fri/modules/callmonitor.module
@@ -0,0 +1,675 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the call monitor recordings
+ */
+
+/**
+ * Class for Callmonitor
+ */
+class Callmonitor {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 2;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=Callmonitor&f=display'>" . _("Call History") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Acts on the selected call monitor recordings in the method indicated by the action and updates page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function recAction($args) {
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+ $order = getArgument($args,'order');
+ $sort = getArgument($args,'sort');
+ $duration_filter = getArgument($args,'duration_filter');
+
+ // get files
+ $files = array();
+ foreach($_REQUEST as $key => $value) {
+ if (preg_match('/selected/',$key)) {
+ array_push($files, $value);
+ }
+ }
+
+ if ($a=='delete') {
+ $this->deleteRecData($files);
+ }
+
+ if ($a=='ignore') {
+
+ $start = 0;
+
+ setcookie("ari_duration_filter", $duration_filter, time()+365*24*60*60);
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&q=" . $q . "&start=" . $start . "&span=" . $span . "&order=" . $order . "&sort=" . $sort . "&duration_filter=" . $duration_filter . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $ASTERISK_CALLMONITOR_PATH;
+ global $CALLMONITOR_ALLOW_DELETE;
+ global $AJAX_PAGE_REFRESH_ENABLE;
+
+ $display = new DisplaySearch();
+
+ // get the search string
+ $m = getArgument($args,'m');
+ $f = getArgument($args,'f');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+ $order = getArgument($args,'order');
+ $sort = getArgument($args,'sort');
+ $duration_filter = getArgument($args,'duration_filter');
+
+ $start = $start=='' ? 0 : $start;
+ $span = $span=='' ? 15 : $span;
+ $order = $order=='' ? 'calldate' : $order;
+ $sort = $sort=='' ? 'desc' : $sort;
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // get data
+ $record_count = $this->getCdrCount($q,$duration_filter);
+ $data = $this->getCdrData($q,$duration_filter,$start,$span,$order,$sort);
+
+ // get the call monitor recording files
+ $paths = split(';',$ASTERISK_CALLMONITOR_PATH);
+ foreach($paths as $key => $path) {
+ if (!is_dir($path)) {
+ $_SESSION['ari_error'] .= sprintf(_("Path is not a directory: %s"),$path) . "<br>";
+ }
+ }
+ $recordings = $this->getRecordings($ASTERISK_CALLMONITOR_PATH,$data);
+
+ // build controls
+ if ($CALLMONITOR_ALLOW_DELETE) {
+ $controls .= "
+ <button class='infobar' type='submit' onclick=\"document.callmonitor_form.a.value='delete'\">
+ " . _("delete") . "
+ </button>
+ &nbsp;";
+ }
+
+ $controls .= "
+ <small>" . _("duration") . "</small>
+ <input name='duration_filter' type='text' size=4 maxlength=8 value='" . $_COOKIE['ari_duration_filter'] . "'>
+ <button class='infobar' type='submit' onclick=\"document.callmonitor_form.a.value='ignore'\">
+ " . _("ignore") . "
+ </button>";
+
+ // table header
+ if ($CALLMONITOR_ALLOW_DELETE) {
+ $recording_delete_header = "<th></th>";
+ }
+
+ $fields[0]['field'] = "calldate";
+ $fields[0]['text'] = _("Date");
+ $fields[1]['field'] = "calldate";
+ $fields[1]['text'] = _("Time");
+ $fields[2]['field'] = "clid";
+ $fields[2]['text'] = _("Caller ID");
+ $fields[3]['field'] = "src";
+ $fields[3]['text'] = _("Source");
+ $fields[4]['field'] = "dst";
+ $fields[4]['text'] = _("Destination");
+ $fields[5]['field'] = "dcontext";
+ $fields[5]['text'] = _("Context");
+ $fields[6]['field'] = "duration";
+ $fields[6]['text'] = _("Duration");
+
+ $i = 0;
+ while ($fields[$i]) {
+
+ $field = $fields[$i]['field'];
+ $text = $fields[$i]['text'];
+ if ($order==$field) {
+ if ($sort=='asc') {
+ $currentSort = 'desc';
+ $arrowImg = "<img src='theme/images/arrow-asc.gif' alt='sort'>";
+ }
+ else {
+ $currentSort = 'asc';
+ $arrowImg = "<img src='theme/images/arrow-desc.gif' alt='sort'>";
+ }
+
+ if ($i==1) {
+ $arrowImg = '';
+ }
+ }
+ else {
+ $arrowImg = '';
+ $currentSort = 'desc';
+ }
+
+ $unicode_q = urlencode($q);
+ $recording_header .= "<th><a href=" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&f=" . $f . "&q=" . $unicode_q . "&order=" . $field . "&sort=" . $currentSort . ">" . $text . $arrowImg . "</a></th>";
+
+ $i++;
+ }
+ $recording_header .= "<th>" . _("Monitor") . "</th>";
+
+ // table body
+ foreach($data as $key=>$value) {
+
+ // recording file
+ $recording = $recordings[$value['uniqueid'] . $value['calldate']];
+
+ // date and time
+ $buf = split(' ', $value[calldate]);
+ $date = $buf[0];
+ $time = $buf[1];
+
+ // recording delete checkbox
+ if ($CALLMONITOR_ALLOW_DELETE) {
+ $recording_delete_checkbox = "<td class='checkbox'><input type=checkbox name='selected" . ++$i . "' value=" . $recording . "></td>";
+ }
+
+ $recordingLink = '';
+ if (is_file($recordings[$value['uniqueid'] . $value['calldate']])) {
+ $recordingLink = "<a href='#' onClick=\"javascript:popUp('misc/recording_popup.php?recording=" . $recording . "&date=" . $date . "&time=" . $time . "'); return false;\">" . _("play") . "</a>";
+ }
+
+ $recording_body .= "<tr>
+ " . $recording_delete_checkbox . "
+ <td width=70>" . $date . "</td>
+ <td>" . $time . "</td>
+ <td>" . $value[clid] . "</td>
+ <td>" . $value[src] . "</td>
+ <td>" . $value[dst] . "</td>
+ <td>" . $value[dcontext] . "</td>
+ <td width=90>" . $value[duration] . " sec</td>
+ <td>" . $recordingLink . "</td>
+ </tr>";
+ }
+ if (!count($data)) {
+ $recording_body .= "<tr></tr>";
+ }
+
+ // options
+ $url_opts = array();
+ $url_opts['sort'] = $sort;
+ $url_opts['order'] = $order;
+ $url_opts['duration_filter'] = $duration_filter;
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ // ajax page refresh script
+ if ($AJAX_PAGE_REFRESH_ENABLE) {
+ // $ret .= ajaxRefreshScript($args);
+ }
+
+ // header
+ if ($_SESSION['ari_user']['admin_callmonitor']) {
+ $header_text = _("Call History");
+ } else {
+ $header_text = sprintf(_("Call History for %s (%s)"),$displayname,$extension);
+ }
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displaySearchBlock('left',$m,$q,$url_opts,true);
+
+ // start form
+ if ($CALLMONITOR_ALLOW_DELETE) {
+
+ $ret .= "
+ <form name='callmonitor_form' action='" . $_SESSION['ARI_ROOT'] . "' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value=recAction>
+ <input type=hidden name=a value=''>
+ <input type=hidden name=q value=" . $q . ">
+ <input type=hidden name=start value=" . $start . ">
+ <input type=hidden name=span value=" . $span . ">
+ <input type=hidden name=order value=" . $order . ">
+ <input type=hidden name=sort value=" . $sort . ">";
+ }
+
+ $ret .= $display->displayInfoBarBlock($controls,$q,$start,$span,$record_count);
+
+ // javascript for popup and message actions
+ $ret .= "
+ <SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function popUp(URL) {
+ eval(\"page = window.open(URL, 'play', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=324,height=110');\");
+ }
+
+ function checkAll(form,set) {
+ var elem = 0;
+ var i = 0;
+ while (elem = form.elements[i]) {
+ if (set) {
+ elem.checked = true;
+ } else {
+ elem.checked = false;
+ }
+ i++;
+ }
+ return true;
+ }
+ // End -->
+ </script>";
+
+ // call monitor delete recording controls
+ if ($CALLMONITOR_ALLOW_DELETE) {
+ $ret .= "
+ <table>
+ <tr>
+ <td>
+ <small>" . _("select") . ": </small>
+ <small><a href='' OnClick=\"checkAll(document.callmonitor_form,true); return false;\">" . _("all") . "</a></small>
+ <small><a href='' OnClick=\"checkAll(document.callmonitor_form,false); return false;\">" . _("none") . "</a></small>
+ </td>
+ </tr>
+ </table>";
+ }
+ else {
+ $ret .= "<br>";
+ }
+
+ // table
+ $ret .= "
+ <table class='callmonitor'>
+ <tr>
+ " . $recording_delete_header . "
+ " . $recording_header . "
+ </tr>
+ " . $recording_body . "
+ </table>";
+
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+ $order = getArgument($args,'order');
+ $sort = getArgument($args,'sort');
+
+ // end form
+ if ($CALLMONITOR_ALLOW_DELETE) {
+ $ret .= "</form>";
+ }
+
+ $ret .= $display->displaySearchBlock('center',$m,$q,$url_opts,false);
+ $ret .= $display->displayNavigationBlock($m,$q,$url_opts,$start,$span,$record_count);
+
+ return $ret;
+ }
+
+ /*
+ * Checks for a recording file
+ *
+ * @param $asterisk_callmonitor_path
+ * path call monitor recording directory on the asterisk server
+ * @param $data
+ * current call monitor recordings on the asterisk server
+ * @return $recording
+ * returns an array of $recording file names if found
+ */
+ function getRecordings($asterisk_callmonitor_path,$data) {
+
+ global $CALLMONITOR_ONLY_EXACT_MATCHING;
+ global $CALLMONITOR_AGGRESSIVE_MATCHING;
+
+ $recordings = array();
+
+ $extension = $_SESSION['ari_user']['extension'];
+
+ $paths = split(';',$asterisk_callmonitor_path);
+ foreach($paths as $key => $path) {
+ $paths[$key] = fixPathSlash($paths[$key]);
+ }
+
+ $files = array();
+ if (!$CALLMONITOR_ONLY_EXACT_MATCHING) {
+ $filter = '';
+ $recursiveMax = 6;
+ $recursiveCount = 0;
+ foreach($paths as $key => $path) {
+ $path_files = getFiles($path,$filter,$recursiveMax,$recursiveCount);
+ if ($path_files) {
+ $files = array_merge($files,$path_files);
+ }
+ }
+ rsort($files);
+ }
+
+ foreach($data as $data_key => $data_value) {
+
+ $recording='';
+
+ $calldate = $data_value['calldate'];
+ $duration = $data_value['duration'];
+ $lastdata = $data_value['lastdata'];
+ $uniqueid = $data_value['uniqueid'];
+ $userfield = $data_value['userfield'];
+
+ // timestamps
+ $st = trim(strtotime($calldate));
+ $et = trim(strtotime($calldate) + $duration); // for on-demand call recordings
+
+ // unique file key
+ if ($uniqueid) {
+ $buf = preg_replace('/\-|\:/', '', $calldate);
+ $calldate_key = preg_replace('/\s+/', '-', $buf);
+ $unique_file_key = $calldate_key . "-" . $uniqueid;
+ }
+ if ($unique_file_key=='') {
+ $buf = preg_split("/\|/", $lastdata);
+ $unique_file_key = $buf[1];
+ }
+
+ $recordingLink = '';
+ foreach($paths as $callmonitor_key => $path) {
+
+ // try to find an exact match using the uniqueid
+ if (isset($uniqueid)) {
+
+ $check_files = array();
+ array_push($check_files,$path . $uniqueid . ".WAV");
+ array_push($check_files,$path . $uniqueid . ".wav");
+ array_push($check_files,$path . $uniqueid . ".gsm");
+
+ array_push($check_files,$path . $unique_file_key . ".WAV");
+ array_push($check_files,$path . $unique_file_key . ".wav");
+ array_push($check_files,$path . $unique_file_key . ".gsm");
+
+ array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".WAV");
+ array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".wav");
+ array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".gsm");
+
+ array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".WAV");
+ array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".wav");
+ array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".gsm");
+
+ array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".WAV");
+ array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".wav");
+ array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".gsm");
+
+ array_push($check_files,$path . $userfield);
+
+ // try to match
+ foreach($check_files as $check_file) {
+ if (is_file($check_file)) {
+ $recording = $check_file;
+ break;
+ }
+ }
+ }
+
+ // if found do not need to check the rest of the paths
+ if ($recording!='') {
+ break;
+ }
+ }
+
+ // get all the callmonitor recordings on server and try to find a non-exact match for this log entry
+ if (!$CALLMONITOR_ONLY_EXACT_MATCHING) {
+
+ // try to find a file using the uniqueid
+ if (!$recording) {
+
+ // try and match the unique id
+ if (!$recording) {
+ foreach($files as $key => $path) {
+ if (strlen($uniqueid)>1 && strpos($path,$uniqueid)!==FALSE) {
+ $recording = $path;
+ $files[$key] = ''; // remove it from the recording files so it will not be matched twice
+ break;
+ }
+ }
+ }
+ }
+
+ // try and match a file using the calldate (if no unique number from database)
+ if (!$recording) {
+
+ foreach($files as $key => $path) {
+ $parts = split("-", $path);
+ if (strlen($st)>1 &&
+ (strpos($path,$st)!==FALSE) ||
+ (strpos($path,"auto")!==FALSE && $parts[1] >= $st && $parts[1] <= $et)) {
+ $recording = $path;
+ $files[$key] = ''; // remove it from the recording files so it will not be matched twice
+ break;
+ }
+ }
+ }
+
+ if ($CALLMONITOR_AGGRESSIVE_MATCHING) {
+
+ // one last stab at finding a recording by adding one or two seconds to the call time
+ if (!$recording) {
+ $st_1 = trim($st+1);
+ $st_2 = trim($st+2);
+ $et_1 = trim($et+1);
+ $et_2 = trim($et+2);
+ foreach($files as $key => $path) {
+ $split = explode("-", $path);
+ if (strlen($st)>1
+ && ((strpos($path,$st_1)!==FALSE) ||
+ (strpos($path,$st_2)!==FALSE) ||
+ (strpos($path,"auto")!==FALSE && $parts[1] >= $st_1 && $parts[1] <= $et_1) ||
+ (strpos($path,"auto")!==FALSE && $parts[1] >= $st_2 && $parts[1] <= $et_2))) {
+ $recording = $path;
+ $files[$key] = ''; // remove it from the recording files so it will not be matched twice
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // add to array to be returned
+ if ($recording) {
+ $recordings[$uniqueid . $calldate] = $recording;
+ }
+ }
+
+ return $recordings;
+ }
+
+ /*
+ * Deletes selected call monitor recordings
+ *
+ * @param $files
+ * Array of files to delete
+ */
+ function deleteRecData($files) {
+
+ foreach($files as $key => $file) {
+ if (is_writable($file)) {
+ unlink($file);
+ } else {
+ $_SESSION['ari_error'] = _("Only deletes recording files, not cdr log");
+ }
+ }
+ }
+
+ /*
+ * Gets cdr record count
+ *
+ * @param $q
+ * query text
+ */
+ function getSearchText($q,$duration_filter) {
+
+ // search text
+ if ($q!='*' && $q!=NULL) {
+ $searchText .= "WHERE ";
+ $tok = strtok($q," \n\t");
+ while ($tok) {
+ $searchText .= " (calldate regexp '" . $tok . "'
+ OR clid regexp '" . $tok . "'
+ OR src regexp '" . $tok . "'
+ OR dst regexp '" . $tok . "'
+ OR dstchannel regexp '" . $tok . "'
+ OR dcontext regexp '" . $tok . "'
+ OR duration regexp '" . $tok . "'
+ OR disposition regexp '" . $tok . "'
+ OR uniqueid regexp '" . $tok . "'
+ OR userfield regexp '" . $tok . "'
+ )";
+ $tok = strtok(" \n\t");
+ if ($tok) {
+ $searchText .= " AND";
+ }
+ }
+ }
+
+ // duration_filter
+ if ($duration_filter) {
+ if (!$searchText) {
+ $searchText .= "WHERE ";
+ } else {
+ $searchText .= "AND ";
+ }
+ $searchText .= "duration>" . $duration_filter . " ";
+ }
+
+ // admin
+ if (!$_SESSION['ari_user']['admin_callmonitor']) {
+ if (!$searchText) {
+ $searchText .= "WHERE ";
+ } else {
+ $searchText .= "AND ";
+ }
+
+ // allow entries to be viewed with users extension
+ $searchText .= "(src = '" . $_SESSION['ari_user']['extension'] . "'
+ OR dst = '" . $_SESSION['ari_user']['extension'] . "'
+
+ OR channel LIKE 'IAX2/" . $_SESSION['ari_user']['extension'] ."-%'
+ OR dstchannel LIKE 'IAX2/" . $_SESSION['ari_user']['extension'] ."-%'
+
+ OR channel LIKE 'SIP/" . $_SESSION['ari_user']['extension'] ."-%'
+ OR dstchannel LIKE 'SIP/" . $_SESSION['ari_user']['extension'] ."-%')";
+
+ // allow entries to be viewed with users outbound CID
+ if (isset($_SESSION['ari_user']['outboundCID']) && trim($_SESSION['ari_user']['outboundCID']) != '') {
+ $searchText .= "OR (src = '" . $_SESSION['ari_user']['outboundCID'] . "'
+ OR dst = '" . $_SESSION['ari_user']['outboundCID'] . "')";
+ }
+ }
+
+ return $searchText;
+ }
+
+ /*
+ * Gets cdr record count
+ *
+ * @param $q
+ * query text
+ * @return $count
+ * Number of cdr records counted
+ */
+ function getCdrCount($q,$duration_filter) {
+
+ global $ASTERISKCDR_DBTABLE;
+
+ $searchText = $this->getSearchText($q,$duration_filter);
+
+ $dbh = $_SESSION['dbh_cdr'];
+ $sql = "SELECT count(*)
+ FROM " . $ASTERISKCDR_DBTABLE . "
+ " . $searchText;
+
+ $result = $dbh->getAll($sql);
+ if (DB::isError($result)) {
+ $_SESSION['ari_error'] = $result->getMessage();
+ return;
+ }
+ $count = $result[0][0];
+
+ return $count;
+ }
+
+ /*
+ * Gets cdr data
+ *
+ * @param $q
+ * query text
+ * @param $start
+ * start record
+ * @param $span
+ * number of records to return
+ * @return $data
+ * cdr data to be returned
+ */
+ function getCdrData($q,$duration_filter,$start,$span,$order,$sort) {
+
+ global $ASTERISKCDR_DBTABLE;
+
+ $data = array();
+
+ $searchText = $this->getSearchText($q,$duration_filter);
+
+ $dbh = $_SESSION['dbh_cdr'];
+ $sql = "SELECT *
+ FROM " . $ASTERISKCDR_DBTABLE . "
+ " . $searchText . "
+ ORDER BY " . $order . " " . $sort . "
+ LIMIT " . $start . "," . $span;
+ $result = $dbh->getAll($sql,DB_FETCHMODE_ASSOC);
+ if (DB::isError($result)) {
+ $_SESSION['ari_error'] = $result->getMessage();
+ return;
+ }
+ $data = $result;
+
+ return $data;
+ }
+
+
+}
+
+
+?>
diff --git a/fs_selfservice/fri/modules/dashboard.module b/fs_selfservice/fri/modules/dashboard.module
new file mode 100644
index 0000000..62d6de4
--- /dev/null
+++ b/fs_selfservice/fri/modules/dashboard.module
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for help
+ */
+class dashboard {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = -4;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=dashboard&f=display'>" . _("Dashboard") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Dashboard");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+ $freeside = new FreesideSelfService();
+ $fs_info = $freeside->customer_info( array(
+ 'session_id' => $_SESSION['freeside_session_id'],
+ ) );
+ $error = $fs_info['error'];
+ if ( $error ) {
+ //$_SESSION['ari_error'] = _("Incorrect Username or Password");
+ $_SESSION['ari_error'] = $error; #// XXX report as ari_error???!
+ }
+
+ $ret .= $fs_info['small_custview'];
+ $ret .= '<BR>';
+
+ if ( $fs_info['balance'] > 0 ) {
+
+ #$ret .= '<B><A HREF="'. $_SESSION['ARI_ROOT'].
+ # '?m=billing&f=make_payment">Make a payment</A></B><BR><BR>';
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=make_payment">Make a payment</A></B><BR><BR>';
+
+ }
+
+ // XXX count() ???
+ if ( count($fs_info['open_invoices']) ) {
+
+ $ret .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>';
+ $link = '<A HREF="'. $_SESSION['ARI_ROOT'].
+ '?m=billing&f=view_invoice&invnum=';
+
+ $col1 = "eeeeee";
+ $col2 = "cccccc";
+ $col = $col1;
+
+ while ( $i = each($fs_info['open_invoices']) ) {
+
+ $invoice = $i[value];
+
+ $td = '<TD BGCOLOR="#'. $col. '">';
+ $a = $link. $invoice['invnum']. '">';
+ $ret .=
+ "<TR>$td$a". 'Invoice #'. $invoice['invnum']. "</A></TD>$td</TD>".
+ "$td$a". $invoice['date']. "</A></TD>$td</TD>".
+ '<TD BGCOLOR="#'. $col. '" ALIGN="right">'. $a. '$'. $invoice['owed'].
+ '</A></TD>'.
+ '</TR>';
+
+ if ( $col == $col1 ) {
+ $col = $col2;
+ } else {
+ $col = $col1;
+ }
+
+ }
+
+ $ret .= '</TABLE><BR>';
+ } else {
+ $ret .= 'You have no outstanding invoices.<BR><BR>';
+ }
+
+ #$ret .= 'Received calls (10)<br><br>';
+ #$ret .= 'Placed calls (10)';
+
+// if ( @tickets ) {
+// $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+// '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Tickets</TH></TR>'.
+// '<TR><TH>#</TH><TH>Subject</TH><TH>Priority</TH><TH>Queue</TH>'.
+// '<TH>Status</TH></TR>';
+// my $col1 = "ffffff";
+// my $col2 = "dddddd";
+// my $col = $col1;
+//
+// foreach my $ticket ( @tickets ) {
+// my $td = qq!<TD BGCOLOR="#$col">!;
+// $OUT .=
+// "<TR>$td". $ticket->{'id'}. "</TD>".
+// $td. $ticket->{'subject'}. "</TD>".
+// $td. ($ticket->{'content'} || $ticket->{'priority'}). "</TD>".
+// $td. $ticket->{'name'}. "</TD>".
+// $td. $ticket->{'status'}. "</TD>".
+// '</TR>';
+// $col = $col eq $col1 ? $col2 : $col1;
+// }
+// $OUT .= '</TABLE>';
+// } else {
+// $OUT .= '';
+// }
+
+ return $ret;
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/modules/featurecodes.module b/fs_selfservice/fri/modules/featurecodes.module
new file mode 100644
index 0000000..75d1d5c
--- /dev/null
+++ b/fs_selfservice/fri/modules/featurecodes.module
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for help
+ */
+class featurecodes {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 7;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=featurecodes&f=display'>" . _("Feature Codes") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $ARI_HELP_FEATURE_CODES;
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("Feature Codes");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // handset feature code header
+ $handset_feature_codes_header =
+ "<tr>
+ <th class='feature_codes'>
+ " . _("Handset Feature Code") . "
+ </th>
+ <th>
+ " . _("Action") . "
+ </th>
+ </tr>";
+
+ // handset feature code body
+ if (isset($_SESSION['dbh_asterisk'])) {
+
+ $sql = "
+ SELECT keycode, description
+ FROM (
+ SELECT modulename, description, defaultcode keycode
+ FROM featurecodes
+ WHERE customcode IS NULL
+ AND enabled = '1'
+ UNION ALL SELECT modulename, description, customcode keycode
+ FROM featurecodes
+ WHERE customcode IS NOT NULL
+ AND enabled = '1'
+ )c
+ WHERE modulename NOT
+ IN ( 'core', 'recordings', 'infoservices', 'polycomreassign')
+ ORDER BY modulename, keycode
+ ";
+
+ $results = $_SESSION['dbh_asterisk']->getAll($sql, DB_FETCHMODE_ASSOC);
+ if(DB::IsError($results)) {
+ $_SESSION['ari_error'] = $results->getMessage();
+ }
+ else {
+ foreach ($results as $item ) {
+ $handset_feature_codes_body .=
+ "<tr>
+ <td class='feature_codes'>
+ " . $item['keycode'] . "
+ </td>
+ <td>
+ " . $item['description'] . "
+ </td>
+ </tr>";
+ }
+ }
+ }
+ else {
+
+ // handset feature code body
+ foreach($ARI_HELP_FEATURE_CODES as $key => $feature_code) {
+
+ $handset_feature_codes_body .=
+ "<tr>
+ <td class='feature_codes'>
+ " . $key . "
+ </td>
+ <td>
+ " . $feature_code . "
+ </td>
+ </tr>";
+ }
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+ // table
+ $ret .= "
+ <table class='help'>
+ " . $handset_feature_codes_header . "
+ " . $handset_feature_codes_body . "
+ </table>";
+
+ return $ret;
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/modules/followme.module b/fs_selfservice/fri/modules/followme.module
new file mode 100644
index 0000000..85a1f37
--- /dev/null
+++ b/fs_selfservice/fri/modules/followme.module
@@ -0,0 +1,678 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the call monitor recordings
+ */
+
+/**
+ * Class for Followme
+ */
+class followme {
+
+ var $protocol_table;
+ var $protocol_config_files;
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 5;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+ global $ARI_ADMIN_USERNAME;
+
+ $exten = $_SESSION['ari_user']['extension'];
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=followme&f=display'>" . _("Follow Me") . "</a></small></small></p>";
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Acts on the user settings
+ *
+ * @param $args
+ * Common arguments
+ * @param $a
+ * action
+ */
+ function action($args) {
+
+ global $STANDALONE;
+ global $ARI_ADMIN_USERNAME;
+ global $SETTINGS_ALLOW_VMX_SETTINGS;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+
+ $lang_code = getArgument($args,'lang_code');
+
+ $follow_me_prering_time = getArgument($args,'follow_me_prering_time');
+ $follow_me_listring_time = getArgument($args,'follow_me_listring_time');
+ $follow_me_list = getArgument($args,'follow_me_list');
+ $follow_me_confirm = getArgument($args,'follow_me_confirm');
+ $follow_me_ddial = getArgument($args,'follow_me_ddial');
+ $follow_me_disabled = getArgument($args,'follow_me_disabled');
+
+ $language = new Language();
+
+ // Lets see if we can make heads or tails of this code?!?
+
+ // The action is 'update
+ if ($a=='update') {
+
+ // Get the extension and make sure we are not in
+ // admin mode
+ $exten = $_SESSION['ari_user']['extension'];
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+
+
+ // Make sure Follow-Me setup has not been deleted for this user since the last refresh
+ $follow_me_disabled_delayed = $_COOKIE['ari_follow_me_disabled'];
+ if (! $_COOKIE['ari_follow_me_disabled']) {
+
+ $follow_me_disabled = ($this->getFollowMeListRingTime($exten) > 0)?0:1;
+
+ if ($follow_me_disabled) {
+
+ setcookie("ari_follow_me_disabled", $follow_me_disabled, time()+365*24*60*60);
+ $follow_me_disabled_delayed = $follow_me_disabled;
+ $_SESSION['ari_error'] =
+ _("Your Follow-Me has been disabled, REFRESH your browser to remove this message") . "<br>" .
+ sprintf(_("Check with your Telephone System Administrator if you think there is a problem"));
+ }
+ }
+
+
+
+ if (! $follow_me_disabled_delayed) {
+
+ // assume no errors, don't update SQL if errors occured
+ $follow_me_update_succeeded=1;
+
+ // update follow me pre-ring time
+ if (!$STANDALONE['use']) {
+
+ $stripped_follow_me_prering_time = preg_replace('/-|\s/','',$follow_me_prering_time);
+ if (!is_numeric($stripped_follow_me_prering_time)) {
+ $_SESSION['ari_error'] =
+ _("Follow-Me pre-ring time not changed") . "<br>" .
+ sprintf(_("Number %s must be an interger number of seconds"),$follow_me_prering_time);
+ $follow_me_update_succeeded=0;
+ }
+ else {
+
+ // set database
+ $this->setFollowMePreRingTime($exten,$stripped_follow_me_prering_time);
+
+ // store cookie
+ $stripped = preg_replace('/-|\s/','',$_COOKIE['ari_follow_me_prering_time']);
+ if ($follow_me_prering_time && $stripped!=$stripped_follow_me_prering_time) {
+ setcookie("ari_follow_me_prering_time", $follow_me_prering_time, time()+365*24*60*60);
+ }
+ }
+ }
+
+ // update follow me list ring time
+ if (!$STANDALONE['use']) {
+
+ $stripped_follow_me_listring_time = preg_replace('/-|\s/','',$follow_me_listring_time);
+ if (!is_numeric($stripped_follow_me_listring_time)) {
+ $_SESSION['ari_error'] =
+ _("Follow-Me list ring time not changed") . "<br>" .
+ sprintf(_("Number %s must be an interger number of seconds"),$follow_me_listring_time);
+ $follow_me_update_succeeded=0;
+ }
+ else {
+
+ // set database
+ $this->setFollowMeListRingTime($exten,$stripped_follow_me_listring_time);
+
+ // store cookie
+ $stripped = preg_replace('/-|\s/','',$_COOKIE['ari_follow_me_listring_time']);
+ if ($follow_me_listring_time && $stripped!=$stripped_follow_me_listring_time) {
+ setcookie("ari_follow_me_listring_time", $follow_me_listring_time, time()+365*24*60*60);
+ }
+ }
+ }
+
+ // update follow me list
+ if (!$STANDALONE['use']) {
+
+ $grplist = explode("\n", $follow_me_list);
+
+ if (!$grplist) {
+ $grplist = null;
+ }
+
+ foreach (array_keys($grplist) as $key) {
+ //trim it
+ $grplist[$key] = trim($grplist[$key]);
+
+ // Lookup the extension and append hash if not a user, and remove invalid chars
+ $grplist[$key] = $this->lookupSetExtensionFormat($grplist[$key]);
+
+ // remove blanks
+ if ($grplist[$key] == "") unset($grplist[$key]);
+ }
+
+ // check for duplicates, and re-sequence
+ $grplist = array_values(array_unique($grplist));
+
+ $stripped_follow_me_list = implode("-",$grplist);
+
+ if ($stripped_follow_me_list == "") {
+ $_SESSION['ari_error'] =
+ _("Follow-Me list must contain at least one valid number") . "<br>" .
+ sprintf(_("The following: %s is not valid"),$follow_me_list);
+ $follow_me_update_succeeded=0;
+ }
+ else {
+
+ // set database
+ $this->setFollowMeList($exten,$stripped_follow_me_list);
+
+ // store cookie
+ $stripped = preg_replace('/|\(|\)|\s/','',$_COOKIE['ari_follow_me_list']);
+ if ($follow_me_list && $stripped!=$stripped_follow_me_list) {
+ setcookie("ari_follow_me_list", $follow_me_list, time()+365*24*60*60);
+ }
+ }
+ }
+
+ // update follow me confirm
+ if (!$STANDALONE['use']) {
+
+ // set database
+ $this->setFollowMeConfirm($exten,$follow_me_confirm);
+ $this->setFollowMeDDial($exten,$follow_me_ddial);
+
+ // store cookie
+ setcookie("ari_follow_me_confirm", $follow_me_confirm, time()+365*24*60*60);
+ setcookie("ari_follow_me_ddial", $follow_me_ddial, time()+365*24*60*60);
+ }
+
+ //If no errors than update the SQL table to keep in sync
+ if ($follow_me_update_succeeded) {
+ $this->setFollowMeMySQL($exten, $follow_me_prering_time, $follow_me_listring_time, $follow_me_list, $follow_me_confirm);
+ }
+
+ } //if !follow_me_disabled
+ }
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $STANDALONE;
+ global $ARI_ADMIN_USERNAME;
+ global $SETTINGS_PRERING_LOW;
+ global $SETTINGS_PRERING_HIGH;
+ global $SETTINGS_LISTRING_LOW;
+ global $SETTINGS_LISTRING_HIGH;
+
+ global $SETTINGS_FOLLOW_ME_LIST_MAX;
+
+ global $loaded_modules;
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $exten = $_SESSION['ari_user']['extension'];
+
+ $language = new Language();
+ $display = new DisplaySearch();
+
+ // build controls
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+
+ // call forward settings
+ if (!$STANDALONE['use']) {
+
+ $follow_me_prering_time = $this->getFollowMePreRingTime($exten);
+ $follow_me_listring_time = $this->getFollowMeListRingTime($exten);
+ $follow_me_list = explode("-", $this->getFollowMeList($exten) );
+ $follow_me_confirm = $this->getFollowMeConfirm($exten);
+ $follow_me_ddial = $this->getFollowMeDDial($exten);
+
+ $FOLLOW_ME_LIST_MAX = (count($follow_me_list) > $SETTINGS_FOLLOW_ME_LIST_MAX) ? count($follow_me_list):$SETTINGS_FOLLOW_ME_LIST_MAX;
+
+ //TODO: Set this better than this?
+ $follow_me_disabled = ($follow_me_listring_time > 0)?0:1;
+ setcookie("ari_follow_me_disabled", $follow_me_disabled, time()+365*24*60*60);
+
+ $followme_text.= "<table class='settings'>";
+
+ if (!$follow_me_disabled) {
+ // $followme_text .= "<tr><td><h3><br>" . _("Follow Me") . "</h3></td></tr>";
+ $followme_text .= "<tr><td>&nbsp;</td></tr>"; // Blank Line
+
+ $followme_text .= "<tr><td><a href='#' class='info'>" . _("Enable") . "<span>";
+ $followme_text .= _( "Dial-by-name Directory, IVR, and internal
+ calls will ring the numbers in the FollowMe
+ List. Any FreePBX routes that directly
+ reference a FollowMe are unaffected by this
+ enable/disable setting.");
+ $followme_text .= "<br></span></a></td>";
+
+ $followme_text .= "<td><input " . $follow_me_ddial . " type=checkbox name='follow_me_ddial' value='checked'></td></tr>";
+
+ $followme_text .= "<tr><td>&nbsp;</td></tr>"; // Blank Line
+ $followme_text .= "<tr><td valign='top'><a href='#' class='info'>" . _("Follow Me List:");
+ $followme_text .= "<span>" . sprintf(_("Extensions and outside numbers to ring next.")) ."<br/><br/>";
+ $followme_text .= sprintf(_("Include %s to keep it ringing."),"<strong>".$exten."</strong>") . "<br></span></a></td>";
+ $followme_text .= "<td><textarea " . $follow_me_list_options . " id='follow_me_list' name='follow_me_list' type='text' cols='20' rows='".$FOLLOW_ME_LIST_MAX."' value='' onKeyUp='rowCounter(this.form.follow_me_list, ".$FOLLOW_ME_LIST_MAX.");' onKeyDown='rowCounter(this.form.follow_me_list, ".$FOLLOW_ME_LIST_MAX.");'>".implode("\n",$follow_me_list)."</textarea>";
+ $followme_text .= "</td></tr>";
+
+ $followme_text .= "<tr><td>&nbsp;</td></tr>"; // Blank Line
+ $followme_text .= "<tr><td><a href='#' class='info'>";
+ $followme_text .= sprintf(_("Ring %s First For:"), $exten);
+ $followme_text .= "<span>" . sprintf( _("Time to ring extension %s before ringing the %s Follow Me List %s"), "<strong>".$exten."</strong>","<strong>","</strong>");
+ $followme_text .= "<br></span></a></td><td>";
+
+ $followme_text .= "<select " . $follow_me_prering_time_text_box_options . " name='follow_me_prering_time'/>";
+ $default_prering = $follow_me_prering_time;
+ for ($i=$SETTINGS_PRERING_LOW; $i <= $SETTINGS_PRERING_HIGH; $i++) {
+ $followme_text .= '<option value="'.$i.'" '.($i == $default_prering ? 'SELECTED' : '').'>'.$i.'</option>';
+ }
+ $followme_text .= "</select>";
+
+ $followme_text .= "<small>" . _("seconds") . "</small>";
+ $followme_text .= "</td></tr>";
+
+ $followme_text .= "<tr><td><a href='#' class='info'>" . _("Ring Followme List for:") . "<span>" . _("Time to ring the Follow Me List.") . "<br></span></a></td>";
+ $followme_text .= "<td>";
+
+ $followme_text .= "<select " . $follow_me_listring_time_text_box_options . " name='follow_me_listring_time'/>";
+ $default_listring = $follow_me_listring_time;
+ for ($i=$SETTINGS_LISTRING_LOW; $i <= $SETTINGS_LISTRING_HIGH; $i++) {
+ $followme_text .= '<option value="'.$i.'" '.($i == $default_listring ? 'SELECTED' : '').'>'.$i.'</option>';
+ }
+ $followme_text .= "</select>";
+
+ $followme_text .= "<small>" . _("seconds") . "</small></td></tr>";
+
+
+ $followme_text .= "<tr><td>&nbsp;</td></tr>"; // Blank Line
+
+ $followme_text .= "<tr><td><a href='#' class='info'>" . _("Use Confirmation:") . "<span>". _("Outside lines that are part of the Follow Me List will be called and offered a menu:<br/><br/> \"You have an incoming call. Press 1 to accept or 2 to decline.\"<br/><br/> This keeps calls from ending up in external voicemail. Make sure that the List Ring Time is long enough to allow for you to hear and react to this message.");
+ $followme_text .= "<br></span></a></td><td>";
+ $followme_text .= "<input " . $follow_me_confirm . " type=checkbox name='follow_me_confirm' value='checked'>";
+ $followme_text .= "<small>" . _("Enable") . "</small></td></tr>";
+ $followme_text .= "<tr><td>&nbsp;</td></tr>"; // Blank Line
+ $followme_text .= "</table>";
+ }
+ }
+
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ if ($_SESSION['ari_user']['admin_settings']) {
+ $headerText = _("Followme Settings");
+ } else {
+ $headerText = sprintf(_("Followme Settings for %s (%s)"),$displayname,$exten);
+ }
+
+ $ret .= $display->displayHeaderText($headerText);
+ $ret .= $display->displayLine();
+
+ $ret .=
+ "\n<SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function rowCounter(field, maxlimit) {
+ temp = field.value.split('\u000A',maxlimit+1)
+ field.value = temp.join('\u000A')
+ if (temp.length == maxlimit+1) {
+ field.value = field.value.substring(0, field.value.length-1)
+ }
+ }
+ // End -->
+ </script>\n";
+
+ $ret .=
+ "<form class='settings' name='ari_settings' action='' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value='action'>
+ <input type=hidden name=a value='update'>
+ " . $followme_text . "
+ <br>
+ <input name='submit' type='submit' value='" . _("Update") . "'>
+ </form>";
+
+ return $ret;
+ }
+
+
+ /*
+ * Sets Follow Me Pre-Ring Time
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_prering_time
+ * Pre-Ring Time to ring
+ */
+ function setFollowMePreRingTime($exten,$follow_me_prering_time) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = $follow_me_prering_time;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/followme/prering $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets Follow Me Pre-Ring Time if set
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $number
+ * follow me pre-ring time returned if set
+ */
+ function getFollowMePreRingTime($exten) {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/prering\r\n\r\n");
+ if (is_numeric($response)) {
+ $number = $response;
+ }
+
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_follow_me_prering_time']);
+ if ($stripped==$number) {
+ $number = $_COOKIE['ari_follow_me_prering_time'];
+ }
+
+ return $number;
+ }
+
+ /*
+ * Sets Follow Me List Ring Time
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_listring_time
+ * List Ring Time to ring
+ */
+ function setFollowMeListRingTime($exten,$follow_me_listring_time) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = $follow_me_listring_time;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/followme/grptime $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets Follow Me List-Ring Time if set
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $number
+ * follow me list-ring time returned if set
+ */
+ function getFollowMeListRingTime($exten) {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/grptime\r\n\r\n");
+ if (is_numeric($response)) {
+ $number = $response;
+ }
+
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_follow_me_listring_time']);
+ if ($stripped==$number) {
+ $number = $_COOKIE['ari_follow_me_listring_time'];
+ }
+
+ return $number;
+ }
+
+ /*
+ * Sets Follow Me List
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_list
+ * Follow Me List
+ */
+ function setFollowMeList($exten,$follow_me_list) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = $follow_me_list;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/followme/grplist $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets Follow Me List if set
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $data
+ * follow me list if set
+ */
+ function getFollowMeList($exten) {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/grplist\r\n\r\n");
+
+ //TODO: really need to check for a bogus response, see how other side does it
+ //
+ return preg_replace("/[^0-9*\-]/", "", $response);
+ }
+
+ /*
+ * Sets Follow Confirmation Setting
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_cofirm
+ * Follow Me Confirm Setting
+ */
+ function setFollowMeConfirm($exten,$follow_me_confirm) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = ($follow_me_confirm)?'ENABLED':'DISABLED';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/followme/grpconf $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets Follow Me Confirmation Setting
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $data
+ * follow me confirm setting
+ */
+ function getFollowMeConfirm($exten) {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/grpconf\r\n\r\n");
+
+ if (preg_match("/ENABLED/",$response)) {
+ $response='checked';
+ }
+ else {
+ $response='';
+ }
+
+ //TODO: really need to check for a bogus response, see how other side does it
+ //
+ return $response;
+
+ }
+
+ /*
+ * Sets Follow Ddial Setting
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_ddial
+ * Follow Me Ddial Setting
+ */
+ function setFollowMeDDial($exten,$follow_me_ddial) {
+
+ global $asterisk_manager_interface;
+
+ $value_opt = ($follow_me_ddial)?'DIRECT':'EXTENSION';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/followme/ddial $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets Follow Me Ddial Setting
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $data
+ * follow me ddial setting
+ */
+ function getFollowMeDDial($exten) {
+
+ global $asterisk_manager_interface;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/followme/ddial\r\n\r\n");
+
+ if (preg_match("/EXTENSION/",$response)) {
+ $response='';
+ }
+ else {
+ $response='checked';
+ }
+
+ //TODO: really need to check for a bogus response, see how other side does it
+ //
+ return $response;
+
+ }
+
+
+
+
+
+ /*
+ * Gets FreePBX Version
+ */
+ function getFreePBXVersion() {
+
+ if (isset($_SESSION['dbh_asterisk'])) {
+ $sql = "SELECT * FROM admin WHERE variable = 'version'";
+ $results = $_SESSION['dbh_asterisk']->getAll($sql);
+ if(DB::IsError($results)) {
+ $_SESSION['ari_error'] = $results->getMessage();
+ }
+
+ return $results[0][1];
+ }
+ }
+
+ /*
+ * Sets Follow-Me Settings in FreePBX MySQL Database
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $follow_me_prering_time
+ * Pre-Ring Time to ring
+ * @param $follow_me_listring_time
+ * List Ring Time to ring
+ * @param $follow_me_list
+ * Follow Me List
+ * @param $follow_me_list
+ * Follow Me Confirm Setting
+ *
+ */
+ function setFollowMeMySQL($exten, $follow_me_prering_time, $follow_me_listring_time, $follow_me_list, $follow_me_confirm) {
+
+ if (isset($_SESSION['dbh_asterisk'])) {
+
+ //format for SQL database
+ $follow_me_confirm = ($follow_me_confirm)?'CHECKED':'';
+
+ $sql = "UPDATE findmefollow SET grptime = '" . $follow_me_listring_time . "', grplist = '".
+ str_replace("'", "''", trim($follow_me_list)) . "', pre_ring = '" . $follow_me_prering_time .
+ "', needsconf = '" . $follow_me_confirm . "' WHERE grpnum = $exten LIMIT 1";
+ $results = $_SESSION['dbh_asterisk']->query($sql);
+
+ if(DB::IsError($results)) {
+ $_SESSION['ari_error'] = $results->getMessage();
+ }
+
+ return 1;
+ }
+ }
+
+ function lookupSetExtensionFormat($exten) {
+
+ if (trim($exten) == "") return $exten;
+
+ $exten = preg_replace("/[^0-9*]/", "", $exten);
+
+ $sql = "SELECT extension FROM users WHERE extension = '".$exten."'";
+ $asa = $_SESSION['dbh_asterisk']->getrow($sql, DB_FETCHMODE_ASSOC);
+ if (!is_array($asa)) {
+ return $exten.'#';
+ } else {
+ return $exten;
+ }
+ }
+
+
+} // class
+
+?>
diff --git a/fs_selfservice/fri/modules/myaccount.module b/fs_selfservice/fri/modules/myaccount.module
new file mode 100644
index 0000000..6b7cb83
--- /dev/null
+++ b/fs_selfservice/fri/modules/myaccount.module
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the help page
+ */
+
+/**
+ * Class for help
+ */
+class myaccount {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 9;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=myaccount&f=display'>" . _("My Account") . "</a></small></small></p><br>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $ARI_HELP_FEATURE_CODES;
+
+ $display = new Display();
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $header_text = _("My Account");
+ if (!$_SESSION['ari_user']['admin_help']) {
+ $header_text .= sprintf(_(" for %s (%s)"), $displayname, $extension);
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $ret .= $display->displayHeaderText($header_text);
+ $ret .= $display->displayLine();
+
+ $freeside = new FreesideSelfService();
+ $fs_info = $freeside->customer_info( array(
+ 'session_id' => $_SESSION['freeside_session_id'],
+ ) );
+ $error = $fs_info['error'];
+ if ( $error ) {
+ //$_SESSION['ari_error'] = _("Incorrect Username or Password");
+ $_SESSION['ari_error'] = $error; #// XXX report as ari_error???!
+ }
+
+ $ret .= $fs_info['small_custview'];
+ $ret .= '<BR>';
+
+
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=change_bill">Change billing address</A></B>';
+
+ $ret .= '&nbsp;&nbsp;|&nbsp;&nbsp;';
+
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=change_ship">Change service address</A></B>';
+
+ $ret .= '<BR><BR>';
+
+ $ret .= '<B><A HREF="/selfservice/selfservice.cgi?session='.
+ $_SESSION['freeside_session_id'].
+ ';action=change_pay">Change payment information</A></B><BR><BR>';
+
+ return $ret;
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/fri/modules/phonefeatures.module b/fs_selfservice/fri/modules/phonefeatures.module
new file mode 100644
index 0000000..89dc903
--- /dev/null
+++ b/fs_selfservice/fri/modules/phonefeatures.module
@@ -0,0 +1,342 @@
+<?php
+//*****************************************************************************
+class PhoneFeatures {
+//*****************************************************************************
+ function rank() {
+
+ $rank = 4;
+ return $rank;
+ }
+
+//*****************************************************************************
+ function init() {
+ }
+//*****************************************************************************
+ function navMenu($args) {
+
+ global $ARI_NO_LOGIN;
+ global $SETTINGS_ALLOW_PHONE_SETTINGS;
+ global $SETTINGS_ALLOW_CALLFORWARD_SETTINGS;
+
+ // If we're not allowing call forwarding AND PHONE SETTINGS get out of here
+ if (!$SETTINGS_ALLOW_PHONE_SETTINGS && !$SETTINGS_ALLOW_CALLFORWARD_SETTINGS) return "";
+
+ $ret .= "
+ <p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=PhoneFeatures&f=display'>" . _("Phone Features") . "</a></small></small></p>";
+
+ return $ret;
+ }
+//*****************************************************************************
+ function action($args) {
+
+ global $ARI_ADMIN_USERNAME;
+ global $SETTINGS_ALLOW_PHONE_SETTINGS;
+ global $SETTINGS_ALLOW_CALLFORWARD_SETTINGS;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+ $lang_code = getArgument( $args,'lang_code');
+ $exten = $_SESSION['ari_user']['extension'];
+
+ if ($a=='update') {
+
+ if ($SETTINGS_ALLOW_PHONE_SETTINGS) {
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+ $this->storePhoneSetting( $args, $exten, 'call_waiting', 'CW', 'ENABLED');
+ $this->storePhoneSetting( $args, $exten, 'do_not_disturb', 'DND', 'YES');
+ }
+ }
+
+ if ($SETTINGS_ALLOW_CALLFORWARD_SETTINGS) {
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+ $this->storeCallForwardNumber( $args, $exten, 'call_forward', 'CF');
+ $this->storeCallForwardNumber( $args, $exten, 'call_forward_busy', 'CFB');
+ $this->storeCallForwardNumber( $args, $exten, 'call_forward_unavailable', 'CFU');
+ }
+ }
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+//*****************************************************************************
+function display($args) {
+
+ global $STANDALONE;
+ global $ARI_ADMIN_USERNAME;
+ global $SETTINGS_ALLOW_PHONE_SETTINGS;
+ global $SETTINGS_ALLOW_CALLFORWARD_SETTINGS;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+ $lang_code = getArgument( $args,'lang_code');
+ $exten = $_SESSION['ari_user']['extension'];
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $exten = $_SESSION['ari_user']['extension'];
+
+ $display = new DisplaySearch();
+
+ // build controls
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+
+ if ($SETTINGS_ALLOW_PHONE_SETTINGS) {
+ $dnd_cw_text = "<table class='settings'>";
+ $dnd_cw_text.= "<tr><td><h3>" . _("Phone Features") . "</h3></td></tr>";
+
+ $dnd_cw_text.= $this->displayPhoneControls( $exten, 'call_waiting', 'CW', "Call Waiting");
+ $dnd_cw_text.= $this->displayPhoneControls( $exten, 'do_not_disturb', 'DND', "Do Not Disturb");
+
+ $dnd_cw_text .= "</table>";
+ }
+
+ if ($SETTINGS_ALLOW_CALLFORWARD_SETTINGS) {
+
+ $set_call_forward_text .= "<SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function rowCounter(field, maxlimit) {
+ temp = field.value.split('\u000A',maxlimit+1)
+ field.value = temp.join('\u000A')
+ if (temp.length == maxlimit+1) {
+ field.value = field.value.substring(0, field.value.length-1)
+ }
+ }
+
+ function disable_fields() {
+
+ if (document.ari_settings.call_forward_enable.checked) {
+ document.ari_settings.call_forward_number.style.backgroundColor = '#FFF';
+ document.ari_settings.call_forward_number.disabled = false;
+ }
+ else {
+ document.ari_settings.call_forward_number.style.backgroundColor = '#DDD';
+ document.ari_settings.call_forward_number.disabled = true;
+ }
+
+ if (document.ari_settings.call_forward_busy_enable.checked) {
+ document.ari_settings.call_forward_busy_number.style.backgroundColor = '#FFF';
+ document.ari_settings.call_forward_busy_number.disabled = false;
+ }
+ else {
+ document.ari_settings.call_forward_busy_number.style.backgroundColor = '#DDD';
+ document.ari_settings.call_forward_busy_number.disabled = true;
+ }
+
+ if (document.ari_settings.call_forward_unavailable_enable.checked) {
+ document.ari_settings.call_forward_unavailable_number.style.backgroundColor = '#FFF';
+ document.ari_settings.call_forward_unavailable_number.disabled = false;
+ }
+ else {
+ document.ari_settings.call_forward_unavailable_number.style.backgroundColor = '#DDD';
+ document.ari_settings.call_forward_unavailable_number.disabled = true;
+ }
+ }
+ // End -->
+ </script>";
+
+ $set_call_forward_text.= "<table class='settings'>";
+ $set_call_forward_text.= "<tr><td><h3>" . _("Call Forwarding") . "</h3></td></tr>";
+
+ $set_call_forward_text.= $this->displayCallForwardControls( $exten, 'call_forward', 'CF', "Unconditional:");
+ $set_call_forward_text.= $this->displayCallForwardControls( $exten, 'call_forward_unavailable', 'CFU', "Unavailable:");
+ $set_call_forward_text.= $this->displayCallForwardControls( $exten, 'call_forward_busy', 'CFB', "Busy:");
+
+ $set_call_forward_text .= "</table>";
+ }
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ if ($_SESSION['ari_user']['admin_settings']) {
+ $headerText = _("Phone Features");
+ } else {
+ $headerText = sprintf(_("Phone Features for %s (%s)"),$displayname,$exten);
+ }
+
+ $ret .= $display->displayHeaderText($headerText);
+ $ret .= $display->displayLine();
+ $ret .= "
+ <form class='settings' name='ari_settings' action='' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value='action'>
+ <input type=hidden name=a value='update'>
+ <br>
+ " . $dnd_cw_text . "
+ <br>
+ " . $set_call_forward_text . "
+ <br>
+ <input name='submit' type='submit' value='" . _("Update") . "'>
+ </form>";
+
+return $ret;
+}
+//*****************************************************************************
+ function setPhoneSetting( $databaseCallFwdType, $exten, $state_value) {
+
+ global $asterisk_manager_interface;
+
+ $type_opt = ($state_value != "") ? "put":"del";
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database $type_opt $databaseCallFwdType $exten $state_value\r\n\r\n");
+ }
+
+//*****************************************************************************
+ function getPhoneSetting($exten, $databaseCallFwdType) {
+
+ global $asterisk_manager_interface;
+ $number = '';
+
+ $result = false;
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get $databaseCallFwdType $exten\r\n\r\n");
+ if (stristr($response, 'ENABLED')) {
+ $result = true;
+ }
+ elseif (stristr($response, 'YES')) {
+ $result = true;
+ }
+
+ return $result;
+ }
+//*****************************************************************************
+ function storePhoneSetting( $args, $exten, $settingType, $databaseCallFwdType, $state_value)
+ {
+ $setting_enable = getArgument( $args, $settingType . '_enable');
+
+ $this->setPhoneSetting( $databaseCallFwdType, $exten, ($setting_enable == 'checked')?$state_value:"");
+ }
+
+//*****************************************************************************
+ function displayPhoneControls( $exten, $callFwdType, $databaseCallFwdType, $title)
+ {
+
+ $phone_setting_enable = ($this->getPhoneSetting($exten, $databaseCallFwdType)) ? 'checked':'';
+
+ $ret = "\n<tr>";
+ $ret.= "<td>";
+ $ret.= "<label><input " . $phone_setting_enable . " type=checkbox name='" . $callFwdType . "_enable' value='checked' >";
+ $ret.= "<small>" . _($title) . "</small></label>";
+ $ret.= "</td>";
+ $ret.= "</tr>\n";
+
+ return $ret;
+ }
+//*****************************************************************************
+ /*
+ * Sets Asterisk call forward setting
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $state
+ * Call forward enable or disable
+ * @param $call_forward_number
+ * Call forward number
+ * @param $variable_opt
+ * Call forward type (CF, CFU, CFB)
+ */
+ function setCallForward($exten,$state,$call_forward_number, $variable_opt = "CF") {
+
+ global $asterisk_manager_interface;
+
+ if ($state) {
+ $type_opt = "put";
+ $value_opt = $call_forward_number;
+ }
+ else {
+ $type_opt = "del";
+ }
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database $type_opt $variable_opt $exten $value_opt\r\n\r\n");
+ }
+
+ /*
+ * Gets call forward number if set
+ *
+ * @param $exten
+ * Extension to get information about
+ * @return $number
+ * call forward number returned if set
+ * @param $variable_opt
+ * Call forward type (CF, CFU, CFB)
+ */
+ function getCallForwardNumber($exten, $variable_opt = "CF") {
+
+ global $asterisk_manager_interface;
+
+ $number = '';
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get $variable_opt $exten\r\n\r\n");
+ if (is_numeric($response)) {
+ $number = $response;
+ }
+
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_call_forward_number']);
+ if ($stripped==$number) {
+ $number = $_COOKIE['ari_call_forward_number'];
+ }
+
+ return $number;
+ }
+
+
+ function storeCallForwardNumber( $args, $exten, $callFwdType, $databaseCallFwdType)
+ {
+ $call_forward_enable = getArgument($args, $callFwdType . '_enable');
+ $call_forward_number = getArgument($args, $callFwdType . '_number');
+
+ $stripped_call_forward_number = preg_replace('/-|\(|\)|\s/','',$call_forward_number);
+
+ if ($call_forward_enable && !is_numeric($stripped_call_forward_number)) {
+ $_SESSION['ari_error'] = _("Call forward number not changed") . "<br>" .
+ sprintf(_("Number %s must contain dial numbers (characters like '(', '-', and ')' are ok)"), $call_forward_number);
+ }
+ else {
+ $this->setCallForward( $exten, $call_forward_enable, $stripped_call_forward_number, $databaseCallFwdType);
+
+ // store cookie
+ $stripped = preg_replace('/-|\(|\)|\s/','',$_COOKIE['ari_' . $callFwdType]);
+ if ($call_forward_number && $stripped!=$stripped_call_forward_number) {
+ setcookie('ari_' . $callFwdType, $call_forward_number, time()+365*24*60*60);
+ }
+ }
+ }
+
+ function displayCallForwardControls( $exten, $callFwdType, $databaseCallFwdType, $title)
+ {
+ $call_forward_number = $this->getCallForwardNumber($exten, $databaseCallFwdType);
+
+ // If we have a value, we want the item checked
+ if ($call_forward_number) {
+ $call_forward_enable = 'checked';
+ }
+ else {
+ $call_forward_number = $_COOKIE['ari_' . $callFwdType ];
+ $call_forward_text_box_options = "disabled style='background: #DDD;'";
+ }
+
+ $ret = "\n<tr>";
+ $ret.= "<td>" . _($title) . "</td>";
+ $ret.= "<td>";
+ $ret.= "<input " . $call_forward_text_box_options . " name='" . $callFwdType . "_number' type='text' size=24 value='" . $call_forward_number . "'>";
+ $ret.= "</td>";
+ $ret.= "<td>";
+ $ret.= "<input " . $call_forward_enable . " type=checkbox name='" . $callFwdType . "_enable' value='checked' OnClick=\"disable_fields(); return true;\">";
+ $ret.= "<small>" . _("Enable") . "</small>";
+ $ret.= "</td>";
+ $ret.= "</tr>\n";
+
+ return $ret;
+ }
+} // class
+?>
diff --git a/fs_selfservice/fri/modules/settings.module b/fs_selfservice/fri/modules/settings.module
new file mode 100644
index 0000000..f20eb02
--- /dev/null
+++ b/fs_selfservice/fri/modules/settings.module
@@ -0,0 +1,813 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the call monitor recordings
+ */
+
+/**
+ * Class for settings
+ */
+class Settings {
+
+ var $protocol_table;
+ var $protocol_config_files;
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 9;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+
+ // determine what protocol user is using
+ global $ASTERISK_PROTOCOLS;
+
+ foreach ($ASTERISK_PROTOCOLS as $protocol => $value) {
+ $data = $this->getProtocolRecordSettings($value['table'],$_SESSION['ari_user']['extension']);
+ if (count($data)) {
+ $this->protocol_table = $value['table'];
+ $this->protocol_config_files = $value['config_files'];
+ break;
+ }
+ }
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ $ret = "";
+ $exten = $_SESSION['ari_user']['extension'];
+
+ // and we are not logged in as admin
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=Settings&f=display'>" . _("Phone Settings") . "</a></small></small></p><br>";
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Acts on the user settings
+ *
+ * @param $args
+ * Common arguments
+ * @param $a
+ * action
+ */
+ function action($args) {
+
+ global $ARI_ADMIN_USERNAME;
+ global $ASTERISK_VOICEMAIL_CONF;
+ global $SETTINGS_ALLOW_VOICEMAIL_SETTINGS;
+ global $SETTINGS_ALLOW_VOICEMAIL_PASSWORD_SET;
+ global $SETTINGS_VOICEMAIL_PASSWORD_LENGTH;
+ global $SETTINGS_VOICEMAIL_PASSWORD_EXACT;
+ global $SETTINGS_ALLOW_CALL_RECORDING_SET;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+
+ $voicemail_password = getArgument($args,'voicemail_password');
+ $voicemail_password_confirm = getArgument($args,'voicemail_password_confirm');
+ $voicemail_email_address = getArgument($args,'voicemail_email_address');
+ $voicemail_pager_address = getArgument($args,'voicemail_pager_address');
+ $voicemail_email_enable = getArgument($args,'voicemail_email_enable');
+ $voicemail_audio_format = getArgument($args,'voicemail_audio_format');
+ $record_in = getArgument($args,'record_in');
+ $record_out = getArgument($args,'record_out');
+
+ if (isset($_SESSION['ari_user']['voicemail_email'])) {
+ foreach (array_keys($_SESSION['ari_user']['voicemail_email']) as $key) {
+ $var = "voicemail_email_$key";
+ $$var = getArgument($args,$var);
+ }
+ }
+
+ if ($a=='update') {
+
+ $exten = $_SESSION['ari_user']['extension'];
+ if ($exten!=$ARI_ADMIN_USERNAME) {
+
+ // Make sure Follow-Me setup has not been deleted for this user since the last refresh
+ $follow_me_disabled_delayed = $_COOKIE['ari_follow_me_disabled'];
+
+ // voicemail settings
+ if ($SETTINGS_ALLOW_VOICEMAIL_SETTINGS && $_SESSION['ari_user']['voicemail_enabled']==1) {
+
+
+ // update voicemail password
+ if ($SETTINGS_ALLOW_VOICEMAIL_PASSWORD_SET) {
+
+ // update voicemail password
+ if ($voicemail_password=='' || $voicemail_password_confirm=='') {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ _("Password and password confirm must not be blank");
+ }
+ else if ((strlen($voicemail_password)<$SETTINGS_VOICEMAIL_PASSWORD_LENGTH) || !is_numeric($voicemail_password)) {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ sprintf(_("Passwords must be all numbers and greater than %d digits"),$SETTINGS_VOICEMAIL_PASSWORD_LENGTH);
+ }
+ else if (strlen($voicemail_password)!=$SETTINGS_VOICEMAIL_PASSWORD_LENGTH && $SETTINGS_VOICEMAIL_PASSWORD_EXACT || !is_numeric($voicemail_password)) {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ sprintf(_("Passwords must be all numbers and only %d digits"),$SETTINGS_VOICEMAIL_PASSWORD_LENGTH);
+ }
+ else if ($voicemail_password!=$voicemail_password_confirm) {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ _("Password and password confirm do not match");
+ }
+ else {
+
+ // check for writable the files
+ $temp_file = $ASTERISK_VOICEMAIL_CONF . ".tmp";
+ $fp = fopen($temp_file, "w");
+ if (!$fp) {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ sprintf(_("%s does not exist or is not writable"),$temp_file);
+ }
+ else if (!is_writable($ASTERISK_VOICEMAIL_CONF)) {
+ $_SESSION['ari_error'] =
+ _("Voicemail password not changed") . "<br>" .
+ sprintf(_("%s does not exist or is not writable"),$ASTERISK_VOICEMAIL_CONF);
+ }
+ else {
+
+ // update session
+ $_SESSION['ari_user']['voicemail_password'] = $voicemail_password;
+
+ // save password
+ $lines = file($ASTERISK_VOICEMAIL_CONF);
+ foreach ($lines as $key => $line) {
+ unset($value);
+ list($var,$value) = split('=>',$line);
+ $var = trim($var);
+ if ($var==$exten && $value) {
+
+ // write out line with password change
+ $buf = split(',',$value);
+ $buf[0] = $voicemail_password;
+ $line = $var . " => " . join(',', $buf);
+
+ fwrite($fp, $line);
+ }
+ else {
+ // write out original line with no changes
+ fwrite($fp, $line);
+ }
+ }
+ fclose($fp);
+ unlink($ASTERISK_VOICEMAIL_CONF);
+ rename($temp_file,$ASTERISK_VOICEMAIL_CONF);
+
+ $voicemail_reload = 1;
+ }
+ }
+
+ // voicemail email address
+ if ($voicemail_email_enable &&
+ ($voicemail_email_address && !preg_match('/@/',$voicemail_email_address) ||
+ ($voicemail_pager_address && !preg_match('/@/',$voicemail_pager_address)))) {
+ $_SESSION['ari_error'] =
+ _("Voicemail email and pager address not changed") . "<br>" .
+ ("'$voicemail_email_address' and '$voicemail_pager_address' must be a valid email addresses");
+ }
+ else {
+
+ // check for writable the files
+ $temp_file = $ASTERISK_VOICEMAIL_CONF . ".tmp";
+ $fp = fopen($temp_file, "w");
+ if (!$fp) {
+ $_SESSION['ari_error'] =
+ _("Voicemail email settings not changed") . "<br>" .
+ sprintf(_("%s does not exist or is not writable"),$temp_file);
+ }
+ else if (!is_writable($ASTERISK_VOICEMAIL_CONF)) {
+ $_SESSION['ari_error'] =
+ _("Voicemail email settings not changed") . "<br>" .
+ sprintf(_("%s does not exist or is not writable"),$ASTERISK_VOICEMAIL_CONF);
+ }
+ else {
+
+ // store cookie
+ if ($voicemail_email_enable) {
+ setcookie("ari_voicemail_email_address", $voicemail_email_address, time()+365*24*60*60);
+ setcookie("ari_voicemail_pager_address", $voicemail_pager_address, time()+365*24*60*60);
+ foreach (array_keys($_SESSION['ari_user']['voicemail_email']) as $key) {
+ $var = "voicemail_email_$key";
+ $var_cookie = "ari_" . $var;
+ setcookie("$var_cookie", $$var, time()+365*24*60*60);
+ }
+ }
+
+ // update session
+ $_SESSION['ari_user']['voicemail_email_enable'] = $voicemail_email_enable;
+ if ($voicemail_email_enable) {
+ $_SESSION['ari_user']['voicemail_email_address'] = $voicemail_email_address;
+ $_SESSION['ari_user']['voicemail_pager_address'] = $voicemail_pager_address;
+ foreach (array_keys($_SESSION['ari_user']['voicemail_email']) as $key) {
+ $option = "voicemail_email_$key";
+ $_SESSION['ari_user']['voicemail_email'][$key] = $$option;
+ }
+ }
+
+ // save settings
+ if (!$voicemail_email_enable) {
+ $voicemail_email_address = '';
+ $voicemail_pager_address = '';
+ }
+
+ $lines = file($ASTERISK_VOICEMAIL_CONF);
+ foreach ($lines as $key => $line) {
+ unset($value);
+ list($var,$value) = split('=>',$line);
+ $var = trim($var);
+ if ($var==$exten && $value) {
+
+ // write out line with voicemail email change
+ $buf = split(',',$value);
+ $buf[2] = $voicemail_email_address;
+ $buf[3] = $voicemail_pager_address;
+
+ foreach ($_SESSION['ari_user']['voicemail_email'] as $key => $value) {
+ $option = "voicemail_email_$key";
+ if ($$option && $key) {
+ $options .= $key . "=" . $value;
+ }
+ else {
+ $options .= $key . "=no";
+ }
+ $options .= "|";
+ }
+ $buf[4] = substr($options, 0, -1);
+
+ $line = $var . " =>" . join(',', $buf);
+ if (substr($line, 0, -1)!="\n") {
+ $line .= "\n";
+ }
+
+ fwrite($fp, $line);
+ }
+ else {
+
+ // write out original line with no changes
+ fwrite($fp, $line);
+ }
+ }
+ fclose($fp);
+ unlink($ASTERISK_VOICEMAIL_CONF);
+ rename($temp_file,$ASTERISK_VOICEMAIL_CONF);
+
+ $voicemail_reload = 1;
+ }
+ }
+
+ // reload asterisk voicemail
+ if ($voicemail_reload) {
+ $this->reloadAsteriskVoicemail();
+ }
+ }
+
+ // update voicemail audio format setting
+ setcookie("ari_voicemail_audio_format", $voicemail_audio_format, time()+365*24*60*60);
+ }
+
+ // update call monitor record setting
+ if ($SETTINGS_ALLOW_CALL_RECORDING_SET) {
+ if ($record_in && $record_out) {
+ $this->setRecordSettings($exten,$record_in,$record_out);
+ }
+ }
+ }
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+ global $SETTINGS_ALLOW_VOICEMAIL_SETTINGS;
+ global $SETTINGS_ALLOW_VOICEMAIL_PASSWORD_SET;
+ global $SETTINGS_VOICEMAIL_PASSWORD_LENGTH;
+ global $SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS;
+ global $ARI_VOICEMAIL_AUDIO_FORMAT_DEFAULT;
+ global $SETTINGS_ALLOW_CALL_RECORDING_SET;
+
+ global $loaded_modules;
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $exten = $_SESSION['ari_user']['extension'];
+
+ $language = new Language();
+ $display = new DisplaySearch();
+
+ // get data
+ $data = $this->getRecordSettings($_SESSION['ari_user']['extension']);
+
+ // lang setting options
+ if (extension_loaded('gettext')) {
+ $setLangText = "<p class='lang'>" . _("Language:") . " " . $language->GetForm() . "</p>";
+ }
+
+
+ // voicemail settings
+ if ($SETTINGS_ALLOW_VOICEMAIL_SETTINGS && $_SESSION['ari_user']['voicemail_enabled']==1 &&
+ in_array('voicemail',array_keys($loaded_modules))) {
+ if ($SETTINGS_ALLOW_VOICEMAIL_PASSWORD_SET) {
+
+ if ($SETTINGS_VOICEMAIL_PASSWORD_EXACT) {
+ $voicemail_password_length_message = sprintf(_("Passwords must be all numbers and only %s digits"),$SETTINGS_VOICEMAIL_PASSWORD_LENGTH);
+ }
+ else {
+ $voicemail_password_length_message = sprintf(_("Passwords must be all numbers and at least %s digits"),$SETTINGS_VOICEMAIL_PASSWORD_LENGTH);
+ }
+
+ $set_voicemail_password_text = "
+ <tr>
+ <td>" . _("Voicemail Password:") . "</td>
+ <td>
+ <input name='voicemail_password' type='password' size=16 value=" . $_SESSION['ari_user']['voicemail_password'] . ">
+ </td>
+ </tr>
+ <tr>
+ <td>" . _("Enter again to confirm:") . "</td>
+ <td>
+ <input name='voicemail_password_confirm' type='password' size=16 value=" . $_SESSION['ari_user']['voicemail_password'] . ">
+ </td>
+ </tr>
+ <tr>
+ <td class='note' colspan=2><small>" . $voicemail_password_length_message . "</small></td>
+ </tr>";
+ }
+
+ if (isset($_SESSION['ari_user']['voicemail_email'])) {
+
+ if ($_SESSION['ari_user']['voicemail_email_enable']) {
+ $voicemail_email_address = $_SESSION['ari_user']['voicemail_email_address'];
+ $voicemail_pager_address = $_SESSION['ari_user']['voicemail_pager_address'];
+ $voicemail_email_enable = 'checked';
+
+ foreach (array_keys($_SESSION['ari_user']['voicemail_email']) as $key) {
+ $var = "voicemail_email_$key";
+ $var_enable = $var . "enable";
+ if ($_SESSION['ari_user']['voicemail_email'][$key]=='yes') {
+ $$var_enable = 'checked';
+ }
+ }
+ }
+ else {
+
+ $voicemail_email_address = $_COOKIE['ari_voicemail_email_address'];
+ $voicemail_email_text_box_options = "disabled style='background: #DDD;'";
+ $voicemail_pager_address = $_COOKIE['ari_voicemail_pager_address'];
+ $voicemail_pager_text_box_options = "disabled style='background: #DDD;'";
+
+ foreach ($_SESSION['ari_user']['voicemail_email'] as $key => $value) {
+ $var = "voicemail_email_$key";
+ $var_cookie = "ari_" . $var;
+ $var_enable = $var . "enable";
+ $var_text_box_options = $var . "text_box_options";
+
+ $$var_text_box_options = "disabled";
+ if ($_COOKIE[$var_cookie]=='yes') {
+ $$var_enable = 'checked';
+ }
+ }
+ }
+
+ $set_voicemail_email_text = "
+
+ <tr>
+ <td> " . _("Email Notification") . " <input " . $voicemail_email_enable . " type=checkbox name='voicemail_email_enable' value='1' OnClick=\"disable_fields(); return true;\">
+ <small> " ._("Enable") . " </small>
+ </td>
+ </tr><tr>
+ <td><a href='#' class='info'>" . _("Email Voicemail To:") . "<span>" . ("Email a notification, including audio file if indicated below.") . " </span></a></td>
+ <td>
+ <input " . $voicemail_email_text_box_options . " name='voicemail_email_address' type='text' size=48 value='" . $voicemail_email_address . "'>
+ </td>
+ </tr>
+ <tr>
+ <td><a href='#' class='info'>" . _("Pager Email Notification To:") . "<span>" . ("Email a short notification") . " </span></a></td>
+ <td>
+ <input " . $voicemail_pager_text_box_options . " name='voicemail_pager_address' type='text' size=48 value='" . $voicemail_pager_address . "'>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ </tr>";
+
+ foreach ($_SESSION['ari_user']['voicemail_email'] as $key => $value) {
+
+ $var = "voicemail_email_$key";
+ $var_enable = $var . "enable";
+ $var_text_box_options = $var . "text_box_options";
+ if ($SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS[$key]) {
+ $var_text = $SETTINGS_VOICEMAIL_EMAIL_OPTION_DESCRIPTIONS[$key];
+ }
+ else {
+ $var_text = $key;
+ }
+
+ if ($value != 'yes' && $value != 'no' && $value !='') {
+
+ $size = strlen($value) - 1;
+ $set_voicemail_email_text .= "
+ <tr>
+ <td></td>
+ <td>
+ <input type=text size='" . $size . "' name='" . $var . "' value='" . $value . "' OnClick=\"disable_fields(); return true;\">
+ <small>" . $var_text . "</small>
+ </td>
+ </tr>";
+ }
+ else {
+
+ $set_voicemail_email_text .= "
+ <tr>
+ <td></td>
+ <td>
+ <input " . $$var_enable . " " . $$var_text_box_options . " type=checkbox name='" . $var . "' value='yes' OnClick=\"disable_fields(); return true;\">
+ <small>" . $var_text . "</small>
+ </td>
+ </tr>";
+ }
+ }
+ }
+
+ $wav_enable = 'selected';
+ if ($_COOKIE['ari_voicemail_audio_format']=='.gsm'||
+ ($_COOKIE['ari_voicemail_audio_format']=='' && $ARI_VOICEMAIL_AUDIO_FORMAT_DEFAULT='.gsm')) {
+ $wav_enable = '';
+ $gsm_enable = 'selected';
+ }
+
+ $set_voicemail_audio_format_text = "
+ <tr>
+ <td>" . _("Audio Format:") . "</td>
+ <td>
+ <select name='voicemail_audio_format'>
+ <option value='.wav' " . $wav_enable . ">" . _("Best Quality") . " (.wav)</option>
+ <option value='.gsm' " . $gsm_enable . ">" . _("Smallest Download") . " (.gsm)</option>
+ </select>
+ </td>
+ </tr>";
+
+ $set_voicemail_text = "
+ <table class='settings'>
+ <tr>
+ <td><h3>" . _("Voicemail Settings") . "</h3></td>
+ </tr>
+ " . $set_voicemail_password_text . "
+ " . $set_voicemail_email_text . "
+ " . $set_voicemail_audio_format_text . "
+ </table>";
+ }
+
+ // call monitor settings
+ if ($this->getFreePBXVersion() &&
+ $SETTINGS_ALLOW_CALL_RECORDING_SET &&
+ in_array('callmonitor',array_keys($loaded_modules))) {
+
+ foreach($data as $key=>$value) {
+ if ($key=='record_in') {
+ if ($value=='Always') {
+ $ri_always = 'checked=checked';
+ }
+ elseif ($value=='Never') {
+ $ri_never = 'checked=checked';
+ }
+ elseif ($value=='Adhoc') {
+ $ri_on_demand = 'checked=checked';
+ }
+ }
+ if ($key=='record_out') {
+ if ($value=='Always') {
+ $ro_always = 'checked=checked';
+ }
+ elseif ($value=='Never') {
+ $ro_never = 'checked=checked';
+ }
+ elseif ($value=='Adhoc') {
+ $ro_on_demand = 'checked=checked';
+ }
+ }
+ }
+
+ $set_callmonitor_text = "
+ <table class='settings'>
+ <tr>
+ <td><h3>" . _("Call Monitor Settings") . "</h3></td>
+ </tr>
+ <tr>
+ <td>" . _("Record INCOMING:") . " </td>
+ <td>
+ <input type='radio' name='record_in' value='Always' " . $ri_always . "/> " . _("Always") . "
+ <input type='radio' name='record_in' value='Never' " . $ri_never . "/> " . _("Never") . "
+ <input type='radio' name='record_in' value='Adhoc' " . $ri_on_demand . "/> " . _("On-Demand") . "
+ </td>
+ </tr>
+ <tr>
+ <td>" . _("Record OUTGOING:") . " </td>
+ <td>
+ <input type='radio' name='record_out' value='Always' " . $ro_always . "/> " . _("Always") . "
+ <input type='radio' name='record_out' value='Never' " . $ro_never . "/> " . _("Never") . "
+ <input type='radio' name='record_out' value='Adhoc' " . $ro_on_demand . "/> " . _("On-Demand") . "
+ </td>
+ </tr>
+ </table>";
+ }
+
+ // javascript enable options
+ if (isset($_SESSION['ari_user']['voicemail_email']) &&
+ in_array('voicemail',array_keys($loaded_modules))) {
+ foreach ($_SESSION['ari_user']['voicemail_email'] as $key => $value) {
+ $var = "voicemail_email_$key";
+ $js_voicemail_email_disable .= "
+ document.ari_settings.$var.disabled = false;";
+ $js_voicemail_email_enable .= "
+ document.ari_settings.$var.disabled = true;";
+ }
+
+ $js_voicemail_script = "
+ if (document.ari_settings.voicemail_email_enable.checked) {
+ document.ari_settings.voicemail_email_address.style.backgroundColor = '#FFF';
+ document.ari_settings.voicemail_email_address.disabled = false;
+ document.ari_settings.voicemail_email_address.value='" . $voicemail_email_address . "';
+ document.ari_settings.voicemail_pager_address.style.backgroundColor = '#FFF';
+ document.ari_settings.voicemail_pager_address.disabled = false;
+ document.ari_settings.voicemail_pager_address.value='" . $voicemail_pager_address . "';
+ " . $js_voicemail_email_disable . "
+ }
+ else {
+ document.ari_settings.voicemail_email_address.style.backgroundColor = '#DDD';
+ document.ari_settings.voicemail_email_address.disabled = true;
+ document.ari_settings.voicemail_pager_address.style.backgroundColor = '#DDD';
+ document.ari_settings.voicemail_pager_address.disabled = true;
+ " . $js_voicemail_email_enable . "
+ }";
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+
+ $headerText = sprintf(_("Phone Settings for %s (%s)"),$displayname,$exten);
+
+ $ret .= $display->displayHeaderText($headerText);
+ $ret .= $display->displayLine();
+
+ $ret .= "
+ <SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function rowCounter(field, maxlimit) {
+ temp = field.value.split('\u000A',maxlimit+1)
+ field.value = temp.join('\u000A')
+ if (temp.length == maxlimit+1) {
+ field.value = field.value.substring(0, field.value.length-1)
+ }
+ }
+
+ function disable_fields() {";
+ $ret .= $js_voicemail_script . "
+ }
+ // End -->
+ </script>";
+
+ $ret .= "
+ " . $setLangText . "
+ <form class='settings' name='ari_settings' action='' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value='action'>
+ <input type=hidden name=a value='update'>
+ <br>
+ " . $set_voicemail_text . "
+ <br>
+ " . $set_callmonitor_text . "
+ <br>
+ <input name='submit' type='submit' value='" . _("Update") . "'>
+ </form>";
+
+ return $ret;
+ }
+
+
+
+
+
+
+ /*
+ * Sets Asterisk call recording setting
+ *
+ * @param $exten
+ * Extension to modify
+ * @param $direction
+ * Call direction
+ * @param $state
+ * State to set to
+ */
+ function setRecordSettings($exten,$state_in,$state_out) {
+
+ global $asterisk_manager_interface;
+
+ if (version_compare($this->getFreePBXVersion(), '1.10', '<')) {
+
+ if ($state_in=="Always") {
+ $type_opt = "put";
+ $value_opt = " " . "ENABLED";
+ }
+ elseif ($state_in=="Never") {
+ $type_opt = "put";
+ $value_opt = " " . "DISABLED";
+ }
+ else {
+ $type_opt = "del";
+ $value_opt = "";
+ }
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database $type_opt RECORD-IN $exten $value_opt\r\n\r\n");
+
+ if ($state_out=="Always") {
+ $type_opt = "put";
+ $value_opt = " " . "ENABLED";
+ }
+ elseif ($state_out=="Never") {
+ $type_opt = "put";
+ $value_opt = " " . "DISABLED";
+ }
+ else {
+ $type_opt = "del";
+ $value_opt = "";
+ }
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database $type_opt RECORD-OUT $exten $value_opt\r\n\r\n");
+ }
+ else {
+
+ $value_opt= "out=".$state_out."|in=".$state_in;
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database put AMPUSER $exten/recording $value_opt\r\n\r\n");
+ }
+ }
+
+ /*
+ * Gets record settings for a protocol
+ *
+ * @param $table
+ * Table to pull information from
+ * @param $exten
+ * Extension to get information about
+ * @return $data
+ * call monitor record settings
+ */
+ function getProtocolRecordSettings($table,$exten) {
+
+ global $asterisk_manager_interface;
+
+ $data = array();
+
+ if (version_compare($this->getFreePBXVersion(), '1.10', '<')) {
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get RECORD-IN $exten\r\n\r\n");
+ if (preg_match("/ENABLED/",$response)) {
+ $data['record_in'] = 'Always';
+ }
+ elseif (preg_match("/DISABLED/",$response)) {
+ $data['record_in'] = 'Never';
+ }
+ else {
+ $data['record_in'] = 'Adhoc';
+ }
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get RECORD-OUT $exten\r\n\r\n");
+ if (preg_match("/ENABLED/",$response)) {
+ $data['record_out'] = 'Always';
+ }
+ elseif (preg_match("/DISABLED/",$response)) {
+ $data['record_out'] = 'Never';
+ }
+ else {
+ $data['record_out'] = 'Adhoc';
+ }
+ }
+ else {
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: database get AMPUSER $exten/recording\r\n\r\n");
+ if (strstr($response,"in=Always")) {
+ $data['record_in'] = 'Always';
+ }
+ elseif (strstr($response,"in=Never")) {
+ $data['record_in'] = 'Never';
+ }
+ else {
+ $data['record_in'] = 'Adhoc';
+ }
+ if (strstr($response,"out=Always")) {
+ $data['record_out'] = 'Always';
+ }
+ elseif (strstr($response,"out=Never")) {
+ $data['record_out'] = 'Never';
+ }
+ else {
+ $data['record_out'] = 'Adhoc';
+ }
+ }
+
+ return $data;
+ }
+
+ /*
+ * Gets record settings
+ *
+ * @param $exten
+ * Extension to get information about
+ * @param $data
+ * Reference to the variable to store the data in
+ */
+ function getRecordSettings($exten) {
+
+ // check protocol tables first
+ $data = $this->getProtocolRecordSettings($this->protocol_table,$exten);
+
+ return $data;
+ }
+
+ /*
+ * Reloads Asterisk Configuration
+ */
+ function reloadAsteriskVoicemail() {
+
+ global $asterisk_manager_interface;
+
+ $response = $asterisk_manager_interface->Command("Action: Command\r\nCommand: Reload app_voicemail.so\r\n\r\n");
+ }
+
+ /*
+ * Gets FreePBX Version
+ */
+ function getFreePBXVersion() {
+
+ if (isset($_SESSION['dbh_asterisk'])) {
+ $sql = "SELECT * FROM admin WHERE variable = 'version'";
+ $results = $_SESSION['dbh_asterisk']->getAll($sql);
+ if(DB::IsError($results)) {
+ $_SESSION['ari_error'] = $results->getMessage();
+ }
+
+ return $results[0][1];
+ }
+ }
+
+ function lookupSetExtensionFormat($exten) {
+
+ if (trim($exten) == "") return $exten;
+
+ $exten = preg_replace("/[^0-9*]/", "", $exten);
+
+ $sql = "SELECT extension FROM users WHERE extension = '".$exten."'";
+ $asa = $_SESSION['dbh_asterisk']->getrow($sql, DB_FETCHMODE_ASSOC);
+ if (!is_array($asa)) {
+ return $exten.'#';
+ } else {
+ return $exten;
+ }
+ }
+
+
+} // class
+
+?>
diff --git a/fs_selfservice/fri/modules/voicemail.module b/fs_selfservice/fri/modules/voicemail.module
new file mode 100644
index 0000000..aad1456
--- /dev/null
+++ b/fs_selfservice/fri/modules/voicemail.module
@@ -0,0 +1,805 @@
+<?php
+
+/**
+ * @file
+ * Functions for the interface to the voicemail recordings
+ */
+
+/**
+ * Class for voicemail
+ */
+class Voicemail {
+
+ /*
+ * rank (for prioritizing modules)
+ */
+ function rank() {
+
+ $rank = 1;
+ return $rank;
+ }
+
+ /*
+ * init
+ */
+ function init() {
+ }
+
+ /*
+ * Adds menu item to nav menu
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navMenu($args) {
+
+ global $ARI_NO_LOGIN;
+
+ // check logout
+ if ($_SESSION['ari_user'] && !$ARI_NO_LOGIN) {
+ $logout = 1;
+ }
+
+ if ($logout!='') {
+ $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=Voicemail&f=display'>" . _("Voicemail") . "</a></small></small></p>";
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Deletes selected voicemails and updates page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function navSubMenu($args) {
+
+ global $ASTERISK_VOICEMAIL_PATH;
+ global $ASTERISK_VOICEMAIL_FOLDERS;
+
+ // args
+ $m = getArgument($args,'m');
+ $q = getArgument($args,'q');
+ $current_folder = getArgument($args,'folder');
+
+ $context = $_SESSION['ari_user']['context'];
+ $extension = $_SESSION['ari_user']['extension'];
+
+ // check for voicemail enabled or admin
+ if ($_SESSION['ari_user']['voicemail_enabled']!=1 ||
+ $extension=='admin') {
+ return;
+ }
+
+ // make folder list
+ $paths = split(';',$ASTERISK_VOICEMAIL_PATH);
+ $i = 0;
+ while ($ASTERISK_VOICEMAIL_FOLDERS[$i]) {
+
+ $f = $ASTERISK_VOICEMAIL_FOLDERS[$i]['folder'];
+ $fn = $ASTERISK_VOICEMAIL_FOLDERS[$i]['name'];
+
+ foreach($paths as $key => $path) {
+
+ $path = appendPath($path,$context);
+ $path = appendPath($path,$extension);
+
+ if (is_dir($path) && is_readable($path)) {
+ $dh = opendir($path);
+ while (false!== ($folder = readdir($dh))) {
+
+ $folder_path = AppendPath($path,$folder);
+
+ if($folder!="." && $folder!=".." &&
+ filetype($folder_path)=='dir') {
+
+ if ($f==$folder) {
+
+ // get message count
+ $indexes = $this->getVoicemailIndex($folder_path,$q,$order,$sort);
+ $record_count = 0;
+ $record_count += $this->getVoicemailCount($indexes);
+
+ // set current folder color
+ $class='';
+ if ($current_folder==$folder ||
+ ($current_folder=='' && $ASTERISK_VOICEMAIL_FOLDERS[0]['folder']==$folder)) {
+ $class = "class='current'";
+ }
+
+ // add folder to list
+ $ret .= "<p><small><small>
+ <a " . $class . " href='" . $_SESSION['ARI_ROOT'] . "?m=Voicemail&q=" . $q . "&folder=" . $f. "'>
+ " . $fn . " (" . $record_count . ")" . "
+ </a>
+ </small></small></p>";
+ }
+ }
+ }
+ }
+ }
+ $i++;
+ }
+
+ return $ret;
+ }
+
+ /*
+ * Acts on the selected voicemails in the method indicated by the action and updates page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function msgAction($args) {
+
+ global $ASTERISK_VOICEMAIL_FOLDERS;
+
+ // args
+ $m = getArgument($args,'m');
+ $a = getArgument($args,'a');
+ $folder = getArgument($args,'folder');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+ $order = getArgument($args,'order');
+ $sort = getArgument($args,'sort');
+
+ // get files
+ $files = array();
+ foreach($_REQUEST as $key => $value) {
+ if (preg_match('/selected/',$key)) {
+ array_push($files, $value);
+ }
+ }
+
+ if ($a=='delete') {
+ $this->deleteVoicemailData($files);
+ }
+ else if ($a=='move_to') {
+ $folder_rx = getArgument($args,'folder_rx');
+ if ($folder_rx=='') {
+ $_SESSION['ari_error']
+ = _("A folder must be selected before the message can be moved.");
+ }
+ else {
+ $context = $_SESSION['ari_user']['context'];
+ $extension = $_SESSION['ari_user']['extension'];
+ $this->moveVoicemailData($files, $context, $extension, $folder_rx);
+ }
+ }
+ else if ($a=='forward_to') {
+
+ $mailbox_rx = getArgument($args,'mailbox_rx');
+ list($context_rx,$extension_rx) = split('/',$mailbox_rx);
+ if ($extension_rx=='') {
+ $_SESSION['ari_error']
+ = _("An extension must be selected before the message can be forwarded.");
+ }
+ else {
+ $folder_rx = $ASTERISK_VOICEMAIL_FOLDERS[0]['folder'];
+ $this->moveVoicemailData($files, $context_rx, $extension_rx, $folder_rx);
+ }
+ }
+
+ // redirect to see updated page
+ $ret .= "
+ <head>
+ <script>
+ <!--
+ window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&folder=" . $folder . "&q=" . $q . "&start=" . $start . "&span=" . $span . "&order=" . $order . "&sort=" . $sort . "\"
+ // -->
+ </script>
+ </head>";
+
+ return $ret;
+ }
+
+ /*
+ * Displays stats page
+ *
+ * @param $args
+ * Common arguments
+ */
+ function display($args) {
+
+ global $ASTERISK_VOICEMAIL_CONF;
+ global $ASTERISK_VOICEMAIL_PATH;
+ global $ASTERISK_VOICEMAIL_FOLDERS;
+ global $AJAX_PAGE_REFRESH_ENABLE;
+
+ $voicemail_audio_format = $_COOKIE['ari_voicemail_audio_format'];
+
+ $display = new DisplaySearch();
+
+ // args
+ $m = getArgument($args,'m');
+ $f = getArgument($args,'f');
+ $q = getArgument($args,'q');
+ $start = getArgument($args,'start');
+ $span = getArgument($args,'span');
+ $order = getArgument($args,'order');
+ $sort = getArgument($args,'sort');
+
+ $start = $start=='' ? 0 : $start;
+ $span = $span=='' ? 15 : $span;
+ $order = $order=='' ? 'calldate' : $order;
+ $sort = $sort=='' ? 'desc' : $sort;
+
+ $paths = split(';',$ASTERISK_VOICEMAIL_PATH);
+
+ $displayname = $_SESSION['ari_user']['displayname'];
+ $extension = $_SESSION['ari_user']['extension'];
+ $context = $_SESSION['ari_user']['context'];
+ $folder = getArgument($args,'folder');
+ if (!$folder) {
+ $folder = $ASTERISK_VOICEMAIL_FOLDERS[0]['folder'];
+ }
+
+ // get data
+ $data = array();
+ foreach($paths as $key => $path) {
+ $path = fixPathSlash($path);
+ $vm_path = $path . "$context/$extension/$folder";
+ $indexes = $this->getVoicemailIndex($vm_path,$q,$order,$sort);
+ $record_count += $this->getVoicemailCount($indexes);
+ $data = array_merge($data,$this->getVoicemailData($indexes,$start,$span));
+ }
+
+ // build controls
+
+ // get the recordings from the asterisk server
+ $filter = '';
+ $recursiveMax = 1;
+ $recursiveCount = 0;
+ $files = array();
+ foreach($paths as $key => $path) {
+ $path_files = GetFiles($path,$filter,$recursiveMax,$recursiveCount);
+ $files = array_merge($files,$path_files);
+ }
+
+ // move options
+ $i=0;
+ while ($ASTERISK_VOICEMAIL_FOLDERS[$i]) {
+ $cf = $ASTERISK_VOICEMAIL_FOLDERS[$i]['folder'];
+ $fn = $ASTERISK_VOICEMAIL_FOLDERS[$i]['name'];
+ if ($cf!=$folder) {
+ $move_options .= "<option VALUE='" . $cf . "'>&nbsp;&nbsp;&nbsp;&nbsp;" . $fn;
+ }
+ $i++;
+ }
+
+ // forward options
+ if (is_readable($ASTERISK_VOICEMAIL_CONF)) {
+ $lines = file($ASTERISK_VOICEMAIL_CONF);
+ $ext_array = array();
+ foreach ($lines as $key => $line) {
+
+ // get context for forward to mailbox
+ if (preg_match("/\[.*\]/i",$line)) {
+ $forwardContext = trim(preg_replace('/\[|\]/', '', $line));
+ }
+
+ // get username and add to options
+ if (preg_match("/\=\>/i",$line)) {
+ list($username,$value) = split('=>',$line);
+ $username = trim($username);
+ if ($username!=$_SESSION['ari_user']['extension']) {
+ //$ext_array[] = $username . "|" . $forwardContext;
+ list(,$real_name,) = split(",",$value,3);
+ $ext_array[] = $real_name . "|" . $username . "|" . $forwardContext;
+ }
+ }
+ } //foreach
+ //sort the array
+ sort($ext_array);
+
+ //get the size of the array
+ $array_size = count($ext_array) - 1;
+
+ //loop through the array and build the drop down list
+ foreach ($ext_array as $item)
+ {
+ //split the values apart
+ list($real_name,$username,$context) = explode("|",$item);
+
+ //add it to the drop down
+ $forward_options .= "<option VALUE='" . $context . "/" . $username . "'>" . substr($real_name,0,15) . " <" . $username . ">";
+ }
+ }
+ else {
+ $_SESSION['ari_error'] = "File not readable: " . $ASTERISK_VOICEMAIL_CONF;
+ return;
+ }
+
+ // table controls
+ $controls = "
+ <button class='infobar' type='submit' onclick=\"document.voicemail_form.a.value='delete'\">
+ " . _("delete") . "
+ </button>
+ <button class='infobar' type='submit' onclick=\"document.voicemail_form.a.value='move_to'\">
+ " . _("move_to") . "
+ </button>
+ <select name='folder_rx' style='width:124px;'>
+ <option VALUE=''>" . _("Folder") . "
+ " . $move_options . "
+ </select>
+ <button class='infobar' type='submit' onclick=\"document.voicemail_form.a.value='forward_to'\">
+ " . _("forward_to") . "
+ </button>
+ <select name='mailbox_rx'>
+ <option VALUE=''>
+ " . $forward_options . "
+ </select>";
+
+ // table header
+ $recording_delete_header = "<th></th>";
+
+ $fields[0]['field'] = "calldate";
+ $fields[0]['text'] = _("Date");
+ $fields[1]['field'] = "calldate";
+ $fields[1]['text'] = _("Time");
+ $fields[2]['field'] = "clid";
+ $fields[2]['text'] = _("Caller ID");
+ $fields[3]['field'] = "priority";
+ $fields[3]['text'] = _("Priority");
+ $fields[4]['field'] = "origmailbox";
+ $fields[4]['text'] = _("Orig Mailbox");
+ $fields[5]['field'] = "duration";
+ $fields[5]['text'] = _("Duration");
+ $i = 0;
+ while ($fields[$i]) {
+
+ $field = $fields[$i]['field'];
+ $text = $fields[$i]['text'];
+ if ($order==$field) {
+ if ($sort=='asc') {
+ $currentSort = 'desc';
+ $arrowImg = "<img src='theme/images/arrow-asc.gif' alt='sort'>";
+ }
+ else {
+ $currentSort = 'asc';
+ $arrowImg = "<img src='theme/images/arrow-desc.gif' alt='sort'>";
+ }
+
+ if ($i==1) {
+ $arrowImg = '';
+ }
+ }
+ else {
+ $arrowImg = '';
+ $currentSort = 'desc';
+ }
+
+ $unicode_q = urlencode($q);
+ $recording_header .= "<th><a href=" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&f=" . $f . "&q=" . $unicode_q . "&order=" . $field . "&sort=" . $currentSort . ">" . $text . $arrowImg . "</a></th>";
+
+ $i++;
+ }
+ $recording_header .= "<th>" . _("Message") . "</th>";
+
+ // table body
+ if (isset($data)) {
+ foreach($data as $file=>$value) {
+
+ // recording popup link
+ $voicemail_audio_format = $voicemail_audio_format=='' ? '.wav' : $voicemail_audio_format;
+ $recording = preg_replace('/.txt/', $voicemail_audio_format, $file);
+ if (is_file($recording)) {
+ $recordingLink = "<a href='#' onClick=\"javascript:popUp('misc/recording_popup.php?recording=" . $recording . "&date=" . $date . "&time=" . $time . "'); return false;\">
+ " . _("play") . "
+ </a>";
+ }
+ else {
+ $_SESSION['ari_error'] = _("Voicemail recording(s) was not found.") . "<br>" .
+ sprintf(_("On settings page, change voicemail audio format. It is currently set to %s"),$voicemail_audio_format);
+ }
+
+ $tableText .= "
+ <tr>
+ <td class='checkbox'><input type=checkbox name='selected" . ++$i . "' value=" . $file . "></td>
+ <td width=68>" . GetDateFormat($value['origtime']) . "</td>
+ <td>" . GetTimeFormat($value['origtime']) . "</td>
+ <td width=100>" . $value[callerid] . "</td>
+ <td>" . $value[priority] . "</td>
+ <td width=90>" . $value[origmailbox] . "</td>
+ <td>" . $value[duration] . " sec</td>
+ <td>" . $recordingLink . "</td>
+ </tr>";
+ }
+ }
+
+ // options
+ $url_opts = array();
+ $url_opts['folder'] = $folder;
+ $url_opts['sort'] = $sort;
+ $url_opts['order'] = $order;
+
+ $error = 0;
+
+ // check for voicemail enabled
+ if ($_SESSION['ari_user']['voicemail_enabled']!=1) {
+ $_SESSION['ari_error'] = _("Voicemail Login not found.") . "<br>" .
+ _("No access to voicemail");
+ $error = 1;
+ }
+
+ // check admin
+ if ($extension=='admin') {
+ $_SESSION['ari_error'] = _("No Voicemail Recordings for Admin");
+ $error = 1;
+ }
+
+ // build page content
+ $ret .= checkErrorMessage();
+ if ($error) {
+ return $ret;
+ }
+
+ // ajax page refresh script
+ if ($AJAX_PAGE_REFRESH_ENABLE) {
+// $ret .= ajaxRefreshScript($args);
+ }
+
+ // header
+ $ret .= $display->displayHeaderText(sprintf(_("Voicemail for %s (%s)"),$displayname,$extension));
+ $ret .= $display->displaySearchBlock('left',$m,$q,$url_opts,true);
+
+ // start form
+ $ret .= "
+ <form name='voicemail_form' action='" . $_SESSION['ARI_ROOT'] . "' method='GET'>
+ <input type=hidden name=m value=" . $m . ">
+ <input type=hidden name=f value=msgAction>
+ <input type=hidden name=a value=''>
+ <input type=hidden name=q value=" . $q . ">
+ <input type=hidden name=folder value=" . $folder . ">
+ <input type=hidden name=start value=" . $start . ">
+ <input type=hidden name=span value=" . $span . ">
+ <input type=hidden name=order value=" . $order . ">
+ <input type=hidden name=sort value=" . $sort . ">";
+
+ $ret .= $display->displayInfoBarBlock($controls,$q,$start,$span,$record_count);
+
+ // add javascript for popup and message actions
+ $ret .= "
+ <SCRIPT LANGUAGE='JavaScript'>
+ <!-- Begin
+ function popUp(URL) {
+ popup = window.open(URL, 'play', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=324,height=110');
+ }
+
+ function checkAll(form,set) {
+ var elem = 0;
+ var i = 0;
+ while (elem = form.elements[i]) {
+ if (set) {
+ elem.checked = true;
+ } else {
+ elem.checked = false;
+ }
+ i++;
+ }
+ return true;
+ }
+ // End -->
+ </script>";
+
+ // voicemail delete recording controls
+ $ret .= "
+ <table>
+ <tr>
+ <td>
+ <small>" . _("select") . ": </small>
+ <small><a href='' OnClick=\"checkAll(document.voicemail_form,true); return false;\">" . _("all") . "</a></small>
+ <small><a href='' OnClick=\"checkAll(document.voicemail_form,false); return false;\">" . _("none") . "</a></small>
+ </td>
+ </tr>
+ </table>";
+
+ // table
+ $ret .= "
+ <table class='voicemail'>
+ <tr>
+ " . $recording_delete_header . "
+ " . $recording_header . "
+ </tr>
+ " . $tableText . "
+ </table>";
+
+ // end form
+ $ret .= "</form>";
+
+ $ret .= $display->displaySearchBlock('center',$m,$q,$url_opts,false);
+ $ret .= $display->displayNavigationBlock($m,$q,$url_opts,$start,$span,$record_count);
+
+ return $ret;
+ }
+
+ /*
+ * Gets voicemail data
+ *
+ * @param $data
+ * Reference to the variable to store the data in
+ * @param $q
+ * search string
+ */
+ function getVoicemailIndex($path,$q,$order,$sort) {
+
+ $indexes = array();
+
+ $filter = '.txt';
+ $recursiveMax = 0;
+ $recursiveCount = 0;
+ $files = getFiles($path,$filter,$recursiveMax,$recursiveCount);
+
+ if (isset($files)) {
+
+ // ugly, but sorts array by time stamp
+ foreach ($files as $file) {
+
+ if (is_file($file)) {
+
+ $lines = file($file);
+ foreach ($lines as $key => $line) {
+ unset($value);
+ list($key,$value) = split('=',$line);
+ if ($value) {
+
+ if ($key=="origtime") {
+ $calldate = $value;
+ $date = GetDateFormat($value);
+ $time = GetTimeFormat($value);
+ }
+ if ($key=="callerid") {
+ $callerid = $value;
+ }
+ if ($key=="priority") {
+ $priority = $value;
+ }
+ if ($key=="origmailbox") {
+ $origmailbox = $value;
+ }
+ if ($key=="duration") {
+ $duration = (int)$value;
+ }
+ }
+ }
+
+ // search filter
+ $found = 1;
+ if ($q) {
+
+ $found = 0;
+
+ if (preg_match("/" . $q . "/", $origmailbox) ||
+ preg_match("/" . $q . "/", $callerid) ||
+ preg_match("/" . $q . "/", $date) ||
+ preg_match("/" . $q . "/", $time)) {
+ $found = 1;
+ }
+ }
+ }
+
+ // add to index
+ if ($found) {
+ $indexes[$file] = $$order;
+ }
+ }
+
+ if (count($indexes)) {
+ if ($sort=='desc') {
+ arsort($indexes);
+ }
+ else {
+ asort($indexes);
+ }
+ }
+ }
+
+ return $indexes;
+ }
+
+ /*
+ * Deletes selected voicemails
+ *
+ * @param $files
+ * Array of files to delete
+ */
+ function deleteVoicemailData($files) {
+
+ foreach($files as $key => $path) {
+
+ // get file parts for search
+ $path_parts = pathinfo($path);
+ $path = fixPathSlash($path_parts['dirname']);
+
+ list($name,$ext) = split("\.",$path_parts['basename']);
+
+ // delete all related files using a wildcard
+ if (is_dir($path)) {
+ $hdl = opendir($path);
+ while ($fn = readdir($hdl)) {
+ if (preg_match("/" . $name ."/",$fn)) {
+ $file = $path . $fn;
+ unlink($file);
+ }
+ }
+ closedir($hdl);
+ }
+ }
+ }
+
+ /*
+ * Moves selected voicemails to a specified folder
+ *
+ * @param $files
+ * Array of files to delete
+ * @param $extension_rx
+ * Mailbox to move message to
+ * @param $folder_rx
+ * Folder to move the messages to
+ */
+ function moveVoicemailData($files,$context_rx,$extension_rx,$folder_rx) {
+
+ global $ASTERISK_VOICEMAIL_PATH;
+
+ $perm = fileperms($ASTERISK_VOICEMAIL_PATH);
+ $uid = fileowner($ASTERISK_VOICEMAIL_PATH);
+ $gid = filegroup($ASTERISK_VOICEMAIL_PATH);
+
+ // recieving path
+ $paths = split(';',$ASTERISK_VOICEMAIL_PATH);
+ $path_rx = appendPath($paths[0],$context_rx);
+ if (!is_dir($path_rx)) {
+ mkdir($path_rx, $perm);
+ chown($path_rx,intval($uid));
+ chgrp($path_rx,intval($gid));
+ }
+ $path_rx = appendPath($path_rx,$extension_rx);
+ if (!is_dir($path_rx)) {
+ mkdir($path_rx, $perm);
+ chown($path_rx,intval($uid));
+ chgrp($path_rx,intval($gid));
+ }
+ $path_rx = appendPath($path_rx,$folder_rx);
+ if (!is_dir($path_rx)) {
+ mkdir($path_rx, $perm);
+ chown($path_rx,intval($uid));
+ chgrp($path_rx,intval($gid));
+ }
+
+ // get recieving folder last message number
+ if (is_dir($path_rx)) {
+
+ $lastNum = -1;
+ $lastNumLen = 4;
+
+ $dh = opendir($path_rx);
+ while (false != ($filename = readdir($dh))) {
+ if($filename!="." && $filename!="..") {
+
+ $msg_path = $path_rx;
+ $msg_path = appendPath($msg_path,$filename);
+ if (is_file($msg_path)) {
+ $path_parts = pathinfo($msg_path);
+ $num = preg_replace("/[a-zA-Z]|\./",'', $path_parts['basename']);
+ if ($num > $lastNum) {
+ $lastNum = $num;
+ $lastNumLen = strlen($lastNum);
+ }
+ }
+ }
+ }
+ }
+ else {
+ $_SESSION['ari_error'] = sprintf(_("Could not create mailbox folder %s on the server"),$folder_rx);
+ return;
+ }
+
+ // copy files to new location, incrementing each message number
+ asort($files);
+ foreach($files as $key => $path) {
+
+ // get file parts for search
+ $path_parts = pathinfo($path);
+ $path = $path_parts['dirname'];
+ $path = fixPathSlash($path);
+ list($name,$ext) = split("\.",$path_parts['basename']);
+ if (is_dir($path)) {
+
+ $lastNum++;
+ $hdl = opendir($path);
+ while ($fn = readdir($hdl)) {
+ if (preg_match("/" . $name . "/",$fn)) {
+ $src = $path . $fn;
+ $path_parts = pathinfo($src);
+ $folder_rx = preg_replace("/\d+/",sprintf("%0" . $lastNumLen . "d",$lastNum),$path_parts['basename']);
+ $dst = appendPath($path_rx,$folder_rx);
+ if (is_writable($src) && is_writable($path_rx)) {
+
+ $perm = fileperms($src);
+ $uid = fileowner($src);
+ $gid = filegroup($src);
+
+ copy($src,$dst);
+
+ if (is_writable($dst)) {
+ chmod($dst, $perm);
+ chown($dst,intval($uid));
+ chgrp($dst,intval($gid));
+ }
+
+ unlink($src);
+ }
+ else {
+ $_SESSION['ari_error'] = sprintf(_("Permission denied on folder %s or %s"),$src,$path_rx);
+ return;
+ }
+ }
+ }
+ closedir($hdl);
+ }
+ }
+ }
+
+ /*
+ * Gets voicemail record count
+ *
+ * @param $indexes
+ * array of files to be counted
+ * @return $count
+ * number of cdr records counted
+ */
+ function getVoicemailCount($indexes) {
+
+ $count = count($indexes);
+
+ return $count;
+ }
+
+ /*
+ * Gets voicemail data
+ *
+ * @param $indexes
+ * array of voicemail files
+ * @param $start
+ * message number to start page with
+ * @param $span
+ * number of messages to display on page
+ * @param $data
+ * Reference to the variable to store the data in
+ */
+ function getVoicemailData($indexes,$start,$span) {
+
+ $data = array();
+
+ if (!isset($indexes)) {
+ return;
+ }
+
+ // populate array
+ $i = 0;
+ foreach ($indexes as $file => $index) {
+ if ($i>$start-1+$span) {
+ return $data;
+ }
+ elseif ($i>$start-1 && $i<$start+$span) {
+ $lines = file($file);
+ foreach ($lines as $key => $line) {
+ unset($value);
+ list($key,$value) = split('=',$line);
+ if ($value) {
+ $data[$file][$key] = $value;
+ }
+ }
+ }
+ $i++;
+ }
+
+ return $data;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/fs_selfservice/fri/theme/global.css b/fs_selfservice/fri/theme/global.css
new file mode 100644
index 0000000..cd97aa2
--- /dev/null
+++ b/fs_selfservice/fri/theme/global.css
@@ -0,0 +1,87 @@
+/*
+ * Global Styles
+ */
+
+body {
+ color: #333;
+ background-color: white;
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+}
+
+div {
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+}
+
+h2 {
+ font-size: 1.2em;
+ font-family: "Trebuchet MS", Arial, Helvetica, Tahoma, Verdana, sans-serif;
+ margin-top: 0;
+ margin-bottom: 0;
+ color: #555;
+}
+
+h3 {
+ font-size: 1em;
+ margin-top: 1.5em;
+ font-family: "Trebuchet MS", Arial, Helvetica, Tahoma, Verdana, sans-serif;
+ margin-top: 0;
+ margin-bottom: 0;
+ color: #555;
+}
+
+
+h4 {
+ font-family: "Trebuchet MS", Arial, Helvetica, Tahoma, Verdana, sans-serif;
+ margin-top: 0;
+ margin-bottom: 0;
+ color: #555;
+ margin-top: 1.5em
+}
+
+
+
+sup {
+ font-size: 9px
+}
+
+small small {
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+ font-weight: bold;
+}
+
+
+
+/***** info popups *****/
+a.info {
+ position:relative;
+ color:black;
+ border-bottom:1px dashed #ccc;
+}
+/* Added to solve the z-order problem of IE
+*/
+a.info:hover {
+ background-color: #FFA178;
+ z-index:2;
+}
+/* End */
+a.info span{
+ display: none;
+ background-color: #FFA178;
+}
+a.info:hover span{
+ display:block;
+ position:absolute;
+ z-index:1;
+ top:2em;
+ left:-10em;
+ width:25em;
+ border:1px solid #F2AF1D;
+ background-color:#FDF1D5;
+ color:#000;
+ text-align:justify;
+ font-size:10px;
+ font-weight:normal;
+ padding:3px;
+ line-height:15px;
+}
+
diff --git a/fs_selfservice/fri/theme/header.css b/fs_selfservice/fri/theme/header.css
new file mode 100644
index 0000000..1c28e7a
--- /dev/null
+++ b/fs_selfservice/fri/theme/header.css
@@ -0,0 +1,83 @@
+/*
+ * Header
+ */
+
+/* Header */
+
+#ariHeader {
+ position: relative;
+ background: #105D90;
+ height: 72px;
+ margin: 0;
+ padding: 0;
+ clear: both;
+}
+
+#ariHeader span.left {
+ position: relative;
+ height: 72px;
+ border: 0px;
+ padding: 0px;
+ margin: 0px;
+ float: left;
+}
+
+#ariHeader img {
+ border: 0px;
+}
+
+#ariHeader span.right {
+ height: 72px;
+ border: 0px;
+ padding: 0px;
+ margin: 0px;
+ float: right;
+}
+
+#ariHeader img {
+ border: 0px;
+}
+
+/* Topnav */
+
+#topnav {
+ width: 100%;
+ height: 36px;
+ border: 0;
+ padding: 0;
+ margin-top: -1px; /* stupid browser hack */
+ color: #999;
+ background-color: #333;
+}
+
+#topnav span.left {
+ float: left;
+ text-align: left;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+
+#topnav span.right {
+ float: right;
+ text-align: right;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+
+.topnav small b {
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+ font-weight: bold;
+ background-color: #105D90;
+}
+
+/* Headerspacer */
+
+#headerspacer {
+ border: 0;
+ padding: 0;
+ margin-top: -16px; /* stupid browser hack */
+ background-color: #fff;
+ height: 16px
+} \ No newline at end of file
diff --git a/fs_selfservice/fri/theme/iefixes.css b/fs_selfservice/fri/theme/iefixes.css
new file mode 100644
index 0000000..a7939a4
--- /dev/null
+++ b/fs_selfservice/fri/theme/iefixes.css
@@ -0,0 +1,16 @@
+/*
+ * IE Fixes
+ */
+
+/*Win IE fix \*/
+* html .minwidth { border-left: 760px solid #fff; position: relative; float: left; z-index: 1; }
+
+/*End Win IE fix*/
+
+/*Win IE fix \*/
+* html .container { margin-left: -760px; position: relative; float: left; z-index :2; }
+/*End Win IE fix*/
+
+
+
+
diff --git a/fs_selfservice/fri/theme/images/arrow-asc.gif b/fs_selfservice/fri/theme/images/arrow-asc.gif
new file mode 100644
index 0000000..46a5848
--- /dev/null
+++ b/fs_selfservice/fri/theme/images/arrow-asc.gif
Binary files differ
diff --git a/fs_selfservice/fri/theme/images/arrow-desc.gif b/fs_selfservice/fri/theme/images/arrow-desc.gif
new file mode 100644
index 0000000..6f4e5e6
--- /dev/null
+++ b/fs_selfservice/fri/theme/images/arrow-desc.gif
Binary files differ
diff --git a/fs_selfservice/fri/theme/layout.css b/fs_selfservice/fri/theme/layout.css
new file mode 100644
index 0000000..a398714
--- /dev/null
+++ b/fs_selfservice/fri/theme/layout.css
@@ -0,0 +1,420 @@
+/*
+ * Layout
+ */
+
+/* Page */
+
+#page {
+ background-color: white;
+ text-align: left;
+ min-width: 760px;
+}
+
+/* main */
+
+#main {
+ min-width: 760px;
+ float: left;
+}
+
+#main span.left {
+ float: left;
+}
+
+#main span.right {
+ float: left;
+}
+
+/* Center */
+
+#center {
+ float: left;
+ margin-bottom: 20px;
+}
+
+/* Login */
+
+#login {
+ margin: 0;
+ padding: 0;
+}
+#login p {
+ font-size: 0.7em;
+}
+table#login {
+ width: 600px;
+ border: 0px;
+}
+table#login td.right {
+ text-align: right;
+ width: 20%;
+}
+table#login td.left {
+ text-align: left;
+}
+table#login td.small {
+ font-size: 0.7em;
+}
+table#login_text {
+ margin-left: 60px;
+ font-size: 0.8em;
+ text-align: left;
+}
+
+/* i18n lang */
+
+.lang {
+ display: inline;
+ font-size: 0.8em;
+ margin: 0;
+ padding: 0;
+}
+.lang_code {
+ margin: 0;
+ padding: 0;
+ width: 10em;
+}
+
+/* Line */
+
+#line {
+ min-width: 604px;
+ border: 1px solid #333;
+ padding: 0;
+ margin: 0;
+ color: #999;
+ background-color: #333;
+ height: 1px;
+}
+#line span.left {
+ float: left;
+ text-align: left;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+#line span.right {
+ float: right;
+ text-align: right;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+
+/* Navbar */
+
+#navbar {
+ width: 604px;
+ height: 24px;
+ border: 1px;
+ padding: 0;
+ margin-bottom: 0;
+ color: #fff;
+ background-color: #333;
+}
+#navbar span.left {
+ margin: 2px;
+ float: left;
+ text-align: left;
+ font-weight: bold;
+ vertical-align: middle;
+ width: 49%;
+}
+#navbar span.right {
+ margin: 2px;
+ float: right;
+ text-align: right;
+ font-weight: bold;
+ vertical-align: middle;
+ width: 49%;
+}
+
+/* Info Bar */
+
+#info_bar {
+ min-width: 604px;
+ border: 1px solid #333;
+ padding: 3px;
+ margin-top: -1px; /* stupid browser hack */
+ color: #999;
+ background-color: #333;
+ height: 20px;
+}
+#info_bar span.left {
+ float: left;
+ text-align: left;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+#info_bar span.right {
+ float: right;
+ text-align: right;
+ font-weight: bold;
+ color: #fff;
+ width: 49%;
+}
+.info_bar a:link {
+ color: white;
+ text-decoration: none;
+}
+.info_bar a:active, a:link {
+ color: #105D90;
+}
+.info_bar a:hover {
+ color: #fc0;
+}
+.info_bar small b {
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+ font-weight: bold;
+}
+input.infoBar {
+ font-size: 11px;
+ padding: 0px;
+ height: 22px;
+}
+
+/* bars */
+
+.bar {
+ margin: 0;
+}
+
+.bar_left {
+ width: 604px;
+ margin: 0 0 16px 0;
+ padding: 0;
+}
+
+.bar_center {
+ width: 604px;
+ text-align: center;
+ margin: 0 0 16px 0;
+ padding: 0;
+}
+.bar_center a:active, .bar_center a:hover {
+ color: red;
+}
+
+/* Subheader */
+
+#subheader {
+ padding: 0px;
+ margin: 0px;
+ margin-bottom: 16px;
+}
+
+/* servBodL */
+
+.servBodL {
+ border-left: 1px dotted #CEDCEA;
+}
+
+/* Callmonitor */
+
+table.callmonitor {
+ border: 1px #6699CC solid;
+ border-collapse: collapse;
+ border-spacing: 0px;
+ margin: 0 0 16px 0;
+ width: 604px;
+}
+table.callmonitor th {
+ background-color: #BEC8D1;
+ border: 1px solid #6699CC;
+ border-bottom: 2px solid #6699CC;
+ text-align: center;
+ font-family: Verdana;
+ font-weight: bold;
+ font-size: 0.7em;
+ color: #404040;
+}
+table.callmonitor th a {
+ color: #404040;
+}
+table.callmonitor img {
+ border: 0;
+}
+table.callmonitor td {
+ background-color: white;
+ border: 1px solid #6699CC;
+ color: #404040;
+ font-family: Verdana, sans-serif, Arial;
+ font-weight: normal;
+ font-size: 0.7em;
+ padding: 3px;
+ text-align: center;
+}
+table.callmonitor td.checkbox {
+ padding: 1px;
+}
+
+/* Voicemail */
+
+.voicemail {
+ margin: 0px;
+}
+table.voicemail {
+ border: 1px #6699CC solid;
+ border-collapse: collapse;
+ border-spacing: 0px;
+ margin: 0 0 16px 0;
+ width: 604px;
+}
+table.voicemail th {
+ background-color: #BEC8D1;
+ border: 1px solid #6699CC;
+ border-bottom: 2px solid #6699CC;
+ text-align: center;
+ font-family: Verdana;
+ font-weight: bold;
+ font-size: 0.7em;
+ color: #404040;
+}
+table.voicemail th a {
+ color: #404040;
+}
+table.voicemail img {
+ border: 0;
+}
+table.voicemail td {
+ background-color: white;
+ border: 1px solid #6699CC;
+ color: #404040;
+ font-family: Verdana, sans-serif, Arial;
+ font-weight: normal;
+ font-size: 0.7em;
+ padding: 3px;
+ text-align: center;
+}
+table.voicemail td.checkbox {
+ padding: 1px;
+}
+
+/* Help */
+
+.help {
+ margin: 0px;
+}
+table.help {
+ border: 1px #6699CC solid;
+ border-collapse: collapse;
+ border-spacing: 0px;
+ margin: 0 0 16px 0;
+}
+table.help th {
+ background-color: #BEC8D1;
+ border: 1px solid #6699CC;
+ border-bottom: 2px solid #6699CC;
+ font-family: Verdana;
+ font-weight: bold;
+ font-size: 0.7em;
+ color: #404040;
+}
+table.help th.feature_codes {
+ text-align: center;
+ width: 9em;
+}
+table.help th a {
+ color: #404040;
+}
+table.help img {
+ border: 0;
+}
+table.help td {
+ background-color: white;
+ border: 1px solid #6699CC;
+ color: #404040;
+ font-family: Verdana, sans-serif, Arial;
+ font-weight: normal;
+ font-size: 0.7em;
+ padding: 3px;
+}
+table.help td.feature_codes {
+ text-align: center;
+}
+table.help td.checkbox {
+ padding: 1px;
+}
+
+/* Settings */
+
+.settings {
+ font-family: Verdana, sans-serif, Arial;
+ font-weight: normal;
+ font-size: 0.9em;
+ padding: 0;
+ margin: 0;
+}
+table.settings {
+ font-family: Verdana;
+ color: #404040;
+ border-collapse: collapse;
+ border-spacing: 0px;
+ padding-bottom: 3px;
+}
+table.settings td {
+ color: #404040;
+ background-color: white;
+ padding: 3px;
+}
+table.settings td.note {
+ color: #105D90;
+}
+
+/* Footer */
+
+#ariFooter {
+ color: #999;
+ margin-left: 148px;
+ font-size: 10px;
+ overflow: auto;
+/* width: 100%; */
+ clear: both;
+}
+
+#ariFooter a {
+ text-decoration: none;
+ color: #999;
+}
+
+#ariFooter a:hover {
+ text-decoration: underline;
+ color: #105D90;
+}
+
+#ariFooter a:link {
+ text-decoration: none;
+ color: #999;
+}
+
+/* Misc */
+
+.ariClearBoth {
+ clear: both;
+ margin: 0;
+ padding: 0;
+}
+
+.ariBlockHide {
+ display: none;
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ position: absolute; /* IE5 Mac */
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fs_selfservice/fri/theme/logo.gif b/fs_selfservice/fri/theme/logo.gif
new file mode 100644
index 0000000..b2d23d7
--- /dev/null
+++ b/fs_selfservice/fri/theme/logo.gif
Binary files differ
diff --git a/fs_selfservice/fri/theme/main.css b/fs_selfservice/fri/theme/main.css
new file mode 100644
index 0000000..6b9ba94
--- /dev/null
+++ b/fs_selfservice/fri/theme/main.css
@@ -0,0 +1,13 @@
+/*
+ * Main
+ */
+
+@import url("global.css");
+@import url("text.css");
+@import url("layout.css");
+@import url("header.css");
+@import url("navigation.css");
+
+@import url("iefixes.css");
+
+
diff --git a/fs_selfservice/fri/theme/navigation.css b/fs_selfservice/fri/theme/navigation.css
new file mode 100644
index 0000000..907851b
--- /dev/null
+++ b/fs_selfservice/fri/theme/navigation.css
@@ -0,0 +1,166 @@
+/*
+ * Navigation
+ */
+
+/* Menu */
+
+#menu {
+ width: 148px;
+ float: left;
+}
+
+/* Nav */
+
+.nav {
+ font-weight: bold;
+ color: #105D90;
+ margin-right: 20px;
+}
+
+.nav p {
+ margin: 0px;
+ padding-top: 2px;
+ padding-bottom: 3px;
+ background: #FFF;
+}
+
+.nav a:visited {
+ color: #105D90;
+}
+
+.sub {
+ margin-left: 1em;
+}
+
+.navtext {
+ margin-left: 20px;
+}
+
+.nav_b1 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display: block;
+ background: #EEE;
+ margin:0 5px;
+}
+
+.nav_b2 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display: block;
+ background: #FFF;
+ border-right: 2px solid #EEE;
+ border-left: 2px solid #EEE;
+ margin:0 3px;
+}
+
+.nav_b3 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display:block;
+ background: #FFF;
+ border-right: 1px solid #EEE;
+ border-left: 1px solid #EEE;
+ margin: 0 2px;
+}
+
+.nav_b4 {
+ height: 2px;
+ font-size: 1px;
+ overflow: hidden;
+ display:block;
+ background: #FFF;
+ border-right: 1px solid #EEE;
+ border-left:1px solid #EEE;
+ margin:0 1px;
+}
+
+#nav_menu {
+ background: #FFF;
+ border-right: 1px solid #EEE;
+ border-left: 1px solid #EEE;
+ padding-left: 0.75em;
+}
+
+/* Subnav */
+
+.subnav {
+ font-weight: bold;
+ color: #105D90;
+ margin-right: 20px;
+}
+
+.subnav p {
+ margin: 0px;
+ padding-top: 2px;
+ padding-bottom: 3px;
+ background: #BEC8D1;
+}
+
+.subnav a:visited {
+ color: #105D90;
+}
+
+.subnav a.current, a:visited.current {
+ color: #404040;
+}
+
+.subnav_b1 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display: block;
+ background: #aaa;
+ margin:0 5px;
+}
+
+.subnav_b2 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display: block;
+ background: #BEC8D1;
+ border-right: 2px solid #aaa;
+ border-left: 2px solid #aaa;
+ margin:0 3px;
+}
+
+.subnav_b3 {
+ height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+ display:block;
+ background: #BEC8D1;
+ border-right: 1px solid #aaa;
+ border-left: 1px solid #aaa;
+ margin: 0 2px;
+}
+
+.subnav_b4 {
+ height: 2px;
+ font-size: 1px;
+ overflow: hidden;
+ display:block;
+ background: #BEC8D1;
+ border-right: 1px solid #aaa;
+ border-left:1px solid #aaa;
+ margin:0 1px;
+}
+
+.subnav_title {
+ font-weight: normal;
+ color: #105D90;
+ font-size: 12px;
+ padding-left: 1em;
+}
+
+#subnav_menu {
+ background: #BEC8D1;
+ border-right: 1px solid #aaa;
+ border-left: 1px solid #aaa;
+ padding-left: 1.25em;
+}
+
diff --git a/fs_selfservice/fri/theme/page.tpl.php b/fs_selfservice/fri/theme/page.tpl.php
new file mode 100644
index 0000000..9d54659
--- /dev/null
+++ b/fs_selfservice/fri/theme/page.tpl.php
@@ -0,0 +1,78 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <TITLE>User Portal</TITLE>
+ <link rel="stylesheet" href="theme/main.css" type="text/css">
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ </head>
+ <body>
+ <div id="page">
+ <div class="minwidth">
+ <div class="container">
+ <div id="topnav">
+ <div class="spacer"></div>
+ <span class="left">
+ </span>
+ <div class="spacer"></div>
+ </div>
+ <div id="headerspacer"><img src="theme/spacer.gif" alt=""></div>
+ <div id="main">
+ <div class="minwidth">
+ <div class="container">
+ <div class="spacer"></div>
+ <span class="left">
+ <div id="menu">
+ <div><img height=4 src="theme/spacer.gif" alt=""></div>
+ <div class="nav">
+ <?php if ($nav_menu != '') { ?>
+ <b class='nav_b1'></b><b class='nav_b2'></b><b class='nav_b3'></b><b class='nav_b4'></b>
+ <div id='nav_menu'>
+ <?php print($nav_menu) ?>
+ </div>
+ <b class='nav_b4'></b><b class='nav_b3'></b><b class='nav_b2'></b><b class='nav_b1'></b>
+ <?php } ?>
+ </div>
+ <div><img height=14 src="theme/spacer.gif" alt=""></div>
+ <?php if ($subnav_menu != '') { ?>
+ <div class="subnav">
+ <div class="subnav_title"><?php echo _("Folders")?>:</div>
+ <b class='subnav_b1'></b><b class='subnav_b2'></b><b class='subnav_b3'></b><b class='subnav_b4'></b>
+ <div id='subnav_menu'>
+ <?php print($subnav_menu) ?>
+ </div>
+ <b class='subnav_b4'></b><b class='subnav_b3'></b><b class='subnav_b2'></b><b class='subnav_b1'></b>
+ </div>
+ <?php } ?>
+ </div>
+ </span>
+ <span class="right">
+ <div id="center">
+ <?php if ($login != "") { ?>
+ <?php print($login) ?>
+ <?php } ?>
+ <div id="content">
+ <!-- begin main content -->
+ <?php print($content) ?>
+ <!-- end main content -->
+ </div>
+ </div>
+ </span>
+ <div class="spacer"></div>
+ </div>
+ </div>
+ </div>
+ <!--begin footer-->
+ <div id="ariFooter">
+ <small>
+ <!--&nbsp;&nbsp;<?php print($ari_version) ?> <?php echo _("Version")?><br> -->
+ Freeside Recording Interface (c) 2008 Freeside Internet Services, Inc.<br>
+ <a href="http<?php print(isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=''?'s':''); ?>://www.littlejohnconsulting.com">Based on ARI from Littlejohn Consulting</a>
+ </small>
+ </div>
+ <!-- end footer -->
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
+
diff --git a/fs_selfservice/fri/theme/spacer.gif b/fs_selfservice/fri/theme/spacer.gif
new file mode 100644
index 0000000..8f09684
--- /dev/null
+++ b/fs_selfservice/fri/theme/spacer.gif
Binary files differ
diff --git a/fs_selfservice/fri/theme/text.css b/fs_selfservice/fri/theme/text.css
new file mode 100644
index 0000000..9625ca0
--- /dev/null
+++ b/fs_selfservice/fri/theme/text.css
@@ -0,0 +1,10 @@
+/*
+ * Text
+ */
+
+/* Error */
+
+.error {
+ color: #CC3333;
+}
+
diff --git a/fs_selfservice/fri/version.php b/fs_selfservice/fri/version.php
new file mode 100644
index 0000000..7f313a1
--- /dev/null
+++ b/fs_selfservice/fri/version.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * version
+ */
+
+$ARI_VERSION = 'FreePBX 2.3';
+
+?>
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_selfservice/java/biz/freeside/SelfService.java b/fs_selfservice/java/biz/freeside/SelfService.java
new file mode 100755
index 0000000..752815a
--- /dev/null
+++ b/fs_selfservice/java/biz/freeside/SelfService.java
@@ -0,0 +1,52 @@
+package biz.freeside;
+
+// see http://ws.apache.org/xmlrpc/client.html for these classes
+import org.apache.xmlrpc.XmlRpcException;
+import org.apache.xmlrpc.client.XmlRpcClient;
+import org.apache.xmlrpc.client.XmlRpcClientConfig;
+import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.net.URL;
+
+public class SelfService extends XmlRpcClient {
+
+ public SelfService( String url ) throws Exception {
+ super();
+ XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
+ config.setServerURL(new URL( url ));
+ this.setConfig(config);
+ }
+
+ private String canonicalMethod ( String method ) {
+ String canonical = new String(method);
+ if (!canonical.startsWith( "FS.SelfService.XMLRPC." )) {
+ canonical = "FS.SelfService.XMLRPC." + canonical;
+ }
+ return canonical;
+ }
+
+ private HashMap testResponse ( Object toTest ) throws XmlRpcException {
+ if (! ( toTest instanceof HashMap )) {
+ throw new XmlRpcException("expected HashMap but got" + toTest.getClass());
+ }
+ return (HashMap) toTest;
+ }
+
+ public HashMap execute( String method, List params ) throws XmlRpcException {
+ return testResponse(super.execute( canonicalMethod(method), params ));
+ }
+
+ public HashMap execute( String method, Object[] params ) throws XmlRpcException {
+ return testResponse(super.execute( canonicalMethod(method), params ));
+ }
+
+ public HashMap execute( XmlRpcClientConfig config, String method, List params ) throws XmlRpcException {
+ return testResponse(super.execute( config, canonicalMethod(method), params ));
+ }
+
+ public HashMap execute( XmlRpcClientConfig config, String method, Object[] params ) throws XmlRpcException {
+ return testResponse(super.execute( config, canonicalMethod(method), params ));
+ }
+}
diff --git a/fs_selfservice/java/freeside_create_ticket_example.java b/fs_selfservice/java/freeside_create_ticket_example.java
new file mode 100755
index 0000000..759a4a6
--- /dev/null
+++ b/fs_selfservice/java/freeside_create_ticket_example.java
@@ -0,0 +1,85 @@
+
+import biz.freeside.SelfService;
+import org.apache.commons.logging.impl.SimpleLog; //included in apache xmlrpc
+import java.util.HashMap;
+import java.util.Vector;
+
+public class freeside_create_ticket_example {
+ private static SimpleLog logger = new SimpleLog("SelfService");
+
+ public static void main( String args[] ) throws Exception {
+ SelfService client =
+ new SelfService( "http://192.168.1.221:8081/xmlrpc.cgi" );
+
+ Vector params = new Vector();
+ params.addElement( "username" );
+ params.addElement( "4155551212" ); // svc_phone.phonenum
+ params.addElement( "password" );
+ params.addElement( "5454" ); // svc_phone.pin
+ params.addElement( "domain" );
+ params.addElement( "svc_phone" );
+ HashMap result = client.execute( "login", params );
+
+ String error = (String) result.get("error");
+
+ if (error.length() < 1) {
+
+ // successful login
+
+ String sessionId = (String) result.get("session_id");
+
+ logger.trace("[login] logged into freeside with session_id="+sessionId);
+
+ // store session id in your session store to be used for other calls
+
+ // like, say, this one to create a ticket
+
+ Vector ticket_params = new Vector();
+ ticket_params.addElement( "session_id" );
+ ticket_params.addElement( sessionId );
+ ticket_params.addElement( "queue" );
+ ticket_params.addElement( 3 ); // otherwise defaults to
+ // ticket_system-selfservice_queueid
+ // or ticket_system-default_queueid
+ ticket_params.addElement( "requestor" ); // these
+ ticket_params.addElement( "email@example.com" ); // are
+ ticket_params.addElement( "cc" ); // optional
+ ticket_params.addElement( "joe@example.com" ); //
+ ticket_params.addElement( "subject" );
+ ticket_params.addElement( "Houston, we have a problem." );
+ ticket_params.addElement( "message" );
+ ticket_params.addElement( "The Oscillation Overthurster has gone out of alignment!<br><br>It needs to be fixed immediately! <A HREF=\"http://linktest.freeside.biz/hi\">link test</A>" );
+ ticket_params.addElement( "mime_type" );
+ ticket_params.addElement( "text/html" );
+
+ HashMap ticket_result = client.execute( "create_ticket", ticket_params);
+
+ String error = (String) ticket_result.get("error");
+
+ if (error.length() < 1) {
+
+ // successful ticket creation
+
+ String ticketId = (String) ticket_result.get("ticket_id");
+
+ logger.trace("[login] ticket created with id="+ticketId);
+
+ } else {
+
+ // unsuccesful creating ticket
+
+ logger.warn("[login] error creating ticket: "+error);
+
+ }
+
+ }else{
+
+ // unsuccessful login
+
+ logger.warn("[login] error logging into freeside: "+error);
+
+ // display/say error message to user
+
+ }
+ }
+}
diff --git a/fs_selfservice/java/freeside_login_example.java b/fs_selfservice/java/freeside_login_example.java
new file mode 100755
index 0000000..cb6d2bc
--- /dev/null
+++ b/fs_selfservice/java/freeside_login_example.java
@@ -0,0 +1,45 @@
+
+import biz.freeside.SelfService;
+import org.apache.commons.logging.impl.SimpleLog; //included in apache xmlrpc
+import java.util.HashMap;
+import java.util.Vector;
+
+public class freeside_login_example {
+ private static SimpleLog logger = new SimpleLog("SelfService");
+
+ public static void main( String args[] ) throws Exception {
+ SelfService client =
+ new SelfService( "http://192.168.1.221:8081/xmlrpc.cgi" );
+
+ Vector params = new Vector();
+ params.addElement( "username" );
+ params.addElement( "testuser" );
+ params.addElement( "domain" );
+ params.addElement( "example.com" );
+ params.addElement( "password" );
+ params.addElement( "testpass" );
+ HashMap result = client.execute( "login", params );
+
+ String error = (String) result.get("error");
+
+ if (error.length() < 1) {
+
+ // successful login
+
+ String sessionId = (String) result.get("session_id");
+
+ logger.trace("[login] logged into freeside with session_id="+sessionId);
+
+ // store session id in your session store to be used for other calls
+
+ }else{
+
+ // successful login
+
+ logger.warn("[login] error logging into freeside: "+error);
+
+ // display error message to user
+
+ }
+ }
+}
diff --git a/fs_selfservice/java/freeside_signup_example.java b/fs_selfservice/java/freeside_signup_example.java
new file mode 100755
index 0000000..6c695c4
--- /dev/null
+++ b/fs_selfservice/java/freeside_signup_example.java
@@ -0,0 +1,69 @@
+
+import biz.freeside.SelfService;
+import org.apache.commons.logging.impl.SimpleLog; // included in apache xmlrpc
+import java.util.HashMap;
+import java.util.Vector;
+
+public class freeside_signup_example {
+ private static SimpleLog logger = new SimpleLog("SelfService");
+
+ public static void main( String args[] ) throws Exception {
+ SelfService client =
+ new SelfService( "http://192.168.1.221:8081/xmlrpc.cgi" );
+
+ Vector params = new Vector();
+ params.addElement( "first" );
+ params.addElement( "Test" );
+ params.addElement( "last" );
+ params.addElement( "User" );
+ params.addElement( "address1");
+ params.addElement( "123 Test Street" );
+ params.addElement( "address2");
+ params.addElement( "Suite A" );
+ params.addElement( "city");
+ params.addElement( "Testville" );
+ params.addElement( "state");
+ params.addElement( "OH" );
+ params.addElement( "zip");
+ params.addElement( "44632" );
+ params.addElement( "country");
+ params.addElement( "US" );
+ params.addElement( "daytime" );
+ params.addElement( "216-412-1234" );
+ params.addElement( "fax" );
+ params.addElement( "216-412-1235" );
+ params.addElement( "payby" );
+ params.addElement( "BILL" );
+ params.addElement( "invoicing_list" );
+ params.addElement( "test@test.example.com" );
+ params.addElement( "pkgpart" );
+ params.addElement( "101" );
+ params.addElement( "popnum" );
+ params.addElement( "4018" );
+ params.addElement( "username" );
+ params.addElement( "testy" );
+ params.addElement( "_password" );
+ params.addElement( "tester" );
+ HashMap result = client.execute( "new_customer", params );
+
+ String error = (String) result.get("error");
+
+ if (error.length() < 1) {
+
+ // successful signup
+
+ String custnum = (String) result.get("custnum");
+
+ logger.trace("[new_customer] signup with custnum "+custnum);
+
+ }else{
+
+ // unsuccessful signup
+
+ logger.warn("[new_customer] signup error: "+error);
+
+ // display error message to user
+
+ }
+ }
+}
diff --git a/fs_selfservice/perl/xmlrpc-create_ticket.pl b/fs_selfservice/perl/xmlrpc-create_ticket.pl
new file mode 100755
index 0000000..0ccada2
--- /dev/null
+++ b/fs_selfservice/perl/xmlrpc-create_ticket.pl
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;;
+
+my $server = new Frontier::Client (
+ url => 'http://localhost/selfservice/xmlrpc.cgi',
+);
+
+my $result = $server->call('FS.SelfService.XMLRPC.login',
+ 'username' => '4155551212',
+ 'password' => '5454',
+ 'domain' => 'svc_phone',
+);
+
+#print Dumper($result);
+die $result->{'error'} if $result->{'error'};
+
+my $session_id = $result->{'session_id'};
+warn "logged in with session_id $session_id\n";
+
+my $t_result = $server->call('FS.SelfService.XMLRPC.create_ticket',
+ 'session_id' => $session_id,
+ 'queue' => 3, #otherwise defaults to ticket_system-selfservice_queueid
+ #or ticket_system-default_queueid
+ 'requestor' => 'harveylala@example.com',
+ 'cc' => 'chiquitabanana@example.com',
+ 'subject' => 'Chiquita keeps sitting on me',
+ 'message' => 'Is there something you can do about this?<BR><BR>She keeps waking me up! <A HREF="http://linktest.freeside.biz/hi">link test</A>',
+ 'mime_type' => 'text/html',
+);
+
+die $t_result->{'error'} if $t_result->{'error'};
+
+warn Dumper($t_result);
+
+my $ticket_id = $t_result->{'ticket_id'};
+warn "ticket $ticket_id created\n";
+
+1;
diff --git a/fs_selfservice/php/freeside.class.php b/fs_selfservice/php/freeside.class.php
new file mode 100644
index 0000000..bb2ac98
--- /dev/null
+++ b/fs_selfservice/php/freeside.class.php
@@ -0,0 +1,34 @@
+<?php
+class FreesideSelfService {
+
+ //Change this to match the location of your selfservice xmlrpc.cgi or daemon
+ #var $URL = 'https://localhost/selfservice/xmlrpc.cgi';
+ var $URL = 'http://localhost/selfservice/xmlrpc.cgi';
+
+ function FreesideSelfService() {
+ $this;
+ }
+
+ public function __call($name, $arguments) {
+
+ error_log("[FreesideSelfService] $name called, sending to ". $this->URL);
+
+ $request = xmlrpc_encode_request("FS.SelfService.XMLRPC.$name", $arguments);
+ $context = stream_context_create( array( 'http' => array(
+ 'method' => "POST",
+ 'header' => "Content-Type: text/xml",
+ 'content' => $request
+ )));
+ $file = file_get_contents($this->URL, false, $context);
+ $response = xmlrpc_decode($file);
+ if (xmlrpc_is_fault($response)) {
+ trigger_error("[FreesideSelfService] XML-RPC communication error: $response[faultString] ($response[faultCode])");
+ } else {
+ //error_log("[FreesideSelfService] $response");
+ return $response;
+ }
+ }
+
+}
+
+?>
diff --git a/fs_selfservice/php/freeside.login_example.php b/fs_selfservice/php/freeside.login_example.php
new file mode 100644
index 0000000..69174a4
--- /dev/null
+++ b/fs_selfservice/php/freeside.login_example.php
@@ -0,0 +1,37 @@
+<?
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$domain = 'example.com';
+
+$response = $freeside->login( array(
+ 'username' => strtolower($_POST['username']),
+ 'domain' => $domain,
+ 'password' => strtolower($_POST['password']),
+) );
+
+error_log("[login] received response from freeside: $response");
+$error = $response['error'];
+
+if ( ! $error ) {
+
+ // sucessful login
+
+ $session_id = $response['session_id'];
+
+ error_log("[login] logged into freeside with session_id=$session_id");
+
+ // store session id in your session store, to be used for other calls
+
+} else {
+
+ // unsucessful login
+
+ error_log("[login] error logging into freeside: $error");
+
+ // display error message to user
+
+}
+
+?>
diff --git a/fs_selfservice/php/freeside_signup_example.php b/fs_selfservice/php/freeside_signup_example.php
new file mode 100644
index 0000000..8b1dc19
--- /dev/null
+++ b/fs_selfservice/php/freeside_signup_example.php
@@ -0,0 +1,49 @@
+<?
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$response = $freeside->new_customer( array(
+ 'agentnum' => 1,
+
+ 'first' => $_POST['first'],
+ 'last' => $_POST['last'],
+ 'address1' => $_POST['address1'],
+ 'address2' => $_POST['address2'],
+ 'city' => $_POST['city'],
+ 'state' => $_POST['state'],
+ 'zip' => $_POST['zip'],
+ 'country' => 'US',
+ 'daytime' => $_POST['daytime'],
+ 'fax' => $_POST['fax'],
+
+ 'payby' => 'BILL',
+ 'invoicing_list' => $_POST['email'],
+
+ 'pkgpart' => 2,
+ 'username' => strtolower($_POST['username']),
+ '_password' => strtolower($_POST['password'])
+) );
+
+error_log("[new_customer] received response from freeside: $response");
+$error = $response['error'];
+
+if ( ! $error ) {
+
+ // sucessful signup
+
+ $custnum = $response['custnum'];
+
+ error_log("[new_customer] signup up with custnum $custnum");
+
+} else {
+
+ // unsucessful signup
+
+ error_log("[new_customer] signup error:: $error");
+
+ // display error message to user
+
+}
+
+?>
diff --git a/fs_selfservice/php/login.php b/fs_selfservice/php/login.php
new file mode 100644
index 0000000..d960914
--- /dev/null
+++ b/fs_selfservice/php/login.php
@@ -0,0 +1,90 @@
+<?php
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$login_info = $freeside->login_info();
+
+extract($login_info);
+
+$error = $_GET['error'];
+if ( $error ) {
+ $username = $_GET['username'];
+ $domain = $_GET['domain'];
+}
+
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML><HEAD><TITLE>Login</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=5>Login</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><?php echo htmlspecialchars($error); ?></FONT>
+
+<FORM ACTION="process_login.php" 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="<?php echo htmlspecialchars($username); ?>"><?php if ( $single_domain ) { echo '@'.$single_domain; } ?>
+ </TD>
+</TR>
+
+<?php if ( $single_domain ) { ?>
+
+ <INPUT TYPE="hidden" NAME="domain" VALUE="<?php echo $single_domain ?>">
+
+<?php } else { ?>
+
+ <TR>
+ <TH ALIGN="right">Domain </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="domain" VALUE="<?php echo htmlspecialchars($domain); ?>">
+ </TD>
+ </TR>
+
+<?php } ?>
+
+<TR>
+ <TH ALIGN="right">Password </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+</TR>
+<TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Login"></TD>
+</TR>
+</TABLE>
+</FORM>
+
+<?php if ( $phone_login ) { ?>
+
+ <B>OR</B><BR><BR>
+
+ <FORM ACTION="process_login.php" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="session" VALUE="login">
+ <TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+ <TR>
+ <TH ALIGN="right">Phone number </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<?php echo htmlspecialchars($username) ?>">
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="domain" VALUE="svc_phone">
+ <TR>
+ <TH ALIGN="right">PIN </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Login"></TD>
+ </TR>
+ </TABLE>
+ </FORM>
+
+<?php } ?>
+
+</BODY></HTML>
+
diff --git a/fs_selfservice/php/main.php b/fs_selfservice/php/main.php
new file mode 100644
index 0000000..b34a477
--- /dev/null
+++ b/fs_selfservice/php/main.php
@@ -0,0 +1,39 @@
+<?php
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$session_id = $_GET['session_id'];
+
+$response = $freeside->customer_info( array(
+ 'session_id' => $session_id,
+) );
+
+$error = $response['error'];
+
+if ( $error ) {
+ header('Location:login.php?error='. urlencode($error));
+ die();
+}
+
+extract($response);
+
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+ <HEAD>
+ <TITLE>My Account</TITLE>
+ </HEAD>
+ <BODY>
+ <H1>My Account</H1>
+
+ Hello, <?php echo htmlspecialchars($name); ?><BR><BR>
+
+ <?php echo $small_custview; ?>
+
+ <BR>
+
+ <A HREF="order_renew.php?session_id=<?php echo $session_id; ?>">Renew early</A>
+
+ </BODY>
+</HTML>
diff --git a/fs_selfservice/php/order_renew.php b/fs_selfservice/php/order_renew.php
new file mode 100644
index 0000000..e74ba40
--- /dev/null
+++ b/fs_selfservice/php/order_renew.php
@@ -0,0 +1,166 @@
+<?php
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$session_id = $_GET['session_id'];
+
+$renew_info = $freeside->renew_info( array(
+ 'session_id' => $session_id,
+) );
+
+$error = $renew_info['error'];
+
+if ( $error ) {
+ header('Location:login.php?error='. urlencode($error));
+ die();
+}
+
+#in the simple case, just deal with the first package
+$bill_date = $renew_info['dates'][0]['bill_date'];
+$bill_date_pretty = $renew_info['dates'][0]['bill_date_pretty'];
+$renew_date = $renew_info['dates'][0]['renew_date'];
+$renew_date_pretty = $renew_info['dates'][0]['renew_date_pretty'];
+$amount = $renew_info['dates'][0]['amount'];
+
+$payment_info = $freeside->payment_info( array(
+ 'session_id' => $session_id,
+) );
+
+extract($payment_info);
+
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+ <HEAD>
+ <TITLE>Renew Early</TITLE>
+ </HEAD>
+ <BODY>
+ <H1>Renew Early</H1>
+
+ <FONT SIZE="+1" COLOR="#ff0000"><?php echo htmlspecialchars($_GET['error']); ?></FONT>
+
+ <FORM NAME="OneTrueForm" METHOD="POST" ACTION="process_payment_order_renew.php" onSubmit="document.OneTrueForm.process.disabled=true">
+
+ <INPUT TYPE="hidden" NAME="date" VALUE="<?php echo $date; ?>">
+ <INPUT TYPE="hidden" NAME="session_id" VALUE="<?php echo $session_id; ?>">
+ <INPUT TYPE="hidden" NAME="amount" VALUE="<?php echo $amount; ?>">
+
+ A payment of $<?php echo $amount; ?> will renew your account through <?php echo $renew_date_pretty; ?>.<BR><BR>
+
+ <TABLE BGCOLOR="#cccccc">
+ <TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<?php echo $amount; ?>
+ </TD></TR></TABLE>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Card&nbsp;type</TD>
+ <TD>
+ <SELECT NAME="card_type"><OPTION></OPTION>
+ <?php foreach ( array_keys($card_types) as $t ) { ?>
+ <OPTION <?php if ($card_type == $card_types[$t] ) { ?> SELECTED <?php } ?>
+ VALUE="<?php echo $card_types[$t]; ?>"
+ ><?php echo $t; ?>
+ <?php } ?>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Card&nbsp;number</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<?php echo $payinfo; ?>"> </TD>
+ <TD>Exp.</TD>
+ <TD>
+ <SELECT NAME="month">
+ <?php foreach ( array('01','02','03','04','05','06','07','08','09','10','11','12') as $m) { ?>
+ <OPTION<?php if ($m == $month ) { ?> SELECTED<?php } ?>
+ ><?php echo $m; ?>
+ <?php } ?>
+ </SELECT>
+ </TD>
+ <TD> / </TD>
+ <TD>
+ <SELECT NAME="year">
+ <?php $lt = localtime(); $y = $lt[5] + 1900;
+ for ($y = $lt[5]+1900; $y < $lt[5] + 1910; $y++ ) { ?>
+ <OPTION<?php if ($y == $year ) { ?> SELECTED<?php } ?>
+ ><?php echo $y; ?>
+ <?php } ?>
+ </SELECT>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ </TR>
+ <?php if ( $withcvv ) { ?>
+ <TR>
+ <TD ALIGN="right">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>)</TD>
+ <TD><INPUT TYPE="text" NAME="paycvv" VALUE="" SIZE=4 MAXLENGTH=4></TD>
+ </TR>
+ <?php } ?>
+ <TR>
+ <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<?php echo $payname; ?>"></TD>
+ </TR><TR>
+ <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<?php echo $address1; ?>">
+ </TD>
+ </TR><TR>
+ <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<?php echo $address2; ?>">
+ </TD>
+ </TR><TR>
+ <TD ALIGN="right">City</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<?php echo $city; ?>">
+ </TD>
+ <TD>State</TD>
+ <TD>
+ <SELECT NAME="state">
+ <?php foreach ( $states as $s ) { ?>
+ <OPTION<?php if ($s == $state) { ?> SELECTED<?php } ?>
+ ><?php echo $s; ?>
+ <?php } ?>
+ </SELECT>
+ </TD>
+ <TD>Zip</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<?php echo $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"<?php if ( $payby == 'CARD' ) { ?> CHECKED<?php } ?> 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="<?php echo $paybatch; ?>">
+ <INPUT TYPE="submit" NAME="process" VALUE="Process payment"> <!-- onClick="this.disabled=true"> -->
+ </FORM>
+
+ </BODY>
+</HTML>
diff --git a/fs_selfservice/php/process_login.php b/fs_selfservice/php/process_login.php
new file mode 100644
index 0000000..1f4fd9a
--- /dev/null
+++ b/fs_selfservice/php/process_login.php
@@ -0,0 +1,38 @@
+<?php
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$response = $freeside->login( array(
+ 'username' => strtolower($_POST['username']),
+ 'domain' => strtolower($_POST['domain']),
+ 'password' => strtolower($_POST['password']),
+) );
+
+#error_log("[login] received response from freeside: $response");
+
+$error = $response['error'];
+
+if ( $error ) {
+
+ header('Location:login.php?username='. urlencode($username).
+ '&domain='. urlencode($domain).
+ '&error='. urlencode($error)
+ );
+ die();
+
+}
+
+// sucessful login
+
+$session_id = $response['session_id'];
+
+#error_log("[login] logged into freeside with session_id=$session_id");
+
+// now what? for now, always redirect to the main page.
+// eventually, other options?
+
+header("Location:main.php?session_id=$session_id")
+#die();
+
+?>
diff --git a/fs_selfservice/php/process_payment_order_renew.php b/fs_selfservice/php/process_payment_order_renew.php
new file mode 100644
index 0000000..2059462
--- /dev/null
+++ b/fs_selfservice/php/process_payment_order_renew.php
@@ -0,0 +1,74 @@
+<?php
+
+require('freeside.class.php');
+$freeside = new FreesideSelfService();
+
+$response = $freeside->process_payment_order_renew( array(
+ 'session_id' => $_POST['session_id'],
+ 'payby' => 'CARD',
+ 'amount' => $_POST['amount'],
+ 'payinfo' => $_POST['payinfo'],
+ 'paycvv' => $_POST['paycvv'],
+ 'month' => $_POST['month'],
+ 'year' => $_POST['year'],
+ 'payname' => $_POST['payname'],
+ 'address1' => $_POST['address1'],
+ 'address2' => $_POST['address2'],
+ 'city' => $_POST['city'],
+ 'state' => $_POST['state'],
+ 'zip' => $_POST['zip'],
+ 'save' => $_POST['save'],
+ 'auto' => $_POST['auto'],
+ 'paybatch' => $_POST['paybatch'],
+) );
+
+error_log("[process_payment_order_renew] received response from freeside: $response");
+
+$error = $response['error'];
+
+if ( $error ) {
+
+ error_log("[process_payment_order_renew] response error: $error");
+
+ header('Location:order_renew.php'.
+ '?session_id='. urlencode($_POST['session_id']).
+ '?error='. urlencode($error).
+ '&payby=CARD'.
+ '&amount='. urlencode($_POST['amount']).
+ '&payinfo='. urlencode($_POST['payinfo']).
+ '&paycvv='. urlencode($_POST['paycvv']).
+ '&month='. urlencode($_POST['month']).
+ '&year='. urlencode($_POST['year']).
+ '&payname='. urlencode($_POST['payname']).
+ '&address1='. urlencode($_POST['address1']).
+ '&address2='. urlencode($_POST['address2']).
+ '&city='. urlencode($_POST['city']).
+ '&state='. urlencode($_POST['state']).
+ '&zip='. urlencode($_POST['zip']).
+ '&save='. urlencode($_POST['save']).
+ '&auto='. urlencode($_POST['auto']).
+ '&paybatch='. urlencode($_POST['paybatch'])
+ );
+ die();
+
+}
+
+// sucessful renewal.
+
+$session_id = $response['session_id'];
+
+// now what?
+
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+ <HEAD>
+ <TITLE>Renew Early</TITLE>
+ </HEAD>
+ <BODY>
+ <H1>Renew Early</H1>
+
+ Renewal processed sucessfully.
+
+ </BODY>
+</HTML>
diff --git a/htetc/freeside-base1.99.conf b/htetc/freeside-base1.99.conf
new file mode 100644
index 0000000..f3f7676
--- /dev/null
+++ b/htetc/freeside-base1.99.conf
@@ -0,0 +1,38 @@
+PerlModule Apache::compat
+
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+PerlSetVar MasonArgsMethod CGI
+PerlModule HTML::Mason::ApacheHandler
+
+PerlRequire "%%%MASON_HANDLER%%%"
+
+#Locale::SubCountry
+AddDefaultCharset ISO-8859-1
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Files>
+</Directory>
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Helpers/>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Directory>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/loginout>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd.logout
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+SetHandler default-handler
+</Files>
+</Directory>
+
diff --git a/htetc/freeside-base1.conf b/htetc/freeside-base1.conf
new file mode 100644
index 0000000..16e1e14
--- /dev/null
+++ b/htetc/freeside-base1.conf
@@ -0,0 +1,35 @@
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+
+#Locale::SubCountry
+AddDefaultCharset ISO-8859-1
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+AddHandler perl-script .cgi .html
+PerlHandler HTML::Mason
+</Files>
+</Directory>
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Helpers/>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Directory>
+<Perl>
+require "%%%MASON_HANDLER%%%";
+</Perl>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/loginout>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd.logout
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+SetHandler default-handler
+</Files>
+</Directory>
+
diff --git a/htetc/freeside-base2.conf b/htetc/freeside-base2.conf
new file mode 100644
index 0000000..76e7997
--- /dev/null
+++ b/htetc/freeside-base2.conf
@@ -0,0 +1,38 @@
+PerlModule Apache2::compat
+
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+PerlSetVar MasonArgsMethod CGI
+PerlModule HTML::Mason::ApacheHandler
+
+PerlRequire "%%%MASON_HANDLER%%%"
+
+#Locale::SubCountry
+AddDefaultCharset ISO-8859-1
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Files>
+</Directory>
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Helpers/>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Directory>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/loginout>
+AuthName Freeside
+AuthType Basic
+AuthUserFile %%%FREESIDE_CONF%%%/htpasswd.logout
+require valid-user
+<Files ~ "(\.cgi|\.html)$">
+SetHandler default-handler
+</Files>
+</Directory>
+
diff --git a/htetc/freeside-rt.conf b/htetc/freeside-rt.conf
new file mode 100644
index 0000000..9b5ccf8
--- /dev/null
+++ b/htetc/freeside-rt.conf
@@ -0,0 +1,36 @@
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/NoAuth>
+<Limit GET POST>
+allow from all
+Satisfy any
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Limit>
+</Directory>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/REST/1.0/NoAuth>
+<Limit GET POST>
+allow from all
+Satisfy any
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Limit>
+</Directory>
+
+<DirectoryMatch "^%%%FREESIDE_DOCUMENT_ROOT%%%/rt/.*NoAuth/images">
+SetHandler None
+</DirectoryMatch>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Ticket/Attachment>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Directory>
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%/rt/Search>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Directory>
+
+<DirectoryMatch "^%%%FREESIDE_DOCUMENT_ROOT%%%/rt/RTx/Statistics/.*/Elements>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</DirectoryMatch>
diff --git a/htetc/handler.pl b/htetc/handler.pl
new file mode 100644
index 0000000..94377cf
--- /dev/null
+++ b/htetc/handler.pl
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+
+package HTML::Mason;
+
+use strict;
+use warnings;
+use FS::Mason qw( mason_interps );
+
+#use vars qw($r);
+
+# 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');
+
+###use Module::Refresh;###
+
+# Create Mason objects
+
+my( $fs_interp, $rt_interp ) = mason_interps('apache');
+
+my $ah = new HTML::Mason::ApacheHandler (
+ interp => $fs_interp,
+ request_class => 'FS::Mason::Request',
+ args_method => 'CGI', #(and FS too)
+);
+
+# 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) = @_;
+ my $r = shift;
+
+ # 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;
+
+ ###Module::Refresh->refresh;###
+
+ #$r->content_type('text/html; charset=utf-8');
+ $r->content_type('text/html; charset=iso-8859-1');
+ #eorar
+
+ my $headers = $r->headers_out;
+ $headers->{'Cache-control'} = 'no-cache';
+ #$r->no_cache(1);
+ $headers->{'Expires'} = '0';
+
+# $r->send_http_header;
+
+ if ( $r->filename =~ /\/rt\// ) { #RT
+
+ $ah->interp($rt_interp);
+
+ local $SIG{__WARN__};
+ local $SIG{__DIE__};
+
+ RT::Init();
+
+ # We don't need to handle non-text, non-xml items
+ return -1 if defined( $r->content_type )
+ && $r->content_type !~ m!(^text/|\bxml\b)!io;
+
+ } else {
+
+ local $SIG{__WARN__};
+ local $SIG{__DIE__};
+
+ RT::Init() if $RT::VERSION; #for lack of something else
+
+ #we don't want the RT error handlers under FS
+ {
+ no warnings 'uninitialized';
+ undef($SIG{__WARN__}) if defined($SIG{__WARN__});
+ undef($SIG{__DIE__}) if defined($SIG{__DIE__} );
+ }
+
+ $ah->interp($fs_interp);
+
+ }
+
+ my %session;
+ my $status;
+ eval { $status = $ah->handle_request($r); };
+#!!
+# if ( $@ ) {
+# $RT::Logger->crit($@);
+# }
+ warn $@ if $@;
+
+ 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/htetc/htpasswd.logout b/htetc/htpasswd.logout
new file mode 100644
index 0000000..3523f23
--- /dev/null
+++ b/htetc/htpasswd.logout
@@ -0,0 +1 @@
+magic:Jgvaxb502SIqQ
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..ae04d42
--- /dev/null
+++ b/httemplate/autohandler
@@ -0,0 +1,44 @@
+% $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'
+ && $FS::CurrentUser::CurrentUser->option('show_db_profile')
+ )
+ {
+
+ ## barely worth it, just in case someone tries to use profiling on a
+ ## non-RT install
+ #eval "use Text::Wrapper;";
+ #die $@ if $@;
+
+ my $text = dbh->sprintProfile();
+ #$text =~ s/^/ /mg;
+
+ $profile = '<PRE>'. encode_entities( $text ). "\n\n". '</PRE>';
+
+ }
+
+ #well, could do this without sprintProfile, but definiately don't want it on
+ #unless DBIx::Profile is loaded
+ if ( $FS::CurrentUser::CurrentUser->option('save_db_profile') ) {
+ #my $file = %%%FREESIDE_LOG%%%; #substitute here? maybe get from FS.pm?
+ my $file = '/usr/local/etc/freeside/'; #bah
+ $file .= "dbix_profile.$$.". time;
+ dbh->setLogFile($file);
+ dbh->printProfile();
+ }
+
+ dbh->{'private_profile'} = {};
+}
+
+s/(<\/BODY>[\s\n]*<\/HTML>[\s\n]*)$/$profile$1/i;
+</%filter>
+<%cleanup>
+ dbh->commit();
+</%cleanup>
diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html
new file mode 100644
index 0000000..aa9097f
--- /dev/null
+++ b/httemplate/browse/access_group.html
@@ -0,0 +1,106 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Employee Groups',
+ 'menubar' => [ 'View Employees' => $p.'browse/access_user.html', ],
+ 'html_init' => $html_init,
+ 'name' => 'employee groups',
+ 'query' => { 'table' => 'access_group',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY groupname', #??
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Group name',
+ 'Agents',
+ 'Rights',
+ ],
+ 'fields' => [ 'groupnum',
+ 'groupname',
+ $agents_sub,
+ $rights_sub,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ '',
+ ],
+ )
+%>
+<%once>
+
+my $html_init =
+ "Employee groups control access to the back-office interface. Each employee can be assigned to one or more groups.<BR><BR>".
+ qq!<A HREF="${p}edit/access_group.html"><I>Add an employee group</I></A><BR><BR>!;
+
+#false laziness w/access_user.html & agent_type.cgi
+my $agents_sub = sub {
+ my $access_group = shift;
+
+ [ map {
+ my $access_groupagent = $_;
+ my $agent = $access_groupagent->agent;
+ [
+ {
+ 'data' => $agent->agent,
+ 'align' => 'left',
+ 'link' => $p. 'edit/agent.cgi?'. $agent->agentnum,
+ },
+ ];
+ }
+ grep { $_->agent } #?
+ $access_group->access_groupagent,
+
+ ];
+
+};
+
+tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_info;
+
+my $rights_sub = sub {
+ my $access_group = shift;
+
+ #[ map { my $access_right = $_;
+ # [
+ # {
+ # 'data' => $access_right->rightname,
+ # 'align' => 'left',
+ # },
+ # ];
+ # }
+ # $access_group->access_rights,
+ #];
+
+ #some false laziness w/edit/access_group.html
+ my $columns = 3;
+ my $count = 0;
+
+ #include('/elements/table-grid.html', bgcolor=>'#cccccc' ).
+ '<TABLE>'.
+ '<TR>'. join( '', map {
+
+ '<TD CLASS="inv" VALIGN="top"><TABLE WIDTH=100%>'.
+ '<TR><TH BGCOLOR="#dcdcdc">'. $_. '</TH></TR>'.
+ '<TR><TD>'.
+
+ join('<BR>', grep { $access_group->access_right($_); }
+ map { ref($_) ? $_->{'rightname'} : $_; }
+ @{ $rights{$_} }
+ ).
+
+ '</TD></TR></TABLE></TD>'.
+ ( ++$count % $columns ? '' : '</TR><TR>')
+
+ } keys %rights ). '</TR></TABLE>';
+
+};
+
+my $count_query = 'SELECT COUNT(*) FROM access_group';
+
+my $link = [ $p.'edit/access_group.html?', 'groupnum' ];
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html
new file mode 100644
index 0000000..3162e3a
--- /dev/null
+++ b/httemplate/browse/access_user.html
@@ -0,0 +1,77 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Employees',
+ 'menubar' => [ 'View Employee groups' => $p.'browse/access_group.html', ],
+ 'html_init' => $html_init,
+ 'name' => 'employees',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'access_user',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY last, first'
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => $align,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ "Employees have access to the back-office interface. Typically, this is your employees and contractors. In a virtualized setup, you can also add accounts for your reseller's employees.<BR><BR>It is <B>highly recommended</B> to add a <B>separate account for each person</B> rather than using role accounts.<BR><BR>".
+ qq!<A HREF="${p}edit/access_user.html"><I>Add an employee</I></A><BR><BR>!;
+
+#false laziness w/access_group.html & agent_type.cgi
+my $groups_sub = sub {
+ my $access_user = shift;
+
+ [ map {
+ my $access_usergroup = $_;
+ my $access_group = $access_usergroup->access_group;
+ [
+ {
+ 'data' => $access_group->groupname,
+ 'align' => 'left',
+ 'link' =>
+ $p. 'edit/access_group.html?'. $access_usergroup->groupnum,
+ },
+ ];
+ }
+ grep { $_->access_group # and ! $_->access_group->disabled
+ }
+ $access_user->access_usergroup,
+
+ ];
+
+};
+
+my $cust_sub = sub {
+ my $access_user = shift;
+ $access_user->user_custnum ? $access_user->user_cust_main->name : '';
+};
+my $cust_link = [ $p.'view/cust_main.cgi?custnum=', 'user_custnum' ];
+
+my $count_query = 'SELECT COUNT(*) FROM access_user';
+
+my $link = [ $p.'edit/access_user.html?', 'usernum' ];
+
+my @header = ( '#', 'Username', 'Full name', 'Groups', 'Customer' );
+my @fields = ( 'usernum', 'username', 'name', $groups_sub, $cust_sub, );
+my $align = 'rllll';
+my @links = ( $link, $link, $link, '', $cust_link );
+
+#if ( FS::Conf->new->config('ticket_system') ) {
+# push @header, 'Ticketing';
+# push @fields, sub {
+# my $access_user = shift;
+#
+# };
+# $align .= 'l';
+# push @links, '';
+#}
+
+</%init>
diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi
new file mode 100644
index 0000000..1bbcdcb
--- /dev/null
+++ b/httemplate/browse/addr_block.cgi
@@ -0,0 +1,145 @@
+<% include('elements/browse.html',
+ 'title' => 'Address Blocks',
+ 'name' => 'address block',
+ 'html_init' => $html_init,
+ 'html_foot' => $html_foot,
+ 'query' => { 'table' => 'addr_block',
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $order_by,
+ },
+ 'count_query' => "SELECT count(*) from addr_block $count_sql",
+ 'header' => [ 'Address Block',
+ 'Router',
+ 'Action(s)',
+ '',
+ '',
+ ],
+ 'fields' => [ 'NetAddr',
+ sub { my $block = shift;
+ my $router = $block->router;
+ my $result = '';
+ if ($router) {
+ $result .= $router->routername. ' (';
+ $result .= scalar($block->svc_broadband). ' services)';
+ }
+ $result;
+ },
+ $allocate_text,
+ sub { shift->router ? '' : '<FONT SIZE="-2">(split)</FONT>' },
+ sub { '<FONT SIZE="-2">('. (shift->manual_flag ? 'allow' : 'prevent'). ' automatic ip assignment)</FONT>' },
+ ],
+ 'links' => [ '',
+ '',
+ [ 'javascript:void(0)', '' ],
+ $split_link,
+ $autoassign_link,
+ ],
+ 'link_onclicks' => [ '',
+ '',
+ $allocate_link,
+ '',
+ ],
+ 'cell_styles' => [ '',
+ '',
+ 'border-right:none;',
+ 'border-left:none;',
+ ],
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ 'agent_pos' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Broadband configuration')
+ || $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+my $p2 = popurl(2);
+my $path = $p2 . "edit/process/addr_block";
+
+my $extra_sql = "";
+
+my $count_sql = "WHERE ". $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration',
+);
+
+my $order_by = "ORDER BY ";
+$order_by .= "inet(ip_gateway), " if driver_name =~ /^Pg/i;
+$order_by .= "inet_aton(ip_gateway), " if driver_name =~ /^mysql/i;
+$order_by .= "ip_netmask";
+
+my $html_init = qq(
+<SCRIPT>
+ function addr_block_areyousure(href, word) {
+ if(confirm("Are you sure you want to "+word+" this address block?") == true)
+ window.location.href = href;
+ }
+</SCRIPT>
+);
+
+$html_init .= include('/elements/error.html');
+
+my $confirm = sub {
+ my ($verb, $num) = (shift, shift);
+ "javascript:addr_block_areyousure('$path/$verb.cgi?blocknum=$num', '$verb')";
+};
+
+my $html_foot = qq(
+ <FORM ACTION="$path/add.cgi" METHOD="POST">
+ Gateway/Netmask:
+ <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2">
+);
+$html_foot .= include( '/elements/select-agent.html',
+ 'agent_null_right' => 'Broadband global configuration',
+ );
+$html_foot .= qq(
+ <INPUT TYPE="submit" NAME="submit" VALUE="Add">
+ </FORM>
+);
+
+my $allocate_text = sub { my $block = shift;
+ my $router = $block->router;
+ my $result = '';
+ if ($router) {
+ $result = '<FONT SIZE="-2">(deallocate)</FONT>'
+ unless scalar($block->svc_broadband);
+ }else{
+ $result .= '<FONT SIZE="-2">(allocate)</FONT>'
+ }
+ $result;
+};
+
+my $allocate_link = sub {
+ my $block = shift;
+ if ($block->router) {
+ if (scalar($block->svc_broadband) == 0) {
+ &{$confirm}('deallocate', $block->blocknum);
+ } else {
+ "";
+ }
+ } else {
+ include( '/elements/popup_link_onclick.html',
+ 'action' => "${p2}edit/allocate.html?blocknum=". $block->blocknum,
+ 'actionlabel' => 'Allocate block to router',
+ );
+ }
+};
+
+my $split_link = sub {
+ my $block = shift;
+ my $ref = [ '', '' ];
+ $ref = [ &{$confirm}('split', $block->blocknum), '' ]
+ unless ($block->router);
+ $ref;
+};
+
+my $autoassign_link = sub {
+ my $block = shift;
+ my $url = "$path/manual_flag.cgi?manual_flag=";
+ $url .= $block->manual_flag ? '' : 'Y';
+ [ "$url;blocknum=", 'blocknum' ];
+};
+
+</%init>
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
new file mode 100755
index 0000000..1c06f1b
--- /dev/null
+++ b/httemplate/browse/agent.cgi
@@ -0,0 +1,424 @@
+<% include("/elements/header.html",'Agent Listing', menubar(
+ '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> )'; }
+ %>
+% }
+
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Master Customer</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice<BR>Template</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Customer<BR>packages</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Reports</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Registration<BR>codes</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Prepaid cards</TH>
+
+% if ( $conf->config('ticket_system') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Ticketing</TH>
+% }
+
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Configuration Overrides</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;
+%
+% my $cust_pkg_link = $p. 'search/cust_pkg.cgi?agentnum='. $agent->agentnum;
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"><% $agent->agentnum %></A>
+ </TD>
+
+% if ( dbdef->table('agent')->column('disabled')
+% && !$cgi->param('showdisabled') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $agent->disabled ? 'DISABLED' : '' %>
+ </TD>
+% }
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"><% $agent->agent %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/agent_type.cgi?<% $agent->typenum %>"><% $agent->agent_type->atype %></A>
+ </TD>
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+% if ( $agent->agent_custnum ) {
+ <% include('/elements/small_custview.html',
+ $agent->agent_custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #show balance
+ )
+ %>
+% }
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% foreach my $access_group (
+% map $_->access_group,
+% qsearch('access_groupagent', { 'agentnum' => $agent->agentnum })
+% ) {
+ <A HREF="<%$p%>edit/access_group.html?<% $access_group->groupnum %>"><% $access_group->groupname |h %><BR>
+% }
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $agent->invoice_template || '(Default)' %>
+ </TD>
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="bottom">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#7e0079">
+ <% my $num_prospect = $agent->num_prospect_cust_main %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_prospect ) {
+
+ <A HREF="<% $cust_main_link %>&prospect=1">
+% }
+prospects
+% if ($num_prospect ) {
+</A>
+% }
+
+ <TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#0000CC">
+ <% my $num_inactive = $agent->num_inactive_cust_main %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_inactive ) {
+
+ <A HREF="<% $cust_main_link %>&inactive=1">
+% }
+inactive
+% if ( $num_inactive ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#00CC00">
+ <% my $num_active = $agent->num_active_cust_main %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_active ) {
+
+ <A HREF="<% $cust_main_link %>&active=1">
+% }
+active
+% if ( $num_active ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#FF9900">
+ <% my $num_susp = $agent->num_susp_cust_main %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_susp ) {
+
+ <A HREF="<% $cust_main_link %>&suspended=1">
+% }
+suspended
+% if ( $num_susp ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#FF0000">
+ <% my $num_cancel = $agent->num_cancel_cust_main %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_cancel ) {
+
+ <A HREF="<% $cust_main_link %>&showcancelledcustomers=1&cancelled=1">
+% }
+cancelled
+% if ( $num_cancel ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ </TABLE>
+ </TD>
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="bottom">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#0000CC">
+ <% my $num_inactive_pkg = $agent->num_inactive_cust_pkg %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_inactive_pkg ) {
+
+ <A HREF="<% $cust_pkg_link %>&magic=inactive">
+% }
+inactive
+% if ( $num_inactive_pkg ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#00CC00">
+ <% my $num_active_pkg = $agent->num_active_cust_pkg %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_active_pkg ) {
+
+ <A HREF="<% $cust_pkg_link %>&magic=active">
+% }
+active
+% if ( $num_active_pkg ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#FF9900">
+ <% my $num_susp_pkg = $agent->num_susp_cust_pkg %>&nbsp;
+ </FONT>
+
+ </TH>
+ <TD>
+% if ( $num_susp_pkg ) {
+
+ <A HREF="<% $cust_pkg_link %>&magic=suspended">
+% }
+suspended
+% if ( $num_susp_pkg ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" WIDTH="40%">
+ <FONT COLOR="#FF0000">
+ <% my $num_cancel_pkg = $agent->num_cancel_cust_pkg %>&nbsp;
+ </FONT>
+ </TH>
+
+ <TD>
+% if ( $num_cancel_pkg ) {
+
+ <A HREF="<% $cust_pkg_link %>&magic=cancelled">
+% }
+cancelled
+% if ( $num_cancel_pkg ) {
+</A>
+% }
+
+ </TD>
+ </TR>
+
+ </TABLE>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<% $p %>graph/report_cust_pkg.html?agentnum=<% $agent->agentnum %>">Package&nbsp;Churn</A>
+ <BR><A HREF="<% $p %>search/report_cust_pay.html?agentnum=<% $agent->agentnum %>">Payments</A>
+ <BR><A HREF="<% $p %>search/report_cust_credit.html?agentnum=<% $agent->agentnum %>">Credits</A>
+ <BR><A HREF="<% $p %>search/report_receivables.cgi?agentnum=<% $agent->agentnum %>">A/R&nbsp;Aging</A>
+ <!--<BR><A HREF="<% $p %>search/money_time.cgi?agentnum=<% $agent->agentnum %>">Sales/Credits/Receipts</A>-->
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% my $num_reg_code = $agent->num_reg_code %>
+% if ( $num_reg_code ) {
+
+ <A HREF="<%$p%>search/reg_code.html?agentnum=<% $agent->agentnum %>">
+% }
+Unused
+% if ( $num_reg_code ) {
+</A>
+% }
+
+ <BR><A HREF="<%$p%>edit/reg_code.cgi?agentnum=<% $agent->agentnum %>">Generate codes</A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% my $num_prepay_credit = $agent->num_prepay_credit %>
+% if ( $num_prepay_credit ) {
+
+ <A HREF="<%$p%>search/prepay_credit.html?agentnum=<% $agent->agentnum %>">
+% }
+Unused
+% if ( $num_prepay_credit ) {
+</A>
+% }
+
+ <BR><A HREF="<%$p%>edit/prepay_credit.cgi?agentnum=<% $agent->agentnum %>">Generate cards</A>
+ </TD>
+% if ( $conf->config('ticket_system') ) {
+
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ( $agent->ticketing_queueid ) {
+
+ Queue: <% $agent->ticketing_queueid %>: <% $agent->ticketing_queue %><BR>
+% }
+
+ </TD>
+% }
+
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+% foreach my $override (
+% # sort { } want taxclass-full stuff first? and default cards (empty cardtype)
+% qsearch('agent_payment_gateway', { 'agentnum' => $agent->agentnum } )
+% ) {
+%
+
+ <TR>
+ <TD>
+ <% $override->cardtype || 'Default' %> to <% $override->payment_gateway->gateway_module %> (<% $override->payment_gateway->gateway_username %>)
+ <% $override->taxclass
+ ? ' for '. $override->taxclass. ' only'
+ : ''
+ %>
+ <FONT SIZE=-1><A HREF="javascript:areyousure('delete this payment gateway override', '<%$p%>misc/delete-agent_payment_gateway.cgi?<% $override->agentgatewaynum %>')">(delete)</A></FONT>
+ </TD>
+ </TR>
+% }
+
+ <TR>
+ <TD><FONT SIZE=-1><A HREF="<%$p%>edit/agent_payment_gateway.html?agentnum=<% $agent->agentnum %>">(add override)</A></FONT></TD>
+ </TR>
+ </TABLE>
+ </TD>
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+% foreach my $override (
+% qsearch('conf', { 'agentnum' => $agent->agentnum } )
+% ) {
+%
+
+ <TR>
+ <TD>
+ <% $override->name %>&nbsp;<FONT SIZE=-1><A HREF="javascript:areyousure('delete this configuration override', '<%$p%>config/config-delete.cgi?confnum=<% $override->confnum %>')">(delete)</A></FONT>
+ </TD>
+ </TR>
+% }
+
+ <TR>
+ <TD><FONT SIZE=-1><A HREF="<%$p%>config/config-view.cgi?agentnum=<% $agent->agentnum %>">(view/add/edit overrides)</A></FONT></TD>
+ </TR>
+ </TABLE>
+ </TD>
+
+ </TR>
+% }
+
+
+ </TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ function areyousure(what, href) {
+ if ( confirm("Are you sure you want to " + what + "?") == true )
+ window.location.href = href;
+ }
+</SCRIPT>
+
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %search;
+if ( $cgi->param('showdisabled')
+ || !dbdef->table('agent')->column('disabled') ) {
+ %search = ();
+} else {
+ %search = ( 'disabled' => '' );
+}
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi
new file mode 100755
index 0000000..f07a651
--- /dev/null
+++ b/httemplate/browse/agent_type.cgi
@@ -0,0 +1,63 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Agent Types',
+ 'menubar' => [ 'Agents' =>"${p}browse/agent.cgi", ],
+ 'html_init' => $html_init,
+ 'name' => 'agent types',
+ 'query' => { 'table' => 'agent_type',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY typenum', # 'ORDER BY atype',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Agent Type',
+ 'Packages',
+ ],
+ 'fields' => [ 'typenum',
+ 'atype',
+ $packages_sub,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+'Agent types define groups of packages that you can then assign to'.
+' particular agents.<BR><BR>'.
+qq!<A HREF="${p}edit/agent_type.cgi"><I>Add a new agent type</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM agent_type';
+
+#false laziness w/access_user.html
+my $packages_sub = sub {
+my $agent_type = shift;
+
+[ map {
+ my $type_pkgs = $_;
+ #my $part_pkg = $type_pkgs->part_pkg;
+ [
+ {
+ #'data' => $part_pkg->pkg. ' - '. $part_pkg->comment,
+ 'data' => $type_pkgs->pkg. ' - '.
+ ( $type_pkgs->custom ? '(CUSTOM) ' : '' ).
+ $type_pkgs->comment,
+ 'align' => 'left',
+ 'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart,
+ },
+ ];
+ }
+
+ $agent_type->type_pkgs_enabled
+];
+
+};
+
+my $link = [ $p.'edit/agent_type.cgi?', 'typenum' ];
+
+</%init>
diff --git a/httemplate/browse/cgp_rule.html b/httemplate/browse/cgp_rule.html
new file mode 100644
index 0000000..6f7b3e0
--- /dev/null
+++ b/httemplate/browse/cgp_rule.html
@@ -0,0 +1,93 @@
+<% include('elements/browse.html',
+ 'title' => "Rules for $svc_label: $svc_value",
+ 'name_singular' => 'rule',
+ 'html_init' => $html_init,
+ 'query' => { 'table' => 'cgp_rule',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'order_by' => 'ORDER BY priority DESC',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ 'Priority', 'Name', 'Conditions', 'Actions', '' ],
+ 'fields' => [ sub { shift->priority || 'Inactive'; },
+ 'name',
+ $condition_sub,
+ $action_sub,
+ sub { 'Delete'; },
+ ],
+ #'align'
+ 'links' => [ $edit_sub, $edit_sub, '', '', $del_sub ],
+ )
+%>
+<%init>
+
+$cgi->param('svcnum') =~ /^(\d+)$/ or die 'no svcnum';
+my $svcnum = $1;
+
+#agent virt so you can't do cross-agent communigate rules
+my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svcnum })
+ or die 'unknown svcnum';
+
+my $count_query = "SELECT COUNT(*) FROM cgp_rule WHERE svcnum = $svcnum";
+
+my($svc_label, $svc_value, $svcdb) = $cust_svc->label;
+
+
+my $view = FS::UI::Web::svc_url( 'm' => $m,
+ 'action' => 'view',
+ 'part_svc' => $cust_svc->part_svc,
+ 'svc' => $cust_svc,
+ );
+
+my $html_init =
+ qq(<A HREF="$view">View this $svc_label</A><BR><BR>).
+ qq!<A HREF="${p}edit/cgp_rule.html?svcnum=$svcnum">Add new rule</A><BR><BR>!.
+ qq!
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Are you sure you want to delete this rule?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+!;
+
+my $condition_sub = sub {
+ my $cgp_rule = shift;
+
+ [ map {
+ [
+ { data => $_->condition,
+ #align =>
+ },
+ { data => $_->op,
+ align => 'center',
+ },
+ { data => $_->params,
+ #align =>
+ },
+ ];
+ }
+ $cgp_rule->cgp_rule_condition
+ ];
+};
+
+my $action_sub = sub {
+ my $cgp_rule = shift;
+
+ [ map {
+ [
+ { data => $_->action },
+ #{ data => '<pre>'.$_->params.'</pre>' }, #gets very big.. limit to
+ { data => $_->params }, # some actions?
+ ];
+ }
+ $cgp_rule->cgp_rule_action
+ ];
+};
+
+my $edit_sub = [ $p.'edit/cgp_rule.html?', 'rulenum' ];
+my $del_sub = sub {
+ my $rulenum = shift->rulenum;
+ [ "javascript:areyousure('${p}misc/delete-cgp_rule.html?$rulenum')", '' ];
+};
+
+</%init>
diff --git a/httemplate/browse/cust_attachment.html b/httemplate/browse/cust_attachment.html
new file mode 100755
index 0000000..e19259d
--- /dev/null
+++ b/httemplate/browse/cust_attachment.html
@@ -0,0 +1,185 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Attachments',
+ 'menubar' => '',
+ 'name' => ($disabled ? 'deleted' : '') .' attachments',
+ 'html_init' => include('/elements/init_overlib.html') .
+ ($curuser->access_right('View deleted attachments') ? (
+ selflink('Show '.($disabled ? 'active' : 'deleted'),
+ show_deleted => (1-$disabled))) : ''),
+ 'html_form' =>
+ qq!<FORM NAME="attachForm" ACTION="$p/misc/cust_attachment.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="orderby" VALUE="$orderby">
+ <INPUT TYPE="hidden" NAME="show_deleted" VALUE="$disabled">!
+ ,
+ 'query' => { 'table' => 'cust_attachment',
+ 'hashref' => $hashref,
+ 'extra_sql' => 'ORDER BY '.$orderby,
+ },
+ 'count_query' => $count_query,
+ 'header' => [ selflink('#',orderby => 'attachnum'),
+ selflink('Customer',orderby => 'custnum'),
+ selflink('Date',orderby => '_date'),
+ selflink('Filename',orderby => 'filename'),
+ selflink('Size',orderby => 'length(body)'),
+ selflink('Uploaded by',orderby => 'otaker'),
+ selflink('Description',orderby => 'title'),
+ '', # checkbox column
+ ],
+ 'fields' => [
+ 'attachnum',
+ $sub_cust,
+ $sub_date,
+ 'filename',
+ $sub_size,
+ 'otaker',
+ 'title',
+ $sub_checkbox,
+ ],
+ 'links' => [ '',
+ [ $p.'view/cust_main.cgi?', 'custnum' ],
+ ],
+ 'link_onclicks' => [
+ '',
+ '',
+ '',
+ $sub_edit_link,
+ ],
+
+ #'links' => [
+ # '',
+ # '',
+ # '',
+ # '',
+ # '',
+ # '', #$acct_link,
+ # '',
+ 'html_foot' => $sub_foot,
+ )
+
+%>
+
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" if !$curuser->access_right('View attachments')
+ or !$curuser->access_right('Browse attachments');
+
+my $conf = new FS::Conf;
+
+my $noactions = 1;
+my $areboxes = 0;
+
+my $disabled = 0;
+
+if($cgi->param('show_deleted')) {
+ if ($curuser->access_right('View deleted attachments')) {
+ $disabled = 1;
+ if ($curuser->access_right('Purge attachment') or
+ $curuser->access_right('Undelete attachment')) {
+ $noactions = 0;
+ }
+ }
+ else {
+ die "access denied";
+ }
+}
+else {
+ if ($curuser->access_right('Delete attachment')) {
+ $noactions = 0;
+ }
+}
+
+my $hashref = $disabled ?
+ { disabled => { op => '>', value => 0 } } :
+ { disabled => '' };
+
+my $count_query = 'SELECT COUNT(*) FROM cust_attachment WHERE '. ($disabled ?
+ 'disabled > 0' : 'disabled IS NULL');
+
+my $orderby = $cgi->param('orderby') || 'custnum';
+
+my $sub_cust = sub {
+ my $c = qsearchs('cust_main', { custnum => shift->custnum } );
+ return $c ? $c->name : '<FONT COLOR="red"><B>(not found)</B></FONT>';
+};
+
+my $sub_date = sub {
+ time2str("%b %o, %Y", shift->_date);
+};
+
+my $sub_size = sub {
+ my $size = shift->size;
+ return $size if $size < 1024;
+ return int($size/1024).'K' if $size < 1048576;
+ return int($size/1048576).'M';
+};
+
+my $sub_checkbox = sub {
+ return '' if $noactions;
+ my $attach = shift;
+ my $attachnum = $attach->attachnum;
+ $areboxes = 1;
+ return qq!<INPUT NAME="attachnum$attachnum" TYPE="checkbox" VALUE="1">!;
+};
+
+my $sub_edit_link = sub {
+ my $attach = shift;
+ my $attachnum = $attach->attachnum;
+ my $custnum = $attach->custnum;
+ return include('/elements/popup_link_onclick.html',
+ action => popurl(2).'edit/cust_main_attach.cgi?'.
+ "custnum=$custnum;attachnum=$attachnum",
+ actionlabel => 'Edit attachment properties',
+ width => 510,
+ height => 315,
+ frame => 'top',
+ );
+};
+
+sub selflink {
+ my $label = shift;
+ my %new_param = @_;
+ my $param = $cgi->Vars;
+ my %old_param = %$param;
+ @{$param}{keys(%new_param)} = values(%new_param);
+ my $link = '<a href="'.$cgi->self_url.'">'.$label.'</a>';
+ %$param = %old_param;
+ return $link;
+}
+
+sub confirm {
+ my $action = shift;
+ my $onclick = "return(confirm('$action all selected files?'))";
+ return qq!onclick="$onclick"!;
+}
+
+my $sub_foot = sub {
+ return '' if ($noactions or !$areboxes);
+ my $foot =
+'<BR><INPUT TYPE="button" VALUE="Select all" onClick="setAll(true)">
+<INPUT TYPE="button" VALUE="Unselect all" onClick="setAll(false)">';
+ if ($disabled) {
+ if ($curuser->access_right('Undelete attachment')) {
+ $foot .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="Undelete selected">';
+ }
+ if ($curuser->access_right('Purge attachment')) {
+ $foot .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="Purge selected" '.confirm('Purge').'>';
+ }
+ }
+ else {
+ $foot .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="Delete selected" '.confirm('Delete').'>';
+ }
+ $foot .=
+'<SCRIPT TYPE="text/javascript">
+ function setAll(setTo) {
+ theForm = document.attachForm;
+ for (i=0,n=theForm.elements.length;i<n;i++)
+ if (theForm.elements[i].name.indexOf("attachnum") != -1)
+ theForm.elements[i].checked = setTo;
+ }
+</SCRIPT>';
+ return $foot;
+};
+
+</%init>
diff --git a/httemplate/browse/cust_category.html b/httemplate/browse/cust_category.html
new file mode 100644
index 0000000..4468d2f
--- /dev/null
+++ b/httemplate/browse/cust_category.html
@@ -0,0 +1,32 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Customer categories',
+ 'html_init' => $html_init,
+ 'name' => 'customer categories',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'cust_category',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY categorynum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#', 'Category' ],
+ 'fields' => [ 'categorynum', 'categoryname' ],
+ 'links' => [ $link, $link ],
+ )
+%>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ qq!<A HREF="${p}browse/cust_class.html">Customer classes</A><BR><BR>!.
+ 'Customer categories define groups of customer classes.<BR><BR>'.
+ qq!<A HREF="${p}edit/cust_category.html"><I>Add a customer category</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM cust_category';
+
+my $link = [ $p.'edit/cust_category.html?', 'categorynum' ];
+
+</%init>
diff --git a/httemplate/browse/cust_class.html b/httemplate/browse/cust_class.html
new file mode 100644
index 0000000..da303cf
--- /dev/null
+++ b/httemplate/browse/cust_class.html
@@ -0,0 +1,45 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Customer classes',
+ 'html_init' => $html_init,
+ 'name' => 'customer classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'cust_class',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY classnum',
+ },
+ 'count_query' => $count_query,
+ 'header' => $header,
+ 'fields' => $fields,
+ 'links' => $links,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Customer classes define groups of customer for reporting.<BR><BR>'.
+ qq!<A HREF="${p}edit/cust_class.html"><I>Add a customer class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM cust_class';
+
+my $link = [ $p.'edit/cust_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links = [ $link, $link ];
+
+my $cat_query = 'SELECT COUNT(*) FROM cust_class where categorynum IS NOT NULL';
+my $sth = dbh->prepare($cat_query)
+ or die "Error preparing $cat_query: ". dbh->errstr;
+$sth->execute
+ or die "Error executing $cat_query: ". $sth->errstr;
+if ($sth->fetchrow_arrayref->[0]) {
+ push @$header, 'Category';
+ push @$fields, 'categoryname';
+ push @$links, $link;
+}
+
+</%init>
diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi
new file mode 100755
index 0000000..04d1ae5
--- /dev/null
+++ b/httemplate/browse/cust_main_county.cgi
@@ -0,0 +1,524 @@
+<% include( 'elements/browse.html',
+ 'title' => "Tax Rates $title",
+ 'name_singular' => 'tax rate',
+ 'menubar' => \@menubar,
+ 'html_init' => $html_init,
+ 'html_posttotal' => $html_posttotal,
+ 'html_form' => '<FORM NAME="taxesForm">',
+ 'html_foot' => $html_foot,
+ 'query' => {
+ 'table' => 'cust_main_county',
+ 'hashref' => $hashref,
+ 'order_by' =>
+ 'ORDER BY country, state, county, taxclass',
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'header2' => \@header2,
+ 'fields' => \@fields,
+ 'align' => $align,
+ 'color' => \@color,
+ 'cell_style' => \@cell_style,
+ 'links' => \@links,
+ 'link_onclicks' => \@link_onclicks,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $exempt_sub = sub {
+ my $cust_main_county = shift;
+
+ my @exempt = ();
+ push @exempt,
+ sprintf("$money_char%.2f&nbsp;per&nbsp;month", $cust_main_county->exempt_amount )
+ if $cust_main_county->exempt_amount > 0;
+
+ push @exempt, 'Setup&nbsp;fee'
+ if $cust_main_county->setuptax =~ /^Y$/i;
+
+ push @exempt, 'Recurring&nbsp;fee'
+ if $cust_main_county->recurtax =~ /^Y$/i;
+
+ [ map [ {'data'=>$_} ], @exempt ];
+};
+
+my $cs_oldrow;
+my $cell_style;
+my $cell_style_sub = sub {
+ my $row = shift;
+ if ( $cs_oldrow ne $row ) {
+ if ( $cs_oldrow ) {
+ if ( $cs_oldrow->country ne $row->country ) {
+ $cell_style = 'border-top:1px solid #000000';
+ } elsif ( $cs_oldrow->state ne $row->state ) {
+ $cell_style = 'border-top:1px solid #cccccc'; #default?
+ } elsif ( $cs_oldrow->state eq $row->state ) {
+ #$cell_style = 'border-top:dashed 1px dark gray';
+ $cell_style = 'border-top:1px dashed #cccccc';
+ }
+ }
+ $cs_oldrow = $row;
+ }
+ return $cell_style;
+};
+
+#my $edit_link = [ "${p}edit/cust_main_county.html", 'taxnum' ];
+my $edit_link = [ 'javascript:void(0);', sub { ''; } ];
+
+my $edit_onclick = sub {
+ my $row = shift;
+ my $taxnum = $row->taxnum;
+ include( '/elements/popup_link_onclick.html',
+ 'action' => "${p}edit/cust_main_county.html?$taxnum",
+ 'actionlabel' => 'Edit tax rate',
+ 'height' => 420,
+ #default# 'width' => 540,
+ #default# 'color' => '#333399',
+ );
+};
+
+my $ex_oldrow;
+sub expand_link {
+ my %param = @_;
+
+ if ( $ex_oldrow eq $param{'row'} ) {
+ return '';
+ } else {
+ $ex_oldrow = $param{'row'};
+ }
+
+ my $taxnum = $param{'row'}->taxnum;
+ my $url = "${p}edit/cust_main_county-expand.cgi?$taxnum";
+
+ '<FONT SIZE="-1">'.
+ include( '/elements/popup_link.html',
+ 'label' => $param{'label'},
+ 'action' => $url,
+ 'actionlabel' => $param{'desc'},
+ 'height' => 420,
+ #default# 'width' => 540,
+ #default# 'color' => '#333399',
+ ).
+ '</FONT>';
+}
+
+sub collapse_link {
+ my %param = @_;
+
+ my $row = $param{'row'};
+ my $col = $param{'col'};
+ return ''
+ if $col eq 'county' and $row->city
+ || qsearch({
+ 'table' => 'cust_main_county',
+ 'hashref' => {
+ 'country' => $row->country,
+ 'state' => $row->state,
+ 'city' => { op=>'!=', value=>'' },
+ },
+ 'order_by' => 'LIMIT 1',
+ });
+
+ my %above = ( 'city' => 'county',
+ 'county' => 'state',
+ );
+
+ #XXX can still show the link when you have some counties broken down into
+ #cities and others not :/
+
+ my $taxnum = $param{'row'}->taxnum;
+ my $url = "${p}edit/process/cust_main_county-collapse.cgi?$taxnum";
+ $url = "javascript:collapse_areyousure('$url', '$col', '$above{$col}')";
+
+ qq(<FONT SIZE="-1"><A HREF="$url">$param{'label'}</A></FONT>);
+}
+
+
+sub separate_taxclasses_link {
+ my( $row ) = @_;
+ my $taxnum = $row->taxnum;
+ my $url = "${p}edit/process/cust_main_county-expand.cgi?taxclass=1;taxnum=$taxnum";
+
+ qq!<FONT SIZE="-1"><A HREF="$url">!;
+}
+
+#un-separate taxclasses too
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#my $conf = new FS::Conf;
+#my $money_char = $conf->config('money_char') || '$';
+my $enable_taxclasses = $conf->exists('enable_taxclasses');
+
+my @menubar;
+
+my $html_init = <<END;
+ <SCRIPT>
+ function collapse_areyousure(href,col,above) {
+ if (confirm('Are you sure you want to remove all ' + col + ' tax rates for this ' + above + '?') == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ Click on <u>add states</u> to specify a country's tax rates by state or province.
+ <BR>Click on <u>add counties</u> to specify a state's tax rates by county, or <u>remove counties</u> to remove per-county tax rates.
+ <BR>Click on <u>add cities</u> to specify a county's tax rates by city, or <u>remove cities</u> to remove per-city tax rates.
+END
+
+$html_init .= "<BR>Click on <u>separate taxclasses</u> to specify taxes per taxclass."
+ if $enable_taxclasses;
+$html_init .= '<BR><BR>';
+
+$html_init .= include('/elements/init_overlib.html');
+
+my $title = '';
+
+my $country = '';
+if ( $cgi->param('country') =~ /^(\w\w)$/ ) {
+ $country = $1;
+ $title = $country;
+}
+$cgi->delete('country');
+
+my $state = '';
+if ( $country && $cgi->param('state') =~ /^([\w \-\'\[\]]+)$/ ) {
+ $state = $1;
+ $title = "$state, $title";
+}
+$cgi->delete('state');
+
+my $county = '';
+if ( $country && $state &&
+ $cgi->param('county') =~
+ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/
+ )
+{
+ $county = $1;
+ if ( $county eq '__NONE__' ) {
+ $title = "No county, $title";
+ } else {
+ $title = "$county county, $title";
+ }
+}
+$cgi->delete('county');
+
+$title = " for $title" if $title;
+
+my $taxclass = '';
+if ( $cgi->param('taxclass') =~ /^([\w \-]+)$/ ) {
+ $taxclass = $1;
+ $title .= " for $taxclass tax class";
+}
+$cgi->delete('taxclass');
+
+if ( $country || $taxclass ) {
+ push @menubar, 'View all tax rates' => $p.'browse/cust_main_county.cgi';
+}
+
+$cgi->param('dummy', 1);
+
+my $filter_change =
+ "window.location = '". $cgi->self_url.
+ ";country=' + encodeURIComponent( document.getElementById('country').options[document.getElementById('country').selectedIndex].value ) + ".
+ "';state=' + encodeURIComponent( document.getElementById('state').options[document.getElementById('state').selectedIndex].value ) +".
+ "';county=' + encodeURIComponent( document.getElementById('county').options[document.getElementById('county').selectedIndex].value );";
+
+#restore this so pagination works
+$cgi->param('country', $country) if $country;
+$cgi->param('state', $state ) if $state;
+$cgi->param('county', $county ) if $county;
+$cgi->param('taxclass', $county ) if $taxclass;
+
+my $html_posttotal =
+ '<BR>( show country: '.
+ include('/elements/select-country.html',
+ 'country' => $country,
+ 'onchange' => $filter_change,
+ 'empty_label' => '(all)',
+ 'disable_empty' => 0,
+ 'disable_stateupdate' => 1,
+ );
+
+my %states_hash = $country ? states_hash($country) : ();
+if ( scalar(keys(%states_hash)) > 1 ) {
+ $html_posttotal .=
+ ' show state: '.
+ include('/elements/select-state.html',
+ 'country' => $country,
+ 'state' => $state,
+ 'onchange' => $filter_change,
+ 'empty_label' => '(all)',
+ 'disable_empty' => 0,
+ 'disable_countyupdate' => 1,
+ );
+} else {
+ $html_posttotal .=
+ '<SELECT NAME="state" ID="state" STYLE="display:none">'.
+ ' <OPTION VALUE="" SELECTED>'.
+ '</SELECT>';
+}
+
+my @counties = ( $country && $state ) ? counties($state, $country) : ();
+if ( scalar(@counties) > 1 ) {
+ $html_posttotal .=
+ ' show county: '.
+ include('/elements/select-county.html',
+ 'country' => $country,
+ 'state' => $state,
+ 'county' => $county,
+ 'onchange' => $filter_change,
+ 'empty_label' => '(all)',
+ 'empty_data_label' => '(none)',
+ 'empty_data_value' => '__NONE__',
+ 'disable_empty' => 0,
+ 'disable_cityupdate' => 1,
+ );
+} else {
+ $html_posttotal .=
+ '<SELECT NAME="county" ID="county" STYLE="display:none">'.
+ ' <OPTION VALUE="" SELECTED>'.
+ '</SELECT>';
+}
+
+$html_posttotal .= ' )';
+
+my $bulk_popup_link =
+ include( '/elements/popup_link_onclick.html',
+ 'action' => "${p}edit/bulk-cust_main_county.html?taxnum=MAGIC_taxnum_MAGIC",
+ 'actionlabel' => 'Bulk add new tax',
+ 'nofalse' => 1,
+ 'height' => 420,
+ #default# 'width' => 540,
+ #default# 'color' => '#333399',
+ );
+
+my $html_foot = <<END;
+<SCRIPT TYPE="text/javascript">
+
+ function setAll(setTo) {
+ theForm = document.taxesForm;
+ for (i=0,n=theForm.elements.length;i<n;i++) {
+ if (theForm.elements[i].name.indexOf("cust_main_county") != -1) {
+ theForm.elements[i].checked = setTo;
+ }
+ }
+ }
+
+ function toggleAll() {
+ theForm = document.taxesForm;
+ for (i=0,n=theForm.elements.length;i<n;i++) {
+ if (theForm.elements[i].name.indexOf("cust_main_county") != -1) {
+ if ( theForm.elements[i].checked == true ) {
+ theForm.elements[i].checked = false;
+ } else {
+ theForm.elements[i].checked = true;
+ }
+ }
+ }
+ }
+
+ function bulkPopup(action) {
+ var bulk_popup_link = "$bulk_popup_link";
+ var bulkstring = '';
+ theForm = document.taxesForm;
+ for (i=0,n=theForm.elements.length;i<n;i++) {
+ if ( theForm.elements[i].name.indexOf("cust_main_county") != -1
+ && theForm.elements[i].checked == true
+ ) {
+ var name = theForm.elements[i].name;
+ var taxnum = name.replace(/cust_main_county/, '');
+ if ( bulkstring != '' ) {
+ bulkstring = bulkstring + ',';
+ }
+ bulkstring = bulkstring + taxnum;
+
+ }
+ }
+ bulkstring = bulkstring + ';action=' + action;
+ if ( bulk_popup_link.length > 1920 ) { // IE 2083 URL limit
+ alert('Too many selections'); // should do some session thing...
+ return false;
+ }
+ bulk_popup_link = bulk_popup_link.replace(/MAGIC_taxnum_MAGIC/, bulkstring);
+ eval(bulk_popup_link);
+ }
+
+</SCRIPT>
+
+<BR>
+<A HREF="javascript:setAll(true)">select all</A> |
+<A HREF="javascript:setAll(false)">unselect all</A> |
+<A HREF="javascript:toggleAll()">toggle all</A>
+<BR><BR>
+<A HREF="javascript:void(0);" onClick="bulkPopup('add');">Add new tax to selected</A>
+|
+<A HREF="javascript:void(0);" onClick="bulkPopup('edit');">Bulk edit selected</A>
+
+END
+
+my $hashref = {};
+my $count_query = 'SELECT COUNT(*) FROM cust_main_county';
+if ( $country ) {
+ $hashref->{'country'} = $country;
+ $count_query .= ' WHERE country = '. dbh->quote($country);
+}
+if ( $state ) {
+ $hashref->{'state'} = $state;
+ $count_query .= ' AND state = '. dbh->quote($state);
+}
+if ( $county ) {
+ if ( $county eq '__NONE__' ) {
+ $hashref->{'county'} = '';
+ $count_query .= " AND ( county = '' OR county IS NULL ) ";
+ } else {
+ $hashref->{'county'} = $county;
+ $count_query .= ' AND county = '. dbh->quote($county);
+ }
+}
+if ( $taxclass ) {
+ $hashref->{'taxclass'} = $taxclass;
+ $count_query .= ( $count_query =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
+ ' taxclass = '. dbh->quote($taxclass);
+}
+
+
+$cell_style = '';
+
+my @header = ( 'Country', 'State/Province', 'County', 'City' );
+my @header2 = ( '', '', '', '', );
+my @links = ( '', '', '', '', );
+my @link_onclicks = ( '', '', '', '', );
+my $align = 'llll';
+
+my @fields = (
+ sub { my $country = shift->country;
+ code2country($country). " ($country)";
+ },
+ sub { state_label($_[0]->state, $_[0]->country).
+ ( $_[0]->state
+ ? ''
+ : '&nbsp'. expand_link( desc => 'Add States',
+ row => $_[0],
+ label => 'add&nbsp;states',
+ )
+ )
+ },
+ sub { $_[0]->county
+ ? $_[0]->county. '&nbsp'.
+ collapse_link( col => 'county',
+ label=> 'remove&nbsp;counties',
+ row => $_[0],
+ )
+ : '(all)&nbsp'.
+ expand_link( desc => 'Add Counties',
+ row => $_[0],
+ label => 'add&nbsp;counties',
+ );
+ },
+ sub { $_[0]->city
+ ? $_[0]->city. '&nbsp'.
+ collapse_link( col => 'city',
+ label=> 'remove&nbsp;cities',
+ row => $_[0],
+ )
+ : '(all)&nbsp'.
+ expand_link( desc => 'Add Cities',
+ row => $_[0],
+ label => 'add&nbsp;cities',
+ );
+ },
+);
+
+my @color = (
+ '000000',
+ sub { shift->state ? '000000' : '999999' },
+ sub { shift->county ? '000000' : '999999' },
+ sub { shift->city ? '000000' : '999999' },
+);
+
+if ( $conf->exists('enable_taxclasses') ) {
+ push @header, qq!Tax class (<A HREF="${p}edit/part_pkg_taxclass.html">add new</A>)!;
+ push @header2, '(per-package classification)';
+ push @fields, sub { $_[0]->taxclass || '(all)&nbsp'.
+ separate_taxclasses_link($_[0], 'Separate Taxclasses').
+ 'separate&nbsp;taxclasses</A></FONT>'
+ };
+ push @color, sub { shift->taxclass ? '000000' : '999999' };
+ push @links, '';
+ push @link_onclicks, '';
+ $align .= 'l';
+}
+
+push @header,
+ '', #checkbox column
+ 'Tax name',
+ 'Rate', #'Tax',
+ 'Exemptions',
+ ;
+
+push @header2,
+ '',
+ '(printed on invoices)',
+ '',
+ '',
+ ;
+
+my $newregion = 1;
+my $cb_oldrow = '';
+my $cb_sub = sub {
+ my $cust_main_county = shift;
+
+ if ( $cb_oldrow ) {
+ if ( $cb_oldrow->city ne $cust_main_county->city
+ || $cb_oldrow->county ne $cust_main_county->county
+ || $cb_oldrow->state ne $cust_main_county->state
+ || $cb_oldrow->country ne $cust_main_county->country
+ || $cb_oldrow->taxclass ne $cust_main_county->taxclass )
+ {
+ $newregion = 1;
+ } else {
+ $newregion = 0;
+ }
+
+ } else {
+ $newregion = 1;
+ }
+ $cb_oldrow = $cust_main_county;
+
+ if ( $newregion ) {
+ my $taxnum = $cust_main_county->taxnum;
+ qq!<INPUT NAME="cust_main_county$taxnum" TYPE="checkbox" VALUE="1">!;
+ } else {
+ '';
+ }
+};
+
+push @fields,
+ $cb_sub,
+ sub { shift->taxname || 'Tax' },
+ sub { shift->tax. '%&nbsp;<FONT SIZE="-1">(edit)</FONT>' },
+ $exempt_sub,
+;
+
+push @color,
+ '000000',
+ sub { shift->taxname ? '000000' : '666666' },
+ sub { shift->tax ? '000000' : '666666' },
+ '000000',
+;
+
+$align .= 'clrl';
+
+my @cell_style = map $cell_style_sub, (1..scalar(@header));
+
+push @links, '', '', $edit_link, '';
+push @link_onclicks, '', '', $edit_onclick, '';
+
+</%init>
diff --git a/httemplate/browse/discount.html b/httemplate/browse/discount.html
new file mode 100644
index 0000000..f7ef0b3
--- /dev/null
+++ b/httemplate/browse/discount.html
@@ -0,0 +1,28 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Discounts',
+ 'name' => 'discounts',
+ 'menubar' => [ 'Add a new discount' =>
+ $p.'edit/discount.html',
+ ],
+ 'query' => { 'table' => 'discount', },
+ 'count_query' => 'SELECT COUNT(*) FROM discount',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'header' => [ '#', 'Name', 'Discount', ],
+ 'fields' => [ 'discountnum',
+ 'name',
+ 'description',
+ ],
+ 'links' => [ $link,
+ $link,
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/discount.html?", 'discountnum' ];
+
+</%init>
diff --git a/httemplate/browse/elements/browse.html b/httemplate/browse/elements/browse.html
new file mode 100644
index 0000000..513c2c4
--- /dev/null
+++ b/httemplate/browse/elements/browse.html
@@ -0,0 +1,6 @@
+<% include( '/search/elements/search.html',
+ 'disable_download' => 1,
+ 'disable_nonefound' => 1,
+ @_,
+ )
+%>
diff --git a/httemplate/browse/inventory_class.html b/httemplate/browse/inventory_class.html
new file mode 100644
index 0000000..2d85f12
--- /dev/null
+++ b/httemplate/browse/inventory_class.html
@@ -0,0 +1,39 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Inventory Classes',
+ 'name' => 'inventory classes',
+ 'menubar' => $menubar,
+ 'query' => { 'table' => 'inventory_class', },
+ 'count_query' => 'SELECT COUNT(*) FROM inventory_class',
+ 'header' => [ '#', 'Inventory class', 'Inventory' ],
+ 'fields' => [ 'classnum',
+ 'classname',
+ FS::inventory_class->countcell_factory(
+ 'p'=>$p,
+ ),
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit inventory')
+ || $curuser->access_right('Edit global inventory')
+ || $curuser->access_right('Configuration');
+
+my $menubar = $curuser->access_right('Configuration')
+ ? [ 'Add a new inventory class' =>
+ $p.'edit/inventory_class.html',
+ ]
+ : [];
+
+my $link = $curuser->access_right('Configuration')
+ ? [ "${p}edit/inventory_class.html?", 'classnum' ]
+ : '';
+
+</%init>
diff --git a/httemplate/browse/invoice_template.html b/httemplate/browse/invoice_template.html
new file mode 100644
index 0000000..0bbfb24
--- /dev/null
+++ b/httemplate/browse/invoice_template.html
@@ -0,0 +1,124 @@
+<% include("/elements/header.html", 'Invoice templates') %>
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Template</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">HTML</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Print/PDF (typeset)</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Plaintext</TH>
+</TR>
+
+% foreach my $templatename ( '', @templatenames ) {
+% my $tname = length($templatename) ? "_$templatename" : '';
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $display = length($templatename) ? $templatename : '<i>(Default)</i>';
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $display %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+
+% my( $logo_label, $logo_link_label)= length( $templatename )
+% ? labels("logo_$templatename.png")
+% : ( '', 'edit' );
+ <% $logo_label %> Logo
+ (<A HREF="<% $p %>edit/invoice_logo.html?type=png;name=<% $templatename %>"><% $logo_link_label %></A>)
+ <BR>
+
+% foreach my $suffix (qw( returnaddress notes footer), '' ) {
+% my $file = "invoice_html$suffix$tname";
+% my($label, $link_label) = length($templatename)
+% ? labels($file)
+% : ( '', 'edit' );
+
+ <% $label %> <% $suffix2name{$suffix} %>
+ (<A HREF="<% $p %>edit/invoice_template.html?type=html;suffix=<% $suffix %>;name=<% $templatename %>"><% $link_label %></A>)
+ <BR>
+
+% }
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+
+% my( $logo_label, $logo_link_label)= length( $templatename )
+% ? labels("logo_$templatename.eps")
+% : ( '', 'edit' );
+ <% $logo_label %> Logo
+ (<A HREF="<% $p %>edit/invoice_logo.html?type=eps;name=<% $templatename %>"><% $logo_link_label %></A>)
+ <BR>
+
+% foreach my $suffix (qw( returnaddress notes footer smallfooter), '' ) {
+% my $file = "invoice_latex$suffix$tname";
+% my($label, $link_label) = length($templatename)
+% ? labels($file)
+% : ( '', 'edit' );
+
+ <% $label %> <% $suffix2name{$suffix} %>
+ (<A HREF="<% $p %>edit/invoice_template.html?type=latex;suffix=<% $suffix %>;name=<% $templatename %>"><% $link_label %></A>)
+ <BR>
+
+% }
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+
+% my( $txt_label, $txtlink_label)=
+% length( $templatename )
+% ? labels("invoice_template_$templatename.png")
+% : ( 'Main template', 'edit' );
+ <% $txt_label %>
+ (<A HREF="<% $p %>edit/invoice_template.html?type=text;name=<% $templatename %>"><% $txtlink_label %></A>)
+
+ </TD>
+
+ </TR>
+
+% }
+
+<% include("/elements/footer.html") %>
+
+<%once>
+
+my %suffix2name = (
+ 'returnaddress' => 'Return address',
+ 'notes' => 'Notes',
+ 'footer' => 'Footer',
+ 'smallfooter' => 'Small footer',
+ '' => 'Main template',
+);
+
+my $conf = new FS::Conf;
+
+sub labels {
+ my $filename = shift;
+ if ( $conf->exists($filename) ) {
+ ( 'Custom', 'edit' );
+ } else {
+ ( 'Standard', 'customize' );
+ }
+}
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @templatenames = $conf->invoice_templatenames;
+
+</%init>
diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi
new file mode 100755
index 0000000..2c916dc
--- /dev/null
+++ b/httemplate/browse/msgcat.cgi
@@ -0,0 +1,44 @@
+<% include('/elements/header.html', "View Message catalog", menubar(
+ 'Edit message catalog' => $p. "edit/msgcat.cgi",
+)) %>
+<% $widget->html %>
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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;
+ },
+
+);
+
+</%init>
diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi
new file mode 100755
index 0000000..b5e0ef8
--- /dev/null
+++ b/httemplate/browse/nas.cgi
@@ -0,0 +1,82 @@
+%print header('NAS ports');
+%
+%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</
+%
+
+<%init>
+
+#this hasn't been used in ages, and isn't linked from anywhere...
+die 'NAS browse not currently active';
+
+</%init>
diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi
new file mode 100755
index 0000000..11bc14e
--- /dev/null
+++ b/httemplate/browse/part_bill_event.cgi
@@ -0,0 +1,122 @@
+<% include('/elements/header.html', 'Invoice Event Listing') %>
+
+ <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken on open invoices. Any events still listed here should be migrated to new-style events.</FONT><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> )'; }
+%>
+<BR><BR>
+% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
+% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly';
+% foreach my $payby ( keys %payby ) {
+% my $oldfreq = '';
+%
+% my @payby_part_bill_event =
+% grep { $payby eq $_->payby }
+% sort { ( $a->freq || '1d') cmp ( $b->freq || '1d' ) # for now
+% || $a->seconds <=> $b->seconds
+% || $a->weight <=> $b->weight
+% || $a->eventpart <=> $b->eventpart
+% }
+% @part_bill_event;
+%
+%
+% if ( @payby_part_bill_event ) {
+
+
+ <% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+%
+% foreach my $part_bill_event ( @payby_part_bill_event ) {
+% my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart;
+% my $delay = duration_exact($part_bill_event->seconds);
+% ( my $plandata = $part_bill_event->plandata ) =~ s/\n/<BR>/go;
+% my $freq = $part_bill_event->freq || '1d';
+% my $reason = $part_bill_event->reasontext ;
+%
+% if ( $oldfreq ne $freq ) {
+
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#999999" COLSPAN=<% $cgi->param('showdisabled') ? 7 : 8 %>><% ucfirst($freq{$freq}) %> event tests for <FONT SIZE="+1"><I><% $payby{$payby} %> customers</I></FONT></TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">After</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Reason</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Options</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Code</TH>
+ </TR>
+%
+% $oldfreq = $freq;
+% $bgcolor = '';
+%
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+
+
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>">
+ <% $part_bill_event->eventpart %></A></TD>
+% unless ( $cgi->param('showdisabled') ) {
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $part_bill_event->disabled ? 'DISABLED' : '' %></TD>
+% }
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>">
+ <% $part_bill_event->event %></A></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $delay %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $part_bill_event->plan %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $reason %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $plandata %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="-1">
+ <% $part_bill_event->eventcode %></FONT></TD>
+ </TR>
+% }
+
+ </TABLE>
+ <BR><BR>
+% }
+% }
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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);
+
+</%init>
diff --git a/httemplate/browse/part_device.html b/httemplate/browse/part_device.html
new file mode 100644
index 0000000..5c8fde3
--- /dev/null
+++ b/httemplate/browse/part_device.html
@@ -0,0 +1,27 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Phone device types',
+ 'name' => 'phone device types',
+ 'menubar' => [ 'Add a new device type' =>
+ $p.'edit/part_device.html',
+ 'Import device types' =>
+ $p.'misc/part_device-import.html',
+ ],
+ 'query' => { 'table' => 'part_device', },
+ 'count_query' => 'SELECT COUNT(*) FROM part_device',
+ 'header' => [ '#', 'Device type' ],
+ 'fields' => [ 'devicepart',
+ 'devicename',
+ ],
+ 'links' => [ $link,
+ $link,
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/part_device.html?", 'devicepart' ];
+
+</%init>
diff --git a/httemplate/browse/part_event.html b/httemplate/browse/part_event.html
new file mode 100644
index 0000000..674004b
--- /dev/null
+++ b/httemplate/browse/part_event.html
@@ -0,0 +1,167 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Billing Event Definitions',
+ 'html_init' => $html_init,
+ 'name' => 'billing event definitions',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global billing events',
+ 'agent_pos' => 3,
+ 'query' => { 'select' => 'part_event.*',
+ 'table' => 'part_event',
+ 'addl_from' => $join_conditions,
+ 'hashref' => {},
+ 'order_by' => $order_conditions,
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Event',
+ 'Type',
+ 'Check freq.',
+ 'Conditions',
+ 'Action',
+ ],
+ 'fields' => [ 'eventpart',
+ 'event',
+ $eventtable_sub,
+ $check_freq_sub,
+ $conditions_sub,
+ $action_sub,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ '',
+ '',
+ '',
+ ],
+ 'align' => 'rllccc',
+ )
+%>
+<%once>
+
+my $eventtable_labels = FS::part_event->eventtable_labels;
+my $eventtable_sub = sub { $eventtable_labels->{ shift->eventtable }; };
+
+my $check_freq_labels = FS::part_event->check_freq_labels;
+my $check_freq_sub = sub { $check_freq_labels->{ shift->check_freq }; };
+
+my $conditions_sub = sub {
+ my $part_event = shift;
+ my $addl = 0;
+
+ [
+ map {
+ my $part_event_condition = $_;
+ my %options = $part_event_condition->options;
+
+ [
+ {
+ 'data' => $part_event_condition->description,
+ 'width' => '100%',
+ 'align' => 'center',
+ 'colspan' => 2,
+ 'style' => ( $addl++ ? 'border-top: 1px solid gray' : '' ),
+ },
+ ],
+
+ map {
+
+ my $data = $options{$_};
+ if ( ref($data) ) {
+ $data = join('<BR>', keys %$data); #XXX display hash values too?
+ }
+
+ [
+ {
+ 'data' => $part_event_condition->option_label($_). ':',
+ 'align' => 'right',
+ 'valign' => 'top',
+ 'size' => '-1',
+ },
+ {
+ 'data' => $data,
+ 'align' => 'left',
+ 'size' => '-1',
+ },
+ ];
+
+ } keys %options
+
+ }
+ $part_event->part_event_condition
+
+ ];
+
+};
+
+my $action_sub = sub {
+ my $part_event = shift;
+
+ my %options = $part_event->options;
+
+ [
+
+ [
+ {
+ 'data' => $part_event->description,
+ 'width' => '100%',
+ 'align' => 'center',
+ 'colspan' => 2,
+ },
+ ],
+
+ map {
+ [
+ {
+ 'data' => $part_event->option_label($_). ':',
+ 'align' => 'right',
+ 'size' => '-1',
+ },
+ {
+ 'data' => $options{$_},
+ 'align' => 'left',
+ 'size' => '-1',
+ },
+ ];
+ }
+
+ keys %options
+ ];
+
+};
+
+my $link = [ $p.'edit/part_event.html?', 'eventpart' ];
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit billing events')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global billing events');
+
+my $html_init =
+ #XXX better description
+ 'Events are billing, collection or other actions triggered when certain '.
+ 'customer, invoice, package or other conditions are met.<BR><BR>'.
+ qq!<FORM METHOD="POST" ACTION="${p}edit/part_event.html">!.
+ qq!<A HREF="${p}edit/part_event.html"><I>Add a new event</I></A>!.
+ '&nbsp;or&nbsp;<SELECT NAME="clone"><OPTION></OPTION>';
+
+foreach my $part_event ( qsearch('part_event', {'diabled'=>''}) ) {
+ $html_init .= '<OPTION VALUE="'. $part_event->eventpart. '">'.
+ $part_event->event. '</OPTION>';
+}
+
+$html_init .= '</SELECT><INPUT TYPE="submit" VALUE="Clone existing event">'.
+ '</FORM><BR>';
+
+my $count_query = 'SELECT COUNT(*) FROM part_event WHERE '.
+ $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'Edit global billing events',
+ );
+
+my $join_conditions = FS::part_event_condition->join_conditions_sql;
+my $order_conditions = FS::part_event_condition->order_conditions_sql;
+
+</%init>
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
new file mode 100755
index 0000000..1cd2013
--- /dev/null
+++ b/httemplate/browse/part_export.cgi
@@ -0,0 +1,65 @@
+<% include("/elements/header.html", "Export Listing") %>
+
+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>
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+ <TR>
+ <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Export</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Options</TH>
+ </TR>
+
+% foreach my $part_export ( sort {
+% $a->getfield('exportnum') <=> $b->getfield('exportnum')
+% } qsearch('part_export',{})
+% ) {
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $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 CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <% itable() %>
+% my %opt = $part_export->options;
+% foreach my $opt ( keys %opt ) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top" WIDTH="33%"><% $opt %>:&nbsp;</TD>
+ <TD ALIGN="left" WIDTH="67%"><% encode_entities($opt{$opt}) %></TD>
+ </TR>
+% }
+
+ </TABLE>
+ </TD>
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+</%init>
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
new file mode 100755
index 0000000..e226ce1
--- /dev/null
+++ b/httemplate/browse/part_pkg.cgi
@@ -0,0 +1,443 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Package Definitions',
+ 'html_init' => $html_init,
+ 'html_posttotal' => $html_posttotal,
+ 'name' => 'package definitions',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 4,
+ 'agent_virt' => 1,
+ 'agent_null_right' => [ $edit, $edit_global ],
+ 'agent_null_right_link' => $edit_global,
+ 'agent_pos' => 6,
+ 'query' => { 'select' => $select,
+ 'table' => 'part_pkg',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY $orderby"
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => $align,
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $edit = 'Edit package definitions';
+my $edit_global = 'Edit global package definitions';
+my $acl_edit = $curuser->access_right($edit);
+my $acl_edit_global = $curuser->access_right($edit_global);
+my $acl_config = $curuser->access_right('Configuration'); #to edit services
+ #and agent types
+
+die "access denied"
+ unless $acl_edit || $acl_edit_global;
+
+my $conf = new FS::Conf;
+my $taxclasses = $conf->exists('enable_taxclasses');
+my $money_char = $conf->config('money_char') || '$';
+
+my $select = '*';
+my $orderby = 'pkgpart';
+my %hash = ();
+my $extra_count = '';
+
+if ( $cgi->param('active') ) {
+ $orderby = 'num_active DESC';
+}
+
+my @where = ();
+
+#if ( $cgi->param('activeONLY') ) {
+# push @where, ' WHERE num_active > 0 '; #XXX doesn't affect count...
+#}
+
+if ( $cgi->param('recurring') ) {
+ $hash{'freq'} = { op=>'!=', value=>'0' };
+ $extra_count = " freq != '0' ";
+}
+
+my $classnum = '';
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ $classnum = $1;
+ push @where, $classnum ? "classnum = $classnum"
+ : "classnum IS NULL";
+}
+$cgi->delete('classnum');
+
+if ( $cgi->param('missing_recur_fee') ) {
+ push @where, "0 = ( SELECT COUNT(*) FROM part_pkg_option
+ WHERE optionname = 'recur_fee'
+ AND part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND CAST ( optionvalue AS NUMERIC ) > 0
+ )";
+}
+
+push @where, FS::part_pkg->curuser_pkgs_sql
+ unless $acl_edit_global;
+
+my $extra_sql = scalar(@where)
+ ? ( scalar(keys %hash) ? ' AND ' : ' WHERE ' ).
+ join( 'AND ', @where)
+ : '';
+
+my $agentnums = join(',', $curuser->agentnums);
+my $count_cust_pkg = "
+ SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )
+ WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+ AND cust_main.agentnum IN ($agentnums)
+";
+
+$select = "
+
+ *,
+
+ ( $count_cust_pkg
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND ( susp IS NULL OR susp = 0 )
+ ) AS num_active,
+
+ ( $count_cust_pkg
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND susp IS NOT NULL AND susp != 0
+ ) AS num_suspended,
+
+ ( $count_cust_pkg
+ AND cancel IS NOT NULL AND cancel != 0
+ ) AS num_cancelled
+
+";
+
+my $html_init;
+#unless ( $cgi->param('active') ) {
+ $html_init = qq!
+ 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>
+ <FORM METHOD="POST" ACTION="${p}edit/part_pkg.cgi">
+ <A HREF="${p}edit/part_pkg.cgi"><I>Add a new package definition</I></A>
+ or
+ !.include('/elements/select-part_pkg.html', 'element_name' => 'clone' ). qq!
+ <INPUT TYPE="submit" VALUE="Clone existing package">
+ </FORM>
+ <BR><BR>
+ !;
+#}
+
+$cgi->param('dummy', 1);
+
+my $filter_change =
+ qq(\n<SCRIPT TYPE="text/javascript">\n).
+ "function filter_change() {".
+ " window.location = '". $cgi->self_url.
+ ";classnum=' + document.getElementById('classnum').options[document.getElementById('classnum').selectedIndex].value".
+ "}".
+ "\n</SCRIPT>\n";
+
+#restore this so pagination works
+$cgi->param('classnum', $classnum) if length($classnum);
+
+my $html_posttotal =
+ "$filter_change\n<BR>( show class: ".
+ include('/elements/select-pkg_class.html',
+ #'curr_value' => $classnum,
+ 'value' => $classnum, #insist on 0 :/
+ 'onchange' => 'filter_change()',
+ 'pre_options' => [ '-1' => 'all',
+ '0' => '(none)', ],
+ 'disable_empty' => 1,
+ ).
+ ' )';
+
+my $recur_toggle = $cgi->param('recurring') ? 'show' : 'hide';
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 );
+
+$html_posttotal .=
+ '( <A HREF="'. $cgi->self_url.'">'. "$recur_toggle one-time charges</A> )";
+
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 ); #put it back
+
+# ------
+
+my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ];
+
+my @header = ( '#', 'Package', 'Comment', 'Custom' );
+my @fields = ( 'pkgpart', 'pkg', 'comment',
+ sub{ '<B><FONT COLOR="#0000CC">'.$_[0]->custom.'</FONT></B>' }
+ );
+my $align = 'rllc';
+my @links = ( $link, $link, '', '' );
+
+unless ( 0 ) { #already showing only one class or something?
+ push @header, 'Class';
+ push @fields, sub { shift->classname || '(none)'; };
+ $align .= 'l';
+}
+
+if ( $conf->exists('pkg-addon_classnum') ) {
+ push @header, "Add'l order class";
+ push @fields, sub { shift->addon_classname || '(none)'; };
+ $align .= 'l';
+}
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+
+tie my %plan_labels, 'Tie::IxHash',
+ map { $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
+ keys %plans;
+
+push @header, 'Pricing';
+$align .= 'r'; #?
+push @fields, sub {
+ my $part_pkg = shift;
+ (my $plan = $plan_labels{$part_pkg->plan} ) =~ s/ /&nbsp;/g;
+ my $is_recur = ( $part_pkg->freq ne '0' );
+
+ [
+ [
+ { data =>$plan,
+ align=>'center',
+ colspan=>2,
+ },
+ ],
+ [
+ { data =>$money_char.
+ sprintf('%.2f', $part_pkg->option('setup_fee') ),
+ align=>'right'
+ },
+ { data => ( $is_recur ? ' setup' : ' one-time' ),
+ align=>'left',
+ },
+ ],
+ [
+ { data=>( $is_recur
+ ? $money_char.sprintf('%.2f ', $part_pkg->option('recur_fee') )
+ : $part_pkg->freq_pretty
+ ),
+ align=> ( $is_recur ? 'right' : 'center' ),
+ colspan=> ( $is_recur ? 1 : 2 ),
+ },
+ ( $is_recur
+ ? { data => ( $is_recur ? $part_pkg->freq_pretty : '' ),
+ align=>'left',
+ }
+ : ()
+ ),
+ ],
+ ( map {
+ my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Add-on:&nbsp;'.$dst_pkg->pkg_comment,
+ align=>'center', #?
+ colspan=>2,
+ }
+ ]
+ }
+ $part_pkg->bill_part_pkg_link
+ ),
+ ];
+
+# $plan_labels{$part_pkg->plan}.'<BR>'.
+# $money_char.sprintf('%.2f setup<BR>', $part_pkg->option('setup_fee') ).
+# ( $part_pkg->freq ne '0'
+# ? $money_char.sprintf('%.2f ', $part_pkg->option('recur_fee') )
+# : ''
+# ).
+# $part_pkg->freq_pretty; #.'<BR>'
+};
+
+###
+# Agent goes here if displayed
+###
+
+#agent type
+if ( $acl_edit_global ) {
+ #really we just want a count, but this is fine unless someone has tons
+ my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+ if ( scalar(@all_agent_types) > 1 ) {
+ push @header, 'Agent types';
+ my $typelink = $p. 'edit/agent_type.cgi?';
+ push @fields, sub { my $part_pkg = shift;
+ [
+ map { my $agent_type = $_->agent_type;
+ [
+ { 'data' => $agent_type->atype, #escape?
+ 'align' => 'left',
+ 'link' => ( $acl_config
+ ? $typelink.
+ $agent_type->typenum
+ : ''
+ ),
+ },
+ ];
+ }
+ $part_pkg->type_pkgs
+ ];
+ };
+ $align .= 'l';
+ }
+}
+
+#if ( $cgi->param('active') ) {
+ push @header, 'Customer<BR>packages';
+ my %col = (
+ 'active' => '00CC00',
+ 'suspended' => 'FF9900',
+ 'cancelled' => 'FF0000',
+ #'one-time charge' => '000000',
+ 'charge' => '000000',
+ );
+ my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart=';
+ push @fields, sub { my $part_pkg = shift;
+ [
+ map {
+ my $magic = $_;
+ my $label = $_;
+ if ( $magic eq 'active' && $part_pkg->freq == 0 ) {
+ $magic = 'inactive';
+ #$label = 'one-time charge',
+ $label = 'charge',
+ }
+
+ [
+ {
+ 'data' => '<B><FONT COLOR="#'. $col{$label}. '">'.
+ $part_pkg->get("num_$_").
+ '</FONT></B>',
+ 'align' => 'right',
+ },
+ {
+ 'data' => $label.
+ ( $part_pkg->get("num_$_") != 1
+ && $label =~ /charge$/
+ ? 's'
+ : ''
+ ),
+ 'align' => 'left',
+ 'link' => ( $part_pkg->get("num_$_")
+ ? $cust_pkg_link.
+ $part_pkg->pkgpart.
+ ";magic=$magic"
+ : ''
+ ),
+ },
+ ],
+ } (qw( active suspended cancelled ))
+ ]; };
+ $align .= 'r';
+#}
+
+if ( $taxclasses ) {
+ push @header, 'Taxclass';
+ push @fields, sub { shift->taxclass() || '&nbsp;'; };
+ $align .= 'l';
+}
+
+push @header, 'Plan options',
+ 'Services';
+ #'Service', 'Quan', 'Primary';
+
+push @fields,
+ sub {
+ my $part_pkg = shift;
+ if ( $part_pkg->plan ) {
+
+ my %options = $part_pkg->options;
+
+ [ map {
+ [
+ { 'data' => $_,
+ 'align' => 'right',
+ },
+ { 'data' => $part_pkg->format($_,$options{$_}),
+ 'align' => 'left',
+ },
+ ];
+ }
+ grep { $options{$_} =~ /\S/ }
+ grep { $_ !~ /^(setup|recur)_fee$/ }
+ keys %options
+ ];
+
+ } else {
+
+ [ map { [
+ { 'data' => uc($_),
+ 'align' => 'right',
+ },
+ {
+ 'data' => $part_pkg->$_(),
+ 'align' => 'left',
+ },
+ ];
+ }
+ (qw(setup recur))
+ ];
+
+ }
+
+ },
+
+ sub {
+ my $part_pkg = shift;
+
+ [
+ (map {
+ my $pkg_svc = $_;
+ my $part_svc = $pkg_svc->part_svc;
+ my $svc = $part_svc->svc;
+ if ( $pkg_svc->primary_svc =~ /^Y/i ) {
+ $svc = "<B>$svc (PRIMARY)</B>";
+ }
+ $svc =~ s/ +/&nbsp;/g;
+
+ [
+ {
+ 'data' => '<B>'. $pkg_svc->quantity. '</B>',
+ 'align' => 'right'
+ },
+ {
+ 'data' => $svc,
+ 'align' => 'left',
+ 'link' => ( $acl_config
+ ? $p. 'edit/part_svc.cgi?'.
+ $part_svc->svcpart
+ : ''
+ ),
+ },
+ ];
+ }
+ sort { $b->primary_svc =~ /^Y/i
+ <=> $a->primary_svc =~ /^Y/i
+ }
+ $part_pkg->pkg_svc('disable_linked'=>1)
+ ),
+ ( map {
+ my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Add-on:&nbsp;'.$dst_pkg->pkg_comment,
+ align=>'center', #?
+ colspan=>2,
+ }
+ ]
+ }
+ $part_pkg->svc_part_pkg_link
+ )
+ ];
+
+ };
+
+$align .= 'lrl'; #rr';
+
+# --------
+
+my $count_extra_sql = $extra_sql;
+$count_extra_sql =~ s/^\s*AND /WHERE /i;
+$extra_count = ( $count_extra_sql ? ' AND ' : ' WHERE ' ). $extra_count
+ if $extra_count;
+my $count_query = "SELECT COUNT(*) FROM part_pkg $count_extra_sql $extra_count";
+
+</%init>
diff --git a/httemplate/browse/part_pkg_report_option.html b/httemplate/browse/part_pkg_report_option.html
new file mode 100644
index 0000000..9745b13
--- /dev/null
+++ b/httemplate/browse/part_pkg_report_option.html
@@ -0,0 +1,28 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Package optional report classes',
+ 'html_init' => $html_init,
+ 'name' => 'package optional report classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'part_pkg_report_option',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY name',
+ },
+ 'count_query' => 'SELECT COUNT(*) FROM part_pkg_report_option',
+ 'header' => [ '#', 'Class' ],
+ 'fields' => [ 'num', 'name' ],
+ 'links' => [ $link, $link ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Package optional report classes define optional groups of packages for reporting only.'.
+ qq!<BR><BR><A HREF="${p}edit/part_pkg_report_option.html"><I>Add a class</I></A><BR><BR>!;
+
+my $link = [ $p.'edit/part_pkg_report_option.html?', 'num' ];
+
+</%init>
diff --git a/httemplate/browse/part_pkg_taxclass.html b/httemplate/browse/part_pkg_taxclass.html
new file mode 100644
index 0000000..fb70ee4
--- /dev/null
+++ b/httemplate/browse/part_pkg_taxclass.html
@@ -0,0 +1,27 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Tax Classes',
+ 'name_singular' => 'tax class',
+ 'menubar' => [ 'Add a new tax class' =>
+ $p.'edit/part_pkg_taxclass.html',
+ ],
+ 'query' => { 'table' => 'part_pkg_taxclass', },
+ 'count_query' => 'SELECT COUNT(*) FROM part_pkg_taxclass',
+ 'header' => [ '#', 'Tax class' ],
+ 'fields' => [ 'taxclassnum',
+ 'taxclass',
+ ],
+ 'links' => [ $link,
+ $link,
+ ],
+ 'disableable' => 1,
+ 'disabled_statuspos' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/part_pkg_taxclass.html?", 'taxclassnum' ];
+
+</%init>
diff --git a/httemplate/browse/part_pkg_taxproduct.cgi b/httemplate/browse/part_pkg_taxproduct.cgi
new file mode 100755
index 0000000..7e0cb81
--- /dev/null
+++ b/httemplate/browse/part_pkg_taxproduct.cgi
@@ -0,0 +1,263 @@
+<% include( 'elements/browse.html',
+ 'title' => "Tax Products $title",
+ 'name_singular' => 'tax product',
+ 'menubar' => \@menubar,
+ 'html_init' => $html_init,
+ 'query' => {
+ 'table' => 'part_pkg_taxproduct',
+ 'hashref' => $hashref,
+ 'order_by' => 'ORDER BY description',
+ 'extra_sql' => $extra_sql,
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'align' => $align,
+ 'links' => \@links,
+ 'link_onclicks' => \@link_onclicks,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+
+my $select_link = [ 'javascript:void(0);', sub { ''; } ];
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @menubar;
+my $title = '';
+my $onclick = 'cClick';
+
+my $data_vendor = '';
+if ( $cgi->param('data_vendor') =~ /^(\w+)$/ ) {
+ $data_vendor = $1;
+ $title = "$data_vendor";
+}
+$cgi->delete('data_vendor');
+
+$title = " for $title" if $title;
+
+my $taxproductnum = $1
+ if ( $cgi->param('taxproductnum') =~ /^(\d+)$/ );
+my $tax_group = $1
+ if ( $cgi->param('tax_group') =~ /^([- \w\(\).\/]+)$/ );
+my $tax_item = $1
+ if ( $cgi->param('tax_item') =~ /^([- \w\(\).\/&%]+)$/ );
+my $tax_provider = $1
+ if ( $cgi->param('tax_provider') =~ /^([ \w]+)$/ );
+my $tax_customer = $1
+ if ( $cgi->param('tax_customer') =~ /^([ \w]+)$/ );
+my $id = $1
+ if ( $cgi->param('id') =~ /^([ \w]+)$/ );
+
+$onclick = $1
+ if ( $cgi->param('onclick') =~ /^(\w+)$/ );
+$cgi->delete('onclick');
+
+my $remove_onclick = <<EOS
+ parent.document.getElementById('$id').value = '';
+ parent.document.getElementById('${id}_description').value = '';
+ parent.$onclick();
+EOS
+ if $id;
+
+my $select_onclick = sub {
+ my $row = shift;
+ my $taxnum = $row->taxproductnum;
+ my $desc = $row->description;
+ "parent.document.getElementById('$id').value = $taxnum;".
+ "parent.document.getElementById('${id}_description').value = '$desc';".
+ "parent.$onclick();";
+}
+ if $id;
+
+my $selected_part_pkg_taxproduct;
+if ($taxproductnum) {
+ $selected_part_pkg_taxproduct =
+ qsearchs('part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum });
+}
+
+my $hashref = {};
+my $extra_sql = '';
+if ( $data_vendor ) {
+ $extra_sql .= ' WHERE data_vendor = '. dbh->quote($data_vendor);
+}
+
+if ($tax_group || $tax_item || $tax_customer || $tax_provider) {
+ my $compare = "LIKE '". ( $tax_group || "%" ). " : ". ( $tax_item || "%" ). " : ".
+ ( $tax_provider || "%" ). " : ". ( $tax_customer || "%" ). "'";
+ $compare = "= '$tax_group:$tax_item:$tax_provider:$tax_customer'"
+ if ($tax_group && $tax_item && $tax_provider && $tax_customer);
+
+ $extra_sql .= ($extra_sql =~ /WHERE/ ? ' AND ' : ' WHERE ').
+ "description $compare";
+
+}
+$cgi->delete('tax_group');
+$cgi->delete('tax_item');
+$cgi->delete('tax_provider');
+$cgi->delete('tax_customer');
+
+
+if ( $tax_group || $tax_item || $tax_provider || $tax_customer ) {
+ push @menubar, 'View all tax products' => $p.'browse/part_pkg_taxproduct.cgi';
+}
+
+$cgi->param('dummy', 1);
+
+#restore this so pagination works
+$cgi->param('data_vendor', $data_vendor) if $data_vendor;
+$cgi->param('tax_group', $tax_group) if $tax_group;
+$cgi->param('tax_item', $tax_item ) if $tax_item;
+$cgi->param('tax_provider', $tax_provider ) if $tax_provider;
+$cgi->param('tax_customer', $tax_customer ) if $tax_customer;
+$cgi->param('onclick', $onclick ) if $onclick;
+
+my $count_query = "SELECT COUNT(*) FROM part_pkg_taxproduct $extra_sql";
+
+my @header = ( 'Data Vendor', 'Group', 'Item', 'Provider', 'Customer' );
+my @links = ( $select_link,
+ $select_link,
+ $select_link,
+ $select_link,
+ $select_link,
+ );
+my @link_onclicks = ( $select_onclick,
+ $select_onclick,
+ $select_onclick,
+ $select_onclick,
+ $select_onclick,
+ );
+my $align = 'lllll';
+
+my @fields = (
+ 'data_vendor',
+ sub { shift->description =~ /^(.*):.*:.*:.*$/; $1;},
+ sub { shift->description =~ /^.*:(.*):.*:.*$/; $1;},
+ sub { shift->description =~ /^.*:.*:(.*):.*$/; $1;},
+ sub { shift->description =~ /^.*:.*:.*:(.*)$/; $1;},
+);
+
+my $html_init = '';
+
+my $select_link = [ 'javascript:void(0);', sub { ''; } ];
+$html_init = '<TABLE><TR><TD><A HREF="javascript:void(0)" '.
+ qq!onClick="$remove_onclick">(remove)</A>&nbsp;!.
+ 'Current tax product: </TD><TD>'.
+ $selected_part_pkg_taxproduct->description.
+ '</TD></TR></TABLE><BR><BR>'
+ if $selected_part_pkg_taxproduct;
+
+my $type = $cgi->param('_type');
+$html_init .= qq(
+ <FORM>
+ <INPUT NAME="_type" TYPE="hidden" VALUE="$type">
+ <INPUT NAME="taxproductnum" TYPE="hidden" VALUE="$taxproductnum">
+ <INPUT NAME="onclick" TYPE="hidden" VALUE="$onclick">
+ <INPUT NAME="id" TYPE="hidden" VALUE="$id">
+ <TABLE>
+ <TR>
+ <TD><SELECT NAME="data_vendor" onChange="this.form.submit()">
+);
+
+my $sql = "SELECT DISTINCT data_vendor FROM part_pkg_taxproduct ORDER BY data_vendor";
+my $dbh = dbh;
+my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (['(choose data vendor)'], @{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $data_vendor ? " SELECTED" : "").
+ '">'. $_->[0];
+}
+$html_init .= qq(
+ </SELECT>
+
+<!-- cch specific -->
+ <TD><SELECT NAME="tax_group" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ qq!substring(description from '#"%#" : % : % : %' for '#'),!.
+ qq!substring(description from '#"%#" : % : % : %' for '#')!.
+ "FROM part_pkg_taxproduct ORDER BY 1";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (['', '(choose group)'], @{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_group ? " SELECTED" : "").
+ '">'. $_->[1];
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ <TD><SELECT NAME="tax_item" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ qq!substring(description from '% : #"%#" : %: %' for '#'),!.
+ qq!substring(description from '% : #"%#" : %: %' for '#')!.
+ "FROM part_pkg_taxproduct ORDER BY 1";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (@{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_item ? " SELECTED" : "").
+ '">'. ($_->[0] ? $_->[1] : '(choose item)');
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ <TD><SELECT NAME="tax_provider" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ qq!substring(description from '% : % : #"%#" : %' for '#'),!.
+ qq!substring(description from '% : % : #"%#" : %' for '#')!.
+ "FROM part_pkg_taxproduct ORDER BY 1";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (@{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_provider ? " SELECTED" : "").
+ '">'. ($_->[0] ? $_->[1] : '(choose provider type)');
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ <TD><SELECT NAME="tax_customer" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ qq!substring(description from '% : % : % : #"%#"' for '#'),!.
+ qq!substring(description from '% : % : % : #"%#"' for '#')!.
+ "FROM part_pkg_taxproduct ORDER BY 1";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (@{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_customer ? " SELECTED" : "").
+ '">'. ($_->[0] ? $_->[1] : '(choose customer type)');
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ </TR>
+ </TABLE>
+ </FORM>
+
+);
+
+</%init>
diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html
new file mode 100755
index 0000000..9cc32c4
--- /dev/null
+++ b/httemplate/browse/part_referral.html
@@ -0,0 +1,181 @@
+<% include("/elements/header.html","Advertising source Listing" ) %>
+
+Where a customer heard about your service. Tracked for informational purposes.
+<BR><BR>
+
+<A HREF="<% $p %>edit/part_referral.html"><I>Add a new advertising source</I></A>
+<BR><BR>
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2 ROWSPAN=2>Advertising source</TH>
+% if ( $show_agentnums ) {
+
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Agent</TH>
+% }
+
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% scalar(keys %after) %>>Customers and Packages</TH>
+</TR>
+% for my $period ( keys %after ) {
+
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% $period %></FONT></TH>
+% }
+
+</TR>
+
+%foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% $a = 0;
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ( $part_referral->agentnum || $curuser->access_right('Edit global advertising sources') ) {
+% $a++;
+%
+
+ <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>">
+% }
+
+ <% $part_referral->refnum %><% $a ? '</A>' : '' %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ( $a ) {
+
+ <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>">
+% }
+
+ <% $part_referral->referral %><% $a ? '</A>' : '' %></TD>
+% if ( $show_agentnums ) {
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %></TD>
+% }
+% for my $period ( keys %after ) {
+% my @param = ( $part_referral->refnum,
+% $today-$after{$period},
+% $today+$before{$period},
+% );
+% $cust_sth->execute(@param) or die $cust_sth->errstr;
+% my $num_cust = $cust_sth->fetchrow_arrayref->[0];
+% $pkg_sth->execute(@param) or die $pkg_sth->errstr;
+% my $num_pkg = $pkg_sth->fetchrow_arrayref->[0];
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TD ALIGN="right"><B><% $num_cust %></B></TD>
+ <TD ALIGN="left">customers</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><B><% $num_pkg %></B></TD>
+ <TD ALIGN="left">packages</TD>
+ </TR>
+ </TABLE>
+ </TD>
+% }
+
+ </TR>
+% }
+%
+% $cust_statement =~ s/AND refnum = \?//;
+% $cust_sth = dbh->prepare($cust_statement)
+% or die dbh->errstr;
+% $pkg_statement =~ s/AND h_pkg_referral\.refnum = \?//;
+% $pkg_sth = dbh->prepare($pkg_statement)
+% or die dbh->errstr;
+
+ <TR>
+ <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD>
+% for my $period ( keys %after ) {
+% my @param = ( $today-$after{$period},
+% $today+$before{$period},
+% );
+% $cust_sth->execute( @param ) or die $cust_sth->errstr;
+% my $num_cust = $cust_sth->fetchrow_arrayref->[0];
+% $pkg_sth->execute(@param) or die $pkg_sth->errstr;
+% my $num_pkg = $pkg_sth->fetchrow_arrayref->[0];
+
+ <TD CLASS="inv" BGCOLOR="#dddddd" ALIGN="right">
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TD ALIGN="right"><B><% $num_cust %></B></TD>
+ <TD ALIGN="left">customers</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><B><% $num_pkg %></B></TD>
+ <TD ALIGN="left">packages</TD>
+ </TR>
+ </TABLE>
+ </TD>
+
+% }
+
+ </TR>
+ </TABLE>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources');
+
+my $today = timelocal(0, 0, 0, (localtime(time))[3..5] );
+
+tie my %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 $curuser = $FS::CurrentUser::CurrentUser;
+
+my $show_agentnums = ( scalar($curuser->agentnums) > 1 );
+
+my $cust_statement = "SELECT COUNT(*) FROM h_cust_main
+ WHERE history_action = 'insert'
+ AND refnum = ?
+ AND history_date >= ?
+ AND history_date < ?
+ AND ". $curuser->agentnums_sql;
+my $cust_sth = dbh->prepare($cust_statement)
+ or die dbh->errstr;
+
+my $pkg_statement = "SELECT COUNT(*) FROM h_pkg_referral
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE history_action = 'insert'
+ AND h_pkg_referral.refnum = ?
+ AND history_date >= ?
+ AND history_date < ?
+ AND ". $curuser->agentnums_sql;
+my $pkg_sth = dbh->prepare($pkg_statement)
+ or die dbh->errstr;
+
+</%init>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
new file mode 100755
index 0000000..94afdef
--- /dev/null
+++ b/httemplate/browse/part_svc.cgi
@@ -0,0 +1,228 @@
+<% include('/elements/header.html', 'Service Definition Listing') %>
+
+<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> )'; }
+%>
+% $cgi->param('showdisabled', ( 1 ^ $cgi->param('showdisabled') ) );
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+ <TR>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH>
+
+% if ( $cgi->param('showdisabled') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+% }
+
+ <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc">Table</TH>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc">Export</TH>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>
+
+ <TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>
+
+ <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH>
+
+ </TR>
+
+% foreach my $part_svc ( @part_svc ) {
+% my $svcdb = $part_svc->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 { my $col = $part_svc->part_svc_column($_);
+% my $def = FS::part_svc->svc_table_fields($svcdb)->{$_};
+% $svc_x->pvf($_)
+% or $_ ne 'svcnum' && (
+% $col->columnflag || ( $col->columnlabel !~ /^\S*$/
+% && $col->columnlabel ne $def->{'label'}
+% )
+% )
+% }
+% @dfields ;
+% my $rowspan = scalar(@fields) || 1;
+% my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart;
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+
+ <TR>
+
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<% $url %>"><% $part_svc->svcpart %></A>
+ </TD>
+
+% if ( $cgi->param('showdisabled') ) {
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $part_svc->disabled
+ ? '<FONT COLOR="#FF0000"><B>Disabled</B></FONT>'
+ : '<FONT COLOR="#00CC00"><B>Enabled</B></FONT>'
+ %>
+ </TD>
+% }
+
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>">
+ <% $part_svc->svc %></A></TD>
+
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $svcdb %></TD>
+
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT>&nbsp;<% $num_active_cust_svc{$part_svc->svcpart} ? svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart ) : '<A NAME="zero">' %>active</A>
+
+% if ( $num_active_cust_svc{$part_svc->svcpart} ) {
+ <BR><FONT SIZE="-1">[ <A HREF="<%$p%>edit/bulk-cust_svc.html?svcpart=<% $part_svc->svcpart %>">change</A> ]</FONT>
+% }
+
+ </TD>
+
+ <TD ROWSPAN=<% $rowspan %> CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv">
+%
+%# 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>
+
+% unless ( @fields ) {
+% for ( 1..4 ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
+% }
+% }
+%
+% my($n1)='';
+% foreach my $field ( @fields ) {
+%
+% #a few lines of false laziness w/edit/part_svc.cgi
+% my $def = FS::part_svc->svc_table_fields($svcdb)->{$field};
+% my $formatter = $def->{format} || sub { shift };
+%
+% my $part_svc_column = $part_svc->part_svc_column($field);
+% my $label = $part_svc_column->columnlabel || $def->{'label'};
+% my $flag = $part_svc_column->columnflag;
+
+ <% $n1 %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
+% if ( $flag =~ /^[MA]$/ ) {
+% $inventory_class{$value}
+% ||= qsearchs('inventory_class', { 'classnum' => $value } );
+%
+
+ <% $inventory_class{$value}
+ ? $inventory_class{$value}->classname
+ : "WARNING: inventory_class.classnum $value not found" %>
+% } else {
+
+ <% $value %>
+% }
+
+ </TD>
+% $n1="</TR><TR>";
+% }
+%
+
+ </TR>
+% }
+
+</TABLE>
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
+my %flag = (
+ '' => '',
+ 'D' => 'Default',
+ 'F' => 'Fixed (unchangeable)',
+ 'S' => 'Selectable choice',
+ #'M' => 'Manual selection from inventory',
+ 'M' => 'Manual selected from inventory',
+ #'A' => 'Automatically fill in from inventory',
+ 'A' => 'Automatically filled in from inventory',
+ 'X' => 'Excluded',
+);
+
+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 = map { $_->svcpart => $_->num_cust_svc } @part_svc;
+
+if ( $cgi->param('orderby') eq 'active' ) {
+ @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=>
+ $num_active_cust_svc{$a->svcpart} } @part_svc;
+} elsif ( $cgi->param('orderby') eq 'svc' ) {
+ @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc;
+}
+
+my %inventory_class = ();
+
+</%init>
diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi
new file mode 100644
index 0000000..b184400
--- /dev/null
+++ b/httemplate/browse/part_virtual_field.cgi
@@ -0,0 +1,42 @@
+<% include('/elements/header.html', 'Virtual field definitions') %>
+
+<% include('/elements/error.html') %>
+
+<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>
+% }
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %pvfs;
+my $block;
+my $p2 = popurl(2);
+my $dbtable;
+
+foreach (qsearch('part_virtual_field', {})) {
+ push @{ $pvfs{$_->dbtable} }, $_;
+}
+
+</%init>
diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html
new file mode 100644
index 0000000..a06e5cf
--- /dev/null
+++ b/httemplate/browse/payment_gateway.html
@@ -0,0 +1,98 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Payment gateways',
+ 'menubar' => [ 'Agents' => $p.'browse/agent.cgi', ],
+ 'html_init' => $html_init,
+ 'name' => 'payment gateways',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 1,
+ 'query' => { 'table' => 'payment_gateway',
+ 'hashref' => {},
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Type',
+ 'Gateway',
+ 'Username',
+ 'Password',
+ 'Action',
+ 'URL',
+ 'Options',
+ ],
+ 'fields' => [ 'gatewaynum',
+ 'namespace_description',
+ $gateway_sub,
+ 'gateway_username',
+ sub { ' - '; },
+ 'gateway_action',
+ 'gateway_callback_url',
+ $options_sub,
+ ],
+ )
+%>
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+<%once>
+
+my $html_init = qq!
+ <A HREF="${p}edit/payment_gateway.html"><I>Add a new payment gateway</I></A>
+ <BR><BR>
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Are you sure you want to disable this payment gateway?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+!;
+
+my $gateway_sub = sub {
+ my($payment_gateway) = @_;
+
+ my $gatewaynum = $payment_gateway->gatewaynum;
+
+ my $html = $payment_gateway->gateway_module. ' '. qq!
+ <FONT SIZE="-1">
+ <A HREF="${p}edit/payment_gateway.html?$gatewaynum">(edit)</A>
+ !;
+
+ unless ( $payment_gateway->disabled ) {
+ $html .= qq!
+ <A HREF="javascript:areyousure('${p}misc/disable-payment_gateway.cgi?$gatewaynum')">(disable)</A>
+ !;
+ }
+
+ $html .= '</FONT>';
+
+ $html;
+
+};
+
+my $options_sub = sub {
+ my($payment_gateway) = @_;
+
+ #should return a structure instead of this manual formatting...
+
+ my $html = '<TABLE CELLSPACING=0 CELLPADDING=0>';
+
+ my %options = $payment_gateway->options;
+ foreach my $option ( keys %options ) {
+ $html .= '<TR><TH>'. $option. ':</TH>'.
+ '<TD>'. $options{$option}. '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html;
+};
+
+my $count_query = 'SELECT COUNT(*) FROM payment_gateway';
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/browse/pkg_category.html b/httemplate/browse/pkg_category.html
new file mode 100644
index 0000000..16da230
--- /dev/null
+++ b/httemplate/browse/pkg_category.html
@@ -0,0 +1,32 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Package categories',
+ 'html_init' => $html_init,
+ 'name' => 'package categories',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 3,
+ 'query' => { 'table' => 'pkg_category',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY categorynum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#', 'Category', 'Weight', 'Condense' ],
+ 'fields' => [ 'categorynum', 'categoryname', 'weight', 'condense' ],
+ 'links' => [ $link, $link, $link, $link ],
+ )
+%>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ qq!<A HREF="${p}browse/pkg_class.html">Package classes</A><BR><BR>!.
+ 'Package categories define groups of package classes, used for sectioned invoices.<BR><BR>'.
+ qq!<A HREF="${p}edit/pkg_category.html"><I>Add a package category</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM pkg_category';
+
+my $link = [ $p.'edit/pkg_category.html?', 'categorynum' ];
+
+</%init>
diff --git a/httemplate/browse/pkg_class.html b/httemplate/browse/pkg_class.html
new file mode 100644
index 0000000..7097c86
--- /dev/null
+++ b/httemplate/browse/pkg_class.html
@@ -0,0 +1,46 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Package classes',
+ 'html_init' => $html_init,
+ 'name' => 'package classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'pkg_class',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY classnum',
+ },
+ 'count_query' => $count_query,
+ 'header' => $header,
+ 'fields' => $fields,
+ 'links' => $links,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Package classes define groups of packages, for taxation, ordering '.
+ 'convenience and reporting.<BR><BR>'.
+ qq!<A HREF="${p}edit/pkg_class.html"><I>Add a package class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM pkg_class';
+
+my $link = [ $p.'edit/pkg_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links = [ $link, $link ];
+
+my $cat_query = 'SELECT COUNT(*) FROM pkg_class where categorynum IS NOT NULL';
+my $sth = dbh->prepare($cat_query)
+ or die "Error preparing $cat_query: ". dbh->errstr;
+$sth->execute
+ or die "Error executing $cat_query: ". $sth->errstr;
+if ($sth->fetchrow_arrayref->[0]) {
+ push @$header, 'Category';
+ push @$fields, 'categoryname';
+ push @$links, $link;
+}
+
+</%init>
diff --git a/httemplate/browse/rate.cgi b/httemplate/browse/rate.cgi
new file mode 100644
index 0000000..02d670f
--- /dev/null
+++ b/httemplate/browse/rate.cgi
@@ -0,0 +1,64 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Rate plans',
+ 'menubar' => [ 'Regions and Prefixes' =>
+ $p.'browse/rate_region.html',
+ ],
+ 'html_init' => $html_init,
+ 'name' => 'rate plans',
+ 'query' => { 'table' => 'rate',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY ratenum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#', 'Rate plan', 'Rates' ],
+ 'fields' => [ 'ratenum', 'ratename', $rates_sub ],
+ 'links' => [ $link, $link, '' ],
+ )
+%>
+<%once>
+
+my $all_countrycodes = join("\n", map qq(<OPTION VALUE="$_">$_),
+ FS::rate_prefix->all_countrycodes
+ );
+
+my $rates_sub = sub {
+ my $rate = shift;
+ my $ratenum = $rate->ratenum;
+
+ qq( <FORM METHOD="GET" ACTION="${p}browse/rate_detail.html">
+ <INPUT TYPE="hidden" NAME="ratenum" VALUE="$ratenum">
+ <SELECT NAME="countrycode" onChange="this.form.submit();">
+ <OPTION SELECTED>Select Country Code
+ <OPTION VALUE="">(all)
+ $all_countrycodes
+ </SELECT>
+ </FORM>
+ );
+
+
+};
+
+my $html_init =
+ 'Rate plans for VoIP and call billing.<BR><BR>'.
+ qq!<A HREF="${p}edit/rate.cgi"><I>Add a rate plan</I></A>!.
+ qq! | <A HREF="${p}misc/copy-rate_detail.html"><I>Copy rates between plans</I></A>!.
+ '<BR><BR>
+ <SCRIPT>
+ function rate_areyousure(href) {
+ if (confirm("Are you sure you want to delete this rate plan?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+ ';
+
+my $count_query = 'SELECT COUNT(*) FROM rate';
+
+my $link = [ $p.'edit/rate.cgi?', 'ratenum' ];
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/browse/rate_detail.html b/httemplate/browse/rate_detail.html
new file mode 100644
index 0000000..3371926
--- /dev/null
+++ b/httemplate/browse/rate_detail.html
@@ -0,0 +1,100 @@
+<% include( 'elements/browse.html',
+ 'title' => $title,
+ 'name_singular' => 'rate',
+ 'html_init' => $html_init,
+ 'menubar' => [ 'Rate plans' => $p.'browse/rate.cgi' ],
+ 'query' => {
+ 'table' => 'rate_detail',
+ 'addl_from' => $join,
+ 'hashref' => { 'ratenum' => $ratenum },
+ 'extra_sql' => $where,
+ },
+ 'count_query' => "SELECT COUNT(*) FROM rate_detail $join".
+ " WHERE ratenum = $ratenum $where",
+ 'header' => [
+ 'Region',
+ 'Prefix(es)',
+ 'Included<BR>minutes',
+ 'Connection<BR>charge',
+ 'Charge per<BR>minute',
+ 'Granularity',
+ 'Usage class',
+ ],
+ 'fields' => [
+ 'regionname',
+ sub { shift->dest_region->prefixes_short },
+ sub { shift->min_included. $edit_hint },
+ $conn_charge_sub,
+ sub { $money_char. shift->min_charge. $edit_hint },
+ sub { $granularity{ shift->sec_granularity } },
+ 'classname',
+ ],
+ 'links' => [ '', '', $edit_link, $edit_link, '', '' ],
+ 'link_onclicks' => [ '', '', $edit_onclick, $edit_onclick, '', '' ],
+ 'align' => 'llrrcc',
+ )
+%>
+<%once>
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+tie my %conn_secs, 'Tie::IxHash', FS::rate_detail::conn_secs();
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $join =
+ ' JOIN rate_region ON ( rate_detail.dest_regionnum = rate_region.regionnum )';
+
+my $edit_link = [ 'javascript:void(0);', sub { ''; } ];
+
+my $edit_onclick = sub {
+ my $rate_detail = shift;
+ my $ratedetailnum = $rate_detail->ratedetailnum;
+ include( '/elements/popup_link_onclick.html',
+ 'action' => "${p}edit/rate_detail.html?$ratedetailnum",
+ 'actionlabel' => 'Edit rate',
+ 'height' => 420,
+ #default# 'width' => 540,
+ #default# 'color' => '#333399',
+ );
+};
+my $edit_hint = '&nbsp;<FONT SIZE="-1">(edit)</FONT>';
+
+my $conn_charge_sub = sub {
+ my $rate_detail = shift;
+ #return '' unless $rate_detail->conn_charge > 0 || $rate_detail->conn_sec;
+ $money_char. $rate_detail->conn_charge.
+ ($rate_detail->conn_sec ? ' for '.$conn_secs{$rate_detail->conn_sec} : '').
+ $edit_hint;
+};
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init = include('/elements/init_overlib.html');
+
+$cgi->param('ratenum') =~ /^(\d+)$/ or die "unparsable ratenum";
+my $ratenum = $1;
+my $rate = qsearchs('rate', { 'ratenum' => $ratenum } )
+ or die "unknown ratenum $ratenum";
+my $ratename = $rate->ratename;
+my $title = "$ratename rates";
+
+my @where = ();
+
+if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) {
+ my $countrycode = $1;
+ push @where, "0 < ( SELECT COUNT(*) FROM rate_prefix
+ WHERE rate_prefix.regionnum = rate_region.regionnum
+ AND countrycode = '$countrycode'
+ )
+ ";
+ $title .= " for +$countrycode";
+}
+
+my $where = scalar(@where) ? ' AND '.join(' AND ', @where ) : '';
+
+</%init>
diff --git a/httemplate/browse/rate_region.html b/httemplate/browse/rate_region.html
new file mode 100644
index 0000000..b7d9589
--- /dev/null
+++ b/httemplate/browse/rate_region.html
@@ -0,0 +1,136 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Rating Regions and Prefixes',
+ 'name_singular' => 'region', #'rate region',
+ 'menubar' => [ 'Rate plans' => $p.'browse/rate.cgi' ],
+ 'html_init' => $html_init,
+ 'html_posttotal' => $html_posttotal,
+ 'query' => {
+ 'select' => $select,
+ 'table' => 'rate_region',
+ 'addl_from' => $join,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => 'ORDER BY LOWER(regionname)',
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => \@align,
+ 'xls_format' => \@xls_format,
+ )
+%>
+<%once>
+
+my $edit_url = $p.'edit/rate_region.cgi';
+
+my $link = [ "$edit_url?", 'regionnum' ];
+
+my $html_init =
+ 'Regions and prefixes for VoIP and call billing.<BR><BR>'.
+ qq(<A HREF="$edit_url"><I>Add a new region</I></A><BR><BR>);
+
+#not quite right for the shouldn't-happen multiple countrycode per region case
+my $select = 'rate_region.*, ';
+my $join = '';
+my $group_sql = '';
+if ( driver_name =~ /^Pg/ ) {
+ my $fromwhere = 'FROM rate_prefix'.
+ ' WHERE rate_prefix.regionnum = rate_region.regionnum';
+ my $prefix_sql = " CASE WHEN nxx IS NULL OR nxx = '' ".
+ " THEN npa ".
+ " ELSE npa || '-' || nxx ".
+ " END";
+ my $prefixes_sql = "SELECT $prefix_sql $fromwhere AND npa IS NOT NULL";
+ $select .= "( SELECT '+'||countrycode $fromwhere LIMIT 1 ) AS ccode,
+ ARRAY_TO_STRING( ARRAY($prefixes_sql), ', ' ) AS prefixes";
+} elsif ( driver_name =~ /^mysql/i ) {
+ $join = 'LEFT JOIN rate_prefix USING ( regionnum )';
+ $select .= "'+'||GROUP_CONCAT( DISTINCT countrycode ) AS ccode,
+ GROUP_CONCAT( npa ORDER BY npa SEPARATOR ', ' ) AS prefixes ";
+ $group_sql = 'GROUP BY regionnum, regionname';
+} else {
+ die 'unknown database '. driver_name;
+}
+
+my $base_count_sql = 'SELECT COUNT(*) FROM rate_region';
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @header = ( '#', 'Region', 'Country code', 'Prefixes' );
+my @fields = ( 'regionnum', 'regionname', 'ccode', 'prefixes' );
+my @links = ( ($link) x 4 );
+my @align = ( 'right', 'left', 'right', 'left' );
+my @xls_format = ( ({ locked=>1, bg_color=>22 }) x 4 );
+
+$cgi->param('dummy', 1);
+my $countrycode_filter_change =
+ "window.location = '".
+ $cgi->self_url. ";countrycode=' + this.options[this.selectedIndex].value;";
+
+my $countrycode = '';
+my $extra_sql = $group_sql;
+my $count_query = $base_count_sql;
+if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) {
+ $countrycode = $1;
+ my $ccode_sql = '( SELECT countrycode FROM rate_prefix
+ WHERE rate_prefix.regionnum = rate_region.regionnum
+ LIMIT 1
+ )';
+ $extra_sql = " WHERE $ccode_sql = '$1' $extra_sql";
+ $count_query .= " WHERE $ccode_sql = '$1'";
+}
+
+sub _rate_detail_factory {
+ my( $rate, $field ) = @_;
+ return sub {
+ my $rate_detail = $rate->dest_detail(shift)
+ || new FS::rate_region { 'min_included' => 0,
+ 'min_charge' => 0,
+ 'sec_granularity' => 0,
+ };
+ my $value = $rate_detail->$field();
+ $field eq 'sec_granularity' ? $granularity{$value} : $value;
+ };
+}
+
+if ( $cgi->param('show_rates') ) {
+ foreach my $rate ( qsearch('rate', {}) ) {
+
+ my $label = $rate->ratenum.': '. $rate->ratename;
+ push @header, "$label: Included minutes/calls",
+ "$label: Charge per minute/call",
+ "$label: Granularity",
+ "$label: Usage class";
+
+ #closure me harder
+ push @fields, _rate_detail_factory($rate, 'min_included'),
+ _rate_detail_factory($rate, 'min_charge'),
+ _rate_detail_factory($rate, 'sec_granularity'),
+ _rate_detail_factory($rate, 'classnum');
+
+ push @links, ( ('') x 4 );
+ push @xls_format, ( ({}) x 4 );
+
+ }
+
+}
+
+my $html_posttotal =
+ '(show country code: '.
+ qq(<SELECT NAME="countrycode" onChange="$countrycode_filter_change">).
+ qq(<OPTION VALUE="">(all)).
+ join("\n", map { qq(<OPTION VALUE="$_").
+ ($_ eq $countrycode ? ' SELECTED' : '' ).
+ ">$_",
+ }
+ FS::rate_prefix->all_countrycodes
+ ).
+ '</SELECT>)';
+
+</%init>
diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html
new file mode 100644
index 0000000..fe285be
--- /dev/null
+++ b/httemplate/browse/reason.html
@@ -0,0 +1,53 @@
+<% include( 'elements/browse.html',
+ 'title' => ucfirst($classname) . ' Reasons',
+ 'menubar' => [ ucfirst($classname).' Reason Types' =>
+ $p."browse/reason_type.html?class=$class"
+ ],
+ 'html_init' => $html_init,
+ 'name' => $classname . ' reasons',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 3,
+ 'query' => { 'table' => 'reason',
+ 'hashref' => {},
+ 'extra_sql' => $where_clause.
+ ' ORDER BY reason_type',
+ 'addl_from' => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ ucfirst($classname) . ' Reason Type',
+ ucfirst($classname) . ' Reason',
+ ],
+ 'fields' => [ 'reasonnum',
+ sub { shift->reasontype->type },
+ 'reason',
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+my $class = $1;
+
+my $classname = $FS::reason_type::class_name{$class};
+my $classpurpose = $FS::reason_type::class_purpose{$class};
+
+my $html_init = ucfirst($classname). " reasons $classpurpose.<BR><BR>".
+qq!<A HREF="${p}edit/reason.html?class=$class">!.
+"<I>Add a $classname reason</I></A><BR><BR>";
+
+my $where_clause = " WHERE class='$class' ";
+
+my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' .
+ 'reason_type.typenum = reason.reason_type ' . $where_clause;
+
+my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ];
+
+</%init>
diff --git a/httemplate/browse/reason_type.html b/httemplate/browse/reason_type.html
new file mode 100644
index 0000000..6b444ba
--- /dev/null
+++ b/httemplate/browse/reason_type.html
@@ -0,0 +1,68 @@
+<% include( 'elements/browse.html',
+ 'title' => ucfirst($classname) . " Reason Types",
+ 'menubar' => [ ucfirst($classname) . " reasons" =>
+ $p.'browse/reason.html?class=' . $class,
+ ],
+ 'html_init' => $html_init,
+ 'name' => $classname . " reason types",
+ 'query' => { 'table' => 'reason_type',
+ 'hashref' => {},
+ 'extra_sql' => $where_clause .
+ 'ORDER BY typenum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ ucfirst($classname) . ' Reason Type',
+ ucfirst($classname) . ' Reasons',
+ ],
+ 'fields' => [ 'typenum',
+ 'type',
+ $reasons_sub,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+my $class=$1;
+
+my $classname = $FS::reason_type::class_name{$class};
+
+my $html_init = ucfirst($classname) .
+ " reason types allow groups of $classname reasons for reporting purposes." .
+ qq!<BR><BR><A HREF="${p}edit/reason_type.html?class=$class"><I>Add a ! .
+ $classname . " reason type</I></A><BR><BR>";
+
+my $reasons_sub = sub {
+ my $reason_type = shift;
+
+ [ map {
+ [
+ {
+ 'data' => $_->reason,
+ 'align' => 'left',
+ 'link' => $p. "edit/reason.html?class=$class&reasonnum=".
+ $_->reasonnum,
+ },
+ ];
+ }
+ $reason_type->enabled_reasons,
+
+ ];
+
+};
+
+my $where_clause = "WHERE class='$class'";
+my $count_query = 'SELECT COUNT(*) FROM reason_type ';
+$count_query .= $where_clause;
+
+my $link = [ $p.'edit/reason_type.html?class='.$class.'&typenum=', 'typenum' ];
+
+</%init>
diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi
new file mode 100644
index 0000000..541e967
--- /dev/null
+++ b/httemplate/browse/router.cgi
@@ -0,0 +1,52 @@
+<% include('elements/browse.html',
+ 'title' => 'Routers',
+ 'menubar' => [ @menubar ],
+ 'name_singular' => 'router',
+ 'query' => { 'table' => 'router',
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ },
+ 'count_query' => "SELECT count(*) from router $count_sql",
+ 'header' => [ 'Router name',
+ 'Address block(s)',
+ ],
+ 'fields' => [ 'routername',
+ sub { join( '<BR>', map { $_->NetAddr }
+ shift->addr_block
+ );
+ },
+ ],
+ 'links' => [ [ "${p2}edit/router.cgi?", 'routernum' ],
+ '',
+ ],
+ 'agent_virt' => 1,
+ 'agent_null_right'=> "Broadband global configuration",
+ 'agent_pos' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Broadband configuration')
+ || $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration');
+
+my $p2 = popurl(2);
+my $extra_sql = '';
+
+my @menubar = ( 'Add a new router', "${p2}edit/router.cgi" );
+
+if ($cgi->param('hidecustomerrouters') eq '1') {
+ $extra_sql = 'WHERE svcnum > 0';
+ $cgi->param('hidecustomerrouters', 0);
+ push @menubar, 'Show customer routers', $cgi->self_url();
+} else {
+ $cgi->param('hidecustomerrouters', 1);
+ push @menubar, 'Hide customer routers', $cgi->self_url();
+}
+
+my $count_sql = $extra_sql. ( $extra_sql =~ /WHERE/ ? ' AND' : 'WHERE' ).
+ $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration',
+ );
+
+</%init>
diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi
new file mode 100755
index 0000000..501d362
--- /dev/null
+++ b/httemplate/browse/svc_acct_pop.cgi
@@ -0,0 +1,78 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Access Numbers',
+ 'html_init' => $html_init,
+ 'name_singular' => 'access number',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [
+ '#',
+ 'City',
+ 'State',
+ 'Area code',
+ 'Exchange',
+ 'Local',
+ 'Accounts',
+ ],
+ 'fields' => [
+ 'popnum',
+ 'city',
+ 'state',
+ 'ac',
+ 'exch',
+ 'loc',
+ $num_accounts_sub,
+ ],
+ 'align' => 'rllrrrr',
+ 'links' => [ map { $svc_acct_pop_link } (1..6) ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Dialup configuration')
+ || $curuser->access_right('Dialup global configuration');
+
+my $html_init = qq!
+ <A HREF="${p}edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A>
+ <BR><BR>
+!;
+
+my $query = { 'select' => '*,
+ ( SELECT COUNT(*) FROM svc_acct
+ WHERE svc_acct.popnum = svc_acct_pop.popnum
+ ) AS num_accounts
+ ',
+ 'table' => 'svc_acct_pop',
+ #'hashref' => { 'disabled' => '' },
+ 'extra_sql' => 'ORDER BY state, city, ac, exch, loc',
+ };
+
+my $count_query = "SELECT COUNT(*) FROM svc_acct_pop"; # WHERE DISABLED IS NULL OR DISABLED = ''";
+
+my $svc_acct_pop_link = [ $p.'edit/svc_acct_pop.cgi?', 'popnum' ];
+
+my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum=';
+
+my $num_accounts_sub = sub {
+ my $svc_acct_pop = shift;
+ [
+ [
+ { 'data' => '<B><FONT COLOR="#00CC00">'.
+ $svc_acct_pop->get('num_accounts').
+ '</FONT></B>',
+ 'align' => 'right',
+ },
+ { 'data' => 'active',
+ 'align' => 'left',
+ 'link' => ( $svc_acct_pop->get('num_accounts')
+ ? $svc_acct_link. $svc_acct_pop->popnum
+ : ''
+ ),
+ },
+ ],
+ ];
+};
+
+</%init>
diff --git a/httemplate/browse/tax_class.html b/httemplate/browse/tax_class.html
new file mode 100755
index 0000000..76d266b
--- /dev/null
+++ b/httemplate/browse/tax_class.html
@@ -0,0 +1,92 @@
+<% include( 'elements/browse.html',
+ 'title' => "Tax classes $title",
+ 'name_singular' => 'tax class',
+ 'menubar' => \@menubar,
+ 'html_init' => $html_init,
+ 'query' => {
+ 'table' => 'tax_class',
+ 'hashref' => $hashref,
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY taxclass',
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'align' => $align,
+ 'links' => \@links,
+ 'link_onclicks' => \@link_onclicks,
+ 'disable_maxselect' => 1,
+ 'disable_total' => 1,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $title = '';
+my @menubar = ();
+my $html_init = '';
+my $hashref = {};
+my @where = ();
+my $onclick = 'return true;';
+
+my $omit = '';
+if ( $cgi->param('magic') eq 'omit' ) {
+ $cgi->param('omit') =~ /^([,\d]+)$/;
+ $omit = $1;
+ $title .= " unselected";
+ push @where, map { "taxclassnum != $_" } grep {$_} split( /,/, $omit );
+ $onclick = sub{ 'parent.doSelect('. shift->taxclassnum. '); return false;' }
+}
+$cgi->delete('omit');
+
+my $data_vendor = '';
+if ( $cgi->param('datavendor') =~ /^([\w]+)$/ ) {
+ $data_vendor = $1;
+ $title .= " for data vendor $1";
+ push @where, 'data_vendor = '. dbh->quote($data_vendor);
+}
+$cgi->delete('data_vendor');
+
+my $selected = '';
+if ( $cgi->param('magic') eq 'select')
+{
+ $cgi->param('selected') =~ /^([,\d]*)$/;
+ $selected = $1;
+ $title = " selected";
+ my @clauses = map { "taxclassnum = $_" } grep {$_} split( /,/, $selected );
+ @where = scalar(@clauses) ? '( '. join(' OR ', @clauses) .')' : '1=0';
+ $onclick = sub{ 'parent.doUnselect('. shift->taxclassnum. '); return false;' } ;
+}
+$cgi->delete('selected');
+
+
+if ( $data_vendor ) {
+ push @menubar, 'View all tax classes' => $p.'browse/tax_class.html';
+}
+
+$cgi->param('dummy', 1);
+
+#restore this so pagination works
+$cgi->param('omit', $omit ) if $omit;
+$cgi->param('selected', $selected ) if $selected;
+$cgi->param('data_vendor', $data_vendor ) if $data_vendor;
+
+my $where = scalar(@where) ? 'WHERE '. join( ' AND ', @where ) : '';
+my $count_query = 'SELECT COUNT(*) FROM tax_class '. $where;
+
+my $link = [ 'javascript:void(0);', sub{ ''; } ];
+
+my @header = ( '', '', '' );
+my @links = ( $link, $link, $link );
+my @link_onclicks = ( $onclick, $onclick, $onclick );
+my $align = 'lll';
+my @fields = ( 'data_vendor', 'taxclass', 'description' );
+
+</%init>
diff --git a/httemplate/browse/tax_rate.cgi b/httemplate/browse/tax_rate.cgi
new file mode 100755
index 0000000..cb997fa
--- /dev/null
+++ b/httemplate/browse/tax_rate.cgi
@@ -0,0 +1,348 @@
+<% include( 'elements/browse.html',
+ 'title' => "Tax Rates $title",
+ 'name_singular' => 'tax rate',
+ 'menubar' => \@menubar,
+ 'html_init' => $html_init,
+ 'html_form' => $html_form,
+ 'disableable' => 1,
+ 'disabled_statuspos' => 5,
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'header2' => \@header2,
+ 'fields' => \@fields,
+ 'align' => $align,
+ 'color' => \@color,
+ 'cell_style' => \@cell_style,
+ 'links' => \@links,
+ 'link_onclicks' => \@link_onclicks,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $rate_sub = sub {
+ my $tax_rate = shift;
+
+ my $units = $tax_rate->unittype_name;
+ $units =~ s/ /&nbsp;/g;
+
+ my @rate = ();
+ push @rate,
+ ($tax_rate->tax * 100). '%&nbsp;<FONT SIZE="-1">(edit)</FONT>'
+ if $tax_rate->tax > 0 || $tax_rate->taxbase > 0;
+ push @rate,
+ ($tax_rate->excessrate * 100). '%&nbsp;<FONT SIZE="-1">(edit)</FONT>'
+ if $tax_rate->excessrate > 0;
+ push @rate,
+ $money_char. $tax_rate->fee.
+ qq!&nbsp;per&nbsp;$units<FONT SIZE="-1">(edit)</FONT>!
+ if $tax_rate->fee > 0 || $tax_rate->feebase > 0;
+ push @rate,
+ $money_char. $tax_rate->excessfee.
+ qq!&nbsp;per&nbsp;$units<FONT SIZE="-1">(edit)</FONT>!
+ if $tax_rate->excessfee > 0;
+
+
+ [ map [ {'data'=>$_} ], @rate ];
+};
+
+my $limit_sub = sub {
+ my $tax_rate = shift;
+
+ my $maxtype = $tax_rate->maxtype_name;
+ $maxtype =~ s/ /&nbsp;/g;
+
+ my $units = $tax_rate->unittype_name;
+ $units =~ s/ /&nbsp;/g;
+
+ my @limit = ();
+ push @limit,
+ sprintf("$money_char%.2f&nbsp%s", $tax_rate->taxbase, $maxtype )
+ if $tax_rate->taxbase > 0;
+ push @limit,
+ sprintf("$money_char%.2f&nbsp;tax", $tax_rate->taxmax )
+ if $tax_rate->taxmax > 0;
+ push @limit,
+ $tax_rate->feebase. "&nbsp;$units". ($tax_rate->feebase == 1 ? '' : 's')
+ if $tax_rate->feebase > 0;
+ push @limit,
+ $tax_rate->feemax. "&nbsp;$units". ($tax_rate->feebase == 1 ? '' : 's')
+ if $tax_rate->feemax > 0;
+
+ push @limit, 'Excluding&nbsp;setup&nbsp;fee'
+ if $tax_rate->setuptax =~ /^Y$/i;
+
+ push @limit, 'Excluding&nbsp;recurring&nbsp;fee'
+ if $tax_rate->recurtax =~ /^Y$/i;
+
+ [ map [ {'data'=>$_} ], @limit ];
+};
+
+my $oldrow;
+my $cell_style;
+my $cell_style_sub = sub {
+ my $row = shift;
+ if ( $oldrow ne $row ) {
+ if ( $oldrow ) {
+ if ( $oldrow->country ne $row->country ) {
+ $cell_style = 'border-top:1px solid #000000';
+ } elsif ( $oldrow->state ne $row->state ) {
+ $cell_style = 'border-top:1px solid #cccccc'; #default?
+ } elsif ( $oldrow->state eq $row->state ) {
+ #$cell_style = 'border-top:dashed 1px dark gray';
+ $cell_style = 'border-top:1px dashed #cccccc';
+ }
+ }
+ $oldrow = $row;
+ }
+ return $cell_style;
+};
+
+my $select_link = [ 'javascript:void(0);', sub { ''; } ];
+
+my $select_onclick = sub {
+ my $row = shift;
+ my $taxnum = $row->taxnum;
+ my $color = '#333399';
+ qq!overlib( OLiframeContent('${p}edit/tax_rate.html?$taxnum', 540, 620, 'edit_tax_rate_popup' ), CAPTION, 'Edit tax rate', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color' ); return false;!;
+};
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @menubar;
+my $title = '';
+
+my $data_vendor = '';
+if ( $cgi->param('data_vendor') =~ /^(\w+)$/ ) {
+ $data_vendor = $1;
+ $title = "$data_vendor";
+}
+$cgi->delete('data_vendor');
+
+my $geocode = '';
+if ( $cgi->param('geocode') =~ /^(\w+)$/ ) {
+ $geocode = $1;
+ $title = " geocode $geocode";
+}
+$cgi->delete('geocode');
+
+$title = " for $title" if $title;
+
+my $taxclassnum = '';
+if ( $cgi->param('taxclassnum') =~ /^(\d+)$/ ) {
+ $taxclassnum = $1;
+ my $tax_class = qsearchs('tax_class', {'taxclassnum' => $taxclassnum});
+ if ($tax_class) {
+ $title .= " for ". $tax_class->taxclass.
+ " (". $tax_class->description. ") tax class";
+ }else{
+ $taxclassnum = '';
+ }
+}
+$cgi->delete('taxclassnum');
+
+my $tax_type = $1
+ if ( $cgi->param('tax_type') =~ /^(\d+)$/ );
+my $tax_cat = $1
+ if ( $cgi->param('tax_cat') =~ /^(\d+)$/ );
+
+if ($tax_type || $tax_cat ) {
+ my $compare = "LIKE '". ( $tax_type || "%" ). ":". ( $tax_cat || "%" ). "'";
+ $compare = "= '$tax_type:$tax_cat'" if ($tax_type && $tax_cat);
+ my @tax_class =
+ qsearch({ 'table' => 'tax_class',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE taxclass $compare",
+ });
+ if (@tax_class) {
+ $tax_class[0]->description =~ /^(.*):(.*)/;
+ $title .= " for";
+ $title .= " $tax_type ($1) tax type" if $tax_type;
+ $title .= " and" if ($tax_type && $tax_cat);
+ $title .= " $tax_cat ($2) tax category" if $tax_cat;
+ }else{
+ $tax_type = '';
+ $tax_cat = '';
+ }
+}
+$cgi->delete('tax_type');
+$cgi->delete('tax_cat');
+
+if ( $geocode || $taxclassnum ) {
+ push @menubar, 'View all tax rates' => $p.'browse/tax_rate.cgi';
+}
+
+$cgi->param('dummy', 1);
+
+#restore this so pagination works
+$cgi->param('data_vendor', $data_vendor) if $data_vendor;
+$cgi->param('geocode', $geocode) if $geocode;
+$cgi->param('taxclassnum', $taxclassnum ) if $taxclassnum;
+$cgi->param('tax_type', $tax_type ) if $tax_type;
+$cgi->param('tax_cat', $tax_cat ) if $tax_cat;
+
+my $html_form = include('/elements/init_overlib.html'). '<BR><BR>'.
+ join(' ',
+ map {
+ include('/elements/popup_link.html',
+ {
+ 'action' => $p. "misc/enable_or_disable_tax.html?action=$_&".
+ $cgi->query_string,
+ 'label' => ucfirst($_). ' all these taxes',
+ 'actionlabel' => ucfirst($_). ' taxes',
+ },
+ );
+ }
+ qw(disable enable)
+ );
+
+my ($query, $count_query) = FS::tax_rate::browse_queries(scalar($cgi->Vars));
+
+$cell_style = '';
+
+my @header = ( 'Location Code', );
+my @header2 = ( '', );
+my @links = ( '', );
+my @link_onclicks = ( '', );
+my $align = 'l';
+
+my @fields = (
+ 'geocode',
+);
+
+my @color = (
+ '000000',
+);
+
+push @header, qq!Tax class (<A HREF="${p}edit/tax_class.html">add new</A>)!;
+push @header2, '(per-tax classification)';
+push @fields, 'taxclass_description';
+push @color, '000000';
+push @links, '';
+push @link_onclicks, '';
+$align .= 'l';
+
+push @header, 'Tax name',
+ 'Rate', #'Tax',
+ 'Limits',
+ ;
+
+push @header2, '(printed on invoices)',
+ '',
+ '',
+ ;
+
+push @fields,
+ sub { shift->taxname || 'Tax' },
+ $rate_sub,
+ $limit_sub,
+;
+
+push @color,
+ sub { shift->taxname ? '000000' : '666666' },
+ sub { shift->tax ? '000000' : '666666' },
+ '000000',
+;
+
+$align .= 'lrl';
+
+my @cell_style = map $cell_style_sub, (1..scalar(@header));
+
+push @links, '', $select_link, '';
+push @link_onclicks, '', $select_onclick, '';
+
+my $html_init = '';
+
+$html_init .= qq(
+ <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws_iframe.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws_draggable.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/iframecontentmws.js"></SCRIPT>
+
+);
+
+$html_init .= qq(
+ <FORM>
+ <TABLE>
+ <TR>
+ <TD><SELECT NAME="data_vendor" onChange="this.form.submit()">
+);
+
+my $sql = "SELECT DISTINCT data_vendor FROM tax_rate ORDER BY data_vendor";
+my $dbh = dbh;
+my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (['(choose data vendor)'], @{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $data_vendor ? " SELECTED" : "").
+ '">'. $_->[0];
+}
+$html_init .= qq(
+ </SELECT>
+
+ <TD><INPUT NAME="geocode" TYPE="text" SIZE="12" VALUE="$geocode"></TD>
+
+<!-- generic
+ <TD><INPUT NAME="taxclassnum" TYPE="text" SIZE="12" VALUE="$taxclassnum"></TD>
+ <TD><INPUT TYPE="submit" VALUE="Filter by tax_class"></TD>
+-->
+
+<!-- cch specific -->
+ <TD><SELECT NAME="tax_type" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ "substring(taxclass from 1 for position(':' in taxclass)-1),".
+ "substring(description from 1 for position(':' in description)-1) ".
+ "FROM tax_class WHERE data_vendor='cch' ORDER BY 2";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (['', '(choose tax type)'], @{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_type ? " SELECTED" : "").
+ '">'. $_->[1];
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ <TD><SELECT NAME="tax_cat" onChange="this.form.submit()">
+);
+
+$sql = "SELECT DISTINCT ".
+ "substring(taxclass from position(':' in taxclass)+1),".
+ "substring(description from position(':' in description)+1) ".
+ "from tax_class WHERE data_vendor='cch' ORDER BY 2";
+
+$sth = $dbh->prepare($sql) or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+for (['', '(choose tax category)'], @{$sth->fetchall_arrayref}) {
+ $html_init .= '<OPTION VALUE="'. $_->[0]. '"'.
+ ($_->[0] eq $tax_cat ? " SELECTED" : "").
+ '">'. $_->[1];
+}
+
+$html_init .= qq(
+ </SELECT>
+
+ </TR>
+ <TR>
+ <TD></TD>
+ <TD><INPUT TYPE="submit" VALUE="Filter by geocode"></TD>
+ <TD></TD>
+ <TD></TD>
+ </TR>
+ </TABLE>
+ </FORM>
+
+);
+
+</%init>
diff --git a/httemplate/browse/usage_class.html b/httemplate/browse/usage_class.html
new file mode 100644
index 0000000..75223e0
--- /dev/null
+++ b/httemplate/browse/usage_class.html
@@ -0,0 +1,45 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Usage classes',
+ 'html_init' => $html_init,
+ 'name' => 'usage classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'usage_class',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY classnum',
+ },
+ 'count_query' => 'SELECT COUNT(*) FROM usage_class',
+ 'header' => [ '#',
+ 'Class',
+ 'Weight',
+ ( $useformat ? ('Format') : () ),
+ ],
+ 'fields' => [ 'classnum',
+ 'classname',
+ 'weight',
+ ( $useformat ? (sub { $labels->{shift->format} } ) : () ),
+ ],
+ 'links' => [ $link,
+ $link,
+ $link,
+ ( $useformat ? ( $link ) : () ),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+my $useformat = $conf->exists('usage_class_as_a_section');
+my $labels = { &FS::usage_class::summary_formats_labelhash() };
+
+
+my $html_init =
+ 'Usage classes define groups of usage for taxation purposes.<BR><BR>'.
+ qq!<A HREF="${p}edit/usage_class.html"><I>Add a usage class</I></A><BR><BR>!;
+
+my $link = [ $p.'edit/usage_class.html?', 'classnum' ];
+
+</%init>
diff --git a/httemplate/config/config-delete.cgi b/httemplate/config/config-delete.cgi
new file mode 100644
index 0000000..a05cb1e
--- /dev/null
+++ b/httemplate/config/config-delete.cgi
@@ -0,0 +1,22 @@
+<%init>
+die "access denied\n"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('confnum') =~ /^(\d+)$/ or die "illegal or missing confnum";
+my $confnum = $1;
+
+my $conf = qsearchs('conf', {'confnum' => $confnum});
+die "Configuration not found!" unless $conf;
+$conf->delete;
+
+my $redirect = popurl(2);
+if ( $cgi->param('redirect') eq 'config_view_showagent' ) {
+ $redirect .= 'config/config-view.cgi?showagent=1#'. $conf->name;
+} elsif ( $cgi->param('redirect') eq 'config_view' ) {
+ $redirect .= 'config/config-view.cgi';
+} else {
+ $redirect .= 'browse/agent.cgi';
+}
+
+</%init>
+<% $cgi->redirect($redirect) %>
diff --git a/httemplate/config/config-download.cgi b/httemplate/config/config-download.cgi
new file mode 100644
index 0000000..6979246
--- /dev/null
+++ b/httemplate/config/config-download.cgi
@@ -0,0 +1,28 @@
+%
+%
+%my $conf=new FS::Conf;
+%
+%http_header('Content-Type' => 'application/x-unknown' );
+%
+%die "No configuration variable specified (bad URL)!" # umm
+% unless $cgi->param('key');
+%$cgi->param('key') =~ /^([-\w.]+)$/;
+%my $name = $1;
+%
+%my $agentnum;
+%if ($cgi->param('agentnum') =~ /^(\d+)$/) {
+% $agentnum = $1;
+%}
+%
+%http_header('Content-Disposition' => "attachment; filename=$name" );
+% print $conf->config_binary($name, $agentnum);
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum;
+if ($cgi->param('agentnum') =~ /^(\d+)$/) {
+ $agentnum = $1;
+}
+
+</%init>
diff --git a/httemplate/config/config-image.cgi b/httemplate/config/config-image.cgi
new file mode 100644
index 0000000..0de9d42
--- /dev/null
+++ b/httemplate/config/config-image.cgi
@@ -0,0 +1,22 @@
+<% $logo %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+http_header( 'Content-Type' => 'image/png' ); #just png for now
+
+$cgi->param('key') =~ /^([-\w.]+)$/ or die "illegal config option";
+my $name = $1;
+
+my $agentnum = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+}
+
+my $logo = $conf->config_binary($name, $agentnum);
+$logo = eps2png($logo) if $name =~ /\.eps$/i;
+
+</%init>
diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi
new file mode 100644
index 0000000..788d901
--- /dev/null
+++ b/httemplate/config/config-process.cgi
@@ -0,0 +1,138 @@
+<% header('Configuration set') %>
+ <SCRIPT TYPE="text/javascript">
+% my $n = 0;
+% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
+ var configCell = window.top.document.getElementById('<% $agentnum. $i->key. $n %>');
+ if ( ! configCell ) {
+ window.top.location.reload();
+ }
+ //alert('found cell ' + configCell);
+% if ( $type eq 'textarea'
+% || $type eq 'editlist'
+% || $type eq 'selectmultiple' ) {
+ configCell.innerHTML =
+ '<font size="-2"><pre>' + "\n" +
+ <% encode_entities(join("\n",
+ map { length($_) > 88 ? substr($_,0,88).'...' : $_ }
+ $conf->config($i->key, $agentnum)
+ ) )
+ |js_string %> +
+ '</pre></font>';
+
+% } elsif ( $type eq 'checkbox' ) {
+% if ( $conf->exists($i->key, $agentnum) ) {
+ configCell.style.backgroundColor = '#00ff00';
+ configCell.innerHTML = 'YES';
+% } else {
+ configCell.style.backgroundColor = '#ff0000';
+ configCell.innerHTML = 'NO';
+% }
+% } elsif ( $type eq 'select' && $i->select_hash ) {
+% my %hash;
+% if ( ref($i->select_hash) eq 'ARRAY' ) {
+% tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
+% } else {
+% tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
+% }
+ configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' |js_string %>;
+
+% } elsif ( $type eq 'text' || $type eq 'select' ) {
+ configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' |js_string %>;
+% } elsif ( $type =~ /^select-(part_svc|part_pkg|pkg_class)$/ && ! $i->multiple ) {
+% my $table = $1;
+% my $namecol = $namecol{$table};
+% my $pkey = dbdef->table($table)->primary_key;
+% my $key = $conf->config($i->key, $agentnum);
+% my $record = qsearchs($table, { $pkey => $key });
+% my $value = $record ? "$key: ".$record->$namecol() : $key;
+ configCell.innerHTML = <% $value |js_string %>;
+% } elsif ( $type eq 'select-sub' ) {
+ configCell.innerHTML =
+ <% $conf->config($i->key, $agentnum) |js_string %> + ': ' +
+ <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>;
+% } else {
+ //alert('unknown type <% $type %>');
+ window.top.location.reload();
+% }
+
+% $n++;
+% }
+ parent.cClick();
+ </SCRIPT>
+</BODY>
+</HTML>
+<%once>
+#false laziness w/config-view.cgi
+my %namecol = (
+ 'part_svc' => 'svc',
+ 'part_pkg' => 'pkg',
+ 'pkg_class' => 'classname',
+);
+</%once>
+<%init>
+die "access denied\n"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+$FS::Conf::DEBUG = 1;
+my @config_items = grep { $_->key != ~/^invoice_(html|latex|template)/ }
+ $conf->config_items;
+my %confitems = map { $_->key => $_ } $conf->config_items;
+
+my $agentnum = $cgi->param('agentnum');
+my $key = $cgi->param('key');
+my $i = $confitems{$key};
+
+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, $agentnum);
+ } else {
+ $conf->delete($i->key, $agentnum);
+ }
+ } elsif ( $type eq 'binary' || $type eq 'image' ) {
+ if ( defined($cgi->param($i->key.$n)) && $cgi->param($i->key.$n) ) {
+ my $fh = $cgi->upload($i->key.$n);
+ if (defined($fh)) {
+ local $/;
+ $conf->set_binary($i->key, <$fh>, $agentnum);
+ }
+ }else{
+ warn "Condition failed for " . $i->key;
+ }
+ } elsif ( $type eq 'checkbox' ) {
+ if ( defined $cgi->param($i->key.$n) ) {
+ push @touch, $i->key;
+ } else {
+ push @delete, $i->key;
+ }
+ } elsif (
+ $type =~ /^(editlist|selectmultiple)$/
+ or ( $type =~ /^select(-(sub|part_svc|part_pkg|pkg_class))?$/
+ || $i->multiple )
+ ) {
+ if ( scalar(@{[ $cgi->param($i->key.$n) ]}) ) {
+ $conf->set($i->key, join("\n", @{[ $cgi->param($i->key.$n) ]} ), $agentnum);
+ } else {
+ $conf->delete($i->key, $agentnum);
+ }
+ } elsif ( $type =~ /^(text|select(-(sub|part_svc|part_pkg|pkg_class))?)$/ ) {
+ if ( $cgi->param($i->key.$n) ne '' ) {
+ $conf->set($i->key, $cgi->param($i->key.$n), $agentnum);
+ } else {
+ $conf->delete($i->key, $agentnum);
+ }
+ }
+ $n++;
+}
+# warn @touch;
+$conf->touch($_, $agentnum) foreach @touch;
+$conf->delete($_, $agentnum) foreach @delete;
+
+</%init>
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
new file mode 100644
index 0000000..08f6c10
--- /dev/null
+++ b/httemplate/config/config-view.cgi
@@ -0,0 +1,367 @@
+<% include("/elements/header.html", $title, menubar(@menubar)) %>
+
+Click on a configuration value to change it.
+<BR><BR>
+
+% unless ( $page_agent ) {
+%
+% if ( $cgi->param('showagent') ) {
+% $cgi->param('showagent', 0);
+ ( <a href="<% $cgi->self_url %>">hide agent overrides</a> )
+% $cgi->param('showagent', 1);
+% } else {
+% $cgi->param('showagent', 1);
+ ( <a href="<% $cgi->self_url %>">show agent overrides</a> )
+% $cgi->param('showagent', 0);
+% }
+%
+% }
+<BR><BR>
+
+<% include('/elements/init_overlib.html') %>
+
+% if ($FS::UID::use_confcompat) {
+ <FONT SIZE="+1" COLOR="#ff0000">CONFIGURATION NOT STORED IN DATABASE -- USING COMPATIBILITY MODE</FONT><BR><BR>
+%}
+
+% foreach my $section (@sections) {
+
+ <A NAME="<% $section || 'unclassified' %>"></A>
+ <FONT SIZE="-2">
+
+% foreach my $nav_section (@sections) {
+%
+% 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 BGCOLOR="#cccccc" BORDER=1 CELLSPACING=0 CELLPADDING=0 BORDERCOLOR="#999999">
+ <tr>
+ <th colspan="2" bgcolor="#dcdcdc">
+ <% ucfirst($section || 'unclassified') %>
+% if ( $curuser->option('show_confitem_counts') ) {
+ (<% scalar( @{ $section_items{$section} } ) %> items)
+% }
+ </th>
+ </tr>
+% foreach my $i (@{ $section_items{$section} }) {
+% my @types = ref($i->type) ? @{$i->type} : ($i->type);
+%# my( $width, $height ) = ( 522, 336 );
+% my( $width, $height ) = ( 600, 336 );
+% if ( grep $_ eq 'textarea', @types ) {
+% #800x600
+% $width = 763;
+% $height = 408;
+% #1024x768
+% #$width =
+% #$height =
+% }
+%
+% my @agents = ();
+% my @add_agents = ();
+% if ( $page_agent ) {
+% @agents = ( $page_agent );
+% } else {
+% @agents = ( '' );
+% if ( $i->per_agent ) {
+% foreach my $agent (@all_agents) {
+% if ( defined($conf->conf( $i->key, $agent->agentnum, 1 ) ) ) {
+% push @agents, $agent;
+% } else {
+% push @add_agents, $agent;
+% }
+% }
+% }
+% }
+%
+% foreach my $agent ( @agents ) {
+% my $agentnum = $agent ? $agent->agentnum : '';
+%
+% next if $section eq 'deprecated' && ! $conf->exists($i->key, $agentnum);
+%
+% my $label = $i->key;
+% $label = '['. $agent->agent. "] $label"
+% if $agent && $cgi->param('showagent');
+%
+% #indentation :/
+
+ <tr>
+ <td><% include('/elements/popup_link.html',
+ 'action' => 'config.cgi?key='. $i->key.
+ ';agentnum='. $agentnum,
+ 'width' => $width,
+ 'height' => $height,
+ 'actionlabel' => 'Enter configuration value',
+ 'label' => "<b>$label</b>",
+ 'aname' => $i->key, #agentnum
+ # if $cgi->param('showagent')?
+ )
+ %>: <% $i->description %>
+% if ( $agent && $cgi->param('showagent') ) {
+% my $confnum = $conf->conf( $i->key, $agent->agentnum, 1 )->confnum;
+ (<A HREF="javascript:areyousure('delete this agent override', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view_showagent')">delete agent override</A>)
+% } elsif ( $i->base_key
+% || ( $deleteable{$i->key} && $conf->exists($i->key) ) ) {
+% my $confnum =
+% $agent
+% ? $conf->conf( $i->key, $agent->agentnum, 1 )->confnum
+% : $conf->conf( $i->key )->confnum;
+% my $showagent = $cgi->param('showagent') ? '_showagent' : '';
+ (<A HREF="javascript:areyousure('delete this configuration item', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view<%$showagent%>')">delete configuration item</A>)
+% }
+
+ </td>
+ <td><table border=0>
+
+% my $n = 0;
+% foreach my $type (@types) {
+
+% if ( $type eq '' ) {
+
+ <tr>
+ <td><font color="#ff0000">no type</font></td>
+ </tr>
+
+% } elsif ( $type eq 'image' ) {
+
+ <tr>
+ <td bgcolor='#ffffff'>
+ <% $conf->exists($i->key, $agentnum)
+ ? '<img src="config-image.cgi?key='. $i->key.
+ ';agentnum='. $agentnum. '">'
+ : 'empty'
+ %>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <% $conf->exists($i->key, $agentnum)
+ ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
+ : ''
+ %>
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'binary' ) {
+
+ <tr>
+ <td>
+ <% $conf->exists($i->key, $agentnum)
+ ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
+ : 'empty'
+ %>
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'textarea'
+% || $type eq 'editlist'
+% || $type eq 'selectmultiple'
+% )
+% {
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+<font size="-2"><pre><% encode_entities(join("\n",
+ map { length($_) > 88 ? substr($_,0,88).'...' : $_ }
+ $conf->config($i->key, $agentnum)
+ ) )
+%></pre></font>
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'checkbox' ) {
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
+ </tr>
+
+% } elsif ( $type eq 'select' && $i->select_hash ) {
+%
+% my %hash;
+% if ( ref($i->select_hash) eq 'ARRAY' ) {
+% tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
+% } else {
+% tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
+% }
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' %>
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'text' || $type eq 'select' ) {
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' %>
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'select-sub' ) {
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% $conf->config($i->key, $agentnum) %>:
+ <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) %>
+ </td>
+ </tr>
+
+% } elsif ( $type =~ /^select-(part_svc|part_pkg|pkg_class)$/ ) {
+%
+% my $table = $1;
+% my $namecol = $namecol{$table};
+% my $pkey = dbdef->table($table)->primary_key;
+%
+% my @keys = $conf->config($i->key, $agentnum);
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% join( '<BR>',
+ map {
+ my $key = $_;
+ my $record = qsearchs($table, { $pkey => $key });
+ $record ? "$key: ".$record->$namecol() : $key;
+ } @keys
+ )
+ %>
+ </td>
+ </tr>
+
+% } else {
+
+ <tr><td>
+ <font color="#ff0000">unknown type <% $type %></font>
+ </td></tr>
+% }
+% $n++;
+% }
+
+ </table></td>
+ </tr>
+
+% } # foreach my $agentnum
+
+% if ( @add_agents ) {
+
+ <tr>
+ <td>
+ <FORM>
+ Add <b><% $i->key %></b> override for
+ <% include('/elements/select-agent.html',
+ 'agents' => \@add_agents,
+ 'empty_label' => 'Select agent',
+ 'onchange' => "agent_changed",
+ 'id' => 'agent_'. $i->key,
+ )
+ %>
+ agent
+
+% my $agent_el = "document.getElementById('agent_". $i->key. "')";
+ <INPUT TYPE = "button"
+ VALUE = "Add"
+ ID = "add_<% $i->key %>"
+ DISABLED
+ onClick = "<%
+ include('/elements/popup_link_onclick.html',
+ 'action' =>
+ 'config.cgi?key='. $i->key.
+ ";agentnum=' + ".
+ "$agent_el.options[$agent_el.selectedIndex].value".
+ " + '",
+ 'width' => $width,
+ 'height' => $height,
+ 'actionlabel' => 'Enter configuration value',
+ )
+ %>"
+ >
+ </FORM>
+ </td>
+ </tr>
+
+% } #if @add_agents
+
+% } # foreach my $i
+
+ </table><br><br>
+
+% } # foreach my $nav_section
+
+<SCRIPT TYPE="text/javascript">
+
+ function agent_changed(what) {
+ var key = what.id.substring(6); // trim agent_
+ var button = document.getElementById('add_'+key);
+ if ( what.selectedIndex > 0 ) {
+ button.disabled = false;
+ } else {
+ button.disabled = true;
+ }
+ }
+
+ function areyousure(what, href) {
+ if ( confirm("Are you sure you want to " + what + "?") == true )
+ window.location.href = href;
+ }
+
+</SCRIPT>
+
+</body></html>
+<%once>
+#false laziness w/config-process.cgi
+my %namecol = (
+ 'part_svc' => 'svc',
+ 'part_pkg' => 'pkg',
+ 'pkg_class' => 'classname',
+);
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Configuration');
+
+my $page_agent = '';
+my $title;
+my @menubar = ();
+if ($cgi->param('agentnum') =~ /^(\d+)$/) {
+ my $page_agentnum = $1;
+ $page_agent = qsearchs('agent', { 'agentnum' => $page_agentnum } );
+ die "Agent $page_agentnum not found!" unless $page_agent;
+
+ push @menubar, 'View all agents' => $p.'browse/agent.cgi';
+ $title = 'Agent Configuration for '. $page_agent->agent;
+} else {
+ $title = 'Global Configuration';
+}
+
+my $conf = new FS::Conf;
+
+my @config_items = grep { $page_agent ? $_->per_agent : 1 }
+ grep { $page_agent ? 1 : !$_->agentonly }
+ $conf->config_items;
+
+my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress );
+my %deleteable = map { $_ => 1 } @deleteable;
+
+my @sections = qw(required billing invoicing UI self-service username password session shell BIND );
+push @sections, '', 'deprecated';
+
+my %section_items = ();
+foreach my $section (@sections) {
+ $section_items{$section} = [ grep $_->section eq $section, @config_items ];
+}
+
+@sections = grep scalar( @{ $section_items{$_} } ), @sections;
+
+my @all_agents = ();
+if ( $cgi->param('showagent') ) {
+ @all_agents = qsearch('agent', { 'disabled' => '' } );
+}
+
+</%init>
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
new file mode 100644
index 0000000..ce96bc1
--- /dev/null
+++ b/httemplate/config/config.cgi
@@ -0,0 +1,348 @@
+<% include("/elements/header-popup.html", $title) %>
+
+<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>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="OneTrueForm" ACTION="config-process.cgi" METHOD="POST" enctype="multipart/form-data" onSubmit="SafeOnsubmit()">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>">
+<INPUT TYPE="hidden" NAME="key" VALUE="<% $key %>">
+
+Setting <b><% $key %></b>
+
+% my $description_printed = 0;
+% if ( grep $_ eq 'textarea', @types ) {
+% $description_printed = 1;
+
+ - <% $description %>
+
+% }
+
+<table><tr><td>
+
+% my $n = 0;
+% foreach my $type (@types) {
+% if ( $type eq '' ) {
+
+ <font color="#ff0000">no type</font>
+
+% } elsif ( $type eq 'image' ) {
+
+ <% $conf->exists($key, $agentnum)
+ ? 'Current image<br>'.
+ '<img src="config-image.cgi?key='. $key.
+ ';agentnum='. $agentnum. '"><br>'
+ : ''
+ %>
+
+ <BR>
+ New image filename <input type="file" name="<% "$key$n" %>">
+
+% } elsif ( $type eq 'binary' ) {
+
+ Filename <input type="file" name="<% "$key$n" %>">
+
+% } elsif ( $type eq 'textarea' ) {
+
+ <textarea name="<% "$key$n" %>" rows=12 cols=78 wrap="off"><% join("\n", $conf->config($key, $agentnum)) |h %></textarea>
+
+% } elsif ( $type eq 'checkbox' ) {
+
+ <input name="<% "$key$n" %>" type="checkbox" value="1"
+ <% $conf->exists($key, $agentnum) ? 'CHECKED' : '' %> >
+
+% } elsif ( $type eq 'text' ) {
+
+ <input name="<% "$key$n" %>" type="text" value="<% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '' |h %>">
+
+% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) {
+
+ <select name="<% "$key$n" %>" <% $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>>
+
+%
+% my %hash = ();
+% if ( $config_item->select_enum ) {
+% tie %hash, 'Tie::IxHash',
+% '' => '', map { $_ => $_ } @{ $config_item->select_enum };
+% } elsif ( $config_item->select_hash ) {
+% if ( ref($config_item->select_hash) eq 'ARRAY' ) {
+% tie %hash, 'Tie::IxHash', '' => '', @{ $config_item->select_hash };
+% } else {
+% tie %hash, 'Tie::IxHash', '' => '', %{ $config_item->select_hash };
+% }
+% } else {
+% %hash = ( '' => 'WARNING: neither select_enum nor select_hash specified in Conf.pm for configuration option "'. $key. '"' );
+% }
+%
+% my %saw = ();
+% foreach my $value ( keys %hash ) {
+% local($^W)=0; next if $saw{$value}++;
+% my $label = $hash{$value};
+%
+
+ <option value="<% $value %>"
+
+% if ( $value eq $conf->config($key, $agentnum)
+% || ( $type eq 'selectmultiple'
+% && grep { $_ eq $value } $conf->config($key, $agentnum) ) ) {
+
+ SELECTED
+
+% }
+
+ ><% $label %>
+
+% }
+% my $curvalue = $conf->config($key, $agentnum);
+% if ( $conf->exists($key, $agentnum) && $curvalue && ! $hash{$curvalue} ) {
+
+ <option value="<% $curvalue %>" SELECTED>
+
+% if ( exists( $hash{ $conf->config($key, $agentnum) } ) ) {
+
+ <% $hash{ $conf->config($key, $agentnum) } %>
+
+% }else{
+
+ <% $curvalue %>
+
+% }
+% }
+
+ </select>
+
+% } elsif ( $type eq 'select-sub' ) {
+
+ <select name="<% "$key$n" %>"><option value="">
+
+% my %options = &{$config_item->options_sub};
+% my @options = sort { $a <=> $b } keys %options;
+% my %saw;
+% foreach my $value ( @options ) {
+% local($^W)=0; next if $saw{$value}++;
+
+ <option value="<% $value %>" <% $value eq $conf->config($key, $agentnum) ? 'SELECTED' : '' %>><% $value %>: <% $options{$value} %>
+
+% }
+% my $curvalue = $conf->config($key, $agentnum);
+% if ( $conf->exists($key, $agentnum) && $curvalue && ! $options{$curvalue} ) {
+
+ <option value="<% $curvalue %>" SELECTED> <% $curvalue %>: <% &{ $config_item->option_sub }( $curvalue ) %>
+
+% }
+
+ </select>
+
+% } elsif ( $type eq 'editlist' ) {
+%
+ <script>
+ function doremove<% "$key$n" %>() {
+ fromObject = document.OneTrueForm.<% "$key$n" %>;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ if (fromObject.options[i].selected)
+ deleteOption<% "$key$n" %>(fromObject,i);
+ }
+ }
+ function deleteOption<% "$key$n" %>(object,index) {
+ object.options[index] = null;
+ }
+ function selectall<% "$key$n" %>() {
+ fromObject = document.OneTrueForm.<% "$key$n" %>;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ fromObject.options[i].selected = true;
+ }
+ }
+ function doadd<% "$key$n" %>(object) {
+ var myvalue = "";
+
+% if ( defined($config_item->editlist_parts) ) {
+% foreach my $pnum ( 0 .. scalar(@{$config_item->editlist_parts})-1 ) {
+
+ if ( myvalue != "" ) { myvalue = myvalue + " "; }
+
+% if ( $config_item->editlist_parts->[$pnum]{type} eq 'select' ) {
+
+ myvalue = myvalue + object.add<% "$key${n}_$pnum" %>.options[object.add<% "$key${n}_$pnum" %>.selectedIndex].value
+ <!-- #RESET SELECT?? maybe not... -->
+
+% } elsif ( $config_item->editlist_parts->[$pnum]{type} eq 'immutable' ) {
+
+ myvalue = myvalue + object.add<% "$key${n}_$pnum" %>.value
+
+% } else {
+
+ myvalue = myvalue + object.add<% "$key${n}_$pnum" %>.value
+ object.add<% "$key${n}_$pnum" %>.value = ""
+
+% }
+% }
+% } else {
+
+ myvalue = object.add<% "$key${n}_1" %>.value
+
+% }
+
+ var optionName = new Option(myvalue, myvalue);
+ var length = object.<% "$key$n" %>.length;
+ object.<% "$key$n" %>.options[length] = optionName;
+ }
+ </script>
+ <select multiple size=5 name="<% "$key$n" %>">
+ <option selected>----------------------------------------------------------------</option>
+
+% foreach my $line ( $conf->config($key, $agentnum) ) {
+
+ <option value="<% $line %>"><% $line %></option>
+
+% }
+
+ </select><br>
+ <input type="button" value="remove selected" onClick="doremove<% "$key$n" %>()">
+ <script>SafeAddOnLoad(doremove<% "$key$n" %>);
+ SafeAddOnSubmit(selectall<% "$key$n" %>);
+ </script>
+ <br><% itable() %><tr>
+
+% if ( defined $config_item->editlist_parts ) {
+% my $pnum=0;
+% foreach my $part ( @{$config_item->editlist_parts} ) {
+
+ <td>
+
+% if ( $part->{type} eq 'text' ) {
+
+ <input type="text" name="add<% "$key${n}_$pnum" %>">
+
+% } elsif ( $part->{type} eq 'immutable' ) {
+
+ <% $part->{value} %>
+ <input type="hidden" name="add<% "$key${n}_$pnum" %>" value="<% $part->{value} %>">
+
+% } elsif ( $part->{type} eq 'select' ) {
+
+ <select name="add<% qq!$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<% "$key${n}_0" %>></td>
+
+% }
+
+ <td><input type="button" value="add" onClick="doadd<% "$key$n" %>(this.form)"></td>
+ </tr></table>
+
+% } elsif ( $element_types{$type} ) {
+%
+% my %opt = ( 'element_name' => "$key$n",
+% 'empty_label' => ' ',
+% 'showdisabled' => 1,
+% );
+% if ( $config_item->multiple ) {
+% $opt{'multiple'} = 1 if $config_item->multiple;
+% $opt{'curr_value'} = [ $conf->config($key, $agentnum) ];
+% } else {
+% $opt{'curr_value'} =
+% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '';
+% }
+
+ <% include("/elements/$type.html", %opt ) %>
+
+% } else {
+
+ <font color="#ff0000">unknown type <% $type %></font>
+
+% }
+% $n++;
+% }
+
+ </td>
+% unless ( $description_printed ) {
+ <td><% $description %></td>
+% }
+</tr>
+</table>
+<INPUT TYPE="submit" VALUE="<% $title %>">
+</FORM>
+
+</BODY>
+</HTML>
+<%once>
+
+my $conf = new FS::Conf;
+my @config_items = $conf->config_items;
+my %confitems = map { $_->key => $_ } @config_items;
+
+my %element_types = map { $_ => 1 } qw(
+ select-part_svc select-part_pkg select-pkg_class
+);
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $action = 'Set';
+
+my $agentnum = '';
+if ($cgi->param('agentnum') =~ /(\d+)$/) {
+ $agentnum=$1;
+}
+
+my $agent = '';
+my $title;
+if ($agentnum) {
+ $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "Agent $agentnum not found!" unless $agent;
+
+ $title = "$action configuration override for ". $agent->agent;
+} else {
+ $title = "$action global configuration";
+}
+
+$cgi->param('key') =~ /^([-.\w]+)$/ or die "illegal configuration item";
+my $key = $1;
+my $value = $conf->config($key);
+my $config_item = $confitems{$key};
+
+my $description = $config_item->description;
+my $config_type = $config_item->type;
+my @types = ref($config_type) ? @$config_type : ($config_type);
+
+</%init>
diff --git a/httemplate/docs/AGPL.html b/httemplate/docs/AGPL.html
new file mode 100644
index 0000000..f55bebb
--- /dev/null
+++ b/httemplate/docs/AGPL.html
@@ -0,0 +1,672 @@
+<h3 style="text-align: center;">GNU AFFERO GENERAL PUBLIC LICENSE</h3>
+<p style="text-align: center;">Version 3, 19 November 2007</p>
+
+<p>Copyright (C) 2007 Free Software Foundation, Inc. &lt;http://fsf.org/&gt;
+ <br>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.</p>
+
+<h3>Preamble</h3>
+
+<p>The GNU Affero General Public License is a free, copyleft license
+for software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.</p>
+
+<p>The licenses for most software and other practical works are
+designed to take away your freedom to share and change the works. By
+contrast, our General Public Licenses are intended to guarantee your
+freedom to share and change all versions of a program--to make sure it
+remains free software for all its users.</p>
+
+<p>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
+them 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.</p>
+
+<p>Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.</p>
+
+<p>A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.</p>
+
+<p>The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.</p>
+
+<p>An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.</p>
+
+<p>The precise terms and conditions for copying, distribution and
+modification follow.</p>
+
+<h3>TERMS AND CONDITIONS</h3>
+
+<h4>0. Definitions.</h4>
+
+<p>"This License" refers to version 3 of the GNU Affero General Public
+License.</p>
+
+<p>"Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.</p>
+
+<p>"The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.</p>
+
+<p>To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.</p>
+
+<p>A "covered work" means either the unmodified Program or a work based
+on the Program.</p>
+
+<p>To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.</p>
+
+<p>To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.</p>
+
+<p>An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.</p>
+
+<h4>1. Source Code.</h4>
+
+<p>The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.</p>
+
+<p>A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.</p>
+
+<p>The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.</p>
+
+<p>The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.</p>
+
+<p>The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.</p>
+
+<p>The Corresponding Source for a work in source code form is that
+same work.</p>
+
+<h4>2. Basic Permissions.</h4>
+
+<p>All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.</p>
+
+<p>You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.</p>
+
+<p>Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.</p>
+
+<h4>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h4>
+
+<p>No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.</p>
+
+<p>When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.</p>
+
+<h4>4. Conveying Verbatim Copies.</h4>
+
+<p>You may convey 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;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.</p>
+
+<p>You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.</p>
+
+<h4>5. Conveying Modified Source Versions.</h4>
+
+<p>You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:</p>
+
+<ul>
+<li>a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.</li>
+
+<li>b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".</li>
+
+<li>c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.</li>
+
+<li>d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.</li>
+
+</ul>
+<p>A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.</p>
+
+<h4>6. Conveying Non-Source Forms.</h4>
+
+<p>You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:</p>
+
+<ul>
+<li>a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.</li>
+
+<li>b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.</li>
+
+<li>c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.</li>
+
+<li>d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.</li>
+
+<li>e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.</li>
+
+</ul>
+<p>A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.</p>
+
+<p>A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.</p>
+
+<p>"Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.</p>
+
+<p>If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).</p>
+
+<p>The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.</p>
+
+<p>Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.</p>
+
+<h4>7. Additional Terms.</h4>
+
+<p>"Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.</p>
+
+<p>When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.</p>
+
+<p>Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:</p>
+
+<ul>
+<li>a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or</li>
+
+<li>b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or</li>
+
+<li>c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or</li>
+
+<li>d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or</li>
+
+<li>e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or</li>
+
+<li>f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.</li>
+
+</ul>
+<p>All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further restriction,
+you may remove that term. If a license document contains a further
+restriction but permits relicensing or conveying under this License, you
+may add to a covered work material governed by the terms of that license
+document, provided that the further restriction does not survive such
+relicensing or conveying.</p>
+
+<p>If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.</p>
+
+<p>Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.</p>
+
+<h4>8. Termination.</h4>
+
+<p>You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).</p>
+
+<p>However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.</p>
+
+<p>Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.</p>
+
+<p>Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.</p>
+
+<h4>9. Acceptance Not Required for Having Copies.</h4>
+
+<p>You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.</p>
+
+<h4>10. Automatic Licensing of Downstream Recipients.</h4>
+
+<p>Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.</p>
+
+<p>An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.</p>
+
+<p>You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.</p>
+
+<h4>11. Patents.</h4>
+
+<p>A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".</p>
+
+<p>A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.</p>
+
+<p>Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.</p>
+
+<p>In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.</p>
+
+<p>If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.</p>
+
+<p>If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.</p>
+
+<p>A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.</p>
+
+<p>Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.</p>
+
+<h4>12. No Surrender of Others' Freedom.</h4>
+
+<p>If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.</p>
+
+<h4>13. Remote Network Interaction; Use with the GNU General Public License.</h4>
+
+<p>Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.</p>
+
+<p>Notwithstanding any other provision of this License, you have permission
+to link or combine any covered work with a work licensed under version 3
+of the GNU General Public License into a single combined work, and to
+convey the resulting work. The terms of this License will continue to
+apply to the part which is the covered work, but the work with which it is
+combined will remain governed by version 3 of the GNU General Public
+License.</p>
+
+<h4>14. Revised Versions of this License.</h4>
+
+<p>The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero 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.</p>
+
+<p>Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero
+General Public License "or any later version" applies to it, you have
+the option of following the terms and conditions either of that
+numbered version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number
+of the GNU Affero General Public License, you may choose any version
+ever published by the Free Software Foundation.</p>
+
+<p>If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that
+proxy's public statement of acceptance of a version permanently
+authorizes you to choose that version for the Program.</p>
+
+<p>Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.</p>
+
+<h4>15. Disclaimer of Warranty.</h4>
+
+<p>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.</p>
+
+<h4>16. Limitation of Liability.</h4>
+
+<p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+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.</p>
+
+<h4>17. Interpretation of Sections 15 and 16.</h4>
+
+<p>If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.</p>
+
+<p>END OF TERMS AND CONDITIONS</p>
+
+<h3>How to Apply These Terms to Your New Programs</h3>
+
+<p>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.</p>
+
+<p>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
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.</p>
+
+<pre> &lt;one line to give the program's name and a brief idea of what it does.&gt;
+ Copyright (C) &lt;year&gt; &lt;name of author&gt;
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
+</pre>
+
+<p>Also add information on how to contact you by electronic and paper mail.</p>
+
+<p>If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.</p>
+
+<p>You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+&lt;http://www.gnu.org/licenses/&gt;.
+</p>
+
diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html
new file mode 100644
index 0000000..50600a4
--- /dev/null
+++ b/httemplate/docs/about.html
@@ -0,0 +1,53 @@
+<% include('/elements/header-popup.html', { title=>'Freeside', nobr=>1 } ) %>
+
+<% include('/elements/init_overlib.html') %>
+
+<CENTER>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
+<H3>version <% $FS::VERSION %></H3>
+</CENTER>
+
+<CENTER>
+<FONT SIZE="-1">&copy; 2010 Freeside Internet Services, Inc.<BR>
+All rights reserved.<BR>
+Licensed under the terms of the<BR>
+GNU <b>Affero</b> General Public License.<BR>
+</FONT>
+</CENTER>
+<BR>
+
+<CENTER>
+<A HREF="credits.html">Credits</A>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+<A HREF="javascript:void(0)" onClick="openLicense()">License</A>
+
+
+<BR><BR>
+<A HREF="http://www.freeside.biz/freeside" TARGET="_blank">Freeside homepage</A>
+</CENTER>
+
+<BR>
+
+<CENTER>
+<FONT SIZE="-3">"Once in a while you get shown the light, in the strangest of places if you look at it right" -R. Hunter</FONT>
+</CENTER>
+
+<SCRIPT TYPE="text/javascript">
+
+function openLicense() {
+ parent.<% include('/elements/popup_link_onclick.html',
+ 'action' => $fsurl.'docs/license.html',
+ 'label' => 'License',
+ 'actionlabel' => 'License',
+ 'width' => 600,
+ 'height' => 360,
+ 'color' => '#7e0079',
+ 'nofalse' => 1,
+ )
+ %>
+}
+
+</SCRIPT>
+
+</BODY>
+</HTML>
diff --git a/httemplate/docs/ach.html b/httemplate/docs/ach.html
new file mode 100644
index 0000000..b8a17c8
--- /dev/null
+++ b/httemplate/docs/ach.html
@@ -0,0 +1,10 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ Electronic check (ACH) information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#ffffff">
+ <IMG BORDER=0 SRC="../images/ach.png">
+ </BODY>
+</HTML>
diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html
new file mode 100755
index 0000000..2aa9348
--- /dev/null
+++ b/httemplate/docs/admin.html
@@ -0,0 +1,41 @@
+<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>Go 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</u> 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/credits.html b/httemplate/docs/credits.html
new file mode 100644
index 0000000..641be46
--- /dev/null
+++ b/httemplate/docs/credits.html
@@ -0,0 +1,177 @@
+<% include('/elements/header-popup.html', '') %>
+
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+
+<FONT SIZE=6>
+ <CENTER>Freeside</CENTER>
+</FONT>
+
+<CENTER>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
+<H3>version <% $FS::VERSION %></H3>
+</CENTER>
+
+<CENTER>
+
+<H3>Core Team</H3>
+Peter Bowen<BR>
+Jeremy Davis<BR>
+Jeff Finucane<BR>
+Jason Hall<BR>
+Ivan Kohler<BR>
+Richard Siddall<BR>
+Mark Wells<BR>
+<BR>
+
+<H3>Core Emeritus</H3>
+Kristian Hoffman<BR>
+Brian McCane<BR>
+Matt Simerson<BR>
+<BR>
+
+<H3>Contributors</H3>
+Stephen Amadei<BR>
+Eric Arvidsson<BR>
+Mark Asplen-Taylor<BR>
+Mihai Bazon<BR>
+Charles A. Beasley<BR>
+Stephen Bechard<BR>
+Eric Bosrup<BR>
+Dave Burgess<BR>
+Joe Camadine<BR>
+Chris Cappuccio<BR>
+Rebecca Cardennis<BR>
+Shane Chrisp<BR>
+Luke Crawford<BR>
+Brad Dameron<BR>
+Dave Denney<BR>
+Serge Dolgov<BR>
+Scott Edwards<BR>
+Kenny Elliott<BR>
+Donald Greer<BR>
+Joel Griffiths<BR>
+Ryan Gunn<BR>
+Troy Hammonds<BR>
+Sean Hanson<BR>
+Dale Hege<BR>
+Kelly Hickel<BR>
+Mark James<BR>
+Frederico Caldeira Knabben<BR>
+Greg Kuhnert<BR>
+Randall Lucas<BR>
+Foteos Macrides<BR>
+Roger Mangraviti<BR>
+mimooh<BR>
+Mack Nagashima<BR>
+Matt Peterson<BR>
+Luke Pfeifer<BR>
+Ricardo Signes<BR>
+Steve Simitzis<BR>
+Jason Spence<BR>
+James Switzer<BR>
+Audrey Tang<BR>
+Jason Thomas<BR>
+Jesse Vincent<BR>
+Johan Vromans<BR>
+Peter Wemm<BR>
+Mark Williamson<BR>
+Tim Yardley<BR>
+
+</CENTER>
+
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+<BR>
+
+<SCRIPT TYPE="text/javascript">
+
+function myScroll() {
+
+ documentYposition += 1;
+ window.scroll(0,documentYposition);
+
+ var timeout = 25;
+
+ if ( documentYposition > documentLength ) {
+ documentYposition = 0;
+ }
+
+ if ( documentYposition == startingPosition ) {
+ timeout = 5000;
+ }
+
+ setTimeout('myScroll()', timeout);
+}
+
+function DelayThenScroll() {
+ window.scroll(0,documentYposition);
+ documentLength = myHeight();
+ setTimeout('myScroll()', 3000);
+}
+
+function myHeight() {
+/* if (document.all)
+ return document.body.offsetHeight;
+ else if (document.layers)
+ return document.body.document.height;
+ else
+*/
+ return 1850; // approx height (add more per contributors)
+}
+
+document.body.style.overflow = 'hidden';
+
+var startingPosition = 360;
+
+//huh, adjust for firefox
+var ua = navigator.userAgent;
+var opera = /opera [56789]|opera\/[56789]/i.test(ua);
+var webkit = /webkit/i.test(ua)
+var moz = !opera && !webkit && /gecko/i.test(ua);
+if ( moz ) {
+ startingPosition += 20;
+} else if ( opera ) {
+ startingPosition += 21;
+}
+
+var documentYposition = startingPosition;
+var documentLength;
+window.onLoad = DelayThenScroll();
+
+</SCRIPT>
+
+</BODY>
+</HTML>
diff --git a/httemplate/docs/cvv2.html b/httemplate/docs/cvv2.html
new file mode 100644
index 0000000..845484d
--- /dev/null
+++ b/httemplate/docs/cvv2.html
@@ -0,0 +1,24 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ CVV2 information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#f8f8f8">
+ 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>
+ </BODY>
+</HTML>
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..3b419de
--- /dev/null
+++ b/httemplate/docs/index.html
@@ -0,0 +1,32 @@
+<head>
+ <title>Freeside Documentation</title>
+</head>
+<body bgcolor="#ffffff">
+ <h1>Freeside Documentation</h1>
+<img src="overview-new.png">
+<h3>Installation and upgrades</h3>
+<ul>
+ <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Installation">New Installation</a>
+ <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:RT_Installation">Installing integrated RT ticketing</a>
+ <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Self-Service_Installation">Signup/Self-service installation</a>
+ <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Upgrading">Upgrading from 1.5.8 or 1.6.X</a>
+</ul>
+<h3>Configuration and setup</h3>
+<ul>
+<!--
+ <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="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Exports_.28provisioning.29">Exports</a>
+ <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Billing">Billing</a>
+</ul>
+<h3>Developer</h3>
+<ul>
+ <li><a href="schema.html">Schema reference</a>
+ <li><a href="man/FS.html">Perl API</a>
+ <li><a href="legacy.html">Importing legacy data</a>
+</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/license.html b/httemplate/docs/license.html
new file mode 100644
index 0000000..fc3da69
--- /dev/null
+++ b/httemplate/docs/license.html
@@ -0,0 +1,120 @@
+<% include('/elements/header-popup.html', { title=>'Freeside', nobr=>1 } ) %>
+<CENTER>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
+<H3>version <% $FS::VERSION %></H3>
+</CENTER>
+
+<P>
+
+Copyright &copy; 2005-2009 Freeside Internet Services, Inc.<BR>
+Copyright &copy; 2000-2005 Ivan Kohler<BR>
+Copyright &copy; 1999 Silicon Interactive Software Design<BR>
+All rights reserved<BR>
+
+<P>
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU <B>Affero</B> General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or (at
+ your option) any later version.
+
+<P>
+ 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 Affero General Public License for more details.
+
+<P>
+ You should have received a copy of the GNU Affero General Public
+ License along with this program, in the file `<A HREF="AGPL.html" TARGET="_blank">AGPL</A>'; if not,
+ see &lt;<A HREF="http://www.fsf.org/licensing/licenses/agpl-3.0.html" TARGET="_blank"">http://www.fsf.org/licensing/licenses/agpl-3.0.html</a>&gt;.
+
+<P>
+ At your option, you may also redistribute and/or modify the files in the
+ fs_selfservice/ directory (but not the rest of the software) under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+<P>
+ At your option, you may also redistribute and/or modify the
+ fs_selfservice/php/freeside.class.php file (but not the rest of the
+ software) under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+<!--entire other packages-->
+
+<P>
+Contains "Request Tracker" <http://www.bestpractical.com/rt/> and
+"RTx::Extension::ActivityReports" from Best Practical Solutions, licensed under
+the terms of the GNU GPL, version two. Best Practical Solutions considers the
+Request Tracker software in this case to be "merely aggregated" with Freeside,
+and not a "combined work", and as such Request Tracker is distributed only
+under the original GPLv2 license.
+
+<!--important widgets or other "whole" bits-->
+
+<P>
+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.
+
+<P>
+Contains "JS Calendar" <http://dynarch.com/mishoo/calendar.epl>
+by Mihai Bazon <mishoo@infoiasi.ro> licensed under the terms of the GNU LGPL.
+
+<P>
+Contains FCKeditor by Frederico Caldeira Knabben, licensed under the terms of
+the GNU GPL.
+
+<P>
+Contains XMenu <http://webfx.eae.net/dhtml/xmenu/xmenu.html>
+by Erik Arvidsson, licensed under the terms of the GNU GPL.
+
+<!--RT add-ons-->
+
+<P>
+Contains "RTx::Statistics Package"
+<http://wiki.bestpractical.com/view/RT3StatisticsPackage> from Kelly Hickel
+<kfh@mqsoftware.com>, licensed under the same terms as Perl (GPL/Artistic).
+
+<P>
+Contains "RTx::WebCronTool" <http://search.cpan.org/dist/RTx-WebCronTool/> from
+Audrey Tang, licensed under the same terms as Perl (GPL/Artistic).
+
+<!--libraries-->
+
+<P>
+Contains the QLIB JavaScript library <http://qlib.quazzle.com/> by
+Quazzle.com, Serge Dolgov, licensed under the terms of the GNU GPL.
+
+<P>
+Contains the overlibmws DHTML Popup Library <http://www.macridesweb.com/oltest/>
+by Foteos Macrides (derived from overLIB <http://www.bosrup.com/web/overlib/>
+by Erik Bosrup), licensed under the terms of the Artistic license
+<http://www.macridesweb.com/oltest/license.html>.
+
+<P>
+XMLHttpRequest implementation based on the SAJAX toolkit, licensed under the
+terms of the BSD license.<BR>
+&copy; 2005 modernmethod, inc<BR>
+Perl backend version &copy; 2005 Nathan Schmidt
+
+<P>
+Contains code derived from eps2png by Johan Vromans, licensed under the same
+terms as Perl (GPL/Artistic).
+
+<!-- artwork -->
+
+<P>
+Contains public domain artwork from openclipart.org by mimooh and other
+authors.
+
+<P>
+Contains icons from
+<A HREF="http://famfamfam.com/" TARGET="_blank">famfamfam.com</A>
+by Mark James, licensed under the terms of the Creative Commons Attribution
+2.5 License.
+
+</BODY>
+</HTML>
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-new.dia b/httemplate/docs/overview-new.dia
new file mode 100644
index 0000000..d9989a3
--- /dev/null
+++ b/httemplate/docs/overview-new.dia
Binary files differ
diff --git a/httemplate/docs/overview-new.png b/httemplate/docs/overview-new.png
new file mode 100644
index 0000000..bf81546
--- /dev/null
+++ b/httemplate/docs/overview-new.png
Binary files differ
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..e00f59c
--- /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..cd4914a
--- /dev/null
+++ b/httemplate/docs/schema.html
@@ -0,0 +1,533 @@
+
+ <title>Schema reference</title>
+</head>
+<body>
+ <h1>Schema reference</h1>
+ Schema diagram (1.4.1): <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>billpkgnum - primary_key
+ <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="pay_batch" href="man/FS/pay_batch.html">pay_batch</a> - Pending batch
+ <ul>
+ <li>batchnum
+ <li>status
+ <li>download
+ <li>upload
+ </ul>
+ <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch members
+ <ul>
+ <li>paybatchnum
+ <li>batchnum
+ <li>payby - CARD, CHEK, LECB, BILL, or COMP
+ <li>payinfo - account number
+ <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
+ <li>status
+ </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>promo_code - promotional code
+ <li><i>deprecated</i> setup - setup fee expression
+ <li>freq - recurring frequency (months)
+ <li><i>deprecated</i> 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><i>deprecated</i> plandata - additional price plan data
+ <li>disabled - Disabled flag, empty or `Y'
+ </ul>
+ <li><a name="part_pkg_option" href="man/FS/part_pkg_option.html">part_pkg_option</a> - Package definition options
+ <ul>
+ <li>optionnum - primary key
+ <li>pkgpart - <a href="#part_pkg">Package definition</a>
+ <li>optionname - option name
+ <li>optionvalue - option value
+ </ul>
+ <li><a name="reg_code" href="man/FS/reg_code.html">reg_code</A> - One-time registration codes
+ <ul>
+ <li>codenum - primary key
+ <li>code
+ <li>agentnum - <a href="#agent">Agent</a>
+ </ul>
+ <li><a name="reg_code_pkg" href="man/FS/reg_code_pkg.html">reg_code_pkg</A> - Registration code link to package definitions
+ <ul>
+ <li>codepkgnum - primary key
+ <li>codenum - <a href="#reg_code">Registration code</a>
+ <li>pkgpart - <a href="#part_pkg">Package definition</a>
+ </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>pkgsvcnum - primary key
+ <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> - prepaid cards
+ <ul>
+ <li>prepaynum - primary key
+ <li>identifier - text or numeric string of prepaid card
+ <li>amount - amount of prepayment
+ <li>seconds - prepaid time instead of (or in addition to) monetary value
+ <li>agentnum - optional agent assignment for prepaid cards
+ </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>typepkgnum - primary key
+ <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="rate" href="man/FS/rate.html">rate</a> - Call rate plans
+ <ul>
+ <li>ratenum - primary key
+ <li>ratename
+ </ul>
+ <li><a name="rate_detail" href="man/FS/rate_detail.html">rate_detail</a> - Call rate detail
+ <ul>
+ <li>ratedetailnum - primary key
+ <li>ratenum - <a href="#rate">rate plan</a>
+ <li>orig_regionnum - call origination <a href="#rate_region">region</a>
+ <li>dest_regionnum - call destination <a href="#rate_region">region</a>
+ <li>min_included - included minutes
+ <li>min_charge - charge per minute
+ <li>sec_granularity - granularity in seconds, i.e. 6 or 60
+ </ul>
+ <li><a name="rate_region" href="man/FS/rate_region.html">rate_region</a> - Call rate region
+ <ul>
+ <li>regionnum - primary key
+ <li>regionname
+ </ul>
+ <li><a name="rate_prefix" href="man/FS/rate_prefix.html">rate_prefix</a> - Call rate prefix
+ <ul>
+ <li>prefixnum - primary key
+ <li>regionnum - <a href="#rate_region">rate region</a>
+ <li>countrycode
+ <li>npa
+ <li>nxx
+ </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>
+ <li><a name="clientapi_session" href="man/FS/clientapi_session.html">clientapi_session</a> - ClientAPI session store
+ <ul>
+ <li>sessionnum - primary key
+ <li>sessionid - session ID
+ <li>namespace - session namespace
+ </ul>
+ <li><a name="clientapi_session_field" href="man/FS/clientapi_session_field.html">clientapi_session_field</a> - Client API session store data
+ <ul>
+ <li>fieldnum - primary key
+ <li>sessionnum - <a href="#session">session</a>
+ <li>fieldname
+ <li>fieldvalue
+ </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/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
new file mode 100755
index 0000000..77ab1fe
--- /dev/null
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -0,0 +1,220 @@
+<% include("/elements/header.html",'Customer package - Edit dates') %>
+
+%#, menubar(
+%# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum",
+%#));
+
+<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="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+% # raw error from below
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT>
+% }
+% #or, regular error handler
+<% include('/elements/error.html') %>
+
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Package number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->pkgnum %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Package</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->pkg %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Custom</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->custom %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Comment</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Order taker</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->otaker %></TD>
+ </TR>
+
+% if ( $cust_pkg->setup && ! $cust_pkg->start_date ) {
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
+% } else {
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
+% }
+
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'last_bill', label=>$last_bill_or_renewed &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'bill', label=>$next_bill_or_prepaid_until &>
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will <b>suspend</b> this package when the date is reached)' &>
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &>
+
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'expire', label=>'Expiration', note=>'(will <b>cancel</b> this package when the date is reached)' &>
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'cancel', label=>'Cancellation' &>
+
+<%def .row_edit>
+<%args>
+ $cust_pkg
+ $column
+ $label
+ $note => ''
+</%args>
+% my $value = $cust_pkg->get($column);
+% $value = $value ? time2str($format, $value) : "";
+
+ <TR>
+ <TD ALIGN="right"><% $label %> date</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "<% $column %>"
+ SIZE = 32
+ ID = "<% $column %>_text"
+ VALUE = "<% $value %>"
+ >
+ <IMG SRC = "../images/calendar.png"
+ ID = "<% $column %>_button"
+ STYLE = "cursor: pointer"
+ TITLE = "Select date"
+ >
+% if ( $note ) {
+ <BR><FONT SIZE=-1><% $note %></FONT>
+% }
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "<% $column %>_text",
+ ifFormat: "<% $date_format %>",
+ button: "<% $column %>_button",
+ align: "BR"
+ });
+ </SCRIPT>
+
+</%def>
+
+<%def .row_display>
+<%args>
+ $cust_pkg
+ $column
+ $label
+ $note => ''
+</%args>
+% if ( $cust_pkg->get($column) ) {
+ <TR>
+ <TD ALIGN="right"><% $label %> date</TD>
+ <TD BGCOLOR="#ffffff"><% time2str($format,$cust_pkg->get($column)) %>
+% if ( $note ) {
+ <BR><FONT SIZE=-1><% $note %></FONT>
+% }
+ </TD>
+ </TR>
+% }
+</%def>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Apply Changes">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%shared>
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $format = $date_format. ' %T %z (%Z)';
+
+</%shared>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer package dates');
+
+
+my $error = '';
+my( $pkgnum, $cust_pkg );
+
+if ( $cgi->param('error') ) {
+
+ $pkgnum = $cgi->param('pkgnum');
+
+ if ( $cgi->param('error') =~ /^_/ ) {
+
+ my @errors = ();
+ my %errors = map { $_=>1 } split(',', $cgi->param('error'));
+ $cgi->param('error', '');
+
+ if ( $errors{'_bill_areyousure'} ) {
+ if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) {
+ my $bill = $1;
+ push @errors,
+ "You are attempting to set the next bill date to $bill, which is
+ in the past. This will charge the customer for the interval
+ from $bill until now. Are you sure you want to do this? ".
+ '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">';
+ }
+ }
+
+ if ( $errors{'_setup_areyousure'} ) {
+ push @errors,
+ "You are attempting to remove the setup date. This will re-charge the
+ customer for the setup fee. Are you sure you want to do this? ".
+ '<INPUT TYPE="checkbox" NAME="setup_areyousure" VALUE="1">';
+ }
+
+ if ( $errors{'_start'} ) {
+ push @errors,
+ "You are attempting to add a start date to a package that has already
+ started billing.";
+ }
+
+ $error = join('<BR><BR>', @errors );
+
+ }
+
+ #get package record
+ $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ die "No package!" unless $cust_pkg;
+
+ foreach my $col (qw( start_date setup last_bill bill adjourn expire )) {
+ my $value = $cgi->param($col);
+ $cust_pkg->set( $col, $value ? parse_datetime($value) : '' );
+ }
+
+} else {
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "no pkgnum";
+ $pkgnum = $1;
+
+ #get package record
+ $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ die "No package!" unless $cust_pkg;
+
+}
+
+my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
+
+my( $last_bill_or_renewed, $next_bill_or_prepaid_until );
+unless ( $part_pkg->is_prepaid ) {
+ #$billed_or_prepaid = 'billed';
+ $last_bill_or_renewed = 'Last bill';
+ $next_bill_or_prepaid_until = 'Next bill';
+} else {
+ #$billed_or_prepaid = 'prepaid';
+ $last_bill_or_renewed = 'Renewed';
+ $next_bill_or_prepaid_until = 'Prepaid until';
+}
+
+</%init>
diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html
new file mode 100644
index 0000000..1eed26d
--- /dev/null
+++ b/httemplate/edit/access_group.html
@@ -0,0 +1,80 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Employee Group',
+ 'table' => 'access_group',
+ 'labels' => {
+ 'groupnum' => 'Group number',
+ 'groupname' => 'Group name',
+ },
+
+ 'viewall_dir' => 'browse',
+
+ 'html_bottom' => $html_bottom_sub,
+ )
+%>
+<%once>
+
+tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_info;
+
+</%once>
+<%init>
+
+my $html_bottom_sub = sub {
+ my $access_group = shift;
+
+ #some false laziness w/browse/access_group.html
+ my $columns = 3;
+ my $count = 0;
+
+ '<BR>'.
+ '<FONT SIZE="+1">Group limited to these agent(s)</FONT><BR>'.
+ 'Employees in this group will only see customers of the selected agents in the system and reports.<BR>'.
+ ntable("#cccccc",2).
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table.html',
+ 'source_obj' => $access_group,
+ 'link_table' => 'access_groupagent',
+ 'target_table' => 'agent',
+ 'name_col' => 'agent',
+ 'target_link' => $p.'edit/agent.cgi?',
+ 'disable-able' => 1,
+ ).
+ '</TD></TR></TABLE>'.
+
+ '<BR><FONT SIZE="+1">Group access rights</FONT><BR>'.
+ include('/elements/table-grid.html', bgcolor=>'#cccccc' ).
+ '<TR>'. join( '', map {
+ '<TD CLASS="inv" VALIGN="top"><TABLE BGCOLOR="#cccccc" WIDTH=100%>'.
+ '<TR><TH BGCOLOR="#dcdcdc">'. $_. '</TH></TR>'.
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table-name.html',
+ 'source_obj' => $access_group,
+ 'link_table' => 'access_right',
+ 'link_static' => { 'righttype' =>
+ 'FS::access_group',
+ },
+ 'num_col' => 'rightobjnum',
+ 'name_col' => 'rightname',
+ 'names_list' => [ map {
+ my $rn =
+ ref($_) ? $_->{'rightname'} : $_;
+ my %hash = ();
+ $hash{'note'} = '&nbsp;*'
+ if ref($_) && $_->{'global'};
+ $hash{'desc'} = $_->{'desc'}
+ if ref($_) && $_->{'desc'};
+ [ $rn => \%hash ];
+ }
+ @{ $rights{$_} }
+ ],
+ ).
+ '<BR>'.
+ '</TD></TR></TABLE></TD>'.
+ ( ++$count % $columns ? '' : '</TR><TR>')
+
+ } keys %rights ). '</TR></TABLE>'.
+
+ '* Global rights. These rights provide access to global data which is shared among all agents. Their use is not recommended for groups which are limited to a subset of agents.<BR>';
+
+};
+
+</%init>
diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html
new file mode 100644
index 0000000..22cf896
--- /dev/null
+++ b/httemplate/edit/access_user.html
@@ -0,0 +1,65 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Employee',
+ 'table' => 'access_user',
+ 'fields' => [
+ 'username',
+ { field=>'_password', type=>'password' },
+ { field=>'_password2', type=>'password' },
+ 'last',
+ 'first',
+ { field=>'user_custnum', type=>'search-cust_main', },
+ { field=>'disabled', type=>'checkbox', value=>'Y' },
+ ],
+ 'labels' => {
+ 'usernum' => 'User number',
+ 'username' => 'Username',
+ '_password' => 'Password',
+ '_password2' => 'Re-enter Password',
+ 'last' => 'Last name',
+ 'first' => 'First name',
+ 'user_custnum' => 'Customer (optional)',
+ 'disabled' => 'Disable employee',
+ },
+ 'edit_callback' => sub { my( $c, $o ) = @_;
+ $o->set('_password', '');
+ },
+ 'viewall_dir' => 'browse',
+ 'html_bottom' =>
+ sub {
+ my $access_user = shift;
+
+ '<BR>Employee Groups<BR>'.
+ ntable("#cccccc",2).
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table.html',
+ 'source_obj' => $access_user,
+ 'link_table' => 'access_usergroup',
+ 'target_table' => 'access_group',
+ 'name_col' => 'groupname',
+ 'target_link' => $p.'edit/access_group.html?',
+ #'disable-able' => 1,
+ ).
+ '</TR></TD></TABLE>'
+ ;
+ },
+ 'onsubmit' => 'check_user_custnum_search',
+ 'html_foot' => $check_user_custnum_search,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $check_user_custnum_search = <<END;
+ <SCRIPT TYPE="text/javascript">
+ function check_user_custnum_search(what) {
+ while ( user_custnum_search_active ) {
+ // javascript needs ambien
+ }
+ return true;
+ }
+ </SCRIPT>
+END
+
+</%init>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
new file mode 100755
index 0000000..30a0865
--- /dev/null
+++ b/httemplate/edit/agent.cgi
@@ -0,0 +1,144 @@
+<% include("/elements/header.html","$action Agent", menubar(
+ 'View all agents' => $p. 'browse/agent.cgi',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM METHOD = POST
+ ACTION = "<%popurl(1)%>process/agent.cgi"
+ onSubmit = "return check_agent_custnum_search(this)"
+>
+
+<SCRIPT TYPE="text/javascript">
+ function check_agent_custnum_search(what) {
+ while ( agent_custnum_search_active ) {
+ // javascript needs ambien
+ }
+ return true;
+ }
+</SCRIPT>
+
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
+
+<% &ntable("#cccccc", 2, '') %>
+
+ <TR>
+ <TH ALIGN="right">Agent</TH>
+ <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<% $agent->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 %>"<% ( $agent->typenum && ( $agent->typenum == $agent_type->typenum ) ) ? ' SELECTED' : '' %>>
+ <% $agent_type->getfield('typenum') %>: <% $agent_type->getfield('atype') %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Master customer</TH>
+ <TD>
+ <% include('/elements/search-cust_main.html',
+ 'field_name' => 'agent_custnum',
+ 'curr_value' => $agent->agent_custnum,
+ 'find_button' => 1,
+ )
+ %>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Disable</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $agent->disabled eq 'Y' ? ' CHECKED' : '' %>></TD>
+ </TR>
+
+% if ( $conf->exists('agent-invoice_template') ) {
+
+ <% include('/elements/tr-select-invoice_template.html',
+ 'label' => 'Invoice template',
+ 'field' => 'invoice_template',
+ 'curr_value' => $agent->invoice_template,
+ )
+ %>
+
+% } else {
+
+ <INPUT TYPE="hidden" NAME="invoice_template" VALUE="<% $agent->invoice_template %>">
+
+% }
+
+% if ( $conf->config('ticket_system') ) {
+% my $default_queueid = $conf->config('ticket_system-default_queueid');
+% my $default_queue = FS::TicketSystem->queue($default_queueid);
+% $default_queue = "(default) $default_queueid: $default_queue"
+% if $default_queueid;
+% my %queues = FS::TicketSystem->queues();
+% my @queueids = sort { $a <=> $b } keys %queues;
+%
+
+ <TR>
+ <TD ALIGN="right">Ticketing queue</TD>
+ <TD>
+ <SELECT NAME="ticketing_queueid">
+ <OPTION VALUE=""><% $default_queue %>
+% foreach my $queueid ( @queueids ) {
+
+ <OPTION VALUE="<% $queueid %>" <% $agent->ticketing_queueid == $queueid ? ' SELECTED' : '' %>><% $queueid %>: <% $queues{$queueid} %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+
+ <TR>
+ <TD ALIGN="right">Access Groups</TD>
+ <TD><% include('/elements/checkboxes-table.html',
+ 'source_obj' => $agent,
+ 'link_table' => 'access_groupagent',
+ 'target_table' => 'access_group',
+ 'name_col' => 'groupname',
+ 'target_link' => $p. 'edit/access_group.html?',
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $agent->agentnum ? "Apply changes" : "Add agent" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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 $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html
new file mode 100644
index 0000000..4a7cedf
--- /dev/null
+++ b/httemplate/edit/agent_payment_gateway.html
@@ -0,0 +1,68 @@
+<% include("/elements/header.html","$action payment gateway override for ". $agent->agent, menubar(
+ #'View all payment gateways' => $p. 'browse/payment_gateway.html',
+ 'View all agents' => $p. 'browse/agent.html',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/agent_payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+
+Use gateway <SELECT NAME="gatewaynum">
+% foreach my $payment_gateway (
+% qsearch('payment_gateway', { 'disabled' => '' } )
+% ) {
+%
+
+ <OPTION VALUE="<% $payment_gateway->gatewaynum %>"><% $payment_gateway->gateway_module %> (<% $payment_gateway->gateway_username %>)
+% }
+
+</SELECT>
+<BR><BR>
+
+for <SELECT NAME="cardtype" MULTIPLE>
+% foreach my $cardtype (
+% "",
+% "VISA card",
+% "MasterCard",
+% "Discover card",
+% "American Express card",
+% "Diner's Club/Carte Blanche",
+% "enRoute",
+% "JCB",
+% "BankCard",
+% "Switch",
+% "Solo",
+% 'ACH',
+%) {
+
+ <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
+% }
+
+</SELECT>
+<BR><BR>
+
+(optional) when invoice contains only items of taxclass <INPUT TYPE="text" NAME="taxclass">
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Add gateway override">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum";
+my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+die "agentnum $1 not found" unless $agent;
+
+#my @agent_payment_gateway;
+if ( $cgi->param('error') ) {
+}
+
+my $action = 'Add';
+
+</%init>
diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi
new file mode 100755
index 0000000..8a6fbc2
--- /dev/null
+++ b/httemplate/edit/agent_type.cgi
@@ -0,0 +1,57 @@
+<% include("/elements/header.html","$action Agent Type", menubar(
+ 'View all agent types' => "${p}browse/agent_type.cgi",
+))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/agent_type.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="typenum" VALUE="<% $agent_type->typenum %>">
+Agent Type #<% $agent_type->typenum || "(NEW)" %>
+<BR>
+
+Agent Type
+<INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="<% $agent_type->atype %>">
+<BR><BR>
+
+Select which packages agents of this type may sell to customers<BR>
+<% ntable("#cccccc", 2) %><TR><TD>
+<% include('/elements/checkboxes-table.html',
+ 'source_obj' => $agent_type,
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'part_pkg',
+ 'name_callback' => sub { $_[0]->pkg_comment(nopkgpart => 1); },
+ 'target_link' => $p.'edit/part_pkg.cgi?',
+ 'disable-able' => 1,
+
+ )
+%>
+</TD></TR></TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="<% $agent_type->typenum ? "Apply changes" : "Add agent type" %>">
+
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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';
+
+</%init>
diff --git a/httemplate/edit/allocate.html b/httemplate/edit/allocate.html
new file mode 100644
index 0000000..8d1347d
--- /dev/null
+++ b/httemplate/edit/allocate.html
@@ -0,0 +1,33 @@
+<% include('elements/edit.html',
+ 'name' => 'Allocation',
+ 'table' => 'addr_block',
+ 'labels' => { 'NetAddr' => 'Block',
+ 'routernum' => 'Router',
+ },
+ 'fields' => [ { 'field' => 'NetAddr',
+ 'type' => 'fixed',
+ },
+ { 'field' => 'routernum',
+ 'type' => 'select-table',
+ 'table' => 'router',
+ 'name_col' => 'routername',
+ 'disable_empty' => 1,
+ 'agent_virt' => 1,
+ 'agent_null_right' =>
+ 'Broadband global configuration',
+ },
+ ],
+ 'post_url' => "process/addr_block/allocate.cgi",
+ 'popup' => 1,
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/bulk-cust_main_county.html b/httemplate/edit/bulk-cust_main_county.html
new file mode 100644
index 0000000..8e447e5
--- /dev/null
+++ b/httemplate/edit/bulk-cust_main_county.html
@@ -0,0 +1,128 @@
+<% include('/elements/header-popup.html', $title ) %>
+
+<FORM ACTION="<% popurl(1)."process/bulk-cust_main_county.html" %>" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="action" VALUE="<% $action %>">
+<INPUT TYPE="hidden" NAME="taxnum" VALUE="<% join(',', @taxnum) %>">
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<% include('/elements/tr-td-label.html', 'label' => 'Country' ) %>
+ <TD BGCOLOR="#dddddd"><% $countries %>
+ </TD>
+</TR>
+
+<% include('/elements/tr-td-label.html', 'label' => 'State' ) %>
+ <TD BGCOLOR="#dddddd"><% $states %>
+ </TD>
+</TR>
+
+% if ( $counties ) {
+ <% include('/elements/tr-td-label.html', 'label' => 'County' ) %>
+ <TD BGCOLOR="#dddddd"><% $counties %>
+ </TD>
+ </TR>
+% }
+
+% if ( $conf->exists('enable_taxclasses') && $taxclasses ) {
+ <% include('/elements/tr-td-label.html', 'label' => 'Tax Class' ) %>
+ <TD BGCOLOR="#dddddd"><% $taxclasses %>
+ </TD>
+ </TR>
+% }
+
+<% include('/elements/tr-input-text.html',
+ 'field' => 'taxname',
+ 'label' => 'Tax name'
+ )
+%>
+
+<% include('/elements/tr-input-percentage.html',
+ 'field' => 'tax',
+ 'label' => 'Tax rate',
+ )
+%>
+
+<% include('/elements/tablebreak-tr-title.html', value=>'Exemptions' ) %>
+
+<% include('/elements/tr-checkbox.html',
+ 'field' => 'setuptax',
+ 'value' => 'Y',
+ 'label' => 'This tax not applicable to setup fees',
+ )
+%>
+
+<% include('/elements/tr-checkbox.html',
+ 'field' => 'recurtax',
+ 'value' => 'Y',
+ 'label' => 'This tax not applicable to recurring fees',
+ )
+%>
+
+<% include('/elements/tr-input-money.html',
+ 'field' => 'exempt_amount',
+ 'label' => 'Monthly exemption per customer ($25 "Texas tax")',
+ )
+%>
+
+</TABLE>
+
+<BR>
+
+<INPUT TYPE="submit" VALUE="Bulk <% $action %> tax">
+
+<%init>
+
+my $conf = new FS::Conf;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @taxnum;
+$cgi->param('taxnum') =~ /^([\d,]+)$/
+ or $m->comp('/elements/errorpage-popup.html', $cgi->param('error') || 'Nothing selected');
+my @taxnum = split(',', $1);
+
+$cgi->param('action') =~ /^(add|edit)$/ or die "unknown action";
+my $action = $1;
+my $title = "Bulk $action tax rate";
+
+my @cust_main_county =
+ map {
+ qsearchs('cust_main_county', { 'taxnum' => $_ })
+ or die "unknown taxnum $1";
+ }
+ @taxnum;
+
+my %seen_country = {};
+my @countries = map code2country($_)."&nbsp;($_)",
+ grep !$seen_country{$_}++,
+ map $_->country,
+ @cust_main_county;
+my $countries = join(', ', @countries);
+
+my %seen_state = {};
+my @states = map state_label($_->[0], $_->[1]),
+ grep !$seen_state{$_->[0]}++,
+ map [ $_->state, $_->country ],
+ @cust_main_county;
+my $states = join(', ', @states);
+
+my %seen_county = {};
+my @counties = grep !$seen_county{$_}++, map $_->county, @cust_main_county;
+my $counties = join(', ', @counties);
+
+my %seen_taxclass = {};
+my @taxclasses = grep !$seen_taxclass{$_}++, map $_->taxclass, @cust_main_county;
+my $taxclasses = join(', ', @taxclasses);
+
+#my @fields = (
+# { field=>'country', type=>'fixed-country', },
+# { field=>'state', type=>'fixed-state', },
+# { field=>'county', type=>'fixed', },
+#);
+
+#push @fields, { field=>'taxclass', type=>'fixed', }
+# if $conf->exists('enable_taxclasses');
+
+</%init>
diff --git a/httemplate/edit/bulk-cust_svc.html b/httemplate/edit/bulk-cust_svc.html
new file mode 100644
index 0000000..a3c21b1
--- /dev/null
+++ b/httemplate/edit/bulk-cust_svc.html
@@ -0,0 +1,95 @@
+<% include('/elements/header.html', 'Bulk customer service change') %>
+
+<% include('/elements/init_overlib.html') %>
+
+<% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [qw( old_svcpart new_svcpart pkgpart )],
+ 'process/bulk-cust_svc.cgi',
+ $p.'browse/part_svc.cgi',
+ )
+%>
+
+<FORM NAME="OneTrueForm">
+%
+% $cgi->param('svcpart') =~ /^(\d+)$/
+% or die "illegal svcpart: ". $cgi->param('svcpart');
+%
+% my $old_svcpart = $1;
+% my $src_part_svc = qsearchs('part_svc', { 'svcpart' => $old_svcpart } )
+% or die "unknown svcpart: $old_svcpart";
+%
+
+
+<INPUT NAME="old_svcpart" TYPE="hidden" VALUE="<% $old_svcpart %>">
+Change <!-- customer
+<B><% $src_part_svc->svcpart %>: <% $src_part_svc->svc %></B> services
+<BR>
+-->
+
+<SELECT NAME="pkgpart">
+% my $num_cust_svc = $src_part_svc->num_cust_svc;
+% if ( $num_cust_svc > 1 ) {
+
+ <OPTION VALUE="">all <% $num_cust_svc %> <% $src_part_svc->svc %> services
+% } else {
+
+ <OPTION VALUE="">the <% $num_cust_svc %> <% $src_part_svc->svc %> service
+% }
+%
+% my $num_unlinked = $src_part_svc->num_cust_svc(0);
+% if ( $num_unlinked ) {
+%
+
+ <OPTION VALUE="0">the <% $num_unlinked %> unlinked <% $src_part_svc->svc %> services
+% }
+% foreach my $schwartz (
+% grep { $_->[1] }
+% map { [ $_, $src_part_svc->num_cust_svc($_->pkgpart) ] }
+% qsearch('part_pkg', {} )
+% ) {
+% my( $part_pkg, $num_cust_svc ) = @$schwartz;
+%
+
+ <OPTION VALUE="<% $part_pkg->pkgpart %>">the <% $num_cust_svc %>
+ <% $src_part_svc->svc %> service<% $num_cust_svc > 1 ? 's in' : ' in a' %>
+ <% $part_pkg->pkg %> package<% $num_cust_svc > 1 ? 's' : '' %>
+% }
+
+</SELECT>
+<BR>
+
+to new service definition
+<SELECT NAME="new_svcpart">
+% foreach my $dest_part_svc (
+% grep { $_->svcpart != $old_svcpart
+% && $_->svcdb eq $src_part_svc->svcdb
+% }
+% qsearch('part_svc', { 'disabled' => '' } )
+% ) {
+%
+
+ <OPTION VALUE="<% $dest_part_svc->svcpart %>"><% $dest_part_svc->svcpart %>: <% $dest_part_svc->svc %>
+% }
+
+</SELECT>
+<BR>
+
+<BR>
+
+<SCRIPT TYPE="text/javascript">
+var confirm_change = '<P ALIGN="center"><B>Bulk customer service change - Are you sure?</B><BR><P ALIGN="CENTER" <INPUT TYPE="button" VALUE="Yes, make changes" onClick="process();">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE="BUTTON" VALUE="Cancel" onClick="cClick()">';
+</SCRIPT>
+
+<INPUT TYPE="button" VALUE="Bulk change customer services" onClick="overlib(confirm_change, CAPTION, 'Confirm bulk customer service change', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' );">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/cgp_rule.html b/httemplate/edit/cgp_rule.html
new file mode 100644
index 0000000..d9c19a1
--- /dev/null
+++ b/httemplate/edit/cgp_rule.html
@@ -0,0 +1,102 @@
+<% include('elements/edit.html',
+ 'name_singular' => 'rule',
+ 'table' => 'cgp_rule',
+ 'labels' => { 'rulenum' => 'Rule',
+ 'name' => 'Name',
+ 'comment' => 'Comment',
+ 'priority' => 'Priority',
+ 'ruleconditionnum' => 'Condition',
+ 'ruleactionnum' => 'Action',
+ },
+ 'fields' => [ 'name',
+ 'comment',
+ { 'field' => 'priority',
+ 'type' => 'select',
+ 'options' => [ 0 .. 10 ],
+ 'labels' => { 0 => 'Inactive' },
+ },
+ { 'field' => 'svcnum', 'type' => 'hidden', },
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Conditions',
+ },
+ { 'field' => 'ruleconditionnum',
+ 'type' => 'select-cgp_rule_condition',
+ 'o2m_table' => 'cgp_rule_condition',
+ 'm2_label' => 'Condition',
+ 'm2_error_callback' => $m2_error_callback_cond,
+ },
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Actions',
+ },
+ { 'field' => 'ruleactionnum',
+ 'type' => 'select-cgp_rule_action',
+ 'o2m_table' => 'cgp_rule_action',
+ 'm2_label' => 'Action',
+ 'm2_error_callback' => $m2_error_callback_action,
+ },
+ ],
+ 'new_callback' => sub { my( $cgi, $cgp_rule ) = @_;
+ $cgp_rule->svcnum( $cgi->param('svcnum') );
+ },
+ #'viewall_url' => $viewall_url,
+ 'menubar' => [],
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+#my $viewall_url = $p. "browse/$table.html?svcnum=$svcnum";
+
+my $m2_error_callback_cond = sub {
+ my($cgi, $object) = @_;
+
+ my @fields = qw( condition op params );
+ my @gfields = ( '', map "_$_", @fields );
+
+ map {
+ if ( /^ruleconditionnum(\d+)$/ ) {
+ my $num = $1;
+ if ( grep $cgi->param("ruleconditionnum$num$_"), @gfields ) {
+ my $x = new FS::cgp_rule_condition {
+ 'ruleconditionnum' => $cgi->param("ruleconditionnum$num"),
+ map { $_ => scalar($cgi->param("ruleconditionnum${num}_$_")) } @fields,
+ };
+ $x;
+ } else {
+ ();
+ }
+ } else {
+ ();
+ }
+ }
+ $cgi->param;
+};
+
+my $m2_error_callback_action = sub {
+ my($cgi, $object) = @_;
+
+ my @fields = qw( action params );
+ my @gfields = ( '', map "_$_", @fields );
+
+ map {
+ if ( /^ruleactionnum(\d+)$/ ) {
+ my $num = $1;
+ if ( grep $cgi->param("ruleactionnum$num$_"), @gfields ) {
+ my $x = new FS::cgp_rule_condition {
+ 'ruleactionnum' => $cgi->param("ruleactionnum$num"),
+ map { $_ => scalar($cgi->param("ruleactionnum${num}_$_")) } @fields,
+ };
+ $x;
+ } else {
+ ();
+ }
+ } else {
+ ();
+ }
+ }
+ $cgi->param;
+
+};
+
+</%init>
diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi
new file mode 100755
index 0000000..532db6a
--- /dev/null
+++ b/httemplate/edit/cust_bill_pay.cgi
@@ -0,0 +1,14 @@
+<% include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'dst_table' => 'cust_bill',
+ 'dst_thing' => 'invoice',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/cust_category.html b/httemplate/edit/cust_category.html
new file mode 100644
index 0000000..18a6189
--- /dev/null
+++ b/httemplate/edit/cust_category.html
@@ -0,0 +1,5 @@
+<% include( 'elements/category_Common.html',
+ 'name' => 'Customer Category',
+ 'table' => 'cust_category',
+ )
+%>
diff --git a/httemplate/edit/cust_class.html b/httemplate/edit/cust_class.html
new file mode 100644
index 0000000..fdb58e6
--- /dev/null
+++ b/httemplate/edit/cust_class.html
@@ -0,0 +1,5 @@
+<% include( 'elements/class_Common.html',
+ 'name' => 'Customer Class',
+ 'table' => 'cust_class',
+ )
+%>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
new file mode 100755
index 0000000..dc80847
--- /dev/null
+++ b/httemplate/edit/cust_credit.cgi
@@ -0,0 +1,81 @@
+<% include('/elements/header-popup.html', 'Enter Credit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="credit_popup" ACTION="<% $p1 %>process/cust_credit.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="crednum" VALUE="">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum |h %>">
+<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 %>">
+
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#ffffff"><% time2str($date_format, $_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount |h %>" SIZE=8 MAXLENGTH=8></TD>
+ </TR>
+
+%
+%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
+%
+
+<% include( '/elements/tr-select-reason.html',
+ 'field' => 'reasonnum',
+ 'reason_class' => 'R',
+ 'control_button' => "document.getElementById('confirm_credit_button')",
+ 'cgi' => $cgi,
+ )
+%>
+
+ <TR>
+ <TD ALIGN="right">Additional info</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="addlinfo" VALUE="<% $cgi->param('addlinfo') |h %>">
+ </TD>
+
+ <TR>
+ <TD ALIGN="right">Auto-apply<BR>to invoices</TD>
+ <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>
+ </TR>
+
+% if ( $conf->exists('pkg-balances') ) {
+ <% include('/elements/tr-select-cust_pkg-balances.html',
+ 'custnum' => $custnum,
+ 'cgi' => $cgi
+ )
+ %>
+% } else {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% }
+
+</TABLE>
+
+<BR>
+
+<CENTER><INPUT TYPE="submit" ID="confirm_credit_button" VALUE="Enter credit" DISABLED></CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+<%init>
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+my $custnum = $cgi->param('custnum');
+my $amount = $cgi->param('amount');
+my $_date = time;
+my $otaker = getotaker;
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi
new file mode 100755
index 0000000..e3627ff
--- /dev/null
+++ b/httemplate/edit/cust_credit_bill.cgi
@@ -0,0 +1,14 @@
+<% include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'dst_table' => 'cust_bill',
+ 'dst_thing' => 'invoice',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+</%init>
diff --git a/httemplate/edit/cust_credit_refund.cgi b/httemplate/edit/cust_credit_refund.cgi
new file mode 100755
index 0000000..f5bbb56
--- /dev/null
+++ b/httemplate/edit/cust_credit_refund.cgi
@@ -0,0 +1,14 @@
+<% include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_credit_refund.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'dst_table' => 'cust_refund',
+ 'dst_thing' => 'refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+</%init>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
new file mode 100755
index 0000000..fac7ef2
--- /dev/null
+++ b/httemplate/edit/cust_main.cgi
@@ -0,0 +1,321 @@
+<% include('/elements/header.html',
+ "Customer $action",
+ '',
+ ' onUnload="myclose()"' #hmm, in billing.html
+) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME = "CustomerForm"
+ METHOD = "POST"
+ ACTION = "<% popurl(1) %>process/cust_main.cgi"
+%# STYLE = "margin-bottom: 0"
+%# STYLE="margin-top: 0; margin-bottom: 0">
+>
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+% if ( $custnum ) {
+ Customer #<B><% $cust_main->display_custnum %></B> -
+ <B><FONT COLOR="#<% $cust_main->statuscolor %>">
+ <% ucfirst($cust_main->status) %>
+ </FONT></B>
+ <BR><BR>
+% }
+
+%# agent, agent_custid, refnum (advertising source), referral_custnum
+<% include('cust_main/top_misc.html', $cust_main, 'custnum' => $custnum ) %>
+
+%# birthdate
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+ <BR>
+ <% include('cust_main/birthdate.html', $cust_main) %>
+% }
+
+%# latitude and longitude
+% if ( $conf->exists('cust_main-require_censustract') ) {
+% my ($latitude, $longitude) = $cust_main->service_coordinates;
+% $latitude ||= $conf->config('company_latitude') || '';
+% $longitude ||= $conf->config('company_longitude') || '';
+ <INPUT NAME="latitude" TYPE="hidden" VALUE="<% $latitude |h %>">
+ <INPUT NAME="longitude" TYPE="hidden" VALUE="<% $longitude |h %>">
+% }
+
+%# contact info
+
+% my $same_checked = '';
+% my $ship_disabled = '';
+% my @ship_style = ();
+% unless ( $cust_main->ship_last && $same ne 'Y' ) {
+% $same_checked = 'CHECKED';
+% $ship_disabled = 'DISABLED';
+% push @ship_style, 'background-color:#dddddd';
+% foreach (
+% qw( last first company address1 address2 city county state zip country
+% daytime night fax )
+% ) {
+% $cust_main->set("ship_$_", $cust_main->get($_) );
+% }
+% }
+
+<BR>
+<FONT SIZE="+1"><B>Billing address</B></FONT>
+
+<% include('cust_main/contact.html',
+ 'cust_main' => $cust_main,
+ 'pre' => '',
+ 'onchange' => 'bill_changed(this)',
+ 'disabled' => '',
+ 'ss' => $ss,
+ 'stateid' => $stateid,
+ 'same_checked' => $same_checked, #for address2 "Unit #" labeling
+ )
+%>
+
+<SCRIPT>
+function bill_changed(what) {
+ if ( what.form.same.checked ) {
+% for (qw( last first company address1 address2 city zip daytime night fax )) {
+ what.form.ship_<%$_%>.value = what.form.<%$_%>.value;
+% }
+
+ what.form.ship_country.selectedIndex = what.form.country.selectedIndex;
+
+ function fix_ship_city() {
+ what.form.ship_city_select.selectedIndex = what.form.city_select.selectedIndex;
+ what.form.ship_city.style.display = what.form.city.style.display;
+ what.form.ship_city_select.style.display = what.form.city_select.style.display;
+ }
+
+ function fix_ship_county() {
+ what.form.ship_county.selectedIndex = what.form.county.selectedIndex;
+ ship_county_changed(what.form.ship_county, fix_ship_city );
+ }
+
+ function fix_ship_state() {
+ what.form.ship_state.selectedIndex = what.form.state.selectedIndex;
+ ship_state_changed(what.form.ship_state, fix_ship_county );
+ }
+
+ ship_country_changed(what.form.ship_country, fix_ship_state );
+
+ }
+}
+function samechanged(what) {
+ if ( what.checked ) {
+ bill_changed(what);
+
+% my @fields = qw( last first company address1 address2 city city_select county state zip country daytime night fax );
+% for (@fields) {
+ what.form.ship_<%$_%>.disabled = true;
+ what.form.ship_<%$_%>.style.backgroundColor = '#dddddd';
+% }
+
+% if ( $conf->exists('cust_main-require_address2') ) {
+ document.getElementById('address2_required').style.visibility = '';
+ document.getElementById('address2_label').style.visibility = '';
+ document.getElementById('ship_address2_required').style.visibility = 'hidden';
+ document.getElementById('ship_address2_label').style.visibility = 'hidden';
+% }
+
+ } else {
+
+% for (@fields) {
+ what.form.ship_<%$_%>.disabled = false;
+ what.form.ship_<%$_%>.style.backgroundColor = '#ffffff';
+% }
+
+% if ( $conf->exists('cust_main-require_address2') ) {
+ document.getElementById('address2_required').style.visibility = 'hidden';
+ document.getElementById('address2_label').style.visibility = 'hidden';
+ document.getElementById('ship_address2_required').style.visibility = '';
+ document.getElementById('ship_address2_label').style.visibility = '';
+% }
+
+ }
+}
+</SCRIPT>
+
+<BR>
+<FONT SIZE="+1"><B>Service address</B></FONT>
+
+(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>>same as billing address)
+<% include('cust_main/contact.html',
+ 'cust_main' => $cust_main,
+ 'pre' => 'ship_',
+ 'onchange' => '',
+ 'disabled' => $ship_disabled,
+ 'style' => \@ship_style
+ )
+%>
+
+%# billing info
+<% include( 'cust_main/billing.html', $cust_main,
+ 'payinfo' => $payinfo,
+ 'invoicing_list' => \@invoicing_list,
+ )
+%>
+
+% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
+% if (!$ro_comments || $cust_main->comments) {
+
+ <BR>Comments
+ <% &ntable("#cccccc") %>
+ <TR>
+ <TD>
+ <TEXTAREA NAME = "comments"
+ COLS = 80
+ ROWS = 5
+ WRAP = "HARD"
+ <% $ro_comments %>
+ ><% $cust_main->comments %></TEXTAREA>
+ </TD>
+ </TR>
+ </TABLE>
+
+% }
+
+% unless ( $custnum ) {
+
+ <% include('cust_main/first_pkg.html', $cust_main,
+ 'pkgpart_svcpart' => $pkgpart_svcpart,
+ #svc_acct
+ 'username' => $username,
+ 'password' => $password,
+ 'popnum' => $popnum,
+ 'saved_domsvc' => $saved_domsvc,
+ %svc_phone,
+ )
+ %>
+
+% }
+
+<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>">
+
+%# cust_main/bottomfixup.js
+% foreach my $hidden (
+% 'payauto',
+% 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+% 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+% 'paystart_month', 'paystart_year', 'payissue',
+% 'payip',
+% 'paid',
+% ) {
+ <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
+% }
+
+<% include('cust_main/bottomfixup.html') %>
+
+<BR>
+<INPUT TYPE = "button"
+ NAME = "submitButton"
+ ID = "submitButton"
+ VALUE = "<% $custnum ? "Apply Changes" : "Add Customer" %>"
+ onClick = "this.disabled=true; bottomfixup(this.form);"
+>
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#probably redundant given the checks below...
+die "access denied"
+ unless $curuser->access_right('New customer')
+ || $curuser->access_right('Edit customer');
+
+my $conf = new FS::Conf;
+
+#get record
+
+my($custnum, $cust_main, $ss, $stateid, $payinfo, @invoicing_list);
+my $same = '';
+my $pkgpart_svcpart = ''; #first_pkg
+my($username, $password, $popnum, $saved_domsvc) = ( '', '', 0, 0 ); #svc_acct
+my %svc_phone = ();
+
+if ( $cgi->param('error') ) {
+
+ $cust_main = new FS::cust_main ( {
+ map { $_, scalar($cgi->param($_)) } fields('cust_main')
+ } );
+
+ $custnum = $cust_main->custnum;
+
+ die "access denied"
+ unless $curuser->access_right($custnum ? 'Edit customer' : 'New customer');
+
+ @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
+ $same = $cgi->param('same');
+ $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid');
+ $ss = $cust_main->ss; # don't mask an entered value on errors
+ $stateid = $cust_main->stateid; # don't mask an entered value on errors
+ $payinfo = $cust_main->payinfo; # don't mask an entered value on errors
+
+ $pkgpart_svcpart = $cgi->param('pkgpart_svcpart') || '';
+
+ #svc_acct
+ $username = $cgi->param('username');
+ $password = $cgi->param('_password');
+ $popnum = $cgi->param('popnum');
+ $saved_domsvc = $cgi->param('domsvc') || '';
+ if ( $saved_domsvc =~ /^(\d+)$/ ) {
+ $saved_domsvc = $1;
+ } else {
+ $saved_domsvc = '';
+ }
+
+ #svc_phone
+ $svc_phone{$_} = $cgi->param($_)
+ foreach qw( countrycode phonenum sip_password pin phone_name );
+
+} elsif ( $cgi->keywords ) { #editing
+
+ die "access denied"
+ unless $curuser->access_right('Edit customer');
+
+ 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);
+ }
+ @invoicing_list = $cust_main->invoicing_list;
+ $ss = $cust_main->masked('ss');
+ $stateid = $cust_main->masked('stateid');
+ $payinfo = $cust_main->paymask;
+
+} else { #new customer
+
+ die "access denied"
+ unless $curuser->access_right('New customer');
+
+ $custnum='';
+ $cust_main = new FS::cust_main ( {} );
+ $cust_main->otaker( &getotaker );
+ $cust_main->referral_custnum( $cgi->param('referral_custnum') );
+ @invoicing_list = ();
+ push @invoicing_list, 'POST'
+ unless $conf->exists('disablepostalinvoicedefault');
+ $ss = '';
+ $stateid = '';
+ $payinfo = '';
+
+}
+
+my $error = $cgi->param('error');
+$cgi->delete_all();
+$cgi->param('error', $error);
+
+my $action = $custnum ? 'Edit' : 'Add';
+$action .= ": ". $cust_main->name if $custnum;
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
new file mode 100644
index 0000000..ad83778
--- /dev/null
+++ b/httemplate/edit/cust_main/billing.html
@@ -0,0 +1,492 @@
+%if ( $payby_default eq 'HIDE' ) {
+%
+% $cust_main->payby('BILL') unless $cust_main->payby;
+% my $payby = $cust_main->payby;
+
+ <INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+
+ <INPUT TYPE="hidden" NAME="<%$payby%>_payinfo" VALUE="<% $cust_main->paymask %>">
+
+% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip paytype paystate )) {
+
+ <INPUT TYPE="hidden" NAME="<% $payby.'_'.$field %>" VALUE="<% $cust_main->get($field) %>">
+
+% }
+
+% #false laziness w/elements/select-month_year.html & view/cust_main/billing.html
+% my( $mon, $year );
+% my $date = $cust_main->paydate || '12-2037';
+% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+% } else {
+% die "unrecognized expiration date format: $date";
+% }
+
+ <INPUT TYPE="hidden" NAME="<%$payby%>_exp_month" VALUE="<% $mon %>">
+ <INPUT TYPE="hidden" NAME="<%$payby%>_exp_year" VALUE="<% $year %>">
+
+ <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>">
+
+ <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>">
+
+% } else {
+%
+% my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+ <BR><FONT SIZE="+1"><B>Billing information</B></FONT>
+ <% &ntable("#cccccc") %>
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200"><%$r%>Billing type</TD>
+
+ <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;
+ }
+
+ function card_changed(what) {
+ if (
+ what.form.payinfo.value.substring(0, 4) == '4093'
+ || what.form.payinfo.value.substring(0, 4) == '4911'
+ || what.form.payinfo.value.substring(0, 4) == '4936'
+ || what.form.payinfo.value.substring(0, 6) == '564132'
+ || what.form.payinfo.value.substring(0, 2) == '63'
+ || what.form.payinfo.value.substring(0, 2) == '67'
+ )
+ {
+ what.form.paystart_month.disabled = false;
+ what.form.paystart_year.disabled = false;
+ what.form.payissue.disabled = false;
+ what.form.paystart_month.style.backgroundColor = '#ffffff';
+ what.form.paystart_year.style.backgroundColor = '#ffffff';
+ what.form.payissue.style.backgroundColor = '#ffffff';
+ document.getElementById('paystart_label').style.color = '#000000';
+ document.getElementById('payissue_label').style.color = '#000000';
+ } else {
+ what.form.paystart_month.disabled = true;
+ what.form.paystart_year.disabled = true;
+ what.form.payissue.disabled = true;
+ what.form.paystart_month.style.backgroundColor = '#dddddd';
+ what.form.paystart_year.style.backgroundColor = '#dddddd';
+ what.form.payissue.style.backgroundColor = '#dddddd';
+ document.getElementById('paystart_label').style.color = '#999999';
+ document.getElementById('payissue_label').style.color = '#999999';
+ }
+ return true;
+ }
+
+ </SCRIPT>
+
+ <% include('/elements/init_overlib.html') %>
+
+% my $payby = $cust_main->payby;
+% my $paytype = $cust_main->paytype;
+% my( $account, $aba ) = split('@', $payinfo);
+%
+% my $disabled = 'DISABLED style="background-color: #dddddd"';
+% my $text_disabled = 'style="color: #999999"';
+%
+% if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) {
+% $disabled = 'style="background-color: #ffffff"';
+% $text_disabled = 'style="color: #000000";'
+% }
+%
+% my %payby = (
+%
+% 'CARD' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'CARD_exp',
+% 'selected_date' =>
+% ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ),
+% ).
+%
+% '</TD></TR>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">CVV2&nbsp;!.
+%
+% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
+% qq!</TD>!.
+% '<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'.
+%
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'CARD_paystart',
+% 'disabled' => $disabled,
+% 'empty_option' => 1,
+% 'start_year' => 2000,
+% 'end_year' => (localtime())[5] + 1900,
+% 'selected_date' => (
+% ( $payby =~ /^(CARD|DCRD)$/
+% && cardtype($payinfo) =~ /^(Switch|Solo)$/ )
+% ? $cust_main->paystart_month. '-'.
+% $cust_main->paystart_year
+% : ''
+% )
+% ).
+%
+% qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!.
+% '<INPUT TYPE="text" NAME="CARD_payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+%
+% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="CARD_payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'CHEK' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!.
+% qq!<TD><INPUT TYPE="text" SIZE=12 NAME="CHEK_payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
+% qq!<TD ALIGN="right">Type</TD><TD><SELECT NAME="CHEK_paytype">!.
+% join('', map { qq!<OPTION VALUE="$_" !.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>" } @FS::cust_main::paytypes).
+% qq!</SELECT></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="CHEK_payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !.
+% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
+% qq!</TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="CHEK_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="CHEK_exp_year" VALUE="2037">!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" NAME="CHEK_payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+% ( $conf->exists('show_bankstate') ?
+% qq!<TR><TD ALIGN="right" WIDTH="200">$paystate_label</TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408">!.
+% include('/elements/select-state.html',
+% 'empty' => '(choose)',
+% 'state' => $cust_main->paystate,
+% 'country' => $cust_main->country,
+% 'prefix' => 'CHEK_pay',
+% ). "</TD></TR>"
+% : '<INPUT TYPE="hidden" NAME="CHEK_paystate" VALUE="'.
+% $cust_main->paystate. '">'
+% ).
+%
+%
+% qq!<TR><TD COLSPAN=4 WIDTH="608"><INPUT TYPE="checkbox" NAME="CHEK_payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'LECB' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="LECB_payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="LECB_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="LECB_exp_year" VALUE="2037">!.
+% qq!<INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'BILL' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="BILL_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="BILL_exp_year" VALUE="2037">!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'COMP' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'COMP_exp',
+% 'selected_date' =>
+% ( $payby eq 'COMP' ? $cust_main->paydate : '' ),
+% ).
+%
+% '</TD></TR>'.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'CASH' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CASH_paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'WEST' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="WEST_paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'MCRD' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="MCRD_paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% );
+%
+% #this should use FS::payby
+% my @allopt = qw( CARD CHEK LECB BILL CASH WEST MCRD COMP );
+%
+% my %allopt = map { $_ => FS::payby->shortname($_) } @allopt;
+%
+% if ( $cust_main->custnum ) {
+% #don't offer CASH/WEST/MCRD initial payment types when editing customer
+% delete $allopt{$_} for qw(CASH WEST MCRD);
+% }
+%
+% my @options = grep exists( $allopt{$_} ), @payby;
+%
+% my %payby2option = (
+% ( map { $_ => $_ } @options ),
+% 'DCRD' => 'CARD',
+% 'DCHK' => 'CHEK',
+% );
+
+ <TD WIDTH="408">
+ <% include( '/elements/selectlayers.html',
+ 'field' => 'payby',
+ 'curr_value' => $payby2option{$payby || $payby_default || $payby[0] },
+ 'options' => \@options,
+ 'labels' => \%allopt,
+ 'html_between' => '</TD></TR></TABLE>',
+ 'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
+ )
+ %>
+
+ <% &ntable("#cccccc") %>
+
+ <TR><TD>&nbsp;</TD></TR>
+
+% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
+ </TR>
+
+% foreach my $exempt_group ( @exempt_groups ) {
+% #escape $exempt_group for NAME
+ <TR>
+ <TD WIDTH="608" COLSPAN="2">&nbsp;&nbsp;<INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" VALUE="Y" <% $cust_main->tax_exemption($exempt_group) ? 'CHECKED' : '' %>> Tax Exempt (<% $exempt_group %> taxes)<TD>
+ </TR>
+% }
+
+% unless ( $conf->exists('emailinvoiceonly') ) {
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <%
+
+ ( grep { $_ eq 'POST' } @invoicing_list )
+
+ ? 'CHECKED'
+ : ''
+
+ %>> Postal mail invoice
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <%
+
+ ( grep { $_ eq 'FAX' } @invoicing_list )
+ ? 'CHECKED'
+ : ''
+
+ %>> Fax invoice
+
+ </TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200">
+ <% $conf->exists('cust_main-require_invoicing_list_email') ? $r : '' %>Email address(es)
+ </TD>
+ <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200">Invoice terms </TD>
+ <TD WIDTH="408">
+ <% include('/elements/select-terms.html',
+ 'curr_value' => $cust_main->invoice_terms,
+ )
+ %>
+ </TD>
+ </TR>
+
+% if ( $conf->exists('voip-cust_cdr_spools') ) {
+ <TR>
+ <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>">
+% }
+
+% if ( $conf->exists('voip-cust_cdr_squelch') ) {
+ <TR>
+ <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="squelch_cdr" VALUE="Y" <% $cust_main->squelch_cdr eq "Y" ? 'CHECKED' : '' %>> Omit CDRs from invoices</TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="squelch_cdr" VALUE="<% $cust_main->squelch_cdr %>">
+% }
+
+% if ( $conf->exists('voip-cust_email_csv_cdr') ) {
+ <TR>
+ <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="email_csv_cdr" VALUE="Y" <% $cust_main->email_csv_cdr eq "Y" ? 'CHECKED' : '' %>> Attach CDRs as CSV to emailed invoices</TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="email_csv_cdr" VALUE="<% $cust_main->email_csv_cdr %>">
+% }
+
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+ <TR>
+ <TD ALIGN="right">CDR termination settlement</TD>
+ <TD><INPUT TYPE = "text"
+ NAME = "cdr_termination_percentage"
+ SIZE = 6
+ VALUE = "<% $cust_main->cdr_termination_percentage %>"
+ STYLE = "text-align:right;"
+ ><B>%</B></TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="cdr_termination_percentage" VALUE="<% $cust_main->cdr_termination_percentage %>">
+% }
+
+ </TABLE>
+
+ <% $r %> required fields
+% }
+
+<%once>
+
+my $paystate_label = FS::Msgcat::_gettext('paystate');
+$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+
+</%once>
+<%init>
+
+my( $cust_main, %options ) = @_;
+my @invoicing_list = @{ $options{'invoicing_list'} };
+my $payinfo = $options{'payinfo'};
+my $conf = new FS::Conf;
+my $payby_default = $conf->config('payby-default');
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+
+my $show_term = '';
+if ( $cust_main->custnum ) {
+ #false laziness w/view/cust_main/billing.html
+ my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+ my $term_sth = dbh->prepare($term_sql) or die dbh->errstr;
+ $term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+ $show_term = $term_sth->fetchrow_arrayref->[0];
+}
+
+</%init>
diff --git a/httemplate/edit/cust_main/birthdate.html b/httemplate/edit/cust_main/birthdate.html
new file mode 100644
index 0000000..415aba3
--- /dev/null
+++ b/httemplate/edit/cust_main/birthdate.html
@@ -0,0 +1,15 @@
+<% ntable("#cccccc", 2) %>
+ <% include ('/elements/tr-input-date-field.html',
+ 'birthdate',
+ $cust_main->birthdate,
+ 'Date of Birth',
+ $conf->config('date_format') || "%m/%d/%Y",
+ 1)
+ %>
+</TABLE>
+<%init>
+
+my( $cust_main, %opt ) = @_;
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/cust_main/bottomfixup.html b/httemplate/edit/cust_main/bottomfixup.html
new file mode 100644
index 0000000..1b29c67
--- /dev/null
+++ b/httemplate/edit/cust_main/bottomfixup.html
@@ -0,0 +1,19 @@
+<% include('/elements/init_overlib.html') %>
+
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-cust_main-address_standardize.html',
+ 'subs' => [ 'address_standardize' ],
+ #'method' => 'POST', #could get too long?
+ )
+%>
+
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-cust_main-censustract.html',
+ 'subs' => [ 'censustract' ],
+ #'method' => 'POST', #could get too long?
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+ <% include('bottomfixup.js') %>
+</SCRIPT>
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
new file mode 100644
index 0000000..5d06f3c
--- /dev/null
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -0,0 +1,399 @@
+function bottomfixup(what) {
+
+%# ../cust_main.cgi
+ var layervars = new Array(
+ 'payauto',
+ 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+ 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+ 'paystart_month', 'paystart_year', 'payissue',
+ 'payip',
+ 'paid'
+ );
+
+ var cf = document.CustomerForm;
+ var payby = cf.payby.options[cf.payby.selectedIndex].value;
+ for ( f=0; f < layervars.length; f++ ) {
+ var field = layervars[f];
+ copyelement( cf.elements[payby + '_' + field],
+ cf.elements[field]
+ );
+ }
+
+ //this part does USPS address correction
+
+ // XXX should this be first and should we update the form fields that are
+ // displayed???
+
+ var cf = document.CustomerForm;
+
+ var state_el = cf.elements['state'];
+ var ship_state_el = cf.elements['ship_state'];
+
+ //address_standardize(
+ var cust_main = new Array(
+ 'company', cf.elements['company'].value,
+ 'address1', cf.elements['address1'].value,
+ 'address2', cf.elements['address2'].value,
+ 'city', cf.elements['city'].value,
+ 'state', state_el.options[ state_el.selectedIndex ].value,
+ 'zip', cf.elements['zip'].value,
+
+ 'ship_company', cf.elements['ship_company'].value,
+ 'ship_address1', cf.elements['ship_address1'].value,
+ 'ship_address2', cf.elements['ship_address2'].value,
+ 'ship_city', cf.elements['ship_city'].value,
+ 'ship_state', ship_state_el.options[ ship_state_el.selectedIndex ].value,
+ 'ship_zip', cf.elements['ship_zip'].value
+ );
+
+ address_standardize( cust_main, update_address );
+
+}
+
+var standardize_address;
+
+function update_address(arg) {
+
+ var argsHash = eval('(' + arg + ')');
+
+ var changed = argsHash['address_standardized'];
+ var ship_changed = argsHash['ship_address_standardized'];
+ var error = argsHash['error'];
+ var ship_error = argsHash['ship_error'];
+
+
+ //yay closures
+ standardize_address = function () {
+
+ var cf = document.CustomerForm;
+ var state_el = cf.elements['state'];
+ var ship_state_el = cf.elements['ship_state'];
+
+ if ( changed ) {
+ cf.elements['company'].value = argsHash['new_company'];
+ cf.elements['address1'].value = argsHash['new_address1'];
+ cf.elements['address2'].value = argsHash['new_address2'];
+ cf.elements['city'].value = argsHash['new_city'];
+ setselect(cf.elements['state'], argsHash['new_state']);
+ cf.elements['zip'].value = argsHash['new_zip'];
+ }
+
+ if ( ship_changed ) {
+ cf.elements['ship_company'].value = argsHash['new_ship_company'];
+ cf.elements['ship_address1'].value = argsHash['new_ship_address1'];
+ cf.elements['ship_address2'].value = argsHash['new_ship_address2'];
+ cf.elements['ship_city'].value = argsHash['new_ship_city'];
+ setselect(cf.elements['ship_state'], argsHash['new_ship_state']);
+ cf.elements['ship_zip'].value = argsHash['new_ship_zip'];
+ }
+
+ post_standardization();
+
+ }
+
+
+
+ if ( changed || ship_changed ) {
+
+% if ( $conf->exists('cust_main-auto_standardize_address') ) {
+
+ standardize_address();
+
+% } else {
+
+ // popup a confirmation popup
+
+ var confirm_change =
+ '<CENTER><BR><B>Confirm address standardization</B><BR><BR>' +
+ '<TABLE>';
+
+ if ( changed ) {
+
+ confirm_change = confirm_change +
+ '<TR><TH>Entered billing address</TH>' +
+ '<TH>Standardized billing address</TH></TR>';
+ // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ if ( argsHash['company'] || argsHash['new_company'] ) {
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['company'] +
+ '</TD><TD>' + argsHash['new_company'] + '</TD></TR>';
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['address1'] +
+ '</TD><TD>' + argsHash['new_address1'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['address2'] +
+ '</TD><TD>' + argsHash['new_address2'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['city'] + ', ' + argsHash['state'] + ' ' + argsHash['zip'] +
+ '</TD><TD>' + argsHash['new_city'] + ', ' + argsHash['new_state'] + ' ' + argsHash['new_zip'] + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ }
+
+ if ( ship_changed ) {
+
+ confirm_change = confirm_change +
+ '<TR><TH>Entered service address</TH>' +
+ '<TH>Standardized service address</TH></TR>';
+ // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ if ( argsHash['ship_company'] || argsHash['new_ship_company'] ) {
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['ship_company'] +
+ '</TD><TD>' + argsHash['new_ship_company'] + '</TD></TR>';
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['ship_address1'] +
+ '</TD><TD>' + argsHash['new_ship_address1'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['ship_address2'] +
+ '</TD><TD>' + argsHash['new_ship_address2'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['ship_city'] + ', ' + argsHash['ship_state'] + ' ' + argsHash['ship_zip'] +
+ '</TD><TD>' + argsHash['new_ship_city'] + ', ' + argsHash['new_ship_state'] + ' ' + argsHash['new_ship_zip'] + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ }
+
+ var addresses = 'address';
+ var height = 268;
+ if ( changed && ship_changed ) {
+ addresses = 'addresses';
+ height = 396; // #what
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' +
+ '<BUTTON TYPE="button" onClick="post_standardization();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' +
+ '</TD><TD>' +
+ '<BUTTON TYPE="button" onClick="standardize_address();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' +
+ '</TD></TR>' +
+ '<TR><TD COLSPAN=2 ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
+
+ '</TABLE></CENTER>';
+
+ overlib( confirm_change, CAPTION, 'Confirm address standardization', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, height, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+% }
+
+ } else {
+
+ post_standardization();
+
+ }
+
+
+}
+
+function post_standardization() {
+
+ var cf = document.CustomerForm;
+
+% if ( $conf->exists('enable_taxproducts') ) {
+
+ if ( new String(cf.elements['<% $taxpre %>zip'].value).length < 10 )
+ {
+
+ var country_el = cf.elements['<% $taxpre %>country'];
+ var country = country_el.options[ country_el.selectedIndex ].value;
+ var geocode = cf.elements['geocode'].value;
+
+ if ( country == 'CA' || country == 'US' ) {
+
+ var state_el = cf.elements['<% $taxpre %>state'];
+ var state = state_el.options[ state_el.selectedIndex ].value;
+
+ var url = "cust_main/choose_tax_location.html" +
+ "?data_vendor=cch-zip" +
+ ";city=" + cf.elements['<% $taxpre %>city'].value +
+ ";state=" + state +
+ ";zip=" + cf.elements['<% $taxpre %>zip'].value +
+ ";country=" + country +
+ ";geocode=" + geocode +
+ ";";
+
+ // popup a chooser
+ OLgetAJAX( url, update_geocode, 300 );
+
+ } else {
+
+ cf.elements['geocode'].value = 'DEFAULT';
+ post_geocode();
+
+ }
+
+ } else {
+
+ cf.elements['geocode'].value = '';
+ post_geocode();
+
+ }
+
+% } else {
+
+ post_geocode();
+
+% }
+
+}
+
+function post_geocode() {
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ //alert('fetch census tract data');
+ var cf = document.CustomerForm;
+ var state_el = cf.elements['ship_state'];
+ var census_data = new Array(
+ 'year', <% $conf->config('census_year') || '2009' %>,
+ 'address', cf.elements['ship_address1'].value,
+ 'city', cf.elements['ship_city'].value,
+ 'state', state_el.options[ state_el.selectedIndex ].value,
+ 'zip', cf.elements['ship_zip'].value
+ );
+
+ censustract( census_data, update_censustract );
+
+% }else{
+
+ document.CustomerForm.submit();
+
+% }
+
+}
+
+function update_geocode() {
+
+ //yay closures
+ set_geocode = function (what) {
+
+ var cf = document.CustomerForm;
+
+ //alert(what.options[what.selectedIndex].value);
+ var argsHash = eval('(' + what.options[what.selectedIndex].value + ')');
+ cf.elements['<% $taxpre %>city'].value = argsHash['city'];
+ setselect(cf.elements['<% $taxpre %>state'], argsHash['state']);
+ cf.elements['<% $taxpre %>zip'].value = argsHash['zip'];
+ cf.elements['geocode'].value = argsHash['geocode'];
+ post_geocode();
+
+ }
+
+ // popup a chooser
+
+ overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+}
+
+var set_censustract;
+
+function update_censustract(arg) {
+
+ var argsHash = eval('(' + arg + ')');
+
+ var cf = document.CustomerForm;
+
+ var msacode = argsHash['msacode'];
+ var statecode = argsHash['statecode'];
+ var countycode = argsHash['countycode'];
+ var tractcode = argsHash['tractcode'];
+ var error = argsHash['error'];
+
+ var newcensus =
+ new String(statecode) +
+ new String(countycode) +
+ new String(tractcode).replace(/\s$/, ''); // JSON 1 workaround
+
+ set_censustract = function () {
+
+ cf.elements['censustract'].value = newcensus
+ cf.submit();
+
+ }
+
+ if (error || cf.elements['censustract'].value != newcensus) {
+ // popup an entry dialog
+
+ if (error) { newcensus = error; }
+ newcensus.replace(/.*ndefined.*/, 'Not found');
+
+ var choose_censustract =
+ '<CENTER><BR><B>Confirm censustract</B><BR>' +
+ '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
+ 'census_year=<% $conf->config('census_year') || '2008' %>' +
+ '&latitude=' + cf.elements['latitude'].value +
+ '&longitude=' + cf.elements['longitude'].value +
+ '" target="_blank">Map service module location</A><BR>' +
+ '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
+ 'census_year=<% $conf->config('census_year') || '2008' %>' +
+ '&zip_code=' + cf.elements['ship_zip'].value +
+ '" target="_blank">Map zip code center</A><BR><BR>' +
+ '<TABLE>';
+
+ choose_censustract = choose_censustract +
+ '<TR><TH style="width:50%">Entered census tract</TH>' +
+ '<TH style="width:50%">Calculated census tract</TH></TR>' +
+ '<TR><TD>' + cf.elements['censustract'].value +
+ '</TD><TD>' + newcensus + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ choose_censustract = choose_censustract +
+ '<TR><TD ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="document.CustomerForm.submit();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract </BUTTON>' +
+ '</TD><TD ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="set_censustract();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract </BUTTON>' +
+ '</TD></TR>' +
+ '<TR><TD COLSPAN=2 ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
+
+ '</TABLE></CENTER>';
+
+ overlib( choose_censustract, CAPTION, 'Confirm censustract', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+ } else {
+
+ cf.submit();
+
+ }
+
+}
+
+function copyelement(from, to) {
+ if ( from == undefined ) {
+ to.value = '';
+ } else if ( from.type == 'select-one' ) {
+ to.value = from.options[from.selectedIndex].value;
+ //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
+ } else if ( from.type == 'checkbox' ) {
+ if ( from.checked ) {
+ to.value = from.value;
+ } else {
+ to.value = '';
+ }
+ } else {
+ if ( from.value == undefined ) {
+ to.value = '';
+ } else {
+ to.value = from.value;
+ }
+ }
+ //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
+}
+
+function setselect(el, value) {
+
+ for ( var s = 0; s < el.options.length; s++ ) {
+ if ( el.options[s].value == value ) {
+ el.selectedIndex = s;
+ }
+ }
+
+}
+<%init>
+
+my $conf = new FS::Conf;
+
+my $taxpre = $conf->exists('tax-ship_address') ? 'ship_' : '';
+
+</%init>
diff --git a/httemplate/edit/cust_main/choose_tax_location.html b/httemplate/edit/cust_main/choose_tax_location.html
new file mode 100644
index 0000000..ac475c5
--- /dev/null
+++ b/httemplate/edit/cust_main/choose_tax_location.html
@@ -0,0 +1,87 @@
+<FORM NAME="choosegeocodeform">
+<CENTER><BR><B>Choose tax location</B><BR><BR>
+<P>the geocode is:<% $header %></P>
+<P STYLE="<% $style %>"><% $header %></P>
+
+<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>">
+% foreach my $location (@cust_tax_location) {
+% my %value = ( zip => $zip5,
+% map { $_ => $location->$_ }
+% qw ( city state geocode )
+% );
+% map { $value{$_} = $location{$_} } qw ( city state )
+% if $location{country} eq 'CA';
+%
+% my $value = encode_entities(objToJson({ %value })
+% );
+% my $content = '';
+% $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
+% foreach qw( city county state );
+% $content .= $location->cityflag eq 'I' ? 'Y' : 'N' ;
+% my $selected = '' ;
+% if ($geocode && $location->geocode eq $geocode) {
+% $selected = 'SELECTED';
+% }
+ <OPTION VALUE="<% $value %>" STYLE="<% $style %>" <% $selected %>><% $content %>
+% }
+</SELECT><BR><BR>
+
+<TABLE><TR>
+ <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes'));"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
+ <TD><BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD>
+</TR>
+</TABLE>
+
+</CENTER>
+</FORM>
+<%init>
+
+my $conf = new FS::Conf;
+
+my %location = ();
+
+($location{data_vendor}) = $cgi->param('data_vendor') =~ /^([-\w]+)$/;
+($location{city}) = $cgi->param('city') =~ /^([\w ]+)$/;
+($location{state}) = $cgi->param('state') =~ /^(\w+)$/;
+($location{zip}) = $cgi->param('zip') =~ /^([-\w ]+)$/;
+($location{country}) = $cgi->param('country') =~ /^([\w ]+)$/;
+
+my($geocode) = $cgi->param('geocode') =~ /^([\w]+)$/;
+
+my($zip5, $zip4) = split('-', $location{zip});
+
+#only support US & CA
+my $hashref = { 'data_vendor' => $location{data_vendor} };
+$hashref->{zip} = $location{country} eq 'CA' ? substr($zip5,0,1) : $zip5,
+
+my @keys = keys(%$hashref);
+my @cust_tax_location = ();
+until ( @cust_tax_location ) {
+ @cust_tax_location = qsearch({ table => 'cust_tax_location',
+ hashref => $hashref,
+ order_by => 'LIMIT 50',
+ });
+ last unless scalar(@keys);
+ delete $hashref->{ shift @keys };
+}
+
+my %max = ( city => 4, county => 6, state => 5);
+foreach my $location (@cust_tax_location) {
+ foreach ( qw( city county state ) ) {
+ my $length = length($location->$_);
+ $max{$_} = ($length > $max{$_}) ? $length : $max{$_};
+ }
+}
+foreach ( qw( city county state ) ) {
+ $max{$_} = $location{$_} if $location{$_} > $max{$_};
+ $max{$_}++;
+}
+
+my $header = '&nbsp;&nbsp;';
+$header .= $_. '&nbsp;' x ( $max{lc($_)} - length($_) )
+ foreach qw( City County State );
+$header .= "In city?";
+
+my $style = "font-family:monospace;";
+
+</%init>
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
new file mode 100644
index 0000000..feb61db
--- /dev/null
+++ b/httemplate/edit/cust_main/contact.html
@@ -0,0 +1,150 @@
+<% &ntable("#cccccc") %>
+
+<TR>
+ <TH ALIGN="right"><%$r%>Contact&nbsp;name<BR>(last,&nbsp;first)</TH>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>last" VALUE="<% $cust_main->get($pre.'last') %>" onChange="<% $onchange %>" <%$disabled%> <%$style%>> ,
+ <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') %>" onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ </TD>
+% if ( $conf->exists('show_ss') && !$pre ) {
+
+ <TD ALIGN="right">SS#</TD>
+ <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $opt{ss} %>" SIZE=11></TD>
+% } elsif ( !$pre ) {
+
+ <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $opt{ss} %>"></TD>
+% }
+
+
+</TR>
+
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ </TD>
+</TR>
+
+<% include('/elements/location.html',
+ 'prefix' => $pre,
+ 'object' => $cust_main,
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+ 'style' => \@style,
+ 'same_checked' => $opt{'same_checked'},
+ 'geocode' => $opt{'geocode'},
+ 'censustract' => $opt{'censustract'},
+ )
+%>
+
+<TR>
+ <TD ALIGN="right"><% $daytime_label %></TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><% $night_label %></TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ </TD>
+</TR>
+
+% if ( $conf->exists('show_stateid') && !$pre ) {
+
+<TR>
+ <TD ALIGN="right"><% $stateid_label %></TD>
+ <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $opt{stateid} %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%> <%$style%>></TD>
+ <TD ALIGN="right"><% $stateid_state_label %></TD>
+ <TD><% include('/elements/select-state.html',
+ 'state' => $cust_main->stateid_state,
+ 'country' => $cust_main->country,
+ 'prefix' => 'stateid_',
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+ 'style' => \@style,
+ )
+ %>
+ </TD>
+</TR>
+% } elsif ( !$pre ) {
+
+ <TD><INPUT TYPE="hidden" NAME="stateid" VALUE="<% $opt{stateid} %>"></TD>
+ <TD><INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $cust_main->stateid_state %>"></TD>
+% }
+
+</TABLE>
+<%$r%>required fields<BR>
+
+<%init>
+
+#my( $cust_main, $pre, $onchange, $disabled, %opt ) = @_;
+my %opt = @_;
+my $cust_main = $opt{'cust_main'};
+my $pre = $opt{'pre'};
+my $onchange = $opt{'onchange'};
+my $disabled = $opt{'disabled'};
+my @style = ( $opt{'style'} ? @{ $opt{'style'} } : () );
+
+#push @style, 'background-color: #dddddd' if $disabled;
+my $style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
+
+my $conf = new FS::Conf;
+
+foreach (qw(ss stateid)) {
+ $opt{$_} = $cust_main->masked($_) unless exists $opt{$_};
+}
+
+#false laziness with ship state
+my $countrydefault = $conf->config('countrydefault') || 'US';
+$cust_main->set($pre.'country', $countrydefault )
+ unless $cust_main->get($pre.'country');
+
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+$cust_main->set($pre.'state', $statedefault )
+ unless $cust_main->get($pre.'state')
+ || $cust_main->get($pre.'country') ne $countrydefault;
+
+$cust_main->set('stateid_state', $cust_main->state )
+ unless $pre || $cust_main->get('stateid_state');
+
+$opt{geocode} ||= $cust_main->get('geocode');
+
+if ( $conf->exists('cust_main-require_censustract') ) {
+ $opt{censustract} ||= $cust_main->censustract;
+}
+
+#my($county_html, $state_html, $country_html) =
+# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'),
+# $cust_main->get($pre.'state'),
+# $cust_main->get($pre.'country'),
+# $pre,
+# $onchange,
+# $disabled,
+# );
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day Phone'
+ : FS::Msgcat::_gettext('daytime');
+my $night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/
+ ? 'Night Phone'
+ : FS::Msgcat::_gettext('night') || 'Night Phone';
+my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
+ ? 'Driver&rsquo;s License'
+ : FS::Msgcat::_gettext('stateid') || 'Driver&rsquo;s License';
+my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
+ ? 'Driver&rsquo;s License State'
+ : FS::Msgcat::_gettext('stateid_state') || 'Driver&rsquo;s License State';
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg.html b/httemplate/edit/cust_main/first_pkg.html
new file mode 100644
index 0000000..0de33c0
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg.html
@@ -0,0 +1,55 @@
+% if ( @part_pkg ) {
+
+ <BR><BR>
+ <FONT SIZE="+1"><B>First package</B></FONT>
+ <% ntable("#cccccc") %>
+
+ <TR>
+ <TD COLSPAN=2>
+ <% include('first_pkg/select-part_pkg.html',
+ 'part_pkg' => \@part_pkg,
+ %opt,
+ # map { $_ => $opt{$_} } qw( pkgpart_svcpart saved_domsvc )
+ )
+ %>
+
+% }
+<%init>
+
+my( $cust_main, %opt ) = @_;
+
+# pry the wrong place for this logic. also pretty expensive
+
+#false laziness, copied from FS::cust_pkg::order
+my $pkgpart;
+my $agentnum = '';
+my @agents = $FS::CurrentUser::CurrentUser->agents;
+if ( scalar(@agents) == 1 ) {
+ # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
+ $pkgpart = $agents[0]->pkgpart_hashref;
+ $agentnum = $agents[0]->agentnum;
+} else {
+ #can't know (agent not chosen), so, allow all
+ $agentnum = 'all';
+ my %typenum;
+ foreach my $agent ( @agents ) {
+ next if $typenum{$agent->typenum}++;
+ $pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
+ }
+}
+#eslaf
+
+my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+my @part_pkg =
+ grep { $_->svcpart(\@first_svc)
+ && ( $pkgpart->{ $_->pkgpart }
+ || $agentnum eq 'all'
+ || ( $agentnum ne 'all' && $agentnum && $_->agentnum
+ && $_->agentnum == $agentnum
+ )
+ )
+ }
+ qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/select-part_pkg.html b/httemplate/edit/cust_main/first_pkg/select-part_pkg.html
new file mode 100644
index 0000000..871e1cd
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/select-part_pkg.html
@@ -0,0 +1,170 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $url_prefix.'misc/svc_acct-domains.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_domains' ],
+ )
+%>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $url_prefix.'misc/part_svc-columns.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_part_svc' ],
+ )
+%>
+
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="">
+
+<SCRIPT TYPE="text/javascript">
+
+ function selopt(what,value,text,selected) {
+ var optionName = new Option(text, value, false, selected);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ var pkgpart_svcpart2svcdb = {
+% foreach my $pkgpart ( map $_->pkgpart, @part_pkg ) {
+ "<% $pkgpart_svcpart{$pkgpart} %>":"<% $svcdb{$pkgpart} %>",
+% }
+ '':''
+ };
+
+ function <% $opt{'prefix'} %>pkgpart_svcpart_changed_too(what,selected) {
+
+ <% $opt{'onchange'} %>;
+
+ pkgpart_svcpart = what.options[what.selectedIndex].value;
+
+ var svcdb = pkgpart_svcpart2svcdb[pkgpart_svcpart];
+
+ what.form.svcdb.value = svcdb;
+
+ if ( svcdb == 'svc_acct' ) {
+
+ // go get the new domains
+ function <% $opt{'prefix'} %>update_domains(domains) {
+
+ // blank the current domain list
+ for ( var i = what.form.<% $opt{'prefix'} %>domsvc.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>domsvc.options[i] = null;
+
+ // add the new domains
+ var domainArray = eval('(' + domains + ')' );
+ for ( var s = 0; s < domainArray.length; s=s+2 ) {
+ var domainLabel = domainArray[s+1];
+ if ( domainLabel == "" )
+ domainLabel = '(n/a)';
+ selopt( what.form.<% $opt{'prefix'} %>domsvc,
+ domainArray[s],
+ domainLabel,
+ (domainArray[s] == selected) ? true : false
+ );
+ }
+
+ }
+
+ <% $opt{'prefix'} %>get_domains( pkgpart_svcpart,
+ <% $opt{'prefix'} %>update_domains
+ );
+
+ } else if ( svcdb == 'svc_phone' ) {
+
+ function <% $opt{'prefix'} %>update_svc_phone(part_svc_column) {
+ var colArray = eval('(' + part_svc_column + ')' );
+ for ( var s = 0; s < colArray.length; s=s+3 ) {
+ var name = colArray[s];
+ var flag = colArray[s+1];
+ var value = colArray[s+2];
+ var td_label = document.getElementById(name+'_label_td');
+ var td = document.getElementById(name+'_td');
+ var input = document.getElementById(name);
+ if ( flag == 'D' ) {
+ if ( ! input.value ) { input.value = value; }
+ td_label.style.display = ''
+ td.style.display = ''
+ } else if ( flag == 'F' ) {
+ input.value = value;
+ td_label.style.display = 'none'
+ td.style.display = 'none'
+ } else {
+ td_label.style.display = ''
+ td.style.display = ''
+ }
+ }
+ }
+
+ <% $opt{'prefix'} %>get_part_svc( pkgpart_svcpart,
+ <% $opt{'prefix'} %>update_svc_phone
+ );
+
+ }
+
+ }
+
+</SCRIPT>
+
+<% include( '/elements/selectlayers.html',
+ 'field' => $opt{'prefix'}. 'pkgpart_svcpart',
+ 'curr_value' => $opt{pkgpart_svcpart},
+ 'options' => \@options,
+ 'labels' => \%labels,
+ 'html_between' => '</TD></TR></TABLE>',
+ #'onchange' => $opt{'prefix'}. 'pkgpart_svcpart_changed(this,0);',
+ 'onchange' => $opt{'prefix'}. 'pkgpart_svcpart_changed_too(what,0)',
+
+ 'layer_callback' => $layer_callback,
+ 'layermap' => \%layermap,
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+ pkgpart_svcpart_changed_too( document.CustomerForm.pkgpart_svcpart,
+ <% $opt{saved_domsvc} %>
+ );
+</SCRIPT>
+
+<%init>
+
+my %opt = @_;
+
+foreach my $opt (qw( svc_part pkgparts saved_pkgpart saved_domsvc prefix)) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+$opt{saved_domsvc} = 0 unless $opt{saved_domsvc};
+
+my $url_prefix = $opt{'relurls'} ? '' : $p;
+
+my @part_pkg = @{$opt{'part_pkg'}};
+
+my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+my %pkgpart_svcpart = ();
+my %svcdb = ();
+my %layermap = ();
+foreach my $part_pkg ( @part_pkg ) {
+ my $pkgpart = $part_pkg->pkgpart;
+ my $pkgpart_svcpart = $pkgpart. "_". $part_pkg->svcpart(\@first_svc);
+ $pkgpart_svcpart{$pkgpart} = $pkgpart_svcpart;
+ $svcdb{$pkgpart} = $part_pkg->part_svc(\@first_svc)->svcdb;
+ $layermap{$pkgpart_svcpart} = $svcdb{$pkgpart};
+}
+
+my @options = ( '', map $pkgpart_svcpart{ $_->pkgpart }, @part_pkg );
+my %labels = ( '' => ( $opt{'empty_label'} || '(none)' ),
+ map { $pkgpart_svcpart{ $_->pkgpart } => $_->pkg_comment }
+ @part_pkg
+ );
+
+my $layer_callback = sub {
+ my $layer = shift;
+ #$layer_fields, $layer_values, $layer_prefix
+
+# my( $pkgpart, $svcpart ) = split('_', $layer);
+# my $svcdb = $svcdb{$pkgpart};
+ my $svcdb = $layer;
+
+ return '' unless $svcdb; #'<BR><BR><BR><BR><BR>'
+
+ #full path cause we're being slung around as a coderef (mason closures?)
+ include("/edit/cust_main/first_pkg/$svcdb.html", %opt, );
+};
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/svc_acct.html b/httemplate/edit/cust_main/first_pkg/svc_acct.html
new file mode 100644
index 0000000..150d4c0
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/svc_acct.html
@@ -0,0 +1,88 @@
+<% ntable("#cccccc") %>
+
+ <TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "username"
+ VALUE = "<% $opt{'username'} %>"
+ SIZE = <% $ulen2 %>
+ MAXLENGTH = <% $ulen %>
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+ <SELECT NAME="domsvc">
+ <OPTION>(none)</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Password</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "_password"
+ VALUE = "<% $opt{'password'} %>"
+ SIZE = <% $pmax2 %>
+ MAXLENGTH = <% $passwordmax %>>
+% unless ( $opt{'password_verify'} ) {
+ (blank to generate)
+% }
+ </TD>
+ </TR>
+
+% if ( $opt{'password_verify'} ) {
+ <TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "_password2"
+ VALUE = "<% $opt{'password2'} %>"
+ SIZE = <% $pmax2 %>
+ MAXLENGTH = <% $passwordmax %>>
+ </TD>
+ </TR>
+% }
+
+% if ( $conf->exists('security_phrase') ) {
+ <TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $opt{'sec_phrase'} %>">
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">
+% }
+
+% if ( $conf->exists('svc_acct-disable_access_number') ) {
+ <INPUT TYPE="hidden" NAME="popnum" VALUE="">
+% } else {
+ <TR>
+ <TD ALIGN="right">Access number</TD>
+%# XXX should gain "area code" selection and labels on the dropdowns
+ <TD><% FS::svc_acct_pop::popselector($opt{'popnum'}) %></TD>
+ </TR>
+% }
+
+</TABLE>
+
+<%init>
+
+#use FS::svc_acct_pop;
+
+my( %opt ) = @_;
+
+my $conf = new FS::Conf;
+
+#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;
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/svc_phone.html b/httemplate/edit/cust_main/first_pkg/svc_phone.html
new file mode 100644
index 0000000..70e013e
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/svc_phone.html
@@ -0,0 +1,82 @@
+<% ntable("#cccccc") %>
+
+%#XXX this should be hidden or something in most/all cases
+ <TR>
+ <TD ALIGN="right" ID="countrycode_label_td">Country code</TD>
+ <TD ID="countrycode_td">
+ <INPUT TYPE = "text"
+ NAME = "countrycode"
+ ID = "countrycode"
+ VALUE = "<% $opt{'countrycode'} %>"
+ SIZE = 4
+ MAXLENGTH = 3
+ >
+ </TD>
+ </TR>
+
+%#we don't know the svcpart until the dropdown is changed :/
+%#<% include('/elements/tr-select-did.html',
+%# 'label' => 'Phone number',
+%# 'curr_value' => $opt{'phonenum'},
+%# )
+%#%>
+ <TR>
+ <TD ALIGN="right" ID="phonenum_label_td">Phone Number</TD>
+ <TD ID="phonenum_td">
+ <INPUT TYPE = "text"
+ NAME = "phonenum"
+ ID = "phonenum"
+ VALUE = "<% $opt{'phonenum'} %>"
+ SIZE = 21
+ MAXLENGTH = 20
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" ID="sip_password_label_td">SIP password</TD>
+ <TD ID="sip_password_td">
+ <INPUT TYPE = "text"
+ NAME = "sip_password"
+ ID = "sip_password"
+ VALUE = "<% $opt{'sip_password'} %>"
+ MAXLENGTH = 80
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" ID="pin_label_td">Voicemail PIN</TD>
+ <TD ID="pin_td">
+ <INPUT TYPE = "text"
+ NAME = "pin"
+ ID = "pin"
+ VALUE = "<% $opt{'pin'} %>"
+ SIZE = 5
+ MAXLENGTH = 4
+ >
+ </TD>
+ </TR>
+
+%#XXX this should be hidden or something in most/all cases
+ <TR>
+ <TD ALIGN="right" ID="phone_name_label_td">Name</TD>
+ <TD ID="phone_name_td">
+ <INPUT TYPE = "text"
+ NAME = "phone_name"
+ ID = "phone_name"
+ VALUE = "<% $opt{'phone_name'} %>"
+ MAXLENGTH = 80
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+<%init>
+
+my( %opt ) = @_;
+
+#my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
new file mode 100644
index 0000000..36add37
--- /dev/null
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -0,0 +1,109 @@
+<% &ntable("#cccccc") %>
+
+%# agent
+<% include('/elements/tr-select-agent.html',
+ 'curr_value' => $cust_main->agentnum,
+ 'label' => "<B>${r}Agent</B>",
+ 'empty_label' => 'Select agent',
+ 'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
+ )
+%>
+
+%# agent_custid
+% if ( $conf->exists('cust_main-edit_agent_custid') ) {
+
+ <TR>
+ <TD ALIGN="right">Customer identifier</TD>
+ <TD><INPUT TYPE="text" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>"></TD>
+ </TR>
+
+% } else {
+
+ <INPUT TYPE="hidden" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>">
+
+% }
+
+%# class
+<% include('/elements/tr-select-cust_class.html',
+ 'curr_value' => $cust_main->classnum,
+ 'label' => "Class",
+ #'empty_label' => '(none)',
+ #'disable_empty' =>
+ )
+%>
+
+%# referral (advertising source)
+%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0;
+%if ( $custnum && ! $conf->exists('editreferrals') ) {
+
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>">
+
+% } else {
+
+ <% include('/elements/tr-select-part_referral.html',
+ 'curr_value' => $refnum
+ )
+ %>
+% }
+
+
+%# referring customer
+%my $referring_cust_main = '';
+%if ( $cust_main->referral_custnum
+% and $referring_cust_main =
+% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+% and ! $curuser->access_right('Edit referring customer')
+%) {
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A>
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>">
+
+% } elsif ( ! $conf->exists('disable_customer_referrals') ) {
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> -->
+ <% include('/elements/search-cust_main.html',
+ 'field_name' => 'referral_custnum',
+ 'curr_value' => $cust_main->referral_custnum,
+ )
+ %>
+ </TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">
+% }
+
+%# signup date
+% if ( $conf->exists('cust_main-edit_signupdate') ) {
+ <% include('/elements/tr-input-date-field.html', {
+ 'name' => 'signupdate',
+ 'value' => $cust_main->signupdate,
+ 'label' => 'Signup date',
+ 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ),
+ })
+ %>
+% }
+
+</TABLE>
+
+<%init>
+
+my( $cust_main, %opt ) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main_attach.cgi b/httemplate/edit/cust_main_attach.cgi
new file mode 100755
index 0000000..ebbaf3c
--- /dev/null
+++ b/httemplate/edit/cust_main_attach.cgi
@@ -0,0 +1,70 @@
+<% include('/elements/header-popup.html', "$action File Attachment") %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="attach_edit" ACTION="<% popurl(1) %>process/cust_main_attach.cgi" METHOD=POST ENCTYPE="multipart/form-data">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="attachnum" VALUE="<% $attachnum %>">
+
+<BR><BR>
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+% if(defined $attach) {
+% if($curuser->access_right("Download attachment")) {
+<A HREF="<% $p.'view/attachment.html?'.$attachnum %>">Download this file</A><BR>
+% }
+<TR><TD> Filename </TD>
+<TD><INPUT TYPE="text" NAME="filename" SIZE=40 MAXLENGTH=255 VALUE="<% $attach->filename %>"<% $disabled %>></TD></TR>
+<TR><TD> Description </TD>
+<TD><INPUT TYPE="text" NAME="title" SIZE=40 MAXLENGTH=80 VALUE="<% $attach->title %>"<% $disabled %></TD></TR>
+<TR><TD> MIME type </TD>
+<TD><INPUT TYPE="text" NAME="mime_type" VALUE="<% $attach->mime_type %>"<% $disabled %></TD></TR>
+<TR><TD> Size </TD><TD><% $attach->size %></TD></TR>
+% }
+% else { # !defined $attach
+<TR><TD> Filename </TD><TD><INPUT TYPE="file" SIZE=24 NAME="file"></TD></TR>
+<TR><TD> Description </TD><TD><INPUT TYPE="text" NAME="title" SIZE=40 MAXLENGTH=80></TD></TR>
+% }
+</TABLE>
+<BR>
+% if(! $disabled) {
+<INPUT TYPE="submit" NAME="submit"
+ VALUE="<% $attachnum ? "Apply Changes" : "Upload File" %>">
+% }
+% if(defined $attach and $curuser->access_right('Delete attachment')) {
+<BR>
+<INPUT TYPE="submit" NAME="delete" value="Delete File"
+onclick="return(confirm('Delete this file?'));">
+% }
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $attachnum = '';
+my $attach;
+if ( $cgi->param('error') ) {
+ #$comment = $cgi->param('comment');
+} elsif ( $cgi->param('attachnum') =~ /^(\d+)$/ ) {
+ $attachnum = $1;
+ die "illegal query ". $cgi->keywords unless $attachnum;
+ $attach = qsearchs('cust_attachment', { 'attachnum' => $attachnum });
+ die "no such attachment: ". $attachnum unless $attach;
+}
+
+my $action = $attachnum ? 'Edit' : 'Add';
+
+my $disabled='';
+if(! $curuser->access_right("$action attachment")) {
+ $disabled = ' disabled="disabled"';
+}
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die "illegal custnum";
+my $custnum = $1;
+
+</%init>
+
diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi
new file mode 100755
index 0000000..265dd1d
--- /dev/null
+++ b/httemplate/edit/cust_main_county-expand.cgi
@@ -0,0 +1,52 @@
+<% include('/elements/header-popup.html', "Enter $title") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_main_county-expand.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="taxnum" VALUE="<% $taxnum %>">
+
+<TEXTAREA NAME="expansion" COLS="50" ROWS="16"><% $expansion |h %></TEXTAREA>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Add <% $title %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my($taxnum, $expansion);
+my($query) = $cgi->keywords;
+if ( $cgi->param('error') ) {
+ $taxnum = $cgi->param('taxnum');
+ $expansion = $cgi->param('expansion');
+} else {
+ $query =~ /^(\d+)$/
+ or die "Illegal taxnum (query $query)";
+ $taxnum = $1;
+ $expansion = '';
+}
+
+my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ or die "cust_main_county.taxnum $taxnum not found";
+
+my $title;
+
+die "Can't expand entry!" if $cust_main_county->city;
+
+if ( $cust_main_county->county ) {
+ $title = 'Cities';
+} elsif ( $cust_main_county->state ) {
+ $title = 'Counties';
+} else {
+ $title = 'States/Provinces';
+}
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/cust_main_county.html b/httemplate/edit/cust_main_county.html
new file mode 100644
index 0000000..11b8e43
--- /dev/null
+++ b/httemplate/edit/cust_main_county.html
@@ -0,0 +1,64 @@
+<% include('elements/edit.html',
+ 'popup' => 1,
+ 'name' => 'Tax rate', #Edit tax rate
+ 'table' => 'cust_main_county',
+ 'labels' => { 'taxnum' => 'Tax',
+ 'country' => 'Country',
+ 'state' => 'State',
+ 'county' => 'County',
+ 'city' => 'City',
+ 'taxclass' => 'Tax class',
+ 'taxname' => 'Tax name',
+ 'tax' => 'Tax rate',
+ 'setuptax' => 'This tax not applicable to setup fees',
+ 'recurtax' => 'This tax not applicable to recurring fees',
+ 'exempt_amount' => 'Monthly exemption per customer ($25 "Texas tax")',
+ },
+ 'fields' => \@fields,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $taxnum;
+if ( $cgi->param('error') ) {
+ $cgi->param('taxnum') =~ /^(\d+)$/
+ or die "no taxnum, but error: ". $cgi->param('error');
+ $taxnum = $1;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die 'no taxnum';
+ $taxnum = $1;
+}
+
+my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum })
+ or die "unknown taxnum $1";
+
+my @fields = (
+ { field=>'country', type=>'fixed-country', },
+ { field=>'state', type=>'fixed-state', },
+ { field=>'county', type=>'fixed', },
+ { field=>'city', type=>'fixed', },
+);
+
+push @fields, { field=>'taxclass', type=>'fixed', }
+ if $conf->exists('enable_taxclasses');
+
+push @fields,
+ 'taxname',
+ { field=>'tax', type=>'percentage', },
+
+ { type=>'tablebreak-tr-title', value=>'Exemptions' },
+ { field=>'setuptax', type=>'checkbox', value=>'Y', },
+ { field=>'recurtax', type=>'checkbox', value=>'Y', },
+ { field=>'exempt_amount', type=>'money', },
+;
+
+</%init>
diff --git a/httemplate/edit/cust_main_note.cgi b/httemplate/edit/cust_main_note.cgi
new file mode 100755
index 0000000..439c844
--- /dev/null
+++ b/httemplate/edit/cust_main_note.cgi
@@ -0,0 +1,51 @@
+<% include('/elements/header-popup.html', "$action Customer Note") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/cust_main_note.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="notenum" VALUE="<% $notenum %>">
+
+% if( $FS::CurrentUser::CurrentUser->option('disable_html_editor') ) {
+ <TEXTAREA NAME="comment_plain" ROWS="12" COLS="60"><%
+ join '', split /<br \/>|&nbsp;/, $comment
+ %></TEXTAREA>
+% }
+% else {
+<% include('/elements/htmlarea.html', 'field' => 'comment_html',
+ 'curr_value' => $comment) %>
+% }
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $notenum ? "Apply Changes" : "Add Note" %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $comment;
+my $notenum = '';
+if ( $cgi->param('error') ) {
+ $comment = $cgi->param('comment');
+} elsif ( $cgi->param('notenum') =~ /^(\d+)$/ ) {
+ $notenum = $1;
+ die "illegal query ". $cgi->keywords unless $notenum;
+ my $note = qsearchs('cust_main_note', { 'notenum' => $notenum });
+ die "no such note: ". $notenum unless $note;
+ $comment = $note->comments;
+}
+
+$comment =~ s/\r//g; # remove weird line breaks to protect FCKeditor
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die "illeagl custnum";
+my $custnum = $1;
+
+my $action = $notenum ? 'Edit' : 'Add';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right("$action customer note");
+
+</%init>
+
diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi
new file mode 100755
index 0000000..cc4ec60
--- /dev/null
+++ b/httemplate/edit/cust_pay.cgi
@@ -0,0 +1,149 @@
+% if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+% } else {
+ <% include("/elements/header.html", $title, '') %>
+% }
+
+<% include('/elements/init_calendar.html') %>
+
+<% include('/elements/error.html') %>
+
+% unless ( $link eq 'popup' ) {
+ <% small_custview($custnum, $conf->config('countrydefault')) %>
+% }
+
+<FORM NAME="PaymentForm" ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST onSubmit="document.PaymentForm.submit.disabled=true">
+<INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>">
+<INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>">
+<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>">
+
+<BR><BR>
+
+Payment
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD COLSPAN=2>
+ <INPUT TYPE="text" NAME="_date" ID="_date_text" VALUE="<% time2str($date_format.' %r',$_date) %>">
+ <IMG SRC="../images/calendar.png" ID="_date_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "_date_text",
+ ifFormat: "<% $date_format %>",
+ button: "_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#ffffff" ALIGN="right"><% $money_char %></TD>
+ <TD><INPUT TYPE="text" NAME="paid" VALUE="<% $paid %>" SIZE=8 MAXLENGTH=8> by <B><% FS::payby->payname($payby) %></B></TD>
+</TR>
+
+% if ( $payby eq 'BILL' ) {
+ <TR>
+ <TD ALIGN="right">Check #</TD>
+ <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD>
+ </TR>
+% }
+
+<TR>
+% if ( $link eq 'custnum' || $link eq 'popup' ) {
+
+ <TD ALIGN="right">Auto-apply<BR>to invoices</TD>
+ <TD COLSPAN=2>
+ <SELECT NAME="apply">
+ <OPTION VALUE="yes" SELECTED>yes
+ <OPTION>no</SELECT>
+ </TD>
+
+% } elsif ( $link eq 'invnum' ) {
+
+ <TD ALIGN="right">Apply to</TD>
+ <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><% $linknum %></B> only</TD>
+ <INPUT TYPE="hidden" NAME="apply" VALUE="no">
+
+% }
+</TR>
+
+% if ( $conf->exists('pkg-balances') ) {
+ <% include('/elements/tr-select-cust_pkg-balances.html',
+ 'custnum' => $custnum,
+ 'cgi' => $cgi
+ )
+ %>
+% } else {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% }
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Post payment">
+
+</FORM>
+
+% if ( $link eq 'popup' ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment');
+
+my($link, $linknum, $paid, $payby, $payinfo, $_date);
+if ( $cgi->param('error') ) {
+ $link = $cgi->param('link');
+ $linknum = $cgi->param('linknum');
+ $paid = $cgi->param('paid');
+ $payby = $cgi->param('payby');
+ $payinfo = $cgi->param('payinfo');
+ $_date = $cgi->param('_date') ? parse_datetime($cgi->param('_date')) : time;
+} elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $link = $cgi->param('popup') ? 'popup' : 'custnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = $cgi->param('payby') || 'BILL';
+ $payinfo = '';
+ $_date = time;
+} elsif ( $cgi->param('invnum') =~ /^(\d+)$/ ) {
+ $link = 'invnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = $cgi->param('payby') || 'BILL';
+ $payinfo = "";
+ $_date = time;
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+my $paybatch = "webui-$_date-$$-". rand() * 2**32;
+
+my $title = 'Post '. FS::payby->payname($payby). ' payment';
+$title .= " against Invoice #$linknum" if $link eq 'invnum';
+
+my $custnum;
+if ( $link eq 'invnum' ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } )
+ or die "unknown invnum $linknum";
+ $custnum = $cust_bill->custnum;
+} elsif ( $link eq 'custnum' || $link eq 'popup' ) {
+ $custnum = $linknum;
+}
+
+</%init>
diff --git a/httemplate/edit/cust_pay_pending.html b/httemplate/edit/cust_pay_pending.html
new file mode 100644
index 0000000..0916a1c
--- /dev/null
+++ b/httemplate/edit/cust_pay_pending.html
@@ -0,0 +1,154 @@
+<% include('/elements/header-popup.html', $title ) %>
+
+% if ( $action eq 'delete' ) {
+
+ <CENTER><FONT SIZE="+1"><B>Are you sure you want to delete this pending payment?</B></FONT></CENTER>
+
+% } elsif ( $action eq 'complete' ) {
+
+ <CENTER><FONT SIZE="+1"><B>No response was received from <% $cust_pay_pending->processor || 'the payment gateway' %> for this transaction. Check <% $cust_pay_pending->processor || 'the payment gateway' %>'s reporting and determine if this transaction completed successfully.</B></FONT></CENTER>
+
+% }
+
+<BR>
+
+%#false laziness w/view/cust_pay.html
+<% include('/elements/small_custview.html',
+ $cust_pay_pending->custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #no balance
+ )
+%>
+<BR>
+
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Pending payment#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay_pending->paypendingnum %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay_pending->_date %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_pay_pending->paid %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Payment method</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay_pending->payby_name %> #<% $cust_pay_pending->paymask %></B></TD>
+</TR>
+
+% #if ( $cust_pay_pending->payby =~ /^(CARD|CHEK|LECB)$/ && $cust_pay_pending->paybatch ) {
+
+ <TR>
+ <TD ALIGN="right">Processor</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay_pending->processor %></B></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Authorization#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay_pending->authorization %></B></TD>
+ </TR>
+
+% if ( $cust_pay_pending->order_number ) {
+ <TR>
+ <TD ALIGN="right">Order#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay_pending->order_number %></B></TD>
+ </TR>
+% }
+
+% #}
+
+</TABLE>
+
+<BR>
+
+<FORM NAME = "pendingform"
+ METHOD = "POST"
+ ACTION = "process/cust_pay_pending.html"
+>
+
+<INPUT TYPE="hidden" NAME="paypendingnum" VALUE="<% $paypendingnum %>">
+
+<% itable() %>
+
+% if ( $action eq 'delete' ) {
+
+ <INPUT TYPE="hidden" NAME="action" VALUE="<% $action %>">
+
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" onClick="document.pendingform.submit();"><!--IMG SRC="<%$p%>images/tick.png" ALT=""-->Yes, delete payment</BUTTON>
+ </TD>
+ <TD>&nbsp;&nbsp;&nbsp;</TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" onClick="parent.cClick();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, cancel deletion</BUTTON>
+ </TD>
+ </TR>
+
+% } elsif ( $action eq 'complete' ) {
+
+ <INPUT TYPE="hidden" NAME="action" VALUE="">
+
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'insert_cust_pay'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/tick.png" ALT=""-->Yes, transaction completed sucessfully.</BUTTON>
+ </TD>
+ <TD>&nbsp;&nbsp;&nbsp;</TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'decline'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, transaction was declined</BUTTON>
+ </TD>
+ <TD>&nbsp;&nbsp;&nbsp;</TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'delete'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, transaction was not received</BUTTON>
+ </TD>
+ </TR>
+
+ <TR><TD COLSPAN=5></TD></TR>
+
+ <TR>
+ <TD COLSPAN=5 ALIGN="center">
+ <BUTTON TYPE="button" onClick="parent.cClick();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->Cancel payment completion; transaction status not yet known</BUTTON>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+
+</FORM>
+</BODY>
+</HTML>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer pending payments');
+
+$cgi->param('action') =~ /^(\w+)$/ or die 'illegal action';
+my $action = $1;
+my $title = ucfirst($action). ' pending payment';
+
+$cgi->param('paypendingnum') =~ /^(\d+)$/ or die 'illegal paypendingnum';
+my $paypendingnum = $1;
+my $cust_pay_pending =
+ qsearchs({
+ 'select' => 'cust_pay_pending.*',
+ 'table' => 'cust_pay_pending',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'paypendingnum' => $paypendingnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ })
+ or die 'unknown paypendingnum';
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/edit/cust_pay_refund.cgi b/httemplate/edit/cust_pay_refund.cgi
new file mode 100755
index 0000000..f82fe36
--- /dev/null
+++ b/httemplate/edit/cust_pay_refund.cgi
@@ -0,0 +1,14 @@
+<% include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_pay_refund.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'dst_table' => 'cust_refund',
+ 'dst_thing' => 'refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
new file mode 100755
index 0000000..dd1ed33
--- /dev/null
+++ b/httemplate/edit/cust_pkg.cgi
@@ -0,0 +1,150 @@
+<% include('/elements/header.html', "Add/Edit Packages", '') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_pkg.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="action" VALUE="bulk">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+%#current packages
+%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
+%if (@cust_pkg) {
+
+ Current packages - select to remove (services are moved to a new package below)
+ <TABLE>
+ <TR STYLE="background-color: #cccccc;">
+ <TH COLSPAN="2">Pkg #</TH>
+ <TH>Package description</TH>
+ </TR>
+ <BR><BR>
+%
+%
+% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') }
+% cmp $all_pkg{ $b->getfield('pkgpart') }
+% }
+% @cust_pkg
+% )
+% {
+% my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') );
+% my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : '';
+%
+%
+
+
+ <TR>
+ <TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="<% $pkgnum %>"<% $checked %>></TD>
+ <TD ALIGN="right"><% $pkgnum %>:</TD>
+ <TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD>
+ </TR>
+% }
+
+
+ </TABLE>
+ <BR><BR>
+% }
+
+
+Order new packages
+<BR><BR>
+%
+%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
+%my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum });
+%
+%my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) }
+% qsearch('type_pkgs',{'typenum'=> $agent->typenum });
+%
+%my $count = 0;
+%my $pkgparts = 0;
+%
+
+
+<TABLE>
+ <TR STYLE="background-color: #cccccc;">
+ <TH>Qty.</TH>
+ <TH COLSPAN="2">Package Description</TH>
+ </TR>
+%
+%#foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+%foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} }
+% keys(%agent_pkgs) ) {
+% $pkgparts++;
+% next unless exists $pkg{$pkgpart}; #skip disabled ones
+% #print qq!<TR>! if ( $count == 0 );
+% my $value = $cgi->param("pkg$pkgpart") || 0;
+%
+
+
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="<% "pkg$pkgpart" %>" VALUE="<% $value %>" SIZE="2" MAXLENGTH="2">
+ </TD>
+ <TD ALIGN="right"><% $pkgpart %>:</TD>
+ <TD><% $pkg{$pkgpart} %> - <% $comment{$pkgpart}%></TD>
+ </TR>
+%
+% $count ++ ;
+% #if ( $count == 2 ) {
+% # print qq!</TR>\n! ;
+% # $count = 0;
+% #}
+%}
+%
+
+
+</TABLE>
+% unless ( $pkgparts ) {
+% my $p2 = popurl(2);
+% my $typenum = $agent->typenum;
+% my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } );
+% my $atype = $agent_type->atype;
+%
+
+
+ (No <A HREF="<% $p2 %>browse/part_pkg.cgi">package definitions</A>,
+ or agent type
+ <A HREF="<% $p2 %>edit/agent_type.cgi?<% $typenum %>"><% $atype %></a>
+ is not allowed to purchase any packages.)
+% }
+
+
+<P><INPUT TYPE="submit" VALUE="Order">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+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') } = $_->custom_comment;
+ next if $_->disabled;
+ $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+ $comment{ $_ -> getfield('pkgpart') } = $_->custom_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);
+
+</%init>
diff --git a/httemplate/edit/cust_pkg_detail.html b/httemplate/edit/cust_pkg_detail.html
new file mode 100644
index 0000000..009ed5c
--- /dev/null
+++ b/httemplate/edit/cust_pkg_detail.html
@@ -0,0 +1,142 @@
+<% include("/elements/header-popup.html", $title, '',
+ ( $cgi->param('error') ? '' : 'onload="addRow()"' ),
+ )
+%>
+
+%# <% include('/elements/error.html') %>
+
+<FORM ACTION="process/cust_pkg_detail.html" NAME="DetailForm" ID="DetailForm" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="detailtype" VALUE="<% $detailtype %>">
+
+<TABLE ID="DetailTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=1 STYLE="background-color: #cccccc">
+
+% if ( $curuser->option('show_pkgnum') ) {
+
+ <TR>
+ <TD ALIGN="right">Package #</TD>
+ <TD BGCOLOR="#ffffff"><% $pkgnum %></TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TD ALIGN="right">Package</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->pkg %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Comment</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Status</TD>
+ <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_pkg->statuscolor %>"><B><% ucfirst($cust_pkg->status) %></B></FONT></TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2><% ucfirst($name{$detailtype}) %>: </TD>
+ </TR>
+
+% my $row = 0;
+% for ( @details ) {
+
+ <TR>
+ <TD></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="detail<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $_->detail |h %>" rownum="<% $row++ %>" onkeyup = "possiblyAddRow;" >
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" ID="submit" NAME="submit" VALUE="<% $title %>">
+
+</FORM>
+
+<SCRIPT TYPE="text/javascript">
+
+ var rownum = <% $row %>;
+
+ function possiblyAddRow() {
+ if ( ( rownum - this.getAttribute('rownum') ) == 1 ) {
+ addRow();
+ }
+ }
+
+ function addRow() {
+
+ var table = document.getElementById('DetailTable');
+ var tablebody = table.getElementsByTagName('tbody').item(0);
+
+ var row = document.createElement('TR');
+
+ var empty_cell = document.createElement('TD');
+ row.appendChild(empty_cell);
+
+ var detail_cell = document.createElement('TD');
+
+ var detail_input = document.createElement('INPUT');
+ detail_input.setAttribute('name', 'detail'+rownum);
+ detail_input.setAttribute('id', 'detail'+rownum);
+ detail_input.setAttribute('size', 60);
+ detail_input.setAttribute('maxLength', 65);
+ detail_input.setAttribute('rownum', rownum);
+ detail_input.onkeyup = possiblyAddRow;
+ detail_cell.appendChild(detail_input);
+
+ row.appendChild(detail_cell);
+
+ tablebody.appendChild(row);
+
+ rownum++;
+
+ }
+
+</SCRIPT>
+
+</BODY>
+</HTML>
+<%init>
+
+my %access_right = (
+ 'I' => 'Edit customer package invoice details',
+ 'C' => 'Edit customer package comments',
+);
+
+my %name = (
+ 'I' => 'invoice details',
+ 'C' => 'package comments',
+);
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+$cgi->param('detailtype') =~ /^(\w)$/ or die 'illegal detailtype';
+my $detailtype = $1;
+
+my $right = $access_right{$detailtype};
+die "access denied"
+ unless $curuser->access_right($right);
+
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'illegal pkgnum';
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+my @details = $cust_pkg->cust_pkg_detail($detailtype);
+
+my $title = ( scalar(@details) ? 'Edit ' : 'Add ' ). $name{$detailtype};
+
+</%init>
diff --git a/httemplate/edit/cust_pkg_discount.html b/httemplate/edit/cust_pkg_discount.html
new file mode 100755
index 0000000..0bb84b8
--- /dev/null
+++ b/httemplate/edit/cust_pkg_discount.html
@@ -0,0 +1,76 @@
+<% include('/elements/header-popup.html', "Discount Package") %>
+
+<SCRIPT TYPE="text/javascript">
+
+ function enable_discount_pkg () {
+ if ( document.DiscountPkgForm.discountnum.selectedIndex > 0 ) {
+ document.DiscountPkgForm.submit.disabled = false;
+ } else {
+ document.DiscountPkgForm.submit.disabled = true;
+ }
+ }
+
+</SCRIPT>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="DiscountPkgForm" ACTION="<% $p %>edit/process/cust_pkg_discount.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right">Current package&nbsp;</TH>
+ <TD COLSPAN=7>
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+<% include('/elements/tr-select-discount.html',
+ 'empty_label' => ( $pkgdiscountnum ? '' : 'Select discount' ),
+ 'onchange' => 'enable_discount_pkg()',
+ 'cgi' => $cgi,
+ )
+%>
+
+</TABLE>
+
+<BR>
+<INPUT NAME="submit" TYPE="submit" VALUE="Discount package" <% $pkgdiscountnum ? '' : 'DISABLED' %>>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#some false laziness w/misc/change_pkg.cgi
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Discount customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $pkgdiscountnum = '';
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+#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 $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
new file mode 100755
index 0000000..59417b4
--- /dev/null
+++ b/httemplate/edit/cust_refund.cgi
@@ -0,0 +1,167 @@
+% if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+% } else {
+ <% include("/elements/header.html", $title, '') %>
+% }
+
+<% include('/elements/error.html') %>
+
+% unless ( $link eq 'popup' ) {
+ <% small_custview($custnum, $conf->config('countrydefault')) %>
+% }
+
+<FORM NAME="RefundForm" ACTION="<% $p1 %>process/cust_refund.cgi" METHOD=POST onSubmit="document.RefundForm.submit.disabled=true">
+<INPUT TYPE="hidden" NAME="popup" VALUE="<% $link %>">
+<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="paybatch" VALUE="">
+<INPUT TYPE="hidden" NAME="credited" VALUE="">
+
+<BR>
+
+% if ( $cust_pay ) {
+%
+% #false laziness w/FS/FS/cust_pay.pm
+% my $payby = FS::payby->payname($cust_pay->payby);
+% my $paymask = $cust_pay->paymask;
+% my $paydate = $cust_pay->paydate;
+% if ( $cgi->param('error') ) {
+% $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01';
+% $paydate = '' unless ($paydate =~ /^\d{2,4}-\d{1,2}-01$'/);
+% }
+
+ <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($date_format, $cust_pay->_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff"><% $payby %> # <% $paymask %></TD>
+ </TR>
+
+% unless ( $paydate || $cust_pay->payby ne 'CARD' ) { # possibly other reasons: i.e. card has since expired
+ <TR>
+ <TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">
+ <% include( '/elements/select-month_year.html',
+ 'prefix' => 'exp',
+ 'selected_date' => $paydate,
+ 'empty_option' => !$paydate,
+ ) %>
+ </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 );
+%
+
+
+ <TR>
+ <TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff"><% $processor %></TD>
+ </TR>
+% if ( length($auth) ) {
+
+ <TR>
+ <TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff"><% $auth %></TD>
+ </TR>
+% }
+% if ( length($order_number) ) {
+
+ <TR>
+ <TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff"><% $order_number %></TD>
+ </TR>
+% }
+% }
+
+ </TABLE>
+% }
+
+
+<BR>Refund
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#ffffff"><% time2str($date_format, $_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="refund" VALUE="<% $refund %>" SIZE=8 MAXLENGTH=8> by <B><% FS::payby->payname($payby) %></B></TD>
+ </TR>
+
+% if ( $payby eq 'BILL' ) {
+ <TR>
+ <TD ALIGN="right">Check #</TD>
+ <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="payinfo" VALUE="">
+% }
+
+ <TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="<% $reason %>"></TD>
+ </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Post refund">
+
+</FORM>
+
+% if ( $link eq 'popup' ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Refund payment');
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $custnum = $cgi->param('custnum');
+my $refund = $cgi->param('refund');
+my $payby = $cgi->param('payby');
+my $payinfo = $cgi->param('payinfo');
+my $reason = $cgi->param('reason');
+my $link = $cgi->param('popup') ? 'popup' : '';
+
+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);
+
+my $title = 'Refund '. FS::payby->payname($payby). ' payment';
+
+</%init>
diff --git a/httemplate/edit/cust_tax_adjustment.html b/httemplate/edit/cust_tax_adjustment.html
new file mode 100644
index 0000000..9d4afbc
--- /dev/null
+++ b/httemplate/edit/cust_tax_adjustment.html
@@ -0,0 +1,102 @@
+<% include('/elements/header-popup.html', 'Tax adjustment' ) %>
+
+<% include('/elements/error.html') %>
+
+<SCRIPT TYPE="text/javascript">
+
+function enable_tax_adjustment () {
+ if ( document.TaxAdjustmentForm.amount.value
+ && document.TaxAdjustmentForm.taxname.selectedIndex > 0 ) {
+ document.TaxAdjustmentForm.submit.disabled = false;
+ } else {
+ document.TaxAdjustmentForm.submit.disabled = true;
+ }
+}
+
+function validate_tax_adjustment () {
+ var comment = document.TaxAdjustmentForm.comment.value;
+ var comment_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ;
+ var amount = document.TaxAdjustmentForm.amount.value;
+ var amount_regex = /^\s*\$?\s*(\d*(\.?\d{1,2}))\s*$/ ;
+ var rval = true;
+
+ if ( ! amount_regex.test(amount) ) {
+ alert('Illegal amount - enter the amount of the tax adjustment, for example, "5" or "43" or "21.46".');
+ return false;
+ }
+ if ( ! comment_regex.test(comment) ) {
+ alert('Illegal comment - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' );
+ return false;
+ }
+
+ return true;
+}
+
+</SCRIPT>
+
+<FORM ACTION="process/cust_tax_adjustment.html" NAME="TaxAdjustmentForm" ID="TaxAdjustmentForm" METHOD="POST" onsubmit="document.TaxAdjustmentForm.submit.disabled=true;return validate_tax_adjustment();">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+<TABLE ID="TaxAdjustmentTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc">
+
+<TR>
+ <TD ALIGN="right">Tax </TD>
+ <TD>
+ <SELECT NAME="taxname" ID="taxname" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ <OPTION VALUE=""></OPTION>
+% foreach my $taxname (@taxname) {
+ <OPTION VALUE="<% $taxname %>"><% $taxname %></OPTION>
+% }
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount </TD>
+ <TD>
+ $<INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Comment </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="comment" SIZE="50" MAXLENGTH="50" VALUE="<% $comment %>" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ </TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" ID="submit" NAME="submit" VALUE="Add tax adjustment" <% $cgi->param('error') ? '' :' DISABLED' %>>
+
+</FORM>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $sql = 'SELECT DISTINCT(taxname) FROM cust_main_county';
+my $sth = dbh->prepare($sql) or die dbh->errstr;
+$sth->execute() or die $sth->errstr;
+my @taxname = map { $_->[0] || 'Tax' } @{ $sth->fetchall_arrayref([]) };
+
+my $conf = new FS::Conf;
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $amount = '';
+if ( $cgi->param('amount') =~ /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ) {
+ $amount = $1;
+}
+
+$cgi->param('comment') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
+ or die 'illegal description';
+my $comment = $1;
+
+</%init>
diff --git a/httemplate/edit/discount.html b/httemplate/edit/discount.html
new file mode 100644
index 0000000..6e0d8e1
--- /dev/null
+++ b/httemplate/edit/discount.html
@@ -0,0 +1,139 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Discount',
+ 'table' => 'discount',
+ 'fields' => [
+ 'name',
+ { field => 'disabled', type => 'checkbox', value=>'Y', },
+ # a weird kind of false laziness
+ # w/elements/tr-select-discount.html
+ { field => '_type', type => 'select',
+ options => \@_type_options,
+ onchange => '_type_changed',
+ },
+ { field => 'amount', type => 'money',
+ default => '0.00',
+ #cell_style => $amount_style,
+ },
+ { field => 'percent', type => 'percentage',
+ default => 0,
+ #cell_style => $percent_style,
+ },
+ { field => 'months', type => 'text', size => 2,
+ postfix => '<BR><FONT SIZE="-1"><I>(blank for non-expiring discount)</I></FONT>',
+ },
+ ],
+ 'labels' => {
+ 'discountnum' => 'Discount #',
+ 'name' => 'Name&nbsp;',
+ 'disabled' => 'Disabled&nbsp;',
+ '_type' => 'Type&nbsp;',
+ 'amount' => 'Amount&nbsp;',
+ 'percent' => 'Percentage&nbsp;',
+ 'months' => 'Duration (months)',
+ },
+ 'viewall_dir' => 'browse',
+ 'new_callback' => $new_callback,
+ 'edit_callback' => $edit_callback,
+ 'error_callback' => $error_callback,
+ 'html_init' => $javascript,
+ 'body_etc' => 'onLoad="_type_changed(document.edit_topform._type)"',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @_type_options = ( 'Amount', 'Percentage' );
+
+#my $amount_style = '';
+#my $percent_style = '';
+
+#my $hide = 'display:none;visibility:hidden';
+my $select = 'Select discount type';
+
+my $new_callback = sub {
+ #my( $cgi, $object, $fields_listref, $opt_hashref ) = @_;
+
+ #$amount_style = $hide;
+ #$percent_style = $hide;
+ unshift @_type_options, $select;
+};
+
+my $edit_callback = sub {
+ #my( $cgi, $object, $fields_listref, $opt_hashref ) = @_;
+ my( $cgi, $object ) = @_;
+
+ if ( $object->amount > 0 && $object->percent == 0 ) {
+ $object->set('_type', 'Amount');
+ #$percent_style = $hide;
+ } elsif ( $object->amount == 0 && $object->percent > 0 ) {
+ $object->set('_type', 'Percentage');
+ #$amount_style = $hide;
+ } elsif ( $object->amount == 0 && $object->percent == 0 ) {
+ #$amount_style = $hide;
+ #$percent_style = $hide;
+ unshift @_type_options, $select;
+ } else {
+ die "discount.amount and discount.percent not yet handled by web UI";
+ }
+
+};
+
+my $error_callback = sub {
+ #my( $cgi, $object, $fields_listref, $opt_hashref ) = @_;
+ my( $cgi, $object ) = @_;
+
+ if ( $cgi->param('_type') eq 'Amount' ) {
+ $object->set('_type', 'Amount');
+ #A$percent_style = $hide;
+ } elsif ( $cgi->param('_type') eq 'Percentage' ) {
+ $object->set('_type', 'Percentage');
+ #$amount_style = $hide;
+ } else {
+ #$amount_style = $hide;
+ #$percent_style = $hide;
+ unshift @_type_options, $select;
+ }
+
+};
+
+my $javascript = <<END;
+ <SCRIPT TYPE="text/javascript">
+ function _type_changed(what) {
+ var _type = what.options[what.selectedIndex].value;
+
+ if ( _type == '$select' ) {
+ document.getElementById('amount_label').style.display = 'none';
+ document.getElementById('amount_label').style.visibility = 'hidden';
+ document.getElementById('amount_input0').style.display = 'none';
+ document.getElementById('amount_input0').style.visibility = 'hidden';
+ document.getElementById('percent_label').style.display = 'none';
+ document.getElementById('percent_label').style.visibility = 'hidden';
+ document.getElementById('percent_input0').style.display = 'none';
+ document.getElementById('percent_input0').style.visibility = 'hidden';
+ } else if ( _type == 'Amount' ) {
+ document.getElementById('amount_label').style.display = '';
+ document.getElementById('amount_label').style.visibility = '';
+ document.getElementById('amount_input0').style.display = '';
+ document.getElementById('amount_input0').style.visibility = '';
+ document.getElementById('percent_label').style.display = 'none';
+ document.getElementById('percent_label').style.visibility = 'hidden';
+ document.getElementById('percent_input0').style.display = 'none';
+ document.getElementById('percent_input0').style.visibility = 'hidden';
+ } else if ( _type == 'Percentage' ) {
+ document.getElementById('amount_label').style.display = 'none';
+ document.getElementById('amount_label').style.visibility = 'hidden';
+ document.getElementById('amount_input0').style.display = 'none';
+ document.getElementById('amount_input0').style.visibility = 'hidden';
+ document.getElementById('percent_label').style.display = '';
+ document.getElementById('percent_label').style.visibility = '';
+ document.getElementById('percent_input0').style.display = '';
+ document.getElementById('percent_input0').style.visibility = '';
+ }
+
+ }
+ </SCRIPT>
+END
+
+</%init>
diff --git a/httemplate/edit/elements/ApplicationCommon.html b/httemplate/edit/elements/ApplicationCommon.html
new file mode 100644
index 0000000..7b1050a
--- /dev/null
+++ b/httemplate/edit/elements/ApplicationCommon.html
@@ -0,0 +1,552 @@
+<%doc>
+
+Examples:
+
+ #cust_bill_pay
+ include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'dst_table' => 'cust_bill',
+ 'dst_thing' => 'invoice',
+ )
+
+ #cust_credit_bill
+ include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'dst_table' => 'cust_bill',
+ 'dst_thing' => 'invoice',
+ )
+
+ #cust_pay_refund
+ include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_pay_refund.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'dst_table' => 'cust_refund',
+ 'dst_thing' => 'refund',
+ )
+
+ #cust_credit_refund
+ include('elements/ApplicationCommon.html',
+ 'form_action' => 'process/cust_credit_refund.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'dst_table' => 'cust_refund',
+ 'dst_thing' => 'refund',
+ )
+
+</%doc>
+
+<% include('/elements/header-popup.html', "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"') %>
+
+<% include('/elements/error.html') %>
+
+<P ID="ErrorMessage"></P>
+<FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST>
+
+<% $src_thing %> #<B><% $src_pkeyvalue %></B><BR>
+<INPUT TYPE="hidden" NAME="<% $src_pkey %>" VALUE="<% $src_pkeyvalue %>">
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<TR>
+ <TD ALIGN="right">Date: </TD>
+ <TD><B><% time2str($date_format, $src->_date) %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount: </TD>
+ <TD ID="original_amount"><B><% $money_char %><% $src_amount %></B>
+ </TD>
+ <TD>
+% if ($use_sub_dst_thing && $can_change_credit) {
+ <INPUT TYPE="hidden" NAME="src_amount" VALUE="<% $src_amount %>" >
+ <BUTTON TYPE="button" NAME="expand_button" ID="expand_button" onClick="do_change_amount(this);">Change</BUTTON>
+% }
+ </TD>
+
+</TR>
+
+<TR>
+ <TD ALIGN="right">Unapplied amount: </TD>
+ <TD ID="unapplied_amount"><B><% $money_char %><% $unapplied %></B></TD>
+</TR>
+
+% if ( $src_table eq 'cust_credit' ) {
+ <TR>
+ <TD ALIGN="right">Reason: </TD>
+ <TD COLSPAN=2><B><% $src->reason %></B></TD>
+ </TR>
+% }
+
+</TABLE>
+<BR>
+
+<SCRIPT TYPE="text/javascript">
+function clear_amounts() {
+ var rownum=0
+ var table = document.getElementById('ApplicationTable');
+ for (var row = 2; table.rows[row]; row++)
+ {
+ var inputs = table.rows[row].getElementsByTagName('input');
+ if ( !inputs.length ) {
+ break;
+ }
+ inputs.item(0).value = ''; // amount
+ }
+
+}
+
+function changed(what) {
+ dst = what.options[what.selectedIndex].value;
+
+ if ( dst == '' ) {
+ what.form.submit.disabled=true;
+%if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
+ what.form.tax_button.disabled=true;
+ what.form.clear_button.disabled=true;
+%}
+ return true;
+ }
+
+ what.form.submit.disabled=false;
+%if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
+ what.form.tax_button.disabled=false;
+ what.form.clear_button.disabled=false;
+%}
+
+% foreach my $dst ( @dst ) {
+
+ if ( dst == <% $dst->$dst_pkey %> ) {
+ what.form.amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
+% if ($use_sub_dst_thing) {
+ what.form.display_amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
+
+ var rownum=0
+ var table = document.getElementById('ApplicationTable');
+ while(table.rows[2]) {
+ table.deleteRow(2);
+ }
+% my $app_class = "FS::$link_table";
+% my $temp_app = $app_class->new(
+% { $src_pkey => $src_pkeyvalue,
+% $dst_pkey => $dst->$dst_pkey,
+% 'amount' => min($dst->$dst_unapplied, $unapplied),
+% }
+% );
+% my %apphash = ();
+% my $listref_or_error = $temp_app->calculate_applications;
+% %apphash = map { &{$key_generator}($_), $_ } @$listref_or_error
+% if ref($listref_or_error);
+% foreach my $cbp ( $dst->open_cust_bill_pkg ) {
+% my $desc = $cbp->desc;
+% my $total_owed = $cbp->owed_setup + $cbp->owed_recur;
+% my $key = &{$key_generator}([ $cbp, 0, {} ]);
+% my $amount = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
+% unless ( $cbp->pkgnum ) {
+% foreach my $taxX ( $cbp->cust_bill_pkg_tax_Xlocation ) {
+% my $pkey = $taxX->primary_key;
+% my $owed = $taxX->owed;
+% my $key = &{$key_generator}([ $cbp, 0, { $pkey => $taxX->$pkey } ]);
+% my $toapp = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
+ <% &{$row_generator}( $key, $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %>
+% $total_owed -= $owed;
+% $amount -= $toapp;
+% }
+% $desc .= ' (default)';
+% }
+% if ( $total_owed > 0 ) {
+ <% &{$row_generator}($key, $cbp, $desc, $total_owed, $amount, '') %>
+% }
+% }
+% }
+ }
+
+% }
+
+}
+
+function sub_changed(what) {
+
+ var amount = 0;
+ var table = document.getElementById('ApplicationTable');
+ var i = table.rows.length;
+ while(i-- > 2) {
+ var inputs = table.rows[i].getElementsByTagName('input');
+ if (! inputs.length) {
+ continue;
+ }
+ amount += parseFloat( inputs.item(0).value ) || 0;
+ }
+ what.form.amount.value = parseFloat(amount).toFixed(2);
+ what.form.display_amount.value = parseFloat(amount).toFixed(2);
+ set_amount_color(what);
+
+}
+
+function set_amount_color(what) {
+ if (what.form.src_amount.value < what.form.amount.value) {
+ what.form.display_amount.style.color = '#ff0000';
+ } else {
+ what.form.display_amount.style.color = '#00ff00';
+ }
+}
+
+</SCRIPT>
+
+Apply to:
+
+% if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
+<CENTER>
+ <TABLE>
+ <TR>
+ <TD>
+ <BUTTON TYPE="button" NAME="tax_button" ID="tax_button" onClick="do_calculate_tax(this);" DISABLED>Calculate Tax</BUTTON>
+ </TD>
+ <TD>
+ <BUTTON TYPE="button" NAME="clear_button" ID="clear_button" onClick="clear_amounts(this);" DISABLED>Clear Amounts</BUTTON>
+ </TD>
+ </TR>
+ </TABLE>
+</CENTER>
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-calculate_taxes.html',
+ 'subs' => [ 'calculate_taxes' ],
+ )
+ %>
+<SCRIPT TYPE="text/javascript">
+
+function show_taxes(arg) {
+ var argsHash = eval('(' + arg + ')');
+
+ var button = document.getElementById('tax_button');
+ button.disabled = false;
+ button.innerHTML = 'Calculate Tax';
+ button = document.getElementById('clear_button');
+ button.disabled = false;
+
+ var error = argsHash['error'];
+
+ var paragraph = document.getElementById('ErrorMessage');
+ if (error) {
+ paragraph.innerHTML = 'Error: ' + error;
+ paragraph.style.color = '#ff0000';
+ } else {
+ paragraph.innerHTML = '';
+ }
+ var taxlines = argsHash['taxlines'];
+
+ var table = document.getElementById('ApplicationTable');
+
+ var aFoundRow = 0;
+ for (i = 0; taxlines[i]; i++) {
+ var itemdesc = taxlines[i][0];
+ var locnum = taxlines[i][2];
+ if (taxlines[i][3]) {
+ locnum = taxlines[i][3];
+ }
+
+ var found = 0;
+ for (var row = 2; table.rows[row]; row++) {
+ var inputs = table.rows[row].getElementsByTagName('input');
+ if (! inputs.length) {
+ while ( table.rows[row] ) {
+ table.deleteRow(row);
+ }
+ break;
+ }
+ if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum )
+ {
+ inputs.item(0).value = taxlines[i][1];
+ aFoundRow = found = row;
+ break;
+ }
+ }
+ if (! found) {
+ var row = table.insertRow(table.rows.length);
+ var warning_cell = document.createElement('TD');
+ warning_cell.style.color = '#ff0000';
+ warning_cell.colSpan = 2;
+ warning_cell.innerHTML = 'Calculated Tax - ' + itemdesc + ' - ' +
+ taxlines[i][1] + ' will not be applied';
+ row.appendChild(warning_cell);
+ }
+ }
+
+ if (aFoundRow) {
+ sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0));
+ }
+
+}
+
+function do_calculate_tax (what) {
+ what.innerHTML = 'Calculating....';
+ what.disabled = true;
+ var button = document.getElementById('clear_button');
+ button.disabled = true;
+ var taxed_items = new Array();
+ var table = document.getElementById('ApplicationTable');
+ for (var row = 2; table.rows[row]; row++)
+ {
+ var inputs = table.rows[row].getElementsByTagName('input');
+ if ( !inputs.length ) {
+ break;
+ }
+ var taxed_item = new Array(
+ inputs.item(1).value, // billpkgnum
+ inputs.item(3).value, // s_or_r
+ inputs.item(0).value || 0 // amount
+ );
+ taxed_items.push(taxed_item);
+ }
+
+ var args = new Array(
+ 'crednum', '<% $src_pkeyvalue %>',
+ 'items', taxed_items
+ );
+ calculate_taxes ( args, show_taxes );
+}
+
+function do_change_amount (what) {
+ var amount_cell = document.getElementById('original_amount');
+ var inputs = amount_cell.getElementsByTagName('input');
+ if (inputs.length) {
+ src_amount_changed();
+ amount_cell.innerHTML = '<B><% $money_char %></B>' + inputs.item(0).value;
+ } else {
+ amount_cell.innerHTML = '<% $money_char %>';
+ var amount_input = document.createElement('INPUT');
+ amount_input.setAttribute('name', 'entered_amount');
+ amount_input.setAttribute('id', 'entered_amount');
+ amount_input.style.textAlign = 'right';
+ amount_input.setAttribute('size', 8);
+ amount_input.setAttribute('maxlength', 8);
+ amount_input.setAttribute('value', what.form.src_amount.value);
+ amount_input.setAttribute('onChange', "src_amount_changed(this);");
+ amount_cell.appendChild(amount_input);
+ }
+}
+
+function src_amount_changed () {
+ //alert('src_amount_changed called');
+ var entered_amount = document.getElementById('entered_amount');
+ if ( entered_amount ) {
+ entered_amount.form.src_amount.value = entered_amount.value;
+ var unapplied_cell = document.getElementById('unapplied_amount');
+ unapplied_cell.innerHTML = '<B><% $money_char %>' + entered_amount.value + '</B>';
+ set_amount_color(entered_amount);
+ }
+ return true;
+}
+
+</SCRIPT>
+
+%}
+
+<TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<TR>
+ <TD ALIGN="right"><% $dst_thing %>: </TD>
+ <TD><SELECT NAME="<% $dst_pkey %>" SIZE=1 onChange="changed(this)">
+<OPTION VALUE="">Select <% $dst_thing %>
+
+% foreach my $dst ( @dst ) {
+ <OPTION<% $dst->$dst_pkey eq $dst_pkeyvalue ? ' SELECTED' : '' %> VALUE="<% $dst->$dst_pkey %>">#<% $dst->$dst_pkey %> - <% time2str($date_format, $dst->_date) %> - $<% $dst->$dst_unapplied %>
+% }
+
+</SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount: </TD>
+ <TD><% $money_char %><INPUT TYPE="text" NAME="<% $use_sub_dst_thing ? 'display_' : '' %>amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8 <% $use_sub_dst_thing ? 'DISABLED' : '' %> STYLE="text-align:right;"></TD>
+% if ($use_sub_dst_thing) {
+ <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" >
+% }
+</TR>
+
+</TABLE>
+
+<BR>
+<CENTER><INPUT TYPE="submit"
+ VALUE="Apply"
+ NAME="submit"
+ ID="submit"
+% if ($use_sub_dst_thing && $can_change_credit) {
+ onClick="src_amount_changed()"
+% }
+ DISABLED
+></CENTER>
+
+</FORM>
+
+<SCRIPT TYPE="text/javascript">
+
+function myOnLoadFunction () {
+ <% $onload %>
+}
+
+</SCRIPT>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $src_thing = ucfirst($opt{'src_thing'});
+my $src_table = $opt{'src_table'};
+my $src_pkey = dbdef->table($src_table)->primary_key;
+
+my $dst_thing = ucfirst($opt{'dst_thing'});
+my $dst_table = $opt{'dst_table'};
+my $dst_pkey = dbdef->table($dst_table)->primary_key;
+my $dst_unapplied = $dst_table eq 'cust_bill' ? 'owed' : 'unapplied';
+
+$opt{form_action} =~ /^process\/(.*)\./ or die "bad form action";
+my $link_table = $1;
+
+my $use_sub_dst_thing = 0;
+$use_sub_dst_thing = 1
+ if ( $dst_table eq 'cust_bill' && $conf->exists("${link_table}_pkg-manual") );
+
+my $can_change_credit = 0;
+$can_change_credit = 1
+ if ( $src_table eq 'cust_credit' &&
+ $FS::CurrentUser::CurrentUser->access_right('Post credit') &&
+ $FS::CurrentUser::CurrentUser->access_right('Delete credit')
+ );
+
+my $to = $dst_table eq 'cust_refund' ? ' to Refund' : '';
+
+my($src_pkeyvalue, $amount, $dst_pkeyvalue, $src_amount);
+if ( $cgi->param('error') ) {
+ $src_pkeyvalue = $cgi->param($src_pkey);
+ $amount = $cgi->param('amount');
+ $dst_pkeyvalue = $cgi->param($dst_pkey);
+ $src_amount = $cgi->param('src_amount');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $src_pkeyvalue = $1;
+ $amount = '';
+ $dst_pkeyvalue = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } );
+die "$src_thing $src_pkeyvalue not found!" unless $src;
+
+$src_amount = $src->amount unless $cgi->param('error');
+
+my $unapplied = $src->unapplied;
+
+my @dst = sort { $a->_date <=> $b->_date
+ or $a->$dst_pkey <=> $b->$dst_pkey
+ }
+ grep { $_->$dst_unapplied != 0 }
+ qsearch($dst_table, { 'custnum' => $src->custnum } );
+
+my $row_generator = sub {
+ my ($key, $cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_;
+ my ($num, $s_or_r, $taxlinenum) = split(':', $key);
+ my $id = $cust_bill_pkg->pkgnum || 'Tax';
+ my $billpkgnum = $cust_bill_pkg->billpkgnum;
+ my $s_or_r = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
+
+ $amount = sprintf("%.2f", $amount);
+ qq!
+ var tablebody = document.getElementsByTagName('tbody').item(0);
+ var row = table.insertRow(rownum+2);
+ var pkg_cell = document.createElement('TD');
+ pkg_cell.style.textAlign = 'right';
+ pkg_cell.innerHTML = "$id - $desc - $owed:";
+ var amount_cell = document.createElement('TD');
+ amount_cell.innerHTML = "$money_char";
+ var amount_input = document.createElement('INPUT');
+ amount_input.setAttribute('name', 'subamount'+rownum);
+ amount_input.setAttribute('id', 'subamount'+rownum);
+ amount_input.style.textAlign = 'right';
+ amount_input.setAttribute('size', 8);
+ amount_input.setAttribute('maxlength', 8);
+ amount_input.setAttribute('rownum', rownum);
+ amount_input.setAttribute('value', "$amount");
+ amount_input.setAttribute('onChange', "sub_changed(this);");
+ amount_cell.appendChild(amount_input);
+ var subnum_input = document.createElement('INPUT');
+ subnum_input.setAttribute('name', 'subnum'+rownum);
+ subnum_input.setAttribute('id', 'subnum'+rownum);
+ subnum_input.setAttribute('type', 'hidden');
+ subnum_input.setAttribute('rownum', rownum);
+ subnum_input.setAttribute('value', "$billpkgnum");
+ amount_cell.appendChild(subnum_input);
+ var taxnum_input = document.createElement('INPUT');
+ taxnum_input.setAttribute('name', 'taxXlocationnum'+rownum);
+ taxnum_input.setAttribute('id', 'taxXlocationnum'+rownum);
+ taxnum_input.setAttribute('type', 'hidden');
+ taxnum_input.setAttribute('rownum', rownum);
+ taxnum_input.setAttribute('value', "$taxXnum");
+ amount_cell.appendChild(taxnum_input);
+ var s_or_r_input = document.createElement('INPUT');
+ s_or_r_input.setAttribute('name', 's_or_r'+rownum);
+ s_or_r_input.setAttribute('id', 's_or_r'+rownum);
+ s_or_r_input.setAttribute('type', 'hidden');
+ s_or_r_input.setAttribute('rownum', rownum);
+ s_or_r_input.setAttribute('value', "$s_or_r");
+ amount_cell.appendChild(s_or_r_input);
+ var itemdesc_input = document.createElement('INPUT');
+ itemdesc_input.setAttribute('name', 'itemdesc'+rownum);
+ itemdesc_input.setAttribute('id', 'itemdesc'+rownum);
+ itemdesc_input.setAttribute('type', 'hidden');
+ itemdesc_input.setAttribute('rownum', rownum);
+ itemdesc_input.setAttribute('value', "$desc");
+ amount_cell.appendChild(itemdesc_input);
+ row.appendChild(pkg_cell);
+ row.appendChild(amount_cell);
+ rownum++;
+ !;
+};
+
+my $key_generator = sub {
+ my $listref = shift;
+ my ($cust_bill_pkg, $amount, $hashref) = @$listref;
+ my $setup_or_recur = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
+ my $taxlinenum = $hashref->{billpkgtaxlocationnum} ||
+ $hashref->{billpkgtaxratelocationnum} ||
+ '';
+
+ join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum);
+};
+
+my $onload = 'return true;';
+
+if ($cgi->param('error')) {
+
+ my $set_sub_amounts =
+ join(';', map { "myform.subamount$_.value = ". $cgi->param("subamount$_") }
+ grep { /.+/ }
+ map { /^subnum(\d+)$/ ? $1 : '' }
+ $cgi->param
+ );
+ $set_sub_amounts &&= "$set_sub_amounts;sub_changed(myform.subamount0)";
+
+ $onload = qq!
+ var myform = document.getElementById('ApplicationForm');
+ changed(myform.elements['$dst_pkey']);
+ $set_sub_amounts;
+ return true;
+ !;
+}
+
+</%init>
diff --git a/httemplate/edit/elements/category_Common.html b/httemplate/edit/elements/category_Common.html
new file mode 100644
index 0000000..8bbdcd1
--- /dev/null
+++ b/httemplate/edit/elements/category_Common.html
@@ -0,0 +1,24 @@
+<% include( 'edit.html',
+ 'fields' => [
+ 'categoryname',
+ 'weight',
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'categorynum' => 'Category number',
+ 'categoryname' => 'Category name',
+ 'weight' => 'Weight',
+ 'disabled' => 'Disable category',
+ },
+ 'viewall_dir' => 'browse',
+ %opt,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html
new file mode 100644
index 0000000..b5f4939
--- /dev/null
+++ b/httemplate/edit/elements/class_Common.html
@@ -0,0 +1,32 @@
+<% include( 'edit.html',
+ 'fields' => [
+ 'classname',
+ (scalar(@category)
+ ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' }
+ : { field=>'categorynum', type=>'hidden' }
+ ),
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'classnum' => 'Class number',
+ 'classname' => 'Class name',
+ 'categorynum' => 'Category',
+ 'disabled' => 'Disable class',
+ },
+ 'viewall_dir' => 'browse',
+ %opt,
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %opt = @_;
+
+my $table = $opt{'table'};
+( my $category_table = $table ) =~ s/class/category/ or die;
+
+my @category = qsearch($category_table, { 'disabled' => '' });
+</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
new file mode 100644
index 0000000..0417dc5
--- /dev/null
+++ b/httemplate/edit/elements/edit.html
@@ -0,0 +1,854 @@
+<%doc>
+
+Example:
+
+ include( 'elements/edit.html',
+ 'name_singular' => #singular name for the record
+ # (preferred, will be pluralized automatically)
+ 'name' => #name for the record
+ # (deprecated, will be pluralized simplistically)
+ 'table' =>
+
+ #? 'primary_key' => #required when the dbdef doesn't know...???
+ 'labels' => {
+ 'column' => 'Label',
+ }
+
+ #listref - each item is a literal column name (or method) or hashref
+ # or (notyet) coderef
+ #if not specified all columns (except for the primary key) will be editable
+ 'fields' => [
+ 'columname',
+ { 'field' => 'another_columname',
+ 'type' => 'text', #text
+ #password
+ #money
+ #percentage
+ #checkbox
+ #select
+ #selectlayers (can now use after a tablebreak-tr-title... but not inside columnstart/columnnext/columnend)
+ #title
+ #tablebreak-tr-title
+ #columnstart
+ #columnnext
+ #columnend
+ #hidden - hidden value from object
+ #fixed - display fixed value from object or here
+ #fixed-country
+ #fixed-state
+ 'value' => 'Y', #for checkbox, title, fixed, hidden
+ 'disabled' => 0,
+ 'onchange' => 'javascript_function',
+
+ 'include_opt_callback' => sub { my $object = @_;
+ ( 'option' => 'value', );
+ },
+
+ 'm2name_table' => 'table_name',
+ 'm2name_namecol' => 'name_column',
+ #OR#
+ 'm2m_method' =>
+ #'m2m_srccol' => #opt, if not the same as this table
+ 'm2m_dstcol' => #required for now, eventuaully opt, if not the same as target table
+ #OR#
+ 'o2m_table' =>
+
+ 'm2_label' => 'Label', #
+ 'm2_new_default' => \@table_name_objects, #default
+ #m2 objects for
+ #new records
+ 'm2_error_callback' => sub { my($cgi, $object) = @_; },
+ 'm2_remove_warnings' => \%warnings, #hashref of warning
+ #messages for m2
+ #removal
+ 'm2_new_js' => 'function_name', #javascript function called
+ #on spawned rows (one arg:
+ #new_element)
+ 'm2_remove_js' => 'function_name', #js function called when
+ #a row is deleted (three
+ #args: value, text,
+ #'no_match')
+ #layer_fields & layer_values_callback only for selectlayer
+ 'layer_fields' => [
+ 'fieldname' => 'Label',
+ 'another_field' => {
+ label=>'Label',
+ type =>'text', #text, money
+ },
+ ],
+ 'layer_values_callback' =>
+ sub {
+ my( $cgi, $object ) = @_;
+ { 'layer' => { 'fieldname' => 'current_value',
+ 'fieldname2' => 'field2value',
+ ...
+ },
+ 'layer2' => { 'l2fieldname' => 'l2value',
+ ...
+ },
+ ...
+ };
+ },
+ },
+ ]
+
+ 'menubar' => '', #menubar arrayref
+
+ #agent virtualization
+ 'agent_virt' => 1,
+ 'agent_null' => 1, #if true, always allow no-agentnum globals
+ 'agent_null_right' => 'Access Right Name',
+ 'agent_clone_extra_sql' => '', #if provided, this overrides the extra_sql
+ #implementing agent virt, for clone
+ #operations. i.e. pass "1=1" to allow
+ #cloning anything
+
+ 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+
+ # overrides default popurl(1)."process/$table.html"
+ 'post_url' => popurl(1).'process/something',
+
+ #we're in a popup (no title/menu/searchboxes)
+ 'popup' => 1,
+
+ ###
+ # HTML callbacks
+ ###
+
+ 'body_etc' => '', # Additional BODY attributes, i.e. onLoad=""
+
+ 'html_init' => '', #after the header/menubar
+
+ #string or coderef of additional HTML to add before </TABLE>
+ 'html_table_bottom' => '',
+
+ #after </TABLE> but before the submit
+ 'html_bottom' => '', #string
+ 'html_bottom' => sub {
+ my $object = shift;
+ # ...
+ "html_string";
+ },
+
+ #javascript function name, will be called with form name as arg
+ 'onsubmit' => 'check_form_data',
+
+ #at the very bottom (well, as low as you can go from here)
+ 'html_foot' => '',
+
+ ###
+ # initialization callbacks
+ ###
+
+ ###global callbacks, always run if provided
+
+ #after decoding long CGI "redirect=" responses but
+ # before object creation/search
+ # (useful if you have a long form that might trigger redirect= and you need
+ # to do things with $cgi params - they're not decoded in the calling
+ # <%init> block yet)
+ 'begin_callback' = sub { my( $cgi, $fields_listref, $opt_hashref ) = @_; },
+
+ #after the mode-specific object creation/search
+ 'end_callback' = sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ ###mode-specific callbacks. one (and only one) of these four is called
+
+ #run when adding
+ 'new_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ #run when editing
+ 'edit_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ #run when re-displaying with an error
+ 'error_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ #run when cloning
+ 'clone_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ ###callbacks called in new mode only
+
+ # returns a hashref for the new object
+ 'new_hashref_callback'
+
+ # returns the new object iself (otherwise, ->new is called)
+ 'new_object_callback'
+
+ ###display callbacks
+
+ #run before display to return a different value
+ 'value_callback' => sub { my( $columname, $value ) = @_; },
+
+ #run before display to manipulate element of the 'fields' arrayref
+ 'field_callback' => sub { my( $cgi, $object, $field_hashref ) = @_; },
+
+ );
+
+</%doc>
+
+<% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
+ $title,
+ include( '/elements/menubar.html', @menubar ),
+ $opt{'body_etc'},
+ )
+%>
+
+<% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+%>
+
+<% include('/elements/error.html') %>
+
+% my $url = $opt{'post_url'} || popurl(1)."process/$table.html";
+
+<FORM NAME = "edit_topform"
+ METHOD = POST
+ ACTION = "<% $url %>"
+ <% $opt{onsubmit} ? 'onSubmit="return '.$opt{onsubmit}.'(this)"' : '' %>
+>
+
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>">
+<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $clone ? '' : $object->$pkey() %>">
+
+<FONT SIZE="+1"><B>
+<% ( $opt{labels} && exists $opt{labels}->{$pkey} )
+ ? $opt{labels}->{$pkey}
+ : $pkey
+%>
+</B></FONT>
+#<% ( !$clone && $object->$pkey() ) || "(NEW)" %>
+
+% my $tablenum = 0;
+<TABLE ID="TableNumber<% $tablenum++ %>" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+% my $g_row = 0;
+% my @g_row_stack = ();
+% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} }
+% @$fields
+% ) {
+%
+% my $trash = &{ $opt{'field_callback'} }( $cgi, $object, $f )
+% if $opt{'field_callback'};
+%
+% my $field = $f->{'field'};
+% my $type = $f->{'type'} ||= 'text';
+%
+% my $label = ( $opt{labels} && exists $opt{labels}->{$field} )
+% ? $opt{labels}->{$field}
+% : $field;
+%
+% my $onchange = $f->{'onchange'};
+%
+% my $layer_values = {};
+% $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object )
+% if $f->{'layer_values_callback'}
+% && ! $f->{'m2name_table'}
+% && ! $f->{'o2m_table'}
+% && ! $f->{'m2m_method'};
+%
+% warn "layer values: ". Dumper($layer_values)
+% if $opt{'debug'};
+%
+% my %include_common = (
+%
+% #text and derivitives
+% 'size' => $f->{'size'},
+% 'maxlength' => $f->{'maxlength'},
+% 'postfix' => $f->{'postfix'},
+%
+% #checkbox, title, fixed, hidden
+% #& deprecated weird value hashref used only by reason.html
+% 'value' => $f->{'value'},
+%
+% #select(-*)
+% 'options' => $f->{'options'},
+% 'labels' => $f->{'labels'},
+% 'multiple' => $f->{'multiple'},
+% 'disable_empty' => $f->{'disable_empty'},
+% #select-reason
+% 'reason_class' => $f->{'reason_class'},
+%
+% #selectlayers
+% 'layer_fields' => $f->{'layer_fields'},
+% 'layer_values' => $layer_values,
+% 'html_between' => $f->{'html_between'},
+%
+% #umm. for select-agent_types at least
+% 'disabled' => $f->{'disabled'},
+% );
+%
+% #selectlayers, others?
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}),
+% qw( js_only html_only select_only layers_only cell_style);
+%
+% #select-*
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}), qw( empty_label );
+%
+% #select-table
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}), qw( value_col extra_sql );
+%
+% #select-table, checkboxes-table
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}), qw( table name_col );
+%
+% #checkboxes-table
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}), qw( target_table link_table );
+%
+% #*-table
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}),
+% qw( hashref agent_virt agent_null agent_null_right );
+%
+% if ( $type eq 'tablebreak-tr-title' ) {
+% $include_common{'table_id'} = 'TableNumber'. $tablenum++;
+% }
+% if ( $type eq 'tablebreak-tr-title' || $type eq 'title' ) {
+% $include_common{'colspan'} = $f->{colspan} if $f->{colspan};
+% }
+%
+% if ( $f->{include_opt_callback} ) {
+% %include_common = ( %include_common,
+% &{ $f->{include_opt_callback} }( $object )
+% );
+% }
+%
+% my $layer_prefix_on = '';
+%
+% my $include_sub = sub {
+% my %opt = @_;
+%
+% my $fieldnum = delete $opt{'fieldnum'};
+%
+% my $include = $type;
+% $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
+% $include = "tr-$include" unless $include =~ /^(hidden|tablebreak|column)/;
+%
+% $include_common{'layer_prefix'} = "$field$fieldnum."
+% if $layer_prefix_on;
+%
+% my @include =
+% ( "/elements/$include.html",
+% 'field' => "$field$fieldnum",
+% 'id' => "$field$fieldnum", #separate?
+% 'label_id' => $field."_label$fieldnum", #don't want field0_label0...
+% %include_common,
+% %opt,
+% );
+% @include;
+% };
+%
+% my $column_sub = sub {
+% my %opt = @_;
+%
+% my $column = delete($opt{field});
+% my $fieldnum = delete($opt{fieldnum});
+% my $include = delete($opt{type}) || 'text';
+% $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
+%
+% ( "/elements/$include.html",
+% 'field' => $field.'__'.$column.$fieldnum,
+% 'id' => $field.'__'.$column.$fieldnum,
+% 'layer_prefix' => $field.'__'.$column.$fieldnum.".",
+% ( $fieldnum
+% ? ('cell_style' => 'border-top:1px solid black')
+% : ()
+% ),
+% 'cgi' => $cgi,
+% %opt,
+% );
+% };
+%
+% unless ( $type =~ /^column/ ) {
+% $g_row = 1 if $type eq 'tablebreak-tr-title';
+% $g_row++;
+% $g_row++ if $type eq 'title';
+% } else {
+% if ( $type eq 'columnstart' ) {
+% push @g_row_stack, $g_row;
+% $g_row = 0;
+% #} elsif ( $type eq 'columnnext' ) {
+% } elsif ( $type eq 'columnend' ) {
+% $g_row = pop @g_row_stack;
+% }
+%
+% }
+%
+% my $fieldnum = '';
+% my $curr_value = '';
+% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) {
+%
+% my($table, $col);
+% if ( $f->{'m2name_table'} ) {
+% $table = $f->{'m2name_table'};
+% $col = $f->{'m2name_namecol'};
+% } elsif ( $f->{'o2m_table'} ) {
+% $table = $f->{'o2m_table'};
+% $col = dbdef->table($f->{'o2m_table'})->primary_key;
+% } elsif ( $f->{'m2m_method'} ) {
+% $table = $f->{'m2m_method'};
+% $col = $f->{'m2m_dstcol'};
+% }
+% $fieldnum = 0;
+% $layer_prefix_on = 1;
+% #print out the fields for the existing m2s
+% my @existing = ();
+% if ( $mode eq 'error' ) {
+% @existing = &{ $f->{'m2_error_callback'} }( $cgi, $object );
+% } elsif ( $object->$pkey() ) { # $mode eq 'edit'||'clone'
+% @existing = $object->$table();
+% warn scalar(@existing). " from $object->$table: ". join('/', @existing)
+% if $opt{'debug'};
+% } elsif ( $f->{'m2_new_default'} ) { # && $mode eq 'new'
+% @existing = @{ $f->{'m2_new_default'} };
+% }
+% foreach my $name_obj ( @existing ) {
+%
+% my $ex_label = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this '.
+% lc($f->{'m2_label'}).
+% qq(" onClick="remove_$field($fieldnum);").
+% ' STYLE="color:#ff0000;font-weight:bold;'.
+% 'padding-left:2px;padding-right:2px"'.
+% '>&nbsp;'. ($f->{'m2_label'} || $field ). ' ';
+%
+% if ( $f->{'layer_values_callback'} ) {
+% my %switches = ( 'mode' => $mode );
+% $layer_values =
+% &{ $f->{'layer_values_callback'} }( $cgi, $name_obj, \%switches );
+% }
+% warn "layer values: ". Dumper($layer_values)
+% if $opt{'debug'};
+%
+% my @existing = &{ $include_sub }(
+% 'label' => $ex_label,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $name_obj->$col(),
+% 'onchange' => $onchange,
+% 'layer_values' => $layer_values,
+% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ),
+% );
+% $existing[0] =~ s(^/elements/tr-)(/elements/);
+% my @label = @existing;
+% $label[0] = '/elements/tr-td-label.html';
+
+ <% include( @label ) %>
+ <TD COLSPAN="<% $f->{'colspan'} || 1 %>">
+ <% include( @existing ) %>
+ </TD>
+
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }( %$c,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $name_obj->$column()
+% );
+
+ <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>'
+ style='text-align:right;vertical-align:top;
+ border-top:1px solid black;padding-top:5px;'>
+ <% $c->{'label'} || '' %>
+ </TD>
+ <TD style='border-top:1px solid black;padding-top:3px;'>
+ <% include( @column ) %>
+ </TD>
+% }
+% }
+
+ </TR>
+
+% $fieldnum++;
+% $g_row++;
+% }
+% #$field .= $fieldnum;
+% $onchange .= "\nspawn_$field(what);";
+% } else {
+% if ( $f->{curr_value_callback} ) {
+% $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ),
+% } else {
+% $curr_value = $object->$field();
+% }
+% $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value )
+% if $opt{'value_callback'} && $mode ne 'error';
+% }
+%
+% my @include = &{ $include_sub }(
+% 'label' => $label,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $curr_value,
+% 'object' => $object,
+% 'cgi' => $cgi,
+% 'onchange' => $onchange,
+% ( $fieldnum ? ('cell_style' => 'border-top:1px solid black') : () ),
+% );
+%
+% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) {
+% $include[0] =~ s(^/elements/tr-)(/elements/);
+% my @label = @include;
+% $label[0] = '/elements/tr-td-label.html';
+
+ <% include( @label ) %>
+ <TD COLSPAN="<% $f->{'colspan'} || 1 %>">
+ <% include( @include ) %>
+ </TD>
+
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }( %$c, 'fieldnum' => $fieldnum );
+
+ <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>'
+ style='text-align:right;vertical-align:top;
+ border-top:1px solid black;padding-top:5px;'>
+ <% $c->{'label'} || '' %>
+ </TD>
+ <TD style='border-top:1px solid black;padding-top:3px;'>
+ <% include( @column ) %>
+ </TD>
+% }
+% }
+
+ </TR>
+
+% } else {
+
+ <% include( @include ) %>
+
+% }
+% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) {
+
+ <SCRIPT TYPE="text/javascript">
+
+ var <%$field%>_rownum = <% $g_row %>;
+ var <%$field%>_fieldnum = <% $fieldnum %>;
+
+ function spawn_<%$field%>(what) {
+
+ // only spawn if we're the last element... return if not
+
+ var field_regex = /(\d+)(_[a-z]+)?$/;
+ var match = field_regex.exec(what.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+ if ( match[1] != <%$field%>_fieldnum ) {
+ return;
+ }
+
+ // change the label on the last entry & add a remove button
+ var prev_label = document.getElementById('<% $field %>_label' + <%$field%>_fieldnum );
+ prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2_label'}) %>" onClick="remove_<% $field %>(' + <%$field%>_fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" >&nbsp;<% $f->{'m2_label'} || $field %>';
+
+ <%$field%>_fieldnum++;
+
+ //get the new widget
+
+% $include[0] =~ s(^/elements/tr-)(/elements/);
+% my @layer_opt = ( @include,
+% 'field' => $field."MAGIC_NUMBER",
+% 'id' => $field."MAGIC_NUMBER",
+% 'layer_prefix' => $field."MAGIC_NUMBER.",
+% );
+% warn @layer_opt if $opt{'debug'};
+
+ var newrow = <% include(@layer_opt, html_only=>1) |js_string %>;
+
+% #until the rest have html/js_only
+% if ( $type eq 'selectlayers' || $type =~ /^select-cgp_rule_/ ) {
+ var newfunc = <% include(@layer_opt, js_only=>1) |js_string %>;
+% } else {
+ var newfunc = '';
+% }
+
+ // substitute in the new field name
+ var magic_regex = /MAGIC_NUMBER/g;
+ newrow = newrow.replace( magic_regex, <%$field%>_fieldnum );
+ newfunc = newfunc.replace( magic_regex, <%$field%>_fieldnum );
+
+ // evaluate new_func
+ if (window.ActiveXObject) {
+ window.execScript(newfunc);
+ } else { /* (window.XMLHttpRequest) */
+ //window.eval(newfunc);
+ setTimeout(newfunc, 0);
+ }
+
+ // add new row
+
+ //hmm, can't use selectlayers after a tablebreak-title for now
+ var table = document.getElementById('TableNumber<% $tablenum-1 %>');
+
+ var row = table.insertRow(<%$field%>_rownum++);
+
+ var label_cell = document.createElement('TD');
+
+ label_cell.id = '<% $field %>_label' + <%$field%>_fieldnum;
+
+ label_cell.style.textAlign = "right";
+ label_cell.style.verticalAlign = "top";
+ label_cell.style.borderTop = "1px solid black";
+ label_cell.style.paddingTop = "5px";
+
+ label_cell.innerHTML = '<% $label %>';
+
+ row.appendChild(label_cell);
+
+ var widget_cell = document.createElement('TD');
+
+ widget_cell.style.borderTop = "1px solid black";
+ widget_cell.style.paddingTop = "3px";
+ widget_cell.colSpan = "<% $f->{'colspan'} || 1 %>"
+
+ widget_cell.innerHTML = newrow;
+
+ row.appendChild(widget_cell);
+
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }(%$c, 'fieldnum' => 'MAGIC_NUMBER');
+
+ var column = <% include(@column, html_only=>1) |js_string %>;
+ column = column.replace( magic_regex, <%$field%>_fieldnum );
+
+ var column_label = document.createElement('TD');
+ column_label.id =
+ '<% $field %>__<% $column %>_label' + <%$field%>_fieldnum;
+
+ column_label.style.textAlign = "right";
+ column_label.style.verticalAlign = "top";
+ column_label.style.borderTop = "1px solid black";
+ column_label.style.paddingTop = "5px";
+
+ column_label.innerHTML = '<% $c->{'label'} || '' %>';
+
+ row.appendChild(column_label);
+
+ var column_widget = document.createElement('TD');
+
+ column_widget.style.borderTop = "1px solid black";
+ column_widget.style.paddingTop = "3px";
+
+ column_widget.innerHTML = column;
+
+ row.appendChild(column_widget);
+
+% }
+% }
+
+% if ( $f->{'m2_new_js'} ) {
+ // take out items selected in previous dropdowns
+ var new_element = document.getElementById("<%$field%>" + <%$field%>_fieldnum );
+ <% $f->{'m2_new_js'} %>(new_element);
+
+ if ( new_element.length < 2 ) {
+ //just the ** Select new **, so don't display the row
+ row.style.display = 'none';
+ }
+% }
+
+ }
+
+ function remove_<%$field%>(remove_fieldnum) {
+ //alert("remove <%$field%> " + remove_fieldnum);
+ var select = document.getElementById('<%$field%>' + remove_fieldnum);
+
+ if ( ! select ) {
+ alert("can't find element <%$field%>" + remove_fieldnum);
+ return;
+ }
+
+% my $warnings = $f->{'m2_remove_warnings'};
+% if ( $warnings ) {
+ var sel_value = select.options[select.selectedIndex].value;
+% foreach my $value ( keys %$warnings ) {
+ if ( sel_value == '<% $value %>' ) {
+ if ( ! confirm( <% $warnings->{$value} |js_string %> ) ) {
+ return;
+ }
+ }
+% }
+% }
+
+ select.disabled = 'disabled'; // this seems to prevent it from being submitted on tested browsers so far (IE, moz, konq at least)
+ var label_td = document.getElementById('<%$field%>_label' + remove_fieldnum );
+ label_td.parentNode.style.display = 'none';
+
+% if ( $f->{m2_remove_js} ) {
+ var opt = select.options[select.selectedIndex];
+ <% $f->{m2_remove_js} %>( opt.value, opt.text, 'no_match');
+% }
+
+ }
+
+ </SCRIPT>
+
+% }
+
+% }
+
+<% ref( $opt{'html_table_bottom'} )
+ ? &{ $opt{'html_table_bottom'} }( $object )
+ : $opt{'html_table_bottom'}
+%>
+
+</TABLE>
+
+<% ref( $opt{'html_bottom'} )
+ ? &{ $opt{'html_bottom'} }( $object )
+ : $opt{'html_bottom'}
+%>
+
+<BR>
+
+<INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "<% ( !$clone && $object->$pkey() )
+ ? "Apply changes"
+ : "Add ". ( $opt{'name'} || $opt{'name_singular'} )
+ %>"
+>
+
+</FORM>
+
+<% ref( $opt{'html_foot'} )
+ ? &{ $opt{'html_foot'} }( $object )
+ : $opt{'html_foot'}
+%>
+
+<% include("/elements/footer.html") %>
+<%init>
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#false laziness w/process.html
+my $table = $opt{'table'};
+my $class = "FS::$table";
+my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} ||
+my $fields = $opt{'fields'}
+ #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ];
+ || [ grep { $_ ne $pkey } fields($table) ];
+#my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields;
+
+if ( $cgi->param('redirect') ) {
+ my $session = $cgi->param('redirect');
+ my $pref = $curuser->option("redirect$session");
+ die "unknown redirect session $session\n" unless length($pref);
+ $cgi = new CGI($pref);
+}
+
+&{$opt{'begin_callback'}}( $cgi, $fields, \%opt )
+ if $opt{'begin_callback'};
+
+my %qsearch = (
+ 'table' => $table,
+ 'extra_sql' => ( $opt{'agent_virt'}
+ ? ' AND '. $curuser->agentnums_sql(
+ 'null_right' => $opt{'agent_null_right'}
+ )
+ : ''
+ ),
+);
+
+my $mode;
+my $object;
+my $clone = '';
+if ( $cgi->param('error') ) {
+
+ $mode = 'error';
+
+ $object = $class->new( {
+ map { $_ => scalar($cgi->param($_)) } fields($table)
+ });
+
+ &{$opt{'error_callback'}}( $cgi, $object, $fields, \%opt )
+ if $opt{'error_callback'};
+
+} elsif ( $cgi->param('clone') =~ /^(\d+)$/ ) {
+
+ $mode = 'clone';
+
+ $clone = $1;
+
+ $qsearch{'extra_sql'} = ' AND '. $opt{'agent_clone_extra_sql'}
+ if $opt{'agent_clone_extra_sql'};
+
+ $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } })
+ or die "$pkey $clone not found in $table";
+
+ &{$opt{'clone_callback'}}( $cgi, $object, $fields, \%opt )
+ if $opt{'clone_callback'};
+
+ #$object->$pkey('');
+
+ $opt{action} ||= 'Add';
+
+} elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing
+
+ $mode = 'edit';
+
+ my $value;
+ if ( $cgi->param($pkey) ) {
+ $value = $cgi->param($pkey)
+ } else {
+ my( $query ) = $cgi->keywords;
+ $value = $query;
+ }
+ $value =~ /^(\d+)$/ or die "unparsable $pkey";
+ $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $1 } })
+ or die "$pkey $1 not found in $table";
+
+ warn "$table $pkey => $1"
+ if $opt{'debug'};
+
+ &{$opt{'edit_callback'}}( $cgi, $object, $fields, \%opt )
+ if $opt{'edit_callback'};
+
+} else { #adding
+
+ $mode = 'new';
+
+ my $hashref = $opt{'new_hashref_callback'}
+ ? &{$opt{'new_hashref_callback'}}
+ : {};
+
+ $object = $opt{'new_object_callback'}
+ ? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt )
+ : $class->new( $hashref );
+
+ &{$opt{'new_callback'}}( $cgi, $object, $fields, \%opt )
+ if $opt{'new_callback'};
+
+}
+
+&{$opt{'end_callback'}}( $cgi, $object, $fields, \%opt )
+ if $opt{'end_callback'};
+
+$opt{action} ||= $object->$pkey() ? 'Edit' : 'Add';
+
+my $title = $opt{action}. ' '. ( $opt{name} || $opt{'name_singular'} );
+
+my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
+$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};
+
+my @menubar = ();
+if ( $opt{'menubar'} ) {
+ @menubar = @{ $opt{'menubar'} };
+} else {
+ my $items = $opt{'name'} ? $opt{'name'}.'s' : PL($opt{'name_singular'});
+ @menubar = (
+ "View all $items" => $viewall_url,
+ );
+}
+
+</%init>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
new file mode 100644
index 0000000..e74f442
--- /dev/null
+++ b/httemplate/edit/elements/svc_Common.html
@@ -0,0 +1,211 @@
+<% include( 'edit.html',
+
+ 'menubar' => [],
+
+ 'error_callback' => sub {
+ my( $cgi, $svc_x, $fields, $opt ) = @_;
+ #$svcnum = $svc_x->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+
+ $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
+ die "No part_svc entry!" unless $part_svc;
+
+ label_fixup($part_svc, $opt);
+
+ $svc_x->setfield('svcpart', $svcpart);
+ },
+
+ 'edit_callback' => sub {
+ my( $cgi, $svc_x, $fields, $opt ) = @_;
+ #$svcnum = $svc_x->svcnum;
+ my $cust_svc = $svc_x->cust_svc
+ 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;
+
+ label_fixup($part_svc, $opt);
+ },
+
+ 'new_hashref_callback' => sub {
+ #my( $cgi, $svc_x ) = @_;
+
+ { pkgnum => $pkgnum,
+ svcpart => $svcpart,
+ };
+
+ },
+
+ 'new_callback' => sub {
+ my( $cgi, $svc_x, $fields, $opt ) = @_;
+
+ $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
+ die "No part_svc entry!" unless $part_svc;
+
+ label_fixup($part_svc, $opt);
+
+ #$svcnum='';
+
+ if ( my $cb = $opt{'svc_new_callback'} ) {
+ my $cust_pkg = $pkgnum
+ ? qsearchs('cust_pkg', {pkgnum=>$pkgnum})
+ : ''; #?
+ &{ $cb }( $cgi,$svc_x, $part_svc,$cust_pkg, $fields,$opt);
+ }
+
+ $svc_x->set_default_and_fixed;
+
+ },
+
+ 'field_callback' => sub {
+ my ($cgi, $object, $f) = @_;
+
+ my $columndef = $part_svc->part_svc_column($f->{'field'});
+ my $flag = $columndef->columnflag;
+ if ( $flag eq 'F' ) {
+ $f->{'type'} = length($columndef->columnvalue)
+ ? 'fixed'
+ : 'hidden';
+ $f->{'value'} = $columndef->columnvalue;
+ } elsif ( $flag eq 'A' ) {
+ $f->{'type'} = 'hidden';
+ } elsif ( $flag eq 'M' ) {
+ $f->{'empty_label'} = 'Select inventory item';
+ $f->{'type'} = 'select-table';
+ $f->{'table'} = 'inventory_item';
+ $f->{'name_col'} = 'item';
+ $f->{'value_col'} = 'item';
+ $f->{'agent_virt'} = 1;
+ $f->{'agent_null'} = 1;
+ $f->{'hashref'} = {
+ 'classnum'=>$columndef->columnvalue,
+ #'svcnum' => '',
+ };
+ $f->{'extra_sql'} = 'AND ( svcnum IS NULL ';
+ $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum
+ if $object->svcnum;
+ $f->{'extra_sql'} .= ' ) ';
+ $f->{'disable_empty'} = $object->svcnum ? 1 : 0,
+ }
+
+ if ( $f->{'type'} eq 'select-svc_pbx'
+ || $f->{'type'} eq 'select-svc-domain'
+ )
+ {
+ $f->{'include_opt_callback'} =
+ sub { ( 'pkgnum' => $pkgnum,
+ 'svcpart' => $svcpart,
+ );
+ };
+ }
+
+ if ( $f->{'field'} eq 'custnum' && $pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum});
+ $object->set('custnum', $cust_pkg->custnum);
+ }
+
+ },
+
+ 'html_init' => sub {
+ my $cust_main;
+ if ( $pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum});
+ $cust_main = $cust_pkg->cust_main if $cust_pkg;
+ }
+ $cust_main
+ ? include( '/elements/small_custview.html',
+ $cust_main,
+ '',
+ 1,
+ popurl(2). "view/cust_main.cgi"
+ ). '<BR>'
+ : '';
+
+ },
+
+ 'html_table_bottom' => sub {
+ my $svc_x = shift;
+ my $html = '';
+ foreach my $field ($svc_x->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.
+ $html .=
+ $svc_x->pvf($field)->widget( 'HTML',
+ 'edit',
+ $svc_x->getfield($field)
+ );
+ }
+ }
+ $html;
+ },
+
+ 'html_bottom' => sub {
+ qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!.
+ qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+ },
+
+ %opt #pass through/override params
+ )
+%>
+<%once>
+
+sub label_fixup {
+ my( $part_svc, $opt ) = @_;
+
+ $opt->{'name'} ||= $part_svc->svc;
+
+ my $svcdb = $part_svc->svcdb;
+ require "FS/$svcdb.pm";
+
+ if ( UNIVERSAL::can("FS::$svcdb", 'table_info') ) {
+ #$opt->{'name'} ||= "FS::$svcdb"->table_info->{'name'};
+
+ my $fields = "FS::$svcdb"->table_info->{'fields'};
+ $opt->{'fields'} ||= [ grep { $_ ne 'svcnum' } keys %$fields ];
+
+ $opt->{labels} ||= {
+ map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields
+ };
+ }
+
+ #false laziness w/view/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ my $labels = $opt->{labels}; # with -> here
+ foreach my $field ( keys %{ $opt->{labels} } ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/;
+ }
+
+}
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+#my( $svcnum, $pkgnum, $svcpart, $part_svc );
+my( $pkgnum, $svcpart, $part_svc );
+
+#get & untaint pkgnum & svcpart
+if ( ! $cgi->param('error')
+ && $cgi->param('pkgnum') && $cgi->param('svcpart')
+ )
+{
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+ #$cgi->delete_all(); #so edit.html treats this correctly as new??
+}
+
+</%init>
diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html
new file mode 100644
index 0000000..3ab47fe
--- /dev/null
+++ b/httemplate/edit/inventory_class.html
@@ -0,0 +1,16 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Inventory Class',
+ 'table' => 'inventory_class',
+ 'labels' => {
+ 'classnum' => 'Class number',
+ 'classname' => 'Class name',
+ },
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/invoice_logo.html b/httemplate/edit/invoice_logo.html
new file mode 100644
index 0000000..e1c6149
--- /dev/null
+++ b/httemplate/edit/invoice_logo.html
@@ -0,0 +1,136 @@
+<% include("/elements/header.html", "Edit $type2desc{$type} invoice logo",
+ menubar(
+ 'View all invoice templates' => $p.'browse/invoice_template.html'
+ )
+ )
+%>
+
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT>
+ <BR><BR>
+% }
+
+% if ( $cgi->param('msg') ) {
+ <FONT SIZE="+1"><B><% $cgi->param('msg') |h %></B></FONT>
+ <BR><BR>
+% }
+
+% if ( $mode eq 'upload' ) {
+ <FORM ACTION="invoice_logo.html" METHOD="POST" ENCTYPE="multipart/form-data">
+ <INPUT TYPE="hidden" NAME="mode" VALUE="preview">
+% } elsif ( $mode eq 'preview' ) {
+ <FORM ACTION="process/invoice_logo.html" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="preview_session" VALUE="<% $session %>">
+% }
+
+<INPUT TYPE="hidden" NAME="type" VALUE="<% $type %>">
+<INPUT TYPE="hidden" NAME="name" VALUE="<% $name %>">
+
+<% include('/elements/table-grid.html') %>
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Current logo</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">New logo preview</TH>
+</TR>
+
+<TR>
+
+ <TD CLASS="grid" BGCOLOR="#ffffff">
+
+% if ( $type eq 'png' ) {
+
+ <IMG SRC="<% $p %>view/logo.cgi?type=png;name=<% $name %>">
+
+% } elsif ( $type eq 'eps' ) {
+
+ <i>EPS preview not yet supported</i>
+
+% }
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#ffffff">
+
+% if ( $mode eq 'upload' ) {
+
+ Upload new logo (.<%uc($type)%> format): <INPUT TYPE="file" NAME="new_logo">
+ <BR><INPUT TYPE="submit" NAME="submit" VALUE="Upload">
+
+% } elsif ( $mode eq 'preview' ) {
+
+ <IMG SRC="<% $p %>view/logo.cgi?type=png;preview_session=<% $session %>">
+
+% }
+
+ </TD>
+
+
+</TR>
+
+</TABLE>
+
+% if ( $mode eq 'preview' ) {
+ <BR>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Change logo">
+% }
+
+</FORM>
+
+<% include("/elements/footer.html") %>
+
+<%once>
+
+my %type2desc = (
+ 'png' => 'online',
+ 'eps' => 'Print/PDF (typeset)',
+);
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $type = $cgi->param('type');
+
+$cgi->param('name') =~ /^([^\.\/]*)$/ or die "illegal name";
+my $name = $1;
+
+$cgi->param('mode') =~ /^(\w*)$/ or die "illegal mode";
+my $mode = $1 || 'upload';
+
+my $error = '';
+my $session = '';
+if ( $mode eq 'preview' ) {
+
+ my $fh = $cgi->upload('new_logo');
+
+ if ( defined $fh ) {
+
+ local $/;
+ my $logo_data = <$fh>;
+
+ $session = int(rand(4294967296)); #XXX
+ my $pref = new FS::access_user_pref({
+ 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
+ 'prefname' => "logo_preview$session",
+ 'prefvalue' => encode_base64($logo_data),
+ 'expiration' => time + 3600, #1h? 1m?
+ });
+ my $pref_error = $pref->insert;
+ if ( $pref_error ) {
+ die "FATAL: couldn't set preview cookie: $pref_error\n";
+ }
+
+ } else {
+
+ $mode = 'upload';
+ $error = 'No file uploaded';
+
+ }
+
+}
+
+</%init>
diff --git a/httemplate/edit/invoice_template.html b/httemplate/edit/invoice_template.html
new file mode 100644
index 0000000..9cec62c
--- /dev/null
+++ b/httemplate/edit/invoice_template.html
@@ -0,0 +1,69 @@
+<% include("/elements/header.html", "Edit $type2desc{$type} invoice template",
+ menubar(
+ 'View all invoice templates' => $p.'browse/invoice_template.html'
+ )
+ )
+%>
+
+<FORM ACTION="process/invoice_template.html" METHOD="POST">
+<INPUT TYPE="hidden" NAME="confname" VALUE="<% $confname %>">
+
+% if ( $type eq 'html' ) {
+
+ <% include('/elements/htmlarea.html',
+ 'field' => 'value',
+ 'curr_value' => $value,
+ 'height' => 800,
+ )
+ %>
+
+% } else {
+
+ <TEXTAREA NAME="value" ROWS=30 COLS=80 WRAP="off"><%$value |h %></TEXTAREA>
+
+% }
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="Change template">
+
+</FORM>
+
+<% include("/elements/footer.html") %>
+
+<%once>
+
+my %type2desc = (
+ 'html' => 'HTML',
+ 'latex' => 'Print/PDF (typeset)',
+ 'text' => 'Plaintext',
+);
+
+my %type2base = (
+ 'html' => 'invoice_html',
+ 'latex' => 'invoice_latex',
+ 'text' => 'invoice_template',
+);
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $type = $cgi->param('type');
+my $name = $cgi->param('name');
+my $suffix = $cgi->param('suffix');
+
+#XXX type handling, just testing this out for now
+
+my $conf = new FS::Conf;
+
+my $value = length($name)
+ ? join("\n", $conf->config_orbase($type2base{$type}.$suffix, $name) )
+ : join("\n", $conf->config($type2base{$type}.$suffix) );
+
+my $confname = length($name)
+ ? $type2base{$type}.$suffix. '_'. $name
+ : $type2base{$type}.$suffix;
+
+</%init>
diff --git a/httemplate/edit/mailinglistmember.html b/httemplate/edit/mailinglistmember.html
new file mode 100644
index 0000000..2391cb6
--- /dev/null
+++ b/httemplate/edit/mailinglistmember.html
@@ -0,0 +1,25 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'member',
+ 'table' => 'mailinglistmember',
+ 'popup' => 1,
+ 'fields' => [
+ { field=>'listnum', type=>'hidden', },
+ { field=>'svcnum', type=>'hidden', }, #not yet
+ { field=>'contactemailnum', type=>'hidden', }, #not yet
+ { field=>'email', type=>'text', },
+ ],
+ 'labels' => { 'membernum' => 'Member',
+ 'email' => 'Email address',
+ },
+ 'new_callback' => $new_callback,
+ )
+%>
+<%init>
+
+my $new_callback = sub {
+ #my( $cgi, $object, $fields_listref, $opt_hashref ) = @_;
+ my( $cgi, $object ) = @_;
+ $object->listnum( $cgi->param('listnum') );
+};
+
+</%init>
diff --git a/httemplate/edit/msgcat.cgi b/httemplate/edit/msgcat.cgi
new file mode 100755
index 0000000..85b3008
--- /dev/null
+++ b/httemplate/edit/msgcat.cgi
@@ -0,0 +1,54 @@
+<% header("Edit Message catalog" ) %>
+<BR>
+
+<% include('/elements/error.html') %>
+
+<% $widget->html %>
+
+ </TABLE>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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;
+ },
+
+);
+
+</%init>
diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi
new file mode 100755
index 0000000..c0ff386
--- /dev/null
+++ b/httemplate/edit/part_bill_event.cgi
@@ -0,0 +1,570 @@
+<% include('/elements/header.html',
+ "$action Invoice Event Definition",
+ menubar(
+ 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi',
+ )
+ )
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/part_bill_event.cgi" NAME="editEvent" METHOD=POST>
+<INPUT TYPE="hidden" NAME="eventpart" VALUE="<% $part_bill_event->eventpart %>">
+Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
+
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Event name </TD>
+ <TD><INPUT TYPE="text" NAME="event" VALUE="<% $hashref->{event} %>"></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">For </TD>
+ <TD>
+ <SELECT NAME="payby" <% $hashref->{eventpart} ? '' : 'MULTIPLE SIZE=7'%>>
+% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
+% foreach my $payby ( keys %payby ) {
+ <OPTION VALUE="<% $payby %>"<% ($part_bill_event->payby eq $payby) ? ' SELECTED' : '' %>><% $payby{$payby} %></OPTION>
+% }
+ </SELECT> customers
+ </TD>
+ </TR>
+% my $days = $hashref->{seconds}/86400;
+
+
+ <TR>
+ <TD ALIGN="right">After</TD>
+ <TD><INPUT TYPE="text" NAME="days" VALUE="<% $days %>"> days</TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Test event</TD>
+ <TD>
+ <SELECT NAME="freq">
+% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly';
+% foreach my $freq ( keys %freq ) {
+%
+
+
+ <OPTION VALUE="<% $freq %>"<% ($part_bill_event->freq eq $freq) ? ' SELECTED' : '' %>><% $freq{$freq} %></OPTION>
+% }
+
+
+ </SELECT>
+ </TD>
+ </TR>
+
+
+ <TR>
+ <TD ALIGN="right">Disabled</TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD VALIGN="top" 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'};
+% my %agentnums = map { $_=>1 } split(/,\s*/, $plandata->{'agentnum'});
+% '<SELECT NAME="agentnum" MULTIPLE>'.
+% join("\n", map {
+% '<OPTION VALUE="'. $_->agentnum. '"'.
+% ( $agentnums{$_->agentnum} ? ' SELECTED' : '' ).
+% '>'. $_->agent
+% } qsearch('agent', { 'disabled' => '' } ) ).
+% '</SELECT>';
+%}
+%
+%sub honor_dundate {
+% my $label = shift;
+% my $plandata = shift;
+% '<TABLE>'.
+% '<TR><TD ALIGN="right">Allow delay until dun date? </TD>'.
+% qq(<TD><INPUT TYPE="checkbox" NAME="$label" VALUE="$label => 1," ).
+% ( $plandata->{$label} eq "$label => 1," ? 'CHECKED' : '' ).
+% '>'.
+% '</TD></TR>'.
+% '</TABLE>'
+%}
+%
+%my $conf = new FS::Conf;
+%my $money_char = $conf->config('money_char') || '$';
+%
+%my $late_taxclass = '';
+%my $late_percent_taxclass = '';
+%if ( $conf->exists('enable_taxclasses') ) {
+% $late_taxclass =
+% '<BR>Taxclass '.
+% include('/elements/select-taxclass.html',
+% 'curr_value' => '%%%late_taxclass%%%',
+% 'name' => 'late_taxclass' );
+% $late_percent_taxclass =
+% '<BR>Taxclass '.
+% include('/elements/select-taxclass.html',
+% 'curr_value' => '%%%late_percent_taxclass%%%',
+% 'name' => 'late_percent_taxclass' );
+%}
+%
+%#this is pretty kludgy right here.
+%tie my %events, 'Tie::IxHash',
+%
+% 'fee' => {
+% 'name' => 'Late fee (flat)',
+% 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\', \'$%%%charge%%%\', \'%%%late_taxclass%%%\' );',
+% 'html' =>
+% 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'.
+% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">'.
+% $late_taxclass,
+% 'weight' => 10,
+% },
+% 'fee_percent' => {
+% 'name' => 'Late fee (percentage)',
+% 'code' => '$cust_main->charge( sprintf(\'%.2f\', $cust_bill->owed * %%%percent%%% / 100 ), \'%%%percent_reason%%%\', \'%%%percent%%% percent\', \'%%%late_percent_taxclass%%%\' );',
+% 'html' =>
+% 'Percent <INPUT TYPE="text" SIZE="2" NAME="percent" VALUE="%%%percent%%%">%'.
+% '<BR>Reason <INPUT TYPE="text" NAME="percent_reason" VALUE="%%%percent_reason%%%">'.
+% $late_percent_taxclass,
+% 'weight' => 10,
+% },
+% 'suspend' => {
+% 'name' => 'Suspend',
+% 'code' => '$cust_main->suspend(reason => %%%sreason%%%, %%%honor_dundate%%% );',
+% 'html' => sub { &honor_dundate('honor_dundate', @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-if-balance' => {
+% 'name' => 'Suspend if balance (this invoice and previous) over',
+% 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%%, reason => %%%sreason%%%, %%%balance_honor_dundate%%% );',
+% 'html' => sub { " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%"> '. &honor_dundate('balance_honor_dundate', @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-if-pkgpart' => {
+% 'name' => 'Suspend packages',
+% 'code' => '$cust_main->suspend_if_pkgpart({pkgparts => [%%%if_pkgpart%%%,], reason => %%%sreason%%%, %%%if_pkgpart_honor_dundate%%% });',
+% 'html' => sub { &select_pkgpart('if_pkgpart', @_). &honor_dundate('if_pkgpart_honor_dundate', @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-unless-pkgpart' => {
+% 'name' => 'Suspend packages except',
+% 'code' => '$cust_main->suspend_unless_pkgpart({unless_pkgpart => [%%%unless_pkgpart%%%], reason => %%%sreason%%%, %%%unless_pkgpart_honor_dundate%%% });',
+% 'html' => sub { &select_pkgpart('unless_pkgpart', @_). &honor_dundate('unless_pkgpart_honor_dundate' => @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'cancel' => {
+% 'name' => 'Cancel',
+% 'code' => '$cust_main->cancel(reason => %%%creason%%%);',
+% 'weight' => 80, #10,
+% 'reason' => 'C',
+% },
+%
+% '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' => 90, #30,
+% },
+%
+% 'credit' => {
+% 'name' => "Create and apply a credit for the customer's balance (i.e. write off as bad debt)",
+% 'code' => '$cust_main->credit( $cust_main->balance, \'%%%credit_reason%%%\' );',
+% 'html' => '<INPUT TYPE="text" NAME="credit_reason" VALUE="%%%credit_reason%%%">',
+% '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 or check to a pending batch',
+% 'code' => '$cust_bill->batch_card(%options);',
+% 'weight' => 40,
+% },
+%
+%
+% #'retriable' => {
+% # 'name' => 'Mark batched card event as retriable',
+% # 'code' => '$cust_pay_batch->retriable();',
+% # 'weight' => 60,
+% #},
+%
+% 'send' => {
+% 'name' => 'Send invoice (email/print/fax)',
+% 'code' => '$cust_bill->send();',
+% 'weight' => 50,
+% },
+%
+% 'send_email' => {
+% 'name' => 'Send invoice (email only)',
+% 'code' => '$cust_bill->email();',
+% 'weight' => 50,
+% },
+%
+% 'send_alternate' => {
+% 'name' => 'Send invoice (email/print/fax) with alternate template',
+% 'code' => '$cust_bill->send(\'%%%templatename%%%\');',
+% 'html' =>
+% '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">',
+% 'weight' => 50,
+% },
+%
+% 'send_if_newest' => {
+% 'name' => 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)',
+% 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');',
+% 'html' =>
+% '<INPUT TYPE="text" NAME="if_newest_templatename" VALUE="%%%if_newest_templatename%%%">',
+% 'weight' => 50,
+% },
+%
+% 'send_agent' => {
+% 'name' => 'Send invoice (email/print/fax) ',
+% 'code' => '$cust_bill->send( \'%%%agent_templatename%%%\',
+% [ %%%agentnum%%% ],
+% \'%%%agent_invoice_from%%%\',
+% %%%agent_balanceover%%%
+% );',
+% 'html' => sub {
+% '<TABLE BORDER=0>
+% <TR>
+% <TD ALIGN="right">only for agent(s) </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>
+% <TR>
+% <TD ALIGN="right">if balance (this invoice and previous) over
+% </TD>
+% <TD>
+% '. $money_char. '<INPUT TYPE="text" SIZE="7" NAME="agent_balanceover" VALUE="%%%agent_balanceover%%%">
+% </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%%%\',
+% \'format\' => \'%%%ftpformat%%%\',
+% );',
+% 'html' =>
+% '<TABLE BORDER=0>'.
+% '<TR><TD ALIGN="right">Format ("default" or "billco"): </TD>'.
+% '<TD>'.
+% '<!--'.
+% '<SELECT NAME="ftpformat">'.
+% '<OPTION VALUE="default">Default'.
+% '<OPTION VALUE="billco">Billco'.
+% '</SELECT>'.
+% '-->'.
+% '<INPUT TYPE="text" NAME="ftpformat" VALUE="%%%ftpformat%%%">'.
+% '</TD></TR>'.
+% '<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,
+% },
+%
+% 'spool_csv' => {
+% 'name' => 'Spool CSV invoice data',
+% 'code' => '$cust_bill->spool_csv(
+% \'format\' => \'%%%spoolformat%%%\',
+% \'dest\' => \'%%%spooldest%%%\',
+% \'balanceover\' => \'%%%spoolbalanceover%%%\',
+% \'agent_spools\' => \'%%%spoolagent_spools%%%\',
+% );',
+% 'html' => sub {
+% my $plandata = shift;
+%
+% my $html =
+% '<TABLE BORDER=0>'.
+% '<TR><TD ALIGN="right">Format: </TD>'.
+% '<TD>'.
+% '<SELECT NAME="spoolformat">';
+%
+% foreach my $option (qw( default billco )) {
+% $html .= qq(<OPTION VALUE="$option");
+% $html .= ' SELECTED' if $option eq $plandata->{'spoolformat'};
+% $html .= ">\u$option";
+% }
+%
+% $html .=
+% '</SELECT>'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">For destination: </TD>'.
+% '<TD>'.
+% '<SELECT NAME="spooldest">';
+%
+% tie my %dest, 'Tie::IxHash',
+% '' => '(all)',
+% 'POST' => 'Postal Mail',
+% 'EMAIL' => 'Email',
+% 'FAX' => 'Fax',
+% ;
+%
+% foreach my $dest (keys %dest) {
+% $html .= qq(<OPTION VALUE="$dest");
+% $html .= ' SELECTED' if $dest eq $plandata->{'spooldest'};
+% $html .= '>'. $dest{$dest};
+% }
+%
+% $html .=
+% '</SELECT>'.
+% '</TD></TR>'.
+%
+% '<TR>'.
+% '<TD ALIGN="right">if balance (this invoice and previous) over </TD>'.
+% '<TD>'.
+% "$money_char ".
+% '<INPUT TYPE="text" SIZE="7" NAME="spoolbalanceover" VALUE="%%%spoolbalanceover%%%">'.
+% '</TD>'.
+% '<TR><TD ALIGN="right">Individual per-agent spools? </TD>'.
+% '<TD><INPUT TYPE="checkbox" NAME="spoolagent_spools" VALUE="1" '.
+% ( $plandata->{'spoolagent_spools'} ? 'CHECKED' : '' ).
+% '>'.
+% '</TD></TR>'.
+% '</TABLE>';
+%
+% $html;
+% },
+% '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_and_credits; "";',
+% 'weight' => 70,
+% },
+%
+%;
+%
+<SCRIPT TYPE="text/javascript">var myreasons = new Array();</SCRIPT>
+%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}/;
+% }
+%
+<SCRIPT TYPE="text/javascript">myreasons.push('<% $events{$event}{reason} %>');
+</SCRIPT>
+% if ($event eq $part_bill_event->plan){
+% $currentreasonclass=$events{$event}{reason};
+% }
+% 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!onClick="showhide_table()" !;
+% 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 qq!<HR WIDTH="90%">!;
+%}
+%
+% if ($currentreasonclass eq 'C'){
+% if ($cgi->param('creason') =~ /^(-?\d+)$/){
+% $creason = $1;
+% }else{
+% $creason = $part_bill_event->reason;
+% }
+% if ($cgi->param('newcreasonT') =~ /^(\d+)$/){
+% $newcreasonT = $1;
+% }
+% if ($cgi->param('newcreason') =~ /^([\w\s]+)$/){
+% $newcreason = $1;
+% }
+% }elsif ($currentreasonclass eq 'S'){
+% if ($cgi->param('sreason') =~ /^(-?\d+)$/){
+% $sreason = $1;
+% }else{
+% $sreason = $part_bill_event->reason;
+% }
+% if ($cgi->param('newsreasonT') =~ /^(\d+)$/){
+% $newsreasonT = $1;
+% }
+% if ($cgi->param('newsreason') =~ /^([\w\s]+)$/){
+% $newsreason = $1;
+% }
+% }
+%
+
+</TD></TR>
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ function showhide_table()
+ {
+ for(i=0;i<document.editEvent.plan_weight_eventcode.length;i++){
+ if (document.editEvent.plan_weight_eventcode[i].checked == true){
+ currentevent=i;
+ }
+ }
+ if(myreasons[currentevent] == 'C'){
+ document.getElementById('Ctable').style.display = 'inline';
+ document.getElementById('Stable').style.display = 'none';
+ }else if(myreasons[currentevent] == 'S'){
+ document.getElementById('Ctable').style.display = 'none';
+ document.getElementById('Stable').style.display = 'inline';
+ }else{
+ document.getElementById('Ctable').style.display = 'none';
+ document.getElementById('Stable').style.display = 'none';
+ }
+ }
+</SCRIPT>
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%">
+<TR><TD>
+<TABLE BORDER=0 id="Ctable" style="display:<% $currentreasonclass eq 'C' ? 'inline' : 'none' %>">
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'creason',
+ 'reason_class' => 'C',
+ 'curr_value' => $creason,
+ 'init_type' => $newcreasonT,
+ 'init_newreason' => $newcreason
+ )
+%>
+</TABLE>
+</TR></TD>
+</TABLE>
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%">
+<TR><TD>
+<TABLE BORDER=0 id="Stable" style="display:<% $currentreasonclass eq 'S' ? 'inline' : 'none' %>">
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'sreason',
+ 'reason_class' => 'S',
+ 'curr_value' => $sreason,
+ 'init_type' => $newsreasonT,
+ 'init_newreason' => $newsreason
+ )
+%>
+</TABLE>
+</TR></TD>
+</TABLE>
+
+%
+%print qq!<INPUT TYPE="submit" VALUE="!,
+% $hashref->{eventpart} ? "Apply changes" : "Add invoice event",
+% qq!">!;
+%
+
+
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) {
+ $cgi->param('eventpart', $1);
+} else {
+ $cgi->param('eventpart', '');
+}
+
+my ($creason, $newcreasonT, $newcreason);
+my ($sreason, $newsreasonT, $newsreason);
+
+my ($query) = $cgi->keywords;
+my $action = '';
+my $part_bill_event = '';
+my $currentreasonclass = '';
+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;
+
+</%init>
diff --git a/httemplate/edit/part_device.html b/httemplate/edit/part_device.html
new file mode 100644
index 0000000..146070f
--- /dev/null
+++ b/httemplate/edit/part_device.html
@@ -0,0 +1,45 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Phone device type',
+ 'table' => 'part_device',
+ 'labels' => {
+ 'devicepart' => 'Part number',
+ 'devicename' => 'Device name',
+ },
+ 'viewall_dir' => 'browse',
+ 'html_bottom' => $html_bottom_sub,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $extra_sql =
+ join( ' OR ', map { "exporttype = '$_'" }
+ keys %{FS::part_export::export_info('part_device')}
+ );
+$extra_sql = $extra_sql ? " WHERE ( $extra_sql ) " : " WHERE 0 = 1 ";
+
+my $html_bottom_sub = sub {
+ my $part_device = shift;
+
+ '<BR>'.
+ '<FONT SIZE="+1">Exports</FONT><BR>'.
+
+ '<TABLE BGCOLOR="#cccccc" WIDTH=100%>'.
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table.html',
+ 'source_obj' => $part_device,
+ 'link_table' => 'export_device',
+ 'target_table' => 'part_export',
+ 'extra_sql' => $extra_sql,
+ 'name_callback' => sub { my $o = shift;
+ $o->exporttype. ' to '. $o->machine;
+ },
+ ).
+ '</TD></TR></TABLE>';
+
+};
+
+</%init>
+
diff --git a/httemplate/edit/part_event.html b/httemplate/edit/part_event.html
new file mode 100644
index 0000000..6a53222
--- /dev/null
+++ b/httemplate/edit/part_event.html
@@ -0,0 +1,679 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Billing event definition',
+ 'table' => 'part_event',
+ 'fields' => [
+ 'event',
+ { field => 'eventtable',
+ type => 'select',
+ options => [ FS::part_event->eventtables ],
+ labels => $eventtable_labels,
+ onchange => 'eventtable_changed',
+ },
+ { field => 'agentnum',
+ type => 'select-agent',
+ disable_empty => $disable_empty_agent,
+ },
+ { field => 'check_freq',
+ type => 'select',
+ options => [ '1d', '1m' ],
+ labels => $check_freq_labels,
+ },
+ { field => 'disabled',
+ type => 'checkbox',
+ value => 'Y',
+ },
+ { type => 'title',
+ value => 'Event Conditions',
+ },
+ { field => 'conditionname',
+ type => 'selectlayers',
+ options => [ keys %all_conditions ],
+ labels => \%condition_labels,
+ onchange => 'condition_changed(what);',
+ layer_fields => \%condition_fields,
+ layer_values_callback => $condition_layer_values,
+ html_between => n_a('action'),
+ m2name_table => 'part_event_condition',
+ m2name_namecol => 'conditionname',
+ m2_label => 'Condition',
+ m2_new_default => \@implicit_condition_objs,
+ m2_error_callback => $condition_error_callback,
+ m2_remove_warnings => \%condition_remove_warnings,
+ m2_new_js => 'condition_repop',
+ m2_remove_js => 'condition_add',
+ },
+ { type => 'title',
+ value => 'Event Action',
+ },
+ { field => 'action',
+ type => 'selectlayers',
+ options => [ keys %all_actions ],
+ labels => \%action_labels,
+ onchange => 'action_changed(what);',
+ layer_fields => \%action_fields,
+ layer_values_callback => $action_layer_values,
+ html_between => n_a('action'),
+ },
+
+ ],
+ 'labels' => {
+ 'eventpart' => 'Event',
+ 'event' => 'Event name',
+ 'eventtable' => 'Type',
+ 'agentnum' => 'Agent',
+ 'check_freq' => 'Check frequency',
+ 'disabled' => 'Disable event',
+
+ 'conditionname' => 'Add&nbsp;new&nbsp;condition',
+ #'weight',
+ 'action' => 'Action',
+ },
+ 'viewall_dir' => 'browse',
+ 'new_callback' => sub { #start empty for new events only
+ my( $cgi, $object, $fields_listref ) = @_;
+ unshift @{ $fields_listref->[1]{'options'} }, '';
+ },
+ 'error_callback' => $error_callback,
+
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global billing events',
+ )
+%>
+<SCRIPT TYPE="text/javascript">
+
+ window.onload = function () { eventtable_changed(document.getElementById('eventtable')) };
+ var notonload = 0;
+
+ function eventtable_changed(what) {
+
+% if ( $JS_DEBUG ) {
+ alert('eventtable_changed called on ' + what );
+% }
+
+ var eventtable = what.options[what.selectedIndex].value;
+% if ( $JS_DEBUG ) {
+ alert ("eventtable: " + eventtable);
+% }
+ var eventdesc = what.options[what.selectedIndex].text;
+
+ //remove the ** Select type **
+ if ( what.options[0].value == '' && notonload++ > 0 ) {
+ what.options[0] = null;
+ }
+
+ ////
+ // XXX gray out conditions that can't apply (in addition to the warning)?
+ ////
+
+ ////
+ // update condition selects
+ ////
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+% if ( $JS_DEBUG ) {
+ alert('updating ' + cond_id);
+% }
+
+ // save off the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+ var cond_desc = cond_select.options[cond_select.selectedIndex].text;
+
+ var seen_condition = condition_repop(cond_select);
+
+ var warning = document.getElementById(cond_id + '_warning');
+% if ( $JS_DEBUG ) {
+ alert('turning off warning; setting style.display of '+ cond_id +
+ '_warning (' + warning + ') to none');
+% }
+ warning.style.display = 'none';
+
+ if ( ! seen_condition && conditionname != '' ) {
+ // add the current (not valid) condition back
+ opt(cond_select, conditionname, cond_desc, true );
+ if ( ! condition_is_implicit(conditionname) ) {
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+ // turn on a warning and gray out the condition row
+% if ( $JS_DEBUG ) {
+ alert('turning on warning; setting style.display of '+ cond_id +
+ '_warning (' + warning + ') to ""');
+% }
+ warning.innerHTML = 'Not applicable to ' + eventdesc + ' events';
+ warning.style.display = '';
+ } else {
+ if ( ! condition_in_eventtable(conditionname) ) {
+% if ( $JS_DEBUG ) {
+ alert(conditionname + " not in " + eventtable + "; disabling");
+% }
+ cond_select.parentNode.parentNode.style.display = 'none';
+ cond_select.disabled = 'disabled';
+ } else {
+% if ( $JS_DEBUG ) {
+ alert(conditionname + " implicit for " + eventtable + "; enabling");
+% }
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+ }
+ }
+ }
+
+ }
+
+
+ ////
+ // update action select
+ ////
+
+ // save off the current value first!!
+ var action = what.form.action.options[what.form.action.selectedIndex].value;
+ var a_desc = what.form.action.options[what.form.action.selectedIndex].text;
+ var seen_action = false;
+
+ // blank the current action select
+ for ( var i = what.form.action.length; i >= 0; i-- )
+ what.form.action.options[i] = null;
+
+ if ( action == '' ) {
+ opt(what.form.action, action, a_desc, true );
+ }
+
+ // repopulate it
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %actions, 'Tie::IxHash', FS::part_event->actions($eventtable);
+% #use Data::Dumper; warn Dumper(%actions);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $action ( keys %actions ) {
+% ( my $description = $actions{$action}->{'description'} ) =~ s/'/\\'/g;
+
+ var sel = false;
+ if ( action == '<% $action %>' ) {
+ seen_action = true;
+ sel = true;
+ }
+ opt( what.form.action, '<% $action %>', '<% $description %>', sel );
+% }
+
+ }
+
+% }
+
+ // by default, turn off warnings and enable the submit button
+ var warning = document.getElementById('action_warning');
+ warning.style.display = 'none';
+ var submit_button = document.getElementById('submit');
+ submit_button.disabled = '';
+
+ if ( ! seen_action && action != '' ) {
+ // add the current (not valid) action back
+ opt( what.form.action, action, a_desc, true );
+ // turn on a warning and disable the submit button
+ //warning.innerHTML = a_desc + ' event not available as a ' +
+ warning.innerHTML = 'Not available as a ' + eventdesc + ' action';
+ warning.style.display = '';
+ submit_button.disabled = 'disabled';
+ }
+
+ }
+
+ function opt(what,value,text,selected) {
+ var optionName = new Option(text, value, false, selected);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function action_changed(what) {
+ // remove '** Select new **'
+ if ( what.options[0].value == '' ) {
+ what.options[0] = null;
+ }
+ // remove the warning, remove the invalid action, enable the submit button
+ var warning = document.getElementById('action_warning');
+ if ( warning.style.display == '' ) {
+ warning.style.display = 'none';
+ what.options[what.length-1] = null;
+ document.getElementById('submit').disabled = '';
+ }
+ }
+
+ function condition_changed(what) {
+ // remove '** Select new **'
+ if ( what.options[0].value == '' ) {
+ what.options[0] = null;
+ }
+
+ var previousValue = what.getAttribute('previousValue');
+ var previousText = what.getAttribute('previousText');
+ var value = what.options[what.selectedIndex].value;
+ var text = what.options[what.selectedIndex].text;
+
+% foreach my $value ( keys %condition_remove_warnings ) {
+ if ( previousValue == '<% $value %>' ) {
+ if ( !confirm( <% $condition_remove_warnings{$value} |js_string %> ) ) {
+ for ( var i=0; i < what.length; i++ ) {
+ if ( what.options[i].value == previousValue ) {
+ what.selectedIndex = i;
+ }
+ }
+ return false;
+ }
+ }
+% }
+
+ //alert(previous + ' changed to ' + value);
+
+ var field_regex = /(\d+)$/;
+ var match = field_regex.exec(what.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+
+ //add the previous condition *back* to all the other selects...
+ condition_add(previousValue, previousText, match[1]);
+
+ what.setAttribute('previousValue', value);
+ what.setAttribute('previousText', text);
+
+ // remove the new condition from all other selects
+ condition_remove(value, match[1]);
+
+ }
+
+ function condition_avail(check_cond, curnum) {
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ //alert("checking " + cond_id + " (" + cond_select.disabled + ")");
+
+ if ( cond_select.disabled ) continue;
+
+ // the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+
+ if ( check_cond == conditionname ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ function condition_remove(remove_cond, curnum) {
+
+ if ( remove_cond.length == 0 ) return;
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ //for ( var i = cond_select.length; i >= 0; i-- ) {
+ for ( var i=0; i < cond_select.length; i++ ) {
+ if ( cond_select.options[i].value == remove_cond ) {
+ cond_select.options[i] = null;
+ }
+ }
+
+ }
+
+ }
+
+ function condition_add(add_condname, add_conddesc, curnum) {
+
+ if ( add_condname.length == 0 ) return;
+
+ var in_eventtable = condition_in_eventtable(add_condname);
+
+ if ( ! in_eventtable ) return;
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ if ( cond_select.disabled ) continue;
+
+ //alert("adding " + add_condname + " to " + cond_id);
+
+ opt(cond_select, add_condname, add_conddesc, false );
+
+ cond_select.parentNode.parentNode.style.display = '';
+
+ }
+
+ }
+
+ function condition_in_eventtable(condname) {
+
+ var eventtable_el = document.getElementById('eventtable');
+ var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
+
+ var in_eventtable = false;
+
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %conditions, 'Tie::IxHash',
+% FS::part_event_condition->conditions($eventtable);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $conditionname ( keys %conditions ) {
+
+ if ( condname == '<% $conditionname %>' ) {
+ in_eventtable = true;
+ }
+
+% }
+
+ }
+
+% }
+
+ return in_eventtable;
+
+ }
+
+ function condition_is_implicit(condname) {
+
+ if ( true <% @implicit_conditions
+ ? ( ' && '. join(' && ', map { "condname != '$_'" }
+ @implicit_conditions
+ )
+ )
+ : ''
+ %> ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function condition_repop(cond_select) {
+
+ var eventtable_el = document.getElementById('eventtable');
+ var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
+
+ // save off the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+ var cond_desc = cond_select.options[cond_select.selectedIndex].text;
+ var seen_condition = false;
+
+ //skip deleted conditions
+ if ( cond_select.disabled && conditionname != '' && ! condition_is_implicit(conditionname) ) {
+ return false;
+ }
+
+ var field_regex = /(\d+)$/;
+ var match = field_regex.exec(cond_select.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+ var cnum = match[1];
+
+ // blank the current condition select
+ for ( var i = cond_select.length; i >= 0; i-- )
+ cond_select.options[i] = null;
+
+ if ( conditionname == '' ) {
+ opt(cond_select, conditionname, cond_desc, true );
+ }
+
+ // repopulate it
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %conditions, 'Tie::IxHash',
+% FS::part_event_condition->conditions($eventtable);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $conditionname ( keys %conditions ) {
+% my $description = $conditions{$conditionname}->{'description'};
+% $description =~ s/'/\\'/g;
+
+ var sel = false;
+ if ( conditionname == '<% $conditionname %>' ) {
+ seen_condition = true;
+ sel = true;
+ }
+
+ if ( condition_avail("<% $conditionname %>", cnum) ) {
+ opt(cond_select, '<% $conditionname %>', '<% $description %>', sel);
+ }
+
+% }
+
+ }
+
+% }
+
+ if ( cond_select.length > 1 || cond_select.length == 1 && cond_select.options[0].value.length > 0 ) {
+
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+
+ } else {
+ cond_select.parentNode.parentNode.style.display = 'none';
+ cond_select.disabled = 'disabled';
+ }
+
+ return seen_condition;
+
+ }
+
+</SCRIPT>
+<%once>
+
+#misc (eventtable, check_freq)
+
+my $eventtable_labels = FS::part_event->eventtable_labels;
+$eventtable_labels->{''} = '** Select type **';
+
+my $check_freq_labels = FS::part_event->check_freq_labels;
+
+#conditions
+
+tie my %all_conditions, 'Tie::IxHash',
+ '' => { 'description' => '*** Select new condition ***', },
+ FS::part_event_condition->conditions();
+
+my %condition_labels = map { $_ => $all_conditions{$_}->{'description'} }
+ keys %all_conditions;
+
+#my %condition_fields = map { $_ => $all_conditions{$_}->{option_fields} }
+# keys %all_conditions;
+my %condition_fields = map { my $c = $_;
+ tie my %opts, 'Tie::IxHash',
+ @{ $all_conditions{$c}->{'option_fields'} || []};
+ %opts = ( map { ( "$c.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $c => [ %opts ] );
+ }
+ keys %all_conditions;
+
+my @implicit_conditions = sort { $all_conditions{$a}->{'implicit_flag'} <=>
+ $all_conditions{$b}->{'implicit_flag'}
+ }
+ grep { $all_conditions{$_}->{'implicit_flag'} }
+ keys %all_conditions;
+
+my @implicit_condition_objs = map {
+ new FS::part_event_condition {
+ 'conditionname' => $_,
+ };
+ }
+ @implicit_conditions;
+
+my %condition_remove_warnings =
+ map { ( $_ => $all_conditions{$_}->{'remove_warning'} ); }
+ grep { $all_conditions{$_}->{'remove_warning'} }
+ keys %all_conditions;
+
+#actions
+
+tie my %all_actions, 'Tie::IxHash',
+ '' => { 'description' => '*** Select event action ***', },
+ FS::part_event->actions();
+
+my %action_labels = map { $_ => $all_actions{$_}->{'description'} }
+ keys %all_actions;
+
+#my %action_fields = map { $_ => $all_actions{$_}->{option_fields} }
+# keys %all_actions;
+my %action_fields = map { my $action = $_;
+ tie my %opts, 'Tie::IxHash',
+ @{ $all_actions{$action}->{option_fields} || [] };
+ %opts = ( map { ( "$action.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $action => [ %opts ] );
+ }
+ keys %all_actions;
+
+#subs
+
+sub n_a {
+ my $t = shift;
+
+ return sub {
+ my $field = shift;
+ qq( <FONT ID="${field}_warning" STYLE="display:none" COLOR="#FF0000">).
+ "Party Party Join us Join us".
+ '</FONT>';
+ };
+}
+
+my $action_layer_values = sub {
+ my( $cgi, $part_event ) = @_;
+ my $action = $cgi->param('action') || $part_event->action;
+ return {} unless $action;
+ scalar( #force hashref
+ {
+ #map { $_ => { $part_event->options } }
+ # keys %action_fields
+ map { my $action = $_;
+ my %fields = @{ $action_fields{$action} };
+ my %obj_opts = $part_event->options;
+ %obj_opts = map { ( "$action.$_" => $obj_opts{$_} ); }
+ keys %obj_opts;
+ my %opts =
+ map { #false laziness w/process/part_event.html
+ my $option = $_;
+ my $value = scalar($cgi->param($_)) || $obj_opts{$_};
+
+ if ( $option =~ /^(.*)\.reasonnum$/ && $value == -1 ) {
+ $value = {
+ 'typenum' => scalar( $cgi->param( "new${option}T" ) ),
+ 'reason' => scalar( $cgi->param( "new${option}" ) ),
+ };
+ }
+
+ ( $option => $value );
+
+ }
+ keys %fields;
+ ( $action => \%opts );
+ }
+ keys %action_fields
+ }
+ );
+};
+
+tie my %cgi_conditions, 'Tie::IxHash';
+
+my $error_callback = sub {
+ my( $cgi, $object, $fields_listref ) = @_;
+
+ my @cond_params = grep /^conditionname\d+$/, $cgi->param;
+
+ %cgi_conditions = map {
+ my $param = $_;
+ my $conditionname = $cgi->param($param);
+ $conditionname => {
+ map {
+
+ my $cgi_key = $_;
+ $cgi_key =~ /^$param\.$conditionname\.(.*)$/ or die 'wtf!';
+ my $key = $1;
+ #my $value = $cgi->param($_);
+
+ #my $info = $all_conditions->{$conditionname}
+ my %cond_opts =
+ @{ $all_conditions{$conditionname}->{'option_fields'} || []};
+ my $info = $cond_opts{$key};
+
+ my $value;
+ #false laziness w/process/part_event.html
+ if ( $info->{'type'} =~ /^(select|checkbox)-?multiple$/
+ or $info->{'type'} =~ /^select/ && $info->{'multiple'} ) {
+ $value = { map { $_ => 1 } $cgi->param($cgi_key) };
+ } elsif ( $info->{'type'} eq 'freq' ) {
+ $value = $cgi->param($cgi_key). $cgi->param($cgi_key.'_units');
+ } else {
+ $value = $cgi->param($cgi_key);
+ }
+
+ $key => $value;
+
+ } grep /^$param\.$conditionname\./, $cgi->param
+ };
+ } grep $cgi->param($_), grep /^conditionname\d+$/, $cgi->param;
+
+};
+
+my $condition_error_callback = sub {
+ map {
+ new FS::part_event_condition { 'conditionname' => $_, };
+ } keys %cgi_conditions;
+};
+
+my $condition_layer_values = sub {
+ #m2_table option causes this to be
+ # part_event_condition instead of part_event
+ my ( $cgi, $part_event_condition, $switches ) = @_;
+ scalar( #force hashref
+ {
+ #map { $_ => { $part_event_condition->options } }
+ # keys %condition_fields
+ map { my $conditionname = $_;
+ my %opts = $switches->{'mode'} eq 'error'
+ ? %{ $cgi_conditions{$conditionname} || {} }
+ : $part_event_condition->options;
+ %opts = (
+ map { ( "$conditionname.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $conditionname => \%opts );
+ }
+ keys %condition_fields
+ }
+ );
+};
+
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit billing events')
+ || $curuser->access_right('Edit global billing events');
+
+my $disable_empty_agent= ! $curuser->access_right('Edit global billing events');
+
+%cgi_conditions = ();
+my $use_cgi_conditions = 0;
+
+my $JS_DEBUG = 0;
+
+</%init>
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
new file mode 100644
index 0000000..8b697e1
--- /dev/null
+++ b/httemplate/edit/part_export.cgi
@@ -0,0 +1,138 @@
+<% include('/elements/header.html', "$action Export", '', ' onLoad="visualize()"') %>
+
+<% include('/elements/error.html') %>
+
+<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 %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#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' ) {
+ my $size = defined($optinfo->{size}) ? " SIZE=" . $optinfo->{size} : '';
+ my $multi = defined($optinfo->{multi}) ? ' MULTIPLE' : '';
+ $html .= qq!<SELECT NAME="$option"$multi$size>!;
+ my @values = split '\s+', $value if $multi;
+ my @options;
+ if (defined($optinfo->{option_values})) {
+ my $valsub = $optinfo->{option_values};
+ @options = &$valsub();
+ } elsif (defined($optinfo->{options})) {
+ @options = @{$optinfo->{options}};
+ }
+ foreach my $select_option ( @options ) {
+ #if ( ref($select_option) ) {
+ #} else {
+ my $selected = ($multi ? grep {$_ eq $select_option} @values : $select_option eq $value ) ? ' SELECTED' : '';
+ my $label = $select_option;
+ if (defined($optinfo->{option_label})) {
+ my $labelsub = $optinfo->{option_label};
+ $label = &$labelsub($select_option);
+ }
+ $html .= qq!<OPTION VALUE="$select_option"$selected>!.
+ qq!$label</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;
+ },
+);
+
+</%init>
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
new file mode 100755
index 0000000..c8b7ecb
--- /dev/null
+++ b/httemplate/edit/part_pkg.cgi
@@ -0,0 +1,735 @@
+<% include( 'elements/edit.html',
+ 'post_url' => popurl(1).'process/part_pkg.cgi',
+ 'name' => "Package definition",
+ 'table' => 'part_pkg',
+
+ 'agent_virt' => 1,
+ 'agent_null_right' => $edit_global,
+ 'agent_clone_extra_sql' => $agent_clone_extra_sql,
+ #'viewall_dir' => 'browse',
+ 'viewall_url' => $p.'browse/part_pkg.cgi',
+ 'html_init' => include('/elements/init_overlib.html').
+ $javascript,
+ 'html_bottom' => $html_bottom,
+ 'body_etc' =>
+ 'onLoad="agent_changed(document.edit_topform.agentnum)"',
+
+ 'begin_callback' => $begin_callback,
+ 'end_callback' => $end_callback,
+ 'new_hashref_callback' => $new_hashref_callback,
+ 'new_object_callback' => $new_object_callback,
+ 'new_callback' => $new_callback,
+ 'clone_callback' => $clone_callback,
+ 'edit_callback' => $edit_callback,
+ 'error_callback' => $error_callback,
+ 'field_callback' => $field_callback,
+
+ 'labels' => {
+ 'pkgpart' => 'Package Definition',
+ 'pkg' => 'Package (customer-visible)',
+ 'comment' => 'Comment (customer-hidden)',
+ 'classnum' => 'Package class',
+ 'addon_classnum' => 'Restrict additional orders to package class',
+ 'promo_code' => 'Promotional code',
+ 'freq' => 'Recurring fee frequency',
+ 'setuptax' => 'Setup fee tax exempt',
+ 'recurtax' => 'Recurring fee tax exempt',
+ 'taxclass' => 'Tax class',
+ 'taxproduct_select'=> 'Tax products',
+ 'plan' => 'Price plan',
+ 'disabled' => 'Disable new orders',
+ 'setup_cost' => 'Setup cost',
+ 'recur_cost' => 'Recur cost',
+ 'pay_weight' => 'Payment weight',
+ 'credit_weight' => 'Credit weight',
+ 'agentnum' => 'Agent',
+ 'setup_fee' => 'Setup fee',
+ 'recur_fee' => 'Recurring fee',
+ 'bill_dst_pkgpart' => 'Include line item(s) from package',
+ 'svc_dst_pkgpart' => 'Include services of package',
+ 'report_option' => 'Report classes',
+ 'fcc_ds0s' => 'Voice-grade eqivalents',
+ },
+
+ 'fields' => [
+ { field=>'clone', type=>'hidden',
+ curr_value_callback =>
+ sub { shift->param('clone') },
+ },
+ { field=>'pkgnum', type=>'hidden',
+ curr_value_callback =>
+ sub { shift->param('pkgnum') },
+ },
+
+ { field=>'custom', type=>'hidden' },
+
+ { type => 'columnstart' },
+
+ { field => 'pkg',
+ type => 'text',
+ size => 40, #32
+ maxlength => 50,
+ },
+ {field=>'comment', type=>'text', size=>40 }, #32
+ { field => 'agentnum',
+ type => 'select-agent',
+ disable_empty => ! $acl_edit_global,
+ empty_label => '(global)',
+ onchange => 'agent_changed',
+ },
+ {field=>'classnum', type=>'select-pkg_class' },
+ ( $conf->exists('pkg-addon_classnum')
+ ? ( { field=>'addon_classnum',
+ type =>'select-pkg_class',
+ }
+ )
+ : ()
+ ),
+ {field=>'disabled', type=>$disabled_type, value=>'Y'},
+
+ { type => 'tablebreak-tr-title',
+ value => 'Pricing', #better name?
+ },
+ { field => 'plan',
+ type => 'selectlayers-select',
+ options => [ keys %plan_labels ],
+ labels => \%plan_labels,
+ },
+ { field => 'setup_fee',
+ type => 'money',
+ },
+ { field => 'freq',
+ type => 'part_pkg_freq',
+ onchange => 'freq_changed',
+ },
+ { field => 'recur_fee',
+ type => 'money',
+ disabled => sub { $recur_disabled },
+ },
+
+ #price plan
+ #setup fee
+ #recurring frequency
+ #recurring fee (auto-disable)
+
+ { type => 'columnnext' },
+
+ {type=>'justtitle', value=>'Taxation' },
+ {field=>'setuptax', type=>'checkbox', value=>'Y'},
+ {field=>'recurtax', type=>'checkbox', value=>'Y'},
+ {field=>'taxclass', type=>'select-taxclass' },
+ { field => 'taxproductnums',
+ type => 'hidden',
+ value => join(',', @taxproductnums),
+ },
+ { field => 'taxproduct_select',
+ type => 'selectlayers',
+ options => [ '(default)', @taxproductnums ],
+ curr_value => '(default)',
+ labels => { ( '(default)' => '(default)' ),
+ map {($_=>$usage_class{$_})}
+ @taxproductnums
+ },
+ layer_fields => \%taxproduct_fields,
+ layer_values_callback => $taxproduct_values,
+ layers_only => !$taxproducts,
+ cell_style => ( !$taxproducts
+ ? 'display:none'
+ : ''
+ ),
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Promotions', #better name?
+ },
+ { field=>'promo_code', type=>'text', size=>15 },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Cost tracking', #better name?
+ },
+ { field=>'setup_cost', type=>'money', },
+ { field=>'recur_cost', type=>'money', },
+
+ { type => 'columnnext' },
+
+ { field => 'agent_type',
+ type => 'select-agent_types',
+ disabled => ! $acl_edit_global,
+ curr_value_callback => sub {
+ my($cgi, $object, $field) = @_;
+ #in the other callbacks..? hmm.
+ \@agent_type;
+ },
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Line-item revenue recogition', #better name?
+ },
+ { field=>'pay_weight', type=>'text', size=>6 },
+ { field=>'credit_weight', type=>'text', size=>6 },
+
+ ( $conf->exists('cust_pkg-show_fcc_voice_grade_equivalent')
+ ? (
+ { type => 'tablebreak-tr-title',
+ value => 'FCC Form 477 information',
+ },
+ { field=>'fcc_ds0s', type=>'text', size=>6 },
+ )
+ : ()
+ ),
+
+
+ { type => 'columnend' },
+
+ { 'type' => $report_option ? 'tablebreak-tr-title'
+ : 'hidden',
+ 'value' => 'Optional report classes',
+ 'field' => 'census_title',
+ },
+ { 'field' => 'report_option',
+ 'type' => $report_option ? 'select-table'
+ : 'hidden',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'multiple' => 1,
+ },
+
+
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Pricing add-ons',
+ 'colspan' => 4,
+ },
+ { 'field' => 'bill_dst_pkgpart',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include line item(s) from package',
+ 'm2m_method' => 'bill_part_pkg_link',
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2_error_callback' =>
+ &{$m2_error_callback_maker}('bill'),
+ 'm2_fields' => [ { 'field' => 'hidden',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ 'curr_value' => '',
+ 'label' => 'Bundle',
+ },
+ ],
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Services',
+ },
+ { type => 'pkg_svc', },
+
+ { 'field' => 'svc_dst_pkgpart',
+ 'label' => 'Also include services from package: ',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include services of package: ',
+ 'm2m_method' => 'svc_part_pkg_link',
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2_error_callback' =>
+ &{$m2_error_callback_maker}('svc'),
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Price plan options',
+ },
+
+ ],
+
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $edit_global = 'Edit global package definitions';
+my $acl_edit = $curuser->access_right('Edit package definitions');
+my $acl_edit_global = $curuser->access_right($edit_global);
+
+my $acl_edit_either = $acl_edit || $acl_edit_global;
+
+my $begin_callback = sub {
+ my( $cgi, $fields, $opt ) = @_;
+ die "access denied"
+ unless $acl_edit_either
+ || ( $cgi->param('pkgnum')
+ && $curuser->access_right('Customize customer package')
+ );
+};
+
+my $disabled_type = $acl_edit_either ? 'checkbox' : 'hidden';
+
+#arg. access rights for cloning are Hard.
+# on the one hand we don't really want cloning (customizing a package) to fail
+# for want of finding the source package in normal usage
+# on the other hand, we don't want people using the clone link to be able to
+# see
+my $agent_clone_extra_sql =
+ ' ( '. FS::part_pkg->curuser_pkgs_sql.
+ " OR ( part_pkg.custom = 'Y' ) ".
+ ' ) ';
+
+my $conf = new FS::Conf;
+my $taxproducts = $conf->exists('enable_taxproducts');
+
+my $sth = dbh->prepare("SELECT COUNT(*) FROM part_pkg_report_option".
+ " WHERE disabled IS NULL OR disabled = '' ")
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $report_option = $sth->fetchrow_arrayref->[0];
+
+#XXX
+# - tr-part_pkg_freq: month_increments_only (from price plans)
+# - test cloning
+# - test errors cloning
+# - test custom pricing
+# - move the selectlayer divs away from lame layer_callback
+
+#my ($query) = $cgi->keywords;
+#
+#my $part_pkg = '';
+
+my @agent_type = ();
+my %tax_override = ();
+
+my %taxproductnums = map { ($_->classnum => 1) }
+ qsearch('usage_class', { 'disabled' => '' });
+my @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
+
+my %options = ();
+my $recur_disabled = 1;
+
+my $error_callback = sub {
+ my($cgi, $object, $fields, $opt ) = @_;
+
+ (@agent_type) = $cgi->param('agent_type');
+
+ $opt->{action} = 'Custom' if $cgi->param('pkgnum');
+
+ $recur_disabled = $cgi->param('freq') ? 0 : 1;
+
+ foreach ($cgi->param) {
+ /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
+ }
+ $tax_override{''} = $cgi->param('tax_override');
+ $tax_override{$_} = $cgi->param('tax_override_$_')
+ foreach(grep { /^tax_override_(\w+)$/ } $cgi->param);
+
+ #some false laziness w/process
+ $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
+ my $plan = $1;
+ my $options = $cgi->param($plan."__OPTIONS");
+ my @options = split(',', $options);
+ %options =
+ map { my $optionname = $_;
+ my $param = $plan."__$optionname";
+ my $value = join(', ', $cgi->param($param));
+ ( $optionname => $value );
+ }
+ @options;
+
+ #$cgi->param($_, $options{$_}) foreach (qw( setup_fee recur_fee ));
+ $object->set($_ => scalar($cgi->param($_)) )
+ foreach (qw( setup_fee recur_fee ));
+
+};
+
+my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
+
+my $new_object_callback = sub {
+ my( $cgi, $hashref, $fields, $opt ) = @_;
+
+ my $part_pkg = FS::part_pkg->new( $hashref );
+ $part_pkg->set($_ => '0')
+ foreach (qw( setup_fee recur_fee ));
+
+ $part_pkg;
+
+};
+
+my $edit_callback = sub {
+ my( $cgi, $object, $fields, $opt ) = @_;
+
+ $recur_disabled = $object->freq ? 0 : 1;
+
+ (@agent_type) =
+ map {$_->typenum} qsearch('type_pkgs', { 'pkgpart' => $object->pkgpart } );
+
+ my @report_option = ();
+ foreach ($object->options) {
+ /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
+ /^report_option_(\d+)$/ && (push @report_option, $1);
+ }
+ foreach ($object->part_pkg_taxoverride) {
+ $taxproductnums{$_->usage_class} = 1
+ if $_->usage_class;
+ }
+
+ $cgi->param('report_option', join(',', @report_option));
+ foreach my $field ( @$fields ) {
+ next unless (
+ ref($field) eq 'HASH' &&
+ $field->{field} &&
+ $field->{field} eq 'report_option'
+ );
+ #$field->{curr_value} = join(',', @report_option);
+ $field->{value} = join(',', @report_option);
+ }
+
+ %options = $object->options;
+
+ $object->set($_ => $object->option($_))
+ foreach (qw( setup_fee recur_fee ));
+
+};
+
+my $new_callback = sub {
+ my( $cgi, $object, $fields ) = @_;
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('agent_defaultpkg') ) {
+ #my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+ @agent_type = map {$_->typenum} qsearch('agent_type',{});
+ }
+
+};
+
+my $clone_callback = sub {
+ my( $cgi, $object, $fields, $opt ) = @_;
+
+ if ( $cgi->param('pkgnum') ) {
+
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cgi->param('pkgnum') } );
+ $object->agentnum( $cust_pkg->cust_main->agentnum );
+
+ $opt->{action} = 'Custom';
+
+ #my $part_pkg = $clone_part_pkg->clone;
+ #this is all clone does anyway
+ $object->custom('Y');
+
+ $object->disabled('Y');
+
+ } else { #not when cloning...
+
+ (@agent_type) =
+ map {$_->typenum} qsearch('type_pkgs',{ 'pkgpart' => $object->pkgpart } );
+
+ }
+
+ %options = $object->options;
+
+ $object->set($_ => $options{$_})
+ foreach (qw( setup_fee recur_fee ));
+
+ $recur_disabled = $object->freq ? 0 : 1;
+};
+
+my $m2_error_callback_maker = sub {
+ my $link_type = shift; #yay closures
+ return sub {
+ my( $cgi, $object ) = @_;
+ map {
+
+ if ( /^${link_type}_dst_pkgpart(\d+)$/ &&
+ ( my $dst = $cgi->param("${link_type}_dst_pkgpart$1") ) )
+ {
+
+ my $hidden = $cgi->param("${link_type}_dst_pkgpart__hidden$1")
+ || '';
+ new FS::part_pkg_link {
+ 'link_type' => $link_type,
+ 'src_pkgpart' => $object->pkgpart,
+ 'dst_pkgpart' => $dst,
+ 'hidden' => $hidden,
+ };
+ } else {
+ ();
+ }
+ }
+ $cgi->param;
+ };
+};
+
+my $javascript = <<'END';
+ <SCRIPT TYPE="text/javascript">
+
+ function freq_changed(what) {
+ var freq = what.options[what.selectedIndex].value;
+
+ if ( freq == '0' ) {
+ what.form.recur_fee.disabled = true;
+ what.form.recur_fee.style.backgroundColor = '#dddddd';
+ } else {
+ what.form.recur_fee.disabled = false;
+ what.form.recur_fee.style.backgroundColor = '#ffffff';
+ }
+
+ }
+
+ function agent_changed(what) {
+
+ var agentnum = what.options[what.selectedIndex].value;
+
+ if ( agentnum == 0 ) {
+ what.form.agent_type.disabled = false;
+ //what.form.agent_type.style.backgroundColor = '#ffffff';
+ what.form.agent_type.style.visibility = '';
+ } else {
+ what.form.agent_type.disabled = true;
+ //what.form.agent_type.style.backgroundColor = '#dddddd';
+ what.form.agent_type.style.visibility = 'hidden';
+ }
+
+ }
+
+ </SCRIPT>
+END
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+
+tie my %plan_labels, 'Tie::IxHash',
+ map { $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
+ keys %plans;
+
+my $html_bottom = sub {
+ my( $object ) = @_;
+
+ #warn join("\n", map { "$_: $options{$_}" } keys %options ). "\n";
+
+ my $layer_callback = sub {
+
+ my $layer = shift;
+ my $html = ntable("#cccccc",2);
+
+ #$html .= '
+ # <TR>
+ # <TD ALIGN="right">Recurring fee frequency </TD>
+ # <TD><SELECT NAME="freq">
+ #';
+ #
+ #my @freq = keys %freq;
+ #@freq = grep { /^\d+$/ } @freq
+ #XXX this bit# # if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
+ #foreach my $freq ( @freq ) {
+ # $html .= qq(<OPTION VALUE="$freq");
+ # $html .= ' SELECTED' if $freq eq $part_pkg->freq;
+ # $html .= ">$freq{$freq}";
+ #}
+
+ #$html .= '</SELECT></TD></TR>';
+
+ my $href = $plans{$layer}->{'fields'};
+ my @fields = exists($plans{$layer}->{'fieldorder'})
+ ? @{$plans{$layer}->{'fieldorder'}}
+ : keys %{ $href };
+
+ foreach my $field ( grep $_ !~ /^(setup|recur)_fee$/, @fields ) {
+
+ if(!exists($href->{$field})) {
+ # shouldn't happen
+ warn "nonexistent part_pkg option: '$field'\n";
+ next;
+ }
+
+ $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
+
+ my $format = sub { shift };
+ $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
+
+ #XXX these should use elements/ fields... (or this whole thing should
+ #just use layer_fields instead of layer_callback)
+
+ if ( ! exists($href->{$field}{'type'}) ) {
+
+ $html .= qq!<INPUT TYPE="text" NAME="${layer}__$field" VALUE="!.
+ ( exists($options{$field})
+ ? &$format($options{$field})
+ : $href->{$field}{'default'} ).
+ qq!">!;
+
+ } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
+
+ $html .= qq!<INPUT TYPE="checkbox" NAME="${layer}__$field" VALUE=1 !.
+ ( exists($options{$field}) && $options{$field}
+ ? ' CHECKED'
+ : ''
+ ). '>';
+
+ } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
+
+ $html .= '<SELECT';
+ $html .= ' MULTIPLE'
+ if $href->{$field}{'type'} eq 'select_multiple';
+ $html .= qq! NAME="${layer}__$field">!;
+
+ if ( $href->{$field}{'select_table'} ) {
+ 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"!.
+ ( $options{$field} =~ /(^|, *)$value *(,|$)/ #?
+ ? ' SELECTED'
+ : ''
+ ).
+ '>'. $record->getfield($href->{$field}{'select_label'});
+ }
+ } elsif ( $href->{$field}{'select_options'} ) {
+ foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
+ my $label = $href->{$field}{'select_options'}{$key};
+ $html .= qq!<OPTION VALUE="$key"!.
+ ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
+ ? ' SELECTED'
+ : ''
+ ).
+ '>'. $label;
+ }
+
+ } else {
+ $html .= '<font color="#ff0000">warning: '.
+ "don't know how to retreive options for $field select field".
+ '</font>';
+ }
+ $html .= '</SELECT>';
+
+ } elsif ( $href->{$field}{'type'} eq 'radio' ) {
+
+ my $radio =
+ qq!<INPUT TYPE="radio" NAME="${layer}__$field"!;
+
+ foreach my $key ( keys %{ $href->{$field}{'options'} } ) {
+ my $label = $href->{$field}{'options'}{$key};
+ $html .= qq!$radio VALUE="$key"!.
+ ( $options{$field} =~ /(^|, *)$key *(,|$)/ #?
+ ? ' CHECKED'
+ : ''
+ ).
+ "> $label<BR>";
+ }
+
+ }
+
+ $html .= '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html .= qq(<INPUT TYPE="hidden" NAME="${layer}__OPTIONS" VALUE=").
+ join(',', keys %{ $href } ). '">';
+
+ $html;
+
+ };
+
+ my %selectlayers = (
+ field => 'plan',
+ options => [ keys %plan_labels ],
+ labels => \%plan_labels,
+ curr_value => $object->plan,
+ layer_callback => $layer_callback,
+ );
+
+ my $return =
+ include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ).
+ '<SCRIPT TYPE="text/javascript">'.
+ include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 );
+
+ $return .=
+ "taxproduct_selectchanged(document.getElementById('taxproduct_select'));\n"
+ if $taxproducts;
+
+ $return .= '</SCRIPT>';
+
+ $return;
+
+};
+
+my %usage_class = map { ($_->classnum => $_->classname) }
+ qsearch('usage_class', {});
+$usage_class{setup} = 'Setup';
+$usage_class{recur} = 'Recurring';
+
+my %taxproduct_fields = ();
+my $end_callback = sub {
+ my( $cgi, $object, $fields, $opt ) = @_;
+
+ @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) );
+
+ if ( $object->pkgpart ) {
+ foreach my $usage_class ( '', @taxproductnums ) {
+ $tax_override{$usage_class} =
+ join (",", map $_->taxclassnum,
+ qsearch( 'part_pkg_taxoverride', {
+ 'pkgpart' => $object->pkgpart,
+ 'usage_class' => $usage_class,
+ })
+ );
+ }
+ }
+
+ %taxproduct_fields =
+ map { $_ => [ "taxproductnum_$_",
+ { type => 'select-taxproduct',
+ #label => "$usage_class{$_} tax product",
+ },
+ "tax_override_$_",
+ { type => 'select-taxoverride' }
+ ]
+ }
+ @taxproductnums;
+
+ $taxproduct_fields{'(default)'} =
+ [ 'taxproductnum', { type => 'select-taxproduct',
+ #label => 'Default tax product',
+ },
+ 'tax_override', { type => 'select-taxoverride' },
+ ];
+};
+
+my $taxproduct_values = sub {
+ my ($cgi, $object, $flags) = @_;
+ my $routine =
+ sub { my $layer = shift;
+ my @fields = @{$taxproduct_fields{$layer}};
+ my @values = ();
+ while( @fields ) {
+ my $field = shift @fields;
+ shift @fields;
+ $field =~ /^taxproductnum_\w+$/ &&
+ push @values, ( $field => $options{"usage_$field"} );
+ $field =~ /^tax_override_(\w+)$/ &&
+ push @values, ( $field => $tax_override{$1} );
+ $field =~ /^taxproductnum$/ &&
+ push @values, ( $field => $object->taxproductnum );
+ $field =~ /^tax_override$/ &&
+ push @values, ( $field => $tax_override{''} );
+ }
+ { (@values) };
+ };
+
+ my @result =
+ map { ( $_ => { &{$routine}($_) } ) } ( '(default)', @taxproductnums );
+ return({ @result });
+
+};
+
+my $field_callback = sub {
+ my ($cgi, $object, $fieldref) = @_;
+
+ my $field = $fieldref->{field};
+ if ($field eq 'taxproductnums') {
+ $fieldref->{value} = join(',', @taxproductnums);
+ } elsif ($field eq 'taxproduct_select') {
+ $fieldref->{options} = [ '(default)', @taxproductnums ];
+ $fieldref->{labels} = { ( '(default)' => '(default)' ),
+ map {( $_ => ($usage_class{$_} || $_) )}
+ @taxproductnums
+ };
+ $fieldref->{layer_fields} = \%taxproduct_fields;
+ $fieldref->{layer_values_callback} = $taxproduct_values;
+ }
+};
+
+</%init>
diff --git a/httemplate/edit/part_pkg_report_option.html b/httemplate/edit/part_pkg_report_option.html
new file mode 100644
index 0000000..a6f8e57
--- /dev/null
+++ b/httemplate/edit/part_pkg_report_option.html
@@ -0,0 +1,23 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Package optional report class',
+ 'table' => 'part_pkg_report_option',
+ 'fields' => [
+ 'name',
+ { field=>'num', type=>'hidden' },
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'num' => 'Class number',
+ 'name' => 'Class name',
+ 'disabled' => 'Disable class',
+ },
+ 'viewall_dir' => 'browse',
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/part_pkg_taxclass.html b/httemplate/edit/part_pkg_taxclass.html
new file mode 100644
index 0000000..ad03044
--- /dev/null
+++ b/httemplate/edit/part_pkg_taxclass.html
@@ -0,0 +1,23 @@
+<% include('elements/edit.html',
+ 'name_singular' => 'tax class',
+ 'table' => 'part_pkg_taxclass',
+ 'labels' => {
+ 'taxclassnum' => 'Tax class',
+ 'taxclass' => 'Tax class',
+ 'disabled' => 'Disabled',
+ },
+ 'fields' => [ 'taxclass',
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ },
+ ],
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/part_pkg_taxoverride.html b/httemplate/edit/part_pkg_taxoverride.html
new file mode 100644
index 0000000..61dfa2a
--- /dev/null
+++ b/httemplate/edit/part_pkg_taxoverride.html
@@ -0,0 +1,132 @@
+<% include('/elements/header-popup.html', 'Override taxes', '', 'onload="resizeFrames()"') %>
+
+<TABLE WIDTH="100%" HEIGHT="100%">
+ <TR><TD>
+ <iframe name="selected" src="<% $p %>browse/tax_class.html?_type=select;magic=select;maxrecords=15;offset=<% $selected_offset %>;selected=<% $selected %>;" width="100%" frameborder="0" border="0" id="selectorSelected" scrolling="no">
+</iframe>
+ <BR>
+ </TD></TR>
+
+ <TR><TD>
+<FORM="dummy">
+ <CENTER>
+ <INPUT type="submit" value="Finish" onclick="s=fetchSelected(); s.shift(); parent.document.getElementById('<% $element_name || "tax_override" %>').value=s.toString(); parent.<% $onclick %>();">
+ <INPUT type="reset" value="Cancel" onclick="parent.<% $onclick %>();">
+ </CENTER>
+</FORM>
+ </TD></TR>
+
+ <TR><TD>
+ <iframe name="unselected" src="<% $p %>browse/tax_class.html?_type=select;magic=omit;maxrecords=15;offset=<% $unselected_offset %>;omit=<% $selected %>;" width="100%" frameborder="0" border="0" id="selectorUnselected" scrolling="no">
+</iframe>
+ <BR>
+ </TD></TR>
+
+</TABLE>
+<SCRIPT>
+
+ function resizeFrames() {
+ //frames['selected'].style.height =
+ // frames['selected'].contentWindow.document.body.scrollHeight + "px";
+ //frames['unselected'].style.height =
+ // frames['unselected'].contentWindow.document.body.scrollHeight + "px";
+ var f = document.getElementById('selectorSelected');
+ f.style.height = f.contentWindow.document.body.scrollHeight + "px";
+ var f = document.getElementById('selectorUnselected');
+ f.style.height = f.contentWindow.document.body.scrollHeight + "px";
+ }
+
+ function fetchOffset(search) {
+ var value = 0;
+ if (search.length > 1) {
+ var params = search.split(';');
+ for (i=0; i<params.length; i++) {
+ if (params[i].substr(0,7) == 'offset=') {
+ value = params[i].substr(7);
+ }
+ }
+ }
+ return value;
+ }
+
+ function fetchOffsetStrings() {
+ return 'selected_offset=' +
+ fetchOffset(frames['selected'].location.search) + ';' +
+ 'unselected_offset=' +
+ fetchOffset(frames['unselected'].location.search) + ';';
+ }
+
+ function fetchSelected() {
+ var i;
+ var selected = new Array;
+ var replace = '?';
+ if (window.location.search.length > 1) {
+ var search = window.location.search.substr(1).split(';');
+ for (i=0; i<search.length; i++) {
+ if (search[i].substr(0,9) == 'selected=') {
+ selected = search[i].substr(9).split(',')
+ }else if (search[i].substr(0,16) == 'selected_offset=') {
+ }else if (search[i].substr(0,18) == 'unselected_offset=') {
+ }else if (search[i].length) {
+ replace += search[i] + ';';
+ }
+ }
+ }
+ selected.unshift(replace);
+ return selected;
+ }
+ function doUnselect(classnum) {
+ var selected = fetchSelected();
+ var search = selected.shift();
+ //alert("discovered: "+selected.toString());
+ var i=-1, j=-1, k=selected.length;
+ while(++j < k) {
+ if (!(selected[j]==classnum)) {
+ selected[++i]=selected[j];
+ }
+ }
+ selected.length = ++i;
+ //alert("finished: "+selected.toString());
+
+ search += "selected=" + selected.toString() + ';';
+ window.location.search = search + fetchOffsetStrings();
+ }
+ function doSelect(classnum) {
+ var selected = fetchSelected();
+ var search = selected.shift();
+ selected.push(classnum);
+ search += "selected=" + selected.toString() + ';';
+ window.location.search = search + fetchOffsetStrings();
+ }
+</SCRIPT>
+
+<% include('/elements/footer.html') %>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+
+my $selected_offset = $1
+ if $cgi->param('selected_offset') =~/^(\d+)$/;
+
+my $unselected_offset = $1
+ if $cgi->param('unselected_offset') =~/^(\d+)$/;
+
+my $selected = $1
+ if $cgi->param('selected') =~/^([,\d]+)$/;
+
+my $element_name = $1
+ if $cgi->param('element_name') =~/^(\w+)$/;
+
+my $onclick = $1
+ if $cgi->param('onclick') =~/^(\w+)$/;
+
+$onclick = 'cClick' unless $onclick;
+
+</%init>
diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html
new file mode 100755
index 0000000..daf8773
--- /dev/null
+++ b/httemplate/edit/part_referral.html
@@ -0,0 +1,19 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Advertising source',
+ 'table' => 'part_referral',
+ 'fields' => [ 'referral',
+ { field=>'agentnum', type=>'select-agent', },
+ ],
+ 'labels' => { 'referral' => 'Advertising source',
+ 'agentnum' => 'Agent',
+ },
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources');
+
+</%init>
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
new file mode 100755
index 0000000..6fe015a
--- /dev/null
+++ b/httemplate/edit/part_svc.cgi
@@ -0,0 +1,418 @@
+<% include('/elements/header.html', "$action Service Definition",
+ menubar('View all service definitions' => "${p}browse/part_svc.cgi"),
+ #" onLoad=\"visualize()\""
+ )
+%>
+
+<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="svcpart" VALUE="<% $hashref->{svcpart} %>">
+<BR>
+Service definitions are the templates for items you offer to your customers.
+<UL><LI>svc_acct - Accounts - anything with a username (Mailboxes, PPP accounts, shell accounts, RADIUS entries for broadband, etc.)
+ <LI>svc_domain - Domains
+ <LI>svc_forward - Mail forwarding
+ <LI>svc_mailinglist - Mailing list
+ <LI>svc_www - Virtual domain website
+ <LI>svc_broadband - Broadband/High-speed Internet service (always-on)
+ <LI>svc_phone - Customer phone numbers
+ <LI>svc_pbx - Customer PBXs
+ <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, or select an inventory class to manually or automatically fill in
+that field.
+<BR><BR>
+
+% #YUCK. false laziness w/part_svc.pm. go away virtual fields, please
+% my %vfields;
+% foreach my $svcdb ( FS::part_svc->svc_tables() ) {
+% eval "use FS::$svcdb;";
+% 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);
+% $vfields{$svcdb}->{$field} = $pvf;
+% #warn "\$vfields{$svcdb}->{$field} = $pvf";
+% } #next $field
+% } #next $svcdb
+%
+% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
+% # and generalize the subs
+% # condition sub is tested to see whether to disable display of this choice
+% # params: ( $def, $layer, $field ) (see SUB below)
+% my $inv_sub = sub {
+% $_[0]->{disable_inventory}
+% || $_[0]->{'type'} ne 'text'
+% };
+% tie my %flag, 'Tie::IxHash',
+% '' => { 'desc' => 'No default', },
+% 'D' => { 'desc' => 'Default',
+% 'condition' =>
+% sub { $_[0]->{disable_default} },
+% },
+% 'F' => { 'desc' => 'Fixed (unchangeable)',
+% 'condition' =>
+% sub { $_[0]->{disable_fixed} },
+% },
+% 'S' => { 'desc' => 'Selectable Choice',
+% 'condition' =>
+% sub { !ref($_[0]) || $_[0]->{disable_select} },
+% },
+% 'M' => { 'desc' => 'Manual selection from inventory',
+% 'condition' => $inv_sub,
+% },
+% 'A' => { 'desc' => 'Automatically fill in from inventory',
+% 'condition' => $inv_sub,
+% },
+% 'X' => { 'desc' => 'Excluded',
+% 'condition' =>
+% sub { ! $vfields{$_[1]}->{$_[2]} },
+%
+% },
+% ;
+%
+% my @dbs = $hashref->{svcdb}
+% ? ( $hashref->{svcdb} )
+% : FS::part_svc->svc_tables();
+%
+% 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( 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().
+% "<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 .= include('/elements/table-grid.html', 'cellpadding' => 4 ).
+% '<TR>'.
+% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'.
+% '<TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>'.
+% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'.
+% '</TR>';
+%
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% #yucky kludge
+% my @fields = defined( dbdef->table($layer) )
+% ? grep {
+% $_ ne 'svcnum' &&
+% ( !FS::part_svc->svc_table_fields($layer)
+% ->{$_}->{disable_part_svc_column} ||
+% $part_svc->part_svc_column($_)->columnflag
+% )
+% }
+% fields($layer)
+% : ();
+% push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge
+% $part_svc->svcpart($clone) if $clone; #haha, undone below
+%
+%
+% foreach my $field (@fields) {
+%
+% #a few lines of false laziness w/browse/part_svc.cgi
+% my $def = FS::part_svc->svc_table_fields($layer)->{$field};
+% my $def_info = $def->{'def_info'};
+% my $formatter = $def->{'format'} || sub { shift };
+%
+% my $part_svc_column = $part_svc->part_svc_column($field);
+% my $label = $part_svc_column->columnlabel || $def->{'label'};
+% my $value = &$formatter($part_svc_column->columnvalue);
+% my $flag = $part_svc_column->columnflag;
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% $html .= qq!<TR><TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
+% ( $def->{'label'} || $field ).
+% "</TD>";
+%
+% $html .= qq!<TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor"><INPUT NAME="${layer}__${field}_label" VALUE="!. encode_entities($label). '" STYLE="text-align:right"></TD>';
+%
+% $flag = '' if $def->{type} eq 'disabled';
+%
+% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!;
+%
+% if ( $def->{type} eq 'disabled' ) {
+%
+% $html .= 'No default';
+%
+% } else {
+%
+% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!.
+% qq! onChange="${layer}__${field}_flag_changed(this)">!;
+%
+% foreach my $f ( keys %flag ) {
+%
+% # need to template-ize more httemplate/edit/svc_* first
+% next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone)$/;
+%
+% #here is where the SUB from above is called, to skip some choices
+% next if $flag{$f}->{condition}
+% && &{ $flag{$f}->{condition} }( $def, $layer, $field );
+%
+% $html .= qq!<OPTION VALUE="$f"!.
+% ' SELECTED'x($flag eq $f ).
+% '>'. $flag{$f}->{desc};
+%
+% }
+%
+% $html .= '</SELECT>';
+%
+% $html .= join("\n",
+% '<SCRIPT>',
+% " function ${layer}__${field}_flag_changed(what) {",
+% ' var f = what.options[what.selectedIndex].value;',
+% ' if ( f == "" || f == "X" ) { //disable',
+% " what.form.${layer}__${field}.disabled = true;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = true;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';".
+% " }".
+% ' } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box',
+% " what.form.${layer}__${field}.disabled = false;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
+% " if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge
+% " what.form.${layer}__${field}.multiple = true;".
+% " } else {".
+% " what.form.${layer}__${field}.multiple = false;".
+% " }".
+% " what.form.${layer}__${field}.style.display = '';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = false;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}_classnum.style.display = 'none';".
+% " }".
+% ' } else if ( f == "M" || f == "A" ) { //enable, inventory',
+% " what.form.${layer}__${field}.disabled = false;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}.style.display = 'none';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = false;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}_classnum.style.display = '';".
+% " }".
+% ' }',
+% ' }',
+% '</SCRIPT>',
+% );
+%
+% }
+%
+% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!;
+%
+% my $disabled = $flag ? ''
+% : 'DISABLED STYLE="background-color: #dddddd"';
+%
+% if ( !$def->{type} || $def->{type} eq 'text' ) {
+%
+% my $nodisplay = ' STYLE="display:none"';
+% my $is_inv = ( $flag =~ /^[MA]$/ );
+%
+% $html .=
+% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !.
+% $disabled.
+% ( $is_inv ? $nodisplay : $disabled ).
+% '>';
+%
+% $html .= include('/elements/select-table.html',
+% 'element_name' => "${layer}__${field}_classnum",
+% 'element_etc' => ( $is_inv
+% ? $disabled
+% : $nodisplay
+% ),
+% 'table' => 'inventory_class',
+% 'name_col' => 'classname',
+% 'value' => $value,
+% 'empty_label' => 'Select inventory class',
+% );
+%
+% } elsif ( $def->{type} eq 'checkbox' ) {
+%
+% $html .= include('/elements/checkbox.html',
+% 'field' => $layer.'__'.$field,
+% 'curr_value' => $value,
+% 'value' => 'Y',
+% );
+%
+% } elsif ( $def->{type} eq 'select' ) {
+%
+% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!;
+% $html .= ' MULTIPLE' if $flag eq 'S';
+% $html .= '>';
+% $html .= '<OPTION> </OPTION>' unless $value;
+% if ( $def->{select_table} ) {
+% foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
+% my $rvalue = $record->getfield($def->{select_key});
+% my $select_label = $def->{select_label};
+% $html .= qq!<OPTION VALUE="$rvalue"!.
+% (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ).
+% $record->$select_label(). '</OPTION>';
+% } #next $record
+% } else { # select_list
+% foreach my $item ( @{$def->{select_list}} ) {
+% $html .= qq!<OPTION VALUE="$item"!.
+% (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ).
+% $item. '</OPTION>';
+% } #next $item
+% } #endif
+% $html .= '</SELECT>';
+%
+% } elsif ( $def->{type} eq 'textarea' ) {
+%
+% $html .=
+% qq!<TEXTAREA NAME="${layer}__${field}">!. encode_entities($value).
+% '</TEXTAREA>';
+%
+% } elsif ( $def->{type} eq 'select-svc_pbx.html' ) {
+%
+% $html .= include('/elements/select-svc_pbx.html',
+% 'curr_value' => $value,
+% 'element_name' => "${layer}__${field}",
+% 'element_etc' => $disabled,
+% 'multiple' => ($flag eq 'S'),
+% );
+%
+% } elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
+%
+% #XXX disable the RADIUS usergroup selector? ugh it sure does need
+% #an overhaul, people have dum group problems because of it
+%
+% $html .= FS::svc_acct::radius_usergroup_selector(
+% [ split(',', $value) ], "${layer}__${field}" );
+%
+% } elsif ( $def->{type} eq 'communigate_pro-accessmodes' ) {
+%
+% $html .= include('/elements/communigate_pro-accessmodes.html',
+% 'element_name_prefix' => "${layer}__${field}_",
+% 'curr_value' => $value,
+% #doesn't work#'element_etc' => $disabled,
+% );
+%
+% } elsif ( $def->{type} eq 'disabled' ) {
+%
+% $html .=
+% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
+%
+% } else {
+%
+% $html .= '<font color="#ff0000">unknown type '. $def->{type};
+%
+% }
+%
+% $html .= "</TD></TR>\n";
+
+% $def_info = "($def_info)" if $def_info;
+% $html .=
+% qq!<TR>!.
+% qq! <TD COLSPAN=2 BGCOLOR="$bgcolor" ALIGN="center" !.
+% qq! STYLE="padding:0; border-top: none">!.
+% qq! <FONT SIZE="-1"><I>$def_info</I></FONT>!.
+% qq! </TD>!.
+% qq!</TR>\n!;
+%
+% } #foreach my $field (@fields) {
+%
+% $part_svc->svcpart('') if $clone; #undone
+% $html .= "</TABLE>";
+%
+% $html .= include('/elements/progress-init.html',
+% $layer, #form name
+% [ qw(svc svcpart disabled exportnum), @fields ],
+% 'process/part_svc.cgi',
+% $p.'browse/part_svc.cgi',
+% $layer,
+% );
+% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'.
+% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '.
+% ' onClick="document.'. "$layer.submit.disabled=true; ".
+% "fixup(document.$layer); $layer". 'process();">';
+%
+% #$html .= '<BR><INPUT TYPE="submit" VALUE="'.
+% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
+%
+% $html;
+%
+% },
+% );
+%
+%
+
+Table <% $widget->html %>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $part_svc;
+my $clone = '';
+if ( $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';
+
+
+
+</%init>
+
+
+
diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi
new file mode 100644
index 0000000..04ba9b0
--- /dev/null
+++ b/httemplate/edit/part_virtual_field.cgi
@@ -0,0 +1,104 @@
+<% include('/elements/header.html', "$action Virtual Field Definition") %>
+
+<% include('/elements/error.html') %>
+
+<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=32 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) {
+% foreach my $dbtable (qw( svc_broadband router )) {
+% 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="80" 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>
+<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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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);
+
+</%init>
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
new file mode 100644
index 0000000..a9909c3
--- /dev/null
+++ b/httemplate/edit/payment_gateway.html
@@ -0,0 +1,139 @@
+<% include( 'elements/edit.html',
+ 'table' => 'payment_gateway',
+ 'name_singular' => 'Payment gateway',
+ 'viewall_dir' => 'browse',
+ 'fields' => $fields,
+ 'field_callback' => $field_callback,
+ 'labels' => {
+ 'gatewaynum' => 'Gateway #',
+ 'gateway_module' => 'Gateway',
+ 'gateway_username' => 'Username',
+ 'gateway_password' => 'Password',
+ 'gateway_action' => 'Action',
+ 'gateway_options' => 'Options: (Name/Value pairs, one element per line)',
+ 'gateway_callback_url' => 'Callback URL',
+ },
+ )
+%>
+
+
+<SCRIPT TYPE="text/javascript">
+ var gatewayNamespace = new Array;
+
+% foreach my $module ( sort { lc($a) cmp lc ($b) } keys %modules ) {
+ gatewayNamespace.push('<% $modules{$module} %>')
+% }
+
+ // document.getElementById('gateway_namespace').value = gatewayNamespace[0];
+ function setNamespace(what) {
+ document.getElementById('gateway_namespace').value =
+ gatewayNamespace[what.selectedIndex];
+ }
+
+</SCRIPT>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %modules = (
+ '2CheckOut' => 'Business::OnlinePayment',
+ 'AuthorizeNet' => 'Business::OnlinePayment',
+ 'BankOfAmerica' => 'Business::OnlinePayment', #deprecated?
+ 'Beanstream' => 'Business::OnlinePayment',
+ 'Capstone' => 'Business::OnlinePayment',
+ 'Cardstream' => 'Business::OnlinePayment',
+ 'CashCow' => 'Business::OnlinePayment',
+ 'CyberSource' => 'Business::OnlinePayment',
+ 'eSec' => 'Business::OnlinePayment',
+ 'eSelectPlus' => 'Business::OnlinePayment',
+ 'ElavonVirtualMerchant' => 'Business::OnlinePayment',
+ 'Exact' => 'Business::OnlinePayment',
+ 'iAuthorizer' => 'Business::OnlinePayment',
+ 'Ingotz' => 'Business::OnlinePayment',
+ 'InternetSecure' => 'Business::OnlinePayment',
+ 'Interswitchng' => 'Business::OnlineThirdPartyPayment',
+ 'IPaymentTPG' => 'Business::OnlinePayment',
+ 'IPPay' => 'Business::OnlinePayment',
+ 'Iridium' => 'Business::OnlinePayment',
+ 'Jettis' => 'Business::OnlinePayment',
+ 'Jety' => 'Business::OnlinePayment',
+ 'LinkPoint' => 'Business::OnlinePayment',
+ 'MerchantCommerce' => 'Business::OnlinePayment',
+ 'Network1Financial' => 'Business::OnlinePayment',
+ 'OCV' => 'Business::OnlinePayment',
+ 'OpenECHO' => 'Business::OnlinePayment',
+ 'PayConnect' => 'Business::OnlinePayment',
+ 'PayflowPro' => 'Business::OnlinePayment',
+ 'PaymenTech' => 'Business::OnlinePayment',
+ 'PaymentsGateway' => 'Business::OnlinePayment',
+ 'PayPal' => 'Business::OnlinePayment',
+ #'PaySystems' => 'Business::OnlinePayment',
+ 'PlugnPay' => 'Business::OnlinePayment',
+ 'PPIPayMover' => 'Business::OnlinePayment',
+ 'Protx' => 'Business::OnlinePayment', #now SagePay
+ 'PXPost' => 'Business::OnlinePayment',
+ 'SagePay' => 'Business::OnlinePayment',
+ 'SecureHostingUPG' => 'Business::OnlinePayment',
+ 'Skipjack' => 'Business::OnlinePayment',
+ 'StGeorge' => 'Business::OnlinePayment',
+ 'SurePay' => 'Business::OnlinePayment',
+ 'TCLink' => 'Business::OnlinePayment',
+ 'TransactionCentral' => 'Business::OnlinePayment',
+ 'TransFirsteLink' => 'Business::OnlinePayment',
+ 'Vanco' => 'Business::OnlinePayment',
+ 'viaKLIX' => 'Business::OnlinePayment',
+ 'VirtualNet' => 'Business::OnlinePayment',
+ 'WesternACH' => 'Business::OnlinePayment',
+ 'WorldPay' => 'Business::OnlinePayment',
+);
+
+my @actions = (
+ 'Normal Authorization',
+ 'Authorization Only',
+ 'Authorization Only, Post Authorization',
+ );
+
+my $fields = [
+ {
+ field => 'gateway_namespace',
+ type => 'hidden',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ $modules{$object->gateway_module}
+ || 'Business::OnlinePayment'
+ },
+ },
+ {
+ field => 'gateway_module',
+ type => 'select',
+ options => [ sort { lc($a) cmp lc ($b) } keys %modules ],
+ onchange => 'setNamespace',
+ },
+ 'gateway_username',
+ 'gateway_password',
+ {
+ field => 'gateway_action',
+ type => 'select',
+ options => \@actions,
+ },
+ 'gateway_callback_url',
+ {
+ field => 'gateway_options',
+ type => 'textarea',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ join("\r", $object->options );
+ },
+ },
+ ];
+
+my $field_callback = sub {
+ my ($cgi, $object, $field_hashref ) = @_;
+ if ($object->gatewaynum) {
+ if ( $field_hashref->{field} eq 'gateway_module' ) {
+ $field_hashref->{type} = 'fixed';
+ }
+ }
+};
+
+</%init>
diff --git a/httemplate/edit/phone_device.html b/httemplate/edit/phone_device.html
new file mode 100644
index 0000000..a1aa166
--- /dev/null
+++ b/httemplate/edit/phone_device.html
@@ -0,0 +1,37 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Phone device',
+ 'table' => 'phone_device',
+ 'labels' => {
+ 'devicenum' => 'Device',
+ 'devicepart' => 'Device type',
+ 'mac_addr' => 'MAC address',
+ },
+ 'fields' => [ { 'field' => 'devicepart',
+ 'type' => 'select-table',
+ 'table' => 'part_device',
+ 'name_col' => 'devicename',
+ 'empty_label' =>'Select device type',
+ #'hashref' =>{ disabled => '' },
+ },
+ 'mac_addr',
+ { 'field' => 'svcnum',
+ 'type' => 'hidden',
+ },
+ ],
+ 'menubar' => [], #disable viewall
+ #'viewall_dir' => 'browse',
+ 'new_callback' => sub {
+ my( $cgi, $object ) = @_;
+ $object->svcnum( $cgi->param('svcnum') );
+ },
+ )
+%>
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+
+</%init>
diff --git a/httemplate/edit/pkg_category.html b/httemplate/edit/pkg_category.html
new file mode 100644
index 0000000..3be74c7
--- /dev/null
+++ b/httemplate/edit/pkg_category.html
@@ -0,0 +1,28 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'Package Category',
+ 'table' => 'pkg_category',
+ 'fields' => [
+ 'categoryname',
+ 'weight',
+ { field=>'condense', type=>'checkbox', value=>'Y', },
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'categorynum' => 'Category number',
+ 'categoryname' => 'Category name',
+ 'weight' => 'Weight',
+ 'condense' => 'Collapse identical items to one',
+ 'disabled' => 'Disable category',
+ },
+ 'viewall_dir' => 'browse',
+ %opt,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html
new file mode 100644
index 0000000..4f7a729
--- /dev/null
+++ b/httemplate/edit/pkg_class.html
@@ -0,0 +1,5 @@
+<% include( 'elements/class_Common.html',
+ 'name' => 'Package Class',
+ 'table' => 'pkg_class',
+ )
+%>
diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi
new file mode 100644
index 0000000..f7a1b08
--- /dev/null
+++ b/httemplate/edit/prepay_credit.cgi
@@ -0,0 +1,113 @@
+<% include("/elements/header.html",'Generate prepaid cards'. ($agent ? ' for '. $agent->agent : '') ) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/prepay_credit.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true">
+
+Generate
+<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') || '(quantity)' |h %>" SIZE=10 MAXLENGTH=10 onFocus="if ( this.value == '(quantity)' ) { this.value = ''; }">
+
+prepaid cards of
+
+<INPUT TYPE="text" NAME="length" SIZE=3 MAXLENGTH=2 VALUE=8>&nbsp;
+<SELECT NAME="type">
+% foreach (qw(alpha alphanumeric numeric)) {
+ <OPTION<% $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><% $_ %>
+% }
+</SELECT>
+
+characters each
+
+<BR>for <SELECT NAME="agentnum"><OPTION>(any agent)
+% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) {
+
+ <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %>
+% }
+
+</SELECT>
+
+<TABLE>
+<TR><TD>Value:
+$<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<% $cgi->param('amount') |h %>">
+</TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('seconds') |h %>">
+<SELECT NAME="multiplier">
+% foreach my $multiplier ( keys %multiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('multiplier') eq $multiplier ? ' SELECTED' : '' %>><% $multiplier{$multiplier} %>
+% }
+
+</SELECT>
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="upbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('upbytes') |h %>">
+<SELECT NAME="upmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('upmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> upload
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="downbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('downbytes') |h %>">
+<SELECT NAME="downmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('downmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> download
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="totalbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('totalbytes') |h %>">
+<SELECT NAME="totalmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('totalmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> total transfer
+</TD></TR>
+</TABLE>
+<BR><BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Generate" onSubmit="this.disabled = true">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agent = '';
+my $agentnum = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum=$1 } );
+}
+
+tie my %multiplier, 'Tie::IxHash',
+ 1 => 'seconds',
+ 60 => 'minutes',
+ 3600 => 'hours',
+;
+
+tie my %bytemultiplier, 'Tie::IxHash',
+ 1 => 'bytes',
+ 1024 => 'Kbytes',
+ 1048576 => 'Mbytes',
+ 1073741824 => 'Gbytes',
+;
+
+$cgi->param('multiplier', '60') unless $cgi->param('multiplier');
+$cgi->param('upmultiplier', '1048576') unless $cgi->param('upmultiplier');
+$cgi->param('downmultiplier', '1048576') unless $cgi->param('downmultiplier');
+$cgi->param('totalmultiplier','1048576') unless $cgi->param('totalmultiplier');
+
+</%init>
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
new file mode 100755
index 0000000..570f0e0
--- /dev/null
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -0,0 +1,49 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ) %>
+%} else {
+% my $custnum = $new->custnum;
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" ) %>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer package dates');
+
+my $pkgnum = $cgi->param('pkgnum') or die;
+my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $old->hash;
+$hash{$_}= $cgi->param($_) ? parse_datetime($cgi->param($_)) : ''
+ foreach qw( start_date setup bill last_bill adjourn expire );
+
+my @errors = ();
+
+push @errors, '_bill_areyousure'
+ if $hash{'bill'} != $old->bill # if the next bill date was changed
+ && $hash{'bill'} < time # to a date in the past
+ && ! $cgi->param('bill_areyousure'); # and it wasn't confirmed
+
+push @errors, '_setup_areyousure'
+ if ! $hash{'setup'} && $old->setup # if the setup date was removed
+ && ! $cgi->param('setup_areyousure'); # and it wasn't confirmed
+
+push @errors, '_start'
+ if $hash{'start_date'} && !$old->start_date # if a start date was added
+ && $hash{'setup'}; # but there's a setup date
+
+my $new;
+my $error;
+if ( @errors ) {
+ $error = join(',', @errors);
+} else {
+ $new = new FS::cust_pkg \%hash;
+ $error = $new->replace($old);
+}
+
+</%init>
diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html
new file mode 100644
index 0000000..ab25cb3
--- /dev/null
+++ b/httemplate/edit/process/access_group.html
@@ -0,0 +1,28 @@
+% if ( $conf->exists('disable_acl_changes') ) {
+ ACL changes disabled in public demo.
+% } else {
+<% include( 'elements/process.html',
+ 'table' => 'access_group',
+ 'viewall_dir' => 'browse',
+ 'process_m2m' => { 'link_table' => 'access_groupagent',
+ 'target_table' => 'agent',
+ },
+ 'process_m2name' => {
+ 'link_table' => 'access_right',
+ 'link_static' => { 'righttype' => 'FS::access_group', },
+ 'num_col' => 'rightobjnum',
+ 'name_col' => 'rightname',
+ 'names_list' => [ FS::AccessRight->rights() ],
+ 'param_style' => 'link_table.value checkboxes',
+ },
+ )
+%>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html
new file mode 100644
index 0000000..ca6bb60
--- /dev/null
+++ b/httemplate/edit/process/access_user.html
@@ -0,0 +1,21 @@
+% if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+% $cgi->param('error', "The passwords do not match");
+% print $cgi->redirect(popurl(2) . "access_user.html?" . $cgi->query_string);
+% } else {
+<% include( 'elements/process.html',
+ 'table' => 'access_user',
+ 'viewall_dir' => 'browse',
+ 'copy_on_empty' => [ '_password' ],
+ 'clear_on_error' => [ '_password', '_password2' ],
+ 'process_m2m' => { 'link_table' => 'access_usergroup',
+ 'target_table' => 'access_group',
+ },
+ )
+%>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi
new file mode 100755
index 0000000..39d6348
--- /dev/null
+++ b/httemplate/edit/process/addr_block/add.cgi
@@ -0,0 +1,20 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'redirect' => popurl(4). 'browse/addr_block.cgi?dummy=',
+ 'error_redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+$cgi->param('routernum', 0) # in FS::addr_block::check instead?
+ unless $cgi->param('routernum');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi
new file mode 100755
index 0000000..40d04b3
--- /dev/null
+++ b/httemplate/edit/process/addr_block/allocate.cgi
@@ -0,0 +1,16 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'copy_on_empty' => [ fields 'addr_block' ],
+ 'error_redirect' => popurl(3). 'allocate.html?',
+ 'popup_reload' => 'Block allocated',
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi
new file mode 100755
index 0000000..128824e
--- /dev/null
+++ b/httemplate/edit/process/addr_block/deallocate.cgi
@@ -0,0 +1,20 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'copy_on_empty' => [ grep { $_ ne 'routernum' }
+ fields 'addr_block' ],
+ 'redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'error_redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+$cgi->param('routernum', 0); # just to be explicit about what we are doing
+</%init>
diff --git a/httemplate/edit/process/addr_block/manual_flag.cgi b/httemplate/edit/process/addr_block/manual_flag.cgi
new file mode 100755
index 0000000..dc0cbbb
--- /dev/null
+++ b/httemplate/edit/process/addr_block/manual_flag.cgi
@@ -0,0 +1,30 @@
+<% $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+my $error = '';
+$cgi->param('blocknum') =~ /^(\d+)$/ or die "invalid blocknum";
+my $blocknum = $1;
+
+my $addr_block = qsearchs({ 'table' => 'addr_block',
+ 'hashref' => { blocknum => $blocknum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration'
+ ),
+ })
+ or $error = "Unknown blocknum: $blocknum";
+
+$addr_block->manual_flag($cgi->param('manual_flag'))
+ unless $error;
+
+$error ||= $addr_block->replace;
+
+$cgi->param('error', $error)
+ if $error;
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi
new file mode 100755
index 0000000..045fd30
--- /dev/null
+++ b/httemplate/edit/process/addr_block/split.cgi
@@ -0,0 +1,27 @@
+<% $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+my $error = '';
+$cgi->param('blocknum') =~ /^(\d+)$/ or die "invalid blocknum";
+my $blocknum = $1;
+
+my $addr_block = qsearchs({ 'table' => 'addr_block',
+ 'hashref' => { blocknum => $blocknum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration'
+ ),
+ })
+ or $error = "Unknown blocknum: $blocknum";
+
+$error ||= $addr_block->split_block;
+
+$cgi->param('error', $error)
+ if $error;
+
+</%init>
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
new file mode 100755
index 0000000..3cdf40c
--- /dev/null
+++ b/httemplate/edit/process/agent.cgi
@@ -0,0 +1,16 @@
+<% include( 'elements/process.html',
+ 'table' => 'agent',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'process_m2m' => { 'link_table' => 'access_groupagent',
+ 'target_table' => 'access_group',
+ },
+ 'edit_ext' => 'cgi',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/agent_payment_gateway.html b/httemplate/edit/process/agent_payment_gateway.html
new file mode 100644
index 0000000..5b5fd94
--- /dev/null
+++ b/httemplate/edit/process/agent_payment_gateway.html
@@ -0,0 +1,29 @@
+<% $cgi->redirect(popurl(3). "browse/agent.cgi") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum";
+my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+die "agentnum $1 not found" unless $agent;
+
+#my $old
+
+my @new = map {
+ my $cardtype = $_;
+ new FS::agent_payment_gateway {
+ ( map { $_ => scalar($cgi->param($_)) }
+ fields('agent_payment_gateway')
+ ),
+ 'cardtype' => $cardtype,
+ };
+ }
+ $cgi->param('cardtype');
+
+foreach my $new (@new) {
+ my $error = $new->insert;
+ die $error if $error;
+}
+
+</%init>
diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi
new file mode 100755
index 0000000..ad5963b
--- /dev/null
+++ b/httemplate/edit/process/agent_type.cgi
@@ -0,0 +1,35 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/agent_type.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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');
+}
+
+ $error ||= $new->process_m2m(
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'part_pkg',
+ 'params' => scalar($cgi->Vars)
+ );
+
+</%init>
diff --git a/httemplate/edit/process/bulk-cust_main_county.html b/httemplate/edit/process/bulk-cust_main_county.html
new file mode 100644
index 0000000..af9e495
--- /dev/null
+++ b/httemplate/edit/process/bulk-cust_main_county.html
@@ -0,0 +1,66 @@
+% if ( $error ) { #better to redirect back to
+%# <% $cgi->redirect("$url?". $cgi->query_string ) %>
+ <% include('/elements/header-popup.html', "Error ${action}ing taxes" ) %>
+
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error |h %></FONT>
+ <BR><BR>
+
+ </BODY>
+ </HTML>
+
+% } else {
+ <% include('/elements/header-popup.html', "Taxes ${action}ed") %>
+
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+$cgi->param('taxnum') =~ /^([\d,]+)$/
+ or die 'Guru Meditation #69'; #??? should have been passed in
+my @taxnum = split(',', $1);
+
+$cgi->param('action') =~ /^(add|edit)$/ or die "unknown action";
+my $action = $1;
+
+my $error = '';
+foreach my $taxnum ( @taxnum ) {
+
+ my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+ or die "unknown taxnum: $taxnum";
+
+ if ( $action eq 'edit' || $cust_main_county->tax == 0 ) { #let's replace
+
+ foreach (qw( taxname tax exempt_amount setuptax recurtax )) {
+ $cust_main_county->set( $_ => scalar($cgi->param($_)) )
+ }
+
+ $error = $cust_main_county->replace and last;
+
+ } else { #let's insert a new record
+
+ my $new =
+ new FS::cust_main_county {
+ ( map { $_ => scalar($cgi->param($_)) }
+ qw( taxname tax exempt_amount setuptax recurtax )
+ ),
+ ( map { $_ => $cust_main_county->get($_) }
+ qw( country state county taxclass )
+ )
+ };
+
+ $error = $new->insert and last;
+
+ }
+
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+}
+
+</%init>
diff --git a/httemplate/edit/process/bulk-cust_svc.cgi b/httemplate/edit/process/bulk-cust_svc.cgi
new file mode 100644
index 0000000..313b061
--- /dev/null
+++ b/httemplate/edit/process/bulk-cust_svc.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process_bulk_cust_svc', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/cgp_rule.html b/httemplate/edit/process/cgp_rule.html
new file mode 100644
index 0000000..f427b6c
--- /dev/null
+++ b/httemplate/edit/process/cgp_rule.html
@@ -0,0 +1,24 @@
+<% include( 'elements/process.html',
+ 'table' => 'cgp_rule',
+ 'redirect' => $redirect,
+ 'process_o2m' => [
+ {
+ 'table' => 'cgp_rule_condition',
+ 'fields' => [qw( condition op params )],
+ },
+ {
+ 'table' => 'cgp_rule_action',
+ 'fields' => [qw( action params )],
+ },
+ ],
+ )
+%>
+<%init>
+
+my $redirect = sub {
+ my($cgi, $new) = @_;
+ my $svcnum = $new->svcnum;
+ popurl(3)."browse/cgp_rule.html?svcnum=$svcnum;rulenum=";
+};
+
+</%init>
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
new file mode 100644
index 0000000..7356e61
--- /dev/null
+++ b/httemplate/edit/process/change-cust_pkg.html
@@ -0,0 +1,46 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'misc/change_pkg.cgi?'. $cgi->query_string );
+% } else {
+
+ <% header("Package changed") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $cust_pkg = qsearchs({
+ #'select' => 'cust_pkg.*',
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die 'unknown pkgnum' unless $cust_pkg;
+
+my %change = map { $_ => scalar($cgi->param($_)) }
+ qw( locationnum pkgpart );
+
+if ( $cgi->param('locationnum') == -1 ) {
+ my $cust_location = new FS::cust_location {
+ 'custnum' => $cust_pkg->custnum,
+ map { $_ => scalar($cgi->param($_)) }
+ qw( address1 address2 city county state zip country )
+ };
+ $change{'cust_location'} = $cust_location;
+}
+
+my $pkg_or_error = $cust_pkg->change( \%change );
+
+my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
+
+</%init>
diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi
new file mode 100755
index 0000000..2845d32
--- /dev/null
+++ b/httemplate/edit/process/cust_bill_pay.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_bill_pay',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/process/cust_category.html b/httemplate/edit/process/cust_category.html
new file mode 100644
index 0000000..c3a8809
--- /dev/null
+++ b/httemplate/edit/process/cust_category.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'cust_category',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cust_class.html b/httemplate/edit/process/cust_class.html
new file mode 100644
index 0000000..3f63ea4
--- /dev/null
+++ b/httemplate/edit/process/cust_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'cust_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
new file mode 100755
index 0000000..8715ad6
--- /dev/null
+++ b/httemplate/edit/process/cust_credit.cgi
@@ -0,0 +1,63 @@
+%if ( $error ) {
+% $cgi->param('reasonnum', $reasonnum);
+% $cgi->param('error', $error);
+% $dbh->rollback if $oldAutoCommit;
+%
+<% $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");
+%
+% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+%
+<% header('Credit sucessful') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+
+$cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum";
+my $reasonnum = $1;
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+my $error = '';
+if ($reasonnum == -1) {
+
+ $error = 'Enter a new reason (or select an existing one)'
+ unless $cgi->param('newreasonnum') !~ /^\s*$/;
+ my $reason = new FS::reason({ 'reason_type' => $cgi->param('newreasonnumT'),
+ 'reason' => $cgi->param('newreasonnum'),
+ });
+ $error ||= $reason->insert;
+ $cgi->param('reasonnum', $reason->reasonnum)
+ unless $error;
+}
+
+unless ($error) {
+ my $new = new FS::cust_credit ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_credit')
+ } );
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi
new file mode 100755
index 0000000..d3847dc
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_bill.cgi
@@ -0,0 +1,19 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_bill',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+if ( $cgi->param('src_amount') ) {
+ die "access denied"
+ unless ( $FS::CurrentUser::CurrentUser->access_right('Post credit') &&
+ $FS::CurrentUser::CurrentUser->access_right('Delete credit') );
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit_refund.cgi b/httemplate/edit/process/cust_credit_refund.cgi
new file mode 100755
index 0000000..88420f8
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_refund.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_refund.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
new file mode 100755
index 0000000..1311ba4
--- /dev/null
+++ b/httemplate/edit/process/cust_main.cgi
@@ -0,0 +1,260 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+%
+<% $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ) %>
+%
+% } else {
+%
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum) %>
+%
+% }
+<%once>
+
+my $me = '[edit/process/cust_main.cgi]';
+my $DEBUG = 0;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $conf = new FS::Conf;
+
+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');
+
+my %noauto = (
+ 'CARD' => 'DCRD',
+ 'CHEK' => 'DCHK',
+);
+$payby = $noauto{$payby}
+ if ! $cgi->param('payauto') && exists $noauto{$payby};
+
+$cgi->param('payby', $payby);
+
+if ( $payby ) {
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ $cgi->param('payinfo',
+ $cgi->param('payinfo1'). '@'. $cgi->param('payinfo2') );
+ }
+ $cgi->param('paydate',
+ $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) );
+}
+
+my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
+push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
+$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
+ );
+}
+
+my %usedatetime = ( 'birthdate' => 1 );
+
+foreach my $dfield (qw( birthdate signupdate )) {
+
+ if ( $cgi->param($dfield) && $cgi->param($dfield) =~ /^([ 0-9\-\/]{0,10})$/) {
+
+ my $value = $1;
+ my $parsed = '';
+
+ if ( exists $usedatetime{$dfield} && $usedatetime{$dfield} ) {
+
+ my $format = $conf->config('date_format') || "%m/%d/%Y";
+ my $parser = DateTime::Format::Strptime->new( pattern => $format,
+ time_zone => 'floating',
+ );
+ my $dt = $parser->parse_datetime($value);
+ if ( $dt ) {
+ $parsed = $dt->epoch;
+ } else {
+ # $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg;
+ $error ||= "Invalid $dfield: $value";
+ }
+
+ } else {
+
+ $parsed = parse_datetime($value)
+ or $error ||= "Invalid $dfield: $value";
+
+ }
+
+ $new->setfield( $dfield, $parsed );
+ $cgi->param( $dfield, $parsed );
+
+ }
+
+}
+
+$new->setfield('paid', $cgi->param('paid') )
+ if $cgi->param('paid');
+
+my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups;
+
+#perhaps this stuff should go to cust_main.pm
+if ( $new->custnum eq '' ) {
+
+ my $cust_pkg = '';
+ my $svc;
+
+ 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);
+ my $part_pkg = qsearchs('part_pkg', { 'pkgpart' => $pkgpart } );
+ #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 });
+
+ if ( $agent ) {
+ # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
+ my $pkgpart_href = $agent->pkgpart_hashref
+ if $agent;
+ #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 }
+ || $agent->agentnum == $part_pkg->agentnum;
+ } else {
+ $error = 'Select agent';
+ }
+
+ $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;
+
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ my $svcdb = $part_svc->svcdb;
+
+ if ( $svcdb eq 'svc_acct' ) {
+
+ my %svc_acct = (
+ 'svcpart' => $svcpart,
+ 'username' => scalar($cgi->param('username')),
+ '_password' => scalar($cgi->param('_password')),
+ 'popnum' => scalar($cgi->param('popnum')),
+ );
+ $svc_acct{'domsvc'} = $cgi->param('domsvc')
+ if $cgi->param('domsvc');
+
+ $svc = new FS::svc_acct \%svc_acct;
+
+ #and just in case you were silly
+ $svc->svcpart($svcpart);
+ $svc->username($cgi->param('username'));
+ $svc->_password($cgi->param('_password'));
+ $svc->popnum($cgi->param('popnum'));
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+
+ my %svc_phone = (
+ 'svcpart' => $svcpart,
+ map { $_ => scalar($cgi->param($_)) }
+ qw( countrycode phonenum sip_password pin phone_name )
+ );
+
+ $svc = new FS::svc_phone \%svc_phone;
+
+ } else {
+ die "$svcdb not handled on new customer yet";
+ }
+
+ #$error ||= $svc_acct->check;
+
+ }
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => [ $svc ] ) if $cust_pkg;
+ $error ||= $new->insert( \%hash, \@invoicing_list,
+ 'tax_exemption' => \@tax_exempt,
+ );
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('backend-realtime') && ! $error ) {
+
+ my $berror = $new->bill
+ || $new->apply_payments_and_credits
+ || $new->collect( 'realtime' => 1 );
+ warn "Warning, error billing during backend-realtime: $berror" if $berror;
+
+ }
+
+} else { #create old record object
+
+ my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );
+ $error ||= "Old record not found!" unless $old;
+ if ( length($old->paycvv) && $new->paycvv =~ /^\s*\*+\s*$/ ) {
+ $new->paycvv($old->paycvv);
+ }
+ if ($new->ss =~ /xx/) {
+ $new->ss($old->ss);
+ }
+ if ($new->stateid =~ /^xxx/) {
+ $new->stateid($old->stateid);
+ }
+ if ($new->payby =~ /^(CARD|DCRD)$/ && $new->payinfo =~ /xx/) {
+ $new->payinfo($old->payinfo);
+ } elsif ($new->payby =~ /^(CHEK|DCHK)$/ && $new->payinfo =~ /xx/) {
+ #fix for #3085 "edit of customer's routing code only surprisingly causes
+ #nothing to happen...
+ # this probably won't do the right thing when we don't have the
+ # public key (can't actually get the real $old->payinfo)
+ my($new_account, $new_aba) = split('@', $new->payinfo);
+ my($old_account, $old_aba) = split('@', $old->payinfo);
+ $new_account = $old_account if $new_account =~ /xx/;
+ $new_aba = $old_aba if $new_aba =~ /xx/;
+ $new->payinfo($new_account.'@'.$new_aba);
+ }
+
+ warn "$me calling $new -> replace( $old, \ @invoicing_list )" if $DEBUG;
+ local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG;
+ local($FS::Record::DEBUG) = $DEBUG if $DEBUG;
+
+ $error ||= $new->replace( $old, \@invoicing_list,
+ 'tax_exemption' => \@tax_exempt,
+ );
+
+ warn "$me returned from replace" if $DEBUG;
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_attach.cgi b/httemplate/edit/process/cust_main_attach.cgi
new file mode 100644
index 0000000..2911357
--- /dev/null
+++ b/httemplate/edit/process/cust_main_attach.cgi
@@ -0,0 +1,99 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_main_attach.cgi?'. $cgi->query_string ) %>
+%} else {
+% my $act = 'added';
+% $act = 'updated' if ($attachnum);
+% $act = 'purged' if($attachnum and $purge);
+% $act = 'undeleted' if($attachnum and $undelete);
+% $act = 'deleted' if($attachnum and $delete);
+<% header('Attachment ' . $act ) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+my $error;
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die "Illegal custnum: ". $cgi->param('custnum');
+my $custnum = $1;
+
+$cgi->param('attachnum') =~ /^(\d*)$/
+ or die "Illegal attachnum: ". $cgi->param('attachnum');
+my $attachnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $delete = $cgi->param('delete');
+my $undelete = $cgi->param('undelete');
+my $purge = $cgi->param('purge');
+
+my $new = new FS::cust_attachment ( {
+ attachnum => $attachnum,
+ custnum => $custnum,
+ _date => time,
+ usernum => $curuser->usernum,
+ disabled => '',
+});
+my $old;
+
+if($attachnum) {
+ $old = qsearchs('cust_attachment', { attachnum => $attachnum });
+ if(!$old) {
+ $error = "Attachnum '$attachnum' not found";
+ }
+ elsif($purge) { # do nothing
+ }
+ else {
+ map { $new->$_($old->$_) }
+ ('_date', 'otaker', 'body', 'disabled');
+ $new->filename($cgi->param('filename') || $old->filename);
+ $new->mime_type($cgi->param('mime_type') || $old->mime_type);
+ $new->title($cgi->param('title'));
+ if($delete and not $old->disabled) {
+ $new->disabled(time);
+ }
+ if($undelete and $old->disabled) {
+ $new->disabled('');
+ }
+ }
+}
+else { # This is a new attachment, so require a file.
+
+ my $filename = $cgi->param('file');
+ if($filename) {
+ $new->filename($filename);
+ $new->mime_type($cgi->uploadInfo($filename)->{'Content-Type'});
+ $new->title($cgi->param('title'));
+
+ local $/;
+ my $fh = $cgi->upload('file');
+ $new->body(<$fh>);
+ }
+ else {
+ $error = 'No file uploaded';
+ }
+}
+my $action = 'Add';
+$action = 'Edit' if $attachnum;
+$action = 'Delete' if $attachnum and $delete;
+$action = 'Undelete' if $attachnum and $undelete;
+$action = 'Purge' if $attachnum and $purge;
+
+$error = 'access denied' unless $curuser->access_right($action . ' attachment');
+
+if(!$error) {
+ if($old and $old->disabled and $purge) {
+ $error = $old->delete;
+ }
+ elsif($old) {
+ $error = $new->replace($old);
+ }
+ else {
+ $error = $new->insert;
+ }
+}
+
+</%init>
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..9608fc9
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-collapse.cgi
@@ -0,0 +1,46 @@
+<% $cgi->redirect(popurl(3). "browse/cust_main_county.cgi") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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
+
+my %search = (
+ 'country' => $cust_main_county->country,
+ 'state' => $cust_main_county->state,
+ );
+
+$search{'county'} = $cust_main_county->county
+ if $cust_main_county->city;
+
+foreach my $delete ( qsearch('cust_main_county', \%search) ) {
+# 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('');
+if ( $cust_main_county->city ) {
+ $cust_main_county->city('');
+} else {
+ $cust_main_county->county('');
+}
+my $error = $cust_main_county->insert;
+die $error if $error;
+
+</%init>
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..9984b08
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-expand.cgi
@@ -0,0 +1,83 @@
+<% include('/elements/header-popup.html', 'Addition successful' ) %>
+
+<SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+</SCRIPT>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$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('taxclass') ) {
+ my $sth = dbh->prepare('SELECT taxclass FROM part_pkg_taxclass')
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ @expansion = map $_->[0], @{$sth->fetchall_arrayref};
+ die "no taxclasses - add one first" unless @expansion;#XXX better err handling
+} else {
+ @expansion = split /[\n\r]{1,2}/, $cgi->param('expansion');
+
+ #warn scalar(@expansion);
+ #warn "$_: $expansion[$_]\n" foreach (0..$#expansion);
+
+ @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',$_);
+ } elsif ( ! $cust_main_county->county ) {
+ $new->setfield('county',$_);
+ } else {
+ #uppercase cities in the US to try and agree with USPS validation
+ $new->setfield('city', $new->country eq 'US' ? uc($_) : $_ );
+ }
+ my $error = $new->insert;
+ die $error if $error;
+}
+
+unless ( qsearch( 'cust_main', {
+ 'city' => $cust_main_county->city,
+ 'county' => $cust_main_county->county,
+ 'state' => $cust_main_county->state,
+ 'country' => $cust_main_county->country,
+ } )
+ || ! @expansion
+) {
+ my $error = $cust_main_county->delete;
+ die $error if $error;
+}
+
+if ( $cgi->param('taxclass') ) {
+ print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?".
+ 'city='. uri_escape($cust_main_county->city ).';'.
+ 'county='. uri_escape($cust_main_county->county ).';'.
+ 'state='. uri_escape($cust_main_county->state ).';'.
+ 'country='. uri_escape($cust_main_county->country)
+ );
+ myexit;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_county.html b/httemplate/edit/process/cust_main_county.html
new file mode 100644
index 0000000..cb56166
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county.html
@@ -0,0 +1,13 @@
+<% include( 'elements/process.html',
+ 'table' => 'cust_main_county',
+ 'popup_reload' => 'Tax changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_note.cgi b/httemplate/edit/process/cust_main_note.cgi
new file mode 100755
index 0000000..f904c59
--- /dev/null
+++ b/httemplate/edit/process/cust_main_note.cgi
@@ -0,0 +1,55 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_main_note.cgi?'. $cgi->query_string ) %>
+%} else {
+<% header('Note ' . ($notenum ? 'updated' : 'added') ) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die "Illegal custnum: ". $cgi->param('custnum');
+my $custnum = $1;
+
+$cgi->param('notenum') =~ /^(\d*)$/
+ or die "Illegal notenum: ". $cgi->param('notenum');
+my $notenum = $1;
+
+my $comment = $cgi->param('comment_html') ||
+ join("<br />\n",
+ split "(?:\r|\n)+", $cgi->param('comment_plain')
+ );
+
+my $new = new FS::cust_main_note ( {
+ notenum => $notenum,
+ custnum => $custnum,
+ _date => time,
+ usernum => $FS::CurrentUser::CurrentUser->usernum,
+ comments => $comment,
+} );
+
+my $error;
+if ($notenum) {
+
+ die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer note');
+
+ my $old = qsearchs('cust_main_note', { 'notenum' => $notenum });
+ $error = "No such note: $notenum" unless $old;
+ unless ($error) {
+ map { $new->$_($old->$_) } ('_date', 'otaker');
+ $error = $new->replace($old);
+ }
+
+} else {
+
+ die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer note');
+
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi
new file mode 100755
index 0000000..df506c6
--- /dev/null
+++ b/httemplate/edit/process/cust_pay.cgi
@@ -0,0 +1,57 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ) %>
+%} elsif ( $field eq 'invnum' ) {
+<% $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum") %>
+%} elsif ( $field 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( 'manual' => 1 );
+% }
+% if ( $link eq 'popup' ) {
+%
+<% header('Payment entered') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+%
+% } elsif ( $link eq 'custnum' ) {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum") %>
+% } else {
+% die "unknown link $link";
+% }
+%
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment');
+
+$cgi->param('linknum') =~ /^(\d+)$/
+ or die "Illegal linknum: ". $cgi->param('linknum');
+my $linknum = $1;
+
+$cgi->param('link') =~ /^(custnum|invnum|popup)$/
+ or die "Illegal link: ". $cgi->param('link');
+my $field = my $link = $1;
+$field = 'custnum' if $field eq 'popup';
+
+my $_date = parse_datetime($cgi->param('_date'));
+
+my $new = new FS::cust_pay ( {
+ $field => $linknum,
+ _date => $_date,
+ map {
+ $_, scalar($cgi->param($_));
+ } qw( paid payby payinfo paybatch
+ pkgnum
+ )
+ #} fields('cust_pay')
+} );
+
+my $error = $new->insert( 'manual' => 1 );
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay_pending.html b/httemplate/edit/process/cust_pay_pending.html
new file mode 100644
index 0000000..1bad6cf
--- /dev/null
+++ b/httemplate/edit/process/cust_pay_pending.html
@@ -0,0 +1,68 @@
+<% include('/elements/header-popup.html', $title ) %>
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error |h %></FONT>
+% } else {
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+% }
+</BODY>
+</HTML>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer pending payments');
+
+$cgi->param('action') =~ /^(\w+)$/ or die 'illegal action';
+my $action = $1;
+
+$cgi->param('paypendingnum') =~ /^(\d+)$/ or die 'illegal paypendingnum';
+my $paypendingnum = $1;
+my $cust_pay_pending =
+ qsearchs({
+ 'select' => 'cust_pay_pending.*',
+ 'table' => 'cust_pay_pending',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'paypendingnum' => $paypendingnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ })
+ or die 'unknown paypendingnum';
+
+my $error;
+my $title;
+if ( $action eq 'delete' ) {
+
+ $error = $cust_pay_pending->delete;
+ if ( $error ) {
+ $title = 'Error deleting pending payment';
+ } else {
+ $title = 'Pending payment deletion sucessful';
+ }
+
+} elsif ( $action eq 'insert_cust_pay' ) {
+
+ $error = $cust_pay_pending->insert_cust_pay;
+ if ( $error ) {
+ $title = 'Error completing pending payment';
+ } else {
+ $title = 'Pending payment completed';
+ }
+
+} elsif ( $action eq 'decline' ) {
+
+ $error = $cust_pay_pending->decline;
+ if ( $error ) {
+ $title = 'Error declining pending payment';
+ } else {
+ $title = 'Pending payment completed (decline)';
+ }
+
+} else {
+
+ die "unknown action $action";
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay_refund.cgi b/httemplate/edit/process/cust_pay_refund.cgi
new file mode 100755
index 0000000..2616cad
--- /dev/null
+++ b/httemplate/edit/process/cust_pay_refund.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_pay_refund.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_pay_refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
new file mode 100755
index 0000000..c564c41
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg.cgi
@@ -0,0 +1,42 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'edit/cust_pkg.cgi?'. $cgi->query_string );
+% } else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Bulk change customer packages');
+
+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( $action, $error_redirect ) = ( '', '' );
+my @pkgparts = ();
+
+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);
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg_detail.html b/httemplate/edit/process/cust_pkg_detail.html
new file mode 100644
index 0000000..132ff63
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg_detail.html
@@ -0,0 +1,59 @@
+% if ( $error ) {
+<% header('Error') %>
+<FONT COLOR="#ff0000"><B><% $error |h %></B></FONT><BR><BR>
+<CENTER><INPUT TYPE="BUTTON" VALUE="OK" onClick="parent.cClick()"></CENTER>
+</BODY></HTML>
+% } else {
+<% header($action) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+my %access_right = (
+ 'I' => 'Edit customer package invoice details',
+ 'C' => 'Edit customer package comments',
+);
+
+my %name = (
+ 'I' => 'invoice details',
+ 'C' => 'package comments',
+);
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+$cgi->param('detailtype') =~ /^(\w)$/ or die 'illegal detailtype';
+my $detailtype = $1;
+
+my $right = $access_right{$detailtype};
+die "access denied"
+ unless $curuser->access_right($right);
+
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'illegal pkgnum';
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+
+
+my @orig_details = $cust_pkg->cust_pkg_detail($detailtype);
+
+my $action = ucfirst($name{$detailtype}).
+ ( scalar(@orig_details) ? ' changed ' : ' added ' );
+
+my $param = $cgi->Vars;
+my @details = ();
+for ( my $row = 0; exists($param->{"detail$row"}); $row++ ) {
+ push @details, $param->{"detail$row"}
+ if $param->{"detail$row"} =~ /\S/;
+}
+
+my $error = $cust_pkg->set_cust_pkg_detail($detailtype, @details);
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg_discount.html b/httemplate/edit/process/cust_pkg_discount.html
new file mode 100644
index 0000000..e3249d7
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg_discount.html
@@ -0,0 +1,47 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'edit/cust_pkg_discount.html?'. $cgi->query_string );
+% } else {
+
+ <% header("Discount applied") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Discount customer package');
+
+#this search is really for security wrt agent virt...
+#maybe move it to the cust_pkg_discount->insert call?
+my $cust_pkg = qsearchs({
+ #'select' => 'cust_pkg.*',
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die 'unknown pkgnum' unless $cust_pkg;
+
+my $cust_pkg_discount = new FS::cust_pkg_discount {
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'discountnum' => scalar($cgi->param('discountnum')),
+ 'months_used' => 0,
+ 'end_date' => '', #XXX
+ 'otaker' => $curuser->username,
+ #for the create a new discount case
+ '_type' => scalar($cgi->param('discountnum__type')),
+ 'amount' => scalar($cgi->param('discountnum_amount')),
+ 'percent' => scalar($cgi->param('discountnum_percent')),
+ 'months' => scalar($cgi->param('discountnum_months')),
+ #'disabled' => $self->discountnum_disabled,
+};
+my $error = $cust_pkg_discount->insert;
+
+</%init>
diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi
new file mode 100755
index 0000000..5749e53
--- /dev/null
+++ b/httemplate/edit/process/cust_refund.cgi
@@ -0,0 +1,56 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ) %>
+%} else {
+%
+% if ( $link eq 'popup' ) {
+%
+<% header('Refund entered') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+% } else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% }
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Refund payment')
+ || $FS::CurrentUser::CurrentUser->access_right('Post refund');
+
+$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 $link = $cgi->param('popup') ? 'popup' : '';
+
+my $error = '';
+if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) {
+ my %options = ();
+ my $bop = $FS::payby::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');
+ my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01';
+ $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/;
+ $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund,
+ 'paynum' => $paynum,
+ 'reason' => $reason,
+ %options );
+} else {
+ my $new = new FS::cust_refund ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_refund') #huh? , 'paynum' )
+ } );
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi
new file mode 100644
index 0000000..e22cbb2
--- /dev/null
+++ b/httemplate/edit/process/cust_svc.cgi
@@ -0,0 +1,30 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+% my $svcdb = $new->part_svc->svcdb;
+<% $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum") %>
+%}
+<%init>
+
+die 'access deined'
+ unless $FS::CurrentUser::CurrentUser->access_right('Change customer service');
+
+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');
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_tax_adjustment.html b/httemplate/edit/process/cust_tax_adjustment.html
new file mode 100644
index 0000000..204b5b9
--- /dev/null
+++ b/httemplate/edit/process/cust_tax_adjustment.html
@@ -0,0 +1,41 @@
+% if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect($p.'cust_tax_adjustment.html?'. $cgi->query_string) %>
+% } else {
+<% header("Tax adjustment added") %>
+ <SCRIPT TYPE="text/javascript">
+ //window.top.location.reload();
+ parent.cClick();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $error = '';
+my $conf = new FS::conf;
+my $param = $cgi->Vars;
+
+$param->{"custnum"} =~ /^(\d+)$/
+ or $error .= "Illegal customer number " . $param->{"custnum"} . " ";
+my $custnum = $1;
+
+$param->{"amount"} =~ /^\s*(\d*(?:\.?\d{1,2}))\s*$/
+ or $error .= "Illegal amount " . $param->{"amount"} . " ";
+my $amount = $1;
+
+unless ( $error ) {
+
+ my $cust_tax_adjustment = new FS::cust_tax_adjustment {
+ 'custnum' => $custnum,
+ 'taxname' => $param->{'taxname'},
+ 'amount' => $amount,
+ 'comment' => $param->{'comment'},
+ };
+ $error = $cust_tax_adjustment->insert;
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/discount.html b/httemplate/edit/process/discount.html
new file mode 100644
index 0000000..eb4e92e
--- /dev/null
+++ b/httemplate/edit/process/discount.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'discount',
+ 'viewall_dir' => 'browse',
+ 'fields' => [ fields('discount'), '_type' ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi
new file mode 100755
index 0000000..2e427e4
--- /dev/null
+++ b/httemplate/edit/process/domain_record.cgi
@@ -0,0 +1,30 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+% my $svcnum = $new->svcnum;
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice');
+
+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');
+}
+
+</%init>
diff --git a/httemplate/edit/process/domreg.cgi b/httemplate/edit/process/domreg.cgi
new file mode 100755
index 0000000..a95474e
--- /dev/null
+++ b/httemplate/edit/process/domreg.cgi
@@ -0,0 +1,62 @@
+%if ($error) {
+% $cgi->param('error', $error);
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('op') =~ /^(register|transfer|revoke|renew)$/ or die "Illegal operation";
+my $operation = $1;
+#my($query) = $cgi->keywords;
+#$query =~ /^(\d+)$/;
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+my $svc_domain = qsearchs({
+ 'select' => 'svc_domain.*',
+ 'table' => 'svc_domain',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Unknown svcnum" unless $svc_domain;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $error = '';
+
+my @exports = $part_svc->part_export();
+
+my $registrar;
+my $export;
+
+# Find the first export that does domain registration
+foreach (@exports) {
+ $export = $_ if $_->can('registrar');
+}
+
+my $period = 1; # Current OpenSRS export can only handle 1 year registrations
+
+# If we have a domain registration export, get the registrar object
+if ($export) {
+ if ($operation eq 'register') {
+ $error = $export->register( $svc_domain, $period );
+ } elsif ($operation eq 'transfer') {
+ $error = $export->transfer( $svc_domain );
+ } elsif ($operation eq 'revoke') {
+ $error = $export->revoke( $svc_domain );
+ } elsif ($operation eq 'renew') {
+ $cgi->param('period') =~ /^(\d+)$/ or die "Illegal renewal period!";
+ $period = $1;
+ $error = $export->renew( $svc_domain, $period );
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/ApplicationCommon.html b/httemplate/edit/process/elements/ApplicationCommon.html
new file mode 100644
index 0000000..c7bdd3e
--- /dev/null
+++ b/httemplate/edit/process/elements/ApplicationCommon.html
@@ -0,0 +1,103 @@
+<%doc>
+
+Examples:
+
+ #cust_bill_pay
+ include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_bill_pay',
+ )
+
+ #cust_credit_bill
+ include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_bill',
+ )
+
+</%doc>
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). $opt{error_redirect}. '?'. $cgi->query_string ) %>
+%} else {
+<% header("$src_thing application$to sucessful") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+my %opt = @_;
+
+my $error = '';
+
+my $src_thing = ucfirst($opt{'src_thing'});
+my $src_table = $opt{'src_table'};
+my $src_pkey = dbdef->table($src_table)->primary_key;
+
+my $to = $opt{'link_table'} =~ /refund/ ? ' to Refund' : '';
+
+$cgi->param($src_pkey) =~ /^(\d+)$/ or die "Illegal $src_pkey!";
+my $src_pkeyvalue = $1;
+
+my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } )
+ or die "No such $src_pkey: $src_pkeyvalue";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $src->custnum } )
+ or die "Bogus $src_thing: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my @subnames = grep { /.+/ } map { /^subnum(\d+)$/ ? $1 : '' } $cgi->param;
+my @subitems = map { [ $cgi->param("subnum$_"), $cgi->param("subamount$_"), $cgi->param("taxXlocationnum$_") ] }
+ @subnames;
+{ local $^W = 0; @subitems = grep { $_->[1] + 0 } @subitems; }
+
+my %options = ();
+$options{subitems} = \@subitems if scalar(@subitems);
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+my $new;
+# $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 {
+
+ if ($src->amount != $cgi->param('src_amount')) {
+ $src->amount($cgi->param('src_amount'));
+ $error = $src->replace;
+ }
+
+ my $class = 'FS::'. $opt{link_table};
+
+ $new = $class->new( {
+ map {
+ $_ => scalar($cgi->param($_));
+ } fields($opt{link_table})
+ } );
+
+#}
+
+
+$options{manual} = 1;
+$error ||= $new->insert( %options );
+
+if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+} else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+</%init>
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
new file mode 100644
index 0000000..87cadb7
--- /dev/null
+++ b/httemplate/edit/process/elements/process.html
@@ -0,0 +1,309 @@
+<%doc>
+
+Example:
+
+ include( 'elements/process.html',
+
+ ###
+ # required
+ ###
+
+ 'table' => 'tablename',
+
+ #? 'primary_key' => #required when the dbdef doesn't know...???
+ #? 'fields' => [] #""
+
+ ###
+ # optional
+ ###
+
+ 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+ 'viewall_ext' => 'html', #'cgi' or 'html', defaults to 'html'
+ OR
+ 'redirect' => 'view/table.cgi?', # value of primary key is appended
+ # (string or coderef returning a string)
+ OR
+ 'popup_reload' => 'Momentary success message', #will reload parent window
+
+ 'error_redirect' => popurl(2).'edit/table.cgi?', #query string appended
+
+ 'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the
+ #naming is still inconsistent
+
+ 'copy_on_empty' => [ 'old_field_name', 'another_old_field', ... ],
+
+ 'clear_on_error' => [ 'form_field1', 'form_field2', ... ],
+
+ #pass an arrayref of hashrefs for multiple m2ms or m2names
+ #be certain you incorporate m2m_Common if you see error: param
+
+ 'process_m2m' => { 'link_table' => 'link_table_name',
+ 'target_table' => 'target_table_name',
+ #optional (see m2m_Common::process_m2m), if not specified
+ # all CGI params will be passed)
+ 'params' =>
+ },
+ 'process_m2name' => { 'link_table' => 'link_table_name',
+ 'link_static' => { 'column' => 'value' },
+ 'num_col' => 'column', #if column name is different in
+ #link_table than source_table
+ 'name_col' => 'name_column',
+ 'names_list' => [ 'list', 'names' ],
+
+ 'param_style' => 'link_table.value checkboxes',
+ #or#
+ 'param_style' => 'name_colN values',
+
+
+ },
+ 'process_o2m' => { 'table' => table_name',
+ 'num_col' => 'column', #if column name is different in
+ #link_table than source_table
+ },
+
+ #checks CGI params and whatever else before much else runs
+ #return an error string or empty for no error
+ 'precheck_callback' => sub { my( $cgi ) = @_; },
+
+ #supplies arguments to insert() and replace()
+ # for use with tables that are FS::option_Common (among other things)
+ 'args_callback' => sub { my( $cgi, $object ) = @_; },
+
+ 'debug' => 1, #turns on debugging output
+
+ #agent virtualization
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Access Right Name',
+
+ )
+
+</%doc>
+%if ( $error ) {
+%
+% my $edit_ext = $opt{'edit_ext'} || 'html';
+% my $url = $opt{'error_redirect'} || popurl(2)."$table.$edit_ext";
+% if ( length($cgi->query_string) > 1920 ) { #stupid IE 2083 URL limit
+%
+% my $session = int(rand(4294967296)); #XXX
+% my $pref = new FS::access_user_pref({
+% 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
+% 'prefname' => "redirect$session",
+% 'prefvalue' => $cgi->query_string,
+% 'expiration' => time + 3600, #1h? 1m?
+% });
+% my $pref_error = $pref->insert;
+% if ( $pref_error ) {
+% die "FATAL: couldn't even set redirect cookie: $pref_error".
+% " attempting to set redirect$session to ". $cgi->query_string."\n";
+% }
+%
+<% $cgi->redirect("$url?redirect=$session") %>
+%
+% } else {
+%
+<% $cgi->redirect("$url?". $cgi->query_string ) %>
+%
+% }
+%
+% #different ways of handling success
+%
+%} elsif ( $opt{'popup_reload'} ) {
+
+ <% include('/elements/header-popup.html', $opt{'popup_reload'} ) %>
+
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY>
+ </HTML>
+
+%} else {
+%
+% $opt{'redirect'} = &{$opt{'redirect'}}($cgi, $new)
+% if ref($opt{'redirect'}) eq 'CODE';
+%
+% if ( $opt{'redirect'} ) {
+%
+<% $cgi->redirect( $opt{'redirect'}. $pkeyvalue ) %>
+%
+% } else {
+%
+% my $ext = $opt{'viewall_ext'} || 'html';
+%
+<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.$ext" ) %>
+%
+% }
+%
+%}
+%
+<%init>
+
+my $me = 'process.html:';
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $error = '';
+if ( $opt{'precheck_callback'} ) {
+ $error = &{ $opt{'precheck_callback'} }( $cgi );
+}
+
+#false laziness w/edit.html
+my $table = $opt{'table'};
+my $class = "FS::$table";
+my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} ||
+my $fields = $opt{'fields'}
+ #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ];
+ || [ fields($table) ];
+
+my $pkeyvalue = $cgi->param($pkey);
+
+my $old = '';
+if ( $pkeyvalue ) {
+ $old = qsearchs({
+ 'table' => $table,
+ 'hashref' => { $pkey => $pkeyvalue },
+ 'extra_sql' => ( $opt{'agent_virt'}
+ ? ' AND '. $curuser->agentnums_sql(
+ 'null_right' => $opt{'agent_null_right'}
+ )
+ : ''
+ ),
+ });
+}
+
+my %hash =
+ map { my @entry = ( $_ => scalar($cgi->param($_)) );
+ $opt{'value_callback'} ? ( $_ => &{ $opt{'value_callback'} }( @entry ))
+ : ( @entry )
+ } @$fields;
+
+my $new = $class->new( \%hash );
+
+if ($old && exists($opt{'copy_on_empty'})) {
+ foreach my $field (@{$opt{'copy_on_empty'}}) {
+ $new->set($field, $old->get($field))
+ unless scalar($cgi->param($field));
+ }
+}
+
+if ( $opt{'agent_virt'} ) {
+
+ if ( ! $new->agentnum
+ && ( ! $opt{'agent_null_right'}
+ || ! $curuser->access_right($opt{'agent_null_right'})
+ )
+ )
+ {
+
+ $error ||= 'Select an agent';
+
+ } else {
+
+ die "illegal agentnum"
+ unless $curuser->agentnums_href->{$new->agentnum}
+ or $opt{'agent_null_right'}
+ && ! $new->agentnum
+ && $curuser->access_right($opt{'agent_null_right'});
+
+ }
+
+}
+
+$error ||= $new->check;
+
+my @args = ();
+if ( !$error && $opt{'args_callback'} ) {
+ @args = &{ $opt{'args_callback'} }( $cgi, $new );
+}
+
+if ( !$error && $opt{'debug'} ) {
+ warn "$me updating record in $table table using $class class\n";
+ warn Dumper(\%hash);
+ warn "with args: \n". Dumper(\@args) if @args;
+}
+
+if ( !$error ) {
+ if ( $pkeyvalue ) {
+ $error = $new->replace($old, @args);
+ } else {
+ $error = $new->insert(@args);
+ $pkeyvalue = $new->getfield($pkey);
+ }
+}
+
+if ( !$error && $opt{'process_m2m'} ) {
+
+ my @process_m2m = ref($opt{'process_m2m'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2m'} }
+ : ( $opt{'process_m2m'} );
+
+ foreach my $process_m2m (@process_m2m) {
+
+ $process_m2m->{'params'} ||= scalar($cgi->Vars);
+
+ warn "$me processing m2m:\n". Dumper( %$process_m2m )
+ if $opt{'debug'};
+
+ $error = $new->process_m2m( %$process_m2m );
+ }
+
+}
+
+if ( !$error && $opt{'process_m2name'} ) {
+
+ my @process_m2name = ref($opt{'process_m2name'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2name'} }
+ : ( $opt{'process_m2name'} );
+
+
+ foreach my $process_m2name (@process_m2name) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing m2name:\n". Dumper( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_m2name( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+}
+
+if ( !$error && $opt{'process_o2m'} ) {
+
+ my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
+ ? @{ $opt{'process_o2m'} }
+ : ( $opt{'process_o2m'} );
+
+
+ foreach my $process_o2m (@process_o2m) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing o2m:\n". Dumper( %{ $process_o2m },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_o2m( %{ $process_o2m },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ if ( $opt{'clear_on_error'} && scalar(@{$opt{'clear_on_error'}}) ) {
+ foreach my $field (@{$opt{'clear_on_error'}}) {
+ $cgi->param($field, '')
+ }
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html
new file mode 100644
index 0000000..8e8c99a
--- /dev/null
+++ b/httemplate/edit/process/elements/svc_Common.html
@@ -0,0 +1,15 @@
+%
+%
+% my %opt = @_;
+% my $table = $opt{'table'};
+% $opt{'fields'} ||= [ fields($table) ];
+% push @{ $opt{'fields'} }, qw( pkgnum svcpart );
+%
+%
+<% include( 'process.html',
+ 'edit_ext' => 'cgi',
+ 'redirect' => popurl(3)."view/$table.cgi?",
+ %opt,
+ )
+%>
+
diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi
new file mode 100644
index 0000000..6428763
--- /dev/null
+++ b/httemplate/edit/process/generic.cgi
@@ -0,0 +1,77 @@
+%if($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect($redirect_error . '?' . $cgi->query_string) %>
+%} else {
+<% $cgi->redirect($redirect_ok) %>
+%}
+<%doc>
+
+See elements/process.html, newer and somewhat along the same lines,
+though it still makes you setup a process file for the table.
+Perhaps safer, perhaps more of a pain in the ass.
+
+In any case, this is probably pretty deprecated; it is only used by
+part_virtual_field.cgi, and so its ACL is hardcoded to 'Configuration'.
+
+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).
+
+</%doc>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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());
+
+</%init>
diff --git a/httemplate/edit/process/inventory_class.html b/httemplate/edit/process/inventory_class.html
new file mode 100644
index 0000000..dbf978e
--- /dev/null
+++ b/httemplate/edit/process/inventory_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'inventory_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/invoice_logo.html b/httemplate/edit/process/invoice_logo.html
new file mode 100644
index 0000000..524d325
--- /dev/null
+++ b/httemplate/edit/process/invoice_logo.html
@@ -0,0 +1,25 @@
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+$cgi->param('type') =~ /^(png|eps)$/ or die "illegal type";
+my $type = $1;
+
+$cgi->param('name') =~ /^([^\.\/]*)$/ or die "illegal name";
+my $tname = my $name = $1;
+$tname = "_$tname" if length($tname);
+
+$cgi->param('preview_session') =~ /^(\w*)$/ or die "illegal preview_session";
+my $session = $1;
+my $data = decode_base64( $curuser->option("logo_preview$session") );
+
+$conf->set_binary("logo$name.$type", $data);
+
+$cgi->redirect(popurl(3). "edit/invoice_logo.html?type=$type;name=$name;msg=Logo%20changed");
+
+</%init>
diff --git a/httemplate/edit/process/invoice_template.html b/httemplate/edit/process/invoice_template.html
new file mode 100644
index 0000000..6c9371a
--- /dev/null
+++ b/httemplate/edit/process/invoice_template.html
@@ -0,0 +1,15 @@
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $confname = $cgi->param('confname');
+my $value = $cgi->param('value');
+
+$conf->set($confname, $value);
+
+$cgi->redirect(popurl(3). 'browse/invoice_template.html');
+
+</%init>
diff --git a/httemplate/edit/process/mailinglistmember.html b/httemplate/edit/process/mailinglistmember.html
new file mode 100644
index 0000000..f1842b8
--- /dev/null
+++ b/httemplate/edit/process/mailinglistmember.html
@@ -0,0 +1,6 @@
+<% include( 'elements/process.html',
+ 'table' => 'mailinglistmember',
+ 'popup_reload' => 'Member added',
+ )
+%>
+%#XXX ACL
diff --git a/httemplate/edit/process/msgcat.cgi b/httemplate/edit/process/msgcat.cgi
new file mode 100644
index 0000000..7175fa2
--- /dev/null
+++ b/httemplate/edit/process/msgcat.cgi
@@ -0,0 +1,22 @@
+%if ( $error ) {
+% $cgi->param('error',$error);
+<% $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/msgcat.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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;
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi
new file mode 100755
index 0000000..eb0529b
--- /dev/null
+++ b/httemplate/edit/process/part_bill_event.cgi
@@ -0,0 +1,106 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3)."browse/part_bill_event.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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 = '';
+
+ my $rnum;
+ my $rtype;
+ my $reasonm;
+ my $class = '';
+ $class='c' if ($eventcode =~ /cancel/);
+ $class='s' if ($eventcode =~ /suspend/);
+ if ($class) {
+ $cgi->param("${class}reason") =~ /^(-?\d+)$/
+ or $error = "Invalid ${class}reason";
+ $rnum = $1;
+ if ($rnum == -1) {
+ $cgi->param("new${class}reasonT") =~ /^(\d+)$/
+ or $error = "Invalid new${class}reasonT";
+ $rtype = $1;
+ $cgi->param("new${class}reason") =~ /^([\s\w]+)$/
+ or $error = "Invalid new${class}reason";
+ $reasonm = $1;
+ }
+ }
+
+ if ($rnum == -1 && !$error) {
+ my $reason = new FS::reason ({ 'reason' => $reasonm,
+ 'reason_type' => $rtype,
+ });
+ $error = $reason->insert;
+ unless ($error) {
+ $rnum = $reason->reasonnum;
+ $cgi->param("${class}reason", $rnum);
+ $cgi->param("new${class}reason", '');
+ $cgi->param("new${class}reasonT", '');
+ }
+ }
+
+ 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);
+
+ unless($error) {
+
+ if ( $eventpart ) {
+
+ my $new = new FS::part_bill_event ( {
+ map { $_ => scalar($cgi->param($_)) }
+ fields('part_bill_event'),
+ } );
+ $new->setfield('reason' => $rnum);
+ $error = $new->replace($old);
+
+ } else {
+
+ foreach my $payby ( $cgi->param('payby') ) {
+ my $new = new FS::part_bill_event ( {
+ map { $_ => scalar($cgi->param($_)) }
+ grep { $_ ne 'payby' }
+ fields('part_bill_event')
+ } );
+ $new->setfield('payby' => $payby);
+ $new->setfield('reason' => $rnum );
+ $error = $new->insert;
+ last if $error;
+ }
+
+ }
+
+ }
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_device.html b/httemplate/edit/process/part_device.html
new file mode 100644
index 0000000..399991f
--- /dev/null
+++ b/httemplate/edit/process/part_device.html
@@ -0,0 +1,15 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_device',
+ 'viewall_dir' => 'browse',
+ 'process_m2m' => {
+ 'link_table' => 'export_device',
+ 'target_table' => 'part_export',
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/part_event.html b/httemplate/edit/process/part_event.html
new file mode 100644
index 0000000..428025f
--- /dev/null
+++ b/httemplate/edit/process/part_event.html
@@ -0,0 +1,86 @@
+<% include( 'elements/process.html',
+ #'debug' => 1,
+ 'table' => 'part_event',
+ 'viewall_dir' => 'browse',
+ 'process_m2name' =>
+ {
+ 'link_table' => 'part_event_condition',
+ 'num_col' => 'eventpart',
+ 'name_col' => 'conditionname',
+ 'names_list' => [ FS::part_event_condition->all_conditionnames() ],
+ 'param_style' => 'name_colN values',
+ 'args_callback' => sub { # FS/FS/m2name_Common.pm
+ my( $object, $prefix, $params, $listref ) = @_;
+ #warn "$object $prefix $params $listref\n";
+
+ my $cond = $object->conditionname;
+
+ my %option_fields = $object->option_fields;
+
+ push @$listref, map {
+ my $field = $_;
+
+ my $cgi_field = "$prefix$cond.$field";
+
+ my $value = $params->{$cgi_field};
+
+ my $info = $option_fields{$_};
+ $info = { label=>$info, type=>'text' }
+ unless ref($info);
+
+ if ( $info->{'type'} =~
+ /^(select|checkbox)-?multiple$/
+ or $info->{'type'} =~ /^select/
+ && $info->{'multiple'}
+ )
+ {
+ #special processing for compound fields
+ $value = { map { $_ => 1 }
+ split(/\0/, $value)
+ };
+ } elsif ( $info->{'type'} eq 'freq' ) {
+ $value .= $params->{$cgi_field.'_units'};
+ }
+
+ #warn "value of $cgi_field is $value\n";
+
+ ( $field => $value );
+ }
+ keys %option_fields;
+ },
+ },
+
+ 'args_callback' => sub {
+
+ my( $cgi, $object ) = @_;
+
+ my $prefix = $object->action.'.';
+
+ map { my $option = $_;
+ #my $value = scalar( $cgi->param( "$prefix$option" ) );
+ my $value = join(',', $cgi->param( "$prefix$option" ) );
+
+ if ( $option eq 'reasonnum' && $value == -1 ) {
+ $value = {
+ 'typenum' => scalar( $cgi->param( "new$prefix${option}T" ) ),
+ 'reason' => scalar( $cgi->param( "new$prefix${option}" ) ),
+ };
+ }
+
+ ( $option => $value );
+ }
+ @{ $object->option_fields_listref };
+
+ },
+
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global billing events',
+)
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit billing events')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global billing events');
+
+</%init>
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
new file mode 100644
index 0000000..209419f
--- /dev/null
+++ b/httemplate/edit/process/part_export.cgi
@@ -0,0 +1,42 @@
+%if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/part_export.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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 @values = $cgi->param($_);
+ my $value = scalar(@values) > 1 ? join (' ', @values) : $values[0];
+ $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;
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
new file mode 100755
index 0000000..c0febf8
--- /dev/null
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -0,0 +1,222 @@
+<% include( 'elements/process.html',
+ #'debug' => 1,
+ 'table' => 'part_pkg',
+ 'agent_virt' => 1,
+ 'agent_null_right' => \@agent_null_right,
+ 'redirect' => $redirect_callback,
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'edit_ext' => 'cgi',
+ 'precheck_callback' => $precheck_callback,
+ 'args_callback' => $args_callback,
+ 'process_m2m' => \@process_m2m,
+ )
+%>
+<%init>
+
+my $customizing = ( ! $cgi->param('pkgpart') && $cgi->param('pkgnum') );
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $edit_global = 'Edit global package definitions';
+my $customize = 'Customize customer package';
+
+die "access denied"
+ unless $curuser->access_right('Edit package definitions')
+ || $curuser->access_right($edit_global)
+ || ( $customizing && $curuser->access_right($customize) );
+
+my @agent_null_right = ( $edit_global );
+push @agent_null_right, $customize if $customizing;
+
+
+my $precheck_callback = sub {
+ my( $cgi ) = @_;
+
+ my $conf = new FS::Conf;
+
+ foreach (qw( setuptax recurtax disabled )) {
+ $cgi->param($_, '') unless defined $cgi->param($_);
+ }
+
+ return 'Must select a tax class'
+ if $cgi->param('taxclass') eq '(select)';
+
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ return "At least one agent type must be specified."
+ unless scalar(@agents)
+ || ( $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ )
+ || ( !$cgi->param('pkgpart') && $conf->exists('agent-defaultpkg') )
+ || $cgi->param('disabled')
+ || $cgi->param('agentnum');
+
+ return '';
+
+};
+
+my $custnum = '';
+
+my $args_callback = sub {
+ my( $cgi, $new ) = @_;
+
+ my @args = ( 'primary_svc' => scalar($cgi->param('pkg_svc_primary')) );
+
+ ##
+ #options
+ ##
+
+ $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
+ my $plan = $1;
+
+ tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+ my $href = $plans{$plan}->{'fields'};
+
+ my $error = '';
+ my $options = $cgi->param($plan."__OPTIONS");
+ my @options = split(',', $options);
+ my %options =
+ map { my $optionname = $_;
+ my $param = $plan."__$optionname";
+ my $parser = exists($href->{$optionname}{parse})
+ ? $href->{$optionname}{parse}
+ : sub { shift };
+ my $value = join(', ', &$parser($cgi->param($param)));
+ my $check = $href->{$optionname}{check};
+ if ( $check && ! &$check($value) ) {
+ $value = join(', ', $cgi->param($param));
+ $error ||= "Illegal ".
+ ($href->{$optionname}{name}||$optionname). ": $value";
+ }
+ ( $optionname => $value );
+ }
+ grep { $_ !~ /^report_option_/ }
+ @options;
+
+ foreach ( split(',', $cgi->param('taxproductnums') ) ) {
+ my $value = $cgi->param("taxproductnum_$_");
+ $error ||= "Illegal taxproductnum_$_: $value"
+ unless ( $value =~ /^\d*$/ );
+ $options{"usage_taxproductnum_$_"} = $value;
+ }
+
+ foreach ( $cgi->param('report_option') ) {
+ $error ||= "Illegal optional report class: $_" unless ( $_ =~ /^\d*$/ );
+ $options{"report_option_$_"} = 1;
+ }
+
+ $options{$_} = scalar( $cgi->param($_) )
+ for (qw( setup_fee recur_fee ));
+
+ push @args, 'options' => \%options;
+
+ ###
+ #pkg_svc
+ ###
+
+ my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
+ map { $_->svcpart }
+ qsearch('part_svc', {} );
+
+ push @args, 'pkg_svc' => \%pkg_svc;
+
+ ###
+ # cust_pkg and custnum_ref (inserts only)
+ ###
+ unless ( $cgi->param('pkgpart') ) {
+ push @args, 'cust_pkg' => scalar($cgi->param('pkgnum')),
+ 'custnum_ref' => \$custnum;
+ }
+
+ #warn "args: ".join('/', @args). "\n";
+
+ @args;
+
+};
+
+my $redirect_callback = sub {
+ #my( $cgi, $new ) = @_;
+ return '' unless $custnum;
+ my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+ #my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+
+ #can we link back to the specific customized package? it would be nice...
+ popurl(3). "view/cust_main.cgi?custnum=$custnum$show;dummy=";
+};
+
+#these should probably move to @args above and be processed by part_pkg.pm...
+
+$cgi->param('tax_override') =~ /^([\d,]+)$/;
+my (@tax_overrides) = (grep "$_", split (",", $1));
+
+my @process_m2m = (
+ {
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'params' => \@tax_overrides,
+ },
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'svc', 'hidden' => '' },
+ 'params' => [ map $cgi->param($_),
+ grep /^svc_dst_pkgpart/, $cgi->param
+ ],
+ },
+ map {
+ my $hidden = $_;
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'bill', 'hidden' => $hidden },
+ 'params' => [ map { $cgi->param($_) }
+ grep { my $param = "bill_dst_pkgpart__hidden";
+ my $digit = '';
+ (($digit) = /^bill_dst_pkgpart(\d+)/ ) &&
+ $cgi->param("$param$digit") eq $hidden;
+ }
+ $cgi->param
+ ],
+ },
+ } ( '', 'Y' ),
+);
+
+foreach my $override_class ($cgi->param) {
+ next unless $override_class =~ /^tax_override_(\w+)$/;
+ my $class = $1;
+
+ my (@tax_overrides) = (grep "$_", split (",", $1))
+ if $cgi->param($override_class) =~ /^([\d,]+)$/;
+
+ push @process_m2m, {
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'hashref' => { 'usage_class' => $class },
+ 'params' => [ @tax_overrides ],
+ };
+
+}
+
+my $conf = new FS::Conf;
+
+if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) {
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ push @process_m2m, {
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'agent_type',
+ 'params' => \@agents,
+ };
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg_report_option.html b/httemplate/edit/process/part_pkg_report_option.html
new file mode 100644
index 0000000..052aabd
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_report_option.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_pkg_report_option',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg_taxclass.html b/httemplate/edit/process/part_pkg_taxclass.html
new file mode 100644
index 0000000..b37279f
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_taxclass.html
@@ -0,0 +1,17 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_pkg_taxclass',
+ 'redirect' => sub {
+ my( $cgi, $part_pkg_taxclass ) = @_;
+
+ popurl(3). 'browse/cust_main_county.cgi?'.
+ 'taxclass='. uri_escape($part_pkg_taxclass->taxclass).
+ ';dummy=';
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html
new file mode 100755
index 0000000..40cbc97
--- /dev/null
+++ b/httemplate/edit/process/part_referral.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_referral',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources');
+
+</%init>
diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi
new file mode 100755
index 0000000..65de3fc
--- /dev/null
+++ b/httemplate/edit/process/part_svc.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html
new file mode 100644
index 0000000..812c988
--- /dev/null
+++ b/httemplate/edit/process/payment_gateway.html
@@ -0,0 +1,22 @@
+<% include( 'elements/process.html',
+ 'table' => 'payment_gateway',
+ 'viewall_dir' => 'browse',
+ 'args_callback' => $args_callback,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $args_callback = sub {
+ my ( $cgi, $new ) = @_;
+
+ my @options = split(/\r?\n/, $cgi->param('gateway_options') );
+ pop @options
+ if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+ (@options)
+};
+
+
+</%init>
diff --git a/httemplate/edit/process/phone_device.html b/httemplate/edit/process/phone_device.html
new file mode 100644
index 0000000..df9d5e7
--- /dev/null
+++ b/httemplate/edit/process/phone_device.html
@@ -0,0 +1,18 @@
+<% include( 'elements/process.html',
+ 'table' => 'phone_device',
+ 'redirect' => sub {
+ my( $cgi, $phone_device ) = @_;
+ popurl(3).'view/svc_phone.cgi?'.
+ 'svcnum='. $phone_device->svcnum.
+ ';devicenum=';
+ },
+ )
+%>
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/pkg_category.html b/httemplate/edit/process/pkg_category.html
new file mode 100644
index 0000000..50cd5cb
--- /dev/null
+++ b/httemplate/edit/process/pkg_category.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'pkg_category',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/pkg_class.html b/httemplate/edit/process/pkg_class.html
new file mode 100644
index 0000000..b196df3
--- /dev/null
+++ b/httemplate/edit/process/pkg_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'pkg_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/prepay_credit.cgi b/httemplate/edit/process/prepay_credit.cgi
new file mode 100644
index 0000000..a55522b
--- /dev/null
+++ b/httemplate/edit/process/prepay_credit.cgi
@@ -0,0 +1,63 @@
+%unless ( ref($error) ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect(popurl(3). "edit/prepay_credit.cgi?". $cgi->query_string ) %>
+% } else {
+
+<% include('/elements/header.html', "$num prepaid cards generated".
+ ( $agent ? ' for '.$agent->agent : '' )
+ )
+%>
+
+<FONT SIZE="+1">
+% foreach my $card ( @$error ) {
+
+ <code><% $card %></code>
+ -
+ <% $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %>
+ <% $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %>
+ <% $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %>
+ <% $hashref->{upbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{upbytes}) : '' %>
+ <% $hashref->{downbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{downbytes}) : '' %>
+ <% $hashref->{totalbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{totalbytes}) : '' %>
+ <br>
+% }
+
+</FONT>
+
+<% include('/elements/footer.html') %>
+
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $hashref = {};
+
+my $agent = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agent = qsearchs('agent', { 'agentnum' => $hashref->{agentnum}=$1 } );
+}
+
+my $error = '';
+
+my $num = 0;
+if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+ $num = $1;
+} else {
+ $error = 'Illegal number of prepaid cards: '. $cgi->param('num');
+}
+
+$hashref->{amount} = $cgi->param('amount');
+$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier');
+$hashref->{upbytes} = $cgi->param('upbytes') * $cgi->param('upmultiplier');
+$hashref->{downbytes} = $cgi->param('downbytes') * $cgi->param('downmultiplier');
+$hashref->{totalbytes} = $cgi->param('totalbytes') * $cgi->param('totalmultiplier');
+
+$error ||= FS::prepay_credit::generate( $num,
+ scalar($cgi->param('type')),
+ $cgi->param('length'),
+ $hashref
+ );
+
+</%init>
diff --git a/httemplate/edit/process/prospect_main.html b/httemplate/edit/process/prospect_main.html
new file mode 100644
index 0000000..34d2642
--- /dev/null
+++ b/httemplate/edit/process/prospect_main.html
@@ -0,0 +1,34 @@
+<% include('elements/process.html',
+ 'table' => 'prospect_main',
+ 'args_callback' => $args_callback,
+ 'agent_virt' => 1,
+ 'process_o2m' => {
+ 'table' => 'contact',
+ 'fields' => [qw( first last title comment )],
+ },
+ 'redirect' => popurl(3). 'view/prospect_main.html?',
+ )
+%>
+<%init>
+
+my $args_callback = sub {
+ my( $cgi, $object ) = @_;
+
+ $cgi->param('locationnum') =~ /^(\-?\d*)$/
+ or die 'illegal locationnum '. $cgi->param('locationnum');
+ my $locationnum = $1;
+
+ return ( 'cust_location' => '' ) unless $locationnum;
+
+ my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) }
+ qw( address1 address2 city county state zip country )
+ };
+
+ $cust_location->locationnum($locationnum) unless $locationnum == -1;
+
+ ( 'cust_location' => $cust_location );
+
+};
+
+</%init>
diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi
new file mode 100644
index 0000000..484f6fc
--- /dev/null
+++ b/httemplate/edit/process/quick-charge.cgi
@@ -0,0 +1,75 @@
+% if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect($p.'quick-charge.html?'. $cgi->query_string) %>
+% } else {
+<% header("One-time charge added") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('One-time charge');
+
+my $error = '';
+my $conf = new FS::conf;
+my $param = $cgi->Vars;
+
+my @description = ();
+for ( my $row = 0; exists($param->{"description$row"}); $row++ ) {
+ push @description, $param->{"description$row"}
+ if ($param->{"description$row"} =~ /\S/);
+}
+
+$param->{"custnum"} =~ /^(\d+)$/
+ or $error .= "Illegal customer number " . $param->{"custnum"} . " ";
+my $custnum = $1;
+
+$param->{"amount"} =~ /^\s*(\d*(?:\.?\d{1,2}))\s*$/
+ or $error .= "Illegal amount " . $param->{"amount"} . " ";
+my $amount = $1;
+
+my $quantity = 1;
+if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
+ $quantity = $1;
+}
+
+$param->{'tax_override'} =~ /^\s*([,\d]*)\s*$/
+ or $error .= "Illegal tax override " . $param->{"tax_override"} . " ";
+my $override = $1;
+
+if ( $param->{'taxclass'} eq '(select)' ) {
+ $error .= "Must select a tax class. "
+ unless ($conf->exists('enable_taxproducts') &&
+ ( $override || $param->{taxproductnum} )
+ );
+ $cgi->param('taxclass', '');
+}
+
+unless ( $error ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or $error .= "Unknown customer number $custnum. ";
+
+ $error ||= $cust_main->charge( {
+ 'amount' => $amount,
+ 'quantity' => $quantity,
+ 'bill_now' => scalar($cgi->param('bill_now')),
+ 'invoice_terms' => scalar($cgi->param('invoice_terms')),
+ 'start_date' => ( scalar($cgi->param('start_date'))
+ ? parse_datetime($cgi->param('start_date'))
+ : ''
+ ),
+ 'no_auto' => scalar($cgi->param('no_auto')),
+ 'pkg' => scalar($cgi->param('pkg')),
+ 'setuptax' => scalar($cgi->param('setuptax')),
+ 'taxclass' => scalar($cgi->param('taxclass')),
+ 'taxproductnum' => scalar($cgi->param('taxproductnum')),
+ 'tax_override' => $override,
+ 'classnum' => scalar($cgi->param('classnum')),
+ 'additional' => \@description,
+ } );
+}
+
+</%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
new file mode 100644
index 0000000..2fde17f
--- /dev/null
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -0,0 +1,84 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(3). 'misc/order_pkg.html?'. $cgi->query_string ) %>
+%} else {
+% my $frag = "cust_pkg". $cust_pkg->pkgnum;
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+<% header('Package ordered') %>
+ <SCRIPT TYPE="text/javascript">
+ // XXX fancy ajax rebuild table at some point, but a page reload will do for now
+
+ // XXX chop off trailing #target and replace... ?
+ window.top.location = '<% popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" %>';
+
+ </SCRIPT>
+
+ </BODY></HTML>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Order customer package');
+
+#untaint custnum (probably not necessary, searching for it is escape enough)
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die 'unknown custnum' unless $cust_main;
+
+#probably not necessary, taken care of by cust_pkg::check
+$cgi->param('pkgpart') =~ /^(\d+)$/
+ or die 'illegal pkgpart '. $cgi->param('pkgpart');
+my $pkgpart = $1;
+$cgi->param('refnum') =~ /^(\d*)$/
+ or die 'illegal refnum '. $cgi->param('refnum');
+my $refnum = $1;
+$cgi->param('locationnum') =~ /^(\-?\d*)$/
+ or die 'illegal locationnum '. $cgi->param('locationnum');
+my $locationnum = $1;
+$cgi->param('discountnum') =~ /^(\-?\d*)$/
+ or die 'illegal discountnum '. $cgi->param('discountnum');
+my $discountnum = $1;
+
+
+my $cust_pkg = new FS::cust_pkg {
+ 'custnum' => $custnum,
+ 'pkgpart' => $pkgpart,
+ 'start_date' => ( scalar($cgi->param('start_date'))
+ ? parse_datetime($cgi->param('start_date'))
+ : ''
+ ),
+ 'no_auto' => scalar($cgi->param('no_auto')),
+ 'refnum' => $refnum,
+ 'locationnum' => $locationnum,
+ 'discountnum' => $discountnum,
+ #for the create a new discount case
+ 'discountnum__type' => scalar($cgi->param('discountnum__type')),
+ 'discountnum_amount' => scalar($cgi->param('discountnum_amount')),
+ 'discountnum_percent' => scalar($cgi->param('discountnum_percent')),
+ 'discountnum_months' => scalar($cgi->param('discountnum_months')),
+ #'discountnum_disabled' => scalar($cgi->param('discountnum_disabled')),
+};
+
+my %opt = ( 'cust_pkg' => $cust_pkg );
+
+if ( $locationnum == -1 ) {
+ my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) }
+ qw( custnum address1 address2 city county state zip country )
+ };
+ $opt{'cust_location'} = $cust_location;
+}
+
+my $error = $cust_main->order_pkg( %opt );
+
+</%init>
diff --git a/httemplate/edit/process/rate.cgi b/httemplate/edit/process/rate.cgi
new file mode 100755
index 0000000..48d9322
--- /dev/null
+++ b/httemplate/edit/process/rate.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::rate::process', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/rate_detail.html b/httemplate/edit/process/rate_detail.html
new file mode 100644
index 0000000..6200d61
--- /dev/null
+++ b/httemplate/edit/process/rate_detail.html
@@ -0,0 +1,13 @@
+<% include( 'elements/process.html',
+ 'table' => 'rate_detail',
+ 'popup_reload' => 'Rate changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/rate_region.cgi b/httemplate/edit/process/rate_region.cgi
new file mode 100755
index 0000000..8036f73
--- /dev/null
+++ b/httemplate/edit/process/rate_region.cgi
@@ -0,0 +1,57 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "rate_region.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/rate_region.html") %>
+%}
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $regionnum = $cgi->param('regionnum');
+
+my $old = qsearchs('rate_region', { 'regionnum' => $regionnum } ) if $regionnum;
+
+my $new = new FS::rate_region ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } ( fields('rate_region') )
+} );
+
+my $countrycode = $cgi->param('countrycode');
+my @npa = split(/\s*,\s*/, $cgi->param('npa'));
+$npa[0] = '' unless @npa;
+my @rate_prefix = map {
+ #my($npa,$nxx) = split('-', $_);
+ s/\D//g;
+ new FS::rate_prefix {
+ 'countrycode' => $countrycode,
+ #'npa' => $npa,
+ #'nxx' => $nxx,
+ 'npa' => $_,
+ }
+ } @npa;
+
+my @dest_detail = map {
+ my $ratenum = $_->ratenum;
+ new FS::rate_detail {
+ 'ratenum' => $ratenum,
+ map { $_ => $cgi->param("$_$ratenum") }
+ qw( min_included conn_charge conn_sec min_charge sec_granularity classnum )
+ };
+} qsearch('rate', {} );
+
+
+my $error;
+if ( $regionnum ) {
+ $error = $new->replace($old, 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => \@dest_detail, );
+} else {
+ $error = $new->insert( 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => \@dest_detail, );
+ $regionnum = $new->getfield('regionnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/reason.html b/httemplate/edit/process/reason.html
new file mode 100644
index 0000000..cb79ed2
--- /dev/null
+++ b/httemplate/edit/process/reason.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason',
+ 'redirect' => popurl(3) . 'browse/reason.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/reason_type.html b/httemplate/edit/process/reason_type.html
new file mode 100644
index 0000000..3172b27
--- /dev/null
+++ b/httemplate/edit/process/reason_type.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason_type',
+ 'redirect' => popurl(3) . 'browse/reason_type.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/reg_code.cgi b/httemplate/edit/process/reg_code.cgi
new file mode 100644
index 0000000..035e10b
--- /dev/null
+++ b/httemplate/edit/process/reg_code.cgi
@@ -0,0 +1,45 @@
+%unless ( ref($error) ) {
+% $cgi->param('error'. $error );
+<% $cgi->redirect(popurl(3). "edit/reg_code.cgi?". $cgi->query_string ) %>
+% } else {
+
+<% include("/elements/header.html","$num registration codes generated for ". $agent->agent, menubar(
+ 'View all agents' => popurl(3). 'browse/agent.cgi',
+) ) %>
+
+<PRE><FONT SIZE="+1">
+% foreach my $code ( @$error ) {
+ <% $code %>
+% }
+</FONT></PRE>
+
+<% include('/elements/footer.html') %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('agentnum') =~ /^(\d+)$/
+ or errorpage('illegal agentnum '. $cgi->param('agentnum'));
+my $agentnum = $1;
+my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+
+my $error = '';
+
+my $num = 0;
+if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+ $num = $1;
+} else {
+ $error = 'Illegal number of codes: '. $cgi->param('num');
+}
+
+my @pkgparts =
+ map { /^pkgpart(.*)$/; $1 }
+ grep { $cgi->param($_) }
+ grep { /^pkgpart/ }
+ $cgi->param;
+
+$error ||= $agent->generate_reg_codes($num, \@pkgparts);
+
+</%init>
diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi
new file mode 100644
index 0000000..3cbb8c5
--- /dev/null
+++ b/httemplate/edit/process/router.cgi
@@ -0,0 +1,20 @@
+<% include('elements/process.html',
+ 'table' => 'router',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'edit_ext' => 'cgi',
+ 'process_m2m' => { 'link_table' => 'part_svc_router',
+ 'target_table' => 'part_svc',
+ },
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ )
+%>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/process/svc_Common.html b/httemplate/edit/process/svc_Common.html
new file mode 100644
index 0000000..cf5f01f
--- /dev/null
+++ b/httemplate/edit/process/svc_Common.html
@@ -0,0 +1,16 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => $table,
+ 'redirect' => popurl(3)."view/svc_Common.html?svcdb=$table;svcnum=",
+ 'error_redirect' => popurl(3)."edit/svc_Common.html?svcdb=$table;",
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+</%init>
diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi
new file mode 100755
index 0000000..0d2c007
--- /dev/null
+++ b/httemplate/edit/process/svc_acct.cgi
@@ -0,0 +1,82 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ) %>
+%}
+<%init>
+use CGI::Carp;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$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 usergroup
+$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] );
+
+#unmunge bytecounts
+foreach (map { $_,$_."_threshold" } qw( upbytes downbytes totalbytes )) {
+ $cgi->param($_, FS::UI::bytecount::parse_bytecount($cgi->param($_)) );
+}
+
+#unmunge cgp_accessmodes (falze laziness-ish w/part_svc.pm::process &svc_domain)
+unless ( $cgi->param('cgp_accessmodes') ) {
+ $cgi->param('cgp_accessmodes',
+ join(' ',
+ sort map { /^cgp_accessmodes_([\w\/]+)$/ or die "no way"; $1; }
+ grep $cgi->param($_),
+ grep /^cgp_accessmodes_([\w\/]+)$/,
+ $cgi->param()
+ )
+ );
+}
+
+my %hash = $svcnum ? $old->hash : ();
+map {
+ $hash{$_} = 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 $new = new FS::svc_acct ( \%hash );
+
+my $error = '';
+
+$new->_password($old->_password) if $old;
+if ( $cgi->param('clear_password') eq '*HIDDEN*'
+ || $cgi->param('clear_password') =~ /^\(.* encrypted\)$/ ) {
+ die "fatal: no previous account to recall hidden password from!" unless $old;
+} else {
+ $error = $new->set_password($cgi->param('clear_password'));
+}
+
+if ( $svcnum ) {
+ foreach ( grep { $old->$_ != $new->$_ }
+ qw( seconds upbytes downbytes totalbytes )
+ )
+ {
+ my %hash = map { $_ => $new->$_ }
+ grep { $new->$_ }
+ qw( seconds upbytes downbytes totalbytes );
+
+ $error ||= $new->set_usage(\%hash); #unoverlimit and trigger radius changes
+ last; #once is enough
+ }
+ $error ||= $new->replace($old);
+} else {
+ $error ||= $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi
new file mode 100755
index 0000000..6e823a8
--- /dev/null
+++ b/httemplate/edit/process/svc_acct_pop.cgi
@@ -0,0 +1,33 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi") %>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Dialup configuration')
+ || $curuser->access_right('Dialup global configuration');
+
+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');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
new file mode 100644
index 0000000..d5c9820
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,8 @@
+<% include('elements/svc_Common.html', 'table' => 'svc_broadband') %>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi
new file mode 100755
index 0000000..381339b
--- /dev/null
+++ b/httemplate/edit/process/svc_domain.cgi
@@ -0,0 +1,57 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+#remove this to actually test the domains!
+$FS::svc_domain::whois_hack = 1;
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+#unmunge cgp_accessmodes (falze laziness-ish w/part_svc.pm::process & svc_acct)
+unless ( $cgi->param('cgp_accessmodes') ) {
+ $cgi->param('cgp_accessmodes',
+ join(' ',
+ sort map { /^cgp_accessmodes_([\w\/]+)$/ or die "no way"; $1; }
+ grep $cgi->param($_),
+ grep /^cgp_accessmodes_([\w\/]+)$/,
+ $cgi->param()
+ )
+ );
+}
+
+#unmunge acct_def_cgp_accessmodes (falze laziness-ahoy)
+unless ( $cgi->param('acct_def_cgp_accessmodes') ) {
+ $cgi->param('acct_def_cgp_accessmodes',
+ join(' ',
+ sort map { /^acct_def_cgp_accessmodes_([\w\/]+)$/ or die "no way"; $1; }
+ grep $cgi->param($_),
+ grep /^acct_def_cgp_accessmodes_([\w\/]+)$/,
+ $cgi->param()
+ )
+ );
+}
+
+my $new = new FS::svc_domain ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(svcnum pkgnum svcpart domain action)
+ } ( fields('svc_domain'), qw( pkgnum svcpart action ) )
+} );
+
+my $error = '';
+if ($cgi->param('svcnum')) {
+ $error = $new->replace();
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi
new file mode 100755
index 0000000..673e5a5
--- /dev/null
+++ b/httemplate/edit/process/svc_external.cgi
@@ -0,0 +1,31 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$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');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_external.html b/httemplate/edit/process/svc_external.html
new file mode 100644
index 0000000..3515afc
--- /dev/null
+++ b/httemplate/edit/process/svc_external.html
@@ -0,0 +1,10 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_external',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi
new file mode 100755
index 0000000..fffad84
--- /dev/null
+++ b/httemplate/edit/process/svc_forward.cgi
@@ -0,0 +1,31 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$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');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_mailinglist.html b/httemplate/edit/process/svc_mailinglist.html
new file mode 100644
index 0000000..580f6cc
--- /dev/null
+++ b/httemplate/edit/process/svc_mailinglist.html
@@ -0,0 +1,11 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_mailinglist',
+ 'fields' => [ fields('svc_mailinglist'), 'listname' ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html
new file mode 100644
index 0000000..e02ec5c
--- /dev/null
+++ b/httemplate/edit/process/svc_phone.html
@@ -0,0 +1,29 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_phone',
+ 'args_callback' => $args_callback,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $args_callback = sub {
+ my( $cgi, $object ) = @_;
+
+ my %opt = ();
+ if ( $cgi->param('locationnum') == -1 ) {
+ my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) }
+ qw( custnum address1 address2 city county state zip country )
+ };
+ $opt{'cust_location'} = $cust_location;
+ }
+
+ %opt;
+
+};
+
+
+
+</%init>
diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi
new file mode 100644
index 0000000..f02d253
--- /dev/null
+++ b/httemplate/edit/process/svc_www.cgi
@@ -0,0 +1,38 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$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;
+}
+
+</%init>
diff --git a/httemplate/edit/process/tax_class.html b/httemplate/edit/process/tax_class.html
new file mode 100644
index 0000000..339c908
--- /dev/null
+++ b/httemplate/edit/process/tax_class.html
@@ -0,0 +1,49 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "tax_class.html?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/tax_rate.cgi?taxclassnum=". uri_escape($tax_class->taxclassnum) ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $tax_class = new FS::tax_class {
+ 'taxclass' => $cgi->param('taxclass'),
+ 'description' => $cgi->param('description'),
+};
+
+#maybe this whole thing should be in a transaction. at some point, no biggie
+#none of the follow-up stuff will fail unless there's a more serious problem
+#than a hanging record in tax_class...
+
+my $error = $tax_class->insert;
+
+# all of this is highly dubious at the moment
+
+#unless ( $error ) {
+# #auto-add the new taxclass to any regions that have taxclasses already
+#
+# my $sth = dbh->prepare("
+# SELECT geocode FROM tax_rate
+# WHERE taxclass IS NOT NULL AND taxclass != ''
+# GROUP BY geocode
+# ") or die dbh->errstr;
+# $sth->execute or die $sth->errstr;
+#
+# while ( my $row = $sth->fetchrow_hashref ) {
+# warn "inserting for $row";
+# my $cust_main_county = new FS::tax_rate {
+# 'geocode' => $row->{geocode},
+# 'tax' => 0,
+# 'taxclassnum' => $tax_class->taxclassnum,
+# };
+# $error = $cust_main_county->insert;
+# #last if $error;
+# die $error if $error;
+# }
+#
+#}
+
+</%init>
diff --git a/httemplate/edit/process/tax_rate.html b/httemplate/edit/process/tax_rate.html
new file mode 100644
index 0000000..431e542
--- /dev/null
+++ b/httemplate/edit/process/tax_rate.html
@@ -0,0 +1,22 @@
+<% include( 'elements/process.html',
+ 'table' => 'tax_rate',
+ 'value_callback' => $value_callback,
+ 'popup_reload' => 'Tax changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%once>
+
+my $value_callback = sub { my ($field, $value) = @_;
+ ($field =~ /^(tax|excessrate|usetax|useexcessrate)$/)
+ ? $value/100
+ : $value
+ };
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/usage_class.html b/httemplate/edit/process/usage_class.html
new file mode 100644
index 0000000..cf50cb7
--- /dev/null
+++ b/httemplate/edit/process/usage_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'usage_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html
new file mode 100644
index 0000000..8ae76a9
--- /dev/null
+++ b/httemplate/edit/prospect_main.html
@@ -0,0 +1,115 @@
+<% include('elements/edit.html',
+ 'name_singular' => 'prospect',
+ 'table' => 'prospect_main',
+ 'labels' => { 'prospectnum' => 'Prospect',
+ 'agentnum' => 'Agent',
+ 'company' => 'Company',
+ 'contactnum' => 'Contact',
+ },
+ 'fields' => [
+ { 'field' => 'agentnum',
+ 'type' => 'select-agent',
+ 'empty_label' => 'Select agent',
+ },
+ { 'field' => 'company',
+ 'type' => 'text',
+ 'size' => 50,
+ },
+ { 'field' => 'contactnum',
+ 'type' => 'contact',
+ 'colspan' => 6,
+ ##actually o2m, but this seems to be working for edit so far
+ #'m2name_table' => 'contact',
+ #'m2name_namecol' => 'contactnum',
+ #'m2_label' => 'Contact',
+ #'m2_error_callback' => $m2_error_callback,
+
+ 'o2m_table' => 'contact',
+ 'm2_label' => 'Contact',
+ 'm2_error_callback' => $m2_error_callback,
+
+ },
+ { 'field' => 'locationnum',
+ 'type' => 'select-cust_location',
+ 'empty_label' => 'No address',
+ },
+ ],
+ 'edit_callback' => $edit_callback,
+ 'error_callbacck' => $error_callback,
+ 'agent_virt' => 1,
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $prospectnum;
+if ( $cgi->param('error') ) {
+ $prospectnum = scalar($cgi->param('prospectnum'));
+
+ die "access denied"
+ unless $curuser->access_right(($prospectnum ? 'Edit' : 'New'). ' prospect');
+
+} elsif ( $cgi->keywords ) { #editing
+
+ die "access denied"
+ unless $curuser->access_right('Edit prospect');
+
+} else { #new prospect
+
+ die "access denied"
+ unless $curuser->access_right('New prospect');
+
+}
+
+my $edit_callback = sub {
+ #my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+ my( $cgi, $prospect_main ) = @_;
+ my @cust_location =
+ qsearch('cust_location', { 'prospectnum' => $prospect_main->prospectnum } );
+ die 'multiple locations for prospect '. $prospect_main->prospectnum
+ if scalar(@cust_location) > 1;
+ $prospect_main->set('locationnum', $cust_location[0]->locationnum)
+ if scalar(@cust_location);
+ #warn 'prospect_main.locationnum '.$prospect_main->get('locationnum');
+};
+
+my $error_callback = sub {
+ #my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+ my( $cgi, $prospect_main ) = @_;
+ $cgi->param('locationnum') =~ /^(\-?\d*)$/
+ or die 'illegal locationnum '. $cgi->param('locationnum');
+ my $locationnum = $1;
+ $prospect_main->set('locationnum', $locationnum);
+};
+
+my $m2_error_callback = sub {
+ my($cgi, $object) = @_;
+
+ #process_o2m fields in process/prospect_main.html
+ my @fields = qw( first last title comment );
+ my @gfields = ( '', map "_$_", @fields );
+
+ map {
+ if ( /^contactnum(\d+)$/ ) {
+ my $num = $1;
+ if ( grep $cgi->param("contactnum$num$_"), @gfields ) {
+ my $x = new FS::contact {
+ 'contactnum' => $cgi->param("contactnum$num"),
+ map { $_ => scalar($cgi->param("contactnum${num}_$_")) } @fields,
+ };
+ use Data::Dumper; warn Dumper $x;
+ $x;
+ } else {
+ ();
+ }
+ } else {
+ ();
+ }
+ }
+ $cgi->param;
+};
+
+my @agentnums = $FS::CurrentUser::CurrentUser->agentnums;
+
+</%init>
diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html
new file mode 100644
index 0000000..a472915
--- /dev/null
+++ b/httemplate/edit/quick-charge.html
@@ -0,0 +1,294 @@
+<% include("/elements/header-popup.html", 'One-time charge', '',
+ ( $cgi->param('error') ? '' : 'onload="addRow()"' ),
+ )
+%>
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
+
+<% include('/elements/error.html') %>
+
+<SCRIPT TYPE="text/javascript">
+
+function enable_quick_charge () {
+ if ( document.QuickChargeForm.amount.value
+ && document.QuickChargeForm.pkg.value ) {
+ document.QuickChargeForm.submit.disabled = false;
+ } else {
+ document.QuickChargeForm.submit.disabled = true;
+ }
+}
+
+function validate_quick_charge () {
+ var pkg = document.QuickChargeForm.pkg.value;
+ var pkg_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ;
+ var amount = document.QuickChargeForm.amount.value;
+ var amount_regex = /^\s*\$?\s*(\d*(\.?\d{1,2}))\s*$/ ;
+ var rval = true;
+
+ if ( ! amount_regex.test(amount) ) {
+ alert('Illegal amount - enter an amount to charge, for example, "5" or "43" or "21.46".');
+ return false;
+ }
+ if ( String(pkg).length < 1 ) {
+ rval = false;
+ }
+ if ( ! pkg_regex.test(pkg) ) {
+ rval = false;
+ }
+ var i=0;
+ for (i=0; i < rownum; i++) {
+ if (! eval('pkg_regex.test(document.QuickChargeForm.description' + i + '.value)')){
+ rval = false;
+ break;
+ }
+ }
+ if (rval == true) {
+ return true;
+ }
+
+ if ( ! pkg ) {
+ alert('Enter a description for the one-time charge');
+ return false;
+ }
+
+ alert('Illegal description - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' );
+ return false;
+}
+
+function bill_now_changed (what) {
+ var form = what.form;
+ if ( what.checked ) {
+ form.start_date_text.disabled = true;
+ form.start_date.style.backgroundColor = '#dddddd';
+ form.start_date_button.style.display = 'none';
+ form.start_date_button_disabled.style.display = '';
+ form.invoice_terms.disabled = false;
+ } else {
+ form.start_date_text.disabled = false;
+ form.start_date.style.backgroundColor = '#ffffff';
+ form.start_date_button.style.display = '';
+ form.start_date_button_disabled.style.display = 'none';
+ form.invoice_terms.disabled = true;
+ }
+}
+
+</SCRIPT>
+
+<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" ID="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+<TABLE ID="QuickChargeTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc">
+
+<TR>
+ <TD ALIGN="right">Amount </TD>
+ <TD>
+ $<INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()">
+ </TD>
+</TR>
+
+% if ( $conf->exists('invoice-unitprice') ) {
+ <TR>
+ <TD ALIGN="right">Quantity </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>">
+ </TD>
+ </TR>
+% }
+
+<% include('/elements/tr-select-pkg_class.html', 'curr_value' => $cgi->param('classnum') ) %>
+
+<TR>
+ <TD ALIGN="right">Invoice now</TD>
+ <TD>
+ <INPUT TYPE = "checkbox"
+ NAME = "bill_now"
+ VALUE = "1"
+ <% $cgi->param('bill_now') ? 'CHECKED' : '' %>
+ onChange = "bill_now_changed(this);"
+ >
+ with terms
+ <% include('/elements/select-terms.html',
+ 'curr_value' => scalar($cgi->param('invoice_terms')),
+ 'empty_value' => $default_terms,
+ 'disabled' => ( $cgi->param('bill_now') ? 0 : 1 ),
+ )
+ %>
+ </TD>
+</TR>
+
+%# false laziness w/misc/order_pkg.html
+<TR>
+ <TD ALIGN="right">Charge date </TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "start_date"
+ SIZE = 32
+ ID = "start_date_text"
+ VALUE = "<% $start_date %>"
+ <% $cgi->param('bill_now') ? 'STYLE = "background-color:#dddddd" DISABLED' : '' %>
+ >
+ <IMG SRC = "<%$fsurl%>images/calendar.png"
+ ID = "start_date_button"
+ TITLE = "Select date"
+ STYLE = "cursor:pointer<% $cgi->param('bill_now') ? ';display:none' : '' %>"
+ >
+ <IMG SRC = "<%$fsurl%>images/calendar-disabled.png"
+ ID = "start_date_button_disabled"
+ <% $cgi->param('bill_now') ? '' : 'STYLE="display:none"' %>
+ >
+ <FONT SIZE=-1>(leave blank to charge immediately)</FONT>
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "start_date_text",
+ ifFormat: "<% $date_format %>",
+ button: "start_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
+% my $what = lc(FS::payby->shortname($cust_main->payby));
+ <TR>
+ <TD ALIGN="right">Disable automatic <% $what %> charge </TD>
+ <TD COLSPAN=6><INPUT TYPE="checkbox" NAME="no_auto" VALUE="Y"></TD>
+ </TR>
+% }
+
+<TR>
+ <TD ALIGN="right">Tax exempt </TD>
+ <TD><INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $cgi->param('setuptax') ? 'CHECKED' : '' %>></TD>
+</TR>
+
+<% include('/elements/tr-select-taxclass.html', 'curr_value' => $cgi->param('taxclass') ) %>
+
+<% include('/elements/tr-select-taxproduct.html', 'label' => 'Tax product', 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $cgi->param('taxproductnum') ) %>
+
+<% include('/elements/tr-select-taxoverride.html', 'onclick' => 'parent.taxoverridemagic(this);', 'curr_value' => $cgi->param('tax_override') ) %>
+
+<TR>
+ <TD ALIGN="right">Description </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg" SIZE="50" MAXLENGTH="50" VALUE="<% $pkg %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()">
+ </TD>
+</TR>
+
+<TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1">Optional additional description (also printed on invoice): </FONT></TD>
+</TR>
+
+% my $row = 0;
+% if ( $cgi->param('error') || $cgi->param('magic') ) {
+% my $param = $cgi->Vars;
+%
+% for ( $row = 0; exists($param->{"description$row"}); $row++ ) {
+
+ <TR>
+ <TD></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="description<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $param->{"description$row"} |h %>" rownum="<% $row %>" onkeyup = "possiblyAddRow;" >
+ </TD>
+ </TR>
+% }
+% }
+
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" ID="submit" NAME="submit" VALUE="Add one-time charge" <% $cgi->param('error') ? '' :' DISABLED' %>>
+
+</FORM>
+
+
+<SCRIPT TYPE="text/javascript">
+
+ var rownum = <% $row %>;
+
+ function possiblyAddRow() {
+ if ( ( rownum - this.getAttribute('rownum') ) == 1 ) {
+ addRow();
+ }
+ }
+
+ function addRow() {
+
+ var table = document.getElementById('QuickChargeTable');
+ var tablebody = table.getElementsByTagName('tbody').item(0);
+
+ var row = document.createElement('TR');
+
+ var empty_cell = document.createElement('TD');
+ row.appendChild(empty_cell);
+
+ var description_cell = document.createElement('TD');
+
+ var description_input = document.createElement('INPUT');
+ description_input.setAttribute('name', 'description'+rownum);
+ description_input.setAttribute('id', 'description'+rownum);
+ description_input.setAttribute('size', 60);
+ description_input.setAttribute('maxLength', 65);
+ description_input.setAttribute('rownum', rownum);
+ description_input.onkeyup = possiblyAddRow;
+ description_cell.appendChild(description_input);
+
+ row.appendChild(description_cell);
+
+ tablebody.appendChild(row);
+
+ rownum++;
+
+ }
+
+</SCRIPT>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('One-time charge');
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); #XXX agent-virt
+
+my $format = "%m/%d/%Y %T %z (%Z)"; #false laziness w/REAL_cust_pkg.cgi?
+my $start_date = $cust_main->next_bill_date;
+$start_date = $start_date ? time2str($format, $start_date) : '';
+
+my $amount = '';
+if ( $cgi->param('amount') =~ /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ) {
+ $amount = $1;
+}
+
+my $quantity = 1;
+if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
+ $quantity = $1;
+}
+
+$cgi->param('pkg') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
+ or die 'illegal description';
+my $pkg = $1;
+
+my $default_terms;
+if ( $cust_main->invoice_terms ) {
+ $default_terms = 'Customer default ('. $cust_main->invoice_terms. ')';
+} else {
+ $default_terms =
+ 'Default ('.
+ ($conf->config('invoice_default_terms') || 'Payable upon receipt').
+ ')';
+}
+
+</%init>
diff --git a/httemplate/edit/rate.cgi b/httemplate/edit/rate.cgi
new file mode 100644
index 0000000..75c7143
--- /dev/null
+++ b/httemplate/edit/rate.cgi
@@ -0,0 +1,45 @@
+<% include("/elements/header.html","$action Rate plan", menubar(
+ 'View all rate plans' => "${p}browse/rate.cgi",
+ ))
+%>
+
+<% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [ 'rate', 'preserve_rate_detail' ], # 'rate', 'min_', 'sec_' ],
+ 'process/rate.cgi',
+ $p.'browse/rate.cgi',
+ )
+%>
+<FORM NAME="OneTrueForm">
+<INPUT TYPE="hidden" NAME="ratenum" VALUE="<% $rate->ratenum %>">
+
+Rate plan
+<INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="<% $rate->ratename %>">
+<BR><BR>
+
+<INPUT TYPE="hidden" NAME="preserve_rate_detail" VALUE="1">
+
+<INPUT NAME="submit" TYPE="button" VALUE="<%
+ $rate->ratenum ? "Apply changes" : "Add rate plan"
+%>" onClick="document.OneTrueForm.submit.disabled=true; process();">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $rate;
+if ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $rate = qsearchs( 'rate', { 'ratenum' => $1 } );
+} else { #adding
+ $rate = new FS::rate {};
+}
+my $action = $rate->ratenum ? 'Edit' : 'Add';
+
+</%init>
diff --git a/httemplate/edit/rate_detail.html b/httemplate/edit/rate_detail.html
new file mode 100644
index 0000000..869ace8
--- /dev/null
+++ b/httemplate/edit/rate_detail.html
@@ -0,0 +1,72 @@
+<% include('elements/edit.html',
+ 'popup' => 1,
+ 'name' => $name,
+ 'table' => 'rate_detail',
+ 'labels' => { 'ratedetailnum' => 'Rate', #should hide...
+ 'dest_regionname' => 'Region',
+ 'dest_prefixes_short' => 'Prefix(es)',
+ 'min_included' => 'Included minutes/calls',
+ 'conn_charge' => 'Connection charge',
+ 'conn_sec' => 'For',
+ 'min_charge' => 'Charge per minute/call',
+ 'sec_granularity' => 'Granularity',
+ 'classnum' => 'Usage class',
+ },
+ 'fields' => [
+ { field=>'ratenum', type=>'hidden', },
+ { field=>'orig_regionnum', type=>'hidden', },
+ { field=>'dest_regionnum', type=>'hidden', },
+ { field=>'dest_regionname', type=>'fixed', },
+ { field=>'dest_prefixes_short', type=>'fixed', },
+ { field=>'min_included', type=>'text', size=>5 },
+ { field=>'conn_charge', type=>'money', size=>4 },
+ { field =>'conn_sec',
+ type =>'select',
+ options => [ keys %conn_secs ],
+ labels => \%conn_secs,
+ disable_empty => 1,
+ },
+ { field=>'min_charge', type=>'money', size=>4 },
+ { field =>'sec_granularity',
+ type =>'select',
+ options => [ keys %granularity ],
+ labels => \%granularity,
+ disable_empty => 1,
+ },
+ { field =>'classnum',
+ type =>'select-table',
+ table =>'usage_class',
+ name_col =>'classname',
+ empty_label =>'(default)',
+ hashref =>{ disabled => '' },
+ },
+
+ ],
+ )
+%>
+<%once>
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+tie my %conn_secs, 'Tie::IxHash', FS::rate_detail::conn_secs();
+
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#slightly inefficient, i suppose an edit+error callback would be better
+my $name = 'rate';
+my ($keywords) = $cgi->keywords;
+if ( $keywords =~ /^(\d+)$/
+ || $cgi->param('ratedetailnum') =~ /^(\d+)$/ ) {
+ my $rate_detail = qsearchs('rate_detail', { 'ratedetailnum' => $1 } )
+ or die "unknown ratedetailnum $1";
+ $name =
+ $rate_detail->rate->ratename. ' rate for '. $rate_detail->dest_regionname;
+}
+
+#sec_granularity should default to 60! for new rates when this gets used for em
+
+</%init>
diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi
new file mode 100644
index 0000000..f77c0db
--- /dev/null
+++ b/httemplate/edit/rate_region.cgi
@@ -0,0 +1,189 @@
+<% include("/elements/header.html","$action Region", menubar(
+ 'View all regions' => "${p}browse/rate_region.html",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/rate_region.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="regionnum" VALUE="<% $rate_region->regionnum %>">
+
+%# region info
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right">Region name</TH>
+ <TD><INPUT TYPE="text" NAME="regionname" SIZE=32 VALUE="<% $rate_region->regionname %>"></TR>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Country code</TH>
+ <TD><INPUT TYPE="text" NAME="countrycode" SIZE=4 MAXLENGTH=3 VALUE="<% $countrycode %>"></TR>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">
+ <B>Prefixes</B>
+ <BR><FONT SIZE="-1">(comma-separated)</FONT>
+ </TD>
+ <TD>
+ <TEXTAREA NAME="npa" WRAP=SOFT><% join(', ', map { $_->npa. (length($_->nxx) ? '-'.$_->nxx : '') } @rate_prefix ) %></TEXTAREA>
+ </TD>
+ </TR>
+
+</TABLE>
+
+%# rate plan info
+
+<BR>
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ Rate plan
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Included<BR>minutes/calls</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Connection<BR>charge</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Connection<BR>charge for</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Charge per<BR>minute/call</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Granularity</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Usage class</FONT>
+ </TH>
+ </TR>
+
+% foreach my $rate ( qsearch('rate', {}) ) {
+%
+% my $n = $rate->ratenum;
+% my $rate_detail = $rate->dest_detail($rate_region)
+% || new FS::rate_region { 'min_included' => 0,
+% 'min_charge' => 0,
+% 'sec_granularity' => '60'
+% };
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/rate.cgi?<% $rate->ratenum %>"><% $rate->ratename %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <INPUT TYPE="text" SIZE=9 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included |h %>">
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <%$money_char%><INPUT TYPE="text" SIZE=9 NAME="conn_charge<%$n%>" VALUE="<% $cgi->param("conn_charge$n") || $rate_detail->conn_charge |h %>">
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <SELECT NAME="conn_sec<%$n%>">
+% foreach my $conn_sec ( keys %conn_secs ) {
+% my $curr_value = $cgi->param("conn_sec$n") || $rate_detail->conn_sec;
+% my $selected = ($conn_sec==$curr_value) ? ' SELECTED' : '';
+ <OPTION VALUE="<% $conn_sec %>" <%$selected%>><% $conn_secs{$conn_sec} %></OPTION>
+% }
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <%$money_char%><INPUT TYPE="text" SIZE=6 NAME="min_charge<%$n%>" VALUE="<% $cgi->param("min_charge$n") || $rate_detail->min_charge |h %>">
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <SELECT NAME="sec_granularity<%$n%>">
+% foreach my $granularity ( keys %granularity ) {
+ <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%>
+% }
+ </SELECT>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% include( '/elements/select-table.html',
+ 'element_name' => "classnum$n",
+ 'table' => 'usage_class',
+ 'name_col' => 'classname',
+ 'empty_label' => '(default)',
+ 'hashref' => { disabled => '' },
+ 'curr_value' => ( $cgi->param("classnum$n") ||
+ $rate_detail->classnum ),
+ )
+ %>
+ </TD>
+
+ </TR>
+
+% }
+
+</TABLE>
+
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $rate_region->regionnum ? "Apply changes" : "Add region" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%once>
+
+tie my %conn_secs, 'Tie::IxHash', FS::rate_detail::conn_secs();
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $rate_region;
+if ( $cgi->param('error') ) {
+ $rate_region = new FS::rate_region ( {
+ map { $_, scalar($cgi->param($_)) } fields('rate_region')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable regionnum";
+ $rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } )
+ or die "unknown regionnum $1\n";
+} else { #adding
+ $rate_region = new FS::rate_region {};
+}
+my $action = $rate_region->regionnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+
+my @rate_prefix = $rate_region->rate_prefix;
+my $countrycode = '';
+if ( @rate_prefix ) {
+ $countrycode = $rate_prefix[0]->countrycode;
+ foreach my $rate_prefix ( @rate_prefix ) {
+ errorpage('multiple country codes per region not yet supported by web UI')
+ unless $rate_prefix->countrycode eq $countrycode;
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html
new file mode 100644
index 0000000..620a2ea
--- /dev/null
+++ b/httemplate/edit/reason.html
@@ -0,0 +1,50 @@
+%
+% $cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+% my $class=$1;
+%
+% my $classname = $FS::reason_type::class_name{$class};
+%
+% my (@types) = qsearch( 'reason_type', { 'class' => $class } );
+%
+% unless (scalar(@types)) {
+% print $cgi->redirect( "reason_type.html?class=$class" );
+% }
+<% include( 'elements/edit.html',
+ 'name' => ucfirst($classname) . ' Reason',
+ 'table' => 'reason',
+ 'labels' => {
+ 'reasonnum' => ucfirst($classname) . ' Reason',
+ 'reason_type' => ucfirst($classname) . ' Reason type',
+ 'reason' => ucfirst($classname) . ' Reason',
+ 'disabled' => 'Disabled',
+ 'class' => '',
+ },
+ 'fields' => [
+ { 'field' => 'reason_type',
+ 'type' => 'select',
+ #XXX use something more sane than a hashref
+ #then fix tr-select.html
+ 'value' => { 'vcolumn' => 'typenum',
+ 'ccolumn' => 'type',
+ 'values' => \@types,
+ },
+ },
+ 'reason',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ 'value' => $class,
+ },
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y'
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason.html?class=$class",
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/reason_type.html b/httemplate/edit/reason_type.html
new file mode 100644
index 0000000..ea5650e
--- /dev/null
+++ b/httemplate/edit/reason_type.html
@@ -0,0 +1,29 @@
+<% include( 'elements/edit.html',
+ 'name' => $classname . ' Reason Type',
+ 'table' => 'reason_type',
+ 'labels' => {
+ 'typenum' => $classname . ' reason type',
+ 'type' => $classname . ' reason type name',
+ 'class' => '',
+ },
+ 'fields' => [
+ 'type',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason_type.html?class=$class",
+ 'new_hashref_callback' => sub {{ 'class' => $class }},
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('class') =~ /^(\w)$/;
+my $class = $1;
+
+my $classname = $FS::reason_type::class_name{$class};
+
+</%init>
diff --git a/httemplate/edit/reg_code.cgi b/httemplate/edit/reg_code.cgi
new file mode 100644
index 0000000..76790ab
--- /dev/null
+++ b/httemplate/edit/reg_code.cgi
@@ -0,0 +1,44 @@
+<% include('/elements/header.html', 'Generate registration codes for '. $agent->agent) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/reg_code.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+
+Generate
+% my $num = '';
+% if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+% $num = $1;
+% }
+<INPUT TYPE="text" NAME="num" VALUE="<% $num %>" SIZE=5 MAXLENGTH=4>
+registration codes for <B><% $agent->agent %></B> allowing the following packages:
+<BR><BR>
+
+% foreach my $part_pkg ( qsearch('part_pkg', { 'disabled' => '' } ) ) {
+% my $pkgpart = $part_pkg->pkgpart;
+
+ <INPUT TYPE="checkbox" NAME="pkgpart<% $pkgpart %>" <% $cgi->param("pkgpart$pkgpart") ? 'CHECKED' : '' %>>
+ <% $part_pkg->pkg_comment %>
+ <BR>
+
+% }
+
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Generate">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = $cgi->param('agentnum');
+$agentnum =~ /^(\d+)$/ or errorpage("illegal agentnum $agentnum");
+$agentnum = $1;
+my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+
+</%init>
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
new file mode 100755
index 0000000..70eaa45
--- /dev/null
+++ b/httemplate/edit/router.cgi
@@ -0,0 +1,54 @@
+<% include('elements/edit.html',
+ 'post_url' => popurl(1).'process/router.cgi',
+ 'name' => 'router',
+ 'table' => 'router',
+ 'viewall_url' => "${p}browse/router.cgi",
+ 'labels' => { 'routernum' => 'Router',
+ 'routername' => 'Name',
+ 'svc_part' => 'Service',
+ },
+ 'fields' => [
+ { 'field'=>'routername', 'type'=>'text', 'size'=>32 },
+ { 'field'=>'agentnum', 'type'=>'select-agent' },
+ { 'field'=>'svcnum', 'type'=>'hidden' },
+ ],
+ 'error_callback' => $callback,
+ 'edit_callback' => $callback,
+ 'new_callback' => $callback,
+ 'html_table_bottom' => $html_table_bottom,
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+my $callback = sub {
+ my ($cgi, $object, $fields) = (shift, shift, shift);
+ unless ($object->svcnum) {
+ push @{$fields},
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Select the service types available on this router',
+ },
+ { 'field' => 'svc_part',
+ 'type' => 'checkboxes-table',
+ 'target_table' => 'part_svc',
+ 'link_table' => 'part_svc_router',
+ 'name_col' => 'svc',
+ 'hashref' => { 'svcdb' => 'svc_broadband', 'disabled' => '' },
+ };
+ }
+};
+
+my $html_table_bottom = sub {
+ my $router = shift;
+ my $html = '';
+ foreach my $field ($router->virtual_fields) {
+ $html .= $router->pvf($field)->widget('HTML', 'edit', $router->get($field));
+ }
+ $html;
+};
+</%init>
diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html
new file mode 100644
index 0000000..3da72d2
--- /dev/null
+++ b/httemplate/edit/svc_Common.html
@@ -0,0 +1,33 @@
+<% include('elements/svc_Common.html',
+ 'table' => $table,
+ 'post_url' => popurl(1). "process/svc_Common.html",
+ %opt,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+# false laziness w/view/svc_Common.html
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+my %opt;
+if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
+ $opt{'name'} = "FS::$table"->table_info->{'name'};
+
+ my $fields = "FS::$table"->table_info->{'fields'};
+ my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+ $opt{'labels'} = \%labels;
+
+}
+
+</%init>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
new file mode 100755
index 0000000..5c2d5f4
--- /dev/null
+++ b/httemplate/edit/svc_acct.cgi
@@ -0,0 +1,754 @@
+<% include('/elements/header.html', "$action $svc account") %>
+
+<% include('/elements/error.html') %>
+
+% if ( $cust_main ) {
+
+ <% include( '/elements/small_custview.html', $cust_main, '', 1,
+ popurl(2) . "view/cust_main.cgi") %>
+ <BR>
+% }
+
+<SCRIPT TYPE="text/javascript">
+function randomPass() {
+ var i=0;
+ var pw_set='<% join('', 'a'..'z', 'A'..'Z', '0'..'9' ) %>';
+ var pass='';
+ while(i < 8) {
+ i++;
+ pass += pw_set.charAt(Math.floor(Math.random() * pw_set.length));
+ }
+ document.OneTrueForm.clear_password.value = pass;
+}
+</SCRIPT>
+
+<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 %>">
+
+Service # <% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>>
+ </TD>
+</TR>
+
+%if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) {
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
+ <INPUT TYPE="button" VALUE="Generate" onclick="randomPass();">
+ </TD>
+</TR>
+%}else{
+ <INPUT TYPE="hidden" NAME="clear_password" VALUE="<% $password %>">
+%}
+<INPUT TYPE="hidden" NAME="_password_encoding" VALUE="<% $svc_acct->_password_encoding %>">
+%
+%my $sec_phrase = $svc_acct->sec_phrase;
+%if ( $conf->exists('security_phrase')
+% && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) {
+%
+
+
+ <TR>
+ <TD ALIGN="right">Security phrase</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32>
+ (for forgotten passwords)
+ </TD>
+ </TD>
+% } else {
+
+
+ <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' ) {
+%
+
+
+ <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";
+% }
+% }
+%
+% %svc_domain = (%svc_domain,
+% domain_select_hash FS::svc_acct('svcpart' => $svcpart,
+% 'pkgnum' => $pkgnum,
+% )
+% );
+%
+
+
+ <TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+ <SELECT NAME="domsvc" SIZE=1>
+% foreach my $svcnum (
+% sort { $svc_domain{$a} cmp $svc_domain{$b} }
+% keys %svc_domain
+% ) {
+% my $svc_domain = $svc_domain{$svcnum};
+%
+
+
+ <OPTION VALUE="<% $svcnum %>" <% $svcnum == $domsvc ? ' SELECTED' : '' %>><% $svc_domain{$svcnum} %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+
+
+% if ( $communigate ) {
+
+ <TR>
+ <TD ALIGN="right">Aliases</TD>
+ <TD><INPUT TYPE="text" NAME="cgp_aliases" VALUE="<% $svc_acct->cgp_aliases %>"></TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="cgp_aliases" VALUE="<% $svc_acct->cgp_aliases %>">
+% }
+
+
+<% include('/elements/tr-select-svc_pbx.html',
+ 'curr_value' => $svc_acct->pbxsvc,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ )
+%>
+
+%#pop
+%my $popnum = $svc_acct->popnum || 0;
+%if ( $part_svc->part_svc_column('popnum')->columnflag eq 'F' ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Access number</TD>
+ <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
+ </TR>
+% }
+% #uid/gid
+% foreach my $xid (qw( uid gid )) {
+%
+% if ( $part_svc->part_svc_column($xid)->columnflag =~ /^[FA]$/
+% || ! $conf->exists("svc_acct-edit_$xid")
+% ) {
+%
+% if ( length($svc_acct->$xid()) ) {
+
+
+ <TR>
+ <TD ALIGN="right"><% uc($xid) %></TD>
+ <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD>
+ <TD>
+ </TD>
+ </TR>
+% }
+
+
+ <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right"><% uc($xid) %></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>">
+ </TD>
+ </TR>
+% }
+% }
+%
+%#finger
+%if ( $part_svc->part_svc_column('uid')->columnflag eq 'F'
+% && ! $svc_acct->finger ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="finger" VALUE="">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Real Name</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>">
+ </TD>
+ </TR>
+% }
+%
+%#dir
+%if ( $part_svc->part_svc_column('dir')->columnflag eq 'F'
+% || !$curuser->access_right('Edit home dir')
+% ) {
+
+
+<INPUT TYPE="hidden" NAME="dir" VALUE="<% $svc_acct->dir %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Home directory</TD>
+ <TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD>
+ </TR>
+% }
+%
+%#shell
+%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' )
+% ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="shell" VALUE="<% $shell %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Shell</TD>
+ <TD>
+ <SELECT NAME="shell" SIZE=1>
+%
+% my($etc_shell);
+% foreach $etc_shell (@shells) {
+%
+
+
+ <OPTION<% $etc_shell eq $shell ? ' SELECTED' : '' %>><% $etc_shell %>
+% }
+
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+
+
+% if ( $communigate
+% && $part_svc->part_svc_column('cgp_type')->columnflag ne 'F' )
+% {
+
+% # settings
+
+ <TR>
+ <TD ALIGN="right">Mailbox type</TD>
+ <TD>
+ <SELECT NAME="cgp_type">
+% foreach my $option (qw( MultiMailbox TextMailbox MailDirMailbox
+% AGrade BGrade CGrade )) {
+ <OPTION VALUE="<% $option %>"
+ <% $option eq $svc_acct->cgp_type() ? 'SELECTED' : '' %>
+ ><% $option %>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="cgp_type" VALUE="<% $svc_acct->cgp_type() %>">
+% }
+
+
+% #false laziness w/svc_domain
+% if ( $communigate
+% && $part_svc->part_svc_column('cgp_accessmodes')->columnflag ne 'F' )
+% {
+
+ <TR>
+ <TD ALIGN="right">Enabled services</TD>
+ <TD>
+ <% include( '/elements/communigate_pro-accessmodes.html',
+ 'curr_value' => $svc_acct->cgp_accessmodes,
+ )
+ %>
+ </TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="cgp_accessmodes" VALUE="<% $svc_acct->cgp_accessmodes() |h %>">
+% }
+
+
+% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) {
+ <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>">
+% } else {
+% my $quota_label = $communigate ? 'Mail storage limit' : 'Quota';
+ <TR>
+ <TD ALIGN="right"><% $quota_label %></TD>
+ <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD>
+ </TR>
+% }
+
+% tie my %cgp_label, 'Tie::IxHash',
+% 'file_quota' => 'File storage limit',
+% 'file_maxnum' => 'Number of files limit',
+% 'file_maxsize' => 'File size limit',
+% ;
+%
+% foreach my $key (keys %cgp_label) {
+%
+% if ( !$communigate || $part_svc->part_svc_column($key)->columnflag eq 'F' ){
+ <INPUT TYPE="hidden" NAME="<%$key%>" VALUE="<% $svc_acct->$key() |h %>">
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $cgp_label{$key} %></TD>
+ <TD><INPUT TYPE="text" NAME="<% $key %>" VALUE="<% $svc_acct->$key() |h %>"></TD>
+ </TR>
+
+% }
+% }
+
+% if ( $communigate ) {
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Password recovery',
+ 'field' => 'password_recover',
+ 'curr_value' => $svc_acct->password_recover,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Allowed mail rules',
+ 'field' => 'cgp_rulesallowed',
+ 'options' => [ '', 'No', 'Filter Only', 'All But Exec', 'Any' ],
+ 'labels' => {
+ '' => 'default (No)', #No always the default?
+ },
+ 'curr_value' => $svc_acct->cgp_rulesallowed,
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'RPOP modifications',
+ 'field' => 'cgp_rpopallowed',
+ 'curr_value' => $svc_acct->cgp_rpopallowed,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Accepts mail to "all"',
+ 'field' => 'cgp_mailtoall',
+ 'curr_value' => $svc_acct->cgp_mailtoall,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Add trailer to sent mail',
+ 'field' => 'cgp_addmailtrailer',
+ 'curr_value' => $svc_acct->cgp_addmailtrailer,
+ 'value' => 'Y',
+ )
+ %>
+
+% #preferences
+
+%# false laziness w/svc_domain acct_def
+ <TR>
+ <TD ALIGN="right">Message delete method</TD>
+ <TD>
+ <SELECT NAME="cgp_deletemode">
+% for ( 'Move To Trash', 'Immediately', 'Mark' ) {
+ <OPTION VALUE="<% $_ %>"
+ <% $_ eq $svc_acct->cgp_deletemode ? 'SELECTED' : '' %>
+ ><% $_ %>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">On logout remove trash</TD>
+ <TD><INPUT TYPE="text" NAME="cgp_emptytrash" VALUE="<% $svc_acct->cgp_emptytrash %>"></TD>
+ </TR>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Language',
+ 'field' => 'cgp_language',
+ 'options' => [ '', qw( English Arabic Chinese Dutch French German Hebrew Italian Japanese Portuguese Russian Slovak Spanish Thai ) ],
+ 'labels' => {
+ '' => 'default (English)',
+ },
+ 'curr_value' => $svc_acct->cgp_language,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Time zone',
+ 'field' => 'cgp_timezone',
+ 'options' => [
+ 'HostOS',
+ '(+0100) Algeria/Congo',
+ '(+0200) Egypt/South Africa',
+ '(+0300) Saudi Arabia',
+ '(+0400) Oman',
+ '(+0500) Pakistan',
+ '(+0600) Bangladesh',
+ '(+0700) Thailand/Vietnam',
+ '(+0800) China/Malaysia',
+ '(+0900) Japan/Korea',
+ '(+1000) Queensland',
+ '(+1100) Micronesia',
+ '(+1200) Fiji',
+ '(+1300) Tonga/Kiribati',
+ '(+1400) Christmas Islands',
+ '(-0100) Azores/Cape Verde',
+ '(-0200) Fernando de Noronha',
+ '(-0300) Argentina/Uruguay',
+ '(-0400) Venezuela/Guyana',
+ '(-0500) Haiti/Peru',
+ '(-0600) Central America',
+ '(-0700) Arisona',
+ '(-0800) Adamstown',
+ '(-0900) Marquesas Islands',
+ '(-1000) Hawaii/Tahiti',
+ '(-1100) Samoa',
+ 'Asia/Afghanistan',
+ 'Asia/India',
+ 'Asia/Iran',
+ 'Asia/Iraq',
+ 'Asia/Israel',
+ 'Asia/Jordan',
+ 'Asia/Lebanon',
+ 'Asia/Syria',
+ 'Australia/Adelaide',
+ 'Australia/East',
+ 'Australia/NorthernTerritory',
+ 'Europe/Central',
+ 'Europe/Eastern',
+ 'Europe/Moscow',
+ 'Europe/Western',
+ 'GMT (+0000)',
+ 'Newfoundland',
+ 'NewZealand/Auckland',
+ 'NorthAmerica/Alaska',
+ 'NorthAmerica/Atlantic',
+ 'NorthAmerica/Central',
+ 'NorthAmerica/Eastern',
+ 'NorthAmerica/Mountain',
+ 'NorthAmerica/Pacific',
+ 'Russia/Ekaterinburg',
+ 'Russia/Irkutsk',
+ 'Russia/Kamchatka',
+ 'Russia/Krasnoyarsk',
+ 'Russia/Magadan',
+ 'Russia/Novosibirsk',
+ 'Russia/Vladivostok',
+ 'Russia/Yakutsk',
+ 'SouthAmerica/Brasil',
+ 'SouthAmerica/Chile',
+ 'SouthAmerica/Paraguay',
+ ],
+ 'labels' => {
+ '' => 'default (HostOS)',
+ },
+ 'curr_value' => $svc_acct->cgp_timezone,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Layout',
+ 'field' => 'cgp_skinname',
+ 'options' => [ '', '***', 'GoldFleece', 'Skin2' ],
+ 'labels' => {
+ '' => 'default (***)',
+ },
+ 'curr_value' => $svc_acct->cgp_skinname,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Pronto style',
+ 'field' => 'cgp_prontoskinname',
+ 'options' => [ '', 'Pronto', 'Pronto-darkflame', 'Pronto-steel', 'Pronto-twilight', ],
+
+ 'curr_value' => $svc_acct->cgp_prontoskinname,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Send read receipts',
+ 'field' => 'cgp_sendmdnmode',
+ 'options' => [ '', 'Never', 'Manually', 'Automatically' ],
+ 'labels' => {
+ '' => 'default (Automatically)',
+ },
+ 'curr_value' => $svc_acct->cgp_language,
+ )
+ %>
+
+%#XXX vacation message, redirect all mail, mail rules
+
+% } else {
+
+% for (qw( password_recover cgp_rulesallowed cgp_rpopallowed cgp_mailtoall
+% cgp_addmailtrailer
+% cgp_deletemode cgp_emptytrash cgp_language cgp_timezone
+% cgp_skinname cgp_sendmdnmode
+% ) ) {
+
+ <INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $svc_acct->$_() %>">
+% }
+
+% }
+
+
+% if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) {
+ <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
+% } else {
+ <TR>
+ <TD ALIGN="right">IP</TD>
+ <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
+ </TR>
+% }
+
+% my %label = ( seconds => 'Time',
+% upbytes => 'Upload bytes',
+% downbytes => 'Download bytes',
+% totalbytes => 'Total bytes',
+% );
+% foreach my $uf (keys %label) {
+% my $tf = $uf . "_threshold";
+% if ( $curuser->access_right('Edit usage') ) {
+ <TR>
+ <TD ALIGN="right"><% $label{$uf} %> remaining</TD>
+ <TD><INPUT TYPE="text" NAME="<% $uf %>" VALUE="<% $svc_acct->$uf %>">(blank disables)</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><% $label{$uf} %> threshold</TD>
+ <TD><INPUT TYPE="text" NAME="<% $tf %>" VALUE="<% $svc_acct->$tf %>">(blank disables)</TD>
+ </TR>
+% }else{
+ <INPUT TYPE="hidden" NAME="<% $uf %>" VALUE="<% $svc_acct->$uf %>">
+ <INPUT TYPE="hidden" NAME="<% $tf %>" VALUE="<% $svc_acct->$tf %>">
+% }
+% }
+%
+%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 =~ /^[FA]$/ ) {
+
+
+ <INPUT TYPE="hidden" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right"><% $FS::raddb::attrib{$a} %></TD>
+ <TD><INPUT TYPE="text" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"></TD>
+ </TR>
+% }
+% }
+
+
+
+<TR>
+ <TD ALIGN="right">RADIUS groups</TD>
+% if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) {
+
+
+ <TD BGCOLOR="#eeeeee"><% join('<BR>', @groups) %></TD>
+% } else {
+
+
+ <TD><% FS::svc_acct::radius_usergroup_selector( \@groups ) %></TD>
+% }
+
+
+</TR>
+% foreach my $field ($svc_acct->virtual_fields) {
+% # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+
+
+ <% $svc_acct->pvf($field)->widget('HTML', 'edit', $svc_acct->getfield($field)) %>
+% }
+% }
+
+
+</TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+my @shells = $conf->config('shells');
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+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');
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_acct = new FS::svc_acct({svcpart => $svcpart});
+
+ $svcnum='';
+
+ $svc_acct->password_recover('Y'); #default. hmm.
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $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;
+
+}
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my( $cust_pkg, $cust_main ) = ( '', '' );
+if ( $pkgnum ) {
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } );
+ $cust_main = $cust_pkg->cust_main;
+}
+
+unless ( $svcnum || $cgi->param('error') ) { #adding
+
+ #set gecos
+ if ($cust_main) {
+ unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+ $svc_acct->setfield('finger',
+ $cust_main->getfield('first') . " " . $cust_main->getfield('last')
+ );
+ }
+ }
+
+ $svc_acct->set_default_and_fixed( {
+ #false laziness w/svc-acct::_fieldhandlers
+ 'usergroup' => sub {
+ my( $self, $groups ) = @_;
+ if ( ref($groups) eq 'ARRAY' ) {
+ @groups = @$groups;
+ $groups;
+ } elsif ( length($groups) ) {
+ @groups = split(/\s*,\s*/, $groups);
+ [ @groups ];
+ } else {
+ @groups = ();
+ [];
+ }
+ }
+ } );
+
+}
+
+#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 ( $cgi->param('error') ) {
+ $password = $cgi->param('clear_password');
+} elsif ( $svcnum ) {
+ my $password_encryption = $svc_acct->_password_encryption;
+ if ( $password = $svc_acct->get_cleartext_password ) {
+ $password = '*HIDDEN*' unless $conf->exists('showpasswords');
+ } elsif( $svc_acct->_password and $password_encryption ne 'plain' ) {
+ $password = "(".uc($password_encryption)." encrypted)";
+ }
+}
+
+my $ulen =
+ $conf->exists('usernamemax')
+ ? $conf->config('usernamemax')
+ : dbdef->table('svc_acct')->column('username')->length;
+my $ulen2 = $ulen+2;
+
+my $pmax = max($conf->config('passwordmax') || 13);
+my $pmax2 = $pmax+2;
+
+my $p1 = popurl(1);
+
+sub max {
+ (sort(@_))[-1]
+}
+
+</%init>
diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi
new file mode 100755
index 0000000..5930a38
--- /dev/null
+++ b/httemplate/edit/svc_acct_pop.cgi
@@ -0,0 +1,53 @@
+<% include('/elements/header.html', "$action Access Number", menubar(
+ 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_acct_pop.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="popnum" VALUE="<% $hashref->{popnum} %>">
+Access Number #<% $hashref->{popnum} ? $hashref->{popnum} : "(NEW)" %>
+
+<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>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $hashref->{popnum} ? "Apply changes" : "Add Access Number" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Dialup configuration')
+ || $curuser->access_right('Dialup global configuration');
+
+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);
+
+</%init>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
new file mode 100644
index 0000000..b11fec7
--- /dev/null
+++ b/httemplate/edit/svc_broadband.cgi
@@ -0,0 +1,107 @@
+<% include('elements/svc_Common.html',
+ 'post_url' => popurl(1). 'process/svc_broadband.cgi',
+ 'name' => 'broadband service',
+ 'table' => 'svc_broadband',
+ 'labels' => { 'svcnum' => 'Service',
+ 'description' => 'Description',
+ 'ip_addr' => 'IP address',
+ 'speed_down' => 'Download speed',
+ 'speed_up' => 'Upload speed',
+ 'blocknum' => 'Router/Block',
+ 'block_label' => 'Router/Block',
+ 'mac_addr' => 'MAC address',
+ 'latitude' => 'Latitude',
+ 'longitude' => 'Longitude',
+ 'altitude' => 'Altitude',
+ 'vlan_profile' => 'VLAN profile',
+ 'performance_profile' => 'Performance profile',
+ 'authkey' => 'Authentication key',
+ },
+ 'fields' => \@fields,
+ 'field_callback' => $callback,
+ 'dummy' => $cgi->query_string,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+# If it's stupid but it works, it's still stupid.
+# -Kristian
+
+my $conf = new FS::Conf;
+
+my @fields = (
+ qw( description ip_addr speed_down speed_up blocknum ),
+ { field=>'block_label', type=>'fixed' },
+ qw( mac_addr latitude longitude altitude vlan_profile performance_profile authkey )
+);
+
+my $fixedblock = '';
+
+my $callback = sub {
+ my ($cgi, $object, $fieldref) = @_;
+
+ my $svcpart = $object->svcnum ? $object->cust_svc->svcpart
+ : $cgi->param('svcpart');
+
+ my $part_svc = qsearchs( 'part_svc', { svcpart => $svcpart } );
+ die "No part_svc entry!" unless $part_svc;
+
+ my $columndef = $part_svc->part_svc_column($fieldref->{'field'});
+ if ($columndef->columnflag eq 'F') {
+ $fieldref->{'type'} = 'fixed';
+ $fieldref->{'value'} = $columndef->columnvalue;
+ $fixedblock = $fieldref->{value}
+ if $fieldref->{field} eq 'blocknum';
+ }
+
+ if ($object->svcnum) {
+
+ $fieldref->{type} = 'hidden'
+ if $fieldref->{field} eq 'blocknum';
+
+ $fieldref->{value} = $object->addr_block->label
+ if $fieldref->{field} eq 'block_label' && $object->addr_block;
+
+ } else {
+
+ if ($fieldref->{field} eq 'block_label') {
+ if ($fixedblock && $object->addr_block) {
+ $object->blocknum($fixedblock);
+ $fieldref->{value} = $object->addr_block->label;
+ }else{
+ $fieldref->{type} = 'hidden';
+ }
+ }
+
+ if ($fieldref->{field} eq 'blocknum') {
+ if ( $fixedblock or $conf->exists('auto_router') ) {
+ $fieldref->{type} = 'hidden';
+ $fieldref->{value} = $fixedblock;
+ return;
+ }
+
+ my $cust_pkg = qsearchs( 'cust_pkg', {pkgnum => $cgi->param('pkgnum')} );
+ die "No cust_pkg entry!" unless $cust_pkg;
+
+ $object->svcpart($part_svc->svcpart);
+ my @addr_block =
+ grep { ! $_->agentnum
+ || $cust_pkg->cust_main->agentnum == $_->agentnum
+ && $FS::CurrentUser::CurrentUser->agentnum($_->agentnum)
+ }
+ map { $_->addr_block } $object->allowed_routers;
+ my @options = map { $_->blocknum }
+ sort { $a->label cmp $b->label } @addr_block;
+ my %option_labels = map { ( $_->blocknum => $_->label ) } @addr_block;
+ $fieldref->{type} = 'select';
+ $fieldref->{options} = \@options;
+ $fieldref->{labels} = \%option_labels;
+ }
+
+ }
+};
+
+</%init>
diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
new file mode 100755
index 0000000..b19e9ee
--- /dev/null
+++ b/httemplate/edit/svc_domain.cgi
@@ -0,0 +1,442 @@
+<% include('/elements/header.html', "$action $svc", '') %>
+
+<% include('/elements/error.html') %>
+
+<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 %>">
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+% if ( !$svcnum || $conf->exists('svc_domain-edit_domain') ) {
+ <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63>
+% } else {
+ <B><% $domain %></B>
+% }
+
+% if ($export) {
+<BR>
+Available top-level domains: <% $export->option('tlds') %>
+</TR>
+
+<TR>
+<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>Register at <% $registrar->{'name'} %>
+<BR>
+
+<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer to <% $registrar->{'name'} %>
+<BR>
+
+<INPUT TYPE="radio" NAME="action" VALUE="I"<% $kludge_action eq 'I' ? ' CHECKED' : '' %>>Registered elsewhere
+
+</TR>
+
+% }
+ </TD>
+</TR>
+
+% if ( $communigate ) {
+ <TR>
+ <TD ALIGN="right">Administrator domain</TD>
+ <TD>
+ <% include('/elements/select-domain.html',
+ 'element_name' => 'parent_svcnum',
+ 'curr_value' => $svc_domain->parent_svcnum,
+ 'empty_label' => '(none)',
+ )
+ %>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="parent_svcnum" VALUE="<% $svc_domain->parent_svcnum %>">
+% }
+
+% if ( $communigate
+% && $part_svc->part_svc_column('cgp_aliases')->columnflag !~ /^[FA]$/ ) {
+
+ <TR>
+ <TD ALIGN="right">Aliases</TD>
+ <TD><INPUT TYPE="text" NAME="cgp_aliases" VALUE="<% $svc_domain->cgp_aliases %>"></TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="cgp_aliases" VALUE="<% $svc_domain->cgp_aliases %>">
+% }
+
+% if ( $part_svc->part_svc_column('max_accounts')->columnflag =~ /^[FA]$/ ) {
+ <INPUT TYPE="hidden" NAME="max_accounts" VALUE="<% $svc_domain->max_accounts %>">
+% } else {
+ <TR>
+ <TD ALIGN="right">Maximum number of accounts</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="max_accounts" SIZE=5 MAXLENGTH=6 VALUE="<% $svc_domain->max_accounts %>">
+ </TD>
+ </TR>
+% }
+
+% if ( $communigate
+% && $part_svc->part_svc_column('cgp_accessmodes')->columnflag ne 'F' )
+% {
+
+ <TR>
+ <TD ALIGN="right">Enabled services</TD>
+ <TD>
+ <% include( '/elements/communigate_pro-accessmodes.html',
+ 'curr_value' => $svc_domain->cgp_accessmodes,
+ )
+ %>
+ </TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="cgp_accessmodes" VALUE="<% $svc_domain->cgp_accessmodes() |h %>">
+% }
+
+% if ( $communigate
+% && $part_svc->part_svc_column('trailer')->columnflag ne 'F' )
+% {
+
+ <TR>
+ <TD ALIGN="right">Mail trailer</TD>
+ <TD>
+ <TEXTAREA NAME="trailer" ROWS=5 COLS=60><% $svc_domain->trailer() |h %></TEXTAREA>
+ </TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="trailer" VALUE="<% $svc_domain->trailer() |h %>">
+% }
+
+
+</TABLE>
+<BR>
+
+% if ( $communigate ) {
+
+Account defaults
+<% ntable("#cccccc",2) %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Password modification',
+ 'field' => 'acct_def_password_selfchange',
+ 'curr_value' => $svc_domain->acct_def_password_selfchange,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Password recovery',
+ 'field' => 'acct_def_password_recover',
+ 'curr_value' => $svc_domain->acct_def_password_recover,
+ 'value' => 'Y',
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right">Enabled services
+ </TD>
+ <TD><% include('/elements/communigate_pro-accessmodes.html',
+ 'element_name_prefix' => 'acct_def_cgp_accessmodes_',
+ 'curr_value' => $svc_domain->acct_def_cgp_accessmodes,
+ )
+ %>
+ </TD>
+ </TR>
+
+ <% include('/elements/tr-input-text.html',
+ 'label' => 'Mail storage limit',
+ 'field' => 'acct_def_quota',
+ 'curr_value' => $svc_domain->acct_def_quota,
+ )
+ %>
+ <% include('/elements/tr-input-text.html',
+ 'label' => 'File storage limit',
+ 'field' => 'acct_def_file_quota',
+ 'curr_value' => $svc_domain->acct_def_file_quota,
+ )
+ %>
+ <% include('/elements/tr-input-text.html',
+ 'label' => 'Files limit',
+ 'field' => 'acct_def_file_maxnum',
+ 'curr_value' => $svc_domain->acct_def_file_maxnum,
+ )
+ %>
+ <% include('/elements/tr-input-text.html',
+ 'label' => 'File size limit',
+ 'field' => 'acct_def_file_maxsize',
+ 'curr_value' => $svc_domain->acct_def_file_maxsize,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Allowed mail rules',
+ 'field' => 'acct_def_cgp_rulesallowed',
+ 'options' => [ '', 'No', 'Filter Only', 'All But Exec', 'Any' ],
+ 'labels' => {
+ '' => 'default (No)', #No always the default?
+ },
+ 'curr_value' => $svc_domain->acct_def_cgp_rulesallowed,
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'RPOP modifications',
+ 'field' => 'acct_def_cgp_rpopallowed',
+ 'curr_value' => $svc_domain->acct_def_cgp_rpopallowed,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Accepts mail to "all"',
+ 'field' => 'acct_def_cgp_mailtoall',
+ 'curr_value' => $svc_domain->acct_def_cgp_mailtoall,
+ 'value' => 'Y',
+ )
+ %>
+
+ <% include('/elements/tr-checkbox.html',
+ 'label' => 'Add trailer to sent mail',
+ 'field' => 'acct_def_cgp_addmailtrailer',
+ 'curr_value' => $svc_domain->acct_def_cgp_addmailtrailer,
+ 'value' => 'Y',
+ )
+ %>
+
+%# false laziness w/svc_acct acct_def
+ <TR>
+ <TD ALIGN="right">Message delete method</TD>
+ <TD>
+ <SELECT NAME="acct_def_cgp_deletemode">
+% for ( 'Move To Trash', 'Immediately', 'Mark' ) {
+ <OPTION VALUE="<% $_ %>"
+ <% $_ eq $svc_domain->acct_def_cgp_deletemode ? 'SELECTED' : '' %>
+ ><% $_ %>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include('/elements/tr-input-text.html',
+ 'label' => 'On logout remove trash',
+ 'curr_value' => $svc_domain->acct_def_cgp_emptytrash,
+ )
+ %>
+ <% include('/elements/tr-select.html',
+ 'label' => 'Language',
+ 'field' => 'acct_def_cgp_language',
+ 'options' => [ '', qw( English Arabic Chinese Dutch French German Hebrew Italian Japanese Portuguese Russian Slovak Spanish Thai ) ],
+ 'labels' => {
+ '' => 'default (English)',
+ },
+ 'curr_value' => $svc_domain->acct_def_cgp_language,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Time zone',
+ 'field' => 'acct_def_cgp_timezone',
+ 'options' => [
+ 'HostOS',
+ '(+0100) Algeria/Congo',
+ '(+0200) Egypt/South Africa',
+ '(+0300) Saudi Arabia',
+ '(+0400) Oman',
+ '(+0500) Pakistan',
+ '(+0600) Bangladesh',
+ '(+0700) Thailand/Vietnam',
+ '(+0800) China/Malaysia',
+ '(+0900) Japan/Korea',
+ '(+1000) Queensland',
+ '(+1100) Micronesia',
+ '(+1200) Fiji',
+ '(+1300) Tonga/Kiribati',
+ '(+1400) Christmas Islands',
+ '(-0100) Azores/Cape Verde',
+ '(-0200) Fernando de Noronha',
+ '(-0300) Argentina/Uruguay',
+ '(-0400) Venezuela/Guyana',
+ '(-0500) Haiti/Peru',
+ '(-0600) Central America',
+ '(-0700) Arisona',
+ '(-0800) Adamstown',
+ '(-0900) Marquesas Islands',
+ '(-1000) Hawaii/Tahiti',
+ '(-1100) Samoa',
+ 'Asia/Afghanistan',
+ 'Asia/India',
+ 'Asia/Iran',
+ 'Asia/Iraq',
+ 'Asia/Israel',
+ 'Asia/Jordan',
+ 'Asia/Lebanon',
+ 'Asia/Syria',
+ 'Australia/Adelaide',
+ 'Australia/East',
+ 'Australia/NorthernTerritory',
+ 'Europe/Central',
+ 'Europe/Eastern',
+ 'Europe/Moscow',
+ 'Europe/Western',
+ 'GMT (+0000)',
+ 'Newfoundland',
+ 'NewZealand/Auckland',
+ 'NorthAmerica/Alaska',
+ 'NorthAmerica/Atlantic',
+ 'NorthAmerica/Central',
+ 'NorthAmerica/Eastern',
+ 'NorthAmerica/Mountain',
+ 'NorthAmerica/Pacific',
+ 'Russia/Ekaterinburg',
+ 'Russia/Irkutsk',
+ 'Russia/Kamchatka',
+ 'Russia/Krasnoyarsk',
+ 'Russia/Magadan',
+ 'Russia/Novosibirsk',
+ 'Russia/Vladivostok',
+ 'Russia/Yakutsk',
+ 'SouthAmerica/Brasil',
+ 'SouthAmerica/Chile',
+ 'SouthAmerica/Paraguay',
+ ],
+ 'labels' => {
+ '' => 'default (HostOS)',
+ },
+ 'curr_value' => $svc_domain->acct_def_cgp_timezone,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Layout',
+ 'field' => 'acct_def_cgp_skinname',
+ 'options' => [ '', '***', 'GoldFleece', 'Skin2' ],
+ 'labels' => {
+ '' => 'default (***)',
+ },
+ 'curr_value' => $svc_domain->acct_def_cgp_skinname,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Pronto style',
+ 'field' => 'acct_def_cgp_prontoskinname',
+ 'options' => [ '', 'Pronto', 'Pronto-darkflame', 'Pronto-steel', 'Pronto-twilight', ],
+ 'curr_value' => $svc_domain->acct_def_cgp_prontoskinname,
+ )
+ %>
+
+ <% include('/elements/tr-select.html',
+ 'label' => 'Send read receipts',
+ 'field' => 'acct_def_cgp_sendmdnmode',
+ 'options' => [ '', 'Never', 'Manually', 'Automatically' ],
+ 'labels' => {
+ '' => 'default (Automatically)',
+ },
+ 'curr_value' => $svc_domain->acct_def_cgp_language,
+ )
+ %>
+
+% #XXX rules, archive rule, spam foldering rule(s)
+
+</TABLE>
+<BR>
+
+% } else {
+
+% foreach my $f (qw( password_selfchange password_recover cgp_accessmodes
+% quota file_quota file_maxnum file_maxsize
+% cgp_rulesallowed cgp_rpopallowed cgp_mailtoall
+% cgp_addmailtrailer
+% cgp_deletemode cgp_emptytrash cgp_language
+% cgp_timezone cgp_skinname cgp_sendmdnmode
+% )) {
+ <INPUT TYPE="hidden" NAME="acct_def_<%$f%>" VALUE="<% $svc_domain->get("acct_def_$f") %>">
+% }
+
+% }
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my($svcnum, $pkgnum, $svcpart, $kludge_action, $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');
+ $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_domain = new FS::svc_domain({});
+
+ $svcnum='';
+
+ $svc_domain->set_default_and_fixed;
+
+} else { #editing
+
+ $kludge_action = '';
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $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;
+
+}
+my $action = $svcnum ? 'Edit' : 'Add';
+
+my $svc = $part_svc->getfield('svc');
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+# Find the first export that does domain registration
+my @exports = grep $_->can('registrar'), $part_svc->part_export;
+my $export = $exports[0];
+# If we have a domain registration export, get the registrar object
+my $registrar = $export ? $export->registrar : '';
+
+my $otaker = getotaker;
+
+my $domain = $svc_domain->domain;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi
new file mode 100644
index 0000000..54aa11f
--- /dev/null
+++ b/httemplate/edit/svc_external.cgi
@@ -0,0 +1 @@
+<% include( 'elements/svc_Common.html', 'table'=>'svc_external' ) %>
diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi
new file mode 100755
index 0000000..73f6465
--- /dev/null
+++ b/httemplate/edit/svc_forward.cgi
@@ -0,0 +1,186 @@
+<% include('/elements/header.html', "Mail Forward $action") %>
+
+<% include('/elements/error.html') %>
+
+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>
+% if ( $conf->exists('svc_forward-no_srcsvc') ) {
+ <INPUT NAME="srcsrc" TYPE="hidden" VALUE="0">
+% } else {
+ <SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
+% foreach $_ (keys %email) {
+ <OPTION VALUE="<% $_ %>"
+ <% $_ eq $srcsvc ? 'SELECTED' : '' %>
+ ><% $email{$_} %></OPTION>
+% }
+ <OPTION VALUE="0" <% $src ? 'SELECTED' : '' %>
+ >(other email address)</OPTION>
+ </SELECT>
+% }
+
+% my $src_disabled = $src
+% || $conf->exists('svc_forward-no_srcsvc')
+% || !scalar(%email);
+ <INPUT NAME = "src"
+ TYPE = "text"
+ VALUE = "<% $src %>"
+ <% $src_disabled ? '' : '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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+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;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_forward = new FS::svc_forward({});
+
+ $svcnum='';
+
+ $svc_forward->set_default_and_fixed;
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $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;
+
+}
+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 : '';
+
+</%init>
diff --git a/httemplate/edit/svc_mailinglist.cgi b/httemplate/edit/svc_mailinglist.cgi
new file mode 100644
index 0000000..c7c739d
--- /dev/null
+++ b/httemplate/edit/svc_mailinglist.cgi
@@ -0,0 +1,25 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_mailinglist',
+ 'fields' => \@fields,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my @fields = (
+ 'username',
+ { field=>'domsvc', type=>'select-svc-domain',
+ #label => 'List address domain',
+ },
+ { field=>'listnum', type=>'hidden', },
+ { field=>'listname', type=>'text', },
+ { field=>'reply_to', type=>'checkbox', value=>'Y' },
+ { field=>'remove_from', type=>'checkbox', value=>'Y' },
+ { field=>'reject_auto', type=>'checkbox', value=>'Y' },
+ { field=>'remove_to_and_cc', type=>'checkbox', value=>'Y' },
+
+);
+
+</%init>
diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi
new file mode 100644
index 0000000..b77d962
--- /dev/null
+++ b/httemplate/edit/svc_phone.cgi
@@ -0,0 +1,77 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_phone',
+ 'fields' => \@fields,
+ 'labels' => {
+ 'svcnum' => 'Service',
+ 'countrycode' => 'Country code',
+ 'phonenum' => 'Phone number',
+ 'domsvc' => 'Domain',
+ 'sip_password' => 'SIP password',
+ 'pin' => 'Voicemail PIN',
+ 'phone_name' => 'Name',
+ 'pbxsvc' => 'PBX',
+ 'locationnum' => 'E911 location',
+ },
+ 'svc_new_callback' => sub {
+ my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt ) = @_;
+ $svc_x->locationnum($cust_pkg->locationnum) if $cust_pkg;
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my @fields = ( 'countrycode',
+ { field => 'phonenum',
+ type => 'select-did',
+ label => 'Phone number',
+ },
+ );
+
+push @fields, { field => 'domsvc',
+ type => 'select-svc-domain',
+ label => 'Domain',
+ }
+ if $conf->exists('svc_phone-domain');
+
+push @fields, { field => 'pbxsvc',
+ type => 'select-svc_pbx',
+ label => 'PBX',
+ },
+ 'sip_password',
+ 'pin',
+ { field => 'phone_name',
+ type => 'text',
+ maxlength => $conf->config('svc_phone-phone_name-max_length'),
+ },
+
+ { value => 'E911 Information',
+ type => 'tablebreak-tr-title',
+ colspan => 7,
+ },
+ { field => 'locationnum',
+ type => 'select-cust_location',
+ label => 'E911 location',
+ include_opt_callback => sub {
+ my $svc_phone = shift;
+ my $pkgnum = $svc_phone->get('pkgnum')
+ || $cgi->param('pkgnum')
+ || $svc_phone->cust_svc->pkgnum; #hua?
+ #cross agent location exposure? sheesh
+ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum});
+ my $cust_main = $cust_pkg ? $cust_pkg->cust_main : '';
+ ( 'no_bold' => 1,
+ 'cust_pkg' => $cust_pkg,
+ 'cust_main' => $cust_main,
+ );
+ },
+ },
+ { field => 'custnum', type=> 'hidden' }, #for new cust_locations
+;
+
+
+</%init>
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
new file mode 100644
index 0000000..cd4db75
--- /dev/null
+++ b/httemplate/edit/svc_www.cgi
@@ -0,0 +1,240 @@
+<% include('/elements/header.html', "Web Hosting $action") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_www.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+Service #<B><% $svcnum ? $svcnum : "(NEW)" %></B>
+<BR><BR>
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+% my $recnum = $svc_www->recnum;
+% my $usersvc = $svc_www->usersvc;
+
+<% &ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Zone</TD>
+ <TD>
+ <SELECT NAME="recnum" SIZE=1>
+% foreach $_ (keys %arec) {
+ <OPTION<% $_ eq $recnum ? " SELECTED" : "" %> VALUE="<%$_%>"><%$arec{$_}%>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+% if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F'
+% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) {
+ <TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <SELECT NAME="usersvc" SIZE=1>
+ <OPTION VALUE="">(none)
+% foreach $_ (keys %svc_acct) {
+ <OPTION<% ($_ eq $usersvc) ? " SELECTED" : "" %> VALUE="<%$_%>"><% $svc_acct{$_} %>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+% }
+
+% if ( $part_svc->part_svc_column('config')->columnflag ne 'F' &&
+% $FS::CurrentUser::CurrentUser->access_right('Edit www config') ) {
+ <TR>
+ <TD ALIGN="right">Config lines</TD>
+ <TD>
+ <TEXTAREA NAME="config" rows="15" cols="80"><% $config |h %></TEXTAREA>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="config" VALUE="<% $config |h %>">
+%}
+
+% 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.
+ <% $svc_www->pvf($field)->widget( 'HTML', 'edit',
+ $svc_www->getfield($field)
+ )
+ %>
+% }
+% }
+
+</TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www, $config );
+
+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');
+ $config = $cgi->param('config');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_www = new FS::svc_www { svcpart => $svcpart };
+
+ $svcnum='';
+
+ $svc_www->set_default_and_fixed;
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $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;
+ $config = $svc_www->config;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+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);
+
+</%init>
diff --git a/httemplate/edit/tax_class.html b/httemplate/edit/tax_class.html
new file mode 100644
index 0000000..d3e2e82
--- /dev/null
+++ b/httemplate/edit/tax_class.html
@@ -0,0 +1,36 @@
+<% include('/elements/header.html', "$action taxclass") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/tax_class.html" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="taxclassnum" VALUE="">
+<INPUT TYPE="hidden" NAME="data_vendor" VALUE="">
+
+Tax class <INPUT TYPE="text" NAME="taxclass" VALUE="<% $taxclass |h %>"><BR>
+Description <INPUT TYPE="text" NAME="description" VALUE="<% $description |h %>">
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $action %> taxclass">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $taxclass = '';
+my $description = '';
+if ( $cgi->param('error') ) {
+ $taxclass = $cgi->param('taxclass');
+ $description = $cgi->param('description');
+}
+
+my $action = 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/tax_rate.html b/httemplate/edit/tax_rate.html
new file mode 100644
index 0000000..bff6999
--- /dev/null
+++ b/httemplate/edit/tax_rate.html
@@ -0,0 +1,106 @@
+<% include('elements/edit.html',
+ 'popup' => 1,
+ 'name' => 'Tax rate', #Edit tax rate
+ 'table' => 'tax_rate',
+ 'labels' => $labels,
+ 'fields' => \@fields,
+ 'value_callback' => $value_callback,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+my $value_callback =
+ sub { my ( $field, $value ) = @_;
+ ( $field =~ /^(tax|excessrate|usetax|useexcessrate)$/ )
+ ? $value*100
+ : $value;
+ };
+
+</%once>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $taxnum;
+if ( $cgi->param('error') ) {
+ $cgi->param('taxnum') =~ /^(\d+)$/ or die 'error, but no taxnum';
+ $taxnum = $1;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die 'no taxnum';
+ $taxnum = $1;
+}
+
+my $tax_rate = qsearchs('tax_rate', { 'taxnum' => $taxnum })
+ or die "unknown taxnum $1";
+
+my $labels = { 'taxnum' => 'Tax',
+ 'data_vendor' => 'Data vendor',
+ 'geocode' => 'Vendor location code',
+ 'location' => 'Tax auth loc code',
+ 'taxclass_description' => 'Tax class',
+ 'taxname' => 'Tax name',
+ 'effective_date' => 'Effective date',
+ 'tax' => 'Tax rate (1st bracket)',
+ 'excessrate' => 'Tax rate (2nd bracket)',
+ 'taxbase' => 'First bracket',
+ 'taxmax' => 'Max tax',
+ 'usetax' => 'Use tax rate (1st bracket)',
+ 'useexcessrate' => 'Use tax rate (2nd bracket)',
+ 'unittype_name' => 'Units',
+ 'fee' => 'Fee per unit (1st bracket)',
+ 'excessfee' => 'Fee per unit (2st bracket)',
+ 'feebase' => 'Units in first bracket',
+ 'feemax' => 'Max Units',
+ 'maxtype_name' => 'Threshold accumulation',
+ 'taxauth_name', => 'Tax authority',
+ 'basetype_name' => 'Basis',
+ 'passtype_name' => 'Passthru',
+ 'passflag' => 'Passable',
+ 'setuptax' => 'This tax not applicable to setup fees',
+ 'recurtax' => 'This tax not applicable to recurring fees',
+ };
+
+my @fields = (
+ { type=>'tablebreak-tr-title', value=>'Location' },
+ { field=>'data_vendor', type=>'hidden',},
+ { field=>'geocode', type=>'fixed' },
+ { field=>'taxclassnum', type=>'hidden' } ,
+ { field=>'taxclass_description', type=>'fixed' } ,
+ { field=>'taxname', type=>'text' } ,
+ { field=>'effective_date', type=>'fixed' } ,
+ { field=>'location', type=>'text' },
+ { type=>'tablebreak-tr-title', value=>'Money based rates' },
+ { field=>'tax', type=>'percentage' } ,
+ { field=>'excessrate', type=>'percentage' } ,
+ { field=>'taxbase', type=>'money' } ,
+ { field=>'taxmax', type=>'money' } ,
+ { field=>'usetax', type=>'percentage' } ,
+ { field=>'useexcessrate', type=>'percentage' } ,
+ { type=>'tablebreak-tr-title', value=>'Service based rates' },
+ { field=>'unittype', type=>'hidden' } ,
+ { field=>'unittype_name', type=>'fixed' } ,
+ { field=>'fee', type=>'money' } ,
+ { field=>'excessfee', type=>'money' } ,
+ { field=>'feebase', type=>'text' } ,
+ { field=>'feemax', type=>'text' } ,
+ { type=>'tablebreak-tr-title', value=>'Taxation rules' },
+ { field=>'maxtype', type=>'hidden' } ,
+ { field=>'maxtype_name', type=>'fixed' } ,
+ { field=>'taxauth', type=>'hidden' } ,
+ { field=>'taxauth_name', type=>'fixed' } ,
+ { field=>'basetype', type=>'hidden' } ,
+ { field=>'basetype_name', type=>'fixed' } ,
+ { field=>'passtype', type=>'hidden' } ,
+ { field=>'passtype_name', type=>'fixed' } ,
+ { field=>'passflag', type=>'fixed' } ,
+ { field=>'setuptax', type=>'checkbox', value=>'Y' } ,
+ { field=>'recurtax', type=>'checkbox', value=>'Y' } ,
+ { field=>'disabled', type=>'checkbox', value=>'Y' } ,
+ { field=>'manual', type=>'hidden', value=>'Y' } ,
+);
+
+</%init>
diff --git a/httemplate/edit/usage_class.html b/httemplate/edit/usage_class.html
new file mode 100644
index 0000000..be01d2e
--- /dev/null
+++ b/httemplate/edit/usage_class.html
@@ -0,0 +1,42 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'Usage Class',
+ 'table' => 'usage_class',
+ 'fields' => [
+ 'classname',
+ 'weight',
+ { field => 'format',
+ type => $useformat ? 'select' : 'hidden',
+ ( $useformat
+ ? ( 'options' => [ keys %labels ],
+ 'labels' => \%labels,
+ )
+ : ()
+ ),
+ },
+ { field => 'disabled',
+ type => 'checkbox',
+ value => 'Y',
+ },
+ ],
+ 'labels' => {
+ 'classnum' => 'Class number',
+ 'classname' => 'Class name',
+ 'weight' => 'Weight',
+ 'format' => 'Format',
+ 'disabled' => 'Disable class',
+ },
+ 'viewall_dir' => 'browse',
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+my $useformat = $conf->exists('usage_class_as_a_section');
+
+my %labels = &FS::usage_class::summary_formats_labelhash();
+
+</%init>
diff --git a/httemplate/elements/about_freeside.html b/httemplate/elements/about_freeside.html
new file mode 100644
index 0000000..8084583
--- /dev/null
+++ b/httemplate/elements/about_freeside.html
@@ -0,0 +1,19 @@
+<FONT SIZE="-2" STYLE="color:#999999">
+ <% include('/elements/popup_link.html',
+ 'action' => $fsurl.'docs/about.html',
+ 'label' => 'Freeside',
+ 'style' => 'color:#999999',
+ 'actionlabel' => 'About',
+ 'width' => 300,
+ 'height' => 360,
+ 'color' => '#7e0079',
+ 'scrolling' => 'no',
+ ) |n
+ %>&nbsp;v<% $FS::VERSION %><BR>
+ <A HREF="<% $conf->config('support-key') ? "http://www.freeside.biz/mediawiki/index.php/Supported:Documentation" : "http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation" %>" TARGET="_blank" STYLE="color:#999999">Documentation</A>
+</FONT>
+<%init>
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/about_rt.html b/httemplate/elements/about_rt.html
new file mode 100644
index 0000000..bdc8519
--- /dev/null
+++ b/httemplate/elements/about_rt.html
@@ -0,0 +1,13 @@
+% if ( $conf->config('ticket_system') eq 'RT_Internal' ) {
+% eval "use RT;";
+
+<FONT SIZE="-2" STYLE="color:#999999">
+ <A HREF="http://www.bestpractical.com/rt" TARGET="_blank" STYLE="color:#999999">RT</A>&nbsp;v<% $RT::VERSION %><BR>
+ <A HREF="http://wiki.bestpractical.com/" TARGET="_blank" STYLE="color:#999999">Documentation</A>
+</FONT>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/ajaxcontentmws.js b/httemplate/elements/ajaxcontentmws.js
new file mode 100644
index 0000000..9177049
--- /dev/null
+++ b/httemplate/elements/ajaxcontentmws.js
@@ -0,0 +1,185 @@
+/*
+ ajaxcontentmws.js - Foteos Macrides (author and Copyright holder)
+ Initial: June 22, 2006 - Last Revised: March 24, 2008
+ Wrapper function set for getting and using the responseText and / or
+ responseXML from a GET or POST XMLHttpRequest, which can be used to
+ generate dynamic content for overlib or overlib2 calls, or to modify
+ the content of a displayed STICKY popup dynamically.
+
+ For GET Use:
+ onmouseover="return OLgetAJAX(url, command, delay, css);"
+ onmouseout="OLclearAJAX();" (if delay > 0)
+ or:
+ onclick="OLgetAJAX(url, command, 0, css); return false;"
+ or:
+ onload="OLgetAJAX(url, command, 0, css);
+
+ Where:
+ url (required)
+ is a quoted string, or unquoted string variable name or array entry, with
+ the full, relative, or partial URL for a file or a server-side script (php,
+ asp, or cgi, e.g. perl), and may have a query string appended (e.g.,
+ 'http://my.domain.com/scripts/myScript.php?foo=bar&life=grand').
+ And:
+ command (required)
+ is the function reference (unquoted name without parens) of a function to
+ be called when the server's response has been received (it could instead be
+ an inline function, i.e., defined within the 2nd argument, or a quoted string
+ for a function with parens and any args)
+ And:
+ delay (may be omitted unless css is included)
+ is an unquoted number indicating the number of millisecs to wait before
+ initiating an XMLHttpRequest GET request. It should be 0 when using onclick
+ or onload, but may be a modest value such as 300 for onmouseover to avoid
+ any chatter of requests. When used with onmouseover, include:
+ onmouseout="OLclearAJAX();"
+ to clear the request if the user does not hover for at least that long. If
+ the popup is not STICKY, include an nd or nd2 call, e.g.,
+ onmouseout="OLclearAJAX(); nd();"
+ And:
+ css (may be omitted)
+ is a quoted string with the CSS class (e.g. 'ovfl510' for
+ .ovfl510 {width:510px; height:145px; overflow:auto; ...} ) for a div to
+ encase the responseText and set the width, height and scrollbars in the
+ main text area of the popup, or the unquoted number 0 if no encasing div
+ is to be used.
+
+ For POST substitute OLpostAJAX(url, qry, command, delay, css);
+ Where
+ qry (required)
+ is the string to be posted, typically a query string (without a lead ?)
+ and the other arguments are as above.
+
+ See http://www.macridesweb.com/oltest/AJAX.html for more information.
+*/
+
+// Initialize our global variables for this function set.
+var OLhttp=false,OLcommandAJAX=null,OLdelayidAJAX=0,OLclassAJAX='',
+OLresponseAJAX='',OLabortAJAX=0,OLdebugAJAX=0;
+
+// Create a series of wrapper functions (e.g. OLcmdT#() for ones which
+// use OLhttp.responseText via the OLresponseAJAX global, and OLcmdX#()
+// for ones which use OLhttp.responseXML) whose reference (unquoted name
+// without parens) is the 2nd argument in OLgetAJAX(url,command,delay,css)
+// calls. This one is for the first example in the AJAX.html support
+// document, to use the OLresponseAJAX global as the lead argument for an
+// overlib popup. Put your functions in the head, or in another imported
+// .js file, so that they will not be affected by updates of this .js file.
+//
+function OLcmdExT1() {
+ return overlib(OLresponseAJAX, TEXTPADDING,0, CAPTIONPADDING,4,
+ CAPTION,'Example with AJAX content via <span '
+ +'class="yellow">responseText</span>.&nbsp; Popup scrolls with the window.',
+ WRAP, BORDER,2, STICKY, CLOSECLICK, SCROLL,
+ MIDX,0, RELY,100,
+ STATUS,'Example with AJAX content via responseText of XMLHttpResponse');
+}
+
+// Alert for old browsers which lack XMLHttpRequest support.
+function OLsorryAJAX() {
+ alert('Sorry, AJAX is not supported by your browser.');
+ return false;
+}
+
+// Check 2nd arg for function
+function OLchkFuncAJAX(ar){
+ var t=typeof ar;return (((t=='function'))||((t=='string')&&(/.+\(.*\)/.test(ar))));
+}
+
+// Alert for bad 2nd argument
+function OLnotFuncAJAX(m) {
+ if(over)cClick();
+ alert('The 2nd arg of OL'+m+'AJAX is not a function reference, nor an inline function, '
+ +'nor a quoted string with a function indicated.');
+ return OLclearAJAX();
+}
+
+// Alert for indicating an XMLHttpRequest network error.
+function OLerrorAJAX() {
+ if(OLhttp.status&&OLhttp.status!=2147746065)alert('Network error '+OLhttp.status+'. Try again later.');
+ return false;
+}
+
+// Returns a new XMLHttpRequest object, or false for older browsers
+// which did not yet support it. Called as OLhttp=OLnewXMLHttp() via
+// the OLgetAJAX(url,command,delay,css) wrapper function.
+//
+function OLnewXMLHttp() {
+ var f=false,req=f;
+ if(window.XMLHttpRequest)eval(new Array('try{',
+ 'req=new XMLHttpRequest();','}catch(e){','req=f;','}').join('\n'));
+ /*@cc_on @if(@_jscript_version>=5)if(!req)
+ eval(new Array('try{','req=new ActiveXObject("Msxml2.XMLHTTP");',
+ '}catch(e){','try{','req=new ActiveXObject("Microsoft.XMLHTTP");',
+ '}catch(e){','req=f;','}}').join('\n')); @end @*/
+ return req;
+}
+
+// Handle the OLhttp.responseText string from the XMLHttpRequest object.
+function OLdoAJAX() {
+ if(OLhttp.readyState==4){
+ if(OLdebugAJAX)alert(
+ 'OLhttp.status = '+OLhttp.status+'\n'
+ +'OLhttp.statusText = '+OLhttp.statusText+'\n'
+ +'OLhttp.getAllResponseHeaders() = \n'
+ +OLhttp.getAllResponseHeaders()+'\n'
+ +'OLhttp.getResponseHeader("Content-Type") = '
+ +OLhttp.getResponseHeader("Content-Type")+'\n');
+ if(OLhttp.status==200||(OLhttp.status==0&&!OLabortAJAX&&!OLie55)){
+ OLresponseAJAX=OLclassAJAX?'<div class="'+OLclassAJAX+'">':'';
+ OLresponseAJAX += OLhttp.responseText;
+ OLresponseAJAX += OLclassAJAX?'</div>':'';
+ if(OLdebugAJAX)alert('OLresponseAJAX = \n'+OLresponseAJAX);
+ OLclassAJAX=0;
+ return (typeof OLcommandAJAX=='string')?eval(OLcommandAJAX):OLcommandAJAX();
+ }else{
+ OLclassAJAX=0;
+ OLabortAJAX=0;
+ return OLerrorAJAX();
+ }
+ }
+}
+
+// Actually make the request initiated via OLgetAJAX or OLpostAJAX, or
+// invoke a "permission denied" alert if a cross-domain URL was used.
+function OLsetAJAX(url,qry) {
+ if(window.location.protocol.indexOf('http')==0&&
+ (url.indexOf('file:')==0||url.indexOf('ftp:')==0)){
+ alert('[object Error]\n(Cross-domain access not permitted)');return false;}
+ qry=(qry||null);var s='',m=(qry)?'POST':'GET';OLabortAJAX=0;
+ OLdelayidAJAX=0;eval(new Array('try{','OLhttp.open(m,url,true);',
+ '}catch(e){','s=e','OLhttp=false;','}').join('\n'));if(!OLhttp){
+ alert(s+'\n(Cross-domain access not permitted)');return false;}if(qry)
+ OLhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');
+ OLhttp.onreadystatechange=OLdoAJAX;
+ OLhttp.send(qry);
+}
+
+// Clear or abort any delayed OLsetAJAX call or pending request.
+function OLclearAJAX() {
+ if(OLdelayidAJAX){clearTimeout(OLdelayidAJAX);OLdelayidAJAX=0;}
+ if(OLhttp&&!OLdebugAJAX){OLabortAJAX=1;OLhttp.abort();}
+ return false;
+}
+
+// Load a new XMLHttpRequest object into the OLhttp global, load the
+// OLcommandAJAX and OLclassAJAX globals, and initiate a GET request
+// via OLsetAJAX(url) to populate OLhttp.
+function OLgetAJAX(url,command,delay,css) {
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('get');
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();
+ OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"')",delay);
+ else OLsetAJAX(url);
+}
+
+// Load a new XMLHttpRequest object into the OLhttp global, load the
+// OLcommandAJAX and OLclassAJAX globals, and initiate a POST request
+// via OLsetAJAX(url,qry) to populate OLhttp.
+function OLpostAJAX(url,qry,command,delay,css) {
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('post');
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();
+ qry=(qry||0);OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"','"+qry+"')",delay);
+ else OLsetAJAX(url,qry);
+}
diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js
new file mode 100644
index 0000000..0dbde79
--- /dev/null
+++ b/httemplate/elements/calendar-en.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// 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");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// 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-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\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..b27d9be
--- /dev/null
+++ b/httemplate/elements/calendar-setup.js
@@ -0,0 +1,200 @@
+/* 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.5 2006-02-09 07:18:08 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("dateText", null);
+ param_default("firstDay", null);
+ 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);
+ param_default("multiple", null);
+
+ 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.multiple || 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.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 && typeof p.onUpdate == "function")
+ p.onUpdate(cal);
+ if (update && p.flat) {
+ if (typeof p.flatCallback == "function")
+ p.flatCallback(cal);
+ }
+ if (update && p.singleClick && cal.dateClicked)
+ cal.callCloseHandler();
+ };
+
+ 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.showsOtherMonths = params.showOthers;
+ 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.getDateText = params.dateText;
+ if (params.ifFormat) {
+ cal.setDateFormat(params.ifFormat);
+ }
+ if (params.inputField && typeof params.inputField.value == "string") {
+ cal.parseDate(params.inputField.value);
+ }
+ 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 (dateEl)
+ params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
+ 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();
+ }
+ if (params.multiple) {
+ cal.multiple = {};
+ for (var i = params.multiple.length; --i >= 0;) {
+ var d = params.multiple[i];
+ var ds = d.print("%Y%m%d");
+ cal.multiple[ds] = d;
+ }
+ }
+ cal.showsOtherMonths = params.showOthers;
+ cal.yearStep = params.step;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ 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;
+ };
+
+ return cal;
+};
diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css
new file mode 100644
index 0000000..7b8de00
--- /dev/null
+++ b/httemplate/elements/calendar-win2k-2.css
@@ -0,0 +1,272 @@
+/* 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;
+ z-index:2;
+}
+
+.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;
+ z-index: 100;
+}
+
+.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..f5c74f6
--- /dev/null
+++ b/httemplate/elements/calendar.js
@@ -0,0 +1,1806 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * 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.5 2006-02-09 07:18:08 ivan Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.getDateToolTip = null;
+ this.getDateText = 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 = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 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;
+ this.hiliteToday = true;
+ this.multiple = null;
+ // 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;
+};
+
+// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
+Calendar.getElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
+ while (f.nodeType != 1 || /^div$/i.test(f.tagName))
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.getTargetElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.target;
+ while (f.nodeType != 1)
+ f = f.parentNode;
+ return f;
+};
+
+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.innerHTML = 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.innerHTML = 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.innerHTML;
+ 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.innerHTML = 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");
+ if (el.calendar)
+ el.calendar.tooltips.innerHTML = _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") {
+ if (cal.currentDateEl) {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ }
+ cal.date.setDateOnly(el.caldate);
+ date = cal.date;
+ var other_month = !(cal.dateClicked = !el.otherMonth);
+ if (!other_month && !cal.currentDateEl)
+ cal._toggleMultipleDate(new Date(date));
+ else
+ newdate = !el.disabled;
+ // a date was clicked
+ if (other_month)
+ cal._init(cal.firstDayOfWeek, date);
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = new Date(cal.date);
+ if (el.navtype == 0)
+ date.setDateOnly(new Date()); // TODAY
+ // 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 <mihai_bazon@yahoo.com> 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.innerHTML;
+ 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.innerHTML = 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())) {
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ } else if (el.navtype == 0)
+ newdate = closing = true;
+ }
+ if (newdate) {
+ ev && cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ ev && 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;
+ cell.innerHTML = "<div unselectable='on'>" + text + "</div>";
+ 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.innerHTML = Calendar._TT["WK"];
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ 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);
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ 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.innerHTML = 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.innerHTML = ":";
+ 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 pm, hrs = this.date.getHours(),
+ mins = this.date.getMinutes();
+ if (t12) {
+ pm = (hrs >= 12);
+ if (pm) hrs -= 12;
+ if (hrs == 0) hrs = 12;
+ AP.innerHTML = pm ? "pm" : "am";
+ }
+ H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.innerHTML, 10);
+ if (t12) {
+ if (/pm/i.test(AP.innerHTML) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.innerHTML) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.innerHTML, 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.innerHTML = 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";
+ div.appendChild(yr);
+ }
+
+ this._init(this.firstDayOfWeek, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ var cal = window._dynarch_popupCalendar;
+ if (!cal || cal.multiple)
+ return false;
+ (Calendar.is_ie) && (ev = window.event);
+ var act = (Calendar.is_ie || ev.type == "keypress"),
+ K = ev.keyCode;
+ if (ev.ctrlKey) {
+ switch (K) {
+ 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 (K) {
+ 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 prev, x, y, ne, el, step;
+ prev = K == 37 || K == 38;
+ step = (K == 37 || K == 39) ? 1 : 7;
+ function setVars() {
+ el = cal.currentDateEl;
+ var p = el.pos;
+ x = p & 15;
+ y = p >> 4;
+ ne = cal.ar_days[y][x];
+ };setVars();
+ function prevMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() - step);
+ cal.setDate(date);
+ };
+ function nextMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() + step);
+ cal.setDate(date);
+ };
+ while (1) {
+ switch (K) {
+ case 37: // KEY left
+ if (--x >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 6;
+ K = 38;
+ continue;
+ }
+ break;
+ case 38: // KEY up
+ if (--y >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ prevMonth();
+ setVars();
+ }
+ break;
+ case 39: // KEY right
+ if (++x < 7)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 0;
+ K = 40;
+ continue;
+ }
+ break;
+ case 40: // KEY down
+ if (++y < cal.ar_days.length)
+ ne = cal.ar_days[y][x];
+ else {
+ nextMonth();
+ setVars();
+ }
+ break;
+ }
+ break;
+ }
+ if (ne) {
+ if (!ne.disabled)
+ Calendar.cellClick(ne);
+ else if (prev)
+ prevMonth();
+ else
+ nextMonth();
+ }
+ }
+ break;
+ case 13: // KEY enter
+ if (act)
+ Calendar.cellClick(cal.currentDateEl, ev);
+ 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(),
+ TY = today.getFullYear(),
+ TM = today.getMonth(),
+ TD = today.getDate();
+ 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 = this.ar_days = new Array();
+ var weekend = Calendar._TT["WEEKEND"];
+ var dates = this.multiple ? (this.datesCells = {}) : null;
+ for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.innerHTML = date.getWeekNumber();
+ cell = cell.nextSibling;
+ }
+ row.className = "daysrow";
+ var hasdays = false, iday, dpos = ar_days[i] = [];
+ for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
+ iday = date.getDate();
+ var wday = date.getDay();
+ cell.className = "day";
+ cell.pos = i << 4 | j;
+ dpos[j] = cell;
+ 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.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
+ if (dates)
+ dates[date.print("%Y%m%d")] = cell;
+ if (this.getDateStatus) {
+ var status = this.getDateStatus(date, year, month, iday);
+ if (this.getDateToolTip) {
+ var toolTip = this.getDateToolTip(date, year, month, iday);
+ if (toolTip)
+ cell.title = toolTip;
+ }
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ cell.caldate = new Date(date);
+ cell.ttip = "_";
+ if (!this.multiple && current_month
+ && iday == mday && this.hiliteToday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (date.getFullYear() == TY &&
+ date.getMonth() == TM &&
+ iday == TD) {
+ 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.title.innerHTML = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ this.table.style.visibility = "visible";
+ this._initMultipleDates();
+ // PROFILE
+ // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+Calendar.prototype._initMultipleDates = function() {
+ if (this.multiple) {
+ for (var i in this.multiple) {
+ var cell = this.datesCells[i];
+ var d = this.multiple[i];
+ if (!d)
+ continue;
+ if (cell)
+ cell.className += " selected";
+ }
+ }
+};
+
+Calendar.prototype._toggleMultipleDate = function(date) {
+ if (this.multiple) {
+ var ds = date.print("%Y%m%d");
+ var cell = this.datesCells[ds];
+ if (cell) {
+ var d = this.multiple[ds];
+ if (!d) {
+ Calendar.addClass(cell, "selected");
+ this.multiple[ds] = date;
+ } else {
+ Calendar.removeClass(cell, "selected");
+ delete this.multiple[ds];
+ }
+ }
+ }
+};
+
+Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
+ this.getDateToolTip = unaryFunction;
+};
+
+/**
+ * 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._dynarch_popupCalendar = 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) {
+ var calendar = window._dynarch_popupCalendar;
+ if (!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._dynarch_popupCalendar.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._dynarch_popupCalendar = 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 "l": p.x += el.offsetWidth - w; break;
+ case "r": 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) {
+ if (!fmt)
+ fmt = this.dateFormat;
+ this.setDate(Date.parseDate(str, fmt));
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ if (!Calendar.is_ie && !Calendar.is_opera)
+ return;
+ 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 = this.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 (this.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";
+ }
+ }
+ }
+};
+
+/** 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.innerHTML = 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;
+
+Date.parseDate = function(str, fmt) {
+ var today = new Date();
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ 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;
+ else if (/am/i.test(a[i]) && hr >= 12)
+ hr -= 12;
+ break;
+
+ case "%M":
+ min = parseInt(a[i], 10);
+ break;
+ }
+ }
+ if (isNaN(y)) y = today.getFullYear();
+ if (isNaN(m)) m = today.getMonth();
+ if (isNaN(d)) d = today.getDate();
+ if (isNaN(hr)) hr = today.getHours();
+ if (isNaN(min)) min = today.getMinutes();
+ if (y != 0 && m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ 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)
+ y = today.getFullYear();
+ if (m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ return today;
+};
+
+/** 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 date and time equality */
+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()));
+};
+
+/** Set only the year, month, date parts (keep existing time) */
+Date.prototype.setDateOnly = function(date) {
+ var tmp = new Date(date);
+ this.setDate(1);
+ this.setFullYear(tmp.getFullYear());
+ this.setMonth(tmp.getMonth());
+ this.setDate(tmp.getDate());
+};
+
+/** 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 && !Calendar.is_khtml)
+ 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._dynarch_popupCalendar = null;
diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js
new file mode 100644
index 0000000..4fe03f1
--- /dev/null
+++ b/httemplate/elements/calendar_stripped.js
@@ -0,0 +1,14 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * 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.getDateToolTip=null;this.getDateText=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=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;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){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};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.innerHTML=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.innerHTML=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.innerHTML;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.innerHTML=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");if(el.calendar)el.calendar.tooltips.innerHTML=_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"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new 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 <mihai_bazon@yahoo.com> 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.innerHTML;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.innerHTML=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;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&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;cell.innerHTML="<div unselectable='on'>"+text+"</div>";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.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);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);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);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.innerHTML=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.innerHTML=":";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 pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,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.innerHTML=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";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){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(K){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 prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++y<cal.ar_days.length)ne=cal.ar_days[y][x];else{nextMonth();setVars();}break;}break;}if(ne){if(!ne.disabled)Calendar.cellClick(ne);else if(prev)prevMonth();else nextMonth();}}break;case 13:if(act)Calendar.cellClick(cal.currentDateEl,ev);break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date(),TY=today.getFullYear(),TM=today.getMonth(),TD=today.getDate();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=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;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.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&&current_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){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.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};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._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!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._dynarch_popupCalendar.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._dynarch_popupCalendar=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 "l":p.x+=el.offsetWidth-w;break;case "r":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){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;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=this.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(this.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";}}}};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.innerHTML=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.parseDate=function(str,fmt){var today=new Date();var y=0;var m=-1;var d=0;var a=str.split(/\W+/);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;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);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)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};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.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};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&&!Calendar.is_khtml)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._dynarch_popupCalendar=null; \ No newline at end of file
diff --git a/httemplate/elements/checkbox.html b/httemplate/elements/checkbox.html
new file mode 100644
index 0000000..5176070
--- /dev/null
+++ b/httemplate/elements/checkbox.html
@@ -0,0 +1,19 @@
+<% $opt{'prefix'} %><INPUT TYPE = "checkbox"
+ NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ VALUE = "<% $opt{value} %>"
+ <% $opt{curr_value} eq $opt{value}
+ ? ' CHECKED'
+ : ''
+ %>
+ <% $onchange %>
+ ><% $opt{'postfix'} %>
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+</%init>
diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html
new file mode 100644
index 0000000..31652f3
--- /dev/null
+++ b/httemplate/elements/checkboxes-table-name.html
@@ -0,0 +1,90 @@
+<%doc>
+
+Example:
+
+ include( '/elements/checkboxes-table-name.html',
+
+ ###
+ # required
+ ###
+ 'link_table' => 'table_name',
+
+ 'name_col' => 'name_column',
+ #or
+ 'name_callback' => sub { },
+
+ 'names_list' => [ 'value',
+ 'other value',
+ [ 'complex value' => { 'desc' => "Add'l description",
+ 'note' => '&nbsp;*',
+ }
+ ],
+ ],
+
+ ###
+ # recommended (required?)
+ ###
+ 'source_obj' => $obj,
+ #or?
+ #'source_table' => 'table_name',
+ #'sourcenum' => '4', #current value of primary key in source_table
+ # # (none is okay, just pass it if you have it)
+
+ ###
+ # optional
+ ###
+ 'num_col' => 'col_name' #if column name is different in link_table than
+ #source_table
+ 'link_static' => { 'column' => 'value' },
+
+ )
+
+</%doc>
+
+<% include('checkboxes.html',
+ 'names_list' => $opt{'names_list'},
+ 'checked_callback' => $checked_callback,
+ 'element_name_prefix' => $opt{'link_table'}. '.',
+ )
+%>
+
+<%init>
+
+my( %opt ) = @_;
+
+my @pset = ( 'a'..'z', 'A'..'Z', '0'..'9' );
+
+my $prefix = $opt{prefix}
+ || join('', map $pset[ int(rand $#pset) ], (0..20) );
+
+my( $source_pkey, $sourcenum, $source_obj );
+if ( $opt{'source_obj'} ) {
+
+ $source_obj = $opt{'source_obj'};
+ #$source_table = $source_obj->dbdef_table->table;
+ $source_pkey = $source_obj->dbdef_table->primary_key;
+ $sourcenum = $source_obj->$source_pkey();
+
+} else {
+
+ #$source_obj?
+ $source_pkey = $opt{'source_table'}
+ ? dbdef->table($opt{'source_table'})->primary_key
+ : '';
+ $sourcenum = $opt{'sourcenum'};
+}
+
+$source_pkey = $opt{'num_col'} || $source_pkey;
+
+my $link_static = $opt{'link_static'} || {};
+
+my $checked_callback = sub {
+ my( $cgi, $name ) = @_;
+ qsearchs( $opt{'link_table'}, {
+ $source_pkey => $sourcenum,
+ $opt{'name_col'} => $name,
+ %$link_static,
+ });
+};
+
+</%init>
diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html
new file mode 100644
index 0000000..a31bdb9
--- /dev/null
+++ b/httemplate/elements/checkboxes-table.html
@@ -0,0 +1,129 @@
+%
+%
+% ##
+% # required
+% ##
+% # 'target_table' => 'table_name',
+% # 'link_table' => 'table_name',
+% #
+% # 'name_col' => 'name_column',
+% # #or
+% # 'name_callback' => sub { },
+% #
+% ##
+% # recommended (required?)
+% ##
+% # 'source_obj' => $obj,
+% # #or?
+% # #'source_table' => 'table_name',
+% # #'sourcenum' => '4', #current value of primary key in source_table
+% # # # (none is okay, just pass it if you have it)
+% ##
+% # optional
+% ##
+% # 'disable-able' => 1,
+%
+% my( %opt ) = @_;
+%
+% my $target_pkey = dbdef->table($opt{'target_table'})->primary_key;
+%
+% my( $source_pkey, $sourcenum, $source_obj );
+% if ( $opt{'source_obj'} || $opt{'object'} ) {
+%
+% $source_obj = $opt{'source_obj'} || $opt{'object'};
+% #$source_table = $source_obj->dbdef_table->table;
+% $source_pkey = $source_obj->dbdef_table->primary_key;
+% $sourcenum = $source_obj->$source_pkey();
+%
+% } else {
+%
+% #$source_obj?
+% $source_pkey = $opt{'source_table'}
+% ? dbdef->table($opt{'source_table'})->primary_key
+% : '';
+% $sourcenum = $opt{'sourcenum'};
+% }
+%
+% my $hashref = $opt{'hashref'} || {};
+%
+% my $extra_sql = $opt{'extra_sql'} || '';
+%
+% if ( $opt{'agent_virt'} ) {
+% $extra_sql .= ' AND' . $FS::CurrentUser::CurrentUser->agentnums_sql(
+% 'null_right' => $opt{'agent_null_right'}
+% );
+% }
+%
+% if ( $opt{'disable-able'} ) {
+% $hashref->{'disabled'} = '';
+%
+% $extra_sql .= ( $sourcenum && $source_pkey )
+% ? " OR $source_pkey = $sourcenum"
+% : '';
+% }
+%
+%
+% foreach my $target_obj (
+% qsearch({ 'table' => $opt{'target_table'},
+% 'hashref' => $hashref,
+% 'select' => $opt{'target_table'}. '.*',
+% 'addl_from' => "LEFT JOIN $opt{'link_table'} USING ( $target_pkey )",
+% 'extra_sql' => $extra_sql,
+% })
+% ) {
+%
+% my $targetnum = $target_obj->$target_pkey();
+%
+% my $checked;
+% if ( $cgi->param('error') ) {
+%
+% $checked = $cgi->param($target_pkey.$targetnum)
+% ? 'CHECKED'
+% : '';
+%
+% } else {
+%
+% $checked = qsearchs( $opt{'link_table'}, {
+% $source_pkey => $sourcenum,
+% $target_pkey => $targetnum,
+% } )
+% ? 'CHECKED'
+% : ''
+%
+% }
+%
+%
+
+
+ <INPUT TYPE="checkbox" NAME="<% $target_pkey. $targetnum %>" <% $checked %> VALUE="ON">
+% if ( $opt{'target_link'} ) {
+
+
+ <A HREF="<% $opt{'target_link'} %><% $targetnum %>">
+%
+%
+% }
+%
+<% $targetnum %>:
+% if ( $opt{'name_callback'} ) {
+
+
+ <% &{ $opt{'name_callback'} }( $target_obj ) %><% $opt{'target_link'} ? '</A>' : '' %>
+% } else {
+% my $name_col = $opt{'name_col'};
+%
+
+
+ <% $target_obj->$name_col() %><% $opt{'target_link'} ? '</A>' : '' %>
+% }
+% if ( $opt{'disable-able'} ) {
+
+
+ <% $target_obj->disabled =~ /^Y/i ? ' (DISABLED)' : '' %>
+% }
+
+
+ <BR>
+% }
+
+
diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html
new file mode 100644
index 0000000..b120ada
--- /dev/null
+++ b/httemplate/elements/checkboxes.html
@@ -0,0 +1,107 @@
+<%doc>
+
+Example:
+
+ include( '/elements/checkboxes.html',
+
+ # required
+
+ #? 'name_callback' => sub { },
+
+ 'names_list' => [ 'value',
+ 'other value',
+ [ 'complex value' => { 'label' => 'Display value',
+ 'desc' => "Add'l description",
+ 'note' => '&nbsp;*',
+ }
+ ],
+ ],
+
+ 'element_name_prefix' => "$link_table.",
+
+ #recommended
+
+ 'checked_callback' => sub { my( $cgi, $name ) = @_; },
+
+ )
+
+</%doc>
+
+<TABLE CELLSPACING=0 CELLPADDING=0>
+
+<TR>
+ <TD COLSPAN=2 ALIGN="center"><FONT SIZE="-1">(
+ <A HREF="javascript:setAll<%$prefix%>(true)">select all</A> |
+ <A HREF="javascript:setAll<%$prefix%>(false)">unselect all</A> |
+ <A HREF="javascript:toggleAll<%$prefix%>()">toggle all</A>
+ )</FONT></TD>
+</TR>
+
+% my $num=0;
+% foreach my $item ( @{ $opt{'names_list'} } ) {
+%
+% my $name = ref($item) ? $item->[0] : $item;
+% my $display = ( ref($item) && $item->[1]{label} )
+% ? $item->[1]{label}
+% : $name;
+% $display =~ s/ /&nbsp;/g;
+% $display .= $item->[1]{note} if ref($item) && $item->[1]{note};
+% my $desc = ref($item) && $item->[1]{desc} ? $item->[1]{desc} : '';
+%
+% my $callback =
+% ( $cgi->param('error') ? 'error_' : '' ). 'checked_callback';
+% my $checked = &{ $opt{$callback} }( $cgi, $name ) ? 'CHECKED' : '';
+
+ <TR>
+ <TD VALIGN="top">
+ <INPUT TYPE="checkbox" NAME="<% $opt{'element_name_prefix'}. $name %>" <% $checked %> ID="<%$prefix.$num++%>" VALUE="ON">
+ </TD>
+ <TD><% $display %>
+% if ( $desc ) {
+ <BR><FONT SIZE="-2"><% $desc %></FONT>
+% }
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+
+ function setAll<%$prefix%>(setTo) {
+% for ( 0 .. ($num-1) ) {
+ document.getElementById('<%$prefix.$_%>').checked = setTo;
+% }
+ }
+
+ function toggleAll<%$prefix%>(setTo) {
+% for ( 0 .. ($num-1) ) {
+ var element = document.getElementById('<%$prefix.$_%>');
+ if ( element.checked == true ) {
+ element.checked = false;
+ } else {
+ element.checked = true;
+ }
+% }
+ }
+
+</SCRIPT>
+
+<%init>
+
+my( %opt ) = @_;
+
+my @pset = ( 'a'..'z', 'A'..'Z', '0'..'9' );
+
+my $prefix = $opt{prefix}
+ || join('', map $pset[ int(rand $#pset) ], (0..20) );
+
+$opt{checked_callback} ||= sub {};
+
+$opt{'error_checked_callback'} ||= sub {
+ my( $cgi, $name ) = @_;
+ $cgi->param($opt{'element_name_prefix'}. $name );
+};
+
+</%init>
diff --git a/httemplate/elements/city.html b/httemplate/elements/city.html
new file mode 100644
index 0000000..61d0578
--- /dev/null
+++ b/httemplate/elements/city.html
@@ -0,0 +1,145 @@
+<%doc>
+
+Example:
+
+ include( '/elements/city.html',
+ #recommended
+ country => $current_country,
+ state => $current_state,
+ county => $current_county,
+ city => $current_city,
+
+ #optional
+ prefix => $optional_unique_prefix,
+ onchange => $javascript,
+ disabled => 0, #bool
+# disable_empty => 1, #defaults to 1, disable the empty option
+# empty_label => 'all', #label for empty option
+ style => [ 'attribute:value', 'another:value' ],
+ );
+
+</%doc>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/cities.cgi',
+ 'subs' => [ $pre. 'get_cities' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ var saved_<%$pre%>city= '<% $saved_city |h %>';
+
+ function <% $pre %>county_changed(what, callback) {
+
+ county = what.options[what.selectedIndex].value;
+ state = what.form.<% $pre %>state.options[what.form.<% $pre %>state.selectedIndex].value;
+ country = what.form.<% $pre %>country.options[what.form.<% $pre %>country.selectedIndex].value;
+
+ function <% $pre %>update_cities(cities) {
+
+ // blank the current city list
+ for ( var i = what.form.<% $pre %>city_select.length; i >= 0; i-- )
+ what.form.<% $pre %>city_select.options[i] = null;
+
+ // add the new cities
+ var citiesArray = eval('(' + cities + ')' );
+
+ for ( var s = 0; s < citiesArray.length; s++ ) {
+ var cityLabel = citiesArray[s];
+ if ( cityLabel == "" )
+ cityLabel = '(n/a)';
+ opt(what.form.<% $pre %>city_select, citiesArray[s], cityLabel);
+ }
+
+ if ( citiesArray.length > 1 || citiesArray[0].length ) {
+ // turn off the text city, turn on the select
+ saved_<%$pre%>city = what.form.<%$ pre %>city.value;
+ <%$pre%>city_select_changed(what.form.<% $pre %>city_select);
+ what.form.<% $pre %>city.style.display = 'none';
+ what.form.<% $pre %>city_select.style.display = '';
+ } else {
+ // turn on the text city, turn off the select
+ what.form.<%$ pre %>city.value = saved_<%$pre%>city;
+ what.form.<% $pre %>city.style.display = '';
+ what.form.<% $pre %>city_select.style.display = 'none';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new cities
+ <% $pre %>get_cities( county, state, country, <% $pre %>update_cities );
+
+ }
+
+ function <%$pre%>city_select_changed(what) {
+ what.form.<%$pre%>city.value = what.options[what.selectedIndex].value;
+ }
+
+</SCRIPT>
+
+<INPUT TYPE = "text"
+ NAME = "<%$pre%>city"
+ ID = "<%$pre%>city"
+ VALUE = "<% $opt{'city'} |h %>"
+ onChange = "<% $opt{'onchange'} %>"
+ <% $opt{'disabled'} %>
+ <% $text_style %>
+>
+
+<SELECT NAME = "<%$pre%>city_select"
+ ID = "<%$pre%>city_select"
+ onChange = "<%$pre%>city_select_changed(this); <% $opt{'onchange'} %>"
+ <% $opt{'disabled'} %>
+ <% $select_style %>
+>
+
+% foreach my $city ( @cities ) {
+
+ <OPTION VALUE="<% $city |h %>"
+ <% $city eq $opt{'city'} ? 'SELECTED' : '' %>
+ ><% $city eq $opt{'empty_data_value'} ? $opt{'empty_data_label'} : $city %>
+
+% }
+
+</SELECT>
+
+%# VALUE = "<% $curr_value |h %>"
+<%init>
+
+my %opt = @_;
+
+my $pre = $opt{'prefix'};
+
+my $text_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : [];
+my $select_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : [];
+
+my @cities = cities( $opt{'county'}, $opt{'state'}, $opt{'country'} );
+my $saved_city = '';
+if ( scalar(@cities) > 1 || $cities[0] ) {
+ push @$text_style, 'display:none';
+} else {
+ push @$select_style, 'display:none';
+ $saved_city = $opt{'city'};
+}
+
+$text_style =
+ scalar(@$text_style)
+ ? 'STYLE="'. join(';', @$text_style). '"'
+ : '';
+
+$select_style =
+ scalar(@$select_style)
+ ? 'STYLE="'. join(';', @$select_style). '"'
+ : '';
+
+</%init>
diff --git a/httemplate/elements/columnend.html b/httemplate/elements/columnend.html
new file mode 100644
index 0000000..021a328
--- /dev/null
+++ b/httemplate/elements/columnend.html
@@ -0,0 +1,6 @@
+ </TABLE>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+</TR>
diff --git a/httemplate/elements/columnnext.html b/httemplate/elements/columnnext.html
new file mode 100644
index 0000000..4dfe82f
--- /dev/null
+++ b/httemplate/elements/columnnext.html
@@ -0,0 +1,4 @@
+ </TABLE>
+ </TD>
+ <TD VALIGN="top" STYLE="padding-left:12px">
+ <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
diff --git a/httemplate/elements/columnstart.html b/httemplate/elements/columnstart.html
new file mode 100644
index 0000000..be37d81
--- /dev/null
+++ b/httemplate/elements/columnstart.html
@@ -0,0 +1,6 @@
+<TR>
+ <TD CLASS="background" COLSPAN=99>
+ <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TD VALIGN="top">
+ <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
diff --git a/httemplate/elements/communigate_pro-accessmodes.html b/httemplate/elements/communigate_pro-accessmodes.html
new file mode 100644
index 0000000..6ce9ca5
--- /dev/null
+++ b/httemplate/elements/communigate_pro-accessmodes.html
@@ -0,0 +1,30 @@
+<% include( 'checkboxes.html',
+ 'element_name_prefix' => 'cgp_accessmodes_',
+ 'names_list' => \@names,
+ 'checked_callback' => $callback,
+ %opt,
+ )
+%>
+<%once>
+
+my @names = (qw(
+ Mail Relay Signal Mobile TLS POP IMAP MAPI
+ AirSync SIP XMPP WebMail XIMSS FTP ACAP PWD
+ LDAP RADIUS S/MIME WebCAL WebSite PBX HTTP
+));
+
+</%once>
+<%init>
+
+my %opt = @_;
+my $curr_value = $opt{'curr_value'};
+
+$curr_value = { map { $_=>1 } split(/\s+/, $curr_value) }
+ unless ref($curr_value);
+
+my $callback = sub {
+ my( $cgi, $name ) = @_;
+ $curr_value->{$name};
+};
+
+</%init>
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
new file mode 100644
index 0000000..a7a33b1
--- /dev/null
+++ b/httemplate/elements/contact.html
@@ -0,0 +1,56 @@
+% unless ( $opt{'js_only'} ) {
+
+ <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
+
+ <TABLE>
+ <TR>
+% foreach my $field ( @fields ) {
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "<%$name%>_<%$field%>"
+ ID = "<%$id%>_<%$field%>"
+ VALUE = "<% scalar($cgi->param($name."_$field"))
+ || $contact->get($field) |h %>"
+ <% $onchange %>
+ ><BR>
+ <FONT SIZE="-1"><% $label{$field} %></FONT>
+ </TD>
+% }
+ </TR>
+ </TABLE>
+
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $name = $opt{'element_name'} || $opt{'field'} || 'contactnum';
+my $id = $opt{'id'} || 'contactnum';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+my $onchange = '';
+if ( $opt{'onchange'} ) {
+ $onchange = $opt{'onchange'};
+ $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/;
+ $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange
+ #callbacks should act the same
+ $onchange = 'onChange="'. $onchange. '"';
+}
+
+my $contact;
+if ( $curr_value ) {
+ $contact = qsearchs('contact', { 'contactnum' => $curr_value } );
+} else {
+ $contact = new FS::contact {};
+}
+
+tie my %label, 'Tie::IxHash',
+ 'first' => 'First name',
+ 'last' => 'Last name',
+ 'title' => 'Title/Position',
+ 'comment' => 'Comment',
+;
+my @fields = keys %label;
+
+</%init>
diff --git a/httemplate/elements/cssexpr.js b/httemplate/elements/cssexpr.js
new file mode 100644
index 0000000..c434d8d
--- /dev/null
+++ b/httemplate/elements/cssexpr.js
@@ -0,0 +1,66 @@
+function constExpression(x) {
+ return x;
+}
+
+function simplifyCSSExpression() {
+ try {
+ var ss,sl, rs, rl;
+ ss = document.styleSheets;
+ sl = ss.length
+
+ for (var i = 0; i < sl; i++) {
+ simplifyCSSBlock(ss[i]);
+ }
+ }
+ catch (exc) {
+ //alert("Got an error while processing css. The page should still work but might be a bit slower");
+ throw exc;
+ }
+}
+
+function simplifyCSSBlock(ss) {
+ var rs, rl;
+
+ for (var i = 0; i < ss.imports.length; i++)
+ simplifyCSSBlock(ss.imports[i]);
+
+ if (ss.cssText.indexOf("expression(constExpression(") == -1)
+ return;
+
+ rs = ss.rules;
+ rl = rs.length;
+ for (var j = 0; j < rl; j++)
+ simplifyCSSRule(rs[j]);
+
+}
+
+function simplifyCSSRule(r) {
+ var str = r.style.cssText;
+ var str2 = str;
+ var lastStr;
+ do {
+ lastStr = str2;
+ str2 = simplifyCSSRuleHelper(lastStr);
+ } while (str2 != lastStr)
+
+ if (str2 != str)
+ r.style.cssText = str2;
+}
+
+function simplifyCSSRuleHelper(str) {
+ var i, i2;
+ i = str.indexOf("expression(constExpression(");
+ if (i == -1) return str;
+ i2 = str.indexOf("))", i);
+ var hd = str.substring(0, i);
+ var tl = str.substring(i2 + 2);
+ var exp = str.substring(i + 27, i2);
+ var val = eval(exp)
+ return hd + val + tl;
+}
+
+if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) {
+ window.attachEvent("onload", function () {
+ simplifyCSSExpression();
+ });
+}
diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html
new file mode 100644
index 0000000..f00419f
--- /dev/null
+++ b/httemplate/elements/customer-table.html
@@ -0,0 +1,524 @@
+<%doc>
+
+Example:
+
+ include( '/elements/customer-table.html',
+
+ ###
+ # required
+ ###
+
+ #listrefs...
+ 'header' => [ '#', 'Item' ],
+ 'fields' => [
+ 'column',
+ sub { my ($row,$param) = @_;
+ $param->{"column$row"};
+ },
+ ],
+
+ ###
+ # optional
+ ###
+
+ 'name_singular' => 'customer', #label
+
+ #listrefs
+ 'types' => ['immutable', ''], # immutable or ''/text
+ 'align' => [ 'c', 'l', 'r', '' ],
+ 'size' => [], # sizes ignored for immutable
+ 'color' => [],
+ 'footer' => ['string', '_TOTAL'], # strings or the special
+ #value _TOTAL
+ 'footer_align' => [ 'c', 'l', 'r', '' ],
+
+ 'param' => { column0 => 1 }, # preset column of row 0 to 1
+
+ )
+
+</%doc>
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_custnum() {
+
+ if ( this.value == 'Not found' || this.value == 'Multiple' ) {
+ this.value = '';
+ this.style.color = '#000000';
+ }
+
+ }
+
+ function clearhint_customer() {
+
+ this.style.color = '#000000';
+
+ if ( this.value == '(last name or company)' || this.value == 'Not found' )
+ this.value = '';
+
+ }
+
+ function <% $opt{prefix} %>search_custnum() {
+
+ this.style.color = '#000000'
+
+ var custnum_obj = this;
+ var searchrow = this.getAttribute('rownum');
+ var custnum = this.value;
+
+ if ( custnum == 'searching...' || custnum == 'Not found' || custnum == '' )
+ return;
+
+ if ( this.getAttribute('magic') == 'nosearch' ) {
+ this.setAttribute('magic', '');
+ return;
+ }
+
+ if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
+ <% $opt{prefix} %>addRow();
+ }
+ var customer = document.getElementById('customer'+searchrow);
+ customer.value = 'searching...';
+ customer.disabled = true;
+ customer.style.color = '#000000';
+ customer.style.backgroundColor = '#dddddd';
+
+ var customer_select = document.getElementById('cust_select'+searchrow);
+
+ customer.style.display = '';
+ customer_select.style.display = 'none';
+
+ function search_custnum_update(name) {
+
+ var name = eval('(' + name + ')' );
+
+ customer.disabled = false;
+ customer.style.backgroundColor = '#ffffff';
+
+ if ( name.length > 0 ) {
+ customer.value = name;
+ customer.setAttribute('magic', 'nosearch');
+ } else {
+ customer.value = 'Not found';
+ customer.style.color = '#ff0000';
+ custnum_obj.style.color = '#ff0000';
+
+ }
+
+ }
+
+ custnum_search( custnum, search_custnum_update );
+
+ }
+
+ function <% $opt{prefix} %>search_customer() {
+
+ var customer_obj = this;
+ var searchrow = this.getAttribute('rownum');
+ var customer = this.value;
+
+ if ( customer == 'searching...' || customer == 'Not found' || customer == '' )
+ return;
+
+ if ( this.getAttribute('magic') == 'nosearch' ) {
+ this.setAttribute('magic', '');
+ return;
+ }
+
+ if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
+ <% $opt{prefix} %>addRow();
+ }
+
+ var custnum_obj = document.getElementById('custnum'+searchrow);
+ custnum_obj.value = 'searching...';
+ custnum_obj.disabled = true;
+ custnum_obj.style.color = '#000000';
+ custnum_obj.style.backgroundColor = '#dddddd';
+
+ var customer_select = document.getElementById('cust_select'+searchrow);
+
+ function search_customer_update(customers) {
+
+ var customerArray = eval('(' + customers + ')');
+
+ custnum_obj.disabled = false;
+ custnum_obj.style.backgroundColor = '#ffffff';
+
+ if ( customerArray.length == 0 ) {
+
+ custnum_obj.value = 'Not found';
+ custnum_obj.style.color = '#ff0000';
+ customer_obj.style.color = '#ff0000';
+
+ customer_obj.style.display = '';
+ customer_select.style.display = 'none';
+
+
+ } else if ( customerArray.length == 1 ) {
+
+ custnum_obj.value = customerArray[0][0];
+ customer_obj.value = customerArray[0][1];
+
+ customer_obj.style.display = '';
+ customer_select.style.display = 'none';
+
+
+ } else {
+
+ custnum_obj.value = 'Multiple'; // or something
+ custnum_obj.style.color = '#ff0000';
+
+ //blank the current list
+ for ( var i = customer_select.length; i >= 0; i-- )
+ customer_select.options[i] = null;
+
+ opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
+
+ //add the multiple customers
+ for ( var s = 0; s < customerArray.length; s++ )
+ opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000');
+
+ opt(customer_select, 'cancel', '(Edit search string)', '#000000');
+
+ customer_obj.style.display = 'none';
+
+ customer_select.style.display = '';
+
+ }
+
+ }
+
+ smart_search( customer, search_customer_update );
+
+ }
+
+ function select_customer() {
+
+ var custnum = this.options[this.selectedIndex].value;
+ var customer = this.options[this.selectedIndex].text;
+
+ var searchrow = this.getAttribute('rownum');
+ var custnum_obj = document.getElementById('custnum'+searchrow);
+ var customer_obj = document.getElementById('customer'+searchrow);
+
+ if ( custnum == '' ) {
+
+ } else if ( custnum == 'cancel' ) {
+
+ custnum_obj.value = '';
+ custnum_obj.style.color = '#000000';
+
+ this.style.display = 'none';
+ customer_obj.style.display = '';
+ customer_obj.focus();
+
+ } else {
+
+ custnum_obj.value = custnum;
+ custnum_obj.style.color = '#000000';
+
+ customer_obj.value = customer;
+ customer_obj.style.color = '#000000';
+
+ this.style.display = 'none';
+ customer_obj.style.display = '';
+
+ }
+
+ }
+
+ function opt(what,value,text,color) {
+ var optionName = new Option(text, value, false, false);
+ optionName.style.color = color;
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+</SCRIPT>
+
+<TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<TR>
+ <TH>Cust #</TH>
+ <TH>Customer</TH>
+% foreach my $header ( @{$opt{header}} ) {
+ <TH><% $header %></TH>
+% }
+</TR>
+% my $row = 0;
+% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+
+ <TR>
+
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "custnum<% $row %>"
+ ID = "custnum<% $row %>"
+ SIZE = 8
+ MAXLENGTH = 12
+ STYLE = "text-align:right;"
+ VALUE = "<% $param->{"custnum$row"} %>"
+ rownum = "<% $row %>"
+ >
+ <SCRIPT TYPE="text/javascript">
+ var custnum_input<% $row %> = document.getElementById("custnum<% $row %>");
+ custnum_input<% $row %>.onfocus = clearhint_custnum;
+ custnum_input<% $row %>.onchange = <% $opt{prefix} %>search_custnum;
+ </SCRIPT>
+ </TD>
+
+ <TD>
+ <INPUT TYPE="text" NAME="customer<% $row %>" ID="customer<% $row %>" SIZE=64 VALUE="<% $param->{"customer$row"} %>" rownum="<% $row %>">
+ <SCRIPT TYPE="text/javascript">
+ var customer_input<% $row %> = document.getElementById("customer<% $row %>");
+ customer_input<% $row %>.onfocus = clearhint_customer;
+ customer_input<% $row %>.onclick = clearhint_customer;
+ customer_input<% $row %>.onchange = <% $opt{prefix} %>search_customer;
+ </SCRIPT>
+ <SELECT NAME="cust_select<% $row %>" ID="cust_select<% $row %>" rownum="<% $row %>" STYLE="color:#ff0000; display:none">
+ </SELECT>
+ <SCRIPT TYPE="text/javascript">
+ var customer_select<% $row %> = document.getElementById("cust_select<% $row %>");
+ customer_select<% $row %>.onchange = select_customer;
+ </SCRIPT>
+ </TD>
+
+% my $col = 0;
+% foreach my $field ( @{$opt{fields}} ) {
+% my $value;
+% if ( ref($field) eq 'CODE' ) {
+% $value = &{$field}($row,$param);
+% } else {
+% $value = $param->{"$field$row"};
+% }
+% my $name = (ref($field) eq 'CODE') ? "column${col}_$row" : "$field$row";
+% my $align = $align{ $opt{align}->[$col] || 'l' };
+% my $size = $sizes->[$col] || 10;
+% my $color = $opt{color}->[$col];
+% my $font = $color ? qq(<FONT COLOR="$color">) : '';
+% my $onchange = '';
+% if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+% $total[$col] += $value;
+% $onchange = $opt{prefix}. "calc_total$col();";
+% $onchange = qq(onchange="$onchange" onkeyup="$onchange");
+% }
+ <TD ALIGN="<% $align %>">
+% if (! $types->[$col] || $types->[$col] eq 'text') {
+ <INPUT TYPE = "text"
+ NAME = "<% $name %>"
+ ID = "<% $name %>"
+ SIZE = "<% $size %>"
+ STYLE = "text-align: <% $align %>;"
+ VALUE = "<% $value %>"
+ <% $onchange %>
+ >
+% } elsif ($types->[$col] eq 'immutable') {
+ <% $font %><% $value %><% $font ? '</FONT>' : '' %>
+ <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $value %>" >
+% } else {
+ Cannot represent unknown type: <% $types->[$col] %>
+% }
+ </TD>
+% $col++;
+% }
+
+ </TR>
+% }
+
+<TR>
+ <TH COLSPAN=2 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
+ Total <% $row ? $row-1 : 0 %>
+ <% PL($opt{name_singular} || 'customer', ( $row ? $row-1 : 0 ) ) %>
+ </TH>
+% my $col = 0;
+% foreach my $footer ( @{$opt{footer}} ) {
+% my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
+% if ($footer eq '_TOTAL' ) {
+% my $id = $opt{'fields'}->[$col];
+% $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
+ <TH ALIGN="<% $align %>" ID="<% $id %>">&nbsp;<% sprintf('%.2f', $total[$col] ) %></TH>
+% } else {
+ <TH ALIGN="<% $align %>"><% $footer %></TH>
+% }
+% $col++;
+% }
+</TR>
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+% my $col = 0;
+% foreach my $footer ( @{$opt{footer}} ) {
+% if ($footer eq '_TOTAL' ) {
+% my $name = $opt{fields}->[$col];
+% $name = ref($name) ? "column$col" : $name;
+ var <% $opt{prefix}.$name %>_CACHE = new Array ();
+ var <% $opt{prefix} %>th_el = document.getElementById("<%$name%>_TOTAL");
+ function <% $opt{prefix} %>calc_total<% $col %>() {
+ var row = 0;
+ var total = 0;
+ for ( var row = 0;
+
+ ( <% $opt{prefix}.$name%>_CACHE[row] =
+ <% $opt{prefix}.$name%>_CACHE[row]
+ || document.getElementById("<%$name%>"+row)
+ ) != null;
+
+ row++
+ )
+ {
+ var value = <%$name%>_CACHE[row].value;
+ value = parseFloat(value);
+ if ( ! isNaN(value) ) {
+ total = total + value;
+ }
+ }
+ <% $opt{prefix} %>th_el.innerHTML = '&nbsp;' + total.toFixed(2);
+
+ }
+% }
+% $col++;
+% }
+</SCRIPT>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi',
+ 'subs' => [qw( custnum_search smart_search )],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ var <% $opt{prefix} %>total_el =
+ document.getElementById("<% $opt{'prefix'} %>_TOTAL_TOTAL");
+
+ var <% $opt{prefix} %>rownum = <% $row %>;
+
+ function <% $opt{prefix} %>addRow() {
+
+ var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
+ var tablebody = table.getElementsByTagName('tbody').item(0);
+
+ var row = table.insertRow(rownum+1);
+
+ var custnum_cell = document.createElement('TD');
+
+ var custnum_input = document.createElement('INPUT');
+ custnum_input.setAttribute('name', 'custnum'+<% $opt{prefix} %>rownum);
+ custnum_input.setAttribute('id', 'custnum'+<% $opt{prefix} %>rownum);
+ custnum_input.style.textAlign = 'right';
+ custnum_input.setAttribute('size', 8);
+ custnum_input.setAttribute('maxlength', 12);
+ custnum_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+ custnum_input.onfocus = clearhint_custnum;
+ custnum_input.onchange = <% $opt{prefix} %>search_custnum;
+ custnum_cell.appendChild(custnum_input);
+
+ row.appendChild(custnum_cell);
+
+ var customer_cell = document.createElement('TD');
+
+ var customer_input = document.createElement('INPUT');
+ customer_input.setAttribute('name', 'customer'+<% $opt{prefix} %>rownum);
+ customer_input.setAttribute('id', 'customer'+<% $opt{prefix} %>rownum);
+ customer_input.setAttribute('size', 64);
+ customer_input.setAttribute('value', '(last name or company)' );
+ customer_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+ customer_input.onfocus = clearhint_customer;
+ customer_input.onclick = clearhint_customer;
+ customer_input.onchange = <% $opt{prefix} %>search_customer;
+ customer_cell.appendChild(customer_input);
+
+ var customer_select = document.createElement('SELECT');
+ customer_select.setAttribute('name', 'cust_select'+<% $opt{prefix} %>rownum);
+ customer_select.setAttribute('id', 'cust_select'+<% $opt{prefix} %>rownum);
+ customer_select.setAttribute('rownum', <% $opt{prefix} %>rownum);
+ customer_select.style.color = '#ff0000';
+ customer_select.style.display = 'none';
+ customer_select.onchange = select_customer;
+ customer_cell.appendChild(customer_select);
+
+ row.appendChild(customer_cell);
+
+% my $col = 0;
+% foreach my $field ( @{$opt{fields}} ) {
+
+ var my_cell = document.createElement('TD');
+ my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
+
+% if ($types->[$col] eq 'immutable') {
+% my $value;
+% if ( ref($field) eq 'CODE' ) {
+% $value = &{$field}($row,$param);
+% } else {
+% $value = $param->{"$field$row"};
+% }
+ var my_text = document.createTextNode('<% $value %>');
+ my_cell.appendChild(my_text);
+% }
+
+ var my_input = document.createElement('INPUT');
+ my_input.setAttribute('name', '<% $field %>'+<% $opt{prefix} %>rownum);
+ my_input.setAttribute('id', '<% $field %>'+<% $opt{prefix} %>rownum);
+ my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
+ my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
+% if ($types->[$col] eq 'immutable') {
+ my_input.setAttribute('type', 'hidden');
+% }
+% if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+ my_input.onchange = <% $opt{prefix} %>calc_total<%$col%>;
+ my_input.onkeyup = <% $opt{prefix} %>calc_total<%$col%>;
+% }
+ my_cell.appendChild(my_input);
+
+ row.appendChild(my_cell);
+
+% $col++;
+% }
+
+ //update the total # of rows display
+ if ( <% $opt{prefix} %>rownum == 1 ) {
+ <% $opt{prefix} %>total_el.innerHTML =
+ 'Total '
+ + <% $opt{prefix} %>rownum
+ + ' <% $opt{name_singular} || 'customer' %>';
+ } else {
+ <% $opt{prefix} %>total_el.innerHTML =
+ 'Total '
+ + <% $opt{prefix} %>rownum
+ + ' <% PL($opt{name_singular} || 'customer') %>';
+ }
+
+ <% $opt{prefix} %>rownum++;
+
+ }
+
+% unless ($cgi->param('error')) {
+ <% $opt{prefix} %>addRow();
+% }
+</SCRIPT>
+
+<%init>
+
+my(%opt) = @_;
+
+$opt{prefix} = '' unless defined $opt{prefix};
+$opt{prefix} .= '_' if $opt{prefix};
+
+my $types = $opt{'types'} ? [ @{$opt{'types'}} ] : [];
+my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
+
+my $param = $opt{param};
+$param = $cgi->Vars if $cgi->param('error');
+
+$opt{$_} ||= [] foreach qw(align color footer footer_align);
+
+my @total = map 0, @{$opt{footer}};
+
+my %align = (
+ 'l' => 'left',
+ 'r' => 'right',
+ 'c' => 'center',
+);
+
+</%init>
diff --git a/httemplate/elements/dashboard-install_welcome.html b/httemplate/elements/dashboard-install_welcome.html
new file mode 100644
index 0000000..c8e2ded
--- /dev/null
+++ b/httemplate/elements/dashboard-install_welcome.html
@@ -0,0 +1,10 @@
+% if ( $which ) {
+<% include("dashboard-install_welcome-$which.html") %>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+
+my $which = $conf->config('dashboard-install_welcome');
+
+</%init>
diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html
new file mode 100644
index 0000000..d8cd7f3
--- /dev/null
+++ b/httemplate/elements/dashboard-toplist.html
@@ -0,0 +1,113 @@
+% if ( $conf->exists('dashboard-toplist') ) {
+
+ <% include('/elements/table-grid.html') %>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = $bgcolor2;
+
+% foreach my $line ( $conf->config('dashboard-toplist') ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+% if ( $line =~ /^\s*cust_main:\s*(\d+)\s*$/ ) { #customer line
+% my $custnum = $1;
+% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+% if ( $cust_main ) {
+
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="view/cust_main.cgi?<% $custnum %>"><% $cust_main->name %></A>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% include('/elements/mcp_lint.html', 'cust_main'=>$cust_main) %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <FONT SIZE="-1"><A HREF="<% FS::TicketSystem->href_new_ticket($cust_main, join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) ) %>">(new ticket)</A></FONT>
+ </TD>
+
+% foreach my $priority ( @custom_priorities, '' ) {
+% my $num =
+% FS::TicketSystem->num_customer_tickets($custnum,$priority);
+% my $ahref = '';
+% $ahref= '<A HREF="'.
+% FS::TicketSystem->href_customer_tickets($custnum,$priority).
+% '">'
+% if $num;
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <% $ahref.$num %></A>
+ </TD>
+% }
+ </TR>
+
+% } else {
+
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ Unknown customer number <% $custnum %>
+ </TD>
+ </TR>
+
+% }
+%
+% } elsif ( $line =~ /^\-\-+$/ ) { #divider
+%
+ <TR>
+ <TH CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 4 %>"></TH>
+ </TR>
+
+% next;
+%
+% } elsif ( $line =~ /^\s*$/ ) {
+
+ <TR>
+ <TD CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 4 %>" BGCOLOR="<% $bgcolor %>">&nbsp;</TD>
+ </TR>
+
+% } elsif ( $line =~ /^\S/ ) { #label line
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% $line %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Lint</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+% foreach my $priority ( @custom_priorities, '' ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <% $priority || '<i>(none)</i>'%>
+ </TH>
+% }
+ </TR>
+
+% } else { #regular line
+
+ <TR>
+ <TD CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 4 %>" BGCOLOR="<% $bgcolor %>"><% $line %></TD>
+ </TR>
+
+% }
+
+%
+% }
+
+ </TABLE>
+ <BR>
+
+% }
+<%init>
+
+my $conf = new FS::Conf;
+
+#false laziness w/httemplate/search/cust_main.cgi... care if
+# custom_priority_field becomes anything but a local hack...
+my @custom_priorities = ();
+if ( $conf->config('ticket_system-custom_priority_field')
+ && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) {
+ @custom_priorities =
+ $conf->config('ticket_system-custom_priority_field-values');
+}
+
+</%init>
diff --git a/httemplate/elements/error.html b/httemplate/elements/error.html
new file mode 100644
index 0000000..f467de2
--- /dev/null
+++ b/httemplate/elements/error.html
@@ -0,0 +1,4 @@
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') |h %></FONT>
+ <BR><BR>
+% }
diff --git a/httemplate/elements/errorpage-popup.html b/httemplate/elements/errorpage-popup.html
new file mode 100644
index 0000000..5248ca1
--- /dev/null
+++ b/httemplate/elements/errorpage-popup.html
@@ -0,0 +1,15 @@
+<% include("/elements/header-popup.html", "Error") %>
+
+% while (@_) {
+
+<P><FONT SIZE="+1" COLOR="#ff0000"><% shift |h %></FONT>
+
+%}
+
+<BR><BR>
+<P ALIGN="center">
+<BUTTON TYPE="button" onClick="parent.cClick();">Close</BUTTON>
+
+% $m->flush_buffer();
+% $HTML::Mason::Commands::m->abort();
+% #die "shouldn't fall through to here (mason \$m->abort didn't)";
diff --git a/httemplate/elements/errorpage.html b/httemplate/elements/errorpage.html
new file mode 100644
index 0000000..76a0bf3
--- /dev/null
+++ b/httemplate/elements/errorpage.html
@@ -0,0 +1,11 @@
+<% include("/elements/header.html", "Error") %>
+
+% while (@_) {
+
+<P><FONT SIZE="+1" COLOR="#ff0000"><% shift |h %></FONT>
+
+%}
+
+% $m->flush_buffer();
+% $HTML::Mason::Commands::m->abort();
+% #die "shouldn't fall through to here (mason \$m->abort didn't)";
diff --git a/httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc b/httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc
new file mode 100644
index 0000000..8dfb661
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc
@@ -0,0 +1,15 @@
+<public:component lightweight="true">
+
+<script language="javascript">
+
+function CancelEvent()
+{
+ return false ;
+}
+
+this.onresizestart = CancelEvent ;
+this.onbeforeeditfocus = CancelEvent ;
+
+</script>
+
+</public:component>
diff --git a/httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc b/httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc
new file mode 100644
index 0000000..77418b9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc
@@ -0,0 +1,36 @@
+<public:component lightweight="true">
+
+<public:attach event="oncontentready" onevent="ShowBorders()" />
+<public:attach event="onpropertychange" onevent="OnPropertyChange()" />
+
+<script language="javascript">
+
+var oClassRegex = /\s*FCK__ShowTableBorders/ ;
+
+function ShowBorders()
+{
+ if ( this.border == 0 )
+ {
+ if ( !oClassRegex.test( this.className ) )
+ this.className += ' FCK__ShowTableBorders' ;
+ }
+ else
+ {
+ if ( oClassRegex.test( this.className ) )
+ {
+ this.className = this.className.replace( oClassRegex, '' ) ;
+ if ( this.className.length == 0 )
+ this.removeAttribute( 'className', 0 ) ;
+ }
+ }
+}
+
+function OnPropertyChange()
+{
+ if ( event.propertyName == 'border' || event.propertyName == 'className' )
+ ShowBorders.call(this) ;
+}
+
+</script>
+
+</public:component>
diff --git a/httemplate/elements/fckeditor/editor/css/fck_editorarea.css b/httemplate/elements/fckeditor/editor/css/fck_editorarea.css
new file mode 100644
index 0000000..50cfe0e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/fck_editorarea.css
@@ -0,0 +1,110 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the default CSS file used by the editor area. It defines the
+ * initial font of the editor and background color.
+ *
+ * A user can configure the editor to use another CSS file. Just change
+ * the value of the FCKConfig.EditorAreaCSS key in the configuration
+ * file.
+ */
+
+/**
+ * The "body" styles should match your editor web site, mainly regarding
+ * background color and font family and size.
+ */
+
+body
+{
+ background-color: #ffffff;
+ padding: 5px 5px 5px 5px;
+ margin: 0px;
+}
+
+body, td
+{
+ font-family: Arial, Verdana, sans-serif;
+ font-size: 12px;
+}
+
+a[href]
+{
+ color: -moz-hyperlinktext !important; /* For Firefox... mark as important, otherwise it becomes black */
+ text-decoration: -moz-anchor-decoration; /* For Firefox 3, otherwise no underline will be used */
+}
+
+/**
+ * Just uncomment the following block if you want to avoid spaces between
+ * paragraphs. Remember to apply the same style in your output front end page.
+ */
+
+/*
+p, ul, li
+{
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+*/
+
+/**
+ * Uncomment the following block, or only selected lines if appropriate,
+ * if you have some style items that would break the styles combo box.
+ * You can also write other CSS overrides inside the style block below
+ * as needed and they will be applied to inside the style combo only.
+ */
+
+/*
+.SC_Item *, .SC_ItemSelected *
+{
+ margin: 0px !important;
+ padding: 0px !important;
+ text-indent: 0px !important;
+ clip: auto !important;
+ position: static !important;
+}
+*/
+
+/**
+ * The following are some sample styles used in the "Styles" toolbar command.
+ * You should instead remove them, and include the styles used by the site
+ * you are using the editor in.
+ */
+
+.Bold
+{
+ font-weight: bold;
+}
+
+.Title
+{
+ font-weight: bold;
+ font-size: 18px;
+ color: #cc3300;
+}
+
+.Code
+{
+ border: #8b4513 1px solid;
+ padding-right: 5px;
+ padding-left: 5px;
+ color: #000066;
+ font-family: 'Courier New' , Monospace;
+ background-color: #ff9933;
+}
diff --git a/httemplate/elements/fckeditor/editor/css/fck_internal.css b/httemplate/elements/fckeditor/editor/css/fck_internal.css
new file mode 100644
index 0000000..d92c75e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/fck_internal.css
@@ -0,0 +1,199 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This CSS Style Sheet defines rules used by the editor for its internal use.
+ */
+
+/* #########
+ * WARNING
+ * #########
+ * When changing this file, the minified version of it must be updated in the
+ * fckeditor.html file (see FCK_InternalCSS).
+ */
+
+/* Fix to allow putting the caret at the end of the content in Firefox if
+ clicking below the content. */
+html
+{
+ min-height: 100%;
+}
+
+table.FCK__ShowTableBorders, table.FCK__ShowTableBorders td, table.FCK__ShowTableBorders th
+{
+ border: #d3d3d3 1px solid;
+}
+
+form
+{
+ border: 1px dotted #FF0000;
+ padding: 2px;
+}
+
+.FCK__Flash
+{
+ border: #a9a9a9 1px solid;
+ background-position: center center;
+ background-image: url(images/fck_flashlogo.gif);
+ background-repeat: no-repeat;
+ width: 80px;
+ height: 80px;
+}
+
+.FCK__UnknownObject
+{
+ border: #a9a9a9 1px solid;
+ background-position: center center;
+ background-image: url(images/fck_plugin.gif);
+ background-repeat: no-repeat;
+ width: 80px;
+ height: 80px;
+}
+
+/* Empty anchors images */
+.FCK__Anchor
+{
+ border: 1px dotted #00F;
+ background-position: center center;
+ background-image: url(images/fck_anchor.gif);
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 15px;
+ vertical-align: middle;
+}
+
+/* Anchors with content */
+.FCK__AnchorC
+{
+ border: 1px dotted #00F;
+ background-position: 1px center;
+ background-image: url(images/fck_anchor.gif);
+ background-repeat: no-repeat;
+ padding-left: 18px;
+}
+
+/* Any anchor for non-IE, if we combine it with the previous rule IE ignores all. */
+a[name]
+{
+ border: 1px dotted #00F;
+ background-position: 0 center;
+ background-image: url(images/fck_anchor.gif);
+ background-repeat: no-repeat;
+ padding-left: 18px;
+}
+
+.FCK__PageBreak
+{
+ background-position: center center;
+ background-image: url(images/fck_pagebreak.gif);
+ background-repeat: no-repeat;
+ clear: both;
+ display: block;
+ float: none;
+ width: 100%;
+ border-top: #999999 1px dotted;
+ border-bottom: #999999 1px dotted;
+ border-right: 0px;
+ border-left: 0px;
+ height: 5px;
+}
+
+/* Hidden fields */
+.FCK__InputHidden
+{
+ width: 19px;
+ height: 18px;
+ background-image: url(images/fck_hiddenfield.gif);
+ background-repeat: no-repeat;
+ vertical-align: text-bottom;
+ background-position: center center;
+}
+
+.FCK__ShowBlocks p,
+.FCK__ShowBlocks div,
+.FCK__ShowBlocks pre,
+.FCK__ShowBlocks address,
+.FCK__ShowBlocks blockquote,
+.FCK__ShowBlocks h1,
+.FCK__ShowBlocks h2,
+.FCK__ShowBlocks h3,
+.FCK__ShowBlocks h4,
+.FCK__ShowBlocks h5,
+.FCK__ShowBlocks h6
+{
+ background-repeat: no-repeat;
+ border: 1px dotted gray;
+ padding-top: 8px;
+ padding-left: 8px;
+}
+
+.FCK__ShowBlocks p
+{
+ background-image: url(images/block_p.png);
+}
+
+.FCK__ShowBlocks div
+{
+ background-image: url(images/block_div.png);
+}
+
+.FCK__ShowBlocks pre
+{
+ background-image: url(images/block_pre.png);
+}
+
+.FCK__ShowBlocks address
+{
+ background-image: url(images/block_address.png);
+}
+
+.FCK__ShowBlocks blockquote
+{
+ background-image: url(images/block_blockquote.png);
+}
+
+.FCK__ShowBlocks h1
+{
+ background-image: url(images/block_h1.png);
+}
+
+.FCK__ShowBlocks h2
+{
+ background-image: url(images/block_h2.png);
+}
+
+.FCK__ShowBlocks h3
+{
+ background-image: url(images/block_h3.png);
+}
+
+.FCK__ShowBlocks h4
+{
+ background-image: url(images/block_h4.png);
+}
+
+.FCK__ShowBlocks h5
+{
+ background-image: url(images/block_h5.png);
+}
+
+.FCK__ShowBlocks h6
+{
+ background-image: url(images/block_h6.png);
+}
diff --git a/httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css b/httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css
new file mode 100644
index 0000000..333161f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css
@@ -0,0 +1,49 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This CSS Style Sheet defines the rules to show table borders on Gecko.
+ */
+
+/* #########
+ * WARNING
+ * #########
+ * When changing this file, the minified version of it must be updated in the
+ * fckeditor.html file (see FCK_ShowTableBordersCSS).
+ */
+
+/* For tables with the "border" attribute set to "0" */
+table[border="0"],
+table[border="0"] > tr > td, table[border="0"] > tr > th,
+table[border="0"] > tbody > tr > td, table[border="0"] > tbody > tr > th,
+table[border="0"] > thead > tr > td, table[border="0"] > thead > tr > th,
+table[border="0"] > tfoot > tr > td, table[border="0"] > tfoot > tr > th
+{
+ border: #d3d3d3 1px dotted ;
+}
+
+/* For tables with no "border" attribute set */
+table:not([border]),
+table:not([border]) > tr > td, table:not([border]) > tr > th,
+table:not([border]) > tbody > tr > td, table:not([border]) > tbody > tr > th,
+table:not([border]) > thead > tr > td, table:not([border]) > thead > tr > th,
+table:not([border]) > tfoot > tr > td, table:not([border]) > tfoot > tr > th
+{
+ border: #d3d3d3 1px dotted ;
+}
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_address.png b/httemplate/elements/fckeditor/editor/css/images/block_address.png
new file mode 100644
index 0000000..8bbae6e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_address.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_blockquote.png b/httemplate/elements/fckeditor/editor/css/images/block_blockquote.png
new file mode 100644
index 0000000..cf065ba
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_blockquote.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_div.png b/httemplate/elements/fckeditor/editor/css/images/block_div.png
new file mode 100644
index 0000000..a2806b1
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_div.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h1.png b/httemplate/elements/fckeditor/editor/css/images/block_h1.png
new file mode 100644
index 0000000..d43fbdb
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h1.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h2.png b/httemplate/elements/fckeditor/editor/css/images/block_h2.png
new file mode 100644
index 0000000..27b5475
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h2.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h3.png b/httemplate/elements/fckeditor/editor/css/images/block_h3.png
new file mode 100644
index 0000000..3c3034f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h3.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h4.png b/httemplate/elements/fckeditor/editor/css/images/block_h4.png
new file mode 100644
index 0000000..ab3f64d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h4.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h5.png b/httemplate/elements/fckeditor/editor/css/images/block_h5.png
new file mode 100644
index 0000000..93477d0
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h5.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_h6.png b/httemplate/elements/fckeditor/editor/css/images/block_h6.png
new file mode 100644
index 0000000..11ea1c2
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_h6.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_p.png b/httemplate/elements/fckeditor/editor/css/images/block_p.png
new file mode 100644
index 0000000..d055c51
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_p.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/block_pre.png b/httemplate/elements/fckeditor/editor/css/images/block_pre.png
new file mode 100644
index 0000000..be8ad26
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/block_pre.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif b/httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif
new file mode 100644
index 0000000..5aa797b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif b/httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif
new file mode 100644
index 0000000..141aac4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif b/httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif
new file mode 100644
index 0000000..953f643
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif b/httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif
new file mode 100644
index 0000000..8d1cffd
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/css/images/fck_plugin.gif b/httemplate/elements/fckeditor/editor/css/images/fck_plugin.gif
new file mode 100644
index 0000000..7d58463
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/css/images/fck_plugin.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css b/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css
new file mode 100644
index 0000000..1119e44
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css
@@ -0,0 +1,85 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the CSS file used for interface details in some dialog
+ * windows.
+ */
+
+/* #########
+ * WARNING
+ * #########
+ * When changing this file, the minified version of it must be updated in the
+ * fck_dialog_common.js file (see GetCommonDialogCss).
+ */
+
+.ImagePreviewArea
+{
+ border: #000000 1px solid;
+ overflow: auto;
+ width: 100%;
+ height: 170px;
+ background-color: #ffffff;
+}
+
+.FlashPreviewArea
+{
+ border: #000000 1px solid;
+ padding: 5px;
+ overflow: auto;
+ width: 100%;
+ height: 170px;
+ background-color: #ffffff;
+}
+
+.BtnReset
+{
+ float: left;
+ background-position: center center;
+ background-image: url(images/reset.gif);
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ border: 1px none;
+ font-size: 1px ;
+}
+
+.BtnLocked, .BtnUnlocked
+{
+ float: left;
+ background-position: center center;
+ background-image: url(images/locked.gif);
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ border: none 1px;
+ font-size: 1px ;
+}
+
+.BtnUnlocked
+{
+ background-image: url(images/unlocked.gif);
+}
+
+.BtnOver
+{
+ border: outset 1px;
+ cursor: pointer;
+ cursor: hand;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js b/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js
new file mode 100644
index 0000000..478d3d5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js
@@ -0,0 +1,347 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Useful functions used by almost all dialog window pages.
+ * Dialogs should link to this file as the very first script on the page.
+ */
+
+// Automatically detect the correct document.domain (#123).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.parent.document.domain ;
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+// Attention: FCKConfig must be available in the page.
+function GetCommonDialogCss( prefix )
+{
+ // CSS minified by http://iceyboard.no-ip.org/projects/css_compressor (see _dev/css_compression.txt).
+ return FCKConfig.BasePath + 'dialog/common/' + '|.ImagePreviewArea{border:#000 1px solid;overflow:auto;width:100%;height:170px;background-color:#fff}.FlashPreviewArea{border:#000 1px solid;padding:5px;overflow:auto;width:100%;height:170px;background-color:#fff}.BtnReset{float:left;background-position:center center;background-image:url(images/reset.gif);width:16px;height:16px;background-repeat:no-repeat;border:1px none;font-size:1px}.BtnLocked,.BtnUnlocked{float:left;background-position:center center;background-image:url(images/locked.gif);width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.BtnUnlocked{background-image:url(images/unlocked.gif)}.BtnOver{border:outset 1px;cursor:pointer;cursor:hand}' ;
+}
+
+// Gets a element by its Id. Used for shorter coding.
+function GetE( elementId )
+{
+ return document.getElementById( elementId ) ;
+}
+
+function ShowE( element, isVisible )
+{
+ if ( typeof( element ) == 'string' )
+ element = GetE( element ) ;
+ element.style.display = isVisible ? '' : 'none' ;
+}
+
+function SetAttribute( element, attName, attValue )
+{
+ if ( attValue == null || attValue.length == 0 )
+ element.removeAttribute( attName, 0 ) ; // 0 : Case Insensitive
+ else
+ element.setAttribute( attName, attValue, 0 ) ; // 0 : Case Insensitive
+}
+
+function GetAttribute( element, attName, valueIfNull )
+{
+ var oAtt = element.attributes[attName] ;
+
+ if ( oAtt == null || !oAtt.specified )
+ return valueIfNull ? valueIfNull : '' ;
+
+ var oValue = element.getAttribute( attName, 2 ) ;
+
+ if ( oValue == null )
+ oValue = oAtt.nodeValue ;
+
+ return ( oValue == null ? valueIfNull : oValue ) ;
+}
+
+function SelectField( elementId )
+{
+ var element = GetE( elementId ) ;
+ element.focus() ;
+
+ // element.select may not be available for some fields (like <select>).
+ if ( element.select )
+ element.select() ;
+}
+
+// Functions used by text fields to accept numbers only.
+var IsDigit = ( function()
+ {
+ var KeyIdentifierMap =
+ {
+ End : 35,
+ Home : 36,
+ Left : 37,
+ Right : 39,
+ 'U+00007F' : 46 // Delete
+ } ;
+
+ return function ( e )
+ {
+ if ( !e )
+ e = event ;
+
+ var iCode = ( e.keyCode || e.charCode ) ;
+
+ if ( !iCode && e.keyIdentifier && ( e.keyIdentifier in KeyIdentifierMap ) )
+ iCode = KeyIdentifierMap[ e.keyIdentifier ] ;
+
+ return (
+ ( iCode >= 48 && iCode <= 57 ) // Numbers
+ || (iCode >= 35 && iCode <= 40) // Arrows, Home, End
+ || iCode == 8 // Backspace
+ || iCode == 46 // Delete
+ || iCode == 9 // Tab
+ ) ;
+ }
+ } )() ;
+
+String.prototype.Trim = function()
+{
+ return this.replace( /(^\s*)|(\s*$)/g, '' ) ;
+}
+
+String.prototype.StartsWith = function( value )
+{
+ return ( this.substr( 0, value.length ) == value ) ;
+}
+
+String.prototype.Remove = function( start, length )
+{
+ var s = '' ;
+
+ if ( start > 0 )
+ s = this.substring( 0, start ) ;
+
+ if ( start + length < this.length )
+ s += this.substring( start + length , this.length ) ;
+
+ return s ;
+}
+
+String.prototype.ReplaceAll = function( searchArray, replaceArray )
+{
+ var replaced = this ;
+
+ for ( var i = 0 ; i < searchArray.length ; i++ )
+ {
+ replaced = replaced.replace( searchArray[i], replaceArray[i] ) ;
+ }
+
+ return replaced ;
+}
+
+function OpenFileBrowser( url, width, height )
+{
+ // oEditor must be defined.
+
+ var iLeft = ( oEditor.FCKConfig.ScreenWidth - width ) / 2 ;
+ var iTop = ( oEditor.FCKConfig.ScreenHeight - height ) / 2 ;
+
+ var sOptions = "toolbar=no,status=no,resizable=yes,dependent=yes,scrollbars=yes" ;
+ sOptions += ",width=" + width ;
+ sOptions += ",height=" + height ;
+ sOptions += ",left=" + iLeft ;
+ sOptions += ",top=" + iTop ;
+
+ window.open( url, 'FCKBrowseWindow', sOptions ) ;
+}
+
+/**
+ Utility function to create/update an element with a name attribute in IE, so it behaves properly when moved around
+ It also allows to change the name or other special attributes in an existing node
+ oEditor : instance of FCKeditor where the element will be created
+ oOriginal : current element being edited or null if it has to be created
+ nodeName : string with the name of the element to create
+ oAttributes : Hash object with the attributes that must be set at creation time in IE
+ Those attributes will be set also after the element has been
+ created for any other browser to avoid redudant code
+*/
+function CreateNamedElement( oEditor, oOriginal, nodeName, oAttributes )
+{
+ var oNewNode ;
+
+ // IE doesn't allow easily to change properties of an existing object,
+ // so remove the old and force the creation of a new one.
+ var oldNode = null ;
+ if ( oOriginal && oEditor.FCKBrowserInfo.IsIE )
+ {
+ // Force the creation only if some of the special attributes have changed:
+ var bChanged = false;
+ for( var attName in oAttributes )
+ bChanged |= ( oOriginal.getAttribute( attName, 2) != oAttributes[attName] ) ;
+
+ if ( bChanged )
+ {
+ oldNode = oOriginal ;
+ oOriginal = null ;
+ }
+ }
+
+ // If the node existed (and it's not IE), then we just have to update its attributes
+ if ( oOriginal )
+ {
+ oNewNode = oOriginal ;
+ }
+ else
+ {
+ // #676, IE doesn't play nice with the name or type attribute
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ var sbHTML = [] ;
+ sbHTML.push( '<' + nodeName ) ;
+ for( var prop in oAttributes )
+ {
+ sbHTML.push( ' ' + prop + '="' + oAttributes[prop] + '"' ) ;
+ }
+ sbHTML.push( '>' ) ;
+ if ( !oEditor.FCKListsLib.EmptyElements[nodeName.toLowerCase()] )
+ sbHTML.push( '</' + nodeName + '>' ) ;
+
+ oNewNode = oEditor.FCK.EditorDocument.createElement( sbHTML.join('') ) ;
+ // Check if we are just changing the properties of an existing node: copy its properties
+ if ( oldNode )
+ {
+ CopyAttributes( oldNode, oNewNode, oAttributes ) ;
+ oEditor.FCKDomTools.MoveChildren( oldNode, oNewNode ) ;
+ oldNode.parentNode.removeChild( oldNode ) ;
+ oldNode = null ;
+
+ if ( oEditor.FCK.Selection.SelectionData )
+ {
+ // Trick to refresh the selection object and avoid error in
+ // fckdialog.html Selection.EnsureSelection
+ var oSel = oEditor.FCK.EditorDocument.selection ;
+ oEditor.FCK.Selection.SelectionData = oSel.createRange() ; // Now oSel.type will be 'None' reflecting the real situation
+ }
+ }
+ oNewNode = oEditor.FCK.InsertElement( oNewNode ) ;
+
+ // FCK.Selection.SelectionData is broken by now since we've
+ // deleted the previously selected element. So we need to reassign it.
+ if ( oEditor.FCK.Selection.SelectionData )
+ {
+ var range = oEditor.FCK.EditorDocument.body.createControlRange() ;
+ range.add( oNewNode ) ;
+ oEditor.FCK.Selection.SelectionData = range ;
+ }
+ }
+ else
+ {
+ oNewNode = oEditor.FCK.InsertElement( nodeName ) ;
+ }
+ }
+
+ // Set the basic attributes
+ for( var attName in oAttributes )
+ oNewNode.setAttribute( attName, oAttributes[attName], 0 ) ; // 0 : Case Insensitive
+
+ return oNewNode ;
+}
+
+// Copy all the attributes from one node to the other, kinda like a clone
+// But oSkipAttributes is an object with the attributes that must NOT be copied
+function CopyAttributes( oSource, oDest, oSkipAttributes )
+{
+ var aAttributes = oSource.attributes ;
+
+ for ( var n = 0 ; n < aAttributes.length ; n++ )
+ {
+ var oAttribute = aAttributes[n] ;
+
+ if ( oAttribute.specified )
+ {
+ var sAttName = oAttribute.nodeName ;
+ // We can set the type only once, so do it with the proper value, not copying it.
+ if ( sAttName in oSkipAttributes )
+ continue ;
+
+ var sAttValue = oSource.getAttribute( sAttName, 2 ) ;
+ if ( sAttValue == null )
+ sAttValue = oAttribute.nodeValue ;
+
+ oDest.setAttribute( sAttName, sAttValue, 0 ) ; // 0 : Case Insensitive
+ }
+ }
+ // The style:
+ if ( oSource.style.cssText !== '' )
+ oDest.style.cssText = oSource.style.cssText ;
+}
+
+/**
+* Replaces a tag with another one, keeping its contents:
+* for example TD --> TH, and TH --> TD.
+* input: the original node, and the new tag name
+* http://www.w3.org/TR/DOM-Level-3-Core/core.html#Document3-renameNode
+*/
+function RenameNode( oNode , newTag )
+{
+ // TODO: if the browser natively supports document.renameNode call it.
+ // does any browser currently support it in order to test?
+
+ // Only rename element nodes.
+ if ( oNode.nodeType != 1 )
+ return null ;
+
+ // If it's already correct exit here.
+ if ( oNode.nodeName == newTag )
+ return oNode ;
+
+ var oDoc = oNode.ownerDocument ;
+ // Create the new node
+ var newNode = oDoc.createElement( newTag ) ;
+
+ // Copy all attributes
+ CopyAttributes( oNode, newNode, {} ) ;
+
+ // Move children to the new node
+ FCKDomTools.MoveChildren( oNode, newNode ) ;
+
+ // Finally replace the node and return the new one
+ oNode.parentNode.replaceChild( newNode, oNode ) ;
+
+ return newNode ;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc b/httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc
new file mode 100644
index 0000000..74f26d0
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc
@@ -0,0 +1,24 @@
+<public:component lightweight="true">
+
+<script language="javascript">
+
+function CheckIsDigit()
+{
+ var iCode = event.keyCode ;
+
+ event.returnValue =
+ (
+ ( iCode >= 48 && iCode <= 57 ) // Numbers
+ || (iCode >= 37 && iCode <= 40) // Arrows
+ || iCode == 8 // Backspace
+ || iCode == 46 // Delete
+ ) ;
+
+ return event.returnValue ;
+}
+
+this.onkeypress = CheckIsDigit ;
+
+</script>
+
+</public:component>
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif b/httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif
new file mode 100644
index 0000000..ea07870
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif b/httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif
new file mode 100644
index 0000000..5e9a2fc
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif b/httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif
new file mode 100644
index 0000000..801e423
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml b/httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml
new file mode 100644
index 0000000..a457577
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="numericfield">
+ <implementation>
+ <constructor>
+ this.keypress = CheckIsDigit ;
+ </constructor>
+ <method name="CheckIsDigit">
+ <body>
+ <![CDATA[
+ var iCode = keyCode ;
+
+ var bAccepted =
+ (
+ ( iCode >= 48 && iCode <= 57 ) // Numbers
+ || (iCode >= 37 && iCode <= 40) // Arrows
+ || iCode == 8 // Backspace
+ || iCode == 46 // Delete
+ ) ;
+
+ return bAccepted ;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <events>
+ <event type="keypress" value="CheckIsDigit()" />
+ </events>
+ </binding>
+</bindings> \ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_about.html b/httemplate/elements/fckeditor/editor/dialog/fck_about.html
new file mode 100644
index 0000000..7e22d7b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_about.html
@@ -0,0 +1,161 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * "About" dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCKLang = oEditor.FCKLang ;
+
+window.parent.AddTab( 'About', FCKLang.DlgAboutAboutTab ) ;
+window.parent.AddTab( 'License', FCKLang.DlgAboutLicenseTab ) ;
+window.parent.AddTab( 'BrowserInfo', FCKLang.DlgAboutBrowserInfoTab ) ;
+
+// Function called when a dialog tag is selected.
+function OnDialogTabChange( tabCode )
+{
+ ShowE('divAbout', ( tabCode == 'About' ) ) ;
+ ShowE('divLicense', ( tabCode == 'License' ) ) ;
+ ShowE('divInfo' , ( tabCode == 'BrowserInfo' ) ) ;
+}
+
+function SendEMail()
+{
+ var eMail = 'mailto:' ;
+ eMail += 'fredck' ;
+ eMail += '@' ;
+ eMail += 'fckeditor' ;
+ eMail += '.' ;
+ eMail += 'net' ;
+
+ window.location = eMail ;
+}
+
+window.onload = function()
+{
+ // Translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ window.parent.SetAutoSize( true ) ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <div id="divAbout">
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" style="height: 100%">
+ <tr>
+ <td colspan="2">
+ <img alt="" src="fck_about/logo_fckeditor.gif" width="236" height="41" align="left" />
+ <table width="80" border="0" cellspacing="0" cellpadding="5" bgcolor="#ffffff" align="right">
+ <tr>
+ <td align="center" nowrap="nowrap" style="border-right: #000000 1px solid; border-top: #000000 1px solid;
+ border-left: #000000 1px solid; border-bottom: #000000 1px solid">
+ <span fcklang="DlgAboutVersion">version</span>
+ <br />
+ <b>2.6.6</b><br />
+ Build 25427</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr style="height: 100%">
+ <td align="center" valign="middle">
+ <span style="font-size: 14px" dir="ltr">
+ <b><a href="http://www.fckeditor.net/?about" target="_blank" title="Visit the FCKeditor web site">
+ Support <b>Open Source</b> Software</a></b> </span>
+ <div style="padding-top:15px">
+ <img alt="" src="fck_about/logo_fredck.gif" width="87" height="36" />
+ </div>
+ </td>
+ <td align="center" nowrap="nowrap" valign="middle">
+ <div>
+ <div style="margin-bottom:5px" dir="ltr">Selected Sponsor</div>
+ <a href="http://www.spellchecker.net/fckeditor/" target="_blank"><img alt="Selected Sponsor" border="0" src="fck_about/sponsors/spellchecker_net.gif" width="75" height="75" /></a>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td width="100%" nowrap="nowrap">
+ <span fcklang="DlgAboutInfo">For further information go to</span> <a href="http://www.fckeditor.net/?About"
+ target="_blank">http://www.fckeditor.net/</a>.
+ <br />
+ Copyright &copy; 2003-2010 <a href="#" onclick="SendEMail();">Frederico Caldeira Knabben</a>
+ </td>
+ <td align="center">
+ <a href="http://www.fckeditor.net/sponsors/apply" target="_blank">Become a Sponsor</a>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divLicense" style="display: none">
+ <p>
+ Licensed under the terms of any of the following licenses at your
+ choice:
+ </p>
+ <ul>
+ <li style="margin-bottom:15px">
+ <b>GNU General Public License</b> Version 2 or later (the "GPL")<br />
+ <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">http://www.gnu.org/licenses/gpl.html</a>
+ </li>
+ <li style="margin-bottom:15px">
+ <b>GNU Lesser General Public License</b> Version 2.1 or later (the "LGPL")<br />
+ <a href="http://www.gnu.org/licenses/lgpl.html" target="_blank">http://www.gnu.org/licenses/lgpl.html</a>
+ </li>
+ <li>
+ <b>Mozilla Public License</b> Version 1.1 or later (the "MPL")<br />
+ <a href="http://www.mozilla.org/MPL/MPL-1.1.html" target="_blank">http://www.mozilla.org/MPL/MPL-1.1.html</a>
+ </li>
+ </ul>
+ </div>
+ <div id="divInfo" style="display: none" dir="ltr">
+ <table align="center" width="80%" border="0">
+ <tr>
+ <td>
+ <script type="text/javascript">
+<!--
+document.write( '<b>User Agent<\/b><br />' + window.navigator.userAgent + '<br /><br />' ) ;
+document.write( '<b>Browser<\/b><br />' + window.navigator.appName + ' ' + window.navigator.appVersion + '<br /><br />' ) ;
+document.write( '<b>Platform<\/b><br />' + window.navigator.platform + '<br /><br />' ) ;
+
+var sUserLang = '?' ;
+
+if ( window.navigator.language )
+ sUserLang = window.navigator.language ;
+else if ( window.navigator.userLanguage )
+ sUserLang = window.navigator.userLanguage ;
+
+document.write( '<b>Language<\/b><br />' + sUserLang ) ;
+//-->
+ </script>
+ </td>
+ </tr>
+ </table>
+ </div>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif
new file mode 100644
index 0000000..b7d6bc6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif
new file mode 100644
index 0000000..3108dd9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gif b/httemplate/elements/fckeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gif
new file mode 100644
index 0000000..f8586f9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_anchor.html b/httemplate/elements/fckeditor/editor/dialog/fck_anchor.html
new file mode 100644
index 0000000..0f08faa
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_anchor.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Anchor dialog window.
+-->
+<html>
+ <head>
+ <title>Anchor Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+var FCK = oEditor.FCK ;
+var FCKBrowserInfo = oEditor.FCKBrowserInfo ;
+var FCKTools = oEditor.FCKTools ;
+var FCKRegexLib = oEditor.FCKRegexLib ;
+
+var oDOM = FCK.EditorDocument ;
+
+var oFakeImage = dialog.Selection.GetSelectedElement() ;
+
+var oAnchor ;
+
+if ( oFakeImage )
+{
+ if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckanchor') )
+ oAnchor = FCK.GetRealElement( oFakeImage ) ;
+ else
+ oFakeImage = null ;
+}
+
+//Search for a real anchor
+if ( !oFakeImage )
+{
+ oAnchor = FCK.Selection.MoveToAncestorNode( 'A' ) ;
+ if ( oAnchor )
+ FCK.Selection.SelectNode( oAnchor ) ;
+}
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oAnchor )
+ GetE('txtName').value = oAnchor.name ;
+ else
+ oAnchor = null ;
+
+ window.parent.SetOkButton( true ) ;
+ window.parent.SetAutoSize( true ) ;
+
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ var sNewName = GetE('txtName').value ;
+
+ // Remove any illegal character in a name attribute:
+ // A name should start with a letter, but the validator passes anyway.
+ sNewName = sNewName.replace( /[^\w-_\.:]/g, '_' ) ;
+
+ if ( sNewName.length == 0 )
+ {
+ // Remove the anchor if the user leaves the name blank
+ if ( oAnchor )
+ {
+ // Removes the current anchor from the document using the new command
+ FCK.Commands.GetCommand( 'AnchorDelete' ).Execute() ;
+ return true ;
+ }
+
+ alert( oEditor.FCKLang.DlgAnchorErrorName ) ;
+ return false ;
+ }
+
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ if ( oAnchor ) // Modifying an existent anchor.
+ {
+ ReadjustLinksToAnchor( oAnchor.name, sNewName );
+
+ // Buggy explorer, bad bad browser. http://alt-tag.com/blog/archives/2006/02/ie-dom-bugs/
+ // Instead of just replacing the .name for the existing anchor (in order to preserve the content), we must remove the .name
+ // and assign .name, although it won't appear until it's specially processed in fckxhtml.js
+
+ // We remove the previous name
+ oAnchor.removeAttribute( 'name' ) ;
+ // Now we set it, but later we must process it specially
+ oAnchor.name = sNewName ;
+
+ return true ;
+ }
+
+ // Create a new anchor preserving the current selection
+ var aNewAnchors = oEditor.FCK.CreateLink( '#' ) ;
+
+ if ( aNewAnchors.length == 0 )
+ aNewAnchors.push( oEditor.FCK.InsertElement( 'a' ) ) ;
+ else
+ {
+ // Remove the fake href
+ for ( var i = 0 ; i < aNewAnchors.length ; i++ )
+ aNewAnchors[i].removeAttribute( 'href' ) ;
+ }
+
+ // More than one anchors may have been created, so interact through all of them (see #220).
+ for ( var i = 0 ; i < aNewAnchors.length ; i++ )
+ {
+ oAnchor = aNewAnchors[i] ;
+
+ // Set the name
+ if ( FCKBrowserInfo.IsIE )
+ {
+ // Setting anchor names directly in IE will trash the HTML code stored
+ // in FCKTempBin after undos. See #2263.
+ var replaceAnchor = oEditor.FCK.EditorDocument.createElement( '<a name="' +
+ FCKTools.HTMLEncode( sNewName ).replace( '"', '&quot;' ) + '">' ) ;
+ oEditor.FCKDomTools.MoveChildren( oAnchor, replaceAnchor ) ;
+ oAnchor.parentNode.replaceChild( replaceAnchor, oAnchor ) ;
+ oAnchor = replaceAnchor ;
+ }
+ else
+ oAnchor.name = sNewName ;
+
+ // IE does require special processing to show the Anchor's image
+ // Opera doesn't allow to select empty anchors
+ if ( FCKBrowserInfo.IsIE || FCKBrowserInfo.IsOpera )
+ {
+ if ( oAnchor.innerHTML != '' )
+ {
+ if ( FCKBrowserInfo.IsIE )
+ oAnchor.className += ' FCK__AnchorC' ;
+ }
+ else
+ {
+ // Create a fake image for both IE and Opera
+ var oImg = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__Anchor', oAnchor.cloneNode(true) ) ;
+ oImg.setAttribute( '_fckanchor', 'true', 0 ) ;
+
+ oAnchor.parentNode.insertBefore( oImg, oAnchor ) ;
+ oAnchor.parentNode.removeChild( oAnchor ) ;
+ }
+
+ }
+ }
+
+ return true ;
+}
+
+// Checks all the links in the current page pointing to the current name and changes them to the new name
+function ReadjustLinksToAnchor( sCurrent, sNew )
+{
+ var oDoc = FCK.EditorDocument ;
+
+ var aLinks = oDoc.getElementsByTagName( 'A' ) ;
+
+ var sReference = '#' + sCurrent ;
+ // The url of the document, so we check absolute and partial references.
+ var sFullReference = oDoc.location.href.replace( /(#.*$)/, '') ;
+ sFullReference += sReference ;
+
+ var oLink ;
+ var i = aLinks.length - 1 ;
+ while ( i >= 0 && ( oLink = aLinks[i--] ) )
+ {
+ var sHRef = oLink.getAttribute( '_fcksavedurl' ) ;
+ if ( sHRef == null )
+ sHRef = oLink.getAttribute( 'href' , 2 ) || '' ;
+
+ if ( sHRef == sReference || sHRef == sFullReference )
+ {
+ oLink.href = '#' + sNew ;
+ SetAttribute( oLink, '_fcksavedurl', '#' + sNew ) ;
+ }
+ }
+}
+
+ </script>
+ </head>
+ <body style="overflow: hidden">
+ <table height="100%" width="100%">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td>
+ <span fckLang="DlgAnchorName">Anchor Name</span><BR>
+ <input id="txtName" style="WIDTH: 100%" type="text">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_button.html b/httemplate/elements/fckeditor/editor/dialog/fck_button.html
new file mode 100644
index 0000000..9351013
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_button.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Button dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Button Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl && oActiveEl.tagName.toUpperCase() == "INPUT" && ( oActiveEl.type == "button" || oActiveEl.type == "submit" || oActiveEl.type == "reset" ) )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtValue').value = oActiveEl.value ;
+ GetE('txtType').value = oActiveEl.type ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'INPUT', {name: GetE('txtName').value, type: GetE('txtType').value } ) ;
+
+ SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table width="100%" style="height: 100%">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td colspan="">
+ <span fcklang="DlgCheckboxName">Name</span><br />
+ <input type="text" size="20" id="txtName" style="width: 100%" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgButtonText">Text (Value)</span><br />
+ <input type="text" id="txtValue" style="width: 100%" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgButtonType">Type</span><br />
+ <select id="txtType">
+ <option fcklang="DlgButtonTypeBtn" value="button" selected="selected">Button</option>
+ <option fcklang="DlgButtonTypeSbm" value="submit">Submit</option>
+ <option fcklang="DlgButtonTypeRst" value="reset">Reset</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html b/httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html
new file mode 100644
index 0000000..624c7df
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Checkbox dialog window.
+-->
+<html>
+ <head>
+ <title>Checkbox Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl && oActiveEl.tagName == 'INPUT' && oActiveEl.type == 'checkbox' )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtValue').value = oEditor.FCKBrowserInfo.IsIE ? oActiveEl.value : GetAttribute( oActiveEl, 'value' ) ;
+ GetE('txtSelected').checked = oActiveEl.checked ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'INPUT', {name: GetE('txtName').value, type: 'checkbox' } ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ oActiveEl.value = GetE('txtValue').value ;
+ else
+ SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;
+
+ var bIsChecked = GetE('txtSelected').checked ;
+ SetAttribute( oActiveEl, 'checked', bIsChecked ? 'checked' : null ) ; // For Firefox
+ oActiveEl.checked = bIsChecked ;
+
+ return true ;
+}
+
+ </script>
+ </head>
+ <body style="OVERFLOW: hidden" scroll="no">
+ <table height="100%" width="100%">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td>
+ <span fckLang="DlgCheckboxName">Name</span><br>
+ <input type="text" size="20" id="txtName" style="WIDTH: 100%">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fckLang="DlgCheckboxValue">Value</span><br>
+ <input type="text" size="20" id="txtValue" style="WIDTH: 100%">
+ </td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" id="txtSelected"><label for="txtSelected" fckLang="DlgCheckboxSelected">Checked</label></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html b/httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html
new file mode 100644
index 0000000..0f2aaac
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Color Selection dialog window.
+-->
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <style TYPE="text/css">
+ #ColorTable { cursor: pointer ; cursor: hand ; }
+ #hicolor { height: 74px ; width: 74px ; border-width: 1px ; border-style: solid ; }
+ #hicolortext { width: 75px ; text-align: right ; margin-bottom: 7px ; }
+ #selhicolor { height: 20px ; width: 74px ; border-width: 1px ; border-style: solid ; }
+ #selcolor { width: 75px ; height: 20px ; margin-top: 0px ; margin-bottom: 7px ; }
+ #btnClear { width: 75px ; height: 22px ; margin-bottom: 6px ; }
+ .ColorCell { height: 15px ; width: 15px ; }
+ </style>
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+
+function OnLoad()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ CreateColorTable() ;
+
+ window.parent.SetOkButton( true ) ;
+ window.parent.SetAutoSize( true ) ;
+}
+
+function CreateColorTable()
+{
+ // Get the target table.
+ var oTable = document.getElementById('ColorTable') ;
+
+ // Create the base colors array.
+ var aColors = ['00','33','66','99','cc','ff'] ;
+
+ // This function combines two ranges of three values from the color array into a row.
+ function AppendColorRow( rangeA, rangeB )
+ {
+ for ( var i = rangeA ; i < rangeA + 3 ; i++ )
+ {
+ var oRow = oTable.insertRow(-1) ;
+
+ for ( var j = rangeB ; j < rangeB + 3 ; j++ )
+ {
+ for ( var n = 0 ; n < 6 ; n++ )
+ {
+ AppendColorCell( oRow, '#' + aColors[j] + aColors[n] + aColors[i] ) ;
+ }
+ }
+ }
+ }
+
+ // This function create a single color cell in the color table.
+ function AppendColorCell( targetRow, color )
+ {
+ var oCell = targetRow.insertCell(-1) ;
+ oCell.className = 'ColorCell' ;
+ oCell.bgColor = color ;
+
+ oCell.onmouseover = function()
+ {
+ document.getElementById('hicolor').style.backgroundColor = this.bgColor ;
+ document.getElementById('hicolortext').innerHTML = this.bgColor ;
+ }
+
+ oCell.onclick = function()
+ {
+ document.getElementById('selhicolor').style.backgroundColor = this.bgColor ;
+ document.getElementById('selcolor').value = this.bgColor ;
+ }
+ }
+
+ AppendColorRow( 0, 0 ) ;
+ AppendColorRow( 3, 0 ) ;
+ AppendColorRow( 0, 3 ) ;
+ AppendColorRow( 3, 3 ) ;
+
+ // Create the last row.
+ var oRow = oTable.insertRow(-1) ;
+
+ // Create the gray scale colors cells.
+ for ( var n = 0 ; n < 6 ; n++ )
+ {
+ AppendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ;
+ }
+
+ // Fill the row with black cells.
+ for ( var i = 0 ; i < 12 ; i++ )
+ {
+ AppendColorCell( oRow, '#000000' ) ;
+ }
+}
+
+function Clear()
+{
+ document.getElementById('selhicolor').style.backgroundColor = '' ;
+ document.getElementById('selcolor').value = '' ;
+}
+
+function ClearActual()
+{
+ document.getElementById('hicolor').style.backgroundColor = '' ;
+ document.getElementById('hicolortext').innerHTML = '&nbsp;' ;
+}
+
+function UpdateColor()
+{
+ try { document.getElementById('selhicolor').style.backgroundColor = document.getElementById('selcolor').value ; }
+ catch (e) { Clear() ; }
+}
+
+function Ok()
+{
+ if ( typeof(window.parent.Args().CustomValue) == 'function' )
+ window.parent.Args().CustomValue( document.getElementById('selcolor').value ) ;
+
+ return true ;
+}
+ </script>
+ </head>
+ <body onload="OnLoad()" scroll="no" style="OVERFLOW: hidden">
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" height="100%">
+ <tr>
+ <td align="center" valign="middle">
+ <table border="0" cellspacing="5" cellpadding="0" width="100%">
+ <tr>
+ <td valign="top" align="center" nowrap width="100%">
+ <table id="ColorTable" border="0" cellspacing="0" cellpadding="0" width="270" onmouseout="ClearActual();">
+ </table>
+ </td>
+ <td valign="top" align="left" nowrap>
+ <span fckLang="DlgColorHighlight">Highlight</span>
+ <div id="hicolor"></div>
+ <div id="hicolortext">&nbsp;</div>
+ <span fckLang="DlgColorSelected">Selected</span>
+ <div id="selhicolor"></div>
+ <input id="selcolor" type="text" maxlength="20" onchange="UpdateColor();">
+ <br>
+ <input id="btnClear" type="button" fckLang="DlgColorBtnClear" value="Clear" onclick="Clear();" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_div.html b/httemplate/elements/fckeditor/editor/dialog/fck_div.html
new file mode 100644
index 0000000..524c66a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_div.html
@@ -0,0 +1,396 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Form dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKBrowserInfo = oEditor.FCKBrowserInfo ;
+var FCKStyles = oEditor.FCKStyles ;
+var FCKElementPath = oEditor.FCKElementPath ;
+var FCKDomRange = oEditor.FCKDomRange ;
+var FCKDomTools = oEditor.FCKDomTools ;
+var FCKDomRangeIterator = oEditor.FCKDomRangeIterator ;
+var FCKListsLib = oEditor.FCKListsLib ;
+var AlwaysCreate = dialog.Args().CustomValue ;
+
+String.prototype.IEquals = function()
+{
+ var thisUpper = this.toUpperCase() ;
+
+ var aArgs = arguments ;
+
+ // The arguments could also be a single array.
+ if ( aArgs.length == 1 && aArgs[0].pop )
+ aArgs = aArgs[0] ;
+
+ for ( var i = 0 ; i < aArgs.length ; i++ )
+ {
+ if ( thisUpper == aArgs[i].toUpperCase() )
+ return true ;
+ }
+ return false ;
+}
+
+var CurrentContainers = [] ;
+if ( !AlwaysCreate )
+{
+ dialog.Selection.EnsureSelection() ;
+ CurrentContainers = FCKDomTools.GetSelectedDivContainers() ;
+}
+
+// Add some tabs
+dialog.AddTab( 'General', FCKLang.DlgDivGeneralTab );
+dialog.AddTab( 'Advanced', FCKLang.DlgDivAdvancedTab ) ;
+
+function AddStyleOption( styleName )
+{
+ var el = GetE( 'selStyle' ) ;
+ var opt = document.createElement( 'option' ) ;
+ opt.text = opt.value = styleName ;
+
+ if ( FCKBrowserInfo.IsIE )
+ el.add( opt ) ;
+ else
+ el.add( opt, null ) ;
+}
+
+function OnDialogTabChange( tabCode )
+{
+ ShowE( 'divGeneral', tabCode == 'General' ) ;
+ ShowE( 'divAdvanced', tabCode == 'Advanced' ) ;
+ dialog.SetAutoSize( true ) ;
+}
+
+function GetNearestAncestorDirection( node )
+{
+ var dir = 'ltr' ; // HTML default.
+ while ( ( node = node.parentNode ) )
+ {
+ if ( node.dir )
+ dir = node.dir ;
+ }
+ return dir ;
+}
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+
+ // Popuplate the style menu
+ var styles = FCKStyles.GetStyles() ;
+ var selectableStyles = {} ;
+ for ( var i in styles )
+ {
+ if ( ! /^_FCK_/.test( i ) && styles[i].Element == 'div' )
+ selectableStyles[i] = styles[i] ;
+ }
+ if ( CurrentContainers.length <= 1 )
+ {
+ var target = CurrentContainers[0] ;
+ var match = null ;
+ for ( var i in selectableStyles )
+ {
+ if ( target && styles[i].CheckElementRemovable( target, true ) )
+ match = i ;
+ }
+ if ( !match )
+ AddStyleOption( "" ) ;
+ for ( var i in selectableStyles )
+ AddStyleOption( i ) ;
+ if ( match )
+ GetE( 'selStyle' ).value = match ;
+
+ // Set the value for other inputs
+ if ( target )
+ {
+ GetE( 'txtClass' ).value = target.className ;
+ GetE( 'txtId' ).value = target.id ;
+ GetE( 'txtLang' ).value = target.lang ;
+ GetE( 'txtInlineStyle').value = target.style.cssText ;
+ GetE( 'txtTitle' ).value = target.title ;
+ GetE( 'selLangDir').value = target.dir || GetNearestAncestorDirection( target ) ;
+ }
+ }
+ else
+ {
+ GetE( 'txtId' ).disabled = true ;
+ AddStyleOption( "" ) ;
+ for ( var i in selectableStyles )
+ AddStyleOption( i ) ;
+ }
+}
+
+function CreateDiv()
+{
+ var newBlocks = [] ;
+ var range = new FCKDomRange( FCK.EditorWindow ) ;
+ range.MoveToSelection() ;
+
+ var bookmark = range.CreateBookmark() ;
+
+ // Kludge for #1592: if the bookmark nodes are in the beginning of
+ // $tagName, then move them to the nearest block element in the
+ // $tagName.
+ if ( FCKBrowserInfo.IsIE )
+ {
+ var bStart = range.GetBookmarkNode( bookmark, true ) ;
+ var bEnd = range.GetBookmarkNode( bookmark, false ) ;
+
+ var cursor ;
+
+ if ( bStart
+ && bStart.parentNode.nodeName.IEquals( 'div' )
+ && !bStart.previousSibling )
+ {
+ cursor = bStart ;
+ while ( ( cursor = cursor.nextSibling ) )
+ {
+ if ( FCKListsLib.BlockElements[ cursor.nodeName.toLowerCase() ] )
+ FCKDomTools.MoveNode( bStart, cursor, true ) ;
+ }
+ }
+
+ if ( bEnd
+ && bEnd.parentNode.nodeName.IEquals( 'div' )
+ && !bEnd.previousSibling )
+ {
+ cursor = bEnd ;
+ while ( ( cursor = cursor.nextSibling ) )
+ {
+ if ( FCKListsLib.BlockElements[ cursor.nodeName.toLowerCase() ] )
+ {
+ if ( cursor.firstChild == bStart )
+ FCKDomTools.InsertAfterNode( bStart, bEnd ) ;
+ else
+ FCKDomTools.MoveNode( bEnd, cursor, true ) ;
+ }
+ }
+ }
+ }
+
+ var iterator = new FCKDomRangeIterator( range ) ;
+ var block ;
+
+ var paragraphs = [] ;
+ while ( ( block = iterator.GetNextParagraph() ) )
+ paragraphs.push( block ) ;
+
+ // Make sure all paragraphs have the same parent.
+ var commonParent = paragraphs[0].parentNode ;
+ var tmp = [] ;
+ for ( var i = 0 ; i < paragraphs.length ; i++ )
+ {
+ block = paragraphs[i] ;
+ commonParent = FCKDomTools.GetCommonParents( block.parentNode, commonParent ).pop() ;
+ }
+
+ // The common parent must not be the following tags: table, tbody, tr, ol, ul.
+ while ( commonParent.nodeName.IEquals( 'table', 'tbody', 'tr', 'ol', 'ul' ) )
+ commonParent = commonParent.parentNode ;
+
+ // Reconstruct the block list to be processed such that all resulting blocks
+ // satisfy parentNode == commonParent.
+ var lastBlock = null ;
+ while ( paragraphs.length > 0 )
+ {
+ block = paragraphs.shift() ;
+ while ( block.parentNode != commonParent )
+ block = block.parentNode ;
+ if ( block != lastBlock )
+ tmp.push( block ) ;
+ lastBlock = block ;
+ }
+ paragraphs = tmp ;
+
+ // Split the paragraphs into groups depending on their BlockLimit element.
+ var groups = [] ;
+ var lastBlockLimit = null ;
+ for ( var i = 0 ; i < paragraphs.length ; i++ )
+ {
+ block = paragraphs[i] ;
+ var elementPath = new FCKElementPath( block ) ;
+ if ( elementPath.BlockLimit != lastBlockLimit )
+ {
+ groups.push( [] ) ;
+ lastBlockLimit = elementPath.BlockLimit ;
+ }
+ groups[groups.length - 1].push( block ) ;
+ }
+
+ // Create a DIV container for each group.
+ for ( var i = 0 ; i < groups.length ; i++ )
+ {
+ var divNode = FCK.EditorDocument.createElement( 'div' ) ;
+ groups[i][0].parentNode.insertBefore( divNode, groups[i][0] ) ;
+ for ( var j = 0 ; j < groups[i].length ; j++ )
+ FCKDomTools.MoveNode( groups[i][j], divNode ) ;
+ newBlocks.push( divNode ) ;
+ }
+
+ range.MoveToBookmark( bookmark ) ;
+ range.Select() ;
+
+ FCK.Focus() ;
+ FCK.Events.FireEvent( 'OnSelectionChange' ) ;
+
+ return newBlocks ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ if ( CurrentContainers.length < 1 )
+ CurrentContainers = CreateDiv();
+
+ var setValue = function( attrName, inputName )
+ {
+ var val = GetE( inputName ).value ;
+ for ( var i = 0 ; i < CurrentContainers.length ; i++ )
+ {
+ if ( val == '' )
+ CurrentContainers[i].removeAttribute( attrName ) ;
+ else
+ CurrentContainers[i].setAttribute( attrName, val ) ;
+ }
+ }
+
+ // Apply modifications to the DIV container according to dialog inputs.
+ if ( CurrentContainers.length == 1 )
+ {
+ setValue( 'class', 'txtClass' ) ;
+ setValue( 'id', 'txtId' ) ;
+ }
+ setValue( 'lang', 'txtLang' ) ;
+ if ( FCKBrowserInfo.IsIE )
+ {
+ for ( var i = 0 ; i < CurrentContainers.length ; i++ )
+ CurrentContainers[i].style.cssText = GetE( 'txtInlineStyle' ).value ;
+ }
+ else
+ setValue( 'style', 'txtInlineStyle' ) ;
+ setValue( 'title', 'txtTitle' ) ;
+ for ( var i = 0 ; i < CurrentContainers.length ; i++ )
+ {
+ var dir = GetE( 'selLangDir' ).value ;
+ var styleName = GetE( 'selStyle' ).value ;
+ if ( GetNearestAncestorDirection( CurrentContainers[i] ) != dir )
+ CurrentContainers[i].dir = dir ;
+ else
+ CurrentContainers[i].removeAttribute( 'dir' ) ;
+
+ if ( styleName )
+ FCKStyles.GetStyle( styleName ).ApplyToObject( CurrentContainers[i] ) ;
+ }
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <div id="divGeneral">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <colgroup span="2">
+ <col width="49%" />
+ <col width="2%" />
+ <col width="49%" />
+ </colgroup>
+ <tr>
+ <td>
+ <span fcklang="DlgDivStyle">Style</span><br />
+ <select id="selStyle" style="width: 100%;">
+ </select>
+ </td>
+ <td>&nbsp;</td>
+ <td>
+ <span fcklang="DlgGenClass">Stylesheet Classes</span><br />
+ <input id="txtClass" style="width: 100%" type="text" />
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divAdvanced" style="display: none">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <colgroup span="2">
+ <col width="49%" />
+ <col width="2%" />
+ <col width="49%" />
+ </colgroup>
+ <tr>
+ <td>
+ <span fcklang="DlgGenId">Id</span><br />
+ <input style="width: 100%" type="text" id="txtId" />
+ </td>
+ <td>&nbsp;</td>
+ <td>
+ <span fcklang="DlgGenLangCode">Language Code</span><br />
+ <input style="width: 100%" type="text" id="txtLang" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">&nbsp;</td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <span fcklang="DlgDivInlineStyle">Inline Style</span><br />
+ <input style="width: 100%" type="text" id="txtInlineStyle" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">&nbsp;</td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <span fcklang="DlgGenTitle">Advisory Title</span><br />
+ <input style="width: 100%" type="text" id="txtTitle" />
+ </td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgGenLangDir">Language Direction</span><br />
+ <select id="selLangDir">
+ <option fcklang="DlgGenLangDirLtr" value="ltr">Left to Right (LTR)
+ <option fcklang="DlgGenLangDirRtl" value="rtl">Right to Left (RTL)
+ </select>
+ </td>
+ </tr>
+ </table>
+ </div>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_docprops.html b/httemplate/elements/fckeditor/editor/dialog/fck_docprops.html
new file mode 100644
index 0000000..935580d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_docprops.html
@@ -0,0 +1,600 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Link dialog window.
+-->
+<html>
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
+ <meta content="noindex, nofollow" name="robots" >
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKConfig = oEditor.FCKConfig ;
+
+//#### Dialog Tabs
+
+// Set the dialog tabs.
+window.parent.AddTab( 'General' , FCKLang.DlgDocGeneralTab ) ;
+window.parent.AddTab( 'Background' , FCKLang.DlgDocBackTab ) ;
+window.parent.AddTab( 'Colors' , FCKLang.DlgDocColorsTab ) ;
+window.parent.AddTab( 'Meta' , FCKLang.DlgDocMetaTab ) ;
+
+// Function called when a dialog tag is selected.
+function OnDialogTabChange( tabCode )
+{
+ ShowE( 'divGeneral' , ( tabCode == 'General' ) ) ;
+ ShowE( 'divBackground' , ( tabCode == 'Background' ) ) ;
+ ShowE( 'divColors' , ( tabCode == 'Colors' ) ) ;
+ ShowE( 'divMeta' , ( tabCode == 'Meta' ) ) ;
+
+ ShowE( 'ePreview' , ( tabCode == 'Background' || tabCode == 'Colors' ) ) ;
+}
+
+//#### Get Base elements from the document: BEGIN
+
+// The HTML element of the document.
+var oHTML = FCK.EditorDocument.getElementsByTagName('html')[0] ;
+
+// The HEAD element of the document.
+var oHead = oHTML.getElementsByTagName('head')[0] ;
+
+var oBody = FCK.EditorDocument.body ;
+
+// This object contains all META tags defined in the document.
+var oMetaTags = new Object() ;
+
+// Get all META tags defined in the document.
+AppendMetaCollection( oMetaTags, oHead.getElementsByTagName('meta') ) ;
+AppendMetaCollection( oMetaTags, oHead.getElementsByTagName('fck:meta') ) ;
+
+function AppendMetaCollection( targetObject, metaCollection )
+{
+ // Loop throw all METAs and put it in the HashTable.
+ for ( var i = 0 ; i < metaCollection.length ; i++ )
+ {
+ // Try to get the "name" attribute.
+ var sName = GetAttribute( metaCollection[i], 'name', GetAttribute( metaCollection[i], '___fcktoreplace:name', '' ) ) ;
+
+ // If no "name", try with the "http-equiv" attribute.
+ if ( sName.length == 0 )
+ {
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ // Get the http-equiv value from the outerHTML.
+ var oHttpEquivMatch = metaCollection[i].outerHTML.match( oEditor.FCKRegexLib.MetaHttpEquiv ) ;
+ if ( oHttpEquivMatch )
+ sName = oHttpEquivMatch[1] ;
+ }
+ else
+ sName = GetAttribute( metaCollection[i], 'http-equiv', '' ) ;
+ }
+
+ if ( sName.length > 0 )
+ targetObject[ sName.toLowerCase() ] = metaCollection[i] ;
+ }
+}
+
+//#### END
+
+// Set a META tag in the document.
+function SetMetadata( name, content, isHttp )
+{
+ if ( content.length == 0 )
+ {
+ RemoveMetadata( name ) ;
+ return ;
+ }
+
+ var oMeta = oMetaTags[ name.toLowerCase() ] ;
+
+ if ( !oMeta )
+ {
+ oMeta = oHead.appendChild( FCK.EditorDocument.createElement('META') ) ;
+
+ if ( isHttp )
+ SetAttribute( oMeta, 'http-equiv', name ) ;
+ else
+ {
+ // On IE, it is not possible to set the "name" attribute of the META tag.
+ // So a temporary attribute is used and it is replaced when getting the
+ // editor's HTML/XHTML value. This is sad, I know :(
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ SetAttribute( oMeta, '___fcktoreplace:name', name ) ;
+ else
+ SetAttribute( oMeta, 'name', name ) ;
+ }
+
+ oMetaTags[ name.toLowerCase() ] = oMeta ;
+ }
+
+ SetAttribute( oMeta, 'content', content ) ;
+// oMeta.content = content ;
+}
+
+function RemoveMetadata( name )
+{
+ var oMeta = oMetaTags[ name.toLowerCase() ] ;
+
+ if ( oMeta && oMeta != null )
+ {
+ oMeta.parentNode.removeChild( oMeta ) ;
+ oMetaTags[ name.toLowerCase() ] = null ;
+ }
+}
+
+function GetMetadata( name )
+{
+ var oMeta = oMetaTags[ name.toLowerCase() ] ;
+
+ if ( oMeta && oMeta != null )
+ return oMeta.getAttribute( 'content', 2 ) ;
+ else
+ return '' ;
+}
+
+window.onload = function ()
+{
+ // Show/Hide the "Browse Server" button.
+ GetE('tdBrowse').style.display = oEditor.FCKConfig.ImageBrowser ? "" : "none";
+
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage( document ) ;
+
+ FillFields() ;
+
+ UpdatePreview() ;
+
+ // Show the "Ok" button.
+ window.parent.SetOkButton( true ) ;
+
+ window.parent.SetAutoSize( true ) ;
+}
+
+function FillFields()
+{
+ // ### General Info
+ GetE('txtPageTitle').value = FCK.EditorDocument.title ;
+
+ GetE('selDirection').value = GetAttribute( oHTML, 'dir', '' ) ;
+ GetE('txtLang').value = GetAttribute( oHTML, 'xml:lang', GetAttribute( oHTML, 'lang', '' ) ) ; // "xml:lang" takes precedence to "lang".
+
+ // Character Set Encoding.
+// if ( oEditor.FCKBrowserInfo.IsIE )
+// var sCharSet = FCK.EditorDocument.charset ;
+// else
+ var sCharSet = GetMetadata( 'Content-Type' ) ;
+
+ if ( sCharSet != null && sCharSet.length > 0 )
+ {
+// if ( !oEditor.FCKBrowserInfo.IsIE )
+ sCharSet = sCharSet.match( /[^=]*$/ ) ;
+
+ GetE('selCharSet').value = sCharSet ;
+
+ if ( GetE('selCharSet').selectedIndex == -1 )
+ {
+ GetE('selCharSet').value = '...' ;
+ GetE('txtCustomCharSet').value = sCharSet ;
+
+ CheckOther( GetE('selCharSet'), 'txtCustomCharSet' ) ;
+ }
+ }
+
+ // Document Type.
+ if ( FCK.DocTypeDeclaration && FCK.DocTypeDeclaration.length > 0 )
+ {
+ GetE('selDocType').value = FCK.DocTypeDeclaration ;
+
+ if ( GetE('selDocType').selectedIndex == -1 )
+ {
+ GetE('selDocType').value = '...' ;
+ GetE('txtDocType').value = FCK.DocTypeDeclaration ;
+
+ CheckOther( GetE('selDocType'), 'txtDocType' ) ;
+ }
+ }
+
+ // Document Type.
+ GetE('chkIncXHTMLDecl').checked = ( FCK.XmlDeclaration && FCK.XmlDeclaration.length > 0 ) ;
+
+ // ### Background
+ GetE('txtBackColor').value = GetAttribute( oBody, 'bgColor' , '' ) ;
+ GetE('txtBackImage').value = GetAttribute( oBody, 'background' , '' ) ;
+ GetE('chkBackNoScroll').checked = ( GetAttribute( oBody, 'bgProperties', '' ).toLowerCase() == 'fixed' ) ;
+
+ // ### Colors
+ GetE('txtColorText').value = GetAttribute( oBody, 'text' , '' ) ;
+ GetE('txtColorLink').value = GetAttribute( oBody, 'link' , '' ) ;
+ GetE('txtColorVisited').value = GetAttribute( oBody, 'vLink' , '' ) ;
+ GetE('txtColorActive').value = GetAttribute( oBody, 'aLink' , '' ) ;
+
+ // ### Margins
+ GetE('txtMarginTop').value = GetAttribute( oBody, 'topMargin' , '' ) ;
+ GetE('txtMarginLeft').value = GetAttribute( oBody, 'leftMargin' , '' ) ;
+ GetE('txtMarginRight').value = GetAttribute( oBody, 'rightMargin' , '' ) ;
+ GetE('txtMarginBottom').value = GetAttribute( oBody, 'bottomMargin' , '' ) ;
+
+ // ### Meta Data
+ GetE('txtMetaKeywords').value = GetMetadata( 'keywords' ) ;
+ GetE('txtMetaDescription').value = GetMetadata( 'description' ) ;
+ GetE('txtMetaAuthor').value = GetMetadata( 'author' ) ;
+ GetE('txtMetaCopyright').value = GetMetadata( 'copyright' ) ;
+}
+
+// Called when the "Ok" button is clicked.
+function Ok()
+{
+ // ### General Info
+ FCK.EditorDocument.title = GetE('txtPageTitle').value ;
+
+ var oHTML = FCK.EditorDocument.getElementsByTagName('html')[0] ;
+
+ SetAttribute( oHTML, 'dir' , GetE('selDirection').value ) ;
+ SetAttribute( oHTML, 'lang' , GetE('txtLang').value ) ;
+ SetAttribute( oHTML, 'xml:lang' , GetE('txtLang').value ) ;
+
+ // Character Set Enconding.
+ var sCharSet = GetE('selCharSet').value ;
+ if ( sCharSet == '...' )
+ sCharSet = GetE('txtCustomCharSet').value ;
+
+ if ( sCharSet.length > 0 )
+ sCharSet = 'text/html; charset=' + sCharSet ;
+
+// if ( oEditor.FCKBrowserInfo.IsIE )
+// FCK.EditorDocument.charset = sCharSet ;
+// else
+ SetMetadata( 'Content-Type', sCharSet, true ) ;
+
+ // Document Type
+ var sDocType = GetE('selDocType').value ;
+ if ( sDocType == '...' )
+ sDocType = GetE('txtDocType').value ;
+
+ FCK.DocTypeDeclaration = sDocType ;
+
+ // XHTML Declarations.
+ if ( GetE('chkIncXHTMLDecl').checked )
+ {
+ if ( sCharSet.length == 0 )
+ sCharSet = 'utf-8' ;
+
+ FCK.XmlDeclaration = '<' + '?xml version="1.0" encoding="' + sCharSet + '"?>' ;
+
+ SetAttribute( oHTML, 'xmlns', 'http://www.w3.org/1999/xhtml' ) ;
+ }
+ else
+ {
+ FCK.XmlDeclaration = null ;
+ oHTML.removeAttribute( 'xmlns', 0 ) ;
+ }
+
+ // ### Background
+ SetAttribute( oBody, 'bgcolor' , GetE('txtBackColor').value ) ;
+ SetAttribute( oBody, 'background' , GetE('txtBackImage').value ) ;
+ SetAttribute( oBody, 'bgproperties' , GetE('chkBackNoScroll').checked ? 'fixed' : '' ) ;
+
+ // ### Colors
+ SetAttribute( oBody, 'text' , GetE('txtColorText').value ) ;
+ SetAttribute( oBody, 'link' , GetE('txtColorLink').value ) ;
+ SetAttribute( oBody, 'vlink', GetE('txtColorVisited').value ) ;
+ SetAttribute( oBody, 'alink', GetE('txtColorActive').value ) ;
+
+ // ### Margins
+ SetAttribute( oBody, 'topmargin' , GetE('txtMarginTop').value ) ;
+ SetAttribute( oBody, 'leftmargin' , GetE('txtMarginLeft').value ) ;
+ SetAttribute( oBody, 'rightmargin' , GetE('txtMarginRight').value ) ;
+ SetAttribute( oBody, 'bottommargin' , GetE('txtMarginBottom').value ) ;
+
+ // ### Meta data
+ SetMetadata( 'keywords' , GetE('txtMetaKeywords').value ) ;
+ SetMetadata( 'description' , GetE('txtMetaDescription').value ) ;
+ SetMetadata( 'author' , GetE('txtMetaAuthor').value ) ;
+ SetMetadata( 'copyright' , GetE('txtMetaCopyright').value ) ;
+
+ return true ;
+}
+
+var bPreviewIsLoaded = false ;
+var oPreviewWindow ;
+var oPreviewBody ;
+
+// Called by the Preview page when loaded.
+function OnPreviewLoad( previewWindow, previewBody )
+{
+ oPreviewWindow = previewWindow ;
+ oPreviewBody = previewBody ;
+
+ bPreviewIsLoaded = true ;
+ UpdatePreview() ;
+}
+
+function UpdatePreview()
+{
+ if ( !bPreviewIsLoaded )
+ return ;
+
+ // ### Background
+ SetAttribute( oPreviewBody, 'bgcolor' , GetE('txtBackColor').value ) ;
+ SetAttribute( oPreviewBody, 'background' , GetE('txtBackImage').value ) ;
+ SetAttribute( oPreviewBody, 'bgproperties' , GetE('chkBackNoScroll').checked ? 'fixed' : '' ) ;
+
+ // ### Colors
+ SetAttribute( oPreviewBody, 'text', GetE('txtColorText').value ) ;
+
+ oPreviewWindow.SetLinkColor( GetE('txtColorLink').value ) ;
+ oPreviewWindow.SetVisitedColor( GetE('txtColorVisited').value ) ;
+ oPreviewWindow.SetActiveColor( GetE('txtColorActive').value ) ;
+}
+
+function CheckOther( combo, txtField )
+{
+ var bNotOther = ( combo.value != '...' ) ;
+
+ GetE(txtField).style.backgroundColor = ( bNotOther ? '#cccccc' : '' ) ;
+ GetE(txtField).disabled = bNotOther ;
+}
+
+function SetColor( inputId, color )
+{
+ GetE( inputId ).value = color + '' ;
+ UpdatePreview() ;
+}
+
+function SelectBackColor( color ) { SetColor('txtBackColor', color ) ; }
+function SelectColorText( color ) { SetColor('txtColorText', color ) ; }
+function SelectColorLink( color ) { SetColor('txtColorLink', color ) ; }
+function SelectColorVisited( color ) { SetColor('txtColorVisited', color ) ; }
+function SelectColorActive( color ) { SetColor('txtColorActive', color ) ; }
+
+function SelectColor( wich )
+{
+ switch ( wich )
+ {
+ case 'Back' : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, SelectBackColor ) ; return ;
+ case 'ColorText' : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, SelectColorText ) ; return ;
+ case 'ColorLink' : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, SelectColorLink ) ; return ;
+ case 'ColorVisited' : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, SelectColorVisited ) ; return ;
+ case 'ColorActive' : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, SelectColorActive ) ; return ;
+ }
+}
+
+function BrowseServerBack()
+{
+ OpenFileBrowser( FCKConfig.ImageBrowserURL, FCKConfig.ImageBrowserWindowWidth, FCKConfig.ImageBrowserWindowHeight ) ;
+}
+
+function SetUrl( url )
+{
+ GetE('txtBackImage').value = url ;
+ UpdatePreview() ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 100%">
+ <tr>
+ <td valign="top" style="height: 100%">
+ <div id="divGeneral">
+ <span fcklang="DlgDocPageTitle">Page Title</span><br />
+ <input id="txtPageTitle" style="width: 100%" type="text" />
+ <br />
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgDocLangDir">Language Direction</span><br />
+ <select id="selDirection">
+ <option value="" selected="selected"></option>
+ <option value="ltr" fcklang="DlgDocLangDirLTR">Left to Right (LTR)</option>
+ <option value="rtl" fcklang="DlgDocLangDirRTL">Right to Left (RTL)</option>
+ </select>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td>
+ <span fcklang="DlgDocLangCode">Language Code</span><br />
+ <input id="txtLang" type="text" />
+ </td>
+ </tr>
+ </table>
+ <br />
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td style="white-space: nowrap">
+ <span fcklang="DlgDocCharSet">Character Set Encoding</span><br />
+ <select id="selCharSet" onchange="CheckOther( this, 'txtCustomCharSet' );">
+ <option value="" selected="selected"></option>
+ <option value="us-ascii">ASCII</option>
+ <option fcklang="DlgDocCharSetCE" value="iso-8859-2">Central European</option>
+ <option fcklang="DlgDocCharSetCT" value="big5">Chinese Traditional (Big5)</option>
+ <option fcklang="DlgDocCharSetCR" value="iso-8859-5">Cyrillic</option>
+ <option fcklang="DlgDocCharSetGR" value="iso-8859-7">Greek</option>
+ <option fcklang="DlgDocCharSetJP" value="iso-2022-jp">Japanese</option>
+ <option fcklang="DlgDocCharSetKR" value="iso-2022-kr">Korean</option>
+ <option fcklang="DlgDocCharSetTR" value="iso-8859-9">Turkish</option>
+ <option fcklang="DlgDocCharSetUN" value="utf-8">Unicode (UTF-8)</option>
+ <option fcklang="DlgDocCharSetWE" value="iso-8859-1">Western European</option>
+ <option fcklang="DlgOpOther" value="...">&lt;Other&gt;</option>
+ </select>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td width="100%">
+ <span fcklang="DlgDocCharSetOther">Other Character Set Encoding</span><br />
+ <input id="txtCustomCharSet" style="width: 100%; background-color: #cccccc" disabled="disabled"
+ type="text" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgDocDocType">Document Type Heading</span><br />
+ <select id="selDocType" onchange="CheckOther( this, 'txtDocType' );">
+ <option value="" selected="selected"></option>
+ <option value='&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"&gt;'>HTML
+ 4.01 Transitional</option>
+ <option value='&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&gt;'>
+ HTML 4.01 Strict</option>
+ <option value='&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"&gt;'>
+ HTML 4.01 Frameset</option>
+ <option value='&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;'>
+ XHTML 1.0 Transitional</option>
+ <option value='&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;'>
+ XHTML 1.0 Strict</option>
+ <option value='&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"&gt;'>
+ XHTML 1.0 Frameset</option>
+ <option value='&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"&gt;'>
+ XHTML 1.1</option>
+ <option value='&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"&gt;'>HTML 3.2</option>
+ <option value='&lt;!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"&gt;'>HTML 2.0</option>
+ <option value="..." fcklang="DlgOpOther">&lt;Other&gt;</option>
+ </select>
+ </td>
+ <td>
+ </td>
+ <td width="100%">
+ <span fcklang="DlgDocDocTypeOther">Other Document Type Heading</span><br />
+ <input id="txtDocType" style="width: 100%; background-color: #cccccc" disabled="disabled"
+ type="text" />
+ </td>
+ </tr>
+ </table>
+ <br />
+ <input id="chkIncXHTMLDecl" type="checkbox" />
+ <label for="chkIncXHTMLDecl" fcklang="DlgDocIncXHTML">
+ Include XHTML Declarations</label>
+ </div>
+ <div id="divBackground" style="display: none">
+ <span fcklang="DlgDocBgColor">Background Color</span><br />
+ <input id="txtBackColor" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" />&nbsp;<input
+ id="btnSelBackColor" onclick="SelectColor( 'Back' )" type="button" value="Select..."
+ fcklang="DlgCellBtnSelect" /><br />
+ <br />
+ <span fcklang="DlgDocBgImage">Background Image URL</span><br />
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td width="100%">
+ <input id="txtBackImage" style="width: 100%" type="text" onchange="UpdatePreview();"
+ onkeyup="UpdatePreview();" /></td>
+ <td id="tdBrowse" nowrap="nowrap">
+ &nbsp;<input id="btnBrowse" onclick="BrowseServerBack();" type="button" fcklang="DlgBtnBrowseServer"
+ value="Browse Server" /></td>
+ </tr>
+ </table>
+ <input id="chkBackNoScroll" type="checkbox" onclick="UpdatePreview();" />
+ <label for="chkBackNoScroll" fcklang="DlgDocBgNoScroll">
+ Nonscrolling Background</label>
+ </div>
+ <div id="divColors" style="display: none">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgDocCText">Text</span><br />
+ <input id="txtColorText" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input
+ onclick="SelectColor( 'ColorText' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />
+ <br />
+ <span fcklang="DlgDocCLink">Link</span><br />
+ <input id="txtColorLink" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input
+ onclick="SelectColor( 'ColorLink' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />
+ <br />
+ <span fcklang="DlgDocCVisited">Visited Link</span><br />
+ <input id="txtColorVisited" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input
+ onclick="SelectColor( 'ColorVisited' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />
+ <br />
+ <span fcklang="DlgDocCActive">Active Link</span><br />
+ <input id="txtColorActive" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input
+ onclick="SelectColor( 'ColorActive' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />
+ </td>
+ <td valign="middle" align="center">
+ <table cellspacing="2" cellpadding="0" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgDocMargins">Page Margins</span></td>
+ </tr>
+ <tr>
+ <td style="border: #000000 1px solid; padding: 5px">
+ <table cellpadding="0" cellspacing="0" border="0" dir="ltr">
+ <tr>
+ <td align="center" colspan="3">
+ <span fcklang="DlgDocMaTop">Top</span><br />
+ <input id="txtMarginTop" type="text" size="3" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left">
+ <span fcklang="DlgDocMaLeft">Left</span><br />
+ <input id="txtMarginLeft" type="text" size="3" />
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td align="right">
+ <span fcklang="DlgDocMaRight">Right</span><br />
+ <input id="txtMarginRight" type="text" size="3" />
+ </td>
+ </tr>
+ <tr>
+ <td align="center" colspan="3">
+ <span fcklang="DlgDocMaBottom">Bottom</span><br />
+ <input id="txtMarginBottom" type="text" size="3" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divMeta" style="display: none">
+ <span fcklang="DlgDocMeIndex">Document Indexing Keywords (comma separated)</span><br />
+ <textarea id="txtMetaKeywords" style="width: 100%" rows="2" cols="20"></textarea>
+ <br />
+ <span fcklang="DlgDocMeDescr">Document Description</span><br />
+ <textarea id="txtMetaDescription" style="width: 100%" rows="4" cols="20"></textarea>
+ <br />
+ <span fcklang="DlgDocMeAuthor">Author</span><br />
+ <input id="txtMetaAuthor" style="width: 100%" type="text" /><br />
+ <br />
+ <span fcklang="DlgDocMeCopy">Copyright</span><br />
+ <input id="txtMetaCopyright" type="text" style="width: 100%" />
+ </div>
+ </td>
+ </tr>
+ <tr id="ePreview" style="display: none">
+ <td>
+ <span fcklang="DlgDocPreview">Preview</span><br />
+ <iframe id="frmPreview" src="fck_docprops/fck_document_preview.html" width="100%"
+ height="100"></iframe>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html b/httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html
new file mode 100644
index 0000000..1df5732
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Preview shown in the "Document Properties" dialog window.
+-->
+<html>
+ <head>
+ <title>Document Properties - Preview</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="robots" content="noindex, nofollow">
+ <script type="text/javascript">
+
+var eBase = parent.FCK.EditorDocument.getElementsByTagName( 'BASE' ) ;
+if ( eBase.length > 0 && eBase[0].href.length > 0 )
+{
+ document.write( '<base href="' + eBase[0].href + '">' ) ;
+}
+
+window.onload = function()
+{
+ if ( typeof( parent.OnPreviewLoad ) == 'function' )
+ parent.OnPreviewLoad( window, document.body ) ;
+}
+
+function SetBaseHRef( baseHref )
+{
+ var eBase = document.createElement( 'BASE' ) ;
+ eBase.href = baseHref ;
+
+ var eHead = document.getElementsByTagName( 'HEAD' )[0] ;
+ eHead.appendChild( eBase ) ;
+}
+
+function SetLinkColor( color )
+{
+ if ( color && color.length > 0 )
+ document.getElementById('eLink').style.color = color ;
+ else
+ document.getElementById('eLink').style.color = window.document.linkColor ;
+}
+
+function SetVisitedColor( color )
+{
+ if ( color && color.length > 0 )
+ document.getElementById('eVisited').style.color = color ;
+ else
+ document.getElementById('eVisited').style.color = window.document.vlinkColor ;
+}
+
+function SetActiveColor( color )
+{
+ if ( color && color.length > 0 )
+ document.getElementById('eActive').style.color = color ;
+ else
+ document.getElementById('eActive').style.color = window.document.alinkColor ;
+}
+ </script>
+ </head>
+ <body>
+ <table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td align="center" valign="middle">
+ Normal Text
+ </td>
+ <td id="eLink" align="center" valign="middle">
+ <u>Link Text</u>
+ </td>
+ </tr>
+ <tr>
+ <td id="eVisited" valign="middle" align="center">
+ <u>Visited Link</u>
+ </td>
+ <td id="eActive" valign="middle" align="center">
+ <u>Active Link</u>
+ </td>
+ </tr>
+ </table>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ <br>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_find.html b/httemplate/elements/fckeditor/editor/dialog/fck_find.html
new file mode 100644
index 0000000..eba7f90
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_find.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * "Find" dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+
+function OnLoad()
+{
+ // Whole word is available on IE only.
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ document.getElementById('divWord').style.display = '' ;
+
+ // First of all, translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage( document ) ;
+
+ window.parent.SetAutoSize( true ) ;
+}
+
+function btnStat(frm)
+{
+ document.getElementById('btnFind').disabled =
+ ( document.getElementById('txtFind').value.length == 0 ) ;
+}
+
+function ReplaceTextNodes( parentNode, regex, replaceValue, replaceAll )
+{
+ for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
+ {
+ var oNode = parentNode.childNodes[i] ;
+ if ( oNode.nodeType == 3 )
+ {
+ var sReplaced = oNode.nodeValue.replace( regex, replaceValue ) ;
+ if ( oNode.nodeValue != sReplaced )
+ {
+ oNode.nodeValue = sReplaced ;
+ if ( ! replaceAll )
+ return true ;
+ }
+ }
+ else
+ {
+ if ( ReplaceTextNodes( oNode, regex, replaceValue ) )
+ return true ;
+ }
+ }
+ return false ;
+}
+
+function GetRegexExpr()
+{
+ var sExpr ;
+
+ if ( document.getElementById('chkWord').checked )
+ sExpr = '\\b' + document.getElementById('txtFind').value + '\\b' ;
+ else
+ sExpr = document.getElementById('txtFind').value ;
+
+ return sExpr ;
+}
+
+function GetCase()
+{
+ return ( document.getElementById('chkCase').checked ? '' : 'i' ) ;
+}
+
+function Ok()
+{
+ if ( document.getElementById('txtFind').value.length == 0 )
+ return ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ FindIE() ;
+ else
+ FindGecko() ;
+}
+
+var oRange ;
+
+if ( oEditor.FCKBrowserInfo.IsIE )
+ oRange = oEditor.FCK.EditorDocument.body.createTextRange() ;
+
+function FindIE()
+{
+ var iFlags = 0 ;
+
+ if ( chkCase.checked )
+ iFlags = iFlags | 4 ;
+
+ if ( chkWord.checked )
+ iFlags = iFlags | 2 ;
+
+ var bFound = oRange.findText( document.getElementById('txtFind').value, 1, iFlags ) ;
+
+ if ( bFound )
+ {
+ oRange.scrollIntoView() ;
+ oRange.select() ;
+ oRange.collapse(false) ;
+ oLastRangeFound = oRange ;
+ }
+ else
+ {
+ oRange = oEditor.FCK.EditorDocument.body.createTextRange() ;
+ alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;
+ }
+}
+
+function FindGecko()
+{
+ var bCase = document.getElementById('chkCase').checked ;
+ var bWord = document.getElementById('chkWord').checked ;
+
+ // window.find( searchString, caseSensitive, backwards, wrapAround, wholeWord, searchInFrames, showDialog ) ;
+ if ( !oEditor.FCK.EditorWindow.find( document.getElementById('txtFind').value, bCase, false, false, bWord, false, false ) )
+ alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;
+}
+ </script>
+</head>
+<body onload="OnLoad()" style="overflow: hidden">
+ <table cellspacing="3" cellpadding="2" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <label for="txtFind" fcklang="DlgReplaceFindLbl">
+ Find what:</label>&nbsp;
+ </td>
+ <td width="100%">
+ <input id="txtFind" style="width: 100%" tabindex="1" type="text" />
+ </td>
+ <td>
+ <input id="btnFind" style="padding-right: 5px; padding-left: 5px" onclick="Ok();"
+ type="button" value="Find" fcklang="DlgFindFindBtn" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="bottom" colspan="3">
+ &nbsp;<input id="chkCase" tabindex="3" type="checkbox" /><label for="chkCase" fcklang="DlgReplaceCaseChk">Match
+ case</label>
+ <br />
+ <div id="divWord" style="display: none">
+ &nbsp;<input id="chkWord" tabindex="4" type="checkbox" /><label for="chkWord" fcklang="DlgReplaceWordChk">Match
+ whole word</label>
+ </div>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_flash.html b/httemplate/elements/fckeditor/editor/dialog/fck_flash.html
new file mode 100644
index 0000000..3428826
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_flash.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Flash Properties dialog window.
+-->
+<html>
+ <head>
+ <title>Flash Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script src="fck_flash/fck_flash.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+document.write( FCKTools.GetStyleHtml( GetCommonDialogCss() ) ) ;
+
+ </script>
+ </head>
+ <body scroll="no" style="OVERFLOW: hidden">
+ <div id="divInfo">
+ <table cellSpacing="1" cellPadding="1" width="100%" border="0">
+ <tr>
+ <td>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td width="100%"><span fckLang="DlgImgURL">URL</span>
+ </td>
+ <td id="tdBrowse" style="DISPLAY: none" noWrap rowSpan="2">&nbsp; <input id="btnBrowse" onclick="BrowseServer();" type="button" value="Browse Server" fckLang="DlgBtnBrowseServer">
+ </td>
+ </tr>
+ <tr>
+ <td vAlign="top"><input id="txtUrl" onblur="UpdatePreview();" style="WIDTH: 100%" type="text">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <TR>
+ <TD>
+ <table cellSpacing="0" cellPadding="0" border="0">
+ <TR>
+ <TD nowrap>
+ <span fckLang="DlgImgWidth">Width</span><br>
+ <input id="txtWidth" onkeypress="return IsDigit(event);" type="text" size="3">
+ </TD>
+ <TD>&nbsp;</TD>
+ <TD>
+ <span fckLang="DlgImgHeight">Height</span><br>
+ <input id="txtHeight" onkeypress="return IsDigit(event);" type="text" size="3">
+ </TD>
+ </TR>
+ </table>
+ </TD>
+ </TR>
+ <tr>
+ <td vAlign="top">
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td valign="top" width="100%">
+ <table cellSpacing="0" cellPadding="0" width="100%">
+ <tr>
+ <td><span fckLang="DlgImgPreview">Preview</span></td>
+ </tr>
+ <tr>
+ <td id="ePreviewCell" valign="top" class="FlashPreviewArea"><iframe src="fck_flash/fck_flash_preview.html" frameborder="0" marginheight="0" marginwidth="0"></iframe></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divUpload" style="DISPLAY: none">
+ <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data" action="" onsubmit="return CheckUpload();">
+ <span fckLang="DlgLnkUpload">Upload</span><br />
+ <input id="txtUploadFile" style="WIDTH: 100%" type="file" size="40" name="NewFile" /><br />
+ <br />
+ <input id="btnUpload" type="submit" value="Send it to the Server" fckLang="DlgLnkBtnUpload" />
+ <script type="text/javascript">
+ document.write( '<iframe name="UploadWindow" style="DISPLAY: none" src="' + FCKTools.GetVoidUrl() + '"><\/iframe>' ) ;
+ </script>
+ </form>
+ </div>
+ <div id="divAdvanced" style="DISPLAY: none">
+ <TABLE cellSpacing="0" cellPadding="0" border="0">
+ <TR>
+ <TD nowrap>
+ <span fckLang="DlgFlashScale">Scale</span><BR>
+ <select id="cmbScale">
+ <option value="" selected></option>
+ <option value="showall" fckLang="DlgFlashScaleAll">Show all</option>
+ <option value="noborder" fckLang="DlgFlashScaleNoBorder">No Border</option>
+ <option value="exactfit" fckLang="DlgFlashScaleFit">Exact Fit</option>
+ </select></TD>
+ <TD>&nbsp;&nbsp;&nbsp; &nbsp;
+ </TD>
+ <td valign="bottom">
+ <table>
+ <tr>
+ <td><input id="chkAutoPlay" type="checkbox" checked></td>
+ <td><label for="chkAutoPlay" nowrap fckLang="DlgFlashChkPlay">Auto Play</label>&nbsp;&nbsp;</td>
+ <td><input id="chkLoop" type="checkbox" checked></td>
+ <td><label for="chkLoop" nowrap fckLang="DlgFlashChkLoop">Loop</label>&nbsp;&nbsp;</td>
+ <td><input id="chkMenu" type="checkbox" checked></td>
+ <td><label for="chkMenu" nowrap fckLang="DlgFlashChkMenu">Enable Flash Menu</label></td>
+ </tr>
+ </table>
+ </td>
+ </TR>
+ </TABLE>
+ <br>
+ &nbsp;
+ <table cellSpacing="0" cellPadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td vAlign="top" width="50%"><span fckLang="DlgGenId">Id</span><br>
+ <input id="txtAttId" style="WIDTH: 100%" type="text">
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td vAlign="top" nowrap><span fckLang="DlgGenClass">Stylesheet Classes</span><br>
+ <input id="txtAttClasses" style="WIDTH: 100%" type="text">
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td vAlign="top" nowrap width="50%">&nbsp;<span fckLang="DlgGenTitle">Advisory Title</span><br>
+ <input id="txtAttTitle" style="WIDTH: 100%" type="text">
+ </td>
+ </tr>
+ </table>
+ <span fckLang="DlgGenStyle">Style</span><br>
+ <input id="txtAttStyle" style="WIDTH: 100%" type="text">
+ </div>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js b/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js
new file mode 100644
index 0000000..cb28d43
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js
@@ -0,0 +1,300 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Scripts related to the Flash dialog window (see fck_flash.html).
+ */
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKConfig = oEditor.FCKConfig ;
+var FCKTools = oEditor.FCKTools ;
+
+//#### Dialog Tabs
+
+// Set the dialog tabs.
+dialog.AddTab( 'Info', oEditor.FCKLang.DlgInfoTab ) ;
+
+if ( FCKConfig.FlashUpload )
+ dialog.AddTab( 'Upload', FCKLang.DlgLnkUpload ) ;
+
+if ( !FCKConfig.FlashDlgHideAdvanced )
+ dialog.AddTab( 'Advanced', oEditor.FCKLang.DlgAdvancedTag ) ;
+
+// Function called when a dialog tag is selected.
+function OnDialogTabChange( tabCode )
+{
+ ShowE('divInfo' , ( tabCode == 'Info' ) ) ;
+ ShowE('divUpload' , ( tabCode == 'Upload' ) ) ;
+ ShowE('divAdvanced' , ( tabCode == 'Advanced' ) ) ;
+}
+
+// Get the selected flash embed (if available).
+var oFakeImage = dialog.Selection.GetSelectedElement() ;
+var oEmbed ;
+
+if ( oFakeImage )
+{
+ if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckflash') )
+ oEmbed = FCK.GetRealElement( oFakeImage ) ;
+ else
+ oFakeImage = null ;
+}
+
+window.onload = function()
+{
+ // Translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ // Load the selected element information (if any).
+ LoadSelection() ;
+
+ // Show/Hide the "Browse Server" button.
+ GetE('tdBrowse').style.display = FCKConfig.FlashBrowser ? '' : 'none' ;
+
+ // Set the actual uploader URL.
+ if ( FCKConfig.FlashUpload )
+ GetE('frmUpload').action = FCKConfig.FlashUploadURL ;
+
+ dialog.SetAutoSize( true ) ;
+
+ // Activate the "OK" button.
+ dialog.SetOkButton( true ) ;
+
+ SelectField( 'txtUrl' ) ;
+}
+
+function LoadSelection()
+{
+ if ( ! oEmbed ) return ;
+
+ GetE('txtUrl').value = GetAttribute( oEmbed, 'src', '' ) ;
+ GetE('txtWidth').value = GetAttribute( oEmbed, 'width', '' ) ;
+ GetE('txtHeight').value = GetAttribute( oEmbed, 'height', '' ) ;
+
+ // Get Advances Attributes
+ GetE('txtAttId').value = oEmbed.id ;
+ GetE('chkAutoPlay').checked = GetAttribute( oEmbed, 'play', 'true' ) == 'true' ;
+ GetE('chkLoop').checked = GetAttribute( oEmbed, 'loop', 'true' ) == 'true' ;
+ GetE('chkMenu').checked = GetAttribute( oEmbed, 'menu', 'true' ) == 'true' ;
+ GetE('cmbScale').value = GetAttribute( oEmbed, 'scale', '' ).toLowerCase() ;
+
+ GetE('txtAttTitle').value = oEmbed.title ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ GetE('txtAttClasses').value = oEmbed.getAttribute('className') || '' ;
+ GetE('txtAttStyle').value = oEmbed.style.cssText ;
+ }
+ else
+ {
+ GetE('txtAttClasses').value = oEmbed.getAttribute('class',2) || '' ;
+ GetE('txtAttStyle').value = oEmbed.getAttribute('style',2) || '' ;
+ }
+
+ UpdatePreview() ;
+}
+
+//#### The OK button was hit.
+function Ok()
+{
+ if ( GetE('txtUrl').value.length == 0 )
+ {
+ dialog.SetSelectedTab( 'Info' ) ;
+ GetE('txtUrl').focus() ;
+
+ alert( oEditor.FCKLang.DlgAlertUrl ) ;
+
+ return false ;
+ }
+
+ oEditor.FCKUndo.SaveUndoStep() ;
+ if ( !oEmbed )
+ {
+ oEmbed = FCK.EditorDocument.createElement( 'EMBED' ) ;
+ oFakeImage = null ;
+ }
+ UpdateEmbed( oEmbed ) ;
+
+ if ( !oFakeImage )
+ {
+ oFakeImage = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__Flash', oEmbed ) ;
+ oFakeImage.setAttribute( '_fckflash', 'true', 0 ) ;
+ oFakeImage = FCK.InsertElement( oFakeImage ) ;
+ }
+
+ oEditor.FCKEmbedAndObjectProcessor.RefreshView( oFakeImage, oEmbed ) ;
+
+ return true ;
+}
+
+function UpdateEmbed( e )
+{
+ SetAttribute( e, 'type' , 'application/x-shockwave-flash' ) ;
+ SetAttribute( e, 'pluginspage' , 'http://www.macromedia.com/go/getflashplayer' ) ;
+
+ SetAttribute( e, 'src', GetE('txtUrl').value ) ;
+ SetAttribute( e, "width" , GetE('txtWidth').value ) ;
+ SetAttribute( e, "height", GetE('txtHeight').value ) ;
+
+ // Advances Attributes
+
+ SetAttribute( e, 'id' , GetE('txtAttId').value ) ;
+ SetAttribute( e, 'scale', GetE('cmbScale').value ) ;
+
+ SetAttribute( e, 'play', GetE('chkAutoPlay').checked ? 'true' : 'false' ) ;
+ SetAttribute( e, 'loop', GetE('chkLoop').checked ? 'true' : 'false' ) ;
+ SetAttribute( e, 'menu', GetE('chkMenu').checked ? 'true' : 'false' ) ;
+
+ SetAttribute( e, 'title' , GetE('txtAttTitle').value ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ SetAttribute( e, 'className', GetE('txtAttClasses').value ) ;
+ e.style.cssText = GetE('txtAttStyle').value ;
+ }
+ else
+ {
+ SetAttribute( e, 'class', GetE('txtAttClasses').value ) ;
+ SetAttribute( e, 'style', GetE('txtAttStyle').value ) ;
+ }
+}
+
+var ePreview ;
+
+function SetPreviewElement( previewEl )
+{
+ ePreview = previewEl ;
+
+ if ( GetE('txtUrl').value.length > 0 )
+ UpdatePreview() ;
+}
+
+function UpdatePreview()
+{
+ if ( !ePreview )
+ return ;
+
+ while ( ePreview.firstChild )
+ ePreview.removeChild( ePreview.firstChild ) ;
+
+ if ( GetE('txtUrl').value.length == 0 )
+ ePreview.innerHTML = '&nbsp;' ;
+ else
+ {
+ var oDoc = ePreview.ownerDocument || ePreview.document ;
+ var e = oDoc.createElement( 'EMBED' ) ;
+
+ SetAttribute( e, 'src', GetE('txtUrl').value ) ;
+ SetAttribute( e, 'type', 'application/x-shockwave-flash' ) ;
+ SetAttribute( e, 'width', '100%' ) ;
+ SetAttribute( e, 'height', '100%' ) ;
+
+ ePreview.appendChild( e ) ;
+ }
+}
+
+// <embed id="ePreview" src="fck_flash/claims.swf" width="100%" height="100%" style="visibility:hidden" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer">
+
+function BrowseServer()
+{
+ OpenFileBrowser( FCKConfig.FlashBrowserURL, FCKConfig.FlashBrowserWindowWidth, FCKConfig.FlashBrowserWindowHeight ) ;
+}
+
+function SetUrl( url, width, height )
+{
+ GetE('txtUrl').value = url ;
+
+ if ( width )
+ GetE('txtWidth').value = width ;
+
+ if ( height )
+ GetE('txtHeight').value = height ;
+
+ UpdatePreview() ;
+
+ dialog.SetSelectedTab( 'Info' ) ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ // Remove animation
+ window.parent.Throbber.Hide() ;
+ GetE( 'divUpload' ).style.display = '' ;
+
+ switch ( errorNumber )
+ {
+ case 0 : // No errors
+ alert( 'Your file has been successfully uploaded' ) ;
+ break ;
+ case 1 : // Custom error
+ alert( customMsg ) ;
+ return ;
+ case 101 : // Custom warning
+ alert( customMsg ) ;
+ break ;
+ case 201 :
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file type' ) ;
+ return ;
+ case 203 :
+ alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
+ return ;
+ case 500 :
+ alert( 'The connector is disabled' ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ return ;
+ }
+
+ SetUrl( fileUrl ) ;
+ GetE('frmUpload').reset() ;
+}
+
+var oUploadAllowedExtRegex = new RegExp( FCKConfig.FlashUploadAllowedExtensions, 'i' ) ;
+var oUploadDeniedExtRegex = new RegExp( FCKConfig.FlashUploadDeniedExtensions, 'i' ) ;
+
+function CheckUpload()
+{
+ var sFile = GetE('txtUploadFile').value ;
+
+ if ( sFile.length == 0 )
+ {
+ alert( 'Please select a file to upload' ) ;
+ return false ;
+ }
+
+ if ( ( FCKConfig.FlashUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||
+ ( FCKConfig.FlashUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )
+ {
+ OnUploadCompleted( 202 ) ;
+ return false ;
+ }
+
+ // Show animation
+ window.parent.Throbber.Show( 100 ) ;
+ GetE( 'divUpload' ).style.display = 'none' ;
+
+ return true ;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html b/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html
new file mode 100644
index 0000000..68a4c79
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Preview page for the Flash dialog window.
+-->
+<html>
+ <head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="robots" content="noindex, nofollow">
+ <script src="../common/fck_dialog_common.js" type="text/javascript"></script>
+ <script language="javascript">
+
+var FCKTools = window.parent.FCKTools ;
+var FCKConfig = window.parent.FCKConfig ;
+
+// Sets the Skin CSS
+document.write( FCKTools.GetStyleHtml( FCKConfig.SkinDialogCSS ) ) ;
+document.write( FCKTools.GetStyleHtml( GetCommonDialogCss( '../' ) ) ) ;
+
+if ( window.parent.FCKConfig.BaseHref.length > 0 )
+ document.write( '<base href="' + window.parent.FCKConfig.BaseHref + '">' ) ;
+
+window.onload = function()
+{
+ window.parent.SetPreviewElement( document.body ) ;
+}
+
+ </script>
+ </head>
+ <body style="COLOR: #000000; BACKGROUND-COLOR: #ffffff"></body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_form.html b/httemplate/elements/fckeditor/editor/dialog/fck_form.html
new file mode 100644
index 0000000..0284527
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_form.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Form dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelection().MoveToAncestorNode( 'FORM' ) ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtAction').value = oActiveEl.getAttribute( 'action', 2 ) ;
+ GetE('txtMethod').value = oActiveEl.method ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ if ( !oActiveEl )
+ {
+ oActiveEl = oEditor.FCK.InsertElement( 'form' ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsGeckoLike )
+ oEditor.FCKTools.AppendBogusBr( oActiveEl ) ;
+ }
+
+ oActiveEl.name = GetE('txtName').value ;
+ SetAttribute( oActiveEl, 'action', GetE('txtAction').value ) ;
+ oActiveEl.method = GetE('txtMethod').value ;
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table width="100%" style="height: 100%">
+ <tr>
+ <td align="center">
+ <table cellspacing="0" cellpadding="0" width="80%" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgFormName">Name</span><br />
+ <input style="width: 100%" type="text" id="txtName" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgFormAction">Action</span><br />
+ <input style="width: 100%" type="text" id="txtAction" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgFormMethod">Method</span><br />
+ <select id="txtMethod">
+ <option value="get" selected="selected">GET</option>
+ <option value="post">POST</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html b/httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html
new file mode 100644
index 0000000..f9bfb74
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Hidden Field dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Hidden Field Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+var FCK = oEditor.FCK ;
+
+// Gets the document DOM
+var oDOM = FCK.EditorDocument ;
+
+// Get the selected flash embed (if available).
+var oFakeImage = dialog.Selection.GetSelectedElement() ;
+var oActiveEl ;
+
+if ( oFakeImage )
+{
+ if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckinputhidden') )
+ oActiveEl = FCK.GetRealElement( oFakeImage ) ;
+ else
+ oFakeImage = null ;
+}
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtValue').value = oActiveEl.value ;
+ }
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'INPUT', {name: GetE('txtName').value, type: 'hidden' } ) ;
+
+ SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;
+
+ if ( !oFakeImage )
+ {
+ oFakeImage = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__InputHidden', oActiveEl ) ;
+ oFakeImage.setAttribute( '_fckinputhidden', 'true', 0 ) ;
+
+ oActiveEl.parentNode.insertBefore( oFakeImage, oActiveEl ) ;
+ oActiveEl.parentNode.removeChild( oActiveEl ) ;
+ }
+ else
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden" scroll="no">
+ <table height="100%" width="100%">
+ <tr>
+ <td align="center">
+ <table border="0" class="inhoud" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td>
+ <span fcklang="DlgHiddenName">Name</span><br />
+ <input type="text" size="20" id="txtName" style="width: 100%" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgHiddenValue">Value</span><br />
+ <input type="text" size="30" id="txtValue" style="width: 100%" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_image.html b/httemplate/elements/fckeditor/editor/dialog/fck_image.html
new file mode 100644
index 0000000..b082b2b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_image.html
@@ -0,0 +1,258 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Image Properties dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Image Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script src="fck_image/fck_image.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+document.write( FCKTools.GetStyleHtml( GetCommonDialogCss() ) ) ;
+
+ </script>
+</head>
+<body scroll="no" style="overflow: hidden">
+ <div id="divInfo">
+ <table cellspacing="1" cellpadding="1" border="0" width="100%" height="100%">
+ <tr>
+ <td>
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td width="100%">
+ <span fcklang="DlgImgURL">URL</span>
+ </td>
+ <td id="tdBrowse" style="display: none" nowrap="nowrap" rowspan="2">
+ &nbsp;
+ <input id="btnBrowse" onclick="BrowseServer();" type="button" value="Browse Server"
+ fcklang="DlgBtnBrowseServer" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <input id="txtUrl" style="width: 100%" type="text" onblur="UpdatePreview();" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgImgAlt">Short Description</span><br />
+ <input id="txtAlt" style="width: 100%" type="text" /><br />
+ </td>
+ </tr>
+ <tr height="100%">
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0" height="100%">
+ <tr>
+ <td valign="top">
+ <br />
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgWidth">Width</span>&nbsp;</td>
+ <td>
+ <input type="text" size="3" id="txtWidth" onkeyup="OnSizeChanged('Width',this.value);" /></td>
+ <td rowspan="2">
+ <div id="btnLockSizes" class="BtnLocked" onmouseover="this.className = (bLockRatio ? 'BtnLocked' : 'BtnUnlocked' ) + ' BtnOver';"
+ onmouseout="this.className = (bLockRatio ? 'BtnLocked' : 'BtnUnlocked' );" title="Lock Sizes"
+ onclick="SwitchLock(this);">
+ </div>
+ </td>
+ <td rowspan="2">
+ <div id="btnResetSize" class="BtnReset" onmouseover="this.className='BtnReset BtnOver';"
+ onmouseout="this.className='BtnReset';" title="Reset Size" onclick="ResetSizes();">
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgHeight">Height</span>&nbsp;</td>
+ <td>
+ <input type="text" size="3" id="txtHeight" onkeyup="OnSizeChanged('Height',this.value);" /></td>
+ </tr>
+ </table>
+ <br />
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgBorder">Border</span>&nbsp;</td>
+ <td>
+ <input type="text" size="2" value="" id="txtBorder" onkeyup="UpdatePreview();" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgHSpace">HSpace</span>&nbsp;</td>
+ <td>
+ <input type="text" size="2" id="txtHSpace" onkeyup="UpdatePreview();" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgVSpace">VSpace</span>&nbsp;</td>
+ <td>
+ <input type="text" size="2" id="txtVSpace" onkeyup="UpdatePreview();" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgImgAlign">Align</span>&nbsp;</td>
+ <td>
+ <select id="cmbAlign" onchange="UpdatePreview();">
+ <option value="" selected="selected"></option>
+ <option fcklang="DlgImgAlignLeft" value="left">Left</option>
+ <option fcklang="DlgImgAlignAbsBottom" value="absBottom">Abs Bottom</option>
+ <option fcklang="DlgImgAlignAbsMiddle" value="absMiddle">Abs Middle</option>
+ <option fcklang="DlgImgAlignBaseline" value="baseline">Baseline</option>
+ <option fcklang="DlgImgAlignBottom" value="bottom">Bottom</option>
+ <option fcklang="DlgImgAlignMiddle" value="middle">Middle</option>
+ <option fcklang="DlgImgAlignRight" value="right">Right</option>
+ <option fcklang="DlgImgAlignTextTop" value="textTop">Text Top</option>
+ <option fcklang="DlgImgAlignTop" value="top">Top</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td width="100%" valign="top">
+ <table cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed">
+ <tr>
+ <td>
+ <span fcklang="DlgImgPreview">Preview</span></td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <iframe class="ImagePreviewArea" src="fck_image/fck_image_preview.html" frameborder="0"
+ marginheight="0" marginwidth="0"></iframe>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divUpload" style="display: none">
+ <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data"
+ action="" onsubmit="return CheckUpload();">
+ <span fcklang="DlgLnkUpload">Upload</span><br />
+ <input id="txtUploadFile" style="width: 100%" type="file" size="40" name="NewFile" /><br />
+ <br />
+ <input id="btnUpload" type="submit" value="Send it to the Server" fcklang="DlgLnkBtnUpload" />
+ <script type="text/javascript">
+ document.write( '<iframe name="UploadWindow" style="display: none" src="' + FCKTools.GetVoidUrl() + '"><\/iframe>' ) ;
+ </script>
+ </form>
+ </div>
+ <div id="divLink" style="display: none">
+ <table cellspacing="1" cellpadding="1" border="0" width="100%">
+ <tr>
+ <td>
+ <div>
+ <span fcklang="DlgLnkURL">URL</span><br />
+ <input id="txtLnkUrl" style="width: 100%" type="text" onblur="UpdatePreview();" />
+ </div>
+ <div id="divLnkBrowseServer" align="right">
+ <input type="button" value="Browse Server" fcklang="DlgBtnBrowseServer" onclick="LnkBrowseServer();" />
+ </div>
+ <div>
+ <span fcklang="DlgLnkTarget">Target</span><br />
+ <select id="cmbLnkTarget">
+ <option value="" fcklang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>
+ <option value="_blank" fcklang="DlgLnkTargetBlank">New Window (_blank)</option>
+ <option value="_top" fcklang="DlgLnkTargetTop">Topmost Window (_top)</option>
+ <option value="_self" fcklang="DlgLnkTargetSelf">Same Window (_self)</option>
+ <option value="_parent" fcklang="DlgLnkTargetParent">Parent Window (_parent)</option>
+ </select>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divAdvanced" style="display: none">
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td valign="top" width="50%">
+ <span fcklang="DlgGenId">Id</span><br />
+ <input id="txtAttId" style="width: 100%" type="text" />
+ </td>
+ <td width="1">
+ &nbsp;&nbsp;</td>
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td width="60%">
+ <span fcklang="DlgGenLangDir">Language Direction</span><br />
+ <select id="cmbAttLangDir" style="width: 100%">
+ <option value="" fcklang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>
+ <option value="ltr" fcklang="DlgGenLangDirLtr">Left to Right (LTR)</option>
+ <option value="rtl" fcklang="DlgGenLangDirRtl">Right to Left (RTL)</option>
+ </select>
+ </td>
+ <td width="1%">
+ &nbsp;&nbsp;</td>
+ <td nowrap="nowrap">
+ <span fcklang="DlgGenLangCode">Language Code</span><br />
+ <input id="txtAttLangCode" style="width: 100%" type="text" />&nbsp;
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <span fcklang="DlgGenLongDescr">Long Description URL</span><br />
+ <input id="txtLongDesc" style="width: 100%" type="text" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <span fcklang="DlgGenClass">Stylesheet Classes</span><br />
+ <input id="txtAttClasses" style="width: 100%" type="text" />
+ </td>
+ <td>
+ </td>
+ <td valign="top">
+ &nbsp;<span fcklang="DlgGenTitle">Advisory Title</span><br />
+ <input id="txtAttTitle" style="width: 100%" type="text" />
+ </td>
+ </tr>
+ </table>
+ <span fcklang="DlgGenStyle">Style</span><br />
+ <input id="txtAttStyle" style="width: 100%" type="text" />
+ </div>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js b/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js
new file mode 100644
index 0000000..c341faa
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js
@@ -0,0 +1,512 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Scripts related to the Image dialog window (see fck_image.html).
+ */
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKConfig = oEditor.FCKConfig ;
+var FCKDebug = oEditor.FCKDebug ;
+var FCKTools = oEditor.FCKTools ;
+
+var bImageButton = ( document.location.search.length > 0 && document.location.search.substr(1) == 'ImageButton' ) ;
+
+//#### Dialog Tabs
+
+// Set the dialog tabs.
+dialog.AddTab( 'Info', FCKLang.DlgImgInfoTab ) ;
+
+if ( !bImageButton && !FCKConfig.ImageDlgHideLink )
+ dialog.AddTab( 'Link', FCKLang.DlgImgLinkTab ) ;
+
+if ( FCKConfig.ImageUpload )
+ dialog.AddTab( 'Upload', FCKLang.DlgLnkUpload ) ;
+
+if ( !FCKConfig.ImageDlgHideAdvanced )
+ dialog.AddTab( 'Advanced', FCKLang.DlgAdvancedTag ) ;
+
+// Function called when a dialog tag is selected.
+function OnDialogTabChange( tabCode )
+{
+ ShowE('divInfo' , ( tabCode == 'Info' ) ) ;
+ ShowE('divLink' , ( tabCode == 'Link' ) ) ;
+ ShowE('divUpload' , ( tabCode == 'Upload' ) ) ;
+ ShowE('divAdvanced' , ( tabCode == 'Advanced' ) ) ;
+}
+
+// Get the selected image (if available).
+var oImage = dialog.Selection.GetSelectedElement() ;
+
+if ( oImage && oImage.tagName != 'IMG' && !( oImage.tagName == 'INPUT' && oImage.type == 'image' ) )
+ oImage = null ;
+
+// Get the active link.
+var oLink = dialog.Selection.GetSelection().MoveToAncestorNode( 'A' ) ;
+
+var oImageOriginal ;
+
+function UpdateOriginal( resetSize )
+{
+ if ( !eImgPreview )
+ return ;
+
+ if ( GetE('txtUrl').value.length == 0 )
+ {
+ oImageOriginal = null ;
+ return ;
+ }
+
+ oImageOriginal = document.createElement( 'IMG' ) ; // new Image() ;
+
+ if ( resetSize )
+ {
+ oImageOriginal.onload = function()
+ {
+ this.onload = null ;
+ ResetSizes() ;
+ }
+ }
+
+ oImageOriginal.src = eImgPreview.src ;
+}
+
+var bPreviewInitialized ;
+
+window.onload = function()
+{
+ // Translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ GetE('btnLockSizes').title = FCKLang.DlgImgLockRatio ;
+ GetE('btnResetSize').title = FCKLang.DlgBtnResetSize ;
+
+ // Load the selected element information (if any).
+ LoadSelection() ;
+
+ // Show/Hide the "Browse Server" button.
+ GetE('tdBrowse').style.display = FCKConfig.ImageBrowser ? '' : 'none' ;
+ GetE('divLnkBrowseServer').style.display = FCKConfig.LinkBrowser ? '' : 'none' ;
+
+ UpdateOriginal() ;
+
+ // Set the actual uploader URL.
+ if ( FCKConfig.ImageUpload )
+ GetE('frmUpload').action = FCKConfig.ImageUploadURL ;
+
+ dialog.SetAutoSize( true ) ;
+
+ // Activate the "OK" button.
+ dialog.SetOkButton( true ) ;
+
+ SelectField( 'txtUrl' ) ;
+}
+
+function LoadSelection()
+{
+ if ( ! oImage ) return ;
+
+ var sUrl = oImage.getAttribute( '_fcksavedurl' ) ;
+ if ( sUrl == null )
+ sUrl = GetAttribute( oImage, 'src', '' ) ;
+
+ GetE('txtUrl').value = sUrl ;
+ GetE('txtAlt').value = GetAttribute( oImage, 'alt', '' ) ;
+ GetE('txtVSpace').value = GetAttribute( oImage, 'vspace', '' ) ;
+ GetE('txtHSpace').value = GetAttribute( oImage, 'hspace', '' ) ;
+ GetE('txtBorder').value = GetAttribute( oImage, 'border', '' ) ;
+ GetE('cmbAlign').value = GetAttribute( oImage, 'align', '' ) ;
+
+ var iWidth, iHeight ;
+
+ var regexSize = /^\s*(\d+)px\s*$/i ;
+
+ if ( oImage.style.width )
+ {
+ var aMatchW = oImage.style.width.match( regexSize ) ;
+ if ( aMatchW )
+ {
+ iWidth = aMatchW[1] ;
+ oImage.style.width = '' ;
+ SetAttribute( oImage, 'width' , iWidth ) ;
+ }
+ }
+
+ if ( oImage.style.height )
+ {
+ var aMatchH = oImage.style.height.match( regexSize ) ;
+ if ( aMatchH )
+ {
+ iHeight = aMatchH[1] ;
+ oImage.style.height = '' ;
+ SetAttribute( oImage, 'height', iHeight ) ;
+ }
+ }
+
+ GetE('txtWidth').value = iWidth ? iWidth : GetAttribute( oImage, "width", '' ) ;
+ GetE('txtHeight').value = iHeight ? iHeight : GetAttribute( oImage, "height", '' ) ;
+
+ // Get Advances Attributes
+ GetE('txtAttId').value = oImage.id ;
+ GetE('cmbAttLangDir').value = oImage.dir ;
+ GetE('txtAttLangCode').value = oImage.lang ;
+ GetE('txtAttTitle').value = oImage.title ;
+ GetE('txtLongDesc').value = oImage.longDesc ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ GetE('txtAttClasses').value = oImage.className || '' ;
+ GetE('txtAttStyle').value = oImage.style.cssText ;
+ }
+ else
+ {
+ GetE('txtAttClasses').value = oImage.getAttribute('class',2) || '' ;
+ GetE('txtAttStyle').value = oImage.getAttribute('style',2) ;
+ }
+
+ if ( oLink )
+ {
+ var sLinkUrl = oLink.getAttribute( '_fcksavedurl' ) ;
+ if ( sLinkUrl == null )
+ sLinkUrl = oLink.getAttribute('href',2) ;
+
+ GetE('txtLnkUrl').value = sLinkUrl ;
+ GetE('cmbLnkTarget').value = oLink.target ;
+ }
+
+ UpdatePreview() ;
+}
+
+//#### The OK button was hit.
+function Ok()
+{
+ if ( GetE('txtUrl').value.length == 0 )
+ {
+ dialog.SetSelectedTab( 'Info' ) ;
+ GetE('txtUrl').focus() ;
+
+ alert( FCKLang.DlgImgAlertUrl ) ;
+
+ return false ;
+ }
+
+ var bHasImage = ( oImage != null ) ;
+
+ if ( bHasImage && bImageButton && oImage.tagName == 'IMG' )
+ {
+ if ( confirm( 'Do you want to transform the selected image on a image button?' ) )
+ oImage = null ;
+ }
+ else if ( bHasImage && !bImageButton && oImage.tagName == 'INPUT' )
+ {
+ if ( confirm( 'Do you want to transform the selected image button on a simple image?' ) )
+ oImage = null ;
+ }
+
+ oEditor.FCKUndo.SaveUndoStep() ;
+ if ( !bHasImage )
+ {
+ if ( bImageButton )
+ {
+ oImage = FCK.EditorDocument.createElement( 'input' ) ;
+ oImage.type = 'image' ;
+ oImage = FCK.InsertElement( oImage ) ;
+ }
+ else
+ oImage = FCK.InsertElement( 'img' ) ;
+ }
+
+ UpdateImage( oImage ) ;
+
+ var sLnkUrl = GetE('txtLnkUrl').value.Trim() ;
+
+ if ( sLnkUrl.length == 0 )
+ {
+ if ( oLink )
+ FCK.ExecuteNamedCommand( 'Unlink' ) ;
+ }
+ else
+ {
+ if ( oLink ) // Modifying an existent link.
+ oLink.href = sLnkUrl ;
+ else // Creating a new link.
+ {
+ if ( !bHasImage )
+ oEditor.FCKSelection.SelectNode( oImage ) ;
+
+ oLink = oEditor.FCK.CreateLink( sLnkUrl )[0] ;
+
+ if ( !bHasImage )
+ {
+ oEditor.FCKSelection.SelectNode( oLink ) ;
+ oEditor.FCKSelection.Collapse( false ) ;
+ }
+ }
+
+ SetAttribute( oLink, '_fcksavedurl', sLnkUrl ) ;
+ SetAttribute( oLink, 'target', GetE('cmbLnkTarget').value ) ;
+ }
+
+ return true ;
+}
+
+function UpdateImage( e, skipId )
+{
+ e.src = GetE('txtUrl').value ;
+ SetAttribute( e, "_fcksavedurl", GetE('txtUrl').value ) ;
+ SetAttribute( e, "alt" , GetE('txtAlt').value ) ;
+ SetAttribute( e, "width" , GetE('txtWidth').value ) ;
+ SetAttribute( e, "height", GetE('txtHeight').value ) ;
+ SetAttribute( e, "vspace", GetE('txtVSpace').value ) ;
+ SetAttribute( e, "hspace", GetE('txtHSpace').value ) ;
+ SetAttribute( e, "border", GetE('txtBorder').value ) ;
+ SetAttribute( e, "align" , GetE('cmbAlign').value ) ;
+
+ // Advances Attributes
+
+ if ( ! skipId )
+ SetAttribute( e, 'id', GetE('txtAttId').value ) ;
+
+ SetAttribute( e, 'dir' , GetE('cmbAttLangDir').value ) ;
+ SetAttribute( e, 'lang' , GetE('txtAttLangCode').value ) ;
+ SetAttribute( e, 'title' , GetE('txtAttTitle').value ) ;
+ SetAttribute( e, 'longDesc' , GetE('txtLongDesc').value ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ e.className = GetE('txtAttClasses').value ;
+ e.style.cssText = GetE('txtAttStyle').value ;
+ }
+ else
+ {
+ SetAttribute( e, 'class' , GetE('txtAttClasses').value ) ;
+ SetAttribute( e, 'style', GetE('txtAttStyle').value ) ;
+ }
+}
+
+var eImgPreview ;
+var eImgPreviewLink ;
+
+function SetPreviewElements( imageElement, linkElement )
+{
+ eImgPreview = imageElement ;
+ eImgPreviewLink = linkElement ;
+
+ UpdatePreview() ;
+ UpdateOriginal() ;
+
+ bPreviewInitialized = true ;
+}
+
+function UpdatePreview()
+{
+ if ( !eImgPreview || !eImgPreviewLink )
+ return ;
+
+ if ( GetE('txtUrl').value.length == 0 )
+ eImgPreviewLink.style.display = 'none' ;
+ else
+ {
+ UpdateImage( eImgPreview, true ) ;
+
+ if ( GetE('txtLnkUrl').value.Trim().length > 0 )
+ eImgPreviewLink.href = 'javascript:void(null);' ;
+ else
+ SetAttribute( eImgPreviewLink, 'href', '' ) ;
+
+ eImgPreviewLink.style.display = '' ;
+ }
+}
+
+var bLockRatio = true ;
+
+function SwitchLock( lockButton )
+{
+ bLockRatio = !bLockRatio ;
+ lockButton.className = bLockRatio ? 'BtnLocked' : 'BtnUnlocked' ;
+ lockButton.title = bLockRatio ? 'Lock sizes' : 'Unlock sizes' ;
+
+ if ( bLockRatio )
+ {
+ if ( GetE('txtWidth').value.length > 0 )
+ OnSizeChanged( 'Width', GetE('txtWidth').value ) ;
+ else
+ OnSizeChanged( 'Height', GetE('txtHeight').value ) ;
+ }
+}
+
+// Fired when the width or height input texts change
+function OnSizeChanged( dimension, value )
+{
+ // Verifies if the aspect ration has to be maintained
+ if ( oImageOriginal && bLockRatio )
+ {
+ var e = dimension == 'Width' ? GetE('txtHeight') : GetE('txtWidth') ;
+
+ if ( value.length == 0 || isNaN( value ) )
+ {
+ e.value = '' ;
+ return ;
+ }
+
+ if ( dimension == 'Width' )
+ value = value == 0 ? 0 : Math.round( oImageOriginal.height * ( value / oImageOriginal.width ) ) ;
+ else
+ value = value == 0 ? 0 : Math.round( oImageOriginal.width * ( value / oImageOriginal.height ) ) ;
+
+ if ( !isNaN( value ) )
+ e.value = value ;
+ }
+
+ UpdatePreview() ;
+}
+
+// Fired when the Reset Size button is clicked
+function ResetSizes()
+{
+ if ( ! oImageOriginal ) return ;
+ if ( oEditor.FCKBrowserInfo.IsGecko && !oImageOriginal.complete )
+ {
+ setTimeout( ResetSizes, 50 ) ;
+ return ;
+ }
+
+ GetE('txtWidth').value = oImageOriginal.width ;
+ GetE('txtHeight').value = oImageOriginal.height ;
+
+ UpdatePreview() ;
+}
+
+function BrowseServer()
+{
+ OpenServerBrowser(
+ 'Image',
+ FCKConfig.ImageBrowserURL,
+ FCKConfig.ImageBrowserWindowWidth,
+ FCKConfig.ImageBrowserWindowHeight ) ;
+}
+
+function LnkBrowseServer()
+{
+ OpenServerBrowser(
+ 'Link',
+ FCKConfig.LinkBrowserURL,
+ FCKConfig.LinkBrowserWindowWidth,
+ FCKConfig.LinkBrowserWindowHeight ) ;
+}
+
+function OpenServerBrowser( type, url, width, height )
+{
+ sActualBrowser = type ;
+ OpenFileBrowser( url, width, height ) ;
+}
+
+var sActualBrowser ;
+
+function SetUrl( url, width, height, alt )
+{
+ if ( sActualBrowser == 'Link' )
+ {
+ GetE('txtLnkUrl').value = url ;
+ UpdatePreview() ;
+ }
+ else
+ {
+ GetE('txtUrl').value = url ;
+ GetE('txtWidth').value = width ? width : '' ;
+ GetE('txtHeight').value = height ? height : '' ;
+
+ if ( alt )
+ GetE('txtAlt').value = alt;
+
+ UpdatePreview() ;
+ UpdateOriginal( true ) ;
+ }
+
+ dialog.SetSelectedTab( 'Info' ) ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ // Remove animation
+ window.parent.Throbber.Hide() ;
+ GetE( 'divUpload' ).style.display = '' ;
+
+ switch ( errorNumber )
+ {
+ case 0 : // No errors
+ alert( 'Your file has been successfully uploaded' ) ;
+ break ;
+ case 1 : // Custom error
+ alert( customMsg ) ;
+ return ;
+ case 101 : // Custom warning
+ alert( customMsg ) ;
+ break ;
+ case 201 :
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file type' ) ;
+ return ;
+ case 203 :
+ alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
+ return ;
+ case 500 :
+ alert( 'The connector is disabled' ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ return ;
+ }
+
+ sActualBrowser = '' ;
+ SetUrl( fileUrl ) ;
+ GetE('frmUpload').reset() ;
+}
+
+var oUploadAllowedExtRegex = new RegExp( FCKConfig.ImageUploadAllowedExtensions, 'i' ) ;
+var oUploadDeniedExtRegex = new RegExp( FCKConfig.ImageUploadDeniedExtensions, 'i' ) ;
+
+function CheckUpload()
+{
+ var sFile = GetE('txtUploadFile').value ;
+
+ if ( sFile.length == 0 )
+ {
+ alert( 'Please select a file to upload' ) ;
+ return false ;
+ }
+
+ if ( ( FCKConfig.ImageUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||
+ ( FCKConfig.ImageUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )
+ {
+ OnUploadCompleted( 202 ) ;
+ return false ;
+ }
+
+ // Show animation
+ window.parent.Throbber.Show( 100 ) ;
+ GetE( 'divUpload' ).style.display = 'none' ;
+
+ return true ;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html b/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html
new file mode 100644
index 0000000..db0d2e0
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Preview page for the Image dialog window.
+ *
+ * Curiosity: http://en.wikipedia.org/wiki/Lorem_ipsum
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="../common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var FCKTools = window.parent.FCKTools ;
+var FCKConfig = window.parent.FCKConfig ;
+
+// Set the preview CSS
+document.write( FCKTools.GetStyleHtml( FCKConfig.EditorAreaCSS ) ) ;
+document.write( FCKTools.GetStyleHtml( FCKConfig.EditorAreaStyles ) ) ;
+
+if ( window.parent.FCKConfig.BaseHref.length > 0 )
+ document.write( '<base href="' + window.parent.FCKConfig.BaseHref + '">' ) ;
+
+window.onload = function()
+{
+ window.parent.SetPreviewElements(
+ document.getElementById( 'imgPreview' ),
+ document.getElementById( 'lnkPreview' ) ) ;
+}
+
+ </script>
+</head>
+<body>
+ <div>
+ <a id="lnkPreview" onclick="return false;" style="cursor: default">
+ <img id="imgPreview" onload="window.parent.UpdateOriginal();"
+ style="display: none" alt="" /></a>Lorem ipsum dolor sit amet, consectetuer adipiscing
+ elit. Maecenas feugiat consequat diam. Maecenas metus. Vivamus diam purus, cursus
+ a, commodo non, facilisis vitae, nulla. Aenean dictum lacinia tortor. Nunc iaculis,
+ nibh non iaculis aliquam, orci felis euismod neque, sed ornare massa mauris sed
+ velit. Nulla pretium mi et risus. Fusce mi pede, tempor id, cursus ac, ullamcorper
+ nec, enim. Sed tortor. Curabitur molestie. Duis velit augue, condimentum at, ultrices
+ a, luctus ut, orci. Donec pellentesque egestas eros. Integer cursus, augue in cursus
+ faucibus, eros pede bibendum sem, in tempus tellus justo quis ligula. Etiam eget
+ tortor. Vestibulum rutrum, est ut placerat elementum, lectus nisl aliquam velit,
+ tempor aliquam eros nunc nonummy metus. In eros metus, gravida a, gravida sed, lobortis
+ id, turpis. Ut ultrices, ipsum at venenatis fringilla, sem nulla lacinia tellus,
+ eget aliquet turpis mauris non enim. Nam turpis. Suspendisse lacinia. Curabitur
+ ac tortor ut ipsum egestas elementum. Nunc imperdiet gravida mauris.
+ </div>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_link.html b/httemplate/elements/fckeditor/editor/dialog/fck_link.html
new file mode 100644
index 0000000..b57e1f0
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_link.html
@@ -0,0 +1,295 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Link dialog window.
+-->
+<html>
+ <head>
+ <title>Link Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script src="fck_link/fck_link.js" type="text/javascript"></script>
+ </head>
+ <body scroll="no" style="OVERFLOW: hidden">
+ <div id="divInfo" style="DISPLAY: none">
+ <span fckLang="DlgLnkType">Link Type</span><br />
+ <select id="cmbLinkType" onchange="SetLinkType(this.value);">
+ <option value="url" fckLang="DlgLnkTypeURL" selected="selected">URL</option>
+ <option value="anchor" fckLang="DlgLnkTypeAnchor">Anchor in this page</option>
+ <option value="email" fckLang="DlgLnkTypeEMail">E-Mail</option>
+ </select>
+ <br />
+ <br />
+ <div id="divLinkTypeUrl">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0" dir="ltr">
+ <tr>
+ <td nowrap="nowrap">
+ <span fckLang="DlgLnkProto">Protocol</span><br />
+ <select id="cmbLinkProtocol">
+ <option value="http://" selected="selected">http://</option>
+ <option value="https://">https://</option>
+ <option value="ftp://">ftp://</option>
+ <option value="news://">news://</option>
+ <option value="" fckLang="DlgLnkProtoOther">&lt;other&gt;</option>
+ </select>
+ </td>
+ <td nowrap="nowrap">&nbsp;</td>
+ <td nowrap="nowrap" width="100%">
+ <span fckLang="DlgLnkURL">URL</span><br />
+ <input id="txtUrl" style="WIDTH: 100%" type="text" onkeyup="OnUrlChange();" onchange="OnUrlChange();" />
+ </td>
+ </tr>
+ </table>
+ <br />
+ <div id="divBrowseServer">
+ <input type="button" value="Browse Server" fckLang="DlgBtnBrowseServer" onclick="BrowseServer();" />
+ </div>
+ </div>
+ <div id="divLinkTypeAnchor" style="DISPLAY: none" align="center">
+ <div id="divSelAnchor" style="DISPLAY: none">
+ <table cellspacing="0" cellpadding="0" border="0" width="70%">
+ <tr>
+ <td colspan="3">
+ <span fckLang="DlgLnkAnchorSel">Select an Anchor</span>
+ </td>
+ </tr>
+ <tr>
+ <td width="50%">
+ <span fckLang="DlgLnkAnchorByName">By Anchor Name</span><br />
+ <select id="cmbAnchorName" onchange="GetE('cmbAnchorId').value='';" style="WIDTH: 100%">
+ <option value="" selected="selected"></option>
+ </select>
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;</td>
+ <td width="50%">
+ <span fckLang="DlgLnkAnchorById">By Element Id</span><br />
+ <select id="cmbAnchorId" onchange="GetE('cmbAnchorName').value='';" style="WIDTH: 100%">
+ <option value="" selected="selected"></option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divNoAnchor" style="DISPLAY: none">
+ <span fckLang="DlgLnkNoAnchors">&lt;No anchors available in the document&gt;</span>
+ </div>
+ </div>
+ <div id="divLinkTypeEMail" style="DISPLAY: none">
+ <span fckLang="DlgLnkEMail">E-Mail Address</span><br />
+ <input id="txtEMailAddress" style="WIDTH: 100%" type="text" /><br />
+ <span fckLang="DlgLnkEMailSubject">Message Subject</span><br />
+ <input id="txtEMailSubject" style="WIDTH: 100%" type="text" /><br />
+ <span fckLang="DlgLnkEMailBody">Message Body</span><br />
+ <textarea id="txtEMailBody" style="WIDTH: 100%" rows="3" cols="20"></textarea>
+ </div>
+ </div>
+ <div id="divUpload" style="DISPLAY: none">
+ <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data" action="" onsubmit="return CheckUpload();">
+ <span fckLang="DlgLnkUpload">Upload</span><br />
+ <input id="txtUploadFile" style="WIDTH: 100%" type="file" size="40" name="NewFile" /><br />
+ <br />
+ <input id="btnUpload" type="submit" value="Send it to the Server" fckLang="DlgLnkBtnUpload" />
+ <script type="text/javascript">
+ document.write( '<iframe name="UploadWindow" style="display: none" src="' + FCKTools.GetVoidUrl() + '"><\/iframe>' ) ;
+ </script>
+ </form>
+ </div>
+ <div id="divTarget" style="DISPLAY: none">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fckLang="DlgLnkTarget">Target</span><br />
+ <select id="cmbTarget" onchange="SetTarget(this.value);">
+ <option value="" fckLang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>
+ <option value="frame" fckLang="DlgLnkTargetFrame">&lt;frame&gt;</option>
+ <option value="popup" fckLang="DlgLnkTargetPopup">&lt;popup window&gt;</option>
+ <option value="_blank" fckLang="DlgLnkTargetBlank">New Window (_blank)</option>
+ <option value="_top" fckLang="DlgLnkTargetTop">Topmost Window (_top)</option>
+ <option value="_self" fckLang="DlgLnkTargetSelf">Same Window (_self)</option>
+ <option value="_parent" fckLang="DlgLnkTargetParent">Parent Window (_parent)</option>
+ </select>
+ </td>
+ <td>&nbsp;</td>
+ <td id="tdTargetFrame" nowrap="nowrap" width="100%">
+ <span fckLang="DlgLnkTargetFrameName">Target Frame Name</span><br />
+ <input id="txtTargetFrame" style="WIDTH: 100%" type="text" onkeyup="OnTargetNameChange();"
+ onchange="OnTargetNameChange();" />
+ </td>
+ <td id="tdPopupName" style="DISPLAY: none" nowrap="nowrap" width="100%">
+ <span fckLang="DlgLnkPopWinName">Popup Window Name</span><br />
+ <input id="txtPopupName" style="WIDTH: 100%" type="text" />
+ </td>
+ </tr>
+ </table>
+ <br />
+ <table id="tablePopupFeatures" style="DISPLAY: none" cellspacing="0" cellpadding="0" align="center"
+ border="0">
+ <tr>
+ <td>
+ <span fckLang="DlgLnkPopWinFeat">Popup Window Features</span><br />
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td valign="top" nowrap="nowrap" width="50%">
+ <input id="chkPopupResizable" name="chkFeature" value="resizable" type="checkbox" /><label for="chkPopupResizable" fckLang="DlgLnkPopResize">Resizable</label><br />
+ <input id="chkPopupLocationBar" name="chkFeature" value="location" type="checkbox" /><label for="chkPopupLocationBar" fckLang="DlgLnkPopLocation">Location
+ Bar</label><br />
+ <input id="chkPopupManuBar" name="chkFeature" value="menubar" type="checkbox" /><label for="chkPopupManuBar" fckLang="DlgLnkPopMenu">Menu
+ Bar</label><br />
+ <input id="chkPopupScrollBars" name="chkFeature" value="scrollbars" type="checkbox" /><label for="chkPopupScrollBars" fckLang="DlgLnkPopScroll">Scroll
+ Bars</label>
+ </td>
+ <td></td>
+ <td valign="top" nowrap="nowrap" width="50%">
+ <input id="chkPopupStatusBar" name="chkFeature" value="status" type="checkbox" /><label for="chkPopupStatusBar" fckLang="DlgLnkPopStatus">Status
+ Bar</label><br />
+ <input id="chkPopupToolbar" name="chkFeature" value="toolbar" type="checkbox" /><label for="chkPopupToolbar" fckLang="DlgLnkPopToolbar">Toolbar</label><br />
+ <input id="chkPopupFullScreen" name="chkFeature" value="fullscreen" type="checkbox" /><label for="chkPopupFullScreen" fckLang="DlgLnkPopFullScrn">Full
+ Screen (IE)</label><br />
+ <input id="chkPopupDependent" name="chkFeature" value="dependent" type="checkbox" /><label for="chkPopupDependent" fckLang="DlgLnkPopDependent">Dependent
+ (Netscape)</label>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top" nowrap="nowrap" width="50%">&nbsp;</td>
+ <td></td>
+ <td valign="top" nowrap="nowrap" width="50%"></td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap"><span fckLang="DlgLnkPopWidth">Width</span></td>
+ <td>&nbsp;<input id="txtPopupWidth" type="text" maxlength="4" size="4" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap"><span fckLang="DlgLnkPopHeight">Height</span></td>
+ <td>&nbsp;<input id="txtPopupHeight" type="text" maxlength="4" size="4" /></td>
+ </tr>
+ </table>
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap"><span fckLang="DlgLnkPopLeft">Left Position</span></td>
+ <td>&nbsp;<input id="txtPopupLeft" type="text" maxlength="4" size="4" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap"><span fckLang="DlgLnkPopTop">Top Position</span></td>
+ <td>&nbsp;<input id="txtPopupTop" type="text" maxlength="4" size="4" /></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divAttribs" style="DISPLAY: none">
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td valign="top" width="50%">
+ <span fckLang="DlgGenId">Id</span><br />
+ <input id="txtAttId" style="WIDTH: 100%" type="text" />
+ </td>
+ <td width="1"></td>
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td width="60%">
+ <span fckLang="DlgGenLangDir">Language Direction</span><br />
+ <select id="cmbAttLangDir" style="WIDTH: 100%">
+ <option value="" fckLang="DlgGenNotSet" selected>&lt;not set&gt;</option>
+ <option value="ltr" fckLang="DlgGenLangDirLtr">Left to Right (LTR)</option>
+ <option value="rtl" fckLang="DlgGenLangDirRtl">Right to Left (RTL)</option>
+ </select>
+ </td>
+ <td width="1%">&nbsp;&nbsp;&nbsp;</td>
+ <td nowrap="nowrap"><span fckLang="DlgGenAccessKey">Access Key</span><br />
+ <input id="txtAttAccessKey" style="WIDTH: 100%" type="text" maxlength="1" size="1" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top" width="50%">
+ <span fckLang="DlgGenName">Name</span><br />
+ <input id="txtAttName" style="WIDTH: 100%" type="text" />
+ </td>
+ <td width="1"></td>
+ <td valign="top">
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td width="60%">
+ <span fckLang="DlgGenLangCode">Language Code</span><br />
+ <input id="txtAttLangCode" style="WIDTH: 100%" type="text" />
+ </td>
+ <td width="1%">&nbsp;&nbsp;&nbsp;</td>
+ <td nowrap="nowrap">
+ <span fckLang="DlgGenTabIndex">Tab Index</span><br />
+ <input id="txtAttTabIndex" style="WIDTH: 100%" type="text" maxlength="5" size="5" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top" width="50%">&nbsp;</td>
+ <td width="1"></td>
+ <td valign="top"></td>
+ </tr>
+ <tr>
+ <td valign="top" width="50%">
+ <span fckLang="DlgGenTitle">Advisory Title</span><br />
+ <input id="txtAttTitle" style="WIDTH: 100%" type="text" />
+ </td>
+ <td width="1">&nbsp;&nbsp;&nbsp;</td>
+ <td valign="top">
+ <span fckLang="DlgGenContType">Advisory Content Type</span><br />
+ <input id="txtAttContentType" style="WIDTH: 100%" type="text" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <span fckLang="DlgGenClass">Stylesheet Classes</span><br />
+ <input id="txtAttClasses" style="WIDTH: 100%" type="text" />
+ </td>
+ <td></td>
+ <td valign="top">
+ <span fckLang="DlgGenLinkCharset">Linked Resource Charset</span><br />
+ <input id="txtAttCharSet" style="WIDTH: 100%" type="text" />
+ </td>
+ </tr>
+ </table>
+ <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
+ <tr>
+ <td>
+ <span fckLang="DlgGenStyle">Style</span><br />
+ <input id="txtAttStyle" style="WIDTH: 100%" type="text" />
+ </td>
+ </tr>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js b/httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js
new file mode 100644
index 0000000..449e13f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js
@@ -0,0 +1,893 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Scripts related to the Link dialog window (see fck_link.html).
+ */
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKConfig = oEditor.FCKConfig ;
+var FCKRegexLib = oEditor.FCKRegexLib ;
+var FCKTools = oEditor.FCKTools ;
+
+//#### Dialog Tabs
+
+// Set the dialog tabs.
+dialog.AddTab( 'Info', FCKLang.DlgLnkInfoTab ) ;
+
+if ( !FCKConfig.LinkDlgHideTarget )
+ dialog.AddTab( 'Target', FCKLang.DlgLnkTargetTab, true ) ;
+
+if ( FCKConfig.LinkUpload )
+ dialog.AddTab( 'Upload', FCKLang.DlgLnkUpload, true ) ;
+
+if ( !FCKConfig.LinkDlgHideAdvanced )
+ dialog.AddTab( 'Advanced', FCKLang.DlgAdvancedTag ) ;
+
+// Function called when a dialog tag is selected.
+function OnDialogTabChange( tabCode )
+{
+ ShowE('divInfo' , ( tabCode == 'Info' ) ) ;
+ ShowE('divTarget' , ( tabCode == 'Target' ) ) ;
+ ShowE('divUpload' , ( tabCode == 'Upload' ) ) ;
+ ShowE('divAttribs' , ( tabCode == 'Advanced' ) ) ;
+
+ dialog.SetAutoSize( true ) ;
+}
+
+//#### Regular Expressions library.
+var oRegex = new Object() ;
+
+oRegex.UriProtocol = /^(((http|https|ftp|news):\/\/)|mailto:)/gi ;
+
+oRegex.UrlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/gi ;
+
+oRegex.UrlOnChangeTestOther = /^((javascript:)|[#\/\.])/gi ;
+
+oRegex.ReserveTarget = /^_(blank|self|top|parent)$/i ;
+
+oRegex.PopupUri = /^javascript:void\(\s*window.open\(\s*'([^']+)'\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*\)\s*$/ ;
+
+// Accessible popups
+oRegex.OnClickPopup = /^\s*on[cC]lick="\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*"$/ ;
+
+oRegex.PopupFeatures = /(?:^|,)([^=]+)=(\d+|yes|no)/gi ;
+
+//#### Parser Functions
+
+var oParser = new Object() ;
+
+// This method simply returns the two inputs in numerical order. You can even
+// provide strings, as the method would parseInt() the values.
+oParser.SortNumerical = function(a, b)
+{
+ return parseInt( a, 10 ) - parseInt( b, 10 ) ;
+}
+
+oParser.ParseEMailParams = function(sParams)
+{
+ // Initialize the oEMailParams object.
+ var oEMailParams = new Object() ;
+ oEMailParams.Subject = '' ;
+ oEMailParams.Body = '' ;
+
+ var aMatch = sParams.match( /(^|^\?|&)subject=([^&]+)/i ) ;
+ if ( aMatch ) oEMailParams.Subject = decodeURIComponent( aMatch[2] ) ;
+
+ aMatch = sParams.match( /(^|^\?|&)body=([^&]+)/i ) ;
+ if ( aMatch ) oEMailParams.Body = decodeURIComponent( aMatch[2] ) ;
+
+ return oEMailParams ;
+}
+
+// This method returns either an object containing the email info, or FALSE
+// if the parameter is not an email link.
+oParser.ParseEMailUri = function( sUrl )
+{
+ // Initializes the EMailInfo object.
+ var oEMailInfo = new Object() ;
+ oEMailInfo.Address = '' ;
+ oEMailInfo.Subject = '' ;
+ oEMailInfo.Body = '' ;
+
+ var aLinkInfo = sUrl.match( /^(\w+):(.*)$/ ) ;
+ if ( aLinkInfo && aLinkInfo[1] == 'mailto' )
+ {
+ // This seems to be an unprotected email link.
+ var aParts = aLinkInfo[2].match( /^([^\?]+)\??(.+)?/ ) ;
+ if ( aParts )
+ {
+ // Set the e-mail address.
+ oEMailInfo.Address = aParts[1] ;
+
+ // Look for the optional e-mail parameters.
+ if ( aParts[2] )
+ {
+ var oEMailParams = oParser.ParseEMailParams( aParts[2] ) ;
+ oEMailInfo.Subject = oEMailParams.Subject ;
+ oEMailInfo.Body = oEMailParams.Body ;
+ }
+ }
+ return oEMailInfo ;
+ }
+ else if ( aLinkInfo && aLinkInfo[1] == 'javascript' )
+ {
+ // This may be a protected email.
+
+ // Try to match the url against the EMailProtectionFunction.
+ var func = FCKConfig.EMailProtectionFunction ;
+ if ( func != null )
+ {
+ try
+ {
+ // Escape special chars.
+ func = func.replace( /([\/^$*+.?()\[\]])/g, '\\$1' ) ;
+
+ // Define the possible keys.
+ var keys = new Array('NAME', 'DOMAIN', 'SUBJECT', 'BODY') ;
+
+ // Get the order of the keys (hold them in the array <pos>) and
+ // the function replaced by regular expression patterns.
+ var sFunc = func ;
+ var pos = new Array() ;
+ for ( var i = 0 ; i < keys.length ; i ++ )
+ {
+ var rexp = new RegExp( keys[i] ) ;
+ var p = func.search( rexp ) ;
+ if ( p >= 0 )
+ {
+ sFunc = sFunc.replace( rexp, '\'([^\']*)\'' ) ;
+ pos[pos.length] = p + ':' + keys[i] ;
+ }
+ }
+
+ // Sort the available keys.
+ pos.sort( oParser.SortNumerical ) ;
+
+ // Replace the excaped single quotes in the url, such they do
+ // not affect the regexp afterwards.
+ aLinkInfo[2] = aLinkInfo[2].replace( /\\'/g, '###SINGLE_QUOTE###' ) ;
+
+ // Create the regexp and execute it.
+ var rFunc = new RegExp( '^' + sFunc + '$' ) ;
+ var aMatch = rFunc.exec( aLinkInfo[2] ) ;
+ if ( aMatch )
+ {
+ var aInfo = new Array();
+ for ( var i = 1 ; i < aMatch.length ; i ++ )
+ {
+ var k = pos[i-1].match(/^\d+:(.+)$/) ;
+ aInfo[k[1]] = aMatch[i].replace(/###SINGLE_QUOTE###/g, '\'') ;
+ }
+
+ // Fill the EMailInfo object that will be returned
+ oEMailInfo.Address = aInfo['NAME'] + '@' + aInfo['DOMAIN'] ;
+ oEMailInfo.Subject = decodeURIComponent( aInfo['SUBJECT'] ) ;
+ oEMailInfo.Body = decodeURIComponent( aInfo['BODY'] ) ;
+
+ return oEMailInfo ;
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+
+ // Try to match the email against the encode protection.
+ var aMatch = aLinkInfo[2].match( /^(?:void\()?location\.href='mailto:'\+(String\.fromCharCode\([\d,]+\))\+'(.*)'\)?$/ ) ;
+ if ( aMatch )
+ {
+ // The link is encoded
+ oEMailInfo.Address = eval( aMatch[1] ) ;
+ if ( aMatch[2] )
+ {
+ var oEMailParams = oParser.ParseEMailParams( aMatch[2] ) ;
+ oEMailInfo.Subject = oEMailParams.Subject ;
+ oEMailInfo.Body = oEMailParams.Body ;
+ }
+ return oEMailInfo ;
+ }
+ }
+ return false;
+}
+
+oParser.CreateEMailUri = function( address, subject, body )
+{
+ // Switch for the EMailProtection setting.
+ switch ( FCKConfig.EMailProtection )
+ {
+ case 'function' :
+ var func = FCKConfig.EMailProtectionFunction ;
+ if ( func == null )
+ {
+ if ( FCKConfig.Debug )
+ {
+ alert('EMailProtection alert!\nNo function defined. Please set "FCKConfig.EMailProtectionFunction"') ;
+ }
+ return '';
+ }
+
+ // Split the email address into name and domain parts.
+ var aAddressParts = address.split( '@', 2 ) ;
+ if ( aAddressParts[1] == undefined )
+ {
+ aAddressParts[1] = '' ;
+ }
+
+ // Replace the keys by their values (embedded in single quotes).
+ func = func.replace(/NAME/g, "'" + aAddressParts[0].replace(/'/g, '\\\'') + "'") ;
+ func = func.replace(/DOMAIN/g, "'" + aAddressParts[1].replace(/'/g, '\\\'') + "'") ;
+ func = func.replace(/SUBJECT/g, "'" + encodeURIComponent( subject ).replace(/'/g, '\\\'') + "'") ;
+ func = func.replace(/BODY/g, "'" + encodeURIComponent( body ).replace(/'/g, '\\\'') + "'") ;
+
+ return 'javascript:' + func ;
+
+ case 'encode' :
+ var aParams = [] ;
+ var aAddressCode = [] ;
+
+ if ( subject.length > 0 )
+ aParams.push( 'subject='+ encodeURIComponent( subject ) ) ;
+ if ( body.length > 0 )
+ aParams.push( 'body=' + encodeURIComponent( body ) ) ;
+ for ( var i = 0 ; i < address.length ; i++ )
+ aAddressCode.push( address.charCodeAt( i ) ) ;
+
+ return 'javascript:void(location.href=\'mailto:\'+String.fromCharCode(' + aAddressCode.join( ',' ) + ')+\'?' + aParams.join( '&' ) + '\')' ;
+ }
+
+ // EMailProtection 'none'
+
+ var sBaseUri = 'mailto:' + address ;
+
+ var sParams = '' ;
+
+ if ( subject.length > 0 )
+ sParams = '?subject=' + encodeURIComponent( subject ) ;
+
+ if ( body.length > 0 )
+ {
+ sParams += ( sParams.length == 0 ? '?' : '&' ) ;
+ sParams += 'body=' + encodeURIComponent( body ) ;
+ }
+
+ return sBaseUri + sParams ;
+}
+
+//#### Initialization Code
+
+// oLink: The actual selected link in the editor.
+var oLink = dialog.Selection.GetSelection().MoveToAncestorNode( 'A' ) ;
+if ( oLink )
+ FCK.Selection.SelectNode( oLink ) ;
+
+window.onload = function()
+{
+ // Translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ // Fill the Anchor Names and Ids combos.
+ LoadAnchorNamesAndIds() ;
+
+ // Load the selected link information (if any).
+ LoadSelection() ;
+
+ // Update the dialog box.
+ SetLinkType( GetE('cmbLinkType').value ) ;
+
+ // Show/Hide the "Browse Server" button.
+ GetE('divBrowseServer').style.display = FCKConfig.LinkBrowser ? '' : 'none' ;
+
+ // Show the initial dialog content.
+ GetE('divInfo').style.display = '' ;
+
+ // Set the actual uploader URL.
+ if ( FCKConfig.LinkUpload )
+ GetE('frmUpload').action = FCKConfig.LinkUploadURL ;
+
+ // Set the default target (from configuration).
+ SetDefaultTarget() ;
+
+ // Activate the "OK" button.
+ dialog.SetOkButton( true ) ;
+
+ // Select the first field.
+ switch( GetE('cmbLinkType').value )
+ {
+ case 'url' :
+ SelectField( 'txtUrl' ) ;
+ break ;
+ case 'email' :
+ SelectField( 'txtEMailAddress' ) ;
+ break ;
+ case 'anchor' :
+ if ( GetE('divSelAnchor').style.display != 'none' )
+ SelectField( 'cmbAnchorName' ) ;
+ else
+ SelectField( 'cmbLinkType' ) ;
+ }
+}
+
+var bHasAnchors ;
+
+function LoadAnchorNamesAndIds()
+{
+ // Since version 2.0, the anchors are replaced in the DOM by IMGs so the user see the icon
+ // to edit them. So, we must look for that images now.
+ var aAnchors = new Array() ;
+ var i ;
+ var oImages = oEditor.FCK.EditorDocument.getElementsByTagName( 'IMG' ) ;
+ for( i = 0 ; i < oImages.length ; i++ )
+ {
+ if ( oImages[i].getAttribute('_fckanchor') )
+ aAnchors[ aAnchors.length ] = oEditor.FCK.GetRealElement( oImages[i] ) ;
+ }
+
+ // Add also real anchors
+ var oLinks = oEditor.FCK.EditorDocument.getElementsByTagName( 'A' ) ;
+ for( i = 0 ; i < oLinks.length ; i++ )
+ {
+ if ( oLinks[i].name && ( oLinks[i].name.length > 0 ) )
+ aAnchors[ aAnchors.length ] = oLinks[i] ;
+ }
+
+ var aIds = FCKTools.GetAllChildrenIds( oEditor.FCK.EditorDocument.body ) ;
+
+ bHasAnchors = ( aAnchors.length > 0 || aIds.length > 0 ) ;
+
+ for ( i = 0 ; i < aAnchors.length ; i++ )
+ {
+ var sName = aAnchors[i].name ;
+ if ( sName && sName.length > 0 )
+ FCKTools.AddSelectOption( GetE('cmbAnchorName'), sName, sName ) ;
+ }
+
+ for ( i = 0 ; i < aIds.length ; i++ )
+ {
+ FCKTools.AddSelectOption( GetE('cmbAnchorId'), aIds[i], aIds[i] ) ;
+ }
+
+ ShowE( 'divSelAnchor' , bHasAnchors ) ;
+ ShowE( 'divNoAnchor' , !bHasAnchors ) ;
+}
+
+function LoadSelection()
+{
+ if ( !oLink ) return ;
+
+ var sType = 'url' ;
+
+ // Get the actual Link href.
+ var sHRef = oLink.getAttribute( '_fcksavedurl' ) ;
+ if ( sHRef == null )
+ sHRef = oLink.getAttribute( 'href' , 2 ) || '' ;
+
+ // Look for a popup javascript link.
+ var oPopupMatch = oRegex.PopupUri.exec( sHRef ) ;
+ if( oPopupMatch )
+ {
+ GetE('cmbTarget').value = 'popup' ;
+ sHRef = oPopupMatch[1] ;
+ FillPopupFields( oPopupMatch[2], oPopupMatch[3] ) ;
+ SetTarget( 'popup' ) ;
+ }
+
+ // Accessible popups, the popup data is in the onclick attribute
+ if ( !oPopupMatch )
+ {
+ var onclick = oLink.getAttribute( 'onclick_fckprotectedatt' ) ;
+ if ( onclick )
+ {
+ // Decode the protected string
+ onclick = decodeURIComponent( onclick ) ;
+
+ oPopupMatch = oRegex.OnClickPopup.exec( onclick ) ;
+ if( oPopupMatch )
+ {
+ GetE( 'cmbTarget' ).value = 'popup' ;
+ FillPopupFields( oPopupMatch[1], oPopupMatch[2] ) ;
+ SetTarget( 'popup' ) ;
+ }
+ }
+ }
+
+ // Search for the protocol.
+ var sProtocol = oRegex.UriProtocol.exec( sHRef ) ;
+
+ // Search for a protected email link.
+ var oEMailInfo = oParser.ParseEMailUri( sHRef );
+
+ if ( oEMailInfo )
+ {
+ sType = 'email' ;
+
+ GetE('txtEMailAddress').value = oEMailInfo.Address ;
+ GetE('txtEMailSubject').value = oEMailInfo.Subject ;
+ GetE('txtEMailBody').value = oEMailInfo.Body ;
+ }
+ else if ( sProtocol )
+ {
+ sProtocol = sProtocol[0].toLowerCase() ;
+ GetE('cmbLinkProtocol').value = sProtocol ;
+
+ // Remove the protocol and get the remaining URL.
+ var sUrl = sHRef.replace( oRegex.UriProtocol, '' ) ;
+ sType = 'url' ;
+ GetE('txtUrl').value = sUrl ;
+ }
+ else if ( sHRef.substr(0,1) == '#' && sHRef.length > 1 ) // It is an anchor link.
+ {
+ sType = 'anchor' ;
+ GetE('cmbAnchorName').value = GetE('cmbAnchorId').value = sHRef.substr(1) ;
+ }
+ else // It is another type of link.
+ {
+ sType = 'url' ;
+
+ GetE('cmbLinkProtocol').value = '' ;
+ GetE('txtUrl').value = sHRef ;
+ }
+
+ if ( !oPopupMatch )
+ {
+ // Get the target.
+ var sTarget = oLink.target ;
+
+ if ( sTarget && sTarget.length > 0 )
+ {
+ if ( oRegex.ReserveTarget.test( sTarget ) )
+ {
+ sTarget = sTarget.toLowerCase() ;
+ GetE('cmbTarget').value = sTarget ;
+ }
+ else
+ GetE('cmbTarget').value = 'frame' ;
+ GetE('txtTargetFrame').value = sTarget ;
+ }
+ }
+
+ // Get Advances Attributes
+ GetE('txtAttId').value = oLink.id ;
+ GetE('txtAttName').value = oLink.name ;
+ GetE('cmbAttLangDir').value = oLink.dir ;
+ GetE('txtAttLangCode').value = oLink.lang ;
+ GetE('txtAttAccessKey').value = oLink.accessKey ;
+ GetE('txtAttTabIndex').value = oLink.tabIndex <= 0 ? '' : oLink.tabIndex ;
+ GetE('txtAttTitle').value = oLink.title ;
+ GetE('txtAttContentType').value = oLink.type ;
+ GetE('txtAttCharSet').value = oLink.charset ;
+
+ var sClass ;
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ sClass = oLink.getAttribute('className',2) || '' ;
+ // Clean up temporary classes for internal use:
+ sClass = sClass.replace( FCKRegexLib.FCK_Class, '' ) ;
+
+ GetE('txtAttStyle').value = oLink.style.cssText ;
+ }
+ else
+ {
+ sClass = oLink.getAttribute('class',2) || '' ;
+ GetE('txtAttStyle').value = oLink.getAttribute('style',2) || '' ;
+ }
+ GetE('txtAttClasses').value = sClass ;
+
+ // Update the Link type combo.
+ GetE('cmbLinkType').value = sType ;
+}
+
+//#### Link type selection.
+function SetLinkType( linkType )
+{
+ ShowE('divLinkTypeUrl' , (linkType == 'url') ) ;
+ ShowE('divLinkTypeAnchor' , (linkType == 'anchor') ) ;
+ ShowE('divLinkTypeEMail' , (linkType == 'email') ) ;
+
+ if ( !FCKConfig.LinkDlgHideTarget )
+ dialog.SetTabVisibility( 'Target' , (linkType == 'url') ) ;
+
+ if ( FCKConfig.LinkUpload )
+ dialog.SetTabVisibility( 'Upload' , (linkType == 'url') ) ;
+
+ if ( !FCKConfig.LinkDlgHideAdvanced )
+ dialog.SetTabVisibility( 'Advanced' , (linkType != 'anchor' || bHasAnchors) ) ;
+
+ if ( linkType == 'email' )
+ dialog.SetAutoSize( true ) ;
+}
+
+//#### Target type selection.
+function SetTarget( targetType )
+{
+ GetE('tdTargetFrame').style.display = ( targetType == 'popup' ? 'none' : '' ) ;
+ GetE('tdPopupName').style.display =
+ GetE('tablePopupFeatures').style.display = ( targetType == 'popup' ? '' : 'none' ) ;
+
+ switch ( targetType )
+ {
+ case "_blank" :
+ case "_self" :
+ case "_parent" :
+ case "_top" :
+ GetE('txtTargetFrame').value = targetType ;
+ break ;
+ case "" :
+ GetE('txtTargetFrame').value = '' ;
+ break ;
+ }
+
+ if ( targetType == 'popup' )
+ dialog.SetAutoSize( true ) ;
+}
+
+//#### Called while the user types the URL.
+function OnUrlChange()
+{
+ var sUrl = GetE('txtUrl').value ;
+ var sProtocol = oRegex.UrlOnChangeProtocol.exec( sUrl ) ;
+
+ if ( sProtocol )
+ {
+ sUrl = sUrl.substr( sProtocol[0].length ) ;
+ GetE('txtUrl').value = sUrl ;
+ GetE('cmbLinkProtocol').value = sProtocol[0].toLowerCase() ;
+ }
+ else if ( oRegex.UrlOnChangeTestOther.test( sUrl ) )
+ {
+ GetE('cmbLinkProtocol').value = '' ;
+ }
+}
+
+//#### Called while the user types the target name.
+function OnTargetNameChange()
+{
+ var sFrame = GetE('txtTargetFrame').value ;
+
+ if ( sFrame.length == 0 )
+ GetE('cmbTarget').value = '' ;
+ else if ( oRegex.ReserveTarget.test( sFrame ) )
+ GetE('cmbTarget').value = sFrame.toLowerCase() ;
+ else
+ GetE('cmbTarget').value = 'frame' ;
+}
+
+// Accessible popups
+function BuildOnClickPopup()
+{
+ var sWindowName = "'" + GetE('txtPopupName').value.replace(/\W/gi, "") + "'" ;
+
+ var sFeatures = '' ;
+ var aChkFeatures = document.getElementsByName( 'chkFeature' ) ;
+ for ( var i = 0 ; i < aChkFeatures.length ; i++ )
+ {
+ if ( i > 0 ) sFeatures += ',' ;
+ sFeatures += aChkFeatures[i].value + '=' + ( aChkFeatures[i].checked ? 'yes' : 'no' ) ;
+ }
+
+ if ( GetE('txtPopupWidth').value.length > 0 ) sFeatures += ',width=' + GetE('txtPopupWidth').value ;
+ if ( GetE('txtPopupHeight').value.length > 0 ) sFeatures += ',height=' + GetE('txtPopupHeight').value ;
+ if ( GetE('txtPopupLeft').value.length > 0 ) sFeatures += ',left=' + GetE('txtPopupLeft').value ;
+ if ( GetE('txtPopupTop').value.length > 0 ) sFeatures += ',top=' + GetE('txtPopupTop').value ;
+
+ if ( sFeatures != '' )
+ sFeatures = sFeatures + ",status" ;
+
+ return ( "window.open(this.href," + sWindowName + ",'" + sFeatures + "'); return false" ) ;
+}
+
+//#### Fills all Popup related fields.
+function FillPopupFields( windowName, features )
+{
+ if ( windowName )
+ GetE('txtPopupName').value = windowName ;
+
+ var oFeatures = new Object() ;
+ var oFeaturesMatch ;
+ while( ( oFeaturesMatch = oRegex.PopupFeatures.exec( features ) ) != null )
+ {
+ var sValue = oFeaturesMatch[2] ;
+ if ( sValue == ( 'yes' || '1' ) )
+ oFeatures[ oFeaturesMatch[1] ] = true ;
+ else if ( ! isNaN( sValue ) && sValue != 0 )
+ oFeatures[ oFeaturesMatch[1] ] = sValue ;
+ }
+
+ // Update all features check boxes.
+ var aChkFeatures = document.getElementsByName('chkFeature') ;
+ for ( var i = 0 ; i < aChkFeatures.length ; i++ )
+ {
+ if ( oFeatures[ aChkFeatures[i].value ] )
+ aChkFeatures[i].checked = true ;
+ }
+
+ // Update position and size text boxes.
+ if ( oFeatures['width'] ) GetE('txtPopupWidth').value = oFeatures['width'] ;
+ if ( oFeatures['height'] ) GetE('txtPopupHeight').value = oFeatures['height'] ;
+ if ( oFeatures['left'] ) GetE('txtPopupLeft').value = oFeatures['left'] ;
+ if ( oFeatures['top'] ) GetE('txtPopupTop').value = oFeatures['top'] ;
+}
+
+//#### The OK button was hit.
+function Ok()
+{
+ var sUri, sInnerHtml ;
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ switch ( GetE('cmbLinkType').value )
+ {
+ case 'url' :
+ sUri = GetE('txtUrl').value ;
+
+ if ( sUri.length == 0 )
+ {
+ alert( FCKLang.DlnLnkMsgNoUrl ) ;
+ return false ;
+ }
+
+ sUri = GetE('cmbLinkProtocol').value + sUri ;
+
+ break ;
+
+ case 'email' :
+ sUri = GetE('txtEMailAddress').value ;
+
+ if ( sUri.length == 0 )
+ {
+ alert( FCKLang.DlnLnkMsgNoEMail ) ;
+ return false ;
+ }
+
+ sUri = oParser.CreateEMailUri(
+ sUri,
+ GetE('txtEMailSubject').value,
+ GetE('txtEMailBody').value ) ;
+ break ;
+
+ case 'anchor' :
+ var sAnchor = GetE('cmbAnchorName').value ;
+ if ( sAnchor.length == 0 ) sAnchor = GetE('cmbAnchorId').value ;
+
+ if ( sAnchor.length == 0 )
+ {
+ alert( FCKLang.DlnLnkMsgNoAnchor ) ;
+ return false ;
+ }
+
+ sUri = '#' + sAnchor ;
+ break ;
+ }
+
+ // If no link is selected, create a new one (it may result in more than one link creation - #220).
+ var aLinks = oLink ? [ oLink ] : oEditor.FCK.CreateLink( sUri, true ) ;
+
+ // If no selection, no links are created, so use the uri as the link text (by dom, 2006-05-26)
+ var aHasSelection = ( aLinks.length > 0 ) ;
+ if ( !aHasSelection )
+ {
+ sInnerHtml = sUri;
+
+ // Built a better text for empty links.
+ switch ( GetE('cmbLinkType').value )
+ {
+ // anchor: use old behavior --> return true
+ case 'anchor':
+ sInnerHtml = sInnerHtml.replace( /^#/, '' ) ;
+ break ;
+
+ // url: try to get path
+ case 'url':
+ var oLinkPathRegEx = new RegExp("//?([^?\"']+)([?].*)?$") ;
+ var asLinkPath = oLinkPathRegEx.exec( sUri ) ;
+ if (asLinkPath != null)
+ sInnerHtml = asLinkPath[1]; // use matched path
+ break ;
+
+ // mailto: try to get email address
+ case 'email':
+ sInnerHtml = GetE('txtEMailAddress').value ;
+ break ;
+ }
+
+ // Create a new (empty) anchor.
+ aLinks = [ oEditor.FCK.InsertElement( 'a' ) ] ;
+ }
+
+ for ( var i = 0 ; i < aLinks.length ; i++ )
+ {
+ oLink = aLinks[i] ;
+
+ if ( aHasSelection )
+ sInnerHtml = oLink.innerHTML ; // Save the innerHTML (IE changes it if it is like an URL).
+
+ oLink.href = sUri ;
+ SetAttribute( oLink, '_fcksavedurl', sUri ) ;
+
+ var onclick;
+ // Accessible popups
+ if( GetE('cmbTarget').value == 'popup' )
+ {
+ onclick = BuildOnClickPopup() ;
+ // Encode the attribute
+ onclick = encodeURIComponent( " onclick=\"" + onclick + "\"" ) ;
+ SetAttribute( oLink, 'onclick_fckprotectedatt', onclick ) ;
+ }
+ else
+ {
+ // Check if the previous onclick was for a popup:
+ // In that case remove the onclick handler.
+ onclick = oLink.getAttribute( 'onclick_fckprotectedatt' ) ;
+ if ( onclick )
+ {
+ // Decode the protected string
+ onclick = decodeURIComponent( onclick ) ;
+
+ if( oRegex.OnClickPopup.test( onclick ) )
+ SetAttribute( oLink, 'onclick_fckprotectedatt', '' ) ;
+ }
+ }
+
+ oLink.innerHTML = sInnerHtml ; // Set (or restore) the innerHTML
+
+ // Target
+ if( GetE('cmbTarget').value != 'popup' )
+ SetAttribute( oLink, 'target', GetE('txtTargetFrame').value ) ;
+ else
+ SetAttribute( oLink, 'target', null ) ;
+
+ // Let's set the "id" only for the first link to avoid duplication.
+ if ( i == 0 )
+ SetAttribute( oLink, 'id', GetE('txtAttId').value ) ;
+
+ // Advances Attributes
+ SetAttribute( oLink, 'name' , GetE('txtAttName').value ) ;
+ SetAttribute( oLink, 'dir' , GetE('cmbAttLangDir').value ) ;
+ SetAttribute( oLink, 'lang' , GetE('txtAttLangCode').value ) ;
+ SetAttribute( oLink, 'accesskey', GetE('txtAttAccessKey').value ) ;
+ SetAttribute( oLink, 'tabindex' , ( GetE('txtAttTabIndex').value > 0 ? GetE('txtAttTabIndex').value : null ) ) ;
+ SetAttribute( oLink, 'title' , GetE('txtAttTitle').value ) ;
+ SetAttribute( oLink, 'type' , GetE('txtAttContentType').value ) ;
+ SetAttribute( oLink, 'charset' , GetE('txtAttCharSet').value ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ {
+ var sClass = GetE('txtAttClasses').value ;
+ // If it's also an anchor add an internal class
+ if ( GetE('txtAttName').value.length != 0 )
+ sClass += ' FCK__AnchorC' ;
+ SetAttribute( oLink, 'className', sClass ) ;
+
+ oLink.style.cssText = GetE('txtAttStyle').value ;
+ }
+ else
+ {
+ SetAttribute( oLink, 'class', GetE('txtAttClasses').value ) ;
+ SetAttribute( oLink, 'style', GetE('txtAttStyle').value ) ;
+ }
+ }
+
+ // Select the (first) link.
+ oEditor.FCKSelection.SelectNode( aLinks[0] );
+
+ return true ;
+}
+
+function BrowseServer()
+{
+ OpenFileBrowser( FCKConfig.LinkBrowserURL, FCKConfig.LinkBrowserWindowWidth, FCKConfig.LinkBrowserWindowHeight ) ;
+}
+
+function SetUrl( url )
+{
+ GetE('txtUrl').value = url ;
+ OnUrlChange() ;
+ dialog.SetSelectedTab( 'Info' ) ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ // Remove animation
+ window.parent.Throbber.Hide() ;
+ GetE( 'divUpload' ).style.display = '' ;
+
+ switch ( errorNumber )
+ {
+ case 0 : // No errors
+ alert( 'Your file has been successfully uploaded' ) ;
+ break ;
+ case 1 : // Custom error
+ alert( customMsg ) ;
+ return ;
+ case 101 : // Custom warning
+ alert( customMsg ) ;
+ break ;
+ case 201 :
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file type' ) ;
+ return ;
+ case 203 :
+ alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
+ return ;
+ case 500 :
+ alert( 'The connector is disabled' ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ return ;
+ }
+
+ SetUrl( fileUrl ) ;
+ GetE('frmUpload').reset() ;
+}
+
+var oUploadAllowedExtRegex = new RegExp( FCKConfig.LinkUploadAllowedExtensions, 'i' ) ;
+var oUploadDeniedExtRegex = new RegExp( FCKConfig.LinkUploadDeniedExtensions, 'i' ) ;
+
+function CheckUpload()
+{
+ var sFile = GetE('txtUploadFile').value ;
+
+ if ( sFile.length == 0 )
+ {
+ alert( 'Please select a file to upload' ) ;
+ return false ;
+ }
+
+ if ( ( FCKConfig.LinkUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||
+ ( FCKConfig.LinkUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )
+ {
+ OnUploadCompleted( 202 ) ;
+ return false ;
+ }
+
+ // Show animation
+ window.parent.Throbber.Show( 100 ) ;
+ GetE( 'divUpload' ).style.display = 'none' ;
+
+ return true ;
+}
+
+function SetDefaultTarget()
+{
+ var target = FCKConfig.DefaultLinkTarget || '' ;
+
+ if ( oLink || target.length == 0 )
+ return ;
+
+ switch ( target )
+ {
+ case '_blank' :
+ case '_self' :
+ case '_parent' :
+ case '_top' :
+ GetE('cmbTarget').value = target ;
+ break ;
+ default :
+ GetE('cmbTarget').value = 'frame' ;
+ break ;
+ }
+
+ GetE('txtTargetFrame').value = target ;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_listprop.html b/httemplate/elements/fckeditor/editor/dialog/fck_listprop.html
new file mode 100644
index 0000000..a67af18
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_listprop.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Bulleted List dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+var sListType = ( location.search == '?OL' ? 'OL' : 'UL' ) ;
+
+var oActiveEl = dialog.Selection.GetSelection().MoveToAncestorNode( sListType ) ;
+var oActiveSel ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( sListType == 'UL' )
+ oActiveSel = GetE('selBulleted') ;
+ else
+ {
+ if ( oActiveEl )
+ {
+ oActiveSel = GetE('selNumbered') ;
+ GetE('eStart').style.display = '' ;
+ GetE('txtStartPosition').value = GetAttribute( oActiveEl, 'start' ) ;
+ }
+ }
+
+ oActiveSel.style.display = '' ;
+
+ if ( oActiveEl )
+ {
+ if ( oActiveEl.getAttribute('type') )
+ oActiveSel.value = oActiveEl.getAttribute('type') ;
+ }
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+
+ SelectField( sListType == 'OL' ? 'txtStartPosition' : 'selBulleted' ) ;
+}
+
+function Ok()
+{
+ if ( oActiveEl ){
+ SetAttribute( oActiveEl, 'type' , oActiveSel.value ) ;
+ if(oActiveEl.tagName == 'OL')
+ SetAttribute( oActiveEl, 'start', GetE('txtStartPosition').value ) ;
+ }
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table width="100%" style="height: 100%">
+ <tr>
+ <td style="text-align:center">
+ <table cellspacing="0" cellpadding="0" border="0" style="margin-left: auto; margin-right: auto;">
+ <tr>
+ <td id="eStart" style="display: none; padding-right: 5px; padding-left: 5px">
+ <span fcklang="DlgLstStart">Start</span><br />
+ <input type="text" id="txtStartPosition" size="5" />
+ </td>
+ <td style="padding-right: 5px; padding-left: 5px">
+ <span fcklang="DlgLstType">List Type</span><br />
+ <select id="selBulleted" style="display: none">
+ <option value="" selected="selected"></option>
+ <option value="circle" fcklang="DlgLstTypeCircle">Circle</option>
+ <option value="disc" fcklang="DlgLstTypeDisc">Disc</option>
+ <option value="square" fcklang="DlgLstTypeSquare">Square</option>
+ </select>
+ <select id="selNumbered" style="display: none">
+ <option value="" selected="selected"></option>
+ <option value="1" fcklang="DlgLstTypeNumbers">Numbers (1, 2, 3)</option>
+ <option value="a" fcklang="DlgLstTypeLCase">Lowercase Letters (a, b, c)</option>
+ <option value="A" fcklang="DlgLstTypeUCase">Uppercase Letters (A, B, C)</option>
+ <option value="i" fcklang="DlgLstTypeSRoman">Small Roman Numerals (i, ii, iii)</option>
+ <option value="I" fcklang="DlgLstTypeLRoman">Large Roman Numerals (I, II, III)</option>
+ </select>
+ &nbsp;
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_paste.html b/httemplate/elements/fckeditor/editor/dialog/fck_paste.html
new file mode 100644
index 0000000..3e11da1
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_paste.html
@@ -0,0 +1,347 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This dialog is shown when, for some reason (usually security settings),
+ * the user is not able to paste data from the clipboard to the editor using
+ * the toolbar buttons or the context menu.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var FCK = oEditor.FCK;
+var FCKTools = oEditor.FCKTools ;
+var FCKConfig = oEditor.FCKConfig ;
+var FCKBrowserInfo = oEditor.FCKBrowserInfo ;
+
+window.onload = function ()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ var sPastingType = dialog.Args().CustomValue ;
+
+ if ( sPastingType == 'Word' || sPastingType == 'Security' )
+ {
+ if ( sPastingType == 'Security' )
+ document.getElementById( 'xSecurityMsg' ).style.display = '' ;
+
+ // For document.domain compatibility (#123) we must do all the magic in
+ // the URL for IE.
+ var sFrameUrl = !oEditor.FCK_IS_CUSTOM_DOMAIN || !FCKBrowserInfo.IsIE ?
+ 'javascript:void(0)' :
+ 'javascript:void( (function(){' +
+ 'document.open() ;' +
+ 'document.domain=\'' + document.domain + '\' ;' +
+ 'document.write(\'<html><head><scr' + 'ipt>window.onerror = function() { return true ; };<\/script><\/head><body><\/body><\/html>\') ;' +
+ 'document.close() ;' +
+ 'document.body.contentEditable = true ;' +
+ 'window.focus() ;' +
+ '})() )' ;
+
+ var eFrameSpace = document.getElementById( 'xFrameSpace' ) ;
+ eFrameSpace.innerHTML = '<iframe id="frmData" src="' + sFrameUrl + '" ' +
+ 'height="98%" width="99%" frameborder="0" style="border: #000000 1px; background-color: #ffffff"><\/iframe>' ;
+
+ var oFrame = eFrameSpace.firstChild ;
+
+ if ( !oEditor.FCK_IS_CUSTOM_DOMAIN || !FCKBrowserInfo.IsIE )
+ {
+ // Avoid errors if the pasted content has any script that fails: #389
+ var oDoc = oFrame.contentWindow.document ;
+ oDoc.open() ;
+ oDoc.write('<html><head><scr' + 'ipt>window.onerror = function() { return true ; };<\/script><\/head><body><\/body><\/html>') ;
+ oDoc.close() ;
+
+ if ( FCKBrowserInfo.IsIE )
+ oDoc.body.contentEditable = true ;
+ else
+ oDoc.designMode = 'on' ;
+
+ oFrame.contentWindow.focus();
+ }
+ }
+ else
+ {
+ document.getElementById('txtData').style.display = '' ;
+ SelectField( 'txtData' ) ;
+ }
+
+ if ( sPastingType != 'Word' )
+ document.getElementById('oWordCommands').style.display = 'none' ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+}
+
+function Ok()
+{
+ // Before doing anything, save undo snapshot.
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ var sHtml ;
+
+ var sPastingType = dialog.Args().CustomValue ;
+
+ if ( sPastingType == 'Word' || sPastingType == 'Security' )
+ {
+ var oFrame = document.getElementById('frmData') ;
+ var oBody ;
+
+ if ( oFrame.contentDocument )
+ oBody = oFrame.contentDocument.body ;
+ else
+ oBody = oFrame.contentWindow.document.body ;
+
+ if ( sPastingType == 'Word' )
+ {
+ // If a plugin creates a FCK.CustomCleanWord function it will be called instead of the default one
+ if ( typeof( FCK.CustomCleanWord ) == 'function' )
+ sHtml = FCK.CustomCleanWord( oBody, document.getElementById('chkRemoveFont').checked, document.getElementById('chkRemoveStyles').checked ) ;
+ else
+ sHtml = CleanWord( oBody, document.getElementById('chkRemoveFont').checked, document.getElementById('chkRemoveStyles').checked ) ;
+ }
+ else
+ sHtml = oBody.innerHTML ;
+
+ // Fix relative anchor URLs (IE automatically adds the current page URL).
+ var re = new RegExp( window.location + "#", "g" ) ;
+ sHtml = sHtml.replace( re, '#') ;
+ }
+ else
+ {
+ sHtml = oEditor.FCKTools.HTMLEncode( document.getElementById('txtData').value ) ;
+ sHtml = FCKTools.ProcessLineBreaks( oEditor, FCKConfig, sHtml ) ;
+
+ // FCK.InsertHtml() does not work for us, since document fragments cannot contain node fragments. :(
+ // Use the marker method instead. It's primitive, but it works.
+ var range = new oEditor.FCKDomRange( oEditor.FCK.EditorWindow ) ;
+ var oDoc = oEditor.FCK.EditorDocument ;
+ dialog.Selection.EnsureSelection() ;
+ range.MoveToSelection() ;
+ range.DeleteContents() ;
+ var marker = [] ;
+ for ( var i = 0 ; i < 5 ; i++ )
+ marker.push( parseInt(Math.random() * 100000, 10 ) ) ;
+ marker = marker.join( "" ) ;
+ range.InsertNode ( oDoc.createTextNode( marker ) ) ;
+ var bookmark = range.CreateBookmark() ;
+
+ // Now we've got a marker indicating the paste position in the editor document.
+ // Find its position in the HTML code.
+ var htmlString = oDoc.body.innerHTML ;
+ var index = htmlString.indexOf( marker ) ;
+
+ // Split it the HTML code up, add the code we generated, and put them back together.
+ var htmlList = [] ;
+ htmlList.push( htmlString.substr( 0, index ) ) ;
+ htmlList.push( sHtml ) ;
+ htmlList.push( htmlString.substr( index + marker.length ) ) ;
+ htmlString = htmlList.join( "" ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ oEditor.FCK.SetInnerHtml( htmlString ) ;
+ else
+ oDoc.body.innerHTML = htmlString ;
+
+ range.MoveToBookmark( bookmark ) ;
+ range.Collapse( false ) ;
+ range.Select() ;
+ range.Release() ;
+ return true ;
+ }
+
+ oEditor.FCK.InsertHtml( sHtml ) ;
+
+ return true ;
+}
+
+// This function will be called from the PasteFromWord dialog (fck_paste.html)
+// Input: oNode a DOM node that contains the raw paste from the clipboard
+// bIgnoreFont, bRemoveStyles booleans according to the values set in the dialog
+// Output: the cleaned string
+function CleanWord( oNode, bIgnoreFont, bRemoveStyles )
+{
+ var html = oNode.innerHTML ;
+
+ html = html.replace(/<o:p>\s*<\/o:p>/g, '') ;
+ html = html.replace(/<o:p>[\s\S]*?<\/o:p>/g, '&nbsp;') ;
+
+ // Remove mso-xxx styles.
+ html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '' ) ;
+
+ // Remove margin styles.
+ html = html.replace( /\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*;/gi, '' ) ;
+ html = html.replace( /\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*"/gi, "\"" ) ;
+
+ html = html.replace( /\s*TEXT-INDENT: 0(?:cm|in)\s*;/gi, '' ) ;
+ html = html.replace( /\s*TEXT-INDENT: 0(?:cm|in)\s*"/gi, "\"" ) ;
+
+ html = html.replace( /\s*TEXT-ALIGN: [^\s;]+;?"/gi, "\"" ) ;
+
+ html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"" ) ;
+
+ html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ) ;
+
+ html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '' ) ;
+ html = html.replace( /\s*tab-stops:[^"]*/gi, '' ) ;
+
+ // Remove FONT face attributes.
+ if ( bIgnoreFont )
+ {
+ html = html.replace( /\s*face="[^"]*"/gi, '' ) ;
+ html = html.replace( /\s*face=[^ >]*/gi, '' ) ;
+
+ html = html.replace( /\s*FONT-FAMILY:[^;"]*;?/gi, '' ) ;
+ }
+
+ // Remove Class attributes
+ html = html.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3") ;
+
+ // Remove styles.
+ if ( bRemoveStyles )
+ html = html.replace( /<(\w[^>]*) style="([^\"]*)"([^>]*)/gi, "<$1$3" ) ;
+
+ // Remove style, meta and link tags
+ html = html.replace( /<STYLE[^>]*>[\s\S]*?<\/STYLE[^>]*>/gi, '' ) ;
+ html = html.replace( /<(?:META|LINK)[^>]*>\s*/gi, '' ) ;
+
+ // Remove empty styles.
+ html = html.replace( /\s*style="\s*"/gi, '' ) ;
+
+ html = html.replace( /<SPAN\s*[^>]*>\s*&nbsp;\s*<\/SPAN>/gi, '&nbsp;' ) ;
+
+ html = html.replace( /<SPAN\s*[^>]*><\/SPAN>/gi, '' ) ;
+
+ // Remove Lang attributes
+ html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3") ;
+
+ html = html.replace( /<SPAN\s*>([\s\S]*?)<\/SPAN>/gi, '$1' ) ;
+
+ html = html.replace( /<FONT\s*>([\s\S]*?)<\/FONT>/gi, '$1' ) ;
+
+ // Remove XML elements and declarations
+ html = html.replace(/<\\?\?xml[^>]*>/gi, '' ) ;
+
+ // Remove w: tags with contents.
+ html = html.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '' ) ;
+
+ // Remove Tags with XML namespace declarations: <o:p><\/o:p>
+ html = html.replace(/<\/?\w+:[^>]*>/gi, '' ) ;
+
+ // Remove comments [SF BUG-1481861].
+ html = html.replace(/<\!--[\s\S]*?-->/g, '' ) ;
+
+ html = html.replace( /<(U|I|STRIKE)>&nbsp;<\/\1>/g, '&nbsp;' ) ;
+
+ html = html.replace( /<H\d>\s*<\/H\d>/gi, '' ) ;
+
+ // Remove "display:none" tags.
+ html = html.replace( /<(\w+)[^>]*\sstyle="[^"]*DISPLAY\s?:\s?none[\s\S]*?<\/\1>/ig, '' ) ;
+
+ // Remove language tags
+ html = html.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3") ;
+
+ // Remove onmouseover and onmouseout events (from MS Word comments effect)
+ html = html.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3") ;
+ html = html.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3") ;
+
+ if ( FCKConfig.CleanWordKeepsStructure )
+ {
+ // The original <Hn> tag send from Word is something like this: <Hn style="margin-top:0px;margin-bottom:0px">
+ html = html.replace( /<H(\d)([^>]*)>/gi, '<h$1>' ) ;
+
+ // Word likes to insert extra <font> tags, when using MSIE. (Wierd).
+ html = html.replace( /<(H\d)><FONT[^>]*>([\s\S]*?)<\/FONT><\/\1>/gi, '<$1>$2<\/$1>' );
+ html = html.replace( /<(H\d)><EM>([\s\S]*?)<\/EM><\/\1>/gi, '<$1>$2<\/$1>' );
+ }
+ else
+ {
+ html = html.replace( /<H1([^>]*)>/gi, '<div$1><b><font size="6">' ) ;
+ html = html.replace( /<H2([^>]*)>/gi, '<div$1><b><font size="5">' ) ;
+ html = html.replace( /<H3([^>]*)>/gi, '<div$1><b><font size="4">' ) ;
+ html = html.replace( /<H4([^>]*)>/gi, '<div$1><b><font size="3">' ) ;
+ html = html.replace( /<H5([^>]*)>/gi, '<div$1><b><font size="2">' ) ;
+ html = html.replace( /<H6([^>]*)>/gi, '<div$1><b><font size="1">' ) ;
+
+ html = html.replace( /<\/H\d>/gi, '<\/font><\/b><\/div>' ) ;
+
+ // Transform <P> to <DIV>
+ var re = new RegExp( '(<P)([^>]*>[\\s\\S]*?)(<\/P>)', 'gi' ) ; // Different because of a IE 5.0 error
+ html = html.replace( re, '<div$2<\/div>' ) ;
+
+ // Remove empty tags (three times, just to be sure).
+ // This also removes any empty anchor
+ html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;
+ html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;
+ html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;
+ }
+
+ return html ;
+}
+
+ </script>
+
+</head>
+<body style="overflow: hidden">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 98%">
+ <tr>
+ <td>
+ <div id="xSecurityMsg" style="display: none">
+ <span fcklang="DlgPasteSec">Because of your browser security settings,
+ the editor is not able to access your clipboard data directly. You are required
+ to paste it again in this window.</span><br />
+ &nbsp;
+ </div>
+ <div>
+ <span fcklang="DlgPasteMsg2">Please paste inside the following box using the keyboard
+ (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.</span><br />
+ &nbsp;
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td id="xFrameSpace" valign="top" height="100%" style="border: #000000 1px solid">
+ <textarea id="txtData" cols="80" rows="5" style="border: #000000 1px; display: none;
+ width: 99%; height: 98%"></textarea>
+ </td>
+ </tr>
+ <tr id="oWordCommands">
+ <td>
+
+ <input id="chkRemoveFont" type="checkbox" checked="checked" />
+ <label for="chkRemoveFont" fcklang="DlgPasteIgnoreFont">
+ Ignore Font Face definitions</label>
+ <br />
+ <input id="chkRemoveStyles" type="checkbox" />
+ <label for="chkRemoveStyles" fcklang="DlgPasteRemoveStyles">
+ Remove Styles definitions</label>
+
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html b/httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html
new file mode 100644
index 0000000..556890f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Radio Button dialog window.
+-->
+<html>
+ <head>
+ <title>Radio Button Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl && oActiveEl.tagName.toUpperCase() == 'INPUT' && oActiveEl.type == 'radio' )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtValue').value = oEditor.FCKBrowserInfo.IsIE ? oActiveEl.value : GetAttribute( oActiveEl, 'value' ) ;
+ GetE('txtSelected').checked = oActiveEl.checked ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'INPUT', {name: GetE('txtName').value, type: 'radio' } ) ;
+
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ oActiveEl.value = GetE('txtValue').value ;
+ else
+ SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;
+
+ var bIsChecked = GetE('txtSelected').checked ;
+ SetAttribute( oActiveEl, 'checked', bIsChecked ? 'checked' : null ) ; // For Firefox
+ oActiveEl.checked = bIsChecked ;
+
+ return true ;
+}
+
+ </script>
+ </head>
+ <body style="OVERFLOW: hidden" scroll="no">
+ <table height="100%" width="100%">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td>
+ <span fckLang="DlgCheckboxName">Name</span><br>
+ <input type="text" size="20" id="txtName" style="WIDTH: 100%">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fckLang="DlgCheckboxValue">Value</span><br>
+ <input type="text" size="20" id="txtValue" style="WIDTH: 100%">
+ </td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" id="txtSelected"><label for="txtSelected" fckLang="DlgCheckboxSelected">Checked</label></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_replace.html b/httemplate/elements/fckeditor/editor/dialog/fck_replace.html
new file mode 100644
index 0000000..2c574d2
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_replace.html
@@ -0,0 +1,650 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * "Find" and "Replace" dialog box window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var dialogArguments = dialog.Args() ;
+
+var FCKLang = oEditor.FCKLang ;
+var FCKDomTools = oEditor.FCKDomTools ;
+var FCKDomRange = oEditor.FCKDomRange ;
+var FCKListsLib = oEditor.FCKListsLib ;
+var FCKTools = oEditor.FCKTools ;
+var EditorDocument = oEditor.FCK.EditorDocument ;
+var HighlightStyle = oEditor.FCKStyles.GetStyle( '_FCK_SelectionHighlight' ) ;
+
+dialog.AddTab( 'Find', FCKLang.DlgFindTitle ) ;
+dialog.AddTab( 'Replace', FCKLang.DlgReplaceTitle ) ;
+var idMap = {} ;
+
+function OnDialogTabChange( tabCode )
+{
+ ShowE( 'divFind', ( tabCode == 'Find' ) ) ;
+ ShowE( 'divReplace', ( tabCode == 'Replace' ) ) ;
+ idMap['FindText'] = 'txtFind' + tabCode ;
+ idMap['CheckCase'] = 'chkCase' + tabCode ;
+ idMap['CheckWord'] = 'chkWord' + tabCode ;
+
+ if ( tabCode == 'Replace' )
+ dialog.SetAutoSize( true ) ;
+}
+
+GetNextNonEmptyTextNode = function( node, stopNode )
+{
+ while ( ( node = FCKDomTools.GetNextSourceNode( node, false, 3, stopNode ) ) && node && node.length < 1 )
+ 1 ;
+ return node ;
+}
+
+CharacterCursor = function( arg )
+{
+ if ( arg.nodeType && arg.nodeType == 9 )
+ {
+ this._textNode = GetNextNonEmptyTextNode( arg.body, arg.documentElement ) ;
+ this._offset = 0 ;
+ this._doc = arg ;
+ }
+ else
+ {
+ this._textNode = arguments[0] ;
+ this._offset = arguments[1] ;
+ this._doc = FCKTools.GetElementDocument( arguments[0] ) ;
+ }
+}
+CharacterCursor.prototype =
+{
+ GetCharacter : function()
+ {
+ return ( this._textNode && this._textNode.nodeValue.charAt( this._offset ) ) || null ;
+ },
+
+ // Non-normalized.
+ GetTextNode : function()
+ {
+ return this._textNode ;
+ },
+
+ // Non-normalized.
+ GetIndex : function()
+ {
+ return this._offset ;
+ },
+
+ // Return value means whehther we've crossed a line break or a paragraph boundary.
+ MoveNext : function()
+ {
+ if ( this._offset < this._textNode.length - 1 )
+ {
+ this._offset++ ;
+ return false ;
+ }
+
+ var crossed = false ;
+ var curNode = this._textNode ;
+ while ( ( curNode = FCKDomTools.GetNextSourceNode( curNode ) )
+ && curNode && ( curNode.nodeType != 3 || curNode.length < 1 ) )
+ {
+ var tag = curNode.nodeName.toLowerCase() ;
+ if ( FCKListsLib.BlockElements[tag] || tag == 'br' )
+ crossed = true ;
+ }
+
+ this._textNode = curNode ;
+ this._offset = 0 ;
+ return crossed ;
+ },
+
+ // Return value means whehther we've crossed a line break or a paragraph boundary.
+ MoveBack : function()
+ {
+ if ( this._offset > 0 && this._textNode.length > 0 )
+ {
+ this._offset = Math.min( this._offset - 1, this._textNode.length - 1 ) ;
+ return false ;
+ }
+
+ var crossed = false ;
+ var curNode = this._textNode ;
+ while ( ( curNode = FCKDomTools.GetPreviousSourceNode( curNode ) )
+ && curNode && ( curNode.nodeType != 3 || curNode.length < 1 ) )
+ {
+ var tag = curNode.nodeName.toLowerCase() ;
+ if ( FCKListsLib.BlockElements[tag] || tag == 'br' )
+ crossed = true ;
+ }
+
+ this._textNode = curNode ;
+ this._offset = curNode && curNode.length - 1 ;
+ return crossed ;
+ },
+
+ Clone : function()
+ {
+ return new CharacterCursor( this._textNode, this._offset ) ;
+ }
+} ;
+
+CharacterRange = function( initCursor, maxLength )
+{
+ this._cursors = initCursor.push ? initCursor : [initCursor] ;
+ this._maxLength = maxLength ;
+ this._highlightRange = null ;
+}
+CharacterRange.prototype =
+{
+ ToDomRange : function()
+ {
+ var firstCursor = this._cursors[0] ;
+ var lastCursor = this._cursors[ this._cursors.length - 1 ] ;
+ var domRange = new FCKDomRange( FCKTools.GetElementWindow( firstCursor.GetTextNode() ) ) ;
+ var w3cRange = domRange._Range = domRange.CreateRange() ;
+ w3cRange.setStart( firstCursor.GetTextNode(), firstCursor.GetIndex() ) ;
+ w3cRange.setEnd( lastCursor.GetTextNode(), lastCursor.GetIndex() + 1 ) ;
+ domRange._UpdateElementInfo() ;
+ return domRange ;
+ },
+
+ Highlight : function()
+ {
+ if ( this._cursors.length < 1 )
+ return ;
+
+ var domRange = this.ToDomRange() ;
+ HighlightStyle.ApplyToRange( domRange, false, true ) ;
+ this._highlightRange = domRange ;
+
+ var charRange = CharacterRange.CreateFromDomRange( domRange ) ;
+ var focusNode = domRange.StartNode ;
+ if ( focusNode.nodeType != 1 )
+ focusNode = focusNode.parentNode ;
+ FCKDomTools.ScrollIntoView( focusNode, false ) ;
+ this._cursors = charRange._cursors ;
+ },
+
+ RemoveHighlight : function()
+ {
+ if ( this._highlightRange )
+ {
+ HighlightStyle.RemoveFromRange( this._highlightRange, false, true ) ;
+ var charRange = CharacterRange.CreateFromDomRange( this._highlightRange ) ;
+ this._cursors = charRange._cursors ;
+ this._highlightRange = null ;
+ }
+ },
+
+ GetHighlightDomRange : function()
+ {
+ return this._highlightRange;
+ },
+
+ MoveNext : function()
+ {
+ var next = this._cursors[ this._cursors.length - 1 ].Clone() ;
+ var retval = next.MoveNext() ;
+ if ( retval )
+ this._cursors = [] ;
+ this._cursors.push( next ) ;
+ if ( this._cursors.length > this._maxLength )
+ this._cursors.shift() ;
+ return retval ;
+ },
+
+ MoveBack : function()
+ {
+ var prev = this._cursors[0].Clone() ;
+ var retval = prev.MoveBack() ;
+ if ( retval )
+ this._cursors = [] ;
+ this._cursors.unshift( prev ) ;
+ if ( this._cursors.length > this._maxLength )
+ this._cursors.pop() ;
+ return retval ;
+ },
+
+ GetEndCharacter : function()
+ {
+ if ( this._cursors.length < 1 )
+ return null ;
+ var retval = this._cursors[ this._cursors.length - 1 ].GetCharacter() ;
+ return retval ;
+ },
+
+ GetNextRange : function( len )
+ {
+ if ( this._cursors.length == 0 )
+ return null ;
+ var cur = this._cursors[ this._cursors.length - 1 ].Clone() ;
+ cur.MoveNext() ;
+ return new CharacterRange( cur, len ) ;
+ },
+
+ GetCursors : function()
+ {
+ return this._cursors ;
+ }
+} ;
+
+CharacterRange.CreateFromDomRange = function( domRange )
+{
+ var w3cRange = domRange._Range ;
+ var startContainer = w3cRange.startContainer ;
+ var endContainer = w3cRange.endContainer ;
+ var startTextNode, startIndex, endTextNode, endIndex ;
+
+ if ( startContainer.nodeType == 3 )
+ {
+ startTextNode = startContainer ;
+ startIndex = w3cRange.startOffset ;
+ }
+ else if ( domRange.StartNode.nodeType == 3 )
+ {
+ startTextNode = domRange.StartNode ;
+ startIndex = 0 ;
+ }
+ else
+ {
+ startTextNode = GetNextNonEmptyTextNode( domRange.StartNode, domRange.StartNode.parentNode ) ;
+ if ( !startTextNode )
+ return null ;
+ startIndex = 0 ;
+ }
+
+ if ( endContainer.nodeType == 3 && w3cRange.endOffset > 0 )
+ {
+ endTextNode = endContainer ;
+ endIndex = w3cRange.endOffset - 1 ;
+ }
+ else
+ {
+ endTextNode = domRange.EndNode ;
+ while ( endTextNode.nodeType != 3 )
+ endTextNode = endTextNode.lastChild ;
+ endIndex = endTextNode.length - 1 ;
+ }
+
+ var cursors = [] ;
+ var current = new CharacterCursor( startTextNode, startIndex ) ;
+ cursors.push( current ) ;
+ if ( !( current.GetTextNode() == endTextNode && current.GetIndex() == endIndex ) && !domRange.CheckIsEmpty() )
+ {
+ do
+ {
+ current = current.Clone() ;
+ current.MoveNext() ;
+ cursors.push( current ) ;
+ }
+ while ( !( current.GetTextNode() == endTextNode && current.GetIndex() == endIndex ) ) ;
+ }
+
+ return new CharacterRange( cursors, cursors.length ) ;
+}
+
+// Knuth-Morris-Pratt Algorithm for stream input
+KMP_NOMATCH = 0 ;
+KMP_ADVANCED = 1 ;
+KMP_MATCHED = 2 ;
+KmpMatch = function( pattern, ignoreCase )
+{
+ var overlap = [ -1 ] ;
+ for ( var i = 0 ; i < pattern.length ; i++ )
+ {
+ overlap.push( overlap[i] + 1 ) ;
+ while ( overlap[ i + 1 ] > 0 && pattern.charAt( i ) != pattern.charAt( overlap[ i + 1 ] - 1 ) )
+ overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1 ;
+ }
+ this._Overlap = overlap ;
+ this._State = 0 ;
+ this._IgnoreCase = ( ignoreCase === true ) ;
+ if ( ignoreCase )
+ this.Pattern = pattern.toLowerCase();
+ else
+ this.Pattern = pattern ;
+}
+KmpMatch.prototype = {
+ FeedCharacter : function( c )
+ {
+ if ( this._IgnoreCase )
+ c = c.toLowerCase();
+
+ while ( true )
+ {
+ if ( c == this.Pattern.charAt( this._State ) )
+ {
+ this._State++ ;
+ if ( this._State == this.Pattern.length )
+ {
+ // found a match, start over, don't care about partial matches involving the current match
+ this._State = 0;
+ return KMP_MATCHED;
+ }
+ return KMP_ADVANCED ;
+ }
+ else if ( this._State == 0 )
+ return KMP_NOMATCH;
+ else
+ this._State = this._Overlap[ this._State ];
+ }
+
+ return null ;
+ },
+
+ Reset : function()
+ {
+ this._State = 0 ;
+ }
+};
+
+// Place a range at the start of document.
+function OnLoad()
+{
+ // First of all, translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage( document ) ;
+
+ // Show the appropriate tab at startup.
+ if ( dialogArguments.CustomValue == 'Find' )
+ {
+ dialog.SetSelectedTab( 'Find' ) ;
+ dialog.SetAutoSize( true ) ;
+ }
+ else
+ dialog.SetSelectedTab( 'Replace' ) ;
+
+ SelectField( 'txtFind' + dialogArguments.CustomValue ) ;
+}
+
+function btnStat()
+{
+ GetE('btnReplace').disabled =
+ GetE('btnReplaceAll').disabled =
+ GetE('btnFind').disabled =
+ ( GetE(idMap["FindText"]).value.length == 0 ) ;
+}
+
+function btnStatDelayed()
+{
+ setTimeout( btnStat, 1 ) ;
+}
+
+function GetSearchString()
+{
+ return GetE(idMap['FindText']).value ;
+}
+
+function GetReplaceString()
+{
+ return GetE("txtReplace").value ;
+}
+
+function GetCheckCase()
+{
+ return !! ( GetE(idMap['CheckCase']).checked ) ;
+}
+
+function GetMatchWord()
+{
+ return !! ( GetE(idMap['CheckWord']).checked ) ;
+}
+
+/* Is this character a unicode whitespace or a punctuation mark?
+ * References:
+ * http://unicode.org/Public/UNIDATA/PropList.txt (whitespaces)
+ * http://php.chinaunix.net/manual/tw/ref.regex.php (punctuation marks)
+ */
+function CheckIsWordSeparator( c )
+{
+ if ( !c )
+ return true;
+ var code = c.charCodeAt( 0 );
+ if ( code >= 9 && code <= 0xd )
+ return true;
+ if ( code >= 0x2000 && code <= 0x200a )
+ return true;
+ switch ( code )
+ {
+ case 0x20:
+ case 0x85:
+ case 0xa0:
+ case 0x1680:
+ case 0x180e:
+ case 0x2028:
+ case 0x2029:
+ case 0x202f:
+ case 0x205f:
+ case 0x3000:
+ return true;
+ default:
+ }
+ return /[.,"'?!;:]/.test( c ) ;
+}
+
+FindRange = null ;
+function _Find()
+{
+ var searchString = GetSearchString() ;
+ if ( !FindRange )
+ FindRange = new CharacterRange( new CharacterCursor( EditorDocument ), searchString.length ) ;
+ else
+ {
+ FindRange.RemoveHighlight() ;
+ FindRange = FindRange.GetNextRange( searchString.length ) ;
+ }
+ var matcher = new KmpMatch( searchString, ! GetCheckCase() ) ;
+ var matchState = KMP_NOMATCH ;
+ var character = '%' ;
+
+ while ( character != null )
+ {
+ while ( ( character = FindRange.GetEndCharacter() ) )
+ {
+ matchState = matcher.FeedCharacter( character ) ;
+ if ( matchState == KMP_MATCHED )
+ break ;
+ if ( FindRange.MoveNext() )
+ matcher.Reset() ;
+ }
+
+ if ( matchState == KMP_MATCHED )
+ {
+ if ( GetMatchWord() )
+ {
+ var cursors = FindRange.GetCursors() ;
+ var head = cursors[ cursors.length - 1 ].Clone() ;
+ var tail = cursors[0].Clone() ;
+ if ( !head.MoveNext() && !CheckIsWordSeparator( head.GetCharacter() ) )
+ continue ;
+ if ( !tail.MoveBack() && !CheckIsWordSeparator( tail.GetCharacter() ) )
+ continue ;
+ }
+
+ FindRange.Highlight() ;
+ return true ;
+ }
+ }
+
+ FindRange = null ;
+ return false ;
+}
+
+function Find()
+{
+ if ( ! _Find() )
+ alert( FCKLang.DlgFindNotFoundMsg ) ;
+}
+
+function Replace()
+{
+ var saveUndoStep = function( selectRange )
+ {
+ var ieRange ;
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ ieRange = document.selection.createRange() ;
+
+ selectRange.Select() ;
+ oEditor.FCKUndo.SaveUndoStep() ;
+ var cloneRange = selectRange.Clone() ;
+ cloneRange.Collapse( false ) ;
+ cloneRange.Select() ;
+
+ if ( ieRange )
+ setTimeout( function(){ ieRange.select() ; }, 1 ) ;
+ }
+
+ if ( FindRange && FindRange.GetHighlightDomRange() )
+ {
+ var range = FindRange.GetHighlightDomRange() ;
+ var bookmark = range.CreateBookmark() ;
+ FindRange.RemoveHighlight() ;
+ range.MoveToBookmark( bookmark ) ;
+
+ saveUndoStep( range ) ;
+ range.DeleteContents() ;
+ range.InsertNode( EditorDocument.createTextNode( GetReplaceString() ) ) ;
+ range._UpdateElementInfo() ;
+
+ FindRange = CharacterRange.CreateFromDomRange( range ) ;
+ }
+ else
+ {
+ if ( ! _Find() )
+ {
+ FindRange && FindRange.RemoveHighlight() ;
+ alert( FCKLang.DlgFindNotFoundMsg ) ;
+ }
+ }
+}
+
+function ReplaceAll()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+ var replaceCount = 0 ;
+
+ while ( _Find() )
+ {
+ var range = FindRange.GetHighlightDomRange() ;
+ var bookmark = range.CreateBookmark() ;
+ FindRange.RemoveHighlight() ;
+ range.MoveToBookmark( bookmark) ;
+
+ range.DeleteContents() ;
+ range.InsertNode( EditorDocument.createTextNode( GetReplaceString() ) ) ;
+ range._UpdateElementInfo() ;
+
+ FindRange = CharacterRange.CreateFromDomRange( range ) ;
+ replaceCount++ ;
+ }
+ if ( replaceCount == 0 )
+ {
+ FindRange && FindRange.RemoveHighlight() ;
+ alert( FCKLang.DlgFindNotFoundMsg ) ;
+ }
+ dialog.Cancel() ;
+}
+
+window.onunload = function()
+{
+ if ( FindRange )
+ {
+ FindRange.RemoveHighlight() ;
+ FindRange.ToDomRange().Select() ;
+ }
+}
+ </script>
+</head>
+<body onload="OnLoad()" style="overflow: hidden">
+ <div id="divFind" style="display: none">
+ <table cellspacing="3" cellpadding="2" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <label for="txtFindFind" fcklang="DlgReplaceFindLbl">
+ Find what:</label>
+ </td>
+ <td width="100%">
+ <input id="txtFindFind" onkeyup="btnStat()" oninput="btnStat()" onpaste="btnStatDelayed()" style="width: 100%" tabindex="1"
+ type="text" />
+ </td>
+ <td>
+ <input id="btnFind" style="width: 80px" disabled="disabled" onclick="Find();"
+ type="button" value="Find" fcklang="DlgFindFindBtn" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="bottom" colspan="3">
+ &nbsp;<input id="chkCaseFind" tabindex="3" type="checkbox" /><label for="chkCaseFind" fcklang="DlgReplaceCaseChk">Match
+ case</label>
+ <br />
+ &nbsp;<input id="chkWordFind" tabindex="4" type="checkbox" /><label for="chkWordFind" fcklang="DlgReplaceWordChk">Match
+ whole word</label>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="divReplace" style="display:none">
+ <table cellspacing="3" cellpadding="2" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <label for="txtFindReplace" fcklang="DlgReplaceFindLbl">
+ Find what:</label>
+ </td>
+ <td width="100%">
+ <input id="txtFindReplace" onkeyup="btnStat()" oninput="btnStat()" onpaste="btnStatDelayed()" style="width: 100%" tabindex="1"
+ type="text" />
+ </td>
+ <td>
+ <input id="btnReplace" style="width: 80px" disabled="disabled" onclick="Replace();"
+ type="button" value="Replace" fcklang="DlgReplaceReplaceBtn" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="top" nowrap="nowrap">
+ <label for="txtReplace" fcklang="DlgReplaceReplaceLbl">
+ Replace with:</label>
+ </td>
+ <td valign="top">
+ <input id="txtReplace" style="width: 100%" tabindex="2" type="text" />
+ </td>
+ <td>
+ <input id="btnReplaceAll" style="width: 80px" disabled="disabled" onclick="ReplaceAll()" type="button"
+ value="Replace All" fcklang="DlgReplaceReplAllBtn" />
+ </td>
+ </tr>
+ <tr>
+ <td valign="bottom" colspan="3">
+ &nbsp;<input id="chkCaseReplace" tabindex="3" type="checkbox" /><label for="chkCaseReplace" fcklang="DlgReplaceCaseChk">Match
+ case</label>
+ <br />
+ &nbsp;<input id="chkWordReplace" tabindex="4" type="checkbox" /><label for="chkWordReplace" fcklang="DlgReplaceWordChk">Match
+ whole word</label>
+ </td>
+ </tr>
+ </table>
+ </div>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_scayt.html b/httemplate/elements/fckeditor/editor/dialog/fck_scayt.html
new file mode 100644
index 0000000..f8ef9b4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_scayt.html
@@ -0,0 +1,746 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+-->
+<html>
+ <head>
+ <title>SCAYT Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <link type="text/css" href="fck_scayt/scayt_dialog.css" rel="stylesheet" />
+ <script type="text/javascript">
+
+ var dialog = window.parent ;
+ var oEditor = dialog.InnerDialogLoaded() ;
+ var FCKLang = oEditor.FCKLang;
+ var scayt = oEditor.scayt;
+ var scayt_control = oEditor.scayt_control;
+ var lang_list = {};
+ var sLang;
+ var fckLang;
+ var chosed_lang;
+ var options;
+ var tabs = scayt_control.uiTags || [1,1,0,1];
+ var userDicActive = tabs[2] == 1;
+ var captions;
+ var dic_buttons = [
+ // [0] contains buttons for creating
+ "dic_create,dic_restore",
+ // [1] contains buton for manipulation
+ "dic_rename,dic_delete"
+ ];
+
+ var get =
+ new function(){
+
+ var mergeObjs = function(obj1, obj2)
+ {
+ for (var k in obj1)
+ obj2[k] = obj1[k];
+
+ return obj2;
+ };
+
+ var removeWhitespaces = function( s )
+ {
+ s = s.replace( new RegExp("^ +| +$"), '' ) ;
+ return s ;
+ };
+
+ var addEvent = function( el ,sEventName, fTodo )
+ {
+ if (el.addEventListener) {
+ el.addEventListener (sEventName,fTodo,false);
+
+ } else if (el.attachEvent) {
+ el.attachEvent ("on"+sEventName,fTodo);
+
+ } else {
+ el["on"+sEventName] = fTodo;
+ }
+ };
+
+ var getElementsByClassName = function (node,classname ,strTag) {
+ strTag = strTag || "*";
+ node = node || document;
+ if (node.getElementsByClassName)
+ return node.getElementsByClassName(classname);
+ else {
+ var objColl = node.getElementsByTagName(strTag);
+ if (!objColl.length && strTag == "*" && node.all) objColl = node.all;
+ var arr = new Array();
+ var delim = classname.indexOf('|') != -1 ? '|' : ' ';
+ var arrClass = classname.split(delim);
+ for (var i = 0, j = objColl.length; i < j; i++) {
+ var arrObjClass = objColl[i].className.split(' ');
+ if (delim == ' ' && arrClass.length > arrObjClass.length) continue;
+ var c = 0;
+ comparisonLoop:
+ for ( var k = 0, l = arrObjClass.length ; k < l ; k++ )
+ {
+ for ( var m = 0, n = arrClass.length ; m < n ; m++ )
+ {
+ if ( arrClass[m] == arrObjClass[k] )
+ c++ ;
+
+ if ( ( delim == '|' && c == 1 ) || ( delim == ' ' && c == arrClass.length ) )
+ {
+ arr.push( objColl[i] ) ;
+ break comparisonLoop ;
+ }
+ }
+ }
+ }
+ return arr;
+ }
+ };
+
+ var hasClassName = function ( sClassName, elem ) {
+ //.split(/\s+/);
+ var aCnames = elem.className.split(/\s+/) || [];
+ for (var i=0, l=aCnames.length; i<l ; i++){
+ if (sClassName == aCnames[i])
+ return true;
+ }
+ return false;
+ }
+
+ var single = {
+ addClass : function ( sClassName ) {
+ //console.info( sClassName, this.className, );
+ if ( hasClassName(sClassName , this) )
+ return this;
+ var s = removeWhitespaces(this.className + " " +sClassName);
+ this.className = s;
+ return this;
+
+ },
+ removeClass : function ( sClassName ) {
+ var s = removeWhitespaces(this.className.replace(sClassName,""));
+ this.className = s;
+ return this;
+ },
+ setStyle : function( oStyles )
+ {
+ for ( var style in oStyles )
+ {
+ this.style[style] = oStyles[style] ;
+ }
+ return this ;
+ },
+ bindOnclick : function ( handler ) {
+ //addEvent( this, "click" , handler);
+ this.onclick = handler;
+ return this;
+ },
+ bindOnchange : function ( handler ) {
+ //addEvent( this, "change" , handler);
+ this.onchange = handler;
+ return this;
+ },
+ getAttr : function ( sAttrName )
+ {
+ if ( !sAttrName )
+ return null;
+
+ return this[sAttrName];
+ },
+ setAttr : function ( sAttrName , attrVal )
+ {
+ if ( !sAttrName || !attrVal )
+ return null;
+
+ this[sAttrName] = attrVal;
+
+ return this;
+ },
+ remAttr : function ( sAttrName )
+ {
+ if ( !sAttrName )
+ return null;
+ }
+ };
+
+ var singleCaller = function ( sMethod,args ) {
+ for ( var i=0, l=this.length; i<l ; i++ ){
+ var oItem = mergeObjs( single, this[i] );
+ oItem[sMethod].apply(this[i],args);
+ }
+ };
+
+
+ var collection = {
+
+ addClass : function ( sClassName ){
+ singleCaller.call(this, "addClass", [sClassName])
+ return this;
+ },
+ removeClass : function ( sClassName ) {
+ singleCaller.call(this, "removeClass", [sClassName])
+ return this;
+ },
+ setStyle : function ( oStyles ) {
+ singleCaller.call(this, "setStyle", [oStyles])
+ return this;
+ },
+ bindOnclick : function ( f ) {
+ singleCaller.call(this, "bindOnclick", [f])
+ return this;
+ },
+ bindOnchange : function ( f ) {
+ singleCaller.call(this, "bindOnchange", [f])
+ return this;
+ },
+
+ forEach : function ( fTodo ) {
+ //el,i
+ for (var i=0, l=this.length; i<l ; i++){
+ fTodo.apply(this[i], [this[i],i ]);
+ }
+ return this;
+ }
+
+ };
+
+
+
+ this.byClass = function( sClassName ){
+ var o = getElementsByClassName(document, sClassName );
+ return o ? mergeObjs( collection, o ) : o;
+ };
+
+ this.byId = function( sId ){
+ var o = document.getElementById( sId );
+ return o ? mergeObjs( single, o ) : o;
+ };
+
+ this.gup = function ( name ){
+ name = name.replace( /[\[]/, '\\\[' ).replace( /[\]]/, '\\\]' ) ;
+ var regexS = '[\\?&]' + name + '=([^&#]*)' ;
+ var regex = new RegExp( regexS ) ;
+ var results = regex.exec( window.location.href ) ;
+
+ if( results == null )
+ return '' ;
+ else
+ return results[ 1 ] ;
+ };
+ this.wrap = function ( o ) {
+ return o ? mergeObjs( single, o ) : o;
+ };
+ this.forEach = function ( oScope, fTodo ){
+ collection.forEach.apply( oScope,[fTodo] );
+ };
+
+ };
+
+
+
+ // Add the dialog tabs.
+ tabs[0] == 1 && dialog.AddTab( 'options', 'Options' ) ;
+ tabs[1] == 1 && dialog.AddTab( 'langs', 'Languages' ) ;
+ tabs[2] == 1 && dialog.AddTab( 'dictionary', 'Dictionary' ) ;
+ tabs[3] == 1 && dialog.AddTab( 'about', 'About' ) ;
+
+ // Function called when a dialog tab is selected.
+ function OnDialogTabChange( tabCode )
+ {
+ ShowE('inner_options' , ( tabCode == 'options' ) ) ;
+ ShowE('inner_langs' , ( tabCode == 'langs' ) ) ;
+ ShowE('inner_dictionary' , ( tabCode == 'dictionary' ) ) ;
+ ShowE('inner_about' , ( tabCode == 'about' ) ) ;
+ }
+
+
+
+
+
+ window.onload = function()
+ {
+ // Things to do when the page is loaded.
+
+ if ( document.location.search.length )
+ dialog.SetSelectedTab( document.location.search.substr(1) ) ;
+
+ dialog.SetOkButton( true ) ;
+
+
+ if (!scayt) throw "SCAYT is undefined";
+ if (!scayt_control) throw "SCAYT_CONTROL is undefined";
+
+ // show alowed tabs
+ tabs = scayt_control.uiTags || [1,1,1,0];
+
+
+ sLang = scayt_control.getLang();
+ fckLang = "en";
+ options = scayt_control.option();
+ // apply captions
+ scayt.getCaption( fckLang, function( caps )
+ {
+ //console.info( "scayt.getCaption runned" )
+ captions = caps;
+ apllyCaptions();
+ //lang_list = scayt.getLangList();
+ lang_list = scayt.getLangList() ;//|| {ltr: {"en_US" : "English","en_GB" : "British English","pt_BR" : "Brazilian Portuguese","da_DK" : "Danish","nl_NL" : "Dutch","en_CA" : "English Canadian","fi_FI" : "Finnish","fr_FR" : "French","fr_CA" : "French Canadian","de_DE" : "German","el_GR" : "Greek","hu_HU" : "Hungarian","it_IT" : "Italian","nb_NO" : "Norwegian","pl_PL" : "Polish","pt_PT" : "Portuguese","ru_RU" : "Russian","es_ES" : "Spanish","sv_SE" : "Swedish","tr_TR" : "Turkish","uk_UA" : "Ukrainian","cy_GB" : "Welsh"},rtl: {"ar_EG" : "Arabic"}};
+
+
+
+
+ // ** animate options
+ get.byClass("_scayt_option").forEach(function(el,i){
+
+ if ('undefined' != typeof(options[el.name])) {
+ // *** set default values
+
+ if ( 1 == options[ el.name ] ){
+ //el.setAttribute("checked","true");
+ get.wrap(el).setAttr("checked" ,true)
+ //document.all_options[el.name].checked = "true";
+ //el.checked = true;
+ //alert( options[ dojo.attr(el ,'name') ] + " el " )
+ }
+ //console.info(options)
+ // *** bind events
+ get.wrap(el).bindOnclick( function(ev){
+
+ var that = get.wrap(this);
+ var isCheck = that.getAttr("checked");
+ //console.info(isCheck)
+ if ( isCheck == false ) {
+
+ //that.setAttr("checked",false);
+ options[ this.name ] = 0;
+ }else{
+ //that.setAttr("checked",true);
+ options[ this.name ] = 1;
+ }
+ //console.info(options)
+ });
+ }
+ });
+
+
+ // * Create languages tab
+ // ** convert langs obj to array
+ var lang_arr = [];
+
+ for (var k in lang_list.rtl){
+ // find curent lang
+ if ( k == sLang)
+ chosed_lang = lang_list.rtl[k] + "::" + k;
+ lang_arr[lang_arr.length] = lang_list.rtl[k] + "::" + k;
+
+ }
+ for (var k in lang_list.ltr){
+ // find curent lang
+ if ( k == sLang)
+ chosed_lang = lang_list.ltr[k] + "::" + k;
+ lang_arr[lang_arr.length] = lang_list.ltr[k] + "::" + k;
+ }
+ lang_arr.sort();
+
+ // ** find lang containers
+
+ var lcol = get.byId("lcolid");
+ var rcol = get.byId("rcolid");
+ // ** place langs in DOM
+
+ get.forEach(lang_arr , function( l , i ){
+
+ //console.info( l,i );
+
+ var l_arr = l.split('::');
+ var l_name = l_arr[0];
+ var l_code = l_arr[1];
+ var row = document.createElement('div');
+ row.id = l_code;
+ row.className = "li";
+ // split langs on half
+ var col = ( i < lang_arr.length/2 ) ? lcol:rcol ;
+
+ // append row
+ //console.dir( col )
+ col.appendChild(row);
+ var row_dom = get.byId( l_code )
+ row_dom.innerHTML = l_name;
+
+ var checkActiveLang = function( id ){
+ return chosed_lang.split("::")[1] == id;
+ };
+ // bind click
+ row_dom.bindOnclick(function(ev){
+
+ if ( checkActiveLang(this.id) ) return false;
+ var elId = this.id;
+ get.byId(this.id)
+ .addClass("Button")
+ .removeClass("DarkBackground");
+
+ window.setTimeout( function (){ get.byId(elId).setStyle({opacity:"0.5",cursor:"no-drop"}); } ,300 );
+
+ get.byId(chosed_lang.split("::")[1])
+ .addClass("DarkBackground")
+ .removeClass("Button")
+ .setStyle({opacity:"1",cursor:"pointer"});
+
+ chosed_lang = this.innerHTML + "::" + this.id;
+ return true;
+ })
+ .setStyle({
+ cursor:"pointer"
+ });
+ // select current lang
+ if (l == chosed_lang)
+ row_dom.addClass("Button").setStyle({opacity:"0.5",cursor:"no-drop"});
+ else
+ row_dom.addClass("DarkBackground").setStyle({opacity:"1"});
+
+ });
+ // * user dictionary
+ if ( userDicActive ){
+ initUserDictionary()
+
+ }
+ });
+
+
+
+ }
+
+
+
+
+ var buttons = [ 'dic_create','dic_delete','dic_rename','dic_restore' ];
+ var labels = [ 'mixedCase','mixedWithDigits','allCaps','ignoreDomainNames' ];
+
+
+ function apllyCaptions ( )
+ {
+
+ // fill tabs headers
+ // add missing captions
+
+ get.byClass("PopupTab").forEach(function(el,i){
+
+ if ( tabs[i] == 1 ){
+ el.style.display = "block";
+ }
+ el.innerHTML = captions['tab_'+el.id];
+
+ });
+
+ // Fill options labels.
+ for ( i in labels )
+ {
+ var label = 'label_' + labels[ i ],
+ labelElement = document.getElementById( label );
+
+ if ( 'undefined' != typeof labelElement
+ && 'undefined' != typeof captions[ label ] && captions[ label ] !== ""
+ && 'undefined' != typeof options[labels[ i ]] )
+ {
+ labelElement.innerHTML = captions[ label ];
+ var labelParent = labelElement.parentNode;
+ labelParent.style.display = "block";
+ }
+ }
+ // fill dictionary section
+ for ( var i in buttons )
+ {
+ var button = buttons[ i ];
+ get.byId( button ).innerHTML = '<span>' + captions[ 'button_' + button] +'</span>' ;
+ }
+ get.byId("dname").innerHTML = captions['label_dname'];
+ get.byId( 'dic_info' ).innerHTML = captions[ 'dic_info' ];
+
+ // fill about tab
+ var about = '<p>' + captions[ 'about_throwt_image' ] + '</p>'+
+ '<p>' + captions[ 'version' ] + scayt.version.toString() + '</p>' +
+ '<p>' + captions[ 'about_throwt_copy' ] + '</p>';
+
+ get.byId( 'scayt_about' ).innerHTML = about;
+
+ }
+
+
+ function initUserDictionary () {
+
+ scayt.getNameUserDictionary(
+ function( o )
+ {
+ var dic_name = o.dname;
+ if ( dic_name )
+ {
+ get.byId( 'dic_name' ).value = dic_name;
+ display_dic_buttons( dic_buttons[1] );
+ }
+ else
+ display_dic_buttons( dic_buttons[0] );
+
+ },
+ function ()
+ {
+ get.byId( 'dic_name' ).value("");
+ dic_error_message(captions["err_dic_enable"] || "Used dictionary are unaveilable now.")
+ }
+ );
+
+ dic_success_message("");
+
+ // ** bind event listeners
+ get.byClass("button").bindOnclick(function( ){
+
+ // get dic name
+ var dic_name = get.byId('dic_name').value ;
+ // check common dictionary rules
+ if (!dic_name) {
+ dic_error_message(" Dictionary name should not be empty. ");
+ return false;
+ }
+ //apply handler
+ window[this.id].apply( window, [this, dic_name, dic_buttons ] );
+
+ //console.info( typeof window[this.id], window[this.id].calle )
+ return false;
+ });
+
+ }
+
+ dic_create = function( el, dic_name , dic_buttons )
+ {
+ // comma separated button's ids include repeats if exists
+ var all_buttons = dic_buttons[0] + ',' + dic_buttons[1];
+
+ var err_massage = captions["err_dic_create"];
+ var suc_massage = captions["succ_dic_create"];
+ //console.info("--plugin ");
+
+ scayt.createUserDictionary(dic_name,
+ function(arg)
+ {
+ //console.info( "dic_create callback called with args" , arg );
+ hide_dic_buttons ( all_buttons );
+ display_dic_buttons ( dic_buttons[1] );
+ suc_massage = suc_massage.replace("%s" , arg.dname );
+ dic_success_message (suc_massage);
+ },
+ function(arg)
+ {
+ //console.info( "dic_create errorback called with args" , arg )
+ err_massage = err_massage.replace("%s" ,arg.dname );
+ dic_error_message ( err_massage + "( "+ (arg.message || "") +")");
+ });
+
+ };
+
+ dic_rename = function( el, dic_name , dic_buttons )
+ {
+ //
+ // try to rename dictionary
+ // @TODO: rename dict
+ //console.info ( captions["err_dic_rename"] )
+ var err_massage = captions["err_dic_rename"] || "";
+ var suc_massage = captions["succ_dic_rename"] || "";
+ scayt.renameUserDictionary(dic_name,
+ function(arg)
+ {
+ //console.info( "dic_rename callback called with args" , arg );
+ suc_massage = suc_massage.replace("%s" , arg.dname );
+ set_dic_name( dic_name );
+ dic_success_message ( suc_massage );
+ },
+ function(arg)
+ {
+ //console.info( "dic_rename errorback called with args" , arg )
+ err_massage = err_massage.replace("%s" , arg.dname );
+ set_dic_name( dic_name );
+ dic_error_message( err_massage + "( " + ( arg.message || "" ) + " )" );
+ });
+ };
+
+ dic_delete = function ( el, dic_name , dic_buttons )
+ {
+ var all_buttons = dic_buttons[0] + ',' + dic_buttons[1];
+ var err_massage = captions["err_dic_delete"];
+ var suc_massage = captions["succ_dic_delete"];
+
+ // try to delete dictionary
+ // @TODO: delete dict
+ scayt.deleteUserDictionary(
+ function(arg)
+ {
+ //console.info( "dic_delete callback " , dic_name ,arg );
+ suc_massage = suc_massage.replace("%s" , arg.dname );
+ hide_dic_buttons ( all_buttons );
+ display_dic_buttons ( dic_buttons[0] );
+ set_dic_name( "" ); // empty input field
+ dic_success_message( suc_massage );
+ },
+ function(arg)
+ {
+ //console.info( " dic_delete errorback called with args" , arg )
+ err_massage = err_massage.replace("%s" , arg.dname );
+ dic_error_message(err_massage);
+ });
+ };
+
+ dic_restore = dialog.dic_restore || function ( el, dic_name , dic_buttons )
+ {
+ // try to restore existing dictionary
+ var all_buttons = dic_buttons[0] + ',' + dic_buttons[1];
+ var err_massage = captions["err_dic_restore"];
+ var suc_massage = captions["succ_dic_restore"];
+
+ scayt.restoreUserDictionary(dic_name,
+ function(arg)
+ {
+ //console.info( "dic_restore callback called with args" , arg );
+ suc_massage = suc_massage.replace("%s" , arg.dname );
+ hide_dic_buttons ( all_buttons );
+ display_dic_buttons(dic_buttons[1]);
+ dic_success_message( suc_massage );
+ },
+ function(arg)
+ {
+ //console.info( " dic_restore errorback called with args" , arg )
+ err_massage = err_massage.replace("%s" , arg.dname );
+ dic_error_message( err_massage );
+ });
+ };
+
+ function dic_error_message( m )
+ {
+ if ( !m )
+ return ;
+
+ get.byId('dic_message').innerHTML = '<span class="error">' + m + '</span>' ;
+ }
+
+ function dic_success_message( m )
+ {
+ if ( !m )
+ return ;
+
+ get.byId('dic_message').innerHTML = '<span class="success">' + m + '</span>' ;
+ }
+
+ function display_dic_buttons ( sIds ){
+ sIds = new String( sIds );
+ get.forEach( sIds.split(','), function ( id,i) {
+ get.byId(id).setStyle({display:"inline"});
+ });
+ }
+ function hide_dic_buttons ( sIds ){
+ sIds = new String( sIds );
+ get.forEach( sIds.split(','), function ( id,i) {
+ get.byId(id).setStyle({display:"none"});
+ });
+ }
+ function set_dic_name ( dic_name ) {
+ get.byId('dic_name').value = dic_name;
+ }
+ function display_dic_tab () {
+ get.byId("dic_tab").style.display = "block";
+ }
+
+ function Ok()
+ {
+ // Things to do when the Ok button is clicked.
+ var c = 0;
+ // set upp options if any was set
+ var o = scayt_control.option();
+ //console.info(options)
+ for ( var oN in options ) {
+
+ if ( o[oN] != options[oN] && c == 0){
+ //console.info( "set option " )
+ scayt_control.option( options );
+ c++;
+ }
+ }
+ //setup languge if it was change
+ var csLang = chosed_lang.split("::")[1];
+ if ( csLang && sLang != csLang ){
+ scayt_control.setLang( csLang );
+ //console.info(sLang+" -> "+csLang , scayt_control)
+ c++;
+ }
+
+ if ( c > 0 ) scayt_control.refresh();
+
+ return dialog.Cancel();
+
+ }
+
+ </script>
+ </head>
+ <body style="OVERFLOW: hidden" scroll="no">
+ <div class="tab_container" id="inner_options">
+
+ <ul id="scayt_options">
+ <li class="_scayt_options">
+ <input class="_scayt_option" type="checkbox" value="0" name="allCaps" />
+ <label for="allCaps" id="label_allCaps"></label>
+ </li>
+ <li>
+ <input class="_scayt_option" type="checkbox" value="0" name="ignoreDomainNames" />
+ <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>
+ </li>
+ <li>
+ <input class="_scayt_option" type="checkbox" value="0" name="mixedCase" />
+ <label for="mixedCase" id="label_mixedCase"></label>
+ </li>
+ <li>
+ <input class="_scayt_option" type="checkbox" value="0" name="mixedWithDigits" />
+ <label for="mixedWithDigits" id="label_mixedWithDigits"></label>
+ </li>
+ </ul>
+ </div>
+ <div class="tab_container" id="inner_langs">
+
+ <div class="lcol" id="lcolid"></div>
+ <div class="rcol" id="rcolid"></div>
+ </div>
+ <div class="tab_container" id="inner_dictionary">
+
+ <div id="dic_message"></div>
+ <div id="_off_dic_tab" class="dictionary" >
+ <div style="padding-left:10px;">
+ <label id="dname" for="dname"></label>
+ <input type="text" size="14" maxlength="15" value="" id="dic_name" name="dic_name"/>
+ </div>
+ <div class="dic_buttons">
+ <a href="#" id="dic_create" class="button"> </a>
+ <a href="#" id="dic_delete" class="button"> </a>
+ <a href="#" id="dic_rename" class="button"> </a>
+ <a href="#" id="dic_restore" class="button"> </a>
+ </div>
+
+ <div id="dic_info"></div>
+
+ </div>
+ </div>
+ <div id="inner_about" class="tab_container">
+ <div id="scayt_about"></div>
+ </div>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_scayt/scayt_dialog.css b/httemplate/elements/fckeditor/editor/dialog/fck_scayt/scayt_dialog.css
new file mode 100644
index 0000000..c2eeb75
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_scayt/scayt_dialog.css
@@ -0,0 +1,169 @@
+html, body
+{
+ background-color: transparent;
+ margin: 0px;
+ padding: 0px;
+}
+
+body
+{
+ padding: 10px;
+}
+
+body, td, input, select, textarea
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+.midtext
+{
+ padding:0px;
+ margin:10px;
+}
+
+.midtext p
+{
+ padding:0px;
+ margin:10px;
+}
+
+.Button
+{
+ border: #737357 1px solid;
+ color: #3b3b1f;
+ background-color: #c7c78f;
+}
+
+.PopupTabArea , .button
+{
+ color: #737357;
+ background-color: #e3e3c7;
+}
+
+.PopupTitleBorder
+{
+ border-bottom: #d5d59d 1px solid;
+}
+.PopupTabEmptyArea
+{
+ padding-left: 10px;
+ border-bottom: #d5d59d 1px solid;
+}
+
+.PopupTab, .PopupTabSelected
+{
+ border-right: #d5d59d 1px solid;
+ border-top: #d5d59d 1px solid;
+ border-left: #d5d59d 1px solid;
+ padding: 3px 5px 3px 5px;
+ color: #737357;
+}
+
+.PopupTab
+{
+ margin-top: 1px;
+ border-bottom: #d5d59d 1px solid;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.PopupTabSelected
+{
+ font-weight: bold;
+ cursor: default;
+ padding-top: 4px;
+ border-bottom: #f1f1e3 1px solid;
+ background-color: #f1f1e3;
+}
+
+ul {
+ padding:0;
+ margin:0px 0px 12px 0px;
+ list-style-type:none;
+}
+ul.tabs {
+ height:20px;
+ margin:10px 0px;
+}
+ul.tabs li {
+ float: left;
+ display:none;
+}
+div.tab_container {
+ /*display:none;*/
+ padding: 0px 5px ;
+}
+.lcol {
+ float:left;
+ width:47%;
+ margin-left:5px;
+}
+.rcol {
+ float:right;
+ width:47%;
+ margin-right:5px;
+}
+div.tabs-container{
+ height:220px;
+ overflow-x:hidden;
+ overflow-y:auto;
+}
+
+div.tabs-container h3{
+ margin:5px 15px 7px 15px;
+ background-color:transparent;
+ font-size: 14px ;
+}
+
+.li {
+ border: 1px solid transparent;
+}
+
+#dic_message{
+ height: 24px;
+}
+#dic_message .error{
+ color: red ;
+}
+#dic_message .success{
+ color: blue ;
+}
+
+.dic_buttons {
+ margin-top: 5px;
+ padding-left:10px;
+}
+.dic_buttons a {
+ display: none;
+}
+a.button {
+ border: #d5d59d 1px solid;
+ padding: 2px 4px;
+ margin-right: 4px;
+ text-decoration: none;
+}
+
+a.button:hover,
+a.button:active,
+a.button:visited{
+ padding: 2px 4px;
+ margin-right: 4px;
+ text-decoration: none;
+}
+a.button:hover {
+ border: #d5d59d 1px solid;
+ color: #e3e3c7;
+ background-color: #737357;
+}
+
+#scayt_options li {
+ display: none;
+}
+
+#dic_info {
+ margin:10px;
+}
+#dic_tab {
+ display:none;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_select.html b/httemplate/elements/fckeditor/editor/dialog/fck_select.html
new file mode 100644
index 0000000..2f28da6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_select.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Select dialog window.
+-->
+<html>
+ <head>
+ <title>Select Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript" src="fck_select/fck_select.js"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+var oListText ;
+var oListValue ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ oListText = document.getElementById( 'cmbText' ) ;
+ oListValue = document.getElementById( 'cmbValue' ) ;
+
+ // Fix the lists widths. (Bug #970)
+ oListText.style.width = oListText.offsetWidth ;
+ oListValue.style.width = oListValue.offsetWidth ;
+
+ if ( oActiveEl && oActiveEl.tagName == 'SELECT' )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtSelValue').value = oActiveEl.value ;
+ GetE('txtLines').value = GetAttribute( oActiveEl, 'size' ) ;
+ GetE('chkMultiple').checked = oActiveEl.multiple ;
+
+ // Load the actual options
+ for ( var i = 0 ; i < oActiveEl.options.length ; i++ )
+ {
+ var sText = HTMLDecode( oActiveEl.options[i].innerHTML ) ;
+ var sValue = oActiveEl.options[i].value ;
+
+ AddComboOption( oListText, sText, sText ) ;
+ AddComboOption( oListValue, sValue, sValue ) ;
+ }
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ var sSize = GetE('txtLines').value ;
+ if ( sSize == null || isNaN( sSize ) || sSize <= 1 )
+ sSize = '' ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'SELECT', {name: GetE('txtName').value} ) ;
+
+ SetAttribute( oActiveEl, 'size' , sSize ) ;
+ oActiveEl.multiple = ( sSize.length > 0 && GetE('chkMultiple').checked ) ;
+
+ // Remove all options.
+ while ( oActiveEl.options.length > 0 )
+ oActiveEl.remove(0) ;
+
+ // Add all available options.
+ for ( var i = 0 ; i < oListText.options.length ; i++ )
+ {
+ var sText = oListText.options[i].value ;
+ var sValue = oListValue.options[i].value ;
+ if ( sValue.length == 0 ) sValue = sText ;
+
+ var oOption = AddComboOption( oActiveEl, sText, sValue, oDOM ) ;
+
+ if ( sValue == GetE('txtSelValue').value )
+ {
+ SetAttribute( oOption, 'selected', 'selected' ) ;
+ oOption.selected = true ;
+ }
+ }
+
+ return true ;
+}
+
+ </script>
+ </head>
+ <body style="overflow: hidden">
+ <table width="100%" height="100%">
+ <tr>
+ <td>
+ <table width="100%">
+ <tr>
+ <td nowrap><span fckLang="DlgSelectName">Name</span>&nbsp;</td>
+ <td width="100%" colSpan="2"><input id="txtName" style="WIDTH: 100%" type="text"></td>
+ </tr>
+ <tr>
+ <td nowrap><span fckLang="DlgSelectValue">Value</span>&nbsp;</td>
+ <td width="100%" colSpan="2"><input id="txtSelValue" style="WIDTH: 100%; BACKGROUND-COLOR: buttonface" type="text" readonly></td>
+ </tr>
+ <tr>
+ <td nowrap><span fckLang="DlgSelectSize">Size</span>&nbsp;</td>
+ <td nowrap><input id="txtLines" type="text" size="2" value="">&nbsp;<span fckLang="DlgSelectLines">lines</span></td>
+ <td nowrap align="right"><input id="chkMultiple" type="checkbox"><label for="chkMultiple" fckLang="DlgSelectChkMulti">Allow
+ multiple selections</label></td>
+ </tr>
+ </table>
+ <br>
+ <hr style="POSITION: absolute">
+ <span style="LEFT: 10px; POSITION: relative; TOP: -7px" class="BackColor">&nbsp;<span fckLang="DlgSelectOpAvail">Available
+ Options</span>&nbsp;</span>
+ <table width="100%">
+ <tr>
+ <td width="50%"><span fckLang="DlgSelectOpText">Text</span><br>
+ <input id="txtText" style="WIDTH: 100%" type="text">
+ </td>
+ <td width="50%"><span fckLang="DlgSelectOpValue">Value</span><br>
+ <input id="txtValue" style="WIDTH: 100%" type="text">
+ </td>
+ <td vAlign="bottom"><input onclick="Add();" type="button" fckLang="DlgSelectBtnAdd" value="Add"></td>
+ <td vAlign="bottom"><input onclick="Modify();" type="button" fckLang="DlgSelectBtnModify" value="Modify"></td>
+ </tr>
+ <tr>
+ <td rowSpan="2"><select id="cmbText" style="WIDTH: 100%" onchange="GetE('cmbValue').selectedIndex = this.selectedIndex;Select(this);"
+ size="5"></select>
+ </td>
+ <td rowSpan="2"><select id="cmbValue" style="WIDTH: 100%" onchange="GetE('cmbText').selectedIndex = this.selectedIndex;Select(this);"
+ size="5"></select>
+ </td>
+ <td vAlign="top" colSpan="2">
+ </td>
+ </tr>
+ <tr>
+ <td vAlign="bottom" colSpan="2"><input style="WIDTH: 100%" onclick="Move(-1);" type="button" fckLang="DlgSelectBtnUp" value="Up">
+ <br>
+ <input style="WIDTH: 100%" onclick="Move(1);" type="button" fckLang="DlgSelectBtnDown"
+ value="Down">
+ </td>
+ </tr>
+ <TR>
+ <TD vAlign="bottom" colSpan="4"><INPUT onclick="SetSelectedValue();" type="button" fckLang="DlgSelectBtnSetValue" value="Set as selected value">&nbsp;&nbsp;
+ <input onclick="Delete();" type="button" fckLang="DlgSelectBtnDelete" value="Delete"></TD>
+ </TR>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js b/httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js
new file mode 100644
index 0000000..3120bb3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js
@@ -0,0 +1,194 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Scripts for the fck_select.html page.
+ */
+
+function Select( combo )
+{
+ var iIndex = combo.selectedIndex ;
+
+ oListText.selectedIndex = iIndex ;
+ oListValue.selectedIndex = iIndex ;
+
+ var oTxtText = document.getElementById( "txtText" ) ;
+ var oTxtValue = document.getElementById( "txtValue" ) ;
+
+ oTxtText.value = oListText.value ;
+ oTxtValue.value = oListValue.value ;
+}
+
+function Add()
+{
+ var oTxtText = document.getElementById( "txtText" ) ;
+ var oTxtValue = document.getElementById( "txtValue" ) ;
+
+ AddComboOption( oListText, oTxtText.value, oTxtText.value ) ;
+ AddComboOption( oListValue, oTxtValue.value, oTxtValue.value ) ;
+
+ oListText.selectedIndex = oListText.options.length - 1 ;
+ oListValue.selectedIndex = oListValue.options.length - 1 ;
+
+ oTxtText.value = '' ;
+ oTxtValue.value = '' ;
+
+ oTxtText.focus() ;
+}
+
+function Modify()
+{
+ var iIndex = oListText.selectedIndex ;
+
+ if ( iIndex < 0 ) return ;
+
+ var oTxtText = document.getElementById( "txtText" ) ;
+ var oTxtValue = document.getElementById( "txtValue" ) ;
+
+ oListText.options[ iIndex ].innerHTML = HTMLEncode( oTxtText.value ) ;
+ oListText.options[ iIndex ].value = oTxtText.value ;
+
+ oListValue.options[ iIndex ].innerHTML = HTMLEncode( oTxtValue.value ) ;
+ oListValue.options[ iIndex ].value = oTxtValue.value ;
+
+ oTxtText.value = '' ;
+ oTxtValue.value = '' ;
+
+ oTxtText.focus() ;
+}
+
+function Move( steps )
+{
+ ChangeOptionPosition( oListText, steps ) ;
+ ChangeOptionPosition( oListValue, steps ) ;
+}
+
+function Delete()
+{
+ RemoveSelectedOptions( oListText ) ;
+ RemoveSelectedOptions( oListValue ) ;
+}
+
+function SetSelectedValue()
+{
+ var iIndex = oListValue.selectedIndex ;
+ if ( iIndex < 0 ) return ;
+
+ var oTxtValue = document.getElementById( "txtSelValue" ) ;
+
+ oTxtValue.value = oListValue.options[ iIndex ].value ;
+}
+
+// Moves the selected option by a number of steps (also negative)
+function ChangeOptionPosition( combo, steps )
+{
+ var iActualIndex = combo.selectedIndex ;
+
+ if ( iActualIndex < 0 )
+ return ;
+
+ var iFinalIndex = iActualIndex + steps ;
+
+ if ( iFinalIndex < 0 )
+ iFinalIndex = 0 ;
+
+ if ( iFinalIndex > ( combo.options.length - 1 ) )
+ iFinalIndex = combo.options.length - 1 ;
+
+ if ( iActualIndex == iFinalIndex )
+ return ;
+
+ var oOption = combo.options[ iActualIndex ] ;
+ var sText = HTMLDecode( oOption.innerHTML ) ;
+ var sValue = oOption.value ;
+
+ combo.remove( iActualIndex ) ;
+
+ oOption = AddComboOption( combo, sText, sValue, null, iFinalIndex ) ;
+
+ oOption.selected = true ;
+}
+
+// Remove all selected options from a SELECT object
+function RemoveSelectedOptions(combo)
+{
+ // Save the selected index
+ var iSelectedIndex = combo.selectedIndex ;
+
+ var oOptions = combo.options ;
+
+ // Remove all selected options
+ for ( var i = oOptions.length - 1 ; i >= 0 ; i-- )
+ {
+ if (oOptions[i].selected) combo.remove(i) ;
+ }
+
+ // Reset the selection based on the original selected index
+ if ( combo.options.length > 0 )
+ {
+ if ( iSelectedIndex >= combo.options.length ) iSelectedIndex = combo.options.length - 1 ;
+ combo.selectedIndex = iSelectedIndex ;
+ }
+}
+
+// Add a new option to a SELECT object (combo or list)
+function AddComboOption( combo, optionText, optionValue, documentObject, index )
+{
+ var oOption ;
+
+ if ( documentObject )
+ oOption = documentObject.createElement("OPTION") ;
+ else
+ oOption = document.createElement("OPTION") ;
+
+ if ( index != null )
+ combo.options.add( oOption, index ) ;
+ else
+ combo.options.add( oOption ) ;
+
+ oOption.innerHTML = optionText.length > 0 ? HTMLEncode( optionText ) : '&nbsp;' ;
+ oOption.value = optionValue ;
+
+ return oOption ;
+}
+
+function HTMLEncode( text )
+{
+ if ( !text )
+ return '' ;
+
+ text = text.replace( /&/g, '&amp;' ) ;
+ text = text.replace( /</g, '&lt;' ) ;
+ text = text.replace( />/g, '&gt;' ) ;
+
+ return text ;
+}
+
+
+function HTMLDecode( text )
+{
+ if ( !text )
+ return '' ;
+
+ text = text.replace( /&gt;/g, '>' ) ;
+ text = text.replace( /&lt;/g, '<' ) ;
+ text = text.replace( /&amp;/g, '&' ) ;
+
+ return text ;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_smiley.html b/httemplate/elements/fckeditor/editor/dialog/fck_smiley.html
new file mode 100644
index 0000000..ba3a302
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_smiley.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Smileys (emoticons) dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <style type="text/css">
+ .Hand
+ {
+ cursor: pointer;
+ cursor: hand;
+ }
+ </style>
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+window.onload = function ()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ dialog.SetAutoSize( true ) ;
+}
+
+function InsertSmiley( url )
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ var oImg = oEditor.FCK.InsertElement( 'img' ) ;
+ oImg.src = url ;
+ oImg.setAttribute( '_fcksavedurl', url ) ;
+
+ // For long smileys list, it seams that IE continues loading the images in
+ // the background when you quickly select one image. so, let's clear
+ // everything before closing.
+ document.body.innerHTML = '' ;
+
+ dialog.Cancel() ;
+}
+
+function over(td)
+{
+ td.className = 'LightBackground Hand' ;
+}
+
+function out(td)
+{
+ td.className = 'DarkBackground Hand' ;
+}
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table cellpadding="2" cellspacing="2" align="center" border="0" width="100%" height="100%">
+ <script type="text/javascript">
+
+var FCKConfig = oEditor.FCKConfig ;
+
+var sBasePath = FCKConfig.SmileyPath ;
+var aImages = FCKConfig.SmileyImages ;
+var iCols = FCKConfig.SmileyColumns ;
+var iColWidth = parseInt( 100 / iCols, 10 ) ;
+
+var i = 0 ;
+while (i < aImages.length)
+{
+ document.write( '<tr>' ) ;
+ for(var j = 0 ; j < iCols ; j++)
+ {
+ if (aImages[i])
+ {
+ var sUrl = sBasePath + aImages[i] ;
+ document.write( '<td width="' + iColWidth + '%" align="center" class="DarkBackground Hand" onclick="InsertSmiley(\'' + sUrl.replace(/'/g, "\\'" ) + '\')" onmouseover="over(this)" onmouseout="out(this)">' ) ;
+ document.write( '<img src="' + sUrl + '" border="0" />' ) ;
+ }
+ else
+ document.write( '<td width="' + iColWidth + '%" class="DarkBackground">&nbsp;' ) ;
+ document.write( '<\/td>' ) ;
+ i++ ;
+ }
+ document.write('<\/tr>') ;
+}
+
+ </script>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_source.html b/httemplate/elements/fckeditor/editor/dialog/fck_source.html
new file mode 100644
index 0000000..0782c65
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_source.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Source editor dialog window.
+-->
+<html>
+ <head>
+ <title>Source</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="robots" content="noindex, nofollow">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script language="javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKConfig = oEditor.FCKConfig ;
+var FCKTools = oEditor.FCKTools ;
+
+document.write( FCKTools.GetStyleHtml( GetCommonDialogCss() ) ) ;
+
+window.onload = function()
+{
+ // EnableXHTML and EnableSourceXHTML has been deprecated
+// document.getElementById('txtSource').value = ( FCKConfig.EnableXHTML && FCKConfig.EnableSourceXHTML ? FCK.GetXHTML( FCKConfig.FormatSource ) : FCK.GetHTML( FCKConfig.FormatSource ) ) ;
+ document.getElementById('txtSource').value = FCK.GetXHTML( FCKConfig.FormatSource ) ;
+
+ // Activate the "OK" button.
+ window.parent.SetOkButton( true ) ;
+}
+
+//#### The OK button was hit.
+function Ok()
+{
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ FCK.SetData( document.getElementById('txtSource').value, false ) ;
+
+ return true ;
+}
+ </script>
+ </head>
+ <body scroll="no" style="OVERFLOW: hidden">
+ <table width="100%" height="100%">
+ <tr>
+ <td height="100%"><textarea id="txtSource" dir="ltr" style="PADDING-RIGHT: 5px; PADDING-LEFT: 5px; FONT-SIZE: 14px; PADDING-BOTTOM: 5px; WIDTH: 100%; PADDING-TOP: 5px; FONT-FAMILY: Monospace; HEIGHT: 100%">Loading. Please wait...</textarea></td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html b/httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html
new file mode 100644
index 0000000..9a8ba77
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Special Chars Selector dialog window.
+-->
+<html>
+ <head>
+ <meta name="robots" content="noindex, nofollow">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <style type="text/css">
+ .Hand
+ {
+ cursor: pointer ;
+ cursor: hand ;
+ }
+ .Sample { font-size: 24px; }
+ </style>
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+
+var oSample ;
+
+function insertChar(charValue)
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+ oEditor.FCK.InsertHtml( charValue || "" ) ;
+ window.parent.Cancel() ;
+}
+
+function over(td)
+{
+ if ( ! oSample )
+ return ;
+ oSample.innerHTML = td.innerHTML ;
+ td.className = 'LightBackground SpecialCharsOver Hand' ;
+}
+
+function out(td)
+{
+ if ( ! oSample )
+ return ;
+ oSample.innerHTML = "&nbsp;" ;
+ td.className = 'DarkBackground SpecialCharsOut Hand' ;
+}
+
+function setDefaults()
+{
+ // Gets the sample placeholder.
+ oSample = document.getElementById("SampleTD") ;
+
+ // First of all, translates the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ window.parent.SetAutoSize( true ) ;
+}
+
+ </script>
+ </head>
+ <body onload="setDefaults()" style="overflow: hidden">
+ <table cellpadding="0" cellspacing="0" width="100%" height="100%">
+ <tr>
+ <td width="100%">
+ <table cellpadding="1" cellspacing="1" align="center" border="0" width="100%" height="100%">
+ <script type="text/javascript">
+var aChars = ["!","&quot;","#","$","%","&amp;","\\'","(",")","*","+","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","&lt;","=","&gt;","?","@","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","[","]","^","_","`","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","{","|","}","~","&euro;","&lsquo;","&rsquo;","&rsquo;","&ldquo;","&rdquo;","&ndash;","&mdash;","&iexcl;","&cent;","&pound;","&curren;","&yen;","&brvbar;","&sect;","&uml;","&copy;","&ordf;","&laquo;","&not;","&reg;","&macr;","&deg;","&plusmn;","&sup2;","&sup3;","&acute;","&micro;","&para;","&middot;","&cedil;","&sup1;","&ordm;","&raquo;","&frac14;","&frac12;","&frac34;","&iquest;","&Agrave;","&Aacute;","&Acirc;","&Atilde;","&Auml;","&Aring;","&AElig;","&Ccedil;","&Egrave;","&Eacute;","&Ecirc;","&Euml;","&Igrave;","&Iacute;","&Icirc;","&Iuml;","&ETH;","&Ntilde;","&Ograve;","&Oacute;","&Ocirc;","&Otilde;","&Ouml;","&times;","&Oslash;","&Ugrave;","&Uacute;","&Ucirc;","&Uuml;","&Yacute;","&THORN;","&szlig;","&agrave;","&aacute;","&acirc;","&atilde;","&auml;","&aring;","&aelig;","&ccedil;","&egrave;","&eacute;","&ecirc;","&euml;","&igrave;","&iacute;","&icirc;","&iuml;","&eth;","&ntilde;","&ograve;","&oacute;","&ocirc;","&otilde;","&ouml;","&divide;","&oslash;","&ugrave;","&uacute;","&ucirc;","&uuml;","&uuml;","&yacute;","&thorn;","&yuml;","&OElig;","&oelig;","&#372;","&#374","&#373","&#375;","&sbquo;","&#8219;","&bdquo;","&hellip;","&trade;","&#9658;","&bull;","&rarr;","&rArr;","&hArr;","&diams;","&asymp;"] ;
+
+var cols = 20 ;
+
+var i = 0 ;
+while (i < aChars.length)
+{
+ document.write("<TR>") ;
+ for(var j = 0 ; j < cols ; j++)
+ {
+ if (aChars[i])
+ {
+ document.write('<TD width="1%" class="DarkBackground SpecialCharsOut Hand" align="center" onclick="insertChar(\'' + aChars[i].replace(/&/g, "&amp;") + '\')" onmouseover="over(this)" onmouseout="out(this)">') ;
+ document.write(aChars[i]) ;
+ }
+ else
+ document.write("<TD class='DarkBackground SpecialCharsOut'>&nbsp;") ;
+ document.write("<\/TD>") ;
+ i++ ;
+ }
+ document.write("<\/TR>") ;
+}
+ </script>
+ </table>
+ </td>
+ <td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td valign="top">
+ <table width="40" cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td id="SampleTD" width="40" height="40" align="center" class="DarkBackground SpecialCharsOut Sample">&nbsp;</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html
new file mode 100644
index 0000000..c3b7409
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Spell Check dialog window.
+-->
+<html>
+ <head>
+ <title>Spell Check</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script src="fck_spellerpages/spellerpages/spellChecker.js"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCKLang = oEditor.FCKLang ;
+
+window.onload = function()
+{
+ document.getElementById('txtHtml').value = oEditor.FCK.EditorDocument.body.innerHTML ;
+
+ var oSpeller = new spellChecker( document.getElementById('txtHtml') ) ;
+ oSpeller.spellCheckScript = oEditor.FCKConfig.SpellerPagesServerScript || 'server-scripts/spellchecker.php' ;
+ oSpeller.OnFinished = oSpeller_OnFinished ;
+ oSpeller.openChecker() ;
+}
+
+function OnSpellerControlsLoad( controlsWindow )
+{
+ // Translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage( controlsWindow.document ) ;
+}
+
+function oSpeller_OnFinished( numberOCorrections )
+{
+ if ( numberOCorrections > 0 )
+ {
+ oEditor.FCKUndo.SaveUndoStep() ;
+ oEditor.FCK.EditorDocument.body.innerHTML = document.getElementById('txtHtml').value ;
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ oEditor.FCKSelection.Collapse( true ) ;
+ }
+ window.parent.Cancel() ;
+}
+
+ </script>
+ </head>
+ <body style="OVERFLOW: hidden" scroll="no" style="padding:0px;">
+ <input type="hidden" id="txtHtml" value="">
+ <iframe id="frmSpell" src="javascript:void(0)" name="spellchecker" width="100%" height="100%" frameborder="0"></iframe>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js
new file mode 100644
index 0000000..80af849
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js
@@ -0,0 +1,87 @@
+////////////////////////////////////////////////////
+// controlWindow object
+////////////////////////////////////////////////////
+function controlWindow( controlForm ) {
+ // private properties
+ this._form = controlForm;
+
+ // public properties
+ this.windowType = "controlWindow";
+// this.noSuggestionSelection = "- No suggestions -"; // by FredCK
+ this.noSuggestionSelection = FCKLang.DlgSpellNoSuggestions ;
+ // set up the properties for elements of the given control form
+ this.suggestionList = this._form.sugg;
+ this.evaluatedText = this._form.misword;
+ this.replacementText = this._form.txtsugg;
+ this.undoButton = this._form.btnUndo;
+
+ // public methods
+ this.addSuggestion = addSuggestion;
+ this.clearSuggestions = clearSuggestions;
+ this.selectDefaultSuggestion = selectDefaultSuggestion;
+ this.resetForm = resetForm;
+ this.setSuggestedText = setSuggestedText;
+ this.enableUndo = enableUndo;
+ this.disableUndo = disableUndo;
+}
+
+function resetForm() {
+ if( this._form ) {
+ this._form.reset();
+ }
+}
+
+function setSuggestedText() {
+ var slct = this.suggestionList;
+ var txt = this.replacementText;
+ var str = "";
+ if( (slct.options[0].text) && slct.options[0].text != this.noSuggestionSelection ) {
+ str = slct.options[slct.selectedIndex].text;
+ }
+ txt.value = str;
+}
+
+function selectDefaultSuggestion() {
+ var slct = this.suggestionList;
+ var txt = this.replacementText;
+ if( slct.options.length == 0 ) {
+ this.addSuggestion( this.noSuggestionSelection );
+ } else {
+ slct.options[0].selected = true;
+ }
+ this.setSuggestedText();
+}
+
+function addSuggestion( sugg_text ) {
+ var slct = this.suggestionList;
+ if( sugg_text ) {
+ var i = slct.options.length;
+ var newOption = new Option( sugg_text, 'sugg_text'+i );
+ slct.options[i] = newOption;
+ }
+}
+
+function clearSuggestions() {
+ var slct = this.suggestionList;
+ for( var j = slct.length - 1; j > -1; j-- ) {
+ if( slct.options[j] ) {
+ slct.options[j] = null;
+ }
+ }
+}
+
+function enableUndo() {
+ if( this.undoButton ) {
+ if( this.undoButton.disabled == true ) {
+ this.undoButton.disabled = false;
+ }
+ }
+}
+
+function disableUndo() {
+ if( this.undoButton ) {
+ if( this.undoButton.disabled == false ) {
+ this.undoButton.disabled = true;
+ }
+ }
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html
new file mode 100644
index 0000000..d91bcce
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html
@@ -0,0 +1,153 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="spellerStyle.css" />
+ <script type="text/javascript" src="controlWindow.js"></script>
+ <script type="text/javascript">
+var spellerObject;
+var controlWindowObj;
+
+if( parent.opener ) {
+ spellerObject = parent.opener.speller;
+}
+
+function ignore_word() {
+ if( spellerObject ) {
+ spellerObject.ignoreWord();
+ }
+}
+
+function ignore_all() {
+ if( spellerObject ) {
+ spellerObject.ignoreAll();
+ }
+}
+
+function replace_word() {
+ if( spellerObject ) {
+ spellerObject.replaceWord();
+ }
+}
+
+function replace_all() {
+ if( spellerObject ) {
+ spellerObject.replaceAll();
+ }
+}
+
+function end_spell() {
+ if( spellerObject ) {
+ spellerObject.terminateSpell();
+ }
+}
+
+function undo() {
+ if( spellerObject ) {
+ spellerObject.undo();
+ }
+}
+
+function suggText() {
+ if( controlWindowObj ) {
+ controlWindowObj.setSuggestedText();
+ }
+}
+
+var FCKLang = window.parent.parent.FCKLang ; // by FredCK
+
+function init_spell() {
+ // By FredCK (fckLang attributes have been added to the HTML source of this page)
+ window.parent.parent.OnSpellerControlsLoad( this ) ;
+
+ var controlForm = document.spellcheck;
+
+ // create a new controlWindow object
+ controlWindowObj = new controlWindow( controlForm );
+
+ // call the init_spell() function in the parent frameset
+ if( parent.frames.length ) {
+ parent.init_spell( controlWindowObj );
+ } else {
+ alert( 'This page was loaded outside of a frameset. It might not display properly' );
+ }
+}
+
+</script>
+ </head>
+ <body class="controlWindowBody" onLoad="init_spell();" style="OVERFLOW: hidden" scroll="no"> <!-- by FredCK -->
+ <form name="spellcheck">
+ <table border="0" cellpadding="0" cellspacing="0" border="0" align="center">
+ <tr>
+ <td colspan="3" class="normalLabel"><span fckLang="DlgSpellNotInDic">Not in dictionary:</span></td>
+ </tr>
+ <tr>
+ <td colspan="3"><input class="readonlyInput" type="text" name="misword" readonly /></td>
+ </tr>
+ <tr>
+ <td colspan="3" height="5"></td>
+ </tr>
+ <tr>
+ <td class="normalLabel"><span fckLang="DlgSpellChangeTo">Change to:</span></td>
+ </tr>
+ <tr valign="top">
+ <td>
+ <table border="0" cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td class="normalLabel">
+ <input class="textDefault" type="text" name="txtsugg" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <select class="suggSlct" name="sugg" size="7" onChange="suggText();" onDblClick="replace_word();">
+ <option></option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td>
+ <table border="0" cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td>
+ <input class="buttonDefault" type="button" fckLang="DlgSpellBtnIgnore" value="Ignore" onClick="ignore_word();">
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td>
+ <input class="buttonDefault" type="button" fckLang="DlgSpellBtnIgnoreAll" value="Ignore All" onClick="ignore_all();">
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" height="5"></td>
+ </tr>
+ <tr>
+ <td>
+ <input class="buttonDefault" type="button" fckLang="DlgSpellBtnReplace" value="Replace" onClick="replace_word();">
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td>
+ <input class="buttonDefault" type="button" fckLang="DlgSpellBtnReplaceAll" value="Replace All" onClick="replace_all();">
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3" height="5"></td>
+ </tr>
+ <tr>
+ <td>
+ <input class="buttonDefault" type="button" name="btnUndo" fckLang="DlgSpellBtnUndo" value="Undo" onClick="undo();"
+ disabled>
+ </td>
+ <td>&nbsp;&nbsp;</td>
+ <td>
+ <!-- by FredCK
+ <input class="buttonDefault" type="button" value="Close" onClick="end_spell();">
+ -->
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </form>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl
new file mode 100644
index 0000000..fae010d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl
@@ -0,0 +1,181 @@
+#!/usr/bin/perl
+
+use CGI qw/ :standard /;
+use File::Temp qw/ tempfile tempdir /;
+
+# my $spellercss = '/speller/spellerStyle.css'; # by FredCK
+my $spellercss = '../spellerStyle.css'; # by FredCK
+# my $wordWindowSrc = '/speller/wordWindow.js'; # by FredCK
+my $wordWindowSrc = '../wordWindow.js'; # by FredCK
+my @textinputs = param( 'textinputs[]' ); # array
+# my $aspell_cmd = 'aspell'; # by FredCK (for Linux)
+my $aspell_cmd = '"C:\Program Files\Aspell\bin\aspell.exe"'; # by FredCK (for Windows)
+my $lang = 'en_US';
+# my $aspell_opts = "-a --lang=$lang --encoding=utf-8"; # by FredCK
+my $aspell_opts = "-a --lang=$lang --encoding=utf-8 -H --rem-sgml-check=alt"; # by FredCK
+my $input_separator = "A";
+
+# set the 'wordtext' JavaScript variable to the submitted text.
+sub printTextVar {
+ for( my $i = 0; $i <= $#textinputs; $i++ ) {
+ print "textinputs[$i] = decodeURIComponent('" . escapeQuote( $textinputs[$i] ) . "')\n";
+ }
+}
+
+sub printTextIdxDecl {
+ my $idx = shift;
+ print "words[$idx] = [];\n";
+ print "suggs[$idx] = [];\n";
+}
+
+sub printWordsElem {
+ my( $textIdx, $wordIdx, $word ) = @_;
+ print "words[$textIdx][$wordIdx] = '" . escapeQuote( $word ) . "';\n";
+}
+
+sub printSuggsElem {
+ my( $textIdx, $wordIdx, @suggs ) = @_;
+ print "suggs[$textIdx][$wordIdx] = [";
+ for my $i ( 0..$#suggs ) {
+ print "'" . escapeQuote( $suggs[$i] ) . "'";
+ if( $i < $#suggs ) {
+ print ", ";
+ }
+ }
+ print "];\n";
+}
+
+sub printCheckerResults {
+ my $textInputIdx = -1;
+ my $wordIdx = 0;
+ my $unhandledText;
+ # create temp file
+ my $dir = tempdir( CLEANUP => 1 );
+ my( $fh, $tmpfilename ) = tempfile( DIR => $dir );
+
+ # temp file was created properly?
+
+ # open temp file, add the submitted text.
+ for( my $i = 0; $i <= $#textinputs; $i++ ) {
+ $text = url_decode( $textinputs[$i] );
+ # Strip all tags for the text. (by FredCK - #339 / #681)
+ $text =~ s/<[^>]+>/ /g;
+ @lines = split( /\n/, $text );
+ print $fh "\%\n"; # exit terse mode
+ print $fh "^$input_separator\n";
+ print $fh "!\n"; # enter terse mode
+ for my $line ( @lines ) {
+ # use carat on each line to escape possible aspell commands
+ print $fh "^$line\n";
+ }
+
+ }
+ # exec aspell command
+ my $cmd = "$aspell_cmd $aspell_opts < $tmpfilename 2>&1";
+ open ASPELL, "$cmd |" or handleError( "Could not execute `$cmd`\\n$!" ) and return;
+ # parse each line of aspell return
+ for my $ret ( <ASPELL> ) {
+ chomp( $ret );
+ # if '&', then not in dictionary but has suggestions
+ # if '#', then not in dictionary and no suggestions
+ # if '*', then it is a delimiter between text inputs
+ if( $ret =~ /^\*/ ) {
+ $textInputIdx++;
+ printTextIdxDecl( $textInputIdx );
+ $wordIdx = 0;
+
+ } elsif( $ret =~ /^(&|#)/ ) {
+ my @tokens = split( " ", $ret, 5 );
+ printWordsElem( $textInputIdx, $wordIdx, $tokens[1] );
+ my @suggs = ();
+ if( $tokens[4] ) {
+ @suggs = split( ", ", $tokens[4] );
+ }
+ printSuggsElem( $textInputIdx, $wordIdx, @suggs );
+ $wordIdx++;
+ } else {
+ $unhandledText .= $ret;
+ }
+ }
+ close ASPELL or handleError( "Error executing `$cmd`\\n$unhandledText" ) and return;
+}
+
+sub escapeQuote {
+ my $str = shift;
+ $str =~ s/'/\\'/g;
+ return $str;
+}
+
+sub handleError {
+ my $err = shift;
+ print "error = '" . escapeQuote( $err ) . "';\n";
+}
+
+sub url_decode {
+ local $_ = @_ ? shift : $_;
+ defined or return;
+ # change + signs to spaces
+ tr/+/ /;
+ # change hex escapes to the proper characters
+ s/%([a-fA-F0-9]{2})/pack "H2", $1/eg;
+ return $_;
+}
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Display HTML
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+print <<EOF;
+Content-type: text/html; charset=utf-8
+
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel="stylesheet" type="text/css" href="$spellercss"/>
+<script src="$wordWindowSrc"></script>
+<script type="text/javascript">
+var suggs = new Array();
+var words = new Array();
+var textinputs = new Array();
+var error;
+EOF
+
+printTextVar();
+
+printCheckerResults();
+
+print <<EOF;
+var wordWindowObj = new wordWindow();
+wordWindowObj.originalSpellings = words;
+wordWindowObj.suggestions = suggs;
+wordWindowObj.textInputs = textinputs;
+
+
+function init_spell() {
+ // check if any error occured during server-side processing
+ if( error ) {
+ alert( error );
+ } else {
+ // call the init_spell() function in the parent frameset
+ if (parent.frames.length) {
+ parent.init_spell( wordWindowObj );
+ } else {
+ error = "This page was loaded outside of a frameset. ";
+ error += "It might not display properly";
+ alert( error );
+ }
+ }
+}
+
+</script>
+
+</head>
+<body onLoad="init_spell();">
+
+<script type="text/javascript">
+wordWindowObj.writeBody();
+</script>
+
+</body>
+</html>
+EOF
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js
new file mode 100644
index 0000000..c85be9a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js
@@ -0,0 +1,461 @@
+////////////////////////////////////////////////////
+// spellChecker.js
+//
+// spellChecker object
+//
+// This file is sourced on web pages that have a textarea object to evaluate
+// for spelling. It includes the implementation for the spellCheckObject.
+//
+////////////////////////////////////////////////////
+
+
+// constructor
+function spellChecker( textObject ) {
+
+ // public properties - configurable
+// this.popUpUrl = '/speller/spellchecker.html'; // by FredCK
+ this.popUpUrl = 'fck_spellerpages/spellerpages/spellchecker.html'; // by FredCK
+ this.popUpName = 'spellchecker';
+// this.popUpProps = "menu=no,width=440,height=350,top=70,left=120,resizable=yes,status=yes"; // by FredCK
+ this.popUpProps = null ; // by FredCK
+// this.spellCheckScript = '/speller/server-scripts/spellchecker.php'; // by FredCK
+ //this.spellCheckScript = '/cgi-bin/spellchecker.pl';
+
+ // values used to keep track of what happened to a word
+ this.replWordFlag = "R"; // single replace
+ this.ignrWordFlag = "I"; // single ignore
+ this.replAllFlag = "RA"; // replace all occurances
+ this.ignrAllFlag = "IA"; // ignore all occurances
+ this.fromReplAll = "~RA"; // an occurance of a "replace all" word
+ this.fromIgnrAll = "~IA"; // an occurance of a "ignore all" word
+ // properties set at run time
+ this.wordFlags = new Array();
+ this.currentTextIndex = 0;
+ this.currentWordIndex = 0;
+ this.spellCheckerWin = null;
+ this.controlWin = null;
+ this.wordWin = null;
+ this.textArea = textObject; // deprecated
+ this.textInputs = arguments;
+
+ // private methods
+ this._spellcheck = _spellcheck;
+ this._getSuggestions = _getSuggestions;
+ this._setAsIgnored = _setAsIgnored;
+ this._getTotalReplaced = _getTotalReplaced;
+ this._setWordText = _setWordText;
+ this._getFormInputs = _getFormInputs;
+
+ // public methods
+ this.openChecker = openChecker;
+ this.startCheck = startCheck;
+ this.checkTextBoxes = checkTextBoxes;
+ this.checkTextAreas = checkTextAreas;
+ this.spellCheckAll = spellCheckAll;
+ this.ignoreWord = ignoreWord;
+ this.ignoreAll = ignoreAll;
+ this.replaceWord = replaceWord;
+ this.replaceAll = replaceAll;
+ this.terminateSpell = terminateSpell;
+ this.undo = undo;
+
+ // set the current window's "speller" property to the instance of this class.
+ // this object can now be referenced by child windows/frames.
+ window.speller = this;
+}
+
+// call this method to check all text boxes (and only text boxes) in the HTML document
+function checkTextBoxes() {
+ this.textInputs = this._getFormInputs( "^text$" );
+ this.openChecker();
+}
+
+// call this method to check all textareas (and only textareas ) in the HTML document
+function checkTextAreas() {
+ this.textInputs = this._getFormInputs( "^textarea$" );
+ this.openChecker();
+}
+
+// call this method to check all text boxes and textareas in the HTML document
+function spellCheckAll() {
+ this.textInputs = this._getFormInputs( "^text(area)?$" );
+ this.openChecker();
+}
+
+// call this method to check text boxe(s) and/or textarea(s) that were passed in to the
+// object's constructor or to the textInputs property
+function openChecker() {
+ this.spellCheckerWin = window.open( this.popUpUrl, this.popUpName, this.popUpProps );
+ if( !this.spellCheckerWin.opener ) {
+ this.spellCheckerWin.opener = window;
+ }
+}
+
+function startCheck( wordWindowObj, controlWindowObj ) {
+
+ // set properties from args
+ this.wordWin = wordWindowObj;
+ this.controlWin = controlWindowObj;
+
+ // reset properties
+ this.wordWin.resetForm();
+ this.controlWin.resetForm();
+ this.currentTextIndex = 0;
+ this.currentWordIndex = 0;
+ // initialize the flags to an array - one element for each text input
+ this.wordFlags = new Array( this.wordWin.textInputs.length );
+ // each element will be an array that keeps track of each word in the text
+ for( var i=0; i<this.wordFlags.length; i++ ) {
+ this.wordFlags[i] = [];
+ }
+
+ // start
+ this._spellcheck();
+
+ return true;
+}
+
+function ignoreWord() {
+ var wi = this.currentWordIndex;
+ var ti = this.currentTextIndex;
+ if( !this.wordWin ) {
+ alert( 'Error: Word frame not available.' );
+ return false;
+ }
+ if( !this.wordWin.getTextVal( ti, wi )) {
+ alert( 'Error: "Not in dictionary" text is missing.' );
+ return false;
+ }
+ // set as ignored
+ if( this._setAsIgnored( ti, wi, this.ignrWordFlag )) {
+ this.currentWordIndex++;
+ this._spellcheck();
+ }
+ return true;
+}
+
+function ignoreAll() {
+ var wi = this.currentWordIndex;
+ var ti = this.currentTextIndex;
+ if( !this.wordWin ) {
+ alert( 'Error: Word frame not available.' );
+ return false;
+ }
+ // get the word that is currently being evaluated.
+ var s_word_to_repl = this.wordWin.getTextVal( ti, wi );
+ if( !s_word_to_repl ) {
+ alert( 'Error: "Not in dictionary" text is missing' );
+ return false;
+ }
+
+ // set this word as an "ignore all" word.
+ this._setAsIgnored( ti, wi, this.ignrAllFlag );
+
+ // loop through all the words after this word
+ for( var i = ti; i < this.wordWin.textInputs.length; i++ ) {
+ for( var j = 0; j < this.wordWin.totalWords( i ); j++ ) {
+ if(( i == ti && j > wi ) || i > ti ) {
+ // future word: set as "from ignore all" if
+ // 1) do not already have a flag and
+ // 2) have the same value as current word
+ if(( this.wordWin.getTextVal( i, j ) == s_word_to_repl )
+ && ( !this.wordFlags[i][j] )) {
+ this._setAsIgnored( i, j, this.fromIgnrAll );
+ }
+ }
+ }
+ }
+
+ // finally, move on
+ this.currentWordIndex++;
+ this._spellcheck();
+ return true;
+}
+
+function replaceWord() {
+ var wi = this.currentWordIndex;
+ var ti = this.currentTextIndex;
+ if( !this.wordWin ) {
+ alert( 'Error: Word frame not available.' );
+ return false;
+ }
+ if( !this.wordWin.getTextVal( ti, wi )) {
+ alert( 'Error: "Not in dictionary" text is missing' );
+ return false;
+ }
+ if( !this.controlWin.replacementText ) {
+ return false ;
+ }
+ var txt = this.controlWin.replacementText;
+ if( txt.value ) {
+ var newspell = new String( txt.value );
+ if( this._setWordText( ti, wi, newspell, this.replWordFlag )) {
+ this.currentWordIndex++;
+ this._spellcheck();
+ }
+ }
+ return true;
+}
+
+function replaceAll() {
+ var ti = this.currentTextIndex;
+ var wi = this.currentWordIndex;
+ if( !this.wordWin ) {
+ alert( 'Error: Word frame not available.' );
+ return false;
+ }
+ var s_word_to_repl = this.wordWin.getTextVal( ti, wi );
+ if( !s_word_to_repl ) {
+ alert( 'Error: "Not in dictionary" text is missing' );
+ return false;
+ }
+ var txt = this.controlWin.replacementText;
+ if( !txt.value ) return false;
+ var newspell = new String( txt.value );
+
+ // set this word as a "replace all" word.
+ this._setWordText( ti, wi, newspell, this.replAllFlag );
+
+ // loop through all the words after this word
+ for( var i = ti; i < this.wordWin.textInputs.length; i++ ) {
+ for( var j = 0; j < this.wordWin.totalWords( i ); j++ ) {
+ if(( i == ti && j > wi ) || i > ti ) {
+ // future word: set word text to s_word_to_repl if
+ // 1) do not already have a flag and
+ // 2) have the same value as s_word_to_repl
+ if(( this.wordWin.getTextVal( i, j ) == s_word_to_repl )
+ && ( !this.wordFlags[i][j] )) {
+ this._setWordText( i, j, newspell, this.fromReplAll );
+ }
+ }
+ }
+ }
+
+ // finally, move on
+ this.currentWordIndex++;
+ this._spellcheck();
+ return true;
+}
+
+function terminateSpell() {
+ // called when we have reached the end of the spell checking.
+ var msg = ""; // by FredCK
+ var numrepl = this._getTotalReplaced();
+ if( numrepl == 0 ) {
+ // see if there were no misspellings to begin with
+ if( !this.wordWin ) {
+ msg = "";
+ } else {
+ if( this.wordWin.totalMisspellings() ) {
+// msg += "No words changed."; // by FredCK
+ msg += FCKLang.DlgSpellNoChanges ; // by FredCK
+ } else {
+// msg += "No misspellings found."; // by FredCK
+ msg += FCKLang.DlgSpellNoMispell ; // by FredCK
+ }
+ }
+ } else if( numrepl == 1 ) {
+// msg += "One word changed."; // by FredCK
+ msg += FCKLang.DlgSpellOneChange ; // by FredCK
+ } else {
+// msg += numrepl + " words changed."; // by FredCK
+ msg += FCKLang.DlgSpellManyChanges.replace( /%1/g, numrepl ) ;
+ }
+ if( msg ) {
+// msg += "\n"; // by FredCK
+ alert( msg );
+ }
+
+ if( numrepl > 0 ) {
+ // update the text field(s) on the opener window
+ for( var i = 0; i < this.textInputs.length; i++ ) {
+ // this.textArea.value = this.wordWin.text;
+ if( this.wordWin ) {
+ if( this.wordWin.textInputs[i] ) {
+ this.textInputs[i].value = this.wordWin.textInputs[i];
+ }
+ }
+ }
+ }
+
+ // return back to the calling window
+// this.spellCheckerWin.close(); // by FredCK
+ if ( typeof( this.OnFinished ) == 'function' ) // by FredCK
+ this.OnFinished(numrepl) ; // by FredCK
+
+ return true;
+}
+
+function undo() {
+ // skip if this is the first word!
+ var ti = this.currentTextIndex;
+ var wi = this.currentWordIndex;
+
+ if( this.wordWin.totalPreviousWords( ti, wi ) > 0 ) {
+ this.wordWin.removeFocus( ti, wi );
+
+ // go back to the last word index that was acted upon
+ do {
+ // if the current word index is zero then reset the seed
+ if( this.currentWordIndex == 0 && this.currentTextIndex > 0 ) {
+ this.currentTextIndex--;
+ this.currentWordIndex = this.wordWin.totalWords( this.currentTextIndex )-1;
+ if( this.currentWordIndex < 0 ) this.currentWordIndex = 0;
+ } else {
+ if( this.currentWordIndex > 0 ) {
+ this.currentWordIndex--;
+ }
+ }
+ } while (
+ this.wordWin.totalWords( this.currentTextIndex ) == 0
+ || this.wordFlags[this.currentTextIndex][this.currentWordIndex] == this.fromIgnrAll
+ || this.wordFlags[this.currentTextIndex][this.currentWordIndex] == this.fromReplAll
+ );
+
+ var text_idx = this.currentTextIndex;
+ var idx = this.currentWordIndex;
+ var preReplSpell = this.wordWin.originalSpellings[text_idx][idx];
+
+ // if we got back to the first word then set the Undo button back to disabled
+ if( this.wordWin.totalPreviousWords( text_idx, idx ) == 0 ) {
+ this.controlWin.disableUndo();
+ }
+
+ var i, j, origSpell ;
+ // examine what happened to this current word.
+ switch( this.wordFlags[text_idx][idx] ) {
+ // replace all: go through this and all the future occurances of the word
+ // and revert them all to the original spelling and clear their flags
+ case this.replAllFlag :
+ for( i = text_idx; i < this.wordWin.textInputs.length; i++ ) {
+ for( j = 0; j < this.wordWin.totalWords( i ); j++ ) {
+ if(( i == text_idx && j >= idx ) || i > text_idx ) {
+ origSpell = this.wordWin.originalSpellings[i][j];
+ if( origSpell == preReplSpell ) {
+ this._setWordText ( i, j, origSpell, undefined );
+ }
+ }
+ }
+ }
+ break;
+
+ // ignore all: go through all the future occurances of the word
+ // and clear their flags
+ case this.ignrAllFlag :
+ for( i = text_idx; i < this.wordWin.textInputs.length; i++ ) {
+ for( j = 0; j < this.wordWin.totalWords( i ); j++ ) {
+ if(( i == text_idx && j >= idx ) || i > text_idx ) {
+ origSpell = this.wordWin.originalSpellings[i][j];
+ if( origSpell == preReplSpell ) {
+ this.wordFlags[i][j] = undefined;
+ }
+ }
+ }
+ }
+ break;
+
+ // replace: revert the word to its original spelling
+ case this.replWordFlag :
+ this._setWordText ( text_idx, idx, preReplSpell, undefined );
+ break;
+ }
+
+ // For all four cases, clear the wordFlag of this word. re-start the process
+ this.wordFlags[text_idx][idx] = undefined;
+ this._spellcheck();
+ }
+}
+
+function _spellcheck() {
+ var ww = this.wordWin;
+
+ // check if this is the last word in the current text element
+ if( this.currentWordIndex == ww.totalWords( this.currentTextIndex) ) {
+ this.currentTextIndex++;
+ this.currentWordIndex = 0;
+ // keep going if we're not yet past the last text element
+ if( this.currentTextIndex < this.wordWin.textInputs.length ) {
+ this._spellcheck();
+ return;
+ } else {
+ this.terminateSpell();
+ return;
+ }
+ }
+
+ // if this is after the first one make sure the Undo button is enabled
+ if( this.currentWordIndex > 0 ) {
+ this.controlWin.enableUndo();
+ }
+
+ // skip the current word if it has already been worked on
+ if( this.wordFlags[this.currentTextIndex][this.currentWordIndex] ) {
+ // increment the global current word index and move on.
+ this.currentWordIndex++;
+ this._spellcheck();
+ } else {
+ var evalText = ww.getTextVal( this.currentTextIndex, this.currentWordIndex );
+ if( evalText ) {
+ this.controlWin.evaluatedText.value = evalText;
+ ww.setFocus( this.currentTextIndex, this.currentWordIndex );
+ this._getSuggestions( this.currentTextIndex, this.currentWordIndex );
+ }
+ }
+}
+
+function _getSuggestions( text_num, word_num ) {
+ this.controlWin.clearSuggestions();
+ // add suggestion in list for each suggested word.
+ // get the array of suggested words out of the
+ // three-dimensional array containing all suggestions.
+ var a_suggests = this.wordWin.suggestions[text_num][word_num];
+ if( a_suggests ) {
+ // got an array of suggestions.
+ for( var ii = 0; ii < a_suggests.length; ii++ ) {
+ this.controlWin.addSuggestion( a_suggests[ii] );
+ }
+ }
+ this.controlWin.selectDefaultSuggestion();
+}
+
+function _setAsIgnored( text_num, word_num, flag ) {
+ // set the UI
+ this.wordWin.removeFocus( text_num, word_num );
+ // do the bookkeeping
+ this.wordFlags[text_num][word_num] = flag;
+ return true;
+}
+
+function _getTotalReplaced() {
+ var i_replaced = 0;
+ for( var i = 0; i < this.wordFlags.length; i++ ) {
+ for( var j = 0; j < this.wordFlags[i].length; j++ ) {
+ if(( this.wordFlags[i][j] == this.replWordFlag )
+ || ( this.wordFlags[i][j] == this.replAllFlag )
+ || ( this.wordFlags[i][j] == this.fromReplAll )) {
+ i_replaced++;
+ }
+ }
+ }
+ return i_replaced;
+}
+
+function _setWordText( text_num, word_num, newText, flag ) {
+ // set the UI and form inputs
+ this.wordWin.setText( text_num, word_num, newText );
+ // keep track of what happened to this word:
+ this.wordFlags[text_num][word_num] = flag;
+ return true;
+}
+
+function _getFormInputs( inputPattern ) {
+ var inputs = new Array();
+ for( var i = 0; i < document.forms.length; i++ ) {
+ for( var j = 0; j < document.forms[i].elements.length; j++ ) {
+ if( document.forms[i].elements[j].type.match( inputPattern )) {
+ inputs[inputs.length] = document.forms[i].elements[j];
+ }
+ }
+ }
+ return inputs;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html
new file mode 100644
index 0000000..cbcd7db
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html
@@ -0,0 +1,71 @@
+
+<script>
+
+var wordWindow = null;
+var controlWindow = null;
+
+function init_spell( spellerWindow ) {
+
+ if( spellerWindow ) {
+ if( spellerWindow.windowType == "wordWindow" ) {
+ wordWindow = spellerWindow;
+ } else if ( spellerWindow.windowType == "controlWindow" ) {
+ controlWindow = spellerWindow;
+ }
+ }
+
+ if( controlWindow && wordWindow ) {
+ // populate the speller object and start it off!
+ var speller = opener.speller;
+ wordWindow.speller = speller;
+ speller.startCheck( wordWindow, controlWindow );
+ }
+}
+
+// encodeForPost
+function encodeForPost( str ) {
+ var s = new String( str );
+ s = encodeURIComponent( s );
+ // additionally encode single quotes to evade any PHP
+ // magic_quotes_gpc setting (it inserts escape characters and
+ // therefore skews the btye positions of misspelled words)
+ return s.replace( /\'/g, '%27' );
+}
+
+// post the text area data to the script that populates the speller
+function postWords() {
+ var bodyDoc = window.frames[0].document;
+ bodyDoc.open();
+ bodyDoc.write('<html>');
+ bodyDoc.write('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">');
+ bodyDoc.write('<link rel="stylesheet" type="text/css" href="spellerStyle.css"/>');
+ if (opener) {
+ var speller = opener.speller;
+ bodyDoc.write('<body class="normalText" onLoad="document.forms[0].submit();">');
+ bodyDoc.write('<p>' + window.parent.FCKLang.DlgSpellProgress + '<\/p>'); // by FredCK
+ bodyDoc.write('<form action="'+speller.spellCheckScript+'" method="post">');
+ for( var i = 0; i < speller.textInputs.length; i++ ) {
+ bodyDoc.write('<input type="hidden" name="textinputs[]" value="'+encodeForPost(speller.textInputs[i].value)+'">');
+ }
+ bodyDoc.write('<\/form>');
+ bodyDoc.write('<\/body>');
+ } else {
+ bodyDoc.write('<body class="normalText">');
+ bodyDoc.write('<p><b>This page cannot be displayed<\/b><\/p><p>The window was not opened from another window.<\/p>');
+ bodyDoc.write('<\/body>');
+ }
+ bodyDoc.write('<\/html>');
+ bodyDoc.close();
+}
+</script>
+
+<html>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<head>
+<title>Speller Pages</title>
+</head>
+<frameset rows="*,201" onLoad="postWords();">
+<frame src="blank.html">
+<frame src="controls.html">
+</frameset>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css
new file mode 100644
index 0000000..9928086
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css
@@ -0,0 +1,49 @@
+.blend {
+ font-family: courier new;
+ font-size: 10pt;
+ border: 0;
+ margin-bottom:-1;
+}
+.normalLabel {
+ font-size:8pt;
+}
+.normalText {
+ font-family:arial, helvetica, sans-serif;
+ font-size:10pt;
+ color:000000;
+ background-color:FFFFFF;
+}
+.plainText {
+ font-family: courier new, courier, monospace;
+ font-size: 10pt;
+ color:000000;
+ background-color:FFFFFF;
+}
+.controlWindowBody {
+ font-family:arial, helvetica, sans-serif;
+ font-size:8pt;
+ padding: 7px ; /* by FredCK */
+ margin: 0px ; /* by FredCK */
+ /* color:000000; by FredCK */
+ /* background-color:DADADA; by FredCK */
+}
+.readonlyInput {
+ background-color:DADADA;
+ color:000000;
+ font-size:8pt;
+ width:392px;
+}
+.textDefault {
+ font-size:8pt;
+ width: 200px;
+}
+.buttonDefault {
+ width:90px;
+ height:22px;
+ font-size:8pt;
+}
+.suggSlct {
+ width:200px;
+ margin-top:2;
+ font-size:8pt;
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js
new file mode 100644
index 0000000..7990296
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js
@@ -0,0 +1,272 @@
+////////////////////////////////////////////////////
+// wordWindow object
+////////////////////////////////////////////////////
+function wordWindow() {
+ // private properties
+ this._forms = [];
+
+ // private methods
+ this._getWordObject = _getWordObject;
+ //this._getSpellerObject = _getSpellerObject;
+ this._wordInputStr = _wordInputStr;
+ this._adjustIndexes = _adjustIndexes;
+ this._isWordChar = _isWordChar;
+ this._lastPos = _lastPos;
+
+ // public properties
+ this.wordChar = /[a-zA-Z]/;
+ this.windowType = "wordWindow";
+ this.originalSpellings = new Array();
+ this.suggestions = new Array();
+ this.checkWordBgColor = "pink";
+ this.normWordBgColor = "white";
+ this.text = "";
+ this.textInputs = new Array();
+ this.indexes = new Array();
+ //this.speller = this._getSpellerObject();
+
+ // public methods
+ this.resetForm = resetForm;
+ this.totalMisspellings = totalMisspellings;
+ this.totalWords = totalWords;
+ this.totalPreviousWords = totalPreviousWords;
+ //this.getTextObjectArray = getTextObjectArray;
+ this.getTextVal = getTextVal;
+ this.setFocus = setFocus;
+ this.removeFocus = removeFocus;
+ this.setText = setText;
+ //this.getTotalWords = getTotalWords;
+ this.writeBody = writeBody;
+ this.printForHtml = printForHtml;
+}
+
+function resetForm() {
+ if( this._forms ) {
+ for( var i = 0; i < this._forms.length; i++ ) {
+ this._forms[i].reset();
+ }
+ }
+ return true;
+}
+
+function totalMisspellings() {
+ var total_words = 0;
+ for( var i = 0; i < this.textInputs.length; i++ ) {
+ total_words += this.totalWords( i );
+ }
+ return total_words;
+}
+
+function totalWords( textIndex ) {
+ return this.originalSpellings[textIndex].length;
+}
+
+function totalPreviousWords( textIndex, wordIndex ) {
+ var total_words = 0;
+ for( var i = 0; i <= textIndex; i++ ) {
+ for( var j = 0; j < this.totalWords( i ); j++ ) {
+ if( i == textIndex && j == wordIndex ) {
+ break;
+ } else {
+ total_words++;
+ }
+ }
+ }
+ return total_words;
+}
+
+//function getTextObjectArray() {
+// return this._form.elements;
+//}
+
+function getTextVal( textIndex, wordIndex ) {
+ var word = this._getWordObject( textIndex, wordIndex );
+ if( word ) {
+ return word.value;
+ }
+}
+
+function setFocus( textIndex, wordIndex ) {
+ var word = this._getWordObject( textIndex, wordIndex );
+ if( word ) {
+ if( word.type == "text" ) {
+ word.focus();
+ word.style.backgroundColor = this.checkWordBgColor;
+ }
+ }
+}
+
+function removeFocus( textIndex, wordIndex ) {
+ var word = this._getWordObject( textIndex, wordIndex );
+ if( word ) {
+ if( word.type == "text" ) {
+ word.blur();
+ word.style.backgroundColor = this.normWordBgColor;
+ }
+ }
+}
+
+function setText( textIndex, wordIndex, newText ) {
+ var word = this._getWordObject( textIndex, wordIndex );
+ var beginStr;
+ var endStr;
+ if( word ) {
+ var pos = this.indexes[textIndex][wordIndex];
+ var oldText = word.value;
+ // update the text given the index of the string
+ beginStr = this.textInputs[textIndex].substring( 0, pos );
+ endStr = this.textInputs[textIndex].substring(
+ pos + oldText.length,
+ this.textInputs[textIndex].length
+ );
+ this.textInputs[textIndex] = beginStr + newText + endStr;
+
+ // adjust the indexes on the stack given the differences in
+ // length between the new word and old word.
+ var lengthDiff = newText.length - oldText.length;
+ this._adjustIndexes( textIndex, wordIndex, lengthDiff );
+
+ word.size = newText.length;
+ word.value = newText;
+ this.removeFocus( textIndex, wordIndex );
+ }
+}
+
+
+function writeBody() {
+ var d = window.document;
+ var is_html = false;
+
+ d.open();
+
+ // iterate through each text input.
+ for( var txtid = 0; txtid < this.textInputs.length; txtid++ ) {
+ var end_idx = 0;
+ var begin_idx = 0;
+ d.writeln( '<form name="textInput'+txtid+'">' );
+ var wordtxt = this.textInputs[txtid];
+ this.indexes[txtid] = [];
+
+ if( wordtxt ) {
+ var orig = this.originalSpellings[txtid];
+ if( !orig ) break;
+
+ //!!! plain text, or HTML mode?
+ d.writeln( '<div class="plainText">' );
+ // iterate through each occurrence of a misspelled word.
+ for( var i = 0; i < orig.length; i++ ) {
+ // find the position of the current misspelled word,
+ // starting at the last misspelled word.
+ // and keep looking if it's a substring of another word
+ do {
+ begin_idx = wordtxt.indexOf( orig[i], end_idx );
+ end_idx = begin_idx + orig[i].length;
+ // word not found? messed up!
+ if( begin_idx == -1 ) break;
+ // look at the characters immediately before and after
+ // the word. If they are word characters we'll keep looking.
+ var before_char = wordtxt.charAt( begin_idx - 1 );
+ var after_char = wordtxt.charAt( end_idx );
+ } while (
+ this._isWordChar( before_char )
+ || this._isWordChar( after_char )
+ );
+
+ // keep track of its position in the original text.
+ this.indexes[txtid][i] = begin_idx;
+
+ // write out the characters before the current misspelled word
+ for( var j = this._lastPos( txtid, i ); j < begin_idx; j++ ) {
+ // !!! html mode? make it html compatible
+ d.write( this.printForHtml( wordtxt.charAt( j )));
+ }
+
+ // write out the misspelled word.
+ d.write( this._wordInputStr( orig[i] ));
+
+ // if it's the last word, write out the rest of the text
+ if( i == orig.length-1 ){
+ d.write( printForHtml( wordtxt.substr( end_idx )));
+ }
+ }
+
+ d.writeln( '</div>' );
+
+ }
+ d.writeln( '</form>' );
+ }
+ //for ( var j = 0; j < d.forms.length; j++ ) {
+ // alert( d.forms[j].name );
+ // for( var k = 0; k < d.forms[j].elements.length; k++ ) {
+ // alert( d.forms[j].elements[k].name + ": " + d.forms[j].elements[k].value );
+ // }
+ //}
+
+ // set the _forms property
+ this._forms = d.forms;
+ d.close();
+}
+
+// return the character index in the full text after the last word we evaluated
+function _lastPos( txtid, idx ) {
+ if( idx > 0 )
+ return this.indexes[txtid][idx-1] + this.originalSpellings[txtid][idx-1].length;
+ else
+ return 0;
+}
+
+function printForHtml( n ) {
+ return n ; // by FredCK
+/*
+ var htmlstr = n;
+ if( htmlstr.length == 1 ) {
+ // do simple case statement if it's just one character
+ switch ( n ) {
+ case "\n":
+ htmlstr = '<br/>';
+ break;
+ case "<":
+ htmlstr = '&lt;';
+ break;
+ case ">":
+ htmlstr = '&gt;';
+ break;
+ }
+ return htmlstr;
+ } else {
+ htmlstr = htmlstr.replace( /</g, '&lt' );
+ htmlstr = htmlstr.replace( />/g, '&gt' );
+ htmlstr = htmlstr.replace( /\n/g, '<br/>' );
+ return htmlstr;
+ }
+*/
+}
+
+function _isWordChar( letter ) {
+ if( letter.search( this.wordChar ) == -1 ) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+function _getWordObject( textIndex, wordIndex ) {
+ if( this._forms[textIndex] ) {
+ if( this._forms[textIndex].elements[wordIndex] ) {
+ return this._forms[textIndex].elements[wordIndex];
+ }
+ }
+ return null;
+}
+
+function _wordInputStr( word ) {
+ var str = '<input readonly ';
+ str += 'class="blend" type="text" value="' + word + '" size="' + word.length + '">';
+ return str;
+}
+
+function _adjustIndexes( textIndex, wordIndex, lengthDiff ) {
+ for( var i = wordIndex + 1; i < this.originalSpellings[textIndex].length; i++ ) {
+ this.indexes[textIndex][i] = this.indexes[textIndex][i] + lengthDiff;
+ }
+}
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_table.html b/httemplate/elements/fckeditor/editor/dialog/fck_table.html
new file mode 100644
index 0000000..3eb85b4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_table.html
@@ -0,0 +1,440 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Table dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Table Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+var FCKDomTools = oEditor.FCKDomTools ;
+
+// Gets the table if there is one selected.
+var table ;
+var e = dialog.Selection.GetSelectedElement() ;
+var hasColumnHeaders ;
+
+if ( ( !e && document.location.search.substr(1) == 'Parent' ) || ( e && e.tagName != 'TABLE' ) )
+ e = oEditor.FCKSelection.MoveToAncestorNode( 'TABLE' ) ;
+
+if ( e && e.tagName == "TABLE" )
+ table = e ;
+
+// Fired when the window loading process is finished. It sets the fields with the
+// actual values if a table is selected in the editor.
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if (table)
+ {
+ document.getElementById('txtRows').value = table.rows.length ;
+ document.getElementById('txtColumns').value = table.rows[0].cells.length ;
+
+ // Gets the value from the Width or the Style attribute
+ var iWidth = (table.style.width ? table.style.width : table.width ) ;
+ var iHeight = (table.style.height ? table.style.height : table.height ) ;
+
+ if (iWidth.indexOf('%') >= 0) // Percentual = %
+ {
+ iWidth = parseInt( iWidth.substr(0,iWidth.length - 1), 10 ) ;
+ document.getElementById('selWidthType').value = "percent" ;
+ }
+ else if (iWidth.indexOf('px') >= 0) // Style Pixel = px
+ { //
+ iWidth = iWidth.substr(0,iWidth.length - 2);
+ document.getElementById('selWidthType').value = "pixels" ;
+ }
+
+ if (iHeight && iHeight.indexOf('px') >= 0) // Style Pixel = px
+ iHeight = iHeight.substr(0,iHeight.length - 2);
+
+ document.getElementById('txtWidth').value = iWidth || '' ;
+ document.getElementById('txtHeight').value = iHeight || '' ;
+ document.getElementById('txtBorder').value = GetAttribute( table, 'border', '' ) ;
+ document.getElementById('selAlignment').value = GetAttribute( table, 'align', '' ) ;
+ document.getElementById('txtCellPadding').value = GetAttribute( table, 'cellPadding', '' ) ;
+ document.getElementById('txtCellSpacing').value = GetAttribute( table, 'cellSpacing', '' ) ;
+ document.getElementById('txtSummary').value = GetAttribute( table, 'summary', '' ) ;
+// document.getElementById('cmbFontStyle').value = table.className ;
+
+ var eCaption = oEditor.FCKDomTools.GetFirstChild( table, 'CAPTION' ) ;
+ if ( eCaption ) document.getElementById('txtCaption').value = eCaption.innerHTML ;
+
+ hasColumnHeaders = true ;
+ // Check if all the first cells in every row are TH
+ for (var row=0; row<table.rows.length; row++)
+ {
+ // If just one cell isn't a TH then it isn't a header column
+ if ( table.rows[row].cells[0].nodeName != 'TH' )
+ {
+ hasColumnHeaders = false ;
+
+ break;
+ }
+ }
+
+ // Check if the table contains <thead>
+ if ((table.tHead !== null) )
+ {
+ if (hasColumnHeaders)
+ GetE('selHeaders').value = 'both' ;
+ else
+ GetE('selHeaders').value = 'row' ;
+ }
+ else
+ {
+ if (hasColumnHeaders)
+ GetE('selHeaders').value = 'col' ;
+ else
+ GetE('selHeaders').value = '' ;
+ }
+
+
+ document.getElementById('txtRows').disabled = true ;
+ document.getElementById('txtColumns').disabled = true ;
+ SelectField( 'txtWidth' ) ;
+ }
+ else
+ SelectField( 'txtRows' ) ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+}
+
+// Fired when the user press the OK button
+function Ok()
+{
+ var bExists = ( table != null ) ;
+
+ var oDoc = oEditor.FCK.EditorDocument ;
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ if ( ! bExists )
+ table = oDoc.createElement( "TABLE" ) ;
+
+ // Removes the Width and Height styles
+ if ( bExists && table.style.width ) table.style.width = null ; //.removeAttribute("width") ;
+ if ( bExists && table.style.height ) table.style.height = null ; //.removeAttribute("height") ;
+
+ var sWidth = GetE('txtWidth').value ;
+ if ( sWidth.length > 0 && GetE('selWidthType').value == 'percent' )
+ sWidth += '%' ;
+
+ SetAttribute( table, 'width' , sWidth ) ;
+ SetAttribute( table, 'height' , GetE('txtHeight').value ) ;
+ SetAttribute( table, 'border' , GetE('txtBorder').value ) ;
+ SetAttribute( table, 'align' , GetE('selAlignment').value ) ;
+ SetAttribute( table, 'cellPadding' , GetE('txtCellPadding').value ) ;
+ SetAttribute( table, 'cellSpacing' , GetE('txtCellSpacing').value ) ;
+ SetAttribute( table, 'summary' , GetE('txtSummary').value ) ;
+
+ var headers = GetE('selHeaders').value ;
+ if ( bExists )
+ {
+ // Should we make a <thead>?
+ if ( table.tHead==null && (headers=='row' || headers=='both') )
+ {
+ var oThead = table.createTHead() ;
+ var tbody = FCKDomTools.GetFirstChild( table, 'TBODY' ) ;
+ var theRow= FCKDomTools.GetFirstChild( tbody, 'TR' ) ;
+
+ //now change TD to TH:
+ for (var i = 0; i<theRow.childNodes.length ; i++)
+ {
+ var th = RenameNode(theRow.childNodes[i], 'TH') ;
+ if (th != null)
+ th.scope='col' ;
+ }
+ oThead.appendChild( theRow ) ;
+ }
+
+ if ( table.tHead!==null && !(headers=='row' || headers=='both') )
+ {
+ // Move the row out of the THead and put it in the TBody:
+ var tHead = table.tHead ;
+ var tbody = FCKDomTools.GetFirstChild( table, 'TBODY' ) ;
+
+ var previousFirstRow = tbody.firstChild ;
+ while ( tHead.firstChild )
+ {
+ var theRow = tHead.firstChild ;
+ for (var i = 0; i < theRow.childNodes.length ; i++ )
+ {
+ var newCell = RenameNode( theRow.childNodes[i], 'TD' ) ;
+ if ( newCell != null )
+ newCell.removeAttribute( 'scope' ) ;
+ }
+ tbody.insertBefore( theRow, previousFirstRow ) ;
+ }
+ table.removeChild( tHead ) ;
+ }
+
+ // Should we make all first cells in a row TH?
+ if ( (!hasColumnHeaders) && (headers=='col' || headers=='both') )
+ {
+ for( var row=0 ; row < table.rows.length ; row++ )
+ {
+ var newCell = RenameNode(table.rows[row].cells[0], 'TH') ;
+ if ( newCell != null )
+ newCell.scope = 'row' ;
+ }
+ }
+
+ // Should we make all first TH-cells in a row make TD? If 'yes' we do it the other way round :-)
+ if ( (hasColumnHeaders) && !(headers=='col' || headers=='both') )
+ {
+ for( var row=0 ; row < table.rows.length ; row++ )
+ {
+ var oRow = table.rows[row] ;
+ if ( oRow.parentNode.nodeName == 'TBODY' )
+ {
+ var newCell = RenameNode(oRow.cells[0], 'TD') ;
+ if (newCell != null)
+ newCell.removeAttribute( 'scope' ) ;
+ }
+ }
+ }
+ }
+
+ if (! bExists)
+ {
+ var iRows = GetE('txtRows').value ;
+ var iCols = GetE('txtColumns').value ;
+
+ var startRow = 0 ;
+ // Should we make a <thead> ?
+ if (headers=='row' || headers=='both')
+ {
+ startRow++ ;
+ var oThead = table.createTHead() ;
+ var oRow = table.insertRow(-1) ;
+ oThead.appendChild(oRow);
+
+ for ( var c = 0 ; c < iCols ; c++ )
+ {
+ var oThcell = oDoc.createElement( 'TH' ) ;
+ oThcell.scope = 'col' ;
+ oRow.appendChild( oThcell ) ;
+ if ( oEditor.FCKBrowserInfo.IsGeckoLike )
+ oEditor.FCKTools.AppendBogusBr( oThcell ) ;
+ }
+ }
+
+ // Opera automatically creates a tbody when a thead has been added
+ var oTbody = FCKDomTools.GetFirstChild( table, 'TBODY' ) ;
+ if ( !oTbody )
+ {
+ // make TBODY if it doesn't exist
+ oTbody = oDoc.createElement( 'TBODY' ) ;
+ table.appendChild( oTbody ) ;
+ }
+ for ( var r = startRow ; r < iRows; r++ )
+ {
+ var oRow = oDoc.createElement( 'TR' ) ;
+ oTbody.appendChild(oRow) ;
+
+ var startCol = 0 ;
+ // Is the first column a header?
+ if (headers=='col' || headers=='both')
+ {
+ var oThcell = oDoc.createElement( 'TH' ) ;
+ oThcell.scope = 'row' ;
+ oRow.appendChild( oThcell ) ;
+ if ( oEditor.FCKBrowserInfo.IsGeckoLike )
+ oEditor.FCKTools.AppendBogusBr( oThcell ) ;
+
+ startCol++ ;
+ }
+ for ( var c = startCol ; c < iCols ; c++ )
+ {
+ // IE will leave the TH at the end of the row if we use now oRow.insertCell(-1)
+ var oCell = oDoc.createElement( 'TD' ) ;
+ oRow.appendChild( oCell ) ;
+ if ( oEditor.FCKBrowserInfo.IsGeckoLike )
+ oEditor.FCKTools.AppendBogusBr( oCell ) ;
+ }
+ }
+
+ oEditor.FCK.InsertElement( table ) ;
+ }
+
+ var eCaption = oEditor.FCKDomTools.GetFirstChild( table, 'CAPTION' ) ;
+
+ if ( eCaption && !oEditor.FCKBrowserInfo.IsIE )
+ eCaption.parentNode.removeChild( eCaption ) ;
+
+ if ( document.getElementById('txtCaption').value != '' )
+ {
+ if ( !eCaption || !oEditor.FCKBrowserInfo.IsIE )
+ {
+ eCaption = oDoc.createElement( 'CAPTION' ) ;
+ table.insertBefore( eCaption, table.firstChild ) ;
+ }
+
+ eCaption.innerHTML = document.getElementById('txtCaption').value ;
+ }
+ else if ( bExists && eCaption )
+ {
+ // TODO: It causes an IE internal error if using removeChild or
+ // table.deleteCaption() (see #505).
+ if ( oEditor.FCKBrowserInfo.IsIE )
+ eCaption.innerHTML = '' ;
+ }
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table id="otable" cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 100%">
+ <tr>
+ <td>
+ <table cellspacing="1" cellpadding="1" width="100%" border="0">
+ <tr>
+ <td valign="top">
+ <table cellspacing="1" cellpadding="0" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgTableRows">Rows</span>:</td>
+ <td>
+ &nbsp;<input id="txtRows" type="text" maxlength="3" size="2" value="3"
+ onkeypress="return IsDigit(event);" /></td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTableColumns">Columns</span>:</td>
+ <td>
+ &nbsp;<input id="txtColumns" type="text" maxlength="2" size="2" value="2"
+ onkeypress="return IsDigit(event);" /></td>
+ </tr>
+ <tr>
+ <td><span fcklang="DlgTableHeaders">Headers</span>:</td>
+ <td>
+ &nbsp;<select id="selHeaders">
+ <option fcklang="DlgTableHeadersNone" value="">None</option>
+ <option fcklang="DlgTableHeadersRow" value="row">First row</option>
+ <option fcklang="DlgTableHeadersColumn" value="col">First column</option>
+ <option fcklang="DlgTableHeadersBoth" value="both">Both</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTableBorder">Border size</span>:</td>
+ <td>
+ &nbsp;<input id="txtBorder" type="text" maxlength="2" size="2" value="1"
+ onkeypress="return IsDigit(event);" /></td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTableAlign">Alignment</span>:</td>
+ <td>
+ &nbsp;<select id="selAlignment">
+ <option fcklang="DlgTableAlignNotSet" value="" selected="selected">&lt;Not set&gt;</option>
+ <option fcklang="DlgTableAlignLeft" value="left">Left</option>
+ <option fcklang="DlgTableAlignCenter" value="center">Center</option>
+ <option fcklang="DlgTableAlignRight" value="right">Right</option>
+ </select></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td align="right" valign="top">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgTableWidth">Width</span>:</td>
+ <td>
+ &nbsp;<input id="txtWidth" type="text" maxlength="4" size="3" value="200"
+ onkeypress="return IsDigit(event);" /></td>
+ <td>
+ &nbsp;<select id="selWidthType">
+ <option fcklang="DlgTableWidthPx" value="pixels" selected="selected">pixels</option>
+ <option fcklang="DlgTableWidthPc" value="percent">percent</option>
+ </select></td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTableHeight">Height</span>:</td>
+ <td>
+ &nbsp;<input id="txtHeight" type="text" maxlength="4" size="3" onkeypress="return IsDigit(event);" /></td>
+ <td>
+ &nbsp;<span fcklang="DlgTableWidthPx">pixels</span></td>
+ </tr>
+ <tr>
+ <td colspan="3">&nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgTableCellSpace">Cell spacing</span>:</td>
+ <td>
+ &nbsp;<input id="txtCellSpacing" type="text" maxlength="2" size="2" value="1"
+ onkeypress="return IsDigit(event);" /></td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgTableCellPad">Cell padding</span>:</td>
+ <td>
+ &nbsp;<input id="txtCellPadding" type="text" maxlength="2" size="2" value="1"
+ onkeypress="return IsDigit(event);" /></td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgTableCaption">Caption</span>:&nbsp;</td>
+ <td>
+ &nbsp;</td>
+ <td width="100%" nowrap="nowrap">
+ <input id="txtCaption" type="text" style="width: 100%" /></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgTableSummary">Summary</span>:&nbsp;</td>
+ <td>
+ &nbsp;</td>
+ <td width="100%" nowrap="nowrap">
+ <input id="txtSummary" type="text" style="width: 100%" /></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html b/httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html
new file mode 100644
index 0000000..a4d1c97
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html
@@ -0,0 +1,293 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Cell properties dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Table Cell Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+var FCKDomTools = oEditor.FCKDomTools ;
+
+// Array of selected Cells
+var aCells = oEditor.FCKTableHandler.GetSelectedCells() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage( document ) ;
+
+ SetStartupValue() ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtWidth' ) ;
+}
+
+function SetStartupValue()
+{
+ if ( aCells.length > 0 )
+ {
+ var oCell = aCells[0] ;
+ var iWidth = GetAttribute( oCell, 'width' ) ;
+
+ if ( iWidth.indexOf && iWidth.indexOf( '%' ) >= 0 )
+ {
+ iWidth = iWidth.substr( 0, iWidth.length - 1 ) ;
+ GetE('selWidthType').value = 'percent' ;
+ }
+
+ if ( oCell.attributes['noWrap'] != null && oCell.attributes['noWrap'].specified )
+ GetE('selWordWrap').value = !oCell.noWrap ;
+
+ GetE('txtWidth').value = iWidth ;
+ GetE('txtHeight').value = GetAttribute( oCell, 'height' ) ;
+ GetE('selHAlign').value = GetAttribute( oCell, 'align' ) ;
+ GetE('selVAlign').value = GetAttribute( oCell, 'vAlign' ) ;
+ GetE('txtRowSpan').value = GetAttribute( oCell, 'rowSpan' ) ;
+ GetE('txtCollSpan').value = GetAttribute( oCell, 'colSpan' ) ;
+ GetE('txtBackColor').value = GetAttribute( oCell, 'bgColor' ) ;
+ GetE('txtBorderColor').value = GetAttribute( oCell, 'borderColor' ) ;
+ GetE('selCellType').value = oCell.nodeName.toLowerCase() ;
+ }
+}
+
+// Fired when the user press the OK button
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ for( i = 0 ; i < aCells.length ; i++ )
+ {
+ if ( GetE('txtWidth').value.length > 0 )
+ aCells[i].width = GetE('txtWidth').value + ( GetE('selWidthType').value == 'percent' ? '%' : '') ;
+ else
+ aCells[i].removeAttribute( 'width', 0 ) ;
+
+ if ( GetE('selWordWrap').value == 'false' )
+ SetAttribute( aCells[i], 'noWrap', 'nowrap' ) ;
+ else
+ aCells[i].removeAttribute( 'noWrap' ) ;
+
+ SetAttribute( aCells[i], 'height' , GetE('txtHeight').value ) ;
+ SetAttribute( aCells[i], 'align' , GetE('selHAlign').value ) ;
+ SetAttribute( aCells[i], 'vAlign' , GetE('selVAlign').value ) ;
+ SetAttribute( aCells[i], 'rowSpan' , GetE('txtRowSpan').value ) ;
+ SetAttribute( aCells[i], 'colSpan' , GetE('txtCollSpan').value ) ;
+ SetAttribute( aCells[i], 'bgColor' , GetE('txtBackColor').value ) ;
+ SetAttribute( aCells[i], 'borderColor' , GetE('txtBorderColor').value ) ;
+
+ var cellType = GetE('selCellType').value ;
+ if ( aCells[i].nodeName.toLowerCase() != cellType )
+ aCells[i] = RenameNode( aCells[i], cellType ) ;
+ }
+
+ // The cells need to be reselected, otherwise the caret will appear inside the table borders (Gecko)
+ // or sent back to the beginning of the document (Opera and Safari).
+ // Strangely, IE works ok so no change is needed for IE.
+ if ( !oEditor.FCKBrowserInfo.IsIE )
+ {
+ var selection = oEditor.FCK.EditorWindow.getSelection() ;
+ selection.removeAllRanges() ;
+ for ( var i = 0 ; i < aCells.length ; i++ )
+ {
+ var range = oEditor.FCK.EditorDocument.createRange() ;
+ range.selectNode( aCells[i] ) ;
+ selection.addRange( range ) ;
+ }
+ }
+
+ return true ;
+}
+
+function SelectBackColor( color )
+{
+ if ( color && color.length > 0 )
+ GetE('txtBackColor').value = color ;
+}
+
+function SelectBorderColor( color )
+{
+ if ( color && color.length > 0 )
+ GetE('txtBorderColor').value = color ;
+}
+
+function SelectColor( wich )
+{
+ oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', oEditor.FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 410, 320, wich == 'Back' ? SelectBackColor : SelectBorderColor ) ;
+}
+
+ </script>
+</head>
+<body scroll="no" style="overflow: hidden">
+ <table cellspacing="0" cellpadding="0" width="100%" border="0" height="100%">
+ <tr>
+ <td>
+ <table cellspacing="1" cellpadding="1" width="100%" border="0">
+ <tr>
+ <td>
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellWidth">Width</span>:</td>
+ <td>
+ &nbsp;<input onkeypress="return IsDigit(event);" id="txtWidth" type="text" maxlength="4"
+ size="3" />&nbsp;<select id="selWidthType">
+ <option fcklang="DlgCellWidthPx" value="pixels" selected="selected">pixels</option>
+ <option fcklang="DlgCellWidthPc" value="percent">percent</option>
+ </select></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellHeight">Height</span>:</td>
+ <td>
+ &nbsp;<input id="txtHeight" type="text" maxlength="4" size="3" onkeypress="return IsDigit(event);" />&nbsp;<span
+ fcklang="DlgCellWidthPx">pixels</span></td>
+ </tr>
+ <tr>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellWordWrap">Word Wrap</span>:</td>
+ <td>
+ &nbsp;<select id="selWordWrap">
+ <option fcklang="DlgCellWordWrapYes" value="true" selected="selected">Yes</option>
+ <option fcklang="DlgCellWordWrapNo" value="false">No</option>
+ </select></td>
+ </tr>
+ <tr>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellHorAlign">Horizontal Alignment</span>:</td>
+ <td>
+ &nbsp;<select id="selHAlign">
+ <option fcklang="DlgCellHorAlignNotSet" value="" selected>&lt;Not set&gt;</option>
+ <option fcklang="DlgCellHorAlignLeft" value="left">Left</option>
+ <option fcklang="DlgCellHorAlignCenter" value="center">Center</option>
+ <option fcklang="DlgCellHorAlignRight" value="right">Right</option>
+ </select></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellVerAlign">Vertical Alignment</span>:</td>
+ <td>
+ &nbsp;<select id="selVAlign">
+ <option fcklang="DlgCellVerAlignNotSet" value="" selected>&lt;Not set&gt;</option>
+ <option fcklang="DlgCellVerAlignTop" value="top">Top</option>
+ <option fcklang="DlgCellVerAlignMiddle" value="middle">Middle</option>
+ <option fcklang="DlgCellVerAlignBottom" value="bottom">Bottom</option>
+ <option fcklang="DlgCellVerAlignBaseline" value="baseline">Baseline</option>
+ </select></td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td align="right">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellType">Cell Type</span>:</td>
+ <td colspan="2">
+ &nbsp; <select id="selCellType">
+ <option fcklang="DlgCellTypeData" value="td">Data</option>
+ <option fcklang="DlgCellTypeHeader" value="th">Header</option>
+ </select>
+ </tr>
+ <tr>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellRowSpan">Rows Span</span>:</td>
+ <td>
+ &nbsp;
+ <input onkeypress="return IsDigit(event);" id="txtRowSpan" type="text" maxlength="3" size="2"
+ ></td>
+ <td>
+ </td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellCollSpan">Columns Span</span>:</td>
+ <td>
+ &nbsp;
+ <input onkeypress="return IsDigit(event);" id="txtCollSpan" type="text" maxlength="2"
+ size="2"></td>
+ <td>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ <td>
+ &nbsp;</td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellBackColor">Background Color</span>:</td>
+ <td>
+ &nbsp;<input id="txtBackColor" type="text" size="8" /></td>
+ <td>
+ &nbsp;
+ <input type="button" fcklang="DlgCellBtnSelect" value="Select..." onclick="SelectColor( 'Back' )"></td>
+ </tr>
+ <tr>
+ <td nowrap="nowrap">
+ <span fcklang="DlgCellBorderColor">Border Color</span>:</td>
+ <td>
+ &nbsp;<input id="txtBorderColor" type="text" size="8" /></td>
+ <td>
+ &nbsp;
+ <input type="button" fcklang="DlgCellBtnSelect" value="Select..." onclick="SelectColor( 'Border' )" /></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_template.html b/httemplate/elements/fckeditor/editor/dialog/fck_template.html
new file mode 100644
index 0000000..e7c1292
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_template.html
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Template selection dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <style type="text/css">
+ .TplList
+ {
+ border: #dcdcdc 2px solid;
+ background-color: #ffffff;
+ overflow: auto;
+ width: 90%;
+ }
+
+ .TplItem
+ {
+ margin: 5px;
+ padding: 7px;
+ border: #eeeeee 1px solid;
+ }
+
+ .TplItem TABLE
+ {
+ display: inline;
+ }
+
+ .TplTitle
+ {
+ font-weight: bold;
+ }
+ </style>
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCK = oEditor.FCK ;
+var FCKLang = oEditor.FCKLang ;
+var FCKConfig = oEditor.FCKConfig ;
+
+window.onload = function()
+{
+ // Set the right box height (browser dependent).
+ GetE('eList').style.height = document.all ? '100%' : '295px' ;
+
+ // Translate the dialog box texts.
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ GetE('xChkReplaceAll').checked = ( FCKConfig.TemplateReplaceAll !== false ) ;
+
+ if ( FCKConfig.TemplateReplaceCheckbox !== false )
+ GetE('xReplaceBlock').style.display = '' ;
+
+ window.parent.SetAutoSize( true ) ;
+
+ LoadTemplatesXml() ;
+}
+
+function LoadTemplatesXml()
+{
+ var oTemplate ;
+
+ if ( !FCK._Templates )
+ {
+ GetE('eLoading').style.display = '' ;
+
+ // Create the Templates array.
+ FCK._Templates = new Array() ;
+
+ // Load the XML file.
+ var oXml = new oEditor.FCKXml() ;
+ oXml.LoadUrl( FCKConfig.TemplatesXmlPath ) ;
+
+ // Get the Images Base Path.
+ var oAtt = oXml.SelectSingleNode( 'Templates/@imagesBasePath' ) ;
+ var sImagesBasePath = oAtt ? oAtt.value : '' ;
+
+ // Get the "Template" nodes defined in the XML file.
+ var aTplNodes = oXml.SelectNodes( 'Templates/Template' ) ;
+
+ for ( var i = 0 ; i < aTplNodes.length ; i++ )
+ {
+ var oNode = aTplNodes[i] ;
+
+ oTemplate = new Object() ;
+
+ var oPart ;
+
+ // Get the Template Title.
+ if ( (oPart = oNode.attributes.getNamedItem('title')) )
+ oTemplate.Title = oPart.value ;
+ else
+ oTemplate.Title = 'Template ' + ( i + 1 ) ;
+
+ // Get the Template Description.
+ if ( (oPart = oXml.SelectSingleNode( 'Description', oNode )) )
+ oTemplate.Description = oPart.text ? oPart.text : oPart.textContent ;
+
+ // Get the Template Image.
+ if ( (oPart = oNode.attributes.getNamedItem('image')) )
+ oTemplate.Image = sImagesBasePath + oPart.value ;
+
+ // Get the Template HTML.
+ if ( (oPart = oXml.SelectSingleNode( 'Html', oNode )) )
+ oTemplate.Html = oPart.text ? oPart.text : oPart.textContent ;
+ else
+ {
+ alert( 'No HTML defined for template index ' + i + '. Please review the "' + FCKConfig.TemplatesXmlPath + '" file.' ) ;
+ continue ;
+ }
+
+ FCK._Templates[ FCK._Templates.length ] = oTemplate ;
+ }
+
+ GetE('eLoading').style.display = 'none' ;
+ }
+
+ if ( FCK._Templates.length == 0 )
+ GetE('eEmpty').style.display = '' ;
+ else
+ {
+ for ( var j = 0 ; j < FCK._Templates.length ; j++ )
+ {
+ oTemplate = FCK._Templates[j] ;
+
+ var oItemDiv = GetE('eList').appendChild( document.createElement( 'DIV' ) ) ;
+ oItemDiv.TplIndex = j ;
+ oItemDiv.className = 'TplItem' ;
+
+ // Build the inner HTML of our new item DIV.
+ var sInner = '<table><tr>' ;
+
+ if ( oTemplate.Image )
+ sInner += '<td valign="top"><img src="' + oTemplate.Image + '"><\/td>' ;
+
+ sInner += '<td valign="top"><div class="TplTitle">' + oTemplate.Title + '<\/div>' ;
+
+ if ( oTemplate.Description )
+ sInner += '<div>' + oTemplate.Description + '<\/div>' ;
+
+ sInner += '<\/td><\/tr><\/table>' ;
+
+ oItemDiv.innerHTML = sInner ;
+
+ oItemDiv.onmouseover = ItemDiv_OnMouseOver ;
+ oItemDiv.onmouseout = ItemDiv_OnMouseOut ;
+ oItemDiv.onclick = ItemDiv_OnClick ;
+ }
+ }
+}
+
+function ItemDiv_OnMouseOver()
+{
+ this.className += ' PopupSelectionBox' ;
+}
+
+function ItemDiv_OnMouseOut()
+{
+ this.className = this.className.replace( /\s*PopupSelectionBox\s*/, '' ) ;
+}
+
+function ItemDiv_OnClick()
+{
+ SelectTemplate( this.TplIndex ) ;
+}
+
+function SelectTemplate( index )
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ if ( GetE('xChkReplaceAll').checked )
+ FCK.SetData( FCK._Templates[index].Html ) ;
+ else
+ FCK.InsertHtml( FCK._Templates[index].Html ) ;
+
+ window.parent.Cancel( true ) ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table width="100%" style="height: 100%">
+ <tr>
+ <td align="center">
+ <span fcklang="DlgTemplatesSelMsg">Please select the template to open in the editor<br />
+ (the actual contents will be lost):</span>
+ </td>
+ </tr>
+ <tr>
+ <td height="100%" align="center">
+ <div id="eList" align="left" class="TplList">
+ <div id="eLoading" align="center" style="display: none">
+ <br />
+ <span fcklang="DlgTemplatesLoading">Loading templates list. Please wait...</span>
+ </div>
+ <div id="eEmpty" align="center" style="display: none">
+ <br />
+ <span fcklang="DlgTemplatesNoTpl">(No templates defined)</span>
+ </div>
+ </div>
+ </td>
+ </tr>
+ <tr id="xReplaceBlock" style="display: none">
+ <td>
+ <table cellpadding="0" cellspacing="0">
+ <tr>
+ <td>
+ <input id="xChkReplaceAll" type="checkbox" /></td>
+ <td>
+ &nbsp;</td>
+ <td>
+ <label for="xChkReplaceAll" fcklang="DlgTemplatesReplace">
+ Replace actual contents</label></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif
new file mode 100644
index 0000000..efdabbe
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif
new file mode 100644
index 0000000..d1cebb3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif
new file mode 100644
index 0000000..db41cb4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_textarea.html b/httemplate/elements/fckeditor/editor/dialog/fck_textarea.html
new file mode 100644
index 0000000..631fe27
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_textarea.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Text Area dialog window.
+-->
+<html>
+ <head>
+ <title>Text Area Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl && oActiveEl.tagName == 'TEXTAREA' )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtCols').value = GetAttribute( oActiveEl, 'cols' ) ;
+ GetE('txtRows').value = GetAttribute( oActiveEl, 'rows' ) ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'TEXTAREA', {name: GetE('txtName').value} ) ;
+
+ SetAttribute( oActiveEl, 'cols', GetE('txtCols').value ) ;
+ SetAttribute( oActiveEl, 'rows', GetE('txtRows').value ) ;
+
+ return true ;
+}
+
+ </script>
+ </head>
+ <body style="overflow: hidden">
+ <table height="100%" width="100%">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="80%">
+ <tr>
+ <td>
+ <span fckLang="DlgTextareaName">Name</span><br>
+ <input type="text" id="txtName" style="WIDTH: 100%">
+ <span fckLang="DlgTextareaCols">Collumns</span><br>
+ <input id="txtCols" type="text" size="5">
+ <br>
+ <span fckLang="DlgTextareaRows">Rows</span><br>
+ <input id="txtRows" type="text" size="5">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_textfield.html b/httemplate/elements/fckeditor/editor/dialog/fck_textfield.html
new file mode 100644
index 0000000..43f91a6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dialog/fck_textfield.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Text field dialog window.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta content="noindex, nofollow" name="robots" />
+ <script src="common/fck_dialog_common.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+
+// Gets the document DOM
+var oDOM = oEditor.FCK.EditorDocument ;
+
+var oActiveEl = dialog.Selection.GetSelectedElement() ;
+
+window.onload = function()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage(document) ;
+
+ if ( oActiveEl && oActiveEl.tagName == 'INPUT' && ( oActiveEl.type == 'text' || oActiveEl.type == 'password' ) )
+ {
+ GetE('txtName').value = oActiveEl.name ;
+ GetE('txtValue').value = oActiveEl.value ;
+ GetE('txtSize').value = GetAttribute( oActiveEl, 'size' ) ;
+ GetE('txtMax').value = GetAttribute( oActiveEl, 'maxLength' ) ;
+ GetE('txtType').value = oActiveEl.type ;
+ }
+ else
+ oActiveEl = null ;
+
+ dialog.SetOkButton( true ) ;
+ dialog.SetAutoSize( true ) ;
+ SelectField( 'txtName' ) ;
+}
+
+function Ok()
+{
+ if ( isNaN( GetE('txtMax').value ) || GetE('txtMax').value < 0 )
+ {
+ alert( "Maximum characters must be a positive number." ) ;
+ GetE('txtMax').focus() ;
+ return false ;
+ }
+ else if( isNaN( GetE('txtSize').value ) || GetE('txtSize').value < 0 )
+ {
+ alert( "Width must be a positive number." ) ;
+ GetE('txtSize').focus() ;
+ return false ;
+ }
+
+ oEditor.FCKUndo.SaveUndoStep() ;
+
+ oActiveEl = CreateNamedElement( oEditor, oActiveEl, 'INPUT', {name: GetE('txtName').value, type: GetE('txtType').value } ) ;
+
+ SetAttribute( oActiveEl, 'value' , GetE('txtValue').value ) ;
+ SetAttribute( oActiveEl, 'size' , GetE('txtSize').value ) ;
+ SetAttribute( oActiveEl, 'maxlength', GetE('txtMax').value ) ;
+
+ return true ;
+}
+
+ </script>
+</head>
+<body style="overflow: hidden">
+ <table width="100%" style="height: 100%">
+ <tr>
+ <td align="center">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td>
+ <span fcklang="DlgTextName">Name</span><br />
+ <input id="txtName" type="text" size="20" />
+ </td>
+ <td>
+ </td>
+ <td>
+ <span fcklang="DlgTextValue">Value</span><br />
+ <input id="txtValue" type="text" size="25" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTextCharWidth">Character Width</span><br />
+ <input id="txtSize" type="text" size="5" />
+ </td>
+ <td>
+ </td>
+ <td>
+ <span fcklang="DlgTextMaxChars">Maximum Characters</span><br />
+ <input id="txtMax" type="text" size="5" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span fcklang="DlgTextType">Type</span><br />
+ <select id="txtType">
+ <option value="text" selected="selected" fcklang="DlgTextTypeText">Text</option>
+ <option value="password" fcklang="DlgTextTypePass">Password</option>
+ </select>
+ </td>
+ <td>
+ &nbsp;</td>
+ <td>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dtd/fck_dtd_test.html b/httemplate/elements/fckeditor/editor/dtd/fck_dtd_test.html
new file mode 100644
index 0000000..c149d15
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dtd/fck_dtd_test.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>DTD Test Page</title>
+ <script type="text/javascript">
+
+ // Define an object for this test page, so the assignment to FCK.DTD works
+ var FCK = {} ;
+ </script>
+ <script type="text/javascript" src="../_source/internals/fcktools.js"></script>
+ <script type="text/javascript" src="fck_xhtml10transitional.js"></script>
+</head>
+<body>
+ <h1>
+ DTD Contents
+ </h1>
+ <table border="1">
+ <script type="text/javascript">
+
+for ( var p in FCK.DTD )
+{
+ document.write( '<tr><td><b>' + p + '</b></td><td>' ) ;
+
+ var isFirst = true ;
+
+ for ( var c in FCK.DTD[p] )
+ {
+ if ( !isFirst )
+ document.write( ', ' ) ;
+ isFirst = false ;
+
+ document.write( c ) ;
+ }
+
+
+ document.write( '</td></tr>' ) ;
+}
+ </script>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10strict.js b/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10strict.js
new file mode 100644
index 0000000..a39fcfc
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10strict.js
@@ -0,0 +1,116 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Contains the DTD mapping for XHTML 1.0 Strict.
+ * This file was automatically generated from the file: xhtml10-strict.dtd
+ */
+FCK.DTD = (function()
+{
+ var X = FCKTools.Merge ;
+
+ var H,I,J,K,C,L,M,A,B,D,E,G,N,F ;
+ A = {ins:1, del:1, script:1} ;
+ B = {hr:1, ul:1, div:1, blockquote:1, noscript:1, table:1, address:1, pre:1, p:1, h5:1, dl:1, h4:1, ol:1, h6:1, h1:1, h3:1, h2:1} ;
+ C = X({fieldset:1}, B) ;
+ D = X({sub:1, bdo:1, 'var':1, sup:1, br:1, kbd:1, map:1, samp:1, b:1, acronym:1, '#':1, abbr:1, code:1, i:1, cite:1, tt:1, strong:1, q:1, em:1, big:1, small:1, span:1, dfn:1}, A) ;
+ E = X({img:1, object:1}, D) ;
+ F = {input:1, button:1, textarea:1, select:1, label:1} ;
+ G = X({a:1}, F) ;
+ H = {img:1, noscript:1, br:1, kbd:1, button:1, h5:1, h4:1, samp:1, h6:1, ol:1, h1:1, h3:1, h2:1, form:1, select:1, '#':1, ins:1, abbr:1, label:1, code:1, table:1, script:1, cite:1, input:1, strong:1, textarea:1, big:1, small:1, span:1, hr:1, sub:1, bdo:1, 'var':1, div:1, object:1, sup:1, map:1, dl:1, del:1, fieldset:1, ul:1, b:1, acronym:1, a:1, blockquote:1, i:1, address:1, tt:1, q:1, pre:1, p:1, em:1, dfn:1} ;
+
+ I = X({form:1, fieldset:1}, B, E, G) ;
+ J = {tr:1} ;
+ K = {'#':1} ;
+ L = X(E, G) ;
+ M = {li:1} ;
+ N = X({form:1}, A, C) ;
+
+ return {
+ col: {},
+ tr: {td:1, th:1},
+ img: {},
+ colgroup: {col:1},
+ noscript: N,
+ td: I,
+ br: {},
+ th: I,
+ kbd: L,
+ button: X(B, E),
+ h5: L,
+ h4: L,
+ samp: L,
+ h6: L,
+ ol: M,
+ h1: L,
+ h3: L,
+ option: K,
+ h2: L,
+ form: X(A, C),
+ select: {optgroup:1, option:1},
+ ins: I,
+ abbr: L,
+ label: L,
+ code: L,
+ table: {thead:1, col:1, tbody:1, tr:1, colgroup:1, caption:1, tfoot:1},
+ script: K,
+ tfoot: J,
+ cite: L,
+ li: I,
+ input: {},
+ strong: L,
+ textarea: K,
+ big: L,
+ small: L,
+ span: L,
+ dt: L,
+ hr: {},
+ sub: L,
+ optgroup: {option:1},
+ bdo: L,
+ param: {},
+ 'var': L,
+ div: I,
+ object: X({param:1}, H),
+ sup: L,
+ dd: I,
+ area: {},
+ map: X({form:1, area:1}, A, C),
+ dl: {dt:1, dd:1},
+ del: I,
+ fieldset: X({legend:1}, H),
+ thead: J,
+ ul: M,
+ acronym: L,
+ b: L,
+ a: X({img:1, object:1}, D, F),
+ blockquote: N,
+ caption: L,
+ i: L,
+ tbody: J,
+ address: L,
+ tt: L,
+ legend: L,
+ q: L,
+ pre: X({a:1}, D, F),
+ p: L,
+ em: L,
+ dfn: L
+ } ;
+})() ;
diff --git a/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10transitional.js b/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10transitional.js
new file mode 100644
index 0000000..53617bd
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/dtd/fck_xhtml10transitional.js
@@ -0,0 +1,140 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Contains the DTD mapping for XHTML 1.0 Transitional.
+ * This file was automatically generated from the file: xhtml10-transitional.dtd
+ */
+FCK.DTD = (function()
+{
+ var X = FCKTools.Merge ;
+
+ var A,L,J,M,N,O,D,H,P,K,Q,F,G,C,B,E,I ;
+ A = {isindex:1, fieldset:1} ;
+ B = {input:1, button:1, select:1, textarea:1, label:1} ;
+ C = X({a:1}, B) ;
+ D = X({iframe:1}, C) ;
+ E = {hr:1, ul:1, menu:1, div:1, blockquote:1, noscript:1, table:1, center:1, address:1, dir:1, pre:1, h5:1, dl:1, h4:1, noframes:1, h6:1, ol:1, h1:1, h3:1, h2:1} ;
+ F = {ins:1, del:1, script:1} ;
+ G = X({b:1, acronym:1, bdo:1, 'var':1, '#':1, abbr:1, code:1, br:1, i:1, cite:1, kbd:1, u:1, strike:1, s:1, tt:1, strong:1, q:1, samp:1, em:1, dfn:1, span:1}, F) ;
+ H = X({sub:1, img:1, object:1, sup:1, basefont:1, map:1, applet:1, font:1, big:1, small:1}, G) ;
+ I = X({p:1}, H) ;
+ J = X({iframe:1}, H, B) ;
+ K = {img:1, noscript:1, br:1, kbd:1, center:1, button:1, basefont:1, h5:1, h4:1, samp:1, h6:1, ol:1, h1:1, h3:1, h2:1, form:1, font:1, '#':1, select:1, menu:1, ins:1, abbr:1, label:1, code:1, table:1, script:1, cite:1, input:1, iframe:1, strong:1, textarea:1, noframes:1, big:1, small:1, span:1, hr:1, sub:1, bdo:1, 'var':1, div:1, object:1, sup:1, strike:1, dir:1, map:1, dl:1, applet:1, del:1, isindex:1, fieldset:1, ul:1, b:1, acronym:1, a:1, blockquote:1, i:1, u:1, s:1, tt:1, address:1, q:1, pre:1, p:1, em:1, dfn:1} ;
+
+ L = X({a:1}, J) ;
+ M = {tr:1} ;
+ N = {'#':1} ;
+ O = X({param:1}, K) ;
+ P = X({form:1}, A, D, E, I) ;
+ Q = {li:1} ;
+
+ return {
+ col: {},
+ tr: {td:1, th:1},
+ img: {},
+ colgroup: {col:1},
+ noscript: P,
+ td: P,
+ br: {},
+ th: P,
+ center: P,
+ kbd: L,
+ button: X(I, E),
+ basefont: {},
+ h5: L,
+ h4: L,
+ samp: L,
+ h6: L,
+ ol: Q,
+ h1: L,
+ h3: L,
+ option: N,
+ h2: L,
+ form: X(A, D, E, I),
+ select: {optgroup:1, option:1},
+ font: J, // Changed from L to J (see (1))
+ ins: P,
+ menu: Q,
+ abbr: L,
+ label: L,
+ table: {thead:1, col:1, tbody:1, tr:1, colgroup:1, caption:1, tfoot:1},
+ code: L,
+ script: N,
+ tfoot: M,
+ cite: L,
+ li: P,
+ input: {},
+ iframe: P,
+ strong: J, // Changed from L to J (see (1))
+ textarea: N,
+ noframes: P,
+ big: J, // Changed from L to J (see (1))
+ small: J, // Changed from L to J (see (1))
+ span: J, // Changed from L to J (see (1))
+ hr: {},
+ dt: L,
+ sub: J, // Changed from L to J (see (1))
+ optgroup: {option:1},
+ param: {},
+ bdo: L,
+ 'var': J, // Changed from L to J (see (1))
+ div: P,
+ object: O,
+ sup: J, // Changed from L to J (see (1))
+ dd: P,
+ strike: J, // Changed from L to J (see (1))
+ area: {},
+ dir: Q,
+ map: X({area:1, form:1, p:1}, A, F, E),
+ applet: O,
+ dl: {dt:1, dd:1},
+ del: P,
+ isindex: {},
+ fieldset: X({legend:1}, K),
+ thead: M,
+ ul: Q,
+ acronym: L,
+ b: J, // Changed from L to J (see (1))
+ a: J,
+ blockquote: P,
+ caption: L,
+ i: J, // Changed from L to J (see (1))
+ u: J, // Changed from L to J (see (1))
+ tbody: M,
+ s: L,
+ address: X(D, I),
+ tt: J, // Changed from L to J (see (1))
+ legend: L,
+ q: L,
+ pre: X(G, C),
+ p: L,
+ em: J, // Changed from L to J (see (1))
+ dfn: L
+ } ;
+})() ;
+
+/*
+ Notes:
+ (1) According to the DTD, many elements, like <b> accept <a> elements
+ inside of them. But, to produce better output results, we have manually
+ changed the map to avoid breaking the links on pieces, having
+ "<b>this is a </b><a><b>link</b> test</a>", instead of
+ "<b>this is a <a>link</a></b><a> test</a>".
+*/
diff --git a/httemplate/elements/fckeditor/editor/fckdebug.html b/httemplate/elements/fckeditor/editor/fckdebug.html
new file mode 100644
index 0000000..e3b3420
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/fckdebug.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the Debug window.
+ * It automatically popups if the Debug = true in the configuration file.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>FCKeditor Debug Window</title>
+ <meta name="robots" content="noindex, nofollow" />
+ <script type="text/javascript">
+
+var oWindow ;
+var oDiv ;
+
+if ( !window.FCKMessages )
+ window.FCKMessages = new Array() ;
+
+window.onload = function()
+{
+ oWindow = document.getElementById('xOutput').contentWindow ;
+ oWindow.document.open() ;
+ oWindow.document.write( '<div id="divMsg"><\/div>' ) ;
+ oWindow.document.close() ;
+ oDiv = oWindow.document.getElementById('divMsg') ;
+}
+
+function Output( message, color, noParse )
+{
+ if ( !noParse && message != null && isNaN( message ) )
+ message = message.replace(/</g, "&lt;") ;
+
+ if ( color )
+ message = '<font color="' + color + '">' + message + '<\/font>' ;
+
+ window.FCKMessages[ window.FCKMessages.length ] = message ;
+ StartTimer() ;
+}
+
+function OutputObject( anyObject, color )
+{
+ var message ;
+
+ if ( anyObject != null )
+ {
+ message = 'Properties of: ' + anyObject + '</b><blockquote>' ;
+
+ for (var prop in anyObject)
+ {
+ try
+ {
+ var sVal = anyObject[ prop ] != null ? anyObject[ prop ] + '' : '[null]' ;
+ message += '<b>' + prop + '</b> : ' + sVal.replace(/</g, '&lt;') + '<br>' ;
+ }
+ catch (e)
+ {
+ try
+ {
+ message += '<b>' + prop + '</b> : [' + typeof( anyObject[ prop ] ) + ']<br>' ;
+ }
+ catch (e)
+ {
+ message += '<b>' + prop + '</b> : [-error-]<br>' ;
+ }
+ }
+ }
+
+ message += '</blockquote><b>' ;
+ } else
+ message = 'OutputObject : Object is "null".' ;
+
+ Output( message, color, true ) ;
+}
+
+function StartTimer()
+{
+ window.setTimeout( 'CheckMessages()', 100 ) ;
+}
+
+function CheckMessages()
+{
+ if ( window.FCKMessages.length > 0 )
+ {
+ // Get the first item in the queue
+ var sMessage = window.FCKMessages[0] ;
+
+ // Removes the first item from the queue
+ var oTempArray = new Array() ;
+ for ( i = 1 ; i < window.FCKMessages.length ; i++ )
+ oTempArray[ i - 1 ] = window.FCKMessages[ i ] ;
+ window.FCKMessages = oTempArray ;
+
+ var d = new Date() ;
+ var sTime =
+ ( d.getHours() + 100 + '' ).substr( 1,2 ) + ':' +
+ ( d.getMinutes() + 100 + '' ).substr( 1,2 ) + ':' +
+ ( d.getSeconds() + 100 + '' ).substr( 1,2 ) + ':' +
+ ( d.getMilliseconds() + 1000 + '' ).substr( 1,3 ) ;
+
+ var oMsgDiv = oWindow.document.createElement( 'div' ) ;
+ oMsgDiv.innerHTML = sTime + ': <b>' + sMessage + '<\/b>' ;
+ oDiv.appendChild( oMsgDiv ) ;
+ oMsgDiv.scrollIntoView() ;
+ }
+}
+
+function Clear()
+{
+ oDiv.innerHTML = '' ;
+}
+ </script>
+</head>
+<body style="margin: 10px">
+ <table style="height: 100%" cellspacing="5" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td style="font-weight: bold; font-size: 1.2em;">
+ FCKeditor Debug Window</td>
+ <td align="right">
+ <input type="button" value="Clear" onclick="Clear();" /></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr style="height: 100%">
+ <td style="border: #696969 1px solid">
+ <iframe id="xOutput" width="100%" height="100%" scrolling="auto" src="javascript:void(0)"
+ frameborder="0"></iframe>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/fckdialog.html b/httemplate/elements/fckeditor/editor/fckdialog.html
new file mode 100644
index 0000000..0770c89
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/fckdialog.html
@@ -0,0 +1,819 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page is used by all dialog box as the container.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <script type="text/javascript">
+// <![CDATA[
+
+// Domain relaxation logic.
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var parentDomain = ( Args().TopWindow || E ).document.domain ;
+
+ if ( document.domain != parentDomain )
+ document.domain = parentDomain ;
+
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ document.domain = d ;
+ }
+})() ;
+
+var E = frameElement._DialogArguments.Editor ;
+
+// It seems referencing to frameElement._DialogArguments directly would lead to memory leaks in IE.
+// So let's use functions to access its members instead.
+function Args()
+{
+ return frameElement._DialogArguments ;
+}
+
+function ParentDialog( dialog )
+{
+ return dialog ? dialog._ParentDialog : frameElement._ParentDialog ;
+}
+
+var FCK = E.FCK ;
+var FCKTools = E.FCKTools ;
+var FCKDomTools = E.FCKDomTools ;
+var FCKDialog = E.FCKDialog ;
+var FCKBrowserInfo = E.FCKBrowserInfo ;
+var FCKConfig = E.FCKConfig ;
+
+// Steal the focus so that the caret would no longer stay in the editor iframe.
+window.focus() ;
+
+// Sets the Skin CSS
+document.write( FCKTools.GetStyleHtml( FCKConfig.SkinDialogCSS ) ) ;
+
+// Sets the language direction.
+var langDir = E.FCKLang.Dir ;
+
+// For IE6-, the fck_dialog_ie6.js is loaded, used to fix limitations in the browser.
+if ( FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsIE7 )
+ document.write( '<' + 'script type="text/javascript" src="' + FCKConfig.SkinPath + 'fck_dialog_ie6.js"><' + '\/script>' ) ;
+
+FCKTools.RegisterDollarFunction( window ) ;
+
+// Resize related functions.
+var Sizer = function()
+{
+ var bAutoSize = false ;
+
+ var retval = {
+ // Sets whether the dialog should auto-resize according to its content's height.
+ SetAutoSize : function( autoSize )
+ {
+ bAutoSize = autoSize ;
+ this.RefreshSize() ;
+ },
+
+ // Fit the dialog container's layout to the inner iframe's external size.
+ RefreshContainerSize : function()
+ {
+ var frmMain = $( 'frmMain' ) ;
+
+ if ( frmMain )
+ {
+ // Get the container size.
+ var height = $( 'contents' ).offsetHeight ;
+
+ // Subtract the size of other elements.
+ height -= $( 'TitleArea' ).offsetHeight ;
+ height -= $( 'TabsRow' ).offsetHeight ;
+ height -= $( 'PopupButtons' ).offsetHeight ;
+
+ frmMain.style.height = Math.max( height, 0 ) + 'px' ;
+ }
+ },
+
+ // Resize and re-layout the dialog.
+ // Triggers the onresize event for the layout logic.
+ ResizeDialog : function( width, height )
+ {
+ FCKDomTools.SetElementStyles( window.frameElement,
+ {
+ 'width' : width + 'px',
+ 'height' : height + 'px'
+ } ) ;
+
+ // If the skin have defined a function for resize fixes, call it now.
+ if ( typeof window.DoResizeFixes == 'function' )
+ window.DoResizeFixes() ;
+ },
+
+ // if bAutoSize is true, automatically fit the dialog size and layout to
+ // accomodate the inner iframe's internal height.
+ // if bAutoSize is false, then only the layout logic for the dialog decorations
+ // is run to accomodate the inner iframe's external height.
+ RefreshSize : function()
+ {
+ if ( bAutoSize )
+ {
+ var frmMain = $( 'frmMain' ) ;
+ var innerDoc = frmMain.contentWindow.document ;
+ var isStrict = FCKTools.IsStrictMode( innerDoc ) ;
+
+ // Get the size of the frame contents.
+ var innerWidth = isStrict ? innerDoc.documentElement.scrollWidth : innerDoc.body.scrollWidth ;
+ var innerHeight = isStrict ? innerDoc.documentElement.scrollHeight : innerDoc.body.scrollHeight ;
+
+ // Get the current frame size.
+ var frameSize = FCKTools.GetViewPaneSize( frmMain.contentWindow ) ;
+
+ var deltaWidth = innerWidth - frameSize.Width ;
+ var deltaHeight = innerHeight - frameSize.Height ;
+
+ // If the contents fits the current size.
+ if ( deltaWidth <= 0 && deltaHeight <= 0 )
+ return ;
+
+ var dialogWidth = frameElement.offsetWidth + Math.max( deltaWidth, 0 ) ;
+ var dialogHeight = frameElement.offsetHeight + Math.max( deltaHeight, 0 ) ;
+
+ this.ResizeDialog( dialogWidth, dialogHeight ) ;
+ }
+ this.RefreshContainerSize() ;
+ }
+ }
+
+ /**
+ * Safari seems to have a bug with the time when RefreshSize() is executed - it
+ * thinks frmMain's innerHeight is 0 if we query the value too soon after the
+ * page is loaded in some circumstances. (#1316)
+ * TODO : Maybe this is not needed anymore after #35.
+ */
+ if ( FCKBrowserInfo.IsSafari )
+ {
+ var originalRefreshSize = retval.RefreshSize ;
+
+ retval.RefreshSize = function()
+ {
+ FCKTools.SetTimeout( originalRefreshSize, 1, retval ) ;
+ }
+ }
+
+ /**
+ * IE6 has a similar bug where it sometimes thinks $('contents') has an
+ * offsetHeight of 0 (#2114).
+ */
+ if ( FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsIE7 )
+ {
+ var originalRefreshContainerSize = retval.RefreshContainerSize ;
+ retval.RefreshContainerSize = function()
+ {
+ FCKTools.SetTimeout( originalRefreshContainerSize, 1, retval ) ;
+ }
+ }
+
+ window.onresize = function()
+ {
+ retval.RefreshContainerSize() ;
+ }
+
+ window.SetAutoSize = FCKTools.Bind( retval, retval.SetAutoSize ) ;
+
+ return retval ;
+}() ;
+
+// Manages the throbber image that appears if the inner part of dialog is taking too long to load.
+var Throbber = function()
+{
+ var timer ;
+
+ var updateThrobber = function()
+ {
+ var throbberParent = $( 'throbberBlock' ) ;
+ var throbberBlocks = throbberParent.childNodes ;
+ var lastClass = throbberParent.lastChild.className ;
+
+ // From the last to the second one, copy the class from the previous one.
+ for ( var i = throbberBlocks.length - 1 ; i > 0 ; i-- )
+ throbberBlocks[i].className = throbberBlocks[i-1].className ;
+
+ // For the first one, copy the last class (rotation).
+ throbberBlocks[0].className = lastClass ;
+ }
+
+ return {
+ Show : function( waitMilliseconds )
+ {
+ // Auto-setup the Show function to be called again after the
+ // requested amount of time.
+ if ( waitMilliseconds && waitMilliseconds > 0 )
+ {
+ timer = FCKTools.SetTimeout( this.Show, waitMilliseconds, this, null, window ) ;
+ return ;
+ }
+
+ var throbberParent = $( 'throbberBlock' ) ;
+
+ if (throbberParent.childNodes.length == 0)
+ {
+ // Create the throbber blocks.
+ var classIds = [ 1,2,3,4,5,4,3,2 ] ;
+ while ( classIds.length > 0 )
+ throbberParent.appendChild( document.createElement( 'div' ) ).className = ' throbber_' + classIds.shift() ;
+ }
+
+ // Center the throbber.
+ var frm = $( 'contents' ) ;
+ var frmCoords = FCKTools.GetDocumentPosition( window, frm ) ;
+ var x = frmCoords.x + ( frm.offsetWidth - throbberParent.offsetWidth ) / 2 ;
+ var y = frmCoords.y + ( frm.offsetHeight - throbberParent.offsetHeight ) / 2 ;
+ throbberParent.style.left = parseInt( x, 10 ) + 'px' ;
+ throbberParent.style.top = parseInt( y, 10 ) + 'px' ;
+
+ // Show it.
+ throbberParent.style.visibility = '' ;
+
+ // Hide tabs and buttons:
+ $( 'Tabs' ).style.visibility = 'hidden' ;
+ $( 'PopupButtons' ).style.visibility = 'hidden' ;
+
+ // Setup the animation interval.
+ timer = setInterval( updateThrobber, 100 ) ;
+ },
+
+ Hide : function()
+ {
+ if ( timer )
+ {
+ clearInterval( timer ) ;
+ timer = null ;
+ }
+
+ $( 'throbberBlock' ).style.visibility = 'hidden' ;
+
+ // Show tabs and buttons:
+ $( 'Tabs' ).style.visibility = '' ;
+ $( 'PopupButtons' ).style.visibility = '' ;
+ }
+ } ;
+}() ;
+
+// Drag and drop handlers.
+var DragAndDrop = function()
+{
+ var registeredWindows = [] ;
+ var lastCoords ;
+ var currentPos ;
+
+ var cleanUpHandlers = function()
+ {
+ for ( var i = 0 ; i < registeredWindows.length ; i++ )
+ {
+ FCKTools.RemoveEventListener( registeredWindows[i].document, 'mousemove', dragMouseMoveHandler ) ;
+ FCKTools.RemoveEventListener( registeredWindows[i].document, 'mouseup', dragMouseUpHandler ) ;
+ }
+ }
+
+ var dragMouseMoveHandler = function( evt )
+ {
+ if ( !lastCoords )
+ return ;
+
+ if ( !evt )
+ evt = FCKTools.GetElementDocument( this ).parentWindow.event ;
+
+ // Updated the last coordinates.
+ var currentCoords =
+ {
+ x : evt.screenX,
+ y : evt.screenY
+ } ;
+
+ currentPos =
+ {
+ x : currentPos.x + ( currentCoords.x - lastCoords.x ),
+ y : currentPos.y + ( currentCoords.y - lastCoords.y )
+ } ;
+
+ lastCoords = currentCoords ;
+
+ frameElement.style.left = currentPos.x + 'px' ;
+ frameElement.style.top = currentPos.y + 'px' ;
+
+ if ( evt.preventDefault )
+ evt.preventDefault() ;
+ else
+ evt.returnValue = false ;
+ }
+
+ var dragMouseUpHandler = function( evt )
+ {
+ if ( !lastCoords )
+ return ;
+ if ( !evt )
+ evt = FCKTools.GetElementDocument( this ).parentWindow.event ;
+ cleanUpHandlers() ;
+ lastCoords = null ;
+ }
+
+ return {
+
+ MouseDownHandler : function( evt )
+ {
+ var view = null ;
+ if ( !evt )
+ {
+ view = FCKTools.GetElementDocument( this ).parentWindow ;
+ evt = view.event ;
+ }
+ else
+ view = evt.view ;
+
+ var target = evt.srcElement || evt.target ;
+ if ( target.id == 'closeButton' || target.className == 'PopupTab' || target.className == 'PopupTabSelected' )
+ return ;
+
+ lastCoords =
+ {
+ x : evt.screenX,
+ y : evt.screenY
+ } ;
+
+ // Save the current IFRAME position.
+ currentPos =
+ {
+ x : parseInt( FCKDomTools.GetCurrentElementStyle( frameElement, 'left' ), 10 ),
+ y : parseInt( FCKDomTools.GetCurrentElementStyle( frameElement, 'top' ), 10 )
+ } ;
+
+ for ( var i = 0 ; i < registeredWindows.length ; i++ )
+ {
+ FCKTools.AddEventListener( registeredWindows[i].document, 'mousemove', dragMouseMoveHandler ) ;
+ FCKTools.AddEventListener( registeredWindows[i].document, 'mouseup', dragMouseUpHandler ) ;
+ }
+
+ if ( evt.preventDefault )
+ evt.preventDefault() ;
+ else
+ evt.returnValue = false ;
+ },
+
+ RegisterHandlers : function( w )
+ {
+ registeredWindows.push( w ) ;
+ }
+ }
+}() ;
+
+// Selection related functions.
+//(Became simple shortcuts after the fix for #1990)
+var Selection =
+{
+ /**
+ * Ensures that the editing area contains an active selection. This is a
+ * requirement for IE, as it looses the selection when the focus moves to other
+ * frames.
+ */
+ EnsureSelection : function()
+ {
+ // Move the focus to the Cancel button so even if the dialog contains a
+ // contentEditable element the selection is properly restored in the editor #2496
+ window.focus() ;
+ $( 'btnCancel' ).focus() ;
+
+ FCK.Selection.Restore() ;
+ },
+
+ /**
+ * Get the FCKSelection object for the editor instance.
+ */
+ GetSelection : function()
+ {
+ return FCK.Selection ;
+ },
+
+ /**
+ * Get the selected element in the editing area (for object selections).
+ */
+ GetSelectedElement : function()
+ {
+ return FCK.Selection.GetSelectedElement() ;
+ }
+}
+
+// Tab related functions.
+var Tabs = function()
+{
+ // Only element ids should be stored here instead of element references since setSelectedTab and TabDiv_OnClick
+ // would build circular references with the element references inside and cause memory leaks in IE6.
+ var oTabs = new Object() ;
+
+ var setSelectedTab = function( tabCode )
+ {
+ for ( var sCode in oTabs )
+ {
+ if ( sCode == tabCode )
+ $( oTabs[sCode] ).className = 'PopupTabSelected' ;
+ else
+ $( oTabs[sCode] ).className = 'PopupTab' ;
+ }
+
+ if ( typeof( window.frames["frmMain"].OnDialogTabChange ) == 'function' )
+ window.frames["frmMain"].OnDialogTabChange( tabCode ) ;
+ }
+
+ function TabDiv_OnClick()
+ {
+ setSelectedTab( this.TabCode ) ;
+ }
+
+ window.AddTab = function( tabCode, tabText, startHidden )
+ {
+ if ( typeof( oTabs[ tabCode ] ) != 'undefined' )
+ return ;
+
+ var eTabsRow = $( 'Tabs' ) ;
+
+ var oCell = eTabsRow.insertCell( eTabsRow.cells.length - 1 ) ;
+ oCell.noWrap = true ;
+
+ var oDiv = document.createElement( 'DIV' ) ;
+ oDiv.className = 'PopupTab' ;
+ oDiv.innerHTML = tabText ;
+ oDiv.TabCode = tabCode ;
+ oDiv.onclick = TabDiv_OnClick ;
+ oDiv.id = Math.random() ;
+
+ if ( startHidden )
+ oDiv.style.display = 'none' ;
+
+ eTabsRow = $( 'TabsRow' ) ;
+
+ oCell.appendChild( oDiv ) ;
+
+ if ( eTabsRow.style.display == 'none' )
+ {
+ var eTitleArea = $( 'TitleArea' ) ;
+ eTitleArea.className = 'PopupTitle' ;
+
+ oDiv.className = 'PopupTabSelected' ;
+ eTabsRow.style.display = '' ;
+
+ if ( window.onresize )
+ window.onresize() ;
+ }
+
+ oTabs[ tabCode ] = oDiv.id ;
+
+ FCKTools.DisableSelection( oDiv ) ;
+ } ;
+
+ window.SetSelectedTab = setSelectedTab ;
+
+ window.SetTabVisibility = function( tabCode, isVisible )
+ {
+ var oTab = $( oTabs[ tabCode ] ) ;
+ oTab.style.display = isVisible ? '' : 'none' ;
+
+ if ( ! isVisible && oTab.className == 'PopupTabSelected' )
+ {
+ for ( var sCode in oTabs )
+ {
+ if ( $( oTabs[sCode] ).style.display != 'none' )
+ {
+ setSelectedTab( sCode ) ;
+ break ;
+ }
+ }
+ }
+ } ;
+}() ;
+
+// readystatechange handler for registering drag and drop handlers in cover
+// iframes, defined out here to avoid memory leak.
+// Do NOT put this function as a private function as it will induce memory leak
+// in IE and it's not detectable with Drip or sIEve and undetectable leaks are
+// really nasty (sigh).
+var onReadyRegister = function()
+{
+ if ( this.readyState != 'complete' )
+ return ;
+ DragAndDrop.RegisterHandlers( this.contentWindow ) ;
+} ;
+
+// The business logic of the dialog, dealing with operational things like
+// dialog open/dialog close/enable/disable/etc.
+(function()
+{
+ var setOnKeyDown = function( targetDocument )
+ {
+ targetDocument.onkeydown = function ( e )
+ {
+ e = e || event || this.parentWindow.event ;
+ switch ( e.keyCode )
+ {
+ case 13 : // ENTER
+ var oTarget = e.srcElement || e.target ;
+ if ( oTarget.tagName == 'TEXTAREA' )
+ return true ;
+ Ok() ;
+ return false ;
+
+ case 27 : // ESC
+ Cancel() ;
+ return false ;
+ }
+ return true ;
+ }
+ } ;
+
+ var contextMenuBlocker = function( e )
+ {
+ var sTagName = e.target.tagName ;
+ if ( ! ( ( sTagName == "INPUT" && e.target.type == "text" ) || sTagName == "TEXTAREA" ) )
+ e.preventDefault() ;
+ } ;
+
+ var disableContextMenu = function( targetDocument )
+ {
+ if ( FCKBrowserInfo.IsIE )
+ return ;
+
+ targetDocument.addEventListener( 'contextmenu', contextMenuBlocker, true ) ;
+ } ;
+
+ // Program entry point.
+ window.Init = function()
+ {
+ $( 'contents' ).dir = langDir;
+
+ // Start the throbber timer.
+ Throbber.Show( 1000 ) ;
+
+ Sizer.RefreshContainerSize() ;
+ LoadInnerDialog() ;
+
+ FCKTools.DisableSelection( document.body ) ;
+
+ // Make the title area draggable.
+ var titleElement = $( 'header' ) ;
+ titleElement.onmousedown = DragAndDrop.MouseDownHandler ;
+
+ // Connect mousemove and mouseup events from dialog frame and outer window to dialog dragging logic.
+ DragAndDrop.RegisterHandlers( window ) ;
+ DragAndDrop.RegisterHandlers( Args().TopWindow ) ;
+
+ // Disable the previous dialog if it exists.
+ if ( ParentDialog() )
+ {
+ ParentDialog().contentWindow.SetEnabled( false ) ;
+ if ( FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsIE7 )
+ {
+ var currentParent = ParentDialog() ;
+ while ( currentParent )
+ {
+ var blockerFrame = currentParent.contentWindow.$( 'blocker' ) ;
+ if ( blockerFrame.readyState == 'complete' )
+ DragAndDrop.RegisterHandlers( blockerFrame.contentWindow ) ;
+ else
+ blockerFrame.onreadystatechange = onReadyRegister ;
+ currentParent = ParentDialog( currentParent ) ;
+ }
+ }
+ else
+ {
+ var currentParent = ParentDialog() ;
+ while ( currentParent )
+ {
+ DragAndDrop.RegisterHandlers( currentParent.contentWindow ) ;
+ currentParent = ParentDialog( currentParent ) ;
+ }
+ }
+ }
+
+ // If this is the only dialog on screen, enable the background cover.
+ if ( FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsIE7 )
+ {
+ var blockerFrame = FCKDialog.GetCover().firstChild ;
+ if ( blockerFrame.readyState == 'complete' )
+ DragAndDrop.RegisterHandlers( blockerFrame.contentWindow ) ;
+ else
+ blockerFrame.onreadystatechange = onReadyRegister;
+ }
+
+ // Add Enter/Esc hotkeys and disable context menu for the dialog.
+ setOnKeyDown( document ) ;
+ disableContextMenu( document ) ;
+ } ;
+
+ window.LoadInnerDialog = function()
+ {
+ if ( window.onresize )
+ window.onresize() ;
+
+ // First of all, translate the dialog box contents.
+ E.FCKLanguageManager.TranslatePage( document ) ;
+
+ // Create the IFRAME that holds the dialog contents.
+ $( 'innerContents' ).innerHTML = '<iframe id="frmMain" src="' + Args().Page + '" name="frmMain" frameborder="0" width="100%" height="100%" scrolling="auto" style="visibility: hidden;" allowtransparency="true"><\/iframe>' ;
+ } ;
+
+ window.InnerDialogLoaded = function()
+ {
+ // If the dialog has been closed before the iframe is loaded, do nothing.
+ if ( !frameElement.parentNode )
+ return null ;
+
+ Throbber.Hide() ;
+
+ var frmMain = $('frmMain') ;
+ var innerWindow = frmMain.contentWindow ;
+ var innerDoc = innerWindow.document ;
+
+ // Show the loaded iframe.
+ frmMain.style.visibility = '' ;
+
+ // Set the language direction.
+ innerDoc.documentElement.dir = langDir ;
+
+ // Sets the Skin CSS.
+ innerDoc.write( FCKTools.GetStyleHtml( FCKConfig.SkinDialogCSS ) ) ;
+
+ setOnKeyDown( innerDoc ) ;
+ disableContextMenu( innerDoc ) ;
+
+ Sizer.RefreshContainerSize();
+
+ DragAndDrop.RegisterHandlers( innerWindow ) ;
+
+ innerWindow.focus() ;
+
+ return E ;
+ } ;
+
+ window.SetOkButton = function( showIt )
+ {
+ $('btnOk').style.visibility = ( showIt ? '' : 'hidden' ) ;
+ } ;
+
+ window.Ok = function()
+ {
+ Selection.EnsureSelection() ;
+
+ var frmMain = window.frames["frmMain"] ;
+
+ if ( frmMain.Ok && frmMain.Ok() )
+ CloseDialog() ;
+ else
+ frmMain.focus() ;
+ } ;
+
+ window.Cancel = function( dontFireChange )
+ {
+ Selection.EnsureSelection() ;
+ return CloseDialog( dontFireChange ) ;
+ } ;
+
+ window.CloseDialog = function( dontFireChange )
+ {
+ Throbber.Hide() ;
+
+ // Points the src to a non-existent location to avoid loading errors later, in case the dialog
+ // haven't been completed loaded at this point.
+ if ( $( 'frmMain' ) )
+ $( 'frmMain' ).src = FCKTools.GetVoidUrl() ;
+
+ if ( !dontFireChange && !FCK.EditMode )
+ {
+ // All dialog windows, by default, will fire the "OnSelectionChange"
+ // event, no matter the Ok or Cancel button has been pressed.
+ // It seems that OnSelectionChange may enter on a concurrency state
+ // on some situations (#1965), so we should put the event firing in
+ // the execution queue instead of executing it immediately.
+ setTimeout( function()
+ {
+ FCK.Events.FireEvent( 'OnSelectionChange' ) ;
+ }, 0 ) ;
+ }
+
+ FCKDialog.OnDialogClose( window ) ;
+ } ;
+
+ window.SetEnabled = function( isEnabled )
+ {
+ var cover = $( 'cover' ) ;
+ cover.style.display = isEnabled ? 'none' : '' ;
+
+ if ( FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsIE7 )
+ {
+ if ( !isEnabled )
+ {
+ // Inser the blocker IFRAME before the cover.
+ var blocker = document.createElement( 'iframe' ) ;
+ blocker.src = FCKTools.GetVoidUrl() ;
+ blocker.hideFocus = true ;
+ blocker.frameBorder = 0 ;
+ blocker.id = blocker.className = 'blocker' ;
+ cover.appendChild( blocker ) ;
+ }
+ else
+ {
+ var blocker = $( 'blocker' ) ;
+ if ( blocker && blocker.parentNode )
+ blocker.parentNode.removeChild( blocker ) ;
+ }
+ }
+ } ;
+})() ;
+// ]]>
+ </script>
+ </head>
+ <body onload="Init();" class="PopupBody">
+ <div class="contents" id="contents">
+ <div id="header">
+ <div id="TitleArea" class="PopupTitle PopupTitleBorder">
+ <script type="text/javascript">
+// <![CDATA[
+document.write( Args().Title ) ;
+// ]]>
+ </script>
+ <div id="closeButton" onclick="Cancel();"></div>
+ </div>
+ <div id="TabsRow" class="PopupTabArea" style="display: none">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tr id="Tabs">
+ <td class="PopupTabEmptyArea">&nbsp;</td>
+ <td class="PopupTabEmptyArea" width="100%">&nbsp;</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div id="innerContents"></div>
+ <div id="PopupButtons" class="PopupButtons">
+ <table border="0" cellpadding="0" cellspacing="0">
+ <tr>
+ <td width="100%">&nbsp;</td>
+ <td nowrap="nowrap">
+ <input id="btnOk" style="visibility: hidden;" type="button" value="Ok" class="Button" onclick="Ok();" fckLang="DlgBtnOK" />
+ &nbsp;
+ <input id="btnCancel" type="button" value="Cancel" class="Button" onclick="Cancel();" fckLang="DlgBtnCancel" />
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <div class="tl"></div>
+ <div class="tc"></div>
+ <div class="tr"></div>
+ <div class="ml"></div>
+ <div class="mr"></div>
+ <div class="bl"></div>
+ <div class="bc"></div>
+ <div class="br"></div>
+ <div class="cover" id="cover" style="display:none"></div>
+ <div id="throbberBlock" style="position: absolute; visibility: hidden"></div>
+ <script type="text/javascript">
+// <![CDATA[
+ // Set the class name for language direction.
+ document.body.className += ' ' + langDir ;
+
+ var cover = $( 'cover' ) ;
+ cover.style.backgroundColor = FCKConfig.BackgroundBlockerColor ;
+ FCKDomTools.SetOpacity( cover, FCKConfig.BackgroundBlockerOpacity ) ;
+// ]]>
+ </script>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/fckeditor.html b/httemplate/elements/fckeditor/editor/fckeditor.html
new file mode 100644
index 0000000..5f90c3d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/fckeditor.html
@@ -0,0 +1,317 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Main page that holds the editor.
+-->
+<html>
+<head>
+ <title>FCKeditor</title>
+ <meta name="robots" content="noindex, nofollow">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta http-equiv="Cache-Control" content="public">
+ <script type="text/javascript">
+
+// #1645: Alert the user if opening FCKeditor in FF3 from local filesystem
+// without security.fileuri.strict_origin_policy disabled.
+if ( document.location.protocol == 'file:' )
+{
+ try
+ {
+ window.parent.document.domain ;
+ }
+ catch ( e )
+ {
+ window.addEventListener( 'load', function()
+ {
+ document.body.innerHTML = '\
+ <div style="border: 1px red solid; font-family: arial; font-size: 12px; color: red; padding:10px;">\
+ <p>\
+ <b>Your browser security settings don\'t allow FCKeditor to be opened from\
+ the local filesystem.<\/b>\
+ <\/p>\
+ <p>\
+ Please open the <b>about:config<\/b> page and disable the\
+ &quot;security.fileuri.strict_origin_policy&quot; option; then load this page again.\
+ <\/p>\
+ <p>\
+ Check our <a href="http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/FAQ#ff3perms">FAQ<\/a>\
+ for more information.\
+ <\/p>\
+ <\/div>' ;
+ }, false ) ;
+ }
+}
+
+// Save a reference to the default domain.
+var FCK_ORIGINAL_DOMAIN ;
+
+// Automatically detect the correct document.domain (#123).
+(function()
+{
+ var d = FCK_ORIGINAL_DOMAIN = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.parent.document.domain ;
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+// Save a reference to the detected runtime domain.
+var FCK_RUNTIME_DOMAIN = document.domain ;
+
+var FCK_IS_CUSTOM_DOMAIN = ( FCK_ORIGINAL_DOMAIN != FCK_RUNTIME_DOMAIN ) ;
+
+// Instead of loading scripts and CSSs using inline tags, all scripts are
+// loaded by code. In this way we can guarantee the correct processing order,
+// otherwise external scripts and inline scripts could be executed in an
+// unwanted order (IE).
+
+function LoadScript( url )
+{
+ document.write( '<scr' + 'ipt type="text/javascript" src="' + url + '"><\/scr' + 'ipt>' ) ;
+}
+
+// Main editor scripts.
+var sSuffix = ( /*@cc_on!@*/false ) ? 'ie' : 'gecko' ;
+
+LoadScript( 'js/fckeditorcode_' + sSuffix + '.js' ) ;
+
+// Base configuration file.
+LoadScript( '../fckconfig.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Adobe AIR compatibility file.
+if ( FCKBrowserInfo.IsAIR )
+ LoadScript( 'js/fckadobeair.js' ) ;
+
+if ( FCKBrowserInfo.IsIE )
+{
+ // Remove IE mouse flickering.
+ try
+ {
+ document.execCommand( 'BackgroundImageCache', false, true ) ;
+ }
+ catch (e)
+ {
+ // We have been reported about loading problems caused by the above
+ // line. For safety, let's just ignore errors.
+ }
+
+ // Create the default cleanup object used by the editor.
+ FCK.IECleanup = new FCKIECleanup( window ) ;
+ FCK.IECleanup.AddItem( FCKTempBin, FCKTempBin.Reset ) ;
+ FCK.IECleanup.AddItem( FCK, FCK_Cleanup ) ;
+}
+
+// The first function to be called on selection change must the the styles
+// change checker, because the result of its processing may be used by another
+// functions listening to the same event.
+FCK.Events.AttachEvent( 'OnSelectionChange', function() { FCKStyles.CheckSelectionChanges() ; } ) ;
+
+// The config hidden field is processed immediately, because
+// CustomConfigurationsPath may be set in the page.
+FCKConfig.ProcessHiddenField() ;
+
+// Load the custom configurations file (if defined).
+if ( FCKConfig.CustomConfigurationsPath.length > 0 )
+ LoadScript( FCKConfig.CustomConfigurationsPath ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Load configurations defined at page level.
+FCKConfig_LoadPageConfig() ;
+
+FCKConfig_PreProcess() ;
+
+// Load the full debug script.
+if ( FCKConfig.Debug )
+ LoadScript( '_source/internals/fckdebug.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// CSS minified by http://iceyboard.no-ip.org/projects/css_compressor (see _dev/css_compression.txt).
+var FCK_InternalCSS = FCKTools.FixCssUrls( FCKConfig.BasePath + 'css/', 'html{min-height:100%}table.FCK__ShowTableBorders,table.FCK__ShowTableBorders td,table.FCK__ShowTableBorders th{border:#d3d3d3 1px solid}form{border:1px dotted #F00;padding:2px}.FCK__Flash{border:#a9a9a9 1px solid;background-position:center center;background-image:url(images/fck_flashlogo.gif);background-repeat:no-repeat;width:80px;height:80px}.FCK__UnknownObject{border:#a9a9a9 1px solid;background-position:center center;background-image:url(images/fck_plugin.gif);background-repeat:no-repeat;width:80px;height:80px}.FCK__Anchor{border:1px dotted #00F;background-position:center center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;width:16px;height:15px;vertical-align:middle}.FCK__AnchorC{border:1px dotted #00F;background-position:1px center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;padding-left:18px}a[name]{border:1px dotted #00F;background-position:0 center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;padding-left:18px}.FCK__PageBreak{background-position:center center;background-image:url(images/fck_pagebreak.gif);background-repeat:no-repeat;clear:both;display:block;float:none;width:100%;border-top:#999 1px dotted;border-bottom:#999 1px dotted;border-right:0;border-left:0;height:5px}.FCK__InputHidden{width:19px;height:18px;background-image:url(images/fck_hiddenfield.gif);background-repeat:no-repeat;vertical-align:text-bottom;background-position:center center}.FCK__ShowBlocks p,.FCK__ShowBlocks div,.FCK__ShowBlocks pre,.FCK__ShowBlocks address,.FCK__ShowBlocks blockquote,.FCK__ShowBlocks h1,.FCK__ShowBlocks h2,.FCK__ShowBlocks h3,.FCK__ShowBlocks h4,.FCK__ShowBlocks h5,.FCK__ShowBlocks h6{background-repeat:no-repeat;border:1px dotted gray;padding-top:8px;padding-left:8px}.FCK__ShowBlocks p{background-image:url(images/block_p.png)}.FCK__ShowBlocks div{background-image:url(images/block_div.png)}.FCK__ShowBlocks pre{background-image:url(images/block_pre.png)}.FCK__ShowBlocks address{background-image:url(images/block_address.png)}.FCK__ShowBlocks blockquote{background-image:url(images/block_blockquote.png)}.FCK__ShowBlocks h1{background-image:url(images/block_h1.png)}.FCK__ShowBlocks h2{background-image:url(images/block_h2.png)}.FCK__ShowBlocks h3{background-image:url(images/block_h3.png)}.FCK__ShowBlocks h4{background-image:url(images/block_h4.png)}.FCK__ShowBlocks h5{background-image:url(images/block_h5.png)}.FCK__ShowBlocks h6{background-image:url(images/block_h6.png)}' ) ;
+var FCK_ShowTableBordersCSS = FCKTools.FixCssUrls( FCKConfig.BasePath + 'css/', 'table:not([border]),table:not([border]) > tr > td,table:not([border]) > tr > th,table:not([border]) > tbody > tr > td,table:not([border]) > tbody > tr > th,table:not([border]) > thead > tr > td,table:not([border]) > thead > tr > th,table:not([border]) > tfoot > tr > td,table:not([border]) > tfoot > tr > th,table[border=\"0\"],table[border=\"0\"] > tr > td,table[border=\"0\"] > tr > th,table[border=\"0\"] > tbody > tr > td,table[border=\"0\"] > tbody > tr > th,table[border=\"0\"] > thead > tr > td,table[border=\"0\"] > thead > tr > th,table[border=\"0\"] > tfoot > tr > td,table[border=\"0\"] > tfoot > tr > th{border:#d3d3d3 1px dotted}' ) ;
+
+// Popup the debug window if debug mode is set to true. It guarantees that the
+// first debug message will not be lost.
+if ( FCKConfig.Debug )
+ FCKDebug._GetWindow() ;
+
+// Load the active skin CSS.
+document.write( FCKTools.GetStyleHtml( FCKConfig.SkinEditorCSS ) ) ;
+
+// Load the language file.
+FCKLanguageManager.Initialize() ;
+LoadScript( 'lang/' + FCKLanguageManager.ActiveLanguage.Code + '.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Initialize the editing area context menu.
+FCK_ContextMenu_Init() ;
+
+FCKPlugins.Load() ;
+
+ </script>
+ <script type="text/javascript">
+
+// Set the editor interface direction.
+window.document.dir = FCKLang.Dir ;
+
+ </script>
+ <script type="text/javascript">
+
+window.onload = function()
+{
+ InitializeAPI() ;
+
+ if ( FCKBrowserInfo.IsIE )
+ FCK_PreloadImages() ;
+ else
+ LoadToolbarSetup() ;
+}
+
+function LoadToolbarSetup()
+{
+ FCKeditorAPI._FunctionQueue.Add( LoadToolbar ) ;
+}
+
+function LoadToolbar()
+{
+ var oToolbarSet = FCK.ToolbarSet = FCKToolbarSet_Create() ;
+
+ if ( oToolbarSet.IsLoaded )
+ StartEditor() ;
+ else
+ {
+ oToolbarSet.OnLoad = StartEditor ;
+ oToolbarSet.Load( FCKURLParams['Toolbar'] || 'Default' ) ;
+ }
+}
+
+function StartEditor()
+{
+ // Remove the onload listener.
+ FCK.ToolbarSet.OnLoad = null ;
+
+ FCKeditorAPI._FunctionQueue.Remove( LoadToolbar ) ;
+
+ FCK.Events.AttachEvent( 'OnStatusChange', WaitForActive ) ;
+
+ // Start the editor.
+ FCK.StartEditor() ;
+}
+
+function WaitForActive( editorInstance, newStatus )
+{
+ if ( newStatus == FCK_STATUS_ACTIVE )
+ {
+ if ( FCKBrowserInfo.IsGecko )
+ FCKTools.RunFunction( window.onresize ) ;
+
+ if ( !FCKConfig.PreventSubmitHandler )
+ _AttachFormSubmitToAPI() ;
+
+ FCK.SetStatus( FCK_STATUS_COMPLETE ) ;
+
+ // Call the special "FCKeditor_OnComplete" function that should be present in
+ // the HTML page where the editor is located.
+ if ( typeof( window.parent.FCKeditor_OnComplete ) == 'function' )
+ window.parent.FCKeditor_OnComplete( FCK ) ;
+ }
+}
+
+// Gecko and Webkit browsers don't calculate well the IFRAME size so we must
+// recalculate it every time the window size changes.
+if ( FCKBrowserInfo.IsGecko || ( FCKBrowserInfo.IsSafari && !FCKBrowserInfo.IsSafari3 ) )
+{
+ window.onresize = function( e )
+ {
+ // Running in Firefox's chrome makes the window receive the event including subframes.
+ // we care only about this window. Ticket #1642.
+ // #2002: The originalTarget from the event can be the current document, the window, or the editing area.
+ if ( e && e.originalTarget && e.originalTarget !== document && e.originalTarget !== window && (!e.originalTarget.ownerDocument || e.originalTarget.ownerDocument != document ))
+ return ;
+
+ var oCell = document.getElementById( 'xEditingArea' ) ;
+
+ var eInnerElement = oCell.firstChild ;
+ if ( eInnerElement )
+ {
+ eInnerElement.style.height = '0px' ;
+ eInnerElement.style.height = ( oCell.scrollHeight - 2 ) + 'px' ;
+ }
+ }
+}
+
+ </script>
+</head>
+<body>
+ <table width="100%" cellpadding="0" cellspacing="0" style="height: 100%; table-layout: fixed">
+ <tr id="xToolbarRow" style="display: none">
+ <td id="xToolbarSpace" style="overflow: hidden">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <tr id="xCollapsed" style="display: none">
+ <td id="xExpandHandle" class="TB_Expand" colspan="3">
+ <img class="TB_ExpandImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>
+ </tr>
+ <tr id="xExpanded" style="display: none">
+ <td id="xTBLeftBorder" class="TB_SideBorder" style="width: 1px; display: none;"></td>
+ <td id="xCollapseHandle" style="display: none" class="TB_Collapse" valign="bottom">
+ <img class="TB_CollapseImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>
+ <td id="xToolbar" class="TB_ToolbarSet"></td>
+ <td class="TB_SideBorder" style="width: 1px"></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td id="xEditingArea" valign="top" style="height: 100%"></td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/fckeditor.original.html b/httemplate/elements/fckeditor/editor/fckeditor.original.html
new file mode 100644
index 0000000..34d76ca
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/fckeditor.original.html
@@ -0,0 +1,425 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Main page that holds the editor.
+-->
+<html>
+<head>
+ <title>FCKeditor</title>
+ <meta name="robots" content="noindex, nofollow">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <!-- @Packager.RemoveLine
+ <meta http-equiv="Cache-Control" content="public">
+ @Packager.RemoveLine -->
+ <script type="text/javascript">
+
+// #1645: Alert the user if opening FCKeditor in FF3 from local filesystem
+// without security.fileuri.strict_origin_policy disabled.
+if ( document.location.protocol == 'file:' )
+{
+ try
+ {
+ window.parent.document.domain ;
+ }
+ catch ( e )
+ {
+ window.addEventListener( 'load', function()
+ {
+ document.body.innerHTML = '\
+ <div style="border: 1px red solid; font-family: arial; font-size: 12px; color: red; padding:10px;">\
+ <p>\
+ <b>Your browser security settings don\'t allow FCKeditor to be opened from\
+ the local filesystem.<\/b>\
+ <\/p>\
+ <p>\
+ Please open the <b>about:config<\/b> page and disable the\
+ &quot;security.fileuri.strict_origin_policy&quot; option; then load this page again.\
+ <\/p>\
+ <p>\
+ Check our <a href="http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/FAQ#ff3perms">FAQ<\/a>\
+ for more information.\
+ <\/p>\
+ <\/div>' ;
+ }, false ) ;
+ }
+}
+
+// Save a reference to the default domain.
+var FCK_ORIGINAL_DOMAIN ;
+
+// Automatically detect the correct document.domain (#123).
+(function()
+{
+ var d = FCK_ORIGINAL_DOMAIN = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.parent.document.domain ;
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+// Save a reference to the detected runtime domain.
+var FCK_RUNTIME_DOMAIN = document.domain ;
+
+var FCK_IS_CUSTOM_DOMAIN = ( FCK_ORIGINAL_DOMAIN != FCK_RUNTIME_DOMAIN ) ;
+
+// Instead of loading scripts and CSSs using inline tags, all scripts are
+// loaded by code. In this way we can guarantee the correct processing order,
+// otherwise external scripts and inline scripts could be executed in an
+// unwanted order (IE).
+
+function LoadScript( url )
+{
+ document.write( '<scr' + 'ipt type="text/javascript" src="' + url + '"><\/scr' + 'ipt>' ) ;
+}
+
+// Main editor scripts.
+var sSuffix = ( /*@cc_on!@*/false ) ? 'ie' : 'gecko' ;
+
+/* @Packager.RemoveLine
+LoadScript( 'js/fckeditorcode_' + sSuffix + '.js' ) ;
+@Packager.RemoveLine */
+// @Packager.Remove.Start
+
+LoadScript( '_source/fckconstants.js' ) ;
+LoadScript( '_source/fckjscoreextensions.js' ) ;
+
+if ( sSuffix == 'ie' )
+ LoadScript( '_source/classes/fckiecleanup.js' ) ;
+
+LoadScript( '_source/internals/fckbrowserinfo.js' ) ;
+LoadScript( '_source/internals/fckurlparams.js' ) ;
+LoadScript( '_source/classes/fckevents.js' ) ;
+LoadScript( '_source/classes/fckdataprocessor.js' ) ;
+LoadScript( '_source/internals/fck.js' ) ;
+LoadScript( '_source/internals/fck_' + sSuffix + '.js' ) ;
+LoadScript( '_source/internals/fckconfig.js' ) ;
+
+LoadScript( '_source/internals/fckdebug_empty.js' ) ;
+LoadScript( '_source/internals/fckdomtools.js' ) ;
+LoadScript( '_source/internals/fcktools.js' ) ;
+LoadScript( '_source/internals/fcktools_' + sSuffix + '.js' ) ;
+LoadScript( '_source/fckeditorapi.js' ) ;
+LoadScript( '_source/classes/fckimagepreloader.js' ) ;
+LoadScript( '_source/internals/fckregexlib.js' ) ;
+LoadScript( '_source/internals/fcklistslib.js' ) ;
+LoadScript( '_source/internals/fcklanguagemanager.js' ) ;
+LoadScript( '_source/internals/fckxhtmlentities.js' ) ;
+LoadScript( '_source/internals/fckxhtml.js' ) ;
+LoadScript( '_source/internals/fckxhtml_' + sSuffix + '.js' ) ;
+LoadScript( '_source/internals/fckcodeformatter.js' ) ;
+LoadScript( '_source/internals/fckundo.js' ) ;
+LoadScript( '_source/classes/fckeditingarea.js' ) ;
+LoadScript( '_source/classes/fckkeystrokehandler.js' ) ;
+
+LoadScript( 'dtd/fck_xhtml10transitional.js' ) ;
+LoadScript( '_source/classes/fckstyle.js' ) ;
+LoadScript( '_source/internals/fckstyles.js' ) ;
+
+LoadScript( '_source/internals/fcklisthandler.js' ) ;
+LoadScript( '_source/classes/fckelementpath.js' ) ;
+LoadScript( '_source/classes/fckdomrange.js' ) ;
+LoadScript( '_source/classes/fckdocumentfragment_' + sSuffix + '.js' ) ;
+LoadScript( '_source/classes/fckw3crange.js' ) ;
+LoadScript( '_source/classes/fckdomrange_' + sSuffix + '.js' ) ;
+LoadScript( '_source/classes/fckdomrangeiterator.js' ) ;
+LoadScript( '_source/classes/fckenterkey.js' ) ;
+
+LoadScript( '_source/internals/fckdocumentprocessor.js' ) ;
+LoadScript( '_source/internals/fckselection.js' ) ;
+LoadScript( '_source/internals/fckselection_' + sSuffix + '.js' ) ;
+
+LoadScript( '_source/internals/fcktablehandler.js' ) ;
+LoadScript( '_source/internals/fcktablehandler_' + sSuffix + '.js' ) ;
+LoadScript( '_source/classes/fckxml.js' ) ;
+LoadScript( '_source/classes/fckxml_' + sSuffix + '.js' ) ;
+
+LoadScript( '_source/commandclasses/fcknamedcommand.js' ) ;
+LoadScript( '_source/commandclasses/fckstylecommand.js' ) ;
+LoadScript( '_source/commandclasses/fck_othercommands.js' ) ;
+LoadScript( '_source/commandclasses/fckshowblocks.js' ) ;
+LoadScript( '_source/commandclasses/fckspellcheckcommand_' + sSuffix + '.js' ) ;
+LoadScript( '_source/commandclasses/fcktextcolorcommand.js' ) ;
+LoadScript( '_source/commandclasses/fckpasteplaintextcommand.js' ) ;
+LoadScript( '_source/commandclasses/fckpastewordcommand.js' ) ;
+LoadScript( '_source/commandclasses/fcktablecommand.js' ) ;
+LoadScript( '_source/commandclasses/fckfitwindow.js' ) ;
+LoadScript( '_source/commandclasses/fcklistcommands.js' ) ;
+LoadScript( '_source/commandclasses/fckjustifycommands.js' ) ;
+LoadScript( '_source/commandclasses/fckindentcommands.js' ) ;
+LoadScript( '_source/commandclasses/fckblockquotecommand.js' ) ;
+LoadScript( '_source/commandclasses/fckcorestylecommand.js' ) ;
+LoadScript( '_source/commandclasses/fckremoveformatcommand.js' ) ;
+LoadScript( '_source/internals/fckcommands.js' ) ;
+
+LoadScript( '_source/classes/fckpanel.js' ) ;
+LoadScript( '_source/classes/fckicon.js' ) ;
+LoadScript( '_source/classes/fcktoolbarbuttonui.js' ) ;
+LoadScript( '_source/classes/fcktoolbarbutton.js' ) ;
+LoadScript( '_source/classes/fckspecialcombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarspecialcombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarstylecombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarfontformatcombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarfontscombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarfontsizecombo.js' ) ;
+LoadScript( '_source/classes/fcktoolbarpanelbutton.js' ) ;
+LoadScript( '_source/internals/fckscayt.js' ) ;
+LoadScript( '_source/internals/fcktoolbaritems.js' ) ;
+LoadScript( '_source/classes/fcktoolbar.js' ) ;
+LoadScript( '_source/classes/fcktoolbarbreak_' + sSuffix + '.js' ) ;
+LoadScript( '_source/internals/fcktoolbarset.js' ) ;
+LoadScript( '_source/internals/fckdialog.js' ) ;
+LoadScript( '_source/classes/fckmenuitem.js' ) ;
+LoadScript( '_source/classes/fckmenublock.js' ) ;
+LoadScript( '_source/classes/fckmenublockpanel.js' ) ;
+LoadScript( '_source/classes/fckcontextmenu.js' ) ;
+LoadScript( '_source/internals/fck_contextmenu.js' ) ;
+LoadScript( '_source/classes/fckhtmliterator.js' ) ;
+LoadScript( '_source/classes/fckplugin.js' ) ;
+LoadScript( '_source/internals/fckplugins.js' ) ;
+
+// @Packager.Remove.End
+
+// Base configuration file.
+LoadScript( '../fckconfig.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Adobe AIR compatibility file.
+if ( FCKBrowserInfo.IsAIR )
+ LoadScript( 'js/fckadobeair.js' ) ;
+
+if ( FCKBrowserInfo.IsIE )
+{
+ // Remove IE mouse flickering.
+ try
+ {
+ document.execCommand( 'BackgroundImageCache', false, true ) ;
+ }
+ catch (e)
+ {
+ // We have been reported about loading problems caused by the above
+ // line. For safety, let's just ignore errors.
+ }
+
+ // Create the default cleanup object used by the editor.
+ FCK.IECleanup = new FCKIECleanup( window ) ;
+ FCK.IECleanup.AddItem( FCKTempBin, FCKTempBin.Reset ) ;
+ FCK.IECleanup.AddItem( FCK, FCK_Cleanup ) ;
+}
+
+// The first function to be called on selection change must the the styles
+// change checker, because the result of its processing may be used by another
+// functions listening to the same event.
+FCK.Events.AttachEvent( 'OnSelectionChange', function() { FCKStyles.CheckSelectionChanges() ; } ) ;
+
+// The config hidden field is processed immediately, because
+// CustomConfigurationsPath may be set in the page.
+FCKConfig.ProcessHiddenField() ;
+
+// Load the custom configurations file (if defined).
+if ( FCKConfig.CustomConfigurationsPath.length > 0 )
+ LoadScript( FCKConfig.CustomConfigurationsPath ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Load configurations defined at page level.
+FCKConfig_LoadPageConfig() ;
+
+FCKConfig_PreProcess() ;
+
+// Load the full debug script.
+if ( FCKConfig.Debug )
+ LoadScript( '_source/internals/fckdebug.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+var FCK_InternalCSS = FCKConfig.BasePath + 'css/fck_internal.css' ; // @Packager.RemoveLine
+var FCK_ShowTableBordersCSS = FCKConfig.BasePath + 'css/fck_showtableborders_gecko.css' ; // @Packager.RemoveLine
+/* @Packager.RemoveLine
+// CSS minified by http://iceyboard.no-ip.org/projects/css_compressor (see _dev/css_compression.txt).
+var FCK_InternalCSS = FCKTools.FixCssUrls( FCKConfig.BasePath + 'css/', 'html{min-height:100%}table.FCK__ShowTableBorders,table.FCK__ShowTableBorders td,table.FCK__ShowTableBorders th{border:#d3d3d3 1px solid}form{border:1px dotted #F00;padding:2px}.FCK__Flash{border:#a9a9a9 1px solid;background-position:center center;background-image:url(images/fck_flashlogo.gif);background-repeat:no-repeat;width:80px;height:80px}.FCK__UnknownObject{border:#a9a9a9 1px solid;background-position:center center;background-image:url(images/fck_plugin.gif);background-repeat:no-repeat;width:80px;height:80px}.FCK__Anchor{border:1px dotted #00F;background-position:center center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;width:16px;height:15px;vertical-align:middle}.FCK__AnchorC{border:1px dotted #00F;background-position:1px center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;padding-left:18px}a[name]{border:1px dotted #00F;background-position:0 center;background-image:url(images/fck_anchor.gif);background-repeat:no-repeat;padding-left:18px}.FCK__PageBreak{background-position:center center;background-image:url(images/fck_pagebreak.gif);background-repeat:no-repeat;clear:both;display:block;float:none;width:100%;border-top:#999 1px dotted;border-bottom:#999 1px dotted;border-right:0;border-left:0;height:5px}.FCK__InputHidden{width:19px;height:18px;background-image:url(images/fck_hiddenfield.gif);background-repeat:no-repeat;vertical-align:text-bottom;background-position:center center}.FCK__ShowBlocks p,.FCK__ShowBlocks div,.FCK__ShowBlocks pre,.FCK__ShowBlocks address,.FCK__ShowBlocks blockquote,.FCK__ShowBlocks h1,.FCK__ShowBlocks h2,.FCK__ShowBlocks h3,.FCK__ShowBlocks h4,.FCK__ShowBlocks h5,.FCK__ShowBlocks h6{background-repeat:no-repeat;border:1px dotted gray;padding-top:8px;padding-left:8px}.FCK__ShowBlocks p{background-image:url(images/block_p.png)}.FCK__ShowBlocks div{background-image:url(images/block_div.png)}.FCK__ShowBlocks pre{background-image:url(images/block_pre.png)}.FCK__ShowBlocks address{background-image:url(images/block_address.png)}.FCK__ShowBlocks blockquote{background-image:url(images/block_blockquote.png)}.FCK__ShowBlocks h1{background-image:url(images/block_h1.png)}.FCK__ShowBlocks h2{background-image:url(images/block_h2.png)}.FCK__ShowBlocks h3{background-image:url(images/block_h3.png)}.FCK__ShowBlocks h4{background-image:url(images/block_h4.png)}.FCK__ShowBlocks h5{background-image:url(images/block_h5.png)}.FCK__ShowBlocks h6{background-image:url(images/block_h6.png)}' ) ;
+var FCK_ShowTableBordersCSS = FCKTools.FixCssUrls( FCKConfig.BasePath + 'css/', 'table:not([border]),table:not([border]) > tr > td,table:not([border]) > tr > th,table:not([border]) > tbody > tr > td,table:not([border]) > tbody > tr > th,table:not([border]) > thead > tr > td,table:not([border]) > thead > tr > th,table:not([border]) > tfoot > tr > td,table:not([border]) > tfoot > tr > th,table[border=\"0\"],table[border=\"0\"] > tr > td,table[border=\"0\"] > tr > th,table[border=\"0\"] > tbody > tr > td,table[border=\"0\"] > tbody > tr > th,table[border=\"0\"] > thead > tr > td,table[border=\"0\"] > thead > tr > th,table[border=\"0\"] > tfoot > tr > td,table[border=\"0\"] > tfoot > tr > th{border:#d3d3d3 1px dotted}' ) ;
+@Packager.RemoveLine */
+
+// Popup the debug window if debug mode is set to true. It guarantees that the
+// first debug message will not be lost.
+if ( FCKConfig.Debug )
+ FCKDebug._GetWindow() ;
+
+// Load the active skin CSS.
+document.write( FCKTools.GetStyleHtml( FCKConfig.SkinEditorCSS ) ) ;
+
+// Load the language file.
+FCKLanguageManager.Initialize() ;
+LoadScript( 'lang/' + FCKLanguageManager.ActiveLanguage.Code + '.js' ) ;
+
+ </script>
+ <script type="text/javascript">
+
+// Initialize the editing area context menu.
+FCK_ContextMenu_Init() ;
+
+FCKPlugins.Load() ;
+
+ </script>
+ <script type="text/javascript">
+
+// Set the editor interface direction.
+window.document.dir = FCKLang.Dir ;
+
+ </script>
+ <script type="text/javascript">
+
+window.onload = function()
+{
+ InitializeAPI() ;
+
+ if ( FCKBrowserInfo.IsIE )
+ FCK_PreloadImages() ;
+ else
+ LoadToolbarSetup() ;
+}
+
+function LoadToolbarSetup()
+{
+ FCKeditorAPI._FunctionQueue.Add( LoadToolbar ) ;
+}
+
+function LoadToolbar()
+{
+ var oToolbarSet = FCK.ToolbarSet = FCKToolbarSet_Create() ;
+
+ if ( oToolbarSet.IsLoaded )
+ StartEditor() ;
+ else
+ {
+ oToolbarSet.OnLoad = StartEditor ;
+ oToolbarSet.Load( FCKURLParams['Toolbar'] || 'Default' ) ;
+ }
+}
+
+function StartEditor()
+{
+ // Remove the onload listener.
+ FCK.ToolbarSet.OnLoad = null ;
+
+ FCKeditorAPI._FunctionQueue.Remove( LoadToolbar ) ;
+
+ FCK.Events.AttachEvent( 'OnStatusChange', WaitForActive ) ;
+
+ // Start the editor.
+ FCK.StartEditor() ;
+}
+
+function WaitForActive( editorInstance, newStatus )
+{
+ if ( newStatus == FCK_STATUS_ACTIVE )
+ {
+ if ( FCKBrowserInfo.IsGecko )
+ FCKTools.RunFunction( window.onresize ) ;
+
+ if ( !FCKConfig.PreventSubmitHandler )
+ _AttachFormSubmitToAPI() ;
+
+ FCK.SetStatus( FCK_STATUS_COMPLETE ) ;
+
+ // Call the special "FCKeditor_OnComplete" function that should be present in
+ // the HTML page where the editor is located.
+ if ( typeof( window.parent.FCKeditor_OnComplete ) == 'function' )
+ window.parent.FCKeditor_OnComplete( FCK ) ;
+ }
+}
+
+// Gecko and Webkit browsers don't calculate well the IFRAME size so we must
+// recalculate it every time the window size changes.
+if ( FCKBrowserInfo.IsGecko || ( FCKBrowserInfo.IsSafari && !FCKBrowserInfo.IsSafari3 ) )
+{
+ window.onresize = function( e )
+ {
+ // Running in Firefox's chrome makes the window receive the event including subframes.
+ // we care only about this window. Ticket #1642.
+ // #2002: The originalTarget from the event can be the current document, the window, or the editing area.
+ if ( e && e.originalTarget && e.originalTarget !== document && e.originalTarget !== window && (!e.originalTarget.ownerDocument || e.originalTarget.ownerDocument != document ))
+ return ;
+
+ var oCell = document.getElementById( 'xEditingArea' ) ;
+
+ var eInnerElement = oCell.firstChild ;
+ if ( eInnerElement )
+ {
+ eInnerElement.style.height = '0px' ;
+ eInnerElement.style.height = ( oCell.scrollHeight - 2 ) + 'px' ;
+ }
+ }
+}
+
+ </script>
+</head>
+<body>
+ <table width="100%" cellpadding="0" cellspacing="0" style="height: 100%; table-layout: fixed">
+ <tr id="xToolbarRow" style="display: none">
+ <td id="xToolbarSpace" style="overflow: hidden">
+ <table width="100%" cellpadding="0" cellspacing="0">
+ <tr id="xCollapsed" style="display: none">
+ <td id="xExpandHandle" class="TB_Expand" colspan="3">
+ <img class="TB_ExpandImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>
+ </tr>
+ <tr id="xExpanded" style="display: none">
+ <td id="xTBLeftBorder" class="TB_SideBorder" style="width: 1px; display: none;"></td>
+ <td id="xCollapseHandle" style="display: none" class="TB_Collapse" valign="bottom">
+ <img class="TB_CollapseImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>
+ <td id="xToolbar" class="TB_ToolbarSet"></td>
+ <td class="TB_SideBorder" style="width: 1px"></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td id="xEditingArea" valign="top" style="height: 100%"></td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css b/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css
new file mode 100644
index 0000000..8325a88
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css
@@ -0,0 +1,87 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * CSS styles used by all pages that compose the File Browser.
+ */
+
+body
+{
+ background-color: #f1f1e3;
+ margin-top:0;
+ margin-bottom:0;
+}
+
+form
+{
+ margin: 0;
+ padding: 0;
+}
+
+.Frame
+{
+ background-color: #f1f1e3;
+ border: thin inset #f1f1e3;
+}
+
+body.FileArea
+{
+ background-color: #ffffff;
+ margin: 10px;
+}
+
+body, td, input, select
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+.ActualFolder
+{
+ font-weight: bold;
+ font-size: 14px;
+}
+
+.PopupButtons
+{
+ border-top: #d5d59d 1px solid;
+ background-color: #e3e3c7;
+ padding: 7px 10px 7px 10px;
+}
+
+.Button, button
+{
+ color: #3b3b1f;
+ border: #737357 1px solid;
+ background-color: #c7c78f;
+}
+
+.FolderListCurrentFolder img
+{
+ background-image: url(images/FolderOpened.gif);
+}
+
+.FolderListFolder img
+{
+ background-image: url(images/Folder.gif);
+}
+
+.fullHeight {
+ height: 100%;
+}
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html
new file mode 100644
index 0000000..d5e773b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
+ "http://www.w3.org/TR/html4/frameset.dtd">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page compose the File Browser dialog frameset.
+-->
+<html>
+ <head>
+ <title>FCKeditor - Resources Browser</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <script type="text/javascript" src="js/fckxml.js"></script>
+ <script type="text/javascript">
+// Automatically detect the correct document.domain (#1919).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.opener.document.domain ;
+ break ;
+ }
+ catch( e )
+ {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+function GetUrlParam( paramName )
+{
+ var oRegex = new RegExp( '[\?&]' + paramName + '=([^&]+)', 'i' ) ;
+ var oMatch = oRegex.exec( window.top.location.search ) ;
+
+ if ( oMatch && oMatch.length > 1 )
+ return decodeURIComponent( oMatch[1] ) ;
+ else
+ return '' ;
+}
+
+var oConnector = new Object() ;
+oConnector.CurrentFolder = '/' ;
+
+var sConnUrl = GetUrlParam( 'Connector' ) ;
+
+// Gecko has some problems when using relative URLs (not starting with slash).
+if ( sConnUrl.substr(0,1) != '/' && sConnUrl.indexOf( '://' ) < 0 )
+ sConnUrl = window.location.href.replace( /browser.html.*$/, '' ) + sConnUrl ;
+
+oConnector.ConnectorUrl = sConnUrl + ( sConnUrl.indexOf('?') != -1 ? '&' : '?' ) ;
+
+var sServerPath = GetUrlParam( 'ServerPath' ) ;
+if ( sServerPath.length > 0 )
+ oConnector.ConnectorUrl += 'ServerPath=' + encodeURIComponent( sServerPath ) + '&' ;
+
+oConnector.ResourceType = GetUrlParam( 'Type' ) ;
+oConnector.ShowAllTypes = ( oConnector.ResourceType.length == 0 ) ;
+
+if ( oConnector.ShowAllTypes )
+ oConnector.ResourceType = 'File' ;
+
+oConnector.SendCommand = function( command, params, callBackFunction )
+{
+ var sUrl = this.ConnectorUrl + 'Command=' + command ;
+ sUrl += '&Type=' + this.ResourceType ;
+ sUrl += '&CurrentFolder=' + encodeURIComponent( this.CurrentFolder ) ;
+
+ if ( params ) sUrl += '&' + params ;
+
+ // Add a random salt to avoid getting a cached version of the command execution
+ sUrl += '&uuid=' + new Date().getTime() ;
+
+ var oXML = new FCKXml() ;
+
+ if ( callBackFunction )
+ oXML.LoadUrl( sUrl, callBackFunction ) ; // Asynchronous load.
+ else
+ return oXML.LoadUrl( sUrl ) ;
+
+ return null ;
+}
+
+oConnector.CheckError = function( responseXml )
+{
+ var iErrorNumber = 0 ;
+ var oErrorNode = responseXml.SelectSingleNode( 'Connector/Error' ) ;
+
+ if ( oErrorNode )
+ {
+ iErrorNumber = parseInt( oErrorNode.attributes.getNamedItem('number').value, 10 ) ;
+
+ switch ( iErrorNumber )
+ {
+ case 0 :
+ break ;
+ case 1 : // Custom error. Message placed in the "text" attribute.
+ alert( oErrorNode.attributes.getNamedItem('text').value ) ;
+ break ;
+ case 101 :
+ alert( 'Folder already exists' ) ;
+ break ;
+ case 102 :
+ alert( 'Invalid folder name' ) ;
+ break ;
+ case 103 :
+ alert( 'You have no permissions to create the folder' ) ;
+ break ;
+ case 110 :
+ alert( 'Unknown error creating folder' ) ;
+ break ;
+ default :
+ alert( 'Error on your request. Error number: ' + iErrorNumber ) ;
+ break ;
+ }
+ }
+ return iErrorNumber ;
+}
+
+var oIcons = new Object() ;
+
+oIcons.AvailableIconsArray = [
+ 'ai','avi','bmp','cs','dll','doc','exe','fla','gif','htm','html','jpg','js',
+ 'mdb','mp3','pdf','png','ppt','rdp','swf','swt','txt','vsd','xls','xml','zip' ] ;
+
+oIcons.AvailableIcons = new Object() ;
+
+for ( var i = 0 ; i < oIcons.AvailableIconsArray.length ; i++ )
+ oIcons.AvailableIcons[ oIcons.AvailableIconsArray[i] ] = true ;
+
+oIcons.GetIcon = function( fileName )
+{
+ var sExtension = fileName.substr( fileName.lastIndexOf('.') + 1 ).toLowerCase() ;
+
+ if ( this.AvailableIcons[ sExtension ] == true )
+ return sExtension ;
+ else
+ return 'default.icon' ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ if (errorNumber == "1")
+ window.frames['frmUpload'].OnUploadCompleted( errorNumber, customMsg ) ;
+ else
+ window.frames['frmUpload'].OnUploadCompleted( errorNumber, fileName ) ;
+}
+
+ </script>
+ </head>
+ <frameset cols="150,*" class="Frame" framespacing="3" bordercolor="#f1f1e3" frameborder="1">
+ <frameset rows="50,*" framespacing="0">
+ <frame src="frmresourcetype.html" scrolling="no" frameborder="0">
+ <frame name="frmFolders" src="frmfolders.html" scrolling="auto" frameborder="1">
+ </frameset>
+ <frameset rows="50,*,50" framespacing="0">
+ <frame name="frmActualFolder" src="frmactualfolder.html" scrolling="no" frameborder="0">
+ <frame name="frmResourcesList" src="frmresourceslist.html" scrolling="auto" frameborder="1">
+ <frameset cols="150,*,0" framespacing="0" frameborder="0">
+ <frame name="frmCreateFolder" src="frmcreatefolder.html" scrolling="no" frameborder="0">
+ <frame name="frmUpload" src="frmupload.html" scrolling="no" frameborder="0">
+ <frame name="frmUploadWorker" src="javascript:void(0)" scrolling="no" frameborder="0">
+ </frameset>
+ </frameset>
+ </frameset>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl
new file mode 100644
index 0000000..f64b7c7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl
@@ -0,0 +1,63 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub CreateXmlHeader
+{
+ local($command,$resourceType,$currentFolder) = @_;
+
+ # Create the XML document header.
+ print '<?xml version="1.0" encoding="utf-8" ?>';
+
+ # Create the main "Connector" node.
+ print '<Connector command="' . $command . '" resourceType="' . $resourceType . '">';
+
+ # Add the current folder node.
+ print '<CurrentFolder path="' . ConvertToXmlAttribute($currentFolder) . '" url="' . ConvertToXmlAttribute(GetUrlFromPath($resourceType,$currentFolder)) . '" />';
+}
+
+sub CreateXmlFooter
+{
+ print '</Connector>';
+}
+
+sub SendError
+{
+ local( $number, $text ) = @_;
+
+ print << "_HTML_HEAD_";
+Content-Type:text/xml; charset=utf-8
+Pragma: no-cache
+Cache-Control: no-cache
+Expires: Thu, 01 Dec 1994 16:00:00 GMT
+
+_HTML_HEAD_
+
+ # Create the XML document header
+ print '<?xml version="1.0" encoding="utf-8" ?>' ;
+
+ print '<Connector><Error number="' . $number . '" text="' . &specialchar_cnv( $text ) . '" /></Connector>' ;
+
+ exit ;
+}
+
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl
new file mode 100644
index 0000000..2ed2e62
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl
@@ -0,0 +1,158 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub GetFolders
+{
+
+ local($resourceType, $currentFolder) = @_;
+
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType, $currentFolder);
+ print "<Folders>"; # Open the "Folders" node.
+
+ opendir(DIR,"$sServerDir");
+ @files = grep(!/^\.\.?$/,readdir(DIR));
+ closedir(DIR);
+
+ foreach $sFile (@files) {
+ if($sFile != '.' && $sFile != '..' && (-d "$sServerDir$sFile")) {
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ print '<Folder name="' . $cnv_filename . '" />';
+ }
+ }
+ print "</Folders>"; # Close the "Folders" node.
+}
+
+sub GetFoldersAndFiles
+{
+
+ local($resourceType, $currentFolder) = @_;
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType,$currentFolder);
+
+ # Initialize the output buffers for "Folders" and "Files".
+ $sFolders = '<Folders>';
+ $sFiles = '<Files>';
+
+ opendir(DIR,"$sServerDir");
+ @files = grep(!/^\.\.?$/,readdir(DIR));
+ closedir(DIR);
+
+ foreach $sFile (@files) {
+ if($sFile ne '.' && $sFile ne '..') {
+ if(-d "$sServerDir$sFile") {
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ $sFolders .= '<Folder name="' . $cnv_filename . '" />' ;
+ } else {
+ ($iFileSize,$refdate,$filedate,$fileperm) = (stat("$sServerDir$sFile"))[7,8,9,2];
+ if($iFileSize > 0) {
+ $iFileSize = int($iFileSize / 1024);
+ if($iFileSize < 1) {
+ $iFileSize = 1;
+ }
+ }
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ $sFiles .= '<File name="' . $cnv_filename . '" size="' . $iFileSize . '" />' ;
+ }
+ }
+ }
+ print $sFolders ;
+ print '</Folders>'; # Close the "Folders" node.
+ print $sFiles ;
+ print '</Files>'; # Close the "Files" node.
+}
+
+sub CreateFolder
+{
+
+ local($resourceType, $currentFolder) = @_;
+ $sErrorNumber = '0' ;
+ $sErrorMsg = '' ;
+
+ if($FORM{'NewFolderName'} ne "") {
+ $sNewFolderName = $FORM{'NewFolderName'};
+ # Map the virtual path to the local server path of the current folder.
+ $sServerDir = &ServerMapFolder($resourceType, $currentFolder);
+ if(-w $sServerDir) {
+ $sServerDir .= $sNewFolderName;
+ $sErrorMsg = &CreateServerFolder($sServerDir);
+ if($sErrorMsg == 0) {
+ $sErrorNumber = '0';
+ } elsif($sErrorMsg eq 'Invalid argument' || $sErrorMsg eq 'No such file or directory') {
+ $sErrorNumber = '102'; #// Path too long.
+ } else {
+ $sErrorNumber = '110';
+ }
+ } else {
+ $sErrorNumber = '103';
+ }
+ } else {
+ $sErrorNumber = '102' ;
+ }
+ # Create the "Error" node.
+ $cnv_errmsg = &ConvertToXmlAttribute($sErrorMsg);
+ print '<Error number="' . $sErrorNumber . '" originalDescription="' . $cnv_errmsg . '" />';
+}
+
+sub FileUpload
+{
+eval("use File::Copy;");
+
+ local($resourceType, $currentFolder) = @_;
+
+ $sErrorNumber = '0' ;
+ $sFileName = '' ;
+ if($new_fname) {
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType,$currentFolder);
+
+ # Get the uploaded file name.
+ $sFileName = $new_fname;
+ $sOriginalFileName = $sFileName;
+
+ $iCounter = 0;
+ while(1) {
+ $sFilePath = $sServerDir . $sFileName;
+ if(-e $sFilePath) {
+ $iCounter++ ;
+ ($path,$BaseName,$ext) = &RemoveExtension($sOriginalFileName);
+ $sFileName = $BaseName . '(' . $iCounter . ').' . $ext;
+ $sErrorNumber = '201';
+ } else {
+ copy("$img_dir/$new_fname","$sFilePath");
+ chmod(0777,$sFilePath);
+ unlink("$img_dir/$new_fname");
+ last;
+ }
+ }
+ } else {
+ $sErrorNumber = '202' ;
+ }
+ $sFileName =~ s/"/\\"/g;
+ print "Content-type: text/html\n\n";
+ print '<script type="text/javascript">';
+ print 'window.parent.frames["frmUpload"].OnUploadCompleted(' . $sErrorNumber . ',"' . $sFileName . '") ;';
+ print '</script>';
+ exit ;
+}
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi
new file mode 100644
index 0000000..a741215
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi
@@ -0,0 +1,137 @@
+#!/usr/bin/env perl
+
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+##
+# ATTENTION: To enable this connector, look for the "SECURITY" comment in this file.
+##
+
+## START: Hack for Windows (Not important to understand the editor code... Perl specific).
+if(Windows_check()) {
+ chdir(GetScriptPath($0));
+}
+
+sub Windows_check
+{
+ # IIS,PWS(NT/95)
+ $www_server_os = $^O;
+ # Win98 & NT(SP4)
+ if($www_server_os eq "") { $www_server_os= $ENV{'OS'}; }
+ # AnHTTPd/Omni/IIS
+ if($ENV{'SERVER_SOFTWARE'} =~ /AnWeb|Omni|IIS\//i) { $www_server_os= 'win'; }
+ # Win Apache
+ if($ENV{'WINDIR'} ne "") { $www_server_os= 'win'; }
+ if($www_server_os=~ /win/i) { return(1); }
+ return(0);
+}
+
+sub GetScriptPath {
+ local($path) = @_;
+ if($path =~ /[\:\/\\]/) { $path =~ s/(.*?)[\/\\][^\/\\]+$/$1/; } else { $path = '.'; }
+ $path;
+}
+## END: Hack for IIS
+
+require 'util.pl';
+require 'io.pl';
+require 'basexml.pl';
+require 'commands.pl';
+require 'upload_fck.pl';
+
+##
+# SECURITY: REMOVE/COMMENT THE FOLLOWING LINE TO ENABLE THIS CONNECTOR.
+##
+&SendError( 1, 'This connector is disabled. Please check the "editor/filemanager/browser/default/connectors/perl/connector.cgi" file' ) ;
+
+ &read_input();
+
+ if($FORM{'ServerPath'} ne "") {
+ $GLOBALS{'UserFilesPath'} = $FORM{'ServerPath'};
+ if(!($GLOBALS{'UserFilesPath'} =~ /\/$/)) {
+ $GLOBALS{'UserFilesPath'} .= '/' ;
+ }
+ } else {
+ $GLOBALS{'UserFilesPath'} = '/userfiles/';
+ }
+
+ # Map the "UserFiles" path to a local directory.
+ $rootpath = &GetRootPath();
+ $GLOBALS{'UserFilesDirectory'} = $rootpath . $GLOBALS{'UserFilesPath'};
+
+ &DoResponse();
+
+sub DoResponse
+{
+
+ if($FORM{'Command'} eq "" || $FORM{'Type'} eq "" || $FORM{'CurrentFolder'} eq "") {
+ return ;
+ }
+ # Get the main request informaiton.
+ $sCommand = $FORM{'Command'};
+ $sResourceType = $FORM{'Type'};
+ $sCurrentFolder = $FORM{'CurrentFolder'};
+
+ # Check the current folder syntax (must begin and start with a slash).
+ if(!($sCurrentFolder =~ /\/$/)) {
+ $sCurrentFolder .= '/';
+ }
+ if(!($sCurrentFolder =~ /^\//)) {
+ $sCurrentFolder = '/' . $sCurrentFolder;
+ }
+
+ # Check for invalid folder paths (..)
+ if ( $sCurrentFolder =~ /\.\./ ) {
+ SendError( 102, "" ) ;
+ }
+
+ # File Upload doesn't have to Return XML, so it must be intercepted before anything.
+ if($sCommand eq 'FileUpload') {
+ FileUpload($sResourceType,$sCurrentFolder);
+ return ;
+ }
+
+ print << "_HTML_HEAD_";
+Content-Type:text/xml; charset=utf-8
+Pragma: no-cache
+Cache-Control: no-cache
+Expires: Thu, 01 Dec 1994 16:00:00 GMT
+
+_HTML_HEAD_
+
+ &CreateXmlHeader($sCommand,$sResourceType,$sCurrentFolder);
+
+ # Execute the required command.
+ if($sCommand eq 'GetFolders') {
+ &GetFolders($sResourceType,$sCurrentFolder);
+ } elsif($sCommand eq 'GetFoldersAndFiles') {
+ &GetFoldersAndFiles($sResourceType,$sCurrentFolder);
+ } elsif($sCommand eq 'CreateFolder') {
+ &CreateFolder($sResourceType,$sCurrentFolder);
+ }
+
+ &CreateXmlFooter();
+
+ exit ;
+}
+
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl
new file mode 100644
index 0000000..c1dbccf
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl
@@ -0,0 +1,131 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub GetUrlFromPath
+{
+ local($resourceType, $folderPath) = @_;
+
+ if($resourceType eq '') {
+ $rmpath = &RemoveFromEnd($GLOBALS{'UserFilesPath'},'/');
+ return("$rmpath$folderPath");
+ } else {
+ return("$GLOBALS{'UserFilesPath'}$resourceType$folderPath");
+ }
+}
+
+sub RemoveExtension
+{
+ local($fileName) = @_;
+ local($path, $base, $ext);
+ if($fileName !~ /\./) {
+ $fileName .= '.';
+ }
+ if($fileName =~ /([^\\\/]*)\.(.*)$/) {
+ $base = $1;
+ $ext = $2;
+ if($fileName =~ /(.*)$base\.$ext$/) {
+ $path = $1;
+ }
+ }
+ return($path,$base,$ext);
+
+}
+
+sub ServerMapFolder
+{
+ local($resourceType,$folderPath) = @_;
+
+ # Get the resource type directory.
+ $sResourceTypePath = $GLOBALS{'UserFilesDirectory'} . $resourceType . '/';
+
+ # Ensure that the directory exists.
+ &CreateServerFolder($sResourceTypePath);
+
+ # Return the resource type directory combined with the required path.
+ $rmpath = &RemoveFromStart($folderPath,'/');
+ return("$sResourceTypePath$rmpath");
+}
+
+sub GetParentFolder
+{
+ local($folderPath) = @_;
+
+ $folderPath =~ s/[\/][^\/]+[\/]?$//g;
+ return $folderPath;
+}
+
+sub CreateServerFolder
+{
+ local($folderPath) = @_;
+
+ $sParent = &GetParentFolder($folderPath);
+ # Check if the parent exists, or create it.
+ if(!(-e $sParent)) {
+ $sErrorMsg = &CreateServerFolder($sParent);
+ if($sErrorMsg == 1) {
+ return(1);
+ }
+ }
+ if(!(-e $folderPath)) {
+ umask(000);
+ mkdir("$folderPath",0777);
+ chmod(0777,"$folderPath");
+ return(0);
+ } else {
+ return(1);
+ }
+}
+
+sub GetRootPath
+{
+#use Cwd;
+
+# my $dir = getcwd;
+# print $dir;
+# $dir =~ s/$ENV{'DOCUMENT_ROOT'}//g;
+# print $dir;
+# return($dir);
+
+# $wk = $0;
+# $wk =~ s/\/connector\.cgi//g;
+# if($wk) {
+# $current_dir = $wk;
+# } else {
+# $current_dir = `pwd`;
+# }
+# return($current_dir);
+use Cwd;
+
+ if($ENV{'DOCUMENT_ROOT'}) {
+ $dir = $ENV{'DOCUMENT_ROOT'};
+ } else {
+ my $dir = getcwd;
+ $workdir =~ s/\/connector\.cgi//g;
+ $dir =~ s/$workdir//g;
+ }
+ return($dir);
+
+
+
+}
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl
new file mode 100644
index 0000000..1c3f4e2
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl
@@ -0,0 +1,667 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+# image data save dir
+$img_dir = './temp/';
+
+
+# File size max(unit KB)
+$MAX_CONTENT_SIZE = 30000;
+
+# Filelock (1=use,0=not use)
+$PM{'flock'} = '1';
+
+
+# upload Content-Type list
+my %UPLOAD_CONTENT_TYPE_LIST = (
+ 'image/(x-)?png' => 'png', # PNG image
+ 'image/p?jpe?g' => 'jpg', # JPEG image
+ 'image/gif' => 'gif', # GIF image
+ 'image/x-xbitmap' => 'xbm', # XBM image
+
+ 'image/(x-(MS-)?)?bmp' => 'bmp', # Windows BMP image
+ 'image/pict' => 'pict', # Macintosh PICT image
+ 'image/tiff' => 'tif', # TIFF image
+ 'application/pdf' => 'pdf', # PDF image
+ 'application/x-shockwave-flash' => 'swf', # Shockwave Flash
+
+ 'video/(x-)?msvideo' => 'avi', # Microsoft Video
+ 'video/quicktime' => 'mov', # QuickTime Video
+ 'video/mpeg' => 'mpeg', # MPEG Video
+ 'video/x-mpeg2' => 'mpv2', # MPEG2 Video
+
+ 'audio/(x-)?midi?' => 'mid', # MIDI Audio
+ 'audio/(x-)?wav' => 'wav', # WAV Audio
+ 'audio/basic' => 'au', # ULAW Audio
+ 'audio/mpeg' => 'mpga', # MPEG Audio
+
+ 'application/(x-)?zip(-compressed)?' => 'zip', # ZIP Compress
+
+ 'text/html' => 'html', # HTML
+ 'text/plain' => 'txt', # TEXT
+ '(?:application|text)/(?:rtf|richtext)' => 'rtf', # RichText
+
+ 'application/msword' => 'doc', # Microsoft Word
+ 'application/vnd.ms-excel' => 'xls', # Microsoft Excel
+
+ ''
+);
+
+# Upload is permitted.
+# A regular expression is possible.
+my %UPLOAD_EXT_LIST = (
+ 'png' => 'PNG image',
+ 'p?jpe?g|jpe|jfif|pjp' => 'JPEG image',
+ 'gif' => 'GIF image',
+ 'xbm' => 'XBM image',
+
+ 'bmp|dib|rle' => 'Windows BMP image',
+ 'pi?ct' => 'Macintosh PICT image',
+ 'tiff?' => 'TIFF image',
+ 'pdf' => 'PDF image',
+ 'swf' => 'Shockwave Flash',
+
+ 'avi' => 'Microsoft Video',
+ 'moo?v|qt' => 'QuickTime Video',
+ 'm(p(e?gv?|e|v)|1v)' => 'MPEG Video',
+ 'mp(v2|2v)' => 'MPEG2 Video',
+
+ 'midi?|kar|smf|rmi|mff' => 'MIDI Audio',
+ 'wav' => 'WAVE Audio',
+ 'au|snd' => 'ULAW Audio',
+ 'mp(e?ga|2|a|3)|abs' => 'MPEG Audio',
+
+ 'zip' => 'ZIP Compress',
+ 'lzh' => 'LZH Compress',
+ 'cab' => 'CAB Compress',
+
+ 'd?html?' => 'HTML',
+ 'rtf|rtx' => 'RichText',
+ 'txt|text' => 'Text',
+
+ ''
+);
+
+
+# sjis or euc
+my $CHARCODE = 'sjis';
+
+$TRANS_2BYTE_CODE = 0;
+
+##############################################################################
+# Summary
+#
+# Form Read input
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub read_input
+{
+eval("use File::Copy;");
+eval("use File::Path;");
+
+ my ($FORM) = @_;
+
+
+ mkdir($img_dir,0777);
+ chmod(0777,$img_dir);
+
+ undef $img_data_exists;
+ undef @NEWFNAMES;
+ undef @NEWFNAME_DATA;
+
+ if($ENV{'CONTENT_LENGTH'} > 10000000 || $ENV{'CONTENT_LENGTH'} > $MAX_CONTENT_SIZE * 1024) {
+ &upload_error(
+ 'Size Error',
+ sprintf(
+ "Transmitting size is too large.MAX <strong>%d KB</strong> Now Size <strong>%d KB</strong>(<strong>%d bytes</strong> Over)",
+ $MAX_CONTENT_SIZE,
+ int($ENV{'CONTENT_LENGTH'} / 1024),
+ $ENV{'CONTENT_LENGTH'} - $MAX_CONTENT_SIZE * 1024
+ )
+ );
+ }
+
+ my $Buffer;
+ if($ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
+ # METHOD POST only
+ return unless($ENV{'CONTENT_LENGTH'});
+
+ binmode(STDIN);
+ # STDIN A pause character is detected.'(MacIE3.0 boundary of $ENV{'CONTENT_TYPE'} cannot be trusted.)
+ my $Boundary = <STDIN>;
+ $Boundary =~ s/\x0D\x0A//;
+ $Boundary = quotemeta($Boundary);
+ while(<STDIN>) {
+ if(/^\s*Content-Disposition:/i) {
+ my($name,$ContentType,$FileName);
+ # form data get
+ if(/\bname="([^"]+)"/i || /\bname=([^\s:;]+)/i) {
+ $name = $1;
+ $name =~ tr/+/ /;
+ $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ &Encode(\$name);
+ }
+ if(/\bfilename="([^"]*)"/i || /\bfilename=([^\s:;]*)/i) {
+ $FileName = $1 || 'unknown';
+ }
+ # head read
+ while(<STDIN>) {
+ last if(! /\w/);
+ if(/^\s*Content-Type:\s*"([^"]+)"/i || /^\s*Content-Type:\s*([^\s:;]+)/i) {
+ $ContentType = $1;
+ }
+ }
+ # body read
+ $value = "";
+ while(<STDIN>) {
+ last if(/^$Boundary/o);
+ $value .= $_;
+ };
+ $lastline = $_;
+ $value =~s /\x0D\x0A$//;
+ if($value ne '') {
+ if($FileName || $ContentType) {
+ $img_data_exists = 1;
+ (
+ $FileName, #
+ $Ext, #
+ $Length, #
+ $ImageWidth, #
+ $ImageHeight, #
+ $ContentName #
+ ) = &CheckContentType(\$value,$FileName,$ContentType);
+
+ $FORM{$name} = $FileName;
+ $new_fname = $FileName;
+ push(@NEWFNAME_DATA,"$FileName\t$Ext\t$Length\t$ImageWidth\t$ImageHeight\t$ContentName");
+
+ # Multi-upload correspondence
+ push(@NEWFNAMES,$new_fname);
+ open(OUT,">$img_dir/$new_fname");
+ binmode(OUT);
+ eval "flock(OUT,2);" if($PM{'flock'} == 1);
+ print OUT $value;
+ eval "flock(OUT,8);" if($PM{'flock'} == 1);
+ close(OUT);
+
+ } elsif($name) {
+ $value =~ tr/+/ /;
+ $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ &Encode(\$value,'trans');
+ $FORM{$name} .= "\0" if(defined($FORM{$name}));
+ $FORM{$name} .= $value;
+ }
+ }
+ };
+ last if($lastline =~ /^$Boundary\-\-/o);
+ }
+ } elsif($ENV{'CONTENT_LENGTH'}) {
+ read(STDIN,$Buffer,$ENV{'CONTENT_LENGTH'});
+ }
+ foreach(split(/&/,$Buffer),split(/&/,$ENV{'QUERY_STRING'})) {
+ my($name, $value) = split(/=/);
+ $name =~ tr/+/ /;
+ $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ $value =~ tr/+/ /;
+ $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+
+ &Encode(\$name);
+ &Encode(\$value,'trans');
+ $FORM{$name} .= "\0" if(defined($FORM{$name}));
+ $FORM{$name} .= $value;
+
+ }
+
+}
+
+##############################################################################
+# Summary
+#
+# CheckContentType
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub CheckContentType
+{
+
+ my($DATA,$FileName,$ContentType) = @_;
+ my($Ext,$ImageWidth,$ImageHeight,$ContentName,$Infomation);
+ my $DataLength = length($$DATA);
+
+ # An unknown file type
+
+ $_ = $ContentType;
+ my $UnknownType = (
+ !$_
+ || /^application\/(x-)?macbinary$/i
+ || /^application\/applefile$/i
+ || /^application\/octet-stream$/i
+ || /^text\/plane$/i
+ || /^x-unknown-content-type/i
+ );
+
+ # MacBinary(Mac Unnecessary data are deleted.)
+ if($UnknownType || $ENV{'HTTP_USER_AGENT'} =~ /Macintosh|Mac_/) {
+ if($DataLength > 128 && !unpack("C",substr($$DATA,0,1)) && !unpack("C",substr($$DATA,74,1)) && !unpack("C",substr($$DATA,82,1)) ) {
+ my $MacBinary_ForkLength = unpack("N", substr($$DATA, 83, 4)); # ForkLength Get
+ my $MacBinary_FileName = quotemeta(substr($$DATA, 2, unpack("C",substr($$DATA, 1, 1))));
+ if($MacBinary_FileName && $MacBinary_ForkLength && $DataLength >= $MacBinary_ForkLength + 128
+ && ($FileName =~ /$MacBinary_FileName/i || substr($$DATA,102,4) eq 'mBIN')) { # DATA TOP 128byte MacBinary!!
+ $$DATA = substr($$DATA,128,$MacBinary_ForkLength);
+ my $ResourceLength = $DataLength - $MacBinary_ForkLength - 128;
+ $DataLength = $MacBinary_ForkLength;
+ }
+ }
+ }
+
+ # A file name is changed into EUC.
+# &jcode::convert(\$FileName,'euc',$FormCodeDefault);
+# &jcode::h2z_euc(\$FileName);
+ $FileName =~ s/^.*\\//; # Windows, Mac
+ $FileName =~ s/^.*\///; # UNIX
+ $FileName =~ s/&/&amp;/g;
+ $FileName =~ s/"/&quot;/g;
+ $FileName =~ s/</&lt;/g;
+ $FileName =~ s/>/&gt;/g;
+#
+# if($CHARCODE ne 'euc') {
+# &jcode::convert(\$FileName,$CHARCODE,'euc');
+# }
+
+ # An extension is extracted and it changes into a small letter.
+ my $FileExt;
+ if($FileName =~ /\.(\w+)$/) {
+ $FileExt = $1;
+ $FileExt =~ tr/A-Z/a-z/;
+ }
+
+ # Executable file detection (ban on upload)
+ if($$DATA =~ /^MZ/) {
+ $Ext = 'exe';
+ }
+ # text
+ if(!$Ext && ($UnknownType || $ContentType =~ /^text\//i || $ContentType =~ /^application\/(?:rtf|richtext)$/i || $ContentType =~ /^image\/x-xbitmap$/i)
+ && ! $$DATA =~ /[\000-\006\177\377]/) {
+# $$DATA =~ s/\x0D\x0A/\n/g;
+# $$DATA =~ tr/\x0D\x0A/\n\n/;
+#
+# if(
+# $$DATA =~ /<\s*SCRIPT(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bONLOAD\s*=(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bONCLICK\s*=(?:.|\n)*?>/i
+# ) {
+# $Infomation = '(JavaScript contains)';
+# }
+# if($$DATA =~ /<\s*TABLE(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*BLINK(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*MARQUEE(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*OBJECT(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*EMBED(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*FRAME(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*APPLET(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*FORM(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bSRC\s*=(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bDYNSRC\s*=(?:.|\n)*?>/i
+# ) {
+# $Infomation = '(the HTML tag which is not safe is included)';
+# }
+
+ if($FileExt =~ /^txt$/i || $FileExt =~ /^cgi$/i || $FileExt =~ /^pl$/i) { # Text File
+ $Ext = 'txt';
+ } elsif($ContentType =~ /^text\/html$/i || $FileExt =~ /html?/i || $$DATA =~ /<\s*HTML(?:.|\n)*?>/i) { # HTML File
+ $Ext = 'html';
+ } elsif($ContentType =~ /^image\/x-xbitmap$/i || $FileExt =~ /^xbm$/i) { # XBM(x-BitMap) Image
+ my $XbmName = $1;
+ my ($XbmWidth, $XbmHeight);
+ if($$DATA =~ /\#define\s*$XbmName\_width\s*(\d+)/i) {
+ $XbmWidth = $1;
+ }
+ if($$DATA =~ /\#define\s*$XbmName\_height\s*(\d+)/i) {
+ $XbmHeight = $1;
+ }
+ if($XbmWidth && $XbmHeight) {
+ $Ext = 'xbm';
+ $ImageWidth = $XbmWidth;
+ $ImageHeight = $XbmHeight;
+ }
+ } else { #
+ $Ext = 'txt';
+ }
+ }
+
+ # image
+ if(!$Ext && ($UnknownType || $ContentType =~ /^image\//i)) {
+ # PNG
+ if($$DATA =~ /^\x89PNG\x0D\x0A\x1A\x0A/) {
+ if(substr($$DATA, 12, 4) eq 'IHDR') {
+ $Ext = 'png';
+ ($ImageWidth, $ImageHeight) = unpack("N2", substr($$DATA, 16, 8));
+ }
+ } elsif($$DATA =~ /^GIF8(?:9|7)a/) { # GIF89a(modified), GIF89a, GIF87a
+ $Ext = 'gif';
+ ($ImageWidth, $ImageHeight) = unpack("v2", substr($$DATA, 6, 4));
+ } elsif($$DATA =~ /^II\x2a\x00\x08\x00\x00\x00/ || $$DATA =~ /^MM\x00\x2a\x00\x00\x00\x08/) { # TIFF
+ $Ext = 'tif';
+ } elsif($$DATA =~ /^BM/) { # BMP
+ $Ext = 'bmp';
+ } elsif($$DATA =~ /^\xFF\xD8\xFF/ || $$DATA =~ /JFIF/) { # JPEG
+ my $HeaderPoint = index($$DATA, "\xFF\xD8\xFF", 0);
+ my $Point = $HeaderPoint + 2;
+ while($Point < $DataLength) {
+ my($Maker, $MakerType, $MakerLength) = unpack("C2n",substr($$DATA,$Point,4));
+ if($Maker != 0xFF || $MakerType == 0xd9 || $MakerType == 0xda) {
+ last;
+ } elsif($MakerType >= 0xC0 && $MakerType <= 0xC3) {
+ $Ext = 'jpg';
+ ($ImageHeight, $ImageWidth) = unpack("n2", substr($$DATA, $Point + 5, 4));
+ if($HeaderPoint > 0) {
+ $$DATA = substr($$DATA, $HeaderPoint);
+ $DataLength = length($$DATA);
+ }
+ last;
+ } else {
+ $Point += $MakerLength + 2;
+ }
+ }
+ }
+ }
+
+ # audio
+ if(!$Ext && ($UnknownType || $ContentType =~ /^audio\//i)) {
+ # MIDI Audio
+ if($$DATA =~ /^MThd/) {
+ $Ext = 'mid';
+ } elsif($$DATA =~ /^\x2esnd/) { # ULAW Audio
+ $Ext = 'au';
+ } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {
+ my $HeaderPoint = index($$DATA, "RIFF", 0);
+ $_ = substr($$DATA, $HeaderPoint + 8, 8);
+ if(/^WAVEfmt $/) {
+ # WAVE
+ if(unpack("V",substr($$DATA, $HeaderPoint + 16, 4)) == 16) {
+ $Ext = 'wav';
+ } else { # RIFF WAVE MP3
+ $Ext = 'mp3';
+ }
+ } elsif(/^RMIDdata$/) { # RIFF MIDI
+ $Ext = 'rmi';
+ } elsif(/^RMP3data$/) { # RIFF MP3
+ $Ext = 'rmp';
+ }
+ if($ContentType =~ /^audio\//i) {
+ $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';
+ }
+ }
+ }
+
+ # a binary file
+ unless ($Ext) {
+ # PDF image
+ if($$DATA =~ /^\%PDF/) {
+ # Picture size is not measured.
+ $Ext = 'pdf';
+ } elsif($$DATA =~ /^FWS/) { # Shockwave Flash
+ $Ext = 'swf';
+ } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {
+ my $HeaderPoint = index($$DATA, "RIFF", 0);
+ $_ = substr($$DATA,$HeaderPoint + 8, 8);
+ # AVI
+ if(/^AVI LIST$/) {
+ $Ext = 'avi';
+ }
+ if($ContentType =~ /^video\//i) {
+ $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';
+ }
+ } elsif($$DATA =~ /^PK/) { # ZIP Compress File
+ $Ext = 'zip';
+ } elsif($$DATA =~ /^MSCF/) { # CAB Compress File
+ $Ext = 'cab';
+ } elsif($$DATA =~ /^Rar\!/) { # RAR Compress File
+ $Ext = 'rar';
+ } elsif(substr($$DATA, 2, 5) =~ /^\-lh(\d+|d)\-$/) { # LHA Compress File
+ $Infomation .= "(lh$1)";
+ $Ext = 'lzh';
+ } elsif(substr($$DATA, 325, 25) eq "Apple Video Media Handler" || substr($$DATA, 325, 30) eq "Apple \x83\x72\x83\x66\x83\x49\x81\x45\x83\x81\x83\x66\x83\x42\x83\x41\x83\x6E\x83\x93\x83\x68\x83\x89") {
+ # QuickTime
+ $Ext = 'mov';
+ }
+ }
+
+ # Header analysis failure
+ unless ($Ext) {
+ # It will be followed if it applies for the MIME type from the browser.
+ foreach (keys %UPLOAD_CONTENT_TYPE_LIST) {
+ next unless ($_);
+ if($ContentType =~ /^$_$/i) {
+ $Ext = $UPLOAD_CONTENT_TYPE_LIST{$_};
+ $ContentName = &CheckContentExt($Ext);
+ if(
+ grep {$_ eq $Ext;} (
+ 'png',
+ 'gif',
+ 'jpg',
+ 'xbm',
+ 'tif',
+ 'bmp',
+ 'pdf',
+ 'swf',
+ 'mov',
+ 'zip',
+ 'cab',
+ 'lzh',
+ 'rar',
+ 'mid',
+ 'rmi',
+ 'au',
+ 'wav',
+ 'avi',
+ 'exe'
+ )
+ ) {
+ $Infomation .= ' / Header analysis failure';
+ }
+ if($Ext ne $FileExt && &CheckContentExt($FileExt) eq $ContentName) {
+ $Ext = $FileExt;
+ }
+ last;
+ }
+ }
+ # a MIME type is unknown--It judges from an extension.
+ unless ($Ext) {
+ $ContentName = &CheckContentExt($FileExt);
+ if($ContentName) {
+ $Ext = $FileExt;
+ $Infomation .= ' / MIME type is unknown('. $ContentType. ')';
+ last;
+ }
+ }
+ }
+
+# $ContentName = &CheckContentExt($Ext) unless($ContentName);
+# if($Ext && $ContentName) {
+# $ContentName .= $Infomation;
+# } else {
+# &upload_error(
+# 'Extension Error',
+# "$FileName A not corresponding extension ($Ext)<BR>The extension which can be responded ". join(',', sort values(%UPLOAD_EXT_LIST))
+# );
+# }
+
+# # SSI Tag Deletion
+# if($Ext =~ /.?html?/ && $$DATA =~ /<\!/) {
+# foreach (
+# 'config',
+# 'echo',
+# 'exec',
+# 'flastmod',
+# 'fsize',
+# 'include'
+# ) {
+# $$DATA =~ s/\#\s*$_/\&\#35\;$_/ig
+# }
+# }
+
+ return (
+ $FileName,
+ $Ext,
+ int($DataLength / 1024 + 1),
+ $ImageWidth,
+ $ImageHeight,
+ $ContentName
+ );
+}
+
+##############################################################################
+# Summary
+#
+# Extension discernment
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub CheckContentExt
+{
+
+ my($Ext) = @_;
+ my $ContentName;
+ foreach (keys %UPLOAD_EXT_LIST) {
+ next unless ($_);
+ if($_ && $Ext =~ /^$_$/) {
+ $ContentName = $UPLOAD_EXT_LIST{$_};
+ last;
+ }
+ }
+ return $ContentName;
+
+}
+
+##############################################################################
+# Summary
+#
+# Form decode
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub Encode
+{
+
+ my($value,$Trans) = @_;
+
+# my $FormCode = &jcode::getcode($value) || $FormCodeDefault;
+# $FormCodeDefault ||= $FormCode;
+#
+# if($Trans && $TRANS_2BYTE_CODE) {
+# if($FormCode ne 'euc') {
+# &jcode::convert($value, 'euc', $FormCode);
+# }
+# &jcode::tr(
+# $value,
+# "\xA3\xB0-\xA3\xB9\xA3\xC1-\xA3\xDA\xA3\xE1-\xA3\xFA",
+# '0-9A-Za-z'
+# );
+# if($CHARCODE ne 'euc') {
+# &jcode::convert($value,$CHARCODE,'euc');
+# }
+# } else {
+# if($CHARCODE ne $FormCode) {
+# &jcode::convert($value,$CHARCODE,$FormCode);
+# }
+# }
+# if($CHARCODE eq 'euc') {
+# &jcode::h2z_euc($value);
+# } elsif($CHARCODE eq 'sjis') {
+# &jcode::h2z_sjis($value);
+# }
+
+}
+
+##############################################################################
+# Summary
+#
+# Error Msg
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub upload_error
+{
+
+ local($error_message) = $_[0];
+ local($error_message2) = $_[1];
+
+ print "Content-type: text/html\n\n";
+ print<<EOF;
+<HTML>
+<HEAD>
+<TITLE>Error Message</TITLE></HEAD>
+<BODY>
+<table border="1" cellspacing="10" cellpadding="10">
+ <TR bgcolor="#0000B0">
+ <TD bgcolor="#0000B0" NOWRAP><font size="-1" color="white"><B>Error Message</B></font></TD>
+ </TR>
+</table>
+<UL>
+<H4> $error_message </H4>
+$error_message2 <BR>
+</UL>
+</BODY>
+</HTML>
+EOF
+ &rm_tmp_uploaded_files; # Image Temporary deletion
+ exit;
+}
+
+##############################################################################
+# Summary
+#
+# Image Temporary deletion
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub rm_tmp_uploaded_files
+{
+ if($img_data_exists == 1){
+ sleep 1;
+ foreach $fname_list(@NEWFNAMES) {
+ if(-e "$img_dir/$fname_list") {
+ unlink("$img_dir/$fname_list");
+ }
+ }
+ }
+
+}
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl
new file mode 100644
index 0000000..e860292
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl
@@ -0,0 +1,60 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2007 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub RemoveFromStart
+{
+ local($sourceString, $charToRemove) = @_;
+ $sPattern = '^' . $charToRemove . '+' ;
+ $sourceString =~ s/^$charToRemove+//g;
+ return $sourceString;
+}
+
+sub RemoveFromEnd
+{
+ local($sourceString, $charToRemove) = @_;
+ $sPattern = $charToRemove . '+$' ;
+ $sourceString =~ s/$charToRemove+$//g;
+ return $sourceString;
+}
+
+sub ConvertToXmlAttribute
+{
+ local($value) = @_;
+ return $value;
+# return utf8_encode(htmlspecialchars($value));
+
+}
+
+sub specialchar_cnv
+{
+ local($ch) = @_;
+
+ $ch =~ s/&/&amp;/g; # &
+ $ch =~ s/\"/&quot;/g; #"
+ $ch =~ s/\'/&#39;/g; # '
+ $ch =~ s/</&lt;/g; # <
+ $ch =~ s/>/&gt;/g; # >
+ return($ch);
+}
+
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html
new file mode 100644
index 0000000..dc1f517
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page shows the actual folder path.
+-->
+<html>
+ <head>
+ <title>Folder path</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <script type="text/javascript">
+// Automatically detect the correct document.domain (#1919).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.top.opener.document.domain ;
+ break ;
+ }
+ catch( e )
+ {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+function SetCurrentFolder( resourceType, folderPath )
+{
+ document.getElementById('tdName').innerHTML = folderPath ;
+}
+
+window.onload = function()
+{
+ window.top.IsLoadedActualFolder = true ;
+}
+
+ </script>
+ </head>
+ <body>
+ <table class="fullHeight" cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <button style="WIDTH: 100%" type="button">
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td><img height="32" alt="" src="images/FolderOpened32.gif" width="32"></td>
+ <td>&nbsp;</td>
+ <td id="tdName" width="100%" nowrap class="ActualFolder">/</td>
+ <td>&nbsp;</td>
+ <td><img height="8" src="images/ButtonArrow.gif" width="12" alt=""></td>
+ <td>&nbsp;</td>
+ </tr>
+ </table>
+ </button>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html
new file mode 100644
index 0000000..390eb49
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Page used to create new folders in the current folder.
+-->
+<html>
+ <head>
+ <title>Create Folder</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript">
+
+function SetCurrentFolder( resourceType, folderPath )
+{
+ oConnector.ResourceType = resourceType ;
+ oConnector.CurrentFolder = folderPath ;
+}
+
+function CreateFolder()
+{
+ var sFolderName ;
+
+ while ( true )
+ {
+ sFolderName = prompt( 'Type the name of the new folder:', '' ) ;
+
+ if ( sFolderName == null )
+ return ;
+ else if ( sFolderName.length == 0 )
+ alert( 'Please type the folder name' ) ;
+ else
+ break ;
+ }
+
+ oConnector.SendCommand( 'CreateFolder', 'NewFolderName=' + encodeURIComponent( sFolderName) , CreateFolderCallBack ) ;
+}
+
+function CreateFolderCallBack( fckXml )
+{
+ if ( oConnector.CheckError( fckXml ) == 0 )
+ window.parent.frames['frmResourcesList'].Refresh() ;
+
+ /*
+ // Get the current folder path.
+ var oNode = fckXml.SelectSingleNode( 'Connector/Error' ) ;
+ var iErrorNumber = parseInt( oNode.attributes.getNamedItem('number').value ) ;
+
+ switch ( iErrorNumber )
+ {
+ case 0 :
+ window.parent.frames['frmResourcesList'].Refresh() ;
+ break ;
+ case 101 :
+ alert( 'Folder already exists' ) ;
+ break ;
+ case 102 :
+ alert( 'Invalid folder name' ) ;
+ break ;
+ case 103 :
+ alert( 'You have no permissions to create the folder' ) ;
+ break ;
+ case 110 :
+ alert( 'Unknown error creating folder' ) ;
+ break ;
+ default :
+ alert( 'Error creating folder. Error number: ' + iErrorNumber ) ;
+ break ;
+ }
+ */
+}
+
+window.onload = function()
+{
+ window.top.IsLoadedCreateFolder = true ;
+}
+ </script>
+ </head>
+ <body>
+ <table class="fullHeight" cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <button type="button" style="WIDTH: 100%" onclick="CreateFolder();">
+ <table cellSpacing="0" cellPadding="0" border="0">
+ <tr>
+ <td><img height="16" alt="" src="images/Folder.gif" width="16"></td>
+ <td>&nbsp;</td>
+ <td nowrap>Create New Folder</td>
+ </tr>
+ </table>
+ </button>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html
new file mode 100644
index 0000000..d4b3b3d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page shows the list of folders available in the parent folder
+ * of the current folder.
+-->
+<html>
+ <head>
+ <title>Folders</title>
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript">
+
+var sActiveFolder ;
+
+var bIsLoaded = false ;
+var iIntervalId ;
+
+var oListManager = new Object() ;
+
+oListManager.Init = function()
+{
+ this.Table = document.getElementById('tableFiles') ;
+ this.UpRow = document.getElementById('trUp') ;
+
+ this.TableRows = new Object() ;
+}
+
+oListManager.Clear = function()
+{
+ // Remove all other rows available.
+ while ( this.Table.rows.length > 1 )
+ this.Table.deleteRow(1) ;
+
+ // Reset the TableRows collection.
+ this.TableRows = new Object() ;
+}
+
+oListManager.AddItem = function( folderName, folderPath )
+{
+ // Create the new row.
+ var oRow = this.Table.insertRow(-1) ;
+ oRow.className = 'FolderListFolder' ;
+
+ // Build the link to view the folder.
+ var sLink = '<a href="#" onclick="OpenFolder(\'' + folderPath + '\');return false;">' ;
+
+ // Add the folder icon cell.
+ var oCell = oRow.insertCell(-1) ;
+ oCell.width = 16 ;
+ oCell.innerHTML = sLink + '<img alt="" src="images/spacer.gif" width="16" height="16" border="0"><\/a>' ;
+
+ // Add the folder name cell.
+ oCell = oRow.insertCell(-1) ;
+ oCell.noWrap = true ;
+ oCell.innerHTML = '&nbsp;' + sLink + folderName + '<\/a>' ;
+
+ this.TableRows[ folderPath ] = oRow ;
+}
+
+oListManager.ShowUpFolder = function( upFolderPath )
+{
+ this.UpRow.style.display = ( upFolderPath != null ? '' : 'none' ) ;
+
+ if ( upFolderPath != null )
+ {
+ document.getElementById('linkUpIcon').onclick = document.getElementById('linkUp').onclick = function()
+ {
+ LoadFolders( upFolderPath ) ;
+ return false ;
+ }
+ }
+}
+
+function CheckLoaded()
+{
+ if ( window.top.IsLoadedActualFolder
+ && window.top.IsLoadedCreateFolder
+ && window.top.IsLoadedUpload
+ && window.top.IsLoadedResourcesList )
+ {
+ window.clearInterval( iIntervalId ) ;
+ bIsLoaded = true ;
+ OpenFolder( sActiveFolder ) ;
+ }
+}
+
+function OpenFolder( folderPath )
+{
+ sActiveFolder = folderPath ;
+
+ if ( ! bIsLoaded )
+ {
+ if ( ! iIntervalId )
+ iIntervalId = window.setInterval( CheckLoaded, 100 ) ;
+ return ;
+ }
+
+ // Change the style for the select row (to show the opened folder).
+ for ( var sFolderPath in oListManager.TableRows )
+ {
+ oListManager.TableRows[ sFolderPath ].className =
+ ( sFolderPath == folderPath ? 'FolderListCurrentFolder' : 'FolderListFolder' ) ;
+ }
+
+ // Set the current folder in all frames.
+ window.parent.frames['frmActualFolder'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;
+ window.parent.frames['frmCreateFolder'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;
+ window.parent.frames['frmUpload'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;
+
+ // Load the resources list for this folder.
+ window.parent.frames['frmResourcesList'].LoadResources( oConnector.ResourceType, folderPath ) ;
+}
+
+function LoadFolders( folderPath )
+{
+ // Clear the folders list.
+ oListManager.Clear() ;
+
+ // Get the parent folder path.
+ var sParentFolderPath ;
+ if ( folderPath != '/' )
+ sParentFolderPath = folderPath.substring( 0, folderPath.lastIndexOf( '/', folderPath.length - 2 ) + 1 ) ;
+
+ // Show/Hide the Up Folder.
+ oListManager.ShowUpFolder( sParentFolderPath ) ;
+
+ if ( folderPath != '/' )
+ {
+ sActiveFolder = folderPath ;
+ oConnector.CurrentFolder = sParentFolderPath ;
+ oConnector.SendCommand( 'GetFolders', null, GetFoldersCallBack ) ;
+ }
+ else
+ OpenFolder( '/' ) ;
+}
+
+function GetFoldersCallBack( fckXml )
+{
+ if ( oConnector.CheckError( fckXml ) != 0 )
+ return ;
+
+ // Get the current folder path.
+ var oNode = fckXml.SelectSingleNode( 'Connector/CurrentFolder' ) ;
+ var sCurrentFolderPath = oNode.attributes.getNamedItem('path').value ;
+
+ var oNodes = fckXml.SelectNodes( 'Connector/Folders/Folder' ) ;
+
+ for ( var i = 0 ; i < oNodes.length ; i++ )
+ {
+ var sFolderName = oNodes[i].attributes.getNamedItem('name').value ;
+ oListManager.AddItem( sFolderName, sCurrentFolderPath + sFolderName + '/' ) ;
+ }
+
+ OpenFolder( sActiveFolder ) ;
+}
+
+function SetResourceType( type )
+{
+ oConnector.ResourceType = type ;
+ LoadFolders( '/' ) ;
+}
+
+window.onload = function()
+{
+ oListManager.Init() ;
+ LoadFolders( '/' ) ;
+}
+ </script>
+ </head>
+ <body class="FileArea">
+ <table id="tableFiles" cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr id="trUp" style="DISPLAY: none">
+ <td width="16"><a id="linkUpIcon" href="#"><img alt="" src="images/FolderUp.gif" width="16" height="16" border="0"></a></td>
+ <td nowrap width="100%">&nbsp;<a id="linkUp" href="#">..</a></td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html
new file mode 100644
index 0000000..3e2771d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html
@@ -0,0 +1,169 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page shows all resources available in a folder in the File Browser.
+-->
+<html>
+<head>
+ <title>Resources</title>
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript">
+
+var oListManager = new Object() ;
+
+oListManager.Clear = function()
+{
+ document.body.innerHTML = '' ;
+}
+
+function ProtectPath(path)
+{
+ path = path.replace( /\\/g, '\\\\') ;
+ path = path.replace( /'/g, '\\\'') ;
+ return path ;
+}
+
+oListManager.GetFolderRowHtml = function( folderName, folderPath )
+{
+ // Build the link to view the folder.
+ var sLink = '<a href="#" onclick="OpenFolder(\'' + ProtectPath( folderPath ) + '\');return false;">' ;
+
+ return '<tr>' +
+ '<td width="16">' +
+ sLink +
+ '<img alt="" src="images/Folder.gif" width="16" height="16" border="0"><\/a>' +
+ '<\/td><td nowrap colspan="2">&nbsp;' +
+ sLink +
+ folderName +
+ '<\/a>' +
+ '<\/td><\/tr>' ;
+}
+
+oListManager.GetFileRowHtml = function( fileName, fileUrl, fileSize )
+{
+ // Build the link to view the folder.
+ var sLink = '<a href="#" onclick="OpenFile(\'' + ProtectPath( fileUrl ) + '\');return false;">' ;
+
+ // Get the file icon.
+ var sIcon = oIcons.GetIcon( fileName ) ;
+
+ return '<tr>' +
+ '<td width="16">' +
+ sLink +
+ '<img alt="" src="images/icons/' + sIcon + '.gif" width="16" height="16" border="0"><\/a>' +
+ '<\/td><td>&nbsp;' +
+ sLink +
+ fileName +
+ '<\/a>' +
+ '<\/td><td align="right" nowrap>&nbsp;' +
+ fileSize +
+ ' KB' +
+ '<\/td><\/tr>' ;
+}
+
+function OpenFolder( folderPath )
+{
+ // Load the resources list for this folder.
+ window.parent.frames['frmFolders'].LoadFolders( folderPath ) ;
+}
+
+function OpenFile( fileUrl )
+{
+ window.top.opener.SetUrl( fileUrl ) ;
+ window.top.close() ;
+ window.top.opener.focus() ;
+}
+
+function LoadResources( resourceType, folderPath )
+{
+ oListManager.Clear() ;
+ oConnector.ResourceType = resourceType ;
+ oConnector.CurrentFolder = folderPath ;
+ oConnector.SendCommand( 'GetFoldersAndFiles', null, GetFoldersAndFilesCallBack ) ;
+}
+
+function Refresh()
+{
+ LoadResources( oConnector.ResourceType, oConnector.CurrentFolder ) ;
+}
+
+function GetFoldersAndFilesCallBack( fckXml )
+{
+ if ( oConnector.CheckError( fckXml ) != 0 )
+ return ;
+
+ // Get the current folder path.
+ var oFolderNode = fckXml.SelectSingleNode( 'Connector/CurrentFolder' ) ;
+ if ( oFolderNode == null )
+ {
+ alert( 'The server didn\'t reply with a proper XML data. Please check your configuration.' ) ;
+ return ;
+ }
+ var sCurrentFolderPath = oFolderNode.attributes.getNamedItem('path').value ;
+ var sCurrentFolderUrl = oFolderNode.attributes.getNamedItem('url').value ;
+
+// var dTimer = new Date() ;
+
+ var oHtml = new StringBuilder( '<table id="tableFiles" cellspacing="1" cellpadding="0" width="100%" border="0">' ) ;
+
+ // Add the Folders.
+ var oNodes ;
+ oNodes = fckXml.SelectNodes( 'Connector/Folders/Folder' ) ;
+ for ( var i = 0 ; i < oNodes.length ; i++ )
+ {
+ var sFolderName = oNodes[i].attributes.getNamedItem('name').value ;
+ oHtml.Append( oListManager.GetFolderRowHtml( sFolderName, sCurrentFolderPath + sFolderName + "/" ) ) ;
+ }
+
+ // Add the Files.
+ oNodes = fckXml.SelectNodes( 'Connector/Files/File' ) ;
+ for ( var j = 0 ; j < oNodes.length ; j++ )
+ {
+ var oNode = oNodes[j] ;
+ var sFileName = oNode.attributes.getNamedItem('name').value ;
+ var sFileSize = oNode.attributes.getNamedItem('size').value ;
+
+ // Get the optional "url" attribute. If not available, build the url.
+ var oFileUrlAtt = oNodes[j].attributes.getNamedItem('url') ;
+ var sFileUrl = oFileUrlAtt != null ? oFileUrlAtt.value : encodeURI( sCurrentFolderUrl + sFileName ).replace( /#/g, '%23' ) ;
+
+ oHtml.Append( oListManager.GetFileRowHtml( sFileName, sFileUrl, sFileSize ) ) ;
+ }
+
+ oHtml.Append( '<\/table>' ) ;
+
+ document.body.innerHTML = oHtml.ToString() ;
+
+// window.top.document.title = 'Finished processing in ' + ( ( ( new Date() ) - dTimer ) / 1000 ) + ' seconds' ;
+
+}
+
+window.onload = function()
+{
+ window.top.IsLoadedResourcesList = true ;
+}
+ </script>
+</head>
+<body class="FileArea">
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html
new file mode 100644
index 0000000..e918f9f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This page shows the list of available resource types.
+-->
+<html>
+ <head>
+ <title>Available types</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="browser.css" type="text/css" rel="stylesheet">
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript">
+
+function SetResourceType( type )
+{
+ window.parent.frames["frmFolders"].SetResourceType( type ) ;
+}
+
+var aTypes = [
+ ['File','File'],
+ ['Image','Image'],
+ ['Flash','Flash'],
+ ['Media','Media']
+] ;
+
+window.onload = function()
+{
+ var oCombo = document.getElementById('cmbType') ;
+ oCombo.innerHTML = '' ;
+ for ( var i = 0 ; i < aTypes.length ; i++ )
+ {
+ if ( oConnector.ShowAllTypes || aTypes[i][0] == oConnector.ResourceType )
+ AddSelectOption( oCombo, aTypes[i][1], aTypes[i][0] ) ;
+ }
+}
+
+ </script>
+ </head>
+ <body>
+ <table class="fullHeight" cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap>
+ Resource Type<BR>
+ <select id="cmbType" style="WIDTH: 100%" onchange="SetResourceType(this.value);">
+ <option>&nbsp;
+ </select>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html
new file mode 100644
index 0000000..e840c0a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Page used to upload new files in the current folder.
+-->
+<html>
+ <head>
+ <title>File Upload</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="browser.css" type="text/css" rel="stylesheet" >
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript">
+
+function SetCurrentFolder( resourceType, folderPath )
+{
+ var sUrl = oConnector.ConnectorUrl + 'Command=FileUpload' ;
+ sUrl += '&Type=' + resourceType ;
+ sUrl += '&CurrentFolder=' + encodeURIComponent( folderPath ) ;
+
+ document.getElementById('frmUpload').action = sUrl ;
+}
+
+function OnSubmit()
+{
+ if ( document.getElementById('NewFile').value.length == 0 )
+ {
+ alert( 'Please select a file from your computer' ) ;
+ return false ;
+ }
+
+ // Set the interface elements.
+ document.getElementById('eUploadMessage').innerHTML = 'Upload a new file in this folder (Upload in progress, please wait...)' ;
+ document.getElementById('btnUpload').disabled = true ;
+
+ return true ;
+}
+
+function OnUploadCompleted( errorNumber, data )
+{
+ // Reset the Upload Worker Frame.
+ window.parent.frames['frmUploadWorker'].location = 'javascript:void(0)' ;
+
+ // Reset the upload form (On IE we must do a little trick to avoid problems).
+ if ( document.all )
+ document.getElementById('NewFile').outerHTML = '<input id="NewFile" name="NewFile" style="WIDTH: 100%" type="file">' ;
+ else
+ document.getElementById('frmUpload').reset() ;
+
+ // Reset the interface elements.
+ document.getElementById('eUploadMessage').innerHTML = 'Upload a new file in this folder' ;
+ document.getElementById('btnUpload').disabled = false ;
+
+ switch ( errorNumber )
+ {
+ case 0 :
+ window.parent.frames['frmResourcesList'].Refresh() ;
+ break ;
+ case 1 : // Custom error.
+ alert( data ) ;
+ break ;
+ case 201 :
+ window.parent.frames['frmResourcesList'].Refresh() ;
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + data + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file' ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ break ;
+ }
+}
+
+window.onload = function()
+{
+ window.top.IsLoadedUpload = true ;
+}
+ </script>
+ </head>
+ <body>
+ <form id="frmUpload" action="" target="frmUploadWorker" method="post" enctype="multipart/form-data" onsubmit="return OnSubmit();">
+ <table class="fullHeight" cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap="nowrap">
+ <span id="eUploadMessage">Upload a new file in this folder</span><br>
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td width="100%"><input id="NewFile" name="NewFile" style="WIDTH: 100%" type="file"></td>
+ <td nowrap="nowrap">&nbsp;<input id="btnUpload" type="submit" value="Upload"></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </form>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif
new file mode 100644
index 0000000..a355e5a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif
new file mode 100644
index 0000000..ab6824d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif
new file mode 100644
index 0000000..b93b752
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif
new file mode 100644
index 0000000..0c5dd41
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif
new file mode 100644
index 0000000..3e3fcf5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif
new file mode 100644
index 0000000..ad5bc20
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif
new file mode 100644
index 0000000..699e6a3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif
new file mode 100644
index 0000000..97025bb
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif
new file mode 100644
index 0000000..f3c7f82
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif
new file mode 100644
index 0000000..b62bd02
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif
new file mode 100644
index 0000000..976997b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif
new file mode 100644
index 0000000..9b54964
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif
new file mode 100644
index 0000000..b557568
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif
new file mode 100644
index 0000000..7584993
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif
new file mode 100644
index 0000000..923079f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif
new file mode 100644
index 0000000..df5f579
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif
new file mode 100644
index 0000000..a9bdf00
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif
new file mode 100644
index 0000000..a9bdf00
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif
new file mode 100644
index 0000000..de78363
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif
new file mode 100644
index 0000000..fe0c98e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif
new file mode 100644
index 0000000..d3af9e8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif
new file mode 100644
index 0000000..7d6360f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif
new file mode 100644
index 0000000..4950ec8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif
new file mode 100644
index 0000000..0a79ebf
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif
new file mode 100644
index 0000000..023431c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif
new file mode 100644
index 0000000..b9eace7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif
new file mode 100644
index 0000000..5df7de5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif
new file mode 100644
index 0000000..7807c07
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif
new file mode 100644
index 0000000..4e2c2e3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif
new file mode 100644
index 0000000..7624697
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif
new file mode 100644
index 0000000..afe724a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif
new file mode 100644
index 0000000..4fae356
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif
new file mode 100644
index 0000000..7157f72
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif
new file mode 100644
index 0000000..ba5a913
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif
new file mode 100644
index 0000000..6f3bac9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif
new file mode 100644
index 0000000..7708dd8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif
new file mode 100644
index 0000000..4d92723
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif
new file mode 100644
index 0000000..6ce26a4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif
new file mode 100644
index 0000000..48d445a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif
new file mode 100644
index 0000000..6535b4c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif
new file mode 100644
index 0000000..315817f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif
new file mode 100644
index 0000000..8f91a98
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif
new file mode 100644
index 0000000..a5e3e6c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif
new file mode 100644
index 0000000..0b5d6ba
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif
new file mode 100644
index 0000000..0b5d6ba
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif
new file mode 100644
index 0000000..634b386
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif
new file mode 100644
index 0000000..4ea17d4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif
new file mode 100644
index 0000000..0d7c102
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif
new file mode 100644
index 0000000..6f3bac9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif
new file mode 100644
index 0000000..ca1f94a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif
new file mode 100644
index 0000000..b6d1b32
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif
new file mode 100644
index 0000000..877a8c8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif
new file mode 100644
index 0000000..916cd7e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif
new file mode 100644
index 0000000..314469d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif
new file mode 100644
index 0000000..314469d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif
new file mode 100644
index 0000000..1511ba3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif
new file mode 100644
index 0000000..9be3daa
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif
new file mode 100644
index 0000000..f57715d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif
new file mode 100644
index 0000000..4559928
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif
new file mode 100644
index 0000000..b1e2492
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif
new file mode 100644
index 0000000..35d42e8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js b/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js
new file mode 100644
index 0000000..98c4b90
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js
@@ -0,0 +1,88 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Common objects and functions shared by all pages that compose the
+ * File Browser dialog window.
+ */
+
+// Automatically detect the correct document.domain (#1919).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.top.opener.document.domain ;
+ break ;
+ }
+ catch( e )
+ {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+function AddSelectOption( selectElement, optionText, optionValue )
+{
+ var oOption = document.createElement("OPTION") ;
+
+ oOption.text = optionText ;
+ oOption.value = optionValue ;
+
+ selectElement.options.add(oOption) ;
+
+ return oOption ;
+}
+
+var oConnector = window.parent.oConnector ;
+var oIcons = window.parent.oIcons ;
+
+
+function StringBuilder( value )
+{
+ this._Strings = new Array( value || '' ) ;
+}
+
+StringBuilder.prototype.Append = function( value )
+{
+ if ( value )
+ this._Strings.push( value ) ;
+}
+
+StringBuilder.prototype.ToString = function()
+{
+ return this._Strings.join( '' ) ;
+}
diff --git a/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js b/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js
new file mode 100644
index 0000000..b7eddc6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js
@@ -0,0 +1,147 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Defines the FCKXml object that is used for XML data calls
+ * and XML processing.
+ *
+ * This script is shared by almost all pages that compose the
+ * File Browser frameset.
+ */
+
+var FCKXml = function()
+{}
+
+FCKXml.prototype.GetHttpRequest = function()
+{
+ // Gecko / IE7
+ try { return new XMLHttpRequest(); }
+ catch(e) {}
+
+ // IE6
+ try { return new ActiveXObject( 'Msxml2.XMLHTTP' ) ; }
+ catch(e) {}
+
+ // IE5
+ try { return new ActiveXObject( 'Microsoft.XMLHTTP' ) ; }
+ catch(e) {}
+
+ return null ;
+}
+
+FCKXml.prototype.LoadUrl = function( urlToCall, asyncFunctionPointer )
+{
+ var oFCKXml = this ;
+
+ var bAsync = ( typeof(asyncFunctionPointer) == 'function' ) ;
+
+ var oXmlHttp = this.GetHttpRequest() ;
+
+ oXmlHttp.open( "GET", urlToCall, bAsync ) ;
+
+ if ( bAsync )
+ {
+ oXmlHttp.onreadystatechange = function()
+ {
+ if ( oXmlHttp.readyState == 4 )
+ {
+ var oXml ;
+ try
+ {
+ // this is the same test for an FF2 bug as in fckxml_gecko.js
+ // but we've moved the responseXML assignment into the try{}
+ // so we don't even have to check the return status codes.
+ var test = oXmlHttp.responseXML.firstChild ;
+ oXml = oXmlHttp.responseXML ;
+ }
+ catch ( e )
+ {
+ try
+ {
+ oXml = (new DOMParser()).parseFromString( oXmlHttp.responseText, 'text/xml' ) ;
+ }
+ catch ( e ) {}
+ }
+
+ if ( !oXml || !oXml.firstChild || oXml.firstChild.nodeName == 'parsererror' )
+ {
+ alert( 'The server didn\'t send back a proper XML response. Please contact your system administrator.\n\n' +
+ 'XML request error: ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')\n\n' +
+ 'Requested URL:\n' + urlToCall + '\n\n' +
+ 'Response text:\n' + oXmlHttp.responseText ) ;
+ return ;
+ }
+
+ oFCKXml.DOMDocument = oXml ;
+ asyncFunctionPointer( oFCKXml ) ;
+ }
+ }
+ }
+
+ oXmlHttp.send( null ) ;
+
+ if ( ! bAsync )
+ {
+ if ( oXmlHttp.status == 200 || oXmlHttp.status == 304 )
+ this.DOMDocument = oXmlHttp.responseXML ;
+ else
+ {
+ alert( 'XML request error: ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')' ) ;
+ }
+ }
+}
+
+FCKXml.prototype.SelectNodes = function( xpath )
+{
+ if ( navigator.userAgent.indexOf('MSIE') >= 0 ) // IE
+ return this.DOMDocument.selectNodes( xpath ) ;
+ else // Gecko
+ {
+ var aNodeArray = new Array();
+
+ var xPathResult = this.DOMDocument.evaluate( xpath, this.DOMDocument,
+ this.DOMDocument.createNSResolver(this.DOMDocument.documentElement), XPathResult.ORDERED_NODE_ITERATOR_TYPE, null) ;
+ if ( xPathResult )
+ {
+ var oNode = xPathResult.iterateNext() ;
+ while( oNode )
+ {
+ aNodeArray[aNodeArray.length] = oNode ;
+ oNode = xPathResult.iterateNext();
+ }
+ }
+ return aNodeArray ;
+ }
+}
+
+FCKXml.prototype.SelectSingleNode = function( xpath )
+{
+ if ( navigator.userAgent.indexOf('MSIE') >= 0 ) // IE
+ return this.DOMDocument.selectSingleNode( xpath ) ;
+ else // Gecko
+ {
+ var xPathResult = this.DOMDocument.evaluate( xpath, this.DOMDocument,
+ this.DOMDocument.createNSResolver(this.DOMDocument.documentElement), 9, null);
+
+ if ( xPathResult && xPathResult.singleNodeValue )
+ return xPathResult.singleNodeValue ;
+ else
+ return null ;
+ }
+}
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/basexml.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/basexml.pl
new file mode 100644
index 0000000..e0835c3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/basexml.pl
@@ -0,0 +1,68 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub CreateXmlHeader
+{
+ local($command,$resourceType,$currentFolder) = @_;
+
+ # Create the XML document header.
+ print '<?xml version="1.0" encoding="utf-8" ?>';
+
+ # Create the main "Connector" node.
+ print '<Connector command="' . $command . '" resourceType="' . $resourceType . '">';
+
+ # Add the current folder node.
+ print '<CurrentFolder path="' . ConvertToXmlAttribute($currentFolder) . '" url="' . ConvertToXmlAttribute(GetUrlFromPath($resourceType,$currentFolder)) . '" />';
+}
+
+sub CreateXmlFooter
+{
+ print '</Connector>';
+}
+
+sub SendError
+{
+ local( $number, $text ) = @_;
+
+ print << "_HTML_HEAD_";
+Content-Type:text/xml; charset=utf-8
+Pragma: no-cache
+Cache-Control: no-cache
+Expires: Thu, 01 Dec 1994 16:00:00 GMT
+
+_HTML_HEAD_
+
+ # Create the XML document header
+ print '<?xml version="1.0" encoding="utf-8" ?>' ;
+
+ if ($text) {
+ print '<Connector><Error number="' . $number . '" text="' . &specialchar_cnv( $text ) . '" /></Connector>' ;
+ }
+ else {
+ print '<Connector><Error number="' . $number . '" /></Connector>' ;
+ }
+
+ exit ;
+}
+
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/commands.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/commands.pl
new file mode 100644
index 0000000..66b4d6d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/commands.pl
@@ -0,0 +1,200 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub GetFolders
+{
+
+ local($resourceType, $currentFolder) = @_;
+
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType, $currentFolder);
+ print "<Folders>"; # Open the "Folders" node.
+
+ opendir(DIR,"$sServerDir");
+ @files = grep(!/^\.\.?$/,readdir(DIR));
+ closedir(DIR);
+
+ foreach $sFile (@files) {
+ if($sFile != '.' && $sFile != '..' && (-d "$sServerDir$sFile")) {
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ print '<Folder name="' . $cnv_filename . '" />';
+ }
+ }
+ print "</Folders>"; # Close the "Folders" node.
+}
+
+sub GetFoldersAndFiles
+{
+
+ local($resourceType, $currentFolder) = @_;
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType,$currentFolder);
+
+ # Initialize the output buffers for "Folders" and "Files".
+ $sFolders = '<Folders>';
+ $sFiles = '<Files>';
+
+ opendir(DIR,"$sServerDir");
+ @files = grep(!/^\.\.?$/,readdir(DIR));
+ closedir(DIR);
+
+ foreach $sFile (@files) {
+ if($sFile ne '.' && $sFile ne '..') {
+ if(-d "$sServerDir$sFile") {
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ $sFolders .= '<Folder name="' . $cnv_filename . '" />' ;
+ } else {
+ ($iFileSize,$refdate,$filedate,$fileperm) = (stat("$sServerDir$sFile"))[7,8,9,2];
+ if($iFileSize > 0) {
+ $iFileSize = int($iFileSize / 1024);
+ if($iFileSize < 1) {
+ $iFileSize = 1;
+ }
+ }
+ $cnv_filename = &ConvertToXmlAttribute($sFile);
+ $sFiles .= '<File name="' . $cnv_filename . '" size="' . $iFileSize . '" />' ;
+ }
+ }
+ }
+ print $sFolders ;
+ print '</Folders>'; # Close the "Folders" node.
+ print $sFiles ;
+ print '</Files>'; # Close the "Files" node.
+}
+
+sub CreateFolder
+{
+
+ local($resourceType, $currentFolder) = @_;
+ $sErrorNumber = '0' ;
+ $sErrorMsg = '' ;
+
+ if($FORM{'NewFolderName'} ne "") {
+ $sNewFolderName = $FORM{'NewFolderName'};
+ $sNewFolderName =~ s/\.|\\|\/|\||\:|\?|\*|\"|<|>|[[:cntrl:]]/_/g;
+ # Map the virtual path to the local server path of the current folder.
+ $sServerDir = &ServerMapFolder($resourceType, $currentFolder);
+ if(-w $sServerDir) {
+ $sServerDir .= $sNewFolderName;
+ $sErrorMsg = &CreateServerFolder($sServerDir);
+ if($sErrorMsg == 0) {
+ $sErrorNumber = '0';
+ } elsif($sErrorMsg eq 'Invalid argument' || $sErrorMsg eq 'No such file or directory') {
+ $sErrorNumber = '102'; #// Path too long.
+ } else {
+ $sErrorNumber = '110';
+ }
+ } else {
+ $sErrorNumber = '103';
+ }
+ } else {
+ $sErrorNumber = '102' ;
+ }
+ # Create the "Error" node.
+ $cnv_errmsg = &ConvertToXmlAttribute($sErrorMsg);
+ print '<Error number="' . $sErrorNumber . '" />';
+}
+
+sub FileUpload
+{
+eval("use File::Copy;");
+
+ local($resourceType, $currentFolder) = @_;
+ $allowedExtensions = $allowedExtensions{$resourceType};
+
+ $sErrorNumber = '0' ;
+ $sFileName = '' ;
+ if($new_fname) {
+ # Map the virtual path to the local server path.
+ $sServerDir = &ServerMapFolder($resourceType,$currentFolder);
+
+ # Get the uploaded file name.
+ $sFileName = $new_fname;
+ $sFileName =~ s/\\|\/|\||\:|\?|\*|\"|<|>|[[:cntrl:]]/_/g;
+ $sFileName =~ s/\.(?![^.]*$)/_/g;
+
+ $ext = '';
+ if($sFileName =~ /([^\\\/]*)\.(.*)$/) {
+ $ext = $2;
+ }
+
+ $allowedRegex = qr/^($allowedExtensions)$/i;
+ if (!($ext =~ $allowedRegex)) {
+ SendUploadResults('202', '', '', '');
+ }
+
+ $sOriginalFileName = $sFileName;
+
+ $iCounter = 0;
+ while(1) {
+ $sFilePath = $sServerDir . $sFileName;
+ if(-e $sFilePath) {
+ $iCounter++ ;
+ ($path,$BaseName,$ext) = &RemoveExtension($sOriginalFileName);
+ $sFileName = $BaseName . '(' . $iCounter . ').' . $ext;
+ $sErrorNumber = '201';
+ } else {
+ copy("$img_dir/$new_fname","$sFilePath");
+ if (defined $CHMOD_ON_UPLOAD) {
+ if ($CHMOD_ON_UPLOAD) {
+ umask(000);
+ chmod($CHMOD_ON_UPLOAD,$sFilePath);
+ }
+ }
+ else {
+ umask(000);
+ chmod(0777,$sFilePath);
+ }
+ unlink("$img_dir/$new_fname");
+ last;
+ }
+ }
+ } else {
+ $sErrorNumber = '202' ;
+ }
+ $sFileName =~ s/"/\\"/g;
+
+ SendUploadResults($sErrorNumber, $GLOBALS{'UserFilesPath'}.$resourceType.$currentFolder.$sFileName, $sFileName, '');
+}
+
+sub SendUploadResults
+{
+
+ local($sErrorNumber, $sFileUrl, $sFileName, $customMsg) = @_;
+
+ # Minified version of the document.domain automatic fix script (#1919).
+ # The original script can be found at _dev/domain_fix_template.js
+ # Note: in Perl replace \ with \\ and $ with \$
+ print <<EOF;
+Content-type: text/html
+
+<script type="text/javascript">
+(function(){var d=document.domain;while (true){try{var A=window.parent.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\\.|\$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();
+
+EOF
+ print 'window.parent.OnUploadCompleted(' . $sErrorNumber . ',"' . JS_cnv($sFileUrl) . '","' . JS_cnv($sFileName) . '","' . JS_cnv($customMsg) . '") ;';
+ print '</script>';
+ exit ;
+}
+
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/config.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/config.pl
new file mode 100644
index 0000000..de85c79
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/config.pl
@@ -0,0 +1,39 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+##
+# SECURITY: REMOVE/COMMENT THE FOLLOWING LINE TO ENABLE THIS CONNECTOR.
+##
+&SendError( 1, 'This connector is disabled. Please check the "editor/filemanager/connectors/perl/config.cgi" file' ) ;
+
+$GLOBALS{'UserFilesPath'} = '/userfiles/';
+
+# Map the "UserFiles" path to a local directory.
+$rootpath = &GetRootPath();
+$GLOBALS{'UserFilesDirectory'} = $rootpath . $GLOBALS{'UserFilesPath'};
+
+%allowedExtensions = ("File", "7z|aiff|asf|avi|bmp|csv|doc|fla|flv|gif|gz|gzip|jpeg|jpg|mid|mov|mp3|mp4|mpc|mpeg|mpg|ods|odt|pdf|png|ppt|pxd|qt|ram|rar|rm|rmi|rmvb|rtf|sdc|sitd|swf|sxc|sxw|tar|tgz|tif|tiff|txt|vsd|wav|wma|wmv|xls|xml|zip",
+"Image", "bmp|gif|jpeg|jpg|png",
+"Flash", "swf|flv",
+"Media", "aiff|asf|avi|bmp|fla|flv|gif|jpeg|jpg|mid|mov|mp3|mp4|mpc|mpeg|mpg|png|qt|ram|rm|rmi|rmvb|swf|tif|tiff|wav|wma|wmv"
+);
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/connector.cgi b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/connector.cgi
new file mode 100644
index 0000000..990a92e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/connector.cgi
@@ -0,0 +1,129 @@
+#!/usr/bin/env perl
+
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+##
+# ATTENTION: To enable this connector, look for the "SECURITY" comment in config.pl.
+##
+
+## START: Hack for Windows (Not important to understand the editor code... Perl specific).
+if(Windows_check()) {
+ chdir(GetScriptPath($0));
+}
+
+sub Windows_check
+{
+ # IIS,PWS(NT/95)
+ $www_server_os = $^O;
+ # Win98 & NT(SP4)
+ if($www_server_os eq "") { $www_server_os= $ENV{'OS'}; }
+ # AnHTTPd/Omni/IIS
+ if($ENV{'SERVER_SOFTWARE'} =~ /AnWeb|Omni|IIS\//i) { $www_server_os= 'win'; }
+ # Win Apache
+ if($ENV{'WINDIR'} ne "") { $www_server_os= 'win'; }
+ if($www_server_os=~ /win/i) { return(1); }
+ return(0);
+}
+
+sub GetScriptPath {
+ local($path) = @_;
+ if($path =~ /[\:\/\\]/) { $path =~ s/(.*?)[\/\\][^\/\\]+$/$1/; } else { $path = '.'; }
+ $path;
+}
+## END: Hack for IIS
+
+require 'util.pl';
+require 'io.pl';
+require 'basexml.pl';
+require 'commands.pl';
+require 'upload_fck.pl';
+require 'config.pl';
+
+&read_input();
+&DoResponse();
+
+sub DoResponse
+{
+
+ if($FORM{'Command'} eq "" || $FORM{'Type'} eq "" || $FORM{'CurrentFolder'} eq "") {
+ return ;
+ }
+ # Get the main request informaiton.
+ $sCommand = &specialchar_cnv($FORM{'Command'});
+ $sResourceType = &specialchar_cnv($FORM{'Type'});
+ $sCurrentFolder = $FORM{'CurrentFolder'};
+
+ if ( !($sCommand =~ /^(FileUpload|GetFolders|GetFoldersAndFiles|CreateFolder)$/) ) {
+ SendError( 1, "Command not allowed" ) ;
+ }
+
+ if ( !($sResourceType =~ /^(File|Image|Flash|Media)$/) ) {
+ SendError( 1, "Invalid type specified" ) ;
+ }
+
+ # Check the current folder syntax (must begin and start with a slash).
+ if(!($sCurrentFolder =~ /\/$/)) {
+ $sCurrentFolder .= '/';
+ }
+ if(!($sCurrentFolder =~ /^\//)) {
+ $sCurrentFolder = '/' . $sCurrentFolder;
+ }
+
+ # Check for invalid folder paths (..)
+ if ( $sCurrentFolder =~ /(?:\.\.|\\)/ ) {
+ SendError( 102, "" ) ;
+ }
+ if ( $sCurrentFolder =~ /(\/\.)|[[:cntrl:]]|(\/\/)|(\\\\)|([\:\*\?\"\<\>\|])/ ) {
+ SendError( 102, "" ) ;
+ }
+
+ # File Upload doesn't have to Return XML, so it must be intercepted before anything.
+ if($sCommand eq 'FileUpload') {
+ FileUpload($sResourceType,$sCurrentFolder);
+ return ;
+ }
+
+ print << "_HTML_HEAD_";
+Content-Type:text/xml; charset=utf-8
+Pragma: no-cache
+Cache-Control: no-cache
+Expires: Thu, 01 Dec 1994 16:00:00 GMT
+
+_HTML_HEAD_
+
+ &CreateXmlHeader($sCommand,$sResourceType,$sCurrentFolder);
+
+ # Execute the required command.
+ if($sCommand eq 'GetFolders') {
+ &GetFolders($sResourceType,$sCurrentFolder);
+ } elsif($sCommand eq 'GetFoldersAndFiles') {
+ &GetFoldersAndFiles($sResourceType,$sCurrentFolder);
+ } elsif($sCommand eq 'CreateFolder') {
+ &CreateFolder($sResourceType,$sCurrentFolder);
+ }
+
+ &CreateXmlFooter();
+
+ exit ;
+}
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/io.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/io.pl
new file mode 100644
index 0000000..56e5481
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/io.pl
@@ -0,0 +1,141 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub GetUrlFromPath
+{
+ local($resourceType, $folderPath) = @_;
+
+ if($resourceType eq '') {
+ $rmpath = &RemoveFromEnd($GLOBALS{'UserFilesPath'},'/');
+ return("$rmpath$folderPath");
+ } else {
+ return("$GLOBALS{'UserFilesPath'}$resourceType$folderPath");
+ }
+}
+
+sub RemoveExtension
+{
+ local($fileName) = @_;
+ local($path, $base, $ext);
+ if($fileName !~ /\./) {
+ $fileName .= '.';
+ }
+ if($fileName =~ /([^\\\/]*)\.(.*)$/) {
+ $base = $1;
+ $ext = $2;
+ if($fileName =~ /(.*)$base\.$ext$/) {
+ $path = $1;
+ }
+ }
+ return($path,$base,$ext);
+
+}
+
+sub ServerMapFolder
+{
+ local($resourceType,$folderPath) = @_;
+
+ # Get the resource type directory.
+ $sResourceTypePath = $GLOBALS{'UserFilesDirectory'} . $resourceType . '/';
+
+ # Ensure that the directory exists.
+ &CreateServerFolder($sResourceTypePath);
+
+ # Return the resource type directory combined with the required path.
+ $rmpath = &RemoveFromStart($folderPath,'/');
+ return("$sResourceTypePath$rmpath");
+}
+
+sub GetParentFolder
+{
+ local($folderPath) = @_;
+
+ $folderPath =~ s/[\/][^\/]+[\/]?$//g;
+ return $folderPath;
+}
+
+sub CreateServerFolder
+{
+ local($folderPath) = @_;
+
+ $sParent = &GetParentFolder($folderPath);
+ # Check if the parent exists, or create it.
+ if(!(-e $sParent)) {
+ $sErrorMsg = &CreateServerFolder($sParent);
+ if($sErrorMsg == 1) {
+ return(1);
+ }
+ }
+ if(!(-e $folderPath)) {
+ if (defined $CHMOD_ON_FOLDER_CREATE && !$CHMOD_ON_FOLDER_CREATE) {
+ mkdir("$folderPath");
+ }
+ else {
+ umask(000);
+ if (defined $CHMOD_ON_FOLDER_CREATE) {
+ mkdir("$folderPath",$CHMOD_ON_FOLDER_CREATE);
+ }
+ else {
+ mkdir("$folderPath",0777);
+ }
+ }
+
+ return(0);
+ } else {
+ return(1);
+ }
+}
+
+sub GetRootPath
+{
+#use Cwd;
+
+# my $dir = getcwd;
+# print $dir;
+# $dir =~ s/$ENV{'DOCUMENT_ROOT'}//g;
+# print $dir;
+# return($dir);
+
+# $wk = $0;
+# $wk =~ s/\/connector\.cgi//g;
+# if($wk) {
+# $current_dir = $wk;
+# } else {
+# $current_dir = `pwd`;
+# }
+# return($current_dir);
+use Cwd;
+
+ if($ENV{'DOCUMENT_ROOT'}) {
+ $dir = $ENV{'DOCUMENT_ROOT'};
+ } else {
+ my $dir = getcwd;
+ $workdir =~ s/\/connector\.cgi//g;
+ $dir =~ s/$workdir//g;
+ }
+ return($dir);
+
+
+
+}
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload.cgi b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload.cgi
new file mode 100644
index 0000000..38c5ea5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload.cgi
@@ -0,0 +1,87 @@
+#!/usr/bin/env perl
+
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+##
+# ATTENTION: To enable this connector, look for the "SECURITY" comment in config.pl.
+##
+
+## START: Hack for Windows (Not important to understand the editor code... Perl specific).
+if(Windows_check()) {
+ chdir(GetScriptPath($0));
+}
+
+sub Windows_check
+{
+ # IIS,PWS(NT/95)
+ $www_server_os = $^O;
+ # Win98 & NT(SP4)
+ if($www_server_os eq "") { $www_server_os= $ENV{'OS'}; }
+ # AnHTTPd/Omni/IIS
+ if($ENV{'SERVER_SOFTWARE'} =~ /AnWeb|Omni|IIS\//i) { $www_server_os= 'win'; }
+ # Win Apache
+ if($ENV{'WINDIR'} ne "") { $www_server_os= 'win'; }
+ if($www_server_os=~ /win/i) { return(1); }
+ return(0);
+}
+
+sub GetScriptPath {
+ local($path) = @_;
+ if($path =~ /[\:\/\\]/) { $path =~ s/(.*?)[\/\\][^\/\\]+$/$1/; } else { $path = '.'; }
+ $path;
+}
+## END: Hack for IIS
+
+require 'util.pl';
+require 'io.pl';
+require 'basexml.pl';
+require 'commands.pl';
+require 'upload_fck.pl';
+require 'config.pl';
+
+&read_input();
+&DoResponse();
+
+sub DoResponse
+{
+ # Get the main request information.
+ $sCommand = 'FileUpload';
+ $sResourceType = &specialchar_cnv($FORM{'Type'});
+ $sCurrentFolder = "/";
+
+ if ($sResourceType eq '') {
+ $sResourceType = 'File' ;
+ }
+
+ if ( !($sResourceType =~ /^(File|Image|Flash|Media)$/) ) {
+ SendError( 1, "Invalid type specified" ) ;
+ }
+
+ # File Upload doesn't have to Return XML, so it must be intercepted before anything.
+ if($sCommand eq 'FileUpload') {
+ FileUpload($sResourceType,$sCurrentFolder);
+ return ;
+ }
+
+}
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload_fck.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload_fck.pl
new file mode 100644
index 0000000..debdad6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/upload_fck.pl
@@ -0,0 +1,686 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+# image data save dir
+$img_dir = './temp/';
+
+
+# File size max(unit KB)
+$MAX_CONTENT_SIZE = 30000;
+
+# After file is uploaded, sometimes it is required to change its permissions
+# so that it was possible to access it at the later time.
+# If possible, it is recommended to set more restrictive permissions, like 0755.
+# Set to 0 to disable this feature.
+$CHMOD_ON_UPLOAD = 0777;
+
+# See comments above.
+# Used when creating folders that does not exist.
+$CHMOD_ON_FOLDER_CREATE = 0755;
+
+# Filelock (1=use,0=not use)
+$PM{'flock'} = '1';
+
+
+# upload Content-Type list
+my %UPLOAD_CONTENT_TYPE_LIST = (
+ 'image/(x-)?png' => 'png', # PNG image
+ 'image/p?jpe?g' => 'jpg', # JPEG image
+ 'image/gif' => 'gif', # GIF image
+ 'image/x-xbitmap' => 'xbm', # XBM image
+
+ 'image/(x-(MS-)?)?bmp' => 'bmp', # Windows BMP image
+ 'image/pict' => 'pict', # Macintosh PICT image
+ 'image/tiff' => 'tif', # TIFF image
+ 'application/pdf' => 'pdf', # PDF image
+ 'application/x-shockwave-flash' => 'swf', # Shockwave Flash
+
+ 'video/(x-)?msvideo' => 'avi', # Microsoft Video
+ 'video/quicktime' => 'mov', # QuickTime Video
+ 'video/mpeg' => 'mpeg', # MPEG Video
+ 'video/x-mpeg2' => 'mpv2', # MPEG2 Video
+
+ 'audio/(x-)?midi?' => 'mid', # MIDI Audio
+ 'audio/(x-)?wav' => 'wav', # WAV Audio
+ 'audio/basic' => 'au', # ULAW Audio
+ 'audio/mpeg' => 'mpga', # MPEG Audio
+
+ 'application/(x-)?zip(-compressed)?' => 'zip', # ZIP Compress
+
+ 'text/html' => 'html', # HTML
+ 'text/plain' => 'txt', # TEXT
+ '(?:application|text)/(?:rtf|richtext)' => 'rtf', # RichText
+
+ 'application/msword' => 'doc', # Microsoft Word
+ 'application/vnd.ms-excel' => 'xls', # Microsoft Excel
+
+ ''
+);
+
+# Upload is permitted.
+# A regular expression is possible.
+my %UPLOAD_EXT_LIST = (
+ 'png' => 'PNG image',
+ 'p?jpe?g|jpe|jfif|pjp' => 'JPEG image',
+ 'gif' => 'GIF image',
+ 'xbm' => 'XBM image',
+
+ 'bmp|dib|rle' => 'Windows BMP image',
+ 'pi?ct' => 'Macintosh PICT image',
+ 'tiff?' => 'TIFF image',
+ 'pdf' => 'PDF image',
+ 'swf' => 'Shockwave Flash',
+
+ 'avi' => 'Microsoft Video',
+ 'moo?v|qt' => 'QuickTime Video',
+ 'm(p(e?gv?|e|v)|1v)' => 'MPEG Video',
+ 'mp(v2|2v)' => 'MPEG2 Video',
+
+ 'midi?|kar|smf|rmi|mff' => 'MIDI Audio',
+ 'wav' => 'WAVE Audio',
+ 'au|snd' => 'ULAW Audio',
+ 'mp(e?ga|2|a|3)|abs' => 'MPEG Audio',
+
+ 'zip' => 'ZIP Compress',
+ 'lzh' => 'LZH Compress',
+ 'cab' => 'CAB Compress',
+
+ 'd?html?' => 'HTML',
+ 'rtf|rtx' => 'RichText',
+ 'txt|text' => 'Text',
+
+ ''
+);
+
+
+# sjis or euc
+my $CHARCODE = 'sjis';
+
+$TRANS_2BYTE_CODE = 0;
+
+##############################################################################
+# Summary
+#
+# Form Read input
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub read_input
+{
+eval("use File::Copy;");
+eval("use File::Path;");
+
+ my ($FORM) = @_;
+
+ if (defined $CHMOD_ON_FOLDER_CREATE && !$CHMOD_ON_FOLDER_CREATE) {
+ mkdir("$img_dir");
+ }
+ else {
+ umask(000);
+ if (defined $CHMOD_ON_FOLDER_CREATE) {
+ mkdir("$img_dir",$CHMOD_ON_FOLDER_CREATE);
+ }
+ else {
+ mkdir("$img_dir",0777);
+ }
+ }
+
+ undef $img_data_exists;
+ undef @NEWFNAMES;
+ undef @NEWFNAME_DATA;
+
+ if($ENV{'CONTENT_LENGTH'} > 10000000 || $ENV{'CONTENT_LENGTH'} > $MAX_CONTENT_SIZE * 1024) {
+ &upload_error(
+ 'Size Error',
+ sprintf(
+ "Transmitting size is too large.MAX <strong>%d KB</strong> Now Size <strong>%d KB</strong>(<strong>%d bytes</strong> Over)",
+ $MAX_CONTENT_SIZE,
+ int($ENV{'CONTENT_LENGTH'} / 1024),
+ $ENV{'CONTENT_LENGTH'} - $MAX_CONTENT_SIZE * 1024
+ )
+ );
+ }
+
+ my $Buffer;
+ if($ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
+ # METHOD POST only
+ return unless($ENV{'CONTENT_LENGTH'});
+
+ binmode(STDIN);
+ # STDIN A pause character is detected.'(MacIE3.0 boundary of $ENV{'CONTENT_TYPE'} cannot be trusted.)
+ my $Boundary = <STDIN>;
+ $Boundary =~ s/\x0D\x0A//;
+ $Boundary = quotemeta($Boundary);
+ while(<STDIN>) {
+ if(/^\s*Content-Disposition:/i) {
+ my($name,$ContentType,$FileName);
+ # form data get
+ if(/\bname="([^"]+)"/i || /\bname=([^\s:;]+)/i) {
+ $name = $1;
+ $name =~ tr/+/ /;
+ $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ &Encode(\$name);
+ }
+ if(/\bfilename="([^"]*)"/i || /\bfilename=([^\s:;]*)/i) {
+ $FileName = $1 || 'unknown';
+ }
+ # head read
+ while(<STDIN>) {
+ last if(! /\w/);
+ if(/^\s*Content-Type:\s*"([^"]+)"/i || /^\s*Content-Type:\s*([^\s:;]+)/i) {
+ $ContentType = $1;
+ }
+ }
+ # body read
+ $value = "";
+ while(<STDIN>) {
+ last if(/^$Boundary/o);
+ $value .= $_;
+ };
+ $lastline = $_;
+ $value =~s /\x0D\x0A$//;
+ if($value ne '') {
+ if($FileName || $ContentType) {
+ $img_data_exists = 1;
+ (
+ $FileName, #
+ $Ext, #
+ $Length, #
+ $ImageWidth, #
+ $ImageHeight, #
+ $ContentName #
+ ) = &CheckContentType(\$value,$FileName,$ContentType);
+
+ $FORM{$name} = $FileName;
+ $new_fname = $FileName;
+ push(@NEWFNAME_DATA,"$FileName\t$Ext\t$Length\t$ImageWidth\t$ImageHeight\t$ContentName");
+
+ # Multi-upload correspondence
+ push(@NEWFNAMES,$new_fname);
+ open(OUT,">$img_dir/$new_fname");
+ binmode(OUT);
+ eval "flock(OUT,2);" if($PM{'flock'} == 1);
+ print OUT $value;
+ eval "flock(OUT,8);" if($PM{'flock'} == 1);
+ close(OUT);
+
+ } elsif($name) {
+ $value =~ tr/+/ /;
+ $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ &Encode(\$value,'trans');
+ $FORM{$name} .= "\0" if(defined($FORM{$name}));
+ $FORM{$name} .= $value;
+ }
+ }
+ };
+ last if($lastline =~ /^$Boundary\-\-/o);
+ }
+ } elsif($ENV{'CONTENT_LENGTH'}) {
+ read(STDIN,$Buffer,$ENV{'CONTENT_LENGTH'});
+ }
+ foreach(split(/&/,$Buffer),split(/&/,$ENV{'QUERY_STRING'})) {
+ my($name, $value) = split(/=/);
+ $name =~ tr/+/ /;
+ $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+ $value =~ tr/+/ /;
+ $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+
+ &Encode(\$name);
+ &Encode(\$value,'trans');
+ $FORM{$name} .= "\0" if(defined($FORM{$name}));
+ $FORM{$name} .= $value;
+
+ }
+
+}
+
+##############################################################################
+# Summary
+#
+# CheckContentType
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub CheckContentType
+{
+
+ my($DATA,$FileName,$ContentType) = @_;
+ my($Ext,$ImageWidth,$ImageHeight,$ContentName,$Infomation);
+ my $DataLength = length($$DATA);
+
+ # An unknown file type
+
+ $_ = $ContentType;
+ my $UnknownType = (
+ !$_
+ || /^application\/(x-)?macbinary$/i
+ || /^application\/applefile$/i
+ || /^application\/octet-stream$/i
+ || /^text\/plane$/i
+ || /^x-unknown-content-type/i
+ );
+
+ # MacBinary(Mac Unnecessary data are deleted.)
+ if($UnknownType || $ENV{'HTTP_USER_AGENT'} =~ /Macintosh|Mac_/) {
+ if($DataLength > 128 && !unpack("C",substr($$DATA,0,1)) && !unpack("C",substr($$DATA,74,1)) && !unpack("C",substr($$DATA,82,1)) ) {
+ my $MacBinary_ForkLength = unpack("N", substr($$DATA, 83, 4)); # ForkLength Get
+ my $MacBinary_FileName = quotemeta(substr($$DATA, 2, unpack("C",substr($$DATA, 1, 1))));
+ if($MacBinary_FileName && $MacBinary_ForkLength && $DataLength >= $MacBinary_ForkLength + 128
+ && ($FileName =~ /$MacBinary_FileName/i || substr($$DATA,102,4) eq 'mBIN')) { # DATA TOP 128byte MacBinary!!
+ $$DATA = substr($$DATA,128,$MacBinary_ForkLength);
+ my $ResourceLength = $DataLength - $MacBinary_ForkLength - 128;
+ $DataLength = $MacBinary_ForkLength;
+ }
+ }
+ }
+
+ # A file name is changed into EUC.
+# &jcode::convert(\$FileName,'euc',$FormCodeDefault);
+# &jcode::h2z_euc(\$FileName);
+ $FileName =~ s/^.*\\//; # Windows, Mac
+ $FileName =~ s/^.*\///; # UNIX
+ $FileName =~ s/&/&amp;/g;
+ $FileName =~ s/"/&quot;/g;
+ $FileName =~ s/</&lt;/g;
+ $FileName =~ s/>/&gt;/g;
+#
+# if($CHARCODE ne 'euc') {
+# &jcode::convert(\$FileName,$CHARCODE,'euc');
+# }
+
+ # An extension is extracted and it changes into a small letter.
+ my $FileExt;
+ if($FileName =~ /\.(\w+)$/) {
+ $FileExt = $1;
+ $FileExt =~ tr/A-Z/a-z/;
+ }
+
+ # Executable file detection (ban on upload)
+ if($$DATA =~ /^MZ/) {
+ $Ext = 'exe';
+ }
+ # text
+ if(!$Ext && ($UnknownType || $ContentType =~ /^text\//i || $ContentType =~ /^application\/(?:rtf|richtext)$/i || $ContentType =~ /^image\/x-xbitmap$/i)
+ && ! $$DATA =~ /[\000-\006\177\377]/) {
+# $$DATA =~ s/\x0D\x0A/\n/g;
+# $$DATA =~ tr/\x0D\x0A/\n\n/;
+#
+# if(
+# $$DATA =~ /<\s*SCRIPT(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bONLOAD\s*=(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bONCLICK\s*=(?:.|\n)*?>/i
+# ) {
+# $Infomation = '(JavaScript contains)';
+# }
+# if($$DATA =~ /<\s*TABLE(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*BLINK(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*MARQUEE(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*OBJECT(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*EMBED(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*FRAME(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*APPLET(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*FORM(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bSRC\s*=(?:.|\n)*?>/i
+# || $$DATA =~ /<\s*(?:.|\n)*?\bDYNSRC\s*=(?:.|\n)*?>/i
+# ) {
+# $Infomation = '(the HTML tag which is not safe is included)';
+# }
+
+ if($FileExt =~ /^txt$/i || $FileExt =~ /^cgi$/i || $FileExt =~ /^pl$/i) { # Text File
+ $Ext = 'txt';
+ } elsif($ContentType =~ /^text\/html$/i || $FileExt =~ /html?/i || $$DATA =~ /<\s*HTML(?:.|\n)*?>/i) { # HTML File
+ $Ext = 'html';
+ } elsif($ContentType =~ /^image\/x-xbitmap$/i || $FileExt =~ /^xbm$/i) { # XBM(x-BitMap) Image
+ my $XbmName = $1;
+ my ($XbmWidth, $XbmHeight);
+ if($$DATA =~ /\#define\s*$XbmName\_width\s*(\d+)/i) {
+ $XbmWidth = $1;
+ }
+ if($$DATA =~ /\#define\s*$XbmName\_height\s*(\d+)/i) {
+ $XbmHeight = $1;
+ }
+ if($XbmWidth && $XbmHeight) {
+ $Ext = 'xbm';
+ $ImageWidth = $XbmWidth;
+ $ImageHeight = $XbmHeight;
+ }
+ } else { #
+ $Ext = 'txt';
+ }
+ }
+
+ # image
+ if(!$Ext && ($UnknownType || $ContentType =~ /^image\//i)) {
+ # PNG
+ if($$DATA =~ /^\x89PNG\x0D\x0A\x1A\x0A/) {
+ if(substr($$DATA, 12, 4) eq 'IHDR') {
+ $Ext = 'png';
+ ($ImageWidth, $ImageHeight) = unpack("N2", substr($$DATA, 16, 8));
+ }
+ } elsif($$DATA =~ /^GIF8(?:9|7)a/) { # GIF89a(modified), GIF89a, GIF87a
+ $Ext = 'gif';
+ ($ImageWidth, $ImageHeight) = unpack("v2", substr($$DATA, 6, 4));
+ } elsif($$DATA =~ /^II\x2a\x00\x08\x00\x00\x00/ || $$DATA =~ /^MM\x00\x2a\x00\x00\x00\x08/) { # TIFF
+ $Ext = 'tif';
+ } elsif($$DATA =~ /^BM/) { # BMP
+ $Ext = 'bmp';
+ } elsif($$DATA =~ /^\xFF\xD8\xFF/ || $$DATA =~ /JFIF/) { # JPEG
+ my $HeaderPoint = index($$DATA, "\xFF\xD8\xFF", 0);
+ my $Point = $HeaderPoint + 2;
+ while($Point < $DataLength) {
+ my($Maker, $MakerType, $MakerLength) = unpack("C2n",substr($$DATA,$Point,4));
+ if($Maker != 0xFF || $MakerType == 0xd9 || $MakerType == 0xda) {
+ last;
+ } elsif($MakerType >= 0xC0 && $MakerType <= 0xC3) {
+ $Ext = 'jpg';
+ ($ImageHeight, $ImageWidth) = unpack("n2", substr($$DATA, $Point + 5, 4));
+ if($HeaderPoint > 0) {
+ $$DATA = substr($$DATA, $HeaderPoint);
+ $DataLength = length($$DATA);
+ }
+ last;
+ } else {
+ $Point += $MakerLength + 2;
+ }
+ }
+ }
+ }
+
+ # audio
+ if(!$Ext && ($UnknownType || $ContentType =~ /^audio\//i)) {
+ # MIDI Audio
+ if($$DATA =~ /^MThd/) {
+ $Ext = 'mid';
+ } elsif($$DATA =~ /^\x2esnd/) { # ULAW Audio
+ $Ext = 'au';
+ } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {
+ my $HeaderPoint = index($$DATA, "RIFF", 0);
+ $_ = substr($$DATA, $HeaderPoint + 8, 8);
+ if(/^WAVEfmt $/) {
+ # WAVE
+ if(unpack("V",substr($$DATA, $HeaderPoint + 16, 4)) == 16) {
+ $Ext = 'wav';
+ } else { # RIFF WAVE MP3
+ $Ext = 'mp3';
+ }
+ } elsif(/^RMIDdata$/) { # RIFF MIDI
+ $Ext = 'rmi';
+ } elsif(/^RMP3data$/) { # RIFF MP3
+ $Ext = 'rmp';
+ }
+ if($ContentType =~ /^audio\//i) {
+ $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';
+ }
+ }
+ }
+
+ # a binary file
+ unless ($Ext) {
+ # PDF image
+ if($$DATA =~ /^\%PDF/) {
+ # Picture size is not measured.
+ $Ext = 'pdf';
+ } elsif($$DATA =~ /^FWS/) { # Shockwave Flash
+ $Ext = 'swf';
+ } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {
+ my $HeaderPoint = index($$DATA, "RIFF", 0);
+ $_ = substr($$DATA,$HeaderPoint + 8, 8);
+ # AVI
+ if(/^AVI LIST$/) {
+ $Ext = 'avi';
+ }
+ if($ContentType =~ /^video\//i) {
+ $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';
+ }
+ } elsif($$DATA =~ /^PK/) { # ZIP Compress File
+ $Ext = 'zip';
+ } elsif($$DATA =~ /^MSCF/) { # CAB Compress File
+ $Ext = 'cab';
+ } elsif($$DATA =~ /^Rar\!/) { # RAR Compress File
+ $Ext = 'rar';
+ } elsif(substr($$DATA, 2, 5) =~ /^\-lh(\d+|d)\-$/) { # LHA Compress File
+ $Infomation .= "(lh$1)";
+ $Ext = 'lzh';
+ } elsif(substr($$DATA, 325, 25) eq "Apple Video Media Handler" || substr($$DATA, 325, 30) eq "Apple \x83\x72\x83\x66\x83\x49\x81\x45\x83\x81\x83\x66\x83\x42\x83\x41\x83\x6E\x83\x93\x83\x68\x83\x89") {
+ # QuickTime
+ $Ext = 'mov';
+ }
+ }
+
+ # Header analysis failure
+ unless ($Ext) {
+ # It will be followed if it applies for the MIME type from the browser.
+ foreach (keys %UPLOAD_CONTENT_TYPE_LIST) {
+ next unless ($_);
+ if($ContentType =~ /^$_$/i) {
+ $Ext = $UPLOAD_CONTENT_TYPE_LIST{$_};
+ $ContentName = &CheckContentExt($Ext);
+ if(
+ grep {$_ eq $Ext;} (
+ 'png',
+ 'gif',
+ 'jpg',
+ 'xbm',
+ 'tif',
+ 'bmp',
+ 'pdf',
+ 'swf',
+ 'mov',
+ 'zip',
+ 'cab',
+ 'lzh',
+ 'rar',
+ 'mid',
+ 'rmi',
+ 'au',
+ 'wav',
+ 'avi',
+ 'exe'
+ )
+ ) {
+ $Infomation .= ' / Header analysis failure';
+ }
+ if($Ext ne $FileExt && &CheckContentExt($FileExt) eq $ContentName) {
+ $Ext = $FileExt;
+ }
+ last;
+ }
+ }
+ # a MIME type is unknown--It judges from an extension.
+ unless ($Ext) {
+ $ContentName = &CheckContentExt($FileExt);
+ if($ContentName) {
+ $Ext = $FileExt;
+ $Infomation .= ' / MIME type is unknown('. $ContentType. ')';
+ last;
+ }
+ }
+ }
+
+# $ContentName = &CheckContentExt($Ext) unless($ContentName);
+# if($Ext && $ContentName) {
+# $ContentName .= $Infomation;
+# } else {
+# &upload_error(
+# 'Extension Error',
+# "$FileName A not corresponding extension ($Ext)<BR>The extension which can be responded ". join(',', sort values(%UPLOAD_EXT_LIST))
+# );
+# }
+
+# # SSI Tag Deletion
+# if($Ext =~ /.?html?/ && $$DATA =~ /<\!/) {
+# foreach (
+# 'config',
+# 'echo',
+# 'exec',
+# 'flastmod',
+# 'fsize',
+# 'include'
+# ) {
+# $$DATA =~ s/\#\s*$_/\&\#35\;$_/ig
+# }
+# }
+
+ return (
+ $FileName,
+ $Ext,
+ int($DataLength / 1024 + 1),
+ $ImageWidth,
+ $ImageHeight,
+ $ContentName
+ );
+}
+
+##############################################################################
+# Summary
+#
+# Extension discernment
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub CheckContentExt
+{
+
+ my($Ext) = @_;
+ my $ContentName;
+ foreach (keys %UPLOAD_EXT_LIST) {
+ next unless ($_);
+ if($_ && $Ext =~ /^$_$/) {
+ $ContentName = $UPLOAD_EXT_LIST{$_};
+ last;
+ }
+ }
+ return $ContentName;
+
+}
+
+##############################################################################
+# Summary
+#
+# Form decode
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+sub Encode
+{
+
+ my($value,$Trans) = @_;
+
+# my $FormCode = &jcode::getcode($value) || $FormCodeDefault;
+# $FormCodeDefault ||= $FormCode;
+#
+# if($Trans && $TRANS_2BYTE_CODE) {
+# if($FormCode ne 'euc') {
+# &jcode::convert($value, 'euc', $FormCode);
+# }
+# &jcode::tr(
+# $value,
+# "\xA3\xB0-\xA3\xB9\xA3\xC1-\xA3\xDA\xA3\xE1-\xA3\xFA",
+# '0-9A-Za-z'
+# );
+# if($CHARCODE ne 'euc') {
+# &jcode::convert($value,$CHARCODE,'euc');
+# }
+# } else {
+# if($CHARCODE ne $FormCode) {
+# &jcode::convert($value,$CHARCODE,$FormCode);
+# }
+# }
+# if($CHARCODE eq 'euc') {
+# &jcode::h2z_euc($value);
+# } elsif($CHARCODE eq 'sjis') {
+# &jcode::h2z_sjis($value);
+# }
+
+}
+
+##############################################################################
+# Summary
+#
+# Error Msg
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub upload_error
+{
+
+ local($error_message) = $_[0];
+ local($error_message2) = $_[1];
+
+ print "Content-type: text/html\n\n";
+ print<<EOF;
+<HTML>
+<HEAD>
+<TITLE>Error Message</TITLE></HEAD>
+<BODY>
+<table border="1" cellspacing="10" cellpadding="10">
+ <TR bgcolor="#0000B0">
+ <TD bgcolor="#0000B0" NOWRAP><font size="-1" color="white"><B>Error Message</B></font></TD>
+ </TR>
+</table>
+<UL>
+<H4> $error_message </H4>
+$error_message2 <BR>
+</UL>
+</BODY>
+</HTML>
+EOF
+ &rm_tmp_uploaded_files; # Image Temporary deletion
+ exit;
+}
+
+##############################################################################
+# Summary
+#
+# Image Temporary deletion
+#
+# Parameters
+# Returns
+# Memo
+##############################################################################
+
+sub rm_tmp_uploaded_files
+{
+ if($img_data_exists == 1){
+ sleep 1;
+ foreach $fname_list(@NEWFNAMES) {
+ if(-e "$img_dir/$fname_list") {
+ unlink("$img_dir/$fname_list");
+ }
+ }
+ }
+
+}
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/util.pl b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/util.pl
new file mode 100644
index 0000000..c245a04
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/perl/util.pl
@@ -0,0 +1,66 @@
+#####
+# FCKeditor - The text editor for Internet - http://www.fckeditor.net
+# Copyright (C) 2003-2010 Frederico Caldeira Knabben
+#
+# == BEGIN LICENSE ==
+#
+# Licensed under the terms of any of the following licenses at your
+# choice:
+#
+# - GNU General Public License Version 2 or later (the "GPL")
+# http://www.gnu.org/licenses/gpl.html
+#
+# - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+# http://www.gnu.org/licenses/lgpl.html
+#
+# - Mozilla Public License Version 1.1 or later (the "MPL")
+# http://www.mozilla.org/MPL/MPL-1.1.html
+#
+# == END LICENSE ==
+#
+# This is the File Manager Connector for Perl.
+#####
+
+sub RemoveFromStart
+{
+ local($sourceString, $charToRemove) = @_;
+ $sPattern = '^' . $charToRemove . '+' ;
+ $sourceString =~ s/^$charToRemove+//g;
+ return $sourceString;
+}
+
+sub RemoveFromEnd
+{
+ local($sourceString, $charToRemove) = @_;
+ $sPattern = $charToRemove . '+$' ;
+ $sourceString =~ s/$charToRemove+$//g;
+ return $sourceString;
+}
+
+sub ConvertToXmlAttribute
+{
+ local($value) = @_;
+ return(&specialchar_cnv($value));
+}
+
+sub specialchar_cnv
+{
+ local($ch) = @_;
+
+ $ch =~ s/&/&amp;/g; # &
+ $ch =~ s/\"/&quot;/g; #"
+ $ch =~ s/\'/&#39;/g; # '
+ $ch =~ s/</&lt;/g; # <
+ $ch =~ s/>/&gt;/g; # >
+ return($ch);
+}
+
+sub JS_cnv
+{
+ local($ch) = @_;
+
+ $ch =~ s/\"/\\\"/g; #"
+ return($ch);
+}
+
+1;
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/test.html b/httemplate/elements/fckeditor/editor/filemanager/connectors/test.html
new file mode 100644
index 0000000..a394750
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/test.html
@@ -0,0 +1,210 @@
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Test page for the File Browser connectors.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>FCKeditor - Connectors Tests</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+// Automatically detect the correct document.domain (#1919).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.opener.document.domain ;
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+function BuildBaseUrl( command )
+{
+ var sUrl =
+ document.getElementById('cmbConnector').value +
+ '?Command=' + command +
+ '&Type=' + document.getElementById('cmbType').value +
+ '&CurrentFolder=' + encodeURIComponent(document.getElementById('txtFolder').value) ;
+
+ return sUrl ;
+}
+
+function SetFrameUrl( url )
+{
+ document.getElementById('eRunningFrame').src = url ;
+
+ document.getElementById('eUrl').innerHTML = url ;
+}
+
+function GetFolders()
+{
+ SetFrameUrl( BuildBaseUrl( 'GetFolders' ) ) ;
+ return false ;
+}
+
+function GetFoldersAndFiles()
+{
+ SetFrameUrl( BuildBaseUrl( 'GetFoldersAndFiles' ) ) ;
+ return false ;
+}
+
+function CreateFolder()
+{
+ var sFolder = prompt( 'Type the folder name:', 'Test Folder' ) ;
+
+ if ( ! sFolder )
+ return false ;
+
+ var sUrl = BuildBaseUrl( 'CreateFolder' ) ;
+ sUrl += '&NewFolderName=' + encodeURIComponent( sFolder ) ;
+
+ SetFrameUrl( sUrl ) ;
+ return false ;
+}
+
+function OnUploadCompleted( errorNumber, fileName )
+{
+ switch ( errorNumber )
+ {
+ case 0 :
+ alert( 'File uploaded with no errors' ) ;
+ break ;
+ case 201 :
+ GetFoldersAndFiles() ;
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file' ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ break ;
+ }
+}
+
+this.frames.frmUpload = this ;
+
+function SetAction()
+{
+ var sUrl = BuildBaseUrl( 'FileUpload' ) ;
+ document.getElementById('eUrl').innerHTML = sUrl ;
+ document.getElementById('frmUpload').action = sUrl ;
+}
+
+ </script>
+</head>
+<body>
+ <table height="100%" cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td>
+ Connector:<br />
+ <select id="cmbConnector" name="cmbConnector">
+ <option value="asp/connector.asp" selected="selected">ASP</option>
+ <option value="aspx/connector.aspx">ASP.Net</option>
+ <option value="cfm/connector.cfm">ColdFusion</option>
+ <option value="lasso/connector.lasso">Lasso</option>
+ <option value="perl/connector.cgi">Perl</option>
+ <option value="php/connector.php">PHP</option>
+ <option value="py/connector.py">Python</option>
+ </select>
+ </td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td>
+ Current Folder<br />
+ <input id="txtFolder" type="text" value="/" name="txtFolder" /></td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td>
+ Resource Type<br />
+ <select id="cmbType" name="cmbType">
+ <option value="File" selected="selected">File</option>
+ <option value="Image">Image</option>
+ <option value="Flash">Flash</option>
+ <option value="Media">Media</option>
+ <option value="Invalid">Invalid Type (for testing)</option>
+ </select>
+ </td>
+ </tr>
+ </table>
+ <br />
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td valign="top">
+ <a href="#" onclick="GetFolders();">Get Folders</a></td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td valign="top">
+ <a href="#" onclick="GetFoldersAndFiles();">Get Folders and Files</a></td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td valign="top">
+ <a href="#" onclick="CreateFolder();">Create Folder</a></td>
+ <td>
+ &nbsp;&nbsp;&nbsp;</td>
+ <td valign="top">
+ <form id="frmUpload" action="" target="eRunningFrame" method="post" enctype="multipart/form-data">
+ File Upload<br />
+ <input id="txtFileUpload" type="file" name="NewFile" />
+ <input type="submit" value="Upload" onclick="SetAction();" />
+ </form>
+ </td>
+ </tr>
+ </table>
+ <br />
+ URL: <span id="eUrl"></span>
+ </td>
+ </tr>
+ <tr>
+ <td height="100%" valign="top">
+ <iframe id="eRunningFrame" src="javascript:void(0)" name="eRunningFrame" width="100%"
+ height="100%"></iframe>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/connectors/uploadtest.html b/httemplate/elements/fckeditor/editor/filemanager/connectors/uploadtest.html
new file mode 100644
index 0000000..9d7b97b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/connectors/uploadtest.html
@@ -0,0 +1,192 @@
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Test page for the "File Uploaders".
+-->
+<html>
+ <head>
+ <title>FCKeditor - Uploaders Tests</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+// Automatically detect the correct document.domain (#1919).
+(function()
+{
+ var d = document.domain ;
+
+ while ( true )
+ {
+ // Test if we can access a parent property.
+ try
+ {
+ var test = window.opener.document.domain ;
+ break ;
+ }
+ catch( e ) {}
+
+ // Remove a domain part: www.mytest.example.com => mytest.example.com => example.com ...
+ d = d.replace( /.*?(?:\.|$)/, '' ) ;
+
+ if ( d.length == 0 )
+ break ; // It was not able to detect the domain.
+
+ try
+ {
+ document.domain = d ;
+ }
+ catch (e)
+ {
+ break ;
+ }
+ }
+})() ;
+
+function SendFile()
+{
+ var sUploaderUrl = cmbUploaderUrl.value ;
+
+ if ( sUploaderUrl.length == 0 )
+ sUploaderUrl = txtCustomUrl.value ;
+
+ if ( sUploaderUrl.length == 0 )
+ {
+ alert( 'Please provide your custom URL or select a default one' ) ;
+ return ;
+ }
+
+ eURL.innerHTML = sUploaderUrl ;
+ txtUrl.value = '' ;
+
+ var date = new Date()
+
+ frmUpload.action = sUploaderUrl + '?time=' + date.getTime();
+ if (document.getElementById('cmbType').value) {
+ frmUpload.action = frmUpload.action + '&Type='+document.getElementById('cmbType').value;
+ }
+ if (document.getElementById('CurrentFolder').value) {
+ frmUpload.action = frmUpload.action + '&CurrentFolder='+document.getElementById('CurrentFolder').value;
+ }
+ frmUpload.submit() ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ switch ( errorNumber )
+ {
+ case 0 : // No errors
+ txtUrl.value = fileUrl ;
+ alert( 'File uploaded with no errors' ) ;
+ break ;
+ case 1 : // Custom error
+ alert( customMsg ) ;
+ break ;
+ case 10 : // Custom warning
+ txtUrl.value = fileUrl ;
+ alert( customMsg ) ;
+ break ;
+ case 201 :
+ txtUrl.value = fileUrl ;
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file' ) ;
+ break ;
+ case 203 :
+ alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ break ;
+ }
+}
+
+ </script>
+ </head>
+ <body>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0" height="100%">
+ <tr>
+ <td>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap>
+ Select the "File Uploader" to use: <br>
+ <select id="cmbUploaderUrl">
+ <option selected value="asp/upload.asp">ASP</option>
+ <option value="aspx/upload.aspx">ASP.Net</option>
+ <option value="cfm/upload.cfm">ColdFusion</option>
+ <option value="lasso/upload.lasso">Lasso</option>
+ <option value="perl/upload.cgi">Perl</option>
+ <option value="php/upload.php">PHP</option>
+ <option value="py/upload.py">Python</option>
+ <option value="">(Custom)</option>
+ </select>
+ </td>
+ <td>
+ Resource Type<br />
+ <select id="cmbType" name="cmbType">
+ <option value="">None</option>
+ <option value="File">File</option>
+ <option value="Image">Image</option>
+ <option value="Flash">Flash</option>
+ <option value="Media">Media</option>
+ <option value="Invalid">Invalid Type (for testing)</option>
+ </select>
+ </td>
+ <td>
+ Current Folder: <br>
+ <input type="text" name="CurrentFolder" id="CurrentFolder" value="/">
+ </td>
+ <td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td width="100%">
+ Custom Uploader URL:<BR>
+ <input id="txtCustomUrl" style="WIDTH: 100%; BACKGROUND-COLOR: #dcdcdc" disabled type="text">
+ </td>
+ </tr>
+ </table>
+ <br>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td noWrap>
+ <form id="frmUpload" target="UploadWindow" enctype="multipart/form-data" action="" method="post">
+ Upload a new file:<br>
+ <input type="file" name="NewFile"><br>
+
+ <input type="button" value="Send it to the Server" onclick="SendFile();">
+ </form>
+ </td>
+ <td style="WIDTH: 16px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td vAlign="top" width="100%">
+ Uploaded File URL:<br>
+ <INPUT id="txtUrl" style="WIDTH: 100%" readonly type="text">
+ </td>
+ </tr>
+ </table>
+ <br>
+ Post URL: <span id="eURL">&nbsp;</span>
+ </td>
+ </tr>
+ <tr>
+ <td height="100%">
+ <iframe name="UploadWindow" width="100%" height="100%" src="javascript:void(0)"></iframe>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/filemanager/upload/test.html b/httemplate/elements/fckeditor/editor/filemanager/upload/test.html
new file mode 100644
index 0000000..cf29e97
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/filemanager/upload/test.html
@@ -0,0 +1,133 @@
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Test page for the "File Uploaders".
+-->
+<html>
+ <head>
+ <title>FCKeditor - Uploaders Tests</title>
+ <script language="javascript">
+
+function SendFile()
+{
+ var sUploaderUrl = cmbUploaderUrl.value ;
+
+ if ( sUploaderUrl.length == 0 )
+ sUploaderUrl = txtCustomUrl.value ;
+
+ if ( sUploaderUrl.length == 0 )
+ {
+ alert( 'Please provide your custom URL or select a default one' ) ;
+ return ;
+ }
+
+ eURL.innerHTML = sUploaderUrl ;
+ txtUrl.value = '' ;
+
+ frmUpload.action = sUploaderUrl ;
+ frmUpload.submit() ;
+}
+
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
+{
+ switch ( errorNumber )
+ {
+ case 0 : // No errors
+ txtUrl.value = fileUrl ;
+ alert( 'File uploaded with no errors' ) ;
+ break ;
+ case 1 : // Custom error
+ alert( customMsg ) ;
+ break ;
+ case 10 : // Custom warning
+ txtUrl.value = fileUrl ;
+ alert( customMsg ) ;
+ break ;
+ case 201 :
+ txtUrl.value = fileUrl ;
+ alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
+ break ;
+ case 202 :
+ alert( 'Invalid file' ) ;
+ break ;
+ case 203 :
+ alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
+ break ;
+ default :
+ alert( 'Error on file upload. Error number: ' + errorNumber ) ;
+ break ;
+ }
+}
+
+ </script>
+ </head>
+ <body>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0" height="100%">
+ <tr>
+ <td>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td nowrap>
+ Select the "File Uploader" to use:<br>
+ <select id="cmbUploaderUrl">
+ <option selected value="asp/upload.asp">ASP</option>
+ <option value="aspx/upload.aspx">ASP.Net</option>
+ <option value="cfm/upload.cfm">ColdFusion</option>
+ <option value="lasso/upload.lasso">Lasso</option>
+ <option value="php/upload.php">PHP</option>
+ <option value="">(Custom)</option>
+ </select>
+ </td>
+ <td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td width="100%">
+ Custom Uploader URL:<BR>
+ <input id="txtCustomUrl" style="WIDTH: 100%; BACKGROUND-COLOR: #dcdcdc" disabled type="text">
+ </td>
+ </tr>
+ </table>
+ <br>
+ <table cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td noWrap>
+ <form id="frmUpload" target="UploadWindow" enctype="multipart/form-data" action="" method="post">
+ Upload a new file:<br>
+ <input type="file" name="NewFile"><br>
+ <input type="button" value="Send it to the Server" onclick="SendFile();">
+ </form>
+ </td>
+ <td style="WIDTH: 16px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td vAlign="top" width="100%">
+ Uploaded File URL:<br>
+ <INPUT id="txtUrl" style="WIDTH: 100%" readonly type="text">
+ </td>
+ </tr>
+ </table>
+ <br>
+ Post URL: <span id="eURL">&nbsp;</span>
+ </td>
+ </tr>
+ <tr>
+ <td height="100%">
+ <iframe name="UploadWindow" width="100%" height="100%" src="javascript:void(0)"></iframe>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/images/anchor.gif b/httemplate/elements/fckeditor/editor/images/anchor.gif
new file mode 100644
index 0000000..5aa797b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/anchor.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/arrow_ltr.gif b/httemplate/elements/fckeditor/editor/images/arrow_ltr.gif
new file mode 100644
index 0000000..9c59bfe
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/arrow_ltr.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/arrow_rtl.gif b/httemplate/elements/fckeditor/editor/images/arrow_rtl.gif
new file mode 100644
index 0000000..22e8649
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/arrow_rtl.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif
new file mode 100644
index 0000000..a95e053
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif
new file mode 100644
index 0000000..c667c5d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif
new file mode 100644
index 0000000..938cce1
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif
new file mode 100644
index 0000000..f6489d7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif
new file mode 100644
index 0000000..aeb0539
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif
new file mode 100644
index 0000000..0758f42
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif
new file mode 100644
index 0000000..15518d7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif
new file mode 100644
index 0000000..c431946
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif
new file mode 100644
index 0000000..66d3656
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif
new file mode 100644
index 0000000..305714f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif
new file mode 100644
index 0000000..f840ea6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif
new file mode 100644
index 0000000..863be6e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif
new file mode 100644
index 0000000..aabc7fd
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif
new file mode 100644
index 0000000..33f297e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif
new file mode 100644
index 0000000..dfb78ef
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif
new file mode 100644
index 0000000..157df77
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif
new file mode 100644
index 0000000..26b5a55
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif
new file mode 100644
index 0000000..f53ee72
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif
new file mode 100644
index 0000000..7e8c746
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif
new file mode 100644
index 0000000..b87ec44
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif
new file mode 100644
index 0000000..c074122
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif b/httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif
new file mode 100644
index 0000000..eefe61d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/spacer.gif b/httemplate/elements/fckeditor/editor/images/spacer.gif
index 5bfd67a..5bfd67a 100644
--- a/rt/html/NoAuth/images/spacer.gif
+++ b/httemplate/elements/fckeditor/editor/images/spacer.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/js/fckadobeair.js b/httemplate/elements/fckeditor/editor/js/fckadobeair.js
new file mode 100644
index 0000000..aea8fdc
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/js/fckadobeair.js
@@ -0,0 +1,176 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Compatibility code for Adobe AIR.
+ */
+
+if ( FCKBrowserInfo.IsAIR )
+{
+ var FCKAdobeAIR = (function()
+ {
+ /*
+ * ### Private functions.
+ */
+
+ var getDocumentHead = function( doc )
+ {
+ var head ;
+ var heads = doc.getElementsByTagName( 'head' ) ;
+
+ if( heads && heads[0] )
+ head = heads[0] ;
+ else
+ {
+ head = doc.createElement( 'head' ) ;
+ doc.documentElement.insertBefore( head, doc.documentElement.firstChild ) ;
+ }
+
+ return head ;
+ } ;
+
+ /*
+ * ### Public interface.
+ */
+ return {
+ FCKeditorAPI_Evaluate : function( parentWindow, script )
+ {
+ // TODO : This one doesn't work always. The parent window will
+ // point to an anonymous function in this window. If this
+ // window is destroyied the parent window will be pointing to
+ // an invalid reference.
+
+ // Evaluate the script in this window.
+ eval( script ) ;
+
+ // Point the FCKeditorAPI property of the parent window to the
+ // local reference.
+ parentWindow.FCKeditorAPI = window.FCKeditorAPI ;
+ },
+
+ EditingArea_Start : function( doc, html )
+ {
+ // Get the HTML for the <head>.
+ var headInnerHtml = html.match( /<head>([\s\S]*)<\/head>/i )[1] ;
+
+ if ( headInnerHtml && headInnerHtml.length > 0 )
+ {
+ // Inject the <head> HTML inside a <div>.
+ // Do that before getDocumentHead because WebKit moves
+ // <link css> elements to the <head> at this point.
+ var div = doc.createElement( 'div' ) ;
+ div.innerHTML = headInnerHtml ;
+
+ // Move the <div> nodes to <head>.
+ FCKDomTools.MoveChildren( div, getDocumentHead( doc ) ) ;
+ }
+
+ doc.body.innerHTML = html.match( /<body>([\s\S]*)<\/body>/i )[1] ;
+
+ //prevent clicking on hyperlinks and navigating away
+ doc.addEventListener('click', function( ev )
+ {
+ ev.preventDefault() ;
+ ev.stopPropagation() ;
+ }, true ) ;
+ },
+
+ Panel_Contructor : function( doc, baseLocation )
+ {
+ var head = getDocumentHead( doc ) ;
+
+ // Set the <base> href.
+ head.appendChild( doc.createElement('base') ).href = baseLocation ;
+
+ doc.body.style.margin = '0px' ;
+ doc.body.style.padding = '0px' ;
+ },
+
+ ToolbarSet_GetOutElement : function( win, outMatch )
+ {
+ var toolbarTarget = win.parent ;
+
+ var targetWindowParts = outMatch[1].split( '.' ) ;
+ while ( targetWindowParts.length > 0 )
+ {
+ var part = targetWindowParts.shift() ;
+ if ( part.length > 0 )
+ toolbarTarget = toolbarTarget[ part ] ;
+ }
+
+ toolbarTarget = toolbarTarget.document.getElementById( outMatch[2] ) ;
+ },
+
+ ToolbarSet_InitOutFrame : function( doc )
+ {
+ var head = getDocumentHead( doc ) ;
+
+ head.appendChild( doc.createElement('base') ).href = window.document.location ;
+
+ var targetWindow = doc.defaultView;
+
+ targetWindow.adjust = function()
+ {
+ targetWindow.frameElement.height = doc.body.scrollHeight;
+ } ;
+
+ targetWindow.onresize = targetWindow.adjust ;
+ targetWindow.setTimeout( targetWindow.adjust, 0 ) ;
+
+ doc.body.style.overflow = 'hidden';
+ doc.body.innerHTML = document.getElementById( 'xToolbarSpace' ).innerHTML ;
+ }
+ } ;
+ })();
+
+ /*
+ * ### Overrides
+ */
+ ( function()
+ {
+ // Save references for override reuse.
+ var _Original_FCKPanel_Window_OnFocus = FCKPanel_Window_OnFocus ;
+ var _Original_FCKPanel_Window_OnBlur = FCKPanel_Window_OnBlur ;
+ var _Original_FCK_StartEditor = FCK.StartEditor ;
+
+ FCKPanel_Window_OnFocus = function( e, panel )
+ {
+ // Call the original implementation.
+ _Original_FCKPanel_Window_OnFocus.call( this, e, panel ) ;
+
+ if ( panel._focusTimer )
+ clearTimeout( panel._focusTimer ) ;
+ }
+
+ FCKPanel_Window_OnBlur = function( e, panel )
+ {
+ // Delay the execution of the original function.
+ panel._focusTimer = FCKTools.SetTimeout( _Original_FCKPanel_Window_OnBlur, 100, this, [ e, panel ] ) ;
+ }
+
+ FCK.StartEditor = function()
+ {
+ // Force pointing to the CSS files instead of using the inline CSS cached styles.
+ window.FCK_InternalCSS = FCKConfig.BasePath + 'css/fck_internal.css' ;
+ window.FCK_ShowTableBordersCSS = FCKConfig.BasePath + 'css/fck_showtableborders_gecko.css' ;
+
+ _Original_FCK_StartEditor.apply( this, arguments ) ;
+ }
+ })();
+}
diff --git a/httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js b/httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js
new file mode 100644
index 0000000..fa57c15
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js
@@ -0,0 +1,109 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This file has been compressed for better performance. The original source
+ * can be found at "editor/_source".
+ */
+
+var FCK_STATUS_NOTLOADED=window.parent.FCK_STATUS_NOTLOADED=0;var FCK_STATUS_ACTIVE=window.parent.FCK_STATUS_ACTIVE=1;var FCK_STATUS_COMPLETE=window.parent.FCK_STATUS_COMPLETE=2;var FCK_TRISTATE_OFF=window.parent.FCK_TRISTATE_OFF=0;var FCK_TRISTATE_ON=window.parent.FCK_TRISTATE_ON=1;var FCK_TRISTATE_DISABLED=window.parent.FCK_TRISTATE_DISABLED=-1;var FCK_UNKNOWN=window.parent.FCK_UNKNOWN=-9;var FCK_TOOLBARITEM_ONLYICON=window.parent.FCK_TOOLBARITEM_ONLYICON=0;var FCK_TOOLBARITEM_ONLYTEXT=window.parent.FCK_TOOLBARITEM_ONLYTEXT=1;var FCK_TOOLBARITEM_ICONTEXT=window.parent.FCK_TOOLBARITEM_ICONTEXT=2;var FCK_EDITMODE_WYSIWYG=window.parent.FCK_EDITMODE_WYSIWYG=0;var FCK_EDITMODE_SOURCE=window.parent.FCK_EDITMODE_SOURCE=1;var FCK_IMAGES_PATH='images/';var FCK_SPACER_PATH='images/spacer.gif';var CTRL=1000;var SHIFT=2000;var ALT=4000;var FCK_STYLE_BLOCK=0;var FCK_STYLE_INLINE=1;var FCK_STYLE_OBJECT=2;
+String.prototype.Contains=function(A){return (this.indexOf(A)>-1);};String.prototype.Equals=function(){var A=arguments;if (A.length==1&&A[0].pop) A=A[0];for (var i=0;i<A.length;i++){if (this==A[i]) return true;};return false;};String.prototype.IEquals=function(){var A=this.toUpperCase();var B=arguments;if (B.length==1&&B[0].pop) B=B[0];for (var i=0;i<B.length;i++){if (A==B[i].toUpperCase()) return true;};return false;};String.prototype.ReplaceAll=function(A,B){var C=this;for (var i=0;i<A.length;i++){C=C.replace(A[i],B[i]);};return C;};String.prototype.StartsWith=function(A){return (this.substr(0,A.length)==A);};String.prototype.EndsWith=function(A,B){var C=this.length;var D=A.length;if (D>C) return false;if (B){var E=new RegExp(A+'$','i');return E.test(this);}else return (D==0||this.substr(C-D,D)==A);};String.prototype.Remove=function(A,B){var s='';if (A>0) s=this.substring(0,A);if (A+B<this.length) s+=this.substring(A+B,this.length);return s;};String.prototype.Trim=function(){return this.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g,'');};String.prototype.LTrim=function(){return this.replace(/^[ \t\n\r]*/g,'');};String.prototype.RTrim=function(){return this.replace(/[ \t\n\r]*$/g,'');};String.prototype.ReplaceNewLineChars=function(A){return this.replace(/\n/g,A);};String.prototype.Replace=function(A,B,C){if (typeof B=='function'){return this.replace(A,function(){return B.apply(C||this,arguments);});}else return this.replace(A,B);};Array.prototype.IndexOf=function(A){for (var i=0;i<this.length;i++){if (this[i]==A) return i;};return-1;};
+var s=navigator.userAgent.toLowerCase();var FCKBrowserInfo={IsIE:/*@cc_on!@*/false,IsIE7:/*@cc_on!@*/false&&(parseInt(s.match(/msie (\d+)/)[1],10)>=7),IsIE6:/*@cc_on!@*/false&&(parseInt(s.match(/msie (\d+)/)[1],10)>=6),IsSafari:s.Contains(' applewebkit/'),IsOpera:!!window.opera,IsAIR:s.Contains(' adobeair/'),IsMac:s.Contains('macintosh')};(function(A){A.IsGecko=(navigator.product=='Gecko')&&!A.IsSafari&&!A.IsOpera;A.IsGeckoLike=(A.IsGecko||A.IsSafari||A.IsOpera);if (A.IsGecko){var B=s.match(/rv:(\d+\.\d+)/);var C=B&&parseFloat(B[1]);if (C){A.IsGecko10=(C<1.8);A.IsGecko19=(C>1.8);}};if (A.IsSafari) A.IsSafari3=(parseFloat(s.match(/ applewebkit\/(\d+)/)[1])<526);})(FCKBrowserInfo);
+var FCKURLParams={};(function(){var A=document.location.search.substr(1).split('&');for (var i=0;i<A.length;i++){var B=A[i].split('=');var C=decodeURIComponent(B[0]);var D=decodeURIComponent(B[1]);FCKURLParams[C]=D;}})();
+var FCKEvents=function(A){this.Owner=A;this._RegisteredEvents={};};FCKEvents.prototype.AttachEvent=function(A,B){var C;if (!(C=this._RegisteredEvents[A])) this._RegisteredEvents[A]=[B];else{if (C.IndexOf(B)==-1) C.push(B);}};FCKEvents.prototype.FireEvent=function(A,B){var C=true;var D=this._RegisteredEvents[A];if (D){for (var i=0;i<D.length;i++){try{C=(D[i](this.Owner,B)&&C);}catch(e){if (e.number!=-2146823277) throw e;}}};return C;};
+var FCKDataProcessor=function(){};FCKDataProcessor.prototype={ConvertToHtml:function(A){if (FCKConfig.FullPage){FCK.DocTypeDeclaration=A.match(FCKRegexLib.DocTypeTag);if (!FCKRegexLib.HasBodyTag.test(A)) A='<body>'+A+'</body>';if (!FCKRegexLib.HtmlOpener.test(A)) A='<html dir="'+FCKConfig.ContentLangDirection+'">'+A+'</html>';if (!FCKRegexLib.HeadOpener.test(A)) A=A.replace(FCKRegexLib.HtmlOpener,'$&<head><title></title></head>');return A;}else{var B=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"';if (FCKBrowserInfo.IsIE&&FCKConfig.DocType.length>0&&!FCKRegexLib.Html4DocType.test(FCKConfig.DocType)) B+=' style="overflow-y: scroll"';B+='><head><title></title></head><body'+FCKConfig.GetBodyAttributes()+'>'+A+'</body></html>';return B;}},ConvertToDataFormat:function(A,B,C,D){var E=FCKXHtml.GetXHTML(A,!B,D);if (C&&FCKRegexLib.EmptyOutParagraph.test(E)) return '';return E;},FixHtml:function(A){return A;}};
+var FCK={Name:FCKURLParams['InstanceName'],Status:0,EditMode:0,Toolbar:null,HasFocus:false,DataProcessor:new FCKDataProcessor(),GetInstanceObject:(function(){var w=window;return function(name){return w[name];}})(),AttachToOnSelectionChange:function(A){this.Events.AttachEvent('OnSelectionChange',A);},GetLinkedFieldValue:function(){return this.LinkedField.value;},GetParentForm:function(){return this.LinkedField.form;},StartupValue:'',IsDirty:function(){if (this.EditMode==1) return (this.StartupValue!=this.EditingArea.Textarea.value);else{if (!this.EditorDocument) return false;return (this.StartupValue!=this.EditorDocument.body.innerHTML);}},ResetIsDirty:function(){if (this.EditMode==1) this.StartupValue=this.EditingArea.Textarea.value;else if (this.EditorDocument.body) this.StartupValue=this.EditorDocument.body.innerHTML;},StartEditor:function(){this.TempBaseTag=FCKConfig.BaseHref.length>0?'<base href="'+FCKConfig.BaseHref+'" _fcktemp="true"></base>':'';var A=FCK.KeystrokeHandler=new FCKKeystrokeHandler();A.OnKeystroke=_FCK_KeystrokeHandler_OnKeystroke;A.SetKeystrokes(FCKConfig.Keystrokes);if (FCKBrowserInfo.IsIE7){if ((CTRL+86) in A.Keystrokes) A.SetKeystrokes([CTRL+86,true]);if ((SHIFT+45) in A.Keystrokes) A.SetKeystrokes([SHIFT+45,true]);};A.SetKeystrokes([CTRL+8,true]);this.EditingArea=new FCKEditingArea(document.getElementById('xEditingArea'));this.EditingArea.FFSpellChecker=FCKConfig.FirefoxSpellChecker;this.SetData(this.GetLinkedFieldValue(),true);FCKTools.AddEventListener(document,"keydown",this._TabKeyHandler);this.AttachToOnSelectionChange(_FCK_PaddingNodeListener);if (FCKBrowserInfo.IsGecko) this.AttachToOnSelectionChange(this._ExecCheckEmptyBlock);},Focus:function(){FCK.EditingArea.Focus();},SetStatus:function(A){this.Status=A;if (A==1){FCKFocusManager.AddWindow(window,true);if (FCKBrowserInfo.IsIE) FCKFocusManager.AddWindow(window.frameElement,true);if (FCKConfig.StartupFocus) FCK.Focus();};this.Events.FireEvent('OnStatusChange',A);},FixBody:function(){var A=FCKConfig.EnterMode;if (A!='p'&&A!='div') return;var B=this.EditorDocument;if (!B) return;var C=B.body;if (!C) return;FCKDomTools.TrimNode(C);var D=C.firstChild;var E;while (D){var F=false;switch (D.nodeType){case 1:var G=D.nodeName.toLowerCase();if (!FCKListsLib.BlockElements[G]&&G!='li'&&!D.getAttribute('_fckfakelement')&&D.getAttribute('_moz_dirty')==null) F=true;break;case 3:if (E||D.nodeValue.Trim().length>0) F=true;break;case 8:if (E) F=true;break;};if (F){var H=D.parentNode;if (!E) E=H.insertBefore(B.createElement(A),D);E.appendChild(H.removeChild(D));D=E.nextSibling;}else{if (E){FCKDomTools.TrimNode(E);E=null;};D=D.nextSibling;}};if (E) FCKDomTools.TrimNode(E);},GetData:function(A){FCK.Events.FireEvent("OnBeforeGetData");if (FCK.EditMode==1) return FCK.EditingArea.Textarea.value;this.FixBody();var B=FCK.EditorDocument;if (!B) return null;var C=FCKConfig.FullPage;var D=FCK.DataProcessor.ConvertToDataFormat(C?B.documentElement:B.body,!C,FCKConfig.IgnoreEmptyParagraphValue,A);D=FCK.ProtectEventsRestore(D);if (FCKBrowserInfo.IsIE) D=D.replace(FCKRegexLib.ToReplace,'$1');if (C){if (FCK.DocTypeDeclaration&&FCK.DocTypeDeclaration.length>0) D=FCK.DocTypeDeclaration+'\n'+D;if (FCK.XmlDeclaration&&FCK.XmlDeclaration.length>0) D=FCK.XmlDeclaration+'\n'+D;};D=FCKConfig.ProtectedSource.Revert(D);setTimeout(function() { FCK.Events.FireEvent("OnAfterGetData");},0);return D;},UpdateLinkedField:function(){var A=FCK.GetXHTML(FCKConfig.FormatOutput);if (FCKConfig.HtmlEncodeOutput) A=FCKTools.HTMLEncode(A);FCK.LinkedField.value=A;FCK.Events.FireEvent('OnAfterLinkedFieldUpdate');},RegisteredDoubleClickHandlers:{},OnDoubleClick:function(A){var B=FCK.RegisteredDoubleClickHandlers[A.tagName.toUpperCase()];if (B){for (var i=0;i<B.length;i++) B[i](A);};B=FCK.RegisteredDoubleClickHandlers['*'];if (B){for (var i=0;i<B.length;i++) B[i](A);}},RegisterDoubleClickHandler:function(A,B){var C=B||'*';C=C.toUpperCase();var D;if (!(D=FCK.RegisteredDoubleClickHandlers[C])) FCK.RegisteredDoubleClickHandlers[C]=[A];else{if (D.IndexOf(A)==-1) D.push(A);}},OnAfterSetHTML:function(){FCKDocumentProcessor.Process(FCK.EditorDocument);FCKUndo.SaveUndoStep();FCK.Events.FireEvent('OnSelectionChange');FCK.Events.FireEvent('OnAfterSetHTML');},ProtectUrls:function(A){A=A.replace(FCKRegexLib.ProtectUrlsA,'$& _fcksavedurl=$1');A=A.replace(FCKRegexLib.ProtectUrlsImg,'$& _fcksavedurl=$1');A=A.replace(FCKRegexLib.ProtectUrlsArea,'$& _fcksavedurl=$1');return A;},ProtectEvents:function(A){return A.replace(FCKRegexLib.TagsWithEvent,_FCK_ProtectEvents_ReplaceTags);},ProtectEventsRestore:function(A){return A.replace(FCKRegexLib.ProtectedEvents,_FCK_ProtectEvents_RestoreEvents);},ProtectTags:function(A){var B=FCKConfig.ProtectedTags;if (FCKBrowserInfo.IsIE) B+=B.length>0?'|ABBR|XML|EMBED|OBJECT':'ABBR|XML|EMBED|OBJECT';var C;if (B.length>0){C=new RegExp('<('+B+')(?!\w|:)','gi');A=A.replace(C,'<FCK:$1');C=new RegExp('<\/('+B+')>','gi');A=A.replace(C,'<\/FCK:$1>');};B='META';if (FCKBrowserInfo.IsIE) B+='|HR';C=new RegExp('<(('+B+')(?=\\s|>|/)[\\s\\S]*?)/?>','gi');A=A.replace(C,'<FCK:$1 />');return A;},SetData:function(A,B){this.EditingArea.Mode=FCK.EditMode;if (FCKBrowserInfo.IsIE&&FCK.EditorDocument){FCK.EditorDocument.detachEvent("onselectionchange",Doc_OnSelectionChange);};FCKTempBin.Reset();FCK.Selection.Release();if (FCK.EditMode==0){this._ForceResetIsDirty=(B===true);A=FCKConfig.ProtectedSource.Protect(A);A=FCK.DataProcessor.ConvertToHtml(A);A=A.replace(FCKRegexLib.InvalidSelfCloseTags,'$1></$2>');A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);if (FCK.TempBaseTag.length>0&&!FCKRegexLib.HasBaseTag.test(A)) A=A.replace(FCKRegexLib.HeadOpener,'$&'+FCK.TempBaseTag);var C='';if (!FCKConfig.FullPage) C+=_FCK_GetEditorAreaStyleTags();if (FCKBrowserInfo.IsIE) C+=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C+=FCKTools.GetStyleHtml(FCK_ShowTableBordersCSS,true);C+=FCKTools.GetStyleHtml(FCK_InternalCSS,true);A=A.replace(FCKRegexLib.HeadCloser,C+'$&');this.EditingArea.OnLoad=_FCK_EditingArea_OnLoad;this.EditingArea.Start(A);}else{FCK.EditorWindow=null;FCK.EditorDocument=null;FCKDomTools.PaddingNode=null;this.EditingArea.OnLoad=null;this.EditingArea.Start(A);this.EditingArea.Textarea._FCKShowContextMenu=true;FCK.EnterKeyHandler=null;if (B) this.ResetIsDirty();FCK.KeystrokeHandler.AttachToElement(this.EditingArea.Textarea);this.EditingArea.Textarea.focus();FCK.Events.FireEvent('OnAfterSetHTML');};if (window.onresize) window.onresize();},RedirectNamedCommands:{},ExecuteNamedCommand:function(A,B,C,D){if (!D) FCKUndo.SaveUndoStep();if (!C&&FCK.RedirectNamedCommands[A]!=null) FCK.ExecuteRedirectedNamedCommand(A,B);else{FCK.Focus();FCK.EditorDocument.execCommand(A,false,B);FCK.Events.FireEvent('OnSelectionChange');};if (!D) FCKUndo.SaveUndoStep();},GetNamedCommandState:function(A){try{if (FCKBrowserInfo.IsSafari&&FCK.EditorWindow&&A.IEquals('Paste')) return 0;if (!FCK.EditorDocument.queryCommandEnabled(A)) return -1;else{return FCK.EditorDocument.queryCommandState(A)?1:0;}}catch (e){return 0;}},GetNamedCommandValue:function(A){var B='';var C=FCK.GetNamedCommandState(A);if (C==-1) return null;try{B=this.EditorDocument.queryCommandValue(A);}catch(e) {};return B?B:'';},Paste:function(A){if (FCK.Status!=2||!FCK.Events.FireEvent('OnPaste')) return false;return A||FCK._ExecPaste();},PasteFromWord:function(){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteFromWord,'dialog/fck_paste.html',400,330,'Word');},Preview:function(){var A;if (FCKConfig.FullPage){if (FCK.TempBaseTag.length>0) A=FCK.TempBaseTag+FCK.GetXHTML();else A=FCK.GetXHTML();}else{A=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"><head>'+FCK.TempBaseTag+'<title>'+FCKLang.Preview+'</title>'+_FCK_GetEditorAreaStyleTags()+'</head><body'+FCKConfig.GetBodyAttributes()+'>'+FCK.GetXHTML()+'</body></html>';};var B=FCKConfig.ScreenWidth*0.8;var C=FCKConfig.ScreenHeight*0.7;var D=(FCKConfig.ScreenWidth-B)/2;var E='';if (FCK_IS_CUSTOM_DOMAIN&&FCKBrowserInfo.IsIE){window._FCKHtmlToLoad=A;E='javascript:void( (function(){document.open() ;document.domain="'+document.domain+'" ;document.write( window.opener._FCKHtmlToLoad );document.close() ;window.opener._FCKHtmlToLoad = null ;})() )';};var F=window.open(E,null,'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width='+B+',height='+C+',left='+D);if (!FCK_IS_CUSTOM_DOMAIN||!FCKBrowserInfo.IsIE){F.document.write(A);F.document.close();}},SwitchEditMode:function(A){var B=(FCK.EditMode==0);var C=FCK.IsDirty();var D;if (B){FCKCommands.GetCommand('ShowBlocks').SaveState();if (!A&&FCKBrowserInfo.IsIE) FCKUndo.SaveUndoStep();D=FCK.GetXHTML(FCKConfig.FormatSource);if (FCKBrowserInfo.IsIE) FCKTempBin.ToHtml();if (D==null) return false;}else D=this.EditingArea.Textarea.value;FCK.EditMode=B?1:0;FCK.SetData(D,!C);FCK.Focus();FCKTools.RunFunction(FCK.ToolbarSet.RefreshModeState,FCK.ToolbarSet);return true;},InsertElement:function(A){if (typeof A=='string') A=this.EditorDocument.createElement(A);var B=A.nodeName.toLowerCase();FCKSelection.Restore();var C=new FCKDomRange(this.EditorWindow);C.MoveToSelection();C.DeleteContents();if (FCKListsLib.BlockElements[B]!=null){if (C.StartBlock){if (C.CheckStartOfBlock()) C.MoveToPosition(C.StartBlock,3);else if (C.CheckEndOfBlock()) C.MoveToPosition(C.StartBlock,4);else C.SplitBlock();};C.InsertNode(A);var D=FCKDomTools.GetNextSourceElement(A,false,null,['hr','br','param','img','area','input'],true);if (!D&&FCKConfig.EnterMode!='br'){D=this.EditorDocument.body.appendChild(this.EditorDocument.createElement(FCKConfig.EnterMode));if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(D);};if (FCKListsLib.EmptyElements[B]==null) C.MoveToElementEditStart(A);else if (D) C.MoveToElementEditStart(D);else C.MoveToPosition(A,4);if (FCKBrowserInfo.IsGeckoLike){if (D) FCKDomTools.ScrollIntoView(D,false);FCKDomTools.ScrollIntoView(A,false);}}else{C.InsertNode(A);C.SetStart(A,4);C.SetEnd(A,4);};C.Select();C.Release();this.Focus();return A;},_InsertBlockElement:function(A){},_IsFunctionKey:function(A){if (A>=16&&A<=20) return true;if (A==27||(A>=33&&A<=40)) return true;if (A==45) return true;return false;},_KeyDownListener:function(A){if (!A) A=FCK.EditorWindow.event;if (FCK.EditorWindow){if (!FCK._IsFunctionKey(A.keyCode)&&!(A.ctrlKey||A.metaKey)&&!(A.keyCode==46)) FCK._KeyDownUndo();};return true;},_KeyDownUndo:function(){if (!FCKUndo.Typing){FCKUndo.SaveUndoStep();FCKUndo.Typing=true;FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.TypesCount++;FCKUndo.Changed=1;if (FCKUndo.TypesCount>FCKUndo.MaxTypes){FCKUndo.TypesCount=0;FCKUndo.SaveUndoStep();}},_TabKeyHandler:function(A){if (!A) A=window.event;var B=A.keyCode;if (B==9&&FCK.EditMode!=0){if (FCKBrowserInfo.IsIE){var C=document.selection.createRange();if (C.parentElement()!=FCK.EditingArea.Textarea) return true;C.text='\t';C.select();}else{var a=[];var D=FCK.EditingArea.Textarea;var E=D.selectionStart;var F=D.selectionEnd;a.push(D.value.substr(0,E));a.push('\t');a.push(D.value.substr(F));D.value=a.join('');D.setSelectionRange(E+1,E+1);};if (A.preventDefault) return A.preventDefault();return A.returnValue=false;};return true;}};FCK.Events=new FCKEvents(FCK);FCK.GetHTML=FCK.GetXHTML=FCK.GetData;FCK.SetHTML=FCK.SetData;FCK.InsertElementAndGetIt=FCK.CreateElement=FCK.InsertElement;function _FCK_ProtectEvents_ReplaceTags(A){return A.replace(FCKRegexLib.EventAttributes,_FCK_ProtectEvents_ReplaceEvents);};function _FCK_ProtectEvents_ReplaceEvents(A,B){return ' '+B+'_fckprotectedatt="'+encodeURIComponent(A)+'"';};function _FCK_ProtectEvents_RestoreEvents(A,B){return decodeURIComponent(B);};function _FCK_MouseEventsListener(A){if (!A) A=window.event;if (A.type=='mousedown') FCK.MouseDownFlag=true;else if (A.type=='mouseup') FCK.MouseDownFlag=false;else if (A.type=='mousemove') FCK.Events.FireEvent('OnMouseMove',A);};function _FCK_PaddingNodeListener(){if (FCKConfig.EnterMode.IEquals('br')) return;FCKDomTools.EnforcePaddingNode(FCK.EditorDocument,FCKConfig.EnterMode);if (!FCKBrowserInfo.IsIE&&FCKDomTools.PaddingNode){var A=FCKSelection.GetSelection();if (A&&A.rangeCount==1){var B=A.getRangeAt(0);if (B.collapsed&&B.startContainer==FCK.EditorDocument.body&&B.startOffset==0){B.selectNodeContents(FCKDomTools.PaddingNode);B.collapse(true);A.removeAllRanges();A.addRange(B);}}}else if (FCKDomTools.PaddingNode){var C=FCKSelection.GetParentElement();var D=FCKDomTools.PaddingNode;if (C&&C.nodeName.IEquals('body')){if (FCK.EditorDocument.body.childNodes.length==1&&FCK.EditorDocument.body.firstChild==D){if (FCKSelection._GetSelectionDocument(FCK.EditorDocument.selection)!=FCK.EditorDocument) return;var B=FCK.EditorDocument.body.createTextRange();var F=false;if (!D.childNodes.firstChild){D.appendChild(FCKTools.GetElementDocument(D).createTextNode('\ufeff'));F=true;};B.moveToElementText(D);B.select();if (F) B.pasteHTML('');}}}};function _FCK_EditingArea_OnLoad(){FCK.EditorWindow=FCK.EditingArea.Window;FCK.EditorDocument=FCK.EditingArea.Document;if (FCKBrowserInfo.IsIE) FCKTempBin.ToElements();FCK.InitializeBehaviors();FCK.MouseDownFlag=false;FCKTools.AddEventListener(FCK.EditorDocument,'mousemove',_FCK_MouseEventsListener);FCKTools.AddEventListener(FCK.EditorDocument,'mousedown',_FCK_MouseEventsListener);FCKTools.AddEventListener(FCK.EditorDocument,'mouseup',_FCK_MouseEventsListener);if (FCKBrowserInfo.IsSafari){FCKTools.AddEventListener(FCK.EditorDocument,'paste',function(evt){var A=new FCKDomRange(FCK.EditorWindow);var B=FCK.EditorDocument.createTextNode('\ufeff');var C=FCK.EditorDocument.createElement('a');C.id='fck_paste_padding';C.innerHTML='&#65279;';A.MoveToSelection();A.DeleteContents();A.InsertNode(B);A.Collapse();A.InsertNode(C);A.MoveToPosition(C,3);A.Select();setTimeout(function(){B.parentNode.removeChild(B);C=FCK.EditorDocument.getElementById('fck_paste_padding');C.parentNode.removeChild(C);},0);});};if (FCKBrowserInfo.IsSafari){var D=function(evt){if (!(evt.ctrlKey||evt.metaKey)) return;if (FCK.EditMode!=0) return;switch (evt.keyCode){case 89:FCKUndo.Redo();break;case 90:FCKUndo.Undo();break;}};FCKTools.AddEventListener(FCK.EditorDocument,'keyup',D);};FCK.EnterKeyHandler=new FCKEnterKey(FCK.EditorWindow,FCKConfig.EnterMode,FCKConfig.ShiftEnterMode,FCKConfig.TabSpaces);FCK.KeystrokeHandler.AttachToElement(FCK.EditorDocument);if (FCK._ForceResetIsDirty) FCK.ResetIsDirty();if (FCKBrowserInfo.IsIE&&FCK.HasFocus) FCK.EditorDocument.body.setActive();FCK.OnAfterSetHTML();FCKCommands.GetCommand('ShowBlocks').RestoreState();if (FCK.Status!=0) return;FCK.SetStatus(1);};function _FCK_GetEditorAreaStyleTags(){return FCKTools.GetStyleHtml(FCKConfig.EditorAreaCSS)+FCKTools.GetStyleHtml(FCKConfig.EditorAreaStyles);};function _FCK_KeystrokeHandler_OnKeystroke(A,B){if (FCK.Status!=2) return false;if (FCK.EditMode==0){switch (B){case 'Paste':return!FCK.Paste();case 'Cut':FCKUndo.SaveUndoStep();return false;}}else{if (B.Equals('Paste','Undo','Redo','SelectAll','Cut')) return false;};var C=FCK.Commands.GetCommand(B);if (C.GetState()==-1) return false;return (C.Execute.apply(C,FCKTools.ArgumentsToArray(arguments,2))!==false);};(function(){var A=window.parent.document;var B=A.getElementById(FCK.Name);var i=0;while (B||i==0){if (B&&B.tagName.toLowerCase().Equals('input','textarea')){FCK.LinkedField=B;break;};B=A.getElementsByName(FCK.Name)[i++];}})();var FCKTempBin={Elements:[],AddElement:function(A){var B=this.Elements.length;this.Elements[B]=A;return B;},RemoveElement:function(A){var e=this.Elements[A];this.Elements[A]=null;return e;},Reset:function(){var i=0;while (i<this.Elements.length) this.Elements[i++]=null;this.Elements.length=0;},ToHtml:function(){for (var i=0;i<this.Elements.length;i++){this.Elements[i]='<div>&nbsp;'+this.Elements[i].outerHTML+'</div>';this.Elements[i].isHtml=true;}},ToElements:function(){var A=FCK.EditorDocument.createElement('div');for (var i=0;i<this.Elements.length;i++){if (this.Elements[i].isHtml){A.innerHTML=this.Elements[i];this.Elements[i]=A.firstChild.removeChild(A.firstChild.lastChild);}}}};var FCKFocusManager=FCK.FocusManager={IsLocked:false,AddWindow:function(A,B){var C;if (FCKBrowserInfo.IsIE) C=A.nodeType==1?A:A.frameElement?A.frameElement:A.document;else if (FCKBrowserInfo.IsSafari) C=A;else C=A.document;FCKTools.AddEventListener(C,'blur',FCKFocusManager_Win_OnBlur);FCKTools.AddEventListener(C,'focus',B?FCKFocusManager_Win_OnFocus_Area:FCKFocusManager_Win_OnFocus);},RemoveWindow:function(A){if (FCKBrowserInfo.IsIE) oTarget=A.nodeType==1?A:A.frameElement?A.frameElement:A.document;else oTarget=A.document;FCKTools.RemoveEventListener(oTarget,'blur',FCKFocusManager_Win_OnBlur);FCKTools.RemoveEventListener(oTarget,'focus',FCKFocusManager_Win_OnFocus_Area);FCKTools.RemoveEventListener(oTarget,'focus',FCKFocusManager_Win_OnFocus);},Lock:function(){this.IsLocked=true;},Unlock:function(){if (this._HasPendingBlur) FCKFocusManager._Timer=window.setTimeout(FCKFocusManager_FireOnBlur,100);this.IsLocked=false;},_ResetTimer:function(){this._HasPendingBlur=false;if (this._Timer){window.clearTimeout(this._Timer);delete this._Timer;}}};function FCKFocusManager_Win_OnBlur(){if (typeof(FCK)!='undefined'&&FCK.HasFocus){FCKFocusManager._ResetTimer();FCKFocusManager._Timer=window.setTimeout(FCKFocusManager_FireOnBlur,100);}};function FCKFocusManager_FireOnBlur(){if (FCKFocusManager.IsLocked) FCKFocusManager._HasPendingBlur=true;else{FCK.HasFocus=false;FCK.Events.FireEvent("OnBlur");}};function FCKFocusManager_Win_OnFocus_Area(){if (FCKFocusManager._IsFocusing) return;FCKFocusManager._IsFocusing=true;FCK.Focus();FCKFocusManager_Win_OnFocus();FCKTools.RunFunction(function(){delete FCKFocusManager._IsFocusing;});};function FCKFocusManager_Win_OnFocus(){FCKFocusManager._ResetTimer();if (!FCK.HasFocus&&!FCKFocusManager.IsLocked){FCK.HasFocus=true;FCK.Events.FireEvent("OnFocus");}};(function(){var A=window.frameElement;var B=A.width;var C=A.height;if (/^\d+$/.test(B)) B+='px';if (/^\d+$/.test(C)) C+='px';var D=A.style;D.border=D.padding=D.margin=0;D.backgroundColor='transparent';D.backgroundImage='none';D.width=B;D.height=C;})();
+FCK.Description="FCKeditor for Gecko Browsers";FCK.InitializeBehaviors=function(){if (window.onresize) window.onresize();FCKFocusManager.AddWindow(this.EditorWindow);this.ExecOnSelectionChange=function(){FCK.Events.FireEvent("OnSelectionChange");};this._ExecDrop=function(evt){if (FCK.MouseDownFlag){FCK.MouseDownFlag=false;return;};if (FCKConfig.ForcePasteAsPlainText){if (evt.dataTransfer){var A=evt.dataTransfer.getData('Text');A=FCKTools.HTMLEncode(A);A=FCKTools.ProcessLineBreaks(window,FCKConfig,A);FCK.InsertHtml(A);}else if (FCKConfig.ShowDropDialog) FCK.PasteAsPlainText();evt.preventDefault();evt.stopPropagation();}};this._ExecCheckCaret=function(evt){if (FCK.EditMode!=0) return;if (evt.type=='keypress'){var B=evt.keyCode;if (B<33||B>40) return;};var C=function(H){if (H.nodeType!=1) return false;var D=H.tagName.toLowerCase();return (FCKListsLib.BlockElements[D]||FCKListsLib.EmptyElements[D]);};var E=function(){var F=FCKSelection.GetSelection();var G=F.getRangeAt(0);if (!G||!G.collapsed) return;var H=G.endContainer;if (H.nodeType!=3) return;if (H.nodeValue.length!=G.endOffset) return;var I=H.parentNode.tagName.toLowerCase();if (!(I=='a'||(!FCKBrowserInfo.IsOpera&&String(H.parentNode.contentEditable)=='false')||(!(FCKListsLib.BlockElements[I]||FCKListsLib.NonEmptyBlockElements[I])&&B==35))) return;var J=FCKTools.GetNextTextNode(H,H.parentNode,C);if (J) return;G=FCK.EditorDocument.createRange();J=FCKTools.GetNextTextNode(H,H.parentNode.parentNode,C);if (J){if (FCKBrowserInfo.IsOpera&&B==37) return;G.setStart(J,0);G.setEnd(J,0);}else{while (H.parentNode&&H.parentNode!=FCK.EditorDocument.body&&H.parentNode!=FCK.EditorDocument.documentElement&&H==H.parentNode.lastChild&&(!FCKListsLib.BlockElements[H.parentNode.tagName.toLowerCase()]&&!FCKListsLib.NonEmptyBlockElements[H.parentNode.tagName.toLowerCase()])) H=H.parentNode;if (FCKListsLib.BlockElements[I]||FCKListsLib.EmptyElements[I]||H==FCK.EditorDocument.body){G.setStart(H,H.childNodes.length);G.setEnd(H,H.childNodes.length);}else{var K=H.nextSibling;while (K){if (K.nodeType!=1){K=K.nextSibling;continue;};var L=K.tagName.toLowerCase();if (FCKListsLib.BlockElements[L]||FCKListsLib.EmptyElements[L]||FCKListsLib.NonEmptyBlockElements[L]) break;K=K.nextSibling;};var M=FCK.EditorDocument.createTextNode('');if (K) H.parentNode.insertBefore(M,K);else H.parentNode.appendChild(M);G.setStart(M,0);G.setEnd(M,0);}};F.removeAllRanges();F.addRange(G);FCK.Events.FireEvent("OnSelectionChange");};setTimeout(E,1);};this.ExecOnSelectionChangeTimer=function(){if (FCK.LastOnChangeTimer) window.clearTimeout(FCK.LastOnChangeTimer);FCK.LastOnChangeTimer=window.setTimeout(FCK.ExecOnSelectionChange,100);};this.EditorDocument.addEventListener('mouseup',this.ExecOnSelectionChange,false);this.EditorDocument.addEventListener('keyup',this.ExecOnSelectionChangeTimer,false);this._DblClickListener=function(e){FCK.OnDoubleClick(e.target);e.stopPropagation();};this.EditorDocument.addEventListener('dblclick',this._DblClickListener,true);this.EditorDocument.addEventListener('keydown',this._KeyDownListener,false);if (FCKBrowserInfo.IsGecko){this.EditorWindow.addEventListener('dragdrop',this._ExecDrop,true);}else if (FCKBrowserInfo.IsSafari){this.EditorDocument.addEventListener('dragover',function (evt){ if (!FCK.MouseDownFlag&&FCK.Config.ForcePasteAsPlainText) evt.returnValue=false;},true);this.EditorDocument.addEventListener('drop',this._ExecDrop,true);this.EditorDocument.addEventListener('mousedown',function(ev){var N=ev.srcElement;if (N.nodeName.IEquals('IMG','HR','INPUT','TEXTAREA','SELECT')){FCKSelection.SelectNode(N);}},true);this.EditorDocument.addEventListener('mouseup',function(ev){if (ev.srcElement.nodeName.IEquals('INPUT','TEXTAREA','SELECT')) ev.preventDefault()},true);this.EditorDocument.addEventListener('click',function(ev){if (ev.srcElement.nodeName.IEquals('INPUT','TEXTAREA','SELECT')) ev.preventDefault()},true);};if (FCKBrowserInfo.IsGecko||FCKBrowserInfo.IsOpera){this.EditorDocument.addEventListener('keypress',this._ExecCheckCaret,false);this.EditorDocument.addEventListener('click',this._ExecCheckCaret,false);};FCK.ContextMenu._InnerContextMenu.SetMouseClickWindow(FCK.EditorWindow);FCK.ContextMenu._InnerContextMenu.AttachToElement(FCK.EditorDocument);};FCK.MakeEditable=function(){this.EditingArea.MakeEditable();};function Document_OnContextMenu(e){if (!e.target._FCKShowContextMenu) e.preventDefault();};document.oncontextmenu=Document_OnContextMenu;FCK._BaseGetNamedCommandState=FCK.GetNamedCommandState;FCK.GetNamedCommandState=function(A){switch (A){case 'Unlink':return FCKSelection.HasAncestorNode('A')?0:-1;default:return FCK._BaseGetNamedCommandState(A);}};FCK.RedirectNamedCommands={Print:true,Paste:true};FCK.ExecuteRedirectedNamedCommand=function(A,B){switch (A){case 'Print':FCK.EditorWindow.print();break;case 'Paste':try{if (FCKBrowserInfo.IsSafari) throw '';if (FCK.Paste()) FCK.ExecuteNamedCommand('Paste',null,true);}catch (e) {if (FCKConfig.ForcePasteAsPlainText) FCK.PasteAsPlainText();else FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.Paste,'dialog/fck_paste.html',400,330,'Security');};break;default:FCK.ExecuteNamedCommand(A,B);}};FCK._ExecPaste=function(){FCKUndo.SaveUndoStep();if (FCKConfig.ForcePasteAsPlainText){FCK.PasteAsPlainText();return false;};return true;};FCK.InsertHtml=function(A){var B=FCK.EditorDocument,range;A=FCKConfig.ProtectedSource.Protect(A);A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);FCKUndo.SaveUndoStep();if (FCKBrowserInfo.IsGecko){A=A.replace(/&nbsp;$/,'$&<span _fcktemp="1"/>');var C=new FCKDocumentFragment(this.EditorDocument);C.AppendHtml(A);var D=C.RootNode.lastChild;range=new FCKDomRange(this.EditorWindow);range.MoveToSelection();var E=C.RootNode.firstChild;while (E&&E.nodeType!=1) E=E.nextSibling;if (E&&FCKListsLib.BlockElements[E.nodeName.toLowerCase()]) range.SplitBlock();range.DeleteContents();range.InsertNode(C.RootNode);range.MoveToPosition(D,4);}else B.execCommand('inserthtml',false,A);this.Focus();if (!range){range=new FCKDomRange(this.EditorWindow);range.MoveToSelection();};var F=range.CreateBookmark();FCKDocumentProcessor.Process(B);try{range.MoveToBookmark(F);range.Select();}catch (e) {};this.Events.FireEvent("OnSelectionChange");};FCK.PasteAsPlainText=function(){FCKTools.RunFunction(FCKDialog.OpenDialog,FCKDialog,['FCKDialog_Paste',FCKLang.PasteAsText,'dialog/fck_paste.html',400,330,'PlainText']);};FCK.GetClipboardHTML=function(){return '';};FCK.CreateLink=function(A,B){var C=[];if (FCKSelection.GetSelection().isCollapsed) return C;FCK.ExecuteNamedCommand('Unlink',null,false,!!B);if (A.length>0){var D='javascript:void(0);/*'+(new Date().getTime())+'*/';FCK.ExecuteNamedCommand('CreateLink',D,false,!!B);var E=this.EditorDocument.evaluate("//a[@href='"+D+"']",this.EditorDocument.body,null,XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,null);for (var i=0;i<E.snapshotLength;i++){var F=E.snapshotItem(i);F.href=A;C.push(F);}};return C;};FCK._FillEmptyBlock=function(A){if (!A||A.nodeType!=1) return;var B=A.tagName.toLowerCase();if (B!='p'&&B!='div') return;if (A.firstChild) return;FCKTools.AppendBogusBr(A);};FCK._ExecCheckEmptyBlock=function(){FCK._FillEmptyBlock(FCK.EditorDocument.body.firstChild);var A=FCKSelection.GetSelection();if (!A||A.rangeCount<1) return;var B=A.getRangeAt(0);FCK._FillEmptyBlock(B.startContainer);};
+var FCKConfig=FCK.Config={};if (document.location.protocol=='file:'){FCKConfig.BasePath=decodeURIComponent(document.location.pathname.substr(1));FCKConfig.BasePath=FCKConfig.BasePath.replace(/\\/gi,'/');var sFullProtocol=document.location.href.match(/^(file\:\/{2,3})/)[1];if (FCKBrowserInfo.IsOpera) sFullProtocol+='localhost/';FCKConfig.BasePath=sFullProtocol+FCKConfig.BasePath.substring(0,FCKConfig.BasePath.lastIndexOf('/')+1);}else FCKConfig.BasePath=document.location.protocol+'//'+document.location.host+document.location.pathname.substring(0,document.location.pathname.lastIndexOf('/')+1);FCKConfig.FullBasePath=FCKConfig.BasePath;FCKConfig.EditorPath=FCKConfig.BasePath.replace(/editor\/$/,'');try{FCKConfig.ScreenWidth=screen.width;FCKConfig.ScreenHeight=screen.height;}catch (e){FCKConfig.ScreenWidth=800;FCKConfig.ScreenHeight=600;};FCKConfig.ProcessHiddenField=function(){this.PageConfig={};var A=window.parent.document.getElementById(FCK.Name+'___Config');if (!A) return;var B=A.value.split('&');for (var i=0;i<B.length;i++){if (B[i].length==0) continue;var C=B[i].split('=');var D=decodeURIComponent(C[0]);var E=decodeURIComponent(C[1]);if (D=='CustomConfigurationsPath') FCKConfig[D]=E;else if (E.toLowerCase()=="true") this.PageConfig[D]=true;else if (E.toLowerCase()=="false") this.PageConfig[D]=false;else if (E.length>0&&!isNaN(E)) this.PageConfig[D]=parseFloat(E);else this.PageConfig[D]=E;}};function FCKConfig_LoadPageConfig(){var A=FCKConfig.PageConfig;for (var B in A) FCKConfig[B]=A[B];};function FCKConfig_PreProcess(){var A=FCKConfig;if (A.AllowQueryStringDebug){try{if ((/fckdebug=true/i).test(window.top.location.search)) A.Debug=true;}catch (e) { }};if (!A.PluginsPath.EndsWith('/')) A.PluginsPath+='/';var B=A.ToolbarComboPreviewCSS;if (!B||B.length==0) A.ToolbarComboPreviewCSS=A.EditorAreaCSS;A.RemoveAttributesArray=(A.RemoveAttributes||'').split(',');if (!FCKConfig.SkinEditorCSS||FCKConfig.SkinEditorCSS.length==0) FCKConfig.SkinEditorCSS=FCKConfig.SkinPath+'fck_editor.css';if (!FCKConfig.SkinDialogCSS||FCKConfig.SkinDialogCSS.length==0) FCKConfig.SkinDialogCSS=FCKConfig.SkinPath+'fck_dialog.css';};FCKConfig.ToolbarSets={};FCKConfig.Plugins={};FCKConfig.Plugins.Items=[];FCKConfig.Plugins.Add=function(A,B,C){FCKConfig.Plugins.Items.push([A,B,C]);};FCKConfig.ProtectedSource={};FCKConfig.ProtectedSource._CodeTag=(new Date()).valueOf();FCKConfig.ProtectedSource.RegexEntries=[/<!--[\s\S]*?-->/g,/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi];FCKConfig.ProtectedSource.Add=function(A){this.RegexEntries.push(A);};FCKConfig.ProtectedSource.Protect=function(A){var B=this._CodeTag;function _Replace(protectedSource){var C=FCKTempBin.AddElement(protectedSource);return '<!--{'+B+C+'}-->';};for (var i=0;i<this.RegexEntries.length;i++){A=A.replace(this.RegexEntries[i],_Replace);};return A;};FCKConfig.ProtectedSource.Revert=function(A,B){function _Replace(m,opener,index){var C=B?FCKTempBin.RemoveElement(index):FCKTempBin.Elements[index];return FCKConfig.ProtectedSource.Revert(C,B);};var D=new RegExp("(<|&lt;)!--\\{"+this._CodeTag+"(\\d+)\\}--(>|&gt;)","g");return A.replace(D,_Replace);};FCKConfig.GetBodyAttributes=function(){var A='';if (this.BodyId&&this.BodyId.length>0) A+=' id="'+this.BodyId+'"';if (this.BodyClass&&this.BodyClass.length>0) A+=' class="'+this.BodyClass+'"';return A;};FCKConfig.ApplyBodyAttributes=function(A){if (this.BodyId&&this.BodyId.length>0) A.id=FCKConfig.BodyId;if (this.BodyClass&&this.BodyClass.length>0) A.className+=' '+FCKConfig.BodyClass;};
+var FCKDebug={Output:function(){},OutputObject:function(){}};
+var FCKDomTools={MoveChildren:function(A,B,C){if (A==B) return;var D;if (C){while ((D=A.lastChild)) B.insertBefore(A.removeChild(D),B.firstChild);}else{while ((D=A.firstChild)) B.appendChild(A.removeChild(D));}},MoveNode:function(A,B,C){if (C) B.insertBefore(FCKDomTools.RemoveNode(A),B.firstChild);else B.appendChild(FCKDomTools.RemoveNode(A));},TrimNode:function(A){this.LTrimNode(A);this.RTrimNode(A);},LTrimNode:function(A){var B;while ((B=A.firstChild)){if (B.nodeType==3){var C=B.nodeValue.LTrim();var D=B.nodeValue.length;if (C.length==0){A.removeChild(B);continue;}else if (C.length<D){B.splitText(D-C.length);A.removeChild(A.firstChild);}};break;}},RTrimNode:function(A){var B;while ((B=A.lastChild)){if (B.nodeType==3){var C=B.nodeValue.RTrim();var D=B.nodeValue.length;if (C.length==0){B.parentNode.removeChild(B);continue;}else if (C.length<D){B.splitText(C.length);A.lastChild.parentNode.removeChild(A.lastChild);}};break;};if (!FCKBrowserInfo.IsIE&&!FCKBrowserInfo.IsOpera){B=A.lastChild;if (B&&B.nodeType==1&&B.nodeName.toLowerCase()=='br'){B.parentNode.removeChild(B);}}},RemoveNode:function(A,B){if (B){var C;while ((C=A.firstChild)) A.parentNode.insertBefore(A.removeChild(C),A);};return A.parentNode.removeChild(A);},GetFirstChild:function(A,B){if (typeof (B)=='string') B=[B];var C=A.firstChild;while(C){if (C.nodeType==1&&C.tagName.Equals.apply(C.tagName,B)) return C;C=C.nextSibling;};return null;},GetLastChild:function(A,B){if (typeof (B)=='string') B=[B];var C=A.lastChild;while(C){if (C.nodeType==1&&(!B||C.tagName.Equals(B))) return C;C=C.previousSibling;};return null;},GetPreviousSourceElement:function(A,B,C,D){if (!A) return null;if (C&&A.nodeType==1&&A.nodeName.IEquals(C)) return null;if (A.previousSibling) A=A.previousSibling;else return this.GetPreviousSourceElement(A.parentNode,B,C,D);while (A){if (A.nodeType==1){if (C&&A.nodeName.IEquals(C)) break;if (!D||!A.nodeName.IEquals(D)) return A;}else if (B&&A.nodeType==3&&A.nodeValue.RTrim().length>0) break;if (A.lastChild) A=A.lastChild;else return this.GetPreviousSourceElement(A,B,C,D);};return null;},GetNextSourceElement:function(A,B,C,D,E){while((A=this.GetNextSourceNode(A,E))){if (A.nodeType==1){if (C&&A.nodeName.IEquals(C)) break;if (D&&A.nodeName.IEquals(D)) return this.GetNextSourceElement(A,B,C,D);return A;}else if (B&&A.nodeType==3&&A.nodeValue.RTrim().length>0) break;};return null;},GetNextSourceNode:function(A,B,C,D){if (!A) return null;var E;if (!B&&A.firstChild) E=A.firstChild;else{if (D&&A==D) return null;E=A.nextSibling;if (!E&&(!D||D!=A.parentNode)) return this.GetNextSourceNode(A.parentNode,true,C,D);};if (C&&E&&E.nodeType!=C) return this.GetNextSourceNode(E,false,C,D);return E;},GetPreviousSourceNode:function(A,B,C,D){if (!A) return null;var E;if (!B&&A.lastChild) E=A.lastChild;else{if (D&&A==D) return null;E=A.previousSibling;if (!E&&(!D||D!=A.parentNode)) return this.GetPreviousSourceNode(A.parentNode,true,C,D);};if (C&&E&&E.nodeType!=C) return this.GetPreviousSourceNode(E,false,C,D);return E;},InsertAfterNode:function(A,B){return A.parentNode.insertBefore(B,A.nextSibling);},GetParents:function(A){var B=[];while (A){B.unshift(A);A=A.parentNode;};return B;},GetCommonParents:function(A,B){var C=this.GetParents(A);var D=this.GetParents(B);var E=[];for (var i=0;i<C.length;i++){if (C[i]==D[i]) E.push(C[i]);};return E;},GetCommonParentNode:function(A,B,C){var D={};if (!C.pop) C=[C];while (C.length>0) D[C.pop().toLowerCase()]=1;var E=this.GetCommonParents(A,B);var F=null;while ((F=E.pop())){if (D[F.nodeName.toLowerCase()]) return F;};return null;},GetIndexOf:function(A){var B=A.parentNode?A.parentNode.firstChild:null;var C=-1;while (B){C++;if (B==A) return C;B=B.nextSibling;};return-1;},PaddingNode:null,EnforcePaddingNode:function(A,B){try{if (!A||!A.body) return;}catch (e){return;};this.CheckAndRemovePaddingNode(A,B,true);try{if (A.body.lastChild&&(A.body.lastChild.nodeType!=1||A.body.lastChild.tagName.toLowerCase()==B.toLowerCase())) return;}catch (e){return;};var C=A.createElement(B);if (FCKBrowserInfo.IsGecko&&FCKListsLib.NonEmptyBlockElements[B]) FCKTools.AppendBogusBr(C);this.PaddingNode=C;if (A.body.childNodes.length==1&&A.body.firstChild.nodeType==1&&A.body.firstChild.tagName.toLowerCase()=='br'&&(A.body.firstChild.getAttribute('_moz_dirty')!=null||A.body.firstChild.getAttribute('type')=='_moz')) A.body.replaceChild(C,A.body.firstChild);else A.body.appendChild(C);},CheckAndRemovePaddingNode:function(A,B,C){var D=this.PaddingNode;if (!D) return;try{if (D.parentNode!=A.body||D.tagName.toLowerCase()!=B||(D.childNodes.length>1)||(D.firstChild&&D.firstChild.nodeValue!='\xa0'&&String(D.firstChild.tagName).toLowerCase()!='br')){this.PaddingNode=null;return;}}catch (e){this.PaddingNode=null;return;};if (!C){if (D.parentNode.childNodes.length>1) D.parentNode.removeChild(D);this.PaddingNode=null;}},HasAttribute:function(A,B){if (A.hasAttribute) return A.hasAttribute(B);else{var C=A.attributes[B];return (C!=undefined&&C.specified);}},HasAttributes:function(A){var B=A.attributes;for (var i=0;i<B.length;i++){if (FCKBrowserInfo.IsIE){var C=B[i].nodeName;if (C.StartsWith('_fck')){continue;};if (C=='class'){if (A.className.length>0) return true;continue;}};if (B[i].specified) return true;};return false;},RemoveAttribute:function(A,B){if (FCKBrowserInfo.IsIE&&B.toLowerCase()=='class') B='className';return A.removeAttribute(B,0);},RemoveAttributes:function (A,B){for (var i=0;i<B.length;i++) this.RemoveAttribute(A,B[i]);},GetAttributeValue:function(A,B){var C=B;if (typeof B=='string') B=A.attributes[B];else C=B.nodeName;if (B&&B.specified){if (C=='style') return A.style.cssText;else if (C=='class'||C.indexOf('on')==0) return B.nodeValue;else{return A.getAttribute(C,2);}};return null;},Contains:function(A,B){if (A.contains&&B.nodeType==1) return A.contains(B);while ((B=B.parentNode)){if (B==A) return true;};return false;},BreakParent:function(A,B,C){var D=C||new FCKDomRange(FCKTools.GetElementWindow(A));D.SetStart(A,4);D.SetEnd(B,4);var E=D.ExtractContents();D.InsertNode(A.parentNode.removeChild(A));E.InsertAfterNode(A);D.Release(!!C);},GetNodeAddress:function(A,B){var C=[];while (A&&A!=FCKTools.GetElementDocument(A).documentElement){var D=A.parentNode;var E=-1;for(var i=0;i<D.childNodes.length;i++){var F=D.childNodes[i];if (B===true&&F.nodeType==3&&F.previousSibling&&F.previousSibling.nodeType==3) continue;E++;if (D.childNodes[i]==A) break;};C.unshift(E);A=A.parentNode;};return C;},GetNodeFromAddress:function(A,B,C){var D=A.documentElement;for (var i=0;i<B.length;i++){var E=B[i];if (!C){D=D.childNodes[E];continue;};var F=-1;for (var j=0;j<D.childNodes.length;j++){var G=D.childNodes[j];if (C===true&&G.nodeType==3&&G.previousSibling&&G.previousSibling.nodeType==3) continue;F++;if (F==E){D=G;break;}}};return D;},CloneElement:function(A){A=A.cloneNode(false);A.removeAttribute('id',false);return A;},ClearElementJSProperty:function(A,B){if (FCKBrowserInfo.IsIE) A.removeAttribute(B);else delete A[B];},SetElementMarker:function (A,B,C,D){var E=String(parseInt(Math.random()*0xffffffff,10));B._FCKMarkerId=E;B[C]=D;if (!A[E]) A[E]={ 'element':B,'markers':{} };A[E]['markers'][C]=D;},ClearElementMarkers:function(A,B,C){var D=B._FCKMarkerId;if (!D) return;this.ClearElementJSProperty(B,'_FCKMarkerId');for (var j in A[D]['markers']) this.ClearElementJSProperty(B,j);if (C) delete A[D];},ClearAllMarkers:function(A){for (var i in A) this.ClearElementMarkers(A,A[i]['element'],true);},ListToArray:function(A,B,C,D,E){if (!A.nodeName.IEquals(['ul','ol'])) return [];if (!D) D=0;if (!C) C=[];for (var i=0;i<A.childNodes.length;i++){var F=A.childNodes[i];if (!F.nodeName.IEquals('li')) continue;var G={ 'parent':A,'indent':D,'contents':[] };if (!E){G.grandparent=A.parentNode;if (G.grandparent&&G.grandparent.nodeName.IEquals('li')) G.grandparent=G.grandparent.parentNode;}else G.grandparent=E;if (B) this.SetElementMarker(B,F,'_FCK_ListArray_Index',C.length);C.push(G);for (var j=0;j<F.childNodes.length;j++){var H=F.childNodes[j];if (H.nodeName.IEquals(['ul','ol'])) this.ListToArray(H,B,C,D+1,G.grandparent);else G.contents.push(H);}};return C;},ArrayToList:function(A,B,C){if (C==undefined) C=0;if (!A||A.length<C+1) return null;var D=FCKTools.GetElementDocument(A[C].parent);var E=D.createDocumentFragment();var F=null;var G=C;var H=Math.max(A[C].indent,0);var I=null;while (true){var J=A[G];if (J.indent==H){if (!F||A[G].parent.nodeName!=F.nodeName){F=A[G].parent.cloneNode(false);E.appendChild(F);};I=D.createElement('li');F.appendChild(I);for (var i=0;i<J.contents.length;i++) I.appendChild(J.contents[i].cloneNode(true));G++;}else if (J.indent==Math.max(H,0)+1){var K=this.ArrayToList(A,null,G);I.appendChild(K.listNode);G=K.nextIndex;}else if (J.indent==-1&&C==0&&J.grandparent){var I;if (J.grandparent.nodeName.IEquals(['ul','ol'])) I=D.createElement('li');else{if (FCKConfig.EnterMode.IEquals(['div','p'])&&!J.grandparent.nodeName.IEquals('td')) I=D.createElement(FCKConfig.EnterMode);else I=D.createDocumentFragment();};for (var i=0;i<J.contents.length;i++) I.appendChild(J.contents[i].cloneNode(true));if (I.nodeType==11){if (I.lastChild&&I.lastChild.getAttribute&&I.lastChild.getAttribute('type')=='_moz') I.removeChild(I.lastChild);I.appendChild(D.createElement('br'));};if (I.nodeName.IEquals(FCKConfig.EnterMode)&&I.firstChild){this.TrimNode(I);if (FCKListsLib.BlockBoundaries[I.firstChild.nodeName.toLowerCase()]){var M=D.createDocumentFragment();while (I.firstChild) M.appendChild(I.removeChild(I.firstChild));I=M;}};if (FCKBrowserInfo.IsGeckoLike&&I.nodeName.IEquals(['div','p'])) FCKTools.AppendBogusBr(I);E.appendChild(I);F=null;G++;}else return null;if (A.length<=G||Math.max(A[G].indent,0)<H){break;}};if (B){var N=E.firstChild;while (N){if (N.nodeType==1) this.ClearElementMarkers(B,N);N=this.GetNextSourceNode(N);}};return { 'listNode':E,'nextIndex':G };},GetNextSibling:function(A,B){A=A.nextSibling;while (A&&!B&&A.nodeType!=1&&(A.nodeType!=3||A.nodeValue.length==0)) A=A.nextSibling;return A;},GetPreviousSibling:function(A,B){A=A.previousSibling;while (A&&!B&&A.nodeType!=1&&(A.nodeType!=3||A.nodeValue.length==0)) A=A.previousSibling;return A;},CheckIsEmptyElement:function(A,B){var C=A.firstChild;var D;while (C){if (C.nodeType==1){if (D||!FCKListsLib.InlineNonEmptyElements[C.nodeName.toLowerCase()]) return false;if (!B||B(C)===true) D=C;}else if (C.nodeType==3&&C.nodeValue.length>0) return false;C=C.nextSibling;};return D?this.CheckIsEmptyElement(D,B):true;},SetElementStyles:function(A,B){var C=A.style;for (var D in B) C[D]=B[D];},SetOpacity:function(A,B){if (FCKBrowserInfo.IsIE){B=Math.round(B*100);A.style.filter=(B>100?'':'progid:DXImageTransform.Microsoft.Alpha(opacity='+B+')');}else A.style.opacity=B;},GetCurrentElementStyle:function(A,B){if (FCKBrowserInfo.IsIE) return A.currentStyle[B];else return A.ownerDocument.defaultView.getComputedStyle(A,'').getPropertyValue(B);},GetPositionedAncestor:function(A){var B=A;while (B!=FCKTools.GetElementDocument(B).documentElement){if (this.GetCurrentElementStyle(B,'position')!='static') return B;if (B==FCKTools.GetElementDocument(B).documentElement&&currentWindow!=w) B=currentWindow.frameElement;else B=B.parentNode;};return null;},ScrollIntoView:function(A,B){var C=FCKTools.GetElementWindow(A);var D=FCKTools.GetViewPaneSize(C).Height;var E=D*-1;if (B===false){E+=A.offsetHeight||0;E+=parseInt(this.GetCurrentElementStyle(A,'marginBottom')||0,10)||0;};var F=FCKTools.GetDocumentPosition(C,A);E+=F.y;var G=FCKTools.GetScrollPosition(C).Y;if (E>0&&(E>G||E<G-D)) C.scrollTo(0,E);},CheckIsEditable:function(A){var B=A.nodeName.toLowerCase();var C=FCK.DTD[B]||FCK.DTD.span;return (C['#']&&!FCKListsLib.NonEditableElements[B]);},GetSelectedDivContainers:function(){var A=[];var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.GetTouchedStartNode();var D=B.GetTouchedEndNode();var E=C;if (C==D){while (D.nodeType==1&&D.lastChild) D=D.lastChild;D=FCKDomTools.GetNextSourceNode(D);}while (E&&E!=D){if (E.nodeType!=3||!/^[ \t\n]*$/.test(E.nodeValue)){var F=new FCKElementPath(E);var G=F.BlockLimit;if (G&&G.nodeName.IEquals('div')&&A.IndexOf(G)==-1) A.push(G);};E=FCKDomTools.GetNextSourceNode(E);};return A;}};
+var FCKTools={};FCKTools.CreateBogusBR=function(A){var B=A.createElement('br');B.setAttribute('type','_moz');return B;};FCKTools.FixCssUrls=function(A,B){if (!A||A.length==0) return B;return B.replace(/url\s*\(([\s'"]*)(.*?)([\s"']*)\)/g,function(match,opener,path,closer){if (/^\/|^\w?:/.test(path)) return match;else return 'url('+opener+A+path+closer+')';});};FCKTools._GetUrlFixedCss=function(A,B){var C=A.match(/^([^|]+)\|([\s\S]*)/);if (C) return FCKTools.FixCssUrls(C[1],C[2]);else return A;};FCKTools.AppendStyleSheet=function(A,B){if (!B) return [];if (typeof(B)=='string'){if (/[\\\/\.][^{}]*$/.test(B)){return this.AppendStyleSheet(A,B.split(','));}else return [this.AppendStyleString(A,FCKTools._GetUrlFixedCss(B))];}else{var C=[];for (var i=0;i<B.length;i++) C.push(this._AppendStyleSheet(A,B[i]));return C;}};FCKTools.GetStyleHtml=(function(){var A=function(styleDef,markTemp){if (styleDef.length==0) return '';var B=markTemp?' _fcktemp="true"':'';return '<style type="text/css"'+B+'>'+styleDef+'</style>';};var C=function(cssFileUrl,markTemp){if (cssFileUrl.length==0) return '';var B=markTemp?' _fcktemp="true"':'';return '<link href="'+cssFileUrl+'" type="text/css" rel="stylesheet" '+B+'/>';};return function(cssFileOrArrayOrDef,markTemp){if (!cssFileOrArrayOrDef) return '';if (typeof(cssFileOrArrayOrDef)=='string'){if (/[\\\/\.][^{}]*$/.test(cssFileOrArrayOrDef)){return this.GetStyleHtml(cssFileOrArrayOrDef.split(','),markTemp);}else return A(this._GetUrlFixedCss(cssFileOrArrayOrDef),markTemp);}else{var E='';for (var i=0;i<cssFileOrArrayOrDef.length;i++) E+=C(cssFileOrArrayOrDef[i],markTemp);return E;}}})();FCKTools.GetElementDocument=function (A){return A.ownerDocument||A.document;};FCKTools.GetElementWindow=function(A){return this.GetDocumentWindow(this.GetElementDocument(A));};FCKTools.GetDocumentWindow=function(A){if (FCKBrowserInfo.IsSafari&&!A.parentWindow) this.FixDocumentParentWindow(window.top);return A.parentWindow||A.defaultView;};FCKTools.FixDocumentParentWindow=function(A){if (A.document) A.document.parentWindow=A;for (var i=0;i<A.frames.length;i++) FCKTools.FixDocumentParentWindow(A.frames[i]);};FCKTools.HTMLEncode=function(A){if (!A) return '';A=A.replace(/&/g,'&amp;');A=A.replace(/</g,'&lt;');A=A.replace(/>/g,'&gt;');return A;};FCKTools.HTMLDecode=function(A){if (!A) return '';A=A.replace(/&gt;/g,'>');A=A.replace(/&lt;/g,'<');A=A.replace(/&amp;/g,'&');return A;};FCKTools._ProcessLineBreaksForPMode=function(A,B,C,D,E){var F=0;var G="<p>";var H="</p>";var I="<br />";if (C){G="<li>";H="</li>";F=1;}while (D&&D!=A.FCK.EditorDocument.body){if (D.tagName.toLowerCase()=='p'){F=1;break;};D=D.parentNode;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};var n=B.charAt(i+1);if (n=='\r'){i++;n=B.charAt(i+1);};if (n=='\n'){i++;if (F) E.push(H);E.push(G);F=1;}else E.push(I);}};FCKTools._ProcessLineBreaksForDivMode=function(A,B,C,D,E){var F=0;var G="<div>";var H="</div>";if (C){G="<li>";H="</li>";F=1;}while (D&&D!=A.FCK.EditorDocument.body){if (D.tagName.toLowerCase()=='div'){F=1;break;};D=D.parentNode;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};if (F){if (E[E.length-1]==G){E.push("&nbsp;");};E.push(H);};E.push(G);F=1;};if (F) E.push(H);};FCKTools._ProcessLineBreaksForBrMode=function(A,B,C,D,E){var F=0;var G="<br />";var H="";if (C){G="<li>";H="</li>";F=1;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};if (F&&H.length) E.push (H);E.push(G);F=1;}};FCKTools.ProcessLineBreaks=function(A,B,C){var D=B.EnterMode.toLowerCase();var E=[];var F=0;var G=new A.FCKDomRange(A.FCK.EditorWindow);G.MoveToSelection();var H=G._Range.startContainer;while (H&&H.nodeType!=1) H=H.parentNode;if (H&&H.tagName.toLowerCase()=='li') F=1;if (D=='p') this._ProcessLineBreaksForPMode(A,C,F,H,E);else if (D=='div') this._ProcessLineBreaksForDivMode(A,C,F,H,E);else if (D=='br') this._ProcessLineBreaksForBrMode(A,C,F,H,E);return E.join("");};FCKTools.AddSelectOption=function(A,B,C){var D=FCKTools.GetElementDocument(A).createElement("OPTION");D.text=B;D.value=C;A.options.add(D);return D;};FCKTools.RunFunction=function(A,B,C,D){if (A) this.SetTimeout(A,0,B,C,D);};FCKTools.SetTimeout=function(A,B,C,D,E){return (E||window).setTimeout(function(){if (D) A.apply(C,[].concat(D));else A.apply(C);},B);};FCKTools.SetInterval=function(A,B,C,D,E){return (E||window).setInterval(function(){A.apply(C,D||[]);},B);};FCKTools.ConvertStyleSizeToHtml=function(A){return A.EndsWith('%')?A:parseInt(A,10);};FCKTools.ConvertHtmlSizeToStyle=function(A){return A.EndsWith('%')?A:(A+'px');};FCKTools.GetElementAscensor=function(A,B){var e=A;var C=","+B.toUpperCase()+",";while (e){if (C.indexOf(","+e.nodeName.toUpperCase()+",")!=-1) return e;e=e.parentNode;};return null;};FCKTools.CreateEventListener=function(A,B){var f=function(){var C=[];for (var i=0;i<arguments.length;i++) C.push(arguments[i]);A.apply(this,C.concat(B));};return f;};FCKTools.IsStrictMode=function(A){return ('CSS1Compat'==(A.compatMode||(FCKBrowserInfo.IsSafari?'CSS1Compat':null)));};FCKTools.ArgumentsToArray=function(A,B,C){B=B||0;C=C||A.length;var D=[];for (var i=B;i<B+C&&i<A.length;i++) D.push(A[i]);return D;};FCKTools.CloneObject=function(A){var B=function() {};B.prototype=A;return new B;};FCKTools.AppendBogusBr=function(A){if (!A) return;var B=this.GetLastItem(A.getElementsByTagName('br'));if (!B||(B.getAttribute('type',2)!='_moz'&&B.getAttribute('_moz_dirty')==null)){var C=this.GetElementDocument(A);if (FCKBrowserInfo.IsOpera) A.appendChild(C.createTextNode(''));else A.appendChild(this.CreateBogusBR(C));}};FCKTools.GetLastItem=function(A){if (A.length>0) return A[A.length-1];return null;};FCKTools.GetDocumentPosition=function(w,A){var x=0;var y=0;var B=A;var C=null;var D=FCKTools.GetElementWindow(B);while (B&&!(D==w&&(B==w.document.body||B==w.document.documentElement))){x+=B.offsetLeft-B.scrollLeft;y+=B.offsetTop-B.scrollTop;if (!FCKBrowserInfo.IsOpera){var E=C;while (E&&E!=B){x-=E.scrollLeft;y-=E.scrollTop;E=E.parentNode;}};C=B;if (B.offsetParent) B=B.offsetParent;else{if (D!=w){B=D.frameElement;C=null;if (B) D=B.contentWindow.parent;}else B=null;}};if (FCKDomTools.GetCurrentElementStyle(w.document.body,'position')!='static'||(FCKBrowserInfo.IsIE&&FCKDomTools.GetPositionedAncestor(A)==null)){x+=w.document.body.offsetLeft;y+=w.document.body.offsetTop;};return { "x":x,"y":y };};FCKTools.GetWindowPosition=function(w,A){var B=this.GetDocumentPosition(w,A);var C=FCKTools.GetScrollPosition(w);B.x-=C.X;B.y-=C.Y;return B;};FCKTools.ProtectFormStyles=function(A){if (!A||A.nodeType!=1||A.tagName.toLowerCase()!='form') return [];var B=[];var C=['style','className'];for (var i=0;i<C.length;i++){var D=C[i];if (A.elements.namedItem(D)){var E=A.elements.namedItem(D);B.push([E,E.nextSibling]);A.removeChild(E);}};return B;};FCKTools.RestoreFormStyles=function(A,B){if (!A||A.nodeType!=1||A.tagName.toLowerCase()!='form') return;if (B.length>0){for (var i=B.length-1;i>=0;i--){var C=B[i][0];var D=B[i][1];if (D) A.insertBefore(C,D);else A.appendChild(C);}}};FCKTools.GetNextNode=function(A,B){if (A.firstChild) return A.firstChild;else if (A.nextSibling) return A.nextSibling;else{var C=A.parentNode;while (C){if (C==B) return null;if (C.nextSibling) return C.nextSibling;else C=C.parentNode;}};return null;};FCKTools.GetNextTextNode=function(A,B,C){node=this.GetNextNode(A,B);if (C&&node&&C(node)) return null;while (node&&node.nodeType!=3){node=this.GetNextNode(node,B);if (C&&node&&C(node)) return null;};return node;};FCKTools.Merge=function(){var A=arguments;var o=A[0];for (var i=1;i<A.length;i++){var B=A[i];for (var p in B) o[p]=B[p];};return o;};FCKTools.IsArray=function(A){return (A instanceof Array);};FCKTools.AppendLengthProperty=function(A,B){var C=0;for (var n in A) C++;return A[B||'length']=C;};FCKTools.NormalizeCssText=function(A){var B=document.createElement('span');B.style.cssText=A;return B.style.cssText;};FCKTools.Bind=function(A,B){return function(){ return B.apply(A,arguments);};};FCKTools.GetVoidUrl=function(){if (FCK_IS_CUSTOM_DOMAIN) return "javascript: void( function(){document.open();document.write('<html><head><title></title></head><body></body></html>');document.domain = '"+FCK_RUNTIME_DOMAIN+"';document.close();}() ) ;";if (FCKBrowserInfo.IsIE){if (FCKBrowserInfo.IsIE7||!FCKBrowserInfo.IsIE6) return "";else return "javascript: '';";};return "javascript: void(0);";};FCKTools.ResetStyles=function(A){A.style.cssText='margin:0;padding:0;border:0;background-color:transparent;background-image:none;';};
+FCKTools.CancelEvent=function(e){if (e) e.preventDefault();};FCKTools.DisableSelection=function(A){if (FCKBrowserInfo.IsGecko) A.style.MozUserSelect='none';else if (FCKBrowserInfo.IsSafari) A.style.KhtmlUserSelect='none';else A.style.userSelect='none';};FCKTools._AppendStyleSheet=function(A,B){var e=A.createElement('LINK');e.rel='stylesheet';e.type='text/css';e.href=B;A.getElementsByTagName("HEAD")[0].appendChild(e);return e;};FCKTools.AppendStyleString=function(A,B){if (!B) return null;var e=A.createElement("STYLE");e.appendChild(A.createTextNode(B));A.getElementsByTagName("HEAD")[0].appendChild(e);return e;};FCKTools.ClearElementAttributes=function(A){for (var i=0;i<A.attributes.length;i++){A.removeAttribute(A.attributes[i].name,0);}};FCKTools.GetAllChildrenIds=function(A){var B=[];var C=function(parent){for (var i=0;i<parent.childNodes.length;i++){var D=parent.childNodes[i].id;if (D&&D.length>0) B[B.length]=D;C(parent.childNodes[i]);}};C(A);return B;};FCKTools.RemoveOuterTags=function(e){var A=e.ownerDocument.createDocumentFragment();for (var i=0;i<e.childNodes.length;i++) A.appendChild(e.childNodes[i].cloneNode(true));e.parentNode.replaceChild(A,e);};FCKTools.CreateXmlObject=function(A){switch (A){case 'XmlHttp':return new XMLHttpRequest();case 'DOMDocument':var B=(new DOMParser()).parseFromString('<tmp></tmp>','text/xml');FCKDomTools.RemoveNode(B.firstChild);return B;};return null;};FCKTools.GetScrollPosition=function(A){return { X:A.pageXOffset,Y:A.pageYOffset };};FCKTools.AddEventListener=function(A,B,C){A.addEventListener(B,C,false);};FCKTools.RemoveEventListener=function(A,B,C){A.removeEventListener(B,C,false);};FCKTools.AddEventListenerEx=function(A,B,C,D){A.addEventListener(B,function(e){C.apply(A,[e].concat(D||[]));},false);};FCKTools.GetViewPaneSize=function(A){return { Width:A.innerWidth,Height:A.innerHeight };};FCKTools.SaveStyles=function(A){var B=FCKTools.ProtectFormStyles(A);var C={};if (A.className.length>0){C.Class=A.className;A.className='';};var D=A.getAttribute('style');if (D&&D.length>0){C.Inline=D;A.setAttribute('style','',0);};FCKTools.RestoreFormStyles(A,B);return C;};FCKTools.RestoreStyles=function(A,B){var C=FCKTools.ProtectFormStyles(A);A.className=B.Class||'';if (B.Inline) A.setAttribute('style',B.Inline,0);else A.removeAttribute('style',0);FCKTools.RestoreFormStyles(A,C);};FCKTools.RegisterDollarFunction=function(A){A.$=function(id){return A.document.getElementById(id);};};FCKTools.AppendElement=function(A,B){return A.appendChild(A.ownerDocument.createElement(B));};FCKTools.GetElementPosition=function(A,B){var c={ X:0,Y:0 };var C=B||window;var D=FCKTools.GetElementWindow(A);var E=null;while (A){var F=D.getComputedStyle(A,'').position;if (F&&F!='static'&&A.style.zIndex!=FCKConfig.FloatingPanelsZIndex) break;c.X+=A.offsetLeft-A.scrollLeft;c.Y+=A.offsetTop-A.scrollTop;if (!FCKBrowserInfo.IsOpera){var G=E;while (G&&G!=A){c.X-=G.scrollLeft;c.Y-=G.scrollTop;G=G.parentNode;}};E=A;if (A.offsetParent) A=A.offsetParent;else{if (D!=C){A=D.frameElement;E=null;if (A) D=FCKTools.GetElementWindow(A);}else{c.X+=A.scrollLeft;c.Y+=A.scrollTop;break;}}};return c;};
+var FCKeditorAPI;function InitializeAPI(){var A=window.parent;if (!(FCKeditorAPI=A.FCKeditorAPI)){var B='window.FCKeditorAPI = {Version : "2.6.6",VersionBuild : "25427",Instances : window.FCKeditorAPI && window.FCKeditorAPI.Instances || {},GetInstance : function( name ){return this.Instances[ name ];},_FormSubmit : function(){for ( var name in FCKeditorAPI.Instances ){var oEditor = FCKeditorAPI.Instances[ name ] ;if ( oEditor.GetParentForm && oEditor.GetParentForm() == this )oEditor.UpdateLinkedField() ;}this._FCKOriginalSubmit() ;},_FunctionQueue : window.FCKeditorAPI && window.FCKeditorAPI._FunctionQueue || {Functions : new Array(),IsRunning : false,Add : function( f ){this.Functions.push( f );if ( !this.IsRunning )this.StartNext();},StartNext : function(){var aQueue = this.Functions ;if ( aQueue.length > 0 ){this.IsRunning = true;aQueue[0].call();}else this.IsRunning = false;},Remove : function( f ){var aQueue = this.Functions;var i = 0, fFunc;while( (fFunc = aQueue[ i ]) ){if ( fFunc == f )aQueue.splice( i,1 );i++ ;}this.StartNext();}}}';if (A.execScript) A.execScript(B,'JavaScript');else{if (FCKBrowserInfo.IsGecko10){eval.call(A,B);}else if(FCKBrowserInfo.IsAIR){FCKAdobeAIR.FCKeditorAPI_Evaluate(A,B);}else if (FCKBrowserInfo.IsSafari){var C=A.document;var D=C.createElement('script');D.appendChild(C.createTextNode(B));C.documentElement.appendChild(D);}else A.eval(B);};FCKeditorAPI=A.FCKeditorAPI;FCKeditorAPI.__Instances=FCKeditorAPI.Instances;};FCKeditorAPI.Instances[FCK.Name]=FCK;};function _AttachFormSubmitToAPI(){var A=FCK.GetParentForm();if (A){FCKTools.AddEventListener(A,'submit',FCK.UpdateLinkedField);if (!A._FCKOriginalSubmit&&(typeof(A.submit)=='function'||(!A.submit.tagName&&!A.submit.length))){A._FCKOriginalSubmit=A.submit;A.submit=FCKeditorAPI._FormSubmit;}}};function FCKeditorAPI_Cleanup(){if (window.FCKConfig&&FCKConfig.MsWebBrowserControlCompat&&!window.FCKUnloadFlag) return;delete FCKeditorAPI.Instances[FCK.Name];};function FCKeditorAPI_ConfirmCleanup(){if (window.FCKConfig&&FCKConfig.MsWebBrowserControlCompat) window.FCKUnloadFlag=true;};FCKTools.AddEventListener(window,'unload',FCKeditorAPI_Cleanup);FCKTools.AddEventListener(window,'beforeunload',FCKeditorAPI_ConfirmCleanup);
+var FCKImagePreloader=function(){this._Images=[];};FCKImagePreloader.prototype={AddImages:function(A){if (typeof(A)=='string') A=A.split(';');this._Images=this._Images.concat(A);},Start:function(){var A=this._Images;this._PreloadCount=A.length;for (var i=0;i<A.length;i++){var B=document.createElement('img');FCKTools.AddEventListenerEx(B,'load',_FCKImagePreloader_OnImage,this);FCKTools.AddEventListenerEx(B,'error',_FCKImagePreloader_OnImage,this);B.src=A[i];_FCKImagePreloader_ImageCache.push(B);}}};var _FCKImagePreloader_ImageCache=[];function _FCKImagePreloader_OnImage(A,B){if ((--B._PreloadCount)==0&&B.OnComplete) B.OnComplete();};
+var FCKRegexLib={AposEntity:/&apos;/gi,ObjectElements:/^(?:IMG|TABLE|TR|TD|TH|INPUT|SELECT|TEXTAREA|HR|OBJECT|A|UL|OL|LI)$/i,NamedCommands:/^(?:Cut|Copy|Paste|Print|SelectAll|RemoveFormat|Unlink|Undo|Redo|Bold|Italic|Underline|StrikeThrough|Subscript|Superscript|JustifyLeft|JustifyCenter|JustifyRight|JustifyFull|Outdent|Indent|InsertOrderedList|InsertUnorderedList|InsertHorizontalRule)$/i,BeforeBody:/(^[\s\S]*\<body[^\>]*\>)/i,AfterBody:/(\<\/body\>[\s\S]*$)/i,ToReplace:/___fcktoreplace:([\w]+)/ig,MetaHttpEquiv:/http-equiv\s*=\s*["']?([^"' ]+)/i,HasBaseTag:/<base /i,HasBodyTag:/<body[\s|>]/i,HtmlOpener:/<html\s?[^>]*>/i,HeadOpener:/<head\s?[^>]*>/i,HeadCloser:/<\/head\s*>/i,FCK_Class:/\s*FCK__[^ ]*(?=\s+|$)/,ElementName:/(^[a-z_:][\w.\-:]*\w$)|(^[a-z_]$)/,ForceSimpleAmpersand:/___FCKAmp___/g,SpaceNoClose:/\/>/g,EmptyParagraph:/^<(p|div|address|h\d|center)(?=[ >])[^>]*>\s*(<\/\1>)?$/,EmptyOutParagraph:/^<(p|div|address|h\d|center)(?=[ >])[^>]*>(?:\s*|&nbsp;|&#160;)(<\/\1>)?$/,TagBody:/></,GeckoEntitiesMarker:/#\?-\:/g,ProtectUrlsImg:/<img(?=\s).*?\ssrc=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsA:/<a(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsArea:/<area(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,Html4DocType:/HTML 4\.0 Transitional/i,DocTypeTag:/<!DOCTYPE[^>]*>/i,HtmlDocType:/DTD HTML/,TagsWithEvent:/<[^\>]+ on\w+[\s\r\n]*=[\s\r\n]*?('|")[\s\S]+?\>/g,EventAttributes:/\s(on\w+)[\s\r\n]*=[\s\r\n]*?('|")([\s\S]*?)\2/g,ProtectedEvents:/\s\w+_fckprotectedatt="([^"]+)"/g,StyleProperties:/\S+\s*:/g,InvalidSelfCloseTags:/(<(?!base|meta|link|hr|br|param|img|area|input)([a-zA-Z0-9:]+)[^>]*)\/>/gi,StyleVariableAttName:/#\(\s*("|')(.+?)\1[^\)]*\s*\)/g,RegExp:/^\/(.*)\/([gim]*)$/,HtmlTag:/<[^\s<>](?:"[^"]*"|'[^']*'|[^<])*>/};
+var FCKListsLib={BlockElements:{ address:1,blockquote:1,center:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,marquee:1,noscript:1,ol:1,p:1,pre:1,script:1,table:1,ul:1 },NonEmptyBlockElements:{ p:1,div:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,address:1,pre:1,ol:1,ul:1,li:1,td:1,th:1 },InlineChildReqElements:{ abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },InlineNonEmptyElements:{ a:1,abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },EmptyElements:{ base:1,col:1,meta:1,link:1,hr:1,br:1,param:1,img:1,area:1,input:1 },PathBlockElements:{ address:1,blockquote:1,dl:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,li:1,dt:1,de:1 },PathBlockLimitElements:{ body:1,div:1,td:1,th:1,caption:1,form:1 },StyleBlockElements:{ address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 },StyleObjectElements:{ img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 },NonEditableElements:{ button:1,option:1,script:1,iframe:1,textarea:1,object:1,embed:1,map:1,applet:1 },BlockBoundaries:{ p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,address:1,pre:1,ol:1,ul:1,li:1,dt:1,de:1,table:1,thead:1,tbody:1,tfoot:1,tr:1,th:1,td:1,caption:1,col:1,colgroup:1,blockquote:1,body:1 },ListBoundaries:{ p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,address:1,pre:1,ol:1,ul:1,li:1,dt:1,de:1,table:1,thead:1,tbody:1,tfoot:1,tr:1,th:1,td:1,caption:1,col:1,colgroup:1,blockquote:1,body:1,br:1 }};
+var FCKLanguageManager=FCK.Language={AvailableLanguages:{af:'Afrikaans',ar:'Arabic',bg:'Bulgarian',bn:'Bengali/Bangla',bs:'Bosnian',ca:'Catalan',cs:'Czech',da:'Danish',de:'German',el:'Greek',en:'English','en-au':'English (Australia)','en-ca':'English (Canadian)','en-uk':'English (United Kingdom)',eo:'Esperanto',es:'Spanish',et:'Estonian',eu:'Basque',fa:'Persian',fi:'Finnish',fo:'Faroese',fr:'French','fr-ca':'French (Canada)',gl:'Galician',gu:'Gujarati',he:'Hebrew',hi:'Hindi',hr:'Croatian',hu:'Hungarian',is:'Icelandic',it:'Italian',ja:'Japanese',km:'Khmer',ko:'Korean',lt:'Lithuanian',lv:'Latvian',mn:'Mongolian',ms:'Malay',nb:'Norwegian Bokmal',nl:'Dutch',no:'Norwegian',pl:'Polish',pt:'Portuguese (Portugal)','pt-br':'Portuguese (Brazil)',ro:'Romanian',ru:'Russian',sk:'Slovak',sl:'Slovenian',sr:'Serbian (Cyrillic)','sr-latn':'Serbian (Latin)',sv:'Swedish',th:'Thai',tr:'Turkish',uk:'Ukrainian',vi:'Vietnamese',zh:'Chinese Traditional','zh-cn':'Chinese Simplified'},GetActiveLanguage:function(){if (FCKConfig.AutoDetectLanguage){var A;if (navigator.userLanguage) A=navigator.userLanguage.toLowerCase();else if (navigator.language) A=navigator.language.toLowerCase();else{return FCKConfig.DefaultLanguage;};if (A.length>=5){A=A.substr(0,5);if (this.AvailableLanguages[A]) return A;};if (A.length>=2){A=A.substr(0,2);if (this.AvailableLanguages[A]) return A;}};return this.DefaultLanguage;},TranslateElements:function(A,B,C,D){var e=A.getElementsByTagName(B);var E,s;for (var i=0;i<e.length;i++){if ((E=e[i].getAttribute('fckLang'))){if ((s=FCKLang[E])){if (D) s=FCKTools.HTMLEncode(s);e[i][C]=s;}}}},TranslatePage:function(A){this.TranslateElements(A,'INPUT','value');this.TranslateElements(A,'SPAN','innerHTML');this.TranslateElements(A,'LABEL','innerHTML');this.TranslateElements(A,'OPTION','innerHTML',true);this.TranslateElements(A,'LEGEND','innerHTML');},Initialize:function(){if (this.AvailableLanguages[FCKConfig.DefaultLanguage]) this.DefaultLanguage=FCKConfig.DefaultLanguage;else this.DefaultLanguage='en';this.ActiveLanguage={};this.ActiveLanguage.Code=this.GetActiveLanguage();this.ActiveLanguage.Name=this.AvailableLanguages[this.ActiveLanguage.Code];}};
+var FCKXHtmlEntities={};FCKXHtmlEntities.Initialize=function(){if (FCKXHtmlEntities.Entities) return;var A='';var B,e;if (FCKConfig.ProcessHTMLEntities){FCKXHtmlEntities.Entities={' ':'nbsp','¡':'iexcl','¢':'cent','£':'pound','¤':'curren','Â¥':'yen','¦':'brvbar','§':'sect','¨':'uml','©':'copy','ª':'ordf','«':'laquo','¬':'not','­':'shy','®':'reg','¯':'macr','°':'deg','±':'plusmn','²':'sup2','³':'sup3','´':'acute','µ':'micro','¶':'para','·':'middot','¸':'cedil','¹':'sup1','º':'ordm','»':'raquo','¼':'frac14','½':'frac12','¾':'frac34','¿':'iquest','×':'times','÷':'divide','Æ’':'fnof','•':'bull','…':'hellip','′':'prime','″':'Prime','‾':'oline','â„':'frasl','℘':'weierp','â„‘':'image','â„œ':'real','â„¢':'trade','ℵ':'alefsym','â†':'larr','↑':'uarr','→':'rarr','↓':'darr','↔':'harr','↵':'crarr','â‡':'lArr','⇑':'uArr','⇒':'rArr','⇓':'dArr','⇔':'hArr','∀':'forall','∂':'part','∃':'exist','∅':'empty','∇':'nabla','∈':'isin','∉':'notin','∋':'ni','âˆ':'prod','∑':'sum','−':'minus','∗':'lowast','√':'radic','âˆ':'prop','∞':'infin','∠':'ang','∧':'and','∨':'or','∩':'cap','∪':'cup','∫':'int','∴':'there4','∼':'sim','≅':'cong','≈':'asymp','≠':'ne','≡':'equiv','≤':'le','≥':'ge','⊂':'sub','⊃':'sup','⊄':'nsub','⊆':'sube','⊇':'supe','⊕':'oplus','⊗':'otimes','⊥':'perp','â‹…':'sdot','\u2308':'lceil','\u2309':'rceil','\u230a':'lfloor','\u230b':'rfloor','\u2329':'lang','\u232a':'rang','â—Š':'loz','â™ ':'spades','♣':'clubs','♥':'hearts','♦':'diams','"':'quot','>':'gt','ˆ':'circ','Ëœ':'tilde',' ':'ensp',' ':'emsp',' ':'thinsp','‌':'zwnj','â€':'zwj','‎':'lrm','â€':'rlm','–':'ndash','—':'mdash','‘':'lsquo','’':'rsquo','‚':'sbquo','“':'ldquo','â€':'rdquo','„':'bdquo','†':'dagger','‡':'Dagger','‰':'permil','‹':'lsaquo','›':'rsaquo','€':'euro'};for (e in FCKXHtmlEntities.Entities) A+=e;if (FCKConfig.IncludeLatinEntities){B={'À':'Agrave','Ã':'Aacute','Â':'Acirc','Ã':'Atilde','Ä':'Auml','Ã…':'Aring','Æ':'AElig','Ç':'Ccedil','È':'Egrave','É':'Eacute','Ê':'Ecirc','Ë':'Euml','ÃŒ':'Igrave','Ã':'Iacute','ÃŽ':'Icirc','Ã':'Iuml','Ã':'ETH','Ñ':'Ntilde','Ã’':'Ograve','Ó':'Oacute','Ô':'Ocirc','Õ':'Otilde','Ö':'Ouml','Ø':'Oslash','Ù':'Ugrave','Ú':'Uacute','Û':'Ucirc','Ãœ':'Uuml','Ã':'Yacute','Þ':'THORN','ß':'szlig','à':'agrave','á':'aacute','â':'acirc','ã':'atilde','ä':'auml','Ã¥':'aring','æ':'aelig','ç':'ccedil','è':'egrave','é':'eacute','ê':'ecirc','ë':'euml','ì':'igrave','í':'iacute','î':'icirc','ï':'iuml','ð':'eth','ñ':'ntilde','ò':'ograve','ó':'oacute','ô':'ocirc','õ':'otilde','ö':'ouml','ø':'oslash','ù':'ugrave','ú':'uacute','û':'ucirc','ü':'uuml','ý':'yacute','þ':'thorn','ÿ':'yuml','Å’':'OElig','Å“':'oelig','Å ':'Scaron','Å¡':'scaron','Ÿ':'Yuml'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;};if (FCKConfig.IncludeGreekEntities){B={'Α':'Alpha','Î’':'Beta','Γ':'Gamma','Δ':'Delta','Ε':'Epsilon','Ζ':'Zeta','Η':'Eta','Θ':'Theta','Ι':'Iota','Κ':'Kappa','Λ':'Lambda','Îœ':'Mu','Î':'Nu','Ξ':'Xi','Ο':'Omicron','Π':'Pi','Ρ':'Rho','Σ':'Sigma','Τ':'Tau','Î¥':'Upsilon','Φ':'Phi','Χ':'Chi','Ψ':'Psi','Ω':'Omega','α':'alpha','β':'beta','γ':'gamma','δ':'delta','ε':'epsilon','ζ':'zeta','η':'eta','θ':'theta','ι':'iota','κ':'kappa','λ':'lambda','μ':'mu','ν':'nu','ξ':'xi','ο':'omicron','Ï€':'pi','Ï':'rho','Ï‚':'sigmaf','σ':'sigma','Ï„':'tau','Ï…':'upsilon','φ':'phi','χ':'chi','ψ':'psi','ω':'omega','\u03d1':'thetasym','\u03d2':'upsih','\u03d6':'piv'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;}}else{FCKXHtmlEntities.Entities={'>':'gt'};A='>';A+=' ';};var C='['+A+']';if (FCKConfig.ProcessNumericEntities) C='[^ -~]|'+C;var D=FCKConfig.AdditionalNumericEntities;if (D&&D.length>0) C+='|'+FCKConfig.AdditionalNumericEntities;FCKXHtmlEntities.EntitiesRegex=new RegExp(C,'g');};
+var FCKXHtml={};FCKXHtml.CurrentJobNum=0;FCKXHtml.GetXHTML=function(A,B,C){FCKDomTools.CheckAndRemovePaddingNode(FCKTools.GetElementDocument(A),FCKConfig.EnterMode);FCKXHtmlEntities.Initialize();this._NbspEntity=(FCKConfig.ProcessHTMLEntities?'nbsp':'#160');var D=FCK.IsDirty();FCKXHtml.SpecialBlocks=[];this.XML=FCKTools.CreateXmlObject('DOMDocument');this.MainNode=this.XML.appendChild(this.XML.createElement('xhtml'));FCKXHtml.CurrentJobNum++;if (B) this._AppendNode(this.MainNode,A);else this._AppendChildNodes(this.MainNode,A,false);if (FCKBrowserInfo.IsIE) FCKXHtml._RemoveXHtmlJobProperties(A);var E=this._GetMainXmlString();this.XML=null;if (FCKBrowserInfo.IsSafari) E=E.replace(/^<xhtml.*?>/,'<xhtml>');E=E.substr(7,E.length-15).Trim();if (FCKConfig.DocType.length>0&&FCKRegexLib.HtmlDocType.test(FCKConfig.DocType)) E=E.replace(FCKRegexLib.SpaceNoClose,'>');else E=E.replace(FCKRegexLib.SpaceNoClose,' />');if (FCKConfig.ForceSimpleAmpersand) E=E.replace(FCKRegexLib.ForceSimpleAmpersand,'&');if (C) E=FCKCodeFormatter.Format(E);for (var i=0;i<FCKXHtml.SpecialBlocks.length;i++){var F=new RegExp('___FCKsi___'+i);E=E.replace(F,FCKXHtml.SpecialBlocks[i]);};E=E.replace(FCKRegexLib.GeckoEntitiesMarker,'&');if (!D) FCK.ResetIsDirty();FCKDomTools.EnforcePaddingNode(FCKTools.GetElementDocument(A),FCKConfig.EnterMode);return E;};FCKXHtml._AppendAttribute=function(A,B,C){try{if (C==undefined||C==null) C='';else if (C.replace){if (FCKConfig.ForceSimpleAmpersand) C=C.replace(/&/g,'___FCKAmp___');C=C.replace(FCKXHtmlEntities.EntitiesRegex,FCKXHtml_GetEntity);};var D=this.XML.createAttribute(B);D.value=C;A.attributes.setNamedItem(D);}catch (e){}};FCKXHtml._AppendChildNodes=function(A,B,C){var D=B.firstChild;while (D){this._AppendNode(A,D);D=D.nextSibling;};if (C&&B.tagName&&B.tagName.toLowerCase()!='pre'){FCKDomTools.TrimNode(A);if (FCKConfig.FillEmptyBlocks){var E=A.lastChild;if (E&&E.nodeType==1&&E.nodeName=='br') this._AppendEntity(A,this._NbspEntity);}};if (A.childNodes.length==0){if (C&&FCKConfig.FillEmptyBlocks){this._AppendEntity(A,this._NbspEntity);return A;};var F=A.nodeName;if (FCKListsLib.InlineChildReqElements[F]) return null;if (!FCKListsLib.EmptyElements[F]) A.appendChild(this.XML.createTextNode(''));};return A;};FCKXHtml._AppendNode=function(A,B){if (!B) return false;switch (B.nodeType){case 1:if (FCKBrowserInfo.IsGecko&&B.tagName.toLowerCase()=='br'&&B.parentNode.tagName.toLowerCase()=='pre'){var C='\r';if (B==B.parentNode.firstChild) C+='\r';return FCKXHtml._AppendNode(A,this.XML.createTextNode(C));};if (B.getAttribute('_fckfakelement')) return FCKXHtml._AppendNode(A,FCK.GetRealElement(B));if (FCKBrowserInfo.IsGecko&&(B.hasAttribute('_moz_editor_bogus_node')||B.getAttribute('type')=='_moz')){if (B.nextSibling) return false;else{B.removeAttribute('_moz_editor_bogus_node');B.removeAttribute('type');}};if (B.getAttribute('_fcktemp')) return false;var D=B.tagName.toLowerCase();if (FCKBrowserInfo.IsIE){if (B.scopeName&&B.scopeName!='HTML'&&B.scopeName!='FCK') D=B.scopeName.toLowerCase()+':'+D;}else{if (D.StartsWith('fck:')) D=D.Remove(0,4);};if (!FCKRegexLib.ElementName.test(D)) return false;if (B._fckxhtmljob&&B._fckxhtmljob==FCKXHtml.CurrentJobNum) return false;var E=this.XML.createElement(D);FCKXHtml._AppendAttributes(A,B,E,D);B._fckxhtmljob=FCKXHtml.CurrentJobNum;var F=FCKXHtml.TagProcessors[D];if (F) E=F(E,B,A);else E=this._AppendChildNodes(E,B,Boolean(FCKListsLib.NonEmptyBlockElements[D]));if (!E) return false;A.appendChild(E);break;case 3:if (B.parentNode&&B.parentNode.nodeName.IEquals('pre')) return this._AppendTextNode(A,B.nodeValue);return this._AppendTextNode(A,B.nodeValue.ReplaceNewLineChars(' '));case 8:if (FCKBrowserInfo.IsIE&&!B.innerHTML) break;try { A.appendChild(this.XML.createComment(B.nodeValue));}catch (e) {};break;default:A.appendChild(this.XML.createComment("Element not supported - Type: "+B.nodeType+" Name: "+B.nodeName));break;};return true;};FCKXHtml._AppendSpecialItem=function(A){return '___FCKsi___'+(FCKXHtml.SpecialBlocks.push(A)-1);};FCKXHtml._AppendEntity=function(A,B){A.appendChild(this.XML.createTextNode('#?-:'+B+';'));};FCKXHtml._AppendTextNode=function(A,B){var C=B.length>0;if (C) A.appendChild(this.XML.createTextNode(B.replace(FCKXHtmlEntities.EntitiesRegex,FCKXHtml_GetEntity)));return C;};function FCKXHtml_GetEntity(A){var B=FCKXHtmlEntities.Entities[A]||('#'+A.charCodeAt(0));return '#?-:'+B+';';};FCKXHtml.TagProcessors={a:function(A,B){if (B.innerHTML.Trim().length==0&&!B.name) return false;var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'href',C);if (FCKBrowserInfo.IsIE){if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);};A=FCKXHtml._AppendChildNodes(A,B,false);return A;},area:function(A,B){var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'href',C);if (FCKBrowserInfo.IsIE){if (!A.attributes.getNamedItem('coords')){var D=B.getAttribute('coords',2);if (D&&D!='0,0,0') FCKXHtml._AppendAttribute(A,'coords',D);};if (!A.attributes.getNamedItem('shape')){var E=B.getAttribute('shape',2);if (E&&E.length>0) FCKXHtml._AppendAttribute(A,'shape',E.toLowerCase());}};return A;},body:function(A,B){A=FCKXHtml._AppendChildNodes(A,B,false);A.removeAttribute('spellcheck');return A;},iframe:function(A,B){var C=B.innerHTML;if (FCKBrowserInfo.IsGecko) C=FCKTools.HTMLDecode(C);C=C.replace(/\s_fcksavedurl="[^"]*"/g,'');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(C)));return A;},img:function(A,B){if (!A.attributes.getNamedItem('alt')) FCKXHtml._AppendAttribute(A,'alt','');var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'src',C);if (B.style.width) A.removeAttribute('width');if (B.style.height) A.removeAttribute('height');return A;},li:function(A,B,C){if (C.nodeName.IEquals(['ul','ol'])) return FCKXHtml._AppendChildNodes(A,B,true);var D=FCKXHtml.XML.createElement('ul');B._fckxhtmljob=null;do{FCKXHtml._AppendNode(D,B);do{B=FCKDomTools.GetNextSibling(B);} while (B&&B.nodeType==3&&B.nodeValue.Trim().length==0)} while (B&&B.nodeName.toLowerCase()=='li') return D;},ol:function(A,B,C){if (B.innerHTML.Trim().length==0) return false;var D=C.lastChild;if (D&&D.nodeType==3) D=D.previousSibling;if (D&&D.nodeName.toUpperCase()=='LI'){B._fckxhtmljob=null;FCKXHtml._AppendNode(D,B);return false;};A=FCKXHtml._AppendChildNodes(A,B);return A;},pre:function (A,B){var C=B.firstChild;if (C&&C.nodeType==3) A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem('\r\n')));FCKXHtml._AppendChildNodes(A,B,true);return A;},script:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/javascript');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(B.text)));return A;},span:function(A,B){if (B.innerHTML.length==0) return false;A=FCKXHtml._AppendChildNodes(A,B,false);return A;},style:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/css');var C=B.innerHTML;if (FCKBrowserInfo.IsIE) C=C.replace(/^(\r\n|\n|\r)/,'');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(C)));return A;},title:function(A,B){A.appendChild(FCKXHtml.XML.createTextNode(FCK.EditorDocument.title));return A;}};FCKXHtml.TagProcessors.ul=FCKXHtml.TagProcessors.ol;
+FCKXHtml._GetMainXmlString=function(){return (new XMLSerializer()).serializeToString(this.MainNode);};FCKXHtml._AppendAttributes=function(A,B,C){var D=B.attributes;for (var n=0;n<D.length;n++){var E=D[n];if (E.specified){var F=E.nodeName.toLowerCase();var G;if (F.StartsWith('_fck')) continue;else if (F.indexOf('_moz')==0) continue;else if (F=='class'){G=E.nodeValue.replace(FCKRegexLib.FCK_Class,'');if (G.length==0) continue;}else if (E.nodeValue===true) G=F;else G=B.getAttribute(F,2);this._AppendAttribute(C,F,G);}}};if (FCKBrowserInfo.IsOpera){FCKXHtml.TagProcessors['head']=function(A,B){FCKXHtml.XML._HeadElement=A;A=FCKXHtml._AppendChildNodes(A,B,true);return A;};FCKXHtml.TagProcessors['meta']=function(A,B,C){if (B.parentNode.nodeName.toLowerCase()!='head'){var D=FCKXHtml.XML._HeadElement;if (D&&C!=D){delete B._fckxhtmljob;FCKXHtml._AppendNode(D,B);return null;}};return A;}};if (FCKBrowserInfo.IsGecko){FCKXHtml.TagProcessors['link']=function(A,B){if (B.href.substr(0,9).toLowerCase()=='chrome://') return false;return A;}};
+var FCKCodeFormatter={};FCKCodeFormatter.Init=function(){var A=this.Regex={};A.BlocksOpener=/\<(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DL|DT|DD|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi;A.BlocksCloser=/\<\/(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DL|DT|DD|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi;A.NewLineTags=/\<(BR|HR)[^\>]*\>/gi;A.MainTags=/\<\/?(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR)[^\>]*\>/gi;A.LineSplitter=/\s*\n+\s*/g;A.IncreaseIndent=/^\<(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL|DL)[ \/\>]/i;A.DecreaseIndent=/^\<\/(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL|DL)[ \>]/i;A.FormatIndentatorRemove=new RegExp('^'+FCKConfig.FormatIndentator);A.ProtectedTags=/(<PRE[^>]*>)([\s\S]*?)(<\/PRE>)/gi;};FCKCodeFormatter._ProtectData=function(A,B,C,D){return B+'___FCKpd___'+(FCKCodeFormatter.ProtectedData.push(C)-1)+D;};FCKCodeFormatter.Format=function(A){if (!this.Regex) this.Init();FCKCodeFormatter.ProtectedData=[];var B=A.replace(this.Regex.ProtectedTags,FCKCodeFormatter._ProtectData);B=B.replace(this.Regex.BlocksOpener,'\n$&');B=B.replace(this.Regex.BlocksCloser,'$&\n');B=B.replace(this.Regex.NewLineTags,'$&\n');B=B.replace(this.Regex.MainTags,'\n$&\n');var C='';var D=B.split(this.Regex.LineSplitter);B='';for (var i=0;i<D.length;i++){var E=D[i];if (E.length==0) continue;if (this.Regex.DecreaseIndent.test(E)) C=C.replace(this.Regex.FormatIndentatorRemove,'');B+=C+E+'\n';if (this.Regex.IncreaseIndent.test(E)) C+=FCKConfig.FormatIndentator;};for (var j=0;j<FCKCodeFormatter.ProtectedData.length;j++){var F=new RegExp('___FCKpd___'+j);B=B.replace(F,FCKCodeFormatter.ProtectedData[j].replace(/\$/g,'$$$$'));};return B.Trim();};
+var FCKUndo={};FCKUndo.SavedData=[];FCKUndo.CurrentIndex=-1;FCKUndo.TypesCount=0;FCKUndo.Changed=false;FCKUndo.MaxTypes=25;FCKUndo.Typing=false;FCKUndo.SaveLocked=false;FCKUndo._GetBookmark=function(){FCKSelection.Restore();var A=new FCKDomRange(FCK.EditorWindow);try{A.MoveToSelection();}catch (e){return null;};if (FCKBrowserInfo.IsIE){var B=A.CreateBookmark();var C=FCK.EditorDocument.body.innerHTML;A.MoveToBookmark(B);return [B,C];};return A.CreateBookmark2();};FCKUndo._SelectBookmark=function(A){if (!A) return;var B=new FCKDomRange(FCK.EditorWindow);if (A instanceof Object){if (FCKBrowserInfo.IsIE) B.MoveToBookmark(A[0]);else B.MoveToBookmark2(A);try{B.Select();}catch (e){B.MoveToPosition(FCK.EditorDocument.body,4);B.Select();}}};FCKUndo._CompareCursors=function(A,B){for (var i=0;i<Math.min(A.length,B.length);i++){if (A[i]<B[i]) return-1;else if (A[i]>B[i]) return 1;};if (A.length<B.length) return-1;else if (A.length>B.length) return 1;return 0;};FCKUndo._CheckIsBookmarksEqual=function(A,B){if (!(A&&B)) return false;if (FCKBrowserInfo.IsIE){var C=A[1].search(A[0].StartId);var D=B[1].search(B[0].StartId);var E=A[1].search(A[0].EndId);var F=B[1].search(B[0].EndId);return C==D&&E==F;}else{return this._CompareCursors(A.Start,B.Start)==0&&this._CompareCursors(A.End,B.End)==0;}};FCKUndo.SaveUndoStep=function(){if (FCK.EditMode!=0||this.SaveLocked) return;if (this.SavedData.length) this.Changed=true;var A=FCK.EditorDocument.body.innerHTML;var B=this._GetBookmark();this.SavedData=this.SavedData.slice(0,this.CurrentIndex+1);if (this.CurrentIndex>0&&A==this.SavedData[this.CurrentIndex][0]&&this._CheckIsBookmarksEqual(B,this.SavedData[this.CurrentIndex][1])) return;else if (this.CurrentIndex==0&&this.SavedData.length&&A==this.SavedData[0][0]){this.SavedData[0][1]=B;return;};if (this.CurrentIndex+1>=FCKConfig.MaxUndoLevels) this.SavedData.shift();else this.CurrentIndex++;this.SavedData[this.CurrentIndex]=[A,B];FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.CheckUndoState=function(){return (this.Changed||this.CurrentIndex>0);};FCKUndo.CheckRedoState=function(){return (this.CurrentIndex<(this.SavedData.length-1));};FCKUndo.Undo=function(){if (this.CheckUndoState()){if (this.CurrentIndex==(this.SavedData.length-1)){this.SaveUndoStep();};this._ApplyUndoLevel(--this.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo.Redo=function(){if (this.CheckRedoState()){this._ApplyUndoLevel(++this.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo._ApplyUndoLevel=function(A){var B=this.SavedData[A];if (!B) return;if (FCKBrowserInfo.IsIE){if (B[1]&&B[1][1]) FCK.SetInnerHtml(B[1][1]);else FCK.SetInnerHtml(B[0]);}else FCK.EditorDocument.body.innerHTML=B[0];this._SelectBookmark(B[1]);this.TypesCount=0;this.Changed=false;this.Typing=false;};
+var FCKEditingArea=function(A){this.TargetElement=A;this.Mode=0;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKEditingArea_Cleanup);};FCKEditingArea.prototype.Start=function(A,B){var C=this.TargetElement;var D=FCKTools.GetElementDocument(C);while(C.firstChild) C.removeChild(C.firstChild);if (this.Mode==0){if (FCK_IS_CUSTOM_DOMAIN) A='<script>document.domain="'+FCK_RUNTIME_DOMAIN+'";</script>'+A;if (FCKBrowserInfo.IsIE) A=A.replace(/(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi,'$1></base>');else if (!B){var E=A.match(FCKRegexLib.BeforeBody);var F=A.match(FCKRegexLib.AfterBody);if (E&&F){var G=A.substr(E[1].length,A.length-E[1].length-F[1].length);A=E[1]+'&nbsp;'+F[1];if (FCKBrowserInfo.IsGecko&&(G.length==0||FCKRegexLib.EmptyParagraph.test(G))) G='<br type="_moz">';this._BodyHTML=G;}else this._BodyHTML=A;};var H=this.IFrame=D.createElement('iframe');var I='<script type="text/javascript" _fcktemp="true">window.onerror=function(){return true;};</script>';H.frameBorder=0;H.style.width=H.style.height='100%';if (FCK_IS_CUSTOM_DOMAIN&&FCKBrowserInfo.IsIE){window._FCKHtmlToLoad=A.replace(/<head>/i,'<head>'+I);H.src='javascript:void( (function(){document.open() ;document.domain="'+document.domain+'" ;document.write( window.parent._FCKHtmlToLoad );document.close() ;window.parent._FCKHtmlToLoad = null ;})() )';}else if (!FCKBrowserInfo.IsGecko){H.src='javascript:void(0)';};C.appendChild(H);this.Window=H.contentWindow;if (!FCK_IS_CUSTOM_DOMAIN||!FCKBrowserInfo.IsIE){var J=this.Window.document;J.open();J.write(A.replace(/<head>/i,'<head>'+I));J.close();};if (FCKBrowserInfo.IsAIR) FCKAdobeAIR.EditingArea_Start(J,A);if (FCKBrowserInfo.IsGecko10&&!B){this.Start(A,true);return;};if (H.readyState&&H.readyState!='completed'){var K=this;setTimeout(function(){try{K.Window.document.documentElement.doScroll("left");}catch(e){setTimeout(arguments.callee,0);return;};K.Window._FCKEditingArea=K;FCKEditingArea_CompleteStart.call(K.Window);},0);}else{this.Window._FCKEditingArea=this;if (FCKBrowserInfo.IsGecko10) this.Window.setTimeout(FCKEditingArea_CompleteStart,500);else FCKEditingArea_CompleteStart.call(this.Window);}}else{var L=this.Textarea=D.createElement('textarea');L.className='SourceField';L.dir='ltr';FCKDomTools.SetElementStyles(L,{width:'100%',height:'100%',border:'none',resize:'none',outline:'none'});C.appendChild(L);L.value=A;FCKTools.RunFunction(this.OnLoad);}};function FCKEditingArea_CompleteStart(){if (!this.document.body){this.setTimeout(FCKEditingArea_CompleteStart,50);return;};var A=this._FCKEditingArea;A.Document=A.Window.document;A.MakeEditable();FCKTools.RunFunction(A.OnLoad);};FCKEditingArea.prototype.MakeEditable=function(){var A=this.Document;if (FCKBrowserInfo.IsIE){A.body.disabled=true;A.body.contentEditable=true;A.body.removeAttribute("disabled");}else{try{A.body.spellcheck=(this.FFSpellChecker!==false);if (this._BodyHTML){A.body.innerHTML=this._BodyHTML;A.body.offsetLeft;this._BodyHTML=null;};A.designMode='on';A.execCommand('enableObjectResizing',false,!FCKConfig.DisableObjectResizing);A.execCommand('enableInlineTableEditing',false,!FCKConfig.DisableFFTableHandles);}catch (e){FCKTools.AddEventListener(this.Window.frameElement,'DOMAttrModified',FCKEditingArea_Document_AttributeNodeModified);}}};function FCKEditingArea_Document_AttributeNodeModified(A){var B=A.currentTarget.contentWindow._FCKEditingArea;if (B._timer) window.clearTimeout(B._timer);B._timer=FCKTools.SetTimeout(FCKEditingArea_MakeEditableByMutation,1000,B);};function FCKEditingArea_MakeEditableByMutation(){delete this._timer;FCKTools.RemoveEventListener(this.Window.frameElement,'DOMAttrModified',FCKEditingArea_Document_AttributeNodeModified);this.MakeEditable();};FCKEditingArea.prototype.Focus=function(){try{if (this.Mode==0){if (FCKBrowserInfo.IsIE) this._FocusIE();else this.Window.focus();}else{var A=FCKTools.GetElementDocument(this.Textarea);if ((!A.hasFocus||A.hasFocus())&&A.activeElement==this.Textarea) return;this.Textarea.focus();}}catch(e) {}};FCKEditingArea.prototype._FocusIE=function(){this.Document.body.setActive();this.Window.focus();var A=this.Document.selection.createRange();var B=A.parentElement();var C=B.nodeName.toLowerCase();if (B.childNodes.length>0||!(FCKListsLib.BlockElements[C]||FCKListsLib.NonEmptyBlockElements[C])){return;};A=new FCKDomRange(this.Window);A.MoveToElementEditStart(B);A.Select();};function FCKEditingArea_Cleanup(){if (this.Document){this.Document.selection.empty();this.Document.body.innerHTML="";};this.TargetElement=null;this.IFrame=null;this.Document=null;this.Textarea=null;if (this.Window){this.Window._FCKEditingArea=null;this.Window=null;}};
+var FCKKeystrokeHandler=function(A){this.Keystrokes={};this.CancelCtrlDefaults=(A!==false);};FCKKeystrokeHandler.prototype.AttachToElement=function(A){FCKTools.AddEventListenerEx(A,'keydown',_FCKKeystrokeHandler_OnKeyDown,this);if (FCKBrowserInfo.IsGecko10||FCKBrowserInfo.IsOpera||(FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsMac)) FCKTools.AddEventListenerEx(A,'keypress',_FCKKeystrokeHandler_OnKeyPress,this);};FCKKeystrokeHandler.prototype.SetKeystrokes=function(){for (var i=0;i<arguments.length;i++){var A=arguments[i];if (!A) continue;if (typeof(A[0])=='object') this.SetKeystrokes.apply(this,A);else{if (A.length==1) delete this.Keystrokes[A[0]];else this.Keystrokes[A[0]]=A[1]===true?true:A;}}};function _FCKKeystrokeHandler_OnKeyDown(A,B){var C=A.keyCode||A.which;var D=0;if (A.ctrlKey||A.metaKey) D+=CTRL;if (A.shiftKey) D+=SHIFT;if (A.altKey) D+=ALT;var E=C+D;var F=B._CancelIt=false;var G=B.Keystrokes[E];if (G){if (G===true||!(B.OnKeystroke&&B.OnKeystroke.apply(B,G))) return true;F=true;};if (F||(B.CancelCtrlDefaults&&D==CTRL&&(C<33||C>40))){B._CancelIt=true;if (A.preventDefault) return A.preventDefault();A.returnValue=false;A.cancelBubble=true;return false;};return true;};function _FCKKeystrokeHandler_OnKeyPress(A,B){if (B._CancelIt){if (A.preventDefault) return A.preventDefault();return false;};return true;};
+FCK.DTD=(function(){var X=FCKTools.Merge;var A,L,J,M,N,O,D,H,P,K,Q,F,G,C,B,E,I;A={isindex:1,fieldset:1};B={input:1,button:1,select:1,textarea:1,label:1};C=X({a:1},B);D=X({iframe:1},C);E={hr:1,ul:1,menu:1,div:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1};F={ins:1,del:1,script:1};G=X({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1},F);H=X({sub:1,img:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1},G);I=X({p:1},H);J=X({iframe:1},H,B);K={img:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1};L=X({a:1},J);M={tr:1};N={'#':1};O=X({param:1},K);P=X({form:1},A,D,E,I);Q={li:1};return {col:{},tr:{td:1,th:1},img:{},colgroup:{col:1},noscript:P,td:P,br:{},th:P,center:P,kbd:L,button:X(I,E),basefont:{},h5:L,h4:L,samp:L,h6:L,ol:Q,h1:L,h3:L,option:N,h2:L,form:X(A,D,E,I),select:{optgroup:1,option:1},font:J,ins:P,menu:Q,abbr:L,label:L,table:{thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1},code:L,script:N,tfoot:M,cite:L,li:P,input:{},iframe:P,strong:J,textarea:N,noframes:P,big:J,small:J,span:J,hr:{},dt:L,sub:J,optgroup:{option:1},param:{},bdo:L,'var':J,div:P,object:O,sup:J,dd:P,strike:J,area:{},dir:Q,map:X({area:1,form:1,p:1},A,F,E),applet:O,dl:{dt:1,dd:1},del:P,isindex:{},fieldset:X({legend:1},K),thead:M,ul:Q,acronym:L,b:J,a:J,blockquote:P,caption:L,i:J,u:J,tbody:M,s:L,address:X(D,I),tt:J,legend:L,q:L,pre:X(G,C),p:L,em:J,dfn:L};})();
+var FCKStyle=function(A){this.Element=(A.Element||'span').toLowerCase();this._StyleDesc=A;};FCKStyle.prototype={GetType:function(){var A=this.GetType_$;if (A!=undefined) return A;var B=this.Element;if (B=='#'||FCKListsLib.StyleBlockElements[B]) A=0;else if (FCKListsLib.StyleObjectElements[B]) A=2;else A=1;return (this.GetType_$=A);},ApplyToSelection:function(A){var B=new FCKDomRange(A);B.MoveToSelection();this.ApplyToRange(B,true);},ApplyToRange:function(A,B,C){switch (this.GetType()){case 0:this.ApplyToRange=this._ApplyBlockStyle;break;case 1:this.ApplyToRange=this._ApplyInlineStyle;break;default:return;};this.ApplyToRange(A,B,C);},ApplyToObject:function(A){if (!A) return;this.BuildElement(null,A);},RemoveFromSelection:function(A){var B=new FCKDomRange(A);B.MoveToSelection();this.RemoveFromRange(B,true);},RemoveFromRange:function(A,B,C){var D;var E=this._GetAttribsForComparison();var F=this._GetOverridesForComparison();if (A.CheckIsCollapsed()){var D=A.CreateBookmark(true);var H=A.GetBookmarkNode(D,true);var I=new FCKElementPath(H.parentNode);var J=[];var K=!FCKDomTools.GetNextSibling(H);var L=K||!FCKDomTools.GetPreviousSibling(H);var M;var N=-1;for (var i=0;i<I.Elements.length;i++){var O=I.Elements[i];if (this.CheckElementRemovable(O)){if (L&&!FCKDomTools.CheckIsEmptyElement(O,function(el){return (el!=H);})){M=O;N=J.length-1;}else{var P=O.nodeName.toLowerCase();if (P==this.Element){for (var Q in E){if (FCKDomTools.HasAttribute(O,Q)){switch (Q){case 'style':this._RemoveStylesFromElement(O);break;case 'class':if (FCKDomTools.GetAttributeValue(O,Q)!=this.GetFinalAttributeValue(Q)) continue;default:FCKDomTools.RemoveAttribute(O,Q);}}}};this._RemoveOverrides(O,F[P]);if (this.GetType()==1) this._RemoveNoAttribElement(O);}}else if (L) J.push(O);L=L&&((K&&!FCKDomTools.GetNextSibling(O))||(!K&&!FCKDomTools.GetPreviousSibling(O)));if (M&&(!L||(i==I.Elements.length-1))){var R=FCKDomTools.RemoveNode(H);for (var j=0;j<=N;j++){var S=FCKDomTools.CloneElement(J[j]);S.appendChild(R);R=S;};if (K) FCKDomTools.InsertAfterNode(M,R);else M.parentNode.insertBefore(R,M);L=false;M=null;}};if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);return;};A.Expand('inline_elements');D=A.CreateBookmark(true);var T=A.GetBookmarkNode(D,true);var U=A.GetBookmarkNode(D,false);A.Release(true);var I=new FCKElementPath(T);var X=I.Elements;var O;for (var i=1;i<X.length;i++){O=X[i];if (O==I.Block||O==I.BlockLimit) break;if (this.CheckElementRemovable(O)) FCKDomTools.BreakParent(T,O,A);};I=new FCKElementPath(U);X=I.Elements;for (var i=1;i<X.length;i++){O=X[i];if (O==I.Block||O==I.BlockLimit) break;b=O.nodeName.toLowerCase();if (this.CheckElementRemovable(O)) FCKDomTools.BreakParent(U,O,A);};var Z=FCKDomTools.GetNextSourceNode(T,true);while (Z){var a=FCKDomTools.GetNextSourceNode(Z);if (Z.nodeType==1){var b=Z.nodeName.toLowerCase();var c=(b==this.Element);if (c){for (var Q in E){if (FCKDomTools.HasAttribute(Z,Q)){switch (Q){case 'style':this._RemoveStylesFromElement(Z);break;case 'class':if (FCKDomTools.GetAttributeValue(Z,Q)!=this.GetFinalAttributeValue(Q)) continue;default:FCKDomTools.RemoveAttribute(Z,Q);}}}}else c=!!F[b];if (c){this._RemoveOverrides(Z,F[b]);this._RemoveNoAttribElement(Z);}};if (a==U) break;Z=a;};this._FixBookmarkStart(T);if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);},CheckElementRemovable:function(A,B){if (!A) return false;var C=A.nodeName.toLowerCase();if (C==this.Element){if (!B&&!FCKDomTools.HasAttributes(A)) return true;var D=this._GetAttribsForComparison();var E=(D._length==0);for (var F in D){if (F=='_length') continue;if (this._CompareAttributeValues(F,FCKDomTools.GetAttributeValue(A,F),(this.GetFinalAttributeValue(F)||''))){E=true;if (!B) break;}else{E=false;if (B) return false;}};if (E) return true;};var G=this._GetOverridesForComparison()[C];if (G){if (!(D=G.Attributes)) return true;for (var i=0;i<D.length;i++){var H=D[i][0];if (FCKDomTools.HasAttribute(A,H)){var I=D[i][1];if (I==null||(typeof I=='string'&&FCKDomTools.GetAttributeValue(A,H)==I)||I.test(FCKDomTools.GetAttributeValue(A,H))) return true;}}};return false;},CheckActive:function(A){switch (this.GetType()){case 0:return this.CheckElementRemovable(A.Block||A.BlockLimit,true);case 1:var B=A.Elements;for (var i=0;i<B.length;i++){var C=B[i];if (C==A.Block||C==A.BlockLimit) continue;if (this.CheckElementRemovable(C,true)) return true;}};return false;},RemoveFromElement:function(A){var B=this._GetAttribsForComparison();var C=this._GetOverridesForComparison();var D=A.getElementsByTagName(this.Element);for (var i=D.length-1;i>=0;i--){var E=D[i];for (var F in B){if (FCKDomTools.HasAttribute(E,F)){switch (F){case 'style':this._RemoveStylesFromElement(E);break;case 'class':if (FCKDomTools.GetAttributeValue(E,F)!=this.GetFinalAttributeValue(F)) continue;default:FCKDomTools.RemoveAttribute(E,F);}}};this._RemoveOverrides(E,C[this.Element]);this._RemoveNoAttribElement(E);};for (var G in C){if (G!=this.Element){D=A.getElementsByTagName(G);for (var i=D.length-1;i>=0;i--){var E=D[i];this._RemoveOverrides(E,C[G]);this._RemoveNoAttribElement(E);}}}},_RemoveStylesFromElement:function(A){var B=A.style.cssText;var C=this.GetFinalStyleValue();if (B.length>0&&C.length==0) return;C='(^|;)\\s*('+C.replace(/\s*([^ ]+):.*?(;|$)/g,'$1|').replace(/\|$/,'')+'):[^;]+';var D=new RegExp(C,'gi');B=B.replace(D,'').Trim();if (B.length==0||B==';') FCKDomTools.RemoveAttribute(A,'style');else A.style.cssText=B.replace(D,'');},_RemoveOverrides:function(A,B){var C=B&&B.Attributes;if (C){for (var i=0;i<C.length;i++){var D=C[i][0];if (FCKDomTools.HasAttribute(A,D)){var E=C[i][1];if (E==null||(E.test&&E.test(FCKDomTools.GetAttributeValue(A,D)))||(typeof E=='string'&&FCKDomTools.GetAttributeValue(A,D)==E)) FCKDomTools.RemoveAttribute(A,D);}}}},_RemoveNoAttribElement:function(A){if (!FCKDomTools.HasAttributes(A)){var B=A.firstChild;var C=A.lastChild;FCKDomTools.RemoveNode(A,true);this._MergeSiblings(B);if (B!=C) this._MergeSiblings(C);}},BuildElement:function(A,B){var C=B||A.createElement(this.Element);var D=this._StyleDesc.Attributes;var E;if (D){for (var F in D){E=this.GetFinalAttributeValue(F);if (F.toLowerCase()=='class') C.className=E;else C.setAttribute(F,E);}};if (this._GetStyleText().length>0) C.style.cssText=this.GetFinalStyleValue();return C;},_CompareAttributeValues:function(A,B,C){if (A=='style'&&B&&C){B=B.replace(/;$/,'').toLowerCase();C=C.replace(/;$/,'').toLowerCase();};return (B==C||((B===null||B==='')&&(C===null||C==='')))},GetFinalAttributeValue:function(A){var B=this._StyleDesc.Attributes;var B=B?B[A]:null;if (!B&&A=='style') return this.GetFinalStyleValue();if (B&&this._Variables) B=B.Replace(FCKRegexLib.StyleVariableAttName,this._GetVariableReplace,this);return B;},GetFinalStyleValue:function(){var A=this._GetStyleText();if (A.length>0&&this._Variables){A=A.Replace(FCKRegexLib.StyleVariableAttName,this._GetVariableReplace,this);A=FCKTools.NormalizeCssText(A);};return A;},_GetVariableReplace:function(){return this._Variables[arguments[2]]||arguments[0];},SetVariable:function(A,B){var C=this._Variables;if (!C) C=this._Variables={};this._Variables[A]=B;},_FromPre:function(A,B,C){var D=B.innerHTML;D=D.replace(/(\r\n|\r)/g,'\n');D=D.replace(/^[ \t]*\n/,'');D=D.replace(/\n$/,'');D=D.replace(/^[ \t]+|[ \t]+$/g,function(match,offset,s){if (match.length==1) return '&nbsp;';else if (offset==0) return new Array(match.length).join('&nbsp;')+' ';else return ' '+new Array(match.length).join('&nbsp;');});var E=new FCKHtmlIterator(D);var F=[];E.Each(function(isTag,value){if (!isTag){value=value.replace(/\n/g,'<br>');value=value.replace(/[ \t]{2,}/g,function (match){return new Array(match.length).join('&nbsp;')+' ';});};F.push(value);});C.innerHTML=F.join('');return C;},_ToPre:function(A,B,C){var D=B.innerHTML.Trim();D=D.replace(/[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi,'<br />');var E=new FCKHtmlIterator(D);var F=[];E.Each(function(isTag,value){if (!isTag) value=value.replace(/([ \t\n\r]+|&nbsp;)/g,' ');else if (isTag&&value=='<br />') value='\n';F.push(value);});if (FCKBrowserInfo.IsIE){var G=A.createElement('div');G.appendChild(C);C.outerHTML='<pre>\n'+F.join('')+'</pre>';C=G.removeChild(G.firstChild);}else C.innerHTML=F.join('');return C;},_CheckAndMergePre:function(A,B){if (A!=FCKDomTools.GetPreviousSourceElement(B,true)) return;var C=A.innerHTML.replace(/\n$/,'')+'\n\n'+B.innerHTML.replace(/^\n/,'');if (FCKBrowserInfo.IsIE) B.outerHTML='<pre>'+C+'</pre>';else B.innerHTML=C;FCKDomTools.RemoveNode(A);},_CheckAndSplitPre:function(A){var B;var C=A.firstChild;C=C&&C.nextSibling;while (C){var D=C.nextSibling;if (D&&D.nextSibling&&C.nodeName.IEquals('br')&&D.nodeName.IEquals('br')){FCKDomTools.RemoveNode(C);C=D.nextSibling;FCKDomTools.RemoveNode(D);B=FCKDomTools.InsertAfterNode(B||A,FCKDomTools.CloneElement(A));continue;};if (B){C=C.previousSibling;FCKDomTools.MoveNode(C.nextSibling,B);};C=C.nextSibling;}},_ApplyBlockStyle:function(A,B,C){var D;if (B) D=A.CreateBookmark();var E=new FCKDomRangeIterator(A);E.EnforceRealBlocks=true;var F;var G=A.Window.document;var H;while((F=E.GetNextParagraph())){var I=this.BuildElement(G);var J=I.nodeName.IEquals('pre');var K=F.nodeName.IEquals('pre');var L=J&&!K;var M=!J&&K;if (L) I=this._ToPre(G,F,I);else if (M) I=this._FromPre(G,F,I);else FCKDomTools.MoveChildren(F,I);F.parentNode.insertBefore(I,F);FCKDomTools.RemoveNode(F);if (J){if (H) this._CheckAndMergePre(H,I);H=I;}else if (M) this._CheckAndSplitPre(I);};if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);},_ApplyInlineStyle:function(A,B,C){var D=A.Window.document;if (A.CheckIsCollapsed()){var E=this.BuildElement(D);A.InsertNode(E);A.MoveToPosition(E,2);A.Select();return;};var F=this.Element;var G=FCK.DTD[F]||FCK.DTD.span;var H=this._GetAttribsForComparison();var I;A.Expand('inline_elements');var J=A.CreateBookmark(true);var K=A.GetBookmarkNode(J,true);var L=A.GetBookmarkNode(J,false);A.Release(true);var M=FCKDomTools.GetNextSourceNode(K,true);while (M){var N=false;var O=M.nodeType;var P=O==1?M.nodeName.toLowerCase():null;if (!P||G[P]){if ((FCK.DTD[M.parentNode.nodeName.toLowerCase()]||FCK.DTD.span)[F]||!FCK.DTD[F]){if (!A.CheckHasRange()) A.SetStart(M,3);if (O!=1||M.childNodes.length==0){var Q=M;var R=Q.parentNode;while (Q==R.lastChild&&G[R.nodeName.toLowerCase()]){Q=R;};A.SetEnd(Q,4);if (Q==Q.parentNode.lastChild&&!G[Q.parentNode.nodeName.toLowerCase()]) N=true;}else{A.SetEnd(M,3);}}else N=true;}else N=true;M=FCKDomTools.GetNextSourceNode(M);if (M==L){M=null;N=true;};if (N&&A.CheckHasRange()&&!A.CheckIsCollapsed()){I=this.BuildElement(D);A.ExtractContents().AppendTo(I);if (I.innerHTML.RTrim().length>0){A.InsertNode(I);this.RemoveFromElement(I);this._MergeSiblings(I,this._GetAttribsForComparison());if (!FCKBrowserInfo.IsIE) I.normalize();};A.Release(true);}};this._FixBookmarkStart(K);if (B) A.SelectBookmark(J);if (C) A.MoveToBookmark(J);},_FixBookmarkStart:function(A){var B;while ((B=A.nextSibling)){if (B.nodeType==1&&FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){if (!B.firstChild) FCKDomTools.RemoveNode(B);else FCKDomTools.MoveNode(A,B,true);continue;};if (B.nodeType==3&&B.length==0){FCKDomTools.RemoveNode(B);continue;};break;}},_MergeSiblings:function(A,B){if (!A||A.nodeType!=1||!FCKListsLib.InlineNonEmptyElements[A.nodeName.toLowerCase()]) return;this._MergeNextSibling(A,B);this._MergePreviousSibling(A,B);},_MergeNextSibling:function(A,B){var C=A.nextSibling;var D=(C&&C.nodeType==1&&C.getAttribute('_fck_bookmark'));if (D) C=C.nextSibling;if (C&&C.nodeType==1&&C.nodeName==A.nodeName){if (!B) B=this._CreateElementAttribsForComparison(A);if (this._CheckAttributesMatch(C,B)){var E=A.lastChild;if (D) FCKDomTools.MoveNode(A.nextSibling,A);FCKDomTools.MoveChildren(C,A);FCKDomTools.RemoveNode(C);if (E) this._MergeNextSibling(E);}}},_MergePreviousSibling:function(A,B){var C=A.previousSibling;var D=(C&&C.nodeType==1&&C.getAttribute('_fck_bookmark'));if (D) C=C.previousSibling;if (C&&C.nodeType==1&&C.nodeName==A.nodeName){if (!B) B=this._CreateElementAttribsForComparison(A);if (this._CheckAttributesMatch(C,B)){var E=A.firstChild;if (D) FCKDomTools.MoveNode(A.previousSibling,A,true);FCKDomTools.MoveChildren(C,A,true);FCKDomTools.RemoveNode(C);if (E) this._MergePreviousSibling(E);}}},_GetStyleText:function(){var A=this._StyleDesc.Styles;var B=(this._StyleDesc.Attributes?this._StyleDesc.Attributes['style']||'':'');if (B.length>0) B+=';';for (var C in A) B+=C+':'+A[C]+';';if (B.length>0&&!(/#\(/.test(B))){B=FCKTools.NormalizeCssText(B);};return (this._GetStyleText=function() { return B;})();},_GetAttribsForComparison:function(){var A=this._GetAttribsForComparison_$;if (A) return A;A={};var B=this._StyleDesc.Attributes;if (B){for (var C in B){A[C.toLowerCase()]=B[C].toLowerCase();}};if (this._GetStyleText().length>0){A['style']=this._GetStyleText().toLowerCase();};FCKTools.AppendLengthProperty(A,'_length');return (this._GetAttribsForComparison_$=A);},_GetOverridesForComparison:function(){var A=this._GetOverridesForComparison_$;if (A) return A;A={};var B=this._StyleDesc.Overrides;if (B){if (!FCKTools.IsArray(B)) B=[B];for (var i=0;i<B.length;i++){var C=B[i];var D;var E;var F;if (typeof C=='string') D=C.toLowerCase();else{D=C.Element?C.Element.toLowerCase():this.Element;F=C.Attributes;};E=A[D]||(A[D]={});if (F){var G=(E.Attributes=E.Attributes||[]);for (var H in F){G.push([H.toLowerCase(),F[H]]);}}}};return (this._GetOverridesForComparison_$=A);},_CreateElementAttribsForComparison:function(A){var B={};var C=0;for (var i=0;i<A.attributes.length;i++){var D=A.attributes[i];if (D.specified){B[D.nodeName.toLowerCase()]=FCKDomTools.GetAttributeValue(A,D).toLowerCase();C++;}};B._length=C;return B;},_CheckAttributesMatch:function(A,B){var C=A.attributes;var D=0;for (var i=0;i<C.length;i++){var E=C[i];if (E.specified){var F=E.nodeName.toLowerCase();var G=B[F];if (!G) break;if (G!=FCKDomTools.GetAttributeValue(A,E).toLowerCase()) break;D++;}};return (D==B._length);}};
+var FCKStyles=FCK.Styles={_Callbacks:{},_ObjectStyles:{},ApplyStyle:function(A){if (typeof A=='string') A=this.GetStyles()[A];if (A){if (A.GetType()==2) A.ApplyToObject(FCKSelection.GetSelectedElement());else A.ApplyToSelection(FCK.EditorWindow);FCK.Events.FireEvent('OnSelectionChange');}},RemoveStyle:function(A){if (typeof A=='string') A=this.GetStyles()[A];if (A){A.RemoveFromSelection(FCK.EditorWindow);FCK.Events.FireEvent('OnSelectionChange');}},AttachStyleStateChange:function(A,B,C){var D=this._Callbacks[A];if (!D) D=this._Callbacks[A]=[];D.push([B,C]);},CheckSelectionChanges:function(){var A=FCKSelection.GetBoundaryParentElement(true);if (!A) return;var B=new FCKElementPath(A);var C=this.GetStyles();for (var D in C){var E=this._Callbacks[D];if (E){var F=C[D];var G=F.CheckActive(B);if (G!=(F._LastState||null)){F._LastState=G;for (var i=0;i<E.length;i++){var H=E[i][0];var I=E[i][1];H.call(I||window,D,G);}}}}},CheckStyleInSelection:function(A){return false;},_GetRemoveFormatTagsRegex:function (){var A=new RegExp('^(?:'+FCKConfig.RemoveFormatTags.replace(/,/g,'|')+')$','i');return (this._GetRemoveFormatTagsRegex=function(){return A;})&&A;},RemoveAll:function(){var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();if (A.CheckIsCollapsed()) return;A.Expand('inline_elements');var B=A.CreateBookmark(true);var C=A.GetBookmarkNode(B,true);var D=A.GetBookmarkNode(B,false);A.Release(true);var E=this._GetRemoveFormatTagsRegex();var F=new FCKElementPath(C);var G=F.Elements;var H;for (var i=1;i<G.length;i++){H=G[i];if (H==F.Block||H==F.BlockLimit) break;if (E.test(H.nodeName)) FCKDomTools.BreakParent(C,H,A);};F=new FCKElementPath(D);G=F.Elements;for (var i=1;i<G.length;i++){H=G[i];if (H==F.Block||H==F.BlockLimit) break;elementName=H.nodeName.toLowerCase();if (E.test(H.nodeName)) FCKDomTools.BreakParent(D,H,A);};var I=FCKDomTools.GetNextSourceNode(C,true,1);while (I){if (I==D) break;var J=FCKDomTools.GetNextSourceNode(I,false,1);if (E.test(I.nodeName)) FCKDomTools.RemoveNode(I,true);else FCKDomTools.RemoveAttributes(I,FCKConfig.RemoveAttributesArray);I=J;};A.SelectBookmark(B);FCK.Events.FireEvent('OnSelectionChange');},GetStyle:function(A){return this.GetStyles()[A];},GetStyles:function(){var A=this._GetStyles;if (!A){A=this._GetStyles=FCKTools.Merge(this._LoadStylesCore(),this._LoadStylesCustom(),this._LoadStylesXml());};return A;},CheckHasObjectStyle:function(A){return!!this._ObjectStyles[A];},_LoadStylesCore:function(){var A={};var B=FCKConfig.CoreStyles;for (var C in B){var D=A['_FCK_'+C]=new FCKStyle(B[C]);D.IsCore=true;};return A;},_LoadStylesCustom:function(){var A={};var B=FCKConfig.CustomStyles;if (B){for (var C in B){var D=A[C]=new FCKStyle(B[C]);D.Name=C;}};return A;},_LoadStylesXml:function(){var A={};var B=FCKConfig.StylesXmlPath;if (!B||B.length==0) return A;var C=new FCKXml();C.LoadUrl(B);var D=FCKXml.TransformToObject(C.SelectSingleNode('Styles'));var E=D.$Style;if (!E) return A;for (var i=0;i<E.length;i++){var F=E[i];var G=(F.element||'').toLowerCase();if (G.length==0) throw('The element name is required. Error loading "'+B+'"');var H={Element:G,Attributes:{},Styles:{},Overrides:[]};var I=F.$Attribute||[];for (var j=0;j<I.length;j++){H.Attributes[I[j].name]=I[j].value;};var J=F.$Style||[];for (j=0;j<J.length;j++){H.Styles[J[j].name]=J[j].value;};var K=F.$Override;if (K){for (j=0;j<K.length;j++){var L=K[j];var M={Element:L.element};var N=L.$Attribute;if (N){M.Attributes={};for (var k=0;k<N.length;k++){var O=N[k].value||null;if (O){var P=O&&FCKRegexLib.RegExp.exec(O);if (P) O=new RegExp(P[1],P[2]||'');};M.Attributes[N[k].name]=O;}};H.Overrides.push(M);}};var Q=new FCKStyle(H);Q.Name=F.name||G;if (Q.GetType()==2) this._ObjectStyles[G]=true;A[Q.Name]=Q;};return A;}};
+var FCKListHandler={OutdentListItem:function(A){var B=A.parentNode;if (B.tagName.toUpperCase().Equals('UL','OL')){var C=FCKTools.GetElementDocument(A);var D=new FCKDocumentFragment(C);var E=D.RootNode;var F=false;var G=FCKDomTools.GetFirstChild(A,['UL','OL']);if (G){F=true;var H;while ((H=G.firstChild)) E.appendChild(G.removeChild(H));FCKDomTools.RemoveNode(G);};var I;var J=false;while ((I=A.nextSibling)){if (!F&&I.nodeType==1&&I.nodeName.toUpperCase()=='LI') J=F=true;E.appendChild(I.parentNode.removeChild(I));if (!J&&I.nodeType==1&&I.nodeName.toUpperCase().Equals('UL','OL')) FCKDomTools.RemoveNode(I,true);};var K=B.parentNode.tagName.toUpperCase();var L=(K=='LI');if (L||K.Equals('UL','OL')){if (F){var G=B.cloneNode(false);D.AppendTo(G);A.appendChild(G);}else if (L) D.InsertAfterNode(B.parentNode);else D.InsertAfterNode(B);if (L) FCKDomTools.InsertAfterNode(B.parentNode,B.removeChild(A));else FCKDomTools.InsertAfterNode(B,B.removeChild(A));}else{if (F){var N=B.cloneNode(false);D.AppendTo(N);FCKDomTools.InsertAfterNode(B,N);};var O=C.createElement(FCKConfig.EnterMode=='p'?'p':'div');FCKDomTools.MoveChildren(B.removeChild(A),O);FCKDomTools.InsertAfterNode(B,O);if (FCKConfig.EnterMode=='br'){if (FCKBrowserInfo.IsGecko) O.parentNode.insertBefore(FCKTools.CreateBogusBR(C),O);else FCKDomTools.InsertAfterNode(O,FCKTools.CreateBogusBR(C));FCKDomTools.RemoveNode(O,true);}};if (this.CheckEmptyList(B)) FCKDomTools.RemoveNode(B,true);}},CheckEmptyList:function(A){return (FCKDomTools.GetFirstChild(A,'LI')==null);},CheckListHasContents:function(A){var B=A.firstChild;while (B){switch (B.nodeType){case 1:if (!B.nodeName.IEquals('UL','LI')) return true;break;case 3:if (B.nodeValue.Trim().length>0) return true;};B=B.nextSibling;};return false;}};
+var FCKElementPath=function(A){var B=null;var C=null;var D=[];var e=A;while (e){if (e.nodeType==1){if (!this.LastElement) this.LastElement=e;var E=e.nodeName.toLowerCase();if (FCKBrowserInfo.IsIE&&e.scopeName!='HTML') E=e.scopeName.toLowerCase()+':'+E;if (!C){if (!B&&FCKListsLib.PathBlockElements[E]!=null) B=e;if (FCKListsLib.PathBlockLimitElements[E]!=null){if (!B&&E=='div'&&!FCKElementPath._CheckHasBlock(e)) B=e;else C=e;}};D.push(e);if (E=='body') break;};e=e.parentNode;};this.Block=B;this.BlockLimit=C;this.Elements=D;};FCKElementPath._CheckHasBlock=function(A){var B=A.childNodes;for (var i=0,count=B.length;i<count;i++){var C=B[i];if (C.nodeType==1&&FCKListsLib.BlockElements[C.nodeName.toLowerCase()]) return true;};return false;};
+var FCKDomRange=function(A){this.Window=A;this._Cache={};};FCKDomRange.prototype={_UpdateElementInfo:function(){var A=this._Range;if (!A) this.Release(true);else{var B=A.startContainer;var C=new FCKElementPath(B);this.StartNode=B.nodeType==3?B:B.childNodes[A.startOffset];this.StartContainer=B;this.StartBlock=C.Block;this.StartBlockLimit=C.BlockLimit;if (A.collapsed){this.EndNode=this.StartNode;this.EndContainer=this.StartContainer;this.EndBlock=this.StartBlock;this.EndBlockLimit=this.StartBlockLimit;}else{var D=A.endContainer;if (B!=D) C=new FCKElementPath(D);var E=D;if (A.endOffset==0){while (E&&!E.previousSibling) E=E.parentNode;if (E) E=E.previousSibling;}else if (E.nodeType==1) E=E.childNodes[A.endOffset-1];this.EndNode=E;this.EndContainer=D;this.EndBlock=C.Block;this.EndBlockLimit=C.BlockLimit;}};this._Cache={};},CreateRange:function(){return new FCKW3CRange(this.Window.document);},DeleteContents:function(){if (this._Range){this._Range.deleteContents();this._UpdateElementInfo();}},ExtractContents:function(){if (this._Range){var A=this._Range.extractContents();this._UpdateElementInfo();return A;};return null;},CheckIsCollapsed:function(){if (this._Range) return this._Range.collapsed;return false;},Collapse:function(A){if (this._Range) this._Range.collapse(A);this._UpdateElementInfo();},Clone:function(){var A=FCKTools.CloneObject(this);if (this._Range) A._Range=this._Range.cloneRange();return A;},MoveToNodeContents:function(A){if (!this._Range) this._Range=this.CreateRange();this._Range.selectNodeContents(A);this._UpdateElementInfo();},MoveToElementStart:function(A){this.SetStart(A,1);this.SetEnd(A,1);},MoveToElementEditStart:function(A){var B;while (A&&A.nodeType==1){if (FCKDomTools.CheckIsEditable(A)) B=A;else if (B) break;A=A.firstChild;};if (B) this.MoveToElementStart(B);},InsertNode:function(A){if (this._Range) this._Range.insertNode(A);},CheckIsEmpty:function(){if (this.CheckIsCollapsed()) return true;var A=this.Window.document.createElement('div');this._Range.cloneContents().AppendTo(A);FCKDomTools.TrimNode(A);return (A.innerHTML.length==0);},CheckStartOfBlock:function(){var A=this._Cache;var B=A.IsStartOfBlock;if (B!=undefined) return B;var C=this.StartBlock||this.StartBlockLimit;var D=this._Range.startContainer;var E=this._Range.startOffset;var F;if (E>0){if (D.nodeType==3){var G=D.nodeValue.substr(0,E).Trim();if (G.length!=0) return A.IsStartOfBlock=false;}else F=D.childNodes[E-1];};if (!F) F=FCKDomTools.GetPreviousSourceNode(D,true,null,C);while (F){switch (F.nodeType){case 1:if (!FCKListsLib.InlineChildReqElements[F.nodeName.toLowerCase()]) return A.IsStartOfBlock=false;break;case 3:if (F.nodeValue.Trim().length>0) return A.IsStartOfBlock=false;};F=FCKDomTools.GetPreviousSourceNode(F,false,null,C);};return A.IsStartOfBlock=true;},CheckEndOfBlock:function(A){var B=this._Cache.IsEndOfBlock;if (B!=undefined) return B;var C=this.EndBlock||this.EndBlockLimit;var D=this._Range.endContainer;var E=this._Range.endOffset;var F;if (D.nodeType==3){var G=D.nodeValue;if (E<G.length){G=G.substr(E);if (G.Trim().length!=0) return this._Cache.IsEndOfBlock=false;}}else F=D.childNodes[E];if (!F) F=FCKDomTools.GetNextSourceNode(D,true,null,C);var H=false;while (F){switch (F.nodeType){case 1:var I=F.nodeName.toLowerCase();if (FCKListsLib.InlineChildReqElements[I]) break;if (I=='br'&&!H){H=true;break;};return this._Cache.IsEndOfBlock=false;case 3:if (F.nodeValue.Trim().length>0) return this._Cache.IsEndOfBlock=false;};F=FCKDomTools.GetNextSourceNode(F,false,null,C);};if (A) this.Select();return this._Cache.IsEndOfBlock=true;},CreateBookmark:function(A){var B={StartId:(new Date()).valueOf()+Math.floor(Math.random()*1000)+'S',EndId:(new Date()).valueOf()+Math.floor(Math.random()*1000)+'E'};var C=this.Window.document;var D;var E;var F;if (!this.CheckIsCollapsed()){E=C.createElement('span');E.style.display='none';E.id=B.EndId;E.setAttribute('_fck_bookmark',true);E.innerHTML='&nbsp;';F=this.Clone();F.Collapse(false);F.InsertNode(E);};D=C.createElement('span');D.style.display='none';D.id=B.StartId;D.setAttribute('_fck_bookmark',true);D.innerHTML='&nbsp;';F=this.Clone();F.Collapse(true);F.InsertNode(D);if (A){B.StartNode=D;B.EndNode=E;};if (E){this.SetStart(D,4);this.SetEnd(E,3);}else this.MoveToPosition(D,4);return B;},GetBookmarkNode:function(A,B){var C=this.Window.document;if (B) return A.StartNode||C.getElementById(A.StartId);else return A.EndNode||C.getElementById(A.EndId);},MoveToBookmark:function(A,B){var C=this.GetBookmarkNode(A,true);var D=this.GetBookmarkNode(A,false);this.SetStart(C,3);if (!B) FCKDomTools.RemoveNode(C);if (D){this.SetEnd(D,3);if (!B) FCKDomTools.RemoveNode(D);}else this.Collapse(true);this._UpdateElementInfo();},CreateBookmark2:function(){if (!this._Range) return { "Start":0,"End":0 };var A={"Start":[this._Range.startOffset],"End":[this._Range.endOffset]};var B=this._Range.startContainer.previousSibling;var C=this._Range.endContainer.previousSibling;var D=this._Range.startContainer;var E=this._Range.endContainer;while (B&&B.nodeType==3&&D.nodeType==3){A.Start[0]+=B.length;D=B;B=B.previousSibling;}while (C&&C.nodeType==3&&E.nodeType==3){A.End[0]+=C.length;E=C;C=C.previousSibling;};if (D.nodeType==1&&D.childNodes[A.Start[0]]&&D.childNodes[A.Start[0]].nodeType==3){var F=D.childNodes[A.Start[0]];var G=0;while (F.previousSibling&&F.previousSibling.nodeType==3){F=F.previousSibling;G+=F.length;};D=F;A.Start[0]=G;};if (E.nodeType==1&&E.childNodes[A.End[0]]&&E.childNodes[A.End[0]].nodeType==3){var F=E.childNodes[A.End[0]];var G=0;while (F.previousSibling&&F.previousSibling.nodeType==3){F=F.previousSibling;G+=F.length;};E=F;A.End[0]=G;};A.Start=FCKDomTools.GetNodeAddress(D,true).concat(A.Start);A.End=FCKDomTools.GetNodeAddress(E,true).concat(A.End);return A;},MoveToBookmark2:function(A){var B=FCKDomTools.GetNodeFromAddress(this.Window.document,A.Start.slice(0,-1),true);var C=FCKDomTools.GetNodeFromAddress(this.Window.document,A.End.slice(0,-1),true);this.Release(true);this._Range=new FCKW3CRange(this.Window.document);var D=A.Start[A.Start.length-1];var E=A.End[A.End.length-1];while (B.nodeType==3&&D>B.length){if (!B.nextSibling||B.nextSibling.nodeType!=3) break;D-=B.length;B=B.nextSibling;}while (C.nodeType==3&&E>C.length){if (!C.nextSibling||C.nextSibling.nodeType!=3) break;E-=C.length;C=C.nextSibling;};this._Range.setStart(B,D);this._Range.setEnd(C,E);this._UpdateElementInfo();},MoveToPosition:function(A,B){this.SetStart(A,B);this.Collapse(true);},SetStart:function(A,B,C){var D=this._Range;if (!D) D=this._Range=this.CreateRange();switch(B){case 1:D.setStart(A,0);break;case 2:D.setStart(A,A.childNodes.length);break;case 3:D.setStartBefore(A);break;case 4:D.setStartAfter(A);};if (!C) this._UpdateElementInfo();},SetEnd:function(A,B,C){var D=this._Range;if (!D) D=this._Range=this.CreateRange();switch(B){case 1:D.setEnd(A,0);break;case 2:D.setEnd(A,A.childNodes.length);break;case 3:D.setEndBefore(A);break;case 4:D.setEndAfter(A);};if (!C) this._UpdateElementInfo();},Expand:function(A){var B,oSibling;switch (A){case 'inline_elements':if (this._Range.startOffset==0){B=this._Range.startContainer;if (B.nodeType!=1) B=B.previousSibling?null:B.parentNode;if (B){while (FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){this._Range.setStartBefore(B);if (B!=B.parentNode.firstChild) break;B=B.parentNode;}}};B=this._Range.endContainer;var C=this._Range.endOffset;if ((B.nodeType==3&&C>=B.nodeValue.length)||(B.nodeType==1&&C>=B.childNodes.length)||(B.nodeType!=1&&B.nodeType!=3)){if (B.nodeType!=1) B=B.nextSibling?null:B.parentNode;if (B){while (FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){this._Range.setEndAfter(B);if (B!=B.parentNode.lastChild) break;B=B.parentNode;}}};break;case 'block_contents':case 'list_contents':var D=FCKListsLib.BlockBoundaries;if (A=='list_contents'||FCKConfig.EnterMode=='br') D=FCKListsLib.ListBoundaries;if (this.StartBlock&&FCKConfig.EnterMode!='br'&&A=='block_contents') this.SetStart(this.StartBlock,1);else{B=this._Range.startContainer;if (B.nodeType==1){var E=B.childNodes[this._Range.startOffset];if (E) B=FCKDomTools.GetPreviousSourceNode(E,true);else B=B.lastChild||B;}while (B&&(B.nodeType!=1||(B!=this.StartBlockLimit&&!D[B.nodeName.toLowerCase()]))){this._Range.setStartBefore(B);B=B.previousSibling||B.parentNode;}};if (this.EndBlock&&FCKConfig.EnterMode!='br'&&A=='block_contents'&&this.EndBlock.nodeName.toLowerCase()!='li') this.SetEnd(this.EndBlock,2);else{B=this._Range.endContainer;if (B.nodeType==1) B=B.childNodes[this._Range.endOffset]||B.lastChild;while (B&&(B.nodeType!=1||(B!=this.StartBlockLimit&&!D[B.nodeName.toLowerCase()]))){this._Range.setEndAfter(B);B=B.nextSibling||B.parentNode;};if (B&&B.nodeName.toLowerCase()=='br') this._Range.setEndAfter(B);};this._UpdateElementInfo();}},SplitBlock:function(A){var B=A||FCKConfig.EnterMode;if (!this._Range) this.MoveToSelection();if (this.StartBlockLimit==this.EndBlockLimit){var C=this.StartBlock;var D=this.EndBlock;var E=null;if (B!='br'){if (!C){C=this.FixBlock(true,B);D=this.EndBlock;};if (!D) D=this.FixBlock(false,B);};var F=(C!=null&&this.CheckStartOfBlock());var G=(D!=null&&this.CheckEndOfBlock());if (!this.CheckIsEmpty()) this.DeleteContents();if (C&&D&&C==D){if (G){E=new FCKElementPath(this.StartContainer);this.MoveToPosition(D,4);D=null;}else if (F){E=new FCKElementPath(this.StartContainer);this.MoveToPosition(C,3);C=null;}else{this.SetEnd(C,2);var H=this.ExtractContents();D=C.cloneNode(false);D.removeAttribute('id',false);H.AppendTo(D);FCKDomTools.InsertAfterNode(C,D);this.MoveToPosition(C,4);if (FCKBrowserInfo.IsGecko&&!C.nodeName.IEquals(['ul','ol'])) FCKTools.AppendBogusBr(C);}};return {PreviousBlock:C,NextBlock:D,WasStartOfBlock:F,WasEndOfBlock:G,ElementPath:E};};return null;},FixBlock:function(A,B){var C=this.CreateBookmark();this.Collapse(A);this.Expand('block_contents');var D=this.Window.document.createElement(B);this.ExtractContents().AppendTo(D);FCKDomTools.TrimNode(D);if (FCKDomTools.CheckIsEmptyElement(D,function(element) { return element.getAttribute('_fck_bookmark')!='true';})&&FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(D);this.InsertNode(D);this.MoveToBookmark(C);return D;},Release:function(A){if (!A) this.Window=null;this.StartNode=null;this.StartContainer=null;this.StartBlock=null;this.StartBlockLimit=null;this.EndNode=null;this.EndContainer=null;this.EndBlock=null;this.EndBlockLimit=null;this._Range=null;this._Cache=null;},CheckHasRange:function(){return!!this._Range;},GetTouchedStartNode:function(){var A=this._Range;var B=A.startContainer;if (A.collapsed||B.nodeType!=1) return B;return B.childNodes[A.startOffset]||B;},GetTouchedEndNode:function(){var A=this._Range;var B=A.endContainer;if (A.collapsed||B.nodeType!=1) return B;return B.childNodes[A.endOffset-1]||B;}};
+FCKDomRange.prototype.MoveToSelection=function(){this.Release(true);var A=this.Window.getSelection();if (A&&A.rangeCount>0){this._Range=FCKW3CRange.CreateFromRange(this.Window.document,A.getRangeAt(0));this._UpdateElementInfo();}else if (this.Window.document) this.MoveToElementStart(this.Window.document.body);};FCKDomRange.prototype.Select=function(){var A=this._Range;if (A){var B=A.startContainer;if (A.collapsed&&B.nodeType==1&&B.childNodes.length==0) B.appendChild(A._Document.createTextNode(''));var C=this.Window.document.createRange();C.setStart(B,A.startOffset);try{C.setEnd(A.endContainer,A.endOffset);}catch (e){if (e.toString().Contains('NS_ERROR_ILLEGAL_VALUE')){A.collapse(true);C.setEnd(A.endContainer,A.endOffset);}else throw(e);};var D=this.Window.getSelection();D.removeAllRanges();D.addRange(C);}};FCKDomRange.prototype.SelectBookmark=function(A){var B=this.Window.document.createRange();var C=this.GetBookmarkNode(A,true);var D=this.GetBookmarkNode(A,false);B.setStart(C.parentNode,FCKDomTools.GetIndexOf(C));FCKDomTools.RemoveNode(C);if (D){B.setEnd(D.parentNode,FCKDomTools.GetIndexOf(D));FCKDomTools.RemoveNode(D);};var E=this.Window.getSelection();E.removeAllRanges();E.addRange(B);};
+var FCKDomRangeIterator=function(A){this.Range=A;this.ForceBrBreak=false;this.EnforceRealBlocks=false;};FCKDomRangeIterator.CreateFromSelection=function(A){var B=new FCKDomRange(A);B.MoveToSelection();return new FCKDomRangeIterator(B);};FCKDomRangeIterator.prototype={GetNextParagraph:function(){var A;var B;var C;var D;var E;var F=this.ForceBrBreak?FCKListsLib.ListBoundaries:FCKListsLib.BlockBoundaries;if (!this._LastNode){var B=this.Range.Clone();B.Expand(this.ForceBrBreak?'list_contents':'block_contents');this._NextNode=B.GetTouchedStartNode();this._LastNode=B.GetTouchedEndNode();B=null;};var H=this._NextNode;var I=this._LastNode;this._NextNode=null;while (H){var J=false;var K=(H.nodeType!=1);var L=false;if (!K){var M=H.nodeName.toLowerCase();if (F[M]&&(!FCKBrowserInfo.IsIE||H.scopeName=='HTML')){if (M=='br') K=true;else if (!B&&H.childNodes.length==0&&M!='hr'){A=H;C=H==I;break;};if (B){B.SetEnd(H,3,true);if (M!='br') this._NextNode=FCKDomTools.GetNextSourceNode(H,true,null,I)||H;};J=true;}else{if (H.firstChild){if (!B){B=new FCKDomRange(this.Range.Window);B.SetStart(H,3,true);};H=H.firstChild;continue;};K=true;}}else if (H.nodeType==3){if (/^[\r\n\t ]+$/.test(H.nodeValue)) K=false;};if (K&&!B){B=new FCKDomRange(this.Range.Window);B.SetStart(H,3,true);};C=((!J||K)&&H==I);if (B&&!J){while (!H.nextSibling&&!C){var N=H.parentNode;if (F[N.nodeName.toLowerCase()]){J=true;C=C||(N==I);break;};H=N;K=true;C=(H==I);L=true;}};if (K) B.SetEnd(H,4,true);if ((J||C)&&B){B._UpdateElementInfo();if (B.StartNode==B.EndNode&&B.StartNode.parentNode==B.StartBlockLimit&&B.StartNode.getAttribute&&B.StartNode.getAttribute('_fck_bookmark')) B=null;else break;};if (C) break;H=FCKDomTools.GetNextSourceNode(H,L,null,I);};if (!A){if (!B){this._NextNode=null;return null;};A=B.StartBlock;if (!A&&!this.EnforceRealBlocks&&B.StartBlockLimit.nodeName.IEquals('DIV','TH','TD')&&B.CheckStartOfBlock()&&B.CheckEndOfBlock()){A=B.StartBlockLimit;}else if (!A||(this.EnforceRealBlocks&&A.nodeName.toLowerCase()=='li')){A=this.Range.Window.document.createElement(FCKConfig.EnterMode=='p'?'p':'div');B.ExtractContents().AppendTo(A);FCKDomTools.TrimNode(A);B.InsertNode(A);D=true;E=true;}else if (A.nodeName.toLowerCase()!='li'){if (!B.CheckStartOfBlock()||!B.CheckEndOfBlock()){A=A.cloneNode(false);B.ExtractContents().AppendTo(A);FCKDomTools.TrimNode(A);var O=B.SplitBlock();D=!O.WasStartOfBlock;E=!O.WasEndOfBlock;B.InsertNode(A);}}else if (!C){this._NextNode=A==I?null:FCKDomTools.GetNextSourceNode(B.EndNode,true,null,I);return A;}};if (D){var P=A.previousSibling;if (P&&P.nodeType==1){if (P.nodeName.toLowerCase()=='br') P.parentNode.removeChild(P);else if (P.lastChild&&P.lastChild.nodeName.IEquals('br')) P.removeChild(P.lastChild);}};if (E){var Q=A.lastChild;if (Q&&Q.nodeType==1&&Q.nodeName.toLowerCase()=='br') A.removeChild(Q);};if (!this._NextNode) this._NextNode=(C||A==I)?null:FCKDomTools.GetNextSourceNode(A,true,null,I);return A;}};
+var FCKDocumentFragment=function(A,B){this.RootNode=B||A.createDocumentFragment();};FCKDocumentFragment.prototype={AppendTo:function(A){A.appendChild(this.RootNode);},AppendHtml:function(A){var B=this.RootNode.ownerDocument.createElement('div');B.innerHTML=A;FCKDomTools.MoveChildren(B,this.RootNode);},InsertAfterNode:function(A){FCKDomTools.InsertAfterNode(A,this.RootNode);}};
+var FCKW3CRange=function(A){this._Document=A;this.startContainer=null;this.startOffset=null;this.endContainer=null;this.endOffset=null;this.collapsed=true;};FCKW3CRange.CreateRange=function(A){return new FCKW3CRange(A);};FCKW3CRange.CreateFromRange=function(A,B){var C=FCKW3CRange.CreateRange(A);C.setStart(B.startContainer,B.startOffset);C.setEnd(B.endContainer,B.endOffset);return C;};FCKW3CRange.prototype={_UpdateCollapsed:function(){this.collapsed=(this.startContainer==this.endContainer&&this.startOffset==this.endOffset);},setStart:function(A,B){this.startContainer=A;this.startOffset=B;if (!this.endContainer){this.endContainer=A;this.endOffset=B;};this._UpdateCollapsed();},setEnd:function(A,B){this.endContainer=A;this.endOffset=B;if (!this.startContainer){this.startContainer=A;this.startOffset=B;};this._UpdateCollapsed();},setStartAfter:function(A){this.setStart(A.parentNode,FCKDomTools.GetIndexOf(A)+1);},setStartBefore:function(A){this.setStart(A.parentNode,FCKDomTools.GetIndexOf(A));},setEndAfter:function(A){this.setEnd(A.parentNode,FCKDomTools.GetIndexOf(A)+1);},setEndBefore:function(A){this.setEnd(A.parentNode,FCKDomTools.GetIndexOf(A));},collapse:function(A){if (A){this.endContainer=this.startContainer;this.endOffset=this.startOffset;}else{this.startContainer=this.endContainer;this.startOffset=this.endOffset;};this.collapsed=true;},selectNodeContents:function(A){this.setStart(A,0);this.setEnd(A,A.nodeType==3?A.data.length:A.childNodes.length);},insertNode:function(A){var B=this.startContainer;var C=this.startOffset;if (B.nodeType==3){B.splitText(C);if (B==this.endContainer) this.setEnd(B.nextSibling,this.endOffset-this.startOffset);FCKDomTools.InsertAfterNode(B,A);return;}else{B.insertBefore(A,B.childNodes[C]||null);if (B==this.endContainer){this.endOffset++;this.collapsed=false;}}},deleteContents:function(){if (this.collapsed) return;this._ExecContentsAction(0);},extractContents:function(){var A=new FCKDocumentFragment(this._Document);if (!this.collapsed) this._ExecContentsAction(1,A);return A;},cloneContents:function(){var A=new FCKDocumentFragment(this._Document);if (!this.collapsed) this._ExecContentsAction(2,A);return A;},_ExecContentsAction:function(A,B){var C=this.startContainer;var D=this.endContainer;var E=this.startOffset;var F=this.endOffset;var G=false;var H=false;if (D.nodeType==3) D=D.splitText(F);else{if (D.childNodes.length>0){if (F>D.childNodes.length-1){D=FCKDomTools.InsertAfterNode(D.lastChild,this._Document.createTextNode(''));H=true;}else D=D.childNodes[F];}};if (C.nodeType==3){C.splitText(E);if (C==D) D=C.nextSibling;}else{if (E==0){C=C.insertBefore(this._Document.createTextNode(''),C.firstChild);G=true;}else if (E>C.childNodes.length-1){C=C.appendChild(this._Document.createTextNode(''));G=true;}else C=C.childNodes[E].previousSibling;};var I=FCKDomTools.GetParents(C);var J=FCKDomTools.GetParents(D);var i,topStart,topEnd;for (i=0;i<I.length;i++){topStart=I[i];topEnd=J[i];if (topStart!=topEnd) break;};var K,levelStartNode,levelClone,currentNode,currentSibling;if (B) K=B.RootNode;for (var j=i;j<I.length;j++){levelStartNode=I[j];if (K&&levelStartNode!=C) levelClone=K.appendChild(levelStartNode.cloneNode(levelStartNode==C));currentNode=levelStartNode.nextSibling;while(currentNode){if (currentNode==J[j]||currentNode==D) break;currentSibling=currentNode.nextSibling;if (A==2) K.appendChild(currentNode.cloneNode(true));else{currentNode.parentNode.removeChild(currentNode);if (A==1) K.appendChild(currentNode);};currentNode=currentSibling;};if (K) K=levelClone;};if (B) K=B.RootNode;for (var k=i;k<J.length;k++){levelStartNode=J[k];if (A>0&&levelStartNode!=D) levelClone=K.appendChild(levelStartNode.cloneNode(levelStartNode==D));if (!I[k]||levelStartNode.parentNode!=I[k].parentNode){currentNode=levelStartNode.previousSibling;while(currentNode){if (currentNode==I[k]||currentNode==C) break;currentSibling=currentNode.previousSibling;if (A==2) K.insertBefore(currentNode.cloneNode(true),K.firstChild);else{currentNode.parentNode.removeChild(currentNode);if (A==1) K.insertBefore(currentNode,K.firstChild);};currentNode=currentSibling;}};if (K) K=levelClone;};if (A==2){var L=this.startContainer;if (L.nodeType==3){L.data+=L.nextSibling.data;L.parentNode.removeChild(L.nextSibling);};var M=this.endContainer;if (M.nodeType==3&&M.nextSibling){M.data+=M.nextSibling.data;M.parentNode.removeChild(M.nextSibling);}}else{if (topStart&&topEnd&&(C.parentNode!=topStart.parentNode||D.parentNode!=topEnd.parentNode)){var N=FCKDomTools.GetIndexOf(topEnd);if (G&&topEnd.parentNode==C.parentNode) N--;this.setStart(topEnd.parentNode,N);};this.collapse(true);};if(G) C.parentNode.removeChild(C);if(H&&D.parentNode) D.parentNode.removeChild(D);},cloneRange:function(){return FCKW3CRange.CreateFromRange(this._Document,this);}};
+var FCKEnterKey=function(A,B,C,D){this.Window=A;this.EnterMode=B||'p';this.ShiftEnterMode=C||'br';var E=new FCKKeystrokeHandler(false);E._EnterKey=this;E.OnKeystroke=FCKEnterKey_OnKeystroke;E.SetKeystrokes([[13,'Enter'],[SHIFT+13,'ShiftEnter'],[8,'Backspace'],[CTRL+8,'CtrlBackspace'],[46,'Delete']]);this.TabText='';if (D>0||FCKBrowserInfo.IsSafari){while (D--) this.TabText+='\xa0';E.SetKeystrokes([9,'Tab']);};E.AttachToElement(A.document);};function FCKEnterKey_OnKeystroke(A,B){var C=this._EnterKey;try{switch (B){case 'Enter':return C.DoEnter();break;case 'ShiftEnter':return C.DoShiftEnter();break;case 'Backspace':return C.DoBackspace();break;case 'Delete':return C.DoDelete();break;case 'Tab':return C.DoTab();break;case 'CtrlBackspace':return C.DoCtrlBackspace();break;}}catch (e){};return false;};FCKEnterKey.prototype.DoEnter=function(A,B){FCKUndo.SaveUndoStep();this._HasShift=(B===true);var C=FCKSelection.GetParentElement();var D=new FCKElementPath(C);var E=A||this.EnterMode;if (E=='br'||D.Block&&D.Block.tagName.toLowerCase()=='pre') return this._ExecuteEnterBr();else return this._ExecuteEnterBlock(E);};FCKEnterKey.prototype.DoShiftEnter=function(){return this.DoEnter(this.ShiftEnterMode,true);};FCKEnterKey.prototype.DoBackspace=function(){var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(B,this.Window.document.body)){this._FixIESelectAllBug(B);return true;};var C=B.CheckIsCollapsed();if (!C){if (FCKBrowserInfo.IsIE&&this.Window.document.selection.type.toLowerCase()=="control"){var D=this.Window.document.selection.createRange();for (var i=D.length-1;i>=0;i--){var E=D.item(i);E.parentNode.removeChild(E);};return true;};return false;};if (FCKBrowserInfo.IsIE){var F=FCKDomTools.GetPreviousSourceElement(B.StartNode,true);if (F&&F.nodeName.toLowerCase()=='br'){var G=B.Clone();G.SetStart(F,4);if (G.CheckIsEmpty()){F.parentNode.removeChild(F);return true;}}};var H=B.StartBlock;var I=B.EndBlock;if (B.StartBlockLimit==B.EndBlockLimit&&H&&I){if (!C){var J=B.CheckEndOfBlock();B.DeleteContents();if (H!=I){B.SetStart(I,1);B.SetEnd(I,1);};B.Select();A=(H==I);};if (B.CheckStartOfBlock()){var K=B.StartBlock;var L=FCKDomTools.GetPreviousSourceElement(K,true,['BODY',B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,L,K);}else if (FCKBrowserInfo.IsGeckoLike){B.Select();}};B.Release();return A;};FCKEnterKey.prototype.DoCtrlBackspace=function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(this.Window);A.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(A,this.Window.document.body)){this._FixIESelectAllBug(A);return true;};return false;};FCKEnterKey.prototype._ExecuteBackspace=function(A,B,C){var D=false;if (!B&&C&&C.nodeName.IEquals('LI')&&C.parentNode.parentNode.nodeName.IEquals('LI')){this._OutdentWithSelection(C,A);return true;};if (B&&B.nodeName.IEquals('LI')){var E=FCKDomTools.GetLastChild(B,['UL','OL']);while (E){B=FCKDomTools.GetLastChild(E,'LI');E=FCKDomTools.GetLastChild(B,['UL','OL']);}};if (B&&C){if (C.nodeName.IEquals('LI')&&!B.nodeName.IEquals('LI')){this._OutdentWithSelection(C,A);return true;};var F=C.parentNode;var G=B.nodeName.toLowerCase();if (FCKListsLib.EmptyElements[G]!=null||G=='table'){FCKDomTools.RemoveNode(B);D=true;}else{FCKDomTools.RemoveNode(C);while (F.innerHTML.Trim().length==0){var H=F.parentNode;H.removeChild(F);F=H;};FCKDomTools.LTrimNode(C);FCKDomTools.RTrimNode(B);A.SetStart(B,2,true);A.Collapse(true);var I=A.CreateBookmark(true);if (!C.tagName.IEquals(['TABLE'])) FCKDomTools.MoveChildren(C,B);A.SelectBookmark(I);D=true;}};return D;};FCKEnterKey.prototype.DoDelete=function(){FCKUndo.SaveUndoStep();var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(B,this.Window.document.body)){this._FixIESelectAllBug(B);return true;};if (B.CheckIsCollapsed()&&B.CheckEndOfBlock(FCKBrowserInfo.IsGeckoLike)){var C=B.StartBlock;var D=FCKTools.GetElementAscensor(C,'td');var E=FCKDomTools.GetNextSourceElement(C,true,[B.StartBlockLimit.nodeName],['UL','OL','TR'],true);if (D){var F=FCKTools.GetElementAscensor(E,'td');if (F!=D) return true;};A=this._ExecuteBackspace(B,C,E);};B.Release();return A;};FCKEnterKey.prototype.DoTab=function(){var A=new FCKDomRange(this.Window);A.MoveToSelection();var B=A._Range.startContainer;while (B){if (B.nodeType==1){var C=B.tagName.toLowerCase();if (C=="tr"||C=="td"||C=="th"||C=="tbody"||C=="table") return false;else break;};B=B.parentNode;};if (this.TabText){A.DeleteContents();A.InsertNode(this.Window.document.createTextNode(this.TabText));A.Collapse(false);A.Select();};return true;};FCKEnterKey.prototype._ExecuteEnterBlock=function(A,B){var C=B||new FCKDomRange(this.Window);var D=C.SplitBlock(A);if (D){var E=D.PreviousBlock;var F=D.NextBlock;var G=D.WasStartOfBlock;var H=D.WasEndOfBlock;if (F){if (F.parentNode.nodeName.IEquals('li')){FCKDomTools.BreakParent(F,F.parentNode);FCKDomTools.MoveNode(F,F.nextSibling,true);}}else if (E&&E.parentNode.nodeName.IEquals('li')){FCKDomTools.BreakParent(E,E.parentNode);C.MoveToElementEditStart(E.nextSibling);FCKDomTools.MoveNode(E,E.previousSibling);};if (!G&&!H){if (F.nodeName.IEquals('li')&&F.firstChild&&F.firstChild.nodeName.IEquals(['ul','ol'])) F.insertBefore(FCKTools.GetElementDocument(F).createTextNode('\xa0'),F.firstChild);if (F) C.MoveToElementEditStart(F);}else{if (G&&H&&E.tagName.toUpperCase()=='LI'){C.MoveToElementStart(E);this._OutdentWithSelection(E,C);C.Release();return true;};var I;if (E){var J=E.tagName.toUpperCase();if (!this._HasShift&&!(/^H[1-6]$/).test(J)){I=FCKDomTools.CloneElement(E);}}else if (F) I=FCKDomTools.CloneElement(F);if (!I) I=this.Window.document.createElement(A);var K=D.ElementPath;if (K){for (var i=0,len=K.Elements.length;i<len;i++){var L=K.Elements[i];if (L==K.Block||L==K.BlockLimit) break;if (FCKListsLib.InlineChildReqElements[L.nodeName.toLowerCase()]){L=FCKDomTools.CloneElement(L);FCKDomTools.MoveChildren(I,L);I.appendChild(L);}}};if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(I);C.InsertNode(I);if (FCKBrowserInfo.IsIE){C.MoveToElementEditStart(I);C.Select();};C.MoveToElementEditStart(G&&!H?F:I);};if (FCKBrowserInfo.IsGeckoLike){if (F){var M=this.Window.document.createElement('span');M.innerHTML='&nbsp;';C.InsertNode(M);FCKDomTools.ScrollIntoView(M,false);C.DeleteContents();}else{FCKDomTools.ScrollIntoView(F||I,false);}};C.Select();};C.Release();return true;};FCKEnterKey.prototype._ExecuteEnterBr=function(A){var B=new FCKDomRange(this.Window);B.MoveToSelection();if (B.StartBlockLimit==B.EndBlockLimit){B.DeleteContents();B.MoveToSelection();var C=B.CheckStartOfBlock();var D=B.CheckEndOfBlock();var E=B.StartBlock?B.StartBlock.tagName.toUpperCase():'';var F=this._HasShift;var G=false;if (!F&&E=='LI') return this._ExecuteEnterBlock(null,B);if (!F&&D&&(/^H[1-6]$/).test(E)){FCKDomTools.InsertAfterNode(B.StartBlock,this.Window.document.createElement('br'));if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(B.StartBlock,this.Window.document.createTextNode(''));B.SetStart(B.StartBlock.nextSibling,FCKBrowserInfo.IsIE?3:1);}else{var H;G=E.IEquals('pre');if (G) H=this.Window.document.createTextNode(FCKBrowserInfo.IsIE?'\r':'\n');else H=this.Window.document.createElement('br');B.InsertNode(H);if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(H,this.Window.document.createTextNode(''));if (D&&FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(H.parentNode);if (FCKBrowserInfo.IsIE) B.SetStart(H,4);else B.SetStart(H.nextSibling,1);if (!FCKBrowserInfo.IsIE){var I=null;if (FCKBrowserInfo.IsOpera) I=this.Window.document.createElement('span');else I=this.Window.document.createElement('br');H.parentNode.insertBefore(I,H.nextSibling);FCKDomTools.ScrollIntoView(I,false);I.parentNode.removeChild(I);}};B.Collapse(true);B.Select(G);};B.Release();return true;};FCKEnterKey.prototype._OutdentWithSelection=function(A,B){var C=B.CreateBookmark();FCKListHandler.OutdentListItem(A);B.MoveToBookmark(C);B.Select();};FCKEnterKey.prototype._CheckIsAllContentsIncluded=function(A,B){var C=false;var D=false;if (A.StartContainer==B||A.StartContainer==B.firstChild) C=(A._Range.startOffset==0);if (A.EndContainer==B||A.EndContainer==B.lastChild){var E=A.EndContainer.nodeType==3?A.EndContainer.length:A.EndContainer.childNodes.length;D=(A._Range.endOffset==E);};return C&&D;};FCKEnterKey.prototype._FixIESelectAllBug=function(A){var B=this.Window.document;B.body.innerHTML='';var C;if (FCKConfig.EnterMode.IEquals(['div','p'])){C=B.createElement(FCKConfig.EnterMode);B.body.appendChild(C);}else C=B.body;A.MoveToNodeContents(C);A.Collapse(true);A.Select();A.Release();};
+var FCKDocumentProcessor={};FCKDocumentProcessor._Items=[];FCKDocumentProcessor.AppendNew=function(){var A={};this._Items.push(A);return A;};FCKDocumentProcessor.Process=function(A){var B=FCK.IsDirty();var C,i=0;while((C=this._Items[i++])) C.ProcessDocument(A);if (!B) FCK.ResetIsDirty();};var FCKDocumentProcessor_CreateFakeImage=function(A,B){var C=FCKTools.GetElementDocument(B).createElement('IMG');C.className=A;C.src=FCKConfig.BasePath+'images/spacer.gif';C.setAttribute('_fckfakelement','true',0);C.setAttribute('_fckrealelement',FCKTempBin.AddElement(B),0);return C;};if (FCKBrowserInfo.IsIE||FCKBrowserInfo.IsOpera){var FCKAnchorsProcessor=FCKDocumentProcessor.AppendNew();FCKAnchorsProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('A');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.name.length>0){if (C.innerHTML!==''){if (FCKBrowserInfo.IsIE) C.className+=' FCK__AnchorC';}else{var D=FCKDocumentProcessor_CreateFakeImage('FCK__Anchor',C.cloneNode(true));D.setAttribute('_fckanchor','true',0);C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}}}};var FCKPageBreaksProcessor=FCKDocumentProcessor.AppendNew();FCKPageBreaksProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('DIV');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.style.pageBreakAfter=='always'&&C.childNodes.length==1&&C.childNodes[0].style&&C.childNodes[0].style.display=='none'){var D=FCKDocumentProcessor_CreateFakeImage('FCK__PageBreak',C.cloneNode(true));C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}};var FCKEmbedAndObjectProcessor=(function(){var A=[];var B=function(el){var C=el.cloneNode(true);var D;var E=D=FCKDocumentProcessor_CreateFakeImage('FCK__UnknownObject',C);FCKEmbedAndObjectProcessor.RefreshView(E,el);for (var i=0;i<A.length;i++) D=A[i](el,D)||D;if (D!=E) FCKTempBin.RemoveElement(E.getAttribute('_fckrealelement'));el.parentNode.replaceChild(D,el);};var F=function(elementName,doc){var G=doc.getElementsByTagName(elementName);for (var i=G.length-1;i>=0;i--) B(G[i]);};var H=function(doc){F('object',doc);F('embed',doc);};return FCKTools.Merge(FCKDocumentProcessor.AppendNew(),{ProcessDocument:function(doc){if (FCKBrowserInfo.IsGecko) FCKTools.RunFunction(H,this,[doc]);else H(doc);},RefreshView:function(placeHolder,original){if (original.getAttribute('width')>0) placeHolder.style.width=FCKTools.ConvertHtmlSizeToStyle(original.getAttribute('width'));if (original.getAttribute('height')>0) placeHolder.style.height=FCKTools.ConvertHtmlSizeToStyle(original.getAttribute('height'));},AddCustomHandler:function(func){A.push(func);}});})();FCK.GetRealElement=function(A){var e=FCKTempBin.Elements[A.getAttribute('_fckrealelement')];if (A.getAttribute('_fckflash')){if (A.style.width.length>0) e.width=FCKTools.ConvertStyleSizeToHtml(A.style.width);if (A.style.height.length>0) e.height=FCKTools.ConvertStyleSizeToHtml(A.style.height);};return e;};if (FCKBrowserInfo.IsIE){FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByTagName('HR');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){var D=A.createElement('hr');D.mergeAttributes(C,true);FCKDomTools.InsertAfterNode(C,D);C.parentNode.removeChild(C);}}};FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByTagName('INPUT');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.type=='hidden'){var D=FCKDocumentProcessor_CreateFakeImage('FCK__InputHidden',C.cloneNode(true));D.setAttribute('_fckinputhidden','true',0);C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}};FCKEmbedAndObjectProcessor.AddCustomHandler(function(A,B){if (!(A.nodeName.IEquals('embed')&&(A.type=='application/x-shockwave-flash'||/\.swf($|#|\?)/i.test(A.src)))) return;B.className='FCK__Flash';B.setAttribute('_fckflash','true',0);});if (FCKBrowserInfo.IsSafari){FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByClassName?A.getElementsByClassName('Apple-style-span'):Array.prototype.filter.call(A.getElementsByTagName('span'),function(item){ return item.className=='Apple-style-span';});for (var i=B.length-1;i>=0;i--) FCKDomTools.RemoveNode(B[i],true);}};
+var FCKSelection=FCK.Selection={GetParentBlock:function(){var A=this.GetParentElement();while (A){if (FCKListsLib.BlockBoundaries[A.nodeName.toLowerCase()]) break;A=A.parentNode;};return A;},ApplyStyle:function(A){FCKStyles.ApplyStyle(new FCKStyle(A));}};
+FCKSelection.GetType=function(){var A='Text';var B;try { B=this.GetSelection();} catch (e) {};if (B&&B.rangeCount==1){var C=B.getRangeAt(0);if (C.startContainer==C.endContainer&&(C.endOffset-C.startOffset)==1&&C.startContainer.nodeType==1&&FCKListsLib.StyleObjectElements[C.startContainer.childNodes[C.startOffset].nodeName.toLowerCase()]){A='Control';}};return A;};FCKSelection.GetSelectedElement=function(){var A=!!FCK.EditorWindow&&this.GetSelection();if (!A||A.rangeCount<1) return null;var B=A.getRangeAt(0);if (B.startContainer!=B.endContainer||B.startContainer.nodeType!=1||B.startOffset!=B.endOffset-1) return null;var C=B.startContainer.childNodes[B.startOffset];if (C.nodeType!=1) return null;return C;};FCKSelection.GetParentElement=function(){if (this.GetType()=='Control') return FCKSelection.GetSelectedElement().parentNode;else{var A=this.GetSelection();if (A){if (A.anchorNode&&A.anchorNode==A.focusNode){var B=A.getRangeAt(0);if (B.collapsed||B.startContainer.nodeType==3) return A.anchorNode.parentNode;else return A.anchorNode;};var C=new FCKElementPath(A.anchorNode);var D=new FCKElementPath(A.focusNode);var E=null;var F=null;if (C.Elements.length>D.Elements.length){E=C.Elements;F=D.Elements;}else{E=D.Elements;F=C.Elements;};var G=E.length-F.length;for(var i=0;i<F.length;i++){if (E[G+i]==F[i]) return F[i];};return null;}};return null;};FCKSelection.GetBoundaryParentElement=function(A){if (!FCK.EditorWindow) return null;if (this.GetType()=='Control') return FCKSelection.GetSelectedElement().parentNode;else{var B=this.GetSelection();if (B&&B.rangeCount>0){var C=B.getRangeAt(A?0:(B.rangeCount-1));var D=A?C.startContainer:C.endContainer;return (D.nodeType==1?D:D.parentNode);}};return null;};FCKSelection.SelectNode=function(A){var B=FCK.EditorDocument.createRange();B.selectNode(A);var C=this.GetSelection();C.removeAllRanges();C.addRange(B);};FCKSelection.Collapse=function(A){var B=this.GetSelection();if (A==null||A===true) B.collapseToStart();else B.collapseToEnd();};FCKSelection.HasAncestorNode=function(A){var B=this.GetSelectedElement();if (!B&&FCK.EditorWindow){try { B=this.GetSelection().getRangeAt(0).startContainer;}catch(e){}}while (B){if (B.nodeType==1&&B.nodeName.IEquals(A)) return true;B=B.parentNode;};return false;};FCKSelection.MoveToAncestorNode=function(A){var B;var C=this.GetSelectedElement();if (!C) C=this.GetSelection().getRangeAt(0).startContainer;while (C){if (C.nodeName.IEquals(A)) return C;C=C.parentNode;};return null;};FCKSelection.Delete=function(){var A=this.GetSelection();for (var i=0;i<A.rangeCount;i++){A.getRangeAt(i).deleteContents();};return A;};FCKSelection.GetSelection=function(){return FCK.EditorWindow.getSelection();};FCKSelection.Save=function(){};FCKSelection.Restore=function(){};FCKSelection.Release=function(){};
+var FCKTableHandler={};FCKTableHandler.InsertRow=function(A){var B=FCKSelection.MoveToAncestorNode('TR');if (!B) return;var C=B.cloneNode(true);B.parentNode.insertBefore(C,B);FCKTableHandler.ClearRow(A?C:B);};FCKTableHandler.DeleteRows=function(A){if (!A){var B=FCKTableHandler.GetSelectedCells();var C=[];for (var i=0;i<B.length;i++){var D=B[i].parentNode;C[D.rowIndex]=D;};for (var i=C.length;i>=0;i--){if (C[i]) FCKTableHandler.DeleteRows(C[i]);};return;};var E=FCKTools.GetElementAscensor(A,'TABLE');if (E.rows.length==1){FCKTableHandler.DeleteTable(E);return;};A.parentNode.removeChild(A);};FCKTableHandler.DeleteTable=function(A){if (!A){A=FCKSelection.GetSelectedElement();if (!A||A.tagName!='TABLE') A=FCKSelection.MoveToAncestorNode('TABLE');};if (!A) return;FCKSelection.SelectNode(A);FCKSelection.Collapse();if (A.parentNode.childNodes.length==1) A.parentNode.parentNode.removeChild(A.parentNode);else A.parentNode.removeChild(A);};FCKTableHandler.InsertColumn=function(A){var B=null;var C=this.GetSelectedCells();if (C&&C.length) B=C[A?0:(C.length-1)];if (!B) return;var D=FCKTools.GetElementAscensor(B,'TABLE');var E=B.cellIndex;for (var i=0;i<D.rows.length;i++){var F=D.rows[i];if (F.cells.length<(E+1)) continue;B=F.cells[E].cloneNode(false);if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(B);var G=F.cells[E];F.insertBefore(B,(A?G:G.nextSibling));}};FCKTableHandler.DeleteColumns=function(A){if (!A){var B=FCKTableHandler.GetSelectedCells();for (var i=B.length;i>=0;i--){if (B[i]) FCKTableHandler.DeleteColumns(B[i]);};return;};if (!A) return;var C=FCKTools.GetElementAscensor(A,'TABLE');var D=A.cellIndex;for (var i=C.rows.length-1;i>=0;i--){var E=C.rows[i];if (D==0&&E.cells.length==1){FCKTableHandler.DeleteRows(E);continue;};if (E.cells[D]) E.removeChild(E.cells[D]);}};FCKTableHandler.InsertCell=function(A,B){var C=null;var D=this.GetSelectedCells();if (D&&D.length) C=D[B?0:(D.length-1)];if (!C) return null;var E=FCK.EditorDocument.createElement('TD');if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(E);if (!B&&C.cellIndex==C.parentNode.cells.length-1) C.parentNode.appendChild(E);else C.parentNode.insertBefore(E,B?C:C.nextSibling);return E;};FCKTableHandler.DeleteCell=function(A){if (A.parentNode.cells.length==1){FCKTableHandler.DeleteRows(A.parentNode);return;};A.parentNode.removeChild(A);};FCKTableHandler.DeleteCells=function(){var A=FCKTableHandler.GetSelectedCells();for (var i=A.length-1;i>=0;i--){FCKTableHandler.DeleteCell(A[i]);}};FCKTableHandler._MarkCells=function(A,B){for (var i=0;i<A.length;i++) A[i][B]=true;};FCKTableHandler._UnmarkCells=function(A,B){for (var i=0;i<A.length;i++){FCKDomTools.ClearElementJSProperty(A[i],B);}};FCKTableHandler._ReplaceCellsByMarker=function(A,B,C){for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){if (A[i][j][B]) A[i][j]=C;}}};FCKTableHandler._GetMarkerGeometry=function(A,B,C,D){var E=0;var F=0;var G=0;var H=0;for (var i=C;A[B][i]&&A[B][i][D];i++) E++;for (var i=C-1;A[B][i]&&A[B][i][D];i--){E++;G++;};for (var i=B;A[i]&&A[i][C]&&A[i][C][D];i++) F++;for (var i=B-1;A[i]&&A[i][C]&&A[i][C][D];i--){F++;H++;};return { 'width':E,'height':F,'x':G,'y':H };};FCKTableHandler.CheckIsSelectionRectangular=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length<1) return false;for (var i=0;i<A.length;i++){if (A[i].parentNode.parentNode!=A[0].parentNode.parentNode) return false;};this._MarkCells(A,'_CellSelected');var B=this._CreateTableMap(A[0]);var C=A[0].parentNode.rowIndex;var D=this._GetCellIndexSpan(B,C,A[0]);var E=this._GetMarkerGeometry(B,C,D,'_CellSelected');var F=D-E.x;var G=C-E.y;if (E.width>=E.height){for (D=F;D<F+E.width;D++){C=G+(D-F) % E.height;if (!B[C]||!B[C][D]){this._UnmarkCells(A,'_CellSelected');return false;};var g=this._GetMarkerGeometry(B,C,D,'_CellSelected');if (g.width!=E.width||g.height!=E.height){this._UnmarkCells(A,'_CellSelected');return false;}}}else{for (C=G;C<G+E.height;C++){D=F+(C-G) % E.width;if (!B[C]||!B[C][D]){this._UnmarkCells(A,'_CellSelected');return false;};var g=this._GetMarkerGeometry(B,C,D,'_CellSelected');if (g.width!=E.width||g.height!=E.height){this._UnmarkCells(A,'_CellSelected');return false;}}};this._UnmarkCells(A,'_CellSelected');return true;};FCKTableHandler.MergeCells=function(){var A=this.GetSelectedCells();if (A.length<2) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);this._MarkCells(A,'_SelectedCells');var F=this._GetMarkerGeometry(C,D,E,'_SelectedCells');var G=E-F.x;var H=D-F.y;var I=FCKTools.GetElementDocument(B).createDocumentFragment();for (var i=0;i<F.height;i++){var J=0;for (var j=0;j<F.width;j++){var K=C[H+i][G+j];while (K.childNodes.length>0){var L=K.removeChild(K.firstChild);if (L.nodeType!=1||(L.getAttribute('type',2)!='_moz'&&L.getAttribute('_moz_dirty')!=null)){I.appendChild(L);J++;}}};if (J>0) I.appendChild(FCK.EditorDocument.createElement('br'));};this._ReplaceCellsByMarker(C,'_SelectedCells',B);this._UnmarkCells(A,'_SelectedCells');this._InstallTableMap(C,B.parentNode.parentNode.parentNode);B.appendChild(I);if (FCKBrowserInfo.IsGeckoLike&&(!B.firstChild)) FCKTools.AppendBogusBr(B);this._MoveCaretToCell(B,false);};FCKTableHandler.MergeRight=function(){var A=this.GetMergeRightTarget();if (A==null) return;var B=A.refCell;var C=A.tableMap;var D=A.nextCell;var E=FCK.EditorDocument.createDocumentFragment();while (D&&D.childNodes&&D.childNodes.length>0) E.appendChild(D.removeChild(D.firstChild));D.parentNode.removeChild(D);B.appendChild(E);this._MarkCells([D],'_Replace');this._ReplaceCellsByMarker(C,'_Replace',B);this._InstallTableMap(C,B.parentNode.parentNode.parentNode);this._MoveCaretToCell(B,false);};FCKTableHandler.MergeDown=function(){var A=this.GetMergeDownTarget();if (A==null) return;var B=A.refCell;var C=A.tableMap;var D=A.nextCell;var E=FCKTools.GetElementDocument(B).createDocumentFragment();while (D&&D.childNodes&&D.childNodes.length>0) E.appendChild(D.removeChild(D.firstChild));if (E.firstChild) E.insertBefore(FCK.EditorDocument.createElement('br'),E.firstChild);B.appendChild(E);this._MarkCells([D],'_Replace');this._ReplaceCellsByMarker(C,'_Replace',B);this._InstallTableMap(C,B.parentNode.parentNode.parentNode);this._MoveCaretToCell(B,false);};FCKTableHandler.HorizontalSplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=FCKTableHandler._GetCellIndexSpan(C,D,B);var F=isNaN(B.colSpan)?1:B.colSpan;if (F>1){var G=Math.ceil(F/2);var H=FCK.EditorDocument.createElement(B.nodeName);if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(H);var I=E+G;var J=E+F;var K=isNaN(B.rowSpan)?1:B.rowSpan;for (var r=D;r<D+K;r++){for (var i=I;i<J;i++) C[r][i]=H;}}else{var L=[];for (var i=0;i<C.length;i++){var M=C[i].slice(0,E);if (C[i].length<=E){L.push(M);continue;};if (C[i][E]==B){M.push(B);M.push(FCK.EditorDocument.createElement(B.nodeName));if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(M[M.length-1]);}else{M.push(C[i][E]);M.push(C[i][E]);};for (var j=E+1;j<C[i].length;j++) M.push(C[i][j]);L.push(M);};C=L;};this._InstallTableMap(C,B.parentNode.parentNode.parentNode);};FCKTableHandler.VerticalSplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=FCKTableHandler._GetCellIndexSpan(C,D,B);var F=isNaN(B.colSpan)?1:B.colSpan;var G=B.rowSpan;if (isNaN(G)) G=1;if (G>1){B.rowSpan=Math.ceil(G/2);var H=D+Math.ceil(G/2);var I=C[H];var J=null;for (var i=E+1;i<I.length;i++){if (I[i].parentNode.rowIndex==H){J=I[i];break;}};var K=FCK.EditorDocument.createElement(B.nodeName);K.rowSpan=Math.floor(G/2);if (F>1) K.colSpan=F;if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(K);B.parentNode.parentNode.parentNode.rows[H].insertBefore(K,J);}else{var L=B.parentNode.sectionRowIndex+1;var M=FCK.EditorDocument.createElement('tr');var N=B.parentNode.parentNode;if (N.rows.length>L) N.insertBefore(M,N.rows[L]);else N.appendChild(M);for (var i=0;i<C[D].length;){var O=C[D][i].colSpan;if (isNaN(O)||O<1) O=1;if (i==E){i+=O;continue;};var P=C[D][i].rowSpan;if (isNaN(P)) P=1;C[D][i].rowSpan=P+1;i+=O;};var K=FCK.EditorDocument.createElement(B.nodeName);if (F>1) K.colSpan=F;if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(K);M.appendChild(K);}};FCKTableHandler._GetCellIndexSpan=function(A,B,C){if (A.length<B+1) return null;var D=A[B];for (var c=0;c<D.length;c++){if (D[c]==C) return c;};return null;};FCKTableHandler._GetCellLocation=function(A,B){for (var i=0;i<A.length;i++){for (var c=0;c<A[i].length;c++){if (A[i][c]==B) return [i,c];}};return null;};FCKTableHandler._CreateTableMap=function(A){var B=(A.nodeName=='TABLE'?A:A.parentNode.parentNode.parentNode);var C=B.rows;var r=-1;var D=[];for (var i=0;i<C.length;i++){r++;if (!D[r]) D[r]=[];var c=-1;for (var j=0;j<C[i].cells.length;j++){var E=C[i].cells[j];c++;while (D[r][c]) c++;var F=isNaN(E.colSpan)?1:E.colSpan;var G=isNaN(E.rowSpan)?1:E.rowSpan;for (var H=0;H<G;H++){if (!D[r+H]) D[r+H]=[];for (var I=0;I<F;I++){D[r+H][c+I]=C[i].cells[j];}};c+=F-1;}};return D;};FCKTableHandler._InstallTableMap=function(A,B){var C=FCKBrowserInfo.IsIE?"_fckrowspan":"rowSpan";for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];if (D.parentNode) D.parentNode.removeChild(D);D.colSpan=D[C]=1;}};var E=0;for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];if (!D) continue;if (j>E) E=j;if (D._colScanned===true) continue;if (A[i][j-1]==D) D.colSpan++;if (A[i][j+1]!=D) D._colScanned=true;}};for (var i=0;i<=E;i++){for (var j=0;j<A.length;j++){if (!A[j]) continue;var D=A[j][i];if (!D||D._rowScanned===true) continue;if (A[j-1]&&A[j-1][i]==D) D[C]++;if (!A[j+1]||A[j+1][i]!=D) D._rowScanned=true;}};for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];FCKDomTools.ClearElementJSProperty(D,'_colScanned');FCKDomTools.ClearElementJSProperty(D,'_rowScanned');}};for (var i=0;i<A.length;i++){var I=FCK.EditorDocument.createElement('tr');for (var j=0;j<A[i].length;){var D=A[i][j];if (A[i-1]&&A[i-1][j]==D){j+=D.colSpan;continue;};I.appendChild(D);if (C!='rowSpan'){D.rowSpan=D[C];D.removeAttribute(C);};j+=D.colSpan;if (D.colSpan==1) D.removeAttribute('colspan');if (D.rowSpan==1) D.removeAttribute('rowspan');};if (FCKBrowserInfo.IsIE){B.rows[i].replaceNode(I);}else{B.rows[i].innerHTML='';FCKDomTools.MoveChildren(I,B.rows[i]);}}};FCKTableHandler._MoveCaretToCell=function (A,B){var C=new FCKDomRange(FCK.EditorWindow);C.MoveToNodeContents(A);C.Collapse(B);C.Select();};FCKTableHandler.ClearRow=function(A){var B=A.cells;for (var i=0;i<B.length;i++){B[i].innerHTML='';if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(B[i]);}};FCKTableHandler.GetMergeRightTarget=function(){var A=this.GetSelectedCells();if (A.length!=1) return null;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);var F=E+(isNaN(B.colSpan)?1:B.colSpan);var G=C[D][F];if (!G) return null;this._MarkCells([B,G],'_SizeTest');var H=this._GetMarkerGeometry(C,D,E,'_SizeTest');var I=this._GetMarkerGeometry(C,D,F,'_SizeTest');this._UnmarkCells([B,G],'_SizeTest');if (H.height!=I.height||H.y!=I.y) return null;return { 'refCell':B,'nextCell':G,'tableMap':C };};FCKTableHandler.GetMergeDownTarget=function(){var A=this.GetSelectedCells();if (A.length!=1) return null;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);var F=D+(isNaN(B.rowSpan)?1:B.rowSpan);if (!C[F]) return null;var G=C[F][E];if (!G) return null;if (B.parentNode.parentNode!=G.parentNode.parentNode) return null;this._MarkCells([B,G],'_SizeTest');var H=this._GetMarkerGeometry(C,D,E,'_SizeTest');var I=this._GetMarkerGeometry(C,F,E,'_SizeTest');this._UnmarkCells([B,G],'_SizeTest');if (H.width!=I.width||H.x!=I.x) return null;return { 'refCell':B,'nextCell':G,'tableMap':C };};
+FCKTableHandler.GetSelectedCells=function(){var A=[];var B=FCKSelection.GetSelection();if (B.rangeCount==1&&B.anchorNode.nodeType==3){var C=FCKTools.GetElementAscensor(B.anchorNode,'TD,TH');if (C) A[0]=C;return A;};for (var i=0;i<B.rangeCount;i++){var D=B.getRangeAt(i);var E;if (D.startContainer.tagName.Equals('TD','TH')) E=D.startContainer;else E=D.startContainer.childNodes[D.startOffset];if (E.nodeName.Equals('TD','TH')) A[A.length]=E;};return A;};
+var FCKXml=function(){this.Error=false;};FCKXml.GetAttribute=function(A,B,C){var D=A.attributes.getNamedItem(B);return D?D.value:C;};FCKXml.TransformToObject=function(A){if (!A) return null;var B={};var C=A.attributes;for (var i=0;i<C.length;i++){var D=C[i];B[D.name]=D.value;};var E=A.childNodes;for (i=0;i<E.length;i++){var F=E[i];if (F.nodeType==1){var G='$'+F.nodeName;var H=B[G];if (!H) H=B[G]=[];H.push(this.TransformToObject(F));}};return B;};
+FCKXml.prototype={LoadUrl:function(A){this.Error=false;var B;var C=FCKTools.CreateXmlObject('XmlHttp');C.open('GET',A,false);C.send(null);if (C.status==200||C.status==304||(C.status==0&&C.readyState==4)){B=C.responseXML;if (!B) B=(new DOMParser()).parseFromString(C.responseText,'text/xml');}else B=null;if (B){try{var D=B.firstChild;}catch (e){B=(new DOMParser()).parseFromString(C.responseText,'text/xml');}};if (!B||!B.firstChild){this.Error=true;if (window.confirm('Error loading "'+A+'" (HTTP Status: '+C.status+').\r\nDo you want to see the server response dump?')) alert(C.responseText);};this.DOMDocument=B;},SelectNodes:function(A,B){if (this.Error) return [];var C=[];var D=this.DOMDocument.evaluate(A,B?B:this.DOMDocument,this.DOMDocument.createNSResolver(this.DOMDocument.documentElement),XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);if (D){var E=D.iterateNext();while(E){C[C.length]=E;E=D.iterateNext();}};return C;},SelectSingleNode:function(A,B){if (this.Error) return null;var C=this.DOMDocument.evaluate(A,B?B:this.DOMDocument,this.DOMDocument.createNSResolver(this.DOMDocument.documentElement),9,null);if (C&&C.singleNodeValue) return C.singleNodeValue;else return null;}};
+var FCKNamedCommand=function(A){this.Name=A;};FCKNamedCommand.prototype.Execute=function(){FCK.ExecuteNamedCommand(this.Name);};FCKNamedCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState(this.Name);};
+var FCKStyleCommand=function(){};FCKStyleCommand.prototype={Name:'Style',Execute:function(A,B){FCKUndo.SaveUndoStep();if (B.Selected) FCK.Styles.RemoveStyle(B.Style);else FCK.Styles.ApplyStyle(B.Style);FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorDocument) return -1;if (FCKSelection.GetType()=='Control'){var A=FCKSelection.GetSelectedElement();if (!A||!FCKStyles.CheckHasObjectStyle(A.nodeName.toLowerCase())) return -1;};return 0;}};
+var FCKDialogCommand=function(A,B,C,D,E,F,G,H){this.Name=A;this.Title=B;this.Url=C;this.Width=D;this.Height=E;this.CustomValue=H;this.GetStateFunction=F;this.GetStateParam=G;this.Resizable=false;};FCKDialogCommand.prototype.Execute=function(){FCKDialog.OpenDialog('FCKDialog_'+this.Name,this.Title,this.Url,this.Width,this.Height,this.CustomValue,this.Resizable);};FCKDialogCommand.prototype.GetState=function(){if (this.GetStateFunction) return this.GetStateFunction(this.GetStateParam);else return FCK.EditMode==0?0:-1;};var FCKUndefinedCommand=function(){this.Name='Undefined';};FCKUndefinedCommand.prototype.Execute=function(){alert(FCKLang.NotImplemented);};FCKUndefinedCommand.prototype.GetState=function(){return 0;};var FCKFormatBlockCommand=function(){};FCKFormatBlockCommand.prototype={Name:'FormatBlock',Execute:FCKStyleCommand.prototype.Execute,GetState:function(){return FCK.EditorDocument?0:-1;}};var FCKFontNameCommand=function(){};FCKFontNameCommand.prototype={Name:'FontName',Execute:FCKStyleCommand.prototype.Execute,GetState:FCKFormatBlockCommand.prototype.GetState};var FCKFontSizeCommand=function(){};FCKFontSizeCommand.prototype={Name:'FontSize',Execute:FCKStyleCommand.prototype.Execute,GetState:FCKFormatBlockCommand.prototype.GetState};var FCKPreviewCommand=function(){this.Name='Preview';};FCKPreviewCommand.prototype.Execute=function(){FCK.Preview();};FCKPreviewCommand.prototype.GetState=function(){return 0;};var FCKSaveCommand=function(){this.Name='Save';};FCKSaveCommand.prototype.Execute=function(){var A=FCK.GetParentForm();if (typeof(A.onsubmit)=='function'){var B=A.onsubmit();if (B!=null&&B===false) return;};if (typeof(A.submit)=='function') A.submit();else A.submit.click();};FCKSaveCommand.prototype.GetState=function(){return 0;};var FCKNewPageCommand=function(){this.Name='NewPage';};FCKNewPageCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();FCK.SetData('');FCKUndo.Typing=true;FCK.Focus();};FCKNewPageCommand.prototype.GetState=function(){return 0;};var FCKSourceCommand=function(){this.Name='Source';};FCKSourceCommand.prototype.Execute=function(){if (FCKConfig.SourcePopup){var A=FCKConfig.ScreenWidth*0.65;var B=FCKConfig.ScreenHeight*0.65;FCKDialog.OpenDialog('FCKDialog_Source',FCKLang.Source,'dialog/fck_source.html',A,B,null,true);}else FCK.SwitchEditMode();};FCKSourceCommand.prototype.GetState=function(){return (FCK.EditMode==0?0:1);};var FCKUndoCommand=function(){this.Name='Undo';};FCKUndoCommand.prototype.Execute=function(){FCKUndo.Undo();};FCKUndoCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return (FCKUndo.CheckUndoState()?0:-1);};var FCKRedoCommand=function(){this.Name='Redo';};FCKRedoCommand.prototype.Execute=function(){FCKUndo.Redo();};FCKRedoCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return (FCKUndo.CheckRedoState()?0:-1);};var FCKPageBreakCommand=function(){this.Name='PageBreak';};FCKPageBreakCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();var e=FCK.EditorDocument.createElement('DIV');e.style.pageBreakAfter='always';e.innerHTML='<span style="DISPLAY:none">&nbsp;</span>';var A=FCKDocumentProcessor_CreateFakeImage('FCK__PageBreak',e);var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.SplitBlock();B.InsertNode(A);FCK.Events.FireEvent('OnSelectionChange');};FCKPageBreakCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};var FCKUnlinkCommand=function(){this.Name='Unlink';};FCKUnlinkCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();if (FCKBrowserInfo.IsGeckoLike){var A=FCK.Selection.MoveToAncestorNode('A');if (A) FCKTools.RemoveOuterTags(A);return;};FCK.ExecuteNamedCommand(this.Name);};FCKUnlinkCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;var A=FCK.GetNamedCommandState(this.Name);if (A==0&&FCK.EditMode==0){var B=FCKSelection.MoveToAncestorNode('A');var C=(B&&B.name.length>0&&B.href.length==0);if (C) A=-1;};return A;};var FCKVisitLinkCommand=function(){this.Name='VisitLink';};FCKVisitLinkCommand.prototype={GetState:function(){if (FCK.EditMode!=0) return -1;var A=FCK.GetNamedCommandState('Unlink');if (A==0){var B=FCKSelection.MoveToAncestorNode('A');if (!B.href) A=-1;};return A;},Execute:function(){var A=FCKSelection.MoveToAncestorNode('A');var B=A.getAttribute('_fcksavedurl')||A.getAttribute('href',2);if (!/:\/\//.test(B)){var C=FCKConfig.BaseHref;var D=FCK.GetInstanceObject('parent');if (!C){C=D.document.location.href;C=C.substring(0,C.lastIndexOf('/')+1);};if (/^\//.test(B)){try{C=C.match(/^.*:\/\/+[^\/]+/)[0];}catch (e){C=D.document.location.protocol+'://'+D.parent.document.location.host;}};B=C+B;};if (!window.open(B,'_blank')) alert(FCKLang.VisitLinkBlocked);}};var FCKSelectAllCommand=function(){this.Name='SelectAll';};FCKSelectAllCommand.prototype.Execute=function(){if (FCK.EditMode==0){FCK.ExecuteNamedCommand('SelectAll');}else{var A=FCK.EditingArea.Textarea;if (FCKBrowserInfo.IsIE){A.createTextRange().execCommand('SelectAll');}else{A.selectionStart=0;A.selectionEnd=A.value.length;};A.focus();}};FCKSelectAllCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};var FCKPasteCommand=function(){this.Name='Paste';};FCKPasteCommand.prototype={Execute:function(){if (FCKBrowserInfo.IsIE) FCK.Paste();else FCK.ExecuteNamedCommand('Paste');},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Paste');}};var FCKRuleCommand=function(){this.Name='Rule';};FCKRuleCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();FCK.InsertElement('hr');},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('InsertHorizontalRule');}};var FCKCutCopyCommand=function(A){this.Name=A?'Cut':'Copy';};FCKCutCopyCommand.prototype={Execute:function(){var A=false;if (FCKBrowserInfo.IsIE){var B=function(){A=true;};var C='on'+this.Name.toLowerCase();FCK.EditorDocument.body.attachEvent(C,B);FCK.ExecuteNamedCommand(this.Name);FCK.EditorDocument.body.detachEvent(C,B);}else{try{FCK.ExecuteNamedCommand(this.Name);A=true;}catch(e){}};if (!A) alert(FCKLang['PasteError'+this.Name]);},GetState:function(){return FCK.EditMode!=0?-1:FCK.GetNamedCommandState('Cut');}};var FCKAnchorDeleteCommand=function(){this.Name='AnchorDelete';};FCKAnchorDeleteCommand.prototype={Execute:function(){if (FCK.Selection.GetType()=='Control'){FCK.Selection.Delete();}else{var A=FCK.Selection.GetSelectedElement();if (A){if (A.tagName=='IMG'&&A.getAttribute('_fckanchor')) oAnchor=FCK.GetRealElement(A);else A=null;};if (!A){oAnchor=FCK.Selection.MoveToAncestorNode('A');if (oAnchor) FCK.Selection.SelectNode(oAnchor);};if (oAnchor.href.length!=0){oAnchor.removeAttribute('name');if (FCKBrowserInfo.IsIE) oAnchor.className=oAnchor.className.replace(FCKRegexLib.FCK_Class,'');return;};if (A){A.parentNode.removeChild(A);return;};if (oAnchor.innerHTML.length==0){oAnchor.parentNode.removeChild(oAnchor);return;};FCKTools.RemoveOuterTags(oAnchor);};if (FCKBrowserInfo.IsGecko) FCK.Selection.Collapse(true);},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Unlink');}};var FCKDeleteDivCommand=function(){};FCKDeleteDivCommand.prototype={GetState:function(){if (FCK.EditMode!=0) return -1;var A=FCKSelection.GetParentElement();var B=new FCKElementPath(A);return B.BlockLimit&&B.BlockLimit.nodeName.IEquals('div')?0:-1;},Execute:function(){FCKUndo.SaveUndoStep();var A=FCKDomTools.GetSelectedDivContainers();var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.CreateBookmark();for (var i=0;i<A.length;i++) FCKDomTools.RemoveNode(A[i],true);B.MoveToBookmark(C);B.Select();}};var FCKNbsp=function(){this.Name='Non Breaking Space';};FCKNbsp.prototype={Execute:function(){FCK.InsertHtml('&nbsp;');},GetState:function(){return (FCK.EditMode!=0?-1:0);}};
+var FCKShowBlockCommand=function(A,B){this.Name=A;if (B!=undefined) this._SavedState=B;else this._SavedState=null;};FCKShowBlockCommand.prototype.Execute=function(){var A=this.GetState();if (A==-1) return;var B=FCK.EditorDocument.body;if (A==1) B.className=B.className.replace(/(^| )FCK__ShowBlocks/g,'');else B.className+=' FCK__ShowBlocks';if (FCKBrowserInfo.IsIE){try{FCK.EditorDocument.selection.createRange().select();}catch (e){}}else{var C=FCK.EditorWindow.getSelection().focusNode;if (C){if (C.nodeType!=1) C=C.parentNode;FCKDomTools.ScrollIntoView(C,false);}};FCK.Events.FireEvent('OnSelectionChange');};FCKShowBlockCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;if (!FCK.EditorDocument) return 0;if (/FCK__ShowBlocks(?:\s|$)/.test(FCK.EditorDocument.body.className)) return 1;return 0;};FCKShowBlockCommand.prototype.SaveState=function(){this._SavedState=this.GetState();};FCKShowBlockCommand.prototype.RestoreState=function(){if (this._SavedState!=null&&this.GetState()!=this._SavedState) this.Execute();};
+var FCKSpellCheckCommand=function(){this.Name='SpellCheck';this.IsEnabled=(FCKConfig.SpellChecker!='ieSpell');};FCKSpellCheckCommand.prototype.Execute=function(){switch (FCKConfig.SpellChecker){case 'SpellerPages':FCKDialog.OpenDialog('FCKDialog_SpellCheck','Spell Check','dialog/fck_spellerpages.html',440,480);break;case 'WSC':FCKDialog.OpenDialog('FCKDialog_SpellCheck','Spell Check','wsc/w.html',530,480);}};FCKSpellCheckCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return this.IsEnabled?0:-1;};
+var FCKTextColorCommand=function(A){this.Name=A=='ForeColor'?'TextColor':'BGColor';this.Type=A;var B;if (FCKBrowserInfo.IsIE) B=window;else if (FCK.ToolbarSet._IFrame) B=FCKTools.GetElementWindow(FCK.ToolbarSet._IFrame);else B=window.parent;this._Panel=new FCKPanel(B);this._Panel.AppendStyleSheet(FCKConfig.SkinEditorCSS);this._Panel.MainNode.className='FCK_Panel';this._CreatePanelBody(this._Panel.Document,this._Panel.MainNode);FCK.ToolbarSet.ToolbarItems.GetItem(this.Name).RegisterPanel(this._Panel);FCKTools.DisableSelection(this._Panel.Document.body);};FCKTextColorCommand.prototype.Execute=function(A,B,C){this._Panel.Show(A,B,C);};FCKTextColorCommand.prototype.SetColor=function(A){FCKUndo.SaveUndoStep();var B=FCKStyles.GetStyle('_FCK_'+(this.Type=='ForeColor'?'Color':'BackColor'));if (!A||A.length==0) FCK.Styles.RemoveStyle(B);else{B.SetVariable('Color',A);FCKStyles.ApplyStyle(B);};FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');};FCKTextColorCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};function FCKTextColorCommand_OnMouseOver(){this.className='ColorSelected';};function FCKTextColorCommand_OnMouseOut(){this.className='ColorDeselected';};function FCKTextColorCommand_OnClick(A,B,C){this.className='ColorDeselected';B.SetColor(C);B._Panel.Hide();};function FCKTextColorCommand_AutoOnClick(A,B){this.className='ColorDeselected';B.SetColor('');B._Panel.Hide();};function FCKTextColorCommand_MoreOnClick(A,B){this.className='ColorDeselected';B._Panel.Hide();FCKDialog.OpenDialog('FCKDialog_Color',FCKLang.DlgColorTitle,'dialog/fck_colorselector.html',410,320,FCKTools.Bind(B,B.SetColor));};FCKTextColorCommand.prototype._CreatePanelBody=function(A,B){function CreateSelectionDiv(){var C=A.createElement("DIV");C.className='ColorDeselected';FCKTools.AddEventListenerEx(C,'mouseover',FCKTextColorCommand_OnMouseOver);FCKTools.AddEventListenerEx(C,'mouseout',FCKTextColorCommand_OnMouseOut);return C;};var D=B.appendChild(A.createElement("TABLE"));D.className='ForceBaseFont';D.style.tableLayout='fixed';D.cellPadding=0;D.cellSpacing=0;D.border=0;D.width=150;var E=D.insertRow(-1).insertCell(-1);E.colSpan=8;var C=E.appendChild(CreateSelectionDiv());C.innerHTML='<table cellspacing="0" cellpadding="0" width="100%" border="0">\n <tr>\n <td><div class="ColorBoxBorder"><div class="ColorBox" style="background-color: #000000"></div></div></td>\n <td nowrap width="100%" align="center">'+FCKLang.ColorAutomatic+'</td>\n </tr>\n </table>';FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_AutoOnClick,this);if (!FCKBrowserInfo.IsIE) C.style.width='96%';var G=FCKConfig.FontColors.toString().split(',');var H=0;while (H<G.length){var I=D.insertRow(-1);for (var i=0;i<8;i++,H++){if (H<G.length){var J=G[H].split('/');var K='#'+J[0];var L=J[1]||K;};C=I.insertCell(-1).appendChild(CreateSelectionDiv());C.innerHTML='<div class="ColorBoxBorder"><div class="ColorBox" style="background-color: '+K+'"></div></div>';if (H>=G.length) C.style.visibility='hidden';else FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_OnClick,[this,L]);}};if (FCKConfig.EnableMoreFontColors){E=D.insertRow(-1).insertCell(-1);E.colSpan=8;C=E.appendChild(CreateSelectionDiv());C.innerHTML='<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td nowrap align="center">'+FCKLang.ColorMoreColors+'</td></tr></table>';FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_MoreOnClick,this);if (!FCKBrowserInfo.IsIE) C.style.width='96%';}};
+var FCKPastePlainTextCommand=function(){this.Name='PasteText';};FCKPastePlainTextCommand.prototype.Execute=function(){FCK.PasteAsPlainText();};FCKPastePlainTextCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Paste');};
+var FCKPasteWordCommand=function(){this.Name='PasteWord';};FCKPasteWordCommand.prototype.Execute=function(){FCK.PasteFromWord();};FCKPasteWordCommand.prototype.GetState=function(){if (FCK.EditMode!=0||FCKConfig.ForcePasteAsPlainText) return -1;else return FCK.GetNamedCommandState('Paste');};
+var FCKTableCommand=function(A){this.Name=A;};FCKTableCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();if (!FCKBrowserInfo.IsGecko){switch (this.Name){case 'TableMergeRight':return FCKTableHandler.MergeRight();case 'TableMergeDown':return FCKTableHandler.MergeDown();}};switch (this.Name){case 'TableInsertRowAfter':return FCKTableHandler.InsertRow(false);case 'TableInsertRowBefore':return FCKTableHandler.InsertRow(true);case 'TableDeleteRows':return FCKTableHandler.DeleteRows();case 'TableInsertColumnAfter':return FCKTableHandler.InsertColumn(false);case 'TableInsertColumnBefore':return FCKTableHandler.InsertColumn(true);case 'TableDeleteColumns':return FCKTableHandler.DeleteColumns();case 'TableInsertCellAfter':return FCKTableHandler.InsertCell(null,false);case 'TableInsertCellBefore':return FCKTableHandler.InsertCell(null,true);case 'TableDeleteCells':return FCKTableHandler.DeleteCells();case 'TableMergeCells':return FCKTableHandler.MergeCells();case 'TableHorizontalSplitCell':return FCKTableHandler.HorizontalSplitCell();case 'TableVerticalSplitCell':return FCKTableHandler.VerticalSplitCell();case 'TableDelete':return FCKTableHandler.DeleteTable();default:return alert(FCKLang.UnknownCommand.replace(/%1/g,this.Name));}};FCKTableCommand.prototype.GetState=function(){if (FCK.EditorDocument!=null&&FCKSelection.HasAncestorNode('TABLE')){switch (this.Name){case 'TableHorizontalSplitCell':case 'TableVerticalSplitCell':if (FCKTableHandler.GetSelectedCells().length==1) return 0;else return -1;case 'TableMergeCells':if (FCKTableHandler.CheckIsSelectionRectangular()&&FCKTableHandler.GetSelectedCells().length>1) return 0;else return -1;case 'TableMergeRight':return FCKTableHandler.GetMergeRightTarget()?0:-1;case 'TableMergeDown':return FCKTableHandler.GetMergeDownTarget()?0:-1;default:return 0;}}else return -1;};
+var FCKFitWindow=function(){this.Name='FitWindow';};FCKFitWindow.prototype.Execute=function(){var A=window.frameElement;var B=A.style;var C=parent;var D=C.document.documentElement;var E=C.document.body;var F=E.style;var G;var H,oEditorScrollPos;if (FCK.EditMode==0){H=new FCKDomRange(FCK.EditorWindow);H.MoveToSelection();oEditorScrollPos=FCKTools.GetScrollPosition(FCK.EditorWindow);}else{var I=FCK.EditingArea.Textarea;H=!FCKBrowserInfo.IsIE&&[I.selectionStart,I.selectionEnd];oEditorScrollPos=[I.scrollLeft,I.scrollTop];};if (!this.IsMaximized){if(FCKBrowserInfo.IsIE) C.attachEvent('onresize',FCKFitWindow_Resize);else C.addEventListener('resize',FCKFitWindow_Resize,true);this._ScrollPos=FCKTools.GetScrollPosition(C);G=A;while((G=G.parentNode)){if (G.nodeType==1){G._fckSavedStyles=FCKTools.SaveStyles(G);G.style.zIndex=FCKConfig.FloatingPanelsZIndex-1;}};if (FCKBrowserInfo.IsIE){this.documentElementOverflow=D.style.overflow;D.style.overflow='hidden';F.overflow='hidden';}else{F.overflow='hidden';F.width='0px';F.height='0px';};this._EditorFrameStyles=FCKTools.SaveStyles(A);var J=FCKTools.GetViewPaneSize(C);B.position="absolute";A.offsetLeft;B.zIndex=FCKConfig.FloatingPanelsZIndex-1;B.left="0px";B.top="0px";B.width=J.Width+"px";B.height=J.Height+"px";if (!FCKBrowserInfo.IsIE){B.borderRight=B.borderBottom="9999px solid white";B.backgroundColor="white";};C.scrollTo(0,0);var K=FCKTools.GetWindowPosition(C,A);if (K.x!=0) B.left=(-1*K.x)+"px";if (K.y!=0) B.top=(-1*K.y)+"px";this.IsMaximized=true;}else{if(FCKBrowserInfo.IsIE) C.detachEvent("onresize",FCKFitWindow_Resize);else C.removeEventListener("resize",FCKFitWindow_Resize,true);G=A;while((G=G.parentNode)){if (G._fckSavedStyles){FCKTools.RestoreStyles(G,G._fckSavedStyles);G._fckSavedStyles=null;}};if (FCKBrowserInfo.IsIE) D.style.overflow=this.documentElementOverflow;FCKTools.RestoreStyles(A,this._EditorFrameStyles);C.scrollTo(this._ScrollPos.X,this._ScrollPos.Y);this.IsMaximized=false;};FCKToolbarItems.GetItem('FitWindow').RefreshState();if (FCK.EditMode==0) FCK.EditingArea.MakeEditable();FCK.Focus();if (FCK.EditMode==0){H.Select();FCK.EditorWindow.scrollTo(oEditorScrollPos.X,oEditorScrollPos.Y);}else{if (!FCKBrowserInfo.IsIE){I.selectionStart=H[0];I.selectionEnd=H[1];};I.scrollLeft=oEditorScrollPos[0];I.scrollTop=oEditorScrollPos[1];}};FCKFitWindow.prototype.GetState=function(){if (FCKConfig.ToolbarLocation!='In') return -1;else return (this.IsMaximized?1:0);};function FCKFitWindow_Resize(){var A=FCKTools.GetViewPaneSize(parent);var B=window.frameElement.style;B.width=A.Width+'px';B.height=A.Height+'px';};
+var FCKListCommand=function(A,B){this.Name=A;this.TagName=B;};FCKListCommand.prototype={GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=FCKSelection.GetBoundaryParentElement(true);var B=A;while (B){if (B.nodeName.IEquals(['ul','ol'])) break;B=B.parentNode;};if (B&&B.nodeName.IEquals(this.TagName)) return 1;else return 0;},Execute:function(){FCKUndo.SaveUndoStep();var A=FCK.EditorDocument;var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=this.GetState();if (C==0){FCKDomTools.TrimNode(A.body);if (!A.body.firstChild){var D=A.createElement('p');A.body.appendChild(D);B.MoveToNodeContents(D);}};var E=B.CreateBookmark();var F=[];var G={};var H=new FCKDomRangeIterator(B);var I;H.ForceBrBreak=(C==0);var J=true;var K=null;while (J){while ((I=H.GetNextParagraph())){var L=new FCKElementPath(I);var M=null;var N=false;var O=L.BlockLimit;for (var i=L.Elements.length-1;i>=0;i--){var P=L.Elements[i];if (P.nodeName.IEquals(['ol','ul'])){if (O._FCK_ListGroupObject) O._FCK_ListGroupObject=null;var Q=P._FCK_ListGroupObject;if (Q) Q.contents.push(I);else{Q={ 'root':P,'contents':[I] };F.push(Q);FCKDomTools.SetElementMarker(G,P,'_FCK_ListGroupObject',Q);};N=true;break;}};if (N) continue;var R=O;if (R._FCK_ListGroupObject) R._FCK_ListGroupObject.contents.push(I);else{var Q={ 'root':R,'contents':[I] };FCKDomTools.SetElementMarker(G,R,'_FCK_ListGroupObject',Q);F.push(Q);}};if (FCKBrowserInfo.IsIE) J=false;else{if (K==null){K=[];var T=FCKSelection.GetSelection();if (T&&F.length==0) K.push(T.getRangeAt(0));for (var i=1;T&&i<T.rangeCount;i++) K.push(T.getRangeAt(i));};if (K.length<1) J=false;else{var U=FCKW3CRange.CreateFromRange(A,K.shift());B._Range=U;B._UpdateElementInfo();if (B.StartNode.nodeName.IEquals('td')) B.SetStart(B.StartNode,1);if (B.EndNode.nodeName.IEquals('td')) B.SetEnd(B.EndNode,2);H=new FCKDomRangeIterator(B);H.ForceBrBreak=(C==0);}}};var W=[];while (F.length>0){var Q=F.shift();if (C==0){if (Q.root.nodeName.IEquals(['ul','ol'])) this._ChangeListType(Q,G,W);else this._CreateList(Q,W);}else if (C==1&&Q.root.nodeName.IEquals(['ul','ol'])) this._RemoveList(Q,G);};for (var i=0;i<W.length;i++){var M=W[i];var Z=false;var a=M;while (!Z){a=a.nextSibling;if (a&&a.nodeType==3&&a.nodeValue.search(/^[\n\r\t ]*$/)==0) continue;Z=true;};if (a&&a.nodeName.IEquals(this.TagName)){a.parentNode.removeChild(a);while (a.firstChild) M.appendChild(a.removeChild(a.firstChild));};Z=false;a=M;while (!Z){a=a.previousSibling;if (a&&a.nodeType==3&&a.nodeValue.search(/^[\n\r\t ]*$/)==0) continue;Z=true;};if (a&&a.nodeName.IEquals(this.TagName)){a.parentNode.removeChild(a);while (a.lastChild) M.insertBefore(a.removeChild(a.lastChild),M.firstChild);}};FCKDomTools.ClearAllMarkers(G);B.MoveToBookmark(E);B.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},_ChangeListType:function(A,B,C){var D=FCKDomTools.ListToArray(A.root,B);var E=[];for (var i=0;i<A.contents.length;i++){var F=A.contents[i];F=FCKTools.GetElementAscensor(F,'li');if (!F||F._FCK_ListItem_Processed) continue;E.push(F);FCKDomTools.SetElementMarker(B,F,'_FCK_ListItem_Processed',true);};var G=FCKTools.GetElementDocument(A.root).createElement(this.TagName);for (var i=0;i<E.length;i++){var H=E[i]._FCK_ListArray_Index;D[H].parent=G;};var I=FCKDomTools.ArrayToList(D,B);for (var i=0;i<I.listNode.childNodes.length;i++){if (I.listNode.childNodes[i].nodeName.IEquals(this.TagName)) C.push(I.listNode.childNodes[i]);};A.root.parentNode.replaceChild(I.listNode,A.root);},_CreateList:function(A,B){var C=A.contents;var D=FCKTools.GetElementDocument(A.root);var E=[];if (C.length==1&&C[0]==A.root){var F=D.createElement('div');while (C[0].firstChild) F.appendChild(C[0].removeChild(C[0].firstChild));C[0].appendChild(F);C[0]=F;};var G=A.contents[0].parentNode;for (var i=0;i<C.length;i++) G=FCKDomTools.GetCommonParents(G,C[i].parentNode).pop();for (var i=0;i<C.length;i++){var H=C[i];while (H.parentNode){if (H.parentNode==G){E.push(H);break;};H=H.parentNode;}};if (E.length<1) return;var I=E[E.length-1].nextSibling;var J=D.createElement(this.TagName);B.push(J);while (E.length){var K=E.shift();var L=D.createDocumentFragment();while (K.firstChild) L.appendChild(K.removeChild(K.firstChild));K.parentNode.removeChild(K);var M=D.createElement('li');M.appendChild(L);J.appendChild(M);};G.insertBefore(J,I);},_RemoveList:function(A,B){var C=FCKDomTools.ListToArray(A.root,B);var D=[];for (var i=0;i<A.contents.length;i++){var E=A.contents[i];E=FCKTools.GetElementAscensor(E,'li');if (!E||E._FCK_ListItem_Processed) continue;D.push(E);FCKDomTools.SetElementMarker(B,E,'_FCK_ListItem_Processed',true);};var F=null;for (var i=0;i<D.length;i++){var G=D[i]._FCK_ListArray_Index;C[G].indent=-1;F=G;};for (var i=F+1;i<C.length;i++){if (C[i].indent>C[i-1].indent+1){var H=C[i-1].indent+1-C[i].indent;var I=C[i].indent;while (C[i]&&C[i].indent>=I){C[i].indent+=H;i++;};i--;}};var J=FCKDomTools.ArrayToList(C,B);if (A.root.nextSibling==null||A.root.nextSibling.nodeName.IEquals('br')){if (J.listNode.lastChild.nodeName.IEquals('br')) J.listNode.removeChild(J.listNode.lastChild);};A.root.parentNode.replaceChild(J.listNode,A.root);}};
+var FCKJustifyCommand=function(A){this.AlignValue=A;var B=FCKConfig.ContentLangDirection.toLowerCase();this.IsDefaultAlign=(A=='left'&&B=='ltr')||(A=='right'&&B=='rtl');var C=this._CssClassName=(function(){var D=FCKConfig.JustifyClasses;if (D){switch (A){case 'left':return D[0]||null;case 'center':return D[1]||null;case 'right':return D[2]||null;case 'justify':return D[3]||null;}};return null;})();if (C&&C.length>0) this._CssClassRegex=new RegExp('(?:^|\\s+)'+C+'(?=$|\\s)');};FCKJustifyCommand._GetClassNameRegex=function(){var A=FCKJustifyCommand._ClassRegex;if (A!=undefined) return A;var B=[];var C=FCKConfig.JustifyClasses;if (C){for (var i=0;i<4;i++){var D=C[i];if (D&&D.length>0) B.push(D);}};if (B.length>0) A=new RegExp('(?:^|\\s+)(?:'+B.join('|')+')(?=$|\\s)');else A=null;return FCKJustifyCommand._ClassRegex=A;};FCKJustifyCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();var B=this.GetState();if (B==-1) return;var C=A.CreateBookmark();var D=this._CssClassName;var E=new FCKDomRangeIterator(A);var F;while ((F=E.GetNextParagraph())){F.removeAttribute('align');if (D){var G=F.className.replace(FCKJustifyCommand._GetClassNameRegex(),'');if (B==0){if (G.length>0) G+=' ';F.className=G+D;}else if (G.length==0) FCKDomTools.RemoveAttribute(F,'class');}else{var H=F.style;if (B==0) H.textAlign=this.AlignValue;else{H.textAlign='';if (H.cssText.length==0) F.removeAttribute('style');}}};A.MoveToBookmark(C);A.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=new FCKElementPath(FCKSelection.GetBoundaryParentElement(true));var B=A.Block||A.BlockLimit;if (!B||B.nodeName.toLowerCase()=='body') return 0;var C;if (FCKBrowserInfo.IsIE) C=B.currentStyle.textAlign;else C=FCK.EditorWindow.getComputedStyle(B,'').getPropertyValue('text-align');C=C.replace(/(-moz-|-webkit-|start|auto)/i,'');if ((!C&&this.IsDefaultAlign)||C==this.AlignValue) return 1;return 0;}};
+var FCKIndentCommand=function(A,B){this.Name=A;this.Offset=B;this.IndentCSSProperty=FCKConfig.ContentLangDirection.IEquals('ltr')?'marginLeft':'marginRight';};FCKIndentCommand._InitIndentModeParameters=function(){if (FCKConfig.IndentClasses&&FCKConfig.IndentClasses.length>0){this._UseIndentClasses=true;this._IndentClassMap={};for (var i=0;i<FCKConfig.IndentClasses.length;i++) this._IndentClassMap[FCKConfig.IndentClasses[i]]=i+1;this._ClassNameRegex=new RegExp('(?:^|\\s+)('+FCKConfig.IndentClasses.join('|')+')(?=$|\\s)');}else this._UseIndentClasses=false;};FCKIndentCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();var B=A.CreateBookmark();var C=FCKDomTools.GetCommonParentNode(A.StartNode||A.StartContainer,A.EndNode||A.EndContainer,['ul','ol']);if (C) this._IndentList(A,C);else this._IndentBlock(A);A.MoveToBookmark(B);A.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;if (FCKIndentCommand._UseIndentClasses==undefined) FCKIndentCommand._InitIndentModeParameters();var A=FCKSelection.GetBoundaryParentElement(true);var B=FCKSelection.GetBoundaryParentElement(false);var C=FCKDomTools.GetCommonParentNode(A,B,['ul','ol']);if (C){if (this.Name.IEquals('outdent')) return 0;var D=FCKTools.GetElementAscensor(A,'li');if (!D||!D.previousSibling) return -1;return 0;};if (!FCKIndentCommand._UseIndentClasses&&this.Name.IEquals('indent')) return 0;var E=new FCKElementPath(A);var F=E.Block||E.BlockLimit;if (!F) return -1;if (FCKIndentCommand._UseIndentClasses){var G=F.className.match(FCKIndentCommand._ClassNameRegex);var H=0;if (G!=null){G=G[1];H=FCKIndentCommand._IndentClassMap[G];};if ((this.Name=='outdent'&&H==0)||(this.Name=='indent'&&H==FCKConfig.IndentClasses.length)) return -1;return 0;}else{var I=parseInt(F.style[this.IndentCSSProperty],10);if (isNaN(I)) I=0;if (I<=0) return -1;return 0;}},_IndentBlock:function(A){var B=new FCKDomRangeIterator(A);B.EnforceRealBlocks=true;A.Expand('block_contents');var C=FCKDomTools.GetCommonParents(A.StartContainer,A.EndContainer);var D=C[C.length-1];var E;while ((E=B.GetNextParagraph())){if (!(E==D||E.parentNode==D)) continue;if (FCKIndentCommand._UseIndentClasses){var F=E.className.match(FCKIndentCommand._ClassNameRegex);var G=0;if (F!=null){F=F[1];G=FCKIndentCommand._IndentClassMap[F];};if (this.Name.IEquals('outdent')) G--;else if (this.Name.IEquals('indent')) G++;G=Math.min(G,FCKConfig.IndentClasses.length);G=Math.max(G,0);var H=E.className.replace(FCKIndentCommand._ClassNameRegex,'');if (G<1) E.className=H;else E.className=(H.length>0?H+' ':'')+FCKConfig.IndentClasses[G-1];}else{var I=parseInt(E.style[this.IndentCSSProperty],10);if (isNaN(I)) I=0;I+=this.Offset;I=Math.max(I,0);I=Math.ceil(I/this.Offset)*this.Offset;E.style[this.IndentCSSProperty]=I?I+FCKConfig.IndentUnit:'';if (E.getAttribute('style')=='') E.removeAttribute('style');}}},_IndentList:function(A,B){var C=A.StartContainer;var D=A.EndContainer;while (C&&C.parentNode!=B) C=C.parentNode;while (D&&D.parentNode!=B) D=D.parentNode;if (!C||!D) return;var E=C;var F=[];var G=false;while (G==false){if (E==D) G=true;F.push(E);E=E.nextSibling;};if (F.length<1) return;var H=FCKDomTools.GetParents(B);for (var i=0;i<H.length;i++){if (H[i].nodeName.IEquals(['ul','ol'])){B=H[i];break;}};var I=this.Name.IEquals('indent')?1:-1;var J=F[0];var K=F[F.length-1];var L={};var M=FCKDomTools.ListToArray(B,L);var N=M[K._FCK_ListArray_Index].indent;for (var i=J._FCK_ListArray_Index;i<=K._FCK_ListArray_Index;i++) M[i].indent+=I;for (var i=K._FCK_ListArray_Index+1;i<M.length&&M[i].indent>N;i++) M[i].indent+=I;var O=FCKDomTools.ArrayToList(M);if (O) B.parentNode.replaceChild(O.listNode,B);FCKDomTools.ClearAllMarkers(L);}};
+var FCKBlockQuoteCommand=function(){};FCKBlockQuoteCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=this.GetState();var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.CreateBookmark();if (FCKBrowserInfo.IsIE){var D=B.GetBookmarkNode(C,true);var E=B.GetBookmarkNode(C,false);var F;if (D&&D.parentNode.nodeName.IEquals('blockquote')&&!D.previousSibling){F=D;while ((F=F.nextSibling)){if (FCKListsLib.BlockElements[F.nodeName.toLowerCase()]) FCKDomTools.MoveNode(D,F,true);}};if (E&&E.parentNode.nodeName.IEquals('blockquote')&&!E.previousSibling){F=E;while ((F=F.nextSibling)){if (FCKListsLib.BlockElements[F.nodeName.toLowerCase()]){if (F.firstChild==D) FCKDomTools.InsertAfterNode(D,E);else FCKDomTools.MoveNode(E,F,true);}}}};var G=new FCKDomRangeIterator(B);var H;if (A==0){var I=[];while ((H=G.GetNextParagraph())) I.push(H);if (I.length<1){para=B.Window.document.createElement(FCKConfig.EnterMode.IEquals('p')?'p':'div');B.InsertNode(para);para.appendChild(B.Window.document.createTextNode('\ufeff'));B.MoveToBookmark(C);B.MoveToNodeContents(para);B.Collapse(true);C=B.CreateBookmark();I.push(para);};var J=I[0].parentNode;var K=[];for (var i=0;i<I.length;i++){H=I[i];J=FCKDomTools.GetCommonParents(H.parentNode,J).pop();}while (J.nodeName.IEquals('table','tbody','tr','ol','ul')) J=J.parentNode;var L=null;while (I.length>0){H=I.shift();while (H.parentNode!=J) H=H.parentNode;if (H!=L) K.push(H);L=H;}while (K.length>0){H=K.shift();if (H.nodeName.IEquals('blockquote')){var M=FCKTools.GetElementDocument(H).createDocumentFragment();while (H.firstChild){M.appendChild(H.removeChild(H.firstChild));I.push(M.lastChild);};H.parentNode.replaceChild(M,H);}else I.push(H);};var N=B.Window.document.createElement('blockquote');J.insertBefore(N,I[0]);while (I.length>0){H=I.shift();N.appendChild(H);}}else if (A==1){var O=[];var P={};while ((H=G.GetNextParagraph())){var Q=null;var R=null;while (H.parentNode){if (H.parentNode.nodeName.IEquals('blockquote')){Q=H.parentNode;R=H;break;};H=H.parentNode;};if (Q&&R&&!R._fckblockquotemoveout){O.push(R);FCKDomTools.SetElementMarker(P,R,'_fckblockquotemoveout',true);}};FCKDomTools.ClearAllMarkers(P);var S=[];var T=[],P={};var U=function(N){for (var i=0;i<N.childNodes.length;i++){if (FCKListsLib.BlockElements[N.childNodes[i].nodeName.toLowerCase()]) return false;};return true;};while (O.length>0){var W=O.shift();var N=W.parentNode;if (W==W.parentNode.firstChild) N.parentNode.insertBefore(N.removeChild(W),N);else if (W==W.parentNode.lastChild) N.parentNode.insertBefore(N.removeChild(W),N.nextSibling);else FCKDomTools.BreakParent(W,W.parentNode,B);if (!N._fckbqprocessed){T.push(N);FCKDomTools.SetElementMarker(P,N,'_fckbqprocessed',true);};S.push(W);};for (var i=T.length-1;i>=0;i--){var N=T[i];if (U(N)) FCKDomTools.RemoveNode(N);};FCKDomTools.ClearAllMarkers(P);if (FCKConfig.EnterMode.IEquals('br')){while (S.length){var W=S.shift();var a=true;if (W.nodeName.IEquals('div')){var M=FCKTools.GetElementDocument(W).createDocumentFragment();var c=a&&W.previousSibling&&!FCKListsLib.BlockBoundaries[W.previousSibling.nodeName.toLowerCase()];if (a&&c) M.appendChild(FCKTools.GetElementDocument(W).createElement('br'));var d=W.nextSibling&&!FCKListsLib.BlockBoundaries[W.nextSibling.nodeName.toLowerCase()];while (W.firstChild) M.appendChild(W.removeChild(W.firstChild));if (d) M.appendChild(FCKTools.GetElementDocument(W).createElement('br'));W.parentNode.replaceChild(M,W);a=false;}}}};B.MoveToBookmark(C);B.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=new FCKElementPath(FCKSelection.GetBoundaryParentElement(true));var B=A.Block||A.BlockLimit;if (!B||B.nodeName.toLowerCase()=='body') return 0;for (var i=0;i<A.Elements.length;i++){if (A.Elements[i].nodeName.IEquals('blockquote')) return 1;};return 0;}};
+var FCKCoreStyleCommand=function(A){this.Name='CoreStyle';this.StyleName='_FCK_'+A;this.IsActive=false;FCKStyles.AttachStyleStateChange(this.StyleName,this._OnStyleStateChange,this);};FCKCoreStyleCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();if (this.IsActive) FCKStyles.RemoveStyle(this.StyleName);else FCKStyles.ApplyStyle(this.StyleName);FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0) return -1;return this.IsActive?1:0;},_OnStyleStateChange:function(A,B){this.IsActive=B;}};
+var FCKRemoveFormatCommand=function(){this.Name='RemoveFormat';};FCKRemoveFormatCommand.prototype={Execute:function(){FCKStyles.RemoveAll();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){return FCK.EditorWindow?0:-1;}};
+var FCKCommands=FCK.Commands={};FCKCommands.LoadedCommands={};FCKCommands.RegisterCommand=function(A,B){this.LoadedCommands[A]=B;};FCKCommands.GetCommand=function(A){var B=FCKCommands.LoadedCommands[A];if (B) return B;switch (A){case 'Bold':case 'Italic':case 'Underline':case 'StrikeThrough':case 'Subscript':case 'Superscript':B=new FCKCoreStyleCommand(A);break;case 'RemoveFormat':B=new FCKRemoveFormatCommand();break;case 'DocProps':B=new FCKDialogCommand('DocProps',FCKLang.DocProps,'dialog/fck_docprops.html',400,380,FCKCommands.GetFullPageState);break;case 'Templates':B=new FCKDialogCommand('Templates',FCKLang.DlgTemplatesTitle,'dialog/fck_template.html',380,450);break;case 'Link':B=new FCKDialogCommand('Link',FCKLang.DlgLnkWindowTitle,'dialog/fck_link.html',400,300);break;case 'Unlink':B=new FCKUnlinkCommand();break;case 'VisitLink':B=new FCKVisitLinkCommand();break;case 'Anchor':B=new FCKDialogCommand('Anchor',FCKLang.DlgAnchorTitle,'dialog/fck_anchor.html',370,160);break;case 'AnchorDelete':B=new FCKAnchorDeleteCommand();break;case 'BulletedList':B=new FCKDialogCommand('BulletedList',FCKLang.BulletedListProp,'dialog/fck_listprop.html?UL',370,160);break;case 'NumberedList':B=new FCKDialogCommand('NumberedList',FCKLang.NumberedListProp,'dialog/fck_listprop.html?OL',370,160);break;case 'About':B=new FCKDialogCommand('About',FCKLang.About,'dialog/fck_about.html',420,330,function(){ return 0;});break;case 'Find':B=new FCKDialogCommand('Find',FCKLang.DlgFindAndReplaceTitle,'dialog/fck_replace.html',340,230,null,null,'Find');break;case 'Replace':B=new FCKDialogCommand('Replace',FCKLang.DlgFindAndReplaceTitle,'dialog/fck_replace.html',340,230,null,null,'Replace');break;case 'Image':B=new FCKDialogCommand('Image',FCKLang.DlgImgTitle,'dialog/fck_image.html',450,390);break;case 'Flash':B=new FCKDialogCommand('Flash',FCKLang.DlgFlashTitle,'dialog/fck_flash.html',450,390);break;case 'SpecialChar':B=new FCKDialogCommand('SpecialChar',FCKLang.DlgSpecialCharTitle,'dialog/fck_specialchar.html',400,290);break;case 'Smiley':B=new FCKDialogCommand('Smiley',FCKLang.DlgSmileyTitle,'dialog/fck_smiley.html',FCKConfig.SmileyWindowWidth,FCKConfig.SmileyWindowHeight);break;case 'Table':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html',480,250);break;case 'TableProp':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html?Parent',480,250);break;case 'TableCellProp':B=new FCKDialogCommand('TableCell',FCKLang.DlgCellTitle,'dialog/fck_tablecell.html',550,240);break;case 'Style':B=new FCKStyleCommand();break;case 'FontName':B=new FCKFontNameCommand();break;case 'FontSize':B=new FCKFontSizeCommand();break;case 'FontFormat':B=new FCKFormatBlockCommand();break;case 'Source':B=new FCKSourceCommand();break;case 'Preview':B=new FCKPreviewCommand();break;case 'Save':B=new FCKSaveCommand();break;case 'NewPage':B=new FCKNewPageCommand();break;case 'PageBreak':B=new FCKPageBreakCommand();break;case 'Rule':B=new FCKRuleCommand();break;case 'Nbsp':B=new FCKNbsp();break;case 'TextColor':B=new FCKTextColorCommand('ForeColor');break;case 'BGColor':B=new FCKTextColorCommand('BackColor');break;case 'Paste':B=new FCKPasteCommand();break;case 'PasteText':B=new FCKPastePlainTextCommand();break;case 'PasteWord':B=new FCKPasteWordCommand();break;case 'JustifyLeft':B=new FCKJustifyCommand('left');break;case 'JustifyCenter':B=new FCKJustifyCommand('center');break;case 'JustifyRight':B=new FCKJustifyCommand('right');break;case 'JustifyFull':B=new FCKJustifyCommand('justify');break;case 'Indent':B=new FCKIndentCommand('indent',FCKConfig.IndentLength);break;case 'Outdent':B=new FCKIndentCommand('outdent',FCKConfig.IndentLength*-1);break;case 'Blockquote':B=new FCKBlockQuoteCommand();break;case 'CreateDiv':B=new FCKDialogCommand('CreateDiv',FCKLang.CreateDiv,'dialog/fck_div.html',380,210,null,null,true);break;case 'EditDiv':B=new FCKDialogCommand('EditDiv',FCKLang.EditDiv,'dialog/fck_div.html',380,210,null,null,false);break;case 'DeleteDiv':B=new FCKDeleteDivCommand();break;case 'TableInsertRowAfter':B=new FCKTableCommand('TableInsertRowAfter');break;case 'TableInsertRowBefore':B=new FCKTableCommand('TableInsertRowBefore');break;case 'TableDeleteRows':B=new FCKTableCommand('TableDeleteRows');break;case 'TableInsertColumnAfter':B=new FCKTableCommand('TableInsertColumnAfter');break;case 'TableInsertColumnBefore':B=new FCKTableCommand('TableInsertColumnBefore');break;case 'TableDeleteColumns':B=new FCKTableCommand('TableDeleteColumns');break;case 'TableInsertCellAfter':B=new FCKTableCommand('TableInsertCellAfter');break;case 'TableInsertCellBefore':B=new FCKTableCommand('TableInsertCellBefore');break;case 'TableDeleteCells':B=new FCKTableCommand('TableDeleteCells');break;case 'TableMergeCells':B=new FCKTableCommand('TableMergeCells');break;case 'TableMergeRight':B=new FCKTableCommand('TableMergeRight');break;case 'TableMergeDown':B=new FCKTableCommand('TableMergeDown');break;case 'TableHorizontalSplitCell':B=new FCKTableCommand('TableHorizontalSplitCell');break;case 'TableVerticalSplitCell':B=new FCKTableCommand('TableVerticalSplitCell');break;case 'TableDelete':B=new FCKTableCommand('TableDelete');break;case 'Form':B=new FCKDialogCommand('Form',FCKLang.Form,'dialog/fck_form.html',380,210);break;case 'Checkbox':B=new FCKDialogCommand('Checkbox',FCKLang.Checkbox,'dialog/fck_checkbox.html',380,200);break;case 'Radio':B=new FCKDialogCommand('Radio',FCKLang.RadioButton,'dialog/fck_radiobutton.html',380,200);break;case 'TextField':B=new FCKDialogCommand('TextField',FCKLang.TextField,'dialog/fck_textfield.html',380,210);break;case 'Textarea':B=new FCKDialogCommand('Textarea',FCKLang.Textarea,'dialog/fck_textarea.html',380,210);break;case 'HiddenField':B=new FCKDialogCommand('HiddenField',FCKLang.HiddenField,'dialog/fck_hiddenfield.html',380,190);break;case 'Button':B=new FCKDialogCommand('Button',FCKLang.Button,'dialog/fck_button.html',380,210);break;case 'Select':B=new FCKDialogCommand('Select',FCKLang.SelectionField,'dialog/fck_select.html',400,340);break;case 'ImageButton':B=new FCKDialogCommand('ImageButton',FCKLang.ImageButton,'dialog/fck_image.html?ImageButton',450,390);break;case 'SpellCheck':B=new FCKSpellCheckCommand();break;case 'FitWindow':B=new FCKFitWindow();break;case 'Undo':B=new FCKUndoCommand();break;case 'Redo':B=new FCKRedoCommand();break;case 'Copy':B=new FCKCutCopyCommand(false);break;case 'Cut':B=new FCKCutCopyCommand(true);break;case 'SelectAll':B=new FCKSelectAllCommand();break;case 'InsertOrderedList':B=new FCKListCommand('insertorderedlist','ol');break;case 'InsertUnorderedList':B=new FCKListCommand('insertunorderedlist','ul');break;case 'ShowBlocks':B=new FCKShowBlockCommand('ShowBlocks',FCKConfig.StartupShowBlocks?1:0);break;case 'Undefined':B=new FCKUndefinedCommand();break;case 'Scayt':B=FCKScayt.CreateCommand();break;case 'ScaytContext':B=FCKScayt.CreateContextCommand();break;default:if (FCKRegexLib.NamedCommands.test(A)) B=new FCKNamedCommand(A);else{alert(FCKLang.UnknownCommand.replace(/%1/g,A));return null;}};FCKCommands.LoadedCommands[A]=B;return B;};FCKCommands.GetFullPageState=function(){return FCKConfig.FullPage?0:-1;};FCKCommands.GetBooleanState=function(A){return A?-1:0;};
+var FCKPanel=function(A){this.IsRTL=(FCKLang.Dir=='rtl');this.IsContextMenu=false;this._LockCounter=0;this._Window=A||window;var B;if (FCKBrowserInfo.IsIE){this._Popup=this._Window.createPopup();var C=this._Window.document;if (FCK_IS_CUSTOM_DOMAIN&&!FCKBrowserInfo.IsIE7){C.domain=FCK_ORIGINAL_DOMAIN;document.domain=FCK_ORIGINAL_DOMAIN;};B=this.Document=this._Popup.document;if (FCK_IS_CUSTOM_DOMAIN){B.domain=FCK_RUNTIME_DOMAIN;C.domain=FCK_RUNTIME_DOMAIN;document.domain=FCK_RUNTIME_DOMAIN;};FCK.IECleanup.AddItem(this,FCKPanel_Cleanup);}else{var D=this._IFrame=this._Window.document.createElement('iframe');FCKTools.ResetStyles(D);D.src='javascript:void(0)';D.allowTransparency=true;D.frameBorder='0';D.scrolling='no';D.style.width=D.style.height='0px';FCKDomTools.SetElementStyles(D,{position:'absolute',zIndex:FCKConfig.FloatingPanelsZIndex});this._Window.document.body.appendChild(D);var E=D.contentWindow;B=this.Document=E.document;var F='';if (FCKBrowserInfo.IsSafari) F='<base href="'+window.document.location+'">';B.open();B.write('<html><head>'+F+'<\/head><body style="margin:0px;padding:0px;"><\/body><\/html>');B.close();if(FCKBrowserInfo.IsAIR) FCKAdobeAIR.Panel_Contructor(B,window.document.location);FCKTools.AddEventListenerEx(E,'focus',FCKPanel_Window_OnFocus,this);FCKTools.AddEventListenerEx(E,'blur',FCKPanel_Window_OnBlur,this);};B.dir=FCKLang.Dir;FCKTools.AddEventListener(B,'contextmenu',FCKTools.CancelEvent);this.MainNode=B.body.appendChild(B.createElement('DIV'));this.MainNode.style.cssFloat=this.IsRTL?'right':'left';};FCKPanel.prototype.AppendStyleSheet=function(A){FCKTools.AppendStyleSheet(this.Document,A);};FCKPanel.prototype.Preload=function(x,y,A){if (this._Popup) this._Popup.show(x,y,0,0,A);};FCKPanel.prototype.ResizeForSubpanel=function(A,B,C){if (!FCKBrowserInfo.IsIE7) return false;if (!this._Popup.isOpen){this.Subpanel=null;return false;};if (B==0&&C==0){if (this.Subpanel!==A) return false;this.Subpanel=null;this.IncreasedX=0;}else{this.Subpanel=A;if ((this.IncreasedX>=B)&&(this.IncreasedY>=C)) return false;this.IncreasedX=Math.max(this.IncreasedX,B);this.IncreasedY=Math.max(this.IncreasedY,C);};var x=this.ShowRect.x;var w=this.IncreasedX;if (this.IsRTL) x=x-w;var D=this.ShowRect.w+w;var E=Math.max(this.ShowRect.h,this.IncreasedY);if (this.ParentPanel) this.ParentPanel.ResizeForSubpanel(this,D,E);this._Popup.show(x,this.ShowRect.y,D,E,this.RelativeElement);return this.IsRTL;};FCKPanel.prototype.Show=function(x,y,A,B,C){var D;var E=this.MainNode;if (this._Popup){this._Popup.show(x,y,0,0,A);FCKDomTools.SetElementStyles(E,{B:B?B+'px':'',C:C?C+'px':''});D=E.offsetWidth;if (FCKBrowserInfo.IsIE7){if (this.ParentPanel&&this.ParentPanel.ResizeForSubpanel(this,D,E.offsetHeight)){FCKTools.RunFunction(this.Show,this,[x,y,A]);return;}};if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=(x*-1)+A.offsetWidth-D;};if (FCKBrowserInfo.IsIE7){this.ShowRect={x:x,y:y,w:D,h:E.offsetHeight};this.IncreasedX=0;this.IncreasedY=0;this.RelativeElement=A;};this._PopupArgs=[x,y,D,E.offsetHeight,A];this._Popup.show(x,y,D,E.offsetHeight,A);if (this.OnHide){if (this._Timer) CheckPopupOnHide.call(this,true);this._Timer=FCKTools.SetInterval(CheckPopupOnHide,100,this);}}else{if (typeof(FCK.ToolbarSet.CurrentInstance.FocusManager)!='undefined') FCK.ToolbarSet.CurrentInstance.FocusManager.Lock();if (this.ParentPanel){this.ParentPanel.Lock();FCKPanel_Window_OnBlur(null,this.ParentPanel);};if (FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsMac){this._IFrame.scrolling='';FCKTools.RunFunction(function(){ this._IFrame.scrolling='no';},this);};if (FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel&&FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel!=this) FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel.Hide(false,true);FCKDomTools.SetElementStyles(E,{B:B?B+'px':'',C:C?C+'px':''});D=E.offsetWidth;if (!B) this._IFrame.width=1;if (!C) this._IFrame.height=1;D=E.offsetWidth||E.firstChild.offsetWidth;var F=FCKTools.GetDocumentPosition(this._Window,A.nodeType==9?(FCKTools.IsStrictMode(A)?A.documentElement:A.body):A);var G=FCKDomTools.GetPositionedAncestor(this._IFrame.parentNode);if (G){var H=FCKTools.GetDocumentPosition(FCKTools.GetElementWindow(G),G);F.x-=H.x;F.y-=H.y;};if (this.IsRTL&&!this.IsContextMenu) x=(x*-1);x+=F.x;y+=F.y;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=x+A.offsetWidth-D;}else{var I=FCKTools.GetViewPaneSize(this._Window);var J=FCKTools.GetScrollPosition(this._Window);var K=I.Height+J.Y;var L=I.Width+J.X;if ((x+D)>L) x-=x+D-L;if ((y+E.offsetHeight)>K) y-=y+E.offsetHeight-K;};FCKDomTools.SetElementStyles(this._IFrame,{left:x+'px',top:y+'px'});this._IFrame.contentWindow.focus();this._IsOpened=true;var M=this;this._resizeTimer=setTimeout(function(){var N=E.offsetWidth||E.firstChild.offsetWidth;var O=E.offsetHeight;M._IFrame.style.width=N+'px';M._IFrame.style.height=O+'px';},0);FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel=this;};FCKTools.RunFunction(this.OnShow,this);};FCKPanel.prototype.Hide=function(A,B){if (this._Popup) this._Popup.hide();else{if (!this._IsOpened||this._LockCounter>0) return;if (typeof(FCKFocusManager)!='undefined'&&!B) FCKFocusManager.Unlock();this._IFrame.style.width=this._IFrame.style.height='0px';this._IsOpened=false;if (this._resizeTimer){clearTimeout(this._resizeTimer);this._resizeTimer=null;};if (this.ParentPanel) this.ParentPanel.Unlock();if (!A) FCKTools.RunFunction(this.OnHide,this);}};FCKPanel.prototype.CheckIsOpened=function(){if (this._Popup) return this._Popup.isOpen;else return this._IsOpened;};FCKPanel.prototype.CreateChildPanel=function(){var A=this._Popup?FCKTools.GetDocumentWindow(this.Document):this._Window;var B=new FCKPanel(A);B.ParentPanel=this;return B;};FCKPanel.prototype.Lock=function(){this._LockCounter++;};FCKPanel.prototype.Unlock=function(){if (--this._LockCounter==0&&!this.HasFocus) this.Hide();};function FCKPanel_Window_OnFocus(e,A){A.HasFocus=true;};function FCKPanel_Window_OnBlur(e,A){A.HasFocus=false;if (A._LockCounter==0) FCKTools.RunFunction(A.Hide,A);};function CheckPopupOnHide(A){if (A||!this._Popup.isOpen){window.clearInterval(this._Timer);this._Timer=null;if (this._Popup&&this.ParentPanel&&!A) this.ParentPanel.ResizeForSubpanel(this,0,0);FCKTools.RunFunction(this.OnHide,this);}};function FCKPanel_Cleanup(){this._Popup=null;this._Window=null;this.Document=null;this.MainNode=null;this.RelativeElement=null;};
+var FCKIcon=function(A){var B=A?typeof(A):'undefined';switch (B){case 'number':this.Path=FCKConfig.SkinPath+'fck_strip.gif';this.Size=16;this.Position=A;break;case 'undefined':this.Path=FCK_SPACER_PATH;break;case 'string':this.Path=A;break;default:this.Path=A[0];this.Size=A[1];this.Position=A[2];}};FCKIcon.prototype.CreateIconElement=function(A){var B,eIconImage;if (this.Position){var C='-'+((this.Position-1)*this.Size)+'px';if (FCKBrowserInfo.IsIE){B=A.createElement('DIV');eIconImage=B.appendChild(A.createElement('IMG'));eIconImage.src=this.Path;eIconImage.style.top=C;}else{B=A.createElement('IMG');B.src=FCK_SPACER_PATH;B.style.backgroundPosition='0px '+C;B.style.backgroundImage='url("'+this.Path+'")';}}else{if (FCKBrowserInfo.IsIE){B=A.createElement('DIV');eIconImage=B.appendChild(A.createElement('IMG'));eIconImage.src=this.Path?this.Path:FCK_SPACER_PATH;}else{B=A.createElement('IMG');B.src=this.Path?this.Path:FCK_SPACER_PATH;}};B.className='TB_Button_Image';return B;};
+var FCKToolbarButtonUI=function(A,B,C,D,E,F){this.Name=A;this.Label=B||A;this.Tooltip=C||this.Label;this.Style=E||0;this.State=F||0;this.Icon=new FCKIcon(D);if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbarButtonUI_Cleanup);};FCKToolbarButtonUI.prototype._CreatePaddingElement=function(A){var B=A.createElement('IMG');B.className='TB_Button_Padding';B.src=FCK_SPACER_PATH;return B;};FCKToolbarButtonUI.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var C=this.MainElement=B.createElement('DIV');C.title=this.Tooltip;if (FCKBrowserInfo.IsGecko) C.onmousedown=FCKTools.CancelEvent;FCKTools.AddEventListenerEx(C,'mouseover',FCKToolbarButtonUI_OnMouseOver,this);FCKTools.AddEventListenerEx(C,'mouseout',FCKToolbarButtonUI_OnMouseOut,this);FCKTools.AddEventListenerEx(C,'click',FCKToolbarButtonUI_OnClick,this);this.ChangeState(this.State,true);if (this.Style==0&&!this.ShowArrow){C.appendChild(this.Icon.CreateIconElement(B));}else{var D=C.appendChild(B.createElement('TABLE'));D.cellPadding=0;D.cellSpacing=0;var E=D.insertRow(-1);var F=E.insertCell(-1);if (this.Style==0||this.Style==2) F.appendChild(this.Icon.CreateIconElement(B));else F.appendChild(this._CreatePaddingElement(B));if (this.Style==1||this.Style==2){F=E.insertCell(-1);F.className='TB_Button_Text';F.noWrap=true;F.appendChild(B.createTextNode(this.Label));};if (this.ShowArrow){if (this.Style!=0){E.insertCell(-1).appendChild(this._CreatePaddingElement(B));};F=E.insertCell(-1);var G=F.appendChild(B.createElement('IMG'));G.src=FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif';G.width=5;G.height=3;};F=E.insertCell(-1);F.appendChild(this._CreatePaddingElement(B));};A.appendChild(C);};FCKToolbarButtonUI.prototype.ChangeState=function(A,B){if (!B&&this.State==A) return;var e=this.MainElement;if (!e) return;switch (parseInt(A,10)){case 0:e.className='TB_Button_Off';break;case 1:e.className='TB_Button_On';break;case -1:e.className='TB_Button_Disabled';break;};this.State=A;};function FCKToolbarButtonUI_OnMouseOver(A,B){if (B.State==0) this.className='TB_Button_Off_Over';else if (B.State==1) this.className='TB_Button_On_Over';};function FCKToolbarButtonUI_OnMouseOut(A,B){if (B.State==0) this.className='TB_Button_Off';else if (B.State==1) this.className='TB_Button_On';};function FCKToolbarButtonUI_OnClick(A,B){if (B.OnClick&&B.State!=-1) B.OnClick(B);};function FCKToolbarButtonUI_Cleanup(){this.MainElement=null;};
+var FCKToolbarButton=function(A,B,C,D,E,F,G){this.CommandName=A;this.Label=B;this.Tooltip=C;this.Style=D;this.SourceView=E?true:false;this.ContextSensitive=F?true:false;if (G==null) this.IconPath=FCKConfig.SkinPath+'toolbar/'+A.toLowerCase()+'.gif';else if (typeof(G)=='number') this.IconPath=[FCKConfig.SkinPath+'fck_strip.gif',16,G];else this.IconPath=G;};FCKToolbarButton.prototype.Create=function(A){this._UIButton=new FCKToolbarButtonUI(this.CommandName,this.Label,this.Tooltip,this.IconPath,this.Style);this._UIButton.OnClick=this.Click;this._UIButton._ToolbarButton=this;this._UIButton.Create(A);};FCKToolbarButton.prototype.RefreshState=function(){var A=this._UIButton;if (!A) return;var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (B==A.State) return;A.ChangeState(B);};FCKToolbarButton.prototype.Click=function(){var A=this._ToolbarButton||this;FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(A.CommandName).Execute();};FCKToolbarButton.prototype.Enable=function(){this.RefreshState();};FCKToolbarButton.prototype.Disable=function(){this._UIButton.ChangeState(-1);};
+var FCKSpecialCombo=function(A,B,C,D,E){this.FieldWidth=B||100;this.PanelWidth=C||150;this.PanelMaxHeight=D||150;this.Label='&nbsp;';this.Caption=A;this.Tooltip=A;this.Style=2;this.Enabled=true;this.Items={};this._Panel=new FCKPanel(E||window);this._Panel.AppendStyleSheet(FCKConfig.SkinEditorCSS);this._PanelBox=this._Panel.MainNode.appendChild(this._Panel.Document.createElement('DIV'));this._PanelBox.className='SC_Panel';this._PanelBox.style.width=this.PanelWidth+'px';this._PanelBox.innerHTML='<table cellpadding="0" cellspacing="0" width="100%" style="TABLE-LAYOUT: fixed"><tr><td nowrap></td></tr></table>';this._ItemsHolderEl=this._PanelBox.getElementsByTagName('TD')[0];if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKSpecialCombo_Cleanup);};function FCKSpecialCombo_ItemOnMouseOver(){this.className+=' SC_ItemOver';};function FCKSpecialCombo_ItemOnMouseOut(){this.className=this.originalClass;};function FCKSpecialCombo_ItemOnClick(A,B,C){this.className=this.originalClass;B._Panel.Hide();B.SetLabel(this.FCKItemLabel);if (typeof(B.OnSelect)=='function') B.OnSelect(C,this);};FCKSpecialCombo.prototype.ClearItems=function (){if (this.Items) this.Items={};var A=this._ItemsHolderEl;while (A.firstChild) A.removeChild(A.firstChild);};FCKSpecialCombo.prototype.AddItem=function(A,B,C,D){var E=this._ItemsHolderEl.appendChild(this._Panel.Document.createElement('DIV'));E.className=E.originalClass='SC_Item';E.innerHTML=B;E.FCKItemLabel=C||A;E.Selected=false;if (FCKBrowserInfo.IsIE) E.style.width='100%';if (D) E.style.backgroundColor=D;FCKTools.AddEventListenerEx(E,'mouseover',FCKSpecialCombo_ItemOnMouseOver);FCKTools.AddEventListenerEx(E,'mouseout',FCKSpecialCombo_ItemOnMouseOut);FCKTools.AddEventListenerEx(E,'click',FCKSpecialCombo_ItemOnClick,[this,A]);this.Items[A.toString().toLowerCase()]=E;return E;};FCKSpecialCombo.prototype.SelectItem=function(A){if (typeof A=='string') A=this.Items[A.toString().toLowerCase()];if (A){A.className=A.originalClass='SC_ItemSelected';A.Selected=true;}};FCKSpecialCombo.prototype.SelectItemByLabel=function(A,B){for (var C in this.Items){var D=this.Items[C];if (D.FCKItemLabel==A){D.className=D.originalClass='SC_ItemSelected';D.Selected=true;if (B) this.SetLabel(A);}}};FCKSpecialCombo.prototype.DeselectAll=function(A){for (var i in this.Items){if (!this.Items[i]) continue;this.Items[i].className=this.Items[i].originalClass='SC_Item';this.Items[i].Selected=false;};if (A) this.SetLabel('');};FCKSpecialCombo.prototype.SetLabelById=function(A){A=A?A.toString().toLowerCase():'';var B=this.Items[A];this.SetLabel(B?B.FCKItemLabel:'');};FCKSpecialCombo.prototype.SetLabel=function(A){A=(!A||A.length==0)?'&nbsp;':A;if (A==this.Label) return;this.Label=A;var B=this._LabelEl;if (B){B.innerHTML=A;FCKTools.DisableSelection(B);}};FCKSpecialCombo.prototype.SetEnabled=function(A){this.Enabled=A;if (this._OuterTable) this._OuterTable.className=A?'':'SC_FieldDisabled';};FCKSpecialCombo.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var C=this._OuterTable=A.appendChild(B.createElement('TABLE'));C.cellPadding=0;C.cellSpacing=0;C.insertRow(-1);var D;var E;switch (this.Style){case 0:D='TB_ButtonType_Icon';E=false;break;case 1:D='TB_ButtonType_Text';E=false;break;case 2:E=true;break;};if (this.Caption&&this.Caption.length>0&&E){var F=C.rows[0].insertCell(-1);F.innerHTML=this.Caption;F.className='SC_FieldCaption';};var G=FCKTools.AppendElement(C.rows[0].insertCell(-1),'div');if (E){G.className='SC_Field';G.style.width=this.FieldWidth+'px';G.innerHTML='<table width="100%" cellpadding="0" cellspacing="0" style="TABLE-LAYOUT: fixed;"><tbody><tr><td class="SC_FieldLabel"><label>&nbsp;</label></td><td class="SC_FieldButton">&nbsp;</td></tr></tbody></table>';this._LabelEl=G.getElementsByTagName('label')[0];this._LabelEl.innerHTML=this.Label;}else{G.className='TB_Button_Off';G.innerHTML='<table title="'+this.Tooltip+'" class="'+D+'" cellspacing="0" cellpadding="0" border="0"><tr><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td><td class="TB_Text">'+this.Caption+'</td><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td><td class="TB_ButtonArrow"><img src="'+FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif" width="5" height="3"></td><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td></tr></table>';};FCKTools.AddEventListenerEx(G,'mouseover',FCKSpecialCombo_OnMouseOver,this);FCKTools.AddEventListenerEx(G,'mouseout',FCKSpecialCombo_OnMouseOut,this);FCKTools.AddEventListenerEx(G,'click',FCKSpecialCombo_OnClick,this);FCKTools.DisableSelection(this._Panel.Document.body);};function FCKSpecialCombo_Cleanup(){this._LabelEl=null;this._OuterTable=null;this._ItemsHolderEl=null;this._PanelBox=null;if (this.Items){for (var A in this.Items) this.Items[A]=null;}};function FCKSpecialCombo_OnMouseOver(A,B){if (B.Enabled){switch (B.Style){case 0:this.className='TB_Button_On_Over';break;case 1:this.className='TB_Button_On_Over';break;case 2:this.className='SC_Field SC_FieldOver';break;}}};function FCKSpecialCombo_OnMouseOut(A,B){switch (B.Style){case 0:this.className='TB_Button_Off';break;case 1:this.className='TB_Button_Off';break;case 2:this.className='SC_Field';break;}};function FCKSpecialCombo_OnClick(e,A){if (A.Enabled){var B=A._Panel;var C=A._PanelBox;var D=A._ItemsHolderEl;var E=A.PanelMaxHeight;if (A.OnBeforeClick) A.OnBeforeClick(A);if (FCKBrowserInfo.IsIE) B.Preload(0,this.offsetHeight,this);if (D.offsetHeight>E) C.style.height=E+'px';else C.style.height='';B.Show(0,this.offsetHeight,this);}};
+var FCKToolbarSpecialCombo=function(){this.SourceView=false;this.ContextSensitive=true;this.FieldWidth=null;this.PanelWidth=null;this.PanelMaxHeight=null;};FCKToolbarSpecialCombo.prototype.DefaultLabel='';function FCKToolbarSpecialCombo_OnSelect(A,B){FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).Execute(A,B);};FCKToolbarSpecialCombo.prototype.Create=function(A){this._Combo=new FCKSpecialCombo(this.GetLabel(),this.FieldWidth,this.PanelWidth,this.PanelMaxHeight,FCKBrowserInfo.IsIE?window:FCKTools.GetElementWindow(A).parent);this._Combo.Tooltip=this.Tooltip;this._Combo.Style=this.Style;this.CreateItems(this._Combo);this._Combo.Create(A);this._Combo.CommandName=this.CommandName;this._Combo.OnSelect=FCKToolbarSpecialCombo_OnSelect;};function FCKToolbarSpecialCombo_RefreshActiveItems(A,B){A.DeselectAll();A.SelectItem(B);A.SetLabelById(B);};FCKToolbarSpecialCombo.prototype.RefreshState=function(){var A;var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (B!=-1){A=1;if (this.RefreshActiveItems) this.RefreshActiveItems(this._Combo,B);else{if (this._LastValue!==B){this._LastValue=B;if (!B||B.length==0){this._Combo.DeselectAll();this._Combo.SetLabel(this.DefaultLabel);}else FCKToolbarSpecialCombo_RefreshActiveItems(this._Combo,B);}}}else A=-1;if (A==this.State) return;if (A==-1){this._Combo.DeselectAll();this._Combo.SetLabel('');};this.State=A;this._Combo.SetEnabled(A!=-1);};FCKToolbarSpecialCombo.prototype.Enable=function(){this.RefreshState();};FCKToolbarSpecialCombo.prototype.Disable=function(){this.State=-1;this._Combo.DeselectAll();this._Combo.SetLabel('');this._Combo.SetEnabled(false);};
+var FCKToolbarStyleCombo=function(A,B){if (A===false) return;this.CommandName='Style';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultStyleLabel||'';};FCKToolbarStyleCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarStyleCombo.prototype.GetLabel=function(){return FCKLang.Style;};FCKToolbarStyleCombo.prototype.GetStyles=function(){var A={};var B=FCK.ToolbarSet.CurrentInstance.Styles.GetStyles();for (var C in B){var D=B[C];if (!D.IsCore) A[C]=D;};return A;};FCKToolbarStyleCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);FCKTools.AppendStyleString(B,FCKConfig.EditorAreaStyles);B.body.className+=' ForceBaseFont';FCKConfig.ApplyBodyAttributes(B.body);var C=this.GetStyles();for (var D in C){var E=C[D];var F=E.GetType()==2?D:FCKToolbarStyleCombo_BuildPreview(E,E.Label||D);var G=A.AddItem(D,F);G.Style=E;};A.OnBeforeClick=this.StyleCombo_OnBeforeClick;};FCKToolbarStyleCombo.prototype.RefreshActiveItems=function(A){var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Elements;for (var e=0;e<D.length;e++){for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D[e],true)){A.SetLabel(F.Label||F.Name);return;}}}};A.SetLabel(this.DefaultLabel);};FCKToolbarStyleCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B;var C;var D;var E=FCK.ToolbarSet.CurrentInstance.Selection;if (E.GetType()=='Control'){B=E.GetSelectedElement();D=B.nodeName.toLowerCase();}else{B=E.GetBoundaryParentElement(true);C=new FCKElementPath(B);};for (var i in A.Items){var F=A.Items[i];var G=F.Style;if ((D&&G.Element==D)||(!D&&G.GetType()!=2)){F.style.display='';if ((C&&G.CheckActive(C))||(!C&&G.CheckElementRemovable(B,true))) A.SelectItem(G.Name);}else F.style.display='none';}};function FCKToolbarStyleCombo_BuildPreview(A,B){var C=A.GetType();var D=[];if (C==0) D.push('<div class="BaseFont">');var E=A.Element;if (E=='bdo') E='span';D=['<',E];var F=A._StyleDesc.Attributes;if (F){for (var G in F){D.push(' ',G,'="',A.GetFinalAttributeValue(G),'"');}};if (A._GetStyleText().length>0) D.push(' style="',A.GetFinalStyleValue(),'"');D.push('>',B,'</',E,'>');if (C==0) D.push('</div>');return D.join('');};
+var FCKToolbarFontFormatCombo=function(A,B){if (A===false) return;this.CommandName='FontFormat';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.NormalLabel='Normal';this.PanelWidth=190;this.DefaultLabel=FCKConfig.DefaultFontFormatLabel||'';};FCKToolbarFontFormatCombo.prototype=new FCKToolbarStyleCombo(false);FCKToolbarFontFormatCombo.prototype.GetLabel=function(){return FCKLang.FontFormat;};FCKToolbarFontFormatCombo.prototype.GetStyles=function(){var A={};var B=FCKLang['FontFormats'].split(';');var C={p:B[0],pre:B[1],address:B[2],h1:B[3],h2:B[4],h3:B[5],h4:B[6],h5:B[7],h6:B[8],div:B[9]||(B[0]+' (DIV)')};var D=FCKConfig.FontFormats.split(';');for (var i=0;i<D.length;i++){var E=D[i];var F=FCKStyles.GetStyle('_FCK_'+E);if (F){F.Label=C[E];A['_FCK_'+E]=F;}else alert("The FCKConfig.CoreStyles['"+E+"'] setting was not found. Please check the fckconfig.js file");};return A;};FCKToolbarFontFormatCombo.prototype.RefreshActiveItems=function(A){var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Block;if (D){for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D)){A.SetLabel(F.Label);return;}}}};A.SetLabel(this.DefaultLabel);};FCKToolbarFontFormatCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Block;for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D)){A.SelectItem(E);return;}}}};
+var FCKToolbarFontsCombo=function(A,B){this.CommandName='FontName';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultFontLabel||'';};FCKToolbarFontsCombo.prototype=new FCKToolbarFontFormatCombo(false);FCKToolbarFontsCombo.prototype.GetLabel=function(){return FCKLang.Font;};FCKToolbarFontsCombo.prototype.GetStyles=function(){var A=FCKStyles.GetStyle('_FCK_FontFace');if (!A){alert("The FCKConfig.CoreStyles['Size'] setting was not found. Please check the fckconfig.js file");return {};};var B={};var C=FCKConfig.FontNames.split(';');for (var i=0;i<C.length;i++){var D=C[i].split('/');var E=D[0];var F=D[1]||E;var G=FCKTools.CloneObject(A);G.SetVariable('Font',E);G.Label=F;B[F]=G;};return B;};FCKToolbarFontsCombo.prototype.RefreshActiveItems=FCKToolbarStyleCombo.prototype.RefreshActiveItems;FCKToolbarFontsCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B=FCKSelection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);for (var i in A.Items){var D=A.Items[i];var E=D.Style;if (E.CheckActive(C)){A.SelectItem(D);return;}}}};
+var FCKToolbarFontSizeCombo=function(A,B){this.CommandName='FontSize';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultFontSizeLabel||'';this.FieldWidth=70;};FCKToolbarFontSizeCombo.prototype=new FCKToolbarFontFormatCombo(false);FCKToolbarFontSizeCombo.prototype.GetLabel=function(){return FCKLang.FontSize;};FCKToolbarFontSizeCombo.prototype.GetStyles=function(){var A=FCKStyles.GetStyle('_FCK_Size');if (!A){alert("The FCKConfig.CoreStyles['FontFace'] setting was not found. Please check the fckconfig.js file");return {};};var B={};var C=FCKConfig.FontSizes.split(';');for (var i=0;i<C.length;i++){var D=C[i].split('/');var E=D[0];var F=D[1]||E;var G=FCKTools.CloneObject(A);G.SetVariable('Size',E);G.Label=F;B[F]=G;};return B;};FCKToolbarFontSizeCombo.prototype.RefreshActiveItems=FCKToolbarStyleCombo.prototype.RefreshActiveItems;FCKToolbarFontSizeCombo.prototype.StyleCombo_OnBeforeClick=FCKToolbarFontsCombo.prototype.StyleCombo_OnBeforeClick;
+var FCKToolbarPanelButton=function(A,B,C,D,E){this.CommandName=A;var F;if (E==null) F=FCKConfig.SkinPath+'toolbar/'+A.toLowerCase()+'.gif';else if (typeof(E)=='number') F=[FCKConfig.SkinPath+'fck_strip.gif',16,E];var G=this._UIButton=new FCKToolbarButtonUI(A,B,C,F,D);G._FCKToolbarPanelButton=this;G.ShowArrow=true;G.OnClick=FCKToolbarPanelButton_OnButtonClick;};FCKToolbarPanelButton.prototype.TypeName='FCKToolbarPanelButton';FCKToolbarPanelButton.prototype.Create=function(A){A.className+='Menu';this._UIButton.Create(A);var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName)._Panel;this.RegisterPanel(B);};FCKToolbarPanelButton.prototype.RegisterPanel=function(A){if (A._FCKToolbarPanelButton) return;A._FCKToolbarPanelButton=this;var B=A.Document.body.appendChild(A.Document.createElement('div'));B.style.position='absolute';B.style.top='0px';var C=A._FCKToolbarPanelButtonLineDiv=B.appendChild(A.Document.createElement('IMG'));C.className='TB_ConnectionLine';C.style.position='absolute';C.src=FCK_SPACER_PATH;A.OnHide=FCKToolbarPanelButton_OnPanelHide;};function FCKToolbarPanelButton_OnButtonClick(A){var B=this._FCKToolbarPanelButton;var e=B._UIButton.MainElement;B._UIButton.ChangeState(1);var C=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(B.CommandName);var D=C._Panel;D._FCKToolbarPanelButtonLineDiv.style.width=(e.offsetWidth-2)+'px';C.Execute(0,e.offsetHeight-1,e);};function FCKToolbarPanelButton_OnPanelHide(){var A=this._FCKToolbarPanelButton;A._UIButton.ChangeState(0);};FCKToolbarPanelButton.prototype.RefreshState=FCKToolbarButton.prototype.RefreshState;FCKToolbarPanelButton.prototype.Enable=FCKToolbarButton.prototype.Enable;FCKToolbarPanelButton.prototype.Disable=FCKToolbarButton.prototype.Disable;
+var FCKScayt;(function(){var A=[];var B=(FCK&&FCK.EditorWindow&&FCK.EditorWindow.parent.parent.scayt)?true:false;var C=false;var D=false;function ScaytEngineLoad(callback){if (B) return;B=true;var E=FCK.EditorWindow.parent.parent;var F=function (){window.scayt=E.scayt;InitScayt();var G=FCKToolbarItems.LoadedItems['ScaytCombobox'];G&&G.SetEnabled(scyt_control&&scyt_control.disabled);InitSetup();};if (E.scayt){F();return;};if (FCK.Config.ScaytCustomUrl) FCK.Config.ScaytCustomUrl=new String(FCK.Config.ScaytCustomUrl).replace(new RegExp("^http[s]*:\/\/"),"");var H=document.location.protocol;var I=FCK.Config.ScaytCustomUrl||'svc.spellchecker.net/spellcheck3/lf/scayt/scayt4.js';var J=H+'//'+I;var K=ParseUrl(J).path+'/';var L=E.window.CKEDITOR||(E.window.CKEDITOR={});L._djScaytConfig={I:K,addOnLoad:function(){F();},isDebug:false};if (callback) A.push(callback);DoLoadScript(J);};function DoLoadScript(url){if (!url) return false;var E=FCK.EditorWindow.parent.parent;var s=E.document.createElement('script');s.type='text/javascript';s.src=url;E.document.getElementsByTagName('head')[0].appendChild(s);return true;};function ParseUrl(data){var m=data.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/);return m?{ path:m[1],file:m[2] }:data;};function createScaytControl (){var N={};var E=FCK.EditorWindow.parent.parent;N.srcNodeRef=FCK.EditingArea.IFrame;N.customerid=FCK.Config.ScaytCustomerid;N.customDictionaryName=FCK.Config.ScaytCustomDictionaryName;N.userDictionaryName=FCK.Config.ScaytUserDictionaryName;N.defLang=FCK.Config.ScaytDefLang;var P=E.scayt;var Q=window.scayt_control=new P(N);};function InitScayt(){createScaytControl();var Q=window.scayt_control;if (Q){Q.setDisabled(false);D=true;C=!Q.disabled;var G=FCKToolbarItems.LoadedItems['ScaytCombobox'];G&&G.Enable();ShowScaytState();};for (var i=0;i<A.length;i++){try{A[i].call(this);}catch(err){}}};var T=function(){name='Scayt';};T.prototype.Execute=function(c){switch (c){case 'Options':case 'Langs':case 'About':if (B&&D&&!C){ScaytMessage('SCAYT is not enabled');break;};if (B&&D) FCKDialog.OpenDialog('Scayt','SCAYT Settings','dialog/fck_scayt.html?'+c.toLowerCase(),343,343);break;default:if (!B){var U=this;ScaytEngineLoad(function (){U.SetEnabled(!window.scayt_control.disabled);});return true;}else if (D){if (C) this.Disable();else this.Enable();ShowScaytState();}};if (!B) return ScaytMessage('SCAYT is not loaded')||false;if (!D) return ScaytMessage('SCAYT is not ready')||false;return true;};T.prototype.Enable=function(){window.scayt_control.setDisabled(false);C=true;};T.prototype.Disable=function(){window.scayt_control.setDisabled(true);C=false;};T.prototype.SetEnabled=function(state){if (state) this.Enable();else this.Disable();ShowScaytState();return true;};T.prototype.GetState=function(){return 0;};function ShowScaytState(){var W=FCKToolbarItems.GetItem('SpellCheck');if (!W||!W._Combo||!W._Combo._OuterTable) return;var X=W._Combo._OuterTable.getElementsByTagName('img')[1];var Y=W._Combo.Items['trigger'];if (C){X.style.opacity='1';Y.innerHTML=GetStatusLabel();}else{X.style.opacity='0.5';Y.innerHTML=GetStatusLabel();}};function GetStatusLabel(){if (!D) return '<b>Enable SCAYT</b>';return C?'<b>Disable SCAYT</b>':'<b>Enable SCAYT</b>';};var Z=function(tooltip,style){this.Command=FCKCommands.GetCommand('Scayt');this.CommandName='Scayt';this.Label=this.GetLabel();this.Tooltip=FCKLang.ScaytTitle;this.Style=1;};Z.prototype=new FCKToolbarSpecialCombo;Z.prototype.CreateItems=function(){this._Combo.AddItem('Trigger','<b>Enable SCAYT</b>');this._Combo.AddItem('Options',FCKLang.ScaytTitleOptions||"Options");this._Combo.AddItem('Langs',FCKLang.ScaytTitleLangs||"Languages");this._Combo.AddItem('About',FCKLang.ScaytTitleAbout||"About");};Z.prototype.GetLabel=function(){var a=FCKConfig.SkinPath+'fck_strip.gif';return FCKBrowserInfo.IsIE?'<div class="TB_Button_Image"><img src="'+a+'" style="top:-192px"></div>':'<img class="TB_Button_Image" src="'+FCK_SPACER_PATH+'" style="background-position: 0px -192px;background-image: url('+a+');">';};function ScaytMessage(m){m&&alert(m);};var b=function(){name='ScaytContext';};b.prototype.Execute=function(contextInfo){var c=contextInfo&&contextInfo.action,g=c&&contextInfo.node,Q=window.scayt_control;if (g){switch (c){case 'Suggestion':Q.replace(g,contextInfo.suggestion);break;case 'Ignore':Q.ignore(g);break;case 'Ignore All':Q.ignoreAll(g);break;case 'Add Word':var E=FCK.EditorWindow.parent.parent;E.scayt.addWordToUserDictionary(g);break;}}};function InitSetup(){FCK.ContextMenu.RegisterListener({AddItems:function(menu){var E=FCK.EditorWindow.parent.parent;var Q=window.scayt_control,P=E.scayt;if (!Q) return;var g=Q.getScaytNode();if (!g) return;var h=P.getSuggestion(Q.getWord(g),Q.getLang());if (!h||!h.length) return;menu.AddSeparator();var j=FCK.Config.ScaytMaxSuggestions||5;var k=(j==-1)?h.length:j;for (var i=0;i<k;i+=1){if (h[i]){menu.AddItem('ScaytContext',h[i],null,false,{'action':'Suggestion','node':g,'suggestion':h[i] });}};menu.AddSeparator();menu.AddItem('ScaytContext','Ignore',null,false,{ 'action':'Ignore','node':g });menu.AddItem('ScaytContext','Ignore All',null,false,{ 'action':'Ignore All','node':g });menu.AddItem('ScaytContext','Add Word',null,false,{ 'action':'Add Word','node':g });try{if (D&&C) Q.fireOnContextMenu(null,FCK.ContextMenu._InnerContextMenu);}catch(err) {}}});FCK.Events.AttachEvent('OnPaste',function(){window.scayt_control.refresh();return true;});};FCK.Events.AttachEvent('OnAfterSetHTML',function(){if (FCKConfig.SpellChecker=='SCAYT'){if (!B&&FCK.Config.ScaytAutoStartup) ScaytEngineLoad();if (FCK.EditMode==0&&B&&D) createScaytControl();ShowScaytState();}});FCK.Events.AttachEvent('OnBeforeGetData',function(){D&&window.scayt_control.reset();});FCK.Events.AttachEvent('OnAfterGetData',function(){D&&window.scayt_control.refresh();});FCKScayt={CreateCommand:function(){return new T();},CreateContextCommand:function(){return new b();},CreateToolbarItem:function(){return new Z();}};})();
+var FCKToolbarItems={};FCKToolbarItems.LoadedItems={};FCKToolbarItems.RegisterItem=function(A,B){this.LoadedItems[A]=B;};FCKToolbarItems.GetItem=function(A){var B=FCKToolbarItems.LoadedItems[A];if (B) return B;switch (A){case 'Source':B=new FCKToolbarButton('Source',FCKLang.Source,null,2,true,true,1);break;case 'DocProps':B=new FCKToolbarButton('DocProps',FCKLang.DocProps,null,null,null,null,2);break;case 'Save':B=new FCKToolbarButton('Save',FCKLang.Save,null,null,true,null,3);break;case 'NewPage':B=new FCKToolbarButton('NewPage',FCKLang.NewPage,null,null,true,null,4);break;case 'Preview':B=new FCKToolbarButton('Preview',FCKLang.Preview,null,null,true,null,5);break;case 'Templates':B=new FCKToolbarButton('Templates',FCKLang.Templates,null,null,null,null,6);break;case 'About':B=new FCKToolbarButton('About',FCKLang.About,null,null,true,null,47);break;case 'Cut':B=new FCKToolbarButton('Cut',FCKLang.Cut,null,null,false,true,7);break;case 'Copy':B=new FCKToolbarButton('Copy',FCKLang.Copy,null,null,false,true,8);break;case 'Paste':B=new FCKToolbarButton('Paste',FCKLang.Paste,null,null,false,true,9);break;case 'PasteText':B=new FCKToolbarButton('PasteText',FCKLang.PasteText,null,null,false,true,10);break;case 'PasteWord':B=new FCKToolbarButton('PasteWord',FCKLang.PasteWord,null,null,false,true,11);break;case 'Print':B=new FCKToolbarButton('Print',FCKLang.Print,null,null,false,true,12);break;case 'Undo':B=new FCKToolbarButton('Undo',FCKLang.Undo,null,null,false,true,14);break;case 'Redo':B=new FCKToolbarButton('Redo',FCKLang.Redo,null,null,false,true,15);break;case 'SelectAll':B=new FCKToolbarButton('SelectAll',FCKLang.SelectAll,null,null,true,null,18);break;case 'RemoveFormat':B=new FCKToolbarButton('RemoveFormat',FCKLang.RemoveFormat,null,null,false,true,19);break;case 'FitWindow':B=new FCKToolbarButton('FitWindow',FCKLang.FitWindow,null,null,true,true,66);break;case 'Bold':B=new FCKToolbarButton('Bold',FCKLang.Bold,null,null,false,true,20);break;case 'Italic':B=new FCKToolbarButton('Italic',FCKLang.Italic,null,null,false,true,21);break;case 'Underline':B=new FCKToolbarButton('Underline',FCKLang.Underline,null,null,false,true,22);break;case 'StrikeThrough':B=new FCKToolbarButton('StrikeThrough',FCKLang.StrikeThrough,null,null,false,true,23);break;case 'Subscript':B=new FCKToolbarButton('Subscript',FCKLang.Subscript,null,null,false,true,24);break;case 'Superscript':B=new FCKToolbarButton('Superscript',FCKLang.Superscript,null,null,false,true,25);break;case 'OrderedList':B=new FCKToolbarButton('InsertOrderedList',FCKLang.NumberedListLbl,FCKLang.NumberedList,null,false,true,26);break;case 'UnorderedList':B=new FCKToolbarButton('InsertUnorderedList',FCKLang.BulletedListLbl,FCKLang.BulletedList,null,false,true,27);break;case 'Outdent':B=new FCKToolbarButton('Outdent',FCKLang.DecreaseIndent,null,null,false,true,28);break;case 'Indent':B=new FCKToolbarButton('Indent',FCKLang.IncreaseIndent,null,null,false,true,29);break;case 'Blockquote':B=new FCKToolbarButton('Blockquote',FCKLang.Blockquote,null,null,false,true,73);break;case 'CreateDiv':B=new FCKToolbarButton('CreateDiv',FCKLang.CreateDiv,null,null,false,true,74);break;case 'Link':B=new FCKToolbarButton('Link',FCKLang.InsertLinkLbl,FCKLang.InsertLink,null,false,true,34);break;case 'Unlink':B=new FCKToolbarButton('Unlink',FCKLang.RemoveLink,null,null,false,true,35);break;case 'Anchor':B=new FCKToolbarButton('Anchor',FCKLang.Anchor,null,null,null,null,36);break;case 'Image':B=new FCKToolbarButton('Image',FCKLang.InsertImageLbl,FCKLang.InsertImage,null,false,true,37);break;case 'Flash':B=new FCKToolbarButton('Flash',FCKLang.InsertFlashLbl,FCKLang.InsertFlash,null,false,true,38);break;case 'Table':B=new FCKToolbarButton('Table',FCKLang.InsertTableLbl,FCKLang.InsertTable,null,false,true,39);break;case 'SpecialChar':B=new FCKToolbarButton('SpecialChar',FCKLang.InsertSpecialCharLbl,FCKLang.InsertSpecialChar,null,false,true,42);break;case 'Smiley':B=new FCKToolbarButton('Smiley',FCKLang.InsertSmileyLbl,FCKLang.InsertSmiley,null,false,true,41);break;case 'PageBreak':B=new FCKToolbarButton('PageBreak',FCKLang.PageBreakLbl,FCKLang.PageBreak,null,false,true,43);break;case 'Rule':B=new FCKToolbarButton('Rule',FCKLang.InsertLineLbl,FCKLang.InsertLine,null,false,true,40);break;case 'JustifyLeft':B=new FCKToolbarButton('JustifyLeft',FCKLang.LeftJustify,null,null,false,true,30);break;case 'JustifyCenter':B=new FCKToolbarButton('JustifyCenter',FCKLang.CenterJustify,null,null,false,true,31);break;case 'JustifyRight':B=new FCKToolbarButton('JustifyRight',FCKLang.RightJustify,null,null,false,true,32);break;case 'JustifyFull':B=new FCKToolbarButton('JustifyFull',FCKLang.BlockJustify,null,null,false,true,33);break;case 'Style':B=new FCKToolbarStyleCombo();break;case 'FontName':B=new FCKToolbarFontsCombo();break;case 'FontSize':B=new FCKToolbarFontSizeCombo();break;case 'FontFormat':B=new FCKToolbarFontFormatCombo();break;case 'TextColor':B=new FCKToolbarPanelButton('TextColor',FCKLang.TextColor,null,null,45);break;case 'BGColor':B=new FCKToolbarPanelButton('BGColor',FCKLang.BGColor,null,null,46);break;case 'Find':B=new FCKToolbarButton('Find',FCKLang.Find,null,null,null,null,16);break;case 'Replace':B=new FCKToolbarButton('Replace',FCKLang.Replace,null,null,null,null,17);break;case 'Form':B=new FCKToolbarButton('Form',FCKLang.Form,null,null,null,null,48);break;case 'Checkbox':B=new FCKToolbarButton('Checkbox',FCKLang.Checkbox,null,null,null,null,49);break;case 'Radio':B=new FCKToolbarButton('Radio',FCKLang.RadioButton,null,null,null,null,50);break;case 'TextField':B=new FCKToolbarButton('TextField',FCKLang.TextField,null,null,null,null,51);break;case 'Textarea':B=new FCKToolbarButton('Textarea',FCKLang.Textarea,null,null,null,null,52);break;case 'HiddenField':B=new FCKToolbarButton('HiddenField',FCKLang.HiddenField,null,null,null,null,56);break;case 'Button':B=new FCKToolbarButton('Button',FCKLang.Button,null,null,null,null,54);break;case 'Select':B=new FCKToolbarButton('Select',FCKLang.SelectionField,null,null,null,null,53);break;case 'ImageButton':B=new FCKToolbarButton('ImageButton',FCKLang.ImageButton,null,null,null,null,55);break;case 'ShowBlocks':B=new FCKToolbarButton('ShowBlocks',FCKLang.ShowBlocks,null,null,null,true,72);break;case 'SpellCheck':if (FCKConfig.SpellChecker=='SCAYT') B=FCKScayt.CreateToolbarItem();else B=new FCKToolbarButton('SpellCheck',FCKLang.SpellCheck,null,null,null,null,13);break;default:alert(FCKLang.UnknownToolbarItem.replace(/%1/g,A));return null;};FCKToolbarItems.LoadedItems[A]=B;return B;};
+var FCKToolbar=function(){this.Items=[];};FCKToolbar.prototype.AddItem=function(A){return this.Items[this.Items.length]=A;};FCKToolbar.prototype.AddButton=function(A,B,C,D,E,F){if (typeof(D)=='number') D=[this.DefaultIconsStrip,this.DefaultIconSize,D];var G=new FCKToolbarButtonUI(A,B,C,D,E,F);G._FCKToolbar=this;G.OnClick=FCKToolbar_OnItemClick;return this.AddItem(G);};function FCKToolbar_OnItemClick(A){var B=A._FCKToolbar;if (B.OnItemClick) B.OnItemClick(B,A);};FCKToolbar.prototype.AddSeparator=function(){this.AddItem(new FCKToolbarSeparator());};FCKToolbar.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var e=B.createElement('table');e.className='TB_Toolbar';e.style.styleFloat=e.style.cssFloat=(FCKLang.Dir=='ltr'?'left':'right');e.dir=FCKLang.Dir;e.cellPadding=0;e.cellSpacing=0;var C=e.insertRow(-1);var D;if (!this.HideStart){D=C.insertCell(-1);D.appendChild(B.createElement('div')).className='TB_Start';};for (var i=0;i<this.Items.length;i++){this.Items[i].Create(C.insertCell(-1));};if (!this.HideEnd){D=C.insertCell(-1);D.appendChild(B.createElement('div')).className='TB_End';};A.appendChild(e);};var FCKToolbarSeparator=function(){};FCKToolbarSeparator.prototype.Create=function(A){FCKTools.AppendElement(A,'div').className='TB_Separator';};
+var FCKToolbarBreak=function(){};FCKToolbarBreak.prototype.Create=function(A){var B=A.ownerDocument.createElement('div');B.style.clear=B.style.cssFloat=FCKLang.Dir=='rtl'?'right':'left';A.appendChild(B);};
+function FCKToolbarSet_Create(A){var B;var C=A||FCKConfig.ToolbarLocation;switch (C){case 'In':document.getElementById('xToolbarRow').style.display='';B=new FCKToolbarSet(document);break;case 'None':B=new FCKToolbarSet(document);break;default:FCK.Events.AttachEvent('OnBlur',FCK_OnBlur);FCK.Events.AttachEvent('OnFocus',FCK_OnFocus);var D;var E=C.match(/^Out:(.+)\((\w+)\)$/);if (E){if (FCKBrowserInfo.IsAIR) FCKAdobeAIR.ToolbarSet_GetOutElement(window,E);else D=eval('parent.'+E[1]).document.getElementById(E[2]);}else{E=C.match(/^Out:(\w+)$/);if (E) D=parent.document.getElementById(E[1]);};if (!D){alert('Invalid value for "ToolbarLocation"');return arguments.callee('In');};B=D.__FCKToolbarSet;if (B) break;var F=FCKTools.GetElementDocument(D).createElement('iframe');F.src='javascript:void(0)';F.frameBorder=0;F.width='100%';F.height='10';D.appendChild(F);F.unselectable='on';var G=F.contentWindow.document;var H='';if (FCKBrowserInfo.IsSafari) H='<base href="'+window.document.location+'">';G.open();G.write('<html><head>'+H+'<script type="text/javascript"> var adjust = function() { window.frameElement.height = document.body.scrollHeight ; }; window.onresize = window.onload = function(){var timer = null;var lastHeight = -1;var lastChange = 0;var poller = function(){var currentHeight = document.body.scrollHeight || 0;var currentTime = (new Date()).getTime();if (currentHeight != lastHeight){lastChange = currentTime;adjust();lastHeight = document.body.scrollHeight;}if (lastChange < currentTime - 1000) clearInterval(timer);};timer = setInterval(poller, 100);}</script></head><body style="overflow: hidden">'+document.getElementById('xToolbarSpace').innerHTML+'</body></html>');G.close();if(FCKBrowserInfo.IsAIR) FCKAdobeAIR.ToolbarSet_InitOutFrame(G);FCKTools.AddEventListener(G,'contextmenu',FCKTools.CancelEvent);FCKTools.AppendStyleSheet(G,FCKConfig.SkinEditorCSS);B=D.__FCKToolbarSet=new FCKToolbarSet(G);B._IFrame=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(D,FCKToolbarSet_Target_Cleanup);};B.CurrentInstance=FCK;if (!B.ToolbarItems) B.ToolbarItems=FCKToolbarItems;FCK.AttachToOnSelectionChange(B.RefreshItemsState);return B;};function FCK_OnBlur(A){var B=A.ToolbarSet;if (B.CurrentInstance==A) B.Disable();};function FCK_OnFocus(A){var B=A.ToolbarSet;var C=A||FCK;B.CurrentInstance.FocusManager.RemoveWindow(B._IFrame.contentWindow);B.CurrentInstance=C;C.FocusManager.AddWindow(B._IFrame.contentWindow,true);B.Enable();};function FCKToolbarSet_Cleanup(){this._TargetElement=null;this._IFrame=null;};function FCKToolbarSet_Target_Cleanup(){this.__FCKToolbarSet=null;};var FCKToolbarSet=function(A){this._Document=A;this._TargetElement=A.getElementById('xToolbar');var B=A.getElementById('xExpandHandle');var C=A.getElementById('xCollapseHandle');B.title=FCKLang.ToolbarExpand;FCKTools.AddEventListener(B,'click',FCKToolbarSet_Expand_OnClick);C.title=FCKLang.ToolbarCollapse;FCKTools.AddEventListener(C,'click',FCKToolbarSet_Collapse_OnClick);if (!FCKConfig.ToolbarCanCollapse||FCKConfig.ToolbarStartExpanded) this.Expand();else this.Collapse();C.style.display=FCKConfig.ToolbarCanCollapse?'':'none';if (FCKConfig.ToolbarCanCollapse) C.style.display='';else A.getElementById('xTBLeftBorder').style.display='';this.Toolbars=[];this.IsLoaded=false;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbarSet_Cleanup);};function FCKToolbarSet_Expand_OnClick(){FCK.ToolbarSet.Expand();};function FCKToolbarSet_Collapse_OnClick(){FCK.ToolbarSet.Collapse();};FCKToolbarSet.prototype.Expand=function(){this._ChangeVisibility(false);};FCKToolbarSet.prototype.Collapse=function(){this._ChangeVisibility(true);};FCKToolbarSet.prototype._ChangeVisibility=function(A){this._Document.getElementById('xCollapsed').style.display=A?'':'none';this._Document.getElementById('xExpanded').style.display=A?'none':'';if (window.onresize){FCKTools.RunFunction(window.onresize);}};FCKToolbarSet.prototype.Load=function(A){this.Name=A;this.Items=[];this.ItemsWysiwygOnly=[];this.ItemsContextSensitive=[];this._TargetElement.innerHTML='';var B=FCKConfig.ToolbarSets[A];if (!B){alert(FCKLang.UnknownToolbarSet.replace(/%1/g,A));return;};this.Toolbars=[];for (var x=0;x<B.length;x++){var C=B[x];if (!C) continue;var D;if (typeof(C)=='string'){if (C=='/') D=new FCKToolbarBreak();}else{D=new FCKToolbar();for (var j=0;j<C.length;j++){var E=C[j];if (E=='-') D.AddSeparator();else{var F=FCKToolbarItems.GetItem(E);if (F){D.AddItem(F);this.Items.push(F);if (!F.SourceView) this.ItemsWysiwygOnly.push(F);if (F.ContextSensitive) this.ItemsContextSensitive.push(F);}}}};D.Create(this._TargetElement);this.Toolbars[this.Toolbars.length]=D;};FCKTools.DisableSelection(this._Document.getElementById('xCollapseHandle').parentNode);if (FCK.Status!=2) FCK.Events.AttachEvent('OnStatusChange',this.RefreshModeState);else this.RefreshModeState();this.IsLoaded=true;this.IsEnabled=true;FCKTools.RunFunction(this.OnLoad);};FCKToolbarSet.prototype.Enable=function(){if (this.IsEnabled) return;this.IsEnabled=true;var A=this.Items;for (var i=0;i<A.length;i++) A[i].RefreshState();};FCKToolbarSet.prototype.Disable=function(){if (!this.IsEnabled) return;this.IsEnabled=false;var A=this.Items;for (var i=0;i<A.length;i++) A[i].Disable();};FCKToolbarSet.prototype.RefreshModeState=function(A){if (FCK.Status!=2) return;var B=A?A.ToolbarSet:this;var C=B.ItemsWysiwygOnly;if (FCK.EditMode==0){for (var i=0;i<C.length;i++) C[i].Enable();B.RefreshItemsState(A);}else{B.RefreshItemsState(A);for (var j=0;j<C.length;j++) C[j].Disable();}};FCKToolbarSet.prototype.RefreshItemsState=function(A){var B=(A?A.ToolbarSet:this).ItemsContextSensitive;for (var i=0;i<B.length;i++) B[i].RefreshState();};
+var FCKDialog=(function(){var A;var B;var C;var D=window.parent;while (D.parent&&D.parent!=D){try{if (D.parent.document.domain!=document.domain) break;if (D.parent.document.getElementsByTagName('frameset').length>0) break;}catch (e){break;};D=D.parent;};var E=D.document;var F=function(){if (!B) B=FCKConfig.FloatingPanelsZIndex+999;return++B;};var G=function(){if (!C) return;var H=FCKTools.IsStrictMode(E)?E.documentElement:E.body;FCKDomTools.SetElementStyles(C,{'width':Math.max(H.scrollWidth,H.clientWidth,E.scrollWidth||0)-1+'px','height':Math.max(H.scrollHeight,H.clientHeight,E.scrollHeight||0)-1+'px'});};return {OpenDialog:function(dialogName,dialogTitle,dialogPage,width,height,customValue,resizable){if (!A) this.DisplayMainCover();var I={Title:dialogTitle,Page:dialogPage,Editor:window,CustomValue:customValue,TopWindow:D};FCK.ToolbarSet.CurrentInstance.Selection.Save(true);var J=FCKTools.GetViewPaneSize(D);var K={ 'X':0,'Y':0 };var L=FCKBrowserInfo.IsIE&&(!FCKBrowserInfo.IsIE7||!FCKTools.IsStrictMode(D.document));if (L) K=FCKTools.GetScrollPosition(D);var M=Math.max(K.Y+(J.Height-height-20)/2,0);var N=Math.max(K.X+(J.Width-width-20)/2,0);var O=E.createElement('iframe');FCKTools.ResetStyles(O);O.src=FCKConfig.BasePath+'fckdialog.html';O.frameBorder=0;O.allowTransparency=true;FCKDomTools.SetElementStyles(O,{'position':(L)?'absolute':'fixed','top':M+'px','left':N+'px','width':width+'px','height':height+'px','zIndex':F()});O._DialogArguments=I;E.body.appendChild(O);O._ParentDialog=A;A=O;},OnDialogClose:function(dialogWindow){var O=dialogWindow.frameElement;FCKDomTools.RemoveNode(O);if (O._ParentDialog){A=O._ParentDialog;O._ParentDialog.contentWindow.SetEnabled(true);}else{if (!FCKBrowserInfo.IsIE) FCK.Focus();this.HideMainCover();setTimeout(function(){ A=null;},0);FCK.ToolbarSet.CurrentInstance.Selection.Release();}},DisplayMainCover:function(){C=E.createElement('div');FCKTools.ResetStyles(C);FCKDomTools.SetElementStyles(C,{'position':'absolute','zIndex':F(),'top':'0px','left':'0px','backgroundColor':FCKConfig.BackgroundBlockerColor});FCKDomTools.SetOpacity(C,FCKConfig.BackgroundBlockerOpacity);if (FCKBrowserInfo.IsIE&&!FCKBrowserInfo.IsIE7){var Q=E.createElement('iframe');FCKTools.ResetStyles(Q);Q.hideFocus=true;Q.frameBorder=0;Q.src=FCKTools.GetVoidUrl();FCKDomTools.SetElementStyles(Q,{'width':'100%','height':'100%','position':'absolute','left':'0px','top':'0px','filter':'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'});C.appendChild(Q);};FCKTools.AddEventListener(D,'resize',G);G();E.body.appendChild(C);FCKFocusManager.Lock();var R=FCK.ToolbarSet.CurrentInstance.GetInstanceObject('frameElement');R._fck_originalTabIndex=R.tabIndex;R.tabIndex=-1;},HideMainCover:function(){FCKDomTools.RemoveNode(C);FCKFocusManager.Unlock();var R=FCK.ToolbarSet.CurrentInstance.GetInstanceObject('frameElement');R.tabIndex=R._fck_originalTabIndex;FCKDomTools.ClearElementJSProperty(R,'_fck_originalTabIndex');},GetCover:function(){return C;}};})();
+var FCKMenuItem=function(A,B,C,D,E,F){this.Name=B;this.Label=C||B;this.IsDisabled=E;this.Icon=new FCKIcon(D);this.SubMenu=new FCKMenuBlockPanel();this.SubMenu.Parent=A;this.SubMenu.OnClick=FCKTools.CreateEventListener(FCKMenuItem_SubMenu_OnClick,this);this.CustomData=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuItem_Cleanup);};FCKMenuItem.prototype.AddItem=function(A,B,C,D,E){this.HasSubMenu=true;return this.SubMenu.AddItem(A,B,C,D,E);};FCKMenuItem.prototype.AddSeparator=function(){this.SubMenu.AddSeparator();};FCKMenuItem.prototype.Create=function(A){var B=this.HasSubMenu;var C=FCKTools.GetElementDocument(A);var r=this.MainElement=A.insertRow(-1);r.className=this.IsDisabled?'MN_Item_Disabled':'MN_Item';if (!this.IsDisabled){FCKTools.AddEventListenerEx(r,'mouseover',FCKMenuItem_OnMouseOver,[this]);FCKTools.AddEventListenerEx(r,'click',FCKMenuItem_OnClick,[this]);if (!B) FCKTools.AddEventListenerEx(r,'mouseout',FCKMenuItem_OnMouseOut,[this]);};var D=r.insertCell(-1);D.className='MN_Icon';D.appendChild(this.Icon.CreateIconElement(C));D=r.insertCell(-1);D.className='MN_Label';D.noWrap=true;D.appendChild(C.createTextNode(this.Label));D=r.insertCell(-1);if (B){D.className='MN_Arrow';var E=D.appendChild(C.createElement('IMG'));E.src=FCK_IMAGES_PATH+'arrow_'+FCKLang.Dir+'.gif';E.width=4;E.height=7;this.SubMenu.Create();this.SubMenu.Panel.OnHide=FCKTools.CreateEventListener(FCKMenuItem_SubMenu_OnHide,this);}};FCKMenuItem.prototype.Activate=function(){this.MainElement.className='MN_Item_Over';if (this.HasSubMenu){this.SubMenu.Show(this.MainElement.offsetWidth+2,-2,this.MainElement);};FCKTools.RunFunction(this.OnActivate,this);};FCKMenuItem.prototype.Deactivate=function(){this.MainElement.className='MN_Item';if (this.HasSubMenu) this.SubMenu.Hide();};function FCKMenuItem_SubMenu_OnClick(A,B){FCKTools.RunFunction(B.OnClick,B,[A]);};function FCKMenuItem_SubMenu_OnHide(A){A.Deactivate();};function FCKMenuItem_OnClick(A,B){if (B.HasSubMenu) B.Activate();else{B.Deactivate();FCKTools.RunFunction(B.OnClick,B,[B]);}};function FCKMenuItem_OnMouseOver(A,B){B.Activate();};function FCKMenuItem_OnMouseOut(A,B){B.Deactivate();};function FCKMenuItem_Cleanup(){this.MainElement=null;};
+var FCKMenuBlock=function(){this._Items=[];};FCKMenuBlock.prototype.Count=function(){return this._Items.length;};FCKMenuBlock.prototype.AddItem=function(A,B,C,D,E){var F=new FCKMenuItem(this,A,B,C,D,E);F.OnClick=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnClick,this);F.OnActivate=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnActivate,this);this._Items.push(F);return F;};FCKMenuBlock.prototype.AddSeparator=function(){this._Items.push(new FCKMenuSeparator());};FCKMenuBlock.prototype.RemoveAllItems=function(){this._Items=[];var A=this._ItemsTable;if (A){while (A.rows.length>0) A.deleteRow(0);}};FCKMenuBlock.prototype.Create=function(A){if (!this._ItemsTable){if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuBlock_Cleanup);this._Window=FCKTools.GetElementWindow(A);var B=FCKTools.GetElementDocument(A);var C=A.appendChild(B.createElement('table'));C.cellPadding=0;C.cellSpacing=0;FCKTools.DisableSelection(C);var D=C.insertRow(-1).insertCell(-1);D.className='MN_Menu';var E=this._ItemsTable=D.appendChild(B.createElement('table'));E.cellPadding=0;E.cellSpacing=0;};for (var i=0;i<this._Items.length;i++) this._Items[i].Create(this._ItemsTable);};function FCKMenuBlock_Item_OnClick(A,B){if (B.Hide) B.Hide();FCKTools.RunFunction(B.OnClick,B,[A]);};function FCKMenuBlock_Item_OnActivate(A){var B=A._ActiveItem;if (B&&B!=this){if (!FCKBrowserInfo.IsIE&&B.HasSubMenu&&!this.HasSubMenu){A._Window.focus();A.Panel.HasFocus=true;};B.Deactivate();};A._ActiveItem=this;};function FCKMenuBlock_Cleanup(){this._Window=null;this._ItemsTable=null;};var FCKMenuSeparator=function(){};FCKMenuSeparator.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var r=A.insertRow(-1);var C=r.insertCell(-1);C.className='MN_Separator MN_Icon';C=r.insertCell(-1);C.className='MN_Separator';C.appendChild(B.createElement('DIV')).className='MN_Separator_Line';C=r.insertCell(-1);C.className='MN_Separator';C.appendChild(B.createElement('DIV')).className='MN_Separator_Line';};
+var FCKMenuBlockPanel=function(){FCKMenuBlock.call(this);};FCKMenuBlockPanel.prototype=new FCKMenuBlock();FCKMenuBlockPanel.prototype.Create=function(){var A=this.Panel=(this.Parent&&this.Parent.Panel?this.Parent.Panel.CreateChildPanel():new FCKPanel());A.AppendStyleSheet(FCKConfig.SkinEditorCSS);FCKMenuBlock.prototype.Create.call(this,A.MainNode);};FCKMenuBlockPanel.prototype.Show=function(x,y,A){if (!this.Panel.CheckIsOpened()) this.Panel.Show(x,y,A);};FCKMenuBlockPanel.prototype.Hide=function(){if (this.Panel.CheckIsOpened()) this.Panel.Hide();};
+var FCKContextMenu=function(A,B){this.CtrlDisable=false;var C=this._Panel=new FCKPanel(A);C.AppendStyleSheet(FCKConfig.SkinEditorCSS);C.IsContextMenu=true;if (FCKBrowserInfo.IsGecko) C.Document.addEventListener('draggesture',function(e) {e.preventDefault();return false;},true);var D=this._MenuBlock=new FCKMenuBlock();D.Panel=C;D.OnClick=FCKTools.CreateEventListener(FCKContextMenu_MenuBlock_OnClick,this);this._Redraw=true;};FCKContextMenu.prototype.SetMouseClickWindow=function(A){if (!FCKBrowserInfo.IsIE){this._Document=A.document;if (FCKBrowserInfo.IsOpera&&!('oncontextmenu' in document.createElement('foo'))){this._Document.addEventListener('mousedown',FCKContextMenu_Document_OnMouseDown,false);this._Document.addEventListener('mouseup',FCKContextMenu_Document_OnMouseUp,false);};this._Document.addEventListener('contextmenu',FCKContextMenu_Document_OnContextMenu,false);}};FCKContextMenu.prototype.AddItem=function(A,B,C,D,E){var F=this._MenuBlock.AddItem(A,B,C,D,E);this._Redraw=true;return F;};FCKContextMenu.prototype.AddSeparator=function(){this._MenuBlock.AddSeparator();this._Redraw=true;};FCKContextMenu.prototype.RemoveAllItems=function(){this._MenuBlock.RemoveAllItems();this._Redraw=true;};FCKContextMenu.prototype.AttachToElement=function(A){if (FCKBrowserInfo.IsIE) FCKTools.AddEventListenerEx(A,'contextmenu',FCKContextMenu_AttachedElement_OnContextMenu,this);else A._FCKContextMenu=this;};function FCKContextMenu_Document_OnContextMenu(e){if (FCKConfig.BrowserContextMenu) return true;var A=e.target;while (A){if (A._FCKContextMenu){if (A._FCKContextMenu.CtrlDisable&&(e.ctrlKey||e.metaKey)) return true;FCKTools.CancelEvent(e);FCKContextMenu_AttachedElement_OnContextMenu(e,A._FCKContextMenu,A);return false;};A=A.parentNode;};return true;};var FCKContextMenu_OverrideButton;function FCKContextMenu_Document_OnMouseDown(e){if(!e||e.button!=2) return false;if (FCKConfig.BrowserContextMenu) return true;var A=e.target;while (A){if (A._FCKContextMenu){if (A._FCKContextMenu.CtrlDisable&&(e.ctrlKey||e.metaKey)) return true;var B=FCKContextMenu_OverrideButton;if(!B){var C=FCKTools.GetElementDocument(e.target);B=FCKContextMenu_OverrideButton=C.createElement('input');B.type='button';var D=C.createElement('p');C.body.appendChild(D);D.appendChild(B);};B.style.cssText='position:absolute;top:'+(e.clientY-2)+'px;left:'+(e.clientX-2)+'px;width:5px;height:5px;opacity:0.01';};A=A.parentNode;};return false;};function FCKContextMenu_Document_OnMouseUp(e){if (FCKConfig.BrowserContextMenu) return true;var A=FCKContextMenu_OverrideButton;if (A){var B=A.parentNode;B.parentNode.removeChild(B);FCKContextMenu_OverrideButton=undefined;if(e&&e.button==2){FCKContextMenu_Document_OnContextMenu(e);return false;}};return true;};function FCKContextMenu_AttachedElement_OnContextMenu(A,B,C){if ((B.CtrlDisable&&(A.ctrlKey||A.metaKey))||FCKConfig.BrowserContextMenu) return true;var D=C||this;if (B.OnBeforeOpen) B.OnBeforeOpen.call(B,D);if (B._MenuBlock.Count()==0) return false;if (B._Redraw){B._MenuBlock.Create(B._Panel.MainNode);B._Redraw=false;};FCKTools.DisableSelection(B._Panel.Document.body);var x=0;var y=0;if (FCKBrowserInfo.IsIE){x=A.screenX;y=A.screenY;}else if (FCKBrowserInfo.IsSafari){x=A.clientX;y=A.clientY;}else{x=A.pageX;y=A.pageY;};B._Panel.Show(x,y,A.currentTarget||null);return false;};function FCKContextMenu_MenuBlock_OnClick(A,B){B._Panel.Hide();FCKTools.RunFunction(B.OnItemClick,B,A);};
+FCK.ContextMenu={};FCK.ContextMenu.Listeners=[];FCK.ContextMenu.RegisterListener=function(A){if (A) this.Listeners.push(A);};function FCK_ContextMenu_Init(){var A=FCK.ContextMenu._InnerContextMenu=new FCKContextMenu(FCKBrowserInfo.IsIE?window:window.parent,FCKLang.Dir);A.CtrlDisable=FCKConfig.BrowserContextMenuOnCtrl;A.OnBeforeOpen=FCK_ContextMenu_OnBeforeOpen;A.OnItemClick=FCK_ContextMenu_OnItemClick;var B=FCK.ContextMenu;for (var i=0;i<FCKConfig.ContextMenu.length;i++) B.RegisterListener(FCK_ContextMenu_GetListener(FCKConfig.ContextMenu[i]));};function FCK_ContextMenu_GetListener(A){switch (A){case 'Generic':return {AddItems:function(menu,tag,tagName){menu.AddItem('Cut',FCKLang.Cut,7,FCKCommands.GetCommand('Cut').GetState()==-1);menu.AddItem('Copy',FCKLang.Copy,8,FCKCommands.GetCommand('Copy').GetState()==-1);menu.AddItem('Paste',FCKLang.Paste,9,FCKCommands.GetCommand('Paste').GetState()==-1);}};case 'Table':return {AddItems:function(menu,tag,tagName){var B=(tagName=='TABLE');var C=(!B&&FCKSelection.HasAncestorNode('TABLE'));if (C){menu.AddSeparator();var D=menu.AddItem('Cell',FCKLang.CellCM);D.AddItem('TableInsertCellBefore',FCKLang.InsertCellBefore,69);D.AddItem('TableInsertCellAfter',FCKLang.InsertCellAfter,58);D.AddItem('TableDeleteCells',FCKLang.DeleteCells,59);if (FCKBrowserInfo.IsGecko) D.AddItem('TableMergeCells',FCKLang.MergeCells,60,FCKCommands.GetCommand('TableMergeCells').GetState()==-1);else{D.AddItem('TableMergeRight',FCKLang.MergeRight,60,FCKCommands.GetCommand('TableMergeRight').GetState()==-1);D.AddItem('TableMergeDown',FCKLang.MergeDown,60,FCKCommands.GetCommand('TableMergeDown').GetState()==-1);};D.AddItem('TableHorizontalSplitCell',FCKLang.HorizontalSplitCell,61,FCKCommands.GetCommand('TableHorizontalSplitCell').GetState()==-1);D.AddItem('TableVerticalSplitCell',FCKLang.VerticalSplitCell,61,FCKCommands.GetCommand('TableVerticalSplitCell').GetState()==-1);D.AddSeparator();D.AddItem('TableCellProp',FCKLang.CellProperties,57,FCKCommands.GetCommand('TableCellProp').GetState()==-1);menu.AddSeparator();D=menu.AddItem('Row',FCKLang.RowCM);D.AddItem('TableInsertRowBefore',FCKLang.InsertRowBefore,70);D.AddItem('TableInsertRowAfter',FCKLang.InsertRowAfter,62);D.AddItem('TableDeleteRows',FCKLang.DeleteRows,63);menu.AddSeparator();D=menu.AddItem('Column',FCKLang.ColumnCM);D.AddItem('TableInsertColumnBefore',FCKLang.InsertColumnBefore,71);D.AddItem('TableInsertColumnAfter',FCKLang.InsertColumnAfter,64);D.AddItem('TableDeleteColumns',FCKLang.DeleteColumns,65);};if (B||C){menu.AddSeparator();menu.AddItem('TableDelete',FCKLang.TableDelete);menu.AddItem('TableProp',FCKLang.TableProperties,39);}}};case 'Link':return {AddItems:function(menu,tag,tagName){var E=(tagName=='A'||FCKSelection.HasAncestorNode('A'));if (E||FCK.GetNamedCommandState('Unlink')!=-1){var F=FCKSelection.MoveToAncestorNode('A');var G=(F&&F.name.length>0&&F.href.length==0);if (G) return;menu.AddSeparator();menu.AddItem('VisitLink',FCKLang.VisitLink);menu.AddSeparator();if (E) menu.AddItem('Link',FCKLang.EditLink,34);menu.AddItem('Unlink',FCKLang.RemoveLink,35);}}};case 'Image':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&!tag.getAttribute('_fckfakelement')){menu.AddSeparator();menu.AddItem('Image',FCKLang.ImageProperties,37);}}};case 'Anchor':return {AddItems:function(menu,tag,tagName){var F=FCKSelection.MoveToAncestorNode('A');var G=(F&&F.name.length>0);if (G||(tagName=='IMG'&&tag.getAttribute('_fckanchor'))){menu.AddSeparator();menu.AddItem('Anchor',FCKLang.AnchorProp,36);menu.AddItem('AnchorDelete',FCKLang.AnchorDelete);}}};case 'Flash':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&tag.getAttribute('_fckflash')){menu.AddSeparator();menu.AddItem('Flash',FCKLang.FlashProperties,38);}}};case 'Form':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('FORM')){menu.AddSeparator();menu.AddItem('Form',FCKLang.FormProp,48);}}};case 'Checkbox':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='checkbox'){menu.AddSeparator();menu.AddItem('Checkbox',FCKLang.CheckboxProp,49);}}};case 'Radio':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='radio'){menu.AddSeparator();menu.AddItem('Radio',FCKLang.RadioButtonProp,50);}}};case 'TextField':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&(tag.type=='text'||tag.type=='password')){menu.AddSeparator();menu.AddItem('TextField',FCKLang.TextFieldProp,51);}}};case 'HiddenField':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&tag.getAttribute('_fckinputhidden')){menu.AddSeparator();menu.AddItem('HiddenField',FCKLang.HiddenFieldProp,56);}}};case 'ImageButton':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='image'){menu.AddSeparator();menu.AddItem('ImageButton',FCKLang.ImageButtonProp,55);}}};case 'Button':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&(tag.type=='button'||tag.type=='submit'||tag.type=='reset')){menu.AddSeparator();menu.AddItem('Button',FCKLang.ButtonProp,54);}}};case 'Select':return {AddItems:function(menu,tag,tagName){if (tagName=='SELECT'){menu.AddSeparator();menu.AddItem('Select',FCKLang.SelectionFieldProp,53);}}};case 'Textarea':return {AddItems:function(menu,tag,tagName){if (tagName=='TEXTAREA'){menu.AddSeparator();menu.AddItem('Textarea',FCKLang.TextareaProp,52);}}};case 'BulletedList':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('UL')){menu.AddSeparator();menu.AddItem('BulletedList',FCKLang.BulletedListProp,27);}}};case 'NumberedList':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('OL')){menu.AddSeparator();menu.AddItem('NumberedList',FCKLang.NumberedListProp,26);}}};case 'DivContainer':return {AddItems:function(menu,tag,tagName){var J=FCKDomTools.GetSelectedDivContainers();if (J.length>0){menu.AddSeparator();menu.AddItem('EditDiv',FCKLang.EditDiv,75);menu.AddItem('DeleteDiv',FCKLang.DeleteDiv,76);}}};};return null;};function FCK_ContextMenu_OnBeforeOpen(){FCK.Events.FireEvent('OnSelectionChange');var A,sTagName;if ((A=FCKSelection.GetSelectedElement())) sTagName=A.tagName;var B=FCK.ContextMenu._InnerContextMenu;B.RemoveAllItems();var C=FCK.ContextMenu.Listeners;for (var i=0;i<C.length;i++) C[i].AddItems(B,A,sTagName);};function FCK_ContextMenu_OnItemClick(A){if (!FCKBrowserInfo.IsIE) FCK.Focus();FCKCommands.GetCommand(A.Name).Execute(A.CustomData);};
+var FCKHtmlIterator=function(A){this._sourceHtml=A;};FCKHtmlIterator.prototype={Next:function(){var A=this._sourceHtml;if (A==null) return null;var B=FCKRegexLib.HtmlTag.exec(A);var C=false;var D="";if (B){if (B.index>0){D=A.substr(0,B.index);this._sourceHtml=A.substr(B.index);}else{C=true;D=B[0];this._sourceHtml=A.substr(B[0].length);}}else{D=A;this._sourceHtml=null;};return { 'isTag':C,'value':D };},Each:function(A){var B;while ((B=this.Next())) A(B.isTag,B.value);}};var FCKHtmlIterator=function(A){this._sourceHtml=A;};FCKHtmlIterator.prototype={Next:function(){var A=this._sourceHtml;if (A==null) return null;var B=FCKRegexLib.HtmlTag.exec(A);var C=false;var D="";if (B){if (B.index>0){D=A.substr(0,B.index);this._sourceHtml=A.substr(B.index);}else{C=true;D=B[0];this._sourceHtml=A.substr(B[0].length);}}else{D=A;this._sourceHtml=null;};return { 'isTag':C,'value':D };},Each:function(A){var B;while ((B=this.Next())) A(B.isTag,B.value);}};
+var FCKPlugin=function(A,B,C){this.Name=A;this.BasePath=C?C:FCKConfig.PluginsPath;this.Path=this.BasePath+A+'/';if (!B||B.length==0) this.AvailableLangs=[];else this.AvailableLangs=B.split(',');};FCKPlugin.prototype.Load=function(){if (this.AvailableLangs.length>0){var A;if (this.AvailableLangs.IndexOf(FCKLanguageManager.ActiveLanguage.Code)>=0) A=FCKLanguageManager.ActiveLanguage.Code;else A=this.AvailableLangs[0];LoadScript(this.Path+'lang/'+A+'.js');};LoadScript(this.Path+'fckplugin.js');};
+var FCKPlugins=FCK.Plugins={};FCKPlugins.ItemsCount=0;FCKPlugins.Items={};FCKPlugins.Load=function(){var A=FCKPlugins.Items;for (var i=0;i<FCKConfig.Plugins.Items.length;i++){var B=FCKConfig.Plugins.Items[i];var C=A[B[0]]=new FCKPlugin(B[0],B[1],B[2]);FCKPlugins.ItemsCount++;};for (var s in A) A[s].Load();FCKPlugins.Load=null;};
diff --git a/httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js b/httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js
new file mode 100644
index 0000000..dab0d63
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js
@@ -0,0 +1,110 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This file has been compressed for better performance. The original source
+ * can be found at "editor/_source".
+ */
+
+var FCK_STATUS_NOTLOADED=window.parent.FCK_STATUS_NOTLOADED=0;var FCK_STATUS_ACTIVE=window.parent.FCK_STATUS_ACTIVE=1;var FCK_STATUS_COMPLETE=window.parent.FCK_STATUS_COMPLETE=2;var FCK_TRISTATE_OFF=window.parent.FCK_TRISTATE_OFF=0;var FCK_TRISTATE_ON=window.parent.FCK_TRISTATE_ON=1;var FCK_TRISTATE_DISABLED=window.parent.FCK_TRISTATE_DISABLED=-1;var FCK_UNKNOWN=window.parent.FCK_UNKNOWN=-9;var FCK_TOOLBARITEM_ONLYICON=window.parent.FCK_TOOLBARITEM_ONLYICON=0;var FCK_TOOLBARITEM_ONLYTEXT=window.parent.FCK_TOOLBARITEM_ONLYTEXT=1;var FCK_TOOLBARITEM_ICONTEXT=window.parent.FCK_TOOLBARITEM_ICONTEXT=2;var FCK_EDITMODE_WYSIWYG=window.parent.FCK_EDITMODE_WYSIWYG=0;var FCK_EDITMODE_SOURCE=window.parent.FCK_EDITMODE_SOURCE=1;var FCK_IMAGES_PATH='images/';var FCK_SPACER_PATH='images/spacer.gif';var CTRL=1000;var SHIFT=2000;var ALT=4000;var FCK_STYLE_BLOCK=0;var FCK_STYLE_INLINE=1;var FCK_STYLE_OBJECT=2;
+String.prototype.Contains=function(A){return (this.indexOf(A)>-1);};String.prototype.Equals=function(){var A=arguments;if (A.length==1&&A[0].pop) A=A[0];for (var i=0;i<A.length;i++){if (this==A[i]) return true;};return false;};String.prototype.IEquals=function(){var A=this.toUpperCase();var B=arguments;if (B.length==1&&B[0].pop) B=B[0];for (var i=0;i<B.length;i++){if (A==B[i].toUpperCase()) return true;};return false;};String.prototype.ReplaceAll=function(A,B){var C=this;for (var i=0;i<A.length;i++){C=C.replace(A[i],B[i]);};return C;};String.prototype.StartsWith=function(A){return (this.substr(0,A.length)==A);};String.prototype.EndsWith=function(A,B){var C=this.length;var D=A.length;if (D>C) return false;if (B){var E=new RegExp(A+'$','i');return E.test(this);}else return (D==0||this.substr(C-D,D)==A);};String.prototype.Remove=function(A,B){var s='';if (A>0) s=this.substring(0,A);if (A+B<this.length) s+=this.substring(A+B,this.length);return s;};String.prototype.Trim=function(){return this.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g,'');};String.prototype.LTrim=function(){return this.replace(/^[ \t\n\r]*/g,'');};String.prototype.RTrim=function(){return this.replace(/[ \t\n\r]*$/g,'');};String.prototype.ReplaceNewLineChars=function(A){return this.replace(/\n/g,A);};String.prototype.Replace=function(A,B,C){if (typeof B=='function'){return this.replace(A,function(){return B.apply(C||this,arguments);});}else return this.replace(A,B);};Array.prototype.IndexOf=function(A){for (var i=0;i<this.length;i++){if (this[i]==A) return i;};return-1;};
+var FCKIECleanup=function(A){if (A._FCKCleanupObj) this.Items=A._FCKCleanupObj.Items;else{this.Items=[];A._FCKCleanupObj=this;FCKTools.AddEventListenerEx(A,'unload',FCKIECleanup_Cleanup);}};FCKIECleanup.prototype.AddItem=function(A,B){this.Items.push([A,B]);};function FCKIECleanup_Cleanup(){if (!this._FCKCleanupObj||(FCKConfig.MsWebBrowserControlCompat&&!window.FCKUnloadFlag)) return;var A=this._FCKCleanupObj.Items;while (A.length>0){var B=A.pop();if (B) B[1].call(B[0]);};this._FCKCleanupObj=null;if (CollectGarbage) CollectGarbage();};
+var s=navigator.userAgent.toLowerCase();var FCKBrowserInfo={IsIE:/*@cc_on!@*/false,IsIE7:/*@cc_on!@*/false&&(parseInt(s.match(/msie (\d+)/)[1],10)>=7),IsIE6:/*@cc_on!@*/false&&(parseInt(s.match(/msie (\d+)/)[1],10)>=6),IsSafari:s.Contains(' applewebkit/'),IsOpera:!!window.opera,IsAIR:s.Contains(' adobeair/'),IsMac:s.Contains('macintosh')};(function(A){A.IsGecko=(navigator.product=='Gecko')&&!A.IsSafari&&!A.IsOpera;A.IsGeckoLike=(A.IsGecko||A.IsSafari||A.IsOpera);if (A.IsGecko){var B=s.match(/rv:(\d+\.\d+)/);var C=B&&parseFloat(B[1]);if (C){A.IsGecko10=(C<1.8);A.IsGecko19=(C>1.8);}};if (A.IsSafari) A.IsSafari3=(parseFloat(s.match(/ applewebkit\/(\d+)/)[1])<526);})(FCKBrowserInfo);
+var FCKURLParams={};(function(){var A=document.location.search.substr(1).split('&');for (var i=0;i<A.length;i++){var B=A[i].split('=');var C=decodeURIComponent(B[0]);var D=decodeURIComponent(B[1]);FCKURLParams[C]=D;}})();
+var FCKEvents=function(A){this.Owner=A;this._RegisteredEvents={};};FCKEvents.prototype.AttachEvent=function(A,B){var C;if (!(C=this._RegisteredEvents[A])) this._RegisteredEvents[A]=[B];else{if (C.IndexOf(B)==-1) C.push(B);}};FCKEvents.prototype.FireEvent=function(A,B){var C=true;var D=this._RegisteredEvents[A];if (D){for (var i=0;i<D.length;i++){try{C=(D[i](this.Owner,B)&&C);}catch(e){if (e.number!=-2146823277) throw e;}}};return C;};
+var FCKDataProcessor=function(){};FCKDataProcessor.prototype={ConvertToHtml:function(A){if (FCKConfig.FullPage){FCK.DocTypeDeclaration=A.match(FCKRegexLib.DocTypeTag);if (!FCKRegexLib.HasBodyTag.test(A)) A='<body>'+A+'</body>';if (!FCKRegexLib.HtmlOpener.test(A)) A='<html dir="'+FCKConfig.ContentLangDirection+'">'+A+'</html>';if (!FCKRegexLib.HeadOpener.test(A)) A=A.replace(FCKRegexLib.HtmlOpener,'$&<head><title></title></head>');return A;}else{var B=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"';if (FCKBrowserInfo.IsIE&&FCKConfig.DocType.length>0&&!FCKRegexLib.Html4DocType.test(FCKConfig.DocType)) B+=' style="overflow-y: scroll"';B+='><head><title></title></head><body'+FCKConfig.GetBodyAttributes()+'>'+A+'</body></html>';return B;}},ConvertToDataFormat:function(A,B,C,D){var E=FCKXHtml.GetXHTML(A,!B,D);if (C&&FCKRegexLib.EmptyOutParagraph.test(E)) return '';return E;},FixHtml:function(A){return A;}};
+var FCK={Name:FCKURLParams['InstanceName'],Status:0,EditMode:0,Toolbar:null,HasFocus:false,DataProcessor:new FCKDataProcessor(),GetInstanceObject:(function(){var w=window;return function(name){return w[name];}})(),AttachToOnSelectionChange:function(A){this.Events.AttachEvent('OnSelectionChange',A);},GetLinkedFieldValue:function(){return this.LinkedField.value;},GetParentForm:function(){return this.LinkedField.form;},StartupValue:'',IsDirty:function(){if (this.EditMode==1) return (this.StartupValue!=this.EditingArea.Textarea.value);else{if (!this.EditorDocument) return false;return (this.StartupValue!=this.EditorDocument.body.innerHTML);}},ResetIsDirty:function(){if (this.EditMode==1) this.StartupValue=this.EditingArea.Textarea.value;else if (this.EditorDocument.body) this.StartupValue=this.EditorDocument.body.innerHTML;},StartEditor:function(){this.TempBaseTag=FCKConfig.BaseHref.length>0?'<base href="'+FCKConfig.BaseHref+'" _fcktemp="true"></base>':'';var A=FCK.KeystrokeHandler=new FCKKeystrokeHandler();A.OnKeystroke=_FCK_KeystrokeHandler_OnKeystroke;A.SetKeystrokes(FCKConfig.Keystrokes);if (FCKBrowserInfo.IsIE7){if ((CTRL+86) in A.Keystrokes) A.SetKeystrokes([CTRL+86,true]);if ((SHIFT+45) in A.Keystrokes) A.SetKeystrokes([SHIFT+45,true]);};A.SetKeystrokes([CTRL+8,true]);this.EditingArea=new FCKEditingArea(document.getElementById('xEditingArea'));this.EditingArea.FFSpellChecker=FCKConfig.FirefoxSpellChecker;this.SetData(this.GetLinkedFieldValue(),true);FCKTools.AddEventListener(document,"keydown",this._TabKeyHandler);this.AttachToOnSelectionChange(_FCK_PaddingNodeListener);if (FCKBrowserInfo.IsGecko) this.AttachToOnSelectionChange(this._ExecCheckEmptyBlock);},Focus:function(){FCK.EditingArea.Focus();},SetStatus:function(A){this.Status=A;if (A==1){FCKFocusManager.AddWindow(window,true);if (FCKBrowserInfo.IsIE) FCKFocusManager.AddWindow(window.frameElement,true);if (FCKConfig.StartupFocus) FCK.Focus();};this.Events.FireEvent('OnStatusChange',A);},FixBody:function(){var A=FCKConfig.EnterMode;if (A!='p'&&A!='div') return;var B=this.EditorDocument;if (!B) return;var C=B.body;if (!C) return;FCKDomTools.TrimNode(C);var D=C.firstChild;var E;while (D){var F=false;switch (D.nodeType){case 1:var G=D.nodeName.toLowerCase();if (!FCKListsLib.BlockElements[G]&&G!='li'&&!D.getAttribute('_fckfakelement')&&D.getAttribute('_moz_dirty')==null) F=true;break;case 3:if (E||D.nodeValue.Trim().length>0) F=true;break;case 8:if (E) F=true;break;};if (F){var H=D.parentNode;if (!E) E=H.insertBefore(B.createElement(A),D);E.appendChild(H.removeChild(D));D=E.nextSibling;}else{if (E){FCKDomTools.TrimNode(E);E=null;};D=D.nextSibling;}};if (E) FCKDomTools.TrimNode(E);},GetData:function(A){FCK.Events.FireEvent("OnBeforeGetData");if (FCK.EditMode==1) return FCK.EditingArea.Textarea.value;this.FixBody();var B=FCK.EditorDocument;if (!B) return null;var C=FCKConfig.FullPage;var D=FCK.DataProcessor.ConvertToDataFormat(C?B.documentElement:B.body,!C,FCKConfig.IgnoreEmptyParagraphValue,A);D=FCK.ProtectEventsRestore(D);if (FCKBrowserInfo.IsIE) D=D.replace(FCKRegexLib.ToReplace,'$1');if (C){if (FCK.DocTypeDeclaration&&FCK.DocTypeDeclaration.length>0) D=FCK.DocTypeDeclaration+'\n'+D;if (FCK.XmlDeclaration&&FCK.XmlDeclaration.length>0) D=FCK.XmlDeclaration+'\n'+D;};D=FCKConfig.ProtectedSource.Revert(D);setTimeout(function() { FCK.Events.FireEvent("OnAfterGetData");},0);return D;},UpdateLinkedField:function(){var A=FCK.GetXHTML(FCKConfig.FormatOutput);if (FCKConfig.HtmlEncodeOutput) A=FCKTools.HTMLEncode(A);FCK.LinkedField.value=A;FCK.Events.FireEvent('OnAfterLinkedFieldUpdate');},RegisteredDoubleClickHandlers:{},OnDoubleClick:function(A){var B=FCK.RegisteredDoubleClickHandlers[A.tagName.toUpperCase()];if (B){for (var i=0;i<B.length;i++) B[i](A);};B=FCK.RegisteredDoubleClickHandlers['*'];if (B){for (var i=0;i<B.length;i++) B[i](A);}},RegisterDoubleClickHandler:function(A,B){var C=B||'*';C=C.toUpperCase();var D;if (!(D=FCK.RegisteredDoubleClickHandlers[C])) FCK.RegisteredDoubleClickHandlers[C]=[A];else{if (D.IndexOf(A)==-1) D.push(A);}},OnAfterSetHTML:function(){FCKDocumentProcessor.Process(FCK.EditorDocument);FCKUndo.SaveUndoStep();FCK.Events.FireEvent('OnSelectionChange');FCK.Events.FireEvent('OnAfterSetHTML');},ProtectUrls:function(A){A=A.replace(FCKRegexLib.ProtectUrlsA,'$& _fcksavedurl=$1');A=A.replace(FCKRegexLib.ProtectUrlsImg,'$& _fcksavedurl=$1');A=A.replace(FCKRegexLib.ProtectUrlsArea,'$& _fcksavedurl=$1');return A;},ProtectEvents:function(A){return A.replace(FCKRegexLib.TagsWithEvent,_FCK_ProtectEvents_ReplaceTags);},ProtectEventsRestore:function(A){return A.replace(FCKRegexLib.ProtectedEvents,_FCK_ProtectEvents_RestoreEvents);},ProtectTags:function(A){var B=FCKConfig.ProtectedTags;if (FCKBrowserInfo.IsIE) B+=B.length>0?'|ABBR|XML|EMBED|OBJECT':'ABBR|XML|EMBED|OBJECT';var C;if (B.length>0){C=new RegExp('<('+B+')(?!\w|:)','gi');A=A.replace(C,'<FCK:$1');C=new RegExp('<\/('+B+')>','gi');A=A.replace(C,'<\/FCK:$1>');};B='META';if (FCKBrowserInfo.IsIE) B+='|HR';C=new RegExp('<(('+B+')(?=\\s|>|/)[\\s\\S]*?)/?>','gi');A=A.replace(C,'<FCK:$1 />');return A;},SetData:function(A,B){this.EditingArea.Mode=FCK.EditMode;if (FCKBrowserInfo.IsIE&&FCK.EditorDocument){FCK.EditorDocument.detachEvent("onselectionchange",Doc_OnSelectionChange);};FCKTempBin.Reset();FCK.Selection.Release();if (FCK.EditMode==0){this._ForceResetIsDirty=(B===true);A=FCKConfig.ProtectedSource.Protect(A);A=FCK.DataProcessor.ConvertToHtml(A);A=A.replace(FCKRegexLib.InvalidSelfCloseTags,'$1></$2>');A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);if (FCK.TempBaseTag.length>0&&!FCKRegexLib.HasBaseTag.test(A)) A=A.replace(FCKRegexLib.HeadOpener,'$&'+FCK.TempBaseTag);var C='';if (!FCKConfig.FullPage) C+=_FCK_GetEditorAreaStyleTags();if (FCKBrowserInfo.IsIE) C+=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C+=FCKTools.GetStyleHtml(FCK_ShowTableBordersCSS,true);C+=FCKTools.GetStyleHtml(FCK_InternalCSS,true);A=A.replace(FCKRegexLib.HeadCloser,C+'$&');this.EditingArea.OnLoad=_FCK_EditingArea_OnLoad;this.EditingArea.Start(A);}else{FCK.EditorWindow=null;FCK.EditorDocument=null;FCKDomTools.PaddingNode=null;this.EditingArea.OnLoad=null;this.EditingArea.Start(A);this.EditingArea.Textarea._FCKShowContextMenu=true;FCK.EnterKeyHandler=null;if (B) this.ResetIsDirty();FCK.KeystrokeHandler.AttachToElement(this.EditingArea.Textarea);this.EditingArea.Textarea.focus();FCK.Events.FireEvent('OnAfterSetHTML');};if (window.onresize) window.onresize();},RedirectNamedCommands:{},ExecuteNamedCommand:function(A,B,C,D){if (!D) FCKUndo.SaveUndoStep();if (!C&&FCK.RedirectNamedCommands[A]!=null) FCK.ExecuteRedirectedNamedCommand(A,B);else{FCK.Focus();FCK.EditorDocument.execCommand(A,false,B);FCK.Events.FireEvent('OnSelectionChange');};if (!D) FCKUndo.SaveUndoStep();},GetNamedCommandState:function(A){try{if (FCKBrowserInfo.IsSafari&&FCK.EditorWindow&&A.IEquals('Paste')) return 0;if (!FCK.EditorDocument.queryCommandEnabled(A)) return -1;else{return FCK.EditorDocument.queryCommandState(A)?1:0;}}catch (e){return 0;}},GetNamedCommandValue:function(A){var B='';var C=FCK.GetNamedCommandState(A);if (C==-1) return null;try{B=this.EditorDocument.queryCommandValue(A);}catch(e) {};return B?B:'';},Paste:function(A){if (FCK.Status!=2||!FCK.Events.FireEvent('OnPaste')) return false;return A||FCK._ExecPaste();},PasteFromWord:function(){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteFromWord,'dialog/fck_paste.html',400,330,'Word');},Preview:function(){var A;if (FCKConfig.FullPage){if (FCK.TempBaseTag.length>0) A=FCK.TempBaseTag+FCK.GetXHTML();else A=FCK.GetXHTML();}else{A=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"><head>'+FCK.TempBaseTag+'<title>'+FCKLang.Preview+'</title>'+_FCK_GetEditorAreaStyleTags()+'</head><body'+FCKConfig.GetBodyAttributes()+'>'+FCK.GetXHTML()+'</body></html>';};var B=FCKConfig.ScreenWidth*0.8;var C=FCKConfig.ScreenHeight*0.7;var D=(FCKConfig.ScreenWidth-B)/2;var E='';if (FCK_IS_CUSTOM_DOMAIN&&FCKBrowserInfo.IsIE){window._FCKHtmlToLoad=A;E='javascript:void( (function(){document.open() ;document.domain="'+document.domain+'" ;document.write( window.opener._FCKHtmlToLoad );document.close() ;window.opener._FCKHtmlToLoad = null ;})() )';};var F=window.open(E,null,'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width='+B+',height='+C+',left='+D);if (!FCK_IS_CUSTOM_DOMAIN||!FCKBrowserInfo.IsIE){F.document.write(A);F.document.close();}},SwitchEditMode:function(A){var B=(FCK.EditMode==0);var C=FCK.IsDirty();var D;if (B){FCKCommands.GetCommand('ShowBlocks').SaveState();if (!A&&FCKBrowserInfo.IsIE) FCKUndo.SaveUndoStep();D=FCK.GetXHTML(FCKConfig.FormatSource);if (FCKBrowserInfo.IsIE) FCKTempBin.ToHtml();if (D==null) return false;}else D=this.EditingArea.Textarea.value;FCK.EditMode=B?1:0;FCK.SetData(D,!C);FCK.Focus();FCKTools.RunFunction(FCK.ToolbarSet.RefreshModeState,FCK.ToolbarSet);return true;},InsertElement:function(A){if (typeof A=='string') A=this.EditorDocument.createElement(A);var B=A.nodeName.toLowerCase();FCKSelection.Restore();var C=new FCKDomRange(this.EditorWindow);C.MoveToSelection();C.DeleteContents();if (FCKListsLib.BlockElements[B]!=null){if (C.StartBlock){if (C.CheckStartOfBlock()) C.MoveToPosition(C.StartBlock,3);else if (C.CheckEndOfBlock()) C.MoveToPosition(C.StartBlock,4);else C.SplitBlock();};C.InsertNode(A);var D=FCKDomTools.GetNextSourceElement(A,false,null,['hr','br','param','img','area','input'],true);if (!D&&FCKConfig.EnterMode!='br'){D=this.EditorDocument.body.appendChild(this.EditorDocument.createElement(FCKConfig.EnterMode));if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(D);};if (FCKListsLib.EmptyElements[B]==null) C.MoveToElementEditStart(A);else if (D) C.MoveToElementEditStart(D);else C.MoveToPosition(A,4);if (FCKBrowserInfo.IsGeckoLike){if (D) FCKDomTools.ScrollIntoView(D,false);FCKDomTools.ScrollIntoView(A,false);}}else{C.InsertNode(A);C.SetStart(A,4);C.SetEnd(A,4);};C.Select();C.Release();this.Focus();return A;},_InsertBlockElement:function(A){},_IsFunctionKey:function(A){if (A>=16&&A<=20) return true;if (A==27||(A>=33&&A<=40)) return true;if (A==45) return true;return false;},_KeyDownListener:function(A){if (!A) A=FCK.EditorWindow.event;if (FCK.EditorWindow){if (!FCK._IsFunctionKey(A.keyCode)&&!(A.ctrlKey||A.metaKey)&&!(A.keyCode==46)) FCK._KeyDownUndo();};return true;},_KeyDownUndo:function(){if (!FCKUndo.Typing){FCKUndo.SaveUndoStep();FCKUndo.Typing=true;FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.TypesCount++;FCKUndo.Changed=1;if (FCKUndo.TypesCount>FCKUndo.MaxTypes){FCKUndo.TypesCount=0;FCKUndo.SaveUndoStep();}},_TabKeyHandler:function(A){if (!A) A=window.event;var B=A.keyCode;if (B==9&&FCK.EditMode!=0){if (FCKBrowserInfo.IsIE){var C=document.selection.createRange();if (C.parentElement()!=FCK.EditingArea.Textarea) return true;C.text='\t';C.select();}else{var a=[];var D=FCK.EditingArea.Textarea;var E=D.selectionStart;var F=D.selectionEnd;a.push(D.value.substr(0,E));a.push('\t');a.push(D.value.substr(F));D.value=a.join('');D.setSelectionRange(E+1,E+1);};if (A.preventDefault) return A.preventDefault();return A.returnValue=false;};return true;}};FCK.Events=new FCKEvents(FCK);FCK.GetHTML=FCK.GetXHTML=FCK.GetData;FCK.SetHTML=FCK.SetData;FCK.InsertElementAndGetIt=FCK.CreateElement=FCK.InsertElement;function _FCK_ProtectEvents_ReplaceTags(A){return A.replace(FCKRegexLib.EventAttributes,_FCK_ProtectEvents_ReplaceEvents);};function _FCK_ProtectEvents_ReplaceEvents(A,B){return ' '+B+'_fckprotectedatt="'+encodeURIComponent(A)+'"';};function _FCK_ProtectEvents_RestoreEvents(A,B){return decodeURIComponent(B);};function _FCK_MouseEventsListener(A){if (!A) A=window.event;if (A.type=='mousedown') FCK.MouseDownFlag=true;else if (A.type=='mouseup') FCK.MouseDownFlag=false;else if (A.type=='mousemove') FCK.Events.FireEvent('OnMouseMove',A);};function _FCK_PaddingNodeListener(){if (FCKConfig.EnterMode.IEquals('br')) return;FCKDomTools.EnforcePaddingNode(FCK.EditorDocument,FCKConfig.EnterMode);if (!FCKBrowserInfo.IsIE&&FCKDomTools.PaddingNode){var A=FCKSelection.GetSelection();if (A&&A.rangeCount==1){var B=A.getRangeAt(0);if (B.collapsed&&B.startContainer==FCK.EditorDocument.body&&B.startOffset==0){B.selectNodeContents(FCKDomTools.PaddingNode);B.collapse(true);A.removeAllRanges();A.addRange(B);}}}else if (FCKDomTools.PaddingNode){var C=FCKSelection.GetParentElement();var D=FCKDomTools.PaddingNode;if (C&&C.nodeName.IEquals('body')){if (FCK.EditorDocument.body.childNodes.length==1&&FCK.EditorDocument.body.firstChild==D){if (FCKSelection._GetSelectionDocument(FCK.EditorDocument.selection)!=FCK.EditorDocument) return;var B=FCK.EditorDocument.body.createTextRange();var F=false;if (!D.childNodes.firstChild){D.appendChild(FCKTools.GetElementDocument(D).createTextNode('\ufeff'));F=true;};B.moveToElementText(D);B.select();if (F) B.pasteHTML('');}}}};function _FCK_EditingArea_OnLoad(){FCK.EditorWindow=FCK.EditingArea.Window;FCK.EditorDocument=FCK.EditingArea.Document;if (FCKBrowserInfo.IsIE) FCKTempBin.ToElements();FCK.InitializeBehaviors();FCK.MouseDownFlag=false;FCKTools.AddEventListener(FCK.EditorDocument,'mousemove',_FCK_MouseEventsListener);FCKTools.AddEventListener(FCK.EditorDocument,'mousedown',_FCK_MouseEventsListener);FCKTools.AddEventListener(FCK.EditorDocument,'mouseup',_FCK_MouseEventsListener);if (FCKBrowserInfo.IsSafari){FCKTools.AddEventListener(FCK.EditorDocument,'paste',function(evt){var A=new FCKDomRange(FCK.EditorWindow);var B=FCK.EditorDocument.createTextNode('\ufeff');var C=FCK.EditorDocument.createElement('a');C.id='fck_paste_padding';C.innerHTML='&#65279;';A.MoveToSelection();A.DeleteContents();A.InsertNode(B);A.Collapse();A.InsertNode(C);A.MoveToPosition(C,3);A.Select();setTimeout(function(){B.parentNode.removeChild(B);C=FCK.EditorDocument.getElementById('fck_paste_padding');C.parentNode.removeChild(C);},0);});};if (FCKBrowserInfo.IsSafari){var D=function(evt){if (!(evt.ctrlKey||evt.metaKey)) return;if (FCK.EditMode!=0) return;switch (evt.keyCode){case 89:FCKUndo.Redo();break;case 90:FCKUndo.Undo();break;}};FCKTools.AddEventListener(FCK.EditorDocument,'keyup',D);};FCK.EnterKeyHandler=new FCKEnterKey(FCK.EditorWindow,FCKConfig.EnterMode,FCKConfig.ShiftEnterMode,FCKConfig.TabSpaces);FCK.KeystrokeHandler.AttachToElement(FCK.EditorDocument);if (FCK._ForceResetIsDirty) FCK.ResetIsDirty();if (FCKBrowserInfo.IsIE&&FCK.HasFocus) FCK.EditorDocument.body.setActive();FCK.OnAfterSetHTML();FCKCommands.GetCommand('ShowBlocks').RestoreState();if (FCK.Status!=0) return;FCK.SetStatus(1);};function _FCK_GetEditorAreaStyleTags(){return FCKTools.GetStyleHtml(FCKConfig.EditorAreaCSS)+FCKTools.GetStyleHtml(FCKConfig.EditorAreaStyles);};function _FCK_KeystrokeHandler_OnKeystroke(A,B){if (FCK.Status!=2) return false;if (FCK.EditMode==0){switch (B){case 'Paste':return!FCK.Paste();case 'Cut':FCKUndo.SaveUndoStep();return false;}}else{if (B.Equals('Paste','Undo','Redo','SelectAll','Cut')) return false;};var C=FCK.Commands.GetCommand(B);if (C.GetState()==-1) return false;return (C.Execute.apply(C,FCKTools.ArgumentsToArray(arguments,2))!==false);};(function(){var A=window.parent.document;var B=A.getElementById(FCK.Name);var i=0;while (B||i==0){if (B&&B.tagName.toLowerCase().Equals('input','textarea')){FCK.LinkedField=B;break;};B=A.getElementsByName(FCK.Name)[i++];}})();var FCKTempBin={Elements:[],AddElement:function(A){var B=this.Elements.length;this.Elements[B]=A;return B;},RemoveElement:function(A){var e=this.Elements[A];this.Elements[A]=null;return e;},Reset:function(){var i=0;while (i<this.Elements.length) this.Elements[i++]=null;this.Elements.length=0;},ToHtml:function(){for (var i=0;i<this.Elements.length;i++){this.Elements[i]='<div>&nbsp;'+this.Elements[i].outerHTML+'</div>';this.Elements[i].isHtml=true;}},ToElements:function(){var A=FCK.EditorDocument.createElement('div');for (var i=0;i<this.Elements.length;i++){if (this.Elements[i].isHtml){A.innerHTML=this.Elements[i];this.Elements[i]=A.firstChild.removeChild(A.firstChild.lastChild);}}}};var FCKFocusManager=FCK.FocusManager={IsLocked:false,AddWindow:function(A,B){var C;if (FCKBrowserInfo.IsIE) C=A.nodeType==1?A:A.frameElement?A.frameElement:A.document;else if (FCKBrowserInfo.IsSafari) C=A;else C=A.document;FCKTools.AddEventListener(C,'blur',FCKFocusManager_Win_OnBlur);FCKTools.AddEventListener(C,'focus',B?FCKFocusManager_Win_OnFocus_Area:FCKFocusManager_Win_OnFocus);},RemoveWindow:function(A){if (FCKBrowserInfo.IsIE) oTarget=A.nodeType==1?A:A.frameElement?A.frameElement:A.document;else oTarget=A.document;FCKTools.RemoveEventListener(oTarget,'blur',FCKFocusManager_Win_OnBlur);FCKTools.RemoveEventListener(oTarget,'focus',FCKFocusManager_Win_OnFocus_Area);FCKTools.RemoveEventListener(oTarget,'focus',FCKFocusManager_Win_OnFocus);},Lock:function(){this.IsLocked=true;},Unlock:function(){if (this._HasPendingBlur) FCKFocusManager._Timer=window.setTimeout(FCKFocusManager_FireOnBlur,100);this.IsLocked=false;},_ResetTimer:function(){this._HasPendingBlur=false;if (this._Timer){window.clearTimeout(this._Timer);delete this._Timer;}}};function FCKFocusManager_Win_OnBlur(){if (typeof(FCK)!='undefined'&&FCK.HasFocus){FCKFocusManager._ResetTimer();FCKFocusManager._Timer=window.setTimeout(FCKFocusManager_FireOnBlur,100);}};function FCKFocusManager_FireOnBlur(){if (FCKFocusManager.IsLocked) FCKFocusManager._HasPendingBlur=true;else{FCK.HasFocus=false;FCK.Events.FireEvent("OnBlur");}};function FCKFocusManager_Win_OnFocus_Area(){if (FCKFocusManager._IsFocusing) return;FCKFocusManager._IsFocusing=true;FCK.Focus();FCKFocusManager_Win_OnFocus();FCKTools.RunFunction(function(){delete FCKFocusManager._IsFocusing;});};function FCKFocusManager_Win_OnFocus(){FCKFocusManager._ResetTimer();if (!FCK.HasFocus&&!FCKFocusManager.IsLocked){FCK.HasFocus=true;FCK.Events.FireEvent("OnFocus");}};(function(){var A=window.frameElement;var B=A.width;var C=A.height;if (/^\d+$/.test(B)) B+='px';if (/^\d+$/.test(C)) C+='px';var D=A.style;D.border=D.padding=D.margin=0;D.backgroundColor='transparent';D.backgroundImage='none';D.width=B;D.height=C;})();
+FCK.Description="FCKeditor for Internet Explorer 5.5+";FCK._GetBehaviorsStyle=function(){if (!FCK._BehaviorsStyle){var A=FCKConfig.BasePath;var B='';var C;C='<style type="text/css" _fcktemp="true">';if (FCKConfig.ShowBorders) B='url('+A+'css/behaviors/showtableborders.htc)';C+='INPUT,TEXTAREA,SELECT,.FCK__Anchor,.FCK__PageBreak,.FCK__InputHidden';if (FCKConfig.DisableObjectResizing){C+=',IMG';B+=' url('+A+'css/behaviors/disablehandles.htc)';};C+=' { behavior: url('+A+'css/behaviors/disablehandles.htc) ; }';if (B.length>0) C+='TABLE { behavior: '+B+' ; }';C+='</style>';FCK._BehaviorsStyle=C;};return FCK._BehaviorsStyle;};function Doc_OnMouseUp(){if (FCK.EditorWindow.event.srcElement.tagName=='HTML'){FCK.Focus();FCK.EditorWindow.event.cancelBubble=true;FCK.EditorWindow.event.returnValue=false;}};function Doc_OnPaste(){var A=FCK.EditorDocument.body;A.detachEvent('onpaste',Doc_OnPaste);var B=FCK.Paste(!FCKConfig.ForcePasteAsPlainText&&!FCKConfig.AutoDetectPasteFromWord);A.attachEvent('onpaste',Doc_OnPaste);return B;};function Doc_OnDblClick(){FCK.OnDoubleClick(FCK.EditorWindow.event.srcElement);FCK.EditorWindow.event.cancelBubble=true;};function Doc_OnSelectionChange(){if (!FCK.IsSelectionChangeLocked&&FCK.EditorDocument) FCK.Events.FireEvent("OnSelectionChange");};function Doc_OnDrop(){if (FCK.MouseDownFlag){FCK.MouseDownFlag=false;return;};if (FCKConfig.ForcePasteAsPlainText){var A=FCK.EditorWindow.event;if (FCK._CheckIsPastingEnabled()||FCKConfig.ShowDropDialog) FCK.PasteAsPlainText(A.dataTransfer.getData('Text'));A.returnValue=false;A.cancelBubble=true;}};FCK.InitializeBehaviors=function(A){this.EditorDocument.attachEvent('onmouseup',Doc_OnMouseUp);this.EditorDocument.body.attachEvent('onpaste',Doc_OnPaste);this.EditorDocument.body.attachEvent('ondrop',Doc_OnDrop);FCK.ContextMenu._InnerContextMenu.AttachToElement(FCK.EditorDocument.body);this.EditorDocument.attachEvent("onkeydown",FCK._KeyDownListener);this.EditorDocument.attachEvent("ondblclick",Doc_OnDblClick);this.EditorDocument.attachEvent("onbeforedeactivate",function(){ FCKSelection.Save();});this.EditorDocument.attachEvent("onselectionchange",Doc_OnSelectionChange);FCKTools.AddEventListener(FCK.EditorDocument,'mousedown',Doc_OnMouseDown);};FCK.InsertHtml=function(A){A=FCKConfig.ProtectedSource.Protect(A);A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);FCKSelection.Restore();FCK.EditorWindow.focus();FCKUndo.SaveUndoStep();var B=FCKSelection.GetSelection();if (B.type.toLowerCase()=='control') B.clear();A='<span id="__fakeFCKRemove__" style="display:none;">fakeFCKRemove</span>'+A;B.createRange().pasteHTML(A);var C=FCK.EditorDocument.getElementById('__fakeFCKRemove__');if (C.parentNode.childNodes.length==1) C=C.parentNode;C.removeNode(true);FCKDocumentProcessor.Process(FCK.EditorDocument);this.Events.FireEvent("OnSelectionChange");};FCK.SetInnerHtml=function(A){var B=FCK.EditorDocument;B.body.innerHTML='<div id="__fakeFCKRemove__">&nbsp;</div>'+A;B.getElementById('__fakeFCKRemove__').removeNode(true);};function FCK_PreloadImages(){var A=new FCKImagePreloader();A.AddImages(FCKConfig.PreloadImages);A.AddImages(FCKConfig.SkinPath+'fck_strip.gif');A.OnComplete=LoadToolbarSetup;A.Start();};function Document_OnContextMenu(){return (event.srcElement._FCKShowContextMenu==true);};document.oncontextmenu=Document_OnContextMenu;function FCK_Cleanup(){this.LinkedField=null;this.EditorWindow=null;this.EditorDocument=null;};FCK._ExecPaste=function(){if (FCK._PasteIsRunning) return true;if (FCKConfig.ForcePasteAsPlainText){FCK.PasteAsPlainText();return false;};var A=FCK._CheckIsPastingEnabled(true);if (A===false) FCKTools.RunFunction(FCKDialog.OpenDialog,FCKDialog,['FCKDialog_Paste',FCKLang.Paste,'dialog/fck_paste.html',400,330,'Security']);else{if (FCKConfig.AutoDetectPasteFromWord&&A.length>0){var B=/<\w[^>]*(( class="?MsoNormal"?)|(="mso-))/gi;if (B.test(A)){if (confirm(FCKLang.PasteWordConfirm)){FCK.PasteFromWord();return false;}}};FCK._PasteIsRunning=true;FCK.ExecuteNamedCommand('Paste');delete FCK._PasteIsRunning;};return false;};FCK.PasteAsPlainText=function(A){if (!FCK._CheckIsPastingEnabled()){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteAsText,'dialog/fck_paste.html',400,330,'PlainText');return;};var B=null;if (!A) B=clipboardData.getData("Text");else B=A;if (B&&B.length>0){B=FCKTools.HTMLEncode(B);B=FCKTools.ProcessLineBreaks(window,FCKConfig,B);var C=B.search('</p>');var D=B.search('<p>');if ((C!=-1&&D!=-1&&C<D)||(C!=-1&&D==-1)){var E=B.substr(0,C);B=B.substr(C+4);this.InsertHtml(E);};FCKUndo.SaveLocked=true;this.InsertHtml(B);FCKUndo.SaveLocked=false;}};FCK._CheckIsPastingEnabled=function(A){FCK._PasteIsEnabled=false;document.body.attachEvent('onpaste',FCK_CheckPasting_Listener);var B=FCK.GetClipboardHTML();document.body.detachEvent('onpaste',FCK_CheckPasting_Listener);if (FCK._PasteIsEnabled){if (!A) B=true;}else B=false;delete FCK._PasteIsEnabled;return B;};function FCK_CheckPasting_Listener(){FCK._PasteIsEnabled=true;};FCK.GetClipboardHTML=function(){var A=document.getElementById('___FCKHiddenDiv');if (!A){A=document.createElement('DIV');A.id='___FCKHiddenDiv';var B=A.style;B.position='absolute';B.visibility=B.overflow='hidden';B.width=B.height=1;document.body.appendChild(A);};A.innerHTML='';var C=document.body.createTextRange();C.moveToElementText(A);C.execCommand('Paste');var D=A.innerHTML;A.innerHTML='';return D;};FCK.CreateLink=function(A,B){var C=[];var D=FCKSelection.GetType()=='Control';var E=D&&FCKSelection.GetSelectedElement();if (!(D&&!FCKTools.GetElementAscensor(E,'a'))) FCK.ExecuteNamedCommand('Unlink',null,false,!!B);if (A.length>0){if (D){var F=this.EditorDocument.createElement('A');F.href=A;var G=E;G.parentNode.insertBefore(F,G);G.parentNode.removeChild(G);F.appendChild(G);return [F];};var H='javascript:void(0);/*'+(new Date().getTime())+'*/';FCK.ExecuteNamedCommand('CreateLink',H,false,!!B);var I=this.EditorDocument.links;for (i=0;i<I.length;i++){var F=I[i];if (F.getAttribute('href',2)==H){var K=F.innerHTML;F.href=A;F.innerHTML=K;var L=F.lastChild;if (L&&L.nodeName=='BR'){FCKDomTools.InsertAfterNode(F,F.removeChild(L));};C.push(F);}}};return C;};function _FCK_RemoveDisabledAtt(){this.removeAttribute('disabled');};function Doc_OnMouseDown(A){var e=A.srcElement;if (e.nodeName&&e.nodeName.IEquals('input')&&e.type.IEquals(['radio','checkbox'])&&!e.disabled){e.disabled=true;FCKTools.SetTimeout(_FCK_RemoveDisabledAtt,1,e);}};
+var FCKConfig=FCK.Config={};if (document.location.protocol=='file:'){FCKConfig.BasePath=decodeURIComponent(document.location.pathname.substr(1));FCKConfig.BasePath=FCKConfig.BasePath.replace(/\\/gi,'/');var sFullProtocol=document.location.href.match(/^(file\:\/{2,3})/)[1];if (FCKBrowserInfo.IsOpera) sFullProtocol+='localhost/';FCKConfig.BasePath=sFullProtocol+FCKConfig.BasePath.substring(0,FCKConfig.BasePath.lastIndexOf('/')+1);}else FCKConfig.BasePath=document.location.protocol+'//'+document.location.host+document.location.pathname.substring(0,document.location.pathname.lastIndexOf('/')+1);FCKConfig.FullBasePath=FCKConfig.BasePath;FCKConfig.EditorPath=FCKConfig.BasePath.replace(/editor\/$/,'');try{FCKConfig.ScreenWidth=screen.width;FCKConfig.ScreenHeight=screen.height;}catch (e){FCKConfig.ScreenWidth=800;FCKConfig.ScreenHeight=600;};FCKConfig.ProcessHiddenField=function(){this.PageConfig={};var A=window.parent.document.getElementById(FCK.Name+'___Config');if (!A) return;var B=A.value.split('&');for (var i=0;i<B.length;i++){if (B[i].length==0) continue;var C=B[i].split('=');var D=decodeURIComponent(C[0]);var E=decodeURIComponent(C[1]);if (D=='CustomConfigurationsPath') FCKConfig[D]=E;else if (E.toLowerCase()=="true") this.PageConfig[D]=true;else if (E.toLowerCase()=="false") this.PageConfig[D]=false;else if (E.length>0&&!isNaN(E)) this.PageConfig[D]=parseFloat(E);else this.PageConfig[D]=E;}};function FCKConfig_LoadPageConfig(){var A=FCKConfig.PageConfig;for (var B in A) FCKConfig[B]=A[B];};function FCKConfig_PreProcess(){var A=FCKConfig;if (A.AllowQueryStringDebug){try{if ((/fckdebug=true/i).test(window.top.location.search)) A.Debug=true;}catch (e) { }};if (!A.PluginsPath.EndsWith('/')) A.PluginsPath+='/';var B=A.ToolbarComboPreviewCSS;if (!B||B.length==0) A.ToolbarComboPreviewCSS=A.EditorAreaCSS;A.RemoveAttributesArray=(A.RemoveAttributes||'').split(',');if (!FCKConfig.SkinEditorCSS||FCKConfig.SkinEditorCSS.length==0) FCKConfig.SkinEditorCSS=FCKConfig.SkinPath+'fck_editor.css';if (!FCKConfig.SkinDialogCSS||FCKConfig.SkinDialogCSS.length==0) FCKConfig.SkinDialogCSS=FCKConfig.SkinPath+'fck_dialog.css';};FCKConfig.ToolbarSets={};FCKConfig.Plugins={};FCKConfig.Plugins.Items=[];FCKConfig.Plugins.Add=function(A,B,C){FCKConfig.Plugins.Items.push([A,B,C]);};FCKConfig.ProtectedSource={};FCKConfig.ProtectedSource._CodeTag=(new Date()).valueOf();FCKConfig.ProtectedSource.RegexEntries=[/<!--[\s\S]*?-->/g,/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi];FCKConfig.ProtectedSource.Add=function(A){this.RegexEntries.push(A);};FCKConfig.ProtectedSource.Protect=function(A){var B=this._CodeTag;function _Replace(protectedSource){var C=FCKTempBin.AddElement(protectedSource);return '<!--{'+B+C+'}-->';};for (var i=0;i<this.RegexEntries.length;i++){A=A.replace(this.RegexEntries[i],_Replace);};return A;};FCKConfig.ProtectedSource.Revert=function(A,B){function _Replace(m,opener,index){var C=B?FCKTempBin.RemoveElement(index):FCKTempBin.Elements[index];return FCKConfig.ProtectedSource.Revert(C,B);};var D=new RegExp("(<|&lt;)!--\\{"+this._CodeTag+"(\\d+)\\}--(>|&gt;)","g");return A.replace(D,_Replace);};FCKConfig.GetBodyAttributes=function(){var A='';if (this.BodyId&&this.BodyId.length>0) A+=' id="'+this.BodyId+'"';if (this.BodyClass&&this.BodyClass.length>0) A+=' class="'+this.BodyClass+'"';return A;};FCKConfig.ApplyBodyAttributes=function(A){if (this.BodyId&&this.BodyId.length>0) A.id=FCKConfig.BodyId;if (this.BodyClass&&this.BodyClass.length>0) A.className+=' '+FCKConfig.BodyClass;};
+var FCKDebug={Output:function(){},OutputObject:function(){}};
+var FCKDomTools={MoveChildren:function(A,B,C){if (A==B) return;var D;if (C){while ((D=A.lastChild)) B.insertBefore(A.removeChild(D),B.firstChild);}else{while ((D=A.firstChild)) B.appendChild(A.removeChild(D));}},MoveNode:function(A,B,C){if (C) B.insertBefore(FCKDomTools.RemoveNode(A),B.firstChild);else B.appendChild(FCKDomTools.RemoveNode(A));},TrimNode:function(A){this.LTrimNode(A);this.RTrimNode(A);},LTrimNode:function(A){var B;while ((B=A.firstChild)){if (B.nodeType==3){var C=B.nodeValue.LTrim();var D=B.nodeValue.length;if (C.length==0){A.removeChild(B);continue;}else if (C.length<D){B.splitText(D-C.length);A.removeChild(A.firstChild);}};break;}},RTrimNode:function(A){var B;while ((B=A.lastChild)){if (B.nodeType==3){var C=B.nodeValue.RTrim();var D=B.nodeValue.length;if (C.length==0){B.parentNode.removeChild(B);continue;}else if (C.length<D){B.splitText(C.length);A.lastChild.parentNode.removeChild(A.lastChild);}};break;};if (!FCKBrowserInfo.IsIE&&!FCKBrowserInfo.IsOpera){B=A.lastChild;if (B&&B.nodeType==1&&B.nodeName.toLowerCase()=='br'){B.parentNode.removeChild(B);}}},RemoveNode:function(A,B){if (B){var C;while ((C=A.firstChild)) A.parentNode.insertBefore(A.removeChild(C),A);};return A.parentNode.removeChild(A);},GetFirstChild:function(A,B){if (typeof (B)=='string') B=[B];var C=A.firstChild;while(C){if (C.nodeType==1&&C.tagName.Equals.apply(C.tagName,B)) return C;C=C.nextSibling;};return null;},GetLastChild:function(A,B){if (typeof (B)=='string') B=[B];var C=A.lastChild;while(C){if (C.nodeType==1&&(!B||C.tagName.Equals(B))) return C;C=C.previousSibling;};return null;},GetPreviousSourceElement:function(A,B,C,D){if (!A) return null;if (C&&A.nodeType==1&&A.nodeName.IEquals(C)) return null;if (A.previousSibling) A=A.previousSibling;else return this.GetPreviousSourceElement(A.parentNode,B,C,D);while (A){if (A.nodeType==1){if (C&&A.nodeName.IEquals(C)) break;if (!D||!A.nodeName.IEquals(D)) return A;}else if (B&&A.nodeType==3&&A.nodeValue.RTrim().length>0) break;if (A.lastChild) A=A.lastChild;else return this.GetPreviousSourceElement(A,B,C,D);};return null;},GetNextSourceElement:function(A,B,C,D,E){while((A=this.GetNextSourceNode(A,E))){if (A.nodeType==1){if (C&&A.nodeName.IEquals(C)) break;if (D&&A.nodeName.IEquals(D)) return this.GetNextSourceElement(A,B,C,D);return A;}else if (B&&A.nodeType==3&&A.nodeValue.RTrim().length>0) break;};return null;},GetNextSourceNode:function(A,B,C,D){if (!A) return null;var E;if (!B&&A.firstChild) E=A.firstChild;else{if (D&&A==D) return null;E=A.nextSibling;if (!E&&(!D||D!=A.parentNode)) return this.GetNextSourceNode(A.parentNode,true,C,D);};if (C&&E&&E.nodeType!=C) return this.GetNextSourceNode(E,false,C,D);return E;},GetPreviousSourceNode:function(A,B,C,D){if (!A) return null;var E;if (!B&&A.lastChild) E=A.lastChild;else{if (D&&A==D) return null;E=A.previousSibling;if (!E&&(!D||D!=A.parentNode)) return this.GetPreviousSourceNode(A.parentNode,true,C,D);};if (C&&E&&E.nodeType!=C) return this.GetPreviousSourceNode(E,false,C,D);return E;},InsertAfterNode:function(A,B){return A.parentNode.insertBefore(B,A.nextSibling);},GetParents:function(A){var B=[];while (A){B.unshift(A);A=A.parentNode;};return B;},GetCommonParents:function(A,B){var C=this.GetParents(A);var D=this.GetParents(B);var E=[];for (var i=0;i<C.length;i++){if (C[i]==D[i]) E.push(C[i]);};return E;},GetCommonParentNode:function(A,B,C){var D={};if (!C.pop) C=[C];while (C.length>0) D[C.pop().toLowerCase()]=1;var E=this.GetCommonParents(A,B);var F=null;while ((F=E.pop())){if (D[F.nodeName.toLowerCase()]) return F;};return null;},GetIndexOf:function(A){var B=A.parentNode?A.parentNode.firstChild:null;var C=-1;while (B){C++;if (B==A) return C;B=B.nextSibling;};return-1;},PaddingNode:null,EnforcePaddingNode:function(A,B){try{if (!A||!A.body) return;}catch (e){return;};this.CheckAndRemovePaddingNode(A,B,true);try{if (A.body.lastChild&&(A.body.lastChild.nodeType!=1||A.body.lastChild.tagName.toLowerCase()==B.toLowerCase())) return;}catch (e){return;};var C=A.createElement(B);if (FCKBrowserInfo.IsGecko&&FCKListsLib.NonEmptyBlockElements[B]) FCKTools.AppendBogusBr(C);this.PaddingNode=C;if (A.body.childNodes.length==1&&A.body.firstChild.nodeType==1&&A.body.firstChild.tagName.toLowerCase()=='br'&&(A.body.firstChild.getAttribute('_moz_dirty')!=null||A.body.firstChild.getAttribute('type')=='_moz')) A.body.replaceChild(C,A.body.firstChild);else A.body.appendChild(C);},CheckAndRemovePaddingNode:function(A,B,C){var D=this.PaddingNode;if (!D) return;try{if (D.parentNode!=A.body||D.tagName.toLowerCase()!=B||(D.childNodes.length>1)||(D.firstChild&&D.firstChild.nodeValue!='\xa0'&&String(D.firstChild.tagName).toLowerCase()!='br')){this.PaddingNode=null;return;}}catch (e){this.PaddingNode=null;return;};if (!C){if (D.parentNode.childNodes.length>1) D.parentNode.removeChild(D);this.PaddingNode=null;}},HasAttribute:function(A,B){if (A.hasAttribute) return A.hasAttribute(B);else{var C=A.attributes[B];return (C!=undefined&&C.specified);}},HasAttributes:function(A){var B=A.attributes;for (var i=0;i<B.length;i++){if (FCKBrowserInfo.IsIE){var C=B[i].nodeName;if (C.StartsWith('_fck')){continue;};if (C=='class'){if (A.className.length>0) return true;continue;}};if (B[i].specified) return true;};return false;},RemoveAttribute:function(A,B){if (FCKBrowserInfo.IsIE&&B.toLowerCase()=='class') B='className';return A.removeAttribute(B,0);},RemoveAttributes:function (A,B){for (var i=0;i<B.length;i++) this.RemoveAttribute(A,B[i]);},GetAttributeValue:function(A,B){var C=B;if (typeof B=='string') B=A.attributes[B];else C=B.nodeName;if (B&&B.specified){if (C=='style') return A.style.cssText;else if (C=='class'||C.indexOf('on')==0) return B.nodeValue;else{return A.getAttribute(C,2);}};return null;},Contains:function(A,B){if (A.contains&&B.nodeType==1) return A.contains(B);while ((B=B.parentNode)){if (B==A) return true;};return false;},BreakParent:function(A,B,C){var D=C||new FCKDomRange(FCKTools.GetElementWindow(A));D.SetStart(A,4);D.SetEnd(B,4);var E=D.ExtractContents();D.InsertNode(A.parentNode.removeChild(A));E.InsertAfterNode(A);D.Release(!!C);},GetNodeAddress:function(A,B){var C=[];while (A&&A!=FCKTools.GetElementDocument(A).documentElement){var D=A.parentNode;var E=-1;for(var i=0;i<D.childNodes.length;i++){var F=D.childNodes[i];if (B===true&&F.nodeType==3&&F.previousSibling&&F.previousSibling.nodeType==3) continue;E++;if (D.childNodes[i]==A) break;};C.unshift(E);A=A.parentNode;};return C;},GetNodeFromAddress:function(A,B,C){var D=A.documentElement;for (var i=0;i<B.length;i++){var E=B[i];if (!C){D=D.childNodes[E];continue;};var F=-1;for (var j=0;j<D.childNodes.length;j++){var G=D.childNodes[j];if (C===true&&G.nodeType==3&&G.previousSibling&&G.previousSibling.nodeType==3) continue;F++;if (F==E){D=G;break;}}};return D;},CloneElement:function(A){A=A.cloneNode(false);A.removeAttribute('id',false);return A;},ClearElementJSProperty:function(A,B){if (FCKBrowserInfo.IsIE) A.removeAttribute(B);else delete A[B];},SetElementMarker:function (A,B,C,D){var E=String(parseInt(Math.random()*0xffffffff,10));B._FCKMarkerId=E;B[C]=D;if (!A[E]) A[E]={ 'element':B,'markers':{} };A[E]['markers'][C]=D;},ClearElementMarkers:function(A,B,C){var D=B._FCKMarkerId;if (!D) return;this.ClearElementJSProperty(B,'_FCKMarkerId');for (var j in A[D]['markers']) this.ClearElementJSProperty(B,j);if (C) delete A[D];},ClearAllMarkers:function(A){for (var i in A) this.ClearElementMarkers(A,A[i]['element'],true);},ListToArray:function(A,B,C,D,E){if (!A.nodeName.IEquals(['ul','ol'])) return [];if (!D) D=0;if (!C) C=[];for (var i=0;i<A.childNodes.length;i++){var F=A.childNodes[i];if (!F.nodeName.IEquals('li')) continue;var G={ 'parent':A,'indent':D,'contents':[] };if (!E){G.grandparent=A.parentNode;if (G.grandparent&&G.grandparent.nodeName.IEquals('li')) G.grandparent=G.grandparent.parentNode;}else G.grandparent=E;if (B) this.SetElementMarker(B,F,'_FCK_ListArray_Index',C.length);C.push(G);for (var j=0;j<F.childNodes.length;j++){var H=F.childNodes[j];if (H.nodeName.IEquals(['ul','ol'])) this.ListToArray(H,B,C,D+1,G.grandparent);else G.contents.push(H);}};return C;},ArrayToList:function(A,B,C){if (C==undefined) C=0;if (!A||A.length<C+1) return null;var D=FCKTools.GetElementDocument(A[C].parent);var E=D.createDocumentFragment();var F=null;var G=C;var H=Math.max(A[C].indent,0);var I=null;while (true){var J=A[G];if (J.indent==H){if (!F||A[G].parent.nodeName!=F.nodeName){F=A[G].parent.cloneNode(false);E.appendChild(F);};I=D.createElement('li');F.appendChild(I);for (var i=0;i<J.contents.length;i++) I.appendChild(J.contents[i].cloneNode(true));G++;}else if (J.indent==Math.max(H,0)+1){var K=this.ArrayToList(A,null,G);I.appendChild(K.listNode);G=K.nextIndex;}else if (J.indent==-1&&C==0&&J.grandparent){var I;if (J.grandparent.nodeName.IEquals(['ul','ol'])) I=D.createElement('li');else{if (FCKConfig.EnterMode.IEquals(['div','p'])&&!J.grandparent.nodeName.IEquals('td')) I=D.createElement(FCKConfig.EnterMode);else I=D.createDocumentFragment();};for (var i=0;i<J.contents.length;i++) I.appendChild(J.contents[i].cloneNode(true));if (I.nodeType==11){if (I.lastChild&&I.lastChild.getAttribute&&I.lastChild.getAttribute('type')=='_moz') I.removeChild(I.lastChild);I.appendChild(D.createElement('br'));};if (I.nodeName.IEquals(FCKConfig.EnterMode)&&I.firstChild){this.TrimNode(I);if (FCKListsLib.BlockBoundaries[I.firstChild.nodeName.toLowerCase()]){var M=D.createDocumentFragment();while (I.firstChild) M.appendChild(I.removeChild(I.firstChild));I=M;}};if (FCKBrowserInfo.IsGeckoLike&&I.nodeName.IEquals(['div','p'])) FCKTools.AppendBogusBr(I);E.appendChild(I);F=null;G++;}else return null;if (A.length<=G||Math.max(A[G].indent,0)<H){break;}};if (B){var N=E.firstChild;while (N){if (N.nodeType==1) this.ClearElementMarkers(B,N);N=this.GetNextSourceNode(N);}};return { 'listNode':E,'nextIndex':G };},GetNextSibling:function(A,B){A=A.nextSibling;while (A&&!B&&A.nodeType!=1&&(A.nodeType!=3||A.nodeValue.length==0)) A=A.nextSibling;return A;},GetPreviousSibling:function(A,B){A=A.previousSibling;while (A&&!B&&A.nodeType!=1&&(A.nodeType!=3||A.nodeValue.length==0)) A=A.previousSibling;return A;},CheckIsEmptyElement:function(A,B){var C=A.firstChild;var D;while (C){if (C.nodeType==1){if (D||!FCKListsLib.InlineNonEmptyElements[C.nodeName.toLowerCase()]) return false;if (!B||B(C)===true) D=C;}else if (C.nodeType==3&&C.nodeValue.length>0) return false;C=C.nextSibling;};return D?this.CheckIsEmptyElement(D,B):true;},SetElementStyles:function(A,B){var C=A.style;for (var D in B) C[D]=B[D];},SetOpacity:function(A,B){if (FCKBrowserInfo.IsIE){B=Math.round(B*100);A.style.filter=(B>100?'':'progid:DXImageTransform.Microsoft.Alpha(opacity='+B+')');}else A.style.opacity=B;},GetCurrentElementStyle:function(A,B){if (FCKBrowserInfo.IsIE) return A.currentStyle[B];else return A.ownerDocument.defaultView.getComputedStyle(A,'').getPropertyValue(B);},GetPositionedAncestor:function(A){var B=A;while (B!=FCKTools.GetElementDocument(B).documentElement){if (this.GetCurrentElementStyle(B,'position')!='static') return B;if (B==FCKTools.GetElementDocument(B).documentElement&&currentWindow!=w) B=currentWindow.frameElement;else B=B.parentNode;};return null;},ScrollIntoView:function(A,B){var C=FCKTools.GetElementWindow(A);var D=FCKTools.GetViewPaneSize(C).Height;var E=D*-1;if (B===false){E+=A.offsetHeight||0;E+=parseInt(this.GetCurrentElementStyle(A,'marginBottom')||0,10)||0;};var F=FCKTools.GetDocumentPosition(C,A);E+=F.y;var G=FCKTools.GetScrollPosition(C).Y;if (E>0&&(E>G||E<G-D)) C.scrollTo(0,E);},CheckIsEditable:function(A){var B=A.nodeName.toLowerCase();var C=FCK.DTD[B]||FCK.DTD.span;return (C['#']&&!FCKListsLib.NonEditableElements[B]);},GetSelectedDivContainers:function(){var A=[];var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.GetTouchedStartNode();var D=B.GetTouchedEndNode();var E=C;if (C==D){while (D.nodeType==1&&D.lastChild) D=D.lastChild;D=FCKDomTools.GetNextSourceNode(D);}while (E&&E!=D){if (E.nodeType!=3||!/^[ \t\n]*$/.test(E.nodeValue)){var F=new FCKElementPath(E);var G=F.BlockLimit;if (G&&G.nodeName.IEquals('div')&&A.IndexOf(G)==-1) A.push(G);};E=FCKDomTools.GetNextSourceNode(E);};return A;}};
+var FCKTools={};FCKTools.CreateBogusBR=function(A){var B=A.createElement('br');B.setAttribute('type','_moz');return B;};FCKTools.FixCssUrls=function(A,B){if (!A||A.length==0) return B;return B.replace(/url\s*\(([\s'"]*)(.*?)([\s"']*)\)/g,function(match,opener,path,closer){if (/^\/|^\w?:/.test(path)) return match;else return 'url('+opener+A+path+closer+')';});};FCKTools._GetUrlFixedCss=function(A,B){var C=A.match(/^([^|]+)\|([\s\S]*)/);if (C) return FCKTools.FixCssUrls(C[1],C[2]);else return A;};FCKTools.AppendStyleSheet=function(A,B){if (!B) return [];if (typeof(B)=='string'){if (/[\\\/\.][^{}]*$/.test(B)){return this.AppendStyleSheet(A,B.split(','));}else return [this.AppendStyleString(A,FCKTools._GetUrlFixedCss(B))];}else{var C=[];for (var i=0;i<B.length;i++) C.push(this._AppendStyleSheet(A,B[i]));return C;}};FCKTools.GetStyleHtml=(function(){var A=function(styleDef,markTemp){if (styleDef.length==0) return '';var B=markTemp?' _fcktemp="true"':'';return '<style type="text/css"'+B+'>'+styleDef+'</style>';};var C=function(cssFileUrl,markTemp){if (cssFileUrl.length==0) return '';var B=markTemp?' _fcktemp="true"':'';return '<link href="'+cssFileUrl+'" type="text/css" rel="stylesheet" '+B+'/>';};return function(cssFileOrArrayOrDef,markTemp){if (!cssFileOrArrayOrDef) return '';if (typeof(cssFileOrArrayOrDef)=='string'){if (/[\\\/\.][^{}]*$/.test(cssFileOrArrayOrDef)){return this.GetStyleHtml(cssFileOrArrayOrDef.split(','),markTemp);}else return A(this._GetUrlFixedCss(cssFileOrArrayOrDef),markTemp);}else{var E='';for (var i=0;i<cssFileOrArrayOrDef.length;i++) E+=C(cssFileOrArrayOrDef[i],markTemp);return E;}}})();FCKTools.GetElementDocument=function (A){return A.ownerDocument||A.document;};FCKTools.GetElementWindow=function(A){return this.GetDocumentWindow(this.GetElementDocument(A));};FCKTools.GetDocumentWindow=function(A){if (FCKBrowserInfo.IsSafari&&!A.parentWindow) this.FixDocumentParentWindow(window.top);return A.parentWindow||A.defaultView;};FCKTools.FixDocumentParentWindow=function(A){if (A.document) A.document.parentWindow=A;for (var i=0;i<A.frames.length;i++) FCKTools.FixDocumentParentWindow(A.frames[i]);};FCKTools.HTMLEncode=function(A){if (!A) return '';A=A.replace(/&/g,'&amp;');A=A.replace(/</g,'&lt;');A=A.replace(/>/g,'&gt;');return A;};FCKTools.HTMLDecode=function(A){if (!A) return '';A=A.replace(/&gt;/g,'>');A=A.replace(/&lt;/g,'<');A=A.replace(/&amp;/g,'&');return A;};FCKTools._ProcessLineBreaksForPMode=function(A,B,C,D,E){var F=0;var G="<p>";var H="</p>";var I="<br />";if (C){G="<li>";H="</li>";F=1;}while (D&&D!=A.FCK.EditorDocument.body){if (D.tagName.toLowerCase()=='p'){F=1;break;};D=D.parentNode;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};var n=B.charAt(i+1);if (n=='\r'){i++;n=B.charAt(i+1);};if (n=='\n'){i++;if (F) E.push(H);E.push(G);F=1;}else E.push(I);}};FCKTools._ProcessLineBreaksForDivMode=function(A,B,C,D,E){var F=0;var G="<div>";var H="</div>";if (C){G="<li>";H="</li>";F=1;}while (D&&D!=A.FCK.EditorDocument.body){if (D.tagName.toLowerCase()=='div'){F=1;break;};D=D.parentNode;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};if (F){if (E[E.length-1]==G){E.push("&nbsp;");};E.push(H);};E.push(G);F=1;};if (F) E.push(H);};FCKTools._ProcessLineBreaksForBrMode=function(A,B,C,D,E){var F=0;var G="<br />";var H="";if (C){G="<li>";H="</li>";F=1;};for (var i=0;i<B.length;i++){var c=B.charAt(i);if (c=='\r') continue;if (c!='\n'){E.push(c);continue;};if (F&&H.length) E.push (H);E.push(G);F=1;}};FCKTools.ProcessLineBreaks=function(A,B,C){var D=B.EnterMode.toLowerCase();var E=[];var F=0;var G=new A.FCKDomRange(A.FCK.EditorWindow);G.MoveToSelection();var H=G._Range.startContainer;while (H&&H.nodeType!=1) H=H.parentNode;if (H&&H.tagName.toLowerCase()=='li') F=1;if (D=='p') this._ProcessLineBreaksForPMode(A,C,F,H,E);else if (D=='div') this._ProcessLineBreaksForDivMode(A,C,F,H,E);else if (D=='br') this._ProcessLineBreaksForBrMode(A,C,F,H,E);return E.join("");};FCKTools.AddSelectOption=function(A,B,C){var D=FCKTools.GetElementDocument(A).createElement("OPTION");D.text=B;D.value=C;A.options.add(D);return D;};FCKTools.RunFunction=function(A,B,C,D){if (A) this.SetTimeout(A,0,B,C,D);};FCKTools.SetTimeout=function(A,B,C,D,E){return (E||window).setTimeout(function(){if (D) A.apply(C,[].concat(D));else A.apply(C);},B);};FCKTools.SetInterval=function(A,B,C,D,E){return (E||window).setInterval(function(){A.apply(C,D||[]);},B);};FCKTools.ConvertStyleSizeToHtml=function(A){return A.EndsWith('%')?A:parseInt(A,10);};FCKTools.ConvertHtmlSizeToStyle=function(A){return A.EndsWith('%')?A:(A+'px');};FCKTools.GetElementAscensor=function(A,B){var e=A;var C=","+B.toUpperCase()+",";while (e){if (C.indexOf(","+e.nodeName.toUpperCase()+",")!=-1) return e;e=e.parentNode;};return null;};FCKTools.CreateEventListener=function(A,B){var f=function(){var C=[];for (var i=0;i<arguments.length;i++) C.push(arguments[i]);A.apply(this,C.concat(B));};return f;};FCKTools.IsStrictMode=function(A){return ('CSS1Compat'==(A.compatMode||(FCKBrowserInfo.IsSafari?'CSS1Compat':null)));};FCKTools.ArgumentsToArray=function(A,B,C){B=B||0;C=C||A.length;var D=[];for (var i=B;i<B+C&&i<A.length;i++) D.push(A[i]);return D;};FCKTools.CloneObject=function(A){var B=function() {};B.prototype=A;return new B;};FCKTools.AppendBogusBr=function(A){if (!A) return;var B=this.GetLastItem(A.getElementsByTagName('br'));if (!B||(B.getAttribute('type',2)!='_moz'&&B.getAttribute('_moz_dirty')==null)){var C=this.GetElementDocument(A);if (FCKBrowserInfo.IsOpera) A.appendChild(C.createTextNode(''));else A.appendChild(this.CreateBogusBR(C));}};FCKTools.GetLastItem=function(A){if (A.length>0) return A[A.length-1];return null;};FCKTools.GetDocumentPosition=function(w,A){var x=0;var y=0;var B=A;var C=null;var D=FCKTools.GetElementWindow(B);while (B&&!(D==w&&(B==w.document.body||B==w.document.documentElement))){x+=B.offsetLeft-B.scrollLeft;y+=B.offsetTop-B.scrollTop;if (!FCKBrowserInfo.IsOpera){var E=C;while (E&&E!=B){x-=E.scrollLeft;y-=E.scrollTop;E=E.parentNode;}};C=B;if (B.offsetParent) B=B.offsetParent;else{if (D!=w){B=D.frameElement;C=null;if (B) D=B.contentWindow.parent;}else B=null;}};if (FCKDomTools.GetCurrentElementStyle(w.document.body,'position')!='static'||(FCKBrowserInfo.IsIE&&FCKDomTools.GetPositionedAncestor(A)==null)){x+=w.document.body.offsetLeft;y+=w.document.body.offsetTop;};return { "x":x,"y":y };};FCKTools.GetWindowPosition=function(w,A){var B=this.GetDocumentPosition(w,A);var C=FCKTools.GetScrollPosition(w);B.x-=C.X;B.y-=C.Y;return B;};FCKTools.ProtectFormStyles=function(A){if (!A||A.nodeType!=1||A.tagName.toLowerCase()!='form') return [];var B=[];var C=['style','className'];for (var i=0;i<C.length;i++){var D=C[i];if (A.elements.namedItem(D)){var E=A.elements.namedItem(D);B.push([E,E.nextSibling]);A.removeChild(E);}};return B;};FCKTools.RestoreFormStyles=function(A,B){if (!A||A.nodeType!=1||A.tagName.toLowerCase()!='form') return;if (B.length>0){for (var i=B.length-1;i>=0;i--){var C=B[i][0];var D=B[i][1];if (D) A.insertBefore(C,D);else A.appendChild(C);}}};FCKTools.GetNextNode=function(A,B){if (A.firstChild) return A.firstChild;else if (A.nextSibling) return A.nextSibling;else{var C=A.parentNode;while (C){if (C==B) return null;if (C.nextSibling) return C.nextSibling;else C=C.parentNode;}};return null;};FCKTools.GetNextTextNode=function(A,B,C){node=this.GetNextNode(A,B);if (C&&node&&C(node)) return null;while (node&&node.nodeType!=3){node=this.GetNextNode(node,B);if (C&&node&&C(node)) return null;};return node;};FCKTools.Merge=function(){var A=arguments;var o=A[0];for (var i=1;i<A.length;i++){var B=A[i];for (var p in B) o[p]=B[p];};return o;};FCKTools.IsArray=function(A){return (A instanceof Array);};FCKTools.AppendLengthProperty=function(A,B){var C=0;for (var n in A) C++;return A[B||'length']=C;};FCKTools.NormalizeCssText=function(A){var B=document.createElement('span');B.style.cssText=A;return B.style.cssText;};FCKTools.Bind=function(A,B){return function(){ return B.apply(A,arguments);};};FCKTools.GetVoidUrl=function(){if (FCK_IS_CUSTOM_DOMAIN) return "javascript: void( function(){document.open();document.write('<html><head><title></title></head><body></body></html>');document.domain = '"+FCK_RUNTIME_DOMAIN+"';document.close();}() ) ;";if (FCKBrowserInfo.IsIE){if (FCKBrowserInfo.IsIE7||!FCKBrowserInfo.IsIE6) return "";else return "javascript: '';";};return "javascript: void(0);";};FCKTools.ResetStyles=function(A){A.style.cssText='margin:0;padding:0;border:0;background-color:transparent;background-image:none;';};
+FCKTools.CancelEvent=function(e){return false;};FCKTools._AppendStyleSheet=function(A,B){return A.createStyleSheet(B).owningElement;};FCKTools.AppendStyleString=function(A,B){if (!B) return null;var s=A.createStyleSheet("");s.cssText=B;return s;};FCKTools.ClearElementAttributes=function(A){A.clearAttributes();};FCKTools.GetAllChildrenIds=function(A){var B=[];for (var i=0;i<A.all.length;i++){var C=A.all[i].id;if (C&&C.length>0) B[B.length]=C;};return B;};FCKTools.RemoveOuterTags=function(e){e.insertAdjacentHTML('beforeBegin',e.innerHTML);e.parentNode.removeChild(e);};FCKTools.CreateXmlObject=function(A){var B;switch (A){case 'XmlHttp':if (document.location.protocol!='file:') try { return new XMLHttpRequest();} catch (e) {};B=['MSXML2.XmlHttp','Microsoft.XmlHttp'];break;case 'DOMDocument':B=['MSXML2.DOMDocument','Microsoft.XmlDom'];break;};for (var i=0;i<2;i++){try { return new ActiveXObject(B[i]);}catch (e){}};if (FCKLang.NoActiveX){alert(FCKLang.NoActiveX);FCKLang.NoActiveX=null;};return null;};FCKTools.DisableSelection=function(A){A.unselectable='on';var e,i=0;while ((e=A.all[i++])){switch (e.tagName){case 'IFRAME':case 'TEXTAREA':case 'INPUT':case 'SELECT':break;default:e.unselectable='on';}}};FCKTools.GetScrollPosition=function(A){var B=A.document;var C={ X:B.documentElement.scrollLeft,Y:B.documentElement.scrollTop };if (C.X>0||C.Y>0) return C;return { X:B.body.scrollLeft,Y:B.body.scrollTop };};FCKTools.AddEventListener=function(A,B,C){A.attachEvent('on'+B,C);};FCKTools.RemoveEventListener=function(A,B,C){A.detachEvent('on'+B,C);};FCKTools.AddEventListenerEx=function(A,B,C,D){var o={};o.Source=A;o.Params=D||[];o.Listener=function(ev){return C.apply(o.Source,[ev].concat(o.Params));};if (FCK.IECleanup) FCK.IECleanup.AddItem(null,function() { o.Source=null;o.Params=null;});A.attachEvent('on'+B,o.Listener);A=null;D=null;};FCKTools.GetViewPaneSize=function(A){var B;var C=A.document.documentElement;if (C&&C.clientWidth) B=C;else B=A.document.body;if (B) return { Width:B.clientWidth,Height:B.clientHeight };else return { Width:0,Height:0 };};FCKTools.SaveStyles=function(A){var B=FCKTools.ProtectFormStyles(A);var C={};if (A.className.length>0){C.Class=A.className;A.className='';};var D=A.style.cssText;if (D.length>0){C.Inline=D;A.style.cssText='';};FCKTools.RestoreFormStyles(A,B);return C;};FCKTools.RestoreStyles=function(A,B){var C=FCKTools.ProtectFormStyles(A);A.className=B.Class||'';A.style.cssText=B.Inline||'';FCKTools.RestoreFormStyles(A,C);};FCKTools.RegisterDollarFunction=function(A){A.$=A.document.getElementById;};FCKTools.AppendElement=function(A,B){return A.appendChild(this.GetElementDocument(A).createElement(B));};FCKTools.ToLowerCase=function(A){return A.toLowerCase();};
+var FCKeditorAPI;function InitializeAPI(){var A=window.parent;if (!(FCKeditorAPI=A.FCKeditorAPI)){var B='window.FCKeditorAPI = {Version : "2.6.6",VersionBuild : "25427",Instances : window.FCKeditorAPI && window.FCKeditorAPI.Instances || {},GetInstance : function( name ){return this.Instances[ name ];},_FormSubmit : function(){for ( var name in FCKeditorAPI.Instances ){var oEditor = FCKeditorAPI.Instances[ name ] ;if ( oEditor.GetParentForm && oEditor.GetParentForm() == this )oEditor.UpdateLinkedField() ;}this._FCKOriginalSubmit() ;},_FunctionQueue : window.FCKeditorAPI && window.FCKeditorAPI._FunctionQueue || {Functions : new Array(),IsRunning : false,Add : function( f ){this.Functions.push( f );if ( !this.IsRunning )this.StartNext();},StartNext : function(){var aQueue = this.Functions ;if ( aQueue.length > 0 ){this.IsRunning = true;aQueue[0].call();}else this.IsRunning = false;},Remove : function( f ){var aQueue = this.Functions;var i = 0, fFunc;while( (fFunc = aQueue[ i ]) ){if ( fFunc == f )aQueue.splice( i,1 );i++ ;}this.StartNext();}}}';if (A.execScript) A.execScript(B,'JavaScript');else{if (FCKBrowserInfo.IsGecko10){eval.call(A,B);}else if(FCKBrowserInfo.IsAIR){FCKAdobeAIR.FCKeditorAPI_Evaluate(A,B);}else if (FCKBrowserInfo.IsSafari){var C=A.document;var D=C.createElement('script');D.appendChild(C.createTextNode(B));C.documentElement.appendChild(D);}else A.eval(B);};FCKeditorAPI=A.FCKeditorAPI;FCKeditorAPI.__Instances=FCKeditorAPI.Instances;};FCKeditorAPI.Instances[FCK.Name]=FCK;};function _AttachFormSubmitToAPI(){var A=FCK.GetParentForm();if (A){FCKTools.AddEventListener(A,'submit',FCK.UpdateLinkedField);if (!A._FCKOriginalSubmit&&(typeof(A.submit)=='function'||(!A.submit.tagName&&!A.submit.length))){A._FCKOriginalSubmit=A.submit;A.submit=FCKeditorAPI._FormSubmit;}}};function FCKeditorAPI_Cleanup(){if (window.FCKConfig&&FCKConfig.MsWebBrowserControlCompat&&!window.FCKUnloadFlag) return;delete FCKeditorAPI.Instances[FCK.Name];};function FCKeditorAPI_ConfirmCleanup(){if (window.FCKConfig&&FCKConfig.MsWebBrowserControlCompat) window.FCKUnloadFlag=true;};FCKTools.AddEventListener(window,'unload',FCKeditorAPI_Cleanup);FCKTools.AddEventListener(window,'beforeunload',FCKeditorAPI_ConfirmCleanup);
+var FCKImagePreloader=function(){this._Images=[];};FCKImagePreloader.prototype={AddImages:function(A){if (typeof(A)=='string') A=A.split(';');this._Images=this._Images.concat(A);},Start:function(){var A=this._Images;this._PreloadCount=A.length;for (var i=0;i<A.length;i++){var B=document.createElement('img');FCKTools.AddEventListenerEx(B,'load',_FCKImagePreloader_OnImage,this);FCKTools.AddEventListenerEx(B,'error',_FCKImagePreloader_OnImage,this);B.src=A[i];_FCKImagePreloader_ImageCache.push(B);}}};var _FCKImagePreloader_ImageCache=[];function _FCKImagePreloader_OnImage(A,B){if ((--B._PreloadCount)==0&&B.OnComplete) B.OnComplete();};
+var FCKRegexLib={AposEntity:/&apos;/gi,ObjectElements:/^(?:IMG|TABLE|TR|TD|TH|INPUT|SELECT|TEXTAREA|HR|OBJECT|A|UL|OL|LI)$/i,NamedCommands:/^(?:Cut|Copy|Paste|Print|SelectAll|RemoveFormat|Unlink|Undo|Redo|Bold|Italic|Underline|StrikeThrough|Subscript|Superscript|JustifyLeft|JustifyCenter|JustifyRight|JustifyFull|Outdent|Indent|InsertOrderedList|InsertUnorderedList|InsertHorizontalRule)$/i,BeforeBody:/(^[\s\S]*\<body[^\>]*\>)/i,AfterBody:/(\<\/body\>[\s\S]*$)/i,ToReplace:/___fcktoreplace:([\w]+)/ig,MetaHttpEquiv:/http-equiv\s*=\s*["']?([^"' ]+)/i,HasBaseTag:/<base /i,HasBodyTag:/<body[\s|>]/i,HtmlOpener:/<html\s?[^>]*>/i,HeadOpener:/<head\s?[^>]*>/i,HeadCloser:/<\/head\s*>/i,FCK_Class:/\s*FCK__[^ ]*(?=\s+|$)/,ElementName:/(^[a-z_:][\w.\-:]*\w$)|(^[a-z_]$)/,ForceSimpleAmpersand:/___FCKAmp___/g,SpaceNoClose:/\/>/g,EmptyParagraph:/^<(p|div|address|h\d|center)(?=[ >])[^>]*>\s*(<\/\1>)?$/,EmptyOutParagraph:/^<(p|div|address|h\d|center)(?=[ >])[^>]*>(?:\s*|&nbsp;|&#160;)(<\/\1>)?$/,TagBody:/></,GeckoEntitiesMarker:/#\?-\:/g,ProtectUrlsImg:/<img(?=\s).*?\ssrc=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsA:/<a(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsArea:/<area(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,Html4DocType:/HTML 4\.0 Transitional/i,DocTypeTag:/<!DOCTYPE[^>]*>/i,HtmlDocType:/DTD HTML/,TagsWithEvent:/<[^\>]+ on\w+[\s\r\n]*=[\s\r\n]*?('|")[\s\S]+?\>/g,EventAttributes:/\s(on\w+)[\s\r\n]*=[\s\r\n]*?('|")([\s\S]*?)\2/g,ProtectedEvents:/\s\w+_fckprotectedatt="([^"]+)"/g,StyleProperties:/\S+\s*:/g,InvalidSelfCloseTags:/(<(?!base|meta|link|hr|br|param|img|area|input)([a-zA-Z0-9:]+)[^>]*)\/>/gi,StyleVariableAttName:/#\(\s*("|')(.+?)\1[^\)]*\s*\)/g,RegExp:/^\/(.*)\/([gim]*)$/,HtmlTag:/<[^\s<>](?:"[^"]*"|'[^']*'|[^<])*>/};
+var FCKListsLib={BlockElements:{ address:1,blockquote:1,center:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,marquee:1,noscript:1,ol:1,p:1,pre:1,script:1,table:1,ul:1 },NonEmptyBlockElements:{ p:1,div:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,address:1,pre:1,ol:1,ul:1,li:1,td:1,th:1 },InlineChildReqElements:{ abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },InlineNonEmptyElements:{ a:1,abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },EmptyElements:{ base:1,col:1,meta:1,link:1,hr:1,br:1,param:1,img:1,area:1,input:1 },PathBlockElements:{ address:1,blockquote:1,dl:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,li:1,dt:1,de:1 },PathBlockLimitElements:{ body:1,div:1,td:1,th:1,caption:1,form:1 },StyleBlockElements:{ address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 },StyleObjectElements:{ img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 },NonEditableElements:{ button:1,option:1,script:1,iframe:1,textarea:1,object:1,embed:1,map:1,applet:1 },BlockBoundaries:{ p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,address:1,pre:1,ol:1,ul:1,li:1,dt:1,de:1,table:1,thead:1,tbody:1,tfoot:1,tr:1,th:1,td:1,caption:1,col:1,colgroup:1,blockquote:1,body:1 },ListBoundaries:{ p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,address:1,pre:1,ol:1,ul:1,li:1,dt:1,de:1,table:1,thead:1,tbody:1,tfoot:1,tr:1,th:1,td:1,caption:1,col:1,colgroup:1,blockquote:1,body:1,br:1 }};
+var FCKLanguageManager=FCK.Language={AvailableLanguages:{af:'Afrikaans',ar:'Arabic',bg:'Bulgarian',bn:'Bengali/Bangla',bs:'Bosnian',ca:'Catalan',cs:'Czech',da:'Danish',de:'German',el:'Greek',en:'English','en-au':'English (Australia)','en-ca':'English (Canadian)','en-uk':'English (United Kingdom)',eo:'Esperanto',es:'Spanish',et:'Estonian',eu:'Basque',fa:'Persian',fi:'Finnish',fo:'Faroese',fr:'French','fr-ca':'French (Canada)',gl:'Galician',gu:'Gujarati',he:'Hebrew',hi:'Hindi',hr:'Croatian',hu:'Hungarian',is:'Icelandic',it:'Italian',ja:'Japanese',km:'Khmer',ko:'Korean',lt:'Lithuanian',lv:'Latvian',mn:'Mongolian',ms:'Malay',nb:'Norwegian Bokmal',nl:'Dutch',no:'Norwegian',pl:'Polish',pt:'Portuguese (Portugal)','pt-br':'Portuguese (Brazil)',ro:'Romanian',ru:'Russian',sk:'Slovak',sl:'Slovenian',sr:'Serbian (Cyrillic)','sr-latn':'Serbian (Latin)',sv:'Swedish',th:'Thai',tr:'Turkish',uk:'Ukrainian',vi:'Vietnamese',zh:'Chinese Traditional','zh-cn':'Chinese Simplified'},GetActiveLanguage:function(){if (FCKConfig.AutoDetectLanguage){var A;if (navigator.userLanguage) A=navigator.userLanguage.toLowerCase();else if (navigator.language) A=navigator.language.toLowerCase();else{return FCKConfig.DefaultLanguage;};if (A.length>=5){A=A.substr(0,5);if (this.AvailableLanguages[A]) return A;};if (A.length>=2){A=A.substr(0,2);if (this.AvailableLanguages[A]) return A;}};return this.DefaultLanguage;},TranslateElements:function(A,B,C,D){var e=A.getElementsByTagName(B);var E,s;for (var i=0;i<e.length;i++){if ((E=e[i].getAttribute('fckLang'))){if ((s=FCKLang[E])){if (D) s=FCKTools.HTMLEncode(s);e[i][C]=s;}}}},TranslatePage:function(A){this.TranslateElements(A,'INPUT','value');this.TranslateElements(A,'SPAN','innerHTML');this.TranslateElements(A,'LABEL','innerHTML');this.TranslateElements(A,'OPTION','innerHTML',true);this.TranslateElements(A,'LEGEND','innerHTML');},Initialize:function(){if (this.AvailableLanguages[FCKConfig.DefaultLanguage]) this.DefaultLanguage=FCKConfig.DefaultLanguage;else this.DefaultLanguage='en';this.ActiveLanguage={};this.ActiveLanguage.Code=this.GetActiveLanguage();this.ActiveLanguage.Name=this.AvailableLanguages[this.ActiveLanguage.Code];}};
+var FCKXHtmlEntities={};FCKXHtmlEntities.Initialize=function(){if (FCKXHtmlEntities.Entities) return;var A='';var B,e;if (FCKConfig.ProcessHTMLEntities){FCKXHtmlEntities.Entities={' ':'nbsp','¡':'iexcl','¢':'cent','£':'pound','¤':'curren','Â¥':'yen','¦':'brvbar','§':'sect','¨':'uml','©':'copy','ª':'ordf','«':'laquo','¬':'not','­':'shy','®':'reg','¯':'macr','°':'deg','±':'plusmn','²':'sup2','³':'sup3','´':'acute','µ':'micro','¶':'para','·':'middot','¸':'cedil','¹':'sup1','º':'ordm','»':'raquo','¼':'frac14','½':'frac12','¾':'frac34','¿':'iquest','×':'times','÷':'divide','Æ’':'fnof','•':'bull','…':'hellip','′':'prime','″':'Prime','‾':'oline','â„':'frasl','℘':'weierp','â„‘':'image','â„œ':'real','â„¢':'trade','ℵ':'alefsym','â†':'larr','↑':'uarr','→':'rarr','↓':'darr','↔':'harr','↵':'crarr','â‡':'lArr','⇑':'uArr','⇒':'rArr','⇓':'dArr','⇔':'hArr','∀':'forall','∂':'part','∃':'exist','∅':'empty','∇':'nabla','∈':'isin','∉':'notin','∋':'ni','âˆ':'prod','∑':'sum','−':'minus','∗':'lowast','√':'radic','âˆ':'prop','∞':'infin','∠':'ang','∧':'and','∨':'or','∩':'cap','∪':'cup','∫':'int','∴':'there4','∼':'sim','≅':'cong','≈':'asymp','≠':'ne','≡':'equiv','≤':'le','≥':'ge','⊂':'sub','⊃':'sup','⊄':'nsub','⊆':'sube','⊇':'supe','⊕':'oplus','⊗':'otimes','⊥':'perp','â‹…':'sdot','\u2308':'lceil','\u2309':'rceil','\u230a':'lfloor','\u230b':'rfloor','\u2329':'lang','\u232a':'rang','â—Š':'loz','â™ ':'spades','♣':'clubs','♥':'hearts','♦':'diams','"':'quot','>':'gt','ˆ':'circ','Ëœ':'tilde',' ':'ensp',' ':'emsp',' ':'thinsp','‌':'zwnj','â€':'zwj','‎':'lrm','â€':'rlm','–':'ndash','—':'mdash','‘':'lsquo','’':'rsquo','‚':'sbquo','“':'ldquo','â€':'rdquo','„':'bdquo','†':'dagger','‡':'Dagger','‰':'permil','‹':'lsaquo','›':'rsaquo','€':'euro'};for (e in FCKXHtmlEntities.Entities) A+=e;if (FCKConfig.IncludeLatinEntities){B={'À':'Agrave','Ã':'Aacute','Â':'Acirc','Ã':'Atilde','Ä':'Auml','Ã…':'Aring','Æ':'AElig','Ç':'Ccedil','È':'Egrave','É':'Eacute','Ê':'Ecirc','Ë':'Euml','ÃŒ':'Igrave','Ã':'Iacute','ÃŽ':'Icirc','Ã':'Iuml','Ã':'ETH','Ñ':'Ntilde','Ã’':'Ograve','Ó':'Oacute','Ô':'Ocirc','Õ':'Otilde','Ö':'Ouml','Ø':'Oslash','Ù':'Ugrave','Ú':'Uacute','Û':'Ucirc','Ãœ':'Uuml','Ã':'Yacute','Þ':'THORN','ß':'szlig','à':'agrave','á':'aacute','â':'acirc','ã':'atilde','ä':'auml','Ã¥':'aring','æ':'aelig','ç':'ccedil','è':'egrave','é':'eacute','ê':'ecirc','ë':'euml','ì':'igrave','í':'iacute','î':'icirc','ï':'iuml','ð':'eth','ñ':'ntilde','ò':'ograve','ó':'oacute','ô':'ocirc','õ':'otilde','ö':'ouml','ø':'oslash','ù':'ugrave','ú':'uacute','û':'ucirc','ü':'uuml','ý':'yacute','þ':'thorn','ÿ':'yuml','Å’':'OElig','Å“':'oelig','Å ':'Scaron','Å¡':'scaron','Ÿ':'Yuml'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;};if (FCKConfig.IncludeGreekEntities){B={'Α':'Alpha','Î’':'Beta','Γ':'Gamma','Δ':'Delta','Ε':'Epsilon','Ζ':'Zeta','Η':'Eta','Θ':'Theta','Ι':'Iota','Κ':'Kappa','Λ':'Lambda','Îœ':'Mu','Î':'Nu','Ξ':'Xi','Ο':'Omicron','Π':'Pi','Ρ':'Rho','Σ':'Sigma','Τ':'Tau','Î¥':'Upsilon','Φ':'Phi','Χ':'Chi','Ψ':'Psi','Ω':'Omega','α':'alpha','β':'beta','γ':'gamma','δ':'delta','ε':'epsilon','ζ':'zeta','η':'eta','θ':'theta','ι':'iota','κ':'kappa','λ':'lambda','μ':'mu','ν':'nu','ξ':'xi','ο':'omicron','Ï€':'pi','Ï':'rho','Ï‚':'sigmaf','σ':'sigma','Ï„':'tau','Ï…':'upsilon','φ':'phi','χ':'chi','ψ':'psi','ω':'omega','\u03d1':'thetasym','\u03d2':'upsih','\u03d6':'piv'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;}}else{FCKXHtmlEntities.Entities={'>':'gt'};A='>';A+=' ';};var C='['+A+']';if (FCKConfig.ProcessNumericEntities) C='[^ -~]|'+C;var D=FCKConfig.AdditionalNumericEntities;if (D&&D.length>0) C+='|'+FCKConfig.AdditionalNumericEntities;FCKXHtmlEntities.EntitiesRegex=new RegExp(C,'g');};
+var FCKXHtml={};FCKXHtml.CurrentJobNum=0;FCKXHtml.GetXHTML=function(A,B,C){FCKDomTools.CheckAndRemovePaddingNode(FCKTools.GetElementDocument(A),FCKConfig.EnterMode);FCKXHtmlEntities.Initialize();this._NbspEntity=(FCKConfig.ProcessHTMLEntities?'nbsp':'#160');var D=FCK.IsDirty();FCKXHtml.SpecialBlocks=[];this.XML=FCKTools.CreateXmlObject('DOMDocument');this.MainNode=this.XML.appendChild(this.XML.createElement('xhtml'));FCKXHtml.CurrentJobNum++;if (B) this._AppendNode(this.MainNode,A);else this._AppendChildNodes(this.MainNode,A,false);if (FCKBrowserInfo.IsIE) FCKXHtml._RemoveXHtmlJobProperties(A);var E=this._GetMainXmlString();this.XML=null;if (FCKBrowserInfo.IsSafari) E=E.replace(/^<xhtml.*?>/,'<xhtml>');E=E.substr(7,E.length-15).Trim();if (FCKConfig.DocType.length>0&&FCKRegexLib.HtmlDocType.test(FCKConfig.DocType)) E=E.replace(FCKRegexLib.SpaceNoClose,'>');else E=E.replace(FCKRegexLib.SpaceNoClose,' />');if (FCKConfig.ForceSimpleAmpersand) E=E.replace(FCKRegexLib.ForceSimpleAmpersand,'&');if (C) E=FCKCodeFormatter.Format(E);for (var i=0;i<FCKXHtml.SpecialBlocks.length;i++){var F=new RegExp('___FCKsi___'+i);E=E.replace(F,FCKXHtml.SpecialBlocks[i]);};E=E.replace(FCKRegexLib.GeckoEntitiesMarker,'&');if (!D) FCK.ResetIsDirty();FCKDomTools.EnforcePaddingNode(FCKTools.GetElementDocument(A),FCKConfig.EnterMode);return E;};FCKXHtml._AppendAttribute=function(A,B,C){try{if (C==undefined||C==null) C='';else if (C.replace){if (FCKConfig.ForceSimpleAmpersand) C=C.replace(/&/g,'___FCKAmp___');C=C.replace(FCKXHtmlEntities.EntitiesRegex,FCKXHtml_GetEntity);};var D=this.XML.createAttribute(B);D.value=C;A.attributes.setNamedItem(D);}catch (e){}};FCKXHtml._AppendChildNodes=function(A,B,C){var D=B.firstChild;while (D){this._AppendNode(A,D);D=D.nextSibling;};if (C&&B.tagName&&B.tagName.toLowerCase()!='pre'){FCKDomTools.TrimNode(A);if (FCKConfig.FillEmptyBlocks){var E=A.lastChild;if (E&&E.nodeType==1&&E.nodeName=='br') this._AppendEntity(A,this._NbspEntity);}};if (A.childNodes.length==0){if (C&&FCKConfig.FillEmptyBlocks){this._AppendEntity(A,this._NbspEntity);return A;};var F=A.nodeName;if (FCKListsLib.InlineChildReqElements[F]) return null;if (!FCKListsLib.EmptyElements[F]) A.appendChild(this.XML.createTextNode(''));};return A;};FCKXHtml._AppendNode=function(A,B){if (!B) return false;switch (B.nodeType){case 1:if (FCKBrowserInfo.IsGecko&&B.tagName.toLowerCase()=='br'&&B.parentNode.tagName.toLowerCase()=='pre'){var C='\r';if (B==B.parentNode.firstChild) C+='\r';return FCKXHtml._AppendNode(A,this.XML.createTextNode(C));};if (B.getAttribute('_fckfakelement')) return FCKXHtml._AppendNode(A,FCK.GetRealElement(B));if (FCKBrowserInfo.IsGecko&&(B.hasAttribute('_moz_editor_bogus_node')||B.getAttribute('type')=='_moz')){if (B.nextSibling) return false;else{B.removeAttribute('_moz_editor_bogus_node');B.removeAttribute('type');}};if (B.getAttribute('_fcktemp')) return false;var D=B.tagName.toLowerCase();if (FCKBrowserInfo.IsIE){if (B.scopeName&&B.scopeName!='HTML'&&B.scopeName!='FCK') D=B.scopeName.toLowerCase()+':'+D;}else{if (D.StartsWith('fck:')) D=D.Remove(0,4);};if (!FCKRegexLib.ElementName.test(D)) return false;if (B._fckxhtmljob&&B._fckxhtmljob==FCKXHtml.CurrentJobNum) return false;var E=this.XML.createElement(D);FCKXHtml._AppendAttributes(A,B,E,D);B._fckxhtmljob=FCKXHtml.CurrentJobNum;var F=FCKXHtml.TagProcessors[D];if (F) E=F(E,B,A);else E=this._AppendChildNodes(E,B,Boolean(FCKListsLib.NonEmptyBlockElements[D]));if (!E) return false;A.appendChild(E);break;case 3:if (B.parentNode&&B.parentNode.nodeName.IEquals('pre')) return this._AppendTextNode(A,B.nodeValue);return this._AppendTextNode(A,B.nodeValue.ReplaceNewLineChars(' '));case 8:if (FCKBrowserInfo.IsIE&&!B.innerHTML) break;try { A.appendChild(this.XML.createComment(B.nodeValue));}catch (e) {};break;default:A.appendChild(this.XML.createComment("Element not supported - Type: "+B.nodeType+" Name: "+B.nodeName));break;};return true;};FCKXHtml._AppendSpecialItem=function(A){return '___FCKsi___'+(FCKXHtml.SpecialBlocks.push(A)-1);};FCKXHtml._AppendEntity=function(A,B){A.appendChild(this.XML.createTextNode('#?-:'+B+';'));};FCKXHtml._AppendTextNode=function(A,B){var C=B.length>0;if (C) A.appendChild(this.XML.createTextNode(B.replace(FCKXHtmlEntities.EntitiesRegex,FCKXHtml_GetEntity)));return C;};function FCKXHtml_GetEntity(A){var B=FCKXHtmlEntities.Entities[A]||('#'+A.charCodeAt(0));return '#?-:'+B+';';};FCKXHtml.TagProcessors={a:function(A,B){if (B.innerHTML.Trim().length==0&&!B.name) return false;var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'href',C);if (FCKBrowserInfo.IsIE){if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);};A=FCKXHtml._AppendChildNodes(A,B,false);return A;},area:function(A,B){var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'href',C);if (FCKBrowserInfo.IsIE){if (!A.attributes.getNamedItem('coords')){var D=B.getAttribute('coords',2);if (D&&D!='0,0,0') FCKXHtml._AppendAttribute(A,'coords',D);};if (!A.attributes.getNamedItem('shape')){var E=B.getAttribute('shape',2);if (E&&E.length>0) FCKXHtml._AppendAttribute(A,'shape',E.toLowerCase());}};return A;},body:function(A,B){A=FCKXHtml._AppendChildNodes(A,B,false);A.removeAttribute('spellcheck');return A;},iframe:function(A,B){var C=B.innerHTML;if (FCKBrowserInfo.IsGecko) C=FCKTools.HTMLDecode(C);C=C.replace(/\s_fcksavedurl="[^"]*"/g,'');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(C)));return A;},img:function(A,B){if (!A.attributes.getNamedItem('alt')) FCKXHtml._AppendAttribute(A,'alt','');var C=B.getAttribute('_fcksavedurl');if (C!=null) FCKXHtml._AppendAttribute(A,'src',C);if (B.style.width) A.removeAttribute('width');if (B.style.height) A.removeAttribute('height');return A;},li:function(A,B,C){if (C.nodeName.IEquals(['ul','ol'])) return FCKXHtml._AppendChildNodes(A,B,true);var D=FCKXHtml.XML.createElement('ul');B._fckxhtmljob=null;do{FCKXHtml._AppendNode(D,B);do{B=FCKDomTools.GetNextSibling(B);} while (B&&B.nodeType==3&&B.nodeValue.Trim().length==0)} while (B&&B.nodeName.toLowerCase()=='li') return D;},ol:function(A,B,C){if (B.innerHTML.Trim().length==0) return false;var D=C.lastChild;if (D&&D.nodeType==3) D=D.previousSibling;if (D&&D.nodeName.toUpperCase()=='LI'){B._fckxhtmljob=null;FCKXHtml._AppendNode(D,B);return false;};A=FCKXHtml._AppendChildNodes(A,B);return A;},pre:function (A,B){var C=B.firstChild;if (C&&C.nodeType==3) A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem('\r\n')));FCKXHtml._AppendChildNodes(A,B,true);return A;},script:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/javascript');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(B.text)));return A;},span:function(A,B){if (B.innerHTML.length==0) return false;A=FCKXHtml._AppendChildNodes(A,B,false);return A;},style:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/css');var C=B.innerHTML;if (FCKBrowserInfo.IsIE) C=C.replace(/^(\r\n|\n|\r)/,'');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(C)));return A;},title:function(A,B){A.appendChild(FCKXHtml.XML.createTextNode(FCK.EditorDocument.title));return A;}};FCKXHtml.TagProcessors.ul=FCKXHtml.TagProcessors.ol;
+FCKXHtml._GetMainXmlString=function(){return this.MainNode.xml;};FCKXHtml._AppendAttributes=function(A,B,C,D){var E=B.attributes,bHasStyle;for (var n=0;n<E.length;n++){var F=E[n];if (F.specified){var G=F.nodeName.toLowerCase();var H;if (G.StartsWith('_fck')) continue;else if (G=='style'){bHasStyle=true;continue;}else if (G=='class'){H=F.nodeValue.replace(FCKRegexLib.FCK_Class,'');if (H.length==0) continue;}else if (G.indexOf('on')==0) H=F.nodeValue;else if (D=='body'&&G=='contenteditable') continue;else if (F.nodeValue===true) H=G;else{try{H=B.getAttribute(G,2);}catch (e) {}};this._AppendAttribute(C,G,H||F.nodeValue);}};if (bHasStyle||B.style.cssText.length>0){var I=FCKTools.ProtectFormStyles(B);var J=B.style.cssText.replace(FCKRegexLib.StyleProperties,FCKTools.ToLowerCase);FCKTools.RestoreFormStyles(B,I);this._AppendAttribute(C,'style',J);}};FCKXHtml._RemoveXHtmlJobProperties=function (A){if (!A||!A.nodeType||A.nodeType!=1) return;if (typeof A._fckxhtmljob=='undefined'&&A.tagName!=='BODY') return;A.removeAttribute('_fckxhtmljob');if (A.hasChildNodes()){var B=A.childNodes;for (var i=B.length-1;i>=0;i--){var C=B[i];if (C.parentNode==A) FCKXHtml._RemoveXHtmlJobProperties(C);}}};FCKXHtml.TagProcessors['div']=function(A,B){if (B.align.length>0) FCKXHtml._AppendAttribute(A,'align',B.align);A=FCKXHtml._AppendChildNodes(A,B,true);return A;};FCKXHtml.TagProcessors['font']=function(A,B){if (A.attributes.length==0) A=FCKXHtml.XML.createDocumentFragment();A=FCKXHtml._AppendChildNodes(A,B);return A;};FCKXHtml.TagProcessors['form']=function(A,B){if (B.acceptCharset&&B.acceptCharset.length>0&&B.acceptCharset!='UNKNOWN') FCKXHtml._AppendAttribute(A,'accept-charset',B.acceptCharset);var C=B.attributes['name'];if (C&&C.value.length>0) FCKXHtml._AppendAttribute(A,'name',C.value);A=FCKXHtml._AppendChildNodes(A,B,true);return A;};FCKXHtml.TagProcessors['input']=function(A,B){if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);if (B.value&&!A.attributes.getNamedItem('value')) FCKXHtml._AppendAttribute(A,'value',B.value);if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text');return A;};FCKXHtml.TagProcessors['label']=function(A,B){if (B.htmlFor.length>0) FCKXHtml._AppendAttribute(A,'for',B.htmlFor);A=FCKXHtml._AppendChildNodes(A,B);return A;};FCKXHtml.TagProcessors['map']=function(A,B){if (!A.attributes.getNamedItem('name')){var C=B.name;if (C) FCKXHtml._AppendAttribute(A,'name',C);};A=FCKXHtml._AppendChildNodes(A,B,true);return A;};FCKXHtml.TagProcessors['meta']=function(A,B){var C=A.attributes.getNamedItem('http-equiv');if (C==null||C.value.length==0){var D=B.outerHTML.match(FCKRegexLib.MetaHttpEquiv);if (D){D=D[1];FCKXHtml._AppendAttribute(A,'http-equiv',D);}};return A;};FCKXHtml.TagProcessors['option']=function(A,B){if (B.selected&&!A.attributes.getNamedItem('selected')) FCKXHtml._AppendAttribute(A,'selected','selected');A=FCKXHtml._AppendChildNodes(A,B);return A;};FCKXHtml.TagProcessors['textarea']=FCKXHtml.TagProcessors['select']=function(A,B){if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);A=FCKXHtml._AppendChildNodes(A,B);return A;};
+var FCKCodeFormatter={};FCKCodeFormatter.Init=function(){var A=this.Regex={};A.BlocksOpener=/\<(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DL|DT|DD|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi;A.BlocksCloser=/\<\/(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DL|DT|DD|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi;A.NewLineTags=/\<(BR|HR)[^\>]*\>/gi;A.MainTags=/\<\/?(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR)[^\>]*\>/gi;A.LineSplitter=/\s*\n+\s*/g;A.IncreaseIndent=/^\<(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL|DL)[ \/\>]/i;A.DecreaseIndent=/^\<\/(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL|DL)[ \>]/i;A.FormatIndentatorRemove=new RegExp('^'+FCKConfig.FormatIndentator);A.ProtectedTags=/(<PRE[^>]*>)([\s\S]*?)(<\/PRE>)/gi;};FCKCodeFormatter._ProtectData=function(A,B,C,D){return B+'___FCKpd___'+(FCKCodeFormatter.ProtectedData.push(C)-1)+D;};FCKCodeFormatter.Format=function(A){if (!this.Regex) this.Init();FCKCodeFormatter.ProtectedData=[];var B=A.replace(this.Regex.ProtectedTags,FCKCodeFormatter._ProtectData);B=B.replace(this.Regex.BlocksOpener,'\n$&');B=B.replace(this.Regex.BlocksCloser,'$&\n');B=B.replace(this.Regex.NewLineTags,'$&\n');B=B.replace(this.Regex.MainTags,'\n$&\n');var C='';var D=B.split(this.Regex.LineSplitter);B='';for (var i=0;i<D.length;i++){var E=D[i];if (E.length==0) continue;if (this.Regex.DecreaseIndent.test(E)) C=C.replace(this.Regex.FormatIndentatorRemove,'');B+=C+E+'\n';if (this.Regex.IncreaseIndent.test(E)) C+=FCKConfig.FormatIndentator;};for (var j=0;j<FCKCodeFormatter.ProtectedData.length;j++){var F=new RegExp('___FCKpd___'+j);B=B.replace(F,FCKCodeFormatter.ProtectedData[j].replace(/\$/g,'$$$$'));};return B.Trim();};
+var FCKUndo={};FCKUndo.SavedData=[];FCKUndo.CurrentIndex=-1;FCKUndo.TypesCount=0;FCKUndo.Changed=false;FCKUndo.MaxTypes=25;FCKUndo.Typing=false;FCKUndo.SaveLocked=false;FCKUndo._GetBookmark=function(){FCKSelection.Restore();var A=new FCKDomRange(FCK.EditorWindow);try{A.MoveToSelection();}catch (e){return null;};if (FCKBrowserInfo.IsIE){var B=A.CreateBookmark();var C=FCK.EditorDocument.body.innerHTML;A.MoveToBookmark(B);return [B,C];};return A.CreateBookmark2();};FCKUndo._SelectBookmark=function(A){if (!A) return;var B=new FCKDomRange(FCK.EditorWindow);if (A instanceof Object){if (FCKBrowserInfo.IsIE) B.MoveToBookmark(A[0]);else B.MoveToBookmark2(A);try{B.Select();}catch (e){B.MoveToPosition(FCK.EditorDocument.body,4);B.Select();}}};FCKUndo._CompareCursors=function(A,B){for (var i=0;i<Math.min(A.length,B.length);i++){if (A[i]<B[i]) return-1;else if (A[i]>B[i]) return 1;};if (A.length<B.length) return-1;else if (A.length>B.length) return 1;return 0;};FCKUndo._CheckIsBookmarksEqual=function(A,B){if (!(A&&B)) return false;if (FCKBrowserInfo.IsIE){var C=A[1].search(A[0].StartId);var D=B[1].search(B[0].StartId);var E=A[1].search(A[0].EndId);var F=B[1].search(B[0].EndId);return C==D&&E==F;}else{return this._CompareCursors(A.Start,B.Start)==0&&this._CompareCursors(A.End,B.End)==0;}};FCKUndo.SaveUndoStep=function(){if (FCK.EditMode!=0||this.SaveLocked) return;if (this.SavedData.length) this.Changed=true;var A=FCK.EditorDocument.body.innerHTML;var B=this._GetBookmark();this.SavedData=this.SavedData.slice(0,this.CurrentIndex+1);if (this.CurrentIndex>0&&A==this.SavedData[this.CurrentIndex][0]&&this._CheckIsBookmarksEqual(B,this.SavedData[this.CurrentIndex][1])) return;else if (this.CurrentIndex==0&&this.SavedData.length&&A==this.SavedData[0][0]){this.SavedData[0][1]=B;return;};if (this.CurrentIndex+1>=FCKConfig.MaxUndoLevels) this.SavedData.shift();else this.CurrentIndex++;this.SavedData[this.CurrentIndex]=[A,B];FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.CheckUndoState=function(){return (this.Changed||this.CurrentIndex>0);};FCKUndo.CheckRedoState=function(){return (this.CurrentIndex<(this.SavedData.length-1));};FCKUndo.Undo=function(){if (this.CheckUndoState()){if (this.CurrentIndex==(this.SavedData.length-1)){this.SaveUndoStep();};this._ApplyUndoLevel(--this.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo.Redo=function(){if (this.CheckRedoState()){this._ApplyUndoLevel(++this.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo._ApplyUndoLevel=function(A){var B=this.SavedData[A];if (!B) return;if (FCKBrowserInfo.IsIE){if (B[1]&&B[1][1]) FCK.SetInnerHtml(B[1][1]);else FCK.SetInnerHtml(B[0]);}else FCK.EditorDocument.body.innerHTML=B[0];this._SelectBookmark(B[1]);this.TypesCount=0;this.Changed=false;this.Typing=false;};
+var FCKEditingArea=function(A){this.TargetElement=A;this.Mode=0;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKEditingArea_Cleanup);};FCKEditingArea.prototype.Start=function(A,B){var C=this.TargetElement;var D=FCKTools.GetElementDocument(C);while(C.firstChild) C.removeChild(C.firstChild);if (this.Mode==0){if (FCK_IS_CUSTOM_DOMAIN) A='<script>document.domain="'+FCK_RUNTIME_DOMAIN+'";</script>'+A;if (FCKBrowserInfo.IsIE) A=A.replace(/(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi,'$1></base>');else if (!B){var E=A.match(FCKRegexLib.BeforeBody);var F=A.match(FCKRegexLib.AfterBody);if (E&&F){var G=A.substr(E[1].length,A.length-E[1].length-F[1].length);A=E[1]+'&nbsp;'+F[1];if (FCKBrowserInfo.IsGecko&&(G.length==0||FCKRegexLib.EmptyParagraph.test(G))) G='<br type="_moz">';this._BodyHTML=G;}else this._BodyHTML=A;};var H=this.IFrame=D.createElement('iframe');var I='<script type="text/javascript" _fcktemp="true">window.onerror=function(){return true;};</script>';H.frameBorder=0;H.style.width=H.style.height='100%';if (FCK_IS_CUSTOM_DOMAIN&&FCKBrowserInfo.IsIE){window._FCKHtmlToLoad=A.replace(/<head>/i,'<head>'+I);H.src='javascript:void( (function(){document.open() ;document.domain="'+document.domain+'" ;document.write( window.parent._FCKHtmlToLoad );document.close() ;window.parent._FCKHtmlToLoad = null ;})() )';}else if (!FCKBrowserInfo.IsGecko){H.src='javascript:void(0)';};C.appendChild(H);this.Window=H.contentWindow;if (!FCK_IS_CUSTOM_DOMAIN||!FCKBrowserInfo.IsIE){var J=this.Window.document;J.open();J.write(A.replace(/<head>/i,'<head>'+I));J.close();};if (FCKBrowserInfo.IsAIR) FCKAdobeAIR.EditingArea_Start(J,A);if (FCKBrowserInfo.IsGecko10&&!B){this.Start(A,true);return;};if (H.readyState&&H.readyState!='completed'){var K=this;setTimeout(function(){try{K.Window.document.documentElement.doScroll("left");}catch(e){setTimeout(arguments.callee,0);return;};K.Window._FCKEditingArea=K;FCKEditingArea_CompleteStart.call(K.Window);},0);}else{this.Window._FCKEditingArea=this;if (FCKBrowserInfo.IsGecko10) this.Window.setTimeout(FCKEditingArea_CompleteStart,500);else FCKEditingArea_CompleteStart.call(this.Window);}}else{var L=this.Textarea=D.createElement('textarea');L.className='SourceField';L.dir='ltr';FCKDomTools.SetElementStyles(L,{width:'100%',height:'100%',border:'none',resize:'none',outline:'none'});C.appendChild(L);L.value=A;FCKTools.RunFunction(this.OnLoad);}};function FCKEditingArea_CompleteStart(){if (!this.document.body){this.setTimeout(FCKEditingArea_CompleteStart,50);return;};var A=this._FCKEditingArea;A.Document=A.Window.document;A.MakeEditable();FCKTools.RunFunction(A.OnLoad);};FCKEditingArea.prototype.MakeEditable=function(){var A=this.Document;if (FCKBrowserInfo.IsIE){A.body.disabled=true;A.body.contentEditable=true;A.body.removeAttribute("disabled");}else{try{A.body.spellcheck=(this.FFSpellChecker!==false);if (this._BodyHTML){A.body.innerHTML=this._BodyHTML;A.body.offsetLeft;this._BodyHTML=null;};A.designMode='on';A.execCommand('enableObjectResizing',false,!FCKConfig.DisableObjectResizing);A.execCommand('enableInlineTableEditing',false,!FCKConfig.DisableFFTableHandles);}catch (e){FCKTools.AddEventListener(this.Window.frameElement,'DOMAttrModified',FCKEditingArea_Document_AttributeNodeModified);}}};function FCKEditingArea_Document_AttributeNodeModified(A){var B=A.currentTarget.contentWindow._FCKEditingArea;if (B._timer) window.clearTimeout(B._timer);B._timer=FCKTools.SetTimeout(FCKEditingArea_MakeEditableByMutation,1000,B);};function FCKEditingArea_MakeEditableByMutation(){delete this._timer;FCKTools.RemoveEventListener(this.Window.frameElement,'DOMAttrModified',FCKEditingArea_Document_AttributeNodeModified);this.MakeEditable();};FCKEditingArea.prototype.Focus=function(){try{if (this.Mode==0){if (FCKBrowserInfo.IsIE) this._FocusIE();else this.Window.focus();}else{var A=FCKTools.GetElementDocument(this.Textarea);if ((!A.hasFocus||A.hasFocus())&&A.activeElement==this.Textarea) return;this.Textarea.focus();}}catch(e) {}};FCKEditingArea.prototype._FocusIE=function(){this.Document.body.setActive();this.Window.focus();var A=this.Document.selection.createRange();var B=A.parentElement();var C=B.nodeName.toLowerCase();if (B.childNodes.length>0||!(FCKListsLib.BlockElements[C]||FCKListsLib.NonEmptyBlockElements[C])){return;};A=new FCKDomRange(this.Window);A.MoveToElementEditStart(B);A.Select();};function FCKEditingArea_Cleanup(){if (this.Document){this.Document.selection.empty();this.Document.body.innerHTML="";};this.TargetElement=null;this.IFrame=null;this.Document=null;this.Textarea=null;if (this.Window){this.Window._FCKEditingArea=null;this.Window=null;}};
+var FCKKeystrokeHandler=function(A){this.Keystrokes={};this.CancelCtrlDefaults=(A!==false);};FCKKeystrokeHandler.prototype.AttachToElement=function(A){FCKTools.AddEventListenerEx(A,'keydown',_FCKKeystrokeHandler_OnKeyDown,this);if (FCKBrowserInfo.IsGecko10||FCKBrowserInfo.IsOpera||(FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsMac)) FCKTools.AddEventListenerEx(A,'keypress',_FCKKeystrokeHandler_OnKeyPress,this);};FCKKeystrokeHandler.prototype.SetKeystrokes=function(){for (var i=0;i<arguments.length;i++){var A=arguments[i];if (!A) continue;if (typeof(A[0])=='object') this.SetKeystrokes.apply(this,A);else{if (A.length==1) delete this.Keystrokes[A[0]];else this.Keystrokes[A[0]]=A[1]===true?true:A;}}};function _FCKKeystrokeHandler_OnKeyDown(A,B){var C=A.keyCode||A.which;var D=0;if (A.ctrlKey||A.metaKey) D+=CTRL;if (A.shiftKey) D+=SHIFT;if (A.altKey) D+=ALT;var E=C+D;var F=B._CancelIt=false;var G=B.Keystrokes[E];if (G){if (G===true||!(B.OnKeystroke&&B.OnKeystroke.apply(B,G))) return true;F=true;};if (F||(B.CancelCtrlDefaults&&D==CTRL&&(C<33||C>40))){B._CancelIt=true;if (A.preventDefault) return A.preventDefault();A.returnValue=false;A.cancelBubble=true;return false;};return true;};function _FCKKeystrokeHandler_OnKeyPress(A,B){if (B._CancelIt){if (A.preventDefault) return A.preventDefault();return false;};return true;};
+FCK.DTD=(function(){var X=FCKTools.Merge;var A,L,J,M,N,O,D,H,P,K,Q,F,G,C,B,E,I;A={isindex:1,fieldset:1};B={input:1,button:1,select:1,textarea:1,label:1};C=X({a:1},B);D=X({iframe:1},C);E={hr:1,ul:1,menu:1,div:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1};F={ins:1,del:1,script:1};G=X({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1},F);H=X({sub:1,img:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1},G);I=X({p:1},H);J=X({iframe:1},H,B);K={img:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1};L=X({a:1},J);M={tr:1};N={'#':1};O=X({param:1},K);P=X({form:1},A,D,E,I);Q={li:1};return {col:{},tr:{td:1,th:1},img:{},colgroup:{col:1},noscript:P,td:P,br:{},th:P,center:P,kbd:L,button:X(I,E),basefont:{},h5:L,h4:L,samp:L,h6:L,ol:Q,h1:L,h3:L,option:N,h2:L,form:X(A,D,E,I),select:{optgroup:1,option:1},font:J,ins:P,menu:Q,abbr:L,label:L,table:{thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1},code:L,script:N,tfoot:M,cite:L,li:P,input:{},iframe:P,strong:J,textarea:N,noframes:P,big:J,small:J,span:J,hr:{},dt:L,sub:J,optgroup:{option:1},param:{},bdo:L,'var':J,div:P,object:O,sup:J,dd:P,strike:J,area:{},dir:Q,map:X({area:1,form:1,p:1},A,F,E),applet:O,dl:{dt:1,dd:1},del:P,isindex:{},fieldset:X({legend:1},K),thead:M,ul:Q,acronym:L,b:J,a:J,blockquote:P,caption:L,i:J,u:J,tbody:M,s:L,address:X(D,I),tt:J,legend:L,q:L,pre:X(G,C),p:L,em:J,dfn:L};})();
+var FCKStyle=function(A){this.Element=(A.Element||'span').toLowerCase();this._StyleDesc=A;};FCKStyle.prototype={GetType:function(){var A=this.GetType_$;if (A!=undefined) return A;var B=this.Element;if (B=='#'||FCKListsLib.StyleBlockElements[B]) A=0;else if (FCKListsLib.StyleObjectElements[B]) A=2;else A=1;return (this.GetType_$=A);},ApplyToSelection:function(A){var B=new FCKDomRange(A);B.MoveToSelection();this.ApplyToRange(B,true);},ApplyToRange:function(A,B,C){switch (this.GetType()){case 0:this.ApplyToRange=this._ApplyBlockStyle;break;case 1:this.ApplyToRange=this._ApplyInlineStyle;break;default:return;};this.ApplyToRange(A,B,C);},ApplyToObject:function(A){if (!A) return;this.BuildElement(null,A);},RemoveFromSelection:function(A){var B=new FCKDomRange(A);B.MoveToSelection();this.RemoveFromRange(B,true);},RemoveFromRange:function(A,B,C){var D;var E=this._GetAttribsForComparison();var F=this._GetOverridesForComparison();if (A.CheckIsCollapsed()){var D=A.CreateBookmark(true);var H=A.GetBookmarkNode(D,true);var I=new FCKElementPath(H.parentNode);var J=[];var K=!FCKDomTools.GetNextSibling(H);var L=K||!FCKDomTools.GetPreviousSibling(H);var M;var N=-1;for (var i=0;i<I.Elements.length;i++){var O=I.Elements[i];if (this.CheckElementRemovable(O)){if (L&&!FCKDomTools.CheckIsEmptyElement(O,function(el){return (el!=H);})){M=O;N=J.length-1;}else{var P=O.nodeName.toLowerCase();if (P==this.Element){for (var Q in E){if (FCKDomTools.HasAttribute(O,Q)){switch (Q){case 'style':this._RemoveStylesFromElement(O);break;case 'class':if (FCKDomTools.GetAttributeValue(O,Q)!=this.GetFinalAttributeValue(Q)) continue;default:FCKDomTools.RemoveAttribute(O,Q);}}}};this._RemoveOverrides(O,F[P]);if (this.GetType()==1) this._RemoveNoAttribElement(O);}}else if (L) J.push(O);L=L&&((K&&!FCKDomTools.GetNextSibling(O))||(!K&&!FCKDomTools.GetPreviousSibling(O)));if (M&&(!L||(i==I.Elements.length-1))){var R=FCKDomTools.RemoveNode(H);for (var j=0;j<=N;j++){var S=FCKDomTools.CloneElement(J[j]);S.appendChild(R);R=S;};if (K) FCKDomTools.InsertAfterNode(M,R);else M.parentNode.insertBefore(R,M);L=false;M=null;}};if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);return;};A.Expand('inline_elements');D=A.CreateBookmark(true);var T=A.GetBookmarkNode(D,true);var U=A.GetBookmarkNode(D,false);A.Release(true);var I=new FCKElementPath(T);var X=I.Elements;var O;for (var i=1;i<X.length;i++){O=X[i];if (O==I.Block||O==I.BlockLimit) break;if (this.CheckElementRemovable(O)) FCKDomTools.BreakParent(T,O,A);};I=new FCKElementPath(U);X=I.Elements;for (var i=1;i<X.length;i++){O=X[i];if (O==I.Block||O==I.BlockLimit) break;b=O.nodeName.toLowerCase();if (this.CheckElementRemovable(O)) FCKDomTools.BreakParent(U,O,A);};var Z=FCKDomTools.GetNextSourceNode(T,true);while (Z){var a=FCKDomTools.GetNextSourceNode(Z);if (Z.nodeType==1){var b=Z.nodeName.toLowerCase();var c=(b==this.Element);if (c){for (var Q in E){if (FCKDomTools.HasAttribute(Z,Q)){switch (Q){case 'style':this._RemoveStylesFromElement(Z);break;case 'class':if (FCKDomTools.GetAttributeValue(Z,Q)!=this.GetFinalAttributeValue(Q)) continue;default:FCKDomTools.RemoveAttribute(Z,Q);}}}}else c=!!F[b];if (c){this._RemoveOverrides(Z,F[b]);this._RemoveNoAttribElement(Z);}};if (a==U) break;Z=a;};this._FixBookmarkStart(T);if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);},CheckElementRemovable:function(A,B){if (!A) return false;var C=A.nodeName.toLowerCase();if (C==this.Element){if (!B&&!FCKDomTools.HasAttributes(A)) return true;var D=this._GetAttribsForComparison();var E=(D._length==0);for (var F in D){if (F=='_length') continue;if (this._CompareAttributeValues(F,FCKDomTools.GetAttributeValue(A,F),(this.GetFinalAttributeValue(F)||''))){E=true;if (!B) break;}else{E=false;if (B) return false;}};if (E) return true;};var G=this._GetOverridesForComparison()[C];if (G){if (!(D=G.Attributes)) return true;for (var i=0;i<D.length;i++){var H=D[i][0];if (FCKDomTools.HasAttribute(A,H)){var I=D[i][1];if (I==null||(typeof I=='string'&&FCKDomTools.GetAttributeValue(A,H)==I)||I.test(FCKDomTools.GetAttributeValue(A,H))) return true;}}};return false;},CheckActive:function(A){switch (this.GetType()){case 0:return this.CheckElementRemovable(A.Block||A.BlockLimit,true);case 1:var B=A.Elements;for (var i=0;i<B.length;i++){var C=B[i];if (C==A.Block||C==A.BlockLimit) continue;if (this.CheckElementRemovable(C,true)) return true;}};return false;},RemoveFromElement:function(A){var B=this._GetAttribsForComparison();var C=this._GetOverridesForComparison();var D=A.getElementsByTagName(this.Element);for (var i=D.length-1;i>=0;i--){var E=D[i];for (var F in B){if (FCKDomTools.HasAttribute(E,F)){switch (F){case 'style':this._RemoveStylesFromElement(E);break;case 'class':if (FCKDomTools.GetAttributeValue(E,F)!=this.GetFinalAttributeValue(F)) continue;default:FCKDomTools.RemoveAttribute(E,F);}}};this._RemoveOverrides(E,C[this.Element]);this._RemoveNoAttribElement(E);};for (var G in C){if (G!=this.Element){D=A.getElementsByTagName(G);for (var i=D.length-1;i>=0;i--){var E=D[i];this._RemoveOverrides(E,C[G]);this._RemoveNoAttribElement(E);}}}},_RemoveStylesFromElement:function(A){var B=A.style.cssText;var C=this.GetFinalStyleValue();if (B.length>0&&C.length==0) return;C='(^|;)\\s*('+C.replace(/\s*([^ ]+):.*?(;|$)/g,'$1|').replace(/\|$/,'')+'):[^;]+';var D=new RegExp(C,'gi');B=B.replace(D,'').Trim();if (B.length==0||B==';') FCKDomTools.RemoveAttribute(A,'style');else A.style.cssText=B.replace(D,'');},_RemoveOverrides:function(A,B){var C=B&&B.Attributes;if (C){for (var i=0;i<C.length;i++){var D=C[i][0];if (FCKDomTools.HasAttribute(A,D)){var E=C[i][1];if (E==null||(E.test&&E.test(FCKDomTools.GetAttributeValue(A,D)))||(typeof E=='string'&&FCKDomTools.GetAttributeValue(A,D)==E)) FCKDomTools.RemoveAttribute(A,D);}}}},_RemoveNoAttribElement:function(A){if (!FCKDomTools.HasAttributes(A)){var B=A.firstChild;var C=A.lastChild;FCKDomTools.RemoveNode(A,true);this._MergeSiblings(B);if (B!=C) this._MergeSiblings(C);}},BuildElement:function(A,B){var C=B||A.createElement(this.Element);var D=this._StyleDesc.Attributes;var E;if (D){for (var F in D){E=this.GetFinalAttributeValue(F);if (F.toLowerCase()=='class') C.className=E;else C.setAttribute(F,E);}};if (this._GetStyleText().length>0) C.style.cssText=this.GetFinalStyleValue();return C;},_CompareAttributeValues:function(A,B,C){if (A=='style'&&B&&C){B=B.replace(/;$/,'').toLowerCase();C=C.replace(/;$/,'').toLowerCase();};return (B==C||((B===null||B==='')&&(C===null||C==='')))},GetFinalAttributeValue:function(A){var B=this._StyleDesc.Attributes;var B=B?B[A]:null;if (!B&&A=='style') return this.GetFinalStyleValue();if (B&&this._Variables) B=B.Replace(FCKRegexLib.StyleVariableAttName,this._GetVariableReplace,this);return B;},GetFinalStyleValue:function(){var A=this._GetStyleText();if (A.length>0&&this._Variables){A=A.Replace(FCKRegexLib.StyleVariableAttName,this._GetVariableReplace,this);A=FCKTools.NormalizeCssText(A);};return A;},_GetVariableReplace:function(){return this._Variables[arguments[2]]||arguments[0];},SetVariable:function(A,B){var C=this._Variables;if (!C) C=this._Variables={};this._Variables[A]=B;},_FromPre:function(A,B,C){var D=B.innerHTML;D=D.replace(/(\r\n|\r)/g,'\n');D=D.replace(/^[ \t]*\n/,'');D=D.replace(/\n$/,'');D=D.replace(/^[ \t]+|[ \t]+$/g,function(match,offset,s){if (match.length==1) return '&nbsp;';else if (offset==0) return new Array(match.length).join('&nbsp;')+' ';else return ' '+new Array(match.length).join('&nbsp;');});var E=new FCKHtmlIterator(D);var F=[];E.Each(function(isTag,value){if (!isTag){value=value.replace(/\n/g,'<br>');value=value.replace(/[ \t]{2,}/g,function (match){return new Array(match.length).join('&nbsp;')+' ';});};F.push(value);});C.innerHTML=F.join('');return C;},_ToPre:function(A,B,C){var D=B.innerHTML.Trim();D=D.replace(/[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi,'<br />');var E=new FCKHtmlIterator(D);var F=[];E.Each(function(isTag,value){if (!isTag) value=value.replace(/([ \t\n\r]+|&nbsp;)/g,' ');else if (isTag&&value=='<br />') value='\n';F.push(value);});if (FCKBrowserInfo.IsIE){var G=A.createElement('div');G.appendChild(C);C.outerHTML='<pre>\n'+F.join('')+'</pre>';C=G.removeChild(G.firstChild);}else C.innerHTML=F.join('');return C;},_CheckAndMergePre:function(A,B){if (A!=FCKDomTools.GetPreviousSourceElement(B,true)) return;var C=A.innerHTML.replace(/\n$/,'')+'\n\n'+B.innerHTML.replace(/^\n/,'');if (FCKBrowserInfo.IsIE) B.outerHTML='<pre>'+C+'</pre>';else B.innerHTML=C;FCKDomTools.RemoveNode(A);},_CheckAndSplitPre:function(A){var B;var C=A.firstChild;C=C&&C.nextSibling;while (C){var D=C.nextSibling;if (D&&D.nextSibling&&C.nodeName.IEquals('br')&&D.nodeName.IEquals('br')){FCKDomTools.RemoveNode(C);C=D.nextSibling;FCKDomTools.RemoveNode(D);B=FCKDomTools.InsertAfterNode(B||A,FCKDomTools.CloneElement(A));continue;};if (B){C=C.previousSibling;FCKDomTools.MoveNode(C.nextSibling,B);};C=C.nextSibling;}},_ApplyBlockStyle:function(A,B,C){var D;if (B) D=A.CreateBookmark();var E=new FCKDomRangeIterator(A);E.EnforceRealBlocks=true;var F;var G=A.Window.document;var H;while((F=E.GetNextParagraph())){var I=this.BuildElement(G);var J=I.nodeName.IEquals('pre');var K=F.nodeName.IEquals('pre');var L=J&&!K;var M=!J&&K;if (L) I=this._ToPre(G,F,I);else if (M) I=this._FromPre(G,F,I);else FCKDomTools.MoveChildren(F,I);F.parentNode.insertBefore(I,F);FCKDomTools.RemoveNode(F);if (J){if (H) this._CheckAndMergePre(H,I);H=I;}else if (M) this._CheckAndSplitPre(I);};if (B) A.SelectBookmark(D);if (C) A.MoveToBookmark(D);},_ApplyInlineStyle:function(A,B,C){var D=A.Window.document;if (A.CheckIsCollapsed()){var E=this.BuildElement(D);A.InsertNode(E);A.MoveToPosition(E,2);A.Select();return;};var F=this.Element;var G=FCK.DTD[F]||FCK.DTD.span;var H=this._GetAttribsForComparison();var I;A.Expand('inline_elements');var J=A.CreateBookmark(true);var K=A.GetBookmarkNode(J,true);var L=A.GetBookmarkNode(J,false);A.Release(true);var M=FCKDomTools.GetNextSourceNode(K,true);while (M){var N=false;var O=M.nodeType;var P=O==1?M.nodeName.toLowerCase():null;if (!P||G[P]){if ((FCK.DTD[M.parentNode.nodeName.toLowerCase()]||FCK.DTD.span)[F]||!FCK.DTD[F]){if (!A.CheckHasRange()) A.SetStart(M,3);if (O!=1||M.childNodes.length==0){var Q=M;var R=Q.parentNode;while (Q==R.lastChild&&G[R.nodeName.toLowerCase()]){Q=R;};A.SetEnd(Q,4);if (Q==Q.parentNode.lastChild&&!G[Q.parentNode.nodeName.toLowerCase()]) N=true;}else{A.SetEnd(M,3);}}else N=true;}else N=true;M=FCKDomTools.GetNextSourceNode(M);if (M==L){M=null;N=true;};if (N&&A.CheckHasRange()&&!A.CheckIsCollapsed()){I=this.BuildElement(D);A.ExtractContents().AppendTo(I);if (I.innerHTML.RTrim().length>0){A.InsertNode(I);this.RemoveFromElement(I);this._MergeSiblings(I,this._GetAttribsForComparison());if (!FCKBrowserInfo.IsIE) I.normalize();};A.Release(true);}};this._FixBookmarkStart(K);if (B) A.SelectBookmark(J);if (C) A.MoveToBookmark(J);},_FixBookmarkStart:function(A){var B;while ((B=A.nextSibling)){if (B.nodeType==1&&FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){if (!B.firstChild) FCKDomTools.RemoveNode(B);else FCKDomTools.MoveNode(A,B,true);continue;};if (B.nodeType==3&&B.length==0){FCKDomTools.RemoveNode(B);continue;};break;}},_MergeSiblings:function(A,B){if (!A||A.nodeType!=1||!FCKListsLib.InlineNonEmptyElements[A.nodeName.toLowerCase()]) return;this._MergeNextSibling(A,B);this._MergePreviousSibling(A,B);},_MergeNextSibling:function(A,B){var C=A.nextSibling;var D=(C&&C.nodeType==1&&C.getAttribute('_fck_bookmark'));if (D) C=C.nextSibling;if (C&&C.nodeType==1&&C.nodeName==A.nodeName){if (!B) B=this._CreateElementAttribsForComparison(A);if (this._CheckAttributesMatch(C,B)){var E=A.lastChild;if (D) FCKDomTools.MoveNode(A.nextSibling,A);FCKDomTools.MoveChildren(C,A);FCKDomTools.RemoveNode(C);if (E) this._MergeNextSibling(E);}}},_MergePreviousSibling:function(A,B){var C=A.previousSibling;var D=(C&&C.nodeType==1&&C.getAttribute('_fck_bookmark'));if (D) C=C.previousSibling;if (C&&C.nodeType==1&&C.nodeName==A.nodeName){if (!B) B=this._CreateElementAttribsForComparison(A);if (this._CheckAttributesMatch(C,B)){var E=A.firstChild;if (D) FCKDomTools.MoveNode(A.previousSibling,A,true);FCKDomTools.MoveChildren(C,A,true);FCKDomTools.RemoveNode(C);if (E) this._MergePreviousSibling(E);}}},_GetStyleText:function(){var A=this._StyleDesc.Styles;var B=(this._StyleDesc.Attributes?this._StyleDesc.Attributes['style']||'':'');if (B.length>0) B+=';';for (var C in A) B+=C+':'+A[C]+';';if (B.length>0&&!(/#\(/.test(B))){B=FCKTools.NormalizeCssText(B);};return (this._GetStyleText=function() { return B;})();},_GetAttribsForComparison:function(){var A=this._GetAttribsForComparison_$;if (A) return A;A={};var B=this._StyleDesc.Attributes;if (B){for (var C in B){A[C.toLowerCase()]=B[C].toLowerCase();}};if (this._GetStyleText().length>0){A['style']=this._GetStyleText().toLowerCase();};FCKTools.AppendLengthProperty(A,'_length');return (this._GetAttribsForComparison_$=A);},_GetOverridesForComparison:function(){var A=this._GetOverridesForComparison_$;if (A) return A;A={};var B=this._StyleDesc.Overrides;if (B){if (!FCKTools.IsArray(B)) B=[B];for (var i=0;i<B.length;i++){var C=B[i];var D;var E;var F;if (typeof C=='string') D=C.toLowerCase();else{D=C.Element?C.Element.toLowerCase():this.Element;F=C.Attributes;};E=A[D]||(A[D]={});if (F){var G=(E.Attributes=E.Attributes||[]);for (var H in F){G.push([H.toLowerCase(),F[H]]);}}}};return (this._GetOverridesForComparison_$=A);},_CreateElementAttribsForComparison:function(A){var B={};var C=0;for (var i=0;i<A.attributes.length;i++){var D=A.attributes[i];if (D.specified){B[D.nodeName.toLowerCase()]=FCKDomTools.GetAttributeValue(A,D).toLowerCase();C++;}};B._length=C;return B;},_CheckAttributesMatch:function(A,B){var C=A.attributes;var D=0;for (var i=0;i<C.length;i++){var E=C[i];if (E.specified){var F=E.nodeName.toLowerCase();var G=B[F];if (!G) break;if (G!=FCKDomTools.GetAttributeValue(A,E).toLowerCase()) break;D++;}};return (D==B._length);}};
+var FCKStyles=FCK.Styles={_Callbacks:{},_ObjectStyles:{},ApplyStyle:function(A){if (typeof A=='string') A=this.GetStyles()[A];if (A){if (A.GetType()==2) A.ApplyToObject(FCKSelection.GetSelectedElement());else A.ApplyToSelection(FCK.EditorWindow);FCK.Events.FireEvent('OnSelectionChange');}},RemoveStyle:function(A){if (typeof A=='string') A=this.GetStyles()[A];if (A){A.RemoveFromSelection(FCK.EditorWindow);FCK.Events.FireEvent('OnSelectionChange');}},AttachStyleStateChange:function(A,B,C){var D=this._Callbacks[A];if (!D) D=this._Callbacks[A]=[];D.push([B,C]);},CheckSelectionChanges:function(){var A=FCKSelection.GetBoundaryParentElement(true);if (!A) return;var B=new FCKElementPath(A);var C=this.GetStyles();for (var D in C){var E=this._Callbacks[D];if (E){var F=C[D];var G=F.CheckActive(B);if (G!=(F._LastState||null)){F._LastState=G;for (var i=0;i<E.length;i++){var H=E[i][0];var I=E[i][1];H.call(I||window,D,G);}}}}},CheckStyleInSelection:function(A){return false;},_GetRemoveFormatTagsRegex:function (){var A=new RegExp('^(?:'+FCKConfig.RemoveFormatTags.replace(/,/g,'|')+')$','i');return (this._GetRemoveFormatTagsRegex=function(){return A;})&&A;},RemoveAll:function(){var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();if (A.CheckIsCollapsed()) return;A.Expand('inline_elements');var B=A.CreateBookmark(true);var C=A.GetBookmarkNode(B,true);var D=A.GetBookmarkNode(B,false);A.Release(true);var E=this._GetRemoveFormatTagsRegex();var F=new FCKElementPath(C);var G=F.Elements;var H;for (var i=1;i<G.length;i++){H=G[i];if (H==F.Block||H==F.BlockLimit) break;if (E.test(H.nodeName)) FCKDomTools.BreakParent(C,H,A);};F=new FCKElementPath(D);G=F.Elements;for (var i=1;i<G.length;i++){H=G[i];if (H==F.Block||H==F.BlockLimit) break;elementName=H.nodeName.toLowerCase();if (E.test(H.nodeName)) FCKDomTools.BreakParent(D,H,A);};var I=FCKDomTools.GetNextSourceNode(C,true,1);while (I){if (I==D) break;var J=FCKDomTools.GetNextSourceNode(I,false,1);if (E.test(I.nodeName)) FCKDomTools.RemoveNode(I,true);else FCKDomTools.RemoveAttributes(I,FCKConfig.RemoveAttributesArray);I=J;};A.SelectBookmark(B);FCK.Events.FireEvent('OnSelectionChange');},GetStyle:function(A){return this.GetStyles()[A];},GetStyles:function(){var A=this._GetStyles;if (!A){A=this._GetStyles=FCKTools.Merge(this._LoadStylesCore(),this._LoadStylesCustom(),this._LoadStylesXml());};return A;},CheckHasObjectStyle:function(A){return!!this._ObjectStyles[A];},_LoadStylesCore:function(){var A={};var B=FCKConfig.CoreStyles;for (var C in B){var D=A['_FCK_'+C]=new FCKStyle(B[C]);D.IsCore=true;};return A;},_LoadStylesCustom:function(){var A={};var B=FCKConfig.CustomStyles;if (B){for (var C in B){var D=A[C]=new FCKStyle(B[C]);D.Name=C;}};return A;},_LoadStylesXml:function(){var A={};var B=FCKConfig.StylesXmlPath;if (!B||B.length==0) return A;var C=new FCKXml();C.LoadUrl(B);var D=FCKXml.TransformToObject(C.SelectSingleNode('Styles'));var E=D.$Style;if (!E) return A;for (var i=0;i<E.length;i++){var F=E[i];var G=(F.element||'').toLowerCase();if (G.length==0) throw('The element name is required. Error loading "'+B+'"');var H={Element:G,Attributes:{},Styles:{},Overrides:[]};var I=F.$Attribute||[];for (var j=0;j<I.length;j++){H.Attributes[I[j].name]=I[j].value;};var J=F.$Style||[];for (j=0;j<J.length;j++){H.Styles[J[j].name]=J[j].value;};var K=F.$Override;if (K){for (j=0;j<K.length;j++){var L=K[j];var M={Element:L.element};var N=L.$Attribute;if (N){M.Attributes={};for (var k=0;k<N.length;k++){var O=N[k].value||null;if (O){var P=O&&FCKRegexLib.RegExp.exec(O);if (P) O=new RegExp(P[1],P[2]||'');};M.Attributes[N[k].name]=O;}};H.Overrides.push(M);}};var Q=new FCKStyle(H);Q.Name=F.name||G;if (Q.GetType()==2) this._ObjectStyles[G]=true;A[Q.Name]=Q;};return A;}};
+var FCKListHandler={OutdentListItem:function(A){var B=A.parentNode;if (B.tagName.toUpperCase().Equals('UL','OL')){var C=FCKTools.GetElementDocument(A);var D=new FCKDocumentFragment(C);var E=D.RootNode;var F=false;var G=FCKDomTools.GetFirstChild(A,['UL','OL']);if (G){F=true;var H;while ((H=G.firstChild)) E.appendChild(G.removeChild(H));FCKDomTools.RemoveNode(G);};var I;var J=false;while ((I=A.nextSibling)){if (!F&&I.nodeType==1&&I.nodeName.toUpperCase()=='LI') J=F=true;E.appendChild(I.parentNode.removeChild(I));if (!J&&I.nodeType==1&&I.nodeName.toUpperCase().Equals('UL','OL')) FCKDomTools.RemoveNode(I,true);};var K=B.parentNode.tagName.toUpperCase();var L=(K=='LI');if (L||K.Equals('UL','OL')){if (F){var G=B.cloneNode(false);D.AppendTo(G);A.appendChild(G);}else if (L) D.InsertAfterNode(B.parentNode);else D.InsertAfterNode(B);if (L) FCKDomTools.InsertAfterNode(B.parentNode,B.removeChild(A));else FCKDomTools.InsertAfterNode(B,B.removeChild(A));}else{if (F){var N=B.cloneNode(false);D.AppendTo(N);FCKDomTools.InsertAfterNode(B,N);};var O=C.createElement(FCKConfig.EnterMode=='p'?'p':'div');FCKDomTools.MoveChildren(B.removeChild(A),O);FCKDomTools.InsertAfterNode(B,O);if (FCKConfig.EnterMode=='br'){if (FCKBrowserInfo.IsGecko) O.parentNode.insertBefore(FCKTools.CreateBogusBR(C),O);else FCKDomTools.InsertAfterNode(O,FCKTools.CreateBogusBR(C));FCKDomTools.RemoveNode(O,true);}};if (this.CheckEmptyList(B)) FCKDomTools.RemoveNode(B,true);}},CheckEmptyList:function(A){return (FCKDomTools.GetFirstChild(A,'LI')==null);},CheckListHasContents:function(A){var B=A.firstChild;while (B){switch (B.nodeType){case 1:if (!B.nodeName.IEquals('UL','LI')) return true;break;case 3:if (B.nodeValue.Trim().length>0) return true;};B=B.nextSibling;};return false;}};
+var FCKElementPath=function(A){var B=null;var C=null;var D=[];var e=A;while (e){if (e.nodeType==1){if (!this.LastElement) this.LastElement=e;var E=e.nodeName.toLowerCase();if (FCKBrowserInfo.IsIE&&e.scopeName!='HTML') E=e.scopeName.toLowerCase()+':'+E;if (!C){if (!B&&FCKListsLib.PathBlockElements[E]!=null) B=e;if (FCKListsLib.PathBlockLimitElements[E]!=null){if (!B&&E=='div'&&!FCKElementPath._CheckHasBlock(e)) B=e;else C=e;}};D.push(e);if (E=='body') break;};e=e.parentNode;};this.Block=B;this.BlockLimit=C;this.Elements=D;};FCKElementPath._CheckHasBlock=function(A){var B=A.childNodes;for (var i=0,count=B.length;i<count;i++){var C=B[i];if (C.nodeType==1&&FCKListsLib.BlockElements[C.nodeName.toLowerCase()]) return true;};return false;};
+var FCKDomRange=function(A){this.Window=A;this._Cache={};};FCKDomRange.prototype={_UpdateElementInfo:function(){var A=this._Range;if (!A) this.Release(true);else{var B=A.startContainer;var C=new FCKElementPath(B);this.StartNode=B.nodeType==3?B:B.childNodes[A.startOffset];this.StartContainer=B;this.StartBlock=C.Block;this.StartBlockLimit=C.BlockLimit;if (A.collapsed){this.EndNode=this.StartNode;this.EndContainer=this.StartContainer;this.EndBlock=this.StartBlock;this.EndBlockLimit=this.StartBlockLimit;}else{var D=A.endContainer;if (B!=D) C=new FCKElementPath(D);var E=D;if (A.endOffset==0){while (E&&!E.previousSibling) E=E.parentNode;if (E) E=E.previousSibling;}else if (E.nodeType==1) E=E.childNodes[A.endOffset-1];this.EndNode=E;this.EndContainer=D;this.EndBlock=C.Block;this.EndBlockLimit=C.BlockLimit;}};this._Cache={};},CreateRange:function(){return new FCKW3CRange(this.Window.document);},DeleteContents:function(){if (this._Range){this._Range.deleteContents();this._UpdateElementInfo();}},ExtractContents:function(){if (this._Range){var A=this._Range.extractContents();this._UpdateElementInfo();return A;};return null;},CheckIsCollapsed:function(){if (this._Range) return this._Range.collapsed;return false;},Collapse:function(A){if (this._Range) this._Range.collapse(A);this._UpdateElementInfo();},Clone:function(){var A=FCKTools.CloneObject(this);if (this._Range) A._Range=this._Range.cloneRange();return A;},MoveToNodeContents:function(A){if (!this._Range) this._Range=this.CreateRange();this._Range.selectNodeContents(A);this._UpdateElementInfo();},MoveToElementStart:function(A){this.SetStart(A,1);this.SetEnd(A,1);},MoveToElementEditStart:function(A){var B;while (A&&A.nodeType==1){if (FCKDomTools.CheckIsEditable(A)) B=A;else if (B) break;A=A.firstChild;};if (B) this.MoveToElementStart(B);},InsertNode:function(A){if (this._Range) this._Range.insertNode(A);},CheckIsEmpty:function(){if (this.CheckIsCollapsed()) return true;var A=this.Window.document.createElement('div');this._Range.cloneContents().AppendTo(A);FCKDomTools.TrimNode(A);return (A.innerHTML.length==0);},CheckStartOfBlock:function(){var A=this._Cache;var B=A.IsStartOfBlock;if (B!=undefined) return B;var C=this.StartBlock||this.StartBlockLimit;var D=this._Range.startContainer;var E=this._Range.startOffset;var F;if (E>0){if (D.nodeType==3){var G=D.nodeValue.substr(0,E).Trim();if (G.length!=0) return A.IsStartOfBlock=false;}else F=D.childNodes[E-1];};if (!F) F=FCKDomTools.GetPreviousSourceNode(D,true,null,C);while (F){switch (F.nodeType){case 1:if (!FCKListsLib.InlineChildReqElements[F.nodeName.toLowerCase()]) return A.IsStartOfBlock=false;break;case 3:if (F.nodeValue.Trim().length>0) return A.IsStartOfBlock=false;};F=FCKDomTools.GetPreviousSourceNode(F,false,null,C);};return A.IsStartOfBlock=true;},CheckEndOfBlock:function(A){var B=this._Cache.IsEndOfBlock;if (B!=undefined) return B;var C=this.EndBlock||this.EndBlockLimit;var D=this._Range.endContainer;var E=this._Range.endOffset;var F;if (D.nodeType==3){var G=D.nodeValue;if (E<G.length){G=G.substr(E);if (G.Trim().length!=0) return this._Cache.IsEndOfBlock=false;}}else F=D.childNodes[E];if (!F) F=FCKDomTools.GetNextSourceNode(D,true,null,C);var H=false;while (F){switch (F.nodeType){case 1:var I=F.nodeName.toLowerCase();if (FCKListsLib.InlineChildReqElements[I]) break;if (I=='br'&&!H){H=true;break;};return this._Cache.IsEndOfBlock=false;case 3:if (F.nodeValue.Trim().length>0) return this._Cache.IsEndOfBlock=false;};F=FCKDomTools.GetNextSourceNode(F,false,null,C);};if (A) this.Select();return this._Cache.IsEndOfBlock=true;},CreateBookmark:function(A){var B={StartId:(new Date()).valueOf()+Math.floor(Math.random()*1000)+'S',EndId:(new Date()).valueOf()+Math.floor(Math.random()*1000)+'E'};var C=this.Window.document;var D;var E;var F;if (!this.CheckIsCollapsed()){E=C.createElement('span');E.style.display='none';E.id=B.EndId;E.setAttribute('_fck_bookmark',true);E.innerHTML='&nbsp;';F=this.Clone();F.Collapse(false);F.InsertNode(E);};D=C.createElement('span');D.style.display='none';D.id=B.StartId;D.setAttribute('_fck_bookmark',true);D.innerHTML='&nbsp;';F=this.Clone();F.Collapse(true);F.InsertNode(D);if (A){B.StartNode=D;B.EndNode=E;};if (E){this.SetStart(D,4);this.SetEnd(E,3);}else this.MoveToPosition(D,4);return B;},GetBookmarkNode:function(A,B){var C=this.Window.document;if (B) return A.StartNode||C.getElementById(A.StartId);else return A.EndNode||C.getElementById(A.EndId);},MoveToBookmark:function(A,B){var C=this.GetBookmarkNode(A,true);var D=this.GetBookmarkNode(A,false);this.SetStart(C,3);if (!B) FCKDomTools.RemoveNode(C);if (D){this.SetEnd(D,3);if (!B) FCKDomTools.RemoveNode(D);}else this.Collapse(true);this._UpdateElementInfo();},CreateBookmark2:function(){if (!this._Range) return { "Start":0,"End":0 };var A={"Start":[this._Range.startOffset],"End":[this._Range.endOffset]};var B=this._Range.startContainer.previousSibling;var C=this._Range.endContainer.previousSibling;var D=this._Range.startContainer;var E=this._Range.endContainer;while (B&&B.nodeType==3&&D.nodeType==3){A.Start[0]+=B.length;D=B;B=B.previousSibling;}while (C&&C.nodeType==3&&E.nodeType==3){A.End[0]+=C.length;E=C;C=C.previousSibling;};if (D.nodeType==1&&D.childNodes[A.Start[0]]&&D.childNodes[A.Start[0]].nodeType==3){var F=D.childNodes[A.Start[0]];var G=0;while (F.previousSibling&&F.previousSibling.nodeType==3){F=F.previousSibling;G+=F.length;};D=F;A.Start[0]=G;};if (E.nodeType==1&&E.childNodes[A.End[0]]&&E.childNodes[A.End[0]].nodeType==3){var F=E.childNodes[A.End[0]];var G=0;while (F.previousSibling&&F.previousSibling.nodeType==3){F=F.previousSibling;G+=F.length;};E=F;A.End[0]=G;};A.Start=FCKDomTools.GetNodeAddress(D,true).concat(A.Start);A.End=FCKDomTools.GetNodeAddress(E,true).concat(A.End);return A;},MoveToBookmark2:function(A){var B=FCKDomTools.GetNodeFromAddress(this.Window.document,A.Start.slice(0,-1),true);var C=FCKDomTools.GetNodeFromAddress(this.Window.document,A.End.slice(0,-1),true);this.Release(true);this._Range=new FCKW3CRange(this.Window.document);var D=A.Start[A.Start.length-1];var E=A.End[A.End.length-1];while (B.nodeType==3&&D>B.length){if (!B.nextSibling||B.nextSibling.nodeType!=3) break;D-=B.length;B=B.nextSibling;}while (C.nodeType==3&&E>C.length){if (!C.nextSibling||C.nextSibling.nodeType!=3) break;E-=C.length;C=C.nextSibling;};this._Range.setStart(B,D);this._Range.setEnd(C,E);this._UpdateElementInfo();},MoveToPosition:function(A,B){this.SetStart(A,B);this.Collapse(true);},SetStart:function(A,B,C){var D=this._Range;if (!D) D=this._Range=this.CreateRange();switch(B){case 1:D.setStart(A,0);break;case 2:D.setStart(A,A.childNodes.length);break;case 3:D.setStartBefore(A);break;case 4:D.setStartAfter(A);};if (!C) this._UpdateElementInfo();},SetEnd:function(A,B,C){var D=this._Range;if (!D) D=this._Range=this.CreateRange();switch(B){case 1:D.setEnd(A,0);break;case 2:D.setEnd(A,A.childNodes.length);break;case 3:D.setEndBefore(A);break;case 4:D.setEndAfter(A);};if (!C) this._UpdateElementInfo();},Expand:function(A){var B,oSibling;switch (A){case 'inline_elements':if (this._Range.startOffset==0){B=this._Range.startContainer;if (B.nodeType!=1) B=B.previousSibling?null:B.parentNode;if (B){while (FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){this._Range.setStartBefore(B);if (B!=B.parentNode.firstChild) break;B=B.parentNode;}}};B=this._Range.endContainer;var C=this._Range.endOffset;if ((B.nodeType==3&&C>=B.nodeValue.length)||(B.nodeType==1&&C>=B.childNodes.length)||(B.nodeType!=1&&B.nodeType!=3)){if (B.nodeType!=1) B=B.nextSibling?null:B.parentNode;if (B){while (FCKListsLib.InlineNonEmptyElements[B.nodeName.toLowerCase()]){this._Range.setEndAfter(B);if (B!=B.parentNode.lastChild) break;B=B.parentNode;}}};break;case 'block_contents':case 'list_contents':var D=FCKListsLib.BlockBoundaries;if (A=='list_contents'||FCKConfig.EnterMode=='br') D=FCKListsLib.ListBoundaries;if (this.StartBlock&&FCKConfig.EnterMode!='br'&&A=='block_contents') this.SetStart(this.StartBlock,1);else{B=this._Range.startContainer;if (B.nodeType==1){var E=B.childNodes[this._Range.startOffset];if (E) B=FCKDomTools.GetPreviousSourceNode(E,true);else B=B.lastChild||B;}while (B&&(B.nodeType!=1||(B!=this.StartBlockLimit&&!D[B.nodeName.toLowerCase()]))){this._Range.setStartBefore(B);B=B.previousSibling||B.parentNode;}};if (this.EndBlock&&FCKConfig.EnterMode!='br'&&A=='block_contents'&&this.EndBlock.nodeName.toLowerCase()!='li') this.SetEnd(this.EndBlock,2);else{B=this._Range.endContainer;if (B.nodeType==1) B=B.childNodes[this._Range.endOffset]||B.lastChild;while (B&&(B.nodeType!=1||(B!=this.StartBlockLimit&&!D[B.nodeName.toLowerCase()]))){this._Range.setEndAfter(B);B=B.nextSibling||B.parentNode;};if (B&&B.nodeName.toLowerCase()=='br') this._Range.setEndAfter(B);};this._UpdateElementInfo();}},SplitBlock:function(A){var B=A||FCKConfig.EnterMode;if (!this._Range) this.MoveToSelection();if (this.StartBlockLimit==this.EndBlockLimit){var C=this.StartBlock;var D=this.EndBlock;var E=null;if (B!='br'){if (!C){C=this.FixBlock(true,B);D=this.EndBlock;};if (!D) D=this.FixBlock(false,B);};var F=(C!=null&&this.CheckStartOfBlock());var G=(D!=null&&this.CheckEndOfBlock());if (!this.CheckIsEmpty()) this.DeleteContents();if (C&&D&&C==D){if (G){E=new FCKElementPath(this.StartContainer);this.MoveToPosition(D,4);D=null;}else if (F){E=new FCKElementPath(this.StartContainer);this.MoveToPosition(C,3);C=null;}else{this.SetEnd(C,2);var H=this.ExtractContents();D=C.cloneNode(false);D.removeAttribute('id',false);H.AppendTo(D);FCKDomTools.InsertAfterNode(C,D);this.MoveToPosition(C,4);if (FCKBrowserInfo.IsGecko&&!C.nodeName.IEquals(['ul','ol'])) FCKTools.AppendBogusBr(C);}};return {PreviousBlock:C,NextBlock:D,WasStartOfBlock:F,WasEndOfBlock:G,ElementPath:E};};return null;},FixBlock:function(A,B){var C=this.CreateBookmark();this.Collapse(A);this.Expand('block_contents');var D=this.Window.document.createElement(B);this.ExtractContents().AppendTo(D);FCKDomTools.TrimNode(D);if (FCKDomTools.CheckIsEmptyElement(D,function(element) { return element.getAttribute('_fck_bookmark')!='true';})&&FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(D);this.InsertNode(D);this.MoveToBookmark(C);return D;},Release:function(A){if (!A) this.Window=null;this.StartNode=null;this.StartContainer=null;this.StartBlock=null;this.StartBlockLimit=null;this.EndNode=null;this.EndContainer=null;this.EndBlock=null;this.EndBlockLimit=null;this._Range=null;this._Cache=null;},CheckHasRange:function(){return!!this._Range;},GetTouchedStartNode:function(){var A=this._Range;var B=A.startContainer;if (A.collapsed||B.nodeType!=1) return B;return B.childNodes[A.startOffset]||B;},GetTouchedEndNode:function(){var A=this._Range;var B=A.endContainer;if (A.collapsed||B.nodeType!=1) return B;return B.childNodes[A.endOffset-1]||B;}};
+FCKDomRange.prototype.MoveToSelection=function(){this.Release(true);this._Range=new FCKW3CRange(this.Window.document);var A=this.Window.document.selection;if (A.type!='Control'){var B=this._GetSelectionMarkerTag(true);var C=this._GetSelectionMarkerTag(false);if (!B&&!C){this._Range.setStart(this.Window.document.body,0);this._UpdateElementInfo();return;};this._Range.setStart(B.parentNode,FCKDomTools.GetIndexOf(B));B.parentNode.removeChild(B);this._Range.setEnd(C.parentNode,FCKDomTools.GetIndexOf(C));C.parentNode.removeChild(C);this._UpdateElementInfo();}else{var D=A.createRange().item(0);if (D){this._Range.setStartBefore(D);this._Range.setEndAfter(D);this._UpdateElementInfo();}}};FCKDomRange.prototype.Select=function(A){if (this._Range) this.SelectBookmark(this.CreateBookmark(true),A);};FCKDomRange.prototype.SelectBookmark=function(A,B){var C=this.CheckIsCollapsed();var D;var E;var F=this.GetBookmarkNode(A,true);if (!F) return;var G;if (!C) G=this.GetBookmarkNode(A,false);var H=this.Window.document.body.createTextRange();H.moveToElementText(F);H.moveStart('character',1);if (G){var I=this.Window.document.body.createTextRange();I.moveToElementText(G);H.setEndPoint('EndToEnd',I);H.moveEnd('character',-1);}else{D=B||!F.previousSibling||F.previousSibling.nodeName.toLowerCase()=='br';E=this.Window.document.createElement('span');E.innerHTML='&#65279;';F.parentNode.insertBefore(E,F);if (D){F.parentNode.insertBefore(this.Window.document.createTextNode('\ufeff'),F);}};if (!this._Range) this._Range=this.CreateRange();this._Range.setStartBefore(F);F.parentNode.removeChild(F);if (C){if (D){H.moveStart('character',-1);H.select();this.Window.document.selection.clear();}else H.select();FCKDomTools.RemoveNode(E);}else{this._Range.setEndBefore(G);G.parentNode.removeChild(G);H.select();}};FCKDomRange.prototype._GetSelectionMarkerTag=function(A){var B=this.Window.document;var C=B.selection;var D;try{D=C.createRange();}catch (e){return null;};if (D.parentElement().document!=B) return null;D.collapse(A===true);var E='fck_dom_range_temp_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000);D.pasteHTML('<span id="'+E+'"></span>');return B.getElementById(E);};
+var FCKDomRangeIterator=function(A){this.Range=A;this.ForceBrBreak=false;this.EnforceRealBlocks=false;};FCKDomRangeIterator.CreateFromSelection=function(A){var B=new FCKDomRange(A);B.MoveToSelection();return new FCKDomRangeIterator(B);};FCKDomRangeIterator.prototype={GetNextParagraph:function(){var A;var B;var C;var D;var E;var F=this.ForceBrBreak?FCKListsLib.ListBoundaries:FCKListsLib.BlockBoundaries;if (!this._LastNode){var B=this.Range.Clone();B.Expand(this.ForceBrBreak?'list_contents':'block_contents');this._NextNode=B.GetTouchedStartNode();this._LastNode=B.GetTouchedEndNode();B=null;};var H=this._NextNode;var I=this._LastNode;this._NextNode=null;while (H){var J=false;var K=(H.nodeType!=1);var L=false;if (!K){var M=H.nodeName.toLowerCase();if (F[M]&&(!FCKBrowserInfo.IsIE||H.scopeName=='HTML')){if (M=='br') K=true;else if (!B&&H.childNodes.length==0&&M!='hr'){A=H;C=H==I;break;};if (B){B.SetEnd(H,3,true);if (M!='br') this._NextNode=FCKDomTools.GetNextSourceNode(H,true,null,I)||H;};J=true;}else{if (H.firstChild){if (!B){B=new FCKDomRange(this.Range.Window);B.SetStart(H,3,true);};H=H.firstChild;continue;};K=true;}}else if (H.nodeType==3){if (/^[\r\n\t ]+$/.test(H.nodeValue)) K=false;};if (K&&!B){B=new FCKDomRange(this.Range.Window);B.SetStart(H,3,true);};C=((!J||K)&&H==I);if (B&&!J){while (!H.nextSibling&&!C){var N=H.parentNode;if (F[N.nodeName.toLowerCase()]){J=true;C=C||(N==I);break;};H=N;K=true;C=(H==I);L=true;}};if (K) B.SetEnd(H,4,true);if ((J||C)&&B){B._UpdateElementInfo();if (B.StartNode==B.EndNode&&B.StartNode.parentNode==B.StartBlockLimit&&B.StartNode.getAttribute&&B.StartNode.getAttribute('_fck_bookmark')) B=null;else break;};if (C) break;H=FCKDomTools.GetNextSourceNode(H,L,null,I);};if (!A){if (!B){this._NextNode=null;return null;};A=B.StartBlock;if (!A&&!this.EnforceRealBlocks&&B.StartBlockLimit.nodeName.IEquals('DIV','TH','TD')&&B.CheckStartOfBlock()&&B.CheckEndOfBlock()){A=B.StartBlockLimit;}else if (!A||(this.EnforceRealBlocks&&A.nodeName.toLowerCase()=='li')){A=this.Range.Window.document.createElement(FCKConfig.EnterMode=='p'?'p':'div');B.ExtractContents().AppendTo(A);FCKDomTools.TrimNode(A);B.InsertNode(A);D=true;E=true;}else if (A.nodeName.toLowerCase()!='li'){if (!B.CheckStartOfBlock()||!B.CheckEndOfBlock()){A=A.cloneNode(false);B.ExtractContents().AppendTo(A);FCKDomTools.TrimNode(A);var O=B.SplitBlock();D=!O.WasStartOfBlock;E=!O.WasEndOfBlock;B.InsertNode(A);}}else if (!C){this._NextNode=A==I?null:FCKDomTools.GetNextSourceNode(B.EndNode,true,null,I);return A;}};if (D){var P=A.previousSibling;if (P&&P.nodeType==1){if (P.nodeName.toLowerCase()=='br') P.parentNode.removeChild(P);else if (P.lastChild&&P.lastChild.nodeName.IEquals('br')) P.removeChild(P.lastChild);}};if (E){var Q=A.lastChild;if (Q&&Q.nodeType==1&&Q.nodeName.toLowerCase()=='br') A.removeChild(Q);};if (!this._NextNode) this._NextNode=(C||A==I)?null:FCKDomTools.GetNextSourceNode(A,true,null,I);return A;}};
+var FCKDocumentFragment=function(A){this._Document=A;this.RootNode=A.createElement('div');};FCKDocumentFragment.prototype={AppendTo:function(A){FCKDomTools.MoveChildren(this.RootNode,A);},AppendHtml:function(A){var B=this._Document.createElement('div');B.innerHTML=A;FCKDomTools.MoveChildren(B,this.RootNode);},InsertAfterNode:function(A){var B=this.RootNode;var C;while((C=B.lastChild)) FCKDomTools.InsertAfterNode(A,B.removeChild(C));}};
+var FCKW3CRange=function(A){this._Document=A;this.startContainer=null;this.startOffset=null;this.endContainer=null;this.endOffset=null;this.collapsed=true;};FCKW3CRange.CreateRange=function(A){return new FCKW3CRange(A);};FCKW3CRange.CreateFromRange=function(A,B){var C=FCKW3CRange.CreateRange(A);C.setStart(B.startContainer,B.startOffset);C.setEnd(B.endContainer,B.endOffset);return C;};FCKW3CRange.prototype={_UpdateCollapsed:function(){this.collapsed=(this.startContainer==this.endContainer&&this.startOffset==this.endOffset);},setStart:function(A,B){this.startContainer=A;this.startOffset=B;if (!this.endContainer){this.endContainer=A;this.endOffset=B;};this._UpdateCollapsed();},setEnd:function(A,B){this.endContainer=A;this.endOffset=B;if (!this.startContainer){this.startContainer=A;this.startOffset=B;};this._UpdateCollapsed();},setStartAfter:function(A){this.setStart(A.parentNode,FCKDomTools.GetIndexOf(A)+1);},setStartBefore:function(A){this.setStart(A.parentNode,FCKDomTools.GetIndexOf(A));},setEndAfter:function(A){this.setEnd(A.parentNode,FCKDomTools.GetIndexOf(A)+1);},setEndBefore:function(A){this.setEnd(A.parentNode,FCKDomTools.GetIndexOf(A));},collapse:function(A){if (A){this.endContainer=this.startContainer;this.endOffset=this.startOffset;}else{this.startContainer=this.endContainer;this.startOffset=this.endOffset;};this.collapsed=true;},selectNodeContents:function(A){this.setStart(A,0);this.setEnd(A,A.nodeType==3?A.data.length:A.childNodes.length);},insertNode:function(A){var B=this.startContainer;var C=this.startOffset;if (B.nodeType==3){B.splitText(C);if (B==this.endContainer) this.setEnd(B.nextSibling,this.endOffset-this.startOffset);FCKDomTools.InsertAfterNode(B,A);return;}else{B.insertBefore(A,B.childNodes[C]||null);if (B==this.endContainer){this.endOffset++;this.collapsed=false;}}},deleteContents:function(){if (this.collapsed) return;this._ExecContentsAction(0);},extractContents:function(){var A=new FCKDocumentFragment(this._Document);if (!this.collapsed) this._ExecContentsAction(1,A);return A;},cloneContents:function(){var A=new FCKDocumentFragment(this._Document);if (!this.collapsed) this._ExecContentsAction(2,A);return A;},_ExecContentsAction:function(A,B){var C=this.startContainer;var D=this.endContainer;var E=this.startOffset;var F=this.endOffset;var G=false;var H=false;if (D.nodeType==3) D=D.splitText(F);else{if (D.childNodes.length>0){if (F>D.childNodes.length-1){D=FCKDomTools.InsertAfterNode(D.lastChild,this._Document.createTextNode(''));H=true;}else D=D.childNodes[F];}};if (C.nodeType==3){C.splitText(E);if (C==D) D=C.nextSibling;}else{if (E==0){C=C.insertBefore(this._Document.createTextNode(''),C.firstChild);G=true;}else if (E>C.childNodes.length-1){C=C.appendChild(this._Document.createTextNode(''));G=true;}else C=C.childNodes[E].previousSibling;};var I=FCKDomTools.GetParents(C);var J=FCKDomTools.GetParents(D);var i,topStart,topEnd;for (i=0;i<I.length;i++){topStart=I[i];topEnd=J[i];if (topStart!=topEnd) break;};var K,levelStartNode,levelClone,currentNode,currentSibling;if (B) K=B.RootNode;for (var j=i;j<I.length;j++){levelStartNode=I[j];if (K&&levelStartNode!=C) levelClone=K.appendChild(levelStartNode.cloneNode(levelStartNode==C));currentNode=levelStartNode.nextSibling;while(currentNode){if (currentNode==J[j]||currentNode==D) break;currentSibling=currentNode.nextSibling;if (A==2) K.appendChild(currentNode.cloneNode(true));else{currentNode.parentNode.removeChild(currentNode);if (A==1) K.appendChild(currentNode);};currentNode=currentSibling;};if (K) K=levelClone;};if (B) K=B.RootNode;for (var k=i;k<J.length;k++){levelStartNode=J[k];if (A>0&&levelStartNode!=D) levelClone=K.appendChild(levelStartNode.cloneNode(levelStartNode==D));if (!I[k]||levelStartNode.parentNode!=I[k].parentNode){currentNode=levelStartNode.previousSibling;while(currentNode){if (currentNode==I[k]||currentNode==C) break;currentSibling=currentNode.previousSibling;if (A==2) K.insertBefore(currentNode.cloneNode(true),K.firstChild);else{currentNode.parentNode.removeChild(currentNode);if (A==1) K.insertBefore(currentNode,K.firstChild);};currentNode=currentSibling;}};if (K) K=levelClone;};if (A==2){var L=this.startContainer;if (L.nodeType==3){L.data+=L.nextSibling.data;L.parentNode.removeChild(L.nextSibling);};var M=this.endContainer;if (M.nodeType==3&&M.nextSibling){M.data+=M.nextSibling.data;M.parentNode.removeChild(M.nextSibling);}}else{if (topStart&&topEnd&&(C.parentNode!=topStart.parentNode||D.parentNode!=topEnd.parentNode)){var N=FCKDomTools.GetIndexOf(topEnd);if (G&&topEnd.parentNode==C.parentNode) N--;this.setStart(topEnd.parentNode,N);};this.collapse(true);};if(G) C.parentNode.removeChild(C);if(H&&D.parentNode) D.parentNode.removeChild(D);},cloneRange:function(){return FCKW3CRange.CreateFromRange(this._Document,this);}};
+var FCKEnterKey=function(A,B,C,D){this.Window=A;this.EnterMode=B||'p';this.ShiftEnterMode=C||'br';var E=new FCKKeystrokeHandler(false);E._EnterKey=this;E.OnKeystroke=FCKEnterKey_OnKeystroke;E.SetKeystrokes([[13,'Enter'],[SHIFT+13,'ShiftEnter'],[8,'Backspace'],[CTRL+8,'CtrlBackspace'],[46,'Delete']]);this.TabText='';if (D>0||FCKBrowserInfo.IsSafari){while (D--) this.TabText+='\xa0';E.SetKeystrokes([9,'Tab']);};E.AttachToElement(A.document);};function FCKEnterKey_OnKeystroke(A,B){var C=this._EnterKey;try{switch (B){case 'Enter':return C.DoEnter();break;case 'ShiftEnter':return C.DoShiftEnter();break;case 'Backspace':return C.DoBackspace();break;case 'Delete':return C.DoDelete();break;case 'Tab':return C.DoTab();break;case 'CtrlBackspace':return C.DoCtrlBackspace();break;}}catch (e){};return false;};FCKEnterKey.prototype.DoEnter=function(A,B){FCKUndo.SaveUndoStep();this._HasShift=(B===true);var C=FCKSelection.GetParentElement();var D=new FCKElementPath(C);var E=A||this.EnterMode;if (E=='br'||D.Block&&D.Block.tagName.toLowerCase()=='pre') return this._ExecuteEnterBr();else return this._ExecuteEnterBlock(E);};FCKEnterKey.prototype.DoShiftEnter=function(){return this.DoEnter(this.ShiftEnterMode,true);};FCKEnterKey.prototype.DoBackspace=function(){var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(B,this.Window.document.body)){this._FixIESelectAllBug(B);return true;};var C=B.CheckIsCollapsed();if (!C){if (FCKBrowserInfo.IsIE&&this.Window.document.selection.type.toLowerCase()=="control"){var D=this.Window.document.selection.createRange();for (var i=D.length-1;i>=0;i--){var E=D.item(i);E.parentNode.removeChild(E);};return true;};return false;};if (FCKBrowserInfo.IsIE){var F=FCKDomTools.GetPreviousSourceElement(B.StartNode,true);if (F&&F.nodeName.toLowerCase()=='br'){var G=B.Clone();G.SetStart(F,4);if (G.CheckIsEmpty()){F.parentNode.removeChild(F);return true;}}};var H=B.StartBlock;var I=B.EndBlock;if (B.StartBlockLimit==B.EndBlockLimit&&H&&I){if (!C){var J=B.CheckEndOfBlock();B.DeleteContents();if (H!=I){B.SetStart(I,1);B.SetEnd(I,1);};B.Select();A=(H==I);};if (B.CheckStartOfBlock()){var K=B.StartBlock;var L=FCKDomTools.GetPreviousSourceElement(K,true,['BODY',B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,L,K);}else if (FCKBrowserInfo.IsGeckoLike){B.Select();}};B.Release();return A;};FCKEnterKey.prototype.DoCtrlBackspace=function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(this.Window);A.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(A,this.Window.document.body)){this._FixIESelectAllBug(A);return true;};return false;};FCKEnterKey.prototype._ExecuteBackspace=function(A,B,C){var D=false;if (!B&&C&&C.nodeName.IEquals('LI')&&C.parentNode.parentNode.nodeName.IEquals('LI')){this._OutdentWithSelection(C,A);return true;};if (B&&B.nodeName.IEquals('LI')){var E=FCKDomTools.GetLastChild(B,['UL','OL']);while (E){B=FCKDomTools.GetLastChild(E,'LI');E=FCKDomTools.GetLastChild(B,['UL','OL']);}};if (B&&C){if (C.nodeName.IEquals('LI')&&!B.nodeName.IEquals('LI')){this._OutdentWithSelection(C,A);return true;};var F=C.parentNode;var G=B.nodeName.toLowerCase();if (FCKListsLib.EmptyElements[G]!=null||G=='table'){FCKDomTools.RemoveNode(B);D=true;}else{FCKDomTools.RemoveNode(C);while (F.innerHTML.Trim().length==0){var H=F.parentNode;H.removeChild(F);F=H;};FCKDomTools.LTrimNode(C);FCKDomTools.RTrimNode(B);A.SetStart(B,2,true);A.Collapse(true);var I=A.CreateBookmark(true);if (!C.tagName.IEquals(['TABLE'])) FCKDomTools.MoveChildren(C,B);A.SelectBookmark(I);D=true;}};return D;};FCKEnterKey.prototype.DoDelete=function(){FCKUndo.SaveUndoStep();var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (FCKBrowserInfo.IsIE&&this._CheckIsAllContentsIncluded(B,this.Window.document.body)){this._FixIESelectAllBug(B);return true;};if (B.CheckIsCollapsed()&&B.CheckEndOfBlock(FCKBrowserInfo.IsGeckoLike)){var C=B.StartBlock;var D=FCKTools.GetElementAscensor(C,'td');var E=FCKDomTools.GetNextSourceElement(C,true,[B.StartBlockLimit.nodeName],['UL','OL','TR'],true);if (D){var F=FCKTools.GetElementAscensor(E,'td');if (F!=D) return true;};A=this._ExecuteBackspace(B,C,E);};B.Release();return A;};FCKEnterKey.prototype.DoTab=function(){var A=new FCKDomRange(this.Window);A.MoveToSelection();var B=A._Range.startContainer;while (B){if (B.nodeType==1){var C=B.tagName.toLowerCase();if (C=="tr"||C=="td"||C=="th"||C=="tbody"||C=="table") return false;else break;};B=B.parentNode;};if (this.TabText){A.DeleteContents();A.InsertNode(this.Window.document.createTextNode(this.TabText));A.Collapse(false);A.Select();};return true;};FCKEnterKey.prototype._ExecuteEnterBlock=function(A,B){var C=B||new FCKDomRange(this.Window);var D=C.SplitBlock(A);if (D){var E=D.PreviousBlock;var F=D.NextBlock;var G=D.WasStartOfBlock;var H=D.WasEndOfBlock;if (F){if (F.parentNode.nodeName.IEquals('li')){FCKDomTools.BreakParent(F,F.parentNode);FCKDomTools.MoveNode(F,F.nextSibling,true);}}else if (E&&E.parentNode.nodeName.IEquals('li')){FCKDomTools.BreakParent(E,E.parentNode);C.MoveToElementEditStart(E.nextSibling);FCKDomTools.MoveNode(E,E.previousSibling);};if (!G&&!H){if (F.nodeName.IEquals('li')&&F.firstChild&&F.firstChild.nodeName.IEquals(['ul','ol'])) F.insertBefore(FCKTools.GetElementDocument(F).createTextNode('\xa0'),F.firstChild);if (F) C.MoveToElementEditStart(F);}else{if (G&&H&&E.tagName.toUpperCase()=='LI'){C.MoveToElementStart(E);this._OutdentWithSelection(E,C);C.Release();return true;};var I;if (E){var J=E.tagName.toUpperCase();if (!this._HasShift&&!(/^H[1-6]$/).test(J)){I=FCKDomTools.CloneElement(E);}}else if (F) I=FCKDomTools.CloneElement(F);if (!I) I=this.Window.document.createElement(A);var K=D.ElementPath;if (K){for (var i=0,len=K.Elements.length;i<len;i++){var L=K.Elements[i];if (L==K.Block||L==K.BlockLimit) break;if (FCKListsLib.InlineChildReqElements[L.nodeName.toLowerCase()]){L=FCKDomTools.CloneElement(L);FCKDomTools.MoveChildren(I,L);I.appendChild(L);}}};if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(I);C.InsertNode(I);if (FCKBrowserInfo.IsIE){C.MoveToElementEditStart(I);C.Select();};C.MoveToElementEditStart(G&&!H?F:I);};if (FCKBrowserInfo.IsGeckoLike){if (F){var M=this.Window.document.createElement('span');M.innerHTML='&nbsp;';C.InsertNode(M);FCKDomTools.ScrollIntoView(M,false);C.DeleteContents();}else{FCKDomTools.ScrollIntoView(F||I,false);}};C.Select();};C.Release();return true;};FCKEnterKey.prototype._ExecuteEnterBr=function(A){var B=new FCKDomRange(this.Window);B.MoveToSelection();if (B.StartBlockLimit==B.EndBlockLimit){B.DeleteContents();B.MoveToSelection();var C=B.CheckStartOfBlock();var D=B.CheckEndOfBlock();var E=B.StartBlock?B.StartBlock.tagName.toUpperCase():'';var F=this._HasShift;var G=false;if (!F&&E=='LI') return this._ExecuteEnterBlock(null,B);if (!F&&D&&(/^H[1-6]$/).test(E)){FCKDomTools.InsertAfterNode(B.StartBlock,this.Window.document.createElement('br'));if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(B.StartBlock,this.Window.document.createTextNode(''));B.SetStart(B.StartBlock.nextSibling,FCKBrowserInfo.IsIE?3:1);}else{var H;G=E.IEquals('pre');if (G) H=this.Window.document.createTextNode(FCKBrowserInfo.IsIE?'\r':'\n');else H=this.Window.document.createElement('br');B.InsertNode(H);if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(H,this.Window.document.createTextNode(''));if (D&&FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(H.parentNode);if (FCKBrowserInfo.IsIE) B.SetStart(H,4);else B.SetStart(H.nextSibling,1);if (!FCKBrowserInfo.IsIE){var I=null;if (FCKBrowserInfo.IsOpera) I=this.Window.document.createElement('span');else I=this.Window.document.createElement('br');H.parentNode.insertBefore(I,H.nextSibling);FCKDomTools.ScrollIntoView(I,false);I.parentNode.removeChild(I);}};B.Collapse(true);B.Select(G);};B.Release();return true;};FCKEnterKey.prototype._OutdentWithSelection=function(A,B){var C=B.CreateBookmark();FCKListHandler.OutdentListItem(A);B.MoveToBookmark(C);B.Select();};FCKEnterKey.prototype._CheckIsAllContentsIncluded=function(A,B){var C=false;var D=false;if (A.StartContainer==B||A.StartContainer==B.firstChild) C=(A._Range.startOffset==0);if (A.EndContainer==B||A.EndContainer==B.lastChild){var E=A.EndContainer.nodeType==3?A.EndContainer.length:A.EndContainer.childNodes.length;D=(A._Range.endOffset==E);};return C&&D;};FCKEnterKey.prototype._FixIESelectAllBug=function(A){var B=this.Window.document;B.body.innerHTML='';var C;if (FCKConfig.EnterMode.IEquals(['div','p'])){C=B.createElement(FCKConfig.EnterMode);B.body.appendChild(C);}else C=B.body;A.MoveToNodeContents(C);A.Collapse(true);A.Select();A.Release();};
+var FCKDocumentProcessor={};FCKDocumentProcessor._Items=[];FCKDocumentProcessor.AppendNew=function(){var A={};this._Items.push(A);return A;};FCKDocumentProcessor.Process=function(A){var B=FCK.IsDirty();var C,i=0;while((C=this._Items[i++])) C.ProcessDocument(A);if (!B) FCK.ResetIsDirty();};var FCKDocumentProcessor_CreateFakeImage=function(A,B){var C=FCKTools.GetElementDocument(B).createElement('IMG');C.className=A;C.src=FCKConfig.BasePath+'images/spacer.gif';C.setAttribute('_fckfakelement','true',0);C.setAttribute('_fckrealelement',FCKTempBin.AddElement(B),0);return C;};if (FCKBrowserInfo.IsIE||FCKBrowserInfo.IsOpera){var FCKAnchorsProcessor=FCKDocumentProcessor.AppendNew();FCKAnchorsProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('A');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.name.length>0){if (C.innerHTML!==''){if (FCKBrowserInfo.IsIE) C.className+=' FCK__AnchorC';}else{var D=FCKDocumentProcessor_CreateFakeImage('FCK__Anchor',C.cloneNode(true));D.setAttribute('_fckanchor','true',0);C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}}}};var FCKPageBreaksProcessor=FCKDocumentProcessor.AppendNew();FCKPageBreaksProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('DIV');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.style.pageBreakAfter=='always'&&C.childNodes.length==1&&C.childNodes[0].style&&C.childNodes[0].style.display=='none'){var D=FCKDocumentProcessor_CreateFakeImage('FCK__PageBreak',C.cloneNode(true));C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}};var FCKEmbedAndObjectProcessor=(function(){var A=[];var B=function(el){var C=el.cloneNode(true);var D;var E=D=FCKDocumentProcessor_CreateFakeImage('FCK__UnknownObject',C);FCKEmbedAndObjectProcessor.RefreshView(E,el);for (var i=0;i<A.length;i++) D=A[i](el,D)||D;if (D!=E) FCKTempBin.RemoveElement(E.getAttribute('_fckrealelement'));el.parentNode.replaceChild(D,el);};var F=function(elementName,doc){var G=doc.getElementsByTagName(elementName);for (var i=G.length-1;i>=0;i--) B(G[i]);};var H=function(doc){F('object',doc);F('embed',doc);};return FCKTools.Merge(FCKDocumentProcessor.AppendNew(),{ProcessDocument:function(doc){if (FCKBrowserInfo.IsGecko) FCKTools.RunFunction(H,this,[doc]);else H(doc);},RefreshView:function(placeHolder,original){if (original.getAttribute('width')>0) placeHolder.style.width=FCKTools.ConvertHtmlSizeToStyle(original.getAttribute('width'));if (original.getAttribute('height')>0) placeHolder.style.height=FCKTools.ConvertHtmlSizeToStyle(original.getAttribute('height'));},AddCustomHandler:function(func){A.push(func);}});})();FCK.GetRealElement=function(A){var e=FCKTempBin.Elements[A.getAttribute('_fckrealelement')];if (A.getAttribute('_fckflash')){if (A.style.width.length>0) e.width=FCKTools.ConvertStyleSizeToHtml(A.style.width);if (A.style.height.length>0) e.height=FCKTools.ConvertStyleSizeToHtml(A.style.height);};return e;};if (FCKBrowserInfo.IsIE){FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByTagName('HR');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){var D=A.createElement('hr');D.mergeAttributes(C,true);FCKDomTools.InsertAfterNode(C,D);C.parentNode.removeChild(C);}}};FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByTagName('INPUT');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){if (C.type=='hidden'){var D=FCKDocumentProcessor_CreateFakeImage('FCK__InputHidden',C.cloneNode(true));D.setAttribute('_fckinputhidden','true',0);C.parentNode.insertBefore(D,C);C.parentNode.removeChild(C);}}};FCKEmbedAndObjectProcessor.AddCustomHandler(function(A,B){if (!(A.nodeName.IEquals('embed')&&(A.type=='application/x-shockwave-flash'||/\.swf($|#|\?)/i.test(A.src)))) return;B.className='FCK__Flash';B.setAttribute('_fckflash','true',0);});if (FCKBrowserInfo.IsSafari){FCKDocumentProcessor.AppendNew().ProcessDocument=function(A){var B=A.getElementsByClassName?A.getElementsByClassName('Apple-style-span'):Array.prototype.filter.call(A.getElementsByTagName('span'),function(item){ return item.className=='Apple-style-span';});for (var i=B.length-1;i>=0;i--) FCKDomTools.RemoveNode(B[i],true);}};
+var FCKSelection=FCK.Selection={GetParentBlock:function(){var A=this.GetParentElement();while (A){if (FCKListsLib.BlockBoundaries[A.nodeName.toLowerCase()]) break;A=A.parentNode;};return A;},ApplyStyle:function(A){FCKStyles.ApplyStyle(new FCKStyle(A));}};
+FCKSelection.GetType=function(){try{var A=FCKSelection.GetSelection().type;if (A=='Control'||A=='Text') return A;if (this.GetSelection().createRange().parentElement) return 'Text';}catch(e){};return 'None';};FCKSelection.GetSelectedElement=function(){if (this.GetType()=='Control'){var A=this.GetSelection().createRange();if (A&&A.item) return this.GetSelection().createRange().item(0);};return null;};FCKSelection.GetParentElement=function(){switch (this.GetType()){case 'Control':var A=FCKSelection.GetSelectedElement();return A?A.parentElement:null;case 'None':return null;default:return this.GetSelection().createRange().parentElement();}};FCKSelection.GetBoundaryParentElement=function(A){switch (this.GetType()){case 'Control':var B=FCKSelection.GetSelectedElement();return B?B.parentElement:null;case 'None':return null;default:var C=FCK.EditorDocument;var D=C.selection.createRange();D.collapse(A!==false);var B=D.parentElement();return FCKTools.GetElementDocument(B)==C?B:null;}};FCKSelection.SelectNode=function(A){FCK.Focus();this.GetSelection().empty();var B;try{B=FCK.EditorDocument.body.createControlRange();B.addElement(A);B.select();}catch(e){B=FCK.EditorDocument.body.createTextRange();B.moveToElementText(A);B.select();}};FCKSelection.Collapse=function(A){FCK.Focus();if (this.GetType()=='Text'){var B=this.GetSelection().createRange();B.collapse(A==null||A===true);B.select();}};FCKSelection.HasAncestorNode=function(A){var B;if (this.GetSelection().type=="Control"){B=this.GetSelectedElement();}else{var C=this.GetSelection().createRange();B=C.parentElement();}while (B){if (B.nodeName.IEquals(A)) return true;B=B.parentNode;};return false;};FCKSelection.MoveToAncestorNode=function(A){var B,oRange;if (!FCK.EditorDocument) return null;if (this.GetSelection().type=="Control"){oRange=this.GetSelection().createRange();for (i=0;i<oRange.length;i++){if (oRange(i).parentNode){B=oRange(i).parentNode;break;}}}else{oRange=this.GetSelection().createRange();B=oRange.parentElement();}while (B&&!B.nodeName.Equals(A)) B=B.parentNode;return B;};FCKSelection.Delete=function(){var A=this.GetSelection();if (A.type.toLowerCase()!="none"){A.clear();};return A;};FCKSelection.GetSelection=function(){this.Restore();return FCK.EditorDocument.selection;};FCKSelection.Save=function(A){var B=FCK.EditorDocument;if (!B) return;if (this.locked) return;this.locked=!!A;var C=B.selection;var D;if (C){try {D=C.createRange();}catch(e) {};if (D){if (D.parentElement&&FCKTools.GetElementDocument(D.parentElement())!=B) D=null;else if (D.item&&FCKTools.GetElementDocument(D.item(0))!=B) D=null;}};this.SelectionData=D;};FCKSelection._GetSelectionDocument=function(A){var B=A.createRange();if (!B) return null;else if (B.item) return FCKTools.GetElementDocument(B.item(0));else return FCKTools.GetElementDocument(B.parentElement());};FCKSelection.Restore=function(){if (this.SelectionData){FCK.IsSelectionChangeLocked=true;try{if (String(this._GetSelectionDocument(FCK.EditorDocument.selection).body.contentEditable)=='true'){FCK.IsSelectionChangeLocked=false;return;};this.SelectionData.select();}catch (e) {};FCK.IsSelectionChangeLocked=false;}};FCKSelection.Release=function(){this.locked=false;delete this.SelectionData;};
+var FCKTableHandler={};FCKTableHandler.InsertRow=function(A){var B=FCKSelection.MoveToAncestorNode('TR');if (!B) return;var C=B.cloneNode(true);B.parentNode.insertBefore(C,B);FCKTableHandler.ClearRow(A?C:B);};FCKTableHandler.DeleteRows=function(A){if (!A){var B=FCKTableHandler.GetSelectedCells();var C=[];for (var i=0;i<B.length;i++){var D=B[i].parentNode;C[D.rowIndex]=D;};for (var i=C.length;i>=0;i--){if (C[i]) FCKTableHandler.DeleteRows(C[i]);};return;};var E=FCKTools.GetElementAscensor(A,'TABLE');if (E.rows.length==1){FCKTableHandler.DeleteTable(E);return;};A.parentNode.removeChild(A);};FCKTableHandler.DeleteTable=function(A){if (!A){A=FCKSelection.GetSelectedElement();if (!A||A.tagName!='TABLE') A=FCKSelection.MoveToAncestorNode('TABLE');};if (!A) return;FCKSelection.SelectNode(A);FCKSelection.Collapse();if (A.parentNode.childNodes.length==1) A.parentNode.parentNode.removeChild(A.parentNode);else A.parentNode.removeChild(A);};FCKTableHandler.InsertColumn=function(A){var B=null;var C=this.GetSelectedCells();if (C&&C.length) B=C[A?0:(C.length-1)];if (!B) return;var D=FCKTools.GetElementAscensor(B,'TABLE');var E=B.cellIndex;for (var i=0;i<D.rows.length;i++){var F=D.rows[i];if (F.cells.length<(E+1)) continue;B=F.cells[E].cloneNode(false);if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(B);var G=F.cells[E];F.insertBefore(B,(A?G:G.nextSibling));}};FCKTableHandler.DeleteColumns=function(A){if (!A){var B=FCKTableHandler.GetSelectedCells();for (var i=B.length;i>=0;i--){if (B[i]) FCKTableHandler.DeleteColumns(B[i]);};return;};if (!A) return;var C=FCKTools.GetElementAscensor(A,'TABLE');var D=A.cellIndex;for (var i=C.rows.length-1;i>=0;i--){var E=C.rows[i];if (D==0&&E.cells.length==1){FCKTableHandler.DeleteRows(E);continue;};if (E.cells[D]) E.removeChild(E.cells[D]);}};FCKTableHandler.InsertCell=function(A,B){var C=null;var D=this.GetSelectedCells();if (D&&D.length) C=D[B?0:(D.length-1)];if (!C) return null;var E=FCK.EditorDocument.createElement('TD');if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(E);if (!B&&C.cellIndex==C.parentNode.cells.length-1) C.parentNode.appendChild(E);else C.parentNode.insertBefore(E,B?C:C.nextSibling);return E;};FCKTableHandler.DeleteCell=function(A){if (A.parentNode.cells.length==1){FCKTableHandler.DeleteRows(A.parentNode);return;};A.parentNode.removeChild(A);};FCKTableHandler.DeleteCells=function(){var A=FCKTableHandler.GetSelectedCells();for (var i=A.length-1;i>=0;i--){FCKTableHandler.DeleteCell(A[i]);}};FCKTableHandler._MarkCells=function(A,B){for (var i=0;i<A.length;i++) A[i][B]=true;};FCKTableHandler._UnmarkCells=function(A,B){for (var i=0;i<A.length;i++){FCKDomTools.ClearElementJSProperty(A[i],B);}};FCKTableHandler._ReplaceCellsByMarker=function(A,B,C){for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){if (A[i][j][B]) A[i][j]=C;}}};FCKTableHandler._GetMarkerGeometry=function(A,B,C,D){var E=0;var F=0;var G=0;var H=0;for (var i=C;A[B][i]&&A[B][i][D];i++) E++;for (var i=C-1;A[B][i]&&A[B][i][D];i--){E++;G++;};for (var i=B;A[i]&&A[i][C]&&A[i][C][D];i++) F++;for (var i=B-1;A[i]&&A[i][C]&&A[i][C][D];i--){F++;H++;};return { 'width':E,'height':F,'x':G,'y':H };};FCKTableHandler.CheckIsSelectionRectangular=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length<1) return false;for (var i=0;i<A.length;i++){if (A[i].parentNode.parentNode!=A[0].parentNode.parentNode) return false;};this._MarkCells(A,'_CellSelected');var B=this._CreateTableMap(A[0]);var C=A[0].parentNode.rowIndex;var D=this._GetCellIndexSpan(B,C,A[0]);var E=this._GetMarkerGeometry(B,C,D,'_CellSelected');var F=D-E.x;var G=C-E.y;if (E.width>=E.height){for (D=F;D<F+E.width;D++){C=G+(D-F) % E.height;if (!B[C]||!B[C][D]){this._UnmarkCells(A,'_CellSelected');return false;};var g=this._GetMarkerGeometry(B,C,D,'_CellSelected');if (g.width!=E.width||g.height!=E.height){this._UnmarkCells(A,'_CellSelected');return false;}}}else{for (C=G;C<G+E.height;C++){D=F+(C-G) % E.width;if (!B[C]||!B[C][D]){this._UnmarkCells(A,'_CellSelected');return false;};var g=this._GetMarkerGeometry(B,C,D,'_CellSelected');if (g.width!=E.width||g.height!=E.height){this._UnmarkCells(A,'_CellSelected');return false;}}};this._UnmarkCells(A,'_CellSelected');return true;};FCKTableHandler.MergeCells=function(){var A=this.GetSelectedCells();if (A.length<2) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);this._MarkCells(A,'_SelectedCells');var F=this._GetMarkerGeometry(C,D,E,'_SelectedCells');var G=E-F.x;var H=D-F.y;var I=FCKTools.GetElementDocument(B).createDocumentFragment();for (var i=0;i<F.height;i++){var J=0;for (var j=0;j<F.width;j++){var K=C[H+i][G+j];while (K.childNodes.length>0){var L=K.removeChild(K.firstChild);if (L.nodeType!=1||(L.getAttribute('type',2)!='_moz'&&L.getAttribute('_moz_dirty')!=null)){I.appendChild(L);J++;}}};if (J>0) I.appendChild(FCK.EditorDocument.createElement('br'));};this._ReplaceCellsByMarker(C,'_SelectedCells',B);this._UnmarkCells(A,'_SelectedCells');this._InstallTableMap(C,B.parentNode.parentNode.parentNode);B.appendChild(I);if (FCKBrowserInfo.IsGeckoLike&&(!B.firstChild)) FCKTools.AppendBogusBr(B);this._MoveCaretToCell(B,false);};FCKTableHandler.MergeRight=function(){var A=this.GetMergeRightTarget();if (A==null) return;var B=A.refCell;var C=A.tableMap;var D=A.nextCell;var E=FCK.EditorDocument.createDocumentFragment();while (D&&D.childNodes&&D.childNodes.length>0) E.appendChild(D.removeChild(D.firstChild));D.parentNode.removeChild(D);B.appendChild(E);this._MarkCells([D],'_Replace');this._ReplaceCellsByMarker(C,'_Replace',B);this._InstallTableMap(C,B.parentNode.parentNode.parentNode);this._MoveCaretToCell(B,false);};FCKTableHandler.MergeDown=function(){var A=this.GetMergeDownTarget();if (A==null) return;var B=A.refCell;var C=A.tableMap;var D=A.nextCell;var E=FCKTools.GetElementDocument(B).createDocumentFragment();while (D&&D.childNodes&&D.childNodes.length>0) E.appendChild(D.removeChild(D.firstChild));if (E.firstChild) E.insertBefore(FCK.EditorDocument.createElement('br'),E.firstChild);B.appendChild(E);this._MarkCells([D],'_Replace');this._ReplaceCellsByMarker(C,'_Replace',B);this._InstallTableMap(C,B.parentNode.parentNode.parentNode);this._MoveCaretToCell(B,false);};FCKTableHandler.HorizontalSplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=FCKTableHandler._GetCellIndexSpan(C,D,B);var F=isNaN(B.colSpan)?1:B.colSpan;if (F>1){var G=Math.ceil(F/2);var H=FCK.EditorDocument.createElement(B.nodeName);if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(H);var I=E+G;var J=E+F;var K=isNaN(B.rowSpan)?1:B.rowSpan;for (var r=D;r<D+K;r++){for (var i=I;i<J;i++) C[r][i]=H;}}else{var L=[];for (var i=0;i<C.length;i++){var M=C[i].slice(0,E);if (C[i].length<=E){L.push(M);continue;};if (C[i][E]==B){M.push(B);M.push(FCK.EditorDocument.createElement(B.nodeName));if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(M[M.length-1]);}else{M.push(C[i][E]);M.push(C[i][E]);};for (var j=E+1;j<C[i].length;j++) M.push(C[i][j]);L.push(M);};C=L;};this._InstallTableMap(C,B.parentNode.parentNode.parentNode);};FCKTableHandler.VerticalSplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=FCKTableHandler._GetCellIndexSpan(C,D,B);var F=isNaN(B.colSpan)?1:B.colSpan;var G=B.rowSpan;if (isNaN(G)) G=1;if (G>1){B.rowSpan=Math.ceil(G/2);var H=D+Math.ceil(G/2);var I=C[H];var J=null;for (var i=E+1;i<I.length;i++){if (I[i].parentNode.rowIndex==H){J=I[i];break;}};var K=FCK.EditorDocument.createElement(B.nodeName);K.rowSpan=Math.floor(G/2);if (F>1) K.colSpan=F;if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(K);B.parentNode.parentNode.parentNode.rows[H].insertBefore(K,J);}else{var L=B.parentNode.sectionRowIndex+1;var M=FCK.EditorDocument.createElement('tr');var N=B.parentNode.parentNode;if (N.rows.length>L) N.insertBefore(M,N.rows[L]);else N.appendChild(M);for (var i=0;i<C[D].length;){var O=C[D][i].colSpan;if (isNaN(O)||O<1) O=1;if (i==E){i+=O;continue;};var P=C[D][i].rowSpan;if (isNaN(P)) P=1;C[D][i].rowSpan=P+1;i+=O;};var K=FCK.EditorDocument.createElement(B.nodeName);if (F>1) K.colSpan=F;if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(K);M.appendChild(K);}};FCKTableHandler._GetCellIndexSpan=function(A,B,C){if (A.length<B+1) return null;var D=A[B];for (var c=0;c<D.length;c++){if (D[c]==C) return c;};return null;};FCKTableHandler._GetCellLocation=function(A,B){for (var i=0;i<A.length;i++){for (var c=0;c<A[i].length;c++){if (A[i][c]==B) return [i,c];}};return null;};FCKTableHandler._CreateTableMap=function(A){var B=(A.nodeName=='TABLE'?A:A.parentNode.parentNode.parentNode);var C=B.rows;var r=-1;var D=[];for (var i=0;i<C.length;i++){r++;if (!D[r]) D[r]=[];var c=-1;for (var j=0;j<C[i].cells.length;j++){var E=C[i].cells[j];c++;while (D[r][c]) c++;var F=isNaN(E.colSpan)?1:E.colSpan;var G=isNaN(E.rowSpan)?1:E.rowSpan;for (var H=0;H<G;H++){if (!D[r+H]) D[r+H]=[];for (var I=0;I<F;I++){D[r+H][c+I]=C[i].cells[j];}};c+=F-1;}};return D;};FCKTableHandler._InstallTableMap=function(A,B){var C=FCKBrowserInfo.IsIE?"_fckrowspan":"rowSpan";for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];if (D.parentNode) D.parentNode.removeChild(D);D.colSpan=D[C]=1;}};var E=0;for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];if (!D) continue;if (j>E) E=j;if (D._colScanned===true) continue;if (A[i][j-1]==D) D.colSpan++;if (A[i][j+1]!=D) D._colScanned=true;}};for (var i=0;i<=E;i++){for (var j=0;j<A.length;j++){if (!A[j]) continue;var D=A[j][i];if (!D||D._rowScanned===true) continue;if (A[j-1]&&A[j-1][i]==D) D[C]++;if (!A[j+1]||A[j+1][i]!=D) D._rowScanned=true;}};for (var i=0;i<A.length;i++){for (var j=0;j<A[i].length;j++){var D=A[i][j];FCKDomTools.ClearElementJSProperty(D,'_colScanned');FCKDomTools.ClearElementJSProperty(D,'_rowScanned');}};for (var i=0;i<A.length;i++){var I=FCK.EditorDocument.createElement('tr');for (var j=0;j<A[i].length;){var D=A[i][j];if (A[i-1]&&A[i-1][j]==D){j+=D.colSpan;continue;};I.appendChild(D);if (C!='rowSpan'){D.rowSpan=D[C];D.removeAttribute(C);};j+=D.colSpan;if (D.colSpan==1) D.removeAttribute('colspan');if (D.rowSpan==1) D.removeAttribute('rowspan');};if (FCKBrowserInfo.IsIE){B.rows[i].replaceNode(I);}else{B.rows[i].innerHTML='';FCKDomTools.MoveChildren(I,B.rows[i]);}}};FCKTableHandler._MoveCaretToCell=function (A,B){var C=new FCKDomRange(FCK.EditorWindow);C.MoveToNodeContents(A);C.Collapse(B);C.Select();};FCKTableHandler.ClearRow=function(A){var B=A.cells;for (var i=0;i<B.length;i++){B[i].innerHTML='';if (FCKBrowserInfo.IsGeckoLike) FCKTools.AppendBogusBr(B[i]);}};FCKTableHandler.GetMergeRightTarget=function(){var A=this.GetSelectedCells();if (A.length!=1) return null;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);var F=E+(isNaN(B.colSpan)?1:B.colSpan);var G=C[D][F];if (!G) return null;this._MarkCells([B,G],'_SizeTest');var H=this._GetMarkerGeometry(C,D,E,'_SizeTest');var I=this._GetMarkerGeometry(C,D,F,'_SizeTest');this._UnmarkCells([B,G],'_SizeTest');if (H.height!=I.height||H.y!=I.y) return null;return { 'refCell':B,'nextCell':G,'tableMap':C };};FCKTableHandler.GetMergeDownTarget=function(){var A=this.GetSelectedCells();if (A.length!=1) return null;var B=A[0];var C=this._CreateTableMap(B);var D=B.parentNode.rowIndex;var E=this._GetCellIndexSpan(C,D,B);var F=D+(isNaN(B.rowSpan)?1:B.rowSpan);if (!C[F]) return null;var G=C[F][E];if (!G) return null;if (B.parentNode.parentNode!=G.parentNode.parentNode) return null;this._MarkCells([B,G],'_SizeTest');var H=this._GetMarkerGeometry(C,D,E,'_SizeTest');var I=this._GetMarkerGeometry(C,F,E,'_SizeTest');this._UnmarkCells([B,G],'_SizeTest');if (H.width!=I.width||H.x!=I.x) return null;return { 'refCell':B,'nextCell':G,'tableMap':C };};
+FCKTableHandler.GetSelectedCells=function(){if (FCKSelection.GetType()=='Control'){var A=FCKSelection.MoveToAncestorNode(['TD','TH']);return A?[A]:[];};var B=[];var C=FCKSelection.GetSelection().createRange();var D=FCKSelection.GetParentElement();if (D&&D.tagName.Equals('TD','TH')) B[0]=D;else{D=FCKSelection.MoveToAncestorNode('TABLE');if (D){for (var i=0;i<D.cells.length;i++){var E=FCK.EditorDocument.body.createTextRange();E.moveToElementText(D.cells[i]);if (C.inRange(E)||(C.compareEndPoints('StartToStart',E)>=0&&C.compareEndPoints('StartToEnd',E)<=0)||(C.compareEndPoints('EndToStart',E)>=0&&C.compareEndPoints('EndToEnd',E)<=0)){B[B.length]=D.cells[i];}}}};return B;};
+var FCKXml=function(){this.Error=false;};FCKXml.GetAttribute=function(A,B,C){var D=A.attributes.getNamedItem(B);return D?D.value:C;};FCKXml.TransformToObject=function(A){if (!A) return null;var B={};var C=A.attributes;for (var i=0;i<C.length;i++){var D=C[i];B[D.name]=D.value;};var E=A.childNodes;for (i=0;i<E.length;i++){var F=E[i];if (F.nodeType==1){var G='$'+F.nodeName;var H=B[G];if (!H) H=B[G]=[];H.push(this.TransformToObject(F));}};return B;};
+FCKXml.prototype={LoadUrl:function(A){this.Error=false;var B=FCKTools.CreateXmlObject('XmlHttp');if (!B){this.Error=true;return;};B.open("GET",A,false);B.send(null);if (B.status==200||B.status==304||(B.status==0&&B.readyState==4)){this.DOMDocument=B.responseXML;if (!this.DOMDocument||this.DOMDocument.firstChild==null){this.DOMDocument=FCKTools.CreateXmlObject('DOMDocument');this.DOMDocument.async=false;this.DOMDocument.resolveExternals=false;this.DOMDocument.loadXML(B.responseText);}}else{this.DOMDocument=null;};if (this.DOMDocument==null||this.DOMDocument.firstChild==null){this.Error=true;if (window.confirm('Error loading "'+A+'"\r\nDo you want to see more info?')) alert('URL requested: "'+A+'"\r\nServer response:\r\nStatus: '+B.status+'\r\nResponse text:\r\n'+B.responseText);}},SelectNodes:function(A,B){if (this.Error) return [];if (B) return B.selectNodes(A);else return this.DOMDocument.selectNodes(A);},SelectSingleNode:function(A,B){if (this.Error) return null;if (B) return B.selectSingleNode(A);else return this.DOMDocument.selectSingleNode(A);}};
+var FCKNamedCommand=function(A){this.Name=A;};FCKNamedCommand.prototype.Execute=function(){FCK.ExecuteNamedCommand(this.Name);};FCKNamedCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState(this.Name);};
+var FCKStyleCommand=function(){};FCKStyleCommand.prototype={Name:'Style',Execute:function(A,B){FCKUndo.SaveUndoStep();if (B.Selected) FCK.Styles.RemoveStyle(B.Style);else FCK.Styles.ApplyStyle(B.Style);FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorDocument) return -1;if (FCKSelection.GetType()=='Control'){var A=FCKSelection.GetSelectedElement();if (!A||!FCKStyles.CheckHasObjectStyle(A.nodeName.toLowerCase())) return -1;};return 0;}};
+var FCKDialogCommand=function(A,B,C,D,E,F,G,H){this.Name=A;this.Title=B;this.Url=C;this.Width=D;this.Height=E;this.CustomValue=H;this.GetStateFunction=F;this.GetStateParam=G;this.Resizable=false;};FCKDialogCommand.prototype.Execute=function(){FCKDialog.OpenDialog('FCKDialog_'+this.Name,this.Title,this.Url,this.Width,this.Height,this.CustomValue,this.Resizable);};FCKDialogCommand.prototype.GetState=function(){if (this.GetStateFunction) return this.GetStateFunction(this.GetStateParam);else return FCK.EditMode==0?0:-1;};var FCKUndefinedCommand=function(){this.Name='Undefined';};FCKUndefinedCommand.prototype.Execute=function(){alert(FCKLang.NotImplemented);};FCKUndefinedCommand.prototype.GetState=function(){return 0;};var FCKFormatBlockCommand=function(){};FCKFormatBlockCommand.prototype={Name:'FormatBlock',Execute:FCKStyleCommand.prototype.Execute,GetState:function(){return FCK.EditorDocument?0:-1;}};var FCKFontNameCommand=function(){};FCKFontNameCommand.prototype={Name:'FontName',Execute:FCKStyleCommand.prototype.Execute,GetState:FCKFormatBlockCommand.prototype.GetState};var FCKFontSizeCommand=function(){};FCKFontSizeCommand.prototype={Name:'FontSize',Execute:FCKStyleCommand.prototype.Execute,GetState:FCKFormatBlockCommand.prototype.GetState};var FCKPreviewCommand=function(){this.Name='Preview';};FCKPreviewCommand.prototype.Execute=function(){FCK.Preview();};FCKPreviewCommand.prototype.GetState=function(){return 0;};var FCKSaveCommand=function(){this.Name='Save';};FCKSaveCommand.prototype.Execute=function(){var A=FCK.GetParentForm();if (typeof(A.onsubmit)=='function'){var B=A.onsubmit();if (B!=null&&B===false) return;};if (typeof(A.submit)=='function') A.submit();else A.submit.click();};FCKSaveCommand.prototype.GetState=function(){return 0;};var FCKNewPageCommand=function(){this.Name='NewPage';};FCKNewPageCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();FCK.SetData('');FCKUndo.Typing=true;FCK.Focus();};FCKNewPageCommand.prototype.GetState=function(){return 0;};var FCKSourceCommand=function(){this.Name='Source';};FCKSourceCommand.prototype.Execute=function(){if (FCKConfig.SourcePopup){var A=FCKConfig.ScreenWidth*0.65;var B=FCKConfig.ScreenHeight*0.65;FCKDialog.OpenDialog('FCKDialog_Source',FCKLang.Source,'dialog/fck_source.html',A,B,null,true);}else FCK.SwitchEditMode();};FCKSourceCommand.prototype.GetState=function(){return (FCK.EditMode==0?0:1);};var FCKUndoCommand=function(){this.Name='Undo';};FCKUndoCommand.prototype.Execute=function(){FCKUndo.Undo();};FCKUndoCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return (FCKUndo.CheckUndoState()?0:-1);};var FCKRedoCommand=function(){this.Name='Redo';};FCKRedoCommand.prototype.Execute=function(){FCKUndo.Redo();};FCKRedoCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return (FCKUndo.CheckRedoState()?0:-1);};var FCKPageBreakCommand=function(){this.Name='PageBreak';};FCKPageBreakCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();var e=FCK.EditorDocument.createElement('DIV');e.style.pageBreakAfter='always';e.innerHTML='<span style="DISPLAY:none">&nbsp;</span>';var A=FCKDocumentProcessor_CreateFakeImage('FCK__PageBreak',e);var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.SplitBlock();B.InsertNode(A);FCK.Events.FireEvent('OnSelectionChange');};FCKPageBreakCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};var FCKUnlinkCommand=function(){this.Name='Unlink';};FCKUnlinkCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();if (FCKBrowserInfo.IsGeckoLike){var A=FCK.Selection.MoveToAncestorNode('A');if (A) FCKTools.RemoveOuterTags(A);return;};FCK.ExecuteNamedCommand(this.Name);};FCKUnlinkCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;var A=FCK.GetNamedCommandState(this.Name);if (A==0&&FCK.EditMode==0){var B=FCKSelection.MoveToAncestorNode('A');var C=(B&&B.name.length>0&&B.href.length==0);if (C) A=-1;};return A;};var FCKVisitLinkCommand=function(){this.Name='VisitLink';};FCKVisitLinkCommand.prototype={GetState:function(){if (FCK.EditMode!=0) return -1;var A=FCK.GetNamedCommandState('Unlink');if (A==0){var B=FCKSelection.MoveToAncestorNode('A');if (!B.href) A=-1;};return A;},Execute:function(){var A=FCKSelection.MoveToAncestorNode('A');var B=A.getAttribute('_fcksavedurl')||A.getAttribute('href',2);if (!/:\/\//.test(B)){var C=FCKConfig.BaseHref;var D=FCK.GetInstanceObject('parent');if (!C){C=D.document.location.href;C=C.substring(0,C.lastIndexOf('/')+1);};if (/^\//.test(B)){try{C=C.match(/^.*:\/\/+[^\/]+/)[0];}catch (e){C=D.document.location.protocol+'://'+D.parent.document.location.host;}};B=C+B;};if (!window.open(B,'_blank')) alert(FCKLang.VisitLinkBlocked);}};var FCKSelectAllCommand=function(){this.Name='SelectAll';};FCKSelectAllCommand.prototype.Execute=function(){if (FCK.EditMode==0){FCK.ExecuteNamedCommand('SelectAll');}else{var A=FCK.EditingArea.Textarea;if (FCKBrowserInfo.IsIE){A.createTextRange().execCommand('SelectAll');}else{A.selectionStart=0;A.selectionEnd=A.value.length;};A.focus();}};FCKSelectAllCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};var FCKPasteCommand=function(){this.Name='Paste';};FCKPasteCommand.prototype={Execute:function(){if (FCKBrowserInfo.IsIE) FCK.Paste();else FCK.ExecuteNamedCommand('Paste');},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Paste');}};var FCKRuleCommand=function(){this.Name='Rule';};FCKRuleCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();FCK.InsertElement('hr');},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('InsertHorizontalRule');}};var FCKCutCopyCommand=function(A){this.Name=A?'Cut':'Copy';};FCKCutCopyCommand.prototype={Execute:function(){var A=false;if (FCKBrowserInfo.IsIE){var B=function(){A=true;};var C='on'+this.Name.toLowerCase();FCK.EditorDocument.body.attachEvent(C,B);FCK.ExecuteNamedCommand(this.Name);FCK.EditorDocument.body.detachEvent(C,B);}else{try{FCK.ExecuteNamedCommand(this.Name);A=true;}catch(e){}};if (!A) alert(FCKLang['PasteError'+this.Name]);},GetState:function(){return FCK.EditMode!=0?-1:FCK.GetNamedCommandState('Cut');}};var FCKAnchorDeleteCommand=function(){this.Name='AnchorDelete';};FCKAnchorDeleteCommand.prototype={Execute:function(){if (FCK.Selection.GetType()=='Control'){FCK.Selection.Delete();}else{var A=FCK.Selection.GetSelectedElement();if (A){if (A.tagName=='IMG'&&A.getAttribute('_fckanchor')) oAnchor=FCK.GetRealElement(A);else A=null;};if (!A){oAnchor=FCK.Selection.MoveToAncestorNode('A');if (oAnchor) FCK.Selection.SelectNode(oAnchor);};if (oAnchor.href.length!=0){oAnchor.removeAttribute('name');if (FCKBrowserInfo.IsIE) oAnchor.className=oAnchor.className.replace(FCKRegexLib.FCK_Class,'');return;};if (A){A.parentNode.removeChild(A);return;};if (oAnchor.innerHTML.length==0){oAnchor.parentNode.removeChild(oAnchor);return;};FCKTools.RemoveOuterTags(oAnchor);};if (FCKBrowserInfo.IsGecko) FCK.Selection.Collapse(true);},GetState:function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Unlink');}};var FCKDeleteDivCommand=function(){};FCKDeleteDivCommand.prototype={GetState:function(){if (FCK.EditMode!=0) return -1;var A=FCKSelection.GetParentElement();var B=new FCKElementPath(A);return B.BlockLimit&&B.BlockLimit.nodeName.IEquals('div')?0:-1;},Execute:function(){FCKUndo.SaveUndoStep();var A=FCKDomTools.GetSelectedDivContainers();var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.CreateBookmark();for (var i=0;i<A.length;i++) FCKDomTools.RemoveNode(A[i],true);B.MoveToBookmark(C);B.Select();}};var FCKNbsp=function(){this.Name='Non Breaking Space';};FCKNbsp.prototype={Execute:function(){FCK.InsertHtml('&nbsp;');},GetState:function(){return (FCK.EditMode!=0?-1:0);}};
+var FCKShowBlockCommand=function(A,B){this.Name=A;if (B!=undefined) this._SavedState=B;else this._SavedState=null;};FCKShowBlockCommand.prototype.Execute=function(){var A=this.GetState();if (A==-1) return;var B=FCK.EditorDocument.body;if (A==1) B.className=B.className.replace(/(^| )FCK__ShowBlocks/g,'');else B.className+=' FCK__ShowBlocks';if (FCKBrowserInfo.IsIE){try{FCK.EditorDocument.selection.createRange().select();}catch (e){}}else{var C=FCK.EditorWindow.getSelection().focusNode;if (C){if (C.nodeType!=1) C=C.parentNode;FCKDomTools.ScrollIntoView(C,false);}};FCK.Events.FireEvent('OnSelectionChange');};FCKShowBlockCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;if (!FCK.EditorDocument) return 0;if (/FCK__ShowBlocks(?:\s|$)/.test(FCK.EditorDocument.body.className)) return 1;return 0;};FCKShowBlockCommand.prototype.SaveState=function(){this._SavedState=this.GetState();};FCKShowBlockCommand.prototype.RestoreState=function(){if (this._SavedState!=null&&this.GetState()!=this._SavedState) this.Execute();};
+var FCKSpellCheckCommand=function(){this.Name='SpellCheck';this.IsEnabled=true;};FCKSpellCheckCommand.prototype.Execute=function(){switch (FCKConfig.SpellChecker){case 'ieSpell':this._RunIeSpell();break;case 'SpellerPages':FCKDialog.OpenDialog('FCKDialog_SpellCheck','Spell Check','dialog/fck_spellerpages.html',440,480);break;case 'WSC':FCKDialog.OpenDialog('FCKDialog_SpellCheck','Spell Check','wsc/w.html',530,480);}};FCKSpellCheckCommand.prototype._RunIeSpell=function(){try{var A=new ActiveXObject("ieSpell.ieSpellExtension");A.CheckAllLinkedDocuments(FCK.EditorDocument);}catch(e){if(e.number==-2146827859){if (confirm(FCKLang.IeSpellDownload)) window.open(FCKConfig.IeSpellDownloadUrl,'IeSpellDownload');}else alert('Error Loading ieSpell: '+e.message+' ('+e.number+')');}};FCKSpellCheckCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return this.IsEnabled?0:-1;};
+var FCKTextColorCommand=function(A){this.Name=A=='ForeColor'?'TextColor':'BGColor';this.Type=A;var B;if (FCKBrowserInfo.IsIE) B=window;else if (FCK.ToolbarSet._IFrame) B=FCKTools.GetElementWindow(FCK.ToolbarSet._IFrame);else B=window.parent;this._Panel=new FCKPanel(B);this._Panel.AppendStyleSheet(FCKConfig.SkinEditorCSS);this._Panel.MainNode.className='FCK_Panel';this._CreatePanelBody(this._Panel.Document,this._Panel.MainNode);FCK.ToolbarSet.ToolbarItems.GetItem(this.Name).RegisterPanel(this._Panel);FCKTools.DisableSelection(this._Panel.Document.body);};FCKTextColorCommand.prototype.Execute=function(A,B,C){this._Panel.Show(A,B,C);};FCKTextColorCommand.prototype.SetColor=function(A){FCKUndo.SaveUndoStep();var B=FCKStyles.GetStyle('_FCK_'+(this.Type=='ForeColor'?'Color':'BackColor'));if (!A||A.length==0) FCK.Styles.RemoveStyle(B);else{B.SetVariable('Color',A);FCKStyles.ApplyStyle(B);};FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');};FCKTextColorCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return 0;};function FCKTextColorCommand_OnMouseOver(){this.className='ColorSelected';};function FCKTextColorCommand_OnMouseOut(){this.className='ColorDeselected';};function FCKTextColorCommand_OnClick(A,B,C){this.className='ColorDeselected';B.SetColor(C);B._Panel.Hide();};function FCKTextColorCommand_AutoOnClick(A,B){this.className='ColorDeselected';B.SetColor('');B._Panel.Hide();};function FCKTextColorCommand_MoreOnClick(A,B){this.className='ColorDeselected';B._Panel.Hide();FCKDialog.OpenDialog('FCKDialog_Color',FCKLang.DlgColorTitle,'dialog/fck_colorselector.html',410,320,FCKTools.Bind(B,B.SetColor));};FCKTextColorCommand.prototype._CreatePanelBody=function(A,B){function CreateSelectionDiv(){var C=A.createElement("DIV");C.className='ColorDeselected';FCKTools.AddEventListenerEx(C,'mouseover',FCKTextColorCommand_OnMouseOver);FCKTools.AddEventListenerEx(C,'mouseout',FCKTextColorCommand_OnMouseOut);return C;};var D=B.appendChild(A.createElement("TABLE"));D.className='ForceBaseFont';D.style.tableLayout='fixed';D.cellPadding=0;D.cellSpacing=0;D.border=0;D.width=150;var E=D.insertRow(-1).insertCell(-1);E.colSpan=8;var C=E.appendChild(CreateSelectionDiv());C.innerHTML='<table cellspacing="0" cellpadding="0" width="100%" border="0">\n <tr>\n <td><div class="ColorBoxBorder"><div class="ColorBox" style="background-color: #000000"></div></div></td>\n <td nowrap width="100%" align="center">'+FCKLang.ColorAutomatic+'</td>\n </tr>\n </table>';FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_AutoOnClick,this);if (!FCKBrowserInfo.IsIE) C.style.width='96%';var G=FCKConfig.FontColors.toString().split(',');var H=0;while (H<G.length){var I=D.insertRow(-1);for (var i=0;i<8;i++,H++){if (H<G.length){var J=G[H].split('/');var K='#'+J[0];var L=J[1]||K;};C=I.insertCell(-1).appendChild(CreateSelectionDiv());C.innerHTML='<div class="ColorBoxBorder"><div class="ColorBox" style="background-color: '+K+'"></div></div>';if (H>=G.length) C.style.visibility='hidden';else FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_OnClick,[this,L]);}};if (FCKConfig.EnableMoreFontColors){E=D.insertRow(-1).insertCell(-1);E.colSpan=8;C=E.appendChild(CreateSelectionDiv());C.innerHTML='<table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td nowrap align="center">'+FCKLang.ColorMoreColors+'</td></tr></table>';FCKTools.AddEventListenerEx(C,'click',FCKTextColorCommand_MoreOnClick,this);if (!FCKBrowserInfo.IsIE) C.style.width='96%';}};
+var FCKPastePlainTextCommand=function(){this.Name='PasteText';};FCKPastePlainTextCommand.prototype.Execute=function(){FCK.PasteAsPlainText();};FCKPastePlainTextCommand.prototype.GetState=function(){if (FCK.EditMode!=0) return -1;return FCK.GetNamedCommandState('Paste');};
+var FCKPasteWordCommand=function(){this.Name='PasteWord';};FCKPasteWordCommand.prototype.Execute=function(){FCK.PasteFromWord();};FCKPasteWordCommand.prototype.GetState=function(){if (FCK.EditMode!=0||FCKConfig.ForcePasteAsPlainText) return -1;else return FCK.GetNamedCommandState('Paste');};
+var FCKTableCommand=function(A){this.Name=A;};FCKTableCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();if (!FCKBrowserInfo.IsGecko){switch (this.Name){case 'TableMergeRight':return FCKTableHandler.MergeRight();case 'TableMergeDown':return FCKTableHandler.MergeDown();}};switch (this.Name){case 'TableInsertRowAfter':return FCKTableHandler.InsertRow(false);case 'TableInsertRowBefore':return FCKTableHandler.InsertRow(true);case 'TableDeleteRows':return FCKTableHandler.DeleteRows();case 'TableInsertColumnAfter':return FCKTableHandler.InsertColumn(false);case 'TableInsertColumnBefore':return FCKTableHandler.InsertColumn(true);case 'TableDeleteColumns':return FCKTableHandler.DeleteColumns();case 'TableInsertCellAfter':return FCKTableHandler.InsertCell(null,false);case 'TableInsertCellBefore':return FCKTableHandler.InsertCell(null,true);case 'TableDeleteCells':return FCKTableHandler.DeleteCells();case 'TableMergeCells':return FCKTableHandler.MergeCells();case 'TableHorizontalSplitCell':return FCKTableHandler.HorizontalSplitCell();case 'TableVerticalSplitCell':return FCKTableHandler.VerticalSplitCell();case 'TableDelete':return FCKTableHandler.DeleteTable();default:return alert(FCKLang.UnknownCommand.replace(/%1/g,this.Name));}};FCKTableCommand.prototype.GetState=function(){if (FCK.EditorDocument!=null&&FCKSelection.HasAncestorNode('TABLE')){switch (this.Name){case 'TableHorizontalSplitCell':case 'TableVerticalSplitCell':if (FCKTableHandler.GetSelectedCells().length==1) return 0;else return -1;case 'TableMergeCells':if (FCKTableHandler.CheckIsSelectionRectangular()&&FCKTableHandler.GetSelectedCells().length>1) return 0;else return -1;case 'TableMergeRight':return FCKTableHandler.GetMergeRightTarget()?0:-1;case 'TableMergeDown':return FCKTableHandler.GetMergeDownTarget()?0:-1;default:return 0;}}else return -1;};
+var FCKFitWindow=function(){this.Name='FitWindow';};FCKFitWindow.prototype.Execute=function(){var A=window.frameElement;var B=A.style;var C=parent;var D=C.document.documentElement;var E=C.document.body;var F=E.style;var G;var H,oEditorScrollPos;if (FCK.EditMode==0){H=new FCKDomRange(FCK.EditorWindow);H.MoveToSelection();oEditorScrollPos=FCKTools.GetScrollPosition(FCK.EditorWindow);}else{var I=FCK.EditingArea.Textarea;H=!FCKBrowserInfo.IsIE&&[I.selectionStart,I.selectionEnd];oEditorScrollPos=[I.scrollLeft,I.scrollTop];};if (!this.IsMaximized){if(FCKBrowserInfo.IsIE) C.attachEvent('onresize',FCKFitWindow_Resize);else C.addEventListener('resize',FCKFitWindow_Resize,true);this._ScrollPos=FCKTools.GetScrollPosition(C);G=A;while((G=G.parentNode)){if (G.nodeType==1){G._fckSavedStyles=FCKTools.SaveStyles(G);G.style.zIndex=FCKConfig.FloatingPanelsZIndex-1;}};if (FCKBrowserInfo.IsIE){this.documentElementOverflow=D.style.overflow;D.style.overflow='hidden';F.overflow='hidden';}else{F.overflow='hidden';F.width='0px';F.height='0px';};this._EditorFrameStyles=FCKTools.SaveStyles(A);var J=FCKTools.GetViewPaneSize(C);B.position="absolute";A.offsetLeft;B.zIndex=FCKConfig.FloatingPanelsZIndex-1;B.left="0px";B.top="0px";B.width=J.Width+"px";B.height=J.Height+"px";if (!FCKBrowserInfo.IsIE){B.borderRight=B.borderBottom="9999px solid white";B.backgroundColor="white";};C.scrollTo(0,0);var K=FCKTools.GetWindowPosition(C,A);if (K.x!=0) B.left=(-1*K.x)+"px";if (K.y!=0) B.top=(-1*K.y)+"px";this.IsMaximized=true;}else{if(FCKBrowserInfo.IsIE) C.detachEvent("onresize",FCKFitWindow_Resize);else C.removeEventListener("resize",FCKFitWindow_Resize,true);G=A;while((G=G.parentNode)){if (G._fckSavedStyles){FCKTools.RestoreStyles(G,G._fckSavedStyles);G._fckSavedStyles=null;}};if (FCKBrowserInfo.IsIE) D.style.overflow=this.documentElementOverflow;FCKTools.RestoreStyles(A,this._EditorFrameStyles);C.scrollTo(this._ScrollPos.X,this._ScrollPos.Y);this.IsMaximized=false;};FCKToolbarItems.GetItem('FitWindow').RefreshState();if (FCK.EditMode==0) FCK.EditingArea.MakeEditable();FCK.Focus();if (FCK.EditMode==0){H.Select();FCK.EditorWindow.scrollTo(oEditorScrollPos.X,oEditorScrollPos.Y);}else{if (!FCKBrowserInfo.IsIE){I.selectionStart=H[0];I.selectionEnd=H[1];};I.scrollLeft=oEditorScrollPos[0];I.scrollTop=oEditorScrollPos[1];}};FCKFitWindow.prototype.GetState=function(){if (FCKConfig.ToolbarLocation!='In') return -1;else return (this.IsMaximized?1:0);};function FCKFitWindow_Resize(){var A=FCKTools.GetViewPaneSize(parent);var B=window.frameElement.style;B.width=A.Width+'px';B.height=A.Height+'px';};
+var FCKListCommand=function(A,B){this.Name=A;this.TagName=B;};FCKListCommand.prototype={GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=FCKSelection.GetBoundaryParentElement(true);var B=A;while (B){if (B.nodeName.IEquals(['ul','ol'])) break;B=B.parentNode;};if (B&&B.nodeName.IEquals(this.TagName)) return 1;else return 0;},Execute:function(){FCKUndo.SaveUndoStep();var A=FCK.EditorDocument;var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=this.GetState();if (C==0){FCKDomTools.TrimNode(A.body);if (!A.body.firstChild){var D=A.createElement('p');A.body.appendChild(D);B.MoveToNodeContents(D);}};var E=B.CreateBookmark();var F=[];var G={};var H=new FCKDomRangeIterator(B);var I;H.ForceBrBreak=(C==0);var J=true;var K=null;while (J){while ((I=H.GetNextParagraph())){var L=new FCKElementPath(I);var M=null;var N=false;var O=L.BlockLimit;for (var i=L.Elements.length-1;i>=0;i--){var P=L.Elements[i];if (P.nodeName.IEquals(['ol','ul'])){if (O._FCK_ListGroupObject) O._FCK_ListGroupObject=null;var Q=P._FCK_ListGroupObject;if (Q) Q.contents.push(I);else{Q={ 'root':P,'contents':[I] };F.push(Q);FCKDomTools.SetElementMarker(G,P,'_FCK_ListGroupObject',Q);};N=true;break;}};if (N) continue;var R=O;if (R._FCK_ListGroupObject) R._FCK_ListGroupObject.contents.push(I);else{var Q={ 'root':R,'contents':[I] };FCKDomTools.SetElementMarker(G,R,'_FCK_ListGroupObject',Q);F.push(Q);}};if (FCKBrowserInfo.IsIE) J=false;else{if (K==null){K=[];var T=FCKSelection.GetSelection();if (T&&F.length==0) K.push(T.getRangeAt(0));for (var i=1;T&&i<T.rangeCount;i++) K.push(T.getRangeAt(i));};if (K.length<1) J=false;else{var U=FCKW3CRange.CreateFromRange(A,K.shift());B._Range=U;B._UpdateElementInfo();if (B.StartNode.nodeName.IEquals('td')) B.SetStart(B.StartNode,1);if (B.EndNode.nodeName.IEquals('td')) B.SetEnd(B.EndNode,2);H=new FCKDomRangeIterator(B);H.ForceBrBreak=(C==0);}}};var W=[];while (F.length>0){var Q=F.shift();if (C==0){if (Q.root.nodeName.IEquals(['ul','ol'])) this._ChangeListType(Q,G,W);else this._CreateList(Q,W);}else if (C==1&&Q.root.nodeName.IEquals(['ul','ol'])) this._RemoveList(Q,G);};for (var i=0;i<W.length;i++){var M=W[i];var Z=false;var a=M;while (!Z){a=a.nextSibling;if (a&&a.nodeType==3&&a.nodeValue.search(/^[\n\r\t ]*$/)==0) continue;Z=true;};if (a&&a.nodeName.IEquals(this.TagName)){a.parentNode.removeChild(a);while (a.firstChild) M.appendChild(a.removeChild(a.firstChild));};Z=false;a=M;while (!Z){a=a.previousSibling;if (a&&a.nodeType==3&&a.nodeValue.search(/^[\n\r\t ]*$/)==0) continue;Z=true;};if (a&&a.nodeName.IEquals(this.TagName)){a.parentNode.removeChild(a);while (a.lastChild) M.insertBefore(a.removeChild(a.lastChild),M.firstChild);}};FCKDomTools.ClearAllMarkers(G);B.MoveToBookmark(E);B.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},_ChangeListType:function(A,B,C){var D=FCKDomTools.ListToArray(A.root,B);var E=[];for (var i=0;i<A.contents.length;i++){var F=A.contents[i];F=FCKTools.GetElementAscensor(F,'li');if (!F||F._FCK_ListItem_Processed) continue;E.push(F);FCKDomTools.SetElementMarker(B,F,'_FCK_ListItem_Processed',true);};var G=FCKTools.GetElementDocument(A.root).createElement(this.TagName);for (var i=0;i<E.length;i++){var H=E[i]._FCK_ListArray_Index;D[H].parent=G;};var I=FCKDomTools.ArrayToList(D,B);for (var i=0;i<I.listNode.childNodes.length;i++){if (I.listNode.childNodes[i].nodeName.IEquals(this.TagName)) C.push(I.listNode.childNodes[i]);};A.root.parentNode.replaceChild(I.listNode,A.root);},_CreateList:function(A,B){var C=A.contents;var D=FCKTools.GetElementDocument(A.root);var E=[];if (C.length==1&&C[0]==A.root){var F=D.createElement('div');while (C[0].firstChild) F.appendChild(C[0].removeChild(C[0].firstChild));C[0].appendChild(F);C[0]=F;};var G=A.contents[0].parentNode;for (var i=0;i<C.length;i++) G=FCKDomTools.GetCommonParents(G,C[i].parentNode).pop();for (var i=0;i<C.length;i++){var H=C[i];while (H.parentNode){if (H.parentNode==G){E.push(H);break;};H=H.parentNode;}};if (E.length<1) return;var I=E[E.length-1].nextSibling;var J=D.createElement(this.TagName);B.push(J);while (E.length){var K=E.shift();var L=D.createDocumentFragment();while (K.firstChild) L.appendChild(K.removeChild(K.firstChild));K.parentNode.removeChild(K);var M=D.createElement('li');M.appendChild(L);J.appendChild(M);};G.insertBefore(J,I);},_RemoveList:function(A,B){var C=FCKDomTools.ListToArray(A.root,B);var D=[];for (var i=0;i<A.contents.length;i++){var E=A.contents[i];E=FCKTools.GetElementAscensor(E,'li');if (!E||E._FCK_ListItem_Processed) continue;D.push(E);FCKDomTools.SetElementMarker(B,E,'_FCK_ListItem_Processed',true);};var F=null;for (var i=0;i<D.length;i++){var G=D[i]._FCK_ListArray_Index;C[G].indent=-1;F=G;};for (var i=F+1;i<C.length;i++){if (C[i].indent>C[i-1].indent+1){var H=C[i-1].indent+1-C[i].indent;var I=C[i].indent;while (C[i]&&C[i].indent>=I){C[i].indent+=H;i++;};i--;}};var J=FCKDomTools.ArrayToList(C,B);if (A.root.nextSibling==null||A.root.nextSibling.nodeName.IEquals('br')){if (J.listNode.lastChild.nodeName.IEquals('br')) J.listNode.removeChild(J.listNode.lastChild);};A.root.parentNode.replaceChild(J.listNode,A.root);}};
+var FCKJustifyCommand=function(A){this.AlignValue=A;var B=FCKConfig.ContentLangDirection.toLowerCase();this.IsDefaultAlign=(A=='left'&&B=='ltr')||(A=='right'&&B=='rtl');var C=this._CssClassName=(function(){var D=FCKConfig.JustifyClasses;if (D){switch (A){case 'left':return D[0]||null;case 'center':return D[1]||null;case 'right':return D[2]||null;case 'justify':return D[3]||null;}};return null;})();if (C&&C.length>0) this._CssClassRegex=new RegExp('(?:^|\\s+)'+C+'(?=$|\\s)');};FCKJustifyCommand._GetClassNameRegex=function(){var A=FCKJustifyCommand._ClassRegex;if (A!=undefined) return A;var B=[];var C=FCKConfig.JustifyClasses;if (C){for (var i=0;i<4;i++){var D=C[i];if (D&&D.length>0) B.push(D);}};if (B.length>0) A=new RegExp('(?:^|\\s+)(?:'+B.join('|')+')(?=$|\\s)');else A=null;return FCKJustifyCommand._ClassRegex=A;};FCKJustifyCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();var B=this.GetState();if (B==-1) return;var C=A.CreateBookmark();var D=this._CssClassName;var E=new FCKDomRangeIterator(A);var F;while ((F=E.GetNextParagraph())){F.removeAttribute('align');if (D){var G=F.className.replace(FCKJustifyCommand._GetClassNameRegex(),'');if (B==0){if (G.length>0) G+=' ';F.className=G+D;}else if (G.length==0) FCKDomTools.RemoveAttribute(F,'class');}else{var H=F.style;if (B==0) H.textAlign=this.AlignValue;else{H.textAlign='';if (H.cssText.length==0) F.removeAttribute('style');}}};A.MoveToBookmark(C);A.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=new FCKElementPath(FCKSelection.GetBoundaryParentElement(true));var B=A.Block||A.BlockLimit;if (!B||B.nodeName.toLowerCase()=='body') return 0;var C;if (FCKBrowserInfo.IsIE) C=B.currentStyle.textAlign;else C=FCK.EditorWindow.getComputedStyle(B,'').getPropertyValue('text-align');C=C.replace(/(-moz-|-webkit-|start|auto)/i,'');if ((!C&&this.IsDefaultAlign)||C==this.AlignValue) return 1;return 0;}};
+var FCKIndentCommand=function(A,B){this.Name=A;this.Offset=B;this.IndentCSSProperty=FCKConfig.ContentLangDirection.IEquals('ltr')?'marginLeft':'marginRight';};FCKIndentCommand._InitIndentModeParameters=function(){if (FCKConfig.IndentClasses&&FCKConfig.IndentClasses.length>0){this._UseIndentClasses=true;this._IndentClassMap={};for (var i=0;i<FCKConfig.IndentClasses.length;i++) this._IndentClassMap[FCKConfig.IndentClasses[i]]=i+1;this._ClassNameRegex=new RegExp('(?:^|\\s+)('+FCKConfig.IndentClasses.join('|')+')(?=$|\\s)');}else this._UseIndentClasses=false;};FCKIndentCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=new FCKDomRange(FCK.EditorWindow);A.MoveToSelection();var B=A.CreateBookmark();var C=FCKDomTools.GetCommonParentNode(A.StartNode||A.StartContainer,A.EndNode||A.EndContainer,['ul','ol']);if (C) this._IndentList(A,C);else this._IndentBlock(A);A.MoveToBookmark(B);A.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;if (FCKIndentCommand._UseIndentClasses==undefined) FCKIndentCommand._InitIndentModeParameters();var A=FCKSelection.GetBoundaryParentElement(true);var B=FCKSelection.GetBoundaryParentElement(false);var C=FCKDomTools.GetCommonParentNode(A,B,['ul','ol']);if (C){if (this.Name.IEquals('outdent')) return 0;var D=FCKTools.GetElementAscensor(A,'li');if (!D||!D.previousSibling) return -1;return 0;};if (!FCKIndentCommand._UseIndentClasses&&this.Name.IEquals('indent')) return 0;var E=new FCKElementPath(A);var F=E.Block||E.BlockLimit;if (!F) return -1;if (FCKIndentCommand._UseIndentClasses){var G=F.className.match(FCKIndentCommand._ClassNameRegex);var H=0;if (G!=null){G=G[1];H=FCKIndentCommand._IndentClassMap[G];};if ((this.Name=='outdent'&&H==0)||(this.Name=='indent'&&H==FCKConfig.IndentClasses.length)) return -1;return 0;}else{var I=parseInt(F.style[this.IndentCSSProperty],10);if (isNaN(I)) I=0;if (I<=0) return -1;return 0;}},_IndentBlock:function(A){var B=new FCKDomRangeIterator(A);B.EnforceRealBlocks=true;A.Expand('block_contents');var C=FCKDomTools.GetCommonParents(A.StartContainer,A.EndContainer);var D=C[C.length-1];var E;while ((E=B.GetNextParagraph())){if (!(E==D||E.parentNode==D)) continue;if (FCKIndentCommand._UseIndentClasses){var F=E.className.match(FCKIndentCommand._ClassNameRegex);var G=0;if (F!=null){F=F[1];G=FCKIndentCommand._IndentClassMap[F];};if (this.Name.IEquals('outdent')) G--;else if (this.Name.IEquals('indent')) G++;G=Math.min(G,FCKConfig.IndentClasses.length);G=Math.max(G,0);var H=E.className.replace(FCKIndentCommand._ClassNameRegex,'');if (G<1) E.className=H;else E.className=(H.length>0?H+' ':'')+FCKConfig.IndentClasses[G-1];}else{var I=parseInt(E.style[this.IndentCSSProperty],10);if (isNaN(I)) I=0;I+=this.Offset;I=Math.max(I,0);I=Math.ceil(I/this.Offset)*this.Offset;E.style[this.IndentCSSProperty]=I?I+FCKConfig.IndentUnit:'';if (E.getAttribute('style')=='') E.removeAttribute('style');}}},_IndentList:function(A,B){var C=A.StartContainer;var D=A.EndContainer;while (C&&C.parentNode!=B) C=C.parentNode;while (D&&D.parentNode!=B) D=D.parentNode;if (!C||!D) return;var E=C;var F=[];var G=false;while (G==false){if (E==D) G=true;F.push(E);E=E.nextSibling;};if (F.length<1) return;var H=FCKDomTools.GetParents(B);for (var i=0;i<H.length;i++){if (H[i].nodeName.IEquals(['ul','ol'])){B=H[i];break;}};var I=this.Name.IEquals('indent')?1:-1;var J=F[0];var K=F[F.length-1];var L={};var M=FCKDomTools.ListToArray(B,L);var N=M[K._FCK_ListArray_Index].indent;for (var i=J._FCK_ListArray_Index;i<=K._FCK_ListArray_Index;i++) M[i].indent+=I;for (var i=K._FCK_ListArray_Index+1;i<M.length&&M[i].indent>N;i++) M[i].indent+=I;var O=FCKDomTools.ArrayToList(M);if (O) B.parentNode.replaceChild(O.listNode,B);FCKDomTools.ClearAllMarkers(L);}};
+var FCKBlockQuoteCommand=function(){};FCKBlockQuoteCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();var A=this.GetState();var B=new FCKDomRange(FCK.EditorWindow);B.MoveToSelection();var C=B.CreateBookmark();if (FCKBrowserInfo.IsIE){var D=B.GetBookmarkNode(C,true);var E=B.GetBookmarkNode(C,false);var F;if (D&&D.parentNode.nodeName.IEquals('blockquote')&&!D.previousSibling){F=D;while ((F=F.nextSibling)){if (FCKListsLib.BlockElements[F.nodeName.toLowerCase()]) FCKDomTools.MoveNode(D,F,true);}};if (E&&E.parentNode.nodeName.IEquals('blockquote')&&!E.previousSibling){F=E;while ((F=F.nextSibling)){if (FCKListsLib.BlockElements[F.nodeName.toLowerCase()]){if (F.firstChild==D) FCKDomTools.InsertAfterNode(D,E);else FCKDomTools.MoveNode(E,F,true);}}}};var G=new FCKDomRangeIterator(B);var H;if (A==0){var I=[];while ((H=G.GetNextParagraph())) I.push(H);if (I.length<1){para=B.Window.document.createElement(FCKConfig.EnterMode.IEquals('p')?'p':'div');B.InsertNode(para);para.appendChild(B.Window.document.createTextNode('\ufeff'));B.MoveToBookmark(C);B.MoveToNodeContents(para);B.Collapse(true);C=B.CreateBookmark();I.push(para);};var J=I[0].parentNode;var K=[];for (var i=0;i<I.length;i++){H=I[i];J=FCKDomTools.GetCommonParents(H.parentNode,J).pop();}while (J.nodeName.IEquals('table','tbody','tr','ol','ul')) J=J.parentNode;var L=null;while (I.length>0){H=I.shift();while (H.parentNode!=J) H=H.parentNode;if (H!=L) K.push(H);L=H;}while (K.length>0){H=K.shift();if (H.nodeName.IEquals('blockquote')){var M=FCKTools.GetElementDocument(H).createDocumentFragment();while (H.firstChild){M.appendChild(H.removeChild(H.firstChild));I.push(M.lastChild);};H.parentNode.replaceChild(M,H);}else I.push(H);};var N=B.Window.document.createElement('blockquote');J.insertBefore(N,I[0]);while (I.length>0){H=I.shift();N.appendChild(H);}}else if (A==1){var O=[];var P={};while ((H=G.GetNextParagraph())){var Q=null;var R=null;while (H.parentNode){if (H.parentNode.nodeName.IEquals('blockquote')){Q=H.parentNode;R=H;break;};H=H.parentNode;};if (Q&&R&&!R._fckblockquotemoveout){O.push(R);FCKDomTools.SetElementMarker(P,R,'_fckblockquotemoveout',true);}};FCKDomTools.ClearAllMarkers(P);var S=[];var T=[],P={};var U=function(N){for (var i=0;i<N.childNodes.length;i++){if (FCKListsLib.BlockElements[N.childNodes[i].nodeName.toLowerCase()]) return false;};return true;};while (O.length>0){var W=O.shift();var N=W.parentNode;if (W==W.parentNode.firstChild) N.parentNode.insertBefore(N.removeChild(W),N);else if (W==W.parentNode.lastChild) N.parentNode.insertBefore(N.removeChild(W),N.nextSibling);else FCKDomTools.BreakParent(W,W.parentNode,B);if (!N._fckbqprocessed){T.push(N);FCKDomTools.SetElementMarker(P,N,'_fckbqprocessed',true);};S.push(W);};for (var i=T.length-1;i>=0;i--){var N=T[i];if (U(N)) FCKDomTools.RemoveNode(N);};FCKDomTools.ClearAllMarkers(P);if (FCKConfig.EnterMode.IEquals('br')){while (S.length){var W=S.shift();var a=true;if (W.nodeName.IEquals('div')){var M=FCKTools.GetElementDocument(W).createDocumentFragment();var c=a&&W.previousSibling&&!FCKListsLib.BlockBoundaries[W.previousSibling.nodeName.toLowerCase()];if (a&&c) M.appendChild(FCKTools.GetElementDocument(W).createElement('br'));var d=W.nextSibling&&!FCKListsLib.BlockBoundaries[W.nextSibling.nodeName.toLowerCase()];while (W.firstChild) M.appendChild(W.removeChild(W.firstChild));if (d) M.appendChild(FCKTools.GetElementDocument(W).createElement('br'));W.parentNode.replaceChild(M,W);a=false;}}}};B.MoveToBookmark(C);B.Select();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0||!FCK.EditorWindow) return -1;var A=new FCKElementPath(FCKSelection.GetBoundaryParentElement(true));var B=A.Block||A.BlockLimit;if (!B||B.nodeName.toLowerCase()=='body') return 0;for (var i=0;i<A.Elements.length;i++){if (A.Elements[i].nodeName.IEquals('blockquote')) return 1;};return 0;}};
+var FCKCoreStyleCommand=function(A){this.Name='CoreStyle';this.StyleName='_FCK_'+A;this.IsActive=false;FCKStyles.AttachStyleStateChange(this.StyleName,this._OnStyleStateChange,this);};FCKCoreStyleCommand.prototype={Execute:function(){FCKUndo.SaveUndoStep();if (this.IsActive) FCKStyles.RemoveStyle(this.StyleName);else FCKStyles.ApplyStyle(this.StyleName);FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){if (FCK.EditMode!=0) return -1;return this.IsActive?1:0;},_OnStyleStateChange:function(A,B){this.IsActive=B;}};
+var FCKRemoveFormatCommand=function(){this.Name='RemoveFormat';};FCKRemoveFormatCommand.prototype={Execute:function(){FCKStyles.RemoveAll();FCK.Focus();FCK.Events.FireEvent('OnSelectionChange');},GetState:function(){return FCK.EditorWindow?0:-1;}};
+var FCKCommands=FCK.Commands={};FCKCommands.LoadedCommands={};FCKCommands.RegisterCommand=function(A,B){this.LoadedCommands[A]=B;};FCKCommands.GetCommand=function(A){var B=FCKCommands.LoadedCommands[A];if (B) return B;switch (A){case 'Bold':case 'Italic':case 'Underline':case 'StrikeThrough':case 'Subscript':case 'Superscript':B=new FCKCoreStyleCommand(A);break;case 'RemoveFormat':B=new FCKRemoveFormatCommand();break;case 'DocProps':B=new FCKDialogCommand('DocProps',FCKLang.DocProps,'dialog/fck_docprops.html',400,380,FCKCommands.GetFullPageState);break;case 'Templates':B=new FCKDialogCommand('Templates',FCKLang.DlgTemplatesTitle,'dialog/fck_template.html',380,450);break;case 'Link':B=new FCKDialogCommand('Link',FCKLang.DlgLnkWindowTitle,'dialog/fck_link.html',400,300);break;case 'Unlink':B=new FCKUnlinkCommand();break;case 'VisitLink':B=new FCKVisitLinkCommand();break;case 'Anchor':B=new FCKDialogCommand('Anchor',FCKLang.DlgAnchorTitle,'dialog/fck_anchor.html',370,160);break;case 'AnchorDelete':B=new FCKAnchorDeleteCommand();break;case 'BulletedList':B=new FCKDialogCommand('BulletedList',FCKLang.BulletedListProp,'dialog/fck_listprop.html?UL',370,160);break;case 'NumberedList':B=new FCKDialogCommand('NumberedList',FCKLang.NumberedListProp,'dialog/fck_listprop.html?OL',370,160);break;case 'About':B=new FCKDialogCommand('About',FCKLang.About,'dialog/fck_about.html',420,330,function(){ return 0;});break;case 'Find':B=new FCKDialogCommand('Find',FCKLang.DlgFindAndReplaceTitle,'dialog/fck_replace.html',340,230,null,null,'Find');break;case 'Replace':B=new FCKDialogCommand('Replace',FCKLang.DlgFindAndReplaceTitle,'dialog/fck_replace.html',340,230,null,null,'Replace');break;case 'Image':B=new FCKDialogCommand('Image',FCKLang.DlgImgTitle,'dialog/fck_image.html',450,390);break;case 'Flash':B=new FCKDialogCommand('Flash',FCKLang.DlgFlashTitle,'dialog/fck_flash.html',450,390);break;case 'SpecialChar':B=new FCKDialogCommand('SpecialChar',FCKLang.DlgSpecialCharTitle,'dialog/fck_specialchar.html',400,290);break;case 'Smiley':B=new FCKDialogCommand('Smiley',FCKLang.DlgSmileyTitle,'dialog/fck_smiley.html',FCKConfig.SmileyWindowWidth,FCKConfig.SmileyWindowHeight);break;case 'Table':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html',480,250);break;case 'TableProp':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html?Parent',480,250);break;case 'TableCellProp':B=new FCKDialogCommand('TableCell',FCKLang.DlgCellTitle,'dialog/fck_tablecell.html',550,240);break;case 'Style':B=new FCKStyleCommand();break;case 'FontName':B=new FCKFontNameCommand();break;case 'FontSize':B=new FCKFontSizeCommand();break;case 'FontFormat':B=new FCKFormatBlockCommand();break;case 'Source':B=new FCKSourceCommand();break;case 'Preview':B=new FCKPreviewCommand();break;case 'Save':B=new FCKSaveCommand();break;case 'NewPage':B=new FCKNewPageCommand();break;case 'PageBreak':B=new FCKPageBreakCommand();break;case 'Rule':B=new FCKRuleCommand();break;case 'Nbsp':B=new FCKNbsp();break;case 'TextColor':B=new FCKTextColorCommand('ForeColor');break;case 'BGColor':B=new FCKTextColorCommand('BackColor');break;case 'Paste':B=new FCKPasteCommand();break;case 'PasteText':B=new FCKPastePlainTextCommand();break;case 'PasteWord':B=new FCKPasteWordCommand();break;case 'JustifyLeft':B=new FCKJustifyCommand('left');break;case 'JustifyCenter':B=new FCKJustifyCommand('center');break;case 'JustifyRight':B=new FCKJustifyCommand('right');break;case 'JustifyFull':B=new FCKJustifyCommand('justify');break;case 'Indent':B=new FCKIndentCommand('indent',FCKConfig.IndentLength);break;case 'Outdent':B=new FCKIndentCommand('outdent',FCKConfig.IndentLength*-1);break;case 'Blockquote':B=new FCKBlockQuoteCommand();break;case 'CreateDiv':B=new FCKDialogCommand('CreateDiv',FCKLang.CreateDiv,'dialog/fck_div.html',380,210,null,null,true);break;case 'EditDiv':B=new FCKDialogCommand('EditDiv',FCKLang.EditDiv,'dialog/fck_div.html',380,210,null,null,false);break;case 'DeleteDiv':B=new FCKDeleteDivCommand();break;case 'TableInsertRowAfter':B=new FCKTableCommand('TableInsertRowAfter');break;case 'TableInsertRowBefore':B=new FCKTableCommand('TableInsertRowBefore');break;case 'TableDeleteRows':B=new FCKTableCommand('TableDeleteRows');break;case 'TableInsertColumnAfter':B=new FCKTableCommand('TableInsertColumnAfter');break;case 'TableInsertColumnBefore':B=new FCKTableCommand('TableInsertColumnBefore');break;case 'TableDeleteColumns':B=new FCKTableCommand('TableDeleteColumns');break;case 'TableInsertCellAfter':B=new FCKTableCommand('TableInsertCellAfter');break;case 'TableInsertCellBefore':B=new FCKTableCommand('TableInsertCellBefore');break;case 'TableDeleteCells':B=new FCKTableCommand('TableDeleteCells');break;case 'TableMergeCells':B=new FCKTableCommand('TableMergeCells');break;case 'TableMergeRight':B=new FCKTableCommand('TableMergeRight');break;case 'TableMergeDown':B=new FCKTableCommand('TableMergeDown');break;case 'TableHorizontalSplitCell':B=new FCKTableCommand('TableHorizontalSplitCell');break;case 'TableVerticalSplitCell':B=new FCKTableCommand('TableVerticalSplitCell');break;case 'TableDelete':B=new FCKTableCommand('TableDelete');break;case 'Form':B=new FCKDialogCommand('Form',FCKLang.Form,'dialog/fck_form.html',380,210);break;case 'Checkbox':B=new FCKDialogCommand('Checkbox',FCKLang.Checkbox,'dialog/fck_checkbox.html',380,200);break;case 'Radio':B=new FCKDialogCommand('Radio',FCKLang.RadioButton,'dialog/fck_radiobutton.html',380,200);break;case 'TextField':B=new FCKDialogCommand('TextField',FCKLang.TextField,'dialog/fck_textfield.html',380,210);break;case 'Textarea':B=new FCKDialogCommand('Textarea',FCKLang.Textarea,'dialog/fck_textarea.html',380,210);break;case 'HiddenField':B=new FCKDialogCommand('HiddenField',FCKLang.HiddenField,'dialog/fck_hiddenfield.html',380,190);break;case 'Button':B=new FCKDialogCommand('Button',FCKLang.Button,'dialog/fck_button.html',380,210);break;case 'Select':B=new FCKDialogCommand('Select',FCKLang.SelectionField,'dialog/fck_select.html',400,340);break;case 'ImageButton':B=new FCKDialogCommand('ImageButton',FCKLang.ImageButton,'dialog/fck_image.html?ImageButton',450,390);break;case 'SpellCheck':B=new FCKSpellCheckCommand();break;case 'FitWindow':B=new FCKFitWindow();break;case 'Undo':B=new FCKUndoCommand();break;case 'Redo':B=new FCKRedoCommand();break;case 'Copy':B=new FCKCutCopyCommand(false);break;case 'Cut':B=new FCKCutCopyCommand(true);break;case 'SelectAll':B=new FCKSelectAllCommand();break;case 'InsertOrderedList':B=new FCKListCommand('insertorderedlist','ol');break;case 'InsertUnorderedList':B=new FCKListCommand('insertunorderedlist','ul');break;case 'ShowBlocks':B=new FCKShowBlockCommand('ShowBlocks',FCKConfig.StartupShowBlocks?1:0);break;case 'Undefined':B=new FCKUndefinedCommand();break;case 'Scayt':B=FCKScayt.CreateCommand();break;case 'ScaytContext':B=FCKScayt.CreateContextCommand();break;default:if (FCKRegexLib.NamedCommands.test(A)) B=new FCKNamedCommand(A);else{alert(FCKLang.UnknownCommand.replace(/%1/g,A));return null;}};FCKCommands.LoadedCommands[A]=B;return B;};FCKCommands.GetFullPageState=function(){return FCKConfig.FullPage?0:-1;};FCKCommands.GetBooleanState=function(A){return A?-1:0;};
+var FCKPanel=function(A){this.IsRTL=(FCKLang.Dir=='rtl');this.IsContextMenu=false;this._LockCounter=0;this._Window=A||window;var B;if (FCKBrowserInfo.IsIE){this._Popup=this._Window.createPopup();var C=this._Window.document;if (FCK_IS_CUSTOM_DOMAIN&&!FCKBrowserInfo.IsIE7){C.domain=FCK_ORIGINAL_DOMAIN;document.domain=FCK_ORIGINAL_DOMAIN;};B=this.Document=this._Popup.document;if (FCK_IS_CUSTOM_DOMAIN){B.domain=FCK_RUNTIME_DOMAIN;C.domain=FCK_RUNTIME_DOMAIN;document.domain=FCK_RUNTIME_DOMAIN;};FCK.IECleanup.AddItem(this,FCKPanel_Cleanup);}else{var D=this._IFrame=this._Window.document.createElement('iframe');FCKTools.ResetStyles(D);D.src='javascript:void(0)';D.allowTransparency=true;D.frameBorder='0';D.scrolling='no';D.style.width=D.style.height='0px';FCKDomTools.SetElementStyles(D,{position:'absolute',zIndex:FCKConfig.FloatingPanelsZIndex});this._Window.document.body.appendChild(D);var E=D.contentWindow;B=this.Document=E.document;var F='';if (FCKBrowserInfo.IsSafari) F='<base href="'+window.document.location+'">';B.open();B.write('<html><head>'+F+'<\/head><body style="margin:0px;padding:0px;"><\/body><\/html>');B.close();if(FCKBrowserInfo.IsAIR) FCKAdobeAIR.Panel_Contructor(B,window.document.location);FCKTools.AddEventListenerEx(E,'focus',FCKPanel_Window_OnFocus,this);FCKTools.AddEventListenerEx(E,'blur',FCKPanel_Window_OnBlur,this);};B.dir=FCKLang.Dir;FCKTools.AddEventListener(B,'contextmenu',FCKTools.CancelEvent);this.MainNode=B.body.appendChild(B.createElement('DIV'));this.MainNode.style.cssFloat=this.IsRTL?'right':'left';};FCKPanel.prototype.AppendStyleSheet=function(A){FCKTools.AppendStyleSheet(this.Document,A);};FCKPanel.prototype.Preload=function(x,y,A){if (this._Popup) this._Popup.show(x,y,0,0,A);};FCKPanel.prototype.ResizeForSubpanel=function(A,B,C){if (!FCKBrowserInfo.IsIE7) return false;if (!this._Popup.isOpen){this.Subpanel=null;return false;};if (B==0&&C==0){if (this.Subpanel!==A) return false;this.Subpanel=null;this.IncreasedX=0;}else{this.Subpanel=A;if ((this.IncreasedX>=B)&&(this.IncreasedY>=C)) return false;this.IncreasedX=Math.max(this.IncreasedX,B);this.IncreasedY=Math.max(this.IncreasedY,C);};var x=this.ShowRect.x;var w=this.IncreasedX;if (this.IsRTL) x=x-w;var D=this.ShowRect.w+w;var E=Math.max(this.ShowRect.h,this.IncreasedY);if (this.ParentPanel) this.ParentPanel.ResizeForSubpanel(this,D,E);this._Popup.show(x,this.ShowRect.y,D,E,this.RelativeElement);return this.IsRTL;};FCKPanel.prototype.Show=function(x,y,A,B,C){var D;var E=this.MainNode;if (this._Popup){this._Popup.show(x,y,0,0,A);FCKDomTools.SetElementStyles(E,{B:B?B+'px':'',C:C?C+'px':''});D=E.offsetWidth;if (FCKBrowserInfo.IsIE7){if (this.ParentPanel&&this.ParentPanel.ResizeForSubpanel(this,D,E.offsetHeight)){FCKTools.RunFunction(this.Show,this,[x,y,A]);return;}};if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=(x*-1)+A.offsetWidth-D;};if (FCKBrowserInfo.IsIE7){this.ShowRect={x:x,y:y,w:D,h:E.offsetHeight};this.IncreasedX=0;this.IncreasedY=0;this.RelativeElement=A;};this._PopupArgs=[x,y,D,E.offsetHeight,A];this._Popup.show(x,y,D,E.offsetHeight,A);if (this.OnHide){if (this._Timer) CheckPopupOnHide.call(this,true);this._Timer=FCKTools.SetInterval(CheckPopupOnHide,100,this);}}else{if (typeof(FCK.ToolbarSet.CurrentInstance.FocusManager)!='undefined') FCK.ToolbarSet.CurrentInstance.FocusManager.Lock();if (this.ParentPanel){this.ParentPanel.Lock();FCKPanel_Window_OnBlur(null,this.ParentPanel);};if (FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsMac){this._IFrame.scrolling='';FCKTools.RunFunction(function(){ this._IFrame.scrolling='no';},this);};if (FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel&&FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel!=this) FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel.Hide(false,true);FCKDomTools.SetElementStyles(E,{B:B?B+'px':'',C:C?C+'px':''});D=E.offsetWidth;if (!B) this._IFrame.width=1;if (!C) this._IFrame.height=1;D=E.offsetWidth||E.firstChild.offsetWidth;var F=FCKTools.GetDocumentPosition(this._Window,A.nodeType==9?(FCKTools.IsStrictMode(A)?A.documentElement:A.body):A);var G=FCKDomTools.GetPositionedAncestor(this._IFrame.parentNode);if (G){var H=FCKTools.GetDocumentPosition(FCKTools.GetElementWindow(G),G);F.x-=H.x;F.y-=H.y;};if (this.IsRTL&&!this.IsContextMenu) x=(x*-1);x+=F.x;y+=F.y;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=x+A.offsetWidth-D;}else{var I=FCKTools.GetViewPaneSize(this._Window);var J=FCKTools.GetScrollPosition(this._Window);var K=I.Height+J.Y;var L=I.Width+J.X;if ((x+D)>L) x-=x+D-L;if ((y+E.offsetHeight)>K) y-=y+E.offsetHeight-K;};FCKDomTools.SetElementStyles(this._IFrame,{left:x+'px',top:y+'px'});this._IFrame.contentWindow.focus();this._IsOpened=true;var M=this;this._resizeTimer=setTimeout(function(){var N=E.offsetWidth||E.firstChild.offsetWidth;var O=E.offsetHeight;M._IFrame.style.width=N+'px';M._IFrame.style.height=O+'px';},0);FCK.ToolbarSet.CurrentInstance.GetInstanceObject('FCKPanel')._OpenedPanel=this;};FCKTools.RunFunction(this.OnShow,this);};FCKPanel.prototype.Hide=function(A,B){if (this._Popup) this._Popup.hide();else{if (!this._IsOpened||this._LockCounter>0) return;if (typeof(FCKFocusManager)!='undefined'&&!B) FCKFocusManager.Unlock();this._IFrame.style.width=this._IFrame.style.height='0px';this._IsOpened=false;if (this._resizeTimer){clearTimeout(this._resizeTimer);this._resizeTimer=null;};if (this.ParentPanel) this.ParentPanel.Unlock();if (!A) FCKTools.RunFunction(this.OnHide,this);}};FCKPanel.prototype.CheckIsOpened=function(){if (this._Popup) return this._Popup.isOpen;else return this._IsOpened;};FCKPanel.prototype.CreateChildPanel=function(){var A=this._Popup?FCKTools.GetDocumentWindow(this.Document):this._Window;var B=new FCKPanel(A);B.ParentPanel=this;return B;};FCKPanel.prototype.Lock=function(){this._LockCounter++;};FCKPanel.prototype.Unlock=function(){if (--this._LockCounter==0&&!this.HasFocus) this.Hide();};function FCKPanel_Window_OnFocus(e,A){A.HasFocus=true;};function FCKPanel_Window_OnBlur(e,A){A.HasFocus=false;if (A._LockCounter==0) FCKTools.RunFunction(A.Hide,A);};function CheckPopupOnHide(A){if (A||!this._Popup.isOpen){window.clearInterval(this._Timer);this._Timer=null;if (this._Popup&&this.ParentPanel&&!A) this.ParentPanel.ResizeForSubpanel(this,0,0);FCKTools.RunFunction(this.OnHide,this);}};function FCKPanel_Cleanup(){this._Popup=null;this._Window=null;this.Document=null;this.MainNode=null;this.RelativeElement=null;};
+var FCKIcon=function(A){var B=A?typeof(A):'undefined';switch (B){case 'number':this.Path=FCKConfig.SkinPath+'fck_strip.gif';this.Size=16;this.Position=A;break;case 'undefined':this.Path=FCK_SPACER_PATH;break;case 'string':this.Path=A;break;default:this.Path=A[0];this.Size=A[1];this.Position=A[2];}};FCKIcon.prototype.CreateIconElement=function(A){var B,eIconImage;if (this.Position){var C='-'+((this.Position-1)*this.Size)+'px';if (FCKBrowserInfo.IsIE){B=A.createElement('DIV');eIconImage=B.appendChild(A.createElement('IMG'));eIconImage.src=this.Path;eIconImage.style.top=C;}else{B=A.createElement('IMG');B.src=FCK_SPACER_PATH;B.style.backgroundPosition='0px '+C;B.style.backgroundImage='url("'+this.Path+'")';}}else{if (FCKBrowserInfo.IsIE){B=A.createElement('DIV');eIconImage=B.appendChild(A.createElement('IMG'));eIconImage.src=this.Path?this.Path:FCK_SPACER_PATH;}else{B=A.createElement('IMG');B.src=this.Path?this.Path:FCK_SPACER_PATH;}};B.className='TB_Button_Image';return B;};
+var FCKToolbarButtonUI=function(A,B,C,D,E,F){this.Name=A;this.Label=B||A;this.Tooltip=C||this.Label;this.Style=E||0;this.State=F||0;this.Icon=new FCKIcon(D);if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbarButtonUI_Cleanup);};FCKToolbarButtonUI.prototype._CreatePaddingElement=function(A){var B=A.createElement('IMG');B.className='TB_Button_Padding';B.src=FCK_SPACER_PATH;return B;};FCKToolbarButtonUI.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var C=this.MainElement=B.createElement('DIV');C.title=this.Tooltip;if (FCKBrowserInfo.IsGecko) C.onmousedown=FCKTools.CancelEvent;FCKTools.AddEventListenerEx(C,'mouseover',FCKToolbarButtonUI_OnMouseOver,this);FCKTools.AddEventListenerEx(C,'mouseout',FCKToolbarButtonUI_OnMouseOut,this);FCKTools.AddEventListenerEx(C,'click',FCKToolbarButtonUI_OnClick,this);this.ChangeState(this.State,true);if (this.Style==0&&!this.ShowArrow){C.appendChild(this.Icon.CreateIconElement(B));}else{var D=C.appendChild(B.createElement('TABLE'));D.cellPadding=0;D.cellSpacing=0;var E=D.insertRow(-1);var F=E.insertCell(-1);if (this.Style==0||this.Style==2) F.appendChild(this.Icon.CreateIconElement(B));else F.appendChild(this._CreatePaddingElement(B));if (this.Style==1||this.Style==2){F=E.insertCell(-1);F.className='TB_Button_Text';F.noWrap=true;F.appendChild(B.createTextNode(this.Label));};if (this.ShowArrow){if (this.Style!=0){E.insertCell(-1).appendChild(this._CreatePaddingElement(B));};F=E.insertCell(-1);var G=F.appendChild(B.createElement('IMG'));G.src=FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif';G.width=5;G.height=3;};F=E.insertCell(-1);F.appendChild(this._CreatePaddingElement(B));};A.appendChild(C);};FCKToolbarButtonUI.prototype.ChangeState=function(A,B){if (!B&&this.State==A) return;var e=this.MainElement;if (!e) return;switch (parseInt(A,10)){case 0:e.className='TB_Button_Off';break;case 1:e.className='TB_Button_On';break;case -1:e.className='TB_Button_Disabled';break;};this.State=A;};function FCKToolbarButtonUI_OnMouseOver(A,B){if (B.State==0) this.className='TB_Button_Off_Over';else if (B.State==1) this.className='TB_Button_On_Over';};function FCKToolbarButtonUI_OnMouseOut(A,B){if (B.State==0) this.className='TB_Button_Off';else if (B.State==1) this.className='TB_Button_On';};function FCKToolbarButtonUI_OnClick(A,B){if (B.OnClick&&B.State!=-1) B.OnClick(B);};function FCKToolbarButtonUI_Cleanup(){this.MainElement=null;};
+var FCKToolbarButton=function(A,B,C,D,E,F,G){this.CommandName=A;this.Label=B;this.Tooltip=C;this.Style=D;this.SourceView=E?true:false;this.ContextSensitive=F?true:false;if (G==null) this.IconPath=FCKConfig.SkinPath+'toolbar/'+A.toLowerCase()+'.gif';else if (typeof(G)=='number') this.IconPath=[FCKConfig.SkinPath+'fck_strip.gif',16,G];else this.IconPath=G;};FCKToolbarButton.prototype.Create=function(A){this._UIButton=new FCKToolbarButtonUI(this.CommandName,this.Label,this.Tooltip,this.IconPath,this.Style);this._UIButton.OnClick=this.Click;this._UIButton._ToolbarButton=this;this._UIButton.Create(A);};FCKToolbarButton.prototype.RefreshState=function(){var A=this._UIButton;if (!A) return;var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (B==A.State) return;A.ChangeState(B);};FCKToolbarButton.prototype.Click=function(){var A=this._ToolbarButton||this;FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(A.CommandName).Execute();};FCKToolbarButton.prototype.Enable=function(){this.RefreshState();};FCKToolbarButton.prototype.Disable=function(){this._UIButton.ChangeState(-1);};
+var FCKSpecialCombo=function(A,B,C,D,E){this.FieldWidth=B||100;this.PanelWidth=C||150;this.PanelMaxHeight=D||150;this.Label='&nbsp;';this.Caption=A;this.Tooltip=A;this.Style=2;this.Enabled=true;this.Items={};this._Panel=new FCKPanel(E||window);this._Panel.AppendStyleSheet(FCKConfig.SkinEditorCSS);this._PanelBox=this._Panel.MainNode.appendChild(this._Panel.Document.createElement('DIV'));this._PanelBox.className='SC_Panel';this._PanelBox.style.width=this.PanelWidth+'px';this._PanelBox.innerHTML='<table cellpadding="0" cellspacing="0" width="100%" style="TABLE-LAYOUT: fixed"><tr><td nowrap></td></tr></table>';this._ItemsHolderEl=this._PanelBox.getElementsByTagName('TD')[0];if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKSpecialCombo_Cleanup);};function FCKSpecialCombo_ItemOnMouseOver(){this.className+=' SC_ItemOver';};function FCKSpecialCombo_ItemOnMouseOut(){this.className=this.originalClass;};function FCKSpecialCombo_ItemOnClick(A,B,C){this.className=this.originalClass;B._Panel.Hide();B.SetLabel(this.FCKItemLabel);if (typeof(B.OnSelect)=='function') B.OnSelect(C,this);};FCKSpecialCombo.prototype.ClearItems=function (){if (this.Items) this.Items={};var A=this._ItemsHolderEl;while (A.firstChild) A.removeChild(A.firstChild);};FCKSpecialCombo.prototype.AddItem=function(A,B,C,D){var E=this._ItemsHolderEl.appendChild(this._Panel.Document.createElement('DIV'));E.className=E.originalClass='SC_Item';E.innerHTML=B;E.FCKItemLabel=C||A;E.Selected=false;if (FCKBrowserInfo.IsIE) E.style.width='100%';if (D) E.style.backgroundColor=D;FCKTools.AddEventListenerEx(E,'mouseover',FCKSpecialCombo_ItemOnMouseOver);FCKTools.AddEventListenerEx(E,'mouseout',FCKSpecialCombo_ItemOnMouseOut);FCKTools.AddEventListenerEx(E,'click',FCKSpecialCombo_ItemOnClick,[this,A]);this.Items[A.toString().toLowerCase()]=E;return E;};FCKSpecialCombo.prototype.SelectItem=function(A){if (typeof A=='string') A=this.Items[A.toString().toLowerCase()];if (A){A.className=A.originalClass='SC_ItemSelected';A.Selected=true;}};FCKSpecialCombo.prototype.SelectItemByLabel=function(A,B){for (var C in this.Items){var D=this.Items[C];if (D.FCKItemLabel==A){D.className=D.originalClass='SC_ItemSelected';D.Selected=true;if (B) this.SetLabel(A);}}};FCKSpecialCombo.prototype.DeselectAll=function(A){for (var i in this.Items){if (!this.Items[i]) continue;this.Items[i].className=this.Items[i].originalClass='SC_Item';this.Items[i].Selected=false;};if (A) this.SetLabel('');};FCKSpecialCombo.prototype.SetLabelById=function(A){A=A?A.toString().toLowerCase():'';var B=this.Items[A];this.SetLabel(B?B.FCKItemLabel:'');};FCKSpecialCombo.prototype.SetLabel=function(A){A=(!A||A.length==0)?'&nbsp;':A;if (A==this.Label) return;this.Label=A;var B=this._LabelEl;if (B){B.innerHTML=A;FCKTools.DisableSelection(B);}};FCKSpecialCombo.prototype.SetEnabled=function(A){this.Enabled=A;if (this._OuterTable) this._OuterTable.className=A?'':'SC_FieldDisabled';};FCKSpecialCombo.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var C=this._OuterTable=A.appendChild(B.createElement('TABLE'));C.cellPadding=0;C.cellSpacing=0;C.insertRow(-1);var D;var E;switch (this.Style){case 0:D='TB_ButtonType_Icon';E=false;break;case 1:D='TB_ButtonType_Text';E=false;break;case 2:E=true;break;};if (this.Caption&&this.Caption.length>0&&E){var F=C.rows[0].insertCell(-1);F.innerHTML=this.Caption;F.className='SC_FieldCaption';};var G=FCKTools.AppendElement(C.rows[0].insertCell(-1),'div');if (E){G.className='SC_Field';G.style.width=this.FieldWidth+'px';G.innerHTML='<table width="100%" cellpadding="0" cellspacing="0" style="TABLE-LAYOUT: fixed;"><tbody><tr><td class="SC_FieldLabel"><label>&nbsp;</label></td><td class="SC_FieldButton">&nbsp;</td></tr></tbody></table>';this._LabelEl=G.getElementsByTagName('label')[0];this._LabelEl.innerHTML=this.Label;}else{G.className='TB_Button_Off';G.innerHTML='<table title="'+this.Tooltip+'" class="'+D+'" cellspacing="0" cellpadding="0" border="0"><tr><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td><td class="TB_Text">'+this.Caption+'</td><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td><td class="TB_ButtonArrow"><img src="'+FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif" width="5" height="3"></td><td><img class="TB_Button_Padding" src="'+FCK_SPACER_PATH+'" /></td></tr></table>';};FCKTools.AddEventListenerEx(G,'mouseover',FCKSpecialCombo_OnMouseOver,this);FCKTools.AddEventListenerEx(G,'mouseout',FCKSpecialCombo_OnMouseOut,this);FCKTools.AddEventListenerEx(G,'click',FCKSpecialCombo_OnClick,this);FCKTools.DisableSelection(this._Panel.Document.body);};function FCKSpecialCombo_Cleanup(){this._LabelEl=null;this._OuterTable=null;this._ItemsHolderEl=null;this._PanelBox=null;if (this.Items){for (var A in this.Items) this.Items[A]=null;}};function FCKSpecialCombo_OnMouseOver(A,B){if (B.Enabled){switch (B.Style){case 0:this.className='TB_Button_On_Over';break;case 1:this.className='TB_Button_On_Over';break;case 2:this.className='SC_Field SC_FieldOver';break;}}};function FCKSpecialCombo_OnMouseOut(A,B){switch (B.Style){case 0:this.className='TB_Button_Off';break;case 1:this.className='TB_Button_Off';break;case 2:this.className='SC_Field';break;}};function FCKSpecialCombo_OnClick(e,A){if (A.Enabled){var B=A._Panel;var C=A._PanelBox;var D=A._ItemsHolderEl;var E=A.PanelMaxHeight;if (A.OnBeforeClick) A.OnBeforeClick(A);if (FCKBrowserInfo.IsIE) B.Preload(0,this.offsetHeight,this);if (D.offsetHeight>E) C.style.height=E+'px';else C.style.height='';B.Show(0,this.offsetHeight,this);}};
+var FCKToolbarSpecialCombo=function(){this.SourceView=false;this.ContextSensitive=true;this.FieldWidth=null;this.PanelWidth=null;this.PanelMaxHeight=null;};FCKToolbarSpecialCombo.prototype.DefaultLabel='';function FCKToolbarSpecialCombo_OnSelect(A,B){FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).Execute(A,B);};FCKToolbarSpecialCombo.prototype.Create=function(A){this._Combo=new FCKSpecialCombo(this.GetLabel(),this.FieldWidth,this.PanelWidth,this.PanelMaxHeight,FCKBrowserInfo.IsIE?window:FCKTools.GetElementWindow(A).parent);this._Combo.Tooltip=this.Tooltip;this._Combo.Style=this.Style;this.CreateItems(this._Combo);this._Combo.Create(A);this._Combo.CommandName=this.CommandName;this._Combo.OnSelect=FCKToolbarSpecialCombo_OnSelect;};function FCKToolbarSpecialCombo_RefreshActiveItems(A,B){A.DeselectAll();A.SelectItem(B);A.SetLabelById(B);};FCKToolbarSpecialCombo.prototype.RefreshState=function(){var A;var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (B!=-1){A=1;if (this.RefreshActiveItems) this.RefreshActiveItems(this._Combo,B);else{if (this._LastValue!==B){this._LastValue=B;if (!B||B.length==0){this._Combo.DeselectAll();this._Combo.SetLabel(this.DefaultLabel);}else FCKToolbarSpecialCombo_RefreshActiveItems(this._Combo,B);}}}else A=-1;if (A==this.State) return;if (A==-1){this._Combo.DeselectAll();this._Combo.SetLabel('');};this.State=A;this._Combo.SetEnabled(A!=-1);};FCKToolbarSpecialCombo.prototype.Enable=function(){this.RefreshState();};FCKToolbarSpecialCombo.prototype.Disable=function(){this.State=-1;this._Combo.DeselectAll();this._Combo.SetLabel('');this._Combo.SetEnabled(false);};
+var FCKToolbarStyleCombo=function(A,B){if (A===false) return;this.CommandName='Style';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultStyleLabel||'';};FCKToolbarStyleCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarStyleCombo.prototype.GetLabel=function(){return FCKLang.Style;};FCKToolbarStyleCombo.prototype.GetStyles=function(){var A={};var B=FCK.ToolbarSet.CurrentInstance.Styles.GetStyles();for (var C in B){var D=B[C];if (!D.IsCore) A[C]=D;};return A;};FCKToolbarStyleCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);FCKTools.AppendStyleString(B,FCKConfig.EditorAreaStyles);B.body.className+=' ForceBaseFont';FCKConfig.ApplyBodyAttributes(B.body);var C=this.GetStyles();for (var D in C){var E=C[D];var F=E.GetType()==2?D:FCKToolbarStyleCombo_BuildPreview(E,E.Label||D);var G=A.AddItem(D,F);G.Style=E;};A.OnBeforeClick=this.StyleCombo_OnBeforeClick;};FCKToolbarStyleCombo.prototype.RefreshActiveItems=function(A){var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Elements;for (var e=0;e<D.length;e++){for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D[e],true)){A.SetLabel(F.Label||F.Name);return;}}}};A.SetLabel(this.DefaultLabel);};FCKToolbarStyleCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B;var C;var D;var E=FCK.ToolbarSet.CurrentInstance.Selection;if (E.GetType()=='Control'){B=E.GetSelectedElement();D=B.nodeName.toLowerCase();}else{B=E.GetBoundaryParentElement(true);C=new FCKElementPath(B);};for (var i in A.Items){var F=A.Items[i];var G=F.Style;if ((D&&G.Element==D)||(!D&&G.GetType()!=2)){F.style.display='';if ((C&&G.CheckActive(C))||(!C&&G.CheckElementRemovable(B,true))) A.SelectItem(G.Name);}else F.style.display='none';}};function FCKToolbarStyleCombo_BuildPreview(A,B){var C=A.GetType();var D=[];if (C==0) D.push('<div class="BaseFont">');var E=A.Element;if (E=='bdo') E='span';D=['<',E];var F=A._StyleDesc.Attributes;if (F){for (var G in F){D.push(' ',G,'="',A.GetFinalAttributeValue(G),'"');}};if (A._GetStyleText().length>0) D.push(' style="',A.GetFinalStyleValue(),'"');D.push('>',B,'</',E,'>');if (C==0) D.push('</div>');return D.join('');};
+var FCKToolbarFontFormatCombo=function(A,B){if (A===false) return;this.CommandName='FontFormat';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.NormalLabel='Normal';this.PanelWidth=190;this.DefaultLabel=FCKConfig.DefaultFontFormatLabel||'';};FCKToolbarFontFormatCombo.prototype=new FCKToolbarStyleCombo(false);FCKToolbarFontFormatCombo.prototype.GetLabel=function(){return FCKLang.FontFormat;};FCKToolbarFontFormatCombo.prototype.GetStyles=function(){var A={};var B=FCKLang['FontFormats'].split(';');var C={p:B[0],pre:B[1],address:B[2],h1:B[3],h2:B[4],h3:B[5],h4:B[6],h5:B[7],h6:B[8],div:B[9]||(B[0]+' (DIV)')};var D=FCKConfig.FontFormats.split(';');for (var i=0;i<D.length;i++){var E=D[i];var F=FCKStyles.GetStyle('_FCK_'+E);if (F){F.Label=C[E];A['_FCK_'+E]=F;}else alert("The FCKConfig.CoreStyles['"+E+"'] setting was not found. Please check the fckconfig.js file");};return A;};FCKToolbarFontFormatCombo.prototype.RefreshActiveItems=function(A){var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Block;if (D){for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D)){A.SetLabel(F.Label);return;}}}};A.SetLabel(this.DefaultLabel);};FCKToolbarFontFormatCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B=FCK.ToolbarSet.CurrentInstance.Selection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);var D=C.Block;for (var i in A.Items){var E=A.Items[i];var F=E.Style;if (F.CheckElementRemovable(D)){A.SelectItem(E);return;}}}};
+var FCKToolbarFontsCombo=function(A,B){this.CommandName='FontName';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultFontLabel||'';};FCKToolbarFontsCombo.prototype=new FCKToolbarFontFormatCombo(false);FCKToolbarFontsCombo.prototype.GetLabel=function(){return FCKLang.Font;};FCKToolbarFontsCombo.prototype.GetStyles=function(){var A=FCKStyles.GetStyle('_FCK_FontFace');if (!A){alert("The FCKConfig.CoreStyles['Size'] setting was not found. Please check the fckconfig.js file");return {};};var B={};var C=FCKConfig.FontNames.split(';');for (var i=0;i<C.length;i++){var D=C[i].split('/');var E=D[0];var F=D[1]||E;var G=FCKTools.CloneObject(A);G.SetVariable('Font',E);G.Label=F;B[F]=G;};return B;};FCKToolbarFontsCombo.prototype.RefreshActiveItems=FCKToolbarStyleCombo.prototype.RefreshActiveItems;FCKToolbarFontsCombo.prototype.StyleCombo_OnBeforeClick=function(A){A.DeselectAll();var B=FCKSelection.GetBoundaryParentElement(true);if (B){var C=new FCKElementPath(B);for (var i in A.Items){var D=A.Items[i];var E=D.Style;if (E.CheckActive(C)){A.SelectItem(D);return;}}}};
+var FCKToolbarFontSizeCombo=function(A,B){this.CommandName='FontSize';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.DefaultLabel=FCKConfig.DefaultFontSizeLabel||'';this.FieldWidth=70;};FCKToolbarFontSizeCombo.prototype=new FCKToolbarFontFormatCombo(false);FCKToolbarFontSizeCombo.prototype.GetLabel=function(){return FCKLang.FontSize;};FCKToolbarFontSizeCombo.prototype.GetStyles=function(){var A=FCKStyles.GetStyle('_FCK_Size');if (!A){alert("The FCKConfig.CoreStyles['FontFace'] setting was not found. Please check the fckconfig.js file");return {};};var B={};var C=FCKConfig.FontSizes.split(';');for (var i=0;i<C.length;i++){var D=C[i].split('/');var E=D[0];var F=D[1]||E;var G=FCKTools.CloneObject(A);G.SetVariable('Size',E);G.Label=F;B[F]=G;};return B;};FCKToolbarFontSizeCombo.prototype.RefreshActiveItems=FCKToolbarStyleCombo.prototype.RefreshActiveItems;FCKToolbarFontSizeCombo.prototype.StyleCombo_OnBeforeClick=FCKToolbarFontsCombo.prototype.StyleCombo_OnBeforeClick;
+var FCKToolbarPanelButton=function(A,B,C,D,E){this.CommandName=A;var F;if (E==null) F=FCKConfig.SkinPath+'toolbar/'+A.toLowerCase()+'.gif';else if (typeof(E)=='number') F=[FCKConfig.SkinPath+'fck_strip.gif',16,E];var G=this._UIButton=new FCKToolbarButtonUI(A,B,C,F,D);G._FCKToolbarPanelButton=this;G.ShowArrow=true;G.OnClick=FCKToolbarPanelButton_OnButtonClick;};FCKToolbarPanelButton.prototype.TypeName='FCKToolbarPanelButton';FCKToolbarPanelButton.prototype.Create=function(A){A.className+='Menu';this._UIButton.Create(A);var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName)._Panel;this.RegisterPanel(B);};FCKToolbarPanelButton.prototype.RegisterPanel=function(A){if (A._FCKToolbarPanelButton) return;A._FCKToolbarPanelButton=this;var B=A.Document.body.appendChild(A.Document.createElement('div'));B.style.position='absolute';B.style.top='0px';var C=A._FCKToolbarPanelButtonLineDiv=B.appendChild(A.Document.createElement('IMG'));C.className='TB_ConnectionLine';C.style.position='absolute';C.src=FCK_SPACER_PATH;A.OnHide=FCKToolbarPanelButton_OnPanelHide;};function FCKToolbarPanelButton_OnButtonClick(A){var B=this._FCKToolbarPanelButton;var e=B._UIButton.MainElement;B._UIButton.ChangeState(1);var C=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(B.CommandName);var D=C._Panel;D._FCKToolbarPanelButtonLineDiv.style.width=(e.offsetWidth-2)+'px';C.Execute(0,e.offsetHeight-1,e);};function FCKToolbarPanelButton_OnPanelHide(){var A=this._FCKToolbarPanelButton;A._UIButton.ChangeState(0);};FCKToolbarPanelButton.prototype.RefreshState=FCKToolbarButton.prototype.RefreshState;FCKToolbarPanelButton.prototype.Enable=FCKToolbarButton.prototype.Enable;FCKToolbarPanelButton.prototype.Disable=FCKToolbarButton.prototype.Disable;
+var FCKScayt;(function(){var A=[];var B=(FCK&&FCK.EditorWindow&&FCK.EditorWindow.parent.parent.scayt)?true:false;var C=false;var D=false;function ScaytEngineLoad(callback){if (B) return;B=true;var E=FCK.EditorWindow.parent.parent;var F=function (){window.scayt=E.scayt;InitScayt();var G=FCKToolbarItems.LoadedItems['ScaytCombobox'];G&&G.SetEnabled(scyt_control&&scyt_control.disabled);InitSetup();};if (E.scayt){F();return;};if (FCK.Config.ScaytCustomUrl) FCK.Config.ScaytCustomUrl=new String(FCK.Config.ScaytCustomUrl).replace(new RegExp("^http[s]*:\/\/"),"");var H=document.location.protocol;var I=FCK.Config.ScaytCustomUrl||'svc.spellchecker.net/spellcheck3/lf/scayt/scayt4.js';var J=H+'//'+I;var K=ParseUrl(J).path+'/';var L=E.window.CKEDITOR||(E.window.CKEDITOR={});L._djScaytConfig={I:K,addOnLoad:function(){F();},isDebug:false};if (callback) A.push(callback);DoLoadScript(J);};function DoLoadScript(url){if (!url) return false;var E=FCK.EditorWindow.parent.parent;var s=E.document.createElement('script');s.type='text/javascript';s.src=url;E.document.getElementsByTagName('head')[0].appendChild(s);return true;};function ParseUrl(data){var m=data.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/);return m?{ path:m[1],file:m[2] }:data;};function createScaytControl (){var N={};var E=FCK.EditorWindow.parent.parent;N.srcNodeRef=FCK.EditingArea.IFrame;N.customerid=FCK.Config.ScaytCustomerid;N.customDictionaryName=FCK.Config.ScaytCustomDictionaryName;N.userDictionaryName=FCK.Config.ScaytUserDictionaryName;N.defLang=FCK.Config.ScaytDefLang;var P=E.scayt;var Q=window.scayt_control=new P(N);};function InitScayt(){createScaytControl();var Q=window.scayt_control;if (Q){Q.setDisabled(false);D=true;C=!Q.disabled;var G=FCKToolbarItems.LoadedItems['ScaytCombobox'];G&&G.Enable();ShowScaytState();};for (var i=0;i<A.length;i++){try{A[i].call(this);}catch(err){}}};var T=function(){name='Scayt';};T.prototype.Execute=function(c){switch (c){case 'Options':case 'Langs':case 'About':if (B&&D&&!C){ScaytMessage('SCAYT is not enabled');break;};if (B&&D) FCKDialog.OpenDialog('Scayt','SCAYT Settings','dialog/fck_scayt.html?'+c.toLowerCase(),343,343);break;default:if (!B){var U=this;ScaytEngineLoad(function (){U.SetEnabled(!window.scayt_control.disabled);});return true;}else if (D){if (C) this.Disable();else this.Enable();ShowScaytState();}};if (!B) return ScaytMessage('SCAYT is not loaded')||false;if (!D) return ScaytMessage('SCAYT is not ready')||false;return true;};T.prototype.Enable=function(){window.scayt_control.setDisabled(false);C=true;};T.prototype.Disable=function(){window.scayt_control.setDisabled(true);C=false;};T.prototype.SetEnabled=function(state){if (state) this.Enable();else this.Disable();ShowScaytState();return true;};T.prototype.GetState=function(){return 0;};function ShowScaytState(){var W=FCKToolbarItems.GetItem('SpellCheck');if (!W||!W._Combo||!W._Combo._OuterTable) return;var X=W._Combo._OuterTable.getElementsByTagName('img')[1];var Y=W._Combo.Items['trigger'];if (C){X.style.opacity='1';Y.innerHTML=GetStatusLabel();}else{X.style.opacity='0.5';Y.innerHTML=GetStatusLabel();}};function GetStatusLabel(){if (!D) return '<b>Enable SCAYT</b>';return C?'<b>Disable SCAYT</b>':'<b>Enable SCAYT</b>';};var Z=function(tooltip,style){this.Command=FCKCommands.GetCommand('Scayt');this.CommandName='Scayt';this.Label=this.GetLabel();this.Tooltip=FCKLang.ScaytTitle;this.Style=1;};Z.prototype=new FCKToolbarSpecialCombo;Z.prototype.CreateItems=function(){this._Combo.AddItem('Trigger','<b>Enable SCAYT</b>');this._Combo.AddItem('Options',FCKLang.ScaytTitleOptions||"Options");this._Combo.AddItem('Langs',FCKLang.ScaytTitleLangs||"Languages");this._Combo.AddItem('About',FCKLang.ScaytTitleAbout||"About");};Z.prototype.GetLabel=function(){var a=FCKConfig.SkinPath+'fck_strip.gif';return FCKBrowserInfo.IsIE?'<div class="TB_Button_Image"><img src="'+a+'" style="top:-192px"></div>':'<img class="TB_Button_Image" src="'+FCK_SPACER_PATH+'" style="background-position: 0px -192px;background-image: url('+a+');">';};function ScaytMessage(m){m&&alert(m);};var b=function(){name='ScaytContext';};b.prototype.Execute=function(contextInfo){var c=contextInfo&&contextInfo.action,g=c&&contextInfo.node,Q=window.scayt_control;if (g){switch (c){case 'Suggestion':Q.replace(g,contextInfo.suggestion);break;case 'Ignore':Q.ignore(g);break;case 'Ignore All':Q.ignoreAll(g);break;case 'Add Word':var E=FCK.EditorWindow.parent.parent;E.scayt.addWordToUserDictionary(g);break;}}};function InitSetup(){FCK.ContextMenu.RegisterListener({AddItems:function(menu){var E=FCK.EditorWindow.parent.parent;var Q=window.scayt_control,P=E.scayt;if (!Q) return;var g=Q.getScaytNode();if (!g) return;var h=P.getSuggestion(Q.getWord(g),Q.getLang());if (!h||!h.length) return;menu.AddSeparator();var j=FCK.Config.ScaytMaxSuggestions||5;var k=(j==-1)?h.length:j;for (var i=0;i<k;i+=1){if (h[i]){menu.AddItem('ScaytContext',h[i],null,false,{'action':'Suggestion','node':g,'suggestion':h[i] });}};menu.AddSeparator();menu.AddItem('ScaytContext','Ignore',null,false,{ 'action':'Ignore','node':g });menu.AddItem('ScaytContext','Ignore All',null,false,{ 'action':'Ignore All','node':g });menu.AddItem('ScaytContext','Add Word',null,false,{ 'action':'Add Word','node':g });try{if (D&&C) Q.fireOnContextMenu(null,FCK.ContextMenu._InnerContextMenu);}catch(err) {}}});FCK.Events.AttachEvent('OnPaste',function(){window.scayt_control.refresh();return true;});};FCK.Events.AttachEvent('OnAfterSetHTML',function(){if (FCKConfig.SpellChecker=='SCAYT'){if (!B&&FCK.Config.ScaytAutoStartup) ScaytEngineLoad();if (FCK.EditMode==0&&B&&D) createScaytControl();ShowScaytState();}});FCK.Events.AttachEvent('OnBeforeGetData',function(){D&&window.scayt_control.reset();});FCK.Events.AttachEvent('OnAfterGetData',function(){D&&window.scayt_control.refresh();});FCKScayt={CreateCommand:function(){return new T();},CreateContextCommand:function(){return new b();},CreateToolbarItem:function(){return new Z();}};})();
+var FCKToolbarItems={};FCKToolbarItems.LoadedItems={};FCKToolbarItems.RegisterItem=function(A,B){this.LoadedItems[A]=B;};FCKToolbarItems.GetItem=function(A){var B=FCKToolbarItems.LoadedItems[A];if (B) return B;switch (A){case 'Source':B=new FCKToolbarButton('Source',FCKLang.Source,null,2,true,true,1);break;case 'DocProps':B=new FCKToolbarButton('DocProps',FCKLang.DocProps,null,null,null,null,2);break;case 'Save':B=new FCKToolbarButton('Save',FCKLang.Save,null,null,true,null,3);break;case 'NewPage':B=new FCKToolbarButton('NewPage',FCKLang.NewPage,null,null,true,null,4);break;case 'Preview':B=new FCKToolbarButton('Preview',FCKLang.Preview,null,null,true,null,5);break;case 'Templates':B=new FCKToolbarButton('Templates',FCKLang.Templates,null,null,null,null,6);break;case 'About':B=new FCKToolbarButton('About',FCKLang.About,null,null,true,null,47);break;case 'Cut':B=new FCKToolbarButton('Cut',FCKLang.Cut,null,null,false,true,7);break;case 'Copy':B=new FCKToolbarButton('Copy',FCKLang.Copy,null,null,false,true,8);break;case 'Paste':B=new FCKToolbarButton('Paste',FCKLang.Paste,null,null,false,true,9);break;case 'PasteText':B=new FCKToolbarButton('PasteText',FCKLang.PasteText,null,null,false,true,10);break;case 'PasteWord':B=new FCKToolbarButton('PasteWord',FCKLang.PasteWord,null,null,false,true,11);break;case 'Print':B=new FCKToolbarButton('Print',FCKLang.Print,null,null,false,true,12);break;case 'Undo':B=new FCKToolbarButton('Undo',FCKLang.Undo,null,null,false,true,14);break;case 'Redo':B=new FCKToolbarButton('Redo',FCKLang.Redo,null,null,false,true,15);break;case 'SelectAll':B=new FCKToolbarButton('SelectAll',FCKLang.SelectAll,null,null,true,null,18);break;case 'RemoveFormat':B=new FCKToolbarButton('RemoveFormat',FCKLang.RemoveFormat,null,null,false,true,19);break;case 'FitWindow':B=new FCKToolbarButton('FitWindow',FCKLang.FitWindow,null,null,true,true,66);break;case 'Bold':B=new FCKToolbarButton('Bold',FCKLang.Bold,null,null,false,true,20);break;case 'Italic':B=new FCKToolbarButton('Italic',FCKLang.Italic,null,null,false,true,21);break;case 'Underline':B=new FCKToolbarButton('Underline',FCKLang.Underline,null,null,false,true,22);break;case 'StrikeThrough':B=new FCKToolbarButton('StrikeThrough',FCKLang.StrikeThrough,null,null,false,true,23);break;case 'Subscript':B=new FCKToolbarButton('Subscript',FCKLang.Subscript,null,null,false,true,24);break;case 'Superscript':B=new FCKToolbarButton('Superscript',FCKLang.Superscript,null,null,false,true,25);break;case 'OrderedList':B=new FCKToolbarButton('InsertOrderedList',FCKLang.NumberedListLbl,FCKLang.NumberedList,null,false,true,26);break;case 'UnorderedList':B=new FCKToolbarButton('InsertUnorderedList',FCKLang.BulletedListLbl,FCKLang.BulletedList,null,false,true,27);break;case 'Outdent':B=new FCKToolbarButton('Outdent',FCKLang.DecreaseIndent,null,null,false,true,28);break;case 'Indent':B=new FCKToolbarButton('Indent',FCKLang.IncreaseIndent,null,null,false,true,29);break;case 'Blockquote':B=new FCKToolbarButton('Blockquote',FCKLang.Blockquote,null,null,false,true,73);break;case 'CreateDiv':B=new FCKToolbarButton('CreateDiv',FCKLang.CreateDiv,null,null,false,true,74);break;case 'Link':B=new FCKToolbarButton('Link',FCKLang.InsertLinkLbl,FCKLang.InsertLink,null,false,true,34);break;case 'Unlink':B=new FCKToolbarButton('Unlink',FCKLang.RemoveLink,null,null,false,true,35);break;case 'Anchor':B=new FCKToolbarButton('Anchor',FCKLang.Anchor,null,null,null,null,36);break;case 'Image':B=new FCKToolbarButton('Image',FCKLang.InsertImageLbl,FCKLang.InsertImage,null,false,true,37);break;case 'Flash':B=new FCKToolbarButton('Flash',FCKLang.InsertFlashLbl,FCKLang.InsertFlash,null,false,true,38);break;case 'Table':B=new FCKToolbarButton('Table',FCKLang.InsertTableLbl,FCKLang.InsertTable,null,false,true,39);break;case 'SpecialChar':B=new FCKToolbarButton('SpecialChar',FCKLang.InsertSpecialCharLbl,FCKLang.InsertSpecialChar,null,false,true,42);break;case 'Smiley':B=new FCKToolbarButton('Smiley',FCKLang.InsertSmileyLbl,FCKLang.InsertSmiley,null,false,true,41);break;case 'PageBreak':B=new FCKToolbarButton('PageBreak',FCKLang.PageBreakLbl,FCKLang.PageBreak,null,false,true,43);break;case 'Rule':B=new FCKToolbarButton('Rule',FCKLang.InsertLineLbl,FCKLang.InsertLine,null,false,true,40);break;case 'JustifyLeft':B=new FCKToolbarButton('JustifyLeft',FCKLang.LeftJustify,null,null,false,true,30);break;case 'JustifyCenter':B=new FCKToolbarButton('JustifyCenter',FCKLang.CenterJustify,null,null,false,true,31);break;case 'JustifyRight':B=new FCKToolbarButton('JustifyRight',FCKLang.RightJustify,null,null,false,true,32);break;case 'JustifyFull':B=new FCKToolbarButton('JustifyFull',FCKLang.BlockJustify,null,null,false,true,33);break;case 'Style':B=new FCKToolbarStyleCombo();break;case 'FontName':B=new FCKToolbarFontsCombo();break;case 'FontSize':B=new FCKToolbarFontSizeCombo();break;case 'FontFormat':B=new FCKToolbarFontFormatCombo();break;case 'TextColor':B=new FCKToolbarPanelButton('TextColor',FCKLang.TextColor,null,null,45);break;case 'BGColor':B=new FCKToolbarPanelButton('BGColor',FCKLang.BGColor,null,null,46);break;case 'Find':B=new FCKToolbarButton('Find',FCKLang.Find,null,null,null,null,16);break;case 'Replace':B=new FCKToolbarButton('Replace',FCKLang.Replace,null,null,null,null,17);break;case 'Form':B=new FCKToolbarButton('Form',FCKLang.Form,null,null,null,null,48);break;case 'Checkbox':B=new FCKToolbarButton('Checkbox',FCKLang.Checkbox,null,null,null,null,49);break;case 'Radio':B=new FCKToolbarButton('Radio',FCKLang.RadioButton,null,null,null,null,50);break;case 'TextField':B=new FCKToolbarButton('TextField',FCKLang.TextField,null,null,null,null,51);break;case 'Textarea':B=new FCKToolbarButton('Textarea',FCKLang.Textarea,null,null,null,null,52);break;case 'HiddenField':B=new FCKToolbarButton('HiddenField',FCKLang.HiddenField,null,null,null,null,56);break;case 'Button':B=new FCKToolbarButton('Button',FCKLang.Button,null,null,null,null,54);break;case 'Select':B=new FCKToolbarButton('Select',FCKLang.SelectionField,null,null,null,null,53);break;case 'ImageButton':B=new FCKToolbarButton('ImageButton',FCKLang.ImageButton,null,null,null,null,55);break;case 'ShowBlocks':B=new FCKToolbarButton('ShowBlocks',FCKLang.ShowBlocks,null,null,null,true,72);break;case 'SpellCheck':if (FCKConfig.SpellChecker=='SCAYT') B=FCKScayt.CreateToolbarItem();else B=new FCKToolbarButton('SpellCheck',FCKLang.SpellCheck,null,null,null,null,13);break;default:alert(FCKLang.UnknownToolbarItem.replace(/%1/g,A));return null;};FCKToolbarItems.LoadedItems[A]=B;return B;};
+var FCKToolbar=function(){this.Items=[];};FCKToolbar.prototype.AddItem=function(A){return this.Items[this.Items.length]=A;};FCKToolbar.prototype.AddButton=function(A,B,C,D,E,F){if (typeof(D)=='number') D=[this.DefaultIconsStrip,this.DefaultIconSize,D];var G=new FCKToolbarButtonUI(A,B,C,D,E,F);G._FCKToolbar=this;G.OnClick=FCKToolbar_OnItemClick;return this.AddItem(G);};function FCKToolbar_OnItemClick(A){var B=A._FCKToolbar;if (B.OnItemClick) B.OnItemClick(B,A);};FCKToolbar.prototype.AddSeparator=function(){this.AddItem(new FCKToolbarSeparator());};FCKToolbar.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var e=B.createElement('table');e.className='TB_Toolbar';e.style.styleFloat=e.style.cssFloat=(FCKLang.Dir=='ltr'?'left':'right');e.dir=FCKLang.Dir;e.cellPadding=0;e.cellSpacing=0;var C=e.insertRow(-1);var D;if (!this.HideStart){D=C.insertCell(-1);D.appendChild(B.createElement('div')).className='TB_Start';};for (var i=0;i<this.Items.length;i++){this.Items[i].Create(C.insertCell(-1));};if (!this.HideEnd){D=C.insertCell(-1);D.appendChild(B.createElement('div')).className='TB_End';};A.appendChild(e);};var FCKToolbarSeparator=function(){};FCKToolbarSeparator.prototype.Create=function(A){FCKTools.AppendElement(A,'div').className='TB_Separator';};
+var FCKToolbarBreak=function(){};FCKToolbarBreak.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A).createElement('div');B.className='TB_Break';B.style.clear=FCKLang.Dir=='rtl'?'left':'right';A.appendChild(B);};
+function FCKToolbarSet_Create(A){var B;var C=A||FCKConfig.ToolbarLocation;switch (C){case 'In':document.getElementById('xToolbarRow').style.display='';B=new FCKToolbarSet(document);break;case 'None':B=new FCKToolbarSet(document);break;default:FCK.Events.AttachEvent('OnBlur',FCK_OnBlur);FCK.Events.AttachEvent('OnFocus',FCK_OnFocus);var D;var E=C.match(/^Out:(.+)\((\w+)\)$/);if (E){if (FCKBrowserInfo.IsAIR) FCKAdobeAIR.ToolbarSet_GetOutElement(window,E);else D=eval('parent.'+E[1]).document.getElementById(E[2]);}else{E=C.match(/^Out:(\w+)$/);if (E) D=parent.document.getElementById(E[1]);};if (!D){alert('Invalid value for "ToolbarLocation"');return arguments.callee('In');};B=D.__FCKToolbarSet;if (B) break;var F=FCKTools.GetElementDocument(D).createElement('iframe');F.src='javascript:void(0)';F.frameBorder=0;F.width='100%';F.height='10';D.appendChild(F);F.unselectable='on';var G=F.contentWindow.document;var H='';if (FCKBrowserInfo.IsSafari) H='<base href="'+window.document.location+'">';G.open();G.write('<html><head>'+H+'<script type="text/javascript"> var adjust = function() { window.frameElement.height = document.body.scrollHeight ; }; window.onresize = window.onload = function(){var timer = null;var lastHeight = -1;var lastChange = 0;var poller = function(){var currentHeight = document.body.scrollHeight || 0;var currentTime = (new Date()).getTime();if (currentHeight != lastHeight){lastChange = currentTime;adjust();lastHeight = document.body.scrollHeight;}if (lastChange < currentTime - 1000) clearInterval(timer);};timer = setInterval(poller, 100);}</script></head><body style="overflow: hidden">'+document.getElementById('xToolbarSpace').innerHTML+'</body></html>');G.close();if(FCKBrowserInfo.IsAIR) FCKAdobeAIR.ToolbarSet_InitOutFrame(G);FCKTools.AddEventListener(G,'contextmenu',FCKTools.CancelEvent);FCKTools.AppendStyleSheet(G,FCKConfig.SkinEditorCSS);B=D.__FCKToolbarSet=new FCKToolbarSet(G);B._IFrame=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(D,FCKToolbarSet_Target_Cleanup);};B.CurrentInstance=FCK;if (!B.ToolbarItems) B.ToolbarItems=FCKToolbarItems;FCK.AttachToOnSelectionChange(B.RefreshItemsState);return B;};function FCK_OnBlur(A){var B=A.ToolbarSet;if (B.CurrentInstance==A) B.Disable();};function FCK_OnFocus(A){var B=A.ToolbarSet;var C=A||FCK;B.CurrentInstance.FocusManager.RemoveWindow(B._IFrame.contentWindow);B.CurrentInstance=C;C.FocusManager.AddWindow(B._IFrame.contentWindow,true);B.Enable();};function FCKToolbarSet_Cleanup(){this._TargetElement=null;this._IFrame=null;};function FCKToolbarSet_Target_Cleanup(){this.__FCKToolbarSet=null;};var FCKToolbarSet=function(A){this._Document=A;this._TargetElement=A.getElementById('xToolbar');var B=A.getElementById('xExpandHandle');var C=A.getElementById('xCollapseHandle');B.title=FCKLang.ToolbarExpand;FCKTools.AddEventListener(B,'click',FCKToolbarSet_Expand_OnClick);C.title=FCKLang.ToolbarCollapse;FCKTools.AddEventListener(C,'click',FCKToolbarSet_Collapse_OnClick);if (!FCKConfig.ToolbarCanCollapse||FCKConfig.ToolbarStartExpanded) this.Expand();else this.Collapse();C.style.display=FCKConfig.ToolbarCanCollapse?'':'none';if (FCKConfig.ToolbarCanCollapse) C.style.display='';else A.getElementById('xTBLeftBorder').style.display='';this.Toolbars=[];this.IsLoaded=false;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbarSet_Cleanup);};function FCKToolbarSet_Expand_OnClick(){FCK.ToolbarSet.Expand();};function FCKToolbarSet_Collapse_OnClick(){FCK.ToolbarSet.Collapse();};FCKToolbarSet.prototype.Expand=function(){this._ChangeVisibility(false);};FCKToolbarSet.prototype.Collapse=function(){this._ChangeVisibility(true);};FCKToolbarSet.prototype._ChangeVisibility=function(A){this._Document.getElementById('xCollapsed').style.display=A?'':'none';this._Document.getElementById('xExpanded').style.display=A?'none':'';if (window.onresize){FCKTools.RunFunction(window.onresize);}};FCKToolbarSet.prototype.Load=function(A){this.Name=A;this.Items=[];this.ItemsWysiwygOnly=[];this.ItemsContextSensitive=[];this._TargetElement.innerHTML='';var B=FCKConfig.ToolbarSets[A];if (!B){alert(FCKLang.UnknownToolbarSet.replace(/%1/g,A));return;};this.Toolbars=[];for (var x=0;x<B.length;x++){var C=B[x];if (!C) continue;var D;if (typeof(C)=='string'){if (C=='/') D=new FCKToolbarBreak();}else{D=new FCKToolbar();for (var j=0;j<C.length;j++){var E=C[j];if (E=='-') D.AddSeparator();else{var F=FCKToolbarItems.GetItem(E);if (F){D.AddItem(F);this.Items.push(F);if (!F.SourceView) this.ItemsWysiwygOnly.push(F);if (F.ContextSensitive) this.ItemsContextSensitive.push(F);}}}};D.Create(this._TargetElement);this.Toolbars[this.Toolbars.length]=D;};FCKTools.DisableSelection(this._Document.getElementById('xCollapseHandle').parentNode);if (FCK.Status!=2) FCK.Events.AttachEvent('OnStatusChange',this.RefreshModeState);else this.RefreshModeState();this.IsLoaded=true;this.IsEnabled=true;FCKTools.RunFunction(this.OnLoad);};FCKToolbarSet.prototype.Enable=function(){if (this.IsEnabled) return;this.IsEnabled=true;var A=this.Items;for (var i=0;i<A.length;i++) A[i].RefreshState();};FCKToolbarSet.prototype.Disable=function(){if (!this.IsEnabled) return;this.IsEnabled=false;var A=this.Items;for (var i=0;i<A.length;i++) A[i].Disable();};FCKToolbarSet.prototype.RefreshModeState=function(A){if (FCK.Status!=2) return;var B=A?A.ToolbarSet:this;var C=B.ItemsWysiwygOnly;if (FCK.EditMode==0){for (var i=0;i<C.length;i++) C[i].Enable();B.RefreshItemsState(A);}else{B.RefreshItemsState(A);for (var j=0;j<C.length;j++) C[j].Disable();}};FCKToolbarSet.prototype.RefreshItemsState=function(A){var B=(A?A.ToolbarSet:this).ItemsContextSensitive;for (var i=0;i<B.length;i++) B[i].RefreshState();};
+var FCKDialog=(function(){var A;var B;var C;var D=window.parent;while (D.parent&&D.parent!=D){try{if (D.parent.document.domain!=document.domain) break;if (D.parent.document.getElementsByTagName('frameset').length>0) break;}catch (e){break;};D=D.parent;};var E=D.document;var F=function(){if (!B) B=FCKConfig.FloatingPanelsZIndex+999;return++B;};var G=function(){if (!C) return;var H=FCKTools.IsStrictMode(E)?E.documentElement:E.body;FCKDomTools.SetElementStyles(C,{'width':Math.max(H.scrollWidth,H.clientWidth,E.scrollWidth||0)-1+'px','height':Math.max(H.scrollHeight,H.clientHeight,E.scrollHeight||0)-1+'px'});};return {OpenDialog:function(dialogName,dialogTitle,dialogPage,width,height,customValue,resizable){if (!A) this.DisplayMainCover();var I={Title:dialogTitle,Page:dialogPage,Editor:window,CustomValue:customValue,TopWindow:D};FCK.ToolbarSet.CurrentInstance.Selection.Save(true);var J=FCKTools.GetViewPaneSize(D);var K={ 'X':0,'Y':0 };var L=FCKBrowserInfo.IsIE&&(!FCKBrowserInfo.IsIE7||!FCKTools.IsStrictMode(D.document));if (L) K=FCKTools.GetScrollPosition(D);var M=Math.max(K.Y+(J.Height-height-20)/2,0);var N=Math.max(K.X+(J.Width-width-20)/2,0);var O=E.createElement('iframe');FCKTools.ResetStyles(O);O.src=FCKConfig.BasePath+'fckdialog.html';O.frameBorder=0;O.allowTransparency=true;FCKDomTools.SetElementStyles(O,{'position':(L)?'absolute':'fixed','top':M+'px','left':N+'px','width':width+'px','height':height+'px','zIndex':F()});O._DialogArguments=I;E.body.appendChild(O);O._ParentDialog=A;A=O;},OnDialogClose:function(dialogWindow){var O=dialogWindow.frameElement;FCKDomTools.RemoveNode(O);if (O._ParentDialog){A=O._ParentDialog;O._ParentDialog.contentWindow.SetEnabled(true);}else{if (!FCKBrowserInfo.IsIE) FCK.Focus();this.HideMainCover();setTimeout(function(){ A=null;},0);FCK.ToolbarSet.CurrentInstance.Selection.Release();}},DisplayMainCover:function(){C=E.createElement('div');FCKTools.ResetStyles(C);FCKDomTools.SetElementStyles(C,{'position':'absolute','zIndex':F(),'top':'0px','left':'0px','backgroundColor':FCKConfig.BackgroundBlockerColor});FCKDomTools.SetOpacity(C,FCKConfig.BackgroundBlockerOpacity);if (FCKBrowserInfo.IsIE&&!FCKBrowserInfo.IsIE7){var Q=E.createElement('iframe');FCKTools.ResetStyles(Q);Q.hideFocus=true;Q.frameBorder=0;Q.src=FCKTools.GetVoidUrl();FCKDomTools.SetElementStyles(Q,{'width':'100%','height':'100%','position':'absolute','left':'0px','top':'0px','filter':'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'});C.appendChild(Q);};FCKTools.AddEventListener(D,'resize',G);G();E.body.appendChild(C);FCKFocusManager.Lock();var R=FCK.ToolbarSet.CurrentInstance.GetInstanceObject('frameElement');R._fck_originalTabIndex=R.tabIndex;R.tabIndex=-1;},HideMainCover:function(){FCKDomTools.RemoveNode(C);FCKFocusManager.Unlock();var R=FCK.ToolbarSet.CurrentInstance.GetInstanceObject('frameElement');R.tabIndex=R._fck_originalTabIndex;FCKDomTools.ClearElementJSProperty(R,'_fck_originalTabIndex');},GetCover:function(){return C;}};})();
+var FCKMenuItem=function(A,B,C,D,E,F){this.Name=B;this.Label=C||B;this.IsDisabled=E;this.Icon=new FCKIcon(D);this.SubMenu=new FCKMenuBlockPanel();this.SubMenu.Parent=A;this.SubMenu.OnClick=FCKTools.CreateEventListener(FCKMenuItem_SubMenu_OnClick,this);this.CustomData=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuItem_Cleanup);};FCKMenuItem.prototype.AddItem=function(A,B,C,D,E){this.HasSubMenu=true;return this.SubMenu.AddItem(A,B,C,D,E);};FCKMenuItem.prototype.AddSeparator=function(){this.SubMenu.AddSeparator();};FCKMenuItem.prototype.Create=function(A){var B=this.HasSubMenu;var C=FCKTools.GetElementDocument(A);var r=this.MainElement=A.insertRow(-1);r.className=this.IsDisabled?'MN_Item_Disabled':'MN_Item';if (!this.IsDisabled){FCKTools.AddEventListenerEx(r,'mouseover',FCKMenuItem_OnMouseOver,[this]);FCKTools.AddEventListenerEx(r,'click',FCKMenuItem_OnClick,[this]);if (!B) FCKTools.AddEventListenerEx(r,'mouseout',FCKMenuItem_OnMouseOut,[this]);};var D=r.insertCell(-1);D.className='MN_Icon';D.appendChild(this.Icon.CreateIconElement(C));D=r.insertCell(-1);D.className='MN_Label';D.noWrap=true;D.appendChild(C.createTextNode(this.Label));D=r.insertCell(-1);if (B){D.className='MN_Arrow';var E=D.appendChild(C.createElement('IMG'));E.src=FCK_IMAGES_PATH+'arrow_'+FCKLang.Dir+'.gif';E.width=4;E.height=7;this.SubMenu.Create();this.SubMenu.Panel.OnHide=FCKTools.CreateEventListener(FCKMenuItem_SubMenu_OnHide,this);}};FCKMenuItem.prototype.Activate=function(){this.MainElement.className='MN_Item_Over';if (this.HasSubMenu){this.SubMenu.Show(this.MainElement.offsetWidth+2,-2,this.MainElement);};FCKTools.RunFunction(this.OnActivate,this);};FCKMenuItem.prototype.Deactivate=function(){this.MainElement.className='MN_Item';if (this.HasSubMenu) this.SubMenu.Hide();};function FCKMenuItem_SubMenu_OnClick(A,B){FCKTools.RunFunction(B.OnClick,B,[A]);};function FCKMenuItem_SubMenu_OnHide(A){A.Deactivate();};function FCKMenuItem_OnClick(A,B){if (B.HasSubMenu) B.Activate();else{B.Deactivate();FCKTools.RunFunction(B.OnClick,B,[B]);}};function FCKMenuItem_OnMouseOver(A,B){B.Activate();};function FCKMenuItem_OnMouseOut(A,B){B.Deactivate();};function FCKMenuItem_Cleanup(){this.MainElement=null;};
+var FCKMenuBlock=function(){this._Items=[];};FCKMenuBlock.prototype.Count=function(){return this._Items.length;};FCKMenuBlock.prototype.AddItem=function(A,B,C,D,E){var F=new FCKMenuItem(this,A,B,C,D,E);F.OnClick=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnClick,this);F.OnActivate=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnActivate,this);this._Items.push(F);return F;};FCKMenuBlock.prototype.AddSeparator=function(){this._Items.push(new FCKMenuSeparator());};FCKMenuBlock.prototype.RemoveAllItems=function(){this._Items=[];var A=this._ItemsTable;if (A){while (A.rows.length>0) A.deleteRow(0);}};FCKMenuBlock.prototype.Create=function(A){if (!this._ItemsTable){if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuBlock_Cleanup);this._Window=FCKTools.GetElementWindow(A);var B=FCKTools.GetElementDocument(A);var C=A.appendChild(B.createElement('table'));C.cellPadding=0;C.cellSpacing=0;FCKTools.DisableSelection(C);var D=C.insertRow(-1).insertCell(-1);D.className='MN_Menu';var E=this._ItemsTable=D.appendChild(B.createElement('table'));E.cellPadding=0;E.cellSpacing=0;};for (var i=0;i<this._Items.length;i++) this._Items[i].Create(this._ItemsTable);};function FCKMenuBlock_Item_OnClick(A,B){if (B.Hide) B.Hide();FCKTools.RunFunction(B.OnClick,B,[A]);};function FCKMenuBlock_Item_OnActivate(A){var B=A._ActiveItem;if (B&&B!=this){if (!FCKBrowserInfo.IsIE&&B.HasSubMenu&&!this.HasSubMenu){A._Window.focus();A.Panel.HasFocus=true;};B.Deactivate();};A._ActiveItem=this;};function FCKMenuBlock_Cleanup(){this._Window=null;this._ItemsTable=null;};var FCKMenuSeparator=function(){};FCKMenuSeparator.prototype.Create=function(A){var B=FCKTools.GetElementDocument(A);var r=A.insertRow(-1);var C=r.insertCell(-1);C.className='MN_Separator MN_Icon';C=r.insertCell(-1);C.className='MN_Separator';C.appendChild(B.createElement('DIV')).className='MN_Separator_Line';C=r.insertCell(-1);C.className='MN_Separator';C.appendChild(B.createElement('DIV')).className='MN_Separator_Line';};
+var FCKMenuBlockPanel=function(){FCKMenuBlock.call(this);};FCKMenuBlockPanel.prototype=new FCKMenuBlock();FCKMenuBlockPanel.prototype.Create=function(){var A=this.Panel=(this.Parent&&this.Parent.Panel?this.Parent.Panel.CreateChildPanel():new FCKPanel());A.AppendStyleSheet(FCKConfig.SkinEditorCSS);FCKMenuBlock.prototype.Create.call(this,A.MainNode);};FCKMenuBlockPanel.prototype.Show=function(x,y,A){if (!this.Panel.CheckIsOpened()) this.Panel.Show(x,y,A);};FCKMenuBlockPanel.prototype.Hide=function(){if (this.Panel.CheckIsOpened()) this.Panel.Hide();};
+var FCKContextMenu=function(A,B){this.CtrlDisable=false;var C=this._Panel=new FCKPanel(A);C.AppendStyleSheet(FCKConfig.SkinEditorCSS);C.IsContextMenu=true;if (FCKBrowserInfo.IsGecko) C.Document.addEventListener('draggesture',function(e) {e.preventDefault();return false;},true);var D=this._MenuBlock=new FCKMenuBlock();D.Panel=C;D.OnClick=FCKTools.CreateEventListener(FCKContextMenu_MenuBlock_OnClick,this);this._Redraw=true;};FCKContextMenu.prototype.SetMouseClickWindow=function(A){if (!FCKBrowserInfo.IsIE){this._Document=A.document;if (FCKBrowserInfo.IsOpera&&!('oncontextmenu' in document.createElement('foo'))){this._Document.addEventListener('mousedown',FCKContextMenu_Document_OnMouseDown,false);this._Document.addEventListener('mouseup',FCKContextMenu_Document_OnMouseUp,false);};this._Document.addEventListener('contextmenu',FCKContextMenu_Document_OnContextMenu,false);}};FCKContextMenu.prototype.AddItem=function(A,B,C,D,E){var F=this._MenuBlock.AddItem(A,B,C,D,E);this._Redraw=true;return F;};FCKContextMenu.prototype.AddSeparator=function(){this._MenuBlock.AddSeparator();this._Redraw=true;};FCKContextMenu.prototype.RemoveAllItems=function(){this._MenuBlock.RemoveAllItems();this._Redraw=true;};FCKContextMenu.prototype.AttachToElement=function(A){if (FCKBrowserInfo.IsIE) FCKTools.AddEventListenerEx(A,'contextmenu',FCKContextMenu_AttachedElement_OnContextMenu,this);else A._FCKContextMenu=this;};function FCKContextMenu_Document_OnContextMenu(e){if (FCKConfig.BrowserContextMenu) return true;var A=e.target;while (A){if (A._FCKContextMenu){if (A._FCKContextMenu.CtrlDisable&&(e.ctrlKey||e.metaKey)) return true;FCKTools.CancelEvent(e);FCKContextMenu_AttachedElement_OnContextMenu(e,A._FCKContextMenu,A);return false;};A=A.parentNode;};return true;};var FCKContextMenu_OverrideButton;function FCKContextMenu_Document_OnMouseDown(e){if(!e||e.button!=2) return false;if (FCKConfig.BrowserContextMenu) return true;var A=e.target;while (A){if (A._FCKContextMenu){if (A._FCKContextMenu.CtrlDisable&&(e.ctrlKey||e.metaKey)) return true;var B=FCKContextMenu_OverrideButton;if(!B){var C=FCKTools.GetElementDocument(e.target);B=FCKContextMenu_OverrideButton=C.createElement('input');B.type='button';var D=C.createElement('p');C.body.appendChild(D);D.appendChild(B);};B.style.cssText='position:absolute;top:'+(e.clientY-2)+'px;left:'+(e.clientX-2)+'px;width:5px;height:5px;opacity:0.01';};A=A.parentNode;};return false;};function FCKContextMenu_Document_OnMouseUp(e){if (FCKConfig.BrowserContextMenu) return true;var A=FCKContextMenu_OverrideButton;if (A){var B=A.parentNode;B.parentNode.removeChild(B);FCKContextMenu_OverrideButton=undefined;if(e&&e.button==2){FCKContextMenu_Document_OnContextMenu(e);return false;}};return true;};function FCKContextMenu_AttachedElement_OnContextMenu(A,B,C){if ((B.CtrlDisable&&(A.ctrlKey||A.metaKey))||FCKConfig.BrowserContextMenu) return true;var D=C||this;if (B.OnBeforeOpen) B.OnBeforeOpen.call(B,D);if (B._MenuBlock.Count()==0) return false;if (B._Redraw){B._MenuBlock.Create(B._Panel.MainNode);B._Redraw=false;};FCKTools.DisableSelection(B._Panel.Document.body);var x=0;var y=0;if (FCKBrowserInfo.IsIE){x=A.screenX;y=A.screenY;}else if (FCKBrowserInfo.IsSafari){x=A.clientX;y=A.clientY;}else{x=A.pageX;y=A.pageY;};B._Panel.Show(x,y,A.currentTarget||null);return false;};function FCKContextMenu_MenuBlock_OnClick(A,B){B._Panel.Hide();FCKTools.RunFunction(B.OnItemClick,B,A);};
+FCK.ContextMenu={};FCK.ContextMenu.Listeners=[];FCK.ContextMenu.RegisterListener=function(A){if (A) this.Listeners.push(A);};function FCK_ContextMenu_Init(){var A=FCK.ContextMenu._InnerContextMenu=new FCKContextMenu(FCKBrowserInfo.IsIE?window:window.parent,FCKLang.Dir);A.CtrlDisable=FCKConfig.BrowserContextMenuOnCtrl;A.OnBeforeOpen=FCK_ContextMenu_OnBeforeOpen;A.OnItemClick=FCK_ContextMenu_OnItemClick;var B=FCK.ContextMenu;for (var i=0;i<FCKConfig.ContextMenu.length;i++) B.RegisterListener(FCK_ContextMenu_GetListener(FCKConfig.ContextMenu[i]));};function FCK_ContextMenu_GetListener(A){switch (A){case 'Generic':return {AddItems:function(menu,tag,tagName){menu.AddItem('Cut',FCKLang.Cut,7,FCKCommands.GetCommand('Cut').GetState()==-1);menu.AddItem('Copy',FCKLang.Copy,8,FCKCommands.GetCommand('Copy').GetState()==-1);menu.AddItem('Paste',FCKLang.Paste,9,FCKCommands.GetCommand('Paste').GetState()==-1);}};case 'Table':return {AddItems:function(menu,tag,tagName){var B=(tagName=='TABLE');var C=(!B&&FCKSelection.HasAncestorNode('TABLE'));if (C){menu.AddSeparator();var D=menu.AddItem('Cell',FCKLang.CellCM);D.AddItem('TableInsertCellBefore',FCKLang.InsertCellBefore,69);D.AddItem('TableInsertCellAfter',FCKLang.InsertCellAfter,58);D.AddItem('TableDeleteCells',FCKLang.DeleteCells,59);if (FCKBrowserInfo.IsGecko) D.AddItem('TableMergeCells',FCKLang.MergeCells,60,FCKCommands.GetCommand('TableMergeCells').GetState()==-1);else{D.AddItem('TableMergeRight',FCKLang.MergeRight,60,FCKCommands.GetCommand('TableMergeRight').GetState()==-1);D.AddItem('TableMergeDown',FCKLang.MergeDown,60,FCKCommands.GetCommand('TableMergeDown').GetState()==-1);};D.AddItem('TableHorizontalSplitCell',FCKLang.HorizontalSplitCell,61,FCKCommands.GetCommand('TableHorizontalSplitCell').GetState()==-1);D.AddItem('TableVerticalSplitCell',FCKLang.VerticalSplitCell,61,FCKCommands.GetCommand('TableVerticalSplitCell').GetState()==-1);D.AddSeparator();D.AddItem('TableCellProp',FCKLang.CellProperties,57,FCKCommands.GetCommand('TableCellProp').GetState()==-1);menu.AddSeparator();D=menu.AddItem('Row',FCKLang.RowCM);D.AddItem('TableInsertRowBefore',FCKLang.InsertRowBefore,70);D.AddItem('TableInsertRowAfter',FCKLang.InsertRowAfter,62);D.AddItem('TableDeleteRows',FCKLang.DeleteRows,63);menu.AddSeparator();D=menu.AddItem('Column',FCKLang.ColumnCM);D.AddItem('TableInsertColumnBefore',FCKLang.InsertColumnBefore,71);D.AddItem('TableInsertColumnAfter',FCKLang.InsertColumnAfter,64);D.AddItem('TableDeleteColumns',FCKLang.DeleteColumns,65);};if (B||C){menu.AddSeparator();menu.AddItem('TableDelete',FCKLang.TableDelete);menu.AddItem('TableProp',FCKLang.TableProperties,39);}}};case 'Link':return {AddItems:function(menu,tag,tagName){var E=(tagName=='A'||FCKSelection.HasAncestorNode('A'));if (E||FCK.GetNamedCommandState('Unlink')!=-1){var F=FCKSelection.MoveToAncestorNode('A');var G=(F&&F.name.length>0&&F.href.length==0);if (G) return;menu.AddSeparator();menu.AddItem('VisitLink',FCKLang.VisitLink);menu.AddSeparator();if (E) menu.AddItem('Link',FCKLang.EditLink,34);menu.AddItem('Unlink',FCKLang.RemoveLink,35);}}};case 'Image':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&!tag.getAttribute('_fckfakelement')){menu.AddSeparator();menu.AddItem('Image',FCKLang.ImageProperties,37);}}};case 'Anchor':return {AddItems:function(menu,tag,tagName){var F=FCKSelection.MoveToAncestorNode('A');var G=(F&&F.name.length>0);if (G||(tagName=='IMG'&&tag.getAttribute('_fckanchor'))){menu.AddSeparator();menu.AddItem('Anchor',FCKLang.AnchorProp,36);menu.AddItem('AnchorDelete',FCKLang.AnchorDelete);}}};case 'Flash':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&tag.getAttribute('_fckflash')){menu.AddSeparator();menu.AddItem('Flash',FCKLang.FlashProperties,38);}}};case 'Form':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('FORM')){menu.AddSeparator();menu.AddItem('Form',FCKLang.FormProp,48);}}};case 'Checkbox':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='checkbox'){menu.AddSeparator();menu.AddItem('Checkbox',FCKLang.CheckboxProp,49);}}};case 'Radio':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='radio'){menu.AddSeparator();menu.AddItem('Radio',FCKLang.RadioButtonProp,50);}}};case 'TextField':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&(tag.type=='text'||tag.type=='password')){menu.AddSeparator();menu.AddItem('TextField',FCKLang.TextFieldProp,51);}}};case 'HiddenField':return {AddItems:function(menu,tag,tagName){if (tagName=='IMG'&&tag.getAttribute('_fckinputhidden')){menu.AddSeparator();menu.AddItem('HiddenField',FCKLang.HiddenFieldProp,56);}}};case 'ImageButton':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&tag.type=='image'){menu.AddSeparator();menu.AddItem('ImageButton',FCKLang.ImageButtonProp,55);}}};case 'Button':return {AddItems:function(menu,tag,tagName){if (tagName=='INPUT'&&(tag.type=='button'||tag.type=='submit'||tag.type=='reset')){menu.AddSeparator();menu.AddItem('Button',FCKLang.ButtonProp,54);}}};case 'Select':return {AddItems:function(menu,tag,tagName){if (tagName=='SELECT'){menu.AddSeparator();menu.AddItem('Select',FCKLang.SelectionFieldProp,53);}}};case 'Textarea':return {AddItems:function(menu,tag,tagName){if (tagName=='TEXTAREA'){menu.AddSeparator();menu.AddItem('Textarea',FCKLang.TextareaProp,52);}}};case 'BulletedList':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('UL')){menu.AddSeparator();menu.AddItem('BulletedList',FCKLang.BulletedListProp,27);}}};case 'NumberedList':return {AddItems:function(menu,tag,tagName){if (FCKSelection.HasAncestorNode('OL')){menu.AddSeparator();menu.AddItem('NumberedList',FCKLang.NumberedListProp,26);}}};case 'DivContainer':return {AddItems:function(menu,tag,tagName){var J=FCKDomTools.GetSelectedDivContainers();if (J.length>0){menu.AddSeparator();menu.AddItem('EditDiv',FCKLang.EditDiv,75);menu.AddItem('DeleteDiv',FCKLang.DeleteDiv,76);}}};};return null;};function FCK_ContextMenu_OnBeforeOpen(){FCK.Events.FireEvent('OnSelectionChange');var A,sTagName;if ((A=FCKSelection.GetSelectedElement())) sTagName=A.tagName;var B=FCK.ContextMenu._InnerContextMenu;B.RemoveAllItems();var C=FCK.ContextMenu.Listeners;for (var i=0;i<C.length;i++) C[i].AddItems(B,A,sTagName);};function FCK_ContextMenu_OnItemClick(A){if (!FCKBrowserInfo.IsIE) FCK.Focus();FCKCommands.GetCommand(A.Name).Execute(A.CustomData);};
+var FCKHtmlIterator=function(A){this._sourceHtml=A;};FCKHtmlIterator.prototype={Next:function(){var A=this._sourceHtml;if (A==null) return null;var B=FCKRegexLib.HtmlTag.exec(A);var C=false;var D="";if (B){if (B.index>0){D=A.substr(0,B.index);this._sourceHtml=A.substr(B.index);}else{C=true;D=B[0];this._sourceHtml=A.substr(B[0].length);}}else{D=A;this._sourceHtml=null;};return { 'isTag':C,'value':D };},Each:function(A){var B;while ((B=this.Next())) A(B.isTag,B.value);}};var FCKHtmlIterator=function(A){this._sourceHtml=A;};FCKHtmlIterator.prototype={Next:function(){var A=this._sourceHtml;if (A==null) return null;var B=FCKRegexLib.HtmlTag.exec(A);var C=false;var D="";if (B){if (B.index>0){D=A.substr(0,B.index);this._sourceHtml=A.substr(B.index);}else{C=true;D=B[0];this._sourceHtml=A.substr(B[0].length);}}else{D=A;this._sourceHtml=null;};return { 'isTag':C,'value':D };},Each:function(A){var B;while ((B=this.Next())) A(B.isTag,B.value);}};
+var FCKPlugin=function(A,B,C){this.Name=A;this.BasePath=C?C:FCKConfig.PluginsPath;this.Path=this.BasePath+A+'/';if (!B||B.length==0) this.AvailableLangs=[];else this.AvailableLangs=B.split(',');};FCKPlugin.prototype.Load=function(){if (this.AvailableLangs.length>0){var A;if (this.AvailableLangs.IndexOf(FCKLanguageManager.ActiveLanguage.Code)>=0) A=FCKLanguageManager.ActiveLanguage.Code;else A=this.AvailableLangs[0];LoadScript(this.Path+'lang/'+A+'.js');};LoadScript(this.Path+'fckplugin.js');};
+var FCKPlugins=FCK.Plugins={};FCKPlugins.ItemsCount=0;FCKPlugins.Items={};FCKPlugins.Load=function(){var A=FCKPlugins.Items;for (var i=0;i<FCKConfig.Plugins.Items.length;i++){var B=FCKConfig.Plugins.Items[i];var C=A[B[0]]=new FCKPlugin(B[0],B[1],B[2]);FCKPlugins.ItemsCount++;};for (var s in A) A[s].Load();FCKPlugins.Load=null;};
diff --git a/httemplate/elements/fckeditor/editor/lang/_getfontformat.html b/httemplate/elements/fckeditor/editor/lang/_getfontformat.html
new file mode 100644
index 0000000..a408642
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/_getfontformat.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+-->
+<html>
+ <head>
+ <title></title>
+ </head>
+ <script language="javascript">
+
+window.onload = function()
+{
+ var oRange = document.selection.createRange() ;
+
+ var sNormal ;
+ var sFormats = '' ;
+ for ( var i = 1 ; i <= 9 ; i++ )
+ {
+ oRange.moveToElementText( document.getElementById( 'x' + i ) ) ;
+ sFormats += oRange.queryCommandValue( 'FormatBlock' ) ;
+ if ( i == 1 )
+ sNormal = sFormats ;
+ sFormats += ';' ;
+ }
+
+ document.getElementById('xFontFormats').innerHTML = sFormats + sNormal + ' (DIV)' ;
+}
+ </script>
+ <body>
+ <table width="70%" align="center">
+ <tr>
+ <td>
+ <h3>FontFormats Localization</h3>
+ <p>
+ IE has some limits when handling the "Font Format". It actually uses localized
+ strings to retrieve the current format value. This makes it very difficult to
+ make a system that works on every single computer in the world.
+ </p>
+ <p>
+ With FCKeditor, this problem impacts in the "Format" toolbar command that
+ doesn't reflects the format of the current cursor position.
+ </p>
+ <p>
+ There is only one way to make it work. We must localize FCKeditor using the
+ strings used by IE. In this way, we will have the expected behavior at least
+ when using FCKeditor in the same language as the browser. So, when localizing
+ FCKeditor, go to a computer with IE in the target language, open this page and
+ use the following string to the "FontFormats" value:
+ </p>
+ <div style="white-space: nowrap">
+ FontFormats : "<span id="xFontFormats" style="COLOR: #000099"></span>",
+ </div>
+ </td>
+ </tr>
+ </table>
+ <div style="DISPLAY: none">
+ <p id="x1">&nbsp;</p>
+ <pre id="x2">&nbsp;</pre>
+ <address id="x3">&nbsp;</address>
+ <h1 id="x4">&nbsp;</h1>
+ <h2 id="x5">&nbsp;</h2>
+ <h3 id="x6">&nbsp;</h3>
+ <h4 id="x7">&nbsp;</h4>
+ <h5 id="x8">&nbsp;</h5>
+ <h6 id="x9">&nbsp;</h6>
+ </div>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/lang/_translationstatus.txt b/httemplate/elements/fckeditor/editor/lang/_translationstatus.txt
new file mode 100644
index 0000000..f5eade9
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/_translationstatus.txt
@@ -0,0 +1,79 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Translations Status.
+ */
+
+af.js Found: 396 Missing: 36
+ar.js Found: 420 Missing: 12
+bg.js Found: 373 Missing: 59
+bn.js Found: 380 Missing: 52
+bs.js Found: 226 Missing: 206
+ca.js Found: 420 Missing: 12
+cs.js Found: 432 Missing: 0
+da.js Found: 419 Missing: 13
+de.js Found: 420 Missing: 12
+el.js Found: 396 Missing: 36
+en-au.js Found: 423 Missing: 9
+en-ca.js Found: 423 Missing: 9
+en-uk.js Found: 423 Missing: 9
+eo.js Found: 346 Missing: 86
+es.js Found: 428 Missing: 4
+et.js Found: 411 Missing: 21
+eu.js Found: 420 Missing: 12
+fa.js Found: 413 Missing: 19
+fi.js Found: 427 Missing: 5
+fo.js Found: 420 Missing: 12
+fr-ca.js Found: 419 Missing: 13
+fr.js Found: 428 Missing: 4
+gl.js Found: 381 Missing: 51
+gu.js Found: 411 Missing: 21
+he.js Found: 428 Missing: 4
+hi.js Found: 420 Missing: 12
+hr.js Found: 420 Missing: 12
+hu.js Found: 411 Missing: 21
+is.js Found: 428 Missing: 4
+it.js Found: 410 Missing: 22
+ja.js Found: 420 Missing: 12
+km.js Found: 370 Missing: 62
+ko.js Found: 391 Missing: 41
+lt.js Found: 428 Missing: 4
+lv.js Found: 381 Missing: 51
+mn.js Found: 411 Missing: 21
+ms.js Found: 352 Missing: 80
+nb.js Found: 414 Missing: 18
+nl.js Found: 420 Missing: 12
+no.js Found: 414 Missing: 18
+pl.js Found: 412 Missing: 20
+pt-br.js Found: 411 Missing: 21
+pt.js Found: 381 Missing: 51
+ro.js Found: 410 Missing: 22
+ru.js Found: 427 Missing: 5
+sk.js Found: 420 Missing: 12
+sl.js Found: 426 Missing: 6
+sr-latn.js Found: 368 Missing: 64
+sr.js Found: 368 Missing: 64
+sv.js Found: 431 Missing: 1
+th.js Found: 393 Missing: 39
+tr.js Found: 428 Missing: 4
+uk.js Found: 419 Missing: 13
+vi.js Found: 419 Missing: 13
+zh-cn.js Found: 428 Missing: 4
+zh.js Found: 423 Missing: 9
diff --git a/httemplate/elements/fckeditor/editor/lang/af.js b/httemplate/elements/fckeditor/editor/lang/af.js
new file mode 100644
index 0000000..ea23385
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/af.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Afrikaans language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Vou Gereedskaps balk toe",
+ToolbarExpand : "Vou Gereedskaps balk oop",
+
+// Toolbar Items and Context Menu
+Save : "Bewaar",
+NewPage : "Nuwe Bladsy",
+Preview : "Voorskou",
+Cut : "Uitsny ",
+Copy : "Kopieer",
+Paste : "Byvoeg",
+PasteText : "Slegs inhoud byvoeg",
+PasteWord : "Van Word af byvoeg",
+Print : "Druk",
+SelectAll : "Selekteer alles",
+RemoveFormat : "Formaat verweider",
+InsertLinkLbl : "Skakel",
+InsertLink : "Skakel byvoeg/verander",
+RemoveLink : "Skakel verweider",
+VisitLink : "Open Link", //MISSING
+Anchor : "Plekhouer byvoeg/verander",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Beeld",
+InsertImage : "Beeld byvoeg/verander",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash byvoeg/verander",
+InsertTableLbl : "Tabel",
+InsertTable : "Tabel byvoeg/verander",
+InsertLineLbl : "Lyn",
+InsertLine : "Horisontale lyn byvoeg",
+InsertSpecialCharLbl: "Spesiaale karakter",
+InsertSpecialChar : "Spesiaale Karakter byvoeg",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Smiley byvoeg",
+About : "Meer oor FCKeditor",
+Bold : "Vet",
+Italic : "Skuins",
+Underline : "Onderstreep",
+StrikeThrough : "Gestreik",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Links rig",
+CenterJustify : "Rig Middel",
+RightJustify : "Regs rig",
+BlockJustify : "Blok paradeer",
+DecreaseIndent : "Paradeering verkort",
+IncreaseIndent : "Paradeering verleng",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Ont-skep",
+Redo : "Her-skep",
+NumberedListLbl : "Genommerde lys",
+NumberedList : "Genommerde lys byvoeg/verweider",
+BulletedListLbl : "Gepunkte lys",
+BulletedList : "Gepunkte lys byvoeg/verweider",
+ShowTableBorders : "Wys tabel kante",
+ShowDetails : "Wys informasie",
+Style : "Styl",
+FontFormat : "Karakter formaat",
+Font : "Karakters",
+FontSize : "Karakter grote",
+TextColor : "Karakter kleur",
+BGColor : "Agtergrond kleur",
+Source : "Source",
+Find : "Vind",
+Replace : "Vervang",
+SpellCheck : "Spelling nagaan",
+UniversalKeyboard : "Universeele Sleutelbord",
+PageBreakLbl : "Bladsy breek",
+PageBreak : "Bladsy breek byvoeg",
+
+Form : "Form",
+Checkbox : "HakBox",
+RadioButton : "PuntBox",
+TextField : "Byvoegbare karakter strook",
+Textarea : "Byvoegbare karakter area",
+HiddenField : "Blinde strook",
+Button : "Knop",
+SelectionField : "Opklapbare keuse strook",
+ImageButton : "Beeld knop",
+
+FitWindow : "Maksimaliseer venster grote",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Verander skakel",
+CellCM : "Cell",
+RowCM : "Ry",
+ColumnCM : "Kolom",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Ry verweider",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Kolom verweider",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Cell verweider",
+MergeCells : "Cell verenig",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Tabel verweider",
+CellProperties : "Cell eienskappe",
+TableProperties : "Tabel eienskappe",
+ImageProperties : "Beeld eienskappe",
+FlashProperties : "Flash eienskappe",
+
+AnchorProp : "Plekhouer eienskappe",
+ButtonProp : "Knop eienskappe",
+CheckboxProp : "HakBox eienskappe",
+HiddenFieldProp : "Blinde strook eienskappe",
+RadioButtonProp : "PuntBox eienskappe",
+ImageButtonProp : "Beeld knop eienskappe",
+TextFieldProp : "Karakter strook eienskappe",
+SelectionFieldProp : "Opklapbare keuse strook eienskappe",
+TextareaProp : "Karakter area eienskappe",
+FormProp : "Form eienskappe",
+
+FontFormats : "Normaal;Geformateerd;Adres;Opskrif 1;Opskrif 2;Opskrif 3;Opskrif 4;Opskrif 5;Opskrif 6;Normaal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML word verarbeit. U geduld asseblief...",
+Done : "Kompleet",
+PasteWordConfirm : "Die informasie wat U probeer byvoeg is warskynlik van Word. Wil U dit reinig voor die byvoeging?",
+NotCompatiblePaste : "Die instruksie is beskikbaar vir Internet Explorer weergawe 5.5 of hor. Wil U dir byvoeg sonder reiniging?",
+UnknownToolbarItem : "Unbekende gereedskaps balk item \"%1\"",
+UnknownCommand : "Unbekende instruksie naam \"%1\"",
+NotImplemented : "Instruksie is nie geimplementeer nie.",
+UnknownToolbarSet : "Gereedskaps balk \"%1\" bestaan nie",
+NoActiveX : "U browser sekuriteit instellings kan die funksies van die editor behinder. U moet die opsie \"Run ActiveX controls and plug-ins\" aktiveer. U ondervinding mag problematies geskiet of sekere funksionaliteit mag verhinder word.",
+BrowseServerBlocked : "Die vorraad venster word geblok! Verseker asseblief dat U die \"popup blocker\" instelling verander.",
+DialogBlocked : "Die dialoog venster vir verdere informasie word geblok. De-aktiveer asseblief die \"popup blocker\" instellings wat dit behinder.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Kanseleer",
+DlgBtnClose : "Sluit",
+DlgBtnBrowseServer : "Server deurblaai",
+DlgAdvancedTag : "Ingewikkeld",
+DlgOpOther : "<Ander>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Voeg asseblief die URL in",
+
+// General Dialogs Labels
+DlgGenNotSet : "<geen instelling>",
+DlgGenId : "Id",
+DlgGenLangDir : "Taal rigting",
+DlgGenLangDirLtr : "Links na regs (LTR)",
+DlgGenLangDirRtl : "Regs na links (RTL)",
+DlgGenLangCode : "Taal kode",
+DlgGenAccessKey : "Toegang sleutel",
+DlgGenName : "Naam",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Lang beskreiwing URL",
+DlgGenClass : "Skakel Tiepe",
+DlgGenTitle : "Voorbeveelings Titel",
+DlgGenContType : "Voorbeveelings inhoud soort",
+DlgGenLinkCharset : "Geskakelde voorbeeld karakterstel",
+DlgGenStyle : "Styl",
+
+// Image Dialog
+DlgImgTitle : "Beeld eienskappe",
+DlgImgInfoTab : "Beeld informasie",
+DlgImgBtnUpload : "Stuur dit na die Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Uplaai",
+DlgImgAlt : "Alternatiewe beskrywing",
+DlgImgWidth : "Weidte",
+DlgImgHeight : "Hoogde",
+DlgImgLockRatio : "Behou preporsie",
+DlgBtnResetSize : "Herstel groote",
+DlgImgBorder : "Kant",
+DlgImgHSpace : "HSpasie",
+DlgImgVSpace : "VSpasie",
+DlgImgAlign : "Paradeer",
+DlgImgAlignLeft : "Links",
+DlgImgAlignAbsBottom: "Abs Onder",
+DlgImgAlignAbsMiddle: "Abs Middel",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Onder",
+DlgImgAlignMiddle : "Middel",
+DlgImgAlignRight : "Regs",
+DlgImgAlignTextTop : "Text Bo",
+DlgImgAlignTop : "Bo",
+DlgImgPreview : "Voorskou",
+DlgImgAlertUrl : "Voeg asseblief Beeld URL in.",
+DlgImgLinkTab : "Skakel",
+
+// Flash Dialog
+DlgFlashTitle : "Flash eienskappe",
+DlgFlashChkPlay : "Automaties Speel",
+DlgFlashChkLoop : "Herhaling",
+DlgFlashChkMenu : "Laat Flash Menu toe",
+DlgFlashScale : "Scale",
+DlgFlashScaleAll : "Wys alles",
+DlgFlashScaleNoBorder : "Geen kante",
+DlgFlashScaleFit : "Presiese pas",
+
+// Link Dialog
+DlgLnkWindowTitle : "Skakel",
+DlgLnkInfoTab : "Skakel informasie",
+DlgLnkTargetTab : "Mikpunt",
+
+DlgLnkType : "Skakel soort",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Skakel na plekhouers in text",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<ander>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Kies 'n plekhouer",
+DlgLnkAnchorByName : "Volgens plekhouer naam",
+DlgLnkAnchorById : "Volgens element Id",
+DlgLnkNoAnchors : "(Geen plekhouers beskikbaar in dokument}",
+DlgLnkEMail : "E-Mail Adres",
+DlgLnkEMailSubject : "Boodskap Opskrif",
+DlgLnkEMailBody : "Boodskap Inhoud",
+DlgLnkUpload : "Oplaai",
+DlgLnkBtnUpload : "Stuur na Server",
+
+DlgLnkTarget : "Mikpunt",
+DlgLnkTargetFrame : "<raam>",
+DlgLnkTargetPopup : "<popup venster>",
+DlgLnkTargetBlank : "Nuwe Venster (_blank)",
+DlgLnkTargetParent : "Vorige Venster (_parent)",
+DlgLnkTargetSelf : "Selfde Venster (_self)",
+DlgLnkTargetTop : "Boonste Venster (_top)",
+DlgLnkTargetFrameName : "Mikpunt Venster Naam",
+DlgLnkPopWinName : "Popup Venster Naam",
+DlgLnkPopWinFeat : "Popup Venster Geaartheid",
+DlgLnkPopResize : "Verstelbare Groote",
+DlgLnkPopLocation : "Adres Balk",
+DlgLnkPopMenu : "Menu Balk",
+DlgLnkPopScroll : "Gleibalkstuk",
+DlgLnkPopStatus : "Status Balk",
+DlgLnkPopToolbar : "Gereedskap Balk",
+DlgLnkPopFullScrn : "Voll Skerm (IE)",
+DlgLnkPopDependent : "Afhanklik (Netscape)",
+DlgLnkPopWidth : "Weite",
+DlgLnkPopHeight : "Hoogde",
+DlgLnkPopLeft : "Links Posisie",
+DlgLnkPopTop : "Bo Posisie",
+
+DlnLnkMsgNoUrl : "Voeg asseblief die URL in",
+DlnLnkMsgNoEMail : "Voeg asseblief die e-mail adres in",
+DlnLnkMsgNoAnchor : "Kies asseblief 'n plekhouer",
+DlnLnkMsgInvPopName : "Die popup naam moet begin met alphabetiese karakters sonder spasies.",
+
+// Color Dialog
+DlgColorTitle : "Kies Kleur",
+DlgColorBtnClear : "Maak skoon",
+DlgColorHighlight : "Highlight",
+DlgColorSelected : "Geselekteer",
+
+// Smiley Dialog
+DlgSmileyTitle : "Voeg Smiley by",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Kies spesiale karakter",
+
+// Table Dialog
+DlgTableTitle : "Tabel eienskappe",
+DlgTableRows : "Reie",
+DlgTableColumns : "Kolome",
+DlgTableBorder : "Kant groote",
+DlgTableAlign : "Parideering",
+DlgTableAlignNotSet : "<geen instelling>",
+DlgTableAlignLeft : "Links",
+DlgTableAlignCenter : "Middel",
+DlgTableAlignRight : "Regs",
+DlgTableWidth : "Weite",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Hoogde",
+DlgTableCellSpace : "Cell spasieering",
+DlgTableCellPad : "Cell buffer",
+DlgTableCaption : "Beskreiwing",
+DlgTableSummary : "Opsomming",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Cell eienskappe",
+DlgCellWidth : "Weite",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Hoogde",
+DlgCellWordWrap : "Woord Wrap",
+DlgCellWordWrapNotSet : "<geen instelling>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nee",
+DlgCellHorAlign : "Horisontale rigting",
+DlgCellHorAlignNotSet : "<geen instelling>",
+DlgCellHorAlignLeft : "Links",
+DlgCellHorAlignCenter : "Middel",
+DlgCellHorAlignRight: "Regs",
+DlgCellVerAlign : "Vertikale rigting",
+DlgCellVerAlignNotSet : "<geen instelling>",
+DlgCellVerAlignTop : "Bo",
+DlgCellVerAlignMiddle : "Middel",
+DlgCellVerAlignBottom : "Onder",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Rei strekking",
+DlgCellCollSpan : "Kolom strekking",
+DlgCellBackColor : "Agtergrond Kleur",
+DlgCellBorderColor : "Kant Kleur",
+DlgCellBtnSelect : "Keuse...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Vind",
+DlgFindFindBtn : "Vind",
+DlgFindNotFoundMsg : "Die gespesifiseerde karakters word nie gevind nie.",
+
+// Replace Dialog
+DlgReplaceTitle : "Vervang",
+DlgReplaceFindLbl : "Soek wat:",
+DlgReplaceReplaceLbl : "Vervang met:",
+DlgReplaceCaseChk : "Vergelyk karakter skryfweise",
+DlgReplaceReplaceBtn : "Vervang",
+DlgReplaceReplAllBtn : "Vervang alles",
+DlgReplaceWordChk : "Vergelyk komplete woord",
+
+// Paste Operations / Dialog
+PasteErrorCut : "U browser se sekuriteit instelling behinder die uitsny aksie. Gebruik asseblief die sleutel kombenasie(Ctrl+X).",
+PasteErrorCopy : "U browser se sekuriteit instelling behinder die kopieerings aksie. Gebruik asseblief die sleutel kombenasie(Ctrl+C).",
+
+PasteAsText : "Voeg slegs karakters by",
+PasteFromWord : "Byvoeging uit Word",
+
+DlgPasteMsg2 : "Voeg asseblief die inhoud in die gegewe box by met sleutel kombenasie(<STRONG>Ctrl+V</STRONG>) en druk <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignoreer karakter soort defenisies",
+DlgPasteRemoveStyles : "Verweider Styl defenisies",
+
+// Color Picker
+ColorAutomatic : "Automaties",
+ColorMoreColors : "Meer Kleure...",
+
+// Document Properties
+DocProps : "Dokument Eienskappe",
+
+// Anchor Dialog
+DlgAnchorTitle : "Plekhouer Eienskappe",
+DlgAnchorName : "Plekhouer Naam",
+DlgAnchorErrorName : "Voltooi die plekhouer naam asseblief",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nie in woordeboek nie",
+DlgSpellChangeTo : "Verander na",
+DlgSpellBtnIgnore : "Ignoreer",
+DlgSpellBtnIgnoreAll : "Ignoreer na-volgende",
+DlgSpellBtnReplace : "Vervang",
+DlgSpellBtnReplaceAll : "vervang na-volgende",
+DlgSpellBtnUndo : "Ont-skep",
+DlgSpellNoSuggestions : "- Geen voorstel -",
+DlgSpellProgress : "Spelling word beproef...",
+DlgSpellNoMispell : "Spellproef kompleet: Geen foute",
+DlgSpellNoChanges : "Spellproef kompleet: Geen woord veranderings",
+DlgSpellOneChange : "Spellproef kompleet: Een woord verander",
+DlgSpellManyChanges : "Spellproef kompleet: %1 woorde verander",
+
+IeSpellDownload : "Geen Spellproefer geinstaleer nie. Wil U dit aflaai?",
+
+// Button Dialog
+DlgButtonText : "Karakters (Waarde)",
+DlgButtonType : "Soort",
+DlgButtonTypeBtn : "Knop",
+DlgButtonTypeSbm : "Indien",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Naam",
+DlgCheckboxValue : "Waarde",
+DlgCheckboxSelected : "Uitgekies",
+
+// Form Dialog
+DlgFormName : "Naam",
+DlgFormAction : "Aksie",
+DlgFormMethod : "Metode",
+
+// Select Field Dialog
+DlgSelectName : "Naam",
+DlgSelectValue : "Waarde",
+DlgSelectSize : "Grote",
+DlgSelectLines : "lyne",
+DlgSelectChkMulti : "Laat meerere keuses toe",
+DlgSelectOpAvail : "Beskikbare Opsies",
+DlgSelectOpText : "Karakters",
+DlgSelectOpValue : "Waarde",
+DlgSelectBtnAdd : "Byvoeg",
+DlgSelectBtnModify : "Verander",
+DlgSelectBtnUp : "Op",
+DlgSelectBtnDown : "Af",
+DlgSelectBtnSetValue : "Stel as uitgekiesde waarde",
+DlgSelectBtnDelete : "Verweider",
+
+// Textarea Dialog
+DlgTextareaName : "Naam",
+DlgTextareaCols : "Kolom",
+DlgTextareaRows : "Reie",
+
+// Text Field Dialog
+DlgTextName : "Naam",
+DlgTextValue : "Waarde",
+DlgTextCharWidth : "Karakter weite",
+DlgTextMaxChars : "Maximale karakters",
+DlgTextType : "Soort",
+DlgTextTypeText : "Karakters",
+DlgTextTypePass : "Wagwoord",
+
+// Hidden Field Dialog
+DlgHiddenName : "Naam",
+DlgHiddenValue : "Waarde",
+
+// Bulleted List Dialog
+BulletedListProp : "Gepunkte lys eienskappe",
+NumberedListProp : "Genommerde lys eienskappe",
+DlgLstStart : "Begin",
+DlgLstType : "Soort",
+DlgLstTypeCircle : "Sirkel",
+DlgLstTypeDisc : "Skyf",
+DlgLstTypeSquare : "Vierkant",
+DlgLstTypeNumbers : "Nommer (1, 2, 3)",
+DlgLstTypeLCase : "Klein Letters (a, b, c)",
+DlgLstTypeUCase : "Hoof Letters (A, B, C)",
+DlgLstTypeSRoman : "Klein Romeinse nommers (i, ii, iii)",
+DlgLstTypeLRoman : "Groot Romeinse nommers (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Algemeen",
+DlgDocBackTab : "Agtergrond",
+DlgDocColorsTab : "Kleure en Rante",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Bladsy Opskrif",
+DlgDocLangDir : "Taal rigting",
+DlgDocLangDirLTR : "Link na Regs (LTR)",
+DlgDocLangDirRTL : "Regs na Links (RTL)",
+DlgDocLangCode : "Taal Kode",
+DlgDocCharSet : "Karakterstel Kodeering",
+DlgDocCharSetCE : "Sentraal Europa",
+DlgDocCharSetCT : "Chinees Traditioneel (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Grieks",
+DlgDocCharSetJP : "Japanees",
+DlgDocCharSetKR : "Koreans",
+DlgDocCharSetTR : "Turks",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "Ander Karakterstel Kodeering",
+
+DlgDocDocType : "Dokument Opskrif Soort",
+DlgDocDocTypeOther : "Ander Dokument Opskrif Soort",
+DlgDocIncXHTML : "Voeg XHTML verklaring by",
+DlgDocBgColor : "Agtergrond kleur",
+DlgDocBgImage : "Agtergrond Beeld URL",
+DlgDocBgNoScroll : "Vasgeklemde Agtergrond",
+DlgDocCText : "Karakters",
+DlgDocCLink : "Skakel",
+DlgDocCVisited : "Besoekte Skakel",
+DlgDocCActive : "Aktiewe Skakel",
+DlgDocMargins : "Bladsy Rante",
+DlgDocMaTop : "Bo",
+DlgDocMaLeft : "Links",
+DlgDocMaRight : "Regs",
+DlgDocMaBottom : "Onder",
+DlgDocMeIndex : "Dokument Index Sleutelwoorde(comma verdeelt)",
+DlgDocMeDescr : "Dokument Beskrywing",
+DlgDocMeAuthor : "Skrywer",
+DlgDocMeCopy : "Kopiereg",
+DlgDocPreview : "Voorskou",
+
+// Templates Dialog
+Templates : "Templates",
+DlgTemplatesTitle : "Inhoud Templates",
+DlgTemplatesSelMsg : "Kies die template om te gebruik in die editor<br>(Inhoud word vervang!):",
+DlgTemplatesLoading : "Templates word gelaai. U geduld asseblief...",
+DlgTemplatesNoTpl : "(Geen templates gedefinieerd)",
+DlgTemplatesReplace : "Vervang bestaande inhoud",
+
+// About Dialog
+DlgAboutAboutTab : "Meer oor",
+DlgAboutBrowserInfoTab : "Blaai Informasie deur",
+DlgAboutLicenseTab : "Lesensie",
+DlgAboutVersion : "weergawe",
+DlgAboutInfo : "Vir meer informasie gaan na ",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ar.js b/httemplate/elements/fckeditor/editor/lang/ar.js
new file mode 100644
index 0000000..8bbbca6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ar.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Arabic language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "rtl",
+
+ToolbarCollapse : "ضم شريط الأدوات",
+ToolbarExpand : "تمدد شريط الأدوات",
+
+// Toolbar Items and Context Menu
+Save : "Ø­Ùظ",
+NewPage : "صÙحة جديدة",
+Preview : "معاينة الصÙحة",
+Cut : "قص",
+Copy : "نسخ",
+Paste : "لصق",
+PasteText : "لصق كنص بسيط",
+PasteWord : "لصق من وورد",
+Print : "طباعة",
+SelectAll : "تحديد الكل",
+RemoveFormat : "إزالة التنسيقات",
+InsertLinkLbl : "رابط",
+InsertLink : "إدراج/تحرير رابط",
+RemoveLink : "إزالة رابط",
+VisitLink : "اÙتح الرابط",
+Anchor : "إدراج/تحرير إشارة مرجعية",
+AnchorDelete : "إزالة إشارة مرجعية",
+InsertImageLbl : "صورة",
+InsertImage : "إدراج/تحرير صورة",
+InsertFlashLbl : "Ùلاش",
+InsertFlash : "إدراج/تحرير Ùيلم Ùلاش",
+InsertTableLbl : "جدول",
+InsertTable : "إدراج/تحرير جدول",
+InsertLineLbl : "خط Ùاصل",
+InsertLine : "إدراج خط Ùاصل",
+InsertSpecialCharLbl: "رموز",
+InsertSpecialChar : "إدراج رموز..Ù",
+InsertSmileyLbl : "ابتسامات",
+InsertSmiley : "إدراج ابتسامات",
+About : "حول FCKeditor",
+Bold : "غامق",
+Italic : "مائل",
+Underline : "تسطير",
+StrikeThrough : "يتوسطه خط",
+Subscript : "منخÙض",
+Superscript : "مرتÙع",
+LeftJustify : "محاذاة إلى اليسار",
+CenterJustify : "توسيط",
+RightJustify : "محاذاة إلى اليمين",
+BlockJustify : "ضبط",
+DecreaseIndent : "إنقاص المساÙØ© البادئة",
+IncreaseIndent : "زيادة المساÙØ© البادئة",
+Blockquote : "اقتباس",
+CreateDiv : "إنشاء حاوية Div",
+EditDiv : "تعديل حاوية Div",
+DeleteDiv : "إزالة حاوية Div",
+Undo : "تراجع",
+Redo : "إعادة",
+NumberedListLbl : "تعداد رقمي",
+NumberedList : "إدراج/إلغاء تعداد رقمي",
+BulletedListLbl : "تعداد نقطي",
+BulletedList : "إدراج/إلغاء تعداد نقطي",
+ShowTableBorders : "معاينة حدود الجداول",
+ShowDetails : "معاينة التÙاصيل",
+Style : "نمط",
+FontFormat : "تنسيق",
+Font : "خط",
+FontSize : "حجم الخط",
+TextColor : "لون النص",
+BGColor : "لون الخلÙية",
+Source : "Ø´Ùرة المصدر",
+Find : "بحث",
+Replace : "إستبدال",
+SpellCheck : "تدقيق إملائي",
+UniversalKeyboard : "لوحة المÙاتيح العالمية",
+PageBreakLbl : "Ùصل الصÙحة",
+PageBreak : "إدخال صÙحة جديدة",
+
+Form : "نموذج",
+Checkbox : "خانة إختيار",
+RadioButton : "زر خيار",
+TextField : "مربع نص",
+Textarea : "ناحية نص",
+HiddenField : "إدراج حقل Ø®ÙÙŠ",
+Button : "زر ضغط",
+SelectionField : "قائمة منسدلة",
+ImageButton : "زر صورة",
+
+FitWindow : "تكبير حجم المحرر",
+ShowBlocks : "مخطط تÙصيلي",
+
+// Context Menu
+EditLink : "تحرير رابط",
+CellCM : "خلية",
+RowCM : "صÙ",
+ColumnCM : "عمود",
+InsertRowAfter : "إدراج ص٠بعد",
+InsertRowBefore : "إدراج ص٠قبل",
+DeleteRows : "حذ٠صÙÙˆÙ",
+InsertColumnAfter : "إدراج عمود بعد",
+InsertColumnBefore : "إدراج عمود قبل",
+DeleteColumns : "حذ٠أعمدة",
+InsertCellAfter : "إدراج خلية بعد",
+InsertCellBefore : "إدراج خلية قبل",
+DeleteCells : "حذ٠خلايا",
+MergeCells : "دمج خلايا",
+MergeRight : "دمج لليمين",
+MergeDown : "دمج للأسÙÙ„",
+HorizontalSplitCell : "تقسيم الخلية Ø£Ùقياً",
+VerticalSplitCell : "تقسيم الخلية عمودياً",
+TableDelete : "حذ٠الجدول",
+CellProperties : "خصائص الخلية",
+TableProperties : "خصائص الجدول",
+ImageProperties : "خصائص الصورة",
+FlashProperties : "خصائص Ùيلم الÙلاش",
+
+AnchorProp : "خصائص الإشارة المرجعية",
+ButtonProp : "خصائص زر الضغط",
+CheckboxProp : "خصائص خانة الإختيار",
+HiddenFieldProp : "خصائص الحقل الخÙÙŠ",
+RadioButtonProp : "خصائص زر الخيار",
+ImageButtonProp : "خصائص زر الصورة",
+TextFieldProp : "خصائص مربع النص",
+SelectionFieldProp : "خصائص القائمة المنسدلة",
+TextareaProp : "خصائص ناحية النص",
+FormProp : "خصائص النموذج",
+
+FontFormats : "عادي;منسّق;دوس;العنوان 1;العنوان 2;العنوان 3;العنوان 4;العنوان 5;العنوان 6",
+
+// Alerts and Messages
+ProcessingXHTML : "إنتظر قليلاً ريثما تتم معالَجة†XHTML. لن يستغرق طويلاً...",
+Done : "تم",
+PasteWordConfirm : "يبدو أن النص المراد لصقه منسوخ من برنامج وورد. هل تود تنظيÙÙ‡ قبل الشروع ÙÙŠ عملية اللصق؟",
+NotCompatiblePaste : "هذه الميزة تحتاج لمتصÙØ­ من النوعInternet Explorer إصدار 5.5 Ùما Ùوق. هل تود اللصق دون تنظي٠الكود؟",
+UnknownToolbarItem : "عنصر شريط أدوات غير معرو٠\"%1\"",
+UnknownCommand : "أمر غير معرو٠\"%1\"",
+NotImplemented : "لم يتم دعم هذا الأمر",
+UnknownToolbarSet : "لم أتمكن من العثور على طقم الأدوات \"%1\" ",
+NoActiveX : "لتأمين متصÙحك يجب أن تحدد بعض مميزات المحرر. يتوجب عليك تمكين الخيار \"Run ActiveX controls and plug-ins\". قد تواجة أخطاء وتلاحظ مميزات Ù…Ùقودة",
+BrowseServerBlocked : "لايمكن Ùتح مصدر المتصÙØ­. Ùضلا يجب التأكد بأن جميع موانع النواÙØ° المنبثقة معطلة",
+DialogBlocked : "لايمكن Ùتح ناÙذة الحوار . Ùضلا تأكد من أن مانع النواÙØ° المنبثة معطل .",
+VisitLinkBlocked : "لا يمكن Ùتح ناÙذة جديدة. تأكد من إيقا٠كل مانعي Ùتح النواÙØ° من العمل.",
+
+// Dialogs
+DlgBtnOK : "مواÙÙ‚",
+DlgBtnCancel : "إلغاء الأمر",
+DlgBtnClose : "إغلاق",
+DlgBtnBrowseServer : "تصÙØ­ الخادم",
+DlgAdvancedTag : "متقدم",
+DlgOpOther : "<أخرى>",
+DlgInfoTab : "معلومات",
+DlgAlertUrl : "الرجاء كتابة عنوان الإنترنت",
+
+// General Dialogs Labels
+DlgGenNotSet : "<بدون تحديد>",
+DlgGenId : "الرقم",
+DlgGenLangDir : "إتجاه النص",
+DlgGenLangDirLtr : "اليسار لليمين (LTR)",
+DlgGenLangDirRtl : "اليمين لليسار (RTL)",
+DlgGenLangCode : "رمز اللغة",
+DlgGenAccessKey : "Ù…Ùاتيح الإختصار",
+DlgGenName : "الاسم",
+DlgGenTabIndex : "الترتيب",
+DlgGenLongDescr : "عنوان الوص٠المÙصّل",
+DlgGenClass : "Ùئات التنسيق",
+DlgGenTitle : "تلميح الشاشة",
+DlgGenContType : "نوع التلميح",
+DlgGenLinkCharset : "ترميز المادة المطلوبة",
+DlgGenStyle : "نمط",
+
+// Image Dialog
+DlgImgTitle : "خصائص الصورة",
+DlgImgInfoTab : "معلومات الصورة",
+DlgImgBtnUpload : "أرسلها للخادم",
+DlgImgURL : "موقع الصورة",
+DlgImgUpload : "رÙع",
+DlgImgAlt : "الوصÙ",
+DlgImgWidth : "العرض",
+DlgImgHeight : "الإرتÙاع",
+DlgImgLockRatio : "تناسق الحجم",
+DlgBtnResetSize : "إستعادة الحجم الأصلي",
+DlgImgBorder : "سمك الحدود",
+DlgImgHSpace : "تباعد Ø£Ùقي",
+DlgImgVSpace : "تباعد عمودي",
+DlgImgAlign : "محاذاة",
+DlgImgAlignLeft : "يسار",
+DlgImgAlignAbsBottom: "أسÙÙ„ النص",
+DlgImgAlignAbsMiddle: "وسط السطر",
+DlgImgAlignBaseline : "على السطر",
+DlgImgAlignBottom : "أسÙÙ„",
+DlgImgAlignMiddle : "وسط",
+DlgImgAlignRight : "يمين",
+DlgImgAlignTextTop : "أعلى النص",
+DlgImgAlignTop : "أعلى",
+DlgImgPreview : "معاينة",
+DlgImgAlertUrl : "Ùضلاً أكتب الموقع الذي توجد عليه هذه الصورة.",
+DlgImgLinkTab : "الرابط",
+
+// Flash Dialog
+DlgFlashTitle : "خصائص Ùيلم الÙلاش",
+DlgFlashChkPlay : "تشغيل تلقائي",
+DlgFlashChkLoop : "تكرار",
+DlgFlashChkMenu : "تمكين قائمة Ùيلم الÙلاش",
+DlgFlashScale : "الحجم",
+DlgFlashScaleAll : "إظهار الكل",
+DlgFlashScaleNoBorder : "بلا حدود",
+DlgFlashScaleFit : "ضبط تام",
+
+// Link Dialog
+DlgLnkWindowTitle : "إرتباط تشعبي",
+DlgLnkInfoTab : "معلومات الرابط",
+DlgLnkTargetTab : "الهدÙ",
+
+DlgLnkType : "نوع الربط",
+DlgLnkTypeURL : "العنوان",
+DlgLnkTypeAnchor : "مكان ÙÙŠ هذا المستند",
+DlgLnkTypeEMail : "بريد إلكتروني",
+DlgLnkProto : "البروتوكول",
+DlgLnkProtoOther : "<أخرى>",
+DlgLnkURL : "الموقع",
+DlgLnkAnchorSel : "اختر علامة مرجعية",
+DlgLnkAnchorByName : "حسب اسم العلامة",
+DlgLnkAnchorById : "حسب تعري٠العنصر",
+DlgLnkNoAnchors : "(لا يوجد علامات مرجعية ÙÙŠ هذا المستند)",
+DlgLnkEMail : "عنوان بريد إلكتروني",
+DlgLnkEMailSubject : "موضوع الرسالة",
+DlgLnkEMailBody : "محتوى الرسالة",
+DlgLnkUpload : "رÙع",
+DlgLnkBtnUpload : "أرسلها للخادم",
+
+DlgLnkTarget : "الهدÙ",
+DlgLnkTargetFrame : "<إطار>",
+DlgLnkTargetPopup : "<ناÙذة منبثقة>",
+DlgLnkTargetBlank : "إطار جديد (_blank)",
+DlgLnkTargetParent : "الإطار الأصل (_parent)",
+DlgLnkTargetSelf : "Ù†Ùس الإطار (_self)",
+DlgLnkTargetTop : "صÙحة كاملة (_top)",
+DlgLnkTargetFrameName : "اسم الإطار الهدÙ",
+DlgLnkPopWinName : "تسمية الناÙذة المنبثقة",
+DlgLnkPopWinFeat : "خصائص الناÙذة المنبثقة",
+DlgLnkPopResize : "قابلة للتحجيم",
+DlgLnkPopLocation : "شريط العنوان",
+DlgLnkPopMenu : "القوائم الرئيسية",
+DlgLnkPopScroll : "أشرطة التمرير",
+DlgLnkPopStatus : "شريط الحالة السÙلي",
+DlgLnkPopToolbar : "شريط الأدوات",
+DlgLnkPopFullScrn : "ملئ الشاشة (IE)",
+DlgLnkPopDependent : "تابع (Netscape)",
+DlgLnkPopWidth : "العرض",
+DlgLnkPopHeight : "الإرتÙاع",
+DlgLnkPopLeft : "التمركز لليسار",
+DlgLnkPopTop : "التمركز للأعلى",
+
+DlnLnkMsgNoUrl : "Ùضلاً أدخل عنوان الموقع الذي يشير إليه الرابط",
+DlnLnkMsgNoEMail : "Ùضلاً أدخل عنوان البريد الإلكتروني",
+DlnLnkMsgNoAnchor : "Ùضلاً حدد العلامة المرجعية المرغوبة",
+DlnLnkMsgInvPopName : "اسم الناÙذة المنبثقة يجب أن يبدأ بحر٠أبجدي دون مساÙات",
+
+// Color Dialog
+DlgColorTitle : "اختر لوناً",
+DlgColorBtnClear : "مسح",
+DlgColorHighlight : "تحديد",
+DlgColorSelected : "إختيار",
+
+// Smiley Dialog
+DlgSmileyTitle : "إدراج إبتسامات ",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "إدراج رمز",
+
+// Table Dialog
+DlgTableTitle : "إدراج جدول",
+DlgTableRows : "صÙÙˆÙ",
+DlgTableColumns : "أعمدة",
+DlgTableBorder : "سمك الحدود",
+DlgTableAlign : "المحاذاة",
+DlgTableAlignNotSet : "<بدون تحديد>",
+DlgTableAlignLeft : "يسار",
+DlgTableAlignCenter : "وسط",
+DlgTableAlignRight : "يمين",
+DlgTableWidth : "العرض",
+DlgTableWidthPx : "بكسل",
+DlgTableWidthPc : "بالمئة",
+DlgTableHeight : "الإرتÙاع",
+DlgTableCellSpace : "تباعد الخلايا",
+DlgTableCellPad : "المساÙØ© البادئة",
+DlgTableCaption : "الوصÙ",
+DlgTableSummary : "الخلاصة",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "خصائص الخلية",
+DlgCellWidth : "العرض",
+DlgCellWidthPx : "بكسل",
+DlgCellWidthPc : "بالمئة",
+DlgCellHeight : "الإرتÙاع",
+DlgCellWordWrap : "التÙا٠النص",
+DlgCellWordWrapNotSet : "<بدون تحديد>",
+DlgCellWordWrapYes : "نعم",
+DlgCellWordWrapNo : "لا",
+DlgCellHorAlign : "المحاذاة الأÙقية",
+DlgCellHorAlignNotSet : "<بدون تحديد>",
+DlgCellHorAlignLeft : "يسار",
+DlgCellHorAlignCenter : "وسط",
+DlgCellHorAlignRight: "يمين",
+DlgCellVerAlign : "المحاذاة العمودية",
+DlgCellVerAlignNotSet : "<بدون تحديد>",
+DlgCellVerAlignTop : "أعلى",
+DlgCellVerAlignMiddle : "وسط",
+DlgCellVerAlignBottom : "أسÙÙ„",
+DlgCellVerAlignBaseline : "على السطر",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "إمتداد الصÙÙˆÙ",
+DlgCellCollSpan : "إمتداد الأعمدة",
+DlgCellBackColor : "لون الخلÙية",
+DlgCellBorderColor : "لون الحدود",
+DlgCellBtnSelect : "حدّد...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "بحث واستبدال",
+
+// Find Dialog
+DlgFindTitle : "بحث",
+DlgFindFindBtn : "ابحث",
+DlgFindNotFoundMsg : "لم يتم العثور على النص المحدد.",
+
+// Replace Dialog
+DlgReplaceTitle : "إستبدال",
+DlgReplaceFindLbl : "البحث عن:",
+DlgReplaceReplaceLbl : "إستبدال بـ:",
+DlgReplaceCaseChk : "مطابقة حالة الأحرÙ",
+DlgReplaceReplaceBtn : "إستبدال",
+DlgReplaceReplAllBtn : "إستبدال الكل",
+DlgReplaceWordChk : "الكلمة بالكامل Ùقط",
+
+// Paste Operations / Dialog
+PasteErrorCut : "الإعدادات الأمنية للمتصÙØ­ الذي تستخدمه تمنع القص التلقائي. Ùضلاً إستخدم لوحة المÙاتيح Ù„Ùعل ذلك (Ctrl+X).",
+PasteErrorCopy : "الإعدادات الأمنية للمتصÙØ­ الذي تستخدمه تمنع النسخ التلقائي. Ùضلاً إستخدم لوحة المÙاتيح Ù„Ùعل ذلك (Ctrl+C).",
+
+PasteAsText : "لصق كنص بسيط",
+PasteFromWord : "لصق من وورد",
+
+DlgPasteMsg2 : "الصق داخل الصندوق بإستخدام زرّي (<STRONG>Ctrl+V</STRONG>) ÙÙŠ لوحة المÙاتيح، ثم اضغط زر <STRONG>مواÙÙ‚</STRONG>.",
+DlgPasteSec : "نظراً لإعدادات الأمان الخاصة بمتصÙحك، لن يتمكن هذا المحرر من الوصول لمحتوى حاÙظتك، لذا وجب عليك لصق المحتوى مرة أخرى ÙÙŠ هذه الناÙذة.",
+DlgPasteIgnoreFont : "تجاهل تعريÙات أسماء الخطوط",
+DlgPasteRemoveStyles : "إزالة تعريÙات الأنماط",
+
+// Color Picker
+ColorAutomatic : "تلقائي",
+ColorMoreColors : "ألوان إضاÙية...",
+
+// Document Properties
+DocProps : "خصائص الصÙحة",
+
+// Anchor Dialog
+DlgAnchorTitle : "خصائص إشارة مرجعية",
+DlgAnchorName : "اسم الإشارة المرجعية",
+DlgAnchorErrorName : "الرجاء كتابة اسم الإشارة المرجعية",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "ليست ÙÙŠ القاموس",
+DlgSpellChangeTo : "التغيير إلى",
+DlgSpellBtnIgnore : "تجاهل",
+DlgSpellBtnIgnoreAll : "تجاهل الكل",
+DlgSpellBtnReplace : "تغيير",
+DlgSpellBtnReplaceAll : "تغيير الكل",
+DlgSpellBtnUndo : "تراجع",
+DlgSpellNoSuggestions : "- لا توجد إقتراحات -",
+DlgSpellProgress : "جاري التدقيق إملائياً",
+DlgSpellNoMispell : "تم إكمال التدقيق الإملائي: لم يتم العثور على أي أخطاء إملائية",
+DlgSpellNoChanges : "تم إكمال التدقيق الإملائي: لم يتم تغيير أي كلمة",
+DlgSpellOneChange : "تم إكمال التدقيق الإملائي: تم تغيير كلمة واحدة Ùقط",
+DlgSpellManyChanges : "تم إكمال التدقيق الإملائي: تم تغيير %1 كلمات\كلمة",
+
+IeSpellDownload : "المدقق الإملائي (الإنجليزي) غير مثبّت. هل تود تحميله الآن؟",
+
+// Button Dialog
+DlgButtonText : "القيمة/التسمية",
+DlgButtonType : "نوع الزر",
+DlgButtonTypeBtn : "زر",
+DlgButtonTypeSbm : "إرسال",
+DlgButtonTypeRst : "إعادة تعيين",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "الاسم",
+DlgCheckboxValue : "القيمة",
+DlgCheckboxSelected : "محدد",
+
+// Form Dialog
+DlgFormName : "الاسم",
+DlgFormAction : "اسم الملÙ",
+DlgFormMethod : "الأسلوب",
+
+// Select Field Dialog
+DlgSelectName : "الاسم",
+DlgSelectValue : "القيمة",
+DlgSelectSize : "الحجم",
+DlgSelectLines : "الأسطر",
+DlgSelectChkMulti : "السماح بتحديدات متعددة",
+DlgSelectOpAvail : "الخيارات المتاحة",
+DlgSelectOpText : "النص",
+DlgSelectOpValue : "القيمة",
+DlgSelectBtnAdd : "إضاÙØ©",
+DlgSelectBtnModify : "تعديل",
+DlgSelectBtnUp : "تحريك لأعلى",
+DlgSelectBtnDown : "تحريك لأسÙÙ„",
+DlgSelectBtnSetValue : "إجعلها محددة",
+DlgSelectBtnDelete : "إزالة",
+
+// Textarea Dialog
+DlgTextareaName : "الاسم",
+DlgTextareaCols : "الأعمدة",
+DlgTextareaRows : "الصÙÙˆÙ",
+
+// Text Field Dialog
+DlgTextName : "الاسم",
+DlgTextValue : "القيمة",
+DlgTextCharWidth : "العرض بالأحرÙ",
+DlgTextMaxChars : "عدد الحرو٠الأقصى",
+DlgTextType : "نوع المحتوى",
+DlgTextTypeText : "نص",
+DlgTextTypePass : "كلمة مرور",
+
+// Hidden Field Dialog
+DlgHiddenName : "الاسم",
+DlgHiddenValue : "القيمة",
+
+// Bulleted List Dialog
+BulletedListProp : "خصائص التعداد النقطي",
+NumberedListProp : "خصائص التعداد الرقمي",
+DlgLstStart : "البدء عند",
+DlgLstType : "النوع",
+DlgLstTypeCircle : "دائرة",
+DlgLstTypeDisc : "قرص",
+DlgLstTypeSquare : "مربع",
+DlgLstTypeNumbers : "أرقام (1، 2، 3)َ",
+DlgLstTypeLCase : "حرو٠صغيرة (a, b, c)َ",
+DlgLstTypeUCase : "حرو٠كبيرة (A, B, C)َ",
+DlgLstTypeSRoman : "ترقيم روماني صغير (i, ii, iii)َ",
+DlgLstTypeLRoman : "ترقيم روماني كبير (I, II, III)َ",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "عام",
+DlgDocBackTab : "الخلÙية",
+DlgDocColorsTab : "الألوان والهوامش",
+DlgDocMetaTab : "المعرّÙات الرأسية",
+
+DlgDocPageTitle : "عنوان الصÙحة",
+DlgDocLangDir : "إتجاه اللغة",
+DlgDocLangDirLTR : "اليسار لليمين (LTR)",
+DlgDocLangDirRTL : "اليمين لليسار (RTL)",
+DlgDocLangCode : "رمز اللغة",
+DlgDocCharSet : "ترميز الحروÙ",
+DlgDocCharSetCE : "أوروبا الوسطى",
+DlgDocCharSetCT : "الصينية التقليدية (Big5)",
+DlgDocCharSetCR : "السيريلية",
+DlgDocCharSetGR : "اليونانية",
+DlgDocCharSetJP : "اليابانية",
+DlgDocCharSetKR : "الكورية",
+DlgDocCharSetTR : "التركية",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "أوروبا الغربية",
+DlgDocCharSetOther : "ترميز آخر",
+
+DlgDocDocType : "ترويسة نوع الصÙحة",
+DlgDocDocTypeOther : "ترويسة نوع صÙحة أخرى",
+DlgDocIncXHTML : "تضمين إعلانات†لغة XHTMLَ",
+DlgDocBgColor : "لون الخلÙية",
+DlgDocBgImage : "رابط الصورة الخلÙية",
+DlgDocBgNoScroll : "جعلها علامة مائية",
+DlgDocCText : "النص",
+DlgDocCLink : "الروابط",
+DlgDocCVisited : "المزارة",
+DlgDocCActive : "النشطة",
+DlgDocMargins : "هوامش الصÙحة",
+DlgDocMaTop : "علوي",
+DlgDocMaLeft : "أيسر",
+DlgDocMaRight : "أيمن",
+DlgDocMaBottom : "سÙلي",
+DlgDocMeIndex : "الكلمات الأساسية (Ù…Ùصولة بÙواصل)ÙŽ",
+DlgDocMeDescr : "وص٠الصÙحة",
+DlgDocMeAuthor : "الكاتب",
+DlgDocMeCopy : "المالك",
+DlgDocPreview : "معاينة",
+
+// Templates Dialog
+Templates : "القوالب",
+DlgTemplatesTitle : "قوالب المحتوى",
+DlgTemplatesSelMsg : "اختر القالب الذي تود وضعه ÙÙŠ المحرر <br>(سيتم Ùقدان المحتوى الحالي):",
+DlgTemplatesLoading : "جاري تحميل قائمة القوالب، الرجاء الإنتظار...",
+DlgTemplatesNoTpl : "(لم يتم تعري٠أي قالب)",
+DlgTemplatesReplace : "استبدال المحتوى",
+
+// About Dialog
+DlgAboutAboutTab : "نبذة",
+DlgAboutBrowserInfoTab : "معلومات متصÙحك",
+DlgAboutLicenseTab : "الترخيص",
+DlgAboutVersion : "الإصدار",
+DlgAboutInfo : "لمزيد من المعلومات تÙضل بزيارة",
+
+// Div Dialog
+DlgDivGeneralTab : "عام",
+DlgDivAdvancedTab : "متقدم",
+DlgDivStyle : "المظهر",
+DlgDivInlineStyle : "المظهر المضمن",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/bg.js b/httemplate/elements/fckeditor/editor/lang/bg.js
new file mode 100644
index 0000000..0a9022f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/bg.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Bulgarian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Скрий панела Ñ Ð¸Ð½Ñтрументите",
+ToolbarExpand : "Покажи панела Ñ Ð¸Ð½Ñтрументите",
+
+// Toolbar Items and Context Menu
+Save : "Запази",
+NewPage : "Ðова Ñтраница",
+Preview : "Предварителен изглед",
+Cut : "Изрежи",
+Copy : "Запамети",
+Paste : "Вмъкни",
+PasteText : "Вмъкни Ñамо текÑÑ‚",
+PasteWord : "Вмъкни от MS Word",
+Print : "Печат",
+SelectAll : "Селектирай вÑичко",
+RemoveFormat : "Изтрий форматирането",
+InsertLinkLbl : "Връзка",
+InsertLink : "Добави/Редактирай връзка",
+RemoveLink : "Изтрий връзка",
+VisitLink : "Open Link", //MISSING
+Anchor : "Добави/Редактирай котва",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Изображение",
+InsertImage : "Добави/Редактирай изображение",
+InsertFlashLbl : "Flash",
+InsertFlash : "Добави/Редактиай Flash обект",
+InsertTableLbl : "Таблица",
+InsertTable : "Добави/Редактирай таблица",
+InsertLineLbl : "ЛиниÑ",
+InsertLine : "Вмъкни хоризонтална линиÑ",
+InsertSpecialCharLbl: "Специален Ñимвол",
+InsertSpecialChar : "Вмъкни Ñпециален Ñимвол",
+InsertSmileyLbl : "УÑмивка",
+InsertSmiley : "Добави уÑмивка",
+About : "За FCKeditor",
+Bold : "Удебелен",
+Italic : "КурÑив",
+Underline : "Подчертан",
+StrikeThrough : "Зачертан",
+Subscript : "Ð˜Ð½Ð´ÐµÐºÑ Ð·Ð° база",
+Superscript : "Ð˜Ð½Ð´ÐµÐºÑ Ð·Ð° Ñтепен",
+LeftJustify : "ПодравнÑване в лÑво",
+CenterJustify : "ПодравнÑвне в Ñредата",
+RightJustify : "ПодравнÑване в дÑÑно",
+BlockJustify : "ДвуÑтранно подравнÑване",
+DecreaseIndent : "Ðамали отÑтъпа",
+IncreaseIndent : "Увеличи отÑтъпа",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Отмени",
+Redo : "Повтори",
+NumberedListLbl : "Ðумериран ÑпиÑък",
+NumberedList : "Добави/Изтрий нумериран ÑпиÑък",
+BulletedListLbl : "Ðенумериран ÑпиÑък",
+BulletedList : "Добави/Изтрий ненумериран ÑпиÑък",
+ShowTableBorders : "Покажи рамките на таблицата",
+ShowDetails : "Покажи подробноÑти",
+Style : "Стил",
+FontFormat : "Формат",
+Font : "Шрифт",
+FontSize : "Размер",
+TextColor : "ЦвÑÑ‚ на текÑта",
+BGColor : "ЦвÑÑ‚ на фона",
+Source : "Код",
+Find : "ТърÑи",
+Replace : "ЗамеÑти",
+SpellCheck : "Провери правопиÑа",
+UniversalKeyboard : "УниверÑална клавиатура",
+PageBreakLbl : "Ðов ред",
+PageBreak : "Вмъкни нов ред",
+
+Form : "ФормулÑÑ€",
+Checkbox : "Поле за отметка",
+RadioButton : "Поле за опциÑ",
+TextField : "ТекÑтово поле",
+Textarea : "ТекÑтова облаÑÑ‚",
+HiddenField : "Скрито поле",
+Button : "Бутон",
+SelectionField : "Падащо меню Ñ Ð¾Ð¿Ñ†Ð¸Ð¸",
+ImageButton : "Бутон-изображение",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Редактирай връзка",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Изтрий редовете",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Изтрий колоните",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Изтрий клетките",
+MergeCells : "Обедини клетките",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Изтрий таблицата",
+CellProperties : "Параметри на клетката",
+TableProperties : "Параметри на таблицата",
+ImageProperties : "Параметри на изображението",
+FlashProperties : "Параметри на Flash обекта",
+
+AnchorProp : "Параметри на котвата",
+ButtonProp : "Параметри на бутона",
+CheckboxProp : "Параметри на полето за отметка",
+HiddenFieldProp : "Параметри на Ñкритото поле",
+RadioButtonProp : "Параметри на полето за опциÑ",
+ImageButtonProp : "Параметри на бутона-изображение",
+TextFieldProp : "Параметри на текÑтовото-поле",
+SelectionFieldProp : "Параметри на падащото меню Ñ Ð¾Ð¿Ñ†Ð¸Ð¸",
+TextareaProp : "Параметри на текÑтовата облаÑÑ‚",
+FormProp : "Параметри на формулÑра",
+
+FontFormats : "Ðормален;Форматиран;ÐдреÑ;Заглавие 1;Заглавие 2;Заглавие 3;Заглавие 4;Заглавие 5;Заглавие 6;Параграф (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Обработка на XHTML. ÐœÐ¾Ð»Ñ Ð¸Ð·Ñ‡Ð°ÐºÐ°Ð¹Ñ‚Ðµ...",
+Done : "Готово",
+PasteWordConfirm : "ТекÑÑ‚ÑŠÑ‚, който иÑкате да вмъкнете е копиран от MS Word. Желаете ли да бъде изчиÑтен преди вмъкването?",
+NotCompatiblePaste : "Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð¸Ñква MS Internet Explorer верÑÐ¸Ñ 5.5 или по-виÑока. Желаете ли да вмъкнете запаметеното без изчиÑтване?",
+UnknownToolbarItem : "Ðепознат инÑтрумент \"%1\"",
+UnknownCommand : "Ðепозната команда \"%1\"",
+NotImplemented : "Командата не е имплементирана",
+UnknownToolbarSet : "Панелът \"%1\" не ÑъщеÑтвува",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ОК",
+DlgBtnCancel : "Отказ",
+DlgBtnClose : "Затвори",
+DlgBtnBrowseServer : "Разгледай Ñървъра",
+DlgAdvancedTag : "ПодробноÑти...",
+DlgOpOther : "<Друго>",
+DlgInfoTab : "ИнформациÑ",
+DlgAlertUrl : "МолÑ, въведете Ð¿ÑŠÐ»Ð½Ð¸Ñ Ð¿ÑŠÑ‚ (URL)",
+
+// General Dialogs Labels
+DlgGenNotSet : "<не е наÑтроен>",
+DlgGenId : "Идентификатор",
+DlgGenLangDir : "поÑока на речта",
+DlgGenLangDirLtr : "От лÑво на дÑÑно",
+DlgGenLangDirRtl : "От дÑÑно на лÑво",
+DlgGenLangCode : "Код на езика",
+DlgGenAccessKey : "Бърз клавиш",
+DlgGenName : "Име",
+DlgGenTabIndex : "Ред на доÑтъп",
+DlgGenLongDescr : "ОпиÑание на връзката",
+DlgGenClass : "ÐšÐ»Ð°Ñ Ð¾Ñ‚ Ñтиловите таблици",
+DlgGenTitle : "Препоръчително заглавие",
+DlgGenContType : "Препоръчителен тип на Ñъдържанието",
+DlgGenLinkCharset : "Тип на ÑÐ²ÑŠÑ€Ð·Ð°Ð½Ð¸Ñ Ñ€ÐµÑурÑ",
+DlgGenStyle : "Стил",
+
+// Image Dialog
+DlgImgTitle : "Параметри на изображението",
+DlgImgInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° изображението",
+DlgImgBtnUpload : "Прати към Ñървъра",
+DlgImgURL : "Пълен път (URL)",
+DlgImgUpload : "Качи",
+DlgImgAlt : "Ðлтернативен текÑÑ‚",
+DlgImgWidth : "Ширина",
+DlgImgHeight : "ВиÑочина",
+DlgImgLockRatio : "Запази пропорциÑта",
+DlgBtnResetSize : "ВъзÑтанови размера",
+DlgImgBorder : "Рамка",
+DlgImgHSpace : "Хоризонтален отÑтъп",
+DlgImgVSpace : "Вертикален отÑтъп",
+DlgImgAlign : "ПодравнÑване",
+DlgImgAlignLeft : "ЛÑво",
+DlgImgAlignAbsBottom: "Ðай-долу",
+DlgImgAlignAbsMiddle: "Точно по Ñредата",
+DlgImgAlignBaseline : "По базовата линиÑ",
+DlgImgAlignBottom : "Долу",
+DlgImgAlignMiddle : "По Ñредата",
+DlgImgAlignRight : "ДÑÑно",
+DlgImgAlignTextTop : "Върху текÑта",
+DlgImgAlignTop : "Отгоре",
+DlgImgPreview : "Изглед",
+DlgImgAlertUrl : "МолÑ, въведете Ð¿ÑŠÐ»Ð½Ð¸Ñ Ð¿ÑŠÑ‚ до изображението",
+DlgImgLinkTab : "Връзка",
+
+// Flash Dialog
+DlgFlashTitle : "Параметри на Flash обекта",
+DlgFlashChkPlay : "Ðвтоматично Ñтартиране",
+DlgFlashChkLoop : "Ðово Ñтартиране Ñлед завършването",
+DlgFlashChkMenu : "Разрешено Flash меню",
+DlgFlashScale : "ОразмерÑване",
+DlgFlashScaleAll : "Покажи Ñ†ÐµÐ»Ð¸Ñ Ð¾Ð±ÐµÐºÑ‚",
+DlgFlashScaleNoBorder : "Без рамка",
+DlgFlashScaleFit : "Според мÑÑтото",
+
+// Link Dialog
+DlgLnkWindowTitle : "Връзка",
+DlgLnkInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° връзката",
+DlgLnkTargetTab : "Цел",
+
+DlgLnkType : "Вид на връзката",
+DlgLnkTypeURL : "Пълен път (URL)",
+DlgLnkTypeAnchor : "Котва в текущата Ñтраница",
+DlgLnkTypeEMail : "Е-поща",
+DlgLnkProto : "Протокол",
+DlgLnkProtoOther : "<друго>",
+DlgLnkURL : "Пълен път (URL)",
+DlgLnkAnchorSel : "Изберете котва",
+DlgLnkAnchorByName : "По име на котвата",
+DlgLnkAnchorById : "По идентификатор на елемент",
+DlgLnkNoAnchors : "(ÐÑма котви в Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚)",
+DlgLnkEMail : "ÐÐ´Ñ€ÐµÑ Ð·Ð° е-поща",
+DlgLnkEMailSubject : "Тема на пиÑмото",
+DlgLnkEMailBody : "ТекÑÑ‚ на пиÑмото",
+DlgLnkUpload : "Качи",
+DlgLnkBtnUpload : "Прати на Ñървъра",
+
+DlgLnkTarget : "Цел",
+DlgLnkTargetFrame : "<рамка>",
+DlgLnkTargetPopup : "<дъщерен прозорец>",
+DlgLnkTargetBlank : "Ðов прозорец (_blank)",
+DlgLnkTargetParent : "РодителÑки прозорец (_parent)",
+DlgLnkTargetSelf : "ÐÐºÑ‚Ð¸Ð²Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ† (_self)",
+DlgLnkTargetTop : "Ð¦ÐµÐ»Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ† (_top)",
+DlgLnkTargetFrameName : "Име на Ñ†ÐµÐ»ÐµÐ²Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ†",
+DlgLnkPopWinName : "Име на Ð´ÑŠÑ‰ÐµÑ€Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ†",
+DlgLnkPopWinFeat : "Параметри на Ð´ÑŠÑ‰ÐµÑ€Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ†",
+DlgLnkPopResize : "С променливи размери",
+DlgLnkPopLocation : "Поле за адреÑ",
+DlgLnkPopMenu : "Меню",
+DlgLnkPopScroll : "Плъзгач",
+DlgLnkPopStatus : "Поле за ÑтатуÑ",
+DlgLnkPopToolbar : "Панел Ñ Ð±ÑƒÑ‚Ð¾Ð½Ð¸",
+DlgLnkPopFullScrn : "ГолÑм екран (MS IE)",
+DlgLnkPopDependent : "ЗавиÑим (Netscape)",
+DlgLnkPopWidth : "Ширина",
+DlgLnkPopHeight : "ВиÑочина",
+DlgLnkPopLeft : "Координати - X",
+DlgLnkPopTop : "Координати - Y",
+
+DlnLnkMsgNoUrl : "МолÑ, напишете Ð¿ÑŠÐ»Ð½Ð¸Ñ Ð¿ÑŠÑ‚ (URL)",
+DlnLnkMsgNoEMail : "МолÑ, напишете адреÑа за е-поща",
+DlnLnkMsgNoAnchor : "МолÑ, изберете котва",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Изберете цвÑÑ‚",
+DlgColorBtnClear : "ИзчиÑти",
+DlgColorHighlight : "Текущ",
+DlgColorSelected : "Избран",
+
+// Smiley Dialog
+DlgSmileyTitle : "Добави уÑмивка",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Изберете Ñпециален Ñимвол",
+
+// Table Dialog
+DlgTableTitle : "Параметри на таблицата",
+DlgTableRows : "Редове",
+DlgTableColumns : "Колони",
+DlgTableBorder : "Размер на рамката",
+DlgTableAlign : "ПодравнÑване",
+DlgTableAlignNotSet : "<Ðе е избрано>",
+DlgTableAlignLeft : "ЛÑво",
+DlgTableAlignCenter : "Център",
+DlgTableAlignRight : "ДÑÑно",
+DlgTableWidth : "Ширина",
+DlgTableWidthPx : "пикÑели",
+DlgTableWidthPc : "проценти",
+DlgTableHeight : "ВиÑочина",
+DlgTableCellSpace : "РазÑтоÑние между клетките",
+DlgTableCellPad : "ОтÑтъп на Ñъдържанието в клетките",
+DlgTableCaption : "Заглавие",
+DlgTableSummary : "Резюме",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Параметри на клетката",
+DlgCellWidth : "Ширина",
+DlgCellWidthPx : "пикÑели",
+DlgCellWidthPc : "проценти",
+DlgCellHeight : "ВиÑочина",
+DlgCellWordWrap : "пренаÑÑне на нов ред",
+DlgCellWordWrapNotSet : "<Ðе е наÑтроено>",
+DlgCellWordWrapYes : "Да",
+DlgCellWordWrapNo : "не",
+DlgCellHorAlign : "Хоризонтално подравнÑване",
+DlgCellHorAlignNotSet : "<Ðе е наÑтроено>",
+DlgCellHorAlignLeft : "ЛÑво",
+DlgCellHorAlignCenter : "Център",
+DlgCellHorAlignRight: "ДÑÑно",
+DlgCellVerAlign : "Вертикално подравнÑване",
+DlgCellVerAlignNotSet : "<Ðе е наÑтроено>",
+DlgCellVerAlignTop : "Горе",
+DlgCellVerAlignMiddle : "По Ñредата",
+DlgCellVerAlignBottom : "Долу",
+DlgCellVerAlignBaseline : "По базовата линиÑ",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "повече от един ред",
+DlgCellCollSpan : "повече от една колона",
+DlgCellBackColor : "фонов цвÑÑ‚",
+DlgCellBorderColor : "цвÑÑ‚ на рамката",
+DlgCellBtnSelect : "Изберете...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "ТърÑи",
+DlgFindFindBtn : "ТърÑи",
+DlgFindNotFoundMsg : "Ð£ÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ñ‚ÐµÐºÑÑ‚ не беше намерен.",
+
+// Replace Dialog
+DlgReplaceTitle : "ЗамеÑти",
+DlgReplaceFindLbl : "ТърÑи:",
+DlgReplaceReplaceLbl : "ЗамеÑти Ñ:",
+DlgReplaceCaseChk : "Ð¡ÑŠÑ ÑÑŠÑ‰Ð¸Ñ Ñ€ÐµÐ³Ð¸ÑÑ‚ÑŠÑ€",
+DlgReplaceReplaceBtn : "ЗамеÑти",
+DlgReplaceReplAllBtn : "ЗамеÑти вÑички",
+DlgReplaceWordChk : "ТърÑи Ñъщата дума",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ÐаÑтройките за ÑигурноÑÑ‚ на Ð²Ð°ÑˆÐ¸Ñ Ð±Ñ€Ð°Ð·ÑƒÑŠÑ€ не разрешават на редактора да изпълни изрÑзването. За целта използвайте клавиатурата (Ctrl+X).",
+PasteErrorCopy : "ÐаÑтройките за ÑигурноÑÑ‚ на Ð²Ð°ÑˆÐ¸Ñ Ð±Ñ€Ð°Ð·ÑƒÑŠÑ€ не разрешават на редактора да изпълни запаметÑването. За целта използвайте клавиатурата (Ctrl+C).",
+
+PasteAsText : "Вмъкни като чиÑÑ‚ текÑÑ‚",
+PasteFromWord : "Вмъкни от MS Word",
+
+DlgPasteMsg2 : "Вмъкнете тук Ñъдъжанието Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÐ°Ñ€Ð°Ñ‚Ð° (<STRONG>Ctrl+V</STRONG>) и натиÑнете <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Игнорирай шрифтовите дефиниции",
+DlgPasteRemoveStyles : "Изтрий Ñтиловите дефиниции",
+
+// Color Picker
+ColorAutomatic : "По подразбиране",
+ColorMoreColors : "Други цветове...",
+
+// Document Properties
+DocProps : "Параметри на документа",
+
+// Anchor Dialog
+DlgAnchorTitle : "Параметри на котвата",
+DlgAnchorName : "Име на котвата",
+DlgAnchorErrorName : "МолÑ, въведете име на котвата",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "ЛипÑва в речника",
+DlgSpellChangeTo : "Промени на",
+DlgSpellBtnIgnore : "Игнорирай",
+DlgSpellBtnIgnoreAll : "Игнорирай вÑички",
+DlgSpellBtnReplace : "ЗамеÑти",
+DlgSpellBtnReplaceAll : "ЗамеÑти вÑички",
+DlgSpellBtnUndo : "Отмени",
+DlgSpellNoSuggestions : "- ÐÑма Ð¿Ñ€ÐµÐ´Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ -",
+DlgSpellProgress : "Извършване на проверката за правопиÑ...",
+DlgSpellNoMispell : "Проверката за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð²ÑŠÑ€ÑˆÐµÐ½Ð°: не Ñа открити правопиÑни грешки",
+DlgSpellNoChanges : "Проверката за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð²ÑŠÑ€ÑˆÐµÐ½Ð°: нÑма променени думи",
+DlgSpellOneChange : "Проверката за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð²ÑŠÑ€ÑˆÐµÐ½Ð°: една дума е променена",
+DlgSpellManyChanges : "Проверката за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð²ÑŠÑ€ÑˆÐµÐ½Ð°: %1 думи Ñа променени",
+
+IeSpellDownload : "ИнÑтрументът за проверка на Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð½Ðµ е инÑталиран. Желаете ли да го инÑталирате ?",
+
+// Button Dialog
+DlgButtonText : "ТекÑÑ‚ (СтойноÑÑ‚)",
+DlgButtonType : "Тип",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Име",
+DlgCheckboxValue : "СтойноÑÑ‚",
+DlgCheckboxSelected : "Отметнато",
+
+// Form Dialog
+DlgFormName : "Име",
+DlgFormAction : "ДейÑтвие",
+DlgFormMethod : "Метод",
+
+// Select Field Dialog
+DlgSelectName : "Име",
+DlgSelectValue : "СтойноÑÑ‚",
+DlgSelectSize : "Размер",
+DlgSelectLines : "линии",
+DlgSelectChkMulti : "Разрешено множеÑтвено Ñелектиране",
+DlgSelectOpAvail : "Възможни опции",
+DlgSelectOpText : "ТекÑÑ‚",
+DlgSelectOpValue : "СтойноÑÑ‚",
+DlgSelectBtnAdd : "Добави",
+DlgSelectBtnModify : "Промени",
+DlgSelectBtnUp : "Ðагоре",
+DlgSelectBtnDown : "Ðадолу",
+DlgSelectBtnSetValue : "ÐаÑтрой като избрана ÑтойноÑÑ‚",
+DlgSelectBtnDelete : "Изтрий",
+
+// Textarea Dialog
+DlgTextareaName : "Име",
+DlgTextareaCols : "Колони",
+DlgTextareaRows : "Редове",
+
+// Text Field Dialog
+DlgTextName : "Име",
+DlgTextValue : "СтойноÑÑ‚",
+DlgTextCharWidth : "Ширина на Ñимволите",
+DlgTextMaxChars : "МакÑимум Ñимволи",
+DlgTextType : "Тип",
+DlgTextTypeText : "ТекÑÑ‚",
+DlgTextTypePass : "Парола",
+
+// Hidden Field Dialog
+DlgHiddenName : "Име",
+DlgHiddenValue : "СтойноÑÑ‚",
+
+// Bulleted List Dialog
+BulletedListProp : "Параметри на Ð½ÐµÐ½ÑƒÐ¼ÐµÑ€Ð¸Ñ€Ð°Ð½Ð¸Ñ ÑпиÑък",
+NumberedListProp : "Параметри на Ð½ÑƒÐ¼ÐµÑ€Ð¸Ñ€Ð°Ð½Ð¸Ñ ÑпиÑък",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Тип",
+DlgLstTypeCircle : "ОкръжноÑÑ‚",
+DlgLstTypeDisc : "Кръг",
+DlgLstTypeSquare : "Квадрат",
+DlgLstTypeNumbers : "ЧиÑла (1, 2, 3)",
+DlgLstTypeLCase : "Малки букви (a, b, c)",
+DlgLstTypeUCase : "Големи букви (A, B, C)",
+DlgLstTypeSRoman : "Малки римÑки чиÑла (i, ii, iii)",
+DlgLstTypeLRoman : "Големи римÑки чиÑла (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Общи",
+DlgDocBackTab : "Фон",
+DlgDocColorsTab : "Цветове и отÑтъпи",
+DlgDocMetaTab : "Мета данни",
+
+DlgDocPageTitle : "Заглавие на Ñтраницата",
+DlgDocLangDir : "ПоÑока на речта",
+DlgDocLangDirLTR : "От лÑво на дÑÑно",
+DlgDocLangDirRTL : "От дÑÑно на лÑво",
+DlgDocLangCode : "Код на езика",
+DlgDocCharSet : "Кодиране на Ñимволите",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Друго кодиране на Ñимволите",
+
+DlgDocDocType : "Тип на документа",
+DlgDocDocTypeOther : "Друг тип на документа",
+DlgDocIncXHTML : "Включи XHTML декларациÑ",
+DlgDocBgColor : "ЦвÑÑ‚ на фона",
+DlgDocBgImage : "Пълен път до фоновото изображение",
+DlgDocBgNoScroll : "Ðе-повтарÑщо Ñе фоново изображение",
+DlgDocCText : "ТекÑÑ‚",
+DlgDocCLink : "Връзка",
+DlgDocCVisited : "ПоÑетена връзка",
+DlgDocCActive : "Ðктивна връзка",
+DlgDocMargins : "ОтÑтъпи на Ñтраницата",
+DlgDocMaTop : "Горе",
+DlgDocMaLeft : "ЛÑво",
+DlgDocMaRight : "ДÑÑно",
+DlgDocMaBottom : "Долу",
+DlgDocMeIndex : "Ключови думи за документа (разделени ÑÑŠÑ Ð·Ð°Ð¿ÐµÑ‚Ð°Ð¸)",
+DlgDocMeDescr : "ОпиÑание на документа",
+DlgDocMeAuthor : "Ðвтор",
+DlgDocMeCopy : "ÐвторÑки права",
+DlgDocPreview : "Изглед",
+
+// Templates Dialog
+Templates : "Шаблони",
+DlgTemplatesTitle : "Шаблони",
+DlgTemplatesSelMsg : "Изберете шаблон <br>(текущото Ñъдържание на редактора ще бъде загубено):",
+DlgTemplatesLoading : "Зареждане на ÑпиÑъка Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¸Ñ‚Ðµ. ÐœÐ¾Ð»Ñ Ð¸Ð·Ñ‡Ð°ÐºÐ°Ð¹Ñ‚Ðµ...",
+DlgTemplatesNoTpl : "(ÐÑма дефинирани шаблони)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "За",
+DlgAboutBrowserInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° браузъра",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "верÑиÑ",
+DlgAboutInfo : "За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ñетете",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/bn.js b/httemplate/elements/fckeditor/editor/lang/bn.js
new file mode 100644
index 0000000..a919b98
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/bn.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Bengali/Bangla language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "টূলবার গà§à¦Ÿà¦¿à§Ÿà§‡ দাও",
+ToolbarExpand : "টূলবার ছড়িয়ে দাও",
+
+// Toolbar Items and Context Menu
+Save : "সংরকà§à¦·à¦¨ কর",
+NewPage : "নতà§à¦¨ পেজ",
+Preview : "পà§à¦°à¦¿à¦­à¦¿à¦‰",
+Cut : "কাট",
+Copy : "কপি",
+Paste : "পেসà§à¦Ÿ",
+PasteText : "পেসà§à¦Ÿ (সাদা টেকà§à¦¸à¦Ÿ)",
+PasteWord : "পেসà§à¦Ÿ (শবà§à¦¦)",
+Print : "পà§à¦°à¦¿à¦¨à§à¦Ÿ",
+SelectAll : "সব সিলেকà§à¦Ÿ কর",
+RemoveFormat : "ফরমেট সরাও",
+InsertLinkLbl : "লিংকের যà§à¦•à§à¦¤ করার লেবেল",
+InsertLink : "লিংক যà§à¦•à§à¦¤ কর",
+RemoveLink : "লিংক সরাও",
+VisitLink : "Open Link", //MISSING
+Anchor : "নোঙà§à¦—র",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "ছবির লেবেল যà§à¦•à§à¦¤ কর",
+InsertImage : "ছবি যà§à¦•à§à¦¤ কর",
+InsertFlashLbl : "ফà§à¦²à¦¾à¦¶ লেবেল যà§à¦•à§à¦¤ কর",
+InsertFlash : "ফà§à¦²à¦¾à¦¶ যà§à¦•à§à¦¤ কর",
+InsertTableLbl : "টেবিলের লেবেল যà§à¦•à§à¦¤ কর",
+InsertTable : "টেবিল যà§à¦•à§à¦¤ কর",
+InsertLineLbl : "রেখা যà§à¦•à§à¦¤ কর",
+InsertLine : "রেখা যà§à¦•à§à¦¤ কর",
+InsertSpecialCharLbl: "বিশেষ অকà§à¦·à¦°à§‡à¦° লেবেল যà§à¦•à§à¦¤ কর",
+InsertSpecialChar : "বিশেষ অকà§à¦·à¦° যà§à¦•à§à¦¤ কর",
+InsertSmileyLbl : "সà§à¦®à¦¾à¦‡à¦²à§€",
+InsertSmiley : "সà§à¦®à¦¾à¦‡à¦²à§€ যà§à¦•à§à¦¤ কর",
+About : "FCKeditor কে বানিয়েছে",
+Bold : "বোলà§à¦¡",
+Italic : "ইটালিক",
+Underline : "আনà§à¦¡à¦¾à¦°à¦²à¦¾à¦‡à¦¨",
+StrikeThrough : "সà§à¦Ÿà§à¦°à¦¾à¦‡à¦• থà§à¦°à§",
+Subscript : "অধোলেখ",
+Superscript : "অভিলেখ",
+LeftJustify : "বা দিকে ঘেà¦à¦·à¦¾",
+CenterJustify : "মাঠবরাবর ঘেষা",
+RightJustify : "ডান দিকে ঘেà¦à¦·à¦¾",
+BlockJustify : "বà§à¦²à¦• জাসà§à¦Ÿà¦¿à¦«à¦¾à¦‡",
+DecreaseIndent : "ইনডেনà§à¦Ÿ কমাও",
+IncreaseIndent : "ইনডেনà§à¦Ÿ বাড়াও",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "আনডà§",
+Redo : "রি-ডà§",
+NumberedListLbl : "সাংখà§à¦¯à¦¿à¦• লিসà§à¦Ÿà§‡à¦° লেবেল",
+NumberedList : "সাংখà§à¦¯à¦¿à¦• লিসà§à¦Ÿ",
+BulletedListLbl : "বà§à¦²à§‡à¦Ÿ লিসà§à¦Ÿ লেবেল",
+BulletedList : "বà§à¦²à§‡à¦Ÿà§‡à¦¡ লিসà§à¦Ÿ",
+ShowTableBorders : "টেবিল বরà§à¦¡à¦¾à¦°",
+ShowDetails : "সবটà§à¦•à§ দেখাও",
+Style : "সà§à¦Ÿà¦¾à¦‡à¦²",
+FontFormat : "ফনà§à¦Ÿ ফরমেট",
+Font : "ফনà§à¦Ÿ",
+FontSize : "সাইজ",
+TextColor : "টেকà§à¦¸à§à¦Ÿ রং",
+BGColor : "বেকগà§à¦°à¦¾à¦‰à¦¨à§à¦¡ রং",
+Source : "সোরà§à¦¸",
+Find : "খোজো",
+Replace : "রিপà§à¦²à§‡à¦¸",
+SpellCheck : "বানান চেক",
+UniversalKeyboard : "সারà§à¦¬à¦œà¦¨à§€à¦¨ কিবোরà§à¦¡",
+PageBreakLbl : "পেজ বà§à¦°à§‡à¦• লেবেল",
+PageBreak : "পেজ বà§à¦°à§‡à¦•",
+
+Form : "ফরà§à¦®",
+Checkbox : "চেক বাকà§à¦¸",
+RadioButton : "রেডিও বাটন",
+TextField : "টেকà§à¦¸à¦Ÿ ফীলà§à¦¡",
+Textarea : "টেকà§à¦¸à¦Ÿ à¦à¦°à¦¿à§Ÿà¦¾",
+HiddenField : "গà§à¦ªà§à¦¤ ফীলà§à¦¡",
+Button : "বাটন",
+SelectionField : "বাছাই ফীলà§à¦¡",
+ImageButton : "ছবির বাটন",
+
+FitWindow : "উইনà§à¦¡à§‹ ফিট কর",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "লিংক সমà§à¦ªà¦¾à¦¦à¦¨",
+CellCM : "সেল",
+RowCM : "রো",
+ColumnCM : "কলাম",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "রো মà§à¦›à§‡ দাও",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "কলাম মà§à¦›à§‡ দাও",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "সেল মà§à¦›à§‡ দাও",
+MergeCells : "সেল জোড়া দাও",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "টেবিল ডিলীট কর",
+CellProperties : "সেলের পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿à¦œ",
+TableProperties : "টেবিল পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+ImageProperties : "ছবি পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+FlashProperties : "ফà§à¦²à¦¾à¦¶ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+
+AnchorProp : "নোঙর পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+ButtonProp : "বাটন পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+CheckboxProp : "চেক বকà§à¦¸ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+HiddenFieldProp : "গà§à¦ªà§à¦¤ ফীলà§à¦¡ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+RadioButtonProp : "রেডিও বাটন পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+ImageButtonProp : "ছবি বাটন পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+TextFieldProp : "টেকà§à¦¸à¦Ÿ ফীলà§à¦¡ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+SelectionFieldProp : "বাছাই ফীলà§à¦¡ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+TextareaProp : "টেকà§à¦¸à¦Ÿ à¦à¦°à¦¿à§Ÿà¦¾ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+FormProp : "ফরà§à¦® পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+
+FontFormats : "সাধারণ;ফরà§à¦®à§‡à¦Ÿà§‡à¦¡;ঠিকানা;শীরà§à¦·à¦• ১;শীরà§à¦·à¦• ২;শীরà§à¦·à¦• ৩;শীরà§à¦·à¦• ৪;শীরà§à¦·à¦• ৫;শীরà§à¦·à¦• ৬;শীরà§à¦·à¦• (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML পà§à¦°à¦¸à§‡à¦¸ করা হচà§à¦›à§‡",
+Done : "শেষ হয়েছে",
+PasteWordConfirm : "যে টেকসà§à¦Ÿà¦Ÿà¦¿ আপনি পেসà§à¦Ÿ করতে চাচà§à¦›à§‡à¦¨ মনে হচà§à¦›à§‡ সেটি ওয়ারà§à¦¡ থেকে কপি করা। আপনি কি পেসà§à¦Ÿ করার আগে à¦à¦•à§‡ পরিষà§à¦•à¦¾à¦° করতে চান?",
+NotCompatiblePaste : "à¦à¦‡ কমানà§à¦¡à¦Ÿà¦¿ শà§à¦§à§à¦®à¦¾à¦¤à§à¦° ইনà§à¦Ÿà¦¾à¦°à¦¨à§‡à¦Ÿ à¦à¦•à§à¦¸à¦ªà§à¦²à§‹à¦°à¦¾à¦° ৫.০ বা তার পরের ভারà§à¦¸à¦¨à§‡ পাওয়া সমà§à¦­à¦¬à¥¤ আপনি কি পরিষà§à¦•à¦¾à¦° না করেই পেসà§à¦Ÿ করতে চান?",
+UnknownToolbarItem : "অজানা টà§à¦²à¦¬à¦¾à¦° আইটেম \"%1\"",
+UnknownCommand : "অজানা কমানà§à¦¡ \"%1\"",
+NotImplemented : "কমানà§à¦¡ ইমপà§à¦²à¦¿à¦®à§‡à¦¨à§à¦Ÿ করা হয়নি",
+UnknownToolbarSet : "টà§à¦²à¦¬à¦¾à¦° সেট \"%1\" à¦à¦° অসà§à¦¤à¦¿à¦¤à§à¦¬ নেই",
+NoActiveX : "আপনার বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° সà§à¦°à¦•à§à¦·à¦¾ সেটিংস কারনে à¦à¦¡à¦¿à¦Ÿà¦°à§‡à¦° কিছৠফিচার পাওয়া নাও যেতে পারে। আপনাকে অবশà§à¦¯à¦‡ \"Run ActiveX controls and plug-ins\" à¦à¦¨à¦¾à¦¬à§‡à¦² করে নিতে হবে। আপনি ভà§à¦²à¦­à§à¦°à¦¾à¦¨à§à¦¤à¦¿ কিছৠকিছৠফিচারের অনà§à¦ªà¦¸à§à¦¥à¦¿à¦¤à¦¿ উপলবà§à¦§à¦¿ করতে পারেন।",
+BrowseServerBlocked : "রিসোরà§à¦¸ বà§à¦°à¦¾à¦‰à¦œà¦¾à¦° খোলা গেল না। নিশà§à¦šà¦¿à¦¤ করà§à¦¨ যে সব পপআপ বà§à¦²à¦•à¦¾à¦° বনà§à¦§ করা আছে।",
+DialogBlocked : "ডায়ালগ ইউনà§à¦¡à§‹ খোলা গেল না। নিশà§à¦šà¦¿à¦¤ করà§à¦¨ যে সব পপআপ বà§à¦²à¦•à¦¾à¦° বনà§à¦§ করা আছে।",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ওকে",
+DlgBtnCancel : "বাতিল",
+DlgBtnClose : "বনà§à¦§ কর",
+DlgBtnBrowseServer : "বà§à¦°à¦¾à¦‰à¦œ সারà§à¦­à¦¾à¦°",
+DlgAdvancedTag : "à¦à¦¡à¦­à¦¾à¦¨à§à¦¸à¦¡",
+DlgOpOther : "<অনà§à¦¯>",
+DlgInfoTab : "তথà§à¦¯",
+DlgAlertUrl : "দয়া করে URL যà§à¦•à§à¦¤ করà§à¦¨",
+
+// General Dialogs Labels
+DlgGenNotSet : "<সেট নেই>",
+DlgGenId : "আইডি",
+DlgGenLangDir : "ভাষা লেখার দিক",
+DlgGenLangDirLtr : "বাম থেকে ডান (LTR)",
+DlgGenLangDirRtl : "ডান থেকে বাম (RTL)",
+DlgGenLangCode : "ভাষা কোড",
+DlgGenAccessKey : "à¦à¦•à§à¦¸à§‡à¦¸ কী",
+DlgGenName : "নাম",
+DlgGenTabIndex : "টà§à¦¯à¦¾à¦¬ ইনà§à¦¡à§‡à¦•à§à¦¸",
+DlgGenLongDescr : "URL à¦à¦° লমà§à¦¬à¦¾ বরà§à¦£à¦¨à¦¾",
+DlgGenClass : "সà§à¦Ÿà¦¾à¦‡à¦²-শীট কà§à¦²à¦¾à¦¸",
+DlgGenTitle : "পরামরà§à¦¶ শীরà§à¦·à¦•",
+DlgGenContType : "পরামরà§à¦¶ কনà§à¦Ÿà§‡à¦¨à§à¦Ÿà§‡à¦° পà§à¦°à¦•à¦¾à¦°",
+DlgGenLinkCharset : "লিংক রিসোরà§à¦¸ কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦° সেট",
+DlgGenStyle : "সà§à¦Ÿà¦¾à¦‡à¦²",
+
+// Image Dialog
+DlgImgTitle : "ছবির পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgImgInfoTab : "ছবির তথà§à¦¯",
+DlgImgBtnUpload : "ইহাকে সারà§à¦­à¦¾à¦°à§‡ পà§à¦°à§‡à¦°à¦¨ কর",
+DlgImgURL : "URL",
+DlgImgUpload : "আপলোড",
+DlgImgAlt : "বিকলà§à¦ª টেকà§à¦¸à¦Ÿ",
+DlgImgWidth : "পà§à¦°à¦¸à§à¦¥",
+DlgImgHeight : "দৈরà§à¦˜à§à¦¯",
+DlgImgLockRatio : "অনà§à¦ªà¦¾à¦¤ লক কর",
+DlgBtnResetSize : "সাইজ পূরà§à¦¬à¦¾à¦¬à¦¸à§à¦¥à¦¾à§Ÿ ফিরিয়ে দাও",
+DlgImgBorder : "বরà§à¦¡à¦¾à¦°",
+DlgImgHSpace : "হরাইজনà§à¦Ÿà¦¾à¦² সà§à¦ªà§‡à¦¸",
+DlgImgVSpace : "ভারà§à¦Ÿà¦¿à¦•à§‡à¦² সà§à¦ªà§‡à¦¸",
+DlgImgAlign : "à¦à¦²à¦¾à¦‡à¦¨",
+DlgImgAlignLeft : "বামে",
+DlgImgAlignAbsBottom: "Abs নীচে",
+DlgImgAlignAbsMiddle: "Abs উপর",
+DlgImgAlignBaseline : "মূল রেখা",
+DlgImgAlignBottom : "নীচে",
+DlgImgAlignMiddle : "মধà§à¦¯",
+DlgImgAlignRight : "ডানে",
+DlgImgAlignTextTop : "টেকà§à¦¸à¦Ÿ উপর",
+DlgImgAlignTop : "উপর",
+DlgImgPreview : "পà§à¦°à§€à¦­à¦¿à¦‰",
+DlgImgAlertUrl : "অনà§à¦—à§à¦°à¦¹à¦• করে ছবির URL টাইপ করà§à¦¨",
+DlgImgLinkTab : "লিংক",
+
+// Flash Dialog
+DlgFlashTitle : "ফà§à¦²à§à¦¯à¦¾à¦¶ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgFlashChkPlay : "অটো পà§à¦²à§‡",
+DlgFlashChkLoop : "লূপ",
+DlgFlashChkMenu : "ফà§à¦²à§à¦¯à¦¾à¦¶ মেনৠà¦à¦¨à¦¾à¦¬à¦² কর",
+DlgFlashScale : "সà§à¦•à§‡à¦²",
+DlgFlashScaleAll : "সব দেখাও",
+DlgFlashScaleNoBorder : "কোনো বরà§à¦¡à¦¾à¦° নেই",
+DlgFlashScaleFit : "নিখà§à¦à¦¤ ফিট",
+
+// Link Dialog
+DlgLnkWindowTitle : "লিংক",
+DlgLnkInfoTab : "লিংক তথà§à¦¯",
+DlgLnkTargetTab : "টারà§à¦—েট",
+
+DlgLnkType : "লিংক পà§à¦°à¦•à¦¾à¦°",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "à¦à¦‡ পেজে নোঙর কর",
+DlgLnkTypeEMail : "ইমেইল",
+DlgLnkProto : "পà§à¦°à§‹à¦Ÿà§‹à¦•à¦²",
+DlgLnkProtoOther : "<অনà§à¦¯>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "নোঙর বাছাই",
+DlgLnkAnchorByName : "নোঙরের নাম দিয়ে",
+DlgLnkAnchorById : "নোঙরের আইডি দিয়ে",
+DlgLnkNoAnchors : "(No anchors available in the document)", //MISSING
+DlgLnkEMail : "ইমেইল ঠিকানা",
+DlgLnkEMailSubject : "মেসেজের বিষয়",
+DlgLnkEMailBody : "মেসেজের দেহ",
+DlgLnkUpload : "আপলোড",
+DlgLnkBtnUpload : "à¦à¦•à§‡ সারà§à¦­à¦¾à¦°à§‡ পাঠাও",
+
+DlgLnkTarget : "টারà§à¦—েট",
+DlgLnkTargetFrame : "<ফà§à¦°à§‡à¦®>",
+DlgLnkTargetPopup : "<পপআপ উইনà§à¦¡à§‹>",
+DlgLnkTargetBlank : "নতà§à¦¨ উইনà§à¦¡à§‹ (_blank)",
+DlgLnkTargetParent : "মূল উইনà§à¦¡à§‹ (_parent)",
+DlgLnkTargetSelf : "à¦à¦‡ উইনà§à¦¡à§‹ (_self)",
+DlgLnkTargetTop : "শীরà§à¦· উইনà§à¦¡à§‹ (_top)",
+DlgLnkTargetFrameName : "টারà§à¦—েট ফà§à¦°à§‡à¦®à§‡à¦° নাম",
+DlgLnkPopWinName : "পপআপ উইনà§à¦¡à§‹à¦° নাম",
+DlgLnkPopWinFeat : "পপআপ উইনà§à¦¡à§‹ ফীচার সমূহ",
+DlgLnkPopResize : "রিসাইজ করা সমà§à¦­à¦¬",
+DlgLnkPopLocation : "লোকেশন বার",
+DlgLnkPopMenu : "মেনà§à¦¯à§ বার",
+DlgLnkPopScroll : "সà§à¦•à§à¦°à¦² বার",
+DlgLnkPopStatus : "সà§à¦Ÿà§à¦¯à¦¾à¦Ÿà¦¾à¦¸ বার",
+DlgLnkPopToolbar : "টà§à¦² বার",
+DlgLnkPopFullScrn : "পূরà§à¦£ পরà§à¦¦à¦¾ জà§à§œà§‡ (IE)",
+DlgLnkPopDependent : "ডিপেনà§à¦¡à§‡à¦¨à§à¦Ÿ (Netscape)",
+DlgLnkPopWidth : "পà§à¦°à¦¸à§à¦¥",
+DlgLnkPopHeight : "দৈরà§à¦˜à§à¦¯",
+DlgLnkPopLeft : "বামের পজিশন",
+DlgLnkPopTop : "ডানের পজিশন",
+
+DlnLnkMsgNoUrl : "অনà§à¦—à§à¦°à¦¹ করে URL লিংক টাইপ করà§à¦¨",
+DlnLnkMsgNoEMail : "অনà§à¦—à§à¦°à¦¹ করে ইমেইল à¦à¦¡à§à¦°à§‡à¦¸ টাইপ করà§à¦¨",
+DlnLnkMsgNoAnchor : "অনà§à¦—à§à¦°à¦¹ করে নোঙর বাছাই করà§à¦¨",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "রং বাছাই কর",
+DlgColorBtnClear : "পরিষà§à¦•à¦¾à¦° কর",
+DlgColorHighlight : "হাইলাইট",
+DlgColorSelected : "সিলেকà§à¦Ÿà§‡à¦¡",
+
+// Smiley Dialog
+DlgSmileyTitle : "সà§à¦®à¦¾à¦‡à¦²à§€ যà§à¦•à§à¦¤ কর",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "বিশেষ কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦° বাছাই কর",
+
+// Table Dialog
+DlgTableTitle : "টেবিল পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgTableRows : "রো",
+DlgTableColumns : "কলাম",
+DlgTableBorder : "বরà§à¦¡à¦¾à¦° সাইজ",
+DlgTableAlign : "à¦à¦²à¦¾à¦‡à¦¨à¦®à§‡à¦¨à§à¦Ÿ",
+DlgTableAlignNotSet : "<সেট নেই>",
+DlgTableAlignLeft : "বামে",
+DlgTableAlignCenter : "মাà¦à¦–ানে",
+DlgTableAlignRight : "ডানে",
+DlgTableWidth : "পà§à¦°à¦¸à§à¦¥",
+DlgTableWidthPx : "পিকà§à¦¸à§‡à¦²",
+DlgTableWidthPc : "শতকরা",
+DlgTableHeight : "দৈরà§à¦˜à§à¦¯",
+DlgTableCellSpace : "সেল সà§à¦ªà§‡à¦¸",
+DlgTableCellPad : "সেল পà§à¦¯à¦¾à¦¡à¦¿à¦‚",
+DlgTableCaption : "শীরà§à¦·à¦•",
+DlgTableSummary : "সারাংশ",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "সেল পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgCellWidth : "পà§à¦°à¦¸à§à¦¥",
+DlgCellWidthPx : "পিকà§à¦¸à§‡à¦²",
+DlgCellWidthPc : "শতকরা",
+DlgCellHeight : "দৈরà§à¦˜à§à¦¯",
+DlgCellWordWrap : "ওয়ারà§à¦¡ রেপ",
+DlgCellWordWrapNotSet : "<সেট নেই>",
+DlgCellWordWrapYes : "হাà¦",
+DlgCellWordWrapNo : "না",
+DlgCellHorAlign : "হরাইজনà§à¦Ÿà¦¾à¦² à¦à¦²à¦¾à¦‡à¦¨à¦®à§‡à¦¨à§à¦Ÿ",
+DlgCellHorAlignNotSet : "<সেট নেই>",
+DlgCellHorAlignLeft : "বামে",
+DlgCellHorAlignCenter : "মাà¦à¦–ানে",
+DlgCellHorAlignRight: "ডানে",
+DlgCellVerAlign : "ভারà§à¦Ÿà¦¿à¦•à§à¦¯à¦¾à¦² à¦à¦²à¦¾à¦‡à¦¨à¦®à§‡à¦¨à§à¦Ÿ",
+DlgCellVerAlignNotSet : "<সেট নেই>",
+DlgCellVerAlignTop : "উপর",
+DlgCellVerAlignMiddle : "মধà§à¦¯",
+DlgCellVerAlignBottom : "নীচে",
+DlgCellVerAlignBaseline : "মূলরেখা",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "রো সà§à¦ªà§à¦¯à¦¾à¦¨",
+DlgCellCollSpan : "কলাম সà§à¦ªà§à¦¯à¦¾à¦¨",
+DlgCellBackColor : "বà§à¦¯à¦¾à¦•à¦—à§à¦°à¦¾à¦‰à¦¨à§à¦¡ রং",
+DlgCellBorderColor : "বরà§à¦¡à¦¾à¦°à§‡à¦° রং",
+DlgCellBtnSelect : "বাছাই কর",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "খোà¦à¦œà§‹",
+DlgFindFindBtn : "খোà¦à¦œà§‹",
+DlgFindNotFoundMsg : "আপনার উলà§à¦²à§‡à¦–িত টেকসà§à¦Ÿ পাওয়া যায়নি",
+
+// Replace Dialog
+DlgReplaceTitle : "বদলে দাও",
+DlgReplaceFindLbl : "যা খà§à¦à¦œà¦¤à§‡ হবে:",
+DlgReplaceReplaceLbl : "যার সাথে বদলাতে হবে:",
+DlgReplaceCaseChk : "কেস মিলাও",
+DlgReplaceReplaceBtn : "বদলে দাও",
+DlgReplaceReplAllBtn : "সব বদলে দাও",
+DlgReplaceWordChk : "পà§à¦°à¦¾ শবà§à¦¦ মেলাও",
+
+// Paste Operations / Dialog
+PasteErrorCut : "আপনার বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° সà§à¦°à¦•à§à¦·à¦¾ সেটিংস à¦à¦¡à¦¿à¦Ÿà¦°à¦•à§‡ অটোমেটিক কাট করার অনà§à¦®à¦¤à¦¿ দেয়নি। দয়া করে à¦à¦‡ কাজের জনà§à¦¯ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ (Ctrl+X)।",
+PasteErrorCopy : "আপনার বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° সà§à¦°à¦•à§à¦·à¦¾ সেটিংস à¦à¦¡à¦¿à¦Ÿà¦°à¦•à§‡ অটোমেটিক কপি করার অনà§à¦®à¦¤à¦¿ দেয়নি। দয়া করে à¦à¦‡ কাজের জনà§à¦¯ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ (Ctrl+C)।",
+
+PasteAsText : "সাদা টেকà§à¦¸à¦Ÿ হিসেবে পেসà§à¦Ÿ কর",
+PasteFromWord : "ওয়ারà§à¦¡ থেকে পেসà§à¦Ÿ কর",
+
+DlgPasteMsg2 : "অনà§à¦—à§à¦°à¦¹ করে নীচের বাকà§à¦¸à§‡ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করে (<STRONG>Ctrl+V</STRONG>) পেসà§à¦Ÿ করà§à¦¨ à¦à¦¬à¦‚ <STRONG>OK</STRONG> চাপ দিন",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "ফনà§à¦Ÿ ফেস ডেফিনেশন ইগনোর করà§à¦¨",
+DlgPasteRemoveStyles : "সà§à¦Ÿà¦¾à¦‡à¦² ডেফিনেশন সরিয়ে দিন",
+
+// Color Picker
+ColorAutomatic : "অটোমেটিক",
+ColorMoreColors : "আরও রং...",
+
+// Document Properties
+DocProps : "ডকà§à¦¯à§à¦®à§‡à¦¨à§à¦Ÿ পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+
+// Anchor Dialog
+DlgAnchorTitle : "নোঙরের পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgAnchorName : "নোঙরের নাম",
+DlgAnchorErrorName : "নোঙরের নাম টাইপ করà§à¦¨",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "শবà§à¦¦à¦•à§‹à¦·à§‡ নেই",
+DlgSpellChangeTo : "à¦à¦¤à§‡ বদলাও",
+DlgSpellBtnIgnore : "ইগনোর কর",
+DlgSpellBtnIgnoreAll : "সব ইগনোর কর",
+DlgSpellBtnReplace : "বদলে দাও",
+DlgSpellBtnReplaceAll : "সব বদলে দাও",
+DlgSpellBtnUndo : "আনà§à¦¡à§",
+DlgSpellNoSuggestions : "- কোন সাজেশন নেই -",
+DlgSpellProgress : "বানান পরীকà§à¦·à¦¾ চলছে...",
+DlgSpellNoMispell : "বানান পরীকà§à¦·à¦¾ শেষ: কোন ভà§à¦² বানান পাওয়া যায়নি",
+DlgSpellNoChanges : "বানান পরীকà§à¦·à¦¾ শেষ: কোন শবà§à¦¦ পরিবরà§à¦¤à¦¨ করা হয়নি",
+DlgSpellOneChange : "বানান পরীকà§à¦·à¦¾ শেষ: à¦à¦•à¦Ÿà¦¿ মাতà§à¦° শবà§à¦¦ পরিবরà§à¦¤à¦¨ করা হয়েছে",
+DlgSpellManyChanges : "বানান পরীকà§à¦·à¦¾ শেষ: %1 গà§à¦²à§‹ শবà§à¦¦ বদলে গà§à¦¯à¦¾à¦›à§‡",
+
+IeSpellDownload : "বানান পরীকà§à¦·à¦• ইনসà§à¦Ÿà¦² করা নেই। আপনি কি à¦à¦–নই à¦à¦Ÿà¦¾ ডাউনলোড করতে চান?",
+
+// Button Dialog
+DlgButtonText : "টেকà§à¦¸à¦Ÿ (ভà§à¦¯à¦¾à¦²à§)",
+DlgButtonType : "পà§à¦°à¦•à¦¾à¦°",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "নাম",
+DlgCheckboxValue : "ভà§à¦¯à¦¾à¦²à§",
+DlgCheckboxSelected : "সিলেকà§à¦Ÿà§‡à¦¡",
+
+// Form Dialog
+DlgFormName : "নাম",
+DlgFormAction : "à¦à¦•à¦¶à§à¦¯à¦¨",
+DlgFormMethod : "পদà§à¦§à¦¤à¦¿",
+
+// Select Field Dialog
+DlgSelectName : "নাম",
+DlgSelectValue : "ভà§à¦¯à¦¾à¦²à§",
+DlgSelectSize : "সাইজ",
+DlgSelectLines : "লাইন সমূহ",
+DlgSelectChkMulti : "à¦à¦•à¦¾à¦§à¦¿à¦• সিলেকশন à¦à¦²à¦¾à¦‰ কর",
+DlgSelectOpAvail : "অনà§à¦¯à¦¾à¦¨à§à¦¯ বিকলà§à¦ª",
+DlgSelectOpText : "টেকà§à¦¸à¦Ÿ",
+DlgSelectOpValue : "ভà§à¦¯à¦¾à¦²à§",
+DlgSelectBtnAdd : "যà§à¦•à§à¦¤",
+DlgSelectBtnModify : "বদলে দাও",
+DlgSelectBtnUp : "উপর",
+DlgSelectBtnDown : "নীচে",
+DlgSelectBtnSetValue : "বাছাই করা ভà§à¦¯à¦¾à¦²à§ হিসেবে সেট কর",
+DlgSelectBtnDelete : "ডিলীট",
+
+// Textarea Dialog
+DlgTextareaName : "নাম",
+DlgTextareaCols : "কলাম",
+DlgTextareaRows : "রো",
+
+// Text Field Dialog
+DlgTextName : "নাম",
+DlgTextValue : "ভà§à¦¯à¦¾à¦²à§",
+DlgTextCharWidth : "কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦° পà§à¦°à¦¶à¦¸à§à¦¤à¦¤à¦¾",
+DlgTextMaxChars : "সরà§à¦¬à¦¾à¦§à¦¿à¦• কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦°",
+DlgTextType : "টাইপ",
+DlgTextTypeText : "টেকà§à¦¸à¦Ÿ",
+DlgTextTypePass : "পাসওয়ারà§à¦¡",
+
+// Hidden Field Dialog
+DlgHiddenName : "নাম",
+DlgHiddenValue : "ভà§à¦¯à¦¾à¦²à§",
+
+// Bulleted List Dialog
+BulletedListProp : "বà§à¦²à§‡à¦Ÿà§‡à¦¡ সূচী পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+NumberedListProp : "সাংখà§à¦¯à¦¿à¦• সূচী পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "পà§à¦°à¦•à¦¾à¦°",
+DlgLstTypeCircle : "গোল",
+DlgLstTypeDisc : "ডিসà§à¦•",
+DlgLstTypeSquare : "চৌকোণা",
+DlgLstTypeNumbers : "সংখà§à¦¯à¦¾ (1, 2, 3)",
+DlgLstTypeLCase : "ছোট অকà§à¦·à¦° (a, b, c)",
+DlgLstTypeUCase : "বড় অকà§à¦·à¦° (A, B, C)",
+DlgLstTypeSRoman : "ছোট রোমান সংখà§à¦¯à¦¾ (i, ii, iii)",
+DlgLstTypeLRoman : "বড় রোমান সংখà§à¦¯à¦¾ (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "সাধারন",
+DlgDocBackTab : "বà§à¦¯à¦¾à¦•à¦—à§à¦°à¦¾à¦‰à¦¨à§à¦¡",
+DlgDocColorsTab : "রং à¦à¦¬à¦‚ মারà§à¦œà¦¿à¦¨",
+DlgDocMetaTab : "মেটাডেটা",
+
+DlgDocPageTitle : "পেজ শীরà§à¦·à¦•",
+DlgDocLangDir : "ভাষা লিখার দিক",
+DlgDocLangDirLTR : "বাম থেকে ডানে (LTR)",
+DlgDocLangDirRTL : "ডান থেকে বামে (RTL)",
+DlgDocLangCode : "ভাষা কোড",
+DlgDocCharSet : "কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦° সেট à¦à¦¨à¦•à§‹à¦¡à¦¿à¦‚",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "অনà§à¦¯ কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦° সেট à¦à¦¨à¦•à§‹à¦¡à¦¿à¦‚",
+
+DlgDocDocType : "ডকà§à¦¯à§à¦®à§‡à¦¨à§à¦Ÿ টাইপ হেডিং",
+DlgDocDocTypeOther : "অনà§à¦¯ ডকà§à¦¯à§à¦®à§‡à¦¨à§à¦Ÿ টাইপ হেডিং",
+DlgDocIncXHTML : "XHTML ডেকà§à¦²à¦¾à¦°à§‡à¦¶à¦¨ যà§à¦•à§à¦¤ কর",
+DlgDocBgColor : "বà§à¦¯à¦¾à¦•à¦—à§à¦°à¦¾à¦‰à¦¨à§à¦¡ রং",
+DlgDocBgImage : "বà§à¦¯à¦¾à¦•à¦—à§à¦°à¦¾à¦‰à¦¨à§à¦¡ ছবির URL",
+DlgDocBgNoScroll : "সà§à¦•à§à¦°à¦²à¦¹à§€à¦¨ বà§à¦¯à¦¾à¦•à¦—à§à¦°à¦¾à¦‰à¦¨à§à¦¡",
+DlgDocCText : "টেকà§à¦¸à¦Ÿ",
+DlgDocCLink : "লিংক",
+DlgDocCVisited : "ভিজিট করা লিংক",
+DlgDocCActive : "সকà§à¦°à¦¿à§Ÿ লিংক",
+DlgDocMargins : "পেজ মারà§à¦œà¦¿à¦¨",
+DlgDocMaTop : "উপর",
+DlgDocMaLeft : "বামে",
+DlgDocMaRight : "ডানে",
+DlgDocMaBottom : "নীচে",
+DlgDocMeIndex : "ডকà§à¦¯à§à¦®à§‡à¦¨à§à¦Ÿ ইনà§à¦¡à§‡à¦•à§à¦¸ কিওয়ারà§à¦¡ (কমা দà§à¦¬à¦¾à¦°à¦¾ বিচà§à¦›à¦¿à¦¨à§à¦¨)",
+DlgDocMeDescr : "ডকà§à¦¯à§‚মেনà§à¦Ÿ বরà§à¦£à¦¨à¦¾",
+DlgDocMeAuthor : "লেখক",
+DlgDocMeCopy : "কপীরাইট",
+DlgDocPreview : "পà§à¦°à§€à¦­à¦¿à¦‰",
+
+// Templates Dialog
+Templates : "টেমপà§à¦²à§‡à¦Ÿ",
+DlgTemplatesTitle : "কনটেনà§à¦Ÿ টেমপà§à¦²à§‡à¦Ÿ",
+DlgTemplatesSelMsg : "অনà§à¦—à§à¦°à¦¹ করে à¦à¦¡à¦¿à¦Ÿà¦°à§‡ ওপেন করার জনà§à¦¯ টেমপà§à¦²à§‡à¦Ÿ বাছাই করà§à¦¨<br>(আসল কনটেনà§à¦Ÿ হারিয়ে যাবে):",
+DlgTemplatesLoading : "টেমপà§à¦²à§‡à¦Ÿ লিসà§à¦Ÿ হারিয়ে যাবে। অনà§à¦—à§à¦°à¦¹ করে অপেকà§à¦·à¦¾ করà§à¦¨...",
+DlgTemplatesNoTpl : "(কোন টেমপà§à¦²à§‡à¦Ÿ ডিফাইন করা নেই)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "কে বানিয়েছে",
+DlgAboutBrowserInfoTab : "বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° বà§à¦¯à¦¾à¦ªà¦¾à¦°à§‡ তথà§à¦¯",
+DlgAboutLicenseTab : "লাইসেনà§à¦¸",
+DlgAboutVersion : "ভারà§à¦¸à¦¨",
+DlgAboutInfo : "আরও তথà§à¦¯à§‡à¦° জনà§à¦¯ যান",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/bs.js b/httemplate/elements/fckeditor/editor/lang/bs.js
new file mode 100644
index 0000000..361ca58
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/bs.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Bosnian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skupi trake sa alatima",
+ToolbarExpand : "Otvori trake sa alatima",
+
+// Toolbar Items and Context Menu
+Save : "Snimi",
+NewPage : "Novi dokument",
+Preview : "Prikaži",
+Cut : "Izreži",
+Copy : "Kopiraj",
+Paste : "Zalijepi",
+PasteText : "Zalijepi kao obièan tekst",
+PasteWord : "Zalijepi iz Word-a",
+Print : "Å tampaj",
+SelectAll : "Selektuj sve",
+RemoveFormat : "Poništi format",
+InsertLinkLbl : "Link",
+InsertLink : "Ubaci/Izmjeni link",
+RemoveLink : "Izbriši link",
+VisitLink : "Open Link", //MISSING
+Anchor : "Insert/Edit Anchor", //MISSING
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Slika",
+InsertImage : "Ubaci/Izmjeni sliku",
+InsertFlashLbl : "Flash", //MISSING
+InsertFlash : "Insert/Edit Flash", //MISSING
+InsertTableLbl : "Tabela",
+InsertTable : "Ubaci/Izmjeni tabelu",
+InsertLineLbl : "Linija",
+InsertLine : "Ubaci horizontalnu liniju",
+InsertSpecialCharLbl: "Specijalni karakter",
+InsertSpecialChar : "Ubaci specijalni karater",
+InsertSmileyLbl : "Smješko",
+InsertSmiley : "Ubaci smješka",
+About : "O FCKeditor-u",
+Bold : "Boldiraj",
+Italic : "Ukosi",
+Underline : "Podvuci",
+StrikeThrough : "Precrtaj",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Lijevo poravnanje",
+CenterJustify : "Centralno poravnanje",
+RightJustify : "Desno poravnanje",
+BlockJustify : "Puno poravnanje",
+DecreaseIndent : "Smanji uvod",
+IncreaseIndent : "Poveæaj uvod",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Vrati",
+Redo : "Ponovi",
+NumberedListLbl : "Numerisana lista",
+NumberedList : "Ubaci/Izmjeni numerisanu listu",
+BulletedListLbl : "Lista",
+BulletedList : "Ubaci/Izmjeni listu",
+ShowTableBorders : "Pokaži okvire tabela",
+ShowDetails : "Pokaži detalje",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Velièina",
+TextColor : "Boja teksta",
+BGColor : "Boja pozadine",
+Source : "HTML kôd",
+Find : "Naði",
+Replace : "Zamjeni",
+SpellCheck : "Check Spelling", //MISSING
+UniversalKeyboard : "Universal Keyboard", //MISSING
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "Form", //MISSING
+Checkbox : "Checkbox", //MISSING
+RadioButton : "Radio Button", //MISSING
+TextField : "Text Field", //MISSING
+Textarea : "Textarea", //MISSING
+HiddenField : "Hidden Field", //MISSING
+Button : "Button", //MISSING
+SelectionField : "Selection Field", //MISSING
+ImageButton : "Image Button", //MISSING
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Izmjeni link",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Briši redove",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Briši kolone",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Briši æelije",
+MergeCells : "Spoji æelije",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Delete Table", //MISSING
+CellProperties : "Svojstva æelije",
+TableProperties : "Svojstva tabele",
+ImageProperties : "Svojstva slike",
+FlashProperties : "Flash Properties", //MISSING
+
+AnchorProp : "Anchor Properties", //MISSING
+ButtonProp : "Button Properties", //MISSING
+CheckboxProp : "Checkbox Properties", //MISSING
+HiddenFieldProp : "Hidden Field Properties", //MISSING
+RadioButtonProp : "Radio Button Properties", //MISSING
+ImageButtonProp : "Image Button Properties", //MISSING
+TextFieldProp : "Text Field Properties", //MISSING
+SelectionFieldProp : "Selection Field Properties", //MISSING
+TextareaProp : "Textarea Properties", //MISSING
+FormProp : "Form Properties", //MISSING
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Procesiram XHTML. Molim saèekajte...",
+Done : "Gotovo",
+PasteWordConfirm : "Tekst koji želite zalijepiti èini se da je kopiran iz Worda. Da li želite da se prvo oèisti?",
+NotCompatiblePaste : "Ova komanda je podržana u Internet Explorer-u verzijama 5.5 ili novijim. Da li želite da izvršite lijepljenje teksta bez èišæenja?",
+UnknownToolbarItem : "Nepoznata stavka sa trake sa alatima \"%1\"",
+UnknownCommand : "Nepoznata komanda \"%1\"",
+NotImplemented : "Komanda nije implementirana",
+UnknownToolbarSet : "Traka sa alatima \"%1\" ne postoji",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Odustani",
+DlgBtnClose : "Zatvori",
+DlgBtnBrowseServer : "Browse Server", //MISSING
+DlgAdvancedTag : "Naprednije",
+DlgOpOther : "<Other>", //MISSING
+DlgInfoTab : "Info", //MISSING
+DlgAlertUrl : "Please insert the URL", //MISSING
+
+// General Dialogs Labels
+DlgGenNotSet : "<nije podešeno>",
+DlgGenId : "Id",
+DlgGenLangDir : "Smjer pisanja",
+DlgGenLangDirLtr : "S lijeva na desno (LTR)",
+DlgGenLangDirRtl : "S desna na lijevo (RTL)",
+DlgGenLangCode : "Jezièni kôd",
+DlgGenAccessKey : "Pristupna tipka",
+DlgGenName : "Naziv",
+DlgGenTabIndex : "Tab indeks",
+DlgGenLongDescr : "Dugaèki opis URL-a",
+DlgGenClass : "Klase CSS stilova",
+DlgGenTitle : "Advisory title",
+DlgGenContType : "Advisory vrsta sadržaja",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Svojstva slike",
+DlgImgInfoTab : "Info slike",
+DlgImgBtnUpload : "Å alji na server",
+DlgImgURL : "URL",
+DlgImgUpload : "Å alji",
+DlgImgAlt : "Tekst na slici",
+DlgImgWidth : "Å irina",
+DlgImgHeight : "Visina",
+DlgImgLockRatio : "Zakljuèaj odnos",
+DlgBtnResetSize : "Resetuj dimenzije",
+DlgImgBorder : "Okvir",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Poravnanje",
+DlgImgAlignLeft : "Lijevo",
+DlgImgAlignAbsBottom: "Abs dole",
+DlgImgAlignAbsMiddle: "Abs sredina",
+DlgImgAlignBaseline : "Bazno",
+DlgImgAlignBottom : "Dno",
+DlgImgAlignMiddle : "Sredina",
+DlgImgAlignRight : "Desno",
+DlgImgAlignTextTop : "Vrh teksta",
+DlgImgAlignTop : "Vrh",
+DlgImgPreview : "Prikaz",
+DlgImgAlertUrl : "Molimo ukucajte URL od slike.",
+DlgImgLinkTab : "Link", //MISSING
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties", //MISSING
+DlgFlashChkPlay : "Auto Play", //MISSING
+DlgFlashChkLoop : "Loop", //MISSING
+DlgFlashChkMenu : "Enable Flash Menu", //MISSING
+DlgFlashScale : "Scale", //MISSING
+DlgFlashScaleAll : "Show all", //MISSING
+DlgFlashScaleNoBorder : "No Border", //MISSING
+DlgFlashScaleFit : "Exact Fit", //MISSING
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link info",
+DlgLnkTargetTab : "Prozor",
+
+DlgLnkType : "Tip linka",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Sidro na ovoj stranici",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<drugi>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Izaberi sidro",
+DlgLnkAnchorByName : "Po nazivu sidra",
+DlgLnkAnchorById : "Po Id-u elementa",
+DlgLnkNoAnchors : "(Nema dostupnih sidra na stranici)",
+DlgLnkEMail : "E-Mail Adresa",
+DlgLnkEMailSubject : "Subjekt poruke",
+DlgLnkEMailBody : "Poruka",
+DlgLnkUpload : "Å alji",
+DlgLnkBtnUpload : "Å alji na server",
+
+DlgLnkTarget : "Prozor",
+DlgLnkTargetFrame : "<frejm>",
+DlgLnkTargetPopup : "<popup prozor>",
+DlgLnkTargetBlank : "Novi prozor (_blank)",
+DlgLnkTargetParent : "Glavni prozor (_parent)",
+DlgLnkTargetSelf : "Isti prozor (_self)",
+DlgLnkTargetTop : "Najgornji prozor (_top)",
+DlgLnkTargetFrameName : "Target Frame Name", //MISSING
+DlgLnkPopWinName : "Naziv popup prozora",
+DlgLnkPopWinFeat : "Moguænosti popup prozora",
+DlgLnkPopResize : "Promjenljive velièine",
+DlgLnkPopLocation : "Traka za lokaciju",
+DlgLnkPopMenu : "Izborna traka",
+DlgLnkPopScroll : "Scroll traka",
+DlgLnkPopStatus : "Statusna traka",
+DlgLnkPopToolbar : "Traka sa alatima",
+DlgLnkPopFullScrn : "Cijeli ekran (IE)",
+DlgLnkPopDependent : "Ovisno (Netscape)",
+DlgLnkPopWidth : "Å irina",
+DlgLnkPopHeight : "Visina",
+DlgLnkPopLeft : "Lijeva pozicija",
+DlgLnkPopTop : "Gornja pozicija",
+
+DlnLnkMsgNoUrl : "Molimo ukucajte URL link",
+DlnLnkMsgNoEMail : "Molimo ukucajte e-mail adresu",
+DlnLnkMsgNoAnchor : "Molimo izaberite sidro",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Izaberi boju",
+DlgColorBtnClear : "Oèisti",
+DlgColorHighlight : "Igled",
+DlgColorSelected : "Selektovana",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ubaci smješka",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Izaberi specijalni karakter",
+
+// Table Dialog
+DlgTableTitle : "Svojstva tabele",
+DlgTableRows : "Redova",
+DlgTableColumns : "Kolona",
+DlgTableBorder : "Okvir",
+DlgTableAlign : "Poravnanje",
+DlgTableAlignNotSet : "<Nije podešeno>",
+DlgTableAlignLeft : "Lijevo",
+DlgTableAlignCenter : "Centar",
+DlgTableAlignRight : "Desno",
+DlgTableWidth : "Å irina",
+DlgTableWidthPx : "piksela",
+DlgTableWidthPc : "posto",
+DlgTableHeight : "Visina",
+DlgTableCellSpace : "Razmak æelija",
+DlgTableCellPad : "Uvod æelija",
+DlgTableCaption : "Naslov",
+DlgTableSummary : "Summary", //MISSING
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Svojstva æelije",
+DlgCellWidth : "Å irina",
+DlgCellWidthPx : "piksela",
+DlgCellWidthPc : "posto",
+DlgCellHeight : "Visina",
+DlgCellWordWrap : "Vrapuj tekst",
+DlgCellWordWrapNotSet : "<Nije podešeno>",
+DlgCellWordWrapYes : "Da",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Horizontalno poravnanje",
+DlgCellHorAlignNotSet : "<Nije podešeno>",
+DlgCellHorAlignLeft : "Lijevo",
+DlgCellHorAlignCenter : "Centar",
+DlgCellHorAlignRight: "Desno",
+DlgCellVerAlign : "Vertikalno poravnanje",
+DlgCellVerAlignNotSet : "<Nije podešeno>",
+DlgCellVerAlignTop : "Gore",
+DlgCellVerAlignMiddle : "Sredina",
+DlgCellVerAlignBottom : "Dno",
+DlgCellVerAlignBaseline : "Bazno",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Spajanje æelija",
+DlgCellCollSpan : "Spajanje kolona",
+DlgCellBackColor : "Boja pozadine",
+DlgCellBorderColor : "Boja okvira",
+DlgCellBtnSelect : "Selektuj...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Naði",
+DlgFindFindBtn : "Naði",
+DlgFindNotFoundMsg : "Traženi tekst nije pronaðen.",
+
+// Replace Dialog
+DlgReplaceTitle : "Zamjeni",
+DlgReplaceFindLbl : "Naði šta:",
+DlgReplaceReplaceLbl : "Zamjeni sa:",
+DlgReplaceCaseChk : "Uporeðuj velika/mala slova",
+DlgReplaceReplaceBtn : "Zamjeni",
+DlgReplaceReplAllBtn : "Zamjeni sve",
+DlgReplaceWordChk : "Uporeðuj samo cijelu rijeè",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Sigurnosne postavke vašeg pretraživaèa ne dozvoljavaju operacije automatskog rezanja. Molimo koristite kraticu na tastaturi (Ctrl+X).",
+PasteErrorCopy : "Sigurnosne postavke Vašeg pretraživaèa ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tastaturi (Ctrl+C).",
+
+PasteAsText : "Zalijepi kao obièan tekst",
+PasteFromWord : "Zalijepi iz Word-a",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.", //MISSING
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignore Font Face definitions", //MISSING
+DlgPasteRemoveStyles : "Remove Styles definitions", //MISSING
+
+// Color Picker
+ColorAutomatic : "Automatska",
+ColorMoreColors : "Više boja...",
+
+// Document Properties
+DocProps : "Document Properties", //MISSING
+
+// Anchor Dialog
+DlgAnchorTitle : "Anchor Properties", //MISSING
+DlgAnchorName : "Anchor Name", //MISSING
+DlgAnchorErrorName : "Please type the anchor name", //MISSING
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Not in dictionary", //MISSING
+DlgSpellChangeTo : "Change to", //MISSING
+DlgSpellBtnIgnore : "Ignore", //MISSING
+DlgSpellBtnIgnoreAll : "Ignore All", //MISSING
+DlgSpellBtnReplace : "Replace", //MISSING
+DlgSpellBtnReplaceAll : "Replace All", //MISSING
+DlgSpellBtnUndo : "Undo", //MISSING
+DlgSpellNoSuggestions : "- No suggestions -", //MISSING
+DlgSpellProgress : "Spell check in progress...", //MISSING
+DlgSpellNoMispell : "Spell check complete: No misspellings found", //MISSING
+DlgSpellNoChanges : "Spell check complete: No words changed", //MISSING
+DlgSpellOneChange : "Spell check complete: One word changed", //MISSING
+DlgSpellManyChanges : "Spell check complete: %1 words changed", //MISSING
+
+IeSpellDownload : "Spell checker not installed. Do you want to download it now?", //MISSING
+
+// Button Dialog
+DlgButtonText : "Text (Value)", //MISSING
+DlgButtonType : "Type", //MISSING
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name", //MISSING
+DlgCheckboxValue : "Value", //MISSING
+DlgCheckboxSelected : "Selected", //MISSING
+
+// Form Dialog
+DlgFormName : "Name", //MISSING
+DlgFormAction : "Action", //MISSING
+DlgFormMethod : "Method", //MISSING
+
+// Select Field Dialog
+DlgSelectName : "Name", //MISSING
+DlgSelectValue : "Value", //MISSING
+DlgSelectSize : "Size", //MISSING
+DlgSelectLines : "lines", //MISSING
+DlgSelectChkMulti : "Allow multiple selections", //MISSING
+DlgSelectOpAvail : "Available Options", //MISSING
+DlgSelectOpText : "Text", //MISSING
+DlgSelectOpValue : "Value", //MISSING
+DlgSelectBtnAdd : "Add", //MISSING
+DlgSelectBtnModify : "Modify", //MISSING
+DlgSelectBtnUp : "Up", //MISSING
+DlgSelectBtnDown : "Down", //MISSING
+DlgSelectBtnSetValue : "Set as selected value", //MISSING
+DlgSelectBtnDelete : "Delete", //MISSING
+
+// Textarea Dialog
+DlgTextareaName : "Name", //MISSING
+DlgTextareaCols : "Columns", //MISSING
+DlgTextareaRows : "Rows", //MISSING
+
+// Text Field Dialog
+DlgTextName : "Name", //MISSING
+DlgTextValue : "Value", //MISSING
+DlgTextCharWidth : "Character Width", //MISSING
+DlgTextMaxChars : "Maximum Characters", //MISSING
+DlgTextType : "Type", //MISSING
+DlgTextTypeText : "Text", //MISSING
+DlgTextTypePass : "Password", //MISSING
+
+// Hidden Field Dialog
+DlgHiddenName : "Name", //MISSING
+DlgHiddenValue : "Value", //MISSING
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted List Properties", //MISSING
+NumberedListProp : "Numbered List Properties", //MISSING
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Type", //MISSING
+DlgLstTypeCircle : "Circle", //MISSING
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "Square", //MISSING
+DlgLstTypeNumbers : "Numbers (1, 2, 3)", //MISSING
+DlgLstTypeLCase : "Lowercase Letters (a, b, c)", //MISSING
+DlgLstTypeUCase : "Uppercase Letters (A, B, C)", //MISSING
+DlgLstTypeSRoman : "Small Roman Numerals (i, ii, iii)", //MISSING
+DlgLstTypeLRoman : "Large Roman Numerals (I, II, III)", //MISSING
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General", //MISSING
+DlgDocBackTab : "Background", //MISSING
+DlgDocColorsTab : "Colors and Margins", //MISSING
+DlgDocMetaTab : "Meta Data", //MISSING
+
+DlgDocPageTitle : "Page Title", //MISSING
+DlgDocLangDir : "Language Direction", //MISSING
+DlgDocLangDirLTR : "Left to Right (LTR)", //MISSING
+DlgDocLangDirRTL : "Right to Left (RTL)", //MISSING
+DlgDocLangCode : "Language Code", //MISSING
+DlgDocCharSet : "Character Set Encoding", //MISSING
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Other Character Set Encoding", //MISSING
+
+DlgDocDocType : "Document Type Heading", //MISSING
+DlgDocDocTypeOther : "Other Document Type Heading", //MISSING
+DlgDocIncXHTML : "Include XHTML Declarations", //MISSING
+DlgDocBgColor : "Background Color", //MISSING
+DlgDocBgImage : "Background Image URL", //MISSING
+DlgDocBgNoScroll : "Nonscrolling Background", //MISSING
+DlgDocCText : "Text", //MISSING
+DlgDocCLink : "Link", //MISSING
+DlgDocCVisited : "Visited Link", //MISSING
+DlgDocCActive : "Active Link", //MISSING
+DlgDocMargins : "Page Margins", //MISSING
+DlgDocMaTop : "Top", //MISSING
+DlgDocMaLeft : "Left", //MISSING
+DlgDocMaRight : "Right", //MISSING
+DlgDocMaBottom : "Bottom", //MISSING
+DlgDocMeIndex : "Document Indexing Keywords (comma separated)", //MISSING
+DlgDocMeDescr : "Document Description", //MISSING
+DlgDocMeAuthor : "Author", //MISSING
+DlgDocMeCopy : "Copyright", //MISSING
+DlgDocPreview : "Preview", //MISSING
+
+// Templates Dialog
+Templates : "Templates", //MISSING
+DlgTemplatesTitle : "Content Templates", //MISSING
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):", //MISSING
+DlgTemplatesLoading : "Loading templates list. Please wait...", //MISSING
+DlgTemplatesNoTpl : "(No templates defined)", //MISSING
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "About", //MISSING
+DlgAboutBrowserInfoTab : "Browser Info", //MISSING
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "verzija",
+DlgAboutInfo : "Za više informacija posjetite",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ca.js b/httemplate/elements/fckeditor/editor/lang/ca.js
new file mode 100644
index 0000000..483e042
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ca.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Catalan language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Redueix la barra d'eines",
+ToolbarExpand : "Amplia la barra d'eines",
+
+// Toolbar Items and Context Menu
+Save : "Desa",
+NewPage : "Nova Pàgina",
+Preview : "Visualització prèvia",
+Cut : "Retalla",
+Copy : "Copia",
+Paste : "Enganxa",
+PasteText : "Enganxa com a text no formatat",
+PasteWord : "Enganxa des del Word",
+Print : "Imprimeix",
+SelectAll : "Selecciona-ho tot",
+RemoveFormat : "Elimina Format",
+InsertLinkLbl : "Enllaç",
+InsertLink : "Insereix/Edita enllaç",
+RemoveLink : "Elimina l'enllaç",
+VisitLink : "Obre l'enllaç",
+Anchor : "Insereix/Edita àncora",
+AnchorDelete : "Elimina àncora",
+InsertImageLbl : "Imatge",
+InsertImage : "Insereix/Edita imatge",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insereix/Edita Flash",
+InsertTableLbl : "Taula",
+InsertTable : "Insereix/Edita taula",
+InsertLineLbl : "Línia",
+InsertLine : "Insereix línia horitzontal",
+InsertSpecialCharLbl: "Caràcter Especial",
+InsertSpecialChar : "Insereix caràcter especial",
+InsertSmileyLbl : "Icona",
+InsertSmiley : "Insereix icona",
+About : "Quant a l'FCKeditor",
+Bold : "Negreta",
+Italic : "Cursiva",
+Underline : "Subratllat",
+StrikeThrough : "Barrat",
+Subscript : "Subíndex",
+Superscript : "Superíndex",
+LeftJustify : "Alinia a l'esquerra",
+CenterJustify : "Centrat",
+RightJustify : "Alinia a la dreta",
+BlockJustify : "Justificat",
+DecreaseIndent : "Redueix el sagnat",
+IncreaseIndent : "Augmenta el sagnat",
+Blockquote : "Bloc de cita",
+CreateDiv : "Crea un contenidor Div",
+EditDiv : "Edita el contenidor Div",
+DeleteDiv : "Elimina el contenidor Div",
+Undo : "Desfés",
+Redo : "Refés",
+NumberedListLbl : "Llista numerada",
+NumberedList : "Numeració activada/desactivada",
+BulletedListLbl : "Llista de pics",
+BulletedList : "Pics activats/descativats",
+ShowTableBorders : "Mostra les vores de les taules",
+ShowDetails : "Mostra detalls",
+Style : "Estil",
+FontFormat : "Format",
+Font : "Tipus de lletra",
+FontSize : "Mida",
+TextColor : "Color de Text",
+BGColor : "Color de Fons",
+Source : "Codi font",
+Find : "Cerca",
+Replace : "Reemplaça",
+SpellCheck : "Revisa l'ortografia",
+UniversalKeyboard : "Teclat universal",
+PageBreakLbl : "Salt de pàgina",
+PageBreak : "Insereix salt de pàgina",
+
+Form : "Formulari",
+Checkbox : "Casella de verificació",
+RadioButton : "Botó d'opció",
+TextField : "Camp de text",
+Textarea : "Àrea de text",
+HiddenField : "Camp ocult",
+Button : "Botó",
+SelectionField : "Camp de selecció",
+ImageButton : "Botó d'imatge",
+
+FitWindow : "Maximiza la mida de l'editor",
+ShowBlocks : "Mostra els blocs",
+
+// Context Menu
+EditLink : "Edita l'enllaç",
+CellCM : "Cel·la",
+RowCM : "Fila",
+ColumnCM : "Columna",
+InsertRowAfter : "Insereix fila darrera",
+InsertRowBefore : "Insereix fila abans de",
+DeleteRows : "Suprimeix una fila",
+InsertColumnAfter : "Insereix columna darrera",
+InsertColumnBefore : "Insereix columna abans de",
+DeleteColumns : "Suprimeix una columna",
+InsertCellAfter : "Insereix cel·la darrera",
+InsertCellBefore : "Insereix cel·la abans de",
+DeleteCells : "Suprimeix les cel·les",
+MergeCells : "Fusiona les cel·les",
+MergeRight : "Fusiona cap a la dreta",
+MergeDown : "Fusiona cap avall",
+HorizontalSplitCell : "Divideix la cel·la horitzontalment",
+VerticalSplitCell : "Divideix la cel·la verticalment",
+TableDelete : "Suprimeix la taula",
+CellProperties : "Propietats de la cel·la",
+TableProperties : "Propietats de la taula",
+ImageProperties : "Propietats de la imatge",
+FlashProperties : "Propietats del Flash",
+
+AnchorProp : "Propietats de l'àncora",
+ButtonProp : "Propietats del botó",
+CheckboxProp : "Propietats de la casella de verificació",
+HiddenFieldProp : "Propietats del camp ocult",
+RadioButtonProp : "Propietats del botó d'opció",
+ImageButtonProp : "Propietats del botó d'imatge",
+TextFieldProp : "Propietats del camp de text",
+SelectionFieldProp : "Propietats del camp de selecció",
+TextareaProp : "Propietats de l'àrea de text",
+FormProp : "Propietats del formulari",
+
+FontFormats : "Normal;Formatejat;Adreça;Encapçalament 1;Encapçalament 2;Encapçalament 3;Encapçalament 4;Encapçalament 5;Encapçalament 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Processant XHTML. Si us plau esperi...",
+Done : "Fet",
+PasteWordConfirm : "El text que voleu enganxar sembla provenir de Word. Voleu netejar aquest text abans que sigui enganxat?",
+NotCompatiblePaste : "Aquesta funció és disponible per a Internet Explorer versió 5.5 o superior. Voleu enganxar sense netejar?",
+UnknownToolbarItem : "Element de la barra d'eines desconegut \"%1\"",
+UnknownCommand : "Nom de comanda desconegut \"%1\"",
+NotImplemented : "Mètode no implementat",
+UnknownToolbarSet : "Conjunt de barra d'eines \"%1\" inexistent",
+NoActiveX : "Les preferències del navegador poden limitar algunes funcions d'aquest editor. Cal habilitar l'opció \"Executa controls ActiveX i plug-ins\". Poden sorgir errors i poden faltar algunes funcions.",
+BrowseServerBlocked : "El visualitzador de recursos no s'ha pogut obrir. Assegura't de que els bloquejos de finestres emergents estan desactivats.",
+DialogBlocked : "No ha estat possible obrir una finestra de diàleg. Assegureu-vos que els bloquejos de finestres emergents estan desactivats.",
+VisitLinkBlocked : "No ha estat possible obrir una nova finestra. Assegureu-vos que els bloquejos de finestres emergents estan desactivats.",
+
+// Dialogs
+DlgBtnOK : "D'acord",
+DlgBtnCancel : "Cancel·la",
+DlgBtnClose : "Tanca",
+DlgBtnBrowseServer : "Veure servidor",
+DlgAdvancedTag : "Avançat",
+DlgOpOther : "Altres",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Si us plau, afegiu la URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<no definit>",
+DlgGenId : "Id",
+DlgGenLangDir : "Direcció de l'idioma",
+DlgGenLangDirLtr : "D'esquerra a dreta (LTR)",
+DlgGenLangDirRtl : "De dreta a esquerra (RTL)",
+DlgGenLangCode : "Codi d'idioma",
+DlgGenAccessKey : "Clau d'accés",
+DlgGenName : "Nom",
+DlgGenTabIndex : "Index de Tab",
+DlgGenLongDescr : "Descripció llarga de la URL",
+DlgGenClass : "Classes del full d'estil",
+DlgGenTitle : "Títol consultiu",
+DlgGenContType : "Tipus de contingut consultiu",
+DlgGenLinkCharset : "Conjunt de caràcters font enllaçat",
+DlgGenStyle : "Estil",
+
+// Image Dialog
+DlgImgTitle : "Propietats de la imatge",
+DlgImgInfoTab : "Informació de la imatge",
+DlgImgBtnUpload : "Envia-la al servidor",
+DlgImgURL : "URL",
+DlgImgUpload : "Puja",
+DlgImgAlt : "Text alternatiu",
+DlgImgWidth : "Amplada",
+DlgImgHeight : "Alçada",
+DlgImgLockRatio : "Bloqueja les proporcions",
+DlgBtnResetSize : "Restaura la mida",
+DlgImgBorder : "Vora",
+DlgImgHSpace : "Espaiat horit.",
+DlgImgVSpace : "Espaiat vert.",
+DlgImgAlign : "Alineació",
+DlgImgAlignLeft : "Ajusta a l'esquerra",
+DlgImgAlignAbsBottom: "Abs Bottom",
+DlgImgAlignAbsMiddle: "Abs Middle",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Bottom",
+DlgImgAlignMiddle : "Middle",
+DlgImgAlignRight : "Ajusta a la dreta",
+DlgImgAlignTextTop : "Text Top",
+DlgImgAlignTop : "Top",
+DlgImgPreview : "Vista prèvia",
+DlgImgAlertUrl : "Si us plau, escriviu la URL de la imatge",
+DlgImgLinkTab : "Enllaç",
+
+// Flash Dialog
+DlgFlashTitle : "Propietats del Flash",
+DlgFlashChkPlay : "Reprodució automàtica",
+DlgFlashChkLoop : "Bucle",
+DlgFlashChkMenu : "Habilita menú Flash",
+DlgFlashScale : "Escala",
+DlgFlashScaleAll : "Mostra-ho tot",
+DlgFlashScaleNoBorder : "Sense vores",
+DlgFlashScaleFit : "Mida exacta",
+
+// Link Dialog
+DlgLnkWindowTitle : "Enllaç",
+DlgLnkInfoTab : "Informació de l'enllaç",
+DlgLnkTargetTab : "Destí",
+
+DlgLnkType : "Tipus d'enllaç",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Àncora en aquesta pàgina",
+DlgLnkTypeEMail : "Correu electrònic",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<altra>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Selecciona una àncora",
+DlgLnkAnchorByName : "Per nom d'àncora",
+DlgLnkAnchorById : "Per Id d'element",
+DlgLnkNoAnchors : "(No hi ha àncores disponibles en aquest document)",
+DlgLnkEMail : "Adreça de correu electrònic",
+DlgLnkEMailSubject : "Assumpte del missatge",
+DlgLnkEMailBody : "Cos del missatge",
+DlgLnkUpload : "Puja",
+DlgLnkBtnUpload : "Envia al servidor",
+
+DlgLnkTarget : "Destí",
+DlgLnkTargetFrame : "<marc>",
+DlgLnkTargetPopup : "<finestra emergent>",
+DlgLnkTargetBlank : "Nova finestra (_blank)",
+DlgLnkTargetParent : "Finestra pare (_parent)",
+DlgLnkTargetSelf : "Mateixa finestra (_self)",
+DlgLnkTargetTop : "Finestra Major (_top)",
+DlgLnkTargetFrameName : "Nom del marc de destí",
+DlgLnkPopWinName : "Nom finestra popup",
+DlgLnkPopWinFeat : "Característiques finestra popup",
+DlgLnkPopResize : "Redimensionable",
+DlgLnkPopLocation : "Barra d'adreça",
+DlgLnkPopMenu : "Barra de menú",
+DlgLnkPopScroll : "Barres d'scroll",
+DlgLnkPopStatus : "Barra d'estat",
+DlgLnkPopToolbar : "Barra d'eines",
+DlgLnkPopFullScrn : "Pantalla completa (IE)",
+DlgLnkPopDependent : "Depenent (Netscape)",
+DlgLnkPopWidth : "Amplada",
+DlgLnkPopHeight : "Alçada",
+DlgLnkPopLeft : "Posició esquerra",
+DlgLnkPopTop : "Posició dalt",
+
+DlnLnkMsgNoUrl : "Si us plau, escrigui l'enllaç URL",
+DlnLnkMsgNoEMail : "Si us plau, escrigui l'adreça correu electrònic",
+DlnLnkMsgNoAnchor : "Si us plau, escrigui l'àncora",
+DlnLnkMsgInvPopName : "El nom de la finestra emergent ha de començar amb una lletra i no pot tenir espais",
+
+// Color Dialog
+DlgColorTitle : "Selecciona el color",
+DlgColorBtnClear : "Neteja",
+DlgColorHighlight : "Realça",
+DlgColorSelected : "Selecciona",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insereix una icona",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Selecciona el caràcter especial",
+
+// Table Dialog
+DlgTableTitle : "Propietats de la taula",
+DlgTableRows : "Files",
+DlgTableColumns : "Columnes",
+DlgTableBorder : "Mida vora",
+DlgTableAlign : "Alineació",
+DlgTableAlignNotSet : "<No Definit>",
+DlgTableAlignLeft : "Esquerra",
+DlgTableAlignCenter : "Centre",
+DlgTableAlignRight : "Dreta",
+DlgTableWidth : "Amplada",
+DlgTableWidthPx : "píxels",
+DlgTableWidthPc : "percentatge",
+DlgTableHeight : "Alçada",
+DlgTableCellSpace : "Espaiat de cel·les",
+DlgTableCellPad : "Encoixinament de cel·les",
+DlgTableCaption : "Títol",
+DlgTableSummary : "Resum",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Propietats de la cel·la",
+DlgCellWidth : "Amplada",
+DlgCellWidthPx : "píxels",
+DlgCellWidthPc : "percentatge",
+DlgCellHeight : "Alçada",
+DlgCellWordWrap : "Ajust de paraula",
+DlgCellWordWrapNotSet : "<No Definit>",
+DlgCellWordWrapYes : "Si",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Alineació horitzontal",
+DlgCellHorAlignNotSet : "<No Definit>",
+DlgCellHorAlignLeft : "Esquerra",
+DlgCellHorAlignCenter : "Centre",
+DlgCellHorAlignRight: "Dreta",
+DlgCellVerAlign : "Alineació vertical",
+DlgCellVerAlignNotSet : "<No definit>",
+DlgCellVerAlignTop : "Top",
+DlgCellVerAlignMiddle : "Middle",
+DlgCellVerAlignBottom : "Bottom",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Rows Span",
+DlgCellCollSpan : "Columns Span",
+DlgCellBackColor : "Color de fons",
+DlgCellBorderColor : "Color de la vora",
+DlgCellBtnSelect : "Seleccioneu...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Cerca i reemplaça",
+
+// Find Dialog
+DlgFindTitle : "Cerca",
+DlgFindFindBtn : "Cerca",
+DlgFindNotFoundMsg : "El text especificat no s'ha trobat.",
+
+// Replace Dialog
+DlgReplaceTitle : "Reemplaça",
+DlgReplaceFindLbl : "Cerca:",
+DlgReplaceReplaceLbl : "Remplaça amb:",
+DlgReplaceCaseChk : "Distingeix majúscules/minúscules",
+DlgReplaceReplaceBtn : "Reemplaça",
+DlgReplaceReplAllBtn : "Reemplaça-ho tot",
+DlgReplaceWordChk : "Només paraules completes",
+
+// Paste Operations / Dialog
+PasteErrorCut : "La seguretat del vostre navegador no permet executar automàticament les operacions de retallar. Si us plau, utilitzeu el teclat (Ctrl+X).",
+PasteErrorCopy : "La seguretat del vostre navegador no permet executar automàticament les operacions de copiar. Si us plau, utilitzeu el teclat (Ctrl+C).",
+
+PasteAsText : "Enganxa com a text no formatat",
+PasteFromWord : "Enganxa com a Word",
+
+DlgPasteMsg2 : "Si us plau, enganxeu dins del següent camp utilitzant el teclat (<STRONG>Ctrl+V</STRONG>) i premeu <STRONG>OK</STRONG>.",
+DlgPasteSec : "A causa de la configuració de seguretat del vostre navegador, l'editor no pot accedir al porta-retalls directament. Enganxeu-ho un altre cop en aquesta finestra.",
+DlgPasteIgnoreFont : "Ignora definicions de font",
+DlgPasteRemoveStyles : "Elimina definicions d'estil",
+
+// Color Picker
+ColorAutomatic : "Automàtic",
+ColorMoreColors : "Més colors...",
+
+// Document Properties
+DocProps : "Propietats del document",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propietats de l'àncora",
+DlgAnchorName : "Nom de l'àncora",
+DlgAnchorErrorName : "Si us plau, escriviu el nom de l'ancora",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "No és al diccionari",
+DlgSpellChangeTo : "Reemplaça amb",
+DlgSpellBtnIgnore : "Ignora",
+DlgSpellBtnIgnoreAll : "Ignora-les totes",
+DlgSpellBtnReplace : "Canvia",
+DlgSpellBtnReplaceAll : "Canvia-les totes",
+DlgSpellBtnUndo : "Desfés",
+DlgSpellNoSuggestions : "Cap suggeriment",
+DlgSpellProgress : "Verificació ortogràfica en curs...",
+DlgSpellNoMispell : "Verificació ortogràfica acabada: no hi ha cap paraula mal escrita",
+DlgSpellNoChanges : "Verificació ortogràfica: no s'ha canviat cap paraula",
+DlgSpellOneChange : "Verificació ortogràfica: s'ha canviat una paraula",
+DlgSpellManyChanges : "Verificació ortogràfica: s'han canviat %1 paraules",
+
+IeSpellDownload : "Verificació ortogràfica no instal·lada. Voleu descarregar-ho ara?",
+
+// Button Dialog
+DlgButtonText : "Text (Valor)",
+DlgButtonType : "Tipus",
+DlgButtonTypeBtn : "Botó",
+DlgButtonTypeSbm : "Transmet formulari",
+DlgButtonTypeRst : "Reinicia formulari",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nom",
+DlgCheckboxValue : "Valor",
+DlgCheckboxSelected : "Seleccionat",
+
+// Form Dialog
+DlgFormName : "Nom",
+DlgFormAction : "Acció",
+DlgFormMethod : "Mètode",
+
+// Select Field Dialog
+DlgSelectName : "Nom",
+DlgSelectValue : "Valor",
+DlgSelectSize : "Mida",
+DlgSelectLines : "Línies",
+DlgSelectChkMulti : "Permet múltiples seleccions",
+DlgSelectOpAvail : "Opcions disponibles",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Valor",
+DlgSelectBtnAdd : "Afegeix",
+DlgSelectBtnModify : "Modifica",
+DlgSelectBtnUp : "Amunt",
+DlgSelectBtnDown : "Avall",
+DlgSelectBtnSetValue : "Selecciona per defecte",
+DlgSelectBtnDelete : "Elimina",
+
+// Textarea Dialog
+DlgTextareaName : "Nom",
+DlgTextareaCols : "Columnes",
+DlgTextareaRows : "Files",
+
+// Text Field Dialog
+DlgTextName : "Nom",
+DlgTextValue : "Valor",
+DlgTextCharWidth : "Amplada",
+DlgTextMaxChars : "Nombre màxim de caràcters",
+DlgTextType : "Tipus",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Contrasenya",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nom",
+DlgHiddenValue : "Valor",
+
+// Bulleted List Dialog
+BulletedListProp : "Propietats de la llista de pics",
+NumberedListProp : "Propietats de llista numerada",
+DlgLstStart : "Inici",
+DlgLstType : "Tipus",
+DlgLstTypeCircle : "Cercle",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Quadrat",
+DlgLstTypeNumbers : "Números (1, 2, 3)",
+DlgLstTypeLCase : "Lletres minúscules (a, b, c)",
+DlgLstTypeUCase : "Lletres majúscules (A, B, C)",
+DlgLstTypeSRoman : "Números romans en minúscules (i, ii, iii)",
+DlgLstTypeLRoman : "Números romans en majúscules (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Fons",
+DlgDocColorsTab : "Colors i marges",
+DlgDocMetaTab : "Metadades",
+
+DlgDocPageTitle : "Títol de la pàgina",
+DlgDocLangDir : "Direcció idioma",
+DlgDocLangDirLTR : "Esquerra a dreta (LTR)",
+DlgDocLangDirRTL : "Dreta a esquerra (RTL)",
+DlgDocLangCode : "Codi d'idioma",
+DlgDocCharSet : "Codificació de conjunt de caràcters",
+DlgDocCharSetCE : "Centreeuropeu",
+DlgDocCharSetCT : "Xinès tradicional (Big5)",
+DlgDocCharSetCR : "Ciríl·lic",
+DlgDocCharSetGR : "Grec",
+DlgDocCharSetJP : "Japonès",
+DlgDocCharSetKR : "Coreà",
+DlgDocCharSetTR : "Turc",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Europeu occidental",
+DlgDocCharSetOther : "Una altra codificació de caràcters",
+
+DlgDocDocType : "Capçalera de tipus de document",
+DlgDocDocTypeOther : "Un altra capçalera de tipus de document",
+DlgDocIncXHTML : "Incloure declaracions XHTML",
+DlgDocBgColor : "Color de fons",
+DlgDocBgImage : "URL de la imatge de fons",
+DlgDocBgNoScroll : "Fons fixe",
+DlgDocCText : "Text",
+DlgDocCLink : "Enllaç",
+DlgDocCVisited : "Enllaç visitat",
+DlgDocCActive : "Enllaç actiu",
+DlgDocMargins : "Marges de pàgina",
+DlgDocMaTop : "Cap",
+DlgDocMaLeft : "Esquerra",
+DlgDocMaRight : "Dreta",
+DlgDocMaBottom : "Peu",
+DlgDocMeIndex : "Mots clau per a indexació (separats per coma)",
+DlgDocMeDescr : "Descripció del document",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Vista prèvia",
+
+// Templates Dialog
+Templates : "Plantilles",
+DlgTemplatesTitle : "Contingut plantilles",
+DlgTemplatesSelMsg : "Si us plau, seleccioneu la plantilla per obrir a l'editor<br>(el contingut actual no serà enregistrat):",
+DlgTemplatesLoading : "Carregant la llista de plantilles. Si us plau, espereu...",
+DlgTemplatesNoTpl : "(No hi ha plantilles definides)",
+DlgTemplatesReplace : "Reemplaça el contingut actual",
+
+// About Dialog
+DlgAboutAboutTab : "Quant a",
+DlgAboutBrowserInfoTab : "Informació del navegador",
+DlgAboutLicenseTab : "Llicència",
+DlgAboutVersion : "versió",
+DlgAboutInfo : "Per a més informació aneu a",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Avançat",
+DlgDivStyle : "Estil",
+DlgDivInlineStyle : "Estil en línia",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/cs.js b/httemplate/elements/fckeditor/editor/lang/cs.js
new file mode 100644
index 0000000..10bef9d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/cs.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Czech language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skrýt panel nástrojů",
+ToolbarExpand : "Zobrazit panel nástrojů",
+
+// Toolbar Items and Context Menu
+Save : "Uložit",
+NewPage : "Nová stránka",
+Preview : "Náhled",
+Cut : "Vyjmout",
+Copy : "Kopírovat",
+Paste : "Vložit",
+PasteText : "Vložit jako Äistý text",
+PasteWord : "Vložit z Wordu",
+Print : "Tisk",
+SelectAll : "Vybrat vše",
+RemoveFormat : "Odstranit formátování",
+InsertLinkLbl : "Odkaz",
+InsertLink : "Vložit/změnit odkaz",
+RemoveLink : "Odstranit odkaz",
+VisitLink : "Otevřít odkaz",
+Anchor : "Vložít/změnit záložku",
+AnchorDelete : "Odstranit kotvu",
+InsertImageLbl : "Obrázek",
+InsertImage : "Vložit/změnit obrázek",
+InsertFlashLbl : "Flash",
+InsertFlash : "Vložit/Upravit Flash",
+InsertTableLbl : "Tabulka",
+InsertTable : "Vložit/změnit tabulku",
+InsertLineLbl : "Linka",
+InsertLine : "Vložit vodorovnou linku",
+InsertSpecialCharLbl: "Speciální znaky",
+InsertSpecialChar : "Vložit speciální znaky",
+InsertSmileyLbl : "Smajlíky",
+InsertSmiley : "Vložit smajlík",
+About : "O aplikaci FCKeditor",
+Bold : "TuÄné",
+Italic : "Kurzíva",
+Underline : "Podtržené",
+StrikeThrough : "Přeškrtnuté",
+Subscript : "Dolní index",
+Superscript : "Horní index",
+LeftJustify : "Zarovnat vlevo",
+CenterJustify : "Zarovnat na střed",
+RightJustify : "Zarovnat vpravo",
+BlockJustify : "Zarovnat do bloku",
+DecreaseIndent : "Zmenšit odsazení",
+IncreaseIndent : "Zvětšit odsazení",
+Blockquote : "Citace",
+CreateDiv : "Vytvořit Div kontejner",
+EditDiv : "Upravit Div kontejner",
+DeleteDiv : "Odstranit Div kontejner",
+Undo : "Zpět",
+Redo : "Znovu",
+NumberedListLbl : "Číslování",
+NumberedList : "Vložit/odstranit Äíslovaný seznam",
+BulletedListLbl : "Odrážky",
+BulletedList : "Vložit/odstranit odrážky",
+ShowTableBorders : "Zobrazit okraje tabulek",
+ShowDetails : "Zobrazit podrobnosti",
+Style : "Styl",
+FontFormat : "Formát",
+Font : "Písmo",
+FontSize : "Velikost",
+TextColor : "Barva textu",
+BGColor : "Barva pozadí",
+Source : "Zdroj",
+Find : "Hledat",
+Replace : "Nahradit",
+SpellCheck : "Zkontrolovat pravopis",
+UniversalKeyboard : "Univerzální klávesnice",
+PageBreakLbl : "Konec stránky",
+PageBreak : "Vložit konec stránky",
+
+Form : "Formulář",
+Checkbox : "ZaÅ¡krtávací políÄko",
+RadioButton : "PÅ™epínaÄ",
+TextField : "Textové pole",
+Textarea : "Textová oblast",
+HiddenField : "Skryté pole",
+Button : "TlaÄítko",
+SelectionField : "Seznam",
+ImageButton : "Obrázkové tlaÄítko",
+
+FitWindow : "Maximalizovat velikost editoru",
+ShowBlocks : "Ukázat bloky",
+
+// Context Menu
+EditLink : "Změnit odkaz",
+CellCM : "Buňka",
+RowCM : "Řádek",
+ColumnCM : "Sloupec",
+InsertRowAfter : "Vložit řádek za",
+InsertRowBefore : "Vložit řádek před",
+DeleteRows : "Smazat řádky",
+InsertColumnAfter : "Vložit sloupec za",
+InsertColumnBefore : "Vložit sloupec před",
+DeleteColumns : "Smazat sloupec",
+InsertCellAfter : "Vložit buňku za",
+InsertCellBefore : "Vložit buňku před",
+DeleteCells : "Smazat buňky",
+MergeCells : "SlouÄit buňky",
+MergeRight : "SlouÄit doprava",
+MergeDown : "SlouÄit dolů",
+HorizontalSplitCell : "Rozdělit buňky vodorovně",
+VerticalSplitCell : "Rozdělit buňky svisle",
+TableDelete : "Smazat tabulku",
+CellProperties : "Vlastnosti buňky",
+TableProperties : "Vlastnosti tabulky",
+ImageProperties : "Vlastnosti obrázku",
+FlashProperties : "Vlastnosti Flashe",
+
+AnchorProp : "Vlastnosti záložky",
+ButtonProp : "Vlastnosti tlaÄítka",
+CheckboxProp : "Vlastnosti zaÅ¡krtávacího políÄka",
+HiddenFieldProp : "Vlastnosti skrytého pole",
+RadioButtonProp : "Vlastnosti pÅ™epínaÄe",
+ImageButtonProp : "Vlastností obrázkového tlaÄítka",
+TextFieldProp : "Vlastnosti textového pole",
+SelectionFieldProp : "Vlastnosti seznamu",
+TextareaProp : "Vlastnosti textové oblasti",
+FormProp : "Vlastnosti formuláře",
+
+FontFormats : "Normální;Naformátováno;Adresa;Nadpis 1;Nadpis 2;Nadpis 3;Nadpis 4;Nadpis 5;Nadpis 6;Normální (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Probíhá zpracování XHTML. Prosím Äekejte...",
+Done : "Hotovo",
+PasteWordConfirm : "Jak je vidÄ›t, vkládaný text je kopírován z Wordu. Chcete jej pÅ™ed vložením vyÄistit?",
+NotCompatiblePaste : "Tento příkaz je dostupný pouze v Internet Exploreru verze 5.5 nebo vyšší. Chcete vložit text bez vyÄiÅ¡tÄ›ní?",
+UnknownToolbarItem : "Neznámá položka panelu nástrojů \"%1\"",
+UnknownCommand : "Neznámý příkaz \"%1\"",
+NotImplemented : "Příkaz není implementován",
+UnknownToolbarSet : "Panel nástrojů \"%1\" neexistuje",
+NoActiveX : "Nastavení bezpeÄnosti VaÅ¡eho prohlížeÄe omezuje funkÄnost nÄ›kterých jeho možností. Je tÅ™eba zapnout volbu \"SpouÅ¡tÄ›t ovládáací prvky ActiveX a moduly plug-in\", jinak nebude možné využívat vÅ¡echny dosputné schopnosti editoru.",
+BrowseServerBlocked : "Průzkumník zdrojů nelze otevřít. Prověřte, zda nemáte aktivováno blokování popup oken.",
+DialogBlocked : "Nelze otevřít dialogové okno. Prověřte, zda nemáte aktivováno blokování popup oken.",
+VisitLinkBlocked : "Není možné otevřít nové okno. Prověřte, zda všechny nástroje pro blokování vyskakovacích oken jsou vypnuty.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Storno",
+DlgBtnClose : "Zavřít",
+DlgBtnBrowseServer : "Vybrat na serveru",
+DlgAdvancedTag : "Rozšířené",
+DlgOpOther : "<Ostatní>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Prosím vložte URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nenastaveno>",
+DlgGenId : "Id",
+DlgGenLangDir : "Orientace jazyka",
+DlgGenLangDirLtr : "Zleva do prava (LTR)",
+DlgGenLangDirRtl : "Zprava do leva (RTL)",
+DlgGenLangCode : "Kód jazyka",
+DlgGenAccessKey : "Přístupový klíÄ",
+DlgGenName : "Jméno",
+DlgGenTabIndex : "Pořadí prvku",
+DlgGenLongDescr : "Dlouhý popis URL",
+DlgGenClass : "Třída stylu",
+DlgGenTitle : "Pomocný titulek",
+DlgGenContType : "Pomocný typ obsahu",
+DlgGenLinkCharset : "Přiřazená znaková sada",
+DlgGenStyle : "Styl",
+
+// Image Dialog
+DlgImgTitle : "Vlastnosti obrázku",
+DlgImgInfoTab : "Informace o obrázku",
+DlgImgBtnUpload : "Odeslat na server",
+DlgImgURL : "URL",
+DlgImgUpload : "Odeslat",
+DlgImgAlt : "Alternativní text",
+DlgImgWidth : "Šířka",
+DlgImgHeight : "Výška",
+DlgImgLockRatio : "Zámek",
+DlgBtnResetSize : "Původní velikost",
+DlgImgBorder : "Okraje",
+DlgImgHSpace : "H-mezera",
+DlgImgVSpace : "V-mezera",
+DlgImgAlign : "Zarovnání",
+DlgImgAlignLeft : "Vlevo",
+DlgImgAlignAbsBottom: "Zcela dolů",
+DlgImgAlignAbsMiddle: "Doprostřed",
+DlgImgAlignBaseline : "Na úÄaří",
+DlgImgAlignBottom : "Dolů",
+DlgImgAlignMiddle : "Na střed",
+DlgImgAlignRight : "Vpravo",
+DlgImgAlignTextTop : "Na horní okraj textu",
+DlgImgAlignTop : "Nahoru",
+DlgImgPreview : "Náhled",
+DlgImgAlertUrl : "Zadejte prosím URL obrázku",
+DlgImgLinkTab : "Odkaz",
+
+// Flash Dialog
+DlgFlashTitle : "Vlastnosti Flashe",
+DlgFlashChkPlay : "Automatické spuštění",
+DlgFlashChkLoop : "Opakování",
+DlgFlashChkMenu : "Nabídka Flash",
+DlgFlashScale : "Zobrazit",
+DlgFlashScaleAll : "Zobrazit vše",
+DlgFlashScaleNoBorder : "Bez okraje",
+DlgFlashScaleFit : "Přizpůsobit",
+
+// Link Dialog
+DlgLnkWindowTitle : "Odkaz",
+DlgLnkInfoTab : "Informace o odkazu",
+DlgLnkTargetTab : "Cíl",
+
+DlgLnkType : "Typ odkazu",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Kotva v této stránce",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<jiný>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Vybrat kotvu",
+DlgLnkAnchorByName : "Podle jména kotvy",
+DlgLnkAnchorById : "Podle Id objektu",
+DlgLnkNoAnchors : "(Ve stránce není definována žádná kotva!)",
+DlgLnkEMail : "E-Mailová adresa",
+DlgLnkEMailSubject : "Předmět zprávy",
+DlgLnkEMailBody : "Tělo zprávy",
+DlgLnkUpload : "Odeslat",
+DlgLnkBtnUpload : "Odeslat na Server",
+
+DlgLnkTarget : "Cíl",
+DlgLnkTargetFrame : "<rámec>",
+DlgLnkTargetPopup : "<vyskakovací okno>",
+DlgLnkTargetBlank : "Nové okno (_blank)",
+DlgLnkTargetParent : "RodiÄovské okno (_parent)",
+DlgLnkTargetSelf : "Stejné okno (_self)",
+DlgLnkTargetTop : "Hlavní okno (_top)",
+DlgLnkTargetFrameName : "Název cílového rámu",
+DlgLnkPopWinName : "Název vyskakovacího okna",
+DlgLnkPopWinFeat : "Vlastnosti vyskakovacího okna",
+DlgLnkPopResize : "Měnitelná velikost",
+DlgLnkPopLocation : "Panel umístění",
+DlgLnkPopMenu : "Panel nabídky",
+DlgLnkPopScroll : "Posuvníky",
+DlgLnkPopStatus : "Stavový řádek",
+DlgLnkPopToolbar : "Panel nástrojů",
+DlgLnkPopFullScrn : "Celá obrazovka (IE)",
+DlgLnkPopDependent : "Závislost (Netscape)",
+DlgLnkPopWidth : "Šířka",
+DlgLnkPopHeight : "Výška",
+DlgLnkPopLeft : "Levý okraj",
+DlgLnkPopTop : "Horní okraj",
+
+DlnLnkMsgNoUrl : "Zadejte prosím URL odkazu",
+DlnLnkMsgNoEMail : "Zadejte prosím e-mailovou adresu",
+DlnLnkMsgNoAnchor : "Vyberte prosím kotvu",
+DlnLnkMsgInvPopName : "Název vyskakovacího okna musí zaÄínat písmenem a nesmí obsahovat mezery",
+
+// Color Dialog
+DlgColorTitle : "Výběr barvy",
+DlgColorBtnClear : "Vymazat",
+DlgColorHighlight : "Zvýrazněná",
+DlgColorSelected : "Vybraná",
+
+// Smiley Dialog
+DlgSmileyTitle : "Vkládání smajlíků",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Výběr speciálního znaku",
+
+// Table Dialog
+DlgTableTitle : "Vlastnosti tabulky",
+DlgTableRows : "Řádky",
+DlgTableColumns : "Sloupce",
+DlgTableBorder : "OhraniÄení",
+DlgTableAlign : "Zarovnání",
+DlgTableAlignNotSet : "<nenastaveno>",
+DlgTableAlignLeft : "Vlevo",
+DlgTableAlignCenter : "Na střed",
+DlgTableAlignRight : "Vpravo",
+DlgTableWidth : "Šířka",
+DlgTableWidthPx : "bodů",
+DlgTableWidthPc : "procent",
+DlgTableHeight : "Výška",
+DlgTableCellSpace : "Vzdálenost buněk",
+DlgTableCellPad : "Odsazení obsahu",
+DlgTableCaption : "Popis",
+DlgTableSummary : "Souhrn",
+DlgTableHeaders : "Záhlaví",
+DlgTableHeadersNone : "Žádné",
+DlgTableHeadersColumn : "První sloupec",
+DlgTableHeadersRow : "První řádek",
+DlgTableHeadersBoth : "Oboje",
+
+// Table Cell Dialog
+DlgCellTitle : "Vlastnosti buňky",
+DlgCellWidth : "Šířka",
+DlgCellWidthPx : "bodů",
+DlgCellWidthPc : "procent",
+DlgCellHeight : "Výška",
+DlgCellWordWrap : "Zalamování",
+DlgCellWordWrapNotSet : "<nenanstaveno>",
+DlgCellWordWrapYes : "Ano",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Vodorovné zarovnání",
+DlgCellHorAlignNotSet : "<nenastaveno>",
+DlgCellHorAlignLeft : "Vlevo",
+DlgCellHorAlignCenter : "Na střed",
+DlgCellHorAlignRight: "Vpravo",
+DlgCellVerAlign : "Svislé zarovnání",
+DlgCellVerAlignNotSet : "<nenastaveno>",
+DlgCellVerAlignTop : "Nahoru",
+DlgCellVerAlignMiddle : "Doprostřed",
+DlgCellVerAlignBottom : "Dolů",
+DlgCellVerAlignBaseline : "Na úÄaří",
+DlgCellType : "Typ buňky",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Zálaví",
+DlgCellRowSpan : "SlouÄené řádky",
+DlgCellCollSpan : "SlouÄené sloupce",
+DlgCellBackColor : "Barva pozadí",
+DlgCellBorderColor : "Barva ohraniÄení",
+DlgCellBtnSelect : "Výběr...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Najít a nahradit",
+
+// Find Dialog
+DlgFindTitle : "Hledat",
+DlgFindFindBtn : "Hledat",
+DlgFindNotFoundMsg : "Hledaný text nebyl nalezen.",
+
+// Replace Dialog
+DlgReplaceTitle : "Nahradit",
+DlgReplaceFindLbl : "Co hledat:",
+DlgReplaceReplaceLbl : "Čím nahradit:",
+DlgReplaceCaseChk : "Rozlišovat velikost písma",
+DlgReplaceReplaceBtn : "Nahradit",
+DlgReplaceReplAllBtn : "Nahradit vše",
+DlgReplaceWordChk : "Pouze celá slova",
+
+// Paste Operations / Dialog
+PasteErrorCut : "BezpeÄnostní nastavení VaÅ¡eho prohlížeÄe nedovolují editoru spustit funkci pro vyjmutí zvoleného textu do schránky. Prosím vyjmÄ›te zvolený text do schránky pomocí klávesnice (Ctrl+X).",
+PasteErrorCopy : "BezpeÄnostní nastavení VaÅ¡eho prohlížeÄe nedovolují editoru spustit funkci pro kopírování zvoleného textu do schránky. Prosím zkopírujte zvolený text do schránky pomocí klávesnice (Ctrl+C).",
+
+PasteAsText : "Vložit jako Äistý text",
+PasteFromWord : "Vložit text z Wordu",
+
+DlgPasteMsg2 : "Do následujícího pole vložte požadovaný obsah pomocí klávesnice (<STRONG>Ctrl+V</STRONG>) a stiskněte <STRONG>OK</STRONG>.",
+DlgPasteSec : "Z důvodů nastavení bezpeÄnosti VaÅ¡eho prohlížeÄe nemůže editor pÅ™istupovat přímo do schránky. Obsah schránky prosím vložte znovu do tohoto okna.",
+DlgPasteIgnoreFont : "Ignorovat písmo",
+DlgPasteRemoveStyles : "Odstranit styly",
+
+// Color Picker
+ColorAutomatic : "Automaticky",
+ColorMoreColors : "Více barev...",
+
+// Document Properties
+DocProps : "Vlastnosti dokumentu",
+
+// Anchor Dialog
+DlgAnchorTitle : "Vlastnosti záložky",
+DlgAnchorName : "Název záložky",
+DlgAnchorErrorName : "Zadejte prosím název záložky",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Není ve slovníku",
+DlgSpellChangeTo : "Změnit na",
+DlgSpellBtnIgnore : "PÅ™eskoÄit",
+DlgSpellBtnIgnoreAll : "Přeskakovat vše",
+DlgSpellBtnReplace : "Zaměnit",
+DlgSpellBtnReplaceAll : "Zaměňovat vše",
+DlgSpellBtnUndo : "Zpět",
+DlgSpellNoSuggestions : "- žádné návrhy -",
+DlgSpellProgress : "Probíhá kontrola pravopisu...",
+DlgSpellNoMispell : "Kontrola pravopisu dokonÄena: Žádné pravopisné chyby nenalezeny",
+DlgSpellNoChanges : "Kontrola pravopisu dokonÄena: Beze zmÄ›n",
+DlgSpellOneChange : "Kontrola pravopisu dokonÄena: Jedno slovo zmÄ›nÄ›no",
+DlgSpellManyChanges : "Kontrola pravopisu dokonÄena: %1 slov zmÄ›nÄ›no",
+
+IeSpellDownload : "Kontrola pravopisu není nainstalována. Chcete ji nyní stáhnout?",
+
+// Button Dialog
+DlgButtonText : "Popisek",
+DlgButtonType : "Typ",
+DlgButtonTypeBtn : "TlaÄítko",
+DlgButtonTypeSbm : "Odeslat",
+DlgButtonTypeRst : "Obnovit",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Název",
+DlgCheckboxValue : "Hodnota",
+DlgCheckboxSelected : "Zaškrtnuto",
+
+// Form Dialog
+DlgFormName : "Název",
+DlgFormAction : "Akce",
+DlgFormMethod : "Metoda",
+
+// Select Field Dialog
+DlgSelectName : "Název",
+DlgSelectValue : "Hodnota",
+DlgSelectSize : "Velikost",
+DlgSelectLines : "Řádků",
+DlgSelectChkMulti : "Povolit mnohonásobné výběry",
+DlgSelectOpAvail : "Dostupná nastavení",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Hodnota",
+DlgSelectBtnAdd : "Přidat",
+DlgSelectBtnModify : "Změnit",
+DlgSelectBtnUp : "Nahoru",
+DlgSelectBtnDown : "Dolů",
+DlgSelectBtnSetValue : "Nastavit jako vybranou hodnotu",
+DlgSelectBtnDelete : "Smazat",
+
+// Textarea Dialog
+DlgTextareaName : "Název",
+DlgTextareaCols : "Sloupců",
+DlgTextareaRows : "Řádků",
+
+// Text Field Dialog
+DlgTextName : "Název",
+DlgTextValue : "Hodnota",
+DlgTextCharWidth : "Šířka ve znacích",
+DlgTextMaxChars : "Maximální poÄet znaků",
+DlgTextType : "Typ",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Heslo",
+
+// Hidden Field Dialog
+DlgHiddenName : "Název",
+DlgHiddenValue : "Hodnota",
+
+// Bulleted List Dialog
+BulletedListProp : "Vlastnosti odrážek",
+NumberedListProp : "Vlastnosti Äíslovaného seznamu",
+DlgLstStart : "ZaÄátek",
+DlgLstType : "Typ",
+DlgLstTypeCircle : "Kružnice",
+DlgLstTypeDisc : "Kruh",
+DlgLstTypeSquare : "ÄŒtverec",
+DlgLstTypeNumbers : "Čísla (1, 2, 3)",
+DlgLstTypeLCase : "Malá písmena (a, b, c)",
+DlgLstTypeUCase : "Velká písmena (A, B, C)",
+DlgLstTypeSRoman : "Malé římská Äíslice (i, ii, iii)",
+DlgLstTypeLRoman : "Velké římské Äíslice (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Obecné",
+DlgDocBackTab : "Pozadí",
+DlgDocColorsTab : "Barvy a okraje",
+DlgDocMetaTab : "Metadata",
+
+DlgDocPageTitle : "Titulek stránky",
+DlgDocLangDir : "Směr jazyku",
+DlgDocLangDirLTR : "Zleva do prava ",
+DlgDocLangDirRTL : "Zprava doleva",
+DlgDocLangCode : "Kód jazyku",
+DlgDocCharSet : "Znaková sada",
+DlgDocCharSetCE : "Středoevropské jazyky",
+DlgDocCharSetCT : "TradiÄní ÄínÅ¡tina (Big5)",
+DlgDocCharSetCR : "Cyrilice",
+DlgDocCharSetGR : "ŘeÄtina",
+DlgDocCharSetJP : "Japonština",
+DlgDocCharSetKR : "Korejština",
+DlgDocCharSetTR : "TureÄtina",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Západoevropské jazyky",
+DlgDocCharSetOther : "Další znaková sada",
+
+DlgDocDocType : "Typ dokumentu",
+DlgDocDocTypeOther : "Jiný typ dokumetu",
+DlgDocIncXHTML : "Zahrnou deklarace XHTML",
+DlgDocBgColor : "Barva pozadí",
+DlgDocBgImage : "URL obrázku na pozadí",
+DlgDocBgNoScroll : "Nerolovatelné pozadí",
+DlgDocCText : "Text",
+DlgDocCLink : "Odkaz",
+DlgDocCVisited : "Navštívený odkaz",
+DlgDocCActive : "Vybraný odkaz",
+DlgDocMargins : "Okraje stránky",
+DlgDocMaTop : "Horní",
+DlgDocMaLeft : "Levý",
+DlgDocMaRight : "Pravý",
+DlgDocMaBottom : "Dolní",
+DlgDocMeIndex : "KlíÄová slova (oddÄ›lená Äárkou)",
+DlgDocMeDescr : "Popis dokumentu",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Autorská práva",
+DlgDocPreview : "Náhled",
+
+// Templates Dialog
+Templates : "Å ablony",
+DlgTemplatesTitle : "Å ablony obsahu",
+DlgTemplatesSelMsg : "Prosím zvolte šablonu pro otevření v editoru<br>(aktuální obsah editoru bude ztracen):",
+DlgTemplatesLoading : "Nahrávám pÅ™eheld Å¡ablon. Prosím Äekejte...",
+DlgTemplatesNoTpl : "(Není definována žádná šablona)",
+DlgTemplatesReplace : "Nahradit aktuální obsah",
+
+// About Dialog
+DlgAboutAboutTab : "O aplikaci",
+DlgAboutBrowserInfoTab : "Informace o prohlížeÄi",
+DlgAboutLicenseTab : "Licence",
+DlgAboutVersion : "verze",
+DlgAboutInfo : "Více informací získáte na",
+
+// Div Dialog
+DlgDivGeneralTab : "Obecné",
+DlgDivAdvancedTab : "Rozšířené",
+DlgDivStyle : "Styl",
+DlgDivInlineStyle : "Vložený styl",
+
+ScaytTitle : "SCAYT",
+ScaytTitleOptions : "Nastavení",
+ScaytTitleLangs : "Jazyky",
+ScaytTitleAbout : "O aplikaci"
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/da.js b/httemplate/elements/fckeditor/editor/lang/da.js
new file mode 100644
index 0000000..9e935b8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/da.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Danish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skjul værktøjslinier",
+ToolbarExpand : "Vis værktøjslinier",
+
+// Toolbar Items and Context Menu
+Save : "Gem",
+NewPage : "Ny side",
+Preview : "Vis eksempel",
+Cut : "Klip",
+Copy : "Kopier",
+Paste : "Indsæt",
+PasteText : "Indsæt som ikke-formateret tekst",
+PasteWord : "Indsæt fra Word",
+Print : "Udskriv",
+SelectAll : "Vælg alt",
+RemoveFormat : "Fjern formatering",
+InsertLinkLbl : "Hyperlink",
+InsertLink : "Indsæt/rediger hyperlink",
+RemoveLink : "Fjern hyperlink",
+VisitLink : "Ã…bn hyperlink",
+Anchor : "Indsæt/rediger bogmærke",
+AnchorDelete : "Fjern bogmærke",
+InsertImageLbl : "Indsæt billede",
+InsertImage : "Indsæt/rediger billede",
+InsertFlashLbl : "Flash",
+InsertFlash : "Indsæt/rediger Flash",
+InsertTableLbl : "Table",
+InsertTable : "Indsæt/rediger tabel",
+InsertLineLbl : "Linie",
+InsertLine : "Indsæt vandret linie",
+InsertSpecialCharLbl: "Symbol",
+InsertSpecialChar : "Indsæt symbol",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Indsæt smiley",
+About : "Om FCKeditor",
+Bold : "Fed",
+Italic : "Kursiv",
+Underline : "Understreget",
+StrikeThrough : "Overstreget",
+Subscript : "Sænket skrift",
+Superscript : "Hævet skrift",
+LeftJustify : "Venstrestillet",
+CenterJustify : "Centreret",
+RightJustify : "Højrestillet",
+BlockJustify : "Lige margener",
+DecreaseIndent : "Formindsk indrykning",
+IncreaseIndent : "Forøg indrykning",
+Blockquote : "Blokcitat",
+CreateDiv : "Opret div container",
+EditDiv : "Rediger div container",
+DeleteDiv : "Fjern div container",
+Undo : "Fortryd",
+Redo : "Annuller fortryd",
+NumberedListLbl : "Talopstilling",
+NumberedList : "Indsæt/fjern talopstilling",
+BulletedListLbl : "Punktopstilling",
+BulletedList : "Indsæt/fjern punktopstilling",
+ShowTableBorders : "Vis tabelkanter",
+ShowDetails : "Vis detaljer",
+Style : "Typografi",
+FontFormat : "Formatering",
+Font : "Skrifttype",
+FontSize : "Skriftstørrelse",
+TextColor : "Tekstfarve",
+BGColor : "Baggrundsfarve",
+Source : "Kilde",
+Find : "Søg",
+Replace : "Erstat",
+SpellCheck : "Stavekontrol",
+UniversalKeyboard : "Universaltastatur",
+PageBreakLbl : "Sidskift",
+PageBreak : "Indsæt sideskift",
+
+Form : "Indsæt formular",
+Checkbox : "Indsæt afkrydsningsfelt",
+RadioButton : "Indsæt alternativknap",
+TextField : "Indsæt tekstfelt",
+Textarea : "Indsæt tekstboks",
+HiddenField : "Indsæt skjult felt",
+Button : "Indsæt knap",
+SelectionField : "Indsæt liste",
+ImageButton : "Indsæt billedknap",
+
+FitWindow : "Maksimer editor vinduet",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Rediger hyperlink",
+CellCM : "Celle",
+RowCM : "Række",
+ColumnCM : "Kolonne",
+InsertRowAfter : "Indsæt række efter",
+InsertRowBefore : "Indsæt række før",
+DeleteRows : "Slet række",
+InsertColumnAfter : "Indsæt kolonne efter",
+InsertColumnBefore : "Indsæt kolonne før",
+DeleteColumns : "Slet kolonne",
+InsertCellAfter : "Indsæt celle efter",
+InsertCellBefore : "Indsæt celle før",
+DeleteCells : "Slet celle",
+MergeCells : "Flet celler",
+MergeRight : "Flet til højre",
+MergeDown : "Flet nedad",
+HorizontalSplitCell : "Del celle vandret",
+VerticalSplitCell : "Del celle lodret",
+TableDelete : "Slet tabel",
+CellProperties : "Egenskaber for celle",
+TableProperties : "Egenskaber for tabel",
+ImageProperties : "Egenskaber for billede",
+FlashProperties : "Egenskaber for Flash",
+
+AnchorProp : "Egenskaber for bogmærke",
+ButtonProp : "Egenskaber for knap",
+CheckboxProp : "Egenskaber for afkrydsningsfelt",
+HiddenFieldProp : "Egenskaber for skjult felt",
+RadioButtonProp : "Egenskaber for alternativknap",
+ImageButtonProp : "Egenskaber for billedknap",
+TextFieldProp : "Egenskaber for tekstfelt",
+SelectionFieldProp : "Egenskaber for liste",
+TextareaProp : "Egenskaber for tekstboks",
+FormProp : "Egenskaber for formular",
+
+FontFormats : "Normal;Formateret;Adresse;Overskrift 1;Overskrift 2;Overskrift 3;Overskrift 4;Overskrift 5;Overskrift 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Behandler XHTML...",
+Done : "Færdig",
+PasteWordConfirm : "Den tekst du forsøger at indsætte ser ud til at komme fra Word.<br>Vil du rense teksten før den indsættes?",
+NotCompatiblePaste : "Denne kommando er tilgændelig i Internet Explorer 5.5 eller senere.<br>Vil du indsætte teksten uden at rense den ?",
+UnknownToolbarItem : "Ukendt værktøjslinjeobjekt \"%1\"!",
+UnknownCommand : "Ukendt kommandonavn \"%1\"!",
+NotImplemented : "Kommandoen er ikke implementeret!",
+UnknownToolbarSet : "Værktøjslinjen \"%1\" eksisterer ikke!",
+NoActiveX : "Din browsers sikkerhedsindstillinger begrænser nogle af editorens muligheder.<br>Slå \"Kør ActiveX-objekter og plug-ins\" til, ellers vil du opleve fejl og manglende muligheder.",
+BrowseServerBlocked : "Browseren kunne ikke åbne de nødvendige ressourcer!<br>Slå pop-up blokering fra.",
+DialogBlocked : "Dialogvinduet kunne ikke åbnes!<br>Slå pop-up blokering fra.",
+VisitLinkBlocked : "Det var ikke muligt at åbne et nyt vindue. Tjek, at ingen popup-blokkere er aktive.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Annuller",
+DlgBtnClose : "Luk",
+DlgBtnBrowseServer : "Gennemse...",
+DlgAdvancedTag : "Avanceret",
+DlgOpOther : "<Andet>",
+DlgInfoTab : "Generelt",
+DlgAlertUrl : "Indtast URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<intet valgt>",
+DlgGenId : "Id",
+DlgGenLangDir : "Tekstretning",
+DlgGenLangDirLtr : "Fra venstre mod højre (LTR)",
+DlgGenLangDirRtl : "Fra højre mod venstre (RTL)",
+DlgGenLangCode : "Sprogkode",
+DlgGenAccessKey : "Genvejstast",
+DlgGenName : "Navn",
+DlgGenTabIndex : "Tabulator indeks",
+DlgGenLongDescr : "Udvidet beskrivelse",
+DlgGenClass : "Typografiark",
+DlgGenTitle : "Titel",
+DlgGenContType : "Indholdstype",
+DlgGenLinkCharset : "Tegnsæt",
+DlgGenStyle : "Typografi",
+
+// Image Dialog
+DlgImgTitle : "Egenskaber for billede",
+DlgImgInfoTab : "Generelt",
+DlgImgBtnUpload : "Upload",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternativ tekst",
+DlgImgWidth : "Bredde",
+DlgImgHeight : "Højde",
+DlgImgLockRatio : "Lås størrelsesforhold",
+DlgBtnResetSize : "Nulstil størrelse",
+DlgImgBorder : "Ramme",
+DlgImgHSpace : "HMargen",
+DlgImgVSpace : "VMargen",
+DlgImgAlign : "Justering",
+DlgImgAlignLeft : "Venstre",
+DlgImgAlignAbsBottom: "Absolut nederst",
+DlgImgAlignAbsMiddle: "Absolut centreret",
+DlgImgAlignBaseline : "Grundlinje",
+DlgImgAlignBottom : "Nederst",
+DlgImgAlignMiddle : "Centreret",
+DlgImgAlignRight : "Højre",
+DlgImgAlignTextTop : "Toppen af teksten",
+DlgImgAlignTop : "Øverst",
+DlgImgPreview : "Vis eksempel",
+DlgImgAlertUrl : "Indtast stien til billedet",
+DlgImgLinkTab : "Hyperlink",
+
+// Flash Dialog
+DlgFlashTitle : "Egenskaber for Flash",
+DlgFlashChkPlay : "Automatisk afspilning",
+DlgFlashChkLoop : "Gentagelse",
+DlgFlashChkMenu : "Vis Flash menu",
+DlgFlashScale : "Skalér",
+DlgFlashScaleAll : "Vis alt",
+DlgFlashScaleNoBorder : "Ingen ramme",
+DlgFlashScaleFit : "Tilpas størrelse",
+
+// Link Dialog
+DlgLnkWindowTitle : "Egenskaber for hyperlink",
+DlgLnkInfoTab : "Generelt",
+DlgLnkTargetTab : "MÃ¥l",
+
+DlgLnkType : "Hyperlink type",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Bogmærke på denne side",
+DlgLnkTypeEMail : "E-mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<anden>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Vælg et anker",
+DlgLnkAnchorByName : "Efter anker navn",
+DlgLnkAnchorById : "Efter element Id",
+DlgLnkNoAnchors : "(Ingen bogmærker dokumentet)",
+DlgLnkEMail : "E-mailadresse",
+DlgLnkEMailSubject : "Emne",
+DlgLnkEMailBody : "Brødtekst",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Upload",
+
+DlgLnkTarget : "MÃ¥l",
+DlgLnkTargetFrame : "<ramme>",
+DlgLnkTargetPopup : "<popup vindue>",
+DlgLnkTargetBlank : "Nyt vindue (_blank)",
+DlgLnkTargetParent : "Overordnet ramme (_parent)",
+DlgLnkTargetSelf : "Samme vindue (_self)",
+DlgLnkTargetTop : "Hele vinduet (_top)",
+DlgLnkTargetFrameName : "Destinationsvinduets navn",
+DlgLnkPopWinName : "Pop-up vinduets navn",
+DlgLnkPopWinFeat : "Egenskaber for pop-up",
+DlgLnkPopResize : "Skalering",
+DlgLnkPopLocation : "Adresselinje",
+DlgLnkPopMenu : "Menulinje",
+DlgLnkPopScroll : "Scrollbars",
+DlgLnkPopStatus : "Statuslinje",
+DlgLnkPopToolbar : "Værktøjslinje",
+DlgLnkPopFullScrn : "Fuld skærm (IE)",
+DlgLnkPopDependent : "Koblet/dependent (Netscape)",
+DlgLnkPopWidth : "Bredde",
+DlgLnkPopHeight : "Højde",
+DlgLnkPopLeft : "Position fra venstre",
+DlgLnkPopTop : "Position fra toppen",
+
+DlnLnkMsgNoUrl : "Indtast hyperlink URL!",
+DlnLnkMsgNoEMail : "Indtast e-mailaddresse!",
+DlnLnkMsgNoAnchor : "Vælg bogmærke!",
+DlnLnkMsgInvPopName : "Navnet på popup'en skal starte med et bogstav og må ikke indeholde mellemrum",
+
+// Color Dialog
+DlgColorTitle : "Vælg farve",
+DlgColorBtnClear : "Nulstil",
+DlgColorHighlight : "Markeret",
+DlgColorSelected : "Valgt",
+
+// Smiley Dialog
+DlgSmileyTitle : "Vælg smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Vælg symbol",
+
+// Table Dialog
+DlgTableTitle : "Egenskaber for tabel",
+DlgTableRows : "Rækker",
+DlgTableColumns : "Kolonner",
+DlgTableBorder : "Rammebredde",
+DlgTableAlign : "Justering",
+DlgTableAlignNotSet : "<intet valgt>",
+DlgTableAlignLeft : "Venstrestillet",
+DlgTableAlignCenter : "Centreret",
+DlgTableAlignRight : "Højrestillet",
+DlgTableWidth : "Bredde",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "procent",
+DlgTableHeight : "Højde",
+DlgTableCellSpace : "Celleafstand",
+DlgTableCellPad : "Cellemargen",
+DlgTableCaption : "Titel",
+DlgTableSummary : "Resume",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Egenskaber for celle",
+DlgCellWidth : "Bredde",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "procent",
+DlgCellHeight : "Højde",
+DlgCellWordWrap : "Orddeling",
+DlgCellWordWrapNotSet : "<intet valgt>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nej",
+DlgCellHorAlign : "Vandret justering",
+DlgCellHorAlignNotSet : "<intet valgt>",
+DlgCellHorAlignLeft : "Venstrestillet",
+DlgCellHorAlignCenter : "Centreret",
+DlgCellHorAlignRight: "Højrestillet",
+DlgCellVerAlign : "Lodret justering",
+DlgCellVerAlignNotSet : "<intet valgt>",
+DlgCellVerAlignTop : "Øverst",
+DlgCellVerAlignMiddle : "Centreret",
+DlgCellVerAlignBottom : "Nederst",
+DlgCellVerAlignBaseline : "Grundlinje",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Højde i antal rækker",
+DlgCellCollSpan : "Bredde i antal kolonner",
+DlgCellBackColor : "Baggrundsfarve",
+DlgCellBorderColor : "Rammefarve",
+DlgCellBtnSelect : "Vælg...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Søg og erstat",
+
+// Find Dialog
+DlgFindTitle : "Find",
+DlgFindFindBtn : "Find",
+DlgFindNotFoundMsg : "Søgeteksten blev ikke fundet!",
+
+// Replace Dialog
+DlgReplaceTitle : "Erstat",
+DlgReplaceFindLbl : "Søg efter:",
+DlgReplaceReplaceLbl : "Erstat med:",
+DlgReplaceCaseChk : "Forskel på store og små bogstaver",
+DlgReplaceReplaceBtn : "Erstat",
+DlgReplaceReplAllBtn : "Erstat alle",
+DlgReplaceWordChk : "Kun hele ord",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Din browsers sikkerhedsindstillinger tillader ikke editoren at klippe tekst automatisk!<br>Brug i stedet tastaturet til at klippe teksten (Ctrl+X).",
+PasteErrorCopy : "Din browsers sikkerhedsindstillinger tillader ikke editoren at kopiere tekst automatisk!<br>Brug i stedet tastaturet til at kopiere teksten (Ctrl+C).",
+
+PasteAsText : "Indsæt som ikke-formateret tekst",
+PasteFromWord : "Indsæt fra Word",
+
+DlgPasteMsg2 : "Indsæt i feltet herunder (<STRONG>Ctrl+V</STRONG>) og klik <STRONG>OK</STRONG>.",
+DlgPasteSec : "På grund af browserens sikkerhedsindstillinger kan editoren ikke tilgå udklipsholderen direkte. Du skal indsætte udklipsholderens indhold i dette vindue igen.",
+DlgPasteIgnoreFont : "Ignorer font definitioner",
+DlgPasteRemoveStyles : "Ignorer typografi",
+
+// Color Picker
+ColorAutomatic : "Automatisk",
+ColorMoreColors : "Flere farver...",
+
+// Document Properties
+DocProps : "Egenskaber for dokument",
+
+// Anchor Dialog
+DlgAnchorTitle : "Egenskaber for bogmærke",
+DlgAnchorName : "Bogmærke navn",
+DlgAnchorErrorName : "Indtast bogmærke navn!",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ikke i ordbogen",
+DlgSpellChangeTo : "Forslag",
+DlgSpellBtnIgnore : "Ignorer",
+DlgSpellBtnIgnoreAll : "Ignorer alle",
+DlgSpellBtnReplace : "Erstat",
+DlgSpellBtnReplaceAll : "Erstat alle",
+DlgSpellBtnUndo : "Tilbage",
+DlgSpellNoSuggestions : "- ingen forslag -",
+DlgSpellProgress : "Stavekontrolen arbejder...",
+DlgSpellNoMispell : "Stavekontrol færdig: Ingen fejl fundet",
+DlgSpellNoChanges : "Stavekontrol færdig: Ingen ord ændret",
+DlgSpellOneChange : "Stavekontrol færdig: Et ord ændret",
+DlgSpellManyChanges : "Stavekontrol færdig: %1 ord ændret",
+
+IeSpellDownload : "Stavekontrol ikke installeret.<br>Vil du hente den nu?",
+
+// Button Dialog
+DlgButtonText : "Tekst",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Knap",
+DlgButtonTypeSbm : "Send",
+DlgButtonTypeRst : "Nulstil",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Navn",
+DlgCheckboxValue : "Værdi",
+DlgCheckboxSelected : "Valgt",
+
+// Form Dialog
+DlgFormName : "Navn",
+DlgFormAction : "Handling",
+DlgFormMethod : "Metod",
+
+// Select Field Dialog
+DlgSelectName : "Navn",
+DlgSelectValue : "Værdi",
+DlgSelectSize : "Størrelse",
+DlgSelectLines : "linier",
+DlgSelectChkMulti : "Tillad flere valg",
+DlgSelectOpAvail : "Valgmuligheder",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Værdi",
+DlgSelectBtnAdd : "Tilføj",
+DlgSelectBtnModify : "Rediger",
+DlgSelectBtnUp : "Op",
+DlgSelectBtnDown : "Ned",
+DlgSelectBtnSetValue : "Sæt som valgt",
+DlgSelectBtnDelete : "Slet",
+
+// Textarea Dialog
+DlgTextareaName : "Navn",
+DlgTextareaCols : "Kolonner",
+DlgTextareaRows : "Rækker",
+
+// Text Field Dialog
+DlgTextName : "Navn",
+DlgTextValue : "Værdi",
+DlgTextCharWidth : "Bredde (tegn)",
+DlgTextMaxChars : "Max antal tegn",
+DlgTextType : "Type",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Adgangskode",
+
+// Hidden Field Dialog
+DlgHiddenName : "Navn",
+DlgHiddenValue : "Værdi",
+
+// Bulleted List Dialog
+BulletedListProp : "Egenskaber for punktopstilling",
+NumberedListProp : "Egenskaber for talopstilling",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Cirkel",
+DlgLstTypeDisc : "Udfyldt cirkel",
+DlgLstTypeSquare : "Firkant",
+DlgLstTypeNumbers : "Nummereret (1, 2, 3)",
+DlgLstTypeLCase : "Små bogstaver (a, b, c)",
+DlgLstTypeUCase : "Store bogstaver (A, B, C)",
+DlgLstTypeSRoman : "Små romertal (i, ii, iii)",
+DlgLstTypeLRoman : "Store romertal (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Generelt",
+DlgDocBackTab : "Baggrund",
+DlgDocColorsTab : "Farver og margen",
+DlgDocMetaTab : "Metadata",
+
+DlgDocPageTitle : "Sidetitel",
+DlgDocLangDir : "Sprog",
+DlgDocLangDirLTR : "Fra venstre mod højre (LTR)",
+DlgDocLangDirRTL : "Fra højre mod venstre (RTL)",
+DlgDocLangCode : "Landekode",
+DlgDocCharSet : "Tegnsæt kode",
+DlgDocCharSetCE : "Centraleuropæisk",
+DlgDocCharSetCT : "Traditionel kinesisk (Big5)",
+DlgDocCharSetCR : "Kyrillisk",
+DlgDocCharSetGR : "Græsk",
+DlgDocCharSetJP : "Japansk",
+DlgDocCharSetKR : "Koreansk",
+DlgDocCharSetTR : "Tyrkisk",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Vesteuropæisk",
+DlgDocCharSetOther : "Anden tegnsæt kode",
+
+DlgDocDocType : "Dokumenttype kategori",
+DlgDocDocTypeOther : "Anden dokumenttype kategori",
+DlgDocIncXHTML : "Inkludere XHTML deklartion",
+DlgDocBgColor : "Baggrundsfarve",
+DlgDocBgImage : "Baggrundsbillede URL",
+DlgDocBgNoScroll : "Fastlåst baggrund",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Hyperlink",
+DlgDocCVisited : "Besøgt hyperlink",
+DlgDocCActive : "Aktivt hyperlink",
+DlgDocMargins : "Sidemargen",
+DlgDocMaTop : "Øverst",
+DlgDocMaLeft : "Venstre",
+DlgDocMaRight : "Højre",
+DlgDocMaBottom : "Nederst",
+DlgDocMeIndex : "Dokument index nøgleord (kommasepareret)",
+DlgDocMeDescr : "Dokument beskrivelse",
+DlgDocMeAuthor : "Forfatter",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Vis",
+
+// Templates Dialog
+Templates : "Skabeloner",
+DlgTemplatesTitle : "Indholdsskabeloner",
+DlgTemplatesSelMsg : "Vælg den skabelon, som skal åbnes i editoren.<br>(Nuværende indhold vil blive overskrevet!):",
+DlgTemplatesLoading : "Henter liste over skabeloner...",
+DlgTemplatesNoTpl : "(Der er ikke defineret nogen skabelon!)",
+DlgTemplatesReplace : "Erstat det faktiske indhold",
+
+// About Dialog
+DlgAboutAboutTab : "Om",
+DlgAboutBrowserInfoTab : "Generelt",
+DlgAboutLicenseTab : "Licens",
+DlgAboutVersion : "version",
+DlgAboutInfo : "For yderlig information gå til",
+
+// Div Dialog
+DlgDivGeneralTab : "Generelt",
+DlgDivAdvancedTab : "Avanceret",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/de.js b/httemplate/elements/fckeditor/editor/lang/de.js
new file mode 100644
index 0000000..774fcd7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/de.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * German language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Symbolleiste einklappen",
+ToolbarExpand : "Symbolleiste ausklappen",
+
+// Toolbar Items and Context Menu
+Save : "Speichern",
+NewPage : "Neue Seite",
+Preview : "Vorschau",
+Cut : "Ausschneiden",
+Copy : "Kopieren",
+Paste : "Einfügen",
+PasteText : "aus Textdatei einfügen",
+PasteWord : "aus MS-Word einfügen",
+Print : "Drucken",
+SelectAll : "Alles auswählen",
+RemoveFormat : "Formatierungen entfernen",
+InsertLinkLbl : "Link",
+InsertLink : "Link einfügen/editieren",
+RemoveLink : "Link entfernen",
+VisitLink : "Link aufrufen",
+Anchor : "Anker einfügen/editieren",
+AnchorDelete : "Anker entfernen",
+InsertImageLbl : "Bild",
+InsertImage : "Bild einfügen/editieren",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash einfügen/editieren",
+InsertTableLbl : "Tabelle",
+InsertTable : "Tabelle einfügen/editieren",
+InsertLineLbl : "Linie",
+InsertLine : "Horizontale Linie einfügen",
+InsertSpecialCharLbl: "Sonderzeichen",
+InsertSpecialChar : "Sonderzeichen einfügen/editieren",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Smiley einfügen",
+About : "Ãœber FCKeditor",
+Bold : "Fett",
+Italic : "Kursiv",
+Underline : "Unterstrichen",
+StrikeThrough : "Durchgestrichen",
+Subscript : "Tiefgestellt",
+Superscript : "Hochgestellt",
+LeftJustify : "Linksbündig",
+CenterJustify : "Zentriert",
+RightJustify : "Rechtsbündig",
+BlockJustify : "Blocksatz",
+DecreaseIndent : "Einzug verringern",
+IncreaseIndent : "Einzug erhöhen",
+Blockquote : "Zitatblock",
+CreateDiv : "Erzeuge Div Block",
+EditDiv : "Bearbeite Div Block",
+DeleteDiv : "Entferne Div Block",
+Undo : "Rückgängig",
+Redo : "Wiederherstellen",
+NumberedListLbl : "Nummerierte Liste",
+NumberedList : "Nummerierte Liste einfügen/entfernen",
+BulletedListLbl : "Liste",
+BulletedList : "Liste einfügen/entfernen",
+ShowTableBorders : "Zeige Tabellenrahmen",
+ShowDetails : "Zeige Details",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Schriftart",
+FontSize : "Größe",
+TextColor : "Textfarbe",
+BGColor : "Hintergrundfarbe",
+Source : "Quellcode",
+Find : "Suchen",
+Replace : "Ersetzen",
+SpellCheck : "Rechtschreibprüfung",
+UniversalKeyboard : "Universal-Tastatur",
+PageBreakLbl : "Seitenumbruch",
+PageBreak : "Seitenumbruch einfügen",
+
+Form : "Formular",
+Checkbox : "Checkbox",
+RadioButton : "Radiobutton",
+TextField : "Textfeld einzeilig",
+Textarea : "Textfeld mehrzeilig",
+HiddenField : "verstecktes Feld",
+Button : "Klickbutton",
+SelectionField : "Auswahlfeld",
+ImageButton : "Bildbutton",
+
+FitWindow : "Editor maximieren",
+ShowBlocks : "Blöcke anzeigen",
+
+// Context Menu
+EditLink : "Link editieren",
+CellCM : "Zelle",
+RowCM : "Zeile",
+ColumnCM : "Spalte",
+InsertRowAfter : "Zeile unterhalb einfügen",
+InsertRowBefore : "Zeile oberhalb einfügen",
+DeleteRows : "Zeile entfernen",
+InsertColumnAfter : "Spalte rechts danach einfügen",
+InsertColumnBefore : "Spalte links davor einfügen",
+DeleteColumns : "Spalte löschen",
+InsertCellAfter : "Zelle danach einfügen",
+InsertCellBefore : "Zelle davor einfügen",
+DeleteCells : "Zelle löschen",
+MergeCells : "Zellen verbinden",
+MergeRight : "nach rechts verbinden",
+MergeDown : "nach unten verbinden",
+HorizontalSplitCell : "Zelle horizontal teilen",
+VerticalSplitCell : "Zelle vertikal teilen",
+TableDelete : "Tabelle löschen",
+CellProperties : "Zellen-Eigenschaften",
+TableProperties : "Tabellen-Eigenschaften",
+ImageProperties : "Bild-Eigenschaften",
+FlashProperties : "Flash-Eigenschaften",
+
+AnchorProp : "Anker-Eigenschaften",
+ButtonProp : "Button-Eigenschaften",
+CheckboxProp : "Checkbox-Eigenschaften",
+HiddenFieldProp : "Verstecktes Feld-Eigenschaften",
+RadioButtonProp : "Optionsfeld-Eigenschaften",
+ImageButtonProp : "Bildbutton-Eigenschaften",
+TextFieldProp : "Textfeld (einzeilig) Eigenschaften",
+SelectionFieldProp : "Auswahlfeld-Eigenschaften",
+TextareaProp : "Textfeld (mehrzeilig) Eigenschaften",
+FormProp : "Formular-Eigenschaften",
+
+FontFormats : "Normal;Formatiert;Addresse;Ãœberschrift 1;Ãœberschrift 2;Ãœberschrift 3;Ãœberschrift 4;Ãœberschrift 5;Ãœberschrift 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Bearbeite XHTML. Bitte warten...",
+Done : "Fertig",
+PasteWordConfirm : "Der Text, den Sie einfügen möchten, scheint aus MS-Word kopiert zu sein. Möchten Sie ihn zuvor bereinigen lassen?",
+NotCompatiblePaste : "Diese Funktion steht nur im Internet Explorer ab Version 5.5 zur Verfügung. Möchten Sie den Text unbereinigt einfügen?",
+UnknownToolbarItem : "Unbekanntes Menüleisten-Objekt \"%1\"",
+UnknownCommand : "Unbekannter Befehl \"%1\"",
+NotImplemented : "Befehl nicht implementiert",
+UnknownToolbarSet : "Menüleiste \"%1\" existiert nicht",
+NoActiveX : "Die Sicherheitseinstellungen Ihres Browsers beschränken evtl. einige Funktionen des Editors. Aktivieren Sie die Option \"ActiveX-Steuerelemente und Plugins ausführen\" in den Sicherheitseinstellungen, um diese Funktionen nutzen zu können",
+BrowseServerBlocked : "Ein Auswahlfenster konnte nicht geöffnet werden. Stellen Sie sicher, das alle Popup-Blocker ausgeschaltet sind.",
+DialogBlocked : "Das Dialog-Fenster konnte nicht geöffnet werden. Stellen Sie sicher, das alle Popup-Blocker ausgeschaltet sind.",
+VisitLinkBlocked : "Es war leider nicht möglich ein neues Fenster zu öffnen. Bitte versichern Sie sich das der Popup-Blocker ausgeschaltet ist.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Abbrechen",
+DlgBtnClose : "Schließen",
+DlgBtnBrowseServer : "Server durchsuchen",
+DlgAdvancedTag : "Erweitert",
+DlgOpOther : "<andere>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Bitte tragen Sie die URL ein",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nichts>",
+DlgGenId : "ID",
+DlgGenLangDir : "Schreibrichtung",
+DlgGenLangDirLtr : "Links nach Rechts (LTR)",
+DlgGenLangDirRtl : "Rechts nach Links (RTL)",
+DlgGenLangCode : "Sprachenkürzel",
+DlgGenAccessKey : "Zugriffstaste",
+DlgGenName : "Name",
+DlgGenTabIndex : "Tab-Index",
+DlgGenLongDescr : "Langform URL",
+DlgGenClass : "Stylesheet Klasse",
+DlgGenTitle : "Titel Beschreibung",
+DlgGenContType : "Inhaltstyp",
+DlgGenLinkCharset : "Ziel-Zeichensatz",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Bild-Eigenschaften",
+DlgImgInfoTab : "Bild-Info",
+DlgImgBtnUpload : "Zum Server senden",
+DlgImgURL : "Bildauswahl",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternativer Text",
+DlgImgWidth : "Breite",
+DlgImgHeight : "Höhe",
+DlgImgLockRatio : "Größenverhältniss beibehalten",
+DlgBtnResetSize : "Größe zurücksetzen",
+DlgImgBorder : "Rahmen",
+DlgImgHSpace : "Horizontal-Abstand",
+DlgImgVSpace : "Vertikal-Abstand",
+DlgImgAlign : "Ausrichtung",
+DlgImgAlignLeft : "Links",
+DlgImgAlignAbsBottom: "Abs Unten",
+DlgImgAlignAbsMiddle: "Abs Mitte",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Unten",
+DlgImgAlignMiddle : "Mitte",
+DlgImgAlignRight : "Rechts",
+DlgImgAlignTextTop : "Text Oben",
+DlgImgAlignTop : "Oben",
+DlgImgPreview : "Vorschau",
+DlgImgAlertUrl : "Bitte geben Sie die Bild-URL an",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash-Eigenschaften",
+DlgFlashChkPlay : "autom. Abspielen",
+DlgFlashChkLoop : "Endlosschleife",
+DlgFlashChkMenu : "Flash-Menü aktivieren",
+DlgFlashScale : "Skalierung",
+DlgFlashScaleAll : "Alles anzeigen",
+DlgFlashScaleNoBorder : "ohne Rand",
+DlgFlashScaleFit : "Passgenau",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link-Info",
+DlgLnkTargetTab : "Zielseite",
+
+DlgLnkType : "Link-Typ",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Anker in dieser Seite",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<anderes>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Anker auswählen",
+DlgLnkAnchorByName : "nach Anker Name",
+DlgLnkAnchorById : "nach Element Id",
+DlgLnkNoAnchors : "(keine Anker im Dokument vorhanden)",
+DlgLnkEMail : "E-Mail Addresse",
+DlgLnkEMailSubject : "Betreffzeile",
+DlgLnkEMailBody : "Nachrichtentext",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Zum Server senden",
+
+DlgLnkTarget : "Zielseite",
+DlgLnkTargetFrame : "<Frame>",
+DlgLnkTargetPopup : "<Pop-up Fenster>",
+DlgLnkTargetBlank : "Neues Fenster (_blank)",
+DlgLnkTargetParent : "Oberes Fenster (_parent)",
+DlgLnkTargetSelf : "Gleiches Fenster (_self)",
+DlgLnkTargetTop : "Oberstes Fenster (_top)",
+DlgLnkTargetFrameName : "Ziel-Fenster-Name",
+DlgLnkPopWinName : "Pop-up Fenster-Name",
+DlgLnkPopWinFeat : "Pop-up Fenster-Eigenschaften",
+DlgLnkPopResize : "Vergrößerbar",
+DlgLnkPopLocation : "Adress-Leiste",
+DlgLnkPopMenu : "Menü-Leiste",
+DlgLnkPopScroll : "Rollbalken",
+DlgLnkPopStatus : "Statusleiste",
+DlgLnkPopToolbar : "Werkzeugleiste",
+DlgLnkPopFullScrn : "Vollbild (IE)",
+DlgLnkPopDependent : "Abhängig (Netscape)",
+DlgLnkPopWidth : "Breite",
+DlgLnkPopHeight : "Höhe",
+DlgLnkPopLeft : "Linke Position",
+DlgLnkPopTop : "Obere Position",
+
+DlnLnkMsgNoUrl : "Bitte geben Sie die Link-URL an",
+DlnLnkMsgNoEMail : "Bitte geben Sie e-Mail Adresse an",
+DlnLnkMsgNoAnchor : "Bitte wählen Sie einen Anker aus",
+DlnLnkMsgInvPopName : "Der Name des Popups muss mit einem Buchstaben beginnen und darf keine Leerzeichen enthalten",
+
+// Color Dialog
+DlgColorTitle : "Farbauswahl",
+DlgColorBtnClear : "Keine Farbe",
+DlgColorHighlight : "Vorschau",
+DlgColorSelected : "Ausgewählt",
+
+// Smiley Dialog
+DlgSmileyTitle : "Smiley auswählen",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Sonderzeichen auswählen",
+
+// Table Dialog
+DlgTableTitle : "Tabellen-Eigenschaften",
+DlgTableRows : "Zeile",
+DlgTableColumns : "Spalte",
+DlgTableBorder : "Rahmen",
+DlgTableAlign : "Ausrichtung",
+DlgTableAlignNotSet : "<keine>",
+DlgTableAlignLeft : "Links",
+DlgTableAlignCenter : "Zentriert",
+DlgTableAlignRight : "Rechts",
+DlgTableWidth : "Breite",
+DlgTableWidthPx : "Pixel",
+DlgTableWidthPc : "%",
+DlgTableHeight : "Höhe",
+DlgTableCellSpace : "Zellenabstand außen",
+DlgTableCellPad : "Zellenabstand innen",
+DlgTableCaption : "Ãœberschrift",
+DlgTableSummary : "Inhaltsübersicht",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Zellen-Eigenschaften",
+DlgCellWidth : "Breite",
+DlgCellWidthPx : "Pixel",
+DlgCellWidthPc : "%",
+DlgCellHeight : "Höhe",
+DlgCellWordWrap : "Umbruch",
+DlgCellWordWrapNotSet : "<keiner>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nein",
+DlgCellHorAlign : "Horizontale Ausrichtung",
+DlgCellHorAlignNotSet : "<keine>",
+DlgCellHorAlignLeft : "Links",
+DlgCellHorAlignCenter : "Zentriert",
+DlgCellHorAlignRight: "Rechts",
+DlgCellVerAlign : "Vertikale Ausrichtung",
+DlgCellVerAlignNotSet : "<keine>",
+DlgCellVerAlignTop : "Oben",
+DlgCellVerAlignMiddle : "Mitte",
+DlgCellVerAlignBottom : "Unten",
+DlgCellVerAlignBaseline : "Grundlinie",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Zeilen zusammenfassen",
+DlgCellCollSpan : "Spalten zusammenfassen",
+DlgCellBackColor : "Hintergrundfarbe",
+DlgCellBorderColor : "Rahmenfarbe",
+DlgCellBtnSelect : "Auswahl...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Suchen und Ersetzen",
+
+// Find Dialog
+DlgFindTitle : "Finden",
+DlgFindFindBtn : "Finden",
+DlgFindNotFoundMsg : "Der gesuchte Text wurde nicht gefunden.",
+
+// Replace Dialog
+DlgReplaceTitle : "Ersetzen",
+DlgReplaceFindLbl : "Suche nach:",
+DlgReplaceReplaceLbl : "Ersetze mit:",
+DlgReplaceCaseChk : "Groß-Kleinschreibung beachten",
+DlgReplaceReplaceBtn : "Ersetzen",
+DlgReplaceReplAllBtn : "Alle Ersetzen",
+DlgReplaceWordChk : "Nur ganze Worte suchen",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Die Sicherheitseinstellungen Ihres Browsers lassen es nicht zu, den Text automatisch auszuschneiden. Bitte benutzen Sie die System-Zwischenablage über STRG-X (ausschneiden) und STRG-V (einfügen).",
+PasteErrorCopy : "Die Sicherheitseinstellungen Ihres Browsers lassen es nicht zu, den Text automatisch kopieren. Bitte benutzen Sie die System-Zwischenablage über STRG-C (kopieren).",
+
+PasteAsText : "Als Text einfügen",
+PasteFromWord : "Aus Word einfügen",
+
+DlgPasteMsg2 : "Bitte fügen Sie den Text in der folgenden Box über die Tastatur (mit <STRONG>Strg+V</STRONG>) ein und bestätigen Sie mit <STRONG>OK</STRONG>.",
+DlgPasteSec : "Aufgrund von Sicherheitsbeschränkungen Ihres Browsers kann der Editor nicht direkt auf die Zwischenablage zugreifen. Bitte fügen Sie den Inhalt erneut in diesem Fenster ein.",
+DlgPasteIgnoreFont : "Ignoriere Schriftart-Definitionen",
+DlgPasteRemoveStyles : "Entferne Style-Definitionen",
+
+// Color Picker
+ColorAutomatic : "Automatisch",
+ColorMoreColors : "Weitere Farben...",
+
+// Document Properties
+DocProps : "Dokument-Eigenschaften",
+
+// Anchor Dialog
+DlgAnchorTitle : "Anker-Eigenschaften",
+DlgAnchorName : "Anker Name",
+DlgAnchorErrorName : "Bitte geben Sie den Namen des Ankers ein",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nicht im Wörterbuch",
+DlgSpellChangeTo : "Ändern in",
+DlgSpellBtnIgnore : "Ignorieren",
+DlgSpellBtnIgnoreAll : "Alle Ignorieren",
+DlgSpellBtnReplace : "Ersetzen",
+DlgSpellBtnReplaceAll : "Alle Ersetzen",
+DlgSpellBtnUndo : "Rückgängig",
+DlgSpellNoSuggestions : " - keine Vorschläge - ",
+DlgSpellProgress : "Rechtschreibprüfung läuft...",
+DlgSpellNoMispell : "Rechtschreibprüfung abgeschlossen - keine Fehler gefunden",
+DlgSpellNoChanges : "Rechtschreibprüfung abgeschlossen - keine Worte geändert",
+DlgSpellOneChange : "Rechtschreibprüfung abgeschlossen - ein Wort geändert",
+DlgSpellManyChanges : "Rechtschreibprüfung abgeschlossen - %1 Wörter geändert",
+
+IeSpellDownload : "Rechtschreibprüfung nicht installiert. Möchten Sie sie jetzt herunterladen?",
+
+// Button Dialog
+DlgButtonText : "Text (Wert)",
+DlgButtonType : "Typ",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Absenden",
+DlgButtonTypeRst : "Zurücksetzen",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name",
+DlgCheckboxValue : "Wert",
+DlgCheckboxSelected : "ausgewählt",
+
+// Form Dialog
+DlgFormName : "Name",
+DlgFormAction : "Action",
+DlgFormMethod : "Method",
+
+// Select Field Dialog
+DlgSelectName : "Name",
+DlgSelectValue : "Wert",
+DlgSelectSize : "Größe",
+DlgSelectLines : "Linien",
+DlgSelectChkMulti : "Erlaube Mehrfachauswahl",
+DlgSelectOpAvail : "Mögliche Optionen",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Wert",
+DlgSelectBtnAdd : "Hinzufügen",
+DlgSelectBtnModify : "Ändern",
+DlgSelectBtnUp : "Hoch",
+DlgSelectBtnDown : "Runter",
+DlgSelectBtnSetValue : "Setze als Standardwert",
+DlgSelectBtnDelete : "Entfernen",
+
+// Textarea Dialog
+DlgTextareaName : "Name",
+DlgTextareaCols : "Spalten",
+DlgTextareaRows : "Reihen",
+
+// Text Field Dialog
+DlgTextName : "Name",
+DlgTextValue : "Wert",
+DlgTextCharWidth : "Zeichenbreite",
+DlgTextMaxChars : "Max. Zeichen",
+DlgTextType : "Typ",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Passwort",
+
+// Hidden Field Dialog
+DlgHiddenName : "Name",
+DlgHiddenValue : "Wert",
+
+// Bulleted List Dialog
+BulletedListProp : "Listen-Eigenschaften",
+NumberedListProp : "Nummerierte Listen-Eigenschaften",
+DlgLstStart : "Start",
+DlgLstType : "Typ",
+DlgLstTypeCircle : "Ring",
+DlgLstTypeDisc : "Kreis",
+DlgLstTypeSquare : "Quadrat",
+DlgLstTypeNumbers : "Nummern (1, 2, 3)",
+DlgLstTypeLCase : "Kleinbuchstaben (a, b, c)",
+DlgLstTypeUCase : "Großbuchstaben (A, B, C)",
+DlgLstTypeSRoman : "Kleine römische Zahlen (i, ii, iii)",
+DlgLstTypeLRoman : "Große römische Zahlen (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Allgemein",
+DlgDocBackTab : "Hintergrund",
+DlgDocColorsTab : "Farben und Abstände",
+DlgDocMetaTab : "Metadaten",
+
+DlgDocPageTitle : "Seitentitel",
+DlgDocLangDir : "Schriftrichtung",
+DlgDocLangDirLTR : "Links nach Rechts",
+DlgDocLangDirRTL : "Rechts nach Links",
+DlgDocLangCode : "Sprachkürzel",
+DlgDocCharSet : "Zeichenkodierung",
+DlgDocCharSetCE : "Zentraleuropäisch",
+DlgDocCharSetCT : "traditionell Chinesisch (Big5)",
+DlgDocCharSetCR : "Kyrillisch",
+DlgDocCharSetGR : "Griechisch",
+DlgDocCharSetJP : "Japanisch",
+DlgDocCharSetKR : "Koreanisch",
+DlgDocCharSetTR : "Türkisch",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Westeuropäisch",
+DlgDocCharSetOther : "Andere Zeichenkodierung",
+
+DlgDocDocType : "Dokumententyp",
+DlgDocDocTypeOther : "Anderer Dokumententyp",
+DlgDocIncXHTML : "Beziehe XHTML Deklarationen ein",
+DlgDocBgColor : "Hintergrundfarbe",
+DlgDocBgImage : "Hintergrundbild URL",
+DlgDocBgNoScroll : "feststehender Hintergrund",
+DlgDocCText : "Text",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Besuchter Link",
+DlgDocCActive : "Aktiver Link",
+DlgDocMargins : "Seitenränder",
+DlgDocMaTop : "Oben",
+DlgDocMaLeft : "Links",
+DlgDocMaRight : "Rechts",
+DlgDocMaBottom : "Unten",
+DlgDocMeIndex : "Schlüsselwörter (durch Komma getrennt)",
+DlgDocMeDescr : "Dokument-Beschreibung",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Vorschau",
+
+// Templates Dialog
+Templates : "Vorlagen",
+DlgTemplatesTitle : "Vorlagen",
+DlgTemplatesSelMsg : "Klicken Sie auf eine Vorlage, um sie im Editor zu öffnen (der aktuelle Inhalt wird dabei gelöscht!):",
+DlgTemplatesLoading : "Liste der Vorlagen wird geladen. Bitte warten...",
+DlgTemplatesNoTpl : "(keine Vorlagen definiert)",
+DlgTemplatesReplace : "Aktuellen Inhalt ersetzen",
+
+// About Dialog
+DlgAboutAboutTab : "Ãœber",
+DlgAboutBrowserInfoTab : "Browser-Info",
+DlgAboutLicenseTab : "Lizenz",
+DlgAboutVersion : "Version",
+DlgAboutInfo : "Für weitere Informationen siehe",
+
+// Div Dialog
+DlgDivGeneralTab : "Allgemein",
+DlgDivAdvancedTab : "Erweitert",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/el.js b/httemplate/elements/fckeditor/editor/lang/el.js
new file mode 100644
index 0000000..b5e4268
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/el.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Greek language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "ΑπόκÏυψη ΜπάÏας ΕÏγαλείων",
+ToolbarExpand : "Εμφάνιση ΜπάÏας ΕÏγαλείων",
+
+// Toolbar Items and Context Menu
+Save : "Αποθήκευση",
+NewPage : "Îέα Σελίδα",
+Preview : "ΠÏοεπισκόπιση",
+Cut : "Αποκοπή",
+Copy : "ΑντιγÏαφή",
+Paste : "Επικόλληση",
+PasteText : "Επικόλληση (απλό κείμενο)",
+PasteWord : "Επικόλληση από το Word",
+Print : "ΕκτÏπωση",
+SelectAll : "Επιλογή όλων",
+RemoveFormat : "ΑφαίÏεση ΜοÏφοποίησης",
+InsertLinkLbl : "ΣÏνδεσμος (Link)",
+InsertLink : "Εισαγωγή/Μεταβολή Συνδέσμου (Link)",
+RemoveLink : "ΑφαίÏεση Συνδέσμου (Link)",
+VisitLink : "Open Link", //MISSING
+Anchor : "Εισαγωγή/επεξεÏγασία Anchor",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Εικόνα",
+InsertImage : "Εισαγωγή/Μεταβολή Εικόνας",
+InsertFlashLbl : "Εισαγωγή Flash",
+InsertFlash : "Εισαγωγή/επεξεÏγασία Flash",
+InsertTableLbl : "Πίνακας",
+InsertTable : "Εισαγωγή/Μεταβολή Πίνακα",
+InsertLineLbl : "ΓÏαμμή",
+InsertLine : "Εισαγωγή ΟÏιζόντιας ΓÏαμμής",
+InsertSpecialCharLbl: "Ειδικό ΣÏμβολο",
+InsertSpecialChar : "Εισαγωγή Î•Î¹Î´Î¹ÎºÎ¿Ï Î£Ï…Î¼Î²ÏŒÎ»Î¿Ï…",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Εισαγωγή Smiley",
+About : "ΠεÏί του FCKeditor",
+Bold : "Έντονα",
+Italic : "Πλάγια",
+Underline : "ΥπογÏάμμιση",
+StrikeThrough : "ΔιαγÏάμμιση",
+Subscript : "Δείκτης",
+Superscript : "Εκθέτης",
+LeftJustify : "Στοίχιση ΑÏιστεÏά",
+CenterJustify : "Στοίχιση στο ΚέντÏο",
+RightJustify : "Στοίχιση Δεξιά",
+BlockJustify : "ΠλήÏης Στοίχιση (Block)",
+DecreaseIndent : "Μείωση Εσοχής",
+IncreaseIndent : "ΑÏξηση Εσοχής",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "ΑναίÏεση",
+Redo : "ΕπαναφοÏά",
+NumberedListLbl : "Λίστα με ΑÏιθμοÏÏ‚",
+NumberedList : "Εισαγωγή/ΔιαγÏαφή Λίστας με ΑÏιθμοÏÏ‚",
+BulletedListLbl : "Λίστα με Bullets",
+BulletedList : "Εισαγωγή/ΔιαγÏαφή Λίστας με Bullets",
+ShowTableBorders : "ΠÏοβολή ΟÏίων Πίνακα",
+ShowDetails : "ΠÏοβολή ΛεπτομεÏειών",
+Style : "Στυλ",
+FontFormat : "ΜοÏφή ΓÏαμματοσειÏάς",
+Font : "ΓÏαμματοσειÏά",
+FontSize : "Μέγεθος",
+TextColor : "ΧÏώμα ΓÏαμμάτων",
+BGColor : "ΧÏώμα ΥποβάθÏου",
+Source : "HTML κώδικας",
+Find : "Αναζήτηση",
+Replace : "Αντικατάσταση",
+SpellCheck : "ΟÏθογÏαφικός έλεγχος",
+UniversalKeyboard : "Διεθνής πληκτÏολόγιο",
+PageBreakLbl : "Τέλος σελίδας",
+PageBreak : "Εισαγωγή τέλους σελίδας",
+
+Form : "ΦόÏμα",
+Checkbox : "Κουτί επιλογής",
+RadioButton : "Κουμπί Radio",
+TextField : "Πεδίο κειμένου",
+Textarea : "ΠεÏιοχή κειμένου",
+HiddenField : "ΚÏυφό πεδίο",
+Button : "Κουμπί",
+SelectionField : "Πεδίο επιλογής",
+ImageButton : "Κουμπί εικόνας",
+
+FitWindow : "Μεγιστοποίηση Ï€ÏογÏάμματος",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Μεταβολή Συνδέσμου (Link)",
+CellCM : "Κελί",
+RowCM : "ΣειÏά",
+ColumnCM : "Στήλη",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "ΔιαγÏαφή ΓÏαμμών",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "ΔιαγÏαφή Κολωνών",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "ΔιαγÏαφή Κελιών",
+MergeCells : "Ενοποίηση Κελιών",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "ΔιαγÏαφή πίνακα",
+CellProperties : "Ιδιότητες ΚελιοÏ",
+TableProperties : "Ιδιότητες Πίνακα",
+ImageProperties : "Ιδιότητες Εικόνας",
+FlashProperties : "Ιδιότητες Flash",
+
+AnchorProp : "Ιδιότητες άγκυÏας",
+ButtonProp : "Ιδιότητες κουμπιοÏ",
+CheckboxProp : "Ιδιότητες ÎºÎ¿Ï…Î¼Ï€Î¹Î¿Ï ÎµÏ€Î¹Î»Î¿Î³Î®Ï‚",
+HiddenFieldProp : "Ιδιότητες κÏÏ…Ï†Î¿Ï Ï€ÎµÎ´Î¯Î¿Ï…",
+RadioButtonProp : "Ιδιότητες ÎºÎ¿Ï…Î¼Ï€Î¹Î¿Ï radio",
+ImageButtonProp : "Ιδιότητες ÎºÎ¿Ï…Î¼Ï€Î¹Î¿Ï ÎµÎ¹ÎºÏŒÎ½Î±Ï‚",
+TextFieldProp : "Ιδιότητες πεδίου κειμένου",
+SelectionFieldProp : "Ιδιότητες πεδίου επιλογής",
+TextareaProp : "Ιδιότητες πεÏιοχής κειμένου",
+FormProp : "Ιδιότητες φόÏμας",
+
+FontFormats : "Κανονικό;ΜοÏφοποιημένο;ΔιεÏθυνση;Επικεφαλίδα 1;Επικεφαλίδα 2;Επικεφαλίδα 3;Επικεφαλίδα 4;Επικεφαλίδα 5;Επικεφαλίδα 6",
+
+// Alerts and Messages
+ProcessingXHTML : "ΕπεξεÏγασία XHTML. ΠαÏακαλώ πεÏιμένετε...",
+Done : "Έτοιμο",
+PasteWordConfirm : "Το κείμενο που θέλετε να επικολήσετε, φαίνεται πως Ï€ÏοέÏχεται από το Word. Θέλετε να καθαÏιστεί Ï€Ïιν επικοληθεί;",
+NotCompatiblePaste : "Αυτή η επιλογή είναι διαθέσιμη στον Internet Explorer έκδοση 5.5+. Θέλετε να γίνει η επικόλληση χωÏίς καθαÏισμό;",
+UnknownToolbarItem : "Άγνωστο αντικείμενο της μπάÏας εÏγαλείων \"%1\"",
+UnknownCommand : "Άγνωστή εντολή \"%1\"",
+NotImplemented : "Η εντολή δεν έχει ενεÏγοποιηθεί",
+UnknownToolbarSet : "Η μπάÏα εÏγαλείων \"%1\" δεν υπάÏχει",
+NoActiveX : "Οι Ïυθμίσεις ασφαλείας του browser σας μποÏεί να πεÏιοÏίσουν κάποιες Ïυθμίσεις του Ï€ÏογÏάμματος. ΧÏειάζεται να ενεÏγοποιήσετε την επιλογή \"Run ActiveX controls and plug-ins\". Ίσως παÏουσιαστοÏν λάθη και παÏατηÏήσετε ελειπείς λειτουÏγίες.",
+BrowseServerBlocked : "Οι πόÏοι του browser σας δεν είναι Ï€Ïοσπελάσιμοι. ΣιγουÏευτείτε ότι δεν υπάÏχουν ενεÏγοί popup blockers.",
+DialogBlocked : "Δεν ήταν δυνατό να ανοίξει το παÏάθυÏο διαλόγου. ΣιγουÏευτείτε ότι δεν υπάÏχουν ενεÏγοί popup blockers.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "ΑκÏÏωση",
+DlgBtnClose : "Κλείσιμο",
+DlgBtnBrowseServer : "ΕξεÏεÏνηση διακομιστή",
+DlgAdvancedTag : "Για Ï€ÏοχωÏημένους",
+DlgOpOther : "<Άλλα>",
+DlgInfoTab : "ΠληÏοφοÏίες",
+DlgAlertUrl : "ΠαÏακαλώ εισάγετε URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<χωÏίς>",
+DlgGenId : "Id",
+DlgGenLangDir : "ΚατεÏθυνση κειμένου",
+DlgGenLangDirLtr : "ΑÏιστεÏά Ï€Ïος Δεξιά (LTR)",
+DlgGenLangDirRtl : "Δεξιά Ï€Ïος ΑÏιστεÏά (RTL)",
+DlgGenLangCode : "Κωδικός Γλώσσας",
+DlgGenAccessKey : "Συντόμευση (Access Key)",
+DlgGenName : "Όνομα",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Αναλυτική πεÏιγÏαφή URL",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Συμβουλευτικός τίτλος",
+DlgGenContType : "Συμβουλευτικός τίτλος πεÏιεχομένου",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "ΣτÏλ",
+
+// Image Dialog
+DlgImgTitle : "Ιδιότητες Εικόνας",
+DlgImgInfoTab : "ΠληÏοφοÏίες Εικόνας",
+DlgImgBtnUpload : "Αποστολή στον Διακομιστή",
+DlgImgURL : "URL",
+DlgImgUpload : "Αποστολή",
+DlgImgAlt : "Εναλλακτικό Κείμενο (ALT)",
+DlgImgWidth : "Πλάτος",
+DlgImgHeight : "Ύψος",
+DlgImgLockRatio : "Κλείδωμα Αναλογίας",
+DlgBtnResetSize : "ΕπαναφοÏά ΑÏÏ‡Î¹ÎºÎ¿Ï ÎœÎµÎ³Î­Î¸Î¿Ï…Ï‚",
+DlgImgBorder : "ΠεÏιθώÏιο",
+DlgImgHSpace : "ΟÏιζόντιος ΧώÏος (HSpace)",
+DlgImgVSpace : "Κάθετος ΧώÏος (VSpace)",
+DlgImgAlign : "ΕυθυγÏάμμιση (Align)",
+DlgImgAlignLeft : "ΑÏιστεÏά",
+DlgImgAlignAbsBottom: "Απόλυτα Κάτω (Abs Bottom)",
+DlgImgAlignAbsMiddle: "Απόλυτα στη Μέση (Abs Middle)",
+DlgImgAlignBaseline : "ΓÏαμμή Βάσης (Baseline)",
+DlgImgAlignBottom : "Κάτω (Bottom)",
+DlgImgAlignMiddle : "Μέση (Middle)",
+DlgImgAlignRight : "Δεξιά (Right)",
+DlgImgAlignTextTop : "ΚοÏυφή Κειμένου (Text Top)",
+DlgImgAlignTop : "Πάνω (Top)",
+DlgImgPreview : "ΠÏοεπισκόπιση",
+DlgImgAlertUrl : "Εισάγετε την τοποθεσία (URL) της εικόνας",
+DlgImgLinkTab : "ΣÏνδεσμος",
+
+// Flash Dialog
+DlgFlashTitle : "Ιδιότητες flash",
+DlgFlashChkPlay : "Αυτόματη έναÏξη",
+DlgFlashChkLoop : "Επανάληψη",
+DlgFlashChkMenu : "ΕνεÏγοποίηση Flash Menu",
+DlgFlashScale : "Κλίμακα",
+DlgFlashScaleAll : "Εμφάνιση όλων",
+DlgFlashScaleNoBorder : "ΧωÏίς ÏŒÏια",
+DlgFlashScaleFit : "ΑκÏιβής εφαÏμογή",
+
+// Link Dialog
+DlgLnkWindowTitle : "ΣÏνδεσμος (Link)",
+DlgLnkInfoTab : "Link",
+DlgLnkTargetTab : "ΠαÏάθυÏο Στόχος (Target)",
+
+DlgLnkType : "ΤÏπος συνδέσμου (Link)",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "ΆγκυÏα σε αυτή τη σελίδα",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "ΠÏοτόκολο",
+DlgLnkProtoOther : "<άλλο>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Επιλέξτε μια άγκυÏα",
+DlgLnkAnchorByName : "Βάσει του Ονόματος (Name) της άγκυÏας",
+DlgLnkAnchorById : "Βάσει του Element Id",
+DlgLnkNoAnchors : "(Δεν υπάÏχουν άγκυÏες στο κείμενο)",
+DlgLnkEMail : "ΔιεÏθυνση ΗλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Î¤Î±Ï‡Ï…Î´Ïομείου",
+DlgLnkEMailSubject : "Θέμα ΜηνÏματος",
+DlgLnkEMailBody : "Κείμενο ΜηνÏματος",
+DlgLnkUpload : "Αποστολή",
+DlgLnkBtnUpload : "Αποστολή στον Διακομιστή",
+
+DlgLnkTarget : "ΠαÏάθυÏο Στόχος (Target)",
+DlgLnkTargetFrame : "<πλαίσιο>",
+DlgLnkTargetPopup : "<παÏάθυÏο popup>",
+DlgLnkTargetBlank : "Îέο ΠαÏάθυÏο (_blank)",
+DlgLnkTargetParent : "Γονικό ΠαÏάθυÏο (_parent)",
+DlgLnkTargetSelf : "Ίδιο ΠαÏάθυÏο (_self)",
+DlgLnkTargetTop : "Ανώτατο ΠαÏάθυÏο (_top)",
+DlgLnkTargetFrameName : "Όνομα πλαισίου στόχου",
+DlgLnkPopWinName : "Όνομα Popup Window",
+DlgLnkPopWinFeat : "Επιλογές Popup Window",
+DlgLnkPopResize : "Με αλλαγή Μεγέθους",
+DlgLnkPopLocation : "ΜπάÏα Τοποθεσίας",
+DlgLnkPopMenu : "ΜπάÏα Menu",
+DlgLnkPopScroll : "ΜπάÏες ΚÏλισης",
+DlgLnkPopStatus : "ΜπάÏα Status",
+DlgLnkPopToolbar : "ΜπάÏα ΕÏγαλείων",
+DlgLnkPopFullScrn : "ΟλόκληÏη η Οθόνη (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Πλάτος",
+DlgLnkPopHeight : "Ύψος",
+DlgLnkPopLeft : "Τοποθεσία ΑÏιστεÏής ΆκÏης",
+DlgLnkPopTop : "Τοποθεσία Πάνω ΆκÏης",
+
+DlnLnkMsgNoUrl : "Εισάγετε την τοποθεσία (URL) του υπεÏσυνδέσμου (Link)",
+DlnLnkMsgNoEMail : "Εισάγετε την διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου",
+DlnLnkMsgNoAnchor : "Επιλέξτε ένα Anchor",
+DlnLnkMsgInvPopName : "Το όνομα του popup Ï€Ïέπει να αÏχίζει με χαÏακτήÏα της αλφαβήτου και να μην πεÏιέχει κενά",
+
+// Color Dialog
+DlgColorTitle : "Επιλογή χÏώματος",
+DlgColorBtnClear : "ΚαθαÏισμός",
+DlgColorHighlight : "ΠÏοεπισκόπιση",
+DlgColorSelected : "Επιλεγμένο",
+
+// Smiley Dialog
+DlgSmileyTitle : "Επιλέξτε ένα Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Επιλέξτε ένα Ειδικό ΣÏμβολο",
+
+// Table Dialog
+DlgTableTitle : "Ιδιότητες Πίνακα",
+DlgTableRows : "ΓÏαμμές",
+DlgTableColumns : "Κολώνες",
+DlgTableBorder : "Μέγεθος ΠεÏιθωÏίου",
+DlgTableAlign : "Στοίχιση",
+DlgTableAlignNotSet : "<χωÏίς>",
+DlgTableAlignLeft : "ΑÏιστεÏά",
+DlgTableAlignCenter : "ΚέντÏο",
+DlgTableAlignRight : "Δεξιά",
+DlgTableWidth : "Πλάτος",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "\%",
+DlgTableHeight : "Ύψος",
+DlgTableCellSpace : "Απόσταση κελιών",
+DlgTableCellPad : "Γέμισμα κελιών",
+DlgTableCaption : "ΥπέÏτιτλος",
+DlgTableSummary : "ΠεÏίληψη",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Ιδιότητες ΚελιοÏ",
+DlgCellWidth : "Πλάτος",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "\%",
+DlgCellHeight : "Ύψος",
+DlgCellWordWrap : "Με αλλαγή γÏαμμής",
+DlgCellWordWrapNotSet : "<χωÏίς>",
+DlgCellWordWrapYes : "Îαι",
+DlgCellWordWrapNo : "Όχι",
+DlgCellHorAlign : "ΟÏιζόντια Στοίχιση",
+DlgCellHorAlignNotSet : "<χωÏίς>",
+DlgCellHorAlignLeft : "ΑÏιστεÏά",
+DlgCellHorAlignCenter : "ΚέντÏο",
+DlgCellHorAlignRight: "Δεξιά",
+DlgCellVerAlign : "Κάθετη Στοίχιση",
+DlgCellVerAlignNotSet : "<χωÏίς>",
+DlgCellVerAlignTop : "Πάνω (Top)",
+DlgCellVerAlignMiddle : "Μέση (Middle)",
+DlgCellVerAlignBottom : "Κάτω (Bottom)",
+DlgCellVerAlignBaseline : "ΓÏαμμή Βάσης (Baseline)",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "ΑÏιθμός ΓÏαμμών (Rows Span)",
+DlgCellCollSpan : "ΑÏιθμός Κολωνών (Columns Span)",
+DlgCellBackColor : "ΧÏώμα ΥποβάθÏου",
+DlgCellBorderColor : "ΧÏώμα ΠεÏιθωÏίου",
+DlgCellBtnSelect : "Επιλογή...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Αναζήτηση",
+DlgFindFindBtn : "Αναζήτηση",
+DlgFindNotFoundMsg : "Το κείμενο δεν βÏέθηκε.",
+
+// Replace Dialog
+DlgReplaceTitle : "Αντικατάσταση",
+DlgReplaceFindLbl : "Αναζήτηση:",
+DlgReplaceReplaceLbl : "Αντικατάσταση με:",
+DlgReplaceCaseChk : "Έλεγχος πεζών/κεφαλαίων",
+DlgReplaceReplaceBtn : "Αντικατάσταση",
+DlgReplaceReplAllBtn : "Αντικατάσταση Όλων",
+DlgReplaceWordChk : "ΕÏÏεση πλήÏους λέξης",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Οι Ïυθμίσεις ασφαλείας του φυλλομετÏητή σας δεν επιτÏέπουν την επιλεγμένη εÏγασία αποκοπής. ΧÏησιμοποιείστε το πληκτÏολόγιο (Ctrl+X).",
+PasteErrorCopy : "Οι Ïυθμίσεις ασφαλείας του φυλλομετÏητή σας δεν επιτÏέπουν την επιλεγμένη εÏγασία αντιγÏαφής. ΧÏησιμοποιείστε το πληκτÏολόγιο (Ctrl+C).",
+
+PasteAsText : "Επικόλληση ως Απλό Κείμενο",
+PasteFromWord : "Επικόλληση από το Word",
+
+DlgPasteMsg2 : "ΠαÏακαλώ επικολήστε στο ακόλουθο κουτί χÏησιμοποιόντας το πληκτÏολόγιο (<STRONG>Ctrl+V</STRONG>) και πατήστε <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Αγνόηση Ï€ÏοδιαγÏαφών γÏαμματοσειÏάς",
+DlgPasteRemoveStyles : "ΑφαίÏεση Ï€ÏοδιαγÏαφών στÏλ",
+
+// Color Picker
+ColorAutomatic : "Αυτόματο",
+ColorMoreColors : "ΠεÏισσότεÏα χÏώματα...",
+
+// Document Properties
+DocProps : "Ιδιότητες εγγÏάφου",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ιδιότητες άγκυÏας",
+DlgAnchorName : "Όνομα άγκυÏας",
+DlgAnchorErrorName : "ΠαÏακαλοÏμε εισάγετε όνομα άγκυÏας",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Δεν υπάÏχει στο λεξικό",
+DlgSpellChangeTo : "Αλλαγή σε",
+DlgSpellBtnIgnore : "Αγνόηση",
+DlgSpellBtnIgnoreAll : "Αγνόηση όλων",
+DlgSpellBtnReplace : "Αντικατάσταση",
+DlgSpellBtnReplaceAll : "Αντικατάσταση όλων",
+DlgSpellBtnUndo : "ΑναίÏεση",
+DlgSpellNoSuggestions : "- Δεν υπάÏχουν Ï€Ïοτάσεις -",
+DlgSpellProgress : "ΟÏθογÏαφικός έλεγχος σε εξέλιξη...",
+DlgSpellNoMispell : "Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Δεν βÏέθηκαν λάθη",
+DlgSpellNoChanges : "Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Δεν άλλαξαν λέξεις",
+DlgSpellOneChange : "Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Μια λέξη άλλαξε",
+DlgSpellManyChanges : "Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: %1 λέξεις άλλαξαν",
+
+IeSpellDownload : "Δεν υπάÏχει εγκατεστημένος οÏθογÏάφος. Θέλετε να τον κατεβάσετε Ï„ÏŽÏα;",
+
+// Button Dialog
+DlgButtonText : "Κείμενο (Τιμή)",
+DlgButtonType : "ΤÏπος",
+DlgButtonTypeBtn : "Κουμπί",
+DlgButtonTypeSbm : "ΚαταχώÏηση",
+DlgButtonTypeRst : "ΕπαναφοÏά",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Όνομα",
+DlgCheckboxValue : "Τιμή",
+DlgCheckboxSelected : "Επιλεγμένο",
+
+// Form Dialog
+DlgFormName : "Όνομα",
+DlgFormAction : "ΔÏάση",
+DlgFormMethod : "Μάθοδος",
+
+// Select Field Dialog
+DlgSelectName : "Όνομα",
+DlgSelectValue : "Τιμή",
+DlgSelectSize : "Μέγεθος",
+DlgSelectLines : "γÏαμμές",
+DlgSelectChkMulti : "Πολλαπλές επιλογές",
+DlgSelectOpAvail : "Διαθέσιμες επιλογές",
+DlgSelectOpText : "Κείμενο",
+DlgSelectOpValue : "Τιμή",
+DlgSelectBtnAdd : "ΠÏοσθήκη",
+DlgSelectBtnModify : "Αλλαγή",
+DlgSelectBtnUp : "Πάνω",
+DlgSelectBtnDown : "Κάτω",
+DlgSelectBtnSetValue : "ΠÏοεπιλεγμένη επιλογή",
+DlgSelectBtnDelete : "ΔιαγÏαφή",
+
+// Textarea Dialog
+DlgTextareaName : "Όνομα",
+DlgTextareaCols : "Στήλες",
+DlgTextareaRows : "ΣειÏές",
+
+// Text Field Dialog
+DlgTextName : "Όνομα",
+DlgTextValue : "Τιμή",
+DlgTextCharWidth : "Μήκος χαÏακτήÏων",
+DlgTextMaxChars : "Μέγιστοι χαÏακτήÏες",
+DlgTextType : "ΤÏπος",
+DlgTextTypeText : "Κείμενο",
+DlgTextTypePass : "Κωδικός",
+
+// Hidden Field Dialog
+DlgHiddenName : "Όνομα",
+DlgHiddenValue : "Τιμή",
+
+// Bulleted List Dialog
+BulletedListProp : "Ιδιότητες λίστας Bulleted",
+NumberedListProp : "Ιδιότητες αÏιθμημένης λίστας ",
+DlgLstStart : "ΑÏχή",
+DlgLstType : "ΤÏπος",
+DlgLstTypeCircle : "ΚÏκλος",
+DlgLstTypeDisc : "Δίσκος",
+DlgLstTypeSquare : "ΤετÏάγωνο",
+DlgLstTypeNumbers : "ΑÏιθμοί (1, 2, 3)",
+DlgLstTypeLCase : "Πεζά γÏάμματα (a, b, c)",
+DlgLstTypeUCase : "Κεφαλαία γÏάμματα (A, B, C)",
+DlgLstTypeSRoman : "ΜικÏά λατινικά αÏιθμητικά (i, ii, iii)",
+DlgLstTypeLRoman : "Μεγάλα λατινικά αÏιθμητικά (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Γενικά",
+DlgDocBackTab : "Φόντο",
+DlgDocColorsTab : "ΧÏώματα και πεÏιθώÏια",
+DlgDocMetaTab : "Δεδομένα Meta",
+
+DlgDocPageTitle : "Τίτλος σελίδας",
+DlgDocLangDir : "ΚατεÏθυνση γÏαφής",
+DlgDocLangDirLTR : "αÏιστεÏά Ï€Ïος δεξιά (LTR)",
+DlgDocLangDirRTL : "δεξιά Ï€Ïος αÏιστεÏά (RTL)",
+DlgDocLangCode : "Κωδικός γλώσσας",
+DlgDocCharSet : "Κωδικοποίηση χαÏακτήÏων",
+DlgDocCharSetCE : "ΚεντÏικής ΕυÏώπης",
+DlgDocCharSetCT : "ΠαÏαδοσιακά κινέζικα (Big5)",
+DlgDocCharSetCR : "ΚυÏιλλική",
+DlgDocCharSetGR : "Ελληνική",
+DlgDocCharSetJP : "Ιαπωνική",
+DlgDocCharSetKR : "ΚοÏεάτικη",
+DlgDocCharSetTR : "ΤουÏκική",
+DlgDocCharSetUN : "Διεθνής (UTF-8)",
+DlgDocCharSetWE : "Δυτικής ΕυÏώπης",
+DlgDocCharSetOther : "Άλλη κωδικοποίηση χαÏακτήÏων",
+
+DlgDocDocType : "Επικεφαλίδα Ï„Ïπου εγγÏάφου",
+DlgDocDocTypeOther : "Άλλη επικεφαλίδα Ï„Ïπου εγγÏάφου",
+DlgDocIncXHTML : "Îα συμπεÏιληφθοÏν οι δηλώσεις XHTML",
+DlgDocBgColor : "ΧÏώμα φόντου",
+DlgDocBgImage : "ΔιεÏθυνση εικόνας φόντου",
+DlgDocBgNoScroll : "Φόντο χωÏίς κÏλιση",
+DlgDocCText : "Κείμενο",
+DlgDocCLink : "ΣÏνδεσμος",
+DlgDocCVisited : "ΣÏνδεσμος που έχει επισκευθεί",
+DlgDocCActive : "ΕνεÏγός σÏνδεσμος",
+DlgDocMargins : "ΠεÏιθώÏια σελίδας",
+DlgDocMaTop : "ΚοÏυφή",
+DlgDocMaLeft : "ΑÏιστεÏά",
+DlgDocMaRight : "Δεξιά",
+DlgDocMaBottom : "Κάτω",
+DlgDocMeIndex : "Λέξεις κλειδιά δείκτες εγγÏάφου (διαχωÏισμός με κόμμα)",
+DlgDocMeDescr : "ΠεÏιγÏαφή εγγÏάφου",
+DlgDocMeAuthor : "ΣυγγÏαφέας",
+DlgDocMeCopy : "Πνευματικά δικαιώματα",
+DlgDocPreview : "ΠÏοεπισκόπηση",
+
+// Templates Dialog
+Templates : "ΠÏότυπα",
+DlgTemplatesTitle : "ΠÏότυπα πεÏιεχομένου",
+DlgTemplatesSelMsg : "ΠαÏακαλώ επιλέξτε Ï€Ïότυπο για εισαγωγή στο Ï€ÏόγÏαμμα<br>(τα υπάÏχοντα πεÏιεχόμενα θα χαθοÏν):",
+DlgTemplatesLoading : "ΦόÏτωση καταλόγου Ï€ÏοτÏπων. ΠαÏακαλώ πεÏιμένετε...",
+DlgTemplatesNoTpl : "(Δεν έχουν καθοÏιστεί Ï€Ïότυπα)",
+DlgTemplatesReplace : "Αντικατάσταση υπάÏχοντων πεÏιεχομένων",
+
+// About Dialog
+DlgAboutAboutTab : "Σχετικά",
+DlgAboutBrowserInfoTab : "ΠληÏοφοÏίες Browser",
+DlgAboutLicenseTab : "Άδεια",
+DlgAboutVersion : "έκδοση",
+DlgAboutInfo : "Για πεÏισσότεÏες πληÏοφοÏίες",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/en-au.js b/httemplate/elements/fckeditor/editor/lang/en-au.js
new file mode 100644
index 0000000..83d6624
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/en-au.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * English (Australia) language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Collapse Toolbar",
+ToolbarExpand : "Expand Toolbar",
+
+// Toolbar Items and Context Menu
+Save : "Save",
+NewPage : "New Page",
+Preview : "Preview",
+Cut : "Cut",
+Copy : "Copy",
+Paste : "Paste",
+PasteText : "Paste as plain text",
+PasteWord : "Paste from Word",
+Print : "Print",
+SelectAll : "Select All",
+RemoveFormat : "Remove Format",
+InsertLinkLbl : "Link",
+InsertLink : "Insert/Edit Link",
+RemoveLink : "Remove Link",
+VisitLink : "Open Link",
+Anchor : "Insert/Edit Anchor",
+AnchorDelete : "Remove Anchor",
+InsertImageLbl : "Image",
+InsertImage : "Insert/Edit Image",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insert/Edit Flash",
+InsertTableLbl : "Table",
+InsertTable : "Insert/Edit Table",
+InsertLineLbl : "Line",
+InsertLine : "Insert Horizontal Line",
+InsertSpecialCharLbl: "Special Character",
+InsertSpecialChar : "Insert Special Character",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Insert Smiley",
+About : "About FCKeditor",
+Bold : "Bold",
+Italic : "Italic",
+Underline : "Underline",
+StrikeThrough : "Strike Through",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Left Justify",
+CenterJustify : "Centre Justify",
+RightJustify : "Right Justify",
+BlockJustify : "Block Justify",
+DecreaseIndent : "Decrease Indent",
+IncreaseIndent : "Increase Indent",
+Blockquote : "Blockquote",
+CreateDiv : "Create Div Container",
+EditDiv : "Edit Div Container",
+DeleteDiv : "Remove Div Container",
+Undo : "Undo",
+Redo : "Redo",
+NumberedListLbl : "Numbered List",
+NumberedList : "Insert/Remove Numbered List",
+BulletedListLbl : "Bulleted List",
+BulletedList : "Insert/Remove Bulleted List",
+ShowTableBorders : "Show Table Borders",
+ShowDetails : "Show Details",
+Style : "Style",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Size",
+TextColor : "Text Colour",
+BGColor : "Background Colour",
+Source : "Source",
+Find : "Find",
+Replace : "Replace",
+SpellCheck : "Check Spelling",
+UniversalKeyboard : "Universal Keyboard",
+PageBreakLbl : "Page Break",
+PageBreak : "Insert Page Break",
+
+Form : "Form",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Hidden Field",
+Button : "Button",
+SelectionField : "Selection Field",
+ImageButton : "Image Button",
+
+FitWindow : "Maximize the editor size",
+ShowBlocks : "Show Blocks",
+
+// Context Menu
+EditLink : "Edit Link",
+CellCM : "Cell",
+RowCM : "Row",
+ColumnCM : "Column",
+InsertRowAfter : "Insert Row After",
+InsertRowBefore : "Insert Row Before",
+DeleteRows : "Delete Rows",
+InsertColumnAfter : "Insert Column After",
+InsertColumnBefore : "Insert Column Before",
+DeleteColumns : "Delete Columns",
+InsertCellAfter : "Insert Cell After",
+InsertCellBefore : "Insert Cell Before",
+DeleteCells : "Delete Cells",
+MergeCells : "Merge Cells",
+MergeRight : "Merge Right",
+MergeDown : "Merge Down",
+HorizontalSplitCell : "Split Cell Horizontally",
+VerticalSplitCell : "Split Cell Vertically",
+TableDelete : "Delete Table",
+CellProperties : "Cell Properties",
+TableProperties : "Table Properties",
+ImageProperties : "Image Properties",
+FlashProperties : "Flash Properties",
+
+AnchorProp : "Anchor Properties",
+ButtonProp : "Button Properties",
+CheckboxProp : "Checkbox Properties",
+HiddenFieldProp : "Hidden Field Properties",
+RadioButtonProp : "Radio Button Properties",
+ImageButtonProp : "Image Button Properties",
+TextFieldProp : "Text Field Properties",
+SelectionFieldProp : "Selection Field Properties",
+TextareaProp : "Textarea Properties",
+FormProp : "Form Properties",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Processing XHTML. Please wait...",
+Done : "Done",
+PasteWordConfirm : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",
+NotCompatiblePaste : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",
+UnknownToolbarItem : "Unknown toolbar item \"%1\"",
+UnknownCommand : "Unknown command name \"%1\"",
+NotImplemented : "Command not implemented",
+UnknownToolbarSet : "Toolbar set \"%1\" doesn't exist",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.",
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancel",
+DlgBtnClose : "Close",
+DlgBtnBrowseServer : "Browse Server",
+DlgAdvancedTag : "Advanced",
+DlgOpOther : "<Other>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Please insert the URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<not set>",
+DlgGenId : "Id",
+DlgGenLangDir : "Language Direction",
+DlgGenLangDirLtr : "Left to Right (LTR)",
+DlgGenLangDirRtl : "Right to Left (RTL)",
+DlgGenLangCode : "Language Code",
+DlgGenAccessKey : "Access Key",
+DlgGenName : "Name",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Long Description URL",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Image Properties",
+DlgImgInfoTab : "Image Info",
+DlgImgBtnUpload : "Send it to the Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternative Text",
+DlgImgWidth : "Width",
+DlgImgHeight : "Height",
+DlgImgLockRatio : "Lock Ratio",
+DlgBtnResetSize : "Reset Size",
+DlgImgBorder : "Border",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Align",
+DlgImgAlignLeft : "Left",
+DlgImgAlignAbsBottom: "Abs Bottom",
+DlgImgAlignAbsMiddle: "Abs Middle",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Bottom",
+DlgImgAlignMiddle : "Middle",
+DlgImgAlignRight : "Right",
+DlgImgAlignTextTop : "Text Top",
+DlgImgAlignTop : "Top",
+DlgImgPreview : "Preview",
+DlgImgAlertUrl : "Please type the image URL",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties",
+DlgFlashChkPlay : "Auto Play",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Enable Flash Menu",
+DlgFlashScale : "Scale",
+DlgFlashScaleAll : "Show all",
+DlgFlashScaleNoBorder : "No Border",
+DlgFlashScaleFit : "Exact Fit",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Target",
+
+DlgLnkType : "Link Type",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Link to anchor in the text",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<other>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Select an Anchor",
+DlgLnkAnchorByName : "By Anchor Name",
+DlgLnkAnchorById : "By Element Id",
+DlgLnkNoAnchors : "(No anchors available in the document)",
+DlgLnkEMail : "E-Mail Address",
+DlgLnkEMailSubject : "Message Subject",
+DlgLnkEMailBody : "Message Body",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Send it to the Server",
+
+DlgLnkTarget : "Target",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<popup window>",
+DlgLnkTargetBlank : "New Window (_blank)",
+DlgLnkTargetParent : "Parent Window (_parent)",
+DlgLnkTargetSelf : "Same Window (_self)",
+DlgLnkTargetTop : "Topmost Window (_top)",
+DlgLnkTargetFrameName : "Target Frame Name",
+DlgLnkPopWinName : "Popup Window Name",
+DlgLnkPopWinFeat : "Popup Window Features",
+DlgLnkPopResize : "Resizable",
+DlgLnkPopLocation : "Location Bar",
+DlgLnkPopMenu : "Menu Bar",
+DlgLnkPopScroll : "Scroll Bars",
+DlgLnkPopStatus : "Status Bar",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Full Screen (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Width",
+DlgLnkPopHeight : "Height",
+DlgLnkPopLeft : "Left Position",
+DlgLnkPopTop : "Top Position",
+
+DlnLnkMsgNoUrl : "Please type the link URL",
+DlnLnkMsgNoEMail : "Please type the e-mail address",
+DlnLnkMsgNoAnchor : "Please select an anchor",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces",
+
+// Color Dialog
+DlgColorTitle : "Select Colour",
+DlgColorBtnClear : "Clear",
+DlgColorHighlight : "Highlight",
+DlgColorSelected : "Selected",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insert a Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Select Special Character",
+
+// Table Dialog
+DlgTableTitle : "Table Properties",
+DlgTableRows : "Rows",
+DlgTableColumns : "Columns",
+DlgTableBorder : "Border size",
+DlgTableAlign : "Alignment",
+DlgTableAlignNotSet : "<Not set>",
+DlgTableAlignLeft : "Left",
+DlgTableAlignCenter : "Centre",
+DlgTableAlignRight : "Right",
+DlgTableWidth : "Width",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Height",
+DlgTableCellSpace : "Cell spacing",
+DlgTableCellPad : "Cell padding",
+DlgTableCaption : "Caption",
+DlgTableSummary : "Summary",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Cell Properties",
+DlgCellWidth : "Width",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Height",
+DlgCellWordWrap : "Word Wrap",
+DlgCellWordWrapNotSet : "<Not set>",
+DlgCellWordWrapYes : "Yes",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Horizontal Alignment",
+DlgCellHorAlignNotSet : "<Not set>",
+DlgCellHorAlignLeft : "Left",
+DlgCellHorAlignCenter : "Centre",
+DlgCellHorAlignRight: "Right",
+DlgCellVerAlign : "Vertical Alignment",
+DlgCellVerAlignNotSet : "<Not set>",
+DlgCellVerAlignTop : "Top",
+DlgCellVerAlignMiddle : "Middle",
+DlgCellVerAlignBottom : "Bottom",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Header",
+DlgCellRowSpan : "Rows Span",
+DlgCellCollSpan : "Columns Span",
+DlgCellBackColor : "Background Colour",
+DlgCellBorderColor : "Border Colour",
+DlgCellBtnSelect : "Select...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace",
+
+// Find Dialog
+DlgFindTitle : "Find",
+DlgFindFindBtn : "Find",
+DlgFindNotFoundMsg : "The specified text was not found.",
+
+// Replace Dialog
+DlgReplaceTitle : "Replace",
+DlgReplaceFindLbl : "Find what:",
+DlgReplaceReplaceLbl : "Replace with:",
+DlgReplaceCaseChk : "Match case",
+DlgReplaceReplaceBtn : "Replace",
+DlgReplaceReplAllBtn : "Replace All",
+DlgReplaceWordChk : "Match whole word",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",
+
+PasteAsText : "Paste as Plain Text",
+PasteFromWord : "Paste from Word",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.",
+DlgPasteIgnoreFont : "Ignore Font Face definitions",
+DlgPasteRemoveStyles : "Remove Styles definitions",
+
+// Color Picker
+ColorAutomatic : "Automatic",
+ColorMoreColors : "More Colours...",
+
+// Document Properties
+DocProps : "Document Properties",
+
+// Anchor Dialog
+DlgAnchorTitle : "Anchor Properties",
+DlgAnchorName : "Anchor Name",
+DlgAnchorErrorName : "Please type the anchor name",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Not in dictionary",
+DlgSpellChangeTo : "Change to",
+DlgSpellBtnIgnore : "Ignore",
+DlgSpellBtnIgnoreAll : "Ignore All",
+DlgSpellBtnReplace : "Replace",
+DlgSpellBtnReplaceAll : "Replace All",
+DlgSpellBtnUndo : "Undo",
+DlgSpellNoSuggestions : "- No suggestions -",
+DlgSpellProgress : "Spell check in progress...",
+DlgSpellNoMispell : "Spell check complete: No misspellings found",
+DlgSpellNoChanges : "Spell check complete: No words changed",
+DlgSpellOneChange : "Spell check complete: One word changed",
+DlgSpellManyChanges : "Spell check complete: %1 words changed",
+
+IeSpellDownload : "Spell checker not installed. Do you want to download it now?",
+
+// Button Dialog
+DlgButtonText : "Text (Value)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name",
+DlgCheckboxValue : "Value",
+DlgCheckboxSelected : "Selected",
+
+// Form Dialog
+DlgFormName : "Name",
+DlgFormAction : "Action",
+DlgFormMethod : "Method",
+
+// Select Field Dialog
+DlgSelectName : "Name",
+DlgSelectValue : "Value",
+DlgSelectSize : "Size",
+DlgSelectLines : "lines",
+DlgSelectChkMulti : "Allow multiple selections",
+DlgSelectOpAvail : "Available Options",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Value",
+DlgSelectBtnAdd : "Add",
+DlgSelectBtnModify : "Modify",
+DlgSelectBtnUp : "Up",
+DlgSelectBtnDown : "Down",
+DlgSelectBtnSetValue : "Set as selected value",
+DlgSelectBtnDelete : "Delete",
+
+// Textarea Dialog
+DlgTextareaName : "Name",
+DlgTextareaCols : "Columns",
+DlgTextareaRows : "Rows",
+
+// Text Field Dialog
+DlgTextName : "Name",
+DlgTextValue : "Value",
+DlgTextCharWidth : "Character Width",
+DlgTextMaxChars : "Maximum Characters",
+DlgTextType : "Type",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Password",
+
+// Hidden Field Dialog
+DlgHiddenName : "Name",
+DlgHiddenValue : "Value",
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted List Properties",
+NumberedListProp : "Numbered List Properties",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Circle",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Numbers (1, 2, 3)",
+DlgLstTypeLCase : "Lowercase Letters (a, b, c)",
+DlgLstTypeUCase : "Uppercase Letters (A, B, C)",
+DlgLstTypeSRoman : "Small Roman Numerals (i, ii, iii)",
+DlgLstTypeLRoman : "Large Roman Numerals (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Background",
+DlgDocColorsTab : "Colours and Margins",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Page Title",
+DlgDocLangDir : "Language Direction",
+DlgDocLangDirLTR : "Left to Right (LTR)",
+DlgDocLangDirRTL : "Right to Left (RTL)",
+DlgDocLangCode : "Language Code",
+DlgDocCharSet : "Character Set Encoding",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "Other Character Set Encoding",
+
+DlgDocDocType : "Document Type Heading",
+DlgDocDocTypeOther : "Other Document Type Heading",
+DlgDocIncXHTML : "Include XHTML Declarations",
+DlgDocBgColor : "Background Colour",
+DlgDocBgImage : "Background Image URL",
+DlgDocBgNoScroll : "Nonscrolling Background",
+DlgDocCText : "Text",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Visited Link",
+DlgDocCActive : "Active Link",
+DlgDocMargins : "Page Margins",
+DlgDocMaTop : "Top",
+DlgDocMaLeft : "Left",
+DlgDocMaRight : "Right",
+DlgDocMaBottom : "Bottom",
+DlgDocMeIndex : "Document Indexing Keywords (comma separated)",
+DlgDocMeDescr : "Document Description",
+DlgDocMeAuthor : "Author",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Preview",
+
+// Templates Dialog
+Templates : "Templates",
+DlgTemplatesTitle : "Content Templates",
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):",
+DlgTemplatesLoading : "Loading templates list. Please wait...",
+DlgTemplatesNoTpl : "(No templates defined)",
+DlgTemplatesReplace : "Replace actual contents",
+
+// About Dialog
+DlgAboutAboutTab : "About",
+DlgAboutBrowserInfoTab : "Browser Info",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "version",
+DlgAboutInfo : "For further information go to",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Advanced",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/en-ca.js b/httemplate/elements/fckeditor/editor/lang/en-ca.js
new file mode 100644
index 0000000..1f82fc8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/en-ca.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * English (Canadian) language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Collapse Toolbar",
+ToolbarExpand : "Expand Toolbar",
+
+// Toolbar Items and Context Menu
+Save : "Save",
+NewPage : "New Page",
+Preview : "Preview",
+Cut : "Cut",
+Copy : "Copy",
+Paste : "Paste",
+PasteText : "Paste as plain text",
+PasteWord : "Paste from Word",
+Print : "Print",
+SelectAll : "Select All",
+RemoveFormat : "Remove Format",
+InsertLinkLbl : "Link",
+InsertLink : "Insert/Edit Link",
+RemoveLink : "Remove Link",
+VisitLink : "Open Link",
+Anchor : "Insert/Edit Anchor",
+AnchorDelete : "Remove Anchor",
+InsertImageLbl : "Image",
+InsertImage : "Insert/Edit Image",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insert/Edit Flash",
+InsertTableLbl : "Table",
+InsertTable : "Insert/Edit Table",
+InsertLineLbl : "Line",
+InsertLine : "Insert Horizontal Line",
+InsertSpecialCharLbl: "Special Character",
+InsertSpecialChar : "Insert Special Character",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Insert Smiley",
+About : "About FCKeditor",
+Bold : "Bold",
+Italic : "Italic",
+Underline : "Underline",
+StrikeThrough : "Strike Through",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Left Justify",
+CenterJustify : "Centre Justify",
+RightJustify : "Right Justify",
+BlockJustify : "Block Justify",
+DecreaseIndent : "Decrease Indent",
+IncreaseIndent : "Increase Indent",
+Blockquote : "Blockquote",
+CreateDiv : "Create Div Container",
+EditDiv : "Edit Div Container",
+DeleteDiv : "Remove Div Container",
+Undo : "Undo",
+Redo : "Redo",
+NumberedListLbl : "Numbered List",
+NumberedList : "Insert/Remove Numbered List",
+BulletedListLbl : "Bulleted List",
+BulletedList : "Insert/Remove Bulleted List",
+ShowTableBorders : "Show Table Borders",
+ShowDetails : "Show Details",
+Style : "Style",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Size",
+TextColor : "Text Colour",
+BGColor : "Background Colour",
+Source : "Source",
+Find : "Find",
+Replace : "Replace",
+SpellCheck : "Check Spelling",
+UniversalKeyboard : "Universal Keyboard",
+PageBreakLbl : "Page Break",
+PageBreak : "Insert Page Break",
+
+Form : "Form",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Hidden Field",
+Button : "Button",
+SelectionField : "Selection Field",
+ImageButton : "Image Button",
+
+FitWindow : "Maximize the editor size",
+ShowBlocks : "Show Blocks",
+
+// Context Menu
+EditLink : "Edit Link",
+CellCM : "Cell",
+RowCM : "Row",
+ColumnCM : "Column",
+InsertRowAfter : "Insert Row After",
+InsertRowBefore : "Insert Row Before",
+DeleteRows : "Delete Rows",
+InsertColumnAfter : "Insert Column After",
+InsertColumnBefore : "Insert Column Before",
+DeleteColumns : "Delete Columns",
+InsertCellAfter : "Insert Cell After",
+InsertCellBefore : "Insert Cell Before",
+DeleteCells : "Delete Cells",
+MergeCells : "Merge Cells",
+MergeRight : "Merge Right",
+MergeDown : "Merge Down",
+HorizontalSplitCell : "Split Cell Horizontally",
+VerticalSplitCell : "Split Cell Vertically",
+TableDelete : "Delete Table",
+CellProperties : "Cell Properties",
+TableProperties : "Table Properties",
+ImageProperties : "Image Properties",
+FlashProperties : "Flash Properties",
+
+AnchorProp : "Anchor Properties",
+ButtonProp : "Button Properties",
+CheckboxProp : "Checkbox Properties",
+HiddenFieldProp : "Hidden Field Properties",
+RadioButtonProp : "Radio Button Properties",
+ImageButtonProp : "Image Button Properties",
+TextFieldProp : "Text Field Properties",
+SelectionFieldProp : "Selection Field Properties",
+TextareaProp : "Textarea Properties",
+FormProp : "Form Properties",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Processing XHTML. Please wait...",
+Done : "Done",
+PasteWordConfirm : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",
+NotCompatiblePaste : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",
+UnknownToolbarItem : "Unknown toolbar item \"%1\"",
+UnknownCommand : "Unknown command name \"%1\"",
+NotImplemented : "Command not implemented",
+UnknownToolbarSet : "Toolbar set \"%1\" doesn't exist",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.",
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancel",
+DlgBtnClose : "Close",
+DlgBtnBrowseServer : "Browse Server",
+DlgAdvancedTag : "Advanced",
+DlgOpOther : "<Other>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Please insert the URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<not set>",
+DlgGenId : "Id",
+DlgGenLangDir : "Language Direction",
+DlgGenLangDirLtr : "Left to Right (LTR)",
+DlgGenLangDirRtl : "Right to Left (RTL)",
+DlgGenLangCode : "Language Code",
+DlgGenAccessKey : "Access Key",
+DlgGenName : "Name",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Long Description URL",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Image Properties",
+DlgImgInfoTab : "Image Info",
+DlgImgBtnUpload : "Send it to the Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternative Text",
+DlgImgWidth : "Width",
+DlgImgHeight : "Height",
+DlgImgLockRatio : "Lock Ratio",
+DlgBtnResetSize : "Reset Size",
+DlgImgBorder : "Border",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Align",
+DlgImgAlignLeft : "Left",
+DlgImgAlignAbsBottom: "Abs Bottom",
+DlgImgAlignAbsMiddle: "Abs Middle",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Bottom",
+DlgImgAlignMiddle : "Middle",
+DlgImgAlignRight : "Right",
+DlgImgAlignTextTop : "Text Top",
+DlgImgAlignTop : "Top",
+DlgImgPreview : "Preview",
+DlgImgAlertUrl : "Please type the image URL",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties",
+DlgFlashChkPlay : "Auto Play",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Enable Flash Menu",
+DlgFlashScale : "Scale",
+DlgFlashScaleAll : "Show all",
+DlgFlashScaleNoBorder : "No Border",
+DlgFlashScaleFit : "Exact Fit",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Target",
+
+DlgLnkType : "Link Type",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Link to anchor in the text",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<other>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Select an Anchor",
+DlgLnkAnchorByName : "By Anchor Name",
+DlgLnkAnchorById : "By Element Id",
+DlgLnkNoAnchors : "(No anchors available in the document)",
+DlgLnkEMail : "E-Mail Address",
+DlgLnkEMailSubject : "Message Subject",
+DlgLnkEMailBody : "Message Body",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Send it to the Server",
+
+DlgLnkTarget : "Target",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<popup window>",
+DlgLnkTargetBlank : "New Window (_blank)",
+DlgLnkTargetParent : "Parent Window (_parent)",
+DlgLnkTargetSelf : "Same Window (_self)",
+DlgLnkTargetTop : "Topmost Window (_top)",
+DlgLnkTargetFrameName : "Target Frame Name",
+DlgLnkPopWinName : "Popup Window Name",
+DlgLnkPopWinFeat : "Popup Window Features",
+DlgLnkPopResize : "Resizable",
+DlgLnkPopLocation : "Location Bar",
+DlgLnkPopMenu : "Menu Bar",
+DlgLnkPopScroll : "Scroll Bars",
+DlgLnkPopStatus : "Status Bar",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Full Screen (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Width",
+DlgLnkPopHeight : "Height",
+DlgLnkPopLeft : "Left Position",
+DlgLnkPopTop : "Top Position",
+
+DlnLnkMsgNoUrl : "Please type the link URL",
+DlnLnkMsgNoEMail : "Please type the e-mail address",
+DlnLnkMsgNoAnchor : "Please select an anchor",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces",
+
+// Color Dialog
+DlgColorTitle : "Select Colour",
+DlgColorBtnClear : "Clear",
+DlgColorHighlight : "Highlight",
+DlgColorSelected : "Selected",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insert a Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Select Special Character",
+
+// Table Dialog
+DlgTableTitle : "Table Properties",
+DlgTableRows : "Rows",
+DlgTableColumns : "Columns",
+DlgTableBorder : "Border size",
+DlgTableAlign : "Alignment",
+DlgTableAlignNotSet : "<Not set>",
+DlgTableAlignLeft : "Left",
+DlgTableAlignCenter : "Centre",
+DlgTableAlignRight : "Right",
+DlgTableWidth : "Width",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Height",
+DlgTableCellSpace : "Cell spacing",
+DlgTableCellPad : "Cell padding",
+DlgTableCaption : "Caption",
+DlgTableSummary : "Summary",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Cell Properties",
+DlgCellWidth : "Width",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Height",
+DlgCellWordWrap : "Word Wrap",
+DlgCellWordWrapNotSet : "<Not set>",
+DlgCellWordWrapYes : "Yes",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Horizontal Alignment",
+DlgCellHorAlignNotSet : "<Not set>",
+DlgCellHorAlignLeft : "Left",
+DlgCellHorAlignCenter : "Centre",
+DlgCellHorAlignRight: "Right",
+DlgCellVerAlign : "Vertical Alignment",
+DlgCellVerAlignNotSet : "<Not set>",
+DlgCellVerAlignTop : "Top",
+DlgCellVerAlignMiddle : "Middle",
+DlgCellVerAlignBottom : "Bottom",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Header",
+DlgCellRowSpan : "Rows Span",
+DlgCellCollSpan : "Columns Span",
+DlgCellBackColor : "Background Colour",
+DlgCellBorderColor : "Border Colour",
+DlgCellBtnSelect : "Select...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace",
+
+// Find Dialog
+DlgFindTitle : "Find",
+DlgFindFindBtn : "Find",
+DlgFindNotFoundMsg : "The specified text was not found.",
+
+// Replace Dialog
+DlgReplaceTitle : "Replace",
+DlgReplaceFindLbl : "Find what:",
+DlgReplaceReplaceLbl : "Replace with:",
+DlgReplaceCaseChk : "Match case",
+DlgReplaceReplaceBtn : "Replace",
+DlgReplaceReplAllBtn : "Replace All",
+DlgReplaceWordChk : "Match whole word",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",
+
+PasteAsText : "Paste as Plain Text",
+PasteFromWord : "Paste from Word",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.",
+DlgPasteIgnoreFont : "Ignore Font Face definitions",
+DlgPasteRemoveStyles : "Remove Styles definitions",
+
+// Color Picker
+ColorAutomatic : "Automatic",
+ColorMoreColors : "More Colours...",
+
+// Document Properties
+DocProps : "Document Properties",
+
+// Anchor Dialog
+DlgAnchorTitle : "Anchor Properties",
+DlgAnchorName : "Anchor Name",
+DlgAnchorErrorName : "Please type the anchor name",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Not in dictionary",
+DlgSpellChangeTo : "Change to",
+DlgSpellBtnIgnore : "Ignore",
+DlgSpellBtnIgnoreAll : "Ignore All",
+DlgSpellBtnReplace : "Replace",
+DlgSpellBtnReplaceAll : "Replace All",
+DlgSpellBtnUndo : "Undo",
+DlgSpellNoSuggestions : "- No suggestions -",
+DlgSpellProgress : "Spell check in progress...",
+DlgSpellNoMispell : "Spell check complete: No misspellings found",
+DlgSpellNoChanges : "Spell check complete: No words changed",
+DlgSpellOneChange : "Spell check complete: One word changed",
+DlgSpellManyChanges : "Spell check complete: %1 words changed",
+
+IeSpellDownload : "Spell checker not installed. Do you want to download it now?",
+
+// Button Dialog
+DlgButtonText : "Text (Value)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name",
+DlgCheckboxValue : "Value",
+DlgCheckboxSelected : "Selected",
+
+// Form Dialog
+DlgFormName : "Name",
+DlgFormAction : "Action",
+DlgFormMethod : "Method",
+
+// Select Field Dialog
+DlgSelectName : "Name",
+DlgSelectValue : "Value",
+DlgSelectSize : "Size",
+DlgSelectLines : "lines",
+DlgSelectChkMulti : "Allow multiple selections",
+DlgSelectOpAvail : "Available Options",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Value",
+DlgSelectBtnAdd : "Add",
+DlgSelectBtnModify : "Modify",
+DlgSelectBtnUp : "Up",
+DlgSelectBtnDown : "Down",
+DlgSelectBtnSetValue : "Set as selected value",
+DlgSelectBtnDelete : "Delete",
+
+// Textarea Dialog
+DlgTextareaName : "Name",
+DlgTextareaCols : "Columns",
+DlgTextareaRows : "Rows",
+
+// Text Field Dialog
+DlgTextName : "Name",
+DlgTextValue : "Value",
+DlgTextCharWidth : "Character Width",
+DlgTextMaxChars : "Maximum Characters",
+DlgTextType : "Type",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Password",
+
+// Hidden Field Dialog
+DlgHiddenName : "Name",
+DlgHiddenValue : "Value",
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted List Properties",
+NumberedListProp : "Numbered List Properties",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Circle",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Numbers (1, 2, 3)",
+DlgLstTypeLCase : "Lowercase Letters (a, b, c)",
+DlgLstTypeUCase : "Uppercase Letters (A, B, C)",
+DlgLstTypeSRoman : "Small Roman Numerals (i, ii, iii)",
+DlgLstTypeLRoman : "Large Roman Numerals (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Background",
+DlgDocColorsTab : "Colours and Margins",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Page Title",
+DlgDocLangDir : "Language Direction",
+DlgDocLangDirLTR : "Left to Right (LTR)",
+DlgDocLangDirRTL : "Right to Left (RTL)",
+DlgDocLangCode : "Language Code",
+DlgDocCharSet : "Character Set Encoding",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "Other Character Set Encoding",
+
+DlgDocDocType : "Document Type Heading",
+DlgDocDocTypeOther : "Other Document Type Heading",
+DlgDocIncXHTML : "Include XHTML Declarations",
+DlgDocBgColor : "Background Colour",
+DlgDocBgImage : "Background Image URL",
+DlgDocBgNoScroll : "Nonscrolling Background",
+DlgDocCText : "Text",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Visited Link",
+DlgDocCActive : "Active Link",
+DlgDocMargins : "Page Margins",
+DlgDocMaTop : "Top",
+DlgDocMaLeft : "Left",
+DlgDocMaRight : "Right",
+DlgDocMaBottom : "Bottom",
+DlgDocMeIndex : "Document Indexing Keywords (comma separated)",
+DlgDocMeDescr : "Document Description",
+DlgDocMeAuthor : "Author",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Preview",
+
+// Templates Dialog
+Templates : "Templates",
+DlgTemplatesTitle : "Content Templates",
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):",
+DlgTemplatesLoading : "Loading templates list. Please wait...",
+DlgTemplatesNoTpl : "(No templates defined)",
+DlgTemplatesReplace : "Replace actual contents",
+
+// About Dialog
+DlgAboutAboutTab : "About",
+DlgAboutBrowserInfoTab : "Browser Info",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "version",
+DlgAboutInfo : "For further information go to",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Advanced",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/en-uk.js b/httemplate/elements/fckeditor/editor/lang/en-uk.js
new file mode 100644
index 0000000..d50b51e
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/en-uk.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * English (United Kingdom) language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Collapse Toolbar",
+ToolbarExpand : "Expand Toolbar",
+
+// Toolbar Items and Context Menu
+Save : "Save",
+NewPage : "New Page",
+Preview : "Preview",
+Cut : "Cut",
+Copy : "Copy",
+Paste : "Paste",
+PasteText : "Paste as plain text",
+PasteWord : "Paste from Word",
+Print : "Print",
+SelectAll : "Select All",
+RemoveFormat : "Remove Format",
+InsertLinkLbl : "Link",
+InsertLink : "Insert/Edit Link",
+RemoveLink : "Remove Link",
+VisitLink : "Open Link",
+Anchor : "Insert/Edit Anchor",
+AnchorDelete : "Remove Anchor",
+InsertImageLbl : "Image",
+InsertImage : "Insert/Edit Image",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insert/Edit Flash",
+InsertTableLbl : "Table",
+InsertTable : "Insert/Edit Table",
+InsertLineLbl : "Line",
+InsertLine : "Insert Horizontal Line",
+InsertSpecialCharLbl: "Special Character",
+InsertSpecialChar : "Insert Special Character",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Insert Smiley",
+About : "About FCKeditor",
+Bold : "Bold",
+Italic : "Italic",
+Underline : "Underline",
+StrikeThrough : "Strike Through",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Left Justify",
+CenterJustify : "Centre Justify",
+RightJustify : "Right Justify",
+BlockJustify : "Block Justify",
+DecreaseIndent : "Decrease Indent",
+IncreaseIndent : "Increase Indent",
+Blockquote : "Blockquote",
+CreateDiv : "Create Div Container",
+EditDiv : "Edit Div Container",
+DeleteDiv : "Remove Div Container",
+Undo : "Undo",
+Redo : "Redo",
+NumberedListLbl : "Numbered List",
+NumberedList : "Insert/Remove Numbered List",
+BulletedListLbl : "Bulleted List",
+BulletedList : "Insert/Remove Bulleted List",
+ShowTableBorders : "Show Table Borders",
+ShowDetails : "Show Details",
+Style : "Style",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Size",
+TextColor : "Text Colour",
+BGColor : "Background Colour",
+Source : "Source",
+Find : "Find",
+Replace : "Replace",
+SpellCheck : "Check Spelling",
+UniversalKeyboard : "Universal Keyboard",
+PageBreakLbl : "Page Break",
+PageBreak : "Insert Page Break",
+
+Form : "Form",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Hidden Field",
+Button : "Button",
+SelectionField : "Selection Field",
+ImageButton : "Image Button",
+
+FitWindow : "Maximize the editor size",
+ShowBlocks : "Show Blocks",
+
+// Context Menu
+EditLink : "Edit Link",
+CellCM : "Cell",
+RowCM : "Row",
+ColumnCM : "Column",
+InsertRowAfter : "Insert Row After",
+InsertRowBefore : "Insert Row Before",
+DeleteRows : "Delete Rows",
+InsertColumnAfter : "Insert Column After",
+InsertColumnBefore : "Insert Column Before",
+DeleteColumns : "Delete Columns",
+InsertCellAfter : "Insert Cell After",
+InsertCellBefore : "Insert Cell Before",
+DeleteCells : "Delete Cells",
+MergeCells : "Merge Cells",
+MergeRight : "Merge Right",
+MergeDown : "Merge Down",
+HorizontalSplitCell : "Split Cell Horizontally",
+VerticalSplitCell : "Split Cell Vertically",
+TableDelete : "Delete Table",
+CellProperties : "Cell Properties",
+TableProperties : "Table Properties",
+ImageProperties : "Image Properties",
+FlashProperties : "Flash Properties",
+
+AnchorProp : "Anchor Properties",
+ButtonProp : "Button Properties",
+CheckboxProp : "Checkbox Properties",
+HiddenFieldProp : "Hidden Field Properties",
+RadioButtonProp : "Radio Button Properties",
+ImageButtonProp : "Image Button Properties",
+TextFieldProp : "Text Field Properties",
+SelectionFieldProp : "Selection Field Properties",
+TextareaProp : "Textarea Properties",
+FormProp : "Form Properties",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Processing XHTML. Please wait...",
+Done : "Done",
+PasteWordConfirm : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",
+NotCompatiblePaste : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",
+UnknownToolbarItem : "Unknown toolbar item \"%1\"",
+UnknownCommand : "Unknown command name \"%1\"",
+NotImplemented : "Command not implemented",
+UnknownToolbarSet : "Toolbar set \"%1\" doesn't exist",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.",
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancel",
+DlgBtnClose : "Close",
+DlgBtnBrowseServer : "Browse Server",
+DlgAdvancedTag : "Advanced",
+DlgOpOther : "<Other>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Please insert the URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<not set>",
+DlgGenId : "Id",
+DlgGenLangDir : "Language Direction",
+DlgGenLangDirLtr : "Left to Right (LTR)",
+DlgGenLangDirRtl : "Right to Left (RTL)",
+DlgGenLangCode : "Language Code",
+DlgGenAccessKey : "Access Key",
+DlgGenName : "Name",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Long Description URL",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Image Properties",
+DlgImgInfoTab : "Image Info",
+DlgImgBtnUpload : "Send it to the Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternative Text",
+DlgImgWidth : "Width",
+DlgImgHeight : "Height",
+DlgImgLockRatio : "Lock Ratio",
+DlgBtnResetSize : "Reset Size",
+DlgImgBorder : "Border",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Align",
+DlgImgAlignLeft : "Left",
+DlgImgAlignAbsBottom: "Abs Bottom",
+DlgImgAlignAbsMiddle: "Abs Middle",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Bottom",
+DlgImgAlignMiddle : "Middle",
+DlgImgAlignRight : "Right",
+DlgImgAlignTextTop : "Text Top",
+DlgImgAlignTop : "Top",
+DlgImgPreview : "Preview",
+DlgImgAlertUrl : "Please type the image URL",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties",
+DlgFlashChkPlay : "Auto Play",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Enable Flash Menu",
+DlgFlashScale : "Scale",
+DlgFlashScaleAll : "Show all",
+DlgFlashScaleNoBorder : "No Border",
+DlgFlashScaleFit : "Exact Fit",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Target",
+
+DlgLnkType : "Link Type",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Link to anchor in the text",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<other>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Select an Anchor",
+DlgLnkAnchorByName : "By Anchor Name",
+DlgLnkAnchorById : "By Element Id",
+DlgLnkNoAnchors : "(No anchors available in the document)",
+DlgLnkEMail : "E-Mail Address",
+DlgLnkEMailSubject : "Message Subject",
+DlgLnkEMailBody : "Message Body",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Send it to the Server",
+
+DlgLnkTarget : "Target",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<popup window>",
+DlgLnkTargetBlank : "New Window (_blank)",
+DlgLnkTargetParent : "Parent Window (_parent)",
+DlgLnkTargetSelf : "Same Window (_self)",
+DlgLnkTargetTop : "Topmost Window (_top)",
+DlgLnkTargetFrameName : "Target Frame Name",
+DlgLnkPopWinName : "Popup Window Name",
+DlgLnkPopWinFeat : "Popup Window Features",
+DlgLnkPopResize : "Resizable",
+DlgLnkPopLocation : "Location Bar",
+DlgLnkPopMenu : "Menu Bar",
+DlgLnkPopScroll : "Scroll Bars",
+DlgLnkPopStatus : "Status Bar",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Full Screen (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Width",
+DlgLnkPopHeight : "Height",
+DlgLnkPopLeft : "Left Position",
+DlgLnkPopTop : "Top Position",
+
+DlnLnkMsgNoUrl : "Please type the link URL",
+DlnLnkMsgNoEMail : "Please type the e-mail address",
+DlnLnkMsgNoAnchor : "Please select an anchor",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces",
+
+// Color Dialog
+DlgColorTitle : "Select Colour",
+DlgColorBtnClear : "Clear",
+DlgColorHighlight : "Highlight",
+DlgColorSelected : "Selected",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insert a Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Select Special Character",
+
+// Table Dialog
+DlgTableTitle : "Table Properties",
+DlgTableRows : "Rows",
+DlgTableColumns : "Columns",
+DlgTableBorder : "Border size",
+DlgTableAlign : "Alignment",
+DlgTableAlignNotSet : "<Not set>",
+DlgTableAlignLeft : "Left",
+DlgTableAlignCenter : "Centre",
+DlgTableAlignRight : "Right",
+DlgTableWidth : "Width",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Height",
+DlgTableCellSpace : "Cell spacing",
+DlgTableCellPad : "Cell padding",
+DlgTableCaption : "Caption",
+DlgTableSummary : "Summary",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Cell Properties",
+DlgCellWidth : "Width",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Height",
+DlgCellWordWrap : "Word Wrap",
+DlgCellWordWrapNotSet : "<Not set>",
+DlgCellWordWrapYes : "Yes",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Horizontal Alignment",
+DlgCellHorAlignNotSet : "<Not set>",
+DlgCellHorAlignLeft : "Left",
+DlgCellHorAlignCenter : "Centre",
+DlgCellHorAlignRight: "Right",
+DlgCellVerAlign : "Vertical Alignment",
+DlgCellVerAlignNotSet : "<Not set>",
+DlgCellVerAlignTop : "Top",
+DlgCellVerAlignMiddle : "Middle",
+DlgCellVerAlignBottom : "Bottom",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Header",
+DlgCellRowSpan : "Rows Span",
+DlgCellCollSpan : "Columns Span",
+DlgCellBackColor : "Background Colour",
+DlgCellBorderColor : "Border Colour",
+DlgCellBtnSelect : "Select...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace",
+
+// Find Dialog
+DlgFindTitle : "Find",
+DlgFindFindBtn : "Find",
+DlgFindNotFoundMsg : "The specified text was not found.",
+
+// Replace Dialog
+DlgReplaceTitle : "Replace",
+DlgReplaceFindLbl : "Find what:",
+DlgReplaceReplaceLbl : "Replace with:",
+DlgReplaceCaseChk : "Match case",
+DlgReplaceReplaceBtn : "Replace",
+DlgReplaceReplAllBtn : "Replace All",
+DlgReplaceWordChk : "Match whole word",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",
+
+PasteAsText : "Paste as Plain Text",
+PasteFromWord : "Paste from Word",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.",
+DlgPasteIgnoreFont : "Ignore Font Face definitions",
+DlgPasteRemoveStyles : "Remove Styles definitions",
+
+// Color Picker
+ColorAutomatic : "Automatic",
+ColorMoreColors : "More Colours...",
+
+// Document Properties
+DocProps : "Document Properties",
+
+// Anchor Dialog
+DlgAnchorTitle : "Anchor Properties",
+DlgAnchorName : "Anchor Name",
+DlgAnchorErrorName : "Please type the anchor name",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Not in dictionary",
+DlgSpellChangeTo : "Change to",
+DlgSpellBtnIgnore : "Ignore",
+DlgSpellBtnIgnoreAll : "Ignore All",
+DlgSpellBtnReplace : "Replace",
+DlgSpellBtnReplaceAll : "Replace All",
+DlgSpellBtnUndo : "Undo",
+DlgSpellNoSuggestions : "- No suggestions -",
+DlgSpellProgress : "Spell check in progress...",
+DlgSpellNoMispell : "Spell check complete: No misspellings found",
+DlgSpellNoChanges : "Spell check complete: No words changed",
+DlgSpellOneChange : "Spell check complete: One word changed",
+DlgSpellManyChanges : "Spell check complete: %1 words changed",
+
+IeSpellDownload : "Spell checker not installed. Do you want to download it now?",
+
+// Button Dialog
+DlgButtonText : "Text (Value)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name",
+DlgCheckboxValue : "Value",
+DlgCheckboxSelected : "Selected",
+
+// Form Dialog
+DlgFormName : "Name",
+DlgFormAction : "Action",
+DlgFormMethod : "Method",
+
+// Select Field Dialog
+DlgSelectName : "Name",
+DlgSelectValue : "Value",
+DlgSelectSize : "Size",
+DlgSelectLines : "lines",
+DlgSelectChkMulti : "Allow multiple selections",
+DlgSelectOpAvail : "Available Options",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Value",
+DlgSelectBtnAdd : "Add",
+DlgSelectBtnModify : "Modify",
+DlgSelectBtnUp : "Up",
+DlgSelectBtnDown : "Down",
+DlgSelectBtnSetValue : "Set as selected value",
+DlgSelectBtnDelete : "Delete",
+
+// Textarea Dialog
+DlgTextareaName : "Name",
+DlgTextareaCols : "Columns",
+DlgTextareaRows : "Rows",
+
+// Text Field Dialog
+DlgTextName : "Name",
+DlgTextValue : "Value",
+DlgTextCharWidth : "Character Width",
+DlgTextMaxChars : "Maximum Characters",
+DlgTextType : "Type",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Password",
+
+// Hidden Field Dialog
+DlgHiddenName : "Name",
+DlgHiddenValue : "Value",
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted List Properties",
+NumberedListProp : "Numbered List Properties",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Circle",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Numbers (1, 2, 3)",
+DlgLstTypeLCase : "Lowercase Letters (a, b, c)",
+DlgLstTypeUCase : "Uppercase Letters (A, B, C)",
+DlgLstTypeSRoman : "Small Roman Numerals (i, ii, iii)",
+DlgLstTypeLRoman : "Large Roman Numerals (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Background",
+DlgDocColorsTab : "Colours and Margins",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Page Title",
+DlgDocLangDir : "Language Direction",
+DlgDocLangDirLTR : "Left to Right (LTR)",
+DlgDocLangDirRTL : "Right to Left (RTL)",
+DlgDocLangCode : "Language Code",
+DlgDocCharSet : "Character Set Encoding",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "Other Character Set Encoding",
+
+DlgDocDocType : "Document Type Heading",
+DlgDocDocTypeOther : "Other Document Type Heading",
+DlgDocIncXHTML : "Include XHTML Declarations",
+DlgDocBgColor : "Background Colour",
+DlgDocBgImage : "Background Image URL",
+DlgDocBgNoScroll : "Nonscrolling Background",
+DlgDocCText : "Text",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Visited Link",
+DlgDocCActive : "Active Link",
+DlgDocMargins : "Page Margins",
+DlgDocMaTop : "Top",
+DlgDocMaLeft : "Left",
+DlgDocMaRight : "Right",
+DlgDocMaBottom : "Bottom",
+DlgDocMeIndex : "Document Indexing Keywords (comma separated)",
+DlgDocMeDescr : "Document Description",
+DlgDocMeAuthor : "Author",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Preview",
+
+// Templates Dialog
+Templates : "Templates",
+DlgTemplatesTitle : "Content Templates",
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):",
+DlgTemplatesLoading : "Loading templates list. Please wait...",
+DlgTemplatesNoTpl : "(No templates defined)",
+DlgTemplatesReplace : "Replace actual contents",
+
+// About Dialog
+DlgAboutAboutTab : "About",
+DlgAboutBrowserInfoTab : "Browser Info",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "version",
+DlgAboutInfo : "For further information go to",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Advanced",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/en.js b/httemplate/elements/fckeditor/editor/lang/en.js
new file mode 100644
index 0000000..2c2f5da
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/en.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * English language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Collapse Toolbar",
+ToolbarExpand : "Expand Toolbar",
+
+// Toolbar Items and Context Menu
+Save : "Save",
+NewPage : "New Page",
+Preview : "Preview",
+Cut : "Cut",
+Copy : "Copy",
+Paste : "Paste",
+PasteText : "Paste as plain text",
+PasteWord : "Paste from Word",
+Print : "Print",
+SelectAll : "Select All",
+RemoveFormat : "Remove Format",
+InsertLinkLbl : "Link",
+InsertLink : "Insert/Edit Link",
+RemoveLink : "Remove Link",
+VisitLink : "Open Link",
+Anchor : "Insert/Edit Anchor",
+AnchorDelete : "Remove Anchor",
+InsertImageLbl : "Image",
+InsertImage : "Insert/Edit Image",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insert/Edit Flash",
+InsertTableLbl : "Table",
+InsertTable : "Insert/Edit Table",
+InsertLineLbl : "Line",
+InsertLine : "Insert Horizontal Line",
+InsertSpecialCharLbl: "Special Character",
+InsertSpecialChar : "Insert Special Character",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Insert Smiley",
+About : "About FCKeditor",
+Bold : "Bold",
+Italic : "Italic",
+Underline : "Underline",
+StrikeThrough : "Strike Through",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Left Justify",
+CenterJustify : "Center Justify",
+RightJustify : "Right Justify",
+BlockJustify : "Block Justify",
+DecreaseIndent : "Decrease Indent",
+IncreaseIndent : "Increase Indent",
+Blockquote : "Blockquote",
+CreateDiv : "Create Div Container",
+EditDiv : "Edit Div Container",
+DeleteDiv : "Remove Div Container",
+Undo : "Undo",
+Redo : "Redo",
+NumberedListLbl : "Numbered List",
+NumberedList : "Insert/Remove Numbered List",
+BulletedListLbl : "Bulleted List",
+BulletedList : "Insert/Remove Bulleted List",
+ShowTableBorders : "Show Table Borders",
+ShowDetails : "Show Details",
+Style : "Style",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Size",
+TextColor : "Text Color",
+BGColor : "Background Color",
+Source : "Source",
+Find : "Find",
+Replace : "Replace",
+SpellCheck : "Check Spelling",
+UniversalKeyboard : "Universal Keyboard",
+PageBreakLbl : "Page Break",
+PageBreak : "Insert Page Break",
+
+Form : "Form",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Hidden Field",
+Button : "Button",
+SelectionField : "Selection Field",
+ImageButton : "Image Button",
+
+FitWindow : "Maximize the editor size",
+ShowBlocks : "Show Blocks",
+
+// Context Menu
+EditLink : "Edit Link",
+CellCM : "Cell",
+RowCM : "Row",
+ColumnCM : "Column",
+InsertRowAfter : "Insert Row After",
+InsertRowBefore : "Insert Row Before",
+DeleteRows : "Delete Rows",
+InsertColumnAfter : "Insert Column After",
+InsertColumnBefore : "Insert Column Before",
+DeleteColumns : "Delete Columns",
+InsertCellAfter : "Insert Cell After",
+InsertCellBefore : "Insert Cell Before",
+DeleteCells : "Delete Cells",
+MergeCells : "Merge Cells",
+MergeRight : "Merge Right",
+MergeDown : "Merge Down",
+HorizontalSplitCell : "Split Cell Horizontally",
+VerticalSplitCell : "Split Cell Vertically",
+TableDelete : "Delete Table",
+CellProperties : "Cell Properties",
+TableProperties : "Table Properties",
+ImageProperties : "Image Properties",
+FlashProperties : "Flash Properties",
+
+AnchorProp : "Anchor Properties",
+ButtonProp : "Button Properties",
+CheckboxProp : "Checkbox Properties",
+HiddenFieldProp : "Hidden Field Properties",
+RadioButtonProp : "Radio Button Properties",
+ImageButtonProp : "Image Button Properties",
+TextFieldProp : "Text Field Properties",
+SelectionFieldProp : "Selection Field Properties",
+TextareaProp : "Textarea Properties",
+FormProp : "Form Properties",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Processing XHTML. Please wait...",
+Done : "Done",
+PasteWordConfirm : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",
+NotCompatiblePaste : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",
+UnknownToolbarItem : "Unknown toolbar item \"%1\"",
+UnknownCommand : "Unknown command name \"%1\"",
+NotImplemented : "Command not implemented",
+UnknownToolbarSet : "Toolbar set \"%1\" doesn't exist",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.",
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancel",
+DlgBtnClose : "Close",
+DlgBtnBrowseServer : "Browse Server",
+DlgAdvancedTag : "Advanced",
+DlgOpOther : "<Other>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Please insert the URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<not set>",
+DlgGenId : "Id",
+DlgGenLangDir : "Language Direction",
+DlgGenLangDirLtr : "Left to Right (LTR)",
+DlgGenLangDirRtl : "Right to Left (RTL)",
+DlgGenLangCode : "Language Code",
+DlgGenAccessKey : "Access Key",
+DlgGenName : "Name",
+DlgGenTabIndex : "Tab Index",
+DlgGenLongDescr : "Long Description URL",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Image Properties",
+DlgImgInfoTab : "Image Info",
+DlgImgBtnUpload : "Send it to the Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternative Text",
+DlgImgWidth : "Width",
+DlgImgHeight : "Height",
+DlgImgLockRatio : "Lock Ratio",
+DlgBtnResetSize : "Reset Size",
+DlgImgBorder : "Border",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Align",
+DlgImgAlignLeft : "Left",
+DlgImgAlignAbsBottom: "Abs Bottom",
+DlgImgAlignAbsMiddle: "Abs Middle",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Bottom",
+DlgImgAlignMiddle : "Middle",
+DlgImgAlignRight : "Right",
+DlgImgAlignTextTop : "Text Top",
+DlgImgAlignTop : "Top",
+DlgImgPreview : "Preview",
+DlgImgAlertUrl : "Please type the image URL",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties",
+DlgFlashChkPlay : "Auto Play",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Enable Flash Menu",
+DlgFlashScale : "Scale",
+DlgFlashScaleAll : "Show all",
+DlgFlashScaleNoBorder : "No Border",
+DlgFlashScaleFit : "Exact Fit",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Target",
+
+DlgLnkType : "Link Type",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Link to anchor in the text",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<other>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Select an Anchor",
+DlgLnkAnchorByName : "By Anchor Name",
+DlgLnkAnchorById : "By Element Id",
+DlgLnkNoAnchors : "(No anchors available in the document)",
+DlgLnkEMail : "E-Mail Address",
+DlgLnkEMailSubject : "Message Subject",
+DlgLnkEMailBody : "Message Body",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Send it to the Server",
+
+DlgLnkTarget : "Target",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<popup window>",
+DlgLnkTargetBlank : "New Window (_blank)",
+DlgLnkTargetParent : "Parent Window (_parent)",
+DlgLnkTargetSelf : "Same Window (_self)",
+DlgLnkTargetTop : "Topmost Window (_top)",
+DlgLnkTargetFrameName : "Target Frame Name",
+DlgLnkPopWinName : "Popup Window Name",
+DlgLnkPopWinFeat : "Popup Window Features",
+DlgLnkPopResize : "Resizable",
+DlgLnkPopLocation : "Location Bar",
+DlgLnkPopMenu : "Menu Bar",
+DlgLnkPopScroll : "Scroll Bars",
+DlgLnkPopStatus : "Status Bar",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Full Screen (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Width",
+DlgLnkPopHeight : "Height",
+DlgLnkPopLeft : "Left Position",
+DlgLnkPopTop : "Top Position",
+
+DlnLnkMsgNoUrl : "Please type the link URL",
+DlnLnkMsgNoEMail : "Please type the e-mail address",
+DlnLnkMsgNoAnchor : "Please select an anchor",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces",
+
+// Color Dialog
+DlgColorTitle : "Select Color",
+DlgColorBtnClear : "Clear",
+DlgColorHighlight : "Highlight",
+DlgColorSelected : "Selected",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insert a Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Select Special Character",
+
+// Table Dialog
+DlgTableTitle : "Table Properties",
+DlgTableRows : "Rows",
+DlgTableColumns : "Columns",
+DlgTableBorder : "Border size",
+DlgTableAlign : "Alignment",
+DlgTableAlignNotSet : "<Not set>",
+DlgTableAlignLeft : "Left",
+DlgTableAlignCenter : "Center",
+DlgTableAlignRight : "Right",
+DlgTableWidth : "Width",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Height",
+DlgTableCellSpace : "Cell spacing",
+DlgTableCellPad : "Cell padding",
+DlgTableCaption : "Caption",
+DlgTableSummary : "Summary",
+DlgTableHeaders : "Headers",
+DlgTableHeadersNone : "None",
+DlgTableHeadersColumn : "First column",
+DlgTableHeadersRow : "First Row",
+DlgTableHeadersBoth : "Both",
+
+// Table Cell Dialog
+DlgCellTitle : "Cell Properties",
+DlgCellWidth : "Width",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Height",
+DlgCellWordWrap : "Word Wrap",
+DlgCellWordWrapNotSet : "<Not set>",
+DlgCellWordWrapYes : "Yes",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Horizontal Alignment",
+DlgCellHorAlignNotSet : "<Not set>",
+DlgCellHorAlignLeft : "Left",
+DlgCellHorAlignCenter : "Center",
+DlgCellHorAlignRight: "Right",
+DlgCellVerAlign : "Vertical Alignment",
+DlgCellVerAlignNotSet : "<Not set>",
+DlgCellVerAlignTop : "Top",
+DlgCellVerAlignMiddle : "Middle",
+DlgCellVerAlignBottom : "Bottom",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Header",
+DlgCellRowSpan : "Rows Span",
+DlgCellCollSpan : "Columns Span",
+DlgCellBackColor : "Background Color",
+DlgCellBorderColor : "Border Color",
+DlgCellBtnSelect : "Select...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace",
+
+// Find Dialog
+DlgFindTitle : "Find",
+DlgFindFindBtn : "Find",
+DlgFindNotFoundMsg : "The specified text was not found.",
+
+// Replace Dialog
+DlgReplaceTitle : "Replace",
+DlgReplaceFindLbl : "Find what:",
+DlgReplaceReplaceLbl : "Replace with:",
+DlgReplaceCaseChk : "Match case",
+DlgReplaceReplaceBtn : "Replace",
+DlgReplaceReplAllBtn : "Replace All",
+DlgReplaceWordChk : "Match whole word",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",
+
+PasteAsText : "Paste as Plain Text",
+PasteFromWord : "Paste from Word",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.",
+DlgPasteIgnoreFont : "Ignore Font Face definitions",
+DlgPasteRemoveStyles : "Remove Styles definitions",
+
+// Color Picker
+ColorAutomatic : "Automatic",
+ColorMoreColors : "More Colors...",
+
+// Document Properties
+DocProps : "Document Properties",
+
+// Anchor Dialog
+DlgAnchorTitle : "Anchor Properties",
+DlgAnchorName : "Anchor Name",
+DlgAnchorErrorName : "Please type the anchor name",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Not in dictionary",
+DlgSpellChangeTo : "Change to",
+DlgSpellBtnIgnore : "Ignore",
+DlgSpellBtnIgnoreAll : "Ignore All",
+DlgSpellBtnReplace : "Replace",
+DlgSpellBtnReplaceAll : "Replace All",
+DlgSpellBtnUndo : "Undo",
+DlgSpellNoSuggestions : "- No suggestions -",
+DlgSpellProgress : "Spell check in progress...",
+DlgSpellNoMispell : "Spell check complete: No misspellings found",
+DlgSpellNoChanges : "Spell check complete: No words changed",
+DlgSpellOneChange : "Spell check complete: One word changed",
+DlgSpellManyChanges : "Spell check complete: %1 words changed",
+
+IeSpellDownload : "Spell checker not installed. Do you want to download it now?",
+
+// Button Dialog
+DlgButtonText : "Text (Value)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Name",
+DlgCheckboxValue : "Value",
+DlgCheckboxSelected : "Selected",
+
+// Form Dialog
+DlgFormName : "Name",
+DlgFormAction : "Action",
+DlgFormMethod : "Method",
+
+// Select Field Dialog
+DlgSelectName : "Name",
+DlgSelectValue : "Value",
+DlgSelectSize : "Size",
+DlgSelectLines : "lines",
+DlgSelectChkMulti : "Allow multiple selections",
+DlgSelectOpAvail : "Available Options",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Value",
+DlgSelectBtnAdd : "Add",
+DlgSelectBtnModify : "Modify",
+DlgSelectBtnUp : "Up",
+DlgSelectBtnDown : "Down",
+DlgSelectBtnSetValue : "Set as selected value",
+DlgSelectBtnDelete : "Delete",
+
+// Textarea Dialog
+DlgTextareaName : "Name",
+DlgTextareaCols : "Columns",
+DlgTextareaRows : "Rows",
+
+// Text Field Dialog
+DlgTextName : "Name",
+DlgTextValue : "Value",
+DlgTextCharWidth : "Character Width",
+DlgTextMaxChars : "Maximum Characters",
+DlgTextType : "Type",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Password",
+
+// Hidden Field Dialog
+DlgHiddenName : "Name",
+DlgHiddenValue : "Value",
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted List Properties",
+NumberedListProp : "Numbered List Properties",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Circle",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Numbers (1, 2, 3)",
+DlgLstTypeLCase : "Lowercase Letters (a, b, c)",
+DlgLstTypeUCase : "Uppercase Letters (A, B, C)",
+DlgLstTypeSRoman : "Small Roman Numerals (i, ii, iii)",
+DlgLstTypeLRoman : "Large Roman Numerals (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Background",
+DlgDocColorsTab : "Colors and Margins",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Page Title",
+DlgDocLangDir : "Language Direction",
+DlgDocLangDirLTR : "Left to Right (LTR)",
+DlgDocLangDirRTL : "Right to Left (RTL)",
+DlgDocLangCode : "Language Code",
+DlgDocCharSet : "Character Set Encoding",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "Other Character Set Encoding",
+
+DlgDocDocType : "Document Type Heading",
+DlgDocDocTypeOther : "Other Document Type Heading",
+DlgDocIncXHTML : "Include XHTML Declarations",
+DlgDocBgColor : "Background Color",
+DlgDocBgImage : "Background Image URL",
+DlgDocBgNoScroll : "Nonscrolling Background",
+DlgDocCText : "Text",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Visited Link",
+DlgDocCActive : "Active Link",
+DlgDocMargins : "Page Margins",
+DlgDocMaTop : "Top",
+DlgDocMaLeft : "Left",
+DlgDocMaRight : "Right",
+DlgDocMaBottom : "Bottom",
+DlgDocMeIndex : "Document Indexing Keywords (comma separated)",
+DlgDocMeDescr : "Document Description",
+DlgDocMeAuthor : "Author",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Preview",
+
+// Templates Dialog
+Templates : "Templates",
+DlgTemplatesTitle : "Content Templates",
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):",
+DlgTemplatesLoading : "Loading templates list. Please wait...",
+DlgTemplatesNoTpl : "(No templates defined)",
+DlgTemplatesReplace : "Replace actual contents",
+
+// About Dialog
+DlgAboutAboutTab : "About",
+DlgAboutBrowserInfoTab : "Browser Info",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "version",
+DlgAboutInfo : "For further information go to",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Advanced",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT",
+ScaytTitleOptions : "Options",
+ScaytTitleLangs : "Languages",
+ScaytTitleAbout : "About"
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/eo.js b/httemplate/elements/fckeditor/editor/lang/eo.js
new file mode 100644
index 0000000..c1897de
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/eo.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Esperanto language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "KaÅi Ilobreton",
+ToolbarExpand : "Vidigi Ilojn",
+
+// Toolbar Items and Context Menu
+Save : "Sekurigi",
+NewPage : "Nova PaÄo",
+Preview : "Vidigi Aspekton",
+Cut : "Eltondi",
+Copy : "Kopii",
+Paste : "Interglui",
+PasteText : "Interglui kiel Tekston",
+PasteWord : "Interglui el Word",
+Print : "Presi",
+SelectAll : "Elekti ĉion",
+RemoveFormat : "Forigi Formaton",
+InsertLinkLbl : "Ligilo",
+InsertLink : "Enmeti/ÅœanÄi Ligilon",
+RemoveLink : "Forigi Ligilon",
+VisitLink : "Open Link", //MISSING
+Anchor : "Enmeti/ÅœanÄi Ankron",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Bildo",
+InsertImage : "Enmeti/ÅœanÄi Bildon",
+InsertFlashLbl : "Flash", //MISSING
+InsertFlash : "Insert/Edit Flash", //MISSING
+InsertTableLbl : "Tabelo",
+InsertTable : "Enmeti/ÅœanÄi Tabelon",
+InsertLineLbl : "Horizonta Linio",
+InsertLine : "Enmeti Horizonta Linio",
+InsertSpecialCharLbl: "Speciala Signo",
+InsertSpecialChar : "Enmeti Specialan Signon",
+InsertSmileyLbl : "Mienvinjeto",
+InsertSmiley : "Enmeti Mienvinjeton",
+About : "Pri FCKeditor",
+Bold : "Grasa",
+Italic : "Kursiva",
+Underline : "Substreko",
+StrikeThrough : "Trastreko",
+Subscript : "Subskribo",
+Superscript : "Superskribo",
+LeftJustify : "Maldekstrigi",
+CenterJustify : "Centrigi",
+RightJustify : "Dekstrigi",
+BlockJustify : "Äœisrandigi AmbaÅ­flanke",
+DecreaseIndent : "Malpligrandigi KrommarÄenon",
+IncreaseIndent : "Pligrandigi KrommarÄenon",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Malfari",
+Redo : "Refari",
+NumberedListLbl : "Numera Listo",
+NumberedList : "Enmeti/Forigi Numeran Liston",
+BulletedListLbl : "Bula Listo",
+BulletedList : "Enmeti/Forigi Bulan Liston",
+ShowTableBorders : "Vidigi Borderojn de Tabelo",
+ShowDetails : "Vidigi Detalojn",
+Style : "Stilo",
+FontFormat : "Formato",
+Font : "Tiparo",
+FontSize : "Grando",
+TextColor : "Teksta Koloro",
+BGColor : "Fona Koloro",
+Source : "Fonto",
+Find : "Serĉi",
+Replace : "AnstataÅ­igi",
+SpellCheck : "Literumada Kontrolilo",
+UniversalKeyboard : "Universala Klavaro",
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "Formularo",
+Checkbox : "Markobutono",
+RadioButton : "Radiobutono",
+TextField : "Teksta kampo",
+Textarea : "Teksta Areo",
+HiddenField : "KaÅita Kampo",
+Button : "Butono",
+SelectionField : "Elekta Kampo",
+ImageButton : "Bildbutono",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Modifier Ligilon",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Forigi Liniojn",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Forigi Kolumnojn",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Forigi Ĉelojn",
+MergeCells : "Kunfandi Ĉelojn",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Delete Table", //MISSING
+CellProperties : "Atributoj de Ĉelo",
+TableProperties : "Atributoj de Tabelo",
+ImageProperties : "Atributoj de Bildo",
+FlashProperties : "Flash Properties", //MISSING
+
+AnchorProp : "Ankraj Atributoj",
+ButtonProp : "Butonaj Atributoj",
+CheckboxProp : "Markobutonaj Atributoj",
+HiddenFieldProp : "Atributoj de KaÅita Kampo",
+RadioButtonProp : "Radiobutonaj Atributoj",
+ImageButtonProp : "Bildbutonaj Atributoj",
+TextFieldProp : "Atributoj de Teksta Kampo",
+SelectionFieldProp : "Atributoj de Elekta Kampo",
+TextareaProp : "Atributoj de Teksta Areo",
+FormProp : "Formularaj Atributoj",
+
+FontFormats : "Normala;Formatita;Adreso;Titolo 1;Titolo 2;Titolo 3;Titolo 4;Titolo 5;Titolo 6;Paragrafo (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Traktado de XHTML. Bonvolu pacienci...",
+Done : "Finita",
+PasteWordConfirm : "La algluota teksto Åajnas esti Word-devena. Ĉu vi volas purigi Äin antaÅ­ ol interglui?",
+NotCompatiblePaste : "Tiu ĉi komando bezonas almenaŭ Internet Explorer 5.5. Ĉu vi volas daŭrigi sen purigado?",
+UnknownToolbarItem : "Ilobretero nekonata \"%1\"",
+UnknownCommand : "Komandonomo nekonata \"%1\"",
+NotImplemented : "Komando ne ankoraÅ­ realigita",
+UnknownToolbarSet : "La ilobreto \"%1\" ne ekzistas",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "Akcepti",
+DlgBtnCancel : "Rezigni",
+DlgBtnClose : "Fermi",
+DlgBtnBrowseServer : "Foliumi en la Servilo",
+DlgAdvancedTag : "Speciala",
+DlgOpOther : "<Alia>",
+DlgInfoTab : "Info", //MISSING
+DlgAlertUrl : "Please insert the URL", //MISSING
+
+// General Dialogs Labels
+DlgGenNotSet : "<DefaÅ­lta>",
+DlgGenId : "Id",
+DlgGenLangDir : "Skribdirekto",
+DlgGenLangDirLtr : "De maldekstro dekstren (LTR)",
+DlgGenLangDirRtl : "De dekstro maldekstren (RTL)",
+DlgGenLangCode : "Lingva Kodo",
+DlgGenAccessKey : "Fulmoklavo",
+DlgGenName : "Nomo",
+DlgGenTabIndex : "Taba Ordo",
+DlgGenLongDescr : "URL de Longa Priskribo",
+DlgGenClass : "Klasoj de Stilfolioj",
+DlgGenTitle : "Indika Titolo",
+DlgGenContType : "Indika Enhavotipo",
+DlgGenLinkCharset : "Signaro de la Ligita Rimedo",
+DlgGenStyle : "Stilo",
+
+// Image Dialog
+DlgImgTitle : "Atributoj de Bildo",
+DlgImgInfoTab : "Informoj pri Bildo",
+DlgImgBtnUpload : "Sendu al Servilo",
+DlgImgURL : "URL",
+DlgImgUpload : "AlÅuti",
+DlgImgAlt : "AnstataÅ­iga Teksto",
+DlgImgWidth : "LarÄo",
+DlgImgHeight : "Alto",
+DlgImgLockRatio : "Konservi Proporcion",
+DlgBtnResetSize : "Origina Grando",
+DlgImgBorder : "Bordero",
+DlgImgHSpace : "HSpaco",
+DlgImgVSpace : "VSpaco",
+DlgImgAlign : "Äœisrandigo",
+DlgImgAlignLeft : "Maldekstre",
+DlgImgAlignAbsBottom: "Abs Malsupre",
+DlgImgAlignAbsMiddle: "Abs Centre",
+DlgImgAlignBaseline : "Je Malsupro de Teksto",
+DlgImgAlignBottom : "Malsupre",
+DlgImgAlignMiddle : "Centre",
+DlgImgAlignRight : "Dekstre",
+DlgImgAlignTextTop : "Je Supro de Teksto",
+DlgImgAlignTop : "Supre",
+DlgImgPreview : "Vidigi Aspekton",
+DlgImgAlertUrl : "Bonvolu tajpi la URL de la bildo",
+DlgImgLinkTab : "Link", //MISSING
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties", //MISSING
+DlgFlashChkPlay : "Auto Play", //MISSING
+DlgFlashChkLoop : "Loop", //MISSING
+DlgFlashChkMenu : "Enable Flash Menu", //MISSING
+DlgFlashScale : "Scale", //MISSING
+DlgFlashScaleAll : "Show all", //MISSING
+DlgFlashScaleNoBorder : "No Border", //MISSING
+DlgFlashScaleFit : "Exact Fit", //MISSING
+
+// Link Dialog
+DlgLnkWindowTitle : "Ligilo",
+DlgLnkInfoTab : "Informoj pri la Ligilo",
+DlgLnkTargetTab : "Celo",
+
+DlgLnkType : "Tipo de Ligilo",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ankri en tiu ĉi paÄo",
+DlgLnkTypeEMail : "RetpoÅto",
+DlgLnkProto : "Protokolo",
+DlgLnkProtoOther : "<alia>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Elekti Ankron",
+DlgLnkAnchorByName : "Per Ankronomo",
+DlgLnkAnchorById : "Per Elementidentigilo",
+DlgLnkNoAnchors : "<Ne disponeblas ankroj en la dokumento>",
+DlgLnkEMail : "Retadreso",
+DlgLnkEMailSubject : "Temlinio",
+DlgLnkEMailBody : "MesaÄa korpo",
+DlgLnkUpload : "AlÅuti",
+DlgLnkBtnUpload : "Sendi al Servilo",
+
+DlgLnkTarget : "Celo",
+DlgLnkTargetFrame : "<kadro>",
+DlgLnkTargetPopup : "<Åprucfenestro>",
+DlgLnkTargetBlank : "Nova Fenestro (_blank)",
+DlgLnkTargetParent : "Gepatra Fenestro (_parent)",
+DlgLnkTargetSelf : "Sama Fenestro (_self)",
+DlgLnkTargetTop : "Plej Supra Fenestro (_top)",
+DlgLnkTargetFrameName : "Nomo de Kadro",
+DlgLnkPopWinName : "Nomo de Åœprucfenestro",
+DlgLnkPopWinFeat : "Atributoj de la Åœprucfenestro",
+DlgLnkPopResize : "Grando ÅœanÄebla",
+DlgLnkPopLocation : "Adresobreto",
+DlgLnkPopMenu : "Menubreto",
+DlgLnkPopScroll : "Rulumlisteloj",
+DlgLnkPopStatus : "Statobreto",
+DlgLnkPopToolbar : "Ilobreto",
+DlgLnkPopFullScrn : "Tutekrane (IE)",
+DlgLnkPopDependent : "Dependa (Netscape)",
+DlgLnkPopWidth : "LarÄo",
+DlgLnkPopHeight : "Alto",
+DlgLnkPopLeft : "Pozicio de Maldekstro",
+DlgLnkPopTop : "Pozicio de Supro",
+
+DlnLnkMsgNoUrl : "Bonvolu entajpi la URL-on",
+DlnLnkMsgNoEMail : "Bonvolu entajpi la retadreson",
+DlnLnkMsgNoAnchor : "Bonvolu elekti ankron",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Elekti",
+DlgColorBtnClear : "Forigi",
+DlgColorHighlight : "Emfazi",
+DlgColorSelected : "Elektita",
+
+// Smiley Dialog
+DlgSmileyTitle : "Enmeti Mienvinjeton",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Enmeti Specialan Signon",
+
+// Table Dialog
+DlgTableTitle : "Atributoj de Tabelo",
+DlgTableRows : "Linioj",
+DlgTableColumns : "Kolumnoj",
+DlgTableBorder : "Bordero",
+DlgTableAlign : "Äœisrandigo",
+DlgTableAlignNotSet : "<DefaÅ­lte>",
+DlgTableAlignLeft : "Maldekstre",
+DlgTableAlignCenter : "Centre",
+DlgTableAlignRight : "Dekstre",
+DlgTableWidth : "LarÄo",
+DlgTableWidthPx : "Bitbilderoj",
+DlgTableWidthPc : "elcentoj",
+DlgTableHeight : "Alto",
+DlgTableCellSpace : "Interspacigo de Ĉeloj",
+DlgTableCellPad : "Ĉirkaŭenhava Plenigado",
+DlgTableCaption : "Titolo",
+DlgTableSummary : "Summary", //MISSING
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Atributoj de Celo",
+DlgCellWidth : "LarÄo",
+DlgCellWidthPx : "bitbilderoj",
+DlgCellWidthPc : "elcentoj",
+DlgCellHeight : "Alto",
+DlgCellWordWrap : "Linifaldo",
+DlgCellWordWrapNotSet : "<DefaÅ­lte>",
+DlgCellWordWrapYes : "Jes",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Horizonta Äœisrandigo",
+DlgCellHorAlignNotSet : "<DefaÅ­lte>",
+DlgCellHorAlignLeft : "Maldekstre",
+DlgCellHorAlignCenter : "Centre",
+DlgCellHorAlignRight: "Dekstre",
+DlgCellVerAlign : "Vertikala Äœisrandigo",
+DlgCellVerAlignNotSet : "<DefaÅ­lte>",
+DlgCellVerAlignTop : "Supre",
+DlgCellVerAlignMiddle : "Centre",
+DlgCellVerAlignBottom : "Malsupre",
+DlgCellVerAlignBaseline : "Je Malsupro de Teksto",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Linioj Kunfanditaj",
+DlgCellCollSpan : "Kolumnoj Kunfanditaj",
+DlgCellBackColor : "Fono",
+DlgCellBorderColor : "Bordero",
+DlgCellBtnSelect : "Elekti...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Serĉi",
+DlgFindFindBtn : "Serĉi",
+DlgFindNotFoundMsg : "La celteksto ne estas trovita.",
+
+// Replace Dialog
+DlgReplaceTitle : "AnstataÅ­igi",
+DlgReplaceFindLbl : "Serĉi:",
+DlgReplaceReplaceLbl : "AnstataÅ­igi per:",
+DlgReplaceCaseChk : "Kongruigi Usklecon",
+DlgReplaceReplaceBtn : "AnstataÅ­igi",
+DlgReplaceReplAllBtn : "Anstataŭigi Ĉiun",
+DlgReplaceWordChk : "Tuta Vorto",
+
+// Paste Operations / Dialog
+PasteErrorCut : "La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras eltondajn operaciojn. Bonvolu uzi la klavaron por tio (ctrl-X).",
+PasteErrorCopy : "La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras kopiajn operaciojn. Bonvolu uzi la klavaron por tio (ctrl-C).",
+
+PasteAsText : "Interglui kiel Tekston",
+PasteFromWord : "Interglui el Word",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.", //MISSING
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignore Font Face definitions", //MISSING
+DlgPasteRemoveStyles : "Remove Styles definitions", //MISSING
+
+// Color Picker
+ColorAutomatic : "AÅ­tomata",
+ColorMoreColors : "Pli da Koloroj...",
+
+// Document Properties
+DocProps : "Dokumentaj Atributoj",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankraj Atributoj",
+DlgAnchorName : "Ankra Nomo",
+DlgAnchorErrorName : "Bv tajpi la ankran nomon",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ne trovita en la vortaro",
+DlgSpellChangeTo : "ÅœanÄi al",
+DlgSpellBtnIgnore : "Malatenti",
+DlgSpellBtnIgnoreAll : "Malatenti Ĉiun",
+DlgSpellBtnReplace : "AnstataÅ­igi",
+DlgSpellBtnReplaceAll : "Anstataŭigi Ĉiun",
+DlgSpellBtnUndo : "Malfari",
+DlgSpellNoSuggestions : "- Neniu propono -",
+DlgSpellProgress : "Literumkontrolado daÅ­ras...",
+DlgSpellNoMispell : "Literumkontrolado finita: neniu fuÅo trovita",
+DlgSpellNoChanges : "Literumkontrolado finita: neniu vorto ÅanÄita",
+DlgSpellOneChange : "Literumkontrolado finita: unu vorto ÅanÄita",
+DlgSpellManyChanges : "Literumkontrolado finita: %1 vortoj ÅanÄitaj",
+
+IeSpellDownload : "Literumada Kontrolilo ne instalita. Ĉu vi volas elÅuti Äin nun?",
+
+// Button Dialog
+DlgButtonText : "Teksto (Valoro)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nomo",
+DlgCheckboxValue : "Valoro",
+DlgCheckboxSelected : "Elektita",
+
+// Form Dialog
+DlgFormName : "Nomo",
+DlgFormAction : "Ago",
+DlgFormMethod : "Metodo",
+
+// Select Field Dialog
+DlgSelectName : "Nomo",
+DlgSelectValue : "Valoro",
+DlgSelectSize : "Grando",
+DlgSelectLines : "Linioj",
+DlgSelectChkMulti : "Permesi Plurajn Elektojn",
+DlgSelectOpAvail : "Elektoj Disponeblaj",
+DlgSelectOpText : "Teksto",
+DlgSelectOpValue : "Valoro",
+DlgSelectBtnAdd : "Aldoni",
+DlgSelectBtnModify : "Modifi",
+DlgSelectBtnUp : "Supren",
+DlgSelectBtnDown : "Malsupren",
+DlgSelectBtnSetValue : "Agordi kiel Elektitan Valoron",
+DlgSelectBtnDelete : "Forigi",
+
+// Textarea Dialog
+DlgTextareaName : "Nomo",
+DlgTextareaCols : "Kolumnoj",
+DlgTextareaRows : "Vicoj",
+
+// Text Field Dialog
+DlgTextName : "Nomo",
+DlgTextValue : "Valoro",
+DlgTextCharWidth : "SignolarÄo",
+DlgTextMaxChars : "Maksimuma Nombro da Signoj",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Teksto",
+DlgTextTypePass : "Pasvorto",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nomo",
+DlgHiddenValue : "Valoro",
+
+// Bulleted List Dialog
+BulletedListProp : "Atributoj de Bula Listo",
+NumberedListProp : "Atributoj de Numera Listo",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Cirklo",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "Kvadrato",
+DlgLstTypeNumbers : "Ciferoj (1, 2, 3)",
+DlgLstTypeLCase : "Minusklaj Literoj (a, b, c)",
+DlgLstTypeUCase : "Majusklaj Literoj (A, B, C)",
+DlgLstTypeSRoman : "Malgrandaj Romanaj Ciferoj (i, ii, iii)",
+DlgLstTypeLRoman : "Grandaj Romanaj Ciferoj (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Ĝeneralaĵoj",
+DlgDocBackTab : "Fono",
+DlgDocColorsTab : "Koloroj kaj MarÄenoj",
+DlgDocMetaTab : "Metadatumoj",
+
+DlgDocPageTitle : "PaÄotitolo",
+DlgDocLangDir : "Skribdirekto de la Lingvo",
+DlgDocLangDirLTR : "De maldekstro dekstren (LTR)",
+DlgDocLangDirRTL : "De dekstro maldekstren (LTR)",
+DlgDocLangCode : "Lingvokodo",
+DlgDocCharSet : "Signara Kodo",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Alia Signara Kodo",
+
+DlgDocDocType : "Dokumenta Tipo",
+DlgDocDocTypeOther : "Alia Dokumenta Tipo",
+DlgDocIncXHTML : "Inkluzivi XHTML Deklaroj",
+DlgDocBgColor : "Fona Koloro",
+DlgDocBgImage : "URL de Fona Bildo",
+DlgDocBgNoScroll : "Neruluma Fono",
+DlgDocCText : "Teksto",
+DlgDocCLink : "Ligilo",
+DlgDocCVisited : "Vizitita Ligilo",
+DlgDocCActive : "Aktiva Ligilo",
+DlgDocMargins : "PaÄaj MarÄenoj",
+DlgDocMaTop : "Supra",
+DlgDocMaLeft : "Maldekstra",
+DlgDocMaRight : "Dekstra",
+DlgDocMaBottom : "Malsupra",
+DlgDocMeIndex : "Åœlosilvortoj de la Dokumento (apartigita de komoj)",
+DlgDocMeDescr : "Dokumenta Priskribo",
+DlgDocMeAuthor : "Verkinto",
+DlgDocMeCopy : "Kopirajto",
+DlgDocPreview : "Aspekto",
+
+// Templates Dialog
+Templates : "Templates", //MISSING
+DlgTemplatesTitle : "Content Templates", //MISSING
+DlgTemplatesSelMsg : "Please select the template to open in the editor<br />(the actual contents will be lost):", //MISSING
+DlgTemplatesLoading : "Loading templates list. Please wait...", //MISSING
+DlgTemplatesNoTpl : "(No templates defined)", //MISSING
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "Pri",
+DlgAboutBrowserInfoTab : "Informoj pri TTT-legilo",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "versio",
+DlgAboutInfo : "Por pli da informoj, vizitu",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/es.js b/httemplate/elements/fckeditor/editor/lang/es.js
new file mode 100644
index 0000000..d77d38a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/es.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Spanish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Contraer Barra",
+ToolbarExpand : "Expandir Barra",
+
+// Toolbar Items and Context Menu
+Save : "Guardar",
+NewPage : "Nueva Página",
+Preview : "Vista Previa",
+Cut : "Cortar",
+Copy : "Copiar",
+Paste : "Pegar",
+PasteText : "Pegar como texto plano",
+PasteWord : "Pegar desde Word",
+Print : "Imprimir",
+SelectAll : "Seleccionar Todo",
+RemoveFormat : "Eliminar Formato",
+InsertLinkLbl : "Vínculo",
+InsertLink : "Insertar/Editar Vínculo",
+RemoveLink : "Eliminar Vínculo",
+VisitLink : "Abrir enlace",
+Anchor : "Referencia",
+AnchorDelete : "Eliminar Referencia",
+InsertImageLbl : "Imagen",
+InsertImage : "Insertar/Editar Imagen",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insertar/Editar Flash",
+InsertTableLbl : "Tabla",
+InsertTable : "Insertar/Editar Tabla",
+InsertLineLbl : "Línea",
+InsertLine : "Insertar Línea Horizontal",
+InsertSpecialCharLbl: "Caracter Especial",
+InsertSpecialChar : "Insertar Caracter Especial",
+InsertSmileyLbl : "Emoticons",
+InsertSmiley : "Insertar Emoticons",
+About : "Acerca de FCKeditor",
+Bold : "Negrita",
+Italic : "Cursiva",
+Underline : "Subrayado",
+StrikeThrough : "Tachado",
+Subscript : "Subíndice",
+Superscript : "Superíndice",
+LeftJustify : "Alinear a Izquierda",
+CenterJustify : "Centrar",
+RightJustify : "Alinear a Derecha",
+BlockJustify : "Justificado",
+DecreaseIndent : "Disminuir Sangría",
+IncreaseIndent : "Aumentar Sangría",
+Blockquote : "Cita",
+CreateDiv : "Crear contenedor (div)",
+EditDiv : "Editar contenedor (div)",
+DeleteDiv : "Eliminar contenedor (div)",
+Undo : "Deshacer",
+Redo : "Rehacer",
+NumberedListLbl : "Numeración",
+NumberedList : "Insertar/Eliminar Numeración",
+BulletedListLbl : "Viñetas",
+BulletedList : "Insertar/Eliminar Viñetas",
+ShowTableBorders : "Mostrar Bordes de Tablas",
+ShowDetails : "Mostrar saltos de Párrafo",
+Style : "Estilo",
+FontFormat : "Formato",
+Font : "Fuente",
+FontSize : "Tamaño",
+TextColor : "Color de Texto",
+BGColor : "Color de Fondo",
+Source : "Fuente HTML",
+Find : "Buscar",
+Replace : "Reemplazar",
+SpellCheck : "Ortografía",
+UniversalKeyboard : "Teclado Universal",
+PageBreakLbl : "Salto de Página",
+PageBreak : "Insertar Salto de Página",
+
+Form : "Formulario",
+Checkbox : "Casilla de Verificación",
+RadioButton : "Botones de Radio",
+TextField : "Campo de Texto",
+Textarea : "Area de Texto",
+HiddenField : "Campo Oculto",
+Button : "Botón",
+SelectionField : "Campo de Selección",
+ImageButton : "Botón Imagen",
+
+FitWindow : "Maximizar el tamaño del editor",
+ShowBlocks : "Mostrar bloques",
+
+// Context Menu
+EditLink : "Editar Vínculo",
+CellCM : "Celda",
+RowCM : "Fila",
+ColumnCM : "Columna",
+InsertRowAfter : "Insertar fila en la parte inferior",
+InsertRowBefore : "Insertar fila en la parte superior",
+DeleteRows : "Eliminar Filas",
+InsertColumnAfter : "Insertar columna a la derecha",
+InsertColumnBefore : "Insertar columna a la izquierda",
+DeleteColumns : "Eliminar Columnas",
+InsertCellAfter : "Insertar celda a la derecha",
+InsertCellBefore : "Insertar celda a la izquierda",
+DeleteCells : "Eliminar Celdas",
+MergeCells : "Combinar Celdas",
+MergeRight : "Combinar a la derecha",
+MergeDown : "Combinar hacia abajo",
+HorizontalSplitCell : "Dividir la celda horizontalmente",
+VerticalSplitCell : "Dividir la celda verticalmente",
+TableDelete : "Eliminar Tabla",
+CellProperties : "Propiedades de Celda",
+TableProperties : "Propiedades de Tabla",
+ImageProperties : "Propiedades de Imagen",
+FlashProperties : "Propiedades de Flash",
+
+AnchorProp : "Propiedades de Referencia",
+ButtonProp : "Propiedades de Botón",
+CheckboxProp : "Propiedades de Casilla",
+HiddenFieldProp : "Propiedades de Campo Oculto",
+RadioButtonProp : "Propiedades de Botón de Radio",
+ImageButtonProp : "Propiedades de Botón de Imagen",
+TextFieldProp : "Propiedades de Campo de Texto",
+SelectionFieldProp : "Propiedades de Campo de Selección",
+TextareaProp : "Propiedades de Area de Texto",
+FormProp : "Propiedades de Formulario",
+
+FontFormats : "Normal;Con formato;Dirección;Encabezado 1;Encabezado 2;Encabezado 3;Encabezado 4;Encabezado 5;Encabezado 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Procesando XHTML. Por favor, espere...",
+Done : "Hecho",
+PasteWordConfirm : "El texto que desea parece provenir de Word. Desea depurarlo antes de pegarlo?",
+NotCompatiblePaste : "Este comando está disponible sólo para Internet Explorer version 5.5 or superior. Desea pegar sin depurar?",
+UnknownToolbarItem : "Item de barra desconocido \"%1\"",
+UnknownCommand : "Nombre de comando desconocido \"%1\"",
+NotImplemented : "Comando no implementado",
+UnknownToolbarSet : "Nombre de barra \"%1\" no definido",
+NoActiveX : "La configuración de las opciones de seguridad de su navegador puede estar limitando algunas características del editor. Por favor active la opción \"Ejecutar controles y complementos de ActiveX \", de lo contrario puede experimentar errores o ausencia de funcionalidades.",
+BrowseServerBlocked : "La ventana de visualización del servidor no pudo ser abierta. Verifique que su navegador no esté bloqueando las ventanas emergentes (pop up).",
+DialogBlocked : "No se ha podido abrir la ventana de diálogo. Verifique que su navegador no esté bloqueando las ventanas emergentes (pop up).",
+VisitLinkBlocked : "Nose ha podido abrir la ventana. Asegurese de que todos los bloqueadores de popups están deshabilitados.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancelar",
+DlgBtnClose : "Cerrar",
+DlgBtnBrowseServer : "Ver Servidor",
+DlgAdvancedTag : "Avanzado",
+DlgOpOther : "<Otro>",
+DlgInfoTab : "Información",
+DlgAlertUrl : "Inserte el URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<No definido>",
+DlgGenId : "Id",
+DlgGenLangDir : "Orientación",
+DlgGenLangDirLtr : "Izquierda a Derecha (LTR)",
+DlgGenLangDirRtl : "Derecha a Izquierda (RTL)",
+DlgGenLangCode : "Cód. de idioma",
+DlgGenAccessKey : "Clave de Acceso",
+DlgGenName : "Nombre",
+DlgGenTabIndex : "Indice de tabulación",
+DlgGenLongDescr : "Descripción larga URL",
+DlgGenClass : "Clases de hojas de estilo",
+DlgGenTitle : "Título",
+DlgGenContType : "Tipo de Contenido",
+DlgGenLinkCharset : "Fuente de caracteres vinculado",
+DlgGenStyle : "Estilo",
+
+// Image Dialog
+DlgImgTitle : "Propiedades de Imagen",
+DlgImgInfoTab : "Información de Imagen",
+DlgImgBtnUpload : "Enviar al Servidor",
+DlgImgURL : "URL",
+DlgImgUpload : "Cargar",
+DlgImgAlt : "Texto Alternativo",
+DlgImgWidth : "Anchura",
+DlgImgHeight : "Altura",
+DlgImgLockRatio : "Proporcional",
+DlgBtnResetSize : "Tamaño Original",
+DlgImgBorder : "Borde",
+DlgImgHSpace : "Esp.Horiz",
+DlgImgVSpace : "Esp.Vert",
+DlgImgAlign : "Alineación",
+DlgImgAlignLeft : "Izquierda",
+DlgImgAlignAbsBottom: "Abs inferior",
+DlgImgAlignAbsMiddle: "Abs centro",
+DlgImgAlignBaseline : "Línea de base",
+DlgImgAlignBottom : "Pie",
+DlgImgAlignMiddle : "Centro",
+DlgImgAlignRight : "Derecha",
+DlgImgAlignTextTop : "Tope del texto",
+DlgImgAlignTop : "Tope",
+DlgImgPreview : "Vista Previa",
+DlgImgAlertUrl : "Por favor escriba la URL de la imagen",
+DlgImgLinkTab : "Vínculo",
+
+// Flash Dialog
+DlgFlashTitle : "Propiedades de Flash",
+DlgFlashChkPlay : "Autoejecución",
+DlgFlashChkLoop : "Repetir",
+DlgFlashChkMenu : "Activar Menú Flash",
+DlgFlashScale : "Escala",
+DlgFlashScaleAll : "Mostrar todo",
+DlgFlashScaleNoBorder : "Sin Borde",
+DlgFlashScaleFit : "Ajustado",
+
+// Link Dialog
+DlgLnkWindowTitle : "Vínculo",
+DlgLnkInfoTab : "Información de Vínculo",
+DlgLnkTargetTab : "Destino",
+
+DlgLnkType : "Tipo de vínculo",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Referencia en esta página",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocolo",
+DlgLnkProtoOther : "<otro>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Seleccionar una referencia",
+DlgLnkAnchorByName : "Por Nombre de Referencia",
+DlgLnkAnchorById : "Por ID de elemento",
+DlgLnkNoAnchors : "(No hay referencias disponibles en el documento)",
+DlgLnkEMail : "Dirección de E-Mail",
+DlgLnkEMailSubject : "Título del Mensaje",
+DlgLnkEMailBody : "Cuerpo del Mensaje",
+DlgLnkUpload : "Cargar",
+DlgLnkBtnUpload : "Enviar al Servidor",
+
+DlgLnkTarget : "Destino",
+DlgLnkTargetFrame : "<marco>",
+DlgLnkTargetPopup : "<ventana emergente>",
+DlgLnkTargetBlank : "Nueva Ventana(_blank)",
+DlgLnkTargetParent : "Ventana Padre (_parent)",
+DlgLnkTargetSelf : "Misma Ventana (_self)",
+DlgLnkTargetTop : "Ventana primaria (_top)",
+DlgLnkTargetFrameName : "Nombre del Marco Destino",
+DlgLnkPopWinName : "Nombre de Ventana Emergente",
+DlgLnkPopWinFeat : "Características de Ventana Emergente",
+DlgLnkPopResize : "Ajustable",
+DlgLnkPopLocation : "Barra de ubicación",
+DlgLnkPopMenu : "Barra de Menú",
+DlgLnkPopScroll : "Barras de desplazamiento",
+DlgLnkPopStatus : "Barra de Estado",
+DlgLnkPopToolbar : "Barra de Herramientas",
+DlgLnkPopFullScrn : "Pantalla Completa (IE)",
+DlgLnkPopDependent : "Dependiente (Netscape)",
+DlgLnkPopWidth : "Anchura",
+DlgLnkPopHeight : "Altura",
+DlgLnkPopLeft : "Posición Izquierda",
+DlgLnkPopTop : "Posición Derecha",
+
+DlnLnkMsgNoUrl : "Por favor tipee el vínculo URL",
+DlnLnkMsgNoEMail : "Por favor tipee la dirección de e-mail",
+DlnLnkMsgNoAnchor : "Por favor seleccione una referencia",
+DlnLnkMsgInvPopName : "El nombre debe empezar con un caracter alfanumérico y no debe contener espacios",
+
+// Color Dialog
+DlgColorTitle : "Seleccionar Color",
+DlgColorBtnClear : "Ninguno",
+DlgColorHighlight : "Resaltado",
+DlgColorSelected : "Seleccionado",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insertar un Emoticon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Seleccione un caracter especial",
+
+// Table Dialog
+DlgTableTitle : "Propiedades de Tabla",
+DlgTableRows : "Filas",
+DlgTableColumns : "Columnas",
+DlgTableBorder : "Tamaño de Borde",
+DlgTableAlign : "Alineación",
+DlgTableAlignNotSet : "<No establecido>",
+DlgTableAlignLeft : "Izquierda",
+DlgTableAlignCenter : "Centrado",
+DlgTableAlignRight : "Derecha",
+DlgTableWidth : "Anchura",
+DlgTableWidthPx : "pixeles",
+DlgTableWidthPc : "porcentaje",
+DlgTableHeight : "Altura",
+DlgTableCellSpace : "Esp. e/celdas",
+DlgTableCellPad : "Esp. interior",
+DlgTableCaption : "Título",
+DlgTableSummary : "Síntesis",
+DlgTableHeaders : "Encabezados",
+DlgTableHeadersNone : "Ninguno",
+DlgTableHeadersColumn : "Primera columna",
+DlgTableHeadersRow : "Primera fila",
+DlgTableHeadersBoth : "Ambas",
+
+// Table Cell Dialog
+DlgCellTitle : "Propiedades de Celda",
+DlgCellWidth : "Anchura",
+DlgCellWidthPx : "pixeles",
+DlgCellWidthPc : "porcentaje",
+DlgCellHeight : "Altura",
+DlgCellWordWrap : "Cortar Línea",
+DlgCellWordWrapNotSet : "<No establecido>",
+DlgCellWordWrapYes : "Si",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Alineación Horizontal",
+DlgCellHorAlignNotSet : "<No establecido>",
+DlgCellHorAlignLeft : "Izquierda",
+DlgCellHorAlignCenter : "Centrado",
+DlgCellHorAlignRight: "Derecha",
+DlgCellVerAlign : "Alineación Vertical",
+DlgCellVerAlignNotSet : "<Not establecido>",
+DlgCellVerAlignTop : "Tope",
+DlgCellVerAlignMiddle : "Medio",
+DlgCellVerAlignBottom : "ie",
+DlgCellVerAlignBaseline : "Línea de Base",
+DlgCellType : "Tipo de celda",
+DlgCellTypeData : "Datos",
+DlgCellTypeHeader : "Encabezado",
+DlgCellRowSpan : "Abarcar Filas",
+DlgCellCollSpan : "Abarcar Columnas",
+DlgCellBackColor : "Color de Fondo",
+DlgCellBorderColor : "Color de Borde",
+DlgCellBtnSelect : "Seleccione...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Buscar y Reemplazar",
+
+// Find Dialog
+DlgFindTitle : "Buscar",
+DlgFindFindBtn : "Buscar",
+DlgFindNotFoundMsg : "El texto especificado no ha sido encontrado.",
+
+// Replace Dialog
+DlgReplaceTitle : "Reemplazar",
+DlgReplaceFindLbl : "Texto a buscar:",
+DlgReplaceReplaceLbl : "Reemplazar con:",
+DlgReplaceCaseChk : "Coincidir may/min",
+DlgReplaceReplaceBtn : "Reemplazar",
+DlgReplaceReplAllBtn : "Reemplazar Todo",
+DlgReplaceWordChk : "Coincidir toda la palabra",
+
+// Paste Operations / Dialog
+PasteErrorCut : "La configuración de seguridad de este navegador no permite la ejecución automática de operaciones de cortado. Por favor use el teclado (Ctrl+X).",
+PasteErrorCopy : "La configuración de seguridad de este navegador no permite la ejecución automática de operaciones de copiado. Por favor use el teclado (Ctrl+C).",
+
+PasteAsText : "Pegar como Texto Plano",
+PasteFromWord : "Pegar desde Word",
+
+DlgPasteMsg2 : "Por favor pegue dentro del cuadro utilizando el teclado (<STRONG>Ctrl+V</STRONG>); luego presione <STRONG>OK</STRONG>.",
+DlgPasteSec : "Debido a la configuración de seguridad de su navegador, el editor no tiene acceso al portapapeles. Es necesario que lo pegue de nuevo en esta ventana.",
+DlgPasteIgnoreFont : "Ignorar definiciones de fuentes",
+DlgPasteRemoveStyles : "Remover definiciones de estilo",
+
+// Color Picker
+ColorAutomatic : "Automático",
+ColorMoreColors : "Más Colores...",
+
+// Document Properties
+DocProps : "Propiedades del Documento",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propiedades de la Referencia",
+DlgAnchorName : "Nombre de la Referencia",
+DlgAnchorErrorName : "Por favor, complete el nombre de la Referencia",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "No se encuentra en el Diccionario",
+DlgSpellChangeTo : "Cambiar a",
+DlgSpellBtnIgnore : "Ignorar",
+DlgSpellBtnIgnoreAll : "Ignorar Todo",
+DlgSpellBtnReplace : "Reemplazar",
+DlgSpellBtnReplaceAll : "Reemplazar Todo",
+DlgSpellBtnUndo : "Deshacer",
+DlgSpellNoSuggestions : "- No hay sugerencias -",
+DlgSpellProgress : "Control de Ortografía en progreso...",
+DlgSpellNoMispell : "Control finalizado: no se encontraron errores",
+DlgSpellNoChanges : "Control finalizado: no se ha cambiado ninguna palabra",
+DlgSpellOneChange : "Control finalizado: se ha cambiado una palabra",
+DlgSpellManyChanges : "Control finalizado: se ha cambiado %1 palabras",
+
+IeSpellDownload : "Módulo de Control de Ortografía no instalado. ¿Desea descargarlo ahora?",
+
+// Button Dialog
+DlgButtonText : "Texto (Valor)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Boton",
+DlgButtonTypeSbm : "Enviar",
+DlgButtonTypeRst : "Reestablecer",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nombre",
+DlgCheckboxValue : "Valor",
+DlgCheckboxSelected : "Seleccionado",
+
+// Form Dialog
+DlgFormName : "Nombre",
+DlgFormAction : "Acción",
+DlgFormMethod : "Método",
+
+// Select Field Dialog
+DlgSelectName : "Nombre",
+DlgSelectValue : "Valor",
+DlgSelectSize : "Tamaño",
+DlgSelectLines : "Lineas",
+DlgSelectChkMulti : "Permitir múltiple selección",
+DlgSelectOpAvail : "Opciones disponibles",
+DlgSelectOpText : "Texto",
+DlgSelectOpValue : "Valor",
+DlgSelectBtnAdd : "Agregar",
+DlgSelectBtnModify : "Modificar",
+DlgSelectBtnUp : "Subir",
+DlgSelectBtnDown : "Bajar",
+DlgSelectBtnSetValue : "Establecer como predeterminado",
+DlgSelectBtnDelete : "Eliminar",
+
+// Textarea Dialog
+DlgTextareaName : "Nombre",
+DlgTextareaCols : "Columnas",
+DlgTextareaRows : "Filas",
+
+// Text Field Dialog
+DlgTextName : "Nombre",
+DlgTextValue : "Valor",
+DlgTextCharWidth : "Caracteres de ancho",
+DlgTextMaxChars : "Máximo caracteres",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Texto",
+DlgTextTypePass : "Contraseña",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nombre",
+DlgHiddenValue : "Valor",
+
+// Bulleted List Dialog
+BulletedListProp : "Propiedades de Viñetas",
+NumberedListProp : "Propiedades de Numeraciones",
+DlgLstStart : "Inicio",
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Círculo",
+DlgLstTypeDisc : "Disco",
+DlgLstTypeSquare : "Cuadrado",
+DlgLstTypeNumbers : "Números (1, 2, 3)",
+DlgLstTypeLCase : "letras en minúsculas (a, b, c)",
+DlgLstTypeUCase : "letras en mayúsculas (A, B, C)",
+DlgLstTypeSRoman : "Números Romanos (i, ii, iii)",
+DlgLstTypeLRoman : "Números Romanos (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Fondo",
+DlgDocColorsTab : "Colores y Márgenes",
+DlgDocMetaTab : "Meta Información",
+
+DlgDocPageTitle : "Título de Página",
+DlgDocLangDir : "Orientación de idioma",
+DlgDocLangDirLTR : "Izq. a Derecha (LTR)",
+DlgDocLangDirRTL : "Der. a Izquierda (RTL)",
+DlgDocLangCode : "Código de Idioma",
+DlgDocCharSet : "Codif. de Conjunto de Caracteres",
+DlgDocCharSetCE : "Centro Europeo",
+DlgDocCharSetCT : "Chino Tradicional (Big5)",
+DlgDocCharSetCR : "Cirílico",
+DlgDocCharSetGR : "Griego",
+DlgDocCharSetJP : "Japonés",
+DlgDocCharSetKR : "Coreano",
+DlgDocCharSetTR : "Turco",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Europeo occidental",
+DlgDocCharSetOther : "Otra Codificación",
+
+DlgDocDocType : "Encabezado de Tipo de Documento",
+DlgDocDocTypeOther : "Otro Encabezado",
+DlgDocIncXHTML : "Incluir Declaraciones XHTML",
+DlgDocBgColor : "Color de Fondo",
+DlgDocBgImage : "URL de Imagen de Fondo",
+DlgDocBgNoScroll : "Fondo sin rolido",
+DlgDocCText : "Texto",
+DlgDocCLink : "Vínculo",
+DlgDocCVisited : "Vínculo Visitado",
+DlgDocCActive : "Vínculo Activo",
+DlgDocMargins : "Márgenes de Página",
+DlgDocMaTop : "Tope",
+DlgDocMaLeft : "Izquierda",
+DlgDocMaRight : "Derecha",
+DlgDocMaBottom : "Pie",
+DlgDocMeIndex : "Claves de indexación del Documento (separados por comas)",
+DlgDocMeDescr : "Descripción del Documento",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Vista Previa",
+
+// Templates Dialog
+Templates : "Plantillas",
+DlgTemplatesTitle : "Contenido de Plantillas",
+DlgTemplatesSelMsg : "Por favor selecciona la plantilla a abrir en el editor<br>(el contenido actual se perderá):",
+DlgTemplatesLoading : "Cargando lista de Plantillas. Por favor, aguarde...",
+DlgTemplatesNoTpl : "(No hay plantillas definidas)",
+DlgTemplatesReplace : "Reemplazar el contenido actual",
+
+// About Dialog
+DlgAboutAboutTab : "Acerca de",
+DlgAboutBrowserInfoTab : "Información de Navegador",
+DlgAboutLicenseTab : "Licencia",
+DlgAboutVersion : "versión",
+DlgAboutInfo : "Para mayor información por favor dirigirse a",
+
+// Div Dialog
+DlgDivGeneralTab : "General",
+DlgDivAdvancedTab : "Avanzado",
+DlgDivStyle : "Estilo",
+DlgDivInlineStyle : "Estilos CSS",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/et.js b/httemplate/elements/fckeditor/editor/lang/et.js
new file mode 100644
index 0000000..3756cad
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/et.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Estonian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Voldi tööriistariba",
+ToolbarExpand : "Laienda tööriistariba",
+
+// Toolbar Items and Context Menu
+Save : "Salvesta",
+NewPage : "Uus leht",
+Preview : "Eelvaade",
+Cut : "Lõika",
+Copy : "Kopeeri",
+Paste : "Kleebi",
+PasteText : "Kleebi tavalise tekstina",
+PasteWord : "Kleebi Wordist",
+Print : "Prindi",
+SelectAll : "Vali kõik",
+RemoveFormat : "Eemalda vorming",
+InsertLinkLbl : "Link",
+InsertLink : "Sisesta link / Muuda linki",
+RemoveLink : "Eemalda link",
+VisitLink : "Open Link", //MISSING
+Anchor : "Sisesta ankur / Muuda ankrut",
+AnchorDelete : "Eemalda ankur",
+InsertImageLbl : "Pilt",
+InsertImage : "Sisesta pilt / Muuda pilti",
+InsertFlashLbl : "Flash",
+InsertFlash : "Sisesta flash / Muuda flashi",
+InsertTableLbl : "Tabel",
+InsertTable : "Sisesta tabel / Muuda tabelit",
+InsertLineLbl : "Joon",
+InsertLine : "Sisesta horisontaaljoon",
+InsertSpecialCharLbl: "Erimärgid",
+InsertSpecialChar : "Sisesta erimärk",
+InsertSmileyLbl : "Emotikon",
+InsertSmiley : "Sisesta emotikon",
+About : "FCKeditor teave",
+Bold : "Paks",
+Italic : "Kursiiv",
+Underline : "Allajoonitud",
+StrikeThrough : "Läbijoonitud",
+Subscript : "Allindeks",
+Superscript : "Ãœlaindeks",
+LeftJustify : "Vasakjoondus",
+CenterJustify : "Keskjoondus",
+RightJustify : "Paremjoondus",
+BlockJustify : "Rööpjoondus",
+DecreaseIndent : "Vähenda taanet",
+IncreaseIndent : "Suurenda taanet",
+Blockquote : "Blokktsitaat",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Võta tagasi",
+Redo : "Korda toimingut",
+NumberedListLbl : "Nummerdatud loetelu",
+NumberedList : "Sisesta/Eemalda nummerdatud loetelu",
+BulletedListLbl : "Punktiseeritud loetelu",
+BulletedList : "Sisesta/Eemalda punktiseeritud loetelu",
+ShowTableBorders : "Näita tabeli jooni",
+ShowDetails : "Näita üksikasju",
+Style : "Laad",
+FontFormat : "Vorming",
+Font : "Kiri",
+FontSize : "Suurus",
+TextColor : "Teksti värv",
+BGColor : "Tausta värv",
+Source : "Lähtekood",
+Find : "Otsi",
+Replace : "Asenda",
+SpellCheck : "Kontrolli õigekirja",
+UniversalKeyboard : "Universaalne klaviatuur",
+PageBreakLbl : "Lehepiir",
+PageBreak : "Sisesta lehevahetuskoht",
+
+Form : "Vorm",
+Checkbox : "Märkeruut",
+RadioButton : "Raadionupp",
+TextField : "Tekstilahter",
+Textarea : "Tekstiala",
+HiddenField : "Varjatud lahter",
+Button : "Nupp",
+SelectionField : "Valiklahter",
+ImageButton : "Piltnupp",
+
+FitWindow : "Maksimeeri redaktori mõõtmed",
+ShowBlocks : "Näita blokke",
+
+// Context Menu
+EditLink : "Muuda linki",
+CellCM : "Lahter",
+RowCM : "Rida",
+ColumnCM : "Veerg",
+InsertRowAfter : "Sisesta rida peale",
+InsertRowBefore : "Sisesta rida enne",
+DeleteRows : "Eemalda read",
+InsertColumnAfter : "Sisesta veerg peale",
+InsertColumnBefore : "Sisesta veerg enne",
+DeleteColumns : "Eemalda veerud",
+InsertCellAfter : "Sisesta lahter peale",
+InsertCellBefore : "Sisesta lahter enne",
+DeleteCells : "Eemalda lahtrid",
+MergeCells : "Ãœhenda lahtrid",
+MergeRight : "Ãœhenda paremale",
+MergeDown : "Ãœhenda alla",
+HorizontalSplitCell : "Poolita lahter horisontaalselt",
+VerticalSplitCell : "Poolita lahter vertikaalselt",
+TableDelete : "Kustuta tabel",
+CellProperties : "Lahtri atribuudid",
+TableProperties : "Tabeli atribuudid",
+ImageProperties : "Pildi atribuudid",
+FlashProperties : "Flash omadused",
+
+AnchorProp : "Ankru omadused",
+ButtonProp : "Nupu omadused",
+CheckboxProp : "Märkeruudu omadused",
+HiddenFieldProp : "Varjatud lahtri omadused",
+RadioButtonProp : "Raadionupu omadused",
+ImageButtonProp : "Piltnupu omadused",
+TextFieldProp : "Tekstilahtri omadused",
+SelectionFieldProp : "Valiklahtri omadused",
+TextareaProp : "Tekstiala omadused",
+FormProp : "Vormi omadused",
+
+FontFormats : "Tavaline;Vormindatud;Aadress;Pealkiri 1;Pealkiri 2;Pealkiri 3;Pealkiri 4;Pealkiri 5;Pealkiri 6;Tavaline (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Töötlen XHTML'i. Palun oota...",
+Done : "Tehtud",
+PasteWordConfirm : "Tekst, mida soovid lisada paistab pärinevat Word'ist. Kas soovid seda enne kleepimist puhastada?",
+NotCompatiblePaste : "See käsk on saadaval ainult Internet Explorer versioon 5.5 või uuema puhul. Kas soovid kleepida ilma puhastamata?",
+UnknownToolbarItem : "Tundmatu tööriistarea üksus \"%1\"",
+UnknownCommand : "Tundmatu käsunimi \"%1\"",
+NotImplemented : "Käsku ei täidetud",
+UnknownToolbarSet : "Tööriistariba \"%1\" ei eksisteeri",
+NoActiveX : "Sinu veebisirvija turvalisuse seaded võivad limiteerida mõningaid tekstirdaktori kasutusvõimalusi. Sa peaksid võimaldama valiku \"Run ActiveX controls and plug-ins\" oma veebisirvija seadetes. Muidu võid sa täheldada vigu tekstiredaktori töös ja märgata puuduvaid funktsioone.",
+BrowseServerBlocked : "Ressursside sirvija avamine ebaõnnestus. Võimalda pop-up akende avanemine.",
+DialogBlocked : "Ei olenud võimalik avada dialoogi akent. Võimalda pop-up akende avanemine.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Loobu",
+DlgBtnClose : "Sulge",
+DlgBtnBrowseServer : "Sirvi serverit",
+DlgAdvancedTag : "Täpsemalt",
+DlgOpOther : "<Teine>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Palun sisesta URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<määramata>",
+DlgGenId : "Id",
+DlgGenLangDir : "Keele suund",
+DlgGenLangDirLtr : "Vasakult paremale (LTR)",
+DlgGenLangDirRtl : "Paremalt vasakule (RTL)",
+DlgGenLangCode : "Keele kood",
+DlgGenAccessKey : "Juurdepääsu võti",
+DlgGenName : "Nimi",
+DlgGenTabIndex : "Tab indeks",
+DlgGenLongDescr : "Pikk kirjeldus URL",
+DlgGenClass : "Stiilistiku klassid",
+DlgGenTitle : "Juhendav tiitel",
+DlgGenContType : "Juhendava sisu tüüp",
+DlgGenLinkCharset : "Lingitud ressurssi märgistik",
+DlgGenStyle : "Laad",
+
+// Image Dialog
+DlgImgTitle : "Pildi atribuudid",
+DlgImgInfoTab : "Pildi info",
+DlgImgBtnUpload : "Saada serverissee",
+DlgImgURL : "URL",
+DlgImgUpload : "Lae üles",
+DlgImgAlt : "Alternatiivne tekst",
+DlgImgWidth : "Laius",
+DlgImgHeight : "Kõrgus",
+DlgImgLockRatio : "Lukusta kuvasuhe",
+DlgBtnResetSize : "Lähtesta suurus",
+DlgImgBorder : "Joon",
+DlgImgHSpace : "H. vaheruum",
+DlgImgVSpace : "V. vaheruum",
+DlgImgAlign : "Joondus",
+DlgImgAlignLeft : "Vasak",
+DlgImgAlignAbsBottom: "Abs alla",
+DlgImgAlignAbsMiddle: "Abs keskele",
+DlgImgAlignBaseline : "Baasjoonele",
+DlgImgAlignBottom : "Alla",
+DlgImgAlignMiddle : "Keskele",
+DlgImgAlignRight : "Paremale",
+DlgImgAlignTextTop : "Tekstit üles",
+DlgImgAlignTop : "Ãœles",
+DlgImgPreview : "Eelvaade",
+DlgImgAlertUrl : "Palun kirjuta pildi URL",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash omadused",
+DlgFlashChkPlay : "Automaatne start ",
+DlgFlashChkLoop : "Korduv",
+DlgFlashChkMenu : "Võimalda flash menüü",
+DlgFlashScale : "Mastaap",
+DlgFlashScaleAll : "Näita kõike",
+DlgFlashScaleNoBorder : "Äärist ei ole",
+DlgFlashScaleFit : "Täpne sobivus",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Lingi info",
+DlgLnkTargetTab : "Sihtkoht",
+
+DlgLnkType : "Lingi tüüp",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ankur sellel lehel",
+DlgLnkTypeEMail : "E-post",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<muu>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Vali ankur",
+DlgLnkAnchorByName : "Ankru nime järgi",
+DlgLnkAnchorById : "Elemendi id järgi",
+DlgLnkNoAnchors : "(Selles dokumendis ei ole ankruid)",
+DlgLnkEMail : "E-posti aadress",
+DlgLnkEMailSubject : "Sõnumi teema",
+DlgLnkEMailBody : "Sõnumi tekst",
+DlgLnkUpload : "Lae üles",
+DlgLnkBtnUpload : "Saada serverisse",
+
+DlgLnkTarget : "Sihtkoht",
+DlgLnkTargetFrame : "<raam>",
+DlgLnkTargetPopup : "<hüpikaken>",
+DlgLnkTargetBlank : "Uus aken (_blank)",
+DlgLnkTargetParent : "Esivanem aken (_parent)",
+DlgLnkTargetSelf : "Sama aken (_self)",
+DlgLnkTargetTop : "Pealmine aken (_top)",
+DlgLnkTargetFrameName : "Sihtmärk raami nimi",
+DlgLnkPopWinName : "Hüpikakna nimi",
+DlgLnkPopWinFeat : "Hüpikakna omadused",
+DlgLnkPopResize : "Suurendatav",
+DlgLnkPopLocation : "Aadressiriba",
+DlgLnkPopMenu : "Menüüriba",
+DlgLnkPopScroll : "Kerimisribad",
+DlgLnkPopStatus : "Olekuriba",
+DlgLnkPopToolbar : "Tööriistariba",
+DlgLnkPopFullScrn : "Täisekraan (IE)",
+DlgLnkPopDependent : "Sõltuv (Netscape)",
+DlgLnkPopWidth : "Laius",
+DlgLnkPopHeight : "Kõrgus",
+DlgLnkPopLeft : "Vasak asukoht",
+DlgLnkPopTop : "Ãœlemine asukoht",
+
+DlnLnkMsgNoUrl : "Palun kirjuta lingi URL",
+DlnLnkMsgNoEMail : "Palun kirjuta E-Posti aadress",
+DlnLnkMsgNoAnchor : "Palun vali ankur",
+DlnLnkMsgInvPopName : "Hüpikakna nimi peab algama alfabeetilise tähega ja ei tohi sisaldada tühikuid",
+
+// Color Dialog
+DlgColorTitle : "Vali värv",
+DlgColorBtnClear : "Tühjenda",
+DlgColorHighlight : "Märgi",
+DlgColorSelected : "Valitud",
+
+// Smiley Dialog
+DlgSmileyTitle : "Sisesta emotikon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Vali erimärk",
+
+// Table Dialog
+DlgTableTitle : "Tabeli atribuudid",
+DlgTableRows : "Read",
+DlgTableColumns : "Veerud",
+DlgTableBorder : "Joone suurus",
+DlgTableAlign : "Joondus",
+DlgTableAlignNotSet : "<Määramata>",
+DlgTableAlignLeft : "Vasak",
+DlgTableAlignCenter : "Kesk",
+DlgTableAlignRight : "Parem",
+DlgTableWidth : "Laius",
+DlgTableWidthPx : "pikslit",
+DlgTableWidthPc : "protsenti",
+DlgTableHeight : "Kõrgus",
+DlgTableCellSpace : "Lahtri vahe",
+DlgTableCellPad : "Lahtri täidis",
+DlgTableCaption : "Tabeli tiitel",
+DlgTableSummary : "Kokkuvõte",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Lahtri atribuudid",
+DlgCellWidth : "Laius",
+DlgCellWidthPx : "pikslit",
+DlgCellWidthPc : "protsenti",
+DlgCellHeight : "Kõrgus",
+DlgCellWordWrap : "Sõna ülekanne",
+DlgCellWordWrapNotSet : "<Määramata>",
+DlgCellWordWrapYes : "Jah",
+DlgCellWordWrapNo : "Ei",
+DlgCellHorAlign : "Horisontaaljoondus",
+DlgCellHorAlignNotSet : "<Määramata>",
+DlgCellHorAlignLeft : "Vasak",
+DlgCellHorAlignCenter : "Kesk",
+DlgCellHorAlignRight: "Parem",
+DlgCellVerAlign : "Vertikaaljoondus",
+DlgCellVerAlignNotSet : "<Määramata>",
+DlgCellVerAlignTop : "Ãœles",
+DlgCellVerAlignMiddle : "Keskele",
+DlgCellVerAlignBottom : "Alla",
+DlgCellVerAlignBaseline : "Baasjoonele",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Reaulatus",
+DlgCellCollSpan : "Veeruulatus",
+DlgCellBackColor : "Tausta värv",
+DlgCellBorderColor : "Joone värv",
+DlgCellBtnSelect : "Vali...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Otsi ja asenda",
+
+// Find Dialog
+DlgFindTitle : "Otsi",
+DlgFindFindBtn : "Otsi",
+DlgFindNotFoundMsg : "Valitud teksti ei leitud.",
+
+// Replace Dialog
+DlgReplaceTitle : "Asenda",
+DlgReplaceFindLbl : "Leia mida:",
+DlgReplaceReplaceLbl : "Asenda millega:",
+DlgReplaceCaseChk : "Erista suur- ja väiketähti",
+DlgReplaceReplaceBtn : "Asenda",
+DlgReplaceReplAllBtn : "Asenda kõik",
+DlgReplaceWordChk : "Otsi terviklike sõnu",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt lõigata. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl+X).",
+PasteErrorCopy : "Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt kopeerida. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl+C).",
+
+PasteAsText : "Kleebi tavalise tekstina",
+PasteFromWord : "Kleebi Wordist",
+
+DlgPasteMsg2 : "Palun kleebi järgnevasse kasti kasutades klaviatuuri klahvikombinatsiooni (<STRONG>Ctrl+V</STRONG>) ja vajuta seejärel <STRONG>OK</STRONG>.",
+DlgPasteSec : "Sinu veebisirvija turvaseadete tõttu, ei oma redaktor otsest ligipääsu lõikelaua andmetele. Sa pead kleepima need uuesti siia aknasse.",
+DlgPasteIgnoreFont : "Ignoreeri kirja definitsioone",
+DlgPasteRemoveStyles : "Eemalda stiilide definitsioonid",
+
+// Color Picker
+ColorAutomatic : "Automaatne",
+ColorMoreColors : "Rohkem värve...",
+
+// Document Properties
+DocProps : "Dokumendi omadused",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankru omadused",
+DlgAnchorName : "Ankru nimi",
+DlgAnchorErrorName : "Palun sisest ankru nimi",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Puudub sõnastikust",
+DlgSpellChangeTo : "Muuda",
+DlgSpellBtnIgnore : "Ignoreeri",
+DlgSpellBtnIgnoreAll : "Ignoreeri kõiki",
+DlgSpellBtnReplace : "Asenda",
+DlgSpellBtnReplaceAll : "Asenda kõik",
+DlgSpellBtnUndo : "Võta tagasi",
+DlgSpellNoSuggestions : "- Soovitused puuduvad -",
+DlgSpellProgress : "Toimub õigekirja kontroll...",
+DlgSpellNoMispell : "Õigekirja kontroll sooritatud: õigekirjuvigu ei leitud",
+DlgSpellNoChanges : "Õigekirja kontroll sooritatud: ühtegi sõna ei muudetud",
+DlgSpellOneChange : "Õigekirja kontroll sooritatud: üks sõna muudeti",
+DlgSpellManyChanges : "Õigekirja kontroll sooritatud: %1 sõna muudetud",
+
+IeSpellDownload : "Õigekirja kontrollija ei ole installeeritud. Soovid sa selle alla laadida?",
+
+// Button Dialog
+DlgButtonText : "Tekst (väärtus)",
+DlgButtonType : "Tüüp",
+DlgButtonTypeBtn : "Nupp",
+DlgButtonTypeSbm : "Saada",
+DlgButtonTypeRst : "Lähtesta",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nimi",
+DlgCheckboxValue : "Väärtus",
+DlgCheckboxSelected : "Valitud",
+
+// Form Dialog
+DlgFormName : "Nimi",
+DlgFormAction : "Toiming",
+DlgFormMethod : "Meetod",
+
+// Select Field Dialog
+DlgSelectName : "Nimi",
+DlgSelectValue : "Väärtus",
+DlgSelectSize : "Suurus",
+DlgSelectLines : "ridu",
+DlgSelectChkMulti : "Võimalda mitu valikut",
+DlgSelectOpAvail : "Võimalikud valikud",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Väärtus",
+DlgSelectBtnAdd : "Lisa",
+DlgSelectBtnModify : "Muuda",
+DlgSelectBtnUp : "Ãœles",
+DlgSelectBtnDown : "Alla",
+DlgSelectBtnSetValue : "Sea valitud olekuna",
+DlgSelectBtnDelete : "Kustuta",
+
+// Textarea Dialog
+DlgTextareaName : "Nimi",
+DlgTextareaCols : "Veerge",
+DlgTextareaRows : "Ridu",
+
+// Text Field Dialog
+DlgTextName : "Nimi",
+DlgTextValue : "Väärtus",
+DlgTextCharWidth : "Laius (tähemärkides)",
+DlgTextMaxChars : "Maksimaalselt tähemärke",
+DlgTextType : "Tüüp",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Parool",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nimi",
+DlgHiddenValue : "Väärtus",
+
+// Bulleted List Dialog
+BulletedListProp : "Täpitud loetelu omadused",
+NumberedListProp : "Nummerdatud loetelu omadused",
+DlgLstStart : "Alusta",
+DlgLstType : "Tüüp",
+DlgLstTypeCircle : "Ring",
+DlgLstTypeDisc : "Ketas",
+DlgLstTypeSquare : "Ruut",
+DlgLstTypeNumbers : "Numbrid (1, 2, 3)",
+DlgLstTypeLCase : "Väiketähed (a, b, c)",
+DlgLstTypeUCase : "Suurtähed (A, B, C)",
+DlgLstTypeSRoman : "Väiksed Rooma numbrid (i, ii, iii)",
+DlgLstTypeLRoman : "Suured Rooma numbrid (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Ãœldine",
+DlgDocBackTab : "Taust",
+DlgDocColorsTab : "Värvid ja veerised",
+DlgDocMetaTab : "Meta andmed",
+
+DlgDocPageTitle : "Lehekülje tiitel",
+DlgDocLangDir : "Kirja suund",
+DlgDocLangDirLTR : "Vasakult paremale (LTR)",
+DlgDocLangDirRTL : "Paremalt vasakule (RTL)",
+DlgDocLangCode : "Keele kood",
+DlgDocCharSet : "Märgistiku kodeering",
+DlgDocCharSetCE : "Kesk-Euroopa",
+DlgDocCharSetCT : "Hiina traditsiooniline (Big5)",
+DlgDocCharSetCR : "Kirillisa",
+DlgDocCharSetGR : "Kreeka",
+DlgDocCharSetJP : "Jaapani",
+DlgDocCharSetKR : "Korea",
+DlgDocCharSetTR : "Türgi",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Lääne-Euroopa",
+DlgDocCharSetOther : "Ülejäänud märgistike kodeeringud",
+
+DlgDocDocType : "Dokumendi tüüppäis",
+DlgDocDocTypeOther : "Teised dokumendi tüüppäised",
+DlgDocIncXHTML : "Arva kaasa XHTML deklaratsioonid",
+DlgDocBgColor : "Taustavärv",
+DlgDocBgImage : "Taustapildi URL",
+DlgDocBgNoScroll : "Mittekeritav tagataust",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Külastatud link",
+DlgDocCActive : "Aktiivne link",
+DlgDocMargins : "Lehekülje äärised",
+DlgDocMaTop : "Ãœlaserv",
+DlgDocMaLeft : "Vasakserv",
+DlgDocMaRight : "Paremserv",
+DlgDocMaBottom : "Alaserv",
+DlgDocMeIndex : "Dokumendi võtmesõnad (eraldatud komadega)",
+DlgDocMeDescr : "Dokumendi kirjeldus",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Autoriõigus",
+DlgDocPreview : "Eelvaade",
+
+// Templates Dialog
+Templates : "Å abloon",
+DlgTemplatesTitle : "Sisu Å¡abloonid",
+DlgTemplatesSelMsg : "Palun vali šabloon, et avada see redaktoris<br />(praegune sisu läheb kaotsi):",
+DlgTemplatesLoading : "Laen Å¡abloonide nimekirja. Palun oota...",
+DlgTemplatesNoTpl : "(Ãœhtegi Å¡ablooni ei ole defineeritud)",
+DlgTemplatesReplace : "Asenda tegelik sisu",
+
+// About Dialog
+DlgAboutAboutTab : "Teave",
+DlgAboutBrowserInfoTab : "Veebisirvija info",
+DlgAboutLicenseTab : "Litsents",
+DlgAboutVersion : "versioon",
+DlgAboutInfo : "Täpsema info saamiseks mine",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/eu.js b/httemplate/elements/fckeditor/editor/lang/eu.js
new file mode 100644
index 0000000..75f91a5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/eu.js
@@ -0,0 +1,540 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Basque language file.
+ * Euskara hizkuntza fitxategia.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Estutu Tresna Barra",
+ToolbarExpand : "Hedatu Tresna Barra",
+
+// Toolbar Items and Context Menu
+Save : "Gorde",
+NewPage : "Orrialde Berria",
+Preview : "Aurrebista",
+Cut : "Ebaki",
+Copy : "Kopiatu",
+Paste : "Itsatsi",
+PasteText : "Itsatsi testu bezala",
+PasteWord : "Itsatsi Word-etik",
+Print : "Inprimatu",
+SelectAll : "Hautatu dena",
+RemoveFormat : "Kendu Formatua",
+InsertLinkLbl : "Esteka",
+InsertLink : "Txertatu/Editatu Esteka",
+RemoveLink : "Kendu Esteka",
+VisitLink : "Ireki Esteka",
+Anchor : "Aingura",
+AnchorDelete : "Ezabatu Aingura",
+InsertImageLbl : "Irudia",
+InsertImage : "Txertatu/Editatu Irudia",
+InsertFlashLbl : "Flasha",
+InsertFlash : "Txertatu/Editatu Flasha",
+InsertTableLbl : "Taula",
+InsertTable : "Txertatu/Editatu Taula",
+InsertLineLbl : "Lerroa",
+InsertLine : "Txertatu Marra Horizontala",
+InsertSpecialCharLbl: "Karaktere Berezia",
+InsertSpecialChar : "Txertatu Karaktere Berezia",
+InsertSmileyLbl : "Aurpegierak",
+InsertSmiley : "Txertatu Aurpegierak",
+About : "FCKeditor-ri buruz",
+Bold : "Lodia",
+Italic : "Etzana",
+Underline : "Azpimarratu",
+StrikeThrough : "Marratua",
+Subscript : "Azpi-indize",
+Superscript : "Goi-indize",
+LeftJustify : "Lerrokatu Ezkerrean",
+CenterJustify : "Lerrokatu Erdian",
+RightJustify : "Lerrokatu Eskuman",
+BlockJustify : "Justifikatu",
+DecreaseIndent : "Txikitu Koska",
+IncreaseIndent : "Handitu Koska",
+Blockquote : "Aipamen blokea",
+CreateDiv : "Sortu Div Edukitzailea",
+EditDiv : "Editatu Div Edukitzailea",
+DeleteDiv : "Ezabatu Div Edukitzailea",
+Undo : "Desegin",
+Redo : "Berregin",
+NumberedListLbl : "Zenbakidun Zerrenda",
+NumberedList : "Txertatu/Kendu Zenbakidun zerrenda",
+BulletedListLbl : "Buletdun Zerrenda",
+BulletedList : "Txertatu/Kendu Buletdun zerrenda",
+ShowTableBorders : "Erakutsi Taularen Ertzak",
+ShowDetails : "Erakutsi Xehetasunak",
+Style : "Estiloa",
+FontFormat : "Formatua",
+Font : "Letra-tipoa",
+FontSize : "Tamaina",
+TextColor : "Testu Kolorea",
+BGColor : "Atzeko kolorea",
+Source : "HTML Iturburua",
+Find : "Bilatu",
+Replace : "Ordezkatu",
+SpellCheck : "Ortografia",
+UniversalKeyboard : "Teklatu Unibertsala",
+PageBreakLbl : "Orrialde-jauzia",
+PageBreak : "Txertatu Orrialde-jauzia",
+
+Form : "Formularioa",
+Checkbox : "Kontrol-laukia",
+RadioButton : "Aukera-botoia",
+TextField : "Testu Eremua",
+Textarea : "Testu-area",
+HiddenField : "Ezkutuko Eremua",
+Button : "Botoia",
+SelectionField : "Hautespen Eremua",
+ImageButton : "Irudi Botoia",
+
+FitWindow : "Maximizatu editorearen tamaina",
+ShowBlocks : "Blokeak erakutsi",
+
+// Context Menu
+EditLink : "Aldatu Esteka",
+CellCM : "Gelaxka",
+RowCM : "Errenkada",
+ColumnCM : "Zutabea",
+InsertRowAfter : "Txertatu Lerroa Ostean",
+InsertRowBefore : "Txertatu Lerroa Aurretik",
+DeleteRows : "Ezabatu Errenkadak",
+InsertColumnAfter : "Txertatu Zutabea Ostean",
+InsertColumnBefore : "Txertatu Zutabea Aurretik",
+DeleteColumns : "Ezabatu Zutabeak",
+InsertCellAfter : "Txertatu Gelaxka Ostean",
+InsertCellBefore : "Txertatu Gelaxka Aurretik",
+DeleteCells : "Kendu Gelaxkak",
+MergeCells : "Batu Gelaxkak",
+MergeRight : "Elkartu Eskumara",
+MergeDown : "Elkartu Behera",
+HorizontalSplitCell : "Banatu Gelaxkak Horizontalki",
+VerticalSplitCell : "Banatu Gelaxkak Bertikalki",
+TableDelete : "Ezabatu Taula",
+CellProperties : "Gelaxkaren Ezaugarriak",
+TableProperties : "Taularen Ezaugarriak",
+ImageProperties : "Irudiaren Ezaugarriak",
+FlashProperties : "Flasharen Ezaugarriak",
+
+AnchorProp : "Ainguraren Ezaugarriak",
+ButtonProp : "Botoiaren Ezaugarriak",
+CheckboxProp : "Kontrol-laukiko Ezaugarriak",
+HiddenFieldProp : "Ezkutuko Eremuaren Ezaugarriak",
+RadioButtonProp : "Aukera-botoiaren Ezaugarriak",
+ImageButtonProp : "Irudi Botoiaren Ezaugarriak",
+TextFieldProp : "Testu Eremuaren Ezaugarriak",
+SelectionFieldProp : "Hautespen Eremuaren Ezaugarriak",
+TextareaProp : "Testu-arearen Ezaugarriak",
+FormProp : "Formularioaren Ezaugarriak",
+
+FontFormats : "Arrunta;Formateatua;Helbidea;Izenburua 1;Izenburua 2;Izenburua 3;Izenburua 4;Izenburua 5;Izenburua 6;Paragrafoa (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML Prozesatzen. Itxaron mesedez...",
+Done : "Eginda",
+PasteWordConfirm : "Itsatsi nahi duzun testua Wordetik hartua dela dirudi. Itsatsi baino lehen garbitu nahi duzu?",
+NotCompatiblePaste : "Komando hau Internet Explorer 5.5 bertsiorako edo ondorengoentzako erabilgarria dago. Garbitu gabe itsatsi nahi duzu?",
+UnknownToolbarItem : "Ataza barrako elementu ezezaguna \"%1\"",
+UnknownCommand : "Komando izen ezezaguna \"%1\"",
+NotImplemented : "Komando ez inplementatua",
+UnknownToolbarSet : "Ataza barra \"%1\" taldea ez da existitzen",
+NoActiveX : "Zure nabigatzailearen segurtasun hobespenak editore honen zenbait ezaugarri mugatu ditzake. \"ActiveX kontrolak eta pluginak\" aktibatu beharko zenituzke, bestela erroreak eta ezaugarrietan mugak egon daitezke.",
+BrowseServerBlocked : "Baliabideen arakatzailea ezin da ireki. Ziurtatu popup blokeatzaileak desgaituta dituzula.",
+DialogBlocked : "Ezin da elkarrizketa-leihoa ireki. Ziurtatu popup blokeatzaileak desgaituta dituzula.",
+VisitLinkBlocked : "Ezin da leiho berri bat ireki. Ziurtatu popup blokeatzaileak desgaituta dituzula.",
+
+// Dialogs
+DlgBtnOK : "Ados",
+DlgBtnCancel : "Utzi",
+DlgBtnClose : "Itxi",
+DlgBtnBrowseServer : "Zerbitzaria arakatu",
+DlgAdvancedTag : "Aurreratua",
+DlgOpOther : "<Bestelakoak>",
+DlgInfoTab : "Informazioa",
+DlgAlertUrl : "Mesedez URLa idatzi ezazu",
+
+// General Dialogs Labels
+DlgGenNotSet : "<Ezarri gabe>",
+DlgGenId : "Id",
+DlgGenLangDir : "Hizkuntzaren Norabidea",
+DlgGenLangDirLtr : "Ezkerretik Eskumara(LTR)",
+DlgGenLangDirRtl : "Eskumatik Ezkerrera (RTL)",
+DlgGenLangCode : "Hizkuntza Kodea",
+DlgGenAccessKey : "Sarbide-gakoa",
+DlgGenName : "Izena",
+DlgGenTabIndex : "Tabulazio Indizea",
+DlgGenLongDescr : "URL Deskribapen Luzea",
+DlgGenClass : "Estilo-orriko Klaseak",
+DlgGenTitle : "Izenburua",
+DlgGenContType : "Eduki Mota (Content Type)",
+DlgGenLinkCharset : "Estekatutako Karaktere Multzoa",
+DlgGenStyle : "Estiloa",
+
+// Image Dialog
+DlgImgTitle : "Irudi Ezaugarriak",
+DlgImgInfoTab : "Irudi informazioa",
+DlgImgBtnUpload : "Zerbitzarira bidalia",
+DlgImgURL : "URL",
+DlgImgUpload : "Gora Kargatu",
+DlgImgAlt : "Ordezko Testua",
+DlgImgWidth : "Zabalera",
+DlgImgHeight : "Altuera",
+DlgImgLockRatio : "Erlazioa Blokeatu",
+DlgBtnResetSize : "Tamaina Berrezarri",
+DlgImgBorder : "Ertza",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Lerrokatu",
+DlgImgAlignLeft : "Ezkerrera",
+DlgImgAlignAbsBottom: "Abs Behean",
+DlgImgAlignAbsMiddle: "Abs Erdian",
+DlgImgAlignBaseline : "Oinan",
+DlgImgAlignBottom : "Behean",
+DlgImgAlignMiddle : "Erdian",
+DlgImgAlignRight : "Eskuman",
+DlgImgAlignTextTop : "Testua Goian",
+DlgImgAlignTop : "Goian",
+DlgImgPreview : "Aurrebista",
+DlgImgAlertUrl : "Mesedez Irudiaren URLa idatzi",
+DlgImgLinkTab : "Esteka",
+
+// Flash Dialog
+DlgFlashTitle : "Flasharen Ezaugarriak",
+DlgFlashChkPlay : "Automatikoki Erreproduzitu",
+DlgFlashChkLoop : "Begizta",
+DlgFlashChkMenu : "Flasharen Menua Gaitu",
+DlgFlashScale : "Eskalatu",
+DlgFlashScaleAll : "Dena erakutsi",
+DlgFlashScaleNoBorder : "Ertzik gabe",
+DlgFlashScaleFit : "Doitu",
+
+// Link Dialog
+DlgLnkWindowTitle : "Esteka",
+DlgLnkInfoTab : "Estekaren Informazioa",
+DlgLnkTargetTab : "Helburua",
+
+DlgLnkType : "Esteka Mota",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Aingura orrialde honetan",
+DlgLnkTypeEMail : "ePosta",
+DlgLnkProto : "Protokoloa",
+DlgLnkProtoOther : "<Beste batzuk>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Aingura bat hautatu",
+DlgLnkAnchorByName : "Aingura izenagatik",
+DlgLnkAnchorById : "Elementuaren ID-gatik",
+DlgLnkNoAnchors : "(Ez daude aingurak eskuragarri dokumentuan)",
+DlgLnkEMail : "ePosta Helbidea",
+DlgLnkEMailSubject : "Mezuaren Gaia",
+DlgLnkEMailBody : "Mezuaren Gorputza",
+DlgLnkUpload : "Gora kargatu",
+DlgLnkBtnUpload : "Zerbitzarira bidali",
+
+DlgLnkTarget : "Target (Helburua)",
+DlgLnkTargetFrame : "<marko>",
+DlgLnkTargetPopup : "<popup leihoa>",
+DlgLnkTargetBlank : "Leiho Berria (_blank)",
+DlgLnkTargetParent : "Leiho Gurasoa (_parent)",
+DlgLnkTargetSelf : "Leiho Berdina (_self)",
+DlgLnkTargetTop : "Goiko Leihoa (_top)",
+DlgLnkTargetFrameName : "Marko Helburuaren Izena",
+DlgLnkPopWinName : "Popup Leihoaren Izena",
+DlgLnkPopWinFeat : "Popup Leihoaren Ezaugarriak",
+DlgLnkPopResize : "Tamaina Aldakorra",
+DlgLnkPopLocation : "Kokaleku Barra",
+DlgLnkPopMenu : "Menu Barra",
+DlgLnkPopScroll : "Korritze Barrak",
+DlgLnkPopStatus : "Egoera Barra",
+DlgLnkPopToolbar : "Tresna Barra",
+DlgLnkPopFullScrn : "Pantaila Osoa (IE)",
+DlgLnkPopDependent : "Menpekoa (Netscape)",
+DlgLnkPopWidth : "Zabalera",
+DlgLnkPopHeight : "Altuera",
+DlgLnkPopLeft : "Ezkerreko Posizioa",
+DlgLnkPopTop : "Goiko Posizioa",
+
+DlnLnkMsgNoUrl : "Mesedez URL esteka idatzi",
+DlnLnkMsgNoEMail : "Mesedez ePosta helbidea idatzi",
+DlnLnkMsgNoAnchor : "Mesedez aingura bat aukeratu",
+DlnLnkMsgInvPopName : "Popup leihoaren izenak karaktere alfabetiko batekin hasi behar du eta eta ezin du zuriunerik izan",
+
+// Color Dialog
+DlgColorTitle : "Kolore Aukeraketa",
+DlgColorBtnClear : "Garbitu",
+DlgColorHighlight : "Nabarmendu",
+DlgColorSelected : "Aukeratuta",
+
+// Smiley Dialog
+DlgSmileyTitle : "Aurpegiera Sartu",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Karaktere Berezia Aukeratu",
+
+// Table Dialog
+DlgTableTitle : "Taularen Ezaugarriak",
+DlgTableRows : "Lerroak",
+DlgTableColumns : "Zutabeak",
+DlgTableBorder : "Ertzaren Zabalera",
+DlgTableAlign : "Lerrokatu",
+DlgTableAlignNotSet : "<Ezarri gabe>",
+DlgTableAlignLeft : "Ezkerrean",
+DlgTableAlignCenter : "Erdian",
+DlgTableAlignRight : "Eskuman",
+DlgTableWidth : "Zabalera",
+DlgTableWidthPx : "pixel",
+DlgTableWidthPc : "ehuneko",
+DlgTableHeight : "Altuera",
+DlgTableCellSpace : "Gelaxka arteko tartea",
+DlgTableCellPad : "Gelaxken betegarria",
+DlgTableCaption : "Epigrafea",
+DlgTableSummary : "Laburpena",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Gelaxken Ezaugarriak",
+DlgCellWidth : "Zabalera",
+DlgCellWidthPx : "pixel",
+DlgCellWidthPc : "ehuneko",
+DlgCellHeight : "Altuera",
+DlgCellWordWrap : "Itzulbira",
+DlgCellWordWrapNotSet : "<Ezarri gabe>",
+DlgCellWordWrapYes : "Bai",
+DlgCellWordWrapNo : "Ez",
+DlgCellHorAlign : "Lerrokatu Horizontalki",
+DlgCellHorAlignNotSet : "<Ezarri gabe>",
+DlgCellHorAlignLeft : "Ezkerrean",
+DlgCellHorAlignCenter : "Erdian",
+DlgCellHorAlignRight: "Eskuman",
+DlgCellVerAlign : "Lerrokatu Bertikalki",
+DlgCellVerAlignNotSet : "<Ezarri gabe>",
+DlgCellVerAlignTop : "Goian",
+DlgCellVerAlignMiddle : "Erdian",
+DlgCellVerAlignBottom : "Behean",
+DlgCellVerAlignBaseline : "Oinean",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Lerroak Hedatu",
+DlgCellCollSpan : "Zutabeak Hedatu",
+DlgCellBackColor : "Atzeko Kolorea",
+DlgCellBorderColor : "Ertzako Kolorea",
+DlgCellBtnSelect : "Aukeratu...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Bilatu eta Ordeztu",
+
+// Find Dialog
+DlgFindTitle : "Bilaketa",
+DlgFindFindBtn : "Bilatu",
+DlgFindNotFoundMsg : "Idatzitako testua ez da topatu.",
+
+// Replace Dialog
+DlgReplaceTitle : "Ordeztu",
+DlgReplaceFindLbl : "Zer bilatu:",
+DlgReplaceReplaceLbl : "Zerekin ordeztu:",
+DlgReplaceCaseChk : "Maiuskula/minuskula",
+DlgReplaceReplaceBtn : "Ordeztu",
+DlgReplaceReplAllBtn : "Ordeztu Guztiak",
+DlgReplaceWordChk : "Esaldi osoa bilatu",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki moztea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl+X).",
+PasteErrorCopy : "Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki kopiatzea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl+C).",
+
+PasteAsText : "Testu Arrunta bezala Itsatsi",
+PasteFromWord : "Word-etik itsatsi",
+
+DlgPasteMsg2 : "Mesedez teklatua erabilita (<STRONG>Ctrl+V</STRONG>) ondorego eremuan testua itsatsi eta <STRONG>OK</STRONG> sakatu.",
+DlgPasteSec : "Nabigatzailearen segurtasun ezarpenak direla eta, editoreak ezin du arbela zuzenean erabili. Leiho honetan berriro itsatsi behar duzu.",
+DlgPasteIgnoreFont : "Letra Motaren definizioa ezikusi",
+DlgPasteRemoveStyles : "Estilo definizioak kendu",
+
+// Color Picker
+ColorAutomatic : "Automatikoa",
+ColorMoreColors : "Kolore gehiago...",
+
+// Document Properties
+DocProps : "Dokumentuaren Ezarpenak",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ainguraren Ezaugarriak",
+DlgAnchorName : "Ainguraren Izena",
+DlgAnchorErrorName : "Idatzi ainguraren izena",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ez dago hiztegian",
+DlgSpellChangeTo : "Honekin ordezkatu",
+DlgSpellBtnIgnore : "Ezikusi",
+DlgSpellBtnIgnoreAll : "Denak Ezikusi",
+DlgSpellBtnReplace : "Ordezkatu",
+DlgSpellBtnReplaceAll : "Denak Ordezkatu",
+DlgSpellBtnUndo : "Desegin",
+DlgSpellNoSuggestions : "- Iradokizunik ez -",
+DlgSpellProgress : "Zuzenketa ortografikoa martxan...",
+DlgSpellNoMispell : "Zuzenketa ortografikoa bukatuta: Akatsik ez",
+DlgSpellNoChanges : "Zuzenketa ortografikoa bukatuta: Ez da ezer aldatu",
+DlgSpellOneChange : "Zuzenketa ortografikoa bukatuta: Hitz bat aldatu da",
+DlgSpellManyChanges : "Zuzenketa ortografikoa bukatuta: %1 hitz aldatu dira",
+
+IeSpellDownload : "Zuzentzaile ortografikoa ez dago instalatuta. Deskargatu nahi duzu?",
+
+// Button Dialog
+DlgButtonText : "Testua (Balorea)",
+DlgButtonType : "Mota",
+DlgButtonTypeBtn : "Botoia",
+DlgButtonTypeSbm : "Bidali",
+DlgButtonTypeRst : "Garbitu",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Izena",
+DlgCheckboxValue : "Balorea",
+DlgCheckboxSelected : "Hautatuta",
+
+// Form Dialog
+DlgFormName : "Izena",
+DlgFormAction : "Ekintza",
+DlgFormMethod : "Metodoa",
+
+// Select Field Dialog
+DlgSelectName : "Izena",
+DlgSelectValue : "Balorea",
+DlgSelectSize : "Tamaina",
+DlgSelectLines : "lerro kopurura",
+DlgSelectChkMulti : "Hautaketa anitzak baimendu",
+DlgSelectOpAvail : "Aukera Eskuragarriak",
+DlgSelectOpText : "Testua",
+DlgSelectOpValue : "Balorea",
+DlgSelectBtnAdd : "Gehitu",
+DlgSelectBtnModify : "Aldatu",
+DlgSelectBtnUp : "Gora",
+DlgSelectBtnDown : "Behera",
+DlgSelectBtnSetValue : "Aukeratutako balorea ezarri",
+DlgSelectBtnDelete : "Ezabatu",
+
+// Textarea Dialog
+DlgTextareaName : "Izena",
+DlgTextareaCols : "Zutabeak",
+DlgTextareaRows : "Lerroak",
+
+// Text Field Dialog
+DlgTextName : "Izena",
+DlgTextValue : "Balorea",
+DlgTextCharWidth : "Zabalera",
+DlgTextMaxChars : "Zenbat karaktere gehienez",
+DlgTextType : "Mota",
+DlgTextTypeText : "Testua",
+DlgTextTypePass : "Pasahitza",
+
+// Hidden Field Dialog
+DlgHiddenName : "Izena",
+DlgHiddenValue : "Balorea",
+
+// Bulleted List Dialog
+BulletedListProp : "Buletdun Zerrendaren Ezarpenak",
+NumberedListProp : "Zenbakidun Zerrendaren Ezarpenak",
+DlgLstStart : "Hasiera",
+DlgLstType : "Mota",
+DlgLstTypeCircle : "Zirkulua",
+DlgLstTypeDisc : "Diskoa",
+DlgLstTypeSquare : "Karratua",
+DlgLstTypeNumbers : "Zenbakiak (1, 2, 3)",
+DlgLstTypeLCase : "Letra xeheak (a, b, c)",
+DlgLstTypeUCase : "Letra larriak (A, B, C)",
+DlgLstTypeSRoman : "Erromatar zenbaki zeheak (i, ii, iii)",
+DlgLstTypeLRoman : "Erromatar zenbaki larriak (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Orokorra",
+DlgDocBackTab : "Atzealdea",
+DlgDocColorsTab : "Koloreak eta Marjinak",
+DlgDocMetaTab : "Meta Informazioa",
+
+DlgDocPageTitle : "Orriaren Izenburua",
+DlgDocLangDir : "Hizkuntzaren Norabidea",
+DlgDocLangDirLTR : "Ezkerretik eskumara (LTR)",
+DlgDocLangDirRTL : "Eskumatik ezkerrera (RTL)",
+DlgDocLangCode : "Hizkuntzaren Kodea",
+DlgDocCharSet : "Karaktere Multzoaren Kodeketa",
+DlgDocCharSetCE : "Erdialdeko Europakoa",
+DlgDocCharSetCT : "Txinatar Tradizionala (Big5)",
+DlgDocCharSetCR : "Zirilikoa",
+DlgDocCharSetGR : "Grekoa",
+DlgDocCharSetJP : "Japoniarra",
+DlgDocCharSetKR : "Korearra",
+DlgDocCharSetTR : "Turkiarra",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Mendebaldeko Europakoa",
+DlgDocCharSetOther : "Beste Karaktere Multzoko Kodeketa",
+
+DlgDocDocType : "Document Type Goiburua",
+DlgDocDocTypeOther : "Beste Document Type Goiburua",
+DlgDocIncXHTML : "XHTML Ezarpenak",
+DlgDocBgColor : "Atzeko Kolorea",
+DlgDocBgImage : "Atzeko Irudiaren URL-a",
+DlgDocBgNoScroll : "Korritze gabeko Atzealdea",
+DlgDocCText : "Testua",
+DlgDocCLink : "Estekak",
+DlgDocCVisited : "Bisitatutako Estekak",
+DlgDocCActive : "Esteka Aktiboa",
+DlgDocMargins : "Orrialdearen marjinak",
+DlgDocMaTop : "Goian",
+DlgDocMaLeft : "Ezkerrean",
+DlgDocMaRight : "Eskuman",
+DlgDocMaBottom : "Behean",
+DlgDocMeIndex : "Dokumentuaren Gako-hitzak (komarekin bananduta)",
+DlgDocMeDescr : "Dokumentuaren Deskribapena",
+DlgDocMeAuthor : "Egilea",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Aurrebista",
+
+// Templates Dialog
+Templates : "Txantiloiak",
+DlgTemplatesTitle : "Eduki Txantiloiak",
+DlgTemplatesSelMsg : "Mesedez txantiloia aukeratu editorean kargatzeko<br>(orain dauden edukiak galduko dira):",
+DlgTemplatesLoading : "Txantiloiak kargatzen. Itxaron mesedez...",
+DlgTemplatesNoTpl : "(Ez dago definitutako txantiloirik)",
+DlgTemplatesReplace : "Ordeztu oraingo edukiak",
+
+// About Dialog
+DlgAboutAboutTab : "Honi buruz",
+DlgAboutBrowserInfoTab : "Nabigatzailearen Informazioa",
+DlgAboutLicenseTab : "Lizentzia",
+DlgAboutVersion : "bertsioa",
+DlgAboutInfo : "Informazio gehiago eskuratzeko hona joan",
+
+// Div Dialog
+DlgDivGeneralTab : "Orokorra",
+DlgDivAdvancedTab : "Aurreratua",
+DlgDivStyle : "Estiloa",
+DlgDivInlineStyle : "Inline Estiloa",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/fa.js b/httemplate/elements/fckeditor/editor/lang/fa.js
new file mode 100644
index 0000000..3ca577c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/fa.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Persian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "rtl",
+
+ToolbarCollapse : "برچیدن نوارابزار",
+ToolbarExpand : "گستردن نوارابزار",
+
+// Toolbar Items and Context Menu
+Save : "ذخیره",
+NewPage : "برگهٴ تازه",
+Preview : "پیش‌نمایش",
+Cut : "برش",
+Copy : "کپی",
+Paste : "چسباندن",
+PasteText : "چسباندن به عنوان متن Ùساده",
+PasteWord : "چسباندن از Word",
+Print : "چاپ",
+SelectAll : "گزینش همه",
+RemoveFormat : "برداشتن Ùرمت",
+InsertLinkLbl : "پیوند",
+InsertLink : "گنجاندن/ویرایش Ùپیوند",
+RemoveLink : "برداشتن پیوند",
+VisitLink : "باز کردن پیوند",
+Anchor : "گنجاندن/ویرایش Ùلنگر",
+AnchorDelete : "برداشتن لنگر",
+InsertImageLbl : "تصویر",
+InsertImage : "گنجاندن/ویرایش Ùتصویر",
+InsertFlashLbl : "Flash",
+InsertFlash : "گنجاندن/ویرایش ÙFlash",
+InsertTableLbl : "جدول",
+InsertTable : "گنجاندن/ویرایش Ùجدول",
+InsertLineLbl : "خط",
+InsertLine : "گنجاندن خط ÙاÙÙ‚ÛŒ",
+InsertSpecialCharLbl: "نویسهٴ ویژه",
+InsertSpecialChar : "گنجاندن نویسهٴ ویژه",
+InsertSmileyLbl : "خندانک",
+InsertSmiley : "گنجاندن خندانک",
+About : "دربارهٴ FCKeditor",
+Bold : "درشت",
+Italic : "خمیده",
+Underline : "خط‌زیردار",
+StrikeThrough : "میان‌خط",
+Subscript : "زیرنویس",
+Superscript : "بالانویس",
+LeftJustify : "چپ‌چین",
+CenterJustify : "میان‌چین",
+RightJustify : "راست‌چین",
+BlockJustify : "بلوک‌چین",
+DecreaseIndent : "کاهش تورÙتگی",
+IncreaseIndent : "اÙزایش تورÙتگی",
+Blockquote : "بلوک نقل قول",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "واچیدن",
+Redo : "بازچیدن",
+NumberedListLbl : "Ùهرست شماره‌دار",
+NumberedList : "گنجاندن/برداشتن Ùهرست شماره‌دار",
+BulletedListLbl : "Ùهرست نقطه‌ای",
+BulletedList : "گنجاندن/برداشتن Ùهرست نقطه‌ای",
+ShowTableBorders : "نمایش لبهٴ جدول",
+ShowDetails : "نمایش جزئیات",
+Style : "سبک",
+FontFormat : "Ùرمت",
+Font : "قلم",
+FontSize : "اندازه",
+TextColor : "رنگ متن",
+BGColor : "رنگ پس‌زمینه",
+Source : "منبع",
+Find : "جستجو",
+Replace : "جایگزینی",
+SpellCheck : "بررسی املا",
+UniversalKeyboard : "صÙحه‌کلید جهانی",
+PageBreakLbl : "شکستگی Ùپایان Ùبرگه",
+PageBreak : "گنجاندن شکستگی Ùپایان Ùبرگه",
+
+Form : "Ùرم",
+Checkbox : "خانهٴ گزینه‌ای",
+RadioButton : "دکمهٴ رادیویی",
+TextField : "Ùیلد متنی",
+Textarea : "ناحیهٴ متنی",
+HiddenField : "Ùیلد پنهان",
+Button : "دکمه",
+SelectionField : "Ùیلد چندگزینه‌ای",
+ImageButton : "دکمهٴ تصویری",
+
+FitWindow : "بیشینه‌سازی Ùاندازهٴ ویرایشگر",
+ShowBlocks : "نمایش بلوک‌ها",
+
+// Context Menu
+EditLink : "ویرایش پیوند",
+CellCM : "سلول",
+RowCM : "سطر",
+ColumnCM : "ستون",
+InsertRowAfter : "اÙزودن سطر بعد از",
+InsertRowBefore : "اÙزودن سطر قبل از",
+DeleteRows : "حذ٠سطرها",
+InsertColumnAfter : "اÙزودن ستون بعد از",
+InsertColumnBefore : "اÙزودن ستون قبل از",
+DeleteColumns : "حذ٠ستونها",
+InsertCellAfter : "اÙزودن سلول بعد از",
+InsertCellBefore : "اÙزودن سلول قبل از",
+DeleteCells : "حذ٠سلولها",
+MergeCells : "ادغام سلولها",
+MergeRight : "ادغام به راست",
+MergeDown : "ادغام به پایین",
+HorizontalSplitCell : "جدا کردن اÙÙ‚ÛŒ سلول",
+VerticalSplitCell : "جدا کردن عمودی سلول",
+TableDelete : "پاک‌کردن جدول",
+CellProperties : "ویژگیهای سلول",
+TableProperties : "ویژگیهای جدول",
+ImageProperties : "ویژگیهای تصویر",
+FlashProperties : "ویژگیهای Flash",
+
+AnchorProp : "ویژگیهای لنگر",
+ButtonProp : "ویژگیهای دکمه",
+CheckboxProp : "ویژگیهای خانهٴ گزینه‌ای",
+HiddenFieldProp : "ویژگیهای Ùیلد پنهان",
+RadioButtonProp : "ویژگیهای دکمهٴ رادیویی",
+ImageButtonProp : "ویژگیهای دکمهٴ تصویری",
+TextFieldProp : "ویژگیهای Ùیلد متنی",
+SelectionFieldProp : "ویژگیهای Ùیلد چندگزینه‌ای",
+TextareaProp : "ویژگیهای ناحیهٴ متنی",
+FormProp : "ویژگیهای Ùرم",
+
+FontFormats : "نرمال;Ùرمت‌شده;آدرس;سرنویس 1;سرنویس 2;سرنویس 3;سرنویس 4;سرنویس 5;سرنویس 6;بند;(DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "پردازش XHTML. لطÙا صبر کنید...",
+Done : "انجام شد",
+PasteWordConfirm : "متنی که می‌خواهید بچسبانید به نظر می‌رسد از Word کپی شده است. آیا می‌خواهید قبل از چسباندن آن را پاک‌سازی کنید؟",
+NotCompatiblePaste : "این Ùرمان برای مرورگر Internet Explorer از نگارش 5.5 یا بالاتر در دسترس است. آیا می‌خواهید بدون پاک‌سازی، متن را بچسبانید؟",
+UnknownToolbarItem : "Ùقرهٴ نوارابزار ناشناخته \"%1\"",
+UnknownCommand : "نام دستور ناشناخته \"%1\"",
+NotImplemented : "دستور پیاده‌سازی‌نشده",
+UnknownToolbarSet : "مجموعهٴ نوارابزار \"%1\" وجود ندارد",
+NoActiveX : "تنظیمات امنیتی مرورگر شما ممکن است در بعضی از ویژگیهای مرورگر محدودیت ایجاد کند. شما باید گزینهٴ \"Run ActiveX controls and plug-ins\" را Ùعال کنید. ممکن است شما با خطاهایی روبرو باشید Ùˆ متوجه کمبود ویژگیهایی شوید.",
+BrowseServerBlocked : "توانایی بازگشایی مرورگر منابع Ùراهم نیست. اطمینان حاصل کنید Ú©Ù‡ تمامی برنامه‌های پیشگیری از نمایش popup را از کار بازداشته‌اید.",
+DialogBlocked : "توانایی بازگشایی پنجرهٴ Ú©ÙˆÚ†Ú© ÙÚ¯Ùتگو Ùراهم نیست. اطمینان حاصل کنید Ú©Ù‡ تمامی برنامه‌های پیشگیری از نمایش popup را از کار بازداشته‌اید.",
+VisitLinkBlocked : "امکان بازکردن یک پنجره جدید نیست. اطمینان حاصل کنید که تمامی برنامه‌های پیشگیری از نمایش popup را از کار بازداشته‌اید.",
+
+// Dialogs
+DlgBtnOK : "پذیرش",
+DlgBtnCancel : "انصراÙ",
+DlgBtnClose : "بستن",
+DlgBtnBrowseServer : "Ùهرست‌نمایی سرور",
+DlgAdvancedTag : "پیشرÙته",
+DlgOpOther : "<غیره>",
+DlgInfoTab : "اطلاعات",
+DlgAlertUrl : "لطÙاً URL را بنویسید",
+
+// General Dialogs Labels
+DlgGenNotSet : "<تعین‌نشده>",
+DlgGenId : "شناسه",
+DlgGenLangDir : "جهت‌نمای زبان",
+DlgGenLangDirLtr : "چپ به راست (LTR)",
+DlgGenLangDirRtl : "راست به چپ (RTL)",
+DlgGenLangCode : "کد زبان",
+DlgGenAccessKey : "کلید دستیابی",
+DlgGenName : "نام",
+DlgGenTabIndex : "نمایهٴ دسترسی با Tab",
+DlgGenLongDescr : "URL توصی٠طولانی",
+DlgGenClass : "کلاسهای شیوه‌نامه(Stylesheet)",
+DlgGenTitle : "عنوان کمکی",
+DlgGenContType : "نوع محتوای کمکی",
+DlgGenLinkCharset : "نویسه‌گان منبع Ùپیوندشده",
+DlgGenStyle : "شیوه(style)",
+
+// Image Dialog
+DlgImgTitle : "ویژگیهای تصویر",
+DlgImgInfoTab : "اطلاعات تصویر",
+DlgImgBtnUpload : "به سرور بÙرست",
+DlgImgURL : "URL",
+DlgImgUpload : "انتقال به سرور",
+DlgImgAlt : "متن جایگزین",
+DlgImgWidth : "پهنا",
+DlgImgHeight : "درازا",
+DlgImgLockRatio : "Ù‚Ùل‌کردن Ùنسبت",
+DlgBtnResetSize : "بازنشانی اندازه",
+DlgImgBorder : "لبه",
+DlgImgHSpace : "Ùاصلهٴ اÙÙ‚ÛŒ",
+DlgImgVSpace : "Ùاصلهٴ عمودی",
+DlgImgAlign : "چینش",
+DlgImgAlignLeft : "Ú†Ù¾",
+DlgImgAlignAbsBottom: "پائین مطلق",
+DlgImgAlignAbsMiddle: "وسط مطلق",
+DlgImgAlignBaseline : "خط‌پایه",
+DlgImgAlignBottom : "پائین",
+DlgImgAlignMiddle : "وسط",
+DlgImgAlignRight : "راست",
+DlgImgAlignTextTop : "متن بالا",
+DlgImgAlignTop : "بالا",
+DlgImgPreview : "پیش‌نمایش",
+DlgImgAlertUrl : "لطÙا URL تصویر را بنویسید",
+DlgImgLinkTab : "پیوند",
+
+// Flash Dialog
+DlgFlashTitle : "ویژگیهای Flash",
+DlgFlashChkPlay : "آغاز Ùخودکار",
+DlgFlashChkLoop : "اجرای پیاپی",
+DlgFlashChkMenu : "دردسترس‌بودن منوی Flash",
+DlgFlashScale : "مقیاس",
+DlgFlashScaleAll : "نمایش همه",
+DlgFlashScaleNoBorder : "بدون کران",
+DlgFlashScaleFit : "جایگیری کامل",
+
+// Link Dialog
+DlgLnkWindowTitle : "پیوند",
+DlgLnkInfoTab : "اطلاعات پیوند",
+DlgLnkTargetTab : "مقصد",
+
+DlgLnkType : "نوع پیوند",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "لنگر در همین صÙحه",
+DlgLnkTypeEMail : "پست الکترونیکی",
+DlgLnkProto : "پروتکل",
+DlgLnkProtoOther : "<دیگر>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "یک لنگر برگزینید",
+DlgLnkAnchorByName : "با نام لنگر",
+DlgLnkAnchorById : "با شناسهٴ المان",
+DlgLnkNoAnchors : "(در این سند لنگری دردسترس نیست)",
+DlgLnkEMail : "نشانی پست الکترونیکی",
+DlgLnkEMailSubject : "موضوع پیام",
+DlgLnkEMailBody : "متن پیام",
+DlgLnkUpload : "انتقال به سرور",
+DlgLnkBtnUpload : "به سرور بÙرست",
+
+DlgLnkTarget : "مقصد",
+DlgLnkTargetFrame : "<Ùریم>",
+DlgLnkTargetPopup : "<پنجرهٴ پاپاپ>",
+DlgLnkTargetBlank : "پنجرهٴ دیگر (_blank)",
+DlgLnkTargetParent : "پنجرهٴ والد (_parent)",
+DlgLnkTargetSelf : "همان پنجره (_self)",
+DlgLnkTargetTop : "بالاترین پنجره (_top)",
+DlgLnkTargetFrameName : "نام Ùریم مقصد",
+DlgLnkPopWinName : "نام پنجرهٴ پاپاپ",
+DlgLnkPopWinFeat : "ویژگیهای پنجرهٴ پاپاپ",
+DlgLnkPopResize : "قابل تغییر اندازه",
+DlgLnkPopLocation : "نوار موقعیت",
+DlgLnkPopMenu : "نوار منو",
+DlgLnkPopScroll : "میله‌های پیمایش",
+DlgLnkPopStatus : "نوار وضعیت",
+DlgLnkPopToolbar : "نوارابزار",
+DlgLnkPopFullScrn : "تمام‌صÙحه (IE)",
+DlgLnkPopDependent : "وابسته (Netscape)",
+DlgLnkPopWidth : "پهنا",
+DlgLnkPopHeight : "درازا",
+DlgLnkPopLeft : "موقعیت ÙÚ†Ù¾",
+DlgLnkPopTop : "موقعیت Ùبالا",
+
+DlnLnkMsgNoUrl : "لطÙا URL پیوند را بنویسید",
+DlnLnkMsgNoEMail : "لطÙا نشانی پست الکترونیکی را بنویسید",
+DlnLnkMsgNoAnchor : "لطÙا لنگری را برگزینید",
+DlnLnkMsgInvPopName : "نام پنجرهٴ پاپاپ باید با یک نویسهٴ الÙبایی آغاز گردد Ùˆ نباید Ùاصله‌های خالی در آن باشند",
+
+// Color Dialog
+DlgColorTitle : "گزینش رنگ",
+DlgColorBtnClear : "پاک‌کردن",
+DlgColorHighlight : "نمونه",
+DlgColorSelected : "برگزیده",
+
+// Smiley Dialog
+DlgSmileyTitle : "گنجاندن خندانک",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "گزینش نویسهٴ‌ویژه",
+
+// Table Dialog
+DlgTableTitle : "ویژگیهای جدول",
+DlgTableRows : "سطرها",
+DlgTableColumns : "ستونها",
+DlgTableBorder : "اندازهٴ لبه",
+DlgTableAlign : "چینش",
+DlgTableAlignNotSet : "<تعین‌نشده>",
+DlgTableAlignLeft : "Ú†Ù¾",
+DlgTableAlignCenter : "وسط",
+DlgTableAlignRight : "راست",
+DlgTableWidth : "پهنا",
+DlgTableWidthPx : "پیکسل",
+DlgTableWidthPc : "درصد",
+DlgTableHeight : "درازا",
+DlgTableCellSpace : "Ùاصلهٴ میان سلولها",
+DlgTableCellPad : "Ùاصلهٴ پرشده در سلول",
+DlgTableCaption : "عنوان",
+DlgTableSummary : "خلاصه",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "ویژگیهای سلول",
+DlgCellWidth : "پهنا",
+DlgCellWidthPx : "پیکسل",
+DlgCellWidthPc : "درصد",
+DlgCellHeight : "درازا",
+DlgCellWordWrap : "شکستن واژه‌ها",
+DlgCellWordWrapNotSet : "<تعین‌نشده>",
+DlgCellWordWrapYes : "بله",
+DlgCellWordWrapNo : "خیر",
+DlgCellHorAlign : "چینش ÙاÙÙ‚ÛŒ",
+DlgCellHorAlignNotSet : "<تعین‌نشده>",
+DlgCellHorAlignLeft : "Ú†Ù¾",
+DlgCellHorAlignCenter : "وسط",
+DlgCellHorAlignRight: "راست",
+DlgCellVerAlign : "چینش Ùعمودی",
+DlgCellVerAlignNotSet : "<تعین‌نشده>",
+DlgCellVerAlignTop : "بالا",
+DlgCellVerAlignMiddle : "میان",
+DlgCellVerAlignBottom : "پائین",
+DlgCellVerAlignBaseline : "خط‌پایه",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "گستردگی سطرها",
+DlgCellCollSpan : "گستردگی ستونها",
+DlgCellBackColor : "رنگ پس‌زمینه",
+DlgCellBorderColor : "رنگ لبه",
+DlgCellBtnSelect : "برگزینید...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "جستجو و جایگزینی",
+
+// Find Dialog
+DlgFindTitle : "یاÙتن",
+DlgFindFindBtn : "یاÙتن",
+DlgFindNotFoundMsg : "متن موردنظر یاÙت نشد.",
+
+// Replace Dialog
+DlgReplaceTitle : "جایگزینی",
+DlgReplaceFindLbl : "چه‌چیز را می‌یابید:",
+DlgReplaceReplaceLbl : "جایگزینی با:",
+DlgReplaceCaseChk : "همسانی در بزرگی و کوچکی نویسه‌ها",
+DlgReplaceReplaceBtn : "جایگزینی",
+DlgReplaceReplAllBtn : "جایگزینی همهٴ یاÙته‌ها",
+DlgReplaceWordChk : "همسانی با واژهٴ کامل",
+
+// Paste Operations / Dialog
+PasteErrorCut : "تنظیمات امنیتی مرورگر شما اجازه نمی‌دهد Ú©Ù‡ ویرایشگر به طور خودکار عملکردهای برش را انجام دهد. لطÙا با دکمه‌های صÙحه‌کلید این کار را انجام دهید (Ctrl+X).",
+PasteErrorCopy : "تنظیمات امنیتی مرورگر شما اجازه نمی‌دهد Ú©Ù‡ ویرایشگر به طور خودکار عملکردهای کپی‌کردن را انجام دهد. لطÙا با دکمه‌های صÙحه‌کلید این کار را انجام دهید (Ctrl+C).",
+
+PasteAsText : "چسباندن به عنوان متن Ùساده",
+PasteFromWord : "چسباندن از Word",
+
+DlgPasteMsg2 : "لطÙا متن را با کلیدهای (<STRONG>Ctrl+V</STRONG>) در این جعبهٴ متنی بچسبانید Ùˆ <STRONG>پذیرش</STRONG> را بزنید.",
+DlgPasteSec : "به خاطر تنظیمات امنیتی مرورگر شما، ویرایشگر نمی‌تواند دسترسی مستقیم به داده‌های clipboard داشته باشد. شما باید دوباره آنرا در این پنجره بچسبانید.",
+DlgPasteIgnoreFont : "چشم‌پوشی از تعاری٠نوع قلم",
+DlgPasteRemoveStyles : "چشم‌پوشی از تعاری٠سبک (style)",
+
+// Color Picker
+ColorAutomatic : "خودکار",
+ColorMoreColors : "رنگهای بیشتر...",
+
+// Document Properties
+DocProps : "ویژگیهای سند",
+
+// Anchor Dialog
+DlgAnchorTitle : "ویژگیهای لنگر",
+DlgAnchorName : "نام لنگر",
+DlgAnchorErrorName : "لطÙا نام لنگر را بنویسید",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "در واژه‌نامه یاÙت نشد",
+DlgSpellChangeTo : "تغییر به",
+DlgSpellBtnIgnore : "چشم‌پوشی",
+DlgSpellBtnIgnoreAll : "چشم‌پوشی همه",
+DlgSpellBtnReplace : "جایگزینی",
+DlgSpellBtnReplaceAll : "جایگزینی همه",
+DlgSpellBtnUndo : "واچینش",
+DlgSpellNoSuggestions : "- پیشنهادی نیست -",
+DlgSpellProgress : "بررسی املا در حال انجام...",
+DlgSpellNoMispell : "بررسی املا انجام شد. هیچ غلط‌املائی یاÙت نشد",
+DlgSpellNoChanges : "بررسی املا انجام شد. هیچ واژه‌ای تغییر نیاÙت",
+DlgSpellOneChange : "بررسی املا انجام شد. یک واژه تغییر یاÙت",
+DlgSpellManyChanges : "بررسی املا انجام شد. %1 واژه تغییر یاÙت",
+
+IeSpellDownload : "بررسی‌کنندهٴ املا نصب نشده است. آیا می‌خواهید آن را هم‌اکنون دریاÙت کنید؟",
+
+// Button Dialog
+DlgButtonText : "متن (مقدار)",
+DlgButtonType : "نوع",
+DlgButtonTypeBtn : "دکمه",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "بازنشانی (Reset)",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "نام",
+DlgCheckboxValue : "مقدار",
+DlgCheckboxSelected : "برگزیده",
+
+// Form Dialog
+DlgFormName : "نام",
+DlgFormAction : "رویداد",
+DlgFormMethod : "متد",
+
+// Select Field Dialog
+DlgSelectName : "نام",
+DlgSelectValue : "مقدار",
+DlgSelectSize : "اندازه",
+DlgSelectLines : "خطوط",
+DlgSelectChkMulti : "گزینش چندگانه Ùراهم باشد",
+DlgSelectOpAvail : "گزینه‌های دردسترس",
+DlgSelectOpText : "متن",
+DlgSelectOpValue : "مقدار",
+DlgSelectBtnAdd : "اÙزودن",
+DlgSelectBtnModify : "ویرایش",
+DlgSelectBtnUp : "بالا",
+DlgSelectBtnDown : "پائین",
+DlgSelectBtnSetValue : "تنظیم به عنوان مقدار Ùبرگزیده",
+DlgSelectBtnDelete : "پاک‌کردن",
+
+// Textarea Dialog
+DlgTextareaName : "نام",
+DlgTextareaCols : "ستونها",
+DlgTextareaRows : "سطرها",
+
+// Text Field Dialog
+DlgTextName : "نام",
+DlgTextValue : "مقدار",
+DlgTextCharWidth : "پهنای نویسه",
+DlgTextMaxChars : "بیشینهٴ نویسه‌ها",
+DlgTextType : "نوع",
+DlgTextTypeText : "متن",
+DlgTextTypePass : "گذرواژه",
+
+// Hidden Field Dialog
+DlgHiddenName : "نام",
+DlgHiddenValue : "مقدار",
+
+// Bulleted List Dialog
+BulletedListProp : "ویژگیهای Ùهرست نقطه‌ای",
+NumberedListProp : "ویژگیهای Ùهرست شماره‌دار",
+DlgLstStart : "آغاز",
+DlgLstType : "نوع",
+DlgLstTypeCircle : "دایره",
+DlgLstTypeDisc : "قرص",
+DlgLstTypeSquare : "چهارگوش",
+DlgLstTypeNumbers : "شماره‌ها (1، 2، 3)",
+DlgLstTypeLCase : "نویسه‌های کوچک (a، b، c)",
+DlgLstTypeUCase : "نویسه‌های بزرگ (A، B، C)",
+DlgLstTypeSRoman : "شمارگان رومی کوچک (i، ii، iii)",
+DlgLstTypeLRoman : "شمارگان رومی بزرگ (I، II، III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "عمومی",
+DlgDocBackTab : "پس‌زمینه",
+DlgDocColorsTab : "رنگها و حاشیه‌ها",
+DlgDocMetaTab : "Ùراداده",
+
+DlgDocPageTitle : "عنوان صÙحه",
+DlgDocLangDir : "جهت زبان",
+DlgDocLangDirLTR : "چپ به راست (LTR(",
+DlgDocLangDirRTL : "راست به چپ (RTL(",
+DlgDocLangCode : "کد زبان",
+DlgDocCharSet : "رمزگذاری نویسه‌گان",
+DlgDocCharSetCE : "اروپای مرکزی",
+DlgDocCharSetCT : "چینی رسمی (Big5)",
+DlgDocCharSetCR : "سیریلیک",
+DlgDocCharSetGR : "یونانی",
+DlgDocCharSetJP : "ژاپنی",
+DlgDocCharSetKR : "کره‌ای",
+DlgDocCharSetTR : "ترکی",
+DlgDocCharSetUN : "یونیکÙد (UTF-8)",
+DlgDocCharSetWE : "اروپای غربی",
+DlgDocCharSetOther : "رمزگذاری نویسه‌گان دیگر",
+
+DlgDocDocType : "عنوان نوع سند",
+DlgDocDocTypeOther : "عنوان نوع سند دیگر",
+DlgDocIncXHTML : "شامل تعاری٠XHTML",
+DlgDocBgColor : "رنگ پس‌زمینه",
+DlgDocBgImage : "URL تصویر پس‌زمینه",
+DlgDocBgNoScroll : "پس‌زمینهٴ پیمایش‌ناپذیر",
+DlgDocCText : "متن",
+DlgDocCLink : "پیوند",
+DlgDocCVisited : "پیوند مشاهده‌شده",
+DlgDocCActive : "پیوند Ùعال",
+DlgDocMargins : "حاشیه‌های صÙحه",
+DlgDocMaTop : "بالا",
+DlgDocMaLeft : "Ú†Ù¾",
+DlgDocMaRight : "راست",
+DlgDocMaBottom : "پایین",
+DlgDocMeIndex : "کلیدواژگان نمایه‌گذاری سند (با کاما جدا شوند)",
+DlgDocMeDescr : "توصی٠سند",
+DlgDocMeAuthor : "نویسنده",
+DlgDocMeCopy : "کپی‌رایت",
+DlgDocPreview : "پیش‌نمایش",
+
+// Templates Dialog
+Templates : "الگوها",
+DlgTemplatesTitle : "الگوهای محتویات",
+DlgTemplatesSelMsg : "لطÙا الگوی موردنظر را برای بازکردن در ویرایشگر برگزینید<br>(محتویات کنونی از دست خواهند رÙت):",
+DlgTemplatesLoading : "بارگذاری Ùهرست الگوها. لطÙا صبر کنید...",
+DlgTemplatesNoTpl : "(الگوئی تعری٠نشده است)",
+DlgTemplatesReplace : "محتویات کنونی جایگزین شوند",
+
+// About Dialog
+DlgAboutAboutTab : "درباره",
+DlgAboutBrowserInfoTab : "اطلاعات مرورگر",
+DlgAboutLicenseTab : "گواهینامه",
+DlgAboutVersion : "نگارش",
+DlgAboutInfo : "برای آگاهی بیشتر به این نشانی بروید",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/fi.js b/httemplate/elements/fckeditor/editor/lang/fi.js
new file mode 100644
index 0000000..00f9e1c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/fi.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Finnish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Piilota työkalurivi",
+ToolbarExpand : "Näytä työkalurivi",
+
+// Toolbar Items and Context Menu
+Save : "Tallenna",
+NewPage : "Tyhjennä",
+Preview : "Esikatsele",
+Cut : "Leikkaa",
+Copy : "Kopioi",
+Paste : "Liitä",
+PasteText : "Liitä tekstinä",
+PasteWord : "Liitä Wordista",
+Print : "Tulosta",
+SelectAll : "Valitse kaikki",
+RemoveFormat : "Poista muotoilu",
+InsertLinkLbl : "Linkki",
+InsertLink : "Lisää linkki/muokkaa linkkiä",
+RemoveLink : "Poista linkki",
+VisitLink : "Avaa linkki",
+Anchor : "Lisää ankkuri/muokkaa ankkuria",
+AnchorDelete : "Poista ankkuri",
+InsertImageLbl : "Kuva",
+InsertImage : "Lisää kuva/muokkaa kuvaa",
+InsertFlashLbl : "Flash",
+InsertFlash : "Lisää/muokkaa Flashia",
+InsertTableLbl : "Taulu",
+InsertTable : "Lisää taulu/muokkaa taulua",
+InsertLineLbl : "Murtoviiva",
+InsertLine : "Lisää murtoviiva",
+InsertSpecialCharLbl: "Erikoismerkki",
+InsertSpecialChar : "Lisää erikoismerkki",
+InsertSmileyLbl : "Hymiö",
+InsertSmiley : "Lisää hymiö",
+About : "FCKeditorista",
+Bold : "Lihavoitu",
+Italic : "Kursivoitu",
+Underline : "Alleviivattu",
+StrikeThrough : "Yliviivattu",
+Subscript : "Alaindeksi",
+Superscript : "Yläindeksi",
+LeftJustify : "Tasaa vasemmat reunat",
+CenterJustify : "Keskitä",
+RightJustify : "Tasaa oikeat reunat",
+BlockJustify : "Tasaa molemmat reunat",
+DecreaseIndent : "Pienennä sisennystä",
+IncreaseIndent : "Suurenna sisennystä",
+Blockquote : "Lainaus",
+CreateDiv : "Lisää Div",
+EditDiv : "Muokkaa Div:ä",
+DeleteDiv : "Poista Div",
+Undo : "Kumoa",
+Redo : "Toista",
+NumberedListLbl : "Numerointi",
+NumberedList : "Lisää/poista numerointi",
+BulletedListLbl : "Luottelomerkit",
+BulletedList : "Lisää/poista luottelomerkit",
+ShowTableBorders : "Näytä taulun rajat",
+ShowDetails : "Näytä muotoilu",
+Style : "Tyyli",
+FontFormat : "Muotoilu",
+Font : "Fontti",
+FontSize : "Koko",
+TextColor : "Tekstiväri",
+BGColor : "Taustaväri",
+Source : "Koodi",
+Find : "Etsi",
+Replace : "Korvaa",
+SpellCheck : "Tarkista oikeinkirjoitus",
+UniversalKeyboard : "Universaali näppäimistö",
+PageBreakLbl : "Sivun vaihto",
+PageBreak : "Lisää sivun vaihto",
+
+Form : "Lomake",
+Checkbox : "Valintaruutu",
+RadioButton : "Radiopainike",
+TextField : "Tekstikenttä",
+Textarea : "Tekstilaatikko",
+HiddenField : "Piilokenttä",
+Button : "Painike",
+SelectionField : "Valintakenttä",
+ImageButton : "Kuvapainike",
+
+FitWindow : "Suurenna editori koko ikkunaan",
+ShowBlocks : "Näytä elementit",
+
+// Context Menu
+EditLink : "Muokkaa linkkiä",
+CellCM : "Solu",
+RowCM : "Rivi",
+ColumnCM : "Sarake",
+InsertRowAfter : "Lisää rivi alapuolelle",
+InsertRowBefore : "Lisää rivi yläpuolelle",
+DeleteRows : "Poista rivit",
+InsertColumnAfter : "Lisää sarake oikealle",
+InsertColumnBefore : "Lisää sarake vasemmalle",
+DeleteColumns : "Poista sarakkeet",
+InsertCellAfter : "Lisää solu perään",
+InsertCellBefore : "Lisää solu eteen",
+DeleteCells : "Poista solut",
+MergeCells : "Yhdistä solut",
+MergeRight : "Yhdistä oikealla olevan kanssa",
+MergeDown : "Yhdistä alla olevan kanssa",
+HorizontalSplitCell : "Jaa solu vaakasuunnassa",
+VerticalSplitCell : "Jaa solu pystysuunnassa",
+TableDelete : "Poista taulu",
+CellProperties : "Solun ominaisuudet",
+TableProperties : "Taulun ominaisuudet",
+ImageProperties : "Kuvan ominaisuudet",
+FlashProperties : "Flash ominaisuudet",
+
+AnchorProp : "Ankkurin ominaisuudet",
+ButtonProp : "Painikkeen ominaisuudet",
+CheckboxProp : "Valintaruudun ominaisuudet",
+HiddenFieldProp : "Piilokentän ominaisuudet",
+RadioButtonProp : "Radiopainikkeen ominaisuudet",
+ImageButtonProp : "Kuvapainikkeen ominaisuudet",
+TextFieldProp : "Tekstikentän ominaisuudet",
+SelectionFieldProp : "Valintakentän ominaisuudet",
+TextareaProp : "Tekstilaatikon ominaisuudet",
+FormProp : "Lomakkeen ominaisuudet",
+
+FontFormats : "Normaali;Muotoiltu;Osoite;Otsikko 1;Otsikko 2;Otsikko 3;Otsikko 4;Otsikko 5;Otsikko 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Prosessoidaan XHTML:ää. Odota hetki...",
+Done : "Valmis",
+PasteWordConfirm : "Teksti, jonka haluat liittää, näyttää olevan kopioitu Wordista. Haluatko puhdistaa sen ennen liittämistä?",
+NotCompatiblePaste : "Tämä komento toimii vain Internet Explorer 5.5:ssa tai uudemmassa. Haluatko liittää ilman puhdistusta?",
+UnknownToolbarItem : "Tuntemanton työkalu \"%1\"",
+UnknownCommand : "Tuntematon komento \"%1\"",
+NotImplemented : "Komentoa ei ole liitetty sovellukseen",
+UnknownToolbarSet : "Työkalukokonaisuus \"%1\" ei ole olemassa",
+NoActiveX : "Selaimesi turvallisuusasetukset voivat rajoittaa joitain editorin ominaisuuksia. Sinun pitää ottaa käyttöön asetuksista \"Suorita ActiveX komponentit ja -plugin-laajennukset\". Saatat kohdata virheitä ja huomata puuttuvia ominaisuuksia.",
+BrowseServerBlocked : "Resurssiselainta ei voitu avata. Varmista, että ponnahdusikkunoiden estäjät eivät ole päällä.",
+DialogBlocked : "Apuikkunaa ei voitu avaata. Varmista, että ponnahdusikkunoiden estäjät eivät ole päällä.",
+VisitLinkBlocked : "IUutta ikkunaa ei voitu avata. Varmista, että ponnahdusikkunoiden estäjät eivät ole päällä.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Peruuta",
+DlgBtnClose : "Sulje",
+DlgBtnBrowseServer : "Selaa palvelinta",
+DlgAdvancedTag : "Lisäominaisuudet",
+DlgOpOther : "Muut",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Lisää URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ei asetettu>",
+DlgGenId : "Tunniste",
+DlgGenLangDir : "Kielen suunta",
+DlgGenLangDirLtr : "Vasemmalta oikealle (LTR)",
+DlgGenLangDirRtl : "Oikealta vasemmalle (RTL)",
+DlgGenLangCode : "Kielikoodi",
+DlgGenAccessKey : "Pikanäppäin",
+DlgGenName : "Nimi",
+DlgGenTabIndex : "Tabulaattori indeksi",
+DlgGenLongDescr : "Pitkän kuvauksen URL",
+DlgGenClass : "Tyyliluokat",
+DlgGenTitle : "Avustava otsikko",
+DlgGenContType : "Avustava sisällön tyyppi",
+DlgGenLinkCharset : "Linkitetty kirjaimisto",
+DlgGenStyle : "Tyyli",
+
+// Image Dialog
+DlgImgTitle : "Kuvan ominaisuudet",
+DlgImgInfoTab : "Kuvan tiedot",
+DlgImgBtnUpload : "Lähetä palvelimelle",
+DlgImgURL : "Osoite",
+DlgImgUpload : "Lisää kuva",
+DlgImgAlt : "Vaihtoehtoinen teksti",
+DlgImgWidth : "Leveys",
+DlgImgHeight : "Korkeus",
+DlgImgLockRatio : "Lukitse suhteet",
+DlgBtnResetSize : "Alkuperäinen koko",
+DlgImgBorder : "Raja",
+DlgImgHSpace : "Vaakatila",
+DlgImgVSpace : "Pystytila",
+DlgImgAlign : "Kohdistus",
+DlgImgAlignLeft : "Vasemmalle",
+DlgImgAlignAbsBottom: "Aivan alas",
+DlgImgAlignAbsMiddle: "Aivan keskelle",
+DlgImgAlignBaseline : "Alas (teksti)",
+DlgImgAlignBottom : "Alas",
+DlgImgAlignMiddle : "Keskelle",
+DlgImgAlignRight : "Oikealle",
+DlgImgAlignTextTop : "Ylös (teksti)",
+DlgImgAlignTop : "Ylös",
+DlgImgPreview : "Esikatselu",
+DlgImgAlertUrl : "Kirjoita kuvan osoite (URL)",
+DlgImgLinkTab : "Linkki",
+
+// Flash Dialog
+DlgFlashTitle : "Flash ominaisuudet",
+DlgFlashChkPlay : "Automaattinen käynnistys",
+DlgFlashChkLoop : "Toisto",
+DlgFlashChkMenu : "Näytä Flash-valikko",
+DlgFlashScale : "Levitä",
+DlgFlashScaleAll : "Näytä kaikki",
+DlgFlashScaleNoBorder : "Ei rajaa",
+DlgFlashScaleFit : "Tarkka koko",
+
+// Link Dialog
+DlgLnkWindowTitle : "Linkki",
+DlgLnkInfoTab : "Linkin tiedot",
+DlgLnkTargetTab : "Kohde",
+
+DlgLnkType : "Linkkityyppi",
+DlgLnkTypeURL : "Osoite",
+DlgLnkTypeAnchor : "Ankkuri tässä sivussa",
+DlgLnkTypeEMail : "Sähköposti",
+DlgLnkProto : "Protokolla",
+DlgLnkProtoOther : "<muu>",
+DlgLnkURL : "Osoite",
+DlgLnkAnchorSel : "Valitse ankkuri",
+DlgLnkAnchorByName : "Ankkurin nimen mukaan",
+DlgLnkAnchorById : "Ankkurin ID:n mukaan",
+DlgLnkNoAnchors : "(Ei ankkureita tässä dokumentissa)",
+DlgLnkEMail : "Sähköpostiosoite",
+DlgLnkEMailSubject : "Aihe",
+DlgLnkEMailBody : "Viesti",
+DlgLnkUpload : "Lisää tiedosto",
+DlgLnkBtnUpload : "Lähetä palvelimelle",
+
+DlgLnkTarget : "Kohde",
+DlgLnkTargetFrame : "<kehys>",
+DlgLnkTargetPopup : "<popup ikkuna>",
+DlgLnkTargetBlank : "Uusi ikkuna (_blank)",
+DlgLnkTargetParent : "Emoikkuna (_parent)",
+DlgLnkTargetSelf : "Sama ikkuna (_self)",
+DlgLnkTargetTop : "Päällimmäisin ikkuna (_top)",
+DlgLnkTargetFrameName : "Kohdekehyksen nimi",
+DlgLnkPopWinName : "Popup ikkunan nimi",
+DlgLnkPopWinFeat : "Popup ikkunan ominaisuudet",
+DlgLnkPopResize : "Venytettävä",
+DlgLnkPopLocation : "Osoiterivi",
+DlgLnkPopMenu : "Valikkorivi",
+DlgLnkPopScroll : "Vierityspalkit",
+DlgLnkPopStatus : "Tilarivi",
+DlgLnkPopToolbar : "Vakiopainikkeet",
+DlgLnkPopFullScrn : "Täysi ikkuna (IE)",
+DlgLnkPopDependent : "Riippuva (Netscape)",
+DlgLnkPopWidth : "Leveys",
+DlgLnkPopHeight : "Korkeus",
+DlgLnkPopLeft : "Vasemmalta (px)",
+DlgLnkPopTop : "Ylhäältä (px)",
+
+DlnLnkMsgNoUrl : "Linkille on kirjoitettava URL",
+DlnLnkMsgNoEMail : "Kirjoita sähköpostiosoite",
+DlnLnkMsgNoAnchor : "Valitse ankkuri",
+DlnLnkMsgInvPopName : "Popup-ikkunan nimi pitää alkaa aakkosella ja ei saa sisältää välejä",
+
+// Color Dialog
+DlgColorTitle : "Valitse väri",
+DlgColorBtnClear : "Tyhjennä",
+DlgColorHighlight : "Kohdalla",
+DlgColorSelected : "Valittu",
+
+// Smiley Dialog
+DlgSmileyTitle : "Lisää hymiö",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Valitse erikoismerkki",
+
+// Table Dialog
+DlgTableTitle : "Taulun ominaisuudet",
+DlgTableRows : "Rivit",
+DlgTableColumns : "Sarakkeet",
+DlgTableBorder : "Rajan paksuus",
+DlgTableAlign : "Kohdistus",
+DlgTableAlignNotSet : "<ei asetettu>",
+DlgTableAlignLeft : "Vasemmalle",
+DlgTableAlignCenter : "Keskelle",
+DlgTableAlignRight : "Oikealle",
+DlgTableWidth : "Leveys",
+DlgTableWidthPx : "pikseliä",
+DlgTableWidthPc : "prosenttia",
+DlgTableHeight : "Korkeus",
+DlgTableCellSpace : "Solujen väli",
+DlgTableCellPad : "Solujen sisennys",
+DlgTableCaption : "Otsikko",
+DlgTableSummary : "Yhteenveto",
+DlgTableHeaders : "Ylätunnisteet",
+DlgTableHeadersNone : "Ei ylätunnisteita",
+DlgTableHeadersColumn : "Ensimmäinen sarake",
+DlgTableHeadersRow : "Ensimmäinen rivi",
+DlgTableHeadersBoth : "Molemmat",
+
+// Table Cell Dialog
+DlgCellTitle : "Solun ominaisuudet",
+DlgCellWidth : "Leveys",
+DlgCellWidthPx : "pikseliä",
+DlgCellWidthPc : "prosenttia",
+DlgCellHeight : "Korkeus",
+DlgCellWordWrap : "Tekstikierrätys",
+DlgCellWordWrapNotSet : "<Ei asetettu>",
+DlgCellWordWrapYes : "Kyllä",
+DlgCellWordWrapNo : "Ei",
+DlgCellHorAlign : "Vaakakohdistus",
+DlgCellHorAlignNotSet : "<Ei asetettu>",
+DlgCellHorAlignLeft : "Vasemmalle",
+DlgCellHorAlignCenter : "Keskelle",
+DlgCellHorAlignRight: "Oikealle",
+DlgCellVerAlign : "Pystykohdistus",
+DlgCellVerAlignNotSet : "<Ei asetettu>",
+DlgCellVerAlignTop : "Ylös",
+DlgCellVerAlignMiddle : "Keskelle",
+DlgCellVerAlignBottom : "Alas",
+DlgCellVerAlignBaseline : "Tekstin alas",
+DlgCellType : "Solun tyyppi",
+DlgCellTypeData : "Sisältö",
+DlgCellTypeHeader : "Ylätunniste",
+DlgCellRowSpan : "Rivin jatkuvuus",
+DlgCellCollSpan : "Sarakkeen jatkuvuus",
+DlgCellBackColor : "Taustaväri",
+DlgCellBorderColor : "Rajan väri",
+DlgCellBtnSelect : "Valitse...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Etsi ja korvaa",
+
+// Find Dialog
+DlgFindTitle : "Etsi",
+DlgFindFindBtn : "Etsi",
+DlgFindNotFoundMsg : "Etsittyä tekstiä ei löytynyt.",
+
+// Replace Dialog
+DlgReplaceTitle : "Korvaa",
+DlgReplaceFindLbl : "Etsi mitä:",
+DlgReplaceReplaceLbl : "Korvaa tällä:",
+DlgReplaceCaseChk : "Sama kirjainkoko",
+DlgReplaceReplaceBtn : "Korvaa",
+DlgReplaceReplAllBtn : "Korvaa kaikki",
+DlgReplaceWordChk : "Koko sana",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Selaimesi turva-asetukset eivät salli editorin toteuttaa leikkaamista. Käytä näppäimistöä leikkaamiseen (Ctrl+X).",
+PasteErrorCopy : "Selaimesi turva-asetukset eivät salli editorin toteuttaa kopioimista. Käytä näppäimistöä kopioimiseen (Ctrl+C).",
+
+PasteAsText : "Liitä tekstinä",
+PasteFromWord : "Liitä Wordista",
+
+DlgPasteMsg2 : "Liitä painamalla (<STRONG>Ctrl+V</STRONG>) ja painamalla <STRONG>OK</STRONG>.",
+DlgPasteSec : "Selaimesi turva-asetukset eivät salli editorin käyttää leikepöytää suoraan. Sinun pitää suorittaa liittäminen tässä ikkunassa.",
+DlgPasteIgnoreFont : "Jätä huomioimatta fonttimääritykset",
+DlgPasteRemoveStyles : "Poista tyylimääritykset",
+
+// Color Picker
+ColorAutomatic : "Automaattinen",
+ColorMoreColors : "Lisää värejä...",
+
+// Document Properties
+DocProps : "Dokumentin ominaisuudet",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankkurin ominaisuudet",
+DlgAnchorName : "Nimi",
+DlgAnchorErrorName : "Ankkurille on kirjoitettava nimi",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ei sanakirjassa",
+DlgSpellChangeTo : "Vaihda",
+DlgSpellBtnIgnore : "Jätä huomioimatta",
+DlgSpellBtnIgnoreAll : "Jätä kaikki huomioimatta",
+DlgSpellBtnReplace : "Korvaa",
+DlgSpellBtnReplaceAll : "Korvaa kaikki",
+DlgSpellBtnUndo : "Kumoa",
+DlgSpellNoSuggestions : "Ei ehdotuksia",
+DlgSpellProgress : "Tarkistus käynnissä...",
+DlgSpellNoMispell : "Tarkistus valmis: Ei virheitä",
+DlgSpellNoChanges : "Tarkistus valmis: Yhtään sanaa ei muutettu",
+DlgSpellOneChange : "Tarkistus valmis: Yksi sana muutettiin",
+DlgSpellManyChanges : "Tarkistus valmis: %1 sanaa muutettiin",
+
+IeSpellDownload : "Oikeinkirjoituksen tarkistusta ei ole asennettu. Haluatko ladata sen nyt?",
+
+// Button Dialog
+DlgButtonText : "Teksti (arvo)",
+DlgButtonType : "Tyyppi",
+DlgButtonTypeBtn : "Painike",
+DlgButtonTypeSbm : "Lähetä",
+DlgButtonTypeRst : "Tyhjennä",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nimi",
+DlgCheckboxValue : "Arvo",
+DlgCheckboxSelected : "Valittu",
+
+// Form Dialog
+DlgFormName : "Nimi",
+DlgFormAction : "Toiminto",
+DlgFormMethod : "Tapa",
+
+// Select Field Dialog
+DlgSelectName : "Nimi",
+DlgSelectValue : "Arvo",
+DlgSelectSize : "Koko",
+DlgSelectLines : "Rivit",
+DlgSelectChkMulti : "Salli usea valinta",
+DlgSelectOpAvail : "Ominaisuudet",
+DlgSelectOpText : "Teksti",
+DlgSelectOpValue : "Arvo",
+DlgSelectBtnAdd : "Lisää",
+DlgSelectBtnModify : "Muuta",
+DlgSelectBtnUp : "Ylös",
+DlgSelectBtnDown : "Alas",
+DlgSelectBtnSetValue : "Aseta valituksi",
+DlgSelectBtnDelete : "Poista",
+
+// Textarea Dialog
+DlgTextareaName : "Nimi",
+DlgTextareaCols : "Sarakkeita",
+DlgTextareaRows : "Rivejä",
+
+// Text Field Dialog
+DlgTextName : "Nimi",
+DlgTextValue : "Arvo",
+DlgTextCharWidth : "Leveys",
+DlgTextMaxChars : "Maksimi merkkimäärä",
+DlgTextType : "Tyyppi",
+DlgTextTypeText : "Teksti",
+DlgTextTypePass : "Salasana",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nimi",
+DlgHiddenValue : "Arvo",
+
+// Bulleted List Dialog
+BulletedListProp : "Luettelon ominaisuudet",
+NumberedListProp : "Numeroinnin ominaisuudet",
+DlgLstStart : "Alku",
+DlgLstType : "Tyyppi",
+DlgLstTypeCircle : "Kehä",
+DlgLstTypeDisc : "Ympyrä",
+DlgLstTypeSquare : "Neliö",
+DlgLstTypeNumbers : "Numerot (1, 2, 3)",
+DlgLstTypeLCase : "Pienet kirjaimet (a, b, c)",
+DlgLstTypeUCase : "Isot kirjaimet (A, B, C)",
+DlgLstTypeSRoman : "Pienet roomalaiset numerot (i, ii, iii)",
+DlgLstTypeLRoman : "Isot roomalaiset numerot (Ii, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Yleiset",
+DlgDocBackTab : "Tausta",
+DlgDocColorsTab : "Värit ja marginaalit",
+DlgDocMetaTab : "Meta-tieto",
+
+DlgDocPageTitle : "Sivun nimi",
+DlgDocLangDir : "Kielen suunta",
+DlgDocLangDirLTR : "Vasemmalta oikealle (LTR)",
+DlgDocLangDirRTL : "Oikealta vasemmalle (RTL)",
+DlgDocLangCode : "Kielikoodi",
+DlgDocCharSet : "Merkistökoodaus",
+DlgDocCharSetCE : "Keskieurooppalainen",
+DlgDocCharSetCT : "Kiina, perinteinen (Big5)",
+DlgDocCharSetCR : "Kyrillinen",
+DlgDocCharSetGR : "Kreikka",
+DlgDocCharSetJP : "Japani",
+DlgDocCharSetKR : "Korealainen",
+DlgDocCharSetTR : "Turkkilainen",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Länsieurooppalainen",
+DlgDocCharSetOther : "Muu merkistökoodaus",
+
+DlgDocDocType : "Dokumentin tyyppi",
+DlgDocDocTypeOther : "Muu dokumentin tyyppi",
+DlgDocIncXHTML : "Lisää XHTML julistukset",
+DlgDocBgColor : "Taustaväri",
+DlgDocBgImage : "Taustakuva",
+DlgDocBgNoScroll : "Paikallaanpysyvä tausta",
+DlgDocCText : "Teksti",
+DlgDocCLink : "Linkki",
+DlgDocCVisited : "Vierailtu linkki",
+DlgDocCActive : "Aktiivinen linkki",
+DlgDocMargins : "Sivun marginaalit",
+DlgDocMaTop : "Ylä",
+DlgDocMaLeft : "Vasen",
+DlgDocMaRight : "Oikea",
+DlgDocMaBottom : "Ala",
+DlgDocMeIndex : "Hakusanat (pilkulla erotettuna)",
+DlgDocMeDescr : "Kuvaus",
+DlgDocMeAuthor : "Tekijä",
+DlgDocMeCopy : "Tekijänoikeudet",
+DlgDocPreview : "Esikatselu",
+
+// Templates Dialog
+Templates : "Pohjat",
+DlgTemplatesTitle : "Sisältöpohjat",
+DlgTemplatesSelMsg : "Valitse pohja editoriin<br>(aiempi sisältö menetetään):",
+DlgTemplatesLoading : "Ladataan listaa pohjista. Hetkinen...",
+DlgTemplatesNoTpl : "(Ei määriteltyjä pohjia)",
+DlgTemplatesReplace : "Korvaa editorin koko sisältö",
+
+// About Dialog
+DlgAboutAboutTab : "Editorista",
+DlgAboutBrowserInfoTab : "Selaimen tiedot",
+DlgAboutLicenseTab : "Lisenssi",
+DlgAboutVersion : "versio",
+DlgAboutInfo : "Lisää tietoa osoitteesta",
+
+// Div Dialog
+DlgDivGeneralTab : "Edistynyt",
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Tyyli",
+DlgDivInlineStyle : "Rivin sisäinen tyyli",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/fo.js b/httemplate/elements/fckeditor/editor/lang/fo.js
new file mode 100644
index 0000000..e13b667
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/fo.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Faroese language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Fjal amboðsbjálkan",
+ToolbarExpand : "Vís amboðsbjálkan",
+
+// Toolbar Items and Context Menu
+Save : "Goym",
+NewPage : "Nýggj síða",
+Preview : "Frumsýning",
+Cut : "Kvett",
+Copy : "Avrita",
+Paste : "Innrita",
+PasteText : "Innrita reinan tekst",
+PasteWord : "Innrita frá Word",
+Print : "Prenta",
+SelectAll : "Markera alt",
+RemoveFormat : "Strika sniðgeving",
+InsertLinkLbl : "Tilknýti",
+InsertLink : "Ger/broyt tilknýti",
+RemoveLink : "Strika tilknýti",
+VisitLink : "Opna tilknýti",
+Anchor : "Ger/broyt marknastein",
+AnchorDelete : "Strika marknastein",
+InsertImageLbl : "Myndir",
+InsertImage : "Set inn/broyt mynd",
+InsertFlashLbl : "Flash",
+InsertFlash : "Set inn/broyt Flash",
+InsertTableLbl : "Tabell",
+InsertTable : "Set inn/broyt tabell",
+InsertLineLbl : "Linja",
+InsertLine : "Ger vatnrætta linju",
+InsertSpecialCharLbl: "Sertekn",
+InsertSpecialChar : "Set inn sertekn",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Set inn Smiley",
+About : "Um FCKeditor",
+Bold : "Feit skrift",
+Italic : "Skráskrift",
+Underline : "Undirstrikað",
+StrikeThrough : "Yvirstrikað",
+Subscript : "Lækkað skrift",
+Superscript : "Hækkað skrift",
+LeftJustify : "Vinstrasett",
+CenterJustify : "Miðsett",
+RightJustify : "Høgrasett",
+BlockJustify : "Javnir tekstkantar",
+DecreaseIndent : "Minka reglubrotarinntriv",
+IncreaseIndent : "Økja reglubrotarinntriv",
+Blockquote : "Blockquote",
+CreateDiv : "Ger DIV øki",
+EditDiv : "Broyt DIV øki",
+DeleteDiv : "Strika DIV øki",
+Undo : "Angra",
+Redo : "Vend aftur",
+NumberedListLbl : "Talmerktur listi",
+NumberedList : "Ger/strika talmerktan lista",
+BulletedListLbl : "Punktmerktur listi",
+BulletedList : "Ger/strika punktmerktan lista",
+ShowTableBorders : "Vís tabellbordar",
+ShowDetails : "Vís í smálutum",
+Style : "Typografi",
+FontFormat : "Skriftsnið",
+Font : "Skrift",
+FontSize : "Skriftstødd",
+TextColor : "Tekstlitur",
+BGColor : "Bakgrundslitur",
+Source : "Kelda",
+Find : "Leita",
+Replace : "Yvirskriva",
+SpellCheck : "Kanna stavseting",
+UniversalKeyboard : "Knappaborð",
+PageBreakLbl : "Síðuskift",
+PageBreak : "Ger síðuskift",
+
+Form : "Formur",
+Checkbox : "Flugubein",
+RadioButton : "Radioknøttur",
+TextField : "Tekstteigur",
+Textarea : "Tekstumráði",
+HiddenField : "Fjaldur teigur",
+Button : "Knøttur",
+SelectionField : "Valskrá",
+ImageButton : "Myndaknøttur",
+
+FitWindow : "Set tekstviðgera til fulla stødd",
+ShowBlocks : "Vís blokkar",
+
+// Context Menu
+EditLink : "Broyt tilknýti",
+CellCM : "Meski",
+RowCM : "Rað",
+ColumnCM : "Kolonna",
+InsertRowAfter : "Set rað inn aftaná",
+InsertRowBefore : "Set rað inn áðrenn",
+DeleteRows : "Strika røðir",
+InsertColumnAfter : "Set kolonnu inn aftaná",
+InsertColumnBefore : "Set kolonnu inn áðrenn",
+DeleteColumns : "Strika kolonnur",
+InsertCellAfter : "Set meska inn aftaná",
+InsertCellBefore : "Set meska inn áðrenn",
+DeleteCells : "Strika meskar",
+MergeCells : "Flætta meskar",
+MergeRight : "Flætta meskar til høgru",
+MergeDown : "Flætta saman",
+HorizontalSplitCell : "Kloyv meska vatnrætt",
+VerticalSplitCell : "Kloyv meska loddrætt",
+TableDelete : "Strika tabell",
+CellProperties : "Meskueginleikar",
+TableProperties : "Tabelleginleikar",
+ImageProperties : "Myndaeginleikar",
+FlashProperties : "Flash eginleikar",
+
+AnchorProp : "Eginleikar fyri marknastein",
+ButtonProp : "Eginleikar fyri knøtt",
+CheckboxProp : "Eginleikar fyri flugubein",
+HiddenFieldProp : "Eginleikar fyri fjaldan teig",
+RadioButtonProp : "Eginleikar fyri radioknøtt",
+ImageButtonProp : "Eginleikar fyri myndaknøtt",
+TextFieldProp : "Eginleikar fyri tekstteig",
+SelectionFieldProp : "Eginleikar fyri valskrá",
+TextareaProp : "Eginleikar fyri tekstumráði",
+FormProp : "Eginleikar fyri Form",
+
+FontFormats : "Vanligt;Sniðgivið;Adressa;Yvirskrift 1;Yvirskrift 2;Yvirskrift 3;Yvirskrift 4;Yvirskrift 5;Yvirskrift 6",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML verður viðgjørt. Bíða við...",
+Done : "Liðugt",
+PasteWordConfirm : "Teksturin, royndur verður at seta inn, tykist at stava frá Word. Vilt tú reinsa tekstin, áðrenn hann verður settur inn?",
+NotCompatiblePaste : "Hetta er bert tøkt í Internet Explorer 5.5 og nýggjari. Vilt tú seta tekstin inn kortini - óreinsaðan?",
+UnknownToolbarItem : "Ókendur lutur í amboðsbjálkanum \"%1\"",
+UnknownCommand : "Ókend kommando \"%1\"",
+NotImplemented : "Hetta er ikki tøkt í hesi útgávuni",
+UnknownToolbarSet : "Amboðsbjálkin \"%1\" finst ikki",
+NoActiveX : "Trygdaruppsetingin í alnótskaganum kann sum er avmarka onkrar hentleikar í tekstviðgeranum. Tú mást loyva møguleikanum \"Run/Kør ActiveX controls and plug-ins\". Tú kanst uppliva feilir og ávaringar um tvørrandi hentleikar.",
+BrowseServerBlocked : "Ambætarakagin kundi ikki opnast. Tryggja tær, at allar pop-up forðingar eru óvirknar.",
+DialogBlocked : "Tað eyðnaðist ikki at opna samskiftisrútin. Tryggja tær, at allar pop-up forðingar eru óvirknar.",
+VisitLinkBlocked : "Tað eyðnaðist ikki at opna nýggjan rút. Tryggja tær, at allar pop-up forðingar eru óvirknar.",
+
+// Dialogs
+DlgBtnOK : "Góðkent",
+DlgBtnCancel : "Avlýst",
+DlgBtnClose : "Lat aftur",
+DlgBtnBrowseServer : "Ambætarakagi",
+DlgAdvancedTag : "Fjølbroytt",
+DlgOpOther : "<Annað>",
+DlgInfoTab : "Upplýsingar",
+DlgAlertUrl : "Vinarliga veit ein URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ikki sett>",
+DlgGenId : "Id",
+DlgGenLangDir : "Tekstkós",
+DlgGenLangDirLtr : "Frá vinstru til høgru (LTR)",
+DlgGenLangDirRtl : "Frá høgru til vinstru (RTL)",
+DlgGenLangCode : "Málkoda",
+DlgGenAccessKey : "Snarvegisknappur",
+DlgGenName : "Navn",
+DlgGenTabIndex : "Inntriv indeks",
+DlgGenLongDescr : "Víðkað URL frágreiðing",
+DlgGenClass : "Typografi klassar",
+DlgGenTitle : "Vegleiðandi heiti",
+DlgGenContType : "Vegleiðandi innihaldsslag",
+DlgGenLinkCharset : "Atknýtt teknsett",
+DlgGenStyle : "Typografi",
+
+// Image Dialog
+DlgImgTitle : "Myndaeginleikar",
+DlgImgInfoTab : "Myndaupplýsingar",
+DlgImgBtnUpload : "Send til ambætaran",
+DlgImgURL : "URL",
+DlgImgUpload : "Send",
+DlgImgAlt : "Alternativur tekstur",
+DlgImgWidth : "Breidd",
+DlgImgHeight : "Hædd",
+DlgImgLockRatio : "Læs lutfallið",
+DlgBtnResetSize : "Upprunastødd",
+DlgImgBorder : "Bordi",
+DlgImgHSpace : "Høgri breddi",
+DlgImgVSpace : "Vinstri breddi",
+DlgImgAlign : "Justering",
+DlgImgAlignLeft : "Vinstra",
+DlgImgAlignAbsBottom: "Abs botnur",
+DlgImgAlignAbsMiddle: "Abs miðja",
+DlgImgAlignBaseline : "Basislinja",
+DlgImgAlignBottom : "Botnur",
+DlgImgAlignMiddle : "Miðja",
+DlgImgAlignRight : "Høgra",
+DlgImgAlignTextTop : "Tekst toppur",
+DlgImgAlignTop : "Ovast",
+DlgImgPreview : "Frumsýning",
+DlgImgAlertUrl : "Rita slóðina til myndina",
+DlgImgLinkTab : "Tilknýti",
+
+// Flash Dialog
+DlgFlashTitle : "Flash eginleikar",
+DlgFlashChkPlay : "Avspælingin byrjar sjálv",
+DlgFlashChkLoop : "Endurspæl",
+DlgFlashChkMenu : "Ger Flash skrá virkna",
+DlgFlashScale : "Skalering",
+DlgFlashScaleAll : "Vís alt",
+DlgFlashScaleNoBorder : "Eingin bordi",
+DlgFlashScaleFit : "Neyv skalering",
+
+// Link Dialog
+DlgLnkWindowTitle : "Tilknýti",
+DlgLnkInfoTab : "Tilknýtis upplýsingar",
+DlgLnkTargetTab : "Mál",
+
+DlgLnkType : "Tilknýtisslag",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Tilknýti til marknastein í tekstinum",
+DlgLnkTypeEMail : "Teldupostur",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<Annað>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Vel ein marknastein",
+DlgLnkAnchorByName : "Eftir navni á marknasteini",
+DlgLnkAnchorById : "Eftir element Id",
+DlgLnkNoAnchors : "(Eingir marknasteinar eru í hesum dokumentið)",
+DlgLnkEMail : "Teldupost-adressa",
+DlgLnkEMailSubject : "Evni",
+DlgLnkEMailBody : "Breyðtekstur",
+DlgLnkUpload : "Send til ambætaran",
+DlgLnkBtnUpload : "Send til ambætaran",
+
+DlgLnkTarget : "Mál",
+DlgLnkTargetFrame : "<ramma>",
+DlgLnkTargetPopup : "<popup vindeyga>",
+DlgLnkTargetBlank : "Nýtt vindeyga (_blank)",
+DlgLnkTargetParent : "Upphavliga vindeygað (_parent)",
+DlgLnkTargetSelf : "Sama vindeygað (_self)",
+DlgLnkTargetTop : "Alt vindeygað (_top)",
+DlgLnkTargetFrameName : "Vís navn vindeygans",
+DlgLnkPopWinName : "Popup vindeygans navn",
+DlgLnkPopWinFeat : "Popup vindeygans víðkaðu eginleikar",
+DlgLnkPopResize : "Kann broyta stødd",
+DlgLnkPopLocation : "Adressulinja",
+DlgLnkPopMenu : "Skrábjálki",
+DlgLnkPopScroll : "Rullibjálki",
+DlgLnkPopStatus : "Støðufrágreiðingarbjálki",
+DlgLnkPopToolbar : "Amboðsbjálki",
+DlgLnkPopFullScrn : "Fullur skermur (IE)",
+DlgLnkPopDependent : "Bundið (Netscape)",
+DlgLnkPopWidth : "Breidd",
+DlgLnkPopHeight : "Hædd",
+DlgLnkPopLeft : "Frástøða frá vinstru",
+DlgLnkPopTop : "Frástøða frá íerva",
+
+DlnLnkMsgNoUrl : "Vinarliga skriva tilknýti (URL)",
+DlnLnkMsgNoEMail : "Vinarliga skriva teldupost-adressu",
+DlnLnkMsgNoAnchor : "Vinarliga vel marknastein",
+DlnLnkMsgInvPopName : "Popup navnið má byrja við bókstavi og má ikki hava millumrúm",
+
+// Color Dialog
+DlgColorTitle : "Vel lit",
+DlgColorBtnClear : "Strika alt",
+DlgColorHighlight : "Framhevja",
+DlgColorSelected : "Valt",
+
+// Smiley Dialog
+DlgSmileyTitle : "Vel Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Vel sertekn",
+
+// Table Dialog
+DlgTableTitle : "Eginleikar fyri tabell",
+DlgTableRows : "Røðir",
+DlgTableColumns : "Kolonnur",
+DlgTableBorder : "Bordabreidd",
+DlgTableAlign : "Justering",
+DlgTableAlignNotSet : "<Einki valt>",
+DlgTableAlignLeft : "Vinstrasett",
+DlgTableAlignCenter : "Miðsett",
+DlgTableAlignRight : "Høgrasett",
+DlgTableWidth : "Breidd",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "prosent",
+DlgTableHeight : "Hædd",
+DlgTableCellSpace : "Fjarstøða millum meskar",
+DlgTableCellPad : "Meskubreddi",
+DlgTableCaption : "Tabellfrágreiðing",
+DlgTableSummary : "Samandráttur",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Mesku eginleikar",
+DlgCellWidth : "Breidd",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "prosent",
+DlgCellHeight : "Hædd",
+DlgCellWordWrap : "Orðkloyving",
+DlgCellWordWrapNotSet : "<Einki valt>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nei",
+DlgCellHorAlign : "Vatnrøtt justering",
+DlgCellHorAlignNotSet : "<Einki valt>",
+DlgCellHorAlignLeft : "Vinstrasett",
+DlgCellHorAlignCenter : "Miðsett",
+DlgCellHorAlignRight: "Høgrasett",
+DlgCellVerAlign : "Lodrøtt justering",
+DlgCellVerAlignNotSet : "<Ikki sett>",
+DlgCellVerAlignTop : "Ovast",
+DlgCellVerAlignMiddle : "Miðjan",
+DlgCellVerAlignBottom : "Niðast",
+DlgCellVerAlignBaseline : "Basislinja",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Røðir, meskin fevnir um",
+DlgCellCollSpan : "Kolonnur, meskin fevnir um",
+DlgCellBackColor : "Bakgrundslitur",
+DlgCellBorderColor : "Litur á borda",
+DlgCellBtnSelect : "Vel...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Finn og broyt",
+
+// Find Dialog
+DlgFindTitle : "Finn",
+DlgFindFindBtn : "Finn",
+DlgFindNotFoundMsg : "Leititeksturin varð ikki funnin",
+
+// Replace Dialog
+DlgReplaceTitle : "Yvirskriva",
+DlgReplaceFindLbl : "Finn:",
+DlgReplaceReplaceLbl : "Yvirskriva við:",
+DlgReplaceCaseChk : "Munur á stórum og smáðum bókstavum",
+DlgReplaceReplaceBtn : "Yvirskriva",
+DlgReplaceReplAllBtn : "Yvirskriva alt",
+DlgReplaceWordChk : "Bert heil orð",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Trygdaruppseting alnótskagans forðar tekstviðgeranum í at kvetta tekstin. Vinarliga nýt knappaborðið til at kvetta tekstin (CTRL+X).",
+PasteErrorCopy : "Trygdaruppseting alnótskagans forðar tekstviðgeranum í at avrita tekstin. Vinarliga nýt knappaborðið til at avrita tekstin (CTRL+C).",
+
+PasteAsText : "Innrita som reinan tekst",
+PasteFromWord : "Innrita fra Word",
+
+DlgPasteMsg2 : "Vinarliga koyr tekstin í hendan rútin við knappaborðinum (<strong>CTRL+V</strong>) og klikk á <strong>Góðtak</strong>.",
+DlgPasteSec : "Trygdaruppseting alnótskagans forðar tekstviðgeranum í beinleiðis atgongd til avritingarminnið. Tygum mugu royna aftur í hesum rútinum.",
+DlgPasteIgnoreFont : "Forfjóna Font definitiónirnar",
+DlgPasteRemoveStyles : "Strika typografi definitiónir",
+
+// Color Picker
+ColorAutomatic : "Automatiskt",
+ColorMoreColors : "Fleiri litir...",
+
+// Document Properties
+DocProps : "Eginleikar fyri dokument",
+
+// Anchor Dialog
+DlgAnchorTitle : "Eginleikar fyri marknastein",
+DlgAnchorName : "Heiti marknasteinsins",
+DlgAnchorErrorName : "Vinarliga rita marknasteinsins heiti",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Finst ikki í orðabókini",
+DlgSpellChangeTo : "Broyt til",
+DlgSpellBtnIgnore : "Forfjóna",
+DlgSpellBtnIgnoreAll : "Forfjóna alt",
+DlgSpellBtnReplace : "Yvirskriva",
+DlgSpellBtnReplaceAll : "Yvirskriva alt",
+DlgSpellBtnUndo : "Angra",
+DlgSpellNoSuggestions : "- Einki uppskot -",
+DlgSpellProgress : "Rættstavarin arbeiðir...",
+DlgSpellNoMispell : "Rættstavarain liðugur: Eingin feilur funnin",
+DlgSpellNoChanges : "Rættstavarain liðugur: Einki orð varð broytt",
+DlgSpellOneChange : "Rættstavarain liðugur: Eitt orð er broytt",
+DlgSpellManyChanges : "Rættstavarain liðugur: %1 orð broytt",
+
+IeSpellDownload : "Rættstavarin er ikki tøkur í tekstviðgeranum. Vilt tú heinta hann nú?",
+
+// Button Dialog
+DlgButtonText : "Tekstur",
+DlgButtonType : "Slag",
+DlgButtonTypeBtn : "Knøttur",
+DlgButtonTypeSbm : "Send",
+DlgButtonTypeRst : "Nullstilla",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Navn",
+DlgCheckboxValue : "Virði",
+DlgCheckboxSelected : "Valt",
+
+// Form Dialog
+DlgFormName : "Navn",
+DlgFormAction : "Hending",
+DlgFormMethod : "Háttur",
+
+// Select Field Dialog
+DlgSelectName : "Navn",
+DlgSelectValue : "Virði",
+DlgSelectSize : "Stødd",
+DlgSelectLines : "Linjur",
+DlgSelectChkMulti : "Loyv fleiri valmøguleikum samstundis",
+DlgSelectOpAvail : "Tøkir møguleikar",
+DlgSelectOpText : "Tekstur",
+DlgSelectOpValue : "Virði",
+DlgSelectBtnAdd : "Legg afturat",
+DlgSelectBtnModify : "Broyt",
+DlgSelectBtnUp : "Upp",
+DlgSelectBtnDown : "Niður",
+DlgSelectBtnSetValue : "Set sum valt virði",
+DlgSelectBtnDelete : "Strika",
+
+// Textarea Dialog
+DlgTextareaName : "Navn",
+DlgTextareaCols : "kolonnur",
+DlgTextareaRows : "røðir",
+
+// Text Field Dialog
+DlgTextName : "Navn",
+DlgTextValue : "Virði",
+DlgTextCharWidth : "Breidd (sjónlig tekn)",
+DlgTextMaxChars : "Mest loyvdu tekn",
+DlgTextType : "Slag",
+DlgTextTypeText : "Tekstur",
+DlgTextTypePass : "Loyniorð",
+
+// Hidden Field Dialog
+DlgHiddenName : "Navn",
+DlgHiddenValue : "Virði",
+
+// Bulleted List Dialog
+BulletedListProp : "Eginleikar fyri punktmerktan lista",
+NumberedListProp : "Eginleikar fyri talmerktan lista",
+DlgLstStart : "Byrjan",
+DlgLstType : "Slag",
+DlgLstTypeCircle : "Sirkul",
+DlgLstTypeDisc : "Fyltur sirkul",
+DlgLstTypeSquare : "Fjórhyrningur",
+DlgLstTypeNumbers : "Talmerkt (1, 2, 3)",
+DlgLstTypeLCase : "Smáir bókstavir (a, b, c)",
+DlgLstTypeUCase : "Stórir bókstavir (A, B, C)",
+DlgLstTypeSRoman : "Smá rómaratøl (i, ii, iii)",
+DlgLstTypeLRoman : "Stór rómaratøl (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Generelt",
+DlgDocBackTab : "Bakgrund",
+DlgDocColorsTab : "Litir og breddar",
+DlgDocMetaTab : "META-upplýsingar",
+
+DlgDocPageTitle : "Síðuheiti",
+DlgDocLangDir : "Tekstkós",
+DlgDocLangDirLTR : "Frá vinstru móti høgru (LTR)",
+DlgDocLangDirRTL : "Frá høgru móti vinstru (RTL)",
+DlgDocLangCode : "Málkoda",
+DlgDocCharSet : "Teknsett koda",
+DlgDocCharSetCE : "Miðeuropa",
+DlgDocCharSetCT : "Kinesiskt traditionelt (Big5)",
+DlgDocCharSetCR : "Cyrilliskt",
+DlgDocCharSetGR : "Grikst",
+DlgDocCharSetJP : "Japanskt",
+DlgDocCharSetKR : "Koreanskt",
+DlgDocCharSetTR : "Turkiskt",
+DlgDocCharSetUN : "UNICODE (UTF-8)",
+DlgDocCharSetWE : "Vestureuropa",
+DlgDocCharSetOther : "Onnur teknsett koda",
+
+DlgDocDocType : "Dokumentslag yvirskrift",
+DlgDocDocTypeOther : "Annað dokumentslag yvirskrift",
+DlgDocIncXHTML : "Viðfest XHTML deklaratiónir",
+DlgDocBgColor : "Bakgrundslitur",
+DlgDocBgImage : "Leið til bakgrundsmynd (URL)",
+DlgDocBgNoScroll : "Læst bakgrund (rullar ikki)",
+DlgDocCText : "Tekstur",
+DlgDocCLink : "Tilknýti",
+DlgDocCVisited : "Vitjaði tilknýti",
+DlgDocCActive : "Virkin tilknýti",
+DlgDocMargins : "Síðubreddar",
+DlgDocMaTop : "Ovast",
+DlgDocMaLeft : "Vinstra",
+DlgDocMaRight : "Høgra",
+DlgDocMaBottom : "Niðast",
+DlgDocMeIndex : "Dokument index lyklaorð (sundurbýtt við komma)",
+DlgDocMeDescr : "Dokumentlýsing",
+DlgDocMeAuthor : "Høvundur",
+DlgDocMeCopy : "Upphavsrættindi",
+DlgDocPreview : "Frumsýning",
+
+// Templates Dialog
+Templates : "Skabelónir",
+DlgTemplatesTitle : "Innihaldsskabelónir",
+DlgTemplatesSelMsg : "Vinarliga vel ta skabelón, ið skal opnast í tekstviðgeranum<br>(Hetta yvirskrivar núverandi innihald):",
+DlgTemplatesLoading : "Heinti yvirlit yvir skabelónir. Vinarliga bíða við...",
+DlgTemplatesNoTpl : "(Ongar skabelónir tøkar)",
+DlgTemplatesReplace : "Yvirskriva núverandi innihald",
+
+// About Dialog
+DlgAboutAboutTab : "Um",
+DlgAboutBrowserInfoTab : "Upplýsingar um alnótskagan",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "version",
+DlgAboutInfo : "Fyri fleiri upplýsingar, far til",
+
+// Div Dialog
+DlgDivGeneralTab : "Generelt",
+DlgDivAdvancedTab : "Fjølbroytt",
+DlgDivStyle : "Typografi",
+DlgDivInlineStyle : "Inline typografi",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/fr-ca.js b/httemplate/elements/fckeditor/editor/lang/fr-ca.js
new file mode 100644
index 0000000..2489bd6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/fr-ca.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Canadian French language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Masquer Outils",
+ToolbarExpand : "Afficher Outils",
+
+// Toolbar Items and Context Menu
+Save : "Sauvegarder",
+NewPage : "Nouvelle page",
+Preview : "Previsualiser",
+Cut : "Couper",
+Copy : "Copier",
+Paste : "Coller",
+PasteText : "Coller en tant que texte",
+PasteWord : "Coller en tant que Word (formaté)",
+Print : "Imprimer",
+SelectAll : "Tout sélectionner",
+RemoveFormat : "Supprimer le formatage",
+InsertLinkLbl : "Lien",
+InsertLink : "Insérer/modifier le lien",
+RemoveLink : "Supprimer le lien",
+VisitLink : "Suivre le lien",
+Anchor : "Insérer/modifier l'ancre",
+AnchorDelete : "Supprimer l'ancre",
+InsertImageLbl : "Image",
+InsertImage : "Insérer/modifier l'image",
+InsertFlashLbl : "Animation Flash",
+InsertFlash : "Insérer/modifier l'animation Flash",
+InsertTableLbl : "Tableau",
+InsertTable : "Insérer/modifier le tableau",
+InsertLineLbl : "Séparateur",
+InsertLine : "Insérer un séparateur",
+InsertSpecialCharLbl: "Caractères spéciaux",
+InsertSpecialChar : "Insérer un caractère spécial",
+InsertSmileyLbl : "Emoticon",
+InsertSmiley : "Insérer un Emoticon",
+About : "A propos de FCKeditor",
+Bold : "Gras",
+Italic : "Italique",
+Underline : "Souligné",
+StrikeThrough : "Barrer",
+Subscript : "Indice",
+Superscript : "Exposant",
+LeftJustify : "Aligner à gauche",
+CenterJustify : "Centrer",
+RightJustify : "Aligner à Droite",
+BlockJustify : "Texte justifié",
+DecreaseIndent : "Diminuer le retrait",
+IncreaseIndent : "Augmenter le retrait",
+Blockquote : "Citation",
+CreateDiv : "Créer Balise Div",
+EditDiv : "Modifier Balise Div",
+DeleteDiv : "Supprimer Balise Div",
+Undo : "Annuler",
+Redo : "Refaire",
+NumberedListLbl : "Liste numérotée",
+NumberedList : "Insérer/supprimer la liste numérotée",
+BulletedListLbl : "Liste à puces",
+BulletedList : "Insérer/supprimer la liste à puces",
+ShowTableBorders : "Afficher les bordures du tableau",
+ShowDetails : "Afficher les caractères invisibles",
+Style : "Style",
+FontFormat : "Format",
+Font : "Police",
+FontSize : "Taille",
+TextColor : "Couleur de caractère",
+BGColor : "Couleur de fond",
+Source : "Source",
+Find : "Chercher",
+Replace : "Remplacer",
+SpellCheck : "Orthographe",
+UniversalKeyboard : "Clavier universel",
+PageBreakLbl : "Saut de page",
+PageBreak : "Insérer un saut de page",
+
+Form : "Formulaire",
+Checkbox : "Case à cocher",
+RadioButton : "Bouton radio",
+TextField : "Champ texte",
+Textarea : "Zone de texte",
+HiddenField : "Champ caché",
+Button : "Bouton",
+SelectionField : "Champ de sélection",
+ImageButton : "Bouton image",
+
+FitWindow : "Edition pleine page",
+ShowBlocks : "Afficher les blocs",
+
+// Context Menu
+EditLink : "Modifier le lien",
+CellCM : "Cellule",
+RowCM : "Ligne",
+ColumnCM : "Colonne",
+InsertRowAfter : "Insérer une ligne après",
+InsertRowBefore : "Insérer une ligne avant",
+DeleteRows : "Supprimer des lignes",
+InsertColumnAfter : "Insérer une colonne après",
+InsertColumnBefore : "Insérer une colonne avant",
+DeleteColumns : "Supprimer des colonnes",
+InsertCellAfter : "Insérer une cellule après",
+InsertCellBefore : "Insérer une cellule avant",
+DeleteCells : "Supprimer des cellules",
+MergeCells : "Fusionner les cellules",
+MergeRight : "Fusionner à droite",
+MergeDown : "Fusionner en bas",
+HorizontalSplitCell : "Scinder la cellule horizontalement",
+VerticalSplitCell : "Scinder la cellule verticalement",
+TableDelete : "Supprimer le tableau",
+CellProperties : "Propriétés de cellule",
+TableProperties : "Propriétés du tableau",
+ImageProperties : "Propriétés de l'image",
+FlashProperties : "Propriétés de l'animation Flash",
+
+AnchorProp : "Propriétés de l'ancre",
+ButtonProp : "Propriétés du bouton",
+CheckboxProp : "Propriétés de la case à cocher",
+HiddenFieldProp : "Propriétés du champ caché",
+RadioButtonProp : "Propriétés du bouton radio",
+ImageButtonProp : "Propriétés du bouton image",
+TextFieldProp : "Propriétés du champ texte",
+SelectionFieldProp : "Propriétés de la liste/du menu",
+TextareaProp : "Propriétés de la zone de texte",
+FormProp : "Propriétés du formulaire",
+
+FontFormats : "Normal;Formaté;Adresse;En-tête 1;En-tête 2;En-tête 3;En-tête 4;En-tête 5;En-tête 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Calcul XHTML. Veuillez patienter...",
+Done : "Terminé",
+PasteWordConfirm : "Le texte à coller semble provenir de Word. Désirez-vous le nettoyer avant de coller?",
+NotCompatiblePaste : "Cette commande nécessite Internet Explorer version 5.5 et plus. Souhaitez-vous coller sans nettoyage?",
+UnknownToolbarItem : "Élément de barre d'outil inconnu \"%1\"",
+UnknownCommand : "Nom de commande inconnu \"%1\"",
+NotImplemented : "Commande indisponible",
+UnknownToolbarSet : "La barre d'outils \"%1\" n'existe pas",
+NoActiveX : "Les paramètres de sécurité de votre navigateur peuvent limiter quelques fonctionnalités de l'éditeur. Veuillez activer l'option \"Exécuter les contrôles ActiveX et les plug-ins\". Il se peut que vous rencontriez des erreurs et remarquiez quelques limitations.",
+BrowseServerBlocked : "Le navigateur n'a pas pu être ouvert. Assurez-vous que les bloqueurs de popups soient désactivés.",
+DialogBlocked : "La fenêtre de dialogue n'a pas pu s'ouvrir. Assurez-vous que les bloqueurs de popups soient désactivés.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Annuler",
+DlgBtnClose : "Fermer",
+DlgBtnBrowseServer : "Parcourir le serveur",
+DlgAdvancedTag : "Avancée",
+DlgOpOther : "<autre>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Veuillez saisir l'URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<Par défaut>",
+DlgGenId : "Id",
+DlgGenLangDir : "Sens d'écriture",
+DlgGenLangDirLtr : "De gauche à droite (LTR)",
+DlgGenLangDirRtl : "De droite à gauche (RTL)",
+DlgGenLangCode : "Code langue",
+DlgGenAccessKey : "Équivalent clavier",
+DlgGenName : "Nom",
+DlgGenTabIndex : "Ordre de tabulation",
+DlgGenLongDescr : "URL de description longue",
+DlgGenClass : "Classes de feuilles de style",
+DlgGenTitle : "Titre",
+DlgGenContType : "Type de contenu",
+DlgGenLinkCharset : "Encodage de caractère",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Propriétés de l'image",
+DlgImgInfoTab : "Informations sur l'image",
+DlgImgBtnUpload : "Envoyer sur le serveur",
+DlgImgURL : "URL",
+DlgImgUpload : "Télécharger",
+DlgImgAlt : "Texte de remplacement",
+DlgImgWidth : "Largeur",
+DlgImgHeight : "Hauteur",
+DlgImgLockRatio : "Garder les proportions",
+DlgBtnResetSize : "Taille originale",
+DlgImgBorder : "Bordure",
+DlgImgHSpace : "Espacement horizontal",
+DlgImgVSpace : "Espacement vertical",
+DlgImgAlign : "Alignement",
+DlgImgAlignLeft : "Gauche",
+DlgImgAlignAbsBottom: "Abs Bas",
+DlgImgAlignAbsMiddle: "Abs Milieu",
+DlgImgAlignBaseline : "Bas du texte",
+DlgImgAlignBottom : "Bas",
+DlgImgAlignMiddle : "Milieu",
+DlgImgAlignRight : "Droite",
+DlgImgAlignTextTop : "Haut du texte",
+DlgImgAlignTop : "Haut",
+DlgImgPreview : "Prévisualisation",
+DlgImgAlertUrl : "Veuillez saisir l'URL de l'image",
+DlgImgLinkTab : "Lien",
+
+// Flash Dialog
+DlgFlashTitle : "Propriétés de l'animation Flash",
+DlgFlashChkPlay : "Lecture automatique",
+DlgFlashChkLoop : "Boucle",
+DlgFlashChkMenu : "Activer le menu Flash",
+DlgFlashScale : "Affichage",
+DlgFlashScaleAll : "Par défaut (tout montrer)",
+DlgFlashScaleNoBorder : "Sans bordure",
+DlgFlashScaleFit : "Ajuster aux dimensions",
+
+// Link Dialog
+DlgLnkWindowTitle : "Propriétés du lien",
+DlgLnkInfoTab : "Informations sur le lien",
+DlgLnkTargetTab : "Destination",
+
+DlgLnkType : "Type de lien",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ancre dans cette page",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocole",
+DlgLnkProtoOther : "<autre>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Sélectionner une ancre",
+DlgLnkAnchorByName : "Par nom",
+DlgLnkAnchorById : "Par id",
+DlgLnkNoAnchors : "(Pas d'ancre disponible dans le document)",
+DlgLnkEMail : "Adresse E-Mail",
+DlgLnkEMailSubject : "Sujet du message",
+DlgLnkEMailBody : "Corps du message",
+DlgLnkUpload : "Télécharger",
+DlgLnkBtnUpload : "Envoyer sur le serveur",
+
+DlgLnkTarget : "Destination",
+DlgLnkTargetFrame : "<Cadre>",
+DlgLnkTargetPopup : "<fenêtre popup>",
+DlgLnkTargetBlank : "Nouvelle fenêtre (_blank)",
+DlgLnkTargetParent : "Fenêtre mère (_parent)",
+DlgLnkTargetSelf : "Même fenêtre (_self)",
+DlgLnkTargetTop : "Fenêtre supérieure (_top)",
+DlgLnkTargetFrameName : "Nom du cadre de destination",
+DlgLnkPopWinName : "Nom de la fenêtre popup",
+DlgLnkPopWinFeat : "Caractéristiques de la fenêtre popup",
+DlgLnkPopResize : "Taille modifiable",
+DlgLnkPopLocation : "Barre d'adresses",
+DlgLnkPopMenu : "Barre de menu",
+DlgLnkPopScroll : "Barres de défilement",
+DlgLnkPopStatus : "Barre d'état",
+DlgLnkPopToolbar : "Barre d'outils",
+DlgLnkPopFullScrn : "Plein écran (IE)",
+DlgLnkPopDependent : "Dépendante (Netscape)",
+DlgLnkPopWidth : "Largeur",
+DlgLnkPopHeight : "Hauteur",
+DlgLnkPopLeft : "Position à partir de la gauche",
+DlgLnkPopTop : "Position à partir du haut",
+
+DlnLnkMsgNoUrl : "Veuillez saisir l'URL",
+DlnLnkMsgNoEMail : "Veuillez saisir l'adresse e-mail",
+DlnLnkMsgNoAnchor : "Veuillez sélectionner une ancre",
+DlnLnkMsgInvPopName : "Le nom de la fenêtre popup doit commencer par une lettre et ne doit pas contenir d'espace",
+
+// Color Dialog
+DlgColorTitle : "Sélectionner",
+DlgColorBtnClear : "Effacer",
+DlgColorHighlight : "Prévisualisation",
+DlgColorSelected : "Sélectionné",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insérer un Emoticon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Insérer un caractère spécial",
+
+// Table Dialog
+DlgTableTitle : "Propriétés du tableau",
+DlgTableRows : "Lignes",
+DlgTableColumns : "Colonnes",
+DlgTableBorder : "Taille de la bordure",
+DlgTableAlign : "Alignement",
+DlgTableAlignNotSet : "<Par défaut>",
+DlgTableAlignLeft : "Gauche",
+DlgTableAlignCenter : "Centré",
+DlgTableAlignRight : "Droite",
+DlgTableWidth : "Largeur",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "pourcentage",
+DlgTableHeight : "Hauteur",
+DlgTableCellSpace : "Espacement",
+DlgTableCellPad : "Contour",
+DlgTableCaption : "Titre",
+DlgTableSummary : "Résumé",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Propriétés de la cellule",
+DlgCellWidth : "Largeur",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "pourcentage",
+DlgCellHeight : "Hauteur",
+DlgCellWordWrap : "Retour à la ligne",
+DlgCellWordWrapNotSet : "<Par défaut>",
+DlgCellWordWrapYes : "Oui",
+DlgCellWordWrapNo : "Non",
+DlgCellHorAlign : "Alignement horizontal",
+DlgCellHorAlignNotSet : "<Par défaut>",
+DlgCellHorAlignLeft : "Gauche",
+DlgCellHorAlignCenter : "Centré",
+DlgCellHorAlignRight: "Droite",
+DlgCellVerAlign : "Alignement vertical",
+DlgCellVerAlignNotSet : "<Par défaut>",
+DlgCellVerAlignTop : "Haut",
+DlgCellVerAlignMiddle : "Milieu",
+DlgCellVerAlignBottom : "Bas",
+DlgCellVerAlignBaseline : "Bas du texte",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Lignes fusionnées",
+DlgCellCollSpan : "Colonnes fusionnées",
+DlgCellBackColor : "Couleur de fond",
+DlgCellBorderColor : "Couleur de bordure",
+DlgCellBtnSelect : "Sélectionner...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Chercher et Remplacer",
+
+// Find Dialog
+DlgFindTitle : "Chercher",
+DlgFindFindBtn : "Chercher",
+DlgFindNotFoundMsg : "Le texte indiqué est introuvable.",
+
+// Replace Dialog
+DlgReplaceTitle : "Remplacer",
+DlgReplaceFindLbl : "Rechercher:",
+DlgReplaceReplaceLbl : "Remplacer par:",
+DlgReplaceCaseChk : "Respecter la casse",
+DlgReplaceReplaceBtn : "Remplacer",
+DlgReplaceReplAllBtn : "Tout remplacer",
+DlgReplaceWordChk : "Mot entier",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Les paramètres de sécurité de votre navigateur empêchent l'éditeur de couper automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl+X).",
+PasteErrorCopy : "Les paramètres de sécurité de votre navigateur empêchent l'éditeur de copier automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl+C).",
+
+PasteAsText : "Coller comme texte",
+PasteFromWord : "Coller à partir de Word",
+
+DlgPasteMsg2 : "Veuillez coller dans la zone ci-dessous en utilisant le clavier (<STRONG>Ctrl+V</STRONG>) et appuyer sur <STRONG>OK</STRONG>.",
+DlgPasteSec : "A cause des paramètres de sécurité de votre navigateur, l'éditeur ne peut accéder au presse-papier directement. Vous devez coller à nouveau le contenu dans cette fenêtre.",
+DlgPasteIgnoreFont : "Ignorer les polices de caractères",
+DlgPasteRemoveStyles : "Supprimer les styles",
+
+// Color Picker
+ColorAutomatic : "Automatique",
+ColorMoreColors : "Plus de couleurs...",
+
+// Document Properties
+DocProps : "Propriétés du document",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propriétés de l'ancre",
+DlgAnchorName : "Nom de l'ancre",
+DlgAnchorErrorName : "Veuillez saisir le nom de l'ancre",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Pas dans le dictionnaire",
+DlgSpellChangeTo : "Changer en",
+DlgSpellBtnIgnore : "Ignorer",
+DlgSpellBtnIgnoreAll : "Ignorer tout",
+DlgSpellBtnReplace : "Remplacer",
+DlgSpellBtnReplaceAll : "Remplacer tout",
+DlgSpellBtnUndo : "Annuler",
+DlgSpellNoSuggestions : "- Pas de suggestion -",
+DlgSpellProgress : "Vérification d'orthographe en cours...",
+DlgSpellNoMispell : "Vérification d'orthographe terminée: pas d'erreur trouvée",
+DlgSpellNoChanges : "Vérification d'orthographe terminée: Pas de modifications",
+DlgSpellOneChange : "Vérification d'orthographe terminée: Un mot modifié",
+DlgSpellManyChanges : "Vérification d'orthographe terminée: %1 mots modifiés",
+
+IeSpellDownload : "Le Correcteur d'orthographe n'est pas installé. Souhaitez-vous le télécharger maintenant?",
+
+// Button Dialog
+DlgButtonText : "Texte (Valeur)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Bouton",
+DlgButtonTypeSbm : "Soumettre",
+DlgButtonTypeRst : "Réinitialiser",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nom",
+DlgCheckboxValue : "Valeur",
+DlgCheckboxSelected : "Sélectionné",
+
+// Form Dialog
+DlgFormName : "Nom",
+DlgFormAction : "Action",
+DlgFormMethod : "Méthode",
+
+// Select Field Dialog
+DlgSelectName : "Nom",
+DlgSelectValue : "Valeur",
+DlgSelectSize : "Taille",
+DlgSelectLines : "lignes",
+DlgSelectChkMulti : "Sélection multiple",
+DlgSelectOpAvail : "Options disponibles",
+DlgSelectOpText : "Texte",
+DlgSelectOpValue : "Valeur",
+DlgSelectBtnAdd : "Ajouter",
+DlgSelectBtnModify : "Modifier",
+DlgSelectBtnUp : "Monter",
+DlgSelectBtnDown : "Descendre",
+DlgSelectBtnSetValue : "Valeur sélectionnée",
+DlgSelectBtnDelete : "Supprimer",
+
+// Textarea Dialog
+DlgTextareaName : "Nom",
+DlgTextareaCols : "Colonnes",
+DlgTextareaRows : "Lignes",
+
+// Text Field Dialog
+DlgTextName : "Nom",
+DlgTextValue : "Valeur",
+DlgTextCharWidth : "Largeur en caractères",
+DlgTextMaxChars : "Nombre maximum de caractères",
+DlgTextType : "Type",
+DlgTextTypeText : "Texte",
+DlgTextTypePass : "Mot de passe",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nom",
+DlgHiddenValue : "Valeur",
+
+// Bulleted List Dialog
+BulletedListProp : "Propriétés de liste à puces",
+NumberedListProp : "Propriétés de liste numérotée",
+DlgLstStart : "Début",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Cercle",
+DlgLstTypeDisc : "Disque",
+DlgLstTypeSquare : "Carré",
+DlgLstTypeNumbers : "Nombres (1, 2, 3)",
+DlgLstTypeLCase : "Lettres minuscules (a, b, c)",
+DlgLstTypeUCase : "Lettres majuscules (A, B, C)",
+DlgLstTypeSRoman : "Chiffres romains minuscules (i, ii, iii)",
+DlgLstTypeLRoman : "Chiffres romains majuscules (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Général",
+DlgDocBackTab : "Fond",
+DlgDocColorsTab : "Couleurs et Marges",
+DlgDocMetaTab : "Méta-Données",
+
+DlgDocPageTitle : "Titre de la page",
+DlgDocLangDir : "Sens d'écriture",
+DlgDocLangDirLTR : "De la gauche vers la droite (LTR)",
+DlgDocLangDirRTL : "De la droite vers la gauche (RTL)",
+DlgDocLangCode : "Code langue",
+DlgDocCharSet : "Encodage de caractère",
+DlgDocCharSetCE : "Europe Centrale",
+DlgDocCharSetCT : "Chinois Traditionnel (Big5)",
+DlgDocCharSetCR : "Cyrillique",
+DlgDocCharSetGR : "Grecque",
+DlgDocCharSetJP : "Japonais",
+DlgDocCharSetKR : "Coréen",
+DlgDocCharSetTR : "Turcque",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Occidental",
+DlgDocCharSetOther : "Autre encodage de caractère",
+
+DlgDocDocType : "Type de document",
+DlgDocDocTypeOther : "Autre type de document",
+DlgDocIncXHTML : "Inclure les déclarations XHTML",
+DlgDocBgColor : "Couleur de fond",
+DlgDocBgImage : "Image de fond",
+DlgDocBgNoScroll : "Image fixe sans défilement",
+DlgDocCText : "Texte",
+DlgDocCLink : "Lien",
+DlgDocCVisited : "Lien visité",
+DlgDocCActive : "Lien activé",
+DlgDocMargins : "Marges",
+DlgDocMaTop : "Haut",
+DlgDocMaLeft : "Gauche",
+DlgDocMaRight : "Droite",
+DlgDocMaBottom : "Bas",
+DlgDocMeIndex : "Mots-clés (séparés par des virgules)",
+DlgDocMeDescr : "Description",
+DlgDocMeAuthor : "Auteur",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Prévisualisation",
+
+// Templates Dialog
+Templates : "Modèles",
+DlgTemplatesTitle : "Modèles de contenu",
+DlgTemplatesSelMsg : "Sélectionner le modèle à ouvrir dans l'éditeur<br>(le contenu actuel sera remplacé):",
+DlgTemplatesLoading : "Chargement de la liste des modèles. Veuillez patienter...",
+DlgTemplatesNoTpl : "(Aucun modèle disponible)",
+DlgTemplatesReplace : "Remplacer tout le contenu actuel",
+
+// About Dialog
+DlgAboutAboutTab : "Ã propos de",
+DlgAboutBrowserInfoTab : "Navigateur",
+DlgAboutLicenseTab : "License",
+DlgAboutVersion : "Version",
+DlgAboutInfo : "Pour plus d'informations, visiter",
+
+// Div Dialog
+DlgDivGeneralTab : "Général",
+DlgDivAdvancedTab : "Avancé",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Attribut Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/fr.js b/httemplate/elements/fckeditor/editor/lang/fr.js
new file mode 100644
index 0000000..a03558f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/fr.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * French language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Masquer Outils",
+ToolbarExpand : "Afficher Outils",
+
+// Toolbar Items and Context Menu
+Save : "Enregistrer",
+NewPage : "Nouvelle page",
+Preview : "Prévisualisation",
+Cut : "Couper",
+Copy : "Copier",
+Paste : "Coller",
+PasteText : "Coller comme texte",
+PasteWord : "Coller de Word",
+Print : "Imprimer",
+SelectAll : "Tout sélectionner",
+RemoveFormat : "Supprimer le format",
+InsertLinkLbl : "Lien",
+InsertLink : "Insérer/modifier le lien",
+RemoveLink : "Supprimer le lien",
+VisitLink : "Suivre le lien",
+Anchor : "Insérer/modifier l'ancre",
+AnchorDelete : "Supprimer l'ancre",
+InsertImageLbl : "Image",
+InsertImage : "Insérer/modifier l'image",
+InsertFlashLbl : "Animation Flash",
+InsertFlash : "Insérer/modifier l'animation Flash",
+InsertTableLbl : "Tableau",
+InsertTable : "Insérer/modifier le tableau",
+InsertLineLbl : "Séparateur",
+InsertLine : "Insérer un séparateur",
+InsertSpecialCharLbl: "Caractères spéciaux",
+InsertSpecialChar : "Insérer un caractère spécial",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Insérer un Smiley",
+About : "A propos de FCKeditor",
+Bold : "Gras",
+Italic : "Italique",
+Underline : "Souligné",
+StrikeThrough : "Barré",
+Subscript : "Indice",
+Superscript : "Exposant",
+LeftJustify : "Aligné à gauche",
+CenterJustify : "Centré",
+RightJustify : "Aligné à Droite",
+BlockJustify : "Texte justifié",
+DecreaseIndent : "Diminuer le retrait",
+IncreaseIndent : "Augmenter le retrait",
+Blockquote : "Citation",
+CreateDiv : "Créer Balise Div",
+EditDiv : "Modifier Balise Div",
+DeleteDiv : "Supprimer Balise Div",
+Undo : "Annuler",
+Redo : "Refaire",
+NumberedListLbl : "Liste numérotée",
+NumberedList : "Insérer/supprimer la liste numérotée",
+BulletedListLbl : "Liste à puces",
+BulletedList : "Insérer/supprimer la liste à puces",
+ShowTableBorders : "Afficher les bordures du tableau",
+ShowDetails : "Afficher les caractères invisibles",
+Style : "Style",
+FontFormat : "Format",
+Font : "Police",
+FontSize : "Taille",
+TextColor : "Couleur de caractère",
+BGColor : "Couleur de fond",
+Source : "Source",
+Find : "Chercher",
+Replace : "Remplacer",
+SpellCheck : "Orthographe",
+UniversalKeyboard : "Clavier universel",
+PageBreakLbl : "Saut de page",
+PageBreak : "Insérer un saut de page",
+
+Form : "Formulaire",
+Checkbox : "Case à cocher",
+RadioButton : "Bouton radio",
+TextField : "Champ texte",
+Textarea : "Zone de texte",
+HiddenField : "Champ caché",
+Button : "Bouton",
+SelectionField : "Liste/menu",
+ImageButton : "Bouton image",
+
+FitWindow : "Edition pleine page",
+ShowBlocks : "Afficher les blocs",
+
+// Context Menu
+EditLink : "Modifier le lien",
+CellCM : "Cellule",
+RowCM : "Ligne",
+ColumnCM : "Colonne",
+InsertRowAfter : "Insérer une ligne après",
+InsertRowBefore : "Insérer une ligne avant",
+DeleteRows : "Supprimer des lignes",
+InsertColumnAfter : "Insérer une colonne après",
+InsertColumnBefore : "Insérer une colonne avant",
+DeleteColumns : "Supprimer des colonnes",
+InsertCellAfter : "Insérer une cellule après",
+InsertCellBefore : "Insérer une cellule avant",
+DeleteCells : "Supprimer des cellules",
+MergeCells : "Fusionner les cellules",
+MergeRight : "Fusionner à droite",
+MergeDown : "Fusionner en bas",
+HorizontalSplitCell : "Scinder la cellule horizontalement",
+VerticalSplitCell : "Scinder la cellule verticalement",
+TableDelete : "Supprimer le tableau",
+CellProperties : "Propriétés de cellule",
+TableProperties : "Propriétés du tableau",
+ImageProperties : "Propriétés de l'image",
+FlashProperties : "Propriétés de l'animation Flash",
+
+AnchorProp : "Propriétés de l'ancre",
+ButtonProp : "Propriétés du bouton",
+CheckboxProp : "Propriétés de la case à cocher",
+HiddenFieldProp : "Propriétés du champ caché",
+RadioButtonProp : "Propriétés du bouton radio",
+ImageButtonProp : "Propriétés du bouton image",
+TextFieldProp : "Propriétés du champ texte",
+SelectionFieldProp : "Propriétés de la liste/du menu",
+TextareaProp : "Propriétés de la zone de texte",
+FormProp : "Propriétés du formulaire",
+
+FontFormats : "Normal;Formaté;Adresse;En-tête 1;En-tête 2;En-tête 3;En-tête 4;En-tête 5;En-tête 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Calcul XHTML. Veuillez patienter...",
+Done : "Terminé",
+PasteWordConfirm : "Le texte à coller semble provenir de Word. Désirez-vous le nettoyer avant de coller?",
+NotCompatiblePaste : "Cette commande nécessite Internet Explorer version 5.5 minimum. Souhaitez-vous coller sans nettoyage?",
+UnknownToolbarItem : "Elément de barre d'outil inconnu \"%1\"",
+UnknownCommand : "Nom de commande inconnu \"%1\"",
+NotImplemented : "Commande non encore écrite",
+UnknownToolbarSet : "La barre d'outils \"%1\" n'existe pas",
+NoActiveX : "Les paramètres de sécurité de votre navigateur peuvent limiter quelques fonctionnalités de l'éditeur. Veuillez activer l'option \"Exécuter les contrôles ActiveX et les plug-ins\". Il se peut que vous rencontriez des erreurs et remarquiez quelques limitations.",
+BrowseServerBlocked : "Le navigateur n'a pas pu être ouvert. Assurez-vous que les bloqueurs de popups soient désactivés.",
+DialogBlocked : "La fenêtre de dialogue n'a pas pu s'ouvrir. Assurez-vous que les bloqueurs de popups soient désactivés.",
+VisitLinkBlocked : "Impossible d'ouvrir une nouvelle fenêtre. Assurez-vous que les bloqueurs de popups soient désactivés.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Annuler",
+DlgBtnClose : "Fermer",
+DlgBtnBrowseServer : "Parcourir le serveur",
+DlgAdvancedTag : "Avancé",
+DlgOpOther : "<Autre>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Veuillez saisir l'URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<Par défaut>",
+DlgGenId : "Id",
+DlgGenLangDir : "Sens d'écriture",
+DlgGenLangDirLtr : "De gauche à droite (LTR)",
+DlgGenLangDirRtl : "De droite à gauche (RTL)",
+DlgGenLangCode : "Code langue",
+DlgGenAccessKey : "Equivalent clavier",
+DlgGenName : "Nom",
+DlgGenTabIndex : "Ordre de tabulation",
+DlgGenLongDescr : "URL de description longue",
+DlgGenClass : "Classes de feuilles de style",
+DlgGenTitle : "Titre",
+DlgGenContType : "Type de contenu",
+DlgGenLinkCharset : "Encodage de caractère",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "Propriétés de l'image",
+DlgImgInfoTab : "Informations sur l'image",
+DlgImgBtnUpload : "Envoyer sur le serveur",
+DlgImgURL : "URL",
+DlgImgUpload : "Télécharger",
+DlgImgAlt : "Texte de remplacement",
+DlgImgWidth : "Largeur",
+DlgImgHeight : "Hauteur",
+DlgImgLockRatio : "Garder les proportions",
+DlgBtnResetSize : "Taille originale",
+DlgImgBorder : "Bordure",
+DlgImgHSpace : "Espacement horizontal",
+DlgImgVSpace : "Espacement vertical",
+DlgImgAlign : "Alignement",
+DlgImgAlignLeft : "Gauche",
+DlgImgAlignAbsBottom: "Abs Bas",
+DlgImgAlignAbsMiddle: "Abs Milieu",
+DlgImgAlignBaseline : "Bas du texte",
+DlgImgAlignBottom : "Bas",
+DlgImgAlignMiddle : "Milieu",
+DlgImgAlignRight : "Droite",
+DlgImgAlignTextTop : "Haut du texte",
+DlgImgAlignTop : "Haut",
+DlgImgPreview : "Prévisualisation",
+DlgImgAlertUrl : "Veuillez saisir l'URL de l'image",
+DlgImgLinkTab : "Lien",
+
+// Flash Dialog
+DlgFlashTitle : "Propriétés de l'animation Flash",
+DlgFlashChkPlay : "Lecture automatique",
+DlgFlashChkLoop : "Boucle",
+DlgFlashChkMenu : "Activer le menu Flash",
+DlgFlashScale : "Affichage",
+DlgFlashScaleAll : "Par défaut (tout montrer)",
+DlgFlashScaleNoBorder : "Sans bordure",
+DlgFlashScaleFit : "Ajuster aux dimensions",
+
+// Link Dialog
+DlgLnkWindowTitle : "Propriétés du lien",
+DlgLnkInfoTab : "Informations sur le lien",
+DlgLnkTargetTab : "Destination",
+
+DlgLnkType : "Type de lien",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ancre dans cette page",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocole",
+DlgLnkProtoOther : "<autre>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Sélectionner une ancre",
+DlgLnkAnchorByName : "Par nom",
+DlgLnkAnchorById : "Par id",
+DlgLnkNoAnchors : "(Pas d'ancre disponible dans le document)",
+DlgLnkEMail : "Adresse E-Mail",
+DlgLnkEMailSubject : "Sujet du message",
+DlgLnkEMailBody : "Corps du message",
+DlgLnkUpload : "Télécharger",
+DlgLnkBtnUpload : "Envoyer sur le serveur",
+
+DlgLnkTarget : "Destination",
+DlgLnkTargetFrame : "<Cadre>",
+DlgLnkTargetPopup : "<fenêtre popup>",
+DlgLnkTargetBlank : "Nouvelle fenêtre (_blank)",
+DlgLnkTargetParent : "Fenêtre mère (_parent)",
+DlgLnkTargetSelf : "Même fenêtre (_self)",
+DlgLnkTargetTop : "Fenêtre supérieure (_top)",
+DlgLnkTargetFrameName : "Nom du cadre de destination",
+DlgLnkPopWinName : "Nom de la fenêtre popup",
+DlgLnkPopWinFeat : "Caractéristiques de la fenêtre popup",
+DlgLnkPopResize : "Taille modifiable",
+DlgLnkPopLocation : "Barre d'adresses",
+DlgLnkPopMenu : "Barre de menu",
+DlgLnkPopScroll : "Barres de défilement",
+DlgLnkPopStatus : "Barre d'état",
+DlgLnkPopToolbar : "Barre d'outils",
+DlgLnkPopFullScrn : "Plein écran (IE)",
+DlgLnkPopDependent : "Dépendante (Netscape)",
+DlgLnkPopWidth : "Largeur",
+DlgLnkPopHeight : "Hauteur",
+DlgLnkPopLeft : "Position à partir de la gauche",
+DlgLnkPopTop : "Position à partir du haut",
+
+DlnLnkMsgNoUrl : "Veuillez saisir l'URL",
+DlnLnkMsgNoEMail : "Veuillez saisir l'adresse e-mail",
+DlnLnkMsgNoAnchor : "Veuillez sélectionner une ancre",
+DlnLnkMsgInvPopName : "Le nom de la fenêtre popup doit commencer par une lettre et ne doit pas contenir d'espace",
+
+// Color Dialog
+DlgColorTitle : "Sélectionner",
+DlgColorBtnClear : "Effacer",
+DlgColorHighlight : "Prévisualisation",
+DlgColorSelected : "Sélectionné",
+
+// Smiley Dialog
+DlgSmileyTitle : "Insérer un Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Insérer un caractère spécial",
+
+// Table Dialog
+DlgTableTitle : "Propriétés du tableau",
+DlgTableRows : "Lignes",
+DlgTableColumns : "Colonnes",
+DlgTableBorder : "Bordure",
+DlgTableAlign : "Alignement",
+DlgTableAlignNotSet : "<Par défaut>",
+DlgTableAlignLeft : "Gauche",
+DlgTableAlignCenter : "Centré",
+DlgTableAlignRight : "Droite",
+DlgTableWidth : "Largeur",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "pourcentage",
+DlgTableHeight : "Hauteur",
+DlgTableCellSpace : "Espacement",
+DlgTableCellPad : "Contour",
+DlgTableCaption : "Titre",
+DlgTableSummary : "Résumé",
+DlgTableHeaders : "Entêtes",
+DlgTableHeadersNone : "Sans",
+DlgTableHeadersColumn : "Première colonne",
+DlgTableHeadersRow : "Première Ligne",
+DlgTableHeadersBoth : "Les 2",
+
+// Table Cell Dialog
+DlgCellTitle : "Propriétés de la cellule",
+DlgCellWidth : "Largeur",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "pourcentage",
+DlgCellHeight : "Hauteur",
+DlgCellWordWrap : "Retour à la ligne",
+DlgCellWordWrapNotSet : "<Par défaut>",
+DlgCellWordWrapYes : "Oui",
+DlgCellWordWrapNo : "Non",
+DlgCellHorAlign : "Alignement horizontal",
+DlgCellHorAlignNotSet : "<Par défaut>",
+DlgCellHorAlignLeft : "Gauche",
+DlgCellHorAlignCenter : "Centré",
+DlgCellHorAlignRight: "Droite",
+DlgCellVerAlign : "Alignement vertical",
+DlgCellVerAlignNotSet : "<Par défaut>",
+DlgCellVerAlignTop : "Haut",
+DlgCellVerAlignMiddle : "Milieu",
+DlgCellVerAlignBottom : "Bas",
+DlgCellVerAlignBaseline : "Bas du texte",
+DlgCellType : "Type de Cellule",
+DlgCellTypeData : "Données",
+DlgCellTypeHeader : "Entête",
+DlgCellRowSpan : "Lignes fusionnées",
+DlgCellCollSpan : "Colonnes fusionnées",
+DlgCellBackColor : "Fond",
+DlgCellBorderColor : "Bordure",
+DlgCellBtnSelect : "Choisir...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Chercher et Remplacer",
+
+// Find Dialog
+DlgFindTitle : "Chercher",
+DlgFindFindBtn : "Chercher",
+DlgFindNotFoundMsg : "Le texte indiqué est introuvable.",
+
+// Replace Dialog
+DlgReplaceTitle : "Remplacer",
+DlgReplaceFindLbl : "Rechercher:",
+DlgReplaceReplaceLbl : "Remplacer par:",
+DlgReplaceCaseChk : "Respecter la casse",
+DlgReplaceReplaceBtn : "Remplacer",
+DlgReplaceReplAllBtn : "Tout remplacer",
+DlgReplaceWordChk : "Mot entier",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Les paramètres de sécurité de votre navigateur empêchent l'éditeur de couper automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl+X).",
+PasteErrorCopy : "Les paramètres de sécurité de votre navigateur empêchent l'éditeur de copier automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl+C).",
+
+PasteAsText : "Coller comme texte",
+PasteFromWord : "Coller à partir de Word",
+
+DlgPasteMsg2 : "Veuillez coller dans la zone ci-dessous en utilisant le clavier (<STRONG>Ctrl+V</STRONG>) et cliquez sur <STRONG>OK</STRONG>.",
+DlgPasteSec : "A cause des paramètres de sécurité de votre navigateur, l'éditeur ne peut accéder au presse-papier directement. Vous devez coller à nouveau le contenu dans cette fenêtre.",
+DlgPasteIgnoreFont : "Ignorer les polices de caractères",
+DlgPasteRemoveStyles : "Supprimer les styles",
+
+// Color Picker
+ColorAutomatic : "Automatique",
+ColorMoreColors : "Plus de couleurs...",
+
+// Document Properties
+DocProps : "Propriétés du document",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propriétés de l'ancre",
+DlgAnchorName : "Nom de l'ancre",
+DlgAnchorErrorName : "Veuillez saisir le nom de l'ancre",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Pas dans le dictionnaire",
+DlgSpellChangeTo : "Changer en",
+DlgSpellBtnIgnore : "Ignorer",
+DlgSpellBtnIgnoreAll : "Ignorer tout",
+DlgSpellBtnReplace : "Remplacer",
+DlgSpellBtnReplaceAll : "Remplacer tout",
+DlgSpellBtnUndo : "Annuler",
+DlgSpellNoSuggestions : "- Aucune suggestion -",
+DlgSpellProgress : "Vérification d'orthographe en cours...",
+DlgSpellNoMispell : "Vérification d'orthographe terminée: Aucune erreur trouvée",
+DlgSpellNoChanges : "Vérification d'orthographe terminée: Pas de modifications",
+DlgSpellOneChange : "Vérification d'orthographe terminée: Un mot modifié",
+DlgSpellManyChanges : "Vérification d'orthographe terminée: %1 mots modifiés",
+
+IeSpellDownload : "Le Correcteur n'est pas installé. Souhaitez-vous le télécharger maintenant?",
+
+// Button Dialog
+DlgButtonText : "Texte (valeur)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Bouton",
+DlgButtonTypeSbm : "Envoyer",
+DlgButtonTypeRst : "Réinitialiser",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nom",
+DlgCheckboxValue : "Valeur",
+DlgCheckboxSelected : "Sélectionné",
+
+// Form Dialog
+DlgFormName : "Nom",
+DlgFormAction : "Action",
+DlgFormMethod : "Méthode",
+
+// Select Field Dialog
+DlgSelectName : "Nom",
+DlgSelectValue : "Valeur",
+DlgSelectSize : "Taille",
+DlgSelectLines : "lignes",
+DlgSelectChkMulti : "Sélection multiple",
+DlgSelectOpAvail : "Options disponibles",
+DlgSelectOpText : "Texte",
+DlgSelectOpValue : "Valeur",
+DlgSelectBtnAdd : "Ajouter",
+DlgSelectBtnModify : "Modifier",
+DlgSelectBtnUp : "Monter",
+DlgSelectBtnDown : "Descendre",
+DlgSelectBtnSetValue : "Valeur sélectionnée",
+DlgSelectBtnDelete : "Supprimer",
+
+// Textarea Dialog
+DlgTextareaName : "Nom",
+DlgTextareaCols : "Colonnes",
+DlgTextareaRows : "Lignes",
+
+// Text Field Dialog
+DlgTextName : "Nom",
+DlgTextValue : "Valeur",
+DlgTextCharWidth : "Largeur en caractères",
+DlgTextMaxChars : "Nombre maximum de caractères",
+DlgTextType : "Type",
+DlgTextTypeText : "Texte",
+DlgTextTypePass : "Mot de passe",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nom",
+DlgHiddenValue : "Valeur",
+
+// Bulleted List Dialog
+BulletedListProp : "Propriétés de liste à puces",
+NumberedListProp : "Propriétés de liste numérotée",
+DlgLstStart : "Début",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Cercle",
+DlgLstTypeDisc : "Disque",
+DlgLstTypeSquare : "Carré",
+DlgLstTypeNumbers : "Nombres (1, 2, 3)",
+DlgLstTypeLCase : "Lettres minuscules (a, b, c)",
+DlgLstTypeUCase : "Lettres majuscules (A, B, C)",
+DlgLstTypeSRoman : "Chiffres romains minuscules (i, ii, iii)",
+DlgLstTypeLRoman : "Chiffres romains majuscules (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Général",
+DlgDocBackTab : "Fond",
+DlgDocColorsTab : "Couleurs et marges",
+DlgDocMetaTab : "Métadonnées",
+
+DlgDocPageTitle : "Titre de la page",
+DlgDocLangDir : "Sens d'écriture",
+DlgDocLangDirLTR : "De la gauche vers la droite (LTR)",
+DlgDocLangDirRTL : "De la droite vers la gauche (RTL)",
+DlgDocLangCode : "Code langue",
+DlgDocCharSet : "Encodage de caractère",
+DlgDocCharSetCE : "Europe Centrale",
+DlgDocCharSetCT : "Chinois Traditionnel (Big5)",
+DlgDocCharSetCR : "Cyrillique",
+DlgDocCharSetGR : "Grec",
+DlgDocCharSetJP : "Japonais",
+DlgDocCharSetKR : "Coréen",
+DlgDocCharSetTR : "Turc",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Occidental",
+DlgDocCharSetOther : "Autre encodage de caractère",
+
+DlgDocDocType : "Type de document",
+DlgDocDocTypeOther : "Autre type de document",
+DlgDocIncXHTML : "Inclure les déclarations XHTML",
+DlgDocBgColor : "Couleur de fond",
+DlgDocBgImage : "Image de fond",
+DlgDocBgNoScroll : "Image fixe sans défilement",
+DlgDocCText : "Texte",
+DlgDocCLink : "Lien",
+DlgDocCVisited : "Lien visité",
+DlgDocCActive : "Lien activé",
+DlgDocMargins : "Marges",
+DlgDocMaTop : "Haut",
+DlgDocMaLeft : "Gauche",
+DlgDocMaRight : "Droite",
+DlgDocMaBottom : "Bas",
+DlgDocMeIndex : "Mots-clés (séparés par des virgules)",
+DlgDocMeDescr : "Description",
+DlgDocMeAuthor : "Auteur",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Prévisualisation",
+
+// Templates Dialog
+Templates : "Modèles",
+DlgTemplatesTitle : "Modèles de contenu",
+DlgTemplatesSelMsg : "Veuillez sélectionner le modèle à ouvrir dans l'éditeur<br>(le contenu actuel sera remplacé):",
+DlgTemplatesLoading : "Chargement de la liste des modèles. Veuillez patienter...",
+DlgTemplatesNoTpl : "(Aucun modèle disponible)",
+DlgTemplatesReplace : "Remplacer tout le contenu",
+
+// About Dialog
+DlgAboutAboutTab : "A propos de",
+DlgAboutBrowserInfoTab : "Navigateur",
+DlgAboutLicenseTab : "Licence",
+DlgAboutVersion : "Version",
+DlgAboutInfo : "Pour plus d'informations, aller à",
+
+// Div Dialog
+DlgDivGeneralTab : "Général",
+DlgDivAdvancedTab : "Avancé",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Attribut Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/gl.js b/httemplate/elements/fckeditor/editor/lang/gl.js
new file mode 100644
index 0000000..311bfb4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/gl.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Galician language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Ocultar Ferramentas",
+ToolbarExpand : "Mostrar Ferramentas",
+
+// Toolbar Items and Context Menu
+Save : "Gardar",
+NewPage : "Nova Páxina",
+Preview : "Vista Previa",
+Cut : "Cortar",
+Copy : "Copiar",
+Paste : "Pegar",
+PasteText : "Pegar como texto plano",
+PasteWord : "Pegar dende Word",
+Print : "Imprimir",
+SelectAll : "Seleccionar todo",
+RemoveFormat : "Eliminar Formato",
+InsertLinkLbl : "Ligazón",
+InsertLink : "Inserir/Editar Ligazón",
+RemoveLink : "Eliminar Ligazón",
+VisitLink : "Open Link", //MISSING
+Anchor : "Inserir/Editar Referencia",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Imaxe",
+InsertImage : "Inserir/Editar Imaxe",
+InsertFlashLbl : "Flash",
+InsertFlash : "Inserir/Editar Flash",
+InsertTableLbl : "Tabla",
+InsertTable : "Inserir/Editar Tabla",
+InsertLineLbl : "Liña",
+InsertLine : "Inserir Liña Horizontal",
+InsertSpecialCharLbl: "Carácter Special",
+InsertSpecialChar : "Inserir Carácter Especial",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Inserir Smiley",
+About : "Acerca de FCKeditor",
+Bold : "Negrita",
+Italic : "Cursiva",
+Underline : "Sub-raiado",
+StrikeThrough : "Tachado",
+Subscript : "Subíndice",
+Superscript : "Superíndice",
+LeftJustify : "Aliñar á Esquerda",
+CenterJustify : "Centrado",
+RightJustify : "Aliñar á Dereita",
+BlockJustify : "Xustificado",
+DecreaseIndent : "Disminuir Sangría",
+IncreaseIndent : "Aumentar Sangría",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Desfacer",
+Redo : "Refacer",
+NumberedListLbl : "Lista Numerada",
+NumberedList : "Inserir/Eliminar Lista Numerada",
+BulletedListLbl : "Marcas",
+BulletedList : "Inserir/Eliminar Marcas",
+ShowTableBorders : "Mostrar Bordes das Táboas",
+ShowDetails : "Mostrar Marcas Parágrafo",
+Style : "Estilo",
+FontFormat : "Formato",
+Font : "Tipo",
+FontSize : "Tamaño",
+TextColor : "Cor do Texto",
+BGColor : "Cor do Fondo",
+Source : "Código Fonte",
+Find : "Procurar",
+Replace : "Substituir",
+SpellCheck : "Corrección Ortográfica",
+UniversalKeyboard : "Teclado Universal",
+PageBreakLbl : "Salto de Páxina",
+PageBreak : "Inserir Salto de Páxina",
+
+Form : "Formulario",
+Checkbox : "Cadro de Verificación",
+RadioButton : "Botón de Radio",
+TextField : "Campo de Texto",
+Textarea : "Ãrea de Texto",
+HiddenField : "Campo Oculto",
+Button : "Botón",
+SelectionField : "Campo de Selección",
+ImageButton : "Botón de Imaxe",
+
+FitWindow : "Maximizar o tamaño do editor",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Editar Ligazón",
+CellCM : "Cela",
+RowCM : "Fila",
+ColumnCM : "Columna",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Borrar Filas",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Borrar Columnas",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Borrar Cela",
+MergeCells : "Unir Celas",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Borrar Táboa",
+CellProperties : "Propriedades da Cela",
+TableProperties : "Propriedades da Táboa",
+ImageProperties : "Propriedades Imaxe",
+FlashProperties : "Propriedades Flash",
+
+AnchorProp : "Propriedades da Referencia",
+ButtonProp : "Propriedades do Botón",
+CheckboxProp : "Propriedades do Cadro de Verificación",
+HiddenFieldProp : "Propriedades do Campo Oculto",
+RadioButtonProp : "Propriedades do Botón de Radio",
+ImageButtonProp : "Propriedades do Botón de Imaxe",
+TextFieldProp : "Propriedades do Campo de Texto",
+SelectionFieldProp : "Propriedades do Campo de Selección",
+TextareaProp : "Propriedades da Ãrea de Texto",
+FormProp : "Propriedades do Formulario",
+
+FontFormats : "Normal;Formateado;Enderezo;Enacabezado 1;Encabezado 2;Encabezado 3;Encabezado 4;Encabezado 5;Encabezado 6;Paragraph (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Procesando XHTML. Por facor, agarde...",
+Done : "Feiro",
+PasteWordConfirm : "Parece que o texto que quere pegar está copiado do Word.¿Quere limpar o formato antes de pegalo?",
+NotCompatiblePaste : "Este comando está disponible para Internet Explorer versión 5.5 ou superior. ¿Quere pegalo sen limpar o formato?",
+UnknownToolbarItem : "Ãtem de ferramentas descoñecido \"%1\"",
+UnknownCommand : "Nome de comando descoñecido \"%1\"",
+NotImplemented : "Comando non implementado",
+UnknownToolbarSet : "O conxunto de ferramentas \"%1\" non existe",
+NoActiveX : "As opcións de seguridade do seu navegador poderían limitar algunha das características de editor. Debe activar a opción \"Executar controis ActiveX e plug-ins\". Pode notar que faltan características e experimentar erros",
+BrowseServerBlocked : "Non se poido abrir o navegador de recursos. Asegúrese de que están desactivados os bloqueadores de xanelas emerxentes",
+DialogBlocked : "Non foi posible abrir a xanela de diálogo. Asegúrese de que están desactivados os bloqueadores de xanelas emerxentes",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancelar",
+DlgBtnClose : "Pechar",
+DlgBtnBrowseServer : "Navegar no Servidor",
+DlgAdvancedTag : "Advanzado",
+DlgOpOther : "<Outro>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Por favor, insira a URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<non definido>",
+DlgGenId : "Id",
+DlgGenLangDir : "Orientación do Idioma",
+DlgGenLangDirLtr : "Esquerda a Dereita (LTR)",
+DlgGenLangDirRtl : "Dereita a Esquerda (RTL)",
+DlgGenLangCode : "Código do Idioma",
+DlgGenAccessKey : "Chave de Acceso",
+DlgGenName : "Nome",
+DlgGenTabIndex : "Ãndice de Tabulación",
+DlgGenLongDescr : "Descrición Completa da URL",
+DlgGenClass : "Clases da Folla de Estilos",
+DlgGenTitle : "Título",
+DlgGenContType : "Tipo de Contido",
+DlgGenLinkCharset : "Fonte de Caracteres Vinculado",
+DlgGenStyle : "Estilo",
+
+// Image Dialog
+DlgImgTitle : "Propriedades da Imaxe",
+DlgImgInfoTab : "Información da Imaxe",
+DlgImgBtnUpload : "Enviar ó Servidor",
+DlgImgURL : "URL",
+DlgImgUpload : "Carregar",
+DlgImgAlt : "Texto Alternativo",
+DlgImgWidth : "Largura",
+DlgImgHeight : "Altura",
+DlgImgLockRatio : "Proporcional",
+DlgBtnResetSize : "Tamaño Orixinal",
+DlgImgBorder : "Límite",
+DlgImgHSpace : "Esp. Horiz.",
+DlgImgVSpace : "Esp. Vert.",
+DlgImgAlign : "Aliñamento",
+DlgImgAlignLeft : "Esquerda",
+DlgImgAlignAbsBottom: "Abs Inferior",
+DlgImgAlignAbsMiddle: "Abs Centro",
+DlgImgAlignBaseline : "Liña Base",
+DlgImgAlignBottom : "Pé",
+DlgImgAlignMiddle : "Centro",
+DlgImgAlignRight : "Dereita",
+DlgImgAlignTextTop : "Tope do Texto",
+DlgImgAlignTop : "Tope",
+DlgImgPreview : "Vista Previa",
+DlgImgAlertUrl : "Por favor, escriba a URL da imaxe",
+DlgImgLinkTab : "Ligazón",
+
+// Flash Dialog
+DlgFlashTitle : "Propriedades Flash",
+DlgFlashChkPlay : "Auto Execución",
+DlgFlashChkLoop : "Bucle",
+DlgFlashChkMenu : "Activar Menú Flash",
+DlgFlashScale : "Escalar",
+DlgFlashScaleAll : "Amosar Todo",
+DlgFlashScaleNoBorder : "Sen Borde",
+DlgFlashScaleFit : "Encaixar axustando",
+
+// Link Dialog
+DlgLnkWindowTitle : "Ligazón",
+DlgLnkInfoTab : "Información da Ligazón",
+DlgLnkTargetTab : "Referencia a esta páxina",
+
+DlgLnkType : "Tipo de Ligazón",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Referencia nesta páxina",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocolo",
+DlgLnkProtoOther : "<outro>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Seleccionar unha Referencia",
+DlgLnkAnchorByName : "Por Nome de Referencia",
+DlgLnkAnchorById : "Por Element Id",
+DlgLnkNoAnchors : "(Non hai referencias disponibles no documento)",
+DlgLnkEMail : "Enderezo de E-Mail",
+DlgLnkEMailSubject : "Asunto do Mensaxe",
+DlgLnkEMailBody : "Corpo do Mensaxe",
+DlgLnkUpload : "Carregar",
+DlgLnkBtnUpload : "Enviar ó servidor",
+
+DlgLnkTarget : "Destino",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<Xanela Emerxente>",
+DlgLnkTargetBlank : "Nova Xanela (_blank)",
+DlgLnkTargetParent : "Xanela Pai (_parent)",
+DlgLnkTargetSelf : "Mesma Xanela (_self)",
+DlgLnkTargetTop : "Xanela Primaria (_top)",
+DlgLnkTargetFrameName : "Nome do Marco Destino",
+DlgLnkPopWinName : "Nome da Xanela Emerxente",
+DlgLnkPopWinFeat : "Características da Xanela Emerxente",
+DlgLnkPopResize : "Axustable",
+DlgLnkPopLocation : "Barra de Localización",
+DlgLnkPopMenu : "Barra de Menú",
+DlgLnkPopScroll : "Barras de Desplazamento",
+DlgLnkPopStatus : "Barra de Estado",
+DlgLnkPopToolbar : "Barra de Ferramentas",
+DlgLnkPopFullScrn : "A Toda Pantalla (IE)",
+DlgLnkPopDependent : "Dependente (Netscape)",
+DlgLnkPopWidth : "Largura",
+DlgLnkPopHeight : "Altura",
+DlgLnkPopLeft : "Posición Esquerda",
+DlgLnkPopTop : "Posición dende Arriba",
+
+DlnLnkMsgNoUrl : "Por favor, escriba a ligazón URL",
+DlnLnkMsgNoEMail : "Por favor, escriba o enderezo de e-mail",
+DlnLnkMsgNoAnchor : "Por favor, seleccione un destino",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Seleccionar Color",
+DlgColorBtnClear : "Nengunha",
+DlgColorHighlight : "Destacado",
+DlgColorSelected : "Seleccionado",
+
+// Smiley Dialog
+DlgSmileyTitle : "Inserte un Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Seleccione Caracter Especial",
+
+// Table Dialog
+DlgTableTitle : "Propiedades da Táboa",
+DlgTableRows : "Filas",
+DlgTableColumns : "Columnas",
+DlgTableBorder : "Tamaño do Borde",
+DlgTableAlign : "Aliñamento",
+DlgTableAlignNotSet : "<Non Definido>",
+DlgTableAlignLeft : "Esquerda",
+DlgTableAlignCenter : "Centro",
+DlgTableAlignRight : "Ereita",
+DlgTableWidth : "Largura",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Altura",
+DlgTableCellSpace : "Marxe entre Celas",
+DlgTableCellPad : "Marxe interior",
+DlgTableCaption : "Título",
+DlgTableSummary : "Sumario",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Propriedades da Cela",
+DlgCellWidth : "Largura",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Altura",
+DlgCellWordWrap : "Axustar Liñas",
+DlgCellWordWrapNotSet : "<Non Definido>",
+DlgCellWordWrapYes : "Si",
+DlgCellWordWrapNo : "Non",
+DlgCellHorAlign : "Aliñamento Horizontal",
+DlgCellHorAlignNotSet : "<Non definido>",
+DlgCellHorAlignLeft : "Esquerda",
+DlgCellHorAlignCenter : "Centro",
+DlgCellHorAlignRight: "Dereita",
+DlgCellVerAlign : "Aliñamento Vertical",
+DlgCellVerAlignNotSet : "<Non definido>",
+DlgCellVerAlignTop : "Arriba",
+DlgCellVerAlignMiddle : "Medio",
+DlgCellVerAlignBottom : "Abaixo",
+DlgCellVerAlignBaseline : "Liña de Base",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Ocupar Filas",
+DlgCellCollSpan : "Ocupar Columnas",
+DlgCellBackColor : "Color de Fondo",
+DlgCellBorderColor : "Color de Borde",
+DlgCellBtnSelect : "Seleccionar...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Procurar",
+DlgFindFindBtn : "Procurar",
+DlgFindNotFoundMsg : "Non te atopou o texto indicado.",
+
+// Replace Dialog
+DlgReplaceTitle : "Substituir",
+DlgReplaceFindLbl : "Texto a procurar:",
+DlgReplaceReplaceLbl : "Substituir con:",
+DlgReplaceCaseChk : "Coincidir Mai./min.",
+DlgReplaceReplaceBtn : "Substituir",
+DlgReplaceReplAllBtn : "Substitiur Todo",
+DlgReplaceWordChk : "Coincidir con toda a palabra",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Os axustes de seguridade do seu navegador non permiten que o editor realice automáticamente as tarefas de corte. Por favor, use o teclado para iso (Ctrl+X).",
+PasteErrorCopy : "Os axustes de seguridade do seu navegador non permiten que o editor realice automáticamente as tarefas de copia. Por favor, use o teclado para iso (Ctrl+C).",
+
+PasteAsText : "Pegar como texto plano",
+PasteFromWord : "Pegar dende Word",
+
+DlgPasteMsg2 : "Por favor, pegue dentro do seguinte cadro usando o teclado (<STRONG>Ctrl+V</STRONG>) e pulse <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignorar as definicións de Tipografía",
+DlgPasteRemoveStyles : "Eliminar as definicións de Estilos",
+
+// Color Picker
+ColorAutomatic : "Automático",
+ColorMoreColors : "Máis Cores...",
+
+// Document Properties
+DocProps : "Propriedades do Documento",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propriedades da Referencia",
+DlgAnchorName : "Nome da Referencia",
+DlgAnchorErrorName : "Por favor, escriba o nome da referencia",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Non está no diccionario",
+DlgSpellChangeTo : "Cambiar a",
+DlgSpellBtnIgnore : "Ignorar",
+DlgSpellBtnIgnoreAll : "Ignorar Todas",
+DlgSpellBtnReplace : "Substituir",
+DlgSpellBtnReplaceAll : "Substituir Todas",
+DlgSpellBtnUndo : "Desfacer",
+DlgSpellNoSuggestions : "- Sen candidatos -",
+DlgSpellProgress : "Corrección ortográfica en progreso...",
+DlgSpellNoMispell : "Corrección ortográfica rematada: Non se atoparon erros",
+DlgSpellNoChanges : "Corrección ortográfica rematada: Non se substituiu nengunha verba",
+DlgSpellOneChange : "Corrección ortográfica rematada: Unha verba substituida",
+DlgSpellManyChanges : "Corrección ortográfica rematada: %1 verbas substituidas",
+
+IeSpellDownload : "O corrector ortográfico non está instalado. ¿Quere descargalo agora?",
+
+// Button Dialog
+DlgButtonText : "Texto (Valor)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nome",
+DlgCheckboxValue : "Valor",
+DlgCheckboxSelected : "Seleccionado",
+
+// Form Dialog
+DlgFormName : "Nome",
+DlgFormAction : "Acción",
+DlgFormMethod : "Método",
+
+// Select Field Dialog
+DlgSelectName : "Nome",
+DlgSelectValue : "Valor",
+DlgSelectSize : "Tamaño",
+DlgSelectLines : "liñas",
+DlgSelectChkMulti : "Permitir múltiples seleccións",
+DlgSelectOpAvail : "Opcións Disponibles",
+DlgSelectOpText : "Texto",
+DlgSelectOpValue : "Valor",
+DlgSelectBtnAdd : "Engadir",
+DlgSelectBtnModify : "Modificar",
+DlgSelectBtnUp : "Subir",
+DlgSelectBtnDown : "Baixar",
+DlgSelectBtnSetValue : "Definir como valor por defecto",
+DlgSelectBtnDelete : "Borrar",
+
+// Textarea Dialog
+DlgTextareaName : "Nome",
+DlgTextareaCols : "Columnas",
+DlgTextareaRows : "Filas",
+
+// Text Field Dialog
+DlgTextName : "Nome",
+DlgTextValue : "Valor",
+DlgTextCharWidth : "Tamaño do Caracter",
+DlgTextMaxChars : "Máximo de Caracteres",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Texto",
+DlgTextTypePass : "Chave",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nome",
+DlgHiddenValue : "Valor",
+
+// Bulleted List Dialog
+BulletedListProp : "Propriedades das Marcas",
+NumberedListProp : "Propriedades da Lista de Numeración",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Círculo",
+DlgLstTypeDisc : "Disco",
+DlgLstTypeSquare : "Cuadrado",
+DlgLstTypeNumbers : "Números (1, 2, 3)",
+DlgLstTypeLCase : "Letras Minúsculas (a, b, c)",
+DlgLstTypeUCase : "Letras Maiúsculas (A, B, C)",
+DlgLstTypeSRoman : "Números Romanos en minúscula (i, ii, iii)",
+DlgLstTypeLRoman : "Números Romanos en Maiúscula (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Xeral",
+DlgDocBackTab : "Fondo",
+DlgDocColorsTab : "Cores e Marxes",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Título da Páxina",
+DlgDocLangDir : "Orientación do Idioma",
+DlgDocLangDirLTR : "Esquerda a Dereita (LTR)",
+DlgDocLangDirRTL : "Dereita a Esquerda (RTL)",
+DlgDocLangCode : "Código de Idioma",
+DlgDocCharSet : "Codificación do Xogo de Caracteres",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Outra Codificación do Xogo de Caracteres",
+
+DlgDocDocType : "Encabezado do Tipo de Documento",
+DlgDocDocTypeOther : "Outro Encabezado do Tipo de Documento",
+DlgDocIncXHTML : "Incluir Declaracións XHTML",
+DlgDocBgColor : "Cor de Fondo",
+DlgDocBgImage : "URL da Imaxe de Fondo",
+DlgDocBgNoScroll : "Fondo Fixo",
+DlgDocCText : "Texto",
+DlgDocCLink : "Ligazóns",
+DlgDocCVisited : "Ligazón Visitada",
+DlgDocCActive : "Ligazón Activa",
+DlgDocMargins : "Marxes da Páxina",
+DlgDocMaTop : "Arriba",
+DlgDocMaLeft : "Esquerda",
+DlgDocMaRight : "Dereita",
+DlgDocMaBottom : "Abaixo",
+DlgDocMeIndex : "Palabras Chave de Indexación do Documento (separadas por comas)",
+DlgDocMeDescr : "Descripción do Documento",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Vista Previa",
+
+// Templates Dialog
+Templates : "Plantillas",
+DlgTemplatesTitle : "Plantillas de Contido",
+DlgTemplatesSelMsg : "Por favor, seleccione a plantilla a abrir no editor<br>(o contido actual perderase):",
+DlgTemplatesLoading : "Cargando listado de plantillas. Por favor, espere...",
+DlgTemplatesNoTpl : "(Non hai plantillas definidas)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "Acerca de",
+DlgAboutBrowserInfoTab : "Información do Navegador",
+DlgAboutLicenseTab : "Licencia",
+DlgAboutVersion : "versión",
+DlgAboutInfo : "Para máis información visitar:",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/gu.js b/httemplate/elements/fckeditor/editor/lang/gu.js
new file mode 100644
index 0000000..3e8b6b2
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/gu.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Gujarati language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "ટૂલબાર નાનà«àª‚ કરવà«àª‚",
+ToolbarExpand : "ટૂલબાર મોટà«àª‚ કરવà«àª‚",
+
+// Toolbar Items and Context Menu
+Save : "સેવ",
+NewPage : "નવૠપાનà«àª‚",
+Preview : "પૂરà«àªµàª¦àª°à«àª¶àª¨",
+Cut : "કાપવà«àª‚",
+Copy : "નકલ",
+Paste : "પેસà«àªŸ",
+PasteText : "પેસà«àªŸ (સાદી ટેકà«àª¸à«àªŸ)",
+PasteWord : "પેસà«àªŸ (વડૅ ટેકà«àª¸à«àªŸ)",
+Print : "પà«àª°àª¿àª¨à«àªŸ",
+SelectAll : "બઘà«àª‚ પસંદ કરવà«àª‚",
+RemoveFormat : "ફૉરà«àª®àªŸ કાઢવà«àª‚",
+InsertLinkLbl : "સંબંધન, લિંક",
+InsertLink : "લિંક ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+RemoveLink : "લિંક કાઢવી",
+VisitLink : "Open Link", //MISSING
+Anchor : "àªàª‚કર ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+AnchorDelete : "àªàª‚કર કાઢવી",
+InsertImageLbl : "ચિતà«àª°",
+InsertImage : "ચિતà«àª° ઇનà«àª¸àª°à«àªŸ/દાખલ કરવà«àª‚",
+InsertFlashLbl : "ફà«àª²à«…શ",
+InsertFlash : "ફà«àª²à«…શ ઇનà«àª¸àª°à«àªŸ/દાખલ કરવà«àª‚",
+InsertTableLbl : "ટેબલ, કોઠો",
+InsertTable : "ટેબલ, કોઠો ઇનà«àª¸àª°à«àªŸ/દાખલ કરવà«àª‚",
+InsertLineLbl : "રેખા",
+InsertLine : "સમસà«àª¤àª°à«€àª¯ રેખા ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+InsertSpecialCharLbl: "વિશિષà«àªŸ અકà«àª·àª°",
+InsertSpecialChar : "વિશિષà«àªŸ અકà«àª·àª° ઇનà«àª¸àª°à«àªŸ/દાખલ કરવà«àª‚",
+InsertSmileyLbl : "સà«àª®àª¾àª‡àª²à«€",
+InsertSmiley : "સà«àª®àª¾àª‡àª²à«€ ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+About : "FCKeditorના વિષે",
+Bold : "બોલà«àª¡/સà«àªªàª·à«àªŸ",
+Italic : "ઇટેલિક, તà«àª°àª¾àª‚સા",
+Underline : "અનà«àª¡àª°à«àª²àª¾àª‡àª¨, નીચે લીટી",
+StrikeThrough : "છેકી નાખવà«àª‚",
+Subscript : "àªàª• ચિહà«àª¨àª¨à«€ નીચે કરેલà«àª‚ બીજà«àª‚ ચિહà«àª¨",
+Superscript : "àªàª• ચિહà«àª¨ ઉપર કરેલà«àª‚ બીજà«àª‚ ચિહà«àª¨.",
+LeftJustify : "ડાબી બાજà«àª/બાજૠતરફ",
+CenterJustify : "સંકેંદà«àª°àª£/સેંટરિંગ",
+RightJustify : "જમણી બાજà«àª/બાજૠતરફ",
+BlockJustify : "બà«àª²à«‰àª•, અંતરાય જસà«àªŸàª¿àª«àª¾àª‡",
+DecreaseIndent : "ઇનà«àª¡à«‡àª¨à«àªŸ લીટીના આરંભમાં જગà«àª¯àª¾ ઘટાડવી",
+IncreaseIndent : "ઇનà«àª¡à«‡àª¨à«àªŸ, લીટીના આરંભમાં જગà«àª¯àª¾ વધારવી",
+Blockquote : "બà«àª²à«‰àª•-કોટ, અવતરણચિહà«àª¨à«‹",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "રદ કરવà«àª‚; પહેલાં હતી àªàªµà«€ સà«àª¥àª¿àª¤àª¿ પાછી લાવવી",
+Redo : "રિડૂ; પછી હતી àªàªµà«€ સà«àª¥àª¿àª¤àª¿ પાછી લાવવી",
+NumberedListLbl : "સંખà«àª¯àª¾àª‚કન સૂચિ",
+NumberedList : "સંખà«àª¯àª¾àª‚કન સૂચિ ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+BulletedListLbl : "બà«àª²à«‡àªŸ સૂચિ",
+BulletedList : "બà«àª²à«‡àªŸ સૂચિ ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી",
+ShowTableBorders : "ટેબલ, કોઠાની બાજà«(બોરà«àª¡àª°) બતાવવી",
+ShowDetails : "વિસà«àª¤à«ƒàª¤ વિગતવાર બતાવવà«àª‚",
+Style : "શૈલી/રીત",
+FontFormat : "ફૉનà«àªŸ ફૉરà«àª®àªŸ, રચનાની શૈલી",
+Font : "ફૉનà«àªŸ",
+FontSize : "ફૉનà«àªŸ સાઇàª/કદ",
+TextColor : "શબà«àª¦àª¨à«‹ રંગ",
+BGColor : "બૅકગà«àª°àª¾àª‰àª¨à«àª¡ રંગ,",
+Source : "મૂળ કે પà«àª°àª¾àª¥àª®àª¿àª• દસà«àª¤àª¾àªµà«‡àªœ",
+Find : "શોધવà«àª‚",
+Replace : "રિપà«àª²à«‡àª¸/બદલવà«àª‚",
+SpellCheck : "જોડણી (સà«àªªà«‡àª²àª¿àª‚ગ) તપાસવી",
+UniversalKeyboard : "યૂનિવરà«àª¸àª²/વિશà«àªµàªµà«àª¯àª¾àªªàª• કીબૉરà«àª¡",
+PageBreakLbl : "પેજબà«àª°à«‡àª•/પાનાને અલગ કરવà«àª‚",
+PageBreak : "ઇનà«àª¸àª°à«àªŸ પેજબà«àª°à«‡àª•/પાનાને અલગ કરવà«àª‚/દાખલ કરવà«àª‚",
+
+Form : "ફૉરà«àª®/પતà«àª°àª•",
+Checkbox : "ચેક બોકà«àª¸",
+RadioButton : "રેડિઓ બટન",
+TextField : "ટેકà«àª¸à«àªŸ ફીલà«àª¡, શબà«àª¦ કà«àª·à«‡àª¤à«àª°",
+Textarea : "ટેકà«àª¸à«àªŸ àªàª°àª¿àª†, શબà«àª¦ વિસà«àª¤àª¾àª°",
+HiddenField : "ગà«àªªà«àª¤ કà«àª·à«‡àª¤à«àª°",
+Button : "બટન",
+SelectionField : "પસંદગી કà«àª·à«‡àª¤à«àª°",
+ImageButton : "ચિતà«àª° બટન",
+
+FitWindow : "àªàª¡àª¿àªŸàª°àª¨à«€ સાઇઠઅધિકતમ કરવી",
+ShowBlocks : "બà«àª²à«‰àª• બતાવવà«àª‚",
+
+// Context Menu
+EditLink : " લિંક àªàª¡àª¿àªŸ/માં ફેરફાર કરવો",
+CellCM : "કોષના ખાના",
+RowCM : "પંકà«àª¤àª¿àª¨àª¾ ખાના",
+ColumnCM : "કૉલમ/ઊભી કટાર",
+InsertRowAfter : "પછી પંકà«àª¤àª¿ ઉમેરવી",
+InsertRowBefore : "પહેલાં પંકà«àª¤àª¿ ઉમેરવી",
+DeleteRows : "પંકà«àª¤àª¿àª“ ડિલીટ/કાઢી નાખવી",
+InsertColumnAfter : "પછી કૉલમ/ઊભી કટાર ઉમેરવી",
+InsertColumnBefore : "પહેલાં કૉલમ/ઊભી કટાર ઉમેરવી",
+DeleteColumns : "કૉલમ/ઊભી કટાર ડિલીટ/કાઢી નાખવી",
+InsertCellAfter : "પછી કોષ ઉમેરવો",
+InsertCellBefore : "પહેલાં કોષ ઉમેરવો",
+DeleteCells : "કોષ ડિલીટ/કાઢી નાખવો",
+MergeCells : "કોષ ભેગા કરવા",
+MergeRight : "જમણી બાજૠભેગા કરવા",
+MergeDown : "નીચે ભેગા કરવા",
+HorizontalSplitCell : "કોષને સમસà«àª¤àª°à«€àª¯ વિભાજન કરવà«àª‚",
+VerticalSplitCell : "કોષને સીધà«àª‚ ને ઊભà«àª‚ વિભાજન કરવà«àª‚",
+TableDelete : "કોઠો ડિલીટ/કાઢી નાખવà«àª‚",
+CellProperties : "કોષના ગà«àª£",
+TableProperties : "કોઠાના ગà«àª£",
+ImageProperties : "ચિતà«àª°àª¨àª¾ ગà«àª£",
+FlashProperties : "ફà«àª²à«…શના ગà«àª£",
+
+AnchorProp : "àªàª‚કરના ગà«àª£",
+ButtonProp : "બટનના ગà«àª£",
+CheckboxProp : "ચેક બોકà«àª¸ ગà«àª£",
+HiddenFieldProp : "ગà«àªªà«àª¤ કà«àª·à«‡àª¤à«àª°àª¨àª¾ ગà«àª£",
+RadioButtonProp : "રેડિઓ બટનના ગà«àª£",
+ImageButtonProp : "ચિતà«àª° બટનના ગà«àª£",
+TextFieldProp : "ટેકà«àª¸à«àªŸ ફીલà«àª¡, શબà«àª¦ કà«àª·à«‡àª¤à«àª°àª¨àª¾ ગà«àª£",
+SelectionFieldProp : "પસંદગી કà«àª·à«‡àª¤à«àª°àª¨àª¾ ગà«àª£",
+TextareaProp : "ટેકà«àª¸à«àªŸ àªàª…રિઆ, શબà«àª¦ વિસà«àª¤àª¾àª°àª¨àª¾ ગà«àª£",
+FormProp : "ફૉરà«àª®/પતà«àª°àª•àª¨àª¾ ગà«àª£",
+
+FontFormats : "સામાનà«àª¯;ફૉરà«àª®àªŸà«‡àª¡;સરનામà«àª‚;શીરà«àª·àª• 1;શીરà«àª·àª• 2;શીરà«àª·àª• 3;શીરà«àª·àª• 4;શીરà«àª·àª• 5;શીરà«àª·àª• 6;શીરà«àª·àª• (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML પà«àª°àª•à«àª°àª¿àª¯àª¾ ચાલૠછે. મહેરબાની કરીને રાહ જોવો...",
+Done : "પતી ગયà«àª‚",
+PasteWordConfirm : "તમે જે ટેકà«àª¸à«àªŸ પેસà«àªŸ કરવા માંગો છો, તે વડૅમાંથી કોપી કરેલૠલાગે છે. પેસà«àªŸ કરતા પહેલાં ટેકà«àª¸à«àªŸ સાફ કરવી છે?",
+NotCompatiblePaste : "આ કમાનà«àª¡ ઈનટરનેટ àªàª•à«àª¸àªªà«àª²à«‹àª°àª°(Internet Explorer) 5.5 અથવા àªàª¨àª¾ પછીના વરà«àªàª¨ માટેજ છે. ટેકà«àª¸à«àªŸàª¨à«‡ સાફ કયૅા પહેલાં પેસà«àªŸ કરવી છે?",
+UnknownToolbarItem : "અજાણી ટૂલબાર આઇટમ \"%1\"",
+UnknownCommand : "અજાણયો કમાનà«àª¡ \"%1\"",
+NotImplemented : "કમાનà«àª¡ ઇમà«àªªà«àª²àª¿àª®àª¨à«àªŸ નથી કરોયો",
+UnknownToolbarSet : "ટૂલબાર સેટ \"%1\" ઉપલબà«àª§ નથી",
+NoActiveX : "તમારા બà«àª°àª¾àª‰àªàª°àª¨à«€ સà«àª°àª•à«àª·àª¾ સેટિંગસ àªàª¡àª¿àªŸàª°àª¨àª¾ અમà«àª• ફીચરને પરવાનગી આપતી નથી. કૃપયા \"Run ActiveX controls and plug-ins\" વિકલà«àªªàª¨à«‡ ઇનેબલ/સમરà«àª¥ કરો. તમારા બà«àª°àª¾àª‰àªàª°àª®àª¾àª‚ àªàª°àª° ઇનà«àªµàª¿àªàª¿àª¬àª² ફીચરનો અનà«àª­àªµ થઈ શકે છે. કૃપયા પૉપ-અપ બà«àª²à«‰àª•àª° ડિસેબલ કરો.",
+BrowseServerBlocked : "રિસૉરà«àª¸ બà«àª°àª¾àª‰àªàª° ખોલી ન સકાયà«àª‚.",
+DialogBlocked : "ડાયલૉગ વિનà«àª¡à«‹ ખોલી ન સકાયà«àª‚. કૃપયા પૉપ-અપ બà«àª²à«‰àª•àª° ડિસેબલ કરો.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ઠીક છે",
+DlgBtnCancel : "રદ કરવà«àª‚",
+DlgBtnClose : "બંધ કરવà«àª‚",
+DlgBtnBrowseServer : "સરà«àªµàª° બà«àª°àª¾àª‰àª કરો",
+DlgAdvancedTag : "અડà«àªµàª¾àª¨à«àª¸àª¡",
+DlgOpOther : "<અનà«àª¯>",
+DlgInfoTab : "સૂચના",
+DlgAlertUrl : "URL ઇનà«àª¸àª°à«àªŸ કરો",
+
+// General Dialogs Labels
+DlgGenNotSet : "<સેટ નથી>",
+DlgGenId : "Id",
+DlgGenLangDir : "ભાષા લેખવાની પદà«àª§àª¤àª¿",
+DlgGenLangDirLtr : "ડાબે થી જમણે (LTR)",
+DlgGenLangDirRtl : "જમણે થી ડાબે (RTL)",
+DlgGenLangCode : "ભાષા કોડ",
+DlgGenAccessKey : "àªàª•à«àª¸à«‡àª¸ કી",
+DlgGenName : "નામ",
+DlgGenTabIndex : "ટૅબ ઇનà«àª¡à«‡àª•à«àª¸",
+DlgGenLongDescr : "વધારે માહિતી માટે URL",
+DlgGenClass : "સà«àªŸàª¾àª‡àª²-શીટ કà«àª²àª¾àª¸",
+DlgGenTitle : "મà«àª–à«àª¯ મથાળà«àª‚",
+DlgGenContType : "મà«àª–à«àª¯ કનà«àªŸà«‡àª¨à«àªŸ પà«àª°àª•àª¾àª°",
+DlgGenLinkCharset : "લિંક રિસૉરà«àª¸ કૅરિકà«àªŸàª° સેટ",
+DlgGenStyle : "સà«àªŸàª¾àª‡àª²",
+
+// Image Dialog
+DlgImgTitle : "ચિતà«àª°àª¨àª¾ ગà«àª£",
+DlgImgInfoTab : "ચિતà«àª° ની જાણકારી",
+DlgImgBtnUpload : "આ સરà«àªµàª°àª¨à«‡ મોકલવà«àª‚",
+DlgImgURL : "URL",
+DlgImgUpload : "અપલોડ",
+DlgImgAlt : "ઑલà«àªŸàª°à«àª¨àªŸ ટેકà«àª¸à«àªŸ",
+DlgImgWidth : "પહોળાઈ",
+DlgImgHeight : "ઊંચાઈ",
+DlgImgLockRatio : "લૉક ગà«àª£à«‹àª¤à«àª¤àª°",
+DlgBtnResetSize : "રીસેટ સાઇàª",
+DlgImgBorder : "બોરà«àª¡àª°",
+DlgImgHSpace : "સમસà«àª¤àª°à«€àª¯ જગà«àª¯àª¾",
+DlgImgVSpace : "લંબરૂપ જગà«àª¯àª¾",
+DlgImgAlign : "લાઇનદોરીમાં ગોઠવવà«àª‚",
+DlgImgAlignLeft : "ડાબી બાજૠગોઠવવà«àª‚",
+DlgImgAlignAbsBottom: "Abs નીચે",
+DlgImgAlignAbsMiddle: "Abs ઉપર",
+DlgImgAlignBaseline : "આધાર લીટી",
+DlgImgAlignBottom : "નીચે",
+DlgImgAlignMiddle : "વચà«àªšà«‡",
+DlgImgAlignRight : "જમણી",
+DlgImgAlignTextTop : "ટેકà«àª¸à«àªŸ ઉપર",
+DlgImgAlignTop : "ઉપર",
+DlgImgPreview : "પૂરà«àªµàª¦àª°à«àª¶àª¨",
+DlgImgAlertUrl : "ચિતà«àª°àª¨à«€ URL ટાઇપ કરો",
+DlgImgLinkTab : "લિંક",
+
+// Flash Dialog
+DlgFlashTitle : "ફà«àª²à«…શ ગà«àª£",
+DlgFlashChkPlay : "ઑટો/સà«àªµàª¯àª‚ પà«àª²à«‡",
+DlgFlashChkLoop : "લૂપ",
+DlgFlashChkMenu : "ફà«àª²à«…શ મેનà«àª¯à«‚ નો પà«àª°àª¯à«‹àª— કરો",
+DlgFlashScale : "સà«àª•à«‡àª²",
+DlgFlashScaleAll : "સà«àª•à«‡àª² ઓલ/બધૠબતાવો",
+DlgFlashScaleNoBorder : "સà«àª•à«‡àª² બોરà«àª¡àª° વગર",
+DlgFlashScaleFit : "સà«àª•à«‡àª² àªàª•àª¦àª® ફીટ",
+
+// Link Dialog
+DlgLnkWindowTitle : "લિંક",
+DlgLnkInfoTab : "લિંક ઇનà«àª«à«‰ ટૅબ",
+DlgLnkTargetTab : "ટારà«àª—ેટ/લકà«àª·à«àª¯ ટૅબ",
+
+DlgLnkType : "લિંક પà«àª°àª•àª¾àª°",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "આ પેજનો àªàª‚કર",
+DlgLnkTypeEMail : "ઈ-મેલ",
+DlgLnkProto : "પà«àª°à«‹àªŸà«‹àª•à«‰àª²",
+DlgLnkProtoOther : "<અનà«àª¯>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "àªàª‚કર પસંદ કરો",
+DlgLnkAnchorByName : "àªàª‚કર નામથી પસંદ કરો",
+DlgLnkAnchorById : "àªàª‚કર àªàª²àª¿àª®àª¨à«àªŸ Id થી પસંદ કરો",
+DlgLnkNoAnchors : "(ડૉકà«àª¯à«àª®àª¨à«àªŸàª®àª¾àª‚ àªàª‚કરની સંખà«àª¯àª¾)",
+DlgLnkEMail : "ઈ-મેલ સરનામà«àª‚",
+DlgLnkEMailSubject : "ઈ-મેલ વિષય",
+DlgLnkEMailBody : "સંદેશ",
+DlgLnkUpload : "અપલોડ",
+DlgLnkBtnUpload : "આ સરà«àªµàª°àª¨à«‡ મોકલવà«àª‚",
+
+DlgLnkTarget : "ટારà«àª—ેટ/લકà«àª·à«àª¯",
+DlgLnkTargetFrame : "<ફà«àª°à«‡àª®>",
+DlgLnkTargetPopup : "<પૉપ-અપ વિનà«àª¡à«‹>",
+DlgLnkTargetBlank : "નવી વિનà«àª¡à«‹ (_blank)",
+DlgLnkTargetParent : "મૂળ વિનà«àª¡à«‹ (_parent)",
+DlgLnkTargetSelf : "આજ વિનà«àª¡à«‹ (_self)",
+DlgLnkTargetTop : "ઉપરની વિનà«àª¡à«‹ (_top)",
+DlgLnkTargetFrameName : "ટારà«àª—ેટ ફà«àª°à«‡àª® નà«àª‚ નામ",
+DlgLnkPopWinName : "પૉપ-અપ વિનà«àª¡à«‹ નà«àª‚ નામ",
+DlgLnkPopWinFeat : "પૉપ-અપ વિનà«àª¡à«‹ ફીચરસૅ",
+DlgLnkPopResize : "સાઇઠબદલી સકાય છે",
+DlgLnkPopLocation : "લોકેશન બાર",
+DlgLnkPopMenu : "મેનà«àª¯à«‚ બાર",
+DlgLnkPopScroll : "સà«àª•à«àª°à«‹àª² બાર",
+DlgLnkPopStatus : "સà«àªŸà«…ટસ બાર",
+DlgLnkPopToolbar : "ટૂલ બાર",
+DlgLnkPopFullScrn : "ફà«àª² સà«àª•à«àª°à«€àª¨ (IE)",
+DlgLnkPopDependent : "ડિપેનà«àª¡àª¨à«àªŸ (Netscape)",
+DlgLnkPopWidth : "પહોળાઈ",
+DlgLnkPopHeight : "ઊંચાઈ",
+DlgLnkPopLeft : "ડાબી બાજà«",
+DlgLnkPopTop : "જમણી બાજà«",
+
+DlnLnkMsgNoUrl : "લિંક URL ટાઇપ કરો",
+DlnLnkMsgNoEMail : "ઈ-મેલ સરનામà«àª‚ ટાઇપ કરો",
+DlnLnkMsgNoAnchor : "àªàª‚કર પસંદ કરો",
+DlnLnkMsgInvPopName : "પૉપ-અપ વિનà«àª¡à«‹ નà«àª‚ નામ àªàª²à«àª«àª¬à«‡àªŸàª¥à«€ શરૂ કરવો અને તેમાં સà«àªªà«‡àª‡àª¸ ન હોવી જોઈàª",
+
+// Color Dialog
+DlgColorTitle : "રંગ પસંદ કરો",
+DlgColorBtnClear : "સાફ કરો",
+DlgColorHighlight : "હાઈલાઇટ",
+DlgColorSelected : "સિલેકà«àªŸà«‡àª¡/પસંદ કરવà«àª‚",
+
+// Smiley Dialog
+DlgSmileyTitle : "સà«àª®àª¾àª‡àª²à«€ પસંદ કરો",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "સà«àªªà«‡àª¶àª¿àª…લ વિશિષà«àªŸ અકà«àª·àª° પસંદ કરો",
+
+// Table Dialog
+DlgTableTitle : "ટેબલ, કોઠાનà«àª‚ મથાળà«àª‚",
+DlgTableRows : "પંકà«àª¤àª¿àª¨àª¾ ખાના",
+DlgTableColumns : "કૉલમ/ઊભી કટાર",
+DlgTableBorder : "કોઠાની બાજà«(બોરà«àª¡àª°) સાઇàª",
+DlgTableAlign : "અલાઇનમનà«àªŸ/ગોઠવાયેલà«àª‚ ",
+DlgTableAlignNotSet : "<સેટ નથી>",
+DlgTableAlignLeft : "ડાબી બાજà«",
+DlgTableAlignCenter : "મધà«àª¯ સેનà«àªŸàª°",
+DlgTableAlignRight : "જમણી બાજà«",
+DlgTableWidth : "પહોળાઈ",
+DlgTableWidthPx : "પિકસલ",
+DlgTableWidthPc : "પà«àª°àª¤àª¿àª¶àª¤",
+DlgTableHeight : "ઊંચાઈ",
+DlgTableCellSpace : "સેલ અંતર",
+DlgTableCellPad : "સેલ પૅડિંગ",
+DlgTableCaption : "મથાળà«àª‚/કૅપà«àª¶àª¨ ",
+DlgTableSummary : "ટૂંકો àªàª¹à«‡àªµàª¾àª²",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "પંકà«àª¤àª¿àª¨àª¾ ખાનાના ગà«àª£",
+DlgCellWidth : "પહોળાઈ",
+DlgCellWidthPx : "પિકસલ",
+DlgCellWidthPc : "પà«àª°àª¤àª¿àª¶àª¤",
+DlgCellHeight : "ઊંચાઈ",
+DlgCellWordWrap : "વરà«àª¡ રૅપ",
+DlgCellWordWrapNotSet : "<સેટ નથી>",
+DlgCellWordWrapYes : "હા",
+DlgCellWordWrapNo : "ના",
+DlgCellHorAlign : "સમસà«àª¤àª°à«€àª¯ ગોઠવવà«àª‚",
+DlgCellHorAlignNotSet : "<સેટ નથી>",
+DlgCellHorAlignLeft : "ડાબી બાજà«",
+DlgCellHorAlignCenter : "મધà«àª¯ સેનà«àªŸàª°",
+DlgCellHorAlignRight: "જમણી બાજà«",
+DlgCellVerAlign : "લંબરૂપ ગોઠવવà«àª‚",
+DlgCellVerAlignNotSet : "<સેટ નથી>",
+DlgCellVerAlignTop : "ઉપર",
+DlgCellVerAlignMiddle : "મધà«àª¯ સેનà«àªŸàª°",
+DlgCellVerAlignBottom : "નીચે",
+DlgCellVerAlignBaseline : "મૂળ રેખા",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "પંકà«àª¤àª¿ સà«àªªàª¾àª¨",
+DlgCellCollSpan : "કૉલમ/ઊભી કટાર સà«àªªàª¾àª¨",
+DlgCellBackColor : "બૅકગà«àª°àª¾àª‰àª¨à«àª¡ રંગ",
+DlgCellBorderColor : "બોરà«àª¡àª°àª¨à«‹ રંગ",
+DlgCellBtnSelect : "પસંદ કરો...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "શોધવà«àª‚ અને બદલવà«àª‚",
+
+// Find Dialog
+DlgFindTitle : "શોધવà«àª‚",
+DlgFindFindBtn : "શોધવà«àª‚",
+DlgFindNotFoundMsg : "તમે શોધેલી ટેકà«àª¸à«àªŸ નથી મળી",
+
+// Replace Dialog
+DlgReplaceTitle : "બદલવà«àª‚",
+DlgReplaceFindLbl : "આ શોધો",
+DlgReplaceReplaceLbl : "આનાથી બદલો",
+DlgReplaceCaseChk : "કેસ સરખા રાખો",
+DlgReplaceReplaceBtn : "બદલવà«àª‚",
+DlgReplaceReplAllBtn : "બઘા બદલી ",
+DlgReplaceWordChk : "બઘા શબà«àª¦ સરખા રાખો",
+
+// Paste Operations / Dialog
+PasteErrorCut : "તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસ કટ કરવાની પરવાનગી નથી આપતી. (Ctrl+X) નો ઉપયોગ કરો.",
+PasteErrorCopy : "તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસ કોપી કરવાની પરવાનગી નથી આપતી. (Ctrl+C) का पà¥à¤°à¤¯à¥‹à¤— करें।",
+
+PasteAsText : "પેસà«àªŸ (ટેકà«àª¸à«àªŸ)",
+PasteFromWord : "પેસà«àªŸ (વરà«àª¡ થી)",
+
+DlgPasteMsg2 : "Ctrl+V નો પà«àª°àª¯à«‹àª— કરી પેસà«àªŸ કરો",
+DlgPasteSec : "તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસના કારણે,àªàª¡àª¿àªŸàª° તમારા કિલà«àªªàª¬à«‹àª°à«àª¡ ડેટા ને કોપી નથી કરી શકતો. તમારે આ વિનà«àª¡à«‹àª®àª¾àª‚ ફરીથી પેસà«àªŸ કરવà«àª‚ પડશે.",
+DlgPasteIgnoreFont : "ફૉનà«àªŸàª«à«‡àª¸ વà«àª¯àª¾àª–à«àª¯àª¾àª¨à«€ અવગણના",
+DlgPasteRemoveStyles : "સà«àªŸàª¾àª‡àª² વà«àª¯àª¾àª–à«àª¯àª¾ કાઢી નાખવી",
+
+// Color Picker
+ColorAutomatic : "સà«àªµàªšàª¾àª²àª¿àª¤",
+ColorMoreColors : "ઔર રંગ...",
+
+// Document Properties
+DocProps : "ડૉકà«àª¯à«àª®àª¨à«àªŸ ગà«àª£/પà«àª°à«‰àªªàª°à«àªŸàª¿àª",
+
+// Anchor Dialog
+DlgAnchorTitle : "àªàª‚કર ગà«àª£/પà«àª°à«‰àªªàª°à«àªŸàª¿àª",
+DlgAnchorName : "àªàª‚કરનà«àª‚ નામ",
+DlgAnchorErrorName : "àªàª‚કરનà«àª‚ નામ ટાઈપ કરો",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "શબà«àª¦àª•à«‹àª¶àª®àª¾àª‚ નથી",
+DlgSpellChangeTo : "આનાથી બદલવà«àª‚",
+DlgSpellBtnIgnore : "ઇગà«àª¨à«‹àª°/અવગણના કરવી",
+DlgSpellBtnIgnoreAll : "બધાની ઇગà«àª¨à«‹àª°/અવગણના કરવી",
+DlgSpellBtnReplace : "બદલવà«àª‚",
+DlgSpellBtnReplaceAll : "બધા બદલી કરો",
+DlgSpellBtnUndo : "અનà«àª¡à«‚",
+DlgSpellNoSuggestions : "- કઇ સજેશન નથી -",
+DlgSpellProgress : "શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક ચાલૠછે...",
+DlgSpellNoMispell : "શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: ખોટી જોડણી મળી નથી",
+DlgSpellNoChanges : "શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: àªàª•àªªàª£ શબà«àª¦ બદલયો નથી",
+DlgSpellOneChange : "શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: àªàª• શબà«àª¦ બદલયો છે",
+DlgSpellManyChanges : "શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: %1 શબà«àª¦ બદલયા છે",
+
+IeSpellDownload : "સà«àªªà«‡àª²-ચેકર ઇનà«àª¸à«àªŸà«‹àª² નથી. શà«àª‚ તમે ડાઉનલોડ કરવા માંગો છો?",
+
+// Button Dialog
+DlgButtonText : "ટેકà«àª¸à«àªŸ (વૅલà«àª¯à«‚)",
+DlgButtonType : "પà«àª°àª•àª¾àª°",
+DlgButtonTypeBtn : "બટન",
+DlgButtonTypeSbm : "સબà«àª®àª¿àªŸ",
+DlgButtonTypeRst : "રિસેટ",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "નામ",
+DlgCheckboxValue : "વૅલà«àª¯à«‚",
+DlgCheckboxSelected : "સિલેકà«àªŸà«‡àª¡",
+
+// Form Dialog
+DlgFormName : "નામ",
+DlgFormAction : "કà«àª°àª¿àª¯àª¾",
+DlgFormMethod : "પદà«àª§àª¤àª¿",
+
+// Select Field Dialog
+DlgSelectName : "નામ",
+DlgSelectValue : "વૅલà«àª¯à«‚",
+DlgSelectSize : "સાઇàª",
+DlgSelectLines : "લીટીઓ",
+DlgSelectChkMulti : "àªàª•àª¥à«€ વધારે પસંદ કરી શકો",
+DlgSelectOpAvail : "ઉપલબà«àª§ વિકલà«àªª",
+DlgSelectOpText : "ટેકà«àª¸à«àªŸ",
+DlgSelectOpValue : "વૅલà«àª¯à«‚",
+DlgSelectBtnAdd : "ઉમેરવà«àª‚",
+DlgSelectBtnModify : "બદલવà«àª‚",
+DlgSelectBtnUp : "ઉપર",
+DlgSelectBtnDown : "નીચે",
+DlgSelectBtnSetValue : "પસંદ કરલી વૅલà«àª¯à«‚ સેટ કરો",
+DlgSelectBtnDelete : "રદ કરવà«àª‚",
+
+// Textarea Dialog
+DlgTextareaName : "નામ",
+DlgTextareaCols : "કૉલમ/ઊભી કટાર",
+DlgTextareaRows : "પંકà«àª¤àª¿àª“",
+
+// Text Field Dialog
+DlgTextName : "નામ",
+DlgTextValue : "વૅલà«àª¯à«‚",
+DlgTextCharWidth : "કેરેકà«àªŸàª°àª¨à«€ પહોળાઈ",
+DlgTextMaxChars : "અધિકતમ કેરેકà«àªŸàª°",
+DlgTextType : "ટાઇપ",
+DlgTextTypeText : "ટેકà«àª¸à«àªŸ",
+DlgTextTypePass : "પાસવરà«àª¡",
+
+// Hidden Field Dialog
+DlgHiddenName : "નામ",
+DlgHiddenValue : "વૅલà«àª¯à«‚",
+
+// Bulleted List Dialog
+BulletedListProp : "બà«àª²à«‡àªŸ સૂચિ ગà«àª£",
+NumberedListProp : "સંખà«àª¯àª¾àª‚કà«àª¤àª¿ સૂચિ ગà«àª£",
+DlgLstStart : "શરૂઆતથી",
+DlgLstType : "પà«àª°àª•àª¾àª°",
+DlgLstTypeCircle : "વરà«àª¤à«àª³",
+DlgLstTypeDisc : "ડિસà«àª•",
+DlgLstTypeSquare : "ચોરસ",
+DlgLstTypeNumbers : "સંખà«àª¯àª¾ (1, 2, 3)",
+DlgLstTypeLCase : "નાના અકà«àª·àª° (a, b, c)",
+DlgLstTypeUCase : "મોટા અકà«àª·àª° (A, B, C)",
+DlgLstTypeSRoman : "નાના રોમન આંક (i, ii, iii)",
+DlgLstTypeLRoman : "મોટા રોમન આંક (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "સાધારણ",
+DlgDocBackTab : "બૅકગà«àª°àª¾àª‰àª¨à«àª¡",
+DlgDocColorsTab : "રંગ અને મારà«àªœàª¿àª¨/કિનાર",
+DlgDocMetaTab : "મેટાડૅટા",
+
+DlgDocPageTitle : "પેજ મથાળà«àª‚/ટાઇટલ",
+DlgDocLangDir : "ભાષા લેખવાની પદà«àª§àª¤àª¿",
+DlgDocLangDirLTR : "ડાબે થી જમણે (LTR)",
+DlgDocLangDirRTL : "જમણે થી ડાબે (RTL)",
+DlgDocLangCode : "ભાષા કોડ",
+DlgDocCharSet : "કેરેકà«àªŸàª° સેટ àªàª¨à«àª•à«‹àª¡àª¿àª‚ગ",
+DlgDocCharSetCE : "મધà«àª¯ યà«àª°à«‹àªªàª¿àª…ન (Central European)",
+DlgDocCharSetCT : "ચાઇનીઠ(Chinese Traditional Big5)",
+DlgDocCharSetCR : "સિરીલિક (Cyrillic)",
+DlgDocCharSetGR : "ગà«àª°à«€àª• (Greek)",
+DlgDocCharSetJP : "જાપાનિઠ(Japanese)",
+DlgDocCharSetKR : "કોરીયન (Korean)",
+DlgDocCharSetTR : "ટરà«àª•àª¿ (Turkish)",
+DlgDocCharSetUN : "યૂનિકોડ (UTF-8)",
+DlgDocCharSetWE : "પશà«àªšàª¿àª® યà«àª°à«‹àªªàª¿àª…ન (Western European)",
+DlgDocCharSetOther : "અનà«àª¯ કેરેકà«àªŸàª° સેટ àªàª¨à«àª•à«‹àª¡àª¿àª‚ગ",
+
+DlgDocDocType : "ડૉકà«àª¯à«àª®àª¨à«àªŸ પà«àª°àª•àª¾àª° શીરà«àª·àª•",
+DlgDocDocTypeOther : "અનà«àª¯ ડૉકà«àª¯à«àª®àª¨à«àªŸ પà«àª°àª•àª¾àª° શીરà«àª·àª•",
+DlgDocIncXHTML : "XHTML સૂચના સમાવિષà«àªŸ કરવી",
+DlgDocBgColor : "બૅકગà«àª°àª¾àª‰àª¨à«àª¡ રંગ",
+DlgDocBgImage : "બૅકગà«àª°àª¾àª‰àª¨à«àª¡ ચિતà«àª° URL",
+DlgDocBgNoScroll : "સà«àª•à«àª°à«‹àª² ન થાય તેવà«àª‚ બૅકગà«àª°àª¾àª‰àª¨à«àª¡",
+DlgDocCText : "ટેકà«àª¸à«àªŸ",
+DlgDocCLink : "લિંક",
+DlgDocCVisited : "વિàªàª¿àªŸà«‡àª¡ લિંક",
+DlgDocCActive : "સકà«àª°àª¿àª¯ લિંક",
+DlgDocMargins : "પેજ મારà«àªœàª¿àª¨",
+DlgDocMaTop : "ઉપર",
+DlgDocMaLeft : "ડાબી",
+DlgDocMaRight : "જમણી",
+DlgDocMaBottom : "નીચે",
+DlgDocMeIndex : "ડૉકà«àª¯à«àª®àª¨à«àªŸ ઇનà«àª¡à«‡àª•à«àª¸ સંકેતશબà«àª¦ (અલà«àªªàªµàª¿àª°àª¾àª® (,) થી અલગ કરો)",
+DlgDocMeDescr : "ડૉકà«àª¯à«àª®àª¨à«àªŸ વરà«àª£àª¨",
+DlgDocMeAuthor : "લેખક",
+DlgDocMeCopy : "કૉપિરાઇટ",
+DlgDocPreview : "પૂરà«àªµàª¦àª°à«àª¶àª¨",
+
+// Templates Dialog
+Templates : "ટેમà«àªªà«àª²à«‡àªŸ",
+DlgTemplatesTitle : "કનà«àªŸà«‡àª¨à«àªŸ ટેમà«àªªà«àª²à«‡àªŸ",
+DlgTemplatesSelMsg : "àªàª¡àª¿àªŸàª°àª®àª¾àª‚ ઓપન કરવા ટેમà«àªªà«àª²à«‡àªŸ પસંદ કરો (વરà«àª¤àª®àª¾àª¨ કનà«àªŸà«‡àª¨à«àªŸ સેવ નહીં થાય):",
+DlgTemplatesLoading : "ટેમà«àªªà«àª²à«‡àªŸ સૂચિ લોડ થાય છે. રાહ જà«àª“...",
+DlgTemplatesNoTpl : "(કોઈ ટેમà«àªªà«àª²à«‡àªŸ ડિફાઇન નથી)",
+DlgTemplatesReplace : "મૂળ શબà«àª¦àª¨à«‡ બદલો",
+
+// About Dialog
+DlgAboutAboutTab : "FCKEditor ના વિષે",
+DlgAboutBrowserInfoTab : "બà«àª°àª¾àª‰àªàª° ના વિષે",
+DlgAboutLicenseTab : "લાઇસનà«àª¸",
+DlgAboutVersion : "વરà«àªàª¨",
+DlgAboutInfo : "વધારે માહિતી માટે:",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/he.js b/httemplate/elements/fckeditor/editor/lang/he.js
new file mode 100644
index 0000000..4cbbf95
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/he.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Hebrew language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "rtl",
+
+ToolbarCollapse : "כיווץ סרגל הכלי×",
+ToolbarExpand : "פתיחת סרגל הכלי×",
+
+// Toolbar Items and Context Menu
+Save : "שמירה",
+NewPage : "דף חדש",
+Preview : "תצוגה מקדימה",
+Cut : "גזירה",
+Copy : "העתקה",
+Paste : "הדבקה",
+PasteText : "הדבקה כטקסט פשוט",
+PasteWord : "הדבקה מ-וורד",
+Print : "הדפסה",
+SelectAll : "בחירת הכל",
+RemoveFormat : "הסרת העיצוב",
+InsertLinkLbl : "קישור",
+InsertLink : "הוספת/עריכת קישור",
+RemoveLink : "הסרת הקישור",
+VisitLink : "פתח קישור",
+Anchor : "הוספת/עריכת נקודת עיגון",
+AnchorDelete : "הסר נקודת עיגון",
+InsertImageLbl : "תמונה",
+InsertImage : "הוספת/עריכת תמונה",
+InsertFlashLbl : "פל×ש",
+InsertFlash : "הוסף/ערוך פל×ש",
+InsertTableLbl : "טבלה",
+InsertTable : "הוספת/עריכת טבלה",
+InsertLineLbl : "קו",
+InsertLine : "הוספת קו ×ופקי",
+InsertSpecialCharLbl: "תו מיוחד",
+InsertSpecialChar : "הוספת תו מיוחד",
+InsertSmileyLbl : "סמיילי",
+InsertSmiley : "הוספת סמיילי",
+About : "×ודות FCKeditor",
+Bold : "מודגש",
+Italic : "נטוי",
+Underline : "קו תחתון",
+StrikeThrough : "כתיב מחוק",
+Subscript : "כתיב תחתון",
+Superscript : "כתיב עליון",
+LeftJustify : "יישור לשמ×ל",
+CenterJustify : "מרכוז",
+RightJustify : "יישור לימין",
+BlockJustify : "יישור לשוליי×",
+DecreaseIndent : "הקטנת ×ינדנטציה",
+IncreaseIndent : "הגדלת ×ינדנטציה",
+Blockquote : "בלוק ציטוט",
+CreateDiv : "צור מיכל(תג)DIV",
+EditDiv : "ערוך מיכל (תג)DIV",
+DeleteDiv : "הסר מיכל(תג) DIV",
+Undo : "ביטול צעד ×חרון",
+Redo : "חזרה על צעד ×חרון",
+NumberedListLbl : "רשימה ממוספרת",
+NumberedList : "הוספת/הסרת רשימה ממוספרת",
+BulletedListLbl : "רשימת נקודות",
+BulletedList : "הוספת/הסרת רשימת נקודות",
+ShowTableBorders : "הצגת מסגרת הטבלה",
+ShowDetails : "הצגת פרטי×",
+Style : "סגנון",
+FontFormat : "עיצוב",
+Font : "גופן",
+FontSize : "גודל",
+TextColor : "צבע טקסט",
+BGColor : "צבע רקע",
+Source : "מקור",
+Find : "חיפוש",
+Replace : "החלפה",
+SpellCheck : "בדיקת ×יות",
+UniversalKeyboard : "מקלדת ×וניברסלית",
+PageBreakLbl : "שבירת דף",
+PageBreak : "הוסף שבירת דף",
+
+Form : "טופס",
+Checkbox : "תיבת סימון",
+RadioButton : "לחצן ×פשרויות",
+TextField : "שדה טקסט",
+Textarea : "×יזור טקסט",
+HiddenField : "שדה חבוי",
+Button : "כפתור",
+SelectionField : "שדה בחירה",
+ImageButton : "כפתור תמונה",
+
+FitWindow : "הגדל ×ת גודל העורך",
+ShowBlocks : "הצג בלוקי×",
+
+// Context Menu
+EditLink : "עריכת קישור",
+CellCM : "ת×",
+RowCM : "שורה",
+ColumnCM : "עמודה",
+InsertRowAfter : "הוסף שורה ×חרי",
+InsertRowBefore : "הוסף שורה לפני",
+DeleteRows : "מחיקת שורות",
+InsertColumnAfter : "הוסף עמודה ×חרי",
+InsertColumnBefore : "הוסף עמודה לפני",
+DeleteColumns : "מחיקת עמודות",
+InsertCellAfter : "הוסף ×ª× ×חרי",
+InsertCellBefore : "הוסף ×ª× ×חרי",
+DeleteCells : "מחיקת ת××™×",
+MergeCells : "מיזוג ת××™×",
+MergeRight : "מזג ימינה",
+MergeDown : "מזג למטה",
+HorizontalSplitCell : "פצל ×ª× ×ופקית",
+VerticalSplitCell : "פצל ×ª× ×נכית",
+TableDelete : "מחק טבלה",
+CellProperties : "תכונות הת×",
+TableProperties : "תכונות הטבלה",
+ImageProperties : "תכונות התמונה",
+FlashProperties : "מ×פייני פל×ש",
+
+AnchorProp : "מ×פייני נקודת עיגון",
+ButtonProp : "מ×פייני כפתור",
+CheckboxProp : "מ×פייני תיבת סימון",
+HiddenFieldProp : "מ×פיני שדה חבוי",
+RadioButtonProp : "מ×פייני לחצן ×פשרויות",
+ImageButtonProp : "מ×פיני כפתור תמונה",
+TextFieldProp : "מ×פייני שדה טקסט",
+SelectionFieldProp : "מ×פייני שדה בחירה",
+TextareaProp : "מ×פיני ×יזור טקסט",
+FormProp : "מ×פיני טופס",
+
+FontFormats : "נורמלי;קוד;כתובת;כותרת;כותרת 2;כותרת 3;כותרת 4;כותרת 5;כותרת 6",
+
+// Alerts and Messages
+ProcessingXHTML : "מעבד XHTML, × × ×œ×”×ž×ª×™×Ÿ...",
+Done : "המשימה הושלמה",
+PasteWordConfirm : "נר××” הטקסט שבכוונתך להדביק מקורו בקובץ וורד. ×”×× ×‘×¨×¦×•× ×š לנקות ×ותו ×˜×¨× ×”×”×“×‘×§×”?",
+NotCompatiblePaste : "פעולה זו זמינה לדפדפן ×ינטרנט ×קספלורר ×ž×’×™×¨×¡× 5.5 ומעלה. ×”×× ×œ×”×ž×©×™×š בהדבקה ×œ×œ× ×”× ×™×§×•×™?",
+UnknownToolbarItem : "פריט ×œ× ×™×“×•×¢ בסרגל ×”×›×œ×™× \"%1\"",
+UnknownCommand : "×©× ×¤×¢×•×œ×” ×œ× ×™×“×•×¢ \"%1\"",
+NotImplemented : "הפקודה ×œ× ×ž×™×•×©×ž×ª",
+UnknownToolbarSet : "ערכת סרגל ×”×›×œ×™× \"%1\" ×œ× ×§×™×™×ž×ª",
+NoActiveX : "הגדרות ×בטחה של הדפדפן עלולות לגביל ×ת ×פשרויות העריכה.יש ל×פשר ×ת ×”×ופציה \"הרץ ×¤×§×“×™× ×¤×¢×™×œ×™× ×•×ª×•×¡×¤×•×ª\". תוכל לחוות טעויות ×•×—×™×•×•×™× ×©×œ ×פשרויות שחסרי×.",
+BrowseServerBlocked : "×œ× × ×™×ª×Ÿ לגשת לדפדפן מש×בי×.×× × ×•×•×“× ×©×—×•×¡× ×—×œ×•× ×•×ª ×”×§×•×¤×¦×™× ×œ× ×¤×¢×™×œ.",
+DialogBlocked : "×œ× ×”×™×” ניתן לפתוח חלון די×לוג. ×× × ×•×•×“× ×©×—×•×¡× ×—×œ×•× ×•×ª ×§×•×¤×¦×™× ×œ× ×¤×¢×™×œ.",
+VisitLinkBlocked : "×œ× × ×™×ª×Ÿ לפתוח חלון חדש.× × ×œ×•×•×“× ×©×—×•×¡×ž×™ החלונות ×”×§×•×¤×¦×™× ×œ× ×¤×¢×™×œ×™×.",
+
+// Dialogs
+DlgBtnOK : "×ישור",
+DlgBtnCancel : "ביטול",
+DlgBtnClose : "סגירה",
+DlgBtnBrowseServer : "סייר השרת",
+DlgAdvancedTag : "×פשרויות מתקדמות",
+DlgOpOther : "<×חר>",
+DlgInfoTab : "מידע",
+DlgAlertUrl : "×× × ×”×–×Ÿ URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<×œ× × ×§×‘×¢>",
+DlgGenId : "זיהוי (Id)",
+DlgGenLangDir : "כיוון שפה",
+DlgGenLangDirLtr : "שמ×ל לימין (LTR)",
+DlgGenLangDirRtl : "ימין לשמ×ל (RTL)",
+DlgGenLangCode : "קוד שפה",
+DlgGenAccessKey : "מקש גישה",
+DlgGenName : "ש×",
+DlgGenTabIndex : "מספר ט×ב",
+DlgGenLongDescr : "קישור לתי×ור מפורט",
+DlgGenClass : "גיליונות עיצוב קבוצות",
+DlgGenTitle : "כותרת מוצעת",
+DlgGenContType : "Content Type מוצע",
+DlgGenLinkCharset : "קידוד המש×ב המקושר",
+DlgGenStyle : "סגנון",
+
+// Image Dialog
+DlgImgTitle : "תכונות התמונה",
+DlgImgInfoTab : "מידע על התמונה",
+DlgImgBtnUpload : "שליחה לשרת",
+DlgImgURL : "כתובת (URL)",
+DlgImgUpload : "העל××”",
+DlgImgAlt : "טקסט חלופי",
+DlgImgWidth : "רוחב",
+DlgImgHeight : "גובה",
+DlgImgLockRatio : "נעילת היחס",
+DlgBtnResetSize : "×יפוס הגודל",
+DlgImgBorder : "מסגרת",
+DlgImgHSpace : "מרווח ×ופקי",
+DlgImgVSpace : "מרווח ×× ×›×™",
+DlgImgAlign : "יישור",
+DlgImgAlignLeft : "לשמ×ל",
+DlgImgAlignAbsBottom: "לתחתית ×”×בסולוטית",
+DlgImgAlignAbsMiddle: "מרכוז ×בסולוטי",
+DlgImgAlignBaseline : "לקו התחתית",
+DlgImgAlignBottom : "לתחתית",
+DlgImgAlignMiddle : "ל×מצע",
+DlgImgAlignRight : "לימין",
+DlgImgAlignTextTop : "לר×ש הטקסט",
+DlgImgAlignTop : "למעלה",
+DlgImgPreview : "תצוגה מקדימה",
+DlgImgAlertUrl : "× × ×œ×”×§×œ×™×“ ×ת כתובת התמונה",
+DlgImgLinkTab : "קישור",
+
+// Flash Dialog
+DlgFlashTitle : "מ×פיני פל×ש",
+DlgFlashChkPlay : "נגן ×וטומטי",
+DlgFlashChkLoop : "לול××”",
+DlgFlashChkMenu : "×פשר תפריט פל×ש",
+DlgFlashScale : "גודל",
+DlgFlashScaleAll : "הצג הכל",
+DlgFlashScaleNoBorder : "×œ×œ× ×’×‘×•×œ×•×ª",
+DlgFlashScaleFit : "הת×מה מושלמת",
+
+// Link Dialog
+DlgLnkWindowTitle : "קישור",
+DlgLnkInfoTab : "מידע על הקישור",
+DlgLnkTargetTab : "מטרה",
+
+DlgLnkType : "סוג קישור",
+DlgLnkTypeURL : "כתובת (URL)",
+DlgLnkTypeAnchor : "עוגן בעמוד זה",
+DlgLnkTypeEMail : "דו×''ל",
+DlgLnkProto : "פרוטוקול",
+DlgLnkProtoOther : "<×חר>",
+DlgLnkURL : "כתובת (URL)",
+DlgLnkAnchorSel : "בחירת עוגן",
+DlgLnkAnchorByName : "עפ''×™ ×©× ×”×¢×•×’×Ÿ",
+DlgLnkAnchorById : "עפ''י זיהוי (Id) הרכיב",
+DlgLnkNoAnchors : "(×ין ×¢×•×’× ×™× ×–×ž×™× ×™× ×‘×“×£)",
+DlgLnkEMail : "כתובת הדו×''ל",
+DlgLnkEMailSubject : "× ×•×©× ×”×”×•×“×¢×”",
+DlgLnkEMailBody : "גוף ההודעה",
+DlgLnkUpload : "העל××”",
+DlgLnkBtnUpload : "שליחה לשרת",
+
+DlgLnkTarget : "מטרה",
+DlgLnkTargetFrame : "<מסגרת>",
+DlgLnkTargetPopup : "<חלון קופץ>",
+DlgLnkTargetBlank : "חלון חדש (_blank)",
+DlgLnkTargetParent : "חלון ×”×ב (_parent)",
+DlgLnkTargetSelf : "ב×ותו החלון (_self)",
+DlgLnkTargetTop : "חלון ר×שי (_top)",
+DlgLnkTargetFrameName : "×©× ×ž×¡×’×¨×ª היעד",
+DlgLnkPopWinName : "×©× ×”×—×œ×•×Ÿ הקופץ",
+DlgLnkPopWinFeat : "תכונות החלון הקופץ",
+DlgLnkPopResize : "בעל גודל ניתן לשינוי",
+DlgLnkPopLocation : "סרגל כתובת",
+DlgLnkPopMenu : "סרגל תפריט",
+DlgLnkPopScroll : "ניתן לגלילה",
+DlgLnkPopStatus : "סרגל חיווי",
+DlgLnkPopToolbar : "סרגל הכלי×",
+DlgLnkPopFullScrn : "מסך ×ž×œ× (IE)",
+DlgLnkPopDependent : "תלוי (Netscape)",
+DlgLnkPopWidth : "רוחב",
+DlgLnkPopHeight : "גובה",
+DlgLnkPopLeft : "×ž×™×§×•× ×¦×“ שמ×ל",
+DlgLnkPopTop : "×ž×™×§×•× ×¦×“ עליון",
+
+DlnLnkMsgNoUrl : "× × ×œ×”×§×œ×™×“ ×ת כתובת הקישור (URL)",
+DlnLnkMsgNoEMail : "× × ×œ×”×§×œ×™×“ ×ת כתובת הדו×''ל",
+DlnLnkMsgNoAnchor : "× × ×œ×‘×—×•×¨ עוגן במסמך",
+DlnLnkMsgInvPopName : "×©× ×”×—×œ×•×Ÿ הקופץ חייב להתחיל ב×ותיות ו×סור לכלול רווחי×",
+
+// Color Dialog
+DlgColorTitle : "בחירת צבע",
+DlgColorBtnClear : "×יפוס",
+DlgColorHighlight : "נוכחי",
+DlgColorSelected : "נבחר",
+
+// Smiley Dialog
+DlgSmileyTitle : "הוספת סמיילי",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "בחירת תו מיוחד",
+
+// Table Dialog
+DlgTableTitle : "תכונות טבלה",
+DlgTableRows : "שורות",
+DlgTableColumns : "עמודות",
+DlgTableBorder : "גודל מסגרת",
+DlgTableAlign : "יישור",
+DlgTableAlignNotSet : "<×œ× × ×§×‘×¢>",
+DlgTableAlignLeft : "שמ×ל",
+DlgTableAlignCenter : "מרכז",
+DlgTableAlignRight : "ימין",
+DlgTableWidth : "רוחב",
+DlgTableWidthPx : "פיקסלי×",
+DlgTableWidthPc : "×חוז",
+DlgTableHeight : "גובה",
+DlgTableCellSpace : "מרווח ת×",
+DlgTableCellPad : "ריפוד ת×",
+DlgTableCaption : "כיתוב",
+DlgTableSummary : "סיכו×",
+DlgTableHeaders : "כותרות",
+DlgTableHeadersNone : "×ין",
+DlgTableHeadersColumn : "עמודה ר×שונה",
+DlgTableHeadersRow : "שורה ר×שונה",
+DlgTableHeadersBoth : "שניה×",
+
+// Table Cell Dialog
+DlgCellTitle : "תכונות ת×",
+DlgCellWidth : "רוחב",
+DlgCellWidthPx : "פיקסלי×",
+DlgCellWidthPc : "×חוז",
+DlgCellHeight : "גובה",
+DlgCellWordWrap : "גלילת שורות",
+DlgCellWordWrapNotSet : "<×œ× × ×§×‘×¢>",
+DlgCellWordWrapYes : "כן",
+DlgCellWordWrapNo : "ל×",
+DlgCellHorAlign : "יישור ×ופקי",
+DlgCellHorAlignNotSet : "<×œ× × ×§×‘×¢>",
+DlgCellHorAlignLeft : "שמ×ל",
+DlgCellHorAlignCenter : "מרכז",
+DlgCellHorAlignRight: "ימין",
+DlgCellVerAlign : "יישור ×× ×›×™",
+DlgCellVerAlignNotSet : "<×œ× × ×§×‘×¢>",
+DlgCellVerAlignTop : "למעלה",
+DlgCellVerAlignMiddle : "ל×מצע",
+DlgCellVerAlignBottom : "לתחתית",
+DlgCellVerAlignBaseline : "קו תחתית",
+DlgCellType : "סוג ת×",
+DlgCellTypeData : "סוג",
+DlgCellTypeHeader : "כותרת",
+DlgCellRowSpan : "טווח שורות",
+DlgCellCollSpan : "טווח עמודות",
+DlgCellBackColor : "צבע רקע",
+DlgCellBorderColor : "צבע מסגרת",
+DlgCellBtnSelect : "בחירה...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "חפש והחלף",
+
+// Find Dialog
+DlgFindTitle : "חיפוש",
+DlgFindFindBtn : "חיפוש",
+DlgFindNotFoundMsg : "הטקסט המבוקש ×œ× × ×ž×¦×.",
+
+// Replace Dialog
+DlgReplaceTitle : "החלפה",
+DlgReplaceFindLbl : "חיפוש מחרוזת:",
+DlgReplaceReplaceLbl : "החלפה במחרוזת:",
+DlgReplaceCaseChk : "הת×מת סוג ×ותיות (Case)",
+DlgReplaceReplaceBtn : "החלפה",
+DlgReplaceReplAllBtn : "החלפה בכל העמוד",
+DlgReplaceWordChk : "הת×מה למילה המל××”",
+
+// Paste Operations / Dialog
+PasteErrorCut : "הגדרות ×”×בטחה בדפדפן שלך ×œ× ×ž×פשרות לעורך לבצע פעולות גזירה ×וטומטיות. יש להשתמש במקלדת ×œ×©× ×›×š (Ctrl+X).",
+PasteErrorCopy : "הגדרות ×”×בטחה בדפדפן שלך ×œ× ×ž×פשרות לעורך לבצע פעולות העתקה ×וטומטיות. יש להשתמש במקלדת ×œ×©× ×›×š (Ctrl+C).",
+
+PasteAsText : "הדבקה כטקסט פשוט",
+PasteFromWord : "הדבקה מ-וורד",
+
+DlgPasteMsg2 : "×× × ×”×“×‘×§ בתוך הקופסה ב×מצעות (<STRONG>Ctrl+V</STRONG>) ולחץ על <STRONG>×ישור</STRONG>.",
+DlgPasteSec : "עקב הגדרות ×בטחה בדפדפן, ×œ× × ×™×ª×Ÿ לגשת ×ל לוח ×”×’×–×™×¨×™× (clipboard) בצורה ישירה.×× × ×‘×¦×¢ הדבק שוב בחלון ×–×”.",
+DlgPasteIgnoreFont : "×”×ª×¢×œ× ×ž×”×’×“×¨×•×ª סוג פונט",
+DlgPasteRemoveStyles : "הסר הגדרות סגנון",
+
+// Color Picker
+ColorAutomatic : "×וטומטי",
+ColorMoreColors : "×¦×‘×¢×™× × ×•×¡×¤×™×...",
+
+// Document Properties
+DocProps : "מ×פיני מסמך",
+
+// Anchor Dialog
+DlgAnchorTitle : "מ×פיני נקודת עיגון",
+DlgAnchorName : "×©× ×œ× ×§×•×“×ª עיגון",
+DlgAnchorErrorName : "×× × ×”×–×Ÿ ×©× ×œ× ×§×•×“×ª עיגון",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "×œ× × ×ž×¦× ×‘×ž×™×œ×•×Ÿ",
+DlgSpellChangeTo : "שנה ל",
+DlgSpellBtnIgnore : "התעל×",
+DlgSpellBtnIgnoreAll : "×”×ª×¢×œ× ×ž×”×›×œ",
+DlgSpellBtnReplace : "החלף",
+DlgSpellBtnReplaceAll : "החלף הכל",
+DlgSpellBtnUndo : "החזר",
+DlgSpellNoSuggestions : "- ×ין הצעות -",
+DlgSpellProgress : "בדיקות ×יות בתהליך ....",
+DlgSpellNoMispell : "בדיקות ×יות הסתיימה: ×œ× × ×ž×¦×ו שגיעות כתיב",
+DlgSpellNoChanges : "בדיקות ×יות הסתיימה: ×œ× ×©×•× ×ª×” ××£ מילה",
+DlgSpellOneChange : "בדיקות ×יות הסתיימה: שונתה מילה ×חת",
+DlgSpellManyChanges : "בדיקות ×יות הסתיימה: %1 ×ž×™×œ×™× ×©×•× ×•",
+
+IeSpellDownload : "בודק ×”×יות ×œ× ×ž×•×ª×§×Ÿ, ×”×× ×תה מעוניין להוריד?",
+
+// Button Dialog
+DlgButtonText : "טקסט (ערך)",
+DlgButtonType : "סוג",
+DlgButtonTypeBtn : "כפתור",
+DlgButtonTypeSbm : "שלח",
+DlgButtonTypeRst : "×פס",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ש×",
+DlgCheckboxValue : "ערך",
+DlgCheckboxSelected : "בחור",
+
+// Form Dialog
+DlgFormName : "ש×",
+DlgFormAction : "שלח ×ל",
+DlgFormMethod : "סוג שליחה",
+
+// Select Field Dialog
+DlgSelectName : "ש×",
+DlgSelectValue : "ערך",
+DlgSelectSize : "גודל",
+DlgSelectLines : "שורות",
+DlgSelectChkMulti : "×פשר בחירות מרובות",
+DlgSelectOpAvail : "×פשרויות זמינות",
+DlgSelectOpText : "טקסט",
+DlgSelectOpValue : "ערך",
+DlgSelectBtnAdd : "הוסף",
+DlgSelectBtnModify : "שנה",
+DlgSelectBtnUp : "למעלה",
+DlgSelectBtnDown : "למטה",
+DlgSelectBtnSetValue : "קבע כברירת מחדל",
+DlgSelectBtnDelete : "מחק",
+
+// Textarea Dialog
+DlgTextareaName : "ש×",
+DlgTextareaCols : "עמודות",
+DlgTextareaRows : "שורות",
+
+// Text Field Dialog
+DlgTextName : "ש×",
+DlgTextValue : "ערך",
+DlgTextCharWidth : "רוחב ב×ותיות",
+DlgTextMaxChars : "מקסימות ×ותיות",
+DlgTextType : "סוג",
+DlgTextTypeText : "טקסט",
+DlgTextTypePass : "סיסמה",
+
+// Hidden Field Dialog
+DlgHiddenName : "ש×",
+DlgHiddenValue : "ערך",
+
+// Bulleted List Dialog
+BulletedListProp : "מ×פייני רשימה",
+NumberedListProp : "מ×פייני רשימה ממוספרת",
+DlgLstStart : "התחלה",
+DlgLstType : "סוג",
+DlgLstTypeCircle : "עיגול",
+DlgLstTypeDisc : "דיסק",
+DlgLstTypeSquare : "מרובע",
+DlgLstTypeNumbers : "×ž×¡×¤×¨×™× (1, 2, 3)",
+DlgLstTypeLCase : "×ותיות קטנות (a, b, c)",
+DlgLstTypeUCase : "×ותיות גדולות (A, B, C)",
+DlgLstTypeSRoman : "ספרות רומ×יות קטנות (i, ii, iii)",
+DlgLstTypeLRoman : "ספרות רומ×יות גדולות (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "כללי",
+DlgDocBackTab : "רקע",
+DlgDocColorsTab : "×¦×‘×¢×™× ×•×’×‘×•×œ×•×ª",
+DlgDocMetaTab : "נתוני META",
+
+DlgDocPageTitle : "כותרת דף",
+DlgDocLangDir : "כיוון שפה",
+DlgDocLangDirLTR : "שמ×ל לימין (LTR)",
+DlgDocLangDirRTL : "ימין לשמ×ל (RTL)",
+DlgDocLangCode : "קוד שפה",
+DlgDocCharSet : "קידוד ×ותיות",
+DlgDocCharSetCE : "מרכז ×ירופה",
+DlgDocCharSetCT : "סיני מסורתי (Big5)",
+DlgDocCharSetCR : "קירילי",
+DlgDocCharSetGR : "יוונית",
+DlgDocCharSetJP : "יפנית",
+DlgDocCharSetKR : "קור×נית",
+DlgDocCharSetTR : "טורקית",
+DlgDocCharSetUN : "יוני קוד (UTF-8)",
+DlgDocCharSetWE : "מערב ×ירופה",
+DlgDocCharSetOther : "קידוד ×ותיות ×חר",
+
+DlgDocDocType : "הגדרות סוג מסמך",
+DlgDocDocTypeOther : "הגדרות סוג מסמך ×חרות",
+DlgDocIncXHTML : "כלול הגדרות XHTML",
+DlgDocBgColor : "צבע רקע",
+DlgDocBgImage : "URL לתמונת רקע",
+DlgDocBgNoScroll : "רגע ×œ×œ× ×’×œ×™×œ×”",
+DlgDocCText : "טקסט",
+DlgDocCLink : "קישור",
+DlgDocCVisited : "קישור שבוקר",
+DlgDocCActive : " קישור פעיל",
+DlgDocMargins : "גבולות דף",
+DlgDocMaTop : "למעלה",
+DlgDocMaLeft : "שמ×לה",
+DlgDocMaRight : "ימינה",
+DlgDocMaBottom : "למטה",
+DlgDocMeIndex : "מפתח ×¢× ×™×™× ×™× ×©×œ המסמך )מופרד בפסיק(",
+DlgDocMeDescr : "ת×ור מסמך",
+DlgDocMeAuthor : "מחבר",
+DlgDocMeCopy : "זכויות יוצרי×",
+DlgDocPreview : "תצוגה מקדימה",
+
+// Templates Dialog
+Templates : "תבניות",
+DlgTemplatesTitle : "תביות תוכן",
+DlgTemplatesSelMsg : "×× × ×‘×—×¨ תבנית לפתיחה בעורך <BR>התוכן המקורי ימחק:",
+DlgTemplatesLoading : "מעלה רשימת תבניות ×× × ×”×ž×ª×Ÿ",
+DlgTemplatesNoTpl : "(×œ× ×”×•×’×“×¨×• תבניות)",
+DlgTemplatesReplace : "החלפת תוכן ממשי",
+
+// About Dialog
+DlgAboutAboutTab : "×ודות",
+DlgAboutBrowserInfoTab : "גירסת דפדפן",
+DlgAboutLicenseTab : "רשיון",
+DlgAboutVersion : "גירס×",
+DlgAboutInfo : "מידע נוסף ניתן ×œ×ž×¦×•× ×›×ן:",
+
+// Div Dialog
+DlgDivGeneralTab : "כללי",
+DlgDivAdvancedTab : "מתקד×",
+DlgDivStyle : "סגנון",
+DlgDivInlineStyle : "סגנון בתוך השורה",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/hi.js b/httemplate/elements/fckeditor/editor/lang/hi.js
new file mode 100644
index 0000000..b4e88fc
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/hi.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Hindi language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "टूलबार सिमटायें",
+ToolbarExpand : "टूलबार का विसà¥à¤¤à¤¾à¤° करें",
+
+// Toolbar Items and Context Menu
+Save : "सेव",
+NewPage : "नया पेज",
+Preview : "पà¥à¤°à¥€à¤µà¥à¤¯à¥‚",
+Cut : "कट",
+Copy : "कॉपी",
+Paste : "पेसà¥à¤Ÿ",
+PasteText : "पेसà¥à¤Ÿ (सादा टॅकà¥à¤¸à¥à¤Ÿ)",
+PasteWord : "पेसà¥à¤Ÿ (वरà¥à¤¡ से)",
+Print : "पà¥à¤°à¤¿à¤¨à¥à¤Ÿ",
+SelectAll : "सब सॅलॅकà¥à¤Ÿ करें",
+RemoveFormat : "फ़ॉरà¥à¤®à¥ˆà¤Ÿ हटायें",
+InsertLinkLbl : "लिंक",
+InsertLink : "लिंक इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+RemoveLink : "लिंक हटायें",
+VisitLink : "लिंक खोलें",
+Anchor : "à¤à¤‚कर इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+AnchorDelete : "à¤à¤‚कर हटायें",
+InsertImageLbl : "तसà¥à¤µà¥€à¤°",
+InsertImage : "तसà¥à¤µà¥€à¤° इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+InsertFlashLbl : "फ़à¥à¤²à¥ˆà¤¶",
+InsertFlash : "फ़à¥à¤²à¥ˆà¤¶ इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+InsertTableLbl : "टेबल",
+InsertTable : "टेबल इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+InsertLineLbl : "रेखा",
+InsertLine : "हॉरिज़ॉनà¥à¤Ÿà¤² रेखा इनà¥à¤¸à¤°à¥à¤Ÿ करें",
+InsertSpecialCharLbl: "विशेष करॅकà¥à¤Ÿà¤°",
+InsertSpecialChar : "विशेष करॅकà¥à¤Ÿà¤° इनà¥à¤¸à¤°à¥à¤Ÿ करें",
+InsertSmileyLbl : "सà¥à¤®à¤¾à¤‡à¤²à¥€",
+InsertSmiley : "सà¥à¤®à¤¾à¤‡à¤²à¥€ इनà¥à¤¸à¤°à¥à¤Ÿ करें",
+About : "FCKeditor के बारे में",
+Bold : "बोलà¥à¤¡",
+Italic : "इटैलिक",
+Underline : "रेखांकण",
+StrikeThrough : "सà¥à¤Ÿà¥à¤°à¤¾à¤‡à¤• थà¥à¤°à¥‚",
+Subscript : "अधोलेख",
+Superscript : "अभिलेख",
+LeftJustify : "बायीं तरफ",
+CenterJustify : "बीच में",
+RightJustify : "दायीं तरफ",
+BlockJustify : "बà¥à¤²à¥‰à¤• जसà¥à¤Ÿà¥€à¥žà¤¾à¤ˆ",
+DecreaseIndent : "इनà¥à¤¡à¥…नà¥à¤Ÿ कम करें",
+IncreaseIndent : "इनà¥à¤¡à¥…नà¥à¤Ÿ बà¥à¤¾à¤¯à¥‡à¤‚",
+Blockquote : "बà¥à¤²à¥‰à¤•-कोट",
+CreateDiv : "डिव (Div) कनà¥à¤Ÿà¥‡à¤¨à¤° बनायें",
+EditDiv : "डिव (Div) कनà¥à¤Ÿà¥‡à¤¨à¤° बदलें",
+DeleteDiv : "डिव कनà¥à¤Ÿà¥‡à¤¨à¤° हटायें",
+Undo : "अनà¥à¤¡à¥‚",
+Redo : "रीडू",
+NumberedListLbl : "अंकीय सूची",
+NumberedList : "अंकीय सूची इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+BulletedListLbl : "बà¥à¤²à¥…ट सूची",
+BulletedList : "बà¥à¤²à¥…ट सूची इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन",
+ShowTableBorders : "टेबल बॉरà¥à¤¡à¤°à¤¯à¥‡à¤‚ दिखायें",
+ShowDetails : "जà¥à¤¯à¤¾à¤¦à¤¾ दिखायें",
+Style : "सà¥à¤Ÿà¤¾à¤‡à¤²",
+FontFormat : "फ़ॉरà¥à¤®à¥ˆà¤Ÿ",
+Font : "फ़ॉनà¥à¤Ÿ",
+FontSize : "साइज़",
+TextColor : "टेकà¥à¤¸à¥à¤Ÿ रंग",
+BGColor : "बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡ रंग",
+Source : "सोरà¥à¤¸",
+Find : "खोजें",
+Replace : "रीपà¥à¤²à¥‡à¤¸",
+SpellCheck : "वरà¥à¤¤à¤¨à¥€ (सà¥à¤ªà¥‡à¤²à¤¿à¤‚ग) जाà¤à¤š",
+UniversalKeyboard : "यूनीवरà¥à¤¸à¤² कीबोरà¥à¤¡",
+PageBreakLbl : "पेज बà¥à¤°à¥‡à¤•",
+PageBreak : "पेज बà¥à¤°à¥‡à¤• इनà¥à¤¸à¤°à¥à¤Ÿà¥ करें",
+
+Form : "फ़ॉरà¥à¤®",
+Checkbox : "चॅक बॉकà¥à¤¸",
+RadioButton : "रेडिओ बटन",
+TextField : "टेकà¥à¤¸à¥à¤Ÿ फ़ीलà¥à¤¡",
+Textarea : "टेकà¥à¤¸à¥à¤Ÿ à¤à¤°à¤¿à¤¯à¤¾",
+HiddenField : "गà¥à¤ªà¥à¤¤ फ़ीलà¥à¤¡",
+Button : "बटन",
+SelectionField : "चà¥à¤¨à¤¾à¤µ फ़ीलà¥à¤¡",
+ImageButton : "तसà¥à¤µà¥€à¤° बटन",
+
+FitWindow : "à¤à¤¡à¤¿à¤Ÿà¤° साइज़ को चरम सीमा तक बà¥à¤¾à¤¯à¥‡à¤‚",
+ShowBlocks : "बà¥à¤²à¥‰à¤• दिखायें",
+
+// Context Menu
+EditLink : "लिंक संपादन",
+CellCM : "खाना",
+RowCM : "पंकà¥à¤¤à¤¿",
+ColumnCM : "कालम",
+InsertRowAfter : "बाद में पंकà¥à¤¤à¤¿ डालें",
+InsertRowBefore : "पहले पंकà¥à¤¤à¤¿ डालें",
+DeleteRows : "पंकà¥à¤¤à¤¿à¤¯à¤¾à¤ डिलीट करें",
+InsertColumnAfter : "बाद में कालम डालें",
+InsertColumnBefore : "पहले कालम डालें",
+DeleteColumns : "कालम डिलीट करें",
+InsertCellAfter : "बाद में सैल डालें",
+InsertCellBefore : "पहले सैल डालें",
+DeleteCells : "सैल डिलीट करें",
+MergeCells : "सैल मिलायें",
+MergeRight : "बाà¤à¤¯à¤¾ विलय",
+MergeDown : "नीचे विलय करें",
+HorizontalSplitCell : "सैल को कà¥à¤·à¥ˆà¤¤à¤¿à¤œ सà¥à¤¥à¤¿à¤¤à¤¿ में विभाजित करें",
+VerticalSplitCell : "सैल को लमà¥à¤¬à¤¾à¤•à¤¾à¤° में विभाजित करें",
+TableDelete : "टेबल डिलीट करें",
+CellProperties : "सैल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+TableProperties : "टेबल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+ImageProperties : "तसà¥à¤µà¥€à¤° पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+FlashProperties : "फ़à¥à¤²à¥ˆà¤¶ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+
+AnchorProp : "à¤à¤‚कर पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+ButtonProp : "बटन पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+CheckboxProp : "चॅक बॉकà¥à¤¸ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+HiddenFieldProp : "गà¥à¤ªà¥à¤¤ फ़ीलà¥à¤¡ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+RadioButtonProp : "रेडिओ बटन पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+ImageButtonProp : "तसà¥à¤µà¥€à¤° बटन पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+TextFieldProp : "टेकà¥à¤¸à¥à¤Ÿ फ़ीलà¥à¤¡ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+SelectionFieldProp : "चà¥à¤¨à¤¾à¤µ फ़ीलà¥à¤¡ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+TextareaProp : "टेकà¥à¤¸à¥à¤¤ à¤à¤°à¤¿à¤¯à¤¾ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+FormProp : "फ़ॉरà¥à¤® पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+
+FontFormats : "साधारण;फ़ॉरà¥à¤®à¥ˆà¤Ÿà¥…ड;पता;शीरà¥à¤·à¤• 1;शीरà¥à¤·à¤• 2;शीरà¥à¤·à¤• 3;शीरà¥à¤·à¤• 4;शीरà¥à¤·à¤• 5;शीरà¥à¤·à¤• 6;शीरà¥à¤·à¤• (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML पà¥à¤°à¥‹à¤¸à¥…स हो रहा है। ज़रा ठहरें...",
+Done : "पूरा हà¥à¤†",
+PasteWordConfirm : "आप जो टेकà¥à¤¸à¥à¤Ÿ पेसà¥à¤Ÿ करना चाहते हैं, वह वरà¥à¤¡ से कॉपी किया हà¥à¤† लग रहा है। कà¥à¤¯à¤¾ पेसà¥à¤Ÿ करने से पहले आप इसे साफ़ करना चाहेंगे?",
+NotCompatiblePaste : "यह कमांड इनà¥à¤Ÿà¤°à¤¨à¥…ट à¤à¤•à¥à¤¸à¥à¤ªà¥à¤²à¥‹à¤°à¤°(Internet Explorer) 5.5 या उसके बाद के वरà¥à¥›à¤¨ के लिठही उपलबà¥à¤§ है। कà¥à¤¯à¤¾ आप बिना साफ़ किठपेसà¥à¤Ÿ करना चाहेंगे?",
+UnknownToolbarItem : "अनजान टूलबार आइटम \"%1\"",
+UnknownCommand : "अनजान कमानà¥à¤¡ \"%1\"",
+NotImplemented : "कमानà¥à¤¡ इमà¥à¤ªà¥à¤²à¥€à¤®à¥…नà¥à¤Ÿ नहीं किया गया है",
+UnknownToolbarSet : "टूलबार सॅट \"%1\" उपलबà¥à¤§ नहीं है",
+NoActiveX : "आपके बà¥à¤°à¤¾à¤‰à¥›à¤°à¥ की सà¥à¤°à¤•à¥à¤¶à¤¾ सेटिंगà¥à¤¸à¥ à¤à¤¡à¤¿à¤Ÿà¤° की कà¥à¤›à¥ फ़ीचरों को सीमित करॠसकती हैं। कà¥à¤°à¤¿à¤ªà¤¯à¤¾ \"Run ActiveX controls and plug-ins\" विकलà¥à¤ª को à¤à¤¨à¥‡à¤¬à¤² करें. आपको à¤à¤°à¤°à¥à¤¸à¥ और गायब फ़ीचरà¥à¤¸à¥ का अनà¥à¤­à¤µ हो सकता है।",
+BrowseServerBlocked : "रिसोरà¥à¤¸à¥‡à¥› बà¥à¤°à¤¾à¤‰à¥›à¤°à¥ नहीं खोला जा सका। कà¥à¤°à¤¿à¤ªà¤¯à¤¾ सभी पॉपà¥-अपॠबà¥à¤²à¥‰à¤•à¤°à¥à¤¸à¥ को निषà¥à¤•à¥à¤°à¤¿à¤¯ करें।",
+DialogBlocked : "डायलग विनà¥à¤¡à¥‹ नहीं खोला जा सका। कà¥à¤°à¤¿à¤ªà¤¯à¤¾ सभी पॉपà¥-अपॠबà¥à¤²à¥‰à¤•à¤°à¥à¤¸à¥ को निषà¥à¤•à¥à¤°à¤¿à¤¯ करें।",
+VisitLinkBlocked : "नया विनà¥à¤¡à¥‹ नहीं खोला जा सका। कà¥à¤°à¤¿à¤ªà¤¯à¤¾ सभी पॉपà¥-अपॠबà¥à¤²à¥‰à¤•à¤°à¥à¤¸à¥ को निषà¥à¤•à¥à¤°à¤¿à¤¯ करें।",
+
+// Dialogs
+DlgBtnOK : "ठीक है",
+DlgBtnCancel : "रदà¥à¤¦ करें",
+DlgBtnClose : "बनà¥à¤¦ करें",
+DlgBtnBrowseServer : "सरà¥à¤µà¤° बà¥à¤°à¤¾à¤‰à¥› करें",
+DlgAdvancedTag : "à¤à¤¡à¥à¤µà¤¾à¤¨à¥à¤¸à¥à¤¡",
+DlgOpOther : "<अनà¥à¤¯>",
+DlgInfoTab : "सूचना",
+DlgAlertUrl : "URL इनà¥à¤¸à¤°à¥à¤Ÿ करें",
+
+// General Dialogs Labels
+DlgGenNotSet : "<सॅट नहीं>",
+DlgGenId : "Id",
+DlgGenLangDir : "भाषा लिखने की दिशा",
+DlgGenLangDirLtr : "बायें से दायें (LTR)",
+DlgGenLangDirRtl : "दायें से बायें (RTL)",
+DlgGenLangCode : "भाषा कोड",
+DlgGenAccessKey : "à¤à¤•à¥à¤¸à¥…स की",
+DlgGenName : "नाम",
+DlgGenTabIndex : "टैब इनà¥à¤¡à¥…कà¥à¤¸",
+DlgGenLongDescr : "अधिक विवरण के लिठURL",
+DlgGenClass : "सà¥à¤Ÿà¤¾à¤‡à¤²-शीट कà¥à¤²à¤¾à¤¸",
+DlgGenTitle : "परामरà¥à¤¶ शीरà¥à¤¶à¤•",
+DlgGenContType : "परामरà¥à¤¶ कनà¥à¤Ÿà¥…नà¥à¤Ÿ पà¥à¤°à¤•à¤¾à¤°",
+DlgGenLinkCharset : "लिंक रिसोरà¥à¤¸ करॅकà¥à¤Ÿà¤° सॅट",
+DlgGenStyle : "सà¥à¤Ÿà¤¾à¤‡à¤²",
+
+// Image Dialog
+DlgImgTitle : "तसà¥à¤µà¥€à¤° पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgImgInfoTab : "तसà¥à¤µà¥€à¤° की जानकारी",
+DlgImgBtnUpload : "इसे सरà¥à¤µà¤° को भेजें",
+DlgImgURL : "URL",
+DlgImgUpload : "अपलोड",
+DlgImgAlt : "वैकलà¥à¤ªà¤¿à¤• टेकà¥à¤¸à¥à¤Ÿ",
+DlgImgWidth : "चौड़ाई",
+DlgImgHeight : "ऊà¤à¤šà¤¾à¤ˆ",
+DlgImgLockRatio : "लॉक अनà¥à¤ªà¤¾à¤¤",
+DlgBtnResetSize : "रीसॅट साइज़",
+DlgImgBorder : "बॉरà¥à¤¡à¤°",
+DlgImgHSpace : "हॉरिज़ॉनà¥à¤Ÿà¤² सà¥à¤ªà¥‡à¤¸",
+DlgImgVSpace : "वरà¥à¤Ÿà¤¿à¤•à¤² सà¥à¤ªà¥‡à¤¸",
+DlgImgAlign : "à¤à¤²à¤¾à¤‡à¤¨",
+DlgImgAlignLeft : "दायें",
+DlgImgAlignAbsBottom: "Abs नीचे",
+DlgImgAlignAbsMiddle: "Abs ऊपर",
+DlgImgAlignBaseline : "मूल रेखा",
+DlgImgAlignBottom : "नीचे",
+DlgImgAlignMiddle : "मधà¥à¤¯",
+DlgImgAlignRight : "दायें",
+DlgImgAlignTextTop : "टेकà¥à¤¸à¥à¤Ÿ ऊपर",
+DlgImgAlignTop : "ऊपर",
+DlgImgPreview : "पà¥à¤°à¥€à¤µà¥à¤¯à¥‚",
+DlgImgAlertUrl : "तसà¥à¤µà¥€à¤° का URL टाइप करें ",
+DlgImgLinkTab : "लिंक",
+
+// Flash Dialog
+DlgFlashTitle : "फ़à¥à¤²à¥ˆà¤¶ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgFlashChkPlay : "ऑटो पà¥à¤²à¥‡",
+DlgFlashChkLoop : "लूप",
+DlgFlashChkMenu : "फ़à¥à¤²à¥ˆà¤¶ मॅनà¥à¤¯à¥‚ का पà¥à¤°à¤¯à¥‹à¤— करें",
+DlgFlashScale : "सà¥à¤•à¥‡à¤²",
+DlgFlashScaleAll : "सभी दिखायें",
+DlgFlashScaleNoBorder : "कोई बॉरà¥à¤¡à¤° नहीं",
+DlgFlashScaleFit : "बिलà¥à¤•à¥à¤² फ़िट",
+
+// Link Dialog
+DlgLnkWindowTitle : "लिंक",
+DlgLnkInfoTab : "लिंक ",
+DlgLnkTargetTab : "टारà¥à¤—ेट",
+
+DlgLnkType : "लिंक पà¥à¤°à¤•à¤¾à¤°",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "इस पेज का à¤à¤‚कर",
+DlgLnkTypeEMail : "ई-मेल",
+DlgLnkProto : "पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤²",
+DlgLnkProtoOther : "<अनà¥à¤¯>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "à¤à¤‚कर चà¥à¤¨à¥‡à¤‚",
+DlgLnkAnchorByName : "à¤à¤‚कर नाम से",
+DlgLnkAnchorById : "à¤à¤²à¥€à¤®à¥…नà¥à¤Ÿ Id से",
+DlgLnkNoAnchors : "(डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ में à¤à¤‚करà¥à¤¸ की संखà¥à¤¯à¤¾)",
+DlgLnkEMail : "ई-मेल पता",
+DlgLnkEMailSubject : "संदेश विषय",
+DlgLnkEMailBody : "संदेश",
+DlgLnkUpload : "अपलोड",
+DlgLnkBtnUpload : "इसे सरà¥à¤µà¤° को भेजें",
+
+DlgLnkTarget : "टारà¥à¤—ेट",
+DlgLnkTargetFrame : "<फ़à¥à¤°à¥‡à¤®>",
+DlgLnkTargetPopup : "<पॉप-अप विनà¥à¤¡à¥‹>",
+DlgLnkTargetBlank : "नया विनà¥à¤¡à¥‹ (_blank)",
+DlgLnkTargetParent : "मूल विनà¥à¤¡à¥‹ (_parent)",
+DlgLnkTargetSelf : "इसी विनà¥à¤¡à¥‹ (_self)",
+DlgLnkTargetTop : "शीरà¥à¤· विनà¥à¤¡à¥‹ (_top)",
+DlgLnkTargetFrameName : "टारà¥à¤—ेट फ़à¥à¤°à¥‡à¤® का नाम",
+DlgLnkPopWinName : "पॉप-अप विनà¥à¤¡à¥‹ का नाम",
+DlgLnkPopWinFeat : "पॉप-अप विनà¥à¤¡à¥‹ फ़ीचरà¥à¤¸",
+DlgLnkPopResize : "साइज़ बदला जा सकता है",
+DlgLnkPopLocation : "लोकेशन बार",
+DlgLnkPopMenu : "मॅनà¥à¤¯à¥‚ बार",
+DlgLnkPopScroll : "सà¥à¤•à¥à¤°à¥‰à¤² बार",
+DlgLnkPopStatus : "सà¥à¤Ÿà¥‡à¤Ÿà¤¸ बार",
+DlgLnkPopToolbar : "टूल बार",
+DlgLnkPopFullScrn : "फ़à¥à¤² सà¥à¤•à¥à¤°à¥€à¤¨ (IE)",
+DlgLnkPopDependent : "डिपेनà¥à¤¡à¥…नà¥à¤Ÿ (Netscape)",
+DlgLnkPopWidth : "चौड़ाई",
+DlgLnkPopHeight : "ऊà¤à¤šà¤¾à¤ˆ",
+DlgLnkPopLeft : "बायीं तरफ",
+DlgLnkPopTop : "दायीं तरफ",
+
+DlnLnkMsgNoUrl : "लिंक URL टाइप करें",
+DlnLnkMsgNoEMail : "ई-मेल पता टाइप करें",
+DlnLnkMsgNoAnchor : "à¤à¤‚कर चà¥à¤¨à¥‡à¤‚",
+DlnLnkMsgInvPopName : "पॉप-अप का नाम अलà¥à¤«à¤¾à¤¬à¥‡à¤Ÿ से शà¥à¤°à¥‚ होना चाहिये और उसमें सà¥à¤ªà¥‡à¤¸ नहीं होने चाहिà¤",
+
+// Color Dialog
+DlgColorTitle : "रंग चà¥à¤¨à¥‡à¤‚",
+DlgColorBtnClear : "साफ़ करें",
+DlgColorHighlight : "हाइलाइट",
+DlgColorSelected : "सॅलॅकà¥à¤Ÿà¥…ड",
+
+// Smiley Dialog
+DlgSmileyTitle : "सà¥à¤®à¤¾à¤‡à¤²à¥€ इनà¥à¤¸à¤°à¥à¤Ÿ करें",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "विशेष करॅकà¥à¤Ÿà¤° चà¥à¤¨à¥‡à¤‚",
+
+// Table Dialog
+DlgTableTitle : "टेबल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgTableRows : "पंकà¥à¤¤à¤¿à¤¯à¤¾à¤",
+DlgTableColumns : "कालम",
+DlgTableBorder : "बॉरà¥à¤¡à¤° साइज़",
+DlgTableAlign : "à¤à¤²à¤¾à¤‡à¤¨à¥à¤®à¥…नà¥à¤Ÿ",
+DlgTableAlignNotSet : "<सॅट नहीं>",
+DlgTableAlignLeft : "दायें",
+DlgTableAlignCenter : "बीच में",
+DlgTableAlignRight : "बायें",
+DlgTableWidth : "चौड़ाई",
+DlgTableWidthPx : "पिकà¥à¤¸à¥ˆà¤²",
+DlgTableWidthPc : "पà¥à¤°à¤¤à¤¿à¤¶à¤¤",
+DlgTableHeight : "ऊà¤à¤šà¤¾à¤ˆ",
+DlgTableCellSpace : "सैल अंतर",
+DlgTableCellPad : "सैल पैडिंग",
+DlgTableCaption : "शीरà¥à¤·à¤•",
+DlgTableSummary : "सारांश",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "सैल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgCellWidth : "चौड़ाई",
+DlgCellWidthPx : "पिकà¥à¤¸à¥ˆà¤²",
+DlgCellWidthPc : "पà¥à¤°à¤¤à¤¿à¤¶à¤¤",
+DlgCellHeight : "ऊà¤à¤šà¤¾à¤ˆ",
+DlgCellWordWrap : "वरà¥à¤¡ रैप",
+DlgCellWordWrapNotSet : "<सॅट नहीं>",
+DlgCellWordWrapYes : "हाà¤",
+DlgCellWordWrapNo : "नहीं",
+DlgCellHorAlign : "हॉरिज़ॉनà¥à¤Ÿà¤² à¤à¤²à¤¾à¤‡à¤¨à¥à¤®à¥…नà¥à¤Ÿ",
+DlgCellHorAlignNotSet : "<सॅट नहीं>",
+DlgCellHorAlignLeft : "दायें",
+DlgCellHorAlignCenter : "बीच में",
+DlgCellHorAlignRight: "बायें",
+DlgCellVerAlign : "वरà¥à¤Ÿà¤¿à¤•à¤² à¤à¤²à¤¾à¤‡à¤¨à¥à¤®à¥…नà¥à¤Ÿ",
+DlgCellVerAlignNotSet : "<सॅट नहीं>",
+DlgCellVerAlignTop : "ऊपर",
+DlgCellVerAlignMiddle : "मधà¥à¤¯",
+DlgCellVerAlignBottom : "नीचे",
+DlgCellVerAlignBaseline : "मूलरेखा",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "पंकà¥à¤¤à¤¿ सà¥à¤ªà¥ˆà¤¨",
+DlgCellCollSpan : "कालम सà¥à¤ªà¥ˆà¤¨",
+DlgCellBackColor : "बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡ रंग",
+DlgCellBorderColor : "बॉरà¥à¤¡à¤° का रंग",
+DlgCellBtnSelect : "चà¥à¤¨à¥‡à¤‚...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "खोजें और बदलें",
+
+// Find Dialog
+DlgFindTitle : "खोजें",
+DlgFindFindBtn : "खोजें",
+DlgFindNotFoundMsg : "आपके दà¥à¤µà¤¾à¤°à¤¾ दिया गया टेकà¥à¤¸à¥à¤Ÿ नहीं मिला",
+
+// Replace Dialog
+DlgReplaceTitle : "रिपà¥à¤²à¥‡à¤¸",
+DlgReplaceFindLbl : "यह खोजें:",
+DlgReplaceReplaceLbl : "इससे रिपà¥à¤²à¥‡à¤¸ करें:",
+DlgReplaceCaseChk : "केस मिलायें",
+DlgReplaceReplaceBtn : "रिपà¥à¤²à¥‡à¤¸",
+DlgReplaceReplAllBtn : "सभी रिपà¥à¤²à¥‡à¤¸ करें",
+DlgReplaceWordChk : "पूरा शबà¥à¤¦ मिलायें",
+
+// Paste Operations / Dialog
+PasteErrorCut : "आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ सॅटिनà¥à¤—à¥à¤¸ ने कट करने की अनà¥à¤®à¤¤à¤¿ नहीं पà¥à¤°à¤¦à¤¾à¤¨ की है। (Ctrl+X) का पà¥à¤°à¤¯à¥‹à¤— करें।",
+PasteErrorCopy : "आपके बà¥à¤°à¤¾à¤†à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ सॅटिनà¥à¤—à¥à¤¸ ने कॉपी करने की अनà¥à¤®à¤¤à¤¿ नहीं पà¥à¤°à¤¦à¤¾à¤¨ की है। (Ctrl+C) का पà¥à¤°à¤¯à¥‹à¤— करें।",
+
+PasteAsText : "पेसà¥à¤Ÿ (सादा टॅकà¥à¤¸à¥à¤Ÿ)",
+PasteFromWord : "पेसà¥à¤Ÿ (वरà¥à¤¡ से)",
+
+DlgPasteMsg2 : "Ctrl+V का पà¥à¤°à¤¯à¥‹à¤— करके पेसà¥à¤Ÿ करें और ठीक है करें.",
+DlgPasteSec : "आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°Kश सैटिंग के कारण, à¤à¤¡à¤¿à¤Ÿà¤° आपके कà¥à¤²à¤¿à¤ªà¤¬à¥‹à¤°à¥à¤¡ डेटा को नहीं पा सकता है. आपको उसे इस विनà¥à¤¡à¥‹ में दोबारा पेसà¥à¤Ÿ करना होगा.",
+DlgPasteIgnoreFont : "फ़ॉनà¥à¤Ÿ परिभाषा निकालें",
+DlgPasteRemoveStyles : "सà¥à¤Ÿà¤¾à¤‡à¤² परिभाषा निकालें",
+
+// Color Picker
+ColorAutomatic : "सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤",
+ColorMoreColors : "और रंग...",
+
+// Document Properties
+DocProps : "डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+
+// Anchor Dialog
+DlgAnchorTitle : "à¤à¤‚कर पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgAnchorName : "à¤à¤‚कर का नाम",
+DlgAnchorErrorName : "à¤à¤‚कर का नाम टाइप करें",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "शबà¥à¤¦à¤•à¥‹à¤¶ में नहीं",
+DlgSpellChangeTo : "इसमें बदलें",
+DlgSpellBtnIgnore : "इगà¥à¤¨à¥‹à¤°",
+DlgSpellBtnIgnoreAll : "सभी इगà¥à¤¨à¥‹à¤° करें",
+DlgSpellBtnReplace : "रिपà¥à¤²à¥‡à¤¸",
+DlgSpellBtnReplaceAll : "सभी रिपà¥à¤²à¥‡à¤¸ करें",
+DlgSpellBtnUndo : "अनà¥à¤¡à¥‚",
+DlgSpellNoSuggestions : "- कोई सà¥à¤à¤¾à¤µ नहीं -",
+DlgSpellProgress : "वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š (सà¥à¤ªà¥…ल-चॅक) जारी है...",
+DlgSpellNoMispell : "वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : कोई गलत वरà¥à¤¤à¤¨à¥€ (सà¥à¤ªà¥…लिंग) नहीं पाई गई",
+DlgSpellNoChanges : "वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š :कोई शबà¥à¤¦ नहीं बदला गया",
+DlgSpellOneChange : "वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : à¤à¤• शबà¥à¤¦ बदला गया",
+DlgSpellManyChanges : "वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : %1 शबà¥à¤¦ बदले गये",
+
+IeSpellDownload : "सà¥à¤ªà¥…ल-चॅकर इनà¥à¤¸à¥à¤Ÿà¤¾à¤² नहीं किया गया है। कà¥à¤¯à¤¾ आप इसे डा‌उनलोड करना चाहेंगे?",
+
+// Button Dialog
+DlgButtonText : "टेकà¥à¤¸à¥à¤Ÿ (वैलà¥à¤¯à¥‚)",
+DlgButtonType : "पà¥à¤°à¤•à¤¾à¤°",
+DlgButtonTypeBtn : "बटन",
+DlgButtonTypeSbm : "सबà¥à¤®à¤¿à¤Ÿ",
+DlgButtonTypeRst : "रिसेट",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "नाम",
+DlgCheckboxValue : "वैलà¥à¤¯à¥‚",
+DlgCheckboxSelected : "सॅलॅकà¥à¤Ÿà¥…ड",
+
+// Form Dialog
+DlgFormName : "नाम",
+DlgFormAction : "कà¥à¤°à¤¿à¤¯à¤¾",
+DlgFormMethod : "तरीका",
+
+// Select Field Dialog
+DlgSelectName : "नाम",
+DlgSelectValue : "वैलà¥à¤¯à¥‚",
+DlgSelectSize : "साइज़",
+DlgSelectLines : "पंकà¥à¤¤à¤¿à¤¯à¤¾à¤",
+DlgSelectChkMulti : "à¤à¤• से जà¥à¤¯à¤¾à¤¦à¤¾ विकलà¥à¤ª चà¥à¤¨à¤¨à¥‡ दें",
+DlgSelectOpAvail : "उपलबà¥à¤§ विकलà¥à¤ª",
+DlgSelectOpText : "टेकà¥à¤¸à¥à¤Ÿ",
+DlgSelectOpValue : "वैलà¥à¤¯à¥‚",
+DlgSelectBtnAdd : "जोड़ें",
+DlgSelectBtnModify : "बदलें",
+DlgSelectBtnUp : "ऊपर",
+DlgSelectBtnDown : "नीचे",
+DlgSelectBtnSetValue : "चà¥à¤¨à¥€ गई वैलà¥à¤¯à¥‚ सॅट करें",
+DlgSelectBtnDelete : "डिलीट",
+
+// Textarea Dialog
+DlgTextareaName : "नाम",
+DlgTextareaCols : "कालम",
+DlgTextareaRows : "पंकà¥à¤¤à¤¿à¤¯à¤¾à¤‚",
+
+// Text Field Dialog
+DlgTextName : "नाम",
+DlgTextValue : "वैलà¥à¤¯à¥‚",
+DlgTextCharWidth : "करॅकà¥à¤Ÿà¤° की चौà¥à¤¾à¤ˆ",
+DlgTextMaxChars : "अधिकतम करॅकà¥à¤Ÿà¤°",
+DlgTextType : "टाइप",
+DlgTextTypeText : "टेकà¥à¤¸à¥à¤Ÿ",
+DlgTextTypePass : "पासà¥à¤µà¤°à¥à¤¡",
+
+// Hidden Field Dialog
+DlgHiddenName : "नाम",
+DlgHiddenValue : "वैलà¥à¤¯à¥‚",
+
+// Bulleted List Dialog
+BulletedListProp : "बà¥à¤²à¥…ट सूची पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+NumberedListProp : "अंकीय सूची पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›",
+DlgLstStart : "पà¥à¤°à¤¾à¤°à¤®à¥à¤­",
+DlgLstType : "पà¥à¤°à¤•à¤¾à¤°",
+DlgLstTypeCircle : "गोल",
+DlgLstTypeDisc : "डिसà¥à¤•",
+DlgLstTypeSquare : "चौकॊण",
+DlgLstTypeNumbers : "अंक (1, 2, 3)",
+DlgLstTypeLCase : "छोटे अकà¥à¤·à¤° (a, b, c)",
+DlgLstTypeUCase : "बड़े अकà¥à¤·à¤° (A, B, C)",
+DlgLstTypeSRoman : "छोटे रोमन अंक (i, ii, iii)",
+DlgLstTypeLRoman : "बड़े रोमन अंक (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "आम",
+DlgDocBackTab : "बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡",
+DlgDocColorsTab : "रंग और मारà¥à¤œà¤¿à¤¨",
+DlgDocMetaTab : "मॅटाडेटा",
+
+DlgDocPageTitle : "पेज शीरà¥à¤·à¤•",
+DlgDocLangDir : "भाषा लिखने की दिशा",
+DlgDocLangDirLTR : "बायें से दायें (LTR)",
+DlgDocLangDirRTL : "दायें से बायें (RTL)",
+DlgDocLangCode : "भाषा कोड",
+DlgDocCharSet : "करेकà¥à¤Ÿà¤° सॅट à¤à¤¨à¥à¤•à¥‹à¤¡à¤¿à¤‚ग",
+DlgDocCharSetCE : "मधà¥à¤¯ यूरोपीय (Central European)",
+DlgDocCharSetCT : "चीनी (Chinese Traditional Big5)",
+DlgDocCharSetCR : "सिरीलिक (Cyrillic)",
+DlgDocCharSetGR : "यवन (Greek)",
+DlgDocCharSetJP : "जापानी (Japanese)",
+DlgDocCharSetKR : "कोरीयन (Korean)",
+DlgDocCharSetTR : "तà¥à¤°à¥à¤•à¥€ (Turkish)",
+DlgDocCharSetUN : "यूनीकोड (UTF-8)",
+DlgDocCharSetWE : "पशà¥à¤šà¤¿à¤® यूरोपीय (Western European)",
+DlgDocCharSetOther : "अनà¥à¤¯ करेकà¥à¤Ÿà¤° सॅट à¤à¤¨à¥à¤•à¥‹à¤¡à¤¿à¤‚ग",
+
+DlgDocDocType : "डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ पà¥à¤°à¤•à¤¾à¤° शीरà¥à¤·à¤•",
+DlgDocDocTypeOther : "अनà¥à¤¯ डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ पà¥à¤°à¤•à¤¾à¤° शीरà¥à¤·à¤•",
+DlgDocIncXHTML : "XHTML सूचना समà¥à¤®à¤¿à¤²à¤¿à¤¤ करें",
+DlgDocBgColor : "बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡ रंग",
+DlgDocBgImage : "बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡ तसà¥à¤µà¥€à¤° URL",
+DlgDocBgNoScroll : "सà¥à¤•à¥à¤°à¥‰à¤² न करने वाला बैकà¥à¤—à¥à¤°à¤¾à¤‰à¤¨à¥à¤¡",
+DlgDocCText : "टेकà¥à¤¸à¥à¤Ÿ",
+DlgDocCLink : "लिंक",
+DlgDocCVisited : "विज़िट किया गया लिंक",
+DlgDocCActive : "सकà¥à¤°à¤¿à¤¯ लिंक",
+DlgDocMargins : "पेज मारà¥à¤œà¤¿à¤¨",
+DlgDocMaTop : "ऊपर",
+DlgDocMaLeft : "बायें",
+DlgDocMaRight : "दायें",
+DlgDocMaBottom : "नीचे",
+DlgDocMeIndex : "डॉकà¥à¤¯à¥à¤®à¥…नà¥à¤Ÿ इनà¥à¤¡à¥‡à¤•à¥à¤¸ संकेतशबà¥à¤¦ (अलà¥à¤ªà¤µà¤¿à¤°à¤¾à¤® से अलग करें)",
+DlgDocMeDescr : "डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ करॅकà¥à¤Ÿà¤°à¤¨",
+DlgDocMeAuthor : "लेखक",
+DlgDocMeCopy : "कॉपीराइट",
+DlgDocPreview : "पà¥à¤°à¥€à¤µà¥à¤¯à¥‚",
+
+// Templates Dialog
+Templates : "टॅमà¥à¤ªà¥à¤²à¥‡à¤Ÿ",
+DlgTemplatesTitle : "कनà¥à¤Ÿà¥‡à¤¨à¥à¤Ÿ टॅमà¥à¤ªà¥à¤²à¥‡à¤Ÿ",
+DlgTemplatesSelMsg : "à¤à¤¡à¤¿à¤Ÿà¤° में ओपन करने हेतॠटॅमà¥à¤ªà¥à¤²à¥‡à¤Ÿ चà¥à¤¨à¥‡à¤‚(वरà¥à¤¤à¤®à¤¾à¤¨ कनà¥à¤Ÿà¥…नà¥à¤Ÿ सेव नहीं होंगे):",
+DlgTemplatesLoading : "टॅमà¥à¤ªà¥à¤²à¥‡à¤Ÿ सूची लोड की जा रही है। ज़रा ठहरें...",
+DlgTemplatesNoTpl : "(कोई टॅमà¥à¤ªà¥à¤²à¥‡à¤Ÿ डिफ़ाइन नहीं किया गया है)",
+DlgTemplatesReplace : "मूल शबà¥à¤¦à¥‹à¤‚ को बदलें",
+
+// About Dialog
+DlgAboutAboutTab : "FCKEditor के बारे में",
+DlgAboutBrowserInfoTab : "बà¥à¤°à¤¾à¤‰à¥›à¤° के बारे में",
+DlgAboutLicenseTab : "लाइसैनà¥à¤¸",
+DlgAboutVersion : "वरà¥à¥›à¤¨",
+DlgAboutInfo : "अधिक जानकारी के लिये यहाठजायें:",
+
+// Div Dialog
+DlgDivGeneralTab : "सामानà¥à¤¯",
+DlgDivAdvancedTab : "à¤à¤¡à¥à¤µà¤¾à¤¨à¥à¤¸à¥à¤¡",
+DlgDivStyle : "सà¥à¤Ÿà¤¾à¤‡à¤²",
+DlgDivInlineStyle : "इनलाइन सà¥à¤Ÿà¤¾à¤‡à¤²",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/hr.js b/httemplate/elements/fckeditor/editor/lang/hr.js
new file mode 100644
index 0000000..3a92096
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/hr.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Croatian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Smanji trake s alatima",
+ToolbarExpand : "Proširi trake s alatima",
+
+// Toolbar Items and Context Menu
+Save : "Snimi",
+NewPage : "Nova stranica",
+Preview : "Pregledaj",
+Cut : "Izreži",
+Copy : "Kopiraj",
+Paste : "Zalijepi",
+PasteText : "Zalijepi kao Äisti tekst",
+PasteWord : "Zalijepi iz Worda",
+Print : "Ispiši",
+SelectAll : "Odaberi sve",
+RemoveFormat : "Ukloni formatiranje",
+InsertLinkLbl : "Link",
+InsertLink : "Ubaci/promijeni link",
+RemoveLink : "Ukloni link",
+VisitLink : "Otvori link",
+Anchor : "Ubaci/promijeni sidro",
+AnchorDelete : "Ukloni sidro",
+InsertImageLbl : "Slika",
+InsertImage : "Ubaci/promijeni sliku",
+InsertFlashLbl : "Flash",
+InsertFlash : "Ubaci/promijeni Flash",
+InsertTableLbl : "Tablica",
+InsertTable : "Ubaci/promijeni tablicu",
+InsertLineLbl : "Linija",
+InsertLine : "Ubaci vodoravnu liniju",
+InsertSpecialCharLbl: "Posebni karakteri",
+InsertSpecialChar : "Ubaci posebne znakove",
+InsertSmileyLbl : "Smješko",
+InsertSmiley : "Ubaci smješka",
+About : "O FCKeditoru",
+Bold : "Podebljaj",
+Italic : "Ukosi",
+Underline : "Potcrtano",
+StrikeThrough : "Precrtano",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Lijevo poravnanje",
+CenterJustify : "Središnje poravnanje",
+RightJustify : "Desno poravnanje",
+BlockJustify : "Blok poravnanje",
+DecreaseIndent : "Pomakni ulijevo",
+IncreaseIndent : "Pomakni udesno",
+Blockquote : "Blockquote",
+CreateDiv : "Napravi Div kontejner",
+EditDiv : "Uredi Div kontejner",
+DeleteDiv : "Ukloni Div kontejner",
+Undo : "Poništi",
+Redo : "Ponovi",
+NumberedListLbl : "BrojÄana lista",
+NumberedList : "Ubaci/ukloni brojÄanu listu",
+BulletedListLbl : "ObiÄna lista",
+BulletedList : "Ubaci/ukloni obiÄnu listu",
+ShowTableBorders : "Prikaži okvir tablice",
+ShowDetails : "Prikaži detalje",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "VeliÄina",
+TextColor : "Boja teksta",
+BGColor : "Boja pozadine",
+Source : "Kôd",
+Find : "Pronađi",
+Replace : "Zamijeni",
+SpellCheck : "Provjeri pravopis",
+UniversalKeyboard : "Univerzalna tipkovnica",
+PageBreakLbl : "Prijelom stranice",
+PageBreak : "Ubaci prijelom stranice",
+
+Form : "Form",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Hidden Field",
+Button : "Button",
+SelectionField : "Selection Field",
+ImageButton : "Image Button",
+
+FitWindow : "Povećaj veliÄinu editora",
+ShowBlocks : "Prikaži blokove",
+
+// Context Menu
+EditLink : "Promijeni link",
+CellCM : "Ćelija",
+RowCM : "Red",
+ColumnCM : "Kolona",
+InsertRowAfter : "Ubaci red poslije",
+InsertRowBefore : "Ubaci red prije",
+DeleteRows : "Izbriši redove",
+InsertColumnAfter : "Ubaci kolonu poslije",
+InsertColumnBefore : "Ubaci kolonu prije",
+DeleteColumns : "Izbriši kolone",
+InsertCellAfter : "Ubaci ćeliju poslije",
+InsertCellBefore : "Ubaci ćeliju prije",
+DeleteCells : "Izbriši ćelije",
+MergeCells : "Spoji ćelije",
+MergeRight : "Spoji desno",
+MergeDown : "Spoji dolje",
+HorizontalSplitCell : "Podijeli ćeliju vodoravno",
+VerticalSplitCell : "Podijeli ćeliju okomito",
+TableDelete : "Izbriši tablicu",
+CellProperties : "Svojstva ćelije",
+TableProperties : "Svojstva tablice",
+ImageProperties : "Svojstva slike",
+FlashProperties : "Flash svojstva",
+
+AnchorProp : "Svojstva sidra",
+ButtonProp : "Image Button svojstva",
+CheckboxProp : "Checkbox svojstva",
+HiddenFieldProp : "Hidden Field svojstva",
+RadioButtonProp : "Radio Button svojstva",
+ImageButtonProp : "Image Button svojstva",
+TextFieldProp : "Text Field svojstva",
+SelectionFieldProp : "Selection svojstva",
+TextareaProp : "Textarea svojstva",
+FormProp : "Form svojstva",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "ObraÄ‘ujem XHTML. Molimo priÄekajte...",
+Done : "Završio",
+PasteWordConfirm : "Tekst koji želite zalijepiti Äini se da je kopiran iz Worda. Želite li prije oÄistiti tekst?",
+NotCompatiblePaste : "Ova naredba je dostupna samo u Internet Exploreru 5.5 ili novijem. Želite li nastaviti bez Äišćenja?",
+UnknownToolbarItem : "Nepoznati Älan trake s alatima \"%1\"",
+UnknownCommand : "Nepoznata naredba \"%1\"",
+NotImplemented : "Naredba nije implementirana",
+UnknownToolbarSet : "Traka s alatima \"%1\" ne postoji",
+NoActiveX : "VaÅ¡e postavke pretraživaÄa mogle bi ograniÄiti neke od mogućnosti editora. Morate ukljuÄiti opciju \"Run ActiveX controls and plug-ins\" u postavkama. Ukoliko to ne uÄinite, moguće su razliite greÅ¡ke tijekom rada.",
+BrowseServerBlocked : "PretraivaÄ nije moguće otvoriti. Provjerite da li je ukljuÄeno blokiranje pop-up prozora.",
+DialogBlocked : "Nije moguće otvoriti novi prozor. Provjerite da li je ukljuÄeno blokiranje pop-up prozora.",
+VisitLinkBlocked : "Nije moguće otvoriti novi prozor. Provjerite da li je ukljuÄeno blokiranje pop-up prozora.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Poništi",
+DlgBtnClose : "Zatvori",
+DlgBtnBrowseServer : "Pretraži server",
+DlgAdvancedTag : "Napredno",
+DlgOpOther : "<Drugo>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Molimo unesite URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nije postavljeno>",
+DlgGenId : "Id",
+DlgGenLangDir : "Smjer jezika",
+DlgGenLangDirLtr : "S lijeva na desno (LTR)",
+DlgGenLangDirRtl : "S desna na lijevo (RTL)",
+DlgGenLangCode : "Kôd jezika",
+DlgGenAccessKey : "Pristupna tipka",
+DlgGenName : "Naziv",
+DlgGenTabIndex : "Tab Indeks",
+DlgGenLongDescr : "DugaÄki opis URL",
+DlgGenClass : "Stylesheet klase",
+DlgGenTitle : "Advisory naslov",
+DlgGenContType : "Advisory vrsta sadržaja",
+DlgGenLinkCharset : "Kodna stranica povezanih resursa",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Svojstva slika",
+DlgImgInfoTab : "Info slike",
+DlgImgBtnUpload : "Pošalji na server",
+DlgImgURL : "URL",
+DlgImgUpload : "Pošalji",
+DlgImgAlt : "Alternativni tekst",
+DlgImgWidth : "Å irina",
+DlgImgHeight : "Visina",
+DlgImgLockRatio : "ZakljuÄaj odnos",
+DlgBtnResetSize : "ObriÅ¡i veliÄinu",
+DlgImgBorder : "Okvir",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Poravnaj",
+DlgImgAlignLeft : "Lijevo",
+DlgImgAlignAbsBottom: "Abs dolje",
+DlgImgAlignAbsMiddle: "Abs sredina",
+DlgImgAlignBaseline : "Bazno",
+DlgImgAlignBottom : "Dolje",
+DlgImgAlignMiddle : "Sredina",
+DlgImgAlignRight : "Desno",
+DlgImgAlignTextTop : "Vrh teksta",
+DlgImgAlignTop : "Vrh",
+DlgImgPreview : "Pregledaj",
+DlgImgAlertUrl : "Unesite URL slike",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Flash svojstva",
+DlgFlashChkPlay : "Auto Play",
+DlgFlashChkLoop : "Ponavljaj",
+DlgFlashChkMenu : "Omogući Flash izbornik",
+DlgFlashScale : "Omjer",
+DlgFlashScaleAll : "Prikaži sve",
+DlgFlashScaleNoBorder : "Bez okvira",
+DlgFlashScaleFit : "ToÄna veliÄina",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Meta",
+
+DlgLnkType : "Link vrsta",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Sidro na ovoj stranici",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<drugo>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Odaberi sidro",
+DlgLnkAnchorByName : "Po nazivu sidra",
+DlgLnkAnchorById : "Po Id elementa",
+DlgLnkNoAnchors : "(Nema dostupnih sidra)",
+DlgLnkEMail : "E-Mail adresa",
+DlgLnkEMailSubject : "Naslov",
+DlgLnkEMailBody : "Sadržaj poruke",
+DlgLnkUpload : "Pošalji",
+DlgLnkBtnUpload : "Pošalji na server",
+
+DlgLnkTarget : "Meta",
+DlgLnkTargetFrame : "<okvir>",
+DlgLnkTargetPopup : "<popup prozor>",
+DlgLnkTargetBlank : "Novi prozor (_blank)",
+DlgLnkTargetParent : "Roditeljski prozor (_parent)",
+DlgLnkTargetSelf : "Isti prozor (_self)",
+DlgLnkTargetTop : "Vršni prozor (_top)",
+DlgLnkTargetFrameName : "Ime ciljnog okvira",
+DlgLnkPopWinName : "Naziv popup prozora",
+DlgLnkPopWinFeat : "Mogućnosti popup prozora",
+DlgLnkPopResize : "Promjenljive veliÄine",
+DlgLnkPopLocation : "Traka za lokaciju",
+DlgLnkPopMenu : "Izborna traka",
+DlgLnkPopScroll : "Scroll traka",
+DlgLnkPopStatus : "Statusna traka",
+DlgLnkPopToolbar : "Traka s alatima",
+DlgLnkPopFullScrn : "Cijeli ekran (IE)",
+DlgLnkPopDependent : "Ovisno (Netscape)",
+DlgLnkPopWidth : "Å irina",
+DlgLnkPopHeight : "Visina",
+DlgLnkPopLeft : "Lijeva pozicija",
+DlgLnkPopTop : "Gornja pozicija",
+
+DlnLnkMsgNoUrl : "Molimo upišite URL link",
+DlnLnkMsgNoEMail : "Molimo upišite e-mail adresu",
+DlnLnkMsgNoAnchor : "Molimo odaberite sidro",
+DlnLnkMsgInvPopName : "Ime popup prozora mora poÄeti sa slovom i ne smije sadržavati razmake",
+
+// Color Dialog
+DlgColorTitle : "Odaberite boju",
+DlgColorBtnClear : "Obriši",
+DlgColorHighlight : "Osvijetli",
+DlgColorSelected : "Odaberi",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ubaci smješka",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Odaberite posebni karakter",
+
+// Table Dialog
+DlgTableTitle : "Svojstva tablice",
+DlgTableRows : "Redova",
+DlgTableColumns : "Kolona",
+DlgTableBorder : "VeliÄina okvira",
+DlgTableAlign : "Poravnanje",
+DlgTableAlignNotSet : "<nije postavljeno>",
+DlgTableAlignLeft : "Lijevo",
+DlgTableAlignCenter : "Središnje",
+DlgTableAlignRight : "Desno",
+DlgTableWidth : "Å irina",
+DlgTableWidthPx : "piksela",
+DlgTableWidthPc : "postotaka",
+DlgTableHeight : "Visina",
+DlgTableCellSpace : "Prostornost ćelija",
+DlgTableCellPad : "Razmak ćelija",
+DlgTableCaption : "Naslov",
+DlgTableSummary : "Sažetak",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Svojstva ćelije",
+DlgCellWidth : "Å irina",
+DlgCellWidthPx : "piksela",
+DlgCellWidthPc : "postotaka",
+DlgCellHeight : "Visina",
+DlgCellWordWrap : "Word Wrap",
+DlgCellWordWrapNotSet : "<nije postavljeno>",
+DlgCellWordWrapYes : "Da",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Vodoravno poravnanje",
+DlgCellHorAlignNotSet : "<nije postavljeno>",
+DlgCellHorAlignLeft : "Lijevo",
+DlgCellHorAlignCenter : "Središnje",
+DlgCellHorAlignRight: "Desno",
+DlgCellVerAlign : "Okomito poravnanje",
+DlgCellVerAlignNotSet : "<nije postavljeno>",
+DlgCellVerAlignTop : "Gornje",
+DlgCellVerAlignMiddle : "Srednišnje",
+DlgCellVerAlignBottom : "Donje",
+DlgCellVerAlignBaseline : "Bazno",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Spajanje redova",
+DlgCellCollSpan : "Spajanje kolona",
+DlgCellBackColor : "Boja pozadine",
+DlgCellBorderColor : "Boja okvira",
+DlgCellBtnSelect : "Odaberi...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Pronađi i zamijeni",
+
+// Find Dialog
+DlgFindTitle : "Pronađi",
+DlgFindFindBtn : "Pronađi",
+DlgFindNotFoundMsg : "Traženi tekst nije pronađen.",
+
+// Replace Dialog
+DlgReplaceTitle : "Zamijeni",
+DlgReplaceFindLbl : "Pronađi:",
+DlgReplaceReplaceLbl : "Zamijeni s:",
+DlgReplaceCaseChk : "Usporedi mala/velika slova",
+DlgReplaceReplaceBtn : "Zamijeni",
+DlgReplaceReplAllBtn : "Zamijeni sve",
+DlgReplaceWordChk : "Usporedi cijele rijeÄi",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Sigurnosne postavke VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog izrezivanja. Molimo koristite kraticu na tipkovnici (Ctrl+X).",
+PasteErrorCopy : "Sigurnosne postavke VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tipkovnici (Ctrl+C).",
+
+PasteAsText : "Zalijepi kao Äisti tekst",
+PasteFromWord : "Zalijepi iz Worda",
+
+DlgPasteMsg2 : "Molimo zaljepite unutar doljnjeg okvira koristeći tipkovnicu (<STRONG>Ctrl+V</STRONG>) i kliknite <STRONG>OK</STRONG>.",
+DlgPasteSec : "Zbog sigurnosnih postavki VaÅ¡eg pretraživaÄa, editor nema direktan pristup VaÅ¡em meÄ‘uspremniku. Potrebno je ponovno zalijepiti tekst u ovaj prozor.",
+DlgPasteIgnoreFont : "Zanemari definiciju vrste fonta",
+DlgPasteRemoveStyles : "Ukloni definicije stilova",
+
+// Color Picker
+ColorAutomatic : "Automatski",
+ColorMoreColors : "Više boja...",
+
+// Document Properties
+DocProps : "Svojstva dokumenta",
+
+// Anchor Dialog
+DlgAnchorTitle : "Svojstva sidra",
+DlgAnchorName : "Ime sidra",
+DlgAnchorErrorName : "Molimo unesite ime sidra",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nije u rjeÄniku",
+DlgSpellChangeTo : "Promijeni u",
+DlgSpellBtnIgnore : "Zanemari",
+DlgSpellBtnIgnoreAll : "Zanemari sve",
+DlgSpellBtnReplace : "Zamijeni",
+DlgSpellBtnReplaceAll : "Zamijeni sve",
+DlgSpellBtnUndo : "Vrati",
+DlgSpellNoSuggestions : "-Nema preporuke-",
+DlgSpellProgress : "Provjera u tijeku...",
+DlgSpellNoMispell : "Provjera završena: Nema grešaka",
+DlgSpellNoChanges : "Provjera završena: Nije napravljena promjena",
+DlgSpellOneChange : "Provjera zavrÅ¡ena: Jedna rijeÄ promjenjena",
+DlgSpellManyChanges : "Provjera zavrÅ¡ena: Promijenjeno %1 rijeÄi",
+
+IeSpellDownload : "Provjera pravopisa nije instalirana. Želite li skinuti provjeru pravopisa?",
+
+// Button Dialog
+DlgButtonText : "Tekst (vrijednost)",
+DlgButtonType : "Vrsta",
+DlgButtonTypeBtn : "Gumb",
+DlgButtonTypeSbm : "Pošalji",
+DlgButtonTypeRst : "Poništi",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Ime",
+DlgCheckboxValue : "Vrijednost",
+DlgCheckboxSelected : "Odabrano",
+
+// Form Dialog
+DlgFormName : "Ime",
+DlgFormAction : "Akcija",
+DlgFormMethod : "Metoda",
+
+// Select Field Dialog
+DlgSelectName : "Ime",
+DlgSelectValue : "Vrijednost",
+DlgSelectSize : "VeliÄina",
+DlgSelectLines : "linija",
+DlgSelectChkMulti : "Dozvoli višestruki odabir",
+DlgSelectOpAvail : "Dostupne opcije",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Vrijednost",
+DlgSelectBtnAdd : "Dodaj",
+DlgSelectBtnModify : "Promijeni",
+DlgSelectBtnUp : "Gore",
+DlgSelectBtnDown : "Dolje",
+DlgSelectBtnSetValue : "Postavi kao odabranu vrijednost",
+DlgSelectBtnDelete : "Obriši",
+
+// Textarea Dialog
+DlgTextareaName : "Ime",
+DlgTextareaCols : "Kolona",
+DlgTextareaRows : "Redova",
+
+// Text Field Dialog
+DlgTextName : "Ime",
+DlgTextValue : "Vrijednost",
+DlgTextCharWidth : "Å irina",
+DlgTextMaxChars : "Najviše karaktera",
+DlgTextType : "Vrsta",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Å ifra",
+
+// Hidden Field Dialog
+DlgHiddenName : "Ime",
+DlgHiddenValue : "Vrijednost",
+
+// Bulleted List Dialog
+BulletedListProp : "Svojstva liste",
+NumberedListProp : "Svojstva brojÄane liste",
+DlgLstStart : "PoÄetak",
+DlgLstType : "Vrsta",
+DlgLstTypeCircle : "Krug",
+DlgLstTypeDisc : "Disk",
+DlgLstTypeSquare : "Kvadrat",
+DlgLstTypeNumbers : "Brojevi (1, 2, 3)",
+DlgLstTypeLCase : "Mala slova (a, b, c)",
+DlgLstTypeUCase : "Velika slova (A, B, C)",
+DlgLstTypeSRoman : "Male rimske brojke (i, ii, iii)",
+DlgLstTypeLRoman : "Velike rimske brojke (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Općenito",
+DlgDocBackTab : "Pozadina",
+DlgDocColorsTab : "Boje i margine",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Naslov stranice",
+DlgDocLangDir : "Smjer jezika",
+DlgDocLangDirLTR : "S lijeva na desno",
+DlgDocLangDirRTL : "S desna na lijevo",
+DlgDocLangCode : "Kôd jezika",
+DlgDocCharSet : "Enkodiranje znakova",
+DlgDocCharSetCE : "Središnja Europa",
+DlgDocCharSetCT : "Tradicionalna kineska (Big5)",
+DlgDocCharSetCR : "Ćirilica",
+DlgDocCharSetGR : "GrÄka",
+DlgDocCharSetJP : "Japanska",
+DlgDocCharSetKR : "Koreanska",
+DlgDocCharSetTR : "Turska",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Zapadna Europa",
+DlgDocCharSetOther : "Ostalo enkodiranje znakova",
+
+DlgDocDocType : "Zaglavlje vrste dokumenta",
+DlgDocDocTypeOther : "Ostalo zaglavlje vrste dokumenta",
+DlgDocIncXHTML : "Ubaci XHTML deklaracije",
+DlgDocBgColor : "Boja pozadine",
+DlgDocBgImage : "URL slike pozadine",
+DlgDocBgNoScroll : "Pozadine se ne pomiÄe",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Posjećeni link",
+DlgDocCActive : "Aktivni link",
+DlgDocMargins : "Margine stranice",
+DlgDocMaTop : "Vrh",
+DlgDocMaLeft : "Lijevo",
+DlgDocMaRight : "Desno",
+DlgDocMaBottom : "Dolje",
+DlgDocMeIndex : "KljuÄne rijeÄi dokumenta (odvojene zarezom)",
+DlgDocMeDescr : "Opis dokumenta",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Autorska prava",
+DlgDocPreview : "Pregledaj",
+
+// Templates Dialog
+Templates : "Predlošci",
+DlgTemplatesTitle : "Predlošci sadržaja",
+DlgTemplatesSelMsg : "Molimo odaberite predložak koji želite otvoriti<br>(stvarni sadržaj će biti izgubljen):",
+DlgTemplatesLoading : "UÄitavam listu predložaka. Molimo priÄekajte...",
+DlgTemplatesNoTpl : "(Nema definiranih predložaka)",
+DlgTemplatesReplace : "Zamijeni trenutne sadržaje",
+
+// About Dialog
+DlgAboutAboutTab : "O FCKEditoru",
+DlgAboutBrowserInfoTab : "Podaci o pretraživaÄu",
+DlgAboutLicenseTab : "Licenca",
+DlgAboutVersion : "inaÄica",
+DlgAboutInfo : "Za više informacija posjetite",
+
+// Div Dialog
+DlgDivGeneralTab : "Općenito",
+DlgDivAdvancedTab : "Napredno",
+DlgDivStyle : "Stil",
+DlgDivInlineStyle : "Stil u redu",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/hu.js b/httemplate/elements/fckeditor/editor/lang/hu.js
new file mode 100644
index 0000000..0a72cac
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/hu.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Hungarian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Eszköztár elrejtése",
+ToolbarExpand : "Eszköztár megjelenítése",
+
+// Toolbar Items and Context Menu
+Save : "Mentés",
+NewPage : "Új oldal",
+Preview : "Előnézet",
+Cut : "Kivágás",
+Copy : "Másolás",
+Paste : "Beillesztés",
+PasteText : "Beillesztés formázás nélkül",
+PasteWord : "Beillesztés Word-ből",
+Print : "Nyomtatás",
+SelectAll : "Mindent kijelöl",
+RemoveFormat : "Formázás eltávolítása",
+InsertLinkLbl : "Hivatkozás",
+InsertLink : "Hivatkozás beillesztése/módosítása",
+RemoveLink : "Hivatkozás törlése",
+VisitLink : "Open Link", //MISSING
+Anchor : "Horgony beillesztése/szerkesztése",
+AnchorDelete : "Horgony eltávolítása",
+InsertImageLbl : "Kép",
+InsertImage : "Kép beillesztése/módosítása",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash beillesztése, módosítása",
+InsertTableLbl : "Táblázat",
+InsertTable : "Táblázat beillesztése/módosítása",
+InsertLineLbl : "Vonal",
+InsertLine : "Elválasztóvonal beillesztése",
+InsertSpecialCharLbl: "Speciális karakter",
+InsertSpecialChar : "Speciális karakter beillesztése",
+InsertSmileyLbl : "Hangulatjelek",
+InsertSmiley : "Hangulatjelek beillesztése",
+About : "FCKeditor névjegy",
+Bold : "Félkövér",
+Italic : "DÅ‘lt",
+Underline : "Aláhúzott",
+StrikeThrough : "Ãthúzott",
+Subscript : "Alsó index",
+Superscript : "Felső index",
+LeftJustify : "Balra",
+CenterJustify : "Középre",
+RightJustify : "Jobbra",
+BlockJustify : "Sorkizárt",
+DecreaseIndent : "Behúzás csökkentése",
+IncreaseIndent : "Behúzás növelése",
+Blockquote : "Idézet blokk",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Visszavonás",
+Redo : "Ismétlés",
+NumberedListLbl : "Számozás",
+NumberedList : "Számozás beillesztése/törlése",
+BulletedListLbl : "Felsorolás",
+BulletedList : "Felsorolás beillesztése/törlése",
+ShowTableBorders : "Táblázat szegély mutatása",
+ShowDetails : "Részletek mutatása",
+Style : "Stílus",
+FontFormat : "Formátum",
+Font : "Betűtípus",
+FontSize : "Méret",
+TextColor : "Betűszín",
+BGColor : "Háttérszín",
+Source : "Forráskód",
+Find : "Keresés",
+Replace : "Csere",
+SpellCheck : "Helyesírás-ellenőrzés",
+UniversalKeyboard : "Univerzális billentyűzet",
+PageBreakLbl : "Oldaltörés",
+PageBreak : "Oldaltörés beillesztése",
+
+Form : "Å°rlap",
+Checkbox : "Jelölőnégyzet",
+RadioButton : "Választógomb",
+TextField : "Szövegmező",
+Textarea : "Szövegterület",
+HiddenField : "Rejtettmező",
+Button : "Gomb",
+SelectionField : "Legördülő lista",
+ImageButton : "Képgomb",
+
+FitWindow : "Maximalizálás",
+ShowBlocks : "Blokkok megjelenítése",
+
+// Context Menu
+EditLink : "Hivatkozás módosítása",
+CellCM : "Cella",
+RowCM : "Sor",
+ColumnCM : "Oszlop",
+InsertRowAfter : "Sor beillesztése az aktuális sor mögé",
+InsertRowBefore : "Sor beillesztése az aktuális sor elé",
+DeleteRows : "Sorok törlése",
+InsertColumnAfter : "Oszlop beillesztése az aktuális oszlop mögé",
+InsertColumnBefore : "Oszlop beillesztése az aktuális oszlop elé",
+DeleteColumns : "Oszlopok törlése",
+InsertCellAfter : "Cella beillesztése az aktuális cella mögé",
+InsertCellBefore : "Cella beillesztése az aktuális cella elé",
+DeleteCells : "Cellák törlése",
+MergeCells : "Cellák egyesítése",
+MergeRight : "Cellák egyesítése jobbra",
+MergeDown : "Cellák egyesítése lefelé",
+HorizontalSplitCell : "Cellák szétválasztása vízszintesen",
+VerticalSplitCell : "Cellák szétválasztása függőlegesen",
+TableDelete : "Táblázat törlése",
+CellProperties : "Cella tulajdonságai",
+TableProperties : "Táblázat tulajdonságai",
+ImageProperties : "Kép tulajdonságai",
+FlashProperties : "Flash tulajdonságai",
+
+AnchorProp : "Horgony tulajdonságai",
+ButtonProp : "Gomb tulajdonságai",
+CheckboxProp : "Jelölőnégyzet tulajdonságai",
+HiddenFieldProp : "Rejtett mező tulajdonságai",
+RadioButtonProp : "Választógomb tulajdonságai",
+ImageButtonProp : "Képgomb tulajdonságai",
+TextFieldProp : "Szövegmező tulajdonságai",
+SelectionFieldProp : "Legördülő lista tulajdonságai",
+TextareaProp : "Szövegterület tulajdonságai",
+FormProp : "Űrlap tulajdonságai",
+
+FontFormats : "Normál;Formázott;Címsor;Fejléc 1;Fejléc 2;Fejléc 3;Fejléc 4;Fejléc 5;Fejléc 6;Bekezdés (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML feldolgozása. Kérem várjon...",
+Done : "Kész",
+PasteWordConfirm : "A beilleszteni kívánt szöveg Word-ből van másolva. El kívánja távolítani a formázást a beillesztés előtt?",
+NotCompatiblePaste : "Ez a parancs csak Internet Explorer 5.5 verziótól használható. Megpróbálja beilleszteni a szöveget az eredeti formázással?",
+UnknownToolbarItem : "Ismeretlen eszköztár elem \"%1\"",
+UnknownCommand : "Ismeretlen parancs \"%1\"",
+NotImplemented : "A parancs nem hajtható végre",
+UnknownToolbarSet : "Az eszközkészlet \"%1\" nem létezik",
+NoActiveX : "A böngésző biztonsági beállításai korlátozzák a szerkesztő lehetőségeit. Engedélyezni kell ezt az opciót: \"Run ActiveX controls and plug-ins\". Ettől függetlenül előfordulhatnak hibaüzenetek ill. bizonyos funkciók hiányozhatnak.",
+BrowseServerBlocked : "Nem lehet megnyitni a fájlböngészőt. Bizonyosodjon meg róla, hogy a felbukkanó ablakok engedélyezve vannak.",
+DialogBlocked : "Nem lehet megnyitni a párbeszédablakot. Bizonyosodjon meg róla, hogy a felbukkanó ablakok engedélyezve vannak.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "Rendben",
+DlgBtnCancel : "Mégsem",
+DlgBtnClose : "Bezárás",
+DlgBtnBrowseServer : "Böngészés a szerveren",
+DlgAdvancedTag : "További opciók",
+DlgOpOther : "Egyéb",
+DlgInfoTab : "Alaptulajdonságok",
+DlgAlertUrl : "Illessze be a webcímet",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nincs beállítva>",
+DlgGenId : "Azonosító",
+DlgGenLangDir : "Ãrás iránya",
+DlgGenLangDirLtr : "Balról jobbra",
+DlgGenLangDirRtl : "Jobbról balra",
+DlgGenLangCode : "Nyelv kódja",
+DlgGenAccessKey : "Billentyűkombináció",
+DlgGenName : "Név",
+DlgGenTabIndex : "Tabulátor index",
+DlgGenLongDescr : "Részletes leírás webcíme",
+DlgGenClass : "Stíluskészlet",
+DlgGenTitle : "Súgócimke",
+DlgGenContType : "Súgó tartalomtípusa",
+DlgGenLinkCharset : "Hivatkozott tartalom kódlapja",
+DlgGenStyle : "Stílus",
+
+// Image Dialog
+DlgImgTitle : "Kép tulajdonságai",
+DlgImgInfoTab : "Alaptulajdonságok",
+DlgImgBtnUpload : "Küldés a szerverre",
+DlgImgURL : "Hivatkozás",
+DlgImgUpload : "Feltöltés",
+DlgImgAlt : "Buborék szöveg",
+DlgImgWidth : "Szélesség",
+DlgImgHeight : "Magasság",
+DlgImgLockRatio : "Arány megtartása",
+DlgBtnResetSize : "Eredeti méret",
+DlgImgBorder : "Keret",
+DlgImgHSpace : "Vízsz. táv",
+DlgImgVSpace : "Függ. táv",
+DlgImgAlign : "Igazítás",
+DlgImgAlignLeft : "Bal",
+DlgImgAlignAbsBottom: "Legaljára",
+DlgImgAlignAbsMiddle: "Közepére",
+DlgImgAlignBaseline : "Alapvonalhoz",
+DlgImgAlignBottom : "Aljára",
+DlgImgAlignMiddle : "Középre",
+DlgImgAlignRight : "Jobbra",
+DlgImgAlignTextTop : "Szöveg tetejére",
+DlgImgAlignTop : "Tetejére",
+DlgImgPreview : "Előnézet",
+DlgImgAlertUrl : "Töltse ki a kép webcímét",
+DlgImgLinkTab : "Hivatkozás",
+
+// Flash Dialog
+DlgFlashTitle : "Flash tulajdonságai",
+DlgFlashChkPlay : "Automata lejátszás",
+DlgFlashChkLoop : "Folyamatosan",
+DlgFlashChkMenu : "Flash menü engedélyezése",
+DlgFlashScale : "Méretezés",
+DlgFlashScaleAll : "Mindent mutat",
+DlgFlashScaleNoBorder : "Keret nélkül",
+DlgFlashScaleFit : "Teljes kitöltés",
+
+// Link Dialog
+DlgLnkWindowTitle : "Hivatkozás tulajdonságai",
+DlgLnkInfoTab : "Alaptulajdonságok",
+DlgLnkTargetTab : "Megjelenítés",
+
+DlgLnkType : "Hivatkozás típusa",
+DlgLnkTypeURL : "Webcím",
+DlgLnkTypeAnchor : "Horgony az oldalon",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<más>",
+DlgLnkURL : "Webcím",
+DlgLnkAnchorSel : "Horgony választása",
+DlgLnkAnchorByName : "Horgony név szerint",
+DlgLnkAnchorById : "Azonosító szerint",
+DlgLnkNoAnchors : "(Nincs horgony a dokumentumban)",
+DlgLnkEMail : "E-Mail cím",
+DlgLnkEMailSubject : "Üzenet tárgya",
+DlgLnkEMailBody : "Ãœzenet",
+DlgLnkUpload : "Feltöltés",
+DlgLnkBtnUpload : "Küldés a szerverre",
+
+DlgLnkTarget : "Tartalom megjelenítése",
+DlgLnkTargetFrame : "<keretben>",
+DlgLnkTargetPopup : "<felugró ablakban>",
+DlgLnkTargetBlank : "Új ablakban (_blank)",
+DlgLnkTargetParent : "Szülő ablakban (_parent)",
+DlgLnkTargetSelf : "Azonos ablakban (_self)",
+DlgLnkTargetTop : "Legfelső ablakban (_top)",
+DlgLnkTargetFrameName : "Keret neve",
+DlgLnkPopWinName : "Felugró ablak neve",
+DlgLnkPopWinFeat : "Felugró ablak jellemzői",
+DlgLnkPopResize : "Méretezhető",
+DlgLnkPopLocation : "Címsor",
+DlgLnkPopMenu : "Menü sor",
+DlgLnkPopScroll : "Gördítősáv",
+DlgLnkPopStatus : "Ãllapotsor",
+DlgLnkPopToolbar : "Eszköztár",
+DlgLnkPopFullScrn : "Teljes képernyő (csak IE)",
+DlgLnkPopDependent : "Szülőhöz kapcsolt (csak Netscape)",
+DlgLnkPopWidth : "Szélesség",
+DlgLnkPopHeight : "Magasság",
+DlgLnkPopLeft : "Bal pozíció",
+DlgLnkPopTop : "Felső pozíció",
+
+DlnLnkMsgNoUrl : "Adja meg a hivatkozás webcímét",
+DlnLnkMsgNoEMail : "Adja meg az E-Mail címet",
+DlnLnkMsgNoAnchor : "Válasszon egy horgonyt",
+DlnLnkMsgInvPopName : "A felbukkanó ablak neve alfanumerikus karakterrel kezdôdjön, valamint ne tartalmazzon szóközt",
+
+// Color Dialog
+DlgColorTitle : "Színválasztás",
+DlgColorBtnClear : "Törlés",
+DlgColorHighlight : "Előnézet",
+DlgColorSelected : "Kiválasztott",
+
+// Smiley Dialog
+DlgSmileyTitle : "Hangulatjel beszúrása",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Speciális karakter választása",
+
+// Table Dialog
+DlgTableTitle : "Táblázat tulajdonságai",
+DlgTableRows : "Sorok",
+DlgTableColumns : "Oszlopok",
+DlgTableBorder : "Szegélyméret",
+DlgTableAlign : "Igazítás",
+DlgTableAlignNotSet : "<Nincs beállítva>",
+DlgTableAlignLeft : "Balra",
+DlgTableAlignCenter : "Középre",
+DlgTableAlignRight : "Jobbra",
+DlgTableWidth : "Szélesség",
+DlgTableWidthPx : "képpont",
+DlgTableWidthPc : "százalék",
+DlgTableHeight : "Magasság",
+DlgTableCellSpace : "Cella térköz",
+DlgTableCellPad : "Cella belső margó",
+DlgTableCaption : "Felirat",
+DlgTableSummary : "Leírás",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Cella tulajdonságai",
+DlgCellWidth : "Szélesség",
+DlgCellWidthPx : "képpont",
+DlgCellWidthPc : "százalék",
+DlgCellHeight : "Magasság",
+DlgCellWordWrap : "Sortörés",
+DlgCellWordWrapNotSet : "<Nincs beállítva>",
+DlgCellWordWrapYes : "Igen",
+DlgCellWordWrapNo : "Nem",
+DlgCellHorAlign : "Vízsz. igazítás",
+DlgCellHorAlignNotSet : "<Nincs beállítva>",
+DlgCellHorAlignLeft : "Balra",
+DlgCellHorAlignCenter : "Középre",
+DlgCellHorAlignRight: "Jobbra",
+DlgCellVerAlign : "Függ. igazítás",
+DlgCellVerAlignNotSet : "<Nincs beállítva>",
+DlgCellVerAlignTop : "Tetejére",
+DlgCellVerAlignMiddle : "Középre",
+DlgCellVerAlignBottom : "Aljára",
+DlgCellVerAlignBaseline : "Egyvonalba",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Sorok egyesítése",
+DlgCellCollSpan : "Oszlopok egyesítése",
+DlgCellBackColor : "Háttérszín",
+DlgCellBorderColor : "Szegélyszín",
+DlgCellBtnSelect : "Kiválasztás...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Keresés és csere",
+
+// Find Dialog
+DlgFindTitle : "Keresés",
+DlgFindFindBtn : "Keresés",
+DlgFindNotFoundMsg : "A keresett szöveg nem található.",
+
+// Replace Dialog
+DlgReplaceTitle : "Csere",
+DlgReplaceFindLbl : "Keresett szöveg:",
+DlgReplaceReplaceLbl : "Csere erre:",
+DlgReplaceCaseChk : "kis- és nagybetű megkülönböztetése",
+DlgReplaceReplaceBtn : "Csere",
+DlgReplaceReplAllBtn : "Az összes cseréje",
+DlgReplaceWordChk : "csak ha ez a teljes szó",
+
+// Paste Operations / Dialog
+PasteErrorCut : "A böngésző biztonsági beállításai nem engedélyezik a szerkesztőnek, hogy végrehajtsa a kivágás műveletet. Használja az alábbi billentyűkombinációt (Ctrl+X).",
+PasteErrorCopy : "A böngésző biztonsági beállításai nem engedélyezik a szerkesztőnek, hogy végrehajtsa a másolás műveletet. Használja az alábbi billentyűkombinációt (Ctrl+X).",
+
+PasteAsText : "Beillesztés formázatlan szövegként",
+PasteFromWord : "Beillesztés Word-ből",
+
+DlgPasteMsg2 : "Másolja be az alábbi mezőbe a <STRONG>Ctrl+V</STRONG> billentyűk lenyomásával, majd nyomjon <STRONG>Rendben</STRONG>-t.",
+DlgPasteSec : "A böngésző biztonsági beállításai miatt a szerkesztő nem képes hozzáférni a vágólap adataihoz. Illeszd be újra ebben az ablakban.",
+DlgPasteIgnoreFont : "Betű formázások megszüntetése",
+DlgPasteRemoveStyles : "Stílusok eltávolítása",
+
+// Color Picker
+ColorAutomatic : "Automatikus",
+ColorMoreColors : "További színek...",
+
+// Document Properties
+DocProps : "Dokumentum tulajdonságai",
+
+// Anchor Dialog
+DlgAnchorTitle : "Horgony tulajdonságai",
+DlgAnchorName : "Horgony neve",
+DlgAnchorErrorName : "Kérem adja meg a horgony nevét",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nincs a szótárban",
+DlgSpellChangeTo : "Módosítás",
+DlgSpellBtnIgnore : "Kihagyja",
+DlgSpellBtnIgnoreAll : "Mindet kihagyja",
+DlgSpellBtnReplace : "Csere",
+DlgSpellBtnReplaceAll : "Összes cseréje",
+DlgSpellBtnUndo : "Visszavonás",
+DlgSpellNoSuggestions : "Nincs javaslat",
+DlgSpellProgress : "Helyesírás-ellenőrzés folyamatban...",
+DlgSpellNoMispell : "Helyesírás-ellenőrzés kész: Nem találtam hibát",
+DlgSpellNoChanges : "Helyesírás-ellenőrzés kész: Nincs változtatott szó",
+DlgSpellOneChange : "Helyesírás-ellenőrzés kész: Egy szó cserélve",
+DlgSpellManyChanges : "Helyesírás-ellenőrzés kész: %1 szó cserélve",
+
+IeSpellDownload : "A helyesírás-ellenőrző nincs telepítve. Szeretné letölteni most?",
+
+// Button Dialog
+DlgButtonText : "Szöveg (Érték)",
+DlgButtonType : "Típus",
+DlgButtonTypeBtn : "Gomb",
+DlgButtonTypeSbm : "Küldés",
+DlgButtonTypeRst : "Alaphelyzet",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Név",
+DlgCheckboxValue : "Érték",
+DlgCheckboxSelected : "Kiválasztott",
+
+// Form Dialog
+DlgFormName : "Név",
+DlgFormAction : "Adatfeldolgozást végző hivatkozás",
+DlgFormMethod : "Adatküldés módja",
+
+// Select Field Dialog
+DlgSelectName : "Név",
+DlgSelectValue : "Érték",
+DlgSelectSize : "Méret",
+DlgSelectLines : "sor",
+DlgSelectChkMulti : "több sor is kiválasztható",
+DlgSelectOpAvail : "Elérhető opciók",
+DlgSelectOpText : "Szöveg",
+DlgSelectOpValue : "Érték",
+DlgSelectBtnAdd : "Hozzáad",
+DlgSelectBtnModify : "Módosít",
+DlgSelectBtnUp : "Fel",
+DlgSelectBtnDown : "Le",
+DlgSelectBtnSetValue : "Legyen az alapértelmezett érték",
+DlgSelectBtnDelete : "Töröl",
+
+// Textarea Dialog
+DlgTextareaName : "Név",
+DlgTextareaCols : "Karakterek száma egy sorban",
+DlgTextareaRows : "Sorok száma",
+
+// Text Field Dialog
+DlgTextName : "Név",
+DlgTextValue : "Érték",
+DlgTextCharWidth : "Megjelenített karakterek száma",
+DlgTextMaxChars : "Maximális karakterszám",
+DlgTextType : "Típus",
+DlgTextTypeText : "Szöveg",
+DlgTextTypePass : "Jelszó",
+
+// Hidden Field Dialog
+DlgHiddenName : "Név",
+DlgHiddenValue : "Érték",
+
+// Bulleted List Dialog
+BulletedListProp : "Felsorolás tulajdonságai",
+NumberedListProp : "Számozás tulajdonságai",
+DlgLstStart : "Start",
+DlgLstType : "Formátum",
+DlgLstTypeCircle : "Kör",
+DlgLstTypeDisc : "Lemez",
+DlgLstTypeSquare : "Négyzet",
+DlgLstTypeNumbers : "Számok (1, 2, 3)",
+DlgLstTypeLCase : "Kisbetűk (a, b, c)",
+DlgLstTypeUCase : "Nagybetűk (A, B, C)",
+DlgLstTypeSRoman : "Kis római számok (i, ii, iii)",
+DlgLstTypeLRoman : "Nagy római számok (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Ãltalános",
+DlgDocBackTab : "Háttér",
+DlgDocColorsTab : "Színek és margók",
+DlgDocMetaTab : "Meta adatok",
+
+DlgDocPageTitle : "Oldalcím",
+DlgDocLangDir : "Ãrás iránya",
+DlgDocLangDirLTR : "Balról jobbra",
+DlgDocLangDirRTL : "Jobbról balra",
+DlgDocLangCode : "Nyelv kód",
+DlgDocCharSet : "Karakterkódolás",
+DlgDocCharSetCE : "Közép-Európai",
+DlgDocCharSetCT : "Kínai Tradicionális (Big5)",
+DlgDocCharSetCR : "Cyrill",
+DlgDocCharSetGR : "Görög",
+DlgDocCharSetJP : "Japán",
+DlgDocCharSetKR : "Koreai",
+DlgDocCharSetTR : "Török",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Nyugat-Európai",
+DlgDocCharSetOther : "Más karakterkódolás",
+
+DlgDocDocType : "Dokumentum típus fejléc",
+DlgDocDocTypeOther : "Más dokumentum típus fejléc",
+DlgDocIncXHTML : "XHTML deklarációk beillesztése",
+DlgDocBgColor : "Háttérszín",
+DlgDocBgImage : "Háttérkép cím",
+DlgDocBgNoScroll : "Nem gördíthető háttér",
+DlgDocCText : "Szöveg",
+DlgDocCLink : "Cím",
+DlgDocCVisited : "Látogatott cím",
+DlgDocCActive : "Aktív cím",
+DlgDocMargins : "Oldal margók",
+DlgDocMaTop : "Felső",
+DlgDocMaLeft : "Bal",
+DlgDocMaRight : "Jobb",
+DlgDocMaBottom : "Alsó",
+DlgDocMeIndex : "Dokumentum keresőszavak (vesszővel elválasztva)",
+DlgDocMeDescr : "Dokumentum leírás",
+DlgDocMeAuthor : "Szerző",
+DlgDocMeCopy : "Szerzői jog",
+DlgDocPreview : "Előnézet",
+
+// Templates Dialog
+Templates : "Sablonok",
+DlgTemplatesTitle : "Elérhető sablonok",
+DlgTemplatesSelMsg : "Válassza ki melyik sablon nyíljon meg a szerkesztőben<br>(a jelenlegi tartalom elveszik):",
+DlgTemplatesLoading : "Sablon lista betöltése. Kis türelmet...",
+DlgTemplatesNoTpl : "(Nincs sablon megadva)",
+DlgTemplatesReplace : "Kicseréli a jelenlegi tartalmat",
+
+// About Dialog
+DlgAboutAboutTab : "Névjegy",
+DlgAboutBrowserInfoTab : "Böngésző információ",
+DlgAboutLicenseTab : "Licensz",
+DlgAboutVersion : "verzió",
+DlgAboutInfo : "További információkért látogasson el ide:",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/is.js b/httemplate/elements/fckeditor/editor/lang/is.js
new file mode 100644
index 0000000..3238f7d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/is.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Icelandic language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Fela verkstiku",
+ToolbarExpand : "Sýna verkstiku",
+
+// Toolbar Items and Context Menu
+Save : "Vista",
+NewPage : "Ný síða",
+Preview : "Forskoða",
+Cut : "Klippa",
+Copy : "Afrita",
+Paste : "Líma",
+PasteText : "Líma ósniðinn texta",
+PasteWord : "Líma úr Word",
+Print : "Prenta",
+SelectAll : "Velja allt",
+RemoveFormat : "Fjarlægja snið",
+InsertLinkLbl : "Stikla",
+InsertLink : "Stofna/breyta stiklu",
+RemoveLink : "Fjarlægja stiklu",
+VisitLink : "Opna stiklusíðu",
+Anchor : "Stofna/breyta kaflamerki",
+AnchorDelete : "Eyða kaflamerki",
+InsertImageLbl : "Setja inn mynd",
+InsertImage : "Setja inn/breyta mynd",
+InsertFlashLbl : "Flash",
+InsertFlash : "Setja inn/breyta Flash",
+InsertTableLbl : "Tafla",
+InsertTable : "Setja inn/breyta töflu",
+InsertLineLbl : "Lína",
+InsertLine : "Lóðrétt lína",
+InsertSpecialCharLbl: "Merki",
+InsertSpecialChar : "Setja inn merki",
+InsertSmileyLbl : "Svipur",
+InsertSmiley : "Setja upp svip",
+About : "Um FCKeditor",
+Bold : "Feitletrað",
+Italic : "Skáletrað",
+Underline : "Undirstrikað",
+StrikeThrough : "Yfirstrikað",
+Subscript : "Niðurskrifað",
+Superscript : "Uppskrifað",
+LeftJustify : "Vinstrijöfnun",
+CenterJustify : "Miðja texta",
+RightJustify : "Hægrijöfnun",
+BlockJustify : "Jafna báðum megin",
+DecreaseIndent : "Auka inndrátt",
+IncreaseIndent : "Minnka inndrátt",
+Blockquote : "Inndráttur",
+CreateDiv : "Búa til DIV-hýsil",
+EditDiv : "Breyta DIV-hýsli",
+DeleteDiv : "Eyða DIV-hýsli",
+Undo : "Afturkalla",
+Redo : "Hætta við afturköllun",
+NumberedListLbl : "Númeraður listi",
+NumberedList : "Setja inn/fella númeraðan lista",
+BulletedListLbl : "Punktalisti",
+BulletedList : "Setja inn/fella punktalista",
+ShowTableBorders : "Sýna töflugrind",
+ShowDetails : "Sýna smáatriði",
+Style : "Stílflokkur",
+FontFormat : "Stílsnið",
+Font : "Leturgerð ",
+FontSize : "Leturstærð ",
+TextColor : "Litur texta",
+BGColor : "Bakgrunnslitur",
+Source : "Kóði",
+Find : "Leita",
+Replace : "Skipta út",
+SpellCheck : "Villuleit",
+UniversalKeyboard : "Hnattrænt lyklaborð",
+PageBreakLbl : "Síðuskil",
+PageBreak : "Setja inn síðuskil",
+
+Form : "Setja inn innsláttarform",
+Checkbox : "Setja inn hökunarreit",
+RadioButton : "Setja inn valhnapp",
+TextField : "Setja inn textareit",
+Textarea : "Setja inn textasvæði",
+HiddenField : "Setja inn falið svæði",
+Button : "Setja inn hnapp",
+SelectionField : "Setja inn lista",
+ImageButton : "Setja inn myndahnapp",
+
+FitWindow : "Skoða ritil í fullri stærð",
+ShowBlocks : "Sýna blokkir",
+
+// Context Menu
+EditLink : "Breyta stiklu",
+CellCM : "Reitur",
+RowCM : "Röð",
+ColumnCM : "Dálkur",
+InsertRowAfter : "Skjóta inn röð fyrir neðan",
+InsertRowBefore : "Skjóta inn röð fyrir ofan",
+DeleteRows : "Eyða röð",
+InsertColumnAfter : "Skjóta inn dálki hægra megin",
+InsertColumnBefore : "Skjóta inn dálki vinstra megin",
+DeleteColumns : "Fella dálk",
+InsertCellAfter : "Skjóta inn reiti fyrir framan",
+InsertCellBefore : "Skjóta inn reiti fyrir aftan",
+DeleteCells : "Fella reit",
+MergeCells : "Sameina reiti",
+MergeRight : "Sameina til hægri",
+MergeDown : "Sameina niður á við",
+HorizontalSplitCell : "Kljúfa reit lárétt",
+VerticalSplitCell : "Kljúfa reit lóðrétt",
+TableDelete : "Fella töflu",
+CellProperties : "Eigindi reits",
+TableProperties : "Eigindi töflu",
+ImageProperties : "Eigindi myndar",
+FlashProperties : "Eigindi Flash",
+
+AnchorProp : "Eigindi kaflamerkis",
+ButtonProp : "Eigindi hnapps",
+CheckboxProp : "Eigindi markreits",
+HiddenFieldProp : "Eigindi falins svæðis",
+RadioButtonProp : "Eigindi valhnapps",
+ImageButtonProp : "Eigindi myndahnapps",
+TextFieldProp : "Eigindi textareits",
+SelectionFieldProp : "Eigindi lista",
+TextareaProp : "Eigindi textasvæðis",
+FormProp : "Eigindi innsláttarforms",
+
+FontFormats : "Venjulegt letur;Forsniðið;Vistfang;Fyrirsögn 1;Fyrirsögn 2;Fyrirsögn 3;Fyrirsögn 4;Fyrirsögn 5;Fyrirsögn 6;Venjulegt (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Meðhöndla XHTML...",
+Done : "Tilbúið",
+PasteWordConfirm : "Textinn sem þú ætlar að líma virðist koma úr Word. Viltu hreinsa óþarfar Word-skipanir úr honum?",
+NotCompatiblePaste : "Þessi aðgerð er bundin við Internet Explorer 5.5 og nýrri. Viltu líma textann án þess að hreinsa hann?",
+UnknownToolbarItem : "Óþekktur hlutur í verkstiku \"%1\"!",
+UnknownCommand : "Óþekkt skipanaheiti \"%1\"!",
+NotImplemented : "Skipun ekki virkjuð!",
+UnknownToolbarSet : "Verkstikan \"%1\" ekki til!",
+NoActiveX : "Öryggisstillingarnar í vafranum þínum leyfa ekki alla möguleika ritilsins.<br>Láttu vafrann leyfa Active-X og viðbætur til að komast hjá villum og takmörkunum.",
+BrowseServerBlocked : "Ritillinn getur ekki opnað nauðsynlega hjálparglugga!<br>Láttu hann leyfa þessari síðu að opna sprettiglugga.",
+DialogBlocked : "Ekki var hægt að opna skipanaglugga!<br>Nauðsynlegt er að leyfa síðunni að opna sprettiglugga.",
+VisitLinkBlocked : "Ekki var hægt að opna nýjan glugga. Gangtu úr skugga um að engir sprettigluggabanar séu virkir.",
+
+// Dialogs
+DlgBtnOK : "Ã lagi",
+DlgBtnCancel : "Hætta við",
+DlgBtnClose : "Loka",
+DlgBtnBrowseServer : "Fletta í skjalasafni",
+DlgAdvancedTag : "Tæknilegt",
+DlgOpOther : "<Annað>",
+DlgInfoTab : "Upplýsingar",
+DlgAlertUrl : "Sláðu inn slóð",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ekkert valið>",
+DlgGenId : "Auðkenni",
+DlgGenLangDir : "Lesstefna",
+DlgGenLangDirLtr : "Frá vinstri til hægri (LTR)",
+DlgGenLangDirRtl : "Frá hægri til vinstri (RTL)",
+DlgGenLangCode : "Tungumálakóði",
+DlgGenAccessKey : "Skammvalshnappur",
+DlgGenName : "Nafn",
+DlgGenTabIndex : "Raðnúmer innsláttarreits",
+DlgGenLongDescr : "Nánari lýsing",
+DlgGenClass : "Stílsniðsflokkur",
+DlgGenTitle : "Titill",
+DlgGenContType : "Tegund innihalds",
+DlgGenLinkCharset : "Táknróf",
+DlgGenStyle : "Stíll",
+
+// Image Dialog
+DlgImgTitle : "Eigindi myndar",
+DlgImgInfoTab : "Almennt",
+DlgImgBtnUpload : "Hlaða upp",
+DlgImgURL : "Vefslóð",
+DlgImgUpload : "Hlaða upp",
+DlgImgAlt : "Baklægur texti",
+DlgImgWidth : "Breidd",
+DlgImgHeight : "Hæð",
+DlgImgLockRatio : "Festa stærðarhlutfall",
+DlgBtnResetSize : "Reikna stærð",
+DlgImgBorder : "Rammi",
+DlgImgHSpace : "Vinstri bil",
+DlgImgVSpace : "Hægri bil",
+DlgImgAlign : "Jöfnun",
+DlgImgAlignLeft : "Vinstri",
+DlgImgAlignAbsBottom: "Abs neðst",
+DlgImgAlignAbsMiddle: "Abs miðjuð",
+DlgImgAlignBaseline : "Grunnlína",
+DlgImgAlignBottom : "Neðst",
+DlgImgAlignMiddle : "Miðjuð",
+DlgImgAlignRight : "Hægri",
+DlgImgAlignTextTop : "Efri brún texta",
+DlgImgAlignTop : "Efst",
+DlgImgPreview : "Sýna dæmi",
+DlgImgAlertUrl : "Sláðu inn slóðina að myndinni",
+DlgImgLinkTab : "Stikla",
+
+// Flash Dialog
+DlgFlashTitle : "Eigindi Flash",
+DlgFlashChkPlay : "Sjálfvirk spilun",
+DlgFlashChkLoop : "Endurtekning",
+DlgFlashChkMenu : "Sýna Flash-valmynd",
+DlgFlashScale : "Skali",
+DlgFlashScaleAll : "Sýna allt",
+DlgFlashScaleNoBorder : "Ãn ramma",
+DlgFlashScaleFit : "Fella skala að stærð",
+
+// Link Dialog
+DlgLnkWindowTitle : "Stikla",
+DlgLnkInfoTab : "Almennt",
+DlgLnkTargetTab : "Mark",
+
+DlgLnkType : "Stikluflokkur",
+DlgLnkTypeURL : "Vefslóð",
+DlgLnkTypeAnchor : "Bókamerki á þessari síðu",
+DlgLnkTypeEMail : "Netfang",
+DlgLnkProto : "Samskiptastaðall",
+DlgLnkProtoOther : "<annað>",
+DlgLnkURL : "Vefslóð",
+DlgLnkAnchorSel : "Veldu akkeri",
+DlgLnkAnchorByName : "Eftir akkerisnafni",
+DlgLnkAnchorById : "Eftir auðkenni einingar",
+DlgLnkNoAnchors : "<Engin bókamerki á skrá>",
+DlgLnkEMail : "Netfang",
+DlgLnkEMailSubject : "Efni",
+DlgLnkEMailBody : "Meginmál",
+DlgLnkUpload : "Senda upp",
+DlgLnkBtnUpload : "Senda upp",
+
+DlgLnkTarget : "Mark",
+DlgLnkTargetFrame : "<rammi>",
+DlgLnkTargetPopup : "<sprettigluggi>",
+DlgLnkTargetBlank : "Nýr gluggi (_blank)",
+DlgLnkTargetParent : "Yfirsettur rammi (_parent)",
+DlgLnkTargetSelf : "Sami gluggi (_self)",
+DlgLnkTargetTop : "Allur glugginn (_top)",
+DlgLnkTargetFrameName : "Nafn markglugga",
+DlgLnkPopWinName : "Nafn sprettiglugga",
+DlgLnkPopWinFeat : "Eigindi sprettiglugga",
+DlgLnkPopResize : "Skölun",
+DlgLnkPopLocation : "Fanglína",
+DlgLnkPopMenu : "Vallína",
+DlgLnkPopScroll : "Skrunstikur",
+DlgLnkPopStatus : "Stöðustika",
+DlgLnkPopToolbar : "Verkfærastika",
+DlgLnkPopFullScrn : "Heilskjár (IE)",
+DlgLnkPopDependent : "Háð venslum (Netscape)",
+DlgLnkPopWidth : "Breidd",
+DlgLnkPopHeight : "Hæð",
+DlgLnkPopLeft : "Fjarlægð frá vinstri",
+DlgLnkPopTop : "Fjarlægð frá efri brún",
+
+DlnLnkMsgNoUrl : "Sláðu inn veffang stiklunnar!",
+DlnLnkMsgNoEMail : "Sláðu inn netfang!",
+DlnLnkMsgNoAnchor : "Veldu fyrst eitthvert bókamerki!",
+DlnLnkMsgInvPopName : "Sprettisíðan verður að byrja á bókstaf (a-z) og má ekki innihalda stafabil",
+
+// Color Dialog
+DlgColorTitle : "Velja lit",
+DlgColorBtnClear : "Núllstilla",
+DlgColorHighlight : "Litmerkja",
+DlgColorSelected : "Valið",
+
+// Smiley Dialog
+DlgSmileyTitle : "Velja svip",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Velja tákn",
+
+// Table Dialog
+DlgTableTitle : "Eigindi töflu",
+DlgTableRows : "Raðir",
+DlgTableColumns : "Dálkar",
+DlgTableBorder : "Breidd ramma",
+DlgTableAlign : "Jöfnun",
+DlgTableAlignNotSet : "<ekkert valið>",
+DlgTableAlignLeft : "Vinstrijafnað",
+DlgTableAlignCenter : "Miðjað",
+DlgTableAlignRight : "Hægrijafnað",
+DlgTableWidth : "Breidd",
+DlgTableWidthPx : "myndeindir",
+DlgTableWidthPc : "prósent",
+DlgTableHeight : "Hæð",
+DlgTableCellSpace : "Bil milli reita",
+DlgTableCellPad : "Reitaspássía",
+DlgTableCaption : "Titill",
+DlgTableSummary : "Ãfram",
+DlgTableHeaders : "Fyrirsagnir",
+DlgTableHeadersNone : "Engar",
+DlgTableHeadersColumn : "Fyrsti dálkur",
+DlgTableHeadersRow : "Fyrsta röð",
+DlgTableHeadersBoth : "Hvort tveggja",
+
+// Table Cell Dialog
+DlgCellTitle : "Eigindi reits",
+DlgCellWidth : "Breidd",
+DlgCellWidthPx : "myndeindir",
+DlgCellWidthPc : "prósent",
+DlgCellHeight : "Hæð",
+DlgCellWordWrap : "Línuskipting",
+DlgCellWordWrapNotSet : "<ekkert valið>",
+DlgCellWordWrapYes : "Já",
+DlgCellWordWrapNo : "Nei",
+DlgCellHorAlign : "Lárétt jöfnun",
+DlgCellHorAlignNotSet : "<ekkert valið>",
+DlgCellHorAlignLeft : "Vinstrijafnað",
+DlgCellHorAlignCenter : "Miðjað",
+DlgCellHorAlignRight: "Hægrijafnað",
+DlgCellVerAlign : "Lóðrétt jöfnun",
+DlgCellVerAlignNotSet : "<ekkert valið>",
+DlgCellVerAlignTop : "Efst",
+DlgCellVerAlignMiddle : "Miðjað",
+DlgCellVerAlignBottom : "Neðst",
+DlgCellVerAlignBaseline : "Grunnlína",
+DlgCellType : "Tegund reits",
+DlgCellTypeData : "Gögn",
+DlgCellTypeHeader : "Fyrirsögn",
+DlgCellRowSpan : "Hæð í röðum talið",
+DlgCellCollSpan : "Breidd í dálkum talið",
+DlgCellBackColor : "Bakgrunnslitur",
+DlgCellBorderColor : "Rammalitur",
+DlgCellBtnSelect : "Veldu...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Finna og skipta",
+
+// Find Dialog
+DlgFindTitle : "Finna",
+DlgFindFindBtn : "Finna",
+DlgFindNotFoundMsg : "Leitartexti fannst ekki!",
+
+// Replace Dialog
+DlgReplaceTitle : "Skipta út",
+DlgReplaceFindLbl : "Leita að:",
+DlgReplaceReplaceLbl : "Skipta út fyrir:",
+DlgReplaceCaseChk : "Gera greinarmun á¡ há¡- og lágstöfum",
+DlgReplaceReplaceBtn : "Skipta út",
+DlgReplaceReplAllBtn : "Skipta út allsstaðar",
+DlgReplaceWordChk : "Aðeins heil orð",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Öryggisstillingar vafrans þíns leyfa ekki klippingu texta með músaraðgerð. Notaðu lyklaborðið í klippa (Ctrl+X).",
+PasteErrorCopy : "Öryggisstillingar vafrans þíns leyfa ekki afritun texta með músaraðgerð. Notaðu lyklaborðið í afrita (Ctrl+C).",
+
+PasteAsText : "Líma sem ósniðinn texta",
+PasteFromWord : "Líma úr Word",
+
+DlgPasteMsg2 : "Límdu í svæðið hér að neðan og (<STRONG>Ctrl+V</STRONG>) og smelltu á <STRONG>OK</STRONG>.",
+DlgPasteSec : "Vegna öryggisstillinga í vafranum þínum fær ritillinn ekki beinan aðgang að klippuborðinu. Þú verður að líma innihaldið aftur inn í þennan glugga.",
+DlgPasteIgnoreFont : "Hunsa leturskilgreiningar",
+DlgPasteRemoveStyles : "Hunsa letureigindi",
+
+// Color Picker
+ColorAutomatic : "Sjálfval",
+ColorMoreColors : "Fleiri liti...",
+
+// Document Properties
+DocProps : "Eigindi skjals",
+
+// Anchor Dialog
+DlgAnchorTitle : "Eigindi bókamerkis",
+DlgAnchorName : "Nafn bókamerkis",
+DlgAnchorErrorName : "Sláðu inn nafn bókamerkis!",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ekki í orðabókinni",
+DlgSpellChangeTo : "Tillaga",
+DlgSpellBtnIgnore : "Hunsa",
+DlgSpellBtnIgnoreAll : "Hunsa allt",
+DlgSpellBtnReplace : "Skipta",
+DlgSpellBtnReplaceAll : "Skipta öllu",
+DlgSpellBtnUndo : "Til baka",
+DlgSpellNoSuggestions : "- engar tillögur -",
+DlgSpellProgress : "Villuleit í gangi...",
+DlgSpellNoMispell : "Villuleit lokið: Engin villa fannst",
+DlgSpellNoChanges : "Villuleit lokið: Engu orði breytt",
+DlgSpellOneChange : "Villuleit lokið: Einu orði breytt",
+DlgSpellManyChanges : "Villuleit lokið: %1 orðum breytt",
+
+IeSpellDownload : "Villuleit ekki sett upp.<br>Viltu setja hana upp?",
+
+// Button Dialog
+DlgButtonText : "Texti",
+DlgButtonType : "Gerð",
+DlgButtonTypeBtn : "Hnappur",
+DlgButtonTypeSbm : "Staðfesta",
+DlgButtonTypeRst : "Hreinsa",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nafn",
+DlgCheckboxValue : "Gildi",
+DlgCheckboxSelected : "Valið",
+
+// Form Dialog
+DlgFormName : "Nafn",
+DlgFormAction : "Aðgerð",
+DlgFormMethod : "Aðferð",
+
+// Select Field Dialog
+DlgSelectName : "Nafn",
+DlgSelectValue : "Gildi",
+DlgSelectSize : "Stærð",
+DlgSelectLines : "línur",
+DlgSelectChkMulti : "Leyfa fleiri kosti",
+DlgSelectOpAvail : "Kostir",
+DlgSelectOpText : "Texti",
+DlgSelectOpValue : "Gildi",
+DlgSelectBtnAdd : "Bæta við",
+DlgSelectBtnModify : "Breyta",
+DlgSelectBtnUp : "Upp",
+DlgSelectBtnDown : "Niður",
+DlgSelectBtnSetValue : "Merkja sem valið",
+DlgSelectBtnDelete : "Eyða",
+
+// Textarea Dialog
+DlgTextareaName : "Nafn",
+DlgTextareaCols : "Dálkar",
+DlgTextareaRows : "Línur",
+
+// Text Field Dialog
+DlgTextName : "Nafn",
+DlgTextValue : "Gildi",
+DlgTextCharWidth : "Breidd (leturtákn)",
+DlgTextMaxChars : "Hámarksfjöldi leturtákna",
+DlgTextType : "Gerð",
+DlgTextTypeText : "Texti",
+DlgTextTypePass : "Lykilorð",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nafn",
+DlgHiddenValue : "Gildi",
+
+// Bulleted List Dialog
+BulletedListProp : "Eigindi depillista",
+NumberedListProp : "Eigindi tölusetts lista",
+DlgLstStart : "Byrja",
+DlgLstType : "Gerð",
+DlgLstTypeCircle : "Hringur",
+DlgLstTypeDisc : "Fylltur hringur",
+DlgLstTypeSquare : "Ferningur",
+DlgLstTypeNumbers : "Tölusett (1, 2, 3)",
+DlgLstTypeLCase : "Lágstafir (a, b, c)",
+DlgLstTypeUCase : "Hástafir (A, B, C)",
+DlgLstTypeSRoman : "Rómverkar lágstafatölur (i, ii, iii)",
+DlgLstTypeLRoman : "Rómverkar hástafatölur (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Almennt",
+DlgDocBackTab : "Bakgrunnur",
+DlgDocColorsTab : "Litir og rammar",
+DlgDocMetaTab : "Lýsigögn",
+
+DlgDocPageTitle : "Titill síðu",
+DlgDocLangDir : "Tungumál",
+DlgDocLangDirLTR : "Vinstri til hægri (LTR)",
+DlgDocLangDirRTL : "Hægri til vinstri (RTL)",
+DlgDocLangCode : "Tungumálakóði",
+DlgDocCharSet : "Letursett",
+DlgDocCharSetCE : "Mið-evrópskt",
+DlgDocCharSetCT : "Kínverskt, hefðbundið (Big5)",
+DlgDocCharSetCR : "Kýrilskt",
+DlgDocCharSetGR : "Grískt",
+DlgDocCharSetJP : "Japanskt",
+DlgDocCharSetKR : "Kóreskt",
+DlgDocCharSetTR : "Tyrkneskt",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Vestur-evrópst",
+DlgDocCharSetOther : "Annað letursett",
+
+DlgDocDocType : "Flokkur skjalategunda",
+DlgDocDocTypeOther : "Annar flokkur skjalategunda",
+DlgDocIncXHTML : "Fella inn XHTML lýsingu",
+DlgDocBgColor : "Bakgrunnslitur",
+DlgDocBgImage : "Slóð bakgrunnsmyndar",
+DlgDocBgNoScroll : "Læstur bakgrunnur",
+DlgDocCText : "Texti",
+DlgDocCLink : "Stikla",
+DlgDocCVisited : "Heimsótt stikla",
+DlgDocCActive : "Virk stikla",
+DlgDocMargins : "Hliðarspássía",
+DlgDocMaTop : "Efst",
+DlgDocMaLeft : "Vinstri",
+DlgDocMaRight : "Hægri",
+DlgDocMaBottom : "Neðst",
+DlgDocMeIndex : "Lykilorð efnisorðaskrár (aðgreind með kommum)",
+DlgDocMeDescr : "Lýsing skjals",
+DlgDocMeAuthor : "Höfundur",
+DlgDocMeCopy : "Höfundarréttur",
+DlgDocPreview : "Sýna",
+
+// Templates Dialog
+Templates : "Sniðmát",
+DlgTemplatesTitle : "Innihaldssniðmát",
+DlgTemplatesSelMsg : "Veldu sniðmát til að opna í ritlinum.<br>(Núverandi innihald víkur fyrir því!):",
+DlgTemplatesLoading : "Sæki lista yfir sniðmát...",
+DlgTemplatesNoTpl : "(Ekkert sniðmát er skilgreint!)",
+DlgTemplatesReplace : "Skipta út raunverulegu innihaldi",
+
+// About Dialog
+DlgAboutAboutTab : "Um",
+DlgAboutBrowserInfoTab : "Almennt",
+DlgAboutLicenseTab : "Leyfi",
+DlgAboutVersion : "útgáfa",
+DlgAboutInfo : "Nánari upplýsinar, sjá:",
+
+// Div Dialog
+DlgDivGeneralTab : "Almennt",
+DlgDivAdvancedTab : "Sérhæft",
+DlgDivStyle : "Stíll",
+DlgDivInlineStyle : "Línulægur stíll",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/it.js b/httemplate/elements/fckeditor/editor/lang/it.js
new file mode 100644
index 0000000..51b0d85
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/it.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Italian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Nascondi la barra degli strumenti",
+ToolbarExpand : "Mostra la barra degli strumenti",
+
+// Toolbar Items and Context Menu
+Save : "Salva",
+NewPage : "Nuova pagina vuota",
+Preview : "Anteprima",
+Cut : "Taglia",
+Copy : "Copia",
+Paste : "Incolla",
+PasteText : "Incolla come testo semplice",
+PasteWord : "Incolla da Word",
+Print : "Stampa",
+SelectAll : "Seleziona tutto",
+RemoveFormat : "Elimina formattazione",
+InsertLinkLbl : "Collegamento",
+InsertLink : "Inserisci/Modifica collegamento",
+RemoveLink : "Elimina collegamento",
+VisitLink : "Open Link", //MISSING
+Anchor : "Inserisci/Modifica Ancora",
+AnchorDelete : "Rimuovi Ancora",
+InsertImageLbl : "Immagine",
+InsertImage : "Inserisci/Modifica immagine",
+InsertFlashLbl : "Oggetto Flash",
+InsertFlash : "Inserisci/Modifica Oggetto Flash",
+InsertTableLbl : "Tabella",
+InsertTable : "Inserisci/Modifica tabella",
+InsertLineLbl : "Riga orizzontale",
+InsertLine : "Inserisci riga orizzontale",
+InsertSpecialCharLbl: "Caratteri speciali",
+InsertSpecialChar : "Inserisci carattere speciale",
+InsertSmileyLbl : "Emoticon",
+InsertSmiley : "Inserisci emoticon",
+About : "Informazioni su FCKeditor",
+Bold : "Grassetto",
+Italic : "Corsivo",
+Underline : "Sottolineato",
+StrikeThrough : "Barrato",
+Subscript : "Pedice",
+Superscript : "Apice",
+LeftJustify : "Allinea a sinistra",
+CenterJustify : "Centra",
+RightJustify : "Allinea a destra",
+BlockJustify : "Giustifica",
+DecreaseIndent : "Riduci rientro",
+IncreaseIndent : "Aumenta rientro",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Annulla",
+Redo : "Ripristina",
+NumberedListLbl : "Elenco numerato",
+NumberedList : "Inserisci/Modifica elenco numerato",
+BulletedListLbl : "Elenco puntato",
+BulletedList : "Inserisci/Modifica elenco puntato",
+ShowTableBorders : "Mostra bordi tabelle",
+ShowDetails : "Mostra dettagli",
+Style : "Stile",
+FontFormat : "Formato",
+Font : "Font",
+FontSize : "Dimensione",
+TextColor : "Colore testo",
+BGColor : "Colore sfondo",
+Source : "Codice Sorgente",
+Find : "Trova",
+Replace : "Sostituisci",
+SpellCheck : "Correttore ortografico",
+UniversalKeyboard : "Tastiera universale",
+PageBreakLbl : "Interruzione di pagina",
+PageBreak : "Inserisci interruzione di pagina",
+
+Form : "Modulo",
+Checkbox : "Checkbox",
+RadioButton : "Radio Button",
+TextField : "Campo di testo",
+Textarea : "Area di testo",
+HiddenField : "Campo nascosto",
+Button : "Bottone",
+SelectionField : "Menu di selezione",
+ImageButton : "Bottone immagine",
+
+FitWindow : "Massimizza l'area dell'editor",
+ShowBlocks : "Visualizza Blocchi",
+
+// Context Menu
+EditLink : "Modifica collegamento",
+CellCM : "Cella",
+RowCM : "Riga",
+ColumnCM : "Colonna",
+InsertRowAfter : "Inserisci Riga Dopo",
+InsertRowBefore : "Inserisci Riga Prima",
+DeleteRows : "Elimina righe",
+InsertColumnAfter : "Inserisci Colonna Dopo",
+InsertColumnBefore : "Inserisci Colonna Prima",
+DeleteColumns : "Elimina colonne",
+InsertCellAfter : "Inserisci Cella Dopo",
+InsertCellBefore : "Inserisci Cella Prima",
+DeleteCells : "Elimina celle",
+MergeCells : "Unisce celle",
+MergeRight : "Unisci a Destra",
+MergeDown : "Unisci in Basso",
+HorizontalSplitCell : "Dividi Cella Orizzontalmente",
+VerticalSplitCell : "Dividi Cella Verticalmente",
+TableDelete : "Cancella Tabella",
+CellProperties : "Proprietà cella",
+TableProperties : "Proprietà tabella",
+ImageProperties : "Proprietà immagine",
+FlashProperties : "Proprietà Oggetto Flash",
+
+AnchorProp : "Proprietà ancora",
+ButtonProp : "Proprietà bottone",
+CheckboxProp : "Proprietà checkbox",
+HiddenFieldProp : "Proprietà campo nascosto",
+RadioButtonProp : "Proprietà radio button",
+ImageButtonProp : "Proprietà bottone immagine",
+TextFieldProp : "Proprietà campo di testo",
+SelectionFieldProp : "Proprietà menu di selezione",
+TextareaProp : "Proprietà area di testo",
+FormProp : "Proprietà modulo",
+
+FontFormats : "Normale;Formattato;Indirizzo;Titolo 1;Titolo 2;Titolo 3;Titolo 4;Titolo 5;Titolo 6;Paragrafo (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Elaborazione XHTML in corso. Attendere prego...",
+Done : "Completato",
+PasteWordConfirm : "Il testo da incollare sembra provenire da Word. Desideri pulirlo prima di incollare?",
+NotCompatiblePaste : "Questa funzione è disponibile solo per Internet Explorer 5.5 o superiore. Desideri incollare il testo senza pulirlo?",
+UnknownToolbarItem : "Elemento della barra strumenti sconosciuto \"%1\"",
+UnknownCommand : "Comando sconosciuto \"%1\"",
+NotImplemented : "Comando non implementato",
+UnknownToolbarSet : "La barra di strumenti \"%1\" non esiste",
+NoActiveX : "Le impostazioni di sicurezza del tuo browser potrebbero limitare alcune funzionalità dell'editor. Devi abilitare l'opzione \"Esegui controlli e plug-in ActiveX\". Potresti avere errori e notare funzionalità mancanti.",
+BrowseServerBlocked : "Non è possibile aprire la finestra di espolorazione risorse. Verifica che tutti i blocca popup siano bloccati.",
+DialogBlocked : "Non è possibile aprire la finestra di dialogo. Verifica che tutti i blocca popup siano bloccati.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Annulla",
+DlgBtnClose : "Chiudi",
+DlgBtnBrowseServer : "Cerca sul server",
+DlgAdvancedTag : "Avanzate",
+DlgOpOther : "<Altro>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Devi inserire l'URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<non impostato>",
+DlgGenId : "Id",
+DlgGenLangDir : "Direzione scrittura",
+DlgGenLangDirLtr : "Da Sinistra a Destra (LTR)",
+DlgGenLangDirRtl : "Da Destra a Sinistra (RTL)",
+DlgGenLangCode : "Codice Lingua",
+DlgGenAccessKey : "Scorciatoia<br />da tastiera",
+DlgGenName : "Nome",
+DlgGenTabIndex : "Ordine di tabulazione",
+DlgGenLongDescr : "URL descrizione estesa",
+DlgGenClass : "Nome classe CSS",
+DlgGenTitle : "Titolo",
+DlgGenContType : "Tipo della risorsa collegata",
+DlgGenLinkCharset : "Set di caretteri della risorsa collegata",
+DlgGenStyle : "Stile",
+
+// Image Dialog
+DlgImgTitle : "Proprietà immagine",
+DlgImgInfoTab : "Informazioni immagine",
+DlgImgBtnUpload : "Invia al server",
+DlgImgURL : "URL",
+DlgImgUpload : "Carica",
+DlgImgAlt : "Testo alternativo",
+DlgImgWidth : "Larghezza",
+DlgImgHeight : "Altezza",
+DlgImgLockRatio : "Blocca rapporto",
+DlgBtnResetSize : "Reimposta dimensione",
+DlgImgBorder : "Bordo",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Allineamento",
+DlgImgAlignLeft : "Sinistra",
+DlgImgAlignAbsBottom: "In basso assoluto",
+DlgImgAlignAbsMiddle: "Centrato assoluto",
+DlgImgAlignBaseline : "Linea base",
+DlgImgAlignBottom : "In Basso",
+DlgImgAlignMiddle : "Centrato",
+DlgImgAlignRight : "Destra",
+DlgImgAlignTextTop : "In alto al testo",
+DlgImgAlignTop : "In Alto",
+DlgImgPreview : "Anteprima",
+DlgImgAlertUrl : "Devi inserire l'URL per l'immagine",
+DlgImgLinkTab : "Collegamento",
+
+// Flash Dialog
+DlgFlashTitle : "Proprietà Oggetto Flash",
+DlgFlashChkPlay : "Avvio Automatico",
+DlgFlashChkLoop : "Cicla",
+DlgFlashChkMenu : "Abilita Menu di Flash",
+DlgFlashScale : "Ridimensiona",
+DlgFlashScaleAll : "Mostra Tutto",
+DlgFlashScaleNoBorder : "Senza Bordo",
+DlgFlashScaleFit : "Dimensione Esatta",
+
+// Link Dialog
+DlgLnkWindowTitle : "Collegamento",
+DlgLnkInfoTab : "Informazioni collegamento",
+DlgLnkTargetTab : "Destinazione",
+
+DlgLnkType : "Tipo di Collegamento",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ancora nella pagina",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocollo",
+DlgLnkProtoOther : "<altro>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Scegli Ancora",
+DlgLnkAnchorByName : "Per Nome",
+DlgLnkAnchorById : "Per id elemento",
+DlgLnkNoAnchors : "(Nessuna ancora disponibile nel documento)",
+DlgLnkEMail : "Indirizzo E-Mail",
+DlgLnkEMailSubject : "Oggetto del messaggio",
+DlgLnkEMailBody : "Corpo del messaggio",
+DlgLnkUpload : "Carica",
+DlgLnkBtnUpload : "Invia al Server",
+
+DlgLnkTarget : "Destinazione",
+DlgLnkTargetFrame : "<riquadro>",
+DlgLnkTargetPopup : "<finestra popup>",
+DlgLnkTargetBlank : "Nuova finestra (_blank)",
+DlgLnkTargetParent : "Finestra padre (_parent)",
+DlgLnkTargetSelf : "Stessa finestra (_self)",
+DlgLnkTargetTop : "Finestra superiore (_top)",
+DlgLnkTargetFrameName : "Nome del riquadro di destinazione",
+DlgLnkPopWinName : "Nome finestra popup",
+DlgLnkPopWinFeat : "Caratteristiche finestra popup",
+DlgLnkPopResize : "Ridimensionabile",
+DlgLnkPopLocation : "Barra degli indirizzi",
+DlgLnkPopMenu : "Barra del menu",
+DlgLnkPopScroll : "Barre di scorrimento",
+DlgLnkPopStatus : "Barra di stato",
+DlgLnkPopToolbar : "Barra degli strumenti",
+DlgLnkPopFullScrn : "A tutto schermo (IE)",
+DlgLnkPopDependent : "Dipendente (Netscape)",
+DlgLnkPopWidth : "Larghezza",
+DlgLnkPopHeight : "Altezza",
+DlgLnkPopLeft : "Posizione da sinistra",
+DlgLnkPopTop : "Posizione dall'alto",
+
+DlnLnkMsgNoUrl : "Devi inserire l'URL del collegamento",
+DlnLnkMsgNoEMail : "Devi inserire un'indirizzo e-mail",
+DlnLnkMsgNoAnchor : "Devi selezionare un'ancora",
+DlnLnkMsgInvPopName : "Il nome del popup deve iniziare con una lettera, e non può contenere spazi",
+
+// Color Dialog
+DlgColorTitle : "Seleziona colore",
+DlgColorBtnClear : "Vuota",
+DlgColorHighlight : "Evidenziato",
+DlgColorSelected : "Selezionato",
+
+// Smiley Dialog
+DlgSmileyTitle : "Inserisci emoticon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Seleziona carattere speciale",
+
+// Table Dialog
+DlgTableTitle : "Proprietà tabella",
+DlgTableRows : "Righe",
+DlgTableColumns : "Colonne",
+DlgTableBorder : "Dimensione bordo",
+DlgTableAlign : "Allineamento",
+DlgTableAlignNotSet : "<non impostato>",
+DlgTableAlignLeft : "Sinistra",
+DlgTableAlignCenter : "Centrato",
+DlgTableAlignRight : "Destra",
+DlgTableWidth : "Larghezza",
+DlgTableWidthPx : "pixel",
+DlgTableWidthPc : "percento",
+DlgTableHeight : "Altezza",
+DlgTableCellSpace : "Spaziatura celle",
+DlgTableCellPad : "Padding celle",
+DlgTableCaption : "Intestazione",
+DlgTableSummary : "Indice",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Proprietà cella",
+DlgCellWidth : "Larghezza",
+DlgCellWidthPx : "pixel",
+DlgCellWidthPc : "percento",
+DlgCellHeight : "Altezza",
+DlgCellWordWrap : "A capo automatico",
+DlgCellWordWrapNotSet : "<non impostato>",
+DlgCellWordWrapYes : "Si",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "Allineamento orizzontale",
+DlgCellHorAlignNotSet : "<non impostato>",
+DlgCellHorAlignLeft : "Sinistra",
+DlgCellHorAlignCenter : "Centrato",
+DlgCellHorAlignRight: "Destra",
+DlgCellVerAlign : "Allineamento verticale",
+DlgCellVerAlignNotSet : "<non impostato>",
+DlgCellVerAlignTop : "In Alto",
+DlgCellVerAlignMiddle : "Centrato",
+DlgCellVerAlignBottom : "In Basso",
+DlgCellVerAlignBaseline : "Linea base",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Righe occupate",
+DlgCellCollSpan : "Colonne occupate",
+DlgCellBackColor : "Colore sfondo",
+DlgCellBorderColor : "Colore bordo",
+DlgCellBtnSelect : "Scegli...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Cerca e Sostituisci",
+
+// Find Dialog
+DlgFindTitle : "Trova",
+DlgFindFindBtn : "Trova",
+DlgFindNotFoundMsg : "L'elemento cercato non è stato trovato.",
+
+// Replace Dialog
+DlgReplaceTitle : "Sostituisci",
+DlgReplaceFindLbl : "Trova:",
+DlgReplaceReplaceLbl : "Sostituisci con:",
+DlgReplaceCaseChk : "Maiuscole/minuscole",
+DlgReplaceReplaceBtn : "Sostituisci",
+DlgReplaceReplAllBtn : "Sostituisci tutto",
+DlgReplaceWordChk : "Solo parole intere",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Le impostazioni di sicurezza del browser non permettono di tagliare automaticamente il testo. Usa la tastiera (Ctrl+X).",
+PasteErrorCopy : "Le impostazioni di sicurezza del browser non permettono di copiare automaticamente il testo. Usa la tastiera (Ctrl+C).",
+
+PasteAsText : "Incolla come testo semplice",
+PasteFromWord : "Incolla da Word",
+
+DlgPasteMsg2 : "Incolla il testo all'interno dell'area sottostante usando la scorciatoia di tastiere (<STRONG>Ctrl+V</STRONG>) e premi <STRONG>OK</STRONG>.",
+DlgPasteSec : "A causa delle impostazioni di sicurezza del browser,l'editor non è in grado di accedere direttamente agli appunti. E' pertanto necessario incollarli di nuovo in questa finestra.",
+DlgPasteIgnoreFont : "Ignora le definizioni di Font",
+DlgPasteRemoveStyles : "Rimuovi le definizioni di Stile",
+
+// Color Picker
+ColorAutomatic : "Automatico",
+ColorMoreColors : "Altri colori...",
+
+// Document Properties
+DocProps : "Proprietà del Documento",
+
+// Anchor Dialog
+DlgAnchorTitle : "Proprietà ancora",
+DlgAnchorName : "Nome ancora",
+DlgAnchorErrorName : "Inserici il nome dell'ancora",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Non nel dizionario",
+DlgSpellChangeTo : "Cambia in",
+DlgSpellBtnIgnore : "Ignora",
+DlgSpellBtnIgnoreAll : "Ignora tutto",
+DlgSpellBtnReplace : "Cambia",
+DlgSpellBtnReplaceAll : "Cambia tutto",
+DlgSpellBtnUndo : "Annulla",
+DlgSpellNoSuggestions : "- Nessun suggerimento -",
+DlgSpellProgress : "Controllo ortografico in corso",
+DlgSpellNoMispell : "Controllo ortografico completato: nessun errore trovato",
+DlgSpellNoChanges : "Controllo ortografico completato: nessuna parola cambiata",
+DlgSpellOneChange : "Controllo ortografico completato: 1 parola cambiata",
+DlgSpellManyChanges : "Controllo ortografico completato: %1 parole cambiate",
+
+IeSpellDownload : "Contollo ortografico non installato. Lo vuoi scaricare ora?",
+
+// Button Dialog
+DlgButtonText : "Testo (Value)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Bottone",
+DlgButtonTypeSbm : "Invio",
+DlgButtonTypeRst : "Annulla",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nome",
+DlgCheckboxValue : "Valore",
+DlgCheckboxSelected : "Selezionato",
+
+// Form Dialog
+DlgFormName : "Nome",
+DlgFormAction : "Azione",
+DlgFormMethod : "Metodo",
+
+// Select Field Dialog
+DlgSelectName : "Nome",
+DlgSelectValue : "Valore",
+DlgSelectSize : "Dimensione",
+DlgSelectLines : "righe",
+DlgSelectChkMulti : "Permetti selezione multipla",
+DlgSelectOpAvail : "Opzioni disponibili",
+DlgSelectOpText : "Testo",
+DlgSelectOpValue : "Valore",
+DlgSelectBtnAdd : "Aggiungi",
+DlgSelectBtnModify : "Modifica",
+DlgSelectBtnUp : "Su",
+DlgSelectBtnDown : "Gi",
+DlgSelectBtnSetValue : "Imposta come predefinito",
+DlgSelectBtnDelete : "Rimuovi",
+
+// Textarea Dialog
+DlgTextareaName : "Nome",
+DlgTextareaCols : "Colonne",
+DlgTextareaRows : "Righe",
+
+// Text Field Dialog
+DlgTextName : "Nome",
+DlgTextValue : "Valore",
+DlgTextCharWidth : "Larghezza",
+DlgTextMaxChars : "Numero massimo di caratteri",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Testo",
+DlgTextTypePass : "Password",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nome",
+DlgHiddenValue : "Valore",
+
+// Bulleted List Dialog
+BulletedListProp : "Proprietà lista puntata",
+NumberedListProp : "Proprietà lista numerata",
+DlgLstStart : "Inizio",
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Tondo",
+DlgLstTypeDisc : "Disco",
+DlgLstTypeSquare : "Quadrato",
+DlgLstTypeNumbers : "Numeri (1, 2, 3)",
+DlgLstTypeLCase : "Caratteri minuscoli (a, b, c)",
+DlgLstTypeUCase : "Caratteri maiuscoli (A, B, C)",
+DlgLstTypeSRoman : "Numeri Romani minuscoli (i, ii, iii)",
+DlgLstTypeLRoman : "Numeri Romani maiuscoli (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Genarale",
+DlgDocBackTab : "Sfondo",
+DlgDocColorsTab : "Colori e margini",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Titolo pagina",
+DlgDocLangDir : "Direzione scrittura",
+DlgDocLangDirLTR : "Da Sinistra a Destra (LTR)",
+DlgDocLangDirRTL : "Da Destra a Sinistra (RTL)",
+DlgDocLangCode : "Codice Lingua",
+DlgDocCharSet : "Set di caretteri",
+DlgDocCharSetCE : "Europa Centrale",
+DlgDocCharSetCT : "Cinese Tradizionale (Big5)",
+DlgDocCharSetCR : "Cirillico",
+DlgDocCharSetGR : "Greco",
+DlgDocCharSetJP : "Giapponese",
+DlgDocCharSetKR : "Coreano",
+DlgDocCharSetTR : "Turco",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Europa Occidentale",
+DlgDocCharSetOther : "Altro set di caretteri",
+
+DlgDocDocType : "Intestazione DocType",
+DlgDocDocTypeOther : "Altra intestazione DocType",
+DlgDocIncXHTML : "Includi dichiarazione XHTML",
+DlgDocBgColor : "Colore di sfondo",
+DlgDocBgImage : "Immagine di sfondo",
+DlgDocBgNoScroll : "Sfondo fissato",
+DlgDocCText : "Testo",
+DlgDocCLink : "Collegamento",
+DlgDocCVisited : "Collegamento visitato",
+DlgDocCActive : "Collegamento attivo",
+DlgDocMargins : "Margini",
+DlgDocMaTop : "In Alto",
+DlgDocMaLeft : "A Sinistra",
+DlgDocMaRight : "A Destra",
+DlgDocMaBottom : "In Basso",
+DlgDocMeIndex : "Chiavi di indicizzazione documento (separate da virgola)",
+DlgDocMeDescr : "Descrizione documento",
+DlgDocMeAuthor : "Autore",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Anteprima",
+
+// Templates Dialog
+Templates : "Modelli",
+DlgTemplatesTitle : "Contenuto dei modelli",
+DlgTemplatesSelMsg : "Seleziona il modello da aprire nell'editor<br />(il contenuto attuale verrà eliminato):",
+DlgTemplatesLoading : "Caricamento modelli in corso. Attendere prego...",
+DlgTemplatesNoTpl : "(Nessun modello definito)",
+DlgTemplatesReplace : "Cancella il contenuto corrente",
+
+// About Dialog
+DlgAboutAboutTab : "Informazioni",
+DlgAboutBrowserInfoTab : "Informazioni Browser",
+DlgAboutLicenseTab : "Licenza",
+DlgAboutVersion : "versione",
+DlgAboutInfo : "Per maggiori informazioni visitare",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ja.js b/httemplate/elements/fckeditor/editor/lang/ja.js
new file mode 100644
index 0000000..b7dde04
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ja.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Japanese language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "ツールãƒãƒ¼ã‚’éš ã™",
+ToolbarExpand : "ツールãƒãƒ¼ã‚’表示",
+
+// Toolbar Items and Context Menu
+Save : "ä¿å­˜",
+NewPage : "æ–°ã—ã„ページ",
+Preview : "プレビュー",
+Cut : "切りå–ã‚Š",
+Copy : "コピー",
+Paste : "貼り付ã‘",
+PasteText : "プレーンテキスト貼り付ã‘",
+PasteWord : "ワード文章ã‹ã‚‰è²¼ã‚Šä»˜ã‘",
+Print : "å°åˆ·",
+SelectAll : "ã™ã¹ã¦é¸æŠž",
+RemoveFormat : "フォーマット削除",
+InsertLinkLbl : "リンク",
+InsertLink : "リンク挿入/編集",
+RemoveLink : "リンク削除",
+VisitLink : "リンクを開ã",
+Anchor : "アンカー挿入/編集",
+AnchorDelete : "アンカー削除",
+InsertImageLbl : "イメージ",
+InsertImage : "イメージ挿入/編集",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash挿入/編集",
+InsertTableLbl : "テーブル",
+InsertTable : "テーブル挿入/編集",
+InsertLineLbl : "ライン",
+InsertLine : "横罫線",
+InsertSpecialCharLbl: "特殊文字",
+InsertSpecialChar : "特殊文字挿入",
+InsertSmileyLbl : "絵文字",
+InsertSmiley : "絵文字挿入",
+About : "FCKeditorヘルプ",
+Bold : "太字",
+Italic : "斜体",
+Underline : "下線",
+StrikeThrough : "打ã¡æ¶ˆã—ç·š",
+Subscript : "æ·»ãˆå­—",
+Superscript : "上付ã文字",
+LeftJustify : "å·¦æƒãˆ",
+CenterJustify : "中央æƒãˆ",
+RightJustify : "å³æƒãˆ",
+BlockJustify : "両端æƒãˆ",
+DecreaseIndent : "インデント解除",
+IncreaseIndent : "インデント",
+Blockquote : "ブロック引用",
+CreateDiv : "Div 作æˆ",
+EditDiv : "Div 編集",
+DeleteDiv : "Div 削除",
+Undo : "å…ƒã«æˆ»ã™",
+Redo : "ã‚„ã‚Šç›´ã—",
+NumberedListLbl : "段è½ç•ªå·",
+NumberedList : "段è½ç•ªå·ã®è¿½åŠ /削除",
+BulletedListLbl : "箇æ¡æ›¸ã",
+BulletedList : "箇æ¡æ›¸ãã®è¿½åŠ /削除",
+ShowTableBorders : "テーブルボーダー表示",
+ShowDetails : "詳細表示",
+Style : "スタイル",
+FontFormat : "フォーマット",
+Font : "フォント",
+FontSize : "サイズ",
+TextColor : "テキスト色",
+BGColor : "背景色",
+Source : "ソース",
+Find : "検索",
+Replace : "ç½®ãæ›ãˆ",
+SpellCheck : "スペルãƒã‚§ãƒƒã‚¯",
+UniversalKeyboard : "ユニãƒãƒ¼ã‚µãƒ«ãƒ»ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰",
+PageBreakLbl : "改ページ",
+PageBreak : "改ページ挿入",
+
+Form : "フォーム",
+Checkbox : "ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹",
+RadioButton : "ラジオボタン",
+TextField : "1行テキスト",
+Textarea : "テキストエリア",
+HiddenField : "ä¸å¯è¦–フィールド",
+Button : "ボタン",
+SelectionField : "é¸æŠžãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰",
+ImageButton : "ç”»åƒãƒœã‚¿ãƒ³",
+
+FitWindow : "エディタサイズを最大ã«ã—ã¾ã™",
+ShowBlocks : "ブロック表示",
+
+// Context Menu
+EditLink : "リンク編集",
+CellCM : "セル",
+RowCM : "行",
+ColumnCM : "カラム",
+InsertRowAfter : "列ã®å¾Œã«æŒ¿å…¥",
+InsertRowBefore : "列ã®å‰ã«æŒ¿å…¥",
+DeleteRows : "行削除",
+InsertColumnAfter : "カラムã®å¾Œã«æŒ¿å…¥",
+InsertColumnBefore : "カラムã®å‰ã«æŒ¿å…¥",
+DeleteColumns : "列削除",
+InsertCellAfter : "セルã®å¾Œã«æŒ¿å…¥",
+InsertCellBefore : "セルã®å‰ã«æŒ¿å…¥",
+DeleteCells : "セル削除",
+MergeCells : "セルçµåˆ",
+MergeRight : "å³ã«çµåˆ",
+MergeDown : "下ã«çµåˆ",
+HorizontalSplitCell : "セルを水平方å‘分割",
+VerticalSplitCell : "セルを垂直方å‘ã«åˆ†å‰²",
+TableDelete : "テーブル削除",
+CellProperties : "セル プロパティ",
+TableProperties : "テーブル プロパティ",
+ImageProperties : "イメージ プロパティ",
+FlashProperties : "Flash プロパティ",
+
+AnchorProp : "アンカー プロパティ",
+ButtonProp : "ボタン プロパティ",
+CheckboxProp : "ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ プロパティ",
+HiddenFieldProp : "ä¸å¯è¦–フィールド プロパティ",
+RadioButtonProp : "ラジオボタン プロパティ",
+ImageButtonProp : "ç”»åƒãƒœã‚¿ãƒ³ プロパティ",
+TextFieldProp : "1行テキスト プロパティ",
+SelectionFieldProp : "é¸æŠžãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ プロパティ",
+TextareaProp : "テキストエリア プロパティ",
+FormProp : "フォーム プロパティ",
+
+FontFormats : "標準;書å¼ä»˜ã;アドレス;見出㗠1;見出㗠2;見出㗠3;見出㗠4;見出㗠5;見出㗠6;標準 (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML処ç†ä¸­. ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„...",
+Done : "完了",
+PasteWordConfirm : "貼り付ã‘ã‚’è¡Œã†ãƒ†ã‚­ã‚¹ãƒˆã¯ã€ãƒ¯ãƒ¼ãƒ‰æ–‡ç« ã‹ã‚‰ã‚³ãƒ”ーã•ã‚Œã‚ˆã†ã¨ã—ã¦ã„ã¾ã™ã€‚貼り付ã‘ã‚‹å‰ã«ã‚¯ãƒªãƒ¼ãƒ‹ãƒ³ã‚°ã‚’è¡Œã„ã¾ã™ã‹ï¼Ÿ",
+NotCompatiblePaste : "ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆãƒ»ã‚¨ã‚¯ã‚¹ãƒ—ローラーãƒãƒ¼ã‚¸ãƒ§ãƒ³5.5以上ã§åˆ©ç”¨å¯èƒ½ã§ã™ã€‚クリーニングã—ãªã„ã§è²¼ã‚Šä»˜ã‘ã‚’è¡Œã„ã¾ã™ã‹ï¼Ÿ",
+UnknownToolbarItem : "未知ã®ãƒ„ールãƒãƒ¼é …ç›® \"%1\"",
+UnknownCommand : "未知ã®ã‚³ãƒžãƒ³ãƒ‰å \"%1\"",
+NotImplemented : "コマンドã¯ã‚¤ãƒ³ãƒ—リメントã•ã‚Œã¾ã›ã‚“ã§ã—ãŸã€‚",
+UnknownToolbarSet : "ツールãƒãƒ¼è¨­å®š \"%1\" 存在ã—ã¾ã›ã‚“。",
+NoActiveX : "エラーã€è­¦å‘Šãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãªã©ãŒç™ºç”Ÿã—ãŸå ´åˆã€ãƒ–ラウザーã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã‚¨ãƒ‡ã‚£ã‚¿ã®ã„ãã¤ã‹ã®æ©Ÿèƒ½ãŒåˆ¶é™ã•ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚セキュリティ設定ã®ã‚ªãƒ—ションã§\"ActiveXコントロールã¨ãƒ—ラグインã®å®Ÿè¡Œ\"を有効ã«ã™ã‚‹ã«ã—ã¦ãã ã•ã„。",
+BrowseServerBlocked : "サーãƒãƒ¼ãƒ–ラウザーを開ãã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãƒãƒƒãƒ—アップ・ブロック機能ãŒç„¡åŠ¹ã«ãªã£ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„。",
+DialogBlocked : "ダイアログウィンドウを開ãã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãƒãƒƒãƒ—アップ・ブロック機能ãŒç„¡åŠ¹ã«ãªã£ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„。",
+VisitLinkBlocked : "æ–°ã—ã„ウィンドウを開ãã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ãƒãƒƒãƒ—アップ・ブロック機能ãŒç„¡åŠ¹ã«ãªã£ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„。",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "キャンセル",
+DlgBtnClose : "é–‰ã˜ã‚‹",
+DlgBtnBrowseServer : "サーãƒãƒ¼ãƒ–ラウザー",
+DlgAdvancedTag : "高度ãªè¨­å®š",
+DlgOpOther : "<ãã®ä»–>",
+DlgInfoTab : "情報",
+DlgAlertUrl : "URLを挿入ã—ã¦ãã ã•ã„",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ãªã—>",
+DlgGenId : "Id",
+DlgGenLangDir : "文字表記ã®æ–¹å‘",
+DlgGenLangDirLtr : "å·¦ã‹ã‚‰å³ (LTR)",
+DlgGenLangDirRtl : "å³ã‹ã‚‰å·¦ (RTL)",
+DlgGenLangCode : "言語コード",
+DlgGenAccessKey : "アクセスキー",
+DlgGenName : "Name属性",
+DlgGenTabIndex : "タブインデックス",
+DlgGenLongDescr : "longdesc属性(長文説明)",
+DlgGenClass : "スタイルシートクラス",
+DlgGenTitle : "Title属性",
+DlgGenContType : "Content Type属性",
+DlgGenLinkCharset : "リンクcharset属性",
+DlgGenStyle : "スタイルシート",
+
+// Image Dialog
+DlgImgTitle : "イメージ プロパティ",
+DlgImgInfoTab : "イメージ 情報",
+DlgImgBtnUpload : "サーãƒãƒ¼ã«é€ä¿¡",
+DlgImgURL : "URL",
+DlgImgUpload : "アップロード",
+DlgImgAlt : "代替テキスト",
+DlgImgWidth : "å¹…",
+DlgImgHeight : "高ã•",
+DlgImgLockRatio : "ロック比率",
+DlgBtnResetSize : "サイズリセット",
+DlgImgBorder : "ボーダー",
+DlgImgHSpace : "横間隔",
+DlgImgVSpace : "縦間隔",
+DlgImgAlign : "è¡Œæƒãˆ",
+DlgImgAlignLeft : "å·¦",
+DlgImgAlignAbsBottom: "下部(絶対的)",
+DlgImgAlignAbsMiddle: "中央(絶対的)",
+DlgImgAlignBaseline : "ベースライン",
+DlgImgAlignBottom : "下",
+DlgImgAlignMiddle : "中央",
+DlgImgAlignRight : "å³",
+DlgImgAlignTextTop : "テキスト上部",
+DlgImgAlignTop : "上",
+DlgImgPreview : "プレビュー",
+DlgImgAlertUrl : "イメージã®URLを入力ã—ã¦ãã ã•ã„。",
+DlgImgLinkTab : "リンク",
+
+// Flash Dialog
+DlgFlashTitle : "Flash プロパティ",
+DlgFlashChkPlay : "å†ç”Ÿ",
+DlgFlashChkLoop : "ループå†ç”Ÿ",
+DlgFlashChkMenu : "Flashメニューå¯èƒ½",
+DlgFlashScale : "拡大縮å°è¨­å®š",
+DlgFlashScaleAll : "ã™ã¹ã¦è¡¨ç¤º",
+DlgFlashScaleNoBorder : "外ãŒè¦‹ãˆãªã„様ã«æ‹¡å¤§",
+DlgFlashScaleFit : "上下左å³ã«ãƒ•ã‚£ãƒƒãƒˆ",
+
+// Link Dialog
+DlgLnkWindowTitle : "ãƒã‚¤ãƒ‘ーリンク",
+DlgLnkInfoTab : "ãƒã‚¤ãƒ‘ーリンク 情報",
+DlgLnkTargetTab : "ターゲット",
+
+DlgLnkType : "リンクタイプ",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "ã“ã®ãƒšãƒ¼ã‚¸ã®ã‚¢ãƒ³ã‚«ãƒ¼",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "プロトコル",
+DlgLnkProtoOther : "<ãã®ä»–>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "アンカーをé¸æŠž",
+DlgLnkAnchorByName : "アンカーå",
+DlgLnkAnchorById : "エレメントID",
+DlgLnkNoAnchors : "(ドキュメントã«ãŠã„ã¦åˆ©ç”¨å¯èƒ½ãªã‚¢ãƒ³ã‚«ãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“。)",
+DlgLnkEMail : "E-Mail アドレス",
+DlgLnkEMailSubject : "件å",
+DlgLnkEMailBody : "本文",
+DlgLnkUpload : "アップロード",
+DlgLnkBtnUpload : "サーãƒãƒ¼ã«é€ä¿¡",
+
+DlgLnkTarget : "ターゲット",
+DlgLnkTargetFrame : "<フレーム>",
+DlgLnkTargetPopup : "<ãƒãƒƒãƒ—アップウィンドウ>",
+DlgLnkTargetBlank : "æ–°ã—ã„ウィンドウ (_blank)",
+DlgLnkTargetParent : "親ウィンドウ (_parent)",
+DlgLnkTargetSelf : "åŒã˜ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ (_self)",
+DlgLnkTargetTop : "最上ä½ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ (_top)",
+DlgLnkTargetFrameName : "目的ã®ãƒ•ãƒ¬ãƒ¼ãƒ å",
+DlgLnkPopWinName : "ãƒãƒƒãƒ—アップウィンドウå",
+DlgLnkPopWinFeat : "ãƒãƒƒãƒ—アップウィンドウ特徴",
+DlgLnkPopResize : "リサイズå¯èƒ½",
+DlgLnkPopLocation : "ロケーションãƒãƒ¼",
+DlgLnkPopMenu : "メニューãƒãƒ¼",
+DlgLnkPopScroll : "スクロールãƒãƒ¼",
+DlgLnkPopStatus : "ステータスãƒãƒ¼",
+DlgLnkPopToolbar : "ツールãƒãƒ¼",
+DlgLnkPopFullScrn : "全画é¢ãƒ¢ãƒ¼ãƒ‰(IE)",
+DlgLnkPopDependent : "é–‹ã„ãŸã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã«é€£å‹•ã—ã¦é–‰ã˜ã‚‹ (Netscape)",
+DlgLnkPopWidth : "å¹…",
+DlgLnkPopHeight : "高ã•",
+DlgLnkPopLeft : "左端ã‹ã‚‰ã®åº§æ¨™ã§æŒ‡å®š",
+DlgLnkPopTop : "上端ã‹ã‚‰ã®åº§æ¨™ã§æŒ‡å®š",
+
+DlnLnkMsgNoUrl : "リンクURLを入力ã—ã¦ãã ã•ã„。",
+DlnLnkMsgNoEMail : "メールアドレスを入力ã—ã¦ãã ã•ã„。",
+DlnLnkMsgNoAnchor : "アンカーをé¸æŠžã—ã¦ãã ã•ã„。",
+DlnLnkMsgInvPopName : "ãƒãƒƒãƒ—・アップåã¯è‹±å­—ã§å§‹ã¾ã‚‹æ–‡å­—ã§æŒ‡å®šã—ã¦ãã ã„。ãƒãƒƒãƒ—・アップåã«ã‚¹ãƒšãƒ¼ã‚¹ã¯å«ã‚ã¾ã›ã‚“",
+
+// Color Dialog
+DlgColorTitle : "色é¸æŠž",
+DlgColorBtnClear : "クリア",
+DlgColorHighlight : "ãƒã‚¤ãƒ©ã‚¤ãƒˆ",
+DlgColorSelected : "é¸æŠžè‰²",
+
+// Smiley Dialog
+DlgSmileyTitle : "顔文字挿入",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "特殊文字é¸æŠž",
+
+// Table Dialog
+DlgTableTitle : "テーブル プロパティ",
+DlgTableRows : "行",
+DlgTableColumns : "列",
+DlgTableBorder : "ボーダーサイズ",
+DlgTableAlign : "キャプションã®æ•´åˆ—",
+DlgTableAlignNotSet : "<ãªã—>",
+DlgTableAlignLeft : "å·¦",
+DlgTableAlignCenter : "中央",
+DlgTableAlignRight : "å³",
+DlgTableWidth : "テーブル幅",
+DlgTableWidthPx : "ピクセル",
+DlgTableWidthPc : "パーセント",
+DlgTableHeight : "テーブル高ã•",
+DlgTableCellSpace : "セル内余白",
+DlgTableCellPad : "セル内間隔",
+DlgTableCaption : "キャプショï¾",
+DlgTableSummary : "テーブル目的/構造",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "セル プロパティ",
+DlgCellWidth : "å¹…",
+DlgCellWidthPx : "ピクセル",
+DlgCellWidthPc : "パーセント",
+DlgCellHeight : "高ã•",
+DlgCellWordWrap : "折り返ã—",
+DlgCellWordWrapNotSet : "<ãªã—>",
+DlgCellWordWrapYes : "Yes",
+DlgCellWordWrapNo : "No",
+DlgCellHorAlign : "セル横ã®æ•´åˆ—",
+DlgCellHorAlignNotSet : "<ãªã—>",
+DlgCellHorAlignLeft : "å·¦",
+DlgCellHorAlignCenter : "中央",
+DlgCellHorAlignRight: "å³",
+DlgCellVerAlign : "セル縦ã®æ•´åˆ—",
+DlgCellVerAlignNotSet : "<ãªã—>",
+DlgCellVerAlignTop : "上",
+DlgCellVerAlignMiddle : "中央",
+DlgCellVerAlignBottom : "下",
+DlgCellVerAlignBaseline : "ベースライン",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "縦幅(行数)",
+DlgCellCollSpan : "横幅(列数)",
+DlgCellBackColor : "背景色",
+DlgCellBorderColor : "ボーダーカラー",
+DlgCellBtnSelect : "é¸æŠž...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "検索ã—ã¦ç½®æ›",
+
+// Find Dialog
+DlgFindTitle : "検索",
+DlgFindFindBtn : "検索",
+DlgFindNotFoundMsg : "指定ã•ã‚ŒãŸæ–‡å­—列ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚",
+
+// Replace Dialog
+DlgReplaceTitle : "ç½®ãæ›ãˆ",
+DlgReplaceFindLbl : "検索ã™ã‚‹æ–‡å­—列:",
+DlgReplaceReplaceLbl : "ç½®æ›ãˆã™ã‚‹æ–‡å­—列:",
+DlgReplaceCaseChk : "部分一致",
+DlgReplaceReplaceBtn : "ç½®æ›ãˆ",
+DlgReplaceReplAllBtn : "ã™ã¹ã¦ç½®æ›ãˆ",
+DlgReplaceWordChk : "å˜èªžå˜ä½ã§ä¸€è‡´",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ブラウザーã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã‚¨ãƒ‡ã‚£ã‚¿ã®åˆ‡ã‚Šå–ã‚Šæ“作ãŒè‡ªå‹•ã§å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。実行ã™ã‚‹ã«ã¯æ‰‹å‹•ã§ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã®(Ctrl+X)を使用ã—ã¦ãã ã•ã„。",
+PasteErrorCopy : "ブラウザーã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã‚¨ãƒ‡ã‚£ã‚¿ã®ã‚³ãƒ”ーæ“作ãŒè‡ªå‹•ã§å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。実行ã™ã‚‹ã«ã¯æ‰‹å‹•ã§ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã®(Ctrl+C)を使用ã—ã¦ãã ã•ã„。",
+
+PasteAsText : "プレーンテキスト貼り付ã‘",
+PasteFromWord : "ワード文章ã‹ã‚‰è²¼ã‚Šä»˜ã‘",
+
+DlgPasteMsg2 : "キーボード(<STRONG>Ctrl+V</STRONG>)を使用ã—ã¦ã€æ¬¡ã®å…¥åŠ›ã‚¨ãƒªã‚¢å†…ã§è²¼ã£ã¦ã€<STRONG>OK</STRONG>を押ã—ã¦ãã ã•ã„。",
+DlgPasteSec : "ブラウザã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã€ã‚¨ãƒ‡ã‚£ã‚¿ã¯ã‚¯ãƒªãƒƒãƒ—ボード・データã«ç›´æŽ¥ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。ã“ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã¯è²¼ã‚Šä»˜ã‘æ“作を行ã†åº¦ã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚",
+DlgPasteIgnoreFont : "Fontã‚¿ã‚°ã®Face属性を無視ã—ã¾ã™ã€‚",
+DlgPasteRemoveStyles : "スタイル定義を削除ã—ã¾ã™ã€‚",
+
+// Color Picker
+ColorAutomatic : "自動",
+ColorMoreColors : "ãã®ä»–ã®è‰²...",
+
+// Document Properties
+DocProps : "文書 プロパティ",
+
+// Anchor Dialog
+DlgAnchorTitle : "アンカー プロパティ",
+DlgAnchorName : "アンカーå",
+DlgAnchorErrorName : "アンカーåã‚’å¿…ãšå…¥åŠ›ã—ã¦ãã ã•ã„。",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "辞書ã«ã‚ã‚Šã¾ã›ã‚“",
+DlgSpellChangeTo : "変更",
+DlgSpellBtnIgnore : "無視",
+DlgSpellBtnIgnoreAll : "ã™ã¹ã¦ç„¡è¦–",
+DlgSpellBtnReplace : "ç½®æ›",
+DlgSpellBtnReplaceAll : "ã™ã¹ã¦ç½®æ›",
+DlgSpellBtnUndo : "ã‚„ã‚Šç›´ã—",
+DlgSpellNoSuggestions : "- 該当ãªã— -",
+DlgSpellProgress : "スペルãƒã‚§ãƒƒã‚¯å‡¦ç†ä¸­...",
+DlgSpellNoMispell : "スペルãƒã‚§ãƒƒã‚¯å®Œäº†: スペルã®èª¤ã‚Šã¯ã‚ã‚Šã¾ã›ã‚“ã§ã—ãŸ",
+DlgSpellNoChanges : "スペルãƒã‚§ãƒƒã‚¯å®Œäº†: 語å¥ã¯å¤‰æ›´ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ",
+DlgSpellOneChange : "スペルãƒã‚§ãƒƒã‚¯å®Œäº†: 1語å¥å¤‰æ›´ã•ã‚Œã¾ã—ãŸ",
+DlgSpellManyChanges : "スペルãƒã‚§ãƒƒã‚¯å®Œäº†: %1 語å¥å¤‰æ›´ã•ã‚Œã¾ã—ãŸ",
+
+IeSpellDownload : "スペルãƒã‚§ãƒƒã‚«ãƒ¼ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã¾ã›ã‚“。今ã™ãダウンロードã—ã¾ã™ã‹?",
+
+// Button Dialog
+DlgButtonText : "テキスト (値)",
+DlgButtonType : "タイプ",
+DlgButtonTypeBtn : "ボタン",
+DlgButtonTypeSbm : "é€ä¿¡",
+DlgButtonTypeRst : "リセット",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "åå‰",
+DlgCheckboxValue : "値",
+DlgCheckboxSelected : "é¸æŠžæ¸ˆã¿",
+
+// Form Dialog
+DlgFormName : "フォームå",
+DlgFormAction : "アクション",
+DlgFormMethod : "メソッド",
+
+// Select Field Dialog
+DlgSelectName : "åå‰",
+DlgSelectValue : "値",
+DlgSelectSize : "サイズ",
+DlgSelectLines : "行",
+DlgSelectChkMulti : "複数項目é¸æŠžã‚’許å¯",
+DlgSelectOpAvail : "利用å¯èƒ½ãªã‚ªãƒ—ション",
+DlgSelectOpText : "é¸æŠžé …ç›®å",
+DlgSelectOpValue : "é¸æŠžé …目値",
+DlgSelectBtnAdd : "追加",
+DlgSelectBtnModify : "編集",
+DlgSelectBtnUp : "上ã¸",
+DlgSelectBtnDown : "下ã¸",
+DlgSelectBtnSetValue : "é¸æŠžã—ãŸå€¤ã‚’設定",
+DlgSelectBtnDelete : "削除",
+
+// Textarea Dialog
+DlgTextareaName : "åå‰",
+DlgTextareaCols : "列",
+DlgTextareaRows : "行",
+
+// Text Field Dialog
+DlgTextName : "åå‰",
+DlgTextValue : "値",
+DlgTextCharWidth : "サイズ",
+DlgTextMaxChars : "最大長",
+DlgTextType : "タイプ",
+DlgTextTypeText : "テキスト",
+DlgTextTypePass : "パスワード入力",
+
+// Hidden Field Dialog
+DlgHiddenName : "åå‰",
+DlgHiddenValue : "値",
+
+// Bulleted List Dialog
+BulletedListProp : "箇æ¡æ›¸ã プロパティ",
+NumberedListProp : "段è½ç•ªå· プロパティ",
+DlgLstStart : "開始文字",
+DlgLstType : "タイプ",
+DlgLstTypeCircle : "白丸",
+DlgLstTypeDisc : "黒丸",
+DlgLstTypeSquare : "四角",
+DlgLstTypeNumbers : "アラビア数字 (1, 2, 3)",
+DlgLstTypeLCase : "英字å°æ–‡å­— (a, b, c)",
+DlgLstTypeUCase : "英字大文字 (A, B, C)",
+DlgLstTypeSRoman : "ローマ数字å°æ–‡å­— (i, ii, iii)",
+DlgLstTypeLRoman : "ローマ数字大文字 (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "全般",
+DlgDocBackTab : "背景",
+DlgDocColorsTab : "色ã¨ãƒžãƒ¼ã‚¸ãƒ³",
+DlgDocMetaTab : "メタデータ",
+
+DlgDocPageTitle : "ページタイトル",
+DlgDocLangDir : "言語文字表記ã®æ–¹å‘",
+DlgDocLangDirLTR : "å·¦ã‹ã‚‰å³ã«è¡¨è¨˜(LTR)",
+DlgDocLangDirRTL : "å³ã‹ã‚‰å·¦ã«è¡¨è¨˜(RTL)",
+DlgDocLangCode : "言語コード",
+DlgDocCharSet : "文字セット符å·åŒ–",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "ä»–ã®æ–‡å­—セット符å·åŒ–",
+
+DlgDocDocType : "文書タイプヘッダー",
+DlgDocDocTypeOther : "ãã®ä»–文書タイプヘッダー",
+DlgDocIncXHTML : "XHTML宣言をインクルード",
+DlgDocBgColor : "背景色",
+DlgDocBgImage : "èƒŒæ™¯ç”»åƒ URL",
+DlgDocBgNoScroll : "スクロールã—ãªã„背景",
+DlgDocCText : "テキスト",
+DlgDocCLink : "リンク",
+DlgDocCVisited : "アクセス済ã¿ãƒªãƒ³ã‚¯",
+DlgDocCActive : "アクセス中リンク",
+DlgDocMargins : "ページ・マージン",
+DlgDocMaTop : "上部",
+DlgDocMaLeft : "å·¦",
+DlgDocMaRight : "å³",
+DlgDocMaBottom : "下部",
+DlgDocMeIndex : "文書ã®ã‚­ãƒ¼ãƒ¯ãƒ¼ãƒ‰(カンマ区切り)",
+DlgDocMeDescr : "文書ã®æ¦‚è¦",
+DlgDocMeAuthor : "文書ã®ä½œè€…",
+DlgDocMeCopy : "文書ã®è‘—作権",
+DlgDocPreview : "プレビュー",
+
+// Templates Dialog
+Templates : "テンプレート(雛形)",
+DlgTemplatesTitle : "テンプレート内容",
+DlgTemplatesSelMsg : "エディターã§ä½¿ç”¨ã™ã‚‹ãƒ†ãƒ³ãƒ—レートをé¸æŠžã—ã¦ãã ã•ã„。<br>(ç¾åœ¨ã®ã‚¨ãƒ‡ã‚£ã‚¿ã®å†…容ã¯å¤±ã‚ã‚Œã¾ã™):",
+DlgTemplatesLoading : "テンプレート一覧読ã¿è¾¼ã¿ä¸­. ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„...",
+DlgTemplatesNoTpl : "(テンプレートãŒå®šç¾©ã•ã‚Œã¦ã„ã¾ã›ã‚“)",
+DlgTemplatesReplace : "ç¾åœ¨ã®ã‚¨ãƒ‡ã‚£ã‚¿ã®å†…容ã¨ç½®æ›ãˆã‚’ã—ã¾ã™",
+
+// About Dialog
+DlgAboutAboutTab : "ãƒãƒ¼ã‚¸ãƒ§ãƒ³æƒ…å ±",
+DlgAboutBrowserInfoTab : "ブラウザ情報",
+DlgAboutLicenseTab : "ライセンス",
+DlgAboutVersion : "ãƒãƒ¼ã‚¸ãƒ§ãƒ³",
+DlgAboutInfo : "より詳ã—ã„情報ã¯ã“ã¡ã‚‰ã§",
+
+// Div Dialog
+DlgDivGeneralTab : "全般",
+DlgDivAdvancedTab : "高度ãªè¨­å®š",
+DlgDivStyle : "スタイル",
+DlgDivInlineStyle : "インラインスタイル",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/km.js b/httemplate/elements/fckeditor/editor/lang/km.js
new file mode 100644
index 0000000..e0af4d0
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/km.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Khmer language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "បង្រួមរបាឧបរកណáŸ",
+ToolbarExpand : "ពង្រីករបាឧបរណáŸ",
+
+// Toolbar Items and Context Menu
+Save : "រក្សាទុក",
+NewPage : "ទំពáŸážšážáŸ’មី",
+Preview : "មើលសាកល្បង",
+Cut : "កាážáŸ‹áž™áž€",
+Copy : "ចំលងយក",
+Paste : "ចំលងដាក់",
+PasteText : "ចំលងដាក់ជាអážáŸ’ážáž”ទធម្មážáž¶",
+PasteWord : "ចំលងដាក់ពី Word",
+Print : "បោះពុម្ភ",
+SelectAll : "ជ្រើសរើសទាំងអស់",
+RemoveFormat : "លប់ចោល ការរចនា",
+InsertLinkLbl : "ឈ្នាប់",
+InsertLink : "បន្ážáŸ‚ម/កែប្រែ ឈ្នាប់",
+RemoveLink : "លប់ឈ្នាប់",
+VisitLink : "Open Link", //MISSING
+Anchor : "បន្ážáŸ‚ម/កែប្រែ យុážáŸ’កា",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "រូបភាព",
+InsertImage : "បន្ážáŸ‚ម/កែប្រែ រូបភាព",
+InsertFlashLbl : "Flash",
+InsertFlash : "បន្ážáŸ‚ម/កែប្រែ Flash",
+InsertTableLbl : "ážáž¶ážšáž¶áž„",
+InsertTable : "បន្ážáŸ‚ម/កែប្រែ ážáž¶ážšáž¶áž„",
+InsertLineLbl : "បន្ទាážáŸ‹",
+InsertLine : "បន្ážáŸ‚មបន្ទាážáŸ‹áž•áŸ’ážáŸáž€",
+InsertSpecialCharLbl: "អក្សរពិសáŸážŸ",
+InsertSpecialChar : "បន្ážáŸ‚មអក្សរពិសáŸážŸ",
+InsertSmileyLbl : "រូបភាព",
+InsertSmiley : "បន្ážáŸ‚ម រូបភាព",
+About : "អំពី FCKeditor",
+Bold : "អក្សរដិážáž’ំ",
+Italic : "អក្សរផ្ážáŸáž€",
+Underline : "ដិážáž”ន្ទាážáŸ‹áž–ីក្រោមអក្សរ",
+StrikeThrough : "ដិážáž”ន្ទាážáŸ‹áž–ាក់កណ្ážáž¶áž›áž¢áž€áŸ’សរ",
+Subscript : "អក្សរážáž¼áž…ក្រោម",
+Superscript : "អក្សរážáž¼áž…លើ",
+LeftJustify : "ážáŸ†ážšáž¹áž˜áž†áŸ’ážœáŸáž„",
+CenterJustify : "ážáŸ†ážšáž¹áž˜áž€ážŽáŸ’ážáž¶áž›",
+RightJustify : "ážáŸ†ážšáž¹áž˜ážŸáŸ’ážáž¶áŸ†",
+BlockJustify : "ážáŸ†ážšáž¹áž˜ážŸáž„ážáž¶áž„",
+DecreaseIndent : "បន្ážáž™áž€áž¶ážšáž…ូលបន្ទាážáŸ‹",
+IncreaseIndent : "បន្ážáŸ‚មការចូលបន្ទាážáŸ‹",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "សារឡើងវិញ",
+Redo : "ធ្វើឡើងវិញ",
+NumberedListLbl : "បញ្ជីជាអក្សរ",
+NumberedList : "បន្ážáŸ‚ម/លប់ បញ្ជីជាអក្សរ",
+BulletedListLbl : "បញ្ជីជារង្វង់មូល",
+BulletedList : "បន្ážáŸ‚ម/លប់ បញ្ជីជារង្វង់មូល",
+ShowTableBorders : "បង្ហាញស៊ុមážáž¶ážšáž¶áž„",
+ShowDetails : "បង្ហាញពិស្ážáž¶ážš",
+Style : "ម៉ូáž",
+FontFormat : "រចនា",
+Font : "ហ្វុង",
+FontSize : "ទំហំ",
+TextColor : "ពណ៌អក្សរ",
+BGColor : "ពណ៌ផ្ទៃážáž¶áž„ក្រោយ",
+Source : "កូáž",
+Find : "ស្វែងរក",
+Replace : "ជំនួស",
+SpellCheck : "áž–áž·áž“áž·ážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ",
+UniversalKeyboard : "ក្ážáž¶ážšáž–ុម្ភអក្សរសកល",
+PageBreakLbl : "ការផ្ážáž¶áž…់ទំពáŸážš",
+PageBreak : "បន្ážáŸ‚ម ការផ្ážáž¶áž…់ទំពáŸážš",
+
+Form : "បែបបទ",
+Checkbox : "ប្រអប់ជ្រើសរើស",
+RadioButton : "ប៉ូážáž»áž“រង្វង់មូល",
+TextField : "ជួរសរសáŸážšáž¢ážáŸ’ážáž”áž‘",
+Textarea : "ážáŸ†áž”ន់សរសáŸážšáž¢ážáŸ’ážáž”áž‘",
+HiddenField : "ជួរលាក់",
+Button : "ប៉ូážáž»áž“",
+SelectionField : "ជួរជ្រើសរើស",
+ImageButton : "ប៉ូážáž»áž“រូបភាព",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "កែប្រែឈ្នាប់",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "លប់ជួរផ្ážáŸáž€",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "លប់ជួរឈរ",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "លប់សែល",
+MergeCells : "បញ្ជូលសែល",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "លប់ážáž¶ážšáž¶áž„",
+CellProperties : "ការកំណážáŸ‹ážŸáŸ‚áž›",
+TableProperties : "ការកំណážáŸ‹ážáž¶ážšáž¶áž„",
+ImageProperties : "ការកំណážáŸ‹ážšáž¼áž”ភាព",
+FlashProperties : "ការកំណážáŸ‹ Flash",
+
+AnchorProp : "ការកំណážáŸ‹áž™áž»ážáŸ’កា",
+ButtonProp : "ការកំណážáŸ‹ ប៉ូážáž»áž“",
+CheckboxProp : "ការកំណážáŸ‹áž”្រអប់ជ្រើសរើស",
+HiddenFieldProp : "ការកំណážáŸ‹áž‡áž½ážšáž›áž¶áž€áŸ‹",
+RadioButtonProp : "ការកំណážáŸ‹áž”៉ូážáž»áž“រង្វង់",
+ImageButtonProp : "ការកំណážáŸ‹áž”៉ូážáž»áž“រូបភាព",
+TextFieldProp : "ការកំណážáŸ‹áž‡áž½ážšáž¢ážáŸ’ážáž”áž‘",
+SelectionFieldProp : "ការកំណážáŸ‹áž‡áž½ážšáž‡áŸ’រើសរើស",
+TextareaProp : "ការកំណážáŸ‹áž€áž“្លែងសរសáŸážšáž¢ážáŸ’ážáž”áž‘",
+FormProp : "ការកំណážáŸ‹áž”ែបបទ",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "កំពុងដំណើរការ XHTML ។ សូមរងចាំ...",
+Done : "ចប់រួចរាល់",
+PasteWordConfirm : "អážáŸ’ážáž”ទដែលលោកអ្នកបំរុងចំលងដាក់ ហាក់បីដូចជាážáŸ’រូវចំលងមកពីកម្មវិធី​Word​។ ážáž¾áž›áŸ„កអ្នកចង់សំអាážáž˜áž»áž“ចំលងអážáŸ’ážáž”ទដាក់ទáŸ?",
+NotCompatiblePaste : "ពាក្យបញ្ជានáŸáŸ‡áž”្រើបានážáŸ‚ជាមួយ Internet Explorer កំរិហ5.5 រឺ លើសនáŸáŸ‡ ។ ážáž¾áž›áŸ„កអ្នកចង់ចំលងដាក់ដោយមិនចាំបាច់សំអាážáž‘áŸ?",
+UnknownToolbarItem : "ážœážáŸ’ážáž»áž›áž¾ážšáž”ាឧបរកណ០មិនស្គាល់ \"%1\"",
+UnknownCommand : "ឈ្មោះពាក្យបញ្ជា មិនស្គាល់ \"%1\"",
+NotImplemented : "ពាក្យបញ្ជា មិនបានអនុវážáŸ’áž",
+UnknownToolbarSet : "របាឧបរកណ០\"%1\" ពុំមាន ។",
+NoActiveX : "ការកំណážáŸ‹ážŸáž»ážœážáŸ’ážáž—ាពរបស់កម្មវិធីរុករករបស់លោកអ្នក áž“áŸáŸ‡â€‹áž¢áž¶áž…ធ្វើអោយលោកអ្នកមិនអាចប្រើមុážáž„ារážáŸ’លះរបស់កម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘áž“áŸáŸ‡ ។ លោកអ្នកážáŸ’រូវកំណážáŸ‹áž¢áŸ„áž™ \"ActiveX និង​កម្មវិធីជំនួយក្នុង (plug-ins)\" អោយដំណើរការ ។ លោកអ្នកអាចជួបប្រទះនឹង បញ្ហា ព្រមជាមួយនឹងការបាážáŸ‹áž”ង់មុážáž„ារណាមួយរបស់កម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘áž“áŸáŸ‡ ។",
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "វីនដូវមិនអាចបើកបានទ០។ សូមពិនិážáŸ’យចំពោះកម្មវិធីបិទ វីនដូវលោហ(popup) ážáž¶ážáž¾ážœáž¶ážŠáŸ†ážŽáž¾ážšáž€áž¶ážšážšážºáž‘០។",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "យល់ព្រម",
+DlgBtnCancel : "មិនយល់ព្រម",
+DlgBtnClose : "បិទ",
+DlgBtnBrowseServer : "មើល",
+DlgAdvancedTag : "កំរិážážáŸ’ពស់",
+DlgOpOther : "<ផ្សáŸáž„ទៅáž>",
+DlgInfoTab : "áž–ážáŸŒáž˜áž¶áž“",
+DlgAlertUrl : "សូមសរសáŸážš URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<មិនមែន>",
+DlgGenId : "Id",
+DlgGenLangDir : "ទិសដៅភាសា",
+DlgGenLangDirLtr : "ពីឆ្វáŸáž„ទៅស្ážáž¶áŸ†(LTR)",
+DlgGenLangDirRtl : "ពីស្ážáž¶áŸ†áž‘ៅឆ្វáŸáž„(RTL)",
+DlgGenLangCode : "áž›áŸážáž€áž¼ážáž—ាសា",
+DlgGenAccessKey : "ឃី សំរាប់ចូល",
+DlgGenName : "ឈ្មោះ",
+DlgGenTabIndex : "áž›áŸáž Tab",
+DlgGenLongDescr : "អធិប្បាយ URL វែង",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "ចំណងជើង ប្រឹក្សា",
+DlgGenContType : "ប្រភáŸáž‘អážáŸ’ážáž”áž‘ ប្រឹក្សា",
+DlgGenLinkCharset : "áž›áŸážáž€áž¼ážáž¢áž€áŸ’សររបស់ឈ្នាប់",
+DlgGenStyle : "ម៉ូáž",
+
+// Image Dialog
+DlgImgTitle : "ការកំណážáŸ‹ážšáž¼áž”ភាព",
+DlgImgInfoTab : "áž–ážáŸŒáž˜áž¶áž“អំពីរូបភាព",
+DlgImgBtnUpload : "បញ្ជូនទៅកាន់ម៉ាស៊ីនផ្ážáž›áŸ‹ážŸáŸážœáž¶",
+DlgImgURL : "URL",
+DlgImgUpload : "ទាញយក",
+DlgImgAlt : "អážáŸ’ážáž”ទជំនួស",
+DlgImgWidth : "ទទឹង",
+DlgImgHeight : "កំពស់",
+DlgImgLockRatio : "អážáŸ’រាឡុក",
+DlgBtnResetSize : "កំណážáŸ‹áž‘ំហំឡើងវិញ",
+DlgImgBorder : "ស៊ុម",
+DlgImgHSpace : "គំលាážáž‘ទឹង",
+DlgImgVSpace : "គំលាážáž”ណ្ážáŸ„áž™",
+DlgImgAlign : "កំណážáŸ‹áž‘ីážáž¶áŸ†áž„",
+DlgImgAlignLeft : "ážáž¶áž„ឆ្វង",
+DlgImgAlignAbsBottom: "Abs Bottom", //MISSING
+DlgImgAlignAbsMiddle: "Abs Middle", //MISSING
+DlgImgAlignBaseline : "បន្ទាážáŸ‹áž‡áž¶áž˜áž¼áž›ážŠáŸ’ឋាន",
+DlgImgAlignBottom : "ážáž¶áž„ក្រោម",
+DlgImgAlignMiddle : "កណ្ážáž¶áž›",
+DlgImgAlignRight : "ážáž¶áž„ស្ážáž¶áŸ†",
+DlgImgAlignTextTop : "លើអážáŸ’ážáž”áž‘",
+DlgImgAlignTop : "ážáž¶áž„លើ",
+DlgImgPreview : "មើលសាកល្បង",
+DlgImgAlertUrl : "សូមសរសáŸážšáž„ាសáŸáž™ážŠáŸ’ឋានរបស់រូបភាព",
+DlgImgLinkTab : "ឈ្នាប់",
+
+// Flash Dialog
+DlgFlashTitle : "ការកំណážáŸ‹ Flash",
+DlgFlashChkPlay : "áž›áŸáž„ដោយស្វáŸáž™áž”្រវážáŸ’áž",
+DlgFlashChkLoop : "ចំនួនដង",
+DlgFlashChkMenu : "បង្ហាញ មឺនុយរបស់ Flash",
+DlgFlashScale : "ទំហំ",
+DlgFlashScaleAll : "បង្ហាញទាំងអស់",
+DlgFlashScaleNoBorder : "មិនបង្ហាញស៊ុម",
+DlgFlashScaleFit : "ážáŸ’រូវល្មម",
+
+// Link Dialog
+DlgLnkWindowTitle : "ឈ្នាប់",
+DlgLnkInfoTab : "áž–ážáŸŒáž˜áž¶áž“អំពីឈ្នាប់",
+DlgLnkTargetTab : "គោលដៅ",
+
+DlgLnkType : "ប្រភáŸáž‘ឈ្នាប់",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "យុážáŸ’កានៅក្នុងទំពáŸážšáž“áŸáŸ‡",
+DlgLnkTypeEMail : "អ៊ីមែល",
+DlgLnkProto : "ប្រូážáž¼áž€áž¼áž›",
+DlgLnkProtoOther : "<ផ្សáŸáž„ទៀáž>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "ជ្រើសរើសយុážáŸ’កា",
+DlgLnkAnchorByName : "ážáž¶áž˜ážˆáŸ’មោះរបស់យុážáŸ’កា",
+DlgLnkAnchorById : "ážáž¶áž˜ Id",
+DlgLnkNoAnchors : "(No anchors available in the document)", //MISSING
+DlgLnkEMail : "អ៊ីមែល",
+DlgLnkEMailSubject : "ចំណងជើងអážáŸ’ážáž”áž‘",
+DlgLnkEMailBody : "អážáŸ’ážáž”áž‘",
+DlgLnkUpload : "ទាញយក",
+DlgLnkBtnUpload : "ទាញយក",
+
+DlgLnkTarget : "គោលដៅ",
+DlgLnkTargetFrame : "<ហ្វ្រáŸáž˜>",
+DlgLnkTargetPopup : "<វីនដូវ លោáž>",
+DlgLnkTargetBlank : "វីនដូវážáŸ’មី (_blank)",
+DlgLnkTargetParent : "វីនដូវម០(_parent)",
+DlgLnkTargetSelf : "វីនដូវដដែល (_self)",
+DlgLnkTargetTop : "វីនដូវនៅលើគáŸ(_top)",
+DlgLnkTargetFrameName : "ឈ្មោះហ្រ្វáŸáž˜ážŠáŸ‚លជាគោលដៅ",
+DlgLnkPopWinName : "ឈ្មោះវីនដូវលោáž",
+DlgLnkPopWinFeat : "លក្ážážŽáŸ‡ážšáž”ស់វីនដូលលោáž",
+DlgLnkPopResize : "ទំហំអាចផ្លាស់ប្ážáž¼ážš",
+DlgLnkPopLocation : "របា ទីážáž¶áŸ†áž„",
+DlgLnkPopMenu : "របា មឺនុយ",
+DlgLnkPopScroll : "របា ទាញ",
+DlgLnkPopStatus : "របា áž–ážáŸŒáž˜áž¶áž“",
+DlgLnkPopToolbar : "របា ឩបករណáŸ",
+DlgLnkPopFullScrn : "អáŸáž€áŸ’រុងពáŸáž‰(IE)",
+DlgLnkPopDependent : "អាស្រáŸáž™áž›áž¾ (Netscape)",
+DlgLnkPopWidth : "ទទឹង",
+DlgLnkPopHeight : "កំពស់",
+DlgLnkPopLeft : "ទីážáž¶áŸ†áž„ážáž¶áž„ឆ្វáŸáž„",
+DlgLnkPopTop : "ទីážáž¶áŸ†áž„ážáž¶áž„លើ",
+
+DlnLnkMsgNoUrl : "សូមសរសáŸážš អាសáŸáž™ážŠáŸ’ឋាន URL",
+DlnLnkMsgNoEMail : "សូមសរសáŸážš អាសáŸáž™ážŠáŸ’ឋាន អ៊ីមែល",
+DlnLnkMsgNoAnchor : "សូមជ្រើសរើស យុážáŸ’កា",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "ជ្រើសរើស ពណ៌",
+DlgColorBtnClear : "លប់",
+DlgColorHighlight : "ផាážáŸ‹áž–ណ៌",
+DlgColorSelected : "បានជ្រើសរើស",
+
+// Smiley Dialog
+DlgSmileyTitle : "បញ្ជូលរូបភាព",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "ážáž¼áž¢áž€áŸ’សរពិសáŸážŸ",
+
+// Table Dialog
+DlgTableTitle : "ការកំណážáŸ‹ ážáž¶ážšáž¶áž„",
+DlgTableRows : "ជួរផ្ážáŸáž€",
+DlgTableColumns : "ជួរឈរ",
+DlgTableBorder : "ទំហំស៊ុម",
+DlgTableAlign : "ការកំណážáŸ‹áž‘ីážáž¶áŸ†áž„",
+DlgTableAlignNotSet : "<មិនកំណážáŸ‹>",
+DlgTableAlignLeft : "ážáž¶áž„ឆ្វáŸáž„",
+DlgTableAlignCenter : "កណ្ážáž¶áž›",
+DlgTableAlignRight : "ážáž¶áž„ស្ážáž¶áŸ†",
+DlgTableWidth : "ទទឹង",
+DlgTableWidthPx : "ភីកសែល",
+DlgTableWidthPc : "ភាគរយ",
+DlgTableHeight : "កំពស់",
+DlgTableCellSpace : "គំលាážážŸáŸ‚áž›",
+DlgTableCellPad : "គែមសែល",
+DlgTableCaption : "ចំណងជើង",
+DlgTableSummary : "សáŸáž…ក្ážáž¸ážŸáž„្ážáŸáž”",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "ការកំណážáŸ‹ សែល",
+DlgCellWidth : "ទទឹង",
+DlgCellWidthPx : "ភីកសែល",
+DlgCellWidthPc : "ភាគរយ",
+DlgCellHeight : "កំពស់",
+DlgCellWordWrap : "បង្ហាញអážáŸ’ážáž”ទទាំងអស់",
+DlgCellWordWrapNotSet : "<មិនកំណážáŸ‹>",
+DlgCellWordWrapYes : "បាទ(ចា)",
+DlgCellWordWrapNo : "áž‘áŸ",
+DlgCellHorAlign : "ážáŸ†ážšáž¹áž˜áž•áŸ’ážáŸáž€",
+DlgCellHorAlignNotSet : "<មិនកំណážáŸ‹>",
+DlgCellHorAlignLeft : "ážáž¶áž„ឆ្វáŸáž„",
+DlgCellHorAlignCenter : "កណ្ážáž¶áž›",
+DlgCellHorAlignRight: "Right", //MISSING
+DlgCellVerAlign : "ážáŸ†ážšáž¹áž˜ážˆážš",
+DlgCellVerAlignNotSet : "<មិនកណážáŸ‹>",
+DlgCellVerAlignTop : "ážáž¶áž„លើ",
+DlgCellVerAlignMiddle : "កណ្ážáž¶áž›",
+DlgCellVerAlignBottom : "ážáž¶áž„ក្រោម",
+DlgCellVerAlignBaseline : "បន្ទាážáŸ‹áž‡áž¶áž˜áž¼áž›ážŠáŸ’ឋាន",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "បញ្ជូលជួរផ្ážáŸáž€",
+DlgCellCollSpan : "បញ្ជូលជួរឈរ",
+DlgCellBackColor : "ពណ៌ផ្នែកážáž¶áž„ក្រោម",
+DlgCellBorderColor : "ពណ៌ស៊ុម",
+DlgCellBtnSelect : "ជ្រើសរើស...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "ស្វែងរក",
+DlgFindFindBtn : "ស្វែងរក",
+DlgFindNotFoundMsg : "ពាក្យនáŸáŸ‡ រកមិនឃើញទ០។",
+
+// Replace Dialog
+DlgReplaceTitle : "ជំនួស",
+DlgReplaceFindLbl : "ស្វែងរកអ្វី:",
+DlgReplaceReplaceLbl : "ជំនួសជាមួយ:",
+DlgReplaceCaseChk : "ករណ៉ážáŸ’រូវរក",
+DlgReplaceReplaceBtn : "ជំនួស",
+DlgReplaceReplAllBtn : "ជំនួសទាំងអស់",
+DlgReplaceWordChk : "ážáŸ’រូវពាក្យទាំងអស់",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ការកំណážáŸ‹ážŸáž»ážœážáŸ’ážáž—ាពរបស់កម្មវិធីរុករករបស់លោកអ្នក áž“áŸáŸ‡â€‹áž˜áž·áž“អាចធ្វើកម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘ កាážáŸ‹áž¢ážáŸ’ážáž”ទយកដោយស្វáŸáž™áž”្រវážáŸ’ážáž”ានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនáŸáŸ‡ (Ctrl+X) ។",
+PasteErrorCopy : "ការកំណážáŸ‹ážŸáž»ážœážáŸ’ážáž—ាពរបស់កម្មវិធីរុករករបស់លោកអ្នក áž“áŸáŸ‡â€‹áž˜áž·áž“អាចធ្វើកម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘ ចំលងអážáŸ’ážáž”ទយកដោយស្វáŸáž™áž”្រវážáŸ’ážáž”ានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនáŸáŸ‡ (Ctrl+C)។",
+
+PasteAsText : "ចំលងដាក់អážáŸ’ážáž”ទធម្មážáž¶",
+PasteFromWord : "ចំលងពាក្យពីកម្មវិធី Word",
+
+DlgPasteMsg2 : "សូមចំលងអážáŸ’ážáž”ទទៅដាក់ក្នុងប្រអប់ដូចážáž¶áž„ក្រោមដោយប្រើប្រាស់ ឃី ​(<STRONG>Ctrl+V</STRONG>) ហើយចុច <STRONG>OK</STRONG> ។",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "មិនគិážáž¢áŸ†áž–ីប្រភáŸáž‘ពុម្ភអក្សរ",
+DlgPasteRemoveStyles : "លប់ម៉ូáž",
+
+// Color Picker
+ColorAutomatic : "ស្វáŸáž™áž”្រវážáŸ’áž",
+ColorMoreColors : "ពណ៌ផ្សáŸáž„ទៀáž..",
+
+// Document Properties
+DocProps : "ការកំណážáŸ‹ ឯកសារ",
+
+// Anchor Dialog
+DlgAnchorTitle : "ការកំណážáŸ‹áž…ំណងជើងយុទ្ធážáŸ’កា",
+DlgAnchorName : "ឈ្មោះយុទ្ធážáŸ’កា",
+DlgAnchorErrorName : "សូមសរសáŸážš ឈ្មោះយុទ្ធážáŸ’កា",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "គ្មានក្នុងវចនានុក្រម",
+DlgSpellChangeTo : "ផ្លាស់ប្ážáž¼ážšáž‘ៅ",
+DlgSpellBtnIgnore : "មិនផ្លាស់ប្ážáž¼ážš",
+DlgSpellBtnIgnoreAll : "មិនផ្លាស់ប្ážáž¼ážš ទាំងអស់",
+DlgSpellBtnReplace : "ជំនួស",
+DlgSpellBtnReplaceAll : "ជំនួសទាំងអស់",
+DlgSpellBtnUndo : "សារឡើងវិញ",
+DlgSpellNoSuggestions : "- គ្មានសំណើរ -",
+DlgSpellProgress : "កំពុងពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ...",
+DlgSpellNoMispell : "ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: គ្មានកំហុស",
+DlgSpellNoChanges : "ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: ពុំមានផ្លាស់ប្ážáž¼ážš",
+DlgSpellOneChange : "ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: ពាក្យមួយážáŸ’រូចបានផ្លាស់ប្ážáž¼ážš",
+DlgSpellManyChanges : "ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: %1 ពាក្យបានផ្លាស់ប្ážáž¼ážš",
+
+IeSpellDownload : "ពុំមានកម្មវិធីពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ ។ ážáž¾áž…ង់ទាញយកពីណា?",
+
+// Button Dialog
+DlgButtonText : "អážáŸ’ážáž”áž‘(ážáŸ†áž›áŸƒ)",
+DlgButtonType : "ប្រភáŸáž‘",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ឈ្មោះ",
+DlgCheckboxValue : "ážáŸ†áž›áŸƒ",
+DlgCheckboxSelected : "បានជ្រើសរើស",
+
+// Form Dialog
+DlgFormName : "ឈ្មោះ",
+DlgFormAction : "សកម្មភាព",
+DlgFormMethod : "វិធី",
+
+// Select Field Dialog
+DlgSelectName : "ឈ្មោះ",
+DlgSelectValue : "ážáŸ†áž›áŸƒ",
+DlgSelectSize : "ទំហំ",
+DlgSelectLines : "បន្ទាážáŸ‹",
+DlgSelectChkMulti : "អនុញ្ញាážáž¢áŸ„យជ្រើសរើសច្រើន",
+DlgSelectOpAvail : "ការកំណážáŸ‹áž‡áŸ’រើសរើស ដែលអាចកំណážáŸ‹áž”ាន",
+DlgSelectOpText : "ពាក្យ",
+DlgSelectOpValue : "ážáŸ†áž›áŸƒ",
+DlgSelectBtnAdd : "បន្ážáŸ‚ម",
+DlgSelectBtnModify : "ផ្លាស់ប្ážáž¼ážš",
+DlgSelectBtnUp : "លើ",
+DlgSelectBtnDown : "ក្រោម",
+DlgSelectBtnSetValue : "Set as selected value", //MISSING
+DlgSelectBtnDelete : "លប់",
+
+// Textarea Dialog
+DlgTextareaName : "ឈ្មោះ",
+DlgTextareaCols : "ជូរឈរ",
+DlgTextareaRows : "ជូរផ្ážáŸáž€",
+
+// Text Field Dialog
+DlgTextName : "ឈ្មោះ",
+DlgTextValue : "ážáŸ†áž›áŸƒ",
+DlgTextCharWidth : "ទទឹង អក្សរ",
+DlgTextMaxChars : "អក្សរអážáž·áž”រិមា",
+DlgTextType : "ប្រភáŸáž‘",
+DlgTextTypeText : "ពាក្យ",
+DlgTextTypePass : "ពាក្យសំងាážáŸ‹",
+
+// Hidden Field Dialog
+DlgHiddenName : "ឈ្មោះ",
+DlgHiddenValue : "ážáŸ†áž›áŸƒ",
+
+// Bulleted List Dialog
+BulletedListProp : "កំណážáŸ‹áž”ញ្ជីរង្វង់",
+NumberedListProp : "កំណážáŸ‹áž”ញ្áŸáž‡áž¸áž›áŸáž",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "ប្រភáŸáž‘",
+DlgLstTypeCircle : "រង្វង់",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "ការáŸ",
+DlgLstTypeNumbers : "áž›áŸáž(1, 2, 3)",
+DlgLstTypeLCase : "អក្សរážáž¼áž…(a, b, c)",
+DlgLstTypeUCase : "អក្សរធំ(A, B, C)",
+DlgLstTypeSRoman : "អក្សរឡាážáž¶áŸ†áž„ážáž¼áž…(i, ii, iii)",
+DlgLstTypeLRoman : "អក្សរឡាážáž¶áŸ†áž„ធំ(I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "ទូទៅ",
+DlgDocBackTab : "ផ្នែកážáž¶áž„ក្រោយ",
+DlgDocColorsTab : "ទំពáŸážšâ€‹áž“áž·áž„ ស៊ុម",
+DlgDocMetaTab : "ទិន្ននáŸáž™áž˜áŸ",
+
+DlgDocPageTitle : "ចំណងជើងទំពáŸážš",
+DlgDocLangDir : "ទិសដៅសរសáŸážšáž—ាសា",
+DlgDocLangDirLTR : "ពីឆ្វáŸáž„ទៅស្ដាំ(LTR)",
+DlgDocLangDirRTL : "ពីស្ដាំទៅឆ្វáŸáž„(RTL)",
+DlgDocLangCode : "áž›áŸážáž€áž¼ážáž—ាសា",
+DlgDocCharSet : "កំណážáŸ‹áž›áŸážáž€áž¼ážáž—ាសា",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "កំណážáŸ‹áž›áŸážáž€áž¼ážáž—ាសាផ្សáŸáž„ទៀáž",
+
+DlgDocDocType : "ប្រភáŸáž‘ក្បាលទំពáŸážš",
+DlgDocDocTypeOther : "ប្រភáŸáž‘ក្បាលទំពáŸážšáž•áŸ’សáŸáž„ទៀáž",
+DlgDocIncXHTML : "បញ្ជូល XHTML",
+DlgDocBgColor : "ពណ៌ážáž¶áž„ក្រោម",
+DlgDocBgImage : "URL របស់រូបភាពážáž¶áž„ក្រោម",
+DlgDocBgNoScroll : "ទំពáŸážšáž€áŸ’រោមមិនប្ážáž¼ážš",
+DlgDocCText : "អážáŸ’ážáž”áž‘",
+DlgDocCLink : "ឈ្នាប់",
+DlgDocCVisited : "ឈ្នាប់មើលហើយ",
+DlgDocCActive : "ឈ្នាប់កំពុងមើល",
+DlgDocMargins : "ស៊ុមទំពáŸážš",
+DlgDocMaTop : "លើ",
+DlgDocMaLeft : "ឆ្វáŸáž„",
+DlgDocMaRight : "ស្ដាំ",
+DlgDocMaBottom : "ក្រោម",
+DlgDocMeIndex : "ពាក្យនៅក្នុងឯកសារ (ផ្ážáž¶áž…់ពីគ្នាដោយក្បៀស)",
+DlgDocMeDescr : "សáŸáž…ក្ážáž¸áž¢ážáŸ’ážáž¶áž’ិប្បាយអំពីឯកសារ",
+DlgDocMeAuthor : "អ្នកនិពន្ធ",
+DlgDocMeCopy : "រក្សាសិទ្ធិáŸ",
+DlgDocPreview : "មើលសាកល្បង",
+
+// Templates Dialog
+Templates : "ឯកសារគំរូ",
+DlgTemplatesTitle : "ឯកសារគំរូ របស់អážáŸ’ážáž“áŸáž™",
+DlgTemplatesSelMsg : "សូមជ្រើសរើសឯកសារគំរូ ដើម្បីបើកនៅក្នុងកម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘<br>(អážáŸ’ážáž”ទនឹងបាážáŸ‹áž”ង់):",
+DlgTemplatesLoading : "កំពុងអានបញ្ជីឯកសារគំរូ ។ សូមរងចាំ...",
+DlgTemplatesNoTpl : "(ពុំមានឯកសារគំរូážáŸ’រូវបានកំណážáŸ‹)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "អំពី",
+DlgAboutBrowserInfoTab : "ព៌ážáž˜áž¶áž“កម្មវិធីរុករក",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "ជំនាន់",
+DlgAboutInfo : "សំរាប់ព៌ážáž˜áž¶áž“ផ្សáŸáž„ទៀហសូមទាក់ទង",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ko.js b/httemplate/elements/fckeditor/editor/lang/ko.js
new file mode 100644
index 0000000..91df044
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ko.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Korean language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "툴바 ê°ì¶”기",
+ToolbarExpand : "툴바 ë³´ì´ê¸°",
+
+// Toolbar Items and Context Menu
+Save : "저장하기",
+NewPage : "새 문서",
+Preview : "미리보기",
+Cut : "잘ë¼ë‚´ê¸°",
+Copy : "복사하기",
+Paste : "붙여넣기",
+PasteText : "í…스트로 붙여넣기",
+PasteWord : "MS Word 형ì‹ì—ì„œ 붙여넣기",
+Print : "ì¸ì‡„하기",
+SelectAll : "ì „ì²´ì„ íƒ",
+RemoveFormat : "í¬ë§· 지우기",
+InsertLinkLbl : "ë§í¬",
+InsertLink : "ë§í¬ 삽입/변경",
+RemoveLink : "ë§í¬ ì‚­ì œ",
+VisitLink : "Open Link", //MISSING
+Anchor : "책갈피 삽입/변경",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "ì´ë¯¸ì§€",
+InsertImage : "ì´ë¯¸ì§€ 삽입/변경",
+InsertFlashLbl : "플래쉬",
+InsertFlash : "플래쉬 삽입/변경",
+InsertTableLbl : "표",
+InsertTable : "표 삽입/변경",
+InsertLineLbl : "수í‰ì„ ",
+InsertLine : "수í‰ì„  삽입",
+InsertSpecialCharLbl: "íŠ¹ìˆ˜ë¬¸ìž ì‚½ìž…",
+InsertSpecialChar : "íŠ¹ìˆ˜ë¬¸ìž ì‚½ìž…",
+InsertSmileyLbl : "ì•„ì´ì½˜",
+InsertSmiley : "ì•„ì´ì½˜ 삽입",
+About : "FCKeditorì— ëŒ€í•˜ì—¬",
+Bold : "진하게",
+Italic : "ì´í…”릭",
+Underline : "밑줄",
+StrikeThrough : "취소선",
+Subscript : "아래 첨ìž",
+Superscript : "위 첨ìž",
+LeftJustify : "왼쪽 정렬",
+CenterJustify : "ê°€ìš´ë° ì •ë ¬",
+RightJustify : "오른쪽 정렬",
+BlockJustify : "양쪽 맞춤",
+DecreaseIndent : "내어쓰기",
+IncreaseIndent : "들여쓰기",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "취소",
+Redo : "재실행",
+NumberedListLbl : "순서있는 목ë¡",
+NumberedList : "순서있는 목ë¡",
+BulletedListLbl : "순서없는 목ë¡",
+BulletedList : "순서없는 목ë¡",
+ShowTableBorders : "í‘œ í…Œë‘리 보기",
+ShowDetails : "문서기호 보기",
+Style : "스타ì¼",
+FontFormat : "í¬ë§·",
+Font : "í°íŠ¸",
+FontSize : "ê¸€ìž í¬ê¸°",
+TextColor : "ê¸€ìž ìƒ‰ìƒ",
+BGColor : "ë°°ê²½ 색ìƒ",
+Source : "소스",
+Find : "찾기",
+Replace : "바꾸기",
+SpellCheck : "ì² ìžê²€ì‚¬",
+UniversalKeyboard : "다국어 입력기",
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "í¼",
+Checkbox : "ì²´í¬ë°•ìŠ¤",
+RadioButton : "ë¼ë””오버튼",
+TextField : "입력필드",
+Textarea : "ìž…ë ¥ì˜ì—­",
+HiddenField : "숨김필드",
+Button : "버튼",
+SelectionField : "펼침목ë¡",
+ImageButton : "ì´ë¯¸ì§€ë²„튼",
+
+FitWindow : "ì—디터 최대화",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "ë§í¬ 수정",
+CellCM : "셀/칸(Cell)",
+RowCM : "í–‰(Row)",
+ColumnCM : "ì—´(Column)",
+InsertRowAfter : "ë’¤ì— í–‰ 삽입",
+InsertRowBefore : "ì•žì— í–‰ 삽입",
+DeleteRows : "가로줄 삭제",
+InsertColumnAfter : "ë’¤ì— ì—´ 삽입",
+InsertColumnBefore : "ì•žì— ì—´ 삽입",
+DeleteColumns : "세로줄 삭제",
+InsertCellAfter : "ë’¤ì— ì…€/칸 삽입",
+InsertCellBefore : "ì•žì— ì…€/칸 삽입",
+DeleteCells : "셀 삭제",
+MergeCells : "셀 합치기",
+MergeRight : "오른쪽 뭉치기",
+MergeDown : "왼쪽 뭉치기",
+HorizontalSplitCell : "ìˆ˜í‰ ë‚˜ëˆ„ê¸°",
+VerticalSplitCell : "ìˆ˜ì§ ë‚˜ëˆ„ê¸°",
+TableDelete : "표 삭제",
+CellProperties : "ì…€ ì†ì„±",
+TableProperties : "í‘œ ì†ì„±",
+ImageProperties : "ì´ë¯¸ì§€ ì†ì„±",
+FlashProperties : "플래쉬 ì†ì„±",
+
+AnchorProp : "책갈피 ì†ì„±",
+ButtonProp : "버튼 ì†ì„±",
+CheckboxProp : "ì²´í¬ë°•ìŠ¤ ì†ì„±",
+HiddenFieldProp : "숨김필드 ì†ì„±",
+RadioButtonProp : "ë¼ë””오버튼 ì†ì„±",
+ImageButtonProp : "ì´ë¯¸ì§€ë²„튼 ì†ì„±",
+TextFieldProp : "입력필드 ì†ì„±",
+SelectionFieldProp : "íŽ¼ì¹¨ëª©ë¡ ì†ì„±",
+TextareaProp : "ìž…ë ¥ì˜ì—­ ì†ì„±",
+FormProp : "í¼ ì†ì„±",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML 처리중. 잠시만 기다려주십시요.",
+Done : "완료",
+PasteWordConfirm : "붙여넣기 í•  í…스트는 MS Wordì—ì„œ 복사한 것입니다. 붙여넣기 ì „ì— MS Word í¬ë©§ì„ 삭제하시겠습니까?",
+NotCompatiblePaste : "ì´ ëª…ë ¹ì€ ì¸í„°ë„·ìµìŠ¤í”Œë¡œëŸ¬ 5.5 버전 ì´ìƒì—서만 ìž‘ë™í•©ë‹ˆë‹¤. í¬ë©§ì„ 삭제하지 ì•Šê³  붙여넣기 하시겠습니까?",
+UnknownToolbarItem : "알수없는 툴바입니다. : \"%1\"",
+UnknownCommand : "알수없는 기능입니다. : \"%1\"",
+NotImplemented : "ê¸°ëŠ¥ì´ ì‹¤í–‰ë˜ì§€ 않았습니다.",
+UnknownToolbarSet : "툴바 ì„¤ì •ì´ ì—†ìŠµë‹ˆë‹¤. : \"%1\"",
+NoActiveX : "ë¸ŒëŸ¬ìš°ì €ì˜ ë³´ì•ˆ 설정으로 ì¸í•´ 몇몇 ê¸°ëŠ¥ì˜ ìž‘ë™ì— 장애가 ìžˆì„ ìˆ˜ 있습니다. \"액티브-액스 기능과 플러그 ì¸\" ì˜µì…˜ì„ í—ˆìš©í•˜ì—¬ 주시지 않으면 오류가 ë°œìƒí•  수 있습니다.",
+BrowseServerBlocked : "브러우저 요소가 열리지 않습니다. íŒì—…차단 ì„¤ì •ì´ êº¼ì ¸ìžˆëŠ”ì§€ 확ì¸í•˜ì—¬ 주십시오.",
+DialogBlocked : "윈ë„ìš° ëŒ€í™”ì°½ì„ ì—´ 수 없습니다. íŒì—…차단 ì„¤ì •ì´ êº¼ì ¸ìžˆëŠ”ì§€ 확ì¸í•˜ì—¬ 주십시오.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "예",
+DlgBtnCancel : "아니오",
+DlgBtnClose : "닫기",
+DlgBtnBrowseServer : "서버 보기",
+DlgAdvancedTag : "ìžì„¸ížˆ",
+DlgOpOther : "<기타>",
+DlgInfoTab : "ì •ë³´",
+DlgAlertUrl : "URLì„ ìž…ë ¥í•˜ì‹­ì‹œìš”",
+
+// General Dialogs Labels
+DlgGenNotSet : "<설정ë˜ì§€ ì•ŠìŒ>",
+DlgGenId : "ID",
+DlgGenLangDir : "쓰기 방향",
+DlgGenLangDirLtr : "왼쪽ì—ì„œ 오른쪽 (LTR)",
+DlgGenLangDirRtl : "오른쪽ì—ì„œ 왼쪽 (RTL)",
+DlgGenLangCode : "언어 코드",
+DlgGenAccessKey : "엑세스 키",
+DlgGenName : "Name",
+DlgGenTabIndex : "탭 순서",
+DlgGenLongDescr : "URL 설명",
+DlgGenClass : "Stylesheet Classes",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Style",
+
+// Image Dialog
+DlgImgTitle : "ì´ë¯¸ì§€ 설정",
+DlgImgInfoTab : "ì´ë¯¸ì§€ ì •ë³´",
+DlgImgBtnUpload : "서버로 전송",
+DlgImgURL : "URL",
+DlgImgUpload : "업로드",
+DlgImgAlt : "ì´ë¯¸ì§€ 설명",
+DlgImgWidth : "너비",
+DlgImgHeight : "높ì´",
+DlgImgLockRatio : "비율 유지",
+DlgBtnResetSize : "ì›ëž˜ í¬ê¸°ë¡œ",
+DlgImgBorder : "í…Œë‘리",
+DlgImgHSpace : "수í‰ì—¬ë°±",
+DlgImgVSpace : "수ì§ì—¬ë°±",
+DlgImgAlign : "ì •ë ¬",
+DlgImgAlignLeft : "왼쪽",
+DlgImgAlignAbsBottom: "줄아래(Abs Bottom)",
+DlgImgAlignAbsMiddle: "줄중간(Abs Middle)",
+DlgImgAlignBaseline : "기준선",
+DlgImgAlignBottom : "아래",
+DlgImgAlignMiddle : "중간",
+DlgImgAlignRight : "오른쪽",
+DlgImgAlignTextTop : "글ìžìƒë‹¨",
+DlgImgAlignTop : "위",
+DlgImgPreview : "미리보기",
+DlgImgAlertUrl : "ì´ë¯¸ì§€ URLì„ ìž…ë ¥í•˜ì‹­ì‹œìš”",
+DlgImgLinkTab : "ë§í¬",
+
+// Flash Dialog
+DlgFlashTitle : "플래쉬 등ë¡ì •ë³´",
+DlgFlashChkPlay : "ìžë™ìž¬ìƒ",
+DlgFlashChkLoop : "반복",
+DlgFlashChkMenu : "플래쉬메뉴 가능",
+DlgFlashScale : "ì˜ì—­",
+DlgFlashScaleAll : "모ë‘보기",
+DlgFlashScaleNoBorder : "경계선없ìŒ",
+DlgFlashScaleFit : "ì˜ì—­ìžë™ì¡°ì ˆ",
+
+// Link Dialog
+DlgLnkWindowTitle : "ë§í¬",
+DlgLnkInfoTab : "ë§í¬ ì •ë³´",
+DlgLnkTargetTab : "타겟",
+
+DlgLnkType : "ë§í¬ 종류",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "책갈피",
+DlgLnkTypeEMail : "ì´ë©”ì¼",
+DlgLnkProto : "프로토콜",
+DlgLnkProtoOther : "<기타>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "책갈피 ì„ íƒ",
+DlgLnkAnchorByName : "책갈피 ì´ë¦„",
+DlgLnkAnchorById : "책갈피 ID",
+DlgLnkNoAnchors : "(ë¬¸ì„œì— ì±…ê°ˆí”¼ê°€ 없습니다.)",
+DlgLnkEMail : "ì´ë©”ì¼ ì£¼ì†Œ",
+DlgLnkEMailSubject : "제목",
+DlgLnkEMailBody : "ë‚´ìš©",
+DlgLnkUpload : "업로드",
+DlgLnkBtnUpload : "서버로 전송",
+
+DlgLnkTarget : "타겟",
+DlgLnkTargetFrame : "<프레임>",
+DlgLnkTargetPopup : "<íŒì—…ì°½>",
+DlgLnkTargetBlank : "새 창 (_blank)",
+DlgLnkTargetParent : "부모 창 (_parent)",
+DlgLnkTargetSelf : "현재 창 (_self)",
+DlgLnkTargetTop : "최 ìƒìœ„ ì°½ (_top)",
+DlgLnkTargetFrameName : "타겟 프레임 ì´ë¦„",
+DlgLnkPopWinName : "íŒì—…ì°½ ì´ë¦„",
+DlgLnkPopWinFeat : "íŒì—…ì°½ 설정",
+DlgLnkPopResize : "í¬ê¸°ì¡°ì •",
+DlgLnkPopLocation : "주소표시줄",
+DlgLnkPopMenu : "메뉴바",
+DlgLnkPopScroll : "스í¬ë¡¤ë°”",
+DlgLnkPopStatus : "ìƒíƒœë°”",
+DlgLnkPopToolbar : "툴바",
+DlgLnkPopFullScrn : "전체화면 (IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "너비",
+DlgLnkPopHeight : "높ì´",
+DlgLnkPopLeft : "왼쪽 위치",
+DlgLnkPopTop : "윗쪽 위치",
+
+DlnLnkMsgNoUrl : "ë§í¬ URLì„ ìž…ë ¥í•˜ì‹­ì‹œìš”.",
+DlnLnkMsgNoEMail : "ì´ë©”ì¼ì£¼ì†Œë¥¼ 입력하십시요.",
+DlnLnkMsgNoAnchor : "ì±…ê°ˆí”¼ëª…ì„ ìž…ë ¥í•˜ì‹­ì‹œìš”.",
+DlnLnkMsgInvPopName : "íŒì—…ì°½ì˜ íƒ€ì´í‹€ì€ ê³µë°±ì„ í—ˆìš©í•˜ì§€ 않습니다.",
+
+// Color Dialog
+DlgColorTitle : "ìƒ‰ìƒ ì„ íƒ",
+DlgColorBtnClear : "지우기",
+DlgColorHighlight : "현재",
+DlgColorSelected : "ì„ íƒë¨",
+
+// Smiley Dialog
+DlgSmileyTitle : "ì•„ì´ì½˜ 삽입",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "íŠ¹ìˆ˜ë¬¸ìž ì„ íƒ",
+
+// Table Dialog
+DlgTableTitle : "표 설정",
+DlgTableRows : "가로줄",
+DlgTableColumns : "세로줄",
+DlgTableBorder : "í…Œë‘리 í¬ê¸°",
+DlgTableAlign : "ì •ë ¬",
+DlgTableAlignNotSet : "<설정ë˜ì§€ ì•ŠìŒ>",
+DlgTableAlignLeft : "왼쪽",
+DlgTableAlignCenter : "가운ë°",
+DlgTableAlignRight : "오른쪽",
+DlgTableWidth : "너비",
+DlgTableWidthPx : "픽셀",
+DlgTableWidthPc : "í¼ì„¼íŠ¸",
+DlgTableHeight : "높ì´",
+DlgTableCellSpace : "셀 간격",
+DlgTableCellPad : "셀 여백",
+DlgTableCaption : "캡션",
+DlgTableSummary : "요약",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "셀 설정",
+DlgCellWidth : "너비",
+DlgCellWidthPx : "픽셀",
+DlgCellWidthPc : "í¼ì„¼íŠ¸",
+DlgCellHeight : "높ì´",
+DlgCellWordWrap : "워드랩",
+DlgCellWordWrapNotSet : "<설정ë˜ì§€ ì•ŠìŒ>",
+DlgCellWordWrapYes : "예",
+DlgCellWordWrapNo : "아니오",
+DlgCellHorAlign : "ìˆ˜í‰ ì •ë ¬",
+DlgCellHorAlignNotSet : "<설정ë˜ì§€ ì•ŠìŒ>",
+DlgCellHorAlignLeft : "왼쪽",
+DlgCellHorAlignCenter : "가운ë°",
+DlgCellHorAlignRight: "오른쪽",
+DlgCellVerAlign : "ìˆ˜ì§ ì •ë ¬",
+DlgCellVerAlignNotSet : "<설정ë˜ì§€ ì•ŠìŒ>",
+DlgCellVerAlignTop : "위",
+DlgCellVerAlignMiddle : "중간",
+DlgCellVerAlignBottom : "아래",
+DlgCellVerAlignBaseline : "기준선",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "세로 합치기",
+DlgCellCollSpan : "가로 합치기",
+DlgCellBackColor : "ë°°ê²½ 색ìƒ",
+DlgCellBorderColor : "í…Œë‘리 색ìƒ",
+DlgCellBtnSelect : "ì„ íƒ",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "찾기 & 바꾸기",
+
+// Find Dialog
+DlgFindTitle : "찾기",
+DlgFindFindBtn : "찾기",
+DlgFindNotFoundMsg : "문ìžì—´ì„ ì°¾ì„ ìˆ˜ 없습니다.",
+
+// Replace Dialog
+DlgReplaceTitle : "바꾸기",
+DlgReplaceFindLbl : "ì°¾ì„ ë¬¸ìžì—´:",
+DlgReplaceReplaceLbl : "바꿀 문ìžì—´:",
+DlgReplaceCaseChk : "ëŒ€ì†Œë¬¸ìž êµ¬ë¶„",
+DlgReplaceReplaceBtn : "바꾸기",
+DlgReplaceReplAllBtn : "ëª¨ë‘ ë°”ê¾¸ê¸°",
+DlgReplaceWordChk : "온전한 단어",
+
+// Paste Operations / Dialog
+PasteErrorCut : "브ë¼ìš°ì €ì˜ ë³´ì•ˆì„¤ì •ë•Œë¬¸ì— ìž˜ë¼ë‚´ê¸° ê¸°ëŠ¥ì„ ì‹¤í–‰í•  수 없습니다. 키보드 ëª…ë ¹ì„ ì‚¬ìš©í•˜ì‹­ì‹œìš”. (Ctrl+X).",
+PasteErrorCopy : "브ë¼ìš°ì €ì˜ ë³´ì•ˆì„¤ì •ë•Œë¬¸ì— ë³µì‚¬í•˜ê¸° ê¸°ëŠ¥ì„ ì‹¤í–‰í•  수 없습니다. 키보드 ëª…ë ¹ì„ ì‚¬ìš©í•˜ì‹­ì‹œìš”. (Ctrl+C).",
+
+PasteAsText : "í…스트로 붙여넣기",
+PasteFromWord : "MS Word 형ì‹ì—ì„œ 붙여넣기",
+
+DlgPasteMsg2 : "í‚¤ë³´ë“œì˜ (<STRONG>Ctrl+V</STRONG>) 를 ì´ìš©í•´ì„œ ìƒìžì•ˆì— 붙여넣고 <STRONG>OK</STRONG> 를 누르세요.",
+DlgPasteSec : "브러우저 보안 설정으로 ì¸í•´, í´ë¦½ë³´ë“œì˜ ìžë£Œë¥¼ ì§ì ‘ 접근할 수 없습니다. ì´ ì°½ì— ë‹¤ì‹œ 붙여넣기 하십시오.",
+DlgPasteIgnoreFont : "í°íŠ¸ 설정 무시",
+DlgPasteRemoveStyles : "ìŠ¤íƒ€ì¼ ì •ì˜ ì œê±°",
+
+// Color Picker
+ColorAutomatic : "기본색ìƒ",
+ColorMoreColors : "색ìƒì„ íƒ...",
+
+// Document Properties
+DocProps : "문서 ì†ì„±",
+
+// Anchor Dialog
+DlgAnchorTitle : "책갈피 ì†ì„±",
+DlgAnchorName : "책갈피 ì´ë¦„",
+DlgAnchorErrorName : "책갈피 ì´ë¦„ì„ ìž…ë ¥í•˜ì‹­ì‹œìš”.",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "ì‚¬ì „ì— ì—†ëŠ” 단어",
+DlgSpellChangeTo : "변경할 단어",
+DlgSpellBtnIgnore : "건너뜀",
+DlgSpellBtnIgnoreAll : "ëª¨ë‘ ê±´ë„ˆëœ€",
+DlgSpellBtnReplace : "변경",
+DlgSpellBtnReplaceAll : "ëª¨ë‘ ë³€ê²½",
+DlgSpellBtnUndo : "취소",
+DlgSpellNoSuggestions : "- 추천단어 ì—†ìŒ -",
+DlgSpellProgress : "ì² ìžê²€ì‚¬ë¥¼ 진행중입니다...",
+DlgSpellNoMispell : "ì² ìžê²€ì‚¬ 완료: ìž˜ëª»ëœ ì² ìžê°€ 없습니다.",
+DlgSpellNoChanges : "ì² ìžê²€ì‚¬ 완료: ë³€ê²½ëœ ë‹¨ì–´ê°€ 없습니다.",
+DlgSpellOneChange : "ì² ìžê²€ì‚¬ 완료: 단어가 변경ë˜ì—ˆìŠµë‹ˆë‹¤.",
+DlgSpellManyChanges : "ì² ìžê²€ì‚¬ 완료: %1 단어가 변경ë˜ì—ˆìŠµë‹ˆë‹¤.",
+
+IeSpellDownload : "ì² ìž ê²€ì‚¬ê¸°ê°€ 철치ë˜ì§€ 않았습니다. 지금 다운로드하시겠습니까?",
+
+// Button Dialog
+DlgButtonText : "버튼글ìž(ê°’)",
+DlgButtonType : "버튼종류",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ì´ë¦„",
+DlgCheckboxValue : "ê°’",
+DlgCheckboxSelected : "ì„ íƒë¨",
+
+// Form Dialog
+DlgFormName : "í¼ì´ë¦„",
+DlgFormAction : "실행경로(Action)",
+DlgFormMethod : "방법(Method)",
+
+// Select Field Dialog
+DlgSelectName : "ì´ë¦„",
+DlgSelectValue : "ê°’",
+DlgSelectSize : "세로í¬ê¸°",
+DlgSelectLines : "줄",
+DlgSelectChkMulti : "여러항목 ì„ íƒ í—ˆìš©",
+DlgSelectOpAvail : "ì„ íƒì˜µì…˜",
+DlgSelectOpText : "ì´ë¦„",
+DlgSelectOpValue : "ê°’",
+DlgSelectBtnAdd : "추가",
+DlgSelectBtnModify : "변경",
+DlgSelectBtnUp : "위로",
+DlgSelectBtnDown : "아래로",
+DlgSelectBtnSetValue : "ì„ íƒëœê²ƒìœ¼ë¡œ 설정",
+DlgSelectBtnDelete : "삭제",
+
+// Textarea Dialog
+DlgTextareaName : "ì´ë¦„",
+DlgTextareaCols : "칸수",
+DlgTextareaRows : "줄수",
+
+// Text Field Dialog
+DlgTextName : "ì´ë¦„",
+DlgTextValue : "ê°’",
+DlgTextCharWidth : "ê¸€ìž ë„ˆë¹„",
+DlgTextMaxChars : "최대 글ìžìˆ˜",
+DlgTextType : "종류",
+DlgTextTypeText : "문ìžì—´",
+DlgTextTypePass : "비밀번호",
+
+// Hidden Field Dialog
+DlgHiddenName : "ì´ë¦„",
+DlgHiddenValue : "ê°’",
+
+// Bulleted List Dialog
+BulletedListProp : "순서없는 ëª©ë¡ ì†ì„±",
+NumberedListProp : "순서있는 ëª©ë¡ ì†ì„±",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "종류",
+DlgLstTypeCircle : "ì›(Circle)",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "네모ì (Square)",
+DlgLstTypeNumbers : "번호 (1, 2, 3)",
+DlgLstTypeLCase : "ì†Œë¬¸ìž (a, b, c)",
+DlgLstTypeUCase : "ëŒ€ë¬¸ìž (A, B, C)",
+DlgLstTypeSRoman : "ë¡œë§ˆìž ìˆ˜ë¬¸ìž (i, ii, iii)",
+DlgLstTypeLRoman : "ë¡œë§ˆìž ëŒ€ë¬¸ìž (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "ì¼ë°˜",
+DlgDocBackTab : "ë°°ê²½",
+DlgDocColorsTab : "ìƒ‰ìƒ ë° ì—¬ë°±",
+DlgDocMetaTab : "메타ë°ì´í„°",
+
+DlgDocPageTitle : "페ì´ì§€ëª…",
+DlgDocLangDir : "ë¬¸ìž ì“°ê¸°ë°©í–¥",
+DlgDocLangDirLTR : "왼쪽ì—ì„œ 오른쪽 (LTR)",
+DlgDocLangDirRTL : "오른쪽ì—ì„œ 왼쪽 (RTL)",
+DlgDocLangCode : "언어코드",
+DlgDocCharSet : "ìºë¦­í„°ì…‹ ì¸ì½”딩",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "다른 ìºë¦­í„°ì…‹ ì¸ì½”딩",
+
+DlgDocDocType : "문서 헤드",
+DlgDocDocTypeOther : "다른 문서헤드",
+DlgDocIncXHTML : "XHTML ë¬¸ì„œì •ì˜ í¬í•¨",
+DlgDocBgColor : "배경색ìƒ",
+DlgDocBgImage : "ë°°ê²½ì´ë¯¸ì§€ URL",
+DlgDocBgNoScroll : "스í¬ë¡¤ë˜ì§€ì•ŠëŠ” ë°°ê²½",
+DlgDocCText : "í…스트",
+DlgDocCLink : "ë§í¬",
+DlgDocCVisited : "방문한 ë§í¬(Visited)",
+DlgDocCActive : "í™œì„±í™”ëœ ë§í¬(Active)",
+DlgDocMargins : "페ì´ì§€ 여백",
+DlgDocMaTop : "위",
+DlgDocMaLeft : "왼쪽",
+DlgDocMaRight : "오른쪽",
+DlgDocMaBottom : "아래",
+DlgDocMeIndex : "문서 키워드 (콤마로 구분)",
+DlgDocMeDescr : "문서 설명",
+DlgDocMeAuthor : "작성ìž",
+DlgDocMeCopy : "저작권",
+DlgDocPreview : "미리보기",
+
+// Templates Dialog
+Templates : "템플릿",
+DlgTemplatesTitle : "내용 템플릿",
+DlgTemplatesSelMsg : "ì—디터ì—ì„œ 사용할 í…œí”Œë¦¿ì„ ì„ íƒí•˜ì‹­ì‹œìš”.<br>(지금까지 ìž‘ì„±ëœ ë‚´ìš©ì€ ì‚¬ë¼ì§‘니다.):",
+DlgTemplatesLoading : "템플릿 목ë¡ì„ 불러오는중입니다. 잠시만 기다려주십시요.",
+DlgTemplatesNoTpl : "(í…œí”Œë¦¿ì´ ì—†ìŠµë‹ˆë‹¤.)",
+DlgTemplatesReplace : "현재 내용 바꾸기",
+
+// About Dialog
+DlgAboutAboutTab : "About",
+DlgAboutBrowserInfoTab : "브ë¼ìš°ì € ì •ë³´",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "버전",
+DlgAboutInfo : "ë” ë§Žì€ ì •ë³´ë¥¼ 보시려면 ë‹¤ìŒ ì‚¬ì´íŠ¸ë¡œ 가십시오.",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/lt.js b/httemplate/elements/fckeditor/editor/lang/lt.js
new file mode 100644
index 0000000..44f5da6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/lt.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Lithuanian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Sutraukti mygtukų juostą",
+ToolbarExpand : "Išplėsti mygtukų juostą",
+
+// Toolbar Items and Context Menu
+Save : "IÅ¡saugoti",
+NewPage : "Naujas puslapis",
+Preview : "Peržiūra",
+Cut : "IÅ¡kirpti",
+Copy : "Kopijuoti",
+Paste : "Įdėti",
+PasteText : "Įdėti kaip gryną tekstą",
+PasteWord : "Įdėti iš Word",
+Print : "Spausdinti",
+SelectAll : "Pažymėti viską",
+RemoveFormat : "Panaikinti formatÄ…",
+InsertLinkLbl : "Nuoroda",
+InsertLink : "Įterpti/taisyti nuorodą",
+RemoveLink : "Panaikinti nuorodÄ…",
+VisitLink : "Atidaryti nuorodÄ…",
+Anchor : "Įterpti/modifikuoti žymę",
+AnchorDelete : "Naikinti žymę",
+InsertImageLbl : "Vaizdas",
+InsertImage : "Įterpti/taisyti vaizdą",
+InsertFlashLbl : "Flash",
+InsertFlash : "Įterpti/taisyti Flash",
+InsertTableLbl : "LentelÄ—",
+InsertTable : "Įterpti/taisyti lentelę",
+InsertLineLbl : "Linija",
+InsertLine : "Įterpti horizontalią liniją",
+InsertSpecialCharLbl: "Spec. simbolis",
+InsertSpecialChar : "Įterpti specialų simbolį",
+InsertSmileyLbl : "Veideliai",
+InsertSmiley : "Įterpti veidelį",
+About : "Apie FCKeditor",
+Bold : "Pusjuodis",
+Italic : "Kursyvas",
+Underline : "Pabrauktas",
+StrikeThrough : "Perbrauktas",
+Subscript : "Apatinis indeksas",
+Superscript : "Viršutinis indeksas",
+LeftJustify : "Lygiuoti kairÄ™",
+CenterJustify : "Centruoti",
+RightJustify : "Lygiuoti dešinę",
+BlockJustify : "Lygiuoti abi puses",
+DecreaseIndent : "Sumažinti įtrauką",
+IncreaseIndent : "Padidinti įtrauką",
+Blockquote : "Citata",
+CreateDiv : "Sukurti Div elementÄ…",
+EditDiv : "Reaguoti Div elementÄ…",
+DeleteDiv : "Å alinti Div elementÄ…",
+Undo : "Atšaukti",
+Redo : "Atstatyti",
+NumberedListLbl : "Numeruotas sąrašas",
+NumberedList : "Įterpti/Panaikinti numeruotą sąrašą",
+BulletedListLbl : "Suženklintas sąrašas",
+BulletedList : "Įterpti/Panaikinti suženklintą sąrašą",
+ShowTableBorders : "Rodyti lentelÄ—s rÄ—mus",
+ShowDetails : "Rodyti detales",
+Style : "Stilius",
+FontFormat : "Å rifto formatas",
+Font : "Å riftas",
+FontSize : "Å rifto dydis",
+TextColor : "Teksto spalva",
+BGColor : "Fono spalva",
+Source : "Å altinis",
+Find : "Rasti",
+Replace : "Pakeisti",
+SpellCheck : "Rašybos tikrinimas",
+UniversalKeyboard : "Universali klaviatūra",
+PageBreakLbl : "Puslapių skirtukas",
+PageBreak : "Įterpti puslapių skirtuką",
+
+Form : "Forma",
+Checkbox : "Žymimasis langelis",
+RadioButton : "Žymimoji akutė",
+TextField : "Teksto laukas",
+Textarea : "Teksto sritis",
+HiddenField : "Nerodomas laukas",
+Button : "Mygtukas",
+SelectionField : "Atrankos laukas",
+ImageButton : "Vaizdinis mygtukas",
+
+FitWindow : "Padidinti redaktorių",
+ShowBlocks : "Rodyti blokus",
+
+// Context Menu
+EditLink : "Taisyti nuorodÄ…",
+CellCM : "Langelis",
+RowCM : "EilutÄ—",
+ColumnCM : "Stulpelis",
+InsertRowAfter : "Įterpti eilutę po",
+InsertRowBefore : "Įterpti eilutę prieš",
+DeleteRows : "Å alinti eilutes",
+InsertColumnAfter : "Įterpti stulpelį po",
+InsertColumnBefore : "Įterpti stulpelį prieš",
+DeleteColumns : "Å alinti stulpelius",
+InsertCellAfter : "Įterpti langelį po",
+InsertCellBefore : "Įterpti langelį prieš",
+DeleteCells : "Å alinti langelius",
+MergeCells : "Sujungti langelius",
+MergeRight : "Sujungti su dešine",
+MergeDown : "Sujungti su apaÄia",
+HorizontalSplitCell : "Skaidyti langelį horizontaliai",
+VerticalSplitCell : "Skaidyti langelį vertikaliai",
+TableDelete : "Å alinti lentelÄ™",
+CellProperties : "Langelio savybÄ—s",
+TableProperties : "LentelÄ—s savybÄ—s",
+ImageProperties : "Vaizdo savybÄ—s",
+FlashProperties : "Flash savybÄ—s",
+
+AnchorProp : "Žymės savybės",
+ButtonProp : "Mygtuko savybÄ—s",
+CheckboxProp : "Žymimojo langelio savybės",
+HiddenFieldProp : "Nerodomo lauko savybÄ—s",
+RadioButtonProp : "Žymimosios akutės savybės",
+ImageButtonProp : "Vaizdinio mygtuko savybÄ—s",
+TextFieldProp : "Teksto lauko savybÄ—s",
+SelectionFieldProp : "Atrankos lauko savybÄ—s",
+TextareaProp : "Teksto srities savybÄ—s",
+FormProp : "Formos savybÄ—s",
+
+FontFormats : "Normalus;Formuotas;Kreipinio;Antraštinis 1;Antraštinis 2;Antraštinis 3;Antraštinis 4;Antraštinis 5;Antraštinis 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Apdorojamas XHTML. Prašome palaukti...",
+Done : "Baigta",
+PasteWordConfirm : "Įdedamas tekstas yra panašus į kopiją iš Word. Ar Jūs norite prieš įdėjimą išvalyti jį?",
+NotCompatiblePaste : "Ši komanda yra prieinama tik per Internet Explorer 5.5 ar aukštesnę versiją. Ar Jūs norite įterpti be valymo?",
+UnknownToolbarItem : "Nežinomas mygtukų juosta elementas \"%1\"",
+UnknownCommand : "Nežinomas komandos vardas \"%1\"",
+NotImplemented : "Komanda nėra įgyvendinta",
+UnknownToolbarSet : "Mygtukų juostos rinkinys \"%1\" neegzistuoja",
+NoActiveX : "Jūsų naršyklės saugumo nuostatos gali riboti kai kurias redaktoriaus savybes. Jūs turite aktyvuoti opciją \"Run ActiveX controls and plug-ins\". Kitu atveju Jums bus pranešama apie klaidas ir trūkstamas savybes.",
+BrowseServerBlocked : "Neįmanoma atidaryti naujo narÅ¡yklÄ—s lango. Ä®sitikinkite, kad iÅ¡kylanÄių langų blokavimo programos neveiksnios.",
+DialogBlocked : "Neįmanoma atidaryti dialogo lango. Ä®sitikinkite, kad iÅ¡kylanÄių langų blokavimo programos neveiksnios.",
+VisitLinkBlocked : "Neįmanoma atidaryti naujo lango. Ä®sitikinkite, kad iÅ¡kylanÄių langų blokavimo programos neveiksnios.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Nutraukti",
+DlgBtnClose : "Uždaryti",
+DlgBtnBrowseServer : "Naršyti po serverį",
+DlgAdvancedTag : "Papildomas",
+DlgOpOther : "<Kita>",
+DlgInfoTab : "Informacija",
+DlgAlertUrl : "Prašome įrašyti URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nÄ—ra nustatyta>",
+DlgGenId : "Id",
+DlgGenLangDir : "Teksto kryptis",
+DlgGenLangDirLtr : "Iš kairės į dešinę (LTR)",
+DlgGenLangDirRtl : "Iš dešinės į kairę (RTL)",
+DlgGenLangCode : "Kalbos kodas",
+DlgGenAccessKey : "Prieigos raktas",
+DlgGenName : "Vardas",
+DlgGenTabIndex : "Tabuliavimo indeksas",
+DlgGenLongDescr : "Ilgas aprašymas URL",
+DlgGenClass : "Stilių lentelės klasės",
+DlgGenTitle : "Konsultacinė antraštė",
+DlgGenContType : "Konsultacinio turinio tipas",
+DlgGenLinkCharset : "Susietų išteklių simbolių lentelė",
+DlgGenStyle : "Stilius",
+
+// Image Dialog
+DlgImgTitle : "Vaizdo savybÄ—s",
+DlgImgInfoTab : "Vaizdo informacija",
+DlgImgBtnUpload : "Siųsti į serverį",
+DlgImgURL : "URL",
+DlgImgUpload : "Nusiųsti",
+DlgImgAlt : "Alternatyvus Tekstas",
+DlgImgWidth : "Plotis",
+DlgImgHeight : "Aukštis",
+DlgImgLockRatio : "IÅ¡laikyti proporcijÄ…",
+DlgBtnResetSize : "Atstatyti dydį",
+DlgImgBorder : "RÄ—melis",
+DlgImgHSpace : "Hor.ErdvÄ—",
+DlgImgVSpace : "Vert.ErdvÄ—",
+DlgImgAlign : "Lygiuoti",
+DlgImgAlignLeft : "KairÄ™",
+DlgImgAlignAbsBottom: "AbsoliuÄiÄ… apaÄiÄ…",
+DlgImgAlignAbsMiddle: "Absoliutų vidurį",
+DlgImgAlignBaseline : "ApatinÄ™ linijÄ…",
+DlgImgAlignBottom : "ApaÄiÄ…",
+DlgImgAlignMiddle : "Vidurį",
+DlgImgAlignRight : "Dešinę",
+DlgImgAlignTextTop : "Teksto viršūnę",
+DlgImgAlignTop : "Viršūnę",
+DlgImgPreview : "Peržiūra",
+DlgImgAlertUrl : "Prašome įvesti vaizdo URL",
+DlgImgLinkTab : "Nuoroda",
+
+// Flash Dialog
+DlgFlashTitle : "Flash savybÄ—s",
+DlgFlashChkPlay : "Automatinis paleidimas",
+DlgFlashChkLoop : "Ciklas",
+DlgFlashChkMenu : "Leisti Flash meniu",
+DlgFlashScale : "Mastelis",
+DlgFlashScaleAll : "Rodyti visÄ…",
+DlgFlashScaleNoBorder : "Be rÄ—melio",
+DlgFlashScaleFit : "Tikslus atitikimas",
+
+// Link Dialog
+DlgLnkWindowTitle : "Nuoroda",
+DlgLnkInfoTab : "Nuorodos informacija",
+DlgLnkTargetTab : "Paskirtis",
+
+DlgLnkType : "Nuorodos tipas",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Žymė šiame puslapyje",
+DlgLnkTypeEMail : "El.paštas",
+DlgLnkProto : "Protokolas",
+DlgLnkProtoOther : "<kitas>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Pasirinkite žymę",
+DlgLnkAnchorByName : "Pagal žymės vardą",
+DlgLnkAnchorById : "Pagal žymės Id",
+DlgLnkNoAnchors : "(Šiame dokumente žymių nėra)",
+DlgLnkEMail : "El.pašto adresas",
+DlgLnkEMailSubject : "Žinutės tema",
+DlgLnkEMailBody : "Žinutės turinys",
+DlgLnkUpload : "Siųsti",
+DlgLnkBtnUpload : "Siųsti į serverį",
+
+DlgLnkTarget : "Paskirties vieta",
+DlgLnkTargetFrame : "<kadras>",
+DlgLnkTargetPopup : "<išskleidžiamas langas>",
+DlgLnkTargetBlank : "Naujas langas (_blank)",
+DlgLnkTargetParent : "Pirminis langas (_parent)",
+DlgLnkTargetSelf : "Tas pats langas (_self)",
+DlgLnkTargetTop : "Svarbiausias langas (_top)",
+DlgLnkTargetFrameName : "Paskirties kadro vardas",
+DlgLnkPopWinName : "Paskirties lango vardas",
+DlgLnkPopWinFeat : "Išskleidžiamo lango savybės",
+DlgLnkPopResize : "KeiÄiamas dydis",
+DlgLnkPopLocation : "Adreso juosta",
+DlgLnkPopMenu : "Meniu juosta",
+DlgLnkPopScroll : "Slinkties juostos",
+DlgLnkPopStatus : "BÅ«senos juosta",
+DlgLnkPopToolbar : "Mygtukų juosta",
+DlgLnkPopFullScrn : "Visas ekranas (IE)",
+DlgLnkPopDependent : "Priklausomas (Netscape)",
+DlgLnkPopWidth : "Plotis",
+DlgLnkPopHeight : "Aukštis",
+DlgLnkPopLeft : "KairÄ— pozicija",
+DlgLnkPopTop : "Viršutinė pozicija",
+
+DlnLnkMsgNoUrl : "Prašome įvesti nuorodos URL",
+DlnLnkMsgNoEMail : "Prašome įvesti el.pašto adresą",
+DlnLnkMsgNoAnchor : "Prašome pasirinkti žymę",
+DlnLnkMsgInvPopName : "IÅ¡Å¡okanÄio lango pavadinimas privalo prasidÄ—ti lotyniÅ¡ka raide ir negali turÄ—ti tarpų",
+
+// Color Dialog
+DlgColorTitle : "Pasirinkite spalvÄ…",
+DlgColorBtnClear : "Trinti",
+DlgColorHighlight : "Paryškinta",
+DlgColorSelected : "Pažymėta",
+
+// Smiley Dialog
+DlgSmileyTitle : "Įterpti veidelį",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Pasirinkite specialų simbolį",
+
+// Table Dialog
+DlgTableTitle : "LentelÄ—s savybÄ—s",
+DlgTableRows : "EilutÄ—s",
+DlgTableColumns : "Stulpeliai",
+DlgTableBorder : "RÄ—melio dydis",
+DlgTableAlign : "Lygiuoti",
+DlgTableAlignNotSet : "<Nenustatyta>",
+DlgTableAlignLeft : "KairÄ™",
+DlgTableAlignCenter : "CentrÄ…",
+DlgTableAlignRight : "Dešinę",
+DlgTableWidth : "Plotis",
+DlgTableWidthPx : "taškais",
+DlgTableWidthPc : "procentais",
+DlgTableHeight : "Aukštis",
+DlgTableCellSpace : "Tarpas tarp langelių",
+DlgTableCellPad : "Trapas nuo langelio rÄ—mo iki teksto",
+DlgTableCaption : "Antraštė",
+DlgTableSummary : "Santrauka",
+DlgTableHeaders : "Antraštės",
+DlgTableHeadersNone : "NÄ—ra",
+DlgTableHeadersColumn : "Pirmas stulpelis",
+DlgTableHeadersRow : "Pirma eilutÄ—",
+DlgTableHeadersBoth : "Abu",
+
+// Table Cell Dialog
+DlgCellTitle : "Langelio savybÄ—s",
+DlgCellWidth : "Plotis",
+DlgCellWidthPx : "taškais",
+DlgCellWidthPc : "procentais",
+DlgCellHeight : "Aukštis",
+DlgCellWordWrap : "Teksto laužymas",
+DlgCellWordWrapNotSet : "<Nenustatyta>",
+DlgCellWordWrapYes : "Taip",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Horizontaliai lygiuoti",
+DlgCellHorAlignNotSet : "<Nenustatyta>",
+DlgCellHorAlignLeft : "KairÄ™",
+DlgCellHorAlignCenter : "CentrÄ…",
+DlgCellHorAlignRight: "Dešinę",
+DlgCellVerAlign : "Vertikaliai lygiuoti",
+DlgCellVerAlignNotSet : "<Nenustatyta>",
+DlgCellVerAlignTop : "Viršų",
+DlgCellVerAlignMiddle : "Vidurį",
+DlgCellVerAlignBottom : "ApaÄiÄ…",
+DlgCellVerAlignBaseline : "ApatinÄ™ linijÄ…",
+DlgCellType : "Langelio tipas",
+DlgCellTypeData : "Duomenys",
+DlgCellTypeHeader : "Antraštė",
+DlgCellRowSpan : "EiluÄių apjungimas",
+DlgCellCollSpan : "Stulpelių apjungimas",
+DlgCellBackColor : "Fono spalva",
+DlgCellBorderColor : "RÄ—melio spalva",
+DlgCellBtnSelect : "Pažymėti...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Surasti ir pakeisti",
+
+// Find Dialog
+DlgFindTitle : "Paieška",
+DlgFindFindBtn : "Surasti",
+DlgFindNotFoundMsg : "Nurodytas tekstas nerastas.",
+
+// Replace Dialog
+DlgReplaceTitle : "Pakeisti",
+DlgReplaceFindLbl : "Surasti tekstÄ…:",
+DlgReplaceReplaceLbl : "Pakeisti tekstu:",
+DlgReplaceCaseChk : "Skirti didžiąsias ir mažąsias raides",
+DlgReplaceReplaceBtn : "Pakeisti",
+DlgReplaceReplAllBtn : "Pakeisti viskÄ…",
+DlgReplaceWordChk : "Atitikti pilną žodį",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Jūsų naršyklės saugumo nustatymai neleidžia redaktoriui automatiškai įvykdyti iškirpimo operacijų. Tam prašome naudoti klaviatūrą (Ctrl+X).",
+PasteErrorCopy : "Jūsų naršyklės saugumo nustatymai neleidžia redaktoriui automatiškai įvykdyti kopijavimo operacijų. Tam prašome naudoti klaviatūrą (Ctrl+C).",
+
+PasteAsText : "Įdėti kaip gryną tekstą",
+PasteFromWord : "Įdėti iš Word",
+
+DlgPasteMsg2 : "Žemiau esanÄiame įvedimo lauke įdÄ—kite tekstÄ…, naudodami klaviatÅ«rÄ… (<STRONG>Ctrl+V</STRONG>) ir paspauskite mygtukÄ… <STRONG>OK</STRONG>.",
+DlgPasteSec : "Dėl jūsų naršyklės saugumo nustatymų, redaktorius negali tiesiogiai pasiekti laikinosios atminties. Jums reikia nukopijuoti dar kartą į šį langą.",
+DlgPasteIgnoreFont : "Ignoruoti šriftų nustatymus",
+DlgPasteRemoveStyles : "Pašalinti stilių nustatymus",
+
+// Color Picker
+ColorAutomatic : "Automatinis",
+ColorMoreColors : "Daugiau spalvų...",
+
+// Document Properties
+DocProps : "Dokumento savybÄ—s",
+
+// Anchor Dialog
+DlgAnchorTitle : "Žymės savybės",
+DlgAnchorName : "Žymės vardas",
+DlgAnchorErrorName : "Prašome įvesti žymės vardą",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Žodyne nerastas",
+DlgSpellChangeTo : "Pakeisti į",
+DlgSpellBtnIgnore : "Ignoruoti",
+DlgSpellBtnIgnoreAll : "Ignoruoti visus",
+DlgSpellBtnReplace : "Pakeisti",
+DlgSpellBtnReplaceAll : "Pakeisti visus",
+DlgSpellBtnUndo : "Atšaukti",
+DlgSpellNoSuggestions : "- Nėra pasiūlymų -",
+DlgSpellProgress : "Vyksta rašybos tikrinimas...",
+DlgSpellNoMispell : "Rašybos tikrinimas baigtas: Nerasta rašybos klaidų",
+DlgSpellNoChanges : "Rašybos tikrinimas baigtas: Nėra pakeistų žodžių",
+DlgSpellOneChange : "Rašybos tikrinimas baigtas: Vienas žodis pakeistas",
+DlgSpellManyChanges : "Rašybos tikrinimas baigtas: Pakeista %1 žodžių",
+
+IeSpellDownload : "Rašybos tikrinimas neinstaliuotas. Ar Jūs norite jį dabar atsisiųsti?",
+
+// Button Dialog
+DlgButtonText : "Tekstas (Reikšmė)",
+DlgButtonType : "Tipas",
+DlgButtonTypeBtn : "Mygtukas",
+DlgButtonTypeSbm : "Siųsti",
+DlgButtonTypeRst : "IÅ¡valyti",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Vardas",
+DlgCheckboxValue : "Reikšmė",
+DlgCheckboxSelected : "Pažymėtas",
+
+// Form Dialog
+DlgFormName : "Vardas",
+DlgFormAction : "Veiksmas",
+DlgFormMethod : "Metodas",
+
+// Select Field Dialog
+DlgSelectName : "Vardas",
+DlgSelectValue : "Reikšmė",
+DlgSelectSize : "Dydis",
+DlgSelectLines : "eiluÄių",
+DlgSelectChkMulti : "Leisti daugeriopÄ… atrankÄ…",
+DlgSelectOpAvail : "Galimos parinktys",
+DlgSelectOpText : "Tekstas",
+DlgSelectOpValue : "Reikšmė",
+DlgSelectBtnAdd : "Įtraukti",
+DlgSelectBtnModify : "Modifikuoti",
+DlgSelectBtnUp : "Aukštyn",
+DlgSelectBtnDown : "Žemyn",
+DlgSelectBtnSetValue : "Laikyti pažymėta reikšme",
+DlgSelectBtnDelete : "Trinti",
+
+// Textarea Dialog
+DlgTextareaName : "Vardas",
+DlgTextareaCols : "Ilgis",
+DlgTextareaRows : "Plotis",
+
+// Text Field Dialog
+DlgTextName : "Vardas",
+DlgTextValue : "Reikšmė",
+DlgTextCharWidth : "Ilgis simboliais",
+DlgTextMaxChars : "Maksimalus simbolių skaiÄius",
+DlgTextType : "Tipas",
+DlgTextTypeText : "Tekstas",
+DlgTextTypePass : "Slaptažodis",
+
+// Hidden Field Dialog
+DlgHiddenName : "Vardas",
+DlgHiddenValue : "Reikšmė",
+
+// Bulleted List Dialog
+BulletedListProp : "Suženklinto sąrašo savybės",
+NumberedListProp : "Numeruoto sąrašo savybės",
+DlgLstStart : "PradÄ—ti nuo",
+DlgLstType : "Tipas",
+DlgLstTypeCircle : "Apskritimas",
+DlgLstTypeDisc : "Diskas",
+DlgLstTypeSquare : "Kvadratas",
+DlgLstTypeNumbers : "SkaiÄiai (1, 2, 3)",
+DlgLstTypeLCase : "Mažosios raidės (a, b, c)",
+DlgLstTypeUCase : "Didžiosios raidės (A, B, C)",
+DlgLstTypeSRoman : "RomÄ—nų mažieji skaiÄiai (i, ii, iii)",
+DlgLstTypeLRoman : "RomÄ—nų didieji skaiÄiai (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Bendros savybÄ—s",
+DlgDocBackTab : "Fonas",
+DlgDocColorsTab : "Spalvos ir kraštinės",
+DlgDocMetaTab : "Meta duomenys",
+
+DlgDocPageTitle : "Puslapio antraštė",
+DlgDocLangDir : "Kalbos kryptis",
+DlgDocLangDirLTR : "Iš kairės į dešinę (LTR)",
+DlgDocLangDirRTL : "Iš dešinės į kairę (RTL)",
+DlgDocLangCode : "Kalbos kodas",
+DlgDocCharSet : "Simbolių kodavimo lentelė",
+DlgDocCharSetCE : "CentrinÄ—s Europos",
+DlgDocCharSetCT : "Tradicinės kinų (Big5)",
+DlgDocCharSetCR : "Kirilica",
+DlgDocCharSetGR : "Graikų",
+DlgDocCharSetJP : "Japonų",
+DlgDocCharSetKR : "KorÄ—jieÄių",
+DlgDocCharSetTR : "Turkų",
+DlgDocCharSetUN : "Unikodas (UTF-8)",
+DlgDocCharSetWE : "Vakarų Europos",
+DlgDocCharSetOther : "Kita simbolių kodavimo lentelė",
+
+DlgDocDocType : "Dokumento tipo antraštė",
+DlgDocDocTypeOther : "Kita dokumento tipo antraštė",
+DlgDocIncXHTML : "Įtraukti XHTML deklaracijas",
+DlgDocBgColor : "Fono spalva",
+DlgDocBgImage : "Fono paveikslÄ—lio nuoroda (URL)",
+DlgDocBgNoScroll : "Neslenkantis fonas",
+DlgDocCText : "Tekstas",
+DlgDocCLink : "Nuoroda",
+DlgDocCVisited : "Aplankyta nuoroda",
+DlgDocCActive : "Aktyvi nuoroda",
+DlgDocMargins : "Puslapio kraštinės",
+DlgDocMaTop : "Viršuje",
+DlgDocMaLeft : "KairÄ—je",
+DlgDocMaRight : "Dešinėje",
+DlgDocMaBottom : "ApaÄioje",
+DlgDocMeIndex : "Dokumento indeksavimo raktiniai žodžiai (atskirti kableliais)",
+DlgDocMeDescr : "Dokumento apibūdinimas",
+DlgDocMeAuthor : "Autorius",
+DlgDocMeCopy : "AutorinÄ—s teisÄ—s",
+DlgDocPreview : "Peržiūra",
+
+// Templates Dialog
+Templates : "Å ablonai",
+DlgTemplatesTitle : "Turinio Å¡ablonai",
+DlgTemplatesSelMsg : "Pasirinkite norimÄ… Å¡ablonÄ…<br>(<b>DÄ—mesio!</b> esamas turinys bus prarastas):",
+DlgTemplatesLoading : "Įkeliamas šablonų sąrašas. Prašome palaukti...",
+DlgTemplatesNoTpl : "(Å ablonų sÄ…raÅ¡as tuÅ¡Äias)",
+DlgTemplatesReplace : "Pakeisti dabartinį turinį pasirinktu šablonu",
+
+// About Dialog
+DlgAboutAboutTab : "Apie",
+DlgAboutBrowserInfoTab : "Naršyklės informacija",
+DlgAboutLicenseTab : "Licenzija",
+DlgAboutVersion : "versija",
+DlgAboutInfo : "PapildomÄ… informacijÄ… galima gauti",
+
+// Div Dialog
+DlgDivGeneralTab : "Bendros savybÄ—s",
+DlgDivAdvancedTab : "Papildomos savybÄ—s",
+DlgDivStyle : "Stilius",
+DlgDivInlineStyle : "Stilius kode",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/lv.js b/httemplate/elements/fckeditor/editor/lang/lv.js
new file mode 100644
index 0000000..666dca7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/lv.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Latvian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "SamazinÄt rÄ«ku joslu",
+ToolbarExpand : "PaplaÅ¡inÄt rÄ«ku joslu",
+
+// Toolbar Items and Context Menu
+Save : "SaglabÄt",
+NewPage : "Jauna lapa",
+Preview : "PÄrskatÄ«t",
+Cut : "Izgriezt",
+Copy : "Kopēt",
+Paste : "Ievietot",
+PasteText : "Ievietot kÄ vienkÄrÅ¡u tekstu",
+PasteWord : "Ievietot no Worda",
+Print : "DrukÄt",
+SelectAll : "Iezīmēt visu",
+RemoveFormat : "Noņemt stilus",
+InsertLinkLbl : "Hipersaite",
+InsertLink : "Ievietot/Labot hipersaiti",
+RemoveLink : "Noņemt hipersaiti",
+VisitLink : "Open Link", //MISSING
+Anchor : "Ievietot/Labot iezīmi",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Attēls",
+InsertImage : "Ievietot/Labot Attēlu",
+InsertFlashLbl : "Flash",
+InsertFlash : "Ievietot/Labot Flash",
+InsertTableLbl : "Tabula",
+InsertTable : "Ievietot/Labot Tabulu",
+InsertLineLbl : "AtdalÄ«tÄjsvÄ«tra",
+InsertLine : "Ievietot horizontÄlu AtdalÄ«tÄjsvÄ«tru",
+InsertSpecialCharLbl: "Īpašs simbols",
+InsertSpecialChar : "Ievietot speciÄlo simbolu",
+InsertSmileyLbl : "Smaidiņi",
+InsertSmiley : "Ievietot smaidiņu",
+About : "ĪsumÄ par FCKeditor",
+Bold : "Treknu Å¡riftu",
+Italic : "SlÄ«prakstÄ",
+Underline : "Apakšsvītra",
+StrikeThrough : "PÄrsvÄ«trots",
+Subscript : "ZemrakstÄ",
+Superscript : "AugÅ¡rakstÄ",
+LeftJustify : "IzlÄ«dzinÄt pa kreisi",
+CenterJustify : "IzlÄ«dzinÄt pret centru",
+RightJustify : "IzlÄ«dzinÄt pa labi",
+BlockJustify : "IzlÄ«dzinÄt malas",
+DecreaseIndent : "SamazinÄt atkÄpi",
+IncreaseIndent : "PalielinÄt atkÄpi",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Atcelt",
+Redo : "AtkÄrtot",
+NumberedListLbl : "Numurēts saraksts",
+NumberedList : "Ievietot/Noņemt numerēto sarakstu",
+BulletedListLbl : "Izcelts saraksts",
+BulletedList : "Ievietot/Noņemt izceltu sarakstu",
+ShowTableBorders : "ParÄdÄ«t tabulas robežas",
+ShowDetails : "ParÄdÄ«t sÄ«kÄku informÄciju",
+Style : "Stils",
+FontFormat : "FormÄts",
+Font : "Å rifts",
+FontSize : "Izmērs",
+TextColor : "Teksta krÄsa",
+BGColor : "Fona krÄsa",
+Source : "HTML kods",
+Find : "Meklēt",
+Replace : "Nomainīt",
+SpellCheck : "PareizrakstÄ«bas pÄrbaude",
+UniversalKeyboard : "UniversÄla klaviatÅ«ra",
+PageBreakLbl : "Lapas pÄrtraukums",
+PageBreak : "Ievietot lapas pÄrtraukumu",
+
+Form : "Forma",
+Checkbox : "Atzīmēšanas kastīte",
+RadioButton : "Izvēles poga",
+TextField : "Teksta rinda",
+Textarea : "Teksta laukums",
+HiddenField : "Paslēpta teksta rinda",
+Button : "Poga",
+SelectionField : "Iezīmēšanas lauks",
+ImageButton : "Attēlpoga",
+
+FitWindow : "Maksimizēt redaktora izmēru",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Labot hipersaiti",
+CellCM : "Å Å«na",
+RowCM : "Rinda",
+ColumnCM : "Kolonna",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Dzēst rindas",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Dzēst kolonnas",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Dzēst rūtiņas",
+MergeCells : "Apvienot rūtiņas",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Dzēst tabulu",
+CellProperties : "Rūtiņas īpašības",
+TableProperties : "Tabulas īpašības",
+ImageProperties : "Attēla īpašības",
+FlashProperties : "Flash īpašības",
+
+AnchorProp : "Iezīmes īpašības",
+ButtonProp : "Pogas īpašības",
+CheckboxProp : "Atzīmēšanas kastītes īpašības",
+HiddenFieldProp : "PaslÄ“ptÄs teksta rindas Ä«paÅ¡Ä«bas",
+RadioButtonProp : "Izvēles poga īpašības",
+ImageButtonProp : "Attēlpogas īpašības",
+TextFieldProp : "Teksta rindas īpašības",
+SelectionFieldProp : "Iezīmēšanas lauka īpašības",
+TextareaProp : "Teksta laukuma īpašības",
+FormProp : "Formas īpašības",
+
+FontFormats : "NormÄls teksts;FormatÄ“ts teksts;Adrese;Virsraksts 1;Virsraksts 2;Virsraksts 3;Virsraksts 4;Virsraksts 5;Virsraksts 6;Rindkopa (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Tiek apstrÄdÄts XHTML. LÅ«dzu uzgaidiet...",
+Done : "Darīts",
+PasteWordConfirm : "Teksta fragments, kas tiek ievietots, izskatÄs, ka bÅ«tu sagatavots Word'Ä. Vai vÄ“laties to apstrÄdÄt pirms ievietoÅ¡anas?",
+NotCompatiblePaste : "Å Ä« darbÄ«ba ir pieejama Internet Explorer'Ä«, kas jaunÄks par 5.5 versiju. Vai vÄ“laties ievietot bez apstrÄdes?",
+UnknownToolbarItem : "NezinÄms rÄ«ku joslas objekts \"%1\"",
+UnknownCommand : "NezinÄmas darbÄ«bas nosaukums \"%1\"",
+NotImplemented : "Darbība netika paveikta",
+UnknownToolbarSet : "Rīku joslas komplekts \"%1\" neeksistē",
+NoActiveX : "Interneta pÄrlÅ«kprogrammas droÅ¡Ä«bas uzstÄdÄ«jumi varÄ“tu ietekmÄ“t dažas no redaktora Ä«paÅ¡Ä«bÄm. JÄbÅ«t aktivizÄ“tai sadaļai \"Run ActiveX controls and plug-ins\". SavÄdÄk ir iespÄ“jamas kļūdas darbÄ«bÄ un kļūdu paziņojumu parÄdÄ«Å¡anÄs.",
+BrowseServerBlocked : "Resursu pÄrlÅ«ks nevar tikt atvÄ“rts. PÄrliecinieties, ka uznirstoÅ¡o logu bloÄ·Ä“tÄji ir atslÄ“gti.",
+DialogBlocked : "Nav iespÄ“jams atvÄ“rt dialoglogu. PÄrliecinieties, ka uznirstoÅ¡o logu bloÄ·Ä“tÄji ir atslÄ“gti.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "Darīts!",
+DlgBtnCancel : "Atcelt",
+DlgBtnClose : "Aizvērt",
+DlgBtnBrowseServer : "Skatīt servera saturu",
+DlgAdvancedTag : "Izvērstais",
+DlgOpOther : "<Cits>",
+DlgInfoTab : "InformÄcija",
+DlgAlertUrl : "LÅ«dzu, ievietojiet hipersaiti",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nav iestatīts>",
+DlgGenId : "Id",
+DlgGenLangDir : "Valodas lasīšanas virziens",
+DlgGenLangDirLtr : "No kreisÄs uz labo (LTR)",
+DlgGenLangDirRtl : "No labÄs uz kreiso (RTL)",
+DlgGenLangCode : "Valodas kods",
+DlgGenAccessKey : "Pieejas kods",
+DlgGenName : "Nosaukums",
+DlgGenTabIndex : "Ciļņu indekss",
+DlgGenLongDescr : "Gara apraksta Hipersaite",
+DlgGenClass : "Stilu saraksta klases",
+DlgGenTitle : "Konsultatīvs virsraksts",
+DlgGenContType : "Konsultatīvs satura tips",
+DlgGenLinkCharset : "PievienotÄ resursa kodu tabula",
+DlgGenStyle : "Stils",
+
+// Image Dialog
+DlgImgTitle : "Attēla īpašības",
+DlgImgInfoTab : "InformÄcija par attÄ“lu",
+DlgImgBtnUpload : "Nosūtīt serverim",
+DlgImgURL : "URL",
+DlgImgUpload : "AugÅ¡upielÄdÄ“t",
+DlgImgAlt : "Alternatīvais teksts",
+DlgImgWidth : "Platums",
+DlgImgHeight : "Augstums",
+DlgImgLockRatio : "Nemainīga Augstuma/Platuma attiecība",
+DlgBtnResetSize : "Atjaunot sÄkotnÄ“jo izmÄ“ru",
+DlgImgBorder : "RÄmis",
+DlgImgHSpace : "HorizontÄlÄ telpa",
+DlgImgVSpace : "VertikÄlÄ telpa",
+DlgImgAlign : "NolÄ«dzinÄt",
+DlgImgAlignLeft : "Pa kreisi",
+DlgImgAlignAbsBottom: "AbsolÅ«ti apakÅ¡Ä",
+DlgImgAlignAbsMiddle: "AbsolÅ«ti vertikÄli centrÄ“ts",
+DlgImgAlignBaseline : "PamatrindÄ",
+DlgImgAlignBottom : "ApakÅ¡Ä",
+DlgImgAlignMiddle : "VertikÄli centrÄ“ts",
+DlgImgAlignRight : "Pa labi",
+DlgImgAlignTextTop : "Teksta augÅ¡Ä",
+DlgImgAlignTop : "AugÅ¡Ä",
+DlgImgPreview : "PÄrskats",
+DlgImgAlertUrl : "LÅ«dzu norÄdÄ«t attÄ“la hipersaiti",
+DlgImgLinkTab : "Hipersaite",
+
+// Flash Dialog
+DlgFlashTitle : "Flash īpašības",
+DlgFlashChkPlay : "AutomÄtiska atskaņoÅ¡ana",
+DlgFlashChkLoop : "NepÄrtraukti",
+DlgFlashChkMenu : "Atļaut Flash izvēlni",
+DlgFlashScale : "Mainīt izmēru",
+DlgFlashScaleAll : "RÄdÄ«t visu",
+DlgFlashScaleNoBorder : "Bez rÄmja",
+DlgFlashScaleFit : "Precīzs izmērs",
+
+// Link Dialog
+DlgLnkWindowTitle : "Hipersaite",
+DlgLnkInfoTab : "Hipersaites informÄcija",
+DlgLnkTargetTab : "MÄ“rÄ·is",
+
+DlgLnkType : "Hipersaites tips",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "IezÄ«me Å¡ajÄ lapÄ",
+DlgLnkTypeEMail : "E-pasts",
+DlgLnkProto : "Protokols",
+DlgLnkProtoOther : "<cits>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Izvēlēties iezīmi",
+DlgLnkAnchorByName : "Pēc iezīmes nosaukuma",
+DlgLnkAnchorById : "PÄ“c elementa ID",
+DlgLnkNoAnchors : "(Å ajÄ dokumentÄ nav iezÄ«mju)",
+DlgLnkEMail : "E-pasta adrese",
+DlgLnkEMailSubject : "Ziņas tēma",
+DlgLnkEMailBody : "Ziņas saturs",
+DlgLnkUpload : "AugÅ¡upielÄdÄ“t",
+DlgLnkBtnUpload : "Nosūtīt serverim",
+
+DlgLnkTarget : "MÄ“rÄ·is",
+DlgLnkTargetFrame : "<ietvars>",
+DlgLnkTargetPopup : "<uznirstoÅ¡Ä logÄ>",
+DlgLnkTargetBlank : "JaunÄ logÄ (_blank)",
+DlgLnkTargetParent : "EsoÅ¡ajÄ logÄ (_parent)",
+DlgLnkTargetSelf : "TajÄ paÅ¡Ä logÄ (_self)",
+DlgLnkTargetTop : "VisredzamÄkajÄ logÄ (_top)",
+DlgLnkTargetFrameName : "MÄ“rÄ·a ietvara nosaukums",
+DlgLnkPopWinName : "UznirstoÅ¡Ä loga nosaukums",
+DlgLnkPopWinFeat : "UznirstoÅ¡Ä loga nosaukums Ä«paÅ¡Ä«bas",
+DlgLnkPopResize : "Ar mainÄmu izmÄ“ru",
+DlgLnkPopLocation : "AtraÅ¡anÄs vietas josla",
+DlgLnkPopMenu : "Izvēlnes josla",
+DlgLnkPopScroll : "Ritjoslas",
+DlgLnkPopStatus : "Statusa josla",
+DlgLnkPopToolbar : "RÄ«ku josla",
+DlgLnkPopFullScrn : "PilnÄ ekrÄnÄ (IE)",
+DlgLnkPopDependent : "Atkarīgs (Netscape)",
+DlgLnkPopWidth : "Platums",
+DlgLnkPopHeight : "Augstums",
+DlgLnkPopLeft : "KreisÄ koordinÄte",
+DlgLnkPopTop : "AugÅ¡Ä“jÄ koordinÄte",
+
+DlnLnkMsgNoUrl : "LÅ«dzu norÄdi hipersaiti",
+DlnLnkMsgNoEMail : "LÅ«dzu norÄdi e-pasta adresi",
+DlnLnkMsgNoAnchor : "LÅ«dzu norÄdi iezÄ«mi",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "IzvÄ“lies krÄsu",
+DlgColorBtnClear : "Dzēst",
+DlgColorHighlight : "Izcelt",
+DlgColorSelected : "Iezīmētais",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ievietot smaidiņu",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Ievietot īpašu simbolu",
+
+// Table Dialog
+DlgTableTitle : "Tabulas īpašības",
+DlgTableRows : "Rindas",
+DlgTableColumns : "Kolonnas",
+DlgTableBorder : "RÄmja izmÄ“rs",
+DlgTableAlign : "Novietojums",
+DlgTableAlignNotSet : "<nav norÄdÄ«ts>",
+DlgTableAlignLeft : "Pa kreisi",
+DlgTableAlignCenter : "Centrēti",
+DlgTableAlignRight : "Pa labi",
+DlgTableWidth : "Platums",
+DlgTableWidthPx : "pikseļos",
+DlgTableWidthPc : "procentuÄli",
+DlgTableHeight : "Augstums",
+DlgTableCellSpace : "Rūtiņu atstatums",
+DlgTableCellPad : "Rūtiņu nobīde",
+DlgTableCaption : "Leģenda",
+DlgTableSummary : "AnotÄcija",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Rūtiņas īpašības",
+DlgCellWidth : "Platums",
+DlgCellWidthPx : "pikseļi",
+DlgCellWidthPc : "procentos",
+DlgCellHeight : "Augstums",
+DlgCellWordWrap : "Teksta pÄrnese",
+DlgCellWordWrapNotSet : "<nav norÄdÄ«ta>",
+DlgCellWordWrapYes : "JÄ",
+DlgCellWordWrapNo : "NÄ“",
+DlgCellHorAlign : "HorizontÄla novietojums",
+DlgCellHorAlignNotSet : "<Nav norÄdÄ«ts>",
+DlgCellHorAlignLeft : "Pa kreisi",
+DlgCellHorAlignCenter : "Centrēti",
+DlgCellHorAlignRight: "Pa labi",
+DlgCellVerAlign : "VertikÄlais novietojums",
+DlgCellVerAlignNotSet : "<nav norÄdÄ«ts>",
+DlgCellVerAlignTop : "Augša",
+DlgCellVerAlignMiddle : "Vidus",
+DlgCellVerAlignBottom : "Apakša",
+DlgCellVerAlignBaseline : "PamatrindÄ",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Rindu pÄrnese",
+DlgCellCollSpan : "Kolonnu pÄrnese",
+DlgCellBackColor : "Fona krÄsa",
+DlgCellBorderColor : "RÄmja krÄsa",
+DlgCellBtnSelect : "Iezīmē...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "MeklÄ“tÄjs",
+DlgFindFindBtn : "Meklēt",
+DlgFindNotFoundMsg : "NorÄdÄ«tÄ frÄze netika atrasta.",
+
+// Replace Dialog
+DlgReplaceTitle : "Aizvietošana",
+DlgReplaceFindLbl : "Meklēt:",
+DlgReplaceReplaceLbl : "Nomainīt uz:",
+DlgReplaceCaseChk : "Reģistrjūtīgs",
+DlgReplaceReplaceBtn : "Aizvietot",
+DlgReplaceReplAllBtn : "Aizvietot visu",
+DlgReplaceWordChk : "JÄsakrÄ«t pilnÄ«bÄ",
+
+// Paste Operations / Dialog
+PasteErrorCut : "JÅ«su pÄrlÅ«kprogrammas droÅ¡Ä«bas iestatÄ«jumi nepieļauj editoram automÄtiski veikt izgrieÅ¡anas darbÄ«bu. LÅ«dzu, izmantojiet (Ctrl+X, lai veiktu Å¡o darbÄ«bu.",
+PasteErrorCopy : "JÅ«su pÄrlÅ«kprogrammas droÅ¡Ä«bas iestatÄ«jumi nepieļauj editoram automÄtiski veikt kopÄ“Å¡anas darbÄ«bu. LÅ«dzu, izmantojiet (Ctrl+C), lai veiktu Å¡o darbÄ«bu.",
+
+PasteAsText : "Ievietot kÄ vienkÄrÅ¡u tekstu",
+PasteFromWord : "Ievietot no Worda",
+
+DlgPasteMsg2 : "LÅ«dzu, ievietojiet tekstu Å¡ajÄ laukumÄ, izmantojot klaviatÅ«ru (<STRONG>Ctrl+V</STRONG>) un apstipriniet ar <STRONG>DarÄ«ts!</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "IgnorÄ“t iepriekÅ¡ norÄdÄ«tos fontus",
+DlgPasteRemoveStyles : "Noņemt norÄdÄ«tos stilus",
+
+// Color Picker
+ColorAutomatic : "AutomÄtiska",
+ColorMoreColors : "PlaÅ¡Äka palete...",
+
+// Document Properties
+DocProps : "Dokumenta īpašības",
+
+// Anchor Dialog
+DlgAnchorTitle : "Iezīmes īpašības",
+DlgAnchorName : "Iezīmes nosaukums",
+DlgAnchorErrorName : "LÅ«dzu norÄdiet iezÄ«mes nosaukumu",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Netika atrasts vÄrdnÄ«cÄ",
+DlgSpellChangeTo : "Nomainīt uz",
+DlgSpellBtnIgnore : "Ignorēt",
+DlgSpellBtnIgnoreAll : "Ignorēt visu",
+DlgSpellBtnReplace : "Aizvietot",
+DlgSpellBtnReplaceAll : "Aizvietot visu",
+DlgSpellBtnUndo : "Atcelt",
+DlgSpellNoSuggestions : "- Nav ieteikumu -",
+DlgSpellProgress : "Notiek pareizrakstÄ«bas pÄrbaude...",
+DlgSpellNoMispell : "PareizrakstÄ«bas pÄrbaude pabeigta: kļūdas netika atrastas",
+DlgSpellNoChanges : "PareizrakstÄ«bas pÄrbaude pabeigta: nekas netika labots",
+DlgSpellOneChange : "PareizrakstÄ«bas pÄrbaude pabeigta: 1 vÄrds izmainÄ«ts",
+DlgSpellManyChanges : "PareizrakstÄ«bas pÄrbaude pabeigta: %1 vÄrdi tika mainÄ«ti",
+
+IeSpellDownload : "PareizrakstÄ«bas pÄrbaudÄ«tÄjs nav pievienots. Vai vÄ“laties to lejupielÄdÄ“t tagad?",
+
+// Button Dialog
+DlgButtonText : "Teksts (vērtība)",
+DlgButtonType : "Tips",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nosaukums",
+DlgCheckboxValue : "Vērtība",
+DlgCheckboxSelected : "Iezīmēts",
+
+// Form Dialog
+DlgFormName : "Nosaukums",
+DlgFormAction : "Darbība",
+DlgFormMethod : "Metode",
+
+// Select Field Dialog
+DlgSelectName : "Nosaukums",
+DlgSelectValue : "Vērtība",
+DlgSelectSize : "Izmērs",
+DlgSelectLines : "rindas",
+DlgSelectChkMulti : "Atļaut vairÄkus iezÄ«mÄ“jumus",
+DlgSelectOpAvail : "PieejamÄs iespÄ“jas",
+DlgSelectOpText : "Teksts",
+DlgSelectOpValue : "Vērtība",
+DlgSelectBtnAdd : "Pievienot",
+DlgSelectBtnModify : "Veikt izmaiņas",
+DlgSelectBtnUp : "Augšup",
+DlgSelectBtnDown : "Lejup",
+DlgSelectBtnSetValue : "Noteikt kÄ iezÄ«mÄ“to vÄ“rtÄ«bu",
+DlgSelectBtnDelete : "Dzēst",
+
+// Textarea Dialog
+DlgTextareaName : "Nosaukums",
+DlgTextareaCols : "Kolonnas",
+DlgTextareaRows : "Rindas",
+
+// Text Field Dialog
+DlgTextName : "Nosaukums",
+DlgTextValue : "Vērtība",
+DlgTextCharWidth : "Simbolu platums",
+DlgTextMaxChars : "Simbolu maksimÄlais daudzums",
+DlgTextType : "Tips",
+DlgTextTypeText : "Teksts",
+DlgTextTypePass : "Parole",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nosaukums",
+DlgHiddenValue : "Vērtība",
+
+// Bulleted List Dialog
+BulletedListProp : "Aizzīmju saraksta īpašības",
+NumberedListProp : "NumerÄ“tÄ saraksta Ä«paÅ¡Ä«bas",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Tips",
+DlgLstTypeCircle : "Aplis",
+DlgLstTypeDisc : "Disks",
+DlgLstTypeSquare : "KvadrÄts",
+DlgLstTypeNumbers : "Skaitļi (1, 2, 3)",
+DlgLstTypeLCase : "Maziem burtiem (a, b, c)",
+DlgLstTypeUCase : "Lieliem burtiem (A, B, C)",
+DlgLstTypeSRoman : "Maziem romiešu cipariem (i, ii, iii)",
+DlgLstTypeLRoman : "Lieliem romiešu cipariem (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "VispÄrÄ«ga informÄcija",
+DlgDocBackTab : "Fons",
+DlgDocColorsTab : "KrÄsas un robežu nobÄ«des",
+DlgDocMetaTab : "META dati",
+
+DlgDocPageTitle : "Dokumenta virsraksts <Title>",
+DlgDocLangDir : "Valodas lasīšanas virziens",
+DlgDocLangDirLTR : "No kreisÄs uz labo (LTR)",
+DlgDocLangDirRTL : "No labÄs uz kreiso (RTL)",
+DlgDocLangCode : "Valodas kods",
+DlgDocCharSet : "Simbolu kodējums",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Cits simbolu kodējums",
+
+DlgDocDocType : "Dokumenta tips",
+DlgDocDocTypeOther : "Cits dokumenta tips",
+DlgDocIncXHTML : "Ietvert XHTML deklarÄcijas",
+DlgDocBgColor : "Fona krÄsa",
+DlgDocBgImage : "Fona attēla hipersaite",
+DlgDocBgNoScroll : "Fona attēls ir fiksēts",
+DlgDocCText : "Teksts",
+DlgDocCLink : "Hipersaite",
+DlgDocCVisited : "Apmeklēta hipersaite",
+DlgDocCActive : "Aktīva hipersaite",
+DlgDocMargins : "Lapas robežas",
+DlgDocMaTop : "AugÅ¡Ä",
+DlgDocMaLeft : "Pa kreisi",
+DlgDocMaRight : "Pa labi",
+DlgDocMaBottom : "ApakÅ¡Ä",
+DlgDocMeIndex : "Dokumentu aprakstoÅ¡i atslÄ“gvÄrdi (atdalÄ«ti ar komatu)",
+DlgDocMeDescr : "Dokumenta apraksts",
+DlgDocMeAuthor : "Autors",
+DlgDocMeCopy : "Autortiesības",
+DlgDocPreview : "Priekšskats",
+
+// Templates Dialog
+Templates : "Sagataves",
+DlgTemplatesTitle : "Satura sagataves",
+DlgTemplatesSelMsg : "LÅ«dzu, norÄdiet sagatavi, ko atvÄ“rt editorÄ<br>(patreizÄ“jie dati tiks zaudÄ“ti):",
+DlgTemplatesLoading : "Notiek sagatavju saraksta ielÄde. LÅ«dzu, uzgaidiet...",
+DlgTemplatesNoTpl : "(Nav norÄdÄ«tas sagataves)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "Par",
+DlgAboutBrowserInfoTab : "InformÄcija par pÄrlÅ«kprogrammu",
+DlgAboutLicenseTab : "Licence",
+DlgAboutVersion : "versija",
+DlgAboutInfo : "Papildus informÄcija ir pieejama",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/mn.js b/httemplate/elements/fckeditor/editor/lang/mn.js
new file mode 100644
index 0000000..4f82297
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/mn.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Mongolian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Багажны Ñ…ÑÑÑг ÑвдÑÑ…",
+ToolbarExpand : "Багажны Ñ…ÑÑÑг өргөтгөх",
+
+// Toolbar Items and Context Menu
+Save : "Хадгалах",
+NewPage : "Ð¨Ð¸Ð½Ñ Ñ…ÑƒÑƒÐ´Ð°Ñ",
+Preview : "Уридчлан харах",
+Cut : "Хайчлах",
+Copy : "Хуулах",
+Paste : "Буулгах",
+PasteText : "plain text-ÑÑÑ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…",
+PasteWord : "Word-Ð¾Ð¾Ñ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…",
+Print : "Ð¥ÑвлÑÑ…",
+SelectAll : "Бүгдийг нь Ñонгох",
+RemoveFormat : "Формат авч хаÑÑ…",
+InsertLinkLbl : "Линк",
+InsertLink : "Линк Оруулах/ЗаÑварлах",
+RemoveLink : "Линк авч хаÑÑ…",
+VisitLink : "Open Link", //MISSING
+Anchor : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ ÐžÑ€ÑƒÑƒÐ»Ð°Ñ…/ЗаÑварлах",
+AnchorDelete : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ðвах",
+InsertImageLbl : "Зураг",
+InsertImage : "Зураг Оруулах/ЗаÑварлах",
+InsertFlashLbl : "Флаш",
+InsertFlash : "Флаш Оруулах/ЗаÑварлах",
+InsertTableLbl : "Ð¥Ò¯ÑнÑгт",
+InsertTable : "Ð¥Ò¯ÑнÑгт Оруулах/ЗаÑварлах",
+InsertLineLbl : "ЗурааÑ",
+InsertLine : "Хөндлөн Ð·ÑƒÑ€Ð°Ð°Ñ Ð¾Ñ€ÑƒÑƒÐ»Ð°Ñ…",
+InsertSpecialCharLbl: "Онцгой Ñ‚ÑмдÑгт",
+InsertSpecialChar : "Онцгой Ñ‚ÑмдÑгт оруулах",
+InsertSmileyLbl : "Тодорхойлолт",
+InsertSmiley : "Тодорхойлолт оруулах",
+About : "FCKeditor-н тухай",
+Bold : "Тод бүдүүн",
+Italic : "Ðалуу",
+Underline : "Доогуур нь зурааÑтай болгох",
+StrikeThrough : "Дундуур нь зурааÑтай болгох",
+Subscript : "Суурь болгох",
+Superscript : "ЗÑÑ€Ñг болгох",
+LeftJustify : "Зүүн талд байрлуулах",
+CenterJustify : "Төвд байрлуулах",
+RightJustify : "Баруун талд байрлуулах",
+BlockJustify : "Блок Ñ…ÑлбÑÑ€ÑÑÑ€ байрлуулах",
+DecreaseIndent : "Догол мөр нÑмÑÑ…",
+IncreaseIndent : "Догол мөр хаÑах",
+Blockquote : "Хайрцаглах",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Хүчингүй болгох",
+Redo : "Өмнөх үйлдлÑÑ ÑÑргÑÑÑ…",
+NumberedListLbl : "ДугаарлагдÑан жагÑаалт",
+NumberedList : "ДугаарлагдÑан жагÑаалт Оруулах/Ðвах",
+BulletedListLbl : "ЦÑгтÑй жагÑаалт",
+BulletedList : "ЦÑгтÑй жагÑаалт Оруулах/Ðвах",
+ShowTableBorders : "Ð¥Ò¯ÑнÑгтийн хүрÑÑг үзүүлÑÑ…",
+ShowDetails : "Деталчлан үзүүлÑÑ…",
+Style : "Загвар",
+FontFormat : "Формат",
+Font : "Фонт",
+FontSize : "Ð¥ÑмжÑÑ",
+TextColor : "Фонтны өнгө",
+BGColor : "Фонны өнгө",
+Source : "Код",
+Find : "Хайх",
+Replace : "Солих",
+SpellCheck : "Үгийн дүрÑÑ… шалгах",
+UniversalKeyboard : "УниварÑал гар",
+PageBreakLbl : "Ð¥ÑƒÑƒÐ´Ð°Ñ Ñ‚ÑƒÑгаарлах",
+PageBreak : "Ð¥ÑƒÑƒÐ´Ð°Ñ Ñ‚ÑƒÑгаарлагч оруулах",
+
+Form : "Форм",
+Checkbox : "ЧекбокÑ",
+RadioButton : "Радио товч",
+TextField : "Техт талбар",
+Textarea : "Техт орчин",
+HiddenField : "Ðууц талбар",
+Button : "Товч",
+SelectionField : "Сонгогч талбар",
+ImageButton : "Зурагтай товч",
+
+FitWindow : "editor-н Ñ…ÑмжÑÑг томруулах",
+ShowBlocks : "Block-уудыг үзүүлÑÑ…",
+
+// Context Menu
+EditLink : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð·Ð°Ñварлах",
+CellCM : "Ðүх/зай",
+RowCM : "Мөр",
+ColumnCM : "Багана",
+InsertRowAfter : "Мөр дараа нь оруулах",
+InsertRowBefore : "Мөр өмнө нь оруулах",
+DeleteRows : "Мөр уÑтгах",
+InsertColumnAfter : "Багана дараа нь оруулах",
+InsertColumnBefore : "Багана өмнө нь оруулах",
+DeleteColumns : "Багана уÑтгах",
+InsertCellAfter : "Ðүх/зай дараа нь оруулах",
+InsertCellBefore : "Ðүх/зай өмнө нь оруулах",
+DeleteCells : "Ðүх уÑтгах",
+MergeCells : "Ðүх нÑгтÑÑ…",
+MergeRight : "Баруун тийш нÑгтгÑÑ…",
+MergeDown : "Доош нÑгтгÑÑ…",
+HorizontalSplitCell : "Ðүх/зайг боÑоогоор нь туÑгаарлах",
+VerticalSplitCell : "Ðүх/зайг хөндлөнгөөр нь туÑгаарлах",
+TableDelete : "Ð¥Ò¯ÑнÑгт уÑтгах",
+CellProperties : "Ðүх/зай зайн шинж чанар",
+TableProperties : "Ð¥Ò¯ÑнÑгт",
+ImageProperties : "Зураг",
+FlashProperties : "Флаш шинж чанар",
+
+AnchorProp : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ ÑˆÐ¸Ð½Ð¶ чанар",
+ButtonProp : "Товчны шинж чанар",
+CheckboxProp : "ЧекбокÑны шинж чанар",
+HiddenFieldProp : "Ðууц талбарын шинж чанар",
+RadioButtonProp : "Радио товчны шинж чанар",
+ImageButtonProp : "Зурган товчны шинж чанар",
+TextFieldProp : "ТекÑÑ‚ талбарын шинж чанар",
+SelectionFieldProp : "Согогч талбарын шинж чанар",
+TextareaProp : "ТекÑÑ‚ орчны шинж чанар",
+FormProp : "Форм шинж чанар",
+
+FontFormats : "Ð¥Ñвийн;Formatted;ХаÑг;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Paragraph (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML үйл Ñвц Ñвагдаж байна. ХүлÑÑÐ½Ñ Ò¯Ò¯...",
+Done : "Хийх",
+PasteWordConfirm : "Word-Ð¾Ð¾Ñ Ñ…ÑƒÑƒÐ»Ñан текÑÑ‚ÑÑ Ñанаж байгааг нь буулгахыг та Ñ…Ò¯Ñч байна уу. Та текÑÑ‚-ÑÑ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…Ñ‹Ð½ өмнө цÑвÑрлÑÑ… Ò¯Ò¯?",
+NotCompatiblePaste : "Ð­Ð½Ñ ÐºÐ¾Ð¼Ð¼Ð°Ð½Ð´ Internet Explorer-ын 5.5 буюу түүнÑÑÑ Ð´ÑÑш хувилбарт идвÑхшинÑ. Та цÑвÑрлÑхгүйгÑÑÑ€ буулгахыг Ñ…Ò¯Ñч байна?",
+UnknownToolbarItem : "Багажны Ñ…ÑÑгийн \"%1\" item мÑдÑгдÑхгүй байна",
+UnknownCommand : "\"%1\" комманд нÑÑ€ мÑдагдÑхгүй байна",
+NotImplemented : "Зөвшөөрөгдөхгүй комманд",
+UnknownToolbarSet : "Багажны Ñ…ÑÑÑгт \"%1\" оноох, Ò¯Ò¯ÑÑÑгүй байна",
+NoActiveX : "Таны үзүүлÑгч/browser-н хамгаалалтын тохиргоо editor-н зарим боломжийг Ñ…Ñзгаарлаж байна. Та \"Run ActiveX controls ба plug-ins\" Ñонголыг идвÑхитÑй болго.",
+BrowseServerBlocked : "Ðөөц үзүүгч нÑÑж чадÑангүй. Бүх popup blocker-г disabled болгоно уу.",
+DialogBlocked : "Харилцах цонхонд Ñнийг нÑÑÑ…Ñд боломжгүй ÑÑ. Бүх popup blocker-г disabled болгоно уу.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Болих",
+DlgBtnClose : "Хаах",
+DlgBtnBrowseServer : "Сервер харуулах",
+DlgAdvancedTag : "ÐÑмÑлт",
+DlgOpOther : "<БуÑад>",
+DlgInfoTab : "ÐœÑдÑÑлÑл",
+DlgAlertUrl : "URL оруулна уу",
+
+// General Dialogs Labels
+DlgGenNotSet : "<Оноохгүй>",
+DlgGenId : "Id",
+DlgGenLangDir : "Ð¥Ñлний чиглÑл",
+DlgGenLangDirLtr : "ЗүүнÑÑÑ Ð±Ð°Ñ€ÑƒÑƒÐ½ (LTR)",
+DlgGenLangDirRtl : "Ð‘Ð°Ñ€ÑƒÑƒÐ½Ð°Ð°Ñ Ð·Ò¯Ò¯Ð½ (RTL)",
+DlgGenLangCode : "Ð¥Ñлний код",
+DlgGenAccessKey : "Холбох түлхүүр",
+DlgGenName : "ÐÑÑ€",
+DlgGenTabIndex : "Tab индекÑ",
+DlgGenLongDescr : "URL-ын тайлбар",
+DlgGenClass : "Stylesheet клаÑÑууд",
+DlgGenTitle : "Зөвлөлдөх гарчиг",
+DlgGenContType : "Зөвлөлдөх төрлийн агуулга",
+DlgGenLinkCharset : "ТÑмдÑгт оноох нөөцөд холбогдÑон",
+DlgGenStyle : "Загвар",
+
+// Image Dialog
+DlgImgTitle : "Зураг",
+DlgImgInfoTab : "Зурагны мÑдÑÑлÑл",
+DlgImgBtnUpload : "Үүнийг ÑервÑррүү илгÑÑ",
+DlgImgURL : "URL",
+DlgImgUpload : "Хуулах",
+DlgImgAlt : "Тайлбар текÑÑ‚",
+DlgImgWidth : "Өргөн",
+DlgImgHeight : "Өндөр",
+DlgImgLockRatio : "Радио түгжих",
+DlgBtnResetSize : "Ñ…ÑмжÑÑ Ð´Ð°Ñ…Ð¸Ð½ оноох",
+DlgImgBorder : "ХүрÑÑ",
+DlgImgHSpace : "Хөндлөн зай",
+DlgImgVSpace : "БоÑоо зай",
+DlgImgAlign : "ЭгнÑÑ",
+DlgImgAlignLeft : "Зүүн",
+DlgImgAlignAbsBottom: "Abs доод талд",
+DlgImgAlignAbsMiddle: "Abs Дунд талд",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Доод талд",
+DlgImgAlignMiddle : "Дунд талд",
+DlgImgAlignRight : "Баруун",
+DlgImgAlignTextTop : "ТекÑÑ‚ дÑÑÑ€",
+DlgImgAlignTop : "ДÑÑд талд",
+DlgImgPreview : "Уридчлан харах",
+DlgImgAlertUrl : "Зурагны URL-ын төрлийн Ñонгоно уу",
+DlgImgLinkTab : "Линк",
+
+// Flash Dialog
+DlgFlashTitle : "Флаш шинж чанар",
+DlgFlashChkPlay : "Ðвтоматаар тоглох",
+DlgFlashChkLoop : "Давтах",
+DlgFlashChkMenu : "Флаш цÑÑ Ð¸Ð´Ð²ÑхжүүлÑÑ…",
+DlgFlashScale : "Өргөгтгөх",
+DlgFlashScaleAll : "Бүгдийг харуулах",
+DlgFlashScaleNoBorder : "ХүрÑÑгүй",
+DlgFlashScaleFit : "Яг тааруулах",
+
+// Link Dialog
+DlgLnkWindowTitle : "Линк",
+DlgLnkInfoTab : "Линкийн мÑдÑÑлÑл",
+DlgLnkTargetTab : "Байрлал",
+
+DlgLnkType : "Линкийн төрөл",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ð­Ð½Ñ Ñ…ÑƒÑƒÐ´Ð°Ñандах холбооÑ",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Протокол",
+DlgLnkProtoOther : "<буÑад>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ñонгох",
+DlgLnkAnchorByName : "ХолбооÑын нÑÑ€ÑÑÑ€",
+DlgLnkAnchorById : "ЭлемÑнт Id-гаар",
+DlgLnkNoAnchors : "(Баримт бичиг холбооÑгүй байна)",
+DlgLnkEMail : "E-Mail ХаÑг",
+DlgLnkEMailSubject : "Message гарчиг",
+DlgLnkEMailBody : "Message-ийн агуулга",
+DlgLnkUpload : "Хуулах",
+DlgLnkBtnUpload : "Үүнийг Ñерверрүү илгÑÑ",
+
+DlgLnkTarget : "Байрлал",
+DlgLnkTargetFrame : "<Ðгуулах хүрÑÑ>",
+DlgLnkTargetPopup : "<popup цонх>",
+DlgLnkTargetBlank : "Ð¨Ð¸Ð½Ñ Ñ†Ð¾Ð½Ñ… (_blank)",
+DlgLnkTargetParent : "ЭцÑг цонх (_parent)",
+DlgLnkTargetSelf : "ТөÑÑ‚Ñй цонх (_self)",
+DlgLnkTargetTop : "Хамгийн түрүүн байх цонх (_top)",
+DlgLnkTargetFrameName : "Очих фремын нÑÑ€",
+DlgLnkPopWinName : "Popup цонхны нÑÑ€",
+DlgLnkPopWinFeat : "Popup цонхны онцлог",
+DlgLnkPopResize : "Ð¥ÑмжÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ…",
+DlgLnkPopLocation : "Location Ñ…ÑÑÑг",
+DlgLnkPopMenu : "Meню Ñ…ÑÑÑг",
+DlgLnkPopScroll : "Скрол Ñ…ÑÑÑгүүд",
+DlgLnkPopStatus : "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ…ÑÑÑг",
+DlgLnkPopToolbar : "Багажны Ñ…ÑÑÑг",
+DlgLnkPopFullScrn : "Цонх дүүргÑÑ… (IE)",
+DlgLnkPopDependent : "Хамаатай (Netscape)",
+DlgLnkPopWidth : "Өргөн",
+DlgLnkPopHeight : "Өндөр",
+DlgLnkPopLeft : "Зүүн байрлал",
+DlgLnkPopTop : "ДÑÑд байрлал",
+
+DlnLnkMsgNoUrl : "Линк URL-ÑÑ Ñ‚Ó©Ñ€Ó©Ð»Ð¶Ò¯Ò¯Ð»Ð½Ñ Ò¯Ò¯",
+DlnLnkMsgNoEMail : "Е-mail хаÑгаа Ñ‚Ó©Ñ€Ó©Ð»Ð¶Ò¯Ò¯Ð»Ð½Ñ Ò¯Ò¯",
+DlnLnkMsgNoAnchor : "ХолбооÑоо Ñонгоно уу",
+DlnLnkMsgInvPopName : "popup нÑÑ€ нь Ò¯ÑгÑн Ñ‚ÑмдÑгтÑÑÑ€ ÑÑ…ÑлÑÑн байх ба хооÑон зай агуулаагүй байх Ñ‘Ñтой.",
+
+// Color Dialog
+DlgColorTitle : "Өнгө Ñонгох",
+DlgColorBtnClear : "ЦÑвÑрлÑÑ…",
+DlgColorHighlight : "Өнгө",
+DlgColorSelected : "СонгогдÑон",
+
+// Smiley Dialog
+DlgSmileyTitle : "Тодорхойлолт оруулах",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Онцгой Ñ‚ÑмдÑгт Ñонгох",
+
+// Table Dialog
+DlgTableTitle : "Ð¥Ò¯ÑнÑгт",
+DlgTableRows : "Мөр",
+DlgTableColumns : "Багана",
+DlgTableBorder : "ХүрÑÑний Ñ…ÑмжÑÑ",
+DlgTableAlign : "ЭгнÑÑ",
+DlgTableAlignNotSet : "<Оноохгүй>",
+DlgTableAlignLeft : "Зүүн талд",
+DlgTableAlignCenter : "Төвд",
+DlgTableAlignRight : "Баруун талд",
+DlgTableWidth : "Өргөн",
+DlgTableWidthPx : "цÑг",
+DlgTableWidthPc : "хувь",
+DlgTableHeight : "Өндөр",
+DlgTableCellSpace : "Ðүх хоорондын зай (spacing)",
+DlgTableCellPad : "Ðүх доторлох(padding)",
+DlgTableCaption : "Тайлбар",
+DlgTableSummary : "Тайлбар",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "ХооÑон зайн шинж чанар",
+DlgCellWidth : "Өргөн",
+DlgCellWidthPx : "цÑг",
+DlgCellWidthPc : "хувь",
+DlgCellHeight : "Өндөр",
+DlgCellWordWrap : "Үг таÑлах",
+DlgCellWordWrapNotSet : "<Оноохгүй>",
+DlgCellWordWrapYes : "Тийм",
+DlgCellWordWrapNo : "Үгүй",
+DlgCellHorAlign : "БоÑоо ÑгнÑÑ",
+DlgCellHorAlignNotSet : "<Оноохгүй>",
+DlgCellHorAlignLeft : "Зүүн",
+DlgCellHorAlignCenter : "Төв",
+DlgCellHorAlignRight: "Баруун",
+DlgCellVerAlign : "Хөндлөн ÑгнÑÑ",
+DlgCellVerAlignNotSet : "<Оноохгүй>",
+DlgCellVerAlignTop : "ДÑÑд тал",
+DlgCellVerAlignMiddle : "Дунд",
+DlgCellVerAlignBottom : "Доод тал",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Ðийт мөр (span)",
+DlgCellCollSpan : "Ðийт багана (span)",
+DlgCellBackColor : "Фонны өнгө",
+DlgCellBorderColor : "ХүрÑÑний өнгө",
+DlgCellBtnSelect : "Сонго...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Хай мөн Дарж бич",
+
+// Find Dialog
+DlgFindTitle : "Хайх",
+DlgFindFindBtn : "Хайх",
+DlgFindNotFoundMsg : "ХайÑан текÑÑ‚ олÑонгүй.",
+
+// Replace Dialog
+DlgReplaceTitle : "Солих",
+DlgReplaceFindLbl : "Хайх үг/Ò¯ÑÑг:",
+DlgReplaceReplaceLbl : "Солих үг:",
+DlgReplaceCaseChk : "ТÑнцÑÑ… төлөв",
+DlgReplaceReplaceBtn : "Солих",
+DlgReplaceReplAllBtn : "Бүгдийг нь Солих",
+DlgReplaceWordChk : "ТÑнцÑÑ… бүтÑн үг",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хайчлах үйлдÑлийг зөвшөөрөхгүй байна. (Ctrl+X) товчны хоÑлолыг ашиглана уу.",
+PasteErrorCopy : "Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хуулах үйлдÑлийг зөвшөөрөхгүй байна. (Ctrl+C) товчны хоÑлолыг ашиглана уу.",
+
+PasteAsText : "Plain Text-ÑÑÑ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…",
+PasteFromWord : "Word-Ð¾Ð¾Ñ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…",
+
+DlgPasteMsg2 : "(<strong>Ctrl+V</strong>) товчийг ашиглан paste Ñ…Ð¸Ð¹Ð½Ñ Ò¯Ò¯. Мөн <strong>OK</strong> дар.",
+DlgPasteSec : "Таны үзүүлÑгч/browser/-н хамгаалалтын Ñ‚Ð¾Ñ…Ð¸Ñ€Ð³Ð¾Ð¾Ð½Ð¾Ð¾Ñ Ð±Ð¾Ð»Ð¾Ð¾Ð´ editor clipboard өгөгдөлрүү шууд хандах боломжгүй. Ð­Ð½Ñ Ñ†Ð¾Ð½Ñ…Ð¾Ð´ дахин paste хийхийг оролд.",
+DlgPasteIgnoreFont : "ТодорхойлогдÑон Font Face зөвшөөрнө",
+DlgPasteRemoveStyles : "ТодорхойлогдÑон загварыг авах",
+
+// Color Picker
+ColorAutomatic : "Ðвтоматаар",
+ColorMoreColors : "ÐÑмÑлт өнгөнүүд...",
+
+// Document Properties
+DocProps : "Баримт бичиг шинж чанар",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ ÑˆÐ¸Ð½Ð¶ чанар",
+DlgAnchorName : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð½ÑÑ€",
+DlgAnchorErrorName : "Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ñ‚Ó©Ñ€Ó©Ð» оруулна уу",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Толь бичиггүй",
+DlgSpellChangeTo : "Өөрчлөх",
+DlgSpellBtnIgnore : "Зөвшөөрөх",
+DlgSpellBtnIgnoreAll : "Бүгдийг зөвшөөрөх",
+DlgSpellBtnReplace : "Дарж бичих",
+DlgSpellBtnReplaceAll : "Бүгдийг Дарж бичих",
+DlgSpellBtnUndo : "Буцаах",
+DlgSpellNoSuggestions : "- Тайлбаргүй -",
+DlgSpellProgress : "ДүрÑм шалгаж байгаа үйл Ñвц...",
+DlgSpellNoMispell : "ДүрÑм шалгаад дууÑÑан: Ðлдаа олдÑонгүй",
+DlgSpellNoChanges : "ДүрÑм шалгаад дууÑÑан: үг өөрчлөгдөөгүй",
+DlgSpellOneChange : "ДүрÑм шалгаад дууÑÑан: 1 үг өөрчлөгдÑөн",
+DlgSpellManyChanges : "ДүрÑм шалгаад дууÑÑан: %1 үг өөрчлөгдÑөн",
+
+IeSpellDownload : "ДүрÑм шалгагч Ñуугаагүй байна. Татаж авахыг Ñ…Ò¯Ñч байна уу?",
+
+// Button Dialog
+DlgButtonText : "ТÑкÑÑ‚ (Утга)",
+DlgButtonType : "Төрөл",
+DlgButtonTypeBtn : "Товч",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Болих",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ÐÑÑ€",
+DlgCheckboxValue : "Утга",
+DlgCheckboxSelected : "СонгогдÑон",
+
+// Form Dialog
+DlgFormName : "ÐÑÑ€",
+DlgFormAction : "ҮйлдÑл",
+DlgFormMethod : "Ðрга",
+
+// Select Field Dialog
+DlgSelectName : "ÐÑÑ€",
+DlgSelectValue : "Утга",
+DlgSelectSize : "Ð¥ÑмжÑÑ",
+DlgSelectLines : "Мөр",
+DlgSelectChkMulti : "Олон Ñонголт зөвшөөрөх",
+DlgSelectOpAvail : "ИдвÑÑ…Ñ‚Ñй Ñонголт",
+DlgSelectOpText : "ТÑкÑÑ‚",
+DlgSelectOpValue : "Утга",
+DlgSelectBtnAdd : "ÐÑмÑÑ…",
+DlgSelectBtnModify : "Өөрчлөх",
+DlgSelectBtnUp : "ДÑÑш",
+DlgSelectBtnDown : "Доош",
+DlgSelectBtnSetValue : "СонгогдÑан утга оноох",
+DlgSelectBtnDelete : "УÑтгах",
+
+// Textarea Dialog
+DlgTextareaName : "ÐÑÑ€",
+DlgTextareaCols : "Багана",
+DlgTextareaRows : "Мөр",
+
+// Text Field Dialog
+DlgTextName : "ÐÑÑ€",
+DlgTextValue : "Утга",
+DlgTextCharWidth : "ТÑмдÑгтын өргөн",
+DlgTextMaxChars : "Хамгийн их Ñ‚ÑмдÑгт",
+DlgTextType : "Төрөл",
+DlgTextTypeText : "ТекÑÑ‚",
+DlgTextTypePass : "Ðууц үг",
+
+// Hidden Field Dialog
+DlgHiddenName : "ÐÑÑ€",
+DlgHiddenValue : "Утга",
+
+// Bulleted List Dialog
+BulletedListProp : "Bulleted жагÑаалын шинж чанар",
+NumberedListProp : "ДугаарлаÑан жагÑаалын шинж чанар",
+DlgLstStart : "ЭхлÑÑ…",
+DlgLstType : "Төрөл",
+DlgLstTypeCircle : "Тойрог",
+DlgLstTypeDisc : "Тайлбар",
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Тоо (1, 2, 3)",
+DlgLstTypeLCase : "Жижиг Ò¯ÑÑг (a, b, c)",
+DlgLstTypeUCase : "Том Ò¯ÑÑг (A, B, C)",
+DlgLstTypeSRoman : "Жижиг Ром тоо (i, ii, iii)",
+DlgLstTypeLRoman : "Том Ром тоо (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Ерөнхий",
+DlgDocBackTab : "Фоно",
+DlgDocColorsTab : "Захын зай ба Өнгө",
+DlgDocMetaTab : "Meta өгөгдөл",
+
+DlgDocPageTitle : "ХуудаÑны гарчиг",
+DlgDocLangDir : "Ð¥Ñлний чиглÑл",
+DlgDocLangDirLTR : "ЗүүнÑÑÑ Ð±Ð°Ñ€ÑƒÑƒÐ½Ñ€ÑƒÑƒ (LTR)",
+DlgDocLangDirRTL : "Ð‘Ð°Ñ€ÑƒÑƒÐ½Ð°Ð°Ñ Ð·Ò¯Ò¯Ð½Ñ€Ò¯Ò¯ (RTL)",
+DlgDocLangCode : "Ð¥Ñлний код",
+DlgDocCharSet : "Encoding Ñ‚ÑмдÑгт",
+DlgDocCharSetCE : "Төв европ",
+DlgDocCharSetCT : "Ð¥Ñтадын уламжлалт (Big5)",
+DlgDocCharSetCR : "Крил",
+DlgDocCharSetGR : "Гред",
+DlgDocCharSetJP : "Япон",
+DlgDocCharSetKR : "СолонгоÑ",
+DlgDocCharSetTR : "Tурк",
+DlgDocCharSetUN : "Юникод (UTF-8)",
+DlgDocCharSetWE : "Баруун европ",
+DlgDocCharSetOther : "Encoding-д Ó©Ó©Ñ€ Ñ‚ÑмдÑгт оноох",
+
+DlgDocDocType : "Баримт бичгийн төрөл Heading",
+DlgDocDocTypeOther : "БуÑад баримт бичгийн төрөл Heading",
+DlgDocIncXHTML : "XHTML агуулж зарлах",
+DlgDocBgColor : "Фоно өнгө",
+DlgDocBgImage : "Фоно зурагны URL",
+DlgDocBgNoScroll : "ГүйдÑггүй фоно",
+DlgDocCText : "ТекÑÑ‚",
+DlgDocCLink : "Линк",
+DlgDocCVisited : "ЗочилÑон линк",
+DlgDocCActive : "ИдвÑхитÑй линк",
+DlgDocMargins : "ХуудаÑны захын зай",
+DlgDocMaTop : "ДÑÑд тал",
+DlgDocMaLeft : "Зүүн тал",
+DlgDocMaRight : "Баруун тал",
+DlgDocMaBottom : "Доод тал",
+DlgDocMeIndex : "Баримт бичгийн Ð¸Ð½Ð´ÐµÐºÑ Ñ‚Ò¯Ð»Ñ…Ò¯Ò¯Ñ€ үг (таÑлалаар туÑгаарлагдана)",
+DlgDocMeDescr : "Баримт бичгийн тайлбар",
+DlgDocMeAuthor : "Зохиогч",
+DlgDocMeCopy : "Зохиогчийн Ñрх",
+DlgDocPreview : "Харах",
+
+// Templates Dialog
+Templates : "Загварууд",
+DlgTemplatesTitle : "Загварын агуулга",
+DlgTemplatesSelMsg : "Загварыг нÑÑж editor-Ñ€Ò¯Ò¯ Ñонгож оруулна уу<br />(Одоогийн агууллагыг уÑтаж магадгүй):",
+DlgTemplatesLoading : "Загваруудыг ачааллаж байна. Түр хүлÑÑÐ½Ñ Ò¯Ò¯...",
+DlgTemplatesNoTpl : "(Загвар тодорхойлогдоогүй байна)",
+DlgTemplatesReplace : "Одоогийн агууллагыг дарж бичих",
+
+// About Dialog
+DlgAboutAboutTab : "Тухай",
+DlgAboutBrowserInfoTab : "ÐœÑдÑÑлÑл үзүүлÑгч",
+DlgAboutLicenseTab : "Лиценз",
+DlgAboutVersion : "Хувилбар",
+DlgAboutInfo : "ÐœÑдÑÑллÑÑÑ€ туÑлах",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ms.js b/httemplate/elements/fckeditor/editor/lang/ms.js
new file mode 100644
index 0000000..1e97973
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ms.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Malay language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Collapse Toolbar",
+ToolbarExpand : "Expand Toolbar",
+
+// Toolbar Items and Context Menu
+Save : "Simpan",
+NewPage : "Helaian Baru",
+Preview : "Prebiu",
+Cut : "Potong",
+Copy : "Salin",
+Paste : "Tampal",
+PasteText : "Tampal sebagai Text Biasa",
+PasteWord : "Tampal dari Word",
+Print : "Cetak",
+SelectAll : "Pilih Semua",
+RemoveFormat : "Buang Format",
+InsertLinkLbl : "Sambungan",
+InsertLink : "Masukkan/Sunting Sambungan",
+RemoveLink : "Buang Sambungan",
+VisitLink : "Open Link", //MISSING
+Anchor : "Masukkan/Sunting Pautan",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Gambar",
+InsertImage : "Masukkan/Sunting Gambar",
+InsertFlashLbl : "Flash", //MISSING
+InsertFlash : "Insert/Edit Flash", //MISSING
+InsertTableLbl : "Jadual",
+InsertTable : "Masukkan/Sunting Jadual",
+InsertLineLbl : "Garisan",
+InsertLine : "Masukkan Garisan Membujur",
+InsertSpecialCharLbl: "Huruf Istimewa",
+InsertSpecialChar : "Masukkan Huruf Istimewa",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Masukkan Smiley",
+About : "Tentang FCKeditor",
+Bold : "Bold",
+Italic : "Italic",
+Underline : "Underline",
+StrikeThrough : "Strike Through",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Jajaran Kiri",
+CenterJustify : "Jajaran Tengah",
+RightJustify : "Jajaran Kanan",
+BlockJustify : "Jajaran Blok",
+DecreaseIndent : "Kurangkan Inden",
+IncreaseIndent : "Tambahkan Inden",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Batalkan",
+Redo : "Ulangkan",
+NumberedListLbl : "Senarai bernombor",
+NumberedList : "Masukkan/Sunting Senarai bernombor",
+BulletedListLbl : "Senarai tidak bernombor",
+BulletedList : "Masukkan/Sunting Senarai tidak bernombor",
+ShowTableBorders : "Tunjukkan Border Jadual",
+ShowDetails : "Tunjukkan Butiran",
+Style : "Stail",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "Saiz",
+TextColor : "Warna Text",
+BGColor : "Warna Latarbelakang",
+Source : "Sumber",
+Find : "Cari",
+Replace : "Ganti",
+SpellCheck : "Semak Ejaan",
+UniversalKeyboard : "Papan Kekunci Universal",
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "Borang",
+Checkbox : "Checkbox",
+RadioButton : "Butang Radio",
+TextField : "Text Field",
+Textarea : "Textarea",
+HiddenField : "Field Tersembunyi",
+Button : "Butang",
+SelectionField : "Field Pilihan",
+ImageButton : "Butang Bergambar",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Sunting Sambungan",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Buangkan Baris",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Buangkan Lajur",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Buangkan Sel-sel",
+MergeCells : "Cantumkan Sel-sel",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Delete Table", //MISSING
+CellProperties : "Ciri-ciri Sel",
+TableProperties : "Ciri-ciri Jadual",
+ImageProperties : "Ciri-ciri Gambar",
+FlashProperties : "Flash Properties", //MISSING
+
+AnchorProp : "Ciri-ciri Pautan",
+ButtonProp : "Ciri-ciri Butang",
+CheckboxProp : "Ciri-ciri Checkbox",
+HiddenFieldProp : "Ciri-ciri Field Tersembunyi",
+RadioButtonProp : "Ciri-ciri Butang Radio",
+ImageButtonProp : "Ciri-ciri Butang Bergambar",
+TextFieldProp : "Ciri-ciri Text Field",
+SelectionFieldProp : "Ciri-ciri Selection Field",
+TextareaProp : "Ciri-ciri Textarea",
+FormProp : "Ciri-ciri Borang",
+
+FontFormats : "Normal;Telah Diformat;Alamat;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Perenggan (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Memproses XHTML. Sila tunggu...",
+Done : "Siap",
+PasteWordConfirm : "Text yang anda hendak tampal adalah berasal dari Word. Adakah anda mahu membuang semua format Word sebelum tampal ke dalam text?",
+NotCompatiblePaste : "Arahan ini bole dilakukan jika anda mempuunyai Internet Explorer version 5.5 atau yang lebih tinggi. Adakah anda hendak tampal text tanpa membuang format Word?",
+UnknownToolbarItem : "Toolbar item tidak diketahui\"%1\"",
+UnknownCommand : "Arahan tidak diketahui \"%1\"",
+NotImplemented : "Arahan tidak terdapat didalam sistem",
+UnknownToolbarSet : "Set toolbar \"%1\" tidak wujud",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Batal",
+DlgBtnClose : "Tutup",
+DlgBtnBrowseServer : "Browse Server",
+DlgAdvancedTag : "Advanced",
+DlgOpOther : "<Lain-lain>",
+DlgInfoTab : "Info", //MISSING
+DlgAlertUrl : "Please insert the URL", //MISSING
+
+// General Dialogs Labels
+DlgGenNotSet : "<tidak di set>",
+DlgGenId : "Id",
+DlgGenLangDir : "Arah Tulisan",
+DlgGenLangDirLtr : "Kiri ke Kanan (LTR)",
+DlgGenLangDirRtl : "Kanan ke Kiri (RTL)",
+DlgGenLangCode : "Kod Bahasa",
+DlgGenAccessKey : "Kunci Akses",
+DlgGenName : "Nama",
+DlgGenTabIndex : "Indeks Tab ",
+DlgGenLongDescr : "Butiran Panjang URL",
+DlgGenClass : "Kelas-kelas Stylesheet",
+DlgGenTitle : "Tajuk Makluman",
+DlgGenContType : "Jenis Kandungan Makluman",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Stail",
+
+// Image Dialog
+DlgImgTitle : "Ciri-ciri Imej",
+DlgImgInfoTab : "Info Imej",
+DlgImgBtnUpload : "Hantar ke Server",
+DlgImgURL : "URL",
+DlgImgUpload : "Muat Naik",
+DlgImgAlt : "Text Alternatif",
+DlgImgWidth : "Lebar",
+DlgImgHeight : "Tinggi",
+DlgImgLockRatio : "Tetapkan Nisbah",
+DlgBtnResetSize : "Saiz Set Semula",
+DlgImgBorder : "Border",
+DlgImgHSpace : "Ruang Melintang",
+DlgImgVSpace : "Ruang Menegak",
+DlgImgAlign : "Jajaran",
+DlgImgAlignLeft : "Kiri",
+DlgImgAlignAbsBottom: "Bawah Mutlak",
+DlgImgAlignAbsMiddle: "Pertengahan Mutlak",
+DlgImgAlignBaseline : "Garis Dasar",
+DlgImgAlignBottom : "Bawah",
+DlgImgAlignMiddle : "Pertengahan",
+DlgImgAlignRight : "Kanan",
+DlgImgAlignTextTop : "Atas Text",
+DlgImgAlignTop : "Atas",
+DlgImgPreview : "Prebiu",
+DlgImgAlertUrl : "Sila taip URL untuk fail gambar",
+DlgImgLinkTab : "Sambungan",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Properties", //MISSING
+DlgFlashChkPlay : "Auto Play", //MISSING
+DlgFlashChkLoop : "Loop", //MISSING
+DlgFlashChkMenu : "Enable Flash Menu", //MISSING
+DlgFlashScale : "Scale", //MISSING
+DlgFlashScaleAll : "Show all", //MISSING
+DlgFlashScaleNoBorder : "No Border", //MISSING
+DlgFlashScaleFit : "Exact Fit", //MISSING
+
+// Link Dialog
+DlgLnkWindowTitle : "Sambungan",
+DlgLnkInfoTab : "Butiran Sambungan",
+DlgLnkTargetTab : "Sasaran",
+
+DlgLnkType : "Jenis Sambungan",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Pautan dalam muka surat ini",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<lain-lain>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Sila pilih pautan",
+DlgLnkAnchorByName : "dengan menggunakan nama pautan",
+DlgLnkAnchorById : "dengan menggunakan ID elemen",
+DlgLnkNoAnchors : "(Tiada pautan terdapat dalam dokumen ini)",
+DlgLnkEMail : "Alamat E-Mail",
+DlgLnkEMailSubject : "Subjek Mesej",
+DlgLnkEMailBody : "Isi Kandungan Mesej",
+DlgLnkUpload : "Muat Naik",
+DlgLnkBtnUpload : "Hantar ke Server",
+
+DlgLnkTarget : "Sasaran",
+DlgLnkTargetFrame : "<bingkai>",
+DlgLnkTargetPopup : "<tetingkap popup>",
+DlgLnkTargetBlank : "Tetingkap Baru (_blank)",
+DlgLnkTargetParent : "Tetingkap Parent (_parent)",
+DlgLnkTargetSelf : "Tetingkap yang Sama (_self)",
+DlgLnkTargetTop : "Tetingkap yang paling atas (_top)",
+DlgLnkTargetFrameName : "Nama Bingkai Sasaran",
+DlgLnkPopWinName : "Nama Tetingkap Popup",
+DlgLnkPopWinFeat : "Ciri Tetingkap Popup",
+DlgLnkPopResize : "Saiz bolehubah",
+DlgLnkPopLocation : "Bar Lokasi",
+DlgLnkPopMenu : "Bar Menu",
+DlgLnkPopScroll : "Bar-bar skrol",
+DlgLnkPopStatus : "Bar Status",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Skrin Penuh (IE)",
+DlgLnkPopDependent : "Bergantungan (Netscape)",
+DlgLnkPopWidth : "Lebar",
+DlgLnkPopHeight : "Tinggi",
+DlgLnkPopLeft : "Posisi Kiri",
+DlgLnkPopTop : "Posisi Atas",
+
+DlnLnkMsgNoUrl : "Sila taip sambungan URL",
+DlnLnkMsgNoEMail : "Sila taip alamat e-mail",
+DlnLnkMsgNoAnchor : "Sila pilih pautan berkenaaan",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Pilihan Warna",
+DlgColorBtnClear : "Nyahwarna",
+DlgColorHighlight : "Terang",
+DlgColorSelected : "Dipilih",
+
+// Smiley Dialog
+DlgSmileyTitle : "Masukkan Smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Sila pilih huruf istimewa",
+
+// Table Dialog
+DlgTableTitle : "Ciri-ciri Jadual",
+DlgTableRows : "Barisan",
+DlgTableColumns : "Jaluran",
+DlgTableBorder : "Saiz Border",
+DlgTableAlign : "Penjajaran",
+DlgTableAlignNotSet : "<Tidak diset>",
+DlgTableAlignLeft : "Kiri",
+DlgTableAlignCenter : "Tengah",
+DlgTableAlignRight : "Kanan",
+DlgTableWidth : "Lebar",
+DlgTableWidthPx : "piksel-piksel",
+DlgTableWidthPc : "peratus",
+DlgTableHeight : "Tinggi",
+DlgTableCellSpace : "Ruangan Antara Sel",
+DlgTableCellPad : "Tambahan Ruang Sel",
+DlgTableCaption : "Keterangan",
+DlgTableSummary : "Summary", //MISSING
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Ciri-ciri Sel",
+DlgCellWidth : "Lebar",
+DlgCellWidthPx : "piksel-piksel",
+DlgCellWidthPc : "peratus",
+DlgCellHeight : "Tinggi",
+DlgCellWordWrap : "Mengulung Perkataan",
+DlgCellWordWrapNotSet : "<Tidak diset>",
+DlgCellWordWrapYes : "Ya",
+DlgCellWordWrapNo : "Tidak",
+DlgCellHorAlign : "Jajaran Membujur",
+DlgCellHorAlignNotSet : "<Tidak diset>",
+DlgCellHorAlignLeft : "Kiri",
+DlgCellHorAlignCenter : "Tengah",
+DlgCellHorAlignRight: "Kanan",
+DlgCellVerAlign : "Jajaran Menegak",
+DlgCellVerAlignNotSet : "<Tidak diset>",
+DlgCellVerAlignTop : "Atas",
+DlgCellVerAlignMiddle : "Tengah",
+DlgCellVerAlignBottom : "Bawah",
+DlgCellVerAlignBaseline : "Garis Dasar",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Penggunaan Baris",
+DlgCellCollSpan : "Penggunaan Lajur",
+DlgCellBackColor : "Warna Latarbelakang",
+DlgCellBorderColor : "Warna Border",
+DlgCellBtnSelect : "Pilih...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Carian",
+DlgFindFindBtn : "Cari",
+DlgFindNotFoundMsg : "Text yang dicari tidak dijumpai.",
+
+// Replace Dialog
+DlgReplaceTitle : "Gantian",
+DlgReplaceFindLbl : "Perkataan yang dicari:",
+DlgReplaceReplaceLbl : "Diganti dengan:",
+DlgReplaceCaseChk : "Padanan case huruf",
+DlgReplaceReplaceBtn : "Ganti",
+DlgReplaceReplAllBtn : "Ganti semua",
+DlgReplaceWordChk : "Padana Keseluruhan perkataan",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Keselamatan perisian browser anda tidak membenarkan operasi suntingan text/imej. Sila gunakan papan kekunci (Ctrl+X).",
+PasteErrorCopy : "Keselamatan perisian browser anda tidak membenarkan operasi salinan text/imej. Sila gunakan papan kekunci (Ctrl+C).",
+
+PasteAsText : "Tampal sebagai text biasa",
+PasteFromWord : "Tampal dari perisian \"Word\"",
+
+DlgPasteMsg2 : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.", //MISSING
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignore Font Face definitions", //MISSING
+DlgPasteRemoveStyles : "Remove Styles definitions", //MISSING
+
+// Color Picker
+ColorAutomatic : "Otomatik",
+ColorMoreColors : "Warna lain-lain...",
+
+// Document Properties
+DocProps : "Ciri-ciri dokumen",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ciri-ciri Pautan",
+DlgAnchorName : "Nama Pautan",
+DlgAnchorErrorName : "Sila taip nama pautan",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Tidak terdapat didalam kamus",
+DlgSpellChangeTo : "Tukarkan kepada",
+DlgSpellBtnIgnore : "Biar",
+DlgSpellBtnIgnoreAll : "Biarkan semua",
+DlgSpellBtnReplace : "Ganti",
+DlgSpellBtnReplaceAll : "Gantikan Semua",
+DlgSpellBtnUndo : "Batalkan",
+DlgSpellNoSuggestions : "- Tiada cadangan -",
+DlgSpellProgress : "Pemeriksaan ejaan sedang diproses...",
+DlgSpellNoMispell : "Pemeriksaan ejaan siap: Tiada salah ejaan",
+DlgSpellNoChanges : "Pemeriksaan ejaan siap: Tiada perkataan diubah",
+DlgSpellOneChange : "Pemeriksaan ejaan siap: Satu perkataan telah diubah",
+DlgSpellManyChanges : "Pemeriksaan ejaan siap: %1 perkataan diubah",
+
+IeSpellDownload : "Pemeriksa ejaan tidak dipasang. Adakah anda mahu muat turun sekarang?",
+
+// Button Dialog
+DlgButtonText : "Teks (Nilai)",
+DlgButtonType : "Jenis",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nama",
+DlgCheckboxValue : "Nilai",
+DlgCheckboxSelected : "Dipilih",
+
+// Form Dialog
+DlgFormName : "Nama",
+DlgFormAction : "Tindakan borang",
+DlgFormMethod : "Cara borang dihantar",
+
+// Select Field Dialog
+DlgSelectName : "Nama",
+DlgSelectValue : "Nilai",
+DlgSelectSize : "Saiz",
+DlgSelectLines : "garisan",
+DlgSelectChkMulti : "Benarkan pilihan pelbagai",
+DlgSelectOpAvail : "Pilihan sediada",
+DlgSelectOpText : "Teks",
+DlgSelectOpValue : "Nilai",
+DlgSelectBtnAdd : "Tambah Pilihan",
+DlgSelectBtnModify : "Ubah Pilihan",
+DlgSelectBtnUp : "Naik ke atas",
+DlgSelectBtnDown : "Turun ke bawah",
+DlgSelectBtnSetValue : "Set sebagai nilai terpilih",
+DlgSelectBtnDelete : "Padam",
+
+// Textarea Dialog
+DlgTextareaName : "Nama",
+DlgTextareaCols : "Lajur",
+DlgTextareaRows : "Baris",
+
+// Text Field Dialog
+DlgTextName : "Nama",
+DlgTextValue : "Nilai",
+DlgTextCharWidth : "Lebar isian",
+DlgTextMaxChars : "Isian Maksimum",
+DlgTextType : "Jenis",
+DlgTextTypeText : "Teks",
+DlgTextTypePass : "Kata Laluan",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nama",
+DlgHiddenValue : "Nilai",
+
+// Bulleted List Dialog
+BulletedListProp : "Ciri-ciri senarai berpeluru",
+NumberedListProp : "Ciri-ciri senarai bernombor",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Jenis",
+DlgLstTypeCircle : "Circle",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "Square",
+DlgLstTypeNumbers : "Nombor-nombor (1, 2, 3)",
+DlgLstTypeLCase : "Huruf-huruf kecil (a, b, c)",
+DlgLstTypeUCase : "Huruf-huruf besar (A, B, C)",
+DlgLstTypeSRoman : "Nombor Roman Kecil (i, ii, iii)",
+DlgLstTypeLRoman : "Nombor Roman Besar (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Umum",
+DlgDocBackTab : "Latarbelakang",
+DlgDocColorsTab : "Warna dan margin",
+DlgDocMetaTab : "Data Meta",
+
+DlgDocPageTitle : "Tajuk Muka Surat",
+DlgDocLangDir : "Arah Tulisan",
+DlgDocLangDirLTR : "Kiri ke Kanan (LTR)",
+DlgDocLangDirRTL : "Kanan ke Kiri (RTL)",
+DlgDocLangCode : "Kod Bahasa",
+DlgDocCharSet : "Enkod Set Huruf",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Enkod Set Huruf yang Lain",
+
+DlgDocDocType : "Jenis Kepala Dokumen",
+DlgDocDocTypeOther : "Jenis Kepala Dokumen yang Lain",
+DlgDocIncXHTML : "Masukkan pemula kod XHTML",
+DlgDocBgColor : "Warna Latarbelakang",
+DlgDocBgImage : "URL Gambar Latarbelakang",
+DlgDocBgNoScroll : "Imej Latarbelakang tanpa Skrol",
+DlgDocCText : "Teks",
+DlgDocCLink : "Sambungan",
+DlgDocCVisited : "Sambungan telah Dilawati",
+DlgDocCActive : "Sambungan Aktif",
+DlgDocMargins : "Margin Muka Surat",
+DlgDocMaTop : "Atas",
+DlgDocMaLeft : "Kiri",
+DlgDocMaRight : "Kanan",
+DlgDocMaBottom : "Bawah",
+DlgDocMeIndex : "Kata Kunci Indeks Dokumen (dipisahkan oleh koma)",
+DlgDocMeDescr : "Keterangan Dokumen",
+DlgDocMeAuthor : "Penulis",
+DlgDocMeCopy : "Hakcipta",
+DlgDocPreview : "Prebiu",
+
+// Templates Dialog
+Templates : "Templat",
+DlgTemplatesTitle : "Templat Kandungan",
+DlgTemplatesSelMsg : "Sila pilih templat untuk dibuka oleh editor<br>(kandungan sebenar akan hilang):",
+DlgTemplatesLoading : "Senarai Templat sedang diproses. Sila Tunggu...",
+DlgTemplatesNoTpl : "(Tiada Templat Disimpan)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "Tentang",
+DlgAboutBrowserInfoTab : "Maklumat Perisian Browser",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "versi",
+DlgAboutInfo : "Untuk maklumat lanjut sila pergi ke",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/nb.js b/httemplate/elements/fckeditor/editor/lang/nb.js
new file mode 100644
index 0000000..d720dce
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/nb.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Norwegian Bokmål language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skjul verktøylinje",
+ToolbarExpand : "Vis verktøylinje",
+
+// Toolbar Items and Context Menu
+Save : "Lagre",
+NewPage : "Ny Side",
+Preview : "Forhåndsvis",
+Cut : "Klipp ut",
+Copy : "Kopier",
+Paste : "Lim inn",
+PasteText : "Lim inn som ren tekst",
+PasteWord : "Lim inn fra Word",
+Print : "Skriv ut",
+SelectAll : "Merk alt",
+RemoveFormat : "Fjern format",
+InsertLinkLbl : "Lenke",
+InsertLink : "Sett inn/Rediger lenke",
+RemoveLink : "Fjern lenke",
+VisitLink : "Ã…pne lenke",
+Anchor : "Sett inn/Rediger anker",
+AnchorDelete : "Fjern anker",
+InsertImageLbl : "Bilde",
+InsertImage : "Sett inn/Rediger bilde",
+InsertFlashLbl : "Flash",
+InsertFlash : "Sett inn/Rediger Flash",
+InsertTableLbl : "Tabell",
+InsertTable : "Sett inn/Rediger tabell",
+InsertLineLbl : "Linje",
+InsertLine : "Sett inn horisontal linje",
+InsertSpecialCharLbl: "Spesielt tegn",
+InsertSpecialChar : "Sett inn spesielt tegn",
+InsertSmileyLbl : "Smil",
+InsertSmiley : "Sett inn smil",
+About : "Om FCKeditor",
+Bold : "Fet",
+Italic : "Kursiv",
+Underline : "Understrek",
+StrikeThrough : "Gjennomstrek",
+Subscript : "Senket skrift",
+Superscript : "Hevet skrift",
+LeftJustify : "Venstrejuster",
+CenterJustify : "Midtjuster",
+RightJustify : "Høyrejuster",
+BlockJustify : "Blokkjuster",
+DecreaseIndent : "Senk nivå",
+IncreaseIndent : "Øk nivå",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Angre",
+Redo : "Gjør om",
+NumberedListLbl : "Nummerert liste",
+NumberedList : "Sett inn/Fjern nummerert liste",
+BulletedListLbl : "Uordnet liste",
+BulletedList : "Sett inn/Fjern uordnet liste",
+ShowTableBorders : "Vis tabellrammer",
+ShowDetails : "Vis detaljer",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Skrift",
+FontSize : "Størrelse",
+TextColor : "Tekstfarge",
+BGColor : "Bakgrunnsfarge",
+Source : "Kilde",
+Find : "Søk",
+Replace : "Erstatt",
+SpellCheck : "Stavekontroll",
+UniversalKeyboard : "Universelt tastatur",
+PageBreakLbl : "Sideskift",
+PageBreak : "Sett inn sideskift",
+
+Form : "Skjema",
+Checkbox : "Avmerkingsboks",
+RadioButton : "Alternativknapp",
+TextField : "Tekstboks",
+Textarea : "Tekstområde",
+HiddenField : "Skjult felt",
+Button : "Knapp",
+SelectionField : "Rullegardinliste",
+ImageButton : "Bildeknapp",
+
+FitWindow : "Maksimer størrelsen på redigeringsverktøyet",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Rediger lenke",
+CellCM : "Celle",
+RowCM : "Rader",
+ColumnCM : "Kolonne",
+InsertRowAfter : "Sett inn rad etter",
+InsertRowBefore : "Sett inn rad før",
+DeleteRows : "Slett rader",
+InsertColumnAfter : "Sett inn kolonne etter",
+InsertColumnBefore : "Sett inn kolonne før",
+DeleteColumns : "Slett kolonner",
+InsertCellAfter : "Sett inn celle etter",
+InsertCellBefore : "Sett inn celle før",
+DeleteCells : "Slett celler",
+MergeCells : "Slå sammen celler",
+MergeRight : "Slå sammen høyre",
+MergeDown : "Slå sammen ned",
+HorizontalSplitCell : "Del celle horisontalt",
+VerticalSplitCell : "Del celle vertikalt",
+TableDelete : "Slett tabell",
+CellProperties : "Egenskaper for celle",
+TableProperties : "Egenskaper for tabell",
+ImageProperties : "Egenskaper for bilde",
+FlashProperties : "Egenskaper for Flash-objekt",
+
+AnchorProp : "Egenskaper for anker",
+ButtonProp : "Egenskaper for knapp",
+CheckboxProp : "Egenskaper for avmerkingsboks",
+HiddenFieldProp : "Egenskaper for skjult felt",
+RadioButtonProp : "Egenskaper for alternativknapp",
+ImageButtonProp : "Egenskaper for bildeknapp",
+TextFieldProp : "Egenskaper for tekstfelt",
+SelectionFieldProp : "Egenskaper for rullegardinliste",
+TextareaProp : "Egenskaper for tekstområde",
+FormProp : "Egenskaper for skjema",
+
+FontFormats : "Normal;Formatert;Adresse;Tittel 1;Tittel 2;Tittel 3;Tittel 4;Tittel 5;Tittel 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Lager XHTML. Vennligst vent...",
+Done : "Ferdig",
+PasteWordConfirm : "Teksten du prøver å lime inn ser ut som om den kommer fra Word. Vil du rense den for unødvendig kode før du limer inn?",
+NotCompatiblePaste : "Denne kommandoen er kun tilgjenglig for Internet Explorer versjon 5.5 eller bedre. Vil du fortsette uten å rense? (Du kan lime inn som ren tekst)",
+UnknownToolbarItem : "Ukjent menyvalg \"%1\"",
+UnknownCommand : "Ukjent kommando \"%1\"",
+NotImplemented : "Kommando ikke implimentert",
+UnknownToolbarSet : "Verktøylinjesett \"%1\" finnes ikke",
+NoActiveX : "Din nettlesers sikkerhetsinstillinger kan begrense noen av funksjonene i redigeringsverktøyet. Du må aktivere \"Kjør ActiveX-kontroller og plugin-modeller\". Du kan oppleve feil og advarsler om manglende funksjoner",
+BrowseServerBlocked : "Kunne ikke åpne dialogboksen for filarkiv. Sjekk at popup-blokkering er deaktivert.",
+DialogBlocked : "Kunne ikke åpne dialogboksen. Sjekk at popup-blokkering er deaktivert.",
+VisitLinkBlocked : "Kunne ikke åpne et nytt vindu. Sjekk at popup-blokkering er deaktivert.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Avbryt",
+DlgBtnClose : "Lukk",
+DlgBtnBrowseServer : "Bla igjennom server",
+DlgAdvancedTag : "Avansert",
+DlgOpOther : "<Annet>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Vennligst skriv inn URL-en",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ikke satt>",
+DlgGenId : "Id",
+DlgGenLangDir : "Språkretning",
+DlgGenLangDirLtr : "Venstre til høyre (VTH)",
+DlgGenLangDirRtl : "Høyre til venstre (HTV)",
+DlgGenLangCode : "Språkkode",
+DlgGenAccessKey : "Aksessknapp",
+DlgGenName : "Navn",
+DlgGenTabIndex : "Tab Indeks",
+DlgGenLongDescr : "Utvidet beskrivelse",
+DlgGenClass : "Stilarkklasser",
+DlgGenTitle : "Tittel",
+DlgGenContType : "Type",
+DlgGenLinkCharset : "Lenket språkkart",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Bildeegenskaper",
+DlgImgInfoTab : "Bildeinformasjon",
+DlgImgBtnUpload : "Send det til serveren",
+DlgImgURL : "URL",
+DlgImgUpload : "Last opp",
+DlgImgAlt : "Alternativ tekst",
+DlgImgWidth : "Bredde",
+DlgImgHeight : "Høyde",
+DlgImgLockRatio : "LÃ¥s forhold",
+DlgBtnResetSize : "Tilbakestill størrelse",
+DlgImgBorder : "Ramme",
+DlgImgHSpace : "HMarg",
+DlgImgVSpace : "VMarg",
+DlgImgAlign : "Juster",
+DlgImgAlignLeft : "Venstre",
+DlgImgAlignAbsBottom: "Abs bunn",
+DlgImgAlignAbsMiddle: "Abs midten",
+DlgImgAlignBaseline : "Bunnlinje",
+DlgImgAlignBottom : "Bunn",
+DlgImgAlignMiddle : "Midten",
+DlgImgAlignRight : "Høyre",
+DlgImgAlignTextTop : "Tekst topp",
+DlgImgAlignTop : "Topp",
+DlgImgPreview : "Forhåndsvis",
+DlgImgAlertUrl : "Vennligst skriv bilde-urlen",
+DlgImgLinkTab : "Lenke",
+
+// Flash Dialog
+DlgFlashTitle : "Flash-egenskaper",
+DlgFlashChkPlay : "Autospill",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Slå på Flash-meny",
+DlgFlashScale : "Skaler",
+DlgFlashScaleAll : "Vis alt",
+DlgFlashScaleNoBorder : "Ingen ramme",
+DlgFlashScaleFit : "Skaler til å passe",
+
+// Link Dialog
+DlgLnkWindowTitle : "Lenke",
+DlgLnkInfoTab : "Lenkeinfo",
+DlgLnkTargetTab : "MÃ¥l",
+
+DlgLnkType : "Lenketype",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Lenke til anker i teksten",
+DlgLnkTypeEMail : "E-post",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<annet>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Velg et anker",
+DlgLnkAnchorByName : "Anker etter navn",
+DlgLnkAnchorById : "Element etter ID",
+DlgLnkNoAnchors : "(Ingen anker i dokumentet)",
+DlgLnkEMail : "E-postadresse",
+DlgLnkEMailSubject : "Meldingsemne",
+DlgLnkEMailBody : "Melding",
+DlgLnkUpload : "Last opp",
+DlgLnkBtnUpload : "Send til server",
+
+DlgLnkTarget : "MÃ¥l",
+DlgLnkTargetFrame : "<ramme>",
+DlgLnkTargetPopup : "<popup vindu>",
+DlgLnkTargetBlank : "Nytt vindu (_blank)",
+DlgLnkTargetParent : "Foreldrevindu (_parent)",
+DlgLnkTargetSelf : "Samme vindu (_self)",
+DlgLnkTargetTop : "Hele vindu (_top)",
+DlgLnkTargetFrameName : "MÃ¥lramme",
+DlgLnkPopWinName : "Navn på popup-vindus",
+DlgLnkPopWinFeat : "Egenskaper for popup-vindu",
+DlgLnkPopResize : "Endre størrelse",
+DlgLnkPopLocation : "Adresselinje",
+DlgLnkPopMenu : "Menylinje",
+DlgLnkPopScroll : "Scrollbar",
+DlgLnkPopStatus : "Statuslinje",
+DlgLnkPopToolbar : "Verktøylinje",
+DlgLnkPopFullScrn : "Full skjerm (IE)",
+DlgLnkPopDependent : "Avhenging (Netscape)",
+DlgLnkPopWidth : "Bredde",
+DlgLnkPopHeight : "Høyde",
+DlgLnkPopLeft : "Venstre posisjon",
+DlgLnkPopTop : "Topp-posisjon",
+
+DlnLnkMsgNoUrl : "Vennligst skriv inn lenkens url",
+DlnLnkMsgNoEMail : "Vennligst skriv inn e-postadressen",
+DlnLnkMsgNoAnchor : "Vennligst velg et anker",
+DlnLnkMsgInvPopName : "Popup-vinduets navn må begynne med en bokstav, og kan ikke inneholde mellomrom",
+
+// Color Dialog
+DlgColorTitle : "Velg farge",
+DlgColorBtnClear : "Tøm",
+DlgColorHighlight : "Marker",
+DlgColorSelected : "Valgt",
+
+// Smiley Dialog
+DlgSmileyTitle : "Sett inn smil",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Velg spesielt tegn",
+
+// Table Dialog
+DlgTableTitle : "Egenskaper for tabell",
+DlgTableRows : "Rader",
+DlgTableColumns : "Kolonner",
+DlgTableBorder : "Rammestørrelse",
+DlgTableAlign : "Justering",
+DlgTableAlignNotSet : "<Ikke satt>",
+DlgTableAlignLeft : "Venstre",
+DlgTableAlignCenter : "Midtjuster",
+DlgTableAlignRight : "Høyre",
+DlgTableWidth : "Bredde",
+DlgTableWidthPx : "piksler",
+DlgTableWidthPc : "prosent",
+DlgTableHeight : "Høyde",
+DlgTableCellSpace : "Cellemarg",
+DlgTableCellPad : "Cellepolstring",
+DlgTableCaption : "Tittel",
+DlgTableSummary : "Sammendrag",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Celleegenskaper",
+DlgCellWidth : "Bredde",
+DlgCellWidthPx : "piksler",
+DlgCellWidthPc : "prosent",
+DlgCellHeight : "Høyde",
+DlgCellWordWrap : "Tekstbrytning",
+DlgCellWordWrapNotSet : "<Ikke satt>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nei",
+DlgCellHorAlign : "Horisontal justering",
+DlgCellHorAlignNotSet : "<Ikke satt>",
+DlgCellHorAlignLeft : "Venstre",
+DlgCellHorAlignCenter : "Midtjuster",
+DlgCellHorAlignRight: "Høyre",
+DlgCellVerAlign : "Vertikal justering",
+DlgCellVerAlignNotSet : "<Ikke satt>",
+DlgCellVerAlignTop : "Topp",
+DlgCellVerAlignMiddle : "Midten",
+DlgCellVerAlignBottom : "Bunn",
+DlgCellVerAlignBaseline : "Bunnlinje",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Radspenn",
+DlgCellCollSpan : "Kolonnespenn",
+DlgCellBackColor : "Bakgrunnsfarge",
+DlgCellBorderColor : "Rammefarge",
+DlgCellBtnSelect : "Velg...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Søk og erstatt",
+
+// Find Dialog
+DlgFindTitle : "Søk",
+DlgFindFindBtn : "Søk",
+DlgFindNotFoundMsg : "Fant ikke søketeksten.",
+
+// Replace Dialog
+DlgReplaceTitle : "Erstatt",
+DlgReplaceFindLbl : "Søk etter:",
+DlgReplaceReplaceLbl : "Erstatt med:",
+DlgReplaceCaseChk : "Skill mellom store og små bokstaver",
+DlgReplaceReplaceBtn : "Erstatt",
+DlgReplaceReplAllBtn : "Erstatt alle",
+DlgReplaceWordChk : "Bare hele ord",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk klipping av tekst. Vennligst bruk snareveien (Ctrl+X).",
+PasteErrorCopy : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst bruk snareveien (Ctrl+C).",
+
+PasteAsText : "Lim inn som ren tekst",
+PasteFromWord : "Lim inn fra Word",
+
+DlgPasteMsg2 : "Vennligst lim inn i den følgende boksen med tastaturet (<STRONG>Ctrl+V</STRONG>) og trykk <STRONG>OK</STRONG>.",
+DlgPasteSec : "Din nettlesers sikkerhetsinstillinger gir ikke redigeringsverktøyet direkte tilgang til utklippstavlen. Du må lime det igjen i dette vinduet.",
+DlgPasteIgnoreFont : "Fjern skrifttyper",
+DlgPasteRemoveStyles : "Fjern stildefinisjoner",
+
+// Color Picker
+ColorAutomatic : "Automatisk",
+ColorMoreColors : "Flere farger...",
+
+// Document Properties
+DocProps : "Dokumentegenskaper",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankeregenskaper",
+DlgAnchorName : "Ankernavn",
+DlgAnchorErrorName : "Vennligst skriv inn ankernavnet",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ikke i ordboken",
+DlgSpellChangeTo : "Endre til",
+DlgSpellBtnIgnore : "Ignorer",
+DlgSpellBtnIgnoreAll : "Ignorer alle",
+DlgSpellBtnReplace : "Erstatt",
+DlgSpellBtnReplaceAll : "Erstatt alle",
+DlgSpellBtnUndo : "Angre",
+DlgSpellNoSuggestions : "- Ingen forslag -",
+DlgSpellProgress : "Stavekontroll pågår...",
+DlgSpellNoMispell : "Stavekontroll fullført: ingen feilstavinger funnet",
+DlgSpellNoChanges : "Stavekontroll fullført: ingen ord endret",
+DlgSpellOneChange : "Stavekontroll fullført: Ett ord endret",
+DlgSpellManyChanges : "Stavekontroll fullført: %1 ord endret",
+
+IeSpellDownload : "Stavekontroll er ikke installert. Vil du laste den ned nå?",
+
+// Button Dialog
+DlgButtonText : "Tekst (verdi)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Knapp",
+DlgButtonTypeSbm : "Send",
+DlgButtonTypeRst : "Nullstill",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Navn",
+DlgCheckboxValue : "Verdi",
+DlgCheckboxSelected : "Valgt",
+
+// Form Dialog
+DlgFormName : "Navn",
+DlgFormAction : "Handling",
+DlgFormMethod : "Metode",
+
+// Select Field Dialog
+DlgSelectName : "Navn",
+DlgSelectValue : "Verdi",
+DlgSelectSize : "Størrelse",
+DlgSelectLines : "Linjer",
+DlgSelectChkMulti : "Tillat flervalg",
+DlgSelectOpAvail : "Tilgjenglige alternativer",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Verdi",
+DlgSelectBtnAdd : "Legg til",
+DlgSelectBtnModify : "Endre",
+DlgSelectBtnUp : "Opp",
+DlgSelectBtnDown : "Ned",
+DlgSelectBtnSetValue : "Sett som valgt",
+DlgSelectBtnDelete : "Slett",
+
+// Textarea Dialog
+DlgTextareaName : "Navn",
+DlgTextareaCols : "Kolonner",
+DlgTextareaRows : "Rader",
+
+// Text Field Dialog
+DlgTextName : "Navn",
+DlgTextValue : "Verdi",
+DlgTextCharWidth : "Tegnbredde",
+DlgTextMaxChars : "Maks antall tegn",
+DlgTextType : "Type",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Passord",
+
+// Hidden Field Dialog
+DlgHiddenName : "Navn",
+DlgHiddenValue : "Verdi",
+
+// Bulleted List Dialog
+BulletedListProp : "Egenskaper for uordnet liste",
+NumberedListProp : "Egenskaper for ordnet liste",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Sirkel",
+DlgLstTypeDisc : "Hel sirkel",
+DlgLstTypeSquare : "Firkant",
+DlgLstTypeNumbers : "Numre (1, 2, 3)",
+DlgLstTypeLCase : "Små bokstaver (a, b, c)",
+DlgLstTypeUCase : "Store bokstaver (A, B, C)",
+DlgLstTypeSRoman : "Små romerske tall (i, ii, iii)",
+DlgLstTypeLRoman : "Store romerske tall (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Generelt",
+DlgDocBackTab : "Bakgrunn",
+DlgDocColorsTab : "Farger og marginer",
+DlgDocMetaTab : "Meta-data",
+
+DlgDocPageTitle : "Sidetittel",
+DlgDocLangDir : "Språkretning",
+DlgDocLangDirLTR : "Venstre til høyre (LTR)",
+DlgDocLangDirRTL : "Høyre til venstre (RTL)",
+DlgDocLangCode : "Språkkode",
+DlgDocCharSet : "Tegnsett",
+DlgDocCharSetCE : "Sentraleuropeisk",
+DlgDocCharSetCT : "Tradisonell kinesisk(Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Gresk",
+DlgDocCharSetJP : "Japansk",
+DlgDocCharSetKR : "Koreansk",
+DlgDocCharSetTR : "Tyrkisk",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Vesteuropeisk",
+DlgDocCharSetOther : "Annet tegnsett",
+
+DlgDocDocType : "Dokumenttype header",
+DlgDocDocTypeOther : "Annet dokumenttype header",
+DlgDocIncXHTML : "Inkluder XHTML-deklarasjon",
+DlgDocBgColor : "Bakgrunnsfarge",
+DlgDocBgImage : "URL for bakgrunnsbilde",
+DlgDocBgNoScroll : "LÃ¥s bakgrunnsbilde",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Besøkt lenke",
+DlgDocCActive : "Aktiv lenke",
+DlgDocMargins : "Sidemargin",
+DlgDocMaTop : "Topp",
+DlgDocMaLeft : "Venstre",
+DlgDocMaRight : "Høyre",
+DlgDocMaBottom : "Bunn",
+DlgDocMeIndex : "Dokument nøkkelord (kommaseparert)",
+DlgDocMeDescr : "Dokumentbeskrivelse",
+DlgDocMeAuthor : "Forfatter",
+DlgDocMeCopy : "Kopirett",
+DlgDocPreview : "Forhåndsvising",
+
+// Templates Dialog
+Templates : "Maler",
+DlgTemplatesTitle : "Innholdsmaler",
+DlgTemplatesSelMsg : "Velg malen du vil åpne<br>(innholdet du har skrevet blir tapt!):",
+DlgTemplatesLoading : "Laster malliste. Vennligst vent...",
+DlgTemplatesNoTpl : "(Ingen maler definert)",
+DlgTemplatesReplace : "Erstatt faktisk innold",
+
+// About Dialog
+DlgAboutAboutTab : "Om",
+DlgAboutBrowserInfoTab : "Nettleserinfo",
+DlgAboutLicenseTab : "Lisens",
+DlgAboutVersion : "versjon",
+DlgAboutInfo : "For mer informasjon, se",
+
+// Div Dialog
+DlgDivGeneralTab : "Generelt",
+DlgDivAdvancedTab : "Avansert",
+DlgDivStyle : "Stil",
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/nl.js b/httemplate/elements/fckeditor/editor/lang/nl.js
new file mode 100644
index 0000000..f84f1a6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/nl.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Dutch language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Menubalk inklappen",
+ToolbarExpand : "Menubalk uitklappen",
+
+// Toolbar Items and Context Menu
+Save : "Opslaan",
+NewPage : "Nieuwe pagina",
+Preview : "Voorbeeld",
+Cut : "Knippen",
+Copy : "Kopiëren",
+Paste : "Plakken",
+PasteText : "Plakken als platte tekst",
+PasteWord : "Plakken als Word-gegevens",
+Print : "Printen",
+SelectAll : "Alles selecteren",
+RemoveFormat : "Opmaak verwijderen",
+InsertLinkLbl : "Link",
+InsertLink : "Link invoegen/wijzigen",
+RemoveLink : "Link verwijderen",
+VisitLink : "Link volgen",
+Anchor : "Interne link",
+AnchorDelete : "Anker verwijderen",
+InsertImageLbl : "Afbeelding",
+InsertImage : "Afbeelding invoegen/wijzigen",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash invoegen/wijzigen",
+InsertTableLbl : "Tabel",
+InsertTable : "Tabel invoegen/wijzigen",
+InsertLineLbl : "Lijn",
+InsertLine : "Horizontale lijn invoegen",
+InsertSpecialCharLbl: "Speciale tekens",
+InsertSpecialChar : "Speciaal teken invoegen",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Smiley invoegen",
+About : "Over FCKeditor",
+Bold : "Vet",
+Italic : "Schuingedrukt",
+Underline : "Onderstreept",
+StrikeThrough : "Doorhalen",
+Subscript : "Subscript",
+Superscript : "Superscript",
+LeftJustify : "Links uitlijnen",
+CenterJustify : "Centreren",
+RightJustify : "Rechts uitlijnen",
+BlockJustify : "Uitvullen",
+DecreaseIndent : "Inspringen verkleinen",
+IncreaseIndent : "Inspringen vergroten",
+Blockquote : "Citaatblok",
+CreateDiv : "DIV aanmaken",
+EditDiv : "DIV wijzigen",
+DeleteDiv : "DIV verwijderen",
+Undo : "Ongedaan maken",
+Redo : "Opnieuw uitvoeren",
+NumberedListLbl : "Genummerde lijst",
+NumberedList : "Genummerde lijst invoegen/verwijderen",
+BulletedListLbl : "Opsomming",
+BulletedList : "Opsomming invoegen/verwijderen",
+ShowTableBorders : "Randen tabel weergeven",
+ShowDetails : "Details weergeven",
+Style : "Stijl",
+FontFormat : "Opmaak",
+Font : "Lettertype",
+FontSize : "Grootte",
+TextColor : "Tekstkleur",
+BGColor : "Achtergrondkleur",
+Source : "Code",
+Find : "Zoeken",
+Replace : "Vervangen",
+SpellCheck : "Spellingscontrole",
+UniversalKeyboard : "Universeel toetsenbord",
+PageBreakLbl : "Pagina-einde",
+PageBreak : "Pagina-einde invoegen",
+
+Form : "Formulier",
+Checkbox : "Aanvinkvakje",
+RadioButton : "Selectievakje",
+TextField : "Tekstveld",
+Textarea : "Tekstvak",
+HiddenField : "Verborgen veld",
+Button : "Knop",
+SelectionField : "Selectieveld",
+ImageButton : "Afbeeldingsknop",
+
+FitWindow : "De editor maximaliseren",
+ShowBlocks : "Toon blokken",
+
+// Context Menu
+EditLink : "Link wijzigen",
+CellCM : "Cel",
+RowCM : "Rij",
+ColumnCM : "Kolom",
+InsertRowAfter : "Voeg rij in achter",
+InsertRowBefore : "Voeg rij in voor",
+DeleteRows : "Rijen verwijderen",
+InsertColumnAfter : "Voeg kolom in achter",
+InsertColumnBefore : "Voeg kolom in voor",
+DeleteColumns : "Kolommen verwijderen",
+InsertCellAfter : "Voeg cel in achter",
+InsertCellBefore : "Voeg cel in voor",
+DeleteCells : "Cellen verwijderen",
+MergeCells : "Cellen samenvoegen",
+MergeRight : "Voeg samen naar rechts",
+MergeDown : "Voeg samen naar beneden",
+HorizontalSplitCell : "Splits cellen horizontaal",
+VerticalSplitCell : "Splits cellen verticaal",
+TableDelete : "Tabel verwijderen",
+CellProperties : "Eigenschappen cel",
+TableProperties : "Eigenschappen tabel",
+ImageProperties : "Eigenschappen afbeelding",
+FlashProperties : "Eigenschappen Flash",
+
+AnchorProp : "Eigenschappen interne link",
+ButtonProp : "Eigenschappen knop",
+CheckboxProp : "Eigenschappen aanvinkvakje",
+HiddenFieldProp : "Eigenschappen verborgen veld",
+RadioButtonProp : "Eigenschappen selectievakje",
+ImageButtonProp : "Eigenschappen afbeeldingsknop",
+TextFieldProp : "Eigenschappen tekstveld",
+SelectionFieldProp : "Eigenschappen selectieveld",
+TextareaProp : "Eigenschappen tekstvak",
+FormProp : "Eigenschappen formulier",
+
+FontFormats : "Normaal;Met opmaak;Adres;Kop 1;Kop 2;Kop 3;Kop 4;Kop 5;Kop 6;Normaal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Bezig met verwerken XHTML. Even geduld aub...",
+Done : "Klaar",
+PasteWordConfirm : "De tekst die u plakte lijkt gekopieerd te zijn vanuit Word. Wilt u de tekst opschonen voordat deze geplakt wordt?",
+NotCompatiblePaste : "Deze opdracht is beschikbaar voor Internet Explorer versie 5.5 of hoger. Wilt u plakken zonder op te schonen?",
+UnknownToolbarItem : "Onbekend item op menubalk \"%1\"",
+UnknownCommand : "Onbekende opdrachtnaam: \"%1\"",
+NotImplemented : "Opdracht niet geïmplementeerd.",
+UnknownToolbarSet : "Menubalk \"%1\" bestaat niet.",
+NoActiveX : "De beveilingsinstellingen van uw browser zouden sommige functies van de editor kunnen beperken. De optie \"Activeer ActiveX-elementen en plug-ins\" dient ingeschakeld te worden. Het kan zijn dat er nu functies ontbreken of niet werken.",
+BrowseServerBlocked : "De bestandsbrowser kon niet geopend worden. Zorg ervoor dat pop-up-blokkeerders uit staan.",
+DialogBlocked : "Kan het dialoogvenster niet weergeven. Zorg ervoor dat pop-up-blokkeerders uit staan.",
+VisitLinkBlocked : "Het was niet mogelijk een nieuw venster te openen. Controleer of er geen pop-up-blocker aktief is.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Annuleren",
+DlgBtnClose : "Afsluiten",
+DlgBtnBrowseServer : "Bladeren op server",
+DlgAdvancedTag : "Geavanceerd",
+DlgOpOther : "<Anders>",
+DlgInfoTab : "Informatie",
+DlgAlertUrl : "Geef URL op",
+
+// General Dialogs Labels
+DlgGenNotSet : "<niet ingevuld>",
+DlgGenId : "Kenmerk",
+DlgGenLangDir : "Schrijfrichting",
+DlgGenLangDirLtr : "Links naar rechts (LTR)",
+DlgGenLangDirRtl : "Rechts naar links (RTL)",
+DlgGenLangCode : "Taalcode",
+DlgGenAccessKey : "Toegangstoets",
+DlgGenName : "Naam",
+DlgGenTabIndex : "Tabvolgorde",
+DlgGenLongDescr : "Lange URL-omschrijving",
+DlgGenClass : "Stylesheet-klassen",
+DlgGenTitle : "Aanbevolen titel",
+DlgGenContType : "Aanbevolen content-type",
+DlgGenLinkCharset : "Karakterset van gelinkte bron",
+DlgGenStyle : "Stijl",
+
+// Image Dialog
+DlgImgTitle : "Eigenschappen afbeelding",
+DlgImgInfoTab : "Informatie afbeelding",
+DlgImgBtnUpload : "Naar server verzenden",
+DlgImgURL : "URL",
+DlgImgUpload : "Upload",
+DlgImgAlt : "Alternatieve tekst",
+DlgImgWidth : "Breedte",
+DlgImgHeight : "Hoogte",
+DlgImgLockRatio : "Afmetingen vergrendelen",
+DlgBtnResetSize : "Afmetingen resetten",
+DlgImgBorder : "Rand",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Uitlijning",
+DlgImgAlignLeft : "Links",
+DlgImgAlignAbsBottom: "Absoluut-onder",
+DlgImgAlignAbsMiddle: "Absoluut-midden",
+DlgImgAlignBaseline : "Basislijn",
+DlgImgAlignBottom : "Beneden",
+DlgImgAlignMiddle : "Midden",
+DlgImgAlignRight : "Rechts",
+DlgImgAlignTextTop : "Boven tekst",
+DlgImgAlignTop : "Boven",
+DlgImgPreview : "Voorbeeld",
+DlgImgAlertUrl : "Geef de URL van de afbeelding",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Eigenschappen Flash",
+DlgFlashChkPlay : "Automatisch afspelen",
+DlgFlashChkLoop : "Herhalen",
+DlgFlashChkMenu : "Flashmenu\'s inschakelen",
+DlgFlashScale : "Schaal",
+DlgFlashScaleAll : "Alles tonen",
+DlgFlashScaleNoBorder : "Geen rand",
+DlgFlashScaleFit : "Precies passend",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Linkomschrijving",
+DlgLnkTargetTab : "Doel",
+
+DlgLnkType : "Linktype",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Interne link in pagina",
+DlgLnkTypeEMail : "E-mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<anders>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Kies een interne link",
+DlgLnkAnchorByName : "Op naam interne link",
+DlgLnkAnchorById : "Op kenmerk interne link",
+DlgLnkNoAnchors : "(Geen interne links in document gevonden)",
+DlgLnkEMail : "E-mailadres",
+DlgLnkEMailSubject : "Onderwerp bericht",
+DlgLnkEMailBody : "Inhoud bericht",
+DlgLnkUpload : "Upload",
+DlgLnkBtnUpload : "Naar de server versturen",
+
+DlgLnkTarget : "Doel",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<popup window>",
+DlgLnkTargetBlank : "Nieuw venster (_blank)",
+DlgLnkTargetParent : "Origineel venster (_parent)",
+DlgLnkTargetSelf : "Zelfde venster (_self)",
+DlgLnkTargetTop : "Hele venster (_top)",
+DlgLnkTargetFrameName : "Naam doelframe",
+DlgLnkPopWinName : "Naam popupvenster",
+DlgLnkPopWinFeat : "Instellingen popupvenster",
+DlgLnkPopResize : "Grootte wijzigen",
+DlgLnkPopLocation : "Locatiemenu",
+DlgLnkPopMenu : "Menubalk",
+DlgLnkPopScroll : "Schuifbalken",
+DlgLnkPopStatus : "Statusbalk",
+DlgLnkPopToolbar : "Menubalk",
+DlgLnkPopFullScrn : "Volledig scherm (IE)",
+DlgLnkPopDependent : "Afhankelijk (Netscape)",
+DlgLnkPopWidth : "Breedte",
+DlgLnkPopHeight : "Hoogte",
+DlgLnkPopLeft : "Positie links",
+DlgLnkPopTop : "Positie boven",
+
+DlnLnkMsgNoUrl : "Geef de link van de URL",
+DlnLnkMsgNoEMail : "Geef een e-mailadres",
+DlnLnkMsgNoAnchor : "Selecteer een interne link",
+DlnLnkMsgInvPopName : "De naam van de popup moet met een alfa-numerieke waarde beginnen, en mag geen spaties bevatten.",
+
+// Color Dialog
+DlgColorTitle : "Selecteer kleur",
+DlgColorBtnClear : "Opschonen",
+DlgColorHighlight : "Accentueren",
+DlgColorSelected : "Geselecteerd",
+
+// Smiley Dialog
+DlgSmileyTitle : "Smiley invoegen",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Selecteer speciaal teken",
+
+// Table Dialog
+DlgTableTitle : "Eigenschappen tabel",
+DlgTableRows : "Rijen",
+DlgTableColumns : "Kolommen",
+DlgTableBorder : "Breedte rand",
+DlgTableAlign : "Uitlijning",
+DlgTableAlignNotSet : "<Niet ingevoerd>",
+DlgTableAlignLeft : "Links",
+DlgTableAlignCenter : "Centreren",
+DlgTableAlignRight : "Rechts",
+DlgTableWidth : "Breedte",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "procent",
+DlgTableHeight : "Hoogte",
+DlgTableCellSpace : "Afstand tussen cellen",
+DlgTableCellPad : "Afstand vanaf rand cel",
+DlgTableCaption : "Naam",
+DlgTableSummary : "Samenvatting",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Eigenschappen cel",
+DlgCellWidth : "Breedte",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "procent",
+DlgCellHeight : "Hoogte",
+DlgCellWordWrap : "Afbreken woorden",
+DlgCellWordWrapNotSet : "<Niet ingevoerd>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nee",
+DlgCellHorAlign : "Horizontale uitlijning",
+DlgCellHorAlignNotSet : "<Niet ingevoerd>",
+DlgCellHorAlignLeft : "Links",
+DlgCellHorAlignCenter : "Centreren",
+DlgCellHorAlignRight: "Rechts",
+DlgCellVerAlign : "Verticale uitlijning",
+DlgCellVerAlignNotSet : "<Niet ingevoerd>",
+DlgCellVerAlignTop : "Boven",
+DlgCellVerAlignMiddle : "Midden",
+DlgCellVerAlignBottom : "Beneden",
+DlgCellVerAlignBaseline : "Basislijn",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Overkoepeling rijen",
+DlgCellCollSpan : "Overkoepeling kolommen",
+DlgCellBackColor : "Achtergrondkleur",
+DlgCellBorderColor : "Randkleur",
+DlgCellBtnSelect : "Selecteren...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Zoeken en vervangen",
+
+// Find Dialog
+DlgFindTitle : "Zoeken",
+DlgFindFindBtn : "Zoeken",
+DlgFindNotFoundMsg : "De opgegeven tekst is niet gevonden.",
+
+// Replace Dialog
+DlgReplaceTitle : "Vervangen",
+DlgReplaceFindLbl : "Zoeken naar:",
+DlgReplaceReplaceLbl : "Vervangen met:",
+DlgReplaceCaseChk : "Hoofdlettergevoelig",
+DlgReplaceReplaceBtn : "Vervangen",
+DlgReplaceReplAllBtn : "Alles vervangen",
+DlgReplaceWordChk : "Hele woord moet voorkomen",
+
+// Paste Operations / Dialog
+PasteErrorCut : "De beveiligingsinstelling van de browser verhinderen het automatisch knippen. Gebruik de sneltoets Ctrl+X van het toetsenbord.",
+PasteErrorCopy : "De beveiligingsinstelling van de browser verhinderen het automatisch kopiëren. Gebruik de sneltoets Ctrl+C van het toetsenbord.",
+
+PasteAsText : "Plakken als platte tekst",
+PasteFromWord : "Plakken als Word-gegevens",
+
+DlgPasteMsg2 : "Plak de tekst in het volgende vak gebruik makend van uw toetsenbord (<strong>Ctrl+V</strong>) en klik op <strong>OK</strong>.",
+DlgPasteSec : "Door de beveiligingsinstellingen van uw browser is het niet mogelijk om direct vanuit het klembord in de editor te plakken. Middels opnieuw plakken in dit venster kunt u de tekst alsnog plakken in de editor.",
+DlgPasteIgnoreFont : "Negeer \"Font Face\"-definities",
+DlgPasteRemoveStyles : "Verwijder \"Style\"-definities",
+
+// Color Picker
+ColorAutomatic : "Automatisch",
+ColorMoreColors : "Meer kleuren...",
+
+// Document Properties
+DocProps : "Eigenschappen document",
+
+// Anchor Dialog
+DlgAnchorTitle : "Eigenschappen interne link",
+DlgAnchorName : "Naam interne link",
+DlgAnchorErrorName : "Geef de naam van de interne link op",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Niet in het woordenboek",
+DlgSpellChangeTo : "Wijzig in",
+DlgSpellBtnIgnore : "Negeren",
+DlgSpellBtnIgnoreAll : "Alles negeren",
+DlgSpellBtnReplace : "Vervangen",
+DlgSpellBtnReplaceAll : "Alles vervangen",
+DlgSpellBtnUndo : "Ongedaan maken",
+DlgSpellNoSuggestions : "-Geen suggesties-",
+DlgSpellProgress : "Bezig met spellingscontrole...",
+DlgSpellNoMispell : "Klaar met spellingscontrole: geen fouten gevonden",
+DlgSpellNoChanges : "Klaar met spellingscontrole: geen woorden aangepast",
+DlgSpellOneChange : "Klaar met spellingscontrole: één woord aangepast",
+DlgSpellManyChanges : "Klaar met spellingscontrole: %1 woorden aangepast",
+
+IeSpellDownload : "De spellingscontrole niet geïnstalleerd. Wilt u deze nu downloaden?",
+
+// Button Dialog
+DlgButtonText : "Tekst (waarde)",
+DlgButtonType : "Soort",
+DlgButtonTypeBtn : "Knop",
+DlgButtonTypeSbm : "Versturen",
+DlgButtonTypeRst : "Leegmaken",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Naam",
+DlgCheckboxValue : "Waarde",
+DlgCheckboxSelected : "Geselecteerd",
+
+// Form Dialog
+DlgFormName : "Naam",
+DlgFormAction : "Actie",
+DlgFormMethod : "Methode",
+
+// Select Field Dialog
+DlgSelectName : "Naam",
+DlgSelectValue : "Waarde",
+DlgSelectSize : "Grootte",
+DlgSelectLines : "Regels",
+DlgSelectChkMulti : "Gecombineerde selecties toestaan",
+DlgSelectOpAvail : "Beschikbare opties",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Waarde",
+DlgSelectBtnAdd : "Toevoegen",
+DlgSelectBtnModify : "Wijzigen",
+DlgSelectBtnUp : "Omhoog",
+DlgSelectBtnDown : "Omlaag",
+DlgSelectBtnSetValue : "Als geselecteerde waarde instellen",
+DlgSelectBtnDelete : "Verwijderen",
+
+// Textarea Dialog
+DlgTextareaName : "Naam",
+DlgTextareaCols : "Kolommen",
+DlgTextareaRows : "Rijen",
+
+// Text Field Dialog
+DlgTextName : "Naam",
+DlgTextValue : "Waarde",
+DlgTextCharWidth : "Breedte (tekens)",
+DlgTextMaxChars : "Maximum aantal tekens",
+DlgTextType : "Soort",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Wachtwoord",
+
+// Hidden Field Dialog
+DlgHiddenName : "Naam",
+DlgHiddenValue : "Waarde",
+
+// Bulleted List Dialog
+BulletedListProp : "Eigenschappen opsommingslijst",
+NumberedListProp : "Eigenschappen genummerde opsommingslijst",
+DlgLstStart : "Start",
+DlgLstType : "Soort",
+DlgLstTypeCircle : "Cirkel",
+DlgLstTypeDisc : "Schijf",
+DlgLstTypeSquare : "Vierkant",
+DlgLstTypeNumbers : "Nummers (1, 2, 3)",
+DlgLstTypeLCase : "Kleine letters (a, b, c)",
+DlgLstTypeUCase : "Hoofdletters (A, B, C)",
+DlgLstTypeSRoman : "Klein Romeins (i, ii, iii)",
+DlgLstTypeLRoman : "Groot Romeins (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Algemeen",
+DlgDocBackTab : "Achtergrond",
+DlgDocColorsTab : "Kleuring en marges",
+DlgDocMetaTab : "META-data",
+
+DlgDocPageTitle : "Paginatitel",
+DlgDocLangDir : "Schrijfrichting",
+DlgDocLangDirLTR : "Links naar rechts",
+DlgDocLangDirRTL : "Rechts naar links",
+DlgDocLangCode : "Taalcode",
+DlgDocCharSet : "Karakterset-encoding",
+DlgDocCharSetCE : "Centraal Europees",
+DlgDocCharSetCT : "Traditioneel Chinees (Big5)",
+DlgDocCharSetCR : "Cyriliaans",
+DlgDocCharSetGR : "Grieks",
+DlgDocCharSetJP : "Japans",
+DlgDocCharSetKR : "Koreaans",
+DlgDocCharSetTR : "Turks",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "West europees",
+DlgDocCharSetOther : "Andere karakterset-encoding",
+
+DlgDocDocType : "Opschrift documentsoort",
+DlgDocDocTypeOther : "Ander opschrift documentsoort",
+DlgDocIncXHTML : "XHTML-declaraties meenemen",
+DlgDocBgColor : "Achtergrondkleur",
+DlgDocBgImage : "URL achtergrondplaatje",
+DlgDocBgNoScroll : "Vaste achtergrond",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Bezochte link",
+DlgDocCActive : "Active link",
+DlgDocMargins : "Afstandsinstellingen document",
+DlgDocMaTop : "Boven",
+DlgDocMaLeft : "Links",
+DlgDocMaRight : "Rechts",
+DlgDocMaBottom : "Onder",
+DlgDocMeIndex : "Trefwoorden betreffende document (kommagescheiden)",
+DlgDocMeDescr : "Beschrijving document",
+DlgDocMeAuthor : "Auteur",
+DlgDocMeCopy : "Copyright",
+DlgDocPreview : "Voorbeeld",
+
+// Templates Dialog
+Templates : "Sjablonen",
+DlgTemplatesTitle : "Inhoud sjabonen",
+DlgTemplatesSelMsg : "Selecteer het sjabloon dat in de editor geopend moet worden (de actuele inhoud gaat verloren):",
+DlgTemplatesLoading : "Bezig met laden sjabonen. Even geduld alstublieft...",
+DlgTemplatesNoTpl : "(Geen sjablonen gedefinieerd)",
+DlgTemplatesReplace : "Vervang de huidige inhoud",
+
+// About Dialog
+DlgAboutAboutTab : "Over",
+DlgAboutBrowserInfoTab : "Browserinformatie",
+DlgAboutLicenseTab : "Licentie",
+DlgAboutVersion : "Versie",
+DlgAboutInfo : "Voor meer informatie ga naar ",
+
+// Div Dialog
+DlgDivGeneralTab : "Algemeen",
+DlgDivAdvancedTab : "Geavanceerd",
+DlgDivStyle : "Style",
+DlgDivInlineStyle : "Inline Style",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/no.js b/httemplate/elements/fckeditor/editor/lang/no.js
new file mode 100644
index 0000000..9809bda
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/no.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Norwegian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skjul verktøylinje",
+ToolbarExpand : "Vis verktøylinje",
+
+// Toolbar Items and Context Menu
+Save : "Lagre",
+NewPage : "Ny Side",
+Preview : "Forhåndsvis",
+Cut : "Klipp ut",
+Copy : "Kopier",
+Paste : "Lim inn",
+PasteText : "Lim inn som ren tekst",
+PasteWord : "Lim inn fra Word",
+Print : "Skriv ut",
+SelectAll : "Merk alt",
+RemoveFormat : "Fjern format",
+InsertLinkLbl : "Lenke",
+InsertLink : "Sett inn/Rediger lenke",
+RemoveLink : "Fjern lenke",
+VisitLink : "Ã…pne lenke",
+Anchor : "Sett inn/Rediger anker",
+AnchorDelete : "Fjern anker",
+InsertImageLbl : "Bilde",
+InsertImage : "Sett inn/Rediger bilde",
+InsertFlashLbl : "Flash",
+InsertFlash : "Sett inn/Rediger Flash",
+InsertTableLbl : "Tabell",
+InsertTable : "Sett inn/Rediger tabell",
+InsertLineLbl : "Linje",
+InsertLine : "Sett inn horisontal linje",
+InsertSpecialCharLbl: "Spesielt tegn",
+InsertSpecialChar : "Sett inn spesielt tegn",
+InsertSmileyLbl : "Smil",
+InsertSmiley : "Sett inn smil",
+About : "Om FCKeditor",
+Bold : "Fet",
+Italic : "Kursiv",
+Underline : "Understrek",
+StrikeThrough : "Gjennomstrek",
+Subscript : "Senket skrift",
+Superscript : "Hevet skrift",
+LeftJustify : "Venstrejuster",
+CenterJustify : "Midtjuster",
+RightJustify : "Høyrejuster",
+BlockJustify : "Blokkjuster",
+DecreaseIndent : "Senk nivå",
+IncreaseIndent : "Øk nivå",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Angre",
+Redo : "Gjør om",
+NumberedListLbl : "Nummerert liste",
+NumberedList : "Sett inn/Fjern nummerert liste",
+BulletedListLbl : "Uordnet liste",
+BulletedList : "Sett inn/Fjern uordnet liste",
+ShowTableBorders : "Vis tabellrammer",
+ShowDetails : "Vis detaljer",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Skrift",
+FontSize : "Størrelse",
+TextColor : "Tekstfarge",
+BGColor : "Bakgrunnsfarge",
+Source : "Kilde",
+Find : "Søk",
+Replace : "Erstatt",
+SpellCheck : "Stavekontroll",
+UniversalKeyboard : "Universelt tastatur",
+PageBreakLbl : "Sideskift",
+PageBreak : "Sett inn sideskift",
+
+Form : "Skjema",
+Checkbox : "Avmerkingsboks",
+RadioButton : "Alternativknapp",
+TextField : "Tekstboks",
+Textarea : "Tekstområde",
+HiddenField : "Skjult felt",
+Button : "Knapp",
+SelectionField : "Rullegardinliste",
+ImageButton : "Bildeknapp",
+
+FitWindow : "Maksimer størrelsen på redigeringsverktøyet",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Rediger lenke",
+CellCM : "Celle",
+RowCM : "Rader",
+ColumnCM : "Kolonne",
+InsertRowAfter : "Sett inn rad etter",
+InsertRowBefore : "Sett inn rad før",
+DeleteRows : "Slett rader",
+InsertColumnAfter : "Sett inn kolonne etter",
+InsertColumnBefore : "Sett inn kolonne før",
+DeleteColumns : "Slett kolonner",
+InsertCellAfter : "Sett inn celle etter",
+InsertCellBefore : "Sett inn celle før",
+DeleteCells : "Slett celler",
+MergeCells : "Slå sammen celler",
+MergeRight : "Slå sammen høyre",
+MergeDown : "Slå sammen ned",
+HorizontalSplitCell : "Del celle horisontalt",
+VerticalSplitCell : "Del celle vertikalt",
+TableDelete : "Slett tabell",
+CellProperties : "Egenskaper for celle",
+TableProperties : "Egenskaper for tabell",
+ImageProperties : "Egenskaper for bilde",
+FlashProperties : "Egenskaper for Flash-objekt",
+
+AnchorProp : "Egenskaper for anker",
+ButtonProp : "Egenskaper for knapp",
+CheckboxProp : "Egenskaper for avmerkingsboks",
+HiddenFieldProp : "Egenskaper for skjult felt",
+RadioButtonProp : "Egenskaper for alternativknapp",
+ImageButtonProp : "Egenskaper for bildeknapp",
+TextFieldProp : "Egenskaper for tekstfelt",
+SelectionFieldProp : "Egenskaper for rullegardinliste",
+TextareaProp : "Egenskaper for tekstområde",
+FormProp : "Egenskaper for skjema",
+
+FontFormats : "Normal;Formatert;Adresse;Tittel 1;Tittel 2;Tittel 3;Tittel 4;Tittel 5;Tittel 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Lager XHTML. Vennligst vent...",
+Done : "Ferdig",
+PasteWordConfirm : "Teksten du prøver å lime inn ser ut som om den kommer fra Word. Vil du rense den for unødvendig kode før du limer inn?",
+NotCompatiblePaste : "Denne kommandoen er kun tilgjenglig for Internet Explorer versjon 5.5 eller bedre. Vil du fortsette uten å rense? (Du kan lime inn som ren tekst)",
+UnknownToolbarItem : "Ukjent menyvalg \"%1\"",
+UnknownCommand : "Ukjent kommando \"%1\"",
+NotImplemented : "Kommando ikke implimentert",
+UnknownToolbarSet : "Verktøylinjesett \"%1\" finnes ikke",
+NoActiveX : "Din nettlesers sikkerhetsinstillinger kan begrense noen av funksjonene i redigeringsverktøyet. Du må aktivere \"Kjør ActiveX-kontroller og plugin-modeller\". Du kan oppleve feil og advarsler om manglende funksjoner",
+BrowseServerBlocked : "Kunne ikke åpne dialogboksen for filarkiv. Sjekk at popup-blokkering er deaktivert.",
+DialogBlocked : "Kunne ikke åpne dialogboksen. Sjekk at popup-blokkering er deaktivert.",
+VisitLinkBlocked : "Kunne ikke åpne et nytt vindu. Sjekk at popup-blokkering er deaktivert.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Avbryt",
+DlgBtnClose : "Lukk",
+DlgBtnBrowseServer : "Bla igjennom server",
+DlgAdvancedTag : "Avansert",
+DlgOpOther : "<Annet>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Vennligst skriv inn URL-en",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ikke satt>",
+DlgGenId : "Id",
+DlgGenLangDir : "Språkretning",
+DlgGenLangDirLtr : "Venstre til høyre (VTH)",
+DlgGenLangDirRtl : "Høyre til venstre (HTV)",
+DlgGenLangCode : "Språkkode",
+DlgGenAccessKey : "Aksessknapp",
+DlgGenName : "Navn",
+DlgGenTabIndex : "Tab Indeks",
+DlgGenLongDescr : "Utvidet beskrivelse",
+DlgGenClass : "Stilarkklasser",
+DlgGenTitle : "Tittel",
+DlgGenContType : "Type",
+DlgGenLinkCharset : "Lenket språkkart",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Bildeegenskaper",
+DlgImgInfoTab : "Bildeinformasjon",
+DlgImgBtnUpload : "Send det til serveren",
+DlgImgURL : "URL",
+DlgImgUpload : "Last opp",
+DlgImgAlt : "Alternativ tekst",
+DlgImgWidth : "Bredde",
+DlgImgHeight : "Høyde",
+DlgImgLockRatio : "LÃ¥s forhold",
+DlgBtnResetSize : "Tilbakestill størrelse",
+DlgImgBorder : "Ramme",
+DlgImgHSpace : "HMarg",
+DlgImgVSpace : "VMarg",
+DlgImgAlign : "Juster",
+DlgImgAlignLeft : "Venstre",
+DlgImgAlignAbsBottom: "Abs bunn",
+DlgImgAlignAbsMiddle: "Abs midten",
+DlgImgAlignBaseline : "Bunnlinje",
+DlgImgAlignBottom : "Bunn",
+DlgImgAlignMiddle : "Midten",
+DlgImgAlignRight : "Høyre",
+DlgImgAlignTextTop : "Tekst topp",
+DlgImgAlignTop : "Topp",
+DlgImgPreview : "Forhåndsvis",
+DlgImgAlertUrl : "Vennligst skriv bilde-urlen",
+DlgImgLinkTab : "Lenke",
+
+// Flash Dialog
+DlgFlashTitle : "Flash-egenskaper",
+DlgFlashChkPlay : "Autospill",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Slå på Flash-meny",
+DlgFlashScale : "Skaler",
+DlgFlashScaleAll : "Vis alt",
+DlgFlashScaleNoBorder : "Ingen ramme",
+DlgFlashScaleFit : "Skaler til å passe",
+
+// Link Dialog
+DlgLnkWindowTitle : "Lenke",
+DlgLnkInfoTab : "Lenkeinfo",
+DlgLnkTargetTab : "MÃ¥l",
+
+DlgLnkType : "Lenketype",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Lenke til anker i teksten",
+DlgLnkTypeEMail : "E-post",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<annet>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Velg et anker",
+DlgLnkAnchorByName : "Anker etter navn",
+DlgLnkAnchorById : "Element etter ID",
+DlgLnkNoAnchors : "(Ingen anker i dokumentet)",
+DlgLnkEMail : "E-postadresse",
+DlgLnkEMailSubject : "Meldingsemne",
+DlgLnkEMailBody : "Melding",
+DlgLnkUpload : "Last opp",
+DlgLnkBtnUpload : "Send til server",
+
+DlgLnkTarget : "MÃ¥l",
+DlgLnkTargetFrame : "<ramme>",
+DlgLnkTargetPopup : "<popup vindu>",
+DlgLnkTargetBlank : "Nytt vindu (_blank)",
+DlgLnkTargetParent : "Foreldrevindu (_parent)",
+DlgLnkTargetSelf : "Samme vindu (_self)",
+DlgLnkTargetTop : "Hele vindu (_top)",
+DlgLnkTargetFrameName : "MÃ¥lramme",
+DlgLnkPopWinName : "Navn på popup-vindus",
+DlgLnkPopWinFeat : "Egenskaper for popup-vindu",
+DlgLnkPopResize : "Endre størrelse",
+DlgLnkPopLocation : "Adresselinje",
+DlgLnkPopMenu : "Menylinje",
+DlgLnkPopScroll : "Scrollbar",
+DlgLnkPopStatus : "Statuslinje",
+DlgLnkPopToolbar : "Verktøylinje",
+DlgLnkPopFullScrn : "Full skjerm (IE)",
+DlgLnkPopDependent : "Avhenging (Netscape)",
+DlgLnkPopWidth : "Bredde",
+DlgLnkPopHeight : "Høyde",
+DlgLnkPopLeft : "Venstre posisjon",
+DlgLnkPopTop : "Topp-posisjon",
+
+DlnLnkMsgNoUrl : "Vennligst skriv inn lenkens url",
+DlnLnkMsgNoEMail : "Vennligst skriv inn e-postadressen",
+DlnLnkMsgNoAnchor : "Vennligst velg et anker",
+DlnLnkMsgInvPopName : "Popup-vinduets navn må begynne med en bokstav, og kan ikke inneholde mellomrom",
+
+// Color Dialog
+DlgColorTitle : "Velg farge",
+DlgColorBtnClear : "Tøm",
+DlgColorHighlight : "Marker",
+DlgColorSelected : "Valgt",
+
+// Smiley Dialog
+DlgSmileyTitle : "Sett inn smil",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Velg spesielt tegn",
+
+// Table Dialog
+DlgTableTitle : "Egenskaper for tabell",
+DlgTableRows : "Rader",
+DlgTableColumns : "Kolonner",
+DlgTableBorder : "Rammestørrelse",
+DlgTableAlign : "Justering",
+DlgTableAlignNotSet : "<Ikke satt>",
+DlgTableAlignLeft : "Venstre",
+DlgTableAlignCenter : "Midtjuster",
+DlgTableAlignRight : "Høyre",
+DlgTableWidth : "Bredde",
+DlgTableWidthPx : "piksler",
+DlgTableWidthPc : "prosent",
+DlgTableHeight : "Høyde",
+DlgTableCellSpace : "Cellemarg",
+DlgTableCellPad : "Cellepolstring",
+DlgTableCaption : "Tittel",
+DlgTableSummary : "Sammendrag",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Celleegenskaper",
+DlgCellWidth : "Bredde",
+DlgCellWidthPx : "piksler",
+DlgCellWidthPc : "prosent",
+DlgCellHeight : "Høyde",
+DlgCellWordWrap : "Tekstbrytning",
+DlgCellWordWrapNotSet : "<Ikke satt>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nei",
+DlgCellHorAlign : "Horisontal justering",
+DlgCellHorAlignNotSet : "<Ikke satt>",
+DlgCellHorAlignLeft : "Venstre",
+DlgCellHorAlignCenter : "Midtjuster",
+DlgCellHorAlignRight: "Høyre",
+DlgCellVerAlign : "Vertikal justering",
+DlgCellVerAlignNotSet : "<Ikke satt>",
+DlgCellVerAlignTop : "Topp",
+DlgCellVerAlignMiddle : "Midten",
+DlgCellVerAlignBottom : "Bunn",
+DlgCellVerAlignBaseline : "Bunnlinje",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Radspenn",
+DlgCellCollSpan : "Kolonnespenn",
+DlgCellBackColor : "Bakgrunnsfarge",
+DlgCellBorderColor : "Rammefarge",
+DlgCellBtnSelect : "Velg...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Søk og erstatt",
+
+// Find Dialog
+DlgFindTitle : "Søk",
+DlgFindFindBtn : "Søk",
+DlgFindNotFoundMsg : "Fant ikke søketeksten.",
+
+// Replace Dialog
+DlgReplaceTitle : "Erstatt",
+DlgReplaceFindLbl : "Søk etter:",
+DlgReplaceReplaceLbl : "Erstatt med:",
+DlgReplaceCaseChk : "Skill mellom store og små bokstaver",
+DlgReplaceReplaceBtn : "Erstatt",
+DlgReplaceReplAllBtn : "Erstatt alle",
+DlgReplaceWordChk : "Bare hele ord",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk klipping av tekst. Vennligst bruk snareveien (Ctrl+X).",
+PasteErrorCopy : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst bruk snareveien (Ctrl+C).",
+
+PasteAsText : "Lim inn som ren tekst",
+PasteFromWord : "Lim inn fra Word",
+
+DlgPasteMsg2 : "Vennligst lim inn i den følgende boksen med tastaturet (<STRONG>Ctrl+V</STRONG>) og trykk <STRONG>OK</STRONG>.",
+DlgPasteSec : "Din nettlesers sikkerhetsinstillinger gir ikke redigeringsverktøyet direkte tilgang til utklippstavlen. Du må lime det igjen i dette vinduet.",
+DlgPasteIgnoreFont : "Fjern skrifttyper",
+DlgPasteRemoveStyles : "Fjern stildefinisjoner",
+
+// Color Picker
+ColorAutomatic : "Automatisk",
+ColorMoreColors : "Flere farger...",
+
+// Document Properties
+DocProps : "Dokumentegenskaper",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankeregenskaper",
+DlgAnchorName : "Ankernavn",
+DlgAnchorErrorName : "Vennligst skriv inn ankernavnet",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ikke i ordboken",
+DlgSpellChangeTo : "Endre til",
+DlgSpellBtnIgnore : "Ignorer",
+DlgSpellBtnIgnoreAll : "Ignorer alle",
+DlgSpellBtnReplace : "Erstatt",
+DlgSpellBtnReplaceAll : "Erstatt alle",
+DlgSpellBtnUndo : "Angre",
+DlgSpellNoSuggestions : "- Ingen forslag -",
+DlgSpellProgress : "Stavekontroll pågår...",
+DlgSpellNoMispell : "Stavekontroll fullført: ingen feilstavinger funnet",
+DlgSpellNoChanges : "Stavekontroll fullført: ingen ord endret",
+DlgSpellOneChange : "Stavekontroll fullført: Ett ord endret",
+DlgSpellManyChanges : "Stavekontroll fullført: %1 ord endret",
+
+IeSpellDownload : "Stavekontroll er ikke installert. Vil du laste den ned nå?",
+
+// Button Dialog
+DlgButtonText : "Tekst (verdi)",
+DlgButtonType : "Type",
+DlgButtonTypeBtn : "Knapp",
+DlgButtonTypeSbm : "Send",
+DlgButtonTypeRst : "Nullstill",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Navn",
+DlgCheckboxValue : "Verdi",
+DlgCheckboxSelected : "Valgt",
+
+// Form Dialog
+DlgFormName : "Navn",
+DlgFormAction : "Handling",
+DlgFormMethod : "Metode",
+
+// Select Field Dialog
+DlgSelectName : "Navn",
+DlgSelectValue : "Verdi",
+DlgSelectSize : "Størrelse",
+DlgSelectLines : "Linjer",
+DlgSelectChkMulti : "Tillat flervalg",
+DlgSelectOpAvail : "Tilgjenglige alternativer",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Verdi",
+DlgSelectBtnAdd : "Legg til",
+DlgSelectBtnModify : "Endre",
+DlgSelectBtnUp : "Opp",
+DlgSelectBtnDown : "Ned",
+DlgSelectBtnSetValue : "Sett som valgt",
+DlgSelectBtnDelete : "Slett",
+
+// Textarea Dialog
+DlgTextareaName : "Navn",
+DlgTextareaCols : "Kolonner",
+DlgTextareaRows : "Rader",
+
+// Text Field Dialog
+DlgTextName : "Navn",
+DlgTextValue : "Verdi",
+DlgTextCharWidth : "Tegnbredde",
+DlgTextMaxChars : "Maks antall tegn",
+DlgTextType : "Type",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Passord",
+
+// Hidden Field Dialog
+DlgHiddenName : "Navn",
+DlgHiddenValue : "Verdi",
+
+// Bulleted List Dialog
+BulletedListProp : "Egenskaper for uordnet liste",
+NumberedListProp : "Egenskaper for ordnet liste",
+DlgLstStart : "Start",
+DlgLstType : "Type",
+DlgLstTypeCircle : "Sirkel",
+DlgLstTypeDisc : "Hel sirkel",
+DlgLstTypeSquare : "Firkant",
+DlgLstTypeNumbers : "Numre (1, 2, 3)",
+DlgLstTypeLCase : "Små bokstaver (a, b, c)",
+DlgLstTypeUCase : "Store bokstaver (A, B, C)",
+DlgLstTypeSRoman : "Små romerske tall (i, ii, iii)",
+DlgLstTypeLRoman : "Store romerske tall (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Generelt",
+DlgDocBackTab : "Bakgrunn",
+DlgDocColorsTab : "Farger og marginer",
+DlgDocMetaTab : "Meta-data",
+
+DlgDocPageTitle : "Sidetittel",
+DlgDocLangDir : "Språkretning",
+DlgDocLangDirLTR : "Venstre til høyre (LTR)",
+DlgDocLangDirRTL : "Høyre til venstre (RTL)",
+DlgDocLangCode : "Språkkode",
+DlgDocCharSet : "Tegnsett",
+DlgDocCharSetCE : "Sentraleuropeisk",
+DlgDocCharSetCT : "Tradisonell kinesisk(Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Gresk",
+DlgDocCharSetJP : "Japansk",
+DlgDocCharSetKR : "Koreansk",
+DlgDocCharSetTR : "Tyrkisk",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Vesteuropeisk",
+DlgDocCharSetOther : "Annet tegnsett",
+
+DlgDocDocType : "Dokumenttype header",
+DlgDocDocTypeOther : "Annet dokumenttype header",
+DlgDocIncXHTML : "Inkluder XHTML-deklarasjon",
+DlgDocBgColor : "Bakgrunnsfarge",
+DlgDocBgImage : "URL for bakgrunnsbilde",
+DlgDocBgNoScroll : "LÃ¥s bakgrunnsbilde",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Besøkt lenke",
+DlgDocCActive : "Aktiv lenke",
+DlgDocMargins : "Sidemargin",
+DlgDocMaTop : "Topp",
+DlgDocMaLeft : "Venstre",
+DlgDocMaRight : "Høyre",
+DlgDocMaBottom : "Bunn",
+DlgDocMeIndex : "Dokument nøkkelord (kommaseparert)",
+DlgDocMeDescr : "Dokumentbeskrivelse",
+DlgDocMeAuthor : "Forfatter",
+DlgDocMeCopy : "Kopirett",
+DlgDocPreview : "Forhåndsvising",
+
+// Templates Dialog
+Templates : "Maler",
+DlgTemplatesTitle : "Innholdsmaler",
+DlgTemplatesSelMsg : "Velg malen du vil åpne<br>(innholdet du har skrevet blir tapt!):",
+DlgTemplatesLoading : "Laster malliste. Vennligst vent...",
+DlgTemplatesNoTpl : "(Ingen maler definert)",
+DlgTemplatesReplace : "Erstatt faktisk innold",
+
+// About Dialog
+DlgAboutAboutTab : "Om",
+DlgAboutBrowserInfoTab : "Nettleserinfo",
+DlgAboutLicenseTab : "Lisens",
+DlgAboutVersion : "versjon",
+DlgAboutInfo : "For mer informasjon, se",
+
+// Div Dialog
+DlgDivGeneralTab : "Generelt",
+DlgDivAdvancedTab : "Avansert",
+DlgDivStyle : "Stil",
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/pl.js b/httemplate/elements/fckeditor/editor/lang/pl.js
new file mode 100644
index 0000000..5708832
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/pl.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Polish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Zwiń pasek narzędzi",
+ToolbarExpand : "Rozwiń pasek narzędzi",
+
+// Toolbar Items and Context Menu
+Save : "Zapisz",
+NewPage : "Nowa strona",
+Preview : "PodglÄ…d",
+Cut : "Wytnij",
+Copy : "Kopiuj",
+Paste : "Wklej",
+PasteText : "Wklej jako czysty tekst",
+PasteWord : "Wklej z Worda",
+Print : "Drukuj",
+SelectAll : "Zaznacz wszystko",
+RemoveFormat : "Usuń formatowanie",
+InsertLinkLbl : "Hiperłącze",
+InsertLink : "Wstaw/edytuj hiperłącze",
+RemoveLink : "Usuń hiperłącze",
+VisitLink : "Open Link", //MISSING
+Anchor : "Wstaw/edytuj kotwicÄ™",
+AnchorDelete : "Usuń kotwicę",
+InsertImageLbl : "Obrazek",
+InsertImage : "Wstaw/edytuj obrazek",
+InsertFlashLbl : "Flash",
+InsertFlash : "Dodaj/Edytuj element Flash",
+InsertTableLbl : "Tabela",
+InsertTable : "Wstaw/edytuj tabelÄ™",
+InsertLineLbl : "Linia pozioma",
+InsertLine : "Wstaw poziomÄ… liniÄ™",
+InsertSpecialCharLbl: "Znak specjalny",
+InsertSpecialChar : "Wstaw znak specjalny",
+InsertSmileyLbl : "Emotikona",
+InsertSmiley : "Wstaw emotikonÄ™",
+About : "O programie FCKeditor",
+Bold : "Pogrubienie",
+Italic : "Kursywa",
+Underline : "Podkreślenie",
+StrikeThrough : "Przekreślenie",
+Subscript : "Indeks dolny",
+Superscript : "Indeks górny",
+LeftJustify : "Wyrównaj do lewej",
+CenterJustify : "Wyrównaj do środka",
+RightJustify : "Wyrównaj do prawej",
+BlockJustify : "Wyrównaj do lewej i prawej",
+DecreaseIndent : "Zmniejsz wcięcie",
+IncreaseIndent : "Zwiększ wcięcie",
+Blockquote : "Cytat",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Cofnij",
+Redo : "Ponów",
+NumberedListLbl : "Lista numerowana",
+NumberedList : "Wstaw/usuń numerowanie listy",
+BulletedListLbl : "Lista wypunktowana",
+BulletedList : "Wstaw/usuń wypunktowanie listy",
+ShowTableBorders : "Pokazuj ramkÄ™ tabeli",
+ShowDetails : "Pokaż szczegóły",
+Style : "Styl",
+FontFormat : "Format",
+Font : "Czcionka",
+FontSize : "Rozmiar",
+TextColor : "Kolor tekstu",
+BGColor : "Kolor tła",
+Source : "Źródło dokumentu",
+Find : "Znajdź",
+Replace : "Zamień",
+SpellCheck : "Sprawdź pisownię",
+UniversalKeyboard : "Klawiatura Uniwersalna",
+PageBreakLbl : "Odstęp",
+PageBreak : "Wstaw odstęp",
+
+Form : "Formularz",
+Checkbox : "Pole wyboru (checkbox)",
+RadioButton : "Pole wyboru (radio)",
+TextField : "Pole tekstowe",
+Textarea : "Obszar tekstowy",
+HiddenField : "Pole ukryte",
+Button : "Przycisk",
+SelectionField : "Lista wyboru",
+ImageButton : "Przycisk-obrazek",
+
+FitWindow : "Maksymalizuj rozmiar edytora",
+ShowBlocks : "Pokaż bloki",
+
+// Context Menu
+EditLink : "Edytuj hiperłącze",
+CellCM : "Komórka",
+RowCM : "Wiersz",
+ColumnCM : "Kolumna",
+InsertRowAfter : "Wstaw wiersz poniżej",
+InsertRowBefore : "Wstaw wiersz powyżej",
+DeleteRows : "Usuń wiersze",
+InsertColumnAfter : "Wstaw kolumnÄ™ z prawej",
+InsertColumnBefore : "Wstaw kolumnÄ™ z lewej",
+DeleteColumns : "Usuń kolumny",
+InsertCellAfter : "Wstaw komórkę z prawej",
+InsertCellBefore : "Wstaw komórkę z lewej",
+DeleteCells : "Usuń komórki",
+MergeCells : "Połącz komórki",
+MergeRight : "Połącz z komórką z prawej",
+MergeDown : "Połącz z komórką poniżej",
+HorizontalSplitCell : "Podziel komórkę poziomo",
+VerticalSplitCell : "Podziel komórkę pionowo",
+TableDelete : "Usuń tabelę",
+CellProperties : "Właściwości komórki",
+TableProperties : "Właściwości tabeli",
+ImageProperties : "Właściwości obrazka",
+FlashProperties : "Właściwości elementu Flash",
+
+AnchorProp : "Właściwości kotwicy",
+ButtonProp : "Właściwości przycisku",
+CheckboxProp : "Właściwości pola wyboru (checkbox)",
+HiddenFieldProp : "Właściwości pola ukrytego",
+RadioButtonProp : "Właściwości pola wyboru (radio)",
+ImageButtonProp : "Właściwości przycisku obrazka",
+TextFieldProp : "Właściwości pola tekstowego",
+SelectionFieldProp : "Właściwości listy wyboru",
+TextareaProp : "Właściwości obszaru tekstowego",
+FormProp : "Właściwości formularza",
+
+FontFormats : "Normalny;Tekst sformatowany;Adres;Nagłówek 1;Nagłówek 2;Nagłówek 3;Nagłówek 4;Nagłówek 5;Nagłówek 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Przetwarzanie XHTML. Proszę czekać...",
+Done : "Gotowe",
+PasteWordConfirm : "Tekst, który chcesz wkleić, prawdopodobnie pochodzi z programu Word. Czy chcesz go wyczyścic przed wklejeniem?",
+NotCompatiblePaste : "Ta funkcja jest dostępna w programie Internet Explorer w wersji 5.5 lub wyższej. Czy chcesz wkleić tekst bez czyszczenia?",
+UnknownToolbarItem : "Nieznany element paska narzędzi \"%1\"",
+UnknownCommand : "Nieznana komenda \"%1\"",
+NotImplemented : "Komenda niezaimplementowana",
+UnknownToolbarSet : "Pasek narzędzi \"%1\" nie istnieje",
+NoActiveX : "Ustawienia zabezpieczeń twojej przeglądarki mogą ograniczyć niektóre funkcje edytora. Musisz włączyć opcję \"Uruchamianie formantów Activex i dodatków plugin\". W przeciwnym wypadku mogą pojawiać się błędy.",
+BrowseServerBlocked : "Nie można otworzyć okno menadżera plików. Upewnij się, że wszystkie blokady wyskakujących okienek są wyłączone.",
+DialogBlocked : "Nie można otworzyć okna dialogowego. Upewnij się, że wszystkie blokady wyskakujących okienek są wyłączone.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Anuluj",
+DlgBtnClose : "Zamknij",
+DlgBtnBrowseServer : "PrzeglÄ…daj",
+DlgAdvancedTag : "Zaawansowane",
+DlgOpOther : "<Inny>",
+DlgInfoTab : "Informacje",
+DlgAlertUrl : "Proszę podać URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nie ustawione>",
+DlgGenId : "Id",
+DlgGenLangDir : "Kierunek tekstu",
+DlgGenLangDirLtr : "Od lewej do prawej (LTR)",
+DlgGenLangDirRtl : "Od prawej do lewej (RTL)",
+DlgGenLangCode : "Kod języka",
+DlgGenAccessKey : "Klawisz dostępu",
+DlgGenName : "Nazwa",
+DlgGenTabIndex : "Indeks tabeli",
+DlgGenLongDescr : "Długi opis hiperłącza",
+DlgGenClass : "Nazwa klasy CSS",
+DlgGenTitle : "Opis obiektu docelowego",
+DlgGenContType : "Typ MIME obiektu docelowego",
+DlgGenLinkCharset : "Kodowanie znaków obiektu docelowego",
+DlgGenStyle : "Styl",
+
+// Image Dialog
+DlgImgTitle : "Właściwości obrazka",
+DlgImgInfoTab : "Informacje o obrazku",
+DlgImgBtnUpload : "Wyślij",
+DlgImgURL : "Adres URL",
+DlgImgUpload : "Wyślij",
+DlgImgAlt : "Tekst zastępczy",
+DlgImgWidth : "Szerokość",
+DlgImgHeight : "Wysokość",
+DlgImgLockRatio : "Zablokuj proporcje",
+DlgBtnResetSize : "Przywróć rozmiar",
+DlgImgBorder : "Ramka",
+DlgImgHSpace : "Odstęp poziomy",
+DlgImgVSpace : "Odstęp pionowy",
+DlgImgAlign : "Wyrównaj",
+DlgImgAlignLeft : "Do lewej",
+DlgImgAlignAbsBottom: "Do dołu",
+DlgImgAlignAbsMiddle: "Do środka w pionie",
+DlgImgAlignBaseline : "Do linii bazowej",
+DlgImgAlignBottom : "Do dołu",
+DlgImgAlignMiddle : "Do środka",
+DlgImgAlignRight : "Do prawej",
+DlgImgAlignTextTop : "Do góry tekstu",
+DlgImgAlignTop : "Do góry",
+DlgImgPreview : "PodglÄ…d",
+DlgImgAlertUrl : "Podaj adres obrazka.",
+DlgImgLinkTab : "Hiperłącze",
+
+// Flash Dialog
+DlgFlashTitle : "Właściwości elementu Flash",
+DlgFlashChkPlay : "Auto Odtwarzanie",
+DlgFlashChkLoop : "Pętla",
+DlgFlashChkMenu : "WÅ‚Ä…cz menu",
+DlgFlashScale : "Skaluj",
+DlgFlashScaleAll : "Pokaż wszystko",
+DlgFlashScaleNoBorder : "Bez Ramki",
+DlgFlashScaleFit : "Dokładne dopasowanie",
+
+// Link Dialog
+DlgLnkWindowTitle : "Hiperłącze",
+DlgLnkInfoTab : "Informacje ",
+DlgLnkTargetTab : "Cel",
+
+DlgLnkType : "Typ hiperłącza",
+DlgLnkTypeURL : "Adres URL",
+DlgLnkTypeAnchor : "Odnośnik wewnątrz strony",
+DlgLnkTypeEMail : "Adres e-mail",
+DlgLnkProto : "Protokół",
+DlgLnkProtoOther : "<inny>",
+DlgLnkURL : "Adres URL",
+DlgLnkAnchorSel : "Wybierz etykietÄ™",
+DlgLnkAnchorByName : "Wg etykiety",
+DlgLnkAnchorById : "Wg identyfikatora elementu",
+DlgLnkNoAnchors : "(W dokumencie nie zdefiniowano żadnych etykiet)",
+DlgLnkEMail : "Adres e-mail",
+DlgLnkEMailSubject : "Temat",
+DlgLnkEMailBody : "Treść",
+DlgLnkUpload : "Wyślij",
+DlgLnkBtnUpload : "Wyślij",
+
+DlgLnkTarget : "Cel",
+DlgLnkTargetFrame : "<ramka>",
+DlgLnkTargetPopup : "<wyskakujÄ…ce okno>",
+DlgLnkTargetBlank : "Nowe okno (_blank)",
+DlgLnkTargetParent : "Okno nadrzędne (_parent)",
+DlgLnkTargetSelf : "To samo okno (_self)",
+DlgLnkTargetTop : "Okno najwyższe w hierarchii (_top)",
+DlgLnkTargetFrameName : "Nazwa Ramki Docelowej",
+DlgLnkPopWinName : "Nazwa wyskakujÄ…cego okna",
+DlgLnkPopWinFeat : "Właściwości wyskakującego okna",
+DlgLnkPopResize : "Możliwa zmiana rozmiaru",
+DlgLnkPopLocation : "Pasek adresu",
+DlgLnkPopMenu : "Pasek menu",
+DlgLnkPopScroll : "Paski przewijania",
+DlgLnkPopStatus : "Pasek statusu",
+DlgLnkPopToolbar : "Pasek narzędzi",
+DlgLnkPopFullScrn : "Pełny ekran (IE)",
+DlgLnkPopDependent : "Okno zależne (Netscape)",
+DlgLnkPopWidth : "Szerokość",
+DlgLnkPopHeight : "Wysokość",
+DlgLnkPopLeft : "Pozycja w poziomie",
+DlgLnkPopTop : "Pozycja w pionie",
+
+DlnLnkMsgNoUrl : "Podaj adres URL",
+DlnLnkMsgNoEMail : "Podaj adres e-mail",
+DlnLnkMsgNoAnchor : "Wybierz etykietÄ™",
+DlnLnkMsgInvPopName : "Nazwa wyskakującego okienka musi zaczynać się od znaku alfanumerycznego i nie może zawierać spacji",
+
+// Color Dialog
+DlgColorTitle : "Wybierz kolor",
+DlgColorBtnClear : "Wyczyść",
+DlgColorHighlight : "PodglÄ…d",
+DlgColorSelected : "Wybrane",
+
+// Smiley Dialog
+DlgSmileyTitle : "Wstaw emotikonÄ™",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Wybierz znak specjalny",
+
+// Table Dialog
+DlgTableTitle : "Właściwości tabeli",
+DlgTableRows : "Liczba wierszy",
+DlgTableColumns : "Liczba kolumn",
+DlgTableBorder : "Grubość ramki",
+DlgTableAlign : "Wyrównanie",
+DlgTableAlignNotSet : "<brak ustawień>",
+DlgTableAlignLeft : "Do lewej",
+DlgTableAlignCenter : "Do środka",
+DlgTableAlignRight : "Do prawej",
+DlgTableWidth : "Szerokość",
+DlgTableWidthPx : "piksele",
+DlgTableWidthPc : "%",
+DlgTableHeight : "Wysokość",
+DlgTableCellSpace : "Odstęp pomiędzy komórkami",
+DlgTableCellPad : "Margines wewnętrzny komórek",
+DlgTableCaption : "Tytuł",
+DlgTableSummary : "Podsumowanie",
+DlgTableHeaders : "Nagłówki",
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Właściwości komórki",
+DlgCellWidth : "Szerokość",
+DlgCellWidthPx : "piksele",
+DlgCellWidthPc : "%",
+DlgCellHeight : "Wysokość",
+DlgCellWordWrap : "Zawijanie tekstu",
+DlgCellWordWrapNotSet : "<brak ustawień>",
+DlgCellWordWrapYes : "Tak",
+DlgCellWordWrapNo : "Nie",
+DlgCellHorAlign : "Wyrównanie poziome",
+DlgCellHorAlignNotSet : "<brak ustawień>",
+DlgCellHorAlignLeft : "Do lewej",
+DlgCellHorAlignCenter : "Do środka",
+DlgCellHorAlignRight: "Do prawej",
+DlgCellVerAlign : "Wyrównanie pionowe",
+DlgCellVerAlignNotSet : "<brak ustawień>",
+DlgCellVerAlignTop : "Do góry",
+DlgCellVerAlignMiddle : "Do środka",
+DlgCellVerAlignBottom : "Do dołu",
+DlgCellVerAlignBaseline : "Do linii bazowej",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Zajętość wierszy",
+DlgCellCollSpan : "Zajętość kolumn",
+DlgCellBackColor : "Kolor tła",
+DlgCellBorderColor : "Kolor ramki",
+DlgCellBtnSelect : "Wybierz...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Znajdź i zamień",
+
+// Find Dialog
+DlgFindTitle : "Znajdź",
+DlgFindFindBtn : "Znajdź",
+DlgFindNotFoundMsg : "Nie znaleziono szukanego hasła.",
+
+// Replace Dialog
+DlgReplaceTitle : "Zamień",
+DlgReplaceFindLbl : "Znajdź:",
+DlgReplaceReplaceLbl : "ZastÄ…p przez:",
+DlgReplaceCaseChk : "Uwzględnij wielkość liter",
+DlgReplaceReplaceBtn : "ZastÄ…p",
+DlgReplaceReplAllBtn : "ZastÄ…p wszystko",
+DlgReplaceWordChk : "Całe słowa",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne wycinanie tekstu. Użyj skrótu klawiszowego Ctrl+X.",
+PasteErrorCopy : "Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne kopiowanie tekstu. Użyj skrótu klawiszowego Ctrl+C.",
+
+PasteAsText : "Wklej jako czysty tekst",
+PasteFromWord : "Wklej z Worda",
+
+DlgPasteMsg2 : "Proszę wkleić w poniższym polu używając klawiaturowego skrótu (<STRONG>Ctrl+V</STRONG>) i kliknąć <STRONG>OK</STRONG>.",
+DlgPasteSec : "Zabezpieczenia przeglądarki uniemożliwiają wklejenie danych bezpośrednio do edytora. Proszę dane wkleić ponownie w tym okienku.",
+DlgPasteIgnoreFont : "Ignoruj definicje 'Font Face'",
+DlgPasteRemoveStyles : "Usuń definicje Stylów",
+
+// Color Picker
+ColorAutomatic : "Automatycznie",
+ColorMoreColors : "Więcej kolorów...",
+
+// Document Properties
+DocProps : "Właściwości dokumentu",
+
+// Anchor Dialog
+DlgAnchorTitle : "Właściwości kotwicy",
+DlgAnchorName : "Nazwa kotwicy",
+DlgAnchorErrorName : "Wpisz nazwÄ™ kotwicy",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Słowa nie ma w słowniku",
+DlgSpellChangeTo : "Zmień na",
+DlgSpellBtnIgnore : "Ignoruj",
+DlgSpellBtnIgnoreAll : "Ignoruj wszystkie",
+DlgSpellBtnReplace : "Zmień",
+DlgSpellBtnReplaceAll : "Zmień wszystkie",
+DlgSpellBtnUndo : "Cofnij",
+DlgSpellNoSuggestions : "- Brak sugestii -",
+DlgSpellProgress : "Trwa sprawdzanie ...",
+DlgSpellNoMispell : "Sprawdzanie zakończone: nie znaleziono błędów",
+DlgSpellNoChanges : "Sprawdzanie zakończone: nie zmieniono żadnego słowa",
+DlgSpellOneChange : "Sprawdzanie zakończone: zmieniono jedno słowo",
+DlgSpellManyChanges : "Sprawdzanie zakończone: zmieniono %l słów",
+
+IeSpellDownload : "Słownik nie jest zainstalowany. Chcesz go ściągnąć?",
+
+// Button Dialog
+DlgButtonText : "Tekst (Wartość)",
+DlgButtonType : "Typ",
+DlgButtonTypeBtn : "Przycisk",
+DlgButtonTypeSbm : "Wyślij",
+DlgButtonTypeRst : "Wyzeruj",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nazwa",
+DlgCheckboxValue : "Wartość",
+DlgCheckboxSelected : "Zaznaczone",
+
+// Form Dialog
+DlgFormName : "Nazwa",
+DlgFormAction : "Akcja",
+DlgFormMethod : "Metoda",
+
+// Select Field Dialog
+DlgSelectName : "Nazwa",
+DlgSelectValue : "Wartość",
+DlgSelectSize : "Rozmiar",
+DlgSelectLines : "linii",
+DlgSelectChkMulti : "Wielokrotny wybór",
+DlgSelectOpAvail : "Dostępne opcje",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Wartość",
+DlgSelectBtnAdd : "Dodaj",
+DlgSelectBtnModify : "Zmień",
+DlgSelectBtnUp : "Do góry",
+DlgSelectBtnDown : "Do dołu",
+DlgSelectBtnSetValue : "Ustaw wartość zaznaczoną",
+DlgSelectBtnDelete : "Usuń",
+
+// Textarea Dialog
+DlgTextareaName : "Nazwa",
+DlgTextareaCols : "Kolumnu",
+DlgTextareaRows : "Wiersze",
+
+// Text Field Dialog
+DlgTextName : "Nazwa",
+DlgTextValue : "Wartość",
+DlgTextCharWidth : "Szerokość w znakach",
+DlgTextMaxChars : "Max. szerokość",
+DlgTextType : "Typ",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Hasło",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nazwa",
+DlgHiddenValue : "Wartość",
+
+// Bulleted List Dialog
+BulletedListProp : "Właściwości listy punktowanej",
+NumberedListProp : "Właściwości listy numerowanej",
+DlgLstStart : "PoczÄ…tek",
+DlgLstType : "Typ",
+DlgLstTypeCircle : "Koło",
+DlgLstTypeDisc : "Dysk",
+DlgLstTypeSquare : "Kwadrat",
+DlgLstTypeNumbers : "Cyfry (1, 2, 3)",
+DlgLstTypeLCase : "Małe litery (a, b, c)",
+DlgLstTypeUCase : "Duże litery (A, B, C)",
+DlgLstTypeSRoman : "Numeracja rzymska (i, ii, iii)",
+DlgLstTypeLRoman : "Numeracja rzymska (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Ogólne",
+DlgDocBackTab : "TÅ‚o",
+DlgDocColorsTab : "Kolory i marginesy",
+DlgDocMetaTab : "Meta Dane",
+
+DlgDocPageTitle : "Tytuł strony",
+DlgDocLangDir : "Kierunek pisania",
+DlgDocLangDirLTR : "Od lewej do prawej (LTR)",
+DlgDocLangDirRTL : "Od prawej do lewej (RTL)",
+DlgDocLangCode : "Kod języka",
+DlgDocCharSet : "Kodowanie znaków",
+DlgDocCharSetCE : "Åšrodkowoeuropejskie",
+DlgDocCharSetCT : "Chińskie tradycyjne (Big5)",
+DlgDocCharSetCR : "Cyrylica",
+DlgDocCharSetGR : "Greckie",
+DlgDocCharSetJP : "Japońskie",
+DlgDocCharSetKR : "Koreańskie",
+DlgDocCharSetTR : "Tureckie",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Zachodnioeuropejskie",
+DlgDocCharSetOther : "Inne kodowanie znaków",
+
+DlgDocDocType : "Nagłówek typu dokumentu",
+DlgDocDocTypeOther : "Inny typ dokumentu",
+DlgDocIncXHTML : "Dołącz deklarację XHTML",
+DlgDocBgColor : "Kolor tła",
+DlgDocBgImage : "Obrazek tła",
+DlgDocBgNoScroll : "TÅ‚o nieruchome",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Hiperłącze",
+DlgDocCVisited : "Odwiedzane hiperłącze",
+DlgDocCActive : "Aktywne hiperłącze",
+DlgDocMargins : "Marginesy strony",
+DlgDocMaTop : "Górny",
+DlgDocMaLeft : "Lewy",
+DlgDocMaRight : "Prawy",
+DlgDocMaBottom : "Dolny",
+DlgDocMeIndex : "SÅ‚owa kluczowe (oddzielone przecinkami)",
+DlgDocMeDescr : "Opis dokumentu",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Prawa autorskie",
+DlgDocPreview : "PodglÄ…d",
+
+// Templates Dialog
+Templates : "Szablony",
+DlgTemplatesTitle : "Szablony zawartości",
+DlgTemplatesSelMsg : "Wybierz szablon do otwarcia w edytorze<br>(obecna zawartość okna edytora zostanie utracona):",
+DlgTemplatesLoading : "Åadowanie listy szablonów. ProszÄ™ czekać...",
+DlgTemplatesNoTpl : "(Brak zdefiniowanych szablonów)",
+DlgTemplatesReplace : "Zastąp aktualną zawartość",
+
+// About Dialog
+DlgAboutAboutTab : "O ...",
+DlgAboutBrowserInfoTab : "O przeglÄ…darce",
+DlgAboutLicenseTab : "Licencja",
+DlgAboutVersion : "wersja",
+DlgAboutInfo : "Więcej informacji uzyskasz pod adresem",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/pt-br.js b/httemplate/elements/fckeditor/editor/lang/pt-br.js
new file mode 100644
index 0000000..4ae448f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/pt-br.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Brazilian Portuguese language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Ocultar Barra de Ferramentas",
+ToolbarExpand : "Exibir Barra de Ferramentas",
+
+// Toolbar Items and Context Menu
+Save : "Salvar",
+NewPage : "Novo",
+Preview : "Visualizar",
+Cut : "Recortar",
+Copy : "Copiar",
+Paste : "Colar",
+PasteText : "Colar como Texto sem Formatação",
+PasteWord : "Colar do Word",
+Print : "Imprimir",
+SelectAll : "Selecionar Tudo",
+RemoveFormat : "Remover Formatação",
+InsertLinkLbl : "Hiperlink",
+InsertLink : "Inserir/Editar Hiperlink",
+RemoveLink : "Remover Hiperlink",
+VisitLink : "Open Link", //MISSING
+Anchor : "Inserir/Editar Âncora",
+AnchorDelete : "Remover Âncora",
+InsertImageLbl : "Figura",
+InsertImage : "Inserir/Editar Figura",
+InsertFlashLbl : "Flash",
+InsertFlash : "Insere/Edita Flash",
+InsertTableLbl : "Tabela",
+InsertTable : "Inserir/Editar Tabela",
+InsertLineLbl : "Linha",
+InsertLine : "Inserir Linha Horizontal",
+InsertSpecialCharLbl: "Caracteres Especiais",
+InsertSpecialChar : "Inserir Caractere Especial",
+InsertSmileyLbl : "Emoticon",
+InsertSmiley : "Inserir Emoticon",
+About : "Sobre FCKeditor",
+Bold : "Negrito",
+Italic : "Itálico",
+Underline : "Sublinhado",
+StrikeThrough : "Tachado",
+Subscript : "Subscrito",
+Superscript : "Sobrescrito",
+LeftJustify : "Alinhar Esquerda",
+CenterJustify : "Centralizar",
+RightJustify : "Alinhar Direita",
+BlockJustify : "Justificado",
+DecreaseIndent : "Diminuir Recuo",
+IncreaseIndent : "Aumentar Recuo",
+Blockquote : "Recuo",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Desfazer",
+Redo : "Refazer",
+NumberedListLbl : "Numeração",
+NumberedList : "Inserir/Remover Numeração",
+BulletedListLbl : "Marcadores",
+BulletedList : "Inserir/Remover Marcadores",
+ShowTableBorders : "Exibir Bordas da Tabela",
+ShowDetails : "Exibir Detalhes",
+Style : "Estilo",
+FontFormat : "Formatação",
+Font : "Fonte",
+FontSize : "Tamanho",
+TextColor : "Cor do Texto",
+BGColor : "Cor do Plano de Fundo",
+Source : "Código-Fonte",
+Find : "Localizar",
+Replace : "Substituir",
+SpellCheck : "Verificar Ortografia",
+UniversalKeyboard : "Teclado Universal",
+PageBreakLbl : "Quebra de Página",
+PageBreak : "Inserir Quebra de Página",
+
+Form : "Formulário",
+Checkbox : "Caixa de Seleção",
+RadioButton : "Botão de Opção",
+TextField : "Caixa de Texto",
+Textarea : "Ãrea de Texto",
+HiddenField : "Campo Oculto",
+Button : "Botão",
+SelectionField : "Caixa de Listagem",
+ImageButton : "Botão de Imagem",
+
+FitWindow : "Maximizar o tamanho do editor",
+ShowBlocks : "Mostrar blocos",
+
+// Context Menu
+EditLink : "Editar Hiperlink",
+CellCM : "Célula",
+RowCM : "Linha",
+ColumnCM : "Coluna",
+InsertRowAfter : "Inserir linha abaixo",
+InsertRowBefore : "Inserir linha acima",
+DeleteRows : "Remover Linhas",
+InsertColumnAfter : "Inserir coluna à direita",
+InsertColumnBefore : "Inserir coluna à esquerda",
+DeleteColumns : "Remover Colunas",
+InsertCellAfter : "Inserir célula à direita",
+InsertCellBefore : "Inserir célula à esquerda",
+DeleteCells : "Remover Células",
+MergeCells : "Mesclar Células",
+MergeRight : "Mesclar com célula à direita",
+MergeDown : "Mesclar com célula abaixo",
+HorizontalSplitCell : "Dividir célula horizontalmente",
+VerticalSplitCell : "Dividir célula verticalmente",
+TableDelete : "Apagar Tabela",
+CellProperties : "Formatar Célula",
+TableProperties : "Formatar Tabela",
+ImageProperties : "Formatar Figura",
+FlashProperties : "Propriedades Flash",
+
+AnchorProp : "Formatar Âncora",
+ButtonProp : "Formatar Botão",
+CheckboxProp : "Formatar Caixa de Seleção",
+HiddenFieldProp : "Formatar Campo Oculto",
+RadioButtonProp : "Formatar Botão de Opção",
+ImageButtonProp : "Formatar Botão de Imagem",
+TextFieldProp : "Formatar Caixa de Texto",
+SelectionFieldProp : "Formatar Caixa de Listagem",
+TextareaProp : "Formatar Ãrea de Texto",
+FormProp : "Formatar Formulário",
+
+FontFormats : "Normal;Formatado;Endereço;Título 1;Título 2;Título 3;Título 4;Título 5;Título 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Processando XHTML. Por favor, aguarde...",
+Done : "Pronto",
+PasteWordConfirm : "O texto que você deseja colar parece ter sido copiado do Word. Você gostaria de remover a formatação antes de colar?",
+NotCompatiblePaste : "Este comando está disponível para o navegador Internet Explorer 5.5 ou superior. Você gostaria de colar sem remover a formatação?",
+UnknownToolbarItem : "O item da barra de ferramentas \"%1\" não é reconhecido",
+UnknownCommand : "O comando \"%1\" não é reconhecido",
+NotImplemented : "O comando não foi implementado",
+UnknownToolbarSet : "A barra de ferramentas \"%1\" não existe",
+NoActiveX : "As configurações de segurança do seu browser podem limitar algumas características do editor. Você precisa habilitar a opção \"Executar controles e plug-ins ActiveX\". Você pode experimentar erros e alertas de características faltantes.",
+BrowseServerBlocked : "Os recursos do browser não puderam ser abertos. Tenha certeza que todos os bloqueadores de popup estão desabilitados.",
+DialogBlocked : "Não foi possível abrir a janela de diálogo. Tenha certeza que todos os bloqueadores de popup estão desabilitados.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancelar",
+DlgBtnClose : "Fechar",
+DlgBtnBrowseServer : "Localizar no Servidor",
+DlgAdvancedTag : "Avançado",
+DlgOpOther : "<Outros>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Inserir a URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<não ajustado>",
+DlgGenId : "Id",
+DlgGenLangDir : "Direção do idioma",
+DlgGenLangDirLtr : "Esquerda para Direita (LTR)",
+DlgGenLangDirRtl : "Direita para Esquerda (RTL)",
+DlgGenLangCode : "Idioma",
+DlgGenAccessKey : "Chave de Acesso",
+DlgGenName : "Nome",
+DlgGenTabIndex : "Ãndice de Tabulação",
+DlgGenLongDescr : "Descrição da URL",
+DlgGenClass : "Classe de Folhas de Estilo",
+DlgGenTitle : "Título",
+DlgGenContType : "Tipo de Conteúdo",
+DlgGenLinkCharset : "Conjunto de Caracteres do Hiperlink",
+DlgGenStyle : "Estilos",
+
+// Image Dialog
+DlgImgTitle : "Formatar Figura",
+DlgImgInfoTab : "Informações da Figura",
+DlgImgBtnUpload : "Enviar para o Servidor",
+DlgImgURL : "URL",
+DlgImgUpload : "Submeter",
+DlgImgAlt : "Texto Alternativo",
+DlgImgWidth : "Largura",
+DlgImgHeight : "Altura",
+DlgImgLockRatio : "Manter proporções",
+DlgBtnResetSize : "Redefinir para o Tamanho Original",
+DlgImgBorder : "Borda",
+DlgImgHSpace : "Horizontal",
+DlgImgVSpace : "Vertical",
+DlgImgAlign : "Alinhamento",
+DlgImgAlignLeft : "Esquerda",
+DlgImgAlignAbsBottom: "Inferior Absoluto",
+DlgImgAlignAbsMiddle: "Centralizado Absoluto",
+DlgImgAlignBaseline : "Baseline",
+DlgImgAlignBottom : "Inferior",
+DlgImgAlignMiddle : "Centralizado",
+DlgImgAlignRight : "Direita",
+DlgImgAlignTextTop : "Superior Absoluto",
+DlgImgAlignTop : "Superior",
+DlgImgPreview : "Visualização",
+DlgImgAlertUrl : "Por favor, digite o URL da figura.",
+DlgImgLinkTab : "Hiperlink",
+
+// Flash Dialog
+DlgFlashTitle : "Propriedades Flash",
+DlgFlashChkPlay : "Tocar Automaticamente",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Habilita Menu Flash",
+DlgFlashScale : "Escala",
+DlgFlashScaleAll : "Mostrar tudo",
+DlgFlashScaleNoBorder : "Sem Borda",
+DlgFlashScaleFit : "Escala Exata",
+
+// Link Dialog
+DlgLnkWindowTitle : "Hiperlink",
+DlgLnkInfoTab : "Informações",
+DlgLnkTargetTab : "Destino",
+
+DlgLnkType : "Tipo de hiperlink",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Âncora nesta página",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocolo",
+DlgLnkProtoOther : "<outro>",
+DlgLnkURL : "URL do hiperlink",
+DlgLnkAnchorSel : "Selecione uma âncora",
+DlgLnkAnchorByName : "Pelo Nome da âncora",
+DlgLnkAnchorById : "Pelo Id do Elemento",
+DlgLnkNoAnchors : "(Não há âncoras disponíveis neste documento)",
+DlgLnkEMail : "Endereço E-Mail",
+DlgLnkEMailSubject : "Assunto da Mensagem",
+DlgLnkEMailBody : "Corpo da Mensagem",
+DlgLnkUpload : "Enviar ao Servidor",
+DlgLnkBtnUpload : "Enviar ao Servidor",
+
+DlgLnkTarget : "Destino",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<janela popup>",
+DlgLnkTargetBlank : "Nova Janela (_blank)",
+DlgLnkTargetParent : "Janela Pai (_parent)",
+DlgLnkTargetSelf : "Mesma Janela (_self)",
+DlgLnkTargetTop : "Janela Superior (_top)",
+DlgLnkTargetFrameName : "Nome do Frame de Destino",
+DlgLnkPopWinName : "Nome da Janela Pop-up",
+DlgLnkPopWinFeat : "Atributos da Janela Pop-up",
+DlgLnkPopResize : "Redimensionável",
+DlgLnkPopLocation : "Barra de Endereços",
+DlgLnkPopMenu : "Barra de Menus",
+DlgLnkPopScroll : "Barras de Rolagem",
+DlgLnkPopStatus : "Barra de Status",
+DlgLnkPopToolbar : "Barra de Ferramentas",
+DlgLnkPopFullScrn : "Modo Tela Cheia (IE)",
+DlgLnkPopDependent : "Dependente (Netscape)",
+DlgLnkPopWidth : "Largura",
+DlgLnkPopHeight : "Altura",
+DlgLnkPopLeft : "Esquerda",
+DlgLnkPopTop : "Superior",
+
+DlnLnkMsgNoUrl : "Por favor, digite o endereço do Hiperlink",
+DlnLnkMsgNoEMail : "Por favor, digite o endereço de e-mail",
+DlnLnkMsgNoAnchor : "Por favor, selecione uma âncora",
+DlnLnkMsgInvPopName : "O nome da janela popup deve começar com uma letra ou sublinhado (_) e não pode conter espaços",
+
+// Color Dialog
+DlgColorTitle : "Selecione uma Cor",
+DlgColorBtnClear : "Limpar",
+DlgColorHighlight : "Visualização",
+DlgColorSelected : "Selecionada",
+
+// Smiley Dialog
+DlgSmileyTitle : "Inserir Emoticon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Selecione um Caractere Especial",
+
+// Table Dialog
+DlgTableTitle : "Formatar Tabela",
+DlgTableRows : "Linhas",
+DlgTableColumns : "Colunas",
+DlgTableBorder : "Borda",
+DlgTableAlign : "Alinhamento",
+DlgTableAlignNotSet : "<Não ajustado>",
+DlgTableAlignLeft : "Esquerda",
+DlgTableAlignCenter : "Centralizado",
+DlgTableAlignRight : "Direita",
+DlgTableWidth : "Largura",
+DlgTableWidthPx : "pixels",
+DlgTableWidthPc : "%",
+DlgTableHeight : "Altura",
+DlgTableCellSpace : "Espaçamento",
+DlgTableCellPad : "Enchimento",
+DlgTableCaption : "Legenda",
+DlgTableSummary : "Resumo",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Formatar célula",
+DlgCellWidth : "Largura",
+DlgCellWidthPx : "pixels",
+DlgCellWidthPc : "%",
+DlgCellHeight : "Altura",
+DlgCellWordWrap : "Quebra de Linha",
+DlgCellWordWrapNotSet : "<Não ajustado>",
+DlgCellWordWrapYes : "Sim",
+DlgCellWordWrapNo : "Não",
+DlgCellHorAlign : "Alinhamento Horizontal",
+DlgCellHorAlignNotSet : "<Não ajustado>",
+DlgCellHorAlignLeft : "Esquerda",
+DlgCellHorAlignCenter : "Centralizado",
+DlgCellHorAlignRight: "Direita",
+DlgCellVerAlign : "Alinhamento Vertical",
+DlgCellVerAlignNotSet : "<Não ajustado>",
+DlgCellVerAlignTop : "Superior",
+DlgCellVerAlignMiddle : "Centralizado",
+DlgCellVerAlignBottom : "Inferior",
+DlgCellVerAlignBaseline : "Baseline",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Transpor Linhas",
+DlgCellCollSpan : "Transpor Colunas",
+DlgCellBackColor : "Cor do Plano de Fundo",
+DlgCellBorderColor : "Cor da Borda",
+DlgCellBtnSelect : "Selecionar...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Localizar e Substituir",
+
+// Find Dialog
+DlgFindTitle : "Localizar...",
+DlgFindFindBtn : "Localizar",
+DlgFindNotFoundMsg : "O texto especificado não foi encontrado.",
+
+// Replace Dialog
+DlgReplaceTitle : "Substituir",
+DlgReplaceFindLbl : "Procurar por:",
+DlgReplaceReplaceLbl : "Substituir por:",
+DlgReplaceCaseChk : "Coincidir Maiúsculas/Minúsculas",
+DlgReplaceReplaceBtn : "Substituir",
+DlgReplaceReplAllBtn : "Substituir Tudo",
+DlgReplaceWordChk : "Coincidir a palavra inteira",
+
+// Paste Operations / Dialog
+PasteErrorCut : "As configurações de segurança do seu navegador não permitem que o editor execute operações de recortar automaticamente. Por favor, utilize o teclado para recortar (Ctrl+X).",
+PasteErrorCopy : "As configurações de segurança do seu navegador não permitem que o editor execute operações de copiar automaticamente. Por favor, utilize o teclado para copiar (Ctrl+C).",
+
+PasteAsText : "Colar como Texto sem Formatação",
+PasteFromWord : "Colar do Word",
+
+DlgPasteMsg2 : "Transfira o link usado no box usando o teclado com (<STRONG>Ctrl+V</STRONG>) e <STRONG>OK</STRONG>.",
+DlgPasteSec : "As configurações de segurança do seu navegador não permitem que o editor acesse os dados da área de transferência diretamente. Por favor cole o conteúdo novamente nesta janela.",
+DlgPasteIgnoreFont : "Ignorar definições de fonte",
+DlgPasteRemoveStyles : "Remove definições de estilo",
+
+// Color Picker
+ColorAutomatic : "Automático",
+ColorMoreColors : "Mais Cores...",
+
+// Document Properties
+DocProps : "Propriedades Documento",
+
+// Anchor Dialog
+DlgAnchorTitle : "Formatar Âncora",
+DlgAnchorName : "Nome da Âncora",
+DlgAnchorErrorName : "Por favor, digite o nome da âncora",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Não encontrada",
+DlgSpellChangeTo : "Alterar para",
+DlgSpellBtnIgnore : "Ignorar uma vez",
+DlgSpellBtnIgnoreAll : "Ignorar Todas",
+DlgSpellBtnReplace : "Alterar",
+DlgSpellBtnReplaceAll : "Alterar Todas",
+DlgSpellBtnUndo : "Desfazer",
+DlgSpellNoSuggestions : "-sem sugestões de ortografia-",
+DlgSpellProgress : "Verificação ortográfica em andamento...",
+DlgSpellNoMispell : "Verificação encerrada: Não foram encontrados erros de ortografia",
+DlgSpellNoChanges : "Verificação ortográfica encerrada: Não houve alterações",
+DlgSpellOneChange : "Verificação ortográfica encerrada: Uma palavra foi alterada",
+DlgSpellManyChanges : "Verificação ortográfica encerrada: %1 foram alteradas",
+
+IeSpellDownload : "A verificação ortográfica não foi instalada. Você gostaria de realizar o download agora?",
+
+// Button Dialog
+DlgButtonText : "Texto (Valor)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Botão",
+DlgButtonTypeSbm : "Enviar",
+DlgButtonTypeRst : "Limpar",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nome",
+DlgCheckboxValue : "Valor",
+DlgCheckboxSelected : "Selecionado",
+
+// Form Dialog
+DlgFormName : "Nome",
+DlgFormAction : "Action",
+DlgFormMethod : "Método",
+
+// Select Field Dialog
+DlgSelectName : "Nome",
+DlgSelectValue : "Valor",
+DlgSelectSize : "Tamanho",
+DlgSelectLines : "linhas",
+DlgSelectChkMulti : "Permitir múltiplas seleções",
+DlgSelectOpAvail : "Opções disponíveis",
+DlgSelectOpText : "Texto",
+DlgSelectOpValue : "Valor",
+DlgSelectBtnAdd : "Adicionar",
+DlgSelectBtnModify : "Modificar",
+DlgSelectBtnUp : "Para cima",
+DlgSelectBtnDown : "Para baixo",
+DlgSelectBtnSetValue : "Definir como selecionado",
+DlgSelectBtnDelete : "Remover",
+
+// Textarea Dialog
+DlgTextareaName : "Nome",
+DlgTextareaCols : "Colunas",
+DlgTextareaRows : "Linhas",
+
+// Text Field Dialog
+DlgTextName : "Nome",
+DlgTextValue : "Valor",
+DlgTextCharWidth : "Comprimento (em caracteres)",
+DlgTextMaxChars : "Número Máximo de Caracteres",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Texto",
+DlgTextTypePass : "Senha",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nome",
+DlgHiddenValue : "Valor",
+
+// Bulleted List Dialog
+BulletedListProp : "Formatar Marcadores",
+NumberedListProp : "Formatar Numeração",
+DlgLstStart : "Iniciar",
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Círculo",
+DlgLstTypeDisc : "Disco",
+DlgLstTypeSquare : "Quadrado",
+DlgLstTypeNumbers : "Números (1, 2, 3)",
+DlgLstTypeLCase : "Letras Minúsculas (a, b, c)",
+DlgLstTypeUCase : "Letras Maiúsculas (A, B, C)",
+DlgLstTypeSRoman : "Números Romanos Minúsculos (i, ii, iii)",
+DlgLstTypeLRoman : "Números Romanos Maiúsculos (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Geral",
+DlgDocBackTab : "Plano de Fundo",
+DlgDocColorsTab : "Cores e Margens",
+DlgDocMetaTab : "Meta Dados",
+
+DlgDocPageTitle : "Título da Página",
+DlgDocLangDir : "Direção do Idioma",
+DlgDocLangDirLTR : "Esquerda para Direita (LTR)",
+DlgDocLangDirRTL : "Direita para Esquerda (RTL)",
+DlgDocLangCode : "Código do Idioma",
+DlgDocCharSet : "Codificação de Caracteres",
+DlgDocCharSetCE : "Europa Central",
+DlgDocCharSetCT : "Chinês Tradicional (Big5)",
+DlgDocCharSetCR : "Cirílico",
+DlgDocCharSetGR : "Grego",
+DlgDocCharSetJP : "Japonês",
+DlgDocCharSetKR : "Coreano",
+DlgDocCharSetTR : "Turco",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Europa Ocidental",
+DlgDocCharSetOther : "Outra Codificação de Caracteres",
+
+DlgDocDocType : "Cabeçalho Tipo de Documento",
+DlgDocDocTypeOther : "Other Document Type Heading",
+DlgDocIncXHTML : "Incluir Declarações XHTML",
+DlgDocBgColor : "Cor do Plano de Fundo",
+DlgDocBgImage : "URL da Imagem de Plano de Fundo",
+DlgDocBgNoScroll : "Plano de Fundo Fixo",
+DlgDocCText : "Texto",
+DlgDocCLink : "Hiperlink",
+DlgDocCVisited : "Hiperlink Visitado",
+DlgDocCActive : "Hiperlink Ativo",
+DlgDocMargins : "Margens da Página",
+DlgDocMaTop : "Superior",
+DlgDocMaLeft : "Inferior",
+DlgDocMaRight : "Direita",
+DlgDocMaBottom : "Inferior",
+DlgDocMeIndex : "Palavras-chave de Indexação do Documento (separadas por vírgula)",
+DlgDocMeDescr : "Descrição do Documento",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Direitos Autorais",
+DlgDocPreview : "Visualizar",
+
+// Templates Dialog
+Templates : "Modelos de layout",
+DlgTemplatesTitle : "Modelo de layout do conteúdo",
+DlgTemplatesSelMsg : "Selecione um modelo de layout para ser aberto no editor<br>(o conteúdo atual será perdido):",
+DlgTemplatesLoading : "Carregando a lista de modelos de layout. Aguarde...",
+DlgTemplatesNoTpl : "(Não foram definidos modelos de layout)",
+DlgTemplatesReplace : "Substituir o conteúdo atual",
+
+// About Dialog
+DlgAboutAboutTab : "Sobre",
+DlgAboutBrowserInfoTab : "Informações do Navegador",
+DlgAboutLicenseTab : "Licença",
+DlgAboutVersion : "versão",
+DlgAboutInfo : "Para maiores informações visite",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/pt.js b/httemplate/elements/fckeditor/editor/lang/pt.js
new file mode 100644
index 0000000..6a88429
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/pt.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Portuguese language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Fechar Barra",
+ToolbarExpand : "Expandir Barra",
+
+// Toolbar Items and Context Menu
+Save : "Guardar",
+NewPage : "Nova Página",
+Preview : "Pré-visualizar",
+Cut : "Cortar",
+Copy : "Copiar",
+Paste : "Colar",
+PasteText : "Colar como texto não formatado",
+PasteWord : "Colar do Word",
+Print : "Imprimir",
+SelectAll : "Seleccionar Tudo",
+RemoveFormat : "Eliminar Formato",
+InsertLinkLbl : "Hiperligação",
+InsertLink : "Inserir/Editar Hiperligação",
+RemoveLink : "Eliminar Hiperligação",
+VisitLink : "Open Link", //MISSING
+Anchor : " Inserir/Editar Âncora",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Imagem",
+InsertImage : "Inserir/Editar Imagem",
+InsertFlashLbl : "Flash",
+InsertFlash : "Inserir/Editar Flash",
+InsertTableLbl : "Tabela",
+InsertTable : "Inserir/Editar Tabela",
+InsertLineLbl : "Linha",
+InsertLine : "Inserir Linha Horizontal",
+InsertSpecialCharLbl: "Caracter Especial",
+InsertSpecialChar : "Inserir Caracter Especial",
+InsertSmileyLbl : "Emoticons",
+InsertSmiley : "Inserir Emoticons",
+About : "Acerca do FCKeditor",
+Bold : "Negrito",
+Italic : "Itálico",
+Underline : "Sublinhado",
+StrikeThrough : "Rasurado",
+Subscript : "Superior à Linha",
+Superscript : "Inferior à Linha",
+LeftJustify : "Alinhar à Esquerda",
+CenterJustify : "Alinhar ao Centro",
+RightJustify : "Alinhar à Direita",
+BlockJustify : "Justificado",
+DecreaseIndent : "Diminuir Avanço",
+IncreaseIndent : "Aumentar Avanço",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Anular",
+Redo : "Repetir",
+NumberedListLbl : "Numeração",
+NumberedList : "Inserir/Eliminar Numeração",
+BulletedListLbl : "Marcas",
+BulletedList : "Inserir/Eliminar Marcas",
+ShowTableBorders : "Mostrar Limites da Tabelas",
+ShowDetails : "Mostrar Parágrafo",
+Style : "Estilo",
+FontFormat : "Formato",
+Font : "Tipo de Letra",
+FontSize : "Tamanho",
+TextColor : "Cor do Texto",
+BGColor : "Cor de Fundo",
+Source : "Fonte",
+Find : "Procurar",
+Replace : "Substituir",
+SpellCheck : "Verificação Ortográfica",
+UniversalKeyboard : "Teclado Universal",
+PageBreakLbl : "Quebra de Página",
+PageBreak : "Inserir Quebra de Página",
+
+Form : "Formulário",
+Checkbox : "Caixa de Verificação",
+RadioButton : "Botão de Opção",
+TextField : "Campo de Texto",
+Textarea : "Ãrea de Texto",
+HiddenField : "Campo Escondido",
+Button : "Botão",
+SelectionField : "Caixa de Combinação",
+ImageButton : "Botão de Imagem",
+
+FitWindow : "Maximizar o tamanho do editor",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Editar Hiperligação",
+CellCM : "Célula",
+RowCM : "Linha",
+ColumnCM : "Coluna",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Eliminar Linhas",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Eliminar Coluna",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Eliminar Célula",
+MergeCells : "Unir Células",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Eliminar Tabela",
+CellProperties : "Propriedades da Célula",
+TableProperties : "Propriedades da Tabela",
+ImageProperties : "Propriedades da Imagem",
+FlashProperties : "Propriedades do Flash",
+
+AnchorProp : "Propriedades da Âncora",
+ButtonProp : "Propriedades do Botão",
+CheckboxProp : "Propriedades da Caixa de Verificação",
+HiddenFieldProp : "Propriedades do Campo Escondido",
+RadioButtonProp : "Propriedades do Botão de Opção",
+ImageButtonProp : "Propriedades do Botão de imagens",
+TextFieldProp : "Propriedades do Campo de Texto",
+SelectionFieldProp : "Propriedades da Caixa de Combinação",
+TextareaProp : "Propriedades da Ãrea de Texto",
+FormProp : "Propriedades do Formulário",
+
+FontFormats : "Normal;Formatado;Endereço;Título 1;Título 2;Título 3;Título 4;Título 5;Título 6",
+
+// Alerts and Messages
+ProcessingXHTML : "A Processar XHTML. Por favor, espere...",
+Done : "Concluído",
+PasteWordConfirm : "O texto que deseja parece ter sido copiado do Word. Deseja limpar a formatação antes de colar?",
+NotCompatiblePaste : "Este comando só está disponível para Internet Explorer versão 5.5 ou superior. Deseja colar sem limpar a formatação?",
+UnknownToolbarItem : "Item de barra desconhecido \"%1\"",
+UnknownCommand : "Nome de comando desconhecido \"%1\"",
+NotImplemented : "Comando não implementado",
+UnknownToolbarSet : "Nome de barra \"%1\" não definido",
+NoActiveX : "As definições de segurança do navegador podem limitar algumas potencalidades do editr. Deve activar a opção \"Executar controlos e extensões ActiveX\". Pode ocorrer erros ou verificar que faltam potencialidades.",
+BrowseServerBlocked : "Não foi possível abrir o navegador de recursos. Certifique-se que todos os bloqueadores de popup estão desactivados.",
+DialogBlocked : "Não foi possível abrir a janela de diálogo. Certifique-se que todos os bloqueadores de popup estão desactivados.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Cancelar",
+DlgBtnClose : "Fechar",
+DlgBtnBrowseServer : "Navegar no Servidor",
+DlgAdvancedTag : "Avançado",
+DlgOpOther : "<Outro>",
+DlgInfoTab : "Informação",
+DlgAlertUrl : "Por favor introduza o URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<Não definido>",
+DlgGenId : "Id",
+DlgGenLangDir : "Orientação de idioma",
+DlgGenLangDirLtr : "Esquerda à Direita (LTR)",
+DlgGenLangDirRtl : "Direita a Esquerda (RTL)",
+DlgGenLangCode : "Código de Idioma",
+DlgGenAccessKey : "Chave de Acesso",
+DlgGenName : "Nome",
+DlgGenTabIndex : "Ãndice de Tubulação",
+DlgGenLongDescr : "Descrição Completa do URL",
+DlgGenClass : "Classes de Estilo de Folhas Classes",
+DlgGenTitle : "Título",
+DlgGenContType : "Tipo de Conteúdo",
+DlgGenLinkCharset : "Fonte de caracteres vinculado",
+DlgGenStyle : "Estilo",
+
+// Image Dialog
+DlgImgTitle : "Propriedades da Imagem",
+DlgImgInfoTab : "Informação da Imagem",
+DlgImgBtnUpload : "Enviar para o Servidor",
+DlgImgURL : "URL",
+DlgImgUpload : "Carregar",
+DlgImgAlt : "Texto Alternativo",
+DlgImgWidth : "Largura",
+DlgImgHeight : "Altura",
+DlgImgLockRatio : "Proporcional",
+DlgBtnResetSize : "Tamanho Original",
+DlgImgBorder : "Limite",
+DlgImgHSpace : "Esp.Horiz",
+DlgImgVSpace : "Esp.Vert",
+DlgImgAlign : "Alinhamento",
+DlgImgAlignLeft : "Esquerda",
+DlgImgAlignAbsBottom: "Abs inferior",
+DlgImgAlignAbsMiddle: "Abs centro",
+DlgImgAlignBaseline : "Linha de base",
+DlgImgAlignBottom : "Fundo",
+DlgImgAlignMiddle : "Centro",
+DlgImgAlignRight : "Direita",
+DlgImgAlignTextTop : "Topo do texto",
+DlgImgAlignTop : "Topo",
+DlgImgPreview : "Pré-visualizar",
+DlgImgAlertUrl : "Por favor introduza o URL da imagem",
+DlgImgLinkTab : "Hiperligação",
+
+// Flash Dialog
+DlgFlashTitle : "Propriedades do Flash",
+DlgFlashChkPlay : "Reproduzir automaticamente",
+DlgFlashChkLoop : "Loop",
+DlgFlashChkMenu : "Permitir Menu do Flash",
+DlgFlashScale : "Escala",
+DlgFlashScaleAll : "Mostrar tudo",
+DlgFlashScaleNoBorder : "Sem Limites",
+DlgFlashScaleFit : "Tamanho Exacto",
+
+// Link Dialog
+DlgLnkWindowTitle : "Hiperligação",
+DlgLnkInfoTab : "Informação de Hiperligação",
+DlgLnkTargetTab : "Destino",
+
+DlgLnkType : "Tipo de Hiperligação",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Referência a esta página",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocolo",
+DlgLnkProtoOther : "<outro>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Seleccionar una referência",
+DlgLnkAnchorByName : "Por Nome de Referência",
+DlgLnkAnchorById : "Por ID de elemento",
+DlgLnkNoAnchors : "(Não há referências disponíveis no documento)",
+DlgLnkEMail : "Endereço de E-Mail",
+DlgLnkEMailSubject : "Título de Mensagem",
+DlgLnkEMailBody : "Corpo da Mensagem",
+DlgLnkUpload : "Carregar",
+DlgLnkBtnUpload : "Enviar ao Servidor",
+
+DlgLnkTarget : "Destino",
+DlgLnkTargetFrame : "<Frame>",
+DlgLnkTargetPopup : "<Janela de popup>",
+DlgLnkTargetBlank : "Nova Janela(_blank)",
+DlgLnkTargetParent : "Janela Pai (_parent)",
+DlgLnkTargetSelf : "Mesma janela (_self)",
+DlgLnkTargetTop : "Janela primaria (_top)",
+DlgLnkTargetFrameName : "Nome do Frame Destino",
+DlgLnkPopWinName : "Nome da Janela de Popup",
+DlgLnkPopWinFeat : "Características de Janela de Popup",
+DlgLnkPopResize : "Ajustável",
+DlgLnkPopLocation : "Barra de localização",
+DlgLnkPopMenu : "Barra de Menu",
+DlgLnkPopScroll : "Barras de deslocamento",
+DlgLnkPopStatus : "Barra de Estado",
+DlgLnkPopToolbar : "Barra de Ferramentas",
+DlgLnkPopFullScrn : "Janela Completa (IE)",
+DlgLnkPopDependent : "Dependente (Netscape)",
+DlgLnkPopWidth : "Largura",
+DlgLnkPopHeight : "Altura",
+DlgLnkPopLeft : "Posição Esquerda",
+DlgLnkPopTop : "Posição Direita",
+
+DlnLnkMsgNoUrl : "Por favor introduza a hiperligação URL",
+DlnLnkMsgNoEMail : "Por favor introduza o endereço de e-mail",
+DlnLnkMsgNoAnchor : "Por favor seleccione uma referência",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Seleccionar Cor",
+DlgColorBtnClear : "Nenhuma",
+DlgColorHighlight : "Destacado",
+DlgColorSelected : "Seleccionado",
+
+// Smiley Dialog
+DlgSmileyTitle : "Inserir um Emoticon",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Seleccione um caracter especial",
+
+// Table Dialog
+DlgTableTitle : "Propriedades da Tabela",
+DlgTableRows : "Linhas",
+DlgTableColumns : "Colunas",
+DlgTableBorder : "Tamanho do Limite",
+DlgTableAlign : "Alinhamento",
+DlgTableAlignNotSet : "<Não definido>",
+DlgTableAlignLeft : "Esquerda",
+DlgTableAlignCenter : "Centrado",
+DlgTableAlignRight : "Direita",
+DlgTableWidth : "Largura",
+DlgTableWidthPx : "pixeis",
+DlgTableWidthPc : "percentagem",
+DlgTableHeight : "Altura",
+DlgTableCellSpace : "Esp. e/células",
+DlgTableCellPad : "Esp. interior",
+DlgTableCaption : "Título",
+DlgTableSummary : "Sumário",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Propriedades da Célula",
+DlgCellWidth : "Largura",
+DlgCellWidthPx : "pixeis",
+DlgCellWidthPc : "percentagem",
+DlgCellHeight : "Altura",
+DlgCellWordWrap : "Moldar Texto",
+DlgCellWordWrapNotSet : "<Não definido>",
+DlgCellWordWrapYes : "Sim",
+DlgCellWordWrapNo : "Não",
+DlgCellHorAlign : "Alinhamento Horizontal",
+DlgCellHorAlignNotSet : "<Não definido>",
+DlgCellHorAlignLeft : "Esquerda",
+DlgCellHorAlignCenter : "Centrado",
+DlgCellHorAlignRight: "Direita",
+DlgCellVerAlign : "Alinhamento Vertical",
+DlgCellVerAlignNotSet : "<Não definido>",
+DlgCellVerAlignTop : "Topo",
+DlgCellVerAlignMiddle : "Médio",
+DlgCellVerAlignBottom : "Fundi",
+DlgCellVerAlignBaseline : "Linha de Base",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Unir Linhas",
+DlgCellCollSpan : "Unir Colunas",
+DlgCellBackColor : "Cor do Fundo",
+DlgCellBorderColor : "Cor do Limite",
+DlgCellBtnSelect : "Seleccione...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Procurar",
+DlgFindFindBtn : "Procurar",
+DlgFindNotFoundMsg : "O texto especificado não foi encontrado.",
+
+// Replace Dialog
+DlgReplaceTitle : "Substituir",
+DlgReplaceFindLbl : "Texto a Procurar:",
+DlgReplaceReplaceLbl : "Substituir por:",
+DlgReplaceCaseChk : "Maiúsculas/Minúsculas",
+DlgReplaceReplaceBtn : "Substituir",
+DlgReplaceReplAllBtn : "Substituir Tudo",
+DlgReplaceWordChk : "Coincidir com toda a palavra",
+
+// Paste Operations / Dialog
+PasteErrorCut : "A configuração de segurança do navegador não permite a execução automática de operações de cortar. Por favor use o teclado (Ctrl+X).",
+PasteErrorCopy : "A configuração de segurança do navegador não permite a execução automática de operações de copiar. Por favor use o teclado (Ctrl+C).",
+
+PasteAsText : "Colar como Texto Simples",
+PasteFromWord : "Colar do Word",
+
+DlgPasteMsg2 : "Por favor, cole dentro da seguinte caixa usando o teclado (<STRONG>Ctrl+V</STRONG>) e prima <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignorar da definições do Tipo de Letra ",
+DlgPasteRemoveStyles : "Remover as definições de Estilos",
+
+// Color Picker
+ColorAutomatic : "Automático",
+ColorMoreColors : "Mais Cores...",
+
+// Document Properties
+DocProps : "Propriedades do Documento",
+
+// Anchor Dialog
+DlgAnchorTitle : "Propriedades da Âncora",
+DlgAnchorName : "Nome da Âncora",
+DlgAnchorErrorName : "Por favor, introduza o nome da âncora",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Não está num directório",
+DlgSpellChangeTo : "Mudar para",
+DlgSpellBtnIgnore : "Ignorar",
+DlgSpellBtnIgnoreAll : "Ignorar Tudo",
+DlgSpellBtnReplace : "Substituir",
+DlgSpellBtnReplaceAll : "Substituir Tudo",
+DlgSpellBtnUndo : "Anular",
+DlgSpellNoSuggestions : "- Sem sugestões -",
+DlgSpellProgress : "Verificação ortográfica em progresso…",
+DlgSpellNoMispell : "Verificação ortográfica completa: não foram encontrados erros",
+DlgSpellNoChanges : "Verificação ortográfica completa: não houve alteração de palavras",
+DlgSpellOneChange : "Verificação ortográfica completa: uma palavra alterada",
+DlgSpellManyChanges : "Verificação ortográfica completa: %1 palavras alteradas",
+
+IeSpellDownload : " Verificação ortográfica não instalada. Quer descarregar agora?",
+
+// Button Dialog
+DlgButtonText : "Texto (Valor)",
+DlgButtonType : "Tipo",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nome",
+DlgCheckboxValue : "Valor",
+DlgCheckboxSelected : "Seleccionado",
+
+// Form Dialog
+DlgFormName : "Nome",
+DlgFormAction : "Acção",
+DlgFormMethod : "Método",
+
+// Select Field Dialog
+DlgSelectName : "Nome",
+DlgSelectValue : "Valor",
+DlgSelectSize : "Tamanho",
+DlgSelectLines : "linhas",
+DlgSelectChkMulti : "Permitir selecções múltiplas",
+DlgSelectOpAvail : "Opções Possíveis",
+DlgSelectOpText : "Texto",
+DlgSelectOpValue : "Valor",
+DlgSelectBtnAdd : "Adicionar",
+DlgSelectBtnModify : "Modificar",
+DlgSelectBtnUp : "Para cima",
+DlgSelectBtnDown : "Para baixo",
+DlgSelectBtnSetValue : "Definir um valor por defeito",
+DlgSelectBtnDelete : "Apagar",
+
+// Textarea Dialog
+DlgTextareaName : "Nome",
+DlgTextareaCols : "Colunas",
+DlgTextareaRows : "Linhas",
+
+// Text Field Dialog
+DlgTextName : "Nome",
+DlgTextValue : "Valor",
+DlgTextCharWidth : "Tamanho do caracter",
+DlgTextMaxChars : "Nr. Máximo de Caracteres",
+DlgTextType : "Tipo",
+DlgTextTypeText : "Texto",
+DlgTextTypePass : "Palavra-chave",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nome",
+DlgHiddenValue : "Valor",
+
+// Bulleted List Dialog
+BulletedListProp : "Propriedades da Marca",
+NumberedListProp : "Propriedades da Numeração",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Tipo",
+DlgLstTypeCircle : "Circulo",
+DlgLstTypeDisc : "Disco",
+DlgLstTypeSquare : "Quadrado",
+DlgLstTypeNumbers : "Números (1, 2, 3)",
+DlgLstTypeLCase : "Letras Minúsculas (a, b, c)",
+DlgLstTypeUCase : "Letras Maiúsculas (A, B, C)",
+DlgLstTypeSRoman : "Numeração Romana em Minúsculas (i, ii, iii)",
+DlgLstTypeLRoman : "Numeração Romana em Maiúsculas (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Geral",
+DlgDocBackTab : "Fundo",
+DlgDocColorsTab : "Cores e Margens",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Título da Página",
+DlgDocLangDir : "Orientação de idioma",
+DlgDocLangDirLTR : "Esquerda à Direita (LTR)",
+DlgDocLangDirRTL : "Direita à Esquerda (RTL)",
+DlgDocLangCode : "Código de Idioma",
+DlgDocCharSet : "Codificação de Caracteres",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Outra Codificação de Caracteres",
+
+DlgDocDocType : "Tipo de Cabeçalho do Documento",
+DlgDocDocTypeOther : "Outro Tipo de Cabeçalho do Documento",
+DlgDocIncXHTML : "Incluir Declarações XHTML",
+DlgDocBgColor : "Cor de Fundo",
+DlgDocBgImage : "Caminho para a Imagem de Fundo",
+DlgDocBgNoScroll : "Fundo Fixo",
+DlgDocCText : "Texto",
+DlgDocCLink : "Hiperligação",
+DlgDocCVisited : "Hiperligação Visitada",
+DlgDocCActive : "Hiperligação Activa",
+DlgDocMargins : "Margem das Páginas",
+DlgDocMaTop : "Topo",
+DlgDocMaLeft : "Esquerda",
+DlgDocMaRight : "Direita",
+DlgDocMaBottom : "Fundo",
+DlgDocMeIndex : "Palavras de Indexação do Documento (separadas por virgula)",
+DlgDocMeDescr : "Descrição do Documento",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Direitos de Autor",
+DlgDocPreview : "Pré-visualizar",
+
+// Templates Dialog
+Templates : "Modelos",
+DlgTemplatesTitle : "Modelo de Conteúdo",
+DlgTemplatesSelMsg : "Por favor, seleccione o modelo a abrir no editor<br>(o conteúdo actual será perdido):",
+DlgTemplatesLoading : "A carregar a lista de modelos. Aguarde por favor...",
+DlgTemplatesNoTpl : "(Sem modelos definidos)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "Acerca",
+DlgAboutBrowserInfoTab : "Informação do Nevegador",
+DlgAboutLicenseTab : "Licença",
+DlgAboutVersion : "versão",
+DlgAboutInfo : "Para mais informações por favor dirija-se a",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ro.js b/httemplate/elements/fckeditor/editor/lang/ro.js
new file mode 100644
index 0000000..51640a5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ro.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Romanian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Ascunde bara cu opţiuni",
+ToolbarExpand : "Expandează bara cu opţiuni",
+
+// Toolbar Items and Context Menu
+Save : "Salvează",
+NewPage : "Pagină nouă",
+Preview : "Previzualizare",
+Cut : "Taie",
+Copy : "Copiază",
+Paste : "Adaugă",
+PasteText : "Adaugă ca text simplu",
+PasteWord : "Adaugă din Word",
+Print : "Printează",
+SelectAll : "Selectează tot",
+RemoveFormat : "Înlătură formatarea",
+InsertLinkLbl : "Link (Legătură web)",
+InsertLink : "Inserează/Editează link (legătură web)",
+RemoveLink : "Înlătură link (legătură web)",
+VisitLink : "Open Link", //MISSING
+Anchor : "Inserează/Editează ancoră",
+AnchorDelete : "Şterge ancoră",
+InsertImageLbl : "Imagine",
+InsertImage : "Inserează/Editează imagine",
+InsertFlashLbl : "Flash",
+InsertFlash : "Inserează/Editează flash",
+InsertTableLbl : "Tabel",
+InsertTable : "Inserează/Editează tabel",
+InsertLineLbl : "Linie",
+InsertLine : "Inserează linie orizontă",
+InsertSpecialCharLbl: "Caracter special",
+InsertSpecialChar : "Inserează caracter special",
+InsertSmileyLbl : "Figură expresivă (Emoticon)",
+InsertSmiley : "Inserează Figură expresivă (Emoticon)",
+About : "Despre FCKeditor",
+Bold : "ÃŽngroÅŸat (bold)",
+Italic : "ÃŽnclinat (italic)",
+Underline : "Subliniat (underline)",
+StrikeThrough : "Tăiat (strike through)",
+Subscript : "Indice (subscript)",
+Superscript : "Putere (superscript)",
+LeftJustify : "Aliniere la stânga",
+CenterJustify : "Aliniere centrală",
+RightJustify : "Aliniere la dreapta",
+BlockJustify : "Aliniere în bloc (Block Justify)",
+DecreaseIndent : "Scade indentarea",
+IncreaseIndent : "CreÅŸte indentarea",
+Blockquote : "Citat",
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Starea anterioară (undo)",
+Redo : "Starea ulterioară (redo)",
+NumberedListLbl : "Listă numerotată",
+NumberedList : "Inserează/Şterge listă numerotată",
+BulletedListLbl : "Listă cu puncte",
+BulletedList : "Inserează/Şterge listă cu puncte",
+ShowTableBorders : "Arată marginile tabelului",
+ShowDetails : "Arată detalii",
+Style : "Stil",
+FontFormat : "Formatare",
+Font : "Font",
+FontSize : "Mărime",
+TextColor : "Culoarea textului",
+BGColor : "Coloarea fundalului",
+Source : "Sursa",
+Find : "Găseşte",
+Replace : "ÃŽnlocuieÅŸte",
+SpellCheck : "Verifică text",
+UniversalKeyboard : "Tastatură universală",
+PageBreakLbl : "Separator de pagină (Page Break)",
+PageBreak : "Inserează separator de pagină (Page Break)",
+
+Form : "Formular (Form)",
+Checkbox : "Bifă (Checkbox)",
+RadioButton : "Buton radio (RadioButton)",
+TextField : "Câmp text (TextField)",
+Textarea : "Suprafaţă text (Textarea)",
+HiddenField : "Câmp ascuns (HiddenField)",
+Button : "Buton",
+SelectionField : "Câmp selecţie (SelectionField)",
+ImageButton : "Buton imagine (ImageButton)",
+
+FitWindow : "Maximizează mărimea editorului",
+ShowBlocks : "Arată blocurile",
+
+// Context Menu
+EditLink : "Editează Link",
+CellCM : "Celulă",
+RowCM : "Linie",
+ColumnCM : "Coloană",
+InsertRowAfter : "Inserează linie după",
+InsertRowBefore : "Inserează linie înainte",
+DeleteRows : "Åžterge linii",
+InsertColumnAfter : "Inserează coloană după",
+InsertColumnBefore : "Inserează coloană înainte",
+DeleteColumns : "Åžterge celule",
+InsertCellAfter : "Inserează celulă după",
+InsertCellBefore : "Inserează celulă înainte",
+DeleteCells : "Åžterge celule",
+MergeCells : "UneÅŸte celule",
+MergeRight : "UneÅŸte la dreapta",
+MergeDown : "UneÅŸte jos",
+HorizontalSplitCell : "Împarte celula pe orizontală",
+VerticalSplitCell : "Împarte celula pe verticală",
+TableDelete : "Åžterge tabel",
+CellProperties : "Proprietăţile celulei",
+TableProperties : "Proprietăţile tabelului",
+ImageProperties : "Proprietăţile imaginii",
+FlashProperties : "Proprietăţile flash-ului",
+
+AnchorProp : "Proprietăţi ancoră",
+ButtonProp : "Proprietăţi buton",
+CheckboxProp : "Proprietăţi bifă (Checkbox)",
+HiddenFieldProp : "Proprietăţi câmp ascuns (Hidden Field)",
+RadioButtonProp : "Proprietăţi buton radio (Radio Button)",
+ImageButtonProp : "Proprietăţi buton imagine (Image Button)",
+TextFieldProp : "Proprietăţi câmp text (Text Field)",
+SelectionFieldProp : "Proprietăţi câmp selecţie (Selection Field)",
+TextareaProp : "Proprietăţi suprafaţă text (Textarea)",
+FormProp : "Proprietăţi formular (Form)",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)", //MISSING
+
+// Alerts and Messages
+ProcessingXHTML : "Procesăm XHTML. Vă rugăm aşteptaţi...",
+Done : "Am terminat",
+PasteWordConfirm : "Textul pe care doriţi să-l adăugaţi pare a fi formatat pentru Word. Doriţi să-l curăţaţi de această formatare înainte de a-l adăuga?",
+NotCompatiblePaste : "Această facilitate e disponibilă doar pentru Microsoft Internet Explorer, versiunea 5.5 sau ulterioară. Vreţi să-l adăugaţi fără a-i fi înlăturat formatarea?",
+UnknownToolbarItem : "Obiectul \"%1\" din bara cu opţiuni necunoscut",
+UnknownCommand : "Comanda \"%1\" necunoscută",
+NotImplemented : "Comandă neimplementată",
+UnknownToolbarSet : "Grupul din bara cu opţiuni \"%1\" nu există",
+NoActiveX : "Setările de securitate ale programului dvs. cu care navigaţi pe internet (browser) pot limita anumite funcţionalităţi ale editorului. Pentru a evita asta, trebuie să activaţi opţiunea \"Run ActiveX controls and plug-ins\". Poate veţi întâlni erori sau veţi observa funcţionalităţi lipsă.",
+BrowseServerBlocked : "The resources browser could not be opened. Asiguraţi-vă că nu e activ niciun \"popup blocker\" (funcţionalitate a programului de navigat (browser) sau a unui plug-in al acestuia de a bloca deschiderea unui noi ferestre).",
+DialogBlocked : "Nu a fost posibilă deschiderea unei ferestre de dialog. Asiguraţi-vă că nu e activ niciun \"popup blocker\" (funcţionalitate a programului de navigat (browser) sau a unui plug-in al acestuia de a bloca deschiderea unui noi ferestre).",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "Bine",
+DlgBtnCancel : "Anulare",
+DlgBtnClose : "ÃŽnchidere",
+DlgBtnBrowseServer : "Răsfoieşte server",
+DlgAdvancedTag : "Avansat",
+DlgOpOther : "<Altul>",
+DlgInfoTab : "Informaţii",
+DlgAlertUrl : "Vă rugăm să scrieţi URL-ul",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nesetat>",
+DlgGenId : "Id",
+DlgGenLangDir : "Direcţia cuvintelor",
+DlgGenLangDirLtr : "stânga-dreapta (LTR)",
+DlgGenLangDirRtl : "dreapta-stânga (RTL)",
+DlgGenLangCode : "Codul limbii",
+DlgGenAccessKey : "Tasta de acces",
+DlgGenName : "Nume",
+DlgGenTabIndex : "Indexul tabului",
+DlgGenLongDescr : "Descrierea lungă URL",
+DlgGenClass : "Clasele cu stilul paginii (CSS)",
+DlgGenTitle : "Titlul consultativ",
+DlgGenContType : "Tipul consultativ al titlului",
+DlgGenLinkCharset : "Setul de caractere al resursei legate",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Proprietăţile imaginii",
+DlgImgInfoTab : "Informaţii despre imagine",
+DlgImgBtnUpload : "Trimite la server",
+DlgImgURL : "URL",
+DlgImgUpload : "Încarcă",
+DlgImgAlt : "Text alternativ",
+DlgImgWidth : "Lăţime",
+DlgImgHeight : "Înălţime",
+DlgImgLockRatio : "Păstrează proporţiile",
+DlgBtnResetSize : "Resetează mărimea",
+DlgImgBorder : "Margine",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Aliniere",
+DlgImgAlignLeft : "Stânga",
+DlgImgAlignAbsBottom: "Jos absolut (Abs Bottom)",
+DlgImgAlignAbsMiddle: "Mijloc absolut (Abs Middle)",
+DlgImgAlignBaseline : "Linia de jos (Baseline)",
+DlgImgAlignBottom : "Jos",
+DlgImgAlignMiddle : "Mijloc",
+DlgImgAlignRight : "Dreapta",
+DlgImgAlignTextTop : "Text sus",
+DlgImgAlignTop : "Sus",
+DlgImgPreview : "Previzualizare",
+DlgImgAlertUrl : "Vă rugăm să scrieţi URL-ul imaginii",
+DlgImgLinkTab : "Link (Legătură web)",
+
+// Flash Dialog
+DlgFlashTitle : "Proprietăţile flash-ului",
+DlgFlashChkPlay : "Rulează automat",
+DlgFlashChkLoop : "Repetă (Loop)",
+DlgFlashChkMenu : "Activează meniul flash",
+DlgFlashScale : "Scală",
+DlgFlashScaleAll : "Arată tot",
+DlgFlashScaleNoBorder : "Fără margini (No border)",
+DlgFlashScaleFit : "PotriveÅŸte",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link (Legătură web)",
+DlgLnkInfoTab : "Informaţii despre link (Legătură web)",
+DlgLnkTargetTab : "Ţintă (Target)",
+
+DlgLnkType : "Tipul link-ului (al legăturii web)",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ancoră în această pagină",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protocol",
+DlgLnkProtoOther : "<altul>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Selectaţi o ancoră",
+DlgLnkAnchorByName : "după numele ancorei",
+DlgLnkAnchorById : "după Id-ul elementului",
+DlgLnkNoAnchors : "(Nicio ancoră disponibilă în document)",
+DlgLnkEMail : "Adresă de e-mail",
+DlgLnkEMailSubject : "Subiectul mesajului",
+DlgLnkEMailBody : "Conţinutul mesajului",
+DlgLnkUpload : "Încarcă",
+DlgLnkBtnUpload : "Trimite la server",
+
+DlgLnkTarget : "Ţintă (Target)",
+DlgLnkTargetFrame : "<frame>",
+DlgLnkTargetPopup : "<fereastra popup>",
+DlgLnkTargetBlank : "Fereastră nouă (_blank)",
+DlgLnkTargetParent : "Fereastra părinte (_parent)",
+DlgLnkTargetSelf : "Aceeaşi fereastră (_self)",
+DlgLnkTargetTop : "Fereastra din topul ierarhiei (_top)",
+DlgLnkTargetFrameName : "Numele frame-ului ţintă",
+DlgLnkPopWinName : "Numele ferestrei popup",
+DlgLnkPopWinFeat : "Proprietăţile ferestrei popup",
+DlgLnkPopResize : "Scalabilă",
+DlgLnkPopLocation : "Bara de locaţie",
+DlgLnkPopMenu : "Bara de meniu",
+DlgLnkPopScroll : "Scroll Bars",
+DlgLnkPopStatus : "Bara de status",
+DlgLnkPopToolbar : "Bara de opţiuni",
+DlgLnkPopFullScrn : "Tot ecranul (Full Screen)(IE)",
+DlgLnkPopDependent : "Dependent (Netscape)",
+DlgLnkPopWidth : "Lăţime",
+DlgLnkPopHeight : "Înălţime",
+DlgLnkPopLeft : "Poziţia la stânga",
+DlgLnkPopTop : "Poziţia la dreapta",
+
+DlnLnkMsgNoUrl : "Vă rugăm să scrieţi URL-ul",
+DlnLnkMsgNoEMail : "Vă rugăm să scrieţi adresa de e-mail",
+DlnLnkMsgNoAnchor : "Vă rugăm să selectaţi o ancoră",
+DlnLnkMsgInvPopName : "Numele 'popup'-ului trebuie să înceapă cu un caracter alfabetic şi trebuie să nu conţină spaţii",
+
+// Color Dialog
+DlgColorTitle : "Selectează culoare",
+DlgColorBtnClear : "Curăţă",
+DlgColorHighlight : "Subliniază (Highlight)",
+DlgColorSelected : "Selectat",
+
+// Smiley Dialog
+DlgSmileyTitle : "Inserează o figură expresivă (Emoticon)",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Selectează caracter special",
+
+// Table Dialog
+DlgTableTitle : "Proprietăţile tabelului",
+DlgTableRows : "Linii",
+DlgTableColumns : "Coloane",
+DlgTableBorder : "Mărimea marginii",
+DlgTableAlign : "Aliniament",
+DlgTableAlignNotSet : "<Nesetat>",
+DlgTableAlignLeft : "Stânga",
+DlgTableAlignCenter : "Centru",
+DlgTableAlignRight : "Dreapta",
+DlgTableWidth : "Lăţime",
+DlgTableWidthPx : "pixeli",
+DlgTableWidthPc : "procente",
+DlgTableHeight : "Înălţime",
+DlgTableCellSpace : "Spaţiu între celule",
+DlgTableCellPad : "Spaţiu în cadrul celulei",
+DlgTableCaption : "Titlu (Caption)",
+DlgTableSummary : "Rezumat",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Proprietăţile celulei",
+DlgCellWidth : "Lăţime",
+DlgCellWidthPx : "pixeli",
+DlgCellWidthPc : "procente",
+DlgCellHeight : "Înălţime",
+DlgCellWordWrap : "Desparte cuvintele (Wrap)",
+DlgCellWordWrapNotSet : "<Nesetat>",
+DlgCellWordWrapYes : "Da",
+DlgCellWordWrapNo : "Nu",
+DlgCellHorAlign : "Aliniament orizontal",
+DlgCellHorAlignNotSet : "<Nesetat>",
+DlgCellHorAlignLeft : "Stânga",
+DlgCellHorAlignCenter : "Centru",
+DlgCellHorAlignRight: "Dreapta",
+DlgCellVerAlign : "Aliniament vertical",
+DlgCellVerAlignNotSet : "<Nesetat>",
+DlgCellVerAlignTop : "Sus",
+DlgCellVerAlignMiddle : "Mijloc",
+DlgCellVerAlignBottom : "Jos",
+DlgCellVerAlignBaseline : "Linia de jos (Baseline)",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Lungimea în linii (Span)",
+DlgCellCollSpan : "Lungimea în coloane (Span)",
+DlgCellBackColor : "Culoarea fundalului",
+DlgCellBorderColor : "Culoarea marginii",
+DlgCellBtnSelect : "Selectaţi...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Găseşte şi înlocuieşte",
+
+// Find Dialog
+DlgFindTitle : "Găseşte",
+DlgFindFindBtn : "Găseşte",
+DlgFindNotFoundMsg : "Textul specificat nu a fost găsit.",
+
+// Replace Dialog
+DlgReplaceTitle : "Replace",
+DlgReplaceFindLbl : "Găseşte:",
+DlgReplaceReplaceLbl : "ÃŽnlocuieÅŸte cu:",
+DlgReplaceCaseChk : "DeosebeÅŸte majuscule de minuscule (Match case)",
+DlgReplaceReplaceBtn : "ÃŽnlocuieÅŸte",
+DlgReplaceReplAllBtn : "ÃŽnlocuieÅŸte tot",
+DlgReplaceWordChk : "Doar cuvintele întregi",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Setările de securitate ale navigatorului (browser) pe care îl folosiţi nu permit editorului să execute automat operaţiunea de tăiere. Vă rugăm folosiţi tastatura (Ctrl+X).",
+PasteErrorCopy : "Setările de securitate ale navigatorului (browser) pe care îl folosiţi nu permit editorului să execute automat operaţiunea de copiere. Vă rugăm folosiţi tastatura (Ctrl+C).",
+
+PasteAsText : "Adaugă ca text simplu (Plain Text)",
+PasteFromWord : "Adaugă din Word",
+
+DlgPasteMsg2 : "Vă rugăm adăugaţi în căsuţa următoare folosind tastatura (<STRONG>Ctrl+V</STRONG>) şi apăsaţi <STRONG>OK</STRONG>.",
+DlgPasteSec : "Din cauza setărilor de securitate ale programului dvs. cu care navigaţi pe internet (browser), editorul nu poate accesa direct datele din clipboard. Va trebui să adăugaţi din nou datele în această fereastră.",
+DlgPasteIgnoreFont : "Ignoră definiţiile Font Face",
+DlgPasteRemoveStyles : "Şterge definiţiile stilurilor",
+
+// Color Picker
+ColorAutomatic : "Automatic",
+ColorMoreColors : "Mai multe culori...",
+
+// Document Properties
+DocProps : "Proprietăţile documentului",
+
+// Anchor Dialog
+DlgAnchorTitle : "Proprietăţile ancorei",
+DlgAnchorName : "Numele ancorei",
+DlgAnchorErrorName : "Vă rugăm scrieţi numele ancorei",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nu e în dicţionar",
+DlgSpellChangeTo : "Schimbă în",
+DlgSpellBtnIgnore : "Ignoră",
+DlgSpellBtnIgnoreAll : "Ignoră toate",
+DlgSpellBtnReplace : "ÃŽnlocuieÅŸte",
+DlgSpellBtnReplaceAll : "ÃŽnlocuieÅŸte tot",
+DlgSpellBtnUndo : "Starea anterioară (undo)",
+DlgSpellNoSuggestions : "- Fără sugestii -",
+DlgSpellProgress : "Verificarea textului în desfăşurare...",
+DlgSpellNoMispell : "Verificarea textului terminată: Nicio greşeală găsită",
+DlgSpellNoChanges : "Verificarea textului terminată: Niciun cuvânt modificat",
+DlgSpellOneChange : "Verificarea textului terminată: Un cuvânt modificat",
+DlgSpellManyChanges : "Verificarea textului terminată: 1% cuvinte modificate",
+
+IeSpellDownload : "Unealta pentru verificat textul (Spell checker) neinstalată. Doriţi să o descărcaţi acum?",
+
+// Button Dialog
+DlgButtonText : "Text (Valoare)",
+DlgButtonType : "Tip",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Nume",
+DlgCheckboxValue : "Valoare",
+DlgCheckboxSelected : "Selectat",
+
+// Form Dialog
+DlgFormName : "Nume",
+DlgFormAction : "Acţiune",
+DlgFormMethod : "Metodă",
+
+// Select Field Dialog
+DlgSelectName : "Nume",
+DlgSelectValue : "Valoare",
+DlgSelectSize : "Mărime",
+DlgSelectLines : "linii",
+DlgSelectChkMulti : "Permite selecţii multiple",
+DlgSelectOpAvail : "Opţiuni disponibile",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Valoare",
+DlgSelectBtnAdd : "Adaugă",
+DlgSelectBtnModify : "Modifică",
+DlgSelectBtnUp : "Sus",
+DlgSelectBtnDown : "Jos",
+DlgSelectBtnSetValue : "Setează ca valoare selectată",
+DlgSelectBtnDelete : "Åžterge",
+
+// Textarea Dialog
+DlgTextareaName : "Nume",
+DlgTextareaCols : "Coloane",
+DlgTextareaRows : "Linii",
+
+// Text Field Dialog
+DlgTextName : "Nume",
+DlgTextValue : "Valoare",
+DlgTextCharWidth : "Lărgimea caracterului",
+DlgTextMaxChars : "Caractere maxime",
+DlgTextType : "Tip",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Parolă",
+
+// Hidden Field Dialog
+DlgHiddenName : "Nume",
+DlgHiddenValue : "Valoare",
+
+// Bulleted List Dialog
+BulletedListProp : "Proprietăţile listei punctate (Bulleted List)",
+NumberedListProp : "Proprietăţile listei numerotate (Numbered List)",
+DlgLstStart : "Start",
+DlgLstType : "Tip",
+DlgLstTypeCircle : "Cerc",
+DlgLstTypeDisc : "Disc",
+DlgLstTypeSquare : "Pătrat",
+DlgLstTypeNumbers : "Numere (1, 2, 3)",
+DlgLstTypeLCase : "Minuscule-litere mici (a, b, c)",
+DlgLstTypeUCase : "Majuscule (A, B, C)",
+DlgLstTypeSRoman : "Cifre romane mici (i, ii, iii)",
+DlgLstTypeLRoman : "Cifre romane mari (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "General",
+DlgDocBackTab : "Fundal",
+DlgDocColorsTab : "Culori si margini",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Titlul paginii",
+DlgDocLangDir : "Descrierea limbii",
+DlgDocLangDirLTR : "stânga-dreapta (LTR)",
+DlgDocLangDirRTL : "dreapta-stânga (RTL)",
+DlgDocLangCode : "Codul limbii",
+DlgDocCharSet : "Encoding setului de caractere",
+DlgDocCharSetCE : "Central european",
+DlgDocCharSetCT : "Chinezesc tradiţional (Big5)",
+DlgDocCharSetCR : "Chirilic",
+DlgDocCharSetGR : "Grecesc",
+DlgDocCharSetJP : "Japonez",
+DlgDocCharSetKR : "Corean",
+DlgDocCharSetTR : "Turcesc",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Vest european",
+DlgDocCharSetOther : "Alt encoding al setului de caractere",
+
+DlgDocDocType : "Document Type Heading",
+DlgDocDocTypeOther : "Alt Document Type Heading",
+DlgDocIncXHTML : "Include declaraţii XHTML",
+DlgDocBgColor : "Culoarea fundalului (Background Color)",
+DlgDocBgImage : "URL-ul imaginii din fundal (Background Image URL)",
+DlgDocBgNoScroll : "Fundal neflotant, fix (Nonscrolling Background)",
+DlgDocCText : "Text",
+DlgDocCLink : "Link (Legătură web)",
+DlgDocCVisited : "Link (Legătură web) vizitat",
+DlgDocCActive : "Link (Legătură web) activ",
+DlgDocMargins : "Marginile paginii",
+DlgDocMaTop : "Sus",
+DlgDocMaLeft : "Stânga",
+DlgDocMaRight : "Dreapta",
+DlgDocMaBottom : "Jos",
+DlgDocMeIndex : "Cuvinte cheie după care se va indexa documentul (separate prin virgulă)",
+DlgDocMeDescr : "Descrierea documentului",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Drepturi de autor",
+DlgDocPreview : "Previzualizare",
+
+// Templates Dialog
+Templates : "Template-uri (ÅŸabloane)",
+DlgTemplatesTitle : "Template-uri (şabloane) de conţinut",
+DlgTemplatesSelMsg : "Vă rugăm selectaţi template-ul (şablonul) ce se va deschide în editor<br>(conţinutul actual va fi pierdut):",
+DlgTemplatesLoading : "Se încarcă lista cu template-uri (şabloane). Vă rugăm aşteptaţi...",
+DlgTemplatesNoTpl : "(Niciun template (ÅŸablon) definit)",
+DlgTemplatesReplace : "ÃŽnlocuieÅŸte cuprinsul actual",
+
+// About Dialog
+DlgAboutAboutTab : "Despre",
+DlgAboutBrowserInfoTab : "Informaţii browser",
+DlgAboutLicenseTab : "Licenţă",
+DlgAboutVersion : "versiune",
+DlgAboutInfo : "Pentru informaţii amănunţite, vizitaţi",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/ru.js b/httemplate/elements/fckeditor/editor/lang/ru.js
new file mode 100644
index 0000000..fb0267f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/ru.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Russian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Свернуть панель инÑтрументов",
+ToolbarExpand : "Развернуть панель инÑтрументов",
+
+// Toolbar Items and Context Menu
+Save : "Сохранить",
+NewPage : "ÐÐ¾Ð²Ð°Ñ Ñтраница",
+Preview : "Предварительный проÑмотр",
+Cut : "Вырезать",
+Copy : "Копировать",
+Paste : "Ð’Ñтавить",
+PasteText : "Ð’Ñтавить только текÑÑ‚",
+PasteWord : "Ð’Ñтавить из Word",
+Print : "Печать",
+SelectAll : "Выделить вÑе",
+RemoveFormat : "Убрать форматирование",
+InsertLinkLbl : "СÑылка",
+InsertLink : "Ð’Ñтавить/Редактировать ÑÑылку",
+RemoveLink : "Убрать ÑÑылку",
+VisitLink : "Перейти по ÑÑылке",
+Anchor : "Ð’Ñтавить/Редактировать Ñкорь",
+AnchorDelete : "Убрать Ñкорь",
+InsertImageLbl : "Изображение",
+InsertImage : "Ð’Ñтавить/Редактировать изображение",
+InsertFlashLbl : "Flash",
+InsertFlash : "Ð’Ñтавить/Редактировать Flash",
+InsertTableLbl : "Таблица",
+InsertTable : "Ð’Ñтавить/Редактировать таблицу",
+InsertLineLbl : "ЛиниÑ",
+InsertLine : "Ð’Ñтавить горизонтальную линию",
+InsertSpecialCharLbl: "Специальный Ñимвол",
+InsertSpecialChar : "Ð’Ñтавить Ñпециальный Ñимвол",
+InsertSmileyLbl : "Смайлик",
+InsertSmiley : "Ð’Ñтавить Ñмайлик",
+About : "О FCKeditor",
+Bold : "Жирный",
+Italic : "КурÑив",
+Underline : "Подчеркнутый",
+StrikeThrough : "Зачеркнутый",
+Subscript : "ПодÑтрочный индекÑ",
+Superscript : "ÐадÑтрочный индекÑ",
+LeftJustify : "По левому краю",
+CenterJustify : "По центру",
+RightJustify : "По правому краю",
+BlockJustify : "По ширине",
+DecreaseIndent : "Уменьшить отÑтуп",
+IncreaseIndent : "Увеличить отÑтуп",
+Blockquote : "Цитата",
+CreateDiv : "Создать Div контейнер",
+EditDiv : "Редактировать Div контейнер",
+DeleteDiv : "Удалить Div контейнер",
+Undo : "Отменить",
+Redo : "Повторить",
+NumberedListLbl : "Ðумерованный ÑпиÑок",
+NumberedList : "Ð’Ñтавить/Удалить нумерованный ÑпиÑок",
+BulletedListLbl : "Маркированный ÑпиÑок",
+BulletedList : "Ð’Ñтавить/Удалить маркированный ÑпиÑок",
+ShowTableBorders : "Показать бордюры таблицы",
+ShowDetails : "Показать детали",
+Style : "Стиль",
+FontFormat : "Форматирование",
+Font : "Шрифт",
+FontSize : "Размер",
+TextColor : "Цвет текÑта",
+BGColor : "Цвет фона",
+Source : "ИÑточник",
+Find : "Ðайти",
+Replace : "Заменить",
+SpellCheck : "Проверить орфографию",
+UniversalKeyboard : "УниверÑÐ°Ð»ÑŒÐ½Ð°Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ð°",
+PageBreakLbl : "Разрыв Ñтраницы",
+PageBreak : "Ð’Ñтавить разрыв Ñтраницы",
+
+Form : "Форма",
+Checkbox : "Ð¤Ð»Ð°Ð³Ð¾Ð²Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÐ°",
+RadioButton : "Кнопка выбора",
+TextField : "ТекÑтовое поле",
+Textarea : "ТекÑÑ‚Ð¾Ð²Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ",
+HiddenField : "Скрытое поле",
+Button : "Кнопка",
+SelectionField : "СпиÑок",
+ImageButton : "Кнопка Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼",
+
+FitWindow : "Развернуть окно редактора",
+ShowBlocks : "Показать блоки",
+
+// Context Menu
+EditLink : "Ð’Ñтавить ÑÑылку",
+CellCM : "Ячейка",
+RowCM : "Строка",
+ColumnCM : "Колонка",
+InsertRowAfter : "Ð’Ñтавить Ñтроку поÑле",
+InsertRowBefore : "Ð’Ñтавить Ñтроку до",
+DeleteRows : "Удалить Ñтроки",
+InsertColumnAfter : "Ð’Ñтавить колонку поÑле",
+InsertColumnBefore : "Ð’Ñтавить колонку до",
+DeleteColumns : "Удалить колонки",
+InsertCellAfter : "Ð’Ñтавить Ñчейку поÑле",
+InsertCellBefore : "Ð’Ñтавить Ñчейку до",
+DeleteCells : "Удалить Ñчейки",
+MergeCells : "Соединить Ñчейки",
+MergeRight : "Соединить вправо",
+MergeDown : "Соединить вниз",
+HorizontalSplitCell : "Разбить Ñчейку горизонтально",
+VerticalSplitCell : "Разбить Ñчейку вертикально",
+TableDelete : "Удалить таблицу",
+CellProperties : "СвойÑтва Ñчейки",
+TableProperties : "СвойÑтва таблицы",
+ImageProperties : "СвойÑтва изображениÑ",
+FlashProperties : "СвойÑтва Flash",
+
+AnchorProp : "СвойÑтва ÑкорÑ",
+ButtonProp : "СвойÑтва кнопки",
+CheckboxProp : "СвойÑтва флаговой кнопки",
+HiddenFieldProp : "СвойÑтва Ñкрытого полÑ",
+RadioButtonProp : "СвойÑтва кнопки выбора",
+ImageButtonProp : "СвойÑтва кнопки Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼",
+TextFieldProp : "СвойÑтва текÑтового полÑ",
+SelectionFieldProp : "СвойÑтва ÑпиÑка",
+TextareaProp : "СвойÑтва текÑтовой облаÑти",
+FormProp : "СвойÑтва формы",
+
+FontFormats : "Ðормальный;Форматированный;ÐдреÑ;Заголовок 1;Заголовок 2;Заголовок 3;Заголовок 4;Заголовок 5;Заголовок 6;Ðормальный (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Обработка XHTML. ПожалуйÑта, подождите...",
+Done : "Сделано",
+PasteWordConfirm : "ТекÑÑ‚, который вы хотите вÑтавить, похож на копируемый из Word. Ð’Ñ‹ хотите очиÑтить его перед вÑтавкой?",
+NotCompatiblePaste : "Эта команда доÑтупна Ð´Ð»Ñ Internet Explorer верÑии 5.5 или выше. Ð’Ñ‹ хотите вÑтавить без очиÑтки?",
+UnknownToolbarItem : "Ðе извеÑтный Ñлемент панели инÑтрументов \"%1\"",
+UnknownCommand : "Ðе извеÑтное Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ‹ \"%1\"",
+NotImplemented : "Команда не реализована",
+UnknownToolbarSet : "Панель инÑтрументов \"%1\" не ÑущеÑтвует",
+NoActiveX : "ÐаÑтройки безопаÑноÑти вашего браузера могут ограничивать некоторые ÑвойÑтва редактора. Ð’Ñ‹ должны включить опцию \"ЗапуÑкать Ñлементы ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ActiveX и плугины\". Ð’Ñ‹ можете видеть ошибки и замечать отÑутÑтвие возможноÑтей.",
+BrowseServerBlocked : "РеÑурÑÑ‹ браузера не могут быть открыты. Проверьте что блокировки вÑплывающих окон выключены.",
+DialogBlocked : "Ðевозможно открыть окно диалога. Проверьте что блокировки вÑплывающих окон выключены.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ОК",
+DlgBtnCancel : "Отмена",
+DlgBtnClose : "Закрыть",
+DlgBtnBrowseServer : "ПроÑмотреть на Ñервере",
+DlgAdvancedTag : "РаÑширенный",
+DlgOpOther : "<Другое>",
+DlgInfoTab : "ИнформациÑ",
+DlgAlertUrl : "ПожалуйÑта, вÑтавьте URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<не определено>",
+DlgGenId : "Идентификатор",
+DlgGenLangDir : "Ðаправление Ñзыка",
+DlgGenLangDirLtr : "Слева на право (LTR)",
+DlgGenLangDirRtl : "Справа на лево (RTL)",
+DlgGenLangCode : "Язык",
+DlgGenAccessKey : "ГорÑÑ‡Ð°Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ°",
+DlgGenName : "ИмÑ",
+DlgGenTabIndex : "ПоÑледовательноÑÑ‚ÑŒ перехода",
+DlgGenLongDescr : "Длинное опиÑание URL",
+DlgGenClass : "КлаÑÑ CSS",
+DlgGenTitle : "Заголовок",
+DlgGenContType : "Тип Ñодержимого",
+DlgGenLinkCharset : "Кодировка",
+DlgGenStyle : "Стиль CSS",
+
+// Image Dialog
+DlgImgTitle : "СвойÑтва изображениÑ",
+DlgImgInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ изображении",
+DlgImgBtnUpload : "ПоÑлать на Ñервер",
+DlgImgURL : "URL",
+DlgImgUpload : "Закачать",
+DlgImgAlt : "Ðльтернативный текÑÑ‚",
+DlgImgWidth : "Ширина",
+DlgImgHeight : "Ð’Ñ‹Ñота",
+DlgImgLockRatio : "СохранÑÑ‚ÑŒ пропорции",
+DlgBtnResetSize : "СброÑить размер",
+DlgImgBorder : "Бордюр",
+DlgImgHSpace : "Горизонтальный отÑтуп",
+DlgImgVSpace : "Вертикальный отÑтуп",
+DlgImgAlign : "Выравнивание",
+DlgImgAlignLeft : "По левому краю",
+DlgImgAlignAbsBottom: "ÐÐ±Ñ Ð¿Ð¾Ð½Ð¸Ð·Ñƒ",
+DlgImgAlignAbsMiddle: "ÐÐ±Ñ Ð¿Ð¾Ñередине",
+DlgImgAlignBaseline : "По базовой линии",
+DlgImgAlignBottom : "Понизу",
+DlgImgAlignMiddle : "ПоÑередине",
+DlgImgAlignRight : "По правому краю",
+DlgImgAlignTextTop : "ТекÑÑ‚ наверху",
+DlgImgAlignTop : "По верху",
+DlgImgPreview : "Предварительный проÑмотр",
+DlgImgAlertUrl : "ПожалуйÑта, введите URL изображениÑ",
+DlgImgLinkTab : "СÑылка",
+
+// Flash Dialog
+DlgFlashTitle : "СвойÑтва Flash",
+DlgFlashChkPlay : "Ðвто проигрывание",
+DlgFlashChkLoop : "Повтор",
+DlgFlashChkMenu : "Включить меню Flash",
+DlgFlashScale : "МаÑштабировать",
+DlgFlashScaleAll : "Показывать вÑе",
+DlgFlashScaleNoBorder : "Без бордюра",
+DlgFlashScaleFit : "Точное Ñовпадение",
+
+// Link Dialog
+DlgLnkWindowTitle : "СÑылка",
+DlgLnkInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ ÑÑылки",
+DlgLnkTargetTab : "Цель",
+
+DlgLnkType : "Тип ÑÑылки",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Якорь на Ñту Ñтраницу",
+DlgLnkTypeEMail : "Эл. почта",
+DlgLnkProto : "Протокол",
+DlgLnkProtoOther : "<другое>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Выберите Ñкорь",
+DlgLnkAnchorByName : "По имени ÑкорÑ",
+DlgLnkAnchorById : "По идентификатору Ñлемента",
+DlgLnkNoAnchors : "(Ðет Ñкорей доÑтупных в Ñтом документе)",
+DlgLnkEMail : "ÐÐ´Ñ€ÐµÑ Ñл. почты",
+DlgLnkEMailSubject : "Заголовок ÑообщениÑ",
+DlgLnkEMailBody : "Тело ÑообщениÑ",
+DlgLnkUpload : "Закачать",
+DlgLnkBtnUpload : "ПоÑлать на Ñервер",
+
+DlgLnkTarget : "Цель",
+DlgLnkTargetFrame : "<фрейм>",
+DlgLnkTargetPopup : "<вÑплывающее окно>",
+DlgLnkTargetBlank : "Ðовое окно (_blank)",
+DlgLnkTargetParent : "РодительÑкое окно (_parent)",
+DlgLnkTargetSelf : "Тоже окно (_self)",
+DlgLnkTargetTop : "Самое верхнее окно (_top)",
+DlgLnkTargetFrameName : "Ð˜Ð¼Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð³Ð¾ фрейма",
+DlgLnkPopWinName : "Ð˜Ð¼Ñ Ð²Ñплывающего окна",
+DlgLnkPopWinFeat : "СвойÑтва вÑплывающего окна",
+DlgLnkPopResize : "ИзменÑющееÑÑ Ð² размерах",
+DlgLnkPopLocation : "Панель локации",
+DlgLnkPopMenu : "Панель меню",
+DlgLnkPopScroll : "ПолоÑÑ‹ прокрутки",
+DlgLnkPopStatus : "Строка ÑоÑтоÑниÑ",
+DlgLnkPopToolbar : "Панель инÑтрументов",
+DlgLnkPopFullScrn : "Полный Ñкран (IE)",
+DlgLnkPopDependent : "ЗавиÑимый (Netscape)",
+DlgLnkPopWidth : "Ширина",
+DlgLnkPopHeight : "Ð’Ñ‹Ñота",
+DlgLnkPopLeft : "ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ñлева",
+DlgLnkPopTop : "ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ñверху",
+
+DlnLnkMsgNoUrl : "ПожалуйÑта, введите URL ÑÑылки",
+DlnLnkMsgNoEMail : "ПожалуйÑта, введите Ð°Ð´Ñ€ÐµÑ Ñл. почты",
+DlnLnkMsgNoAnchor : "ПожалуйÑта, выберете Ñкорь",
+DlnLnkMsgInvPopName : "Ðазвание вÑпывающего окна должно начинатьÑÑ Ð±ÑƒÐºÐ²Ñ‹ и не может Ñодержать пробелов",
+
+// Color Dialog
+DlgColorTitle : "Выберите цвет",
+DlgColorBtnClear : "ОчиÑтить",
+DlgColorHighlight : "ПодÑвеченный",
+DlgColorSelected : "Выбранный",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ð’Ñтавить Ñмайлик",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Выберите Ñпециальный Ñимвол",
+
+// Table Dialog
+DlgTableTitle : "СвойÑтва таблицы",
+DlgTableRows : "Строки",
+DlgTableColumns : "Колонки",
+DlgTableBorder : "Размер бордюра",
+DlgTableAlign : "Выравнивание",
+DlgTableAlignNotSet : "<Ðе уÑÑ‚.>",
+DlgTableAlignLeft : "Слева",
+DlgTableAlignCenter : "По центру",
+DlgTableAlignRight : "Справа",
+DlgTableWidth : "Ширина",
+DlgTableWidthPx : "пикÑелей",
+DlgTableWidthPc : "процентов",
+DlgTableHeight : "Ð’Ñ‹Ñота",
+DlgTableCellSpace : "Промежуток (spacing)",
+DlgTableCellPad : "ОтÑтуп (padding)",
+DlgTableCaption : "Заголовок",
+DlgTableSummary : "Резюме",
+DlgTableHeaders : "Заголовки",
+DlgTableHeadersNone : "Ðет",
+DlgTableHeadersColumn : "Первый Ñтолбец",
+DlgTableHeadersRow : "ÐŸÐµÑ€Ð²Ð°Ñ Ñтрока",
+DlgTableHeadersBoth : "Оба варианта",
+
+// Table Cell Dialog
+DlgCellTitle : "СвойÑтва Ñчейки",
+DlgCellWidth : "Ширина",
+DlgCellWidthPx : "пикÑелей",
+DlgCellWidthPc : "процентов",
+DlgCellHeight : "Ð’Ñ‹Ñота",
+DlgCellWordWrap : "Заворачивание текÑта",
+DlgCellWordWrapNotSet : "<Ðе уÑÑ‚.>",
+DlgCellWordWrapYes : "Да",
+DlgCellWordWrapNo : "Ðет",
+DlgCellHorAlign : "Гор. выравнивание",
+DlgCellHorAlignNotSet : "<Ðе уÑÑ‚.>",
+DlgCellHorAlignLeft : "Слева",
+DlgCellHorAlignCenter : "По центру",
+DlgCellHorAlignRight: "Справа",
+DlgCellVerAlign : "Верт. выравнивание",
+DlgCellVerAlignNotSet : "<Ðе уÑÑ‚.>",
+DlgCellVerAlignTop : "Сверху",
+DlgCellVerAlignMiddle : "ПоÑередине",
+DlgCellVerAlignBottom : "Снизу",
+DlgCellVerAlignBaseline : "По базовой линии",
+DlgCellType : "Тип Ñчейки",
+DlgCellTypeData : "Данные",
+DlgCellTypeHeader : "Заголовок",
+DlgCellRowSpan : "Диапазон Ñтрок (span)",
+DlgCellCollSpan : "Диапазон колонок (span)",
+DlgCellBackColor : "Цвет фона",
+DlgCellBorderColor : "Цвет бордюра",
+DlgCellBtnSelect : "Выберите...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Ðайти и заменить",
+
+// Find Dialog
+DlgFindTitle : "Ðайти",
+DlgFindFindBtn : "Ðайти",
+DlgFindNotFoundMsg : "Указанный текÑÑ‚ не найден.",
+
+// Replace Dialog
+DlgReplaceTitle : "Заменить",
+DlgReplaceFindLbl : "Ðайти:",
+DlgReplaceReplaceLbl : "Заменить на:",
+DlgReplaceCaseChk : "Учитывать региÑÑ‚Ñ€",
+DlgReplaceReplaceBtn : "Заменить",
+DlgReplaceReplAllBtn : "Заменить вÑе",
+DlgReplaceWordChk : "Совпадение целых Ñлов",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ÐаÑтройки безопаÑноÑти вашего браузера не позволÑÑŽÑ‚ редактору автоматичеÑки выполнÑÑ‚ÑŒ операции вырезаниÑ. ПожалуйÑта, иÑпользуйте клавиатуру Ð´Ð»Ñ Ñтого (Ctrl+X).",
+PasteErrorCopy : "ÐаÑтройки безопаÑноÑти вашего браузера не позволÑÑŽÑ‚ редактору автоматичеÑки выполнÑÑ‚ÑŒ операции копированиÑ. ПожалуйÑта, иÑпользуйте клавиатуру Ð´Ð»Ñ Ñтого (Ctrl+C).",
+
+PasteAsText : "Ð’Ñтавить только текÑÑ‚",
+PasteFromWord : "Ð’Ñтавить из Word",
+
+DlgPasteMsg2 : "ПожалуйÑта, вÑтавьте текÑÑ‚ в прÑмоугольник, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ñочетание клавиш (<STRONG>Ctrl+V</STRONG>), и нажмите <STRONG>OK</STRONG>.",
+DlgPasteSec : "По причине наÑтроек безопаÑноÑти браузера, редактор не имеет доÑтупа к данным буфера обмена напрÑмую. Вам необходимо вÑтавить текÑÑ‚ Ñнова в Ñто окно.",
+DlgPasteIgnoreFont : "Игнорировать Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð³Ð°Ñ€Ð½Ð¸Ñ‚ÑƒÑ€Ñ‹",
+DlgPasteRemoveStyles : "Убрать Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñтилей",
+
+// Color Picker
+ColorAutomatic : "ÐвтоматичеÑкий",
+ColorMoreColors : "Цвета...",
+
+// Document Properties
+DocProps : "СвойÑтва документа",
+
+// Anchor Dialog
+DlgAnchorTitle : "СвойÑтва ÑкорÑ",
+DlgAnchorName : "Ð˜Ð¼Ñ ÑкорÑ",
+DlgAnchorErrorName : "ПожалуйÑта, введите Ð¸Ð¼Ñ ÑкорÑ",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ðет в Ñловаре",
+DlgSpellChangeTo : "Заменить на",
+DlgSpellBtnIgnore : "Игнорировать",
+DlgSpellBtnIgnoreAll : "Игнорировать вÑе",
+DlgSpellBtnReplace : "Заменить",
+DlgSpellBtnReplaceAll : "Заменить вÑе",
+DlgSpellBtnUndo : "Отменить",
+DlgSpellNoSuggestions : "- Ðет предположений -",
+DlgSpellProgress : "Идет проверка орфографии...",
+DlgSpellNoMispell : "Проверка орфографии закончена: ошибок не найдено",
+DlgSpellNoChanges : "Проверка орфографии закончена: ни одного Ñлова не изменено",
+DlgSpellOneChange : "Проверка орфографии закончена: одно Ñлово изменено",
+DlgSpellManyChanges : "Проверка орфографии закончена: 1% Ñлов изменен",
+
+IeSpellDownload : "Модуль проверки орфографии не уÑтановлен. Хотите Ñкачать его ÑейчаÑ?",
+
+// Button Dialog
+DlgButtonText : "ТекÑÑ‚ (Значение)",
+DlgButtonType : "Тип",
+DlgButtonTypeBtn : "Кнопка",
+DlgButtonTypeSbm : "Отправить",
+DlgButtonTypeRst : "СброÑить",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ИмÑ",
+DlgCheckboxValue : "Значение",
+DlgCheckboxSelected : "ВыбраннаÑ",
+
+// Form Dialog
+DlgFormName : "ИмÑ",
+DlgFormAction : "ДейÑтвие",
+DlgFormMethod : "Метод",
+
+// Select Field Dialog
+DlgSelectName : "ИмÑ",
+DlgSelectValue : "Значение",
+DlgSelectSize : "Размер",
+DlgSelectLines : "линии",
+DlgSelectChkMulti : "Разрешить множеÑтвенный выбор",
+DlgSelectOpAvail : "ДоÑтупные варианты",
+DlgSelectOpText : "ТекÑÑ‚",
+DlgSelectOpValue : "Значение",
+DlgSelectBtnAdd : "Добавить",
+DlgSelectBtnModify : "Модифицировать",
+DlgSelectBtnUp : "Вверх",
+DlgSelectBtnDown : "Вниз",
+DlgSelectBtnSetValue : "УÑтановить как выбранное значение",
+DlgSelectBtnDelete : "Удалить",
+
+// Textarea Dialog
+DlgTextareaName : "ИмÑ",
+DlgTextareaCols : "Колонки",
+DlgTextareaRows : "Строки",
+
+// Text Field Dialog
+DlgTextName : "ИмÑ",
+DlgTextValue : "Значение",
+DlgTextCharWidth : "Ширина",
+DlgTextMaxChars : "МакÑ. кол-во Ñимволов",
+DlgTextType : "Тип",
+DlgTextTypeText : "ТекÑÑ‚",
+DlgTextTypePass : "Пароль",
+
+// Hidden Field Dialog
+DlgHiddenName : "ИмÑ",
+DlgHiddenValue : "Значение",
+
+// Bulleted List Dialog
+BulletedListProp : "СвойÑтва маркированного ÑпиÑка",
+NumberedListProp : "СвойÑтва нумерованного ÑпиÑка",
+DlgLstStart : "Ðачало",
+DlgLstType : "Тип",
+DlgLstTypeCircle : "Круг",
+DlgLstTypeDisc : "ДиÑк",
+DlgLstTypeSquare : "Квадрат",
+DlgLstTypeNumbers : "Ðомера (1, 2, 3)",
+DlgLstTypeLCase : "Буквы нижнего региÑтра (a, b, c)",
+DlgLstTypeUCase : "Буквы верхнего региÑтра (A, B, C)",
+DlgLstTypeSRoman : "Малые римÑкие буквы (i, ii, iii)",
+DlgLstTypeLRoman : "Большие римÑкие буквы (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Общие",
+DlgDocBackTab : "Задний фон",
+DlgDocColorsTab : "Цвета и отÑтупы",
+DlgDocMetaTab : "Мета данные",
+
+DlgDocPageTitle : "Заголовок Ñтраницы",
+DlgDocLangDir : "Ðаправление текÑта",
+DlgDocLangDirLTR : "Слева направо (LTR)",
+DlgDocLangDirRTL : "Справа налево (RTL)",
+DlgDocLangCode : "Код Ñзыка",
+DlgDocCharSet : "Кодировка набора Ñимволов",
+DlgDocCharSetCE : "Центрально-европейÑкаÑ",
+DlgDocCharSetCT : "КитайÑÐºÐ°Ñ Ñ‚Ñ€Ð°Ð´Ð¸Ñ†Ð¸Ð¾Ð½Ð½Ð°Ñ (Big5)",
+DlgDocCharSetCR : "Кириллица",
+DlgDocCharSetGR : "ГречеÑкаÑ",
+DlgDocCharSetJP : "ЯпонÑкаÑ",
+DlgDocCharSetKR : "КорейÑкаÑ",
+DlgDocCharSetTR : "ТурецкаÑ",
+DlgDocCharSetUN : "Юникод (UTF-8)",
+DlgDocCharSetWE : "Западно-европейÑкаÑ",
+DlgDocCharSetOther : "Ð”Ñ€ÑƒÐ³Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° набора Ñимволов",
+
+DlgDocDocType : "Заголовок типа документа",
+DlgDocDocTypeOther : "Другой заголовок типа документа",
+DlgDocIncXHTML : "Включить XHTML объÑвлениÑ",
+DlgDocBgColor : "Цвет фона",
+DlgDocBgImage : "URL Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ„Ð¾Ð½Ð°",
+DlgDocBgNoScroll : "ÐеÑкроллируемый фон",
+DlgDocCText : "ТекÑÑ‚",
+DlgDocCLink : "СÑылка",
+DlgDocCVisited : "ПоÑÐµÑ‰ÐµÐ½Ð½Ð°Ñ ÑÑылка",
+DlgDocCActive : "ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ ÑÑылка",
+DlgDocMargins : "ОтÑтупы Ñтраницы",
+DlgDocMaTop : "Верхний",
+DlgDocMaLeft : "Левый",
+DlgDocMaRight : "Правый",
+DlgDocMaBottom : "Ðижний",
+DlgDocMeIndex : "Ключевые Ñлова документа (разделенные запÑтой)",
+DlgDocMeDescr : "ОпиÑание документа",
+DlgDocMeAuthor : "Ðвтор",
+DlgDocMeCopy : "ÐвторÑкие права",
+DlgDocPreview : "Предварительный проÑмотр",
+
+// Templates Dialog
+Templates : "Шаблоны",
+DlgTemplatesTitle : "Шаблоны Ñодержимого",
+DlgTemplatesSelMsg : "ПожалуйÑта, выберете шаблон Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ð² редакторе<br>(текущее Ñодержимое будет потерÑно):",
+DlgTemplatesLoading : "Загрузка ÑпиÑка шаблонов. ПожалуйÑта, подождите...",
+DlgTemplatesNoTpl : "(Ðи одного шаблона не определено)",
+DlgTemplatesReplace : "Заменить текущее Ñодержание",
+
+// About Dialog
+DlgAboutAboutTab : "О программе",
+DlgAboutBrowserInfoTab : "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð°",
+DlgAboutLicenseTab : "ЛицензиÑ",
+DlgAboutVersion : "ВерÑиÑ",
+DlgAboutInfo : "Ð”Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐµÐ¹ информации, поÑетите",
+
+// Div Dialog
+DlgDivGeneralTab : "ИнформациÑ",
+DlgDivAdvancedTab : "РаÑширенные наÑтройки",
+DlgDivStyle : "Стиль",
+DlgDivInlineStyle : "Ð’Ñтроенные Ñтили",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/sk.js b/httemplate/elements/fckeditor/editor/lang/sk.js
new file mode 100644
index 0000000..ff1783c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/sk.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Slovak language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Skryť panel nástrojov",
+ToolbarExpand : "Zobraziť panel nástrojov",
+
+// Toolbar Items and Context Menu
+Save : "Uložiť",
+NewPage : "Nová stránka",
+Preview : "Náhľad",
+Cut : "Vystrihnúť",
+Copy : "Kopírovať",
+Paste : "Vložiť",
+PasteText : "VložiÅ¥ ako Äistý text",
+PasteWord : "Vložiť z Wordu",
+Print : "TlaÄ",
+SelectAll : "Vybrať všetko",
+RemoveFormat : "Odstrániť formátovanie",
+InsertLinkLbl : "Odkaz",
+InsertLink : "Vložiť/zmeniť odkaz",
+RemoveLink : "Odstrániť odkaz",
+VisitLink : "ÃsÅ¥ na odkaz",
+Anchor : "Vložiť/zmeniť kotvu",
+AnchorDelete : "Odstrániť kotvu",
+InsertImageLbl : "Obrázok",
+InsertImage : "Vložiť/zmeniť obrázok",
+InsertFlashLbl : "Flash",
+InsertFlash : "Vložiť/zmeniť Flash",
+InsertTableLbl : "Tabuľka",
+InsertTable : "Vložiť/zmeniť tabuľku",
+InsertLineLbl : "ÄŒiara",
+InsertLine : "VložiÅ¥ vodorovnú Äiaru",
+InsertSpecialCharLbl: "Špeciálne znaky",
+InsertSpecialChar : "Vložiť špeciálne znaky",
+InsertSmileyLbl : "Smajlíky",
+InsertSmiley : "Vložiť smajlíka",
+About : "O aplikácii FCKeditor",
+Bold : "TuÄné",
+Italic : "Kurzíva",
+Underline : "PodÄiarknuté",
+StrikeThrough : "PreÄiarknuté",
+Subscript : "Dolný index",
+Superscript : "Horný index",
+LeftJustify : "Zarovnať vľavo",
+CenterJustify : "Zarovnať na stred",
+RightJustify : "Zarovnať vpravo",
+BlockJustify : "Zarovnať do bloku",
+DecreaseIndent : "Zmenšiť odsadenie",
+IncreaseIndent : "ZväÄÅ¡iÅ¥ odsadenie",
+Blockquote : "Citácia",
+CreateDiv : "Vytvoriť Div kontajner",
+EditDiv : "Editovať Div kontajner",
+DeleteDiv : "Odstrániť Div kontajner",
+Undo : "Späť",
+Redo : "Znovu",
+NumberedListLbl : "Číslovanie",
+NumberedList : "VložiÅ¥/odstrániÅ¥ Äíslovanie",
+BulletedListLbl : "Odrážky",
+BulletedList : "Vložiť/odstraniť odrážky",
+ShowTableBorders : "Zobraziť okraje tabuliek",
+ShowDetails : "Zobraziť podrobnosti",
+Style : "Štýl",
+FontFormat : "Formát",
+Font : "Písmo",
+FontSize : "Veľkosť",
+TextColor : "Farba textu",
+BGColor : "Farba pozadia",
+Source : "Zdroj",
+Find : "Hľadať",
+Replace : "Nahradiť",
+SpellCheck : "Kontrola pravopisu",
+UniversalKeyboard : "Univerzálna klávesnica",
+PageBreakLbl : "OddeľovaÄ stránky",
+PageBreak : "VložiÅ¥ oddeľovaÄ stránky",
+
+Form : "Formulár",
+Checkbox : "ZaÅ¡krtávacie políÄko",
+RadioButton : "PrepínaÄ",
+TextField : "Textové pole",
+Textarea : "Textová oblasť",
+HiddenField : "Skryté pole",
+Button : "TlaÄidlo",
+SelectionField : "Rozbaľovací zoznam",
+ImageButton : "Obrázkové tlaÄidlo",
+
+FitWindow : "Maximalizovať veľkosť okna editora",
+ShowBlocks : "Ukázať bloky",
+
+// Context Menu
+EditLink : "Zmeniť odkaz",
+CellCM : "Bunka",
+RowCM : "Riadok",
+ColumnCM : "Stĺpec",
+InsertRowAfter : "Vložiť riadok pred",
+InsertRowBefore : "Vložiť riadok za",
+DeleteRows : "Vymazať riadok",
+InsertColumnAfter : "Vložiť stĺpec pred",
+InsertColumnBefore : "Vložiť stĺpec za",
+DeleteColumns : "Zmazať stĺpec",
+InsertCellAfter : "Vložiť bunku za",
+InsertCellBefore : "Vložiť bunku pred",
+DeleteCells : "Vymazať bunky",
+MergeCells : "ZlúÄiÅ¥ bunky",
+MergeRight : "ZlúÄiÅ¥ doprava",
+MergeDown : "ZlúÄiÅ¥ dole",
+HorizontalSplitCell : "Rozdeliť bunky horizontálne",
+VerticalSplitCell : "Rozdeliť bunky vertikálne",
+TableDelete : "Vymazať tabuľku",
+CellProperties : "Vlastnosti bunky",
+TableProperties : "Vlastnosti tabuľky",
+ImageProperties : "Vlastnosti obrázku",
+FlashProperties : "Vlastnosti Flashu",
+
+AnchorProp : "Vlastnosti kotvy",
+ButtonProp : "Vlastnosti tlaÄidla",
+CheckboxProp : "Vlastnosti zaÅ¡krtávacieho políÄka",
+HiddenFieldProp : "Vlastnosti skrytého poľa",
+RadioButtonProp : "Vlastnosti prepínaÄa",
+ImageButtonProp : "Vlastnosti obrázkového tlaÄidla",
+TextFieldProp : "Vlastnosti textového poľa",
+SelectionFieldProp : "Vlastnosti rozbaľovacieho zoznamu",
+TextareaProp : "Vlastnosti textovej oblasti",
+FormProp : "Vlastnosti formulára",
+
+FontFormats : "Normálny;Formátovaný;Adresa;Nadpis 1;Nadpis 2;Nadpis 3;Nadpis 4;Nadpis 5;Nadpis 6;Odsek (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Prebieha spracovanie XHTML. Čakajte prosím...",
+Done : "DokonÄené.",
+PasteWordConfirm : "Vyzerá to tak, že vkladaný text je kopírovaný z Wordu. Chcete ho pred vložením vyÄistiÅ¥?",
+NotCompatiblePaste : "Tento príkaz je dostupný len v prehliadaÄi Internet Explorer verzie 5.5 alebo vyÅ¡Å¡ej. Chcete vložiÅ¥ text bez vyÄistenia?",
+UnknownToolbarItem : "Neznáma položka panela nástrojov \"%1\"",
+UnknownCommand : "Neznámy príkaz \"%1\"",
+NotImplemented : "Príkaz nie je implementovaný",
+UnknownToolbarSet : "Panel nástrojov \"%1\" neexistuje",
+NoActiveX : "BezpeÄnostné nastavenia vášho prehliadaÄa môžu obmedzovaÅ¥ niektoré funkcie editora. Pre ich plnú funkÄnosÅ¥ musíte zapnúť voľbu \"SpúšťaÅ¥ ActiveX moduly a zásuvné moduly\", inak sa môžete stretnúť s chybami a nefunkÄnosÅ¥ou niektorých funkcií.",
+BrowseServerBlocked : "PrehliadaÄ zdrojových prvkov nebolo možné otvoriÅ¥. Uistite sa, že máte vypnutú službu blokovania popup okien.",
+DialogBlocked : "Dialógové okno nebolo možné otvoriť. Uistite sa, že máte vypnutú službu blokovania popup okien.",
+VisitLinkBlocked : "Nebolo možné otvoriť nové okno. Uistite sa, že máte vypnutú službu blokovania popup okien.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Zrušiť",
+DlgBtnClose : "Zavrieť",
+DlgBtnBrowseServer : "Prechádzať server",
+DlgAdvancedTag : "Rozšírené",
+DlgOpOther : "<Ďalšie>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Prosím vložte URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nenastavené>",
+DlgGenId : "Id",
+DlgGenLangDir : "Orientácia jazyka",
+DlgGenLangDirLtr : "Zľava doprava (LTR)",
+DlgGenLangDirRtl : "Sprava doľava (RTL)",
+DlgGenLangCode : "Kód jazyka",
+DlgGenAccessKey : "Prístupový kľúÄ",
+DlgGenName : "Meno",
+DlgGenTabIndex : "Poradie prvku",
+DlgGenLongDescr : "Dlhý popis URL",
+DlgGenClass : "Trieda štýlu",
+DlgGenTitle : "Pomocný titulok",
+DlgGenContType : "Pomocný typ obsahu",
+DlgGenLinkCharset : "Priradená znaková sada",
+DlgGenStyle : "Štýl",
+
+// Image Dialog
+DlgImgTitle : "Vlastnosti obrázku",
+DlgImgInfoTab : "Informácie o obrázku",
+DlgImgBtnUpload : "Odoslať na server",
+DlgImgURL : "URL",
+DlgImgUpload : "Odoslať",
+DlgImgAlt : "Alternatívny text",
+DlgImgWidth : "Šírka",
+DlgImgHeight : "Výška",
+DlgImgLockRatio : "Zámok",
+DlgBtnResetSize : "Pôvodná veľkosť",
+DlgImgBorder : "Okraje",
+DlgImgHSpace : "H-medzera",
+DlgImgVSpace : "V-medzera",
+DlgImgAlign : "Zarovnanie",
+DlgImgAlignLeft : "Vľavo",
+DlgImgAlignAbsBottom: "Úplne dole",
+DlgImgAlignAbsMiddle: "Do stredu",
+DlgImgAlignBaseline : "Na základňu",
+DlgImgAlignBottom : "Dole",
+DlgImgAlignMiddle : "Na stred",
+DlgImgAlignRight : "Vpravo",
+DlgImgAlignTextTop : "Na horný okraj textu",
+DlgImgAlignTop : "Nahor",
+DlgImgPreview : "Náhľad",
+DlgImgAlertUrl : "Zadajte prosím URL obrázku",
+DlgImgLinkTab : "Odkaz",
+
+// Flash Dialog
+DlgFlashTitle : "Vlastnosti Flashu",
+DlgFlashChkPlay : "Automatické prehrávanie",
+DlgFlashChkLoop : "Opakovanie",
+DlgFlashChkMenu : "Povoliť Flash Menu",
+DlgFlashScale : "Mierka",
+DlgFlashScaleAll : "Zobraziť mierku",
+DlgFlashScaleNoBorder : "Bez okrajov",
+DlgFlashScaleFit : "Roztiahnuť na celé",
+
+// Link Dialog
+DlgLnkWindowTitle : "Odkaz",
+DlgLnkInfoTab : "Informácie o odkaze",
+DlgLnkTargetTab : "Cieľ",
+
+DlgLnkType : "Typ odkazu",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Kotva v tejto stránke",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<iný>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Vybrať kotvu",
+DlgLnkAnchorByName : "Podľa mena kotvy",
+DlgLnkAnchorById : "Podľa Id objektu",
+DlgLnkNoAnchors : "(V stránke nie je definovaná žiadna kotva)",
+DlgLnkEMail : "E-Mailová adresa",
+DlgLnkEMailSubject : "Predmet správy",
+DlgLnkEMailBody : "Telo správy",
+DlgLnkUpload : "Odoslať",
+DlgLnkBtnUpload : "Odoslať na server",
+
+DlgLnkTarget : "Cieľ",
+DlgLnkTargetFrame : "<rámec>",
+DlgLnkTargetPopup : "<vyskakovacie okno>",
+DlgLnkTargetBlank : "Nové okno (_blank)",
+DlgLnkTargetParent : "RodiÄovské okno (_parent)",
+DlgLnkTargetSelf : "Rovnaké okno (_self)",
+DlgLnkTargetTop : "Hlavné okno (_top)",
+DlgLnkTargetFrameName : "Meno rámu cieľa",
+DlgLnkPopWinName : "Názov vyskakovacieho okna",
+DlgLnkPopWinFeat : "Vlastnosti vyskakovacieho okna",
+DlgLnkPopResize : "Meniteľná veľkosť",
+DlgLnkPopLocation : "Panel umiestnenia",
+DlgLnkPopMenu : "Panel ponuky",
+DlgLnkPopScroll : "Posuvníky",
+DlgLnkPopStatus : "Stavový riadok",
+DlgLnkPopToolbar : "Panel nástrojov",
+DlgLnkPopFullScrn : "Celá obrazovka (IE)",
+DlgLnkPopDependent : "Závislosť (Netscape)",
+DlgLnkPopWidth : "Šírka",
+DlgLnkPopHeight : "Výška",
+DlgLnkPopLeft : "Ľavý okraj",
+DlgLnkPopTop : "Horný okraj",
+
+DlnLnkMsgNoUrl : "Zadajte prosím URL odkazu",
+DlnLnkMsgNoEMail : "Zadajte prosím e-mailovú adresu",
+DlnLnkMsgNoAnchor : "Vyberte prosím kotvu",
+DlnLnkMsgInvPopName : "Názov vyskakovacieho okna sa musá zaÄínaÅ¥ písmenom a nemôže obsahovaÅ¥ medzery",
+
+// Color Dialog
+DlgColorTitle : "Výber farby",
+DlgColorBtnClear : "Vymazať",
+DlgColorHighlight : "Zvýraznená",
+DlgColorSelected : "Vybraná",
+
+// Smiley Dialog
+DlgSmileyTitle : "Vkladanie smajlíkov",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Výber špeciálneho znaku",
+
+// Table Dialog
+DlgTableTitle : "Vlastnosti tabuľky",
+DlgTableRows : "Riadky",
+DlgTableColumns : "Stĺpce",
+DlgTableBorder : "OhraniÄenie",
+DlgTableAlign : "Zarovnanie",
+DlgTableAlignNotSet : "<nenastavené>",
+DlgTableAlignLeft : "Vľavo",
+DlgTableAlignCenter : "Na stred",
+DlgTableAlignRight : "Vpravo",
+DlgTableWidth : "Šírka",
+DlgTableWidthPx : "pixelov",
+DlgTableWidthPc : "percent",
+DlgTableHeight : "Výška",
+DlgTableCellSpace : "Vzdialenosť buniek",
+DlgTableCellPad : "Odsadenie obsahu",
+DlgTableCaption : "Popis",
+DlgTableSummary : "Prehľad",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Vlastnosti bunky",
+DlgCellWidth : "Šírka",
+DlgCellWidthPx : "bodov",
+DlgCellWidthPc : "percent",
+DlgCellHeight : "Výška",
+DlgCellWordWrap : "Zalamovannie",
+DlgCellWordWrapNotSet : "<nenastavené>",
+DlgCellWordWrapYes : "Ãno",
+DlgCellWordWrapNo : "Nie",
+DlgCellHorAlign : "Vodorovné zarovnanie",
+DlgCellHorAlignNotSet : "<nenastavené>",
+DlgCellHorAlignLeft : "Vľavo",
+DlgCellHorAlignCenter : "Na stred",
+DlgCellHorAlignRight: "Vpravo",
+DlgCellVerAlign : "Zvislé zarovnanie",
+DlgCellVerAlignNotSet : "<nenastavené>",
+DlgCellVerAlignTop : "Nahor",
+DlgCellVerAlignMiddle : "Doprostred",
+DlgCellVerAlignBottom : "Dole",
+DlgCellVerAlignBaseline : "Na základňu",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "ZlúÄené riadky",
+DlgCellCollSpan : "ZlúÄené stĺpce",
+DlgCellBackColor : "Farba pozadia",
+DlgCellBorderColor : "Farba ohraniÄenia",
+DlgCellBtnSelect : "Výber...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Nájsť a nahradiť",
+
+// Find Dialog
+DlgFindTitle : "Hľadať",
+DlgFindFindBtn : "Hľadať",
+DlgFindNotFoundMsg : "Hľadaný text nebol nájdený.",
+
+// Replace Dialog
+DlgReplaceTitle : "Nahradiť",
+DlgReplaceFindLbl : "Čo hľadať:",
+DlgReplaceReplaceLbl : "Čím nahradiť:",
+DlgReplaceCaseChk : "Rozlišovať malé/veľké písmená",
+DlgReplaceReplaceBtn : "Nahradiť",
+DlgReplaceReplAllBtn : "Nahradiť všetko",
+DlgReplaceWordChk : "Len celé slová",
+
+// Paste Operations / Dialog
+PasteErrorCut : "BezpeÄnostné nastavenia Vášho prehliadaÄa nedovoľujú editoru spustiÅ¥ funkciu pre vystrihnutie zvoleného textu do schránky. Prosím vystrihnite zvolený text do schránky pomocou klávesnice (Ctrl+X).",
+PasteErrorCopy : "BezpeÄnostné nastavenia Vášho prehliadaÄa nedovoľujú editoru spustiÅ¥ funkciu pre kopírovanie zvoleného textu do schránky. Prosím skopírujte zvolený text do schránky pomocou klávesnice (Ctrl+C).",
+
+PasteAsText : "VložiÅ¥ ako Äistý text",
+PasteFromWord : "Vložiť text z Wordu",
+
+DlgPasteMsg2 : "Prosím vložte nasledovný rámÄek použitím klávesnice (<STRONG>Ctrl+V</STRONG>) a stlaÄte <STRONG>OK</STRONG>.",
+DlgPasteSec : "BezpeÄnostné nastavenia Vášho prehliadaÄa nedovoľujú editoru pristupovaÅ¥ priamo k datám v schránke. Musíte ich vložiÅ¥ znovu do tohto okna.",
+DlgPasteIgnoreFont : "Ignorovať nastavenia typu písma",
+DlgPasteRemoveStyles : "Odstrániť formátovanie",
+
+// Color Picker
+ColorAutomatic : "Automaticky",
+ColorMoreColors : "Viac farieb...",
+
+// Document Properties
+DocProps : "Vlastnosti dokumentu",
+
+// Anchor Dialog
+DlgAnchorTitle : "Vlastnosti kotvy",
+DlgAnchorName : "Meno kotvy",
+DlgAnchorErrorName : "Zadajte prosím meno kotvy",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nie je v slovníku",
+DlgSpellChangeTo : "Zmeniť na",
+DlgSpellBtnIgnore : "Ignorovať",
+DlgSpellBtnIgnoreAll : "Ignorovať všetko",
+DlgSpellBtnReplace : "Prepísat",
+DlgSpellBtnReplaceAll : "Prepísat všetko",
+DlgSpellBtnUndo : "Späť",
+DlgSpellNoSuggestions : "- Žiadny návrh -",
+DlgSpellProgress : "Prebieha kontrola pravopisu...",
+DlgSpellNoMispell : "Kontrola pravopisu dokonÄená: bez chýb",
+DlgSpellNoChanges : "Kontrola pravopisu dokonÄená: žiadne slová nezmenené",
+DlgSpellOneChange : "Kontrola pravopisu dokonÄená: zmenené jedno slovo",
+DlgSpellManyChanges : "Kontrola pravopisu dokonÄená: zmenených %1 slov",
+
+IeSpellDownload : "Kontrola pravopisu nie je naiÅ¡talovaná. Chcete ju hneÄ stiahnuÅ¥?",
+
+// Button Dialog
+DlgButtonText : "Text",
+DlgButtonType : "Typ",
+DlgButtonTypeBtn : "TlaÄidlo",
+DlgButtonTypeSbm : "Odoslať",
+DlgButtonTypeRst : "Vymazať",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Názov",
+DlgCheckboxValue : "Hodnota",
+DlgCheckboxSelected : "Vybrané",
+
+// Form Dialog
+DlgFormName : "Názov",
+DlgFormAction : "Akcie",
+DlgFormMethod : "Metóda",
+
+// Select Field Dialog
+DlgSelectName : "Názov",
+DlgSelectValue : "Hodnota",
+DlgSelectSize : "Veľkosť",
+DlgSelectLines : "riadkov",
+DlgSelectChkMulti : "Povoliť viacnásobný výber",
+DlgSelectOpAvail : "Dostupné možnosti",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Hodnota",
+DlgSelectBtnAdd : "Pridať",
+DlgSelectBtnModify : "Zmeniť",
+DlgSelectBtnUp : "Hore",
+DlgSelectBtnDown : "Dole",
+DlgSelectBtnSetValue : "Nastaviť ako vybranú hodnotu",
+DlgSelectBtnDelete : "Zmazať",
+
+// Textarea Dialog
+DlgTextareaName : "Názov",
+DlgTextareaCols : "Stĺpce",
+DlgTextareaRows : "Riadky",
+
+// Text Field Dialog
+DlgTextName : "Názov",
+DlgTextValue : "Hodnota",
+DlgTextCharWidth : "Šírka pola (znakov)",
+DlgTextMaxChars : "Maximálny poÄet znakov",
+DlgTextType : "Typ",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Heslo",
+
+// Hidden Field Dialog
+DlgHiddenName : "Názov",
+DlgHiddenValue : "Hodnota",
+
+// Bulleted List Dialog
+BulletedListProp : "Vlastnosti odrážok",
+NumberedListProp : "Vlastnosti Äíslovania",
+DlgLstStart : "Å tart",
+DlgLstType : "Typ",
+DlgLstTypeCircle : "Krúžok",
+DlgLstTypeDisc : "Disk",
+DlgLstTypeSquare : "Å tvorec",
+DlgLstTypeNumbers : "Číslovanie (1, 2, 3)",
+DlgLstTypeLCase : "Malé písmená (a, b, c)",
+DlgLstTypeUCase : "Veľké písmená (A, B, C)",
+DlgLstTypeSRoman : "Malé rímske Äíslice (i, ii, iii)",
+DlgLstTypeLRoman : "Veľké rímske Äíslice (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Všeobecné",
+DlgDocBackTab : "Pozadie",
+DlgDocColorsTab : "Farby a okraje",
+DlgDocMetaTab : "Meta Data",
+
+DlgDocPageTitle : "Titulok",
+DlgDocLangDir : "Orientácie jazyka",
+DlgDocLangDirLTR : "Zľava doprava (LTR)",
+DlgDocLangDirRTL : "Sprava doľava (RTL)",
+DlgDocLangCode : "Kód jazyka",
+DlgDocCharSet : "Kódová stránka",
+DlgDocCharSetCE : "Stredoeurópske",
+DlgDocCharSetCT : "ČínÅ¡tina tradiÄná (Big5)",
+DlgDocCharSetCR : "Cyrillika",
+DlgDocCharSetGR : "GréÄtina",
+DlgDocCharSetJP : "JaponÄina",
+DlgDocCharSetKR : "KorejÄina",
+DlgDocCharSetTR : "TureÄtina",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Západná európa",
+DlgDocCharSetOther : "Iná kódová stránka",
+
+DlgDocDocType : "Typ záhlavia dokumentu",
+DlgDocDocTypeOther : "Iný typ záhlavia dokumentu",
+DlgDocIncXHTML : "Obsahuje deklarácie XHTML",
+DlgDocBgColor : "Farba pozadia",
+DlgDocBgImage : "URL adresa obrázku na pozadí",
+DlgDocBgNoScroll : "Fixné pozadie",
+DlgDocCText : "Text",
+DlgDocCLink : "Odkaz",
+DlgDocCVisited : "Navštívený odkaz",
+DlgDocCActive : "Aktívny odkaz",
+DlgDocMargins : "Okraje stránky",
+DlgDocMaTop : "Horný",
+DlgDocMaLeft : "Ľavý",
+DlgDocMaRight : "Pravý",
+DlgDocMaBottom : "Dolný",
+DlgDocMeIndex : "KľúÄové slová pre indexovanie (oddelené Äiarkou)",
+DlgDocMeDescr : "Popis stránky",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Autorské práva",
+DlgDocPreview : "Náhľad",
+
+// Templates Dialog
+Templates : "Šablóny",
+DlgTemplatesTitle : "Šablóny obsahu",
+DlgTemplatesSelMsg : "Prosím vyberte šablóny na otvorenie v editore<br>(súšasný obsah bude stratený):",
+DlgTemplatesLoading : "Nahrávam zoznam šablón. Čakajte prosím...",
+DlgTemplatesNoTpl : "(žiadne šablóny nenájdené)",
+DlgTemplatesReplace : "Nahradiť aktuálny obsah",
+
+// About Dialog
+DlgAboutAboutTab : "O aplikáci",
+DlgAboutBrowserInfoTab : "Informácie o prehliadaÄi",
+DlgAboutLicenseTab : "Licencia",
+DlgAboutVersion : "verzia",
+DlgAboutInfo : "Viac informácií získate na",
+
+// Div Dialog
+DlgDivGeneralTab : "Hlavné",
+DlgDivAdvancedTab : "Rozšírené",
+DlgDivStyle : "Štýl",
+DlgDivInlineStyle : "Inline štýl",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/sl.js b/httemplate/elements/fckeditor/editor/lang/sl.js
new file mode 100644
index 0000000..b23b93c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/sl.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Slovenian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Zloži orodno vrstico",
+ToolbarExpand : "Razširi orodno vrstico",
+
+// Toolbar Items and Context Menu
+Save : "Shrani",
+NewPage : "Nova stran",
+Preview : "Predogled",
+Cut : "Izreži",
+Copy : "Kopiraj",
+Paste : "Prilepi",
+PasteText : "Prilepi kot golo besedilo",
+PasteWord : "Prilepi iz Worda",
+Print : "Natisni",
+SelectAll : "Izberi vse",
+RemoveFormat : "Odstrani oblikovanje",
+InsertLinkLbl : "Povezava",
+InsertLink : "Vstavi/uredi povezavo",
+RemoveLink : "Odstrani povezavo",
+VisitLink : "Open Link", //MISSING
+Anchor : "Vstavi/uredi zaznamek",
+AnchorDelete : "Odstrani zaznamek",
+InsertImageLbl : "Slika",
+InsertImage : "Vstavi/uredi sliko",
+InsertFlashLbl : "Flash",
+InsertFlash : "Vstavi/Uredi Flash",
+InsertTableLbl : "Tabela",
+InsertTable : "Vstavi/uredi tabelo",
+InsertLineLbl : "ÄŒrta",
+InsertLine : "Vstavi vodoravno Ärto",
+InsertSpecialCharLbl: "Posebni znak",
+InsertSpecialChar : "Vstavi posebni znak",
+InsertSmileyLbl : "Smeško",
+InsertSmiley : "Vstavi smeška",
+About : "O FCKeditorju",
+Bold : "Krepko",
+Italic : "LežeÄe",
+Underline : "PodÄrtano",
+StrikeThrough : "PreÄrtano",
+Subscript : "Podpisano",
+Superscript : "Nadpisano",
+LeftJustify : "Leva poravnava",
+CenterJustify : "Sredinska poravnava",
+RightJustify : "Desna poravnava",
+BlockJustify : "Obojestranska poravnava",
+DecreaseIndent : "Zmanjšaj zamik",
+IncreaseIndent : "PoveÄaj zamik",
+Blockquote : "Citat",
+CreateDiv : "Ustvari Div element",
+EditDiv : "Uredi Div element",
+DeleteDiv : "Odstrani Div element",
+Undo : "Razveljavi",
+Redo : "Ponovi",
+NumberedListLbl : "OÅ¡tevilÄen seznam",
+NumberedList : "Vstavi/odstrani oÅ¡tevilÄevanje",
+BulletedListLbl : "OznaÄen seznam",
+BulletedList : "Vstavi/odstrani oznaÄevanje",
+ShowTableBorders : "Pokaži meje tabele",
+ShowDetails : "Pokaži podrobnosti",
+Style : "Slog",
+FontFormat : "Oblika",
+Font : "Pisava",
+FontSize : "Velikost",
+TextColor : "Barva besedila",
+BGColor : "Barva ozadja",
+Source : "Izvorna koda",
+Find : "Najdi",
+Replace : "Zamenjaj",
+SpellCheck : "Preveri Ärkovanje",
+UniversalKeyboard : "VeÄjeziÄna tipkovnica",
+PageBreakLbl : "Prelom strani",
+PageBreak : "Vstavi prelom strani",
+
+Form : "Obrazec",
+Checkbox : "Potrditveno polje",
+RadioButton : "Izbirno polje",
+TextField : "Vnosno polje",
+Textarea : "Vnosno obmoÄje",
+HiddenField : "Skrito polje",
+Button : "Gumb",
+SelectionField : "Spustni seznam",
+ImageButton : "Gumb s sliko",
+
+FitWindow : "RazÅ¡iri velikost urejevalnika Äez cel zaslon",
+ShowBlocks : "Prikaži ograde",
+
+// Context Menu
+EditLink : "Uredi povezavo",
+CellCM : "Celica",
+RowCM : "Vrstica",
+ColumnCM : "Stolpec",
+InsertRowAfter : "Vstavi vrstico za",
+InsertRowBefore : "Vstavi vrstico pred",
+DeleteRows : "Izbriši vrstice",
+InsertColumnAfter : "Vstavi stolpec za",
+InsertColumnBefore : "Vstavi stolpec pred",
+DeleteColumns : "Izbriši stolpce",
+InsertCellAfter : "Vstavi celico za",
+InsertCellBefore : "Vstavi celico pred",
+DeleteCells : "Izbriši celice",
+MergeCells : "Združi celice",
+MergeRight : "Združi desno",
+MergeDown : "Druži navzdol",
+HorizontalSplitCell : "Razdeli celico vodoravno",
+VerticalSplitCell : "Razdeli celico navpiÄno",
+TableDelete : "Izbriši tabelo",
+CellProperties : "Lastnosti celice",
+TableProperties : "Lastnosti tabele",
+ImageProperties : "Lastnosti slike",
+FlashProperties : "Lastnosti Flash",
+
+AnchorProp : "Lastnosti zaznamka",
+ButtonProp : "Lastnosti gumba",
+CheckboxProp : "Lastnosti potrditvenega polja",
+HiddenFieldProp : "Lastnosti skritega polja",
+RadioButtonProp : "Lastnosti izbirnega polja",
+ImageButtonProp : "Lastnosti gumba s sliko",
+TextFieldProp : "Lastnosti vnosnega polja",
+SelectionFieldProp : "Lastnosti spustnega seznama",
+TextareaProp : "Lastnosti vnosnega obmoÄja",
+FormProp : "Lastnosti obrazca",
+
+FontFormats : "Navaden;Oblikovan;Napis;Naslov 1;Naslov 2;Naslov 3;Naslov 4;Naslov 5;Naslov 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Obdelujem XHTML. Prosim poÄakajte...",
+Done : "Narejeno",
+PasteWordConfirm : "Izgleda, da želite prilepiti besedilo iz Worda. Ali ga želite oÄistiti, preden ga prilepite?",
+NotCompatiblePaste : "Ta ukaz deluje le v Internet Explorerje razliÄice 5.5 ali viÅ¡je. Ali želite prilepiti brez ÄiÅ¡Äenja?",
+UnknownToolbarItem : "Neznan element orodne vrstice \"%1\"",
+UnknownCommand : "Neznano ime ukaza \"%1\"",
+NotImplemented : "Ukaz ni izdelan",
+UnknownToolbarSet : "Skupina orodnih vrstic \"%1\" ne obstoja",
+NoActiveX : "Varnostne nastavitve vaÅ¡ega brskalnika lahko omejijo delovanje nekaterih zmožnosti urejevalnika. ÄŒe ne želite zaznavati napak in sporoÄil o manjkajoÄih zmožnostih, omogoÄite možnost \"Zaženi ActiveX kontrolnike in vtiÄnike\".",
+BrowseServerBlocked : "Brskalnik virov se ne more odpreti. PrepriÄajte se, da je prepreÄevanje pojavnih oken onemogoÄeno.",
+DialogBlocked : "Pogovorno okno se ni moglo odpreti. PrepriÄajte se, da je prepreÄevanje pojavnih oken onemogoÄeno.",
+VisitLinkBlocked : "Pogovorno okno se ni moglo odpreti. PrepriÄajte se, da je prepreÄevanje pojavnih oken onemogoÄeno.",
+
+// Dialogs
+DlgBtnOK : "V redu",
+DlgBtnCancel : "PrekliÄi",
+DlgBtnClose : "Zapri",
+DlgBtnBrowseServer : "Prebrskaj na strežniku",
+DlgAdvancedTag : "Napredno",
+DlgOpOther : "<Ostalo>",
+DlgInfoTab : "Podatki",
+DlgAlertUrl : "Prosim vpiši spletni naslov",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ni postavljen>",
+DlgGenId : "Id",
+DlgGenLangDir : "Smer jezika",
+DlgGenLangDirLtr : "Od leve proti desni (LTR)",
+DlgGenLangDirRtl : "Od desne proti levi (RTL)",
+DlgGenLangCode : "Oznaka jezika",
+DlgGenAccessKey : "Vstopno geslo",
+DlgGenName : "Ime",
+DlgGenTabIndex : "Å tevilka tabulatorja",
+DlgGenLongDescr : "Dolg opis URL-ja",
+DlgGenClass : "Razred stilne predloge",
+DlgGenTitle : "Predlagani naslov",
+DlgGenContType : "Predlagani tip vsebine (content-type)",
+DlgGenLinkCharset : "Kodna tabela povezanega vira",
+DlgGenStyle : "Slog",
+
+// Image Dialog
+DlgImgTitle : "Lastnosti slike",
+DlgImgInfoTab : "Podatki o sliki",
+DlgImgBtnUpload : "Pošlji na strežnik",
+DlgImgURL : "URL",
+DlgImgUpload : "Pošlji",
+DlgImgAlt : "Nadomestno besedilo",
+DlgImgWidth : "Å irina",
+DlgImgHeight : "Višina",
+DlgImgLockRatio : "Zakleni razmerje",
+DlgBtnResetSize : "Ponastavi velikost",
+DlgImgBorder : "Obroba",
+DlgImgHSpace : "Vodoravni razmik",
+DlgImgVSpace : "NavpiÄni razmik",
+DlgImgAlign : "Poravnava",
+DlgImgAlignLeft : "Levo",
+DlgImgAlignAbsBottom: "Popolnoma na dno",
+DlgImgAlignAbsMiddle: "Popolnoma v sredino",
+DlgImgAlignBaseline : "Na osnovno Ärto",
+DlgImgAlignBottom : "Na dno",
+DlgImgAlignMiddle : "V sredino",
+DlgImgAlignRight : "Desno",
+DlgImgAlignTextTop : "Besedilo na vrh",
+DlgImgAlignTop : "Na vrh",
+DlgImgPreview : "Predogled",
+DlgImgAlertUrl : "Vnesite URL slike",
+DlgImgLinkTab : "Povezava",
+
+// Flash Dialog
+DlgFlashTitle : "Lastnosti Flash",
+DlgFlashChkPlay : "Samodejno predvajaj",
+DlgFlashChkLoop : "Ponavljanje",
+DlgFlashChkMenu : "OmogoÄi Flash Meni",
+DlgFlashScale : "PoveÄava",
+DlgFlashScaleAll : "Pokaži vse",
+DlgFlashScaleNoBorder : "Brez obrobe",
+DlgFlashScaleFit : "NatanÄno prileganje",
+
+// Link Dialog
+DlgLnkWindowTitle : "Povezava",
+DlgLnkInfoTab : "Podatki o povezavi",
+DlgLnkTargetTab : "Cilj",
+
+DlgLnkType : "Vrsta povezave",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Zaznamek na tej strani",
+DlgLnkTypeEMail : "Elektronski naslov",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<drugo>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Izberi zaznamek",
+DlgLnkAnchorByName : "Po imenu zaznamka",
+DlgLnkAnchorById : "Po ID-ju elementa",
+DlgLnkNoAnchors : "(V tem dokumentu ni zaznamkov)",
+DlgLnkEMail : "Elektronski naslov",
+DlgLnkEMailSubject : "Predmet sporoÄila",
+DlgLnkEMailBody : "Vsebina sporoÄila",
+DlgLnkUpload : "Prenesi",
+DlgLnkBtnUpload : "Pošlji na strežnik",
+
+DlgLnkTarget : "Cilj",
+DlgLnkTargetFrame : "<okvir>",
+DlgLnkTargetPopup : "<pojavno okno>",
+DlgLnkTargetBlank : "Novo okno (_blank)",
+DlgLnkTargetParent : "Starševsko okno (_parent)",
+DlgLnkTargetSelf : "Isto okno (_self)",
+DlgLnkTargetTop : "Najvišje okno (_top)",
+DlgLnkTargetFrameName : "Ime ciljnega okvirja",
+DlgLnkPopWinName : "Ime pojavnega okna",
+DlgLnkPopWinFeat : "ZnaÄilnosti pojavnega okna",
+DlgLnkPopResize : "Spremenljive velikosti",
+DlgLnkPopLocation : "Naslovna vrstica",
+DlgLnkPopMenu : "Menijska vrstica",
+DlgLnkPopScroll : "Drsniki",
+DlgLnkPopStatus : "Vrstica stanja",
+DlgLnkPopToolbar : "Orodna vrstica",
+DlgLnkPopFullScrn : "Celozaslonska slika (IE)",
+DlgLnkPopDependent : "Podokno (Netscape)",
+DlgLnkPopWidth : "Å irina",
+DlgLnkPopHeight : "Višina",
+DlgLnkPopLeft : "Lega levo",
+DlgLnkPopTop : "Lega na vrhu",
+
+DlnLnkMsgNoUrl : "Vnesite URL povezave",
+DlnLnkMsgNoEMail : "Vnesite elektronski naslov",
+DlnLnkMsgNoAnchor : "Izberite zaznamek",
+DlnLnkMsgInvPopName : "Ime pojavnega okna se mora zaÄeti s Ärko ali Å¡tevilko in ne sme vsebovati presledkov",
+
+// Color Dialog
+DlgColorTitle : "Izberite barvo",
+DlgColorBtnClear : "PoÄisti",
+DlgColorHighlight : "OznaÄi",
+DlgColorSelected : "Izbrano",
+
+// Smiley Dialog
+DlgSmileyTitle : "Vstavi smeška",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Izberi posebni znak",
+
+// Table Dialog
+DlgTableTitle : "Lastnosti tabele",
+DlgTableRows : "Vrstice",
+DlgTableColumns : "Stolpci",
+DlgTableBorder : "Velikost obrobe",
+DlgTableAlign : "Poravnava",
+DlgTableAlignNotSet : "<Ni nastavljeno>",
+DlgTableAlignLeft : "Levo",
+DlgTableAlignCenter : "Sredinsko",
+DlgTableAlignRight : "Desno",
+DlgTableWidth : "Å irina",
+DlgTableWidthPx : "pik",
+DlgTableWidthPc : "procentov",
+DlgTableHeight : "Višina",
+DlgTableCellSpace : "Razmik med celicami",
+DlgTableCellPad : "Polnilo med celicami",
+DlgTableCaption : "Naslov",
+DlgTableSummary : "Povzetek",
+DlgTableHeaders : "Glava",
+DlgTableHeadersNone : "Brez",
+DlgTableHeadersColumn : "Prvi stolpec",
+DlgTableHeadersRow : "Prva vrstica",
+DlgTableHeadersBoth : "Oboje",
+
+// Table Cell Dialog
+DlgCellTitle : "Lastnosti celice",
+DlgCellWidth : "Å irina",
+DlgCellWidthPx : "pik",
+DlgCellWidthPc : "procentov",
+DlgCellHeight : "Višina",
+DlgCellWordWrap : "Pomikanje besedila",
+DlgCellWordWrapNotSet : "<Ni nastavljeno>",
+DlgCellWordWrapYes : "Da",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Vodoravna poravnava",
+DlgCellHorAlignNotSet : "<Ni nastavljeno>",
+DlgCellHorAlignLeft : "Levo",
+DlgCellHorAlignCenter : "Sredinsko",
+DlgCellHorAlignRight: "Desno",
+DlgCellVerAlign : "NavpiÄna poravnava",
+DlgCellVerAlignNotSet : "<Ni nastavljeno>",
+DlgCellVerAlignTop : "Na vrh",
+DlgCellVerAlignMiddle : "V sredino",
+DlgCellVerAlignBottom : "Na dno",
+DlgCellVerAlignBaseline : "Na osnovno Ärto",
+DlgCellType : "Tip celice",
+DlgCellTypeData : "Podatek",
+DlgCellTypeHeader : "Naslov",
+DlgCellRowSpan : "Spojenih vrstic (row-span)",
+DlgCellCollSpan : "Spojenih stolpcev (col-span)",
+DlgCellBackColor : "Barva ozadja",
+DlgCellBorderColor : "Barva obrobe",
+DlgCellBtnSelect : "Izberi...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Najdi in zamenjaj",
+
+// Find Dialog
+DlgFindTitle : "Najdi",
+DlgFindFindBtn : "Najdi",
+DlgFindNotFoundMsg : "Navedeno besedilo ni bilo najdeno.",
+
+// Replace Dialog
+DlgReplaceTitle : "Zamenjaj",
+DlgReplaceFindLbl : "Najdi:",
+DlgReplaceReplaceLbl : "Zamenjaj z:",
+DlgReplaceCaseChk : "Razlikuj velike in male Ärke",
+DlgReplaceReplaceBtn : "Zamenjaj",
+DlgReplaceReplAllBtn : "Zamenjaj vse",
+DlgReplaceWordChk : "Samo cele besede",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Varnostne nastavitve brskalnika ne dopuÅ¡Äajo samodejnega izrezovanja. Uporabite kombinacijo tipk na tipkovnici (Ctrl+X).",
+PasteErrorCopy : "Varnostne nastavitve brskalnika ne dopuÅ¡Äajo samodejnega kopiranja. Uporabite kombinacijo tipk na tipkovnici (Ctrl+C).",
+
+PasteAsText : "Prilepi kot golo besedilo",
+PasteFromWord : "Prilepi iz Worda",
+
+DlgPasteMsg2 : "Prosim prilepite v sleÄi okvir s pomoÄjo tipkovnice (<STRONG>Ctrl+V</STRONG>) in pritisnite <STRONG>V redu</STRONG>.",
+DlgPasteSec : "Zaradi varnostnih nastavitev vaÅ¡ega brskalnika urejevalnik ne more neposredno dostopati do odložiÅ¡Äa. Vsebino odložiÅ¡Äa ponovno prilepite v to okno.",
+DlgPasteIgnoreFont : "Prezri obliko pisave",
+DlgPasteRemoveStyles : "Odstrani nastavitve stila",
+
+// Color Picker
+ColorAutomatic : "Samodejno",
+ColorMoreColors : "VeÄ barv...",
+
+// Document Properties
+DocProps : "Lastnosti dokumenta",
+
+// Anchor Dialog
+DlgAnchorTitle : "Lastnosti zaznamka",
+DlgAnchorName : "Ime zaznamka",
+DlgAnchorErrorName : "Prosim vnesite ime zaznamka",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ni v slovarju",
+DlgSpellChangeTo : "Spremeni v",
+DlgSpellBtnIgnore : "Prezri",
+DlgSpellBtnIgnoreAll : "Prezri vse",
+DlgSpellBtnReplace : "Zamenjaj",
+DlgSpellBtnReplaceAll : "Zamenjaj vse",
+DlgSpellBtnUndo : "Razveljavi",
+DlgSpellNoSuggestions : "- Ni predlogov -",
+DlgSpellProgress : "Preverjanje Ärkovanja se izvaja...",
+DlgSpellNoMispell : "ÄŒrkovanje je konÄano: Brez napak",
+DlgSpellNoChanges : "ÄŒrkovanje je konÄano: Nobena beseda ni bila spremenjena",
+DlgSpellOneChange : "ÄŒrkovanje je konÄano: Spremenjena je bila ena beseda",
+DlgSpellManyChanges : "ÄŒrkovanje je konÄano: Spremenjenih je bilo %1 besed",
+
+IeSpellDownload : "ÄŒrkovalnik ni nameÅ¡Äen. Ali ga želite prenesti sedaj?",
+
+// Button Dialog
+DlgButtonText : "Besedilo (Vrednost)",
+DlgButtonType : "Tip",
+DlgButtonTypeBtn : "Gumb",
+DlgButtonTypeSbm : "Potrdi",
+DlgButtonTypeRst : "Ponastavi",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Ime",
+DlgCheckboxValue : "Vrednost",
+DlgCheckboxSelected : "Izbrano",
+
+// Form Dialog
+DlgFormName : "Ime",
+DlgFormAction : "Akcija",
+DlgFormMethod : "Metoda",
+
+// Select Field Dialog
+DlgSelectName : "Ime",
+DlgSelectValue : "Vrednost",
+DlgSelectSize : "Velikost",
+DlgSelectLines : "vrstic",
+DlgSelectChkMulti : "Dovoli izbor veÄih vrstic",
+DlgSelectOpAvail : "Razpoložljive izbire",
+DlgSelectOpText : "Besedilo",
+DlgSelectOpValue : "Vrednost",
+DlgSelectBtnAdd : "Dodaj",
+DlgSelectBtnModify : "Spremeni",
+DlgSelectBtnUp : "Gor",
+DlgSelectBtnDown : "Dol",
+DlgSelectBtnSetValue : "Postavi kot privzeto izbiro",
+DlgSelectBtnDelete : "Izbriši",
+
+// Textarea Dialog
+DlgTextareaName : "Ime",
+DlgTextareaCols : "Stolpcev",
+DlgTextareaRows : "Vrstic",
+
+// Text Field Dialog
+DlgTextName : "Ime",
+DlgTextValue : "Vrednost",
+DlgTextCharWidth : "Dolžina",
+DlgTextMaxChars : "NajveÄje Å¡tevilo znakov",
+DlgTextType : "Tip",
+DlgTextTypeText : "Besedilo",
+DlgTextTypePass : "Geslo",
+
+// Hidden Field Dialog
+DlgHiddenName : "Ime",
+DlgHiddenValue : "Vrednost",
+
+// Bulleted List Dialog
+BulletedListProp : "Lastnosti oznaÄenega seznama",
+NumberedListProp : "Lastnosti oÅ¡tevilÄenega seznama",
+DlgLstStart : "ZaÄetek",
+DlgLstType : "Tip",
+DlgLstTypeCircle : "Pikica",
+DlgLstTypeDisc : "Kroglica",
+DlgLstTypeSquare : "Kvadratek",
+DlgLstTypeNumbers : "Å tevilke (1, 2, 3)",
+DlgLstTypeLCase : "Male Ärke (a, b, c)",
+DlgLstTypeUCase : "Velike Ärke (A, B, C)",
+DlgLstTypeSRoman : "Male rimske Å¡tevilke (i, ii, iii)",
+DlgLstTypeLRoman : "Velike rimske Å¡tevilke (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Splošno",
+DlgDocBackTab : "Ozadje",
+DlgDocColorsTab : "Barve in zamiki",
+DlgDocMetaTab : "Meta podatki",
+
+DlgDocPageTitle : "Naslov strani",
+DlgDocLangDir : "Smer jezika",
+DlgDocLangDirLTR : "Od leve proti desni (LTR)",
+DlgDocLangDirRTL : "Od desne proti levi (RTL)",
+DlgDocLangCode : "Oznaka jezika",
+DlgDocCharSet : "Kodna tabela",
+DlgDocCharSetCE : "Srednjeevropsko",
+DlgDocCharSetCT : "Tradicionalno Kitajsko (Big5)",
+DlgDocCharSetCR : "Cirilica",
+DlgDocCharSetGR : "Grško",
+DlgDocCharSetJP : "Japonsko",
+DlgDocCharSetKR : "Korejsko",
+DlgDocCharSetTR : "Turško",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Zahodnoevropsko",
+DlgDocCharSetOther : "Druga kodna tabela",
+
+DlgDocDocType : "Glava tipa dokumenta",
+DlgDocDocTypeOther : "Druga glava tipa dokumenta",
+DlgDocIncXHTML : "Vstavi XHTML deklaracije",
+DlgDocBgColor : "Barva ozadja",
+DlgDocBgImage : "URL slike za ozadje",
+DlgDocBgNoScroll : "NepremiÄno ozadje",
+DlgDocCText : "Besedilo",
+DlgDocCLink : "Povezava",
+DlgDocCVisited : "Obiskana povezava",
+DlgDocCActive : "Aktivna povezava",
+DlgDocMargins : "Zamiki strani",
+DlgDocMaTop : "Na vrhu",
+DlgDocMaLeft : "Levo",
+DlgDocMaRight : "Desno",
+DlgDocMaBottom : "Spodaj",
+DlgDocMeIndex : "KljuÄne besede (loÄene z vejicami)",
+DlgDocMeDescr : "Opis strani",
+DlgDocMeAuthor : "Avtor",
+DlgDocMeCopy : "Avtorske pravice",
+DlgDocPreview : "Predogled",
+
+// Templates Dialog
+Templates : "Predloge",
+DlgTemplatesTitle : "Vsebinske predloge",
+DlgTemplatesSelMsg : "Izberite predlogo, ki jo želite odpreti v urejevalniku<br>(trenutna vsebina bo izgubljena):",
+DlgTemplatesLoading : "Nalagam seznam predlog. Prosim poÄakajte...",
+DlgTemplatesNoTpl : "(Ni pripravljenih predlog)",
+DlgTemplatesReplace : "Zamenjaj trenutno vsebino",
+
+// About Dialog
+DlgAboutAboutTab : "Vizitka",
+DlgAboutBrowserInfoTab : "Informacije o brskalniku",
+DlgAboutLicenseTab : "Dovoljenja",
+DlgAboutVersion : "razliÄica",
+DlgAboutInfo : "Za veÄ informacij obiÅ¡Äite",
+
+// Div Dialog
+DlgDivGeneralTab : "Splošno",
+DlgDivAdvancedTab : "Napredno",
+DlgDivStyle : "Oblika",
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/sr-latn.js b/httemplate/elements/fckeditor/editor/lang/sr-latn.js
new file mode 100644
index 0000000..3b059ee
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/sr-latn.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Serbian (Latin) language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Smanji liniju sa alatkama",
+ToolbarExpand : "Proiri liniju sa alatkama",
+
+// Toolbar Items and Context Menu
+Save : "SaÄuvaj",
+NewPage : "Nova stranica",
+Preview : "Izgled stranice",
+Cut : "Iseci",
+Copy : "Kopiraj",
+Paste : "Zalepi",
+PasteText : "Zalepi kao neformatiran tekst",
+PasteWord : "Zalepi iz Worda",
+Print : "Å tampa",
+SelectAll : "OznaÄi sve",
+RemoveFormat : "Ukloni formatiranje",
+InsertLinkLbl : "Link",
+InsertLink : "Unesi/izmeni link",
+RemoveLink : "Ukloni link",
+VisitLink : "Open Link", //MISSING
+Anchor : "Unesi/izmeni sidro",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Slika",
+InsertImage : "Unesi/izmeni sliku",
+InsertFlashLbl : "Fleš",
+InsertFlash : "Unesi/izmeni fleš",
+InsertTableLbl : "Tabela",
+InsertTable : "Unesi/izmeni tabelu",
+InsertLineLbl : "Linija",
+InsertLine : "Unesi horizontalnu liniju",
+InsertSpecialCharLbl: "Specijalni karakteri",
+InsertSpecialChar : "Unesi specijalni karakter",
+InsertSmileyLbl : "Smajli",
+InsertSmiley : "Unesi smajlija",
+About : "O FCKeditoru",
+Bold : "Podebljano",
+Italic : "Kurziv",
+Underline : "PodvuÄeno",
+StrikeThrough : "Precrtano",
+Subscript : "Indeks",
+Superscript : "Stepen",
+LeftJustify : "Levo ravnanje",
+CenterJustify : "Centriran tekst",
+RightJustify : "Desno ravnanje",
+BlockJustify : "Obostrano ravnanje",
+DecreaseIndent : "Smanji levu marginu",
+IncreaseIndent : "Uvećaj levu marginu",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Poni�ti akciju",
+Redo : "Ponovi akciju",
+NumberedListLbl : "Nabrojiva lista",
+NumberedList : "Unesi/ukloni nabrojivu listu",
+BulletedListLbl : "Nenabrojiva lista",
+BulletedList : "Unesi/ukloni nenabrojivu listu",
+ShowTableBorders : "Prikaži okvir tabele",
+ShowDetails : "Prikaži detalje",
+Style : "Stil",
+FontFormat : "Format",
+Font : "Font",
+FontSize : "VeliÄina fonta",
+TextColor : "Boja teksta",
+BGColor : "Boja pozadine",
+Source : "Kôd",
+Find : "Pretraga",
+Replace : "Zamena",
+SpellCheck : "Proveri spelovanje",
+UniversalKeyboard : "Univerzalna tastatura",
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "Forma",
+Checkbox : "Polje za potvrdu",
+RadioButton : "Radio-dugme",
+TextField : "Tekstualno polje",
+Textarea : "Zona teksta",
+HiddenField : "Skriveno polje",
+Button : "Dugme",
+SelectionField : "Izborno polje",
+ImageButton : "Dugme sa slikom",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Izmeni link",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Obriši redove",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Obriši kolone",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Obriši ćelije",
+MergeCells : "Spoj celije",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Delete Table", //MISSING
+CellProperties : "Osobine celije",
+TableProperties : "Osobine tabele",
+ImageProperties : "Osobine slike",
+FlashProperties : "Osobine fleša",
+
+AnchorProp : "Osobine sidra",
+ButtonProp : "Osobine dugmeta",
+CheckboxProp : "Osobine polja za potvrdu",
+HiddenFieldProp : "Osobine skrivenog polja",
+RadioButtonProp : "Osobine radio-dugmeta",
+ImageButtonProp : "Osobine dugmeta sa slikom",
+TextFieldProp : "Osobine tekstualnog polja",
+SelectionFieldProp : "Osobine izbornog polja",
+TextareaProp : "Osobine zone teksta",
+FormProp : "Osobine forme",
+
+FontFormats : "Normal;Formatirano;Adresa;Naslov 1;Naslov 2;Naslov 3;Naslov 4;Naslov 5;Naslov 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Obradujem XHTML. Malo strpljenja...",
+Done : "Završio",
+PasteWordConfirm : "Tekst koji želite da nalepite kopiran je iz Worda. Da li želite da bude oÄišćen od formata pre lepljenja?",
+NotCompatiblePaste : "Ova komanda je dostupna samo za Internet Explorer od verzije 5.5. Da li želite da nalepim tekst bez Äišćenja?",
+UnknownToolbarItem : "Nepoznata stavka toolbara \"%1\"",
+UnknownCommand : "Nepoznata naredba \"%1\"",
+NotImplemented : "Naredba nije implementirana",
+UnknownToolbarSet : "Toolbar \"%1\" ne postoji",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Otkaži",
+DlgBtnClose : "Zatvori",
+DlgBtnBrowseServer : "Pretraži server",
+DlgAdvancedTag : "Napredni tagovi",
+DlgOpOther : "<Ostali>",
+DlgInfoTab : "Info",
+DlgAlertUrl : "Molimo Vas, unesite URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<nije postavljeno>",
+DlgGenId : "Id",
+DlgGenLangDir : "Smer jezika",
+DlgGenLangDirLtr : "S leva na desno (LTR)",
+DlgGenLangDirRtl : "S desna na levo (RTL)",
+DlgGenLangCode : "Kôd jezika",
+DlgGenAccessKey : "Pristupni taster",
+DlgGenName : "Naziv",
+DlgGenTabIndex : "Tab indeks",
+DlgGenLongDescr : "Pun opis URL",
+DlgGenClass : "Stylesheet klase",
+DlgGenTitle : "Advisory naslov",
+DlgGenContType : "Advisory vrsta sadržaja",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Osobine slika",
+DlgImgInfoTab : "Info slike",
+DlgImgBtnUpload : "Pošalji na server",
+DlgImgURL : "URL",
+DlgImgUpload : "Pošalji",
+DlgImgAlt : "Alternativni tekst",
+DlgImgWidth : "Å irina",
+DlgImgHeight : "Visina",
+DlgImgLockRatio : "ZakljuÄaj odnos",
+DlgBtnResetSize : "Resetuj veliÄinu",
+DlgImgBorder : "Okvir",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Ravnanje",
+DlgImgAlignLeft : "Levo",
+DlgImgAlignAbsBottom: "Abs dole",
+DlgImgAlignAbsMiddle: "Abs sredina",
+DlgImgAlignBaseline : "Bazno",
+DlgImgAlignBottom : "Dole",
+DlgImgAlignMiddle : "Sredina",
+DlgImgAlignRight : "Desno",
+DlgImgAlignTextTop : "Vrh teksta",
+DlgImgAlignTop : "Vrh",
+DlgImgPreview : "Izgled",
+DlgImgAlertUrl : "Unesite URL slike",
+DlgImgLinkTab : "Link",
+
+// Flash Dialog
+DlgFlashTitle : "Osobine fleša",
+DlgFlashChkPlay : "Automatski start",
+DlgFlashChkLoop : "Ponavljaj",
+DlgFlashChkMenu : "UkljuÄi fleÅ¡ meni",
+DlgFlashScale : "Skaliraj",
+DlgFlashScaleAll : "Prikaži sve",
+DlgFlashScaleNoBorder : "Bez ivice",
+DlgFlashScaleFit : "Popuni površinu",
+
+// Link Dialog
+DlgLnkWindowTitle : "Link",
+DlgLnkInfoTab : "Link Info",
+DlgLnkTargetTab : "Meta",
+
+DlgLnkType : "Vrsta linka",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Sidro na ovoj stranici",
+DlgLnkTypeEMail : "E-Mail",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<drugo>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Odaberi sidro",
+DlgLnkAnchorByName : "Po nazivu sidra",
+DlgLnkAnchorById : "Po Id-ju elementa",
+DlgLnkNoAnchors : "(Nema dostupnih sidra)",
+DlgLnkEMail : "E-Mail adresa",
+DlgLnkEMailSubject : "Naslov",
+DlgLnkEMailBody : "Sadržaj poruke",
+DlgLnkUpload : "Pošalji",
+DlgLnkBtnUpload : "Pošalji na server",
+
+DlgLnkTarget : "Meta",
+DlgLnkTargetFrame : "<okvir>",
+DlgLnkTargetPopup : "<popup prozor>",
+DlgLnkTargetBlank : "Novi prozor (_blank)",
+DlgLnkTargetParent : "Roditeljski prozor (_parent)",
+DlgLnkTargetSelf : "Isti prozor (_self)",
+DlgLnkTargetTop : "Prozor na vrhu (_top)",
+DlgLnkTargetFrameName : "Naziv odredišnog frejma",
+DlgLnkPopWinName : "Naziv popup prozora",
+DlgLnkPopWinFeat : "Mogućnosti popup prozora",
+DlgLnkPopResize : "Promenljiva velicina",
+DlgLnkPopLocation : "Lokacija",
+DlgLnkPopMenu : "Kontekstni meni",
+DlgLnkPopScroll : "Scroll bar",
+DlgLnkPopStatus : "Statusna linija",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Prikaz preko celog ekrana (IE)",
+DlgLnkPopDependent : "Zavisno (Netscape)",
+DlgLnkPopWidth : "Å irina",
+DlgLnkPopHeight : "Visina",
+DlgLnkPopLeft : "Od leve ivice ekrana (px)",
+DlgLnkPopTop : "Od vrha ekrana (px)",
+
+DlnLnkMsgNoUrl : "Unesite URL linka",
+DlnLnkMsgNoEMail : "Otkucajte adresu elektronske pote",
+DlnLnkMsgNoAnchor : "Odaberite sidro",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Odaberite boju",
+DlgColorBtnClear : "Obriši",
+DlgColorHighlight : "Posvetli",
+DlgColorSelected : "Odaberi",
+
+// Smiley Dialog
+DlgSmileyTitle : "Unesi smajlija",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Odaberite specijalni karakter",
+
+// Table Dialog
+DlgTableTitle : "Osobine tabele",
+DlgTableRows : "Redova",
+DlgTableColumns : "Kolona",
+DlgTableBorder : "VeliÄina okvira",
+DlgTableAlign : "Ravnanje",
+DlgTableAlignNotSet : "<nije postavljeno>",
+DlgTableAlignLeft : "Levo",
+DlgTableAlignCenter : "Sredina",
+DlgTableAlignRight : "Desno",
+DlgTableWidth : "Å irina",
+DlgTableWidthPx : "piksela",
+DlgTableWidthPc : "procenata",
+DlgTableHeight : "Visina",
+DlgTableCellSpace : "Ćelijski prostor",
+DlgTableCellPad : "Razmak ćelija",
+DlgTableCaption : "Naslov tabele",
+DlgTableSummary : "Summary", //MISSING
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Osobine ćelije",
+DlgCellWidth : "Å irina",
+DlgCellWidthPx : "piksela",
+DlgCellWidthPc : "procenata",
+DlgCellHeight : "Visina",
+DlgCellWordWrap : "Deljenje reÄi",
+DlgCellWordWrapNotSet : "<nije postavljeno>",
+DlgCellWordWrapYes : "Da",
+DlgCellWordWrapNo : "Ne",
+DlgCellHorAlign : "Vodoravno ravnanje",
+DlgCellHorAlignNotSet : "<nije postavljeno>",
+DlgCellHorAlignLeft : "Levo",
+DlgCellHorAlignCenter : "Sredina",
+DlgCellHorAlignRight: "Desno",
+DlgCellVerAlign : "Vertikalno ravnanje",
+DlgCellVerAlignNotSet : "<nije postavljeno>",
+DlgCellVerAlignTop : "Gornje",
+DlgCellVerAlignMiddle : "Sredina",
+DlgCellVerAlignBottom : "Donje",
+DlgCellVerAlignBaseline : "Bazno",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Spajanje redova",
+DlgCellCollSpan : "Spajanje kolona",
+DlgCellBackColor : "Boja pozadine",
+DlgCellBorderColor : "Boja okvira",
+DlgCellBtnSelect : "Odaberi...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Pronađi",
+DlgFindFindBtn : "Pronađi",
+DlgFindNotFoundMsg : "Traženi tekst nije pronađen.",
+
+// Replace Dialog
+DlgReplaceTitle : "Zameni",
+DlgReplaceFindLbl : "Pronadi:",
+DlgReplaceReplaceLbl : "Zameni sa:",
+DlgReplaceCaseChk : "Razlikuj mala i velika slova",
+DlgReplaceReplaceBtn : "Zameni",
+DlgReplaceReplAllBtn : "Zameni sve",
+DlgReplaceWordChk : "Uporedi cele reci",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Sigurnosna podeÅ¡avanja VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog isecanja teksta. Molimo Vas da koristite preÄicu sa tastature (Ctrl+X).",
+PasteErrorCopy : "Sigurnosna podeÅ¡avanja VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog kopiranja teksta. Molimo Vas da koristite preÄicu sa tastature (Ctrl+C).",
+
+PasteAsText : "Zalepi kao Äist tekst",
+PasteFromWord : "Zalepi iz Worda",
+
+DlgPasteMsg2 : "Molimo Vas da zalepite unutar donje povrine koristeći tastaturnu preÄicu (<STRONG>Ctrl+V</STRONG>) i da pritisnete <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Ignoriši definicije fontova",
+DlgPasteRemoveStyles : "Ukloni definicije stilova",
+
+// Color Picker
+ColorAutomatic : "Automatski",
+ColorMoreColors : "Više boja...",
+
+// Document Properties
+DocProps : "Osobine dokumenta",
+
+// Anchor Dialog
+DlgAnchorTitle : "Osobine sidra",
+DlgAnchorName : "Ime sidra",
+DlgAnchorErrorName : "Unesite ime sidra",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Nije u reÄniku",
+DlgSpellChangeTo : "Izmeni",
+DlgSpellBtnIgnore : "Ignoriši",
+DlgSpellBtnIgnoreAll : "Ignoriši sve",
+DlgSpellBtnReplace : "Zameni",
+DlgSpellBtnReplaceAll : "Zameni sve",
+DlgSpellBtnUndo : "Vrati akciju",
+DlgSpellNoSuggestions : "- Bez sugestija -",
+DlgSpellProgress : "Provera spelovanja u toku...",
+DlgSpellNoMispell : "Provera spelovanja završena: greške nisu pronadene",
+DlgSpellNoChanges : "Provera spelovanja završena: Nije izmenjena nijedna rec",
+DlgSpellOneChange : "Provera spelovanja zavrÅ¡ena: Izmenjena je jedna reÄ",
+DlgSpellManyChanges : "Provera spelovanja zavrÅ¡ena: %1 reÄ(i) je izmenjeno",
+
+IeSpellDownload : "Provera spelovanja nije instalirana. Da li želite da je skinete sa Interneta?",
+
+// Button Dialog
+DlgButtonText : "Tekst (vrednost)",
+DlgButtonType : "Tip",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Naziv",
+DlgCheckboxValue : "Vrednost",
+DlgCheckboxSelected : "OznaÄeno",
+
+// Form Dialog
+DlgFormName : "Naziv",
+DlgFormAction : "Akcija",
+DlgFormMethod : "Metoda",
+
+// Select Field Dialog
+DlgSelectName : "Naziv",
+DlgSelectValue : "Vrednost",
+DlgSelectSize : "VeliÄina",
+DlgSelectLines : "linija",
+DlgSelectChkMulti : "Dozvoli višestruku selekciju",
+DlgSelectOpAvail : "Dostupne opcije",
+DlgSelectOpText : "Tekst",
+DlgSelectOpValue : "Vrednost",
+DlgSelectBtnAdd : "Dodaj",
+DlgSelectBtnModify : "Izmeni",
+DlgSelectBtnUp : "Gore",
+DlgSelectBtnDown : "Dole",
+DlgSelectBtnSetValue : "Podesi kao oznaÄenu vrednost",
+DlgSelectBtnDelete : "Obriši",
+
+// Textarea Dialog
+DlgTextareaName : "Naziv",
+DlgTextareaCols : "Broj kolona",
+DlgTextareaRows : "Broj redova",
+
+// Text Field Dialog
+DlgTextName : "Naziv",
+DlgTextValue : "Vrednost",
+DlgTextCharWidth : "Å irina (karaktera)",
+DlgTextMaxChars : "Maksimalno karaktera",
+DlgTextType : "Tip",
+DlgTextTypeText : "Tekst",
+DlgTextTypePass : "Lozinka",
+
+// Hidden Field Dialog
+DlgHiddenName : "Naziv",
+DlgHiddenValue : "Vrednost",
+
+// Bulleted List Dialog
+BulletedListProp : "Osobine nenabrojive liste",
+NumberedListProp : "Osobine nabrojive liste",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Tip",
+DlgLstTypeCircle : "Krug",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "Kvadrat",
+DlgLstTypeNumbers : "Brojevi (1, 2, 3)",
+DlgLstTypeLCase : "mala slova (a, b, c)",
+DlgLstTypeUCase : "VELIKA slova (A, B, C)",
+DlgLstTypeSRoman : "Male rimske cifre (i, ii, iii)",
+DlgLstTypeLRoman : "Velike rimske cifre (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Opšte osobine",
+DlgDocBackTab : "Pozadina",
+DlgDocColorsTab : "Boje i margine",
+DlgDocMetaTab : "Metapodaci",
+
+DlgDocPageTitle : "Naslov stranice",
+DlgDocLangDir : "Smer jezika",
+DlgDocLangDirLTR : "Sleva nadesno (LTR)",
+DlgDocLangDirRTL : "Zdesna nalevo (RTL)",
+DlgDocLangCode : "Å ifra jezika",
+DlgDocCharSet : "Kodiranje skupa karaktera",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "Ostala kodiranja skupa karaktera",
+
+DlgDocDocType : "Zaglavlje tipa dokumenta",
+DlgDocDocTypeOther : "Ostala zaglavlja tipa dokumenta",
+DlgDocIncXHTML : "Ukljuci XHTML deklaracije",
+DlgDocBgColor : "Boja pozadine",
+DlgDocBgImage : "URL pozadinske slike",
+DlgDocBgNoScroll : "Fiksirana pozadina",
+DlgDocCText : "Tekst",
+DlgDocCLink : "Link",
+DlgDocCVisited : "Posećeni link",
+DlgDocCActive : "Aktivni link",
+DlgDocMargins : "Margine stranice",
+DlgDocMaTop : "Gornja",
+DlgDocMaLeft : "Leva",
+DlgDocMaRight : "Desna",
+DlgDocMaBottom : "Donja",
+DlgDocMeIndex : "KljuÄne reci za indeksiranje dokumenta (razdvojene zarezima)",
+DlgDocMeDescr : "Opis dokumenta",
+DlgDocMeAuthor : "Autor",
+DlgDocMeCopy : "Autorska prava",
+DlgDocPreview : "Izgled stranice",
+
+// Templates Dialog
+Templates : "Obrasci",
+DlgTemplatesTitle : "Obrasci za sadržaj",
+DlgTemplatesSelMsg : "Molimo Vas da odaberete obrazac koji ce biti primenjen na stranicu (trenutni sadržaj ce biti obrisan):",
+DlgTemplatesLoading : "UÄitavam listu obrazaca. Malo strpljenja...",
+DlgTemplatesNoTpl : "(Nema definisanih obrazaca)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "O editoru",
+DlgAboutBrowserInfoTab : "Informacije o pretraživacu",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "verzija",
+DlgAboutInfo : "Za više informacija posetite",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/sr.js b/httemplate/elements/fckeditor/editor/lang/sr.js
new file mode 100644
index 0000000..42ef972
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/sr.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Serbian (Cyrillic) language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Смањи линију Ñа алаткама",
+ToolbarExpand : "Прошири линију Ñа алаткама",
+
+// Toolbar Items and Context Menu
+Save : "Сачувај",
+NewPage : "Ðова Ñтраница",
+Preview : "Изглед Ñтранице",
+Cut : "ИÑеци",
+Copy : "Копирај",
+Paste : "Залепи",
+PasteText : "Залепи као неформатиран текÑÑ‚",
+PasteWord : "Залепи из Worda",
+Print : "Штампа",
+SelectAll : "Означи Ñве",
+RemoveFormat : "Уклони форматирање",
+InsertLinkLbl : "Линк",
+InsertLink : "УнеÑи/измени линк",
+RemoveLink : "Уклони линк",
+VisitLink : "Open Link", //MISSING
+Anchor : "УнеÑи/измени Ñидро",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "Слика",
+InsertImage : "УнеÑи/измени Ñлику",
+InsertFlashLbl : "Флеш елемент",
+InsertFlash : "УнеÑи/измени флеш",
+InsertTableLbl : "Табела",
+InsertTable : "УнеÑи/измени табелу",
+InsertLineLbl : "Линија",
+InsertLine : "УнеÑи хоризонталну линију",
+InsertSpecialCharLbl: "Специјални карактери",
+InsertSpecialChar : "УнеÑи Ñпецијални карактер",
+InsertSmileyLbl : "Смајли",
+InsertSmiley : "УнеÑи Ñмајлија",
+About : "О ФЦКедитору",
+Bold : "Подебљано",
+Italic : "Курзив",
+Underline : "Подвучено",
+StrikeThrough : "Прецртано",
+Subscript : "ИндекÑ",
+Superscript : "Степен",
+LeftJustify : "Лево равнање",
+CenterJustify : "Центриран текÑÑ‚",
+RightJustify : "ДеÑно равнање",
+BlockJustify : "ОбоÑтрано равнање",
+DecreaseIndent : "Смањи леву маргину",
+IncreaseIndent : "Увећај леву маргину",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "Поништи акцију",
+Redo : "Понови акцију",
+NumberedListLbl : "Ðабројиву лиÑту",
+NumberedList : "УнеÑи/уклони набројиву лиÑту",
+BulletedListLbl : "Ðенабројива лиÑта",
+BulletedList : "УнеÑи/уклони ненабројиву лиÑту",
+ShowTableBorders : "Прикажи оквир табеле",
+ShowDetails : "Прикажи детаље",
+Style : "Стил",
+FontFormat : "Формат",
+Font : "Фонт",
+FontSize : "Величина фонта",
+TextColor : "Боја текÑта",
+BGColor : "Боја позадине",
+Source : "Kôд",
+Find : "Претрага",
+Replace : "Замена",
+SpellCheck : "Провери Ñпеловање",
+UniversalKeyboard : "Универзална таÑтатура",
+PageBreakLbl : "Page Break", //MISSING
+PageBreak : "Insert Page Break", //MISSING
+
+Form : "Форма",
+Checkbox : "Поље за потврду",
+RadioButton : "Радио-дугме",
+TextField : "ТекÑтуално поље",
+Textarea : "Зона текÑта",
+HiddenField : "Скривено поље",
+Button : "Дугме",
+SelectionField : "Изборно поље",
+ImageButton : "Дугме Ñа Ñликом",
+
+FitWindow : "Maximize the editor size", //MISSING
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "Промени линк",
+CellCM : "Cell", //MISSING
+RowCM : "Row", //MISSING
+ColumnCM : "Column", //MISSING
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "Обриши редове",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "Обриши колоне",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "Обриши ћелије",
+MergeCells : "Спој ћелије",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "Delete Table", //MISSING
+CellProperties : "ОÑобине ћелије",
+TableProperties : "ОÑобине табеле",
+ImageProperties : "ОÑобине Ñлике",
+FlashProperties : "ОÑобине Флеша",
+
+AnchorProp : "ОÑобине Ñидра",
+ButtonProp : "ОÑобине дугмета",
+CheckboxProp : "ОÑобине поља за потврду",
+HiddenFieldProp : "ОÑобине Ñкривеног поља",
+RadioButtonProp : "ОÑобине радио-дугмета",
+ImageButtonProp : "ОÑобине дугмета Ñа Ñликом",
+TextFieldProp : "ОÑобине текÑтуалног поља",
+SelectionFieldProp : "ОÑобине изборног поља",
+TextareaProp : "ОÑобине зоне текÑта",
+FormProp : "ОÑобине форме",
+
+FontFormats : "Normal;Formatirano;Adresa;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",
+
+// Alerts and Messages
+ProcessingXHTML : "Обрађујем XHTML. Maлo Ñтрпљења...",
+Done : "Завршио",
+PasteWordConfirm : "ТекÑÑ‚ који желите да налепите копиран је из Worda. Да ли желите да буде очишћен од формата пре лепљења?",
+NotCompatiblePaste : "Ова команда је доÑтупна Ñамо за Интернет Екплорер од верзије 5.5. Да ли желите да налепим текÑÑ‚ без чишћења?",
+UnknownToolbarItem : "Ðепозната Ñтавка toolbara \"%1\"",
+UnknownCommand : "Ðепозната наредба \"%1\"",
+NotImplemented : "Ðаредба није имплементирана",
+UnknownToolbarSet : "Toolbar \"%1\" не поÑтоји",
+NoActiveX : "Your browser's security settings could limit some features of the editor. You must enable the option \"Run ActiveX controls and plug-ins\". You may experience errors and notice missing features.", //MISSING
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.", //MISSING
+DialogBlocked : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.", //MISSING
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Oткажи",
+DlgBtnClose : "Затвори",
+DlgBtnBrowseServer : "Претражи Ñервер",
+DlgAdvancedTag : "Ðапредни тагови",
+DlgOpOther : "<ОÑтали>",
+DlgInfoTab : "Инфо",
+DlgAlertUrl : "Молимо ВаÑ, унеÑите УРЛ",
+
+// General Dialogs Labels
+DlgGenNotSet : "<није поÑтављено>",
+DlgGenId : "Ид",
+DlgGenLangDir : "Смер језика",
+DlgGenLangDirLtr : "С лева на деÑно (LTR)",
+DlgGenLangDirRtl : "С деÑна на лево (RTL)",
+DlgGenLangCode : "Kôд језика",
+DlgGenAccessKey : "ПриÑтупни таÑтер",
+DlgGenName : "Ðазив",
+DlgGenTabIndex : "Таб индекÑ",
+DlgGenLongDescr : "Пун Ð¾Ð¿Ð¸Ñ Ð£Ð Ð›",
+DlgGenClass : "Stylesheet клаÑе",
+DlgGenTitle : "Advisory наÑлов",
+DlgGenContType : "Advisory врÑта Ñадржаја",
+DlgGenLinkCharset : "Linked Resource Charset",
+DlgGenStyle : "Стил",
+
+// Image Dialog
+DlgImgTitle : "ОÑобине Ñлика",
+DlgImgInfoTab : "Инфо Ñлике",
+DlgImgBtnUpload : "Пошаљи на Ñервер",
+DlgImgURL : "УРЛ",
+DlgImgUpload : "Пошаљи",
+DlgImgAlt : "Ðлтернативни текÑÑ‚",
+DlgImgWidth : "Ширина",
+DlgImgHeight : "ВиÑина",
+DlgImgLockRatio : "Закључај одноÑ",
+DlgBtnResetSize : "РеÑетуј величину",
+DlgImgBorder : "Оквир",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Равнање",
+DlgImgAlignLeft : "Лево",
+DlgImgAlignAbsBottom: "Abs доле",
+DlgImgAlignAbsMiddle: "Abs Ñредина",
+DlgImgAlignBaseline : "Базно",
+DlgImgAlignBottom : "Доле",
+DlgImgAlignMiddle : "Средина",
+DlgImgAlignRight : "ДеÑно",
+DlgImgAlignTextTop : "Врх текÑта",
+DlgImgAlignTop : "Врх",
+DlgImgPreview : "Изглед",
+DlgImgAlertUrl : "УнеÑите УРЛ Ñлике",
+DlgImgLinkTab : "Линк",
+
+// Flash Dialog
+DlgFlashTitle : "ОÑобине флеша",
+DlgFlashChkPlay : "ÐутоматÑки Ñтарт",
+DlgFlashChkLoop : "Понављај",
+DlgFlashChkMenu : "Укључи флеш мени",
+DlgFlashScale : "Скалирај",
+DlgFlashScaleAll : "Прикажи Ñве",
+DlgFlashScaleNoBorder : "Без ивице",
+DlgFlashScaleFit : "Попуни површину",
+
+// Link Dialog
+DlgLnkWindowTitle : "Линк",
+DlgLnkInfoTab : "Линк инфо",
+DlgLnkTargetTab : "Мета",
+
+DlgLnkType : "Ð’Ñ€Ñта линка",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Сидро на овој Ñтраници",
+DlgLnkTypeEMail : "EлектронÑка пошта",
+DlgLnkProto : "Протокол",
+DlgLnkProtoOther : "<друго>",
+DlgLnkURL : "УРЛ",
+DlgLnkAnchorSel : "Одабери Ñидро",
+DlgLnkAnchorByName : "По називу Ñидра",
+DlgLnkAnchorById : "Пo Ид-jу елемента",
+DlgLnkNoAnchors : "(Ðема доÑтупних Ñидра)",
+DlgLnkEMail : "ÐдреÑа електронÑке поште",
+DlgLnkEMailSubject : "ÐаÑлов",
+DlgLnkEMailBody : "Садржај поруке",
+DlgLnkUpload : "Пошаљи",
+DlgLnkBtnUpload : "Пошаљи на Ñервер",
+
+DlgLnkTarget : "Meтa",
+DlgLnkTargetFrame : "<оквир>",
+DlgLnkTargetPopup : "<иÑкачући прозор>",
+DlgLnkTargetBlank : "Ðови прозор (_blank)",
+DlgLnkTargetParent : "РодитељÑки прозор (_parent)",
+DlgLnkTargetSelf : "ИÑти прозор (_self)",
+DlgLnkTargetTop : "Прозор на врху (_top)",
+DlgLnkTargetFrameName : "Ðазив одредишног фрејма",
+DlgLnkPopWinName : "Ðазив иÑкачућег прозора",
+DlgLnkPopWinFeat : "МогућноÑти иÑкачућег прозора",
+DlgLnkPopResize : "Променљива величина",
+DlgLnkPopLocation : "Локација",
+DlgLnkPopMenu : "КонтекÑтни мени",
+DlgLnkPopScroll : "Скрол бар",
+DlgLnkPopStatus : "СтатуÑна линија",
+DlgLnkPopToolbar : "Toolbar",
+DlgLnkPopFullScrn : "Приказ преко целог екрана (ИE)",
+DlgLnkPopDependent : "ЗавиÑно (Netscape)",
+DlgLnkPopWidth : "Ширина",
+DlgLnkPopHeight : "ВиÑина",
+DlgLnkPopLeft : "Од леве ивице екрана (пикÑела)",
+DlgLnkPopTop : "Од врха екрана (пикÑела)",
+
+DlnLnkMsgNoUrl : "УнеÑите УРЛ линка",
+DlnLnkMsgNoEMail : "Откуцајте адреÑу електронÑке поште",
+DlnLnkMsgNoAnchor : "Одаберите Ñидро",
+DlnLnkMsgInvPopName : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING
+
+// Color Dialog
+DlgColorTitle : "Одаберите боју",
+DlgColorBtnClear : "Обриши",
+DlgColorHighlight : "ПоÑветли",
+DlgColorSelected : "Одабери",
+
+// Smiley Dialog
+DlgSmileyTitle : "УнеÑи Ñмајлија",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Одаберите Ñпецијални карактер",
+
+// Table Dialog
+DlgTableTitle : "ОÑобине табеле",
+DlgTableRows : "Редова",
+DlgTableColumns : "Kолона",
+DlgTableBorder : "Величина оквира",
+DlgTableAlign : "Равнање",
+DlgTableAlignNotSet : "<није поÑтављено>",
+DlgTableAlignLeft : "Лево",
+DlgTableAlignCenter : "Средина",
+DlgTableAlignRight : "ДеÑно",
+DlgTableWidth : "Ширина",
+DlgTableWidthPx : "пикÑела",
+DlgTableWidthPc : "процената",
+DlgTableHeight : "ВиÑина",
+DlgTableCellSpace : "ЋелијÑки проÑтор",
+DlgTableCellPad : "Размак ћелија",
+DlgTableCaption : "ÐаÑлов табеле",
+DlgTableSummary : "Summary", //MISSING
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "ОÑобине ћелије",
+DlgCellWidth : "Ширина",
+DlgCellWidthPx : "пикÑела",
+DlgCellWidthPc : "процената",
+DlgCellHeight : "ВиÑина",
+DlgCellWordWrap : "Дељење речи",
+DlgCellWordWrapNotSet : "<није поÑтављено>",
+DlgCellWordWrapYes : "Да",
+DlgCellWordWrapNo : "Ðе",
+DlgCellHorAlign : "Водоравно равнање",
+DlgCellHorAlignNotSet : "<није поÑтављено>",
+DlgCellHorAlignLeft : "Лево",
+DlgCellHorAlignCenter : "Средина",
+DlgCellHorAlignRight: "ДеÑно",
+DlgCellVerAlign : "Вертикално равнање",
+DlgCellVerAlignNotSet : "<није поÑтављено>",
+DlgCellVerAlignTop : "Горње",
+DlgCellVerAlignMiddle : "Средина",
+DlgCellVerAlignBottom : "Доње",
+DlgCellVerAlignBaseline : "Базно",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Спајање редова",
+DlgCellCollSpan : "Спајање колона",
+DlgCellBackColor : "Боја позадине",
+DlgCellBorderColor : "Боја оквира",
+DlgCellBtnSelect : "Oдабери...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "Пронађи",
+DlgFindFindBtn : "Пронађи",
+DlgFindNotFoundMsg : "Тражени текÑÑ‚ није пронађен.",
+
+// Replace Dialog
+DlgReplaceTitle : "Замени",
+DlgReplaceFindLbl : "Пронађи:",
+DlgReplaceReplaceLbl : "Замени Ñа:",
+DlgReplaceCaseChk : "Разликуј велика и мала Ñлова",
+DlgReplaceReplaceBtn : "Замени",
+DlgReplaceReplAllBtn : "Замени Ñве",
+DlgReplaceWordChk : "Упореди целе речи",
+
+// Paste Operations / Dialog
+PasteErrorCut : "СигурноÑна подешавања Вашег претраживача не дозвољавају операције аутоматÑког иÑецања текÑта. Молимо Ð’Ð°Ñ Ð´Ð° кориÑтите пречицу Ñа таÑтатуре (Ctrl+X).",
+PasteErrorCopy : "СигурноÑна подешавања Вашег претраживача не дозвољавају операције аутоматÑког копирања текÑта. Молимо Ð’Ð°Ñ Ð´Ð° кориÑтите пречицу Ñа таÑтатуре (Ctrl+C).",
+
+PasteAsText : "Залепи као чиÑÑ‚ текÑÑ‚",
+PasteFromWord : "Залепи из Worda",
+
+DlgPasteMsg2 : "Молимо Ð’Ð°Ñ Ð´Ð° залепите унутар доње површине кориÑтећи таÑтатурну пречицу (<STRONG>Ctrl+V</STRONG>) и да притиÑнете <STRONG>OK</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Игнориши Font Face дефиниције",
+DlgPasteRemoveStyles : "Уклони дефиниције Ñтилова",
+
+// Color Picker
+ColorAutomatic : "ÐутоматÑки",
+ColorMoreColors : "Више боја...",
+
+// Document Properties
+DocProps : "ОÑобине документа",
+
+// Anchor Dialog
+DlgAnchorTitle : "ОÑобине Ñидра",
+DlgAnchorName : "Име Ñидра",
+DlgAnchorErrorName : "Молимо Ð’Ð°Ñ Ð´Ð° унеÑете име Ñидра",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ðије у речнику",
+DlgSpellChangeTo : "Измени",
+DlgSpellBtnIgnore : "Игнориши",
+DlgSpellBtnIgnoreAll : "Игнориши Ñве",
+DlgSpellBtnReplace : "Замени",
+DlgSpellBtnReplaceAll : "Замени Ñве",
+DlgSpellBtnUndo : "Врати акцију",
+DlgSpellNoSuggestions : "- Без ÑугеÑтија -",
+DlgSpellProgress : "Провера Ñпеловања у току...",
+DlgSpellNoMispell : "Провера Ñпеловања завршена: грешке ниÑу пронађене",
+DlgSpellNoChanges : "Провера Ñпеловања завршена: Ðије измењена ниједна реч",
+DlgSpellOneChange : "Провера Ñпеловања завршена: Измењена је једна реч",
+DlgSpellManyChanges : "Провера Ñпеловања завршена: %1 реч(и) је измењено",
+
+IeSpellDownload : "Провера Ñпеловања није инÑталирана. Да ли желите да је Ñкинете Ñа Интернета?",
+
+// Button Dialog
+DlgButtonText : "ТекÑÑ‚ (вредноÑÑ‚)",
+DlgButtonType : "Tип",
+DlgButtonTypeBtn : "Button", //MISSING
+DlgButtonTypeSbm : "Submit", //MISSING
+DlgButtonTypeRst : "Reset", //MISSING
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Ðазив",
+DlgCheckboxValue : "ВредноÑÑ‚",
+DlgCheckboxSelected : "Означено",
+
+// Form Dialog
+DlgFormName : "Ðазив",
+DlgFormAction : "Aкција",
+DlgFormMethod : "Mетода",
+
+// Select Field Dialog
+DlgSelectName : "Ðазив",
+DlgSelectValue : "ВредноÑÑ‚",
+DlgSelectSize : "Величина",
+DlgSelectLines : "линија",
+DlgSelectChkMulti : "Дозволи вишеÑтруку Ñелекцију",
+DlgSelectOpAvail : "ДоÑтупне опције",
+DlgSelectOpText : "ТекÑÑ‚",
+DlgSelectOpValue : "ВредноÑÑ‚",
+DlgSelectBtnAdd : "Додај",
+DlgSelectBtnModify : "Измени",
+DlgSelectBtnUp : "Горе",
+DlgSelectBtnDown : "Доле",
+DlgSelectBtnSetValue : "ПодеÑи као означену вредноÑÑ‚",
+DlgSelectBtnDelete : "Обриши",
+
+// Textarea Dialog
+DlgTextareaName : "Ðазив",
+DlgTextareaCols : "Број колона",
+DlgTextareaRows : "Број редова",
+
+// Text Field Dialog
+DlgTextName : "Ðазив",
+DlgTextValue : "ВредноÑÑ‚",
+DlgTextCharWidth : "Ширина (карактера)",
+DlgTextMaxChars : "МакÑимално карактера",
+DlgTextType : "Тип",
+DlgTextTypeText : "ТекÑÑ‚",
+DlgTextTypePass : "Лозинка",
+
+// Hidden Field Dialog
+DlgHiddenName : "Ðазив",
+DlgHiddenValue : "ВредноÑÑ‚",
+
+// Bulleted List Dialog
+BulletedListProp : "ОÑобине Bulleted лиÑте",
+NumberedListProp : "ОÑобине набројиве лиÑте",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "Тип",
+DlgLstTypeCircle : "Круг",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "Квадрат",
+DlgLstTypeNumbers : "Бројеви (1, 2, 3)",
+DlgLstTypeLCase : "мала Ñлова (a, b, c)",
+DlgLstTypeUCase : "ВЕЛИКРСЛОВР(A, B, C)",
+DlgLstTypeSRoman : "Мале римÑке цифре (i, ii, iii)",
+DlgLstTypeLRoman : "Велике римÑке цифре (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Опште оÑобине",
+DlgDocBackTab : "Позадина",
+DlgDocColorsTab : "Боје и маргине",
+DlgDocMetaTab : "Метаподаци",
+
+DlgDocPageTitle : "ÐаÑлов Ñтранице",
+DlgDocLangDir : "Смер језика",
+DlgDocLangDirLTR : "Слева надеÑно (LTR)",
+DlgDocLangDirRTL : "ЗдеÑна налево (RTL)",
+DlgDocLangCode : "Шифра језика",
+DlgDocCharSet : "Кодирање Ñкупа карактера",
+DlgDocCharSetCE : "Central European", //MISSING
+DlgDocCharSetCT : "Chinese Traditional (Big5)", //MISSING
+DlgDocCharSetCR : "Cyrillic", //MISSING
+DlgDocCharSetGR : "Greek", //MISSING
+DlgDocCharSetJP : "Japanese", //MISSING
+DlgDocCharSetKR : "Korean", //MISSING
+DlgDocCharSetTR : "Turkish", //MISSING
+DlgDocCharSetUN : "Unicode (UTF-8)", //MISSING
+DlgDocCharSetWE : "Western European", //MISSING
+DlgDocCharSetOther : "ОÑтала кодирања Ñкупа карактера",
+
+DlgDocDocType : "Заглавље типа документа",
+DlgDocDocTypeOther : "ОÑтала заглавља типа документа",
+DlgDocIncXHTML : "Улључи XHTML декларације",
+DlgDocBgColor : "Боја позадине",
+DlgDocBgImage : "УРЛ позадинÑке Ñлике",
+DlgDocBgNoScroll : "ФикÑирана позадина",
+DlgDocCText : "ТекÑÑ‚",
+DlgDocCLink : "Линк",
+DlgDocCVisited : "ПоÑећени линк",
+DlgDocCActive : "Ðктивни линк",
+DlgDocMargins : "Маргине Ñтранице",
+DlgDocMaTop : "Горња",
+DlgDocMaLeft : "Лева",
+DlgDocMaRight : "ДеÑна",
+DlgDocMaBottom : "Доња",
+DlgDocMeIndex : "Кључне речи за индекÑирање документа (раздвојене зарезом)",
+DlgDocMeDescr : "ÐžÐ¿Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°",
+DlgDocMeAuthor : "Ðутор",
+DlgDocMeCopy : "ÐуторÑка права",
+DlgDocPreview : "Изглед Ñтранице",
+
+// Templates Dialog
+Templates : "ОбраÑци",
+DlgTemplatesTitle : "ОбраÑци за Ñадржај",
+DlgTemplatesSelMsg : "Молимо Ð’Ð°Ñ Ð´Ð° одаберете образац који ће бити примењен на Ñтраницу (тренутни Ñадржај ће бити обриÑан):",
+DlgTemplatesLoading : "Учитавам лиÑту образаца. Мало Ñтрпљења...",
+DlgTemplatesNoTpl : "(Ðема дефиниÑаних образаца)",
+DlgTemplatesReplace : "Replace actual contents", //MISSING
+
+// About Dialog
+DlgAboutAboutTab : "О едитору",
+DlgAboutBrowserInfoTab : "Информације о претраживачу",
+DlgAboutLicenseTab : "License", //MISSING
+DlgAboutVersion : "верзија",
+DlgAboutInfo : "За више информација поÑетите",
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/sv.js b/httemplate/elements/fckeditor/editor/lang/sv.js
new file mode 100644
index 0000000..c62d6be
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/sv.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Swedish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Dölj verktygsfält",
+ToolbarExpand : "Visa verktygsfält",
+
+// Toolbar Items and Context Menu
+Save : "Spara",
+NewPage : "Ny sida",
+Preview : "Förhandsgranska",
+Cut : "Klipp ut",
+Copy : "Kopiera",
+Paste : "Klistra in",
+PasteText : "Klistra in som text",
+PasteWord : "Klistra in från Word",
+Print : "Skriv ut",
+SelectAll : "Markera allt",
+RemoveFormat : "Radera formatering",
+InsertLinkLbl : "Länk",
+InsertLink : "Infoga/Redigera länk",
+RemoveLink : "Radera länk",
+VisitLink : "Öppna länk",
+Anchor : "Infoga/Redigera ankarlänk",
+AnchorDelete : "Radera ankarlänk",
+InsertImageLbl : "Bild",
+InsertImage : "Infoga/Redigera bild",
+InsertFlashLbl : "Flash",
+InsertFlash : "Infoga/Redigera Flash",
+InsertTableLbl : "Tabell",
+InsertTable : "Infoga/Redigera tabell",
+InsertLineLbl : "Linje",
+InsertLine : "Infoga horisontal linje",
+InsertSpecialCharLbl: "Utökade tecken",
+InsertSpecialChar : "Klistra in utökat tecken",
+InsertSmileyLbl : "Smiley",
+InsertSmiley : "Infoga Smiley",
+About : "Om FCKeditor",
+Bold : "Fet",
+Italic : "Kursiv",
+Underline : "Understruken",
+StrikeThrough : "Genomstruken",
+Subscript : "Nedsänkta tecken",
+Superscript : "Upphöjda tecken",
+LeftJustify : "Vänsterjustera",
+CenterJustify : "Centrera",
+RightJustify : "Högerjustera",
+BlockJustify : "Justera till marginaler",
+DecreaseIndent : "Minska indrag",
+IncreaseIndent : "Öka indrag",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Skapa Div behållare",
+EditDiv : "Redigera Div behållare",
+DeleteDiv : "Radera Div behållare",
+Undo : "Ã…ngra",
+Redo : "Gör om",
+NumberedListLbl : "Numrerad lista",
+NumberedList : "Infoga/Radera numrerad lista",
+BulletedListLbl : "Punktlista",
+BulletedList : "Infoga/Radera punktlista",
+ShowTableBorders : "Visa tabellkant",
+ShowDetails : "Visa radbrytningar",
+Style : "Anpassad stil",
+FontFormat : "Teckenformat",
+Font : "Typsnitt",
+FontSize : "Storlek",
+TextColor : "Textfärg",
+BGColor : "Bakgrundsfärg",
+Source : "Källa",
+Find : "Sök",
+Replace : "Ersätt",
+SpellCheck : "Stavningskontroll",
+UniversalKeyboard : "Universellt tangentbord",
+PageBreakLbl : "Sidbrytning",
+PageBreak : "Infoga sidbrytning",
+
+Form : "Formulär",
+Checkbox : "Kryssruta",
+RadioButton : "Alternativknapp",
+TextField : "Textfält",
+Textarea : "Textruta",
+HiddenField : "Dolt fält",
+Button : "Knapp",
+SelectionField : "Flervalslista",
+ImageButton : "Bildknapp",
+
+FitWindow : "Anpassa till fönstrets storlek",
+ShowBlocks : "Visa block",
+
+// Context Menu
+EditLink : "Redigera länk",
+CellCM : "Cell",
+RowCM : "Rad",
+ColumnCM : "Kolumn",
+InsertRowAfter : "Lägg till Rad Efter",
+InsertRowBefore : "Lägg till Rad Före",
+DeleteRows : "Radera rad",
+InsertColumnAfter : "Lägg till Kolumn Efter",
+InsertColumnBefore : "Lägg till Kolumn Före",
+DeleteColumns : "Radera kolumn",
+InsertCellAfter : "Lägg till Cell Efter",
+InsertCellBefore : "Lägg till Cell Före",
+DeleteCells : "Radera celler",
+MergeCells : "Sammanfoga celler",
+MergeRight : "Sammanfoga Höger",
+MergeDown : "Sammanfoga Ner",
+HorizontalSplitCell : "Dela Cell Horisontellt",
+VerticalSplitCell : "Dela Cell Vertikalt",
+TableDelete : "Radera tabell",
+CellProperties : "Cellegenskaper",
+TableProperties : "Tabellegenskaper",
+ImageProperties : "Bildegenskaper",
+FlashProperties : "Flashegenskaper",
+
+AnchorProp : "Egenskaper för ankarlänk",
+ButtonProp : "Egenskaper för knapp",
+CheckboxProp : "Egenskaper för kryssruta",
+HiddenFieldProp : "Egenskaper för dolt fält",
+RadioButtonProp : "Egenskaper för alternativknapp",
+ImageButtonProp : "Egenskaper för bildknapp",
+TextFieldProp : "Egenskaper för textfält",
+SelectionFieldProp : "Egenskaper för flervalslista",
+TextareaProp : "Egenskaper för textruta",
+FormProp : "Egenskaper för formulär",
+
+FontFormats : "Normal;Formaterad;Adress;Rubrik 1;Rubrik 2;Rubrik 3;Rubrik 4;Rubrik 5;Rubrik 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Bearbetar XHTML. Var god vänta...",
+Done : "Klar",
+PasteWordConfirm : "Texten du vill klistra in verkar vara kopierad från Word. Vill du rensa innan du klistar in?",
+NotCompatiblePaste : "Denna åtgärd är inte tillgängligt för Internet Explorer version 5.5 eller högre. Vill du klistra in utan att rensa?",
+UnknownToolbarItem : "Okänt verktygsfält \"%1\"",
+UnknownCommand : "Okänt kommando \"%1\"",
+NotImplemented : "Kommandot finns ej",
+UnknownToolbarSet : "Verktygsfält \"%1\" finns ej",
+NoActiveX : "Din webläsares säkerhetsinställningar kan begränsa funktionaliteten. Du bör aktivera \"Kör ActiveX kontroller och plug-ins\". Fel och avsaknad av funktioner kan annars uppstå.",
+BrowseServerBlocked : "Kunde Ej öppna resursfönstret. Var god och avaktivera alla popup-blockerare.",
+DialogBlocked : "Kunde Ej öppna dialogfönstret. Var god och avaktivera alla popup-blockerare.",
+VisitLinkBlocked : "Kunde Ej öppna nytt fönster. Var god och avaktivera alla popup-blockerare.",
+
+// Dialogs
+DlgBtnOK : "OK",
+DlgBtnCancel : "Avbryt",
+DlgBtnClose : "Stäng",
+DlgBtnBrowseServer : "Bläddra på server",
+DlgAdvancedTag : "Avancerad",
+DlgOpOther : "Övrigt",
+DlgInfoTab : "Information",
+DlgAlertUrl : "Var god och ange en URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ej angivet>",
+DlgGenId : "Id",
+DlgGenLangDir : "Språkriktning",
+DlgGenLangDirLtr : "Vänster till Höger (VTH)",
+DlgGenLangDirRtl : "Höger till Vänster (HTV)",
+DlgGenLangCode : "Språkkod",
+DlgGenAccessKey : "Behörighetsnyckel",
+DlgGenName : "Namn",
+DlgGenTabIndex : "Tabindex",
+DlgGenLongDescr : "URL-beskrivning",
+DlgGenClass : "Stylesheet class",
+DlgGenTitle : "Titel",
+DlgGenContType : "Innehållstyp",
+DlgGenLinkCharset : "Teckenuppställning",
+DlgGenStyle : "Stil",
+
+// Image Dialog
+DlgImgTitle : "Bildegenskaper",
+DlgImgInfoTab : "Bildinformation",
+DlgImgBtnUpload : "Skicka till server",
+DlgImgURL : "URL",
+DlgImgUpload : "Ladda upp",
+DlgImgAlt : "Alternativ text",
+DlgImgWidth : "Bredd",
+DlgImgHeight : "Höjd",
+DlgImgLockRatio : "Lås höjd/bredd förhållanden",
+DlgBtnResetSize : "Återställ storlek",
+DlgImgBorder : "Kant",
+DlgImgHSpace : "Horis. marginal",
+DlgImgVSpace : "Vert. marginal",
+DlgImgAlign : "Justering",
+DlgImgAlignLeft : "Vänster",
+DlgImgAlignAbsBottom: "Absolut nederkant",
+DlgImgAlignAbsMiddle: "Absolut centrering",
+DlgImgAlignBaseline : "Baslinje",
+DlgImgAlignBottom : "Nederkant",
+DlgImgAlignMiddle : "Mitten",
+DlgImgAlignRight : "Höger",
+DlgImgAlignTextTop : "Text överkant",
+DlgImgAlignTop : "Överkant",
+DlgImgPreview : "Förhandsgranska",
+DlgImgAlertUrl : "Var god och ange bildens URL",
+DlgImgLinkTab : "Länk",
+
+// Flash Dialog
+DlgFlashTitle : "Flashegenskaper",
+DlgFlashChkPlay : "Automatisk uppspelning",
+DlgFlashChkLoop : "Upprepa/Loopa",
+DlgFlashChkMenu : "Aktivera Flashmeny",
+DlgFlashScale : "Skala",
+DlgFlashScaleAll : "Visa allt",
+DlgFlashScaleNoBorder : "Ingen ram",
+DlgFlashScaleFit : "Exakt passning",
+
+// Link Dialog
+DlgLnkWindowTitle : "Länk",
+DlgLnkInfoTab : "Länkinformation",
+DlgLnkTargetTab : "MÃ¥l",
+
+DlgLnkType : "Länktyp",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Ankare i sidan",
+DlgLnkTypeEMail : "E-post",
+DlgLnkProto : "Protokoll",
+DlgLnkProtoOther : "<övrigt>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Välj ett ankare",
+DlgLnkAnchorByName : "efter ankarnamn",
+DlgLnkAnchorById : "efter objektid",
+DlgLnkNoAnchors : "(Inga ankare kunde hittas)",
+DlgLnkEMail : "E-postadress",
+DlgLnkEMailSubject : "Ämne",
+DlgLnkEMailBody : "Innehåll",
+DlgLnkUpload : "Ladda upp",
+DlgLnkBtnUpload : "Skicka till servern",
+
+DlgLnkTarget : "MÃ¥l",
+DlgLnkTargetFrame : "<ram>",
+DlgLnkTargetPopup : "<popup-fönster>",
+DlgLnkTargetBlank : "Nytt fönster (_blank)",
+DlgLnkTargetParent : "Föregående Window (_parent)",
+DlgLnkTargetSelf : "Detta fönstret (_self)",
+DlgLnkTargetTop : "Översta fönstret (_top)",
+DlgLnkTargetFrameName : "MÃ¥lets ramnamn",
+DlgLnkPopWinName : "Popup-fönstrets namn",
+DlgLnkPopWinFeat : "Popup-fönstrets egenskaper",
+DlgLnkPopResize : "Kan ändra storlek",
+DlgLnkPopLocation : "Adressfält",
+DlgLnkPopMenu : "Menyfält",
+DlgLnkPopScroll : "Scrolllista",
+DlgLnkPopStatus : "Statusfält",
+DlgLnkPopToolbar : "Verktygsfält",
+DlgLnkPopFullScrn : "Helskärm (endast IE)",
+DlgLnkPopDependent : "Beroende (endest Netscape)",
+DlgLnkPopWidth : "Bredd",
+DlgLnkPopHeight : "Höjd",
+DlgLnkPopLeft : "Position från vänster",
+DlgLnkPopTop : "Position från sidans topp",
+
+DlnLnkMsgNoUrl : "Var god ange länkens URL",
+DlnLnkMsgNoEMail : "Var god ange E-postadress",
+DlnLnkMsgNoAnchor : "Var god ange ett ankare",
+DlnLnkMsgInvPopName : "Popup-rutans namn måste börja med en alfabetisk bokstav och får inte innehålla mellanslag",
+
+// Color Dialog
+DlgColorTitle : "Välj färg",
+DlgColorBtnClear : "Rensa",
+DlgColorHighlight : "Markera",
+DlgColorSelected : "Vald",
+
+// Smiley Dialog
+DlgSmileyTitle : "Infoga smiley",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Välj utökat tecken",
+
+// Table Dialog
+DlgTableTitle : "Tabellegenskaper",
+DlgTableRows : "Rader",
+DlgTableColumns : "Kolumner",
+DlgTableBorder : "Kantstorlek",
+DlgTableAlign : "Justering",
+DlgTableAlignNotSet : "<ej angivet>",
+DlgTableAlignLeft : "Vänster",
+DlgTableAlignCenter : "Centrerad",
+DlgTableAlignRight : "Höger",
+DlgTableWidth : "Bredd",
+DlgTableWidthPx : "pixlar",
+DlgTableWidthPc : "procent",
+DlgTableHeight : "Höjd",
+DlgTableCellSpace : "Cellavstånd",
+DlgTableCellPad : "Cellutfyllnad",
+DlgTableCaption : "Titel",
+DlgTableSummary : "Sammanfattning",
+DlgTableHeaders : "Rubrikrad",
+DlgTableHeadersNone : "Ingen",
+DlgTableHeadersColumn : "Första kolumnen",
+DlgTableHeadersRow : "Första raden",
+DlgTableHeadersBoth : "BÃ¥da",
+
+// Table Cell Dialog
+DlgCellTitle : "Cellegenskaper",
+DlgCellWidth : "Bredd",
+DlgCellWidthPx : "pixlar",
+DlgCellWidthPc : "procent",
+DlgCellHeight : "Höjd",
+DlgCellWordWrap : "Automatisk radbrytning",
+DlgCellWordWrapNotSet : "<Ej angivet>",
+DlgCellWordWrapYes : "Ja",
+DlgCellWordWrapNo : "Nej",
+DlgCellHorAlign : "Horisontal justering",
+DlgCellHorAlignNotSet : "<Ej angivet>",
+DlgCellHorAlignLeft : "Vänster",
+DlgCellHorAlignCenter : "Centrerad",
+DlgCellHorAlignRight: "Höger",
+DlgCellVerAlign : "Vertikal justering",
+DlgCellVerAlignNotSet : "<Ej angivet>",
+DlgCellVerAlignTop : "Topp",
+DlgCellVerAlignMiddle : "Mitten",
+DlgCellVerAlignBottom : "Nederkant",
+DlgCellVerAlignBaseline : "Underst",
+DlgCellType : "Cell Typ",
+DlgCellTypeData : "Data",
+DlgCellTypeHeader : "Titel",
+DlgCellRowSpan : "Radomfång",
+DlgCellCollSpan : "Kolumnomfång",
+DlgCellBackColor : "Bakgrundsfärg",
+DlgCellBorderColor : "Kantfärg",
+DlgCellBtnSelect : "Välj...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Sök och ersätt",
+
+// Find Dialog
+DlgFindTitle : "Sök",
+DlgFindFindBtn : "Sök",
+DlgFindNotFoundMsg : "Angiven text kunde ej hittas.",
+
+// Replace Dialog
+DlgReplaceTitle : "Ersätt",
+DlgReplaceFindLbl : "Sök efter:",
+DlgReplaceReplaceLbl : "Ersätt med:",
+DlgReplaceCaseChk : "Skiftläge",
+DlgReplaceReplaceBtn : "Ersätt",
+DlgReplaceReplAllBtn : "Ersätt alla",
+DlgReplaceWordChk : "Inkludera hela ord",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Säkerhetsinställningar i Er webläsare tillåter inte åtgården Klipp ut. Använd (Ctrl+X) istället.",
+PasteErrorCopy : "Säkerhetsinställningar i Er webläsare tillåter inte åtgården Kopiera. Använd (Ctrl+C) istället",
+
+PasteAsText : "Klistra in som vanlig text",
+PasteFromWord : "Klistra in från Word",
+
+DlgPasteMsg2 : "Var god och klistra in Er text i rutan nedan genom att använda (<STRONG>Ctrl+V</STRONG>) klicka sen på <STRONG>OK</STRONG>.",
+DlgPasteSec : "På grund av din webläsares säkerhetsinställningar kan verktyget inte få åtkomst till urklippsdatan. Var god och använd detta fönster istället.",
+DlgPasteIgnoreFont : "Ignorera typsnittsdefinitioner",
+DlgPasteRemoveStyles : "Radera Stildefinitioner",
+
+// Color Picker
+ColorAutomatic : "Automatisk",
+ColorMoreColors : "Fler färger...",
+
+// Document Properties
+DocProps : "Dokumentegenskaper",
+
+// Anchor Dialog
+DlgAnchorTitle : "Ankaregenskaper",
+DlgAnchorName : "Ankarnamn",
+DlgAnchorErrorName : "Var god ange ett ankarnamn",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Saknas i ordlistan",
+DlgSpellChangeTo : "Ändra till",
+DlgSpellBtnIgnore : "Ignorera",
+DlgSpellBtnIgnoreAll : "Ignorera alla",
+DlgSpellBtnReplace : "Ersätt",
+DlgSpellBtnReplaceAll : "Ersätt alla",
+DlgSpellBtnUndo : "Ã…ngra",
+DlgSpellNoSuggestions : "- Förslag saknas -",
+DlgSpellProgress : "Stavningskontroll pågår...",
+DlgSpellNoMispell : "Stavningskontroll slutförd: Inga stavfel påträffades.",
+DlgSpellNoChanges : "Stavningskontroll slutförd: Inga ord rättades.",
+DlgSpellOneChange : "Stavningskontroll slutförd: Ett ord rättades.",
+DlgSpellManyChanges : "Stavningskontroll slutförd: %1 ord rättades.",
+
+IeSpellDownload : "Stavningskontrollen är ej installerad. Vill du göra det nu?",
+
+// Button Dialog
+DlgButtonText : "Text (Värde)",
+DlgButtonType : "Typ",
+DlgButtonTypeBtn : "Knapp",
+DlgButtonTypeSbm : "Skicka",
+DlgButtonTypeRst : "Återställ",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Namn",
+DlgCheckboxValue : "Värde",
+DlgCheckboxSelected : "Vald",
+
+// Form Dialog
+DlgFormName : "Namn",
+DlgFormAction : "Funktion",
+DlgFormMethod : "Metod",
+
+// Select Field Dialog
+DlgSelectName : "Namn",
+DlgSelectValue : "Värde",
+DlgSelectSize : "Storlek",
+DlgSelectLines : "Linjer",
+DlgSelectChkMulti : "Tillåt flerval",
+DlgSelectOpAvail : "Befintliga val",
+DlgSelectOpText : "Text",
+DlgSelectOpValue : "Värde",
+DlgSelectBtnAdd : "Lägg till",
+DlgSelectBtnModify : "Redigera",
+DlgSelectBtnUp : "Upp",
+DlgSelectBtnDown : "Ner",
+DlgSelectBtnSetValue : "Markera som valt värde",
+DlgSelectBtnDelete : "Radera",
+
+// Textarea Dialog
+DlgTextareaName : "Namn",
+DlgTextareaCols : "Kolumner",
+DlgTextareaRows : "Rader",
+
+// Text Field Dialog
+DlgTextName : "Namn",
+DlgTextValue : "Värde",
+DlgTextCharWidth : "Teckenbredd",
+DlgTextMaxChars : "Max antal tecken",
+DlgTextType : "Typ",
+DlgTextTypeText : "Text",
+DlgTextTypePass : "Lösenord",
+
+// Hidden Field Dialog
+DlgHiddenName : "Namn",
+DlgHiddenValue : "Värde",
+
+// Bulleted List Dialog
+BulletedListProp : "Egenskaper för punktlista",
+NumberedListProp : "Egenskaper för numrerad lista",
+DlgLstStart : "Start",
+DlgLstType : "Typ",
+DlgLstTypeCircle : "Cirkel",
+DlgLstTypeDisc : "Punkt",
+DlgLstTypeSquare : "Ruta",
+DlgLstTypeNumbers : "Nummer (1, 2, 3)",
+DlgLstTypeLCase : "Gemener (a, b, c)",
+DlgLstTypeUCase : "Versaler (A, B, C)",
+DlgLstTypeSRoman : "Små romerska siffror (i, ii, iii)",
+DlgLstTypeLRoman : "Stora romerska siffror (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Allmän",
+DlgDocBackTab : "Bakgrund",
+DlgDocColorsTab : "Färg och marginal",
+DlgDocMetaTab : "Metadata",
+
+DlgDocPageTitle : "Sidtitel",
+DlgDocLangDir : "Språkriktning",
+DlgDocLangDirLTR : "Vänster till Höger",
+DlgDocLangDirRTL : "Höger till Vänster",
+DlgDocLangCode : "Språkkod",
+DlgDocCharSet : "Teckenuppsättningar",
+DlgDocCharSetCE : "Central Europa",
+DlgDocCharSetCT : "Traditionell Kinesisk (Big5)",
+DlgDocCharSetCR : "Kyrillisk",
+DlgDocCharSetGR : "Grekiska",
+DlgDocCharSetJP : "Japanska",
+DlgDocCharSetKR : "Koreanska",
+DlgDocCharSetTR : "Turkiska",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Väst Europa",
+DlgDocCharSetOther : "Övriga teckenuppsättningar",
+
+DlgDocDocType : "Sidhuvud",
+DlgDocDocTypeOther : "Övriga sidhuvuden",
+DlgDocIncXHTML : "Inkludera XHTML deklaration",
+DlgDocBgColor : "Bakgrundsfärg",
+DlgDocBgImage : "Bakgrundsbildens URL",
+DlgDocBgNoScroll : "Fast bakgrund",
+DlgDocCText : "Text",
+DlgDocCLink : "Länk",
+DlgDocCVisited : "Besökt länk",
+DlgDocCActive : "Aktiv länk",
+DlgDocMargins : "Sidmarginal",
+DlgDocMaTop : "Topp",
+DlgDocMaLeft : "Vänster",
+DlgDocMaRight : "Höger",
+DlgDocMaBottom : "Botten",
+DlgDocMeIndex : "Sidans nyckelord",
+DlgDocMeDescr : "Sidans beskrivning",
+DlgDocMeAuthor : "Författare",
+DlgDocMeCopy : "Upphovsrätt",
+DlgDocPreview : "Förhandsgranska",
+
+// Templates Dialog
+Templates : "Sidmallar",
+DlgTemplatesTitle : "Sidmallar",
+DlgTemplatesSelMsg : "Var god välj en mall att använda med editorn<br>(allt nuvarande innehåll raderas):",
+DlgTemplatesLoading : "Laddar mallar. Var god vänta...",
+DlgTemplatesNoTpl : "(Ingen mall är vald)",
+DlgTemplatesReplace : "Ersätt aktuellt innehåll",
+
+// About Dialog
+DlgAboutAboutTab : "Om",
+DlgAboutBrowserInfoTab : "Webläsare",
+DlgAboutLicenseTab : "Licens",
+DlgAboutVersion : "Version",
+DlgAboutInfo : "För mer information se",
+
+// Div Dialog
+DlgDivGeneralTab : "Allmänt",
+DlgDivAdvancedTab : "Avancerat",
+DlgDivStyle : "Stil",
+DlgDivInlineStyle : "Inbäddad stil",
+
+ScaytTitle : "SCAYT",
+ScaytTitleOptions : "Alternativ",
+ScaytTitleLangs : "Språk",
+ScaytTitleAbout : "Om"
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/th.js b/httemplate/elements/fckeditor/editor/lang/th.js
new file mode 100644
index 0000000..7f3b1a4
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/th.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Thai language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "ซ่อนà¹à¸–บเครื่องมือ",
+ToolbarExpand : "à¹à¸ªà¸”งà¹à¸–บเครื่องมือ",
+
+// Toolbar Items and Context Menu
+Save : "บันทึà¸",
+NewPage : "สร้างหน้าเอà¸à¸ªà¸²à¸£à¹ƒà¸«à¸¡à¹ˆ",
+Preview : "ดูหน้าเอà¸à¸ªà¸²à¸£à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡",
+Cut : "ตัด",
+Copy : "สำเนา",
+Paste : "วาง",
+PasteText : "วางสำเนาจาà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸˜à¸£à¸£à¸¡à¸”า",
+PasteWord : "วางสำเนาจาà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¹€à¸§à¸´à¸£à¹Œà¸”",
+Print : "สั่งพิมพ์",
+SelectAll : "เลือà¸à¸—ั้งหมด",
+RemoveFormat : "ล้างรูปà¹à¸šà¸š",
+InsertLinkLbl : "ลิงค์เชื่อมโยงเว็บ อีเมล์ รูปภาพ หรือไฟล์อื่นๆ",
+InsertLink : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ลิงค์",
+RemoveLink : "ลบ ลิงค์",
+VisitLink : "Open Link", //MISSING
+Anchor : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข Anchor",
+AnchorDelete : "Remove Anchor", //MISSING
+InsertImageLbl : "รูปภาพ",
+InsertImage : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข รูปภาพ",
+InsertFlashLbl : "ไฟล์ Flash",
+InsertFlash : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ไฟล์ Flash",
+InsertTableLbl : "ตาราง",
+InsertTable : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ตาราง",
+InsertLineLbl : "เส้นคั่นบรรทัด",
+InsertLine : "à¹à¸—รà¸à¹€à¸ªà¹‰à¸™à¸„ั่นบรรทัด",
+InsertSpecialCharLbl: "ตัวอัà¸à¸©à¸£à¸žà¸´à¹€à¸¨à¸©",
+InsertSpecialChar : "à¹à¸—รà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸žà¸´à¹€à¸¨à¸©",
+InsertSmileyLbl : "รูปสื่ออารมณ์",
+InsertSmiley : "à¹à¸—รà¸à¸£à¸¹à¸›à¸ªà¸·à¹ˆà¸­à¸­à¸²à¸£à¸¡à¸“์",
+About : "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹‚ปรà¹à¸à¸£à¸¡ FCKeditor",
+Bold : "ตัวหนา",
+Italic : "ตัวเอียง",
+Underline : "ตัวขีดเส้นใต้",
+StrikeThrough : "ตัวขีดเส้นทับ",
+Subscript : "ตัวห้อย",
+Superscript : "ตัวยà¸",
+LeftJustify : "จัดชิดซ้าย",
+CenterJustify : "จัดà¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡",
+RightJustify : "จัดชิดขวา",
+BlockJustify : "จัดพอดีหน้าà¸à¸£à¸°à¸”าษ",
+DecreaseIndent : "ลดระยะย่อหน้า",
+IncreaseIndent : "เพิ่มระยะย่อหน้า",
+Blockquote : "Blockquote", //MISSING
+CreateDiv : "Create Div Container", //MISSING
+EditDiv : "Edit Div Container", //MISSING
+DeleteDiv : "Remove Div Container", //MISSING
+Undo : "ยà¸à¹€à¸¥à¸´à¸à¸„ำสั่ง",
+Redo : "ทำซ้ำคำสั่ง",
+NumberedListLbl : "ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸•à¸±à¸§à¹€à¸¥à¸‚",
+NumberedList : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸•à¸±à¸§à¹€à¸¥à¸‚",
+BulletedListLbl : "ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸ªà¸±à¸à¸¥à¸±à¸à¸©à¸“์",
+BulletedList : "à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸ªà¸±à¸à¸¥à¸±à¸à¸©à¸“์",
+ShowTableBorders : "à¹à¸ªà¸”งขอบของตาราง",
+ShowDetails : "à¹à¸ªà¸”งรายละเอียด",
+Style : "ลัà¸à¸©à¸“ะ",
+FontFormat : "รูปà¹à¸šà¸š",
+Font : "à¹à¸šà¸šà¸­à¸±à¸à¸©à¸£",
+FontSize : "ขนาด",
+TextColor : "สีตัวอัà¸à¸©à¸£",
+BGColor : "สีพื้นหลัง",
+Source : "ดูรหัส HTML",
+Find : "ค้นหา",
+Replace : "ค้นหาà¹à¸¥à¸°à¹à¸—นที่",
+SpellCheck : "ตรวจà¸à¸²à¸£à¸ªà¸°à¸à¸”คำ",
+UniversalKeyboard : "คีย์บอร์ดหลาà¸à¸ à¸²à¸©à¸²",
+PageBreakLbl : "ใส่ตัวà¹à¸šà¹ˆà¸‡à¸«à¸™à¹‰à¸² Page Break",
+PageBreak : "à¹à¸—รà¸à¸•à¸±à¸§à¹à¸šà¹ˆà¸‡à¸«à¸™à¹‰à¸² Page Break",
+
+Form : "à¹à¸šà¸šà¸Ÿà¸­à¸£à¹Œà¸¡",
+Checkbox : "เช็คบ๊อà¸",
+RadioButton : "เรดิโอบัตตอน",
+TextField : "เท็à¸à¸‹à¹Œà¸Ÿà¸´à¸¥à¸”์",
+Textarea : "เท็à¸à¸‹à¹Œà¹à¸­à¹€à¸£à¸µà¸¢",
+HiddenField : "ฮิดเดนฟิลด์",
+Button : "ปุ่ม",
+SelectionField : "à¹à¸–บตัวเลือà¸",
+ImageButton : "ปุ่มà¹à¸šà¸šà¸£à¸¹à¸›à¸ à¸²à¸ž",
+
+FitWindow : "ขยายขนาดตัวอีดิตเตอร์",
+ShowBlocks : "Show Blocks", //MISSING
+
+// Context Menu
+EditLink : "à¹à¸à¹‰à¹„ข ลิงค์",
+CellCM : "ช่องตาราง",
+RowCM : "à¹à¸–ว",
+ColumnCM : "คอลัมน์",
+InsertRowAfter : "Insert Row After", //MISSING
+InsertRowBefore : "Insert Row Before", //MISSING
+DeleteRows : "ลบà¹à¸–ว",
+InsertColumnAfter : "Insert Column After", //MISSING
+InsertColumnBefore : "Insert Column Before", //MISSING
+DeleteColumns : "ลบสดมน์",
+InsertCellAfter : "Insert Cell After", //MISSING
+InsertCellBefore : "Insert Cell Before", //MISSING
+DeleteCells : "ลบช่อง",
+MergeCells : "ผสานช่อง",
+MergeRight : "Merge Right", //MISSING
+MergeDown : "Merge Down", //MISSING
+HorizontalSplitCell : "Split Cell Horizontally", //MISSING
+VerticalSplitCell : "Split Cell Vertically", //MISSING
+TableDelete : "ลบตาราง",
+CellProperties : "คุณสมบัติของช่อง",
+TableProperties : "คุณสมบัติของตาราง",
+ImageProperties : "คุณสมบัติของรูปภาพ",
+FlashProperties : "คุณสมบัติของไฟล์ Flash",
+
+AnchorProp : "รายละเอียด Anchor",
+ButtonProp : "รายละเอียดของ ปุ่ม",
+CheckboxProp : "คุณสมบัติของ เช็คบ๊อà¸",
+HiddenFieldProp : "คุณสมบัติของ ฮิดเดนฟิลด์",
+RadioButtonProp : "คุณสมบัติของ เรดิโอบัตตอน",
+ImageButtonProp : "คุณสมบัติของ ปุ่มà¹à¸šà¸šà¸£à¸¹à¸›à¸ à¸²à¸ž",
+TextFieldProp : "คุณสมบัติของ เท็à¸à¸‹à¹Œà¸Ÿà¸´à¸¥à¸”์",
+SelectionFieldProp : "คุณสมบัติของ à¹à¸–บตัวเลือà¸",
+TextareaProp : "คุณสมบัติของ เท็à¸à¹à¸­à¹€à¸£à¸µà¸¢",
+FormProp : "คุณสมบัติของ à¹à¸šà¸šà¸Ÿà¸­à¸£à¹Œà¸¡",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Paragraph (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "โปรà¹à¸à¸£à¸¡à¸à¸³à¸¥à¸±à¸‡à¸—ำงานด้วยเทคโนโลยี XHTML à¸à¸£à¸¸à¸“ารอสัà¸à¸„รู่...",
+Done : "โปรà¹à¸à¸£à¸¡à¸—ำงานเสร็จสมบูรณ์",
+PasteWordConfirm : "ข้อมูลที่ท่านต้องà¸à¸²à¸£à¸§à¸²à¸‡à¸¥à¸‡à¹ƒà¸™à¹à¸œà¹ˆà¸™à¸‡à¸²à¸™ ถูà¸à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¸ˆà¸²à¸à¹‚ปรà¹à¸à¸£à¸¡à¹€à¸§à¸´à¸£à¹Œà¸”. ท่านต้องà¸à¸²à¸£à¸¥à¹‰à¸²à¸‡à¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่มาจาà¸à¹‚ปรà¹à¸à¸£à¸¡à¹€à¸§à¸´à¸£à¹Œà¸”หรือไม่?",
+NotCompatiblePaste : "คำสั่งนี้ทำงานในโปรà¹à¸à¸£à¸¡à¸—่องเว็บ Internet Explorer version รุ่น 5.5 หรือใหม่à¸à¸§à¹ˆà¸²à¹€à¸—่านั้น. ท่านต้องà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¹‚ดยไม่ล้างรูปà¹à¸šà¸šà¸—ี่มาจาà¸à¹‚ปรà¹à¸à¸£à¸¡à¹€à¸§à¸´à¸£à¹Œà¸”หรือไม่?",
+UnknownToolbarItem : "ไม่สามารถระบุปุ่มเครื่องมือได้ \"%1\"",
+UnknownCommand : "ไม่สามารถระบุชื่อคำสั่งได้ \"%1\"",
+NotImplemented : "ไม่สามารถใช้งานคำสั่งได้",
+UnknownToolbarSet : "ไม่มีà¸à¸²à¸£à¸•à¸´à¸”ตั้งชุดคำสั่งในà¹à¸–บเครื่องมือ \"%1\" à¸à¸£à¸¸à¸“าติดต่อผู้ดูà¹à¸¥à¸£à¸°à¸šà¸š",
+NoActiveX : "โปรà¹à¸à¸£à¸¡à¸—่องอินเตอร์เน็ตของท่านไม่อนุà¸à¸²à¸•à¸´à¹ƒà¸«à¹‰à¸­à¸µà¸”ิตเตอร์ทำงาน \"Run ActiveX controls and plug-ins\". หาà¸à¹„ม่อนุà¸à¸²à¸•à¸´à¹ƒà¸«à¹‰à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ ActiveX controls ท่านจะไม่สามารถใช้งานได้อย่างเต็มประสิทธิภาพ.",
+BrowseServerBlocked : "เปิดหน้าต่างป๊อบอัพเพื่อทำงานต่อไม่ได้ à¸à¸£à¸¸à¸“าปิดเครื่องมือป้องà¸à¸±à¸™à¸›à¹Šà¸­à¸šà¸­à¸±à¸žà¹ƒà¸™à¹‚ปรà¹à¸à¸£à¸¡à¸—่องอินเตอร์เน็ตของท่านด้วย",
+DialogBlocked : "เปิดหน้าต่างป๊อบอัพเพื่อทำงานต่อไม่ได้ à¸à¸£à¸¸à¸“าปิดเครื่องมือป้องà¸à¸±à¸™à¸›à¹Šà¸­à¸šà¸­à¸±à¸žà¹ƒà¸™à¹‚ปรà¹à¸à¸£à¸¡à¸—่องอินเตอร์เน็ตของท่านด้วย",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ตà¸à¸¥à¸‡",
+DlgBtnCancel : "ยà¸à¹€à¸¥à¸´à¸",
+DlgBtnClose : "ปิด",
+DlgBtnBrowseServer : "เปิดหน้าต่างจัดà¸à¸²à¸£à¹„ฟล์อัพโหลด",
+DlgAdvancedTag : "ขั้นสูง",
+DlgOpOther : "<อื่นๆ>",
+DlgInfoTab : "อินโฟ",
+DlgAlertUrl : "à¸à¸£à¸¸à¸“าระบุ URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<ไม่ระบุ>",
+DlgGenId : "ไอดี",
+DlgGenLangDir : "à¸à¸²à¸£à¹€à¸‚ียน-อ่านภาษา",
+DlgGenLangDirLtr : "จาà¸à¸‹à¹‰à¸²à¸¢à¹„ปขวา (LTR)",
+DlgGenLangDirRtl : "จาà¸à¸‚วามาซ้าย (RTL)",
+DlgGenLangCode : "รหัสภาษา",
+DlgGenAccessKey : "à¹à¸­à¸„เซส คีย์",
+DlgGenName : "ชื่อ",
+DlgGenTabIndex : "ลำดับของ à¹à¸—็บ",
+DlgGenLongDescr : "คำอธิบายประà¸à¸­à¸š URL",
+DlgGenClass : "คลาสของไฟล์à¸à¸³à¸«à¸™à¸”ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล",
+DlgGenTitle : "คำเà¸à¸£à¸´à¹ˆà¸™à¸™à¸³",
+DlgGenContType : "ชนิดของคำเà¸à¸£à¸´à¹ˆà¸™à¸™à¸³",
+DlgGenLinkCharset : "ลิงค์เชื่อมโยงไปยังชุดตัวอัà¸à¸©à¸£",
+DlgGenStyle : "ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล",
+
+// Image Dialog
+DlgImgTitle : "คุณสมบัติของ รูปภาพ",
+DlgImgInfoTab : "ข้อมูลของรูปภาพ",
+DlgImgBtnUpload : "อัพโหลดไฟล์ไปเà¸à¹‡à¸šà¹„ว้ที่เครื่องà¹à¸¡à¹ˆà¸‚่าย (เซิร์ฟเวอร์)",
+DlgImgURL : "ที่อยู่อ้างอิง URL",
+DlgImgUpload : "อัพโหลดไฟล์",
+DlgImgAlt : "คำประà¸à¸­à¸šà¸£à¸¹à¸›à¸ à¸²à¸ž",
+DlgImgWidth : "ความà¸à¸§à¹‰à¸²à¸‡",
+DlgImgHeight : "ความสูง",
+DlgImgLockRatio : "à¸à¸³à¸«à¸™à¸”อัตราส่วน à¸à¸§à¹‰à¸²à¸‡-สูง à¹à¸šà¸šà¸„งที่",
+DlgBtnResetSize : "à¸à¸³à¸«à¸™à¸”รูปเท่าขนาดจริง",
+DlgImgBorder : "ขนาดขอบรูป",
+DlgImgHSpace : "ระยะà¹à¸™à¸§à¸™à¸­à¸™",
+DlgImgVSpace : "ระยะà¹à¸™à¸§à¸•à¸±à¹‰à¸‡",
+DlgImgAlign : "à¸à¸²à¸£à¸ˆà¸±à¸”วาง",
+DlgImgAlignLeft : "ชิดซ้าย",
+DlgImgAlignAbsBottom: "ชิดด้านล่างสุด",
+DlgImgAlignAbsMiddle: "à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡",
+DlgImgAlignBaseline : "ชิดบรรทัด",
+DlgImgAlignBottom : "ชิดด้านล่าง",
+DlgImgAlignMiddle : "à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡à¹à¸™à¸§à¸•à¸±à¹‰à¸‡",
+DlgImgAlignRight : "ชิดขวา",
+DlgImgAlignTextTop : "ใต้ตัวอัà¸à¸©à¸£",
+DlgImgAlignTop : "บนสุด",
+DlgImgPreview : "หน้าเอà¸à¸ªà¸²à¸£à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡",
+DlgImgAlertUrl : "à¸à¸£à¸¸à¸“าระบุที่อยู่อ้างอิงออนไลน์ของไฟล์รูปภาพ (URL)",
+DlgImgLinkTab : "ลิ้งค์",
+
+// Flash Dialog
+DlgFlashTitle : "คุณสมบัติของไฟล์ Flash",
+DlgFlashChkPlay : "เล่นอัตโนมัติ Auto Play",
+DlgFlashChkLoop : "เล่นวนรอบ Loop",
+DlgFlashChkMenu : "ให้ใช้งานเมนูของ Flash",
+DlgFlashScale : "อัตราส่วน Scale",
+DlgFlashScaleAll : "à¹à¸ªà¸”งให้เห็นทั้งหมด Show all",
+DlgFlashScaleNoBorder : "ไม่à¹à¸ªà¸”งเส้นขอบ No Border",
+DlgFlashScaleFit : "à¹à¸ªà¸”งให้พอดีà¸à¸±à¸šà¸žà¸·à¹‰à¸™à¸—ี่ Exact Fit",
+
+// Link Dialog
+DlgLnkWindowTitle : "ลิงค์เชื่อมโยงเว็บ อีเมล์ รูปภาพ หรือไฟล์อื่นๆ",
+DlgLnkInfoTab : "รายละเอียด",
+DlgLnkTargetTab : "à¸à¸²à¸£à¹€à¸›à¸´à¸”หน้าจอ",
+
+DlgLnkType : "ประเภทของลิงค์",
+DlgLnkTypeURL : "ที่อยู่อ้างอิงออนไลน์ (URL)",
+DlgLnkTypeAnchor : "จุดเชื่อมโยง (Anchor)",
+DlgLnkTypeEMail : "ส่งอีเมล์ (E-Mail)",
+DlgLnkProto : "โปรโตคอล",
+DlgLnkProtoOther : "<อื่นๆ>",
+DlgLnkURL : "ที่อยู่อ้างอิงออนไลน์ (URL)",
+DlgLnkAnchorSel : "ระบุข้อมูลของจุดเชื่อมโยง (Anchor)",
+DlgLnkAnchorByName : "ชื่อ",
+DlgLnkAnchorById : "ไอดี",
+DlgLnkNoAnchors : "(ยังไม่มีจุดเชื่อมโยงภายในหน้าเอà¸à¸ªà¸²à¸£à¸™à¸µà¹‰)",
+DlgLnkEMail : "อีเมล์ (E-Mail)",
+DlgLnkEMailSubject : "หัวเรื่อง",
+DlgLnkEMailBody : "ข้อความ",
+DlgLnkUpload : "อัพโหลดไฟล์",
+DlgLnkBtnUpload : "บันทึà¸à¹„ฟล์ไว้บนเซิร์ฟเวอร์",
+
+DlgLnkTarget : "à¸à¸²à¸£à¹€à¸›à¸´à¸”หน้าลิงค์",
+DlgLnkTargetFrame : "<เปิดในเฟรม>",
+DlgLnkTargetPopup : "<เปิดหน้าจอเล็ภ(Pop-up)>",
+DlgLnkTargetBlank : "เปิดหน้าจอใหม่ (_blank)",
+DlgLnkTargetParent : "เปิดในหน้าหลัภ(_parent)",
+DlgLnkTargetSelf : "เปิดในหน้าปัจจุบัน (_self)",
+DlgLnkTargetTop : "เปิดในหน้าบนสุด (_top)",
+DlgLnkTargetFrameName : "ชื่อทาร์เà¸à¹‡à¸•à¹€à¸Ÿà¸£à¸¡",
+DlgLnkPopWinName : "ระบุชื่อหน้าจอเล็ภ(Pop-up)",
+DlgLnkPopWinFeat : "คุณสมบัติของหน้าจอเล็ภ(Pop-up)",
+DlgLnkPopResize : "ปรับขนาดหน้าจอ",
+DlgLnkPopLocation : "à¹à¸ªà¸”งที่อยู่ของไฟล์",
+DlgLnkPopMenu : "à¹à¸ªà¸”งà¹à¸–บเมนู",
+DlgLnkPopScroll : "à¹à¸ªà¸”งà¹à¸–บเลื่อน",
+DlgLnkPopStatus : "à¹à¸ªà¸”งà¹à¸–บสถานะ",
+DlgLnkPopToolbar : "à¹à¸ªà¸”งà¹à¸–บเครื่องมือ",
+DlgLnkPopFullScrn : "à¹à¸ªà¸”งเต็มหน้าจอ (IE5.5++ เท่านั้น)",
+DlgLnkPopDependent : "à¹à¸ªà¸”งเต็มหน้าจอ (Netscape)",
+DlgLnkPopWidth : "à¸à¸§à¹‰à¸²à¸‡",
+DlgLnkPopHeight : "สูง",
+DlgLnkPopLeft : "พิà¸à¸±à¸”ซ้าย (Left Position)",
+DlgLnkPopTop : "พิà¸à¸±à¸”บน (Top Position)",
+
+DlnLnkMsgNoUrl : "à¸à¸£à¸¸à¸“าระบุที่อยู่อ้างอิงออนไลน์ (URL)",
+DlnLnkMsgNoEMail : "à¸à¸£à¸¸à¸“าระบุอีเมล์ (E-mail)",
+DlnLnkMsgNoAnchor : "à¸à¸£à¸¸à¸“าระบุจุดเชื่อมโยง (Anchor)",
+DlnLnkMsgInvPopName : "ชื่อของหน้าต่างป๊อบอัพ จะต้องขึ้นต้นด้วยตัวอัà¸à¸©à¸£à¹€à¸—่านั้น à¹à¸¥à¸°à¸•à¹‰à¸­à¸‡à¹„ม่มีช่องว่างในชื่อ",
+
+// Color Dialog
+DlgColorTitle : "เลือà¸à¸ªà¸µ",
+DlgColorBtnClear : "ล้างค่ารหัสสี",
+DlgColorHighlight : "ตัวอย่างสี",
+DlgColorSelected : "สีที่เลือà¸",
+
+// Smiley Dialog
+DlgSmileyTitle : "à¹à¸—รà¸à¸ªà¸±à¸à¸¥à¸±à¸à¸©à¸“์สื่ออารมณ์",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "à¹à¸—รà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸žà¸´à¹€à¸¨à¸©",
+
+// Table Dialog
+DlgTableTitle : "คุณสมบัติของ ตาราง",
+DlgTableRows : "à¹à¸–ว",
+DlgTableColumns : "สดมน์",
+DlgTableBorder : "ขนาดเส้นขอบ",
+DlgTableAlign : "à¸à¸²à¸£à¸ˆà¸±à¸”ตำà¹à¸«à¸™à¹ˆà¸‡",
+DlgTableAlignNotSet : "<ไม่ระบุ>",
+DlgTableAlignLeft : "ชิดซ้าย",
+DlgTableAlignCenter : "à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡",
+DlgTableAlignRight : "ชิดขวา",
+DlgTableWidth : "à¸à¸§à¹‰à¸²à¸‡",
+DlgTableWidthPx : "จุดสี",
+DlgTableWidthPc : "เปอร์เซ็น",
+DlgTableHeight : "สูง",
+DlgTableCellSpace : "ระยะà¹à¸™à¸§à¸™à¸­à¸™à¸™",
+DlgTableCellPad : "ระยะà¹à¸™à¸§à¸•à¸±à¹‰à¸‡",
+DlgTableCaption : "หัวเรื่องของตาราง",
+DlgTableSummary : "สรุปความ",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "คุณสมบัติของ ช่อง",
+DlgCellWidth : "à¸à¸§à¹‰à¸²à¸‡",
+DlgCellWidthPx : "จุดสี",
+DlgCellWidthPc : "เปอร์เซ็น",
+DlgCellHeight : "สูง",
+DlgCellWordWrap : "ตัดบรรทัดอัตโนมัติ",
+DlgCellWordWrapNotSet : "<ไม่ระบุ>",
+DlgCellWordWrapYes : "ใ่ช่",
+DlgCellWordWrapNo : "ไม่",
+DlgCellHorAlign : "à¸à¸²à¸£à¸ˆà¸±à¸”วางà¹à¸™à¸§à¸™à¸­à¸™",
+DlgCellHorAlignNotSet : "<ไม่ระบุ>",
+DlgCellHorAlignLeft : "ชิดซ้าย",
+DlgCellHorAlignCenter : "à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡",
+DlgCellHorAlignRight: "ชิดขวา",
+DlgCellVerAlign : "à¸à¸²à¸£à¸ˆà¸±à¸”วางà¹à¸™à¸§à¸•à¸±à¹‰à¸‡",
+DlgCellVerAlignNotSet : "<ไม่ระบุ>",
+DlgCellVerAlignTop : "บนสุด",
+DlgCellVerAlignMiddle : "à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡",
+DlgCellVerAlignBottom : "ล่างสุด",
+DlgCellVerAlignBaseline : "อิงบรรทัด",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "จำนวนà¹à¸–วที่คร่อมà¸à¸±à¸™",
+DlgCellCollSpan : "จำนวนสดมน์ที่คร่อมà¸à¸±à¸™",
+DlgCellBackColor : "สีพื้นหลัง",
+DlgCellBorderColor : "สีเส้นขอบ",
+DlgCellBtnSelect : "เลือà¸..",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Find and Replace", //MISSING
+
+// Find Dialog
+DlgFindTitle : "ค้นหา",
+DlgFindFindBtn : "ค้นหา",
+DlgFindNotFoundMsg : "ไม่พบคำที่ค้นหา.",
+
+// Replace Dialog
+DlgReplaceTitle : "ค้นหาà¹à¸¥à¸°à¹à¸—นที่",
+DlgReplaceFindLbl : "ค้นหาคำว่า:",
+DlgReplaceReplaceLbl : "à¹à¸—นที่ด้วย:",
+DlgReplaceCaseChk : "ตัวโหà¸à¹ˆ-เล็ภต้องตรงà¸à¸±à¸™",
+DlgReplaceReplaceBtn : "à¹à¸—นที่",
+DlgReplaceReplAllBtn : "à¹à¸—นที่ทั้งหมดที่พบ",
+DlgReplaceWordChk : "ต้องตรงà¸à¸±à¸™à¸—ุà¸à¸„ำ",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ไม่สามารถตัดข้อความที่เลือà¸à¹„ว้ได้เนื่องจาà¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าระดับความปลอดภัย. à¸à¸£à¸¸à¸“าใช้ปุ่มลัดเพื่อวางข้อความà¹à¸—น (à¸à¸”ปุ่ม Ctrl à¹à¸¥à¸°à¸•à¸±à¸§ X พร้อมà¸à¸±à¸™).",
+PasteErrorCopy : "ไม่สามารถสำเนาข้อความที่เลือà¸à¹„ว้ได้เนื่องจาà¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าระดับความปลอดภัย. à¸à¸£à¸¸à¸“าใช้ปุ่มลัดเพื่อวางข้อความà¹à¸—น (à¸à¸”ปุ่ม Ctrl à¹à¸¥à¸°à¸•à¸±à¸§ C พร้อมà¸à¸±à¸™).",
+
+PasteAsText : "วางà¹à¸šà¸šà¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸˜à¸£à¸£à¸¡à¸”า",
+PasteFromWord : "วางà¹à¸šà¸šà¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸ˆà¸²à¸à¹‚ปรà¹à¸à¸£à¸¡à¹€à¸§à¸´à¸£à¹Œà¸”",
+
+DlgPasteMsg2 : "à¸à¸£à¸¸à¸“าใช้คีย์บอร์ดเท่านั้น โดยà¸à¸”ปุ๋ม (<strong>Ctrl à¹à¸¥à¸° V</strong>)พร้อมๆà¸à¸±à¸™ à¹à¸¥à¸°à¸à¸” <strong>OK</strong>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "ไม่สนใจ Font Face definitions",
+DlgPasteRemoveStyles : "ลบ Styles definitions",
+
+// Color Picker
+ColorAutomatic : "สีอัตโนมัติ",
+ColorMoreColors : "เลือà¸à¸ªà¸µà¸­à¸·à¹ˆà¸™à¹†...",
+
+// Document Properties
+DocProps : "คุณสมบัติของเอà¸à¸ªà¸²à¸£",
+
+// Anchor Dialog
+DlgAnchorTitle : "คุณสมบัติของ Anchor",
+DlgAnchorName : "ชื่อ Anchor",
+DlgAnchorErrorName : "à¸à¸£à¸¸à¸“าระบุชื่อของ Anchor",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "ไม่พบในดิà¸à¸Šà¸±à¸™à¸™à¸²à¸£à¸µ",
+DlgSpellChangeTo : "à¹à¸à¹‰à¹„ขเป็น",
+DlgSpellBtnIgnore : "ยà¸à¹€à¸§à¹‰à¸™",
+DlgSpellBtnIgnoreAll : "ยà¸à¹€à¸§à¹‰à¸™à¸—ั้งหมด",
+DlgSpellBtnReplace : "à¹à¸—นที่",
+DlgSpellBtnReplaceAll : "à¹à¸—นที่ทั้งหมด",
+DlgSpellBtnUndo : "ยà¸à¹€à¸¥à¸´à¸",
+DlgSpellNoSuggestions : "- ไม่มีคำà¹à¸™à¸°à¸™à¸³à¹ƒà¸”ๆ -",
+DlgSpellProgress : "à¸à¸³à¸¥à¸±à¸‡à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸„ำสะà¸à¸”...",
+DlgSpellNoMispell : "ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: ไม่พบคำสะà¸à¸”ผิด",
+DlgSpellNoChanges : "ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: ไม่มีà¸à¸²à¸£à¹à¸à¹‰à¸„ำใดๆ",
+DlgSpellOneChange : "ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: à¹à¸à¹‰à¹„ข1คำ",
+DlgSpellManyChanges : "ตรวจสอบคำสะà¸à¸”เสร็จสิ้น:: à¹à¸à¹‰à¹„ข %1 คำ",
+
+IeSpellDownload : "ไม่ได้ติดตั้งระบบตรวจสอบคำสะà¸à¸”. ต้องà¸à¸²à¸£à¸•à¸´à¸”ตั้งไหมครับ?",
+
+// Button Dialog
+DlgButtonText : "ข้อความ (ค่าตัวà¹à¸›à¸£)",
+DlgButtonType : "ข้อความ",
+DlgButtonTypeBtn : "Button",
+DlgButtonTypeSbm : "Submit",
+DlgButtonTypeRst : "Reset",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "ชื่อ",
+DlgCheckboxValue : "ค่าตัวà¹à¸›à¸£",
+DlgCheckboxSelected : "เลือà¸à¹€à¸›à¹‡à¸™à¸„่าเริ่มต้น",
+
+// Form Dialog
+DlgFormName : "ชื่อ",
+DlgFormAction : "à¹à¸­à¸„ชั่น",
+DlgFormMethod : "เมธอด",
+
+// Select Field Dialog
+DlgSelectName : "ชื่อ",
+DlgSelectValue : "ค่าตัวà¹à¸›à¸£",
+DlgSelectSize : "ขนาด",
+DlgSelectLines : "บรรทัด",
+DlgSelectChkMulti : "เลือà¸à¸«à¸¥à¸²à¸¢à¸„่าได้",
+DlgSelectOpAvail : "รายà¸à¸²à¸£à¸•à¸±à¸§à¹€à¸¥à¸·à¸­à¸",
+DlgSelectOpText : "ข้อความ",
+DlgSelectOpValue : "ค่าตัวà¹à¸›à¸£",
+DlgSelectBtnAdd : "เพิ่ม",
+DlgSelectBtnModify : "à¹à¸à¹‰à¹„ข",
+DlgSelectBtnUp : "บน",
+DlgSelectBtnDown : "ล่าง",
+DlgSelectBtnSetValue : "เลือà¸à¹€à¸›à¹‡à¸™à¸„่าเริ่มต้น",
+DlgSelectBtnDelete : "ลบ",
+
+// Textarea Dialog
+DlgTextareaName : "ชื่อ",
+DlgTextareaCols : "สดมภ์",
+DlgTextareaRows : "à¹à¸–ว",
+
+// Text Field Dialog
+DlgTextName : "ชื่อ",
+DlgTextValue : "ค่าตัวà¹à¸›à¸£",
+DlgTextCharWidth : "ความà¸à¸§à¹‰à¸²à¸‡",
+DlgTextMaxChars : "จำนวนตัวอัà¸à¸©à¸£à¸ªà¸¹à¸‡à¸ªà¸¸à¸”",
+DlgTextType : "ชนิด",
+DlgTextTypeText : "ข้อความ",
+DlgTextTypePass : "รหัสผ่าน",
+
+// Hidden Field Dialog
+DlgHiddenName : "ชื่อ",
+DlgHiddenValue : "ค่าตัวà¹à¸›à¸£",
+
+// Bulleted List Dialog
+BulletedListProp : "คุณสมบัติของ บูลเล็ตลิสต์",
+NumberedListProp : "คุณสมบัติของ นัมเบอร์ลิสต์",
+DlgLstStart : "Start", //MISSING
+DlgLstType : "ชนิด",
+DlgLstTypeCircle : "รูปวงà¸à¸¥à¸¡",
+DlgLstTypeDisc : "Disc", //MISSING
+DlgLstTypeSquare : "รูปสี่เหลี่ยม",
+DlgLstTypeNumbers : "หมายเลข (1, 2, 3)",
+DlgLstTypeLCase : "ตัวพิมพ์เล็ภ(a, b, c)",
+DlgLstTypeUCase : "ตัวพิมพ์ใหà¸à¹ˆ (A, B, C)",
+DlgLstTypeSRoman : "เลขโรมันพิมพ์เล็ภ(i, ii, iii)",
+DlgLstTypeLRoman : "เลขโรมันพิมพ์ใหà¸à¹ˆ (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "ลัà¸à¸©à¸“ะทั่วไปของเอà¸à¸ªà¸²à¸£",
+DlgDocBackTab : "พื้นหลัง",
+DlgDocColorsTab : "สีà¹à¸¥à¸°à¸£à¸°à¸¢à¸°à¸‚อบ",
+DlgDocMetaTab : "ข้อมูลสำหรับเสิร์ชเอนจิ้น",
+
+DlgDocPageTitle : "ชื่อไตเติ้ล",
+DlgDocLangDir : "à¸à¸²à¸£à¸­à¹ˆà¸²à¸™à¸ à¸²à¸©à¸²",
+DlgDocLangDirLTR : "จาà¸à¸‹à¹‰à¸²à¸¢à¹„ปขวา (LTR)",
+DlgDocLangDirRTL : "จาà¸à¸‚วาไปซ้าย (RTL)",
+DlgDocLangCode : "รหัสภาษา",
+DlgDocCharSet : "ชุดตัวอัà¸à¸©à¸£",
+DlgDocCharSetCE : "Central European",
+DlgDocCharSetCT : "Chinese Traditional (Big5)",
+DlgDocCharSetCR : "Cyrillic",
+DlgDocCharSetGR : "Greek",
+DlgDocCharSetJP : "Japanese",
+DlgDocCharSetKR : "Korean",
+DlgDocCharSetTR : "Turkish",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Western European",
+DlgDocCharSetOther : "ชุดตัวอัà¸à¸©à¸£à¸­à¸·à¹ˆà¸™à¹†",
+
+DlgDocDocType : "ประเภทของเอà¸à¸ªà¸²à¸£",
+DlgDocDocTypeOther : "ประเภทเอà¸à¸ªà¸²à¸£à¸­à¸·à¹ˆà¸™à¹†",
+DlgDocIncXHTML : "รวมเอา XHTML Declarations ไว้ด้วย",
+DlgDocBgColor : "สีพื้นหลัง",
+DlgDocBgImage : "ที่อยู่อ้างอิงออนไลน์ของรูปพื้นหลัง (Image URL)",
+DlgDocBgNoScroll : "พื้นหลังà¹à¸šà¸šà¹„ม่มีà¹à¸–บเลื่อน",
+DlgDocCText : "ข้อความ",
+DlgDocCLink : "ลิงค์",
+DlgDocCVisited : "ลิงค์ที่เคยคลิ้à¸à¹à¸¥à¹‰à¸§ Visited Link",
+DlgDocCActive : "ลิงค์ที่à¸à¸³à¸¥à¸±à¸‡à¸„ลิ้ภActive Link",
+DlgDocMargins : "ระยะขอบของหน้าเอà¸à¸ªà¸²à¸£",
+DlgDocMaTop : "ด้านบน",
+DlgDocMaLeft : "ด้านซ้าย",
+DlgDocMaRight : "ด้านขวา",
+DlgDocMaBottom : "ด้านล่าง",
+DlgDocMeIndex : "คำสำคัà¸à¸­à¸˜à¸´à¸šà¸²à¸¢à¹€à¸­à¸à¸ªà¸²à¸£ (คั่นคำด้วย คอมม่า)",
+DlgDocMeDescr : "ประโยคอธิบายเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹€à¸­à¸à¸ªà¸²à¸£",
+DlgDocMeAuthor : "ผู้สร้างเอà¸à¸ªà¸²à¸£",
+DlgDocMeCopy : "สงวนลิขสิทธิ์",
+DlgDocPreview : "ตัวอย่างหน้าเอà¸à¸ªà¸²à¸£",
+
+// Templates Dialog
+Templates : "เทมเพลต",
+DlgTemplatesTitle : "เทมเพลตของส่วนเนื้อหาเว็บไซต์",
+DlgTemplatesSelMsg : "à¸à¸£à¸¸à¸“าเลือภเทมเพลต เพื่อนำไปà¹à¸à¹‰à¹„ขในอีดิตเตอร์<br />(เนื้อหาส่วนนี้จะหายไป):",
+DlgTemplatesLoading : "à¸à¸³à¸¥à¸±à¸‡à¹‚หลดรายà¸à¸²à¸£à¹€à¸—มเพลตทั้งหมด...",
+DlgTemplatesNoTpl : "(ยังไม่มีà¸à¸²à¸£à¸à¸³à¸«à¸™à¸”เทมเพลต)",
+DlgTemplatesReplace : "à¹à¸—นที่เนื้อหาเว็บไซต์ที่เลือà¸",
+
+// About Dialog
+DlgAboutAboutTab : "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹‚ปรà¹à¸à¸£à¸¡",
+DlgAboutBrowserInfoTab : "โปรà¹à¸à¸£à¸¡à¸—่องเว็บที่ท่านใช้",
+DlgAboutLicenseTab : "ลิขสิทธิ์",
+DlgAboutVersion : "รุ่น",
+DlgAboutInfo : "For further information go to", //MISSING
+
+// Div Dialog
+DlgDivGeneralTab : "General", //MISSING
+DlgDivAdvancedTab : "Advanced", //MISSING
+DlgDivStyle : "Style", //MISSING
+DlgDivInlineStyle : "Inline Style", //MISSING
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/tr.js b/httemplate/elements/fckeditor/editor/lang/tr.js
new file mode 100644
index 0000000..7707c3c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/tr.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Turkish language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Araç Çubuğunu Kapat",
+ToolbarExpand : "Araç Çubuğunu Aç",
+
+// Toolbar Items and Context Menu
+Save : "Kaydet",
+NewPage : "Yeni Sayfa",
+Preview : "Ön İzleme",
+Cut : "Kes",
+Copy : "Kopyala",
+Paste : "Yapıştır",
+PasteText : "Düzyazı Olarak Yapıştır",
+PasteWord : "Word'den Yapıştır",
+Print : "Yazdır",
+SelectAll : "Tümünü Seç",
+RemoveFormat : "Biçimi Kaldır",
+InsertLinkLbl : "Köprü",
+InsertLink : "Köprü Ekle/Düzenle",
+RemoveLink : "Köprü Kaldır",
+VisitLink : "Köprü Aç",
+Anchor : "Çapa Ekle/Düzenle",
+AnchorDelete : "Çapa Sil",
+InsertImageLbl : "Resim",
+InsertImage : "Resim Ekle/Düzenle",
+InsertFlashLbl : "Flash",
+InsertFlash : "Flash Ekle/Düzenle",
+InsertTableLbl : "Tablo",
+InsertTable : "Tablo Ekle/Düzenle",
+InsertLineLbl : "Satır",
+InsertLine : "Yatay Satır Ekle",
+InsertSpecialCharLbl: "Özel Karakter",
+InsertSpecialChar : "Özel Karakter Ekle",
+InsertSmileyLbl : "Ä°fade",
+InsertSmiley : "Ä°fade Ekle",
+About : "FCKeditor Hakkında",
+Bold : "Kalın",
+Italic : "Ä°talik",
+Underline : "Altı Çizgili",
+StrikeThrough : "Üstü Çizgili",
+Subscript : "Alt Simge",
+Superscript : "Ãœst Simge",
+LeftJustify : "Sola Dayalı",
+CenterJustify : "Ortalanmış",
+RightJustify : "Sağa Dayalı",
+BlockJustify : "İki Kenara Yaslanmış",
+DecreaseIndent : "Sekme Azalt",
+IncreaseIndent : "Sekme Arttır",
+Blockquote : "Blok OluÅŸtur",
+CreateDiv : "Div Ekle",
+EditDiv : "Div Düzenle",
+DeleteDiv : "Div Sil",
+Undo : "Geri Al",
+Redo : "Tekrarla",
+NumberedListLbl : "Numaralı Liste",
+NumberedList : "Numaralı Liste Ekle/Kaldır",
+BulletedListLbl : "Simgeli Liste",
+BulletedList : "Simgeli Liste Ekle/Kaldır",
+ShowTableBorders : "Tablo Kenarlarını Göster",
+ShowDetails : "Detayları Göster",
+Style : "Biçem",
+FontFormat : "Biçim",
+Font : "Yazı Türü",
+FontSize : "Boyut",
+TextColor : "Yazı Rengi",
+BGColor : "Arka Renk",
+Source : "Kaynak",
+Find : "Bul",
+Replace : "DeÄŸiÅŸtir",
+SpellCheck : "Yazım Denetimi",
+UniversalKeyboard : "Evrensel Klavye",
+PageBreakLbl : "Sayfa sonu",
+PageBreak : "Sayfa Sonu Ekle",
+
+Form : "Form",
+Checkbox : "Onay Kutusu",
+RadioButton : "Seçenek Düğmesi",
+TextField : "Metin GiriÅŸi",
+Textarea : "Çok Satırlı Metin",
+HiddenField : "Gizli Veri",
+Button : "Düğme",
+SelectionField : "Seçim Menüsü",
+ImageButton : "Resimli Düğme",
+
+FitWindow : "Düzenleyici boyutunu büyüt",
+ShowBlocks : "Blokları Göster",
+
+// Context Menu
+EditLink : "Köprü Düzenle",
+CellCM : "Hücre",
+RowCM : "Satır",
+ColumnCM : "Sütun",
+InsertRowAfter : "Satır Ekle - Sonra",
+InsertRowBefore : "Satır Ekle - Önce",
+DeleteRows : "Satır Sil",
+InsertColumnAfter : "Kolon Ekle - Sonra",
+InsertColumnBefore : "Kolon Ekle - Önce",
+DeleteColumns : "Sütun Sil",
+InsertCellAfter : "Hücre Ekle - Sonra",
+InsertCellBefore : "Hücre Ekle - Önce",
+DeleteCells : "Hücre Sil",
+MergeCells : "Hücreleri Birleştir",
+MergeRight : "BirleÅŸtir - SaÄŸdaki Ä°le ",
+MergeDown : "Birleştir - Aşağıdaki İle ",
+HorizontalSplitCell : "Hücreyi Yatay Böl",
+VerticalSplitCell : "Hücreyi Dikey Böl",
+TableDelete : "Tabloyu Sil",
+CellProperties : "Hücre Özellikleri",
+TableProperties : "Tablo Özellikleri",
+ImageProperties : "Resim Özellikleri",
+FlashProperties : "Flash Özellikleri",
+
+AnchorProp : "Çapa Özellikleri",
+ButtonProp : "Düğme Özellikleri",
+CheckboxProp : "Onay Kutusu Özellikleri",
+HiddenFieldProp : "Gizli Veri Özellikleri",
+RadioButtonProp : "Seçenek Düğmesi Özellikleri",
+ImageButtonProp : "Resimli Düğme Özellikleri",
+TextFieldProp : "Metin Girişi Özellikleri",
+SelectionFieldProp : "Seçim Menüsü Özellikleri",
+TextareaProp : "Çok Satırlı Metin Özellikleri",
+FormProp : "Form Özellikleri",
+
+FontFormats : "Normal;Biçimli;Adres;Başlık 1;Başlık 2;Başlık 3;Başlık 4;Başlık 5;Başlık 6;Paragraf (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "XHTML işleniyor. Lütfen bekleyin...",
+Done : "Bitti",
+PasteWordConfirm : "Yapıştırdığınız yazı Word'den gelmişe benziyor. Yapıştırmadan önce gereksiz eklentileri silmek ister misiniz?",
+NotCompatiblePaste : "Bu komut Internet Explorer 5.5 ve ileriki sürümleri için mevcuttur. Temizlenmeden yapıştırılmasını ister misiniz ?",
+UnknownToolbarItem : "Bilinmeyen araç çubugu öğesi \"%1\"",
+UnknownCommand : "Bilinmeyen komut \"%1\"",
+NotImplemented : "Komut uyarlanamadı",
+UnknownToolbarSet : "\"%1\" araç çubuğu öğesi mevcut değil",
+NoActiveX : "Kullandığınız tarayıcının güvenlik ayarları bazı özelliklerin kullanılmasını engelliyor. Bu özelliklerin çalışması için \"Run ActiveX controls and plug-ins (Activex ve eklentileri çalıştır)\" seçeneğinin aktif yapılması gerekiyor. Kullanılamayan eklentiler ve hatalar konusunda daha fazla bilgi sahibi olun.",
+BrowseServerBlocked : "Kaynak tarayıcısı açılamadı. Tüm \"popup blocker\" programlarının devre dışı olduğundan emin olun. (Yahoo toolbar, Msn toolbar, Google toolbar gibi)",
+DialogBlocked : "Diyalog açmak mümkün olmadı. Tüm \"Popup Blocker\" programlarının devre dışı olduğundan emin olun.",
+VisitLinkBlocked : "Yeni pencere açmak mümkün olmadı. Tüm \"Popup Blocker\" programlarının devre dışı olduğundan emin olun",
+
+// Dialogs
+DlgBtnOK : "Tamam",
+DlgBtnCancel : "Ä°ptal",
+DlgBtnClose : "Kapat",
+DlgBtnBrowseServer : "Sunucuyu Gez",
+DlgAdvancedTag : "GeliÅŸmiÅŸ",
+DlgOpOther : "<DiÄŸer>",
+DlgInfoTab : "Bilgi",
+DlgAlertUrl : "Lütfen URL girin",
+
+// General Dialogs Labels
+DlgGenNotSet : "<tanımlanmamış>",
+DlgGenId : "Kimlik",
+DlgGenLangDir : "Dil Yönü",
+DlgGenLangDirLtr : "Soldan SaÄŸa (LTR)",
+DlgGenLangDirRtl : "SaÄŸdan Sola (RTL)",
+DlgGenLangCode : "Dil Kodlaması",
+DlgGenAccessKey : "EriÅŸim TuÅŸu",
+DlgGenName : "Ad",
+DlgGenTabIndex : "Sekme Ä°ndeksi",
+DlgGenLongDescr : "Uzun Tanımlı URL",
+DlgGenClass : "Biçem Sayfası Sınıfları",
+DlgGenTitle : "Danışma Başlığı",
+DlgGenContType : "Danışma İçerik Türü",
+DlgGenLinkCharset : "Bağlı Kaynak Karakter Gurubu",
+DlgGenStyle : "Biçem",
+
+// Image Dialog
+DlgImgTitle : "Resim Özellikleri",
+DlgImgInfoTab : "Resim Bilgisi",
+DlgImgBtnUpload : "Sunucuya Yolla",
+DlgImgURL : "URL",
+DlgImgUpload : "Karşıya Yükle",
+DlgImgAlt : "Alternatif Yazı",
+DlgImgWidth : "GeniÅŸlik",
+DlgImgHeight : "Yükseklik",
+DlgImgLockRatio : "Oranı Kilitle",
+DlgBtnResetSize : "Boyutu Başa Döndür",
+DlgImgBorder : "Kenar",
+DlgImgHSpace : "Yatay BoÅŸluk",
+DlgImgVSpace : "Dikey BoÅŸluk",
+DlgImgAlign : "Hizalama",
+DlgImgAlignLeft : "Sol",
+DlgImgAlignAbsBottom: "Tam Altı",
+DlgImgAlignAbsMiddle: "Tam Ortası",
+DlgImgAlignBaseline : "Taban Çizgisi",
+DlgImgAlignBottom : "Alt",
+DlgImgAlignMiddle : "Orta",
+DlgImgAlignRight : "SaÄŸ",
+DlgImgAlignTextTop : "Yazı Tepeye",
+DlgImgAlignTop : "Tepe",
+DlgImgPreview : "Ön İzleme",
+DlgImgAlertUrl : "Lütfen resmin URL'sini yazınız",
+DlgImgLinkTab : "Köprü",
+
+// Flash Dialog
+DlgFlashTitle : "Flash Özellikleri",
+DlgFlashChkPlay : "Otomatik Oynat",
+DlgFlashChkLoop : "Döngü",
+DlgFlashChkMenu : "Flash Menüsünü Kullan",
+DlgFlashScale : "Boyutlandır",
+DlgFlashScaleAll : "Hepsini Göster",
+DlgFlashScaleNoBorder : "Kenar Yok",
+DlgFlashScaleFit : "Tam Sığdır",
+
+// Link Dialog
+DlgLnkWindowTitle : "Köprü",
+DlgLnkInfoTab : "Köprü Bilgisi",
+DlgLnkTargetTab : "Hedef",
+
+DlgLnkType : "Köprü Türü",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Bu sayfada çapa",
+DlgLnkTypeEMail : "E-Posta",
+DlgLnkProto : "Protokol",
+DlgLnkProtoOther : "<diÄŸer>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Çapa Seç",
+DlgLnkAnchorByName : "Çapa Adı ile",
+DlgLnkAnchorById : "Eleman Kimlik Numarası ile",
+DlgLnkNoAnchors : "(Bu belgede hiç çapa yok)",
+DlgLnkEMail : "E-Posta Adresi",
+DlgLnkEMailSubject : "Ä°leti Konusu",
+DlgLnkEMailBody : "İleti Gövdesi",
+DlgLnkUpload : "Karşıya Yükle",
+DlgLnkBtnUpload : "Sunucuya Gönder",
+
+DlgLnkTarget : "Hedef",
+DlgLnkTargetFrame : "<çerçeve>",
+DlgLnkTargetPopup : "<yeni açılan pencere>",
+DlgLnkTargetBlank : "Yeni Pencere(_blank)",
+DlgLnkTargetParent : "Anne Pencere (_parent)",
+DlgLnkTargetSelf : "Kendi Penceresi (_self)",
+DlgLnkTargetTop : "En Ãœst Pencere (_top)",
+DlgLnkTargetFrameName : "Hedef Çerçeve Adı",
+DlgLnkPopWinName : "Yeni Açılan Pencere Adı",
+DlgLnkPopWinFeat : "Yeni Açılan Pencere Özellikleri",
+DlgLnkPopResize : "Boyutlandırılabilir",
+DlgLnkPopLocation : "Yer Çubuğu",
+DlgLnkPopMenu : "Menü Çubuğu",
+DlgLnkPopScroll : "Kaydırma Çubukları",
+DlgLnkPopStatus : "Durum Çubuğu",
+DlgLnkPopToolbar : "Araç Çubuğu",
+DlgLnkPopFullScrn : "Tam Ekran (IE)",
+DlgLnkPopDependent : "Bağımlı (Netscape)",
+DlgLnkPopWidth : "GeniÅŸlik",
+DlgLnkPopHeight : "Yükseklik",
+DlgLnkPopLeft : "Sola Göre Konum",
+DlgLnkPopTop : "Yukarıya Göre Konum",
+
+DlnLnkMsgNoUrl : "Lütfen köprü URL'sini yazın",
+DlnLnkMsgNoEMail : "Lütfen E-posta adresini yazın",
+DlnLnkMsgNoAnchor : "Lütfen bir çapa seçin",
+DlnLnkMsgInvPopName : "Açılır pencere adı abecesel bir karakterle başlamalı ve boşluk içermemelidir",
+
+// Color Dialog
+DlgColorTitle : "Renk Seç",
+DlgColorBtnClear : "Temizle",
+DlgColorHighlight : "Vurgula",
+DlgColorSelected : "Seçilmiş",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ä°fade Ekle",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Özel Karakter Seç",
+
+// Table Dialog
+DlgTableTitle : "Tablo Özellikleri",
+DlgTableRows : "Satırlar",
+DlgTableColumns : "Sütunlar",
+DlgTableBorder : "Kenar Kalınlığı",
+DlgTableAlign : "Hizalama",
+DlgTableAlignNotSet : "<Tanımlanmamış>",
+DlgTableAlignLeft : "Sol",
+DlgTableAlignCenter : "Merkez",
+DlgTableAlignRight : "SaÄŸ",
+DlgTableWidth : "GeniÅŸlik",
+DlgTableWidthPx : "piksel",
+DlgTableWidthPc : "yüzde",
+DlgTableHeight : "Yükseklik",
+DlgTableCellSpace : "Izgara kalınlığı",
+DlgTableCellPad : "Izgara yazı arası",
+DlgTableCaption : "Başlık",
+DlgTableSummary : "Özet",
+DlgTableHeaders : "Başlıklar",
+DlgTableHeadersNone : "Yok",
+DlgTableHeadersColumn : "İlk Sütun",
+DlgTableHeadersRow : "İlk Satır",
+DlgTableHeadersBoth : "Her Ä°kisi",
+
+// Table Cell Dialog
+DlgCellTitle : "Hücre Özellikleri",
+DlgCellWidth : "GeniÅŸlik",
+DlgCellWidthPx : "piksel",
+DlgCellWidthPc : "yüzde",
+DlgCellHeight : "Yükseklik",
+DlgCellWordWrap : "Sözcük Kaydır",
+DlgCellWordWrapNotSet : "<Tanımlanmamış>",
+DlgCellWordWrapYes : "Evet",
+DlgCellWordWrapNo : "Hayır",
+DlgCellHorAlign : "Yatay Hizalama",
+DlgCellHorAlignNotSet : "<Tanımlanmamış>",
+DlgCellHorAlignLeft : "Sol",
+DlgCellHorAlignCenter : "Merkez",
+DlgCellHorAlignRight: "SaÄŸ",
+DlgCellVerAlign : "Dikey Hizalama",
+DlgCellVerAlignNotSet : "<Tanımlanmamış>",
+DlgCellVerAlignTop : "Tepe",
+DlgCellVerAlignMiddle : "Orta",
+DlgCellVerAlignBottom : "Alt",
+DlgCellVerAlignBaseline : "Taban Çizgisi",
+DlgCellType : "Hücre Tipi",
+DlgCellTypeData : "Veri",
+DlgCellTypeHeader : "Başlık",
+DlgCellRowSpan : "Satır Kapla",
+DlgCellCollSpan : "Sütun Kapla",
+DlgCellBackColor : "Arka Plan Rengi",
+DlgCellBorderColor : "Kenar Rengi",
+DlgCellBtnSelect : "Seç...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Bul ve DeÄŸiÅŸtir",
+
+// Find Dialog
+DlgFindTitle : "Bul",
+DlgFindFindBtn : "Bul",
+DlgFindNotFoundMsg : "Belirtilen yazı bulunamadı.",
+
+// Replace Dialog
+DlgReplaceTitle : "DeÄŸiÅŸtir",
+DlgReplaceFindLbl : "Aranan:",
+DlgReplaceReplaceLbl : "Bununla deÄŸiÅŸtir:",
+DlgReplaceCaseChk : "Büyük/küçük harf duyarlı",
+DlgReplaceReplaceBtn : "DeÄŸiÅŸtir",
+DlgReplaceReplAllBtn : "Tümünü Değiştir",
+DlgReplaceWordChk : "Kelimenin tamamı uysun",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Gezgin yazılımınızın güvenlik ayarları düzenleyicinin otomatik kesme işlemine izin vermiyor. İşlem için (Ctrl+X) tuşlarını kullanın.",
+PasteErrorCopy : "Gezgin yazılımınızın güvenlik ayarları düzenleyicinin otomatik kopyalama işlemine izin vermiyor. İşlem için (Ctrl+C) tuşlarını kullanın.",
+
+PasteAsText : "Düz Metin Olarak Yapıştır",
+PasteFromWord : "Word'den yapıştır",
+
+DlgPasteMsg2 : "Lütfen aşağıdaki kutunun içine yapıştırın. (<STRONG>Ctrl+V</STRONG>) ve <STRONG>Tamam</STRONG> butonunu tıklayın.",
+DlgPasteSec : "Gezgin yazılımınızın güvenlik ayarları düzenleyicinin direkt olarak panoya erişimine izin vermiyor. Bu pencere içine tekrar yapıştırmalısınız..",
+DlgPasteIgnoreFont : "Yazı Tipi tanımlarını yoksay",
+DlgPasteRemoveStyles : "Biçem Tanımlarını çıkar",
+
+// Color Picker
+ColorAutomatic : "Otomatik",
+ColorMoreColors : "DiÄŸer renkler...",
+
+// Document Properties
+DocProps : "Belge Özellikleri",
+
+// Anchor Dialog
+DlgAnchorTitle : "Çapa Özellikleri",
+DlgAnchorName : "Çapa Adı",
+DlgAnchorErrorName : "Lütfen çapa için ad giriniz",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Sözlükte Yok",
+DlgSpellChangeTo : "Åžuna deÄŸiÅŸtir:",
+DlgSpellBtnIgnore : "Yoksay",
+DlgSpellBtnIgnoreAll : "Tümünü Yoksay",
+DlgSpellBtnReplace : "DeÄŸiÅŸtir",
+DlgSpellBtnReplaceAll : "Tümünü Değiştir",
+DlgSpellBtnUndo : "Geri Al",
+DlgSpellNoSuggestions : "- Öneri Yok -",
+DlgSpellProgress : "Yazım denetimi işlemde...",
+DlgSpellNoMispell : "Yazım denetimi tamamlandı: Yanlış yazıma rastlanmadı",
+DlgSpellNoChanges : "Yazım denetimi tamamlandı: Hiçbir kelime değiştirilmedi",
+DlgSpellOneChange : "Yazım denetimi tamamlandı: Bir kelime değiştirildi",
+DlgSpellManyChanges : "Yazım denetimi tamamlandı: %1 kelime değiştirildi",
+
+IeSpellDownload : "Yazım denetimi yüklenmemiş. Şimdi yüklemek ister misiniz?",
+
+// Button Dialog
+DlgButtonText : "Metin (DeÄŸer)",
+DlgButtonType : "Tip",
+DlgButtonTypeBtn : "Düğme",
+DlgButtonTypeSbm : "Gönder",
+DlgButtonTypeRst : "Sıfırla",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Ad",
+DlgCheckboxValue : "DeÄŸer",
+DlgCheckboxSelected : "Seçili",
+
+// Form Dialog
+DlgFormName : "Ad",
+DlgFormAction : "Ä°ÅŸlem",
+DlgFormMethod : "Yöntem",
+
+// Select Field Dialog
+DlgSelectName : "Ad",
+DlgSelectValue : "DeÄŸer",
+DlgSelectSize : "Boyut",
+DlgSelectLines : "satır",
+DlgSelectChkMulti : "Çoklu seçime izin ver",
+DlgSelectOpAvail : "Mevcut Seçenekler",
+DlgSelectOpText : "Metin",
+DlgSelectOpValue : "DeÄŸer",
+DlgSelectBtnAdd : "Ekle",
+DlgSelectBtnModify : "Düzenle",
+DlgSelectBtnUp : "Yukarı",
+DlgSelectBtnDown : "Aşağı",
+DlgSelectBtnSetValue : "Seçili değer olarak ata",
+DlgSelectBtnDelete : "Sil",
+
+// Textarea Dialog
+DlgTextareaName : "Ad",
+DlgTextareaCols : "Sütunlar",
+DlgTextareaRows : "Satırlar",
+
+// Text Field Dialog
+DlgTextName : "Ad",
+DlgTextValue : "DeÄŸer",
+DlgTextCharWidth : "Karakter GeniÅŸliÄŸi",
+DlgTextMaxChars : "En Fazla Karakter",
+DlgTextType : "Tür",
+DlgTextTypeText : "Metin",
+DlgTextTypePass : "Parola",
+
+// Hidden Field Dialog
+DlgHiddenName : "Ad",
+DlgHiddenValue : "DeÄŸer",
+
+// Bulleted List Dialog
+BulletedListProp : "Simgeli Liste Özellikleri",
+NumberedListProp : "Numaralı Liste Özellikleri",
+DlgLstStart : "Başlangıç",
+DlgLstType : "Tip",
+DlgLstTypeCircle : "Çember",
+DlgLstTypeDisc : "Disk",
+DlgLstTypeSquare : "Kare",
+DlgLstTypeNumbers : "Sayılar (1, 2, 3)",
+DlgLstTypeLCase : "Küçük Harfler (a, b, c)",
+DlgLstTypeUCase : "Büyük Harfler (A, B, C)",
+DlgLstTypeSRoman : "Küçük Romen Rakamları (i, ii, iii)",
+DlgLstTypeLRoman : "Büyük Romen Rakamları (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Genel",
+DlgDocBackTab : "Arka Plan",
+DlgDocColorsTab : "Renkler ve Kenar Boşlukları",
+DlgDocMetaTab : "Tanım Bilgisi (Meta)",
+
+DlgDocPageTitle : "Sayfa Başlığı",
+DlgDocLangDir : "Dil Yönü",
+DlgDocLangDirLTR : "Soldan SaÄŸa (LTR)",
+DlgDocLangDirRTL : "SaÄŸdan Sola (RTL)",
+DlgDocLangCode : "Dil Kodu",
+DlgDocCharSet : "Karakter Kümesi Kodlaması",
+DlgDocCharSetCE : "Orta Avrupa",
+DlgDocCharSetCT : "Geleneksel Çince (Big5)",
+DlgDocCharSetCR : "Kiril",
+DlgDocCharSetGR : "Yunanca",
+DlgDocCharSetJP : "Japonca",
+DlgDocCharSetKR : "Korece",
+DlgDocCharSetTR : "Türkçe",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Batı Avrupa",
+DlgDocCharSetOther : "Diğer Karakter Kümesi Kodlaması",
+
+DlgDocDocType : "Belge Türü Başlığı",
+DlgDocDocTypeOther : "Diğer Belge Türü Başlığı",
+DlgDocIncXHTML : "XHTML Bildirimlerini Dahil Et",
+DlgDocBgColor : "Arka Plan Rengi",
+DlgDocBgImage : "Arka Plan Resim URLsi",
+DlgDocBgNoScroll : "Sabit Arka Plan",
+DlgDocCText : "Metin",
+DlgDocCLink : "Köprü",
+DlgDocCVisited : "Ziyaret Edilmiş Köprü",
+DlgDocCActive : "Etkin Köprü",
+DlgDocMargins : "Kenar Boşlukları",
+DlgDocMaTop : "Tepe",
+DlgDocMaLeft : "Sol",
+DlgDocMaRight : "SaÄŸ",
+DlgDocMaBottom : "Alt",
+DlgDocMeIndex : "Belge Dizinleme Anahtar Kelimeleri (virgülle ayrılmış)",
+DlgDocMeDescr : "Belge Tanımı",
+DlgDocMeAuthor : "Yazar",
+DlgDocMeCopy : "Telif",
+DlgDocPreview : "Ön İzleme",
+
+// Templates Dialog
+Templates : "Åžablonlar",
+DlgTemplatesTitle : "İçerik Şablonları",
+DlgTemplatesSelMsg : "Düzenleyicide açmak için lütfen bir şablon seçin.<br>(hali hazırdaki içerik kaybolacaktır.):",
+DlgTemplatesLoading : "Şablon listesi yüklenmekte. Lütfen bekleyiniz...",
+DlgTemplatesNoTpl : "(Belirli bir şablon seçilmedi)",
+DlgTemplatesReplace : "Mevcut içerik ile değiştir",
+
+// About Dialog
+DlgAboutAboutTab : "Hakkında",
+DlgAboutBrowserInfoTab : "Gezgin Bilgisi",
+DlgAboutLicenseTab : "Lisans",
+DlgAboutVersion : "sürüm",
+DlgAboutInfo : "Daha fazla bilgi için:",
+
+// Div Dialog
+DlgDivGeneralTab : "Genel",
+DlgDivAdvancedTab : "GeliÅŸmiÅŸ",
+DlgDivStyle : "Sitil",
+DlgDivInlineStyle : "Satıriçi Sitil",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/uk.js b/httemplate/elements/fckeditor/editor/lang/uk.js
new file mode 100644
index 0000000..8191d54
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/uk.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Ukrainian language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Згорнути панель інÑтрументів",
+ToolbarExpand : "Розгорнути панель інÑтрументів",
+
+// Toolbar Items and Context Menu
+Save : "Зберегти",
+NewPage : "Ðова Ñторінка",
+Preview : "Попередній переглÑд",
+Cut : "Вирізати",
+Copy : "Копіювати",
+Paste : "Ð’Ñтавити",
+PasteText : "Ð’Ñтавити тільки текÑÑ‚",
+PasteWord : "Ð’Ñтавити з Word",
+Print : "Друк",
+SelectAll : "Виділити вÑе",
+RemoveFormat : "Прибрати форматуваннÑ",
+InsertLinkLbl : "ПоÑиланнÑ",
+InsertLink : "Ð’Ñтавити/Редагувати поÑиланнÑ",
+RemoveLink : "Знищити поÑиланнÑ",
+VisitLink : "Відкрити поÑиланнÑ",
+Anchor : "Ð’Ñтавити/Редагувати Ñкір",
+AnchorDelete : "Видалити Ñкір",
+InsertImageLbl : "ЗображеннÑ",
+InsertImage : "Ð’Ñтавити/Редагувати зображеннÑ",
+InsertFlashLbl : "Flash",
+InsertFlash : "Ð’Ñтавити/Редагувати Flash",
+InsertTableLbl : "ТаблицÑ",
+InsertTable : "Ð’Ñтавити/Редагувати таблицю",
+InsertLineLbl : "ЛініÑ",
+InsertLine : "Ð’Ñтавити горизонтальну лінію",
+InsertSpecialCharLbl: "Спеціальний Ñимвол",
+InsertSpecialChar : "Ð’Ñтавити Ñпеціальний Ñимвол",
+InsertSmileyLbl : "Смайлик",
+InsertSmiley : "Ð’Ñтавити Ñмайлик",
+About : "Про FCKeditor",
+Bold : "Жирний",
+Italic : "КурÑив",
+Underline : "ПідкреÑлений",
+StrikeThrough : "ЗакреÑлений",
+Subscript : "ПідрÑдковий індекÑ",
+Superscript : "ÐадрÑдковий индекÑ",
+LeftJustify : "По лівому краю",
+CenterJustify : "По центру",
+RightJustify : "По правому краю",
+BlockJustify : "По ширині",
+DecreaseIndent : "Зменшити відÑтуп",
+IncreaseIndent : "Збільшити відÑтуп",
+Blockquote : "Цитата",
+CreateDiv : "Створити Div контейнер",
+EditDiv : "Редагувати Div контейнер",
+DeleteDiv : "Видалити Div контейнер",
+Undo : "Повернути",
+Redo : "Повторити",
+NumberedListLbl : "Ðумерований ÑпиÑок",
+NumberedList : "Ð’Ñтавити/Видалити нумерований ÑпиÑок",
+BulletedListLbl : "Маркований ÑпиÑок",
+BulletedList : "Ð’Ñтавити/Видалити маркований ÑпиÑок",
+ShowTableBorders : "Показати бордюри таблиці",
+ShowDetails : "Показати деталі",
+Style : "Стиль",
+FontFormat : "ФорматуваннÑ",
+Font : "Шрифт",
+FontSize : "Розмір",
+TextColor : "Колір текÑту",
+BGColor : "Колір фону",
+Source : "Джерело",
+Find : "Пошук",
+Replace : "Заміна",
+SpellCheck : "Перевірити орфографію",
+UniversalKeyboard : "УніверÑальна клавіатура",
+PageBreakLbl : "Розривши Ñторінки",
+PageBreak : "Ð’Ñтавити розривши Ñторінки",
+
+Form : "Форма",
+Checkbox : "Флагова кнопка",
+RadioButton : "Кнопка вибору",
+TextField : "ТекÑтове поле",
+Textarea : "ТекÑтова облаÑÑ‚ÑŒ",
+HiddenField : "Приховане поле",
+Button : "Кнопка",
+SelectionField : "СпиÑок",
+ImageButton : "Кнопка із зображеннÑм",
+
+FitWindow : "Розвернути вікно редактора",
+ShowBlocks : "Показувати блоки",
+
+// Context Menu
+EditLink : "Ð’Ñтавити поÑиланнÑ",
+CellCM : "ОÑередок",
+RowCM : "РÑдок",
+ColumnCM : "Колонка",
+InsertRowAfter : "Ð’Ñтавити Ñ€Ñдок піÑлÑ",
+InsertRowBefore : "Ð’Ñтавити Ñ€Ñдок до",
+DeleteRows : "Видалити Ñтроки",
+InsertColumnAfter : "Ð’Ñтавити колонку піÑлÑ",
+InsertColumnBefore : "Ð’Ñтавити колонку до",
+DeleteColumns : "Видалити колонки",
+InsertCellAfter : "Ð’Ñтавити комірку піÑлÑ",
+InsertCellBefore : "Ð’Ñтавити комірку до",
+DeleteCells : "Видалити комірки",
+MergeCells : "Об'єднати комірки",
+MergeRight : "Об'єднати зправа",
+MergeDown : "Об'єднати до низу",
+HorizontalSplitCell : "Розділити комірку по горизонталі",
+VerticalSplitCell : "Розділити комірку по вертикалі",
+TableDelete : "Видалити таблицю",
+CellProperties : "ВлаÑтивоÑÑ‚Ñ– комірки",
+TableProperties : "ВлаÑтивоÑÑ‚Ñ– таблиці",
+ImageProperties : "ВлаÑтивоÑÑ‚Ñ– зображеннÑ",
+FlashProperties : "ВлаÑтивоÑÑ‚Ñ– Flash",
+
+AnchorProp : "ВлаÑтивоÑÑ‚Ñ– ÑкорÑ",
+ButtonProp : "ВлаÑтивоÑÑ‚Ñ– кнопки",
+CheckboxProp : "ВлаÑтивоÑÑ‚Ñ– флагової кнопки",
+HiddenFieldProp : "ВлаÑтивоÑÑ‚Ñ– прихованого полÑ",
+RadioButtonProp : "ВлаÑтивоÑÑ‚Ñ– кнопки вибору",
+ImageButtonProp : "ВлаÑтивоÑÑ‚Ñ– кнопки із зображеннÑм",
+TextFieldProp : "ВлаÑтивоÑÑ‚Ñ– текÑтового полÑ",
+SelectionFieldProp : "ВлаÑтивоÑÑ‚Ñ– ÑпиÑку",
+TextareaProp : "ВлаÑтивоÑÑ‚Ñ– текÑтової облаÑÑ‚Ñ–",
+FormProp : "ВлаÑтивоÑÑ‚Ñ– форми",
+
+FontFormats : "Ðормальний;Форматований;ÐдреÑа;Заголовок 1;Заголовок 2;Заголовок 3;Заголовок 4;Заголовок 5;Заголовок 6;Ðормальний (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Обробка XHTML. Зачекайте, будь лаÑка...",
+Done : "Зроблено",
+PasteWordConfirm : "ТекÑÑ‚, що ви хочете вÑтавити, Ñхожий на копійований з Word. Ви хочете очиÑтити його перед вÑтавкою?",
+NotCompatiblePaste : "Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° доÑтупна Ð´Ð»Ñ Internet Explorer верÑÑ–Ñ— 5.5 або вище. Ви хочете вÑтавити без очищеннÑ?",
+UnknownToolbarItem : "Ðевідомий елемент панелі інÑтрументів \"%1\"",
+UnknownCommand : "Ðевідоме ім'Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ \"%1\"",
+NotImplemented : "Команда не реалізована",
+UnknownToolbarSet : "Панель інÑтрументів \"%1\" не Ñ–Ñнує",
+NoActiveX : "ÐаÑтройки безпеки вашого браузера можуть обмежувати деÑкі влаÑтивоÑÑ‚Ñ– редактора. Ви повинні включити опцію \"ЗапуÑкати елементи ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ ACTIVEX Ñ– плугіни\". Ви можете бачити помилки Ñ– помічати відÑутніÑÑ‚ÑŒ можливоÑтей.",
+BrowseServerBlocked : "РеÑурÑи браузера не можуть бути відкриті. Перевірте що Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñпливаючих вікон вимкнені.",
+DialogBlocked : "Ðе можливо відкрити вікно діалогу. Перевірте що Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñпливаючих вікон вимкнені.",
+VisitLinkBlocked : "It was not possible to open a new window. Make sure all popup blockers are disabled.", //MISSING
+
+// Dialogs
+DlgBtnOK : "ОК",
+DlgBtnCancel : "СкаÑувати",
+DlgBtnClose : "Зачинити",
+DlgBtnBrowseServer : "ПередивитиÑÑ Ð½Ð° Ñервері",
+DlgAdvancedTag : "Розширений",
+DlgOpOther : "<Інше>",
+DlgInfoTab : "Інфо",
+DlgAlertUrl : "Ð’Ñтавте, будь-лаÑка, URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<не визначено>",
+DlgGenId : "Ідентифікатор",
+DlgGenLangDir : "ÐапрÑмок мови",
+DlgGenLangDirLtr : "Зліва на право (LTR)",
+DlgGenLangDirRtl : "Зправа на ліво (RTL)",
+DlgGenLangCode : "Мова",
+DlgGenAccessKey : "ГарÑча клавіша",
+DlgGenName : "Им'Ñ",
+DlgGenTabIndex : "ПоÑлідовніÑÑ‚ÑŒ переходу",
+DlgGenLongDescr : "Довгий Ð¾Ð¿Ð¸Ñ URL",
+DlgGenClass : "ÐšÐ»Ð°Ñ CSS",
+DlgGenTitle : "Заголовок",
+DlgGenContType : "Тип вміÑту",
+DlgGenLinkCharset : "Кодировка",
+DlgGenStyle : "Стиль CSS",
+
+// Image Dialog
+DlgImgTitle : "ВлаÑтивоÑÑ‚Ñ– зображеннÑ",
+DlgImgInfoTab : "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ изображении",
+DlgImgBtnUpload : "ÐадіÑлати на Ñервер",
+DlgImgURL : "URL",
+DlgImgUpload : "Закачати",
+DlgImgAlt : "Ðльтернативний текÑÑ‚",
+DlgImgWidth : "Ширина",
+DlgImgHeight : "ВиÑота",
+DlgImgLockRatio : "Зберегти пропорції",
+DlgBtnResetSize : "Скинути розмір",
+DlgImgBorder : "Бордюр",
+DlgImgHSpace : "Горизонтальний відÑтуп",
+DlgImgVSpace : "Вертикальний відÑтуп",
+DlgImgAlign : "ВирівнюваннÑ",
+DlgImgAlignLeft : "По лівому краю",
+DlgImgAlignAbsBottom: "ÐÐ±Ñ Ð¿Ð¾ низу",
+DlgImgAlignAbsMiddle: "ÐÐ±Ñ Ð¿Ð¾ Ñередині",
+DlgImgAlignBaseline : "По базовій лінії",
+DlgImgAlignBottom : "По низу",
+DlgImgAlignMiddle : "По Ñередині",
+DlgImgAlignRight : "По правому краю",
+DlgImgAlignTextTop : "ТекÑÑ‚ на верху",
+DlgImgAlignTop : "По верху",
+DlgImgPreview : "Попередній переглÑд",
+DlgImgAlertUrl : "Будь лаÑка, введіть URL зображеннÑ",
+DlgImgLinkTab : "ПоÑиланнÑ",
+
+// Flash Dialog
+DlgFlashTitle : "ВлаÑтивоÑÑ‚Ñ– Flash",
+DlgFlashChkPlay : "Ðвто програваннÑ",
+DlgFlashChkLoop : "Зациклити",
+DlgFlashChkMenu : "Дозволити меню Flash",
+DlgFlashScale : "МаÑштаб",
+DlgFlashScaleAll : "Показати вÑÑ–",
+DlgFlashScaleNoBorder : "Без рамки",
+DlgFlashScaleFit : "ДійÑний розмір",
+
+// Link Dialog
+DlgLnkWindowTitle : "ПоÑиланнÑ",
+DlgLnkInfoTab : "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ð¾ÑиланнÑ",
+DlgLnkTargetTab : "Ціль",
+
+DlgLnkType : "Тип поÑиланнÑ",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Якір на цю Ñторінку",
+DlgLnkTypeEMail : "Эл. пошта",
+DlgLnkProto : "Протокол",
+DlgLnkProtoOther : "<інше>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Оберіть Ñкір",
+DlgLnkAnchorByName : "За ім'Ñм ÑкорÑ",
+DlgLnkAnchorById : "За ідентифікатором елемента",
+DlgLnkNoAnchors : "(Ðемає Ñкорів доÑтупних в цьому документі)",
+DlgLnkEMail : "ÐдреÑа ел. пошти",
+DlgLnkEMailSubject : "Тема лиÑта",
+DlgLnkEMailBody : "Тіло повідомленнÑ",
+DlgLnkUpload : "Закачати",
+DlgLnkBtnUpload : "ПереÑлати на Ñервер",
+
+DlgLnkTarget : "Ціль",
+DlgLnkTargetFrame : "<фрейм>",
+DlgLnkTargetPopup : "<Ñпливаюче вікно>",
+DlgLnkTargetBlank : "Ðове вікно (_blank)",
+DlgLnkTargetParent : "БатьківÑьке вікно (_parent)",
+DlgLnkTargetSelf : "Теж вікно (_self)",
+DlgLnkTargetTop : "Ðайвище вікно (_top)",
+DlgLnkTargetFrameName : "Ім'Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð³Ð¾ фрейма",
+DlgLnkPopWinName : "Ім'Ñ Ñпливаючого вікна",
+DlgLnkPopWinFeat : "ВлаÑтивоÑÑ‚Ñ– Ñпливаючого вікна",
+DlgLnkPopResize : "ЗмінюєтьÑÑ Ð² розмірах",
+DlgLnkPopLocation : "Панель локації",
+DlgLnkPopMenu : "Панель меню",
+DlgLnkPopScroll : "ПолоÑи прокрутки",
+DlgLnkPopStatus : "Строка ÑтатуÑу",
+DlgLnkPopToolbar : "Панель інÑтрументів",
+DlgLnkPopFullScrn : "Повний екран (IE)",
+DlgLnkPopDependent : "Залежний (Netscape)",
+DlgLnkPopWidth : "Ширина",
+DlgLnkPopHeight : "ВиÑота",
+DlgLnkPopLeft : "ÐŸÐ¾Ð·Ð¸Ñ†Ñ–Ñ Ð·Ð»Ñ–Ð²Ð°",
+DlgLnkPopTop : "ÐŸÐ¾Ð·Ð¸Ñ†Ñ–Ñ Ð·Ð²ÐµÑ€Ñ…Ñƒ",
+
+DlnLnkMsgNoUrl : "Будь лаÑка, занеÑÑ–Ñ‚ÑŒ URL поÑиланнÑ",
+DlnLnkMsgNoEMail : "Будь лаÑка, занеÑÑ–Ñ‚ÑŒ Ð°Ð´Ñ€ÐµÑ Ñл. почты",
+DlnLnkMsgNoAnchor : "Будь лаÑка, оберіть Ñкір",
+DlnLnkMsgInvPopName : "Ðазва Ñпливаючого вікна повинна починатиÑÑ Ð±ÑƒÐºÐ²Ð¸ Ñ– не може міÑтити пропуÑків",
+
+// Color Dialog
+DlgColorTitle : "Оберіть колір",
+DlgColorBtnClear : "ОчиÑтити",
+DlgColorHighlight : "ПідÑвічений",
+DlgColorSelected : "Обраний",
+
+// Smiley Dialog
+DlgSmileyTitle : "Ð’Ñтавити Ñмайлик",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Оберіть Ñпеціальний Ñимвол",
+
+// Table Dialog
+DlgTableTitle : "ВлаÑтивоÑÑ‚Ñ– таблиці",
+DlgTableRows : "Строки",
+DlgTableColumns : "Колонки",
+DlgTableBorder : "Розмір бордюра",
+DlgTableAlign : "ВирівнюваннÑ",
+DlgTableAlignNotSet : "<Ðе вÑÑ‚.>",
+DlgTableAlignLeft : "Зліва",
+DlgTableAlignCenter : "По центру",
+DlgTableAlignRight : "Зправа",
+DlgTableWidth : "Ширина",
+DlgTableWidthPx : "пікÑелів",
+DlgTableWidthPc : "відÑотків",
+DlgTableHeight : "ВиÑота",
+DlgTableCellSpace : "Проміжок (spacing)",
+DlgTableCellPad : "ВідÑтуп (padding)",
+DlgTableCaption : "Заголовок",
+DlgTableSummary : "Резюме",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "ВлаÑтивоÑÑ‚Ñ– комірки",
+DlgCellWidth : "Ширина",
+DlgCellWidthPx : "пікÑелів",
+DlgCellWidthPc : "відÑотків",
+DlgCellHeight : "ВиÑота",
+DlgCellWordWrap : "Ð—Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑта",
+DlgCellWordWrapNotSet : "<Ðе вÑÑ‚.>",
+DlgCellWordWrapYes : "Так",
+DlgCellWordWrapNo : "ÐÑ–",
+DlgCellHorAlign : "Горизонтальне вирівнюваннÑ",
+DlgCellHorAlignNotSet : "<Ðе вÑÑ‚.>",
+DlgCellHorAlignLeft : "Зліва",
+DlgCellHorAlignCenter : "По центру",
+DlgCellHorAlignRight: "Зправа",
+DlgCellVerAlign : "Вертикальное вирівнюваннÑ",
+DlgCellVerAlignNotSet : "<Ðе вÑÑ‚.>",
+DlgCellVerAlignTop : "Зверху",
+DlgCellVerAlignMiddle : "ПоÑередині",
+DlgCellVerAlignBottom : "Знизу",
+DlgCellVerAlignBaseline : "По базовій лінії",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Діапазон Ñтрок (span)",
+DlgCellCollSpan : "Діапазон колонок (span)",
+DlgCellBackColor : "Колір фона",
+DlgCellBorderColor : "Колір бордюра",
+DlgCellBtnSelect : "Оберіть...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Знайти і замінити",
+
+// Find Dialog
+DlgFindTitle : "Пошук",
+DlgFindFindBtn : "Пошук",
+DlgFindNotFoundMsg : "Вказаний текÑÑ‚ не знайдений.",
+
+// Replace Dialog
+DlgReplaceTitle : "Замінити",
+DlgReplaceFindLbl : "Шукати:",
+DlgReplaceReplaceLbl : "Замінити на:",
+DlgReplaceCaseChk : "Учитывать региÑÑ‚Ñ€",
+DlgReplaceReplaceBtn : "Замінити",
+DlgReplaceReplAllBtn : "Замінити вÑе",
+DlgReplaceWordChk : "Збіг цілих Ñлів",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ÐаÑтройки безпеки вашого браузера не дозволÑÑŽÑ‚ÑŒ редактору автоматично виконувати операції вирізуваннÑ. Будь лаÑка, викориÑтовуйте клавіатуру Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ (Ctrl+X).",
+PasteErrorCopy : "ÐаÑтройки безпеки вашого браузера не дозволÑÑŽÑ‚ÑŒ редактору автоматично виконувати операції копіюваннÑ. Будь лаÑка, викориÑтовуйте клавіатуру Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ (Ctrl+C).",
+
+PasteAsText : "Ð’Ñтавити тільки текÑÑ‚",
+PasteFromWord : "Ð’Ñтавити з Word",
+
+DlgPasteMsg2 : "Будь-лаÑка, вÑтавте з буфера обміну в цю облаÑÑ‚ÑŒ, кориÑтуючиÑÑŒ комбінацією клавіш (<STRONG>Ctrl+V</STRONG>) та натиÑніть <STRONG>OK</STRONG>.",
+DlgPasteSec : "Редактор не може отримати прÑмий доÑтуп до буферу обміну у зв'Ñзку з налаштуваннÑми вашого браузера. Вам потрібно вÑтавити інформацію повторно в це вікно.",
+DlgPasteIgnoreFont : "Ігнорувати Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÑ€Ð¸Ñ„Ñ‚Ñ–Ð²",
+DlgPasteRemoveStyles : "Видалити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñтилів",
+
+// Color Picker
+ColorAutomatic : "Ðвтоматичний",
+ColorMoreColors : "Кольори...",
+
+// Document Properties
+DocProps : "ВлаÑтивоÑÑ‚Ñ– документа",
+
+// Anchor Dialog
+DlgAnchorTitle : "ВлаÑтивоÑÑ‚Ñ– ÑкорÑ",
+DlgAnchorName : "Ім'Ñ ÑкорÑ",
+DlgAnchorErrorName : "Будь лаÑка, занеÑÑ–Ñ‚ÑŒ ім'Ñ ÑкорÑ",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Ðе має в Ñловнику",
+DlgSpellChangeTo : "Замінити на",
+DlgSpellBtnIgnore : "Ігнорувати",
+DlgSpellBtnIgnoreAll : "Ігнорувати вÑе",
+DlgSpellBtnReplace : "Замінити",
+DlgSpellBtnReplaceAll : "Замінити вÑе",
+DlgSpellBtnUndo : "Ðазад",
+DlgSpellNoSuggestions : "- Ðемає припущень -",
+DlgSpellProgress : "ВиконуєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° орфографії...",
+DlgSpellNoMispell : "Перевірку орфографії завершено: помилок не знайдено",
+DlgSpellNoChanges : "Перевірку орфографії завершено: жодне Ñлово не змінено",
+DlgSpellOneChange : "Перевірку орфографії завершено: змінено одно Ñлово",
+DlgSpellManyChanges : "Перевірку орфографії завершено: 1% Ñлів змінено",
+
+IeSpellDownload : "Модуль перевірки орфографії не вÑтановлено. Бажаєтн завантажити його зараз?",
+
+// Button Dialog
+DlgButtonText : "ТекÑÑ‚ (ЗначеннÑ)",
+DlgButtonType : "Тип",
+DlgButtonTypeBtn : "Кнопка",
+DlgButtonTypeSbm : "Відправити",
+DlgButtonTypeRst : "Скинути",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Ім'Ñ",
+DlgCheckboxValue : "ЗначеннÑ",
+DlgCheckboxSelected : "Обрана",
+
+// Form Dialog
+DlgFormName : "Ім'Ñ",
+DlgFormAction : "ДіÑ",
+DlgFormMethod : "Метод",
+
+// Select Field Dialog
+DlgSelectName : "Ім'Ñ",
+DlgSelectValue : "ЗначеннÑ",
+DlgSelectSize : "Розмір",
+DlgSelectLines : "лінії",
+DlgSelectChkMulti : "Дозволити Ð¾Ð±Ñ€Ð°Ð½Ð½Ñ Ð´ÐµÐºÑ–Ð»ÑŒÐºÐ¾Ñ… позицій",
+DlgSelectOpAvail : "ДоÑтупні варіанти",
+DlgSelectOpText : "ТекÑÑ‚",
+DlgSelectOpValue : "ЗначеннÑ",
+DlgSelectBtnAdd : "Добавити",
+DlgSelectBtnModify : "Змінити",
+DlgSelectBtnUp : "Вгору",
+DlgSelectBtnDown : "Вниз",
+DlgSelectBtnSetValue : "Ð’Ñтановити Ñк вибране значеннÑ",
+DlgSelectBtnDelete : "Видалити",
+
+// Textarea Dialog
+DlgTextareaName : "Ім'Ñ",
+DlgTextareaCols : "Колонки",
+DlgTextareaRows : "Строки",
+
+// Text Field Dialog
+DlgTextName : "Ім'Ñ",
+DlgTextValue : "ЗначеннÑ",
+DlgTextCharWidth : "Ширина",
+DlgTextMaxChars : "МакÑ. кіл-Ñ‚ÑŒ Ñимволів",
+DlgTextType : "Тип",
+DlgTextTypeText : "ТекÑÑ‚",
+DlgTextTypePass : "Пароль",
+
+// Hidden Field Dialog
+DlgHiddenName : "Ім'Ñ",
+DlgHiddenValue : "ЗначеннÑ",
+
+// Bulleted List Dialog
+BulletedListProp : "ВлаÑтивоÑÑ‚Ñ– маркованого ÑпиÑка",
+NumberedListProp : "ВлаÑтивоÑÑ‚Ñ– нумерованного ÑпиÑка",
+DlgLstStart : "Початок",
+DlgLstType : "Тип",
+DlgLstTypeCircle : "Коло",
+DlgLstTypeDisc : "ДиÑк",
+DlgLstTypeSquare : "Квадрат",
+DlgLstTypeNumbers : "Ðомери (1, 2, 3)",
+DlgLstTypeLCase : "Літери нижнього регіÑтра(a, b, c)",
+DlgLstTypeUCase : "Букви верхнього регіÑтра (A, B, C)",
+DlgLstTypeSRoman : "Малі римÑькі літери (i, ii, iii)",
+DlgLstTypeLRoman : "Великі римÑькі літери (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Загальні",
+DlgDocBackTab : "Заднє тло",
+DlgDocColorsTab : "Кольори та відÑтупи",
+DlgDocMetaTab : "Мета дані",
+
+DlgDocPageTitle : "Заголовок Ñторінки",
+DlgDocLangDir : "ÐапрÑмок текÑту",
+DlgDocLangDirLTR : "Зліва на право (LTR)",
+DlgDocLangDirRTL : "Зправа на лево (RTL)",
+DlgDocLangCode : "Код мови",
+DlgDocCharSet : "ÐšÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð°Ð±Ð¾Ñ€Ñƒ Ñимволів",
+DlgDocCharSetCE : "Центрально-європейÑька",
+DlgDocCharSetCT : "КитайÑька традиційна (Big5)",
+DlgDocCharSetCR : "КирилицÑ",
+DlgDocCharSetGR : "Грецька",
+DlgDocCharSetJP : "ЯпонÑька",
+DlgDocCharSetKR : "КорейÑька",
+DlgDocCharSetTR : "Турецька",
+DlgDocCharSetUN : "Юнікод (UTF-8)",
+DlgDocCharSetWE : "Західно-европейÑкаÑ",
+DlgDocCharSetOther : "Інше ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð°Ð±Ð¾Ñ€Ñƒ Ñимволів",
+
+DlgDocDocType : "Заголовок типу документу",
+DlgDocDocTypeOther : "Інший заголовок типу документу",
+DlgDocIncXHTML : "Ввімкнути XHTML оголошеннÑ",
+DlgDocBgColor : "Колір тла",
+DlgDocBgImage : "URL Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚Ð»Ð°",
+DlgDocBgNoScroll : "Тло без прокрутки",
+DlgDocCText : "ТекÑÑ‚",
+DlgDocCLink : "ПоÑиланнÑ",
+DlgDocCVisited : "Відвідане поÑиланнÑ",
+DlgDocCActive : "Ðктивне поÑиланнÑ",
+DlgDocMargins : "ВідÑтупи Ñторінки",
+DlgDocMaTop : "Верхній",
+DlgDocMaLeft : "Лівий",
+DlgDocMaRight : "Правий",
+DlgDocMaBottom : "Ðижній",
+DlgDocMeIndex : "Ключові Ñлова документа (розділені комами)",
+DlgDocMeDescr : "ÐžÐ¿Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°",
+DlgDocMeAuthor : "Ðвтор",
+DlgDocMeCopy : "ÐвторÑькі права",
+DlgDocPreview : "Попередній переглÑд",
+
+// Templates Dialog
+Templates : "Шаблони",
+DlgTemplatesTitle : "Шаблони зміÑту",
+DlgTemplatesSelMsg : "Оберіть, будь лаÑка, шаблон Ð´Ð»Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð² редакторі<br>(поточний зміÑÑ‚ буде втрачено):",
+DlgTemplatesLoading : "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑпиÑку шаблонів. Зачекайте, будь лаÑка...",
+DlgTemplatesNoTpl : "(Ðе визначено жодного шаблону)",
+DlgTemplatesReplace : "Замінити поточний вміÑÑ‚",
+
+// About Dialog
+DlgAboutAboutTab : "Про програму",
+DlgAboutBrowserInfoTab : "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð°",
+DlgAboutLicenseTab : "ЛіцензіÑ",
+DlgAboutVersion : "ВерÑÑ–Ñ",
+DlgAboutInfo : "Додаткову інформацію дивітьÑÑ Ð½Ð° ",
+
+// Div Dialog
+DlgDivGeneralTab : "Загальна",
+DlgDivAdvancedTab : "Розширена",
+DlgDivStyle : "Стиль",
+DlgDivInlineStyle : "Inline Ñтиль",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/vi.js b/httemplate/elements/fckeditor/editor/lang/vi.js
new file mode 100644
index 0000000..ac94038
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/vi.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Vietnamese language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "Thu gá»n Thanh công cụ",
+ToolbarExpand : "Mở rộng Thanh công cụ",
+
+// Toolbar Items and Context Menu
+Save : "LÆ°u",
+NewPage : "Trang má»›i",
+Preview : "Xem trÆ°á»›c",
+Cut : "Cắt",
+Copy : "Sao chép",
+Paste : "Dán",
+PasteText : "Dán theo dạng văn bản thuần",
+PasteWord : "Dán với định dạng Word",
+Print : "In",
+SelectAll : "Chá»n Tất cả",
+RemoveFormat : "Xoá Äịnh dạng",
+InsertLinkLbl : "Liên kết",
+InsertLink : "Chèn/Sửa Liên kết",
+RemoveLink : "Xoá Liên kết",
+VisitLink : "Mở Liên Kết",
+Anchor : "Chèn/Sửa Neo",
+AnchorDelete : "Gỡ bỠNeo",
+InsertImageLbl : "Hình ảnh",
+InsertImage : "Chèn/Sửa Hình ảnh",
+InsertFlashLbl : "Flash",
+InsertFlash : "Chèn/Sửa Flash",
+InsertTableLbl : "Bảng",
+InsertTable : "Chèn/Sửa Bảng",
+InsertLineLbl : "ÄÆ°á»ng phân cách ngang",
+InsertLine : "Chèn ÄÆ°á»ng phân cách ngang",
+InsertSpecialCharLbl: "Ký tự đặc biệt",
+InsertSpecialChar : "Chèn Ký tự đặc biệt",
+InsertSmileyLbl : "Hình biểu lá»™ cảm xúc (mặt cÆ°á»i)",
+InsertSmiley : "Chèn Hình biểu lá»™ cảm xúc (mặt cÆ°á»i)",
+About : "Giới thiệu vỠFCKeditor",
+Bold : "Äậm",
+Italic : "Nghiêng",
+Underline : "Gạch chân",
+StrikeThrough : "Gạch xuyên ngang",
+Subscript : "Chỉ số dưới",
+Superscript : "Chỉ số trên",
+LeftJustify : "Canh trái",
+CenterJustify : "Canh giữa",
+RightJustify : "Canh phải",
+BlockJustify : "Canh Ä‘á»u",
+DecreaseIndent : "Dịch ra ngoài",
+IncreaseIndent : "Dịch vào trong",
+Blockquote : "Khối Trích dẫn",
+CreateDiv : "Tạo Div Container",
+EditDiv : "Chỉnh sửa Div Container",
+DeleteDiv : "Gỡ bỠDiv Container",
+Undo : "Khôi phục thao tác",
+Redo : "Làm lại thao tác",
+NumberedListLbl : "Danh sách có thứ tự",
+NumberedList : "Chèn/Xoá Danh sách có thứ tự",
+BulletedListLbl : "Danh sách không thứ tự",
+BulletedList : "Chèn/Xoá Danh sách không thứ tự",
+ShowTableBorders : "Hiển thị ÄÆ°á»ng viá»n bảng",
+ShowDetails : "Hiển thị Chi tiết",
+Style : "Mẫu",
+FontFormat : "Äịnh dạng",
+Font : "Phông",
+FontSize : "Cỡ chữ",
+TextColor : "Màu chữ",
+BGColor : "Màu ná»n",
+Source : "Mã HTML",
+Find : "Tìm kiếm",
+Replace : "Thay thế",
+SpellCheck : "Kiểm tra Chính tả",
+UniversalKeyboard : "Bàn phím Quốc tế",
+PageBreakLbl : "Ngắt trang",
+PageBreak : "Chèn Ngắt trang",
+
+Form : "Biểu mẫu",
+Checkbox : "Nút kiểm",
+RadioButton : "Nút chá»n",
+TextField : "TrÆ°á»ng văn bản",
+Textarea : "Vùng văn bản",
+HiddenField : "TrÆ°á»ng ẩn",
+Button : "Nút",
+SelectionField : "Ô chá»n",
+ImageButton : "Nút hình ảnh",
+
+FitWindow : "Mở rộng tối đa kích thước trình biên tập",
+ShowBlocks : "Hiển thị các Khối",
+
+// Context Menu
+EditLink : "Sửa Liên kết",
+CellCM : "Ô",
+RowCM : "Hàng",
+ColumnCM : "Cá»™t",
+InsertRowAfter : "Chèn Hàng Phía sau",
+InsertRowBefore : "Chèn Hàng Phía trước",
+DeleteRows : "Xoá Hàng",
+InsertColumnAfter : "Chèn Cột Phía sau",
+InsertColumnBefore : "Chèn Cột Phía trước",
+DeleteColumns : "Xoá Cột",
+InsertCellAfter : "Chèn Ô Phía sau",
+InsertCellBefore : "Chèn Ô Phía trước",
+DeleteCells : "Xoá Ô",
+MergeCells : "Kết hợp Ô",
+MergeRight : "Kết hợp Sang phải",
+MergeDown : "Kết hợp Xuống dưới",
+HorizontalSplitCell : "Tách ngang Ô",
+VerticalSplitCell : "Tách dá»c Ô",
+TableDelete : "Xóa Bảng",
+CellProperties : "Thuộc tính Ô",
+TableProperties : "Thuộc tính Bảng",
+ImageProperties : "Thuộc tính Hình ảnh",
+FlashProperties : "Thuộc tính Flash",
+
+AnchorProp : "Thuộc tính Neo",
+ButtonProp : "Thuộc tính Nút",
+CheckboxProp : "Thuộc tính Nút kiểm",
+HiddenFieldProp : "Thuá»™c tính TrÆ°á»ng ẩn",
+RadioButtonProp : "Thuá»™c tính Nút chá»n",
+ImageButtonProp : "Thuộc tính Nút hình ảnh",
+TextFieldProp : "Thuá»™c tính TrÆ°á»ng văn bản",
+SelectionFieldProp : "Thuá»™c tính Ô chá»n",
+TextareaProp : "Thuộc tính Vùng văn bản",
+FormProp : "Thuộc tính Biểu mẫu",
+
+FontFormats : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "Äang xá»­ lý XHTML. Vui lòng đợi trong giây lát...",
+Done : "Äã hoàn thành",
+PasteWordConfirm : "Văn bản bạn muốn dán có kèm định dạng của Word. Bạn có muốn loại bỠđịnh dạng Word trước khi dán?",
+NotCompatiblePaste : "Lệnh này chỉ được hỗ trợ từ trình duyệt Internet Explorer phiên bản 5.5 hoặc mới hơn. Bạn có muốn dán nguyên mẫu?",
+UnknownToolbarItem : "Không rõ mục trên thanh công cụ \"%1\"",
+UnknownCommand : "Không rõ lệnh \"%1\"",
+NotImplemented : "Lệnh không được thực hiện",
+UnknownToolbarSet : "Thanh công cụ \"%1\" không tồn tại",
+NoActiveX : "Các thiết lập bảo mật của trình duyệt có thể giá»›i hạn má»™t số chức năng của trình biên tập. Bạn phải bật tùy chá»n \"Run ActiveX controls and plug-ins\". Bạn có thể gặp má»™t số lá»—i và thấy thiếu má»™t số chức năng.",
+BrowseServerBlocked : "Không thể mở được bộ duyệt tài nguyên. Hãy đảm bảo chức năng chặn popup đã bị vô hiệu hóa.",
+DialogBlocked : "Không thể mở được cửa sổ hộp thoại. Hãy đảm bảo chức năng chặn popup đã bị vô hiệu hóa.",
+VisitLinkBlocked : "Không thể mở được cửa sổ trình duyệt mới. Hãy đảm bảo chức năng chặn popup đã bị vô hiệu hóa.",
+
+// Dialogs
+DlgBtnOK : "Äồng ý",
+DlgBtnCancel : "Bá» qua",
+DlgBtnClose : "Äóng",
+DlgBtnBrowseServer : "Duyệt trên máy chủ",
+DlgAdvancedTag : "Mở rộng",
+DlgOpOther : "<Khác>",
+DlgInfoTab : "Thông tin",
+DlgAlertUrl : "Hãy nhập vào một URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<không thiết lập>",
+DlgGenId : "Äịnh danh",
+DlgGenLangDir : "ÄÆ°á»ng dẫn Ngôn ngữ",
+DlgGenLangDirLtr : "Trái sang Phải (LTR)",
+DlgGenLangDirRtl : "Phải sang Trái (RTL)",
+DlgGenLangCode : "Mã Ngôn ngữ",
+DlgGenAccessKey : "Phím Hỗ trợ truy cập",
+DlgGenName : "Tên",
+DlgGenTabIndex : "Chỉ số của Tab",
+DlgGenLongDescr : "Mô tả URL",
+DlgGenClass : "Lá»›p Stylesheet",
+DlgGenTitle : "Advisory Title",
+DlgGenContType : "Advisory Content Type",
+DlgGenLinkCharset : "Bảng mã của tài nguyên được liên kết đến",
+DlgGenStyle : "Mẫu",
+
+// Image Dialog
+DlgImgTitle : "Thuộc tính Hình ảnh",
+DlgImgInfoTab : "Thông tin Hình ảnh",
+DlgImgBtnUpload : "Tải lên Máy chủ",
+DlgImgURL : "URL",
+DlgImgUpload : "Tải lên",
+DlgImgAlt : "Chú thích Hình ảnh",
+DlgImgWidth : "Rá»™ng",
+DlgImgHeight : "Cao",
+DlgImgLockRatio : "Giữ nguyên tỷ lệ",
+DlgBtnResetSize : "Kích thước gốc",
+DlgImgBorder : "ÄÆ°á»ng viá»n",
+DlgImgHSpace : "HSpace",
+DlgImgVSpace : "VSpace",
+DlgImgAlign : "Vị trí",
+DlgImgAlignLeft : "Trái",
+DlgImgAlignAbsBottom: "Dưới tuyệt đối",
+DlgImgAlignAbsMiddle: "Giữa tuyệt đối",
+DlgImgAlignBaseline : "ÄÆ°á»ng cÆ¡ sở",
+DlgImgAlignBottom : "DÆ°á»›i",
+DlgImgAlignMiddle : "Giữa",
+DlgImgAlignRight : "Phải",
+DlgImgAlignTextTop : "Phía trên chữ",
+DlgImgAlignTop : "Trên",
+DlgImgPreview : "Xem trÆ°á»›c",
+DlgImgAlertUrl : "Hãy đưa vào URL của hình ảnh",
+DlgImgLinkTab : "Liên kết",
+
+// Flash Dialog
+DlgFlashTitle : "Thuộc tính Flash",
+DlgFlashChkPlay : "Tự động chạy",
+DlgFlashChkLoop : "Lặp",
+DlgFlashChkMenu : "Cho phép bật Menu của Flash",
+DlgFlashScale : "Tỷ lệ",
+DlgFlashScaleAll : "Hiển thị tất cả",
+DlgFlashScaleNoBorder : "Không Ä‘Æ°á»ng viá»n",
+DlgFlashScaleFit : "Vừa vặn",
+
+// Link Dialog
+DlgLnkWindowTitle : "Liên kết",
+DlgLnkInfoTab : "Thông tin Liên kết",
+DlgLnkTargetTab : "Äích",
+
+DlgLnkType : "Kiểu Liên kết",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "Neo trong trang này",
+DlgLnkTypeEMail : "Thư điện tử",
+DlgLnkProto : "Giao thức",
+DlgLnkProtoOther : "<khác>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "Chá»n má»™t Neo",
+DlgLnkAnchorByName : "Theo Tên Neo",
+DlgLnkAnchorById : "Theo Äịnh danh Element",
+DlgLnkNoAnchors : "(Không có Neo nào trong tài liệu)",
+DlgLnkEMail : "Thư điện tử",
+DlgLnkEMailSubject : "Tiêu đỠThông điệp",
+DlgLnkEMailBody : "Nội dung Thông điệp",
+DlgLnkUpload : "Tải lên",
+DlgLnkBtnUpload : "Tải lên Máy chủ",
+
+DlgLnkTarget : "Äích",
+DlgLnkTargetFrame : "<khung>",
+DlgLnkTargetPopup : "<cửa sổ popup>",
+DlgLnkTargetBlank : "Cửa sổ mới (_blank)",
+DlgLnkTargetParent : "Cửa sổ cha (_parent)",
+DlgLnkTargetSelf : "Cùng cửa sổ (_self)",
+DlgLnkTargetTop : "Cửa sổ trên cùng(_top)",
+DlgLnkTargetFrameName : "Tên Khung đích",
+DlgLnkPopWinName : "Tên Cửa sổ Popup",
+DlgLnkPopWinFeat : "Äặc Ä‘iểm của Cá»­a sổ Popup",
+DlgLnkPopResize : "Kích thước thay đổi",
+DlgLnkPopLocation : "Thanh vị trí",
+DlgLnkPopMenu : "Thanh Menu",
+DlgLnkPopScroll : "Thanh cuá»™n",
+DlgLnkPopStatus : "Thanh trạng thái",
+DlgLnkPopToolbar : "Thanh công cụ",
+DlgLnkPopFullScrn : "Toàn màn hình (IE)",
+DlgLnkPopDependent : "Phụ thuộc (Netscape)",
+DlgLnkPopWidth : "Rá»™ng",
+DlgLnkPopHeight : "Cao",
+DlgLnkPopLeft : "Vị trí Trái",
+DlgLnkPopTop : "Vị trí Trên",
+
+DlnLnkMsgNoUrl : "Hãy đưa vào Liên kết URL",
+DlnLnkMsgNoEMail : "Hãy đưa vào địa chỉ thư điện tử",
+DlnLnkMsgNoAnchor : "Hãy chá»n má»™t Neo",
+DlnLnkMsgInvPopName : "Tên của cửa sổ Popup phải bắt đầu bằng một ký tự và không được chứa khoảng trắng",
+
+// Color Dialog
+DlgColorTitle : "Chá»n màu",
+DlgColorBtnClear : "Xoá",
+DlgColorHighlight : "Tô sáng",
+DlgColorSelected : "Äã chá»n",
+
+// Smiley Dialog
+DlgSmileyTitle : "Chèn Hình biểu lá»™ cảm xúc (mặt cÆ°á»i)",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "Hãy chá»n Ký tá»± đặc biệt",
+
+// Table Dialog
+DlgTableTitle : "Thuộc tính bảng",
+DlgTableRows : "Hàng",
+DlgTableColumns : "Cá»™t",
+DlgTableBorder : "Cỡ ÄÆ°á»ng viá»n",
+DlgTableAlign : "Canh lá»",
+DlgTableAlignNotSet : "<Chưa thiết lập>",
+DlgTableAlignLeft : "Trái",
+DlgTableAlignCenter : "Giữa",
+DlgTableAlignRight : "Phải",
+DlgTableWidth : "Rá»™ng",
+DlgTableWidthPx : "điểm (px)",
+DlgTableWidthPc : "%",
+DlgTableHeight : "Cao",
+DlgTableCellSpace : "Khoảng cách Ô",
+DlgTableCellPad : "Äệm Ô",
+DlgTableCaption : "Äầu Ä‘á»",
+DlgTableSummary : "Tóm lược",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "Thuộc tính Ô",
+DlgCellWidth : "Rá»™ng",
+DlgCellWidthPx : "điểm (px)",
+DlgCellWidthPc : "%",
+DlgCellHeight : "Cao",
+DlgCellWordWrap : "Bá»c từ",
+DlgCellWordWrapNotSet : "<Chưa thiết lập>",
+DlgCellWordWrapYes : "Äồng ý",
+DlgCellWordWrapNo : "Không",
+DlgCellHorAlign : "Canh theo Chiá»u ngang",
+DlgCellHorAlignNotSet : "<Chưa thiết lập>",
+DlgCellHorAlignLeft : "Trái",
+DlgCellHorAlignCenter : "Giữa",
+DlgCellHorAlignRight: "Phải",
+DlgCellVerAlign : "Canh theo Chiá»u dá»c",
+DlgCellVerAlignNotSet : "<Chưa thiết lập>",
+DlgCellVerAlignTop : "Trên",
+DlgCellVerAlignMiddle : "Giữa",
+DlgCellVerAlignBottom : "DÆ°á»›i",
+DlgCellVerAlignBaseline : "ÄÆ°á»ng cÆ¡ sở",
+DlgCellType : "Cell Type", //MISSING
+DlgCellTypeData : "Data", //MISSING
+DlgCellTypeHeader : "Header", //MISSING
+DlgCellRowSpan : "Nối Hàng",
+DlgCellCollSpan : "Nối Cột",
+DlgCellBackColor : "Màu ná»n",
+DlgCellBorderColor : "Màu viá»n",
+DlgCellBtnSelect : "Chá»n...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "Tìm kiếm và Thay Thế",
+
+// Find Dialog
+DlgFindTitle : "Tìm kiếm",
+DlgFindFindBtn : "Tìm kiếm",
+DlgFindNotFoundMsg : "Không tìm thấy chuỗi cần tìm.",
+
+// Replace Dialog
+DlgReplaceTitle : "Thay thế",
+DlgReplaceFindLbl : "Tìm chuỗi:",
+DlgReplaceReplaceLbl : "Thay bằng:",
+DlgReplaceCaseChk : "Phân biệt chữ hoa/thÆ°á»ng",
+DlgReplaceReplaceBtn : "Thay thế",
+DlgReplaceReplAllBtn : "Thay thế Tất cả",
+DlgReplaceWordChk : "Äúng toàn bá»™ từ",
+
+// Paste Operations / Dialog
+PasteErrorCut : "Các thiết lập bảo mật của trình duyệt không cho phép trình biên tập tự động thực thi lệnh cắt. Hãy sử dụng bàn phím cho lệnh này (Ctrl+X).",
+PasteErrorCopy : "Các thiết lập bảo mật của trình duyệt không cho phép trình biên tập tự động thực thi lệnh sao chép. Hãy sử dụng bàn phím cho lệnh này (Ctrl+C).",
+
+PasteAsText : "Dán theo định dạng văn bản thuần",
+PasteFromWord : "Dán với định dạng Word",
+
+DlgPasteMsg2 : "Hãy dán ná»™i dung vào trong khung bên dÆ°á»›i, sá»­ dụng tổ hợp phím (<STRONG>Ctrl+V</STRONG>) và nhấn vào nút <STRONG>Äồng ý</STRONG>.",
+DlgPasteSec : "Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.", //MISSING
+DlgPasteIgnoreFont : "Chấp nhận các định dạng phông",
+DlgPasteRemoveStyles : "Gỡ bỠcác định dạng Styles",
+
+// Color Picker
+ColorAutomatic : "Tá»± Ä‘á»™ng",
+ColorMoreColors : "Màu khác...",
+
+// Document Properties
+DocProps : "Thuộc tính Tài liệu",
+
+// Anchor Dialog
+DlgAnchorTitle : "Thuộc tính Neo",
+DlgAnchorName : "Tên của Neo",
+DlgAnchorErrorName : "Hãy nhập vào tên của Neo",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "Không có trong từ điển",
+DlgSpellChangeTo : "Chuyển thành",
+DlgSpellBtnIgnore : "Bá» qua",
+DlgSpellBtnIgnoreAll : "BỠqua Tất cả",
+DlgSpellBtnReplace : "Thay thế",
+DlgSpellBtnReplaceAll : "Thay thế Tất cả",
+DlgSpellBtnUndo : "Phục hồi lại",
+DlgSpellNoSuggestions : "- Không đưa ra gợi ý vỠtừ -",
+DlgSpellProgress : "Äang tiến hành kiểm tra chính tả...",
+DlgSpellNoMispell : "Hoàn tất kiểm tra chính tả: Không có lỗi chính tả",
+DlgSpellNoChanges : "Hoàn tất kiểm tra chính tả: Không có từ nào được thay đổi",
+DlgSpellOneChange : "Hoàn tất kiểm tra chính tả: Một từ đã được thay đổi",
+DlgSpellManyChanges : "Hoàn tất kiểm tra chính tả: %1 từ đã được thay đổi",
+
+IeSpellDownload : "Chức năng kiểm tra chính tả chưa được cài đặt. Bạn có muốn tải vỠngay bây gi�",
+
+// Button Dialog
+DlgButtonText : "Chuỗi hiển thị (Giá trị)",
+DlgButtonType : "Kiểu",
+DlgButtonTypeBtn : "Nút Bấm",
+DlgButtonTypeSbm : "Nút Gửi",
+DlgButtonTypeRst : "Nút Nhập lại",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "Tên",
+DlgCheckboxValue : "Giá trị",
+DlgCheckboxSelected : "Äược chá»n",
+
+// Form Dialog
+DlgFormName : "Tên",
+DlgFormAction : "Hành động",
+DlgFormMethod : "Phương thức",
+
+// Select Field Dialog
+DlgSelectName : "Tên",
+DlgSelectValue : "Giá trị",
+DlgSelectSize : "Kích cỡ",
+DlgSelectLines : "dòng",
+DlgSelectChkMulti : "Cho phép chá»n nhiá»u",
+DlgSelectOpAvail : "Các tùy chá»n có thể sá»­ dụng",
+DlgSelectOpText : "Văn bản",
+DlgSelectOpValue : "Giá trị",
+DlgSelectBtnAdd : "Thêm",
+DlgSelectBtnModify : "Thay đổi",
+DlgSelectBtnUp : "Lên",
+DlgSelectBtnDown : "Xuống",
+DlgSelectBtnSetValue : "Giá trị được chá»n",
+DlgSelectBtnDelete : "Xoá",
+
+// Textarea Dialog
+DlgTextareaName : "Tên",
+DlgTextareaCols : "Cá»™t",
+DlgTextareaRows : "Hàng",
+
+// Text Field Dialog
+DlgTextName : "Tên",
+DlgTextValue : "Giá trị",
+DlgTextCharWidth : "Rá»™ng",
+DlgTextMaxChars : "Số Ký tự tối đa",
+DlgTextType : "Kiểu",
+DlgTextTypeText : "Ký tự",
+DlgTextTypePass : "Mật khẩu",
+
+// Hidden Field Dialog
+DlgHiddenName : "Tên",
+DlgHiddenValue : "Giá trị",
+
+// Bulleted List Dialog
+BulletedListProp : "Thuộc tính Danh sách không thứ tự",
+NumberedListProp : "Thuộc tính Danh sách có thứ tự",
+DlgLstStart : "Bắt đầu",
+DlgLstType : "Kiểu",
+DlgLstTypeCircle : "Hình tròn",
+DlgLstTypeDisc : "Hình đĩa",
+DlgLstTypeSquare : "Hình vuông",
+DlgLstTypeNumbers : "Số thứ tự (1, 2, 3)",
+DlgLstTypeLCase : "Chữ cái thÆ°á»ng (a, b, c)",
+DlgLstTypeUCase : "Chữ cái hoa (A, B, C)",
+DlgLstTypeSRoman : "Số La Mã thÆ°á»ng (i, ii, iii)",
+DlgLstTypeLRoman : "Số La Mã hoa (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "Toàn thể",
+DlgDocBackTab : "Ná»n",
+DlgDocColorsTab : "Màu sắc và ÄÆ°á»ng biên",
+DlgDocMetaTab : "Siêu dữ liệu",
+
+DlgDocPageTitle : "Tiêu đỠTrang",
+DlgDocLangDir : "ÄÆ°á»ng dẫn Ngôn ngữ",
+DlgDocLangDirLTR : "Trái sang Phải (LTR)",
+DlgDocLangDirRTL : "Phải sang Trái (RTL)",
+DlgDocLangCode : "Mã Ngôn ngữ",
+DlgDocCharSet : "Bảng mã ký tự",
+DlgDocCharSetCE : "Trung Âu",
+DlgDocCharSetCT : "Tiếng Trung Quốc (Big5)",
+DlgDocCharSetCR : "Tiếng Kirin",
+DlgDocCharSetGR : "Tiếng Hy Lạp",
+DlgDocCharSetJP : "Tiếng Nhật",
+DlgDocCharSetKR : "Tiếng Hàn",
+DlgDocCharSetTR : "Tiếng Thổ Nhĩ Kỳ",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "Tây Âu",
+DlgDocCharSetOther : "Bảng mã ký tự khác",
+
+DlgDocDocType : "Kiểu Äá» mục Tài liệu",
+DlgDocDocTypeOther : "Kiểu Äá» mục Tài liệu khác",
+DlgDocIncXHTML : "Bao gồm cả định nghĩa XHTML",
+DlgDocBgColor : "Màu ná»n",
+DlgDocBgImage : "URL của Hình ảnh ná»n",
+DlgDocBgNoScroll : "Không cuá»™n ná»n",
+DlgDocCText : "Văn bản",
+DlgDocCLink : "Liên kết",
+DlgDocCVisited : "Liên kết Äã ghé thăm",
+DlgDocCActive : "Liên kết Hiện hành",
+DlgDocMargins : "ÄÆ°á»ng biên của Trang",
+DlgDocMaTop : "Trên",
+DlgDocMaLeft : "Trái",
+DlgDocMaRight : "Phải",
+DlgDocMaBottom : "DÆ°á»›i",
+DlgDocMeIndex : "Các từ khóa chỉ mục tài liệu (phân cách bởi dấu phẩy)",
+DlgDocMeDescr : "Mô tả tài liệu",
+DlgDocMeAuthor : "Tác giả",
+DlgDocMeCopy : "Bản quyá»n",
+DlgDocPreview : "Xem trÆ°á»›c",
+
+// Templates Dialog
+Templates : "Mẫu dựng sẵn",
+DlgTemplatesTitle : "Nội dung Mẫu dựng sẵn",
+DlgTemplatesSelMsg : "Hãy chá»n Mẫu dá»±ng sẵn để mở trong trình biên tập<br>(ná»™i dung hiện tại sẽ bị mất):",
+DlgTemplatesLoading : "Äang nạp Danh sách Mẫu dá»±ng sẵn. Vui lòng đợi trong giây lát...",
+DlgTemplatesNoTpl : "(Không có Mẫu dựng sẵn nào được định nghĩa)",
+DlgTemplatesReplace : "Thay thế nội dung hiện tại",
+
+// About Dialog
+DlgAboutAboutTab : "Giới thiệu",
+DlgAboutBrowserInfoTab : "Thông tin trình duyệt",
+DlgAboutLicenseTab : "Giấy phép",
+DlgAboutVersion : "phiên bản",
+DlgAboutInfo : "Äể biết thêm thông tin, hãy truy cập",
+
+// Div Dialog
+DlgDivGeneralTab : "Chung",
+DlgDivAdvancedTab : "Nâng cao",
+DlgDivStyle : "Kiểu Style",
+DlgDivInlineStyle : "Kiểu Style Trực tiếp",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/zh-cn.js b/httemplate/elements/fckeditor/editor/lang/zh-cn.js
new file mode 100644
index 0000000..256852b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/zh-cn.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Chinese Simplified language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "折å å·¥å…·æ ",
+ToolbarExpand : "展开工具æ ",
+
+// Toolbar Items and Context Menu
+Save : "ä¿å­˜",
+NewPage : "新建",
+Preview : "预览",
+Cut : "剪切",
+Copy : "å¤åˆ¶",
+Paste : "粘贴",
+PasteText : "粘贴为无格å¼æ–‡æœ¬",
+PasteWord : "从 MS Word 粘贴",
+Print : "打å°",
+SelectAll : "全选",
+RemoveFormat : "清除格å¼",
+InsertLinkLbl : "超链接",
+InsertLink : "æ’å…¥/编辑超链接",
+RemoveLink : "å–消超链接",
+VisitLink : "打开超链接",
+Anchor : "æ’å…¥/编辑锚点链接",
+AnchorDelete : "清除锚点链接",
+InsertImageLbl : "图象",
+InsertImage : "æ’å…¥/编辑图象",
+InsertFlashLbl : "Flash",
+InsertFlash : "æ’å…¥/编辑 Flash",
+InsertTableLbl : "表格",
+InsertTable : "æ’å…¥/编辑表格",
+InsertLineLbl : "水平线",
+InsertLine : "æ’入水平线",
+InsertSpecialCharLbl: "特殊符å·",
+InsertSpecialChar : "æ’入特殊符å·",
+InsertSmileyLbl : "表情符",
+InsertSmiley : "æ’入表情图标",
+About : "关于 FCKeditor",
+Bold : "加粗",
+Italic : "倾斜",
+Underline : "下划线",
+StrikeThrough : "删除线",
+Subscript : "下标",
+Superscript : "上标",
+LeftJustify : "左对é½",
+CenterJustify : "居中对é½",
+RightJustify : "å³å¯¹é½",
+BlockJustify : "两端对é½",
+DecreaseIndent : "å‡å°‘缩进é‡",
+IncreaseIndent : "增加缩进é‡",
+Blockquote : "å—引用",
+CreateDiv : "æ’å…¥ Div 标签",
+EditDiv : "编辑 Div 标签",
+DeleteDiv : "删除 Div 标签",
+Undo : "撤消",
+Redo : "é‡åš",
+NumberedListLbl : "ç¼–å·åˆ—表",
+NumberedList : "æ’å…¥/删除编å·åˆ—表",
+BulletedListLbl : "项目列表",
+BulletedList : "æ’å…¥/删除项目列表",
+ShowTableBorders : "显示表格边框",
+ShowDetails : "显示详细资料",
+Style : "æ ·å¼",
+FontFormat : "æ ¼å¼",
+Font : "字体",
+FontSize : "大å°",
+TextColor : "文本颜色",
+BGColor : "背景颜色",
+Source : "æºä»£ç ",
+Find : "查找",
+Replace : "替æ¢",
+SpellCheck : "拼写检查",
+UniversalKeyboard : "软键盘",
+PageBreakLbl : "分页符",
+PageBreak : "æ’入分页符",
+
+Form : "表å•",
+Checkbox : "å¤é€‰æ¡†",
+RadioButton : "å•é€‰æŒ‰é’®",
+TextField : "å•è¡Œæ–‡æœ¬",
+Textarea : "多行文本",
+HiddenField : "éšè—域",
+Button : "按钮",
+SelectionField : "列表/èœå•",
+ImageButton : "图åƒåŸŸ",
+
+FitWindow : "å…¨å±ç¼–辑",
+ShowBlocks : "显示区å—",
+
+// Context Menu
+EditLink : "编辑超链接",
+CellCM : "å•å…ƒæ ¼",
+RowCM : "行",
+ColumnCM : "列",
+InsertRowAfter : "在下方æ’入行",
+InsertRowBefore : "在上方æ’入行",
+DeleteRows : "删除行",
+InsertColumnAfter : "在å³ä¾§æ’入列",
+InsertColumnBefore : "在左侧æ’入列",
+DeleteColumns : "删除列",
+InsertCellAfter : "在å³ä¾§æ’å…¥å•å…ƒæ ¼",
+InsertCellBefore : "在左侧æ’å…¥å•å…ƒæ ¼",
+DeleteCells : "删除å•å…ƒæ ¼",
+MergeCells : "åˆå¹¶å•å…ƒæ ¼",
+MergeRight : "å‘å³åˆå¹¶å•å…ƒæ ¼",
+MergeDown : "å‘下åˆå¹¶å•å…ƒæ ¼",
+HorizontalSplitCell : "水平拆分å•å…ƒæ ¼",
+VerticalSplitCell : "垂直拆分å•å…ƒæ ¼",
+TableDelete : "删除表格",
+CellProperties : "å•å…ƒæ ¼å±žæ€§",
+TableProperties : "表格属性",
+ImageProperties : "图象属性",
+FlashProperties : "Flash 属性",
+
+AnchorProp : "锚点链接属性",
+ButtonProp : "按钮属性",
+CheckboxProp : "å¤é€‰æ¡†å±žæ€§",
+HiddenFieldProp : "éšè—域属性",
+RadioButtonProp : "å•é€‰æŒ‰é’®å±žæ€§",
+ImageButtonProp : "图åƒåŸŸå±žæ€§",
+TextFieldProp : "å•è¡Œæ–‡æœ¬å±žæ€§",
+SelectionFieldProp : "èœå•/列表属性",
+TextareaProp : "多行文本属性",
+FormProp : "表å•å±žæ€§",
+
+FontFormats : "普通;已编排格å¼;地å€;标题 1;标题 2;标题 3;标题 4;标题 5;标题 6;段è½(DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "æ­£åœ¨å¤„ç† XHTML,请ç¨ç­‰...",
+Done : "完æˆ",
+PasteWordConfirm : "您è¦ç²˜è´´çš„内容好åƒæ˜¯æ¥è‡ª MS Word,是å¦è¦æ¸…除 MS Word æ ¼å¼åŽå†ç²˜è´´ï¼Ÿ",
+NotCompatiblePaste : "è¯¥å‘½ä»¤éœ€è¦ Internet Explorer 5.5 或更高版本的支æŒï¼Œæ˜¯å¦æŒ‰å¸¸è§„粘贴进行?",
+UnknownToolbarItem : "未知工具æ é¡¹ç›® \"%1\"",
+UnknownCommand : "未知命令å称 \"%1\"",
+NotImplemented : "命令无法执行",
+UnknownToolbarSet : "工具æ è®¾ç½® \"%1\" ä¸å­˜åœ¨",
+NoActiveX : "æµè§ˆå™¨å®‰å…¨è®¾ç½®é™åˆ¶äº†æœ¬ç¼–辑器的æŸäº›åŠŸèƒ½ã€‚您必须å¯ç”¨å®‰å…¨è®¾ç½®ä¸­çš„“è¿è¡Œ ActiveX 控件和æ’件â€ï¼Œå¦åˆ™å°†å‡ºçŽ°æŸäº›é”™è¯¯å¹¶ç¼ºå°‘功能。",
+BrowseServerBlocked : "无法打开资æºæµè§ˆå™¨ï¼Œè¯·ç¡®è®¤æ˜¯å¦å¯ç”¨äº†ç¦æ­¢å¼¹å‡ºçª—å£ã€‚",
+DialogBlocked : "无法打开对è¯æ¡†çª—å£ï¼Œè¯·ç¡®è®¤æ˜¯å¦å¯ç”¨äº†ç¦æ­¢å¼¹å‡ºçª—å£æˆ–网页对è¯æ¡†ï¼ˆIE)。",
+VisitLinkBlocked : "无法打开新窗å£ï¼Œè¯·ç¡®è®¤æ˜¯å¦å¯ç”¨äº†ç¦æ­¢å¼¹å‡ºçª—å£æˆ–网页对è¯æ¡†ï¼ˆIE)。",
+
+// Dialogs
+DlgBtnOK : "确定",
+DlgBtnCancel : "å–消",
+DlgBtnClose : "关闭",
+DlgBtnBrowseServer : "æµè§ˆæœåŠ¡å™¨",
+DlgAdvancedTag : "高级",
+DlgOpOther : "<其它>",
+DlgInfoTab : "ä¿¡æ¯",
+DlgAlertUrl : "请æ’å…¥ URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<没有设置>",
+DlgGenId : "ID",
+DlgGenLangDir : "语言方å‘",
+DlgGenLangDirLtr : "ä»Žå·¦åˆ°å³ (LTR)",
+DlgGenLangDirRtl : "从å³åˆ°å·¦ (RTL)",
+DlgGenLangCode : "语言代ç ",
+DlgGenAccessKey : "访问键",
+DlgGenName : "å称",
+DlgGenTabIndex : "Tab 键次åº",
+DlgGenLongDescr : "详细说明地å€",
+DlgGenClass : "æ ·å¼ç±»å称",
+DlgGenTitle : "标题",
+DlgGenContType : "内容类型",
+DlgGenLinkCharset : "字符编ç ",
+DlgGenStyle : "行内样å¼",
+
+// Image Dialog
+DlgImgTitle : "图象属性",
+DlgImgInfoTab : "图象",
+DlgImgBtnUpload : "å‘é€åˆ°æœåŠ¡å™¨ä¸Š",
+DlgImgURL : "æºæ–‡ä»¶",
+DlgImgUpload : "上传",
+DlgImgAlt : "替æ¢æ–‡æœ¬",
+DlgImgWidth : "宽度",
+DlgImgHeight : "高度",
+DlgImgLockRatio : "é”定比例",
+DlgBtnResetSize : "æ¢å¤å°ºå¯¸",
+DlgImgBorder : "边框大å°",
+DlgImgHSpace : "水平间è·",
+DlgImgVSpace : "åž‚ç›´é—´è·",
+DlgImgAlign : "对é½æ–¹å¼",
+DlgImgAlignLeft : "左对é½",
+DlgImgAlignAbsBottom: "ç»å¯¹åº•è¾¹",
+DlgImgAlignAbsMiddle: "ç»å¯¹å±…中",
+DlgImgAlignBaseline : "基线",
+DlgImgAlignBottom : "底边",
+DlgImgAlignMiddle : "居中",
+DlgImgAlignRight : "å³å¯¹é½",
+DlgImgAlignTextTop : "文本上方",
+DlgImgAlignTop : "顶端",
+DlgImgPreview : "预览",
+DlgImgAlertUrl : "请输入图象地å€",
+DlgImgLinkTab : "链接",
+
+// Flash Dialog
+DlgFlashTitle : "Flash 属性",
+DlgFlashChkPlay : "自动播放",
+DlgFlashChkLoop : "循环",
+DlgFlashChkMenu : "å¯ç”¨ Flash èœå•",
+DlgFlashScale : "缩放",
+DlgFlashScaleAll : "全部显示",
+DlgFlashScaleNoBorder : "无边框",
+DlgFlashScaleFit : "严格匹é…",
+
+// Link Dialog
+DlgLnkWindowTitle : "超链接",
+DlgLnkInfoTab : "超链接信æ¯",
+DlgLnkTargetTab : "目标",
+
+DlgLnkType : "超链接类型",
+DlgLnkTypeURL : "超链接",
+DlgLnkTypeAnchor : "页内锚点链接",
+DlgLnkTypeEMail : "电å­é‚®ä»¶",
+DlgLnkProto : "åè®®",
+DlgLnkProtoOther : "<其它>",
+DlgLnkURL : "地å€",
+DlgLnkAnchorSel : "选择一个锚点",
+DlgLnkAnchorByName : "按锚点å称",
+DlgLnkAnchorById : "按锚点 ID",
+DlgLnkNoAnchors : "(此文档没有å¯ç”¨çš„锚点)",
+DlgLnkEMail : "地å€",
+DlgLnkEMailSubject : "主题",
+DlgLnkEMailBody : "内容",
+DlgLnkUpload : "上传",
+DlgLnkBtnUpload : "å‘é€åˆ°æœåŠ¡å™¨ä¸Š",
+
+DlgLnkTarget : "目标",
+DlgLnkTargetFrame : "<框架>",
+DlgLnkTargetPopup : "<弹出窗å£>",
+DlgLnkTargetBlank : "æ–°çª—å£ (_blank)",
+DlgLnkTargetParent : "çˆ¶çª—å£ (_parent)",
+DlgLnkTargetSelf : "æœ¬çª—å£ (_self)",
+DlgLnkTargetTop : "整页 (_top)",
+DlgLnkTargetFrameName : "目标框架å称",
+DlgLnkPopWinName : "弹出窗å£å称",
+DlgLnkPopWinFeat : "弹出窗å£å±žæ€§",
+DlgLnkPopResize : "调整大å°",
+DlgLnkPopLocation : "地å€æ ",
+DlgLnkPopMenu : "èœå•æ ",
+DlgLnkPopScroll : "滚动æ¡",
+DlgLnkPopStatus : "状æ€æ ",
+DlgLnkPopToolbar : "工具æ ",
+DlgLnkPopFullScrn : "å…¨å± (IE)",
+DlgLnkPopDependent : "ä¾é™„ (NS)",
+DlgLnkPopWidth : "宽",
+DlgLnkPopHeight : "高",
+DlgLnkPopLeft : "å·¦",
+DlgLnkPopTop : "å³",
+
+DlnLnkMsgNoUrl : "请输入超链接地å€",
+DlnLnkMsgNoEMail : "请输入电å­é‚®ä»¶åœ°å€",
+DlnLnkMsgNoAnchor : "请选择一个锚点",
+DlnLnkMsgInvPopName : "弹出窗å£å称必须以字æ¯å¼€å¤´ï¼Œå¹¶ä¸”ä¸èƒ½å«æœ‰ç©ºæ ¼ã€‚",
+
+// Color Dialog
+DlgColorTitle : "选择颜色",
+DlgColorBtnClear : "清除",
+DlgColorHighlight : "预览",
+DlgColorSelected : "选择",
+
+// Smiley Dialog
+DlgSmileyTitle : "æ’入表情图标",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "选择特殊符å·",
+
+// Table Dialog
+DlgTableTitle : "表格属性",
+DlgTableRows : "行数",
+DlgTableColumns : "列数",
+DlgTableBorder : "边框",
+DlgTableAlign : "对é½",
+DlgTableAlignNotSet : "<没有设置>",
+DlgTableAlignLeft : "左对é½",
+DlgTableAlignCenter : "居中",
+DlgTableAlignRight : "å³å¯¹é½",
+DlgTableWidth : "宽度",
+DlgTableWidthPx : "åƒç´ ",
+DlgTableWidthPc : "百分比",
+DlgTableHeight : "高度",
+DlgTableCellSpace : "é—´è·",
+DlgTableCellPad : "è¾¹è·",
+DlgTableCaption : "标题",
+DlgTableSummary : "摘è¦",
+DlgTableHeaders : "标题å•å…ƒæ ¼",
+DlgTableHeadersNone : "æ— ",
+DlgTableHeadersColumn : "第一列",
+DlgTableHeadersRow : "第一行",
+DlgTableHeadersBoth : "第一列和第一行",
+
+// Table Cell Dialog
+DlgCellTitle : "å•å…ƒæ ¼å±žæ€§",
+DlgCellWidth : "宽度",
+DlgCellWidthPx : "åƒç´ ",
+DlgCellWidthPc : "百分比",
+DlgCellHeight : "高度",
+DlgCellWordWrap : "自动æ¢è¡Œ",
+DlgCellWordWrapNotSet : "<没有设置>",
+DlgCellWordWrapYes : "是",
+DlgCellWordWrapNo : "å¦",
+DlgCellHorAlign : "水平对é½",
+DlgCellHorAlignNotSet : "<没有设置>",
+DlgCellHorAlignLeft : "左对é½",
+DlgCellHorAlignCenter : "居中",
+DlgCellHorAlignRight: "å³å¯¹é½",
+DlgCellVerAlign : "垂直对é½",
+DlgCellVerAlignNotSet : "<没有设置>",
+DlgCellVerAlignTop : "顶端",
+DlgCellVerAlignMiddle : "居中",
+DlgCellVerAlignBottom : "底部",
+DlgCellVerAlignBaseline : "基线",
+DlgCellType : "å•å…ƒæ ¼ç±»åž‹",
+DlgCellTypeData : "资料",
+DlgCellTypeHeader : "标题",
+DlgCellRowSpan : "纵跨行数",
+DlgCellCollSpan : "横跨列数",
+DlgCellBackColor : "背景颜色",
+DlgCellBorderColor : "边框颜色",
+DlgCellBtnSelect : "选择...",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "查找和替æ¢",
+
+// Find Dialog
+DlgFindTitle : "查找",
+DlgFindFindBtn : "查找",
+DlgFindNotFoundMsg : "指定文本没有找到。",
+
+// Replace Dialog
+DlgReplaceTitle : "替æ¢",
+DlgReplaceFindLbl : "查找:",
+DlgReplaceReplaceLbl : "替æ¢:",
+DlgReplaceCaseChk : "区分大å°å†™",
+DlgReplaceReplaceBtn : "替æ¢",
+DlgReplaceReplAllBtn : "全部替æ¢",
+DlgReplaceWordChk : "全字匹é…",
+
+// Paste Operations / Dialog
+PasteErrorCut : "您的æµè§ˆå™¨å®‰å…¨è®¾ç½®ä¸å…许编辑器自动执行剪切æ“作,请使用键盘快æ·é”®(Ctrl+X)æ¥å®Œæˆã€‚",
+PasteErrorCopy : "您的æµè§ˆå™¨å®‰å…¨è®¾ç½®ä¸å…许编辑器自动执行å¤åˆ¶æ“作,请使用键盘快æ·é”®(Ctrl+C)æ¥å®Œæˆã€‚",
+
+PasteAsText : "粘贴为无格å¼æ–‡æœ¬",
+PasteFromWord : "从 MS Word 粘贴",
+
+DlgPasteMsg2 : "请使用键盘快æ·é”®(<STRONG>Ctrl+V</STRONG>)把内容粘贴到下é¢çš„方框里,å†æŒ‰ <STRONG>确定</STRONG>。",
+DlgPasteSec : "因为你的æµè§ˆå™¨çš„安全设置原因,本编辑器ä¸èƒ½ç›´æŽ¥è®¿é—®ä½ çš„剪贴æ¿å†…容,你需è¦åœ¨æœ¬çª—å£é‡æ–°ç²˜è´´ä¸€æ¬¡ã€‚",
+DlgPasteIgnoreFont : "忽略 Font 标签",
+DlgPasteRemoveStyles : "æ¸…ç† CSS æ ·å¼",
+
+// Color Picker
+ColorAutomatic : "自动",
+ColorMoreColors : "其它颜色...",
+
+// Document Properties
+DocProps : "页é¢å±žæ€§",
+
+// Anchor Dialog
+DlgAnchorTitle : "命å锚点",
+DlgAnchorName : "锚点å称",
+DlgAnchorErrorName : "请输入锚点å称",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "没有在字典里",
+DlgSpellChangeTo : "更改为",
+DlgSpellBtnIgnore : "忽略",
+DlgSpellBtnIgnoreAll : "全部忽略",
+DlgSpellBtnReplace : "替æ¢",
+DlgSpellBtnReplaceAll : "全部替æ¢",
+DlgSpellBtnUndo : "撤消",
+DlgSpellNoSuggestions : "- 没有建议 -",
+DlgSpellProgress : "正在进行拼写检查...",
+DlgSpellNoMispell : "拼写检查完æˆï¼šæ²¡æœ‰å‘现拼写错误",
+DlgSpellNoChanges : "拼写检查完æˆï¼šæ²¡æœ‰æ›´æ”¹ä»»ä½•å•è¯",
+DlgSpellOneChange : "拼写检查完æˆï¼šæ›´æ”¹äº†ä¸€ä¸ªå•è¯",
+DlgSpellManyChanges : "拼写检查完æˆï¼šæ›´æ”¹äº† %1 个å•è¯",
+
+IeSpellDownload : "拼写检查æ’件还没安装,你是å¦æƒ³çŽ°åœ¨å°±ä¸‹è½½ï¼Ÿ",
+
+// Button Dialog
+DlgButtonText : "标签(值)",
+DlgButtonType : "类型",
+DlgButtonTypeBtn : "按钮",
+DlgButtonTypeSbm : "æ交",
+DlgButtonTypeRst : "é‡è®¾",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "å称",
+DlgCheckboxValue : "选定值",
+DlgCheckboxSelected : "已勾选",
+
+// Form Dialog
+DlgFormName : "å称",
+DlgFormAction : "动作",
+DlgFormMethod : "方法",
+
+// Select Field Dialog
+DlgSelectName : "å称",
+DlgSelectValue : "选定",
+DlgSelectSize : "高度",
+DlgSelectLines : "行",
+DlgSelectChkMulti : "å…许多选",
+DlgSelectOpAvail : "列表值",
+DlgSelectOpText : "标签",
+DlgSelectOpValue : "值",
+DlgSelectBtnAdd : "新增",
+DlgSelectBtnModify : "修改",
+DlgSelectBtnUp : "上移",
+DlgSelectBtnDown : "下移",
+DlgSelectBtnSetValue : "设为åˆå§‹åŒ–时选定",
+DlgSelectBtnDelete : "删除",
+
+// Textarea Dialog
+DlgTextareaName : "å称",
+DlgTextareaCols : "字符宽度",
+DlgTextareaRows : "行数",
+
+// Text Field Dialog
+DlgTextName : "å称",
+DlgTextValue : "åˆå§‹å€¼",
+DlgTextCharWidth : "字符宽度",
+DlgTextMaxChars : "最多字符数",
+DlgTextType : "类型",
+DlgTextTypeText : "文本",
+DlgTextTypePass : "密ç ",
+
+// Hidden Field Dialog
+DlgHiddenName : "å称",
+DlgHiddenValue : "åˆå§‹å€¼",
+
+// Bulleted List Dialog
+BulletedListProp : "项目列表属性",
+NumberedListProp : "ç¼–å·åˆ—表属性",
+DlgLstStart : "开始åºå·",
+DlgLstType : "列表类型",
+DlgLstTypeCircle : "圆圈",
+DlgLstTypeDisc : "圆点",
+DlgLstTypeSquare : "æ–¹å—",
+DlgLstTypeNumbers : "æ•°å­— (1, 2, 3)",
+DlgLstTypeLCase : "å°å†™å­—æ¯ (a, b, c)",
+DlgLstTypeUCase : "å¤§å†™å­—æ¯ (A, B, C)",
+DlgLstTypeSRoman : "å°å†™ç½—马数字 (i, ii, iii)",
+DlgLstTypeLRoman : "大写罗马数字 (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "常规",
+DlgDocBackTab : "背景",
+DlgDocColorsTab : "颜色和边è·",
+DlgDocMetaTab : "Meta æ•°æ®",
+
+DlgDocPageTitle : "页é¢æ ‡é¢˜",
+DlgDocLangDir : "语言方å‘",
+DlgDocLangDirLTR : "ä»Žå·¦åˆ°å³ (LTR)",
+DlgDocLangDirRTL : "从å³åˆ°å·¦ (RTL)",
+DlgDocLangCode : "语言代ç ",
+DlgDocCharSet : "字符编ç ",
+DlgDocCharSetCE : "中欧",
+DlgDocCharSetCT : "ç¹ä½“中文 (Big5)",
+DlgDocCharSetCR : "西里尔文",
+DlgDocCharSetGR : "希腊文",
+DlgDocCharSetJP : "日文",
+DlgDocCharSetKR : "韩文",
+DlgDocCharSetTR : "土耳其文",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "西欧",
+DlgDocCharSetOther : "其它字符编ç ",
+
+DlgDocDocType : "文档类型",
+DlgDocDocTypeOther : "其它文档类型",
+DlgDocIncXHTML : "åŒ…å« XHTML 声明",
+DlgDocBgColor : "背景颜色",
+DlgDocBgImage : "背景图åƒ",
+DlgDocBgNoScroll : "ä¸æ»šåŠ¨èƒŒæ™¯å›¾åƒ",
+DlgDocCText : "文本",
+DlgDocCLink : "超链接",
+DlgDocCVisited : "已访问的超链接",
+DlgDocCActive : "活动超链接",
+DlgDocMargins : "页é¢è¾¹è·",
+DlgDocMaTop : "上",
+DlgDocMaLeft : "å·¦",
+DlgDocMaRight : "å³",
+DlgDocMaBottom : "下",
+DlgDocMeIndex : "页é¢ç´¢å¼•å…³é”®å­— (用åŠè§’逗å·[,]分隔)",
+DlgDocMeDescr : "页é¢è¯´æ˜Ž",
+DlgDocMeAuthor : "作者",
+DlgDocMeCopy : "版æƒ",
+DlgDocPreview : "预览",
+
+// Templates Dialog
+Templates : "模æ¿",
+DlgTemplatesTitle : "内容模æ¿",
+DlgTemplatesSelMsg : "请选择编辑器内容模æ¿:",
+DlgTemplatesLoading : "正在加载模æ¿åˆ—表,请ç¨ç­‰...",
+DlgTemplatesNoTpl : "(没有模æ¿)",
+DlgTemplatesReplace : "替æ¢å½“å‰å†…容",
+
+// About Dialog
+DlgAboutAboutTab : "关于",
+DlgAboutBrowserInfoTab : "æµè§ˆå™¨ä¿¡æ¯",
+DlgAboutLicenseTab : "许å¯è¯",
+DlgAboutVersion : "版本",
+DlgAboutInfo : "è¦èŽ·å¾—更多信æ¯è¯·è®¿é—® ",
+
+// Div Dialog
+DlgDivGeneralTab : "常规",
+DlgDivAdvancedTab : "高级",
+DlgDivStyle : "æ ·å¼",
+DlgDivInlineStyle : "CSS æ ·å¼",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/lang/zh.js b/httemplate/elements/fckeditor/editor/lang/zh.js
new file mode 100644
index 0000000..8d47829
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/lang/zh.js
@@ -0,0 +1,539 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Chinese Traditional language file.
+ */
+
+var FCKLang =
+{
+// Language direction : "ltr" (left to right) or "rtl" (right to left).
+Dir : "ltr",
+
+ToolbarCollapse : "éš±è—é¢æ¿",
+ToolbarExpand : "顯示é¢æ¿",
+
+// Toolbar Items and Context Menu
+Save : "儲存",
+NewPage : "開新檔案",
+Preview : "é è¦½",
+Cut : "剪下",
+Copy : "複製",
+Paste : "貼上",
+PasteText : "貼為純文字格å¼",
+PasteWord : "自 Word 貼上",
+Print : "列å°",
+SelectAll : "å…¨é¸",
+RemoveFormat : "清除格å¼",
+InsertLinkLbl : "超連çµ",
+InsertLink : "æ’å…¥/編輯超連çµ",
+RemoveLink : "移除超連çµ",
+VisitLink : "開啟超連çµ",
+Anchor : "æ’å…¥/編輯錨點",
+AnchorDelete : "移除錨點",
+InsertImageLbl : "å½±åƒ",
+InsertImage : "æ’å…¥/編輯影åƒ",
+InsertFlashLbl : "Flash",
+InsertFlash : "æ’å…¥/編輯 Flash",
+InsertTableLbl : "表格",
+InsertTable : "æ’å…¥/編輯表格",
+InsertLineLbl : "水平線",
+InsertLine : "æ’入水平線",
+InsertSpecialCharLbl: "特殊符號",
+InsertSpecialChar : "æ’入特殊符號",
+InsertSmileyLbl : "表情符號",
+InsertSmiley : "æ’入表情符號",
+About : "關於 FCKeditor",
+Bold : "ç²—é«”",
+Italic : "斜體",
+Underline : "底線",
+StrikeThrough : "刪除線",
+Subscript : "下標",
+Superscript : "上標",
+LeftJustify : "é å·¦å°é½Š",
+CenterJustify : "置中",
+RightJustify : "é å³å°é½Š",
+BlockJustify : "å·¦å³å°é½Š",
+DecreaseIndent : "減少縮排",
+IncreaseIndent : "增加縮排",
+Blockquote : "引用文字",
+CreateDiv : "新增 Div 標籤",
+EditDiv : "變更 Div 標籤",
+DeleteDiv : "移除 Div 標籤",
+Undo : "復原",
+Redo : "é‡è¤‡",
+NumberedListLbl : "編號清單",
+NumberedList : "æ’å…¥/移除編號清單",
+BulletedListLbl : "項目清單",
+BulletedList : "æ’å…¥/移除項目清單",
+ShowTableBorders : "顯示表格邊框",
+ShowDetails : "顯示詳細資料",
+Style : "樣å¼",
+FontFormat : "æ ¼å¼",
+Font : "å­—é«”",
+FontSize : "大å°",
+TextColor : "文字é¡è‰²",
+BGColor : "背景é¡è‰²",
+Source : "原始碼",
+Find : "尋找",
+Replace : "å–代",
+SpellCheck : "拼字檢查",
+UniversalKeyboard : "è¬åœ‹éµç›¤",
+PageBreakLbl : "分é ç¬¦è™Ÿ",
+PageBreak : "æ’入分é ç¬¦è™Ÿ",
+
+Form : "表單",
+Checkbox : "æ ¸å–方塊",
+RadioButton : "é¸é …按鈕",
+TextField : "文字方塊",
+Textarea : "文字å€åŸŸ",
+HiddenField : "éš±è—欄ä½",
+Button : "按鈕",
+SelectionField : "清單/é¸å–®",
+ImageButton : "å½±åƒæŒ‰éˆ•",
+
+FitWindow : "編輯器最大化",
+ShowBlocks : "顯示å€å¡Š",
+
+// Context Menu
+EditLink : "編輯超連çµ",
+CellCM : "儲存格",
+RowCM : "列",
+ColumnCM : "欄",
+InsertRowAfter : "å‘下æ’入列",
+InsertRowBefore : "å‘上æ’入列",
+DeleteRows : "刪除列",
+InsertColumnAfter : "å‘å³æ’入欄",
+InsertColumnBefore : "å‘å·¦æ’入欄",
+DeleteColumns : "刪除欄",
+InsertCellAfter : "å‘å³æ’入儲存格",
+InsertCellBefore : "å‘å·¦æ’入儲存格",
+DeleteCells : "刪除儲存格",
+MergeCells : "åˆä½µå„²å­˜æ ¼",
+MergeRight : "å‘å³åˆä½µå„²å­˜æ ¼",
+MergeDown : "å‘下åˆä½µå„²å­˜æ ¼",
+HorizontalSplitCell : "æ©«å‘分割儲存格",
+VerticalSplitCell : "縱å‘分割儲存格",
+TableDelete : "刪除表格",
+CellProperties : "儲存格屬性",
+TableProperties : "表格屬性",
+ImageProperties : "å½±åƒå±¬æ€§",
+FlashProperties : "Flash 屬性",
+
+AnchorProp : "錨點屬性",
+ButtonProp : "按鈕屬性",
+CheckboxProp : "æ ¸å–方塊屬性",
+HiddenFieldProp : "éš±è—欄ä½å±¬æ€§",
+RadioButtonProp : "é¸é …按鈕屬性",
+ImageButtonProp : "å½±åƒæŒ‰éˆ•å±¬æ€§",
+TextFieldProp : "文字方塊屬性",
+SelectionFieldProp : "清單/é¸å–®å±¬æ€§",
+TextareaProp : "文字å€åŸŸå±¬æ€§",
+FormProp : "表單屬性",
+
+FontFormats : "一般;已格å¼åŒ–;ä½å€;標題 1;標題 2;標題 3;標題 4;標題 5;標題 6;一般 (DIV)",
+
+// Alerts and Messages
+ProcessingXHTML : "è™•ç† XHTML 中,請ç¨å€™â€¦",
+Done : "完æˆ",
+PasteWordConfirm : "您想貼上的文字似乎是自 Word 複製而來,請å•æ‚¨æ˜¯å¦è¦å…ˆæ¸…除 Word çš„æ ¼å¼å¾Œå†è¡Œè²¼ä¸Šï¼Ÿ",
+NotCompatiblePaste : "此指令僅在 Internet Explorer 5.5 或以上的版本有效。請å•æ‚¨æ˜¯å¦åŒæ„ä¸æ¸…除格å¼å³è²¼ä¸Šï¼Ÿ",
+UnknownToolbarItem : "未知工具列項目 \"%1\"",
+UnknownCommand : "未知指令å稱 \"%1\"",
+NotImplemented : "尚未安è£æ­¤æŒ‡ä»¤",
+UnknownToolbarSet : "工具列設定 \"%1\" ä¸å­˜åœ¨",
+NoActiveX : "ç€è¦½å™¨çš„安全性設定é™åˆ¶äº†æœ¬ç·¨è¼¯å™¨çš„æŸäº›åŠŸèƒ½ã€‚您必須啟用安全性設定中的「執行ActiveX控制項與外掛程å¼ã€é …目,å¦å‰‡æœ¬ç·¨è¼¯å™¨å°‡æœƒå‡ºç¾éŒ¯èª¤ä¸¦ç¼ºå°‘æŸäº›åŠŸèƒ½",
+BrowseServerBlocked : "無法開啟資æºç€è¦½å™¨ï¼Œè«‹ç¢ºå®šæ‰€æœ‰å¿«é¡¯è¦–窗å°éŽ–程å¼æ˜¯å¦é—œé–‰",
+DialogBlocked : "無法開啟å°è©±è¦–窗,請確定所有快顯視窗å°éŽ–程å¼æ˜¯å¦é—œé–‰",
+VisitLinkBlocked : "無法開啟新視窗,請確定所有快顯視窗å°éŽ–程å¼æ˜¯å¦é—œé–‰",
+
+// Dialogs
+DlgBtnOK : "確定",
+DlgBtnCancel : "å–消",
+DlgBtnClose : "關閉",
+DlgBtnBrowseServer : "ç€è¦½ä¼ºæœå™¨ç«¯",
+DlgAdvancedTag : "進階",
+DlgOpOther : "<其他>",
+DlgInfoTab : "資訊",
+DlgAlertUrl : "è«‹æ’å…¥ URL",
+
+// General Dialogs Labels
+DlgGenNotSet : "<尚未設定>",
+DlgGenId : "ID",
+DlgGenLangDir : "語言方å‘",
+DlgGenLangDirLtr : "ç”±å·¦è€Œå³ (LTR)",
+DlgGenLangDirRtl : "ç”±å³è€Œå·¦ (RTL)",
+DlgGenLangCode : "語言代碼",
+DlgGenAccessKey : "å­˜å–éµ",
+DlgGenName : "å稱",
+DlgGenTabIndex : "定ä½é †åº",
+DlgGenLongDescr : "詳細 URL",
+DlgGenClass : "樣å¼è¡¨é¡žåˆ¥",
+DlgGenTitle : "標題",
+DlgGenContType : "內容類型",
+DlgGenLinkCharset : "連çµè³‡æºä¹‹ç·¨ç¢¼",
+DlgGenStyle : "樣å¼",
+
+// Image Dialog
+DlgImgTitle : "å½±åƒå±¬æ€§",
+DlgImgInfoTab : "å½±åƒè³‡è¨Š",
+DlgImgBtnUpload : "上傳至伺æœå™¨",
+DlgImgURL : "URL",
+DlgImgUpload : "上傳",
+DlgImgAlt : "替代文字",
+DlgImgWidth : "寬度",
+DlgImgHeight : "高度",
+DlgImgLockRatio : "等比例",
+DlgBtnResetSize : "é‡è¨­ç‚ºåŽŸå¤§å°",
+DlgImgBorder : "邊框",
+DlgImgHSpace : "æ°´å¹³è·é›¢",
+DlgImgVSpace : "åž‚ç›´è·é›¢",
+DlgImgAlign : "å°é½Š",
+DlgImgAlignLeft : "é å·¦å°é½Š",
+DlgImgAlignAbsBottom: "絕å°ä¸‹æ–¹",
+DlgImgAlignAbsMiddle: "絕å°ä¸­é–“",
+DlgImgAlignBaseline : "基準線",
+DlgImgAlignBottom : "é ä¸‹å°é½Š",
+DlgImgAlignMiddle : "置中å°é½Š",
+DlgImgAlignRight : "é å³å°é½Š",
+DlgImgAlignTextTop : "文字上方",
+DlgImgAlignTop : "é ä¸Šå°é½Š",
+DlgImgPreview : "é è¦½",
+DlgImgAlertUrl : "è«‹è¼¸å…¥å½±åƒ URL",
+DlgImgLinkTab : "超連çµ",
+
+// Flash Dialog
+DlgFlashTitle : "Flash 屬性",
+DlgFlashChkPlay : "自動播放",
+DlgFlashChkLoop : "é‡è¤‡",
+DlgFlashChkMenu : "é–‹å•Ÿé¸å–®",
+DlgFlashScale : "縮放",
+DlgFlashScaleAll : "全部顯示",
+DlgFlashScaleNoBorder : "無邊框",
+DlgFlashScaleFit : "精確符åˆ",
+
+// Link Dialog
+DlgLnkWindowTitle : "超連çµ",
+DlgLnkInfoTab : "超連çµè³‡è¨Š",
+DlgLnkTargetTab : "目標",
+
+DlgLnkType : "超連接類型",
+DlgLnkTypeURL : "URL",
+DlgLnkTypeAnchor : "本é éŒ¨é»ž",
+DlgLnkTypeEMail : "é›»å­éƒµä»¶",
+DlgLnkProto : "通訊å”定",
+DlgLnkProtoOther : "<其他>",
+DlgLnkURL : "URL",
+DlgLnkAnchorSel : "è«‹é¸æ“‡éŒ¨é»ž",
+DlgLnkAnchorByName : "ä¾éŒ¨é»žå稱",
+DlgLnkAnchorById : "ä¾å…ƒä»¶ ID",
+DlgLnkNoAnchors : "(本文件尚無å¯ç”¨ä¹‹éŒ¨é»ž)",
+DlgLnkEMail : "é›»å­éƒµä»¶",
+DlgLnkEMailSubject : "郵件主旨",
+DlgLnkEMailBody : "郵件內容",
+DlgLnkUpload : "上傳",
+DlgLnkBtnUpload : "傳é€è‡³ä¼ºæœå™¨",
+
+DlgLnkTarget : "目標",
+DlgLnkTargetFrame : "<框架>",
+DlgLnkTargetPopup : "<快顯視窗>",
+DlgLnkTargetBlank : "新視窗 (_blank)",
+DlgLnkTargetParent : "父視窗 (_parent)",
+DlgLnkTargetSelf : "本視窗 (_self)",
+DlgLnkTargetTop : "最上層視窗 (_top)",
+DlgLnkTargetFrameName : "目標框架å稱",
+DlgLnkPopWinName : "快顯視窗å稱",
+DlgLnkPopWinFeat : "快顯視窗屬性",
+DlgLnkPopResize : "å¯èª¿æ•´å¤§å°",
+DlgLnkPopLocation : "網å€åˆ—",
+DlgLnkPopMenu : "é¸å–®åˆ—",
+DlgLnkPopScroll : "æ²è»¸",
+DlgLnkPopStatus : "狀態列",
+DlgLnkPopToolbar : "工具列",
+DlgLnkPopFullScrn : "全螢幕 (IE)",
+DlgLnkPopDependent : "從屬 (NS)",
+DlgLnkPopWidth : "寬",
+DlgLnkPopHeight : "高",
+DlgLnkPopLeft : "å·¦",
+DlgLnkPopTop : "å³",
+
+DlnLnkMsgNoUrl : "請輸入欲連çµçš„ URL",
+DlnLnkMsgNoEMail : "請輸入電å­éƒµä»¶ä½å€",
+DlnLnkMsgNoAnchor : "è«‹é¸æ“‡éŒ¨é»ž",
+DlnLnkMsgInvPopName : "快顯å稱必須以「英文字æ¯ã€ç‚ºé–‹é ­ï¼Œä¸”ä¸å¾—å«æœ‰ç©ºç™½",
+
+// Color Dialog
+DlgColorTitle : "è«‹é¸æ“‡é¡è‰²",
+DlgColorBtnClear : "清除",
+DlgColorHighlight : "é è¦½",
+DlgColorSelected : "é¸æ“‡",
+
+// Smiley Dialog
+DlgSmileyTitle : "æ’入表情符號",
+
+// Special Character Dialog
+DlgSpecialCharTitle : "è«‹é¸æ“‡ç‰¹æ®Šç¬¦è™Ÿ",
+
+// Table Dialog
+DlgTableTitle : "表格屬性",
+DlgTableRows : "列數",
+DlgTableColumns : "欄數",
+DlgTableBorder : "邊框",
+DlgTableAlign : "å°é½Š",
+DlgTableAlignNotSet : "<未設定>",
+DlgTableAlignLeft : "é å·¦å°é½Š",
+DlgTableAlignCenter : "置中",
+DlgTableAlignRight : "é å³å°é½Š",
+DlgTableWidth : "寬度",
+DlgTableWidthPx : "åƒç´ ",
+DlgTableWidthPc : "百分比",
+DlgTableHeight : "高度",
+DlgTableCellSpace : "é–“è·",
+DlgTableCellPad : "å…§è·",
+DlgTableCaption : "標題",
+DlgTableSummary : "摘è¦",
+DlgTableHeaders : "Headers", //MISSING
+DlgTableHeadersNone : "None", //MISSING
+DlgTableHeadersColumn : "First column", //MISSING
+DlgTableHeadersRow : "First Row", //MISSING
+DlgTableHeadersBoth : "Both", //MISSING
+
+// Table Cell Dialog
+DlgCellTitle : "儲存格屬性",
+DlgCellWidth : "寬度",
+DlgCellWidthPx : "åƒç´ ",
+DlgCellWidthPc : "百分比",
+DlgCellHeight : "高度",
+DlgCellWordWrap : "自動æ›è¡Œ",
+DlgCellWordWrapNotSet : "<尚未設定>",
+DlgCellWordWrapYes : "是",
+DlgCellWordWrapNo : "å¦",
+DlgCellHorAlign : "æ°´å¹³å°é½Š",
+DlgCellHorAlignNotSet : "<尚未設定>",
+DlgCellHorAlignLeft : "é å·¦å°é½Š",
+DlgCellHorAlignCenter : "置中",
+DlgCellHorAlignRight: "é å³å°é½Š",
+DlgCellVerAlign : "åž‚ç›´å°é½Š",
+DlgCellVerAlignNotSet : "<尚未設定>",
+DlgCellVerAlignTop : "é ä¸Šå°é½Š",
+DlgCellVerAlignMiddle : "置中",
+DlgCellVerAlignBottom : "é ä¸‹å°é½Š",
+DlgCellVerAlignBaseline : "基準線",
+DlgCellType : "儲存格類型",
+DlgCellTypeData : "資料",
+DlgCellTypeHeader : "標題",
+DlgCellRowSpan : "åˆä½µåˆ—數",
+DlgCellCollSpan : "åˆä½µæ¬„æ•°",
+DlgCellBackColor : "背景é¡è‰²",
+DlgCellBorderColor : "邊框é¡è‰²",
+DlgCellBtnSelect : "è«‹é¸æ“‡â€¦",
+
+// Find and Replace Dialog
+DlgFindAndReplaceTitle : "尋找與å–代",
+
+// Find Dialog
+DlgFindTitle : "尋找",
+DlgFindFindBtn : "尋找",
+DlgFindNotFoundMsg : "未找到指定的文字。",
+
+// Replace Dialog
+DlgReplaceTitle : "å–代",
+DlgReplaceFindLbl : "尋找:",
+DlgReplaceReplaceLbl : "å–代:",
+DlgReplaceCaseChk : "大å°å¯«é ˆç›¸ç¬¦",
+DlgReplaceReplaceBtn : "å–代",
+DlgReplaceReplAllBtn : "全部å–代",
+DlgReplaceWordChk : "全字相符",
+
+// Paste Operations / Dialog
+PasteErrorCut : "ç€è¦½å™¨çš„安全性設定ä¸å…許編輯器自動執行剪下動作。請使用快æ·éµ (Ctrl+X) 剪下。",
+PasteErrorCopy : "ç€è¦½å™¨çš„安全性設定ä¸å…許編輯器自動執行複製動作。請使用快æ·éµ (Ctrl+C) 複製。",
+
+PasteAsText : "貼為純文字格å¼",
+PasteFromWord : "自 Word 貼上",
+
+DlgPasteMsg2 : "請使用快æ·éµ (<strong>Ctrl+V</strong>) 貼到下方å€åŸŸä¸­ä¸¦æŒ‰ä¸‹ <strong>確定</strong>",
+DlgPasteSec : "因為ç€è¦½å™¨çš„安全性設定,本編輯器無法直接存å–您的剪貼簿資料,請您自行在本視窗進行貼上動作。",
+DlgPasteIgnoreFont : "移除字型設定",
+DlgPasteRemoveStyles : "移除樣å¼è¨­å®š",
+
+// Color Picker
+ColorAutomatic : "自動",
+ColorMoreColors : "更多é¡è‰²â€¦",
+
+// Document Properties
+DocProps : "文件屬性",
+
+// Anchor Dialog
+DlgAnchorTitle : "命å錨點",
+DlgAnchorName : "錨點å稱",
+DlgAnchorErrorName : "請輸入錨點å稱",
+
+// Speller Pages Dialog
+DlgSpellNotInDic : "ä¸åœ¨å­—典中",
+DlgSpellChangeTo : "更改為",
+DlgSpellBtnIgnore : "忽略",
+DlgSpellBtnIgnoreAll : "全部忽略",
+DlgSpellBtnReplace : "å–代",
+DlgSpellBtnReplaceAll : "全部å–代",
+DlgSpellBtnUndo : "復原",
+DlgSpellNoSuggestions : "- 無建議值 -",
+DlgSpellProgress : "進行拼字檢查中…",
+DlgSpellNoMispell : "拼字檢查完æˆï¼šæœªç™¼ç¾æ‹¼å­—錯誤",
+DlgSpellNoChanges : "拼字檢查完æˆï¼šæœªæ›´æ”¹ä»»ä½•å–®å­—",
+DlgSpellOneChange : "拼字檢查完æˆï¼šæ›´æ”¹äº† 1 個單字",
+DlgSpellManyChanges : "拼字檢查完æˆï¼šæ›´æ”¹äº† %1 個單字",
+
+IeSpellDownload : "尚未安è£æ‹¼å­—檢查元件。您是å¦æƒ³è¦ç¾åœ¨ä¸‹è¼‰ï¼Ÿ",
+
+// Button Dialog
+DlgButtonText : "顯示文字 (值)",
+DlgButtonType : "é¡žåž‹",
+DlgButtonTypeBtn : "按鈕 (Button)",
+DlgButtonTypeSbm : "é€å‡º (Submit)",
+DlgButtonTypeRst : "é‡è¨­ (Reset)",
+
+// Checkbox and Radio Button Dialogs
+DlgCheckboxName : "å稱",
+DlgCheckboxValue : "é¸å–值",
+DlgCheckboxSelected : "å·²é¸å–",
+
+// Form Dialog
+DlgFormName : "å稱",
+DlgFormAction : "動作",
+DlgFormMethod : "方法",
+
+// Select Field Dialog
+DlgSelectName : "å稱",
+DlgSelectValue : "é¸å–值",
+DlgSelectSize : "大å°",
+DlgSelectLines : "行",
+DlgSelectChkMulti : "å¯å¤šé¸",
+DlgSelectOpAvail : "å¯ç”¨é¸é …",
+DlgSelectOpText : "顯示文字",
+DlgSelectOpValue : "值",
+DlgSelectBtnAdd : "新增",
+DlgSelectBtnModify : "修改",
+DlgSelectBtnUp : "上移",
+DlgSelectBtnDown : "下移",
+DlgSelectBtnSetValue : "設為é è¨­å€¼",
+DlgSelectBtnDelete : "刪除",
+
+// Textarea Dialog
+DlgTextareaName : "å稱",
+DlgTextareaCols : "字元寬度",
+DlgTextareaRows : "列數",
+
+// Text Field Dialog
+DlgTextName : "å稱",
+DlgTextValue : "值",
+DlgTextCharWidth : "字元寬度",
+DlgTextMaxChars : "最多字元數",
+DlgTextType : "é¡žåž‹",
+DlgTextTypeText : "文字",
+DlgTextTypePass : "密碼",
+
+// Hidden Field Dialog
+DlgHiddenName : "å稱",
+DlgHiddenValue : "值",
+
+// Bulleted List Dialog
+BulletedListProp : "項目清單屬性",
+NumberedListProp : "編號清單屬性",
+DlgLstStart : "起始編號",
+DlgLstType : "清單類型",
+DlgLstTypeCircle : "圓圈",
+DlgLstTypeDisc : "圓點",
+DlgLstTypeSquare : "方塊",
+DlgLstTypeNumbers : "數字 (1, 2, 3)",
+DlgLstTypeLCase : "å°å¯«å­—æ¯ (a, b, c)",
+DlgLstTypeUCase : "å¤§å¯«å­—æ¯ (A, B, C)",
+DlgLstTypeSRoman : "å°å¯«ç¾…馬數字 (i, ii, iii)",
+DlgLstTypeLRoman : "大寫羅馬數字 (I, II, III)",
+
+// Document Properties Dialog
+DlgDocGeneralTab : "一般",
+DlgDocBackTab : "背景",
+DlgDocColorsTab : "顯色與邊界",
+DlgDocMetaTab : "Meta 資料",
+
+DlgDocPageTitle : "é é¢æ¨™é¡Œ",
+DlgDocLangDir : "語言方å‘",
+DlgDocLangDirLTR : "ç”±å·¦è€Œå³ (LTR)",
+DlgDocLangDirRTL : "ç”±å³è€Œå·¦ (RTL)",
+DlgDocLangCode : "語言代碼",
+DlgDocCharSet : "字元編碼",
+DlgDocCharSetCE : "中æ­èªžç³»",
+DlgDocCharSetCT : "正體中文 (Big5)",
+DlgDocCharSetCR : "斯拉夫文",
+DlgDocCharSetGR : "希臘文",
+DlgDocCharSetJP : "日文",
+DlgDocCharSetKR : "韓文",
+DlgDocCharSetTR : "土耳其文",
+DlgDocCharSetUN : "Unicode (UTF-8)",
+DlgDocCharSetWE : "西æ­èªžç³»",
+DlgDocCharSetOther : "其他字元編碼",
+
+DlgDocDocType : "文件類型",
+DlgDocDocTypeOther : "其他文件類型",
+DlgDocIncXHTML : "åŒ…å« XHTML 定義",
+DlgDocBgColor : "背景é¡è‰²",
+DlgDocBgImage : "背景影åƒ",
+DlgDocBgNoScroll : "浮水å°",
+DlgDocCText : "文字",
+DlgDocCLink : "超連çµ",
+DlgDocCVisited : "å·²ç€è¦½éŽçš„超連çµ",
+DlgDocCActive : "作用中的超連çµ",
+DlgDocMargins : "é é¢é‚Šç•Œ",
+DlgDocMaTop : "上",
+DlgDocMaLeft : "å·¦",
+DlgDocMaRight : "å³",
+DlgDocMaBottom : "下",
+DlgDocMeIndex : "文件索引關éµå­— (用åŠå½¢é€—號[,]分隔)",
+DlgDocMeDescr : "文件說明",
+DlgDocMeAuthor : "作者",
+DlgDocMeCopy : "版權所有",
+DlgDocPreview : "é è¦½",
+
+// Templates Dialog
+Templates : "樣版",
+DlgTemplatesTitle : "內容樣版",
+DlgTemplatesSelMsg : "è«‹é¸æ“‡æ¬²é–‹å•Ÿçš„樣版<br> (原有的內容將會被清除):",
+DlgTemplatesLoading : "讀å–樣版清單中,請ç¨å€™â€¦",
+DlgTemplatesNoTpl : "(無樣版)",
+DlgTemplatesReplace : "å–代原有內容",
+
+// About Dialog
+DlgAboutAboutTab : "關於",
+DlgAboutBrowserInfoTab : "ç€è¦½å™¨è³‡è¨Š",
+DlgAboutLicenseTab : "許å¯è­‰",
+DlgAboutVersion : "版本",
+DlgAboutInfo : "想ç²å¾—更多資訊請至 ",
+
+// Div Dialog
+DlgDivGeneralTab : "一般",
+DlgDivAdvancedTab : "進階",
+DlgDivStyle : "樣å¼",
+DlgDivInlineStyle : "CSS 樣å¼",
+
+ScaytTitle : "SCAYT", //MISSING
+ScaytTitleOptions : "Options", //MISSING
+ScaytTitleLangs : "Languages", //MISSING
+ScaytTitleAbout : "About" //MISSING
+};
diff --git a/httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js
new file mode 100644
index 0000000..1df2d0f
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js
@@ -0,0 +1,111 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Plugin: automatically resizes the editor until a configurable maximun
+ * height (FCKConfig.AutoGrowMax), based on its contents.
+ */
+
+var FCKAutoGrow = {
+ MIN_HEIGHT : window.frameElement.offsetHeight,
+
+ Check : function()
+ {
+ var delta = FCKAutoGrow.GetHeightDelta() ;
+ if ( delta != 0 )
+ {
+ var newHeight = window.frameElement.offsetHeight + delta ;
+
+ newHeight = FCKAutoGrow.GetEffectiveHeight( newHeight ) ;
+
+ if ( newHeight != window.frameElement.height )
+ {
+ window.frameElement.style.height = newHeight + "px" ;
+
+ // Gecko browsers use an onresize handler to update the innermost
+ // IFRAME's height. If the document is modified before the onresize
+ // is triggered, the plugin will miscalculate the new height. Thus,
+ // forcibly trigger onresize. #1336
+ if ( typeof window.onresize == 'function' )
+ {
+ window.onresize() ;
+ }
+ }
+ }
+ },
+
+ CheckEditorStatus : function( sender, status )
+ {
+ if ( status == FCK_STATUS_COMPLETE )
+ FCKAutoGrow.Check() ;
+ },
+
+ GetEffectiveHeight : function( height )
+ {
+ if ( height < FCKAutoGrow.MIN_HEIGHT )
+ height = FCKAutoGrow.MIN_HEIGHT;
+ else
+ {
+ var max = FCKConfig.AutoGrowMax;
+ if ( max && max > 0 && height > max )
+ height = max;
+ }
+
+ return height;
+ },
+
+ GetHeightDelta : function()
+ {
+ var oInnerDoc = FCK.EditorDocument ;
+
+ var iFrameHeight ;
+ var iInnerHeight ;
+
+ if ( FCKBrowserInfo.IsIE )
+ {
+ iFrameHeight = FCK.EditorWindow.frameElement.offsetHeight ;
+ iInnerHeight = oInnerDoc.body.scrollHeight ;
+ }
+ else
+ {
+ iFrameHeight = FCK.EditorWindow.innerHeight ;
+ iInnerHeight = oInnerDoc.body.offsetHeight +
+ ( parseInt( FCKDomTools.GetCurrentElementStyle( oInnerDoc.body, 'margin-top' ), 10 ) || 0 ) +
+ ( parseInt( FCKDomTools.GetCurrentElementStyle( oInnerDoc.body, 'margin-bottom' ), 10 ) || 0 ) ;
+ }
+
+ return iInnerHeight - iFrameHeight ;
+ },
+
+ SetListeners : function()
+ {
+ if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )
+ return ;
+
+ FCK.EditorWindow.attachEvent( 'onscroll', FCKAutoGrow.Check ) ;
+ FCK.EditorDocument.attachEvent( 'onkeyup', FCKAutoGrow.Check ) ;
+ }
+};
+
+FCK.AttachToOnSelectionChange( FCKAutoGrow.Check ) ;
+
+if ( FCKBrowserInfo.IsIE )
+ FCK.Events.AttachEvent( 'OnAfterSetHTML', FCKAutoGrow.SetListeners ) ;
+
+FCK.Events.AttachEvent( 'OnStatusChange', FCKAutoGrow.CheckEditorStatus ) ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/bbcode/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/bbcode/fckplugin.js
new file mode 100644
index 0000000..f9521dd
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/bbcode/fckplugin.js
@@ -0,0 +1,123 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is a sample implementation for a custom Data Processor for basic BBCode.
+ */
+
+FCK.DataProcessor =
+{
+ /*
+ * Returns a string representing the HTML format of "data". The returned
+ * value will be loaded in the editor.
+ * The HTML must be from <html> to </html>, eventually including
+ * the DOCTYPE.
+ * @param {String} data The data to be converted in the
+ * DataProcessor specific format.
+ */
+ ConvertToHtml : function( data )
+ {
+ // Convert < and > to their HTML entities.
+ data = data.replace( /</g, '&lt;' ) ;
+ data = data.replace( />/g, '&gt;' ) ;
+
+ // Convert line breaks to <br>.
+ data = data.replace( /(?:\r\n|\n|\r)/g, '<br>' ) ;
+
+ // [url]
+ data = data.replace( /\[url\](.+?)\[\/url]/gi, '<a href="$1">$1</a>' ) ;
+ data = data.replace( /\[url\=([^\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>' ) ;
+
+ // [b]
+ data = data.replace( /\[b\](.+?)\[\/b]/gi, '<b>$1</b>' ) ;
+
+ // [i]
+ data = data.replace( /\[i\](.+?)\[\/i]/gi, '<i>$1</i>' ) ;
+
+ // [u]
+ data = data.replace( /\[u\](.+?)\[\/u]/gi, '<u>$1</u>' ) ;
+
+ return '<html><head><title></title></head><body>' + data + '</body></html>' ;
+ },
+
+ /*
+ * Converts a DOM (sub-)tree to a string in the data format.
+ * @param {Object} rootNode The node that contains the DOM tree to be
+ * converted to the data format.
+ * @param {Boolean} excludeRoot Indicates that the root node must not
+ * be included in the conversion, only its children.
+ * @param {Boolean} format Indicates that the data must be formatted
+ * for human reading. Not all Data Processors may provide it.
+ */
+ ConvertToDataFormat : function( rootNode, excludeRoot, ignoreIfEmptyParagraph, format )
+ {
+ var data = rootNode.innerHTML ;
+
+ // Convert <br> to line breaks.
+ data = data.replace( /<br(?=[ \/>]).*?>/gi, '\r\n') ;
+
+ // [url]
+ data = data.replace( /<a .*?href=(["'])(.+?)\1.*?>(.+?)<\/a>/gi, '[url=$2]$3[/url]') ;
+
+ // [b]
+ data = data.replace( /<(?:b|strong)>/gi, '[b]') ;
+ data = data.replace( /<\/(?:b|strong)>/gi, '[/b]') ;
+
+ // [i]
+ data = data.replace( /<(?:i|em)>/gi, '[i]') ;
+ data = data.replace( /<\/(?:i|em)>/gi, '[/i]') ;
+
+ // [u]
+ data = data.replace( /<u>/gi, '[u]') ;
+ data = data.replace( /<\/u>/gi, '[/u]') ;
+
+ // Remove remaining tags.
+ data = data.replace( /<[^>]+>/g, '') ;
+
+ return data ;
+ },
+
+ /*
+ * Makes any necessary changes to a piece of HTML for insertion in the
+ * editor selection position.
+ * @param {String} html The HTML to be fixed.
+ */
+ FixHtml : function( html )
+ {
+ return html ;
+ }
+} ;
+
+// This Data Processor doesn't support <p>, so let's use <br>.
+FCKConfig.EnterMode = 'br' ;
+
+// To avoid pasting invalid markup (which is discarded in any case), let's
+// force pasting to plain text.
+FCKConfig.ForcePasteAsPlainText = true ;
+
+// Rename the "Source" buttom to "BBCode".
+FCKToolbarItems.RegisterItem( 'Source', new FCKToolbarButton( 'Source', 'BBCode', null, FCK_TOOLBARITEM_ICONTEXT, true, true, 1 ) ) ;
+
+// Let's enforce the toolbar to the limits of this Data Processor. A custom
+// toolbar set may be defined in the configuration file with more or less entries.
+FCKConfig.ToolbarSets["Default"] = [
+ ['Source'],
+ ['Bold','Italic','Underline','-','Link'],
+ ['About']
+] ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/dragresizetable/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/dragresizetable/fckplugin.js
new file mode 100644
index 0000000..87061ff
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/dragresizetable/fckplugin.js
@@ -0,0 +1,529 @@
+var FCKDragTableHandler =
+{
+ "_DragState" : 0,
+ "_LeftCell" : null,
+ "_RightCell" : null,
+ "_MouseMoveMode" : 0, // 0 - find candidate cells for resizing, 1 - drag to resize
+ "_ResizeBar" : null,
+ "_OriginalX" : null,
+ "_MinimumX" : null,
+ "_MaximumX" : null,
+ "_LastX" : null,
+ "_TableMap" : null,
+ "_doc" : document,
+ "_IsInsideNode" : function( w, domNode, pos )
+ {
+ var myCoords = FCKTools.GetWindowPosition( w, domNode ) ;
+ var xMin = myCoords.x ;
+ var yMin = myCoords.y ;
+ var xMax = parseInt( xMin, 10 ) + parseInt( domNode.offsetWidth, 10 ) ;
+ var yMax = parseInt( yMin, 10 ) + parseInt( domNode.offsetHeight, 10 ) ;
+ if ( pos.x >= xMin && pos.x <= xMax && pos.y >= yMin && pos.y <= yMax )
+ return true;
+ return false;
+ },
+ "_GetBorderCells" : function( w, tableNode, tableMap, mouse )
+ {
+ // Enumerate all the cells in the table.
+ var cells = [] ;
+ for ( var i = 0 ; i < tableNode.rows.length ; i++ )
+ {
+ var r = tableNode.rows[i] ;
+ for ( var j = 0 ; j < r.cells.length ; j++ )
+ cells.push( r.cells[j] ) ;
+ }
+
+ if ( cells.length < 1 )
+ return null ;
+
+ // Get the cells whose right or left border is nearest to the mouse cursor's x coordinate.
+ var minRxDist = null ;
+ var lxDist = null ;
+ var minYDist = null ;
+ var rbCell = null ;
+ var lbCell = null ;
+ for ( var i = 0 ; i < cells.length ; i++ )
+ {
+ var pos = FCKTools.GetWindowPosition( w, cells[i] ) ;
+ var rightX = pos.x + parseInt( cells[i].clientWidth, 10 ) ;
+ var rxDist = mouse.x - rightX ;
+ var yDist = mouse.y - ( pos.y + ( cells[i].clientHeight / 2 ) ) ;
+ if ( minRxDist == null ||
+ ( Math.abs( rxDist ) <= Math.abs( minRxDist ) &&
+ ( minYDist == null || Math.abs( yDist ) <= Math.abs( minYDist ) ) ) )
+ {
+ minRxDist = rxDist ;
+ minYDist = yDist ;
+ rbCell = cells[i] ;
+ }
+ }
+ /*
+ var rowNode = FCKTools.GetElementAscensor( rbCell, "tr" ) ;
+ var cellIndex = rbCell.cellIndex + 1 ;
+ if ( cellIndex >= rowNode.cells.length )
+ return null ;
+ lbCell = rowNode.cells.item( cellIndex ) ;
+ */
+ var rowIdx = rbCell.parentNode.rowIndex ;
+ var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, rbCell ) ;
+ var colSpan = isNaN( rbCell.colSpan ) ? 1 : rbCell.colSpan ;
+ lbCell = tableMap[rowIdx][colIdx + colSpan] ;
+
+ if ( ! lbCell )
+ return null ;
+
+ // Abort if too far from the border.
+ lxDist = mouse.x - FCKTools.GetWindowPosition( w, lbCell ).x ;
+ if ( lxDist < 0 && minRxDist < 0 && minRxDist < -2 )
+ return null ;
+ if ( lxDist > 0 && minRxDist > 0 && lxDist > 3 )
+ return null ;
+
+ return { "leftCell" : rbCell, "rightCell" : lbCell } ;
+ },
+ "_GetResizeBarPosition" : function()
+ {
+ var row = FCKTools.GetElementAscensor( this._RightCell, "tr" ) ;
+ return FCKTableHandler._GetCellIndexSpan( this._TableMap, row.rowIndex, this._RightCell ) ;
+ },
+ "_ResizeBarMouseDownListener" : function( evt )
+ {
+ if ( FCKDragTableHandler._LeftCell )
+ FCKDragTableHandler._MouseMoveMode = 1 ;
+ if ( FCKBrowserInfo.IsIE )
+ FCKDragTableHandler._ResizeBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 50 ;
+ else
+ FCKDragTableHandler._ResizeBar.style.opacity = 0.5 ;
+ FCKDragTableHandler._OriginalX = evt.clientX ;
+
+ // Calculate maximum and minimum x-coordinate delta.
+ var borderIndex = FCKDragTableHandler._GetResizeBarPosition() ;
+ var offset = FCKDragTableHandler._GetIframeOffset();
+ var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" );
+ var minX = null ;
+ var maxX = null ;
+ for ( var r = 0 ; r < FCKDragTableHandler._TableMap.length ; r++ )
+ {
+ var leftCell = FCKDragTableHandler._TableMap[r][borderIndex - 1] ;
+ var rightCell = FCKDragTableHandler._TableMap[r][borderIndex] ;
+ var leftPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, leftCell ) ;
+ var rightPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, rightCell ) ;
+ var leftPadding = FCKDragTableHandler._GetCellPadding( table, leftCell ) ;
+ var rightPadding = FCKDragTableHandler._GetCellPadding( table, rightCell ) ;
+ if ( minX == null || leftPosition.x + leftPadding > minX )
+ minX = leftPosition.x + leftPadding ;
+ if ( maxX == null || rightPosition.x + rightCell.clientWidth - rightPadding < maxX )
+ maxX = rightPosition.x + rightCell.clientWidth - rightPadding ;
+ }
+
+ FCKDragTableHandler._MinimumX = minX + offset.x ;
+ FCKDragTableHandler._MaximumX = maxX + offset.x ;
+ FCKDragTableHandler._LastX = null ;
+
+ if (evt.preventDefault)
+ evt.preventDefault();
+ else
+ evt.returnValue = false;
+ },
+ "_ResizeBarMouseUpListener" : function( evt )
+ {
+ FCKDragTableHandler._MouseMoveMode = 0 ;
+ FCKDragTableHandler._HideResizeBar() ;
+
+ if ( FCKDragTableHandler._LastX == null )
+ return ;
+
+ // Calculate the delta value.
+ var deltaX = FCKDragTableHandler._LastX - FCKDragTableHandler._OriginalX ;
+
+ // Then, build an array of current column width values.
+ // This algorithm can be very slow if the cells have insane colSpan values. (e.g. colSpan=1000).
+ var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" ) ;
+ var colArray = [] ;
+ var tableMap = FCKDragTableHandler._TableMap ;
+ for ( var i = 0 ; i < tableMap.length ; i++ )
+ {
+ for ( var j = 0 ; j < tableMap[i].length ; j++ )
+ {
+ var cell = tableMap[i][j] ;
+ var width = FCKDragTableHandler._GetCellWidth( table, cell ) ;
+ var colSpan = isNaN( cell.colSpan) ? 1 : cell.colSpan ;
+ if ( colArray.length <= j )
+ colArray.push( { width : width / colSpan, colSpan : colSpan } ) ;
+ else
+ {
+ var guessItem = colArray[j] ;
+ if ( guessItem.colSpan > colSpan )
+ {
+ guessItem.width = width / colSpan ;
+ guessItem.colSpan = colSpan ;
+ }
+ }
+ }
+ }
+
+ // Find out the equivalent column index of the two cells selected for resizing.
+ colIndex = FCKDragTableHandler._GetResizeBarPosition() ;
+
+ // Note that colIndex must be at least 1 here, so it's safe to subtract 1 from it.
+ colIndex-- ;
+
+ // Modify the widths in the colArray according to the mouse coordinate delta value.
+ colArray[colIndex].width += deltaX ;
+ colArray[colIndex + 1].width -= deltaX ;
+
+ // Clear all cell widths, delete all <col> elements from the table.
+ for ( var r = 0 ; r < table.rows.length ; r++ )
+ {
+ var row = table.rows.item( r ) ;
+ for ( var c = 0 ; c < row.cells.length ; c++ )
+ {
+ var cell = row.cells.item( c ) ;
+ cell.width = "" ;
+ cell.style.width = "" ;
+ }
+ }
+ var colElements = table.getElementsByTagName( "col" ) ;
+ for ( var i = colElements.length - 1 ; i >= 0 ; i-- )
+ colElements[i].parentNode.removeChild( colElements[i] ) ;
+
+ // Set new cell widths.
+ var processedCells = [] ;
+ for ( var i = 0 ; i < tableMap.length ; i++ )
+ {
+ for ( var j = 0 ; j < tableMap[i].length ; j++ )
+ {
+ var cell = tableMap[i][j] ;
+ if ( cell._Processed )
+ continue ;
+ if ( tableMap[i][j-1] != cell )
+ cell.width = colArray[j].width ;
+ else
+ cell.width = parseInt( cell.width, 10 ) + parseInt( colArray[j].width, 10 ) ;
+ if ( tableMap[i][j+1] != cell )
+ {
+ processedCells.push( cell ) ;
+ cell._Processed = true ;
+ }
+ }
+ }
+ for ( var i = 0 ; i < processedCells.length ; i++ )
+ {
+ if ( FCKBrowserInfo.IsIE )
+ processedCells[i].removeAttribute( '_Processed' ) ;
+ else
+ delete processedCells[i]._Processed ;
+ }
+
+ FCKDragTableHandler._LastX = null ;
+ },
+ "_ResizeBarMouseMoveListener" : function( evt )
+ {
+ if ( FCKDragTableHandler._MouseMoveMode == 0 )
+ return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
+ else
+ return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
+ },
+ // Calculate the padding of a table cell.
+ // It returns the value of paddingLeft + paddingRight of a table cell.
+ // This function is used, in part, to calculate the width parameter that should be used for setting cell widths.
+ // The equation in question is clientWidth = paddingLeft + paddingRight + width.
+ // So that width = clientWidth - paddingLeft - paddingRight.
+ // The return value of this function must be pixel accurate acorss all supported browsers, so be careful if you need to modify it.
+ "_GetCellPadding" : function( table, cell )
+ {
+ var attrGuess = parseInt( table.cellPadding, 10 ) * 2 ;
+ var cssGuess = null ;
+ if ( typeof( window.getComputedStyle ) == "function" )
+ {
+ var styleObj = window.getComputedStyle( cell, null ) ;
+ cssGuess = parseInt( styleObj.getPropertyValue( "padding-left" ), 10 ) +
+ parseInt( styleObj.getPropertyValue( "padding-right" ), 10 ) ;
+ }
+ else
+ cssGuess = parseInt( cell.currentStyle.paddingLeft, 10 ) + parseInt (cell.currentStyle.paddingRight, 10 ) ;
+
+ var cssRuntime = cell.style.padding ;
+ if ( isFinite( cssRuntime ) )
+ cssGuess = parseInt( cssRuntime, 10 ) * 2 ;
+ else
+ {
+ cssRuntime = cell.style.paddingLeft ;
+ if ( isFinite( cssRuntime ) )
+ cssGuess = parseInt( cssRuntime, 10 ) ;
+ cssRuntime = cell.style.paddingRight ;
+ if ( isFinite( cssRuntime ) )
+ cssGuess += parseInt( cssRuntime, 10 ) ;
+ }
+
+ attrGuess = parseInt( attrGuess, 10 ) ;
+ cssGuess = parseInt( cssGuess, 10 ) ;
+ if ( isNaN( attrGuess ) )
+ attrGuess = 0 ;
+ if ( isNaN( cssGuess ) )
+ cssGuess = 0 ;
+ return Math.max( attrGuess, cssGuess ) ;
+ },
+ // Calculate the real width of the table cell.
+ // The real width of the table cell is the pixel width that you can set to the width attribute of the table cell and after
+ // that, the table cell should be of exactly the same width as before.
+ // The real width of a table cell can be calculated as:
+ // width = clientWidth - paddingLeft - paddingRight.
+ "_GetCellWidth" : function( table, cell )
+ {
+ var clientWidth = cell.clientWidth ;
+ if ( isNaN( clientWidth ) )
+ clientWidth = 0 ;
+ return clientWidth - this._GetCellPadding( table, cell ) ;
+ },
+ "MouseMoveListener" : function( FCK, evt )
+ {
+ if ( FCKDragTableHandler._MouseMoveMode == 0 )
+ return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
+ else
+ return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
+ },
+ "_MouseFindHandler" : function( FCK, evt )
+ {
+ if ( FCK.MouseDownFlag )
+ return ;
+ var node = evt.srcElement || evt.target ;
+ try
+ {
+ if ( ! node || node.nodeType != 1 )
+ {
+ this._HideResizeBar() ;
+ return ;
+ }
+ }
+ catch ( e )
+ {
+ this._HideResizeBar() ;
+ return ;
+ }
+
+ // Since this function might be called from the editing area iframe or the outer fckeditor iframe,
+ // the mouse point coordinates from evt.clientX/Y can have different reference points.
+ // We need to resolve the mouse pointer position relative to the editing area iframe.
+ var mouseX = evt.clientX ;
+ var mouseY = evt.clientY ;
+ if ( FCKTools.GetElementDocument( node ) == document )
+ {
+ var offset = this._GetIframeOffset() ;
+ mouseX -= offset.x ;
+ mouseY -= offset.y ;
+ }
+
+
+ if ( this._ResizeBar && this._LeftCell )
+ {
+ var leftPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._LeftCell ) ;
+ var rightPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._RightCell ) ;
+ var rxDist = mouseX - ( leftPos.x + this._LeftCell.clientWidth ) ;
+ var lxDist = mouseX - rightPos.x ;
+ var inRangeFlag = false ;
+ if ( lxDist >= 0 && rxDist <= 0 )
+ inRangeFlag = true ;
+ else if ( rxDist > 0 && lxDist <= 3 )
+ inRangeFlag = true ;
+ else if ( lxDist < 0 && rxDist >= -2 )
+ inRangeFlag = true ;
+ if ( inRangeFlag )
+ {
+ this._ShowResizeBar( FCK.EditorWindow,
+ FCKTools.GetElementAscensor( this._LeftCell, "table" ),
+ { "x" : mouseX, "y" : mouseY } ) ;
+ return ;
+ }
+ }
+
+ var tagName = node.tagName.toLowerCase() ;
+ if ( tagName != "table" && tagName != "td" && tagName != "th" )
+ {
+ if ( this._LeftCell )
+ this._LeftCell = this._RightCell = this._TableMap = null ;
+ this._HideResizeBar() ;
+ return ;
+ }
+ node = FCKTools.GetElementAscensor( node, "table" ) ;
+ var tableMap = FCKTableHandler._CreateTableMap( node ) ;
+ var cellTuple = this._GetBorderCells( FCK.EditorWindow, node, tableMap, { "x" : mouseX, "y" : mouseY } ) ;
+
+ if ( cellTuple == null )
+ {
+ if ( this._LeftCell )
+ this._LeftCell = this._RightCell = this._TableMap = null ;
+ this._HideResizeBar() ;
+ }
+ else
+ {
+ this._LeftCell = cellTuple["leftCell"] ;
+ this._RightCell = cellTuple["rightCell"] ;
+ this._TableMap = tableMap ;
+ this._ShowResizeBar( FCK.EditorWindow,
+ FCKTools.GetElementAscensor( this._LeftCell, "table" ),
+ { "x" : mouseX, "y" : mouseY } ) ;
+ }
+ },
+ "_MouseDragHandler" : function( FCK, evt )
+ {
+ var mouse = { "x" : evt.clientX, "y" : evt.clientY } ;
+
+ // Convert mouse coordinates in reference to the outer iframe.
+ var node = evt.srcElement || evt.target ;
+ if ( FCKTools.GetElementDocument( node ) == FCK.EditorDocument )
+ {
+ var offset = this._GetIframeOffset() ;
+ mouse.x += offset.x ;
+ mouse.y += offset.y ;
+ }
+
+ // Calculate the mouse position delta and see if we've gone out of range.
+ if ( mouse.x >= this._MaximumX - 5 )
+ mouse.x = this._MaximumX - 5 ;
+ if ( mouse.x <= this._MinimumX + 5 )
+ mouse.x = this._MinimumX + 5 ;
+
+ var docX = mouse.x + FCKTools.GetScrollPosition( window ).X ;
+ this._ResizeBar.style.left = ( docX - this._ResizeBar.offsetWidth / 2 ) + "px" ;
+ this._LastX = mouse.x ;
+ },
+ "_ShowResizeBar" : function( w, table, mouse )
+ {
+ if ( this._ResizeBar == null )
+ {
+ this._ResizeBar = this._doc.createElement( "div" ) ;
+ var paddingBar = this._ResizeBar ;
+ var paddingStyles = { 'position' : 'absolute', 'cursor' : 'e-resize' } ;
+ if ( FCKBrowserInfo.IsIE )
+ paddingStyles.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=10,enabled=true)" ;
+ else
+ paddingStyles.opacity = 0.10 ;
+ FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
+ this._avoidStyles( paddingBar );
+ paddingBar.setAttribute('_fcktemp', true);
+ this._doc.body.appendChild( paddingBar ) ;
+ FCKTools.AddEventListener( paddingBar, "mousemove", this._ResizeBarMouseMoveListener ) ;
+ FCKTools.AddEventListener( paddingBar, "mousedown", this._ResizeBarMouseDownListener ) ;
+ FCKTools.AddEventListener( document, "mouseup", this._ResizeBarMouseUpListener ) ;
+ FCKTools.AddEventListener( FCK.EditorDocument, "mouseup", this._ResizeBarMouseUpListener ) ;
+
+ // IE doesn't let the tranparent part of the padding block to receive mouse events unless there's something inside.
+ // So we need to create a spacer image to fill the block up.
+ var filler = this._doc.createElement( "img" ) ;
+ filler.setAttribute('_fcktemp', true);
+ filler.border = 0 ;
+ filler.src = FCKConfig.BasePath + "images/spacer.gif" ;
+ filler.style.position = "absolute" ;
+ paddingBar.appendChild( filler ) ;
+
+ // Disable drag and drop, and selection for the filler image.
+ var disabledListener = function( evt )
+ {
+ if ( evt.preventDefault )
+ evt.preventDefault() ;
+ else
+ evt.returnValue = false ;
+ }
+ FCKTools.AddEventListener( filler, "dragstart", disabledListener ) ;
+ FCKTools.AddEventListener( filler, "selectstart", disabledListener ) ;
+ }
+
+ var paddingBar = this._ResizeBar ;
+ var offset = this._GetIframeOffset() ;
+ var tablePos = this._GetTablePosition( w, table ) ;
+ var barHeight = table.offsetHeight ;
+ var barTop = offset.y + tablePos.y ;
+ // Do not let the resize bar intrude into the toolbar area.
+ if ( tablePos.y < 0 )
+ {
+ barHeight += tablePos.y ;
+ barTop -= tablePos.y ;
+ }
+ var bw = parseInt( table.border, 10 ) ;
+ if ( isNaN( bw ) )
+ bw = 0 ;
+ var cs = parseInt( table.cellSpacing, 10 ) ;
+ if ( isNaN( cs ) )
+ cs = 0 ;
+ var barWidth = Math.max( bw+100, cs+100 ) ;
+ var paddingStyles =
+ {
+ 'top' : barTop + 'px',
+ 'height' : barHeight + 'px',
+ 'width' : barWidth + 'px',
+ 'left' : ( offset.x + mouse.x + FCKTools.GetScrollPosition( w ).X - barWidth / 2 ) + 'px'
+ } ;
+ if ( FCKBrowserInfo.IsIE )
+ paddingBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 10 ;
+ else
+ paddingStyles.opacity = 0.1 ;
+
+ FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
+ var filler = paddingBar.getElementsByTagName( "img" )[0] ;
+
+ FCKDomTools.SetElementStyles( filler,
+ {
+ width : paddingBar.offsetWidth + 'px',
+ height : barHeight + 'px'
+ } ) ;
+
+ barWidth = Math.max( bw, cs, 3 ) ;
+ var visibleBar = null ;
+ if ( paddingBar.getElementsByTagName( "div" ).length < 1 )
+ {
+ visibleBar = this._doc.createElement( "div" ) ;
+ this._avoidStyles( visibleBar );
+ visibleBar.setAttribute('_fcktemp', true);
+ paddingBar.appendChild( visibleBar ) ;
+ }
+ else
+ visibleBar = paddingBar.getElementsByTagName( "div" )[0] ;
+
+ FCKDomTools.SetElementStyles( visibleBar,
+ {
+ position : 'absolute',
+ backgroundColor : 'blue',
+ width : barWidth + 'px',
+ height : barHeight + 'px',
+ left : '50px',
+ top : '0px'
+ } ) ;
+ },
+ "_HideResizeBar" : function()
+ {
+ if ( this._ResizeBar )
+ // IE bug: display : none does not hide the resize bar for some reason.
+ // so set the position to somewhere invisible.
+ FCKDomTools.SetElementStyles( this._ResizeBar,
+ {
+ top : '-100000px',
+ left : '-100000px'
+ } ) ;
+ },
+ "_GetIframeOffset" : function ()
+ {
+ return FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
+ },
+ "_GetTablePosition" : function ( w, table )
+ {
+ return FCKTools.GetWindowPosition( w, table ) ;
+ },
+ "_avoidStyles" : function( element )
+ {
+ FCKDomTools.SetElementStyles( element,
+ {
+ padding : '0',
+ backgroundImage : 'none',
+ border : '0'
+ } ) ;
+ },
+ "Reset" : function()
+ {
+ FCKDragTableHandler._LeftCell = FCKDragTableHandler._RightCell = FCKDragTableHandler._TableMap = null ;
+ }
+
+};
+
+FCK.Events.AttachEvent( "OnMouseMove", FCKDragTableHandler.MouseMoveListener ) ;
+FCK.Events.AttachEvent( "OnAfterSetHTML", FCKDragTableHandler.Reset ) ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html b/httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html
new file mode 100644
index 0000000..df8c563
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placeholder Plugin.
+-->
+<html>
+ <head>
+ <title>Placeholder Properties</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta content="noindex, nofollow" name="robots">
+ <script src="../../dialog/common/fck_dialog_common.js" type="text/javascript"></script>
+ <script language="javascript">
+
+var dialog = window.parent ;
+var oEditor = dialog.InnerDialogLoaded() ;
+var FCKLang = oEditor.FCKLang ;
+var FCKPlaceholders = oEditor.FCKPlaceholders ;
+
+window.onload = function ()
+{
+ // First of all, translate the dialog box texts
+ oEditor.FCKLanguageManager.TranslatePage( document ) ;
+
+ LoadSelected() ;
+
+ // Show the "Ok" button.
+ dialog.SetOkButton( true ) ;
+
+ // Select text field on load.
+ SelectField( 'txtName' ) ;
+}
+
+var eSelected = dialog.Selection.GetSelectedElement() ;
+
+function LoadSelected()
+{
+ if ( !eSelected )
+ return ;
+
+ if ( eSelected.tagName == 'SPAN' && eSelected._fckplaceholder )
+ document.getElementById('txtName').value = eSelected._fckplaceholder ;
+ else
+ eSelected == null ;
+}
+
+function Ok()
+{
+ var sValue = document.getElementById('txtName').value ;
+
+ if ( eSelected && eSelected._fckplaceholder == sValue )
+ return true ;
+
+ if ( sValue.length == 0 )
+ {
+ alert( FCKLang.PlaceholderErrNoName ) ;
+ return false ;
+ }
+
+ if ( FCKPlaceholders.Exist( sValue ) )
+ {
+ alert( FCKLang.PlaceholderErrNameInUse ) ;
+ return false ;
+ }
+
+ FCKPlaceholders.Add( sValue ) ;
+ return true ;
+}
+
+ </script>
+ </head>
+ <body scroll="no" style="OVERFLOW: hidden">
+ <table height="100%" cellSpacing="0" cellPadding="0" width="100%" border="0">
+ <tr>
+ <td>
+ <table cellSpacing="0" cellPadding="0" align="center" border="0">
+ <tr>
+ <td>
+ <span fckLang="PlaceholderDlgName">Placeholder Name</span><br>
+ <input id="txtName" type="text">
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js
new file mode 100644
index 0000000..ec4e1a5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js
@@ -0,0 +1,187 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Plugin to insert "Placeholders" in the editor.
+ */
+
+// Register the related command.
+FCKCommands.RegisterCommand( 'Placeholder', new FCKDialogCommand( 'Placeholder', FCKLang.PlaceholderDlgTitle, FCKPlugins.Items['placeholder'].Path + 'fck_placeholder.html', 340, 160 ) ) ;
+
+// Create the "Plaholder" toolbar button.
+var oPlaceholderItem = new FCKToolbarButton( 'Placeholder', FCKLang.PlaceholderBtn ) ;
+oPlaceholderItem.IconPath = FCKPlugins.Items['placeholder'].Path + 'placeholder.gif' ;
+
+FCKToolbarItems.RegisterItem( 'Placeholder', oPlaceholderItem ) ;
+
+
+// The object used for all Placeholder operations.
+var FCKPlaceholders = new Object() ;
+
+// Add a new placeholder at the actual selection.
+FCKPlaceholders.Add = function( name )
+{
+ var oSpan = FCK.InsertElement( 'span' ) ;
+ this.SetupSpan( oSpan, name ) ;
+}
+
+FCKPlaceholders.SetupSpan = function( span, name )
+{
+ span.innerHTML = '[[ ' + name + ' ]]' ;
+
+ span.style.backgroundColor = '#ffff00' ;
+ span.style.color = '#000000' ;
+
+ if ( FCKBrowserInfo.IsGecko )
+ span.style.cursor = 'default' ;
+
+ span._fckplaceholder = name ;
+ span.contentEditable = false ;
+
+ // To avoid it to be resized.
+ span.onresizestart = function()
+ {
+ FCK.EditorWindow.event.returnValue = false ;
+ return false ;
+ }
+}
+
+// On Gecko we must do this trick so the user select all the SPAN when clicking on it.
+FCKPlaceholders._SetupClickListener = function()
+{
+ FCKPlaceholders._ClickListener = function( e )
+ {
+ if ( e.target.tagName == 'SPAN' && e.target._fckplaceholder )
+ FCKSelection.SelectNode( e.target ) ;
+ }
+
+ FCK.EditorDocument.addEventListener( 'click', FCKPlaceholders._ClickListener, true ) ;
+}
+
+// Open the Placeholder dialog on double click.
+FCKPlaceholders.OnDoubleClick = function( span )
+{
+ if ( span.tagName == 'SPAN' && span._fckplaceholder )
+ FCKCommands.GetCommand( 'Placeholder' ).Execute() ;
+}
+
+FCK.RegisterDoubleClickHandler( FCKPlaceholders.OnDoubleClick, 'SPAN' ) ;
+
+// Check if a Placholder name is already in use.
+FCKPlaceholders.Exist = function( name )
+{
+ var aSpans = FCK.EditorDocument.getElementsByTagName( 'SPAN' ) ;
+
+ for ( var i = 0 ; i < aSpans.length ; i++ )
+ {
+ if ( aSpans[i]._fckplaceholder == name )
+ return true ;
+ }
+
+ return false ;
+}
+
+if ( FCKBrowserInfo.IsIE )
+{
+ FCKPlaceholders.Redraw = function()
+ {
+ if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )
+ return ;
+
+ var aPlaholders = FCK.EditorDocument.body.innerText.match( /\[\[[^\[\]]+\]\]/g ) ;
+ if ( !aPlaholders )
+ return ;
+
+ var oRange = FCK.EditorDocument.body.createTextRange() ;
+
+ for ( var i = 0 ; i < aPlaholders.length ; i++ )
+ {
+ if ( oRange.findText( aPlaholders[i] ) )
+ {
+ var sName = aPlaholders[i].match( /\[\[\s*([^\]]*?)\s*\]\]/ )[1] ;
+ oRange.pasteHTML( '<span style="color: #000000; background-color: #ffff00" contenteditable="false" _fckplaceholder="' + sName + '">' + aPlaholders[i] + '</span>' ) ;
+ }
+ }
+ }
+}
+else
+{
+ FCKPlaceholders.Redraw = function()
+ {
+ if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )
+ return ;
+
+ var oInteractor = FCK.EditorDocument.createTreeWalker( FCK.EditorDocument.body, NodeFilter.SHOW_TEXT, FCKPlaceholders._AcceptNode, true ) ;
+
+ var aNodes = new Array() ;
+
+ while ( ( oNode = oInteractor.nextNode() ) )
+ {
+ aNodes[ aNodes.length ] = oNode ;
+ }
+
+ for ( var n = 0 ; n < aNodes.length ; n++ )
+ {
+ var aPieces = aNodes[n].nodeValue.split( /(\[\[[^\[\]]+\]\])/g ) ;
+
+ for ( var i = 0 ; i < aPieces.length ; i++ )
+ {
+ if ( aPieces[i].length > 0 )
+ {
+ if ( aPieces[i].indexOf( '[[' ) == 0 )
+ {
+ var sName = aPieces[i].match( /\[\[\s*([^\]]*?)\s*\]\]/ )[1] ;
+
+ var oSpan = FCK.EditorDocument.createElement( 'span' ) ;
+ FCKPlaceholders.SetupSpan( oSpan, sName ) ;
+
+ aNodes[n].parentNode.insertBefore( oSpan, aNodes[n] ) ;
+ }
+ else
+ aNodes[n].parentNode.insertBefore( FCK.EditorDocument.createTextNode( aPieces[i] ) , aNodes[n] ) ;
+ }
+ }
+
+ aNodes[n].parentNode.removeChild( aNodes[n] ) ;
+ }
+
+ FCKPlaceholders._SetupClickListener() ;
+ }
+
+ FCKPlaceholders._AcceptNode = function( node )
+ {
+ if ( /\[\[[^\[\]]+\]\]/.test( node.nodeValue ) )
+ return NodeFilter.FILTER_ACCEPT ;
+ else
+ return NodeFilter.FILTER_SKIP ;
+ }
+}
+
+FCK.Events.AttachEvent( 'OnAfterSetHTML', FCKPlaceholders.Redraw ) ;
+
+// We must process the SPAN tags to replace then with the real resulting value of the placeholder.
+FCKXHtml.TagProcessors['span'] = function( node, htmlNode )
+{
+ if ( htmlNode._fckplaceholder )
+ node = FCKXHtml.XML.createTextNode( '[[' + htmlNode._fckplaceholder + ']]' ) ;
+ else
+ FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;
+
+ return node ;
+}
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js
new file mode 100644
index 0000000..aad19ba
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placholder German language file.
+ */
+FCKLang.PlaceholderBtn = 'Einfügen/editieren Platzhalter' ;
+FCKLang.PlaceholderDlgTitle = 'Platzhalter Eigenschaften' ;
+FCKLang.PlaceholderDlgName = 'Platzhalter Name' ;
+FCKLang.PlaceholderErrNoName = 'Bitte den Namen des Platzhalters schreiben' ;
+FCKLang.PlaceholderErrNameInUse = 'Der angegebene Namen ist schon in Gebrauch' ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js
new file mode 100644
index 0000000..d716718
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placholder English language file.
+ */
+FCKLang.PlaceholderBtn = 'Insert/Edit Placeholder' ;
+FCKLang.PlaceholderDlgTitle = 'Placeholder Properties' ;
+FCKLang.PlaceholderDlgName = 'Placeholder Name' ;
+FCKLang.PlaceholderErrNoName = 'Please type the placeholder name' ;
+FCKLang.PlaceholderErrNameInUse = 'The specified name is already in use' ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/es.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/es.js
new file mode 100644
index 0000000..eaf4b72
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/es.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placholder Spanish language file.
+ */
+FCKLang.PlaceholderBtn = 'Insertar/Editar contenedor' ;
+FCKLang.PlaceholderDlgTitle = 'Propiedades del contenedor ' ;
+FCKLang.PlaceholderDlgName = 'Nombre de contenedor' ;
+FCKLang.PlaceholderErrNoName = 'Por favor escriba el nombre de contenedor' ;
+FCKLang.PlaceholderErrNameInUse = 'El nombre especificado ya esta en uso' ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js
new file mode 100644
index 0000000..558793b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placeholder French language file.
+ */
+FCKLang.PlaceholderBtn = "Insérer/Modifier l'Espace réservé" ;
+FCKLang.PlaceholderDlgTitle = "Propriétés de l'Espace réservé" ;
+FCKLang.PlaceholderDlgName = "Nom de l'Espace réservé" ;
+FCKLang.PlaceholderErrNoName = "Veuillez saisir le nom de l'Espace réservé" ;
+FCKLang.PlaceholderErrNameInUse = "Ce nom est déjà utilisé" ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js
new file mode 100644
index 0000000..ac0df55
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placholder Italian language file.
+ */
+FCKLang.PlaceholderBtn = 'Aggiungi/Modifica Placeholder' ;
+FCKLang.PlaceholderDlgTitle = 'Proprietà del Placeholder' ;
+FCKLang.PlaceholderDlgName = 'Nome del Placeholder' ;
+FCKLang.PlaceholderErrNoName = 'Digitare il nome del placeholder' ;
+FCKLang.PlaceholderErrNameInUse = 'Il nome inserito è già in uso' ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js
new file mode 100644
index 0000000..40e221b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js
@@ -0,0 +1,27 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Placholder Polish language file.
+ */
+FCKLang.PlaceholderBtn = 'Wstaw/Edytuj nagłówek' ;
+FCKLang.PlaceholderDlgTitle = 'Właśności nagłówka' ;
+FCKLang.PlaceholderDlgName = 'Nazwa nagłówka' ;
+FCKLang.PlaceholderErrNoName = 'Proszę wprowadzić nazwę nagłówka' ;
+FCKLang.PlaceholderErrNameInUse = 'Podana nazwa jest już w użyciu' ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif b/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif
new file mode 100644
index 0000000..c07078c
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js
new file mode 100644
index 0000000..23f5cab
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js
@@ -0,0 +1,29 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This plugin register Toolbar items for the combos modifying the style to
+ * not show the box.
+ */
+
+FCKToolbarItems.RegisterItem( 'SourceSimple' , new FCKToolbarButton( 'Source', FCKLang.Source, null, FCK_TOOLBARITEM_ONLYICON, true, true, 1 ) ) ;
+FCKToolbarItems.RegisterItem( 'StyleSimple' , new FCKToolbarStyleCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;
+FCKToolbarItems.RegisterItem( 'FontNameSimple' , new FCKToolbarFontsCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;
+FCKToolbarItems.RegisterItem( 'FontSizeSimple' , new FCKToolbarFontSizeCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;
+FCKToolbarItems.RegisterItem( 'FontFormatSimple', new FCKToolbarFontFormatCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;
diff --git a/httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js
new file mode 100644
index 0000000..0a2f76a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js
@@ -0,0 +1,33 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This plugin register the required Toolbar items to be able to insert the
+ * table commands in the toolbar.
+ */
+
+FCKToolbarItems.RegisterItem( 'TableInsertRowAfter' , new FCKToolbarButton( 'TableInsertRowAfter' , FCKLang.InsertRowAfter, null, null, null, true, 62 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableDeleteRows' , new FCKToolbarButton( 'TableDeleteRows' , FCKLang.DeleteRows, null, null, null, true, 63 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableInsertColumnAfter' , new FCKToolbarButton( 'TableInsertColumnAfter' , FCKLang.InsertColumnAfter, null, null, null, true, 64 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableDeleteColumns' , new FCKToolbarButton( 'TableDeleteColumns', FCKLang.DeleteColumns, null, null, null, true, 65 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableInsertCellAfter' , new FCKToolbarButton( 'TableInsertCellAfter' , FCKLang.InsertCellAfter, null, null, null, true, 58 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableDeleteCells' , new FCKToolbarButton( 'TableDeleteCells' , FCKLang.DeleteCells, null, null, null, true, 59 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableMergeCells' , new FCKToolbarButton( 'TableMergeCells' , FCKLang.MergeCells, null, null, null, true, 60 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableHorizontalSplitCell' , new FCKToolbarButton( 'TableHorizontalSplitCell' , FCKLang.SplitCell, null, null, null, true, 61 ) ) ;
+FCKToolbarItems.RegisterItem( 'TableCellProp' , new FCKToolbarButton( 'TableCellProp' , FCKLang.CellProperties, null, null, null, true, 57 ) ) ;
diff --git a/httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html b/httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html
new file mode 100644
index 0000000..fe3dc0a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Useful page that enumerates all icons in the skins strips.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>FCKeditor - View Icons Strips</title>
+ <style type="text/css">
+ .TB_Button_Image
+ {
+ overflow: hidden;
+ width: 16px;
+ height: 16px;
+ margin: 3px;
+ background-repeat: no-repeat;
+ }
+
+ .TB_Button_Image img
+ {
+ position: relative;
+ }
+ </style>
+ <script type="text/javascript">
+
+window.onload = function()
+{
+ var eImg1 = document.createElement( 'img' ) ;
+ eImg1.onload = Img_OnLoad ;
+ eImg1.src = 'default/fck_strip.gif' ;
+
+ var eImg2 = document.createElement( 'img' ) ;
+ eImg2.onload = Img_OnLoad ;
+ eImg2.src = 'office2003/fck_strip.gif' ;
+
+ var eImg3 = document.createElement( 'img' ) ;
+ eImg3.onload = Img_OnLoad ;
+ eImg3.src = 'silver/fck_strip.gif' ;
+}
+
+var iTotalStrips = 3 ;
+var iMaxHeight = 0 ;
+
+function Img_OnLoad()
+{
+ if ( iMaxHeight < this.height )
+ iMaxHeight = this.height ;
+
+ iTotalStrips-- ;
+
+ if ( iTotalStrips == 0 )
+ LoadIcons( iMaxHeight / 16 ) ;
+}
+
+function LoadIcons( total )
+{
+ var xIconsTable = document.getElementById( 'xIconsTable' ) ;
+
+ for ( var i = 0 ; i < total ; i++ )
+ {
+ var eRow = xIconsTable.insertRow(-1) ;
+
+ var eCell = eRow.insertCell(-1) ;
+ eCell.innerHTML = i + 1 ;
+
+ eCell = eRow.insertCell(-1) ;
+ eCell.align = 'center' ;
+ eCell.style.border = '#dcdcdc 1px solid' ;
+ eCell.innerHTML = '<div class="TB_Button_Image"><img src="default/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;
+
+ eCell = eRow.insertCell(-1) ;
+ eCell.align = 'center' ;
+ eCell.style.border = '#dcdcdc 1px solid' ;
+ eCell.innerHTML = '<div class="TB_Button_Image"><img src="office2003/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;
+
+ eCell = eRow.insertCell(-1) ;
+ eCell.align = 'center' ;
+ eCell.style.border = '#dcdcdc 1px solid' ;
+ eCell.innerHTML = '<div class="TB_Button_Image"><img src="silver/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;
+ }
+}
+
+ </script>
+</head>
+<body>
+ <table id="xIconsTable">
+ <tr>
+ <td rowspan="2">
+ Index</td>
+ <td align="center" colspan="3">
+ Skins</td>
+ </tr>
+ <tr>
+ <td width="80" align="center">
+ default</td>
+ <td width="80" align="center">
+ office2003</td>
+ <td width="80" align="center">
+ silver</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css b/httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css
new file mode 100644
index 0000000..7e68ef3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css
@@ -0,0 +1,402 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the dialog boxes.
+ */
+
+html, body
+{
+ background-color: transparent;
+ margin: 0px;
+ padding: 0px;
+}
+
+body
+{
+ padding: 10px;
+}
+
+body, td, input, select, textarea
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+body, .BackColor
+{
+ background-color: #f1f1e3;
+}
+
+.PopupBody
+{
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ background-color: transparent;
+ padding: 0px;
+}
+
+#header
+{
+ cursor: move;
+}
+
+.PopupTitle
+{
+ font-weight: bold;
+ font-size: 14pt;
+ color: #737357;
+ background-color: #e3e3c7;
+ padding: 3px 10px 3px 10px;
+}
+
+.PopupButtons
+{
+ position: absolute;
+ right: 0px;
+ left: 0px;
+ bottom: 0px;
+ border-top: #d5d59d 1px solid;
+ background-color: #e3e3c7;
+ padding: 7px 10px 7px 10px;
+}
+
+.Button
+{
+ border: #737357 1px solid;
+ color: #3b3b1f;
+ background-color: #c7c78f;
+}
+
+#btnOk
+{
+ width: 100px;
+}
+
+.DarkBackground
+{
+ background-color: #eaead1;
+}
+
+.LightBackground
+{
+ background-color: #ffffbe;
+}
+
+.PopupTitleBorder
+{
+ border-bottom: #d5d59d 1px solid;
+}
+
+.PopupTabArea
+{
+ color: #737357;
+ background-color: #e3e3c7;
+}
+
+.PopupTabEmptyArea
+{
+ padding-left: 10px;
+ border-bottom: #d5d59d 1px solid;
+}
+
+.PopupTab, .PopupTabSelected
+{
+ border-right: #d5d59d 1px solid;
+ border-top: #d5d59d 1px solid;
+ border-left: #d5d59d 1px solid;
+ padding: 3px 5px 3px 5px;
+ color: #737357;
+}
+
+.PopupTab
+{
+ margin-top: 1px;
+ border-bottom: #d5d59d 1px solid;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.PopupTabSelected
+{
+ font-weight: bold;
+ cursor: default;
+ padding-top: 4px;
+ border-bottom: #f1f1e3 1px solid;
+ background-color: #f1f1e3;
+}
+
+.PopupSelectionBox
+{
+ border: #ff9933 1px solid !important;
+ background-color: #fffacd !important;
+ cursor: pointer;
+ cursor: hand;
+}
+
+#tdBrowse
+{
+ vertical-align: bottom;
+}
+
+/**
+ * Dialog frame related styles.
+ */
+
+.contents
+{
+ position: absolute;
+ top: 2px;
+ left: 16px;
+ right: 16px;
+ bottom: 20px;
+ background-color: #f1f1e3;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.tl, .tr, .tc, .bl, .br, .bc
+{
+ position: absolute;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+}
+
+* html .tl, * html .tr, * html .tc, * html .bl, * html .br, * html .bc
+{
+ background-image: url(images/sprites.gif);
+}
+
+.ml, .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.png);
+ background-repeat: repeat-y;
+}
+
+* html .ml, * html .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.rtl .ml, .rtl .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.rtl.png);
+ background-repeat: repeat-y;
+}
+
+* html .rtl .ml, * html .rtl .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.tl
+{
+ top: 0px;
+ left: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -16px;
+}
+
+.rtl .tl
+{
+ background-position: -16px -397px;
+}
+
+.tr
+{
+ top: 0px;
+ right: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -76px;
+}
+
+.rtl .tr
+{
+ background-position: -16px -457px;
+}
+
+.tc
+{
+ top: 0px;
+ right: 16px;
+ left: 16px;
+ height: 16px;
+ background-position: 0px -136px;
+ background-repeat: repeat-x;
+}
+
+.ml
+{
+ top: 16px;
+ left: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: 0px 0px;
+}
+
+.mr
+{
+ top: 16px;
+ right: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: -16px 0px;
+}
+
+.bl
+{
+ bottom: 0px;
+ left: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -196px;
+}
+
+.rtl .bl
+{
+ background-position: -16px -517px;
+}
+
+.br
+{
+ bottom: 0px;
+ right: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -263px;
+}
+
+.rtl .br
+{
+ background-position: -16px -584px;
+}
+
+.bc
+{
+ bottom: 0px;
+ right: 30px;
+ left: 30px;
+ height: 51px;
+ background-position: 0px -330px;
+ background-repeat: repeat-x;
+}
+
+/* For IE6. Do not change it. */
+* html .blocker
+{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 12;
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
+}
+
+/* The layer used to cover the dialog when opening a child dialog. */
+.cover
+{
+ position: absolute;
+ top: 0px;
+ left: 14px;
+ right: 14px;
+ bottom: 18px;
+ z-index: 11;
+}
+
+#closeButton
+{
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ margin-top: 5px;
+ margin-right: 10px;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+ background-position: -16px -651px;
+}
+
+* html #closeButton
+{
+ cursor: hand;
+ background-image: url(images/sprites.gif);
+}
+
+.rtl #closeButton
+{
+ right: auto;
+ left: 10px;
+ margin-right: 0px;
+}
+
+#closeButton:hover
+{
+ background-position: -16px -687px;
+}
+
+#throbberBlock
+{
+ z-index: 10;
+}
+
+#throbberBlock div
+{
+ float: left;
+ width: 8px;
+ height: 9px;
+ margin-left: 2px;
+ margin-right: 2px;
+ font-size: 1px; /* IE6 */
+}
+
+/*
+ Color Gradient Generator:
+ http://www.herethere.net/~samson/php/color_gradient/?cbegin=737357&cend=E3E3C7&steps=4
+*/
+
+.throbber_1
+{
+ background-color: #737357;
+}
+
+.throbber_2
+{
+ background-color: #8f8f73;
+}
+
+.throbber_3
+{
+ background-color: #abab8f;
+}
+
+.throbber_4
+{
+ background-color: #c7c7ab;
+}
+
+.throbber_5
+{
+ background-color: #e3e3c7;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/default/fck_dialog_ie6.js b/httemplate/elements/fckeditor/editor/skins/default/fck_dialog_ie6.js
new file mode 100644
index 0000000..93dd674
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/fck_dialog_ie6.js
@@ -0,0 +1,110 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+(function()
+{
+ // IE6 doens't handle absolute positioning properly (it is always in quirks
+ // mode). This function fixes the sizes and positions of many elements that
+ // compose the skin (this is skin specific).
+ var fixSizes = window.DoResizeFixes = function()
+ {
+ var fckDlg = window.document.body ;
+
+ for ( var i = 0 ; i < fckDlg.childNodes.length ; i++ )
+ {
+ var child = fckDlg.childNodes[i] ;
+ switch ( child.className )
+ {
+ case 'contents' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 ) ; // -left -right
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 ) ; // -bottom -top
+ break ;
+
+ case 'blocker' :
+ case 'cover' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 + 4 ) ; // -left -right + 4
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 + 4 ) ; // -bottom -top + 4
+ break ;
+
+ case 'tr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ break ;
+
+ case 'tc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 16 - 16 ) ;
+ break ;
+
+ case 'ml' :
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'mr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'bl' :
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'br' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'bc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 30 - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+ }
+ }
+ }
+
+ var closeButtonOver = function()
+ {
+ this.style.backgroundPosition = '-16px -687px' ;
+ } ;
+
+ var closeButtonOut = function()
+ {
+ this.style.backgroundPosition = '-16px -651px' ;
+ } ;
+
+ var fixCloseButton = function()
+ {
+ var closeButton = document.getElementById ( 'closeButton' ) ;
+
+ closeButton.onmouseover = closeButtonOver ;
+ closeButton.onmouseout = closeButtonOut ;
+ }
+
+ var onLoad = function()
+ {
+ fixSizes() ;
+ fixCloseButton() ;
+
+ window.attachEvent( 'onresize', fixSizes ) ;
+ window.detachEvent( 'onload', onLoad ) ;
+ }
+
+ window.attachEvent( 'onload', onLoad ) ;
+
+})() ;
diff --git a/httemplate/elements/fckeditor/editor/skins/default/fck_editor.css b/httemplate/elements/fckeditor/editor/skins/default/fck_editor.css
new file mode 100644
index 0000000..f9aff70
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/fck_editor.css
@@ -0,0 +1,464 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the editor IFRAME and Toolbar.
+ */
+
+/*
+ ### Basic Editor IFRAME Styles.
+*/
+
+body
+{
+ padding: 1px;
+ margin: 0;
+ background-color: #ffffff;
+}
+
+#xEditingArea
+{
+ border: #696969 1px solid;
+}
+
+.SourceField
+{
+ padding: 5px;
+ margin: 0px;
+ font-family: Monospace;
+}
+
+/*
+ Toolbar
+*/
+
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse
+{
+ cursor: default;
+ background-color: #efefde;
+}
+
+.TB_ToolbarSet
+{
+ border-top: #efefde 1px outset;
+ border-bottom: #efefde 1px outset;
+}
+
+.TB_ToolbarSet TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.TB_Toolbar
+{
+ height: 24px;
+ display: inline-table; /* inline = Opera jumping buttons bug */
+}
+
+.TB_Separator
+{
+ width: 1px;
+ height: 16px;
+ margin: 2px;
+ background-color: #999966;
+}
+
+.TB_Start
+{
+ background-image: url(images/toolbar.start.gif);
+ margin: 2px;
+ width: 3px;
+ background-repeat: no-repeat;
+ height: 16px;
+}
+
+.TB_End
+{
+ display: none;
+}
+
+.TB_ExpandImg
+{
+ background-image: url(images/toolbar.expand.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_CollapseImg
+{
+ background-image: url(images/toolbar.collapse.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_SideBorder
+{
+ background-color: #696969;
+}
+
+.TB_Expand, .TB_Collapse
+{
+ padding: 2px 2px 2px 2px;
+ border: #efefde 1px outset;
+}
+
+.TB_Collapse
+{
+ width: 5px;
+}
+
+.TB_Break
+{
+ height: 24px; /* IE needs the height to be set, otherwise no break */
+}
+
+/*
+ Toolbar Button
+*/
+
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled
+{
+ border: #efefde 1px solid; /* This is the default border */
+ height: 22px; /* The height is necessary, otherwise IE will not apply the alpha */
+}
+
+.TB_Button_On
+{
+ border: #316ac5 1px solid;
+ background-color: #c1d2ee;
+}
+
+.TB_Button_On_Over, .TB_Button_Off_Over
+{
+ border: #316ac5 1px solid;
+ background-color: #dff1ff;
+}
+
+.TB_Button_Off
+{
+ filter: alpha(opacity=70); /* IE */
+ opacity: 0.70; /* Safari, Opera and Mozilla */
+}
+
+.TB_Button_Disabled
+{
+ filter: gray() alpha(opacity=30); /* IE */
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+}
+
+.TB_Button_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 22px;
+}
+
+.TB_Button_Image
+{
+ overflow: hidden;
+ width: 16px;
+ height: 16px;
+ margin: 3px;
+ background-repeat: no-repeat;
+}
+
+.TB_Button_Image img
+{
+ position: relative;
+}
+
+.TB_Button_Off .TB_Button_Text
+{
+ background-color: #efefde; /* Needed because of a bug on Clear Type */
+}
+
+.TB_ConnectionLine
+{
+ background-color: #ffffff;
+ height: 1px;
+ margin-left: 1px; /* ltr */
+ margin-right: 1px; /* rtl */
+}
+
+.TB_Text
+{
+ height: 22px;
+}
+
+.TB_Button_Off .TB_Text
+{
+ background-color: #efefde ; /* Needed because of a bug on ClearType */
+}
+
+.TB_Button_On_Over .TB_Text
+{
+ background-color: #dff1ff ; /* Needed because of a bug on ClearType */
+}
+
+/*
+ Menu
+*/
+
+.MN_Menu
+{
+ border: 1px solid #8f8f73;
+ padding: 2px;
+ background-color: #ffffff;
+ cursor: default;
+}
+
+.MN_Menu, .MN_Menu .MN_Label
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.MN_Item_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 20px;
+}
+
+.MN_Icon
+{
+ background-color: #e3e3c7;
+ text-align: center;
+ height: 20px;
+}
+
+.MN_Label
+{
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.MN_Separator
+{
+ height: 3px;
+}
+
+.MN_Separator_Line
+{
+ border-top: #b9b99d 1px solid;
+}
+
+.MN_Item .MN_Icon IMG
+{
+ filter: alpha(opacity=70);
+ opacity: 0.70;
+}
+
+.MN_Item_Over
+{
+ color: #ffffff;
+ background-color: #8f8f73;
+}
+
+.MN_Item_Over .MN_Icon
+{
+ background-color: #737357;
+}
+
+.MN_Item_Disabled IMG
+{
+ filter: gray() alpha(opacity=30); /* IE */
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+}
+
+.MN_Item_Disabled .MN_Label
+{
+ color: #b7b7b7;
+}
+
+.MN_Arrow
+{
+ padding-right: 3px;
+ padding-left: 3px;
+}
+
+.MN_ConnectionLine
+{
+ background-color: #ffffff;
+}
+
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over
+{
+ border: #8f8f73 1px solid;
+ background-color: #ffffff;
+}
+
+/*
+ ### Panel Styles
+*/
+
+.FCK_Panel
+{
+ border: #8f8f73 1px solid;
+ padding: 2px;
+ background-color: #ffffff;
+}
+
+.FCK_Panel, .FCK_Panel TD
+{
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+ font-size: 11px;
+}
+
+/*
+ ### Special Combos
+*/
+
+.SC_Panel
+{
+ overflow: auto;
+ white-space: nowrap;
+ cursor: default;
+ border: 1px solid #8f8f73;
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.SC_Panel, .SC_Panel TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.SC_Item, .SC_ItemSelected
+{
+ margin-top: 2px;
+ margin-bottom: 2px;
+ background-position: left center;
+ padding-left: 11px;
+ padding-right: 3px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ background-repeat: no-repeat;
+ border: #dddddd 1px solid;
+}
+
+.SC_Item *, .SC_ItemSelected *
+{
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.SC_ItemSelected
+{
+ border: #9a9afb 1px solid;
+ background-image: url(images/toolbar.arrowright.gif);
+}
+
+.SC_ItemOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_Field
+{
+ border: #b7b7a6 1px solid;
+ cursor: default;
+}
+
+.SC_FieldCaption
+{
+ overflow: visible;
+ padding-right: 5px;
+ padding-left: 5px;
+ opacity: 0.75; /* Safari, Opera and Mozilla */
+ filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */
+ height: 23px;
+ background-color: #efefde;
+}
+
+.SC_FieldLabel
+{
+ white-space: nowrap;
+ padding: 2px;
+ width: 100%;
+ cursor: default;
+ background-color: #ffffff;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.SC_FieldButton
+{
+ background-position: center center;
+ background-image: url(images/toolbar.buttonarrow.gif);
+ border-left: #b7b7a6 1px solid;
+ width: 14px;
+ background-repeat: no-repeat;
+}
+
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption, .SC_FieldDisabled .TB_ButtonType_Text
+{
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+ filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */
+}
+
+.SC_FieldOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_FieldOver .SC_FieldButton
+{
+ border-left: #316ac5 1px solid;
+}
+
+/*
+ ### Color Selector Panel
+*/
+
+.ColorBoxBorder
+{
+ border: #808080 1px solid;
+ position: static;
+}
+
+.ColorBox
+{
+ font-size: 1px;
+ width: 10px;
+ position: static;
+ height: 10px;
+}
+
+.ColorDeselected, .ColorSelected
+{
+ cursor: default;
+}
+
+.ColorDeselected
+{
+ border: #ffffff 1px solid;
+ padding: 2px;
+ float: left;
+}
+
+.ColorSelected
+{
+ border: #330066 1px solid;
+ padding: 2px;
+ float: left;
+ background-color: #c4cdd6;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif b/httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif
new file mode 100644
index 0000000..a6ca532
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.gif b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.gif
new file mode 100644
index 0000000..8f91b47
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.png b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.png
new file mode 100644
index 0000000..1042a61
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.rtl.png b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.rtl.png
new file mode 100644
index 0000000..d7f7b49
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/dialog.sides.rtl.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/sprites.gif b/httemplate/elements/fckeditor/editor/skins/default/images/sprites.gif
new file mode 100644
index 0000000..2f57d93
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/sprites.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/sprites.png b/httemplate/elements/fckeditor/editor/skins/default/images/sprites.png
new file mode 100644
index 0000000..5be90ed
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/sprites.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif
new file mode 100644
index 0000000..6843c8d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif
new file mode 100644
index 0000000..ea60995
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif
new file mode 100644
index 0000000..87aa56d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif
new file mode 100644
index 0000000..5bfd67a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif
new file mode 100644
index 0000000..79075e7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif
new file mode 100644
index 0000000..eaed04a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif
new file mode 100644
index 0000000..1774246
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css b/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css
new file mode 100644
index 0000000..5c7836b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css
@@ -0,0 +1,402 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the dialog boxes.
+ */
+
+html, body
+{
+ background-color: transparent;
+ margin: 0px;
+ padding: 0px;
+}
+
+body
+{
+ padding: 10px;
+}
+
+body, td, input, select, textarea
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+body, .BackColor
+{
+ background-color: #f7f8fd;
+}
+
+.PopupBody
+{
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ background-color: transparent;
+ padding: 0px;
+}
+
+#header
+{
+ cursor: move;
+}
+
+.PopupTitle
+{
+ font-weight: bold;
+ font-size: 14pt;
+ color: #0e3460;
+ background-color: #8cb2fd;
+ padding: 3px 10px 3px 10px;
+}
+
+.PopupButtons
+{
+ position: absolute;
+ right: 0px;
+ left: 0px;
+ bottom: 0px;
+ border-top: #466ca6 1px solid;
+ background-color: #8cb2fd;
+ padding: 7px 10px 7px 10px;
+}
+
+.Button
+{
+ border: #1c3460 1px solid;
+ color: #000a28;
+ background-color: #7096d3;
+}
+
+#btnOk
+{
+ width: 100px;
+}
+
+.DarkBackground
+{
+ background-color: #eaf2f8;
+}
+
+.LightBackground
+{
+ background-color: #ffffbe;
+}
+
+.PopupTitleBorder
+{
+ border-bottom: #d5d59d 1px solid;
+}
+
+.PopupTabArea
+{
+ color: #0e3460;
+ background-color: #8cb2fd;
+}
+
+.PopupTabEmptyArea
+{
+ padding-left: 10px ;
+ border-bottom: #466ca6 1px solid;
+}
+
+.PopupTab, .PopupTabSelected
+{
+ border-right: #466ca6 1px solid;
+ border-top: #466ca6 1px solid;
+ border-left: #466ca6 1px solid;
+ padding: 3px 5px 3px 5px;
+ color: #0e3460;
+}
+
+.PopupTab
+{
+ margin-top: 1px;
+ border-bottom: #466ca6 1px solid;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.PopupTabSelected
+{
+ font-weight: bold;
+ cursor: default;
+ padding-top: 4px;
+ border-bottom: #f7f8fd 1px solid;
+ background-color: #f7f8fd;
+}
+
+.PopupSelectionBox
+{
+ border: #1e90ff 1px solid !important;
+ background-color: #add8e6 !important;
+ cursor: pointer;
+ cursor: hand;
+}
+
+#tdBrowse
+{
+ vertical-align: bottom;
+}
+
+/**
+ * Dialog frame related styles.
+ */
+
+.contents
+{
+ position: absolute;
+ top: 2px;
+ left: 16px;
+ right: 16px;
+ bottom: 20px;
+ background-color: #f7f8fD;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.tl, .tr, .tc, .bl, .br, .bc
+{
+ position: absolute;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+}
+
+* html .tl, * html .tr, * html .tc, * html .bl, * html .br, * html .bc
+{
+ background-image: url(images/sprites.gif);
+}
+
+.ml, .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.png);
+ background-repeat: repeat-y;
+}
+
+* html .ml, * html .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.rtl .ml, .rtl .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.rtl.png);
+ background-repeat: repeat-y;
+}
+
+* html .rtl .ml, * html .rtl .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.tl
+{
+ top: 0px;
+ left: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -16px;
+}
+
+.rtl .tl
+{
+ background-position: -16px -397px;
+}
+
+.tr
+{
+ top: 0px;
+ right: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -76px;
+}
+
+.rtl .tr
+{
+ background-position: -16px -457px;
+}
+
+.tc
+{
+ top: 0px;
+ right: 16px;
+ left: 16px;
+ height: 16px;
+ background-position: 0px -136px;
+ background-repeat: repeat-x;
+}
+
+.ml
+{
+ top: 16px;
+ left: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: 0px 0px;
+}
+
+.mr
+{
+ top: 16px;
+ right: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: -16px 0px;
+}
+
+.bl
+{
+ bottom: 0px;
+ left: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -196px;
+}
+
+.rtl .bl
+{
+ background-position: -16px -517px;
+}
+
+.br
+{
+ bottom: 0px;
+ right: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -263px;
+}
+
+.rtl .br
+{
+ background-position: -16px -584px;
+}
+
+.bc
+{
+ bottom: 0px;
+ right: 30px;
+ left: 30px;
+ height: 51px;
+ background-position: 0px -330px;
+ background-repeat: repeat-x;
+}
+
+/* For IE6. Do not change it. */
+* html .blocker
+{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 12;
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
+}
+
+/* The layer used to cover the dialog when opening a child dialog. */
+.cover
+{
+ position: absolute;
+ top: 0px;
+ left: 14px;
+ right: 14px;
+ bottom: 18px;
+ z-index: 11;
+}
+
+#closeButton
+{
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ margin-top: 5px;
+ margin-right: 10px;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+ background-position: -16px -651px;
+}
+
+* html #closeButton
+{
+ cursor: hand;
+ background-image: url(images/sprites.gif);
+}
+
+.rtl #closeButton
+{
+ right: auto;
+ left: 10px;
+ margin-right: 0px;
+}
+
+#closeButton:hover
+{
+ background-position: -16px -687px;
+}
+
+#throbberBlock
+{
+ z-index: 10;
+}
+
+#throbberBlock div
+{
+ float: left;
+ width: 8px;
+ height: 9px;
+ margin-left: 2px;
+ margin-right: 2px;
+ font-size: 1px; /* IE6 */
+}
+
+/*
+ Color Gradient Generator:
+ http://www.herethere.net/~samson/php/color_gradient/?cbegin=0E3460&cend=8cb2fd&steps=4
+*/
+
+.throbber_1
+{
+ background-color: #0E3460;
+}
+
+.throbber_2
+{
+ background-color: #2D5387;
+}
+
+.throbber_3
+{
+ background-color: #4D73AE;
+}
+
+.throbber_4
+{
+ background-color: #6C92D5;
+}
+
+.throbber_5
+{
+ background-color: #8CB2FD;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog_ie6.js b/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog_ie6.js
new file mode 100644
index 0000000..93dd674
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog_ie6.js
@@ -0,0 +1,110 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+(function()
+{
+ // IE6 doens't handle absolute positioning properly (it is always in quirks
+ // mode). This function fixes the sizes and positions of many elements that
+ // compose the skin (this is skin specific).
+ var fixSizes = window.DoResizeFixes = function()
+ {
+ var fckDlg = window.document.body ;
+
+ for ( var i = 0 ; i < fckDlg.childNodes.length ; i++ )
+ {
+ var child = fckDlg.childNodes[i] ;
+ switch ( child.className )
+ {
+ case 'contents' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 ) ; // -left -right
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 ) ; // -bottom -top
+ break ;
+
+ case 'blocker' :
+ case 'cover' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 + 4 ) ; // -left -right + 4
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 + 4 ) ; // -bottom -top + 4
+ break ;
+
+ case 'tr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ break ;
+
+ case 'tc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 16 - 16 ) ;
+ break ;
+
+ case 'ml' :
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'mr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'bl' :
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'br' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'bc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 30 - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+ }
+ }
+ }
+
+ var closeButtonOver = function()
+ {
+ this.style.backgroundPosition = '-16px -687px' ;
+ } ;
+
+ var closeButtonOut = function()
+ {
+ this.style.backgroundPosition = '-16px -651px' ;
+ } ;
+
+ var fixCloseButton = function()
+ {
+ var closeButton = document.getElementById ( 'closeButton' ) ;
+
+ closeButton.onmouseover = closeButtonOver ;
+ closeButton.onmouseout = closeButtonOut ;
+ }
+
+ var onLoad = function()
+ {
+ fixSizes() ;
+ fixCloseButton() ;
+
+ window.attachEvent( 'onresize', fixSizes ) ;
+ window.detachEvent( 'onload', onLoad ) ;
+ }
+
+ window.attachEvent( 'onload', onLoad ) ;
+
+})() ;
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css b/httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css
new file mode 100644
index 0000000..443c8e5
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css
@@ -0,0 +1,476 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the editor IFRAME and Toolbar.
+ */
+
+/*
+ ### Basic Editor IFRAME Styles.
+*/
+
+body
+{
+ padding: 1px;
+ margin: 0;
+ background-color: #ffffff;
+}
+
+#xEditingArea
+{
+ border: #696969 1px solid;
+}
+
+.SourceField
+{
+ padding: 5px;
+ margin: 0px;
+ font-family: Monospace;
+}
+
+/*
+ Toolbar
+*/
+
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse
+{
+ cursor: default;
+ background-color: #f7f8fd;
+}
+
+.TB_ToolbarSet
+{
+ border-top: #f7f8fd 1px outset;
+ border-bottom: #f7f8fd 1px outset;
+}
+
+.TB_ToolbarSet TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.TB_Toolbar
+{
+ background-color: #d6dff7;
+ background-image: url(images/toolbar.bg.gif);
+ background-repeat: repeat-x;
+ display: inline-table;
+}
+
+.TB_Separator
+{
+ width: 1px;
+ height: 16px;
+ margin: 2px;
+ background-color: #B2CBFF;
+}
+
+.TB_Start
+{
+ background-image: url(images/toolbar.start.gif);
+ background-repeat: no-repeat;
+ background-position: center center;
+ margin: 0px;
+ width: 7px;
+ height: 24px;
+}
+
+.TB_End
+{
+ background-image: url(images/toolbar.end.gif);
+ background-repeat: no-repeat;
+ background-position: center left;
+ height: 24px;
+ width: 4px;
+}
+
+.TB_ExpandImg
+{
+ background-image: url(images/toolbar.expand.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_CollapseImg
+{
+ background-image: url(images/toolbar.collapse.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_SideBorder
+{
+ background-color: #696969;
+}
+
+.TB_Expand, .TB_Collapse
+{
+ padding: 2px 2px 2px 2px;
+ border: #f7f8fd 1px outset;
+}
+
+.TB_Collapse
+{
+ width: 5px;
+}
+
+.TB_Break
+{
+ height: 24px; /* IE needs the height to be set, otherwise no break */
+}
+
+/*
+ Toolbar Button
+*/
+
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled
+{
+ margin: 1px;
+ height: 22px; /* The height is necessary, otherwise IE will not apply the alpha */
+}
+
+.TB_Button_On
+{
+ margin: 0px;
+ border: #316ac5 1px solid;
+ background-color: #c1d2ee;
+}
+
+.TB_Button_On_Over, .TB_Button_Off_Over
+{
+ margin: 0px ;
+ border: #316ac5 1px solid;
+ background-color: #dff1ff;
+}
+
+.TB_Button_Off
+{
+ filter: alpha(opacity=70); /* IE */
+ opacity: 0.70; /* Safari, Opera and Mozilla */
+}
+
+.TB_Button_Disabled
+{
+ filter: gray() alpha(opacity=30); /* IE */
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+}
+
+.TB_Button_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 22px;
+}
+
+.TB_Button_Image
+{
+ overflow: hidden;
+ width: 16px;
+ height: 16px;
+ margin: 3px;
+ background-repeat: no-repeat;
+}
+
+.TB_Button_Image img
+{
+ position: relative;
+}
+
+.TB_Button_Off .TB_Button_Text
+{
+ background-color: #d6dff7; /* Needed because of a bug on ClearType */
+ background-image: url(images/toolbar.bg.gif);
+ background-repeat: repeat-x;
+}
+
+.TB_ConnectionLine
+{
+ background-color: #f7f8fd;
+ height: 1px;
+ margin-left: 1px; /* ltr */
+ margin-right: 1px; /* rtl */
+}
+
+.TB_Button_Off .TB_Text
+{
+ background-color: #d6dff7; /* Needed because of a bug on ClearType */
+ background-image: url(images/toolbar.bg.gif);
+ background-repeat: repeat-x;
+}
+
+.TB_Button_On_Over .TB_Text
+{
+ background-color: #dff1ff ; /* Needed because of a bug on ClearType */
+}
+
+/*
+ Menu
+*/
+
+.MN_Menu
+{
+ border: 1px solid #8f8f73;
+ padding: 2px;
+ background-color: #f7f8fd;
+ cursor: default;
+}
+
+.MN_Menu, .MN_Menu .MN_Label
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.MN_Item_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 20px;
+}
+
+.MN_Icon
+{
+ background-color: #d6dff7;
+ text-align: center;
+ height: 20px;
+}
+
+.MN_Label
+{
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.MN_Separator
+{
+ height: 3px;
+}
+
+.MN_Separator_Line
+{
+ border-top: #b9b99d 1px solid;
+}
+
+.MN_Item .MN_Icon IMG
+{
+ filter: alpha(opacity=70);
+ opacity: 0.70;
+}
+
+.MN_Item_Over
+{
+ color: #ffffff;
+ background-color: #7096FA;
+}
+
+.MN_Item_Over .MN_Icon
+{
+ background-color: #466ca6;
+}
+
+.MN_Item_Disabled IMG
+{
+ filter: gray() alpha(opacity=30); /* IE */
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+}
+
+.MN_Item_Disabled .MN_Label
+{
+ color: #b7b7b7;
+}
+
+.MN_Arrow
+{
+ padding-right: 3px;
+ padding-left: 3px;
+}
+
+.MN_ConnectionLine
+{
+ background-color: #f7f8fd;
+}
+
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over
+{
+ border: #8f8f73 1px solid;
+ background-color: #f7f8fd;
+}
+
+/*
+ ### Panel Styles
+*/
+
+.FCK_Panel
+{
+ border: #8f8f73 1px solid;
+ padding: 2px;
+ background-color: #f7f8fd;
+}
+
+.FCK_Panel, .FCK_Panel TD
+{
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+ font-size: 11px;
+}
+
+/*
+ ### Special Combos
+*/
+
+.SC_Panel
+{
+ overflow: auto;
+ white-space: nowrap;
+ cursor: default;
+ border: 1px solid #8f8f73;
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.SC_Panel, .SC_Panel TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.SC_Item, .SC_ItemSelected
+{
+ margin-top: 2px;
+ margin-bottom: 2px;
+ background-position: left center;
+ padding-left: 11px;
+ padding-right: 3px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ background-repeat: no-repeat;
+ border: #dddddd 1px solid;
+}
+
+.SC_Item *, .SC_ItemSelected *
+{
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.SC_ItemSelected
+{
+ border: #9a9afb 1px solid;
+ background-image: url(images/toolbar.arrowright.gif);
+}
+
+.SC_ItemOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_Field
+{
+ margin-top: 2px ;
+ border: #b7b7a6 1px solid;
+ cursor: default;
+}
+
+.SC_FieldCaption
+{
+ overflow: visible;
+ padding-right: 5px;
+ padding-left: 5px;
+ opacity: 0.75; /* Safari, Opera and Mozilla */
+ filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */
+ height: 23px;
+ background-color: #d6dff7; /* Needed because of a bug on ClearType */
+ background-image: url(images/toolbar.bg.gif);
+ background-repeat: repeat-x;
+/* background-color: inherit; Maybe this is needed wait to check */
+}
+
+.SC_FieldLabel
+{
+ white-space: nowrap;
+ padding: 2px;
+ width: 100%;
+ cursor: default;
+ background-color: #ffffff;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.SC_FieldButton
+{
+ background-position: center center;
+ background-image: url(images/toolbar.buttonarrow.gif);
+ border-left: #b7b7a6 1px solid;
+ width: 14px;
+ background-repeat: no-repeat;
+}
+
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption, .SC_FieldDisabled .TB_ButtonType_Text
+{
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+ filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */
+}
+
+.SC_FieldOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_FieldOver .SC_FieldButton
+{
+ border-left: #316ac5 1px solid;
+}
+
+/*
+ ### Color Selector Panel
+*/
+
+.ColorBoxBorder
+{
+ border: #808080 1px solid;
+ position: static;
+}
+
+.ColorBox
+{
+ font-size: 1px;
+ width: 10px;
+ position: static;
+ height: 10px;
+}
+
+.ColorDeselected, .ColorSelected
+{
+ cursor: default;
+}
+
+.ColorDeselected
+{
+ border: #ffffff 1px solid;
+ padding: 2px;
+ float: left;
+}
+
+.ColorSelected
+{
+ border: #330066 1px solid;
+ padding: 2px;
+ float: left;
+ background-color: #c4cdd6;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif b/httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif
new file mode 100644
index 0000000..5607cc8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.gif
new file mode 100644
index 0000000..921fa1a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.png b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.png
new file mode 100644
index 0000000..be15730
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.rtl.png b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.rtl.png
new file mode 100644
index 0000000..e18f13d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/dialog.sides.rtl.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.gif
new file mode 100644
index 0000000..8763e48
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.png b/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.png
new file mode 100644
index 0000000..7f4e196
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/sprites.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif
new file mode 100644
index 0000000..6843c8d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif
new file mode 100644
index 0000000..b03960b
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif
new file mode 100644
index 0000000..ea60995
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif
new file mode 100644
index 0000000..d549166
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif
new file mode 100644
index 0000000..7ff599d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif
new file mode 100644
index 0000000..c4a7326
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif
new file mode 100644
index 0000000..27db9c3
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif
new file mode 100644
index 0000000..41f1241
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css b/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css
new file mode 100644
index 0000000..dfbc6d8
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css
@@ -0,0 +1,402 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the dialog boxes.
+ */
+
+html, body
+{
+ background-color: transparent;
+ margin: 0px;
+ padding: 0px;
+}
+
+body
+{
+ padding: 10px;
+}
+
+body, td, input, select, textarea
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+body, .BackColor
+{
+ background-color: #f7f7f7;
+}
+
+.PopupBody
+{
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ background-color: transparent;
+ padding: 0px;
+}
+
+#header
+{
+ cursor: move;
+}
+
+.PopupTitle
+{
+ font-weight: bold;
+ font-size: 14pt;
+ color: #504845;
+ background-color: #dedede;
+ padding: 3px 10px 3px 10px;
+}
+
+.PopupButtons
+{
+ position: absolute;
+ right: 0px;
+ left: 0px;
+ bottom: 0px;
+ border-top: #cec6b5 1px solid;
+ background-color: #DEDEDE;
+ padding: 7px 10px 7px 10px;
+}
+
+.Button
+{
+ border: #7a7261 1px solid;
+ color: #504845;
+ background-color: #cec6b5;
+}
+
+#btnOk
+{
+ width: 100px;
+}
+
+.DarkBackground
+{
+ background-color: #f2f2f2;
+}
+
+.LightBackground
+{
+ background-color: #ffffbe;
+}
+
+.PopupTitleBorder
+{
+ border-bottom: #cec6b5 1px solid;
+}
+
+.PopupTabArea
+{
+ color: #504845;
+ background-color: #DEDEDE;
+}
+
+.PopupTabEmptyArea
+{
+ padding-left: 10px ;
+ border-bottom: #cec6b5 1px solid;
+}
+
+.PopupTab, .PopupTabSelected
+{
+ border-right: #cec6b5 1px solid;
+ border-top: #cec6b5 1px solid;
+ border-left: #cec6b5 1px solid;
+ padding: 3px 5px 3px 5px;
+ color: #504845;
+}
+
+.PopupTab
+{
+ margin-top: 1px;
+ border-bottom: #cec6b5 1px solid;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.PopupTabSelected
+{
+ font-weight:bold;
+ cursor: default;
+ padding-top: 4px;
+ border-bottom: #f1f1e3 1px solid;
+ background-color: #f7f7f7;
+}
+
+.PopupSelectionBox
+{
+ border: #a9a9a9 1px solid !important;
+ background-color: #dcdcdc !important;
+ cursor: pointer;
+ cursor: hand;
+}
+
+#tdBrowse
+{
+ vertical-align: bottom;
+}
+
+/**
+ * Dialog frame related styles.
+ */
+
+.contents
+{
+ position: absolute;
+ top: 2px;
+ left: 16px;
+ right: 16px;
+ bottom: 20px;
+ background-color: #f7f7f7;
+ overflow: hidden;
+ z-index: 1;
+}
+
+.tl, .tr, .tc, .bl, .br, .bc
+{
+ position: absolute;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+}
+
+* html .tl, * html .tr, * html .tc, * html .bl, * html .br, * html .bc
+{
+ background-image: url(images/sprites.gif);
+}
+
+.ml, .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.png);
+ background-repeat: repeat-y;
+}
+
+* html .ml, * html .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.rtl .ml, .rtl .mr
+{
+ position: absolute;
+ background-image: url(images/dialog.sides.rtl.png);
+ background-repeat: repeat-y;
+}
+
+* html .rtl .ml, * html .rtl .mr
+{
+ background-image: url(images/dialog.sides.gif);
+}
+
+.tl
+{
+ top: 0px;
+ left: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -16px;
+}
+
+.rtl .tl
+{
+ background-position: -16px -397px;
+}
+
+.tr
+{
+ top: 0px;
+ right: 0px;
+ width: 16px;
+ height: 16px;
+ background-position: -16px -76px;
+}
+
+.rtl .tr
+{
+ background-position: -16px -457px;
+}
+
+.tc
+{
+ top: 0px;
+ right: 16px;
+ left: 16px;
+ height: 16px;
+ background-position: 0px -136px;
+ background-repeat: repeat-x;
+}
+
+.ml
+{
+ top: 16px;
+ left: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: 0px 0px;
+}
+
+.mr
+{
+ top: 16px;
+ right: 0px;
+ width: 16px;
+ bottom: 51px;
+ background-position: -16px 0px;
+}
+
+.bl
+{
+ bottom: 0px;
+ left: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -196px;
+}
+
+.rtl .bl
+{
+ background-position: -16px -517px;
+}
+
+.br
+{
+ bottom: 0px;
+ right: 0px;
+ width: 30px;
+ height: 51px;
+ background-position: -16px -263px;
+}
+
+.rtl .br
+{
+ background-position: -16px -584px;
+}
+
+.bc
+{
+ bottom: 0px;
+ right: 30px;
+ left: 30px;
+ height: 51px;
+ background-position: 0px -330px;
+ background-repeat: repeat-x;
+}
+
+/* For IE6. Do not change it. */
+* html .blocker
+{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 12;
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
+}
+
+/* The layer used to cover the dialog when opening a child dialog. */
+.cover
+{
+ position: absolute;
+ top: 0px;
+ left: 14px;
+ right: 14px;
+ bottom: 18px;
+ z-index: 11;
+}
+
+#closeButton
+{
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ margin-top: 5px;
+ margin-right: 10px;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ background-image: url(images/sprites.png);
+ background-repeat: no-repeat;
+ background-position: -16px -651px;
+}
+
+* html #closeButton
+{
+ cursor: hand;
+ background-image: url(images/sprites.gif);
+}
+
+.rtl #closeButton
+{
+ right: auto;
+ left: 10px;
+ margin-right: 0px;
+}
+
+#closeButton:hover
+{
+ background-position: -16px -687px;
+}
+
+#throbberBlock
+{
+ z-index: 10;
+}
+
+#throbberBlock div
+{
+ float: left;
+ width: 8px;
+ height: 9px;
+ margin-left: 2px;
+ margin-right: 2px;
+ font-size: 1px; /* IE6 */
+}
+
+/*
+ Color Gradient Generator:
+ http://www.herethere.net/~samson/php/color_gradient/?cbegin=504845&cend=DEDEDE&steps=4
+*/
+
+.throbber_1
+{
+ background-color: #504845;
+}
+
+.throbber_2
+{
+ background-color: #736D6B;
+}
+
+.throbber_3
+{
+ background-color: #979391;
+}
+
+.throbber_4
+{
+ background-color: #BAB8B7;
+}
+
+.throbber_5
+{
+ background-color: #DEDEDE;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog_ie6.js b/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog_ie6.js
new file mode 100644
index 0000000..93dd674
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/fck_dialog_ie6.js
@@ -0,0 +1,110 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+(function()
+{
+ // IE6 doens't handle absolute positioning properly (it is always in quirks
+ // mode). This function fixes the sizes and positions of many elements that
+ // compose the skin (this is skin specific).
+ var fixSizes = window.DoResizeFixes = function()
+ {
+ var fckDlg = window.document.body ;
+
+ for ( var i = 0 ; i < fckDlg.childNodes.length ; i++ )
+ {
+ var child = fckDlg.childNodes[i] ;
+ switch ( child.className )
+ {
+ case 'contents' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 ) ; // -left -right
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 ) ; // -bottom -top
+ break ;
+
+ case 'blocker' :
+ case 'cover' :
+ child.style.width = Math.max( 0, fckDlg.offsetWidth - 16 - 16 + 4 ) ; // -left -right + 4
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 20 - 2 + 4 ) ; // -bottom -top + 4
+ break ;
+
+ case 'tr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ break ;
+
+ case 'tc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 16 - 16 ) ;
+ break ;
+
+ case 'ml' :
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'mr' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 16 ) ;
+ child.style.height = Math.max( 0, fckDlg.clientHeight - 16 - 51 ) ;
+ break ;
+
+ case 'bl' :
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'br' :
+ child.style.left = Math.max( 0, fckDlg.clientWidth - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+
+ case 'bc' :
+ child.style.width = Math.max( 0, fckDlg.clientWidth - 30 - 30 ) ;
+ child.style.top = Math.max( 0, fckDlg.clientHeight - 51 ) ;
+ break ;
+ }
+ }
+ }
+
+ var closeButtonOver = function()
+ {
+ this.style.backgroundPosition = '-16px -687px' ;
+ } ;
+
+ var closeButtonOut = function()
+ {
+ this.style.backgroundPosition = '-16px -651px' ;
+ } ;
+
+ var fixCloseButton = function()
+ {
+ var closeButton = document.getElementById ( 'closeButton' ) ;
+
+ closeButton.onmouseover = closeButtonOver ;
+ closeButton.onmouseout = closeButtonOut ;
+ }
+
+ var onLoad = function()
+ {
+ fixSizes() ;
+ fixCloseButton() ;
+
+ window.attachEvent( 'onresize', fixSizes ) ;
+ window.detachEvent( 'onload', onLoad ) ;
+ }
+
+ window.attachEvent( 'onload', onLoad ) ;
+
+})() ;
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css b/httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css
new file mode 100644
index 0000000..1d4cd73
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css
@@ -0,0 +1,473 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Styles used by the editor IFRAME and Toolbar.
+ */
+
+/*
+ ### Basic Editor IFRAME Styles.
+*/
+
+body
+{
+ padding: 1px;
+ margin: 0;
+ background-color: #ffffff;
+}
+
+#xEditingArea
+{
+ border: #696969 1px solid;
+}
+
+.SourceField
+{
+ padding: 5px;
+ margin: 0px;
+ font-family: Monospace;
+}
+
+/*
+ Toolbar
+*/
+
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse
+{
+ cursor: default;
+ background-color: #f7f7f7;
+}
+
+.TB_ToolbarSet
+{
+ padding: 1px;
+ border-top: #efefde 1px outset;
+ border-bottom: #efefde 1px outset;
+}
+
+.TB_ToolbarSet TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.TB_Toolbar
+{
+ display: inline-table;
+}
+
+.TB_Separator
+{
+ width: 1px;
+ height: 21px;
+ margin: 2px;
+ background-color: #C6C3BD;
+}
+
+.TB_Start
+{
+ background-image: url(images/toolbar.start.gif);
+ margin-left: 2px;
+ margin-right: 2px;
+ width: 3px;
+ background-repeat: no-repeat;
+ height: 27px;
+ background-position: center center;
+}
+
+.TB_End
+{
+ display: none;
+}
+
+.TB_ExpandImg
+{
+ background-image: url(images/toolbar.expand.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_CollapseImg
+{
+ background-image: url(images/toolbar.collapse.gif);
+ background-repeat: no-repeat;
+}
+
+.TB_SideBorder
+{
+ background-color: #696969;
+}
+
+.TB_Expand, .TB_Collapse
+{
+ padding: 2px 2px 2px 2px;
+ border: #efefde 1px outset;
+}
+
+.TB_Collapse
+{
+ border: #efefde 1px outset;
+ width: 5px;
+}
+
+.TB_Break
+{
+ height: 27px;
+}
+
+/*
+ Toolbar Button
+*/
+
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled
+{
+ padding: 1px ;
+ margin:1px;
+ height: 21px;
+}
+
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled
+{
+ border: #cec6b5 1px solid;
+}
+
+.TB_Button_On
+{
+ border-color: #316ac5;
+ background-color: #c1d2ee;
+}
+
+.TB_Button_On_Over, .TB_Button_Off_Over
+{
+ border: #316ac5 1px solid;
+ background-color: #dff1ff;
+}
+
+.TB_Button_Off
+{
+ background: #efefef url(images/toolbar.buttonbg.gif) repeat-x;
+}
+
+.TB_Button_Off, .TB_Combo_Off
+{
+ opacity: 0.70; /* Safari, Opera and Mozilla */
+ filter: alpha(opacity=70); /* IE */
+ /* -moz-opacity: 0.70; Mozilla (Old) */
+}
+
+.TB_Button_Disabled
+{
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+ filter: gray() alpha(opacity=30); /* IE */
+}
+
+.TB_Button_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 21px;
+}
+
+.TB_Button_Image
+{
+ overflow: hidden;
+ width: 16px;
+ height: 16px;
+ margin: 3px;
+ margin-top: 4px;
+ margin-bottom: 2px;
+ background-repeat: no-repeat;
+}
+
+/* For composed button ( icon + text, icon + arrow ), we must compensate the table */
+.TB_Button_On TABLE .TB_Button_Image,
+.TB_Button_Off TABLE .TB_Button_Image,
+.TB_Button_On_Over TABLE .TB_Button_Image,
+.TB_Button_Off_Over TABLE .TB_Button_Image,
+.TB_Button_Disabled TABLE .TB_Button_Image
+{
+ margin-top: 3px;
+}
+
+.TB_Button_Image img
+{
+ position: relative;
+}
+
+.TB_ConnectionLine
+{
+ background-color: #ffffff;
+ height: 1px;
+ margin-left: 1px; /* ltr */
+ margin-right: 1px; /* rtl */
+}
+
+/*
+ Menu
+*/
+
+.MN_Menu
+{
+ border: 1px solid #8f8f73;
+ padding: 2px;
+ background-color: #f7f7f7;
+ cursor: default;
+}
+
+.MN_Menu, .MN_Menu .MN_Label
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.MN_Item_Padding
+{
+ visibility: hidden;
+ width: 3px;
+ height: 20px;
+}
+
+.MN_Icon
+{
+ background-color: #dedede;
+ text-align: center;
+ height: 20px;
+}
+
+.MN_Label
+{
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.MN_Separator
+{
+ height: 3px;
+}
+
+.MN_Separator_Line
+{
+ border-top: #b9b99d 1px solid;
+}
+
+.MN_Item .MN_Icon IMG
+{
+ filter: alpha(opacity=70);
+ opacity: 0.70;
+}
+
+.MN_Item_Over
+{
+ color: #ffffff;
+ background-color: #8a857d;
+}
+
+.MN_Item_Over .MN_Icon
+{
+ background-color: #6c6761;
+}
+
+.MN_Item_Disabled IMG
+{
+ filter: gray() alpha(opacity=30); /* IE */
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+}
+
+.MN_Item_Disabled .MN_Label
+{
+ color: #b7b7b7;
+}
+
+.MN_Arrow
+{
+ padding-right: 3px;
+ padding-left: 3px;
+}
+
+.MN_ConnectionLine
+{
+ background-color: #ffffff;
+}
+
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over
+{
+ border: #8f8f73 1px solid;
+ background-color: #ffffff;
+}
+
+/*
+ ### Panel Styles
+*/
+
+.FCK_Panel
+{
+ border: #8f8f73 1px solid;
+ padding: 2px;
+ background-color: #ffffff;
+}
+
+.FCK_Panel, .FCK_Panel TD
+{
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+ font-size: 11px;
+}
+
+/*
+ ### Special Combos
+*/
+
+.SC_Panel
+{
+ overflow: auto;
+ white-space: nowrap;
+ cursor: default;
+ border: 1px solid #8f8f73;
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.SC_Panel, .SC_Panel TD
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;
+}
+
+.SC_Item, .SC_ItemSelected
+{
+ margin-top: 2px;
+ margin-bottom: 2px;
+ background-position: left center;
+ padding-left: 11px;
+ padding-right: 3px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ background-repeat: no-repeat;
+ border: #dddddd 1px solid;
+}
+
+.SC_Item *, .SC_ItemSelected *
+{
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.SC_ItemSelected
+{
+ border: #9a9afb 1px solid;
+ background-image: url(images/toolbar.arrowright.gif);
+}
+
+.SC_ItemOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_Field
+{
+ margin-top:1px ;
+ border: #b7b7a6 1px solid;
+ cursor: default;
+}
+
+.SC_FieldCaption
+{
+ padding-top: 1px ;
+ overflow: visible;
+ padding-right: 5px;
+ padding-left: 5px;
+ opacity: 0.75; /* Safari, Opera and Mozilla */
+ filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */
+ height: 23px;
+ background-color: #f7f7f7;
+}
+
+.SC_FieldLabel
+{
+ white-space: nowrap;
+ padding: 2px;
+ width: 100%;
+ cursor: default;
+ background-color: #ffffff;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.SC_FieldButton
+{
+ background-position: center center;
+ background-image: url(images/toolbar.buttonarrow.gif);
+ border-left: #b7b7a6 1px solid;
+ width: 14px;
+ background-repeat: no-repeat;
+}
+
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption, .SC_FieldDisabled .TB_ButtonType_Text
+{
+ opacity: 0.30; /* Safari, Opera and Mozilla */
+ filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */
+}
+
+.SC_FieldOver
+{
+ border: #316ac5 1px solid;
+}
+
+.SC_FieldOver .SC_FieldButton
+{
+ border-left: #316ac5 1px solid;
+}
+
+/*
+ ### Color Selector Panel
+*/
+
+.ColorBoxBorder
+{
+ border: #808080 1px solid;
+ position: static;
+}
+
+.ColorBox
+{
+ font-size: 1px;
+ width: 10px;
+ position: static;
+ height: 10px;
+}
+
+.ColorDeselected, .ColorSelected
+{
+ cursor: default;
+}
+
+.ColorDeselected
+{
+ border: #ffffff 1px solid;
+ padding: 2px;
+ float: left;
+}
+
+.ColorSelected
+{
+ border: #316ac5 1px solid;
+ padding: 2px;
+ float: left;
+ background-color: #c1d2ee;
+}
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif b/httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif
new file mode 100644
index 0000000..a6ca532
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.gif
new file mode 100644
index 0000000..49a4d8d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.png b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.png
new file mode 100644
index 0000000..ab1ff32
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.rtl.png b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.rtl.png
new file mode 100644
index 0000000..e0a7aa7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/dialog.sides.rtl.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.gif
new file mode 100644
index 0000000..6a17ed1
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.png b/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.png
new file mode 100644
index 0000000..0ab5219
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/sprites.png
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif
new file mode 100644
index 0000000..6843c8d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif
new file mode 100644
index 0000000..ea60995
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif
new file mode 100644
index 0000000..a93ffca
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif
new file mode 100644
index 0000000..87aa56d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif
new file mode 100644
index 0000000..5bfd67a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif
new file mode 100644
index 0000000..79075e7
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif
new file mode 100644
index 0000000..eaed04a
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif
new file mode 100644
index 0000000..1774246
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif
Binary files differ
diff --git a/httemplate/elements/fckeditor/editor/wsc/ciframe.html b/httemplate/elements/fckeditor/editor/wsc/ciframe.html
new file mode 100644
index 0000000..2bf419d
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/wsc/ciframe.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+-->
+<html>
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+function gup( name )
+{
+ name = name.replace( /[\[]/, '\\\[' ).replace( /[\]]/, '\\\]' ) ;
+ var regexS = '[\\?&]' + name + '=([^&#]*)' ;
+ var regex = new RegExp( regexS ) ;
+ var results = regex.exec( window.location.href ) ;
+
+ if( results == null )
+ return '' ;
+ else
+ return results[ 1 ] ;
+}
+
+function sendData2Master()
+{
+ var destination = parent.parent ;
+ try
+ {
+ if ( destination.XDTMaster )
+ {
+ var t = destination.XDTMaster.read( [ gup( 'cmd' ), gup( 'data' ) ] ) ;
+ window.clearInterval( interval ) ;
+ }
+ }
+ catch (e) {}
+}
+
+function onLoad()
+{
+ interval = window.setInterval( sendData2Master, 100 );
+}
+
+ </script>
+</head>
+<body onload="onLoad()">
+ <p></p>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/wsc/tmpFrameset.html b/httemplate/elements/fckeditor/editor/wsc/tmpFrameset.html
new file mode 100644
index 0000000..478d226
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/wsc/tmpFrameset.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+-->
+<html>
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+function doLoadScript( url )
+{
+ if ( !url )
+ return false ;
+
+ var s = document.createElement( "script" ) ;
+ s.type = "text/javascript" ;
+ s.src = url ;
+ document.getElementsByTagName( "head" )[ 0 ].appendChild( s ) ;
+
+ return true ;
+}
+
+function tryLoad ()
+{
+ if ( typeof( opener ) == 'undefined' || !opener )
+ opener = parent ;
+
+ // get access to global parameters
+ oParams = opener.oldFramesetPageParams ;
+
+ // make frameset rows string prepare
+ sFramesetRows = ( parseInt( oParams.firstframeh, 10 ) || '30') + ",*," + ( parseInt( oParams.thirdframeh, 10 ) || '150' ) + ',0' ;
+ document.getElementById( 'itFrameset' ).rows = sFramesetRows ;
+
+ // dynamic including init frames and crossdomain transport code
+ // from config sproxy_js_frameset url
+ var addScriptUrl = oParams.sproxy_js_frameset ;
+ doLoadScript( addScriptUrl ) ;
+}
+
+ </script>
+</head>
+<frameset id="itFrameset" onload="tryLoad();" border="0" rows="30,*,*,0">
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="0" marginwidth="2" src="" name="navbar"></frame>
+ <frame scrolling="auto" framespacing="0" frameborder="0" noresize="noresize" marginheight="0" marginwidth="0" src="" name="mid"></frame>
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="1" marginwidth="1" src="" name="bot"></frame>
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="1" marginwidth="1" src="" name="spellsuggestall"></frame>
+</frameset>
+</html>
diff --git a/httemplate/elements/fckeditor/editor/wsc/w.html b/httemplate/elements/fckeditor/editor/wsc/w.html
new file mode 100644
index 0000000..df327a6
--- /dev/null
+++ b/httemplate/elements/fckeditor/editor/wsc/w.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+-->
+<html>
+<head>
+ <title></title>
+ <style>
+ #wsc_frames , #errorMessage{
+ position:absolute;
+ top:0px;
+ left:0px;
+ width:500px;
+ height:395px;
+ margin:0px;
+ padding:0px;
+ border:0px;
+ display:block;
+ overflow: hidden;
+ }
+ #wsc_frames { z-index:10;}
+ #errorMessage {
+ color:red;
+ display:none;
+ font-size:16px;
+ font-weight:bold;
+ padding-top:160px;
+ text-align:center;
+ z-index:11;
+ }
+ #errorMessage p {
+ color:#000;
+ font-size:11px;
+ text-align:left;
+ font-weight: normal;
+ padding-left:80px;
+ }
+
+ </style>
+ <script type="text/javascript">
+
+var oEditor = window.parent.InnerDialogLoaded() ;
+var FCKConfig = oEditor.FCKConfig;
+
+function doLoadScript(url)
+{
+ if (!url)
+ return false ;
+
+ var s = document.createElement('script') ;
+ s.type = 'text/javascript' ;
+ s.src = url ;
+
+ document.getElementsByTagName('head')[0].appendChild(s) ;
+
+ return true ;
+}
+
+function Ok()
+{
+ return window.parent.Cancel() ;
+}
+
+function _callOnCancel( dT )
+{
+ window.parent.Cancel() ;
+}
+
+function _callOnFinish( dT )
+{
+ oEditor.FCK.SetData( dT.value ) ;
+ window.parent.CloseDialog( true ) ;
+}
+
+function _cancelOnError(m)
+{
+ var _conId = 'errorMessage' ;
+ var message = m || 'Sorry, but service is unavailable now.' ;
+
+ if ( typeof( WSC_Error ) == 'undefined' )
+ {
+ var _con = document.createElement( 'div' ) ;
+ _con.setAttribute( 'id', _conId ) ;
+ document.body.appendChild( _con ) ;
+ dom_con = document.getElementById( _conId ) ;
+ dom_con.innerHTML = message ;
+ dom_con.style.display = 'block' ;
+ }
+ //return Ok() ;
+}
+
+function URL_abs2full( uri )
+{
+ return uri.match( 'http' )
+ ? uri
+ : document.location.protocol + '//' + document.location.host + uri ;
+}
+
+function clearErrorUsermessage()
+{
+ // empty error container
+ var _con = document.getElementById( 'errorMessage' ) ;
+
+ if ( !_con )
+ return ;
+
+ _con.innerHTML = '' ;
+ _con.style.display = 'none' ;
+}
+
+var gInterval ;
+
+function onLoad()
+{
+ clearErrorUsermessage() ;
+ var _errorMessage = 'The SpellChecker Service is currently unavailable.' ;
+ if ( 'undefined' != typeof( oEditor.FCK.Config.WSChLoaderScript ) )
+ _errorMessage = '<div>The SpellChecker Service is currently unavailable.</div><p>Error loading application<br>service host: ' + oEditor.FCK.Config.WSChLoaderScript + '</p>';
+
+ var burnSpelling = function( oName, _eMessage )
+ {
+ var i = 0 ;
+
+ return function ()
+ {
+ if ( typeof( window[oName] ) == 'function' )
+ initAndSpell() ;
+ else if ( i++ == 180 )
+ _cancelOnError( _eMessage ) ;
+ }
+ }
+
+ gInterval = window.setInterval( burnSpelling( 'doSpell', _errorMessage ), 250 ) ;
+
+ // WSC CORE init section
+ var protocol = document.location.protocol || 'http:' ;
+ var baseUrl = protocol + '//loader.spellchecker.net/sproxy_fck/sproxy.php' ;
+ var plugin = "fck2" ;
+ var customerid = oEditor.FCK.Config.WSCnCustomerId
+ || "1:ua3xw1-2XyGJ3-GWruD3-6OFNT1-oXcuB1-nR6Bp4-hgQHc-EcYng3-sdRXG3-NOfFk" ;
+ var wscCoreUrl = oEditor.FCK.Config.WSChLoaderScript
+ || ( baseUrl + '?'
+ + 'plugin=' + plugin + '&'
+ + 'customerid='+ customerid + '&'
+ + 'cmd=script&doc=wsc&schema=22' ) ;
+
+ // load WSC core
+ doLoadScript( wscCoreUrl ) ;
+}
+
+function initAndSpell()
+{
+ //xall from window.setInteval expected at once
+ if ( typeof( gInterval ) == 'undefined' )
+ return null ;
+ window.clearInterval( gInterval ) ;
+
+ // global var is used in FCK specific core
+ // change on equal var used in fckplugin.js
+ gFCKPluginName = 'wsc' ;
+
+ // get the data to be checked
+ var sData = oEditor.FCK.GetData() ;
+
+ // prepare content
+ var ctrlId = 'myEditor' ;
+ var dCurT = document.getElementById( ctrlId ) ;
+ dCurT.value = sData ;
+
+ // service paths corecting/preparing
+ var sPath2Scin = URL_abs2full( oEditor.FCK.Config.SkinDialogCSS ) ;
+ var sPathCiframe = FCKConfig.BasePath + 'wsc/ciframe.html' ;
+ var sPathFrameset = FCKConfig.BasePath + 'wsc/tmpFrameset.html' ;
+
+ // language abbr standarts comparer
+ var LangComparer = new _SP_FCK_LangCompare() ;
+ LangComparer.setDefaulLangCode( oEditor.FCK.Language.DefaultLanguage ) ;
+
+ // clear user message console (if application was loaded more then after 2 seconds)
+ clearErrorUsermessage() ;
+
+ doSpell( {
+ ctrl : ctrlId,
+ lang : LangComparer.getSPLangCode( oEditor.FCK.Language.GetActiveLanguage() ),
+ winType : 'wsc_frames',// if not defined app will run on winpopup
+
+ // callback binding section
+ onCancel :window._callOnCancel,
+ onFinish :window._callOnFinish,
+
+ // @TODO: basePath assingning
+
+ // some manipulations with client static pages
+ framesetPath : sPathFrameset,
+ iframePath : sPathCiframe,
+
+ // styles defining
+ schemaURI : sPath2Scin
+ } ) ;
+
+ return true ;
+}
+
+ </script>
+</head>
+<body onload="onLoad()" style="padding: 0px; overflow: hidden;">
+ <textarea style="display: none;" id="myEditor" rows="10" cols="40"></textarea>
+ <iframe src="" name="wsc_frames" id="wsc_frames"></iframe>
+</body>
+</html>
diff --git a/httemplate/elements/fckeditor/fckconfig.js b/httemplate/elements/fckeditor/fckconfig.js
new file mode 100644
index 0000000..c35b37a
--- /dev/null
+++ b/httemplate/elements/fckeditor/fckconfig.js
@@ -0,0 +1,325 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Editor configuration settings.
+ *
+ * Follow this link for more information:
+ * http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/Configuration/Configuration_Options
+ */
+
+FCKConfig.CustomConfigurationsPath = '' ;
+
+FCKConfig.EditorAreaCSS = FCKConfig.BasePath + 'css/fck_editorarea.css' ;
+FCKConfig.EditorAreaStyles = '' ;
+FCKConfig.ToolbarComboPreviewCSS = '' ;
+
+FCKConfig.DocType = '' ;
+
+FCKConfig.BaseHref = '' ;
+
+FCKConfig.FullPage = false ;
+
+// The following option determines whether the "Show Blocks" feature is enabled or not at startup.
+FCKConfig.StartupShowBlocks = false ;
+
+FCKConfig.Debug = false ;
+FCKConfig.AllowQueryStringDebug = true ;
+
+FCKConfig.SkinPath = FCKConfig.BasePath + 'skins/default/' ;
+FCKConfig.SkinEditorCSS = '' ; // FCKConfig.SkinPath + "|<minified css>" ;
+FCKConfig.SkinDialogCSS = '' ; // FCKConfig.SkinPath + "|<minified css>" ;
+
+FCKConfig.PreloadImages = [ FCKConfig.SkinPath + 'images/toolbar.start.gif', FCKConfig.SkinPath + 'images/toolbar.buttonarrow.gif' ] ;
+
+FCKConfig.PluginsPath = FCKConfig.BasePath + 'plugins/' ;
+
+// FCKConfig.Plugins.Add( 'autogrow' ) ;
+// FCKConfig.Plugins.Add( 'dragresizetable' );
+FCKConfig.AutoGrowMax = 400 ;
+
+// FCKConfig.ProtectedSource.Add( /<%[\s\S]*?%>/g ) ; // ASP style server side code <%...%>
+// FCKConfig.ProtectedSource.Add( /<\?[\s\S]*?\?>/g ) ; // PHP style server side code
+// FCKConfig.ProtectedSource.Add( /(<asp:[^\>]+>[\s|\S]*?<\/asp:[^\>]+>)|(<asp:[^\>]+\/>)/gi ) ; // ASP.Net style tags <asp:control>
+
+FCKConfig.AutoDetectLanguage = true ;
+FCKConfig.DefaultLanguage = 'en' ;
+FCKConfig.ContentLangDirection = 'ltr' ;
+
+FCKConfig.ProcessHTMLEntities = true ;
+FCKConfig.IncludeLatinEntities = true ;
+FCKConfig.IncludeGreekEntities = true ;
+
+FCKConfig.ProcessNumericEntities = false ;
+
+FCKConfig.AdditionalNumericEntities = '' ; // Single Quote: "'"
+
+FCKConfig.FillEmptyBlocks = true ;
+
+FCKConfig.FormatSource = true ;
+FCKConfig.FormatOutput = true ;
+FCKConfig.FormatIndentator = ' ' ;
+
+FCKConfig.EMailProtection = 'none' ; // none | encode | function
+FCKConfig.EMailProtectionFunction = 'mt(NAME,DOMAIN,SUBJECT,BODY)' ;
+
+FCKConfig.StartupFocus = false ;
+FCKConfig.ForcePasteAsPlainText = false ;
+FCKConfig.AutoDetectPasteFromWord = true ; // IE only.
+FCKConfig.ShowDropDialog = true ;
+FCKConfig.ForceSimpleAmpersand = false ;
+FCKConfig.TabSpaces = 0 ;
+FCKConfig.ShowBorders = true ;
+FCKConfig.SourcePopup = false ;
+FCKConfig.ToolbarStartExpanded = true ;
+FCKConfig.ToolbarCanCollapse = true ;
+FCKConfig.IgnoreEmptyParagraphValue = true ;
+FCKConfig.FloatingPanelsZIndex = 10000 ;
+FCKConfig.HtmlEncodeOutput = false ;
+
+FCKConfig.TemplateReplaceAll = true ;
+FCKConfig.TemplateReplaceCheckbox = true ;
+
+FCKConfig.ToolbarLocation = 'In' ;
+
+FCKConfig.ToolbarSets["Default"] = [
+ ['Source','DocProps','-','Save','Preview','-'],
+ ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
+ ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
+ // ['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
+ '/',
+ ['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
+ ['OrderedList','UnorderedList','-','Outdent','Indent','Blockquote'],
+ ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
+ ['Link','Unlink','Anchor'],
+ ['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],
+ '/',
+ ['Style','FontFormat','FontName','FontSize'],
+ ['TextColor','BGColor'],
+ ['FitWindow','ShowBlocks','-','About'] // No comma for the last row.
+] ;
+
+FCKConfig.ToolbarSets["Basic"] = [
+ ['Bold','Italic','-','OrderedList','UnorderedList','-','Link','Unlink','-','About']
+] ;
+
+FCKConfig.EnterMode = 'p' ; // p | div | br
+FCKConfig.ShiftEnterMode = 'br' ; // p | div | br
+
+FCKConfig.Keystrokes = [
+ [ CTRL + 65 /*A*/, true ],
+ [ CTRL + 67 /*C*/, true ],
+ [ CTRL + 70 /*F*/, true ],
+ [ CTRL + 83 /*S*/, true ],
+ [ CTRL + 84 /*T*/, true ],
+ [ CTRL + 88 /*X*/, true ],
+ [ CTRL + 86 /*V*/, 'Paste' ],
+ [ CTRL + 45 /*INS*/, true ],
+ [ SHIFT + 45 /*INS*/, 'Paste' ],
+ [ CTRL + 88 /*X*/, 'Cut' ],
+ [ SHIFT + 46 /*DEL*/, 'Cut' ],
+ [ CTRL + 90 /*Z*/, 'Undo' ],
+ [ CTRL + 89 /*Y*/, 'Redo' ],
+ [ CTRL + SHIFT + 90 /*Z*/, 'Redo' ],
+ [ CTRL + 76 /*L*/, 'Link' ],
+ [ CTRL + 66 /*B*/, 'Bold' ],
+ [ CTRL + 73 /*I*/, 'Italic' ],
+ [ CTRL + 85 /*U*/, 'Underline' ],
+ [ CTRL + SHIFT + 83 /*S*/, 'Save' ],
+ [ CTRL + ALT + 13 /*ENTER*/, 'FitWindow' ],
+ [ SHIFT + 32 /*SPACE*/, 'Nbsp' ]
+] ;
+
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','BulletedList','NumberedList','Table','Form','DivContainer'] ;
+FCKConfig.BrowserContextMenuOnCtrl = false ;
+FCKConfig.BrowserContextMenu = false ;
+
+FCKConfig.EnableMoreFontColors = true ;
+FCKConfig.FontColors = '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,808080,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF' ;
+
+FCKConfig.FontFormats = 'p;h1;h2;h3;h4;h5;h6;pre;address;div' ;
+FCKConfig.FontNames = 'Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;
+FCKConfig.FontSizes = 'smaller;larger;xx-small;x-small;small;medium;large;x-large;xx-large' ;
+
+FCKConfig.StylesXmlPath = FCKConfig.EditorPath + 'fckstyles.xml' ;
+FCKConfig.TemplatesXmlPath = FCKConfig.EditorPath + 'fcktemplates.xml' ;
+
+FCKConfig.SpellChecker = 'WSC' ; // 'WSC' | 'SCAYT' | 'SpellerPages' | 'ieSpell'
+FCKConfig.IeSpellDownloadUrl = 'http://www.iespell.com/download.php' ;
+FCKConfig.SpellerPagesServerScript = 'server-scripts/spellchecker.php' ; // Available extension: .php .cfm .pl
+FCKConfig.FirefoxSpellChecker = false ;
+
+FCKConfig.MaxUndoLevels = 15 ;
+
+FCKConfig.DisableObjectResizing = false ;
+FCKConfig.DisableFFTableHandles = true ;
+
+FCKConfig.LinkDlgHideTarget = false ;
+FCKConfig.LinkDlgHideAdvanced = false ;
+
+FCKConfig.ImageDlgHideLink = false ;
+FCKConfig.ImageDlgHideAdvanced = false ;
+
+FCKConfig.FlashDlgHideAdvanced = false ;
+
+FCKConfig.ProtectedTags = '' ;
+
+// This will be applied to the body element of the editor
+FCKConfig.BodyId = '' ;
+FCKConfig.BodyClass = '' ;
+
+FCKConfig.DefaultStyleLabel = '' ;
+FCKConfig.DefaultFontFormatLabel = '' ;
+FCKConfig.DefaultFontLabel = '' ;
+FCKConfig.DefaultFontSizeLabel = '' ;
+
+FCKConfig.DefaultLinkTarget = '' ;
+
+// The option switches between trying to keep the html structure or do the changes so the content looks like it was in Word
+FCKConfig.CleanWordKeepsStructure = false ;
+
+// Only inline elements are valid.
+FCKConfig.RemoveFormatTags = 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var' ;
+
+// Attributes that will be removed
+FCKConfig.RemoveAttributes = 'class,style,lang,width,height,align,hspace,valign' ;
+
+FCKConfig.CustomStyles =
+{
+ 'Red Title' : { Element : 'h3', Styles : { 'color' : 'Red' } }
+};
+
+// Do not add, rename or remove styles here. Only apply definition changes.
+FCKConfig.CoreStyles =
+{
+ // Basic Inline Styles.
+ 'Bold' : { Element : 'strong', Overrides : 'b' },
+ 'Italic' : { Element : 'em', Overrides : 'i' },
+ 'Underline' : { Element : 'u' },
+ 'StrikeThrough' : { Element : 'strike' },
+ 'Subscript' : { Element : 'sub' },
+ 'Superscript' : { Element : 'sup' },
+
+ // Basic Block Styles (Font Format Combo).
+ 'p' : { Element : 'p' },
+ 'div' : { Element : 'div' },
+ 'pre' : { Element : 'pre' },
+ 'address' : { Element : 'address' },
+ 'h1' : { Element : 'h1' },
+ 'h2' : { Element : 'h2' },
+ 'h3' : { Element : 'h3' },
+ 'h4' : { Element : 'h4' },
+ 'h5' : { Element : 'h5' },
+ 'h6' : { Element : 'h6' },
+
+ // Other formatting features.
+ 'FontFace' :
+ {
+ Element : 'span',
+ Styles : { 'font-family' : '#("Font")' },
+ Overrides : [ { Element : 'font', Attributes : { 'face' : null } } ]
+ },
+
+ 'Size' :
+ {
+ Element : 'span',
+ Styles : { 'font-size' : '#("Size","fontSize")' },
+ Overrides : [ { Element : 'font', Attributes : { 'size' : null } } ]
+ },
+
+ 'Color' :
+ {
+ Element : 'span',
+ Styles : { 'color' : '#("Color","color")' },
+ Overrides : [ { Element : 'font', Attributes : { 'color' : null } } ]
+ },
+
+ 'BackColor' : { Element : 'span', Styles : { 'background-color' : '#("Color","color")' } },
+
+ 'SelectionHighlight' : { Element : 'span', Styles : { 'background-color' : 'navy', 'color' : 'white' } }
+};
+
+// The distance of an indentation step.
+FCKConfig.IndentLength = 40 ;
+FCKConfig.IndentUnit = 'px' ;
+
+// Alternatively, FCKeditor allows the use of CSS classes for block indentation.
+// This overrides the IndentLength/IndentUnit settings.
+FCKConfig.IndentClasses = [] ;
+
+// [ Left, Center, Right, Justified ]
+FCKConfig.JustifyClasses = [] ;
+
+// The following value defines which File Browser connector and Quick Upload
+// "uploader" to use. It is valid for the default implementaion and it is here
+// just to make this configuration file cleaner.
+// It is not possible to change this value using an external file or even
+// inline when creating the editor instance. In that cases you must set the
+// values of LinkBrowserURL, ImageBrowserURL and so on.
+// Custom implementations should just ignore it.
+var _FileBrowserLanguage = 'php' ; // asp | aspx | cfm | lasso | perl | php | py
+var _QuickUploadLanguage = 'php' ; // asp | aspx | cfm | lasso | perl | php | py
+
+// Don't care about the following two lines. It just calculates the correct connector
+// extension to use for the default File Browser (Perl uses "cgi").
+var _FileBrowserExtension = _FileBrowserLanguage == 'perl' ? 'cgi' : _FileBrowserLanguage ;
+var _QuickUploadExtension = _QuickUploadLanguage == 'perl' ? 'cgi' : _QuickUploadLanguage ;
+
+FCKConfig.LinkBrowser = true ;
+FCKConfig.LinkBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Connector=' + encodeURIComponent( FCKConfig.BasePath + 'filemanager/connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ) ;
+FCKConfig.LinkBrowserWindowWidth = FCKConfig.ScreenWidth * 0.7 ; // 70%
+FCKConfig.LinkBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ; // 70%
+
+FCKConfig.ImageBrowser = true ;
+FCKConfig.ImageBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Image&Connector=' + encodeURIComponent( FCKConfig.BasePath + 'filemanager/connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ) ;
+FCKConfig.ImageBrowserWindowWidth = FCKConfig.ScreenWidth * 0.7 ; // 70% ;
+FCKConfig.ImageBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ; // 70% ;
+
+FCKConfig.FlashBrowser = true ;
+FCKConfig.FlashBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Flash&Connector=' + encodeURIComponent( FCKConfig.BasePath + 'filemanager/connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ) ;
+FCKConfig.FlashBrowserWindowWidth = FCKConfig.ScreenWidth * 0.7 ; //70% ;
+FCKConfig.FlashBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ; //70% ;
+
+FCKConfig.LinkUpload = true ;
+FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/connectors/' + _QuickUploadLanguage + '/upload.' + _QuickUploadExtension ;
+FCKConfig.LinkUploadAllowedExtensions = ".(7z|aiff|asf|avi|bmp|csv|doc|fla|flv|gif|gz|gzip|jpeg|jpg|mid|mov|mp3|mp4|mpc|mpeg|mpg|ods|odt|pdf|png|ppt|pxd|qt|ram|rar|rm|rmi|rmvb|rtf|sdc|sitd|swf|sxc|sxw|tar|tgz|tif|tiff|txt|vsd|wav|wma|wmv|xls|xml|zip)$" ; // empty for all
+FCKConfig.LinkUploadDeniedExtensions = "" ; // empty for no one
+
+FCKConfig.ImageUpload = true ;
+FCKConfig.ImageUploadURL = FCKConfig.BasePath + 'filemanager/connectors/' + _QuickUploadLanguage + '/upload.' + _QuickUploadExtension + '?Type=Image' ;
+FCKConfig.ImageUploadAllowedExtensions = ".(jpg|gif|jpeg|png|bmp)$" ; // empty for all
+FCKConfig.ImageUploadDeniedExtensions = "" ; // empty for no one
+
+FCKConfig.FlashUpload = true ;
+FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/connectors/' + _QuickUploadLanguage + '/upload.' + _QuickUploadExtension + '?Type=Flash' ;
+FCKConfig.FlashUploadAllowedExtensions = ".(swf|flv)$" ; // empty for all
+FCKConfig.FlashUploadDeniedExtensions = "" ; // empty for no one
+
+FCKConfig.SmileyPath = FCKConfig.BasePath + 'images/smiley/msn/' ;
+FCKConfig.SmileyImages = ['regular_smile.gif','sad_smile.gif','wink_smile.gif','teeth_smile.gif','confused_smile.gif','tounge_smile.gif','embaressed_smile.gif','omg_smile.gif','whatchutalkingabout_smile.gif','angry_smile.gif','angel_smile.gif','shades_smile.gif','devil_smile.gif','cry_smile.gif','lightbulb.gif','thumbs_down.gif','thumbs_up.gif','heart.gif','broken_heart.gif','kiss.gif','envelope.gif'] ;
+FCKConfig.SmileyColumns = 8 ;
+FCKConfig.SmileyWindowWidth = 320 ;
+FCKConfig.SmileyWindowHeight = 210 ;
+
+FCKConfig.BackgroundBlockerColor = '#ffffff' ;
+FCKConfig.BackgroundBlockerOpacity = 0.50 ;
+
+FCKConfig.MsWebBrowserControlCompat = false ;
+
+FCKConfig.PreventSubmitHandler = false ;
diff --git a/httemplate/elements/fckeditor/fckeditor.js b/httemplate/elements/fckeditor/fckeditor.js
new file mode 100644
index 0000000..8e0126b
--- /dev/null
+++ b/httemplate/elements/fckeditor/fckeditor.js
@@ -0,0 +1,330 @@
+/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the integration file for JavaScript.
+ *
+ * It defines the FCKeditor class that can be used to create editor
+ * instances in a HTML page in the client side. For server side
+ * operations, use the specific integration system.
+ */
+
+// FCKeditor Class
+var FCKeditor = function( instanceName, width, height, toolbarSet, value )
+{
+ // Properties
+ this.InstanceName = instanceName ;
+ this.Width = width || '100%' ;
+ this.Height = height || '200' ;
+ this.ToolbarSet = toolbarSet || 'Default' ;
+ this.Value = value || '' ;
+ this.BasePath = FCKeditor.BasePath ;
+ this.CheckBrowser = true ;
+ this.DisplayErrors = true ;
+
+ this.Config = new Object() ;
+
+ // Events
+ this.OnError = null ; // function( source, errorNumber, errorDescription )
+}
+
+/**
+ * This is the default BasePath used by all editor instances.
+ */
+FCKeditor.BasePath = '/fckeditor/' ;
+
+/**
+ * The minimum height used when replacing textareas.
+ */
+FCKeditor.MinHeight = 200 ;
+
+/**
+ * The minimum width used when replacing textareas.
+ */
+FCKeditor.MinWidth = 750 ;
+
+FCKeditor.prototype.Version = '2.6.6' ;
+FCKeditor.prototype.VersionBuild = '25427' ;
+
+FCKeditor.prototype.Create = function()
+{
+ document.write( this.CreateHtml() ) ;
+}
+
+FCKeditor.prototype.CreateHtml = function()
+{
+ // Check for errors
+ if ( !this.InstanceName || this.InstanceName.length == 0 )
+ {
+ this._ThrowError( 701, 'You must specify an instance name.' ) ;
+ return '' ;
+ }
+
+ var sHtml = '' ;
+
+ if ( !this.CheckBrowser || this._IsCompatibleBrowser() )
+ {
+ sHtml += '<input type="hidden" id="' + this.InstanceName + '" name="' + this.InstanceName + '" value="' + this._HTMLEncode( this.Value ) + '" style="display:none" />' ;
+ sHtml += this._GetConfigHtml() ;
+ sHtml += this._GetIFrameHtml() ;
+ }
+ else
+ {
+ var sWidth = this.Width.toString().indexOf('%') > 0 ? this.Width : this.Width + 'px' ;
+ var sHeight = this.Height.toString().indexOf('%') > 0 ? this.Height : this.Height + 'px' ;
+
+ sHtml += '<textarea name="' + this.InstanceName +
+ '" rows="4" cols="40" style="width:' + sWidth +
+ ';height:' + sHeight ;
+
+ if ( this.TabIndex )
+ sHtml += '" tabindex="' + this.TabIndex ;
+
+ sHtml += '">' +
+ this._HTMLEncode( this.Value ) +
+ '<\/textarea>' ;
+ }
+
+ return sHtml ;
+}
+
+FCKeditor.prototype.ReplaceTextarea = function()
+{
+ if ( document.getElementById( this.InstanceName + '___Frame' ) )
+ return ;
+ if ( !this.CheckBrowser || this._IsCompatibleBrowser() )
+ {
+ // We must check the elements firstly using the Id and then the name.
+ var oTextarea = document.getElementById( this.InstanceName ) ;
+ var colElementsByName = document.getElementsByName( this.InstanceName ) ;
+ var i = 0;
+ while ( oTextarea || i == 0 )
+ {
+ if ( oTextarea && oTextarea.tagName.toLowerCase() == 'textarea' )
+ break ;
+ oTextarea = colElementsByName[i++] ;
+ }
+
+ if ( !oTextarea )
+ {
+ alert( 'Error: The TEXTAREA with id or name set to "' + this.InstanceName + '" was not found' ) ;
+ return ;
+ }
+
+ oTextarea.style.display = 'none' ;
+
+ if ( oTextarea.tabIndex )
+ this.TabIndex = oTextarea.tabIndex ;
+
+ this._InsertHtmlBefore( this._GetConfigHtml(), oTextarea ) ;
+ this._InsertHtmlBefore( this._GetIFrameHtml(), oTextarea ) ;
+ }
+}
+
+FCKeditor.prototype._InsertHtmlBefore = function( html, element )
+{
+ if ( element.insertAdjacentHTML ) // IE
+ element.insertAdjacentHTML( 'beforeBegin', html ) ;
+ else // Gecko
+ {
+ var oRange = document.createRange() ;
+ oRange.setStartBefore( element ) ;
+ var oFragment = oRange.createContextualFragment( html );
+ element.parentNode.insertBefore( oFragment, element ) ;
+ }
+}
+
+FCKeditor.prototype._GetConfigHtml = function()
+{
+ var sConfig = '' ;
+ for ( var o in this.Config )
+ {
+ if ( sConfig.length > 0 ) sConfig += '&amp;' ;
+ sConfig += encodeURIComponent( o ) + '=' + encodeURIComponent( this.Config[o] ) ;
+ }
+
+ return '<input type="hidden" id="' + this.InstanceName + '___Config" value="' + sConfig + '" style="display:none" />' ;
+}
+
+FCKeditor.prototype._GetIFrameHtml = function()
+{
+ var sFile = 'fckeditor.html' ;
+
+ try
+ {
+ if ( (/fcksource=true/i).test( window.top.location.search ) )
+ sFile = 'fckeditor.original.html' ;
+ }
+ catch (e) { /* Ignore it. Much probably we are inside a FRAME where the "top" is in another domain (security error). */ }
+
+ var sLink = this.BasePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent( this.InstanceName ) ;
+ if (this.ToolbarSet)
+ sLink += '&amp;Toolbar=' + this.ToolbarSet ;
+
+ var html = '<iframe id="' + this.InstanceName +
+ '___Frame" src="' + sLink +
+ '" width="' + this.Width +
+ '" height="' + this.Height ;
+
+ if ( this.TabIndex )
+ html += '" tabindex="' + this.TabIndex ;
+
+ html += '" frameborder="0" scrolling="no"></iframe>' ;
+
+ return html ;
+}
+
+FCKeditor.prototype._IsCompatibleBrowser = function()
+{
+ return FCKeditor_IsCompatibleBrowser() ;
+}
+
+FCKeditor.prototype._ThrowError = function( errorNumber, errorDescription )
+{
+ this.ErrorNumber = errorNumber ;
+ this.ErrorDescription = errorDescription ;
+
+ if ( this.DisplayErrors )
+ {
+ document.write( '<div style="COLOR: #ff0000">' ) ;
+ document.write( '[ FCKeditor Error ' + this.ErrorNumber + ': ' + this.ErrorDescription + ' ]' ) ;
+ document.write( '</div>' ) ;
+ }
+
+ if ( typeof( this.OnError ) == 'function' )
+ this.OnError( this, errorNumber, errorDescription ) ;
+}
+
+FCKeditor.prototype._HTMLEncode = function( text )
+{
+ if ( typeof( text ) != "string" )
+ text = text.toString() ;
+
+ text = text.replace(
+ /&/g, "&amp;").replace(
+ /"/g, "&quot;").replace(
+ /</g, "&lt;").replace(
+ />/g, "&gt;") ;
+
+ return text ;
+}
+
+;(function()
+{
+ var textareaToEditor = function( textarea )
+ {
+ var editor = new FCKeditor( textarea.name ) ;
+
+ editor.Width = Math.max( textarea.offsetWidth, FCKeditor.MinWidth ) ;
+ editor.Height = Math.max( textarea.offsetHeight, FCKeditor.MinHeight ) ;
+
+ return editor ;
+ }
+
+ /**
+ * Replace all <textarea> elements available in the document with FCKeditor
+ * instances.
+ *
+ * // Replace all <textarea> elements in the page.
+ * FCKeditor.ReplaceAllTextareas() ;
+ *
+ * // Replace all <textarea class="myClassName"> elements in the page.
+ * FCKeditor.ReplaceAllTextareas( 'myClassName' ) ;
+ *
+ * // Selectively replace <textarea> elements, based on custom assertions.
+ * FCKeditor.ReplaceAllTextareas( function( textarea, editor )
+ * {
+ * // Custom code to evaluate the replace, returning false if it
+ * // must not be done.
+ * // It also passes the "editor" parameter, so the developer can
+ * // customize the instance.
+ * } ) ;
+ */
+ FCKeditor.ReplaceAllTextareas = function()
+ {
+ var textareas = document.getElementsByTagName( 'textarea' ) ;
+
+ for ( var i = 0 ; i < textareas.length ; i++ )
+ {
+ var editor = null ;
+ var textarea = textareas[i] ;
+ var name = textarea.name ;
+
+ // The "name" attribute must exist.
+ if ( !name || name.length == 0 )
+ continue ;
+
+ if ( typeof arguments[0] == 'string' )
+ {
+ // The textarea class name could be passed as the function
+ // parameter.
+
+ var classRegex = new RegExp( '(?:^| )' + arguments[0] + '(?:$| )' ) ;
+
+ if ( !classRegex.test( textarea.className ) )
+ continue ;
+ }
+ else if ( typeof arguments[0] == 'function' )
+ {
+ // An assertion function could be passed as the function parameter.
+ // It must explicitly return "false" to ignore a specific <textarea>.
+ editor = textareaToEditor( textarea ) ;
+ if ( arguments[0]( textarea, editor ) === false )
+ continue ;
+ }
+
+ if ( !editor )
+ editor = textareaToEditor( textarea ) ;
+
+ editor.ReplaceTextarea() ;
+ }
+ }
+})() ;
+
+function FCKeditor_IsCompatibleBrowser()
+{
+ var sAgent = navigator.userAgent.toLowerCase() ;
+
+ // Internet Explorer 5.5+
+ if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1 )
+ {
+ var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
+ return ( sBrowserVersion >= 5.5 ) ;
+ }
+
+ // Gecko (Opera 9 tries to behave like Gecko at this point).
+ if ( navigator.product == "Gecko" && navigator.productSub >= 20030210 && !( typeof(opera) == 'object' && opera.postError ) )
+ return true ;
+
+ // Opera 9.50+
+ if ( window.opera && window.opera.version && parseFloat( window.opera.version() ) >= 9.5 )
+ return true ;
+
+ // Adobe AIR
+ // Checked before Safari because AIR have the WebKit rich text editor
+ // features from Safari 3.0.4, but the version reported is 420.
+ if ( sAgent.indexOf( ' adobeair/' ) != -1 )
+ return ( sAgent.match( / adobeair\/(\d+)/ )[1] >= 1 ) ; // Build must be at least v1
+
+ // Safari 3+
+ if ( sAgent.indexOf( ' applewebkit/' ) != -1 )
+ return ( sAgent.match( / applewebkit\/(\d+)/ )[1] >= 522 ) ; // Build must be at least 522 (v3)
+
+ return false ;
+}
diff --git a/httemplate/elements/fckeditor/fckpackager.xml b/httemplate/elements/fckeditor/fckpackager.xml
new file mode 100644
index 0000000..2d14502
--- /dev/null
+++ b/httemplate/elements/fckeditor/fckpackager.xml
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the configuration file to be used with FCKpackager to generate the
+ * compressed code files in the "js" folder.
+ *
+ * Please check http://www.fckeditor.net for more info.
+-->
+<Package>
+ <Header><![CDATA[/*
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This file has been compressed for better performance. The original source
+ * can be found at "editor/_source".
+ */
+]]></Header>
+ <Constants removeDeclaration="false">
+ <Constant name="FCK_STATUS_NOTLOADED" value="0" />
+ <Constant name="FCK_STATUS_ACTIVE" value="1" />
+ <Constant name="FCK_STATUS_COMPLETE" value="2" />
+ <Constant name="FCK_TRISTATE_OFF" value="0" />
+ <Constant name="FCK_TRISTATE_ON" value="1" />
+ <Constant name="FCK_TRISTATE_DISABLED" value="-1" />
+ <Constant name="FCK_UNKNOWN" value="-9" />
+ <Constant name="FCK_TOOLBARITEM_ONLYICON" value="0" />
+ <Constant name="FCK_TOOLBARITEM_ONLYTEXT" value="1" />
+ <Constant name="FCK_TOOLBARITEM_ICONTEXT" value="2" />
+ <Constant name="FCK_EDITMODE_WYSIWYG" value="0" />
+ <Constant name="FCK_EDITMODE_SOURCE" value="1" />
+ <Constant name="FCK_STYLE_BLOCK" value="0" />
+ <Constant name="FCK_STYLE_INLINE" value="1" />
+ <Constant name="FCK_STYLE_OBJECT" value="2" />
+ </Constants>
+ <PackageFile path="editor/js/fckeditorcode_ie.js">
+ <File path="editor/_source/fckconstants.js" />
+ <File path="editor/_source/fckjscoreextensions.js" />
+ <File path="editor/_source/classes/fckiecleanup.js" />
+ <File path="editor/_source/internals/fckbrowserinfo.js" />
+ <File path="editor/_source/internals/fckurlparams.js" />
+ <File path="editor/_source/classes/fckevents.js" />
+ <File path="editor/_source/classes/fckdataprocessor.js" />
+ <File path="editor/_source/internals/fck.js" />
+ <File path="editor/_source/internals/fck_ie.js" />
+ <File path="editor/_source/internals/fckconfig.js" />
+ <File path="editor/_source/internals/fckdebug_empty.js" />
+ <File path="editor/_source/internals/fckdomtools.js" />
+ <File path="editor/_source/internals/fcktools.js" />
+ <File path="editor/_source/internals/fcktools_ie.js" />
+ <File path="editor/_source/fckeditorapi.js" />
+ <File path="editor/_source/classes/fckimagepreloader.js" />
+
+ <File path="editor/_source/internals/fckregexlib.js" />
+ <File path="editor/_source/internals/fcklistslib.js" />
+ <File path="editor/_source/internals/fcklanguagemanager.js" />
+ <File path="editor/_source/internals/fckxhtmlentities.js" />
+ <File path="editor/_source/internals/fckxhtml.js" />
+ <File path="editor/_source/internals/fckxhtml_ie.js" />
+ <File path="editor/_source/internals/fckcodeformatter.js" />
+ <File path="editor/_source/internals/fckundo.js" />
+ <File path="editor/_source/classes/fckeditingarea.js" />
+ <File path="editor/_source/classes/fckkeystrokehandler.js" />
+
+ <File path="editor/dtd/fck_xhtml10transitional.js" />
+ <File path="editor/_source/classes/fckstyle.js" />
+ <File path="editor/_source/internals/fckstyles.js" />
+
+ <File path="editor/_source/internals/fcklisthandler.js" />
+ <File path="editor/_source/classes/fckelementpath.js" />
+ <File path="editor/_source/classes/fckdomrange.js" />
+ <File path="editor/_source/classes/fckdomrange_ie.js" />
+ <File path="editor/_source/classes/fckdomrangeiterator.js" />
+ <File path="editor/_source/classes/fckdocumentfragment_ie.js" />
+ <File path="editor/_source/classes/fckw3crange.js" />
+ <File path="editor/_source/classes/fckenterkey.js" />
+
+ <File path="editor/_source/internals/fckdocumentprocessor.js" />
+ <File path="editor/_source/internals/fckselection.js" />
+ <File path="editor/_source/internals/fckselection_ie.js" />
+
+ <File path="editor/_source/internals/fcktablehandler.js" />
+ <File path="editor/_source/internals/fcktablehandler_ie.js" />
+ <File path="editor/_source/classes/fckxml.js" />
+ <File path="editor/_source/classes/fckxml_ie.js" />
+
+ <File path="editor/_source/commandclasses/fcknamedcommand.js" />
+ <File path="editor/_source/commandclasses/fckstylecommand.js" />
+ <File path="editor/_source/commandclasses/fck_othercommands.js" />
+ <File path="editor/_source/commandclasses/fckshowblocks.js" />
+ <File path="editor/_source/commandclasses/fckspellcheckcommand_ie.js" />
+ <File path="editor/_source/commandclasses/fcktextcolorcommand.js" />
+ <File path="editor/_source/commandclasses/fckpasteplaintextcommand.js" />
+ <File path="editor/_source/commandclasses/fckpastewordcommand.js" />
+ <File path="editor/_source/commandclasses/fcktablecommand.js" />
+ <File path="editor/_source/commandclasses/fckfitwindow.js" />
+ <File path="editor/_source/commandclasses/fcklistcommands.js" />
+ <File path="editor/_source/commandclasses/fckjustifycommands.js" />
+ <File path="editor/_source/commandclasses/fckindentcommands.js" />
+ <File path="editor/_source/commandclasses/fckblockquotecommand.js" />
+ <File path="editor/_source/commandclasses/fckcorestylecommand.js" />
+ <File path="editor/_source/commandclasses/fckremoveformatcommand.js" />
+ <File path="editor/_source/internals/fckcommands.js" />
+
+ <File path="editor/_source/classes/fckpanel.js" />
+ <File path="editor/_source/classes/fckicon.js" />
+ <File path="editor/_source/classes/fcktoolbarbuttonui.js" />
+ <File path="editor/_source/classes/fcktoolbarbutton.js" />
+ <File path="editor/_source/classes/fckspecialcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarspecialcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarstylecombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontformatcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontscombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontsizecombo.js" />
+ <File path="editor/_source/classes/fcktoolbarpanelbutton.js" />
+ <File path="editor/_source/internals/fckscayt.js" />
+ <File path="editor/_source/internals/fcktoolbaritems.js" />
+ <File path="editor/_source/classes/fcktoolbar.js" />
+ <File path="editor/_source/classes/fcktoolbarbreak_ie.js" />
+ <File path="editor/_source/internals/fcktoolbarset.js" />
+ <File path="editor/_source/internals/fckdialog.js" />
+
+ <File path="editor/_source/classes/fckmenuitem.js" />
+ <File path="editor/_source/classes/fckmenublock.js" />
+ <File path="editor/_source/classes/fckmenublockpanel.js" />
+ <File path="editor/_source/classes/fckcontextmenu.js" />
+ <File path="editor/_source/internals/fck_contextmenu.js" />
+ <File path="editor/_source/classes/fckhtmliterator.js" />
+
+ <File path="editor/_source/classes/fckplugin.js" />
+ <File path="editor/_source/internals/fckplugins.js" />
+ </PackageFile>
+
+ <PackageFile path="editor/js/fckeditorcode_gecko.js">
+ <File path="editor/_source/fckconstants.js" />
+ <File path="editor/_source/fckjscoreextensions.js" />
+ <File path="editor/_source/internals/fckbrowserinfo.js" />
+ <File path="editor/_source/internals/fckurlparams.js" />
+ <File path="editor/_source/classes/fckevents.js" />
+ <File path="editor/_source/classes/fckdataprocessor.js" />
+ <File path="editor/_source/internals/fck.js" />
+ <File path="editor/_source/internals/fck_gecko.js" />
+ <File path="editor/_source/internals/fckconfig.js" />
+ <File path="editor/_source/internals/fckdebug_empty.js" />
+ <File path="editor/_source/internals/fckdomtools.js" />
+ <File path="editor/_source/internals/fcktools.js" />
+ <File path="editor/_source/internals/fcktools_gecko.js" />
+ <File path="editor/_source/fckeditorapi.js" />
+ <File path="editor/_source/classes/fckimagepreloader.js" />
+
+ <File path="editor/_source/internals/fckregexlib.js" />
+ <File path="editor/_source/internals/fcklistslib.js" />
+ <File path="editor/_source/internals/fcklanguagemanager.js" />
+ <File path="editor/_source/internals/fckxhtmlentities.js" />
+ <File path="editor/_source/internals/fckxhtml.js" />
+ <File path="editor/_source/internals/fckxhtml_gecko.js" />
+ <File path="editor/_source/internals/fckcodeformatter.js" />
+ <File path="editor/_source/internals/fckundo.js" />
+ <File path="editor/_source/classes/fckeditingarea.js" />
+ <File path="editor/_source/classes/fckkeystrokehandler.js" />
+
+ <File path="editor/dtd/fck_xhtml10transitional.js" />
+ <File path="editor/_source/classes/fckstyle.js" />
+ <File path="editor/_source/internals/fckstyles.js" />
+
+ <File path="editor/_source/internals/fcklisthandler.js" />
+ <File path="editor/_source/classes/fckelementpath.js" />
+ <File path="editor/_source/classes/fckdomrange.js" />
+ <File path="editor/_source/classes/fckdomrange_gecko.js" />
+ <File path="editor/_source/classes/fckdomrangeiterator.js" />
+ <File path="editor/_source/classes/fckdocumentfragment_gecko.js" />
+ <File path="editor/_source/classes/fckw3crange.js" />
+ <File path="editor/_source/classes/fckenterkey.js" />
+
+ <File path="editor/_source/internals/fckdocumentprocessor.js" />
+ <File path="editor/_source/internals/fckselection.js" />
+ <File path="editor/_source/internals/fckselection_gecko.js" />
+
+ <File path="editor/_source/internals/fcktablehandler.js" />
+ <File path="editor/_source/internals/fcktablehandler_gecko.js" />
+ <File path="editor/_source/classes/fckxml.js" />
+ <File path="editor/_source/classes/fckxml_gecko.js" />
+
+ <File path="editor/_source/commandclasses/fcknamedcommand.js" />
+ <File path="editor/_source/commandclasses/fckstylecommand.js" />
+ <File path="editor/_source/commandclasses/fck_othercommands.js" />
+ <File path="editor/_source/commandclasses/fckshowblocks.js" />
+ <File path="editor/_source/commandclasses/fckspellcheckcommand_gecko.js" />
+ <File path="editor/_source/commandclasses/fcktextcolorcommand.js" />
+ <File path="editor/_source/commandclasses/fckpasteplaintextcommand.js" />
+ <File path="editor/_source/commandclasses/fckpastewordcommand.js" />
+ <File path="editor/_source/commandclasses/fcktablecommand.js" />
+ <File path="editor/_source/commandclasses/fckfitwindow.js" />
+ <File path="editor/_source/commandclasses/fcklistcommands.js" />
+ <File path="editor/_source/commandclasses/fckjustifycommands.js" />
+ <File path="editor/_source/commandclasses/fckindentcommands.js" />
+ <File path="editor/_source/commandclasses/fckblockquotecommand.js" />
+ <File path="editor/_source/commandclasses/fckcorestylecommand.js" />
+ <File path="editor/_source/commandclasses/fckremoveformatcommand.js" />
+ <File path="editor/_source/internals/fckcommands.js" />
+
+ <File path="editor/_source/classes/fckpanel.js" />
+ <File path="editor/_source/classes/fckicon.js" />
+ <File path="editor/_source/classes/fcktoolbarbuttonui.js" />
+ <File path="editor/_source/classes/fcktoolbarbutton.js" />
+ <File path="editor/_source/classes/fckspecialcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarspecialcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarstylecombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontformatcombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontscombo.js" />
+ <File path="editor/_source/classes/fcktoolbarfontsizecombo.js" />
+ <File path="editor/_source/classes/fcktoolbarpanelbutton.js" />
+ <File path="editor/_source/internals/fckscayt.js" />
+ <File path="editor/_source/internals/fcktoolbaritems.js" />
+ <File path="editor/_source/classes/fcktoolbar.js" />
+ <File path="editor/_source/classes/fcktoolbarbreak_gecko.js" />
+ <File path="editor/_source/internals/fcktoolbarset.js" />
+ <File path="editor/_source/internals/fckdialog.js" />
+
+ <File path="editor/_source/classes/fckmenuitem.js" />
+ <File path="editor/_source/classes/fckmenublock.js" />
+ <File path="editor/_source/classes/fckmenublockpanel.js" />
+ <File path="editor/_source/classes/fckcontextmenu.js" />
+ <File path="editor/_source/internals/fck_contextmenu.js" />
+ <File path="editor/_source/classes/fckhtmliterator.js" />
+
+ <File path="editor/_source/classes/fckplugin.js" />
+ <File path="editor/_source/internals/fckplugins.js" />
+ </PackageFile>
+
+</Package>
diff --git a/httemplate/elements/fckeditor/fckstyles.xml b/httemplate/elements/fckeditor/fckstyles.xml
new file mode 100644
index 0000000..b2f12a4
--- /dev/null
+++ b/httemplate/elements/fckeditor/fckstyles.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the sample style definitions file. It makes the styles combo
+ * completely customizable.
+ *
+ * See FCKConfig.StylesXmlPath in the configuration file.
+-->
+<Styles>
+
+ <!-- Block Styles -->
+
+ <!--
+ # These styles are already available in the "Format" combo, so they are not
+ # needed here by default.
+
+ <Style name="Heading 1" element="h1" />
+ <Style name="Heading 2" element="h2" />
+ <Style name="Heading 3" element="h3" />
+ <Style name="Heading 4" element="h4" />
+ <Style name="Heading 5" element="h5" />
+ <Style name="Heading 6" element="h6" />
+ <Style name="Paragraph" element="p" />
+ <Style name="Document Block" element="div" />
+ <Style name="Preformatted Text" element="pre" />
+ <Style name="Address" element="address" />
+ -->
+
+ <!-- Inline Styles -->
+
+ <!--
+ # These are core styles available as toolbar buttons.
+
+ <Style name="Bold" element="b">
+ <Override element="strong" />
+ </Style>
+ <Style name="Italic" element="i">
+ <Override element="em" />
+ </Style>
+ <Style name="Underline" element="u" />
+ <Style name="Strikethrough" element="strike" />
+ <Style name="Subscript" element="sub" />
+ <Style name="Superscript" element="sup" />
+ -->
+
+ <Style name="Marker: Yellow" element="span">
+ <Style name="background-color" value="Yellow" />
+ </Style>
+ <Style name="Marker: Green" element="span">
+ <Style name="background-color" value="Lime" />
+ </Style>
+
+ <Style name="Big" element="big" />
+ <Style name="Small" element="small" />
+ <Style name="Typewriter" element="tt" />
+
+ <Style name="Computer Code" element="code" />
+ <Style name="Keyboard Phrase" element="kbd" />
+ <Style name="Sample Text" element="samp" />
+ <Style name="Variable" element="var" />
+
+ <Style name="Deleted Text" element="del" />
+ <Style name="Inserted Text" element="ins" />
+
+ <Style name="Cited Work" element="cite" />
+ <Style name="Inline Quotation" element="q" />
+
+ <Style name="Language: RTL" element="span">
+ <Attribute name="dir" value="rtl" />
+ </Style>
+ <Style name="Language: LTR" element="span">
+ <Attribute name="dir" value="ltr" />
+ </Style>
+ <Style name="Language: RTL Strong" element="bdo">
+ <Attribute name="dir" value="rtl" />
+ </Style>
+ <Style name="Language: LTR Strong" element="bdo">
+ <Attribute name="dir" value="ltr" />
+ </Style>
+
+ <!-- Object Styles -->
+
+ <Style name="Image on Left" element="img">
+ <Attribute name="style" value="padding: 5px; margin-right: 5px" />
+ <Attribute name="border" value="2" />
+ <Attribute name="align" value="left" />
+ </Style>
+ <Style name="Image on Right" element="img">
+ <Attribute name="style" value="padding: 5px; margin-left: 5px" />
+ <Attribute name="border" value="2" />
+ <Attribute name="align" value="right" />
+ </Style>
+</Styles>
diff --git a/httemplate/elements/fckeditor/fcktemplates.xml b/httemplate/elements/fckeditor/fcktemplates.xml
new file mode 100644
index 0000000..4378584
--- /dev/null
+++ b/httemplate/elements/fckeditor/fcktemplates.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2010 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ * - GNU General Public License Version 2 or later (the "GPL")
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * - Mozilla Public License Version 1.1 or later (the "MPL")
+ * http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * This is the sample templates definitions file. It makes the "templates"
+ * command completely customizable.
+ *
+ * See FCKConfig.TemplatesXmlPath in the configuration file.
+-->
+<Templates imagesBasePath="fck_template/images/">
+ <Template title="Image and Title" image="template1.gif">
+ <Description>One main image with a title and text that surround the image.</Description>
+ <Html>
+ <![CDATA[
+ <img style="MARGIN-RIGHT: 10px" height="100" alt="" width="100" align="left"/>
+ <h3>Type the title here</h3>
+ Type the text here
+ ]]>
+ </Html>
+ </Template>
+ <Template title="Strange Template" image="template2.gif">
+ <Description>A template that defines two colums, each one with a title, and some text.</Description>
+ <Html>
+ <![CDATA[
+ <table cellspacing="0" cellpadding="0" width="100%" border="0">
+ <tbody>
+ <tr>
+ <td width="50%">
+ <h3>Title 1</h3>
+ </td>
+ <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </td>
+ <td width="50%">
+ <h3>Title 2</h3>
+ </td>
+ </tr>
+ <tr>
+ <td>Text 1</td>
+ <td>&nbsp;</td>
+ <td>Text 2</td>
+ </tr>
+ </tbody>
+ </table>
+ More text goes here.
+ ]]>
+ </Html>
+ </Template>
+ <Template title="Text and Table" image="template3.gif">
+ <Description>A title with some text and a table.</Description>
+ <Html>
+ <![CDATA[
+ <table align="left" width="80%" border="0" cellspacing="0" cellpadding="0"><tr><td>
+ <h3>Title goes here</h3>
+ <p>
+ <table style="FLOAT: right" cellspacing="0" cellpadding="0" width="150" border="1">
+ <tbody>
+ <tr>
+ <td align="center" colspan="3"><strong>Table title</strong></td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
+ </table>
+ Type the text here</p>
+ </td></tr></table>
+ ]]>
+ </Html>
+ </Template>
+</Templates>
diff --git a/httemplate/elements/file-upload.html b/httemplate/elements/file-upload.html
new file mode 100644
index 0000000..7e2eeef
--- /dev/null
+++ b/httemplate/elements/file-upload.html
@@ -0,0 +1,74 @@
+<SCRIPT TYPE="text/javascript">
+
+ function doUpload(form, callback) {
+ var name = 'form' + Math.floor(Math.random() * 99999); // perlize?
+ var d = document.createElement('DIV');
+ d.innerHTML = '<iframe style="display:none" src="about:blank" ' +
+ 'id="' + name + '" ' +
+ 'name="' + name + '" ' +
+ 'onload="uploadComplete(\'' + name + '\')">' +
+ '</iframe>';
+ document.body.appendChild(d);
+
+ var i = document.getElementById(name);
+ if (callback && typeof(callback) == 'function') {
+ i.onComplete = callback;
+ }
+
+ form.setAttribute('target', name);
+ return true;
+ }
+
+ function uploadComplete(id) {
+ var i = document.getElementById(id);
+ if (i.contentDocument) {
+ var d = i.contentDocument;
+ } else if (i.contentWindow) {
+ var d = i.contentWindow.document;
+ } else {
+ var d = window.frames[id].document;
+ }
+ if (d.location.href == "about:blank") {
+ return;
+ }
+
+ document.getElementById('r').innerHTML = d.body.innerHTML;
+ if (typeof(i.onComplete) == 'function') {
+ var p;
+ if (p = d.body.innerHTML.indexOf("File Upload Successful ") >= 0) {
+ var v = d.body.innerHTML.substr(p+24);
+ var u = document.getElementById('uploaded_files');
+ v = v.substr(0, v.indexOf(';'));
+ u.value = v;
+ i.onComplete(true, '');
+ }else{
+ i.onComplete(false, d.body.innerHTML);
+ }
+ }
+ }
+
+</SCRIPT>
+
+<INPUT TYPE="hidden" NAME="uploaded_files" ID="uploaded_files" VALUE="" />
+
+<INPUT TYPE="hidden" NAME="upload_fields" VALUE="<% join(',', @field) %>" />
+
+% foreach (@field) {
+ <TR>
+ <TH ALIGN="<% $param{'label_align'} || 'right' %>"><% shift @label %></TH>
+ <TD><INPUT TYPE="file" NAME="<% $_ %>" /></TD>
+ </TR>
+% }
+
+<DIV STYLE="display:<% $param{debug} ? 'visible' : 'none' %>">
+ Debugging: <PRE ID="r"></PRE>
+</DIV>
+
+<%init>
+
+my %param = @_;
+
+my @label = ref($param{'label'}) ? @{$param{'label'}} : ($param{'label'});
+my @field = ref($param{'field'}) ? @{$param{'field'}} : ($param{'field'});
+
+</%init>
diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html
new file mode 100644
index 0000000..32d1219
--- /dev/null
+++ b/httemplate/elements/footer.html
@@ -0,0 +1,5 @@
+ </TD>
+ </TR>
+ </TABLE>
+ </BODY>
+</HTML>
diff --git a/httemplate/elements/form-file_upload.html b/httemplate/elements/form-file_upload.html
new file mode 100644
index 0000000..4ab70ad
--- /dev/null
+++ b/httemplate/elements/form-file_upload.html
@@ -0,0 +1,93 @@
+<%doc>
+
+Example:
+
+ <% include( '/elements/form-file_upload.html',
+
+ 'name' => 'form_name',
+ 'action' => 'process/target.cgi', #progress-init target
+ 'fields' => [ 'other', 'form', 'fields' ],
+ 'num_files' => 1, #or more
+
+ 'url' => $url
+ #AND/OR
+ 'message' => 'Message',
+
+ #optional
+ 'key' => 'unique_key', #for using more than once on a page
+ )
+
+% #...
+
+% # num_files=>1
+ include( '/elements/file-upload.html',
+ 'field' => 'element',
+ 'label' => 'Label',
+ )
+
+% # OR
+
+% # num_files=>2 # or more
+ include( '/elements/file-upload.html',
+ 'field' => [ 'element', 'element2', ], #etc.
+ 'label' => [ 'Label', 'Label2', ], #etc.
+ )
+
+
+%>
+
+</%doc>
+
+<% include( '/elements/progress-init.html',
+ $opt{name},
+ $opt{fields},
+ $opt{action},
+ $msg_or_url,
+ $opt{key},
+ )
+%>
+
+<SCRIPT>
+
+ function <% $opt{key} %>gotUploaded(success, message) {
+
+ var uploaded = document.getElementById('uploaded_files');
+ var a = uploaded.value.split(',');
+ if (success && uploaded.value.split(',').length == <% $opt{num_files} %>){
+ process();
+ }else{
+ var p = document.getElementById('uploadError');
+ p.innerHTML='<FONT SIZE="+1" COLOR="#ff0000">Error: '+message+'</FONT><BR><BR>';
+ p.style='display:visible';
+ return false;
+ }
+
+ }
+
+</SCRIPT>
+
+<div style="display:none:" id="uploadError"></div>
+
+<FORM NAME = "<% $opt{name} %>"
+ ACTION = "<% $fsurl %>misc/file-upload.html"
+ METHOD = "POST"
+ ENCTYPE = "multipart/form-data"
+ onSubmit = "return doUpload(this, <% $opt{key} %>gotUploaded)"
+>
+
+<%init>
+
+#my( $formname, $fields, $action, $url_or_message, $key ) = @_;
+my %opt = ref($_[0]) ? %{ $_[0] } : @_;
+
+my $key = exists $opt{key} ? $opt{key} : '';
+
+push @{ $opt{fields} }, 'uploaded_files';
+
+my $msg_or_url = $opt{message}
+ ? { 'message' => $opt{message},
+ 'url' => $opt{url},
+ }
+ : $opt{url};
+
+</%init>
diff --git a/httemplate/elements/freeside-menu.css b/httemplate/elements/freeside-menu.css
new file mode 100644
index 0000000..9533f19
--- /dev/null
+++ b/httemplate/elements/freeside-menu.css
@@ -0,0 +1,148 @@
+input.fsblackbutton {
+ background-color:#aaaaaa;
+ color: #333333;
+ border:1px solid #888888;
+
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+
+ font-family: Arial, Verdana, Helvetica, sans-serif;
+ font-weight:bold;
+ padding-left:12px;
+ padding-right:12px;
+ padding-top:0px;
+ padding-bottom:0px;
+
+ margin-bottom:4px;
+ overflow:visible;
+}
+
+input.fsblackbutton:hover,input.fsblackbutton:focus,
+input.fsblackbuttonselected:hover, input.fsblackbuttonselected:focus {
+ background:#aaaaaa;
+}
+
+input.fsblackbuttonselected {
+ background-color:#aaaaaa;
+ color: #7e0079;
+ border:1px solid #7e0079;
+
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+
+ font-family: Arial, Verdana, Helvetica, sans-serif;
+ font-weight:bold;
+ padding-left:12px;
+ padding-right:12px;
+ padding-top:0px;
+ padding-bottom:0px;
+
+ margin-bottom:4px;
+ overflow:visible;
+}
+
+input.fstext {
+ border: 1px solid #666666;
+ padding: 1px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+
+ vertical-align:bottom;
+ text-align:right;
+ font-family: Arial,Verdana,Helvetica,sans-serif;
+ font-size: 13px;
+
+ margin:0px;
+}
+
+input.fstext:focus {
+ border: 1px solid #7e0079;
+}
+
+a.fslink {
+ text-decoration: underline;
+}
+
+a:hover.fslink {
+ color: #7e0079;
+}
+
+a.fsblackbutton {
+ background-color:#333333;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ text-decoration:none;
+ overflow:visible;
+}
+
+a.fsblackbuttonselected,
+a:link:hover.fsblackbutton,
+a:visited:hover.fsblackbutton {
+ background-color:#7e0079;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ text-decoration:none;
+ overflow:visible;
+}
+
+a.fsdarkbutton {
+ background-color:#555555;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ text-decoration:none;
+ overflow:visible;
+}
+
+a.fsdarkbuttonselected,
+a:link:hover.fsdarkbutton,
+a:visited:hover.fsdarkbutton {
+ background-color:#7e0079;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ text-decoration:none;
+ overflow:visible;
+}
+
diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css
new file mode 100644
index 0000000..3816f98
--- /dev/null
+++ b/httemplate/elements/freeside.css
@@ -0,0 +1,152 @@
+* {
+ font-family: Arial, Verdana, Helvetica, sans-serif;
+ /* font-family: Verdana, Arial, Helvetica, sans-serif; */
+}
+
+A:link IMG, A:visited { border-style: none }
+/* A:focus {text-decoration: underline } */
+
+a:link, a:visited {
+ /* text-decoration: none; */
+ color: #000000;
+}
+
+a[href]:hover {
+ text-decoration: underline;
+ color: #7e0079;
+}
+
+textarea, input[type="text"] {
+ border: 1px solid #666666;
+ padding: 1px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+}
+
+textarea:hover, input[type="text"]:hover {
+ border: 1px solid #7e0079;
+ padding: 1px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+}
+
+textarea:focus, input[type="text"]:focus {
+ background-color: #ffffdd;
+ border: 1px solid #7e0079;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+}
+
+input[type="reset"], input[type="submit"], input[type="button"] {
+ background-color: #dddddd;
+ border: 1px solid #666666;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ -moz-box-shadow: #666666 1px 1px 2px;
+ -webkit-box-shadow: #666666 1px 1px 2px;
+ box-shadow: #666666 1px 1px 2px;
+ filter: progid:DXImageTransform.Microsoft.Shadow(color='#666666', Direction=135, Strength=2);
+}
+
+input[type="reset"]:hover, input[type="submit"]:hover, input[type="button"]:hover {
+ background-color: #e4e4e4;
+ border: 1px solid #7e0079;
+ color: #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ -moz-box-shadow: #7e0079 1px 1px 2px;
+ -webkit-box-shadow: #7e0079 1px 1px 2px;
+ box-shadow: #7e0079 1px 1px 2px;
+ filter: progid:DXImageTransform.Microsoft.Shadow(color='#7e0079', Direction=135, Strength=2);
+}
+
+/*input[type="select"] {
+ background-color: #eeeeee;
+ border: 1px solid #666666;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+}
+
+input[type="select"]:hover {
+ background-color: #eeeeee;
+ border: 1px solid #7e0079;
+ color: #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+}
+*/
+
+
+/* a:focus { background-color: #ccccee } */
+
+a.fstab {
+ background-color:#cccccc;
+ color: #666666;
+ border:1px solid;
+ border-top-color:#666666;
+ border-left-color:#666666;
+ border-right-color:#666666;
+ border-bottom-color:#7e0079;
+ -moz-border-radius-topleft:8px;
+ -moz-border-radius-topright:8px;
+ -webkit-border-radius-topleft:8px;
+ -webkit-border-radius-topright:8px;
+ border-radius-topleft:8px;
+ border-radius-topright:8px;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ font-weight:bold;
+ text-decoration:none;
+ overflow:visible;
+}
+a.fstab:hover {
+ text-decoration:none;
+ border-color:#7e0079;
+}
+
+/* a:link:hover.fsblackbutton,
+a:visited:hover.fsblackbutton
+*/
+a.fstabselected {
+ background-color:#f8f8f8;
+ color: #000000;
+ border:1px solid;
+ border-top-color:#7e0079;
+ border-left-color:#7e0079;
+ border-right-color:#7e0079;
+ border-bottom-color:#ffffff;
+ -moz-border-radius-topleft:8px;
+ -moz-border-radius-topright:8px;
+ -webkit-border-radius-topleft:8px;
+ -webkit-border-radius-topright:8px;
+ border-radius-topleft:8px;
+ border-radius-topright:8px;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ font-size:16px;
+ font-weight:bold;
+ text-decoration:none;
+ overflow:visible;
+}
+a.fstabselected:hover {
+ text-decoration:none;
+ color: #000000;
+}
+
+.background {
+ background-color:#f8f8f8;
+}
diff --git a/httemplate/elements/header-minimal.html b/httemplate/elements/header-minimal.html
new file mode 100644
index 0000000..28a78b4
--- /dev/null
+++ b/httemplate/elements/header-minimal.html
@@ -0,0 +1,19 @@
+%
+% my($title, $menubar) = ( shift, shift ); #$menubar is unused here though
+% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+% my $conf = new FS::Conf;
+%
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<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="#f8f8f8" <% $etc %>>
diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html
new file mode 100644
index 0000000..cd8da56
--- /dev/null
+++ b/httemplate/elements/header-popup.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<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="#f8f8f8" <% $etc %>>
+ <link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+ <FONT SIZE=6>
+ <CENTER><% $title %></CENTER>
+ </FONT>
+
+% unless ( $nobr ) {
+ <BR><!--<BR>-->
+% }
+
+<%init>
+
+my( $title, $menubar, $etc, $head ) = ( '', '', '', '' );
+#my( $nobr, $nocss ) = ( 0, 0 );
+my $nobr = 0;
+if ( ref($_[0]) ) {
+ my $opt = shift;
+ $title = $opt->{title};
+ $menubar = $opt->{menubar};
+ $etc = $opt->{etc};
+ $head = $opt->{head};
+ $nobr = $opt->{nobr};
+# $nocss = $opt->{nocss};
+} else {
+ ($title, $menubar) = ( shift, shift );
+ $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+ $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+}
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
new file mode 100644
index 0000000..90ab0b7
--- /dev/null
+++ b/httemplate/elements/header.html
@@ -0,0 +1,187 @@
+<%doc>
+
+Example:
+
+ include( '/elements/header.html',
+ {
+ 'title' => 'Title',
+ 'menubar' => \@menubar,
+ 'etc' => '', #included in <BODY> tag, for things like onLoad=
+ 'head' => '', #included before closing </HEAD> tag
+ 'nobr' => 0, #1 for no <BR><BR> after the title
+ }
+ );
+
+ #old-style
+ include( '/elements/header.html', 'Title', $menubar, $etc, $head);
+
+
+</%doc>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+%# above is what RT declares, should we switch now? hopefully no glitches result
+%# or just fuck it, XHTML died anyway, HTML 5 or bust?
+<HTML>
+ <HEAD>
+ <TITLE>
+ <% $title |h %>
+ </TITLE>
+ <META HTTP-Equiv="Cache-Control" Content="no-cache">
+ <META HTTP-Equiv="Pragma" Content="no-cache">
+ <META HTTP-Equiv="Expires" Content="0">
+
+ <% include('menu.html', 'freeside_baseurl' => $fsurl,
+ 'position' => $menu_position,
+ 'nocss' => $nocss,
+ ) |n
+ %>
+
+ <% include('init_overlib.html') |n %>
+ <% include('rs_init_object.html') |n %>
+ <% include('logout.html') |n %>
+
+ <% $head |n %>
+
+ </HEAD>
+ <BODY BGCOLOR="#f8f8f8" <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0">
+ <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0; padding-right:4">
+ <tr>
+ <td BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"></td>
+ <td align=left BGCOLOR="#ffffff"> <!-- valign="top" -->
+ <font size=6><% $company_name || 'ExampleCo' %></font>
+ </td>
+ <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b> <FONT SIZE="-2"><a href="javascript:void(0);" onClick="logout();">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
+% if ( $conf->config("ticket_system")
+% && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) {
+ | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a>
+% }
+ <BR></FONT>
+ </td>
+ </tr>
+ </table>
+
+ <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0>
+
+<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet">
+
+% if ( $menu_position eq 'top' ) {
+
+ <TR>
+
+ <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#cccccc">
+ <SCRIPT TYPE="text/javascript">
+ document.write(myBar);
+ </SCRIPT>
+ </TD>
+
+ </TR>
+
+ <TR>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="right" STYLE="padding-left:2px">
+ <% include('searchbar-prospect.html') |n %>
+ </TD>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="right" STYLE="padding-left:2px">
+ <% include('searchbar-cust_main.html') |n %>
+ </TD>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="center">
+ <% include('searchbar-address2.html') |n %>
+ </TD>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="right">
+ <% include('searchbar-cust_bill.html') |n %>
+ </TD>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="right" STYLE="padding-left:2px">
+ <% include('searchbar-cust_svc.html') |n %>
+ </TD>
+
+ <TD COLSPAN=1 BGCOLOR="#cccccc" ALIGN="right" STYLE="padding-left:2px;padding-right:2px">
+ <% include('searchbar-ticket.html') |n %>
+ </TD>
+
+ </TR>
+ </TABLE>
+
+% } else { #$menu_position eq 'left'
+
+ <TR>
+
+ <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#cccccc">
+ </TD>
+
+ </TR>
+
+% }
+
+
+ <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4>
+
+ <TR HEIGHT="100%">
+
+% if ( $menu_position eq 'left' ) {
+
+ <TD BGCOLOR="#cccccc" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right">
+ <SCRIPT TYPE="text/javascript">
+ document.write(myBar);
+ </SCRIPT>
+
+ <BR>
+ <% include('searchbar-prospect.html') |n %>
+ <% include('searchbar-cust_main.html') |n %>
+ <% include('searchbar-address2.html') |n %>
+ <% include('searchbar-cust_bill.html') |n %>
+ <% include('searchbar-cust_svc.html') |n %>
+ <% include('searchbar-ticket.html') |n %>
+
+ </TD>
+
+% }
+
+ <TD CLASS="background" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> -->
+
+ <FONT SIZE=6>
+ <% $title |h %>
+ </FONT>
+
+% unless ( $nobr ) {
+ <BR><BR>
+% }
+
+ <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %>
+<%init>
+
+my( $title, $menubar, $etc, $head ) = ( '', '', '', '' );
+my( $nobr, $nocss ) = ( 0, 0 );
+if ( ref($_[0]) ) {
+ my $opt = shift;
+ $title = $opt->{title};
+ $menubar = $opt->{menubar};
+ $etc = $opt->{etc};
+ $head = $opt->{head};
+ $nobr = $opt->{nobr};
+ $nocss = $opt->{nocss};
+} else {
+ ($title, $menubar) = ( shift, shift );
+ $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+ $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+}
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position')
+ || 'top'; #new default for 1.9
+
+my $company_name;
+my @agentnums = $curuser->agentnums;
+if ( scalar(@agentnums) == 1 ) {
+ $company_name = $conf->config('company_name', $agentnums[0] );
+} else {
+ $company_name = $conf->config('company_name');
+}
+
+</%init>
diff --git a/httemplate/elements/hidden.html b/httemplate/elements/hidden.html
new file mode 100644
index 0000000..8311081
--- /dev/null
+++ b/httemplate/elements/hidden.html
@@ -0,0 +1,11 @@
+<INPUT TYPE = "hidden"
+ NAME = "<% $opt{field} %>"
+ ID = "<% $opt{field} %>"
+ VALUE = "<% $opt{curr_value} || $opt{value} |h %>"
+>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/htmlarea.html b/httemplate/elements/htmlarea.html
new file mode 100644
index 0000000..dca4328
--- /dev/null
+++ b/httemplate/elements/htmlarea.html
@@ -0,0 +1,37 @@
+<%doc>
+
+Example:
+
+ include('/elements/htmlarea.html',
+ 'field' => 'fieldname',
+ 'curr_value' => $curr_value,
+ 'height' => 800,
+ );
+
+</%doc>
+
+% #init
+<SCRIPT TYPE="text/javascript" src="<% $p %>elements/fckeditor/fckeditor.js">
+</SCRIPT>
+
+% #editor
+<SCRIPT TYPE="text/javascript">
+
+ var oFCKeditor = new FCKeditor('<% $opt{'field'} %>');
+ oFCKeditor.Value = <% $opt{'curr_value'} |js_string %>;
+
+ oFCKeditor.BasePath = '<% $p %>elements/fckeditor/';
+ oFCKeditor.Config['SkinPath'] = '<% $p %>elements/fckeditor/editor/skins/silver/';
+ oFCKeditor.Height = '<% $opt{'height'} || 420 %>';
+ oFCKeditor.Config['StartupFocus'] = true;
+ oFCKeditor.Config['EnterMode'] = 'br';
+
+ oFCKeditor.Create();
+
+</SCRIPT>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/iframecontentmws.js b/httemplate/elements/iframecontentmws.js
new file mode 100644
index 0000000..f2a91d2
--- /dev/null
+++ b/httemplate/elements/iframecontentmws.js
@@ -0,0 +1,59 @@
+/*
+ iframecontentmws.js - Foteos Macrides (author and copyright holder)
+ Initial: October 10, 2004 - Last Revised: January 26, 2008
+ Scripts for using HTML documents as iframe content in overlibmws popups.
+
+ See http://www.macridesweb.com/oltest/IFRAME.html
+ and http://www.macridesweb.com/oltest/AJAX.html#ajaxex3
+ for more information.
+*/
+
+/*
+ Use as lead argument in overlib or overlb2 calls. Include WRAP and
+ TEXTPADDING,0 in the call to ensure that the width arg is respected (unless
+ the CAPTION plus CLOSETEXT widths add up to more than the width arg, in which
+ case you should increase the width arg). The name arg should be a unique
+ string for each popup with iframe content in the document. The frameborder
+ arg should be 1 (browser default if omitted) or 0. The scrolling arg should
+ be 'auto' (default if omitted), 'yes' or 'no'.
+*/
+function OLiframeContent(src, width, height, name, frameborder, scrolling) {
+
+ /* stupid safari iframe location caching... */
+ var d = new Date();
+ var unique = d.getTime() + '' + Math.floor(1000 * Math.random());
+ name = name + '' + unique;
+
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+ +(name!=null?' name="'+name+'" id="'+name+'"':'')
+ +(frameborder!=null?' frameborder="'+frameborder+'"':'')
+ +' scrolling="'+(scrolling!=null?scrolling:'auto')
+ +'"><div>[iframe not supported]</div></iframe>');
+}
+
+/*
+ Swap the src if we are iframe content. The name arg should be the same
+ string as in the OLiframeContent function for the popup. The src arg is
+ a partial, relative, or complete URL for the document to be swapped in.
+*/
+function OLswapIframeSrc(name, src){
+ if(parent==self){
+ alert(src+'\n\n is only for iframe content');
+ return;
+ }
+ var o=parent.OLgetRef(name);
+ if(o)o.src=src;
+ else alert(src+'\n\n is not available');
+}
+
+/*
+ Emulate the Back button if we are iframe content. Use only in documents
+ which are swapped in by using the OLswapIframeSrc function.
+*/
+function OLiframeBack(){
+ if(parent==self){
+ alert('This feature is only for iframe content');
+ return;
+ }
+ history.back();
+}
diff --git a/httemplate/elements/init_calendar.html b/httemplate/elements/init_calendar.html
new file mode 100644
index 0000000..04b0135
--- /dev/null
+++ b/httemplate/elements/init_calendar.html
@@ -0,0 +1,5 @@
+<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
+
+% foreach (qw( _stripped -en -setup )) {
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar<%$_%>.js"></SCRIPT>
+% }
diff --git a/httemplate/elements/init_overlib.html b/httemplate/elements/init_overlib.html
new file mode 100644
index 0000000..d27ca3b
--- /dev/null
+++ b/httemplate/elements/init_overlib.html
@@ -0,0 +1,9 @@
+% for my $file (@files) {
+ <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/<%$file%>.js"></SCRIPT>
+% }
+<%init>
+
+my @files = map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
+push @files, map { "${_}contentmws" } qw( iframe ajax );
+
+</%init>
diff --git a/httemplate/elements/input-text.html b/httemplate/elements/input-text.html
new file mode 100644
index 0000000..fb50a50
--- /dev/null
+++ b/httemplate/elements/input-text.html
@@ -0,0 +1,48 @@
+<% $opt{'prefix'} %><INPUT TYPE = "<% $opt{type} || 'text' %>"
+ NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ VALUE = "<% $value |h %>"
+ <% $size %>
+ <% $maxlength %>
+ <% $style %>
+ <% $opt{disabled} %>
+ <% $onchange %>
+ ><% $opt{'postfix'} %>
+<%init>
+
+my %opt = @_;
+
+my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value};
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $size = $opt{'size'}
+ ? 'SIZE="'. $opt{'size'}. '"'
+ : '';
+
+my $maxlength = $opt{'maxlength'}
+ ? 'MAXLENGTH="'. $opt{'maxlength'}. '"'
+ : '';
+
+$opt{'disabled'} = &{ $opt{'disabled'} }( \%opt )
+ if ref($opt{'disabled'}) eq 'CODE';
+$opt{'disabled'} = 'DISABLED'
+ if $opt{'disabled'} && $opt{'disabled'} !~ /disabled/i; # uuh... yeah?
+
+my @style = ref($opt{'style'})
+ ? @{ $opt{'style'} }
+ : $opt{'style'}
+ ? ( $opt{'style'} )
+ : ();
+
+push @style, 'text-align: '. $opt{'text-align'}
+ if $opt{'text-align'};
+
+push @style, 'background-color: #dddddd'
+ if $opt{'disabled'} && ! $opt{'nodarken_disabled'};
+
+my $style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
+
+</%init>
diff --git a/httemplate/elements/jsrsClient.js b/httemplate/elements/jsrsClient.js
new file mode 100644
index 0000000..3a2572c
--- /dev/null
+++ b/httemplate/elements/jsrsClient.js
@@ -0,0 +1,356 @@
+//
+// jsrsClient.js - javascript remote scripting client include
+//
+// Author: Brent Ashley [jsrs@megahuge.com]
+//
+// make asynchronous remote calls to server without client page refresh
+//
+// see license.txt for copyright and license information
+
+/*
+see history.txt for full history
+2.0 26 Jul 2001 - added POST capability for IE/MOZ
+2.2 10 Aug 2003 - added Opera support
+2.3(beta) 10 Oct 2003 - added Konqueror support - **needs more testing**
+*/
+
+// callback pool needs global scope
+var jsrsContextPoolSize = 0;
+var jsrsContextMaxPool = 10;
+var jsrsContextPool = new Array();
+var jsrsBrowser = jsrsBrowserSniff();
+var jsrsPOST = true;
+var containerName;
+
+// constructor for context object
+function jsrsContextObj( contextID ){
+
+ // properties
+ this.id = contextID;
+ this.busy = true;
+ this.callback = null;
+ this.container = contextCreateContainer( contextID );
+
+ // methods
+ this.GET = contextGET;
+ this.POST = contextPOST;
+ this.getPayload = contextGetPayload;
+ this.setVisibility = contextSetVisibility;
+}
+
+// method functions are not privately scoped
+// because Netscape's debugger chokes on private functions
+function contextCreateContainer( containerName ){
+ // creates hidden container to receive server data
+ var container;
+ switch( jsrsBrowser ) {
+ case 'NS':
+ container = new Layer(100);
+ container.name = containerName;
+ container.visibility = 'hidden';
+ container.clip.width = 100;
+ container.clip.height = 100;
+ break;
+
+ case 'IE':
+ document.body.insertAdjacentHTML( "afterBegin", '<span id="SPAN' + containerName + '"></span>' );
+ var span = document.all( "SPAN" + containerName );
+ var html = '<iframe name="' + containerName + '" src=""></iframe>';
+ span.innerHTML = html;
+ span.style.display = 'none';
+ container = window.frames[ containerName ];
+ break;
+
+ case 'MOZ':
+ var span = document.createElement('SPAN');
+ span.id = "SPAN" + containerName;
+ document.body.appendChild( span );
+ var iframe = document.createElement('IFRAME');
+ iframe.name = containerName;
+ iframe.id = containerName;
+ span.appendChild( iframe );
+ container = iframe;
+ break;
+
+ case 'OPR':
+ var span = document.createElement('SPAN');
+ span.id = "SPAN" + containerName;
+ document.body.appendChild( span );
+ var iframe = document.createElement('IFRAME');
+ iframe.name = containerName;
+ iframe.id = containerName;
+ span.appendChild( iframe );
+ container = iframe;
+ break;
+
+ case 'KONQ':
+ var span = document.createElement('SPAN');
+ span.id = "SPAN" + containerName;
+ document.body.appendChild( span );
+ var iframe = document.createElement('IFRAME');
+ iframe.name = containerName;
+ iframe.id = containerName;
+ span.appendChild( iframe );
+ container = iframe;
+
+ // Needs to be hidden for Konqueror, otherwise it'll appear on the page
+ span.style.display = none;
+ iframe.style.display = none;
+ iframe.style.visibility = hidden;
+ iframe.height = 0;
+ iframe.width = 0;
+
+ break;
+ }
+ return container;
+}
+
+function contextPOST( rsPage, func, parms ){
+
+ var d = new Date();
+ var unique = d.getTime() + '' + Math.floor(1000 * Math.random());
+ var doc = (jsrsBrowser == "IE" ) ? this.container.document : this.container.contentDocument;
+ doc.open();
+ doc.write('<html><body>');
+ doc.write('<form name="jsrsForm" method="post" target="" ');
+ doc.write(' action="' + rsPage + '?U=' + unique + '">');
+ doc.write('<input type="hidden" name="C" value="' + this.id + '">');
+
+ // func and parms are optional
+ if (func != null){
+ doc.write('<input type="hidden" name="F" value="' + func + '">');
+
+ if (parms != null){
+ if (typeof(parms) == "string"){
+ // single parameter
+ doc.write( '<input type="hidden" name="P0" '
+ + 'value="[' + jsrsEscapeQQ(parms) + ']">');
+ } else {
+ // assume parms is array of strings
+ for( var i=0; i < parms.length; i++ ){
+ doc.write( '<input type="hidden" name="P' + i + '" '
+ + 'value="[' + jsrsEscapeQQ(parms[i]) + ']">');
+ }
+ } // parm type
+ } // parms
+ } // func
+
+ doc.write('</form></body></html>');
+ doc.close();
+ doc.forms['jsrsForm'].submit();
+}
+
+function contextGET( rsPage, func, parms ){
+
+ // build URL to call
+ var URL = rsPage;
+
+ // always send context
+ URL += "?C=" + this.id;
+
+ // func and parms are optional
+ if (func != null){
+ URL += "&F=" + escape(func);
+
+ if (parms != null){
+ if (typeof(parms) == "string"){
+ // single parameter
+ URL += "&P0=[" + escape(parms+'') + "]";
+ } else {
+ // assume parms is array of strings
+ for( var i=0; i < parms.length; i++ ){
+ URL += "&P" + i + "=[" + escape(parms[i]+'') + "]";
+ }
+ } // parm type
+ } // parms
+ } // func
+
+ // unique string to defeat cache
+ var d = new Date();
+ URL += "&U=" + d.getTime();
+
+ // make the call
+ switch( jsrsBrowser ) {
+ case 'NS':
+ this.container.src = URL;
+ break;
+ case 'IE':
+ this.container.document.location.replace(URL);
+ break;
+ case 'MOZ':
+ this.container.src = '';
+ this.container.src = URL;
+ break;
+ case 'OPR':
+ this.container.src = '';
+ this.container.src = URL;
+ break;
+ case 'KONQ':
+ this.container.src = '';
+ this.container.src = URL;
+ break;
+ }
+}
+
+function contextGetPayload(){
+ switch( jsrsBrowser ) {
+ case 'NS':
+ return this.container.document.forms['jsrs_Form'].elements['jsrs_Payload'].value;
+ case 'IE':
+ return this.container.document.forms['jsrs_Form']['jsrs_Payload'].value;
+ case 'MOZ':
+ return window.frames[this.container.name].document.forms['jsrs_Form']['jsrs_Payload'].value;
+ case 'OPR':
+ var textElement = window.frames[this.container.name].document.getElementById("jsrs_Payload");
+ case 'KONQ':
+ var textElement = window.frames[this.container.name].document.getElementById("jsrs_Payload");
+ return textElement.value;
+ }
+}
+
+function contextSetVisibility( vis ){
+ switch( jsrsBrowser ) {
+ case 'NS':
+ this.container.visibility = (vis)? 'show' : 'hidden';
+ break;
+ case 'IE':
+ document.all("SPAN" + this.id ).style.display = (vis)? '' : 'none';
+ break;
+ case 'MOZ':
+ document.getElementById("SPAN" + this.id).style.visibility = (vis)? '' : 'hidden';
+ case 'OPR':
+ document.getElementById("SPAN" + this.id).style.visibility = (vis)? '' : 'hidden';
+ this.container.width = (vis)? 250 : 0;
+ this.container.height = (vis)? 100 : 0;
+ break;
+ }
+}
+
+// end of context constructor
+
+function jsrsGetContextID(){
+ var contextObj;
+ for (var i = 1; i <= jsrsContextPoolSize; i++){
+ contextObj = jsrsContextPool[ 'jsrs' + i ];
+ if ( !contextObj.busy ){
+ contextObj.busy = true;
+ return contextObj.id;
+ }
+ }
+ // if we got here, there are no existing free contexts
+ if ( jsrsContextPoolSize <= jsrsContextMaxPool ){
+ // create new context
+ var contextID = "jsrs" + (jsrsContextPoolSize + 1);
+ jsrsContextPool[ contextID ] = new jsrsContextObj( contextID );
+ jsrsContextPoolSize++;
+ return contextID;
+ } else {
+ alert( "jsrs Error: context pool full" );
+ return null;
+ }
+}
+
+function jsrsExecute( rspage, callback, func, parms, visibility ){
+ // call a server routine from client code
+ //
+ // rspage - href to asp file
+ // callback - function to call on return
+ // or null if no return needed
+ // (passes returned string to callback)
+ // func - sub or function name to call
+ // parm - string parameter to function
+ // or array of string parameters if more than one
+ // visibility - optional boolean to make container visible for debugging
+
+ // get context
+ var contextObj = jsrsContextPool[ jsrsGetContextID() ];
+ contextObj.callback = callback;
+
+ var vis = (visibility == null)? false : visibility;
+ contextObj.setVisibility( vis );
+
+ if ( jsrsPOST && ((jsrsBrowser == 'IE') || (jsrsBrowser == 'MOZ'))){
+ contextObj.POST( rspage, func, parms );
+ } else {
+ contextObj.GET( rspage, func, parms );
+ }
+
+ return contextObj.id;
+}
+
+function jsrsLoaded( contextID ){
+ // get context object and invoke callback
+ var contextObj = jsrsContextPool[ contextID ];
+ if( contextObj.callback != null){
+ contextObj.callback( jsrsUnescape( contextObj.getPayload() ), contextID );
+ }
+ // clean up and return context to pool
+ contextObj.callback = null;
+ contextObj.busy = false;
+}
+
+function jsrsError( contextID, str ){
+ alert( unescape(str) );
+ jsrsContextPool[ contextID ].busy = false
+}
+
+function jsrsEscapeQQ( thing ){
+ return thing.replace(/'"'/g, '\\"');
+}
+
+function jsrsUnescape( str ){
+ // payload has slashes escaped with whacks
+ return str.replace( /\\\//g, "/" );
+}
+
+function jsrsBrowserSniff(){
+ if (document.layers) return "NS";
+ if (document.all) {
+ // But is it really IE?
+ // convert all characters to lowercase to simplify testing
+ var agt=navigator.userAgent.toLowerCase();
+ var is_opera = (agt.indexOf("opera") != -1);
+ var is_konq = (agt.indexOf("konqueror") != -1);
+ if(is_opera) {
+ return "OPR";
+ } else {
+ if(is_konq) {
+ return "KONQ";
+ } else {
+ // Really is IE
+ return "IE";
+ }
+ }
+ }
+ if (document.getElementById) return "MOZ";
+ return "OTHER";
+}
+
+/////////////////////////////////////////////////
+//
+// user functions
+
+function jsrsArrayFromString( s, delim ){
+ // rebuild an array returned from server as string
+ // optional delimiter defaults to ~
+ var d = (delim == null)? '~' : delim;
+ return s.split(d);
+}
+
+function jsrsDebugInfo(){
+ // use for debugging by attaching to f1 (works with IE)
+ // with onHelp = "return jsrsDebugInfo();" in the body tag
+ var doc = window.open().document;
+ doc.open;
+ doc.write( 'Pool Size: ' + jsrsContextPoolSize + '<br><font face="arial" size="2"><b>' );
+ for( var i in jsrsContextPool ){
+ var contextObj = jsrsContextPool[i];
+ doc.write( '<hr>' + contextObj.id + ' : ' + (contextObj.busy ? 'busy' : 'available') + '<br>');
+ doc.write( contextObj.container.document.location.pathname + '<br>');
+ doc.write( contextObj.container.document.location.search + '<br>');
+ doc.write( '<table border="1"><tr><td>' + contextObj.container.document.body.innerHTML + '</td></tr></table>' );
+ }
+ doc.write('</table>');
+ doc.close();
+ return false;
+}
diff --git a/httemplate/elements/jsrsServer.html b/httemplate/elements/jsrsServer.html
new file mode 100644
index 0000000..f37b0aa
--- /dev/null
+++ b/httemplate/elements/jsrsServer.html
@@ -0,0 +1,4 @@
+%
+% my $server = new FS::UI::Web::JSRPC '', $cgi;
+%
+<% $server->process %>
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
new file mode 100644
index 0000000..0d2fa38
--- /dev/null
+++ b/httemplate/elements/location.html
@@ -0,0 +1,158 @@
+<%doc>
+
+Example:
+
+ include( '/elements/location.html',
+ 'object' => $cust_main, # or $cust_location
+ 'prefix' => $pre, #only for cust_main objects
+ 'onchange' => $javascript,
+ 'disabled' => $disabled,
+ 'same_checked' => $same_checked,
+ 'geocode' => $geocode, #passed through
+ 'censustract' => $censustract, #passed through
+ 'no_asterisks' => 0, #set true to disable the red asterisks next
+ #to required fields
+ 'address1_label' => 'Address', #label for address
+ )
+
+</%doc>
+
+<TR>
+ <<%$th%> ALIGN="right"><%$r%><% $opt{'address1_label'} || 'Address' %></<%$th%>>
+ <TD COLSPAN=7>
+ <INPUT TYPE = "text"
+ NAME = "<%$pre%>address1"
+ ID = "<%$pre%>address1"
+ VALUE = "<% $object->get($pre.'address1') |h %>"
+ SIZE = 54
+ onChange = "<% $onchange %>"
+ <% $disabled %>
+ <% $style %>
+ >
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><FONT ID="<% $pre %>address2_required" color="#ff0000" <% $address2_label_style %>>*</FONT>&nbsp;<FONT ID="<% $pre %>address2_label" <% $address2_label_style %>><B>Unit&nbsp;#</B></FONT></TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE = "text"
+ NAME = "<%$pre%>address2"
+ ID = "<%$pre%>address2"
+ VALUE = "<% $object->get($pre.'address2') |h %>"
+ SIZE = 54
+ onChange = "<% $onchange %>"
+ <% $disabled %>
+ <% $style %>
+ >
+ </TD>
+</TR>
+
+<TR>
+ <<%$th%> ALIGN="right"><%$r%>City</<%$th%>>
+ <TD WIDTH="1"><% include('/elements/city.html', %select_hash) %></TD>
+ <<%$th%> ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</<%$th%>>
+ <TD><% include('/elements/select-county.html', %select_hash ) %></TD>
+ <<%$th%> ALIGN="right" WIDTH="1"><%$r%>State</<%$th%>>
+ <TD WIDTH="1">
+ <% include('/elements/select-state.html', %select_hash ) %>
+ </TD>
+ <<%$th%>><%$r%>Zip</<%$th%>>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "<%$pre%>zip"
+ ID = "<%$pre%>zip"
+ VALUE = "<% $object->get($pre.'zip') |h %>"
+ SIZE = 10
+ onChange = "<% $onchange %>"
+ <% $disabled %>
+ <% $style %>
+ >
+ </TD>
+</TR>
+
+<TR>
+ <<%$th%> ALIGN="right"><%$r%>Country</<%$th%>>
+ <TD COLSPAN=6><% include('/elements/select-country.html', %select_hash ) %></TD>
+</TR>
+
+% if ( !$pre ) {
+ <INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>">
+% } else {
+% if ( $pre eq 'ship_' && $conf->exists('cust_main-require_censustract') ) {
+ <TR><<%$th%> ALIGN="right">Census tract<BR>(automatic)</<%$th%>>
+ <TD>
+ <INPUT TYPE="text" NAME="censustract" VALUE="<% $opt{censustract} %>">
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>">
+% }
+% }
+
+<%init>
+
+my %opt = @_;
+
+my $pre = $opt{'prefix'};
+my $object = $opt{'object'};
+my $onchange = $opt{'onchange'};
+my $disabled = $opt{'disabled'};
+
+my $conf = new FS::Conf;
+
+my $r = $opt{'no_asterisks'} ? '' : qq!<font color="#ff0000">*</font>&nbsp;!;
+
+#false laziness with ship state
+my $countrydefault = $conf->config('countrydefault') || 'US';
+$object->set($pre.'country', $countrydefault )
+ unless $object->get($pre.'country');
+
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+$object->set($pre.'state', $statedefault )
+ unless $object->get($pre.'state')
+ || $object->get($pre.'country') ne $countrydefault;
+
+my @style = ();
+push @style, 'background-color: #dddddd' if $disabled;
+
+my @address2_label_style = ();
+push @address2_label_style, 'visibility:hidden'
+ if $disabled
+ || ! $conf->exists('cust_main-require_address2')
+ || ( !$pre && !$opt{'same_checked'} );
+
+my @counties = counties( $object->get($pre.'state'),
+ $object->get($pre.'country'),
+ );
+my @county_style = ();
+push @county_style, 'display:none' # 'visibility:hidden'
+ unless scalar(@counties) > 1;
+
+my $style =
+ scalar(@style)
+ ? 'STYLE="'. join(';', @style). '"'
+ : '';
+my $address2_label_style =
+ scalar(@address2_label_style)
+ ? 'STYLE="'. join(';', @address2_label_style). '"'
+ : '';
+my $county_style =
+ scalar(@county_style)
+ ? 'STYLE="'. join(';', @county_style). '"'
+ : '';
+
+my %select_hash = (
+ 'city' => $object->get($pre.'city'),
+ 'county' => $object->get($pre.'county'),
+ 'state' => $object->get($pre.'state'),
+ 'country' => $object->get($pre.'country'),
+ 'prefix' => $pre,
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+ 'style' => \@style,
+);
+
+my $th = $opt{'no_bold'} ? 'TD' : 'TH';
+
+</%init>
diff --git a/httemplate/elements/logout.html b/httemplate/elements/logout.html
new file mode 100644
index 0000000..313dbfa
--- /dev/null
+++ b/httemplate/elements/logout.html
@@ -0,0 +1,44 @@
+<%doc>
+
+Example:
+
+ include( '/elements/logout.html');
+ This is the <a href="javascript:void()" onClick="logout();">logout</a> link.
+
+</%doc>
+<SCRIPT TYPE="text/javascript">
+
+ function logout() {
+ // count args; build URL
+ var url = "<% $fsurl. 'loginout/logout.html' %>";
+
+ var xmlhttp = rs_init_object();
+ xmlhttp.open("GET", url, false, "magic", "notyet");
+ xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
+ xmlhttp.send(null);
+
+ if (xmlhttp.readyState != 4) {
+ alert("Logout failed: readyState is " + xmlhttp.readyState);
+ return;
+ }
+
+ if (xmlhttp.status != 200) {
+ alert("Logout failed: status is " + xmlhttp.status);
+ } else {
+ var data = xmlhttp.responseText;
+ // alert('received response: ' + data);
+ if ( data.indexOf("<b>System error</b>") > -1 ) {
+ var w;
+ if ( w = window.open("about:blank") ) {
+ w.document.write(data);
+ } else {
+ // popup blocking? should use an overlib popup instead
+ alert("Error popup disabled; try disabling popup blocking to see");
+ }
+ } else {
+ window.location = "<% $fsurl. 'loginout/logout.html' %>";
+ }
+ }
+ }
+
+</SCRIPT>
diff --git a/httemplate/elements/mcp_lint.html b/httemplate/elements/mcp_lint.html
new file mode 100644
index 0000000..161415e
--- /dev/null
+++ b/httemplate/elements/mcp_lint.html
@@ -0,0 +1,40 @@
+% foreach my $lint (@lint) {
+% my $color = ( $lint =~ /unchecked$/ ? '#FF9900' : '#FF0000' );
+ <FONT COLOR="<% $color %>"><% $lint %></FONT><BR>
+% }
+
+<%init>
+
+my(%opt) = @_;
+
+my $conf = new FS::Conf;
+
+my @svc = ();
+if ( $opt{svc} ) {
+ @svc = ref($opt{svc}) ? @{ $opt{svc} } : ( $opt{svc} );
+} elsif ( $opt{cust_main} ) {
+ my $custnum = $opt{cust_main}->custnum;
+ @svc = qsearchs({
+ 'table' => 'cust_svc',
+ 'addl_from' => ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'svcpart' => $conf->config('mcp_svcpart') },
+ 'extra_sql' => " AND custnum = $custnum ",
+ });
+} else {
+ die 'neither svc nor cust_main options passed to mcp_lint';
+}
+
+
+my @lint = ();
+push @lint, 'unchecked' unless @svc;
+foreach my $svc ( @svc ) {
+ my @svc_lint = tron_lint($svc);
+ if ( scalar(@svc) > 1 ) {
+ push @lint, map $svc->title.": $_", @svc_lint;
+ } else {
+ push @lint, @svc_lint;
+ }
+}
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
new file mode 100644
index 0000000..2d28e49
--- /dev/null
+++ b/httemplate/elements/menu.html
@@ -0,0 +1,622 @@
+<script type="text/javascript" src="<%$fsurl%>elements/cssexpr.js"></script>
+
+% if ( $opt{'position'} eq 'top' ) {
+
+ <script type="text/javascript" src="<%$fsurl%>elements/xmenu.top.js"></script>
+ <link href="<%$fsurl%>elements/xmenu.top.css" type="text/css" rel="stylesheet">
+
+% } else { # elsif ( $opt{'position'} eq 'left' ) {
+
+ <script type="text/javascript" src="<%$fsurl%>elements/xmenu.js"></script>
+ <link href="<%$fsurl%>elements/xmenu.css" type="text/css" rel="stylesheet">
+
+% }
+
+% unless ( $opt{'nocss'} ) {
+ <link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+% }
+<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet">
+
+<SCRIPT TYPE="text/javascript">
+
+% my $fs_popup = include( '/elements/popup_link_onclick.html',
+% 'action' => $fsurl.'docs/about.html',
+% 'label' => 'Freeside',
+% 'style' => 'color:#999999',
+% 'actionlabel' => 'About',
+% 'width' => 300,
+% 'height' => 360,
+% 'color' => '#7e0079',
+% 'scrolling' => 'no',
+% );
+% $fs_popup =~ s/return false;//;
+ function about_freeside() {
+ <% $fs_popup |n %>
+ }
+
+ webfxMenuImagePath = "<%$fsurl%>images/";
+ webfxMenuUseHover = 1;
+ webfxMenuShowTime = 300;
+ webfxMenuHideTime = 500;
+
+ var myBar = new WebFXMenuBar;
+
+% foreach my $item ( keys %menu ) {
+%
+% my( $url_or_submenu, $tooltip ) = @{ $menu{$item} };
+%
+% if ( ref($url_or_submenu) ) {
+%
+% #warn $item;
+%
+% my( $subhtml, $submenuname ) = submenu($url_or_submenu, $item);
+
+ <% $subhtml |n %>
+ myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname |n %> ));
+
+% } else {
+
+ myBar.add(new WebFXMenuButton("<% $item %>", "<% $url_or_submenu %>", "<% $tooltip %>" ));
+
+% }
+%
+% }
+
+ myBar.show( null, 'vertical' );
+ myBar.width = 154;
+
+</SCRIPT>
+
+<%init>
+my( %opt ) = @_;
+my $conf = new FS::Conf;
+my $fsurl = $opt{'freeside_baseurl'};
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#XXX Active tickets not assigned to a customer
+
+tie my %report_prospects, 'Tie::IxHash',
+ 'List prospects' => [ $fsurl. 'search/prospect_main.html', '' ],
+ 'Advanced prospect reports' => [ $fsurl. 'search/report_prospect_main.html', '' ],
+;
+
+tie my %report_customers_lists, 'Tie::IxHash',
+ 'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ],
+ 'by last name' => [ $fsurl. 'search/cust_main.cgi?browse=last', '' ],
+ 'by company name' => [ $fsurl. 'search/cust_main.cgi?browse=company', '' ],
+;
+$report_customers_lists{'by active trouble tickets'} = [ $fsurl. 'search/cust_main.cgi?browse=tickets', '' ]
+ if $conf->config('ticket_system');
+
+tie my %report_customers_search, 'Tie::IxHash';
+$report_customers_search{'by ordering employee'} = [ $fsurl. 'search/cust_main-otaker.cgi' ]
+ if $curuser->access_right('Configuration');
+
+tie my %report_customers, 'Tie::IxHash';
+$report_customers{'List customers'} = [ \%report_customers_lists, 'List customers' ]
+ if $curuser->access_right('List customers');
+$report_customers{'Search customers'} = [ \%report_customers_search, 'Search customers' ]
+ if keys %report_customers_search;
+$report_customers{'Zip code distribution'} = [ $fsurl. 'search/report_cust_main-zip.html', 'Zip codes by number of customers' ];
+$report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_main.html', 'by status, signup date, agent, etc.' ]
+ if $curuser->access_right('List customers')
+ && $curuser->access_right('List packages');
+
+tie my %report_invoices_open, 'Tie::IxHash',
+ 'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ],
+ '15 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN15_date', 'Invoices 15 days or older with an unpaid balance' ],
+ '30 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN30_date', 'Invoices 30 days or older with an unpaid balance' ],
+ '60 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN60_date', 'Invoices 60 days or older with an unpaid balance' ],
+ '90 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN90_date', 'Invoices 90 days or older with an unpaid balance' ],
+ '120 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN120_date', 'Invoices 120 days or older with an unpaid balance' ],
+;
+
+tie my %report_invoices, 'Tie::IxHash',
+ 'Open invoices' => [ \%report_invoices_open, 'Open invoices' ],
+ 'All invoices' => [ $fsurl. 'search/cust_bill.html?date', 'List all invoices' ],
+ 'Advanced invoice reports' => [ $fsurl.'search/report_cust_bill.html', 'by agent, date range, etc.' ],
+;
+
+tie my %report_discounts, 'Tie::IxHash',
+ 'Discount graph' => [ $fsurl. 'graph/report_cust_bill_pkg_discount.html', 'Discount overview per month' ],
+ 'Discount detail' => [ $fsurl.'search/report_cust_bill_pkg_discount.html', 'Discount report (by employee and/or date range)' ],
+ #awful name
+ 'Package discounts' => [ $fsurl.'search/report_cust_pkg_discount.html', 'Active/inactive discounts by package' ],
+;
+
+tie my %report_services, 'Tie::IxHash';
+if ( $curuser->access_right('Configuration') ) {
+ $report_services{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi?orderby=active', 'Service definitions by number of active packages' ];
+ $report_services{'separator'} = '';
+}
+foreach my $svcdb ( FS::part_svc->svc_tables() ) {
+
+ my $name = "FS::$svcdb"->table_info->{'name_plural'}
+ || PL( "FS::$svcdb"->table_info->{'name'} );
+ my $lcname = lc($name);
+ my $lcsname = lc("FS::$svcdb"->table_info->{'name'});
+ my $longname = "FS::$svcdb"->table_info->{'longname_plural'} || $name;
+ my $lclongname = lc($longname);
+ my $sorts = "FS::$svcdb"->table_info->{'sorts'} || [ 'svcnum' ];
+ $sorts = [ $sorts ] unless ref($sorts);
+ my %svc_url = ( 'm' => $m,
+ 'action' => 'search',
+ 'svcdb' => $svcdb,
+ );
+
+ tie my %report_svc, 'Tie::IxHash';
+
+ foreach my $sort ( @$sorts ) {
+
+ my $field_info = FS::part_svc->svc_table_fields($svcdb)->{$sort};
+ my $label = $field_info->{'label_sort'} || 'by '.$field_info->{'label'};
+
+ my $title = "All $lcname";
+ $title .= " $label"
+ if scalar(@$sorts) > 1;
+
+ $report_svc{$title} =
+ [ svc_url( %svc_url, 'query' => "magic=all;sortby=$sort" ),
+ '',
+ ];
+ }
+
+ if ( $svcdb eq 'svc_acct' ) {
+
+ $report_svc{"All $lcname never logged in"} =
+ [ svc_url( %svc_url, 'query' => "magic=nologin;sortby=svcnum" ),
+ '',
+ ];
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+
+ $report_svc{"${name}' total usage by time period"} =
+ [ $fsurl. 'search/report_svc_phone.html',
+ 'Total usage (minutes, and amount billed) for the specified time period, per phone number.',
+ ];
+
+ }
+
+ if ( $curuser->access_right('View/link unlinked services') ) {
+ $report_svc{"Unlinked $lcname"} =
+ [ svc_url( %svc_url, 'query' => "magic=unlinked;sortby=". $sorts->[0] ),
+ "Pre-Freeside $lcname without a customer record",
+ ];
+ }
+
+ if ( $svcdb eq 'svc_acct' ) {
+ $report_svc{"Advanced $lcsname reports"} =
+ [ $fsurl."search/report_$svcdb.html", '' ];
+ }
+
+ $report_services{$name} = [ \%report_svc, $longname ];
+
+}
+
+tie my %report_packages, 'Tie::IxHash';
+if ( $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions')
+ )
+{
+ $report_packages{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ];
+ $report_packages{'separator'} = '';
+}
+if ( $curuser->access_right('Financial reports') ) {
+ $report_packages{'Package churn'} = [ $fsurl.'graph/report_cust_pkg.html', 'Orders, suspensions and cancellations summary graph' ];
+ $report_packages{'separator2'} = '';
+}
+$report_packages{'All customer packages'} = [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ];
+$report_packages{'Suspended customer packages'} = [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ];
+$report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ];
+$report_packages{'FCC Form 477 packages'} = [ $fsurl.'search/report_477.html', 'Summarize packages by census tract for particular types' ]
+ if $conf->exists('cust_main-require_censustract');
+$report_packages{'Advanced package reports'} = [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ];
+
+tie my %report_inventory, 'Tie::IxHash',
+ 'Inventory by agent' => [ $fsurl.'search/report_agent_inventory.html', '' ],
+ #'Inventory activity' => [ $fsurl.'search/report_h_inventory_item.html', '' ],
+;
+
+tie my %report_rating, 'Tie::IxHash',
+ 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ],
+ 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ],
+ 'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ],
+;
+
+tie my %report_ticketing_statistics, 'Tie::IxHash',
+ 'Tickets per day per Queue' => [ $fsurl.'rt/RTx/Statistics/CallsQueueDay', 'View the number of tickets created, resolved or deleted in a specific Queue, over the requested period of days' ],
+ 'Ticket status by Queue' => [ $fsurl.'rt/RTx/Statistics/OpenStalled', 'View numbers of new, open and stalled tickets in a selected Queue' ],
+ 'Tickets per day (multiple Queues)' => [ $fsurl.'rt/RTx/Statistics/CallsMultiQueue', 'View tickets created, resolved or deleted on in one or more Queues over a specified time period' ],
+ 'Tickets per Day of Week' => [ $fsurl.'rt/RTx/Statistics/DayOfWeek', 'View trends showing when tickets are created, resolved or deleted' ],
+ 'Time to resolve' => [ $fsurl.'rt/RTx/Statistics/Resolution', 'View how long tickets take to be resolved by Queue' ],
+ 'Time to resolve (scatter graph)' => [ $fsurl.'rt/RTx/Statistics/TimeToResolve', 'View a detailed scatter graph of time to resolve tickets by Queue' ],
+;
+
+tie my %report_ticketing, 'Tie::IxHash',
+ 'Resolved by owner' => [ $fsurl.'rt/Tools/Reports/ResolvedByOwner.html', '' ],
+ 'Resolved in date range' => [ $fsurl.'rt/Tools/Reports/ResolvedByDates.html', '' ],
+ 'Created in date range' => [ $fsurl.'rt/Tools/Reports/CreatedByDates.html', '' ],
+ 'separator' => '',
+ 'Statistics' => [ \%report_ticketing_statistics, '' ],
+ 'separator2' => '',
+ 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html', 'List tickets by any criteria' ],
+;
+
+tie my %report_bill_event, 'Tie::IxHash',
+ 'All billing events' => [ $fsurl.'search/report_cust_event.html', 'All billing events for a date range' ],
+ 'Billing event errors' => [ $fsurl.'search/report_cust_event.html?failed=1', 'Failed credit cards, processor or printer problems, etc.' ],
+# 'All invoice events' => [ $fsurl.'search/cust_bill_event.html', 'Reports on deprecated, old-style invoice events for a date range' ],
+# 'Invoice event errors' => [ $fsurl.'search/cust_bill_event.html?failed=1', 'Reports on deprecated, old-style events for failed credit cards, processor or printer problems, etc.' ],
+;
+
+tie my %report_payments, 'Tie::IxHash',
+ 'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
+;
+$report_payments{'Pending Payments'} = [ $fsurl.'search/cust_pay_pending.html?magic=_date;statusNOT=done', 'Pending real-time payments' ]
+ if $curuser->access_right('View customer pending payments');
+$report_payments{'Voided Payments'} = [ $fsurl.'search/report_cust_pay.html?void=1', 'Voided payment report (by type and/or date range)' ]
+ if $curuser->access_right('View customer pending payments');
+$report_payments{'Payment Batches'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ]
+ if $conf->exists('batch-enable') || $conf->config('batch-enable_payby');
+$report_payments{'Unapplied Payment Aging'} = [ $fsurl.'search/report_unapplied_cust_pay.html', 'Unapplied payment aging report' ];
+
+tie my %report_financial, 'Tie::IxHash';
+if($curuser->access_right('Financial reports')) {
+
+ %report_financial = (
+ 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ],
+ 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
+ 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
+ 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ],
+ 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
+ 'Refund Report' => [ $fsurl.'search/report_cust_refund.html', 'Refund report (by type and/or date range)' ],
+ 'Package Costs Report' => [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ],
+ );
+ $report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
+ $report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ];
+ $report_financial{'Sales Tax Liability'} = [ $fsurl.'search/report_tax.html', 'Sales tax liability report (internal taxclass system)' ];
+ $report_financial{'Tax Liability'} = [ $fsurl.'search/report_newtax.html', 'Tax liability report (vendor data tax products system)' ]
+ if $conf->exists('enable_taxproducts');
+
+} elsif($curuser->access_right('Receivables report')) {
+
+ $report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
+
+} # else $report_financial contains nothing.
+
+tie my %report_menu, 'Tie::IxHash';
+$report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ]
+ if $curuser->access_right('List prospects');
+$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ]
+ if $curuser->access_right('List customers');
+$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ]
+ if $curuser->access_right('List invoices');
+$report_menu{'Discounts'} = [ \%report_discounts, 'Discount reports' ]
+ if $curuser->access_right('Financial reports');
+$report_menu{'Payments'} = [ \%report_payments, 'Payment reports' ]
+ if $curuser->access_right('Financial reports');
+$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ]
+ if $curuser->access_right('List packages');
+$report_menu{'Services'} = [ \%report_services, 'Services reports' ]
+ if $curuser->access_right('List services');
+$report_menu{'Inventory'} = [ \%report_inventory, 'Inventory reports' ]
+ if $curuser->access_right('Configuration'); #XXX List inventory?
+$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ]
+ if $curuser->access_right('List rating data');
+$report_menu{'Tickets'} = [ \%report_ticketing, 'Ticket reports' ]
+ if $conf->config('ticket_system')
+ ;#&& FS::TicketSystem->access_right(\%session, 'Something');
+$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ]
+ if $curuser->access_right('Billing event reports');
+$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ]
+ if $curuser->access_right('Financial reports')
+ or $curuser->access_right('Receivables report');
+$report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query' ]
+ if $curuser->access_right('Raw SQL');
+
+tie my %tools_importing, 'Tie::IxHash',
+ 'Customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
+ 'Customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
+ 'One-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
+ 'Payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
+ 'Phone numbers (DIDs)' => [ $fsurl.'misc/phone_avail-import.html', '' ],
+ 'Call Detail Records (CDRs)' => [ $fsurl.'misc/cdr-import.html', '' ],
+# 'Import call rates and regions' => [ $fsurl.'misc/rate-import.html', '' ],
+;
+if ( $conf->exists('enable_taxproducts') ) {
+ if ( $conf->exists('taxdatadirectdownload') ) {
+ $tools_importing{'Tax rates from vendor site'} =
+ [ $fsurl.'misc/tax-fetch_and_import.cgi', '' ];
+ } else {
+ $tools_importing{'Tax rates from CSV files'} =
+ [ $fsurl.'misc/tax-import.cgi', '' ];
+ }
+}
+
+tie my %tools_exporting, 'Tie::IxHash',
+ 'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ],
+;
+
+# <!-- <BR>View active NAS ports:
+# <A HREF="browse/nas.cgi">session server</A> -->
+# <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A>
+# <BR> -->
+
+tie my %tools_ticketing, 'Tie::IxHash',
+ 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ],
+ 'My Day' => [ $fsurl.'rt/Tools/MyDay.html', '' ],
+ 'My Approvals' => [ $fsurl.'rt/Approvals/', '' ],
+;
+$tools_ticketing{'Cron Tool'} = [ $fsurl.'rt/Developer/CronTool/', '' ]
+ if $conf->exists('rt-crontool');
+
+tie my %tools_menu, 'Tie::IxHash', ();
+$tools_menu{'Quick payment entry'} = [ $fsurl.'misc/batch-cust_pay.html', 'Enter multiple payments in a batch' ]
+ if $curuser->access_right('Post payment batch');
+$tools_menu{'Process payment batches'} = [ $fsurl.'search/pay_batch.cgi?magic=_date;open=1;intransit=1', 'Process credit card and electronic check batches' ]
+ if ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
+ && $curuser->access_right('Process batches');
+$tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queue' ]
+ if $curuser->access_right('Job queue');
+$tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ]
+ if $conf->config('ticket_system');
+$tools_menu{'Time Queue'} = [ $fsurl.'search/report_timeworked.html', 'View pending support time' ]
+ if $curuser->access_right('Time queue');
+$tools_menu{'Attachments'} = [ $fsurl.'browse/cust_attachment.html', 'View customer attachments' ]
+ if !$conf->config('disable_cust_attachment') and $curuser->access_right('View attachments') and $curuser->access_right('Browse attachments');
+$tools_menu{'Importing'} = [ \%tools_importing, 'Import tools' ]
+ if $curuser->access_right('Import');
+$tools_menu{'Exporting'} = [ \%tools_exporting, 'Export tools' ]
+ if $curuser->access_right('Export');
+
+tie my %config_employees, 'Tie::IxHash',
+ 'Employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ],
+ 'Employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ],
+;
+
+tie my %config_export_svc, 'Tie::IxHash', ();
+if ( $curuser->access_right('Configuration') ) {
+ $config_export_svc{'Exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
+ $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
+}
+
+tie my %config_pkg_reason, 'Tie::IxHash',
+ 'Cancel reasons' => [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ],
+ 'Cancel reason types' => [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons.' ],
+ 'Suspend reasons' => [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ],
+ 'Suspend reason types' => [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons.' ],
+;
+
+tie my %config_pkg, 'Tie::IxHash', ();
+$config_pkg{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ]
+ if $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions');
+if ( $curuser->access_right('Configuration') ) {
+
+ #package grouping sub-menu?
+ $config_pkg{'Package classes'} = [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for taxation, ordering convenience and reporting.' ];
+ $config_pkg{'Package categories'} = [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes.' ];
+ $config_pkg{'Package report classes'} = [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting only.' ];
+ #eo package grouping sub-menu
+
+ $config_pkg{'Discounts'} = [ $fsurl.'browse/discount.html', '' ];
+ $config_pkg{'Cancel/Suspend Reasons'} = [ \%config_pkg_reason, '' ];
+}
+
+tie my %config_cust, 'Tie::IxHash',
+ 'Customer classes' => [ $fsurl.'browse/cust_class.html', 'Customer classes define groups of customers for reporting.' ],
+ 'Customer categories' => [ $fsurl.'browse/cust_category.html', 'Customer categories define groups of customer classes.' ],
+;
+
+tie my %config_agent, 'Tie::IxHash',
+ 'Agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
+ 'Agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ],
+ 'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ];
+;
+
+tie my %config_billing_rates, 'Tie::IxHash',
+ 'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
+ 'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
+ 'Usage classes' => [ $fsurl.'browse/usage_class.html', 'Usage classes define groups of usage for taxation.' ],
+ 'Edit rates with Excel' => [ $fsurl.'misc/rate_edit_excel.html', 'Download and edit rates with Excel, then upload changes.' ], #"Edit with Excel" ?
+;
+
+tie my %config_billing, 'Tie::IxHash';
+# 'Payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ];
+$config_billing{'Billing events'} = [ $fsurl.'browse/part_event.html', 'Billing actions for customers, invoices and packages' ]
+ if $curuser->access_right('Edit billing events')
+ || $curuser->access_right('Edit global billing events');
+if ( $curuser->access_right('Configuration') ) {
+ #$config_billing{'Invoice events'} = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ];
+ $config_billing{'Invoice templates'} = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ];
+ $config_billing{'Prepaid cards'} = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ];
+ $config_billing{'Call rates and regions'} = [ \%config_billing_rates, 'Manage rate plans, regions and prefixes for VoIP and call billing' ];
+
+ my $config_taxes_name = 'Locales and tax rates'.
+ ( $conf->exists('enable_taxproducts')
+ ? ' (internal tax class system)'
+ : ''
+ );
+ $config_billing{$config_taxes_name} = [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ];
+ $config_billing{'Tax rates (vendor data tax products system)'} = [ $fsurl.'browse/tax_rate.cgi', 'Edit tax rates for the vendor data tax products system' ]
+ if $conf->exists('enable_taxproducts');
+ $config_billing{'Tax classes'} = [ $fsurl. 'browse/part_pkg_taxclass.html', 'Tax classes' ];
+
+ $config_billing{'Credit reasons'} = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
+ $config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ];
+}
+
+tie my %config_ticketing, 'Tie::IxHash',
+ 'Ticketing Users' => [ $fsurl.'rt/Admin/Users', 'View/Edit ticketing users' ], #XXX to be unified
+ 'Ticketing Groups' => [ $fsurl.'rt/Admin/Groups', 'View/Edit ticketing groups and group membership' ], #XXX to be unified
+ 'Ticketing Queues' => [ $fsurl.'rt/Admin/Queues', 'View/Edit ticketing queues and queue-specific properties' ],
+ 'Ticket Custom Fields' => [ $fsurl.'rt/Admin/CustomFields', 'View/Edit ticketing custom fields' ],
+ 'Ticketing Global' => [ $fsurl.'rt/Admin/Global', 'View/Edit ticketing configuration applicable to all queues' ],
+ #"System Configuraiton"? useless, just makes people report errors about missing Module::Versions::Report #'Ticketing Tools' => [ $fsurl.'rt/Admin/Tools', '' ],
+;
+
+tie my %config_dialup, 'Tie::IxHash',
+ 'Access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ],
+;
+
+tie my %config_broadband, 'Tie::IxHash',
+ 'Routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
+ 'Address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
+;
+
+tie my %config_phone, 'Tie::IxHash',
+ 'View/Edit phone device types' => [ $fsurl.'browse/part_device.html', 'Phone device types' ],
+;
+
+tie my %config_misc, 'Tie::IxHash';
+$config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.' ]
+ if $curuser->access_right('Edit advertising sources')
+ || $curuser->access_right('Edit global advertising sources');
+if ( $curuser->access_right('Configuration') ) {
+ $config_misc{'Virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ];
+ $config_misc{'Message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ];
+}
+$config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]
+ if $curuser->access_right('Edit inventory')
+ || $curuser->access_right('Edit global inventory')
+ || $curuser->access_right('Configuration');
+
+tie my %config_menu, 'Tie::IxHash';
+if ( $curuser->access_right('Configuration' ) ) {
+ %config_menu = (
+ 'Settings' => [ $fsurl.'config/config-view.cgi', '' ],
+ 'separator' => '', #its a separator!
+ 'Employees' => [ \%config_employees, '' ],
+ );
+}
+$config_menu{'Provisioning and services'} = [ \%config_export_svc, '' ]
+ if $curuser->access_right('Configuration' );
+$config_menu{'Packages'} = [ \%config_pkg, '' ]
+ if $curuser->access_right('Configuration' )
+ || $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions');
+$config_menu{'Customers'} = [ \%config_cust, '' ]
+ if $curuser->access_right('Configuration');
+$config_menu{'Resellers'} = [ \%config_agent, '' ]
+ if $curuser->access_right('Configuration');
+$config_menu{'Billing'} = [ \%config_billing, '' ]
+ if $curuser->access_right('Edit billing events')
+ || $curuser->access_right('Edit global billing events');
+$config_menu{'Ticketing'} = [ \%config_ticketing, '' ]
+ if $conf->config('ticket_system')
+ && FS::TicketSystem->access_right(\%session, 'ShowConfigTab');
+$config_menu{'Dialup'} = [ \%config_dialup, '' ]
+ if ( $curuser->access_right('Dialup configuration') );
+$config_menu{'Broadband'} = [ \%config_broadband, '' ]
+ if ( $curuser->access_right('Broadband configuration') );
+$config_menu{'Phone'} = [ \%config_phone, '' ]
+ if ( $curuser->access_right('Configuration') );
+$config_menu{'Miscellaneous'} = [ \%config_misc, '' ]
+ if $curuser->access_right('Edit advertising sources')
+ || $curuser->access_right('Edit global advertising sources');
+
+
+my $wiki = 'http://www.freeside.biz/mediawiki/index.php';
+my $doc_link = $conf->config('support-key')
+ ? "$wiki/Supported:Documentation"
+ : $curuser->access_right('Configuration')
+ ? "$wiki/Freeside:1.9:Documentation"
+ : "$wiki/Freeside:1.9:Documentation:User";
+
+eval "use RT;"
+ if $conf->config('ticket_system') eq 'RT_Internal';
+
+tie my %help_menu, 'Tie::IxHash', 'Billing documentation' => [ $doc_link, 'Freeside documentation' ];
+$help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Request Tracker Wiki' ]
+ if $conf->config('ticket_system') eq 'RT_Internal';
+$help_menu{'separator'} = '';
+$help_menu{"About Freeside v$FS::VERSION"} = [ "javascript:about_freeside()", '' ];
+$help_menu{"About RT v$RT::VERSION"} = [ 'http://www.bestpractical.com/rt', 'Request Tracker Homepage' ]
+ if $conf->config('ticket_system') eq 'RT_Internal';
+
+
+tie my %menu, 'Tie::IxHash',
+ 'Billing Main' => [ $fsurl, 'Billing start page', ],
+;
+if ( $conf->config('ticket_system') ) {
+ $menu{'Ticketing Main'} =
+ [
+ ( $conf->config('ticket_system') eq 'RT_External'
+ ? FS::TicketSystem->baseurl()
+ : $fsurl.'rt/'
+ ),
+ 'Ticketing start page',
+ ],
+}
+$menu{'New prospect'} = [ $fsurl.'edit/prospect_main.html', 'Add a new prospect' ]
+ if $curuser->access_right('New prospect');
+$menu{'New customer'} = [ $fsurl.'edit/cust_main.cgi', 'Add a new customer' ]
+ if $curuser->access_right('New customer');
+$menu{'Reports'} = [ \%report_menu, 'Lists, reporting and graphing' ]
+ if keys %report_menu;
+$menu{'Tools'} = [ \%tools_menu, 'Tools' ]
+ if keys %tools_menu;
+$menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ]
+ if $curuser->access_right('Configuration')
+ || $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions')
+ || $curuser->access_right('Edit billing events')
+ || $curuser->access_right('Edit global billing events')
+ || $curuser->access_right('Dialup configuration')
+ || $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Phone configuration')
+ || $curuser->access_right('Edit advertising sources')
+ || $curuser->access_right('Edit global advertising sources');
+$menu{'Help'} = [ \%help_menu, '' ];
+
+
+use vars qw($gmenunum);
+$gmenunum = 0;
+
+sub submenu {
+ my($submenu, $title) = @_;
+ my $menunum = $gmenunum++;
+
+ #return two args: html, menuname
+
+ "var myMenu$menunum = new WebFXMenu;\n".
+ #"myMenu$menunum.useAutoPosition = true;\n".
+# "myMenu$menunum.emptyText = '$title';\n".
+ "myMenu$menunum.emptyText = '';\n".
+
+ (
+ join("\n", map {
+
+ if ( !ref( $submenu->{$_} ) ) {
+
+ "myMenu$menunum.add(new WebFXMenuSeparator());";
+
+ } else {
+
+ my($url_or_submenu, $tooltip ) = @{ $submenu->{$_} };
+ if ( ref($url_or_submenu) ) {
+
+ my($subhtml, $submenuname ) = submenu($url_or_submenu, $_); #mmm, recursion
+
+ "$subhtml\n".
+ "myMenu$menunum.add(new WebFXMenuItem(\"$_\", null, \"$tooltip\", $submenuname ));";
+
+ } else {
+
+ "myMenu$menunum.add(new WebFXMenuItem(\"$_\", \"$url_or_submenu\", \"$tooltip\" ));";
+
+ }
+
+ }
+
+ } keys %$submenu )
+ ). "\n".
+ "myMenu$menunum.width = 256;\n",
+
+ "myMenu$menunum";
+
+}
+
+</%init>
+
diff --git a/httemplate/elements/menuarrow.gif b/httemplate/elements/menuarrow.gif
new file mode 100644
index 0000000..ed2dee0
--- /dev/null
+++ b/httemplate/elements/menuarrow.gif
Binary files differ
diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html
new file mode 100644
index 0000000..fe49f7b
--- /dev/null
+++ b/httemplate/elements/menubar.html
@@ -0,0 +1,59 @@
+<%doc>
+
+Example:
+
+ include( '/elements/menubar.html',
+
+ #options hashref (optional)
+ { 'newstyle' => 1, #may become the default at some point
+ 'url_base' => '', #prepended to menubar URLs, for convenience
+ 'selected' => '', #currently selected label
+ },
+
+ #menubar entries (required)
+ 'label' => $url,
+ 'label2' => $url2,
+ #etc.
+
+ );
+
+</%doc>
+%if ( $opt->{'newstyle'} ) {
+% my $s = '<FONT STYLE="border-bottom:1px solid #7e0079">';
+
+ <% join("$s &nbsp; </FONT>", ( '', @html, '' ) ) %>
+ <BR>
+
+%} else {
+
+ <% join(' | ', @html) %>
+
+%}
+<%init>
+
+my $opt = ref($_[0]) ? shift : {};
+
+my $url_base = $opt->{'url_base'};
+
+my @html;
+while (@_) {
+
+ my ($item, $url) = splice(@_,0,2);
+ next if $item =~ /^\s*Main\s+Menu\s*$/i;
+
+ my $style = '';
+ if ( $opt->{'newstyle'} ) {
+
+ my $dclass = $item eq $opt->{'selected'}
+ ? 'fstabselected'
+ : 'fstab';
+
+ $style = qq( CLASS="$dclass" );
+
+ }
+
+ push @html, qq!<A HREF="$url_base$url" $style>$item</A>!;
+
+}
+
+</%init>
diff --git a/httemplate/elements/overlibmws.js b/httemplate/elements/overlibmws.js
new file mode 100644
index 0000000..6a446ab
--- /dev/null
+++ b/httemplate/elements/overlibmws.js
@@ -0,0 +1,621 @@
+/*
+ Do not remove or change this notice.
+ overlibmws.js core module - Copyright Foteos Macrides 2002-2010. All rights reserved.
+ Initial: August 18, 2002 - Last Revised: January 5, 2010
+ This module is subject to the same terms of usage as for Erik Bosrup's overLIB,
+ though only a minority of the code and API now correspond with Erik's version.
+ See the overlibmws Change History and Command Reference via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+ Give credit on sites that use overlibmws and submit changes so others can use them as well.
+ You can get Erik's version via: http://www.bosrup.com/web/overlib/
+*/
+
+// PRE-INIT -- Ignore these lines, configuration is below.
+var OLloaded=0,OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0,
+OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLmodalPI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0,OLprintPI=0,
+pmCnt=1,pMtr=new Array(),OLcmdLine=new Array(),OLrunTime=new Array(),OLv,OLudf,OLrefXY,
+OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%");if(typeof OLgateOK=='undefined')var OLgateOK=1;
+var OLp1or2c='inarray,caparray,caption,closetext,right,left,center,autostatuscap,padx,pady,below,above,vcenter,donothing',
+OLp1or2co='nofollow,background,offsetx,offsety,fgcolor,bgcolor,cgcolor,textcolor,capcolor,width,wrap,wrapmax,height,border,'
++'base,status,autostatus,snapx,snapy,fixx,fixy,relx,rely,midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,'
++'cgbackground,fullhtml,capicon,textfont,captionfont,textsize,captionsize,timeout,delay,hauto,vauto,nojustx,nojusty,fgclass,'
++'bgclass,cgclass,capbelow,textpadding,textfontclass,captionpadding,captionfontclass,sticky,noclose,mouseoff,offdelay,'
++'closecolor,closefont,closesize,closeclick,closetitle,closefontclass,decode',OLp1or2o='text,cap,close,hpos,vpos,padxl,'
++'padxr,padyt,padyb',OLp1co='label',OLp1or2=OLp1or2co+','+OLp1or2o,OLp1=OLp1co+','+'frame';
+OLregCmds(OLp1or2c+','+OLp1or2co+','+OLp1co);
+function OLud(v){return eval('typeof ol_'+v+'=="undefined"')?1:0;}
+
+// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions
+if(OLud('fgcolor'))var ol_fgcolor="#ccccff";
+if(OLud('bgcolor'))var ol_bgcolor="#333399";
+if(OLud('cgcolor'))var ol_cgcolor="#333399";
+if(OLud('textcolor'))var ol_textcolor="#000000";
+if(OLud('capcolor'))var ol_capcolor="#ffffff";
+if(OLud('closecolor'))var ol_closecolor="#eeeeff";
+if(OLud('textfont'))var ol_textfont="Verdana,Arial,Helvetica";
+if(OLud('captionfont'))var ol_captionfont="Verdana,Arial,Helvetica";
+if(OLud('closefont'))var ol_closefont="Verdana,Arial,Helvetica";
+if(OLud('textsize'))var ol_textsize=1;
+if(OLud('captionsize'))var ol_captionsize=1;
+if(OLud('closesize'))var ol_closesize=1;
+if(OLud('fgclass'))var ol_fgclass="";
+if(OLud('bgclass'))var ol_bgclass="";
+if(OLud('cgclass'))var ol_cgclass="";
+if(OLud('textpadding'))var ol_textpadding=2;
+if(OLud('textfontclass'))var ol_textfontclass="";
+if(OLud('captionpadding'))var ol_captionpadding=2;
+if(OLud('captionfontclass'))var ol_captionfontclass="";
+if(OLud('closefontclass'))var ol_closefontclass="";
+if(OLud('close'))var ol_close="Close";
+if(OLud('closeclick'))var ol_closeclick=0;
+if(OLud('closetitle'))var ol_closetitle="Click to Close";
+if(OLud('text'))var ol_text="Default Text";
+if(OLud('cap'))var ol_cap="";
+if(OLud('capbelow'))var ol_capbelow=0;
+if(OLud('background'))var ol_background="";
+if(OLud('width'))var ol_width=200;
+if(OLud('wrap'))var ol_wrap=0;
+if(OLud('wrapmax'))var ol_wrapmax=0;
+if(OLud('height'))var ol_height= -1;
+if(OLud('border'))var ol_border=1;
+if(OLud('base'))var ol_base=0;
+if(OLud('offsetx'))var ol_offsetx=10;
+if(OLud('offsety'))var ol_offsety=10;
+if(OLud('sticky'))var ol_sticky=0;
+if(OLud('nofollow'))var ol_nofollow=0;
+if(OLud('noclose'))var ol_noclose=0;
+if(OLud('mouseoff'))var ol_mouseoff=0;
+if(OLud('offdelay'))var ol_offdelay=300;
+if(OLud('hpos'))var ol_hpos=RIGHT;
+if(OLud('vpos'))var ol_vpos=BELOW;
+if(OLud('status'))var ol_status="";
+if(OLud('autostatus'))var ol_autostatus=0;
+if(OLud('snapx'))var ol_snapx=0;
+if(OLud('snapy'))var ol_snapy=0;
+if(OLud('fixx'))var ol_fixx= -1;
+if(OLud('fixy'))var ol_fixy= -1;
+if(OLud('relx'))var ol_relx=null;
+if(OLud('rely'))var ol_rely=null;
+if(OLud('midx'))var ol_midx=null;
+if(OLud('midy'))var ol_midy=null;
+if(OLud('ref'))var ol_ref="";
+if(OLud('refc'))var ol_refc='UL';
+if(OLud('refp'))var ol_refp='UL';
+if(OLud('refx'))var ol_refx=0;
+if(OLud('refy'))var ol_refy=0;
+if(OLud('fgbackground'))var ol_fgbackground="";
+if(OLud('bgbackground'))var ol_bgbackground="";
+if(OLud('cgbackground'))var ol_cgbackground="";
+if(OLud('padxl'))var ol_padxl=1;
+if(OLud('padxr'))var ol_padxr=1;
+if(OLud('padyt'))var ol_padyt=1;
+if(OLud('padyb'))var ol_padyb=1;
+if(OLud('fullhtml'))var ol_fullhtml=0;
+if(OLud('capicon'))var ol_capicon="";
+if(OLud('frame'))var ol_frame=self;
+if(OLud('timeout'))var ol_timeout=0;
+if(OLud('delay'))var ol_delay=0;
+if(OLud('hauto'))var ol_hauto=0;
+if(OLud('vauto'))var ol_vauto=0;
+if(OLud('nojustx'))var ol_nojustx=0;
+if(OLud('nojusty'))var ol_nojusty=0;
+if(OLud('label'))var ol_label="";
+if(OLud('decode'))var ol_decode=0;
+// ARRAY CONFIGURATION - See overlibConfig.txt for descriptions.
+if(OLud('texts'))var ol_texts=new Array("Text 0","Text 1");
+if(OLud('caps'))var ol_caps=new Array("Caption 0","Caption 1");
+// END CONFIGURATION -- Don't change anything below, all configuration is above.
+
+// INIT -- Runtime variables.
+var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0,o3_offdelay=300,o3_hpos=RIGHT,
+o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="",o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",
+o3_width=200,o3_wrap=0,o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0,o3_snapy=0,
+o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null,o3_ref="",o3_refc='UL',o3_refp='UL',o3_refx=0,
+o3_refy=0,o3_fgbackground="",o3_bgbackground="",o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0,o3_fullhtml=0,
+o3_vpos=BELOW,o3_capicon="",o3_textfont="Verdana,Arial,Helvetica",o3_captionfont="",o3_closefont="",o3_textsize=1,OLcC=null,
+o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0,o3_hauto=0,o3_vauto=0,o3_nojustx=0,o3_nojusty=0,
+o3_close="",o3_closeclick=0,o3_closetitle="",o3_fgclass="",o3_bgclass="",o3_cgclass="",o3_textpadding=2,o3_textfontclass="",
+o3_captionpadding=2,o3_captionfontclass="",o3_closefontclass="",o3_capbelow=0,o3_label="",o3_decode=0,
+CSSOFF=DONOTHING,CSSCLASS=DONOTHING,over=null,OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0,OLfnRef="",OLhover=0,OLx=0,OLy=0,
+OLshowingsticky=0,OLallowmove=0,OLoverHTML="",OLover2HTML="",OLifRef="",OLo2Ref="",OLifX=0,OLifY=0,
+OLua=(OLv=navigator.userAgent)?OLv.toLowerCase():'',
+OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4)?1:0,
+OLns6=(document.getElementById)?1:0,
+OLie4=(document.all)?1:0,
+OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0,
+OLmac=(OLua.indexOf('mac')>=0)?1:0,
+OLsaf=(OLua.indexOf('safari')>=0)?1:0,
+OLkon=(OLua.indexOf('konqueror')>=0)?1:0,
+OLkht=(OLsaf||OLkon)?1:0,
+OLopr=(OLua.indexOf('opera')>=0)?1:0,
+OLop7=(OLopr&&document.createTextNode)?1:0;
+OLop95=(OLop7&&document.getElementsByClassName)?1:0;
+if(OLopr){OLns4=OLns6=OLgek=0;OLie4=(OLop7)?1:0;}
+var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0,
+OLie5=0,OLie55=0,OLie7=0;OLie8=0;if(OLie4&&!OLop7){
+if((OLv=OLua.match(/msie (\d\.\d+)\.*/i))&&(OLv=parseFloat(OLv[1]))>=5.0){
+OLie5=1;OLns6=0;if(OLv>=5.5)OLie55=1;if(OLv>=7.0)OLie7=1;if(OLv>=8.0)OLie8=1;}if(OLns6)OLie4=0;}
+if(OLns4)window.onresize=function(){location.reload();};var OLchkMh=1,OLdw;
+if(OLns4||OLie4||OLns6){OLmh();if(window.addEventListener)window.addEventListener("unload",
+OLulCl,false);}else{overlib=nd=cClick=OLpageDefaults=no_overlib;}
+function OLulCl(){if(over)cClick();window.removeEventListener("unload",OLulCl,false);}
+
+/*
+ PUBLIC FUNCTIONS
+*/
+// Loads defaults then args into runtime variables.
+function overlib(){
+if(!(OLloaded&&OLgateOK))return;if((OLexclusivePI)&&OLisExclusive(arguments))return true;if(OLchkMh)OLmh();if(OLndt&&
+!OLtimerid)OLndt=0;if(over){if(OLfilterPI)o3_filter=0;cClick();}if(parent!=self){if(parent.OLo2Ref){parent.OLeval(
+parent.OLo2Ref);parent.OLo2Ref="";}if(parent.OLifRef){parent.OLeval(parent.OLifRef);parent.OLifRef="";}}if(OLo2Ref){
+eval(OLo2Ref);OLo2Ref="";}if(OLifRef){eval(OLifRef);OLifRef="";}OLload(OLp1or2);OLload(OLp1);OLfnRef="";OLifX=0;OLifY=0;
+OLhover=0;if(OLcrossframePI&&parent!=self)OLchkFRAME(arguments);OLsetRunTimeVar();OLparseTokens('o3_',arguments);if(!(
+over=OLmkLyr()))return false;over.onmouseover=over.onmouseout=null;if(o3_decode)OLdecode();if(OLprintPI)OLchkPrint();
+if(OLbubblePI)OLchkForBubbleEffect();if(OLdebugPI)OLsetDebugCanShow();if(OLshadowPI)OLinitShadow();if(OLiframePI)OLinitIfs();
+if(OLfilterPI)OLinitFilterLyr();if(OLexclusivePI&&o3_exclusive&&o3_exclusivestatus!="")o3_status=o3_exclusivestatus;else
+if(o3_autostatus==2&&o3_cap!="")o3_status=o3_cap;else if(o3_autostatus==1&&o3_text!="")o3_status=o3_text;if(!o3_delay){
+return OLmain();}else{OLdelayid=setTimeout("OLmain()",o3_delay);if(o3_status!=""){self.status=o3_status;return true;}else
+if(!(OLop7&&event&&event.type=='mouseover'))return false;}
+}
+function OLeval(s){eval(s);}
+
+// Clears popups if appropriate
+function nd(time){
+if(OLloaded&&OLgateOK){if(!((OLexclusivePI)&&OLisExclusive())){if(time&&over&&!o3_delay){if(OLtimerid>0)
+clearTimeout(OLtimerid);OLtimerid=(OLhover&&!OLcursorOff())?0:setTimeout("cClick()",(o3_timeout=OLndt=time));
+}else{if(!OLshowingsticky){OLallowmove=0;if(over)OLhideObject(over);}}}}return false;
+}
+
+// Close function for stickies
+function cClick(){
+if(OLloaded&&OLgateOK){OLhover=0;if(over){if(OLo2Ref){eval(OLo2Ref);OLo2Ref="";}if(OLovertwoPI&&over==over2)cClick2();
+OLhideObject(over);OLshowingsticky=0;OLallowmove=0;}if(OLmodalPI)OLclearModal();}return false;
+}
+
+// Sets page-specific defaults.
+function OLpageDefaults(){
+OLparseTokens('ol_',arguments);
+}
+
+// Gets object referenced by its id or name
+function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);}
+
+// For unsupported browsers.
+function no_overlib(){return false;}
+
+/*
+ OVERLIB MAIN FUNCTION SET
+*/
+function OLmain(){
+o3_delay=0;if(parent!=self&&o3_frame==parent&&parent.OLscrollPI&&parent.over)parent.OLclearScroll();if(o3_noclose)
+OLoptMOUSEOFF(0);else if(o3_mouseoff)OLoptMOUSEOFF(1);if(o3_sticky){OLshowingsticky=1;if(OLfnRef&&parent!=self&&
+o3_frame==parent&&parent.overlib)parent.OLifRef=(OLfilterPI?OLfnRef+'o3_filter=0;':'')+OLfnRef+'cClick();';}OLdoLyr();
+OLallowmove=0;if(o3_timeout>0){if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_timeout);}
+OLchkRef();OLdisp(o3_status);if(OLdraggablePI)OLcheckDrag();if(o3_status!="")return true;else if(!(OLop7&&event&&
+event.type=='mouseover'))return false;
+}
+function OLchkRef(){
+if(o3_ref){OLrefXY=OLgetRefXY(o3_ref);if(OLrefXY[0]==null&&OLcrossframePI)OLchkIfRef();
+if(OLrefXY[0]==null){o3_ref="";o3_midx=0;o3_midy=0;}}
+}
+
+// Loads o3_ variables
+function OLload(c){var i,m=c.split(',');for(i=0;i<m.length;i++)eval('o3_'+m[i]+'=ol_'+m[i]);}
+
+// Chooses LGF
+function OLdoLGF(){
+return (o3_background!=''||o3_fullhtml)?OLcontentBackground(o3_text,o3_background,o3_fullhtml):(o3_cap=="")?
+OLcontentSimple(o3_text):(o3_sticky)?OLcontentCaption(o3_text,o3_cap,o3_close):OLcontentCaption(o3_text,o3_cap,'');
+}
+
+// Makes Layer
+function OLmkLyr(id,f,z){
+id=(id||'overDiv');f=(f||o3_frame);z=(z||1000);var fd=f.document,d=OLgetRefById(id,fd);if(!d){if(OLns4)d=fd.layers[id]=
+new Layer(1024,f);else if(OLie4&&!OLop7){fd.body.insertAdjacentHTML('AfterBegin','<div id="'+id+'"></div>');d=fd.all[id];}
+else{d=fd.createElement('div');if(d){d.id=id;fd.body.appendChild(d);}}if(!d)return null;if(OLns4)d.zIndex=z;else{var o=
+d.style;o.position='absolute';o.visibility='hidden';o.zIndex=z;}}return d;
+}
+
+// Creates and writes layer content
+function OLdoLyr(){
+if(o3_sticky&&OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}if(o3_background==''&&!o3_fullhtml){
+if(o3_fgbackground!='')o3_fgbackground=' background="'+o3_fgbackground+'"';
+if(o3_bgbackground!='')o3_bgbackground=' background="'+o3_bgbackground+'"';
+if(o3_cgbackground!='')o3_cgbackground=' background="'+o3_cgbackground+'"';
+if(o3_fgcolor!='')o3_fgcolor=' bgcolor="'+o3_fgcolor+'"';if(o3_bgcolor!='')o3_bgcolor=' bgcolor="'+o3_bgcolor+'"';
+if(o3_cgcolor!='')o3_cgcolor=' bgcolor="'+o3_cgcolor+'"';if(o3_height>0)o3_height=(OLns4)?' height="'+o3_height+'"':
+' style="height:'+o3_height+'px;"';else o3_height='';}if(!OLns4)OLrepositionTo(over,(OLns6?20:0),0);var lyrHtml=OLdoLGF();
+if(o3_wrap&&!o3_fullhtml){OLlayerWrite(lyrHtml);o3_width=(OLns4?over.clip.width:over.offsetWidth);if(OLie4&&!OLop95){
+var w=OLfd().clientWidth;if(o3_width>=w){if(OLop7){if(OLovertwoPI&&over==over2){var z=over2.style.zIndex;
+o3_frame.document.body.removeChild(over);over2=OLmkLyr('overDiv2',o3_frame,z);over=over2;}else{
+o3_frame.document.body.removeChild(over);over=OLmkLyr();}}o3_width=w-20;}}
+if(o3_wrapmax<1&&o3_frame.innerWidth)o3_wrapmax=o3_frame.innerWidth-40;
+if(o3_wrapmax>0&&o3_width>o3_wrapmax)o3_width=o3_wrapmax;o3_wrap=0;lyrHtml=OLdoLGF();}OLlayerWrite(lyrHtml);
+o3_width=(OLns4?over.clip.width:over.offsetWidth);if(OLbubblePI)OLgenerateBubble(lyrHtml);
+}
+
+/*
+ LAYER GENERATION FUNCTIONS
+*/
+// Makes simple table without caption
+function OLcontentSimple(txt){
+var t=OLbgLGF()+OLfgLGF(txt)+OLbaseLGF();OLsetBackground('');return t;
+}
+
+// Makes table with caption and optional close link
+function OLcontentCaption(txt,title,close){
+var closing=(OLprintPI?OLprintCapLGF():''),closeevent='onmouseover',caption,t,cC='javascript:return '+OLfnRef
++(OLovertwoPI&&over==over2?'cClick2();':'cClick();');if(o3_closeclick)closeevent=(o3_closetitle?'title="'
++o3_closetitle+'" ':'')+'onclick';if(o3_capicon!=''&&o3_capicon.indexOf('<img')!=0)o3_capicon='<img src="'+o3_capicon
++'" /> ';if(close){closing+='<td align="right"><a href="'+cC+'" '+closeevent+'="'+cC+'"'+(o3_closefontclass?' class="'
++o3_closefontclass+'">':(OLns4?'><':'')+OLlgfUtil(0,1,'','a',o3_closecolor,o3_closefont,o3_closesize))+close+
+(o3_closefontclass?'':(OLns4?OLlgfUtil(1,1,'','a'):''))+'</a></td>';}caption='<table id="overCap'
++(OLovertwoPI&&over==over2?'2':'')+'"'+OLwd(0)+' border="0" cellpadding="'+o3_captionpadding+'" cellspacing="0"'
++(o3_cgclass?' class="'+o3_cgclass+'"':o3_cgcolor+o3_cgbackground)+'><tr><td'+OLwd(0)+(o3_cgclass?' class="'
++o3_cgclass+'">':'>')+(o3_captionfontclass?'<div'+OLhL(1)+' class="'+o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',
+o3_capcolor,o3_captionfont,o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+'</td>'+closing+'</tr></table>';
+t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF();OLsetBackground('');return t;
+}
+
+// For BACKGROUND and FULLHTML commands
+function OLcontentBackground(txt,image,hasfullhtml){
+var t;if(hasfullhtml){t=txt;}else{t='<table'+OLwd(1)+' border="0" cellpadding="0" '+'cellspacing="0" '+'height="'
++o3_height+'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'+o3_padxl+'"></td><td valign="top"'
++OLwd(2)+'>'+OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+OLlgfUtil(1,0,'','div')
++'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'+o3_padyb+'"></td></tr></table>';}
+OLsetBackground(image);return t;
+}
+
+// LGF utilities
+function OLbgLGF(){
+return '<table'+OLwd(1)+o3_height+' border="0" cellpadding="'+o3_border+'" cellspacing="0"'+(o3_bgclass?' class="'
++o3_bgclass+'"':o3_bgcolor+o3_bgbackground)+'><tr><td>';
+}
+function OLfgLGF(t){
+return '<table'+OLwd(0)+o3_height+' border="0" cellpadding="'+o3_textpadding+'" cellspacing="0"'+(o3_fgclass?' class="'
++o3_fgclass+'"':o3_fgcolor+o3_fgbackground)+'><tr><td valign="top"'+(o3_fgclass?' class="'+o3_fgclass+'"':'')+'>'
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t+(OLprintPI?OLprintFgLGF():'')
++OLlgfUtil(1,0,'','div')+'</td></tr></table>';
+}
+function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){
+if(end)return('</'+(OLns4?'font'+(stg?'></strong':''):ele)+'>');else return(tfc?'<div'+OLhL(1)+' class="'+tfc+'">':
+((ele=='a'?'':'<')+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="'+OLquoteMultiNameFonts(fac)+'" size="'
++siz:(ele=='a'?'':ele)+' style="'+((ele=='div')?OLhL(0):'')+'color:'+col+(stg?';font-weight:bold':'')+';font-family:'
++OLquoteMultiNameFonts(fac)+';font-size:'+siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">'));
+}
+function OLquoteMultiNameFonts(f){
+var i,v,pM=f.split(',');for(i=0;i<pM.length;i++){v=pM[i];v=v.replace(/^\s+/,'').replace(/\s+$/,'');
+if(/\s/.test(v) && !/['"]/.test(v)){v="\'"+v+"\'";pM[i]=v;}}return pM.join();
+}
+function OLbaseLGF(){
+return ((o3_base>0&&!o3_wrap)?('<table width="100%" border="0" cellpadding="0" cellspacing="0"'+(o3_bgclass?' class="'
++o3_bgclass+'"':'')+'><tr><td height="'+o3_base+'"></td></tr></table>'):'')+'</td></tr></table>';
+}
+function OLwd(a){return(o3_wrap?'':' width="'+(!a?'100%':(a==1?o3_width:(o3_width-o3_padxl-o3_padxr)))+'"');}
+function OLhL(s){if(!OLie5)return '';return(s?' style="overflow:auto;"':'overflow:auto;');}
+
+// Loads image into the div.
+function OLsetBackground(i){
+if(i==''){if(OLns4)over.background.src=null;else{if(OLns6)over.style.width='';over.style.backgroundImage='none';}}
+else{if(OLns4)over.background.src=i;else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i+')';}}
+}
+
+/*
+ HANDLING FUNCTIONS
+*/
+// Displays layer
+function OLdisp(s){
+if(OLmodalPI&&!o3_modalscroll)OLchkModal();if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();
+OLplaceLayer();if(OLmodalPI&&o3_modalscroll)OLchkModal();if(OLndt)OLshowObject(over);else OLshowid=
+setTimeout("OLshowObject(over)",1);OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s;
+}
+
+// Decides placement of layer.
+function OLplaceLayer(){
+var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0,o=OLfd(),
+nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;
+if(!OLkht&&!OLop95&&o&&o.clientWidth)iWd=o.clientWidth;
+else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth));
+if(SB>20)SB=20;iWd=o3_frame.innerWidth;}
+pgLeft=(OLie4&&!OLop95)?o.scrollLeft:o3_frame.pageXOffset;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0);
+LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);}
+if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){
+if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='UR'||o3_refp=='LR')X-=5;}
+else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){
+if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else
+if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;}
+}else{if(o3_midx!=null){
+X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx);
+}else{if(o3_relx!=null){
+if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB;
+}else{X=o3_fixx+LM;}}}
+}else{
+if(o3_hauto){
+if(o3_hpos==LEFT&&OLx-pgLeft+OLifX<iWd/2&&OLx-pWd-o3_offsetx+OLifX<pgLeft+LM)o3_hpos=RIGHT;else
+if(o3_hpos==RIGHT&&OLx-pgLeft+OLifX>iWd/2&&OLx+pWd+o3_offsetx+OLifX>pgLeft+iWd-SB)o3_hpos=LEFT;}
+X=(o3_hpos==CENTER)?parseInt(OLx-((pWd+CX)/2)+o3_offsetx):
+(o3_hpos==LEFT)?OLx-o3_offsetx-pWd:OLx+o3_offsetx;
+if(o3_snapx>1){
+snp=X % o3_snapx;
+if(o3_hpos==LEFT){X=X-(o3_snapx+snp);}else{X=X+(o3_snapx-snp);}}X+=OLifX;}
+if(!o3_nojustx&&X+pWd>pgLeft+iWd-SB)
+X=iWd+pgLeft-pWd-SB;if(!o3_nojustx&&X-LM<pgLeft)X=pgLeft+LM;
+pgTop=OLie4&&!OLop95?o.scrollTop:o3_frame.pageYOffset;
+if(!OLkht&&!OLop95&&!nsb&&o&&o.clientHeight)iHt=o.clientHeight;
+else if(o3_frame.innerHeight)iHt=o3_frame.innerHeight;
+if(OLbubblePI&&o3_bubble)pHt=OLbubbleHt;else pHt=OLns4?over.clip.height:over.offsetHeight;
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){TM=(o3_shadowy<0)?Math.abs(o3_shadowy):0;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)BM=CY=5;else
+BM=(o3_shadowy>0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);}
+if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){
+if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){
+if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else
+if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;}
+}else{if(o3_midy!=null){
+Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy);
+}else{if(o3_rely!=null){
+if(o3_rely>=0)Y=pgTop+o3_rely+TM;else Y=pgTop+o3_rely+iHt-pHt-BM;}else{
+Y=o3_fixy+TM;}}}
+}else{
+if(o3_vauto){
+if(o3_vpos==ABOVE&&OLy-pgTop+OLifY<iHt/2&&OLy-pHt-o3_offsety+OLifY<pgTop)o3_vpos=BELOW;else
+if(o3_vpos==BELOW&&OLy-pgTop+OLifY>iHt/2&&OLy+pHt+o3_offsety+((OLns4||OLkht)?17:0)+OLifY>pgTop+iHt-BM)
+o3_vpos=ABOVE;}Y=(o3_vpos==VCENTER)?parseInt(OLy-((pHt+CY)/2)+o3_offsety):
+(o3_vpos==ABOVE)?OLy-(pHt+o3_offsety+BM):OLy+o3_offsety+TM;
+if(o3_snapy>1){
+snp=Y % o3_snapy;
+if(pHt>0&&o3_vpos==ABOVE){Y=Y-(o3_snapy+snp);}else{Y=Y+(o3_snapy-snp);}}Y+=OLifY;}
+if(!o3_nojusty&&Y+pHt+BM>pgTop+iHt)Y=pgTop+iHt-pHt-BM;if(!o3_nojusty&&Y-TM<pgTop)Y=pgTop+TM;
+OLrepositionTo(over,X,Y);
+if(OLshadowPI)OLrepositionShadow(X,Y);if(OLiframePI)OLrepositionIfs(X,Y);
+if(OLns6&&o3_frame.innerHeight){iHt=o3_frame.innerHeight;OLrepositionTo(over,X,Y);}
+if(OLscrollPI)OLchkScroll(X-pgLeft,Y-pgTop);
+}
+
+// Chooses body or documentElement
+function OLfd(f){
+var fd=((f)?f:o3_frame).document,fdc=fd.compatMode,fdd=fd.documentElement;
+return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement:fd.body;
+}
+
+// Gets location of REFerence object
+function OLgetRefXY(r,d){
+var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of;if(!o)return [null,null];if(OLns4){
+if(typeof o.length!='undefined'&&o.length>1){ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY;}else{
+if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){rXY[0]+=o.x;rXY[1]+=o.y;}
+else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}}}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');}
+of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1];return rXY;
+}
+
+// Seeks REFerence by id
+function OLgetRefById(l,d){
+l=(l||'overDiv');d=(d||o3_frame.document);var j,r;if(d.getElementById)return d.getElementById(l);
+if(OLie4&&d.all)return d.all[l];if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];
+for(j=0;j<d.layers.length;j++){r=OLgetRefById(l,d.layers[j].document);if(r)return r;}}return null;
+}
+
+// Seeks REFerence by name
+function OLgetRefByName(l,d){
+d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'):OLns6?d.getElementsByTagName('iframe'):null;
+if(typeof d.images!='undefined'&&d.images[l])return d.images[l];
+if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l];
+if(v)for(j=0;j<v.length;j++)if(v[j].name==l)return v[j];if(d.layers&&d.layers.length>0)for(j=0;j<d.layers.length;j++){
+r=OLgetRefByName(l,d.layers[j].document);if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}return null;
+}
+
+// Gets layer vs REFerence offsets
+function OLgetRefOffsets(o){
+var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0];pW=(OLbubblePI&&o3_bubble)?
+o3_width:OLns4?over.clip.width:over.offsetWidth;pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?
+over.clip.height:over.offsetHeight;if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height;}
+else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{W=(OLns4)?o.clip.width:o.offsetWidth;
+H=(OLns4)?o.clip.height:o.offsetHeight;}if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);
+H+=2*parseInt(o.border);}if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0];}else if(c=='UR'){
+of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0];}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:
+(p=='LR')?[-pW,H-pH]:[0,H];}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]:[W,H];}return of;
+}
+
+// Gets x or y location of object
+function OLpageLoc(o,t){
+var l=0,s=o;while(o.offsetParent&&o.offsetParent.tagName.toLowerCase()!='html'){l+=o['offset'+t];o=o.offsetParent;}
+l+=o['offset'+t];if(!OLop7)while(s=s.parentNode){if((s['scroll'+t]>0)&&s.tagName.toLowerCase()=='div')l-=s['scroll'+t];}
+return l;
+}
+
+// Moves layer
+function OLmouseMove(e){
+var e=(e||event);OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);if((OLallowmove&&
+over)&&(o3_frame==self||over==OLgetRefById()||(OLovertwoPI&&over2==over&&over==OLgetRefById('overDiv2')))){OLplaceLayer();
+if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}
+}
+
+// Capture mouse and chain other scripts.
+function OLmh(){
+var fN,f,j,k,s,mh=OLmouseMove,w=(OLns4&&window.onmousemove),re=/function[ ]*(\w*)\(/;OLdw=document;if(document.onmousemove||
+w){if(w)OLdw=window;f=OLdw.onmousemove.toString();fN=f.match(re);if(!fN||fN[1]=='anonymous'||fN[1]=='OLmouseMove'){OLchkMh=0;
+return;}if(fN[1])s=fN[1]+'(e)';else{j=f.indexOf('{');k=f.lastIndexOf('}')+1;s=f.substring(j,k);}s+=';OLmouseMove(e);';
+mh=new Function('e',s);}OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE);
+}
+
+/*
+ PARSING
+*/
+function OLparseTokens(pf,ar){
+var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);
+for(i=0;i<ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;}
+else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0;}else{
+if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[(++i)]+']');continue;}
+if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[(++i)]+']');continue;}
+if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;}
+if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;}
+if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;}
+if(ar[i]==BACKGROUND){q(ar[++i],pf+'background');continue;}
+if(Math.abs(ar[i])==NOCLOSE){t(ar[i],pf+'noclose');continue;}
+if(Math.abs(ar[i])==MOUSEOFF){t(ar[i],pf+'mouseoff');continue;}
+if(ar[i]==OFFDELAY){p(ar[++i],pf+'offdelay');continue;}
+if(ar[i]==RIGHT||ar[i]==LEFT||ar[i]==CENTER){p(ar[i],pf+'hpos');continue;}
+if(ar[i]==OFFSETX){p(ar[++i],pf+'offsetx');continue;}
+if(ar[i]==OFFSETY){p(ar[++i],pf+'offsety');continue;}
+if(ar[i]==FGCOLOR){q(ar[++i],pf+'fgcolor');continue;}
+if(ar[i]==BGCOLOR){q(ar[++i],pf+'bgcolor');continue;}
+if(ar[i]==CGCOLOR){q(ar[++i],pf+'cgcolor');continue;}
+if(ar[i]==TEXTCOLOR){q(ar[++i],pf+'textcolor');continue;}
+if(ar[i]==CAPCOLOR){q(ar[++i],pf+'capcolor');continue;}
+if(ar[i]==CLOSECOLOR){q(ar[++i],pf+'closecolor');continue;}
+if(ar[i]==WIDTH){p(ar[++i],pf+'width');continue;}
+if(Math.abs(ar[i])==WRAP){t(ar[i],pf+'wrap');continue;}
+if(ar[i]==WRAPMAX){p(ar[++i],pf+'wrapmax');continue;}
+if(ar[i]==HEIGHT){p(ar[++i],pf+'height');continue;}
+if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;}
+if(ar[i]==BASE){p(ar[++i],pf+'base');continue;}
+if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;}
+if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}
+if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}
+if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;}
+if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;}
+if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;}
+if(ar[i]==FIXX){p(ar[++i],pf+'fixx');continue;}
+if(ar[i]==FIXY){p(ar[++i],pf+'fixy');continue;}
+if(ar[i]==RELX){p(ar[++i],pf+'relx');continue;}
+if(ar[i]==RELY){p(ar[++i],pf+'rely');continue;}
+if(ar[i]==MIDX){p(ar[++i],pf+'midx');continue;}
+if(ar[i]==MIDY){p(ar[++i],pf+'midy');continue;}
+if(ar[i]==REF){q(ar[++i],pf+'ref');continue;}
+if(ar[i]==REFC){q(ar[++i],pf+'refc');continue;}
+if(ar[i]==REFP){q(ar[++i],pf+'refp');continue;}
+if(ar[i]==REFX){p(ar[++i],pf+'refx');continue;}
+if(ar[i]==REFY){p(ar[++i],pf+'refy');continue;}
+if(ar[i]==FGBACKGROUND){q(ar[++i],pf+'fgbackground');continue;}
+if(ar[i]==BGBACKGROUND){q(ar[++i],pf+'bgbackground');continue;}
+if(ar[i]==CGBACKGROUND){q(ar[++i],pf+'cgbackground');continue;}
+if(ar[i]==PADX){p(ar[++i],pf+'padxl');p(ar[++i],pf+'padxr');continue;}
+if(ar[i]==PADY){p(ar[++i],pf+'padyt');p(ar[++i],pf+'padyb');continue;}
+if(Math.abs(ar[i])==FULLHTML){t(ar[i],pf+'fullhtml');continue;}
+if(ar[i]==BELOW||ar[i]==ABOVE||ar[i]==VCENTER){p(ar[i],pf+'vpos');continue;}
+if(ar[i]==CAPICON){q(ar[++i],pf+'capicon');continue;}
+if(ar[i]==TEXTFONT){q(ar[++i],pf+'textfont');continue;}
+if(ar[i]==CAPTIONFONT){q(ar[++i],pf+'captionfont');continue;}
+if(ar[i]==CLOSEFONT){q(ar[++i],pf+'closefont');continue;}
+if(ar[i]==TEXTSIZE){q(ar[++i],pf+'textsize');continue;}
+if(ar[i]==CAPTIONSIZE){q(ar[++i],pf+'captionsize');continue;}
+if(ar[i]==CLOSESIZE){q(ar[++i],pf+'closesize');continue;}
+if(ar[i]==TIMEOUT){p(ar[++i],pf+'timeout');continue;}
+if(ar[i]==DELAY){p(ar[++i],pf+'delay');continue;}
+if(Math.abs(ar[i])==HAUTO){t(ar[i],pf+'hauto');continue;}
+if(Math.abs(ar[i])==VAUTO){t(ar[i],pf+'vauto');continue;}
+if(Math.abs(ar[i])==NOJUSTX){t(ar[i],pf+'nojustx');continue;}
+if(Math.abs(ar[i])==NOJUSTY){t(ar[i],pf+'nojusty');continue;}
+if(Math.abs(ar[i])==CLOSECLICK){t(ar[i],pf+'closeclick');continue;}
+if(ar[i]==CLOSETITLE){q(ar[++i],pf+'closetitle');continue;}
+if(ar[i]==FGCLASS){q(ar[++i],pf+'fgclass');continue;}
+if(ar[i]==BGCLASS){q(ar[++i],pf+'bgclass');continue;}
+if(ar[i]==CGCLASS){q(ar[++i],pf+'cgclass');continue;}
+if(ar[i]==TEXTPADDING){p(ar[++i],pf+'textpadding');continue;}
+if(ar[i]==TEXTFONTCLASS){q(ar[++i],pf+'textfontclass');continue;}
+if(ar[i]==CAPTIONPADDING){p(ar[++i],pf+'captionpadding');continue;}
+if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;}
+if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;}
+if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;}
+if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;}
+if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;}
+if(ar[i]==DONOTHING){continue;}
+i=OLparseCmdLine(pf,i,ar);}}
+if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function();
+if(pf=='o3_')OLfontSize();
+}
+function OLpar(a,v){eval(v+'='+a);}
+function OLparQuo(a,v){eval(v+"='"+OLescSglQt(a)+"'");}
+function OLescSglQt(s){return s.toString().replace(/\\/g,"\\\\").replace(/'/g,"\\'");}
+function OLtoggle(a,v){eval(v+'=('+v+'==0&&'+a+'>=0)?1:0');}
+function OLhasDims(s){return /[%\-a-z]+$/.test(s);}
+function OLfontSize(){
+var i;if(OLhasDims(o3_textsize)){if(OLns4)o3_textsize="2";}else
+if(!OLns4){i=parseInt(o3_textsize);o3_textsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else
+if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else
+if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLprintPI)OLprintDims();
+}
+function OLdecode(){
+var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826)&&typeof decodeURIComponent?
+decodeURIComponent:u;if(typeof(window.TypeError)=='function'){if(re.test(t)){eval(new Array('try{','o3_text=d(t);',
+'}catch(e){','o3_text=u(t);','}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){',
+'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);}
+}
+
+/*
+ LAYER FUNCTIONS
+*/
+// Writes to layer
+function OLlayerWrite(t){
+t+="\n";if(OLns4){over.document.write(t);over.document.close();}else if(typeof over.innerHTML!='undefined'){
+if(OLieM)over.innerHTML='';over.innerHTML=t;}else{var range=o3_frame.document.createRange();range.setStartAfter(over);
+var domfrag=range.createContextualFragment(t);while(over.hasChildNodes()){over.removeChild(over.lastChild);}
+over.appendChild(domfrag);}if(OLovertwoPI&&over==over2)OLover2HTML=t;else OLoverHTML=t;
+if(OLprintPI)over.print=o3_print?t:null;
+}
+
+// Makes object visible
+function OLshowObject(o){
+OLshowid=0;o=(OLns4)?o:o.style;if(((OLfilterPI)&&!OLchkFilter(o))||!OLfilterPI)o.visibility="visible";
+if(OLshadowPI)OLshowShadow();if(OLiframePI)OLshowIfs();if(OLhidePI)OLhideUtil(1,1,0);
+}
+
+// Hides object
+function OLhideObject(o){
+if(OLshowid>0){clearTimeout(OLshowid);OLshowid=0;}if(OLtimerid>0)clearTimeout(OLtimerid);
+if(OLdelayid>0)clearTimeout(OLdelayid);OLtimerid=0;OLdelayid=0;self.status="";o3_label=ol_label;
+if(o3_frame!=self)o=OLgetRefById();if(o){if(o.onmouseover)o.onmouseover=null;if(OLscrollPI&&o==over)OLclearScroll();
+if(OLdraggablePI)OLclearDrag();if(OLfilterPI)OLcleanupFilter(o);if(OLshadowPI)OLhideShadow();var os=(OLns4)?o:o.style;
+if(((OLfilterPI)&&!OLchkFadeOut(os))||!OLfilterPI){os.visibility="hidden";if(!OLie55||(typeof ggOnChange=='undefined'&&
+(!OLfilterPI||!o3_filter||o3_fadeout<0)))o.innerHTML='';}if(OLhidePI&&o==over)OLhideUtil(0,0,1);if(OLiframePI)OLhideIfs(o);}
+}
+
+// Moves layer
+function OLrepositionTo(o,xL,yL){
+o=(OLns4)?o:o.style;o.left=(OLns4?xL:xL+'px');o.top=(OLns4?yL:yL+'px');
+}
+
+// Handle NOCLOSE-MOUSEOFF
+function OLoptMOUSEOFF(c){
+if(!c)o3_close="";over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}}
+over.onmouseout=function(){if(OLhover){OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick);if(OLtimerid>0)
+clearTimeout(OLtimerid);OLtimerid=setTimeout("OLcC()",(o3_offdelay<1)?1:o3_offdelay);}}
+}
+function OLcursorOff(){
+var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight,left=parseInt(o.left),top=parseInt(o.top),
+right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);
+if(OLx<left||OLx>right||OLy<top||OLy>bottom)return true;return false;
+}
+
+/*
+ REGISTRATION
+*/
+function OLsetRunTimeVar(){
+if(OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();
+}
+function OLparseCmdLine(pf,i,ar){
+if(OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){var j=OLcmdLine[k](pf,i,ar);if(j>-1){i=j;break;}}}return i;
+}
+function OLregCmds(c){
+if(typeof c!='string')return;var pM=c.split(',');pMtr=pMtr.concat(pM);
+for(var i=0;i<pM.length;i++)eval(pM[i].toUpperCase()+'='+(pmCnt++));
+}
+function OLregRunTimeFunc(f){
+if(typeof f=='object')OLrunTime=OLrunTime.concat(f);else OLrunTime[OLrunTime.length++]=f;
+}
+function OLregCmdLineFunc(f){
+if(typeof f=='object')OLcmdLine=OLcmdLine.concat(f);else OLcmdLine[OLcmdLine.length++]=f;
+}
+
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_crossframe.js b/httemplate/elements/overlibmws_crossframe.js
new file mode 100644
index 0000000..e1bbf41
--- /dev/null
+++ b/httemplate/elements/overlibmws_crossframe.js
@@ -0,0 +1,58 @@
+/*
+ overlibmws_crossframe.js plug-in module - Copyright Foteos Macrides 2003-2010. All rights reserved.
+ For support of FRAME.
+ Initial: August 3, 2003 - Last Revised: October 25, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+OLregCmds('frame');
+
+function OLparseCrossframe(pf,i,ar){
+var k=i,v;
+if(k<ar.length){
+if(ar[k]==FRAME){v=ar[++k];if(pf=='ol_')ol_frame=v;else OLoptFRAME(v);return k;}}
+return -1;
+}
+
+function OLgetFrameRef(thisFrame,ofrm){
+var i,v,retVal='';for(i=0;i<thisFrame.length;i++){if((((thisFrame[i].length>0)))&&(((OLns4))||
+((OLie4)&&(v=thisFrame[i].document.all.tags('iframe'))!=null&&v.length==0)||
+((OLns6)&&(v=thisFrame[i].document.getElementsByTagName('iframe'))!=null&&v.length==0))){
+retVal=OLgetFrameRef(thisFrame[i],ofrm);if(retVal=='')continue;}
+else if(thisFrame[i]!=ofrm)continue;retVal='['+i+']'+retVal;break;}
+return retVal;
+}
+
+function OLoptFRAME(frm){
+o3_frame=OLmkLyr('overDiv',frm)?frm:self;if(o3_frame!=self){var l,tFrm=OLgetFrameRef(top.frames,o3_frame),
+sFrm=OLgetFrameRef(top.frames,ol_frame);if(sFrm.length==tFrm.length) {l=tFrm.lastIndexOf('[');if(l){
+while(sFrm.substring(0,l)!=tFrm.substring(0,l))l=tFrm.lastIndexOf('[',l-1);tFrm=tFrm.substr(l);sFrm=sFrm.substr(l);}}
+var i,k,cnt=0,p='',str=tFrm;while((k=str.lastIndexOf('['))!= -1){cnt++;str=str.substring(0,k);}if(!sFrm&&o3_frame==parent)
+sFrm=OLgetFrameRef(parent,self);else for(i=0;i<cnt;i++)p=p+'parent.';OLfnRef=p+'frames'+sFrm+'.';var n=window.name,o;
+if((n&&parent!=self&&o3_frame==parent)&&(o=OLgetRef(n,parent.document))){if(OLie4&&!OLop7){
+OLx=event.clientX+OLfd().scrollLeft;OLy=event.clientY+OLfd().scrollTop;}
+OLifX=OLpageLoc(o,'Left')-(OLie4&&!OLop7?OLfd().scrollLeft:self.pageXOffset);
+OLifY=OLpageLoc(o,'Top')-(OLie4&&!OLop7?OLfd().scrollTop:self.pageYOffset);}}
+}
+
+function OLchkIfRef(){
+var n=(parent!=self&&o3_frame==parent)?window.name:'',o=n?OLgetRef(n):null;
+if(o){var oR=OLgetRef(o3_ref,document);if(oR){OLrefXY=OLgetRefXY(o3_ref,document);
+OLrefXY[0]+=(OLpageLoc(o,'Left')-(OLie4&&!OLop7?OLfd(self).scrollLeft:self.pageXOffset));
+OLrefXY[1]+=(OLpageLoc(o,'Top')-(OLie4&&!OLop7?OLfd(self).scrollTop:self.pageYOffset));}}
+}
+
+function OLchkFRAME(args){
+var OLfrmVal=self;for(var i=0;i<args.length;i++){if(typeof args[i]=='number'&&args[i]==FRAME){OLfrmVal=args[i+1];break;}}
+if(OLfrmVal!=self&&OLfrmVal.over&&OLfrmVal.cClick)OLfrmVal.cClick();
+}
+
+OLregCmdLineFunc(OLparseCrossframe);
+
+OLcrossframePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_draggable.js b/httemplate/elements/overlibmws_draggable.js
new file mode 100644
index 0000000..d2b5eb1
--- /dev/null
+++ b/httemplate/elements/overlibmws_draggable.js
@@ -0,0 +1,85 @@
+/*
+ overlibmws_draggable.js plug-in module - Copyright Foteos Macrides 2002-2010. All rights reserved.
+ For support of the DRAGGABLE feature.
+ Initial: August 24, 2002 - Last Revised: January 26, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+var OLdraggableCmds='draggable,dragcap,dragid';
+OLregCmds(OLdraggableCmds);
+
+// DEFAULT CONFIGURATION
+if(OLud('draggable'))var ol_draggable=0;
+if(OLud('dragcap'))var ol_dragcap=0;
+if(OLud('dragid'))var ol_dragid='';
+// END CONFIGURATION
+
+var o3_draggable=0,o3_dragcap=0,o3_dragid='',o3_dragging=0,OLdrg=null,OLmMv,
+OLcX,OLcY,OLcbX,OLcbY;function OLloadDraggable(){OLload(OLdraggableCmds);}
+function OLparseDraggable(pf,i,ar){var t=OLtoggle,k=i;if(k<ar.length){
+if(Math.abs(ar[k])==DRAGGABLE){t(ar[k],pf+'draggable');return k;}
+if(Math.abs(ar[k])==DRAGCAP){t(ar[k],pf+'dragcap');return k;}
+if(ar[k]==DRAGID){OLparQuo(ar[++k],pf+'dragid');return k;}}return -1;
+}
+
+function OLcheckDrag(){
+if(o3_draggable){if(o3_sticky&&(o3_frame==self))OLinitDrag();else o3_draggable=0;}
+}
+function OLinitDrag(){
+OLmMv=OLdw.onmousemove;o3_dragging=0;
+if(OLns4){document.captureEvents(Event.MOUSEDOWN|Event.CLICK);
+document.onmousedown=OLgrabEl;document.onclick=function(e){return routeEvent(e);}}
+else{var dvido=(o3_dragid)?OLgetRef(o3_dragid):null,capid=(OLovertwoPI&&over==over2?
+'overCap2':'overCap');if(dvido)dvido.onscroll=function(){OLdw.onmousemove=OLmMv;
+OLinitDrag();};OLdrg=(o3_cap&&o3_dragcap)?OLgetRef(capid):over;
+if(!OLdrg||!OLdrg.style)OLdrg=over;OLdrg.onmousedown=OLgrabEl;OLsetDrgCur(1);}
+}
+function OLsetDrgCur(d){if(!OLns4&&OLdrg)OLdrg.style.cursor=(d?'move':'auto');}
+
+function OLgrabEl(e){
+var e=(e||event);
+var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(e.altKey||(OLop7&&e.ctrlKey)));o3_dragging=1;
+if(cKy){OLsetDrgCur(0);document.onmouseup=function(){OLsetDrgCur(1);o3_dragging=0;}
+return(OLns4?routeEvent(e):true);}
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(OLie4)over.onselectstart=function(){return false;}
+if(OLns4){OLcX=OLx;OLcY=OLy;document.captureEvents(Event.MOUSEUP)}else{
+OLcX=OLx-(OLns4?over.left:parseInt(over.style.left));
+OLcY=OLy-(OLns4?over.top:parseInt(over.style.top));
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLcbX=OLx-(parseInt(bkdrop.style.left));
+OLcbY=OLy-(parseInt(bkdrop.style.top));}}OLdw.onmousemove=OLmoveEl;
+document.onmouseup=function(){
+if(OLie4)over.onselectstart=null;o3_dragging=0;OLdw.onmousemove=OLmMv;}
+return(OLns4?routeEvent(e):false);
+}
+
+function OLmoveEl(e){
+var e=(e||event);
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(o3_dragging){if(OLns4){over.moveBy(OLx-OLcX,OLy-OLcY);
+if(OLshadowPI&&bkdrop&&o3_shadow)bkdrop.moveBy(OLx-OLcX,OLy-OLcY);}
+else{OLrepositionTo(over,OLx-OLcX,OLy-OLcY);
+if((OLiframePI)&&OLie55&&OLifsP1)OLrepositionTo(OLifsP1,OLx-OLcX,OLy-OLcY);
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLrepositionTo(bkdrop,OLx-OLcbX,OLy-OLcbY);
+if((OLiframePI)&&OLie55&&OLifsSh)OLrepositionTo(OLifsSh,OLx-OLcbX,OLy-OLcbY);}}
+if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}if(OLns4){OLcX=OLx;OLcY=OLy;}
+return false;
+}
+
+function OLclearDrag(){
+if(OLns4){document.releaseEvents(Event.MOUSEDOWN|Event.MOUSEUP|Event.CLICK);
+document.onmousedown=document.onclick=null;}else{
+if(OLdrg)OLdrg.onmousedown=null;over.onmousedown=null;OLsetDrgCur(0);}
+document.onmouseup=null;o3_dragging=0;
+}
+
+OLregRunTimeFunc(OLloadDraggable);
+OLregCmdLineFunc(OLparseDraggable);
+
+OLdraggablePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_iframe.js b/httemplate/elements/overlibmws_iframe.js
new file mode 100644
index 0000000..a06bdc5
--- /dev/null
+++ b/httemplate/elements/overlibmws_iframe.js
@@ -0,0 +1,93 @@
+/*
+ overlibmws_iframe.js plug-in module - Copyright Foteos Macrides 2003-2010. All rights reserved.
+ Masks system controls to prevent obscuring of popops for IE v5.5 or higher.
+ Initial: October 19, 2003 - Last Revised: January 26, 2008
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+
+var OLifsP1=null,OLifsSh=null,OLifsP2=null;
+
+// IFRAME SHIM SUPPORT FUNCTIONS
+function OLinitIfs(){
+if(!OLie55)return;
+if((OLovertwoPI)&&over2&&over==over2){
+var o=o3_frame.document.all['overIframeOvertwo'];
+if(!o||OLifsP2!=o){OLifsP2=null;OLgetIfsP2Ref();}return;}
+o=o3_frame.document.all['overIframe'];
+if(!o||OLifsP1!=o){OLifsP1=null;OLgetIfsRef();}
+if((OLshadowPI)&&o3_shadow){o=o3_frame.document.all['overIframeShadow'];
+if(!o||OLifsSh!=o){OLifsSh=null;OLgetIfsShRef();}}
+}
+
+function OLsetIfsRef(o,i,z){
+o.id=i;o.src='javascript:false;';o.scrolling='no';var os=o.style;os.position='absolute';
+os.top='0px';os.left='0px';os.width='1px';os.height='1px';os.visibility='hidden';
+os.zIndex=over.style.zIndex-z;os.filter='Alpha(style=0,opacity=0)';
+}
+
+function OLgetIfsRef(){
+if(OLifsP1||!OLie55)return;
+OLifsP1=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP1,'overIframe',2);
+o3_frame.document.body.appendChild(OLifsP1);
+}
+
+function OLgetIfsShRef(){
+if(OLifsSh||!OLie55)return;
+OLifsSh=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsSh,'overIframeShadow',3);
+o3_frame.document.body.appendChild(OLifsSh);
+}
+
+function OLgetIfsP2Ref(){
+if(OLifsP2||!OLie55)return;
+OLifsP2=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP2,'overIframeOvertwo',1);
+o3_frame.document.body.appendChild(OLifsP2);
+}
+
+function OLsetDispIfs(o,w,h){
+var os=o.style;
+os.width=w+'px';os.height=h+'px';os.clip='rect(0px '+w+'px '+h+'px 0px)';
+o.filters.alpha.enabled=true;
+}
+
+function OLdispIfs(){
+if(!OLie55)return;
+var wd=over.offsetWidth,ht=over.offsetHeight;
+if(OLfilterPI&&o3_filter&&o3_filtershadow){wd+=5;ht+=5;}
+if((OLovertwoPI)&&over2&&over==over2){
+if(!OLifsP2)return;
+OLsetDispIfs(OLifsP2,wd,ht);return;}
+if(!OLifsP1)return;
+OLsetDispIfs(OLifsP1,wd,ht);
+if((!OLshadowPI)||!o3_shadow||!OLifsSh)return;
+OLsetDispIfs(OLifsSh,wd,ht);
+}
+
+function OLshowIfs(){
+if(OLifsP1){OLifsP1.style.visibility="visible";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="visible";}
+}
+
+function OLhideIfs(o){
+if(!OLie55||o!=over)return;
+if(OLifsP1)OLifsP1.style.visibility="hidden";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="hidden";
+}
+
+function OLrepositionIfs(X,Y){
+if(OLie55){if((OLovertwoPI)&&over2&&over==over2){
+if(OLifsP2)OLrepositionTo(OLifsP2,X,Y);}
+else{if(OLifsP1){OLrepositionTo(OLifsP1,X,Y);if((OLshadowPI)&&o3_shadow&&OLifsSh)
+OLrepositionTo(OLifsSh,X+o3_shadowx,Y+o3_shadowy);}}}
+}
+
+OLiframePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html
new file mode 100644
index 0000000..a53300f
--- /dev/null
+++ b/httemplate/elements/pager.html
@@ -0,0 +1,55 @@
+% 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;
+% my $prevpage = 0;
+% my $over = 0;
+% my $step = $opt{total} / 10; # 10 evenly spaced
+% for ( my $poff = 0; $poff < $opt{total}; $poff += $opt{maxrecords} ) {
+% $page++;
+%
+% next unless
+% $page <= 4 #first four
+% || $page >= ( $opt{total} / $opt{maxrecords} ) - 3 #last four
+% || abs( ($opt{offset}-$poff) / $opt{maxrecords} ) <= 3 #w/i 3 of current
+% || $poff > $over # evenly spaced
+% ;
+%
+% $over += $step if $poff > $over;
+%
+% if ( $opt{'offset'} == $poff ) {
+
+ <FONT SIZE="+2"><% $page %></FONT>
+
+% } else {
+% $cgi->param('offset', $poff);
+%
+% if ( $page > $prevpage+1 ) {
+ ...
+% }
+
+ <A HREF="<% $cgi->self_url %>"><% $page %></A>
+
+% }
+%
+% $prevpage = $page;
+%
+% }
+%
+% 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/phonenumber.html b/httemplate/elements/phonenumber.html
new file mode 100644
index 0000000..854f584
--- /dev/null
+++ b/httemplate/elements/phonenumber.html
@@ -0,0 +1,81 @@
+<% include('/elements/init_overlib.html') %>
+
+% if ( length($number) ) {
+
+ <% $number %>
+
+% if ( $opt{'callable'} ) {
+%
+% if ( $curuser->option('vonage-username') ) {
+%
+% (my $vonage_number = $curuser->option('vonage-fromnumber')) =~ s/\D//g;
+% $vonage_number =~ /^1/ or $vonage_number = "1$vonage_number";
+
+ <% include('/elements/popup_link.html',
+ 'action' =>
+ 'https://secure.click2callu.com/tpcc/makecall'.
+ '?username='. uri_escape($curuser->option('vonage-username')).
+ '&password='. uri_escape($curuser->option('vonage-password')).
+ "&fromnumber=$vonage_number".
+ "&tonumber=$snumber",
+ 'width' => 240,
+ 'height' => 64,
+ 'actionlabel' => 'Initiating call',
+ 'label' => "<$img>",
+ )
+ %>
+
+% } elsif ( $curuser->option('snom-ip') ) {
+%
+% my $host = $curuser->option('snom-ip');
+% if ( $curuser->option('snom-username') ) {
+% my $userpass = uri_escape($curuser->option('snom-username'));
+% $userpass .= ':'. uri_escape($curuser->option('snom-password'))
+% if $curuser->option('snom-password');
+% $host = $userpass.'@'.$host;
+% }
+%
+% $snumber = "1$snumber" unless $snumber =~ /~1/; #NANPA-centric
+
+%# <% include('/elements/popup_link.html',
+%# 'action' => "http://$host/command.htm?number=$snumber",
+%# %link_common,
+%# )
+%# %>
+
+ <A HREF="javascript:snom_call(<%$snumber%>)"><<% $img %>></A>
+
+ <SCRIPT TYPE="text/javascript">
+ function snom_call(number) {
+
+ var url = '<% "http://$host/command.htm?number=" %>';
+ url = url + number;
+
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open('GET', url, true);
+ xmlhttp.send(null);
+
+ }
+
+ </SCRIPT>
+
+
+% }
+%
+% }
+%
+% } else {
+
+ &nbsp;
+
+% }
+<%init>
+
+my( $number, %opt ) = @_;
+( my $snumber = $number ) =~ s/\D//g;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $img = qq(IMG SRC="${fsurl}images/red_telephone_mimooh_01.png" BORDER=0 ALT="Call this number");
+
+</%init>
diff --git a/httemplate/elements/popup_link-cust_main.html b/httemplate/elements/popup_link-cust_main.html
new file mode 100644
index 0000000..454fcc4
--- /dev/null
+++ b/httemplate/elements/popup_link-cust_main.html
@@ -0,0 +1,42 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/popup_link-cust_main.html', { #hashref or a list, either way
+
+ #required
+ 'action' => 'content.html', # uri for content of popup which should
+ # be suitable for appending keywords
+ 'label' => 'click me', # text of <A> tag
+ 'cust_main' => $cust_main # a FS::cust_main object
+
+ #strongly recommended (you want a title, right?)
+ 'actionlabel => 'You clicked', # popup title
+
+ #opt
+ 'width' => '540',
+ 'color' => '#ff0000',
+ 'closetext' => 'Go Away', # the value '' removes the link
+ )
+
+</%doc>
+% if ( $params->{'cust_main'} ) {
+<% include('/elements/popup_link.html', $params ) %>\
+% }
+<%init>
+
+my $params = { 'closetext' => 'Close' };
+
+if (ref($_[0]) eq 'HASH') {
+ $params = { %$params, %{ $_[0] } };
+} else {
+ $params = { %$params, @_ };
+}
+
+$params->{'action'} .=
+ ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
+ 'custnum='. $params->{'cust_main'}->custnum;
+
+</%init>
diff --git a/httemplate/elements/popup_link-cust_pkg.html b/httemplate/elements/popup_link-cust_pkg.html
new file mode 100644
index 0000000..cd8d5c0
--- /dev/null
+++ b/httemplate/elements/popup_link-cust_pkg.html
@@ -0,0 +1,47 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/pkg_popup_link.html', { #hashref or a list, either way
+
+ #required
+ 'action' => 'content.html', # uri for content of popup which should
+ # be suitable for appending '&stuff...'
+ 'label' => 'click me', # text of <A> tag
+ 'cust_pkg' => $cust_pkg # a FS::cust_pkg object
+
+ #strongly recommended (you want a title, right?)
+ 'actionlabel => 'You clicked', # popup title
+
+ #opt
+ 'width' => '540',
+ 'color' => '#ff0000',
+ 'closetext' => 'Go Away', # the value '' removes the link
+ )
+
+</%doc>
+% if ( $params->{'cust_pkg'} ) {
+<% include('/elements/popup_link.html', $params ) %>\
+% }
+<%init>
+
+my $params = { 'closetext' => 'Close',
+ 'width' => 768,
+ };
+
+if (ref($_[0]) eq 'HASH') {
+ $params = { %$params, %{ $_[0] } };
+} else {
+ $params = { %$params, @_ };
+}
+
+$params->{'action'} .=
+ ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
+ 'pkgnum='. $params->{'cust_pkg'}->pkgnum;
+
+$params->{'actionlabel'} .=
+ ' package '. $params->{'cust_pkg'}->pkgnum; #XXX pkgnum? really?
+
+</%init>
diff --git a/httemplate/elements/popup_link-cust_svc.html b/httemplate/elements/popup_link-cust_svc.html
new file mode 100644
index 0000000..8255ffc
--- /dev/null
+++ b/httemplate/elements/popup_link-cust_svc.html
@@ -0,0 +1,47 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/svc_popup_link.html', { #hashref or a list, either way
+
+ #required
+ 'action' => 'content.html', # uri for content of popup which should
+ # be suitable for appending '?svcnum='
+ 'label' => 'click me', # text of <A> tag
+ 'cust_svc' => $cust_svc # a FS::cust_svc object
+
+ #strongly recommended (you want a title, right?)
+ 'actionlabel => 'You clicked', # popup title
+
+ #opt
+ 'width' => '540',
+ 'color' => '#ff0000',
+ 'closetext' => 'Go Away', # the value '' removes the link
+ )
+
+</%doc>
+% if ( $params->{'cust_svc'} ) {
+<% include( '/elements/popup_link.html', $params ) %>\
+% }
+<%init>
+
+my $params = { 'closetext' => 'Close',
+ 'width' => 392,
+ };
+
+if (ref($_[0]) eq 'HASH') {
+ $params = { %$params, %{ $_[0] } };
+} else {
+ $params = { %$params, @_ };
+}
+
+$params->{'action'} .=
+ ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
+ 'svcnum='. $params->{'cust_svc'}->svcnum;
+
+$params->{'actionlabel'} .=
+ ' service '. $params->{'cust_svc'}->svcnum; #XXX svcnum? really?
+
+</%init>
diff --git a/httemplate/elements/popup_link-ping.html b/httemplate/elements/popup_link-ping.html
new file mode 100644
index 0000000..9e5f143
--- /dev/null
+++ b/httemplate/elements/popup_link-ping.html
@@ -0,0 +1,30 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/popup_link-ping.html', { #hashref or a list, either way
+ 'ip' => '10.9.8.7',
+ })
+
+</%doc>
+<% include('/elements/popup_link.html', $params ) %>\
+<%init>
+
+my $params = { 'closetext' => 'Close' };
+
+if (ref($_[0]) eq 'HASH') {
+ $params = { %$params, %{ $_[0] } };
+} else {
+ $params = { %$params, @_ };
+}
+
+$params->{'label'} ||= 'ping';
+$params->{'actionlabel'} ||= 'Ping '. $params->{'ip'};
+$params->{'width'} ||= 350;
+$params->{'height'} ||= 220;
+
+$params->{'action'} = $p. 'misc/ping.html?'. $params->{'ip'};
+
+</%init>
diff --git a/httemplate/elements/popup_link.html b/httemplate/elements/popup_link.html
new file mode 100644
index 0000000..49b624c
--- /dev/null
+++ b/httemplate/elements/popup_link.html
@@ -0,0 +1,51 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/popup_link.html', { #hashref or a list, either way is fine
+
+ #required
+ 'action' => 'content.html', # uri for content of popup
+ 'label' => 'click me', # text of <A> tag
+
+ #strongly recommended
+ 'actionlabel => 'You clicked', # popup title
+
+ #opt
+ 'width' => 540,
+ 'height' => 336,
+ 'color' => '#ff0000',
+ 'closetext' => 'Go Away', # the value '' removes the link
+
+ #uncommon opt
+ 'aname' => "target", # link NAME= value, useful for #targets
+ 'target' => '_parent',
+ 'style' => 'css-attribute:value',
+ } )
+
+</%doc>
+% if ($params->{'action'} && $label) {
+<A HREF="javascript:void(0);"
+ onClick="<% $onclick |n %>"
+ <% $params->{'aname'} ? 'NAME="'. $params->{'aname'}. '"' : '' |n %>
+ <% $params->{'target'} ? 'TARGET="'. $params->{'target'}. '"' : '' |n %>
+ <% $params->{'style'} ? 'STYLE="'. $params->{'style'}. '"' : '' |n %>
+><% $label %></A>\
+% }
+<%init>
+
+my $params;
+if (ref($_[0]) eq 'HASH') {
+ #$params = { %$params, %{ $_[0] } };
+ $params = shift;
+} else {
+ #$params = { %$params, @_ };
+ $params = { @_ };
+}
+
+my $label = $params->{'label'};
+my $onclick = include('/elements/popup_link_onclick.html', $params);
+
+</%init>
diff --git a/httemplate/elements/popup_link_onclick.html b/httemplate/elements/popup_link_onclick.html
new file mode 100644
index 0000000..4c61a6c
--- /dev/null
+++ b/httemplate/elements/popup_link_onclick.html
@@ -0,0 +1,74 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/popup_link_onclick.html', { #hashref or a list, either way
+
+ #required
+ 'action' => 'content.html', # uri for content of popup
+
+ #strongly recommended
+ 'actionlabel => 'You clicked', # popup title
+
+ #opt
+ 'width' => 540,
+ 'height' => 336,
+ 'color' => '#ff0000',
+ 'closetext' => 'Go Away', # the value '' removes the link
+
+ #uncommon opt
+ 'frame' => 0, #bool
+ 'scrolling' => 'yes', #scrollbars:
+ # 'auto' (default if omitted), 'yes' or 'no'.
+ 'nofalse' => 0, #bool, eliminates "return false;"
+
+ } )
+
+</%doc>
+% if ($action) {
+<% $onclick |n %>\
+% }
+<%init>
+
+my( $action, $actionlabel, $frame ) = ( '', '', '' );
+my( $width, $height ) = ( 540, 336 );
+my $closetext = 'Close';
+my $color = '#333399';
+my $scrolling = 'auto';
+
+my $params;
+if (ref($_[0]) eq 'HASH') {
+ #$params = { %$params, %{ $_[0] } };
+ $params = shift;
+} else {
+ #$params = { %$params, @_ };
+ $params = { @_ };
+}
+
+$action = $params->{'action'} if exists $params->{'action'};
+$actionlabel = $params->{'actionlabel'} if exists $params->{'actionlabel'};
+$width = $params->{'width'} if exists $params->{'width'};
+$height = $params->{'height'} if exists $params->{'height'};
+$color = $params->{'color'} if exists $params->{'color'};
+$closetext = $params->{'closetext'} if exists $params->{'closetext'};
+$frame = $params->{'frame'} if exists $params->{'frame'};
+$scrolling = $params->{'scrolling'} if exists $params->{'scrolling'};
+
+#stupid safari is caching the "location" of popup iframs, and submitting them
+#instead of displaying them. this should prevent that.
+my $popup_name = 'popup-'.time. "-$$-". rand() * 2**32;
+
+my $onclick =
+ "overlib( OLiframeContent('$action', $width, $height, '$popup_name', 0, '$scrolling' ), ".
+ "CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, ".
+ "DRAGGABLE, CLOSECLICK, ".
+ "BGCOLOR, '$color', CGCOLOR, '$color', CLOSETEXT, '$closetext'".
+ ( $frame ? ", FRAME, $frame" : '' ).
+ ");";
+
+$onclick .= " return false;"
+ unless $params->{'nofalse'};
+
+</%init>
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
new file mode 100644
index 0000000..194fc74
--- /dev/null
+++ b/httemplate/elements/progress-init.html
@@ -0,0 +1,87 @@
+<% include('/elements/xmlhttp.html',
+ 'method' => 'POST',
+ 'url' => $action,
+ 'subs' => [ 'start_job' ],
+ 'key' => $key,
+ )
+%>
+
+<% include('/elements/init_overlib.html') %>
+
+<SCRIPT TYPE="text/javascript">
+
+function <%$key%>process () {
+
+ //alert('<%$key%>process for form <%$formname%>');
+
+ if ( document.<%$formname%>.submit.disabled == false ) {
+ document.<%$formname%>.submit.disabled=true;
+ }
+
+ overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+
+ var Hash = new Array();
+ var x = 0;
+ var fieldName;
+ for (var i = 0; i<document.<%$formname%>.elements.length; i++) {
+ field = document.<%$formname%>.elements[i];
+ if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %>
+ )
+ {
+ if ( field.type == 'select-multiple' ) {
+ //alert('select-multiple ' + field.name);
+ for (var j=0; j < field.options.length; j++) {
+ if ( field.options[j].selected ) {
+ //alert(field.name + ' => ' + field.options[j].value);
+ Hash[x++] = field.name;
+ Hash[x++] = field.options[j].value;
+ }
+ }
+ } else if ( ( field.type != 'radio' && field.type != 'checkbox' )
+ || ( ( field.type == 'radio' || field.type == 'checkbox' )
+ && document.<%$formname%>.elements[i].checked
+ )
+ )
+ {
+ Hash[x++] = field.name;
+ Hash[x++] = field.value;
+ }
+ }
+ }
+
+ // jsrsPOST = true;
+ // jsrsExecute( '<% $action %>', <%$key%>myCallback, 'start_job', Hash );
+
+ //alert('start_job( ' + Hash + ', <%$key%>myCallback )' );
+ //alert('start_job()' );
+ <%$key%>start_job( Hash, <%$key%>myCallback );
+
+}
+
+function <%$key%>myCallback( jobnum ) {
+
+ overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+
+}
+
+</SCRIPT>
+
+<%init>
+
+my( $formname, $fields, $action, $url_or_message, $key ) = @_;
+$key = '' unless defined $key;
+
+my $url_or_message_link;
+if ( ref($url_or_message) ) { #its a message or something
+ $url_or_message_link = 'message='. uri_escape( $url_or_message->{'message'} );
+ $url_or_message_link .= ';url='. uri_escape( $url_or_message->{'url'} )
+ if $url_or_message->{'url'};
+} else {
+ $url_or_message_link = "url=$url_or_message";
+}
+
+#stupid safari is caching the "location" of popup iframs, and submitting them
+#instead of displaying them. this should prevent that.
+my $popup_name = 'popup-'.time. "-$$-". rand() * 2**32;
+
+</%init>
diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html
new file mode 100644
index 0000000..8a55efb
--- /dev/null
+++ b/httemplate/elements/progress-popup.html
@@ -0,0 +1,116 @@
+%
+% my $jobnum = $cgi->param('jobnum');
+% my $url = $cgi->param('url');
+% my $message = $cgi->param('message');
+% my $formname = scalar($cgi->param('formname'));
+%
+
+<HTML>
+ <HEAD>
+ <TITLE></TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#ccccff" onLoad="refreshStatus()">
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'elements/jsrsServer.html',
+ 'subs' => [ 'job_status' ],
+ )
+%>
+<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/control.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/imagelist.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" src="<%$fsurl%>elements/qlib/progress.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript">
+function refreshStatus () {
+ //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' );
+
+ job_status( '<% $jobnum %>', updateStatus );
+}
+function updateStatus( status_statustext ) {
+
+ //var Array = status_statustext.split("\n");
+ var statusArray = eval('(' + status_statustext + ')');
+ var status = statusArray[0];
+ var statustext = statusArray[1];
+ var actiontext = statusArray[2];
+
+ //if ( status == 'progress' ) {
+ //IE workaround, no i have no idea why
+ if ( status.indexOf('progress') > -1 ) {
+ document.getElementById("progress_message").innerHTML = actiontext + '...';
+ document.getElementById("progress_percent").innerHTML = statustext + '%';
+ bar1.set(statustext);
+ bar1.update;
+ //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' );
+ job_status( '<% $jobnum %>', updateStatus );
+ } else if ( status.indexOf('complete') > -1 ) {
+% if ( $message ) {
+%
+% my $onClick = $url
+% ? "window.top.location.href = \\'$url\\';"
+% : 'parent.nd(1);';
+
+ document.getElementById("progress_message").innerHTML = "<% $message %>";
+ document.getElementById("progress_bar").innerHTML = '';
+ document.getElementById("progress_percent").innerHTML =
+ '<INPUT TYPE="button" VALUE="OK" onClick="<% $onClick %>">';
+ document.getElementById("progress_jobnum").innerHTML = '';
+
+% unless ( $url ) {
+ if ( parent.document.<%$formname%>.submit.disabled == true ) {
+ parent.document.<%$formname%>.submit.disabled=false;
+ }
+% }
+
+% } elsif ( $url ) {
+
+ window.top.location.href = '<% $url %>';
+% } else {
+
+ alert('job done but no url or message specified');
+% }
+
+ } else if ( status.indexOf('error') > -1 ) {
+ document.getElementById("progress_message").innerHTML = '<FONT SIZE="+1" COLOR="#FF0000">Error: ' + statustext + '</FONT>';
+ document.getElementById("progress_bar").innerHTML = '';
+ document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">';
+ document.getElementById("progress_jobnum").innerHTML = '';
+ if ( parent.document.<%$formname%>.submit.disabled == true ) {
+ parent.document.<%$formname%>.submit.disabled=false;
+ }
+ } else {
+ alert('XXX unknown status returned from server: ' + status);
+ }
+
+}
+</SCRIPT>
+
+ <TABLE WIDTH="100%">
+ <TR>
+ <TD ALIGN="center" ID="progress_message">
+ Server processing job...
+ </TD>
+ </TR><TR>
+ <TD ALIGN="center" ID="progress_bar">
+ <SCRIPT TYPE="text/javascript">
+ // Create imagelist
+ SEGS = new QImageList(4, 23, "<%$fsurl%>images/progressbar-empty.png", "<%$fsurl%>images/progressbar-full.png");
+ // Create bars
+ bar1 = new QProgress(null, "bar1", SEGS, 100);
+ // bar1.set(0);
+ // bar1.update;
+ </SCRIPT>
+ </TD>
+ </TR><TR>
+ <TD ALIGN="center">
+ <DIV ID="progress_percent">%</DIV>
+ </TD>
+ </TR><TR>
+ <TD ALIGN="center" ID="progress_jobnum">
+ (progress of job #<% $jobnum %>)
+ </TD>
+ </TR>
+ </TABLE>
+
+ </BODY>
+</HTML>
+
diff --git a/httemplate/elements/qlib/box.js b/httemplate/elements/qlib/box.js
new file mode 100644
index 0000000..537aac4
--- /dev/null
+++ b/httemplate/elements/qlib/box.js
@@ -0,0 +1,29 @@
+/**
+ * QLIB 1.0 Box Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QBox(parent, name, res, x, y, width, height, body, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ if (this.res = res) {
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = width - 0;
+ this.height = (typeof(height) == "number") ? height : null;
+ this.body = body || "&nbsp;";
+ var j = QBox.arguments.length;
+ this.visible = (j > 8) ? visible : true;
+ this.effects = (j > 9) ? effects : (res.effects || 0);
+ this.opacity = (j > 10) ? opacity : (res.opacity != null ? res.opacity : 100);
+ this.zindex = (j > 11) ? zindex : null;
+ this.create();
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QBox.prototype = new QBoxCtrl();
diff --git a/httemplate/elements/qlib/boxctrl.js b/httemplate/elements/qlib/boxctrl.js
new file mode 100644
index 0000000..417b204
--- /dev/null
+++ b/httemplate/elements/qlib/boxctrl.js
@@ -0,0 +1,48 @@
+/**
+ * QLIB 1.0 Box Abstraction
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QBoxCtrl_content() {
+ with (this) {
+ if (res) {
+ this.cwidth = width - res.L - res.R - 8;
+ this.cheight = height && (height - res.T - res.B - 8);
+ var ec = '"><table border="0" cellspacing="0" cellpadding="0"><tr><td></td></tr></table></td>';
+ document.write('<table class="qbox" border="0" cellspacing="0" cellpadding="0" width="' +
+ (width - 8) + (height != null ? '" height="' + (height - 8) : '') + '"><tr><td width="' +
+ res.L + '" height="' + res.T + '"><img src="' + res.TL.src + '" border="0" width="' +
+ res.L + '" height="' + res.T + '"></td><td width="' + cwidth + '" height="' + res.T +
+ '" background="' + res.TC.src + ec + '<td width="' + res.R + '" height="' + res.T +
+ '"><img src="' + res.TR.src + '" border="0" width="' + res.R + '" height="' + res.T +
+ '"></td></tr><tr><td width="' + res.L + (cheight != null ? '" height="' + cheight : '') +
+ '" background="' + res.ML.src + ec + '<td width="' + cwidth + '" bgcolor="' + res.bgcolor +
+ (cheight != null ? '" height="' + cheight : '') + (res.bgtile ? '" background="' +
+ res.bgtile.src : '') + '" align="left" valign="top" class="body" unselectable="on">');
+ if (typeof(body) == "function") {
+ this.body();
+ } else {
+ document.write(body);
+ }
+ document.write('</td><td width="' + res.R + (cheight != null ? '" height="' + cheight : '') +
+ '" background="' + res.MR.src + ec + '</tr><tr><td width="' + res.L + '" height="' + res.B +
+ '"><img src="' + res.BL.src + '" border="0" width="' + res.L + '" height="' + res.B +
+ '"></td><td width="' + cwidth + '" height="' + res.B + '" background="' + res.BC.src + ec +
+ '<td width="' + res.R + '" height="' + res.B + '"><img src="' + res.BR.src +
+ '" border="0" width="' + res.R + '" height="' + res.B + '"></td></tr></table><br>');
+ }
+ }
+}
+
+function QBoxCtrl() {
+ this.res = false;
+ this.body = "&nbsp;";
+ this.cwidth = this.cheight = 0;
+ this.content = QBoxCtrl_content;
+}
+QBoxCtrl.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/boxres.js b/httemplate/elements/qlib/boxres.js
new file mode 100644
index 0000000..0878172
--- /dev/null
+++ b/httemplate/elements/qlib/boxres.js
@@ -0,0 +1,42 @@
+/**
+ * QLIB 1.0 Box Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QBoxRes(t, r, b, l, tc, tr, mr, br, bc, bl, ml, tl, bgcolor, bgtile, effects, opacity) {
+ var args = QBoxRes.arguments.length;
+ this.T = t;
+ this.R = r;
+ this.B = b;
+ this.L = l;
+ this.TC = new Image();
+ this.TC.src = tc;
+ this.TR = new Image(r, t);
+ this.TR.src = tr;
+ this.MR = new Image();
+ this.MR.src = mr;
+ this.BR = new Image(r, b);
+ this.BR.src = br;
+ this.BC = new Image();
+ this.BC.src = bc;
+ this.BL = new Image(l, b);
+ this.BL.src = bl;
+ this.ML = new Image();
+ this.ML.src = ml;
+ this.TL = new Image(l, t);
+ this.TL.src = tl;
+ this.bgcolor = bgcolor || "#FFFFFF";
+ if (bgtile) {
+ this.bgtile = new Image();
+ this.bgtile.src = bgtile;
+ } else {
+ this.bgtile = false;
+ }
+ this.effects = (args > 13) ? effects : null;
+ this.opacity = (args > 14) ? opacity : null;
+}
diff --git a/httemplate/elements/qlib/button.js b/httemplate/elements/qlib/button.js
new file mode 100644
index 0000000..05247d5
--- /dev/null
+++ b/httemplate/elements/qlib/button.js
@@ -0,0 +1,74 @@
+/**
+ * QLIB 1.0 Button Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QButton_update() {
+ with (this) {
+ image.src = ((!enabled && res.imgD) || (value ? res.imgP : res.imgN)).src;
+ }
+}
+
+function QButton_doEvent() {
+ with (this) {
+ if (enabled) {
+ if (res.style == 1) {
+ this.value = value ? 0 : 1;
+ update();
+ }
+ onClick(value, tag);
+ }
+ }
+ return false;
+}
+
+function QButton_enable(state) {
+ this.enabled = state;
+ this.update();
+}
+
+function QButton_set(value) {
+ if (this.enabled) {
+ this.value = value ? 1 : 0;
+ this.update();
+ }
+ return true;
+}
+
+function QButton(parent, name, res, tooltip) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.tip = tooltip || "";
+ this.enabled = true;
+ this.value = 0;
+ this.set = QButton_set;
+ this.enable = QButton_enable;
+ this.update = QButton_update;
+ this.doEvent = QButton_doEvent;
+ this.onClick = QControl.event;
+ with (this) {
+ document.write('<a href="#" hidefocus="true" unselectable="on"' +
+ (tip ? ' title="' + tip + '"' : '') + ' onClick="return ' + name +
+ '.doEvent()" onMouseOver="' + (res.style == 2 ? name + '.set(1);' : '') +
+ 'window.top.status=' + name + '.tip;return true" onMouseOut="' +
+ (!res.style || (res.style == 2) ? name + '.set();' : '') + 'window.top.status=\'\'"' +
+ (!res.style ? ' onMouseDown="return ' + name + '.set(1)" onMouseUp="return ' + name + '.set()"' : '') +
+ '><img class="qbutton" name="' + id + '" src="' + res.imgN.src + '" border="0" width="' +
+ res.width + '" height="' + res.height + '"></a>');
+ this.image = document.images[id] || new Image(1, 1);
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QButton.prototype = new QControl();
+QButton.NORMAL = 0;
+QButton.CHECKBOX = 1;
+QButton.WEB = 2;
+QButton.SIGNAL = 3;
diff --git a/httemplate/elements/qlib/buttonres.js b/httemplate/elements/qlib/buttonres.js
new file mode 100644
index 0000000..97f6dfc
--- /dev/null
+++ b/httemplate/elements/qlib/buttonres.js
@@ -0,0 +1,23 @@
+/**
+ * QLIB 1.0 Button Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QButtonRes(style, width, height, normal, pressed, disabled) {
+ this.style = style;
+ this.width = width;
+ this.height = height;
+ this.imgN = new Image(width, height);
+ this.imgN.src = normal;
+ this.imgP = new Image(width, height);
+ this.imgP.src = pressed;
+ if (disabled) {
+ this.imgD = new Image(width, height);
+ this.imgD.src = disabled;
+ }
+}
diff --git a/httemplate/elements/qlib/control.js b/httemplate/elements/qlib/control.js
new file mode 100644
index 0000000..f50206e
--- /dev/null
+++ b/httemplate/elements/qlib/control.js
@@ -0,0 +1,51 @@
+/**
+ * QLIB 1.0 Base Abstract Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QControl_init(parent, name) {
+ this.parent = parent || self;
+ this.window = (parent && parent.window) || self;
+ this.document = (parent && parent.document) || self.document;
+ this.name = (parent && parent.name) ? (parent.name + "." + name) : ("self." + name);
+ this.id = "Q";
+ var h = this.hash(this.name);
+ for (var j=0; j<8; j++) {
+ this.id += QControl.HEXTABLE.charAt(h & 15);
+ h >>>= 4;
+ }
+}
+
+function QControl_hash(str) {
+ var h = 0;
+ if (str) {
+ for (var j=str.length-1; j>=0; j--) {
+ h ^= QControl.ANTABLE.indexOf(str.charAt(j)) + 1;
+ for (var i=0; i<3; i++) {
+ var m = (h = h<<7 | h>>>25) & 150994944;
+ h ^= m ? (m == 150994944 ? 1 : 0) : 1;
+ }
+ }
+ }
+ return h;
+}
+
+function QControl_nop() {
+}
+
+function QControl() {
+ this.init = QControl_init;
+ this.hash = QControl_hash;
+ this.window = self;
+ this.document = self.document;
+ this.tag = null;
+}
+QControl.ANTABLE = "w5Q2KkFts3deLIPg8Nynu_JAUBZ9YxmH1XW47oDpa6lcjMRfi0CrhbGSOTvqzEV";
+QControl.HEXTABLE = "0123456789ABCDEF";
+QControl.nop = QControl_nop;
+QControl.event = QControl_nop;
diff --git a/httemplate/elements/qlib/counter.js b/httemplate/elements/qlib/counter.js
new file mode 100644
index 0000000..72aeddb
--- /dev/null
+++ b/httemplate/elements/qlib/counter.js
@@ -0,0 +1,81 @@
+/**
+ * QLIB 1.0 Animated Digital Counter
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QCounter_update() {
+ with (this) {
+ var v = Math.max(value, 0);
+ var mod;
+ for (var j=0; j<size; j++) {
+ mod = Math.floor(v % 10);
+ images[j].src = (v >= 1) || (!j) ? res.list[mod].src : res.list[10].src;
+ v /= 10;
+ }
+ }
+}
+
+function QCounter_count(value, step) {
+ this._cntt = false;
+ this.value += step;
+ if ((step * (this.value - value)) >= 0) {
+ this.value = value - 0; // convert to number
+ } else {
+ this._cntt = setTimeout(this.name + ".count(" + value + "," + step + ")", 50);
+ }
+ this.update();
+}
+
+function QCounter_set(value) {
+ this.setval = value;
+ if (value != this.value) {
+ if (this._cntt) {
+ clearTimeout(this._cntt);
+ this._cntt = false;
+ }
+ var dv = value - this.value;
+ if (this.effect == 2) {
+ dv = dv / Math.min(10, Math.abs(dv));
+ } else if (this.effect == 3) {
+ dv = dv / Math.abs(dv);
+ }
+ this.count(value, dv);
+ }
+}
+
+function QCounter(parent, name, res, size, effect) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.setval = this.value = 0;
+ this.size = size || 4;
+ this.effect = effect || 2;
+ this._cntt = false;
+ this.images = new Array(this.size);
+ this.set = QCounter_set;
+ this.update = QCounter_update;
+ this.count = QCounter_count;
+ with (this) {
+ document.write('<table class="qcounter" width="' + (res.width * size) + '" height="' + res.height +
+ '" border="0" cellspacing="0" cellpadding="0" unselectable="on"><tr>');
+ for (var j=(size - 1); j>=0; j--) {
+ document.write('<td width="' + res.width + '" height="' + res.height +
+ '" unselectable="on"><img name="' + id + j + '" src="' + (j ? res.list[10].src : res.list[0].src) +
+ '" border="0" width="' + res.width + '" height="' + res.height + '"></td>');
+ images[j] = document.images[id + j] || new Image(1, 1);
+ }
+ document.write('</tr></table>');
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QCounter.prototype = new QControl();
+QCounter.INSTANT = 1;
+QCounter.FAST = 2;
+QCounter.SLOW = 3;
diff --git a/httemplate/elements/qlib/imagelist.js b/httemplate/elements/qlib/imagelist.js
new file mode 100644
index 0000000..9f12de0
--- /dev/null
+++ b/httemplate/elements/qlib/imagelist.js
@@ -0,0 +1,25 @@
+/**
+ * QLIB 1.0 ImageList Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QImageList(width, height) {
+ var len = QImageList.arguments.length - 2;
+ if (len > 0) {
+ this.list = new Array(len);
+ this.length = len;
+ this.width = width;
+ this.height = height;
+ var im;
+ for (var j=0; j<len; j++) {
+ im = new Image(width, height);
+ im.src = QImageList.arguments[j + 2];
+ this.list[j] = im;
+ }
+ }
+} \ No newline at end of file
diff --git a/httemplate/elements/qlib/label.js b/httemplate/elements/qlib/label.js
new file mode 100644
index 0000000..2d8b1e7
--- /dev/null
+++ b/httemplate/elements/qlib/label.js
@@ -0,0 +1,72 @@
+/**
+ * QLIB 1.0 Text Label
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QLabel_set_ie(value) {
+ this.label.innerText = (this.value = value) || "\xA0";
+}
+
+function QLabel_set_dom2(value) {
+ with (this.label) {
+ replaceChild(this.document.createTextNode((this.value = value) || "\xA0"), firstChild);
+ }
+}
+
+function QLabel_set_ns4(value) {
+ this.value = value || "";
+ with (this) {
+ document.open();
+ document.write('<div class="qlabel">' + (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' +
+ name + '.doEvent()" onMouseOut="window.top.status=\'\'" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true">' + value + '</a>' : value) + '</div>');
+ document.close();
+ }
+}
+
+function QLabel_doEvent() {
+ this.onClick(this.value, this.tag);
+ return false;
+}
+
+function QLabel(parent, name, value, clickable, tooltip) {
+ this.init(parent, name);
+ this.value = value || "";
+ this.clickable = clickable || false;
+ this.tooltip = tooltip || "";
+ this.doEvent = QLabel_doEvent;
+ this.onClick = QControl.event;
+ with (this) {
+ if (document.getElementById || document.all) {
+ document.write(clickable ? '<div class="qlabel" unselectable="on"><a id="' + id + '" href="#" title="' +
+ tooltip + '" onClick="return ' + name + '.doEvent()" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true" onMouseOut="window.top.status=\'\'" hidefocus="true" unselectable="on">' +
+ (value || '&nbsp;') + '</a></div>' : '<div id="' + id + '" class="qlabel" unselectable="on">' +
+ (value || '&nbsp;') + '</div>');
+ this.label = document.getElementById ? document.getElementById(id) :
+ (document.all.item ? document.all.item(id) : document.all[id]);
+ this.set = (label && (label.innerText ? QLabel_set_ie :
+ (label.replaceChild && QLabel_set_dom2))) || QControl.nop;
+ } else if (document.layers) {
+ var suffix = "";
+ for (var j=value.length; j<QLabel.TEXTQUOTA; j++) suffix += " &nbsp;";
+ document.write('<div><ilayer id="i' + id + '"><layer id="' + id + '"><div class="qlabel">' +
+ (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' + name +
+ '.doEvent()" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true" onMouseOut="window.top.status=\'\'">' + value + suffix + '</a>' :
+ value + suffix) + '</div></layer></ilayer></div>');
+ this.label = (this.label = document.layers["i" + id]) && label.document.layers[id];
+ this.document = label && label.document;
+ this.set = (label && document) ? QLabel_set_ns4 : QControl.nop;
+ } else {
+ document.write("Object is not supported");
+ }
+ }
+}
+QLabel.prototype = new QControl();
+QLabel.TEXTQUOTA = 50;
diff --git a/httemplate/elements/qlib/messagebox.js b/httemplate/elements/qlib/messagebox.js
new file mode 100644
index 0000000..2e45839
--- /dev/null
+++ b/httemplate/elements/qlib/messagebox.js
@@ -0,0 +1,57 @@
+/**
+ * QLIB 1.0 Message Box Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QMessageBox_alert(msg) {
+ if (typeof(msg) == "string") {
+ this.label.set(this.value = msg);
+ }
+ this.center();
+ this.focus();
+ this.show(true);
+}
+
+function QMessageBox_close() {
+ with (this.parent) {
+ if (!onClose(tag)) show(false);
+ }
+}
+
+function QMessageBox_body() {
+ with (this) {
+ document.write('<table border="0" width="' + cwidth + '"><tr><td align="left" valign="top" unselectable="on">');
+ this.label = new QLabel(this, "label", value);
+ document.write('</td></tr><tr><td height="' + (bres.height + 14) + '" align="center" valign="bottom" unselectable="on">');
+ this.button = new QButton(this, "button", bres, "Close");
+ document.write('</td></tr></table>');
+ button.onClick = QMessageBox_close;
+ }
+}
+
+function QMessageBox(parent, name, box, btn, msg, effects, opacity) {
+ this.init(parent, name);
+ if ((this.res = box) && (this.bres = btn)) {
+ this.value = typeof(msg) == "string" ? msg : "";
+ this.width = Math.max(200, Math.floor(Math.sqrt(555 * this.value.length)));
+ this.height = null;
+ this.x = this.y = 0;
+ this.visible = false;
+ this.zindex = null;
+ this.body = QMessageBox_body;
+ var j = QMessageBox.arguments.length;
+ this.effects = j > 5 ? effects : (box.effects != null ? box.effects : 0);
+ this.opacity = j > 6 ? opacity : (box.opacity != null ? box.opacity : 100);
+ this.create();
+ this.alert = QMessageBox_alert;
+ this.onClose = QControl.event;
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QMessageBox.prototype = new QBoxCtrl();
diff --git a/httemplate/elements/qlib/progress.js b/httemplate/elements/qlib/progress.js
new file mode 100644
index 0000000..2de077e
--- /dev/null
+++ b/httemplate/elements/qlib/progress.js
@@ -0,0 +1,73 @@
+/**
+ * QLIB 1.0 Progress Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QProgress_update() {
+ with (this) {
+ var i = low;
+ for (var j=0; j<size; j++) {
+ images[j].src = i < value ? imgsrc1 : imgsrc0;
+ i += delta;
+ }
+ }
+}
+
+function QProgress_set(value) {
+ this.value = value - 0;
+ this.update();
+}
+
+function QProgress_setBounds(low, high) {
+ this.low = Math.min(low, high);
+ this.high = Math.max(low, high);
+ this.delta = (this.high - this.low) / this.size;
+ this.update();
+}
+
+function QProgress(parent, name, res, size, style) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.value = 0;
+ this.low = 0;
+ this.high = 100;
+ this.size = size || 10;
+ this.delta = 100 / this.size;
+ this.style = style || 0;
+ this.images = new Array(this.size);
+ this.imgsrc0 = res.list[0] && res.list[0].src;
+ this.imgsrc1 = res.list[1] && res.list[1].src;
+ this.set = QProgress_set;
+ this.update = QProgress_update;
+ this.setBounds = QProgress_setBounds;
+ with (this) {
+ var hor = this.style < 2;
+ var rev = this.style % 2;
+ document.write('<table class="qprogress" border="0" cellspacing="0" cellpadding="0" unselectable="on" ' +
+ (hor ? 'width="' + (size * res.width) + '" height="' + res.height + '"><tr>' : 'width="' + res.width +
+ '" height="' + (size * res.height) + '">'));
+ for (var j=0; j<size; j++) {
+ document.write((hor ? '' : '<tr>') + '<td width="' + res.width + '" height="' + res.height +
+ '" unselectable="on"><img name="' + id + (rev ? size - j - 1 : j) + '" src="' + res.list[0].src +
+ '" border="0" width="' + res.width + '" height="' + res.height + '"></td>' + (hor ? '' : '</tr>'));
+ }
+ document.write((hor ? '</tr>' : '') + '</table>');
+ for (var j=0; j<size; j++) {
+ images[j] = document.images[id + j] || new Image(1, 1);
+ }
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QProgress.prototype = new QControl();
+QProgress.NORMAL = 0;
+QProgress.REVERSE = 1;
+QProgress.FALL = 2;
+QProgress.RISE = 3;
diff --git a/httemplate/elements/qlib/sound.js b/httemplate/elements/qlib/sound.js
new file mode 100644
index 0000000..3d1aaf6
--- /dev/null
+++ b/httemplate/elements/qlib/sound.js
@@ -0,0 +1,47 @@
+/**
+ * QLIB 1.0 Preloaded Sound
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QSound_play(loop) {
+ this._out.loop = loop || 0;
+ this._out.src = this._buf.src;
+}
+
+function QSound_stop() {
+ this._out.loop = 0;
+ this._out.src = "";
+}
+
+function QSound_setVolume(volume) {
+ this._out.volume = this.volume = volume;
+}
+
+function QSound(parent, name, src, volume) {
+ this.init(parent, name);
+ this.volume = volume || 0;
+ this.play = this.stop = this.setVolume = QControl.nop;
+ with (this) {
+ document.write('<bgsound id="' + id + '" src="" volume="' + volume + '">');
+ if (document.all && document.all.item) {
+ this._out = document.all.item(id);
+ if (_out && (typeof _out.src != "undefined") && (_out.volume === volume)) {
+ document.write('<bgsound id="b' + id + '" src="' + src + '" volume="-10000">');
+ this._buf = document.all.item("b" + id);
+ if (_buf) {
+ this.play = QSound_play;
+ this.stop = QSound_stop;
+ this.setVolume = QSound_setVolume;
+
+ _out.onreadystatechange = new Function("alert(0)");
+ }
+ }
+ }
+ }
+}
+QSound.prototype = new QControl();
diff --git a/httemplate/elements/qlib/sprite.js b/httemplate/elements/qlib/sprite.js
new file mode 100644
index 0000000..72a68fb
--- /dev/null
+++ b/httemplate/elements/qlib/sprite.js
@@ -0,0 +1,125 @@
+/**
+ * QLIB 1.0 Sprite Object
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QSprite_load(src) {
+ if (src) {
+ this.face = new Image(this.cwidth, this.cheight);
+ this.face.src = src;
+ this.valid = false;
+ }
+}
+
+function QSprite_show(show) {
+ if (show && !this.valid && this.face.complete) {
+ this._img.src = this.face.src;
+ this.valid = true;
+ }
+ this._show(show);
+}
+
+function QSprite_moveTo(x, y) {
+ this.stop();
+ this._move(x, y);
+}
+
+function QSprite_slideTo(x, y) {
+ this.stop();
+ if (this.visible) {
+ this.doSlide(++this._spro, x, y);
+ } else {
+ this.moveTo(x, y);
+ }
+}
+
+function QSprite_shake() {
+ this.stop();
+ if (this.visible) {
+ this.doShake(++this._spro, 0, this.x, this.y);
+ }
+}
+
+function QSprite_stop() {
+ this._spro++;
+ if (this._sprt) {
+ clearTimeout(this._sprt);
+ this._sprt = false;
+ }
+}
+
+function QSprite_doSlide(id, x, y) {
+ if (this._spro == id) {
+ this._sprt = false;
+ var dx = Math.round(x - this.x);
+ var dy = Math.round(y - this.y);
+ if (dx || dy) {
+ if (dx) dx = dx > 0 ? Math.ceil(dx/4) : Math.floor(dx/4);
+ if (dy) dy = dy > 0 ? Math.ceil(dy/4) : Math.floor(dy/4);
+ this._move(this.x + dx, this.y + dy);
+ this._sprt = setTimeout(this.name + ".doSlide(" + id + "," + x + "," + y + ")", 30);
+ } else {
+ this._move(x, y);
+ }
+ }
+}
+
+function QSprite_doShake(id, phase, x, y) {
+ if (this._spro == id) {
+ this._sprt = false;
+ if (phase < 20) {
+ var m = 3 * Math.sin(.16 * phase);
+ this._move(x + m * Math.sin(phase), y + m * Math.cos(phase));
+ this._sprt = setTimeout(this.name + ".doShake(" + id + "," + (++phase) + "," + x + "," + y + ")", 20);
+ } else {
+ this._move(x, y);
+ }
+ }
+}
+
+function QSprite_doClick() {
+ if (!this._sprt) {
+ this.onClick(this.tag);
+ }
+ return false;
+}
+
+function QSprite(parent, name, x, y, width, height, src, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = (this.cwidth = width - 0) + 8;
+ this.height = (this.cheight = height - 0) + 8;
+ var j = QSprite.arguments.length;
+ this.visible = (j > 7) ? visible : true;
+ this.effects = (j > 8) ? effects : 0;
+ this.opacity = (j > 9) ? opacity : 100;
+ this.zindex = (j > 10) ? zindex : null;
+ this.valid = !!src;
+ this.content = '<a href="#" title="" onclick="return false" onmousedown="return ' + this.name +
+ '.doClick()" onmouseover="window.top.status=\'\';return true" hidefocus="true" unselectable="on"><img name="' +
+ this.id + '" src="' + (src || '') + '" border="0" width="' + this.cwidth + '" height="' + this.cheight +
+ '" alt="" unselectable="on"></a>';
+ this.doClick = QSprite_doClick;
+ this.doSlide = QSprite_doSlide;
+ this.doShake = QSprite_doShake;
+ this.onClick = QControl.event;
+ this.create();
+ this.face = this._img = this.document.images[this.id] || new Image(1, 1);
+ this._spro = 0;
+ this._sprt = false;
+ this._show = this.show;
+ this._move = this.moveTo;
+ this.load = QSprite_load;
+ this.show = QSprite_show;
+ this.moveTo = QSprite_moveTo;
+ this.slideTo = QSprite_slideTo;
+ this.shake = QSprite_shake;
+ this.stop = QSprite_stop;
+}
+QSprite.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/window.js b/httemplate/elements/qlib/window.js
new file mode 100644
index 0000000..6056fda
--- /dev/null
+++ b/httemplate/elements/qlib/window.js
@@ -0,0 +1,25 @@
+/**
+ * QLIB 1.0 Window Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QWindow(parent, name, x, y, width, height, content, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = width - 0;
+ this.height = (typeof(height) == "number") ? height : null;
+ this.content = content;
+ var j = QWindow.arguments.length;
+ this.visible = (j > 7) ? visible : true;
+ this.effects = (j > 8) ? effects : 0;
+ this.opacity = (j > 9) ? opacity : 100;
+ this.zindex = (j > 10) ? zindex : null;
+ this.create();
+}
+QWindow.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/wndctrl.js b/httemplate/elements/qlib/wndctrl.js
new file mode 100644
index 0000000..b3bde4e
--- /dev/null
+++ b/httemplate/elements/qlib/wndctrl.js
@@ -0,0 +1,322 @@
+/**
+ * QLIB 1.0 Window Abstraction
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * 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.
+ * http://qlib.quazzle.com
+ */
+
+function QWndCtrl_center_ie4() {
+ var b = this.document.body;
+ this.moveTo(b.scrollLeft + Math.max(0, Math.floor((b.clientWidth -
+ this.width) / 2)), b.scrollTop + 100);
+}
+
+function QWndCtrl_center_moz() {
+ this.moveTo(self.pageXOffset + Math.max(0, Math.floor((self.innerWidth -
+ this.width) / 2)), self.pageYOffset + 100);
+}
+
+function QWndCtrl_setEffects_ie4(fx) {
+ this.effects = fx;
+ with (this.wnd) {
+ filters[0].enabled = (fx & 256) != 0;
+ filters[1].enabled = (fx & 512) != 0;
+ filters[2].enabled = (fx & 1024) != 0;
+ filters[4].enabled = (fx & 2048) != 0;
+ }
+}
+
+function QWndCtrl_setEffects_moz(fx) {
+ this.effects = fx;
+}
+
+function QWndCtrl_setOpacity_ie4(op) {
+ this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));
+ this.wnd.filters[3].opacity = this.opacity;
+ this.wnd.filters[3].enabled = (this.opacity < 100);
+}
+
+function QWndCtrl_setOpacity_moz(op) {
+ this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));
+ this.wnd.style.MozOpacity = this.opacity + "%";
+}
+
+function QWndCtrl_setSize_css(w, h) {
+ this.wnd.style.width = (this.width = Math.floor(w - 0)) + "px";
+ this.wnd.style.height = typeof(h) == "number" ? (this.height = Math.floor(h)) + "px" : "auto";
+}
+
+function QWndCtrl_setSize_ns4(w, h) {
+ this.wnd.clip.width = this.width = Math.floor(w - 0);
+ if (typeof(h) == "number") {
+ this.wnd.clip.height = this.height = Math.floor(h);
+ }
+}
+
+function QWndCtrl_focus() {
+ this.setZIndex(QWndCtrl.TOPZINDEX++);
+}
+
+function QWndCtrl_setZIndex_css(z) {
+ this.wnd.style.zIndex = this.zindex = z || 0;
+}
+
+function QWndCtrl_setZIndex_ns4(z) {
+ this.wnd.zIndex = this.zindex = z || 0;
+}
+
+function QWndCtrl_moveTo_css(x, y) {
+ this.wnd.style.left = (this.x = Math.floor(x - 0)) + "px";
+ this.wnd.style.top = (this.y = Math.floor(y - 0)) + "px";
+}
+
+function QWndCtrl_moveTo_ns4(x, y) {
+ this.wnd.moveTo(this.x = Math.floor(x - 0), this.y = Math.floor(y - 0));
+}
+
+function QWndCtrl_fxhandler() {
+ this.fxhandler = QControl.nop;
+ this.onShow(this.visible, this.tag);
+}
+
+function QWndCtrl_show_ie4(show) {
+ if (this.visible != show) {
+ var fx = false;
+ switch (show ? this.effects & 15 : (this.effects & 240) >>> 4) {
+ case 1:
+ fx = this.wnd.filters[5];
+ break;
+ case 2:
+ (fx = this.wnd.filters[6]).transition = show ? 1 : 0;
+ break;
+ case 3:
+ (fx = this.wnd.filters[6]).transition = show ? 3 : 2;
+ break;
+ case 4:
+ (fx = this.wnd.filters[6]).transition = show ? 5 : 4;
+ break;
+ case 5:
+ (fx = this.wnd.filters[6]).transition = show ? 14 : 13;
+ break;
+ case 6:
+ (fx = this.wnd.filters[6]).transition = show ? 16 : 15;
+ break;
+ case 7:
+ (fx = this.wnd.filters[6]).transition = 12;
+ break;
+ case 8:
+ (fx = this.wnd.filters[6]).transition = 8;
+ break;
+ case 9:
+ (fx = this.wnd.filters[6]).transition = 9;
+ }
+ if (fx) {
+ fx.apply();
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.fxhandler = QWndCtrl_fxhandler;
+ fx.play(0.3);
+ } else {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_fade_moz(op, step) {
+ this._wndt = false;
+ if (step) {
+ op += step;
+ if ((op > 0) && (op < this.opacity)) {
+ this.wnd.style.MozOpacity = op + "%";
+ this._wndt = setTimeout(this.name + ".fade(" + op + "," + step + ")", 50);
+ } else {
+ if (op <= 0) {
+ this.wnd.style.visibility = "hidden";
+ this.visible = false;
+ }
+ this.wnd.style.MozOpacity = this.opacity + "%";
+ this.onShow(this.visible, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_show_moz(show) {
+ if (this.visible != show) {
+ if (this._wndt) {
+ clearTimeout(this._wndt);
+ this._wndt = false;
+ }
+ var step = show ? ((this.effects & 15) == 1) && Math.floor(this.opacity / 5) :
+ ((this.effects & 240) == 16) && -Math.floor(this.opacity / 5);
+ if (step) {
+ if (this.visible) {
+ this.fade(this.opacity - 0, step);
+ } else {
+ this.wnd.style.MozOpacity = "0%";
+ this.wnd.style.visibility = "visible";
+ this.visible = true;
+ this.fade(0, step);
+ }
+ } else {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_show_css(show) {
+ if (this.visible != show) {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+}
+
+function QWndCtrl_show_ns4(show) {
+ if (this.visible != show) {
+ this.wnd.visibility = (this.visible = show) ? "show" : "hidden";
+ this.onShow(show, this.tag);
+ }
+}
+
+function QWndCtrl_create_dom2() {
+ with (this) {
+ this.fxhandler = QControl.nop;
+ var ie4 = document.body && document.body.filters;
+ var moz = document.body && document.body.style &&
+ typeof(document.body.style.MozOpacity) == "string";
+ document.write('<div unselectable="on" id="' + id +
+ (ie4 ? '" onfilterchange="' + name + '.fxhandler()': '') +
+ '" style="position:absolute;left:' + x + 'px;top:' + y +
+ 'px;width:' + width + (height != null ? 'px;height:' + height : '') +
+ 'px;visibility:' + (visible ? 'visible' : 'hidden') +
+ ';overflow:hidden' + (zindex ? ';z-index:' + zindex : '') +
+ (ie4 ? ';filter:Gray(enabled=' + (effects & 256 ? '1' : '0') +
+ ') Xray(enabled=' + (effects & 512 ? '1' : '0') +
+ ') Invert(enabled=' + (effects & 1024 ? '1' : '0') +
+ ') alpha(enabled=' + (opacity < 100 ? '1' : '0') + ',opacity=' + opacity +
+ ') shadow(enabled=' + (effects & 2048 ? '1' : '0') +
+ ',direction=135) BlendTrans(enabled=0) RevealTrans(enabled=0)' : '') +
+ (moz && (opacity < 100) ? ';-moz-opacity:' + opacity + '%' : '') +
+ '"><div unselectable="on" class="qwindow">');
+ if (typeof(content) == "function") {
+ this.content();
+ } else {
+ document.write(content);
+ }
+ document.write('</div></div>');
+ if (this.wnd = document.getElementById ? document.getElementById(id) :
+ (document.all.item ? document.all.item(id) : document.all[id])) {
+ if (wnd.style) {
+ ie4 = ie4 && wnd.filters;
+ moz = moz && typeof(wnd.style.MozOpacity) == "string";
+ this.moveTo = QWndCtrl_moveTo_css;
+ this.setZIndex = QWndCtrl_setZIndex_css;
+ this.focus = QWndCtrl_focus;
+ this.setSize = QWndCtrl_setSize_css;
+ this.show = ie4 ? QWndCtrl_show_ie4 : (moz ? QWndCtrl_show_moz : QWndCtrl_show_css);
+ this.fade = moz ? QWndCtrl_fade_moz : QControl.nop;
+ this.setOpacity = ie4 ? QWndCtrl_setOpacity_ie4 : (moz ? QWndCtrl_setOpacity_moz : QControl.nop);
+ this.setEffects = ie4 ? QWndCtrl_setEffects_ie4 : (moz ? QWndCtrl_setEffects_moz : QControl.nop);
+ this.center = self.innerWidth ? QWndCtrl_center_moz :
+ (document.body && document.body.clientWidth ? QWndCtrl_center_ie4 : QControl.nop);
+ }
+ }
+ }
+}
+
+function QWndCtrl_create_ns4(finalize) {
+ with (this) {
+ if (finalize) {
+ if (_wnde) {
+ parent.window.onload = _wnde;
+ parent.window.onload();
+ }
+ document.open();
+ document.write('<div class="qwindow">');
+ this.content();
+ document.write('</div>');
+ document.close();
+ } else {
+ document.write('<layer id="' + id + '" left="' + x + '" top="' + y +
+ '" width="' + width + '" visibility="' + (visible ? 'show' : 'hidden') +
+ (height != null ? '" height="' + height + '" clip="' + width + ',' + height : '') +
+ (zindex ? '" z-index="' + zindex : '') + (typeof(content) != "function" ?
+ '"><div class="qwindow">' + content + '</div></layer>' : '">&nbsp;</layer>'));
+ if (this.window = this.wnd = document.layers[id]) {
+ if (this.document = wnd.document) {
+ this.show = QWndCtrl_show_ns4;
+ this.moveTo = QWndCtrl_moveTo_ns4;
+ this.setZIndex = QWndCtrl_setZIndex_ns4;
+ this.focus = QWndCtrl_focus;
+ this.center = QWndCtrl_center_moz;
+ this.setSize = QWndCtrl_setSize_ns4;
+ if (typeof(content) == "function") {
+ this._wnde = parent.window.onload;
+ parent.window.onload = new Function(name + ".create(true)");
+ }
+ }
+ }
+ }
+ }
+}
+
+function QWndCtrl_create_na() {
+ this.document.write('Object is not supported.');
+ this.wnd = null;
+}
+
+function QWndCtrl_create() {
+ with (this) {
+ this.create = (document.getElementById || document.all) ? QWndCtrl_create_dom2 :
+ (document.layers ? QWndCtrl_create_ns4 : QWndCtrl_create_na);
+ create();
+ }
+}
+
+function QWndCtrl() {
+ this.x = this.y = 0;
+ this.width = this.height = 0;
+ this.content = "";
+ this.visible = true;
+ this.effects = 0;
+ this.opacity = 100;
+ this.zindex = null;
+ this._wndt = this._wnde = false;
+ this.create = QWndCtrl_create;
+ this.show = QControl.nop;
+ this.focus = QControl.nop;
+ this.center = QControl.nop;
+ this.moveTo = QControl.nop;
+ this.setSize = QControl.nop;
+ this.setOpacity = QControl.nop;
+ this.setEffects = QControl.nop;
+ this.setZIndex = QControl.nop;
+ this.onShow = QControl.event;
+}
+QWndCtrl.prototype = new QControl();
+QWndCtrl.TOPZINDEX = 1000;
+QWndCtrl.GRAY = 256;
+QWndCtrl.XRAY = 512;
+QWndCtrl.INVERT = 1024;
+QWndCtrl.SHADOW = 2048;
+QWndCtrl.FADEIN = 1;
+QWndCtrl.FADEOUT = 16;
+QWndCtrl.BOXIN = 2;
+QWndCtrl.BOXOUT = 32;
+QWndCtrl.CIRCLEIN = 3;
+QWndCtrl.CIRCLEOUT = 48;
+QWndCtrl.WIPEIN = 4;
+QWndCtrl.WIPEOUT = 64;
+QWndCtrl.HBARNIN = 5;
+QWndCtrl.HBARNOUT = 80;
+QWndCtrl.VBARNIN = 6;
+QWndCtrl.VBARNOUT = 96;
+QWndCtrl.DISSOLVEIN = 7;
+QWndCtrl.DISSOLVEOUT = 112;
+QWndCtrl.HBLINDSIN = 8;
+QWndCtrl.HBLINDSOUT = 128;
+QWndCtrl.VBLINDSIN = 9;
+QWndCtrl.VBLINDSOUT = 144;
diff --git a/httemplate/elements/rs_init_object.html b/httemplate/elements/rs_init_object.html
new file mode 100644
index 0000000..3bb0b42
--- /dev/null
+++ b/httemplate/elements/rs_init_object.html
@@ -0,0 +1,29 @@
+<%doc>
+
+Example:
+
+ include( '/elements/rs_init_object.html' );
+
+</%doc>
+<SCRIPT TYPE="text/javascript">
+
+ function rs_init_object() {
+ var A;
+ try {
+ A=new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ try {
+ A=new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (oc) {
+ A=null;
+ }
+ }
+ if(!A && typeof XMLHttpRequest != "undefined")
+ A = new XMLHttpRequest();
+ if (!A)
+ alert("Can't create XMLHttpRequest object");
+ return A;
+
+ }
+
+</SCRIPT>
diff --git a/httemplate/elements/search-cust_main.html b/httemplate/elements/search-cust_main.html
new file mode 100644
index 0000000..317922d
--- /dev/null
+++ b/httemplate/elements/search-cust_main.html
@@ -0,0 +1,201 @@
+<%doc>
+
+Example:
+
+ include( '/elements/search-cust_main.html,
+ 'field' => 'custnum',
+ #slightly deprecated old synonym for field#'field_name'=>'custnum',
+ 'find_button' => 1, #add a "find" button to the field
+ 'curr_value' => 54, #current value
+ 'value => 32, #deprecated synonym for curr_value
+ );
+
+</%doc>
+<INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $value %>">
+
+<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... -->
+
+<INPUT TYPE = "text"
+ NAME = "<% $field %>_search"
+ ID = "<% $field %>_search"
+ SIZE = "32"
+ VALUE="<% $cust_main ? $cust_main->name : '(cust #, name or company)' %>"
+ onFocus="clearhint_<% $field %>_search(this);"
+ onClick="clearhint_<% $field %>_search(this);"
+ onChange="smart_<% $field %>_search(this);"
+>
+
+% if ( $opt{'find_button'} ) {
+ <INPUT TYPE = "button"
+ VALUE = 'Find',
+ NAME = "<% $field %>_findbutton"
+ onClick = "smart_<% $field %>_search(this.form.<% $field %>_search);"
+ >
+% }
+
+<SELECT NAME="<% $field %>_select" ID="<% $field %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $field %>(this);">
+</SELECT>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi',
+ 'subs' => [ 'smart_search' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_<% $field %>_search (what) {
+
+ what.style.color = '#000000';
+
+ if ( what.value == '(cust #, name or company)' )
+ what.value = '';
+
+ if ( what.value.indexOf('Customer not found: ') == 0 )
+ what.value = what.value.substr(20);
+
+ }
+
+ var <% $field %>_search_active = false;
+
+ function smart_<% $field %>_search(what) {
+
+ var customer = what.value;
+
+ if ( customer == 'searching...' || customer == ''
+ || customer.indexOf('Customer not found: ') == 0 )
+ return;
+
+ if ( what.getAttribute('magic') == 'nosearch' ) {
+ what.setAttribute('magic', '');
+ return;
+ }
+
+ //what.value = 'searching...'
+ what.disabled = true;
+ what.style.color= '#000000';
+ what.style.backgroundColor = '#dddddd';
+
+ var customer_select = document.getElementById('<% $field %>_select');
+
+ //alert("search for customer " + customer);
+
+ function <% $field %>_search_update(customers) {
+
+ //alert('customers returned: ' + customers);
+
+ var customerArray = eval('(' + customers + ')');
+
+ what.disabled = false;
+ what.style.backgroundColor = '#ffffff';
+
+ if ( customerArray.length == 0 ) {
+
+ what.form.<% $field %>.value = '';
+
+ what.value = 'Customer not found: ' + what.value;
+ what.style.color = '#ff0000';
+
+ what.style.display = '';
+ customer_select.style.display = 'none';
+
+ } else if ( customerArray.length == 1 ) {
+
+ //alert('one customer found: ' + customerArray[0]);
+
+ what.form.<% $field %>.value = customerArray[0][0];
+ what.value = customerArray[0][1];
+
+ what.style.display = '';
+ customer_select.style.display = 'none';
+
+ } else {
+
+ //alert('multiple customers found, have to create select dropdown');
+
+ //blank the current list
+ for ( var i = customer_select.length; i >= 0; i-- )
+ customer_select.options[i] = null;
+
+ opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
+
+ //add the multiple customers
+ for ( var s = 0; s < customerArray.length; s++ )
+ opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000');
+
+ opt(customer_select, 'cancel', '(Edit search string)', '#000000');
+
+ what.style.display = 'none';
+ customer_select.style.display = '';
+
+ }
+
+ <% $field %>_search_active = false;
+
+ }
+
+ <% $field %>_search_active = true;
+
+ smart_search( customer, <% $field %>_search_update );
+
+
+ }
+
+ function select_<% $field %> (what) {
+
+ var custnum = what.options[what.selectedIndex].value;
+ var customer = what.options[what.selectedIndex].text;
+
+ var customer_obj = document.getElementById('<% $field %>_search');
+
+ if ( custnum == '' ) {
+ //what.style.color = '#ff0000';
+
+ } else if ( custnum == 'cancel' ) {
+
+ customer_obj.style.color = '#000000';
+
+ what.style.display = 'none';
+ customer_obj.style.display = '';
+ customer_obj.focus();
+
+ } else {
+
+ what.form.<% $field %>.value = custnum;
+
+ customer_obj.value = customer;
+ customer_obj.style.color = '#000000';
+
+ what.style.display = 'none';
+ customer_obj.style.display = '';
+
+ }
+
+ }
+
+ function opt(what,value,text,color) {
+ var optionName = new Option(text, value, false, false);
+ optionName.style.color = color;
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+</SCRIPT>
+<%init>
+
+my( %opt ) = @_;
+
+my $field = $opt{'field'} || $opt{'field_name'} || 'custnum';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $cust_main = '';
+if ( $value ) {
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $value },
+ 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+}
+
+</%init>
diff --git a/httemplate/elements/searchbar-address2.html b/httemplate/elements/searchbar-address2.html
new file mode 100644
index 0000000..d5e2b37
--- /dev/null
+++ b/httemplate/elements/searchbar-address2.html
@@ -0,0 +1,34 @@
+% if ( $conf->exists('address2-search') ) {
+
+ <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0;display:inline">
+ <INPUT TYPE="hidden" NAME="address2_on" VALUE="1">
+ <INPUT NAME="address2_text" TYPE="text" VALUE="<% $address2_label |n %>" STYLE="width:67px" onFocus="clearhint_search_address2(this);" onClick="clearhint_search_address2(this);" CLASS="fstext">
+ <BR>
+ <INPUT TYPE="submit" VALUE="Search units" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px;margin-top:3px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_address2 (what) {
+ if ( what.value == '<% $address2_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%once>
+
+my $address2_label = '(Unit #)';
+
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+</%init>
diff --git a/httemplate/elements/searchbar-cust_bill.html b/httemplate/elements/searchbar-cust_bill.html
new file mode 100644
index 0000000..448f40f
--- /dev/null
+++ b/httemplate/elements/searchbar-cust_bill.html
@@ -0,0 +1,34 @@
+% if ( $curuser->access_right('View invoices') ) {
+
+ <FORM ACTION="<%$fsurl%>search/cust_bill.html" METHOD="GET" STYLE="margin:0;display:inline">
+ <INPUT NAME="invnum" TYPE="text" VALUE="<% $inv_label |n %>" STYLE="width:56px" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" CLASS="fstext">
+% if ( $curuser->access_right('List invoices') ) {
+ <A HREF="<%$fsurl%>search/report_cust_bill.html" CLASS="fslink" STYLE="font-size: 11px">Adv</A>\
+% }
+ <BR>
+ <INPUT TYPE="submit" VALUE="Search invoices" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px;margin-top:3px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR><BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_invoice (what) {
+ if ( what.value == '<% $inv_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%once>
+
+my $inv_label = '(inv #)';
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+</%init>
diff --git a/httemplate/elements/searchbar-cust_main.html b/httemplate/elements/searchbar-cust_main.html
new file mode 100644
index 0000000..e910681
--- /dev/null
+++ b/httemplate/elements/searchbar-cust_main.html
@@ -0,0 +1,38 @@
+% if ( $curuser->access_right('List customers') ) {
+
+ <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0">
+ <INPUT NAME="search_cust" TYPE="text" VALUE="<% $cust_label |n %>" STYLE="width:<%$width%>" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" CLASS="fstext"><BR>
+ <A HREF="<%$fsurl%>search/report_cust_main.html" CLASS="fslink" STYLE="font-size: 11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_cust (what) {
+ if ( what.value == '<% $cust_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+my $cust_width = 246;
+my $cust_label = '(cust #, name, company';
+if ( $conf->exists('address1-search') ) {
+ $cust_label .= ', address';
+ $cust_width += 56;
+}
+$cust_label .= ' or contact phone)';
+
+my $width = $menu_position eq 'left' ? '190px' : $cust_width.'px';
+
+</%init>
diff --git a/httemplate/elements/searchbar-cust_svc.html b/httemplate/elements/searchbar-cust_svc.html
new file mode 100644
index 0000000..cc0ec97
--- /dev/null
+++ b/httemplate/elements/searchbar-cust_svc.html
@@ -0,0 +1,33 @@
+% if ( $curuser->access_right('View customer services') ) {
+
+ <FORM ACTION="<%$fsurl%>search/cust_svc.html" METHOD="GET" STYLE="margin:0">
+ <INPUT NAME="search_svc" TYPE="text" VALUE="<% $svc_label |n %>" STYLE="width:<% $width %>" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" CLASS="fstext"><BR>
+ <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #cccccc; font-size:11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search services" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_svc (what) {
+ if ( what.value == '<% $svc_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%once>
+
+my $svc_label = '(user, email, ip, mac, domain or service phone)';
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+my $width = $menu_position eq 'left' ? '190px' : '271px';
+
+</%init>
diff --git a/httemplate/elements/searchbar-prospect.html b/httemplate/elements/searchbar-prospect.html
new file mode 100644
index 0000000..68b90d4
--- /dev/null
+++ b/httemplate/elements/searchbar-prospect.html
@@ -0,0 +1,33 @@
+% if ( $curuser->access_right('List prospects') ) {
+
+ <FORM ACTION="<%$fsurl%>search/prospect_main.html" METHOD="GET" STYLE="margin:0">
+ <INPUT NAME="search_prospect" TYPE="text" VALUE="<% $prospect_label |n %>" STYLE="width:<%$width%>" onFocus="clearhint_search_prospect(this);" onClick="clearhint_search_prospect(this);" CLASS="fstext"><BR>
+ <A HREF="<%$fsurl%>search/report_prospect_main.html" CLASS="fslink" STYLE="font-size: 11px">Adv</A>
+ <INPUT TYPE="submit" VALUE="Search prospects" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_prospect (what) {
+ if ( what.value == '<% $prospect_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%once>
+
+my $prospect_label = '(name, company or phone)';
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+my $width = $menu_position eq 'left' ? '190px' : '155px';
+
+</%init>
diff --git a/httemplate/elements/searchbar-ticket.html b/httemplate/elements/searchbar-ticket.html
new file mode 100644
index 0000000..0907a89
--- /dev/null
+++ b/httemplate/elements/searchbar-ticket.html
@@ -0,0 +1,35 @@
+% if ( $conf->config("ticket_system") ) {
+
+ <FORM ACTION="<% FS::TicketSystem->baseurl %>index.html" METHOD="GET" STYLE="margin:0">
+ <INPUT NAME="q" TYPE="text" VALUE="<% $ticketing_label |n %>" STYLE="width:<% $width %>" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" CLASS="fstext"><BR>
+ <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" CLASS="fslink" STYLE="font-size:11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search tickets" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
+ </FORM>
+ <% $menu_position eq 'left' ? '<BR>' : '' %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_search_ticket (what) {
+ if ( what.value == '<% $ticketing_label |n %>' )
+ what.value = '';
+ }
+
+</SCRIPT>
+<%once>
+
+my $ticketing_label = '(ticket #, subject, email or fulltext:text)';
+
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $menu_position = $curuser->option('menu_position') || 'top';
+
+my $width = $menu_position eq 'left' ? '190px' : '223px';
+
+</%init>
diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html
new file mode 100644
index 0000000..299a66a
--- /dev/null
+++ b/httemplate/elements/select-access_group.html
@@ -0,0 +1,16 @@
+%
+% my( $groupnum, %opt ) = @_;
+%
+% %opt{'records'} = delete $opt{'access_group'}
+% if $opt{'access_group'};
+%
+%
+<% include( '/elements/select-table.html',
+ 'table' => 'access_group',
+ 'name_col' => 'groupname',
+ 'value' => $groupnum,
+ 'empty_label' => '(none)',
+ #'hashref' => { 'disabled' => '' },
+ %opt,
+ )
+%>
diff --git a/httemplate/elements/select-agent.html b/httemplate/elements/select-agent.html
new file mode 100644
index 0000000..897c982
--- /dev/null
+++ b/httemplate/elements/select-agent.html
@@ -0,0 +1,31 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'agent',
+ 'name_col' => 'agent',
+ 'value' => $agentnum || '',
+ 'agent_virt' => 1,
+ 'empty_label' => 'all',
+ 'hashref' => { 'disabled' => '' },
+ 'order_by' => ' ORDER BY agent',
+ 'disable_empty' => $disable_empty,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $agentnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'agents'}
+ if $opt{'agents'};
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $disable_empty = 0;
+if ( $opt{'agent_null_right'} ) {
+ if ( $curuser->access_right($opt{'agent_null_right'}) ) {
+ $disable_empty = 0;
+ } else {
+ $disable_empty = 1;
+ }
+}
+
+</%init>
diff --git a/httemplate/elements/select-agent_type.html b/httemplate/elements/select-agent_type.html
new file mode 100644
index 0000000..ba59cd3
--- /dev/null
+++ b/httemplate/elements/select-agent_type.html
@@ -0,0 +1,21 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'agent_type',
+ 'name_col' => 'atype',
+ 'value' => $typenum || '',
+ 'empty_label' => 'all',
+ 'hashref' => { 'disabled' => '' },
+ #'extra_sql' => ' AND '.
+ # $FS::CurrentUser::CurrentUser->agentnums_sql.
+ # ' ORDER BY agent',
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $typenum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'agent_types'}
+ if $opt{'agent_types'};
+
+</%init>
diff --git a/httemplate/elements/select-agent_types.html b/httemplate/elements/select-agent_types.html
new file mode 100644
index 0000000..400b453
--- /dev/null
+++ b/httemplate/elements/select-agent_types.html
@@ -0,0 +1,30 @@
+%# if ( $cgi->param('clone') ) { #XXX
+% if ( $opt{'disabled'} ) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="">
+
+% } elsif ( scalar(@all_agent_types) == 1) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="<% $all_agent_types[0] %>">
+
+% } else {
+
+ <% include( 'select-table.html',
+ 'element_name' => 'agent_type',
+ 'table' => 'agent_type',
+ 'name_col' => 'atype',
+ #'value' => \@agent_type,
+ 'element_etc' => 'size="10"',
+ %opt,
+ 'multiple' => '1', #cause edit.html is dum
+ )
+ %>
+
+% }
+<%init>
+
+my %opt = @_;
+
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+
+</%init>
diff --git a/httemplate/elements/select-areacode.html b/httemplate/elements/select-areacode.html
new file mode 100644
index 0000000..453205c
--- /dev/null
+++ b/httemplate/elements/select-areacode.html
@@ -0,0 +1,92 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/areacodes.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_areacodes' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'state_prefix'} %>state_changed(what, callback) {
+
+ what.form.<% $opt{'prefix'} %>areacode.disabled = 'disabled';
+ what.form.<% $opt{'prefix'} %>areacode.style.display = 'none';
+ var areacodewait = document.getElementById('<% $opt{'prefix'} %>areacodewait');
+ areacodewait.style.display = '';
+ var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror');
+ areacodeerror.style.display = 'none';
+
+ what.form.<% $opt{'prefix'} %>exchange.disabled = 'disabled';
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
+
+ var state = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_areacodes(areacodes) {
+
+ // blank the current areacode
+ for ( var i = what.form.<% $opt{'prefix'} %>areacode.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>areacode.options[i] = null;
+ // blank the current exchange too
+ for ( var i = what.form.<% $opt{'prefix'} %>exchange.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>exchange.options[i] = null;
+ opt(what.form.<% $opt{'prefix'} %>exchange, '', 'Select city / exchange');
+ // blank the current phonenum too
+ for ( var i = what.form.<% $opt{'prefix'} %>phonenum.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>phonenum.options[i] = null;
+ opt(what.form.<% $opt{'prefix'} %>phonenum, '', 'Select phone number');
+
+% if ($opt{empty}) {
+ opt(what.form.<% $opt{'prefix'} %>areacode, '', '<% $opt{empty} %>');
+% }
+
+ // add the new areacodes
+ var areacodeArray = eval('(' + areacodes + ')' );
+ for ( var s = 0; s < areacodeArray.length; s++ ) {
+ var areacodeLabel = areacodeArray[s];
+ if ( areacodeLabel == "" )
+ areacodeLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>areacode, areacodeArray[s], areacodeLabel);
+ }
+
+ areacodewait.style.display = 'none';
+ if ( areacodeArray.length >= 1 ) {
+ what.form.<% $opt{'prefix'} %>areacode.disabled = '';
+ what.form.<% $opt{'prefix'} %>areacode.style.display = '';
+ } else {
+ var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror');
+ areacodeerror.style.display = '';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new areacodes
+ <% $opt{'prefix'} %>get_areacodes( state, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_areacodes );
+
+ }
+
+</SCRIPT>
+
+<DIV ID="areacodewait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding area codes</B></DIV>
+
+<DIV ID="areacodeerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different state</B></DIV>
+
+<SELECT NAME="<% $opt{'prefix'} %>areacode" onChange="<% $opt{'prefix'} %>areacode_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+ <OPTION VALUE="">Select area code</OPTION>
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{disabled} = 'disabled' unless exists $opt{disabled};
+$opt{state_prefix} = $opt{prefix} unless exists $opt{state_prefix};
+
+</%init>
diff --git a/httemplate/elements/select-cdrbatch.html b/httemplate/elements/select-cdrbatch.html
new file mode 100644
index 0000000..034db3a
--- /dev/null
+++ b/httemplate/elements/select-cdrbatch.html
@@ -0,0 +1,14 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'cdr_batch',
+ 'name_col' => 'cdrbatch',
+ 'curr_value' => $cdrbatchnum,
+ 'empty_label' => '(none)',
+ 'pre_options' => [ '__ALL__' => 'All' ],
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $cdrbatchnum = $opt{'curr_value'}; # || $opt{'value'} necessary?
+
+</%init>
diff --git a/httemplate/elements/select-cgp_rule_action.html b/httemplate/elements/select-cgp_rule_action.html
new file mode 100644
index 0000000..7cefdc4
--- /dev/null
+++ b/httemplate/elements/select-cgp_rule_action.html
@@ -0,0 +1,116 @@
+% unless ( $opt{'js_only'} ) {
+
+ <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<%$curr_value%>">
+
+ <% include( 'select.html',
+ 'field' => $name.'_action',
+ 'id' => $id.'_action',
+ 'options' => \@actions,
+ 'curr_value' => $action,
+ 'labels' => { '' => 'Select Action' },
+ 'onchange' => $name.'_changed',
+ 'style' => 'vertical-align:top',
+ )
+ %>
+
+ <TEXTAREA NAME = "<% $name %>_params"
+ ID = "<% $id %>_params"
+ <% $disabled %>
+ <% $style %>
+%# <% $rows %>
+%# <% $cols %>
+%# <% $onchange %>
+ ><% scalar($cgi->param($name.'_params')) || $cgp_rule_action->params |h %></TEXTAREA>
+
+% }
+% unless ( $opt{'html_only'} || $opt{'js_only'} ) {
+ <SCRIPT TYPE="text/javascript">
+% }
+% unless ( $opt{'html_only'} ) {
+
+ function <% $name %>_changed(what) {
+
+ <% $opt{'onchange'} %>
+
+ var <% $name %>_value = what.options[what.selectedIndex].value;
+
+ var params_Element = what.form.<% $name %>_params;
+
+ // if bool, hide/disable _op and _params entirely
+ if ( <%$name%>_value == '' || <%$name%>_value == 'Stop Processing' || <%$name%>_value == 'Discard' ) {
+ params_Element.disabled = true;
+ params_Element.style.visibility = "hidden";
+ } else {
+ params_Element.disabled = false;
+ params_Element.style.visibility = "visible";
+
+ }
+
+ }
+
+% }
+% unless ( $opt{'html_only'} || $opt{'js_only'} ) {
+ </SCRIPT>
+% }
+<%once>
+
+my @actions = (
+ '',
+
+ #generic http://www.communigate.com/CommunigatePro/Rules.html#Actions
+ 'Reject',
+ 'SendURL',
+ 'Send IM',
+ 'FingerNotify',
+ 'Write To Log',
+ "Remember 'From' in",
+
+ #email http://www.communigate.com/CommunigatePro/QueueRules.html#Actions
+ 'Stop Processing',
+ 'Discard',
+ 'Reject With',
+ 'Mark',
+ 'Add Header',
+ 'Tag Subject',
+ 'Store in',
+ 'Redirect to',
+ 'Forward to',
+ 'Mirror to',
+ 'Reply with',
+ 'Reply to All with',
+ 'React with',
+ 'store Encrypted in',
+ 'Copy attachments into',
+ 'Execute',
+ 'ExternalFilter',
+ 'Accept Request',
+);
+
+my %noparam = ( map { $_=>1 } '', 'Stop Processing', 'Discard' );
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+my $name = $opt{'element_name'} || $opt{'field'} || 'ruleactionnum';
+#my $id = $opt{'id'} || 'contactnum';
+my $id = $opt{'id'} || $opt{'field'} || 'ruleactionnum';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+my $cgp_rule_action;
+if ( $curr_value ) {
+ $cgp_rule_action = qsearchs('cgp_rule_action',
+ { 'ruleactionnum' => $curr_value } );
+} else {
+ $cgp_rule_action = new FS::cgp_rule_action {};
+}
+
+my $action = scalar($cgi->param($name.'_action'))
+ || $cgp_rule_action->action;
+
+my $disabled = $noparam{$action} ? 'DISABLED' : '';
+my $style = $disabled ? 'STYLE="visibility:hidden"' : '';
+
+</%init>
diff --git a/httemplate/elements/select-cgp_rule_condition.html b/httemplate/elements/select-cgp_rule_condition.html
new file mode 100644
index 0000000..622cbe8
--- /dev/null
+++ b/httemplate/elements/select-cgp_rule_condition.html
@@ -0,0 +1,200 @@
+% unless ( $opt{'js_only'} ) {
+
+ <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
+
+ <% include( 'select.html',
+ 'field' => $name.'_condition',
+ 'id' => $id.'_condition',
+ 'options' => \@conditions,
+ 'curr_value' => $condition,
+ 'labels' => { '' => 'Select Condition' },
+ 'onchange' => $name.'_changed',
+ )
+ %>
+
+ <% include( 'select.html',
+ 'field' => $name.'_op',
+ 'id' => $id.'_op',
+ 'options' => \@op,
+ 'curr_value' => scalar($cgi->param($name.'_op'))
+ || $cgp_rule_condition->op,
+ 'disabled' => $disabled,
+ 'style' => $style,
+ )
+ %>
+
+ <% include( 'input-text.html',
+ 'field' => $name.'_params',
+ 'id' => $id.'_params',
+ 'curr_value' => scalar($cgi->param($name.'_params'))
+ || $cgp_rule_condition->params,
+ 'disabled' => $disabled,
+ 'style' => $style,
+ 'nodarken_disabled' => 1,
+ )
+ %>
+
+% # could add more UI sugar for date/time ranges, string #lists, etc.
+
+% }
+% unless ( $opt{'html_only'} || $opt{'js_only'} ) {
+ <SCRIPT TYPE="text/javascript">
+% }
+% unless ( $opt{'html_only'} ) {
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $name %>_changed(what) {
+
+ <% $onchange %>
+
+ var <% $name %>_value = what.options[what.selectedIndex].value;
+
+ var op_Element = what.form.<% $name %>_op;
+ var params_Element = what.form.<% $name %>_params;
+
+ //cond2op in javascript... not as elegant cause my js << my perl
+
+ // if bool, hide/disable _op and _params entirely
+ if ( <%$name%>_value == '' || <%$name%>_value == 'Human Generated' ) {
+ op_Element.disabled = true;
+ op_Element.style.visibility = "hidden";
+ params_Element.disabled = true;
+ params_Element.style.visibility = "hidden";
+ return true;
+ }
+
+ var OpArray = [ 'is', 'is not' ];
+
+ // if lt_ge, add em
+ if ( <%$name%>_value == 'Message Size' || <%$name%>_value == 'Time of Day' || <%$name%>_value == 'Current Date' ) {
+ OpArray.push('less than');
+ OpArray.push('greater than');
+ }
+
+ // unless no_in, add em
+ if ( <%$name%>_value != 'Message Size' && <%$name%>_value != 'Current Date' && <%$name%>_value != 'Existing Mailbox' ) {
+ OpArray.push('in');
+ OpArray.push('not in');
+ }
+
+ // blank the current op list
+ for ( var i = op_Element.length; i >= 0; i-- )
+ op_Element.options[i] = null;
+
+ // update the _op select with this new array
+ for ( var s = 0; s < OpArray.length; s++ )
+ opt(what.form.<% $name %>_op, OpArray[s], OpArray[s]);
+
+ // show _op and _params (in case we were a bool before)
+ op_Element.disabled = false;
+ op_Element.style.visibility = "visible";
+ params_Element.disabled = false;
+ params_Element.style.visibility = "visible";
+
+ }
+% }
+% unless ( $opt{'html_only'} || $opt{'js_only'} ) {
+ </SCRIPT>
+% }
+<%once>
+
+my @conditions = (
+ '',
+
+ #generic http://www.communigate.com/CommunigatePro/Rules.html#Conditions
+ 'Submit Address',
+ 'Time of Day',
+ 'Current Date',
+ 'Current Day',
+ 'Preference',
+ 'FreeBusy',
+ 'Existing Mailbox',
+
+ #email http://www.communigate.com/CommunigatePro/QueueRules.html#Conditions
+ 'From',
+ 'Sender',
+ 'To',
+ 'Cc',
+ 'Reply-To',
+ 'Any To or Cc',
+ 'Each To or Cc',
+ 'Return-Path',
+ "'From' Name",
+ 'Subject',
+ 'Message-ID',
+ 'Message Size',
+ 'Human Generated',
+ 'Header Field',
+ 'Any Recipient',
+ 'Each Recipient',
+ 'Source',
+ 'Security',
+ 'Any Route',
+ 'Each Route'
+);
+
+my %bool = ( map { $_=>1 } ( #hide the op and valud dropdowns entirely
+ '',
+ 'Human Generated',
+));
+
+my %no_in = ( map { $_=>1 } ( #hide in/not in
+ 'Message Size',
+ 'Current Date',
+ 'Existing Mailbox',
+));
+
+my %lt_gt = ( map { $_=>1 } ( #add less than/greater than
+ 'Message Size',
+ 'Time of Day',
+ 'Current Date',
+));
+
+my $cond2op = sub {
+ my $cond = shift;
+ return () if $bool{$cond};
+ my @op = ( 'is', 'is not' );
+ push @op, 'less than', 'greater than' if $lt_gt{$cond};
+ push @op, 'in', 'not in' unless $no_in{$cond};
+ @op;
+};
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+my $name = $opt{'element_name'} || $opt{'field'} || 'ruleconditionnum';
+#my $id = $opt{'id'} || 'contactnum';
+my $id = $opt{'id'} || $opt{'field'} || 'ruleconditionnum';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+my $onchange = '';
+if ( $opt{'onchange'} ) {
+ $onchange = $opt{'onchange'};
+ $onchange .= '(what)' unless $onchange =~ /\(\w*\);?$/;
+}
+
+my $cgp_rule_condition;
+if ( $curr_value ) {
+ $cgp_rule_condition = qsearchs('cgp_rule_condition',
+ { 'ruleconditionnum' => $curr_value } );
+} else {
+ $cgp_rule_condition = new FS::cgp_rule_condition {};
+}
+
+my $condition = scalar($cgi->param($name.'_condition'))
+ || $cgp_rule_condition->condition;
+
+my @op = &$cond2op($condition);
+
+my $disabled = scalar(@op) ? '' : 1;
+my $style = $disabled ? 'visibility:hidden' : '';
+
+</%init>
diff --git a/httemplate/elements/select-country.html b/httemplate/elements/select-country.html
new file mode 100644
index 0000000..e5656dc
--- /dev/null
+++ b/httemplate/elements/select-country.html
@@ -0,0 +1,130 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-country.html',
+ #recommended
+ country => $current_country,
+
+ #optional
+ prefix => $optional_unique_prefix,
+ onchange => $javascript,
+ disabled => 0, #bool
+ disable_empty => 1, #defaults to 1, disable the empty option
+ empty_label => 'all', #label for empty option
+ disable_stateupdate => 0, #bool - disabled update of the select-state.html
+ style => [ 'attribute:value', 'another:value' ],
+ );
+
+</%doc>
+% unless ( $opt{'disable_stateupdate'} ) {
+
+ <% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/states.cgi',
+ 'subs' => [ $pre. 'get_states' ],
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $pre %>country_changed(what, callback) {
+
+ country = what.options[what.selectedIndex].value;
+
+ function <% $pre %>update_states(states) {
+
+ // blank the current state list
+ for ( var i = what.form.<% $pre %>state.length; i >= 0; i-- )
+ what.form.<% $pre %>state.options[i] = null;
+
+ // add the new states
+ var statesArray = eval('(' + states + ')' );
+ for ( var s = 0; s < statesArray.length; s=s+2 ) {
+ var stateLabel = statesArray[s+1];
+ if ( stateLabel == "" )
+ stateLabel = '(n/a)';
+ opt(what.form.<% $pre %>state, statesArray[s], stateLabel);
+ }
+
+ //run the callback
+ if ( callback != null ) {
+ callback();
+ } else {
+ <% $pre %>state_changed(what.form.<% $pre %>state);
+ }
+ }
+
+ // go get the new states
+ <% $pre %>get_states( country, <% $pre %>update_states );
+
+ }
+
+ </SCRIPT>
+
+% }
+
+<SELECT NAME = "<% $pre %>country"
+ ID = "<% $pre %>country"
+ onChange = "<% $onchange %>"
+ <% $opt{'disabled'} %>
+ <% $style %>
+>
+
+% unless ( $opt{'disable_empty'} ) {
+ <OPTION VALUE=""><% $opt{'empty_label'} || '(all)' %>
+% }
+
+% foreach my $country ( @all_countries ) {
+
+ <OPTION VALUE="<% $country |h %>"
+ <% $country eq $opt{'country'} ? ' SELECTED' : '' %>
+ ><% code2country($country). " ($country)" %>
+
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+foreach my $opt (qw( country prefix onchange disabled disable_stateupdate )) {
+ $opt{$opt} = '' unless exists($opt{$opt}) && defined($opt{$opt});
+}
+
+$opt{'disable_empty'} = 1 unless exists($opt{'disable_empty'});
+
+my $pre = $opt{'prefix'};
+
+my $onchange =
+ ( $opt{'disable_stateupdate'} ? '' : $pre.'country_changed(this); ' ).
+ $opt{'onchange'};
+
+$opt{'style'} ||= [];
+my $style =
+ scalar(@{$opt{style}})
+ ? 'STYLE="'. join(';', @{$opt{style}}). '"'
+ : '';
+
+my $conf = new FS::Conf;
+my $default = $conf->config('countrydefault') || 'US';
+
+my @all_countries = (
+ sort { ($b eq $default) <=> ($a eq $default)
+ or code2country($a) cmp code2country($b)
+ }
+ map { $_->country }
+ qsearch({
+ 'select' => 'country',
+ 'table' => 'cust_main_county',
+ 'hashref' => {},
+ 'extra_sql' => 'GROUP BY country',
+ })
+ );
+
+</%init>
diff --git a/httemplate/elements/select-county.html b/httemplate/elements/select-county.html
new file mode 100644
index 0000000..6d498be
--- /dev/null
+++ b/httemplate/elements/select-county.html
@@ -0,0 +1,166 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-county.html',
+ #recommended
+ country => $current_country,
+ state => $current_state,
+ county => $current_county,
+
+ #optional
+ prefix => $optional_unique_prefix,
+ onchange => $javascript,
+ disabled => 0, #bool
+ disable_empty => 1, #defaults to 1, disable the empty option
+ empty_label => 'all', #label for empty option
+ style => [ 'attribute:value', 'another:value' ],
+ );
+
+</%doc>
+% if ( $countyflag ) {
+
+ <% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/counties.cgi',
+ 'subs' => [ $pre. 'get_counties' ],
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $pre %>state_changed(what, callback) {
+
+ state = what.options[what.selectedIndex].value;
+ country = what.form.<% $pre %>country.options[what.form.<% $pre %>country.selectedIndex].value;
+
+ function <% $pre %>update_counties(counties) {
+
+ // blank the current county list
+ for ( var i = what.form.<% $pre %>county.length; i >= 0; i-- )
+ what.form.<% $pre %>county.options[i] = null;
+
+ // add the new counties
+ var countiesArray = eval('(' + counties + ')' );
+ for ( var s = 0; s < countiesArray.length; s++ ) {
+ var countyLabel = countiesArray[s];
+ if ( countyLabel == "" )
+ countyLabel = '(n/a)';
+ opt(what.form.<% $pre %>county, countiesArray[s], countyLabel);
+ }
+
+ var countyFormLabel = document.getElementById('<% $pre %>countylabel');
+
+ if ( countiesArray.length > 1 ) {
+ what.form.<% $pre %>county.style.display = '';
+ //countyFormLabel.style.visibility = 'visible';
+ countyFormLabel.style.display = '';
+ } else {
+ what.form.<% $pre %>county.style.display = 'none';
+ //countyFormLabel.style.visibility = 'hidden';
+ countyFormLabel.style.display = 'none';
+ }
+
+ //run the callback
+ if ( callback != null ) {
+ callback();
+ } else {
+ <% $pre %>county_changed(what.form.<% $pre %>county);
+ }
+ }
+
+ // go get the new counties
+ <% $pre %>get_counties( state, country, <% $pre %>update_counties );
+
+ }
+
+ </SCRIPT>
+
+ <SELECT NAME = "<% $pre %>county"
+ ID = "<% $pre %>county"
+ onChange= "<% $onchange %>"
+ <% $opt{'disabled'} %>
+ <% $style %>
+ >
+
+% unless ( $opt{'disable_empty'} ) {
+ <OPTION VALUE="" <% $opt{county} eq '' ? 'SELECTED' : '' %>><% $opt{empty_label} %>
+% }
+
+% foreach my $county ( @counties ) {
+
+ <OPTION VALUE="<% $county |h %>"
+ <% $county eq $opt{'county'} ? 'SELECTED' : '' %>
+ ><% $county eq $opt{'empty_data_value'} ? $opt{'empty_data_label'} : $county %>
+
+% }
+
+ </SELECT>
+
+% } else {
+
+ <SCRIPT TYPE="text/javascript">
+ function <% $pre %>state_changed(what) {
+ }
+ </SCRIPT>
+
+ <SELECT NAME = "<% $pre %>county"
+ ID = "<% $pre %>county"
+ STYLE = "display:none"
+ >
+ <OPTION SELECTED VALUE="<% $opt{'county'} |h %>">
+ </SELECT>
+
+% }
+
+<%init>
+
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled
+ empty_value )) {
+ $opt{$opt} = '' unless exists($opt{$opt}) && defined($opt{$opt});
+}
+
+$opt{'disable_empty'} = 1 unless exists($opt{'disable_empty'});
+
+my $pre = $opt{'prefix'};
+
+
+# disable_cityupdate?
+my $onchange =
+ ( $opt{'disable_cityupdate'} ? '' : $pre.'county_changed(this); ' ).
+ $opt{'onchange'};
+
+my $county_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : [];
+
+my @counties = ();
+if ( $countyflag ) {
+
+ @counties = map { length($_) ? $_ : $opt{'empty_data_value'} }
+ counties( $opt{'state'}, $opt{'country'} );
+
+ push @$county_style, 'display:none'
+ unless scalar(@counties) > 1;
+
+}
+
+my $style =
+ scalar(@$county_style)
+ ? 'STYLE="'. join(';', @$county_style). '"'
+ : '';
+
+</%init>
+<%once>
+
+my $sql = "SELECT COUNT(*) FROM cust_main_county".
+ " WHERE county IS NOT NULL AND county != ''";
+my $sth = dbh->prepare($sql) or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $countyflag = $sth->fetchrow_arrayref->[0];
+
+</%once>
diff --git a/httemplate/elements/select-cust-fields.html b/httemplate/elements/select-cust-fields.html
new file mode 100644
index 0000000..98feaf8
--- /dev/null
+++ b/httemplate/elements/select-cust-fields.html
@@ -0,0 +1,24 @@
+%
+% my( $cust_fields, %opt ) = @_;
+%
+% use FS::ConfDefaults;
+% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ];
+%
+% tie my %hash, 'Tie::IxHash', @{ $opt{'avail_fields'} };
+%
+%
+
+
+<SELECT NAME="cust_fields">
+
+ <OPTION VALUE="">(configured default)
+%
+% foreach my $value ( keys %hash ) {
+
+
+ <OPTION VALUE="<% $value %>"><% $hash{$value} %>
+% }
+
+
+</SELECT>
+
diff --git a/httemplate/elements/select-cust-part_pkg.html b/httemplate/elements/select-cust-part_pkg.html
new file mode 100644
index 0000000..7f91e81
--- /dev/null
+++ b/httemplate/elements/select-cust-part_pkg.html
@@ -0,0 +1,41 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-cust-part_pkg.html',
+
+ #required
+ 'cust_main' => $cust_main, #or 'custnum' ?
+
+ #strongly recommended (you want your forms to be "sticky" on errors, right?)
+ 'curr_value' => 'current_value',
+
+ #opt
+ 'part_pkg' => \@records,
+
+ #select-table.html options
+ )
+
+</%doc>
+
+<% include( '/elements/select-part_pkg.html',
+ 'empty_label' => 'Select package', #? need here in case removed
+ #from select-part_pkg ??
+ %opt,
+ )
+%>
+<%init>
+
+my( %opt ) = @_;
+
+my $cust_main = $opt{'cust_main'}
+ or die "cust_main not specified";
+
+$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent );
+# ' AND ( agentnum IS NOT NULL '.
+# ' OR 0 < ( SELECT COUNT(*) FROM type_pkgs '.
+# ' WHERE typenum = '. $cust_main->agent->typenum.
+# ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'.
+# ' )';
+
+</%init>
diff --git a/httemplate/elements/select-cust-pkg_class.html b/httemplate/elements/select-cust-pkg_class.html
new file mode 100644
index 0000000..5c19efa
--- /dev/null
+++ b/httemplate/elements/select-cust-pkg_class.html
@@ -0,0 +1,12 @@
+<% include( '/elements/select-pkg_class.html',
+ 'pre_options' => [ '-1' => 'all' ], #XXX a config ?
+ #'pre_options' => [ '-2' => 'Select package class' ],
+ 'disable_empty' => 1,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/select-cust_class.html b/httemplate/elements/select-cust_class.html
new file mode 100644
index 0000000..94b935a
--- /dev/null
+++ b/httemplate/elements/select-cust_class.html
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'cust_class',
+ 'name_col' => 'classname',
+ 'value' => $classnum,
+ 'empty_label' => '(none)',
+ 'hashref' => { 'disabled' => '' },
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'cust_class'}
+ if $opt{'cust_class'};
+
+</%init>
diff --git a/httemplate/elements/select-cust_main-status.html b/httemplate/elements/select-cust_main-status.html
new file mode 100644
index 0000000..bdbaac7
--- /dev/null
+++ b/httemplate/elements/select-cust_main-status.html
@@ -0,0 +1,33 @@
+<SELECT NAME="<% $opt{'field'} || 'status' %>"
+ <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+ <% $onchange %>
+>
+
+ <OPTION VALUE="">all
+
+% foreach my $option ( @{ $opt{'statuses'} } ) {
+
+ <OPTION VALUE="<% $option %>"
+ <% ref($value) && $value->{$option} || $option eq $value
+ ? 'SELECTED' : ''
+ %>
+ ><% $option %>
+
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{'statuses'} ||= [ FS::cust_main->statuses() ]; # { disabled=>'' } )
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+$value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
+
+</%init>
diff --git a/httemplate/elements/select-cust_pkg-balances.html b/httemplate/elements/select-cust_pkg-balances.html
new file mode 100644
index 0000000..cd2e1a8
--- /dev/null
+++ b/httemplate/elements/select-cust_pkg-balances.html
@@ -0,0 +1,32 @@
+<SELECT NAME="pkgnum">
+ <OPTION VALUE="">(any)
+% foreach my $cust_pkg (@cust_pkg) {
+% my $sel = ( $cgi->param('pkgnum') == $cust_pkg->pkgnum ) ? 'SELECTED' : '';
+ <OPTION <% $sel %> VALUE="<% $cust_pkg->pkgnum %>"><% $cust_pkg->pkg_label_long |h %>
+% }
+</SELECT>
+<%init>
+
+my %opt = @_;
+
+my $cgi = $opt{'cgi'};
+
+my @cust_pkg;
+if ( $opt{'cust_pkg'} ) {
+
+ @cust_pkg = @{ $opt{'cust_pkg'} };
+
+} else {
+
+ my $custnum = $opt{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum\n";
+
+ @cust_pkg =
+ grep { ! $_->get('cancel') || $cust_main->balance_pkgnum($_->pkgnum) }
+ $cust_main->all_pkgs;
+
+}
+
+</%init>
diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html
new file mode 100644
index 0000000..ec37eaf
--- /dev/null
+++ b/httemplate/elements/select-cust_pkg-status.html
@@ -0,0 +1,33 @@
+<SELECT NAME="<% $opt{'field'} || 'status' %>"
+ <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+ <% $onchange %>
+>
+
+ <OPTION VALUE="">all
+
+% foreach my $option ( @{ $opt{'statuses'} } ) {
+
+ <OPTION VALUE="<% $option %>"
+ <% ref($value) && $value->{$option} || $option eq $value
+ ? 'SELECTED' : ''
+ %>
+ ><% $option %>
+
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } )
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+$value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
+
+</%init>
diff --git a/httemplate/elements/select-did.html b/httemplate/elements/select-did.html
new file mode 100644
index 0000000..b62d6a0
--- /dev/null
+++ b/httemplate/elements/select-did.html
@@ -0,0 +1,91 @@
+<%doc>
+
+Example:
+
+ include('/elements/select-did.html',
+ #can't actuall change from phonenum yet# 'field' => 'phonenum',
+
+ 'svcpart' => 5,
+ #OR
+ 'object' => $svc_phone,
+ );
+
+</%doc>
+% if ( $use_selector ) {
+
+ <TABLE>
+
+ <TR>
+ <TD>
+ <% include('/elements/select-state.html',
+ 'prefix' => 'phonenum_', #$field.'_',
+ 'country' => $country,
+ 'disable_empty' => 0,
+ 'empty_label' => 'Select state',
+ )
+ %>
+ </TD>
+ <TD>
+ <% include('/elements/select-areacode.html',
+ 'state_prefix' => 'phonenum_', #$field.'_',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select area code',
+ )
+ %>
+ </TD>
+ <TD>
+ <% include('/elements/select-exchange.html',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select exchange',
+ )
+ %>
+ </TD>
+ <TD>
+ <% include('/elements/select-phonenum.html',
+ 'svcpart' => $svcpart,
+ 'empty' => 'Select phone number',
+ )
+ %>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD><FONT SIZE="-1">State</FONT></TD>
+ <TD><FONT SIZE="-1">Area code</FONT></TD>
+ <TD><FONT SIZE="-1">City / Exchange</FONT></TD>
+ <TD><FONT SIZE="-1">Phone number</FONT></TD>
+ </TR>
+
+ </TABLE>
+
+% } else {
+
+ <% include( '/elements/input-text.html', %opt, 'type'=>'text' ) %>
+
+% }
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+my $country = $conf->config('countrydefault') || 'US';
+
+#false laziness w/tr-select-did.html
+#XXX make sure this comes through on errors too
+my $svcpart = $opt{'svcpart'}
+ || $opt{'object'}->svcpart
+ || $opt{'object'}->cust_svc->svcpart;
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+}
+
+my $use_selector = scalar(@exports) ? 1 : 0;
+
+#my $field = $opt{'field'} || 'phonenum';
+
+</%init>
diff --git a/httemplate/elements/select-discount.html b/httemplate/elements/select-discount.html
new file mode 100644
index 0000000..b7f1fa5
--- /dev/null
+++ b/httemplate/elements/select-discount.html
@@ -0,0 +1,28 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'discount',
+ 'name_col' => 'description',
+ 'order_by' => 'ORDER BY discountnum', #XXX weight
+ 'value' => $discountnum,
+ 'empty_label' => '(none)',
+ 'hashref' => { 'disabled' => '' },
+ 'post_options' => $post_options,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $discountnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'discount'}
+ if $opt{'discount'};
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#make an opt if we need to turn this off
+my $post_options = $curuser->access_right('Custom discount customer package')
+ ? [ -1 => 'Custom discount' ]
+ : [];
+
+</%init>
+
diff --git a/httemplate/elements/select-domain.html b/httemplate/elements/select-domain.html
new file mode 100644
index 0000000..3372e06
--- /dev/null
+++ b/httemplate/elements/select-domain.html
@@ -0,0 +1,13 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'svc_domain',
+ 'name_col' => 'domain',
+ 'empty_label' => 'all',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ #' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'View/link unlinked services',
+ @_,
+ )
+%>
diff --git a/httemplate/elements/select-exchange.html b/httemplate/elements/select-exchange.html
new file mode 100644
index 0000000..012e7c6
--- /dev/null
+++ b/httemplate/elements/select-exchange.html
@@ -0,0 +1,86 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/exchanges.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_exchanges' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'prefix'} %>areacode_changed(what, callback) {
+
+ what.form.<% $opt{'prefix'} %>exchange.disabled = 'disabled';
+ what.form.<% $opt{'prefix'} %>exchange.style.display = 'none';
+ var exchangewait = document.getElementById('<% $opt{'prefix'} %>exchangewait');
+ exchangewait.style.display = '';
+ var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror');
+ exchangeerror.style.display = 'none';
+
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
+
+ areacode = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_exchanges(exchanges) {
+
+ // blank the current exchange
+ for ( var i = what.form.<% $opt{'prefix'} %>exchange.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>exchange.options[i] = null;
+ // blank the current phonenum too
+ for ( var i = what.form.<% $opt{'prefix'} %>phonenum.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>phonenum.options[i] = null;
+ opt(what.form.<% $opt{'prefix'} %>phonenum, '', 'Select phone number');
+
+% if ($opt{empty}) {
+ opt(what.form.<% $opt{'prefix'} %>exchange, '', '<% $opt{empty} %>');
+% }
+
+ // add the new exchanges
+ var exchangeArray = eval('(' + exchanges + ')' );
+ for ( var s = 0; s < exchangeArray.length; s++ ) {
+ var exchangeLabel = exchangeArray[s];
+ if ( exchangeLabel == "" )
+ exchangeLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>exchange, exchangeArray[s], exchangeLabel);
+ }
+
+ exchangewait.style.display = 'none';
+ if ( exchangeArray.length >= 1 ) {
+ what.form.<% $opt{'prefix'} %>exchange.disabled = '';
+ what.form.<% $opt{'prefix'} %>exchange.style.display = '';
+ } else {
+ var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror');
+ exchangeerror.style.display = '';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new exchanges
+ <% $opt{'prefix'} %>get_exchanges( areacode, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_exchanges );
+
+ }
+
+</SCRIPT>
+
+<DIV ID="exchangewait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding cities / exchanges</B></DIV>
+
+<DIV ID="exchangeerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different area code</B></DIV>
+
+<SELECT NAME="<% $opt{'prefix'} %>exchange" onChange="<% $opt{'prefix'} %>exchange_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+ <OPTION VALUE="">Select city / exchange</OPTION>
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{disabled} = 'disabled' unless exists $opt{disabled};
+
+</%init>
diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html
new file mode 100644
index 0000000..34476bc
--- /dev/null
+++ b/httemplate/elements/select-month_year.html
@@ -0,0 +1,62 @@
+%
+%
+% my %opt = @_;
+%
+% my $prefix = $opt{'prefix'} || '';
+% my $disabled = $opt{'disabled'} || '';
+% my $empty = $opt{'empty_option'} || '';
+% my $start_year = $opt{'start_year'};
+% my $end_year = $opt{'end_year'} || '2037';
+%
+% my @mon;
+% if ( $opt{'show_month_abbr'} ) {
+% @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+% } else {
+% @mon = ( 1 .. 12 );
+% }
+%
+% my $date = $opt{'selected_date'} || '';
+% $date = '' if $date eq '-';
+% #$date ||= '01-2000' unless $empty;
+%
+% my $mon = $opt{'selected_mon'} || 0;
+% my $year = $opt{'selected_year'} || 0;
+% if ( $date ) {
+% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+% } else {
+% die "unrecognized expiration date format: $date";
+% }
+% }
+%
+% unless ( $start_year ) {
+% my @t = localtime;
+% $start_year = $t[5] + 1900;
+% }
+% $start_year = $year if $start_year > $year && $year > 0;
+%
+%
+
+
+<SELECT NAME="<% $prefix %>_month" SIZE="1" <% $disabled%>>
+
+<% $empty ? '<OPTION VALUE="">' : '' %>
+% foreach ( 1 .. 12 ) {
+
+ <OPTION<% $_ == $mon ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $mon[$_-1] %>
+% }
+
+
+</SELECT>/<SELECT NAME="<% $prefix %>_year" SIZE="1" <% $disabled%>>
+
+<% $empty ? '<OPTION VALUE="">' : '' %>
+% for ( $start_year .. $end_year ) {
+
+ <OPTION<% $_ == $year ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $_ %>
+% }
+
+
+</SELECT>
+
diff --git a/httemplate/elements/select-otaker.html b/httemplate/elements/select-otaker.html
new file mode 100644
index 0000000..2a689f3
--- /dev/null
+++ b/httemplate/elements/select-otaker.html
@@ -0,0 +1,27 @@
+<SELECT NAME="otaker">
+
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
+ <OPTION VALUE="">all</OPTION>
+% }
+
+% foreach my $otaker ( @{ $opt{'otakers'} } ) {
+ <OPTION VALUE="<% $otaker %>"><% $otaker %></OPTION>
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+unless ( $opt{'otakers'} ) {
+
+ my $sth = dbh->prepare("SELECT username FROM access_user".
+ " WHERE disabled = '' or disabled IS NULL")
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ $opt{'otakers'} = [ map { $_->[0] } @{$sth->fetchall_arrayref} ];
+
+}
+
+</%init>
diff --git a/httemplate/elements/select-part_event.html b/httemplate/elements/select-part_event.html
new file mode 100644
index 0000000..f510672
--- /dev/null
+++ b/httemplate/elements/select-part_event.html
@@ -0,0 +1,6 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'part_event',
+ 'name_col' => 'event',
+ @_,
+ )
+%>
diff --git a/httemplate/elements/select-part_pkg.html b/httemplate/elements/select-part_pkg.html
new file mode 100644
index 0000000..885bb80
--- /dev/null
+++ b/httemplate/elements/select-part_pkg.html
@@ -0,0 +1,51 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-part_pkg.html',
+
+ #strongly recommended (you want your forms to be "sticky" on errors, right?)
+ 'curr_value' => 'current_value',
+
+ #opt
+ 'part_pkg' => \@records,
+
+ #select-table.html options
+ )
+
+</%doc>
+
+<% include( '/elements/select-table.html',
+ 'table' => 'part_pkg',
+ 'agent_virt' => 1,
+ 'agent_null' => 1,
+ 'name_col' => 'pkg',
+ 'empty_label' => 'Select package', #should this be the default?
+ 'label_callback' => sub { shift->pkg_comment },
+ 'hashref' => \%hash,
+ 'extra_option_attributes' => [ 'can_discount' ],
+ %opt,
+ )
+%>
+<%init>
+
+my( %opt ) = @_;
+
+$opt{'records'} = delete $opt{'part_pkg'}
+ if $opt{'part_pkg'};
+
+my %hash = ();
+$hash{'disabled'} = '' unless $opt{'showdisabled'};
+
+if ( exists($opt{'classnum'}) && defined($opt{'classnum'}) ) {
+ if ( $opt{'classnum'} > 0 ) {
+ $hash{'classnum'} = $opt{'classnum'};
+ } elsif ( $opt{'classnum'} eq '' || $opt{'classnum'} == 0 ) {
+ $hash{'classnum'} = '';
+ } #else -1 or not specified, all classes, so don't set classnum
+}
+
+$opt{'extra_sql'} .= ( keys(%hash) ? ' AND ' : ' WHERE ' ).
+ FS::part_pkg->curuser_pkgs_sql;
+
+</%init>
diff --git a/httemplate/elements/select-part_referral.html b/httemplate/elements/select-part_referral.html
new file mode 100644
index 0000000..c4b8829
--- /dev/null
+++ b/httemplate/elements/select-part_referral.html
@@ -0,0 +1,20 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'part_referral',
+ 'name_col' => 'referral',
+ 'value' => $refnum,
+ 'empty_label' => 'Select advertising source',
+ 'hashref' => { 'disabled' => '' },
+ 'extra_sql' => ' AND '.
+ FS::part_referral->acl_agentnum_sql(1),
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $refnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'part_referrals'}
+ if $opt{'part_referrals'};
+
+</%init>
diff --git a/httemplate/elements/select-part_svc.html b/httemplate/elements/select-part_svc.html
new file mode 100644
index 0000000..72ab7f6
--- /dev/null
+++ b/httemplate/elements/select-part_svc.html
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'part_svc',
+ 'name_col' => 'svc',
+ 'label_showkey' => 1,
+ #N/A 'empty_label' => '(none)',
+ %opt,
+ )
+%>
+<%init>
+
+my( %opt ) = @_;
+
+$opt{'records'} = delete $opt{'part_svc'}
+ if $opt{'part_svc'};
+
+$opt{'records'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } )
+
+</%init>
diff --git a/httemplate/elements/select-payby.html b/httemplate/elements/select-payby.html
new file mode 100644
index 0000000..e0fb4f0
--- /dev/null
+++ b/httemplate/elements/select-payby.html
@@ -0,0 +1,42 @@
+<SELECT NAME="<% $opt{'field'} || 'payby' %>"
+ <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+ <% $onchange %>
+>
+
+% unless ( $opt{'multiple'} ) {
+ <OPTION VALUE="" <% '' eq $value ? 'SELECTED' : '' %> >all
+% }
+
+% foreach my $option ( keys %{ $opt{'paybys'} } ) {
+% my $sel = $opt{'all_selected'}
+% || ( ref($value) && $value->{$option} )
+% || $option eq $value;
+
+ <OPTION VALUE="<% $option %>"
+ <% $sel ? 'SELECTED' : '' %>
+ ><% $opt{'paybys'}->{$option} %>
+
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+my $method = 'payby';
+$method = 'cust_payby' if $opt{'payby_type'} eq 'cust';
+#$method = 'event_payby' if $opt{'payby_type'} eq 'event';
+#$method = 'pay_payby' if $opt{'payby_type'} eq 'pay';
+
+unless ( $opt{'paybys'} ) {
+ tie %{ $opt{'paybys'} }, 'Tie::IxHash', FS::payby->$method();
+}
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/select-phonenum.html b/httemplate/elements/select-phonenum.html
new file mode 100644
index 0000000..b98d140
--- /dev/null
+++ b/httemplate/elements/select-phonenum.html
@@ -0,0 +1,84 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/phonenums.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_phonenums' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'prefix'} %>exchange_changed(what, callback) {
+
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled';
+ what.form.<% $opt{'prefix'} %>phonenum.style.display = 'none';
+ var phonenumwait = document.getElementById('<% $opt{'prefix'} %>phonenumwait');
+ phonenumwait.style.display = '';
+ var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror');
+ phonenumerror.style.display = 'none';
+
+ exchange = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_phonenums(phonenums) {
+
+ // blank the current phonenum
+ for ( var i = what.form.<% $opt{'prefix'} %>phonenum.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>phonenum.options[i] = null;
+
+% if ($opt{empty}) {
+ opt(what.form.<% $opt{'prefix'} %>phonenum, '', '<% $opt{empty} %>');
+% }
+
+ // add the new phonenums
+ var phonenumArray = eval('(' + phonenums + ')' );
+ for ( var s = 0; s < phonenumArray.length; s++ ) {
+ var phonenumLabel = phonenumArray[s];
+ if ( phonenumLabel == "" )
+ phonenumLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>phonenum, phonenumArray[s], phonenumLabel);
+ }
+
+ //var phonenumFormLabel = document.getElementById('<% $opt{'prefix'} %>phonenumlabel');
+
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = '';
+
+ phonenumwait.style.display = 'none';
+ if ( phonenumArray.length >= 1 ) {
+ what.form.<% $opt{'prefix'} %>phonenum.disabled = '';
+ what.form.<% $opt{'prefix'} %>phonenum.style.display = '';
+ } else {
+ var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror');
+ phonenumerror.style.display = '';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new phonenums
+ <% $opt{'prefix'} %>get_phonenums( exchange, <% $opt{'svcpart'} %>, <% $opt{'prefix'} %>update_phonenums );
+
+ }
+
+</SCRIPT>
+
+<DIV ID="phonenumwait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding phone numbers</B></DIV>
+
+<DIV ID="phonenumerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different city/exchange</B></DIV>
+
+<SELECT NAME="<% $opt{'prefix'} %>phonenum" notonChange="<% $opt{'prefix'} %>phonenum_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+ <OPTION VALUE="">Select phone number</OPTION>
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{disabled} = 'disabled' unless exists $opt{disabled};
+
+</%init>
diff --git a/httemplate/elements/select-pkg_class.html b/httemplate/elements/select-pkg_class.html
new file mode 100644
index 0000000..e47cfe4
--- /dev/null
+++ b/httemplate/elements/select-pkg_class.html
@@ -0,0 +1,22 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'pkg_class',
+ 'name_col' => 'classname',
+ 'value' => $classnum,
+ 'empty_label' => '(none)',
+ 'hashref' => \%hash,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+my %hash = ();
+$hash{'disabled'} = '' unless $opt{'showdisabled'};
+
+
+$opt{'records'} = delete $opt{'pkg_class'}
+ if $opt{'pkg_class'};
+
+</%init>
diff --git a/httemplate/elements/select-rate.html b/httemplate/elements/select-rate.html
new file mode 100644
index 0000000..83a7add
--- /dev/null
+++ b/httemplate/elements/select-rate.html
@@ -0,0 +1,9 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'rate',
+ 'name_col' => 'ratename',
+ 'empty_label' => 'Select rate plan',
+ #'hashref' => { 'disabled' => '' },
+ 'order_by' => ' ORDER BY ratenum', #ratename ?
+ @_,
+ )
+%>
diff --git a/httemplate/elements/select-state.html b/httemplate/elements/select-state.html
new file mode 100644
index 0000000..9b358e2
--- /dev/null
+++ b/httemplate/elements/select-state.html
@@ -0,0 +1,66 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-state.html',
+ #recommended
+ country => $current_country,
+ state => $current_state,
+
+ #optional
+ prefix => $optional_unique_prefix,
+ onchange => $javascript,
+ disabled => 0, #bool
+ disable_empty => 1, #defaults to 1, disable the empty option
+ empty_label => 'all', #label for empty option
+ disable_countyupdate => 0, #bool - disabled update of the select-state.html
+ style => [ 'attribute:value', 'another:value' ],
+ );
+
+</%doc>
+
+<SELECT NAME = "<% $pre %>state"
+ ID = "<% $pre %>state"
+ onChange = "<% $onchange %>"
+ <% $opt{'disabled'} %>
+ <% $style %>
+>
+
+% unless ( $opt{'disable_empty'} ) {
+ <OPTION VALUE=""<% $opt{state} eq '' ? ' SELECTED' : '' %>><% $opt{empty_label} %>
+% }
+
+% foreach my $state ( keys %states ) {
+
+ <OPTION VALUE="<% $state |h %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' %>
+
+% }
+
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+foreach my $opt (qw( state country prefix onchange disabled empty_label )) {
+ $opt{$opt} = '' unless exists($opt{$opt}) && defined($opt{$opt});
+}
+
+$opt{'disable_empty'} = 1 unless exists($opt{'disable_empty'});
+
+my $pre = $opt{'prefix'};
+
+my $onchange =
+ ( $opt{'disable_countyupdate'} ? '' : $pre.'state_changed(this); ' ).
+ $opt{'onchange'};
+
+$opt{'style'} ||= [];
+my $style =
+ scalar(@{$opt{style}})
+ ? 'STYLE="'. join(';', @{$opt{style}}). '"'
+ : '';
+
+tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} );
+
+</%init>
+
diff --git a/httemplate/elements/select-svc-domain.html b/httemplate/elements/select-svc-domain.html
new file mode 100644
index 0000000..4c04466
--- /dev/null
+++ b/httemplate/elements/select-svc-domain.html
@@ -0,0 +1,50 @@
+<SELECT NAME="domsvc" SIZE=1>
+% foreach my $svcnum (
+% sort { $svc_domain{$a} cmp $svc_domain{$b} }
+% keys %svc_domain
+% ) {
+
+ <OPTION VALUE="<% $svcnum %>"
+ <% ($svcnum == $domsvc) ? ' SELECTED' : '' %>
+ ><% $svc_domain{$svcnum} %>
+
+% }
+
+</SELECT>
+<%init>
+
+my %opt = @_;
+
+my %svc_domain = ();
+my $domsvc;
+
+my $domsvc = $opt{'curr_value'};
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+my $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+
+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";
+ }
+}
+
+%svc_domain = (
+ %svc_domain,
+ FS::svc_Domain_Mixin->domain_select_hash( 'svcpart' => $part_svc->svcpart,
+ 'pkgnum' => $pkgnum,
+ )
+);
+
+</%init>
diff --git a/httemplate/elements/select-svc_acct-domain.html b/httemplate/elements/select-svc_acct-domain.html
new file mode 100644
index 0000000..c9a9206
--- /dev/null
+++ b/httemplate/elements/select-svc_acct-domain.html
@@ -0,0 +1,46 @@
+<SELECT NAME="domsvc" SIZE=1>
+% foreach my $svcnum (
+% sort { $svc_domain{$a} cmp $svc_domain{$b} }
+% keys %svc_domain
+% ) {
+% my $svc_domain = $svc_domain{$svcnum};
+% my $selected = ($svcnum == $domsvc) ? ' SELECTED' : ''
+
+ <OPTION VALUE="<% $svcnum %>" <% $selected %>><% $svc_domain{$svcnum} %>
+
+% }
+
+</SELECT>
+<%init>
+
+my %opt = @_;
+
+my $domsvc = $opt{'curr_value'};
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+my $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+
+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";
+ }
+}
+
+%svc_domain = (
+ %svc_domain,
+ FS::svc_acct->domain_select_hash( 'svcpart' => $part_svc->svcpart,
+ 'pkgnum' => $pkgnum,
+ )
+);
+</%init>
diff --git a/httemplate/elements/select-svc_pbx.html b/httemplate/elements/select-svc_pbx.html
new file mode 100644
index 0000000..19bce96
--- /dev/null
+++ b/httemplate/elements/select-svc_pbx.html
@@ -0,0 +1,57 @@
+<SELECT <% $opt{'multiple'} ? 'MULTIPLE' : 'SIZE=1' %>
+ NAME = "<% $opt{'element_name'} || $opt{'field'} || 'pbxsvc' %>"
+ <% $opt{'element_etc'} %>
+>
+
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
+ <OPTION VALUE=""><% $opt{'empty_label'} || '' %>
+% }
+
+% foreach my $svcnum (
+% sort { $svc_pbx{$a} cmp $svc_pbx{$b} }
+% keys %svc_pbx
+% ) {
+% my $svc_pbx = $svc_pbx{$svcnum};
+% my $selected = ($svcnum == $pbxsvc) ? ' SELECTED' : '';
+
+ <OPTION VALUE="<% $svcnum %>" <% $selected %>><% $svc_pbx{$svcnum} %>
+
+% }
+
+</SELECT>
+<%init>
+
+# false laziness w/select-svc_acct-domain.html
+
+my %opt = @_;
+
+my $pbxsvc = $opt{'curr_value'};
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+my $svcpart = $part_svc ? $part_svc->svcpart : '';
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+my $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+
+my %svc_pbx = ();
+
+if ( $pbxsvc ) {
+ my $svc_pbx = qsearchs('svc_pbx', { 'svcnum' => $pbxsvc } );
+ if ( $svc_pbx ) {
+ $svc_pbx{$svc_pbx->svcnum} = $svc_pbx;
+ } else {
+ warn "unknown svc_pbx.svcnum for svc_acct.pbxsvc: $pbxsvc";
+ }
+}
+
+%svc_pbx = (
+ %svc_pbx,
+ FS::svc_Common->pbx_select_hash( 'svcpart' => $svcpart,
+ 'pkgnum' => $pkgnum,
+ )
+);
+</%init>
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
new file mode 100644
index 0000000..741e51e
--- /dev/null
+++ b/httemplate/elements/select-table.html
@@ -0,0 +1,200 @@
+<%doc>
+
+Example:
+
+ include( '/elements/select-table.html',
+
+ ##
+ # required
+ ##
+ 'table' => 'table_name',
+ 'name_col' => 'name_column',
+
+ #strongly recommended (you want your forms to be "sticky" on errors, right?)
+ 'curr_value' => 'current_value',
+ #'value' => #deprecated form of 'curr_value',
+
+ ##
+ # optional
+ ##
+
+ #search params
+ 'hashref' => {},
+ 'addl_from' => '',
+ 'extra_sql' => '',
+ 'agent_virt' => 0, #set true and make sure the result is JOINed to
+ #something with agentnum (usually cust_main)
+ 'agent_null' => 0, #set true to always show un-agented entries
+ 'agent_null_right' => '', #right to see un-agented entries
+ #or
+ 'records' => \@records, #instead of search params
+
+ #instead of the primary key... only for special cases
+ 'value_col' => 'columnname',
+
+ #basic params controlling the resulting <SELECT>
+ 'pre_options' => [ 'value' => 'option' ], #before normal options
+ 'post_options' => [ 'value' => 'option' ], #after normal options
+ 'empty_label' => '', #better specify it though, the default might change
+ 'multiple' => 0, # bool
+ 'disable_empty' => 0, # bool (implied by multiple)
+ 'label_showkey' => 0, # bool
+ 'label_callback' => sub { my $record = shift; return "label"; },
+
+ #more params controlling HTML stuff about the <SELECT>
+ 'element_name' => '', #HTML element name, defaults to the name of
+ # the primary key column
+ 'field' => '', #synonym for element_name
+ 'element_etc' => '', #additional attributes (i.e. "DISABLED") for the
+ #<SELECT> element
+ 'onchange' => '', #javascript code
+
+ #params (well, a param) controlling the <OPTION>s
+ 'extra_option_attributes' => [ 'field' ], #field or method in $table objects
+ #(are prefixed w/data- per HTML5)
+
+ #special return options
+ 'js_only' => 0, #set true to return only the JS portions (i.e. nothing)
+ 'html_only' => 0, #set true to return only the HTML portions (no-op, i.e. return everything)
+
+ #debugging
+ 'debug' => 0, #set true to enable
+
+ )
+
+</%doc>
+% unless ( $opt{'js_only'} ) {
+
+<SELECT <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+ NAME = "<% $opt{'element_name'} || $opt{'field'} || $key %>"
+ ID = "<% $opt{'id'} || $key %>"
+ <% $onchange %>
+ <% $opt{'element_etc'} %>
+>
+
+% while ( @pre_options ) {
+% my $pre_opt = shift(@pre_options);
+% my $pre_label = shift(@pre_options);
+% my $selected = ( ref($value) && $value->{$pre_opt} )
+% || ( $value eq $pre_opt );
+ <OPTION VALUE="<% $pre_opt %>"
+ <% $selected ? 'SELECTED' : '' %>
+ ><% $pre_label %>
+% }
+
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
+ <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %>
+% }
+
+% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col()
+% || $a->$key() <=> $b->$key()
+% }
+% @records
+% )
+% {
+% my $recvalue = $record->$key();
+ <OPTION VALUE="<% $recvalue %>"
+ <% $opt{'all_selected'} || ref($value) && $value->{$recvalue} || $value && $value eq $recvalue # not == because of value_col
+ ? ' SELECTED' : ''
+ %>
+% foreach my $att ( @{ $opt{'extra_option_attributes'} } ) {
+ data-<% $att %>="<% $record->$att() |h %>"
+% }
+ ><% $opt{'label_showkey'} ? "$recvalue: " : '' %>
+ <% $opt{'label_callback'}
+ ? &{ $opt{'label_callback'} }( $record )
+ : $record->$name_col()
+ %>
+% }
+
+% while ( @post_options ) {
+% my $post_opt = shift(@post_options);
+% my $post_label = shift(@post_options);
+% my $selected = ( ref($value) && $value->{$post_opt} )
+% || ( $value eq $post_opt );
+ <OPTION VALUE="<% $post_opt %>"
+ <% $selected ? 'SELECTED' : '' %>
+ ><% $post_label %>
+% }
+
+</SELECT>
+
+%}
+<%init>
+
+my( %opt ) = @_;
+
+warn "elements/select-table.html: \n". Dumper(%opt)
+ if exists $opt{debug} && $opt{debug};
+
+$opt{'extra_option_attributes'} ||= [];
+
+my $onchange = '';
+if ( $opt{'onchange'} ) {
+ $onchange = $opt{'onchange'};
+ $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/;
+ $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange
+ #callbacks should act the same
+ $onchange = 'onChange="'. $onchange. '"';
+}
+
+my $dbdef_table = dbdef->table($opt{'table'})
+ or die "can't find dbdef for ". $opt{'table'}. " table\n";
+
+my $key = $opt{'value_col'} || $dbdef_table->primary_key;
+
+my $name_col = $opt{'name_col'};
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+$value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
+
+#my $addl_from = $opt{'addl_from'} || '';
+my $extra_sql = $opt{'extra_sql'} || '';
+my $hashref = $opt{'hashref'} || {};
+
+if ( $opt{'agent_virt'} ) {
+ $extra_sql .=
+ ( $extra_sql =~ /WHERE/i || scalar(keys %$hashref ) ? ' AND ' : ' WHERE ' ).
+ $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null' => $opt{'agent_null'},
+ 'null_right' => $opt{'agent_null_right'},
+ );
+}
+
+my @records = ();
+if ( $opt{'records'} ) {
+ @records = @{ $opt{'records'} };
+} else {
+ @records = qsearch( {
+ 'table' => $opt{'table'},
+ 'addl_from' => $opt{'addl_from'},
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => ( $opt{'order_by'} || "ORDER BY $name_col" ),
+ });
+}
+
+unless ( $value < 1 # !$value #ignore negatives too
+ or ref($value)
+ or ! exists( $opt{hashref}->{disabled} ) #??
+ or grep { $value == $_->$key() } @records
+ ) {
+ delete $opt{hashref}->{disabled};
+ $opt{hashref}->{$key} = $value;
+ my $record = qsearchs( {
+ 'table' => $opt{table},
+ 'addl_from' => $opt{'addl_from'},
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+ });
+ push @records, $record if $record;
+}
+
+if ( ref( $value ) eq 'ARRAY' ) {
+ $value = { map { $_ => 1 } @$value };
+}
+
+my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
+my @post_options = $opt{post_options} ? @{ $opt{post_options} } : ();
+
+</%init>
diff --git a/httemplate/elements/select-taxclass.html b/httemplate/elements/select-taxclass.html
new file mode 100644
index 0000000..6845d23
--- /dev/null
+++ b/httemplate/elements/select-taxclass.html
@@ -0,0 +1,41 @@
+% if ( $conf->exists('enable_taxclasses') ) {
+
+ <SELECT NAME="<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>">
+
+% if ( $conf->exists('require_taxclasses') ) {
+ <OPTION VALUE="(select)">Select tax class
+% } else {
+ <OPTION VALUE="">
+% }
+
+% foreach my $taxclass ( @{ $opt{'taxclasses'} } ) {
+ <OPTION VALUE="<% $taxclass %>"<% $taxclass eq $selected_taxclass ? ' SELECTED' : '' %>><% $taxclass %>
+% }
+
+ </SELECT>
+
+% } else {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>" VALUE="<% $selected_taxclass %>">
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $selected_taxclass = $opt{'curr_value'}; # || $opt{'value'} necessary?
+
+my $conf = new FS::Conf;
+
+unless ( $opt{'taxclasses'} ) {
+
+ #my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+ my $sth = dbh->prepare("SELECT taxclass FROM part_pkg_taxclass WHERE disabled IS NULL OR disabled = '' OR taxclass = ?")
+ or die dbh->errstr;
+ $sth->execute($selected_taxclass) or die $sth->errstr;
+ my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
+ @{ $opt{'taxclasses'} } = grep $_, keys %taxclasses;
+
+}
+
+</%init>
diff --git a/httemplate/elements/select-taxoverride.html b/httemplate/elements/select-taxoverride.html
new file mode 100644
index 0000000..8b1c528
--- /dev/null
+++ b/httemplate/elements/select-taxoverride.html
@@ -0,0 +1,28 @@
+ <INPUT NAME = "<% $name %>"
+ ID = "<% $name %>"
+ TYPE = "hidden"
+ VALUE = "<% $value %>"
+ >
+ <A href="javascript:void(0)" onclick="<% $onclick %>">
+ <% $value ? "Edit $class tax overrides" : "Override $class taxes" %>
+ </A>
+<%init>
+
+my %opt = @_;
+my $name = $opt{element_name} || $opt{field} || 'tax_override';
+my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value};
+
+my %usage_class = map { ($_->classnum => $_->classname) }
+ qsearch('usage_class', {});
+$usage_class{setup} = 'Setup';
+$usage_class{recur} = 'Recurring';
+
+my $usage;
+$name =~ /^tax_override_(\w+)$/ && ( $usage = $1 );
+
+my $class = lc($usage_class{$usage} || "Usage class $usage")
+ if $usage;
+
+my $onclick = $opt{onclick} || "overlib( OLiframeContent('part_pkg_taxoverride.html?element_name=$name;selected='+document.getElementById('$name').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit $class product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;";
+
+</%init>
diff --git a/httemplate/elements/select-taxproduct.html b/httemplate/elements/select-taxproduct.html
new file mode 100644
index 0000000..0f6ef55
--- /dev/null
+++ b/httemplate/elements/select-taxproduct.html
@@ -0,0 +1,28 @@
+<% $opt{'prefix'} %><INPUT NAME = "<% $name %>"
+ ID = "<% $name %>"
+ TYPE = "hidden"
+ VALUE = "<% $value |h %>"
+ >
+ <INPUT NAME = "<% $name %>_description"
+ ID = "<% $name %>_description"
+ TYPE = "text"
+ VALUE = "<% $description %>"
+ SIZE = "12"
+ onClick = "<% $onclick %>"><% $opt{'postfix'} %>
+<%init>
+
+my %opt = @_;
+my $name = $opt{element_name} || $opt{field} || 'taxproductnum';
+my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value};
+my $description = $opt{'taxproduct_description'};
+
+unless ( $description || ! $value ) {
+ my $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct', { 'taxproductnum'=> $value } );
+ $description = $part_pkg_taxproduct->description
+ if $part_pkg_taxproduct;
+}
+
+my $onclick = $opt{onclick} || "overlib( OLiframeContent('${p}/browse/part_pkg_taxproduct.cgi?_type=select&id=${name}&taxproductnum='+document.getElementById('${name}').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;";
+
+</%init>
diff --git a/httemplate/elements/select-terms.html b/httemplate/elements/select-terms.html
new file mode 100644
index 0000000..52f9fb5
--- /dev/null
+++ b/httemplate/elements/select-terms.html
@@ -0,0 +1,41 @@
+<SELECT NAME = "invoice_terms"
+ ID = "invoice_terms"
+ <% $opt{'disabled'} ? 'DISABLED' : ''%>
+>
+# #false laziness w/select-table.html
+% while ( @pre_options ) {
+% my $pre_opt = shift(@pre_options);
+% my $pre_label = shift(@pre_options);
+% my $selected = # ( ref($value) && $value->{$pre_opt} ) ||
+% ( $curr_value eq $pre_opt );
+ <OPTION VALUE="<% $pre_opt %>"
+ <% $selected ? 'SELECTED' : '' %>
+ ><% $pre_label %>
+% }
+
+ <OPTION VALUE="<% $empty_value %>"><% $empty_label %>
+% foreach my $term ( @terms ) {
+ <OPTION VALUE="<% $term %>" <% $curr_value eq $term ? ' SELECTED' : '' %>><% $term %>
+% }
+</SELECT>
+<%init>
+
+my %opt = @_;
+my $curr_value = $opt{'curr_value'};
+my $conf = new FS::Conf;
+
+my $empty_label =
+ $opt{'empty_label'}
+ || 'Default ('.
+ ($conf->config('invoice_default_terms') || 'Payable upon receipt').
+ ')';
+
+my $empty_value = $opt{'empty_value'} || '';
+
+my @terms = ( 'Payable upon receipt',
+ ( map "Net $_", 0, 10, 15, 20, 30, 45, 60 ),
+ );
+
+my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
+
+</%init>
diff --git a/httemplate/elements/select-user.html b/httemplate/elements/select-user.html
new file mode 100644
index 0000000..bdb92e7
--- /dev/null
+++ b/httemplate/elements/select-user.html
@@ -0,0 +1,30 @@
+<SELECT NAME="usernum">
+
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
+ <OPTION VALUE="">all</OPTION>
+% }
+
+% foreach my $otaker ( @{ $opt{'otakers'} } ) {
+ <OPTION VALUE="<% shift(@{$opt{'usernums'}}) %>"><% $otaker %></OPTION>
+% }
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+unless ( $opt{'otakers'} ) {
+
+ my $sth = dbh->prepare("SELECT username,usernum FROM access_user".
+ " WHERE disabled = '' or disabled IS NULL")
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ for($sth->fetchall_arrayref) {
+ $opt{'otakers'} = [ map { $_->[0] } @$_ ];
+ $opt{'usernums'} = [ map { $_->[1] } @$_ ];
+ }
+
+}
+
+</%init>
diff --git a/httemplate/elements/select.html b/httemplate/elements/select.html
new file mode 100644
index 0000000..5249a6d
--- /dev/null
+++ b/httemplate/elements/select.html
@@ -0,0 +1,79 @@
+% unless ( $opt{'js_only'} ) {
+
+<SELECT NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ previousValue = "<% $curr_value %>"
+ previousText = "<% $labels->{$curr_value} || $curr_value %>"
+ <% $style %>
+ <% $opt{disabled} %>
+ <% $onchange %>
+>
+
+% if ( $opt{options} ) {
+%
+% foreach my $option ( @{ $opt{options} } ) { #just arrayref for now
+
+ <OPTION VALUE="<% $option %>"
+ <% $opt{curr_value} eq $option ? 'SELECTED' : '' %>
+ >
+ <% $labels->{$option} || $option %>
+ </OPTION>
+
+% }
+%
+% } else { #deprecated weird value hashref used only by reason.html
+%
+% my $aref = $opt{'value'}->{'values'};
+% my $vkey = $opt{'value'}->{'vcolumn'};
+% my $ckey = $opt{'value'}->{'ccolumn'};
+% foreach my $v (@$aref) {
+
+ <OPTION VALUE="<% $v->$vkey %>"
+ <% ($opt{curr_value} eq $v->$vkey) ? 'SELECTED' : '' %>
+ >
+ <% $v->$ckey %>
+ </OPTION>
+
+% }
+%
+% }
+
+</SELECT>
+
+% }
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $labels = $opt{'option_labels'} || $opt{'labels'};
+
+my $curr_value = $opt{'curr_value'};
+
+my $onchange = '';
+if ( $opt{'onchange'} ) {
+ $onchange = $opt{'onchange'};
+ $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/;
+ $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange
+ #callbacks should act the same
+ $onchange = 'onChange="'. $onchange. '"' unless $onchange =~ /^onChange=/i;
+}
+
+$opt{'disabled'} = &{ $opt{'disabled'} }( \%opt )
+ if ref($opt{'disabled'}) eq 'CODE';
+$opt{'disabled'} = 'DISABLED'
+ if $opt{'disabled'} && $opt{'disabled'} !~ /disabled/i; # uuh... yeah?
+
+my @style = ref($opt{'style'})
+ ? @{ $opt{'style'} }
+ : $opt{'style'}
+ ? ( $opt{'style'} )
+ : ();
+
+my $style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
+
+
+</%init>
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
new file mode 100644
index 0000000..89fe41b
--- /dev/null
+++ b/httemplate/elements/selectlayers.html
@@ -0,0 +1,242 @@
+<%doc>
+
+Example:
+
+ include( '/elements/selectlayers.html',
+ 'field' => $key, # SELECT element NAME (passed as form field)
+ # also used as ID and a unique key for layers and
+ # functions
+ 'curr_value' => $selected_layer,
+ 'options' => [ 'option1', 'option2' ],
+ 'labels' => { 'option1' => 'Option 1 Label',
+ 'option2' => 'Option 2 Label',
+ },
+
+ #XXX put this handling it its own selectlayers-fields.html element?
+ 'layer_prefix' => 'prefix_', #optional prefix for fieldnames
+ 'layer_fields' => { 'layer' => [ 'fieldname',
+ { label => 'fieldname2',
+ type => 'text', #implemented:
+ # text, money, fixed,
+ # hidden, checkbox,
+ # checkbox-multiple,
+ # select, select-agent,
+ # select-pkg_class,
+ # select-part_referral,
+ # select-taxclass,
+ # select-table,
+ #XXX tbd:
+ # more?
+ },
+ ...
+ ],
+ 'layer2' => [ 'l2fieldname',
+ ...
+ ],
+ },
+
+ #current values for layer fields above
+ 'layer_values' => { 'layer' => { 'fieldname' => 'current_value',
+ 'fieldname2' => 'field2value',
+ ...
+ },
+ 'layer2' => { 'l2fieldname' => 'l2value',
+ ...
+ },
+ ...
+ },
+
+ #or manual control, instead of layer_fields and layer_values above
+ #called with args: my( $layer, $layer_fields, $layer_values, $layer_prefix )
+ 'layer_callback' =>
+
+ 'html_between => '', #optional HTML displayed between the SELECT and the
+ #layers, scalar or coderef ('field' passed as a param)
+ 'onchange' => '', #javascript code run when the SELECT changes
+ # ("what" is the element)
+ 'js_only' => 0, #set true to return only the JS portions
+ 'html_only' => 0, #set true to return only the HTML portions
+ 'select_only' => 0, #set true to return only the <SELECT> HTML
+ 'layers_only' => 0, #set true to return only the layers <DIV> HTML
+ )
+
+</%doc>
+% unless ( grep $opt{$_}, qw(html_only js_only select_only layers_only) ) {
+ <SCRIPT TYPE="text/javascript">
+% }
+% unless ( grep $opt{$_}, qw(html_only select_only layers_only) ) {
+
+% if ( $opt{layermap} ) {
+% my %map = %{ $opt{layermap} };
+ var layermap = { "":"",
+ <% join(',', map { qq("$_":"$map{$_}") } keys %map ) %>
+ };
+% }
+
+ function <% $key %>changed(what) {
+
+ <% $opt{'onchange'} %>
+
+ var <% $key %>layer = what.options[what.selectedIndex].value;
+
+% foreach my $layer ( @layers ) {
+%
+% if ( $opt{layermap} ) {
+ if ( layermap[ <% $key %>layer ] == "<% $layer %>" ) {
+% } else {
+ if (<% $key %>layer == "<% $layer %>" ) {
+% }
+
+% foreach my $not ( grep { $_ ne $layer } @layers ) {
+% my $element = "document.getElementById('${key}d$not').style";
+ <% $element %>.display = "none";
+ <% $element %>.zIndex = 0;
+% }
+
+% my $element = "document.getElementById('${key}d$layer').style";
+ <% $element %>.display = "";
+ <% $element %>.zIndex = 1;
+
+ }
+% }
+
+ //<% $opt{'onchange'} %>
+
+ }
+% }
+% unless ( grep $opt{$_}, qw(html_only js_only select_only layers_only) ) {
+ </SCRIPT>
+% }
+%
+% unless ( grep $opt{$_}, qw(js_only layers_only) ) {
+
+ <SELECT NAME = "<% $key %>"
+ ID = "<% $key %>"
+ previousValue = "<% $selected %>"
+ previousText = "<% $options{$selected} %>"
+ onChange="<% $key %>changed(this);"
+ >
+
+% foreach my $option ( keys %$options ) {
+
+ <OPTION VALUE="<% $option %>"
+ <% $option eq $selected ? ' SELECTED' : '' %>
+ ><% $options->{$option} %></OPTION>
+
+% }
+
+ </SELECT>
+
+% }
+% unless ( grep $opt{$_}, qw(js_only select_only layers_only) ) {
+
+<% ref($between) ? &{$between}($key) : $between %>
+
+% }
+%
+% unless ( grep $opt{$_}, qw(js_only select_only) ) {
+
+% foreach my $layer ( @layers ) {
+% my $selected_layer;
+% if ( $opt{layermap} ) {
+% $selected_layer = $opt{layermap}->{$selected};
+% } else {
+% $selected_layer = $selected;
+% }
+
+ <DIV ID="<% $key %>d<% $layer %>"
+ STYLE="<% $selected_layer eq $layer
+ ? 'display: "" ; z-index: 1'
+ : 'display: none; z-index: 0'
+ %>"
+ >
+
+ <% &{$layer_callback}($layer, $layer_fields, $layer_values, $layer_prefix) %>
+
+ </DIV>
+
+% }
+
+% }
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+#use Data::Dumper;
+#warn Dumper(%opt);
+
+my $key = $opt{field}; # || 'generate_one' #?
+
+tie my %options, 'Tie::IxHash',
+ map { $_ => $opt{'labels'}->{$_} }
+ @{ $opt{'options'} }; #just arrayref for now
+
+my $between = exists($opt{html_between}) ? $opt{html_between} : '';
+my $options = \%options;
+
+my @layers = ();
+if ( $opt{layermap} ) {
+ my %layers = map { $opt{layermap}->{$_} => 1 } keys %options;
+ @layers = keys %layers;
+} else {
+ @layers = keys %options;
+}
+
+my $selected = exists($opt{curr_value}) ? $opt{curr_value} : '';
+
+#XXX eek. also eek $layer_fields in the layer_callback() call...
+my $layer_fields = $opt{layer_fields};
+my $layer_values = $opt{layer_values};
+my $layer_prefix = $opt{layer_prefix};
+
+my $layer_callback = $opt{layer_callback} || \&layer_callback;
+
+sub layer_callback {
+ my( $layer, $layer_fields, $layer_values, $layer_prefix ) = @_;
+
+ return '' unless $layer && exists $layer_fields->{$layer};
+ tie my %fields, 'Tie::IxHash', @{ $layer_fields->{$layer} };
+
+ #XXX this should become an element itself... (false laziness w/edit.html)
+ # but at least all the elements inside are the shared mason elements now
+
+ return '' unless keys %fields;
+ my $html = "<TABLE>";
+
+ foreach my $field ( keys %fields ) {
+
+ my $lf = ref($fields{$field})
+ ? $fields{$field}
+ : { 'label'=>$fields{$field} };
+
+ my $value = $layer_values->{$layer}{$field};
+
+ my $type = $lf->{type} || 'text';
+
+ my $include = $type;
+ $include = "input-$include" if $include =~ /^(text|money)$/;
+ $include = "tr-$include" unless $include eq 'hidden';
+
+ $html .= include( "/elements/$include.html",
+ %$lf,
+ 'field' => "$layer_prefix$field",
+ 'id' => "$layer_prefix$field", #separate?
+ #don't want field0_label0...?
+ 'label_id' => $layer_prefix.$field."_label",
+
+ 'value' => ( $lf->{'value'} || $value ), #hmm.
+ 'curr_value' => $value,
+ );
+
+ }
+ $html .= '</TABLE>';
+ return $html;
+}
+
+</%init>
diff --git a/httemplate/elements/small_custview.html b/httemplate/elements/small_custview.html
new file mode 100644
index 0000000..9060d89
--- /dev/null
+++ b/httemplate/elements/small_custview.html
@@ -0,0 +1,3 @@
+% my $conf = new FS::Conf;
+
+<% small_custview( shift, shift || scalar($conf->config('countrydefault')), @_ ) %>
diff --git a/httemplate/elements/table-grid.html b/httemplate/elements/table-grid.html
new file mode 100644
index 0000000..e1e6c36
--- /dev/null
+++ b/httemplate/elements/table-grid.html
@@ -0,0 +1,25 @@
+<STYLE TYPE="text/css">
+
+.grid table { border: solid; empty-cells: show }
+.grid TH { padding-left: 3px; padding-right: 3px; border: 1px solid #dddddd; border-bottom: dashed 1px black; border-right: none }
+.grid TD { padding-left: 3px; padding-right: 3px; empty-cells: show; border: 1px solid #cccccc; border-bottom: none; border-right: none }
+
+.inv table { border: none }
+.inv TH { border: none }
+.inv TD { border: none }
+
+</STYLE>
+
+<TABLE CLASS="grid" CELLSPACING=<% $opt{cellspacing} %> CELLPADDING=<% $opt{cellpadding} %> BORDER=1 BORDERCOLOR="#000000" <% $opt{bgcolor} %> STYLE="border: solid 1px black; empty-cells: show">
+
+<%init>
+
+my %opt = @_;
+$opt{cellspacing} ||= 0;
+$opt{cellpadding} ||= 0;
+
+$opt{bgcolor} =~ s/^#//;
+$opt{bgcolor} = 'BGCOLOR="#'. $opt{bgcolor}. '"' if length($opt{bgcolor});
+
+</%init>
+
diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html
new file mode 100644
index 0000000..8152b65
--- /dev/null
+++ b/httemplate/elements/table.html
@@ -0,0 +1,11 @@
+%
+% 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/elements/tablebreak-tr-title.html b/httemplate/elements/tablebreak-tr-title.html
new file mode 100644
index 0000000..ee28312
--- /dev/null
+++ b/httemplate/elements/tablebreak-tr-title.html
@@ -0,0 +1,14 @@
+</TABLE>
+
+<TABLE <% $id %> BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<% include('tr-title.html', @_ ) %>
+
+<%init>
+
+my %opt = @_;
+
+my $id = '';
+$id = 'ID="'. $opt{'table_id'}. '"' if $opt{'table_id'};
+
+</%init>
diff --git a/httemplate/elements/tr-checkbox-multiple.html b/httemplate/elements/tr-checkbox-multiple.html
new file mode 100644
index 0000000..bb90a82
--- /dev/null
+++ b/httemplate/elements/tr-checkbox-multiple.html
@@ -0,0 +1,40 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+
+% foreach my $option ( @{ $opt{options} } ) { #just arrayref for now
+
+ <INPUT TYPE = "checkbox"
+ NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id}.'_'.$option %>"
+ VALUE = "<% $option %>"
+ <% ref($value) && $value->{$option} || $value eq $option
+ ? ' CHECKED' : ''
+ %>
+ <% $onchange %>
+
+ >&nbsp;<% $labels->{$option} %>
+
+ <BR>
+
+% }
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $labels = $opt{'option_labels'} || $opt{'labels'};
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-checkbox.html b/httemplate/elements/tr-checkbox.html
new file mode 100644
index 0000000..c3cf92d
--- /dev/null
+++ b/httemplate/elements/tr-checkbox.html
@@ -0,0 +1,19 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+ <% include('checkbox.html', @_) %>
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-checkboxes-table.html b/httemplate/elements/tr-checkboxes-table.html
new file mode 100644
index 0000000..0099427
--- /dev/null
+++ b/httemplate/elements/tr-checkboxes-table.html
@@ -0,0 +1,20 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+% }
+
+ <% include( '/elements/checkboxes-table.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+ </TD>
+ </TR>
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-contact.html b/httemplate/elements/tr-contact.html
new file mode 100644
index 0000000..ee0e6e8
--- /dev/null
+++ b/httemplate/elements/tr-contact.html
@@ -0,0 +1,24 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', %opt) %>
+ <TD <% $cell_style %>>
+
+% }
+%
+ <% include( '/elements/contact.html', %opt ) %>
+%
+% unless ( $opt{'js_only'} ) {
+
+ </TD>
+ </TR>
+
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+$opt{'label'} ||= 'Contact';
+
+</%init>
diff --git a/httemplate/elements/tr-fixed-country.html b/httemplate/elements/tr-fixed-country.html
new file mode 100644
index 0000000..806d92c
--- /dev/null
+++ b/httemplate/elements/tr-fixed-country.html
@@ -0,0 +1,10 @@
+<% include('tr-fixed.html', %opt ) %>
+<%init>
+
+my %opt = @_;
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'formatted_value'} = code2country($value). " ($value)";
+
+</%init>
diff --git a/httemplate/elements/tr-fixed-state.html b/httemplate/elements/tr-fixed-state.html
new file mode 100644
index 0000000..eea30dd
--- /dev/null
+++ b/httemplate/elements/tr-fixed-state.html
@@ -0,0 +1,10 @@
+<% include('tr-fixed.html', %opt ) %>
+<%init>
+
+my %opt = @_;
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'formatted_value'} = state_label($value, $opt{'object'}->country);
+
+</%init>
diff --git a/httemplate/elements/tr-fixed.html b/httemplate/elements/tr-fixed.html
new file mode 100644
index 0000000..095e1bc
--- /dev/null
+++ b/httemplate/elements/tr-fixed.html
@@ -0,0 +1,15 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD BGCOLOR="#dddddd" <% $style %>><% $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'} |h %></TD>
+
+</TR>
+
+<% include('hidden.html', %opt ) %>
+
+<%init>
+
+my %opt = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-freq.html b/httemplate/elements/tr-freq.html
new file mode 100644
index 0000000..cb58bf6
--- /dev/null
+++ b/httemplate/elements/tr-freq.html
@@ -0,0 +1,54 @@
+<% include('tr-td-label.html', @_) %>
+
+ <TD <% $style %>>
+
+ <INPUT TYPE = "text"
+ SIZE = "<% $opt{'size'} || 4 %>"
+ NAME = "<% $opt{'field'} || 'freq' %>"
+ ID = "<% $opt{'id'} %>"
+ VALUE = "<% $curr_value %>"
+ >
+
+ <SELECT NAME = "<% $opt{'field'} || 'freq' %>_units">
+% foreach my $freq ( keys %freq ) {
+ <OPTION VALUE="<% $freq %>"
+ <% $freq eq $units ? 'SELECTED' : '' %>
+ ><% $freq{$freq} %>
+% }
+ </SELECT>
+
+ </TD>
+
+</TR>
+
+<%once>
+
+ tie my %freq, 'Tie::IxHash',
+ #'y' => 'years',
+ 'm' => 'months',
+ 'w' => 'weeks',
+ 'd' => 'days',
+ 'h' => 'hours',
+ ;
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+my $units = 'm';
+
+if ( $curr_value =~ /^(\d*)([mwdh])$/i ) {
+ $curr_value = $1;
+ $units = lc($2);
+}
+
+</%init>
+
diff --git a/httemplate/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html
new file mode 100644
index 0000000..2aa5974
--- /dev/null
+++ b/httemplate/elements/tr-input-beginning_ending.html
@@ -0,0 +1,79 @@
+% unless ( $m->count == $previous_request_count ) {
+ <LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
+% }
+
+<TR>
+ <TD ALIGN="right">From date: </TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>beginning" ID="<% $opt{prefix} %>beginning_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>beginning_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>beginning_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "<% $opt{prefix} %>beginning_text",
+ ifFormat: "<% $date_format. $time_format %>",
+ button: "<% $opt{prefix} %>beginning_button",
+ align: "BR"
+ <% $input_time %>
+ });
+</SCRIPT>
+
+% unless ( $opt{layout} =~ /^h/i ) { #horizontal
+
+</TR>
+<TR>
+
+% }
+
+ <TD ALIGN="right">To date: </TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>ending" ID="<% $opt{prefix} %>ending_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>ending_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>ending_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "<% $opt{prefix} %>ending_text",
+ ifFormat: "<% $date_format. $time_format %>",
+ button: "<% $opt{prefix} %>ending_button",
+ align: "BR"
+ <% $input_time %>
+ });
+</SCRIPT>
+</TR>
+
+<TR>
+ <TD></TD>
+ <TD COLSPAN=<% $opt{layout} =~ /^h/i ? 3 : 1 %>>
+ <FONT SIZE="-1">(leave one or both dates blank for an open-ended search)</FONT>
+ </TD>
+</TR>
+
+<%once>
+
+my $previous_request_count = '';
+
+</%once>
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$opt{prefix} = '' unless defined $opt{prefix};
+$opt{prefix} .= '_' if $opt{prefix};
+
+my( $input_time, $time_format, $time_hint ) = ( '', '', '' );
+my( $size, $maxlength ) = ( 11, 10 );
+if ( $opt{'input_time'} ) {
+ $input_time = ', showsTime: true, timeFormat: "12"'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3
+ $time_format = ' %k:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5
+ $time_hint = ' h:m:s';
+ $size = 21;
+ $maxlength = 27;
+}
+
+</%init>
+<%cleanup>
+
+$previous_request_count = $m->count;
+
+</%cleanup>
diff --git a/httemplate/elements/tr-input-date-field.html b/httemplate/elements/tr-input-date-field.html
new file mode 100644
index 0000000..ff4996f
--- /dev/null
+++ b/httemplate/elements/tr-input-date-field.html
@@ -0,0 +1,56 @@
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
+
+<TR>
+ <TD ALIGN="right"><% $label %></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<% $name %>" ID="<% $name %>_text" VALUE="<% $value %>">
+ <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $name %>_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "<% $name %>_text",
+ ifFormat: "<% $format %>",
+ button: "<% $name %>_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+<%init>
+
+my($name, $value, $label, $format, $usedatetime);
+if ( ref($_[0]) ) {
+ my $opt = shift;
+ $name = $opt->{'name'};
+ $value = $opt->{'value'};
+ $label = $opt->{'label'};
+ $format = $opt->{'format'};
+ $usedatetime = $opt->{'usedatetime'};
+} else {
+ ($name, $value, $label, $format, $usedatetime) = @_;
+}
+
+my $conf = new FS::Conf;
+
+$format ||= $conf->config('date_format') || '%m/%d/%Y';
+
+$label = $name unless $label;
+
+if ( $value =~ /\S/ ) {
+ if ( $usedatetime ) {
+ my $dt = DateTime->from_epoch(epoch => $value, time_zone => 'floating');
+ $value = $dt->strftime($format);
+ } elsif ( $value =~ /^\d+$/ ) {
+ $value = time2str($format, $value);
+ }
+} else {
+ $value = '';
+}
+
+</%init>
+
diff --git a/httemplate/elements/tr-input-lessthan_greaterthan.html b/httemplate/elements/tr-input-lessthan_greaterthan.html
new file mode 100644
index 0000000..16c2ed9
--- /dev/null
+++ b/httemplate/elements/tr-input-lessthan_greaterthan.html
@@ -0,0 +1,13 @@
+<TR>
+ <TD ALIGN="right"><% $opt{label} %> less than: </TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{field} %>_lt" SIZE=7></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><% $opt{label} %> greater than: </TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{field} %>_gt" SIZE=7></TD>
+</TR>
+
+<%init>
+ my %opt = @_;
+</%init>
diff --git a/httemplate/elements/tr-input-money.html b/httemplate/elements/tr-input-money.html
new file mode 100644
index 0000000..8801419
--- /dev/null
+++ b/httemplate/elements/tr-input-money.html
@@ -0,0 +1,13 @@
+<% include('tr-input-text.html', @_,
+ 'type' => 'text',
+ 'prefix' => $money_char,
+ 'size' => 8,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%once>
+
diff --git a/httemplate/elements/tr-input-percentage.html b/httemplate/elements/tr-input-percentage.html
new file mode 100644
index 0000000..ae553a9
--- /dev/null
+++ b/httemplate/elements/tr-input-percentage.html
@@ -0,0 +1,8 @@
+<% include('tr-input-text.html', @_,
+ 'type' => 'text',
+ 'postfix' => '%',
+ 'size' => 5, #6? check in IE (not a big deal)
+ 'maxlength' => 7,
+ 'text-align' => 'right',
+ )
+%>
diff --git a/httemplate/elements/tr-input-text.html b/httemplate/elements/tr-input-text.html
new file mode 100644
index 0000000..2127933
--- /dev/null
+++ b/httemplate/elements/tr-input-text.html
@@ -0,0 +1,15 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $colspan %> <% $cell_style %> ID="<% $opt{input_id} || $opt{id}.'_input0' %>"><% include('input-text.html', @_ ) %></TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-justtitle.html b/httemplate/elements/tr-justtitle.html
new file mode 100644
index 0000000..e9eda8b
--- /dev/null
+++ b/httemplate/elements/tr-justtitle.html
@@ -0,0 +1,11 @@
+<TR>
+ <TH CLASS="background" COLSPAN=<% $opt{colspan} || 2 %> ALIGN="left">
+ <FONT SIZE="+1"><% $opt{value} %></FONT>
+ </TH>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/tr-part_pkg_freq.html b/httemplate/elements/tr-part_pkg_freq.html
new file mode 100644
index 0000000..649f8a2
--- /dev/null
+++ b/httemplate/elements/tr-part_pkg_freq.html
@@ -0,0 +1,24 @@
+<% include('tr-select.html', @_,
+ 'field' => 'freq',
+ 'options' => \@freq,
+ 'labels' => \%freq,
+ 'curr_value' => $curr_value,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+my $curr_value = $opt{'curr_value'} || $opt{'freq'};
+
+tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()};
+if ( dbdef->table('part_pkg')->column('freq')->type =~ /(int)/i ) {
+ delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
+}
+
+my @freq = keys %freq;
+@freq = grep { /^\d+$/ } @freq
+ if $opt{'month_increments_only'};
+# if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
+
+</%init>
diff --git a/httemplate/elements/tr-password.html b/httemplate/elements/tr-password.html
new file mode 100644
index 0000000..bbc624d
--- /dev/null
+++ b/httemplate/elements/tr-password.html
@@ -0,0 +1,4 @@
+<% include('tr-input-text.html', @_,
+ 'type' => 'password',
+ )
+%>
diff --git a/httemplate/elements/tr-pkg_svc.html b/httemplate/elements/tr-pkg_svc.html
new file mode 100644
index 0000000..e66bdf7
--- /dev/null
+++ b/httemplate/elements/tr-pkg_svc.html
@@ -0,0 +1,93 @@
+<TR>
+ <TD CLASS="background" COLSPAN=99>
+
+<% itable('', 4, 1) %><TR><TD VALIGN="top">
+<% $thead %>
+
+%foreach my $part_svc ( @part_svc ) {
+% my $svcpart = $part_svc->svcpart;
+% my $pkg_svc = $pkg_svc{$svcpart}
+% || new FS::pkg_svc ( {
+% 'pkgpart' => $pkgpart,
+% 'svcpart' => $svcpart,
+% 'quantity' => 0,
+% 'primary_svc' => '',
+% } );
+% if ( $cgi->param('error') ) {
+% my $primary_svc = ( $pkg_svc->primary_svc =~ /^Y/i );
+% my $pkg_svc_primary = scalar($cgi->param('pkg_svc_primary'));
+% $pkg_svc->primary_svc('')
+% if $primary_svc && $pkg_svc_primary != $svcpart;
+% $pkg_svc->primary_svc('Y')
+% if ! $primary_svc && $pkg_svc_primary == $svcpart;
+% }
+%
+% push @fixups, "pkg_svc$svcpart";
+%
+% my $quan = 0;
+% if ( $cgi->param("pkg_svc$svcpart") =~ /^\s*(\d+)\s*$/ ) {
+% $quan = $1;
+% } elsif ( $pkg_svc->quantity ) {
+% $quan = $pkg_svc->quantity;
+% }
+
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=7 MAXLENGTH=6 VALUE="<% $quan %>">
+ </TD>
+
+ <TD ALIGN="center">
+ <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>>
+ </TD>
+
+ <TD>
+ <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %>
+ </TD>
+ </TR>
+% foreach ( 1 .. $columns-1 ) {
+% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) {
+%
+
+ </TABLE></TD><TD VALIGN="top"><% $thead %>
+% }
+% }
+% $count++;
+%
+% }
+
+</TR></TABLE></TD></TR></TABLE>
+
+ </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+my $cgi = $opt{'cgi'};
+
+my $thead = "\n\n". ntable('#cccccc', 2).
+ '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'.
+ '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-2>Primary</FONT></TH>'.
+ '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
+
+my $part_pkg = $opt{'object'};
+my $pkgpart = $part_pkg->pkgpart;
+
+my $where = "WHERE disabled IS NULL OR disabled = ''";
+if ( $pkgpart ) {
+ $where .= " OR 0 < ( SELECT quantity FROM pkg_svc
+ WHERE pkg_svc.svcpart = part_svc.svcpart
+ AND pkgpart = $pkgpart
+ )";
+}
+my @part_svc = qsearch('part_svc', {}, '', $where);
+
+#my $q_part_pkg = $clone_part_pkg || $part_pkg;
+#my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc;
+my %pkg_svc = map { $_->svcpart => $_ } $part_pkg->pkg_svc;
+
+my @fixups = ();
+my $count = 0;
+my $columns = 3;
+
+</%init>
diff --git a/httemplate/elements/tr-search-cust_main.html b/httemplate/elements/tr-search-cust_main.html
new file mode 100644
index 0000000..9df91a1
--- /dev/null
+++ b/httemplate/elements/tr-search-cust_main.html
@@ -0,0 +1,15 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $colspan %> <% $cell_style %> ID="<% $opt{input_id} || $opt{id}.'_input0' %>"><% include('search-cust_main.html', @_ ) %></TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html
new file mode 100644
index 0000000..e443ad2
--- /dev/null
+++ b/httemplate/elements/tr-select-access_group.html
@@ -0,0 +1,22 @@
+%
+% my( $groupnum, %opt ) = @_;
+%
+% $opt{'access_group'} ||= [ qsearch( 'access_group', {} ) ]; # { disabled=>'' } )
+%
+% #warn "***** tr-select-access_group: \n". Dumper(%opt);
+%
+% if ( scalar(@{ $opt{'access_group'} }) == 0 ) {
+
+
+ <INPUT TYPE="hidden" NAME="groupnum" VALUE="">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Access group' %></TD>
+ <TD>
+ <% include( '/elements/select-access_group.html', $groupnum, %opt ) %>
+ </TD>
+ </TR>
+% }
+
diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html
new file mode 100644
index 0000000..fcfa9f3
--- /dev/null
+++ b/httemplate/elements/tr-select-agent.html
@@ -0,0 +1,33 @@
+% if ( scalar(@agents) == 1 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'agentnum' %>" VALUE="<% $agents[0]->agentnum %>">
+
+%# YUCK. empty row so we don't throw g_row in edit.html off :/
+ <TR>
+ </TR>
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Agent' %></TD>
+ <TD>
+ <% include( '/elements/select-agent.html',
+ 'curr_value' => $agentnum,
+ 'agents' => \@agents,
+ %opt,
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $agentnum = $opt{'curr_value'} || $opt{'value'};
+
+my @agents = $opt{'agents'}
+ ? @{ $opt{'agents'} }
+ : $FS::CurrentUser::CurrentUser->agents;
+
+</%init>
diff --git a/httemplate/elements/tr-select-agent_type.html b/httemplate/elements/tr-select-agent_type.html
new file mode 100644
index 0000000..1b0dfd4
--- /dev/null
+++ b/httemplate/elements/tr-select-agent_type.html
@@ -0,0 +1,39 @@
+% if ( scalar(@agent_types) == 1 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'typenum' %>" VALUE="<% $agent_types[0]->typenum %>">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Agent Type' %></TD>
+ <TD>
+ <% include( '/elements/select-agent_type.html',
+ 'curr_value' => $typenum,
+ 'agent_types' => \@agent_types,
+ %opt,
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $typenum = $opt{'curr_value'} || $opt{'value'};
+
+my @agent_types = ();
+if ( $opt{'agent_types'} ) {
+ #@agents = @{ $opt{'agents'} };
+
+ #here is the agent virtualization
+# my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href;
+# @agent_types = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agent_types'} };
+
+ delete $opt{'agent_types'};
+} else {
+# @agents = $FS::CurrentUser::CurrentUser->agents;
+}
+
+</%init>
diff --git a/httemplate/elements/tr-select-agent_types.html b/httemplate/elements/tr-select-agent_types.html
new file mode 100644
index 0000000..efbf386
--- /dev/null
+++ b/httemplate/elements/tr-select-agent_types.html
@@ -0,0 +1,19 @@
+% unless ( $opt{'disabled'} || scalar(@all_agent_types) == 1 ) {
+
+<% include('/elements/tr-justtitle.html', value=>'Agent (reseller) types') %>
+
+% }
+
+<TR>
+ <TD COLSPAN=2>
+ <% include('select-agent_types.html', %opt) %>
+ </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+
+</%init>
diff --git a/httemplate/elements/tr-select-cdrbatch.html b/httemplate/elements/tr-select-cdrbatch.html
new file mode 100644
index 0000000..d080e24
--- /dev/null
+++ b/httemplate/elements/tr-select-cdrbatch.html
@@ -0,0 +1,30 @@
+% if ( ! $show ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'cdrbatchnum' %>" VALUE="__ALL__">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'cdrbatch'} || 'CDR Batch: ' %></TD>
+ <TD>
+ <% include( '/elements/select-cdrbatch.html', 'curr_value' => $selected_cdrbatch, %opt ) %>
+ </TD>
+ </TR>
+
+% }
+<%init>
+
+my( %opt ) = @_;
+my $conf = new FS::Conf;
+my $selected_cdrbatch = $opt{'curr_value'}; # || $opt{'value'} necessary?
+
+$opt{'records'} = delete $opt{'cdr_batch'}
+ if $opt{'cdr_batch'};
+
+my $sth = dbh->prepare('SELECT COUNT(*) FROM cdr_batch LIMIT 1')
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $show = $sth->fetchrow_arrayref->[0];
+
+</%init>
+
diff --git a/httemplate/elements/tr-select-cust-fields.html b/httemplate/elements/tr-select-cust-fields.html
new file mode 100644
index 0000000..80562fe
--- /dev/null
+++ b/httemplate/elements/tr-select-cust-fields.html
@@ -0,0 +1,15 @@
+%
+% my( $cust_fields, %opt ) = @_;
+%
+% use FS::ConfDefaults;
+% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ];
+%
+%
+
+
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Customer fields' %></TD>
+ <TD>
+ <% include( '/elements/select-cust-fields.html', $cust_fields, %opt ) %>
+ </TD>
+</TR>
diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html
new file mode 100644
index 0000000..69400f1
--- /dev/null
+++ b/httemplate/elements/tr-select-cust-part_pkg.html
@@ -0,0 +1,115 @@
+%if ( scalar(@pkg_class) > 1 && ! $conf->exists('disable-cust-pkg_class') ) {
+
+ <% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/cust-part_pkg.cgi',
+ 'subs' => [ 'get_part_pkg' ],
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function part_pkg_opt(what,value,text,can_discount) {
+ var optionName = new Option(text, value, false, false);
+ optionName.setAttribute('data-can_discount', can_discount);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function classnum_changed(what) {
+
+ what.form.pkgpart.disabled = 'disabled'; //disable part_pkg dropdown
+ what.form.submit.disabled = true; //disable the submit button
+ var discountnum = what.form.discountnum;
+ if ( discountnum ) {
+ discountnum.disabled = true; //disable discount dropdown
+ }
+
+ classnum = what.options[what.selectedIndex].value;
+
+ function update_part_pkg(part_pkg) {
+
+ // blank the current packages
+ for ( var i = what.form.pkgpart.length; i>= 0; i-- )
+ what.form.pkgpart.options[i] = null;
+
+ // add the new packages
+ opt(what.form.pkgpart, '', 'Select package');
+ var packagesArray = eval('(' + part_pkg + ')' );
+ for ( var s = 0; s < packagesArray.length; s=s+3 ) {
+ var packagesLabel = packagesArray[s+1];
+ var can_discount = packagesArray[s+2];
+ part_pkg_opt(
+ what.form.pkgpart, packagesArray[s], packagesLabel, can_discount
+ );
+ }
+
+ what.form.pkgpart.disabled = ''; //re-enable part_pkg dropdown
+
+ }
+
+ get_part_pkg( <% $cust_main->custnum %>, classnum, update_part_pkg );
+
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TH ALIGN="right">Package Class</TH>
+ <TD COLSPAN=7>
+ <% include('/elements/select-cust-pkg_class.html',
+ 'curr_value' => $opt{'classnum'},
+ 'pkg_class' => \@pkg_class,
+ 'onchange' => 'classnum_changed',
+ )
+ %>
+ </TD>
+ </TR>
+
+%}
+
+<TR>
+ <TH ALIGN="right">Package</TH>
+ <TD COLSPAN=7>
+ <% include('/elements/select-cust-part_pkg.html',
+ 'curr_value' => $opt{'curr_value'}, #$pkgpart
+ 'classnum' => $opt{'classnum'},
+ 'cust_main' => $opt{'cust_main'}, #$cust_main
+ 'onchange' => 'enable_order_pkg',
+ )
+ %>
+ </TD>
+</TR>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my %opt = @_;
+
+my $pre_label = $opt{'pre_label'} || '';
+$pre_label .= ' ' if length($pre_label) && $pre_label =~ /\S$/;
+
+my $cust_main = $opt{'cust_main'}
+ or die "cust_main not specified";
+
+#my @pkg_class = sort { $a->classname cmp $b->classname }
+# qsearch( 'pkg_class', { 'disabled' => '' } );
+
+#"normal" part_pkg agent virtualization (agentnum or type)
+my @part_pkg = qsearch({
+ 'select' => 'DISTINCT classnum',
+ 'table' => 'part_pkg',
+ 'hashref' => { 'disabled' => '' },
+ 'extra_sql' =>
+ ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $opt{'cust_main'}->agent ),
+});
+
+my @pkg_class =
+ sort { $a->classname cmp $b->classname } #should get a sort order in config
+ map { $_->pkg_class || new FS::pkg_class { 'classnum' => '',
+ 'classname' => '(none)' }
+ }
+ @part_pkg;
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_class.html b/httemplate/elements/tr-select-cust_class.html
new file mode 100644
index 0000000..54a11d7
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_class.html
@@ -0,0 +1,27 @@
+% if ( scalar(@{ $opt{'cust_class'} }) == 0 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Customer class' %></TD>
+ <TD>
+ <% include( '/elements/select-cust_class.html',
+ 'curr_value' => $classnum,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'cust_class'} ||= [ qsearch( 'cust_class', { disabled=>'' } ) ];
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html
new file mode 100644
index 0000000..f06ea0f
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_location.html
@@ -0,0 +1,232 @@
+<%doc>
+
+Example:
+
+ include('/elements/tr-select-cust_location.html',
+ 'cgi' => $cgi,
+
+ 'cust_main' => $cust_main,
+ #or
+ 'prospect_main' => $prospect_main,
+
+ #optional
+ 'empty_label' => '(default service address)',
+ )
+
+</%doc>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/location.cgi',
+ 'subs' => [ 'get_location' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function locationnum_changed(what) {
+ var locationnum = what.options[what.selectedIndex].value;
+ if ( locationnum == -1 ) {
+
+% for (@location_fields, 'city_select') {
+ what.form.<%$_%>.disabled = false;
+ what.form.<%$_%>.style.backgroundColor = '#ffffff';
+% }
+
+ what.form.address1.value = '';
+ what.form.address2.value = '';
+ what.form.city.value = '';
+ what.form.zip.value = '';
+
+ changeSelect(what.form.country, <% $countrydefault |js_string %>);
+
+ country_changed( what.form.country,
+ fix_state_factory( <% $statedefault |js_string %>,
+ ''
+ )
+ );
+
+ } else {
+
+ if ( locationnum == 0 ) {
+% if ( $cust_main ) {
+ what.form.address1.value = <% $cust_main->get($prefix.'address1') |js_string %>;
+ what.form.address2.value = <% $cust_main->get($prefix.'address2') |js_string %>;
+ what.form.city.value = <% $cust_main->get($prefix.'city') |js_string %>;
+ what.form.zip.value = <% $cust_main->get($prefix.'zip') |js_string %>;
+
+ changeSelect(what.form.country, <% $cust_main->get($prefix.'country') | js_string %> );
+
+ country_changed( what.form.country,
+ fix_state_factory( <% $cust_main->get($prefix.'state') | js_string %>,
+ <% $cust_main->get($prefix.'county') | js_string %>
+ )
+ );
+% }
+
+ } else {
+ get_location( locationnum, update_location );
+ }
+
+% if ( $editable ) {
+ if ( locationnum == 0 ) {
+% }
+
+%#sleep/wait until dropdowns are updated?
+% for (@location_fields, 'city_select') {
+ what.form.<%$_%>.disabled = true;
+ what.form.<%$_%>.style.backgroundColor = '#dddddd';
+% }
+
+% if ( $editable ) {
+ } else {
+
+%#sleep/wait until dropdowns are updated?
+% for (@location_fields, 'city_select') {
+ what.form.<%$_%>.disabled = false;
+ what.form.<%$_%>.style.backgroundColor = '#ffffff';
+% }
+
+ }
+% }
+
+ }
+ }
+
+ function fix_state_factory (state, county) {
+ function fix_state() {
+ var state_el = document.getElementById('state');
+ changeSelect(state_el, state);
+ state_changed(state_el, fix_county_factory(county) );
+ }
+ return fix_state;
+ }
+
+ function fix_county_factory(county) {
+ function fix_county() {
+ var county_el = document.getElementById('county');
+ if ( county.length > 0 ) {
+ changeSelect(county_el, county );
+ } else {
+ county_el.selectedIndex = 0;
+ }
+ county_changed(county_el);
+ }
+ return fix_county;
+ }
+
+ function changeSelect(what, value) {
+ for ( var i=0; i<what.length; i++) {
+ if ( what.options[i].value == value ) {
+ what.selectedIndex = i;
+ }
+ }
+ }
+
+ function update_location( string ) {
+ var hash = eval('('+string+')');
+ document.getElementById('address1').value = hash['address1'];
+ document.getElementById('address2').value = hash['address2'];
+ document.getElementById('city').value = hash['city'];
+ document.getElementById('zip').value = hash['zip'];
+
+ var country_el = document.getElementById('country');
+
+ changeSelect( country_el, hash['country'] );
+
+ country_changed( country_el,
+ fix_state_factory( hash['state'],
+ hash['county']
+ )
+ );
+ }
+
+</SCRIPT>
+
+<TR>
+ <<%$th%> ALIGN="right"><% $opt{'label'} || 'Service&nbsp;location' %></<%$th%>>
+ <TD COLSPAN=7>
+ <SELECT NAME="locationnum" onChange="locationnum_changed(this);">
+ <OPTION VALUE=""><% $opt{'empty_label'} || '(default service address)' |h %>
+% my @locations = $cust_main ? $cust_main->cust_location : ();
+% push @locations, $cust_location
+% if !$cust_main && $cust_location && $cust_location->locationnum>0;
+% foreach my $loc ( @locations ) {
+ <OPTION VALUE="<% $loc->locationnum %>"
+ <% $locationnum == $loc->locationnum ? 'SELECTED' : '' %>
+ ><% $loc->line |h %>
+% }
+% if ( $addnew ) {
+ <OPTION VALUE="-1"
+ <% $locationnum == -1 ? 'SELECTED' : '' %>
+ >Add new location
+% }
+ </SELECT>
+ </TD>
+</TR>
+
+<% include('/elements/location.html',
+ 'object' => $cust_location,
+ #'onchange' ? probably not
+ 'disabled' => $disabled,
+ 'no_asterisks' => 1,
+ 'no_bold' => $opt{'no_bold'},
+ )
+%>
+
+<%once>
+
+my @location_fields = qw( address1 address2 city county state zip country );
+
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+
+my %opt = @_;
+my $cgi = $opt{'cgi'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $cust_main = $opt{'cust_main'};
+my $prospect_main = $opt{'prospect_main'};
+
+my $prefix = ($cust_main && length($cust_main->ship_last)) ? 'ship_' : '';
+
+my $locationnum;
+if ( length($opt{'curr_value'}) ) {
+ $locationnum = $opt{'curr_value'};
+} else {
+ $cgi->param('locationnum') =~ /^(\-?\d*)$/ or die "illegal locationnum";
+ $locationnum = $1;
+}
+
+#probably could use explicit controls
+# (cust_main locations not editable for tax reasons)
+my $editable = $cust_main ? 0 : 1; #could use explicit control
+my $addnew = $cust_main ? 1 : ( $locationnum>0 ? 0 : 1 );
+
+my $cust_location;
+if ( $locationnum && $locationnum != -1 ) {
+ $cust_location = qsearchs('cust_location', { 'locationnum' => $locationnum } )
+ or die "unknown locationnum";
+} else {
+ $cust_location = new FS::cust_location;
+ if ( $locationnum == -1 ) {
+ $cust_location->$_( $cgi->param($_) ) foreach @location_fields;
+ } elsif ( $cust_pkg && $cust_pkg->locationnum ) {
+ my $pkg_location = $cust_pkg->cust_location;
+ $cust_location->$_( $pkg_location->$_ ) foreach @location_fields;
+ $opt{'empty_label'} ||= 'package address: '.$pkg_location->line;
+ } elsif ( $cust_main ) {
+ $cust_location->$_( $cust_main->get($prefix.$_) ) foreach @location_fields;
+ }
+}
+
+my $disabled = ( $locationnum == -1 || ($editable && $locationnum) )
+ ? ''
+ : 'DISABLED';
+
+my $th = $opt{'no_bold'} ? 'TD' : 'TH';
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_main-status.html b/httemplate/elements/tr-select-cust_main-status.html
new file mode 100644
index 0000000..6700d7e
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_main-status.html
@@ -0,0 +1,29 @@
+<% include ('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+
+ <% include( '/elements/select-cust_main-status.html',
+ 'curr_value' => $curr_value,
+ %opt
+ )
+ %>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+#my $onchange = $opt{'onchange'}
+# ? 'onChange="'. $opt{'onchange'}. '(this)"'
+# : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+$opt{'statuses'} ||= [ FS::cust_main->statuses() ]; # { disabled=>'' } )
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_pkg-balances.html b/httemplate/elements/tr-select-cust_pkg-balances.html
new file mode 100644
index 0000000..89dc5d4
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_pkg-balances.html
@@ -0,0 +1,31 @@
+% if ( scalar(@cust_pkg) == 0 ) {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% } elsif ( scalar(@cust_pkg) == 1 ) {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $cust_pkg[0]->pkgnum %>">
+% } else {
+ <TR>
+ <TD ALIGN="right">For package</TD>
+ <TD COLSPAN=2>
+ <% include('select-cust_pkg-balances.html',
+ 'cust_pkg' => \@cust_pkg,
+ 'cgi' => $opt{'cgi'},
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+my %opt = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum\n";
+
+my @cust_pkg =
+ grep { ! $_->get('cancel') || $cust_main->balance_pkgnum($_->pkgnum) }
+ $cust_main->all_pkgs;
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_pkg-status.html b/httemplate/elements/tr-select-cust_pkg-status.html
new file mode 100644
index 0000000..6cc29d0
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_pkg-status.html
@@ -0,0 +1,29 @@
+<% include ('tr-td-label.html', 'label' => 'Status', @_ ) %>
+
+ <TD <% $style %>>
+
+ <% include( '/elements/select-cust_pkg-status.html',
+ 'curr_value' => $curr_value,
+ %opt
+ )
+ %>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+#my $onchange = $opt{'onchange'}
+# ? 'onChange="'. $opt{'onchange'}. '(this)"'
+# : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+$opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } )
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-did.html b/httemplate/elements/tr-select-did.html
new file mode 100644
index 0000000..987ade6
--- /dev/null
+++ b/httemplate/elements/tr-select-did.html
@@ -0,0 +1,41 @@
+<% include('tr-td-label.html', @_ ) %>
+
+% if ( $opt{'curr_value'} ne '' && $use_selector ) {
+
+ <TD BGCOLOR="#dddddd" <% $cell_style %>><% $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'} |h %></TD>
+
+ <% include('hidden.html', %opt ) %>
+
+% } else {
+
+ <TD <% $cell_style %>>
+ <% include('/elements/select-did.html', %opt ) %>
+ </TD>
+
+% }
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+#warn Dumper(\%opt); if $DEBUG;
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+#false laziness w/select-did.html
+#XXX make sure this comes through on errors too
+my $svcpart = $opt{'svcpart'}
+ || $opt{'object'}->svcpart
+ || $opt{'object'}->cust_svc->svcpart;
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+}
+
+my $use_selector = scalar(@exports) ? 1 : 0;
+
+</%init>
diff --git a/httemplate/elements/tr-select-discount.html b/httemplate/elements/tr-select-discount.html
new file mode 100644
index 0000000..258eeb3
--- /dev/null
+++ b/httemplate/elements/tr-select-discount.html
@@ -0,0 +1,179 @@
+% if ( scalar(@{ $opt{'discount'} }) == 0
+% && ! $curuser->access_right('Custom discount customer package') ) {
+
+ <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $discountnum %>">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right" WIDTH="176"><% $opt{'label'} || '<B>Discount</B>' %></TD>
+ <TD <% $colspan %>>
+ <% include( '/elements/select-discount.html',
+ 'curr_value' => $discountnum,
+ 'onchange' => $onchange,
+ %opt,
+ )
+ %>
+ </TD>
+ </TR>
+
+% # a weird kind of false laziness w/edit/discount.html
+
+% # <INPUT TYPE="hidden" NAME="<% $name %>_disabled" VALUE="Y">
+
+
+ <% include( '/elements/tr-select.html',
+ 'label' => '<B>Discount Type</B>',
+ 'field' => $name. '__type',
+ 'id' => $name. '__type',
+ 'options' => \@_type_options,
+ 'curr_value' => scalar($cgi->param($name.'__type')),
+ 'onchange' => $name.'__type_changed',
+ 'colspan' => $opt{'colspan'},
+ )
+ %>
+
+ <% include( '/elements/tr-input-money.html',
+ 'label' => '<B>Discount Amount&nbsp;</B>',
+ 'field' => $name. '_amount',
+ 'id' => $name. '_amount',
+ 'default' => '0.00',
+ 'curr_value' => scalar($cgi->param($name.'_amount')),
+ 'colspan' => $opt{'colspan'},
+ )
+ %>
+
+ <% include( '/elements/tr-input-percentage.html',
+ 'label' => '<B>Discount Percentage</B>',
+ 'field' => $name. '_percent',
+ 'id' => $name. '_percent',
+ 'default' => '0',
+ 'curr_value' => scalar($cgi->param($name.'_percent')),
+ 'colspan' => $opt{'colspan'},
+ )
+ %>
+
+ <% include( '/elements/tr-input-text.html',
+ 'label' => '<B>Discount duration (months)</B>',
+ 'field' => $name. '_months',
+ 'id' => $name. '_months',
+ 'size' => 2,
+ 'postfix' => qq(<FONT SIZE="-1" ID="${name}_months_postfix"><I>(blank for non-expiring discount)</I></FONT>),
+ 'curr_value' => scalar($cgi->param($name.'_months')),
+ 'colspan' => $opt{'colspan'},
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+% my $ge = 'document.getElementById';
+
+ function <% $name %>_changed(what) {
+ var <% $name %> = what.options[what.selectedIndex].value;
+
+ if ( <% $name %> == '-1' ) {
+ <% $ge %>('<% $name %>__type_label0').style.display = '';
+ <% $ge %>('<% $name %>__type_label0').style.visibility = '';
+ <% $ge %>('<% $name %>__type').style.display = '';
+ <% $ge %>('<% $name %>__type').style.visibility = '';
+% #XXX retrieve previous visibility for amount, percent :/
+ <% $ge %>('<% $name %>_months_label0').style.display = '';
+ <% $ge %>('<% $name %>_months_label0').style.visibility = '';
+ <% $ge %>('<% $name %>_months').style.display = '';
+ <% $ge %>('<% $name %>_months').style.visibility = '';
+ <% $ge %>('<% $name %>_months_postfix').style.display = '';
+ <% $ge %>('<% $name %>_months_postfix').style.visibility = '';
+ } else {
+
+ <% $ge %>('<% $name %>__type_label0').style.display = 'none';
+ <% $ge %>('<% $name %>__type_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>__type').style.display = 'none';
+ <% $ge %>('<% $name %>__type').style.visibility = 'hidden';
+
+% #XXX save visibility settings for amount, percent :/
+ <% $ge %>('<% $name %>_amount_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_amount_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_input0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_amount_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_input0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+
+ <% $ge %>('<% $name %>_months_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_months_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_months').style.display = 'none';
+ <% $ge %>('<% $name %>_months').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_months_postfix').style.display = 'none';
+ <% $ge %>('<% $name %>_months_postfix').style.visibility = 'hidden';
+
+ }
+
+ }
+
+ function <% $name %>__type_changed(what) {
+ var <% $name %>__type = what.options[what.selectedIndex].value;
+
+ if ( <% $name %>__type == '<% $select %>' ) {
+ <% $ge %>('<% $name %>_amount_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_amount').style.display = 'none';
+ <% $ge %>('<% $name %>_amount').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent').style.display = 'none';
+ <% $ge %>('<% $name %>_percent').style.visibility = 'hidden';
+ } else if ( <% $name %>__type == 'Amount' ) {
+ <% $ge %>('<% $name %>_amount_label0').style.display = '';
+ <% $ge %>('<% $name %>_amount_label0').style.visibility = '';
+ <% $ge %>('<% $name %>_amount_input0').style.display = '';
+ <% $ge %>('<% $name %>_amount_input0').style.visibility = '';
+ <% $ge %>('<% $name %>_percent_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+ } else if ( <% $name %>__type == 'Percentage' ) {
+ <% $ge %>('<% $name %>_amount_label0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_label0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_amount_input0').style.display = 'none';
+ <% $ge %>('<% $name %>_amount_input0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_label0').style.display = '';
+ <% $ge %>('<% $name %>_percent_label0').style.visibility = '';
+ <% $ge %>('<% $name %>_percent_input0').style.display = '';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = '';
+ }
+
+ }
+
+ <% $name %>_changed(<% $ge %>('<% $name %>'));
+
+ </SCRIPT>
+
+% }
+<%init>
+
+my %opt = @_;
+my $cgi = $opt{'cgi'};
+my $discountnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'discount'} ||= [ qsearch( 'discount', { disabled=>'' } ) ];
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $name = $opt{'element_name'} || $opt{'field'} || 'discountnum';
+
+my $select = 'Select discount type';
+
+my @_type_options = ( 'Amount', 'Percentage' );
+unshift @_type_options, $select;
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+my $onchange = ( $opt{'onchange'} ? delete($opt{'onchange'}).';' : '' ).
+ $name.'_changed(this);';
+
+</%init>
diff --git a/httemplate/elements/tr-select-domain.html b/httemplate/elements/tr-select-domain.html
new file mode 100644
index 0000000..5b8d237
--- /dev/null
+++ b/httemplate/elements/tr-select-domain.html
@@ -0,0 +1,12 @@
+% #if ( scalar(@domains) < 2 ) {
+% #} else {
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Domain' %></TD>
+ <TD>
+ <% include( '/elements/select-domain.html', %opt) %>
+ </TD>
+ </TR>
+% #}
+<%init>
+ my %opt = @_;
+</%init>
diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html
new file mode 100644
index 0000000..083243d
--- /dev/null
+++ b/httemplate/elements/tr-select-from_to.html
@@ -0,0 +1,52 @@
+%
+%
+% #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+% my ($curmon,$curyear) = (localtime(time))[4,5];
+%
+% #find first month
+% my $syear = 1899+$curyear;
+% my $smonth = $curmon+1;
+%
+% #want 12 month by default, not 13
+% $smonth++;
+% if ( $smonth > 12 ) { $smonth-=12; $syear++ }
+%
+% #find last month
+% my $eyear = 1900+$curyear;
+% my $emonth = $curmon+1;
+%
+% my %hash = (
+% 'show_month_abbr' => 1,
+% 'start_year' => '1999',
+% 'end_year' => '2012', #haha, well...
+% @_,
+% );
+%
+%
+
+
+<TR>
+ <TD ALIGN="right">From: </TD>
+ <TD>
+ <% include('/elements/select-month_year.html',
+ 'prefix' => 'start',
+ 'selected_mon' => $smonth,
+ 'selected_year' => $syear,
+ %hash,
+ )
+ %>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">To: </TD>
+ <TD>
+ <% include('/elements/select-month_year.html',
+ 'prefix' => 'end',
+ 'selected_mon' => $emonth,
+ 'selected_year' => $eyear,
+ %hash,
+ )
+ %>
+ </TD>
+</TR>
diff --git a/httemplate/elements/tr-select-invoice_template.html b/httemplate/elements/tr-select-invoice_template.html
new file mode 100644
index 0000000..7ba8d99
--- /dev/null
+++ b/httemplate/elements/tr-select-invoice_template.html
@@ -0,0 +1,39 @@
+<% include('tr-td-label.html', @_) %>
+
+ <TD <% $style %>>
+
+ <SELECT NAME = "<% $opt{'field'} || 'templatename' %>"
+ ID = "<% $opt{'id'} %>"
+ >
+
+% foreach my $templatename ( '', @templatenames ) {
+
+ <OPTION VALUE="<% $templatename %>"
+ <% $templatename eq $curr_value ? 'SELECTED' : '' %>
+ ><% $templatename || '(Default)' %>
+
+% }
+
+ </SELECT>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+my $conf = new FS::Conf;
+
+my @templatenames = $conf->invoice_templatenames;
+
+</%init>
diff --git a/httemplate/elements/tr-select-otaker.html b/httemplate/elements/tr-select-otaker.html
new file mode 100644
index 0000000..edf62dc
--- /dev/null
+++ b/httemplate/elements/tr-select-otaker.html
@@ -0,0 +1,10 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Employee: ' %></TD>
+ <TD><% include('select-otaker.html', %opt) %></TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/tr-select-part_event.html b/httemplate/elements/tr-select-part_event.html
new file mode 100644
index 0000000..e1d913f
--- /dev/null
+++ b/httemplate/elements/tr-select-part_event.html
@@ -0,0 +1,20 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+% }
+
+ <% include( '/elements/select-part_event.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+ </TD>
+ </TR>
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-select-part_pkg.html b/httemplate/elements/tr-select-part_pkg.html
new file mode 100644
index 0000000..88653f4
--- /dev/null
+++ b/httemplate/elements/tr-select-part_pkg.html
@@ -0,0 +1,39 @@
+% if ( $opt{'part_pkg'} && scalar(@{ $opt{'part_pkg'} }) == 0 ) {
+% unless ( $opt{'js_only'} ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'pkgpart' %>" VALUE="">
+
+% }
+%
+% } else {
+%
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', %opt) %>
+ <TD <% $cell_style %>>
+
+% }
+%
+ <% include( '/elements/select-part_pkg.html', %opt ) %>
+%
+% unless ( $opt{'js_only'} ) {
+
+ </TD>
+ </TR>
+
+% }
+%
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+$opt{'label'} ||= 'Package definition';
+
+#taken care of (better) in select-part_pkg now (is there anything using this
+# that needs to override the disabed=>'' ??)
+#$opt{'part_pkg'} ||= [ qsearch( 'part_pkg', {} ) ]; # { disabled=>'' } )
+
+</%init>
diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html
new file mode 100644
index 0000000..a589528
--- /dev/null
+++ b/httemplate/elements/tr-select-part_referral.html
@@ -0,0 +1,37 @@
+% if ( scalar( @{$opt{'part_referrals'}} ) == 0 ) {
+ <P><FONT SIZE="+1" COLOR="#ff0000">You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to <A HREF="<% popurl(2) %>browse/part_referral.html">advertising source listing</A> and create one or more advertising sources.</FONT>
+% } elsif ( scalar( @{$opt{'part_referrals'}} ) == 1 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'refnum' %>" VALUE="<% $opt{'part_referrals'}->[0]->refnum %>">
+
+% } else {
+
+ <TR>
+% if ( $opt{'label'} ) {
+ <TD ALIGN="right"><% $opt{'label'} %></TD>
+% } else {
+ <TH ALIGN="right"><%$r%>Advertising source</TH>
+% }
+ <TD COLSPAN="<% $colspan %>">
+ <% include( '/elements/select-part_referral.html',
+ 'curr_value' => $refnum,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+<%init>
+
+my %opt = @_;
+my $refnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'part_referrals'} ||=
+ [ FS::part_referral->all_part_referral( 1 ) ]; #1: include global
+
+my $colspan = delete($opt{'colspan'}) || 1;
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/elements/tr-select-part_svc.html b/httemplate/elements/tr-select-part_svc.html
new file mode 100644
index 0000000..af51487
--- /dev/null
+++ b/httemplate/elements/tr-select-part_svc.html
@@ -0,0 +1,26 @@
+% if ( scalar(@{ $opt{'part_svc'} }) == 0 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'svcpart' %>" VALUE="">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Package definition' %></TD>
+ <TD>
+ <% include( '/elements/select-part_svc.html',
+ 'multiple' => 1,
+ %opt,
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my( %opt ) = @_;
+
+$opt{'part_svc'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } )
+
+</%init>
diff --git a/httemplate/elements/tr-select-payby.html b/httemplate/elements/tr-select-payby.html
new file mode 100644
index 0000000..354eb55
--- /dev/null
+++ b/httemplate/elements/tr-select-payby.html
@@ -0,0 +1,37 @@
+<% include ('tr-td-label.html', 'label' => 'Payment type', @_ ) %>
+
+ <TD <% $style %>>
+
+ <% include( '/elements/select-payby.html',
+ 'curr_value' => $curr_value,
+ %opt
+ )
+ %>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+#my $onchange = $opt{'onchange'}
+# ? 'onChange="'. $opt{'onchange'}. '(this)"'
+# : '';
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $method = 'payby2longname';
+$method = 'cust_payby2longname' if $opt{'payby_type'} eq 'cust';
+#$method = 'event_payby2longname' if $opt{'payby_type'} eq 'event';
+#$method = 'pay_payby2longname' if $opt{'payby_type'} eq 'pay';
+
+unless ( $opt{'paybys'} ) {
+ tie %{ $opt{'paybys'} }, 'Tie::IxHash', FS::payby->$method();
+}
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
+
diff --git a/httemplate/elements/tr-select-pkg_class.html b/httemplate/elements/tr-select-pkg_class.html
new file mode 100644
index 0000000..28ed5d6
--- /dev/null
+++ b/httemplate/elements/tr-select-pkg_class.html
@@ -0,0 +1,27 @@
+% if ( scalar(@{ $opt{'pkg_class'} }) == 0 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Package class' %></TD>
+ <TD>
+ <% include( '/elements/select-pkg_class.html',
+ 'curr_value' => $classnum,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'pkg_class'} ||= [ qsearch( 'pkg_class', { disabled=>'' } ) ];
+
+</%init>
diff --git a/httemplate/elements/tr-select-rate.html b/httemplate/elements/tr-select-rate.html
new file mode 100644
index 0000000..27f2645
--- /dev/null
+++ b/httemplate/elements/tr-select-rate.html
@@ -0,0 +1,21 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+% }
+
+ <% include( '/elements/select-rate.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+ </TD>
+ </TR>
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
+
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
new file mode 100755
index 0000000..d85538f
--- /dev/null
+++ b/httemplate/elements/tr-select-reason.html
@@ -0,0 +1,189 @@
+<%doc>
+
+Example:
+
+ include( '/elements/tr-select-reason.html',
+
+ #required
+ 'field' => 'reasonnum',
+ 'reason_class' => 'C', # currently 'C', 'R', or 'S'
+ # for cancel, credit, or suspend
+
+ #recommended
+ 'cgi' => $cgi, #easiest way for things to be properly "sticky" on errors
+
+ #optional
+ 'control_button' => 'element_name', #button to be enabled when a reason is
+ #selected
+ 'id' => 'element_id',
+
+ #deprecated ways to keep things "sticky" on errors
+ # (requires duplicate code in each using file to parse cgi params)
+ 'curr_value' => $curr_value,
+ 'curr_value' => {
+ 'typenum' => $typenum,
+ 'reason' => $reason,
+ },
+
+ )
+
+</%doc>
+
+<SCRIPT TYPE="text/javascript">
+ function sh_add<% $func_suffix %>()
+ {
+
+ if (document.getElementById('<% $id %>').selectedIndex == 0){
+ <% $controlledbutton ? $controlledbutton.'.disabled = true;' : ';' %>
+ }else{
+ <% $controlledbutton ? $controlledbutton.'.disabled = false;' : ';' %>
+ }
+
+%if ($curuser->access_right($add_access_right)){
+
+ if (document.getElementById('<% $id %>').selectedIndex ==
+ (document.getElementById('<% $id %>').length - 1)) {
+ document.getElementById('new<% $id %>').disabled = false;
+ document.getElementById('new<% $id %>').style.display = 'inline';
+ document.getElementById('new<% $id %>Label').style.display = 'inline';
+ document.getElementById('new<% $id %>T').disabled = false;
+ document.getElementById('new<% $id %>T').style.display = 'inline';
+ document.getElementById('new<% $id %>TLabel').style.display = 'inline';
+ } else {
+ document.getElementById('new<% $id %>').disabled = true;
+ document.getElementById('new<% $id %>').style.display = 'none';
+ document.getElementById('new<% $id %>Label').style.display = 'none';
+ document.getElementById('new<% $id %>T').disabled = true;
+ document.getElementById('new<% $id %>T').style.display = 'none';
+ document.getElementById('new<% $id %>TLabel').style.display = 'none';
+ }
+
+%}
+
+ }
+</SCRIPT>
+
+<TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD>
+ <SELECT id="<% $id %>" name="<% $name %>" onFocus="sh_add<% $func_suffix %>()" onChange="sh_add<% $func_suffix %>()">
+ <OPTION VALUE="" <% ($init_reason eq '') ? 'SELECTED' : '' %>>Select Reason...</OPTION>
+% foreach my $reason (@reasons) {
+ <OPTION VALUE="<% $reason->reasonnum %>" <% ($init_reason == $reason->reasonnum) ? 'SELECTED' : '' %>><% $reason->reasontype->type %> : <% $reason->reason %></OPTION>
+% }
+% if ($curuser->access_right($add_access_right)) {
+ <OPTION VALUE="-1" <% ($init_reason == -1) ? 'SELECTED' : '' %>>Add new reason</OPTION>
+% }
+%
+ </SELECT>
+ </TD>
+</TR>
+
+% my @types = qsearch( 'reason_type', { 'class' => $class } );
+% if (scalar(@types) < 1) { # we should never reach this
+<TR>
+ <TD ALIGN="right">
+ <P>No reason types. Go add some. </P>
+ </TD>
+</TR>
+% }elsif (scalar(@types) == 1) {
+<TR>
+ <TD ALIGN="right">
+ <P id="new<% $name %>TLabel" style="display:<% $display %>">Reason Type</P>
+ </TD>
+ <TD>
+ <P id="new<% $name %>T" disabled="<% $disabled %>" style="display:<% $display %>"><% $types[0]->type %>
+ <INPUT type="hidden" name="new<% $name %>T" value="<% $types[0]->typenum %>">
+ </TD>
+</TR>
+
+% }else{
+
+<TR>
+ <TD ALIGN="right">
+ <P id="new<% $id %>TLabel" style="display:<% $display %>">Reason Type</P>
+ </TD>
+ <TD>
+ <SELECT id="new<% $id %>T" name="new<% $name %>T" "<% $disabled %>" style="display:<% $display %>">
+% for my $type (@types) {
+ <OPTION VALUE="<% $type->typenum %>" <% ($init_type == $type->typenum) ? 'SELECTED' : '' %>><% $type->type %></OPTION>
+% }
+ </SELECT>
+ </TD>
+</TR>
+% }
+
+<TR>
+ <TD ALIGN="right">
+ <P id="new<% $id %>Label" style="display:<% $display %>">New Reason</P>
+ </TD>
+ <TD><INPUT id="new<% $id %>" name="new<% $name %>" type="text" value="<% $init_newreason |h %>" "<% $disabled %>" style="display:<% $display %>"></TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $name = $opt{'field'};
+my $class = $opt{'reason_class'};
+
+my $init_reason;
+if ( $opt{'cgi'} ) {
+ $init_reason = $opt{'cgi'}->param($name);
+} else {
+ $init_reason = $opt{'curr_value'};
+}
+
+my $controlledbutton = $opt{'control_button'};
+
+( my $func_suffix = $name ) =~ s/\./_/g;
+
+my $id = $opt{'id'} || $func_suffix;
+
+my( $add_access_right, $access_right );
+if ($class eq 'C') {
+ $access_right = 'Cancel customer';
+ $add_access_right = 'Add on-the-fly cancel reason';
+} elsif ($class eq 'S') {
+ $access_right = 'Suspend customer package';
+ $add_access_right = 'Add on-the-fly suspend reason';
+} elsif ($class eq 'R') {
+ $access_right = 'Post credit';
+ $add_access_right = 'Add on-the-fly credit reason';
+} else {
+ die "illegal class: $class";
+}
+
+my( $display, $disabled ) = ( 'none', 'DISABLED' );
+my( $init_type, $init_newreason ) = ( '', '' );
+if ($init_reason == -1 || ref($init_reason) ) {
+
+ $display = 'inline';
+ $disabled = '';
+
+ if ( ref($init_reason) ) {
+ $init_type = $init_reason->{'typenum'};
+ $init_newreason = $init_reason->{'reason'};
+ $init_reason = -1;
+ } elsif ( $opt{'cgi'} ) {
+ $init_type = $opt{'cgi'}->param( "new${name}T" );
+ $init_newreason = $opt{'cgi'}->param( "new$name" );
+ }
+
+}
+
+my $extra_sql =
+ "WHERE class = '$class' and (disabled = '' OR disabled is NULL)";
+
+my @reasons = qsearch({
+ table => 'reason',
+ hashref => {},
+ extra_sql => $extra_sql,
+ addl_from => 'LEFT JOIN reason_type '.
+ ' ON reason_type.typenum = reason.reason_type',
+ order_by => 'ORDER BY reason_type.type ASC, reason.reason ASC',
+});
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+</%init>
diff --git a/httemplate/elements/tr-select-svc-domain.html b/httemplate/elements/tr-select-svc-domain.html
new file mode 100644
index 0000000..437bc58
--- /dev/null
+++ b/httemplate/elements/tr-select-svc-domain.html
@@ -0,0 +1,34 @@
+%if ( $columnflag eq 'F' ) {
+ <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>">
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Domain' %></TD>
+ <TD>
+ <% include('/elements/select-svc-domain.html',
+ 'curr_value' => $domsvc,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+<%init>
+
+my %opt = @_;
+
+my $domsvc = $opt{'curr_value'};
+
+#required
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+my $columnflag = $part_svc->part_svc_column('domsvc')->columnflag;
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-svc_acct-domain.html b/httemplate/elements/tr-select-svc_acct-domain.html
new file mode 100644
index 0000000..9d1a4b6
--- /dev/null
+++ b/httemplate/elements/tr-select-svc_acct-domain.html
@@ -0,0 +1,34 @@
+%if ( $columnflag eq 'F' ) {
+ <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>">
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Domain' %></TD>
+ <TD>
+ <% include('/elements/select-svc_acct-domain.html',
+ 'curr_value' => $domsvc,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+<%init>
+
+my %opt = @_;
+
+my $domsvc = $opt{'curr_value'};
+
+#required
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+my $columnflag = $part_svc->part_svc_column('domsvc')->columnflag;
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-svc_pbx.html b/httemplate/elements/tr-select-svc_pbx.html
new file mode 100644
index 0000000..b02bd65
--- /dev/null
+++ b/httemplate/elements/tr-select-svc_pbx.html
@@ -0,0 +1,60 @@
+%if ( $columnflag eq 'F' || !keys(%svc_pbx) ) {
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'pbxsvc' %>" VALUE="<% $pbxsvc %>">
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'PBX' %></TD>
+ <TD>
+ <% include('/elements/select-svc_pbx.html',
+ 'curr_value' => $pbxsvc,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+<%init>
+
+# false laziness w/tr-select-svc_acct-domain.html
+
+my %opt = @_;
+
+my $pbxsvc = $opt{'curr_value'};
+
+#required
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+my $svcpart =
+ $part_svc ? $part_svc->svcpart : '';
+my $columnflag =
+ $part_svc ? $part_svc->part_svc_column('pbxsvc')->columnflag : '';
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+# false laziness w/select-svc_pbx.html
+
+my $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+
+my %svc_pbx = ();
+
+if ( $pbxsvc ) {
+ my $svc_pbx = qsearchs('svc_pbx', { 'svcnum' => $pbxsvc } );
+ if ( $svc_pbx ) {
+ $svc_pbx{$svc_pbx->svcnum} = $svc_pbx;
+ } else {
+ warn "unknown svc_pbx.svcnum for svc_acct.pbxsvc: $pbxsvc";
+ }
+}
+
+%svc_pbx = (
+ %svc_pbx,
+ FS::svc_Common->pbx_select_hash( 'svcpart' => $svcpart,
+ 'pkgnum' => $pkgnum,
+ )
+);
+
+</%init>
diff --git a/httemplate/elements/tr-select-table.html b/httemplate/elements/tr-select-table.html
new file mode 100644
index 0000000..6ac7487
--- /dev/null
+++ b/httemplate/elements/tr-select-table.html
@@ -0,0 +1,20 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+% }
+
+ <% include( '/elements/select-table.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+ </TD>
+ </TR>
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-select-taxclass.html b/httemplate/elements/tr-select-taxclass.html
new file mode 100644
index 0000000..97f3cad
--- /dev/null
+++ b/httemplate/elements/tr-select-taxclass.html
@@ -0,0 +1,38 @@
+% if ( ! $conf->exists('enable_taxclasses')
+% || scalar(@{ $opt{'taxclasses'} }) == 0
+% ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>" VALUE="<% $selected_taxclass %>">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Tax class: ' %></TD>
+ <TD>
+ <% include( '/elements/select-taxclass.html',
+ 'curr_value' => $selected_taxclass,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+<%init>
+
+my( %opt ) = @_;
+my $conf = new FS::Conf;
+my $selected_taxclass = $opt{'curr_value'}; # || $opt{'value'} necessary?
+
+unless ( $opt{'taxclasses'} ) {
+
+ #my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+ my $sth = dbh->prepare("SELECT taxclass FROM part_pkg_taxclass WHERE disabled IS NULL OR disabled = '' OR taxclass = ?")
+ or die dbh->errstr;
+ $sth->execute($selected_taxclass) or die $sth->errstr;
+ my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
+ @{ $opt{'taxclasses'} } = grep $_, keys %taxclasses;
+
+}
+
+</%init>
diff --git a/httemplate/elements/tr-select-taxoverride.html b/httemplate/elements/tr-select-taxoverride.html
new file mode 100644
index 0000000..e20d37e
--- /dev/null
+++ b/httemplate/elements/tr-select-taxoverride.html
@@ -0,0 +1,18 @@
+% if ( $conf->exists('enable_taxproducts') ) {
+ <%include('tr-td-label.html', @_) %>
+ <TD <% $cell_style %>><% include('select-taxoverride.html', @_) %></TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $opt{value} %>">
+% }
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my %opt = @_;
+my $cell_style = $opt{'cell_style'}? 'STYLE="'. $opt{cell_style}. '"' : '';
+my $name = $opt{element_name} || $opt{field} || 'tax_override';
+
+</%init>
diff --git a/httemplate/elements/tr-select-taxproduct.html b/httemplate/elements/tr-select-taxproduct.html
new file mode 100644
index 0000000..759d0c01
--- /dev/null
+++ b/httemplate/elements/tr-select-taxproduct.html
@@ -0,0 +1,18 @@
+% if ( $conf->exists('enable_taxproducts') ) {
+ <%include('tr-td-label.html', @_) %>
+ <TD <% $cell_style %>><% include('select-taxproduct.html', @_) %></TD>
+ </TR>
+
+% } else {
+ <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $opt{value} %>">
+% }
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my %opt = @_;
+my $cell_style = $opt{cell_style} ? 'STYLE="'. $opt{cell_style}. '"' : '';
+my $name = $opt{element_name} || $opt{field} || 'taxproductnum';
+
+</%init>
diff --git a/httemplate/elements/tr-select-user.html b/httemplate/elements/tr-select-user.html
new file mode 100644
index 0000000..a9572af
--- /dev/null
+++ b/httemplate/elements/tr-select-user.html
@@ -0,0 +1,10 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Employee: ' %></TD>
+ <TD><% include('select-user.html', %opt) %></TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/tr-select.html b/httemplate/elements/tr-select.html
new file mode 100644
index 0000000..cf1b3cc
--- /dev/null
+++ b/httemplate/elements/tr-select.html
@@ -0,0 +1,22 @@
+% unless ( $opt{'js_only'} ) {
+
+ <% include('tr-td-label.html', %opt ) %>
+
+ <TD <% $colspan %> <% $style %>>
+% }
+
+ <% include('select.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+ </TD>
+ </TR>
+% }
+<%init>
+
+my %opt = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-selectlayers-select.html b/httemplate/elements/tr-selectlayers-select.html
new file mode 100644
index 0000000..af81336
--- /dev/null
+++ b/httemplate/elements/tr-selectlayers-select.html
@@ -0,0 +1 @@
+<% include('tr-selectlayers.html', @_, 'select_only'=>1 ) %>
diff --git a/httemplate/elements/tr-selectlayers.html b/httemplate/elements/tr-selectlayers.html
new file mode 100644
index 0000000..865d822
--- /dev/null
+++ b/httemplate/elements/tr-selectlayers.html
@@ -0,0 +1,25 @@
+% unless ( $opt{js_only} ) {
+
+ <% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $style %>>
+
+% }
+
+ <% include('selectlayers.html', @_ ) %>
+
+% unless ( $opt{js_only} ) {
+
+ </TD>
+
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-selectmultiple-part_pkg.html b/httemplate/elements/tr-selectmultiple-part_pkg.html
new file mode 100644
index 0000000..d959a5b
--- /dev/null
+++ b/httemplate/elements/tr-selectmultiple-part_pkg.html
@@ -0,0 +1,19 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Packages' %></TD>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg',
+ 'name_col' => 'pkg',
+ 'disable_empty' => 1,
+ 'element_etc' => 'multiple',
+ %opt,
+ )
+ %>
+ </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/tr-td-label.html b/httemplate/elements/tr-td-label.html
new file mode 100644
index 0000000..77c0484
--- /dev/null
+++ b/httemplate/elements/tr-td-label.html
@@ -0,0 +1,17 @@
+<TR>
+
+ <TD ALIGN="right" VALIGN="top" STYLE="<% $style %>" ID="<% $opt{label_id} || $opt{id}. '_label0' %>">
+
+ <% $opt{label} %>
+
+ </TD>
+
+<%init>
+
+my %opt = @_;
+
+my $style = 'padding-top: 3px';
+$style .= '; '. $opt{'cell_style'}
+ if $opt{'cell_style'};
+
+</%init>
diff --git a/httemplate/elements/tr-textarea.html b/httemplate/elements/tr-textarea.html
new file mode 100644
index 0000000..ae2ef81
--- /dev/null
+++ b/httemplate/elements/tr-textarea.html
@@ -0,0 +1,30 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $cell_style %>>
+
+ <TEXTAREA NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ <% $rows %>
+ <% $cols %>
+ <% $onchange %>
+ ><% $curr_value |h %></TEXTAREA>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $rows = $opt{'rows'} ? 'ROWS="'.$opt{'rows'}.'"' : '';
+my $cols = $opt{'cols'} ? 'COLS="'.$opt{'cols'}.'"' : '';
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+my $curr_value = $opt{'curr_value'};
+
+</%init>
diff --git a/httemplate/elements/tr-title.html b/httemplate/elements/tr-title.html
new file mode 100644
index 0000000..af37341
--- /dev/null
+++ b/httemplate/elements/tr-title.html
@@ -0,0 +1,10 @@
+<TR>
+ <TD CLASS="background" COLSPAN=<% $opt{colspan} || 2 %>>&nbsp;</TD>
+</TR>
+
+<% include('tr-justtitle.html', @_) %>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css
new file mode 100644
index 0000000..73699b6
--- /dev/null
+++ b/httemplate/elements/xmenu.css
@@ -0,0 +1,166 @@
+
+.webfx-menu, .webfx-menu * {
+ /*
+ Set the box sizing to content box
+ in the future when IE6 supports box-sizing
+ there will be an issue to fix the sizes
+
+ There is probably an issue with IE5 mac now
+ because IE5 uses content-box but the script
+ assumes all versions of IE uses border-box.
+
+ At the time of this writing mozilla did not support
+ box-sizing for absolute positioned element.
+
+ Opera only supports content-box
+ */
+ box-sizing: content-box;
+ -moz-box-sizing: content-box;
+}
+
+.webfx-menu {
+ position: absolute;
+ z-index: 100;
+ visibility: hidden;
+ border: 1px solid #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ padding: 1px;
+ background: white;
+ -moz-box-shadow: #000000 1px 1px 4px;
+ -webkit-box-shadow: #000000 1px 1px 4px;
+ box-shadow: #000000 1px 1px 4px;
+}
+
+.webfx-menu-empty {
+ display: block;
+ border: 1px solid white;
+ padding: 2px 5px 2px 5px;
+ font-size: 11px;
+ /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */
+ color: black;
+}
+
+.webfx-menu a {
+ display: block;
+ /* width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+ overflow: visible;
+ /* padding: 2px 0px 2px 5px; */
+ padding: 1px 0px 1px 5px;
+ font-size: 14px;
+/* font-family: Verdana, Arial, Helvetica, sans-serif; */
+ font-weight: bold;
+ text-decoration: none;
+ vertical-align: center;
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:visited {
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:hover {
+ border: 1px solid #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ background: #fff8fe;
+}
+
+.webfx-menu a[href]:hover {
+ color: black;
+ text-decoration: none;
+}
+
+.webfx-menu a .arrow {
+ float: right;
+ border: 0;
+ width: 3px;
+ margin-right: 3px;
+ margin-top: 4px;
+}
+
+/* separtor */
+.webfx-menu div {
+ height: 0;
+ height: expression(constExpression(ieBox ? "2px" : "0"));
+ border-top: 1px solid #7e0079; /* rgb(120,172,255); */
+ border-bottom: 1px solid rgb(234,242,255);
+ overflow: hidden;
+ margin: 2px 0px 2px 0px;
+ font-size: 0mm;
+}
+
+.webfx-menu-bar {
+ /* i want a vertical bar */
+ display: block;
+
+ /* background: rgb(120,172,255);/*rgb(255,128,0);*/
+ /* background: #a097ed; */
+ background: #cccccc;
+ /* border: 1px solid #7E0079; */
+ /* border: 1px solid #000000; */
+ /* border: none */
+ color: white;
+
+ padding: 2px;
+
+ /* IE5.0 has the wierdest box model for inline elements */
+ padding: expression(constExpression(ie50 ? "0px" : "2px"));
+}
+
+.webfx-menu-bar a,
+.webfx-menu-bar a:visited {
+ /* i want a vertical bar */
+ display: block;
+
+
+ padding: 1px 5px 1px 5px;
+
+ font-size: 14px;
+
+ color: black;
+ /* color: white; */
+ text-decoration: none;
+
+ /* IE5.0 Does not paint borders and padding on inline elements without a height/width */
+ height: expression(constExpression(ie50 ? "17px" : "auto"));
+}
+
+.webfx-menu-bar a:link {
+ /* color: white; */
+ color: black;
+}
+
+.webfx-menu-bar a:hover {
+ text-decoration: underline;
+ color: #7e0079;
+}
+
+.webfx-menu-bar a .arrow {
+ float: right;
+ border: 0;
+/* vertical-align: top; */
+ width: 3px;
+ margin-right: 3px;
+ margin-top: 4px;
+}
+
+.webfx-menu-bar a:active, .webfx-menu-bar a:focus {
+ -moz-outline: none;
+ outline: none;
+ /*
+ ie does not support outline but ie55 can hide the outline using
+ a proprietary property on HTMLElement. Did I say that IE sucks at CSS?
+ */
+ ie-dummy: expression(this.hideFocus=true);
+}
+
+.webfx-menu-title {
+ display: block;
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+}
diff --git a/httemplate/elements/xmenu.js b/httemplate/elements/xmenu.js
new file mode 100644
index 0000000..3ef9a3d
--- /dev/null
+++ b/httemplate/elements/xmenu.js
@@ -0,0 +1,668 @@
+//<script>
+/*
+ * This script was created by Erik Arvidsson (erik@eae.net)
+ * for WebFX (http://webfx.eae.net)
+ * Copyright 2001
+ *
+ * For usage see license at http://webfx.eae.net/license.html
+ *
+ * Created: 2001-01-12
+ * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks
+ * 2001-12-20 Added auto positioning and some properties to support this
+ * 2002-08-13 toString used ' for attributes. Changed to " to allow in args
+ */
+
+// check browsers
+var ua = navigator.userAgent;
+var opera = /opera [56789]|opera\/[56789]/i.test(ua);
+var ie = !opera && /MSIE/.test(ua);
+var ie50 = ie && /MSIE 5\.[01234]/.test(ua);
+var ie6 = ie && /MSIE [6789]/.test(ua);
+var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat");
+var moz = !opera && /gecko/i.test(ua);
+var nn6 = !opera && /netscape.*6\./i.test(ua);
+var khtml = /KHTML/i.test(ua);
+
+// define the default values
+
+webfxMenuDefaultWidth = 154;
+
+webfxMenuDefaultBorderLeft = 1;
+webfxMenuDefaultBorderRight = 1;
+webfxMenuDefaultBorderTop = 1;
+webfxMenuDefaultBorderBottom = 1;
+
+webfxMenuDefaultPaddingLeft = 1;
+webfxMenuDefaultPaddingRight = 1;
+webfxMenuDefaultPaddingTop = 1;
+webfxMenuDefaultPaddingBottom = 1;
+
+webfxMenuDefaultShadowLeft = 0;
+webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0;
+webfxMenuDefaultShadowTop = 0;
+webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0;
+
+
+webfxMenuItemDefaultHeight = 18;
+webfxMenuItemDefaultText = "Untitled";
+webfxMenuItemDefaultHref = "javascript:void(0)";
+
+webfxMenuSeparatorDefaultHeight = 6;
+
+webfxMenuDefaultEmptyText = "Empty";
+
+webfxMenuDefaultUseAutoPosition = nn6 ? false : true;
+
+
+
+// other global constants
+
+webfxMenuImagePath = "";
+
+webfxMenuUseHover = opera ? true : false;
+webfxMenuHideTime = 500;
+webfxMenuShowTime = 200;
+
+
+
+var webFXMenuHandler = {
+ idCounter : 0,
+ idPrefix : "webfx-menu-object-",
+ all : {},
+ getId : function () { return this.idPrefix + this.idCounter++; },
+ overMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuShowTime <= 0)
+ this._over(jsItem);
+ else if ( jsItem )
+ //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime);
+ // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object
+ this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime);
+ },
+ outMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuHideTime <= 0)
+ this._out(jsItem);
+ else if ( jsItem )
+ //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime);
+ this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime);
+ },
+ blurMenu : function (oMenuItem) {
+ window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime);
+ },
+ _over : function (jsItem) {
+ if (jsItem.subMenu) {
+ jsItem.parentMenu.hideAllSubs();
+ jsItem.subMenu.show();
+ }
+ else
+ jsItem.parentMenu.hideAllSubs();
+ },
+ _out : function (jsItem) {
+ // find top most menu
+ var root = jsItem;
+ var m;
+ if (root instanceof WebFXMenuButton)
+ m = root.subMenu;
+ else {
+ m = jsItem.parentMenu;
+ while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar))
+ m = m.parentMenu;
+ }
+ if (m != null)
+ m.hide();
+ },
+ hideMenu : function (menu) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime);
+ },
+ showMenu : function (menu, src, dir) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ if (arguments.length < 3)
+ dir = "vertical";
+
+ menu.show(src, dir);
+ }
+};
+
+function WebFXMenu() {
+ this._menuItems = [];
+ this._subMenus = [];
+ this.id = webFXMenuHandler.getId();
+ this.top = 0;
+ this.left = 0;
+ this.shown = false;
+ this.parentMenu = null;
+ webFXMenuHandler.all[this.id] = this;
+}
+
+WebFXMenu.prototype.width = webfxMenuDefaultWidth;
+WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText;
+WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition;
+
+WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft;
+WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight;
+WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop;
+WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom;
+
+WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft;
+WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight;
+WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop;
+WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom;
+
+WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft;
+WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight;
+WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop;
+WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom;
+
+
+
+WebFXMenu.prototype.add = function (menuItem) {
+ this._menuItems[this._menuItems.length] = menuItem;
+ if (menuItem.subMenu) {
+ this._subMenus[this._subMenus.length] = menuItem.subMenu;
+ menuItem.subMenu.parentMenu = this;
+ }
+
+ menuItem.parentMenu = this;
+};
+
+WebFXMenu.prototype.show = function (relObj, sDir) {
+ if (this.useAutoPosition)
+ this.position(relObj, sDir);
+
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+
+ divElement.style.left = opera ? this.left : this.left + "px";
+ divElement.style.top = opera ? this.top : this.top + "px";
+ divElement.style.visibility = "visible";
+
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.width = divElement.offsetWidth;
+ shimElement.style.height = divElement.offsetHeight;
+ shimElement.style.top = divElement.style.top;
+ shimElement.style.left = divElement.style.left;
+ /*shimElement.style.zIndex = divElement.style.zIndex - 1; */
+ shimElement.style.display = "block";
+ shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
+ }
+ }
+
+ }
+
+ this.shown = true;
+
+ if (this.parentMenu)
+ this.parentMenu.show();
+};
+
+WebFXMenu.prototype.hide = function () {
+ this.hideAllSubs();
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+ divElement.style.visibility = "hidden";
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.display = "none";
+ }
+ }
+ }
+
+ this.shown = false;
+};
+
+WebFXMenu.prototype.hideAllSubs = function () {
+ for (var i = 0; i < this._subMenus.length; i++) {
+ if (this._subMenus[i].shown)
+ this._subMenus[i].hide();
+ }
+};
+
+WebFXMenu.prototype.toString = function () {
+ var top = this.top + this.borderTop + this.paddingTop;
+ var str = "<div id='" + this.id + "' class='webfx-menu' style='" +
+ "width:" + (!ieBox ?
+ this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight :
+ this.width) + "px;" +
+ (this.useAutoPosition ?
+ "left:" + this.left + "px;" + "top:" + this.top + "px;" :
+ "") +
+ (ie50 ? "filter: none;" : "") +
+ "'>";
+
+ if (this._menuItems.length == 0) {
+ str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>";
+ }
+ else {
+ str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ '>' + this.emptyText + '</span>';
+ // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>';
+ // loop through all menuItems
+ for (var i = 0; i < this._menuItems.length; i++) {
+ var mi = this._menuItems[i];
+ str += mi;
+ if (!this.useAutoPosition) {
+ if (mi.subMenu && !mi.subMenu.useAutoPosition)
+ mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop;
+ top += mi.height;
+ }
+ }
+
+ }
+
+ str += "</div>";
+
+ if ( ie ) {
+ str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>";
+ }
+
+ for (var i = 0; i < this._subMenus.length; i++) {
+ this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft;
+ str += this._subMenus[i];
+ }
+
+ return str;
+};
+// WebFXMenu.prototype.position defined later
+
+function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) {
+ this.text = sText || webfxMenuItemDefaultText;
+ this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref;
+ this.subMenu = oSubMenu;
+ if (oSubMenu)
+ oSubMenu.parentMenuItem = this;
+ this.toolTip = sToolTip;
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight;
+WebFXMenuItem.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href=\"" + this.href + "\"" +
+ (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") +
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ (this.subMenu ? " unselectable='on' tabindex='-1'" : "") +
+ ">" +
+ (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") +
+ this.text +
+ "</a>";
+};
+
+
+function WebFXMenuSeparator() {
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight;
+WebFXMenuSeparator.prototype.toString = function () {
+ return "<div" +
+ " id='" + this.id + "'" +
+ (webfxMenuUseHover ?
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'"
+ :
+ "") +
+ "></div>"
+};
+
+function WebFXMenuBar() {
+ this._parentConstructor = WebFXMenu;
+ this._parentConstructor();
+}
+WebFXMenuBar.prototype = new WebFXMenu;
+WebFXMenuBar.prototype.toString = function () {
+ var str = "<div id='" + this.id + "' class='webfx-menu-bar'>";
+
+ // loop through all menuButtons
+ for (var i = 0; i < this._menuItems.length; i++)
+ str += this._menuItems[i];
+
+ str += "</div>";
+
+ for (var i = 0; i < this._subMenus.length; i++)
+ str += this._subMenus[i];
+
+ return str;
+};
+
+function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) {
+ this._parentConstructor = WebFXMenuItem;
+ this._parentConstructor(sText, sHref, sToolTip, oSubMenu);
+}
+WebFXMenuButton.prototype = new WebFXMenuItem;
+WebFXMenuButton.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href='" + this.href + "'" +
+ (this.toolTip ? " title='" + this.toolTip + "'" : "") +
+ (webfxMenuUseHover ?
+ (" onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'") :
+ (
+ " onfocus='webFXMenuHandler.overMenuItem(this)'" +
+ (this.subMenu ?
+ " onblur='webFXMenuHandler.blurMenu(this)'" :
+ ""
+ )
+ )) +
+ ">" +
+ (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.right.black.png'>" : "") +
+ this.text +
+ "</a>";
+};
+
+
+
+
+
+/* Position functions */
+
+
+function getInnerLeft(el) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) );
+
+}
+
+
+
+function getLeft(el, debug) {
+
+ if (el == null) return 0;
+
+ //if ( debug )
+ // alert ( el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) );
+
+ return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) );
+
+}
+
+
+
+function getInnerTop(el) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ return parseInt( getTop(el) + parseInt(getBorderTop(el)) );
+
+}
+
+
+
+function getTop(el) {
+
+ if (el == null) return 0;
+
+ return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) );
+
+}
+
+
+
+function getBorderLeft(el) {
+
+ return ie ?
+
+ el.clientLeft :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ );
+
+}
+
+
+
+function getBorderTop(el) {
+
+ return ie ?
+
+ el.clientTop :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width"))
+ );
+
+}
+
+
+
+function opera_getLeft(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetLeft + opera_getLeft(el.offsetParent);
+
+}
+
+
+
+function opera_getTop(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetTop + opera_getTop(el.offsetParent);
+
+}
+
+
+
+function getOuterRect(el, debug) {
+
+ return {
+
+ left: (opera ? opera_getLeft(el) : getLeft(el, debug)),
+
+ top: (opera ? opera_getTop(el) : getTop(el)),
+
+ width: el.offsetWidth,
+
+ height: el.offsetHeight
+
+ };
+
+}
+
+
+
+// mozilla bug! scrollbars not included in innerWidth/height
+
+function getDocumentRect(el) {
+
+ return {
+
+ left: 0,
+
+ top: 0,
+
+ width: (ie ?
+
+ (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) :
+
+ window.innerWidth
+
+ ),
+
+ height: (ie ?
+
+ (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) :
+
+ window.innerHeight
+
+ )
+
+ };
+
+}
+
+
+
+function getScrollPos(el) {
+
+ return {
+
+ left: (ie ?
+
+ (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) :
+
+ window.pageXOffset
+
+ ),
+
+ top: (ie ?
+
+ (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) :
+
+ window.pageYOffset
+
+ )
+
+ };
+
+}
+
+
+/* end position functions */
+
+WebFXMenu.prototype.position = function (relEl, sDir) {
+ var dir = sDir;
+ // find parent item rectangle, piRect
+ var piRect;
+ if (!relEl) {
+ var pi = this.parentMenuItem;
+ if (!this.parentMenuItem)
+ return;
+
+ relEl = document.getElementById(pi.id);
+ if (dir == null)
+ dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal";
+ //alert('created RelEl from parent: ' + pi.id);
+ piRect = getOuterRect(relEl, 1);
+ }
+ else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect
+ //alert('passed a Rect as RelEl: ' + typeof(relEl));
+
+ piRect = relEl;
+ }
+ else {
+ //alert('passed an element as RelEl: ' + typeof(relEl));
+ piRect = getOuterRect(relEl);
+ }
+
+ var menuEl = document.getElementById(this.id);
+ var menuRect = getOuterRect(menuEl);
+ var docRect = getDocumentRect();
+ var scrollPos = getScrollPos();
+ var pMenu = this.parentMenu;
+
+ if (dir == "vertical") {
+ if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) {
+ //alert('piRect.left: ' + piRect.left);
+ this.left = piRect.left;
+ if ( ! ie )
+ this.left = this.left + 138;
+ } else if (docRect.width >= menuRect.width) {
+ //konq (not safari though) winds up here by accident and positions the menus all weird
+ //alert('docRect.width + scrollPos.left - menuRect.width');
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+ } else {
+ //alert('scrollPos.left: ' + scrollPos.left);
+ this.left = scrollPos.left;
+ }
+
+ if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top + piRect.height;
+
+ else if (piRect.top - menuRect.height >= scrollPos.top)
+
+ this.top = piRect.top - menuRect.height;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+ }
+ else {
+ if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top - this.borderTop - this.paddingTop;
+
+ else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0)
+
+ this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+
+
+
+ var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0;
+
+ var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0;
+
+ var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0;
+
+ var pMenuBorderRight = pMenu ? pMenu.borderRight : 0;
+
+
+
+ if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight +
+
+ pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left)
+
+ this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft;
+
+ else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0)
+
+ this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight;
+
+ else if (docRect.width >= menuRect.width)
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+
+ else
+
+ this.left = scrollPos.left;
+ }
+};
diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css
new file mode 100644
index 0000000..ff0d6f6
--- /dev/null
+++ b/httemplate/elements/xmenu.top.css
@@ -0,0 +1,168 @@
+
+.webfx-menu, .webfx-menu * {
+ /*
+ Set the box sizing to content box
+ in the future when IE6 supports box-sizing
+ there will be an issue to fix the sizes
+
+ There is probably an issue with IE5 mac now
+ because IE5 uses content-box but the script
+ assumes all versions of IE uses border-box.
+
+ At the time of this writing mozilla did not support
+ box-sizing for absolute positioned element.
+
+ Opera only supports content-box
+ */
+ box-sizing: content-box;
+ -moz-box-sizing: content-box;
+}
+
+.webfx-menu {
+ position: absolute;
+ z-index: 100;
+ visibility: hidden;
+ border: 1px solid #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ padding: 1px;
+ background: white;
+ -moz-box-shadow: #000000 1px 1px 4px;
+ -webkit-box-shadow: #000000 1px 1px 4px;
+ box-shadow: #000000 1px 1px 4px;
+}
+
+.webfx-menu-empty {
+ display: block;
+ border: 1px solid white;
+ padding: 2px 5px 2px 5px;
+ font-size: 11px;
+ /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */
+ color: black;
+}
+
+.webfx-menu a {
+ display: block;
+ /* width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+ overflow: visible;
+ /* padding: 2px 0px 2px 5px; */
+ padding: 1px 0px 1px 5px;
+ font-size: 14px;
+/* font-family: Verdana, Arial, Helvetica, sans-serif; */
+ font-weight: bold;
+ text-decoration: none;
+ vertical-align: center;
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:visited {
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:hover {
+ border: 1px solid #7e0079;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ background: #fff8fe;
+}
+
+.webfx-menu a[href]:hover {
+ color: black;
+ text-decoration: none;
+}
+
+.webfx-menu a .arrow {
+ float: right;
+ border: 0;
+ width: 3px;
+ margin-right: 3px;
+ margin-top: 4px;
+}
+
+/* separtor */
+.webfx-menu div {
+ height: 0;
+ height: expression(constExpression(ieBox ? "2px" : "0"));
+ border-top: 1px solid #7e0079; /* rgb(120,172,255); */
+ border-bottom: 1px solid rgb(234,242,255);
+ overflow: hidden;
+ margin: 2px 0px 2px 0px;
+ font-size: 0mm;
+}
+
+.webfx-menu-bar {
+ /* background: rgb(120,172,255);/*rgb(255,128,0);*/
+ /* background: #a097ed; */
+ background: #cccccc;
+ /* border: 1px solid #7E0079; */
+ /* border: 1px solid #000000; */
+ /* border: none */
+ color: white;
+
+ padding: 2px;
+
+ /* IE5.0 has the wierdest box model for inline elements */
+ padding: expression(constExpression(ie50 ? "0px" : "2px"));
+}
+
+.webfx-menu-bar a,
+.webfx-menu-bar a:visited {
+ /* i want a vertical bar */
+ /* display: block; */
+
+ padding: 1px 5px 1px 5px;
+
+ font-size: 16px;
+
+ color: black;
+ /* color: white; */
+ text-decoration: none;
+
+ /* IE5.0 Does not paint borders and padding on inline elements without a height/width */
+ height: expression(constExpression(ie50 ? "17px" : "auto"));
+
+ border: 1px solid #cccccc;
+
+ margin-right: 4px
+
+}
+
+.webfx-menu-bar a:link {
+ /* color: white; */
+ color: black;
+}
+
+.webfx-menu-bar a:hover {
+ text-decoration: underline;
+ color: #7e0079;
+}
+
+.webfx-menu-bar a .arrow {
+ /* float: right; */
+ border: 0;
+/* vertical-align: top; */
+/* width: 3px; */
+/* margin-right: 3px; */
+ margin-bottom: 2px;
+
+}
+
+.webfx-menu-bar a:active, .webfx-menu-bar a:focus {
+ -moz-outline: none;
+ outline: none;
+ /*
+ ie does not support outline but ie55 can hide the outline using
+ a proprietary property on HTMLElement. Did I say that IE sucks at CSS?
+ */
+ ie-dummy: expression(this.hideFocus=true);
+}
+
+.webfx-menu-title {
+ display: block;
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+}
diff --git a/httemplate/elements/xmenu.top.js b/httemplate/elements/xmenu.top.js
new file mode 100644
index 0000000..b9df515
--- /dev/null
+++ b/httemplate/elements/xmenu.top.js
@@ -0,0 +1,671 @@
+//<script>
+/*
+ * This script was created by Erik Arvidsson (erik@eae.net)
+ * for WebFX (http://webfx.eae.net)
+ * Copyright 2001
+ *
+ * For usage see license at http://webfx.eae.net/license.html
+ *
+ * Created: 2001-01-12
+ * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks
+ * 2001-12-20 Added auto positioning and some properties to support this
+ * 2002-08-13 toString used ' for attributes. Changed to " to allow in args
+ */
+
+// check browsers
+var ua = navigator.userAgent;
+var opera = /opera [56789]|opera\/[56789]/i.test(ua);
+var ie = !opera && /MSIE/.test(ua);
+var ie50 = ie && /MSIE 5\.[01234]/.test(ua);
+var ie6 = ie && /MSIE [6789]/.test(ua);
+var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat");
+var moz = !opera && /gecko/i.test(ua);
+var nn6 = !opera && /netscape.*6\./i.test(ua);
+var khtml = /KHTML/i.test(ua);
+
+// define the default values
+
+webfxMenuDefaultWidth = 154;
+
+webfxMenuDefaultBorderLeft = 1;
+webfxMenuDefaultBorderRight = 1;
+webfxMenuDefaultBorderTop = 1;
+webfxMenuDefaultBorderBottom = 1;
+
+webfxMenuDefaultPaddingLeft = 1;
+webfxMenuDefaultPaddingRight = 1;
+webfxMenuDefaultPaddingTop = 1;
+webfxMenuDefaultPaddingBottom = 1;
+
+webfxMenuDefaultShadowLeft = 0;
+webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0;
+webfxMenuDefaultShadowTop = 0;
+webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0;
+
+
+webfxMenuItemDefaultHeight = 18;
+webfxMenuItemDefaultText = "Untitled";
+webfxMenuItemDefaultHref = "javascript:void(0)";
+
+webfxMenuSeparatorDefaultHeight = 6;
+
+webfxMenuDefaultEmptyText = "Empty";
+
+webfxMenuDefaultUseAutoPosition = nn6 ? false : true;
+
+
+
+// other global constants
+
+webfxMenuImagePath = "";
+
+webfxMenuUseHover = opera ? true : false;
+webfxMenuHideTime = 500;
+webfxMenuShowTime = 200;
+
+
+
+var webFXMenuHandler = {
+ idCounter : 0,
+ idPrefix : "webfx-menu-object-",
+ all : {},
+ getId : function () { return this.idPrefix + this.idCounter++; },
+ overMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuShowTime <= 0)
+ this._over(jsItem);
+ else if ( jsItem )
+ //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime);
+ // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object
+ this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime);
+ },
+ outMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuHideTime <= 0)
+ this._out(jsItem);
+ else if ( jsItem )
+ //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime);
+ this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime);
+ },
+ blurMenu : function (oMenuItem) {
+ window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime);
+ },
+ _over : function (jsItem) {
+ if (jsItem.subMenu) {
+ jsItem.parentMenu.hideAllSubs();
+ jsItem.subMenu.show();
+ }
+ else
+ jsItem.parentMenu.hideAllSubs();
+ },
+ _out : function (jsItem) {
+ // find top most menu
+ var root = jsItem;
+ var m;
+ if (root instanceof WebFXMenuButton)
+ m = root.subMenu;
+ else {
+ m = jsItem.parentMenu;
+ while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar))
+ m = m.parentMenu;
+ }
+ if (m != null)
+ m.hide();
+ },
+ hideMenu : function (menu) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime);
+ },
+ showMenu : function (menu, src, dir) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ if (arguments.length < 3)
+ dir = "vertical";
+
+ menu.show(src, dir);
+ }
+};
+
+function WebFXMenu() {
+ this._menuItems = [];
+ this._subMenus = [];
+ this.id = webFXMenuHandler.getId();
+ this.top = 0;
+ this.left = 0;
+ this.shown = false;
+ this.parentMenu = null;
+ webFXMenuHandler.all[this.id] = this;
+}
+
+WebFXMenu.prototype.width = webfxMenuDefaultWidth;
+WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText;
+WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition;
+
+WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft;
+WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight;
+WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop;
+WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom;
+
+WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft;
+WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight;
+WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop;
+WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom;
+
+WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft;
+WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight;
+WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop;
+WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom;
+
+
+
+WebFXMenu.prototype.add = function (menuItem) {
+ this._menuItems[this._menuItems.length] = menuItem;
+ if (menuItem.subMenu) {
+ this._subMenus[this._subMenus.length] = menuItem.subMenu;
+ menuItem.subMenu.parentMenu = this;
+ }
+
+ menuItem.parentMenu = this;
+};
+
+WebFXMenu.prototype.show = function (relObj, sDir) {
+ if (this.useAutoPosition)
+ this.position(relObj, sDir);
+
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+
+ divElement.style.left = opera ? this.left : this.left + "px";
+ divElement.style.top = opera ? this.top : this.top + "px";
+ divElement.style.visibility = "visible";
+
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.width = divElement.offsetWidth;
+ shimElement.style.height = divElement.offsetHeight;
+ shimElement.style.top = divElement.style.top;
+ shimElement.style.left = divElement.style.left;
+ /*shimElement.style.zIndex = divElement.style.zIndex - 1; */
+ shimElement.style.display = "block";
+ shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
+ }
+ }
+
+ }
+
+ this.shown = true;
+
+ if (this.parentMenu)
+ this.parentMenu.show();
+};
+
+WebFXMenu.prototype.hide = function () {
+ this.hideAllSubs();
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+ divElement.style.visibility = "hidden";
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.display = "none";
+ }
+ }
+ }
+
+ this.shown = false;
+};
+
+WebFXMenu.prototype.hideAllSubs = function () {
+ for (var i = 0; i < this._subMenus.length; i++) {
+ if (this._subMenus[i].shown)
+ this._subMenus[i].hide();
+ }
+};
+
+WebFXMenu.prototype.toString = function () {
+ var top = this.top + this.borderTop + this.paddingTop;
+ var str = "<div id='" + this.id + "' class='webfx-menu' style='" +
+ "width:" + (!ieBox ?
+ this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight :
+ this.width) + "px;" +
+ (this.useAutoPosition ?
+ "left:" + this.left + "px;" + "top:" + this.top + "px;" :
+ "") +
+ (ie50 ? "filter: none;" : "") +
+ "'>";
+
+ if (this._menuItems.length == 0) {
+ str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>";
+ }
+ else {
+ str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ '>' + this.emptyText + '</span>';
+ // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>';
+ // loop through all menuItems
+ for (var i = 0; i < this._menuItems.length; i++) {
+ var mi = this._menuItems[i];
+ str += mi;
+ if (!this.useAutoPosition) {
+ if (mi.subMenu && !mi.subMenu.useAutoPosition)
+ mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop;
+ top += mi.height;
+ }
+ }
+
+ }
+
+ str += "</div>";
+
+ if ( ie ) {
+ str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>";
+ }
+
+ for (var i = 0; i < this._subMenus.length; i++) {
+ this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft;
+ str += this._subMenus[i];
+ }
+
+ return str;
+};
+// WebFXMenu.prototype.position defined later
+
+function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) {
+ this.text = sText || webfxMenuItemDefaultText;
+ this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref;
+ this.subMenu = oSubMenu;
+ if (oSubMenu)
+ oSubMenu.parentMenuItem = this;
+ this.toolTip = sToolTip;
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight;
+WebFXMenuItem.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href=\"" + this.href + "\"" +
+ (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") +
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ (this.subMenu ? " unselectable='on' tabindex='-1'" : "") +
+ ">" +
+ (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") +
+ this.text +
+ "</a>";
+};
+
+
+function WebFXMenuSeparator() {
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight;
+WebFXMenuSeparator.prototype.toString = function () {
+ return "<div" +
+ " id='" + this.id + "'" +
+ (webfxMenuUseHover ?
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'"
+ :
+ "") +
+ "></div>"
+};
+
+function WebFXMenuBar() {
+ this._parentConstructor = WebFXMenu;
+ this._parentConstructor();
+}
+WebFXMenuBar.prototype = new WebFXMenu;
+WebFXMenuBar.prototype.toString = function () {
+ var str = "<div id='" + this.id + "' class='webfx-menu-bar'>";
+
+ // loop through all menuButtons
+ for (var i = 0; i < this._menuItems.length; i++)
+ str += this._menuItems[i];
+
+ str += "</div>";
+
+ for (var i = 0; i < this._subMenus.length; i++)
+ str += this._subMenus[i];
+
+ return str;
+};
+
+function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) {
+ this._parentConstructor = WebFXMenuItem;
+ this._parentConstructor(sText, sHref, sToolTip, oSubMenu);
+}
+WebFXMenuButton.prototype = new WebFXMenuItem;
+WebFXMenuButton.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href='" + this.href + "'" +
+ (this.toolTip ? " title='" + this.toolTip + "'" : "") +
+ (webfxMenuUseHover ?
+ (" onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'") :
+ (
+ " onfocus='webFXMenuHandler.overMenuItem(this)'" +
+ (this.subMenu ?
+ " onblur='webFXMenuHandler.blurMenu(this)'" :
+ ""
+ )
+ )) +
+ ">" +
+ this.text +
+ (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.down.black.png'>" : "") +
+ "</a>";
+};
+
+
+
+
+
+/* Position functions */
+
+
+function getInnerLeft(el, debug) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ //if ( debug )
+ // alert ( 'getInnerLeft: ' + getLeft(el) + ' - ' + getBorderLeft(el) );
+
+ return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) );
+
+}
+
+
+
+function getLeft(el, debug) {
+
+ if (el == null) return 0;
+
+ //if ( debug )
+ // alert ( el + ': ' + el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) );
+
+ return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) );
+
+}
+
+
+
+function getInnerTop(el) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ return parseInt( getTop(el) + parseInt(getBorderTop(el)) );
+
+}
+
+
+
+function getTop(el) {
+
+ if (el == null) return 0;
+
+ return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) );
+
+}
+
+
+
+function getBorderLeft(el) {
+
+ return ie ?
+
+ el.clientLeft :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ );
+
+}
+
+
+
+function getBorderTop(el) {
+
+ return ie ?
+
+ el.clientTop :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width"))
+ );
+
+}
+
+
+
+function opera_getLeft(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetLeft + opera_getLeft(el.offsetParent);
+
+}
+
+
+
+function opera_getTop(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetTop + opera_getTop(el.offsetParent);
+
+}
+
+
+
+function getOuterRect(el, debug) {
+
+ return {
+
+ left: (opera ? opera_getLeft(el) : getLeft(el, debug)),
+
+ top: (opera ? opera_getTop(el) : getTop(el)),
+
+ width: el.offsetWidth,
+
+ height: el.offsetHeight
+
+ };
+
+}
+
+
+
+// mozilla bug! scrollbars not included in innerWidth/height
+
+function getDocumentRect(el) {
+
+ return {
+
+ left: 0,
+
+ top: 0,
+
+ width: (ie ?
+
+ (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) :
+
+ window.innerWidth
+
+ ),
+
+ height: (ie ?
+
+ (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) :
+
+ window.innerHeight
+
+ )
+
+ };
+
+}
+
+
+
+function getScrollPos(el) {
+
+ return {
+
+ left: (ie ?
+
+ (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) :
+
+ window.pageXOffset
+
+ ),
+
+ top: (ie ?
+
+ (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) :
+
+ window.pageYOffset
+
+ )
+
+ };
+
+}
+
+
+/* end position functions */
+
+WebFXMenu.prototype.position = function (relEl, sDir) {
+ var dir = sDir;
+ // find parent item rectangle, piRect
+ var piRect;
+ if (!relEl) {
+ var pi = this.parentMenuItem;
+ if (!this.parentMenuItem)
+ return;
+
+ relEl = document.getElementById(pi.id);
+ if (dir == null)
+ dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal";
+ //alert('created RelEl from parent: ' + pi.id);
+ piRect = getOuterRect(relEl, 1);
+ }
+ else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect
+ //alert('passed a Rect as RelEl: ' + typeof(relEl));
+
+ piRect = relEl;
+ }
+ else {
+ //alert('passed an element as RelEl: ' + typeof(relEl));
+ piRect = getOuterRect(relEl);
+ }
+
+ var menuEl = document.getElementById(this.id);
+ var menuRect = getOuterRect(menuEl);
+ var docRect = getDocumentRect();
+ var scrollPos = getScrollPos();
+ var pMenu = this.parentMenu;
+
+ if (dir == "vertical") {
+ if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) {
+ //alert('piRect.left: ' + piRect.left);
+ this.left = piRect.left;
+// if ( ! ie )
+// this.left = this.left + 138;
+ } else if (docRect.width >= menuRect.width) {
+ //konq (not safari though) winds up here by accident and positions the menus all weird
+ //alert('docRect.width + scrollPos.left - menuRect.width');
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+ } else {
+ //alert('scrollPos.left: ' + scrollPos.left);
+ this.left = scrollPos.left;
+ }
+
+ if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top + piRect.height;
+
+ else if (piRect.top - menuRect.height >= scrollPos.top)
+
+ this.top = piRect.top - menuRect.height;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+ }
+ else {
+ if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top - this.borderTop - this.paddingTop;
+
+ else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0)
+
+ this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+
+
+
+ var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0;
+
+ var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0;
+
+ var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0;
+
+ var pMenuBorderRight = pMenu ? pMenu.borderRight : 0;
+
+
+
+ if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight +
+
+ pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left)
+
+ this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft;
+
+ else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0)
+
+ this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight;
+
+ else if (docRect.width >= menuRect.width)
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+
+ else
+
+ this.left = scrollPos.left;
+ }
+};
diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html
new file mode 100644
index 0000000..2df3c42
--- /dev/null
+++ b/httemplate/elements/xmlhttp.html
@@ -0,0 +1,108 @@
+<%doc>
+
+Example:
+
+ include( '/elements/xmlhttp.html',
+ # required
+ 'url' => $p.'misc/something.html',
+ 'subs' => [ 'subroutine' ],
+
+ # optional
+ 'method' => 'GET', #defaults to GET, could specify POST
+ 'key' => 'unique', #unique key
+
+ );
+
+</%doc>
+<% include( '/elements/rs_init_object.html' ) %>
+<SCRIPT TYPE="text/javascript">
+
+% foreach my $func ( @{$opt{'subs'}} ) {
+%
+% my $furl = $url;
+% $furl =~ s/\"/\\\\\"/; #javascript escape
+%
+%
+
+
+ function <%$key%><%$func%>() {
+ // count args; build URL
+ var url = "<%$furl%>";
+ var a = <%$key%><%$func%>.arguments;
+
+ var args;
+ var len;
+ var content = 'sub=<% uri_escape($func) %>';
+ if ( a && typeof a == 'object' && a[0].constructor == Array ) {
+ args = a[0];
+ len = args.length
+ } else {
+ args = a;
+ len = args.length - 1;
+ }
+ for (var i = 0; i < len; i++)
+ content = content + "&arg=" + escape(args[i]);
+ content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs
+
+ if ( '<%$method%>' == 'GET' ) {
+ url = url + content;
+ }
+
+ //alert('<%$method%> ' + url);
+
+ var xmlhttp = rs_init_object();
+ xmlhttp.open("<%$method%>", url, true);
+
+ xmlhttp.onreadystatechange = function() {
+ if (xmlhttp.readyState != 4)
+ return;
+
+ if (xmlhttp.status != 200) {
+ alert(xmlhttp.status + " status connecting to " + url);
+ } else {
+ var data = xmlhttp.responseText;
+ //alert('received response: ' + data);
+ a[a.length-1](data);
+ if ( data.indexOf("<b>System error</b>") > -1 ) {
+ var w;
+ if ( w = window.open("about:blank") ) {
+ w.document.write(data);
+ } else {
+ // popup blocking? should use an overlib popup instead
+ alert("Error popup disabled; try disabling popup blocking to see");
+ }
+ }
+ }
+ }
+
+ if ( '<%$method%>' == 'POST' ) {
+
+ xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xmlhttp.send(content);
+
+ } else {
+
+ xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
+ xmlhttp.send(null);
+
+ }
+
+ //rs_debug("x_$func_name url = " + url);
+ //rs_debug("x_$func_name waiting..");
+ }
+% }
+
+
+</SCRIPT>
+<%init>
+my ( %opt ) = @_;
+
+my $url = $opt{'url'};
+my $method = exists($opt{'method'}) ? $opt{'method'} : 'GET';
+#my @subs = @{ $opt{'subs'};
+my $key = exists($opt{'key'}) ? $opt{'key'} : '';
+
+$url .= ( ($url =~ /\?/) ? '&' : '?' )
+ if $method eq 'GET';
+
+</%init>
diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi
new file mode 100644
index 0000000..1fad5e1
--- /dev/null
+++ b/httemplate/graph/cust_bill_pkg.cgi
@@ -0,0 +1,151 @@
+<% include('elements/monthly.html',
+ #Dumper(
+ 'title' => $title,
+ 'graph_type' => 'Mountain',
+ 'items' => \@items,
+ 'params' => \@params,
+ 'labels' => \@labels,
+ 'graph_labels' => \@labels,
+ 'colors' => \@colors,
+ 'links' => \@links,
+ 'remove_empty' => 1,
+ 'bottom_total' => 1,
+ 'bottom_link' => $bottom_link,
+ 'agentnum' => $agentnum,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1";
+my $bottom_link = "$link;";
+
+my $use_override = $cgi->param('use_override') ? 1 : 0;
+my $use_usage = $cgi->param('use_usage') ? 1 : 0;
+my $average_per_cust_pkg = $cgi->param('average_per_cust_pkg') ? 1 : 0;
+
+#XXX or virtual
+my( $agentnum, $sel_agent, $all_agent ) = ('', '', '');
+if ( $cgi->param('agentnum') eq 'all' ) {
+ $agentnum = 0;
+ $all_agent = 'ALL';
+}
+elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $bottom_link .= "agentnum=$agentnum;";
+ $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $sel_agent;
+}
+my $title = $sel_agent ? $sel_agent->agent.' ' : '';
+$title .= 'Sales Report (Gross)';
+$title .= ', average per customer package' if $average_per_cust_pkg;
+
+#classnum (here)
+# 0: all classes
+# not specified: empty class
+# N: classnum
+#classnum (link)
+# not specified: all classes
+# 0: empty class
+# N: classnum
+
+#false lazinessish w/FS::cust_pkg::search_sql (previously search/cust_pkg.cgi)
+my $classnum = 0;
+my @pkg_class = ();
+my $all_class = '';
+if ( $cgi->param('classnum') eq 'all' ) {
+ $all_class = 'ALL';
+ @pkg_class = ('');
+}
+elsif ( $cgi->param('classnum') =~ /^(\d*)$/ ) {
+ $classnum = $1;
+ if ( $classnum ) { #a specific class
+
+ @pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
+ die "classnum $classnum not found!" unless $pkg_class[0];
+ $title .= ' '.$pkg_class[0]->classname.' ';
+ $bottom_link .= "classnum=$classnum;";
+
+ } elsif ( $classnum eq '' ) { #the empty class
+
+ $title .= 'Empty class ';
+ @pkg_class = ( '(empty class)' );
+ $bottom_link .= "classnum=0;";
+
+ } elsif ( $classnum eq '0' ) { #all classes
+
+ @pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } );
+ push @pkg_class, '(empty class)';
+
+ }
+}
+#eslaf
+
+my $hue = 0;
+#my $hue_increment = 170;
+#my $hue_increment = 145;
+my $hue_increment = 125;
+
+my @items = ();
+my @params = ();
+my @labels = ();
+my @colors = ();
+my @links = ();
+
+foreach my $agent ( $all_agent || $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
+
+ my $col_scheme = Color::Scheme->new
+ ->from_hue($hue) #->from_hex($agent->color)
+ ->scheme('analogic')
+ ;
+ my @recur_colors = ();
+ my @onetime_colors = ();
+
+ ### fixup the color handling for package classes...
+ ### and usage
+ my $n = 0;
+
+ foreach my $pkg_class ( @pkg_class ) {
+ foreach my $component ( $use_usage ? ('recurring', 'usage') : ('') ) {
+
+ push @items, 'cust_bill_pkg';
+
+ push @labels,
+ ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
+ ( $classnum eq '0'
+ ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class )
+ : ''
+ ).
+ " $component";
+
+ my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
+ my $row_agentnum = $all_agent || $agent->agentnum;
+ push @params, [ ($all_class ? () : ('classnum' => $row_classnum) ),
+ ($all_agent ? () : ('agentnum' => $row_agentnum) ),
+ 'use_override' => $use_override,
+ 'use_usage' => $component,
+ 'average_per_cust_pkg' => $average_per_cust_pkg,
+ ];
+
+ push @links, "$link;".($all_agent ? '' : "agentnum=$row_agentnum;").
+ ($all_class ? '' : "classnum=$row_classnum;").
+ "use_override=$use_override;use_usage=$component;";
+
+ @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9]
+ unless @recur_colors;
+ @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11]
+ unless @onetime_colors;
+ push @colors, shift @recur_colors;
+
+ }
+ }
+
+ $hue += $hue_increment;
+
+}
+
+#use Data::Dumper;
+
+</%init>
diff --git a/httemplate/graph/cust_bill_pkg_detail.cgi b/httemplate/graph/cust_bill_pkg_detail.cgi
new file mode 100644
index 0000000..642a9ec
--- /dev/null
+++ b/httemplate/graph/cust_bill_pkg_detail.cgi
@@ -0,0 +1,137 @@
+<% include('elements/monthly.html',
+ 'title' => $title. 'Rated Call Sales Report (Gross)',
+ 'graph_type' => 'Mountain',
+ 'items' => \@items,
+ 'params' => \@params,
+ 'labels' => \@labels,
+ 'graph_labels' => \@labels,
+ 'colors' => \@colors,
+ 'remove_empty' => 1,
+ 'bottom_total' => 1,
+ 'agentnum' => $agentnum,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $sel_agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $sel_agent;
+}
+my $title = $sel_agent ? $sel_agent->agent.' ' : '';
+
+#false lazinessish w/FS::cust_pkg::search_sql (previously search/cust_pkg.cgi)
+my $classnum = '';
+if ( $cgi->param('classnum') =~ /^(\d*)$/ ) {
+ $classnum = $1;
+
+ if ( $classnum ) { #a specific class
+
+ my $pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
+ die "classnum $classnum not found!" unless $pkg_class;
+ $title .= $pkg_class->classname.' ';
+
+ } elsif ( $classnum eq '' ) { #the empty class
+
+ $title .= 'Empty class ';
+ # FS::Report::Table::Monthly.pm has the converse view
+ $classnum = 0;
+
+ } elsif ( $classnum eq '0' ) { #all classes
+
+ # FS::Report::Table::Monthly.pm has the converse view
+ $classnum = '';
+ }
+}
+#eslaf
+
+my $use_override = 0;
+$use_override = 1 if ( $cgi->param('use_override') );
+
+my $usageclass = 0;
+my @usage_class = ();
+if ( $cgi->param('usageclass') =~ /^(\d*)$/ ) {
+ $usageclass = $1;
+
+ if ( $usageclass ) { #a specific class
+
+ @usage_class = ( qsearchs('usage_class', { 'classnum' => $usageclass } ) );
+ die "usage class $usageclass not found!" unless $usage_class[0];
+ $title .= $usage_class[0]->classname.' ';
+
+ } elsif ( $usageclass eq '' ) { #the empty class -- legacy
+
+ $title .= 'Empty usage class ';
+ @usage_class = ( '(empty usage class)' );
+
+ } elsif ( $usageclass eq '0' ) { #all classes
+
+ @usage_class = qsearch('usage_class', {} ); # { 'disabled' => '' } );
+ push @usage_class, '(empty usage class)';
+
+ }
+}
+#eslaf
+
+my $hue = 0;
+#my $hue_increment = 170;
+#my $hue_increment = 145;
+my $hue_increment = 125;
+
+my @items = ();
+my @params = ();
+my @labels = ();
+my @colors = ();
+
+foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
+
+ my $col_scheme = Color::Scheme->new
+ ->from_hue($hue) #->from_hex($agent->color)
+ ->scheme('analogic')
+ ;
+ my @recur_colors = ();
+ my @onetime_colors = ();
+
+ ### fixup the color handling for usage classes...
+ my $n = 0;
+
+ foreach my $usage_class ( @usage_class ) {
+
+ push @items, 'cust_bill_pkg_detail';
+
+ push @labels,
+ ( $sel_agent ? '' : $agent->agent.' ' ).
+ ( $usageclass eq '0'
+ ? ( ref($usage_class) ? $usage_class->classname : $usage_class )
+ : ''
+ );
+
+ my $row_classnum = ref($usage_class) ? $usage_class->classnum : 0;
+ my $row_agentnum = $agent->agentnum;
+ push @params, [ 'usageclass' => $row_classnum,
+ 'agentnum' => $row_agentnum,
+ 'use_override' => $use_override,
+ 'classnum' => $classnum,
+ ];
+
+ @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9]
+ unless @recur_colors;
+ @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11]
+ unless @onetime_colors;
+ push @colors, shift @recur_colors;
+
+ }
+
+ $hue += $hue_increment;
+
+}
+
+#use Data::Dumper;
+#warn Dumper(\@items);
+
+</%init>
diff --git a/httemplate/graph/cust_pkg.cgi b/httemplate/graph/cust_pkg.cgi
new file mode 100644
index 0000000..21ce07d
--- /dev/null
+++ b/httemplate/graph/cust_pkg.cgi
@@ -0,0 +1,63 @@
+<% include('elements/monthly.html',
+ 'title' => $agentname. 'Package Churn',
+ 'items' => \@items,
+ 'labels' => \%label,
+ 'graph_labels' => \%graph_label,
+ 'colors' => \%color,
+ 'links' => \%link,
+ 'agentnum' => $agentnum,
+ 'sprintf' => '%u',
+ 'disable_money' => 1,
+ )
+%>
+<%init>
+
+#XXX use a different ACL for package churn?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#false laziness w/money_time.cgi, cust_bill_pkg.cgi
+
+#XXX or virtual
+my( $agentnum, $agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $agent;
+}
+
+my $agentname = $agent ? $agent->agent.' ' : '';
+
+my @items = qw( setup_pkg susp_pkg cancel_pkg );
+
+my %label = (
+ 'setup_pkg' => 'New orders',
+ 'susp_pkg' => 'Suspensions',
+# 'unsusp' => 'Unsuspensions',
+ 'cancel_pkg' => 'Cancellations',
+);
+my %graph_label = %label;
+
+my %color = (
+ 'setup_pkg' => '00cc00', #green
+ 'susp_pkg' => 'ff9900', #yellow
+ #'unsusp' => '', #light green?
+ 'cancel_pkg' => 'cc0000', #red ? 'ff0000'
+);
+
+my %link = (
+ 'setup_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;",
+ 'fromparam' => 'setup_begin',
+ 'toparam' => 'setup_end',
+ },
+ 'susp_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;",
+ 'fromparam' => 'susp_begin',
+ 'toparam' => 'susp_end',
+ },
+ 'cancel_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;",
+ 'fromparam' => 'cancel_begin',
+ 'toparam' => 'cancel_end',
+ },
+);
+
+</%init>
diff --git a/httemplate/graph/cust_pkg_cost.cgi b/httemplate/graph/cust_pkg_cost.cgi
new file mode 100644
index 0000000..0aa7e32
--- /dev/null
+++ b/httemplate/graph/cust_pkg_cost.cgi
@@ -0,0 +1,61 @@
+<% include('elements/monthly.html',
+ 'title' => $agentname.
+ 'Package Costs Report',
+ 'graph_type' => 'Lines',
+ 'items' => \@items,
+ 'labels' => \%label,
+ 'graph_labels' => \%label,
+ 'colors' => \%color,
+ 'links' => \%link,
+ 'agentnum' => $agentnum,
+ 'nototal' => scalar($cgi->param('12mo')),
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $agent;
+}
+
+my $agentname = $agent ? $agent->agent.' ' : '';
+
+my @items = qw( cust_pkg_setup_cost cust_pkg_recur_cost );
+if ( $cgi->param('12mo') == 1 ) {
+ @items = map $_.'_12mo', @items;
+}
+
+my %label = (
+ 'cust_pkg_setup_cost' => 'Setup Costs',
+ 'cust_pkg_recur_cost' => 'Recurring Costs',
+);
+
+$label{$_.'_12mo'} = $label{$_}. " (prev 12 months)"
+ foreach keys %label;
+
+my %color = (
+ 'cust_pkg_setup_cost' => '0000cc',
+ 'cust_pkg_recur_cost' => '00cc00',
+);
+$color{$_.'_12mo'} = $color{$_}
+ foreach keys %color;
+
+my %link = (
+ 'cust_pkg_setup_cost' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;",
+ 'fromparam' => 'setup_begin',
+ 'toparam' => 'setup_end',
+ },
+ 'cust_pkg_recur_cost' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;",
+ 'fromparam' => 'active_begin',
+ 'toparam' => 'active_end',
+ },
+);
+# XXX link 12mo?
+
+</%init>
diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html
new file mode 100644
index 0000000..7b1b98a
--- /dev/null
+++ b/httemplate/graph/elements/monthly.html
@@ -0,0 +1,139 @@
+<%doc>
+
+Example:
+
+ include('elements/monthly.html',
+ #required
+ 'title' => 'Page title',
+ 'items' => \@items,
+ 'labels' => \@labels, # or \%labels (keys are items)
+
+ #required?
+ 'colors' => \@colors, # or \%colors,
+
+ #recommended
+ 'graph_labels' => \@graph_labels, # or \%graph_labels,
+
+ #optional
+ 'params' => \@params, # opt,
+ 'links' => \@links, # or \%link, #opt
+ 'link_fromparam' => 'param_from', #defaults to 'begin'
+ 'link_toparam' => 'param_to', #defaults to 'end'
+
+ #optional, pulled from CGI params if not specified
+ 'start_month' => $smonth,
+ 'start_year' => $syear,
+ 'end_month' => $emonth,
+ 'end_year' => $eyear,
+
+ #optional
+ 'agentnum' => $agentnum,
+ 'nototal' => 1,
+ 'graph_type' => 'LinesPoints',
+ 'remove_empty' => 1,
+ 'bottom_total' => 1,
+ 'sprintf' => '%u', #sprintf format, overrides default %.2f
+ 'disable_money' => 1,
+ );
+
+</%doc>
+<% include('report.html',
+ 'items' => $data->{'items'},
+ 'data' => $data->{'data'},
+ 'row_labels' => $data->{'item_labels'},
+ 'graph_labels' => $opt{'graph_labels'} || $data->{'item_labels'},
+ 'col_labels' => [ map { my $m = $_; $m =~ s/^(\d+)\//$mon[$1-1] / ; $m }
+ @{$data->{label}} ],
+ 'axis_labels' => $data->{label},
+ 'colors' => $data->{colors},
+ 'links' => \@links,
+ 'bottom_link' => \@bottom_link,
+ map { $_, $opt{$_} } (qw(title
+ nototal
+ graph_type
+ bottom_total
+ sprintf
+ disable_money)),
+ ) %>
+<%init>
+
+my(%opt) = @_;
+
+my $conf = new FS::Conf;
+my $money_char = $opt{'disable_money'} ? '' : $conf->config('money_char');
+
+my $fromparam = $opt{'link_fromparam'} || 'begin';
+my $toparam = $opt{'link_toparam'} || 'end';
+
+my @items = @{ $opt{'items'} };
+
+foreach my $other (qw( labels graph_labels colors links )) {
+ if ( ref($opt{$other}) eq 'HASH' ) {
+ $opt{$other} = [ map $opt{$other}{$_}, @items ];
+ }
+}
+
+my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+
+#find first month
+$opt{'start_month'} ||= $cgi->param('start_month'); # || $curmon+1;
+$opt{'start_year'} ||= $cgi->param('start_year'); # || 1899+$curyear;
+
+#find last month
+$opt{'end_month'} ||= $cgi->param('end_month'); # || $curmon+1;
+$opt{'end_year'} ||= $cgi->param('end_year'); # || 1900+$curyear;
+
+my $report = new FS::Report::Table::Monthly (
+
+ 'items' => \@items,
+ 'params' => $opt{'params'},
+ 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/
+ ? $opt{'graph_labels'}
+ : $opt{'labels'}
+ ),
+ 'colors' => $opt{'colors'},
+ 'links' => $opt{'links'},
+
+ 'start_month' => $opt{'start_month'},
+ 'start_year' => $opt{'start_year'},
+ 'end_month' => $opt{'end_month'},
+ 'end_year' => $opt{'end_year'},
+
+ 'agentnum' => $opt{'agentnum'},
+ 'remove_empty' => $opt{'remove_empty'},
+);
+my $data = $report->data;
+
+my @links;
+foreach my $link (@{ $data->{'links'} }) {
+ my @speriod = @{$data->{'speriod'}};
+ my @eperiod = @{$data->{'eperiod'}};
+ my ($begin, $end) = ($fromparam, $toparam);
+
+ my @new = ( $link );
+ if(ref($link)) {
+ $begin = $link->{'fromparam'};
+ $end = $link->{'toparam'};
+ @new = ( $link->{'link'} );
+ }
+ while(@speriod) {
+ push @new, "$begin=". shift(@speriod).";$end=".shift(@eperiod);
+ }
+ if(! $opt{'nototal'}) {
+ push @new, "$begin=". $data->{'speriod'}[0] . ";$end=". $data->{'eperiod'}[-1];
+ }
+ push @links, \@new;
+}
+
+my @bottom_link;
+if($opt{'bottom_link'}) {
+ my @speriod = (@{$data->{'speriod'}}, $data->{'speriod'}[0]);
+ my @eperiod = (@{$data->{'eperiod'}}, $data->{'eperiod'}[-1]);
+
+ push @bottom_link, $opt{'bottom_link'};
+ while(@speriod) {
+ push @bottom_link, "$fromparam=". shift(@speriod). ";$toparam=". shift(@eperiod);
+ }
+}
+
+</%init>
diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html
new file mode 100644
index 0000000..b831f3a
--- /dev/null
+++ b/httemplate/graph/elements/report.html
@@ -0,0 +1,296 @@
+<%doc>
+
+Example:
+
+ include('elements/report.html',
+ #required
+ 'title' => 'Page title',
+ 'items' => \@items,
+ 'data' => [ \@item1 \@item2 ... ],
+
+ #these run parallel to items, and can be given as hashes
+ 'row_labels' => \@row_labels, #required
+ 'colors' => \@colors, #required
+ 'graph_labels' => \@graph_labels, #defaults to row_labels
+
+ 'links' => \@links, #optional
+
+ #these run parallel to the elements of each @item
+ 'col_labels' => \@col_labels, #required
+ 'axis_labels' => \@axis_labels, #defaults to col_labels
+
+ #optional
+ 'nototal' => 1,
+ 'graph_type' => 'LinesPoints',
+ 'bottom_total' => 1,
+ 'sprintf' => '%u', #sprintf format, overrides default %.2f
+ 'disable_money' => 1,
+ );
+
+About @links: Each element must be an arrayref, corresponding to an element of
+@items. Within the array, the first element is a URL prefix, and the rest
+are suffixes corresponding to data elements. These will be joined without
+any delimiter and linked from the elements in @data.
+
+</%doc>
+% if ( $cgi->param('_type') =~ /^(csv)$/ ) {
+%
+% #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+% #http_header('Content-Type' => 'text/plain' );
+% http_header('Content-Type' => 'text/csv');
+% http_header('Content-Disposition' => "attachment;filename=$filename.csv");
+%
+% my $csv = new Text::CSV_XS { 'always_quote' => 1,
+% 'eol' => "\n", #"\015\012", #"\012"
+% };
+%
+% $csv->combine('', @col_labels, $opt{'nototal'} ? () : 'Total');
+%
+<% $csv->string %>
+%
+% my @bottom_total = ();
+% foreach ( @items ) {
+%
+% my $col = 0;
+% my $total = 0;
+% $csv->combine(
+% shift( @row_labels ),
+% map { $total += $_; $bottom_total[$col++] += $_; sprintf($sprintf, $_); }
+% ( @{ shift( @data ) } ),
+% ( $opt{'nototal'} ? () : sprintf($sprintf, $total) ),
+% );
+% unless ( $opt{'nototal'} ) {
+% $bottom_total[$col++] += $total;
+% }
+<% $csv->string %>
+%
+% }
+%
+% if ( $opt{'bottom_total'} ) {
+% $csv->combine(
+% 'Total',
+% map { sprintf($sprintf, $_) } @bottom_total,
+% );
+%
+<% $csv->string %>
+%
+% }
+%
+% } elsif ( $cgi->param('_type') =~ /(xls)$/ ) {
+%
+% #http_header('Content-Type' => 'application/excel' ); #eww
+% http_header('Content-Type' => 'application/vnd.ms-excel' );
+% #http_header('Content-Type' => 'application/msexcel' ); #alas
+% http_header('Content-Disposition' => "attachment;filename=$filename.xls");
+%
+% my $output = '';
+% my $XLS = new IO::Scalar \$output;
+% my $workbook = Spreadsheet::WriteExcel->new($XLS)
+% or die "Error opening .xls file: $!";
+%
+% my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
+%
+% my($r,$c) = (0,0);
+%
+% foreach ('', @col_labels, ($opt{'nototal'} ? () : 'Total') ) {
+% my $header = $_;
+% $worksheet->write($r, $c++, $header)
+% }
+%
+% my @bottom_total = ();
+% foreach ( @items ) {
+% $r++;
+% $c = 0;
+% my $total = 0;
+% $worksheet->write( $r, $c++, shift( @row_labels ) );
+% foreach ( @{ shift( @data ) } ) {
+% $total += $_;
+% $bottom_total[$c] += $_;
+% $worksheet->write($r, $c++, sprintf($sprintf, $_) );
+% }
+% unless ( $opt{'nototal'} ) {
+% $bottom_total[$c] += $total;
+% $worksheet->write($r, $c++, sprintf($sprintf, $total) );
+% }
+% }
+%
+% $c = 0;
+% if ( $opt{'bottom_total'} ) {
+% $r++;
+% $worksheet->write($r, $c++, 'Total');
+% $worksheet->write($r, $c++, sprintf($sprintf, $_)) foreach @bottom_total;
+% }
+%
+% $workbook->close();# or die "Error creating .xls file: $!";
+%
+% http_header('Content-Length' => length($output) );
+%
+<% $output %>
+% } elsif ( $cgi->param('_type') eq 'png' ) {
+%
+% my $graph_type = 'LinesPoints';
+% if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain|Bars)$/ ) {
+% $graph_type = $1;
+% }
+% my $class = "Chart::$graph_type";
+%
+% my $chart = $class->new(976,384);
+%
+% my $d = 0;
+% $chart->set(
+% #'min_val' => 0,
+% 'legend' => 'bottom',
+% 'colors' => { (
+% map { my $color = $_;
+% 'dataset'.$d++ =>
+% [ map hex($_), unpack 'a2a2a2', $color ]
+% }
+% @{ $opt{'colors'} }
+% ),
+% 'grey_background' => 'white',
+% 'background' => [ 0xe8, 0xe8, 0xe8 ], #grey
+% },
+% 'legend_labels' => $opt{'graph_labels'},
+% 'brush_size' => 4,
+% );
+%
+% http_header('Content-Type' => 'image/png' );
+%
+% $chart->_set_colors();
+%
+<% $chart->scalar_png([ $opt{'axis_labels'}, @data ]) %>
+%
+% } else {
+%
+<% include('/elements/header.html', $opt{'title'} ) %>
+% $cgi->param('_type', 'png');
+
+<IMG SRC="<% $cgi->self_url %>" WIDTH="976" HEIGHT="384">
+<P ALIGN="right">
+
+% unless ( $opt{'disable_download'} ) {
+% $cgi->param('_type', "xls" );
+ Download full results<BR>
+ as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR>
+% $cgi->param('_type', 'csv');
+ as <A HREF="<% $cgi->self_url %>">CSV file</A></P>
+% $cgi->param('_type', "html" );
+% }
+%
+</P>
+<% include('/elements/table.html', 'f8f8f8') %>
+
+<TR>
+
+ <TD></TD>
+
+% foreach my $column ( @col_labels ) {
+% $column =~ s/ /\<BR\>/; # working on a smarter way to do this
+ <TH><% $column %></TH>
+% }
+
+% unless ( $opt{'nototal'} ) {
+ <TH>Total</TH>
+% }
+
+</TR>
+
+% my @bottom_total = ();
+% foreach my $row ( @items ) {
+%
+% my $color = shift( @{ $opt{'colors'} } );
+% my @links = @{ shift( @{ $opt{'links'} } ) };
+% # $opt{'links'} is an array parallel to items.
+% # Each element of that is an array containing a prefix,
+% # followed by suffixes matched to the cells of the table.
+% my $link_prefix = shift @links;
+% $link_prefix = $link_prefix ? qq(<A HREF="$link_prefix) : ''; #"
+% my $label = shift( @row_labels );
+
+ <TR>
+
+ <TH>
+ <FONT COLOR="#<% $color %>"><% $label %></FONT>
+ </TH>
+
+% my $total = 0;
+% my $col = 0;
+% foreach my $column ( @{ shift( @data ) } ) {
+
+ <TD ALIGN="right" BGCOLOR="#ffffff">
+ <% $link_prefix ? $link_prefix . shift(@links) . '">' : '' %><FONT COLOR="#<% $color %>"><% $money_char %><% sprintf($sprintf,, $column) %></FONT><% $link_prefix ? '</A>' : '' %>
+ </TD>
+%
+% $total += $column;
+% $bottom_total[$col++] += $column;
+%
+% }
+
+% unless ( $opt{'nototal'} ) {
+ <TD ALIGN="right" BGCOLOR="#f5f6be">
+ <% $link_prefix ? $link_prefix. shift(@links) . '">' : '' %><FONT COLOR="#<% $color %>"><% $money_char %><% sprintf($sprintf, $total) %></FONT><% $link_prefix ? '</A>' : '' %>
+ </TD>
+% $bottom_total[$col++] += $total;
+% }
+
+ </TR>
+
+% }
+
+% if ( $opt{'bottom_total'} ) {
+ <TR>
+ <TH>Total</TH>
+% my @bottom_links = $opt{'bottom_link'} ? @{ $opt{'bottom_link'} } : ();
+% my $prefix = shift(@bottom_links);
+% pop @bottom_links if $opt{'nototal'};
+% foreach my $total ( @bottom_total ) {
+
+ <TD ALIGN="right" BGCOLOR="#f5f6be">
+ <% $prefix
+ ? '<A HREF="'. $prefix .shift(@bottom_links). '">'
+ : ''
+ %><% $money_char %><% sprintf($sprintf, $total) %><% $prefix ? '</A>' : '' %>
+
+ </TD>
+
+% }
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+% }
+<%once>
+
+</%once>
+<%init>
+
+my(%opt) = @_;
+
+my $sprintf = $opt{'sprintf'} || '%.2f';
+
+my $conf = new FS::Conf;
+my $money_char = $opt{'disable_money'} ? '' : $conf->config('money_char');
+
+my @items = @{ $opt{'items'} };
+
+foreach my $other (qw( col_labels row_labels graph_labels axis_labels colors links )) {
+ if ( ref($opt{$other}) eq 'HASH' ) {
+ $opt{$other} = [ map $opt{$other}{$_}, @items ];
+ }
+}
+
+my @col_labels = @{$opt{'col_labels'}};
+my @row_labels = @{$opt{'row_labels'}};
+my @data = @{$opt{'data'}};
+
+$opt{'axis_labels'} ||= $opt{'col_labels'};
+$opt{'graph_labels'} ||= $opt{'row_labels'};
+
+my $filename = $cgi->url(-relative => 1);
+$filename =~ s/\.(cgi|html)$//;
+
+</%init>
diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi
new file mode 100644
index 0000000..4e4157e
--- /dev/null
+++ b/httemplate/graph/money_time.cgi
@@ -0,0 +1,98 @@
+<% include('elements/monthly.html',
+ 'title' => $agentname.
+ 'Sales, Credits and Receipts Summary',
+ 'items' => \@items,
+ 'labels' => \%label,
+ 'graph_labels' => \%graph_label,
+ 'colors' => \%color,
+ 'links' => \%link,
+ 'agentnum' => $agentnum,
+ 'nototal' => scalar($cgi->param('12mo')),
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $agent;
+}
+
+my $agentname = $agent ? $agent->agent.' ' : '';
+
+my @items = qw( invoiced netsales
+ credits netcredits
+ payments receipts
+ refunds netrefunds
+ cashflow netcashflow
+ );
+if ( $cgi->param('12mo') == 1 ) {
+ @items = map $_.'_12mo', @items;
+}
+
+my %label = (
+ 'invoiced' => 'Gross Sales',
+ 'netsales' => 'Net Sales',
+ 'credits' => 'Gross Credits',
+ 'netcredits' => 'Net Credits',
+ 'payments' => 'Gross Receipts',
+ 'receipts' => 'Net Receipts',
+ 'refunds' => 'Gross Refunds',
+ 'netrefunds' => 'Net Refunds',
+ 'cashflow' => 'Gross Cashflow',
+ 'netcashflow' => 'Net Cashflow',
+);
+
+my %graph_suffix = (
+ 'invoiced' => ' (invoiced)',
+ 'netsales' => ' (invoiced - applied credits)',
+ 'credits' => ' (credited)',
+ 'netcredits' => ' (applied credits)',
+ 'payments' => ' (payments)',
+ 'receipts' => ' (applied payments)',
+ 'refunds' => ' (refunds)',
+ 'netrefunds' => ' (applied refunds)',
+ 'cashflow' => ' (payments - refunds)',
+ 'netcashflow' => ' (applied payments - applied refunds)',
+);
+my %graph_label = map { $_ => $label{$_}.$graph_suffix{$_} } keys %label;
+
+$label{$_.'_12mo'} = $label{$_}. " (prev 12 months)"
+ foreach keys %label;
+
+$graph_label{$_.'_12mo'} = $graph_label{$_}. " (prev 12 months)"
+ foreach keys %graph_label;
+
+my %color = (
+ 'invoiced' => '9999ff', #light blue
+ 'netsales' => '0000cc', #blue
+ 'credits' => 'ff9999', #light red
+ 'netcredits' => 'cc0000', #red
+ 'payments' => '99cc99', #light green
+ 'receipts' => '00cc00', #green
+ 'refunds' => 'ffcc99', #light orange
+ 'netrefunds' => 'ff9900', #orange
+ 'cashflow' => '99cc33', #light olive
+ 'netcashflow' => '339900', #olive
+);
+$color{$_.'_12mo'} = $color{$_}
+ foreach keys %color;
+
+my %link = (
+ 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;",
+ 'netsales' => "${p}search/cust_bill.html?agentnum=$agentnum;net=1;",
+ 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;",
+ 'netcredits' => "${p}search/cust_credit_bill.html?agentnum=$agentnum;",
+ 'payments' => "${p}search/cust_pay.cgi?magic=_date;agentnum=$agentnum;",
+ 'receipts' => "${p}search/cust_bill_pay.html?agentnum=$agentnum;",
+ 'refunds' => "${p}search/cust_refund.html?magic=_date;agentnum=$agentnum;",
+ 'netrefunds' => "${p}search/cust_credit_refund.html?agentnum=$agentnum;",
+);
+# XXX link 12mo?
+
+</%init>
diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html
new file mode 100644
index 0000000..3487465
--- /dev/null
+++ b/httemplate/graph/report_cust_bill_pkg.html
@@ -0,0 +1,57 @@
+<% include('/elements/header.html', 'Sales Report' ) %>
+
+<FORM ACTION="cust_bill_pkg.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ 'pre_options' => [ 'all' => 'all (aggregate)' ],
+ 'empty_label' => 'all (breakdown)',
+ )
+%>
+
+<% include('/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ 'all' => 'all (aggregate)',
+ '0' => 'all (breakdown)' ],
+ 'empty_label' => '(empty class)',
+ )
+%>
+
+<!--
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="separate_0freq" VALUE="1"></TD>
+ <TD>Separate one-time vs. recurring sales</TD>
+</TR>
+-->
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD>
+ <TD>Separate sub-packages from parents</TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_usage" VALUE="1"></TD>
+ <TD>Separate rated usage from recurring fees</TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="average_per_cust_pkg" VALUE="1"></TD>
+ <TD>Average per customer package</TD>
+</TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/report_cust_bill_pkg_detail.html b/httemplate/graph/report_cust_bill_pkg_detail.html
new file mode 100644
index 0000000..3b85d52
--- /dev/null
+++ b/httemplate/graph/report_cust_bill_pkg_detail.html
@@ -0,0 +1,48 @@
+<% include('/elements/header.html', 'Usage Sales Report' ) %>
+
+<FORM ACTION="cust_bill_pkg_detail.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+<% include('/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+%>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD>
+ <TD>Separate sub-packages from parents</TD>
+</TR>
+
+<% include('/elements/tr-select-table.html',
+ 'label' => 'Usage class: ',
+ 'element_name' => 'usageclass',
+ 'table' => 'usage_class',
+ 'name_col' => 'classname',
+ 'hashref' => { 'disabled' => '' },
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+%>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/report_cust_pkg.html b/httemplate/graph/report_cust_pkg.html
new file mode 100644
index 0000000..22ccd5d
--- /dev/null
+++ b/httemplate/graph/report_cust_pkg.html
@@ -0,0 +1,28 @@
+<% include('/elements/header.html', 'Package Churn Summary' ) %>
+
+<FORM ACTION="cust_pkg.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+#XXX use a different ACL for package churn?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/report_cust_pkg_cost.html b/httemplate/graph/report_cust_pkg_cost.html
new file mode 100644
index 0000000..553db09
--- /dev/null
+++ b/httemplate/graph/report_cust_pkg_cost.html
@@ -0,0 +1,26 @@
+<% include('/elements/header.html', 'Package Costs Report' ) %>
+
+<FORM ACTION="cust_pkg_cost.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html
new file mode 100644
index 0000000..b85bb65
--- /dev/null
+++ b/httemplate/graph/report_money_time.html
@@ -0,0 +1,43 @@
+<% include('/elements/header.html', 'Sales, Credits and Receipts Summary' ) %>
+
+<FORM ACTION="money_time.cgi" METHOD="GET">
+
+<!--
+<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>
+-->
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="12mo" VALUE="1"></TD>
+ <TD>Show 12 month totals instead of monthly values</TD>
+</TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/report_signupdate.html b/httemplate/graph/report_signupdate.html
new file mode 100644
index 0000000..7c22f11
--- /dev/null
+++ b/httemplate/graph/report_signupdate.html
@@ -0,0 +1,30 @@
+<% include('/elements/header.html', 'Customer Signups by Date/Time' ) %>
+
+<FORM ACTION="signupdate.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+<% include('/elements/tr-select-user.html') %>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+#XXX use a different ACL for package churn?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/graph/signupdate.cgi b/httemplate/graph/signupdate.cgi
new file mode 100644
index 0000000..5b70758
--- /dev/null
+++ b/httemplate/graph/signupdate.cgi
@@ -0,0 +1,60 @@
+<% include('elements/report.html',
+ 'title' => $agentname . 'Customer signups by time of day',
+ 'items' => [ 'signupdate' ],
+ 'data' => [ \@count ],
+ 'row_labels' => [ 'New customers' ],
+ 'colors' => [ '00cc00' ], #green
+ 'col_labels' => [ map { "$_:00" } @hours ],
+ 'links' => [ \@links ],
+ 'graph_type' => 'Bars',
+ 'nototal' => 0,
+ 'sprintf' => '%u',
+ 'disable_money' => 1,
+ ) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $agent;
+}
+
+my $agentname = $agent ? $agent->agent.' ' : '';
+my $usernum = $cgi->param('usernum');
+
+my @hours = (0..23);
+my @count = (0) x 24;
+my %where;
+$where{'agentnum'} = $agentnum if $agentnum;
+$where{'usernum'} = $usernum if $usernum;
+my $sdate = $cgi->param('start_year').
+ '-'.
+ $cgi->param('start_month').
+ '-01';
+my $edate = ($cgi->param('end_year') +
+ ($cgi->param('end_month')==12)).
+ '-'.
+ ($cgi->param('end_month') % 12 + 1).
+ '-01'; # first day of the next month
+
+my $sql = "AND signupdate >= ".str2time($sdate).
+ " AND signupdate < ".str2time($edate);
+
+foreach my $cust (qsearch({ table => 'cust_main',
+ hashref => \%where,
+ extra_sql => $sql } )) {
+ next if !$cust->signupdate;
+ my $hour = time2str('%H',$cust->signupdate);
+ $count[$hour]++;
+}
+
+my @links = ("${p}search/cust_main.html?" .
+ join (';', map {$_.'='.$where{$_}} (keys(%where))) ).
+ ";signupdate_beginning=$sdate;signupdate_ending=$edate";
+push @links, map { ";signuphour=$_" } @hours;
+</%init>
diff --git a/httemplate/images/32clear.gif b/httemplate/images/32clear.gif
new file mode 100644
index 0000000..5fdcea2
--- /dev/null
+++ b/httemplate/images/32clear.gif
Binary files differ
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/arrow.down.black.png b/httemplate/images/arrow.down.black.png
new file mode 100644
index 0000000..92a8b51
--- /dev/null
+++ b/httemplate/images/arrow.down.black.png
Binary files differ
diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png
new file mode 100644
index 0000000..34cb028
--- /dev/null
+++ b/httemplate/images/arrow.down.png
Binary files differ
diff --git a/httemplate/images/arrow.right.black.png b/httemplate/images/arrow.right.black.png
new file mode 100644
index 0000000..933c258
--- /dev/null
+++ b/httemplate/images/arrow.right.black.png
Binary files differ
diff --git a/httemplate/images/arrow.right.png b/httemplate/images/arrow.right.png
new file mode 100644
index 0000000..60bcb76
--- /dev/null
+++ b/httemplate/images/arrow.right.png
Binary files differ
diff --git a/httemplate/images/background-cheat.png b/httemplate/images/background-cheat.png
new file mode 100644
index 0000000..ad332f6
--- /dev/null
+++ b/httemplate/images/background-cheat.png
Binary files differ
diff --git a/httemplate/images/black-gray-corner.png b/httemplate/images/black-gray-corner.png
new file mode 100644
index 0000000..17954cd
--- /dev/null
+++ b/httemplate/images/black-gray-corner.png
Binary files differ
diff --git a/httemplate/images/black-gray-top.png b/httemplate/images/black-gray-top.png
new file mode 100644
index 0000000..ed07075
--- /dev/null
+++ b/httemplate/images/black-gray-top.png
Binary files differ
diff --git a/httemplate/images/calendar-disabled.png b/httemplate/images/calendar-disabled.png
new file mode 100644
index 0000000..81816bc
--- /dev/null
+++ b/httemplate/images/calendar-disabled.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/cross.png b/httemplate/images/cross.png
new file mode 100644
index 0000000..1514d51
--- /dev/null
+++ b/httemplate/images/cross.png
Binary files differ
diff --git a/httemplate/images/cvv2.png b/httemplate/images/cvv2.png
new file mode 100644
index 0000000..502f27f
--- /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..5cf86c8
--- /dev/null
+++ b/httemplate/images/cvv2_amex.png
Binary files differ
diff --git a/httemplate/images/error.png b/httemplate/images/error.png
new file mode 100644
index 0000000..628cf2d
--- /dev/null
+++ b/httemplate/images/error.png
Binary files differ
diff --git a/httemplate/images/menu-left-example.png b/httemplate/images/menu-left-example.png
new file mode 100644
index 0000000..375725c
--- /dev/null
+++ b/httemplate/images/menu-left-example.png
Binary files differ
diff --git a/httemplate/images/menu-top-example.png b/httemplate/images/menu-top-example.png
new file mode 100644
index 0000000..bd9bea8
--- /dev/null
+++ b/httemplate/images/menu-top-example.png
Binary files differ
diff --git a/httemplate/images/progressbar-empty.png b/httemplate/images/progressbar-empty.png
new file mode 100644
index 0000000..318219c
--- /dev/null
+++ b/httemplate/images/progressbar-empty.png
Binary files differ
diff --git a/httemplate/images/progressbar-full.png b/httemplate/images/progressbar-full.png
new file mode 100644
index 0000000..863d8e1
--- /dev/null
+++ b/httemplate/images/progressbar-full.png
Binary files differ
diff --git a/httemplate/images/red_telephone_mimooh_01.png b/httemplate/images/red_telephone_mimooh_01.png
new file mode 100644
index 0000000..2212ff0
--- /dev/null
+++ b/httemplate/images/red_telephone_mimooh_01.png
Binary files differ
diff --git a/httemplate/images/small-logo.png b/httemplate/images/small-logo.png
new file mode 100644
index 0000000..1e415e6
--- /dev/null
+++ b/httemplate/images/small-logo.png
Binary files differ
diff --git a/httemplate/images/tick.png b/httemplate/images/tick.png
new file mode 100644
index 0000000..a9925a0
--- /dev/null
+++ b/httemplate/images/tick.png
Binary files differ
diff --git a/httemplate/images/wait-orange.gif b/httemplate/images/wait-orange.gif
new file mode 100644
index 0000000..92c7f34
--- /dev/null
+++ b/httemplate/images/wait-orange.gif
Binary files differ
diff --git a/httemplate/index.html b/httemplate/index.html
new file mode 100644
index 0000000..c813991
--- /dev/null
+++ b/httemplate/index.html
@@ -0,0 +1,54 @@
+% my $conf = new FS::Conf;
+
+<% include('/elements/header.html', 'Billing Main' ) %>
+
+<% include('/elements/dashboard-toplist.html') %>
+
+% my $sth = dbh->prepare(
+% #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum )
+% "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum )
+% WHERE ( history_action = 'insert' OR history_action = 'replace_new' )
+% AND history_user = ?
+% ORDER BY history_date desc" # LIMIT 10
+% ) or die dbh->errstr;
+%
+% $sth->execute( getotaker() ) or die $sth->errstr;
+%
+% my %saw = ();
+% my @custnums = grep { !$saw{$_}++ } map $_->[0], @{ $sth->fetchall_arrayref };
+%
+% @custnums = splice(@custnums, 0, 10);
+%
+% if ( @custnums ) {
+
+ <% include('/elements/table-grid.html') %>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = $bgcolor2;
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=1>Customers I recently added or modified</TH>
+ </TR>
+
+% foreach my $custnum ( @custnums ) {
+% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+% next unless $cust_main;
+
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="view/cust_main.cgi?<% $custnum %>"><% $cust_main->display_custnum %>: <% $cust_main->name %></A></TD>
+ </TR>
+
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% }
+
+ </TABLE>
+
+% }
+
+<% include('/elements/footer.html') %>
diff --git a/httemplate/loginout/logout.html b/httemplate/loginout/logout.html
new file mode 100644
index 0000000..130cf66
--- /dev/null
+++ b/httemplate/loginout/logout.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML>
+ <HEAD>
+ <TITLE>
+ <% "Logout page" %>
+ </TITLE>
+ </HEAD>
+ <BODY>
+ <BR><BR>
+ <CENTER>
+ You have logged out.
+ </CENTER>
+ <BR><BR>
+ <CENTER>
+ Return to <a href="..">freeside</a>
+ </CENTER>
+ </BODY>
+</HTML>
diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi
new file mode 100644
index 0000000..69c9573
--- /dev/null
+++ b/httemplate/misc/areacodes.cgi
@@ -0,0 +1,24 @@
+%# [ <% join(', ', map { qq("$_") } @areacodes) %> ]
+<% objToJson(\@areacodes) %>
+<%init>
+
+my( $state, $svcpart ) = $cgi->param('arg');
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+} elsif ( ! @exports ) {
+ die "no DID providing export attached to svcpart $svcpart";
+}
+my $export = $exports[0];
+
+my $something = $export->get_dids('state'=>$state);
+
+#warn Dumper($something);
+
+my @areacodes = @{ $something };
+
+</%init>
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
new file mode 100644
index 0000000..505f2d0
--- /dev/null
+++ b/httemplate/misc/batch-cust_pay.html
@@ -0,0 +1,51 @@
+<% include('/elements/header.html', 'Quick payment entry') %>
+
+<% include('/elements/error.html') %>
+
+<SCRIPT TYPE="text/javascript">
+function warnUnload() {
+ if(document.getElementById("OneTrueTable").rows.length > 3 &&
+ !document.OneTrueForm.submit.disabled) {
+ return "The current batch will be lost.";
+ }
+ else {
+ return null;
+ }
+}
+window.onbeforeunload = warnUnload;
+</SCRIPT>
+
+<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.submit.disabled=true;window.onbeforeunload = null;">
+
+<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
+
+<% include( "/elements/customer-table.html",
+ name_singular => 'payment',
+ header => [ '', 'Amount', 'Check #', '' ],
+ fields => [ sub {'$'}, 'paid', 'payinfo', 'error', ],
+ types => [ 'immutable', '', '', 'immutable', ],
+ align => [ 'c', 'r', 'r', 'l' ],
+ sizes => [ 0, 8, 10, 0, ],
+ colors => [ '', '', '', '#ff0000' ],
+ param => { () },
+ footer => [ '$', '_TOTAL', '', '' ],
+ footer_align => [ 'c', 'r', 'r', '' ],
+ )
+%>
+
+<!-- <BR>
+<INPUT TYPE="button" VALUE="TEST addrow" onclick="addRow()"> -->
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Post payment batch">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
+
+</%init>
diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi
new file mode 100755
index 0000000..6151dce
--- /dev/null
+++ b/httemplate/misc/bill.cgi
@@ -0,0 +1,38 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bill customer now');
+
+#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 $conf = new FS::Conf;
+
+my $error = $cust_main->bill_and_collect( 'fatal' => 'return',
+ 'retry' => 'yes',
+ );
+
+ #'invoice-time'=>$time,
+ #'batch_card'=> 'yes',
+ #'batch_card'=> 'no',
+ #'report_badcard'=> 'yes',
+ #'retry_card' => 'yes',
+
+ #this is used only by cust_main::batch_card
+ #need to pick & create an actual config
+ #value if we're going to turn this on
+ #("realtime-backend" doesn't exist,
+ # "backend-realtime" is for something
+ # entirely different)
+ #'realtime' => $conf->exists('realtime-backend'),
+
+</%init>
diff --git a/httemplate/misc/bulk_change_pkg.cgi b/httemplate/misc/bulk_change_pkg.cgi
new file mode 100755
index 0000000..4964e59
--- /dev/null
+++ b/httemplate/misc/bulk_change_pkg.cgi
@@ -0,0 +1,62 @@
+<% include('/elements/header-popup.html', "Change Packages") %>
+
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+ <BR><BR>
+% }
+
+<FORM ACTION="<% $p %>misc/process/bulk_change_pkg.cgi" METHOD=POST>
+
+%# some false laziness w/search/cust_pkg.cgi
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>">
+% for my $param (qw(agentnum custnum magic status classnum custom censustract)) {
+<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% foreach my $pkgpart ($cgi->param('pkgpart')) {
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart |h %>">
+% }
+%
+% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+%
+ <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>ending" VALUE="<% $cgi->param("${field}.ending") |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TD>New package: </TD>
+ <TD><% include('/elements/select-table.html',
+ 'table' => 'part_pkg',
+ 'name_col' => 'pkg',
+ 'empty_label' => 'Select package',
+ 'label_callback' => sub { $_[0]->pkg_comment },
+ 'element_name' => 'new_pkgpart',
+ 'curr_value' => ( $cgi->param('error')
+ ? scalar($cgi->param('new_pkgpart'))
+ : ''
+ ),
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Change packages">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+</%init>
diff --git a/httemplate/misc/bulk_pkg_increment_bill.cgi b/httemplate/misc/bulk_pkg_increment_bill.cgi
new file mode 100755
index 0000000..d594b55
--- /dev/null
+++ b/httemplate/misc/bulk_pkg_increment_bill.cgi
@@ -0,0 +1,50 @@
+<% include('/elements/header-popup.html', "Increment Next Bill Date") %>
+
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+ <BR><BR>
+% }
+
+<FORM ACTION="<% $p %>misc/process/bulk_pkg_increment_bill.cgi" METHOD=POST>
+
+%# some false laziness w/search/cust_pkg.cgi
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>">
+% for my $param (qw(agentnum custnum magic status classnum custom censustract)) {
+<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
+% }
+%
+% foreach my $pkgpart ($cgi->param('pkgpart')) {
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart |h %>">
+% }
+%
+% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+%
+ <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>ending" VALUE="<% $cgi->param("${field}.ending") |h %>">
+% }
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TD>Days to increment: </TD>
+ <TD><INPUT type="text" name="days"></TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Increment next bill date">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+</%init>
diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi
new file mode 100755
index 0000000..4919c66
--- /dev/null
+++ b/httemplate/misc/cancel-unaudited.cgi
@@ -0,0 +1,33 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2)) %>
+%}
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unprovision customer service')
+ && $FS::CurrentUser::CurrentUser->access_right('View/link unlinked services');
+
+#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 ) {
+ errorpage( '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;
+
+</%init>
diff --git a/httemplate/misc/cancel_cust.html b/httemplate/misc/cancel_cust.html
new file mode 100644
index 0000000..12c37eb
--- /dev/null
+++ b/httemplate/misc/cancel_cust.html
@@ -0,0 +1,63 @@
+<% include('/elements/header-popup.html', 'Cancel customer' ) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="cust_cancel_popup" ACTION="<% popurl(1) %>cust_main-cancel.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+
+ <P ALIGN="center"><B>Permanently delete all services and cancel this customer?</B>
+
+ <% $ban %>
+
+<BR><BR>
+
+<% ntable("#cccccc", 2) %>
+
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'reasonnum',
+ 'reason_class' => 'C',
+ 'cgi' => $cgi,
+ 'control_button' => "document.getElementById('confirm_cancel_cust_button')",
+ )
+%>
+
+</TABLE>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_cust_button" VALUE="Cancel customer" DISABLED> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE="BUTTON" VALUE="Don't cancel" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Cancel customer');
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+die "No customer # $custnum" unless $cust_main;
+
+my $ban = '';
+if ( $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) {
+ $ban = '<BR><P ALIGN="center">'.
+ '<INPUT TYPE="checkbox" NAME="ban" VALUE="1"> Ban this customer\'s ';
+ if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ $ban .= 'credit card';
+ } elsif ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+ $ban .= 'ACH account';
+ }
+}
+
+</%init>
+
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
new file mode 100755
index 0000000..10c0e0d
--- /dev/null
+++ b/httemplate/misc/cancel_pkg.html
@@ -0,0 +1,112 @@
+%# if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+%# } else {
+%# <% include("/elements/header.html", $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>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="sc_popup" ACTION="<% popurl(1) %>process/cancel_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="method" VALUE="<% $method %>">
+
+
+<BR><BR>
+<% ucfirst($method) %> <% $part_pkg->pkg_comment %>
+<% ntable("#cccccc", 2) %>
+
+% if ($method eq 'expire' || $method eq 'adjourn') {
+<TR>
+ <TD><% $submit =~ /^(\w*)\s/ %> package on </TD>
+ <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<% $date |h %>">
+ <IMG SRC="<% $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date">
+ <BR><I>m/d/y</I>
+ </TD>
+</TR>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "expire_date",
+ ifFormat: "<% $date_format %>",
+ button: "expire_button",
+ align: "BR"
+ });
+</SCRIPT>
+%}
+%
+
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'reasonnum',
+ 'reason_class' => $class,
+ 'curr_value' => $reasonnum,
+ 'control_button' => "document.getElementById('confirm_cancel_pkg_button')",
+ )
+%>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_pkg_button" VALUE="<% $submit %>" DISABLED>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $date = time2str($date_format, time);
+
+my($pkgnum, $reasonnum);
+if ( $cgi->param('error') ) {
+ $pkgnum = $cgi->param('pkgnum');
+ $reasonnum = $cgi->param('reasonnum');
+ $date = $cgi->param('date');
+} elsif ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $pkgnum = $1;
+ $reasonnum = '';
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+$cgi->param('method') =~ /^(\w+)$/ or die 'illegal method';
+my $method = $1;
+
+my($class, $submit, $right);
+if ($method eq 'cancel') {
+ $class = 'C';
+ $submit = 'Cancel Now';
+ $right = 'Cancel customer package immediately';
+} elsif ($method eq 'expire') {
+ $class = 'C';
+ $submit = 'Cancel Later';
+ $right = 'Cancel customer package later';
+} elsif ($method eq 'suspend') {
+ $class = 'S';
+ $submit = 'Suspend Now';
+ $right = 'Suspend customer package';
+} elsif ($method eq 'adjourn') {
+ $class = 'S';
+ $submit = "Suspend Later";
+ $right = 'Suspend customer package later';
+} else {
+ die 'illegal query (unknown method param)';
+}
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right($right);
+
+my $title = ucfirst($method) . ' Package';
+
+my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
+ or die "Unknown pkgnum: $pkgnum";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi
new file mode 100755
index 0000000..240f34d
--- /dev/null
+++ b/httemplate/misc/catchall.cgi
@@ -0,0 +1,118 @@
+<% include('/elements/header.html', 'Domain Catchall Edit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/catchall.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum |h %>">
+Service #<FONT SIZE=+1><B><% $svcnum ? $svcnum : ' (NEW)' |h %></B></FONT>
+<BR><BR>
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum |h %>">
+
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+% my $domain = $svc_domain->domain;
+% my $catchall = $svc_domain->catchall;
+
+<INPUT TYPE="hidden" NAME="domain" VALUE="<% $domain |h %>">
+
+Mail to <I>(anything)</I>@<B><% $domain |h %></B> forwards to <SELECT NAME="catchall" SIZE=1>
+% foreach $_ (keys %email) {
+ <OPTION<% $_ eq $catchall ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $email{$_} %>
+% }
+</SELECT>
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall');
+
+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);
+
+</%init>
diff --git a/httemplate/misc/cdr-import.html b/httemplate/misc/cdr-import.html
new file mode 100644
index 0000000..7af6c52
--- /dev/null
+++ b/httemplate/misc/cdr-import.html
@@ -0,0 +1,61 @@
+<% include("/elements/header.html",'Call Detail Record Import') %>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'CDRImportForm',
+ 'action' => 'process/cdr-import.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format', 'cdrbatch', ],
+ 'message' => 'CDR import successful',
+ 'url' => $p."search/cdr.html?cdrbatch=$cdrbatch",
+ )
+%>
+
+Import a file containing Call Detail Records (CDRs).<BR><BR>
+
+<INPUT TYPE="hidden" NAME="cdrbatch" VALUE="<% $cdrbatch %>"%>
+
+<% ntable('#cccccc', 2) %>
+
+ <TR>
+ <TD>CDR Format</TD>
+ <TD>
+ <SELECT NAME="format">
+% foreach my $format ( keys %formats ) {
+ <OPTION VALUE="<% $format %>"><% $formats{$format} %></OPTION>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.InventoryItemImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+tie my %formats, 'Tie::IxHash', FS::cdr->import_formats;
+
+my $cdrbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+</%init>
diff --git a/httemplate/misc/cdr.cgi b/httemplate/misc/cdr.cgi
new file mode 100644
index 0000000..d2ee773
--- /dev/null
+++ b/httemplate/misc/cdr.cgi
@@ -0,0 +1,48 @@
+%# <% $cgi->redirect(popurl(2). "search/cdr.html") %>
+%# i should be a popup and reload my parent... until then, this will do
+<% include('/elements/header.html','CDR update successful') %>
+<% include('/elements/footer.html') %>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit rating data');
+
+$cgi->param('action') =~ /^(new|del|(reprocess|delete) selected)$/
+ or die "Illegal action";
+my $action = $1;
+
+my $cdr;
+if ( $action eq 'new' || $action eq 'del' ) {
+ $cgi->param('acctid') =~ /^(\d+)$/ or die "Illegal acctid";
+ my $acctid = $1;
+ $cdr = qsearchs('cdr', { 'acctid' => $1 })
+ or die "unknown acctid $acctid";
+}
+
+if ( $action eq 'new' ) {
+ my %hash = $cdr->hash;
+ $hash{'freesidestatus'} = '';
+ my $new = new FS::cdr \%hash;
+ my $error = $new->replace($cdr);
+ die $error if $error;
+} elsif ( $action eq 'del' ) {
+ my $error = $cdr->delete;
+ die $error if $error;
+} elsif ( $action =~ /^(reprocess|delete) selected$/ ) {
+ foreach my $acctid (
+ map { /^acctid(\d+)$/; $1; } grep /^acctid\d+$/, $cgi->param
+ ) {
+ my $cdr = qsearchs('cdr', { 'acctid' => $acctid });
+ if ( $action eq 'reprocess selected' && $cdr ) { #new
+ my %hash = $cdr->hash;
+ $hash{'freesidestatus'} = '';
+ my $new = new FS::cdr \%hash;
+ my $error = $new->replace($cdr);
+ die $error if $error;
+ } elsif ( $action eq 'delete selected' && $cdr ) { #del
+ my $error = $cdr->delete;
+ die $error if $error;
+ }
+ }
+}
+
+</%init>
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
new file mode 100755
index 0000000..16b7071
--- /dev/null
+++ b/httemplate/misc/change_pkg.cgi
@@ -0,0 +1,68 @@
+<% include('/elements/header-popup.html', "Change Package") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p %>edit/process/change-cust_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right">Current package</TH>
+ <TD COLSPAN=7>
+ <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
+ </TD>
+ </TR>
+
+ <% include('/elements/tr-select-cust-part_pkg.html',
+ 'pre_label' => 'New',
+ 'curr_value' => scalar($cgi->param('pkgpart')),
+ 'classnum' => $part_pkg->classnum,
+ 'cust_main' => $cust_main,
+ #'extra_sql' => ' AND pkgpart != '. $cust_pkg->pkgpart,
+ )
+ %>
+
+ <% include('/elements/tr-select-cust_location.html',
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_main,
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Change package">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $pkgnum = scalar($cgi->param('pkgnum'));
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg =
+ qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ }) or die "unknown pkgnum $pkgnum";
+
+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 $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/cities.cgi b/httemplate/misc/cities.cgi
new file mode 100644
index 0000000..c92485e
--- /dev/null
+++ b/httemplate/misc/cities.cgi
@@ -0,0 +1,7 @@
+[ <% join(', ', map { qq("$_") } @cities) %> ]
+<%init>
+
+my( $county, $state, $country ) = $cgi->param('arg');
+my @cities = cities($county, $state, $country);
+
+</%init>
diff --git a/httemplate/misc/copy-rate_detail.html b/httemplate/misc/copy-rate_detail.html
new file mode 100644
index 0000000..3d328ce
--- /dev/null
+++ b/httemplate/misc/copy-rate_detail.html
@@ -0,0 +1,61 @@
+<% include( '/elements/header.html', 'Copy rates between plans', menubar(
+ 'View all rate plans' => "${p}browse/rate.cgi",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="process/copy-rate_detail.html">
+
+<% ntable('#cccccc') %>
+
+ <% include( '/elements/tr-justtitle.html', 'value' => 'Copy rates' ) %>
+
+ <% include( '/elements/tr-select-rate.html',
+ 'label' => 'From rate plan',
+ 'element_name' => 'src_ratenum',
+ )
+ %>
+
+ <% include( '/elements/tr-select-rate.html',
+ 'label' => 'To rate plan',
+ 'element_name' => 'dst_ratenum',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2>Copy country codes</TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2>
+
+ <% include( '/elements/checkboxes.html',
+ 'names_list' => [ FS::rate_prefix->all_countrycodes ],
+ 'element_name_prefix' => 'countrycode',
+ )
+ %>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center">
+ <INPUT TYPE="submit" VALUE="Copy rates">
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#should have some javascript that enables submit button only when both src & dst
+#rates are chosen
+
+</%init>
diff --git a/httemplate/misc/counties.cgi b/httemplate/misc/counties.cgi
new file mode 100644
index 0000000..c022a27
--- /dev/null
+++ b/httemplate/misc/counties.cgi
@@ -0,0 +1,7 @@
+[ <% join(', ', map { qq("$_") } @counties) %> ]
+<%init>
+
+my( $state, $country ) = $cgi->param('arg');
+my @counties = counties($state, $country);
+
+</%init>
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
new file mode 100644
index 0000000..524799c
--- /dev/null
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -0,0 +1,30 @@
+<% objToJson( \@return ) %>
+<%init>
+
+my( $custnum, $classnum ) = $cgi->param('arg');
+
+#XXX i guess i should be agent-virtualized. cause "packages a customer can
+#order" is such a huge deal
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+my %hash = ( 'disabled' => '' );
+if ( $classnum > 0 ) {
+ $hash{'classnum'} = $classnum;
+} elsif ( $classnum eq '' || $classnum == 0 ) {
+ $hash{'classnum'} = '';
+} #else -1, all classes, so don't set classnum
+
+my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'hashref' => \%hash,
+ 'extra_sql' =>
+ ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ),
+ 'order_by' => 'ORDER BY pkg',
+});
+
+my @return = map { ( $_->pkgpart, $_->pkg_comment, $_->can_discount ); }
+ #sort { $a->pkg_comment cmp $b->pkg_comment }
+ @part_pkg;
+
+</%init>
diff --git a/httemplate/misc/cust_attachment.cgi b/httemplate/misc/cust_attachment.cgi
new file mode 100644
index 0000000..d1ec777
--- /dev/null
+++ b/httemplate/misc/cust_attachment.cgi
@@ -0,0 +1,34 @@
+<% '',$cgi->redirect(popurl(2). "browse/cust_attachment.html?$browse_opts") %>
+<%init>
+
+$cgi->param('action') =~ /^(Delete|Undelete|Purge) selected$/
+ or die "Illegal action";
+my $action = $1;
+
+my $browse_opts = join(';', map { $_.'='.$cgi->param($_) }
+ qw( orderby show_deleted )
+ );
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right("$action attachment");
+
+foreach my $attachnum (
+ map { /^attachnum(\d+)$/; $1; } grep /^attachnum\d+$/, $cgi->param
+ ) {
+ my $attach = qsearchs('cust_attachment', { 'attachnum' => $attachnum });
+ my $error;
+ if ( $action eq 'Delete' and !$attach->disabled ) {
+ $attach->disabled(time);
+ $error = $attach->replace;
+ }
+ elsif ( $action eq 'Undelete' and $attach->disabled ) {
+ $attach->disabled('');
+ $error = $attach->replace;
+ }
+ elsif ( $action eq 'Purge' and $attach->disabled ) {
+ $error = $attach->delete;
+ }
+ die $error if $error;
+}
+
+</%init>
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
new file mode 100755
index 0000000..009a7d4
--- /dev/null
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -0,0 +1,57 @@
+<% header("Customer cancelled") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer');
+
+my $custnum;
+my $ban = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $ban = $cgi->param('ban');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ || die "Illegal custnum";
+ $custnum = $1;
+}
+
+#false laziness w/process/cancel_pkg.html
+
+#untaint reasonnum
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
+$reasonnum = $1;
+
+if ($reasonnum == -1) {
+ $reasonnum = {
+ 'typenum' => scalar( $cgi->param('newreasonnumT') ),
+ 'reason' => scalar( $cgi->param('newreasonnum' ) ),
+ };
+}
+
+#eslaf
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+
+warn "cancelling $cust_main";
+my @errors = $cust_main->cancel(
+ 'ban' => $ban,
+ 'reason' => $reasonnum,
+);
+my $error = join(' / ', @errors) if scalar(@errors);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(1). "cancel_cust.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
new file mode 100644
index 0000000..9c1f984
--- /dev/null
+++ b/httemplate/misc/cust_main-import.cgi
@@ -0,0 +1,148 @@
+<% include("/elements/header.html",'Batch Customer Import') %>
+
+Import a file containing customer records.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'CustomerImportForm',
+ 'action' => 'process/cust_main-import.cgi',
+ 'num_files' => 1,
+ 'fields' => [ 'agentnum', 'custbatch', 'format' ],
+ 'message' => 'Customer import successful',
+ 'url' => $p."search/cust_main.html?custbatch=$custbatch",
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <% include( '/elements/tr-select-agent.html',
+ #'curr_value' => '', #$agentnum,
+ 'label' => "<B>Agent</B>",
+ 'empty_label' => 'Select agent',
+ )
+ %>
+
+ <INPUT TYPE="hidden" NAME="custbatch" VALUE="<% $custbatch %>"%>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <!-- <OPTION VALUE="simple">Simple -->
+ <OPTION VALUE="extended" SELECTED>Extended
+ <OPTION VALUE="extended-plus_company">Extended plus company
+ <OPTION VALUE="svc_external">External service
+ <OPTION VALUE="svc_external_svc_phone">External service and phone service
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+
+% #include('/elements/tr-select-part_referral.html')
+%
+
+
+<!--
+<TR>
+ <TH>First package</TH>
+ <TD>
+ This needs to be agent-virtualized if it gets used!
+ <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) {
+
+ <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg_comment %></OPTION>
+% }
+
+ </SELECT>
+ </TD>
+</TR>
+-->
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.CustomerImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+<!-- Simple 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> -->
+
+Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. The file should have a .CSV or .XLS extension.
+<BR><BR>
+
+<b>Extended</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, username, _password</i>
+<BR><BR>
+
+<b>Extended plus company</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, username, _password</i>
+<BR><BR>
+
+<b>External service</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, id, title</i>
+<BR><BR>
+
+<b>External service and phone service</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, id, title, countrycode, phonenum, sip_password, pin</i>
+<BR><BR>
+
+<%$req%> Required fields
+<BR><BR>
+
+Field information:
+
+<ul>
+
+ <li><i>agent_custid</i>: This is the reseller's idea of the customer number or identifier. It may be left blank. If specified, it must be unique per-agent.
+
+ <li><i>refnum</i>: Advertising source number - where a customer heard about your service. Configuration -&gt; Miscellaneous -&gt; View/Edit advertising sources. This field has special treatment upon import: If a string is passed instead
+of an integer, the string is searched for and if necessary auto-created in the
+advertising source table.
+
+ <li><i>payinfo</i>: Credit card number, or leave this, <i>paycvv</i> and <i>paydate</i> blank for email/paper invoicing.
+
+ <li><i>paycvv</i>: CVV2 number (three digits on the back of the credit card)
+
+ <li><i>paydate</i>: Credit card expiration date, MM/YYYY or MM/YY (M/YY and M/YYYY are also accepted).
+
+ <li><i>invoicing_list</i>: Email address for invoices, or POST for postal invoices.
+
+ <li><i>pkgpart</i>: Package definition. Configuration -&gt; Provisioning, services and packages -&gt; View/Edit package definitions
+
+ <li><i>username</i> and <i>_password</i> are required if <i>pkgpart</i> is specified. (Extended and Extended plus company formats)
+
+ <li><i>id</i>: External service id, integer
+
+ <li><i>title</i>: External service identifier, text
+
+</ul>
+
+<BR>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+
+my $req = qq!<font color="#ff0000">*</font>!;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $custbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+</%init>
diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi
new file mode 100644
index 0000000..3801929
--- /dev/null
+++ b/httemplate/misc/cust_main-import_charges.cgi
@@ -0,0 +1,22 @@
+<% include('/elements/header.html', '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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/cust_main_note-import.cgi b/httemplate/misc/cust_main_note-import.cgi
new file mode 100644
index 0000000..b93c5c1
--- /dev/null
+++ b/httemplate/misc/cust_main_note-import.cgi
@@ -0,0 +1,207 @@
+<% include("/elements/header.html", 'Batch Customer Note Import') %>
+%
+
+<FORM ACTION="process/cust_main_note-import.cgi" METHOD="POST">
+
+
+<SCRIPT TYPE="text/javascript">
+
+ function clearhint_custnum() {
+
+ if ( this.value == 'Not found' ) {
+ this.value = '';
+ this.style.color = '#000000';
+ }
+
+ }
+
+ function search_custnum() {
+
+ this.style.color = '#000000'
+
+ var custnum_obj = this;
+ var searchrow = this.getAttribute('rownum');
+ var custnum = this.value;
+ var name_obj = document.getElementById('name'+searchrow);
+
+ if ( custnum == 'searching...' || custnum == 'Not found' )
+ return;
+
+ var customer_select = document.getElementById('cust_select'+searchrow);
+
+ if ( custnum == '' ) {
+ customer_select.selectedIndex = 0;
+ return;
+ }
+
+ custnum_obj.value = 'searching...';
+ custnum_obj.disabled = true;
+ custnum_obj.style.backgroundColor = '#dddddd';
+
+
+ //alert('search for custnum ' + custnum + ', row#' + searchrow );
+
+ function search_custnum_update(name) {
+
+ var name = eval('(' + name + ')' );
+
+ custnum_obj.disabled = false;
+ custnum_obj.style.backgroundColor = '#ffffff';
+
+ if ( name.length > 0 ) {
+ //alert('custnum found: ' + name);
+ opt(customer_select,custnum,name,'#000000');
+ customer_select.selectedIndex = customer_select.length - 1;
+ custnum_obj.value = custnum;
+ name_obj.value = name;
+ } else {
+ custnum_obj.value = 'Not found';
+ custnum_obj.style.color = '#ff0000';
+ }
+
+ }
+
+ custnum_search( custnum, search_custnum_update );
+
+ }
+
+ function select_customer() {
+
+ var custnum = this.options[this.selectedIndex].value;
+ var name = this.options[this.selectedIndex].text;
+
+ var searchrow = this.getAttribute('rownum');
+ var custnum_obj = document.getElementById('custnum'+searchrow);
+ var name_obj = document.getElementById('name'+searchrow);
+
+ custnum_obj.value = custnum;
+ custnum_obj.style.color = '#000000';
+
+ name_obj.value = name;
+
+ }
+
+ function opt(what,value,text,color) {
+ var optionName = new Option(text, value, false, false);
+ optionName.style.color = color;
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function previewChanged(what) {
+ var submit_obj = document.getElementById('importsubmit');
+ if (what.checked) {
+ submit_obj.value = 'Preview note import';
+ }else{
+ submit_obj.value = 'Import notes';
+ }
+ }
+
+</SCRIPT>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi',
+ 'subs' => [qw( custnum_search )],
+ )
+%>
+
+% my $fh = $cgi->upload('csvfile');
+% my $csv = new Text::CSV_XS;
+% my $skip_fuzzies = $cgi->param('fuzzies') ? 0 : 1;
+%
+% if ( defined($fh) ) {
+ <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+ <TR>
+ <TH>Cust #</TH>
+ <TH>Customer</TH>
+ <TH>Last</TH>
+ <TH>First</TH>
+ <TH>Note to be added</TH>
+ </TR>
+% my $agentnum => scalar($cgi->param('agentnum')),
+% my $line;
+% my $row = 0;
+% while ( defined($line=<$fh>) ) {
+% $line =~ s/(\S*)\s*$/$1/;
+% $line =~ s/^(.*)(#!).*/$1/;
+%
+% $csv->parse($line) or die "can't parse line: " . $csv->error_input();
+% my $custnum = 0;
+% my @values = $csv->fields();
+% my $last = shift @values;
+% if ($last =~ /^\s*(\d+)\s*$/ ) {
+% $custnum = $1;
+% $last = shift @values;
+% }
+% my $first = shift @values;
+% my $note = join ' ', @values;
+% next unless ( $last || $first || $note );
+% my @cust_main = ();
+% warn "searching for: $last, $first" if ($first || $last);
+% if ($custnum) {
+% @cust_main = qsearch('cust_main', { 'custnum' => $custnum });
+% } else {
+% @cust_main = FS::cust_main::smart_search(
+% 'search' => "$last, $first",
+% 'no_fuzzy_on_exact' => $skip_fuzzies,
+% )
+% if ($first || $last);
+% }
+%
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="custnum<% $row %>" ID="custnum<% $row %>" SIZE=8 MAXLENGTH=12 VALUE="<% $cust_main[0] ? $cust_main[0]->custnum : '' %>" rownum="<% $row %>">
+ <SCRIPT TYPE="text/javascript">
+ var custnum_input<% $row %> = document.getElementById("custnum<% $row %>");
+ custnum_input<% $row %>.onfocus = clearhint_custnum;
+ custnum_input<% $row %>.onchange = search_custnum;
+ </SCRIPT>
+ </TD>
+ <TD>
+ <SELECT NAME="cust_select<% $row %>" ID="cust_select<% $row %>" rownum="<% $row %>">
+ <OPTION VALUE="">---</OPTION>
+% my $i=0;
+% foreach (@cust_main) {
+ <OPTION <% $i ? '' : 'SELECTED' %> VALUE="<% $_->custnum %>"><% $_->name %></OPTION>
+% $i++;
+% }
+ </SELECT>
+ <SCRIPT TYPE="text/javascript">
+ var customer_select<% $row %> = document.getElementById("cust_select<% $row %>");
+ customer_select<% $row %>.onchange = select_customer;
+ </SCRIPT>
+ <INPUT TYPE="hidden" NAME="name<% $row %>" ID="name<% $row %>" VALUE="<% $i ? $cust_main[0]->name : '' %>">
+ </TD>
+ <TD>
+ <% $first %>
+ <INPUT TYPE="hidden" NAME="first<% $row %>" VALUE="<% $first %>">
+ </TD>
+ <TD>
+ <% $last %>
+ <INPUT TYPE="hidden" NAME="last<% $row %>" VALUE="<% $last %>">
+ </TD>
+ <TD>
+ <% $note %>
+ <INPUT TYPE="hidden" NAME="note<% $row %>" VALUE="<% $note %>">
+ </TD>
+ </TR>
+% $row++;
+% }
+ </TABLE>
+ <INPUT TYPE="submit" NAME="submit" ID="importsubmit" VALUE="Import notes">
+ <INPUT TYPE="checkbox" NAME="preview" onchange="previewChanged(this);">
+ Preview mode
+% } else {
+ No file supplied
+% }
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/cust_main_note-import.html b/httemplate/misc/cust_main_note-import.html
new file mode 100644
index 0000000..d8fefa7
--- /dev/null
+++ b/httemplate/misc/cust_main_note-import.html
@@ -0,0 +1,39 @@
+<% include("/elements/header.html",'Batch Customer Note Import') %>
+
+<FORM ACTION="cust_main_note-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+Import a CSV file containing customer notes records.
+<BR><BR>
+
+File format is CSV, with the following field order: <i>[custnum,] last, first, notefield1, notefield2, notefield3...</i>
+<BR>
+The optional custnum field is identified by being numeric.
+Anything after the character sequence #! is ignored.
+<BR><BR>
+
+<% &ntable("#cccccc") %>
+
+<TR>
+ <TH ALIGN="right">CSV filename</TH>
+ <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right">Include additional possibilites when exact match is found</TH>
+ <TD><INPUT TYPE="checkbox" NAME="fuzzies"></TD>
+</TR>
+
+</TABLE>
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Load and match">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
+
diff --git a/httemplate/misc/cust_pay-import.cgi b/httemplate/misc/cust_pay-import.cgi
new file mode 100644
index 0000000..849a25b
--- /dev/null
+++ b/httemplate/misc/cust_pay-import.cgi
@@ -0,0 +1,62 @@
+<% include("/elements/header.html",'Batch Payment Import') %>
+
+Import a CSV file containing customer payments.
+<BR><BR>
+
+<FORM ACTION="process/cust_pay-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+<% &ntable("#cccccc", 2) %>
+
+<% include('/elements/tr-select-agent.html',
+ #'curr_value' => '', #$agentnum,
+ 'label' => "<B>Agent</B>",
+ 'empty_label' => 'Select agent',
+ )
+%>
+
+<TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <OPTION VALUE="simple">Simple
+<!-- <OPTION VALUE="extended" SELECTED>Extended -->
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right">CSV filename</TH>
+ <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+
+<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import CSV file"></TD></TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Simple file format is CSV, with the following field order: <i>custnum, agent_custid, amount, checknum</i>
+<BR><BR>
+
+<!-- Extended file format is not yet defined</i>
+<BR><BR> -->
+
+Field information:
+
+<ul>
+
+ <li><i>custnum</i>: This is the freeside customer number. It may be left blank. If specified, agent_custid must be blank.
+
+ <li><i>agent_custid</i>: This is the reseller's idea of the customer number or identifier. It may be left blank. If specified, custnum must be blank.
+
+ <li><i>amount</i>: A positive numeric value with at most two digits after the decimal point.
+
+ <li><i>checknum</i>: A sequences of digits. May be left blank.
+
+</ul>
+
+<BR>
+
+<% include('/elements/footer.html') %>
diff --git a/httemplate/misc/delay_susp_pkg.html b/httemplate/misc/delay_susp_pkg.html
new file mode 100755
index 0000000..8adc40d
--- /dev/null
+++ b/httemplate/misc/delay_susp_pkg.html
@@ -0,0 +1,73 @@
+%# if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+%# } else {
+%# <% include("/elements/header.html", $title, '') %>
+%# }
+
+<% include('/elements/init_calendar.html') %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="ds_popup" ACTION="<% popurl(1) %>process/delay_susp_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<BR><BR>
+<% "Delay automatic suspension of " .$part_pkg->pkg_comment %>
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD>Delay until</TD>
+ <TD><INPUT TYPE="text" NAME="date" ID="dun_date" VALUE="<% $date |h %>">
+ <IMG SRC="<% $p %>images/calendar.png" ID="dun_button" STYLE="cursor:pointer" TITLE="Select date">
+ <BR><I>m/d/y</I>
+ </TD>
+</TR>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "dun_date",
+ ifFormat: "<% $date_format %>",
+ button: "dun_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% $submit %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $date = time2str($date_format, time);
+
+my($pkgnum);
+if ( $cgi->param('error') ) {
+ $pkgnum = $cgi->param('pkgnum');
+ $date = $cgi->param('date');
+} elsif ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $pkgnum = $1;
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+my $submit = 'Delay Suspension';
+my $right = 'Delay suspension events';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" unless $curuser->access_right($right);
+
+my $title = 'Delay Suspension of Package';
+
+my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
+ or die "Unknown pkgnum: $pkgnum";
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/delete-agent_payment_gateway.cgi b/httemplate/misc/delete-agent_payment_gateway.cgi
new file mode 100644
index 0000000..20a202e
--- /dev/null
+++ b/httemplate/misc/delete-agent_payment_gateway.cgi
@@ -0,0 +1,15 @@
+% die "you don't have the 'Configuration' access right"
+% unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+%
+% my($query) = $cgi->keywords;
+% $query =~ /^(\d+)$/ || die "Illegal agentgatewaynum";
+% my $agentgatewaynum = $1;
+%
+% my $agent_payment_gateway = qsearchs('agent_payment_gateway', {
+% 'agentgatewaynum' => $agentgatewaynum,
+% });
+%
+% my $error = $agent_payment_gateway->delete;
+% errorpage($error) if $error;
+%
+% print $cgi->redirect($p. "browse/agent.cgi");
diff --git a/httemplate/misc/delete-cgp_rule.html b/httemplate/misc/delete-cgp_rule.html
new file mode 100644
index 0000000..a2ba2db
--- /dev/null
+++ b/httemplate/misc/delete-cgp_rule.html
@@ -0,0 +1,23 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "browse/cgp_rule.html?svcnum=". $svcnum) %>
+% }
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary rules
+
+#die "access denied"
+# unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service');
+
+#untaint devicenum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal rulenum";
+my $rulenum = $1;
+
+my $cgp_rule = qsearchs('cgp_rule', { 'rulenum' => $rulenum } );
+my $svcnum = $cgp_rule->svcnum;
+
+my $error = $cgp_rule->delete;
+
+</%init>
diff --git a/httemplate/misc/delete-cust_bill.html b/httemplate/misc/delete-cust_bill.html
new file mode 100644
index 0000000..3a642b0
--- /dev/null
+++ b/httemplate/misc/delete-cust_bill.html
@@ -0,0 +1,21 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+my $custnum = $cust_bill->custnum;
+
+my $error = $cust_bill->delete;
+
+</%init>
diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi
new file mode 100755
index 0000000..03eb472
--- /dev/null
+++ b/httemplate/misc/delete-cust_credit.cgi
@@ -0,0 +1,21 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete credit');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/delete-cust_pay.cgi b/httemplate/misc/delete-cust_pay.cgi
new file mode 100755
index 0000000..38e7e4b
--- /dev/null
+++ b/httemplate/misc/delete-cust_pay.cgi
@@ -0,0 +1,21 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete payment');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/delete-cust_pkg_discount.html b/httemplate/misc/delete-cust_pkg_discount.html
new file mode 100644
index 0000000..0bdaa13
--- /dev/null
+++ b/httemplate/misc/delete-cust_pkg_discount.html
@@ -0,0 +1,32 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+% my $frag = "cust_pkg". $cust_pkg->pkgnum;
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+<% $cgi->redirect($p. "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" ) %>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#XXX ACL to remove discounts
+#die "access denied"
+# unless $curuser->access_right('Delete invoices');
+
+#untaint pkgdiscountnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $pkgdiscountnum = $1;
+
+my $cust_pkg_discount =
+ qsearchs('cust_pkg_discount', {'pkgdiscountnum'=>$pkgdiscountnum});
+my $cust_pkg = $cust_pkg_discount->cust_pkg;
+my $custnum = $cust_pkg->custnum;
+
+$cust_pkg_discount->disabled('Y');
+
+my $error = $cust_pkg_discount->replace;
+
+</%init>
diff --git a/httemplate/misc/delete-cust_refund.cgi b/httemplate/misc/delete-cust_refund.cgi
new file mode 100755
index 0000000..983a79d
--- /dev/null
+++ b/httemplate/misc/delete-cust_refund.cgi
@@ -0,0 +1,21 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete refund');
+
+#untaint refundnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal refundnum";
+my $refundnum = $1;
+
+my $cust_refund = qsearchs('cust_refund',{'refundnum'=>$refundnum});
+my $custnum = $cust_refund->custnum;
+
+my $error = $cust_refund->delete;
+
+</%init>
diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi
new file mode 100755
index 0000000..203ed36
--- /dev/null
+++ b/httemplate/misc/delete-customer.cgi
@@ -0,0 +1,64 @@
+<% include('/elements/header.html', 'Delete customer') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/delete-customer.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum |h %>">
+
+%if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) {
+ Move uncancelled packages to customer number
+ <INPUT TYPE="text" NAME="new_custnum" VALUE="<% $new_custnum |h %>"><BR><BR>
+%}
+
+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>
+
+<% include('/elements/footer.html') %>
+
+%#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.
+
+<%init>
+
+my $conf = new FS::Conf;
+die "Customer deletions not enabled in configuration"
+ unless $conf->exists('deletecustomers');
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete customer');
+
+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( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} )
+ or die 'Unknown custnum';
+
+</%init>
diff --git a/httemplate/misc/delete-domain_record.cgi b/httemplate/misc/delete-domain_record.cgi
new file mode 100755
index 0000000..08eedde
--- /dev/null
+++ b/httemplate/misc/delete-domain_record.cgi
@@ -0,0 +1,20 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/svc_domain.cgi?". $domain_record->svcnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/delete-mailinglistmember.html b/httemplate/misc/delete-mailinglistmember.html
new file mode 100644
index 0000000..6b91de8
--- /dev/null
+++ b/httemplate/misc/delete-mailinglistmember.html
@@ -0,0 +1,20 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p."search/mailinglistmember.html?listnum=$listnum") %>
+% }
+<%init>
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal devicenum";
+my $membernum = $1;
+
+my $mailinglistmember =
+ qsearchs('mailinglistmember', { 'membernum' => $membernum } )
+ or die "unknown membernum $membernum";
+
+my $listnum = $mailinglistmember->listnum;
+
+my $error = $mailinglistmember->delete;
+
+</%init>
diff --git a/httemplate/misc/delete-part_export.cgi b/httemplate/misc/delete-part_export.cgi
new file mode 100755
index 0000000..52404e0
--- /dev/null
+++ b/httemplate/misc/delete-part_export.cgi
@@ -0,0 +1,20 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "browse/part_export.cgi") %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/delete-phone_device.html b/httemplate/misc/delete-phone_device.html
new file mode 100755
index 0000000..7220c41
--- /dev/null
+++ b/httemplate/misc/delete-phone_device.html
@@ -0,0 +1,23 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/svc_phone.cgi?". $svcnum) %>
+% }
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+#untaint devicenum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal devicenum";
+my $devicenum = $1;
+
+my $phone_device = qsearchs('phone_device', { 'devicenum' => $devicenum } );
+my $svcnum = $phone_device->svcnum;
+
+my $error = $phone_device->delete;
+
+</%init>
diff --git a/httemplate/misc/disable-payment_gateway.cgi b/httemplate/misc/disable-payment_gateway.cgi
new file mode 100644
index 0000000..13e1f92
--- /dev/null
+++ b/httemplate/misc/disable-payment_gateway.cgi
@@ -0,0 +1,25 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+%#<% $cgi->redirect(popurl(2). "browse/payment_gateway.html?showdiabled=$showdisabled") %>
+<% $cgi->redirect(popurl(2). "browse/payment_gateway.html") %>
+%}
+<%init>
+
+die "access deined"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#my $showdisabled = 0;
+#$cgi->param('showdisabled') =~ /^(\d+)$/ and $showdisabled = $1;
+
+#$cgi->param('gatewaynum') =~ /^(\d+)$/ or die 'illegal gatewaynum';
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ or die 'illegal gatewaynum';
+my $gatewaynum = $1;
+
+my $payment_gateway =
+ qsearchs('payment_gateway', { 'gatewaynum' => $gatewaynum } );
+
+my $error = $payment_gateway->disable;
+
+</%init>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
new file mode 100644
index 0000000..01bf5d2
--- /dev/null
+++ b/httemplate/misc/download-batch.cgi
@@ -0,0 +1,23 @@
+<% $pay_batch->export_batch($format) %>
+
+<%init>
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
+
+my $batchnum;
+if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
+ $batchnum = $1;
+} else {
+ die "No batch number (bad URL) \n";
+}
+
+my $format;
+if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
+ $format = $1;
+}
+
+my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
+die "Batch not found: '$batchnum'" if !$pay_batch;
+
+</%init>
diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi
new file mode 100644
index 0000000..3b60b20
--- /dev/null
+++ b/httemplate/misc/dump.cgi
@@ -0,0 +1,20 @@
+% die "access denied"
+% unless $FS::CurrentUser::CurrentUser->access_right('Export');
+%
+% 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 {
+% errorpage("don't (yet) know how to dump ". driver_name. " databases");
+% }
+%
+% http_header('Content-Type' => 'text/plain' );
+%
+% while (<DUMP>) {
+% print $_;
+% }
+% close DUMP;
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
new file mode 100644
index 0000000..201aed4
--- /dev/null
+++ b/httemplate/misc/email-customers.html
@@ -0,0 +1,145 @@
+<% include('/elements/header.html', $title) %>
+
+<FORM NAME="OneTrueForm" ACTION="email-customers.html" METHOD="POST">
+% foreach my $key ( keys %search ) {
+% my @values = ref($search{$key}) ? @{$search{$key}} : ( $search{$key} );
+% foreach my $value ( @values ) {
+ <INPUT TYPE="hidden" NAME="<% $key %>" VALUE="<% $value %>">
+% }
+% }
+
+% if ( $cgi->param('magic') eq 'send' ) {
+
+ <FONT SIZE="+2">Sending notice</FONT>
+
+ <% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [ keys(%search), qw( from subject html_body text_body ) ],
+ 'process/email-customers.html',
+ { 'message' => "Notice sent" }, #would be nice to show #, but..
+ )
+ %>
+
+% } elsif ( $cgi->param('magic') eq 'preview' ) {
+
+ <FONT SIZE="+2">Preview notice</FONT>
+
+% }
+
+% if ( $cgi->param('magic') ) {
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <% include('/elements/tr-fixed.html',
+ 'field' => 'from',
+ 'label' => 'From:',
+ 'value' => scalar( $cgi->param('from') ),
+ )
+ %>
+
+ <% include('/elements/tr-fixed.html',
+ 'field' => 'subject',
+ 'label' => 'Subject:',
+ 'value' => scalar( $cgi->param('subject') ),
+ )
+ %>
+
+ <INPUT TYPE="hidden" NAME="html_body" VALUE="<% $cgi->param('html_body') |h %>">
+ <TR>
+ <TD ALIGN="right" VALIGN="top">Message (HTML display): </TD>
+ <TD CLASS="background" ALIGN="left"><% $cgi->param('html_body') %></TD>
+ </TR>
+
+% my $text_body = HTML::FormatText->new(leftmargin=>0)->format(
+% HTML::TreeBuilder->new_from_content(
+% $cgi->param('html_body')
+% )
+% );
+ <INPUT TYPE="hidden" NAME="text_body" VALUE="<% $text_body |h %>">
+ <TR>
+ <TD ALIGN="right" VALIGN="top">Message (Text display): </TD>
+ <TD CLASS="background" ALIGN="left"><PRE><% $text_body %></PRE></TD>
+ </TR>
+
+ </TABLE>
+
+% if ( $cgi->param('magic') eq 'preview' ) {
+
+ <SCRIPT>
+ function areyousure(href) {
+ return confirm("Send this notice to <% $num_cust %> customers?");
+ }
+ </SCRIPT>
+
+ <BR>
+ <INPUT TYPE="hidden" NAME="magic" VALUE="send">
+ <INPUT TYPE="submit" VALUE="Send notice" onClick="return areyousure()">
+
+% }
+
+% } else {
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%">
+
+ <% include('/elements/tr-input-text.html',
+ 'field' => 'from',
+ 'label' => 'From:',
+ )
+ %>
+
+ <% include('/elements/tr-input-text.html',
+ 'field' => 'subject',
+ 'label' => 'Subject:',
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top">Message: </TD>
+ <TD><% include('/elements/htmlarea.html', 'field'=>'html_body') %></TD>
+ </TR>
+
+ </TABLE>
+
+%#Substitution vars:
+
+ <BR><BR>
+ <INPUT TYPE="hidden" NAME="magic" VALUE="preview">
+ <INPUT TYPE="submit" VALUE="Preview notice">
+
+% }
+
+</FORM>
+
+% if ( $cgi->param('magic') eq 'send' ) {
+ <SCRIPT TYPE="text/javascript">
+ process();
+ </SCRIPT>
+% }
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices');
+
+my %search = $cgi->Vars;
+delete $search{$_} for qw( magic from subject html_body text_body );
+$search{$_} = [ split(/\0/, $search{$_}) ]
+ foreach grep { $_ eq 'payby' || $search{$_} =~ /\0/ } keys %search;
+
+my $title = 'Bulk send customer notices';
+
+my $num_cust;
+if ( $cgi->param('magic') eq 'preview' ) {
+ my $sql_query = FS::cust_main->search(\%search);
+ my $count_query = delete($sql_query->{'count_query'});
+ my $count_sth = dbh->prepare($count_query)
+ or die "Error preparing $count_query: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $count_query: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+ $num_cust = $count_arrayref->[0];
+}
+
+</%init>
diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi
new file mode 100755
index 0000000..269722f
--- /dev/null
+++ b/httemplate/misc/email-invoice.cgi
@@ -0,0 +1,19 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $template = $2;
+my $invnum = $3;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+$cust_bill->email($template);
+
+my $custnum = $cust_bill->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/email_events.cgi b/httemplate/misc/email_events.cgi
new file mode 100644
index 0000000..e7a0e77
--- /dev/null
+++ b/httemplate/misc/email_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_event::process_reemail', $cgi;
+
+</%init>
diff --git a/httemplate/misc/email_invoice_events.cgi b/httemplate/misc/email_invoice_events.cgi
new file mode 100644
index 0000000..d65fe17
--- /dev/null
+++ b/httemplate/misc/email_invoice_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail', $cgi;
+
+</%init>
diff --git a/httemplate/misc/email_invoices.cgi b/httemplate/misc/email_invoices.cgi
new file mode 100644
index 0000000..78ca0f6
--- /dev/null
+++ b/httemplate/misc/email_invoices.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail', $cgi;
+
+</%init>
diff --git a/httemplate/misc/enable_or_disable_tax.html b/httemplate/misc/enable_or_disable_tax.html
new file mode 100755
index 0000000..0efd07d
--- /dev/null
+++ b/httemplate/misc/enable_or_disable_tax.html
@@ -0,0 +1,37 @@
+<% include('/elements/header-popup.html', ucfirst($action). ' Tax Rates') %>
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/enable_or_disable_tax.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="action" VALUE="<% $action %>">
+<INPUT TYPE="hidden" NAME="data_vendor" VALUE="<% $cgi->param('data_vendor') %>">
+<INPUT TYPE="hidden" NAME="geocode" VALUE="<% $cgi->param('geocode') %>">
+<INPUT TYPE="hidden" NAME="taxclassnum" VALUE="<% $cgi->param('taxclassnum') %>">
+<INPUT TYPE="hidden" NAME="tax_type" VALUE="<% $cgi->param('tax_type') %>">
+<INPUT TYPE="hidden" NAME="tax_cat" VALUE="<% $cgi->param('tax_cat') %>">
+<INPUT TYPE="hidden" NAME="showdisabled" VALUE="<% $cgi->param('showdisabled') |h %>">
+
+This will <B><% $action %></B> <% $count %> tax
+<% $count == 1 ? 'rate' : 'rates' %>. Are you <B>certain</B> you want to do
+this?
+<BR><BR><INPUT TYPE="submit" VALUE="Yes">
+</FORM>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $action = '';
+if ( $cgi->param('action') =~ /^(\w+)$/ ) {
+ $action = $1;
+}
+
+my ($query, $count_query) = FS::tax_rate::browse_queries(scalar($cgi->Vars));
+
+my $count_sth = dbh->prepare($count_query)
+ or die "Error preparing $count_query: ". dbh->errstr;
+$count_sth->execute
+ or die "Error executing $count_query: ". $count_sth->errstr;
+my $count = $count_sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi
new file mode 100644
index 0000000..f5860cf
--- /dev/null
+++ b/httemplate/misc/exchanges.cgi
@@ -0,0 +1,24 @@
+%# [ <% join(', ', map { qq("$_") } @exchanges) %> ]
+<% objToJson(\@exchanges) %>
+<%init>
+
+my( $areacode, $svcpart ) = $cgi->param('arg');
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+} elsif ( ! @exports ) {
+ die "no DID providing export attached to svcpart $svcpart";
+}
+my $export = $exports[0];
+
+my $something = $export->get_dids('areacode'=>$areacode);
+
+#warn Dumper($something);
+
+my @exchanges = @{ $something };
+
+</%init>
diff --git a/httemplate/misc/fax-invoice.cgi b/httemplate/misc/fax-invoice.cgi
new file mode 100755
index 0000000..2591fce
--- /dev/null
+++ b/httemplate/misc/fax-invoice.cgi
@@ -0,0 +1,19 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $template = $2;
+my $invnum = $3;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+$cust_bill->fax_invoice($template);
+
+my $custnum = $cust_bill->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/fax_events.cgi b/httemplate/misc/fax_events.cgi
new file mode 100644
index 0000000..39cba07
--- /dev/null
+++ b/httemplate/misc/fax_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_event::process_refax', $cgi;
+
+</%init>
diff --git a/httemplate/misc/fax_invoice_events.cgi b/httemplate/misc/fax_invoice_events.cgi
new file mode 100644
index 0000000..05420ee
--- /dev/null
+++ b/httemplate/misc/fax_invoice_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax', $cgi;
+
+</%init>
diff --git a/httemplate/misc/fax_invoices.cgi b/httemplate/misc/fax_invoices.cgi
new file mode 100644
index 0000000..a843523
--- /dev/null
+++ b/httemplate/misc/fax_invoices.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax', $cgi;
+
+</%init>
diff --git a/httemplate/misc/file-upload.html b/httemplate/misc/file-upload.html
new file mode 100644
index 0000000..469274c
--- /dev/null
+++ b/httemplate/misc/file-upload.html
@@ -0,0 +1,53 @@
+<% include('/elements/header-minimal.html', 'File Upload') %>
+% if ($error) {
+Error: <% $error %>
+% }else{
+File Upload Successful <% join(',', @filenames) %>;
+% }
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import'); #?
+
+my @filenames = ();
+my $error = ''; # could be extended to the access control
+
+$cgi->param('upload_fields') =~ /^([,\w]+)$/
+ or $error = "invalid upload_fields";
+my $fields = $1;
+
+my $dir = $FS::UID::cache_dir. "/cache.". $FS::UID::datasrc;
+
+foreach my $field (split /,/, $fields) {
+ next if $error;
+
+ my $fh = $cgi->upload($field)
+ or $error = "No valid file was provided.";
+
+ my $suffix = '';
+ if ( $cgi->param($field) =~ /(\.\w+)$/i ) {
+ $suffix = lc($1);
+ }
+
+ my $sh = new File::Temp( TEMPLATE => 'upload.XXXXXXXX',
+ SUFFIX => $suffix,
+ DIR => $dir,
+ UNLINK => 0,
+ )
+ or $error ||= "can't open temporary file to store upload: $!\n";
+
+ unless ($error) {
+ while(<$fh>) {
+ print $sh $_;
+ }
+ $sh->filename =~ m!.*/([.\w]+)$!;
+ push @filenames, "$field:$1";
+ close $sh
+ }
+
+}
+
+$error = "No files" unless scalar(@filenames);
+
+</%init>
diff --git a/httemplate/misc/ftp_invoices.cgi b/httemplate/misc/ftp_invoices.cgi
new file mode 100644
index 0000000..9a072b9
--- /dev/null
+++ b/httemplate/misc/ftp_invoices.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reftp', $cgi;
+
+</%init>
diff --git a/httemplate/misc/inventory_item-import.html b/httemplate/misc/inventory_item-import.html
new file mode 100644
index 0000000..990c14f
--- /dev/null
+++ b/httemplate/misc/inventory_item-import.html
@@ -0,0 +1,70 @@
+<% include("/elements/header.html", PL($inventory_class->classname)) %>
+
+Import a file containing <% PL($inventory_class->classname) %>, one per line.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'InventoryItemImportForm',
+ 'action' => 'process/inventory_item-import.html',
+ 'num_files' => 1,
+ #'fields' => [ 'format', 'itembatch', 'classnum', 'agentnum' ],
+ 'fields' => [ 'format', 'classnum', 'agentnum', ],
+ 'message' => 'Inventory import successful',
+ #XXX redirect via $itembatch? or just back to class browse?
+ #'url' => $p."search/phone_avail.html?availbatch=$availbatch",
+ 'url' => $p."search/inventory_item.html?classnum=$classnum;avail=1",
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <INPUT TYPE="hidden" NAME="classnum" VALUE="<% $classnum %>">
+
+%# <INPUT TYPE="hidden" NAME="itembatch" VALUE="<% $itembatch %>">
+
+ <% include('/elements/tr-select-agent.html') %>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.InventoryItemImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Upload file can be a text file or Excel spreadsheet. If an Excel spreadsheet,
+ should have an .XLS extension.
+<BR><BR>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+$cgi->param('classnum') =~ /^(\d+)$/ or errorpage("illegal classnum");
+my $classnum = $1;
+my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } );
+
+#my $conf = new FS::Conf;
+#my $itembatch =
+# time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+</%init>
diff --git a/httemplate/misc/inventory_item-move.cgi b/httemplate/misc/inventory_item-move.cgi
new file mode 100644
index 0000000..4d53beb
--- /dev/null
+++ b/httemplate/misc/inventory_item-move.cgi
@@ -0,0 +1,23 @@
+<% '',$cgi->redirect(popurl(2). "search/inventory_item.html?$browse_opts") %>
+<%init>
+
+# Shamelessly copied from misc/cust_attachment.cgi.
+
+my $browse_opts = join(';', map { $_.'='.$cgi->param($_) }
+ qw( classnum avail )
+ );
+
+my $move_agentnum = $cgi->param('move_agentnum') or
+ die "No agent selected";
+foreach my $itemnum (
+ map { /^itemnum(\d+)$/; $1; } grep /^itemnum\d+$/, $cgi->param ) {
+ my $item = qsearchs('inventory_item', { 'itemnum' => $itemnum });
+# die "Can't move assigned inventory item $itemnum" if $item->svcnum;
+ my $error;
+ $item->agentnum($move_agentnum);
+ $error = $item->replace;
+ die $error if $error;
+}
+
+</%init>
+
diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi
new file mode 100755
index 0000000..f37f769
--- /dev/null
+++ b/httemplate/misc/link.cgi
@@ -0,0 +1,85 @@
+<% include("/elements/header.html","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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View/link unlinked services');
+
+my %link_field = (
+ 'svc_acct' => 'username',
+ 'svc_domain' => 'domain',
+ 'svc_phone' => 'phonenum',
+);
+
+my %link_field2 = (
+ 'svc_acct' => { label => 'Domain',
+ field => 'domsvc',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain'
+ },
+);
+
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+my $pkgnum = $1;
+$cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+my $svcpart = $1;
+
+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};
+
+</%init>
diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi
new file mode 100644
index 0000000..82ad636
--- /dev/null
+++ b/httemplate/misc/location.cgi
@@ -0,0 +1,30 @@
+<% objToJson(\%hash) %>
+<%init>
+
+my $locationnum = $cgi->param('arg');
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $cust_location = qsearchs({
+ 'select' => 'cust_location.*',
+ 'table' => 'cust_location',
+ 'hashref' => { 'locationnum' => $locationnum },
+ 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ',
+ ' LEFT JOIN prospect_main USING ( prospectnum ) ',
+ 'extra_sql' => ' AND ( '.
+ ' ( custnum IS NOT NULL AND '.
+ $curuser->agentnums_sql( table=>'cust_main' ).
+ ' ) '.
+ ' OR '.
+ ' ( prospectnum IS NOT NULL AND '.
+ $curuser->agentnums_sql( table=>'prospect_main' ).
+ ' ) '.
+ ' )',
+});
+
+my %hash = ();
+%hash = map { $_ => $cust_location->$_() }
+ qw( address1 address2 city county state zip country )
+ if $cust_location;
+
+</%init>
diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi
new file mode 100644
index 0000000..8c158bd
--- /dev/null
+++ b/httemplate/misc/meta-import.cgi
@@ -0,0 +1,79 @@
+<% include('/elements/header.html', '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_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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+#there's no ACL for this... haven't used in ages
+die 'meta-import not enabled; remove this if you want to use it';
+
+</%init>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
new file mode 100644
index 0000000..33b2bb3
--- /dev/null
+++ b/httemplate/misc/order_pkg.html
@@ -0,0 +1,134 @@
+<% include('/elements/header-popup.html', 'Order new package' ) %>
+
+<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>
+
+<SCRIPT TYPE="text/javascript">
+
+ function enable_order_pkg () {
+ if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) {
+ document.OrderPkgForm.submit.disabled = false;
+ if ( document.OrderPkgForm.pkgpart.options[document.OrderPkgForm.pkgpart.selectedIndex].getAttribute('data-can_discount') == 1 ) {
+ document.OrderPkgForm.discountnum.disabled = false;
+ } else {
+ document.OrderPkgForm.discountnum.disabled = true;
+ }
+ } else {
+ document.OrderPkgForm.submit.disabled = true;
+ document.OrderPkgForm.discountnum.disabled = true;
+ }
+ }
+
+</SCRIPT>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>">
+
+<% ntable("#cccccc", 2) %>
+<% include('/elements/tr-select-cust-part_pkg.html',
+ 'curr_value' => $pkgpart,
+ 'classnum' => -1,
+ 'cust_main' => $cust_main,
+ 'onchange' => 'enable_order_pkg',
+ )
+%>
+
+%# false laziness w/edit/quick-charge.html
+<TR>
+ <TH ALIGN="right">Start date </TD>
+ <TD COLSPAN=6>
+ <INPUT TYPE = "text"
+ NAME = "start_date"
+ SIZE = 32
+ ID = "start_date_text"
+ VALUE = "<% $start_date %>"
+ >
+ <IMG SRC = "../images/calendar.png"
+ ID = "start_date_button"
+ STYLE = "cursor: pointer"
+ TITLE = "Select date"
+ >
+ <FONT SIZE=-1>(leave blank to start immediately)</FONT>
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "start_date_text",
+ ifFormat: "<% $date_format %>",
+ button: "start_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
+% my $what = lc(FS::payby->shortname($cust_main->payby));
+ <TR>
+ <TH ALIGN="right">Disable automatic <% $what %> charge </TH>
+ <TD COLSPAN=6><INPUT TYPE="checkbox" NAME="no_auto" VALUE="Y"></TD>
+ </TR>
+% }
+
+% if ( $curuser->access_right('Discount customer package') ) {
+ <% include('/elements/tr-select-discount.html',
+ 'element_etc' => 'DISABLED',
+ 'colspan' => 7,
+ 'cgi' => $cgi,
+ )
+ %>
+% }
+
+% if ( $conf->exists('pkg_referral') ) {
+ <% include('/elements/tr-select-part_referral.html',
+ 'curr_value' => scalar( $cgi->param('refnum') ), #get rid of empty_label first# || $cust_main->refnum,
+ 'disable_empty' => 1,
+ 'multiple' => $conf->exists('pkg_referral-multiple'),
+ 'colspan' => 7,
+ )
+ %>
+% }
+
+<% include('/elements/tr-select-cust_location.html',
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_main,
+ )
+%>
+
+</TABLE>
+
+<BR>
+<INPUT NAME="submit" TYPE="submit" VALUE="Order Package" <% $pkgpart ? '' : 'DISABLED' %>>
+
+</FORM>
+</BODY>
+</HTML>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Order customer package');
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die "no custnum";
+my $custnum = $1;
+my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+
+my $pkgpart = scalar($cgi->param('pkgpart'));
+
+my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
+my $start_date = $cust_main->next_bill_date;
+$start_date = $start_date ? time2str($format, $start_date) : '';
+
+</%init>
diff --git a/httemplate/misc/part_device-import.html b/httemplate/misc/part_device-import.html
new file mode 100644
index 0000000..7bd6404
--- /dev/null
+++ b/httemplate/misc/part_device-import.html
@@ -0,0 +1,53 @@
+<% include("/elements/header.html", 'Import device types') %>
+
+Import a file containing phone device types, one per line.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'PartDeviceImportForm',
+ 'action' => 'process/part_device-import.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format', ],
+ 'message' => 'Device type import successful',
+ 'url' => $p.'browse/part_device.html',
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.PartDeviceImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Upload file can be a text file or Excel spreadsheet. If an Excel spreadsheet,
+ should have an .XLS extension.
+<BR><BR>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi
new file mode 100644
index 0000000..0602561
--- /dev/null
+++ b/httemplate/misc/part_svc-columns.cgi
@@ -0,0 +1,13 @@
+<% objToJson(\@output) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $pkgpart_svcpart = $cgi->param('arg');
+$pkgpart_svcpart =~ /^\d+_(\d+)$/;
+my $part_svc = qsearchs('part_svc', { 'svcpart' => $1 }) if $1;
+
+my @output = map { ( $_->columnname, $_->columnflag, $_->columnvalue ) }
+ $part_svc->all_part_svc_column;
+
+</%init>
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
new file mode 100644
index 0000000..813b560
--- /dev/null
+++ b/httemplate/misc/payment.cgi
@@ -0,0 +1,335 @@
+<% include( '/elements/header.html', "Process $type{$payby} payment" ) %>
+<% include( '/elements/small_custview.html', $cust_main, '', '', popurl(2) . "view/cust_main.cgi" ) %>
+<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="payunique" VALUE="<% $payunique %>">
+<INPUT TYPE="hidden" NAME="balance" VALUE="<% $balance %>">
+
+<% include('/elements/init_overlib.html') %>
+
+% #include( '/elements/table.html', '#cccccc' )
+
+<% ntable('#cccccc') %>
+ <TR>
+ <TH ALIGN="right">Payment amount</TH>
+ <TD COLSPAN=7>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ <% $money_char %><INPUT NAME = "amount"
+ TYPE = "text"
+ VALUE = "<% $amount %>"
+ SIZE = 8
+ STYLE = "text-align:right;"
+% if ( $fee ) {
+ onChange = "amount_changed(this)"
+ onKeyDown = "amount_changed(this)"
+ onKeyUp = "amount_changed(this)"
+ onKeyPress = "amount_changed(this)"
+% }
+ >
+ </TD><TD BGCOLOR="#cccccc">
+% if ( $fee ) {
+ <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>">
+ <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>">
+ <B><FONT SIZE='+1'><% $fee_op %></FONT>
+ <% $money_char . $fee %>
+ </B>
+ <% $fee_pkg->pkg |h %>
+ <B><FONT SIZE='+1'>=</FONT></B>
+ </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue">
+ <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>
+
+% }
+ </TD></TR></TABLE>
+ </TD>
+ </TR>
+
+% if ( $fee ) {
+
+ <SCRIPT TYPE="text/javascript">
+
+ function amount_changed(what) {
+
+
+ var total = '';
+ if ( what.value.length ) {
+ total = parseFloat(what.value) <% $fee_op %> <% $fee %>;
+ /* total = Math.round(total*100)/100; */
+ total = '<% $money_char %>' + total.toFixed(2);
+ }
+
+ var total_cell = document.getElementById('ajax_total_cell');
+ total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>';
+
+ }
+
+ </SCRIPT>
+
+% }
+
+
+% if ( $payby eq 'CARD' ) {
+%
+% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
+% my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
+% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+% $payinfo = $cust_main->paymask;
+% $paycvv = $cust_main->paycvv;
+% ( $month, $year ) = $cust_main->paydate_monthyear;
+% $payname = $cust_main->payname if $cust_main->payname;
+% }
+
+ <TR>
+ <TH ALIGN="right">Card&nbsp;number</TH>
+ <TD COLSPAN=7>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%$payinfo%>"> </TD>
+ <TH>Exp.</TH>
+ <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>
+ <TH ALIGN="right">CVV2</TH>
+ <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<% $paycvv %>" SIZE=4 MAXLENGTH=4>
+ (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)
+ </TD>
+ </TR>
+ <TR>
+ <TH ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TH>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%$payname%>"></TD>
+ </TR>
+
+ <% include( '/elements/location.html',
+ 'object' => $cust_main, #XXX errors???
+ 'no_asterisks' => 1,
+ 'address1_label' => 'Card billing address',
+ )
+ %>
+
+% } elsif ( $payby eq 'CHEK' ) {
+%
+% my( $payinfo1, $payinfo2, $payname, $ss, $paytype, $paystate,
+% $stateid, $stateid_state )
+% = ( '', '', '', '', '', '', '', '' );
+% if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+% $cust_main->paymask =~ /^([\dx]+)\@([\dx]+)$/i
+% or die "unparsable payinfo ". $cust_main->payinfo;
+% ($payinfo1, $payinfo2) = ($1, $2);
+% $payname = $cust_main->payname;
+% $ss = $cust_main->ss;
+% $paytype = $cust_main->getfield('paytype');
+% $paystate = $cust_main->getfield('paystate');
+% $stateid = $cust_main->getfield('stateid');
+% $stateid_state = $cust_main->getfield('stateid_state');
+% }
+
+ <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>
+ <TD ALIGN="right">Type</TD>
+ <TD><SELECT NAME="paytype"><% join('', map { qq!<OPTION VALUE="$_" !.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>" } @FS::cust_main::paytypes) %></SELECT></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:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Bank&nbsp;name</TD>
+ <TD><INPUT TYPE="text" NAME="payname" VALUE="<%$payname%>"></TD>
+ </TR>
+
+% if ( $conf->exists('show_bankstate') ) {
+ <TR>
+ <TD ALIGN="right">Bank&nbsp;state</TD>
+ <TD><% include('/elements/select-state.html',
+ 'disable_empty' => 0,
+ 'empty_label' => '(choose)',
+ 'state' => $paystate,
+ 'country' => $cust_main->country,
+ 'prefix' => 'pay',
+ )
+ %>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="paystate" VALUE="<% $paystate %>">
+% }
+
+% if ( $conf->exists('show_ss') ) {
+ <TR>
+ <TD ALIGN="right">
+ Account&nbsp;holder<BR>
+ Social&nbsp;security&nbsp;or&nbsp;tax&nbsp;ID&nbsp;#
+ </TD>
+ <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $ss %>"></TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="ss" VALUE="<% $ss %>"></TD>
+% }
+
+% if ( $conf->exists('show_stateid') ) {
+ <TR>
+ <TD ALIGN="right">
+ Account&nbsp;holder<BR>
+ Driver&rsquo;s&nbsp;license&nbsp;or&nbsp;state&nbsp;ID&nbsp;#
+ </TD>
+ <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $stateid %>"></TD>
+ <TD ALIGN="right">State</TD>
+ <TD><% include('/elements/select-state.html',
+ 'disable_empty' => 0,
+ 'empty_label' => '(choose)',
+ 'state' => $stateid_state,
+ 'country' => $cust_main->country,
+ 'prefix' => 'stateid_',
+ )
+ %>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="stateid" VALUE="<% $stateid %>">
+ <INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $stateid_state %>">
+% }
+
+% } #end CARD/CHEK-specific section
+
+
+<TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
+ Remember this information
+ </TD>
+</TR>
+
+% if ( $conf->exists("batch-enable")
+% || grep $payby eq $_, $conf->config('batch-enable_payby')
+% ) {
+%
+% if ( grep $payby eq $_, $conf->config('realtime-disable_payby') ) {
+
+ <INPUT TYPE="hidden" NAME="batch" VALUE="1">
+
+% } else {
+
+ <TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox" NAME="batch" VALUE="1">
+ Add to current batch
+ </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>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Process payment');
+
+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 = '';
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+#false laziness w/selfservice make_payment.html shortcut for one-country
+my %states = map { $_->state => 1 }
+ qsearch('cust_main_county', {
+ 'country' => $conf->config('countrydefault') || 'US'
+ } );
+my @states = sort { $a cmp $b } keys %states;
+
+my $fee = '';
+my $fee_pkg = '';
+my $fee_display = '';
+my $fee_op = '';
+my $num_payments = scalar($cust_main->cust_pay);
+#handle old cust_main.pm (remove...)
+$num_payments = scalar( @{ [ $cust_main->cust_pay ] } )
+ unless defined $num_payments;
+if ( $conf->config('manual_process-pkgpart')
+ and ! $conf->exists('manual_process-skip_first') || $num_payments
+ )
+{
+
+ $fee_display = $conf->config('manual_process-display') || 'add';
+ $fee_op = $fee_display eq 'add' ? '+' : '-';
+
+ $fee_pkg =
+ qsearchs('part_pkg', { pkgpart=>$conf->config('manual_process-pkgpart') } );
+
+ #well ->unit_setup or ->calc_setup both call for a $cust_pkg
+ # (though ->unit_setup doesn't use it...)
+ $fee = $fee_pkg->option('setup_fee')
+ if $fee_pkg; #in case.. better than dying with a perl traceback
+
+}
+
+my $amount = '';
+if ( $balance > 0 ) {
+ $amount = $balance;
+ $amount += $fee
+ if $fee && $fee_display eq 'subtract';
+ $amount = sprintf("%.2f", $amount);
+}
+
+my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32;
+
+</%init>
diff --git a/httemplate/misc/phone_avail-import.html b/httemplate/misc/phone_avail-import.html
new file mode 100644
index 0000000..1f4d8ca
--- /dev/null
+++ b/httemplate/misc/phone_avail-import.html
@@ -0,0 +1,88 @@
+<% include('/elements/header.html', 'Phone number (DID) import') %>
+
+Import a file containing phone numbers (DIDs).
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'PhonenumImportForm',
+ 'action' => 'process/phone_avail-import.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format', 'availbatch', 'exportnum', 'countrycode' ],
+ 'message' => 'DID import successful',
+ 'url' => $p."search/phone_avail.html?availbatch=$availbatch",
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <INPUT TYPE="hidden" NAME="availbatch" VALUE="<% $availbatch %>">
+
+ <% include( '/elements/tr-select-table.html',
+ 'table' => 'part_export',
+ 'name_col' => 'machine',
+ 'label' => 'Export',
+ 'empty_label' => 'Select export',
+ 'hashref' => { 'exporttype' => 'internal_diddb', },
+ #'label_callback' =>
+ )
+ %>
+
+ <TR>
+ <TH ALIGN="right">Country code</TH>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "countrycode"
+ VALUE = "<% $conf->config('default_phone_countrycode') || 1 %>"
+ >
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.PhonenumImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. The file should have a .CSV or .XLS extension.
+<BR><BR>
+
+<b>Default</b> format has the following field order: <i>state, number<i></i>
+<BR><BR>
+
+Field information:
+<ul>
+ <li><i>state</i>: Two-letter state code, i.e. "CA"
+ <li><i>number</i>: Phone number
+</ul>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $conf = new FS::Conf;
+
+my $availbatch =
+ time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+</%init>
diff --git a/httemplate/misc/phone_device_config.html b/httemplate/misc/phone_device_config.html
new file mode 100644
index 0000000..9ea0d0d
--- /dev/null
+++ b/httemplate/misc/phone_device_config.html
@@ -0,0 +1,57 @@
+%if ($config) {
+<% $config %>
+%}else{
+<% include("/elements/errorpage.html", "No configuration data produced.") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $exportnum;
+if ( $cgi->param('exportnum') ) {
+ $cgi->param('exportnum') =~ /^(\d+)$/ or die "unparsable exportnum";
+ $exportnum = $1;
+}
+
+die "no export provided"
+ unless $exportnum;
+
+my $svcnum;
+if ( $cgi->param('svcnum') ) {
+ $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum = $1;
+}
+
+my $devicenum;
+if ( $cgi->param('devicenum') ) {
+ $cgi->param('devicenum') =~ /^(\d+)$/ or die "unparsable devicenum";
+ $devicenum = $1;
+}
+
+die "no device or service provided"
+ unless $svcnum || $devicenum;
+
+my $part_export = qsearchs('part_export', { 'exportnum' => $exportnum })
+ or die "Unknown exportnum $exportnum\n";
+
+my $phone_device;
+my $svc_phone;
+if ($devicenum) {
+ $phone_device = qsearchs('phone_device', { 'devicenum' => $devicenum })
+ or die "Unknown device $devicenum\n";
+ $svc_phone = $phone_device->svc_phone;
+} else {
+ $svc_phone = qsearchs('svc_phone', { 'svcnum' => $svcnum })
+ or die "Unknown svc_phone $svcnum\n";
+}
+
+my $config = $part_export->export_device_config($svc_phone, $phone_device);
+
+if ($config) {
+ http_header('Content-Type' => 'application/octet-stream');
+ http_header('Content-Disposition' => 'attachment;filename="config"');
+ http_header('Content-Length' => length($config));
+}
+
+</%init>
diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi
new file mode 100644
index 0000000..2ed0f61
--- /dev/null
+++ b/httemplate/misc/phonenums.cgi
@@ -0,0 +1,29 @@
+%# [ <% join(', ', map { qq("$_") } @exchanges) %> ]
+<% objToJson(\@exchanges) %>
+<%init>
+
+my( $exchangestring, $svcpart ) = $cgi->param('arg');
+
+$exchangestring =~ /\((\d{3})-(\d{3})-XXXX\)\s*$/i
+ or die "unparsable exchange: $exchangestring";
+my( $areacode, $exchange ) = ( $1, $2 );
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+} elsif ( ! @exports ) {
+ die "no DID providing export attached to svcpart $svcpart";
+}
+my $export = $exports[0];
+
+my $something = $export->get_dids('areacode'=>$areacode,
+ 'exchange'=>$exchange,
+ );
+
+#warn Dumper($something);
+
+my @exchanges = @{ $something };
+
+</%init>
diff --git a/httemplate/misc/ping.html b/httemplate/misc/ping.html
new file mode 100644
index 0000000..4f0360e
--- /dev/null
+++ b/httemplate/misc/ping.html
@@ -0,0 +1,102 @@
+<% include('/elements/header-popup.html', "Ping $ip" ) %>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-ping.html',
+ 'subs' => [ 'ping' ],
+ )
+%>
+
+%# <img src="<%$p%>images/bullet_red.png" border=0>
+
+
+<%ntable("#cccccc", 2)%>
+
+<TR>
+ <TD>Status</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_status">Checking...</TD>
+</TR>
+<TR>
+ <TD>Packet loss</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_packetloss"></TD>
+</TR>
+<TR>
+ <TD>Latency</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_latency"></TD>
+</TR>
+<TR>
+ <TD>Packets</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_packets"></TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<CENTER>
+<INPUT TYPE="button" VALUE="Close" onClick="parent.nd(1);">
+</CENTER>
+
+<SCRIPT TYPE="text/javascript">
+
+ var fails = 0;
+ var pongs = 0;
+ var totaltime = 0;
+ var avg = 0;
+
+ function ping_update ( updatetext ) {
+ var pingArray = eval('(' + updatetext + ')');
+ var status = pingArray[0];
+ var rtt = pingArray[1];
+
+ if ( status == 0 ) {
+ fails++;
+ } else if ( status == 1 ) {
+ pongs++;
+ totaltime = totaltime + rtt;
+ avg = totaltime / pongs;
+ }
+
+ var loss = 100 * fails / ( fails + pongs );
+
+ var statusCell = document.getElementById('ping_status');
+ var packetlossCell = document.getElementById('ping_packetloss');
+ var latencyCell = document.getElementById('ping_latency');
+ var packetsCell = document.getElementById('ping_packets');
+
+ var status = '';
+ // red conditions
+ if ( loss == 100 ) {
+ status = '<FONT COLOR="#ff0000">Unreachable</FONT>';
+ } else
+ // yellow conditions
+ if ( loss > 50 ) {
+ status = '<FONT COLOR="#ff9900">High packet loss</FONT>';
+ } else
+ if ( avg > 1 ) {
+ status = '<FONT COLOR="#ff9900">High latency</FONT>';
+ } else {
+ status = '<FONT COLOR="#00cc00">Up</FONT>';
+ }
+
+ statusCell.innerHTML = '<B>' + status + '</B>';
+ packetlossCell.innerHTML = '<B>' + Math.round(loss) + '%</B>';
+ if ( avg > 0 ) {
+ latencyCell.innerHTML = '<B>' + Math.round( avg*1000 ) + 'ms</B>';
+ }
+ var packets = fails + pongs;
+ packetsCell.innerHTML = '<B>' + packets + '</B>';
+
+ setTimeout( "ping('<%$ip%>', ping_update)", 1000 );
+
+ }
+
+ ping( '<%$ip%>', ping_update );
+
+</SCRIPT>
+
+<%init>
+
+my($query) = $cgi->keywords;
+$query =~ /^([\d\.]+)$/ or die 'Illegal IP';
+my $ip = $1;
+
+</%init>
diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi
new file mode 100755
index 0000000..aeef687
--- /dev/null
+++ b/httemplate/misc/print-invoice.cgi
@@ -0,0 +1,19 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $template = $2;
+my $invnum = $3;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+$cust_bill->print($template);
+
+my $custnum = $cust_bill->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/print_events.cgi b/httemplate/misc/print_events.cgi
new file mode 100644
index 0000000..8d83d3d
--- /dev/null
+++ b/httemplate/misc/print_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_event::process_reprint', $cgi;
+
+</%init>
diff --git a/httemplate/misc/print_invoice_events.cgi b/httemplate/misc/print_invoice_events.cgi
new file mode 100644
index 0000000..c974d5f
--- /dev/null
+++ b/httemplate/misc/print_invoice_events.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi;
+
+</%init>
diff --git a/httemplate/misc/print_invoices.cgi b/httemplate/misc/print_invoices.cgi
new file mode 100644
index 0000000..f859f6d
--- /dev/null
+++ b/httemplate/misc/print_invoices.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi
new file mode 100644
index 0000000..058a225
--- /dev/null
+++ b/httemplate/misc/process/batch-cust_pay.cgi
@@ -0,0 +1,47 @@
+% die "access denied"
+% unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
+%
+% my $param = $cgi->Vars;
+%
+% #my $paybatch = $param->{'paybatch'};
+% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+%
+% my @cust_pay = ();
+% #my $row = 0;
+% #while ( exists($param->{"custnum$row"}) ) {
+% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+% push @cust_pay, new FS::cust_pay {
+% 'custnum' => $param->{"custnum$row"},
+% 'paid' => $param->{"paid$row"},
+% 'payby' => 'BILL',
+% 'payinfo' => $param->{"payinfo$row"},
+% 'paybatch' => $paybatch,
+% }
+% if $param->{"custnum$row"}
+% || $param->{"paid$row"}
+% || $param->{"payinfo$row"};
+% #$row++;
+% }
+%
+% my @errors = FS::cust_pay->batch_insert(@cust_pay);
+% my $num_errors = scalar(grep $_, @errors);
+%
+% if ( $num_errors ) {
+%
+% $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : '').
+% ' - Batch not processed, correct and resubmit'
+% );
+%
+% my $erow=0;
+% $cgi->param('error'. $erow++, shift @errors) while @errors;
+%
+%
+<% $cgi->redirect($p.'batch-cust_pay.html?'. $cgi->query_string)
+
+ %>
+% } else {
+%
+%
+<% $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %>
+% }
+
diff --git a/httemplate/misc/process/bulk_change_pkg.cgi b/httemplate/misc/process/bulk_change_pkg.cgi
new file mode 100755
index 0000000..e22dafe
--- /dev/null
+++ b/httemplate/misc/process/bulk_change_pkg.cgi
@@ -0,0 +1,56 @@
+% if ($error) {
+<% $cgi->redirect(popurl(2)."/bulk_change_pkg.cgi?".$cgi->query_string ) %>
+% }
+<% include('/elements/header-popup.html', "Packages Changed") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+my %search_hash = ();
+
+$search_hash{'query'} = $cgi->param('query');
+
+for my $param (qw(agentnum magic status classnum pkgpart)) {
+ $search_hash{$param} = $cgi->param($param)
+ if $cgi->param($param);
+}
+
+###
+# parse dates
+###
+
+#false laziness w/report_cust_pkg.html
+my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+);
+
+foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295
+ or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending ];
+
+}
+
+my $sql_query = FS::cust_pkg->search(\%search_hash);
+$sql_query->{'select'} = 'cust_pkg.pkgnum';
+
+my $error = FS::cust_pkg::bulk_change( [ $cgi->param('new_pkgpart') ],
+ [ map { $_->pkgnum } qsearch($sql_query) ],
+ );
+
+$cgi->param("error", substr($error, 0, 512)); # arbitrary length believed
+ # suited for all supported
+ # browsers
+
+
+</%init>
diff --git a/httemplate/misc/process/bulk_pkg_increment_bill.cgi b/httemplate/misc/process/bulk_pkg_increment_bill.cgi
new file mode 100755
index 0000000..0d8417b
--- /dev/null
+++ b/httemplate/misc/process/bulk_pkg_increment_bill.cgi
@@ -0,0 +1,76 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'bulk_pkg_increment_bill.cgi?'. $cgi->query_string ) %>
+%} else {
+<% header('Packages Adjusted') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+my $error;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages')
+ and $FS::CurrentUser::CurrentUser->access_right('Edit customer package dates');
+
+my $days = $cgi->param('days') or die "missing parameter: days";
+$days > 0 or $error = "Number of days must be > 0";
+
+my %search_hash = ();
+
+$search_hash{'query'} = $cgi->param('query');
+
+for my $param (qw(agentnum magic status classnum pkgpart)) {
+ $search_hash{$param} = $cgi->param($param)
+ if $cgi->param($param);
+}
+
+###
+# parse dates
+###
+
+#false laziness w/report_cust_pkg.html
+# and, now, w/bulk_change_pkg.cgi
+my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+);
+
+foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295
+ or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending ];
+
+}
+
+if(!$error) {
+ foreach my $cust_pkg (qsearch(FS::cust_pkg->search(\%search_hash))) {
+ next if ! $cust_pkg->bill;
+ my $new_cust_pkg = FS::cust_pkg->new({ $cust_pkg->hash });
+ $new_cust_pkg->bill($new_cust_pkg->bill + $days*86400);
+ $error = $new_cust_pkg->replace($cust_pkg);
+
+ if($error) {
+ $cgi->param("error",substr($error, 0, 512));
+ $dbh->rollback;
+ return;
+ }
+ }
+
+ $dbh->commit;
+}
+
+</%init>
diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
new file mode 100755
index 0000000..e17872c
--- /dev/null
+++ b/httemplate/misc/process/cancel_pkg.html
@@ -0,0 +1,72 @@
+<% header("Package $past{$method}") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%once>
+
+my %past = ( 'cancel' => 'cancelled',
+ 'expire' => 'expired',
+ 'suspend' => 'suspended',
+ 'adjourn' => 'adjourned',
+ );
+
+#i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html
+my %right = ( 'cancel' => 'Cancel customer package immediately',
+ 'expire' => 'Cancel customer package later',
+ 'suspend' => 'Suspend customer package',
+ 'adjourn' => 'Suspend customer package later',
+ );
+
+</%once>
+<%init>
+
+#untaint method
+my $method = $cgi->param('method');
+$method =~ /^(cancel|expire|suspend|adjourn)$/ or die "Illegal method";
+$method = $1;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right($right{$method});
+
+#untaint pkgnum
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
+$pkgnum = $1;
+
+#untaint reasonnum
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
+$reasonnum = $1;
+
+my $date = time;
+if ($method eq 'expire' || $method eq 'adjourn'){
+ #untaint date
+ $date = $cgi->param('date');
+ parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
+ $date = $1;
+ $method = ($method eq 'expire') ? 'cancel' : 'suspend';
+}
+
+my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
+
+#my $otaker = $FS::CurrentUser::CurrentUser->name;
+#$otaker = $FS::CurrentUser::CurrentUser->username
+# if ($otaker eq "User, Legacy");
+
+if ($reasonnum == -1) {
+ $reasonnum = {
+ 'typenum' => scalar( $cgi->param('newreasonnumT') ),
+ 'reason' => scalar( $cgi->param('newreasonnum' ) ),
+ };
+}
+
+my $error = $cust_pkg->$method( 'reason' => $reasonnum, 'date' => $date );
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cancel_pkg.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/process/catchall.cgi b/httemplate/misc/process/catchall.cgi
new file mode 100755
index 0000000..0dda2ea
--- /dev/null
+++ b/httemplate/misc/process/catchall.cgi
@@ -0,0 +1,35 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "catchall.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall');
+
+$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');
+}
+
+</%init>
diff --git a/httemplate/misc/process/cdr-import.html b/httemplate/misc/process/cdr-import.html
new file mode 100644
index 0000000..edc441e
--- /dev/null
+++ b/httemplate/misc/process/cdr-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cdr::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/copy-rate_detail.html b/httemplate/misc/process/copy-rate_detail.html
new file mode 100644
index 0000000..60b2aeb
--- /dev/null
+++ b/httemplate/misc/process/copy-rate_detail.html
@@ -0,0 +1,61 @@
+%# if ( $error ) {
+%# <% $cgi->redirect(popurl(2).'copy-rate_detail.html?'. $cgi->query_string ) %>
+%# } else {
+<% include('/elements/header.html', 'Rates copied',
+ menubar( 'View all rate plans' => popurl(3).'browse/rate.cgi' ),
+ ) %>
+%# }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('src_ratenum') =~ /^(\d+)$/ or die 'Illegal src_ratenum';
+my $src_ratenum = $1;
+
+$cgi->param('dst_ratenum') =~ /^(\d+)$/ or die 'Illegal src_ratenum';
+my $dst_ratenum = $1;
+
+my @countrycodes = map { /^countrycode(\d+)$/ or die; $1 }
+ grep { /^countrycode(\d+)$/ && $cgi->param($_) }
+ $cgi->param;
+
+foreach my $countrycode ( @countrycodes ) {
+
+ my @src_rate_detail = qsearch({
+ 'table' => 'rate_detail',
+ 'addl_from' => 'JOIN rate_region'.
+ ' ON ( rate_detail.dest_regionnum = rate_region.regionnum )',
+ 'hashref' => { 'ratenum' => $src_ratenum },
+ 'extra_sql' =>
+ "AND 0 < ( SELECT COUNT(*) FROM rate_prefix
+ WHERE rate_prefix.regionnum = rate_region.regionnum
+ AND countrycode = '$countrycode'
+ )
+ ",
+ });
+
+ foreach my $src_rate_detail ( @src_rate_detail ) {
+
+ my %hash = (
+ 'ratenum' => $dst_ratenum,
+ map { $_ => $src_rate_detail->get($_) }
+ qw( orig_regionnum dest_regionnum )
+ );
+
+ my $dst_rate_detail = qsearchs( 'rate_detail', \%hash)
+ || new FS::rate_detail \%hash;
+
+ $dst_rate_detail->$_( $src_rate_detail->get($_) )
+ foreach qw( min_included conn_charge conn_sec min_charge sec_granularity classnum );
+
+ my $method = $dst_rate_detail->ratedetailnum ? 'replace' : 'insert';
+
+ my $error = $dst_rate_detail->$method();
+
+ die $error if $error; # "shouldn't" happen
+
+ }
+}
+
+</%init>
diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi
new file mode 100644
index 0000000..2b705a6
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import.cgi
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server =
+ new FS::UI::Web::JSRPC 'FS::cust_main::Import::process_batch_import', $cgi;
+
+</%init>
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..3ca6894
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import_charges.cgi
@@ -0,0 +1,23 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+ <% include('/elements/header.html','Import successful') %>
+ <% include('/elements/footer.html') %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+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';
+
+</%init>
diff --git a/httemplate/misc/process/cust_main_note-import.cgi b/httemplate/misc/process/cust_main_note-import.cgi
new file mode 100644
index 0000000..6aa8b1d
--- /dev/null
+++ b/httemplate/misc/process/cust_main_note-import.cgi
@@ -0,0 +1,82 @@
+<% include("/elements/header.html", "Batch Customer Note Import $op") %>
+
+The following items <% $op eq 'Preview' ? 'would not be' : 'were not' %> imported. (See below for imported items)
+<PRE>
+% foreach my $row (@uninserted) {
+% $csv->combine( (map{ $row->{$_} } qw(last first note) ),
+% $row->{error} ? ('#!', $row->{error}) : (),
+% );
+<% $csv->string %>
+% }
+</PRE>
+
+The following items <% $op eq 'Preview' ? 'would be' : 'were' %> imported. (See above for unimported items)
+
+<PRE>
+% foreach my $row (@inserted) {
+% $csv->combine( (map{ $row->{$_} } qw(custnum last first note) ),
+% ('#!', $row->{name}),
+% );
+<% $csv->string %>
+% }
+</PRE>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $date = time;
+my $otaker = $FS::CurrentUser::CurrentUser->username;
+my $csv = new Text::CSV_XS;
+
+my $param = $cgi->Vars;
+
+my $op = $param->{preview} ? "Preview" : "Results";
+
+my @inserted = ();
+my @uninserted = ();
+for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+ if ( $param->{"custnum$row"} ) {
+# my $cust_main_note = new FS::cust_main_note {
+# 'custnum' => $param->{"custnum$row"},
+# '_date' => $date,
+# 'otaker' => $otaker,
+# 'comments' => $param->{"note$row"},
+# };
+# my $error = '';
+# $error = $cust_main_note->insert unless ($op eq "Preview");
+ my $cust_main = qsearchs('cust_main',
+ { 'custnum' => $param->{"custnum$row"} }
+ );
+ my $error;
+ if ($cust_main) {
+ $cust_main->comments
+ ? $cust_main->comments($cust_main->comments. " ". $param->{"note$row"})
+ : $cust_main->comments($param->{"note$row"});
+ $error = $cust_main->replace;
+ }else{
+ $error = "Can't find customer " . $param->{"custnum$row"};
+ }
+ my $result = { 'custnum' => $param->{"custnum$row"},
+ 'last' => $param->{"last$row"},
+ 'first' => $param->{"first$row"},
+ 'note' => $param->{"note$row"},
+ 'name' => $param->{"name$row"},
+ 'error' => $error,
+ };
+ if ($error) {
+ push @uninserted, $result;
+ }else{
+ push @inserted, $result;
+ }
+ }else{
+ push @uninserted, { 'custnum' => '',
+ 'last' => $param->{"last$row"},
+ 'first' => $param->{"first$row"},
+ 'note' => $param->{"note$row"},
+ 'error' => '',
+ };
+ }
+}
+</%init>
diff --git a/httemplate/misc/process/cust_pay-import.cgi b/httemplate/misc/process/cust_pay-import.cgi
new file mode 100644
index 0000000..d4ff226
--- /dev/null
+++ b/httemplate/misc/process/cust_pay-import.cgi
@@ -0,0 +1,21 @@
+<% $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %>
+<%init>
+
+my $fh = $cgi->upload('csvfile');
+
+# webbatch? I suppose
+my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+my $error = defined($fh)
+ ? FS::cust_pay::batch_import( {
+ 'filehandle' => $fh,
+ 'agentnum' => scalar($cgi->param('agentnum')),
+ 'format' => scalar($cgi->param('format')),
+ 'paybatch' => $paybatch,
+ } )
+ : 'No file';
+
+errorpage($error)
+ if ( $error );
+
+</%init>
diff --git a/httemplate/misc/process/delay_susp_pkg.html b/httemplate/misc/process/delay_susp_pkg.html
new file mode 100755
index 0000000..8649cc2
--- /dev/null
+++ b/httemplate/misc/process/delay_susp_pkg.html
@@ -0,0 +1,41 @@
+<% header("Package suspension delayed") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%once>
+
+my $right = 'Delay suspension events';
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right($right);
+
+my ($pkgnum, $date, $cust_pkg, $cust_main, $error);
+
+#untaint pkgnum
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum";
+$pkgnum = $1;
+
+#untaint date
+parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
+my $date = $1;
+
+$cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
+if ($cust_pkg) {
+ $cust_main = $cust_pkg->cust_main;
+ $cust_main->dundate( $date );
+ $error = $cust_main->replace;
+} else {
+ $error = "Invalid pkgnum";
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cancel_pkg.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi
new file mode 100755
index 0000000..d509a5e
--- /dev/null
+++ b/httemplate/misc/process/delete-customer.cgi
@@ -0,0 +1,33 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ) %>
+%} elsif ( $new_custnum ) {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum") %>
+%} else {
+<% $cgi->redirect(popurl(3)) %>
+%}
+<%init>
+
+my $conf = new FS::Conf;
+die "Customer deletions not enabled in configuration"
+ unless $conf->exists('deletecustomers');
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete customer');
+
+$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);
+
+</%init>
diff --git a/httemplate/misc/process/email-customers.html b/httemplate/misc/process/email-customers.html
new file mode 100644
index 0000000..c54bc6d
--- /dev/null
+++ b/httemplate/misc/process/email-customers.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_main::process_email_search_result', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/enable_or_disable_tax.html b/httemplate/misc/process/enable_or_disable_tax.html
new file mode 100755
index 0000000..9b7324b
--- /dev/null
+++ b/httemplate/misc/process/enable_or_disable_tax.html
@@ -0,0 +1,41 @@
+%if ($error) {
+<% $cgi->redirect(popurl(2).'enable_or_disable_tax.html?'.$cgi->query_string) %>
+%}else{
+ <% include('/elements/header-popup.html', $title) %>
+
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY>
+ </HTML>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $action = '';
+if ( $cgi->param('action') =~ /^(\w+)$/ ) {
+ $action = $1;
+}
+
+my ($query, $count_query) = FS::tax_rate::browse_queries(scalar($cgi->Vars));
+my @tax_rate = qsearch( $query );
+
+#transaction?
+my $error;
+$error = "Invalid action" unless ($action =~ /enable|disable/);
+
+foreach my $tax_rate (@tax_rate) {
+ $action eq 'enable' ? $tax_rate->disabled('') : $tax_rate->disabled('Y');
+ # $tax_rate->manual('Y');
+ $error ||= $tax_rate->replace;
+ last if $error;
+}
+$cgi->param('error', $error) if $error;
+
+my $title = scalar(@tax_rate) == 1 ? 'Tax rate ' : 'Tax rates ';
+$title .= lc($action). 'd';
+
+</%init>
diff --git a/httemplate/misc/process/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html
new file mode 100644
index 0000000..377943f
--- /dev/null
+++ b/httemplate/misc/process/inventory_item-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::inventory_item::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi
new file mode 100755
index 0000000..77546f3
--- /dev/null
+++ b/httemplate/misc/process/link.cgi
@@ -0,0 +1,78 @@
+%unless ($error) {
+% #no errors, so let's view this customer.
+% my $custnum = $new->cust_pkg->custnum;
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" ) %>
+%} else {
+% errorpage($error);
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View/link unlinked services');
+
+my $DEBUG = 0;
+
+$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 { ($a->cust_svc->pkgnum > 0) <=> ($b->cust_svc->pkgnum > 0)
+ or ($b->cust_svc->svcpart == $svcpart)
+ <=> ($a->cust_svc->svcpart == $svcpart)
+ }
+ qsearch( $svcdb, \%search )
+ );
+
+ if ( $DEBUG ) {
+ warn scalar(@svc_x). " candidate accounts found for linking ".
+ "(svcpart $svcpart):\n";
+ foreach my $svc_x ( @svc_x ) {
+ warn " ". $svc_x->email.
+ " (svcnum ". $svc_x->svcnum. ",".
+ " pkgnum ". $svc_x->cust_svc->pkgnum. ",".
+ " svcpart ". $svc_x->cust_svc->svcpart. ")\n";
+ }
+ }
+
+ my $svc_x = $svc_x[0];
+
+ errorpage("$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 { $old->hash };
+ $new->pkgnum($pkgnum);
+ $new->svcpart($svcpart);
+
+ $error = $new->replace($old);
+}
+
+</%init>
diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi
new file mode 100644
index 0000000..68ae49c
--- /dev/null
+++ b/httemplate/misc/process/meta-import.cgi
@@ -0,0 +1,190 @@
+<% include("/elements/header.html",'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>".
+% '';
+% }
+%
+%
+<%init>
+
+#there's no ACL for this... haven't used in ages
+#make XSS-safe if this is used for more than just admins to import data....
+die 'meta-import not enabled; remove this if you want to use it';
+
+</%init>
diff --git a/httemplate/misc/process/part_device-import.html b/httemplate/misc/process/part_device-import.html
new file mode 100644
index 0000000..eac111a
--- /dev/null
+++ b/httemplate/misc/process/part_device-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_device::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
new file mode 100644
index 0000000..906c996
--- /dev/null
+++ b/httemplate/misc/process/payment.cgi
@@ -0,0 +1,204 @@
+% if ( $cgi->param('batch') ) {
+
+ <% include( '/elements/header.html', ucfirst($type{$payby}). ' processing successful',
+ include('/elements/menubar.html'),
+
+ )
+ %>
+
+ <% include( '/elements/small_custview.html', $cust_main, '', '', popurl(3). "view/cust_main.cgi" ) %>
+
+ <% include('/elements/footer.html') %>
+
+% } else {
+<% $cgi->redirect(popurl(3). "view/cust_pay.html?paynum=$paynum" ) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Process payment');
+
+#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 errorpage("illegal amount ". $cgi->param('amount'));
+my $amount = $1;
+errorpage("amount <= 0") unless $amount > 0;
+
+if ( $cgi->param('fee') =~ /^\s*(\d*(\.\d\d)?)\s*$/ ) {
+ my $fee = $1;
+ $amount = sprintf('%.2f', $amount + $fee);
+}
+
+$cgi->param('year') =~ /^(\d+)$/
+ or errorpage("illegal year ". $cgi->param('year'));
+my $year = $1;
+
+$cgi->param('month') =~ /^(\d+)$/
+ or errorpage("illegal month ". $cgi->param('month'));
+my $month = $1;
+
+$cgi->param('payby') =~ /^(CARD|CHEK)$/
+ or errorpage("illegal payby ". $cgi->param('payby'));
+my $payby = $1;
+my %payby2fields = (
+ 'CARD' => [ qw( address1 address2 city county state zip country ) ],
+ 'CHEK' => [ qw( ss paytype paystate stateid stateid_state ) ],
+);
+my %type = ( 'CARD' => 'credit card',
+ 'CHEK' => 'electronic check (ACH)',
+ );
+
+$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/
+ or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname'));
+my $payname = $1;
+
+$cgi->param('payunique') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or errorpage(gettext('illegal_text'). " payunique: ". $cgi->param('payunique'));
+my $payunique = $1;
+
+$cgi->param('balance') =~ /^\s*(\-?\s*\d*(\.\d\d)?)\s*$/
+ or errorpage("illegal balance");
+my $balance = $1;
+
+my $payinfo;
+my $paycvv = '';
+if ( $payby eq 'CHEK' ) {
+
+ if ($cgi->param('payinfo1') =~ /xx/i || $cgi->param('payinfo2') =~ /xx/i ) {
+ $payinfo = $cust_main->payinfo;
+ } else {
+ $cgi->param('payinfo1') =~ /^(\d+)$/
+ or errorpage("illegal account number ". $cgi->param('payinfo1'));
+ my $payinfo1 = $1;
+ $cgi->param('payinfo2') =~ /^(\d+)$/
+ or errorpage("illegal ABA/routing number ". $cgi->param('payinfo2'));
+ my $payinfo2 = $1;
+ $payinfo = $payinfo1. '@'. $payinfo2;
+ }
+
+} elsif ( $payby eq 'CARD' ) {
+
+ $payinfo = $cgi->param('payinfo');
+ if ($payinfo eq $cust_main->paymask) {
+ $payinfo = $cust_main->payinfo;
+ }
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^(\d{13,16})$/
+ or errorpage(gettext('invalid_card')); # . ": ". $self->payinfo;
+ $payinfo = $1;
+ validate($payinfo)
+ or errorpage(gettext('invalid_card')); # . ": ". $self->payinfo;
+ errorpage(gettext('unknown_card_type'))
+ if $payinfo !~ /^99\d{14}$/ && 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 errorpage("CVV2 (CID) for American Express cards is four digits.");
+ $paycvv = $1;
+ } else {
+ $cgi->param('paycvv') =~ /^(\d{3})$/
+ or errorpage("CVV2 (CVC2/CID) is three digits.");
+ $paycvv = $1;
+ }
+ }
+ }
+
+} else {
+ die "unknown payby $payby";
+}
+
+my $error = '';
+my $paynum = '';
+if ( $cgi->param('batch') ) {
+
+ $error = $cust_main->batch_card(
+ 'payby' => $payby,
+ 'amount' => $amount,
+ 'payinfo' => $payinfo,
+ 'paydate' => "$year-$month-01",
+ 'payname' => $payname,
+ map { $_ => $cgi->param($_) }
+ @{$payby2fields{$payby}}
+ );
+ errorpage($error) if $error;
+
+} else {
+
+ $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
+ 'quiet' => 1,
+ 'manual' => 1,
+ 'balance' => $balance,
+ 'payinfo' => $payinfo,
+ 'paydate' => "$year-$month-01",
+ 'payname' => $payname,
+ 'payunique' => $payunique,
+ 'paycvv' => $paycvv,
+ 'paynum_ref' => \$paynum,
+ map { $_ => $cgi->param($_) } @{$payby2fields{$payby}}
+ );
+ errorpage($error) if $error;
+
+ #no error, so order the fee package if applicable...
+ if ( $cgi->param('fee_pkgpart') =~ /^(\d+)$/ ) {
+
+ my $cust_pkg = new FS::cust_pkg { 'pkgpart' => $1 };
+
+ my $error = $cust_main->order_pkg( 'cust_pkg' => $cust_pkg );
+ errorpage("payment processed successfully, but error ordering fee: $error")
+ if $error;
+
+ #and generate an invoice for it now too
+ $error = $cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
+ errorpage("payment processed and fee ordered sucessfully, but error billing fee: $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' => $cust_main->card_token || $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);
+ errorpage("payment processed successfully, but error saving info: $error")
+ if $error;
+ $cust_main = $new;
+}
+
+#success!
+
+</%init>
diff --git a/httemplate/misc/process/phone_avail-import.html b/httemplate/misc/process/phone_avail-import.html
new file mode 100644
index 0000000..f1a2f24
--- /dev/null
+++ b/httemplate/misc/process/phone_avail-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::phone_avail::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/rate-import.html b/httemplate/misc/process/rate-import.html
new file mode 100644
index 0000000..2c64164
--- /dev/null
+++ b/httemplate/misc/process/rate-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::rate::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/rate_edit_excel.html b/httemplate/misc/process/rate_edit_excel.html
new file mode 100644
index 0000000..acd5f49
--- /dev/null
+++ b/httemplate/misc/process/rate_edit_excel.html
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::rate_detail::process_edit_import', $cgi;
+
+</%init>
+
diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html
new file mode 100755
index 0000000..5f68bf1
--- /dev/null
+++ b/httemplate/misc/process/recharge_svc.html
@@ -0,0 +1,91 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string ) %>
+%} else {
+<% header("Package recharged") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+%}
+<%init>
+
+my $conf = new FS::Conf;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Recharge customer service');
+
+#untaint svcnum
+my $svcnum = $cgi->param('svcnum');
+$svcnum =~ /^(\d+)$/ || die "Illegal svcnum";
+$svcnum = $1;
+
+#untaint prepaid
+my $prepaid = $cgi->param('prepaid');
+$prepaid =~ /^(\w*)$/;
+$prepaid = $1;
+
+#untaint payby
+my $payby = $cgi->param('payby');
+$payby =~ /^([A-Z]*)$/;
+$payby = $1;
+
+my $error = '';
+my $svc_acct = qsearchs( 'svc_acct', {'svcnum'=>$svcnum} );
+$error = "Can't recharge service $svcnum. " unless $svc_acct;
+
+my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+unless ($error) {
+
+ #should probably use payby.pm but whatever
+ if ($payby eq 'PREP') {
+ $error = $cust_main->recharge_prepay( $prepaid );
+ } elsif ( $payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP)$/ ) {
+ my $part_pkg = $svc_acct->cust_svc->cust_pkg->part_pkg;
+ my $amount = $part_pkg->option('recharge_amount', 1);
+ my %rhash = map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_) }
+ grep { $part_pkg->option($_, 1) }
+ qw ( recharge_seconds recharge_upbytes recharge_downbytes
+ recharge_totalbytes );
+
+ my $description = "Recharge";
+ $description .= " $rhash{seconds}s" if $rhash{seconds};
+ $description .= " $rhash{upbytes} up" if $rhash{upbytes};
+ $description .= " $rhash{downbytes} down" if $rhash{downbytes};
+ $description .= " $rhash{totalbytes} total" if $rhash{totalbytes};
+
+ $error = $cust_main->charge($amount, "Recharge " . $svc_acct->label,
+ $description, $part_pkg->taxclass);
+
+ if ($part_pkg->option('recharge_reset', 1)) {
+ $error ||= $svc_acct->set_usage(\%rhash, 'null' => 1);
+ }else{
+ $error ||= $svc_acct->recharge(\%rhash);
+ }
+
+ my $old_balance = $cust_main->balance;
+ $error ||= $cust_main->bill;
+ $error ||= $cust_main->apply_payments_and_credits;
+ my $bill_error = $cust_main->collect('realtime' => 1) unless $error;
+ $error ||= "Failed to collect - $bill_error"
+ if $cust_main->balance > $old_balance && $cust_main->balance > 0
+ && $payby ne 'BILL';
+
+ } else {
+ $error = "fatal error - unknown payby: $payby";
+ }
+
+}
+
+if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+} else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+</%init>
diff --git a/httemplate/misc/process/recharge_svc.new b/httemplate/misc/process/recharge_svc.new
new file mode 100755
index 0000000..bc916e5
--- /dev/null
+++ b/httemplate/misc/process/recharge_svc.new
@@ -0,0 +1,85 @@
+%
+%
+%#untaint svcnum
+%my $svcnum = $cgi->param('svcnum');
+%$svcnum =~ /^(\d+)$/ || die "Illegal svcnum";
+%$svcnum = $1;
+%
+%#untaint prepaid
+%my $prepaid = $cgi->param('prepaid');
+%$prepaid =~ /^(\w*)$/;
+%$prepaid = $1;
+
+%#untaint payby
+%my $payby = $cgi->param('payby');
+%$payby =~ /^([A-Z]*)$/;
+%$payby = $1;
+%
+%my $error = '';
+%my $svc_acct = qsearchs( 'svc_acct', {'svcnum'=>$svcnum} );
+%$error = "Can't recharge service $svcnum. " unless $svc_acct;
+%
+%my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
+%
+%my $oldAutoCommit = $FS::UID::AutoCommit;
+%local $FS::UID::AutoCommit = 0;
+%my $dbh = dbh;
+%
+%
+%unless ($error) {
+%
+% my ($amount, $seconds, $up, $down, $total) = (0, 0, 0, 0, 0);
+% #should probably use payby.pm but whatever
+% if ($payby eq 'PREP') {
+% $error = $cust_main->get_prepay($prepaid, \$amount, \$seconds, \$up, \$down, \$total)
+% || $svc_acct->increment_seconds($seconds)
+% || $svc_acct->increment_upbytes($up)
+% || $svc_acct->increment_downbytes($down)
+% || $svc_acct->increment_totalbytes($total)
+% || $cust_main->insert_cust_pay_prepay( $amount, $prepaid );
+% } elsif ( $payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP)$/ ) {
+% my $part_pkg = $svc_acct->cust_svc->cust_pkg->part_pkg;
+% $amount = $part_pkg->option('recharge_amount', 1);
+% my %rhash = map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_, 1) }
+% qw ( recharge_seconds recharge_upbytes recharge_downbytes
+% recharge_totalbytes );
+%
+% my $description = "Recharge";
+% $description .= " $rhash{seconds}s" if $rhash{seconds};
+% $description .= " $rhash{upbytes} up" if $rhash{upbytes};
+% $description .= " $rhash{downbytes} down" if $rhash{downbytes};
+% $description .= " $rhash{totalbytes} total" if $rhash{totalbytes};
+%
+% $error = $cust_main->charge($amount, "Recharge " . $svc_acct->label,
+% $description, $part_pkg->taxclass);
+%
+% $error ||= $svc_acct->recharge(\%rhash);
+%
+% my $old_balance = $cust_main->balance;
+% $error ||= $cust_main->bill;
+% $error ||= $cust_main->apply_payments_and_credits;
+% my $bill_error = $cust_main->collect('realtime' => 1) unless $error;
+% $error ||= "Failed to collect - $bill_error"
+% if $cust_main->balance > $old_balance && $cust_main->balance > 0
+% && $payby ne 'BILL';
+%
+% } else {
+% $error = "fatal error - unknown payby: $payby";
+% }
+%}
+%
+%if ($error) {
+% $cgi->param('error', $error);
+% $dbh->rollback if $oldAutoCommit;
+% print $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string );
+%}
+%$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+%
+<% header("Package recharged") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+<%init>
+my $conf = new FS::Conf;
+</%init>
diff --git a/httemplate/misc/process/tax-fetch_and_import.cgi b/httemplate/misc/process/tax-fetch_and_import.cgi
new file mode 100644
index 0000000..553c755
--- /dev/null
+++ b/httemplate/misc/process/tax-fetch_and_import.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_download_and_update', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/tax-fetch_and_replace.cgi b/httemplate/misc/process/tax-fetch_and_replace.cgi
new file mode 100644
index 0000000..1a9b626
--- /dev/null
+++ b/httemplate/misc/process/tax-fetch_and_replace.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_download_and_reload', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/tax-import.cgi b/httemplate/misc/process/tax-import.cgi
new file mode 100644
index 0000000..b9e9daa
--- /dev/null
+++ b/httemplate/misc/process/tax-import.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/tax-upgrade.cgi b/httemplate/misc/process/tax-upgrade.cgi
new file mode 100644
index 0000000..8782282
--- /dev/null
+++ b/httemplate/misc/process/tax-upgrade.cgi
@@ -0,0 +1,147 @@
+% if ( $error ) {
+% warn $error;
+% errorpage($error);
+% } else {
+ <% include('/elements/header.html','Import successful') %>
+ <% include('/elements/footer.html') %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $cfh = $cgi->upload('codefile');
+my $zfh = $cgi->upload('plus4file');
+my $tfh = $cgi->upload('txmatrix');
+my $dfh = $cgi->upload('detail');
+#warn $cgi;
+#warn $fh;
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+my $error = '';
+
+my ($cifh, $cdfh, $zifh, $zdfh, $tifh, $tdfh);
+
+if (defined($cfh)) {
+ $cifh = new File::Temp( TEMPLATE => 'code.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ $cdfh = new File::Temp( TEMPLATE => 'code.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ while(<$cfh>) {
+ my $fh = '';
+ $fh = $cifh if $_ =~ /"I"\s*$/;
+ $fh = $cdfh if $_ =~ /"D"\s*$/;
+ die "bad input line: $_" unless $fh;
+ print $fh $_;
+ }
+ seek $cifh, 0, 0;
+ seek $cdfh, 0, 0;
+
+}else{
+ $error = 'No code file';
+}
+
+$error ||= FS::tax_class::batch_import( {
+ filehandle => $cifh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+
+close $cifh if $cifh;
+
+if (defined($zfh)) {
+ $zifh = new File::Temp( TEMPLATE => 'plus4.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ $zdfh = new File::Temp( TEMPLATE => 'plus4.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ while(<$zfh>) {
+ my $fh = '';
+ $fh = $zifh if $_ =~ /"I"\s*$/;
+ $fh = $zdfh if $_ =~ /"D"\s*$/;
+ die "bad input line: $_" unless $fh;
+ print $fh $_;
+ }
+ seek $zifh, 0, 0;
+ seek $zdfh, 0, 0;
+
+}else{
+ $error = 'No plus4 file';
+}
+
+$error ||= FS::cust_tax_location::batch_import( {
+ filehandle => $zifh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+close $zifh if $zifh;
+
+if (defined($tfh)) {
+ $tifh = new File::Temp( TEMPLATE => 'txmatrix.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ $tdfh = new File::Temp( TEMPLATE => 'txmatrix.insert.XXXXXXXX',
+ DIR => $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc,
+ ) or die "can't open temp file: $!\n";
+
+ while(<$tfh>) {
+ my $fh = '';
+ $fh = $tifh if $_ =~ /"I"\s*$/;
+ $fh = $tdfh if $_ =~ /"D"\s*$/;
+ die "bad input line: $_" unless $fh;
+ print $fh $_;
+ }
+ seek $tifh, 0, 0;
+ seek $tdfh, 0, 0;
+
+}else{
+ $error = 'No tax matrix file';
+}
+
+$error ||= FS::part_pkg_taxrate::batch_import( {
+ filehandle => $tifh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+close $tifh if $tifh;
+
+$error ||= defined($dfh)
+ ? FS::tax_rate::batch_update( {
+ filehandle => $dfh,
+ 'format' => scalar($cgi->param('format')),
+ } )
+ : 'No tax detail file';
+
+$error ||= FS::part_pkg_taxrate::batch_import( {
+ filehandle => $tdfh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+close $tdfh if $tdfh;
+
+$error ||= FS::cust_tax_location::batch_import( {
+ filehandle => $zdfh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+close $zdfh if $zdfh;
+
+$error ||= FS::tax_class::batch_import( {
+ filehandle => $cdfh,
+ 'format' => scalar($cgi->param('format')),
+ } );
+close $cdfh if $cdfh;
+
+if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+}else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+</%init>
diff --git a/httemplate/misc/process/timeworked.html b/httemplate/misc/process/timeworked.html
new file mode 100644
index 0000000..200a751
--- /dev/null
+++ b/httemplate/misc/process/timeworked.html
@@ -0,0 +1,59 @@
+% if ($error) {
+<% $cgi->redirect(popurl(2). "timeworked.html?". $cgi->query_string) %>
+% } else {
+<% $cgi->redirect(popurl(3). "search/timeworked.html?begin=$begin;end=$end") %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Time queue');
+
+my($begin, $end) = FS::UI::Web::parse_beginning_ending($cgi);
+
+my @acct_rt_transaction;
+foreach my $transaction (
+ map { /^transactionid(\d+)$/; $1; } grep /^transactionid\d+$/, $cgi->param
+) {
+ my $s = "multiplier${transaction}_";
+ my %multipliers = map { /^$s(\d+)$/; $1 => $cgi->param("$s$1"); }
+ grep /^$s\d+$/, $cgi->param;
+ my $msum = 0;
+ foreach(values %multipliers) {$msum += $_};
+
+ my $seconds = $cgi->param("seconds$transaction");
+ my %seconds =
+ map { $_ => sprintf("%.0f", $seconds * $multipliers{$_} / $msum) }
+ (keys %multipliers);
+ my $sum = 0;
+ my $count = 0;
+ foreach (values %seconds) {
+ $sum += $_;
+ $count++;
+ }
+
+ #fudge in some time if we're close
+ if (abs($seconds-$sum) <= $count) {
+ my $adjustment = $seconds-$sum;
+ foreach (keys %seconds) { # explicitly choose one?
+ $seconds{$_} += $adjustment;
+ last;
+ }
+ } else {
+ die "unexpectedly cannot apportion time";
+ }
+
+ foreach my $customer ( grep {$seconds{$_}} keys %seconds ) {
+ push @acct_rt_transaction, new FS::acct_rt_transaction {
+ 'custnum' => $customer,
+ 'transaction_id' => $transaction,
+ 'seconds' => $seconds{$customer},
+ 'support' => int( $seconds{$customer} * $msum ),
+ };
+ }
+
+}
+
+my $error = FS::acct_rt_transaction->batch_insert(@acct_rt_transaction);
+$cgi->param('error', $error) if $error;
+
+</%init>
diff --git a/httemplate/misc/queue.cgi b/httemplate/misc/queue.cgi
new file mode 100644
index 0000000..5dee29b
--- /dev/null
+++ b/httemplate/misc/queue.cgi
@@ -0,0 +1,49 @@
+<% $cgi->redirect(popurl(2). "search/queue.html") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Job queue');
+
+$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;
+ }
+ }
+}
+
+</%init>
diff --git a/httemplate/misc/queued_report.html b/httemplate/misc/queued_report.html
new file mode 100755
index 0000000..8754045
--- /dev/null
+++ b/httemplate/misc/queued_report.html
@@ -0,0 +1,29 @@
+<% include($report_comp) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $report = '';
+$cgi->param('report') =~ /^([.\w]+)$/ && ($report = $1);
+die "no such report" unless $report;
+
+my $prefix = "$FS::UID::cache_dir/cache.$FS::UID::datasrc";
+open my $fh, "$prefix/report.$report"
+ or die "can't open report: $!\n";
+
+my $reportvalue = '';
+{
+ local $/;
+ $reportvalue = <$fh>;
+}
+close $fh;
+
+#my ($interp, $other) = FS::Mason::mason_interps('standalone');
+my $interp = $m->interp;
+my $report_comp =
+ eval { $interp->make_component ( comp_source => $reportvalue ) };
+die $@ if $@;
+errorpage($@) if $@;
+
+</%init>
diff --git a/httemplate/misc/rate-import.html b/httemplate/misc/rate-import.html
new file mode 100644
index 0000000..ae8ee69
--- /dev/null
+++ b/httemplate/misc/rate-import.html
@@ -0,0 +1,76 @@
+<% include("/elements/header.html",'Import Rate Plan') %>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'RateImportForm',
+ 'action ' => 'process/rate-import.html',
+ 'num_files' => 1,
+ 'fields' => [ 'ratename' ],
+ 'message' => 'Rate plan import successful',
+# 'url' => $p."browse/rate_detail.cgi?ratenum=$ratenum", #XXX how?
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD>Rate plan</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="">
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import rate plan"
+ onClick = "document.RateImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+<!--Upload file can be a text file or Excel spreadsheet. If an Excel spreadsheet,
+ should have an .XLS extension.
+<BR><BR>
+-->
+File format is CSV (comma-separated value), with the following field order:
+<ul>
+ <li>Destination name
+ <li>Country code / Prefix. See below for formatting rules.
+ <li>Rate (per minute)
+<!--
+ <li>(Optional) Included minutes
+ <li>(Optional) Granularity
+-->
+</ul>
+
+Formatting rules for second field:
+<ul>
+ <li>Simple entries contain just a countrycode or a countrycode and single prefix for example, "61" or "52 33". Whitespace, plus and dash are ignored.
+ <li>Additional prefixes may be appended after a comma (appropriately quoted), but country code should only be listed once at the beginning. For example, "61 38,39".
+ <li>
+</ul>
+
+Have caution when importing prefix data that is mismatched to your current
+prefixes.
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/rate_edit_excel.html b/httemplate/misc/rate_edit_excel.html
new file mode 100644
index 0000000..442d83a
--- /dev/null
+++ b/httemplate/misc/rate_edit_excel.html
@@ -0,0 +1,70 @@
+<% include('/elements/header.html', 'Edit rates with Excel' ) %>
+
+% if ( $have_conn ) {
+ <FONT COLOR="#FF0000">WARNING: This functionality does not yet preserve connection charges.</FONT><BR><BR>
+% }
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'RateImportForm',
+ 'action' => 'process/rate_edit_excel.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format' ],
+ 'message' => 'Rate edit successful',
+ 'url' => $p."browse/rate_region.html",
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="left">1. Download current rates:</TH>
+ <TD>
+ <A HREF="<%$p%>/browse/rate_region.html?show_rates=1;_type=regions.xls">Download rate spreadsheet</A>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="left" COLSPAN=2>2. Edit rates with Excel (or other .XLS-compatible application)</TH>
+ </TR>
+
+ <TR>
+ <TD ALIGN="left" COLSPAN=2>
+ &nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;To add rates, add four columns like an existing rate, with headers starting with "NEW: Rate Name" or "Rate Name".<BR>
+ &nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;<FONT SIZE="-2"><I>For rate addition, protection can be turned off in Excel via the Tools-&gt;Protection-&gt;Unprotect Sheet menu command. Note that only new rates can be added; modified grayed out cells will not be imported.</I></FONT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => '3. Upload edited rate file: ',
+ 'label_align' => 'left',
+ )
+ %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Upload"
+ onClick = "document.RateImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $sth = dbh->prepare('SELECT COUNT(*) FROM rate_detail WHERE conn_charge > 0 OR conn_sec > 0 LIMIT 1')
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $have_conn = $sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/misc/recharge_svc.html b/httemplate/misc/recharge_svc.html
new file mode 100755
index 0000000..d8a8faa
--- /dev/null
+++ b/httemplate/misc/recharge_svc.html
@@ -0,0 +1,105 @@
+<% include('/elements/header-popup.html', 'Recharge Service' ) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="recharge_popup" ACTION="<% popurl(1) %>process/recharge_svc.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+
+<BR><BR>
+<% "Recharge $svcnum: $label - $value" %>
+<% ntable("#cccccc", 2) %>
+
+<SCRIPT>
+ function toggle_prep(what) {
+ if (what.value == "PREP"){
+ what.form.prepaid.disabled = false;
+ }else{
+ what.form.prepaid.disabled = true;
+ }
+ }
+</SCRIPT>
+<TR>
+ <TD><INPUT TYPE="radio" NAME="payby" onchange="toggle_prep(this)" VALUE="PREP" <% $payby eq "PREP" ? 'checked' : '' %> <% $recharge_label ? '' : 'disabled' %>></TD>
+ <TD>Prepaid Card</TD>
+% if ($recharge_label) {
+ <TD><INPUT TYPE="radio" NAME="payby" onchange="toggle_prep(this)" VALUE="<% $cust_svc->cust_pkg->cust_main->payby %>" <% $payby eq "PREP" ? '' : 'checked' %>></TD>
+ <TD><% $recharge_label %></TD>
+% }
+</TR>
+<TR>
+ <TD>Enter prepaid card: </TD>
+ <TD><INPUT TYPE="text" NAME="prepaid" VALUE="<% $prepaid |h %>" <% $payby eq "PREP" ? '' : 'disabled' %>></TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Recharge">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Recharge customer service');
+
+my($svcnum, $prepaid, $payby);
+if ( $cgi->param('error') ) {
+ $svcnum = $cgi->param('svcnum');
+ $prepaid = $cgi->param('prepaid');
+ $payby = $cgi->param('payby');
+} elsif ( $cgi->param('svcnum') =~ /^(\d+)$/ ) {
+ $svcnum = $1;
+ $prepaid = '';
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+my $title = 'Recharge Service';
+
+my $cust_svc = qsearchs('cust_svc', {'svcnum' => $svcnum});
+die "No such service: $svcnum" unless $cust_svc;
+
+my($label, $value) = $cust_svc->label;
+
+$payby = $cust_svc->cust_pkg->cust_main->payby unless $payby;
+my $part_pkg = $cust_svc->cust_pkg->part_pkg;
+my $amount = $part_pkg->option('recharge_amount', 1) || 0;
+
+my $recharge_label = "Charge $money_char$amount for ";
+
+$recharge_label .= $part_pkg->option('recharge_seconds', 1) . 's '
+ if $part_pkg->option('recharge_seconds', 1);
+
+
+$recharge_label .= FS::UI::bytecount::display_bytecount(
+ $part_pkg->option('recharge_upbytes', 1) )
+ . ' up '
+ if $part_pkg->option('recharge_upbytes', 1);
+
+
+$recharge_label .= FS::UI::bytecount::display_bytecount(
+ $part_pkg->option('recharge_downbytes', 1) )
+ . ' down '
+ if $part_pkg->option('recharge_downbytes', 1);
+
+
+$recharge_label .= FS::UI::bytecount::display_bytecount(
+ $part_pkg->option('recharge_totalbytes', 1) )
+ . ' total '
+ if $part_pkg->option('recharge_totalbytes', 1);
+
+
+$recharge_label = ''
+ unless ($recharge_label ne "Charge $money_char$amount for ");
+
+</%init>
+
diff --git a/httemplate/misc/send-invoice.cgi b/httemplate/misc/send-invoice.cgi
new file mode 100644
index 0000000..32dfe27
--- /dev/null
+++ b/httemplate/misc/send-invoice.cgi
@@ -0,0 +1,30 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%once>
+
+my %method = ( map { $_=>1 } qw( email print fax_invoice ) );
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $invnum = $cgi->param('invnum');
+my $template = $cgi->param('template');
+my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name');
+my $method = $cgi->param('method');
+
+$method .= '_invoice' if $method eq 'fax'; #!
+
+die "unknown method $method" unless $method{$method};
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+$cust_bill->$method({ 'template' => $template,
+ 'notice_name' => $notice_name,
+ });
+
+my $custnum = $cust_bill->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/send-statement.cgi b/httemplate/misc/send-statement.cgi
new file mode 100755
index 0000000..e363fbd
--- /dev/null
+++ b/httemplate/misc/send-statement.cgi
@@ -0,0 +1,28 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%once>
+
+my %method = map { $_=>1 } qw( email print fax_invoice );
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $statementnum = $cgi->param('statementnum');
+my $template = $cgi->param('template') || 'statement'; #XXX configure... via event?? eh..
+my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name');
+my $method = $cgi->param('method');
+
+$method .= '_invoice' if $method eq 'fax'; #!
+
+die "unknown method $method" unless $method{$method};
+
+my $cust_statement = qsearchs('cust_statement',{'statementnum'=>$statementnum});
+die "Can't find statement!\n" unless $cust_statement;
+
+$cust_statement->$method({ 'template' => $template });
+
+my $custnum = $cust_statement->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/spool_invoices.cgi b/httemplate/misc/spool_invoices.cgi
new file mode 100644
index 0000000..bfe24e6
--- /dev/null
+++ b/httemplate/misc/spool_invoices.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_respool', $cgi;
+
+</%init>
diff --git a/httemplate/misc/states.cgi b/httemplate/misc/states.cgi
new file mode 100644
index 0000000..02b7be4
--- /dev/null
+++ b/httemplate/misc/states.cgi
@@ -0,0 +1,7 @@
+[ <% join(', ', map { qq("$_") } @output) %> ]
+<%init>
+
+my $country = $cgi->param('arg');
+my @output = states_hash($country);
+
+</%init>
diff --git a/httemplate/misc/svc_acct-domains.cgi b/httemplate/misc/svc_acct-domains.cgi
new file mode 100644
index 0000000..5734574
--- /dev/null
+++ b/httemplate/misc/svc_acct-domains.cgi
@@ -0,0 +1,31 @@
+[ <% join(', ', map { qq("$_->[0]", "$_->[1]") } @svc_domain) %> ]
+<%init>
+
+my $conf = new FS::Conf;
+
+my $pkgpart_svcpart = $cgi->param('arg');
+$pkgpart_svcpart =~ /^\d+_(\d+)$/;
+my $part_svc = qsearchs('part_svc', { 'svcpart' => $1 }) if $1;
+my $part_svc_column = $part_svc->part_svc_column('domsvc') if $part_svc;
+
+my @output = split /,/, $part_svc_column->columnvalue if $part_svc_column;
+my $columnflag = $part_svc_column->columnflag if $part_svc_column;
+my @svc_domain = ();
+my %seen = ();
+
+foreach (@output) {
+ my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $_ })
+ or warn "unknown svc_domain.svcnum $_ for part_svc_column domsvc; ".
+ "svcpart = " . $part_svc->svcpart;
+ push @svc_domain, [ $_ => $svc_domain->domain ];
+ $seen{$_}++;
+}
+if ($conf->exists('svc_acct-alldomains')
+ && ( $columnflag eq 'D' || $columnflag eq '' )
+ ) {
+ foreach (grep { $_->svcnum ne $output[0] } qsearch('svc_domain', {}) ){
+ push @svc_domain, [ $_->svcnum => $_->domain ];
+ }
+}
+
+</%init>
diff --git a/httemplate/misc/tax-fetch_and_import.cgi b/httemplate/misc/tax-fetch_and_import.cgi
new file mode 100644
index 0000000..33a6c9b
--- /dev/null
+++ b/httemplate/misc/tax-fetch_and_import.cgi
@@ -0,0 +1,48 @@
+<% include("/elements/header.html",'Tax Rate Download and Import') %>
+
+Import a tax data update.
+<BR><BR>
+
+<% include( '/elements/progress-init.html', 'TaxRateImport',[ 'format', ],
+ 'process/tax-fetch_and_import.cgi', { 'message' => 'Tax rates imported' },
+ )
+%>
+
+<FORM NAME="TaxRateImport" ACTION="javascript:void()" METHOD="POST">
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <OPTION VALUE="cch">CCH import
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TH ALIGN="right">Update Password</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="password">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ VALUE = "Download and Import"
+ onClick = "document.TaxRateImport.submit.disabled=true; process();"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/tax-fetch_and_replace.cgi b/httemplate/misc/tax-fetch_and_replace.cgi
new file mode 100644
index 0000000..3290a3c
--- /dev/null
+++ b/httemplate/misc/tax-fetch_and_replace.cgi
@@ -0,0 +1,48 @@
+<% include("/elements/header.html",'Tax Rate Download and Import') %>
+
+Replace tax data.
+<BR><BR>
+
+<% include( '/elements/progress-init.html', 'TaxRateImport',[ 'format', ],
+ 'process/tax-fetch_and_replace.cgi', { 'message' => 'Tax rates replaced' },
+ )
+%>
+
+<FORM NAME="TaxRateImport" ACTION="javascript:void()" METHOD="POST">
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <OPTION VALUE="cch">CCH import
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TH ALIGN="right">Update Password</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="password">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ VALUE = "Download and Import"
+ onClick = "document.TaxRateImport.submit.disabled=true; process();"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/tax-import.cgi b/httemplate/misc/tax-import.cgi
new file mode 100644
index 0000000..ceb7464
--- /dev/null
+++ b/httemplate/misc/tax-import.cgi
@@ -0,0 +1,74 @@
+<% include("/elements/header.html",'Batch Tax Rate Import') %>
+
+Import a CSV file set containing tax rate records.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'TaxRateUpload',
+ 'action' => 'process/tax-import.cgi',
+ 'num_files' => 6,
+ 'fields' => [ 'format', 'reload' ],
+ 'message' => 'Tax rates imported',
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <!-- <OPTION VALUE="cch-update" SELECTED>CCH update (CSV) -->
+ <OPTION VALUE="cch">CCH import (CSV)
+ <!-- <OPTION VALUE="cch-fixed-update">CCH update (fixed length) -->
+ <OPTION VALUE="cch-fixed">CCH import (fixed length)
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Replace existing data from this vendor</TH>
+ <TD>
+ <INPUT NAME="reload" TYPE="checkbox" VALUE="1" CHECKED>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => [ 'geocodefile',
+ 'codefile',
+ 'plus4file',
+ 'zipfile',
+ 'txmatrixfile',
+ 'detailfile',
+ ],
+ 'label' => [ 'geocode filename',
+ 'code filename',
+ 'plus4 filename',
+ 'zip filename',
+ 'txmatrix filename',
+ 'detail filename',
+ ],
+ 'debug' => 0,
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ VALUE = "Import CSV files"
+ onClick = "document.TaxRateUpload.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html
new file mode 100755
index 0000000..46063e8
--- /dev/null
+++ b/httemplate/misc/timeworked.html
@@ -0,0 +1,138 @@
+<% include('/elements/header.html', $title, '' ) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="timeworked_form" ACTION="<% popurl(1) %>process/timeworked.html" METHOD=POST>
+
+<TABLE CELLSPACING="2" CELLPADDING="2" RULES="groups" FRAME="hsides">
+
+ <THEAD>
+ <TR>
+ <TH>Trans</TH>
+ <TH COLSPAN="2">Ticket</TH>
+ <TH>Time</TH>
+ <TH COLSPAN="2">Customer</TH>
+ <TH>Multiplier</TH>
+ </TR>
+
+ <TR>
+ <TH>#</TH>
+ <TH>#</TH>
+ <TH>Subject</TH>
+ <TH>hours</TH>
+ <TH>#</TH>
+ <TH>Name</TH>
+ <TH></TH>
+ </TR>
+ </THEAD>
+
+ <TBODY>
+
+% foreach my $tr_id ( keys %ticketmap ) {
+% my (@customers) = @{$customers{$ticketmap{$tr_id}}};
+% next unless @customers;
+% my $default_multiplier = sprintf("%.2f", 1/@customers);
+% my ($custnum, $name) = split(':', pop @customers, 2);
+% my $link = $p. 'rt/Ticket/Display.html?id='. $ticketmap{$tr_id}.
+% '#txn-'. $tr_id;
+
+ <TR>
+ <TD><a href="<% $link %>"><% $tr_id %></a></TD>
+ <TD><a href="<% $link %>"><% $ticketmap{$tr_id} %></a></TD>
+ <TD><a href="<% $link %>"><% $ticket{$ticketmap{$tr_id}} |h %></a></TD>
+
+% my $seconds = 0;
+% if ( $cgi->param("seconds$tr_id") =~ /^(\d+)$/ ) {
+% $seconds = $1;
+% }
+
+ <TD><% sprintf("%0.2f", $seconds/3600) %></TD>
+ <TD ALIGN="right"><% $custnum %></TD>
+ <TD ALIGN="right"><% $name %></TD>
+ <TD>
+ <INPUT TYPE="hidden" NAME="transactionid<%$tr_id%>" VALUE="1" >
+ <INPUT TYPE="hidden" NAME="seconds<%$tr_id%>" VALUE="<% $seconds %>" >
+
+% my $multiplier = $default_multiplier;
+% my $mult_paramname = "multiplier${tr_id}_$custnum";
+% if ( $cgi->param($mult_paramname) =~ /^\s*([\d\.]+)\s*$/ ) {
+% $multiplier = $1;
+% }
+
+ <INPUT TYPE="text" NAME="<% $mult_paramname %>" SIZE="5" VALUE="<% $multiplier %>" >
+ </TD>
+ </TR>
+
+% foreach ( @customers ) {
+% ($custnum, $name) = split(':', $_, 2);
+
+ <TR>
+ <TD ALIGN="right" COLSPAN="5" ><% $custnum %></TD>
+ <TD ALIGN="right"><% $name %></TD>
+ <TD>
+
+% $multiplier = $default_multiplier;
+% $mult_paramname = "multiplier${tr_id}_$custnum";
+% if ( $cgi->param($mult_paramname) =~ /^\s*([\d\.]+)\s*$/ ) {
+% $multiplier = $1;
+% }
+
+ <INPUT TYPE="text" NAME="<% $mult_paramname %>" SIZE="5" VALUE="<% $multiplier %>" >
+
+ </TD>
+
+ </TR>
+
+% }
+% }
+
+ </TBODY>
+
+</TABLE>
+
+<BR>
+
+<INPUT TYPE="hidden" NAME="begin" VALUE="<% $cgi->param('begin') |h %>">
+<INPUT TYPE="hidden" NAME="end" VALUE="<% $cgi->param('end') |h %>">
+
+<INPUT TYPE="submit" NAME="submit" VALUE="<% $title %>">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Time queue');
+
+my(%ticketmap, %ticket, %customers);
+my $title = 'Assign Time Worked';
+tie %ticketmap, 'Tie::IxHash';
+
+RT::Init();
+
+my $CurrentUser = RT::CurrentUser->new();
+$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
+
+foreach my $id ( map { /^transactionid(\d+)$/; $1; }
+ grep /^transactionid\d+$/, $cgi->param) {
+ my $transaction = new RT::Transaction($CurrentUser);
+ $transaction->Load($id);
+ $ticketmap{$id} = $transaction->ObjectId;
+ unless(exists($ticket{$ticketmap{$id}})) {
+ my $ticket = new RT::Ticket($CurrentUser);
+ $ticket->Load($ticketmap{$id});
+ $ticket{$ticketmap{$id}} = $ticket->Subject;
+ $customers{$ticketmap{$id}} =
+ [ map { $_->Resolver->AsString }
+ grep { $_->Resolver->{'fstable'} eq 'cust_main' }
+ grep { $_->Scheme eq 'freeside' }
+ map { $_->TargetURI }
+ @{ $ticket->_Links('Base')->ItemsArrayRef }
+ ];
+
+ }
+}
+
+</%init>
+
diff --git a/httemplate/misc/unadjourn_pkg.cgi b/httemplate/misc/unadjourn_pkg.cgi
new file mode 100755
index 0000000..356b49c
--- /dev/null
+++ b/httemplate/misc/unadjourn_pkg.cgi
@@ -0,0 +1,17 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Suspend customer package later');
+
+my ($pkgnum) = $cgi->keywords;
+my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+my $error = "No package $pkgnum" unless $cust_pkg;
+
+$error ||= $cust_pkg->unadjourn;
+
+</%init>
diff --git a/httemplate/misc/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi
new file mode 100755
index 0000000..ed739ac
--- /dev/null
+++ b/httemplate/misc/unapply-cust_credit.cgi
@@ -0,0 +1,20 @@
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unapply credit');
+
+#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;
+ errorpage($error) if $error;
+}
+
+</%init>
diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi
new file mode 100755
index 0000000..8cdac18
--- /dev/null
+++ b/httemplate/misc/unapply-cust_pay.cgi
@@ -0,0 +1,20 @@
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unapply payment');
+
+#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;
+ errorpage($error) if $error;
+}
+
+</%init>
diff --git a/httemplate/misc/unexpire_pkg.cgi b/httemplate/misc/unexpire_pkg.cgi
new file mode 100755
index 0000000..4450255
--- /dev/null
+++ b/httemplate/misc/unexpire_pkg.cgi
@@ -0,0 +1,17 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer package later');
+
+my ($pkgnum) = $cgi->keywords;
+my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+my $error = "No package $pkgnum" unless $cust_pkg;
+
+$error ||= $cust_pkg->unexpire;
+
+</%init>
diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi
new file mode 100755
index 0000000..4ab15fd
--- /dev/null
+++ b/httemplate/misc/unprovision.cgi
@@ -0,0 +1,26 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unprovision customer service');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/unsusp_pkg.cgi b/httemplate/misc/unsusp_pkg.cgi
new file mode 100755
index 0000000..b350693
--- /dev/null
+++ b/httemplate/misc/unsusp_pkg.cgi
@@ -0,0 +1,20 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer package');
+
+#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;
+
+</%init>
diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi
new file mode 100755
index 0000000..91fe1c2
--- /dev/null
+++ b/httemplate/misc/unvoid-cust_pay_void.cgi
@@ -0,0 +1,21 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unvoid');
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay_void = qsearchs('cust_pay_void', { 'paynum' => $paynum } );
+my $custnum = $cust_pay_void->custnum;
+
+my $error = $cust_pay_void->unvoid;
+
+</%init>
diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi
new file mode 100644
index 0000000..d1a84fd
--- /dev/null
+++ b/httemplate/misc/upload-batch.cgi
@@ -0,0 +1,36 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+ <% include('/elements/header.html','Batch results upload successful') %>
+ <% include('/elements/footer.html') %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Process batches');
+
+my $error;
+
+my $fh = $cgi->upload('batch_results');
+$error = 'No file uploaded' unless defined($fh);
+
+unless ( $error ) {
+
+ $cgi->param('batchnum') =~ /^(\d+)$/;
+ my $batchnum = $1;
+
+ my $pay_batch = qsearchs( 'pay_batch', { 'batchnum' => $batchnum } );
+ if ( ! $pay_batch ) {
+ $error = "batchnum $batchnum not found";
+ } elsif ( $pay_batch->status ne 'I' ) {
+ $error = "batch $batchnum is not in transit";
+ } else {
+ $error = $pay_batch->import_results(
+ 'filehandle' => $fh,
+ 'format' => $cgi->param('format'),
+ );
+ }
+
+}
+
+</%init>
diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi
new file mode 100755
index 0000000..7b484e9
--- /dev/null
+++ b/httemplate/misc/void-cust_pay.cgi
@@ -0,0 +1,26 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+%}
+<%init>
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum});
+
+my $right = 'Regular void';
+$right = 'Credit card void' if $cust_pay->payby eq 'CARD';
+$right = 'Echeck void' if $cust_pay->payby eq 'CHEK';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right($right);
+
+my $custnum = $cust_pay->custnum;
+
+my $error = $cust_pay->void;
+
+</%init>
diff --git a/httemplate/misc/whois.cgi b/httemplate/misc/whois.cgi
new file mode 100644
index 0000000..533b2d7
--- /dev/null
+++ b/httemplate/misc/whois.cgi
@@ -0,0 +1,33 @@
+<% include("/elements/header.html","Whois $domain", menubar(
+ ( $custnum
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ()
+ ),
+ "View this domain (#$svcnum)" => "${p}view/svc_domain.cgi?$svcnum",
+)) %>
+
+<PRE><% $whois %></PRE>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+my $svcnum = $cgi->param('svcnum');
+my $custnum = $cgi->param('custnum');
+my $domain = $cgi->param('domain');
+
+my $display_custnum;
+if ( $custnum ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ $display_custnum = $cust_main->display_custnum;
+}
+
+my $whois = eval { whois($domain) };
+ if ( $@ ) {
+ ( $whois = $@ ) =~ s/ at \/.*Net\/Whois\/Raw\.pm line \d+.*$//s;
+ } else {
+ $whois =~ s/^\n+//;
+ }
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-calculate_taxes.html b/httemplate/misc/xmlhttp-calculate_taxes.html
new file mode 100644
index 0000000..d3dc36a
--- /dev/null
+++ b/httemplate/misc/xmlhttp-calculate_taxes.html
@@ -0,0 +1,106 @@
+<% objToJson($return) %>
+<%init>
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+
+if ( $sub eq 'calculate_taxes' ) {
+
+ {
+ my %arg = $cgi->param('arg');
+ $return = \%arg;
+ warn join('', map "$_: $arg{$_}\n", keys %arg )
+ if $DEBUG;
+
+ my $cust_credit = qsearchs( 'cust_credit', { 'crednum' => $arg{crednum} } );
+ unless ($cust_credit) {
+ $return->{error} = "No such credit: $arg{crednum}";
+ last;
+ }
+
+ my %cust_bill_pkg = ();
+ my @items = split(',', $arg{items});
+ while ( @items ) {
+ my ($billpkgnum, $s_or_r, $amount ) = splice(@items, 0, 3);
+ unless ( defined($amount) ) {
+ $return->{error} = "Bad items argument list";
+ last;
+ }
+ unless ( $cust_bill_pkg{$billpkgnum} ) {
+ my $cust_bill_pkg =
+ qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $billpkgnum } );
+ unless ($cust_bill_pkg) {
+ $return->{error} = "No such line item: $billpkgnum";
+ last;
+ }
+ next unless $cust_bill_pkg->pkgnum > 0; #hmmm @ one shot (-1)
+ unless ($cust_bill_pkg->cust_pkg->custnum == $cust_credit->custnum) {
+ $return->{error} = "Credit/line item customer mismatch";
+ last;
+ }
+ $cust_bill_pkg{$billpkgnum} = $cust_bill_pkg;
+ $cust_bill_pkg->setup(0);
+ $cust_bill_pkg->recur(0);
+ }
+
+ last if $return->{error};
+
+ my $cust_bill_pkg = $cust_bill_pkg{$billpkgnum};
+ $s_or_r = $s_or_r eq 'setup' ? 'setup' : 'recur';
+ my $value = sprintf('%.2f', $cust_bill_pkg->$s_or_r + $amount);
+ $cust_bill_pkg->$s_or_r($value);
+ }
+
+ last if $return->{error};
+
+ my $cust_main = $cust_credit->cust_main;
+
+ my $taxlisthash = {};
+ foreach my $cust_bill_pkg (values %cust_bill_pkg) {
+ my $part_pkg = $cust_bill_pkg->part_pkg;
+ $cust_main->_handle_taxes( $part_pkg,
+ $taxlisthash,
+ $cust_bill_pkg,
+ $cust_bill_pkg->cust_pkg,
+ $cust_bill_pkg->cust_bill->_date,
+ $cust_bill_pkg->cust_pkg->pkgpart,
+ );
+ }
+ my $listref_or_error =
+ $cust_main->calculate_taxes( [ values %cust_bill_pkg ], $taxlisthash, [ values %cust_bill_pkg ]->[0]->cust_bill->_date );
+
+ unless ( ref( $listref_or_error ) ) {
+ $return->{error} = "No such credit: $arg{crednum}";
+ last;
+ }
+
+ my @taxlines = ();
+ $return->{taxlines} = \@taxlines;
+ foreach my $taxline ( @$listref_or_error ) {
+ my $amount = $taxline->setup;
+ my $desc = $taxline->desc;
+ foreach my $location ( @{$taxline->cust_bill_pkg_tax_location}, @{$taxline->cust_bill_pkg_tax_rate_location} ) {
+ my $taxlocnum = $location->locationnum || '';
+ my $taxratelocnum = $location->taxratelocationnum || '';
+ $location->cust_bill_pkg_desc($taxline->desc); #ugh @ that kludge
+ push @taxlines,
+ [ $location->desc, $taxline->setup, $taxlocnum, $taxratelocnum ];
+ $amount -= $location->amount;
+ }
+ if ($amount > 0) {
+ push @taxlines,
+ [ $taxline->itemdesc. ' (default)', sprintf('%.2f', $amount), '', '' ];
+ }
+ }
+
+ $return->{taxlines} = \@taxlines;
+
+ }
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-address_standardize.html b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
new file mode 100644
index 0000000..3b9e142
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
@@ -0,0 +1,92 @@
+<% objToJson($return) %>
+<%init>
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+
+if ( $sub eq 'address_standardize' ) {
+
+ my %arg = $cgi->param('arg');
+ $return = \%arg;
+ warn join('', map "$_: $arg{$_}\n", keys %arg )
+ if $DEBUG;
+
+ my $userid = $conf->config('usps_webtools-userid');
+ my $password = $conf->config('usps_webtools-password');
+
+ if ( length($userid) && length($password) ) {
+
+ my $verifier = Business::US::USPS::WebTools::AddressStandardization->new( {
+ UserID => $userid, #$ENV{USPS_WEBTOOLS_USERID},
+ Password => $password, #$ENV{USPS_WEBTOOLS_PASSWORD},
+ #Testing => 1,
+ } );
+
+ foreach my $pre ( '', 'ship_' ) {
+
+ my($zip5, $zip4) = split('-',$arg{$pre.'zip'});
+
+ my %usps_args = (
+ FirmName => $arg{$pre.'company'},
+ Address2 => $arg{$pre.'address1'},
+ Address1 => $arg{$pre.'address2'},
+ City => $arg{$pre.'city'},
+ State => $arg{$pre.'state'},
+ Zip5 => $zip5,
+ Zip4 => $zip4,
+ );
+ warn join('', map "$_: $usps_args{$_}\n", keys %usps_args )
+ if $DEBUG;
+
+ my $hash = $verifier->verify_address( %usps_args );
+
+ warn $verifier->response
+ if $DEBUG;
+
+ unless ( $verifier->is_error ) {
+
+ my $zip = $hash->{Zip5};
+ $zip .= '-'. $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
+
+ $return = {
+ %$return,
+ "new_$pre".'company' => $hash->{FirmName},
+ "new_$pre".'address1' => $hash->{Address2},
+ "new_$pre".'address2' => $hash->{Address1},
+ "new_$pre".'city' => $hash->{City},
+ "new_$pre".'state' => $hash->{State},
+ "new_$pre".'zip' => $zip,
+ };
+
+ my @fields = (qw( company address1 address2 city state zip )); #hmm
+
+ my $changed =
+ scalar( grep { $return->{$pre.$_} ne $return->{"new_$pre$_"} }
+ @fields
+ )
+ ? 1 : 0;
+
+ $return->{$pre.'address_standardized'} = $changed;
+
+ } else {
+
+ $return->{$pre.'error'} = "USPS WebTools error: ".
+ $verifier->{error}{description};
+
+
+ }
+
+ }
+
+ }
+
+ $return;
+
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
new file mode 100644
index 0000000..9d588d7
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-censustract.html
@@ -0,0 +1,105 @@
+<% objToJson($return) %>
+<%init>
+
+my $DEBUG = 0;
+
+my $url='http://www.ffiec.gov/Geocode/default.aspx';
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+my $error = '';
+
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Request::Common qw( GET POST );
+use HTML::TokeParser;
+
+if ( $sub eq 'censustract' ) {
+
+ my %arg = $cgi->param('arg');
+ warn join('', map "$_: $arg{$_}\n", keys %arg )
+ if $DEBUG;
+
+ my $ua = new LWP::UserAgent;
+ my $res = $ua->request( GET( $url ) );
+
+ warn $res->as_string
+ if $DEBUG > 1;
+
+ unless ($res->code eq '200') {
+
+ $error = $res->message;
+
+ } else {
+
+ my $content = $res->content;
+ my $p = new HTML::TokeParser \$content;
+ my $viewstate;
+ while (my $token = $p->get_tag('input') ) {
+ next unless $token->[1]->{name} eq '__VIEWSTATE';
+ $viewstate = $token->[1]->{value};
+ last;
+ }
+
+ unless ($viewstate) {
+
+ $error = "no __VIEWSTATE found";
+
+ } else {
+
+ my($zip5, $zip4) = split('-',$arg{zip});
+
+ my @ffiec_args = (
+ __VIEWSTATE => $viewstate,
+ ddlbYear => $arg{year},
+ txtAddress => $arg{address},
+ txtCity => $arg{city},
+ ddlbState => $arg{state},
+ txtZipCode => $zip5,
+ btnSearch => 'Search',
+ );
+ warn join("\n", @ffiec_args )
+ if $DEBUG;
+
+ $res = $ua->request( POST( $url, \@ffiec_args ) );
+ warn $res->as_string
+ if $DEBUG > 1;
+
+ unless ($res->code eq '200') {
+
+ $error = $res->message;
+
+ } else {
+
+ my @id = qw( MSACode StateCode CountyCode TractCode );
+ $content = $res->content;
+ $p = new HTML::TokeParser \$content;
+ my $prefix = 'UcGeoResult11_lb';
+ my $compare =
+ sub { my $t=shift; scalar( grep { lc($t) eq lc("$prefix$_")} @id ) };
+
+ while (my $token = $p->get_tag('span') ) {
+ next unless ( $token->[1]->{id} && &$compare( $token->[1]->{id} ) );
+ $token->[1]->{id} =~ /^$prefix(\w+)$/;
+ $return->{lc($1)} = $p->get_trimmed_text("/span");
+ }
+
+ $error = "No census tract found" unless $return->{tractcode};
+ $return->{tractcode} .= ' '
+ unless $error || $JSON::VERSION >= 2; #broken JSON 1 workaround
+
+ } #unless ($res->code eq '200')
+
+ } #unless ($viewstate)
+
+ } #unless ($res->code eq '200')
+
+ $error = "FFIEC Geocoding error: $error" if $error;
+ $return->{'error'} = $error;
+
+ $return;
+
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
new file mode 100644
index 0000000..26e68b5
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -0,0 +1,36 @@
+% if ( $sub eq 'custnum_search' ) {
+%
+% my $custnum = $cgi->param('arg');
+% my $cust_main = '';
+% if ( $custnum <= 2147483647 ) {
+% $cust_main = qsearchs({
+% 'table' => 'cust_main',
+% 'hashref' => { 'custnum' => $custnum },
+% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+% });
+% }
+% if ( ! $cust_main ) {
+% $cust_main = qsearchs({
+% 'table' => 'cust_main',
+% 'hashref' => { 'agent_custid' => $custnum },
+% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+% });
+% }
+%
+"<% $cust_main ? $cust_main->name : '' %>"
+%
+% } elsif ( $sub eq 'smart_search' ) {
+%
+% my $string = $cgi->param('arg');
+% my @cust_main = smart_search( 'search' => $string );
+% my $return = [ map [ $_->custnum, $_->name ], @cust_main ];
+%
+<% objToJson($return) %>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html
new file mode 100644
index 0000000..e993032
--- /dev/null
+++ b/httemplate/misc/xmlhttp-ping.html
@@ -0,0 +1,20 @@
+<% objToJson($return) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+die "$sub not supported" unless $sub eq 'ping';
+
+my $ip = $cgi->param('arg');
+
+my $ping = new Net::Ping('external', 5);
+$ping->hires(1);
+#my $a=time; warn "pinging\n";
+my ($ret, $duration, $ip2) = $ping->ping($ip);
+#warn "done pinging (". int(time-$a). "s)\n";
+
+my $return = [ $ret, $duration ];
+
+</%init>
diff --git a/httemplate/misc/xmlrpc.cgi b/httemplate/misc/xmlrpc.cgi
new file mode 100644
index 0000000..1d0383f
--- /dev/null
+++ b/httemplate/misc/xmlrpc.cgi
@@ -0,0 +1,18 @@
+%
+%
+% my $request_xml = $cgi->param('POSTDATA');
+%
+% #$r->log_error($request_xml);
+%
+% my $fsxmlrpc = new FS::XMLRPC;
+% my ($error, $response_xml) = $fsxmlrpc->serve($request_xml);
+%
+% #$r->log_error($error) if $error;
+%
+% http_header('Content-Type' => 'text/xml',
+% 'Content-Length' => length($response_xml));
+%
+% print $response_xml;
+%
+%
+
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
new file mode 100644
index 0000000..47f80af
--- /dev/null
+++ b/httemplate/pref/pref-process.html
@@ -0,0 +1,70 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string ) %>
+% } else {
+<% include('/elements/header.html', 'Preferences updated') %>
+<% include('/elements/footer.html') %>
+% }
+<%init>
+
+my $error = '';
+my $access_user = '';
+
+if ( grep { $cgi->param($_) !~ /^\s*$/ }
+ qw(_password new_password new_password2)
+ ) {
+
+ $access_user = qsearchs( 'access_user', {
+ 'username' => getotaker,
+ '_password' => $cgi->param('_password'),
+ } );
+
+ $error = 'Current password incorrect; password not changed'
+ unless $access_user;
+
+ $error ||= "New passwords don't match"
+ unless $cgi->param('new_password') eq $cgi->param('new_password2');
+
+ $error ||= "No new password entered"
+ unless length($cgi->param('new_password'));
+
+ $access_user->_password($cgi->param('new_password')) unless $error;
+
+} else {
+
+ $access_user = $FS::CurrentUser::CurrentUser;
+
+}
+
+#well, if you got your password change wrong, you don't get anything else
+#changed right now. but it should be sticky on the form
+unless ( $error ) { # if ($access_user) {
+
+ my %param = $access_user->options;
+
+ #XXX autogen
+ my @paramlist = qw( menu_position default_customer_view
+ disable_html_editor
+ email_address
+ snom-ip snom-username snom-password
+ vonage-fromnumber vonage-username vonage-password
+ show_pkgnum show_confitem_counts export_getsettings
+ show_db_profile save_db_profile
+ height width availHeight availWidth colorDepth
+ );
+
+ foreach (@paramlist) {
+ scalar($cgi->param($_)) =~ /^[,.\-\@\w]*$/ && next;
+ $error ||= "Illegal value for parameter $_";
+ last;
+ }
+
+ foreach (@paramlist) {
+ $param{$_} = scalar($cgi->param($_));
+ }
+
+ $error ||= $access_user->replace( \%param );
+
+}
+
+</%init>
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
new file mode 100644
index 0000000..ab76c3b
--- /dev/null
+++ b/httemplate/pref/pref.html
@@ -0,0 +1,189 @@
+<% include('/elements/header.html', 'Preferences for '. getotaker ) %>
+
+<FORM METHOD="POST" NAME="pref_form" ACTION="pref-process.html">
+
+<% include('/elements/error.html') %>
+
+
+Change password (leave blank for no change)
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH ALIGN="right">Current password: </TH>
+ <TD><INPUT TYPE="password" NAME="_password"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">New password: </TH>
+ <TD><INPUT TYPE="password" NAME="new_password"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Re-enter new password: </TH>
+ <TD><INPUT TYPE="password" NAME="new_password2"></TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+
+Interface
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH ALIGN="right">Menu location: </TH>
+ <TD>
+ <INPUT TYPE="radio" NAME="menu_position" VALUE="left" onClick="document.images['menu_example'].src='../images/menu-left-example.png';" <% $menu_position eq 'left' ? ' CHECKED' : ''%>> Left<BR>
+ <INPUT TYPE="radio" NAME="menu_position" VALUE="top"onClick="document.images['menu_example'].src='../images/menu-top-example.png';" <% $menu_position eq 'top' ? ' CHECKED' : ''%>> Top <BR>
+ </TD>
+ <TD><IMG NAME="menu_example" SRC="../images/menu-<% $menu_position %>-example.png"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Default customer view: </TH>
+ <TD COLSPAN=2>
+ <SELECT NAME="default_customer_view">
+% foreach my $view ( keys %customer_views ) {
+% my $selected =
+% $customer_views{$view} eq $curuser->option('default_customer_view')
+% ? 'SELECTED'
+% : '';
+ <OPTION VALUE="<%$customer_views{$view}%>" <%$selected%>><%$view%></OPTION>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" COLSPAN=1>Disable HTML editor for customer notes: </TH>
+ <TD ALIGN="left" COLSPAN=2>
+ <INPUT TYPE="checkbox" NAME="disable_html_editor" VALUE="1" <% $curuser->option('disable_html_editor') ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+
+Email Address
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH>Email Address(es) (comma separated) </TH>
+ <TD>
+ <TD><INPUT TYPE="text" NAME="email_address" VALUE="<% $email_address %>">
+ </TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+
+Development
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH>Show internal package numbers: </TH>
+ <TD><INPUT TYPE="checkbox" NAME="show_pkgnum" VALUE="1" <% $curuser->option('show_pkgnum') ? 'CHECKED' : '' %>></TD>
+ </TR>
+ <TR>
+ <TH>Show config item counts: </TH>
+ <TD><INPUT TYPE="checkbox" NAME="show_confitem_counts" VALUE="1" <% $curuser->option('show_confitem_counts') ? 'CHECKED' : '' %>></TD>
+ </TR>
+ <TR>
+ <TH>Show export data on service view (when available): </TH>
+ <TD><INPUT TYPE="checkbox" NAME="export_getsettings" VALUE="1" <% $curuser->option('export_getsettings') ? 'CHECKED' : '' %>></TD>
+ </TR>
+ <TR>
+ <TH>Show database profiling (when available): </TH>
+ <TD><INPUT TYPE="checkbox" NAME="show_db_profile" VALUE="1" <% $curuser->option('show_db_profile') ? 'CHECKED' : '' %>></TD>
+ </TR>
+ <TR>
+ <TH>Save database profiling logs (when available): </TH>
+ <TD><INPUT TYPE="checkbox" NAME="save_db_profile" VALUE="1" <% $curuser->option('save_db_profile') ? 'CHECKED' : '' %>></TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+SNOM integration
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH ALIGN="right">SNOM IP address</TH>
+ <TD><INPUT TYPE="text" NAME="snom-ip" VALUE="<% $curuser->option('snom-ip') %>"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">SNOM HTTP username (if necessary)</TH>
+ <TD><INPUT TYPE="text" NAME="snom-username" VALUE="<% $curuser->option('snom-username') %>"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">SNOM HTTP password (if necessary)</TH>
+ <TD><INPUT TYPE="password" NAME="snom-password" VALUE="<% $curuser->option('snom-password') %>"></TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+OR<BR><BR>
+
+Vonage integration (see <a href="https://secure.click2callu.com/">Click2Call</a>)
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TH ALIGN="right">Vonage phone number</TH>
+ <TD><INPUT TYPE="text" NAME="vonage-fromnumber" VALUE="<% $curuser->option('vonage-fromnumber') %>"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Vonage username</TH>
+ <TD><INPUT TYPE="text" NAME="vonage-username" VALUE="<% $curuser->option('vonage-username') %>"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Vonage password</TH>
+ <TD><INPUT TYPE="password" NAME="vonage-password" VALUE="<% $curuser->option('vonage-password') %>"></TD>
+ </TR>
+
+</TABLE>
+<BR>
+
+
+% foreach my $prop (qw( height width availHeight availWidth colorDepth )) {
+ <INPUT TYPE="hidden" NAME="<% $prop %>" VALUE="">
+ <SCRIPT TYPE="text/javascript">
+ document.pref_form.<% $prop %>.value = screen.<% $prop %>;
+ </script>
+% }
+
+<INPUT TYPE="submit" VALUE="Update preferences">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#false laziness w/view/cust_main.cgi and Conf.pm (cust_main-default_view)
+
+tie my %customer_views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+ 'Tickets' => 'tickets',
+ 'Packages' => 'packages',
+ 'Payment History' => 'payment_history',
+;
+$customer_views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$customer_views{'Jumbo'} = 'jumbo';
+
+# XSS via your own preferences? seems unlikely, but nice try anyway...
+( $curuser->option('menu_position') || 'top' )
+ =~ /^(\w+)$/ or die "illegal menu_position";
+my $menu_position = $1;
+( $curuser->option('email_address') )
+ =~ /^([,\w\@.]*)$/ or die "illegal email_address"; #too late
+my $email_address = $1;
+
+</%init>
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
new file mode 100755
index 0000000..bd7fb2d
--- /dev/null
+++ b/httemplate/search/477.html
@@ -0,0 +1,92 @@
+% unless ( $type eq 'xml' ) {
+<% include( '/elements/header.html', 'FCC Form 477 Results') %>
+%}else{
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<Form_477_submission xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://specialreports.fcc.gov/wcb/Form477/XMLSchema-instance/form_477_upload_Schema.xsd" >
+%}
+% if ( $type eq 'html' || $type eq 'html-print' ) {
+<TABLE WIDTH="100%">
+ <TR><TD></TD>
+%}elsif ( $type eq 'xml' ) {
+%}
+% unless ( $type eq 'html-print' || $type eq 'xml' ) {
+
+ <TD ALIGN="right">
+
+ Download full results<BR>
+% $cgi->param('_type', 'xml');
+ as <A HREF="<% $cgi->self_url %>">XML file</A><BR>
+
+% $cgi->param('_type', 'html-print');
+ as <A HREF="<% $cgi->self_url %>">printable copy</A>
+
+ </TD>
+% $cgi->param('_type', $type );
+% }
+% if ( $type eq 'html' || $type eq 'html-print' ) {
+ </TR>
+</TABLE>
+%}elsif ( $type eq 'xml' ) {
+%}
+% foreach my $part ( @parts ) {
+% if ( $part{$part} ) {
+%
+% if ( $part eq 'V' ) {
+% next unless ( $part{'IIA'} || $part{'IIB'} );
+% }
+%
+% if ( $part eq 'VI' ) {
+% next unless $part{'IA'};
+% }
+%
+% my @reports = ();
+% if ( $part eq 'IA' ) {
+% for ( my $tech = 0; $tech < scalar(@technology_option); $tech++ ) {
+% next unless $technology_option[$tech];
+% my $url = &{$url_mangler}($cgi->self_url, $part);
+% if ( $type eq 'xml' ) {
+<<% 'Part_IA_'. chr(65 + $tech) %>>
+% }
+<% include( "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url ) %>
+<% include( "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url ) %>
+% if ( $type eq 'xml' ) {
+</<% 'Part_IA_'. chr(65 + $tech) %>>
+% }
+% }
+% } else {
+% if ( $type eq 'xml' ) {
+<<% 'Part_'. uc($part) %>>
+% }
+% my $url = &{$url_mangler}($cgi->self_url, $part);
+<% include( "477part${part}.html", 'url' => $url ) %>
+% if ( $type eq 'xml' ) {
+</<% 'Part_'. uc($part) %>>
+% }
+% }
+% }
+% }
+%
+% if ( $type eq 'html' || $type eq 'html-print' ) {
+<% include( '/elements/footer.html') %>
+%}elsif ( $type eq 'xml' ) {
+</Form_477_submission>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %part = map { $_ => 1 } grep { /^\w+$/ } $cgi->param('part');
+my $type = $cgi->param('_type') || 'html';
+my $xlsname = '477report';
+my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
+my $url_mangler = sub {
+ my ($url, $part) = (shift, shift);
+ $url =~ s/477\./477part$part./;
+ $url;
+};
+my @parts = qw( IA IIA IIB IV V VI );
+
+</%init>
diff --git a/httemplate/search/477partIA_detail.html b/httemplate/search/477partIA_detail.html
new file mode 100755
index 0000000..546d56c
--- /dev/null
+++ b/httemplate/search/477partIA_detail.html
@@ -0,0 +1,125 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'lines',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'really_disable_download' => 1,
+ 'disable_download' => 1,
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'header' => [ '', @column_option_name ],
+ 'xml_elements' => [ @xml_elements ],
+ 'fields' => [ @fields ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %opt = @_;
+my %search_hash = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @column_option = grep { /^\d+/ } $cgi->param('part1_column_option')
+ if $cgi->param('part1_column_option');
+
+my @row_option = grep { /^\d+/ } $cgi->param('part1_row_option')
+ if $cgi->param('part1_row_option');
+
+my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
+
+my @column_option_name = scalar(@column_option)
+ ? ( map { my $part_pkg_report_option =
+ qsearchs({ 'table' => 'part_pkg_report_option',
+ 'hashref' => { num => $_ },
+ });
+ $part_pkg_report_option ? $part_pkg_report_option->name
+ : 'no such report option';
+ } @column_option
+ )
+ : ( 'all packages' );
+
+my $where = join(' OR ', map { "num = $_" } @row_option );
+my %row_option_name = $where ?
+ ( map { $_->num => $_->name }
+ qsearch({ 'table' => 'part_pkg_report_option',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE $where",
+ })
+ ) :
+ ();
+
+my $tech_code = $opt{tech_code};
+my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown';
+my $html_init = "<H2>Part IA $technology breakdown by speeds</H2>";
+my $xml_prefix = 'PartIA_'. chr(65 + $tech_code);
+
+my $query = 'SELECT '. join(' UNION ALL SELECT ',@row_option);
+my $count_query = 'SELECT '. scalar(@row_option);
+
+my $value = sub {
+ my ($rowref, $column) = (shift, shift);
+ my $row = $rowref->[0];
+
+ if ($column eq 'name') {
+ return $row_option_name{$row} || 'no such report option';
+ } elsif ( $column =~ /^(\d+)$/ ) {
+ my @report_option = ( $row || '',
+ $column_option[$1 - 2] || '',
+ $technology_option[$tech_code] || '',
+ );
+
+ my ( $count, $residential ) = FS::cust_pkg->fcc_477_count(
+ { %search_hash, 'report_option' => join(',', @report_option) }
+ );
+
+ my $percentage = sprintf('%.2f', $count ? 100 * $residential / $count : 0);
+ my $return = $count;
+ $return .= "<BR>$percentage% residential"
+ unless $cgi->param('_type') eq 'xml';
+ return $return;
+ } else {
+ return '<FONT SIZE="+1" COLOR="#ff0000">Bad call to column_value</FONT>';
+ }
+};
+
+my @fields = (
+ sub { &{$value}(shift, 'name');},
+ sub { &{$value}(shift, 2);},
+ sub { &{$value}(shift, 3);},
+ sub { &{$value}(shift, 4);},
+ sub { &{$value}(shift, 5);},
+ sub { &{$value}(shift, 6);},
+ sub { &{$value}(shift, 7);},
+ sub { &{$value}(shift, 8);},
+ sub { &{$value}(shift, 9);},
+ );
+shift @fields if $cgi->param('_type') eq 'xml';
+
+my $xml_element = sub {
+ my ($rowref, $column) = (shift, shift);
+ my $row = $rowref->[0];
+
+ $row++;
+ $xml_prefix. $column. $row;
+
+};
+
+my @xml_elements = (
+ sub { &{$xml_element}(shift, 'f') },
+ sub { &{$xml_element}(shift, 'g') },
+ sub { &{$xml_element}(shift, 'h') },
+ sub { &{$xml_element}(shift, 'i') },
+ sub { &{$xml_element}(shift, 'j') },
+ sub { &{$xml_element}(shift, 'k') },
+ sub { &{$xml_element}(shift, 'l') },
+ sub { &{$xml_element}(shift, 'm') },
+);
+
+</%init>
diff --git a/httemplate/search/477partIA_summary.html b/httemplate/search/477partIA_summary.html
new file mode 100755
index 0000000..269f2ca
--- /dev/null
+++ b/httemplate/search/477partIA_summary.html
@@ -0,0 +1,80 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'lines',
+ 'query' => 'SELECT 1',
+ 'count_query' => 'SELECT 1',
+ 'really_disable_download' => 1,
+ 'disable_download' => 1,
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'header' => [
+ 'Total Connections',
+ '% owned loop',
+ '% billed to end users',
+ '% residential',
+ '% residential &gt; 200kbps',
+ ],
+ 'xml_elements' => [
+ $xml_prefix. 'a1',
+ $xml_prefix. 'b1',
+ $xml_prefix. 'c1',
+ $xml_prefix. 'd1',
+ $xml_prefix. 'e1',
+ ],
+ 'fields' => [
+ sub { $total_count },
+ sub { '100.00' },
+ sub { '100.00' },
+ sub { $total_percentage },
+ sub { $total_percentage },
+ ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %opt = @_;
+my %search_hash = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option')
+ if $cgi->param('part1_column_option');
+
+my @row_option = grep { /^\d+$/ } $cgi->param('part1_row_option')
+ if $cgi->param('part1_row_option');
+
+my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
+
+my $total_count = 0;
+my $total_residential = 0;
+my $tech_code = $opt{tech_code};
+my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown';
+my $html_init = "<H2>Part IA $technology totals</H2>";
+my $xml_prefix = 'PartIA_'. chr(65 + $tech_code);
+
+foreach my $row ( @row_option ) {
+ foreach my $column ( @column_option ) {
+
+ my @report_option = ( $row || '-1', $column || '-1', $technology_option[$tech_code] );
+
+ my ( $count, $residential ) = FS::cust_pkg->fcc_477_count(
+ { %search_hash, 'report_option' => join(',', @report_option) }
+ );
+
+ $total_count += $count;
+ $total_residential += $residential;
+ }
+}
+
+my $total_percentage =
+ sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0);
+
+
+</%init>
diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html
new file mode 100755
index 0000000..64f773a
--- /dev/null
+++ b/httemplate/search/477partIIA.html
@@ -0,0 +1,113 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'lines',
+ 'query' => $query,
+ 'count_query' => 'SELECT 11',
+ 'really_disable_download' => 1,
+ 'disable_download' => 1,
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'header' => [ @headers ],
+ 'xml_elements' => [ @xml_elements ],
+ 'fields' => [ @fields ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my $html_init = '<H2>Part IIA</H2>';
+my %search_hash = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @row_option = grep { /^\d+$/ } $cgi->param('part2a_row_option')
+ if $cgi->param('part2a_row_option');
+
+# fudge in two rows of LD carrier
+unshift @row_option, $row_option[0];
+
+# fudge in the first pair of rows
+unshift @row_option, '';
+unshift @row_option, '';
+
+my $query = 'SELECT '. join(' UNION SELECT ', 1..11);
+
+my $total_count = 0;
+my $column_value = sub {
+ my $row = shift;
+
+ my @report_option = ( $row_option[$row - 1] || '' );
+
+ my $sql_query = FS::cust_pkg->search(
+ { %search_hash, 'report_option' => join(',', @report_option) }
+ );
+
+ my $count_sql = delete($sql_query->{'count_query'});
+ if ( $row == 2 || $row == 4 ) {
+ $count_sql =~ s/COUNT\(\*\) FROM/COALESCE( sum(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN fcc_ds0s END), 0 ) FROM/
+ or die "couldn't parse count_sql";
+ } else {
+ $count_sql =~ s/COUNT\(\*\) FROM/COALESCE( sum(fcc_ds0s), 0 ) FROM/
+ or die "couldn't parse count_sql";
+ }
+
+ my $count_sth = dbh->prepare($count_sql)
+ or die "Error preparing $count_sql: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $count_sql: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+ my $count = $count_arrayref->[0];
+
+ $total_count = $count if $row == 1;
+ $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0)
+ if $row != 1;
+
+ return "$count";
+};
+
+my @headers = (
+ '',
+ 'End user lines',
+ 'UNE-P replacement',
+ 'UNE (unswitched)',
+ 'UNE-P',
+);
+
+my @xml_elements = (
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" },
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" },
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" },
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}d" },
+);
+
+
+my @rows = (
+ 'lines',
+ '% residential',
+ '% LD carrier',
+ '% residential and LD carrier',
+ '% own loops',
+ '% obtained unswitched UNE loops',
+ '% UNE-P',
+ '% UNE-P replacement',
+ '% FTTP',
+ '% coax',
+ '% wireless',
+);
+
+my @fields = (
+ sub { my $row = shift; $rows[$row->[0] - 1]; },
+ sub { my $row = shift; &{$column_value}($row->[0]); },
+ sub { 0; },
+ sub { 0; },
+ sub { 0; },
+);
+
+shift @fields if $cgi->param('_type') eq 'xml';
+</%init>
diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html
new file mode 100755
index 0000000..278dfdc
--- /dev/null
+++ b/httemplate/search/477partIIB.html
@@ -0,0 +1,102 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'lines',
+ 'query' => $query,
+ 'count_query' => 'SELECT 11',
+ 'really_disable_download' => 1,
+ 'disable_download' => 1,
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'header' => [ @headers ],
+ 'xml_elements' => [ @xml_elements ],
+ 'fields' => [ @fields ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my $html_init = '<H2>Part IIB</H2>';
+my %search_hash = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @row_option = grep { /^\d+$/ } $cgi->param('part2b_row_option')
+ if $cgi->param('part2b_row_option');
+
+# fudge in 2nd row
+unshift @row_option, $row_option[0];
+
+my $query = 'SELECT '. join(' UNION SELECT ', 1..8);
+
+my $total_count = 0;
+my $column_value = sub {
+ my $row = shift;
+
+ my @report_option = ( $row_option[$row - 1] || '' );
+
+ my $sql_query = FS::cust_pkg->search(
+ { %search_hash, 'report_option' => join(',', @report_option) }
+ );
+
+ my $count_sql = delete($sql_query->{'count_query'});
+ if ( $row == 2 ) {
+ $count_sql =~ s/COUNT\(\*\) FROM/COALESCE( sum(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN fcc_ds0s END), 0 ) FROM/
+ or die "couldn't parse count_sql";
+ } else {
+ $count_sql =~ s/COUNT\(\*\) FROM/COALESCE( sum(fcc_ds0s), 0 ) FROM/
+ or die "couldn't parse count_sql";
+ }
+
+ my $count_sth = dbh->prepare($count_sql)
+ or die "Error preparing $count_sql: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $count_sql: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+ my $count = $count_arrayref->[0];
+
+ $total_count = $count if $row == 1;
+ $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0)
+ if $row != 1;
+
+ return "$count";
+};
+
+my @headers = (
+ '',
+ 'with broadband',
+ 'without broadband',
+ 'wholesale',
+);
+
+my @xml_elements = (
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" },
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" },
+ sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" },
+);
+
+my @rows = (
+ 'total number',
+ '% residential',
+ '% nomadic',
+ '% copper',
+ '% FTTP',
+ '% coax',
+ '% wireless',
+ '% other broadband',
+);
+
+my @fields = (
+ sub { my $row = shift; $rows[$row->[0] - 1]; },
+ sub { 0; },
+ sub { my $row = shift; &{$column_value}($row->[0]); },
+ sub { 0; },
+);
+
+shift @fields if $cgi->param('_type') eq 'xml';
+</%init>
diff --git a/httemplate/search/477partIV.html b/httemplate/search/477partIV.html
new file mode 100755
index 0000000..269a925
--- /dev/null
+++ b/httemplate/search/477partIV.html
@@ -0,0 +1,17 @@
+%if ( $cgi->param('_type') eq 'html' || $cgi->param('_type') eq 'html-print' ) {
+<H2>Part IV</H2>
+%} elsif ( $cgi->param('_type') eq 'xml' ) {
+<notes>
+%}
+<% $cgi->param('notes') |h %>
+%if ( $cgi->param('_type') eq 'xml' ) {
+</notes>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+</%init>
diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html
new file mode 100755
index 0000000..c6ceac4
--- /dev/null
+++ b/httemplate/search/477partV.html
@@ -0,0 +1,53 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'zip code',
+ 'query' => [ @sql_query ],
+ 'count_query' => $count_query,
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'header' => [ 'zip code' ],
+ 'xml_elements' => [ 'zip codes' ],
+ 'no_field_elements' => 1,
+ 'fields' => [ 'zip' ],
+ 'url' => $opt{url} || $cgi->self_url,
+
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %opt = @_;
+my $html_init = '<H2>Part V</H2>';
+my %search_hash = ();
+my @sql_query = ();
+my @count_query = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my $sql_query = FS::cust_pkg->search( { %search_hash, 'fcc_line' > 1 });
+$sql_query->{select} = 'DISTINCT zip';
+$sql_query->{extra_sql} =~ s/ORDER BY [.\w]+//;
+push @sql_query, $sql_query;
+push @count_query, delete($sql_query->{'count_query'});
+$count_query[0] =~ s/count\(\*\)/count(DISTINCT zip)/;
+$count_query[0] =~ s/ORDER BY [.\w]+//;
+
+$search_hash{classnum} = $cgi->param('part2a_classnum')
+ if $cgi->param('part2a_classnum');
+
+$sql_query = FS::cust_pkg->search( { %search_hash } );
+$sql_query->{select} = 'DISTINCT zip';
+$sql_query->{extra_sql} =~ s/ORDER BY [.\w]+//;
+push @sql_query, $sql_query;
+push @count_query, delete($sql_query->{'count_query'});
+$count_query[1] =~ s/count\(\*\)/count(DISTINCT zip)/;
+$count_query[1] =~ s/ORDER BY [.\w]+//;
+my $count_query = join(' UNION ', @count_query);
+
+</%init>
diff --git a/httemplate/search/477partVI.html b/httemplate/search/477partVI.html
new file mode 100755
index 0000000..dbd1703
--- /dev/null
+++ b/httemplate/search/477partVI.html
@@ -0,0 +1,130 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'name' => 'regions',
+ 'query' => [ @sql_query ],
+ 'count_query' => $count_query,
+ 'order_by' => 'ORDER BY censustract',
+ 'avoid_quote' => 1,
+ 'no_csv_header' => 1,
+ 'nohtmlheader' => 1,
+ 'header' => [
+ 'County code',
+ 'Census tract code',
+ 'Upload rate',
+ 'Download rate',
+ 'Technology code',
+ 'Technology code other',
+ 'Quantity',
+ 'Percentage residential',
+ ],
+ 'xml_elements' => [
+ 'county_fips',
+ 'census_tract',
+ 'upload_rate_code',
+ 'download_rate_code',
+ 'technology_code',
+ 'technology_code_other',
+ 'value',
+ 'percentage',
+ ],
+ 'fields' => [
+ sub { my $row = shift; substr($row->censustract, 2, 3) },
+ sub { my $row = shift; substr($row->censustract, 5) },
+ 'upload',
+ 'download',
+ 'technology_code',
+ sub { '' }, # doesn't really work
+ 'quantity',
+ sub { my $row = shift; sprintf "%.2f", $row->residential },
+ ],
+ 'links' => [
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ ],
+ 'url' => $opt{url} || $cgi->self_url,
+ 'xml_row_element' => 'Datarow',
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %opt = @_;
+
+my $html_init = '<H2>Part VI</H2>';
+
+my %search_hash = ();
+my @sql_query = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option')
+ if $cgi->param('part1_column_option');
+
+my @row_option = grep { /^\d+$/ } $cgi->param('part1_row_option')
+ if $cgi->param('part1_row_option');
+
+my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi);
+
+my $rowcount = 1;
+foreach my $row ( @row_option ) {
+ my $columncount = 2;
+ foreach my $column ( @column_option ) {
+ my $tech_code = 0;
+ foreach my $technology ( @technology_option ) {
+ $tech_code++;
+ next unless $technology;
+ my @report_option = ();
+ push @report_option, $row if $row;
+ push @report_option, $column if $column;
+ push @report_option, $technology;
+ my $report_option = join(',', @report_option) if @report_option;
+
+ my $sql_query = FS::cust_pkg->search(
+ { %search_hash,
+ ($report_option ? ( 'report_option' => $report_option ) : () ),
+ }
+ );
+ my $extracolumns = "$rowcount AS upload, $columncount AS download, $tech_code as technology_code";
+ my $percent = "CASE WHEN count(*) > 0 THEN 100-100*cast(count(cust_main.company) as numeric)/cast(count(*) as numeric) ELSE cast(0 as numeric) END AS residential";
+ $sql_query->{select} = "count(*) AS quantity, $extracolumns, censustract, $percent";
+ $sql_query->{extra_sql} =~ /^(.*)(ORDER BY pkgnum)(.*)$/s
+ or die "couldn't parse extra_sql";
+ $sql_query->{extra_sql} = "$1 GROUP BY censustract $3";
+ push @sql_query, $sql_query;
+ }
+ $columncount++;
+ }
+ $rowcount++;
+}
+
+my $count_query = 'SELECT count(*) FROM ( ('.
+ join( ') UNION ALL (',
+ map { my $extra = $_->{extra_sql}; my $addl = $_->{addl_from};
+ "SELECT censustract from cust_pkg $addl $extra";
+ }
+ @sql_query
+ ). ') ) AS foo';
+
+my $link = 'cust_pkg.cgi?'.
+ join(';', map{ "$_=". $search_hash{$_} } keys %search_hash). ';';
+my $link_suffix = sub { my $row = shift;
+ my $result = 'censustract='. $row->censustract. ';';
+ $result .= 'report_option='. @row_option[$row->upload - 1]
+ if @row_option[$row->upload - 1];
+ $result .= 'report_option='. @column_option[$row->download - 1]
+ if @column_option[$row->download - 1];
+ $result;
+ };
+</%init>
diff --git a/httemplate/search/agent_inventory.html b/httemplate/search/agent_inventory.html
new file mode 100644
index 0000000..ac65371
--- /dev/null
+++ b/httemplate/search/agent_inventory.html
@@ -0,0 +1,40 @@
+<% include('elements/search.html',
+ 'title' => 'Inventory summary per agent',
+ 'name_singular' => 'agent',
+ 'query' => { 'table' => 'agent',
+ 'hashref' => { 'disabled' => '' },
+ 'extra_sql' => "AND $agentnums_sql",
+ },
+ 'count_query' => "SELECT COUNT(*) FROM agent".
+ " WHERE disabled = '' OR disabled IS NULL".
+ " AND $agentnums_sql",
+ 'header' => \@header,
+ 'fields' => \@fields,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+#XXX List inventory
+
+my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my @header = ('Agent');
+my @fields = ('agent');
+
+ #{ 'disabled' => '' }
+foreach my $inventory_class ( qsearch('inventory_class', {}) ) {
+ push @header, $inventory_class->classname;
+ push @fields, sub {
+ my $agent = shift;
+ my $sub = FS::inventory_class->countcell_factory(
+ 'p' => $p, 'agentnum' => $agent->agentnum,
+ );
+ &{$sub}($inventory_class);
+ };
+}
+
+#XXX show global inventory too
+
+</%init>
diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html
new file mode 100644
index 0000000..6b38d3b
--- /dev/null
+++ b/httemplate/search/cdr.html
@@ -0,0 +1,289 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => 'call detail records',
+
+ 'query' => { 'table' => 'cdr',
+ 'hashref' => $hashref,
+ 'extra_sql' => $qsearch,
+ 'order_by' => 'ORDER BY calldate',
+ },
+ 'count_query' => $count_query,
+ 'header' => [
+ '', # checkbox column
+ @header,
+ ],
+ 'fields' => [
+ sub {
+ return '' unless $edit_data;
+ $areboxes = 1;
+ my $cdr = shift;
+ my $acctid = $cdr->acctid;
+ qq!<INPUT NAME="acctid$acctid" TYPE="checkbox" VALUE="1">!;
+ },
+ @fields, #XXX fill in some pretty-print
+ #processing, etc.
+ ],
+ 'links' => \@links,
+
+ 'html_form' => qq!<FORM NAME="cdrForm" ACTION="$p/misc/cdr.cgi" METHOD="POST">!,
+ #false laziness w/queue.html
+ 'html_foot' => sub {
+ if ( $areboxes ) {
+ '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'.
+ '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'.
+ qq!<BR><INPUT TYPE="submit" NAME="action" VALUE="reprocess selected" onClick="return confirm('Are you sure you want to reprocess the selected CDRs?')">!.
+ qq!<INPUT TYPE="submit" NAME="action" VALUE="delete selected" onClick="return confirm('Are you sure you want to delete the selected CDRs?')"><BR>!.
+ '<SCRIPT TYPE="text/javascript">'.
+ ' function setAll(setTo) { '.
+ ' theForm = document.cdrForm;'.
+ ' for (i=0,n=theForm.elements.length;i<n;i++)'.
+ ' if (theForm.elements[i].name.indexOf("acctid") != -1)'.
+ ' theForm.elements[i].checked = setTo;'.
+ ' }'.
+ '</SCRIPT>';
+ } else {
+ '';
+ }
+ },
+
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+my $edit_data = $FS::CurrentUser::CurrentUser->access_right('Edit rating data');
+
+my $areboxes = 0;
+
+my $title = 'Call Detail Records';
+my $hashref = {};
+
+#process params for CDR search, populate $hashref...
+# and fixup $count_query
+
+my @search = ();
+
+###
+# dates
+###
+
+my $str2time_sql = str2time_sql;
+my $closing = str2time_sql_closing;
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "$str2time_sql calldate $closing >= $beginning ",
+ "$str2time_sql calldate $closing <= $ending";
+
+###
+# duration / billsec
+###
+
+push @search, FS::UI::Web::parse_lt_gt($cgi, 'duration');
+push @search, FS::UI::Web::parse_lt_gt($cgi, 'billsec');
+
+#above here things just push @search
+#below here things also have to define $hashref->{} or push @qsearch
+my @qsearch = @search;
+
+###
+# freesidestatus
+###
+
+if ( $cgi->param('freesidestatus') eq 'NULL' ) {
+
+ $title = "Unprocessed $title";
+ $hashref->{'freesidestatus'} = ''; # Record.pm will take care of it
+ push @search, "( freesidestatus IS NULL OR freesidestatus = '' )";
+
+} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) {
+
+ $title = "Processed $title";
+ $hashref->{'freesidestatus'} = $1;
+ push @search, "freesidestatus = '$1'";
+
+}
+
+###
+# termpartNstatus
+###
+
+foreach my $param ( grep /^termpart\d+status$/, $cgi->param ) {
+
+ my $status = $cgi->param($param);
+
+ $param =~ /^termpart(\d+)status$/ or die 'guru meditation 54something';
+ my $termpart = $1;
+
+ my $search = '';
+ if ( $status eq 'NULL' ) {
+
+ #false lazienss w/cdr_termination.pm (i should be a part_termination method)
+ my $where_term =
+ "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart ) ";
+ #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
+ $search =
+ "NOT EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
+
+ } elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) {
+
+ #false lazienss w/cdr_termination.pm (i should be a part_termination method)
+ my $where_term =
+ "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart AND status = '$1' ) ";
+ #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
+ $search =
+ "EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
+
+ }
+
+ if ( $search ) {
+ push @search, $search;
+ push @qsearch, $search;
+ }
+
+}
+
+###
+# src/dest/charged_party
+###
+
+if ( $cgi->param('src') =~ /^\s*([\d\-\+\ ]+)\s*$/ ) {
+ ( my $src = $1 ) =~ s/\D//g;
+ $hashref->{'src'} = $src;
+ push @search, "src = '$src'";
+}
+
+if ( $cgi->param('dst') =~ /^\s*([\d\-\+ ]+)\s*$/ ) {
+ ( my $dst = $1 ) =~ s/\D//g;
+ $hashref->{'dst'} = $dst;
+ push @search, "dst = '$dst'";
+}
+
+if ( $cgi->param('dcontext') =~ /^\s*(.+)\s*$/ ) {
+ my $dcontext = $1;
+ $hashref->{'dcontext'} = $dcontext;
+ push @search, "dcontext = '$dcontext'";
+}
+
+if ( $cgi->param('charged_party') =~ /^\s*([\d\-\+\ ]+)\s*$/ ) {
+ ( my $charged_party = $1 ) =~ s/\D//g;
+ #$hashref->{'charged_party'} = $charged_party;
+ #push @search, "charged_party = '$charged_party'";
+ #XXX countrycode
+
+ my $search = " ( charged_party = '$charged_party'
+ OR charged_party = '1$charged_party' ) ";
+
+ push @search, $search;
+ push @qsearch, $search;
+}
+
+###
+# cdrbatchnum (or legacy cdrbatch)
+###
+
+if ( $cgi->param('cdrbatch') ) {
+
+ my $cdr_batch =
+ qsearchs('cdr_batch', { 'cdrbatch' => scalar($cgi->param('cdrbatch')) } );
+ if ( $cdr_batch ) {
+ $hashref->{cdrbatchnum} = $cdr_batch->cdrbatchnum;
+ push @search, 'cdrbatchnum = '. $cdr_batch->cdrbatchnum;
+ } else {
+ die "unknown cdrbatch ". $cgi->param('cdrbatch');
+ }
+
+} elsif ( $cgi->param('cdrbatchnum') ne '__ALL__' ) {
+
+ if ( $cgi->param('cdrbatchnum') eq '' ) {
+ my $search = "( cdrbatchnum IS NULL )";
+ push @qsearch, $search;
+ push @search, $search;
+ } elsif ( $cgi->param('cdrbatchnum') =~ /^(\d+)$/ ) {
+ $hashref->{cdrbatchnum} = $1;
+ push @search, "cdrbatchnum = $1";
+ }
+
+}
+
+###
+# acctid
+###
+
+if ( $cgi->param('acctid') =~ /\d/ ) {
+ my $acctid = $cgi->param('acctid');
+ $acctid =~ s/\r\n/\n/g; #browsers?
+ my @acctid = map { /^\s*(\d+)\s*$/ or die "guru meditation #4"; $1; }
+ grep { /^\s*(\d+)\s*$/ }
+ split(/\n/, $acctid);
+ if ( @acctid ) {
+ my $search = 'acctid IN ( '. join(',', @acctid). ' )';
+ push @qsearch, $search;
+ push @search, $search;
+ }
+}
+
+###
+# finish it up
+###
+
+my $search = join(' AND ', @search);
+$search = "WHERE $search" if $search;
+
+my $count_query = "SELECT COUNT(*) FROM cdr $search";
+
+my $qsearch = join(' AND ', @qsearch);
+$qsearch = ( scalar(keys %$hashref) ? ' AND ' : ' WHERE ' ) . $qsearch
+ if $qsearch;
+
+###
+# display fields
+###
+
+my %header = %{ FS::cdr->table_info->{'fields'} };
+
+my @first = qw( acctid calldate clid charged_party src dst dcontext );
+my %first = map { $_=>1 } @first;
+
+my @fields = ( @first, grep !$first{$_}, fields('cdr') );
+
+if ( $cgi->param('show') ) {
+ @fields = grep $cgi->param("show_$_"), @fields;
+}
+
+my @header = map {
+ if ( exists($header{$_}) ) {
+ $header{$_};
+ } else {
+ my $header = $_;
+ $header =~ s/\_/ /g; #//wtf
+ ucfirst($header);
+ }
+ } @fields;
+
+my $date_sub_factory = sub {
+ my $column = shift;
+ sub {
+ #my $cdr = shift;
+ my $date = shift->$column();
+ $date ? time2str( '%Y-%m-%d %T', $date ) : ''; #config time2str format?
+ };
+};
+
+my %fields = (
+ #any other formatters?
+ map { $_ => &{ $date_sub_factory }($_) } qw( startdate answerdate enddate )
+);
+
+my %links = (
+ 'svcnum' =>
+ sub { $_[0]->svcnum ? [ $p.'view/svc_phone.cgi?', 'svcnum' ] : ''; },
+);
+
+@fields = map { exists($fields{$_}) ? $fields{$_} : $_ } @fields;
+
+ #checkbox column
+my @links = ( '', map { exists($links{$_}) ? $links{$_} : '' } @fields );
+
+</%init>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
new file mode 100755
index 0000000..1e9ee8d
--- /dev/null
+++ b/httemplate/search/cust_bill.html
@@ -0,0 +1,250 @@
+<% include( 'elements/search.html',
+ 'title' => 'Invoice Search Results',
+ 'html_init' => $html_init,
+ 'menubar' => $menubar,
+ 'name' => 'invoices',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => $count_addl,
+ 'redirect' => $link,
+ 'header' => [ 'Invoice #',
+ 'Balance',
+ 'Net Amount',
+ 'Gross Amount',
+ 'Date',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ 'display_invnum',
+ sub { sprintf($money_char.'%.2f', shift->get('owed') ) },
+ sub { sprintf($money_char.'%.2f', shift->get('net') ) },
+ sub { sprintf($money_char.'%.2f', shift->charged ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'rrrrl'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ $link,
+ $link,
+ $link,
+ $link,
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+
+
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List invoices');
+
+my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )';
+#here is the agent virtualization
+my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my( $count_query, $sql_query );
+my $count_addl = '';
+#my $distinct = '';
+my %search;
+
+if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
+
+ my $invnum_or_invid = "( invnum = $2 OR agent_invid = $2 )";
+ my $where = "WHERE $invnum_or_invid AND $agentnums_sql";
+
+ $count_query = "SELECT COUNT(*) FROM cust_bill $join_cust_main $where";
+
+ $sql_query = {
+ #'select' => '*',
+ 'table' => 'cust_bill',
+ 'addl_from' => $join_cust_main,
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ };
+
+} else {
+
+ #some false laziness w/cust_bill::re_X
+ my $orderby = 'ORDER BY cust_bill._date';
+
+ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $search{'agentnum'} = $1;
+ }
+
+ # begin/end/beginning/ending
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, '');
+ $search{'_date'} = [ $beginning, $ending ]
+ unless $beginning == 0 && $ending == 4294967295;
+
+ if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) {
+ $search{'invnum_min'} = $1;
+ }
+ if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) {
+ $search{'invnum_max'} = $1;
+ }
+
+ #amounts
+ $search{$_} = [ FS::UI::Web::parse_lt_gt($cgi, $_) ]
+ foreach qw( charged owed );
+
+ $search{'open'} = 1 if $cgi->param('open');
+ $search{'net'} = 1 if $cgi->param('net' );
+
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) {
+ $search{'open'} = 1 if $1;
+ ($search{'days'}, my $field) = ($2, $3);
+ $field = "_date" if $field eq 'date';
+ $orderby = "ORDER BY cust_bill.$field";
+ }
+
+ if ( $cgi->param('newest_percust') ) {
+ $search{'newest_percust'} = 1;
+ $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'";
+ }
+
+ my $extra_sql = ' WHERE '. FS::cust_bill->search_sql_where( \%search );
+
+ unless ( $count_query ) {
+ $count_query = 'SELECT COUNT(*), '. join(', ',
+ map "SUM($_)",
+ ( 'charged',
+ FS::cust_bill->net_sql,
+ FS::cust_bill->owed_sql,
+ )
+ );
+ $count_addl = [ '$%.2f invoiced (gross)',
+ '$%.2f invoiced (net)',
+ '$%.2f outstanding balance',
+ ];
+ }
+ $count_query .= " FROM cust_bill $join_cust_main $extra_sql";
+
+ $sql_query = {
+ 'table' => 'cust_bill',
+ 'addl_from' => $join_cust_main,
+ 'hashref' => {},
+ #'select' => "$distinct ". join(', ',
+ 'select' => join(', ',
+ 'cust_bill.*',
+ #( map "cust_main.$_", qw(custnum last first company) ),
+ 'cust_main.custnum as cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ FS::cust_bill->owed_sql. ' AS owed',
+ FS::cust_bill->net_sql. ' AS net',
+ ),
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ };
+
+}
+
+my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ];
+my $clink = sub {
+ my $cust_bill = shift;
+ $cust_bill->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+};
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $html_init = join("\n", map {
+ ( my $action = $_ ) =~ s/_$//;
+ include('/elements/progress-init.html',
+ $_.'form',
+ [ keys %search ],
+ "../misc/${_}invoices.cgi",
+ { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but...
+ $_, #key
+ ),
+ qq!<FORM NAME="${_}form">!,
+ ( map { my $f = $_;
+ my @values = ref($search{$f}) ? @{ $search{$f} } : $search{$f};
+ map qq!<INPUT TYPE="hidden" NAME="$f" VALUE="$_">!, @values;
+ }
+ keys %search
+ ),
+ qq!</FORM>!
+} qw( print_ email_ fax_ ftp_ spool_ ) ).
+
+'<SCRIPT TYPE="text/javascript">
+
+function confirm_print_process() {
+ if ( ! confirm("Are you sure you want to reprint these invoices?") ) {
+ return;
+ }
+ print_process();
+}
+function confirm_email_process() {
+ if ( ! confirm("Are you sure you want to re-email these invoices?") ) {
+ return;
+ }
+ email_process();
+}
+function confirm_fax_process() {
+ if ( ! confirm("Are you sure you want to re-fax these invoices?") ) {
+ return;
+ }
+ fax_process();
+}
+function confirm_ftp_process() {
+ if ( ! confirm("Are you sure you want to re-FTP these invoices?") ) {
+ return;
+ }
+ ftp_process();
+}
+function confirm_spool_process() {
+ if ( ! confirm("Are you sure you want to re-spool these invoices?") ) {
+ return;
+ }
+ spool_process();
+}
+
+</SCRIPT>';
+
+my $menubar = [];
+
+if ( $FS::CurrentUser::CurrentUser->access_right('Resend invoices') ) {
+
+ push @$menubar, 'Print these invoices' =>
+ "javascript:confirm_print_process()",
+ 'Email these invoices' =>
+ "javascript:confirm_email_process()";
+
+ push @$menubar, 'Fax these invoices' =>
+ "javascript:confirm_fax_process()"
+ if $conf->exists('hylafax');
+
+ push @$menubar, 'FTP these invoices' =>
+ "javascript:confirm_ftp_process()"
+ if $conf->exists('cust_bill-ftpformat');
+
+ push @$menubar, 'Spool these invoices' =>
+ "javascript:confirm_spool_process()"
+ if $conf->exists('cust_bill-spoolformat');
+
+}
+
+</%init>
diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi
new file mode 100644
index 0000000..16c9acd
--- /dev/null
+++ b/httemplate/search/cust_bill_event.cgi
@@ -0,0 +1,166 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'html_init' => $html_init,
+ 'menubar' => $menubar,
+ 'name' => 'billing events',
+ 'query' => $sql_query,
+ 'count_query' => $count_sql,
+ 'header' => [ 'Event',
+ 'Date',
+ 'Status',
+ #'Inv #', 'Inv Date', 'Cust #',
+ 'Invoice',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ 'event',
+ sub { time2str("%b %d %Y %T", $_[0]->_date) },
+ sub {
+ #my $cust_bill_event = shift;
+ my $status = $_[0]->status;
+ $status .= ': '.$_[0]->statustext
+ if $_[0]->statustext;
+ $status;
+ },
+ sub {
+ #my $cust_bill_event = shift;
+ 'Invoice #'. $_[0]->invnum.
+ ' ('.
+ time2str("%D", $_[0]->cust_bill_date).
+ ')';
+ },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'lrlr'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ '',
+ sub {
+ my $part_bill_event = shift;
+ my $template = $part_bill_event->templatename;
+ $template .= '-' if $template;
+ [ "${p}view/cust_bill.cgi?$template", 'invnum'];
+ },
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Billing event reports')
+ or $curuser->access_right('View customer billing events')
+ && $cgi->param('invnum') =~ /^(\d+)$/;
+
+my $title = $cgi->param('failed')
+ ? 'Failed invoice events'
+ : 'Invoice events';
+
+my %search = ();
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $search{agentnum} = $1;
+}
+
+($search{beginning}, $search{ending})
+ = FS::UI::Web::parse_beginning_ending($cgi);
+
+if ( $cgi->param('failed') ) {
+ $search{failed} = '1';
+}
+
+if ( $cgi->param('part_bill_event.payby') =~ /^(\w+)$/ ) {
+ $search{payby} = $1;
+}
+
+if ( $cgi->param('invnum') =~ /^(\d+)$/ ) {
+ $search{invnum} = $1;
+}
+
+my $where = 'WHERE '. FS::cust_bill_event->search_sql_where( \%search );
+
+my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '.
+ 'LEFT JOIN cust_bill USING ( invnum ) '.
+ 'LEFT JOIN cust_main USING ( custnum ) ';
+
+my $sql_query = {
+ 'table' => 'cust_bill_event',
+ 'select' => join(', ',
+ 'cust_bill_event.*',
+ 'part_bill_event.event',
+ 'cust_bill.custnum',
+ 'cust_bill._date AS cust_bill_date',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => "$where ORDER BY _date ASC",
+ 'addl_from' => $join,
+};
+
+my $count_sql = "SELECT COUNT(*) FROM cust_bill_event $join $where";
+
+my $conf = new FS::Conf;
+
+my $html_init = '
+ <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken o
+n open invoices. See Reports-&gt;Billing events-&gt;Billing events for current event reports.</FONT><BR><BR>';
+
+$html_init .= join("\n", map {
+ ( my $action = $_ ) =~ s/_$//;
+ include('/elements/progress-init.html',
+ $_.'form',
+ [ keys(%search) ],
+ "../misc/${_}invoice_events.cgi",
+ { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but...
+ $_, #key
+ ),
+ qq!<FORM NAME="${_}form">!,
+ qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though
+ (map {qq!<INPUT TYPE="hidden" NAME="$_" VALUE="$search{$_}">!} keys(%search)),
+ qq!</FORM>!
+} qw( print_ email_ fax_ ) );
+
+my $menubar = [];
+
+if ( $curuser->access_right('Resend invoices') ) {
+
+ push @$menubar, 'Re-print these events' =>
+ "javascript:print_process()",
+ 'Re-email these events' =>
+ "javascript:email_process()",
+ ;
+
+ push @$menubar, 'Re-fax these events' =>
+ "javascript:fax_process()"
+ if $conf->exists('hylafax');
+
+}
+
+my $link_cust = sub {
+ my $cust_bill_event = shift;
+ $cust_bill_event->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_bill_event.html b/httemplate/search/cust_bill_event.html
new file mode 100755
index 0000000..0f84a55
--- /dev/null
+++ b/httemplate/search/cust_bill_event.html
@@ -0,0 +1,67 @@
+<% include(
+ '/elements/header.html',
+ ( $cgi->param('failed') ? 'Failed invoice events' : 'Invoice events' ),
+ )
+%>
+
+ <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken
+ on open invoices. See Reports-&gt;Billing events-&gt;Billing events for current event reports.</FONT><BR><BR>
+
+ <FORM ACTION="cust_bill_event.cgi" METHOD="GET">
+ <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') ? 1 : 0 %>">
+ <TABLE>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <!--<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>
+ -->
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+ <!--
+ <TR>
+ <TD ALIGN="right">Events: </TD>
+ <TD>
+ <SELECT NAME="eventpart">
+ <OPTION SELECTED VALUE=""><% $cgi->param('failed') ? '(all failed events)' : '(all events)' %>
+% #foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) {
+% #}
+
+ </SELECT>
+ </TD>
+ </TR>
+ -->
+ <TR>
+ <TD ALIGN="right">Events for payment type: </TD>
+ <TD>
+ <SELECT NAME="part_bill_event.payby">
+ <OPTION SELECTED VALUE="">(all)
+ <OPTION VALUE="CARD">Credit card (automatic)
+ <OPTION VALUE="BILL">Billing
+ <OPTION VALUE="CHEK">Electronic check (automatic)
+ <OPTION VALUE="DCRD">Credit card (on-demand)
+ <OPTION VALUE="DCHK">Electronic check (on-demand)
+ <OPTION VALUE="LECB">Phone bill billing
+ <OPTION VALUE="COMP">Complimentary
+ </SELECT>
+ </TD>
+ </TR>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports');
+
+</%init>
diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html
new file mode 100644
index 0000000..4272d86
--- /dev/null
+++ b/httemplate/search/cust_bill_pay.html
@@ -0,0 +1,141 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => 'net payments',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total paid (net)', ],
+ 'header' => [ 'Net applied',
+ 'Invoice',
+ 'Invoice amount',
+ 'Invoice date',
+ 'Payment',
+ 'Payment amount',
+ 'Payment date',
+ 'By',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ sub { $money_char.sprintf('%.2f', shift->amount ) },
+ 'invnum',
+ sub { $money_char.sprintf('%.2f', shift->cust_bill_charged)},
+ sub { time2str('%b %d %Y', shift->cust_bill_date ) },
+ sub { shift->cust_pay->payby_payinfo_pretty },
+ sub { $money_char.sprintf('%.2f', shift->cust_pay_paid)},
+ sub { time2str('%b %d %Y', shift->cust_pay_date ) },
+ sub { shift->cust_pay_otaker },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'rrrrlrrl'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ $cust_bill_link,
+ $cust_bill_link,
+ $cust_bill_link,
+ $cust_pay_link,
+ $cust_pay_link,
+ $cust_pay_link,
+ '',
+ ( map { $_ ne 'Cust. Status' ? $cust_link : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $title = 'Net Payment Search Results';
+
+my @search = ();
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @search, "agentnum = $1";
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "unknown agentnum $1" unless $agent;
+ $title = $agent->agent. " $title";
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "cust_bill._date >= $beginning ",
+ "cust_bill._date <= $ending";
+
+#here is the agent virtualization
+push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = 'WHERE '. join(' AND ', @search);
+#
+my $count_query = 'SELECT COUNT(*), SUM(amount)
+ FROM cust_bill_pay
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) '.
+ $where;
+
+my $sql_query = {
+ 'table' => 'cust_bill_pay',
+ 'select' => join(', ',
+ 'cust_bill_pay.*',
+ 'cust_bill._date AS cust_bill_date',
+ 'cust_bill.charged AS cust_bill_charged',
+ 'cust_pay.paid AS cust_pay_paid',
+ 'cust_pay._date AS cust_pay_date',
+ 'cust_pay.otaker AS cust_pay_otaker',
+ 'cust_pay.custnum AS custnum',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_pay USING ( paynum )
+ LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )',
+};
+
+my $cust_bill_link = sub {
+ my $cust_bill_pay = shift;
+ $cust_bill_pay->invnum
+ ? [ "${p}view/cust_bill.cgi?", 'invnum' ]
+ : '';
+};
+
+my $cust_pay_link = sub {
+ my $cust_bill_pay = shift;
+ $cust_bill_pay->paynum
+ ? [ "${p}view/cust_pay.html?paynum=", 'paynum' ]
+ : '';
+};
+
+my $cust_link = sub {
+ my $cust_credit_bill = shift;
+ $cust_credit_bill->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ]
+ : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
new file mode 100644
index 0000000..77901de
--- /dev/null
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -0,0 +1,595 @@
+<% include( 'elements/search.html',
+ 'title' => 'Line items',
+ 'name' => 'line items',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total',
+ $unearned ? ( $money_char. '%.2f unearned revenue' ) : (),
+ ],
+ 'header' => [
+ #'#',
+ 'Description',
+ ( $unearned
+ ? ( 'Unearned', 'Owed', 'Payment date' )
+ : ( 'Setup charge' )
+ ),
+ ( $use_usage eq 'usage'
+ ? 'Usage charge'
+ : 'Recurring charge'
+ ),
+ ( $unearned
+ ? ( 'Charge start', 'Charge end' )
+ : ()
+ ),
+ 'Invoice',
+ 'Date',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ #'billpkgnum',
+ sub { $_[0]->pkgnum > 0
+ ? $_[0]->get('pkg') # possibly use override.pkg
+ : $_[0]->get('itemdesc') # but i think this correct
+ },
+ #strikethrough or "N/A ($amount)" or something these when
+ # they're not applicable to pkg_tax search
+ sub { my $cust_bill_pkg = shift;
+ if ( $unearned ) {
+ my $period =
+ $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
+ my $elapsed = $unearned - $cust_bill_pkg->sdate;
+ $elapsed = 0 if $elapsed < 0;
+
+ my $remaining = 1 - $elapsed/$period;
+
+ sprintf($money_char. '%.2f',
+ $remaining * $cust_bill_pkg->recur );
+
+ } else {
+ sprintf($money_char.'%.2f', $cust_bill_pkg->setup );
+ }
+ },
+ ( $unearned
+ ? ( $owed_sub, $payment_date_sub, )
+ : ()
+ ),
+ sub { my $row = shift;
+ my $value = 0;
+ if ( $use_usage eq 'recurring' ) {
+ $value = $row->recur - $row->usage;
+ } elsif ( $use_usage eq 'usage' ) {
+ $value = $row->usage;
+ } else {
+ $value = $row->recur;
+ }
+ sprintf($money_char.'%.2f', $value );
+ },
+ ( $unearned
+ ? ( sub { time2str('%b %d %Y', shift->sdate ) },
+ sub { time2str('%b %d %Y', shift->edate ) },
+ )
+ : ()
+ ),
+ 'invnum',
+ sub { time2str('%b %d %Y', shift->_date ) },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ #'',
+ '',
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ $ilink,
+ $ilink,
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
+ 'align' => 'lr'.
+ ( $unearned ? 'rc' : '' ).
+ 'r'.
+ ( $unearned ? 'cc' : '' ).
+ 'rc'.
+ FS::UI::Web::cust_aligns(),
+ 'color' => [
+ #'',
+ '',
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ #'',
+ '',
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ '',
+ ( $unearned ? ( '', '' ) : () ),
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+#LOTS of false laziness below w/cust_credit_bill_pkg.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $unearned = '';
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @where, "_date >= $beginning",
+ "_date <= $ending";
+
+push @where , " payby != 'COMP' "
+ unless $cgi->param('include_comp_cust');
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+#classnum
+# not specified: all classes
+# 0: empty class
+# N: classnum
+my $use_override = $cgi->param('use_override');
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ my $comparison = '';
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $use_override ) {
+ push @where, "(
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ push @where, "part_pkg.classnum $comparison";
+ }
+}
+
+if ( $cgi->param('taxclass')
+ && ! $cgi->param('istax') #no part_pkg.taxclass in this case
+ #(should we save a taxclass or a link to taxnum
+ # in cust_bill_pkg or something like
+ # cust_bill_pkg_tax_location?)
+ )
+{
+
+ #override taxclass when use_override is specified? probably
+ #if ( $use_override ) {
+ #
+ # push @where,
+ # ' ( '. join(' OR ',
+ # map {
+ # ' ( part_pkg.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NULL '.
+ # ' OR '.
+ # ' override.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NOT NULL '.
+ # ' ) '
+ # }
+ # $cgi->param('taxclass')
+ # ).
+ # ' ) ';
+ #
+ #} else {
+
+ push @where,
+ ' ( '. join(' OR ',
+ map ' part_pkg.taxclass = '.dbh->quote($_),
+ $cgi->param('taxclass')
+ ).
+ ' ) ';
+
+ #}
+
+}
+
+my @loc_param = qw( city county state country );
+
+if ( $cgi->param('out') ) {
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 );
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e;
+ }
+
+ $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g
+ if $cgi->param('istax');
+
+ push @where, "
+ 0 = (
+ SELECT COUNT(*) FROM cust_main_county
+ WHERE cust_main_county.tax > 0
+ AND $loc_sql
+ )
+ ";
+
+ #not linked to by anything, but useful for debugging "out of taxable region"
+ if ( grep $cgi->param($_), @loc_param ) {
+
+ my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ push @where, $loc_sql;
+
+ }
+
+} elsif ( $cgi->param('country') ) {
+
+ my @counties = $cgi->param('county');
+
+ if ( scalar(@counties) > 1 ) {
+
+ #hacky, could be more efficient. care if it is ever used for more than the
+ # tax-report_groups filtering kludge
+
+ my $locs_sql =
+ ' ( '. join(' OR ', map {
+
+ my %ph = ( 'county' => dbh->quote($_),
+ map { $_ => dbh->quote( $cgi->param($_) ) }
+ qw( city state country )
+ );
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ $loc_sql;
+
+ } @counties
+
+ ). ' ) ';
+
+ push @where, $locs_sql;
+
+ } else {
+
+ my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ push @where, $loc_sql;
+
+ }
+
+ if ( $cgi->param('istax') ) {
+ if ( $cgi->param('taxname') ) {
+ push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') );
+ #} elsif ( $cgi->param('taxnameNULL') {
+ } else {
+ push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+ }
+ } elsif ( $cgi->param('nottax') ) {
+ #what can we usefully do with "taxname" ???? look up a class???
+ } else {
+ #warn "neither nottax nor istax parameters specified";
+ }
+
+ if ( $cgi->param('taxclassNULL') ) {
+
+ my %hash = ( 'country' => scalar($cgi->param('country')) );
+ foreach (qw( state county )) {
+ $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_);
+ }
+ my $cust_main_county = qsearchs('cust_main_county', \%hash);
+ die "unknown base region for empty taxclass" unless $cust_main_county;
+
+ my $same_sql = $cust_main_county->sql_taxclass_sameregion;
+ push @where, $same_sql if $same_sql;
+
+ }
+
+} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+
+ # this should really be shoved out to FS::cust_pkg->location_sql or something
+ # along with the code in report_newtax.cgi
+
+ my %pn = (
+ 'county' => 'tax_rate_location.county',
+ 'state' => 'tax_rate_location.state',
+ 'city' => 'tax_rate_location.city',
+ 'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid',
+ );
+
+ my %ph = map { ( $pn{$_} => dbh->quote( $cgi->param($_) || '' ) ) }
+ qw( city county state locationtaxid );
+
+ push @where,
+ join( ' AND ', map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" }
+ keys %ph
+ );
+
+} elsif ( $cgi->param('unearned_now') =~ /^(\d+)$/ ) {
+
+ $unearned = $1;
+
+ push @where, "cust_bill_pkg.sdate < $unearned",
+ "cust_bill_pkg.edate > $unearned",
+ "cust_bill_pkg.recur != 0",
+ "part_pkg.freq != '0'",
+ "part_pkg.freq != '1'",
+ "part_pkg.freq NOT LIKE '%h'",
+ "part_pkg.freq NOT LIKE '%d'",
+ "part_pkg.freq NOT LIKE '%w'";
+
+}
+
+if ( $cgi->param('itemdesc') ) {
+ if ( $cgi->param('itemdesc') eq 'Tax' ) {
+ push @where, "(itemdesc='Tax' OR itemdesc is null)";
+ } else {
+ push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc'));
+ }
+}
+
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) {
+ my ( $group_op, $group_value ) = ( $1, $2 );
+ if ( $group_op eq '=' ) {
+ #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
+ push @where, 'itemdesc = '. dbh->quote($group_value);
+ } elsif ( $group_op eq '!=' ) {
+ push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
+ } else {
+ die "guru meditation #00de: group_op $group_op\n";
+ }
+
+}
+
+push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax');
+push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax');
+
+if ( $cgi->param('cust_tax') ) {
+ #false laziness -ish w/report_tax.cgi
+ my $cust_exempt;
+ if ( $cgi->param('taxname') ) {
+ my $q_taxname = dbh->quote($cgi->param('taxname'));
+ $cust_exempt =
+ "( tax = 'Y'
+ OR EXISTS ( SELECT 1 FROM cust_main_exemption
+ WHERE cust_main_exemption.custnum = cust_main.custnum
+ AND cust_main_exemption.taxname = $q_taxname )
+ )
+ ";
+ } else {
+ $cust_exempt = " tax = 'Y' ";
+ }
+
+ push @where, $cust_exempt;
+}
+
+my $use_usage = $cgi->param('use_usage');
+
+my $count_query;
+if ( $cgi->param('pkg_tax') ) {
+
+ $count_query =
+ "SELECT COUNT(*),
+ SUM(
+ ( CASE WHEN part_pkg.setuptax = 'Y'
+ THEN cust_bill_pkg.setup
+ ELSE 0
+ END
+ )
+ +
+ ( CASE WHEN part_pkg.recurtax = 'Y'
+ THEN cust_bill_pkg.recur
+ ELSE 0
+ END
+ )
+ )
+ ";
+
+ push @where, "( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
+ OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) )",
+ "( tax != 'Y' OR tax IS NULL )";
+
+} elsif ( $cgi->param('taxable') ) {
+
+ my $setup_taxable = "(
+ CASE WHEN part_pkg.setuptax = 'Y'
+ THEN 0
+ ELSE cust_bill_pkg.setup
+ END
+ )";
+
+ my $recur_taxable = "(
+ CASE WHEN part_pkg.recurtax = 'Y'
+ THEN 0
+ ELSE cust_bill_pkg.recur
+ END
+ )";
+
+ my $exempt = "(
+ SELECT COALESCE( SUM(amount), 0 ) FROM cust_tax_exempt_pkg
+ WHERE cust_tax_exempt_pkg.billpkgnum = cust_bill_pkg.billpkgnum
+ )";
+
+ $count_query =
+ "SELECT COUNT(*), SUM( $setup_taxable + $recur_taxable - $exempt )";
+
+ push @where,
+ #not tax-exempt package (setup or recur)
+ "(
+ ( ( part_pkg.setuptax != 'Y' OR part_pkg.setuptax IS NULL )
+ AND cust_bill_pkg.setup > 0 )
+ OR
+ ( ( part_pkg.recurtax != 'Y' OR part_pkg.recurtax IS NULL )
+ AND cust_bill_pkg.recur > 0 )
+ )",
+ #not a tax_exempt customer
+ "( tax != 'Y' OR tax IS NULL )";
+ #not covered in full by a monthly tax exemption (texas tax)
+ "0 < ( $setup_taxable + $recur_taxable - $exempt )",
+
+} else {
+
+ $count_query = "SELECT COUNT(*), ";
+
+ if ( $use_usage eq 'recurring' ) {
+ $count_query .= "SUM(setup + recur - usage)";
+ } elsif ( $use_usage eq 'usage' ) {
+ $count_query .= "SUM(usage)";
+ } elsif ( $unearned ) {
+ $count_query .= "SUM(cust_bill_pkg.recur)";
+ } else {
+ $count_query .= "SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)";
+ }
+
+ if ( $unearned ) {
+
+ #false laziness w/report_prepaid_income.cgi
+
+ my $float = 'REAL'; #'DOUBLE PRECISION';
+
+ my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)";
+ my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $unearned
+ THEN 0
+ ELSE ($unearned - cust_bill_pkg.sdate)
+ END)";
+ #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)";
+
+ my $remaining = "(1 - $elapsed/$period)";
+
+ $count_query .= ", SUM($remaining * cust_bill_pkg.recur)";
+
+ }
+
+}
+
+my $join_cust = ' JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) ';
+
+
+my $join_pkg;
+if ( $cgi->param('nottax') ) {
+
+ $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override
+ ON pkgpart_override = override.pkgpart ';
+ $join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) '
+ if $conf->exists('tax-pkg_address');
+
+} elsif ( $cgi->param('istax') ) {
+
+ #false laziness w/report_tax.cgi $taxfromwhere
+ if ( $conf->exists('tax-pkg_address') ) {
+ $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
+ LEFT JOIN cust_location USING ( locationnum ) ';
+
+ #quelle kludge, somewhat false laziness w/report_tax.cgi
+ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
+ } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ||
+ $cgi->param('iscredit') eq 'rate') {
+ $join_pkg .=
+ ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
+ ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
+ }
+
+ if ( $cgi->param('iscredit') ) {
+ $join_pkg .= ' JOIN cust_credit_bill_pkg USING ( billpkgnum';
+ if ( $conf->exists('tax-pkg_address') ) {
+ $join_pkg .= ', billpkgtaxlocationnum )';
+ push @where, "billpkgtaxratelocationnum IS NULL";
+ } elsif ( $cgi->param('iscredit') eq 'rate' ) {
+ $join_pkg .= ', billpkgtaxratelocationnum )';
+ } else {
+ $join_pkg .= ' )';
+ push @where, "billpkgtaxratelocationnum IS NULL";
+ }
+ }
+
+} else {
+
+ #die?
+ warn "neiether nottax nor istax parameters specified";
+ #same as before?
+ $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart ) ';
+
+}
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+if ($use_usage) {
+ $count_query .=
+ " FROM (SELECT cust_bill_pkg.setup, cust_bill_pkg.recur,
+ ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_bill_pkg_detail
+ WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_detail.billpkgnum
+ ) AS usage FROM cust_bill_pkg $join_cust $join_pkg $where
+ ) AS countquery";
+} else {
+ $count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where";
+}
+
+my @select = ( 'cust_bill_pkg.*',
+ 'cust_bill._date', );
+
+push @select, 'part_pkg.pkg',
+ 'part_pkg.freq',
+ unless $cgi->param('istax');
+
+push @select, 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields();
+
+my $query = {
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => "$join_cust $join_pkg",
+ 'hashref' => {},
+ 'select' => join(', ', @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY _date, billpkgnum',
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $owed_sub = sub {
+ $money_char. shift->owed_recur; #_recur :/
+};
+
+my $payment_date_sub = sub {
+ #my $cust_bill_pkg = shift;
+ my @cust_pay = sort { $a->_date <=> $b->_date }
+ map $_->cust_bill_pay->cust_pay,
+ shift->cust_bill_pay_pkg('recur') #recur :/
+ or return '';
+ time2str('%b %d %Y', $cust_pay[-1]->_date );
+};
+
+</%init>
diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html
new file mode 100644
index 0000000..088b291
--- /dev/null
+++ b/httemplate/search/cust_bill_pkg_discount.html
@@ -0,0 +1,151 @@
+<% include( 'elements/search.html',
+ 'title' => 'Discounts',
+ 'name' => 'discounts',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total', ],
+ 'header' => [
+ #'#',
+ 'Discount',
+ 'Amount',
+ 'Months',
+ 'Package',
+ 'Invoice',
+ 'Date',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ #'billpkgdiscountnum',
+ sub { $_[0]->cust_pkg_discount->discount->description },
+ sub { sprintf($money_char.'%.2f', shift->amount ) },
+ sub { my $m = shift->months;
+ $m =~ /\./ ? sprintf('%.2f', $m) : $m;
+ },
+ 'pkg',#sub { $_[0]->cust_bill_pkg->cust_pkg->part_pkg->pkg },
+ 'invnum',
+ sub { time2str('%b %d %Y', shift->_date ) },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ #'',
+ '', #link to customer discount???
+ '',
+ '',
+ '',
+ $ilink,
+ $ilink,
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
+ 'align' => 'lrrlrr'.FS::UI::Web::cust_aligns(),
+ 'color' => [
+ #'',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ #'',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+#a little false laziness below w/cust_bill_pkg.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @where, "_date >= $beginning",
+ "_date <= $ending";
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+# #classnum
+# # not specified: all classes
+# # 0: empty class
+# # N: classnum
+# my $use_override = $cgi->param('use_override');
+# if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+# my $comparison = '';
+# if ( $1 == 0 ) {
+# $comparison = "IS NULL";
+# } else {
+# $comparison = "= $1";
+# }
+#
+# if ( $use_override ) {
+# push @where, "(
+# part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+# override.classnum $comparison AND pkgpart_override IS NOT NULL
+# )";
+# } else {
+# push @where, "part_pkg.classnum $comparison";
+# }
+# }
+
+my $count_query = "SELECT COUNT(*), SUM(amount)";
+
+my $join_cust = ' JOIN cust_bill_pkg USING ( billpkgnum )
+ JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) ';
+
+my $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart ) ';
+ #LEFT JOIN part_pkg AS override
+ # ON pkgpart_override = override.pkgpart ';
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+$count_query .= " FROM cust_bill_pkg_discount $join_cust $join_pkg $where";
+
+my @select = (
+ 'cust_bill_pkg_discount.*',
+ #'cust_bill_pkg.*',
+ 'cust_bill.invnum',
+ 'cust_bill._date',
+ );
+push @select, 'part_pkg.pkg';
+push @select, 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields();
+
+my $query = {
+ 'table' => 'cust_bill_pkg_discount',
+ 'addl_from' => "$join_cust $join_pkg",
+ 'hashref' => {},
+ 'select' => join(', ', @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY _date, billpkgdiscountnum',
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html
new file mode 100755
index 0000000..9a14dce
--- /dev/null
+++ b/httemplate/search/cust_credit.html
@@ -0,0 +1,104 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => 'credits',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total credited (gross)', ],
+ #'redirect' => $link,
+ 'header' => [ 'Amount',
+ 'Date',
+ 'By',
+ 'Reason',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ #'crednum',
+ sub { sprintf('$%.2f', shift->amount ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ 'otaker',
+ 'reason',
+ \&FS::UI::Web::cust_fields,
+ ],
+ #'align' => 'rrrllll',
+ 'align' => 'rrll'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $title = 'Credit Search Results';
+#my( $count_query, $sql_query );
+
+my @search = ();
+
+if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) {
+ push @search, "cust_credit.otaker = '$1'";
+}
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @search, "agentnum = $1";
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "unknown agentnum $1" unless $agent;
+ $title = $agent->agent. " $title";
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "_date >= $beginning ",
+ "_date <= $ending";
+
+push @search, FS::UI::Web::parse_lt_gt($cgi, 'amount' );
+
+#here is the agent virtualization
+push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = 'WHERE '. join(' AND ', @search);
+
+my $count_query = 'SELECT COUNT(*), SUM(amount) '.
+ 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '.
+ $where;
+
+my $sql_query = {
+ 'table' => 'cust_credit',
+ 'select' => join(', ',
+ 'cust_credit.*',
+ 'cust_main.custnum as cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+};
+
+ my $clink = sub {
+ my $cust_bill = shift;
+ $cust_bill->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+ };
+
+</%init>
diff --git a/httemplate/search/cust_credit_bill.html b/httemplate/search/cust_credit_bill.html
new file mode 100644
index 0000000..818e603
--- /dev/null
+++ b/httemplate/search/cust_credit_bill.html
@@ -0,0 +1,135 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => 'net credits',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total credited (net)', ],
+ 'header' => [ 'Net applied',
+ 'to Invoice',
+ 'Credit',
+ 'By',
+ 'Reason',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ sub { $money_char. sprintf('%.2f', shift->amount ) },
+ sub { my $ccb = shift;
+ '#'.$ccb->invnum. ' '.
+ time2str('%b %d %Y', $ccb->cust_bill_date ).
+ " ($money_char".
+ sprintf('%.2f', $ccb->cust_bill_amount).
+ ")"
+ },
+ sub { my $ccb = shift;
+ time2str('%b %d %Y', $ccb->_date ).
+ " ($money_char".
+ sprintf('%.2f', $ccb->cust_credit_amount ).
+ ")"
+ },
+ sub { shift->cust_credit->otaker },
+ sub { shift->cust_credit->reason },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'rrrll'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ $cust_bill_link,
+ '',
+ '',
+ '',
+ ( map { $_ ne 'Cust. Status' ? $cust_link : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $title = 'Net Credit Search Results';
+
+my @search = ();
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @search, "agentnum = $1";
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "unknown agentnum $1" unless $agent;
+ $title = $agent->agent. " $title";
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "cust_bill._date >= $beginning ",
+ "cust_bill._date <= $ending";
+
+#here is the agent virtualization
+push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = 'WHERE '. join(' AND ', @search);
+#
+my $count_query = 'SELECT COUNT(*), SUM(amount)
+ FROM cust_credit_bill
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) '.
+ $where;
+
+my $sql_query = {
+ 'table' => 'cust_credit_bill',
+ 'select' => join(', ',
+ 'cust_credit_bill.*',
+ 'cust_credit.amount AS cust_credit_amount',
+ 'cust_bill._date AS cust_bill_date',
+ 'cust_bill.charged AS cust_bill_charged',
+ 'cust_credit.custnum AS custnum',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_credit USING ( crednum )
+ LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )',
+};
+
+my $cust_bill_link = sub {
+ my $cust_credit_bill = shift;
+ $cust_credit_bill->invnum
+ ? [ "${p}view/cust_bill.cgi?", 'invnum' ]
+ : '';
+};
+
+#my $cust_credit_link = sub {
+# my $cust_credit_bill = shift;
+# $cust_credit_bill->crednum
+# ? [ "${p}view/cust_credit.cgi?", 'crednum' ]
+# : '';
+#};
+
+my $cust_link = sub {
+ my $cust_credit_bill = shift;
+ $cust_credit_bill->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ]
+ : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html
new file mode 100644
index 0000000..52e0ac6
--- /dev/null
+++ b/httemplate/search/cust_credit_bill_pkg.html
@@ -0,0 +1,520 @@
+<% include( 'elements/search.html',
+ 'title' => 'Tax credits', #well, actually application of
+ 'name' => 'tax credits', # credit to line item
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total', ],
+ 'header' => [
+ #'#',
+
+ 'Amount',
+
+ #credit
+ 'Date',
+ 'By',
+ 'Reason',
+
+ # line item
+ 'Description',
+
+ #invoice
+ 'Invoice',
+ 'Date',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ #'creditbillpkgnum',
+ sub { sprintf($money_char.'%.2f', shift->amount ) },
+
+ sub { time2str('%b %d %Y', shift->get('cust_credit_date') ) },
+ 'otaker',
+ sub { shift->cust_credit_bill->cust_credit->reason },
+
+ sub { $_[0]->pkgnum > 0
+ ? $_[0]->get('pkg') # possibly use override.pkg
+ : $_[0]->get('itemdesc') # but i think this correct
+ },
+ 'invnum',
+ sub { time2str('%b %d %Y', shift->_date ) },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ $ilink,
+ $ilink,
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rrlllrr'.FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+#LOTS of false laziness below w/cust_bill_pkg.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @where, "cust_bill._date >= $beginning",
+ "cust_bill._date <= $ending";
+
+push @where , " payby != 'COMP' "
+ unless $cgi->param('include_comp_cust');
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+#classnum
+# not specified: all classes
+# 0: empty class
+# N: classnum
+my $use_override = $cgi->param('use_override');
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ my $comparison = '';
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $use_override ) {
+ push @where, "(
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ push @where, "part_pkg.classnum $comparison";
+ }
+}
+
+if ( $cgi->param('taxclass')
+ && ! $cgi->param('istax') #no part_pkg.taxclass in this case
+ #(should we save a taxclass or a link to taxnum
+ # in cust_bill_pkg or something like
+ # cust_bill_pkg_tax_location?)
+ )
+{
+
+ #override taxclass when use_override is specified? probably
+ #if ( $use_override ) {
+ #
+ # push @where,
+ # ' ( '. join(' OR ',
+ # map {
+ # ' ( part_pkg.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NULL '.
+ # ' OR '.
+ # ' override.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NOT NULL '.
+ # ' ) '
+ # }
+ # $cgi->param('taxclass')
+ # ).
+ # ' ) ';
+ #
+ #} else {
+
+ push @where,
+ ' ( '. join(' OR ',
+ map ' part_pkg.taxclass = '.dbh->quote($_),
+ $cgi->param('taxclass')
+ ).
+ ' ) ';
+
+ #}
+
+}
+
+my @loc_param = qw( city county state country );
+
+if ( $cgi->param('out') ) {
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 );
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e;
+ }
+
+ $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g
+ if $cgi->param('istax');
+
+ push @where, "
+ 0 = (
+ SELECT COUNT(*) FROM cust_main_county
+ WHERE cust_main_county.tax > 0
+ AND $loc_sql
+ )
+ ";
+
+ #not linked to by anything, but useful for debugging "out of taxable region"
+ if ( grep $cgi->param($_), @loc_param ) {
+
+ my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ push @where, $loc_sql;
+
+ }
+
+} elsif ( $cgi->param('country') ) {
+
+ my @counties = $cgi->param('county');
+
+ if ( scalar(@counties) > 1 ) {
+
+ #hacky, could be more efficient. care if it is ever used for more than the
+ # tax-report_groups filtering kludge
+
+ my $locs_sql =
+ ' ( '. join(' OR ', map {
+
+ my %ph = ( 'county' => dbh->quote($_),
+ map { $_ => dbh->quote( $cgi->param($_) ) }
+ qw( city state country )
+ );
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ $loc_sql;
+
+ } @counties
+
+ ). ' ) ';
+
+ push @where, $locs_sql;
+
+ } else {
+
+ my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ push @where, $loc_sql;
+
+ }
+
+ my($title, $name);
+ if ( $cgi->param('istax') ) {
+ $title = 'Tax credits';
+ $name = 'tax credits';
+ if ( $cgi->param('taxname') ) {
+ push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') );
+ #} elsif ( $cgi->param('taxnameNULL') {
+ } else {
+ push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+ }
+ } elsif ( $cgi->param('nottax') ) {
+ $title = 'Credit applications to line items';
+ $name = 'applications';
+ #what can we usefully do with "taxname" ???? look up a class???
+ } else {
+ $title = 'Credit applications to line items';
+ $name = 'applications';
+ #warn "neither nottax nor istax parameters specified";
+ }
+
+ if ( $cgi->param('taxclassNULL') ) {
+
+ my %hash = ( 'country' => scalar($cgi->param('country')) );
+ foreach (qw( state county )) {
+ $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_);
+ }
+ my $cust_main_county = qsearchs('cust_main_county', \%hash);
+ die "unknown base region for empty taxclass" unless $cust_main_county;
+
+ my $same_sql = $cust_main_county->sql_taxclass_sameregion;
+ push @where, $same_sql if $same_sql;
+
+ }
+
+} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+
+ # this should really be shoved out to FS::cust_pkg->location_sql or something
+ # along with the code in report_newtax.cgi
+
+ my %pn = (
+ 'county' => 'tax_rate_location.county',
+ 'state' => 'tax_rate_location.state',
+ 'city' => 'tax_rate_location.city',
+ 'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid',
+ );
+
+ my %ph = map { ( $pn{$_} => dbh->quote( $cgi->param($_) || '' ) ) }
+ qw( city county state locationtaxid );
+
+ push @where,
+ join( ' AND ', map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" }
+ keys %ph
+ );
+
+}
+
+if ( $cgi->param('itemdesc') ) {
+ if ( $cgi->param('itemdesc') eq 'Tax' ) {
+ push @where, "(itemdesc='Tax' OR itemdesc is null)";
+ } else {
+ push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc'));
+ }
+}
+
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) {
+ my ( $group_op, $group_value ) = ( $1, $2 );
+ if ( $group_op eq '=' ) {
+ #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
+ push @where, 'itemdesc = '. dbh->quote($group_value);
+ } elsif ( $group_op eq '!=' ) {
+ push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
+ } else {
+ die "guru meditation #00de: group_op $group_op\n";
+ }
+
+}
+
+push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax');
+push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax');
+
+if ( $cgi->param('cust_tax') ) {
+ #false laziness -ish w/report_tax.cgi
+ my $cust_exempt;
+ if ( $cgi->param('taxname') ) {
+ my $q_taxname = dbh->quote($cgi->param('taxname'));
+ $cust_exempt =
+ "( tax = 'Y'
+ OR EXISTS ( SELECT 1 FROM cust_main_exemption
+ WHERE cust_main_exemption.custnum = cust_main.custnum
+ AND cust_main_exemption.taxname = $q_taxname )
+ )
+ ";
+ } else {
+ $cust_exempt = " tax = 'Y' ";
+ }
+
+ push @where, $cust_exempt;
+}
+
+my $use_usage = $cgi->param('use_usage');
+
+my $count_query;
+if ( $cgi->param('pkg_tax') ) { #does this mean anything here?
+
+ $count_query =
+ "SELECT COUNT(*),
+ SUM(
+ ( CASE WHEN part_pkg.setuptax = 'Y'
+ THEN cust_bill_pkg.setup
+ ELSE 0
+ END
+ )
+ +
+ ( CASE WHEN part_pkg.recurtax = 'Y'
+ THEN cust_bill_pkg.recur
+ ELSE 0
+ END
+ )
+ )
+ ";
+
+ push @where, "( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
+ OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) )",
+ "( tax != 'Y' OR tax IS NULL )";
+
+} elsif ( $cgi->param('taxable') ) { #again, meaningful?
+
+ my $setup_taxable = "(
+ CASE WHEN part_pkg.setuptax = 'Y'
+ THEN 0
+ ELSE cust_bill_pkg.setup
+ END
+ )";
+
+ my $recur_taxable = "(
+ CASE WHEN part_pkg.recurtax = 'Y'
+ THEN 0
+ ELSE cust_bill_pkg.recur
+ END
+ )";
+
+ my $exempt = "(
+ SELECT COALESCE( SUM(amount), 0 ) FROM cust_tax_exempt_pkg
+ WHERE cust_tax_exempt_pkg.billpkgnum = cust_bill_pkg.billpkgnum
+ )";
+
+ $count_query =
+ "SELECT COUNT(*), SUM( $setup_taxable + $recur_taxable - $exempt )";
+
+ push @where,
+ #not tax-exempt package (setup or recur)
+ "(
+ ( ( part_pkg.setuptax != 'Y' OR part_pkg.setuptax IS NULL )
+ AND cust_bill_pkg.setup > 0 )
+ OR
+ ( ( part_pkg.recurtax != 'Y' OR part_pkg.recurtax IS NULL )
+ AND cust_bill_pkg.recur > 0 )
+ )",
+ #not a tax_exempt customer
+ "( tax != 'Y' OR tax IS NULL )";
+ #not covered in full by a monthly tax exemption (texas tax)
+ "0 < ( $setup_taxable + $recur_taxable - $exempt )",
+
+} else {
+
+ $count_query = "SELECT COUNT(*), ";
+
+ if ( $use_usage eq 'recurring' ) { #mean anything?
+ $count_query .= "SUM(setup + recur - usage)";
+ } elsif ( $use_usage eq 'usage' ) { #mean anything?
+ $count_query .= "SUM(usage)";
+ } else {
+ $count_query .= "SUM(cust_credit_bill_pkg.amount)";
+ }
+
+}
+
+my $join_cust =
+ ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )
+ LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum ) ';
+
+
+my $join_pkg;
+
+my $join_cust_bill_pkg = 'LEFT JOIN cust_bill_pkg USING ( billpkgnum ';
+
+if ( $cgi->param('nottax') ) {
+
+ $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override
+ ON pkgpart_override = override.pkgpart ';
+ $join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) '
+ if $conf->exists('tax-pkg_address');
+
+} elsif ( $cgi->param('istax') ) {
+
+ #false laziness w/report_tax.cgi $taxfromwhere
+ if ( $conf->exists('tax-pkg_address') ) {
+ $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
+ LEFT JOIN cust_location USING ( locationnum ) ';
+
+ #quelle kludge, somewhat false laziness w/report_tax.cgi
+ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
+ } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ||
+ $cgi->param('iscredit') eq 'rate') {
+ $join_pkg .=
+ ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
+ ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
+ }
+
+ if ( $conf->exists('tax-pkg_address') ) {
+ $join_cust_bill_pkg .= ', billpkgtaxlocationnum )';
+ push @where, "billpkgtaxratelocationnum IS NULL";
+ #} elsif ( $cgi->param('iscredit') eq 'rate' ) {
+ # $join_pkg .= ', billpkgtaxratelocationnum )';
+ } else {
+ $join_cust_bill_pkg .= ' )';
+ push @where, "billpkgtaxratelocationnum IS NULL";
+ }
+
+} else {
+
+ #die?
+ warn "neiether nottax nor istax parameters specified";
+ #same as before?
+ $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart ) ';
+
+}
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+my $join_credit = ' LEFT JOIN cust_credit_bill USING ( creditbillnum )
+ LEFT JOIN cust_credit USING ( crednum ) ';
+
+#if ($use_usage) {
+# $count_query .=
+# " FROM (SELECT cust_bill_pkg.setup, cust_bill_pkg.recur,
+# ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_bill_pkg_detail
+# WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_detail.billpkgnum
+# ) AS usage FROM cust_bill_pkg $join_cust $join_pkg $where
+# ) AS countquery";
+#} else {
+ $count_query .= " FROM cust_credit_bill_pkg
+ $join_pkg
+ $join_cust_bill_pkg
+ $join_credit
+ $join_cust
+ $where";
+#}
+
+my @select = ( 'cust_credit_bill_pkg.*',
+ 'cust_bill_pkg.*',
+ 'cust_credit.otaker',
+ 'cust_credit._date AS cust_credit_date',
+ 'cust_bill._date',
+ );
+push @select, 'part_pkg.pkg' unless $cgi->param('istax');
+push @select, 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields();
+
+my $query = {
+ 'table' => 'cust_credit_bill_pkg',
+ 'addl_from' => "$join_pkg
+ $join_cust_bill_pkg
+ $join_credit
+ $join_cust",
+ 'hashref' => {},
+ 'select' => join(', ', @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY creditbillpkgnum', #cust_bill. or cust_credit._date?
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html
new file mode 100644
index 0000000..d9abe2e
--- /dev/null
+++ b/httemplate/search/cust_credit_refund.html
@@ -0,0 +1,130 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => 'net refunds',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total refunded (net)', ],
+ 'header' => [ 'Net applied',
+ 'to Credit',
+ 'Refund',
+ 'By',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ sub { $money_char. sprintf('%.2f', shift->amount ) },
+ sub { my $ccr = shift;
+ '#'.$ccr->crednum. ' '.
+ time2str('%b %d %Y', $ccr->cust_credit_date ).
+ " ($money_char".
+ sprintf('%.2f', $ccr->cust_credit_amount).
+ ")"
+ },
+ sub { my $ccr = shift;
+ time2str('%b %d %Y', $ccr->_date ).
+ " ($money_char".
+ sprintf('%.2f', $ccr->cust_refund_refund ).
+ ")"
+ },
+ sub { shift->cust_refund->otaker },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'rrrl'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+ ( map { $_ ne 'Cust. Status' ? $cust_link : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $title = 'Net Refund Search Results';
+
+my @search = ();
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @search, "agentnum = $1";
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "unknown agentnum $1" unless $agent;
+ $title = $agent->agent. " $title";
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "cust_credit._date >= $beginning ",
+ "cust_credit._date <= $ending";
+
+#here is the agent virtualization
+push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = 'WHERE '. join(' AND ', @search);
+#
+my $count_query = 'SELECT COUNT(*), SUM(cust_credit_refund.amount)
+ FROM cust_credit_refund
+ LEFT JOIN cust_credit USING ( crednum )
+ LEFT JOIN cust_main USING ( custnum ) '.
+ $where;
+
+my $sql_query = {
+ 'table' => 'cust_credit_refund',
+ 'select' => join(', ',
+ 'cust_credit_refund.*',
+ 'cust_refund.refund AS cust_refund_refund',
+ 'cust_credit._date AS cust_credit_date',
+ 'cust_credit.amount AS cust_credit_amnount',
+ 'cust_refund.custnum AS custnum',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ 'addl_from' => 'LEFT JOIN cust_credit USING ( crednum )
+ LEFT JOIN cust_refund USING ( refundnum )
+ LEFT JOIN cust_main ON ( cust_credit.custnum = cust_main.custnum )',
+};
+
+#my $cust_credit_link = sub {
+# my $cust_credit_refund = shift;
+# $cust_credit_refund->crednum
+# ? [ "${p}view/cust_credit.cgi?", 'credum' ]
+# : '';
+#};
+
+#my $cust_refund_link = sub {
+# my $cust_credit_refund = shift;
+# $cust_credit_refund->refundnum
+# ? [ "${p}view/cust_refund.cgi?", 'refundnum' ]
+# : '';
+#};
+
+my $cust_link = sub {
+ my $cust_credit_refund = shift;
+ $cust_credit_refund->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ]
+ : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html
new file mode 100644
index 0000000..a0429e4
--- /dev/null
+++ b/httemplate/search/cust_event.html
@@ -0,0 +1,271 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'html_init' => $html_init,
+ 'menubar' => $menubar,
+ 'name' => 'billing events',
+ 'query' => $sql_query,
+ 'count_query' => $count_sql,
+ 'header' => [ 'Event',
+ 'Date',
+ 'Status',
+ 'Trigger',
+ #'Inv #', 'Inv Date', 'Cust #',
+ #'Invoice',
+
+ FS::UI::Web::cust_header(), #'cust_main_custnum',
+ ],
+ 'fields' => [
+ 'event',
+ sub { time2str("%b %d %Y %T", $_[0]->_date) },
+ $status_sub,
+ $trigger_sub,
+ #sub {
+ # #my $cust_event = shift;
+ # 'Invoice #'. $_[0]->invnum.
+ # ' ('.
+ # time2str("%D", $_[0]->cust_bill_date).
+ # ')';
+ # },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'lrll'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ '',
+ $trigger_link,
+ #sub {
+ # my $part_event = shift;
+ # #XXX
+ # my $template = $part_event->templatename;
+ # $template .= '-' if $template;
+ # [ "${p}view/cust_bill.cgi?$template", 'invnum'];
+ #},
+
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ #'',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ #'',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%once>
+
+my $status_sub = sub {
+ my $cust_event = shift;
+
+ my $status = $cust_event->status;
+ $status .= ': '.$cust_event->statustext
+ if $cust_event->statustext;
+
+ my $part_event = $cust_event->part_event;
+
+ if ( $part_event->eventtable eq 'cust_bill'
+ && ( $part_event->templatename || $part_event->option('notice_name') )
+ )
+ {
+ my $link = 'invnum='. $cust_event->tablenum;
+ $link .= ';template='. uri_escape($part_event->templatename)
+ if $part_event->templatename;
+ $link .= ';notice_name='. uri_escape($part_event->option('notice_name'))
+ if $part_event->option('notice_name');
+
+ my $conf = new FS::Conf;
+ my $cust_bill = $cust_event->cust_X;
+
+ $status .= qq{
+ ( <A HREF="${p}view/cust_bill.cgi?$link">view</A>
+ | <A HREF="${p}view/cust_bill-pdf.cgi?$link">view&nbsp;typeset</A>
+ | <A HREF="${p}misc/send-invoice.cgi?method=print;$link">re-print</A>
+ };
+
+ if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ $status .= qq{
+ | <A HREF="${p}misc/send-invoice.cgi?method=email;$link">re-email</A>
+ };
+ }
+
+ if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
+ $status .= qq{
+ | <A HREF="${p}misc/send-invoice.cgi?method=fax;$link">re-fax</A>
+ }
+ }
+
+ $status .= ' ) ';
+
+ }
+
+ $status;
+};
+
+my $trigger_sub = sub {
+ my $cust_event = shift;
+ my $eventtable = $cust_event->eventtable;
+ my $label = FS::part_event->eventtable_labels->{$eventtable};
+ #if ( $eventtable eq 'cust_pkg' || $eventtable eq 'cust_bill' ) {
+ "$label #". $cust_event->tablenum;
+ #} else {
+ # $label;
+ #}
+};
+
+my $trigger_link = sub {
+ my $cust_event = shift;
+ my $eventtable = $cust_event->eventtable;
+ if ( $eventtable eq 'cust_pkg' ) {
+ my $custnum = $cust_event->cust_main_custnum;
+ my $show = $FS::CurrentUser::CurrentUser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+ my $pkgnum = $cust_event->tablenum;
+ my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+ [ "${p}view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#cust_pkg", 'tablenum' ];
+ } else {
+ [ "${p}view/$eventtable.cgi?", 'tablenum' ];
+ }
+};
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Billing event reports')
+ or $curuser->access_right('View customer billing events')
+ && ( $cgi->param('custnum') =~ /^(\d+)$/
+ || $cgi->param('invnum') =~ /^(\d+)$/
+ || $cgi->param('pkgnum') =~ /^(\d+)$/
+ );
+
+my $title = $cgi->param('failed') ? 'Failed billing events' : 'Billing events';
+
+my %search = ();
+
+my @scalars = qw( agentnum status custnum invnum pkgnum failed );
+for my $param (@scalars) {
+ $search{$param} = scalar( $cgi->param($param) )
+ if $cgi->param($param);
+}
+
+#lists
+my @lists = qw( payby eventpart );
+foreach my $param (@lists) {
+ $search{$param} = [ $cgi->param($param) ];
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+$search{'beginning'} = $beginning;
+$search{'ending'} = $ending;
+
+my $where = ' WHERE '. FS::cust_event->search_sql_where( \%search );
+
+my $join = FS::cust_event->join_sql();
+
+my $sql_query = {
+ 'table' => 'cust_event',
+ 'select' => join(', ',
+ 'cust_event.*',
+ 'part_event.*',
+ #'cust_bill.custnum',
+ #'cust_bill._date AS cust_bill_date',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => "$where ORDER BY _date ASC",
+ 'addl_from' => $join,
+};
+
+my $count_sql = "SELECT COUNT(*) FROM cust_event $join $where";
+
+my $conf = new FS::Conf;
+
+my @params = ( @scalars, qw( beginning ending ) );
+
+my $html_init = join("\n", map {
+ ( my $action = $_ ) =~ s/_$//;
+ include('/elements/progress-init.html',
+ $_.'form',
+ [ 'action', @params ],
+ "../misc/${_}events.cgi",
+ { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but...
+ $_, #key
+ ),
+ qq!<FORM NAME="${_}form">!,
+ qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though
+ ( map { my $value = encode_entities( $search{$_} );
+ qq(<INPUT TYPE="hidden" NAME="$_" VALUE="$value">);
+ }
+ @params #keys %search
+ ),
+ ( map { my $value = encode_entities( join(',', @{ $search{$_} } ) );
+ qq(<INPUT TYPE="hidden" NAME="$_" VALUE="$value">);
+ }
+ @lists
+ ),
+ qq!</FORM>!
+} qw( print_ email_ fax_ ) ).
+
+'<SCRIPT TYPE="text/javascript">
+
+function confirm_print_process() {
+ if ( ! confirm("Are you sure you want to reprint these invoices?") ) {
+ return;
+ }
+ print_process();
+}
+function confirm_email_process() {
+ if ( ! confirm("Are you sure you want to re-email these invoices?") ) {
+ return;
+ }
+ email_process();
+}
+function confirm_fax_process() {
+ if ( ! confirm("Are you sure you want to re-fax these invoices?") ) {
+ return;
+ }
+ fax_process();
+}
+
+</SCRIPT>';
+
+my $menubar = [];
+
+if ( $curuser->access_right('Resend invoices') ) {
+
+ push @$menubar, 'Re-print these events' =>
+ "javascript:confirm_print_process()",
+ 'Re-email these events' =>
+ "javascript:confirm_email_process()",
+ ;
+
+ push @$menubar, 'Re-fax these events' =>
+ "javascript:confirm_fax_process()"
+ if $conf->exists('hylafax');
+
+}
+
+my $link_cust = sub {
+ my $cust_event = shift;
+ $cust_event->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ]
+ : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi
new file mode 100755
index 0000000..0c252e4
--- /dev/null
+++ b/httemplate/search/cust_main-otaker.cgi
@@ -0,0 +1,31 @@
+<% include('/elements/header.html', 'Customer Search' ) %>
+
+<FORM ACTION="cust_main.cgi" METHOD="GET">
+
+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->fetchall_arrayref};
+%
+
+<SELECT NAME="otaker">
+% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) {
+
+ <OPTION><% $otaker->[0] %>
+% }
+
+</SELECT>
+
+<P><INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html
new file mode 100644
index 0000000..e87b214
--- /dev/null
+++ b/httemplate/search/cust_main-zip.html
@@ -0,0 +1,110 @@
+<% include( 'elements/search.html',
+ 'title' => 'Zip code Search Results',
+ 'name' => 'zip codes',
+ 'query' => $sql_query,
+ 'count_query' => $count_sql,
+ 'header' => [ 'Zip code', 'Customers', ],
+ #'fields' => [ 'zip', 'num_cust', ],
+ 'links' => [ '', sub { 'somewhere'; } ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List zip codes');
+
+# XXX link to customers
+
+my @where = ();
+
+# select status
+
+if ( $cgi->param('status') =~ /^(prospect|uncancel|active|susp|cancel)$/ ) {
+ my $method = $1.'_sql';
+ push @where, FS::cust_main->$method();
+}
+
+# select agent
+# XXX this needs to be virtualized by agent too (like lots of stuff)
+
+my $agentnum = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ push @where, "cust_main.agentnum = $agentnum";
+}
+
+# select svcdb
+
+if ( $cgi->param('svcdb') =~ /^(\w+)$/ ) {
+ my $svcdb = $1;
+ push @where, "EXISTS( SELECT 1 FROM $svcdb LEFT JOIN cust_svc USING ( svcnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )";
+}
+
+my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where) : '';
+
+# bill zip vs ship zip
+
+sub fieldorempty {
+ my $field = shift;
+ "CASE WHEN $field IS NULL THEN '' ELSE $field END";
+}
+
+sub strip_plus4 {
+ my $field = shift;
+ "CASE WHEN $field is NULL
+ THEN ''
+ ELSE CASE WHEN $field LIKE '_____-____'
+ THEN SUBSTRING($field FROM 1 FOR 5)
+ ELSE $field
+ END
+ END";
+}
+
+my( $zip, $czip);
+if ( $cgi->param('column') eq 'ship_zip' ) {
+
+ my $casewhen_noship =
+ "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN ";
+
+ $czip = "$casewhen_noship zip ELSE ship_zip END";
+
+ if ( $cgi->param('ignore_plus4') ) {
+ $zip = $casewhen_noship. strip_plus4('zip').
+ " ELSE ". strip_plus4('ship_zip'). ' END';
+
+ } else {
+ $zip = $casewhen_noship. fieldorempty('zip').
+ " ELSE ". fieldorempty('ship_zip'). ' END';
+ }
+
+} else {
+
+ $czip = 'zip';
+
+ if ( $cgi->param('ignore_plus4') ) {
+ $zip = strip_plus4('zip');
+ } else {
+ $zip = fieldorempty('zip');
+ }
+
+}
+
+# construct the queries and send 'em off
+
+my $sql_query =
+ "SELECT $zip AS zipcode,
+ COUNT(*) AS num_cust
+ FROM cust_main
+ $where
+ GROUP BY zipcode
+ ORDER BY num_cust DESC
+ ";
+
+my $count_sql = "select count(distinct $czip) from cust_main $where";
+
+# XXX should link...
+
+</%init>
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
new file mode 100755
index 0000000..e65dc71
--- /dev/null
+++ b/httemplate/search/cust_main.cgi
@@ -0,0 +1,741 @@
+%my $curuser = $FS::CurrentUser::CurrentUser;
+%
+%die "access denied"
+% unless $curuser->access_right('List customers');
+%
+%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);
+%my @select = ();
+%my @addl_headers = ();
+%my @addl_cols = ();
+%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' ) {
+% if ( $conf->exists('cust_main-default_agent_custid') ) {
+% $sortby=\*display_custnum_sort;
+% $orderby = "ORDER BY CASE WHEN agent_custid IS NOT NULL AND agent_custid != '' THEN CAST(agent_custid AS BIGINT) ELSE custnum END";
+% } else {
+% $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 )";
+% } elsif ( $query eq 'tickets' ) {
+% $sortby = \*tickets_sort;
+% $orderby = "ORDER BY tickets DESC";
+% push @select, FS::TicketSystem->sql_num_customer_tickets. " as tickets";
+% push @addl_headers, 'Tickets';
+% push @addl_cols, 'tickets';
+% } else {
+% die "unknown browse field $query";
+% }
+% } else {
+% $sortby = \*last_sort; #??
+% $orderby = "ORDER BY LOWER(last || ' ' || first)"; #??
+% }
+%
+% if ( $cgi->param('otaker_on') ) {
+% die "access denied"
+% unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+% $cgi->param('otaker') =~ /^(\w{1,32})$/ or errorpage("Illegal otaker");
+% $search{otaker} = $1;
+% } elsif ( $cgi->param('agentnum_on') ) {
+% $cgi->param('agentnum') =~ /^(\d+)$/ or errorpage("Illegal agentnum");
+% $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, FS::cust_main->uncancel_sql;
+%
+% }
+%
+% 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->inactive_sql if $cgi->param('inactive');
+% 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);
+%
+% #here is the agent virtualization
+% $addl_qual .= ( $addl_qual ? ' AND ' : '' ).
+% $FS::CurrentUser::CurrentUser->agentnums_sql;
+%
+% 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";
+% }
+% }
+%
+% my $select;
+% if ( @select ) {
+% $select = 'cust_main.*, '. join (', ', @select);
+% } else {
+% $select = '*';
+% }
+%
+% @cust_main = qsearch('cust_main', \%search, $select,
+% "$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};
+% }
+%
+% if ( $cgi->param('search_cust') ) {
+% $sortby = \*company_sort;
+% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )";
+% push @cust_main, smart_search( 'search' => $cgi->param('search_cust') );
+% }
+%
+% @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 -->
+%
+% errorpage("No matching customers found!");
+%} else {
+%
+
+<% include('/elements/header.html', "Customer Search Results", '' ) %>
+% $total ||= scalar(@cust_main);
+
+
+ <% $total %> matching customers found
+
+% my $pager = include( '/elements/pager.html',
+% 'offset' => $offset,
+% 'num_rows' => scalar(@cust_main),
+% 'total' => $total,
+% 'maxrecords' => $maxrecords,
+% );
+%
+% 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 errorpage("Illegal referral_custnum");
+% my $referral_custnum = $1;
+% my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } );
+% print '<FORM METHOD="GET">'.
+% 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 errorpage("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>';
+% }
+%
+% my @custom_priorities = ();
+% if ( $conf->config('ticket_system-custom_priority_field')
+% && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) {
+% @custom_priorities =
+% $conf->config('ticket_system-custom_priority_field-values');
+% }
+%
+% print "<BR><BR>". $pager. include('/elements/table-grid.html'). <<END;
+% <TR>
+% <TH CLASS="grid" BGCOLOR="#cccccc">#</TH>
+% <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+% <TH CLASS="grid" BGCOLOR="#cccccc">(bill) name</TH>
+% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH>
+%END
+%
+%if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+% print <<END;
+% <TH CLASS="grid" BGCOLOR="#cccccc">(service) name</TH>
+% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH>
+%END
+%}
+%
+%foreach my $addl_header ( @addl_headers ) {
+% print '<TH CLASS="grid" BGCOLOR="#cccccc">'. "$addl_header</TH>";
+%}
+%
+%print <<END;
+% <TH CLASS="grid" BGCOLOR="#cccccc">Packages</TH>
+% <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Services</TH>
+% </TR>
+%END
+%
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% my(%saw,$cust_main);
+% foreach $cust_main (
+% sort $sortby grep(!$saw{$_->custnum}++, @cust_main)
+% ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% 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>';
+%
+% my $status = $cust_main->status;
+% my $statuscol = $cust_main->statuscolor;
+
+ <TR>
+ <TD CLASS="grid" ALIGN="right" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% $cust_main->display_custnum %></FONT></A></TD>
+ <TD CLASS="grid" ALIGN="center" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><FONT SIZE="-1" COLOR="#<% $statuscol %>"><B><% ucfirst($status) %></B></FONT></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$last, $first" %></FONT></A></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pcompany %></TD>
+%
+% 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>';
+%
+
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pship_company %></A></TD>
+% }
+%
+% foreach my $addl_col ( @addl_cols ) {
+% if ( $addl_col eq 'tickets' ) {
+% if ( @custom_priorities ) {
+
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1>
+
+ <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
+% foreach my $priority ( @custom_priorities, '' ) {
+%
+% my $num =
+% FS::TicketSystem->num_customer_tickets($custnum,$priority);
+% my $ahref = '';
+% $ahref= '<A HREF="'.
+% FS::TicketSystem->href_customer_tickets($custnum,$priority).
+% '">'
+% if $num;
+%
+
+
+ <TR>
+ <TD ALIGN=right>
+ <FONT SIZE=-1><% $ahref.$num %></A></FONT>
+ </TD>
+ <TD ALIGN=left>
+ <FONT SIZE=-1><% $ahref %><% $priority || '<i>(none)</i>' %></A></FONT>
+ </TD>
+ </TR>
+% }
+
+
+ <TR>
+ <TH ALIGN=right STYLE="border-top: dashed 1px black">
+ <FONT SIZE=-1>
+% } else {
+
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1>
+% }
+%
+% my $ahref = '';
+% $ahref = '<A HREF="'.
+% FS::TicketSystem->href_customer_tickets($custnum).
+% '">'
+% if $cust_main->get($addl_col);
+%
+
+
+ <% $ahref %><% $cust_main->get($addl_col) %></A>
+% if ( @custom_priorities ) {
+
+
+ </FONT></TH>
+ <TH ALIGN=left STYLE="border-top: dashed 1px black">
+ <FONT SIZE=-1><% ${ahref} %>Total</A><FONT>
+ </TH>
+ </TR>
+ </TABLE>
+% }
+
+
+ </FONT></TD>
+% } else {
+
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1>
+ <% $cust_main->get($addl_col) %>
+ </FONT></TD>
+%
+% }
+% }
+%
+% my($n1)='';
+% foreach ( @{$all_pkgs{$custnum}} ) {
+% my $pkgnum = $_->pkgnum;
+%# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } );
+% my $part_pkg = $_->part_pkg;
+%
+% my $pkg_comment = $part_pkg->pkg_comment(nopkgpart => 1);
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+% my $pkgview = "${p}view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag";
+% 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 CLASS="grid" BGCOLOR="$bgcolor" 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 CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !.
+% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !;
+% $n2="</TR><TR>";
+% }
+%
+% unless ( @cust_svc ) {
+% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=2>&nbsp;</TD>!;
+% }
+%
+% #print qq!</TR><TR>\n!;
+% $n1="</TR><TR>";
+% }
+%
+% unless ( @{$all_pkgs{$custnum}} ) {
+% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=3>&nbsp;</TD>!;
+% }
+%
+% print "</TR>";
+% }
+%
+%
+
+
+ </TABLE><% $pager %>
+
+ <% include('/elements/footer.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 display_custnum_sort {
+% $a->display_custnum <=> $b->display_custnum;
+%}
+%
+%sub custnum_sort {
+% $a->getfield('custnum') <=> $b->getfield('custnum');
+%}
+%
+%sub tickets_sort {
+% $b->getfield('tickets') <=> $a->getfield('tickets');
+%}
+%
+%sub custnumsearch {
+%
+% my $custnum = $cgi->param('custnum_text');
+% $custnum =~ s/\D//g;
+% $custnum =~ /^(\d{1,23})$/ or errorpage("Illegal customer number");
+% $custnum = $1;
+%
+% [ qsearchs('cust_main', { 'custnum' => $custnum } ) ];
+%}
+%
+%sub cardsearch {
+%
+% my($card)=$cgi->param('card');
+% $card =~ s/\D//g;
+% $card =~ /^(\d{13,16})$/ or errorpage("Illegal card number");
+% 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 errorpage("Illegal referral_custnum");
+% my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } )
+% or errorpage("Customer $1 not found");
+% my $depth;
+% if ( $cgi->param('referral_depth') ) {
+% $cgi->param('referral_depth') =~ /^(\d+)$/
+% or errorpage("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 errorpage("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 errorpage("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 errorpage("Illegal address2");
+% my $address2 = $1;
+%
+% push @cust_main, qsearch( 'cust_main',
+% { 'address2' => { 'op' => 'ILIKE',
+% 'value' => $address2 } } );
+% push @cust_main, qsearch( 'cust_main',
+% { 'ship_address2' => { 'op' => 'ILIKE',
+% 'value' => $address2 } } );
+%
+% \@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 {
+% errorpage(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..270fc38
--- /dev/null
+++ b/httemplate/search/cust_main.html
@@ -0,0 +1,111 @@
+<% include( 'elements/search.html',
+ 'title' => 'Customer Search Results',
+ 'menubar' => $menubar,
+ 'name' => 'customers',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'header' => [ FS::UI::Web::cust_header(
+ $cgi->param('cust_fields')
+ ),
+ @extra_headers,
+ ],
+ 'fields' => [
+ \&FS::UI::Web::cust_fields,
+ @extra_fields,
+ ],
+ 'color' => [ FS::UI::Web::cust_colors(),
+ map '', @extra_fields
+ ],
+ 'style' => [ FS::UI::Web::cust_styles(),
+ map '', @extra_fields
+ ],
+ 'align' => [ FS::UI::Web::cust_aligns(),
+ map '', @extra_fields
+ ],
+ 'links' => [ ( map { $_ ne 'Cust. Status' ? $link : '' }
+ FS::UI::Web::cust_header(
+ $cgi->param('cust_fields')
+ )
+ ),
+ map '', @extra_fields
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') &&
+ $FS::CurrentUser::CurrentUser->access_right('List packages')
+ );
+
+my %search_hash = ();
+
+#$search_hash{'query'} = $cgi->keywords;
+
+#scalars
+my @scalars = qw (
+ agentnum status cancelled_pkgs cust_fields flattened_pkgs custbatch usernum
+ no_censustract paydate_year paydate_month invoice_terms
+);
+
+for my $param ( @scalars ) {
+ $search_hash{$param} = scalar( $cgi->param($param) )
+ if $cgi->param($param);
+}
+
+#lists
+for my $param (qw( classnum payby )) {
+ $search_hash{$param} = [ $cgi->param($param) ];
+}
+
+###
+# parse dates
+###
+
+foreach my $field (qw( signupdate )) {
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295 && !defined($cgi->param('signuphour'));
+ #or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending, $cgi->param('signuphour') ];
+
+}
+
+##
+# amounts
+##
+
+$search_hash{'current_balance'} =
+ [ FS::UI::Web::parse_lt_gt($cgi, 'current_balance') ];
+
+###
+# etc
+###
+
+my $sql_query = FS::cust_main->search(\%search_hash);
+my $count_query = delete($sql_query->{'count_query'});
+my @extra_headers = @{ delete($sql_query->{'extra_headers'}) };
+my @extra_fields = @{ delete($sql_query->{'extra_fields'}) };
+
+my $link = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+###
+# email links
+###
+
+my $menubar = [];
+
+if ( $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices') ) {
+
+ my $uri = new URI;
+ $uri->query_form( \%search_hash );
+ my $query = $uri->query;
+
+ push @$menubar, 'Email a notice to these customers' =>
+ "${p}misc/email-customers.html?$query",
+
+}
+
+</%init>
diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi
new file mode 100755
index 0000000..65bd39e
--- /dev/null
+++ b/httemplate/search/cust_pay.cgi
@@ -0,0 +1,7 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'payment',
+ 'name_verb' => 'paid',
+ )
+%>
diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi
new file mode 100755
index 0000000..7376e9d
--- /dev/null
+++ b/httemplate/search/cust_pay_batch.cgi
@@ -0,0 +1,200 @@
+<% include('elements/search.html',
+ 'title' => 'Batch payment details',
+ 'name' => 'batch details',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'html_init' => $pay_batch ? $html_init : '',
+ 'header' => [ '#',
+ 'Inv #',
+ 'Customer',
+ 'Customer',
+ 'Card Name',
+ 'Card',
+ 'Exp',
+ 'Amount',
+ 'Status',
+ ],
+ 'fields' => [ sub {
+ shift->[0];
+ },
+ sub {
+ shift->[1];
+ },
+ sub {
+ shift->[2];
+ },
+ sub {
+ my $cpb = shift;
+ $cpb->[3] . ', ' . $cpb->[4];
+ },
+ sub {
+ shift->[5];
+ },
+ sub {
+ my $cardnum = shift->[6];
+ 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
+ },
+ sub {
+ shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my( $mon, $year ) = ( $2, $1 );
+ $mon = "0$mon" if length($mon) == 1;
+ "$mon/$year";
+ },
+ sub {
+ shift->[8];
+ },
+ sub {
+ shift->[9];
+ },
+ ],
+ 'align' => 'lllllllrl',
+ 'links' => [ ['', sub{'#';}],
+ ["${p}view/cust_bill.cgi?", sub{shift->[1];},],
+ ["${p}view/cust_main.cgi?", sub{shift->[2];},],
+ ["${p}view/cust_main.cgi?", sub{shift->[2];},],
+ ],
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports')
+ || $FS::CurrentUser::CurrentUser->access_right('Process batches')
+ || ( $cgi->param('custnum')
+ && ( $conf->exists('batch-enable')
+ || $conf->config('batch-enable_payby')
+ )
+ #&& $FS::CurrentUser::CurrentUser->access_right('View customer batched payments')
+ );
+
+my( $count_query, $sql_query );
+my $hashref = {};
+my @search = ();
+my $orderby = 'paybatchnum';
+
+my( $pay_batch, $batchnum ) = ( '', '');
+if ( $cgi->param('batchnum') && $cgi->param('batchnum') =~ /^(\d+)$/ ) {
+ push @search, "batchnum = $1";
+ $pay_batch = qsearchs('pay_batch', { 'batchnum' => $1 } );
+ die "Batch $1 not found!" unless $pay_batch;
+ $batchnum = $pay_batch->batchnum;
+}
+
+if ( $cgi->param('custnum') && $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ push @search, "custnum = $1";
+}
+
+if ( $cgi->param('status') && $cgi->param('status') =~ /^(\w)$/ ) {
+ push @search, "pay_batch.status = '$1'";
+}
+
+if ( $cgi->param('payby') ) {
+ $cgi->param('payby') =~ /^(CARD|CHEK)$/
+ or die "illegal payby " . $cgi->param('payby');
+
+ push @search, "cust_pay_batch.payby = '$1'";
+}
+
+if ( not $cgi->param('dcln') ) {
+ push @search, "cpb.status IS DISTINCT FROM 'Approved'";
+}
+
+my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+unless ($pay_batch){
+ push @search, "pay_batch.upload >= $beginning" if ($beginning);
+ push @search, "pay_batch.upload <= $ending" if ($ending < 4294967295);#2^32-1
+ $orderby = "pay_batch.download,paybatchnum";
+}
+
+push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+my $search = ' WHERE ' . join(' AND ', @search);
+
+$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' .
+ 'LEFT JOIN cust_main USING ( custnum ) ' .
+ 'LEFT JOIN pay_batch USING ( batchnum )' .
+ $search;
+
+#grr
+$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," .
+ "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " .
+ "FROM cust_pay_batch AS cpb " .
+ 'LEFT JOIN cust_main USING ( custnum ) ' .
+ 'LEFT JOIN pay_batch USING ( batchnum ) ' .
+ "$search ORDER BY $orderby";
+
+my $html_init = '';
+if ( $pay_batch ) {
+ my $fixed = $conf->config('batch-fixed_format-'. $pay_batch->payby);
+ if (
+ $pay_batch->status eq 'O'
+ || ( $pay_batch->status eq 'I'
+ && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches')
+ )
+ || ( $pay_batch->status eq 'R'
+ && $FS::CurrentUser::CurrentUser->access_right('Redownload resolved batches')
+ )
+ ) {
+ $html_init .= qq!<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">!;
+ if ( $fixed ) {
+ $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!;
+ } else {
+ $html_init .= qq!Download batch in format <SELECT NAME="format">!.
+ qq!<OPTION VALUE="">Default batch mode</OPTION>!.
+ qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>!.
+ qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!.
+ qq!<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>!.
+ qq!<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>!.
+ qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
+ qq!<OPTION VALUE="paymentech">Chase Paymentech XML</OPTION>!.
+ qq!<OPTION VALUE="RBC">Royal Bank of Canada PDS</OPTION>!.
+ qq!</SELECT>!;
+ }
+ $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR>!;
+ }
+
+ if (
+ $pay_batch->status eq 'I'
+ || ( $pay_batch->status eq 'R'
+ && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches')
+ )
+ ) {
+ $html_init .= qq!<FORM ACTION="$p/misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">!.
+ qq!Upload results<BR>!.
+ qq!Filename <INPUT TYPE="file" NAME="batch_results"><BR>!;
+ if ( $fixed ) {
+ $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!;
+ } else {
+ $html_init .= qq!Format <SELECT NAME="format">!.
+ qq!<OPTION VALUE="">Default batch mode</OPTION>!.
+ qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>!.
+ qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!.
+ qq!<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>!.
+ qq!<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>!.
+ qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
+ qq!<OPTION VALUE="paymentech">Chase Paymentech XML</OPTION>!.
+ qq!<OPTION VALUE="RBC">Royal Bank of Canada PDS</OPTION>!.
+ qq!</SELECT><BR>!;
+ }
+ $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum">!;
+ $html_init .= '<INPUT TYPE="submit" VALUE="Upload"></FORM><BR>';
+ }
+
+}
+
+if ($pay_batch) {
+ my $sth = dbh->prepare($count_query) or die dbh->errstr. "doing $count_query";
+ $sth->execute or die "Error executing \"$count_query\": ". $sth->errstr;
+ my $cards = $sth->fetchrow_arrayref->[0];
+
+ my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". $batchnum;
+ $sth = dbh->prepare($st) or die dbh->errstr. "doing $st";
+ $sth->execute or die "Error executing \"$st\": ". $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+
+ $html_init .= "$cards credit card payments batched<BR>\$" .
+ sprintf("%.2f", $total) ." total in batch<BR>";
+}
+
+</%init>
diff --git a/httemplate/search/cust_pay_pending.html b/httemplate/search/cust_pay_pending.html
new file mode 100755
index 0000000..f46e08a
--- /dev/null
+++ b/httemplate/search/cust_pay_pending.html
@@ -0,0 +1,57 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay_pending',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'pending payment',
+ 'name_verb' => 'pending',
+ 'disable_link' => 1,
+ 'disable_by' => 1, #add otaker to cust_pay_pending?
+ 'html_init' => include('/elements/init_overlib.html'),
+ 'addl_header' => [ 'Time', 'Payment Status', ],
+ 'addl_fields' => [ sub { time2str('%r', shift->_date ) },
+ $status_sub,
+ ],
+ 'redirect_empty' => $redirect_empty,
+ )
+%>
+<%init>
+
+my %statusaction = (
+ 'new' => 'delete',
+ 'pending' => 'complete',
+ #'authorized' => '',
+ #'captured' => '',
+ #'declined' => '',
+ #wouldn't need to take action on a done state#'done'
+);
+
+my $edit_pending =
+ $FS::CurrentUser::CurrentUser->access_right('Edit customer pending payments');
+
+my $status_sub = sub {
+ my $pending = shift;
+ my $return = $pending->status;
+ my $action = $statusaction{$pending->status};
+ return $return unless $action && $edit_pending;
+ my $link = include('/elements/popup_link.html',
+ 'action' => $p. 'edit/cust_pay_pending.html'.
+ '?paypendingnum='. $pending->paypendingnum.
+ ";action=$action",
+ 'label' => $action,
+ 'color' => '#ff0000',
+ 'width' => 655,
+ 'height' => ( $action eq 'delete' ? 480 : 575 ),
+ 'actionlabel' => ucfirst($action). ' pending payment',
+ );
+ $return. qq! <FONT SIZE="-1">($link)</FONT>!;
+};
+
+my $redirect_empty = sub {
+ my $cgi = shift;
+ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $p. "view/cust_main.cgi?$1";
+ } else {
+ '';
+ }
+};
+
+</%init>
diff --git a/httemplate/search/cust_pay_void.html b/httemplate/search/cust_pay_void.html
new file mode 100755
index 0000000..431bb2c
--- /dev/null
+++ b/httemplate/search/cust_pay_void.html
@@ -0,0 +1,13 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay_void',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'voided payment',
+ 'name_verb' => 'voided', # 'paid',
+ 'disable_by' => 1, #showing original not voiding otaker
+ 'addl_header' => [ 'Void Date', ], # 'Void Reason' ],
+ 'addl_fields' => [
+ sub { time2str('%b %d %Y', shift->void_date ) },
+ #'reason',
+ ],
+ )
+%>
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
new file mode 100755
index 0000000..adbec7a
--- /dev/null
+++ b/httemplate/search/cust_pkg.cgi
@@ -0,0 +1,283 @@
+<% include( 'elements/search.html',
+ 'html_init' => $html_init,
+ 'title' => 'Package Search Results',
+ 'name' => 'packages',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ #'redirect' => $link,
+ 'header' => [ '#',
+ 'Quan.',
+ 'Package',
+ 'Class',
+ 'Status',
+ 'Setup',
+ 'Base Recur',
+ 'Freq.',
+ 'Setup',
+ 'Last bill',
+ 'Next bill',
+ 'Adjourn',
+ 'Susp.',
+ 'Expire',
+ 'Cancel',
+ 'Reason',
+ FS::UI::Web::cust_header(
+ $cgi->param('cust_fields')
+ ),
+ 'Services',
+ ],
+ 'fields' => [
+ 'pkgnum',
+ 'quantity',
+ sub { #my $part_pkg = $part_pkg{shift->pkgpart};
+ #$part_pkg->pkg; # ' - '. $part_pkg->comment;
+ $_[0]->pkg; # ' - '. $_[0]->comment;
+ },
+ 'classname',
+ sub { ucfirst(shift->status); },
+ sub { sprintf( $money_char.'%.2f',
+ shift->part_pkg->option('setup_fee'),
+ );
+ },
+ sub { my $c = shift;
+ sprintf( $money_char.'%.2f',
+ $c->part_pkg->base_recur($c)
+ );
+ },
+ sub { #shift->part_pkg->freq_pretty;
+
+ #my $part_pkg = $part_pkg{shift->pkgpart};
+ #$part_pkg->freq_pretty;
+
+ FS::part_pkg::freq_pretty(shift);
+ },
+
+ #sub { time2str('%b %d %Y', shift->setup); },
+ #sub { time2str('%b %d %Y', shift->last_bill); },
+ #sub { time2str('%b %d %Y', shift->bill); },
+ #sub { time2str('%b %d %Y', shift->susp); },
+ #sub { time2str('%b %d %Y', shift->expire); },
+ #sub { time2str('%b %d %Y', shift->get('cancel')); },
+ ( map { time_or_blank($_) }
+ qw( setup last_bill bill adjourn susp expire cancel ) ),
+
+ sub { my $self = shift;
+ my $return = '';
+ foreach my $action ( qw ( cancel susp ) ) {
+ my $reason = $self->last_reason($action);
+ $return = $reason->reason if $reason;
+ last if $return;
+ }
+ $return;
+ },
+
+ \&FS::UI::Web::cust_fields,
+ #sub { '<table border=0 cellspacing=0 cellpadding=0 STYLE="border:none">'.
+ # join('', map { '<tr><td align="right" style="border:none">'. $_->[0].
+ # ':</td><td style="border:none">'. $_->[1]. '</td></tr>' }
+ # shift->labels
+ # ).
+ # '</table>';
+ # },
+ sub {
+ [ map {
+ [
+ { 'data' => $_->[0]. ':',
+ 'align'=> 'right',
+ },
+ { 'data' => $_->[1],
+ 'align'=> 'left',
+ 'link' => $p. 'view/' .
+ $_->[2]. '.cgi?'. $_->[3],
+ },
+ ];
+ } shift->labels
+ ];
+ },
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ sub { shift->statuscolor; },
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ '',
+ ],
+ 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '',
+ FS::UI::Web::cust_styles() ],
+ 'size' => [ '', '', '', '', '-1' ],
+ 'align' => 'rrlccrrlrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
+ 'links' => [
+ $link,
+ $link,
+ $link,
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header(
+ $cgi->param('cust_fields')
+ )
+ ),
+ '',
+ ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {});
+
+my %search_hash = ();
+
+#some false laziness w/misc/bulk_change_pkg.cgi
+
+$search_hash{'query'} = $cgi->keywords;
+
+for (qw( agentnum custnum magic status classnum custom cust_fields )) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+$search_hash{'pkgpart'} = [ $cgi->param('pkgpart') ];
+
+for my $param ( qw(censustract) ) {
+ $search_hash{$param} = $cgi->param($param) || ''
+ if ( grep { /$param/ } $cgi->param );
+}
+
+my @report_option = $cgi->param('report_option')
+ if $cgi->param('report_option');
+$search_hash{report_option} = join(',', @report_option) if @report_option;
+
+###
+# parse dates
+###
+
+#false laziness w/report_cust_pkg.html
+my %disable = (
+ 'all' => {},
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+);
+
+foreach my $field (qw( setup last_bill bill adjourn susp expire cancel active )) {
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295
+ or $disable{$cgi->param('status')}->{$field};
+
+ $search_hash{$field} = [ $beginning, $ending ];
+
+}
+
+my $sql_query = FS::cust_pkg->search(\%search_hash);
+my $count_query = delete($sql_query->{'count_query'});
+
+my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+
+my $link = sub {
+ my $self = shift;
+ my $frag = 'cust_pkg'. $self->pkgnum; #hack for IE ignoring real #fragment
+ [ "${p}view/cust_main.cgi?custnum=".$self->custnum.
+ "$show;fragment=$frag#cust_pkg",
+ 'pkgnum'
+ ];
+};
+
+my $clink = sub {
+ my $cust_pkg = shift;
+ $cust_pkg->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+};
+
+#if ( scalar(@cust_pkg) == 1 ) {
+# print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum.
+# "#cust_pkg". $cust_pkg[0]->pkgnum );
+
+# my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } );
+# my $rowspan = scalar(@cust_svc) || 1;
+
+# 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>";
+# }
+
+sub time_or_blank {
+ my $column = shift;
+ return sub {
+ my $record = shift;
+ my $value = $record->get($column); #mmm closures
+ $value ? time2str('%b %d %Y', $value ) : '';
+ };
+}
+
+my $html_init = sub {
+ my $query = shift;
+ my $text = '';
+ my $curuser = $FS::CurrentUser::CurrentUser;
+
+ if ( $curuser->access_right('Bulk change customer packages') ) {
+ $text .= include('/elements/init_overlib.html').
+ include( '/elements/popup_link.html',
+ 'label' => 'Change these packages',
+ 'action' => "${p}misc/bulk_change_pkg.cgi?$query",
+ 'actionlabel' => 'Change Packages',
+ 'width' => 569,
+ 'height' => 210,
+ ). '<BR>';
+
+ if ( $curuser->access_right('Edit customer package dates') ) {
+ $text .= include( '/elements/popup_link.html',
+ 'label' => 'Increment next bill date',
+ 'action' => "${p}misc/bulk_pkg_increment_bill.cgi?$query",
+ 'actionlabel' => 'Increment Bill Date',
+ 'width' => 569,
+ 'height' => 210,
+ ). '<BR>';
+ }
+ }
+ return $text;
+};
+
+</%init>
diff --git a/httemplate/search/cust_pkg_discount.html b/httemplate/search/cust_pkg_discount.html
new file mode 100644
index 0000000..233345e
--- /dev/null
+++ b/httemplate/search/cust_pkg_discount.html
@@ -0,0 +1,122 @@
+<% include( 'elements/search.html',
+ 'title' => 'Package discounts',
+ 'name' => 'discounts',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ #'redirect' => $link,
+ 'header' => [ 'Status',
+ 'Discount',
+ 'Months used',
+ 'Employee',
+ 'Package',
+ FS::UI::Web::cust_header(
+ # $cgi->param('cust_fields')
+ ),
+ ],
+ 'fields' => [
+ sub { ucfirst( shift->status ) },
+ sub { shift->discount->description },
+ sub { my $m = shift->months_used;
+ $m =~ /\./ ? sprintf('%.2f',$m) : $m;
+ },
+ 'otaker',
+ 'pkg',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ ( map { $_ ne 'Cust. Status' ? $clink : ''}
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'clrll'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#my $conf = new FS::Conf;
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+#status
+if ( $cgi->param('status') eq 'active' ) {
+ push @where, " ( cust_pkg_discount.disabled IS NULL
+ OR cust_pkg_discount.disabled != 'Y' )
+ AND ( months IS NULL OR months_used < months ) ";
+ #XXX also end date
+} elsif ( $cgi->param('status') eq 'expired' ) {
+ push @where, " ( cust_pkg_discount.disabled IS NOT NULL
+ AND cust_pkg_discount.disabled = 'Y' )
+ OR ( months IS NOT NULL AND months_used >= months )
+ "; #XXX also end date
+}
+
+#otaker
+if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) {
+ push @where, "cust_pkg_discount.otaker = '$1'";
+}
+
+#agent
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+my $count_query = "SELECT COUNT(*), SUM(amount)";
+
+my $join = ' LEFT JOIN discount USING ( discountnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN cust_main USING ( custnum ) ';
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+$count_query .= " FROM cust_pkg_discount $join $where";
+
+my @select = (
+ 'cust_pkg_discount.*',
+ 'part_pkg.pkg',
+ );
+push @select, 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields();
+
+my $query = {
+ 'table' => 'cust_pkg_discount',
+ 'addl_from' => $join,
+ 'hashref' => {},
+ 'select' => join(', ', @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY pkgdiscountnum',
+};
+
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/search/cust_refund.html b/httemplate/search/cust_refund.html
new file mode 100644
index 0000000..e31e088
--- /dev/null
+++ b/httemplate/search/cust_refund.html
@@ -0,0 +1,7 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'refund',
+ 'amount_field' => 'refund',
+ 'name_singular' => 'refund',
+ 'name_verb' => 'refunded',
+ )
+%>
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
new file mode 100644
index 0000000..2c17561
--- /dev/null
+++ b/httemplate/search/cust_svc.html
@@ -0,0 +1,140 @@
+<% include( 'elements/search.html',
+ 'title' => 'Service search results',
+ 'name' => 'services',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => [ '#',
+ 'Service',
+ # package?
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ sub {
+ #$_[0]->svc. ': '. $_[0]->label;
+ my($label, $value, $svcdb) = $_[0]->label;
+ "$label: $value";
+ },
+ # package?
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ # package?
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rl'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my @extra_sql = ();
+my $orderby = 'ORDER BY svcnum'; #has to be ordered by something
+ #for pagination to work
+if ( length( $cgi->param('search_svc') ) ) {
+
+ my $string = $cgi->param('search_svc');
+ $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace
+
+ # implement fuzzy searching in subclasses too at some point?
+ # service searching maybe shouldn't be fuzzy...
+
+ push @extra_sql,
+ ' ( '. join(' OR ',
+ map { my $table = $_;
+ my $search_sql = "FS::$table"->search_sql($string);
+ " ( svcdb = '$table'
+ AND 0 < ( SELECT COUNT(*) FROM $table
+ WHERE $table.svcnum = cust_svc.svcnum
+ AND $search_sql
+ )
+ ) ";
+ }
+ FS::part_svc->svc_tables
+ ). ' ) ';
+
+} elsif ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb";
+ push @extra_sql, "svcdb = '$1'";
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+
+ push @extra_sql, "svcpart = $1";
+
+} else {
+ errorpage("No search term specified");
+}
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
+
+my $sql_query = {
+ 'select' => join(', ',
+ 'cust_svc.*',
+ 'part_svc.*',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => "$extra_sql $orderby",
+};
+
+my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
+
+my $link = sub {
+ my $cust_svc = shift;
+ my $url = svc_url(
+ 'm' => $m,
+ 'action' => 'view',
+ #'part_svc' => $cust_svc->part_svc,
+ 'svcdb' => $cust_svc->svcdb, #we have it from the joined search
+ #'svc' => $cust_svc, #redundant
+ 'query' => '',
+ );
+ [ $url, 'svcnum' ];
+};
+
+my $link_cust = sub {
+ my $cust_svc = shift;
+ if ( $cust_svc->custnum ) {
+ [ "${p}view/cust_main.cgi?", 'custnum' ];
+ } else {
+ '';
+ }
+};
+
+</%init>
diff --git a/httemplate/search/cust_tax_adjustment.html b/httemplate/search/cust_tax_adjustment.html
new file mode 100644
index 0000000..9254765
--- /dev/null
+++ b/httemplate/search/cust_tax_adjustment.html
@@ -0,0 +1,54 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name_singular' => 'tax adjustment',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ 'Tax', 'Amount', 'Comment', 'Invoice' ],
+ 'fields' => [ 'taxname',
+ sub { $money_char. shift->amount },
+ 'comment',
+ sub { my $l = shift->cust_bill_pkg;
+ $l ? '#'.$l->invnum : '';
+ },
+ ],
+ 'links' => [ '', '', '', $ilink ],
+ )
+%>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $count_query = 'SELECT COUNT(*) FROM cust_tax_adjustment';
+
+my $hashref = {};
+
+my $custnum = '';
+my $cust_main = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ $hashref->{'custnum'} = $custnum;
+ $count_query .= " WHERE custnum = $custnum ";
+}
+
+my $title = 'Tax adjustments';
+$title .= ' for '. $cust_main->name if $cust_main;
+
+my $query = { 'table' => 'cust_tax_adjustment',
+ 'hashref' => $hashref,
+ };
+
+my $ilink = [ $p.'view/cust_bill.cgi?', sub { my $l = shift->cust_bill_pkg;
+ $l ? $l->invnum : 'EXCEPTION';
+ }
+ ];
+
+#XXX would be nice to list customer fields on the report too, if we ever need
+# to link to here without a custnum (i'm sure we will, eventually...)
+
+</%init>
diff --git a/httemplate/search/cust_tax_exempt.cgi b/httemplate/search/cust_tax_exempt.cgi
new file mode 100644
index 0000000..3704b20
--- /dev/null
+++ b/httemplate/search/cust_tax_exempt.cgi
@@ -0,0 +1,139 @@
+<% include( 'elements/search.html',
+ 'title' => 'Legacy tax exemptions',
+ 'name' => 'legacy tax exemptions',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total', ],
+ 'header' => [
+ '#',
+ 'Month',
+ 'Inserted',
+ 'Amount',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ 'exemptnum',
+ sub { $_[0]->month. '/'. $_[0]->year; },
+ sub { my $h = $_[0]->h_search('insert');
+ $h ? time2str('%L/%d/%Y', $h->history_date ) : ''
+ },
+ sub { $money_char. $_[0]->amount; },
+
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rrrr'.FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+my $join_cust = "
+ LEFT JOIN cust_main USING ( custnum )
+";
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
+
+my @where = ();
+
+#my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+#if ( $beginning || $ending ) {
+# push @where, "_date >= $beginning",
+# "_date <= $ending";
+# #"payby != 'COMP';
+#}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "agentnum = $1";
+}
+
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.custnum = $1";
+}
+
+#prospect active inactive suspended cancelled
+if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) {
+ my $method = $cgi->param('status'). '_sql';
+ #push @where, $class->$method();
+ push @where, FS::cust_main->$method();
+}
+
+if ( $cgi->param('out') ) {
+
+ push @where, "
+ 0 = (
+ SELECT COUNT(*) FROM cust_main_county AS county_out
+ WHERE ( county_out.county = cust_main.county
+ OR ( county_out.county IS NULL AND cust_main.county = '' )
+ OR ( county_out.county = '' AND cust_main.county IS NULL)
+ OR ( county_out.county IS NULL AND cust_main.county IS NULL)
+ )
+ AND ( county_out.state = cust_main.state
+ OR ( county_out.state IS NULL AND cust_main.state = '' )
+ OR ( county_out.state = '' AND cust_main.state IS NULL )
+ OR ( county_out.state IS NULL AND cust_main.state IS NULL )
+ )
+ AND county_out.country = cust_main.country
+ AND county_out.tax > 0
+ )
+ ";
+
+} elsif ( $cgi->param('country' ) ) {
+
+ my $county = dbh->quote( $cgi->param('county') );
+ my $state = dbh->quote( $cgi->param('state') );
+ my $country = dbh->quote( $cgi->param('country') );
+ push @where, "( county = $county OR $county = '' )",
+ "( state = $state OR $state = '' )",
+ " country = $country";
+ push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') )
+ if $cgi->param('taxclass');
+
+}
+
+my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : '';
+
+my $count_query = "SELECT COUNT(*), SUM(amount)".
+ " FROM cust_tax_exempt $join_cust $where";
+
+my $query = {
+ 'table' => 'cust_tax_exempt',
+ 'addl_from' => $join_cust,
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'cust_tax_exempt.*',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $where,
+};
+
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/search/cust_tax_exempt.html b/httemplate/search/cust_tax_exempt.html
new file mode 100644
index 0000000..869854f
--- /dev/null
+++ b/httemplate/search/cust_tax_exempt.html
@@ -0,0 +1,31 @@
+<% include('/elements/header.html', 'Legacy tax exemption report' ) %>
+
+<FORM ACTION="cust_tax_exempt.cgi" METHOD="GET">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Customer Status'
+ )
+ %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
+
+</%init>
+
diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi
new file mode 100644
index 0000000..3a5155a
--- /dev/null
+++ b/httemplate/search/cust_tax_exempt_pkg.cgi
@@ -0,0 +1,182 @@
+<% include( 'elements/search.html',
+ 'title' => 'Tax exemptions',
+ 'name' => 'tax exemptions',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total', ],
+ 'header' => [
+ '#',
+ 'Month',
+ 'Amount',
+ 'Line item',
+ 'Invoice',
+ 'Date',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ 'exemptpkgnum',
+ sub { $_[0]->month. '/'. $_[0]->year; },
+ sub { $money_char. $_[0]->amount; },
+
+ sub {
+ $_[0]->billpkgnum. ': '.
+ ( $_[0]->pkgnum > 0
+ ? $_[0]->get('pkg')
+ : $_[0]->get('itemdesc')
+ ).
+ ' ('.
+ ( $_[0]->setup > 0
+ ? $money_char. $_[0]->setup. ' setup'
+ : ''
+ ).
+ ( $_[0]->setup > 0 && $_[0]->recur > 0
+ ? ' / '
+ : ''
+ ).
+ ( $_[0]->recur > 0
+ ? $money_char. $_[0]->recur. ' recur'
+ : ''
+ ).
+ ')';
+ },
+
+ 'invnum',
+ sub { time2str('%b %d %Y', shift->_date ) },
+
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [
+ '',
+ '',
+ '',
+
+ '',
+ $ilink,
+ $ilink,
+
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rrrlrc'.FS::UI::Web::cust_aligns(), # 'rlrrrc',
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%once>
+
+my $join_cust = "
+ JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+";
+
+my $join_pkg = "
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+";
+
+my $join = "
+ JOIN cust_bill_pkg USING ( billpkgnum )
+ $join_cust
+ $join_pkg
+";
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
+
+my @where = ();
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+if ( $beginning || $ending ) {
+ push @where, "_date >= $beginning",
+ "_date <= $ending";
+ #"payby != 'COMP';
+}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.custnum = $1";
+}
+
+if ( $cgi->param('out') ) {
+
+ push @where, "
+ 0 = (
+ SELECT COUNT(*) FROM cust_main_county AS county_out
+ WHERE ( county_out.county = cust_main.county
+ OR ( county_out.county IS NULL AND cust_main.county = '' )
+ OR ( county_out.county = '' AND cust_main.county IS NULL)
+ OR ( county_out.county IS NULL AND cust_main.county IS NULL)
+ )
+ AND ( county_out.state = cust_main.state
+ OR ( county_out.state IS NULL AND cust_main.state = '' )
+ OR ( county_out.state = '' AND cust_main.state IS NULL )
+ OR ( county_out.state IS NULL AND cust_main.state IS NULL )
+ )
+ AND county_out.country = cust_main.country
+ AND county_out.tax > 0
+ )
+ ";
+
+} elsif ( $cgi->param('country' ) ) {
+
+ my $county = dbh->quote( $cgi->param('county') );
+ my $state = dbh->quote( $cgi->param('state') );
+ my $country = dbh->quote( $cgi->param('country') );
+ push @where, "( county = $county OR $county = '' )",
+ "( state = $state OR $state = '' )",
+ " country = $country";
+ push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') )
+ if $cgi->param('taxclass');
+
+}
+
+my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : '';
+
+my $count_query = "SELECT COUNT(*), SUM(amount)".
+ " FROM cust_tax_exempt_pkg $join $where";
+
+my $query = {
+ 'table' => 'cust_tax_exempt_pkg',
+ 'addl_from' => $join,
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'cust_tax_exempt_pkg.*',
+ 'cust_bill_pkg.*',
+ 'cust_bill.*',
+ 'part_pkg.pkg',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $where,
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html
new file mode 100644
index 0000000..c53e680
--- /dev/null
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -0,0 +1,268 @@
+<%doc>
+
+Example:
+
+ include( 'elements/cust_main_dayranges.html',
+ 'title' => 'Accounts Receivable Aging Summary',
+ 'range_sub' => $mysub,
+ )
+
+ my $mysub = sub {
+ my( $start, $end ) = @_;
+
+ "SQL EXPRESSION BASED ON $start AND $end";
+ };
+
+</%doc>
+<% include( 'search.html',
+ 'name' => 'customers',
+ 'query' => $sql_query,
+ 'count_query' => $count_sql,
+ 'header' => [
+ FS::UI::Web::cust_header(),
+ '0-30',
+ '30-60',
+ '60-90',
+ '90+',
+ 'Total',
+ @pay_head,
+ ],
+ 'footer' => [
+ 'Total',
+ ( map '',
+ ( 1 ..
+ scalar(FS::UI::Web::cust_header()-1)
+ ),
+ ),
+
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_0_30'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_30_60'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_60_90'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_90_0'} ),
+ sprintf( '<b>'. $money_char.'%.2f'. '</b>',
+ $row->{'rangecol_0_0'} ),
+ ('') x @pay_labels,
+ ],
+ 'fields' => [
+ FS::UI::Web::cust_fields_subs(),
+ format_rangecol('0_30'),
+ format_rangecol('30_60'),
+ format_rangecol('60_90'),
+ format_rangecol('90_0'),
+ format_rangecol('0_0'),
+ @pay_labels,
+ ],
+ 'links' => [
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ '',
+ '',
+ '',
+ '',
+ '',
+ @pay_links,
+ ],
+ #'align' => 'rlccrrrrr',
+ 'align' => FS::UI::Web::cust_aligns().
+ 'rrrrr'.
+ ('c' x @pay_labels),
+ #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ],
+ #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ],
+ 'size' => [ ( map '', FS::UI::Web::cust_header() ),
+ #'-1', '', '', '', '', '', ],
+ '', '', '', '', '', '',
+ ( map '', @pay_labels ),
+ ],
+ 'style' => [ FS::UI::Web::cust_styles(),
+ #'b', '', '', '', '', 'b', ],
+ '', '', '', '', 'b',
+ ( map '', @pay_labels ),
+ ],
+ 'color' => [
+ FS::UI::Web::cust_colors(),
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ( map '', @pay_labels ),
+ ],
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+#actually need to auto-generate other things too for a passed-in ranges to work
+my $ranges = $opt{'ranges'} ? delete($opt{'ranges'}) : [
+ [ 0, 30 ],
+ [ 30, 60 ],
+ [ 60, 90 ],
+ [ 90, 0 ],
+ [ 0, 0 ],
+];
+
+my $range_sub = delete($opt{'range_sub'}); #or die
+
+my $offset = 0;
+if($cgi->param('as_of')) {
+ $offset = int((time - parse_datetime($cgi->param('as_of'))) / 86400);
+ $opt{'title'} .= ' ('.$cgi->param('as_of').')' if $offset > 0;
+}
+
+#my $range_cols = join(',', map &{$range_sub}( @$_ ), @ranges );
+my $range_cols = join(',', map call_range_sub($range_sub, @$_, 'offset' => $offset ), @$ranges );
+
+my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql;
+
+my $active_sql = FS::cust_pkg->active_sql;
+my $inactive_sql = FS::cust_pkg->inactive_sql;
+my $suspended_sql = FS::cust_pkg->suspended_sql;
+my $cancelled_sql = FS::cust_pkg->cancelled_sql;
+
+my $packages_cols = <<END;
+ ( $select_count_pkgs ) AS num_pkgs_sql,
+ ( $select_count_pkgs AND $active_sql ) AS active_pkgs,
+ ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs,
+ ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs,
+ ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs
+END
+
+my @where = ();
+
+unless ( $cgi->param('all_customers') ) {
+# Exclude entire cust_main records where the balance is >0
+ my $days = 0;
+ if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) {
+ $days = $1;
+ }
+
+ push @where,
+ call_range_sub($range_sub, $days + $offset, 0, 'no_as'=>1). ' > 0'; # != 0';
+}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ my $agentnum = $1;
+ push @where, "agentnum = $agentnum";
+}
+
+#status (false laziness w/cust_main::search_sql
+
+#prospect active inactive suspended cancelled
+if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) {
+ my $method = $cgi->param('status'). '_sql';
+ #push @where, $class->$method();
+ push @where, FS::cust_main->$method();
+}
+
+#here is the agent virtualization
+push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = join(' AND ', @where);
+$where = "WHERE $where" if $where;
+
+my $count_sql = "select count(*) from cust_main $where";
+
+my $sql_query = {
+ 'table' => 'cust_main',
+ 'hashref' => {},
+ 'select' => join(',',
+ #'cust_main.*',
+ 'custnum',
+ $range_cols,
+ $packages_cols,
+ FS::UI::Web::cust_sql_fields(),
+ 'payby',
+ ),
+ 'extra_sql' => $where,
+ 'order_by' => "order by coalesce(lower(company), ''), lower(last)",
+};
+
+my $total_sql =
+ "SELECT ".
+ join(',', map call_range_sub( $range_sub, @$_, 'offset' => $offset, 'sum'=>1 ), @$ranges).
+ " FROM cust_main $where";
+
+my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
+$total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr;
+my $row = $total_sth->fetchrow_hashref();
+
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my (@payby, @pay_head, @pay_labels, @pay_links);
+
+my %payby = map {$_ => 1} $conf->config('payby');
+if(%payby) {
+ push @payby, 'CARD' if ($payby{'CARD'} or $payby{'DCRD'});
+ push @payby, 'CHEK' if ($payby{'CHEK'} or $payby{'DCHK'});
+}
+else {
+ @payby = ('CARD','CHEK')
+}
+
+if($opt{'payment_links'} && $curuser->access_right('Process payment') && @payby) {
+ my %label = ( CARD => 'Card',
+ CHEK => 'E-Check' );
+ push @pay_head, ({nodownload => 1}) foreach @payby;
+ $pay_head[0] = { label => 'Process',
+ nodownload => 1,
+ colspan => scalar(@payby) };
+
+ @pay_labels = (map { my $payby = $_;
+ my $label = $label{$payby};
+ sub {($payby eq $_[0]->payby) ? "<b>$label (on file)</b>" : $label}
+ } @payby );
+
+ @pay_links = (map { [ "${p}misc/payment.cgi?payby=$_;custnum=", 'custnum' ] }
+ @payby );
+}
+
+</%init>
+<%once>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $money_char = $conf->config('money_char') || '$';
+
+#Example:
+#
+# my $balance = balance(
+# $start, $end,
+# 'no_as' => 1, #set to true when using in a WHERE clause (supress AS clause)
+# #or 0 / omit when using in a SELECT clause as a column
+# # ("AS balance_$start_$end")
+# 'sum' => 1, #set to true to get a SUM() of the values, for totals
+#
+# #obsolete? options for totals (passed to cust_main::balance_date_sql)
+# 'total' => 1, #set to true to remove all customer comparison clauses
+# 'join' => $join, #JOIN clause
+# 'where' => \@where, #WHERE clause hashref (elements "AND"ed together)
+# )
+
+sub call_range_sub {
+ my($range_sub, $start, $end, %opt) = @_;
+
+ my $as = $opt{'no_as'} ? '' : " AS rangecol_${start}_$end";
+
+ my $sql = &{$range_sub}( $start, $end, $opt{'offset'} ); #%opt?
+
+ $sql = "SUM($sql)" if $opt{'sum'};
+
+ $sql.$as;
+
+}
+
+sub format_rangecol { #closures help alot
+ my $range = shift;
+ sub { sprintf( $money_char.'%.2f', shift->get("rangecol_$range") ) };
+}
+
+</%once>
diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
new file mode 100755
index 0000000..4f83d0a
--- /dev/null
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -0,0 +1,345 @@
+<%doc>
+
+Examples:
+
+ include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'payment',
+ 'name_verb' => 'paid',
+ )
+
+ include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'refund',
+ 'amount_field' => 'refund',
+ 'name_singular' => 'refund',
+ 'name_verb' => 'refunded',
+ )
+
+ include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay_pending',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'pending payment',
+ 'name_verb' => 'pending',
+ 'disable_link' => 1,
+ 'disable_by' => 1,
+ 'html_init' => '',
+ 'addl_header' => [],
+ 'addl_fields' => [],
+ 'redirect_empty' => $redirect_empty,
+ )
+
+ include( 'elements/cust_pay_or_refund.html',
+ 'table' => 'h_cust_pay',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'payment',
+ 'name_verb' => 'paid',
+ 'pre_header' => [ 'Transaction', 'By' ],
+ 'pre_fields' => [ 'history_action', 'history_user' ],
+ )
+
+</%doc>
+<% include( 'search.html',
+ 'title' => $title,
+ 'name_singular' => $name_singular,
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total '.$opt{name_verb}, ],
+ 'redirect_empty' => $opt{'redirect_empty'},
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'align' => $align,
+ 'links' => \@links,
+ 'color' => \@color,
+ 'style' => \@style,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Financial reports');
+
+my $table = $opt{'table'} || 'cust_'.$opt{'thing'};
+
+my $amount_field = $opt{'amount_field'};
+my $name_singular = $opt{'name_singular'};
+
+my $title = "\u$name_singular Search Results";
+
+my $link = '';
+if ( ( $curuser->access_right('View invoices') #XXX for now
+ || $curuser->access_right('View customer payments')
+ )
+ && ! $opt{'disable_link'}
+ )
+{
+
+ my $key;
+ my $q = '';
+ if ( $table eq 'cust_pay_void' ) {
+ $key = 'paynum';
+ $q .= 'void=1;';
+ } elsif ( $table eq /^cust_(\w+)$/ ) {
+ $key = $1.'num';
+ }
+
+ if ( $key ) {
+ $q .= "$key=";
+ $link = [ "${p}view/$table.html?$q", $key ]
+ }
+}
+
+my $cust_link = sub {
+ my $cust_thing = shift;
+ $cust_thing->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+};
+
+my @header = ();
+my @fields = ();
+my $align = '';
+my @links = ();
+if ( $opt{'pre_header'} ) {
+ push @header, @{ $opt{'pre_header'} };
+ $align .= 'c' x scalar(@{ $opt{'pre_header'} });
+ push @links, map '', @{ $opt{'pre_header'} };
+ push @fields, @{ $opt{'pre_fields'} };
+}
+
+push @header, "\u$name_singular",
+ 'Amount',
+ 'Date',
+;
+$align .= 'rrr';
+push @links, '', '', '';
+push @fields, 'payby_payinfo_pretty',
+ sub { sprintf('$%.2f', shift->$amount_field() ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+;
+
+unless ( $opt{'disable_by'} ) {
+ push @header, 'By';
+ $align .= 'c';
+ push @links, '';
+ push @fields, sub { my $o = shift->otaker;
+ $o = 'auto billing' if $o eq 'fs_daily';
+ $o = 'customer self-service' if $o eq 'fs_selfservice';
+ $o;
+ };
+}
+
+push @header, FS::UI::Web::cust_header();
+$align .= FS::UI::Web::cust_aligns();
+push @links, map { $_ ne 'Cust. Status' ? $cust_link : '' }
+ FS::UI::Web::cust_header();
+my @color = ( ( map '', @fields ), FS::UI::Web::cust_colors() );
+my @style = ( ( map '', @fields ), FS::UI::Web::cust_styles() );
+push @fields, \&FS::UI::Web::cust_fields;
+
+push @header, @{ $opt{'addl_header'} }
+ if $opt{'addl_header'};
+push @fields, @{ $opt{'addl_fields'} }
+ if $opt{'addl_fields'};
+
+my( $count_query, $sql_query );
+if ( $cgi->param('magic') ) {
+
+ my @search = ();
+ my $orderby;
+ if ( $cgi->param('magic') eq '_date' ) {
+
+ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @search, "agentnum = $1"; # $search{'agentnum'} = $1;
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "unknown agentnum $1" unless $agent;
+ $title = $agent->agent. " $title";
+ }
+
+ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ push @search, "custnum = $1";
+ }
+
+ if ( $cgi->param('payby') ) {
+ $cgi->param('payby') =~
+ /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/
+ or die "illegal payby ". $cgi->param('payby');
+ push @search, "$table.payby = '$1'";
+ if ( $3 ) {
+
+ my $cardtype = $3;
+
+ my $search;
+ if ( $cardtype eq 'VisaMC' ) {
+ #avoid posix regexes for portability
+ $search =
+ " ( ( substring($table.payinfo from 1 for 1) = '4' ".
+ " AND substring($table.payinfo from 1 for 4) != '4936' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49030[2-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49033[5-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49110[1-2]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49117[4-9]' ".
+ " AND substring($table.payinfo from 1 for 6) ".
+ " NOT SIMILAR TO '49118[1-2]' ".
+ " )".
+ " OR substring($table.payinfo from 1 for 2) = '51' ".
+ " OR substring($table.payinfo from 1 for 2) = '52' ".
+ " OR substring($table.payinfo from 1 for 2) = '53' ".
+ " OR substring($table.payinfo from 1 for 2) = '54' ".
+ " OR substring($table.payinfo from 1 for 2) = '54' ".
+ " OR substring($table.payinfo from 1 for 2) = '55' ".
+ " OR substring($table.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US
+ " ) ";
+ } elsif ( $cardtype eq 'Amex' ) {
+ $search =
+ " ( substring($table.payinfo from 1 for 2 ) = '34' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '37' ".
+ " ) ";
+ } elsif ( $cardtype eq 'Discover' ) {
+ $search =
+ " ( substring($table.payinfo from 1 for 4 ) = '6011' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '65' ".
+ " OR substring($table.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN
+ " ) ";
+ } elsif ( $cardtype eq 'Maestro' ) {
+ $search =
+ " ( substring($table.payinfo from 1 for 2 ) = '63' ".
+ " OR substring($table.payinfo from 1 for 2 ) = '67' ".
+ " OR substring($table.payinfo from 1 for 6 ) = '564182' ".
+ " OR substring($table.payinfo from 1 for 4 ) = '4936' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49030[2-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49033[5-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49110[1-2]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49117[4-9]' ".
+ " OR substring($table.payinfo from 1 for 6 ) ".
+ " SIMILAR TO '49118[1-2]' ".
+ " ) ";
+ } else {
+ die "unknown card type $cardtype";
+ }
+
+ my $masksearch = $search;
+ $masksearch =~ s/$table\.payinfo/$table.paymask/gi;
+
+ push @search,
+ "( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) )";
+
+ }
+ }
+
+ if ( $cgi->param('payinfo') ) {
+ $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/
+ or die "illegal payinfo ". $cgi->param('payinfo');
+ push @search, "$table.payinfo = '$1'";
+ }
+
+ if ( $cgi->param('otaker') =~ /^(\w+)$/ ) {
+ push @search, "$table.otaker = '$1'";
+ }
+
+ #for cust_pay_pending... statusNOT=done
+ if ( $cgi->param('statusNOT') =~ /^(\w+)$/ ) {
+ push @search, "status != '$1'";
+ }
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+ push @search, "_date >= $beginning ",
+ "_date <= $ending";
+
+ if ( $table eq 'cust_pay_void' ) {
+ my($v_beginning, $v_ending) =
+ FS::UI::Web::parse_beginning_ending($cgi, 'void');
+ push @search, "void_date >= $v_beginning ",
+ "void_date <= $v_ending";
+ }
+
+ push @search, FS::UI::Web::parse_lt_gt($cgi, $amount_field );
+
+ $orderby = '_date';
+
+ } elsif ( $cgi->param('magic') eq 'paybatch' ) {
+
+ $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/
+ or die "illegal paybatch: ". $cgi->param('paybatch');
+
+ push @search, "paybatch = '$1'";
+
+ $orderby = "LOWER(company || ' ' || last || ' ' || first )";
+
+ } else {
+ die "unknown search magic: ". $cgi->param('magic');
+ }
+
+ #for the history search
+ if ( $cgi->param('history_action') =~ /^([\w,]+)$/ ) {
+ my @history_action = split(/,/, $1);
+ push @search, 'history_action IN ('.
+ join(',', map "'$_'", @history_action ). ')';
+ }
+
+ if ( $cgi->param('history_date_beginning')
+ || $cgi->param('history_date_ending') ) {
+ my($h_beginning, $h_ending) =
+ FS::UI::Web::parse_beginning_ending($cgi, 'history_date');
+ push @search, "history_date >= $h_beginning ",
+ "history_date <= $h_ending";
+ }
+
+ #here is the agent virtualization
+ push @search, $curuser->agentnums_sql;
+
+ my $search = ' WHERE '. join(' AND ', @search);
+
+ $count_query = "SELECT COUNT(*), SUM($amount_field) ".
+ "FROM $table LEFT JOIN cust_main USING ( custnum )".
+ $search;
+
+ $sql_query = {
+ 'table' => $table,
+ 'select' => join(', ',
+ "$table.*",
+ 'cust_main.custnum as cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'hashref' => {},
+ 'extra_sql' => "$search ORDER BY $orderby",
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ };
+
+} else {
+
+ #hmm... is this still used?
+
+ $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($amount_field) FROM $table".
+ " WHERE payinfo = '$payinfo' AND payby = '$payby'".
+ " AND ". $curuser->agentnums_sql;
+
+ $sql_query = {
+ 'table' => $table,
+ 'hashref' => { 'payinfo' => $payinfo,
+ 'payby' => $payby },
+ 'extra_sql' => $curuser->agentnums_sql.
+ " ORDER BY _date",
+ };
+
+}
+
+</%init>
diff --git a/httemplate/search/elements/metasearch.html b/httemplate/search/elements/metasearch.html
new file mode 100644
index 0000000..b9d3e3c
--- /dev/null
+++ b/httemplate/search/elements/metasearch.html
@@ -0,0 +1,71 @@
+<%doc>
+
+Example:
+
+ include( 'elements/metasearch.html',
+
+ ###
+ # required
+ ###
+
+ 'title' => 'Page title',
+
+ #arrayref of hashrefs suited for passing to elements/search.html
+ #see that documentation
+ 'search' => [
+ {
+ query => { 'table' => 'tablename',
+ #everything else is optional...
+ 'hashref' => { 'f1' => 'value',
+ 'f2' => { 'op' => '<',
+ 'value' => '54',
+ },
+ },
+ 'select' => '*',
+ 'order_by' => 'ORDER BY something',
+
+ },
+ count_query => 'SELECT COUNT(*) FROM tablename',
+ },
+ {
+ query => 'table' => 'anothertablename',
+ count_query => 'SELECT COUNT(*) FROM anothertablename',
+ },
+ ],
+
+ ###
+ # optional
+ ###
+
+ # some HTML callbacks...
+ 'menubar' => '', #menubar arrayref
+ 'html_init' => '', #after the header/menubar and before the pager
+ 'html_form' => '', #after the pager, right before the results
+ # (only shown if there are results)
+ # (use this for any form-opening tag rather than
+ # html_init, to avoid a nested form)
+ 'html_foot' => '', #at the bottom
+ 'html_posttotal' => '', #at the bottom
+ # (these three can be strings or coderefs)
+
+ );
+
+</%doc>
+% foreach my $search ( @{$opt{search}} ) {
+<% include('search.html',
+ %$search,
+ 'type' => $type,
+ 'nohtmlheader' => 1,
+ )
+%>
+%
+% }
+<%init>
+
+my(%opt) = @_;
+#warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n";
+
+my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|select|html(-print)?)$/
+ ? $1 : 'html' ;
+
+</%init>
diff --git a/httemplate/search/elements/search-csv.html b/httemplate/search/elements/search-csv.html
new file mode 100644
index 0000000..9eb1b66
--- /dev/null
+++ b/httemplate/search/elements/search-csv.html
@@ -0,0 +1,54 @@
+% $csv->combine(@$header); #or die $csv->status;
+%
+<% $opt{no_csv_header} ? '' : $csv->string %>\
+%
+% foreach my $row ( @$rows ) {
+%
+% if ( $opt{'fields'} ) {
+%
+% my @line = ();
+%
+% foreach my $field ( @{$opt{'fields'}} ) {
+% if ( ref($field) eq 'CODE' ) {
+% push @line, map {
+% ref($_) eq 'ARRAY'
+% ? '(N/A)' #unimplemented
+% : $_;
+% }
+% &{$field}($row);
+% } else {
+% push @line, $row->$field();
+% }
+% }
+%
+% $csv->combine(@line); #or die $csv->status;
+%
+% } else {
+% $csv->combine(@$row); #or die $csv->status;
+% }
+%
+%
+<% $csv->string %>\
+%
+% }
+<%init>
+
+my %args = @_;
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my %opt = %{ $args{'opt'} };
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+#http_header('Content-Type' => 'text/plain' );
+http_header('Content-Type' => 'text/csv' ); # So saith RFC 4180
+http_header('Content-Disposition' =>
+ 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.csv"');
+
+my $quote_char = '"';
+$quote_char = $opt{csv_quote} if exists($opt{csv_quote});
+
+my $csv = new Text::CSV_XS { 'always_quote' => $opt{avoid_quote} ? 0 : 1,
+ 'eol' => "\n", #"\015\012", #"\012"
+ };
+
+</%init>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
new file mode 100644
index 0000000..e5e6ca9
--- /dev/null
+++ b/httemplate/search/elements/search-html.html
@@ -0,0 +1,477 @@
+%
+% if ( exists($opt{'redirect'}) && $opt{'redirect'}
+% && scalar(@$rows) == 1 && $total == 1
+% && $type ne 'html-print'
+% ) {
+% my $redirect = $opt{'redirect'};
+% $redirect = &{$redirect}($rows->[0], $cgi) if ref($redirect) eq 'CODE';
+% my( $url, $method ) = @$redirect;
+% redirect( $url. $rows->[0]->$method() );
+% } elsif ( exists($opt{'redirect_empty'}) && ! scalar(@$rows) && $total == 0
+% && $type ne 'html-print'
+% && $opt{'redirect_empty'}
+% && ( ref($opt{'redirect_empty'}) ne 'CODE'
+% || &{$opt{'redirect_empty'}}($cgi) )
+% ) {
+% my $redirect = $opt{'redirect_empty'};
+% $redirect = &{$redirect}($cgi) if ref($redirect) eq 'CODE';
+% redirect( $redirect );
+% } else {
+% if ( $opt{'name_singular'} ) {
+% $opt{'name'} = PL($opt{'name_singular'});
+% }
+% ( my $xlsname = $opt{'name'} ) =~ s/\W//g;
+% if ( $total == 1 ) {
+% if ( $opt{'name_singular'} ) {
+% $opt{'name'} = $opt{'name_singular'}
+% } else {
+% #$opt{'name'} =~ s/s$// if $total == 1;
+% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1;
+% }
+% }
+%
+% if ( $type eq 'html-print' ) {
+
+ <% $opt{nohtmlheader}
+ ? ''
+ : include( '/elements/header-popup.html', $opt{'title'} )
+ %>
+
+% } elsif ( $type eq 'select' ) {
+
+ <% $opt{nohtmlheader}
+ ? ''
+ : include( '/elements/header-popup.html', $opt{'title'} )
+ %>
+ <% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+ %>
+
+% } else {
+%
+% my @menubar = ();
+% if ( $opt{'menubar'} ) {
+% @menubar = @{ $opt{'menubar'} };
+% #} else {
+% # @menubar = ( 'Main menu' => $p );
+% }
+
+ <% $opt{nohtmlheader}
+ ? ''
+ : include( '/elements/header.html', $opt{'title'},
+ include( '/elements/menubar.html', @menubar )
+ )
+ %>
+
+ <% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+ %>
+
+% }
+
+% unless ( $total ) {
+% unless ( $opt{'disable_nonefound'} ) {
+ No matching <% $opt{'name'} %> found.<BR>
+% }
+% }
+%
+% if ( $total || $opt{'disableable'} ) { #hmm... and there *are* ones to show??
+
+ <TABLE>
+ <TR>
+
+ <TD VALIGN="bottom">
+
+ <FORM>
+
+% if (! $opt{'disable_total'}) {
+ <% $total %> total <% $opt{'name'} %>
+% }
+
+% if ( $confmax && $total > $confmax
+% && ! $opt{'disable_maxselect'}
+% && $type ne 'html-print' )
+% {
+% $cgi->delete('maxrecords');
+% $cgi->param('_dummy', 1);
+
+ ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $self_url %>;maxrecords=' + this.options[this.selectedIndex].value;">
+
+% foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) {
+ <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION>
+% }
+
+ </SELECT> per page )
+
+% $cgi->param('maxrecords', $maxrecords);
+% }
+
+% if ( defined($opt{'html_posttotal'}) && $type ne 'html-print' ) {
+ <% ref($opt{'html_posttotal'})
+ ? &{$opt{'html_posttotal'}}()
+ : $opt{'html_posttotal'}
+ %>
+% }
+ <BR>
+
+% if ( $opt{'count_addl'} ) {
+% my $n=0;
+% foreach my $count ( @{$opt{'count_addl'}} ) {
+% my $data = $count_arrayref->[++$n];
+% if ( ref($count) ) {
+ <% &{ $count }( $data ) %>
+% } else {
+ <% sprintf( $count, $data ) %><BR>
+% }
+% }
+% }
+ </FORM>
+
+ </TD>
+
+% unless ( $opt{'disable_download'} || $type eq 'html-print' ) {
+
+ <TD ALIGN="right">
+
+ Download full results<BR>
+
+% $cgi->param('_type', "$xlsname.xls" );
+ as <A HREF="<% $self_url %>">Excel spreadsheet</A><BR>
+
+% $cgi->param('_type', 'csv');
+ as <A HREF="<% $self_url %>">CSV file</A><BR>
+
+% if ( defined($opt{xml_elements}) ) {
+% $cgi->param('_type', 'xml');
+ as <A HREF="<% $self_url %>">XML file</A><BR>
+% }
+
+% $cgi->param('_type', 'html-print');
+ as <A HREF="<% $self_url %>">printable copy</A>
+
+ </TD>
+% $cgi->param('_type', "html" );
+% }
+
+ </TR>
+ <TR>
+ <TD COLSPAN=2>
+
+% my $pager = '';
+% unless ( $type eq 'html_print' ) {
+
+ <% $pager = include( '/elements/pager.html',
+ 'offset' => $offset,
+ 'num_rows' => scalar(@$rows),
+ 'total' => $total,
+ 'maxrecords' => $maxrecords,
+ )
+ %>
+
+ <% defined($opt{'html_form'})
+ ? ( ref($opt{'html_form'})
+ ? &{$opt{'html_form'}}()
+ : $opt{'html_form'}
+ )
+ : ''
+ %>
+
+% }
+
+ <% include('/elements/table-grid.html') %>
+
+ <TR>
+% my $h2 = 0;
+% my $colspan = 0;
+% foreach my $header ( @{ $opt{header} } ) {
+% $colspan-- if $colspan > 0;
+% next if $colspan;
+%
+% my $label = ref($header) ? $header->{label} : $header;
+% $colspan = ref($header) ? $header->{colspan} : 0;
+% my $rowspan = 1;
+% my $style = '';
+% if ( $opt{header2} ) {
+% if ( !length($opt{header2}->[$h2]) ) {
+% $rowspan = 2;
+% splice @{ $opt{header2} }, $h2, 1;
+% } else {
+% $h2++;
+% $style = 'STYLE="border-bottom: none"'
+% }
+% }
+ <TH CLASS = "grid"
+ BGCOLOR = "#cccccc"
+ ROWSPAN = "<% $rowspan %>"
+ <% $colspan ? 'COLSPAN = "'.$colspan.'"' : '' %>
+ <% $style %>
+
+ >
+ <% $label %>
+ </TH>
+% }
+ </TR>
+
+% if ( $opt{header2} ) {
+ <TR>
+% foreach my $header ( @{ $opt{header2} } ) {
+% my $label = ref($header) ? $header->{label} : $header;
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE="-1"><% $label %></FONT>
+ </TH>
+% }
+ </TR>
+% }
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% foreach my $row ( @$rows ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+% if ( $opt{'fields'} ) {
+%
+% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+% my $onclicks = $opt{'link_onclicks'} ? [ @{$opt{'link_onclicks'}} ] : [];
+% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : [];
+% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
+% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : [];
+% my $cstyles = $opt{'cell_style'} ? [ @{$opt{'cell_style'}} ] : [];
+%
+% foreach my $field (
+%
+% map {
+% if ( ref($_) eq 'ARRAY' ) {
+%
+% my $tableref = $_;
+%
+% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 WIDTH="100%">'.
+%
+% join('', map {
+%
+% my $rowref = $_;
+%
+% '<tr>'.
+%
+% join('', map {
+%
+% my $e = $_;
+%
+% '<TD '.
+% join(' ', map {
+% uc($_).'="'. $e->{$_}. '"';
+% }
+% grep exists($e->{$_}),
+% qw( align bgcolor colspan rowspan
+% style valign width )
+% ).
+% '>'.
+%
+% ( $e->{'link'}
+% ? '<A HREF="'. $e->{'link'}. '">'
+% : ''
+% ).
+% ( $e->{'size'}
+% ? '<FONT SIZE="'.uc($e->{'size'}).'">'
+% : ''
+% ).
+% ( $e->{'data_style'}
+% ? '<'. uc($e->{'data_style'}). '>'
+% : ''
+% ).
+% $e->{'data'}.
+% ( $e->{'data_style'}
+% ? '</'. uc($e->{'data_style'}). '>'
+% : ''
+% ).
+% ( $e->{'size'} ? '</FONT>' : '' ).
+% ( $e->{'link'} ? '</A>' : '' ).
+% '</td>';
+%
+% } @$rowref ).
+%
+% '</tr>';
+% } @$tableref ).
+%
+% '</table>';
+%
+% } else {
+% $_;
+% }
+% }
+%
+% map {
+% if ( ref($_) eq 'CODE' ) {
+% &{$_}($row);
+% } else {
+% $row->$_();
+% }
+% }
+% @{$opt{'fields'}}
+%
+% ) {
+%
+% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
+%
+% my $align = $aligns ? shift @$aligns : '';
+% $align = " ALIGN=$align" if $align;
+%
+% my $a = '';
+% if ( $links ) {
+% my $link = shift @$links;
+% my $onclick = shift @$onclicks;
+%
+% if ( ! $opt{'agent_virt'}
+% || ( $null_link && ! $row->agentnum )
+% || grep { $row->agentnum == $_ }
+% @link_agentnums
+% ) {
+%
+% $link = &{$link}($row)
+% if ref($link) eq 'CODE';
+%
+% $onclick = &{$onclick}($row)
+% if ref($onclick) eq 'CODE';
+% $onclick = qq( onClick="$onclick") if $onclick;
+%
+% 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"$onclick>);
+% }
+% elsif ( $onclick ) {
+% $a = qq(<A HREF="javascript:void(0);"$onclick>);
+% }
+% }
+%
+% }
+%
+% my $font = '';
+% my $color = shift @$colors;
+% $color = &{$color}($row) if ref($color) eq 'CODE';
+% my $size = shift @$sizes;
+% $size = &{$size}($row) if ref($size) eq 'CODE';
+% if ( $color || $size ) {
+% $font = '<FONT '.
+% ( $color ? "COLOR=#$color " : '' ).
+% ( $size ? qq(SIZE="$size" ) : '' ).
+% '>';
+% }
+%
+% my($s, $es) = ( '', '' );
+% my $style = shift @$styles;
+% $style = &{$style}($row) if ref($style) eq 'CODE';
+% if ( $style ) {
+% $s = join( '', map "<$_>", split('', $style) );
+% $es = join( '', map "</$_>", split('', $style) );
+% }
+%
+% my $cstyle = shift @$cstyles;
+% $cstyle = &{$cstyle}($row) if ref($cstyle) eq 'CODE';
+% $cstyle = qq(STYLE="$cstyle")
+% if $cstyle;
+
+ <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>" <% $align %> <% $cstyle %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD>
+
+% }
+%
+% } else {
+%
+% foreach ( @$row ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD>
+% }
+%
+% }
+
+ </TR>
+
+% }
+
+% if ( $opt{'footer'} ) {
+
+ <TR>
+
+% foreach my $footer ( @{ $opt{'footer'} } ) {
+% $footer = &{$footer}() if ref($footer) eq 'CODE';
+ <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TD>
+% }
+
+ </TR>
+% }
+
+ </TABLE>
+
+ <% $pager %>
+
+ </TD>
+ </TR>
+ </TABLE>
+% }
+
+% if ( $type eq 'html-print' ) {
+% unless ( $opt{nohtmlheader} ) {
+
+ </BODY></HTML>
+
+% }
+% } else {
+
+ <% defined($opt{'html_foot'})
+ ? ( ref($opt{'html_foot'})
+ ? &{$opt{'html_foot'}}()
+ : $opt{'html_foot'}
+ )
+ : ''
+ %>
+
+ <% $opt{nohtmlheader}
+ ? ''
+ : include( '/elements/footer.html' )
+ %>
+
+% }
+
+% }
+<%init>
+
+my %args = @_;
+my $type = $args{'type'};
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my @link_agentnums = @{ $args{'link_agentnums'} };
+my $null_link = $args{'null_link'};
+my $confmax = $args{'confmax'};
+my $maxrecords = $args{'maxrecords'};
+my $offset = $args{'offset'};
+my %opt = %{ $args{'opt'} };
+my $self_url = $opt{'url'} || $cgi->self_url;
+
+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];
+
+</%init>
diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html
new file mode 100644
index 0000000..8323f55
--- /dev/null
+++ b/httemplate/search/elements/search-xls.html
@@ -0,0 +1,85 @@
+<% $data %>
+<%init>
+
+my %args = @_;
+my $type = $args{'type'};
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my %opt = %{ $args{'opt'} };
+
+#http_header('Content-Type' => 'application/excel' ); #eww
+#http_header('Content-Type' => 'application/msexcel' ); #alas
+#http_header('Content-Type' => 'application/x-msexcel' ); #?
+
+#http://support.microsoft.com/kb/199841
+http_header('Content-Type' => 'application/vnd.ms-excel' );
+http_header('Content-Disposition' =>
+ 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xls"');
+
+#http://support.microsoft.com/kb/812935
+#http://support.microsoft.com/kb/323308
+$HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0';
+
+my $data = '';
+my $XLS = new IO::Scalar \$data;
+my $workbook = Spreadsheet::WriteExcel->new($XLS)
+ or die "Error opening .xls file: $!";
+
+my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
+
+$worksheet->protect();
+
+my($r,$c) = (0,0);
+
+my $header_format = $workbook->add_format(
+ bold => 1,
+ locked => 1,
+ bg_color => 55, #22,
+ bottom => 3,
+);
+
+$worksheet->write($r, $c++, $_, $header_format ) foreach @$header;
+
+foreach my $row ( @$rows ) {
+ $r++;
+ $c = 0;
+
+ if ( $opt{'fields'} ) {
+
+ #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+ #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+ #could also translate color, size, style into xls equivalents?
+ my $formats = $opt{'xls_format'} ? [ @{$opt{'xls_format'}} ] : [];
+
+ foreach my $field ( @{$opt{'fields'}} ) {
+
+ my $format = shift @$formats;
+ $format = &{$format}($row) if ref($format) eq 'CODE';
+ $format ||= {};
+ my $xls_format = $workbook->add_format(locked=>0, %$format);
+
+ if ( ref($field) eq 'CODE' ) {
+ foreach my $value ( &{$field}($row) ) {
+ if ( ref($value) eq 'ARRAY' ) {
+ $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
+ } else {
+ $worksheet->write($r, $c++, $value, $xls_format );
+ }
+ }
+ } else {
+ $worksheet->write($r, $c++, $row->$field(), $xls_format );
+ }
+ }
+
+ } else {
+ my $xls_format = $workbook->add_format(locked=>0);
+ $worksheet->write($r, $c++, $_, $xls_format ) foreach @$row;
+ }
+
+}
+
+$workbook->close();# or die "Error creating .xls file: $!";
+
+http_header('Content-Length' => length($data) );
+
+</%init>
diff --git a/httemplate/search/elements/search-xml.html b/httemplate/search/elements/search-xml.html
new file mode 100644
index 0000000..9f5e9b6
--- /dev/null
+++ b/httemplate/search/elements/search-xml.html
@@ -0,0 +1,88 @@
+% foreach my $row ( @$rows ) {
+%
+% if (&{$beginrow}($row)){
+<% &{$beginrow}($row) %>
+% }
+%
+% foreach my $i ( 0 .. scalar( @{$opt{'fields'}} ) - 1 ) {
+% my $field = $opt{'fields'}->[$i];
+% my $value = '';
+% if ( ref($field) eq 'CODE' ) {
+% $value = &{$field}($row);
+% $value = '(N/A)' #unimplemented
+% if ref($value) eq 'ARRAY';
+% } else {
+% $value = $row->$field();
+% }
+%
+<% &{$beginfield}($row, $i) %><% $value |h %><% &{$endfield}($row, $i) %>
+%
+% }
+%
+% if (&{$endrow}($row)) {
+<% &{$endrow}($row) %>
+% }
+%
+% }
+<%init>
+
+my %args = @_;
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my %opt = %{ $args{'opt'} };
+
+http_header('Content-Type' => 'application/XML' ); # So saith RFC 4180
+http_header('Content-Disposition' =>
+ 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xml"');
+
+unless ( $opt{'fields'} ) {
+ foreach my $i ( 0 .. ( $#{ @$rows[0] } ) ) {
+ $opt{'fields'}->[$i] = sub { my $row = shift; $row->[$i]; };
+ }
+}
+
+my $beginrow = sub { return ''; };
+my $endrow = sub { return ''; };
+if ($opt{xml_row_element}) {
+ $beginrow = sub { my ($row, $index) = @_;
+ my $value;
+ if ( ref($opt{xml_row_element}) eq 'CODE' ) {
+ $value = &{$opt{xml_row_element}}($row);
+ } else {
+ $value = $opt{xml_row_element};
+ }
+ return "<$value>";
+ };
+ $endrow = sub { my ($row, $index) = @_;
+ my $value;
+ if ( ref($opt{xml_row_element}) eq 'CODE' ) {
+ $value = &{$opt{xml_row_element}}($row);
+ } else {
+ $value = $opt{xml_row_element};
+ }
+ return "</$value>";
+ };
+}
+my $beginfield = sub { my ($row, $index) = @_;
+ my $value;
+ if ( ref($opt{xml_elements}->[$index]) eq 'CODE' ) {
+ $value = &{$opt{xml_elements}->[$index]}($row);
+ } else {
+ $value = $opt{xml_elements}->[$index];
+ }
+ return "<$value>";
+ };
+my $endfield = sub { my ($row, $index) = @_;
+ my $value;
+ if ( ref($opt{xml_elements}->[$index]) eq 'CODE' ) {
+ $value = &{$opt{xml_elements}->[$index]}($row);
+ } else {
+ $value = $opt{xml_elements}->[$index];
+ }
+ return "</$value>";
+ };
+
+$beginfield = sub { return ''; } if $opt{no_field_elements}; #hmm
+$endfield = sub { return ''; } if $opt{no_field_elements}; #hmm
+
+</%init>
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
new file mode 100644
index 0000000..a258f17
--- /dev/null
+++ b/httemplate/search/elements/search.html
@@ -0,0 +1,415 @@
+<%doc>
+
+Example:
+
+ include( 'elements/search.html',
+
+ ###
+ # required
+ ###
+
+ 'title' => 'Page title',
+
+ 'name_singular' => 'item', #singular name for the records returned
+ #OR# # (preferred, will be pluralized automatically)
+ 'name' => 'items', #plural name for the records returned
+ # (deprecated, will be singularlized
+ # simplisticly)
+
+ #literal SQL query string (deprecated?) or qsearch hashref or arrayref
+ #of qsearch hashrefs for a union of qsearches
+ 'query' => {
+ 'table' => 'tablename',
+ #everything else is optional...
+ 'hashref' => { 'field' => 'value',
+ 'field' => { 'op' => '<',
+ 'value' => '54',
+ },
+ },
+ 'select' => '*',
+ 'addl_from' => '', #'LEFT JOIN othertable USING ( key )',
+ 'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff',
+ 'order_by' => 'ORDER BY something',
+
+ },
+ # "select * from tablename";
+
+ #required unless 'query' is an SQL query string (shouldn't be...)
+ 'count_query' => 'SELECT COUNT(*) FROM tablename',
+
+ ###
+ # recommended / common
+ ###
+
+ #listref of column labels, <TH>
+ #recommended unless 'query' is an SQL query string
+ # (if not specified the database column names will be used)
+ 'header' => [ '#',
+ 'Item',
+ { 'label' => 'Another Item',
+
+ },
+ ],
+
+ #listref - each item is a literal column name (or method) or coderef
+ #if not specified all columns will be shown
+ 'fields' => [
+ 'column',
+ sub { my $row = shift; $row->column; },
+ ],
+
+ #redirect if there's only one item...
+ # listref of URL base and column name (or method)
+ # or a coderef that returns the same
+ 'redirect' => sub { my( $record, $cgi ) = @_;
+ [ popurl(2).'view/item.html', 'primary_key' ];
+ },
+
+ #redirect if there's no items
+ # scalar URL or a coderef that returns a URL
+ 'redirect_empty' => sub { my( $cgi ) = @_;
+ popurl(2).'view/item.html';
+ },
+
+ ###
+ # optional
+ ###
+
+ # some HTML callbacks...
+ 'menubar' => '', #menubar arrayref
+ 'html_init' => '', #after the header/menubar and before the pager
+ 'html_form' => '', #after the pager, right before the results
+ # (only shown if there are results)
+ # (use this for any form-opening tag rather than
+ # html_init, to avoid a nested form)
+ 'html_foot' => '', #at the bottom
+ 'html_posttotal' => '', #at the bottom
+ # (these three can be strings or coderefs)
+
+ 'count_addl' => [], #additional count fields listref of sprintf strings or coderefs
+ # [ $money_char.'%.2f total paid', ],
+
+ #second (smaller) header line, currently only for HTML
+ 'header2 => [ '#',
+ 'Item',
+ { 'label' => 'Another Item',
+
+ },
+ ],
+
+ #listref of column footers
+ 'footer' => [],
+
+ #disabling things
+ 'disable_download' => '', # set true to hide the CSV/Excel download links
+ 'disable_total' => '', # set true to hide the total"
+ 'disable_maxselect' => '', # set true to disable record/page selection
+ 'disable_nonefound' => '', # set true to disable the "No matching Xs found"
+ # message
+
+ #handling "disabled" fields in the records
+ 'disableable' => 1, # set set to 1 (or column position for "disabled"
+ # status col) to enable if this table has a "disabled"
+ # field, to hide disabled records & have
+ # "show disabled/hide disabled" links
+ #(can't be used with a literal query)
+ 'disabled_statuspos' => 3, #optional position (starting from 0) to insert
+ #a Status column when showing disabled records
+ #(query needs to be a qsearch hashref and
+ # header & fields need to be defined)
+
+ #handling agent virtualization
+ 'agent_virt' => 1, # set true if this search should be
+ # agent-virtualized
+ 'agent_null' => 1, # set true to view global records always
+ 'agent_null_right' => 'Access Right', # optional right to view global
+ # records
+ 'agent_null_right_link' => 'Access Right' # optional right to link to
+ # global records; defaults to
+ # same as agent_null_right
+ 'agent_pos' => 3, # optional position (starting from 0) to
+ # insert an Agent column (query needs to be a
+ # qsearch hashref and header & fields need to
+ # be defined)
+
+ # link & display properties for fields
+
+ #listref - each item is the empty string,
+ # or a listref of link and method name to append,
+ # or a listref of link and coderef to run and append
+ # or a coderef that returns such a listref
+ 'links' => [],`
+
+ #listref - each item is the empty string,
+ # or a string onClick handler for the corresponding link
+ # or a coderef that returns string onClick handler
+ 'link_onclicks' => [],
+
+ #one letter for each column, left/right/center/none
+ # or pass a listref with full values: [ 'left', 'right', 'center', '' ]
+ 'align' => 'lrc.',
+
+ #listrefs of ( scalars or coderefs )
+ # currently only HTML, maybe eventually Excel too
+ 'color' => [],
+ 'size' => [],
+ 'style' => [], #<B> or <I>, etc.
+ 'cell_style' => [], #STYLE= attribute of TR, very HTML-specific...
+
+ # Excel-specific listref of ( hashrefs or coderefs )
+ # each hashref: http://search.cpan.org/dist/Spreadsheet-WriteExcel/lib/Spreadsheet/WriteExcel.pm#Format_methods_and_Format_properties
+ 'xls_format' => => [],
+
+ )
+
+</%doc>
+% if ( $type eq 'csv' ) {
+%
+<% include('search-csv.html', header=>$header, rows=>$rows, opt=>\%opt ) %>
+%
+% #} elsif ( $type eq 'excel' ) {
+% } elsif ( $type =~ /\.xls$/ ) {
+%
+<% include('search-xls.html', header=>$header, rows=>$rows, opt=>\%opt ) %>
+%
+% } elsif ( $type eq 'xml' ) {
+%
+<% include('search-xml.html', rows=>$rows, opt=>\%opt ) %>
+%
+% } else { # regular HTML
+%
+<% include('search-html.html',
+ type => $type,
+ header => $header,
+ rows => $rows,
+ link_agentnums => \@link_agentnums,
+ null_link => $null_link,
+ confmax => $confmax,
+ maxrecords => $maxrecords,
+ offset => $offset,
+ opt => \%opt
+ )
+%>
+%
+% }
+<%init>
+
+my(%opt) = @_;
+#warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n";
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|xml|select|html(-print)?)$/
+ ? $1 : 'html' ;
+
+my %align = (
+ 'l' => 'left',
+ 'r' => 'right',
+ 'c' => 'center',
+ ' ' => '',
+ '.' => '',
+);
+$opt{align} = [ map $align{$_}, split(//, $opt{align}) ],
+ unless !$opt{align} || ref($opt{align});
+
+if($type =~ /csv|xls/) {
+ my $h = $opt{'header'};
+ my @del;
+ my $i = 0;
+ do {
+ if( ref($h->[$i]) and exists($h->[$i]->{'nodownload'}) ) {
+ splice(@{$opt{$_}}, $i, 1) foreach
+ qw(header footer fields links link_onclicks
+ align color size style cell_style xls_format);
+ }
+ else {
+ $i++;
+ }
+ } while ( exists($h->[$i]) );
+}
+
+# wtf?
+$opt{disable_download} = 0
+ if $opt{disable_download} && $curuser->access_right('Configuration download');
+
+$opt{disable_download} = 1
+ if $opt{really_disable_download};
+
+my @link_agentnums = ();
+my $null_link = '';
+if ( $opt{'agent_virt'} ) {
+
+ @link_agentnums = $curuser->agentnums;
+ $null_link = $curuser->access_right( $opt{'agent_null_right_link'}
+ || $opt{'agent_null_right'} );
+
+ my $agentnums_sql = $curuser->agentnums_sql(
+ 'null' => $opt{'agent_null'},
+ 'null_right' => $opt{'agent_null_right'},
+ 'table' => $opt{'query'}{'table'},
+ );
+
+ $opt{'query'}{'extra_sql'} .=
+ ( $opt{'query'}{'extra_sql'} =~ /WHERE/i || keys %{$opt{'query'}{'hashref'}}
+ ? ' AND '
+ : ' WHERE ' ). $agentnums_sql;
+
+ $opt{'count_query'} .=
+ ( $opt{'count_query'} =~ /WHERE/i ? ' AND ' : ' WHERE ' ). $agentnums_sql;
+
+ if ( $opt{'agent_pos'} || $opt{'agent_pos'} eq '0'
+ and scalar($curuser->agentnums) > 1 ) {
+ #false laziness w/statuspos above
+ my $pos = $opt{'agent_pos'};
+
+ foreach my $att (qw( align color size style cell_style xls_format )) {
+ $opt{$att} ||= [ map '', @{ $opt{'fields'} } ];
+ }
+
+ splice @{ $opt{'header'} }, $pos, 0, 'Agent';
+ splice @{ $opt{'align'} }, $pos, 0, 'c';
+ splice @{ $opt{'style'} }, $pos, 0, '';
+ splice @{ $opt{'size'} }, $pos, 0, '';
+ splice @{ $opt{'fields'} }, $pos, 0,
+ sub { $_[0]->agentnum ? $_[0]->agent->agent : '(global)'; };
+ splice @{ $opt{'color'} }, $pos, 0, '';
+ splice @{ $opt{'links'} }, $pos, 0, '' #[ 'agent link?', 'agentnum' ]
+ if $opt{'links'};
+ splice @{ $opt{'link_onclicks'} }, $pos, 0, ''
+ if $opt{'link_onclicks'};
+
+ }
+
+}
+
+if ( $opt{'disableable'} ) {
+
+ unless ( $cgi->param('showdisabled') ) { #modify searches
+
+ $opt{'query'}{'hashref'}{'disabled'} = '';
+ $opt{'query'}{'extra_sql'} =~ s/^\s*WHERE/ AND/i;
+
+ $opt{'count_query'} .=
+ ( $opt{'count_query'} =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
+ "( disabled = '' OR disabled IS NULL )";
+
+ } elsif ( $opt{'disabled_statuspos'}
+ || $opt{'disabled_statuspos'} eq '0' ) { #add status column
+
+ my $pos = $opt{'disabled_statuspos'};
+
+ foreach my $att (qw( align style color size )) {
+ $opt{$att} ||= [ map '', @{ $opt{'fields'} } ];
+ }
+
+ splice @{ $opt{'header'} }, $pos, 0, 'Status';
+ splice @{ $opt{'align'} }, $pos, 0, 'c';
+ splice @{ $opt{'style'} }, $pos, 0, 'b';
+ splice @{ $opt{'size'} }, $pos, 0, '';
+ splice @{ $opt{'fields'} }, $pos, 0,
+ sub { shift->disabled ? 'DISABLED' : 'Active'; };
+ splice @{ $opt{'color'} }, $pos, 0,
+ sub { shift->disabled ? 'FF0000' : '00CC00'; };
+ splice @{ $opt{'links'} }, $pos, 0, ''
+ if $opt{'links'};
+ splice @{ $opt{'link_onlicks'} }, $pos, 0, ''
+ if $opt{'link_onlicks'};
+ }
+
+ #add show/hide disabled links
+ my $items = $opt{'name'} || PL($opt{'name_singular'});
+ if ( $cgi->param('showdisabled') ) {
+ $cgi->param('showdisabled', 0);
+ $opt{'html_posttotal'} .=
+ '( <a href="'. $cgi->self_url. qq!">hide disabled $items</a> )!; #"
+ $cgi->param('showdisabled', 1);
+ } else {
+ $cgi->param('showdisabled', 1);
+ $opt{'html_posttotal'} .=
+ '( <a href="'. $cgi->self_url. qq!">show disabled $items</a> )!; #"
+ $cgi->param('showdisabled', 0);
+ }
+
+}
+
+my $limit = '';
+my($confmax, $maxrecords, $offset );
+
+if ( !$type =~ /^(csv|\w*.xls)$/) {
+# html mode
+ 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; #silly vim:/
+ }
+
+ if ( $opt{disableable} && ! $cgi->param('showdisabled') ) {
+ $opt{count_query} .=
+ ( ( $opt{count_query} =~ /WHERE/i ) ? ' AND ' : ' WHERE ' ).
+ "( disabled = '' OR disabled IS NULL )";
+ }
+
+ unless ( $type eq 'html-print' ) {
+
+ #setup some pagination things if we're in html mode
+
+ my $conf = new FS::Conf;
+ $confmax = $conf->config('maxsearchrecordsperpage');
+ if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) {
+ $maxrecords = $1;
+ } else {
+ $maxrecords ||= $confmax;
+ }
+
+ $limit = $maxrecords ? "LIMIT $maxrecords" : '';
+
+ $offset = $cgi->param('offset') =~ /^(\d+)$/ ? $1 : 0;
+ $limit .= " OFFSET $offset" if $offset;
+
+ }
+
+}
+
+# run the query
+
+my $header = [ map { ref($_) ? $_->{'label'} : $_ } @{$opt{header}} ];
+my $rows;
+if ( ref($opt{query}) ) {
+
+ my @query;
+ if (ref($opt{query}) eq 'HASH') {
+ @query = ( $opt{query} );
+ } elsif (ref($opt{query}) eq 'ARRAY') {
+ @query = @{ $opt{query} };
+ } else {
+ die "invalid query reference";
+ }
+
+ if ( $opt{disableable} && ! $cgi->param('showdisabled') ) {
+ #%search = ( 'disabled' => '' );
+ $opt{'query'}->{'hashref'}->{'disabled'} = '';
+ $opt{'query'}->{'extra_sql'} =~ s/^\s*WHERE/ AND/i;
+ }
+
+ #eval "use FS::$opt{'query'};";
+ my @param = qw( select table addl_from hashref extra_sql order_by );
+ $rows = [ qsearch( [ map { my $query = $_;
+ ({ map { $_ => $query->{$_} } @param });
+ }
+ @query
+ ],
+ 'order_by' => $opt{order_by}. " ". $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};
+}
+
+</%init>
diff --git a/httemplate/search/h_cust_pay.html b/httemplate/search/h_cust_pay.html
new file mode 100755
index 0000000..99330fa
--- /dev/null
+++ b/httemplate/search/h_cust_pay.html
@@ -0,0 +1,9 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'table' => 'h_cust_pay',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'payment',
+ 'name_verb' => 'paid',
+ 'pre_header' => [ 'Transaction', 'By' ],
+ 'pre_fields' => [ 'history_action', 'history_user' ],
+ )
+%>
diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html
new file mode 100644
index 0000000..086c8e9
--- /dev/null
+++ b/httemplate/search/inventory_item.html
@@ -0,0 +1,198 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+
+ 'menubar' => [ 'View inventory classes' =>
+ $p.'browse/inventory_class.html',
+ 'Upload '. PL($inventory_class->classname)=>
+ $p.'misc/inventory_item-import.html?'.
+ "classnum=$classnum"
+ ],
+
+ 'name' => PL($inventory_class->classname),
+
+ 'query' => {
+ 'table' => 'inventory_item',
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'inventory_item.*',
+ 'part_svc.svcdb',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $extra_sql,
+ 'addl_from' => $addl_from,
+ },
+
+ 'count_query' => $count_query,
+
+ 'agent_virt' => 1,
+ 'agent_null' => 1,
+ 'agent_pos' => 2,
+
+ 'header' => [
+ '#',
+ $inventory_class->classname,
+ 'Service',
+ FS::UI::Web::cust_header(),
+ '', # checkbox column
+ ],
+
+ 'fields' => [
+ 'itemnum',
+ 'item',
+ #'svcnum', #XXX proper full service customer link ala svc_acct
+ # "unallocated" ? "available" ?
+ sub {
+ #this could be way more efficient with a mixin
+ # like cust_main_Mixin that let us all all the methods
+ # on data we already have...
+ my $inventory_item = shift;
+ my $cust_svc = $inventory_item->cust_svc;
+ if ( $cust_svc ) {
+ my($label, $value) = $cust_svc->label;
+ "$label: $value";
+ } else {
+ '(available)';
+ }
+ },
+
+ \&FS::UI::Web::cust_fields,
+ $sub_checkbox,
+
+ ],
+ 'align' => 'rll'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ 'html_form' =>
+ qq!
+<FORM NAME="itemForm" ACTION="$p/misc/inventory_item-move.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="classnum" VALUE="$classnum">
+<INPUT TYPE="hidden" NAME="avail" VALUE="! .$cgi->param('avail') . '">', #'
+ 'html_foot' => $sub_foot,
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit inventory')
+ || $curuser->access_right('Edit global inventory')
+ || $curuser->access_right('Configuration');
+
+my $classnum = $cgi->param('classnum');
+$classnum =~ /^(\d+)$/ or errorpage("illegal classnum $classnum");
+$classnum = $1;
+my $extra_sql = "WHERE inventory_item.classnum = $classnum ";
+
+my $inventory_class = qsearchs( {
+ 'table' => 'inventory_class',
+ 'hashref' => { 'classnum' => $classnum },
+} );
+
+my $title = $inventory_class->classname. ' Inventory';
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $extra_sql .= " AND inventory_item.agentnum = $1 ";
+ my $agent = qsearchs('agent', { 'agentnum' => $1 }) or die "unknown agentnum";
+ $title = $agent->agent. " $title";
+}
+
+#little false laziness with SQL fragments in inventory_class.pm
+if ( $cgi->param('avail') ) {
+ $extra_sql .= ' AND ( svcnum IS NULL OR svcnum = 0 )';
+ $title .= ' - Available';
+} elsif ( $cgi->param('used') ) {
+ $extra_sql .= ' AND svcnum IS NOT NULL AND svcnum > 0';
+ $title .= ' - In use';
+}
+
+my $count_query =
+ "SELECT COUNT(*) FROM inventory_item $extra_sql";
+
+my $link = sub {
+ my $inventory_item = shift;
+ if ( $inventory_item->svcnum ) {
+
+ #[ "${p}view/svc_acct.cgi?", 'svcnum' ];
+ my $url = svc_url(
+ 'm' => $m,
+ 'action' => 'view',
+ #'svcdb' => $inventory_item->cust_svc->part_svc->svcdb,
+ 'svcdb' => $inventory_item->svcdb, #we have it from the joined search
+ 'query' => '',
+ );
+ [ $url, 'svcnum' ];
+ } else {
+ '';
+ }
+};
+my $link_cust = sub {
+ my $inventory_item = shift;
+ if ( $inventory_item->custnum ) {
+ [ "${p}view/cust_main.cgi?", 'custnum' ];
+ } else {
+ '';
+ }
+};
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+my $areboxes = 0;
+
+my $sub_checkbox = sub {
+ my $item = $_[0];
+ my $itemnum = $item->itemnum;
+ #return '' if $item->svcnum;
+ $areboxes = 1;
+ return qq!<INPUT NAME="itemnum$itemnum" TYPE="checkbox" VALUE="1">!;
+};
+
+my $sub_foot = sub {
+ return if !$areboxes;
+ my $foot =
+'<BR><INPUT TYPE="button" VALUE="Select all" onClick="setAll(true)">
+<INPUT TYPE="button" VALUE="Unselect all" onClick="setAll(false)">
+<BR><INPUT TYPE="submit" NAME="action" VALUE="Move to agent">
+<SELECT NAME="move_agentnum">';
+ foreach my $agent ($curuser->agents) {
+ $foot .= '<OPTION VALUE="'.$agent->agentnum.'">'.
+ $agent->agent . '</OPTION>
+ ';
+ }
+ $foot .= '</SELECT>
+<SCRIPT TYPE="text/javascript">
+ function setAll(setTo) {
+ theForm = document.itemForm;
+ for (i=0,n=theForm.elements.length;i<n;i++)
+ if (theForm.elements[i].name.indexOf("itemnum") != -1)
+ theForm.elements[i].checked = setTo;
+ }
+</SCRIPT>';
+ $foot;
+};
+
+
+
+
+</%init>
diff --git a/httemplate/search/mailinglistmember.html b/httemplate/search/mailinglistmember.html
new file mode 100644
index 0000000..ee395f4
--- /dev/null
+++ b/httemplate/search/mailinglistmember.html
@@ -0,0 +1,57 @@
+<% include('elements/search.html',
+ 'title' => $title,
+ 'name_singular' => 'member',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ 'Email address' ],
+ 'fields' => [ $email_sub, ], #just this one for now
+ 'html_init' => $html_init,
+ )
+%>
+<%init>
+
+#XXX ACL:
+#make sure the mailing list is attached to a customer service i can see/view
+
+$cgi->param('listnum') =~ /^(\d+)$/ or die 'illegal listnum';
+my $listnum = $1;
+
+my $mailinglist = qsearchs('mailinglist', { 'listnum' => $listnum })
+ or die "unknown listnum $listnum";
+my $title = $mailinglist->listname. ' mailing list';
+
+my $svc_mailinglist = $mailinglist->svc_mailinglist;
+
+my $query = {
+ 'table' => 'mailinglistmember',
+ 'hashref' => { 'listnum' => $listnum },
+};
+
+my $count_query = "SELECT COUNT(*) FROM mailinglistmember WHERE listnum = $listnum";
+
+my $email_sub = sub {
+ my $member = shift;
+ my $r = $member->email; #just this one for now
+ my $a = qq[<A HREF="javascript:areyousure('$r', ]. $member->membernum. ')">';
+ $r .= " (${a}remove</A>)";
+ $r;
+};
+
+my $html_init = '';
+if ( $svc_mailinglist ) {
+ my $svcnum = $svc_mailinglist->svcnum;
+ my $label = encode_entities($svc_mailinglist->label);
+ $html_init .= qq[<A HREF="${p}/view/svc_mailinglist.cgi?$svcnum">View customer mailing list: $label</A><BR><BR>];
+}
+
+$html_init .= <<"END";
+<SCRIPT TYPE="text/javascript">
+ function areyousure(email,membernum) {
+ if ( confirm('Are you sure you want to remove ' + email + ' from this mailing list?') )
+ window.location.href="${p}misc/delete-mailinglistmember.html?" + membernum;
+
+ }
+</SCRIPT>
+END
+
+</%init>
diff --git a/httemplate/search/part_pkg.html b/httemplate/search/part_pkg.html
new file mode 100644
index 0000000..915dbf4
--- /dev/null
+++ b/httemplate/search/part_pkg.html
@@ -0,0 +1,213 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name' => $name,
+ 'header' => \@header,
+ 'query' => { 'select' => $select,
+ 'table' => 'part_pkg',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY $order_by",
+ },
+ 'count_query' => $count_query,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => $align,
+ )
+%>
+<%init>
+
+#this is about reports about packages definitions (starting w/commission ones)
+# while browse/part_pkg.cgi is config->package definitions
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $title = 'Package definition report';
+my $name = 'package definition';
+
+my $select = '';
+my $addl_from = '';
+my @where = ();
+my @order_by = ();
+my @header = ();
+my @fields = ();
+my @links = ();
+my $align = '';
+
+if (1) { #commission reports
+
+ if (1) { #employee commission reports
+
+ $select = 'DISTINCT usernum, username, part_pkg.*';
+
+ $addl_from .= ' CROSS JOIN access_user ';
+
+ if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
+
+ #XXX in this context, agent virt for employees, not package defs
+ my $access_user = qsearchs('access_user', { 'usernum' => $1, })
+ or die "unknown usernum";
+
+ $title = $access_user->name;
+
+ } else {
+
+ push @header, 'Employee';
+ push @fields, sub { shift->get('username'); }; #access_user->name
+ push @links, ''; #link to employee edit w/ACL?
+ $align .= 'c';
+
+ push @order_by, 'usernum'; #join to username? we're mostly interested in grouping rather than order
+
+ $title = 'Employee';
+
+ }
+
+ } elsif (0) { #agent commission reports
+
+ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+
+ #agent virt
+ my $agent = qsearchs('agent', { 'agentnum' => $1 })
+ or die "unknown agentnum";
+
+ $title = $agent->agent;
+
+ push @header, 'Agent';
+ push @fields, sub { 'XXXagent' };
+ push @links, ''; #link to agent edit w/ACL?
+ $align .= 'c';
+
+ push @order_by, 'agentnum'; #join to agent? we're mostly interested in grouping rather than order
+
+ } else {
+ $title = 'Agent';
+ }
+
+ }
+
+ $title .= ' commission report';
+ $name = "commissionable $name";
+
+
+}
+
+push @header, 'Package definition';
+push @fields, 'pkg_comment';
+push @links, ''; #link to pkg definition edit w/ACL?
+$align .= 'l';
+
+if (1) { #commission reports
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+ my $match = '';
+ if (1) { #employee commission reports
+ $match = 'cust_pkg.usernum = access_user.usernum';
+ } elsif (0) { #agent commission reports
+ $match = 'cust_main.agentnum = agent.agentnum';
+ }
+
+ my $from_cust_bill_pkg_where = "FROM cust_bill_pkg
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ WHERE cust_bill_pkg.pkgnum > 0
+ AND cust_bill._date >= $beginning
+ AND cust_bill._date <= $ending ";
+ my $and = " AND $match
+ AND cust_pkg.pkgpart = part_pkg.pkgpart";
+
+ push @where, "EXISTS( SELECT 1 $from_cust_bill_pkg_where $and )";
+
+ push @header, '#'; # of sales';
+ push @links, ''; #link to detail report
+ $align .= 'r';
+ push @fields, 'num_cust_pkg';
+ $select .= ", ( SELECT COUNT(DISTINCT pkgnum)
+ $from_cust_bill_pkg_where $and )
+ AS num_cust_pkg";
+# push @fields, sub {
+# my $part_pkg = shift;
+# my $sql =
+# #"SELECT COUNT( SELECT DISTINCT pkgnum $from_cust_bill_pkg_where )";
+# "SELECT COUNT(DISTINCT pkgnum) $from_cust_bill_pkg_where";
+# my $sth = dbh->prepare($sql) or die dbh->errstr;
+# $sth->execute or die $sth->errstr;
+# $sth->fetchrow_arrayref->[0];
+# };
+
+ push @header, 'Sales';
+ push @links, ''; #link to detail report
+ $align .= 'r';
+# push @fields, sub { $money_char. sprintf('%.2f', shift->get('pkg_sales')); };
+# $select .=
+# ", SUM( SELECT setup+recur $from_cust_bill_pkg_where ) AS pkg_sales";
+ push @fields, sub {
+ my $part_pkg = shift;
+ my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $from_cust_bill_pkg_where AND pkgpart = ? AND ";
+ my @arg = ($part_pkg->pkgpart);
+ if (1) { #employee commission reports
+ $sql .= 'usernum = ?';
+ push @arg, $part_pkg->get('usernum');
+ } elsif (0) { #agent commission reports
+ $match = 'cust_main.agentnum = agent.agentnum';
+ }
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute(@arg) or die $sth->errstr;
+ $money_char. sprintf('%.2f', $sth->fetchrow_arrayref->[0] );
+ };
+
+ push @header, 'Commission';
+ push @links, ''; #link to detail report
+ $align .= 'r';
+ #push @fields, sub { $money_char. sprintf('%.2f', shift->get('pkg_commission')); };
+ push @fields, sub {
+ my $part_pkg = shift;
+ my $sql = "SELECT SUM(amount) FROM cust_credit
+ LEFT JOIN cust_event USING ( eventnum )
+ LEFT JOIN part_event USING ( eventpart )
+ LEFT JOIN cust_pkg ON ( cust_event.tablenum = cust_pkg.pkgnum )
+ WHERE eventnum IS NOT NULL
+ AND action IN ( 'pkg_employee_credit',
+ 'pkg_employee_credit_pkg'
+ )
+ AND cust_credit._date >= $beginning
+ AND cust_credit._date <= $ending
+ AND pkgpart = ?
+ AND cust_credit.custnum = ?
+ ";
+ my @arg = ($part_pkg->pkgpart);
+ if (1) { #employee commission reports
+
+ #XXX in this context, agent virt for employees, not package defs
+ my $access_user = qsearchs('access_user', { 'usernum' => $part_pkg->get('usernum'), })
+ or die "unknown usernum";
+
+ return 0 unless $access_user->user_custnum;
+ push @arg, $access_user->user_custnum;
+
+ } elsif (0) { #agent commission reports
+ push @arg, 'XXXagent_custnum'; #$agent->agent_custnum
+ }
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute(@arg) or die $sth->errstr;
+ $money_char. sprintf('%.2f', $sth->fetchrow_arrayref->[0] );
+
+ };
+
+}
+
+push @order_by, 'pkgpart'; #pkg?
+
+$select ||= 'part_pkg.*';
+my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : '';
+my $order_by = join(', ', @order_by);
+
+my $count_query = "SELECT COUNT(*) FROM part_pkg $addl_from $extra_sql";
+
+</%init>
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
new file mode 100755
index 0000000..ebd323e
--- /dev/null
+++ b/httemplate/search/pay_batch.cgi
@@ -0,0 +1,130 @@
+<% include( 'elements/search.html',
+ 'title' => 'Payment Batches',
+ 'name_singular' => 'batch',
+ 'query' => { 'table' => 'pay_batch',
+ 'hashref' => $hashref,
+ 'extra_sql' => "$extra_sql ORDER BY batchnum DESC",
+ },
+ 'count_query' => "$count_query $extra_sql",
+ 'header' => [ 'Batch',
+ 'Type',
+ 'First Download',
+ 'Last Upload',
+ 'Item Count',
+ 'Amount',
+ 'Status',
+ ],
+ 'align' => 'rcllrrc',
+ 'fields' => [ 'batchnum',
+ sub {
+ FS::payby->shortname(shift->payby);
+ },
+ sub {
+ my $self = shift;
+ my $_date = $self->download;
+ if ( $_date ) {
+ time2str("%a %b %e %T %Y", $_date);
+ } elsif ( $self->status eq 'O' ) {
+ 'Download batch';
+ } else {
+ '';
+ }
+ },
+ sub {
+ my $self = shift;
+ my $_date = $self->upload;
+ if ( $_date ) {
+ time2str("%a %b %e %T %Y", $_date);
+ } elsif ( $self->status eq 'I' ) {
+ 'Upload results';
+ } else {
+ '';
+ }
+ },
+ sub {
+ my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+ my $sth = dbh->prepare($st)
+ or die dbh->errstr. "doing $st";
+ $sth->execute
+ or die "Error executing \"$st\": ". $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+ },
+ sub {
+ my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+ my $sth = dbh->prepare($st)
+ or die dbh->errstr. "doing $st";
+ $sth->execute
+ or die "Error executing \"$st\": ". $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+ },
+ sub {
+ $statusmap{shift->status};
+ },
+ ],
+ 'links' => [
+ $link,
+ '',
+ sub { shift->status eq 'O' ? $link : '' },
+ sub { shift->status eq 'I' ? $link : '' },
+ ],
+ 'size' => [
+ '',
+ '',
+ sub { shift->status eq 'O' ? "+1" : '' },
+ sub { shift->status eq 'I' ? "+1" : '' },
+ ],
+ 'style' => [
+ '',
+ '',
+ sub { shift->status eq 'O' ? "b" : '' },
+ sub { shift->status eq 'I' ? "b" : '' },
+ ],
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports')
+ || $FS::CurrentUser::CurrentUser->access_right('Process batches');
+
+my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved');
+my $hashref = {};
+my $count_query = 'SELECT COUNT(*) FROM pay_batch';
+
+my($begin, $end) = ( '', '' );
+
+my @where;
+if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ $begin = parse_datetime($1);
+ push @where, "download >= $begin";
+}
+if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ $end = parse_datetime($1) + 86399;
+ push @where, "download < $end";
+}
+
+my @status;
+if ( $cgi->param('open') ) {
+ push @status, "O";
+}
+
+if ( $cgi->param('intransit') ) {
+ push @status, "I";
+}
+
+if ( $cgi->param('resolved') ) {
+ push @status, "R";
+}
+
+push @where,
+ scalar(@status) ? q!(status='! . join(q!' OR status='!, @status) . q!')!
+ : q!status='X'!; # kludgy, X is unused at present
+
+my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : '';
+
+my $link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ];
+
+</%init>
diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html
new file mode 100644
index 0000000..5907169
--- /dev/null
+++ b/httemplate/search/pay_batch.html
@@ -0,0 +1,33 @@
+<% include('/elements/header.html', 'Batch criteria' ) %>
+
+<FORM ACTION="pay_batch.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD>
+ <TD>Show open batches</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="intransit" VALUE="1" CHECKED></TD>
+ <TD>Show in-transit batches</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="resolved" VALUE="1" CHECKED></TD>
+ <TD>Show resolved batches</TD>
+ </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Batches">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/phone_avail.html b/httemplate/search/phone_avail.html
new file mode 100644
index 0000000..2388d25
--- /dev/null
+++ b/httemplate/search/phone_avail.html
@@ -0,0 +1,102 @@
+<% include( 'elements/search.html',
+ 'title' => 'Phone Number (DID) Search Results',
+ 'name_singular' => 'phone number',
+ 'query' => {
+ 'table' => 'phone_avail',
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'phone_avail.*',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $search,
+ 'addl_from' => $addl_from,
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'State',
+ 'Phone Number',
+ 'Export',
+ 'Service',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ 'availnum',
+ 'state',
+ sub { my $pn = shift;
+ '+'. $pn->countrycode. ' '.
+ $pn->npa. ' '. $pn->nxx. '-'. $pn->station;
+ },
+ 'exportnum', #XXX
+ #sub { },
+ 'svcnum', #XXX
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'align' => 'rllll'.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ '',
+ '',
+ '',
+ '', #XXX #$export_link
+ '', #XXX #$svc_link
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my @search = ();
+
+if ( $cgi->param('availbatch') =~ /^([\w\/\:\-\.]+)$/ ) {
+ push @search, "availbatch = '$1'";
+}
+
+# #here is the agent virtualization
+# push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $search = scalar(@search)
+ ? ' WHERE '. join(' AND ', @search)
+ : '';
+
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ #' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my $count_query = "SELECT COUNT(*) FROM phone_avail $search"; #$addl_from?
+
+my $link_cust = sub {
+ my $phone_avail = shift;
+ if ( $phone_avail->svcnum ) {
+ my $cust_svc = $phone_avail->svc_phone->cust_svc;
+ if ( $cust_svc->pkgnum ) {
+ #my $cust_main = $cust_svc->cust_pkg->cust_main;
+ return [ "${p}view/cust_main.cgi?", 'custnum' ];
+ }
+ }
+ '';
+};
+
+</%init>
diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html
new file mode 100644
index 0000000..3640351
--- /dev/null
+++ b/httemplate/search/prepay_credit.html
@@ -0,0 +1,67 @@
+<% include( 'elements/search.html',
+ 'title' => 'Unused Prepaid Cards'.
+ ($agent ? ' for '. $agent->agent : ''),
+ 'menubar' => [
+ 'Generate cards' => $p.'edit/prepay_credit.cgi',
+ ],
+ 'name' => 'prepaid cards',
+ 'query' => { 'table' => 'prepay_credit',
+ 'hashref' => $hashref,
+ },
+ 'count_query' => $count_query,
+ #'redirect' => $link,
+ 'header' => [ '#', qw(Amount Time Upload Download Total Agent) ],
+ 'fields' => [
+ 'identifier',
+ sub { sprintf('$%.2f', shift->amount ) },
+ sub { my $c = shift;
+ $c->seconds ? duration_exact($c->seconds) : ''
+ },
+ sub { my $c = shift;
+ $c->upbytes
+ ? FS::UI::bytecount::bytecount_unexact($c->upbytes)
+ : ''
+ },
+ sub { my $c = shift;
+ $c->downbytes
+ ? FS::UI::bytecount::bytecount_unexact($c->downbytes)
+ : ''
+ },
+ sub { my $c = shift;
+ $c->totalbytes
+ ? FS::UI::bytecount::bytecount_unexact($c->totalbytes)
+ : ''
+ },
+ sub { my $agent = shift->agent;
+ $agent ? $agent->agent : '';
+ },
+ ],
+ 'links' => [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ sub { my $agent = shift->agent;
+ $agent ? [ "${p}edit/agent.cgi?", 'agentnum' ] : '';
+ },
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agent = '';
+my $hashref = {};
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+$hashref->{agentnum} = $1;
+$agent = qsearchs('agent', { 'agentnum' => $1 } );
+}
+
+my $count_query = 'SELECT COUNT(*) FROM prepay_credit';
+$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent;
+
+</%init>
diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html
new file mode 100644
index 0000000..12e3e18
--- /dev/null
+++ b/httemplate/search/prospect_main.html
@@ -0,0 +1,74 @@
+<% include('elements/search.html',
+ 'title' => 'Prospect Search Results',
+ 'name_singular' => 'prospect',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Prospect',
+ 'Contact(s)',
+ ],
+ 'fields' => [ 'prospectnum',
+ 'company',
+ sub {
+ my $pm = shift;
+ [ map {
+ [ { 'data' => $_->line, }, ];
+ }
+ $pm->contact
+ ];
+ },
+ ],
+ 'links' => [ '',
+ $link,
+ '', #link to contact edit???
+ ],
+ 'agent_virt' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List prospects');
+
+my %search_hash = ();
+
+#$search_hash{'query'} = $cgi->keywords;
+
+#scalars
+my @scalars = qw (
+ agentnum
+);
+
+for my $param ( @scalars ) {
+ $search_hash{$param} = scalar( $cgi->param($param) )
+ if $cgi->param($param);
+}
+
+#lists
+#for my $param () {
+# $search_hash{$param} = [ $cgi->param($param) ];
+#}
+
+# parse dates
+#foreach my $field (qw( signupdate )) {
+#
+# my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+#
+# next if $beginning == 0 && $ending == 4294967295;
+# #or $disable{$cgi->param('status')}->{$field};
+#
+# $search_hash{$field} = [ $beginning, $ending ];
+#
+#}
+
+my $query = FS::prospect_main->search(\%search_hash);
+my $count_query = delete($query->{'count_query'});
+#my @extra_headers = @{ delete($query->{'extra_headers'}) };
+#my @extra_fields = @{ delete($query->{'extra_fields'}) };
+
+my $link = sub {
+ my $prospect_main = shift;
+ [ "${p}view/prospect_main.html?", 'prospectnum' ];
+};
+
+</%init>
diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html
new file mode 100644
index 0000000..e5f7aed
--- /dev/null
+++ b/httemplate/search/queue.html
@@ -0,0 +1,142 @@
+<% include( 'elements/search.html',
+ 'title' => 'Job Queue',
+ 'name' => 'jobs',
+ 'html_form' => qq!<FORM NAME="jobForm" ACTION="$p/misc/queue.cgi" METHOD="POST">!,
+ 'query' => { 'table' => 'queue',
+ 'hashref' => $hashref,
+ 'extra_sql' => 'ORDER BY jobnum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Job',
+ 'Args',
+ 'Date',
+ 'Status',
+ 'Account', # unless $hashref->{'svcnum'}
+ '', # checkbox column
+ ],
+ 'fields' => [
+ 'jobnum',
+ 'job',
+ sub {
+ my $queue = shift;
+ if ( $dangerous
+ || $queue->job !~ /^FS::part_export::/
+ || !$noactions
+ )
+ {
+ encode_entities( join(' ', $queue->args) );
+ } else {
+ '';
+ }
+ },
+ sub {
+ time2str( "%a %b %e %T %Y", shift->_date );
+ },
+ sub {
+ my $queue = shift;
+ my $jobnum = $queue->jobnum;
+ 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/
+ || $status =~ /^done/
+ );
+ if ( $changable ) {
+ $status .= qq! (!;
+ $status .=
+ qq! &nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A>&nbsp;|!
+ unless $status =~ /^done/;
+ $status .=
+ qq!&nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A>&nbsp;)!;
+ }
+ $status;
+ },
+ sub {
+ my $queue = shift;
+ # return '' if $hashref->{'svcnum'}
+ my $cust_svc = $queue->cust_svc;
+ my $account;
+ if ( $cust_svc ) {
+ my $table = $cust_svc->part_svc->svcdb;
+ my $label = ( $cust_svc->label )[1];
+ qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
+ qq!">$label</A>!;
+ } else {
+ '';
+ }
+ },
+ sub {
+ my $queue = shift;
+ my $jobnum = $queue->jobnum;
+ my $status = $queue->status;
+ my $changable = $dangerous
+ || ( ! $noactions
+ && $status eq 'failed'
+ || $status eq 'locked'
+ );
+ if ( $changable ) {
+ $areboxes = 1;
+ qq!<INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1">!;
+ } else {
+ '';
+ }
+ },
+ ],
+ #'links' => [
+ # '',
+ # '',
+ # '',
+ # '',
+ # '',
+ # '', #$acct_link,
+ # '',
+ # ],
+ 'html_foot' => sub {
+ if ( $areboxes ) {
+ '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'.
+ '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'.
+ '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
+ '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>'.
+ '<SCRIPT TYPE="text/javascript">'.
+ ' function setAll(setTo) { '.
+ ' theForm = document.jobForm;'.
+ ' for (i=0,n=theForm.elements.length;i<n;i++)'.
+ ' if (theForm.elements[i].name.indexOf("jobnum") != -1)'.
+ ' theForm.elements[i].checked = setTo;'.
+ ' }'.
+ '</SCRIPT>';
+ } else {
+ '';
+ }
+ },
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Job queue');
+
+my $hashref = {};
+
+my $conf = new FS::Conf;
+my $dangerous = $conf->exists('queue_dangerous_controls');
+
+my $noactions = 0;
+
+my $count_query = 'SELECT COUNT(*) FROM queue'; # + $hashref
+
+my $areboxes = 0;
+
+</%init>
diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html
new file mode 100644
index 0000000..f7d6d20
--- /dev/null
+++ b/httemplate/search/reg_code.html
@@ -0,0 +1,40 @@
+<% include( 'elements/search.html',
+ 'title' => 'Unused Registration Codes for '.
+ $agent->agent,
+ 'name' => 'registration codes',
+ 'query' => { 'table' => 'reg_code',
+ 'hashref' => { 'agentnum' => $agentnum, },
+ },
+ 'count_query' => $count_query,
+ #'redirect' => $link,
+ 'header' => [ qw(Code Packages) ],
+ 'fields' => [
+ 'code',
+ sub {
+ map {
+ qq!<A HREF="${p}edit/part_pkg.cgi?!. $_->pkgpart. '">'.
+ $_->pkg_comment(nopkgpart => 1).
+ '</A><BR>'
+ } $_[0]->part_pkg
+ },
+ ],
+ 'links' => [
+ '',
+ #$plink,
+ '',
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = $cgi->param('agentnum');
+$agentnum =~ /^(\d+)$/ or errorpage("illegal agentnum $agentnum");
+$agentnum = $1;
+my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+
+my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum";
+
+</%init>
diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html
new file mode 100755
index 0000000..bc2a958
--- /dev/null
+++ b/httemplate/search/report_477.html
@@ -0,0 +1,201 @@
+<% include('/elements/header.html', 'FCC Form 477 Report' ) %>
+
+<FORM ACTION="477.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="active">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+ %>
+
+ <SCRIPT type="text/javascript">
+ function partchange(what) {
+ var id = 'part' + what.value;
+ var element = document.getElementById(id);
+ if (what.checked) {
+ element.style.display = '';
+ } else {
+ element.style.display = 'none';
+ }
+ }
+ </SCRIPT>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part IA?',
+ 'field' => 'part',
+ 'value' => 'IA',
+ 'onchange' => 'partchange(this)',
+ )
+ %>
+
+ <TR id='partIA' style="display:none"><TD>Part IA</TD><TD><TABLE>
+ <TR><TD>Download speeds</TD><TD>
+ <TABLE>
+% foreach my $speed ( @FS::Report::FCC_477::download ) {
+ <TR>
+ <TH><% $speed %></TH>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'part1_column_option',
+ 'disable_empty' => 1,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD>
+ <TD>Upload speeds</TD><TD>
+ <TABLE>
+% foreach my $speed ( @FS::Report::FCC_477::upload ) {
+ <TR>
+ <TH><% $speed %></TH>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'part1_row_option',
+ 'disable_empty' => 1,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD></TR>
+ <TR><TD>Technologies</TD><TD>
+ <TABLE>
+% my $i = 0;
+% foreach my $tech ( @FS::Report::FCC_477::technology ) {
+ <TR>
+ <TH><% $tech %></TH>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => "part1_technology_option_$i",
+ 'empty_label' => '(omit)',
+ )
+ %>
+ </TD>
+ </TR>
+% $i++
+% }
+ </TABLE></TD></TR>
+ </TABLE></TD></TR>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part IIA?',
+ 'field' => 'part',
+ 'value' => 'IIA',
+ 'onchange' => 'partchange(this)',
+ )
+ %>
+
+ <TR id='partIIA' style="display:none"><TD>Part IIA</TD><TD><TABLE>
+% foreach my $option ( @FS::Report::FCC_477::part2aoption ) {
+ <TR>
+ <TH><% $option %></TH>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'part2a_row_option',
+ )
+ %>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD></TR>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part IIB?',
+ 'field' => 'part',
+ 'value' => 'IIB',
+ 'onchange' => 'partchange(this)',
+ )
+ %>
+
+ <TR id='partIIB' style="display:none"><TD>Part IIB</TD><TD><TABLE>
+% foreach my $option ( @FS::Report::FCC_477::part2boption ) {
+ <TR>
+ <TH><% $option %></TH>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'part2b_row_option',
+ )
+ %>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD></TR>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part IV?',
+ 'field' => 'part',
+ 'value' => 'IV',
+ 'onchange' => 'partchange(this)',
+ )
+ %>
+
+ <TR id='partIV' style="display:none"><TD>Part IV</TD><TD><TABLE>
+ <% include( '/elements/tr-textarea.html',
+ 'label' => 'Explanatory notes',
+ 'id' => 'partIV',
+ 'field' => 'notes',
+ 'rows' => 15,
+ 'cols' => 80,
+ )
+ %>
+ </TABLE></TD></TR>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part V?',
+ 'field' => 'part',
+ 'value' => 'V',
+ )
+ %>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Enable part VI?',
+ 'field' => 'part',
+ 'value' => 'VI',
+ )
+ %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+
+</%init>
diff --git a/httemplate/search/report_agent_inventory.html b/httemplate/search/report_agent_inventory.html
new file mode 100644
index 0000000..af66043
--- /dev/null
+++ b/httemplate/search/report_agent_inventory.html
@@ -0,0 +1,26 @@
+<% include('/elements/header.html', 'Inventory summary per agent' ) %>
+
+<FORM ACTION="agent_inventory.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+%# select agents
+
+%# select inventory classes
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+#XXX List inventory
+
+</%init>
+
diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html
new file mode 100644
index 0000000..a50e4db
--- /dev/null
+++ b/httemplate/search/report_cdr.html
@@ -0,0 +1,148 @@
+<% include('/elements/header.html', 'Call Detail Record Search' ) %>
+
+<FORM ACTION="cdr.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Status: </TD>
+ <TD>
+ <SELECT NAME="freesidestatus">
+ <OPTION VALUE="">(all)
+ <OPTION VALUE="NULL">unprocessed
+ <OPTION VALUE="done">processed
+ </SELECT>
+ </TD>
+ </TR>
+
+% #if ( ) { # disable for everyone not using termination billing...
+% foreach my $termpart ( 1..1 ) { #qsearch('part_termination
+
+ <TR>
+ <TD ALIGN="right">Termination Status: </TD>
+ <TD>
+ <SELECT NAME="termpart<%$termpart%>status">
+ <OPTION VALUE="">(all)
+ <OPTION VALUE="NULL">unprocessed
+ <OPTION VALUE="done">processed
+ </SELECT>
+ </TD>
+ </TR>
+
+% }
+% #}
+
+ <% include ( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <TR>
+ <TD ALIGN="right">Source #: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="src">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Destination #: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="dst">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Destination Context: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="dcontext">
+ </TD>
+ </TR>
+
+
+ <TR>
+ <TD ALIGN="right">Charged Party #: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="charged_party">
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Duration (sec)',
+ 'field' => 'duration',
+ )
+ %>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Billable duration (sec)',
+ 'field' => 'billsec',
+ )
+ %>
+
+ <% include( '/elements/tr-select-cdrbatch.html' ) %>
+
+ <TR>
+ <TD ALIGN="right">Acct ID (one per-line):</TD>
+ <TD><TEXTAREA NAME="acctid"></TEXTAREA></TD>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
+ </TR>
+
+ <INPUT TYPE="hidden" NAME="show" VALUE="1">
+
+ <TR>
+ <TD COLSPAN=2>
+ <% include('/elements/checkboxes.html',
+ 'names_list' => $names_list,
+ 'element_name_prefix' => 'show_',
+ 'checked_callback' => sub { $show_default{$_[1]} },
+ # my($cgi, $name) = @_;
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search Call Detail Records">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+my @fields = fields('cdr');
+my $labels = FS::cdr->table_info->{'fields'};
+
+#XXX config
+my @show_default = qw(
+ calldate clid src dst dcontext charged_party
+ startdate answerdate enddate duration billsec
+ disposition amaflags accountcode userfield
+ rated_price upstream_price carrierid
+ svcnum freesidestatus freesiderewritestatus
+);
+my %show_default = map { $_=>1 } @show_default;
+
+my $names_list = [ map {
+ [ $_ => {
+ 'label' => 'Show '. ( $labels->{$_} || $_ )
+ }
+ ]
+ }
+ @fields
+ ];
+
+</%init>
diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html
new file mode 100644
index 0000000..00d566a
--- /dev/null
+++ b/httemplate/search/report_cust_bill.html
@@ -0,0 +1,51 @@
+<% include('/elements/header.html', 'Invoice Report' ) %>
+
+<FORM ACTION="cust_bill.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'Invoices for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ label => 'Charged',
+ field => 'charged',
+ )
+ %>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ label => 'Owed',
+ field => 'owed',
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD>
+ <TD>Show only open invoices</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="newest_percust" VALUE="1"></TD>
+ <TD>Show only the single most recent invoice per-customer</TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List invoices');
+
+</%init>
diff --git a/httemplate/search/report_cust_bill_pkg_discount.html b/httemplate/search/report_cust_bill_pkg_discount.html
new file mode 100644
index 0000000..f1879d4
--- /dev/null
+++ b/httemplate/search/report_cust_bill_pkg_discount.html
@@ -0,0 +1,47 @@
+<% include('/elements/header.html', 'Discount report' ) %>
+
+<FORM ACTION="cust_bill_pkg_discount.html" METHOD="GET">
+
+
+<TABLE>
+
+ <% include( '/elements/tr-select-otaker.html',
+ 'label' => 'Discounts by employee: ',
+ 'otakers' => \@otakers,
+ )
+ %>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Amount',
+ 'field' => 'amount',
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_pkg_discount")
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref};
+
+</%init>
diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html
new file mode 100644
index 0000000..9c719b7
--- /dev/null
+++ b/httemplate/search/report_cust_credit.html
@@ -0,0 +1,48 @@
+<% include('/elements/header.html', 'Credit report' ) %>
+
+<FORM ACTION="cust_credit.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE>
+
+ <% include( '/elements/tr-select-otaker.html',
+ 'label' => 'Credits by employee: ',
+ 'otakers' => \@otakers,
+ )
+ %>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Amount',
+ 'field' => 'amount',
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+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};
+
+</%init>
diff --git a/httemplate/search/report_cust_event.html b/httemplate/search/report_cust_event.html
new file mode 100644
index 0000000..e0d6242
--- /dev/null
+++ b/httemplate/search/report_cust_event.html
@@ -0,0 +1,49 @@
+<% include(
+ '/elements/header.html',
+ ( $cgi->param('failed') ? 'Failed billing events' : 'Billing events' ),
+ )
+%>
+
+ <FORM ACTION="cust_event.html" METHOD="GET">
+ <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') ? 1 : 0 %>">
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Status'
+ )
+ %>
+
+ <% include( '/elements/tr-select-payby.html',
+ 'label' => 'Customer payment type',
+ 'payby_type' => 'cust',
+ 'multiple' => 1,
+ 'all_selected' => 1,
+ )
+ %>
+
+ <% include( '/elements/tr-select-part_event.html',
+ 'label' => 'Events',
+ 'multiple' => 1,
+ 'all_selected' => 1,
+ )
+ %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports');
+
+</%init>
diff --git a/httemplate/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html
new file mode 100644
index 0000000..00cb9ed
--- /dev/null
+++ b/httemplate/search/report_cust_main-zip.html
@@ -0,0 +1,70 @@
+<% include('/elements/header.html', 'Zip code report') %>
+
+ <FORM ACTION="cust_main-zip.html" METHOD="GET">
+
+ <TABLE>
+
+ <TR>
+ <TD ALIGN="right">Billing or service zip</TD>
+ <TD>
+ <SELECT NAME="column">
+ <OPTION VALUE="zip">Billing zip
+ <OPTION VALUE="ship_zip">Service zip
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Ignore +4 for US zip codes</TD>
+ <TD><INPUT TYPE="checkbox" NAME="ignore_plus4" VALUE="yes" CHECKED> </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Show customers with status</TD>
+ <TD>
+ <SELECT NAME="status">
+ <OPTION VALUE="">all
+ <OPTION VALUE="prospect">prospect (no packages ever)
+ <OPTION SELECTED VALUE="uncancel">all except cancelled
+ <OPTION VALUE="active">active recurring packages
+ <OPTION VALUE="susp">suspended
+ <OPTION VALUE="cancel">cancelled
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Limit to customers with provisioned service</TD>
+ <TD>
+ <SELECT NAME="svcdb">
+ <OPTION VALUE="">(no)
+ <OPTION VALUE="svc_acct">Account (svc_acct)
+ <OPTION VALUE="svc_broadband">Broadband service (svc_broadband)
+ <OPTION VALUE="svc_domain">Domain (svc_domain)
+ <OPTION VALUE="svc_external">External service (svc_external)
+ <OPTION VALUE="svc_forward">Mail forward (svc_foward)
+ <OPTION VALUE="svc_pbx">PBX (svc_pbx)
+ <OPTION VALUE="svc_phone">Phone number (svc_phone)
+ <OPTION VALUE="svc_www">Hosting (svc_www)
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List zip codes');
+
+</%init>
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
new file mode 100755
index 0000000..eb1a662
--- /dev/null
+++ b/httemplate/search/report_cust_main.html
@@ -0,0 +1,154 @@
+<% include('/elements/header.html', 'Customer Report' ) %>
+
+<FORM ACTION="cust_main.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="bill">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Status'
+ )
+ %>
+
+ <% include( '/elements/tr-select-cust_class.html',
+ 'label' => 'Class',
+ 'multiple' => 1,
+ 'pre_options' => [ '' => '(none)' ],
+ 'all_selected' => 1,
+ )
+ %>
+
+% foreach my $field (qw( signupdate )) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => $field,
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% }
+
+ <% include( '/elements/tr-select-payby.html',
+ 'payby_type' => 'cust',
+ 'multiple' => 1,
+ 'all_selected' => 1,
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right">Payment expiration before</TD>
+ <TD>
+ <SELECT NAME="paydate_month" DISABLED>
+% foreach my $month ( 1 .. 12 ) {
+ <OPTION VALUE="<% $month %>"><% $month %></OPTION>
+% }
+ </SELECT>
+ /
+ <SELECT NAME="paydate_year" onChange="paydate_year_changed(this);">
+ <OPTION VALUE=""></OPTION>
+% my $lastyear = (localtime(time))[5] + 1899;
+% foreach my $year ( $lastyear .. $lastyear+12 ) {
+ <OPTION VALUE="<% $year %>"><% $year %></OPTION>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+ function paydate_year_changed(what) {
+ var value = what.options[what.selectedIndex].value;
+ var month_select = what.form.paydate_month;
+ if ( value == '' ) {
+ month_select.disabled = true;
+ } else {
+ month_select.disabled = false;
+ }
+ }
+ </SCRIPT>
+
+ <TR>
+ <TD ALIGN="right">Invoice terms</TD>
+ <TD>
+ <% include( '/elements/select-terms.html',
+ 'pre_options' => [ '' => 'all' ],
+ 'empty_value' => 'NULL',
+ )
+ %>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ label => 'Current balance',
+ field => 'current_balance',
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Include cancelled packages</TD>
+ <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD>
+ </TR>
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Without census tract</TD>
+ <TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
+ </TR>
+ <% include( '/elements/tr-select-cust-fields.html' ) %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Add package columns</TD>
+ <TD><INPUT TYPE="checkbox" NAME="flattened_pkgs"></TD>
+ </TR>
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') &&
+ $FS::CurrentUser::CurrentUser->access_right('List packages')
+ );
+
+my $conf = new FS::Conf;
+
+</%init>
+<%once>
+
+my %label = (
+ 'signupdate' => 'Signup date',
+);
+
+</%once>
diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html
new file mode 100644
index 0000000..6c10a2e
--- /dev/null
+++ b/httemplate/search/report_cust_pay.html
@@ -0,0 +1,116 @@
+<% include('/elements/header.html', $title ) %>
+
+<FORM ACTION="<% $void ? 'cust_pay_void.html' : 'cust_pay.cgi' %>" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Payments of type: </TD>
+ <TD>
+ <SELECT NAME="payby" onChange="payby_changed(this)">
+ <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="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ <OPTION VALUE="BILL">check</OPTION>
+ <OPTION VALUE="PREP">prepaid card</OPTION>
+ <OPTION VALUE="CASH">cash</OPTION>
+ <OPTION VALUE="WEST">Western Union</OPTION>
+ <OPTION VALUE="MCRD">manual credit card</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function payby_changed(what) {
+ if ( what.options[what.selectedIndex].value == 'BILL' ) {
+ document.getElementById('checkno_caption').style.color = '#000000';
+ what.form.payinfo.disabled = false;
+ what.form.payinfo.style.backgroundColor = '#ffffff';
+ } else {
+ document.getElementById('checkno_caption').style.color = '#bbbbbb';
+ what.form.payinfo.disabled = true;
+ what.form.payinfo.style.backgroundColor = '#dddddd';
+ }
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd">
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-otaker.html' ) %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Payment</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% if ( $void ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Voided</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => 'void',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+% }
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Amount',
+ 'field' => 'paid',
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $void = $cgi->param('void') ? 1 : 0;
+
+my $title = $void ? 'Voided payment report' : 'Payment report';
+
+</%init>
diff --git a/httemplate/search/report_cust_pay_batch.html b/httemplate/search/report_cust_pay_batch.html
new file mode 100644
index 0000000..2d3ef06
--- /dev/null
+++ b/httemplate/search/report_cust_pay_batch.html
@@ -0,0 +1,44 @@
+<% include('/elements/header.html', 'Batch payment report' ) %>
+
+<FORM ACTION="cust_pay_batch.cgi" METHOD="GET">
+
+<TABLE>
+
+ <TR>
+ <TD ALIGN="right">Payments of type: </TD>
+ <TD>
+ <SELECT NAME="payby">
+ <OPTION VALUE="">all</OPTION>
+ <OPTION VALUE="CARD">credit card</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0
+ )
+ %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="dcln" VALUE="1" CHECKED></TD>
+ <TD>Include approved items</TD>
+ </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html
new file mode 100755
index 0000000..58fcf61
--- /dev/null
+++ b/httemplate/search/report_cust_pkg.html
@@ -0,0 +1,208 @@
+<% include('/elements/header.html', $title ) %>
+
+<FORM ACTION="cust_pkg.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="bill">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+% unless ( $custnum ) {
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'disable_empty' => 0,
+ )
+ %>
+% }
+
+ <% include( '/elements/tr-select-cust_pkg-status.html',
+ 'onchange' => 'status_changed(this);',
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function status_changed(what) {
+
+% foreach my $status ( '', FS::cust_pkg->statuses() ) {
+
+ if ( what.options[what.selectedIndex].value == '<% $status %>' ) {
+
+% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+% if ( $disable{$status}->{$field} ) {
+
+ what.form.<% $field %>_beginning_text.disabled = true;
+ what.form.<% $field %>_ending_text.disabled = true;
+ what.form.<% $field %>_beginning_text.style.backgroundColor = '#dddddd';
+ what.form.<% $field %>_ending_text.style.backgroundColor = '#dddddd';
+
+ what.form.<% $field %>_beginning_button.style.display = 'none';
+ what.form.<% $field %>_ending_button.style.display = 'none';
+ what.form.<% $field %>_beginning_disabled.style.display = '';
+ what.form.<% $field %>_ending_disabled.style.display = '';
+
+% } else {
+
+ what.form.<% $field %>_beginning_text.disabled = false;
+ what.form.<% $field %>_ending_text.disabled = false;
+ what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff';
+ what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff';
+
+ what.form.<% $field %>_beginning_button.style.display = '';
+ what.form.<% $field %>_ending_button.style.display = '';
+ what.form.<% $field %>_beginning_disabled.style.display = 'none';
+ what.form.<% $field %>_ending_disabled.style.display = 'none';
+
+% }
+% }
+
+ }
+
+% }
+
+ }
+
+ </SCRIPT>
+
+ <% include( '/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+ %>
+
+% if ( scalar( qsearch( 'part_pkg_report_option', { 'disabled' => '' } ) ) ) {
+
+ <% include( '/elements/tr-select-table.html',
+ 'label' => 'Report classes',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'report_option',
+ 'multiple' => 'multiple',
+ )
+ %>
+
+% }
+
+% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => $field,
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% }
+
+ <SCRIPT TYPE="text/javascript">
+
+ function custom_changed(what) {
+
+ if ( what.checked ) {
+
+ what.form.pkgpart.disabled = true;
+ what.form.pkgpart.style.backgroundColor = '#dddddd';
+
+ } else {
+
+ what.form.pkgpart.disabled = false;
+ what.form.pkgpart.style.backgroundColor = '#ffffff';
+
+ }
+
+ }
+
+ </SCRIPT>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Custom packages',
+ 'field' => 'custom',
+ 'value' => 1,
+ 'onchange' => 'custom_changed(this);',
+ )
+ %>
+
+ <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
+ </TR>
+ <% include( '/elements/tr-select-cust-fields.html' ) %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+
+my $title = 'Package Report';
+
+my $custnum = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or die "unknown custnum $custnum";
+ $title .= ': '. $cust_main->name;
+}
+
+</%init>
+<%once>
+
+my %label = (
+ 'setup' => 'Setup',
+ 'last_bill' => 'Last bill',
+ 'bill' => 'Next bill',
+ 'adjourn' => 'Adjourns',
+ 'susp' => 'Suspended',
+ 'expire' => 'Expires',
+ 'cancel' => 'Cancelled',
+);
+
+#false laziness w/cust_pkg.cgi
+my %disable = (
+ 'all' => {},
+ 'not yet billed' => { 'setup'=>1, 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
+ 'active' => { 'susp'=>1, 'cancel'=>1 },
+ 'suspended' => { 'cancel' => 1 },
+ 'cancelled' => {},
+ '' => {},
+);
+
+#hmm?
+my %checkbox = (
+ 'setup' => 0,
+ 'last_bill' => 0,
+ 'bill' => 0,
+ 'susp' => 1,
+ 'expire' => 1,
+ 'cancel' => 1,
+);
+
+</%once>
diff --git a/httemplate/search/report_cust_pkg_discount.html b/httemplate/search/report_cust_pkg_discount.html
new file mode 100644
index 0000000..7ebd44f
--- /dev/null
+++ b/httemplate/search/report_cust_pkg_discount.html
@@ -0,0 +1,50 @@
+<% include('/elements/header.html', 'Package discount report' ) %>
+
+<FORM ACTION="cust_pkg_discount.html" METHOD="GET">
+
+
+<TABLE>
+
+ <TR>
+ <TD>Discount status</TD>
+ <TD>
+ <SELECT NAME="status">
+ <OPTION VALUE="active">Active
+ <OPTION VALUE="expired">Expired
+ <OPTION VALUE="">(all)
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-otaker.html',
+ 'label' => 'Discounts by employee: ',
+ 'otakers' => \@otakers,
+ )
+ %>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_pkg_discount")
+ or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref};
+
+</%init>
diff --git a/httemplate/search/report_cust_refund.html b/httemplate/search/report_cust_refund.html
new file mode 100644
index 0000000..4d31100
--- /dev/null
+++ b/httemplate/search/report_cust_refund.html
@@ -0,0 +1,116 @@
+<% include('/elements/header.html', $title ) %>
+
+<FORM ACTION="<% $void ? 'cust_refund_void.html' : 'cust_refund.html' %>" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Refunds of type: </TD>
+ <TD>
+ <SELECT NAME="payby" onChange="payby_changed(this)">
+ <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="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ <OPTION VALUE="BILL">check</OPTION>
+ <OPTION VALUE="PREP">prepaid card</OPTION>
+ <OPTION VALUE="CASH">cash</OPTION>
+ <OPTION VALUE="WEST">Western Union</OPTION>
+ <OPTION VALUE="MCRD">manual credit card</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function payby_changed(what) {
+ if ( what.options[what.selectedIndex].value == 'BILL' ) {
+ document.getElementById('checkno_caption').style.color = '#000000';
+ what.form.payinfo.disabled = false;
+ what.form.payinfo.style.backgroundColor = '#ffffff';
+ } else {
+ document.getElementById('checkno_caption').style.color = '#bbbbbb';
+ what.form.payinfo.disabled = true;
+ what.form.payinfo.style.backgroundColor = '#dddddd';
+ }
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd">
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-otaker.html' ) %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Refund</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% if ( $void ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Voided</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => 'void',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+% }
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Amount',
+ 'field' => 'paid',
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $void = $cgi->param('void') ? 1 : 0;
+
+my $title = $void ? 'Voided refund report' : 'Refund report';
+
+</%init>
diff --git a/httemplate/search/report_employee_commission.html b/httemplate/search/report_employee_commission.html
new file mode 100644
index 0000000..51afad3
--- /dev/null
+++ b/httemplate/search/report_employee_commission.html
@@ -0,0 +1,30 @@
+<% include('/elements/header.html', 'Employee commission report' ) %>
+
+<FORM ACTION="part_pkg.html">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+%#
+%# <% include( '/elements/tr-select-agent.html',
+%# 'curr_value' => scalar( $cgi->param('agentnum') ),
+%# 'disable_empty' => 0,
+%# )
+%# %>
+%#
+
+<% include( '/elements/tr-select-user.html' ) %>
+
+<% include( '/elements/tr-input-beginning_ending.html', ) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_h_cust_pay.html b/httemplate/search/report_h_cust_pay.html
new file mode 100644
index 0000000..fe7c4a9
--- /dev/null
+++ b/httemplate/search/report_h_cust_pay.html
@@ -0,0 +1,124 @@
+<% include('/elements/header.html', 'Payment transaction history' ) %>
+
+<FORM ACTION="h_cust_pay.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+%#history stuff
+ <TR>
+ <TD ALIGN="right">Search transactions for: </TD>
+ <TD>
+ <SELECT NAME="history_action">
+ <OPTION VALUE="insert,replace_old,replace_new,delete">(all changes)
+ <OPTION VALUE="delete">Insertions
+ <OPTION VALUE="replace_old,replace_new">Replacements
+ <OPTION VALUE="delete">Deletions
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Transaction date: </TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => 'history_date',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+%#eo history stuff
+
+ <TR>
+ <TD ALIGN="right">Payments of type: </TD>
+ <TD>
+ <SELECT NAME="payby" onChange="payby_changed(this)">
+ <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="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ <OPTION VALUE="BILL">check</OPTION>
+ <OPTION VALUE="PREP">prepaid card</OPTION>
+ <OPTION VALUE="CASH">cash</OPTION>
+ <OPTION VALUE="WEST">Western Union</OPTION>
+ <OPTION VALUE="MCRD">manual credit card</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function payby_changed(what) {
+ if ( what.options[what.selectedIndex].value == 'BILL' ) {
+ document.getElementById('checkno_caption').style.color = '#000000';
+ what.form.payinfo.disabled = false;
+ what.form.payinfo.style.backgroundColor = '#ffffff';
+ } else {
+ document.getElementById('checkno_caption').style.color = '#bbbbbb';
+ what.form.payinfo.disabled = true;
+ what.form.payinfo.style.backgroundColor = '#dddddd';
+ }
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd">
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-otaker.html' ) %>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Payment</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-input-lessthan_greaterthan.html',
+ 'label' => 'Amount',
+ 'field' => 'paid',
+ )
+ %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_newtax.cgi b/httemplate/search/report_newtax.cgi
new file mode 100755
index 0000000..6a2cbb0
--- /dev/null
+++ b/httemplate/search/report_newtax.cgi
@@ -0,0 +1,207 @@
+<% include("/elements/header.html", "$agentname Tax Report - ".
+ ( $beginning
+ ? time2str('%h %o %Y ', $beginning )
+ : ''
+ ).
+ 'through '.
+ ( $ending == 4294967295
+ ? 'now'
+ : time2str('%h %o %Y', $ending )
+ )
+ )
+%>
+
+<% include('/elements/table-grid.html') %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">&nbsp;&nbsp;&nbsp;&nbsp;</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
+ </TR>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% foreach my $tax ( @taxes ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $link = '';
+% if ( $tax->{'label'} ne 'Total' ) {
+% $link = ';'. $tax->{'url_param'};
+% }
+%
+
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $tax->{'label'} %></TD>
+ <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax->{'tax'} ) %></A>
+ </TD>
+ <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"></TD>
+ <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+ <A HREF="<% $baselink. $link %>;istax=1;iscredit=rate"><% $money_char %><% sprintf('%.2f', $tax->{'credit'} ) %></A>
+ </TD>
+ <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+ </TR>
+% }
+
+</TABLE>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+my $join_cust = "
+ JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+";
+
+my $join_loc = "LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum )";
+my $join_tax_loc = "LEFT JOIN tax_rate_location USING ( taxratelocationnum )";
+
+my $addl_from = " $join_cust $join_loc $join_tax_loc ";
+
+my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+
+my $agentname = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "agent not found" unless $agent;
+ $agentname = $agent->agent;
+ $where .= ' AND cust_main.agentnum = '. $agent->agentnum;
+}
+
+# my ( $location_sql, @location_param ) = FS::cust_pkg->location_sql;
+# $where .= " AND $location_sql";
+#my @taxparam = ( 'itemdesc', @location_param );
+# now something along the lines of geocode matching ?
+#$where .= FS::cust_pkg->_location_sql_where('cust_tax_location');;
+my @taxparam = ( 'itemdesc', 'tax_rate_location.state', 'tax_rate_location.county', 'tax_rate_location.city', 'cust_bill_pkg_tax_rate_location.locationtaxid' );
+
+my $select = 'DISTINCT itemdesc,locationtaxid,tax_rate_location.state,tax_rate_location.county,tax_rate_location.city';
+
+my $tax = 0;
+my $credit = 0;
+my %taxes = ();
+my %basetaxes = ();
+foreach my $t (qsearch({ table => 'cust_bill_pkg',
+ select => $select,
+ hashref => { pkgpart => 0 },
+ addl_from => $addl_from,
+ extra_sql => $where,
+ })
+ )
+{
+ my @params = map { my $f = $_; $f =~ s/.*\.//; $f } @taxparam;
+ my $label = join('~', map { $t->$_ } @params);
+ $label = 'Tax'. $label if $label =~ /^~/;
+ unless ( exists( $taxes{$label} ) ) {
+ my ($baselabel, @trash) = split /~/, $label;
+
+ $taxes{$label}->{'label'} = join(', ', split(/~/, $label) );
+ $taxes{$label}->{'url_param'} =
+ join(';', map { "$_=". uri_escape($t->$_) } @params);
+
+ my $taxwhere = "FROM cust_bill_pkg $addl_from $where AND payby != 'COMP' ".
+ "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+
+ my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
+ " $taxwhere AND cust_bill_pkg.pkgnum = 0";
+
+ my $x = scalar_sql($t, [ map { $_, $_ } @params ], $sql );
+ $tax += $x;
+ $taxes{$label}->{'tax'} += $x;
+
+ my $creditfrom = " JOIN cust_credit_bill_pkg USING (billpkgnum,billpkgtaxratelocationnum) ";
+ my $creditwhere = "FROM cust_bill_pkg $addl_from $creditfrom $where ".
+ "AND payby != 'COMP' ".
+ "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+
+ $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
+ " $creditwhere AND cust_bill_pkg.pkgnum = 0";
+
+ my $y = scalar_sql($t, [ map { $_, $_ } @params ], $sql );
+ $credit += $y;
+ $taxes{$label}->{'credit'} += $y;
+
+ unless ( exists( $taxes{$baselabel} ) ) {
+
+ $basetaxes{$baselabel}->{'label'} = $baselabel;
+ $basetaxes{$baselabel}->{'url_param'} = "itemdesc=$baselabel";
+ $basetaxes{$baselabel}->{'base'} = 1;
+
+ }
+
+ $basetaxes{$baselabel}->{'tax'} += $x;
+ $basetaxes{$baselabel}->{'credit'} += $y;
+
+ }
+
+ # calculate customer-exemption for this tax
+ # calculate package-exemption for this tax
+ # calculate monthly exemption (texas tax) for this tax
+ # count up all the cust_tax_exempt_pkg records associated with
+ # the actual line items.
+}
+
+
+#ordering
+my @taxes = ();
+
+foreach my $tax ( sort { $a cmp $b } keys %taxes ) {
+ my ($base, @trash) = split '~', $tax;
+ my $basetax = delete( $basetaxes{$base} );
+ if ($basetax) {
+ if ( $basetax->{tax} == $taxes{$tax}->{tax} ) {
+ $taxes{$tax}->{base} = 1;
+ } else {
+ push @taxes, $basetax;
+ }
+ }
+ push @taxes, $taxes{$tax};
+}
+
+push @taxes, {
+ 'label' => 'Total',
+ 'url_param' => '',
+ 'tax' => $tax,
+ 'credit' => $credit,
+ 'base' => 1,
+};
+
+#--
+
+#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 ) = @_;
+ 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;
+}
+
+my $dateagentlink = "begin=$beginning;end=$ending";
+$dateagentlink .= ';agentnum='. $cgi->param('agentnum')
+ if length($agentname);
+my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink";
+
+</%init>
diff --git a/httemplate/search/report_newtax.html b/httemplate/search/report_newtax.html
new file mode 100755
index 0000000..2588b48
--- /dev/null
+++ b/httemplate/search/report_newtax.html
@@ -0,0 +1,23 @@
+<% include('/elements/header.html', 'Tax Report' ) %>
+
+<FORM ACTION="report_queued_newtax.cgi" METHOD="GET">
+
+<TABLE>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi
new file mode 100644
index 0000000..bfb699b
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.cgi
@@ -0,0 +1,235 @@
+<% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report') %>
+
+<% include( '/elements/table-grid.html' ) %>
+
+ <TR>
+% if ( scalar(@agentnums) > 1 ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Agent</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% $actual_label %>Unearned Revenue</TH>
+% if ( $legacy ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Legacy Unearned Revenue</TH>
+% }
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% push @agentnums, 0 unless scalar(@agentnums) < 2;
+% foreach my $agentnum (@agentnums) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $alink = $agentnum ? "$link;agentnum=$agentnum" : $link;
+%
+% my $agent_name = 'Total';
+% if ( $agentnum ) {
+% my $agent = qsearchs('agent', { 'agentnum' => $agentnum })
+% or die "unknown agentnum $agentnum";
+% $agent_name = $agent->agent;
+% }
+
+ <TR>
+
+% if ( scalar(@agentnums) > 1 ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent_name |h %></TD>
+% }
+
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $alink %>"><% $money_char %><% $total{$agentnum} %></A></TD>
+
+% if ( $legacy ) {
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $now == $time ? $money_char.$total_legacy{$agentnum} : '<i>N/A</i>'%>
+ </TD>
+% }
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<BR>
+<% $actual_label %><% $actual_label ? 'u' : 'U' %>nearned revenue
+is the amount of unearned revenue
+<% $actual_label ? 'Freeside has actually' : '' %>
+invoiced for packages with longer-than monthly terms.
+
+% if ( $legacy ) {
+ <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.
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $legacy = $conf->exists('enable_legacy_prepaid_income');
+my $actual_label = $legacy ? 'Actual ' : '';
+
+#doesn't yet deal with daily/weekly packages
+
+my $time = time;
+
+my $now = $cgi->param('date') && parse_datetime($cgi->param('date')) || $time;
+$now =~ /^(\d+)$/ or die "unparsable date?";
+$now = $1;
+
+my $link = "cust_bill_pkg.cgi?nottax=1;unearned_now=$now";
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $agentnum = '';
+my @agentnums = ();
+$agentnum ? ($agentnum) : $curuser->agentnums;
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ @agentnums = ($1);
+ #XXX#push @where, "agentnum = $agentnum";
+ #XXX#$link .= ";agentnum=$agentnum";
+} else {
+ @agentnums = $curuser->agentnums;
+}
+
+my @where = ();
+
+#here is the agent virtualization
+push @where, $curuser->agentnums_sql( 'table'=>'cust_main' );
+
+#well, because cust_bill_pkg.cgi has it and without it the numbers don't match..
+push @where , " payby != 'COMP' "
+ unless $cgi->param('include_comp_cust');
+
+my %total = ();
+my %total_legacy = ();
+foreach my $agentnum (@agentnums) {
+
+ my $where = join(' AND ', @where, "cust_main.agentnum = $agentnum");
+ $where = "AND $where" if $where;
+
+ my( $total, $total_legacy ) = ( 0, 0 );
+
+ # my @cust_bill_pkg =
+ # grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[hdw])$/ }
+ # qsearch({
+ # 'select' => 'cust_bill_pkg.*',
+ # 'table' => 'cust_bill_pkg',
+ # 'addl_from' => ' LEFT JOIN cust_bill USING ( invnum ) '.
+ # ' LEFT JOIN cust_main USING ( custnum ) ',
+ # 'hashref' => {
+ # 'recur' => { op=>'!=', value=>0 },
+ # 'sdate' => { op=>'<', value=>$now },
+ # 'edate' => { op=>'>', value=>$now },
+ # },
+ # 'extra_sql' => $where,
+ # });
+ #
+ # 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;
+ #
+ # }
+
+ #re-written in sql:
+
+ #false laziness w/cust_bill_pkg.cgi
+
+ my $float = 'REAL'; #'DOUBLE PRECISION';
+
+ my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)";
+ my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $now
+ THEN 0
+ ELSE ($now - cust_bill_pkg.sdate)
+ END)";
+ #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)";
+
+ my $remaining = "(1 - $elapsed/$period)";
+
+ my $select = "SUM($remaining * cust_bill_pkg.recur)";
+
+ #[...]
+
+ my $sql = "SELECT $select FROM cust_bill_pkg
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE pkgpart > 0
+ AND sdate < $now
+ AND edate > $now
+ AND cust_bill_pkg.recur != 0
+ AND part_pkg.freq != '0'
+ AND part_pkg.freq != '1'
+ AND part_pkg.freq NOT LIKE '%h'
+ AND part_pkg.freq NOT LIKE '%d'
+ AND part_pkg.freq NOT LIKE '%w'
+ $where
+ ";
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+
+ $total = sprintf('%.2f', $total);
+ $total{$agentnum} = $total;
+ $total{0} += $total;
+
+ if ( $legacy ) {
+
+ #not yet rewritten in sql, but now not enabled by default
+
+ my @cust_pkg =
+ grep { $_->part_pkg->recur != 0
+ && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/
+ }
+ qsearch({
+ 'select' => 'cust_pkg.*',
+ 'table' => 'cust_pkg',
+ 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'bill' => { op=>'>', value=>$now } },
+ 'extra_sql' => $where,
+ });
+
+ 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_legacy = sprintf('%.2f', $total_legacy);
+ $total_legacy{$agentnum} = $total_legacy;
+ $total_legacy{0} += $total_legacy;
+
+ }
+
+}
+
+$total{0} = sprintf('%.2f', $total{0});
+$total_legacy{0} = sprintf('%.2f', $total_legacy{0});
+
+</%init>
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
new file mode 100644
index 0000000..061b24c
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.html
@@ -0,0 +1,64 @@
+<% include('/elements/header.html','Prepaid Income (Unearned Revenue) Report')%>
+
+<% include('/elements/init_calendar.html') %>
+
+<FORM ACTION="report_prepaid_income.cgi" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <TR>
+ <TD>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><FONT SIZE="-1"><i>m/d/y</i></FONT></TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2>&nbsp;</TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <TR>
+ <TD COLSPAN=2>&nbsp;</TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Generate report"></TD>
+ </TR>
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "date_text",
+ ifFormat: "<% $date_format %>",
+ button: "date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+</%init>
diff --git a/httemplate/search/report_prospect_main.html b/httemplate/search/report_prospect_main.html
new file mode 100644
index 0000000..4834c20
--- /dev/null
+++ b/httemplate/search/report_prospect_main.html
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', 'Prospect Report' ) %>
+
+<FORM ACTION="prospect_main.html" METHOD="GET">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar($cgi->param('agentnum')),
+ 'disable_empty' => 0,
+ )
+ %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List prospects');
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/search/report_queued_newtax.cgi b/httemplate/search/report_queued_newtax.cgi
new file mode 100755
index 0000000..1d5813e
--- /dev/null
+++ b/httemplate/search/report_queued_newtax.cgi
@@ -0,0 +1,16 @@
+<% include("/elements/header.html", "Queue Tax Report") %>
+<% include("/elements/error.html") %>
+% unless ($error) {
+ <CENTER>
+ Report queued. Check the job queue for status.
+ </CENTER>
+% }
+<% include("/elements/footer.html") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $error = FS::tax_rate::queue_liability_report($cgi);
+
+</%init>
diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi
new file mode 100755
index 0000000..3696ed4
--- /dev/null
+++ b/httemplate/search/report_receivables.cgi
@@ -0,0 +1,52 @@
+<% include( 'elements/cust_main_dayranges.html',
+ 'title' => 'Accounts Receivable Aging Summary',
+ 'range_sub' => \&balance,
+ 'payment_links' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Receivables report')
+ or $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
+<%once>
+
+#Example:
+#
+# my $balance = balance(
+# $start, $end, $offset,
+# 'no_as' => 1, #set to true when using in a WHERE clause (supress AS clause)
+# #or 0 / omit when using in a SELECT clause as a column
+# # ("AS balance_$start_$end")
+# 'sum' => 1, #set to true to get a SUM() of the values, for totals
+#
+# #obsolete? options for totals (passed to cust_main::balance_date_sql)
+# 'total' => 1, #set to true to remove all customer comparison clauses
+# 'join' => $join, #JOIN clause
+# 'where' => \@where, #WHERE clause hashref (elements "AND"ed together)
+# )
+
+sub balance {
+ my($start, $end, $offset) = @_; #, %opt ?
+ #handle start and end ranges (86400 = 24h * 60m * 60s)
+ my $str2time = str2time_sql;
+ my $closing = str2time_sql_closing;
+
+ # $end == 0 means "+infinity", while $start == 0 really means 0
+ # so we should always include a start condition
+ $start = "( $str2time now() $closing - ". ($start + $offset) * 86400 . ' )';
+ # but only include an end condition if $end != 0
+ $end = $end ?
+ "( $str2time now() $closing - ". ($end + $offset) * 86400 . ' )'
+ : '';
+
+ #$opt{'unapplied_date'} = 1;
+
+ FS::cust_main->balance_date_sql( $start, $end, 'unapplied_date'=>1,
+ 'cutoff' => "( $str2time now() $closing - ".$offset * 86400 . ')' );
+
+}
+
+</%once>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
new file mode 100755
index 0000000..912ef26
--- /dev/null
+++ b/httemplate/search/report_receivables.html
@@ -0,0 +1,47 @@
+<% include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %>
+
+<FORM NAME="OneTrueForm" ACTION="report_receivables.cgi" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Customer Status'
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right">Customers</TD>
+ <TD>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">All customers (even those without an outstanding balance)<BR>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="0" CHECKED onClick="if ( ! this.checked ) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">Customers with a balance over <INPUT NAME="days" TYPE="text" SIZE=4 MAXLENGTH=3 VALUE="0"> days old
+ </TD>
+ </TR>
+ <% include( '/elements/tr-input-date-field.html', {
+ 'name' => 'as_of',
+ 'value' => time,
+ 'label' => 'As of date ',
+ 'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y',
+ } ) %>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Get Report">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Receivables report')
+ or $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_rt_transaction.html b/httemplate/search/report_rt_transaction.html
new file mode 100644
index 0000000..9b7b7cb
--- /dev/null
+++ b/httemplate/search/report_rt_transaction.html
@@ -0,0 +1,24 @@
+<% include('/elements/header.html', 'Time worked report criteria' ) %>
+
+<FORM ACTION="rt_transaction.html" METHOD="GET">
+
+<TABLE>
+
+ <% include ( '/elements/tr-input-beginning_ending.html' ) %>
+
+ <% include ( '/elements/tr-select-otaker.html' ) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
diff --git a/httemplate/search/report_sql.html b/httemplate/search/report_sql.html
new file mode 100644
index 0000000..9953308
--- /dev/null
+++ b/httemplate/search/report_sql.html
@@ -0,0 +1,23 @@
+<% include('/elements/header.html', 'SQL Query' ) %>
+
+<FORM ACTION="sql.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+ <TR>
+ <TD ALIGN="right" VALIGN="top">SELECT </TD>
+ <TD><TEXTAREA NAME="sql"></TEXTAREA></TD>
+ </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Query">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Raw SQL');
+
+</%init>
diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html
new file mode 100755
index 0000000..c7fac46
--- /dev/null
+++ b/httemplate/search/report_svc_acct.html
@@ -0,0 +1,134 @@
+<% include('/elements/header.html', $title ) %>
+
+<FORM ACTION="svc_acct.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="advanced">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+% unless ( $custnum ) {
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'disable_empty' => 0,
+ )
+ %>
+
+% # just this customer's domains?
+ <% include( '/elements/tr-select-domain.html',
+ 'element_name' => 'domsvc',
+ 'curr_value' => scalar( $cgi->param('domsvc') ),
+ 'disable_empty' => 0,
+ )
+ %>
+% }
+
+ <SCRIPT type="text/javascript">
+ function toggle(what) {
+ label = document.getElementById (what + '_label');
+ field = document.getElementById ( what + '_invert');
+ if (field.value == 1) {
+ field.value = 0;
+ } else {
+ field.value = 1;
+ }
+ if (field.value == 1) {
+ label.firstChild.nodeValue = 'Did not ' + label.firstChild.nodeValue;
+ }else{
+ text = label.firstChild.nodeValue;
+ label.firstChild.nodeValue = text.replace(/Did not /, '');
+ }
+ }
+ </SCRIPT>
+% foreach my $field (qw( last_login last_logout )) {
+% my $invert = $field."_invert";
+
+ <TR>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD ALIGN="right" VALIGN="center" ID="<% $field."_label" %>">
+ <% $label{$field} %>
+ </TD>
+ <TD>
+ <INPUT NAME="<% $invert %>" ID="<% $invert %>" TYPE="hidden">
+ <A HREF="javascript:void(0)" onClick="toggle('<% $field %>'); return false;">Invert</A>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => $field,
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% }
+
+ <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
+ </TR>
+
+% #move to /elements/tr-select-cust_pkg-fields if anything else needs it...
+ <TR>
+ <TD ALIGN="right">Package fields</TD>
+ <TD>
+ <SELECT NAME="cust_pkg_fields">
+ <OPTION VALUE="">(none)
+ <OPTION VALUE="setup,last_bill,bill,cancel">Setup date | Last bill date | Next bill date | Cancel date
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/tr-select-cust-fields.html' ) %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #?
+
+my $title = 'Account Report';
+
+#false laziness w/report_cust_pkg.html
+my $custnum = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or die "unknown custnum $custnum";
+ $title .= ': '. $cust_main->name;
+}
+
+</%init>
+<%once>
+
+my %label = (
+ 'last_login' => 'Last login',
+ 'last_logout' => 'Last logout',
+);
+
+</%once>
diff --git a/httemplate/search/report_svc_phone.html b/httemplate/search/report_svc_phone.html
new file mode 100644
index 0000000..9f10426
--- /dev/null
+++ b/httemplate/search/report_svc_phone.html
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', 'Phone number total usage' ) %>
+
+<FORM ACTION="svc_phone.cgi" METHOD="GET">
+
+<INPUT TYPE="hidden" NAME="magic" VALUE="all">
+<INPUT TYPE="hidden" NAME="usage_total" VALUE="1">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+%# <TR>
+%# <TH CLASS="background" COLSPAN=2 ALIGN="left">
+%# <FONT SIZE="+1">Search options</FONT>
+%# </TH>
+%# </TR>
+
+ <% include ( '/elements/tr-input-beginning_ending.html', prefix=>'usage' ) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search phone numbers">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+#? 'List services' ? something new?
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
new file mode 100755
index 0000000..803b7d4
--- /dev/null
+++ b/httemplate/search/report_tax.cgi
@@ -0,0 +1,786 @@
+<% include("/elements/header.html", "$agentname Tax Report - ".
+ ( $beginning
+ ? time2str('%h %o %Y ', $beginning )
+ : ''
+ ).
+ 'through '.
+ ( $ending == 4294967295
+ ? 'now'
+ : time2str('%h %o %Y', $ending )
+ )
+ )
+%>
+
+<% include('/elements/table-grid.html') %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH>
+% unless ( $cgi->param('show_taxclasses') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax credited</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax collected</TH>
+% }
+ </TR>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH>
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% foreach my $region ( @regions ) {
+%
+% my $link = '';
+% if ( $region->{'label'} eq $out ) {
+% $link = ';out=1';
+% } else {
+% $link = ';'. $region->{'url_param'}
+% if $region->{'url_param'};
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% #my $diff = 0;
+% my $hicolor = $bgcolor;
+% unless ( $cgi->param('show_taxclasses') ) {
+% my $diff = abs( sprintf( '%.2f', $region->{'owed'} )
+% - sprintf( '%.2f', $region->{'tax'} )
+% );
+% if ( $diff > 0.02 ) {
+% # $hicolor = $hicolor eq '#eeeeee' ? '#eeee66' : '#ffff99';
+% #} elsif ( $diff ) {
+% $hicolor = $hicolor eq '#eeeeee' ? '#eeee99' : '#ffffcc';
+% }
+% }
+%
+%
+% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
+% my $tdh = qq(TD CLASS="grid" BGCOLOR="$hicolor");
+% my $bigmath = '<FONT FACE="sans-serif" SIZE="+1"><B>';
+% my $bme = '</B></FONT>';
+
+ <TR>
+ <<%$td%>><% $region->{'label'} %></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $link %>;nottax=1"
+ ><% &$money_sprintf( $region->{'total'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"
+ ><% &$money_sprintf( $region->{'exempt_cust'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"
+ ><% &$money_sprintf( $region->{'exempt_pkg'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $exemptlink. $link %>"
+ ><% &$money_sprintf( $region->{'exempt_monthly'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $link %>;nottax=1;taxable=1"
+ ><% &$money_sprintf( $region->{'taxable'} ) %></A>
+ </TD>
+ <<%$td%>><% $region->{'label'} eq 'Total' ? '' : "$bigmath X $bme" %></TD>
+ <<%$td%> ALIGN="right"><% $region->{'rate'} %></TD>
+ <<%$td%>><% $region->{'label'} eq 'Total' ? '' : "$bigmath = $bme" %></TD>
+ <<%$tdh%> ALIGN="right">
+ <% &$money_sprintf( $region->{'owed'} ) %>
+ </TD>
+
+% unless ( $cgi->param('show_taxclasses') ) {
+% my $invlink = $region->{'url_param_inv'}
+% ? ';'. $region->{'url_param_inv'}
+% : $link;
+
+ <<%$tdh%> ALIGN="right">
+ <A HREF="<% $baselink. $invlink %>;istax=1"
+ ><% &$money_sprintf( $region->{'tax'} ) %></A>
+ </TD>
+ <<%$tdh%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$tdh%> ALIGN="right">
+ <A HREF="<% $creditlink. $invlink %>;istax=1"
+ ><% &$money_sprintf( $region->{'credit'} ) %></A>
+ </TD>
+ <<%$tdh%>><FONT SIZE="+1"><B> = </B></FONT></TD>
+ <<%$tdh%> ALIGN="right">
+ <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
+ </TD>
+% }
+
+ </TR>
+% }
+
+</TABLE>
+
+% if ( $cgi->param('show_taxclasses') ) {
+
+ <BR>
+ <% include('/elements/table-grid.html') %>
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
+ </TR>
+
+% #some false laziness w/above
+% $bgcolor1 = '#eeeeee';
+% $bgcolor2 = '#ffffff';
+%
+% foreach my $region ( @base_regions ) {
+%
+% my $link = '';
+% if ( $region->{'label'} eq $out ) {
+% $link = ';out=1';
+% } else {
+% $link = ';'. $region->{'url_param'}
+% if $region->{'url_param'};
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
+% my $tdh = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
+%
+% #?
+% my $invlink = $region->{'url_param_inv'}
+% ? ';'. $region->{'url_param_inv'}
+% : $link;
+
+ <TR>
+ <<%$td%>><% $region->{'label'} %></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $link %>;istax=1"
+ ><% &$money_sprintf( $region->{'tax'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$tdh%> ALIGN="right">
+ <A HREF="<% $creditlink. $invlink %>;istax=1"
+ ><% &$money_sprintf( $region->{'credit'} ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
+ <<%$tdh%> ALIGN="right">
+ <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
+ </TD>
+ </TR>
+
+% }
+
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
+
+ <TR>
+ <<%$td%>>Total</TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink %>;istax=1"
+ ><% &$money_sprintf( $tot_tax ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $creditlink %>;istax=1"
+ ><% &$money_sprintf( $tot_credit ) %></A>
+ </TD>
+ <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
+ <<%$td%> ALIGN="right">
+ <% &$money_sprintf( $tot_tax - $tot_credit ) %>
+ </TD>
+ </TR>
+
+ </TABLE>
+
+% }
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $user = getotaker;
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+my $join_cust = ' JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) ';
+my $join_cust_pkg = $join_cust.
+ ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart ) ';
+$join_cust_pkg .= ' LEFT JOIN cust_location USING ( locationnum )'
+ if $conf->exists('tax-pkg_address');
+
+my $from_join_cust_pkg = " FROM cust_bill_pkg $join_cust_pkg ";
+
+my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+
+my( $location_sql, @base_param ) = FS::cust_pkg->location_sql;
+$where .= " AND $location_sql ";
+
+my $agentname = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+ die "agent not found" unless $agent;
+ $agentname = $agent->agent;
+ $where .= ' AND cust_main.agentnum = '. $agent->agentnum;
+}
+
+sub gotcust {
+ my $table = shift;
+ my $prefix = @_ ? shift : '';
+ "
+ ( $table.${prefix}city = cust_main_county.city
+ OR cust_main_county.city = ''
+ OR cust_main_county.city IS NULL )
+ AND ( $table.${prefix}county = cust_main_county.county
+ OR cust_main_county.county = ''
+ OR cust_main_county.county IS NULL )
+ AND ( $table.${prefix}state = cust_main_county.state
+ OR cust_main_county.state = ''
+ OR cust_main_county.state IS NULL )
+ AND ( $table.${prefix}country = cust_main_county.country )
+ ";
+}
+
+my $gotcust;
+if ( $conf->exists('tax-ship_address') ) {
+
+ $gotcust = "
+ ( cust_main_county.country = cust_main.country
+ OR cust_main_county.country = cust_main.ship_country
+ )
+
+ AND
+
+ (
+ ( ( ship_last IS NULL OR ship_last = '' )
+ AND ". gotcust('cust_main'). "
+ )
+ OR
+ ( ship_last IS NOT NULL AND ship_last != ''
+ AND ". gotcust('cust_main', 'ship_'). "
+ )
+ )
+ ";
+
+} else {
+
+ $gotcust = gotcust('cust_main');
+
+}
+if ( $conf->exists('tax-pkg_address') ) {
+ $gotcust = "
+ ( cust_pkg.locationnum IS NULL AND $gotcust)
+ OR ( cust_pkg.locationnum IS NOT NULL AND ". gotcust('cust_location'). " )";
+ $gotcust =
+ "WHERE 0 < ( SELECT COUNT(*) FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_location USING ( locationnum )
+ WHERE $gotcust
+ LIMIT 1
+ )
+ ";
+} else {
+ $gotcust =
+ "WHERE 0 < ( SELECT COUNT(*) FROM cust_main WHERE $gotcust LIMIT 1 )";
+}
+
+my $out = 'Out of taxable region(s)';
+my %regions = ();
+
+foreach my $r ( qsearch({ 'table' => 'cust_main_county',
+ 'extra_sql' => $gotcust,
+ })
+ )
+{
+ #warn $r->county. ' '. $r->state. ' '. $r->country. "\n";
+
+ my $label = getlabel($r);
+ $regions{$label}->{'label'} = $label;
+
+ $regions{$label}->{$_} = $r->$_() for (qw( county state country )); #taxname?
+
+ my @url_param = qw( county state country taxname );
+ push @url_param, 'city' if $cgi->param('show_cities') && $r->city();
+
+ $regions{$label}->{'url_param'} =
+ join(';', map "$_=".uri_escape($r->$_()), @url_param );
+
+ my @param = @base_param;
+ my $mywhere = $where;
+
+ if ( $r->taxclass ) {
+
+ $mywhere .= " AND taxclass = ? ";
+ push @param, 'taxclass';
+ $regions{$label}->{'url_param'} .= ';taxclass='. uri_escape($r->taxclass);
+ #no, always# if $cgi->param('show_taxclasses');
+
+ $regions{$label}->{'taxclass'} = $r->taxclass;
+
+ } else {
+
+ $regions{$label}->{'url_param'} .= ';taxclassNULL=1'
+ if $cgi->param('show_taxclasses');
+
+ my $same_sql = $r->sql_taxclass_sameregion;
+ $mywhere .= " AND $same_sql" if $same_sql;
+
+ }
+
+ my $fromwhere = "$from_join_cust_pkg $mywhere"; # AND payby != 'COMP' ";
+
+# my $label = getlabel($r);
+# $regions{$label}->{'label'} = $label;
+
+ my $nottax = 'pkgnum != 0';
+
+ ## calculate total for this region
+
+ my $t_sql =
+ "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax";
+ my $t = scalar_sql($r, \@param, $t_sql);
+ $regions{$label}->{'total'} += $t;
+
+ #if ( $label eq $out ) # && $t ) {
+ # warn "adding $t for ".
+ # join('/', map $r->$_, qw( taxclass county state country ) ). "\n";
+ # #warn $t_sql if $r->state eq 'FL';
+ #}
+
+ ## calculate customer-exemption for this region
+
+## my $taxable = $t;
+
+# my($taxable, $x_cust) = (0, 0);
+# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i }
+# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) {
+# $taxable += scalar_sql($r, \@param,
+# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )"
+# );
+#
+# $x_cust += scalar_sql($r, \@param,
+# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'"
+# );
+# }
+
+ #false laziness -ish w/report_tax.cgi
+ my $cust_exempt;
+ if ( $r->taxname ) {
+ my $q_taxname = dbh->quote($r->taxname);
+ $cust_exempt =
+ "( tax = 'Y'
+ OR EXISTS ( SELECT 1 FROM cust_main_exemption
+ WHERE cust_main_exemption.custnum = cust_main.custnum
+ AND cust_main_exemption.taxname = $q_taxname
+ )
+ )
+ ";
+ } else {
+ $cust_exempt = " tax = 'Y' ";
+ }
+
+ my $x_cust = scalar_sql($r, \@param,
+ "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur)
+ $fromwhere AND $nottax AND $cust_exempt "
+ );
+
+ $regions{$label}->{'exempt_cust'} += $x_cust;
+
+ ## calculate package-exemption for this region
+
+ my $x_pkg = scalar_sql($r, \@param,
+ "SELECT SUM(
+ ( CASE WHEN part_pkg.setuptax = 'Y'
+ THEN cust_bill_pkg.setup
+ ELSE 0
+ END
+ )
+ +
+ ( CASE WHEN part_pkg.recurtax = 'Y'
+ THEN cust_bill_pkg.recur
+ ELSE 0
+ END
+ )
+ )
+ $fromwhere
+ AND $nottax
+ AND (
+ ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
+ OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 )
+ )
+ AND ( tax != 'Y' OR tax IS NULL )
+ "
+ );
+ $regions{$label}->{'exempt_pkg'} += $x_pkg;
+
+ ## calculate monthly exemption (texas tax) for this region
+
+ # count up all the cust_tax_exempt_pkg records associated with
+ # the actual line items.
+
+ my $x_monthly = scalar_sql($r, \@param,
+ "SELECT SUM(amount)
+ FROM cust_tax_exempt_pkg
+ JOIN cust_bill_pkg USING ( billpkgnum )
+ $join_cust_pkg
+ $mywhere"
+ );
+ $regions{$label}->{'exempt_monthly'} += $x_monthly;
+
+ my $taxable = $t - $x_cust - $x_pkg - $x_monthly;
+ $regions{$label}->{'taxable'} += $taxable;
+
+ $regions{$label}->{'owed'} += $taxable * ($r->tax/100);
+
+ if ( defined($regions{$label}->{'rate'})
+ && $regions{$label}->{'rate'} != $r->tax.'%' ) {
+ $regions{$label}->{'rate'} = 'variable';
+ } else {
+ $regions{$label}->{'rate'} = $r->tax.'%';
+ }
+
+}
+
+my $distinct = "country, state, county, city,
+ CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname";
+my $taxclass_distinct =
+ #a little bit unsure of this part... test?
+ #ah, it looks like it winds up being irrelevant as ->{'tax'}
+ # from $regions is not displayed when show_taxclasses is on
+ ( $cgi->param('show_taxclasses')
+ ? " CASE WHEN taxclass IS NULL THEN '' ELSE taxclass END "
+ : " '' "
+ )." AS taxclass";
+
+
+my %qsearch = (
+ 'select' => "DISTINCT $distinct, $taxclass_distinct",
+ 'table' => 'cust_main_county',
+ 'hashref' => {},
+ 'extra_sql' => $gotcust,
+);
+
+my $taxfromwhere = " FROM cust_bill_pkg $join_cust ";
+my $taxwhere = $where;
+if ( $conf->exists('tax-pkg_address') ) {
+
+ $taxfromwhere .= 'LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
+ LEFT JOIN cust_location USING ( locationnum ) ';
+
+ #quelle kludge
+ $taxwhere =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g;
+
+}
+my $creditfromwhere = $taxfromwhere.
+ " JOIN cust_credit_bill_pkg USING (billpkgnum";
+$creditfromwhere .= " ,billpkgtaxlocationnum"
+ if $conf->exists('tax-pkg_address');
+$creditfromwhere .= ")";
+
+$taxfromwhere .= " $taxwhere "; #AND payby != 'COMP' ";
+$creditfromwhere .= " $taxwhere AND billpkgtaxratelocationnum IS NULL"; #AND payby != 'COMP' ";
+
+my @taxparam = @base_param;
+
+
+#should i be a cust_main_county method or something
+#need to pass in $taxfromwhere & @taxparam???
+my $_taxamount_sub = sub {
+ my $r = shift;
+
+ #match itemdesc if necessary!
+ my $named_tax =
+ $r->taxname
+ ? 'AND itemdesc = '. dbh->quote($r->taxname)
+ : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+
+ my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
+ " $taxfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax";
+
+ scalar_sql($r, \@taxparam, $sql );
+};
+
+my $_creditamount_sub = sub {
+ my $r = shift;
+
+ #match itemdesc if necessary!
+ my $named_tax =
+ $r->taxname
+ ? 'AND itemdesc = '. dbh->quote($r->taxname)
+ : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+
+ my $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
+ " $creditfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax";
+
+ scalar_sql($r, \@taxparam, $sql );
+};
+
+#tax-report_groups filtering
+my($group_op, $group_value) = ( '', '' );
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
+ ( $group_op, $group_value ) = ( $1, $2 );
+}
+my $group_test = sub {
+ my $label = shift;
+ return 1 unless $group_op; #in case we get called inadvertantly
+ if ( $label eq $out ) { #don't display "out of taxable region" in this case
+ 0;
+ } elsif ( $group_op eq '=' ) {
+ $label =~ /^$group_value/;
+ } elsif ( $group_op eq '!=' ) {
+ $label !~ /^$group_value/;
+ } else {
+ die "guru meditation #00de: group_op $group_op\n";
+ }
+};
+
+my $tot_tax = 0;
+my $tot_credit = 0;
+#foreach my $label ( keys %regions ) {
+foreach my $r ( qsearch(\%qsearch) ) {
+
+ #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n";
+
+ my $label = getlabel($r);
+ if ( $group_op ) {
+ next unless &{$group_test}($label);
+ }
+
+ #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' ";
+ #my @param = @base_param;
+
+ my $x = &{$_taxamount_sub}($r);
+
+ $regions{$label}->{'tax'} += $x;
+ $tot_tax += $x unless $cgi->param('show_taxclasses');
+
+ ## calculate credit for this region
+
+ $x = &{$_creditamount_sub}($r);
+
+ $regions{$label}->{'credit'} += $x;
+ $tot_credit += $x unless $cgi->param('show_taxclasses');
+
+}
+
+my %base_regions = ();
+if ( $cgi->param('show_taxclasses') ) {
+
+ $qsearch{'select'} = "DISTINCT $distinct";
+ foreach my $r ( qsearch(\%qsearch) ) {
+
+ my $x = &{$_taxamount_sub}($r);
+
+ my $base_label = getlabel($r, 'no_taxclass'=>1 );
+ $base_regions{$base_label}->{'label'} = $base_label;
+
+ $base_regions{$base_label}->{'url_param'} =
+ join(';', map "$_=". uri_escape($r->$_()),
+ qw( county state country taxname )
+ );
+
+ $base_regions{$base_label}->{'tax'} += $x;
+ $tot_tax += $x;
+
+ ## calculate credit for this region
+
+ $x = &{$_creditamount_sub}($r);
+
+ $base_regions{$base_label}->{'credit'} += $x;
+ $tot_credit += $x;
+
+ }
+
+}
+
+my @regions = keys %regions;
+
+#tax-report_groups filtering
+@regions = grep &{$group_test}($_), @regions
+ if $group_op;
+
+#calculate totals
+my( $total, $tot_taxable, $tot_owed ) = ( 0, 0, 0 );
+my( $exempt_cust, $exempt_pkg, $exempt_monthly, $tot_credit ) = ( 0, 0, 0, 0 );
+my %taxclasses = ();
+my %county = ();
+my %state = ();
+my %country = ();
+foreach (@regions) {
+ $total += $regions{$_}->{'total'};
+ $tot_taxable += $regions{$_}->{'taxable'};
+ $tot_owed += $regions{$_}->{'owed'};
+ $exempt_cust += $regions{$_}->{'exempt_cust'};
+ $exempt_pkg += $regions{$_}->{'exempt_pkg'};
+ $exempt_monthly += $regions{$_}->{'exempt_monthly'};
+ $tot_credit += $regions{$_}->{'credit'};
+ $taxclasses{$regions{$_}->{'taxclass'}} = 1
+ if $regions{$_}->{'taxclass'};
+ $county{$regions{$_}->{'county'}} = 1;
+ $state{$regions{$_}->{'state'}} = 1;
+ $country{$regions{$_}->{'country'}} = 1;
+}
+
+my $total_url_param = '';
+my $total_url_param_invoiced = '';
+if ( $group_op ) {
+
+ my @country = keys %country;
+ warn "WARNING: multiple countries on this grouped report; total links broken"
+ if scalar(@country) > 1;
+ my $country = $country[0];
+
+ my @state = keys %state;
+ warn "WARNING: multiple countries on this grouped report; total links broken"
+ if scalar(@state) > 1;
+ my $state = $state[0];
+
+ $total_url_param_invoiced =
+ $total_url_param =
+ 'report_group='.uri_escape("$group_op $group_value").';'.
+ join(';', map 'taxclass='.uri_escape($_), keys %taxclasses );
+ $total_url_param .= ';'.
+ "country=$country;state=".uri_escape($state).';'.
+ join(';', map 'county='.uri_escape($_), keys %county ) ;
+
+}
+
+#ordering
+@regions =
+ map $regions{$_},
+ sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+ @regions;
+
+my @base_regions =
+ map $base_regions{$_},
+ sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+ keys %base_regions;
+
+#add total line
+push @regions, {
+ 'label' => 'Total',
+ 'url_param' => $total_url_param,
+ 'url_param_inv' => $total_url_param_invoiced,
+ 'total' => $total,
+ 'exempt_cust' => $exempt_cust,
+ 'exempt_pkg' => $exempt_pkg,
+ 'exempt_monthly' => $exempt_monthly,
+ 'taxable' => $tot_taxable,
+ 'rate' => '',
+ 'owed' => $tot_owed,
+ 'tax' => $tot_tax,
+ 'credit' => $tot_credit,
+};
+
+#--
+
+my $money_char = $conf->config('money_char') || '$';
+my $money_sprintf = sub {
+ $money_char. sprintf('%.2f', shift );
+};
+
+sub getlabel {
+ my $r = shift;
+ my %opt = @_;
+
+ my $label;
+ if (
+ $r->tax == 0
+ && ! scalar( qsearch('cust_main_county', { 'city' => $r->city,
+ 'county' => $r->county,
+ 'state' => $r->state,
+ 'country' => $r->country,
+ 'tax' => { op=>'>', value=>0 },
+ }
+ )
+ )
+
+ ) {
+ #kludge to avoid "will not stay shared" warning
+ my $out = 'Out of taxable region(s)';
+ $label = $out;
+# } elsif ( $r->taxname && count_taxname($r->taxname) == 1 ) {
+# $label = $r->taxname;
+## $regions{$label}->{'taxname'} = $label;
+## push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country );
+ } else {
+ $label = $r->country;
+ $label = $r->state.", $label" if $r->state;
+ $label = $r->county." county, $label" if $r->county;
+ $label = $r->city. ", $label" if $r->city && $cgi->param('show_cities');
+ $label = "$label (". $r->taxclass. ")"
+ if $r->taxclass
+ && $cgi->param('show_taxclasses')
+ && ! $opt{'no_taxclass'};
+ $label = $r->taxname. " ($label)" if $r->taxname;
+ }
+ return $label;
+}
+
+#my %count_taxname = (); #cache
+#sub count_taxname {
+# my $taxname = shift;
+# return $count_taxname{$taxname} if exists $count_taxname{$taxname};
+# my $sql = 'SELECT COUNT(*) FROM cust_main_county WHERE taxname = ?';
+# my $sth = dbh->prepare($sql) or die dbh->errstr;
+# $sth->execute( $taxname )
+# or die "Unexpected error executing statement $sql: ". $sth->errstr;
+# $count_taxname{$taxname} = $sth->fetchrow_arrayref->[0];
+#}
+
+#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;
+}
+
+my $dateagentlink = "begin=$beginning;end=$ending";
+$dateagentlink .= ';agentnum='. $cgi->param('agentnum')
+ if length($agentname);
+my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink";
+my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink";
+my $creditlink = $p. "search/cust_credit_bill_pkg.html?$dateagentlink";
+
+</%init>
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
new file mode 100755
index 0000000..2ab0e0b
--- /dev/null
+++ b/httemplate/search/report_tax.html
@@ -0,0 +1,79 @@
+<% include('/elements/header.html', 'Tax Report' ) %>
+
+<FORM ACTION="report_tax.cgi" METHOD="GET">
+
+<TABLE>
+
+% if ( $conf->config('tax-report_groups') ) {
+% my @lines = $conf->config('tax-report_groups');
+
+ <TR>
+ <TD ALIGN="right">Tax group</TD>
+ <TD>
+ <SELECT NAME="report_group">
+
+ <OPTION VALUE="">all</OPTION>
+
+% foreach my $line ( @lines ) {
+% $line =~ /^\s*(.+)\s+(=|!=)\s+(.*)\s*$/ #or next;
+% or do { warn "bad report_group line: $line\n"; next; };
+% my($label, $op, $value) = ($1, $2, $3);
+
+ <OPTION VALUE="<% "$op $value" %>"><% $label %></OPTION>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+
+% }
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+% if ( $city ) {
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1"></TD>
+ <TD>Show cities</TD>
+ </TR>
+% }
+
+% if ( $conf->exists('enable_taxclasses') ) {
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD>
+ <TD>Show tax classes</TD>
+ </TR>
+% }
+
+% my @pkg_class = qsearch('pkg_class', {});
+% if ( @pkg_class ) {
+ <TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD>
+ <TD>Show package classes</TD>
+ </TR>
+% }
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $city_sql = "SELECT COUNT(*) FROM cust_main_county
+ WHERE city != '' AND city IS NOT NULL
+ LIMIT 1";
+
+my $city_sth = dbh->prepare($city_sql) or die dbh->errstr;
+$city_sth->execute or die $city_sth->errstr;
+my $city = $city_sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/search/report_timeworked.html b/httemplate/search/report_timeworked.html
new file mode 100644
index 0000000..492e738
--- /dev/null
+++ b/httemplate/search/report_timeworked.html
@@ -0,0 +1,28 @@
+<% include( '/elements/header.html', 'Time Worked' ) %>
+
+<FORM ACTION="timeworked.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include ('/elements/tr-input-beginning_ending.html') %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Time queue');
+
+</%init>
diff --git a/httemplate/search/report_unapplied_cust_pay.html b/httemplate/search/report_unapplied_cust_pay.html
new file mode 100755
index 0000000..d2dd9e7
--- /dev/null
+++ b/httemplate/search/report_unapplied_cust_pay.html
@@ -0,0 +1,47 @@
+<% include('/elements/header.html', 'Unapplied Payments Aging Summary' ) %>
+%# 'Prepaid Balance Aging Summary', #???
+
+<FORM NAME="OneTrueForm" ACTION="unapplied_cust_pay.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Customer Status'
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right">Customers</TD>
+ <TD>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">All customers (even those without unapplied payments)<BR>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="0" CHECKED onClick="if ( ! this.checked ) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">Customers with unapplied payments over <INPUT NAME="days" TYPE="text" SIZE=4 MAXLENGTH=3 VALUE="0"> days old
+ </TD>
+ </TR>
+ <% include( '/elements/tr-input-date-field.html', {
+ 'name' => 'as_of',
+ 'value' => time,
+ 'label' => 'As of date ',
+ 'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y',
+ } ) %>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Get Report">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html
new file mode 100644
index 0000000..651f289
--- /dev/null
+++ b/httemplate/search/rt_transaction.html
@@ -0,0 +1,96 @@
+<% include('elements/search.html',
+ 'title' => 'Time worked',
+ 'name_singular' => 'transaction',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $format_seconds_sub, ],
+ 'header' => [ 'Ticket #',
+ 'Ticket',
+ 'Date',
+ 'Time',
+ ],
+ 'fields' => [ 'ticketid',
+ sub { encode_entities(shift->get('subject')) },
+ 'created',
+ sub { my $seconds = shift->get('transaction_time');
+ &{ $format_seconds_sub }( $seconds );
+ },
+ ],
+ 'links' => [
+ $link,
+ $link,
+ '',
+ '',
+ ],
+ )
+%>
+<%once>
+
+my $format_seconds_sub = sub {
+ my $seconds = shift;
+ (($seconds < 0) ? '-' : '') . concise(duration($seconds));
+};
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+#some amount of false laziness w/timeworked.html...
+
+my $transactiontime = "
+ CASE transactions.type when 'Set'
+ THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60
+ ELSE timetaken*60
+ END
+";
+
+my $join = 'JOIN Tickets ON Transactions.ObjectId = Tickets.Id '.
+ 'JOIN Users ON Transactions.Creator = Users.Id ';
+
+my $where = "
+ WHERE objecttype='RT::Ticket'
+ AND ( ( Transactions.Type = 'Set'
+ AND Transactions.Field = 'TimeWorked'
+ AND Transactions.NewValue != Transactions.OldValue )
+ OR ( ( Transactions.Type='Create' OR Transactions.Type='Comment' OR Transactions.Type='Correspond' )
+ AND Transactions.TimeTaken > 0
+ )
+ )
+";
+#AND transaction_time != 0
+#AND $wheretimeleft
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+# TIMESTAMP is Pg-specific... ?
+if ( $beginning > 0 ) {
+ $beginning = "TIMESTAMP '". time2str('%Y-%m-%d %X', $beginning). "'";
+ $where .= " AND Transactions.Created >= $beginning ";
+}
+if ( $ending < 4294967295 ) {
+ $ending = "TIMESTAMP '". time2str('%Y-%m-%d %X', $ending). "'";
+ $where .= " AND Transactions.Created <= $ending ";
+}
+
+if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) {
+ $where .= " AND Users.name = '$1' ";
+}
+
+my $query = {
+ 'select' => "Transactions.*, Tickets.Id AS ticketid, Tickets.Subject, Users.name as otaker, $transactiontime AS transaction_time",
+ #'table' => 'Transactions',
+ 'table' => 'transactions',
+ 'addl_from' => $join.
+ 'LEFT JOIN acct_rt_transaction '.
+ ' ON Transactions.Id = acct_rt_transaction.transaction_id',
+ 'extra_sql' => $where,
+ 'order by' => 'ORDER BY Created',
+};
+
+my $count_query =
+ "SELECT COUNT(*), SUM($transactiontime) FROM Transactions $join $where";
+
+my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->get('id'); } ];
+
+</%init>
diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html
new file mode 100644
index 0000000..df9b8cd
--- /dev/null
+++ b/httemplate/search/sql.html
@@ -0,0 +1,13 @@
+<% include( 'elements/search.html',
+ 'title' => 'Query Results',
+ 'name' => 'rows',
+ 'query' => 'SELECT '. ( $cgi->param('sql')
+ || errorpage('Empty query') ),
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Raw SQL');
+
+</%init>
diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi
new file mode 100644
index 0000000..cca1211
--- /dev/null
+++ b/httemplate/search/sqlradius.cgi
@@ -0,0 +1,328 @@
+<% include( '/elements/header.html', 'RADIUS Sessions') %>
+
+% ###
+% # and finally, display the thing
+% ###
+%
+% foreach my $part_export (
+% #grep $_->can('usage_sessions'), qsearch( 'part_export' )
+% qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ),
+% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } )
+% ) {
+% %user2svc_acct = ();
+%
+% my $efields = tie my %efields, 'Tie::IxHash', %fields;
+% delete $efields{'framedipaddress'} if $part_export->option('hide_ip');
+% if ( $part_export->option('hide_data') ) {
+% delete $efields{$_} foreach qw(acctinputoctets acctoutputoctets);
+% }
+% if ( $part_export->option('show_called_station') ) {
+% $efields->Splice(1, 0,
+% 'calledstationid' => {
+% 'name' => 'Destination',
+% 'attrib' => 'Called-Station-ID',
+% 'fmt' =>
+% sub { length($_[0]) ? shift : '&nbsp'; },
+% 'align' => 'left',
+% },
+% );
+% }
+%
+%
+
+ <% $part_export->exporttype %> to <% $part_export->machine %><BR>
+ <% include( '/elements/table-grid.html' ) %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+
+ <TR>
+% foreach my $field ( keys %efields ) {
+
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <% $efields{$field}->{name} %><BR>
+ <FONT SIZE=-2><% $efields{$field}->{attrib} %></FONT>
+ </TH>
+
+% }
+ </TR>
+
+% foreach my $session (
+% @{ $part_export->usage_sessions( {
+% 'stoptime_start' => $beginning,
+% 'stoptime_end' => $ending,
+% 'open_sessions' => $open_sessions,
+% 'starttime_start' => $starttime_beginning,
+% 'starttime_end' => $starttime_ending,
+% 'svc_acct' => $cgi_svc_acct,
+% 'ip' => $ip,
+% 'prefix' => $prefix,
+% } )
+% }
+% ) {
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+% foreach my $field ( keys %efields ) {
+% my $html = &{ $efields{$field}->{fmt} }( $session->{$field},
+% $session,
+% $part_export,
+% );
+% my $class = ( $html =~ /<TABLE/ ? 'inv' : 'grid' );
+
+ <TD CLASS="<%$class%>" BGCOLOR="<% $bgcolor %>" ALIGN="<% $efields{$field}->{align} %>">
+ <% $html %>
+ </TD>
+% }
+ </TR>
+
+% }
+
+</TABLE>
+<BR><BR>
+
+% }
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+###
+# parse cgi params
+###
+
+#sort of false laziness w/cust_pay.cgi
+my( $beginning, $ending ) = ( '', '' );
+if ( $cgi->param('stoptime_beginning')
+ && $cgi->param('stoptime_beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+ $beginning = parse_datetime($1);
+}
+if ( $cgi->param('stoptime_ending')
+ && $cgi->param('stoptime_ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+ $ending = parse_datetime($1); # + 86399;
+}
+if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) {
+ $beginning = $1;
+}
+if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) {
+ $ending = $1;
+}
+
+my $open_sessions = '';
+if ( $cgi->param('open_sessions') =~ /^(\d*)$/ ) {
+ $open_sessions = $1;
+}
+
+my( $starttime_beginning, $starttime_ending ) = ( '', '' );
+if ( $cgi->param('starttime_beginning')
+ && $cgi->param('starttime_beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+ $starttime_beginning = parse_datetime($1);
+}
+if ( $cgi->param('starttime_ending')
+ && $cgi->param('starttime_ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+ $starttime_ending = parse_datetime($1); # + 86399;
+}
+
+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;
+}
+
+my $prefix = $cgi->param('prefix');
+$prefix =~ s/\D//g;
+if ( $prefix =~ /^(\d+)$/ ) {
+ $prefix = $1;
+ $prefix = "011$prefix" unless $prefix =~ /^1/;
+} else {
+ $prefix = '';
+}
+
+###
+# 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 CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0>'.
+ '<TR><TD CLASS="inv" ALIGN="right">'.
+ ( $hour ? "<B>$hour</B>h" : '&nbsp;' ).
+ '</TD><TD CLASS="inv" ALIGN="right">'.
+ ( ( $hour || $min ) ? "<B>$min</B>m" : '&nbsp;' ).
+ '</TD><TD CLASS="inv" 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;
+
+</%init>
diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html
new file mode 100644
index 0000000..8c40598
--- /dev/null
+++ b/httemplate/search/sqlradius.html
@@ -0,0 +1,123 @@
+<% include( '/elements/header.html', 'Search RADIUS sessions' ) %>
+
+<FORM NAME="OneTrueForm" ACTION="sqlradius.cgi" METHOD="GET">
+% #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>
+% my @part_export = qsearch( 'part_export', { 'exporttype' => 'sqlradius' } );
+% push @part_export,
+% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } );
+%
+% if ( grep { ! $_->option('hide_ip') } @part_export ) {
+
+ <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>
+% }
+% if ( grep { $_->option('show_called_station') } @part_export ) {
+
+ <TR>
+ <TD ALIGN="right">Destination prefix:</TD>
+ <TD><INPUT TYPE="text" NAME="prefix"></TD>
+ </TR>
+ <TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1"><I>(country code or country code and prefix)</I></FONT></TD>
+ </TR>
+ <TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1"><I>(leave blank to show all destinations)</I></FONT></TD>
+ </TR>
+% }
+
+ <TR>
+ <TD>Show:</TD>
+ <TD>
+ <INPUT TYPE="radio" NAME="open_sessions" VALUE="0" onClick="open_changed(this);" CHECKED>Completed sessions<BR>
+ <INPUT TYPE="radio" NAME="open_sessions" VALUE="1" onClick="open_changed(this);">Open sessions
+ </TD>
+ </TR>
+
+ <TR>
+ <TH COLSPAN=2>Session start</TD>
+ </TR>
+
+ <% include( '/elements/tr-input-beginning_ending.html',
+ 'prefix' => 'starttime',
+ 'input_time' => 1,
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function open_changed(what) {
+
+ var value=get_open_value(what);
+ if ( value == '1' ) {
+ what.form.stoptime_beginning_text.disabled = true;
+ what.form.stoptime_ending_text.disabled = true;
+ what.form.stoptime_beginning_text.style.backgroundColor = '#dddddd';
+ what.form.stoptime_ending_text.style.backgroundColor = '#dddddd';
+ what.form.stoptime_beginning_button.style.display = 'none';
+ what.form.stoptime_ending_button.style.display = 'none';
+ what.form.stoptime_beginning_disabled.style.display = '';
+ what.form.stoptime_ending_disabled.style.display = '';
+ } else if ( value == '0' ) {
+ what.form.stoptime_beginning_text.disabled = false;
+ what.form.stoptime_ending_text.disabled = false;
+ what.form.stoptime_beginning_text.style.backgroundColor = '#ffffff';
+ what.form.stoptime_ending_text.style.backgroundColor = '#ffffff';
+ what.form.stoptime_beginning_button.style.display = '';
+ what.form.stoptime_ending_button.style.display = '';
+ what.form.stoptime_beginning_disabled.style.display = 'none';
+ what.form.stoptime_ending_disabled.style.display = 'none';
+ }
+
+ }
+
+ function get_open_value(what) {
+ var rad_val = '';
+ for (var i=0; i < what.form.open_sessions.length; i++) {
+ if (what.form.open_sessions[i].checked) {
+ var rad_val = what.form.open_sessions[i].value;
+ }
+ }
+ return rad_val;
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TH COLSPAN=2>Session end</TD>
+ </TR>
+
+ <% include( '/elements/tr-input-beginning_ending.html',
+ 'prefix' => 'stoptime',
+ 'input_time' => 1,
+ )
+ %>
+
+</TABLE>
+<BR><INPUT TYPE="submit" VALUE="View sessions">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi
new file mode 100755
index 0000000..1407d9e
--- /dev/null
+++ b/httemplate/search/svc_acct.cgi
@@ -0,0 +1,334 @@
+<% include( 'elements/search.html',
+ 'title' => 'Account Search Results',
+ 'name' => 'accounts',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => $align,
+ 'color' => \@color,
+ 'style' => \@style,
+ 'footer' => \@footer,
+ )
+%>
+<%once>
+
+#false laziness w/ClientAPI/MyAccount.pm
+sub format_time {
+ my $support = shift;
+ (($support < 0) ? '-' : '' ). int(abs($support)/3600)."h".sprintf("%02d",(abs($support)%3600)/60)."m";
+}
+
+sub timelast {
+ my( $svc_acct, $last, $permonth ) = @_;
+
+ my $sql = "
+ SELECT SUM(support) FROM acct_rt_transaction
+ LEFT JOIN Transactions
+ ON Transactions.Id = acct_rt_transaction.transaction_id
+ WHERE svcnum = ?
+ AND Transactions.Created >= ?
+ ";
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute( $svc_acct->svcnum,
+ time2str('%Y-%m-%d %X', time - $last*86400 )
+ )
+ or die $sth->errstr;
+
+ my $seconds = $sth->fetchrow_arrayref->[0];
+
+ #my $return = (($seconds < 0) ? '-' : '') . concise(duration($seconds));
+ my $return = (($seconds < 0) ? '-' : '') . format_time($seconds);
+
+ $return .= sprintf(' (%.2fx)', $seconds / $permonth ) if $permonth;
+
+ $return;
+
+}
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('List services');
+
+my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ];
+my $link_cust = sub {
+ my $svc_acct = shift;
+ if ( $svc_acct->custnum ) {
+ [ "${p}view/cust_main.cgi?", 'custnum' ];
+ } else {
+ '';
+ }
+};
+
+my %search_hash = ();
+my @extra_sql = ();
+
+my @header = ( 'Service', 'Account' );
+my @fields = ( 'svc', 'email' );
+my @links = ( $link, $link );
+my $align = 'll';
+my @color = ( '', '' );
+my @style = ( '', '' );
+my @footer = ();
+
+my $conf = new FS::Conf;
+
+if ( $conf->exists('report-showpasswords') #its a terrible idea
+ && $curuser->access_right('List service passwords') #but if you insist...
+ )
+{
+ push @header, 'Password';
+ push @fields, 'get_cleartext_password';
+ push @links, $link;
+ $align .= 'l';
+ push @color, '';
+ push @style, '';
+}
+
+push @header, 'Real Name';
+push @fields, 'finger';
+push @links, $link;
+$align .= 'l';
+push @color, '';
+push @style, '';
+
+#hide the UID, its much less useful these days
+if ( $cgi->param('show_uid') ) { #XXX add a checkbox
+ push @header, 'UID';
+ push @fields, 'uid';
+ push @links, $link;
+ $align .= 'l';
+ push @color, '';
+ push @style, '';
+}
+
+push @header, 'Last Login';
+push @fields, 'last_login_text';
+push @links, $link;
+$align .= 'r';
+push @color, '';
+push @style, '';
+
+
+for (qw( domain domsvc agentnum custnum popnum svcpart cust_fields )) {
+ $search_hash{$_} = $cgi->param($_) if length($cgi->param($_));
+}
+
+my $timepermonth = '';
+
+my $orderby = 'ORDER BY svcnum';
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ $search_hash{'unlinked'} = 1
+ if $cgi->param('magic') eq 'unlinked';
+
+ my $sortby = '';
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ $sortby = $1;
+ $sortby = "LOWER($sortby)"
+ if $sortby eq 'username';
+ push @extra_sql, "$sortby IS NOT NULL" #XXX search_hash
+ if $sortby eq 'uid' || $sortby eq 'seconds' || $sortby eq 'last_login';
+ $orderby = "ORDER BY $sortby";
+ }
+
+ if ( $sortby eq 'seconds' ) {
+ my $tot_time = 0;
+ #push @header, 'Time remaining';
+ push @header, 'Time';
+ push @fields, sub { my $svc_acct = shift;
+ $tot_time += $svc_acct->seconds;
+ format_time($svc_acct->seconds);
+ };
+ push @links, '';
+ $align .= 'r';
+ push @color, '';
+ push @style, '';
+
+ @footer = ( 'Total', '', '', '',
+ sub { format_time($tot_time) }, #time
+ );
+
+ if ( $conf->exists('svc_acct-display_paid_time_remaining') ) {
+ my $tot_paid_time = 0;
+ my %tot = ( '30'=>0, '60'=>0, '90'=>0 );
+ push @header, 'Paid time', 'Last 30', 'Last 60', 'Last 90';
+ push @fields,
+ sub {
+ my $svc_acct = shift;
+ my $seconds = $svc_acct->seconds;
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ #my $timepermonth = $part_pkg->option('seconds');
+ $timepermonth = $part_pkg->option('seconds');
+ $timepermonth = $timepermonth / $part_pkg->freq
+ if $part_pkg->freq =~ /^\d+$/ && $part_pkg->freq != 0;
+
+ #my $recur = $part_pkg->calc_recur($cust_pkg);
+ my $recur = $part_pkg->base_recur($cust_pkg);
+
+ return format_time($seconds) unless $timepermonth && $recur;
+
+ my $balance = $cust_pkg->cust_main->balance;
+ my $periods_unpaid = $balance / $recur;
+ my $time_unpaid = $periods_unpaid * $timepermonth;
+ $time_unpaid *= $part_pkg->freq
+ if $part_pkg->freq =~ /^\d+$/ && $part_pkg->freq != 0;
+ $tot_paid_time += $seconds-$time_unpaid;
+ format_time($seconds-$time_unpaid).
+ sprintf(' (%.2fx monthly)', ( $seconds-$time_unpaid ) / $timepermonth );
+ },
+ sub { timelast( shift, 30, $timepermonth ); },
+ sub { timelast( shift, 60, $timepermonth ); },
+ sub { timelast( shift, 90, $timepermonth ); },
+ ;
+ push @links, '', '', '', '';
+ $align .= 'rrrr';
+ push @color, '', '', '', '';
+ push @style, '', '', '', '';
+ push @footer,
+ sub { format_time($tot_paid_time) }, #paid time
+ '', #XXX sub { $tot{'30'} }, #30
+ '', #XXX sub { $tot{'60'} }, #60
+ '', #XXX sub { $tot{'90'} }, #90
+ ;
+ }
+
+ push @footer, '', '';
+
+ }
+
+} elsif ( $cgi->param('magic') =~ /^nologin$/ ) {
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $sortby = "LOWER($sortby)"
+ if $sortby eq 'username';
+ push @extra_sql, "last_login IS NULL";
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('magic') =~ /^advanced$/ ) {
+
+ $orderby = "";
+
+ $search_hash{'pkgpart'} = [ $cgi->param('pkgpart') ];
+
+ foreach my $field (qw( last_login last_logout )) {
+
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+ next if $beginning == 0 && $ending == 4294967295;
+
+ if ($cgi->param($field."_invert")) {
+ push @extra_sql,
+ "(svc_acct.$field IS NULL OR ".
+ "svc_acct.$field < $beginning AND ".
+ "svc_acct.$field > $ending)";
+ } else {
+ push @extra_sql,
+ "svc_acct.$field IS NOT NULL",
+ "svc_acct.$field >= $beginning",
+ "svc_acct.$field <= $ending";
+ }
+
+ $orderby ||= "ORDER BY svc_acct.$field" .
+ ($cgi->param($field."_invert") ? ' DESC' : '');
+
+ }
+
+ $orderby ||= "ORDER BY svcnum";
+
+} elsif ( $cgi->param('popnum') ) {
+ $orderby = "ORDER BY LOWER(username)";
+} elsif ( $cgi->param('svcpart') ) {
+ $orderby = "ORDER BY uid";
+ #$orderby = "ORDER BY svcnum";
+} else {
+ $orderby = "ORDER BY uid";
+
+ my @username_sql;
+
+ my %username_type;
+ foreach ( $cgi->param('username_type') ) {
+ $username_type{$_}++;
+ }
+
+ $cgi->param('username') =~ /^([\w\-\.\&]+)$/; #untaint username_text
+ my $username = $1;
+
+ push @username_sql, "username ILIKE '$username'"
+ if $username_type{'Exact'}
+ || $username_type{'Fuzzy'};
+
+ push @username_sql, "username ILIKE '\%$username\%'"
+ if $username_type{'Substring'}
+ || $username_type{'All'};
+
+ 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'}) {
+ #}
+
+ push @username_sql, "username = '$_'"
+ foreach (keys %username);
+
+ }
+
+ push @extra_sql, '( '. join( ' OR ', @username_sql). ' )';
+
+}
+
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$cgi->param('cust_pkg_fields') =~ /^([\w\,]*)$/ or die "bad cust_pkg_fields";
+my @pkg_fields = split(',', $1);
+foreach my $pkg_field ( @pkg_fields ) {
+ ( my $header = ucfirst($pkg_field) ) =~ s/_/ /; #:/
+ push @header, $header;
+
+ #not the most efficient to do it every field, but this is of niche use. so far
+ push @fields, sub { my $svc_acct = shift;
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg or return '';
+ my $value = $cust_pkg->get($pkg_field);#closures help alot
+ $value ? time2str('%b %d %Y', $value ) : '';
+ };
+
+ push @links, '';
+ $align .= 'c';
+ push @color, '';
+ push @style, '';
+
+}
+
+push @header, FS::UI::Web::cust_header($cgi->param('cust_fields'));
+push @fields, \&FS::UI::Web::cust_fields,
+push @links, map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header($cgi->param('cust_fields'));
+$align .= FS::UI::Web::cust_aligns();
+push @color, FS::UI::Web::cust_colors();
+push @style, FS::UI::Web::cust_styles();
+
+$search_hash{'order_by'} = $orderby;
+$search_hash{'where'} = \@extra_sql;
+
+my $sql_query = FS::svc_acct->search(\%search_hash);
+my $count_query = delete($sql_query->{'count_query'});
+
+</%init>
diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi
new file mode 100755
index 0000000..d0b1029
--- /dev/null
+++ b/httemplate/search/svc_broadband.cgi
@@ -0,0 +1,123 @@
+<% include( 'elements/search.html',
+ 'title' => 'Broadband Search Results',
+ 'name' => 'broadband services',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => [ popurl(2). "view/svc_broadband.cgi?", 'svcnum' ],
+ 'header' => [ '#',
+ 'Service',
+ 'Router',
+ 'IP Address',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ sub { $routerbyblock{shift->blocknum}->routername; },
+ 'ip_addr',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link_router,
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rllr'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my $orderby = 'ORDER BY svcnum';
+my %svc_broadband = ();
+my @extra_sql = ();
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ push @extra_sql, "svcpart = $1";
+} elsif ( $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
+ push @extra_sql, "ip_addr = '$1'";
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = '';
+if ( @extra_sql ) {
+ $extra_sql = ( keys(%svc_broadband) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @extra_sql );
+}
+
+my $count_query = "SELECT COUNT(*) FROM svc_broadband $addl_from ";
+#if ( keys %svc_broadband ) {
+# $count_query .= ' WHERE '.
+# join(' AND ', map "$_ = ". dbh->quote($svc_broadband{$_}),
+# keys %svc_broadband
+# );
+#}
+$count_query .= $extra_sql;
+
+my $sql_query = {
+ 'table' => 'svc_broadband',
+ 'hashref' => {}, #\%svc_broadband,
+ 'select' => join(', ',
+ 'svc_broadband.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $extra_sql,
+ 'addl_from' => $addl_from,
+};
+
+my %routerbyblock = ();
+foreach my $router (qsearch('router', {})) {
+ foreach ($router->addr_block) {
+ $routerbyblock{$_->blocknum} = $router;
+ }
+}
+
+my $link = [ $p.'view/svc_broadband.cgi?', 'svcnum' ];
+
+#XXX get the router link working
+my $link_router = sub { my $routernum = $routerbyblock{shift->blocknum}->routernum;
+ [ $p.'view/router.cgi?'.$routernum, 'routernum' ];
+ };
+
+my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ];
+
+</%init>
diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi
new file mode 100755
index 0000000..08ffdba
--- /dev/null
+++ b/httemplate/search/svc_domain.cgi
@@ -0,0 +1,112 @@
+<% include( 'elements/search.html',
+ 'title' => "Domain Search Results",
+ 'name' => 'domains',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => [ '#',
+ 'Service',
+ 'Domain',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ 'domain',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rll'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my $orderby = 'ORDER BY svcnum';
+my %svc_domain = ();
+my @extra_sql = ();
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ push @extra_sql, "svcpart = $1";
+} else {
+ $cgi->param('domain') =~ /^([\w\-\.]+)$/;
+ $svc_domain{'domain'} = $1;
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = '';
+if ( @extra_sql ) {
+ $extra_sql = ( keys(%svc_domain) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @extra_sql );
+}
+
+my $count_query = "SELECT COUNT(*) FROM svc_domain $addl_from ";
+if ( keys %svc_domain ) {
+ $count_query .= ' WHERE '.
+ join(' AND ', map "$_ = ". dbh->quote($svc_domain{$_}),
+ keys %svc_domain
+ );
+}
+$count_query .= $extra_sql;
+
+my $sql_query = {
+ 'table' => 'svc_domain',
+ 'hashref' => \%svc_domain,
+ 'select' => join(', ',
+ 'svc_domain.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+my $link = [ "${p}view/svc_domain.cgi?", 'svcnum' ];
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+</%init>
diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi
new file mode 100755
index 0000000..f061754
--- /dev/null
+++ b/httemplate/search/svc_external.cgi
@@ -0,0 +1,135 @@
+<% include( 'elements/search.html',
+ 'title' => 'External service search results',
+ 'name' => 'external services',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $redirect,
+ 'header' => [ '#',
+ 'Service',
+ ( FS::Msgcat::_gettext('svc_external-id') || 'External ID' ),
+ ( FS::Msgcat::_gettext('svc_external-title') || 'Title' ),
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ 'id',
+ 'title',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link,
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rlrr'.
+ FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my %svc_external;
+my @extra_sql = ();
+my $orderby = 'ORDER BY svcnum';
+
+my $link = [ "${p}view/svc_external.cgi?", 'svcnum' ];
+my $redirect = $link;
+
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+
+ push @extra_sql, "svcpart = $1";
+
+} elsif ( $cgi->param('title') =~ /^(.*)$/ ) {
+
+ $svc_external{'title'} = $1;
+ $orderby = 'ORDER BY id';
+
+ # is this linked from anywhere???
+ # if( $cgi->param('history') == 1 ) {
+ # @h_svc_external=qsearch('h_svc_external',{ title => $1 });
+ # }
+
+} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) {
+
+ $svc_external{'id'} = $1;
+
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = '';
+if ( @extra_sql ) {
+ $extra_sql = ( keys(%svc_external) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @extra_sql );
+}
+
+my $count_query = "SELECT COUNT(*) FROM svc_external $addl_from ";
+if ( keys %svc_external ) {
+ $count_query .= ' WHERE '.
+ join(' AND ', map "$_ = ". dbh->quote($svc_external{$_}),
+ keys %svc_external
+ );
+}
+$count_query .= $extra_sql;
+
+my $sql_query = {
+ 'table' => 'svc_external',
+ 'hashref' => \%svc_external,
+ 'select' => join(', ',
+ 'svc_external.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+
+</%init>
diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi
new file mode 100755
index 0000000..2bcd0c8
--- /dev/null
+++ b/httemplate/search/svc_forward.cgi
@@ -0,0 +1,146 @@
+<% include( 'elements/search.html',
+ 'title' => "Mail forward Search Results",
+ 'name' => 'mail forwards',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => [ '#',
+ 'Service',
+ 'Mail to',
+ 'Forwards to',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ $format_src,
+ $format_dst,
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link_src,
+ $link_dst,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rlll'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my $orderby = 'ORDER BY svcnum';
+my @extra_sql = ();
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ push @extra_sql, "svcpart = $1";
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql =
+ scalar(@extra_sql)
+ ? ' WHERE '. join(' AND ', @extra_sql )
+ : '';
+
+my $count_query = "SELECT COUNT(*) FROM svc_forward $addl_from $extra_sql";
+my $sql_query = {
+ 'table' => 'svc_forward',
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'svc_forward.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+# <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>
+
+my $link = [ "${p}view/svc_forward.cgi?", 'svcnum' ];
+
+my $format_src = sub {
+ my $svc_forward = shift;
+ if ( $svc_forward->srcsvc_acct ) {
+ $svc_forward->srcsvc_acct->email;
+ } else {
+ my $src = $svc_forward->src;
+ $src = "<I>(anything)</I>$src" if $src =~ /^@/;
+ $src;
+ }
+};
+
+my $link_src = sub {
+ my $svc_forward = shift;
+ if ( $svc_forward->srcsvc_acct ) {
+ [ "${p}view/svc_acct.cgi?", 'srcsvc' ];
+ } else {
+ '';
+ }
+};
+
+my $format_dst = sub {
+ my $svc_forward = shift;
+ if ( $svc_forward->dstsvc_acct ) {
+ $svc_forward->dstsvc_acct->email;
+ } else {
+ $svc_forward->dst;
+ }
+};
+
+my $link_dst = sub {
+ my $svc_forward = shift;
+ if ( $svc_forward->dstsvc_acct ) {
+ [ "${p}view/svc_acct.cgi?", 'dstsvc' ];
+ } else {
+ '';
+ }
+};
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+</%init>
diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi
new file mode 100644
index 0000000..0ad458b
--- /dev/null
+++ b/httemplate/search/svc_phone.cgi
@@ -0,0 +1,174 @@
+<% include( 'elements/search.html',
+ 'title' => "Phone number search results",
+ 'name' => 'phone numbers',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $redirect,
+ 'header' => [ '#',
+ 'Service',
+ 'Country code',
+ 'Phone number',
+ @header,
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ 'countrycode',
+ 'phonenum',
+ @fields,
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link,
+ $link,
+ ( map '', @header ),
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rlrr'.
+ join('', map 'r', @header).
+ FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ ( map '', @header ),
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ ( map '', @header ),
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my @select = ();
+my %svc_phone = ();
+my @extra_sql = ();
+my $orderby = 'ORDER BY svcnum';
+
+my @header = ();
+my @fields = ();
+my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ];
+my $redirect = $link;
+
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+ if ( $cgi->param('usage_total') ) {
+
+ my($beginning,$ending) = FS::UI::Web::parse_beginning_ending($cgi, 'usage');
+
+ $redirect = '';
+
+ #my $and_date = " AND startdate >= $beginning AND startdate <= $ending ";
+ my $and_date = " AND enddate >= $beginning AND enddate <= $ending ";
+
+ my $fromwhere = " FROM cdr WHERE cdr.svcnum = svc_phone.svcnum $and_date";
+
+ #more efficient to join against cdr just once... this will do for now
+ push @select, map { " ( SELECT SUM($_) $fromwhere ) AS $_ " }
+ qw( billsec rated_price );
+
+ my $money_char = $conf->config('money_char') || '$';
+
+ push @header, 'Minutes', 'Billed';
+ push @fields,
+ sub { sprintf('%.3f', shift->get('billsec') / 60 ); },
+ sub { $money_char. sprintf('%.2f', shift->get('rated_price') ); };
+
+ #XXX and termination... (this needs a config to turn on, not by default)
+ if ( 1 ) { # $conf->exists('cdr-termination_hack') { #}
+
+ my $f_w =
+ " FROM cdr_termination LEFT JOIN cdr USING ( acctid ) ".
+ " WHERE cdr.carrierid = CAST(svc_phone.phonenum AS BIGINT) ". # XXX connectone-specific, has to match svc_external.id :/
+ $and_date;
+
+ push @select,
+ " ( SELECT SUM(billsec) $f_w ) AS term_billsec ",
+ " ( SELECT SUM(cdr_termination.rated_price) $f_w ) AS term_rated_price";
+
+ push @header, 'Term Min', 'Term Billed';
+ push @fields,
+ sub { sprintf('%.3f', shift->get('term_billsec') / 60 ); },
+ sub { $money_char. sprintf('%.2f', shift->get('rated_price') ); };
+
+ }
+
+
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ push @extra_sql, "svcpart = $1";
+} else {
+ $cgi->param('phonenum') =~ /^([\d\- ]+)$/;
+ ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g;
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = '';
+if ( @extra_sql ) {
+ $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @extra_sql );
+}
+
+my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from ";
+if ( keys %svc_phone ) {
+ $count_query .= ' WHERE '.
+ join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}),
+ keys %svc_phone
+ );
+}
+$count_query .= $extra_sql;
+
+my $sql_query = {
+ 'table' => 'svc_phone',
+ 'hashref' => \%svc_phone,
+ 'select' => join(', ',
+ 'svc_phone.*',
+ 'part_svc.svc',
+ @select,
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+</%init>
diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi
new file mode 100755
index 0000000..2e3c461
--- /dev/null
+++ b/httemplate/search/svc_www.cgi
@@ -0,0 +1,113 @@
+<% include( 'elements/search.html',
+ 'title' => 'Virtual Host Search Results',
+ 'name' => 'virtual hosts',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => [ '#',
+ 'Service',
+ 'Zone',
+ 'User',
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ sub { $_[0]->domain_record->zone },
+ sub {
+ my $svc_www = shift;
+ my $svc_acct = $svc_www->svc_acct;
+ $svc_acct
+ ? $svc_acct->email
+ : '';
+ },
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ $ulink,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rlll'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+#my $conf = new FS::Conf;
+
+my $orderby = 'ORDER BY svcnum';
+my @extra_sql = ();
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ push @extra_sql, "svcpart = $1";
+}
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql =
+ scalar(@extra_sql)
+ ? ' WHERE '. join(' AND ', @extra_sql )
+ : '';
+
+
+my $count_query = "SELECT COUNT(*) FROM svc_www $addl_from $extra_sql";
+my $sql_query = {
+ 'table' => 'svc_www',
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'svc_www.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ];
+#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ];
+my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ];
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+</%init>
diff --git a/httemplate/search/timeworked.html b/httemplate/search/timeworked.html
new file mode 100644
index 0000000..d07cd4f
--- /dev/null
+++ b/httemplate/search/timeworked.html
@@ -0,0 +1,130 @@
+<% include( 'elements/search.html',
+ 'title' => 'Time Worked',
+ 'name' => 'time',
+ 'html_form' => qq!<FORM NAME="timeForm" ACTION="${p}misc/timeworked.html" METHOD="POST">!,
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Ticket',
+ 'Date',
+ 'Time',
+ '', # checkbox column
+ ],
+ 'fields' => [ sub { shift->[0] },
+ sub { encode_entities(shift->[1]) },
+ sub { shift->[2] },
+ sub { my $seconds = shift->[3];
+ (($seconds < 0) ? '-' : '') .
+ concise(duration($seconds));
+ },
+ sub {
+ my $row = shift;
+ my $seconds = $row->[3];
+ my $id = $row->[4];
+ qq!<INPUT NAME="transactionid$id" TYPE="checkbox" VALUE="1">!.
+ qq!<INPUT NAME="seconds$id" TYPE="hidden" VALUE="$seconds">!;
+ },
+ ],
+ 'links' => [
+ $link,
+ $link,
+ '',
+ '',
+ '',
+ ],
+ 'html_foot' => $html_foot,
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Time queue');
+
+my @groupby = ();
+
+my $transactiontime = "
+ CASE Transactions.Type WHEN 'Set'
+ THEN (TO_NUMBER(NewValue,'999999')-TO_NUMBER(OldValue, '999999')) * 60
+ ELSE TimeTaken*60
+ END
+";
+
+push @groupby, qw( Transactions.Type NewValue OldValue TimeTaken );
+
+my $appliedtimeclause = "COALESCE (SUM(acct_rt_transaction.seconds), 0)";
+
+my $appliedtimeselect = "
+ COALESCE(
+ ( SELECT SUM(seconds) FROM acct_rt_transaction
+ WHERE transaction_id = Transactions.id
+ ),
+ 0
+ )
+";
+
+push @groupby, "Transactions.id";
+
+my $wheretimeleft = "$transactiontime != $appliedtimeselect";
+
+push @groupby, "Tickets.id";
+push @groupby, "Tickets.Subject";
+push @groupby, "Transactions.Created";
+
+my $groupby = join(',', @groupby);
+
+my $where = "
+ WHERE ObjectType='RT::Ticket'
+ AND ( ( Transactions.Type='Set' AND Field='TimeWorked' )
+ OR Transactions.Type='Create'
+ OR Transactions.Type='Comment'
+ OR Transactions.Type='Correspond'
+ )
+ AND $wheretimeleft
+";
+ #AND $wheretimeleft
+
+my $str2time_sql = str2time_sql;
+my $closing = str2time_sql_closing;
+
+my($begin, $end) = FS::UI::Web::parse_beginning_ending($cgi);
+$where .= " AND $str2time_sql Transactions.Created $closing >= $begin ".
+ " AND $str2time_sql Transactions.Created $closing <= $end ";
+
+my $query = "
+ SELECT Tickets.id, Tickets.Subject,
+ TO_CHAR(Transactions.Created, 'Dy Mon DD HH24:MI:SS YYYY'),
+ $transactiontime-$appliedtimeclause,
+ Transactions.id
+ FROM Transactions
+ JOIN Tickets ON Transactions.ObjectId = Tickets.id
+ LEFT JOIN acct_rt_transaction
+ ON Transactions.id = acct_rt_transaction.transaction_id
+ $where
+ GROUP BY $groupby
+ ORDER BY Transactions.Created
+";
+
+my $count_query = "SELECT COUNT(*) FROM Transactions $where";
+
+my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->[0]; } ];
+
+my $html_foot = qq'
+ <INPUT TYPE="hidden" NAME="begin" VALUE="$begin">
+ <INPUT TYPE="hidden" NAME="end" VALUE="$end">
+ <BR>
+ <INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">
+ <INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">
+ <BR>
+ <INPUT TYPE="submit" NAME="action" VALUE="Assign to accounts"><BR>
+ <SCRIPT TYPE="text/javascript">
+ function setAll(setTo) {
+ theForm = document.timeForm;
+ for (i=0,n=theForm.elements.length;i<n;i++)
+ if (theForm.elements[i].name.indexOf("transactionid") != -1)
+ theForm.elements[i].checked = setTo;
+ }
+ </SCRIPT>
+';
+
+</%init>
diff --git a/httemplate/search/unapplied_cust_pay.html b/httemplate/search/unapplied_cust_pay.html
new file mode 100755
index 0000000..73361c0
--- /dev/null
+++ b/httemplate/search/unapplied_cust_pay.html
@@ -0,0 +1,30 @@
+<% include( 'elements/cust_main_dayranges.html',
+ #'title' => 'Prepaid Balance Aging Summary', #???
+ 'title' => 'Unapplied Payments Aging Summary',
+ 'range_sub' => \&unapplied_payments,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
+<%once>
+
+sub unapplied_payments {
+ my($start, $end, $offset) = @_;
+
+ #handle start and end ranges (86400 = 24h * 60m * 60s)
+ my $str2time = str2time_sql;
+ my $closing = str2time_sql_closing;
+ $start = "( $str2time now() $closing - ".($start + $offset) * 86400 . ' )';
+ $end = $end ?
+ "( $str2time now() $closing - ".($end + $offset) * 86400 . ' )'
+ : '';
+
+ FS::cust_main->unapplied_payments_date_sql( $start, $end );
+
+}
+
+</%once>
diff --git a/httemplate/view/REAL_logo.cgi b/httemplate/view/REAL_logo.cgi
new file mode 100755
index 0000000..c269c7d
--- /dev/null
+++ b/httemplate/view/REAL_logo.cgi
@@ -0,0 +1,14 @@
+<% $conf->config_binary("logo.png", $agentnum) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $agentnum = '';
+my @agentnums = $FS::CurrentUser::CurrentUser->agentnums;
+if ( scalar(@agentnums) == 1 ) {
+ $agentnum = $agentnums[0];
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
diff --git a/httemplate/view/attachment.html b/httemplate/view/attachment.html
new file mode 100644
index 0000000..5fc0539
--- /dev/null
+++ b/httemplate/view/attachment.html
@@ -0,0 +1,16 @@
+<% $attach->body %>
+<%init>
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $attachnum = $1 or die 'Invalid attachment number';
+$FS::CurrentUser::CurrentUser->access_right('Download attachment') or die 'access denied';
+
+my $attach = qsearchs('cust_attachment', { attachnum => $attachnum }) or die "Attachment not found: $attachnum";
+die 'access denied' if $attach->disabled;
+
+$m->clear_buffer;
+$r->content_type($attach->mime_type || 'text/plain');
+$r->headers_out->add('Content-Disposition' => 'attachment;filename=' . $attach->filename);
+
+
+</%init>
diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi
new file mode 100755
index 0000000..ad2ff54
--- /dev/null
+++ b/httemplate/view/cust_bill-logo.cgi
@@ -0,0 +1,31 @@
+<% $conf->config_binary("logo$templatename.png", $agentnum) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices')
+ or $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $templatename;
+my $agentnum = '';
+if ( $cgi->param('invnum') ) {
+ $templatename = $cgi->param('template') || $cgi->param('templatename');
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $cgi->param('invnum') } )
+ or die 'unknown invnum';
+ $agentnum = $cust_bill->cust_main->agentnum;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^([^\.\/]*)$/ or die 'illegal query';
+ $templatename = $1;
+}
+
+if ( $templatename && $conf->exists("logo_$templatename.png") ) {
+ $templatename = "_$templatename";
+} else {
+ $templatename = '';
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
new file mode 100755
index 0000000..51e47e0
--- /dev/null
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -0,0 +1,40 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $invnum =~ s/\.pdf//i;
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+my $pdf = $cust_bill->print_pdf(\%opt);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
new file mode 100755
index 0000000..881491f
--- /dev/null
+++ b/httemplate/view/cust_bill-ps.cgi
@@ -0,0 +1,35 @@
+<% $cust_bill->print_ps(\%opt) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+http_header('Content-Type' => 'application/postscript' );
+
+</%init>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
new file mode 100755
index 0000000..ce8d96a
--- /dev/null
+++ b/httemplate/view/cust_bill.cgi
@@ -0,0 +1,151 @@
+<% include("/elements/header.html",'Invoice View', menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+% if ( $conf->exists('deleteinvoices')
+% && $curuser->access_right('Delete invoices' )
+% )
+% {
+
+ <SCRIPT TYPE="text/javascript">
+ function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <A HREF = "javascript:areyousure(
+ '<%$p%>misc/delete-cust_bill.html?<% $invnum %>',
+ 'Are you sure you want to delete this invoice?'
+ )"
+ TITLE = "Delete this invoice from the database completely"
+ >Delete this invoice</A>
+ <BR><BR>
+
+% }
+
+% if ( $cust_bill->owed > 0
+% && scalar( grep $payby{$_}, qw(BILL CASH WEST MCRD) )
+% && $curuser->access_right('Post payment')
+% && ! $conf->exists('pkg-balances')
+% )
+% {
+% my $s = 0;
+
+ Post
+
+% if ( $payby{'BILL'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=BILL;invnum=<% $invnum %>">check</A>
+% }
+
+% if ( $payby{'CASH'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=CASH;invnum=<% $invnum %>">cash</A>
+% }
+
+% if ( $payby{'WEST'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;invnum=<% $invnum %>">Western Union</A>
+% }
+
+% if ( $payby{'MCRD'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;invnum=<% $invnum %>">manual credit card</A>
+% }
+
+ payment against this invoice<BR><BR>
+
+% }
+
+% if ( $curuser->access_right('Resend invoices') ) {
+
+ <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>">Re-print this invoice</A>
+
+% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>">Re-email this invoice</A>
+% }
+
+% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>">Re-fax this invoice</A>
+% }
+
+ <BR><BR>
+
+% }
+
+% if ( $conf->exists('invoice_latex') ) {
+
+ <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>">View typeset invoice PDF</A>
+ <BR><BR>
+% }
+
+% my $br = 0;
+% if ( $cust_bill->num_cust_event ) { $br++;
+<A HREF="<%$p%>search/cust_event.html?invnum=<% $cust_bill->invnum %>">(&nbsp;View invoice events&nbsp;)</A>
+% }
+
+% if ( $cust_bill->num_cust_bill_event ) { $br++;
+<A HREF="<%$p%>search/cust_bill_event.cgi?invnum=<% $cust_bill->invnum %>">(&nbsp;View deprecated, old-style invoice events&nbsp;)</A>
+% }
+
+<% $br ? '<BR><BR>' : '' %>
+
+% if ( $conf->exists('invoice_html') ) {
+ <% join('', $cust_bill->print_html(\%opt) ) %>
+% } else {
+ <PRE><% join('', $cust_bill->print_text(\%opt) ) %></PRE>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = $cgi->param('notice_name');
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $conf = new FS::Conf;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+my $custnum = $cust_bill->custnum;
+my $display_custnum = $cust_bill->cust_main->display_custnum;
+
+#my $printed = $cust_bill->printed;
+
+my $link = "invnum=$invnum";
+$link .= ';template='. uri_escape($template) if $template;
+$link .= ';notice_name='. $notice_name if $notice_name;
+
+</%init>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
new file mode 100755
index 0000000..ca8bf54
--- /dev/null
+++ b/httemplate/view/cust_main.cgi
@@ -0,0 +1,262 @@
+<% include('/elements/header.html', {
+ 'title' => "Customer: ". $cust_main->name,
+ 'nobr' => 1,
+ })
+%>
+<BR>
+
+<% include('/elements/menubar.html',
+ { 'newstyle' => 1,
+ 'selected' => $viewname{$view},
+ 'url_base' => $cgi->url. "?custnum=$custnum;show=",
+ },
+ %views,
+ )
+%>
+<BR>
+
+<% include('/elements/init_overlib.html') %>
+
+<SCRIPT TYPE="text/javascript">
+function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+% if ( $view eq 'basics' || $view eq 'jumbo' ) {
+
+% if ( $curuser->access_right('Edit customer') ) {
+ <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> |
+% }
+
+% if ( $curuser->access_right('Cancel customer')
+% && $cust_main->ncancelled_pkgs
+% ) {
+
+ <% include( '/elements/popup_link-cust_main.html',
+ { 'action' => $p. 'misc/cancel_cust.html',
+ 'label' => 'Cancel&nbsp;this&nbsp;customer',
+ 'actionlabel' => 'Confirm Cancellation',
+ 'color' => '#ff0000',
+ 'cust_main' => $cust_main,
+ 'width' => 616, #make room for reasons
+ }
+ )
+ %> |
+
+% }
+
+% if ( $conf->exists('deletecustomers')
+% && $curuser->access_right('Delete customer')
+% ) {
+ <A HREF="<% $p %>misc/delete-customer.cgi?<% $custnum%>">Delete this customer</A> |
+% }
+
+% unless ( $conf->exists('disable_customer_referrals') ) {
+ <A HREF="<% $p %>edit/cust_main.cgi?referral_custnum=<% $custnum %>">Refer a new customer</A> |
+ <A HREF="<% $p %>search/cust_main.cgi?referral_custnum=<% $custnum %>">View this customer's referrals</A>
+% }
+
+<BR><BR>
+
+% if ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% ) {
+
+ <A HREF="<% $p %>search/cust_event.html?custnum=<% $custnum %>">View billing events for this customer</A>
+ <BR><BR>
+
+% }
+
+%my $signupurl = $conf->config('signupurl');
+%if ( $signupurl ) {
+ This customer's signup URL: <A HREF="<% $signupurl %>?ref=<% $custnum %>"><% $signupurl %>?ref=<% $custnum %></A><BR><BR>
+% }
+
+
+<A NAME="cust_main"></A>
+<TABLE BORDER=0>
+<TR>
+ <TD VALIGN="top">
+ <% include('cust_main/contacts.html', $cust_main ) %>
+ </TD>
+ <TD VALIGN="top" STYLE="padding-left: 54px">
+ <% include('cust_main/misc.html', $cust_main ) %>
+% if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ <BR>
+ <% include('cust_main/billing.html', $cust_main ) %>
+% }
+
+ </TD>
+</TR>
+</TABLE>
+
+% }
+
+% if ( $view eq 'notes' || $view eq 'jumbo' ) {
+
+%if ( $cust_main->comments =~ /[^\s\n\r]/ ) {
+<BR>
+Comments
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+<TR>
+ <TD BGCOLOR="#ffffff">
+ <PRE><% encode_entities($cust_main->comments) %></PRE>
+ </TD>
+</TR>
+</TABLE></TABLE>
+<BR><BR>
+% }
+<A NAME="notes">
+% my $notecount = scalar($cust_main->notes());
+% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) {
+
+% unless ( $view eq 'notes' && $cust_main->comments !~ /[^\s\n\r]/ ) {
+ <BR>
+ <A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR>
+% }
+
+% if ( $curuser->access_right('Add customer note') &&
+% ! $conf->exists('cust_main-disable_notes')
+% ) {
+
+ <% include( '/elements/popup_link-cust_main.html',
+ 'label' => 'Add customer note',
+ 'action' => $p. 'edit/cust_main_note.cgi',
+ 'actionlabel' => 'Enter customer note',
+ 'cust_main' => $cust_main,
+ 'width' => 616,
+ 'height' => 538, #575
+ )
+ %>
+
+% }
+
+<BR>
+
+<% include('cust_main/notes.html', 'custnum' => $cust_main->custnum ) %>
+
+% }
+<BR>
+
+% if(! $conf->config('disable_cust_attachment')
+% and $curuser->access_right('Add attachment')) {
+<% include( '/elements/popup_link-cust_main.html',
+ 'label' => 'Attach file',
+ 'action' => $p.'edit/cust_main_attach.cgi',
+ 'actionlabel' => 'Upload file',
+ 'cust_main' => $cust_main,
+ 'width' => 480,
+ 'height' => 296,
+ )
+%>
+% }
+% if( $curuser->access_right('View attachments') ) {
+<% include('cust_main/attachments.html', 'custnum' => $cust_main->custnum ) %>
+% if ($cgi->param('show_deleted')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . '#notes'
+ %>"><I>(Show active attachments)</I></A>
+% }
+% elsif($curuser->access_right('View deleted attachments')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . ';show_deleted=1#notes'
+ %>"><I>(Show deleted attachments)</I></A>
+% }
+% }
+<BR>
+
+% }
+
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A><BR>
+% }
+
+% if ( $view eq 'tickets' || $view eq 'jumbo' ) {
+
+% if ( $conf->config('ticket_system') ) {
+ <% include('cust_main/tickets.html', $cust_main ) %>
+% }
+ <BR><BR>
+
+% }
+
+% if ( $view eq 'jumbo' ) { #XXX enable me && $curuser->access_right('View customer packages') {
+
+ <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A><BR>
+% }
+
+% if ( $view eq 'packages' || $view eq 'jumbo' ) {
+
+% #XXX enable me# if ( $curuser->access_right('View customer packages') {
+<% include('cust_main/packages.html', $cust_main ) %>
+% #}
+
+% }
+
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
+% }
+
+% if ( $view eq 'payment_history' || $view eq 'jumbo' ) {
+
+% if ( $conf->config('payby-default') ne 'HIDE' ) {
+ <% include('cust_main/payment_history.html', $cust_main ) %>
+% }
+
+% }
+
+% if ( $view eq 'change_history' ) { # || $view eq 'jumbo'
+<% include('cust_main/change_history.html', $cust_main ) %>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View customer');
+
+my $conf = new FS::Conf;
+
+my $custnum;
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+} else {
+ die "No customer specified (bad URL)!" unless $cgi->keywords;
+ my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+}
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Customer not found!" unless $cust_main;
+
+#false laziness w/pref/pref.html and Conf.pm (cust_main-default_view)
+tie my %views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+;
+$views{'Tickets'} = 'tickets'
+ if $conf->config('ticket_system');
+$views{'Packages'} = 'packages';
+$views{'Payment History'} = 'payment_history'
+ unless $conf->config('payby-default' eq 'HIDE');
+$views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$views{'Jumbo'} = 'jumbo';
+
+my %viewname = reverse %views;
+
+my $view = $cgi->param('show') || $curuser->default_customer_view;
+
+</%init>
diff --git a/httemplate/view/cust_main/attachments.html b/httemplate/view/cust_main/attachments.html
new file mode 100755
index 0000000..bdd4f59
--- /dev/null
+++ b/httemplate/view/cust_main/attachments.html
@@ -0,0 +1,156 @@
+% if ( scalar(@attachments) ) {
+
+ <% include('/elements/init_overlib.html') %>
+
+ <% include("/elements/table-grid.html") %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+% if ( $conf->exists('cust_main_note-display_times') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Filename</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Size</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+% if($cgi->param('show_deleted')) {
+% if ($curuser->access_right('View deleted attachments')) {
+% @attachments = grep { $_->disabled } @attachments;
+% }
+% else {
+% @attachments = ();
+% }
+% }
+% else {
+% @attachments = grep { not $_->disabled } @attachments;
+% }
+%
+% foreach my $attach (@attachments) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $pop = popurl(3);
+% my $attachnum = $attach->attachnum;
+% my $edit = '';
+% if($attach->disabled) { # then you can undelete it or purge it.
+% if ($curuser->access_right('Undelete attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "undelete=1",
+% 'Undelete attachment');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(undelete)</A>!;
+% }
+% if ($curuser->access_right('Purge attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "purge=1",
+% 'Purge attachment',
+% 'Permanently remove this file?');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(purge)</A>!;
+% }
+% }
+% else { # you can download or edit it
+% if ($curuser->access_right('Edit attachment') ) {
+% my $clickjs = popup('edit/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum",
+% 'Edit attachment properties');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(edit)</A>!;
+% }
+% if($curuser->access_right('Delete attachment') ) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;delete=1",
+% 'Delete attachment',
+% 'Delete this file?');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(delete)</A>!;
+% }
+% if ($curuser->access_right('Download attachment') ) {
+% $edit .= qq!&nbsp; <A HREF="!.popurl(1).'attachment.html?'.$attachnum.qq!">(download)</A>!;
+% }
+% }
+
+ <TR>
+ <% note_datestr($attach,$conf,$bgcolor) %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->usernum ? $attach->access_user->name : $attach->otaker %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->filename %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->title %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->mime_type %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% size_units( $attach->size ) %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $edit %>
+ </TD>
+ </TR>
+
+% } #end display notes
+
+</TABLE>
+
+% }
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" if !$curuser->access_right('View attachments');
+my(%opt) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+die "Customer not found!" unless $cust_main;
+
+my (@attachments) = qsearch('cust_attachment', {'custnum' => $custnum});
+
+#subroutines
+
+sub note_datestr {
+ my($note, $conf, $bgcolor) = @_ or return '';
+ my $td = qq{<TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">};
+ my $format = "$td%b&nbsp;%o,&nbsp;%Y</TD>";
+ $format .= "$td%l:%M%P</TD>"
+ if $conf->exists('cust_main_note-display_times');
+ ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub size_units {
+ my $bytes = shift;
+ return $bytes if $bytes < 1024;
+ return int($bytes / 1024)."K" if $bytes < 1048576;
+ return int($bytes / 1048576)."M";
+}
+
+sub popup {
+ my ($url, $label, $confirm) = @_;
+ my $onclick =
+ include('/elements/popup_link_onclick.html',
+ 'action' => popurl(2).$url,
+ 'actionlabel' => $label,
+ 'width' => 510,
+ 'height' => 315,
+ 'frame' => 'top',
+ );
+ $onclick = qq!if(confirm('$confirm')) { $onclick }! if $confirm;
+ return qq!onclick="$onclick"!;
+}
+
+
+</%init>
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
new file mode 100644
index 0000000..c8d0c47
--- /dev/null
+++ b/httemplate/view/cust_main/billing.html
@@ -0,0 +1,253 @@
+Billing information
+%# If we can't see the unencrypted card, then bill now is an exercise in
+%# frustration (without some sort of job queue magic to send it to a secure
+%# machine, anyway)
+%if ( $FS::CurrentUser::CurrentUser->access_right('Bill customer now')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+ (<A HREF="<% $p %>misc/bill.cgi?<% $cust_main->custnum %>">Bill now</A>)
+% }
+
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+
+%( my $balance = $cust_main->balance )
+% =~ s/^(\-?)(.*)$/<FONT SIZE=+1>$1<\/FONT>$money_char$2/;
+
+<TR>
+ <TD ALIGN="right">Balance due</TD>
+ <TD BGCOLOR="#ffffff"><B><% $balance %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Billing&nbsp;type</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) {
+
+
+ Credit&nbsp;card&nbsp;<% $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Card number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paymask %></TD>
+</TR>
+%
+%#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html
+%my( $mon, $year );
+%my $date = $cust_main->paydate || '12-2037';
+%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+%} else {
+% warn "unrecognized expiration date format: $date";
+% ( $mon, $year ) = ( '', '' );
+%}
+%
+
+<TR>
+ <TD ALIGN="right">Expiration</TD>
+ <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
+</TR>
+% if ( $cust_main->paystart_month ) {
+
+ <TR>
+ <TD ALIGN="right">Start date</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paystart_month. '/'. $cust_main->paystart_year %>
+ </TR>
+% } elsif ( $cust_main->payissue ) {
+
+ <TR>
+ <TD ALIGN="right">Issue #</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payissue %>
+ </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->paymask );
+
+
+ Electronic&nbsp;check&nbsp;<% $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">ABA/Routing code</TD>
+ <TD BGCOLOR="#ffffff"><% $aba %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Account number</TD>
+ <TD BGCOLOR="#ffffff"><% $account %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Account type</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paytype %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Bank name</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
+</TR>
+% if ( $conf->exists('show_bankstate') ) {
+<TR>
+ <TD ALIGN="right"><% $paystate_label %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paystate || '&nbsp;&nbsp;&nbsp;' %></TD>
+</TR>
+% }
+% } elsif ( $cust_main->payby eq 'LECB' ) {
+% $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/;
+% my $payinfo = "$1-$2-$3";
+%
+
+
+ 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' ) {
+
+
+ Billing
+ </TD>
+</TR>
+% if ( $cust_main->payinfo ) {
+
+<TR>
+ <TD ALIGN="right">P.O. </TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
+</TR>
+% }
+
+
+<TR>
+ <TD ALIGN="right">Attention</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
+</TR>
+% } elsif ( $cust_main->payby eq 'COMP' ) {
+
+
+ Complimentary
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Authorized&nbsp;by</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
+</TR>
+%
+%#false laziness w/above etc.
+%my( $mon, $year );
+%my $date = $cust_main->paydate || '12-2037';
+%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+%} else {
+% warn "unrecognized expiration date format: $date";
+% ( $mon, $year ) = ( '', '' );
+%}
+%
+
+<TR>
+ <TD ALIGN="right">Expiration</TD>
+ <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
+</TR>
+% }
+
+% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+<TR>
+ <TD ALIGN="right">Tax&nbsp;exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax ? 'yes' : 'no' %></TD>
+</TR>
+% foreach my $exempt_group ( @exempt_groups ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;exempt (<% $exempt_group %> taxes)</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax_exemption($exempt_group) ? 'yes' : 'no' %></TD>
+</TR>
+% }
+
+% if ( $conf->exists('enable_taxproducts') ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;location</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->geocode('cch') %></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">FAX&nbsp;invoices</TD>
+ <TD BGCOLOR="#ffffff">
+ <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'yes' : 'no' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Email&nbsp;invoices</TD>
+ <TD BGCOLOR="#ffffff">
+ <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Invoice&nbsp;terms</TD>
+ <TD BGCOLOR="#ffffff">
+ <% $cust_main->invoice_terms || 'Default ('. ( $conf->config('invoice_default_terms') || 'Payable upon receipt' ). ')' %>
+ </TD>
+</TR>
+
+% if ( $conf->exists('voip-cust_cdr_spools') ) {
+ <TR>
+ <TD ALIGN="right">Spool&nbsp;CDRs</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->spool_cdr ? 'yes' : 'no' %></TD>
+ </TR>
+% }
+
+% if ( $conf->exists('voip-cust_cdr_squelch') ) {
+ <TR>
+ <TD ALIGN="right">Print&nbsp;CDRs</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->squelch_cdr ? 'no' : 'yes' %></TD>
+ </TR>
+% }
+
+% if ( $conf->exists('voip-cust_email_csv_cdr') ) {
+ <TR>
+ <TD ALIGN="right">Email&nbsp;CDRs&nbsp;as&nbsp;CSV</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->email_csv_cdr ? 'yes' : 'no' %></TD>
+ </TR>
+% }
+
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+ <TR>
+ <TD ALIGN="right">CDR termination settlement</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->cdr_termination_percentage %><% $cust_main->cdr_termination_percentage =~ /\d/ ? '%' : '' %></TD>
+ </TR>
+% }
+
+</TABLE></TD></TR></TABLE>
+<%once>
+
+my $paystate_label = FS::Msgcat::_gettext('paystate');
+$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+my @invoicing_list = $cust_main->invoicing_list;
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+#false laziness w/edit/cust_main/billing.html
+my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+my $term_sth = dbh->prepare($term_sql) or die dbh->errstr;
+$term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+my $show_term = $term_sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
new file mode 100644
index 0000000..53a79f4
--- /dev/null
+++ b/httemplate/view/cust_main/change_history.html
@@ -0,0 +1,302 @@
+% if ( int( time - (keys %years)[0] * 31556736 ) > $start ) {
+ Show:
+% my $chy = $cgi->param('change_history-years');
+% foreach my $y (keys %years) {
+% if ( $y == $years ) {
+ <FONT SIZE="+1"><% $years{$y} %></FONT>
+% } else {
+% $cgi->param('change_history-years', $y);
+ <A HREF="<% $cgi->self_url %>"><% $years{$y} %></A>
+% }
+% last if int( time - $y * 31556736 ) < $start;
+% }
+% $cgi->param('change_history-years', $chy);
+% }
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">User</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Item</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+</TR>
+
+% foreach my $item ( sort { $a->history_date <=> $b->history_date
+% #|| table order
+% || $a->historynum <=> $b->historynum
+% }
+% @history
+% )
+% {
+%
+% my $history_other = '';
+% my $act = $item->history_action;
+% if ( $act =~ /^replace/ ) {
+% my $pkey = $item->primary_key;
+% my $date = $item->history_date;
+% $history_other = qsearchs({
+% 'table' => $item->table,
+% 'hashref' => { $pkey => $item->$pkey(),
+% 'history_action' => $replace_other{$act},
+% 'historynum' => { 'op' => $replace_dir{$act},
+% 'value' => $item->historynum
+% },
+% },
+% 'extra_sql' => "
+% AND history_date $replace_direq{$act} $date
+% AND ($date $replace_op{$act} $fuzz) $replace_direq{$act} history_date
+% ORDER BY historynum $replace_ord{$act} LIMIT 1
+% ",
+% });
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $otaker = $item->history_user;
+% $otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+% $otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+% $otaker = '<i>job queue</i>' if $otaker eq 'fs_queue';
+ <% $otaker %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $d = time2str('%b %o, %Y', $item->history_date );
+% $d =~ s/ /&nbsp;/g;
+ <% $d %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $t = time2str('%r', $item->history_date );
+% $t =~ s/ /&nbsp;/g;
+ <% $t %>
+ </TD>
+ <TD ALIGN="center" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $label = $h_tables{$item->table};
+% $label = &{ $h_table_labelsub{$item->table} }( $item, $label )
+% if $h_table_labelsub{$item->table};
+ <% $label %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $action{$item->history_action} %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% join(', ',
+ map { my $value = ( $_ =~ /(^pay(info|cvv)|^ss|_password)$/ )
+ ? 'N/A'
+ : $item->get($_);
+ $value = substr($value, 0, 77).'...' if length($value) > 80;
+ $value = encode_entities($value);
+ "<I>$_</I>:<B>$value</B>";
+ }
+ grep { $history_other
+ ? ( $item->get($_) ne $history_other->get($_) )
+ : ( $item->get($_) =~ /\S/ )
+ }
+ grep { ! /^(history|custnum$)/i }
+ $item->fields
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+<%once>
+
+# length-switching
+
+tie my %years, 'Tie::IxHash',
+ .5 => '6 months',
+ 1 => '1 year',
+ 2 => '2 years',
+ 5 => '5 years',
+ 39 => 'all history',
+;
+
+# labeling history rows
+
+my %action = (
+ 'insert' => 'Insert', #'Create',
+ 'replace_old' => 'Change&nbsp;from',
+ 'replace_new' => 'Change&nbsp;to',
+ 'delete' => 'Remove',
+);
+
+# finding the other replace row
+
+my %replace_other = (
+ 'replace_new' => 'replace_old',
+ 'replace_old' => 'replace_new',
+);
+my %replace_dir = (
+ 'replace_new' => '<',
+ 'replace_old' => '>',
+);
+my %replace_direq = (
+ 'replace_new' => '<=',
+ 'replace_old' => '>=',
+);
+my %replace_op = (
+ 'replace_new' => '-',
+ 'replace_old' => '+',
+);
+my %replace_ord = (
+ 'replace_new' => 'DESC',
+ 'replace_old' => 'ASC',
+);
+
+my $fuzz = 5; #seems like a lot
+
+# which tables to search and what to call them
+
+tie my %tables, 'Tie::IxHash',
+ 'cust_main' => 'Customer',
+ 'cust_main_invoice' => 'Invoice destination',
+ 'cust_pkg' => 'Package',
+ #? or just svc_* ? 'cust_svc' =>
+ 'svc_acct' => 'Account',
+ 'radius_usergroup' => 'RADIUS group',
+ 'svc_domain' => 'Domain',
+ 'svc_www' => 'Hosting',
+ 'svc_forward' => 'Mail forward',
+ 'svc_broadband' => 'Broadband',
+ 'svc_external' => 'External service',
+ 'svc_phone' => 'Phone',
+ 'phone_device' => 'Phone device',
+ #? it gets provisioned anyway 'phone_avail' => 'Phone',
+;
+
+my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+
+my %table_join = (
+ 'svc_acct' => $svc_join,
+ 'radius_usergroup' => $svc_join,
+ 'svc_domain' => $svc_join,
+ 'svc_www' => $svc_join,
+ 'svc_forward' => $svc_join,
+ 'svc_broadband' => $svc_join,
+ 'svc_external' => $svc_join,
+ 'svc_phone' => $svc_join,
+ 'phone_device' => $svc_join,
+);
+
+my %h_tables = map { ( "h_$_" => $tables{$_} ) } keys %tables;
+
+my %pkgpart = ();
+my $pkg_labelsub = sub {
+ my($item, $label) = @_;
+ $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+ $label. ': <b>'. encode_entities($pkgpart{$item->pkgpart}). '</b>';
+};
+
+my $svc_labelsub = sub {
+ my($item, $label) = @_;
+ $label. ': <b>'. encode_entities($item->label($item->history_date)). '</b>';
+};
+
+my %h_table_labelsub = (
+ 'h_cust_pkg' => $pkg_labelsub,
+ 'h_svc_acct' => $svc_labelsub,
+ #'h_radius_usergroup' =>
+ 'h_svc_domain' => $svc_labelsub,
+ 'h_svc_www' => $svc_labelsub,
+ 'h_svc_forward' => $svc_labelsub,
+ 'h_svc_broadband' => $svc_labelsub,
+ 'h_svc_external' => $svc_labelsub,
+ 'h_svc_phone' => $svc_labelsub,
+ #'h_phone_device'
+);
+
+# cust_main
+# cust_main_invoice
+
+# cust_pkg
+# cust_pkg_option?
+# cust_pkg_detail
+# cust_pkg_reason? no
+
+#cust_svc
+#cust_svc_option?
+#svc_*
+# svc_acct
+# radius_usergroup
+# acct_snarf? is this even used?
+# svc_domain
+# domain_record
+# registrar
+# svc_forward
+# svc_www
+# svc_broadband
+# (virtual fields? eh... maybe when they're real)
+# svc_external
+# svc_phone
+# phone_device
+# phone_avail
+
+# future:
+
+# inventory_item (from services)
+# pkg_referral? (changed?)
+
+#random others:
+
+# cust_location?
+# cust_main-exemption?? (295.ca named tax exemptions)
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access deined"
+ unless $curuser->access_right('View customer history');
+
+# find out the beginning of this customer history, if possible
+my $h_insert = qsearchs({
+ 'table' => 'h_cust_main',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ 'history_action' => 'insert',
+ },
+ 'extra_sql' => 'ORDER BY historynum LIMIT 1',
+});
+my $start = $h_insert ? $h_insert->history_date : 0;
+
+# retreive the history
+
+my @history = ();
+
+my $years = $conf->config('change_history-years') || .5;
+if ( $cgi->param('change_history-years') =~ /^([\d\.]+)$/ ) {
+ $years = $1;
+}
+my $newer_than = int( time - $years * 31556736 ); #60*60*24*365.24
+
+local($FS::Record::nowarn_classload) = 1;
+
+foreach my $table ( keys %tables ) {
+ my @items = qsearch({
+ 'table' => "h_$table",
+ 'addl_from' => $table_join{$table},
+ 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, },
+ 'extra_sql' => ' AND custnum = '. $cust_main->custnum,
+ });
+ push @history, @items;
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html
new file mode 100644
index 0000000..e88c02e
--- /dev/null
+++ b/httemplate/view/cust_main/contacts.html
@@ -0,0 +1,122 @@
+% my %which = (
+% '' => 'Billing',
+% 'ship_' => 'Service',
+% );
+% foreach my $which ( '', 'ship_' ) {
+% my $pre = $cust_main->get("${which}last") ? $which : '';
+
+<% $which{$which} %> 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>
+% if ( $which eq '' && $conf->exists('show_ss') ) {
+ <TD ALIGN="right">SS#</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->masked('ss') || '&nbsp' %></TD>
+% }
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Address</TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") %></TD>
+</TR>
+
+% if ( $cust_main->get("${pre}address2") ) {
+% my $address2_label =
+% ( $conf->exists('cust_main-require_address2')
+% && ! ( $pre xor $cust_main->has_ship_address )
+% )
+% ? 'Unit&nbsp;#'
+% : '&nbsp;';
+
+ <TR>
+ <TD ALIGN="right"><% $address2_label %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") %></TD>
+ </TR>
+
+% }
+
+<TR>
+ <TD ALIGN="right">City</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") %></TD>
+% if ( $cust_main->get("${pre}county") ) {
+ <TD ALIGN="right">County</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}county") %></TD>
+% }
+ <TD ALIGN="right">State</TD>
+ <TD BGCOLOR="#ffffff"><% state_label( $cust_main->get("${pre}state"), $cust_main->get("${pre}country") ) %></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"><% code2country( $cust_main->get("${pre}country") ) %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right"><% $daytime_label %></TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% include('/elements/phonenumber.html',
+ $cust_main->get("${pre}daytime"),
+ 'callable'=>1
+ )
+ %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right"><% $night_label %></TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% include('/elements/phonenumber.html',
+ $cust_main->get("${pre}night"),
+ 'callable'=>1
+ )
+ %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% $cust_main->get("${pre}fax") || '&nbsp' %>
+ </TD>
+</TR>
+% if ( $which eq '' && $conf->exists('show_stateid') ) {
+ <TR>
+ <TD ALIGN="right"><% $stateid_label %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->masked('stateid') || '&nbsp' %></TD>
+ <TD ALIGN="right"><% $stateid_state_label %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->stateid_state || '&nbsp' %></TD>
+ </TR>
+% }
+</TABLE></TD></TR></TABLE>
+% if ( $which ne 'ship_' ) {
+<BR>
+% }
+% }
+
+<%once>
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day&nbsp;Phone'
+ : FS::Msgcat::_gettext('daytime');
+my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/
+ ? 'Night&nbsp;Phone'
+ : FS::Msgcat::_gettext('night');
+my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
+ ? 'Driver&rsquo;s&nbsp;License'
+ : FS::Msgcat::_gettext('stateid');
+my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
+ ? 'Driver&rsquo;s&nbsp;License State'
+ : FS::Msgcat::_gettext('stateid_state');
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+
+</%init>
+
diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html
new file mode 100644
index 0000000..2cfe026
--- /dev/null
+++ b/httemplate/view/cust_main/misc.html
@@ -0,0 +1,126 @@
+<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Customer&nbsp;number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->display_custnum %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Status</TD>
+ <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% ucfirst($cust_main->status) %></B></FONT></TD>
+</TR>
+
+%my $agent;
+%if ( $num_agents == 1 ) {
+% my @agents = qsearchs( 'agent', {} );
+% $agent = $agents[0];
+%} else {
+% $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } );
+ <TR>
+ <TD ALIGN="right">Agent</TD>
+ <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD>
+ </TR>
+% }
+
+% if ( $cust_main->agent_custid
+% && ! $conf->exists('cust_main-default_agent_custid') ) {
+
+<TR>
+ <TD ALIGN="right">Agent customer ref#</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->agent_custid %></TD>
+</TR>
+%
+% }
+
+% #if ( $cust_main->classnum ) {
+ <TR>
+ <TD ALIGN="right">Class</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->classname || '(none)' %></TD>
+ </TR>
+% #}
+
+% unless ( FS::part_referral->num_part_referral == 1 ) {
+% my $referral = qsearchs('part_referral', {
+% 'refnum' => $cust_main->refnum
+% } );
+
+<TR>
+ <TD ALIGN="right">Advertising&nbsp;source</TD>
+ <TD BGCOLOR="#ffffff"><% $referral->refnum %>: <% $referral->referral%></TD>
+</TR>
+% }
+
+
+<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 } )
+% )
+% ) {
+%
+
+
+<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>
+% }
+
+
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Order taker</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->otaker %></TD>
+</TR>
+
+ <TR>
+ <TD ALIGN="right">Signup Date</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->signupdate ? time2str($date_format, $cust_main->signupdate) : '' %></TD>
+ </TR>
+
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+% my $dt = $cust_main->birthdate ne ''
+% ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate,
+% 'time_zone' =>'floating',
+% )
+% : '';
+
+ <TR>
+ <TD ALIGN="right">Date of Birth</TD>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+ </TR>
+
+% }
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ <TR>
+ <TD ALIGN="right">Census tract</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->censustract %></TD>
+ </TR>
+
+% }
+
+</TABLE></TD></TR></TABLE>
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+my $date_format = ($conf->config('date_format') || "%m/%d/%Y");
+
+my $sth = dbh->prepare('SELECT COUNT(*) FROM agent') or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $num_agents = $sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html
new file mode 100755
index 0000000..1283b19
--- /dev/null
+++ b/httemplate/view/cust_main/notes.html
@@ -0,0 +1,96 @@
+% if ( scalar(@notes) ) {
+
+ <% include('/elements/init_overlib.html') %>
+
+ <% include("/elements/table-grid.html") %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+% if ( $conf->exists('cust_main_note-display_times') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Note</TH>
+% if ($curuser->access_right('Edit customer note') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">&nbsp;</TH>
+% }
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+%
+% foreach my $note (@notes) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $pop = popurl(3);
+% my $notenum = $note->notenum;
+% my $onclick = include( '/elements/popup_link_onclick.html',
+% 'action' => popurl(2).
+% 'edit/cust_main_note.cgi'.
+% "?custnum=$custnum".
+% ";notenum=$notenum",
+% 'actionlabel' => 'Edit customer note',
+% 'width' => 616,
+% 'height' => 538, #575
+% 'frame' => 'top',
+% );
+% my $clickjs = qq!onclick="$onclick"!;
+%
+% my $edit = '';
+% if ($curuser->access_right('Edit customer note') ) {
+% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(edit)</A>!;
+% }
+
+ <TR>
+ <% note_datestr($note,$conf,$bgcolor) %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $note->usernum ? $note->access_user->name : $note->otaker %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $note->comments | defang %>
+ </TD>
+% if($edit) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $edit %></TD>
+% }
+ </TR>
+
+% } #end display notes
+
+</TABLE>
+
+% }
+<%init>
+
+use HTML::Defang;
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my(%opt) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+die "Custimer not found!" unless $cust_main;
+
+my (@notes) = $cust_main->notes();
+
+#subroutines
+
+sub note_datestr {
+ my($note, $conf, $bgcolor) = @_ or return '';
+ my $td = qq{<TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">};
+ my $format = "$td%b&nbsp;%o,&nbsp;%Y</TD>";
+ $format .= "$td%l:%M%P</TD>"
+ if $conf->exists('cust_main_note-display_times');
+ ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/one_time_charge_link.html b/httemplate/view/cust_main/one_time_charge_link.html
new file mode 100644
index 0000000..b3defa2
--- /dev/null
+++ b/httemplate/view/cust_main/one_time_charge_link.html
@@ -0,0 +1,91 @@
+<SCRIPT TYPE="text/javascript">
+
+function taxproductmagic(which) {
+
+ var str = '';
+ var elements = which.form.elements;
+ for (var i = 0; i<elements.length; i++) {
+
+ if (elements[i].name == 'taxproductnum'){
+ document.getElementById('taxproductnum').value = elements[i].value;
+ continue;
+ }
+ if (elements[i].name == 'taxproductnum_description'){
+ continue;
+ }
+
+ if (str.length){str += ';';}
+
+ var value = '';
+ if ( elements[i].type == 'checkbox' || elements[i].type == 'radio' ) {
+ if ( elements[i].checked == true ) {
+ value = elements[i].value;
+ //} else {
+ // value = '';
+ }
+ } else {
+ value = elements[i].value;
+ }
+ str += elements[i].name + '=' + escape(value);
+
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=taxproductnum&onclick=taxproductquickchargemagic&taxproductnum='+document.getElementById('taxproductnum').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxproductquickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxproductnum;taxproductnum=';
+ str += escape(document.getElementById('taxproductnum').value);
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+function taxoverridemagic(which) {
+ var str = '';
+ var elements = which.ownerDocument.QuickChargeForm.elements;
+ for (var i = 0; i<elements.length; i++) {
+ if (elements[i].name == 'tax_override'){
+ document.getElementById('tax_override').value = elements[i].value;
+ continue;
+ }
+ if (str.length){str += ';';}
+ str += elements[i].name + '=' + escape(elements[i].value);
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/part_pkg_taxoverride.html?element_name=tax_override;onclick=taxoverridequickchargemagic;selected='+document.getElementById('tax_override').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxoverridequickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxoverride;tax_override=';
+ str += document.getElementById('tax_override').value;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+</SCRIPT>
+
+<FORM NAME='quickcharge' STYLE="margin:0; padding:0; display:inline"><INPUT NAME="taxproductnum" ID="taxproductnum" TYPE="hidden"><INPUT NAME="tax_override" ID="tax_override" TYPE="hidden"><INPUT NAME="charge_storage" ID="charge_storage" TYPE="hidden"><INPUT NAME="taxproductnum_description" ID="taxproductnum_description" TYPE="hidden"></FORM>
+
+<% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/quick-charge.html?custnum='. $cust_main->custnum,
+ 'label' => 'One-time charge',
+ 'actionlabel' => 'One-time charge',
+ 'color' => '#333399',
+ 'width' => 763,
+ 'height' => 460, #more for more room for lines of add'l description?
+ })
+%>
+
+<%init>
+
+my($cust_main) = @_;
+
+</%init>
diff --git a/httemplate/view/cust_main/order_pkg_link.html b/httemplate/view/cust_main/order_pkg_link.html
new file mode 100644
index 0000000..30c86a7
--- /dev/null
+++ b/httemplate/view/cust_main/order_pkg_link.html
@@ -0,0 +1,16 @@
+<% include( '/elements/popup_link-cust_main.html',
+ 'action' => $p. 'misc/order_pkg.html',
+ 'label' => 'Order&nbsp;new&nbsp;package',
+ 'actionlabel' => 'Order new package',
+ 'color' => '#333399',
+ 'cust_main' => $cust_main,
+ 'closetext' => 'Close',
+ 'width' => 763,
+ 'height' => 538,
+ )
+%>
+<%init>
+
+my($cust_main) = @_;
+
+</%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
new file mode 100755
index 0000000..3ac4861
--- /dev/null
+++ b/httemplate/view/cust_main/packages.html
@@ -0,0 +1,228 @@
+% my $s = 0;
+% if ( $curuser->access_right('Order customer package') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('order_pkg_link.html', $cust_main) %>
+% }
+
+% if ( $curuser->access_right('One-time charge')
+% && $conf->config('payby-default') ne 'HIDE'
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('one_time_charge_link.html', $cust_main) %>
+% }
+
+% if ( $curuser->access_right('Bulk change customer packages') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pkg.cgi?<% $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services)
+% }
+
+<BR><BR>
+
+<TABLE>
+ <TR>
+ <TD ALIGN="left">
+
+% if ( @$packages ) {
+
+Current packages
+% }
+% if ( $cust_main->num_cancelled_pkgs ) {
+% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+% || ( $conf->exists('hidecancelledpackages')
+% && ! $cgi->param('showcancelledpackages')
+% )
+% )
+% {
+% my $prev = $cgi->param('showcancelledpackages');
+% $cgi->param('showcancelledpackages', 1);
+ ( <a href="<% $cgi->self_url %>">show
+% $cgi->param('showcancelledpackages', $prev);
+% } else {
+% $cgi->param('showcancelledpackages', 0);
+ ( <a href="<% $cgi->self_url %>">hide
+% $cgi->param('showcancelledpackages', 1);
+% }
+
+ cancelled packages</a> )
+% }
+% if ( $num_old_packages ) {
+% $cgi->param('showoldpackages', 1);
+ ( <a href="<% $cgi->self_url %>">show old packages</a> )
+% } elsif ( $cgi->param('showoldpackages') ) {
+% $cgi->param('showoldpackages', 0);
+ ( <a href="<% $cgi->self_url %>">hide old packages</a> )
+% }
+
+ </TD>
+ <TD ALIGN="right">
+ <A HREF="<%$p%>search/report_cust_pkg.html?custnum=<% $cust_main->custnum %>">Package reports</A><BR>
+ Service reports:
+ <A HREF="<%$p%>search/report_svc_acct.html?custnum=<% $cust_main->custnum %>">accounts</A>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2>
+
+% if ( @$packages ) {
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Package</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+% if ( $show_location ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Location</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH>
+</TR>
+
+% #$FS::cust_pkg::DEBUG = 2;
+% foreach my $cust_pkg (@$packages) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my %iopt = (
+% 'bgcolor' => $bgcolor,
+% 'cust_pkg' => $cust_pkg,
+% 'part_pkg' => $cust_pkg->part_pkg,
+% %conf_opt,
+% );
+%
+
+ <!--pkgnum: <% $cust_pkg->pkgnum %>-->
+ <TR>
+ <% include('packages/package.html', %iopt) %>
+ <% include('packages/status.html', %iopt) %>
+% if ( $show_location ) {
+ <% include('packages/location.html', %iopt) %>
+% }
+ <% include('packages/services.html', %iopt) %>
+ </TR>
+
+% }
+
+</TABLE>
+
+% } else {
+<BR>
+% }
+
+ </TD>
+ </TR>
+</TABLE>
+
+% if ( $cgi->param('fragment') =~ /^cust_pkg(\d+)$/ ) {
+ <SCRIPT>
+ // IE-specific hack. other browsers listen to #fragments
+ // is this even working? or is the #target redirection just working cause
+ // we set the URL params differently?
+ var el = document.getElementById( 'cust_pkg<% $1 %>' );
+ if ( el ) el.scrollIntoView(true);
+ </SCRIPT>
+% }
+
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my( $packages, $num_old_packages ) = get_packages($cust_main, $conf);
+
+my $show_location = $conf->exists('cust_pkg-always_show_location')
+ || ( grep $_->locationnum, @$packages ); # ? '1' : '0';
+
+my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
+my %conf_opt = (
+ #for services.html and status.html
+ 'cust_pkg-display_times' => $conf->exists('cust_pkg-display_times'),
+
+ #for status.html
+ 'cust_pkg-show_autosuspend' => $conf->exists('cust_pkg-show_autosuspend'),
+ #for status.html pkg-balances
+ 'pkg-balances' => $conf->exists('pkg-balances'),
+ 'money_char' => ( $conf->config('money_char') || '$' ),
+
+ #for location.html
+ 'countrydefault' => $countrydefault,
+ 'statedefault' => ( scalar($conf->config('statedefault'))
+ || ($countrydefault eq 'US' ? 'CA' : '') ),
+ #for services.html
+ 'svc_external-skip_manual' => $conf->exists('svc_external-skip_manual'),
+ 'legacy_link' => $conf->exists('legacy_link'),
+ 'svc_broadband-manage_link' => $conf->config('svc_broadband-manage_link'),
+);
+
+#subroutines
+
+sub get_packages {
+ my $cust_main = shift or return undef;
+ my $conf = shift;
+
+ my $method;
+ if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledpackages')
+ && ! $cgi->param('showcancelledpackages') )
+ )
+ {
+ $method = 'ncancelled_pkgs';
+ } else {
+ $method = 'all_pkgs';
+ }
+
+ my $cust_pkg_fields =
+ join(', ', map { "cust_pkg.$_ AS $_" } fields('cust_pkg') );
+
+ my $part_pkg_fields =
+ join(', ', map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') );
+
+ my $group_by =
+ join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
+ join(', ', map "part_pkg.$_", fields('part_pkg') );
+
+ my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
+ ' WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
+
+ my @packages = $cust_main->$method( {
+ 'select' => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+ } );
+ my $num_old_packages = scalar(@packages);
+
+ foreach my $cust_pkg ( @packages ) {
+ my %hash = $cust_pkg->hash;
+ my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
+ grep { /^part_pkg_/ } keys %hash;
+ $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
+ }
+
+ unless ( $cgi->param('showoldpackages') ) {
+ my $years = $conf->config('cust_main-packages-years') || 2;
+ my $then = time - $years * 31556926; #60*60*24*365.2422 is close enough
+
+ my %hide = ( 'cancelled' => 'cancel',
+ 'one-time charge' => 'setup',
+ );
+
+ @packages =
+ grep { !exists($hide{$_->status}) or $_->get($hide{$_->status}) > $then
+ or $_->num_svcs #don't hide packages w/services
+ }
+ @packages;
+ }
+
+ $num_old_packages -= scalar(@packages);
+
+ ( \@packages, $num_old_packages );
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html
new file mode 100644
index 0000000..41155cb
--- /dev/null
+++ b/httemplate/view/cust_main/packages/location.html
@@ -0,0 +1,53 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+
+% unless ( $cust_pkg->locationnum ) {
+ <I><FONT SIZE=-1>(default service address)</FONT><BR>
+% }
+
+ <% $loc->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ )
+ %>
+
+% unless ( $cust_pkg->locationnum ) {
+ </I>
+% }
+
+% if ( ! $cust_pkg->get('cancel')
+% && $FS::CurrentUser::CurrentUser->access_right('Change customer package')
+% )
+% {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_change_location_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+
+</TD>
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $countrydefault = $opt{'countrydefault'} || 'US';
+my $statedefault = $opt{'statedefault'}
+ || ($countrydefault eq 'US' ? 'CA' : '');
+
+my $loc = $cust_pkg->cust_location_or_main;
+
+sub pkg_change_location_link {
+ my $cust_pkg = shift;
+ my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg.cgi?locationnum=-1;pkgpart=$pkgpart;".
+ "address1=;address2=;city=;county=;state=$statedefault;".
+ "zip=;country=$countrydefault",
+ 'label' => 'Change&nbsp;location',
+ 'actionlabel' => 'Change',
+ 'cust_pkg' => $cust_pkg,
+ );
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
new file mode 100644
index 0000000..3c486dd
--- /dev/null
+++ b/httemplate/view/cust_main/packages/package.html
@@ -0,0 +1,238 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+ <TR>
+ <TD COLSPAN=2>
+ <A NAME="cust_pkg<% $cust_pkg->pkgnum %>"
+ ID ="cust_pkg<% $cust_pkg->pkgnum %>"
+ ><% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B></A>
+ -
+ <% $part_pkg->custom_comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->quantity > 1 ) {
+ <TR>
+ <TD COLSPAN=2>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity:
+ <B><% $cust_pkg->quantity %></B>
+ </TD>
+ </TR>
+% }
+
+ <TR>
+ <TD COLSPAN=2>
+ <FONT SIZE=-1>
+
+% unless ( $cust_pkg->get('cancel') ) {
+%
+% my $br = 0;
+% if ( $curuser->access_right('Change customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Edit customer package dates') ) {
+% $br=1;
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Discount customer package')
+% && $part_pkg->can_discount
+% && ! scalar($cust_pkg->cust_pkg_discount_active)
+% )
+% {
+% $br=1;
+ (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Customize customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
+% }
+%
+ <% $br ? '<BR>' : '' %>
+% }
+
+% if ( $cust_pkg->num_cust_event
+% && ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% )
+% ) {
+ (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
+% }
+
+ </FONT>
+ </TD>
+ </TR>
+
+% my $editi = $curuser->access_right('Edit customer package invoice details');
+% my $editc = $curuser->access_right('Edit customer package comments');
+% my @cust_pkg_detail = $cust_pkg->cust_pkg_detail;
+% my @invoice_detail = grep { $_->detailtype eq 'I' } @cust_pkg_detail;
+% my @comments = grep { $_->detailtype eq 'C' } @cust_pkg_detail;
+%
+% if ( scalar(@invoice_detail) || scalar(@comments) || $editi || $editc ) {
+%
+% my $editlink = $p. 'edit/cust_pkg_detail?pkgnum='. $cust_pkg->pkgnum.
+% ';detailtype=';
+
+ <TR>
+
+% if ( @invoice_detail ) {
+ <TD VALIGN="top">
+ <% include('/elements/table-grid.html') %>
+ <TR>
+ <TH BGCOLOR="#dddddd" STYLE="border-bottom: dashed 1px black; padding-bottom: 1px">
+ <FONT SIZE="-1">
+ Invoice details
+% if ( $editi && ! $cust_pkg->get('cancel') ) {
+ (<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'I',
+ 'label' => 'edit',
+ 'actionlabel' => 'Edit invoice details',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>)
+% }
+ </FONT>
+ </TH>
+ </TR>
+% foreach my $cust_pkg_detail ( @invoice_detail ) {
+ <TR>
+ <TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
+ </TR>
+% }
+ </TABLE>
+ </TD>
+% } else {
+ <TD>
+% if ( $editi && ! $cust_pkg->get('cancel') ) {
+ <FONT SIZE="-1">
+ (&nbsp;<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'I',
+ 'label' => 'Add&nbsp;invoice&nbsp;details',
+ 'actionlabel' => 'Add invoice details',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>&nbsp;)
+ </FONT>
+% }
+ </TD>
+% }
+
+% if ( @comments ) {
+ <TD VALIGN="top">
+ <% include('/elements/table-grid.html') %>
+ <TR>
+ <TH BGCOLOR="#dddddd" STYLE="border-bottom: dashed 1px black; padding-bottom: 1px">
+ <FONT SIZE="-1">
+ Comments
+% if ( $editc ) {
+ (<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'C',
+ 'label' => 'edit',
+ 'actionlabel' => 'Edit comments',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>)
+% }
+ </FONT>
+ </TH>
+ </TR>
+% foreach my $cust_pkg_detail ( @comments ) {
+ <TR>
+ <TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
+ </TR>
+% }
+ </TABLE>
+ </TD>
+% } else {
+ <TD>
+% if ( $editc ) {
+ <FONT SIZE="-1">
+ (&nbsp;<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'C',
+ 'label' => 'Add&nbsp;comments',
+ 'actionlabel' => 'Add comments',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>&nbsp;)
+ </FONT>
+% }
+ </TD>
+% }
+
+ </TR>
+% }
+
+ </TABLE>
+
+</TD>
+
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#subroutines
+
+#false laziness w/status.html
+sub pkg_link {
+ my($action, $label, $cust_pkg) = @_;
+ return '' unless $cust_pkg;
+ qq!<a href="$p$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!;
+}
+
+sub pkg_change_link {
+ my $cust_pkg = shift;
+ my $locationnum = $cust_pkg->locationnum;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg.cgi?locationnum=$locationnum",
+ 'label' => 'Change&nbsp;package',
+ 'actionlabel' => 'Change',
+ 'cust_pkg' => $cust_pkg,
+ );
+}
+
+sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', 'Edit&nbsp;dates', @_ ); }
+
+sub pkg_discount_link {
+ my $cust_pkg = shift or return '';
+ #my $part_pkg = shift;
+ #my $custnum = $cust_pkg->custnum;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p.'edit/cust_pkg_discount.html',
+ 'label' => 'Discount',
+ 'actionlabel' => 'Discount',
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ );
+}
+
+sub pkg_customize_link {
+ my $cust_pkg = shift or return '';
+ my $part_pkg = shift;
+ my $custnum = $cust_pkg->custnum;
+ qq!<A HREF="${p}edit/part_pkg.cgi?!.
+ "clone=". $part_pkg->pkgpart. ';'.
+ "pkgnum=". $cust_pkg->pkgnum.
+ qq!">Customize</A>!;
+}
+
+sub pkg_event_link {
+ my($cust_pkg) = @_;
+ qq!<a href="${p}search/cust_event.html?pkgnum=!. $cust_pkg->pkgnum. qq!">!.
+ 'View package events'.
+ '</a>';
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
new file mode 100644
index 0000000..0fe7931
--- /dev/null
+++ b/httemplate/view/cust_main/packages/services.html
@@ -0,0 +1,135 @@
+% ###
+% # Services
+% ###
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+
+% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
+% foreach my $part_svc ( $cust_pkg->part_svc ) {
+
+% #foreach my $service (@{$svcpart->{services}}) {
+% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD>
+ <TD STYLE="padding-bottom:0px"><B><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD>
+ <TD><% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" COLSPAN="3" VALIGN="top" STYLE="padding-bottom:1px;padding-top:0px"><FONT SIZE="-2" COLOR="#FFD000">
+
+ <% $cust_svc->overlimit ? "Overlimit: ". time2str('%b %o %Y' . ($opt{'cust_pkg-display_times'} ? ' %l:%M %P' : ''), $cust_svc->overlimit) : '' %>
+ </FONT></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
+
+% if ( $curuser->access_right('Recharge customer service')
+% && $part_svc->svcdb eq 'svc_acct'
+% && ( $cust_svc->svc_x->seconds ne ''
+% || $cust_svc->svc_x->upbytes ne ''
+% || $cust_svc->svc_x->downbytes ne ''
+% || $cust_svc->svc_x->totalbytes ne ''
+% )
+% ) {
+ (&nbsp;<%svc_recharge_link($cust_svc)%>&nbsp;)
+% }
+ </FONT></TD>
+
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px">
+
+% my $ip_addr = $cust_svc->svc_x->ip_addr;
+
+% if ( $part_svc->svcdb eq 'svc_broadband' ) {
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<% include('/elements/popup_link-ping.html', 'ip'=> $ip_addr ) %>&nbsp;)</FONT>
+
+% }
+
+% my $manage_link = $opt{'svc_broadband-manage_link'};
+% if ( $manage_link && $part_svc->svcdb eq 'svc_broadband' ) {
+% my $svc_manage_link = eval(qq("$manage_link"));
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<A HREF="<% $svc_manage_link %>">Manage Device</A>&nbsp;)</FONT>
+
+% }
+
+% if ( $curuser->access_right('Unprovision customer service') ) {
+ <FONT SIZE="-2">(&nbsp;<%svc_unprovision_link($cust_svc)%>&nbsp;)</FONT>
+% }
+ </TD>
+ </TR>
+% }
+
+% if ( ! $cust_pkg->get('cancel')
+% && $curuser->access_right('Provision customer service')
+% && $part_svc->num_avail
+% ) {
+
+ <TR>
+ <TD COLSPAN=3 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px">
+ <B><% svc_provision_link($cust_pkg, $part_svc, \%opt, $curuser) %></B>
+ </TD>
+ </TR>
+
+% }
+
+% }
+
+ </TABLE>
+ </TD>
+
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $conf = new FS::Conf;
+
+sub svc_provision_link {
+ my ($cust_pkg, $part_svc, $opt, $curuser) = @_;
+ ( my $svc_nbsp = $part_svc->svc ) =~ s/\s+/&nbsp;/g;
+ my $num_avail = $part_svc->num_avail;
+ my $pkgnum_svcpart = "pkgnum=". $cust_pkg->pkgnum. ';'.
+ "svcpart=". $part_svc->svcpart;
+ my $url;
+ if ( $part_svc->svcdb eq 'svc_external' #could be generalized
+ && $opt->{'svc_external-skip_manual'}
+ ) {
+ $url = "${p}edit/process/". $part_svc->svcdb. ".cgi?$pkgnum_svcpart";
+ } else {
+ $url = svc_url(
+ 'm' => $m,
+ 'action' => 'edit',
+ 'part_svc' => $part_svc,
+ 'query' => $pkgnum_svcpart,
+ );
+ #$url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
+ }
+
+ my $link = qq!<A CLASS="provision" HREF="$url">!.
+ "Provision&nbsp;$svc_nbsp&nbsp;($num_avail)</A>";
+ if ( $opt->{'legacy_link'}
+ && $curuser->access_right('View/link unlinked services')
+ )
+ {
+ $link .= '<BR>'.
+ qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
+ qq!$pkgnum_svcpart">!.
+ "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_avail)</A>";
+ }
+ $link;
+}
+
+sub svc_unprovision_link {
+ my $cust_svc = shift or return '';
+ qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?!. $cust_svc->svcnum.
+ qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
new file mode 100644
index 0000000..a686843
--- /dev/null
+++ b/httemplate/view/cust_main/packages/status.html
@@ -0,0 +1,484 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+
+%#this should use cust_pkg->status and cust_pkg->statuscolor eventually
+
+% if ( $cust_pkg->get('cancel') ) { #status: cancelled
+% my $cpr = $cust_pkg->last_cust_pkg_reason('cancel');
+
+ <% pkg_status_row($cust_pkg, 'Cancelled', 'cancel', 'color'=>'FF0000', %opt ) %>
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
+ 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+
+% unless ( $cust_pkg->get('setup') ) {
+
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt, ) %>
+
+% } else {
+
+ <% pkg_status_row( $cust_pkg, 'Setup', 'setup', %opt ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, 'Suspended', 'susp', %opt, curuser=>$curuser ) %>
+
+% }
+%
+% } else {
+%
+% if ( $cust_pkg->get('susp') ) { #status: suspended
+% my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+
+ <% pkg_status_row( $cust_pkg, 'Suspended', 'susp', 'color'=>'FF9900', %opt ) %>
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
+ 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt,
+ )
+ %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+% unless ( $cust_pkg->get('setup') ) {
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt ) %>
+% } else {
+ <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt ) %>
+% }
+
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+% # pkg_status_row($cust_pkg, 'Next bill', 'bill', %opt)
+ <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire', %opt, curuser=>$curuser ) %>
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Unsuspend customer package') ) {
+ (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+
+% } else { #status: active
+%
+% unless ( $cust_pkg->get('setup') ) { #not setup
+%
+% unless ( $part_pkg->freq ) {
+
+ <% pkg_status_row_colspan( $cust_pkg, 'Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_if(
+ $cust_pkg,
+ ( $part_pkg->freq ? 'Start billing' : 'Bill on' ),
+ 'start_date',
+ %opt
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+
+% } else {
+
+ <% pkg_status_row_colspan($cust_pkg, "Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %>
+
+% }
+%
+% } else { #setup
+%
+% unless ( $part_pkg->freq ) {
+
+ <% pkg_status_row_colspan($cust_pkg, 'One-time&nbsp;charge', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+% } else {
+%
+% if (scalar($cust_pkg->overlimit)) {
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ 'Overlimit',
+ $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+ 'color'=>'FFD000', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+
+% } else {
+ <% pkg_status_row_colspan( $cust_pkg,
+ 'Active',
+ $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+ 'color'=>'00CC00', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+% }
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt) %>
+
+% }
+%
+% }
+%
+% if ( $opt{'cust_pkg-show_autosuspend'} ) {
+% my $autosuspend = pkg_autosuspend_time( $cust_pkg );
+% $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend;
+% }
+
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if($cust_pkg, 'Will automatically suspend by', 'autosuspend', %opt) %>
+ <% pkg_status_row_if( $cust_pkg, 'Will suspend on', 'adjourn', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire', %opt, curuser=>$curuser ) %>
+
+% if ( $part_pkg->freq ) {
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Suspend customer package') ) {
+ (&nbsp;<% pkg_suspend_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Suspend customer package later') ) {
+ (&nbsp;<% pkg_adjourn_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Delay suspension events') ) {
+ (&nbsp;<% pkg_delay_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package later') ) {
+ (&nbsp;<% pkg_expire_link($cust_pkg) %>&nbsp;)
+% }
+
+ <FONT>
+ </TD>
+ </TR>
+% }
+%
+% }
+% }
+
+ </TABLE>
+</TD>
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $colspan = $opt{'cust_pkg-display_times'} ? 8 : 4;
+my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%';
+
+#false laziness w/edit/REAL_cust_pkg.cgi
+my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until );
+unless ( $part_pkg->is_prepaid ) {
+ $billed_or_prepaid = 'billed';
+ $last_bill_or_renewed = 'Last&nbsp;bill';
+ $next_bill_or_prepaid_until = 'Next&nbsp;bill';
+} else {
+ $billed_or_prepaid = 'prepaid';
+ $last_bill_or_renewed = 'Renewed';
+ $next_bill_or_prepaid_until = 'Prepaid&nbsp;until';
+}
+
+#subroutines
+
+sub myfreq {
+ my $part_pkg = shift;
+ my $freq = $part_pkg->freq_pretty;
+ $freq =~ s/ /&nbsp;/g;
+ $freq;
+}
+
+#false laziness w/package.html
+sub pkg_link {
+ my($action, $label, $cust_pkg) = @_;
+ return '' unless $cust_pkg;
+ qq!<a href="$p$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!;
+}
+
+sub pkg_status_row {
+ my( $cust_pkg, $title, $field, %opt ) = @_;
+
+ my $color = $opt{'color'};
+
+ my $html = qq(<TR><TD WIDTH="<%$width%>" ALIGN="right">);
+ $html .= qq(<FONT COLOR="#$color"><B>) if length($color);
+ $html .= qq($title&nbsp;);
+ $html .= qq(</B></FONT>) if length($color);
+
+ if ( $opt{'pkg_balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
+ $html .= qq(</TD>);
+ $html .= pkg_datestr($cust_pkg, $field, %opt). '</TR>';
+
+ $html;
+}
+
+sub pkg_status_row_if {
+ my( $cust_pkg, $title, $field, %opt ) = @_;
+
+ $title = '<FONT SIZE=-1>(&nbsp;'. pkg_unadjourn_link($cust_pkg). '&nbsp;)&nbsp;</FONT>'. $title
+ if ( $field eq 'adjourn' &&
+ $opt{curuser}->access_right('Suspend customer package later')
+ );
+
+ $title = '<FONT SIZE=-1>(&nbsp;'. pkg_unexpire_link($cust_pkg). '&nbsp;)&nbsp;</FONT>'. $title
+ if ( $field eq 'expire' &&
+ $opt{curuser}->access_right('Cancel customer package later')
+ );
+
+ $cust_pkg->get($field) ? pkg_status_row($cust_pkg, $title, $field, %opt) : '';
+}
+
+sub pkg_status_row_changed {
+ my( $cust_pkg, %opt ) = @_;
+
+ return '' unless $cust_pkg->change_date;
+
+ my $html =
+ pkg_status_row( $cust_pkg, 'Package&nbsp;changed', 'change_date', %opt );
+
+ my $old = $cust_pkg->old_cust_pkg;
+ if ( $old ) {
+ my $part_pkg = $old->part_pkg;
+ my $label = 'Changed from '. $cust_pkg->change_pkgnum. ': '.
+ $part_pkg->pkg_comment(nopartpkg => 1);
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
+ 'size' => '-1',
+ 'align' => 'right',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+ }
+
+ $html;
+}
+
+sub pkg_status_row_noauto {
+ my( $cust_pkg, %opt ) = @_;
+ my $part_pkg = $opt{'part_pkg'};
+ return '' unless $cust_pkg->no_auto || $part_pkg->no_auto;
+
+ #inefficient, should be passed in
+ my $cust_main = $cust_pkg->cust_main;
+
+ return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/;
+ my $what = lc(FS::payby->shortname($cust_main->payby));
+
+ pkg_status_row_colspan( $cust_pkg, "No automatic $what charge", '',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+}
+
+sub pkg_status_row_discount {
+ my( $cust_pkg, %opt ) = @_;
+
+ my $html;
+
+ foreach my $cust_pkg_discount ( $cust_pkg->cust_pkg_discount_active ) {
+
+ my $discount = $cust_pkg_discount->discount;
+
+ my $label = '<B>Discount</B>: '. $discount->description;
+ if ( $discount->months ) {
+ my $remaining = $discount->months - $cust_pkg_discount->months_used;
+ $remaining = sprintf('%.2f', $remaining) if $remaining =~ /\./;
+ $label .= " ($remaining months remaining)"
+ }
+
+ $label .= ' <FONT SIZE="-1">('.
+ '<A HREF="../misc/delete-cust_pkg_discount.html?'.
+ $cust_pkg_discount->pkgdiscountnum.
+ '">remove&nbsp;discount</A>)</FONT>';
+
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+
+ }
+
+ $html;
+}
+
+sub pkg_status_row_colspan {
+ my($cust_pkg, $title, $addl, %opt) = @_;
+
+ my $colspan = $opt{'colspan'};
+
+ my $align = $opt{'align'} ? 'ALIGN="'. $opt{'align'}.'"' : '';
+ my $color = $opt{'color'} ? 'COLOR="#'.$opt{'color'}.'"' : '';
+ my $size = $opt{'size'} ? 'SIZE="'. $opt{'size'}. '"' : '';
+
+ my $html = qq(<TR><TD COLSPAN=$colspan $align>);
+ $html .= qq(<FONT $color $size>) if length($color) || $size;
+ $html .= qq(<B>) if $color && !$size;
+ $html .= $title;
+ $html .= qq(</B>) if $color && !$size;
+ $html .= qq(</FONT>) if length($color) || $size;
+ $html .= ",&nbsp;$addl" if length($addl);
+
+ if ( $opt{'pkg-balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
+ $html .= qq(</TD></TR>);
+
+ $html;
+
+}
+
+sub pkg_datestr {
+ my($cust_pkg, $field, %opt) = @_ or return '';
+ return '&nbsp;' unless $cust_pkg->get($field);
+ my $format = '<TD align="left"><B>%b</B></TD>'.
+ '<TD align="right"><B>&nbsp;%o,</B></TD>'.
+ '<TD align="right"><B>&nbsp;%Y</B></TD>';
+ #$format .= '&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
+ $format .= '<TD ALIGN="right"><B>&nbsp;%l</TD>'.
+ '<TD ALIGN="center"><B>:</B></TD>'.
+ '<TD ALIGN="left"><B>%M</B></TD>'.
+ '<TD ALIGN="left"><B>&nbsp;%P</B></TD>'
+ if $opt{'cust_pkg-display_times'};
+ my $strip = time2str($format, $cust_pkg->get($field) );
+ $strip =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub pkg_suspend_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=suspend',
+ 'label' => 'Suspend&nbsp;now',
+ 'actionlabel' => 'Suspend',
+ 'color' => '#FF9900',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_adjourn_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=adjourn',
+ 'label' => 'Suspend&nbsp;later',
+ 'actionlabel' => 'Adjourn',
+ 'color' => '#CC6600',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_delay_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/delay_susp_pkg.html',
+ 'label' => 'Delay&nbsp;suspend',
+ 'actionlabel' => 'Delay suspend for',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', 'Unsuspend', @_ ); }
+sub pkg_unadjourn_link { pkg_link('misc/unadjourn_pkg', 'Abort', @_ ); }
+sub pkg_unexpire_link { pkg_link('misc/unexpire_pkg', 'Abort', @_ ); }
+
+sub pkg_cancel_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=cancel',
+ 'label' => 'Cancel&nbsp;now',
+ 'actionlabel' => 'Cancel',
+ 'color' => '#ff0000',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_expire_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=expire',
+ 'label' => 'Cancel&nbsp;later',
+ 'actionlabel' => 'Expire', #"Cancel package $num later"
+ 'color' => '#CC0000',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub svc_recharge_link {
+ include( '/elements/popup_link-cust_svc.html',
+ 'action' => $p. 'misc/recharge_svc.html',
+ 'label' => 'Recharge',
+ 'actionlabel' => 'Recharge',
+ 'color' => '#333399',
+ 'cust_svc' => shift,
+ )
+}
+
+sub pkg_autosuspend_time {
+ my $cust_pkg = shift or return '';
+ my $days = 7;
+ my $time = time;
+ my $pending_suspend = 0;
+ #this seems to be extremely inefficient... and is slowing down all customer
+ #views
+ while ( $days > 0 &&
+ scalar(
+ grep { $_->part_event->action eq 'suspend' }
+ @{$cust_pkg->cust_main->due_cust_event( time => $time + 86400*$days,
+ testonly => 1,
+ ) }
+ )
+ )
+ {
+ $pending_suspend = 1;
+ $days--;
+ }
+
+ $pending_suspend ? time + ($days + 1) * 86400 : '';
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
new file mode 100644
index 0000000..0dc4c41
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history.html
@@ -0,0 +1,445 @@
+%# payment links
+
+% my $s = 0;
+% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter check payment',
+ 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=BILL",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter check payment',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter cash payment',
+ 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=CASH",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter cash payment',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>">Enter Western Union payment</A>
+% }
+
+% if ( ( $payby{'CARD'} || $payby{'DCRD'} )
+% && $curuser->access_right('Process payment')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card payment</A>
+% }
+
+% if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
+% && $curuser->access_right('Process payment')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) payment</A>
+% }
+
+% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline/POS) credit card payment</A>
+% }
+
+<BR>
+
+%# credit link
+
+% if ( $curuser->access_right('Post credit') ) {
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter credit',
+ 'action' => "${p}edit/cust_credit.cgi",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter credit',
+ 'width' => 616, #make room for reasons #540 default
+ #default# 'height' => 336,
+ )
+ %>
+ <BR>
+% }
+
+%# refund links
+
+% $s = 0;
+% if ( $payby{'BILL'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter check refund',
+ 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=BILL",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter check refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'CASH'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter cash refund',
+ 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=CASH",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter cash refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+%# someday, perhaps. very few gateways let you do unlinked refunds at all.
+%# Authorize.net makes you sign a special form
+%#
+%# % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
+%# % && $curuser->access_right('Process refund')
+%# % && ! $cust_main->is_encrypted($cust_main->payinfo)
+%# % ) {
+%# <% $s++ ? ' | ' : '' %>
+%# <A HREF="<% $p %>misc/refund.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card refund</A>
+%# % }
+%#
+%# % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
+%# % && $curuser->access_right('Process refund')
+%# % && ! $cust_main->is_encrypted($cust_main->payinfo)
+%# % ) {
+%# <% $s++ ? ' | ' : '' %>
+%# <A HREF="<% $p %>misc/refund.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) refund</A>
+%# % }
+
+% if ( $payby{'MCRD'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline/POS) credit card refund</A>
+% }
+
+<BR>
+
+%# tax exemption link
+
+% my $view_exemptions = $curuser->access_right('View customer tax exemptions');
+% my $add_adjustment = ( $conf->exists('enable_tax_adjustments')
+% && $curuser->access_right('Add customer tax adjustment')
+% );
+% if ( $view_exemptions || $add_adjustment ) {
+
+% if ( $view_exemptions ) {
+ <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A>
+ <% $add_adjustment ? '|' : '' %>
+% }
+
+% if ( $add_adjustment ) {
+ <% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum,
+ 'label' => 'Add tax adjustment',
+ 'actionlabel' => 'Add tax adjustment',
+ #'color' => '#333399',
+ #'width' => 763,
+ 'height' => 200,
+ })
+ %>
+ |
+ <A HREF="<% $p %>search/cust_tax_adjustment.html?custnum=<% $custnum %>">View tax adjustments</A>
+% }
+
+ <BR>
+% }
+
+%# batched payment links
+
+% if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
+% && $curuser->access_right('View customer batched payments')
+% )
+% {
+ View batched payments:
+% foreach my $status (qw( Queued In-transit Complete All )) {
+ <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% $status %></A>
+ <% $status ne 'All' ? '|' : '' %>
+% }
+ <BR>
+% }
+
+%# pending payment links
+
+% if ( $curuser->access_right('View customer pending payments')
+% && scalar($cust_main->cust_pay_pending)
+% )
+% {
+ <A HREF="<% $p %>search/cust_pay_pending.html?magic=_date;statusNOT=done;custnum=<% $custnum %>">View pending payments</A><BR>
+% }
+
+%# and now the table
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Refund</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Balance</FONT></TH>
+</TR>
+
+%#display payment history
+
+%my $money_char = $conf->config('money_char') || '$';
+%
+%sub balance_forward_row {
+% my( $b, $date, $money_char ) = @_;
+% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
+
+ <TR ID="balance_forward_row">
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <% time2str($date_format, $date) %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <I>Starting balance on <% time2str($date_format, $date) %></I>
+ (<A HREF="javascript:void(0);" onClick="show_history();">show prior history</A>)
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
+
+ </TR>
+%}
+%
+%my $balance = 0;
+%my %target = ();
+%
+%my $years = $conf->config('payment_history-years') || 2;
+%my $older_than = time - $years * 31556926; #60*60*24*365.2422
+%my $hidden = 0;
+%my $seen = 0;
+%my $old_history = 0;
+%my $lastdate = 0;
+%
+%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
+%
+% $lastdate = $item->{'date'};
+%
+% my $display;
+% if ( $item->{'date'} < $older_than ) {
+% $display = ' STYLE="display:none" ';
+% $hidden = 1;
+% } else {
+%
+% $display = '';
+%
+% if ( $hidden && ! $seen++ ) {
+% balance_forward_row($balance, $item->{'date'}, $money_char);
+% }
+%
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $charge = exists($item->{'charge'})
+% ? sprintf("$money_char\%.2f", $item->{'charge'})
+% : '';
+%
+% my $payment = exists($item->{'payment'})
+% ? sprintf("-&nbsp;$money_char\%.2f", $item->{'payment'})
+% : '';
+%
+% $payment ||= sprintf( "<DEL>-&nbsp;$money_char\%.2f</DEL>",
+% $item->{'void_payment'}
+% )
+% if exists($item->{'void_payment'});
+%
+% my $credit = exists($item->{'credit'})
+% ? sprintf("-&nbsp;$money_char\%.2f", $item->{'credit'})
+% : '';
+%
+% my $refund = exists($item->{'refund'})
+% ? sprintf("$money_char\%.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 = $money_char. $balance ) =~ s/^\$\-/-&nbsp;\$/;
+%
+%
+
+
+ <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% unless ( !$target || $target{$target}++ ) {
+
+ <A NAME="<% $target %>">
+% }
+
+ <% time2str($date_format, $item->{'date'}) %>
+% if ( $target && $target{$target} == 1 ) {
+
+ </A>
+% }
+
+ </FONT>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $item->{'desc'} %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $charge %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $payment %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $credit %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $refund %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $showbalance %>
+ </TD>
+ </TR>
+% }
+
+%if ( scalar(@history) && $hidden && ! $seen++ ) {
+% balance_forward_row($balance, $lastdate, $money_char);
+%}
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+
+function show_history () {
+ //alert('showing history!');
+
+ var balance_forward_row = document.getElementById('balance_forward_row');
+
+ balance_forward_row.style.display = 'none';
+ for ( var i = 0; i < <% $old_history %>; i++ ) {
+ var oldRow = document.getElementById('old_history'+i);
+ oldRow.style.display = '';
+ }
+
+}
+
+</SCRIPT>
+
+<%init>
+
+my( $cust_main ) = @_;
+my $custnum = $cust_main->custnum;
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my %status = (
+ 'Queued' => 'O', #Open
+ 'In-transit' => 'I',
+ 'Complete' => 'R', #Resolved
+ 'All' => '',
+);
+
+#get payment history
+my @history = ();
+
+my %opt = (
+ ( map { $_ => scalar($conf->config($_)) }
+ qw( card_refund-days date_format )
+ ),
+ ( map { $_ => $conf->exists($_) }
+ qw( deleteinvoices deletepayments deleterefunds pkg-balances )
+ )
+);
+
+#invoices
+foreach my $cust_bill ($cust_main->cust_bill) {
+ push @history, {
+ 'date' => $cust_bill->_date,
+ 'desc' => include('payment_history/invoice.html', $cust_bill, %opt ),
+ 'charge' => $cust_bill->charged,
+ };
+}
+
+#statements
+foreach my $cust_statement ($cust_main->cust_statement) {
+ push @history, {
+ 'date' => $cust_statement->_date,
+ 'desc' => include('payment_history/statement.html', $cust_statement, %opt ),
+ #'charge' => $cust_bill->charged,
+ };
+}
+
+#payments (some false laziness w/credits)
+foreach my $cust_pay ($cust_main->cust_pay) {
+ push @history, {
+ 'date' => $cust_pay->_date,
+ 'desc' => include('payment_history/payment.html', $cust_pay, %opt ),
+ 'payment' => $cust_pay->paid,
+ #'target' => $target, #XXX
+ };
+}
+
+#voided payments
+foreach my $cust_pay_void ($cust_main->cust_pay_void) {
+ push @history, {
+ 'date' => $cust_pay_void->_date,
+ 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
+ 'void_payment' => $cust_pay_void->paid,
+ };
+
+}
+
+#credits (some false laziness w/payments)
+foreach my $cust_credit ($cust_main->cust_credit) {
+ push @history, {
+ 'date' => $cust_credit->_date,
+ 'desc' => include('payment_history/credit.html', $cust_credit, %opt ),
+ 'credit' => $cust_credit->amount,
+ };
+
+}
+
+#refunds
+foreach my $cust_refund ($cust_main->cust_refund) {
+ push @history, {
+ 'date' => $cust_refund->_date,
+ 'desc' => include('payment_history/refund.html', $cust_refund),
+ 'refund' => $cust_refund->refund,
+ };
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/credit.html b/httemplate/view/cust_main/payment_history/credit.html
new file mode 100644
index 0000000..75038cc
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/credit.html
@@ -0,0 +1,156 @@
+<% $pre %>Credit<% $post %>
+by <% $cust_credit->otaker %><% "$reason$desc$apply$delete$unapply" %>
+<%init>
+
+my( $cust_credit, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my @cust_credit_bill = $cust_credit->cust_credit_bill;
+my @cust_credit_refund = $cust_credit->cust_credit_refund;
+
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_credit->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_credit->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my %cust_credit_bill_width = ('width' => 392);
+my %cust_credit_bill_height = ();
+if ($conf->exists('cust_credit_bill_pkg-manual')) {
+ %cust_credit_bill_width = ('width' => 592);
+ %cust_credit_bill_height = ('height' => 436);
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
+if ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ if ( $curuser->access_right('Apply credit') ) {
+ if ( $cust_credit->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_credit_bill.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit',
+ %cust_credit_bill_width,
+ %cust_credit_bill_height,
+ ).
+ ')';
+ }
+ if ( $cust_credit->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_credit_refund.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+} elsif ( scalar(@cust_credit_bill) == 1
+ && scalar(@cust_credit_refund) == 0
+ && $cust_credit->credited == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc .= ' '. $cust_credit_bill[0]->applied_to_invoice;
+} elsif ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 1
+ && $cust_credit->credited == 0 ) {
+ #applied to one refund
+ $desc .= ' refunded on '. time2str($date_format, $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.
+ ' '. $app->applied_to_invoice.
+ '<BR>';
+ #' on '. time2str($date_format, $app->_date).
+ } elsif ( $app->isa('FS::cust_credit_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on '. time2str($date_format, $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>';
+ if ( $curuser->access_right('Apply credit') ) {
+ if ( $cust_credit->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_credit_bill.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit',
+ %cust_credit_bill_width,
+ %cust_credit_bill_height,
+ ).
+ ')';
+ }
+ if ( $cust_credit->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_credit_refund.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+ $desc .= '<BR>';
+ }
+}
+#
+my $delete = '';
+if ( $cust_credit->closed !~ /^Y/i
+
+ #s'pose deleting a credit isn't bad like deleting a payment
+ # and this needs to be generally available until we have credit voiding..
+ #&& $conf->exists('deletecredits')
+
+ && $curuser->access_right('Delete credit')
+ )
+{
+ $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
+ && scalar(@cust_credit_bill)
+ && $curuser->access_right('Unapply credit')
+ )
+{
+ $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>)!;
+}
+
+my $reason = $cust_credit->reason
+ ? ' ('. $cust_credit->reason. ')'
+ : '';
+
+</%init>
+
diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html
new file mode 100644
index 0000000..c0d32df
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/invoice.html
@@ -0,0 +1,45 @@
+<% $link %><% $pre %>Invoice #<% $cust_bill->display_invnum %>
+(Balance $ <% $cust_bill->owed %>)<% $post %><% $link ? '</A>' : '' %><% $delete %><% $events %>
+<%init>
+
+my( $cust_bill, %opt ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($pre, $post) = ('', '');
+if ( $cust_bill->owed > 0 ) {
+ $pre = '<B><FONT SIZE="+1" COLOR="#FF0000">Open ';
+ $post = '</FONT></B>';
+}
+
+my $invnum = $cust_bill->invnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
+ : '';
+
+my $delete = '';
+if ( $opt{'deleteinvoices'} && $curuser->access_right('Delete invoices') ) {
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_bill.html?$invnum',!.
+ qq!'Are you sure you want to delete this invoice?')"!.
+ qq! TITLE="Delete this invoice from the database completely"!.
+ qq!>delete</A>)!;
+}
+
+my $events = '';
+#1.9
+if ( $cust_bill->num_cust_event
+ && ( $curuser->access_right('Billing event reports')
+ || $curuser->access_right('View customer billing events')
+ )
+ ) {
+ $events =
+ qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=$invnum!.
+ '">(&nbsp;View invoice events&nbsp;)</A></FONT>';
+}
+#
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
new file mode 100644
index 0000000..6ec9fdb
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -0,0 +1,225 @@
+<% $pre %>Payment<% $post %> by <% $otaker %>
+<% "$info$desc$view$apply$refund$void$delete$unapply" %>
+<%init>
+
+my( $cust_pay, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_pay->payby;
+
+my $payinfo;
+if ( $payby eq 'CARD' ) {
+ $payinfo = $cust_pay->paymask;
+} elsif ( $payby eq 'CHEK' ) {
+ my( $account, $aba ) = split('@', $cust_pay->paymask );
+ $payinfo = "ABA $aba, Acct #$account";
+} else {
+ $payinfo = $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/^PREP$/Prepaid card /;
+$payby =~ s/^CARD$/Credit card #/;
+$payby =~ s/^COMP$/Complimentary by /;
+$payby =~ s/^CASH$/Cash/;
+$payby =~ s/^WEST$/Western Union/;
+$payby =~ s/^MCRD$/Manual credit card/;
+$payby =~ s/^BILL$//;
+my $info = $payby ? "($payby$payinfo)" : '';
+
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_pay->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my %cust_bill_pay_width = ('width' => 392);
+my %cust_bill_pay_height = ();
+if ($conf->exists('cust_bill_pay_pkg-manual')) {
+ %cust_bill_pay_width = ('width' => 592);
+ %cust_bill_pay_height = ('height' => 436);
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
+if ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ if ( $curuser->access_right('Apply payment') ) {
+ if ( $cust_pay->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_bill_pay.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment',
+ %cust_bill_pay_width,
+ %cust_bill_pay_height,
+ ).
+ ')';
+ }
+ if ( $cust_pay->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_pay_refund.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+} elsif ( scalar(@cust_bill_pay) == 1
+ && scalar(@cust_pay_refund) == 0
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc .= ' '. $cust_bill_pay[0]->applied_to_invoice;
+} elsif ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 1
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one refund
+ $desc .= ' refunded on '. time2str($date_format, $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.
+ ' '. $app->applied_to_invoice.
+ '<BR>';
+ #' on '. time2str($date_format, $cust_bill_pay->_date).
+ } elsif ( $app->isa('FS::cust_pay_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on '. time2str($date_format, $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>';
+ if ( $curuser->access_right('Apply payment') ) {
+ if ( $cust_pay->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_bill_pay.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment',
+ %cust_bill_pay_width,
+ %cust_bill_pay_height,
+ ).
+ ')';
+ }
+ if ( $cust_pay->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_pay_refund.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+ $desc .= '<BR>';
+ }
+}
+
+my $view =
+ ' ('. include('/elements/popup_link.html',
+ 'label' => 'view receipt',
+ 'action' => "${p}view/cust_pay.html?link=popup;paynum=".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Payment Receipt',
+ ).
+ ')';
+
+my $refund = '';
+my $refund_days = $opt{'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
+ && $curuser->access_right('Refund payment')
+) {
+ $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
+ qq!paynum=!. $cust_pay->paynum. '"'.
+ qq! TITLE="Send a refund for this payment to the payment gateway"!.
+ qq!>refund</A>)!;
+}
+
+my $void = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && ( ( $cust_pay->payby eq 'CARD'
+ && $curuser->access_right('Credit card void')
+ )
+ || ( $cust_pay->payby eq 'CHEK'
+ && $curuser->access_right('Echeck void')
+ )
+ || ( $cust_pay->payby !~ /^(CARD|CHEK)$/
+ && $curuser->access_right('Regular void')
+ )
+ )
+ )
+{
+ $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! TITLE="Void this payment from the database!.
+ ( $cust_pay->payby =~ /^(CARD|CHEK)$/
+ ? ' (do not send anything to the payment gateway)'
+ : ''
+ ). '"'.
+ qq!>void</A>)!;
+}
+
+my $delete = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && $opt{'deletepayments'}
+ && $curuser->access_right('Delete payment')
+ )
+{
+ $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! TITLE="Delete this payment from the database completely - not recommended"!.
+ qq!>delete</A>)!;
+}
+
+my $unapply = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && scalar(@cust_bill_pay)
+ && $curuser->access_right('Unapply payment')
+ )
+{
+ $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! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!.
+ qq!>unapply</A>)!;
+}
+
+my $otaker = $cust_pay->otaker;
+$otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+$otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/refund.html b/httemplate/view/cust_main/payment_history/refund.html
new file mode 100644
index 0000000..4a48fea
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/refund.html
@@ -0,0 +1,50 @@
+<% $pre %>Refund<% $post %>
+(<% $payby. $payinfo %>)
+by <% $cust_refund->otaker %><% $view %><% $delete %>
+<%init>
+
+my( $cust_refund, %opt ) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_refund->payby;
+my $payinfo = $payby eq 'CARD'
+ ? $cust_refund->paymask
+ : $cust_refund->payinfo;
+
+$payby =~ s/^BILL$/Check #/ if $payinfo;
+$payby =~ s/^BILL$/Check/;
+$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^(CARD|COMP)$/$1 /;
+
+my($pre, $post) = ('', '');
+if ( $cust_refund->unapplied > 0 ) {
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+}
+
+my $view =
+ ' ('. include('/elements/popup_link.html',
+ 'label' => 'view receipt',
+ 'action' => "${p}view/cust_refund.html?link=popup;".
+ 'refundnum='. $cust_refund->refundnum,
+ 'actionlabel' => 'Payment Receipt',
+ ).
+ ')';
+
+
+my $delete = '';
+if ( $cust_refund->closed !~ /^Y/i
+ && $opt{'deleterefunds'}
+ && $curuser->access_right('Delete refund')
+ )
+{
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_refund.cgi?!. $cust_refund->refundnum.
+ qq!', 'Are you sure you want to delete this refund?')"!.
+ qq! TITLE="Delete this refund from the database completely - not recommended"!.
+ qq!>delete</A>)!;
+}
+
+</%init>
+
diff --git a/httemplate/view/cust_main/payment_history/statement.html b/httemplate/view/cust_main/payment_history/statement.html
new file mode 100644
index 0000000..dedec9b
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/statement.html
@@ -0,0 +1,34 @@
+<% $link %><% $pre %>Statement #<% $statementnum %>
+%# (Balance $ <% $cust_statement->owed %>)
+<% $post %><% $link ? '</A>' : '' %><% $events %>
+<%init>
+
+my( $cust_statement, %opt ) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($pre, $post) = ('', '');
+#if ( $cust_statement->owed > 0 ) {
+# $pre = '<B><FONT SIZE="+1" COLOR="#FF0000">Open ';
+# $post = '</FONT></B>';
+#}
+
+my $statementnum = $cust_statement->statementnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_statement.html?$statementnum">!
+ : '';
+
+my $events = '';
+
+#if ( $cust_statement->num_cust_event
+# && ( $curuser->access_right('Billing event reports')
+# || $curuser->access_right('View customer billing events')
+# )
+# ) {
+# $events =
+# qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?statementnum=!.
+# $cust_statement->statementnum. '">(&nbsp;View statement events&nbsp;)</A></FONT>';
+#}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html
new file mode 100644
index 0000000..be68ff0
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/voided_payment.html
@@ -0,0 +1,56 @@
+<DEL>Payment <% $info %></DEL>
+<I>voided <% time2str($date_format, $cust_pay_void->void_date) %>
+by <% $cust_pay_void->otaker %></I><% $unvoid %>
+<%init>
+
+my( $cust_pay_void, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_pay_void->payby;
+
+my $payinfo;
+if ( $payby eq 'CARD' ) {
+ $payinfo = $cust_pay_void->paymask;
+} elsif ( $payby eq 'CHEK' ) {
+ my( $account, $aba ) = split('@', $cust_pay_void->paymask );
+ $payinfo = "ABA $aba, Acct #$account";
+} else {
+ $payinfo = $cust_pay_void->payinfo;
+}
+
+$payby =~ s/^BILL$/Check #/ if $payinfo;
+$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^PREP$/Prepaid card /;
+$payby =~ s/^CARD$/Credit card #/;
+$payby =~ s/^COMP$/Complimentary by /;
+$payby =~ s/^CASH$/Cash/;
+$payby =~ s/^WEST$/Western Union/;
+$payby =~ s/^MCRD$/Manual credit card/;
+$payby =~ s/^BILL$//;
+my $info = $payby ? "($payby$payinfo)" : '';
+
+if ( $opt{'pkg-balances'} && $cust_pay_void->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay_void->pkgnum } );
+ $info .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my $unvoid = '';
+if ( $cust_pay_void->closed !~ /^Y/i
+ && $curuser->access_right('Unvoid')
+ )
+{
+ $unvoid = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum.
+ qq!', 'Are you sure you want to unvoid this payment?')"!.
+ qq! TITLE="Unvoid this payment from the database!.
+ ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/
+ ? ' (do not send anything to the payment gateway)'
+ : ''
+ ). '"'.
+ qq!>unvoid</A>)!;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
new file mode 100644
index 0000000..e1f9a13
--- /dev/null
+++ b/httemplate/view/cust_main/tickets.html
@@ -0,0 +1,102 @@
+<FORM METHOD="GET" ACTION="<% $new_base %>" NAME="CreateTicketForm">
+<INPUT TYPE="submit" VALUE="Create new ticket">
+in queue
+<SELECT NAME="Queue">
+% my %queues = FS::TicketSystem->queues();
+% foreach my $queueid ( keys %queues ) {
+% #should consider whether the user has ACL to create ticket in each queue
+ <OPTION VALUE="<% $queueid %>"
+ <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
+ ><% $queues{$queueid} |h %>
+% }
+</SELECT>
+% foreach my $param ( grep { $_ ne 'Queue' } keys %new_param ) {
+ <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $new_param{$param} |h %>">
+% }
+</FORM>
+<BR>
+
+(<A HREF="<% $open_link %>">View <% $openlabel %> tickets for this customer</A>)
+(<A HREF="<% $res_link %>">View resolved tickets for this customer</A>)
+<BR><BR>
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">#</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Subject</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Queue</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Owner</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Priority</TH>
+</TR>
+
+% foreach my $ticket ( @tickets ) {
+% my $href = FS::TicketSystem->href_ticket($ticket->{id});
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{id} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{subject} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{status} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{queue} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{owner} %>
+ </TD>
+
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{content}
+ ? $ticket->{content}.' ('.$ticket->{priority}.')'
+ : $ticket->{priority}
+ %>
+ </TD>
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<%init>
+
+my( $cust_main ) = @_;
+my( @tickets ) = $cust_main->tickets;
+
+my $open_link = FS::TicketSystem->href_customer_tickets($cust_main->custnum);
+my $openlabel = join('/', FS::TicketSystem->statuses );
+
+my $res_link = FS::TicketSystem->href_customer_tickets(
+ $cust_main->custnum,
+ { 'statuses' => [ 'resolved' ] }
+ );
+
+my( $new_base, %new_param ) = FS::TicketSystem->href_params_new_ticket(
+ $cust_main,
+ join(', ', $cust_main->invoicing_list_emailonly ) );
+
+my $new_link = FS::TicketSystem->href_new_ticket(
+ $cust_main,
+ join(', ', $cust_main->invoicing_list_emailonly )
+ );
+
+</%init>
diff --git a/httemplate/view/cust_pay.html b/httemplate/view/cust_pay.html
new file mode 100644
index 0000000..2f23d9e
--- /dev/null
+++ b/httemplate/view/cust_pay.html
@@ -0,0 +1,162 @@
+% if ( $link eq 'popup' ) {
+
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
+
+ <CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
+
+% } elsif ( $link eq 'print' ) {
+
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
+
+% #it would be nice if the menubar could be hidden for print, but better to
+% # have it available than not, otherwise the user winds up at a dead end
+ <% menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ %>
+ <BR><BR>
+
+% } else {
+
+ <% include('/elements/header.html', "$thing Receipt", menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Print receipt' => $pr_link,
+ ))
+ %>
+
+% }
+
+% unless ($link eq 'popup' ) {
+ <% include('/elements/small_custview.html',
+ $custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #no balance
+ )
+ %>
+ <BR><BR>
+% }
+
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Payment#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->paynum %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->_date %></B></TD>
+</TR>
+
+% if ( $void ) {
+
+ <TR>
+ <TD ALIGN="right">Void Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->void_date %></B></TD>
+ </TR>
+
+%# <TR>
+%# <TD ALIGN="right">Void reason</TD>
+%# <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->reason %></B></TD>
+%# </TR>
+
+% }
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_pay->paid %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Payment method</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->payby_name %> #<% $cust_pay->paymask %></B></TD>
+</TR>
+
+% if ( $cust_pay->payby =~ /^(CARD|CHEK|LECB)$/ && $cust_pay->paybatch ) {
+
+ <TR>
+ <TD ALIGN="right">Processor</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->processor %></B></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Authorization#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->authorization %></B></TD>
+ </TR>
+
+% if ( $cust_pay->order_number ) {
+ <TR>
+ <TD ALIGN="right">Order#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->order_number %></B></TD>
+ </TR>
+% }
+
+% }
+
+% if ( $conf->exists('pkg-balances') && $cust_pay->pkgnum ) {
+% my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ <TR>
+ <TD ALIGN="right">For package</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pkg->pkg_label_long %></B></TD>
+ </TR>
+
+% }
+
+</TABLE>
+
+% if ( $link eq 'print' ) {
+
+ <SCRIPT TYPE="text/javascript">
+ window.print();
+ </SCRIPT>
+
+% }
+
+% if ( $link =~ /^(popup|print)$/ ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices') #remove this in 1.9 EVENTUALLY
+ || $curuser->access_right('View customer payments');
+
+$cgi->param('paynum') =~ /^(\d+)$/ or die "no paynum";
+my $paynum = $1;
+
+my $link = '';
+if ( $cgi->param('link') =~ /^(\w+)$/ ) {
+ $link = $1;
+}
+
+my $void = $cgi->param('void') ? 1 : 0;
+my $thing = $void ? 'Voided Payment' : 'Payment';
+my $table = $void ? 'cust_pay_void' : 'cust_pay';
+
+my $cust_pay = qsearchs({
+ 'select' => "$table.*",
+ 'table' => $table,
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'paynum' => $paynum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "$thing #$paynum not found!" unless $cust_pay;
+
+my $pr_link = "${p}view/cust_pay.html?link=print;paynum=$paynum;void=$void";
+
+my $custnum = $cust_pay->custnum;
+my $display_custnum = $cust_pay->cust_main->display_custnum;
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+tie my %payby, 'Tie::IxHash', FS::payby->payby2longname;
+
+</%init>
diff --git a/httemplate/view/cust_pay_void.html b/httemplate/view/cust_pay_void.html
new file mode 100644
index 0000000..8c22170
--- /dev/null
+++ b/httemplate/view/cust_pay_void.html
@@ -0,0 +1 @@
+<% include('cust_pay.html', @_, 'void' => 1 ) %>
diff --git a/httemplate/view/cust_refund.html b/httemplate/view/cust_refund.html
new file mode 100644
index 0000000..138c878
--- /dev/null
+++ b/httemplate/view/cust_refund.html
@@ -0,0 +1,142 @@
+% if ( $link eq 'popup' ) {
+
+ <% include('/elements/header-popup.html', "Refund Receipt" ) %>
+
+ <CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
+
+% } elsif ( $link eq 'print' ) {
+
+ <% include('/elements/header-popup.html', "Refund Receipt" ) %>
+
+% #it would be nice if the menubar could be hidden for print, but better to
+% # have it available than not, otherwise the user winds up at a dead end
+ <% menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ %>
+ <BR><BR>
+
+% } else {
+
+ <% include('/elements/header.html', "Refund Receipt", menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Print receipt' => $pr_link,
+ ))
+ %>
+
+% }
+
+% unless ($link eq 'popup' ) {
+ <% include('/elements/small_custview.html',
+ $custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #no balance
+ )
+ %>
+ <BR><BR>
+% }
+
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Refund#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->refundnum %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_refund->_date %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_refund->refund %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->reason %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Refund method</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->payby_name %><% $cust_refund->paymask ? ' #'.$cust_refund->paymask : '' %></B></TD>
+</TR>
+
+% if ( $cust_refund->payby =~ /^(CARD|CHEK|LECB)$/ && $cust_refund->paybatch ) {
+
+ <TR>
+ <TD ALIGN="right">Processor</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->processor %></B></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Authorization#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->authorization %></B></TD>
+ </TR>
+
+% if ( $cust_refund->order_number ) {
+ <TR>
+ <TD ALIGN="right">Order#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->order_number %></B></TD>
+ </TR>
+% }
+
+% }
+
+</TABLE>
+
+% if ( $link eq 'print' ) {
+
+ <SCRIPT TYPE="text/javascript">
+ window.print();
+ </SCRIPT>
+
+% }
+
+% if ( $link =~ /^(popup|print)$/ ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices') #remove this in 1.9 EVENTUALLY
+ || $curuser->access_right('View customer payments');
+ #'View customer refunds' ???
+
+
+$cgi->param('refundnum') =~ /^(\d+)$/ or die "no refundnum";
+my $refundnum = $1;
+
+my $link = '';
+if ( $cgi->param('link') =~ /^(\w+)$/ ) {
+ $link = $1;
+}
+
+my $cust_refund = qsearchs({
+ 'select' => 'cust_refund.*',
+ 'table' => 'cust_refund',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'refundnum' => $refundnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Refund #$refundnum not found!" unless $cust_refund;
+
+my $pr_link = "${p}view/cust_refund.html?link=print;refundnum=$refundnum";
+
+my $custnum = $cust_refund->custnum;
+my $display_custnum = $cust_refund->cust_main->display_custnum;
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+tie my %payby, 'Tie::IxHash', FS::payby->payby2longname;
+
+</%init>
diff --git a/httemplate/view/cust_statement-pdf.cgi b/httemplate/view/cust_statement-pdf.cgi
new file mode 100755
index 0000000..a1739e0
--- /dev/null
+++ b/httemplate/view/cust_statement-pdf.cgi
@@ -0,0 +1,28 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statementnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $pdf = $cust_statement->print_pdf( '', $templatename);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/cust_statement.html b/httemplate/view/cust_statement.html
new file mode 100755
index 0000000..3e1345e
--- /dev/null
+++ b/httemplate/view/cust_statement.html
@@ -0,0 +1,79 @@
+<% include("/elements/header.html",'Statement View', menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Resend invoices') ) {
+
+%# <A HREF="<% $p %>misc/send-statement.cgi?method=print;<% $link %>">Re-print this statement</A>
+
+% if ( grep { $_ ne 'POST' } $cust_statement->cust_main->invoicing_list ) {
+%# |
+ <A HREF="<% $p %>misc/send-statement.cgi?method=email;<% $link %>">Re-email this statement</A>
+% }
+
+% if ( 0 ) {
+% #if ( $conf->exists('hylafax') && length($cust_statement->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-statement.cgi?method=fax;<% $link %>">Re-fax this statement</A>
+% }
+
+ <BR><BR>
+
+% }
+
+
+% #if ( $conf->exists('invoice_latex') ) {
+% if ( 0 ) { #broken???
+
+ <A HREF="<% $p %>view/cust_statement-pdf.cgi?<% $link %>">View typeset statement</A>
+ <BR><BR>
+% }
+
+% #if ( $cust_statement->num_cust_event ) {
+% if ( 0 ) {
+<A HREF="<%$p%>search/cust_event.html?statementnum=<% $cust_statement->statementnum %>">(&nbsp;View statement events&nbsp;)</A><BR><BR>
+% }
+
+% if ( $conf->exists('invoice_html') ) {
+
+ <% join('', $cust_statement->print_html('', $templatename) ) %>
+% } else {
+
+ <PRE><% join('', $cust_statement->print_text('', $templatename) ) %></PRE>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statement
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $conf = new FS::Conf;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $custnum = $cust_statement->custnum;
+my $display_custnum = $cust_statement->cust_main->display_custnum;
+
+my $link = "statementnum=$statementnum";
+$link .= ';template='. uri_escape($templatename) if $templatename;
+
+</%init>
diff --git a/httemplate/view/cust_svc.cgi b/httemplate/view/cust_svc.cgi
new file mode 100644
index 0000000..8ccfce3
--- /dev/null
+++ b/httemplate/view/cust_svc.cgi
@@ -0,0 +1,23 @@
+<% $cgi->redirect(popurl(1)."$svcdb.cgi?". $svcnum ) %>
+<%init>
+
+#needed here? we're just redirecting. i guess it could reveal the svcdb of a
+#svcnum... oooooo scary. not.
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+#some false laziness w/svc_*.cgi
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+die "Unknown svcnum" unless $cust_svc;
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $svcdb = $part_svc->svcdb;
+
+</%init>
+
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
new file mode 100644
index 0000000..852640e
--- /dev/null
+++ b/httemplate/view/elements/svc_Common.html
@@ -0,0 +1,167 @@
+<%doc>
+
+#Example:
+
+ include( 'elements/svc_Common.html,
+
+ 'table' => 'svc_something'
+
+ 'labels' => {
+ 'column' => 'Label',
+ },
+
+ #listref - each item is a literal column name (or method) or
+ # (notyet) coderef. if not specified all columns (except for the
+ #primary key) will be viewable
+ 'fields' => [
+ ]
+
+ # defaults to "edit/$table.cgi?", will have svcnum appended
+ 'edit_url' =>
+ )
+
+</%doc>
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View $label: $value") %>
+
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html","View $label: $value", menubar(
+ "Cancel this (unaudited) $label" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
+ )) %>
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this <% $label %>?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+% }
+
+Service #<B><% $svcnum %></B>
+% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
+| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
+<BR>
+
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+
+% foreach my $f ( @$fields ) {
+%
+% my($field, $type);
+% if ( ref($f) ) {
+% $field = $f->{'field'},
+% $type = $f->{'type'} || 'text',
+% } else {
+% $field = $f;
+% $type = 'text';
+% }
+%
+% my $columndef = $part_svc->part_svc_column($field);
+% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
+
+ <TR>
+ <TD ALIGN="right">
+ <% ( $opt{labels} && exists $opt{labels}->{$field} )
+ ? $opt{labels}->{$field}
+ : $field
+ %>
+ </TD>
+
+% #eventually more options for <SELECT>, etc. fields
+
+ <TD BGCOLOR="#ffffff"><% $svc_x->$field %><TD>
+
+ </TR>
+
+% }
+%
+% }
+
+% foreach (sort { $a cmp $b } $svc_x->virtual_fields) {
+ <% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %>
+% }
+
+
+</TABLE></TD></TR></TABLE>
+
+<BR>
+
+% if ( defined($opt{'html_foot'}) ) {
+
+ <% ref($opt{'html_foot'})
+ ? &{ $opt{'html_foot'} }($svc_x)
+ : $opt{'html_foot'}
+ %>
+ <BR>
+
+% }
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my(%opt) = @_;
+
+my $table = $opt{'table'};
+
+my $fields = $opt{'fields'}
+ #|| [ grep { $_ ne 'svcnum' } dbdef->table($table)->columns ];
+ || [ grep { $_ ne 'svcnum' } fields($table) ];
+
+my $svcnum;
+if ( $cgi->param('svcnum') ) {
+ $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum = $1;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "no svcnum";
+ $svcnum = $1;
+}
+my $svc_x = qsearchs({
+ 'select' => $opt{'table'}.'.*',
+ 'table' => $opt{'table'},
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n";
+
+my $cust_svc = $svc_x->cust_svc;
+my($label, $value, $svcdb) = $cust_svc->label;
+
+my $part_svc = $cust_svc->part_svc;
+
+ #false laziness w/edit/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ my $labels = $opt{labels}; #not -> here
+ foreach my $field ( keys %$labels ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\S*$/;
+ }
+
+my $pkgnum = $cust_svc->pkgnum;
+
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = $cust_svc->cust_pkg;
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+</%init>
diff --git a/httemplate/view/elements/svc_export_settings.html b/httemplate/view/elements/svc_export_settings.html
new file mode 100644
index 0000000..c5f2555
--- /dev/null
+++ b/httemplate/view/elements/svc_export_settings.html
@@ -0,0 +1,34 @@
+% if ( $FS::CurrentUser::CurrentUser->option('export_getsettings') ) {
+
+% my ( $settings, $defaults ) = $svc_x->export_getsettings;
+% if ( keys %$settings ) {
+
+%# a way to label this "Communigate pro settings".. just a config maybe... eh,
+%# its just for devel
+ External settings
+ <% ntable('#cccccc',2) %>
+
+% foreach my $key ( sort {$defaults->{$a} <=> $defaults->{$b} or $a cmp $b}
+% keys %$settings
+% )
+% {
+ <TR>
+ <TD ALIGN="right"><% $key |h %></TD>
+ <TD BGCOLOR="<% $defaults->{$key} ? '#eeeeee' : '#ffffff' %>">
+ <% $defaults->{$key} ? '<I>' : '<B>' %>
+ <% $settings->{$key} |h %>
+ <% $defaults->{$key} ? '</I>' : '</B>' %>
+ </TD>
+ </TR>
+% }
+
+ </TABLE>
+ <BR>
+
+% }
+% }
+<%init>
+
+my $svc_x = shift;
+
+</%init>
diff --git a/httemplate/view/elements/tr.html b/httemplate/view/elements/tr.html
new file mode 100644
index 0000000..e2ec7d4
--- /dev/null
+++ b/httemplate/view/elements/tr.html
@@ -0,0 +1,9 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} %></TD>
+ <TD BGCOLOR="#ffffff"><% $opt{'value'} %></TD>
+</TR>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/view/logo.cgi b/httemplate/view/logo.cgi
new file mode 100644
index 0000000..aeca0f3
--- /dev/null
+++ b/httemplate/view/logo.cgi
@@ -0,0 +1,47 @@
+<% $data %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $type;
+if ( $cgi->param('type') eq 'png' ) {
+ $type = 'png';
+} elsif ( $cgi->param('type') eq 'eps' ) {
+ $type = 'eps';
+} else {
+ die "unknown logo type ". $cgi->param('type');
+}
+
+my $data;
+if ( $cgi->param('preview_session') =~ /^(\w+)$/ ) {
+
+ my $session = $1;
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ $data = decode_base64( $curuser->option("logo_preview$session") );
+
+} elsif ( $cgi->param('name') =~ /^([^\.\/]*)$/ ) {
+
+ my $templatename = $1;
+ if ( $templatename && $conf->exists("logo_$templatename.$type") ) {
+ $templatename = "_$templatename";
+ } else {
+ $templatename = '';
+ }
+
+ if ( $type eq 'png' ) {
+ $data = $conf->config_binary("logo$templatename.png");
+ } elsif ( $type eq 'eps' ) {
+ #convert EPS to a png... punting on that for now
+ }
+
+} else {
+ die "neither a valid name nor a valid preview_session specified";
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
+
diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html
new file mode 100644
index 0000000..9883e67
--- /dev/null
+++ b/httemplate/view/prospect_main.html
@@ -0,0 +1,92 @@
+<% include('/elements/header.html',
+ 'Prospect View: '. $prospect_main->company
+ )
+%>
+
+% if ( $curuser->access_right('Edit prospect') ) {
+ <A HREF="<% $p %>edit/prospect_main.html?<% $prospectnum %>">Edit this prospect</A>
+% }
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Prospect #</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $prospectnum %></B></TD>
+</TR>
+
+%unless ( scalar(@agentnums) == 1 ) {
+% my $agent = qsearchs('agent',{ 'agentnum' => $prospect_main->agentnum } );
+ <TR>
+ <TD ALIGN="right">Agent</TD>
+ <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD>
+ </TR>
+%}
+
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $prospect_main->company |h %></B></TD>
+</TR>
+
+% foreach my $contact ( $prospect_main->contact ) {
+ <TR>
+ <TD ALIGN="right">Contact</TD>
+ <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD>
+ </TR>
+%}
+
+% my @cust_location =
+% qsearch('cust_location', { 'prospectnum' => $prospectnum } );
+% #but only one, for now
+% foreach my $cust_location (@cust_location) {
+ <TR>
+ <TD ALIGN="right">Address</TD>
+ <TD BGCOLOR="#FFFFFF">
+ <% $cust_location->location_label(
+ 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+
+</TABLE>
+
+<BR>
+
+<% ntable("#cccccc") %>
+
+<TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Tickets</FONT></TH>
+</TR>
+
+</TABLE>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View prospect');
+
+my $prospectnum;
+if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ $prospectnum = $1;
+} else {
+ die "No prospect specified (bad URL)!" unless $cgi->keywords;
+ my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+ $query =~ /^(\d+)$/;
+ $prospectnum = $1;
+}
+
+my $prospect_main = qsearchs( {
+ 'table' => 'prospect_main',
+ 'hashref' => { 'prospectnum' => $prospectnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Prospect not found!" unless $prospect_main;
+
+my @agentnums = $curuser->agentnums;
+
+</%init>
diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html
new file mode 100644
index 0000000..defbee9
--- /dev/null
+++ b/httemplate/view/svc_Common.html
@@ -0,0 +1,29 @@
+<% include('elements/svc_Common.html',
+ 'table' => $table,
+ 'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=",
+ %opt,
+ )
+%>
+<%init>
+
+# false laziness w/edit/svc_Common.html
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+my %opt;
+if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
+ $opt{'name'} = "FS::$table"->table_info->{'name'};
+
+ my $fields = "FS::$table"->table_info->{'fields'};
+ my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+ $opt{'labels'} = \%labels;
+}
+
+</%init>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
new file mode 100755
index 0000000..9135e67
--- /dev/null
+++ b/httemplate/view/svc_acct.cgi
@@ -0,0 +1,141 @@
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View $svc account") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this account?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <% include("/elements/header.html",'View account', menubar(
+ "Cancel this (unaudited) account" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')",
+ )) %>
+
+% }
+
+<% include( 'svc_acct/radius_usage.html',
+ 'svc_acct' => $svc_acct,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ %gopt,
+ )
+%>
+
+<% include( 'svc_acct/change_svc_form.html',
+ 'part_svc' => \@part_svc,
+ 'svcnum' => $svcnum,
+ 'pkgnum' => $pkgnum,
+ %gopt,
+ )
+%>
+
+Service #<B><% $svcnum %></B>
+| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A>
+
+<% include( 'svc_acct/change_svc.html',
+ 'part_svc' => \@part_svc,
+ %gopt,
+ )
+%>
+
+<% include( 'svc_acct/basics.html',
+ 'svc_acct' => $svc_acct,
+ 'part_svc' => $part_svc,
+ %gopt,
+ )
+%>
+
+</FORM>
+<BR>
+
+<% include( 'svc_acct/hosting.html',
+ %gopt,
+ )
+%>
+
+%#remove this? does anybody even use it? it was a misunderstood customer
+%#request IIRC?
+% my $conf = new FS::Conf;
+% if ( $conf->exists('svc_acct-notes') ) {
+% warn 'WARNING: svc_acct-notes deprecated\n';
+<% join("<BR>", $conf->config('svc_acct-notes') ) %>
+<BR><BR>
+% }
+
+<% include('elements/svc_export_settings.html', $svc_acct) %>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_acct = qsearchs({
+ 'select' => 'svc_acct.*',
+ 'table' => 'svc_acct',
+ 'addl_from' => $addl_from,
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+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 $svc = $part_svc->svc;
+
+my @part_svc = ();
+if ($FS::CurrentUser::CurrentUser->access_right('Change customer service')) {
+
+ 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 },
+ } );
+ }
+
+}
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my %gopt = ( 'communigate' => $communigate,
+ );
+
+</%init>
diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html
new file mode 100644
index 0000000..f4c8388
--- /dev/null
+++ b/httemplate/view/svc_acct/basics.html
@@ -0,0 +1,136 @@
+<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<% include('/view/elements/tr.html', label=>'Service', value=>$part_svc->svc) %>
+<% include('/view/elements/tr.html', label=>'Username', value=>$svc_acct->username) %>
+<% include('/view/elements/tr.html', label=>'Domain', value=>$domain) %>
+
+% if ( $opt{'communigate'} ) {
+ <% include('/view/elements/tr.html', label=>'Aliases', value=>$svc_acct->cgp_aliases) %>
+%}
+
+% if ( $svc_acct->pbxsvc ) {
+ <% include('/view/elements/tr.html', label=>'PBX', value=>$svc_acct->pbx_title) %>
+%}
+
+% my $show_pw = '';
+% my $password = $svc_acct->get_cleartext_password;
+% if ( $password =~ /^\*\w+\* (.*)$/ ) {
+% $password = $1;
+% $show_pw .= '<I>(login disabled)</I> ';
+% }
+% if ( ! $password
+% && $svc_acct->_password_encryption ne 'plain'
+% && $svc_acct->_password
+% )
+% {
+% $show_pw .= '<I>('. uc($svc_acct->_password_encryption). ' encrypted)</I>';
+% } elsif ( $conf->exists('showpasswords') ) {
+% $show_pw .= '<PRE>'. encode_entities($password). '</PRE>';
+% } else {
+% $show_pw .= '<I>(hidden)</I>';
+% }
+% $password = '';
+<% include('/view/elements/tr.html', label=>'Password', value=>$show_pw) %>
+
+
+% if ( $conf->exists('security_phrase') ) {
+ <%include('/view/elements/tr.html', label=>'Security phrase', value=>$svc_acct->sec_phrase)%>
+% }
+
+% if ( $svc_acct->popnum ) {
+% my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum});
+ <% include('/view/elements/tr.html', label=>'Access number', value=>$svc_acct_pop->text) %>
+% }
+
+% if ($svc_acct->uid ne '') {
+ <% include('/view/elements/tr.html', label=>'UID', value=>$svc_acct->uid) %>
+% }
+
+% if ($svc_acct->gid ne '') {
+ <% include('/view/elements/tr.html', label=>'GID', value=>$svc_acct->gid) %>
+% }
+
+% if ($svc_acct->finger ne '') {
+ <% include('/view/elements/tr.html', label=>'Real Name', value=>$svc_acct->finger) %>
+% }
+
+% if ($svc_acct->dir ne '') {
+ <% include('/view/elements/tr.html', label=>'Home directory', value=>$svc_acct->dir) %>
+% }
+
+% if ($svc_acct->shell ne '') {
+ <% include('/view/elements/tr.html', label=>'Shell', value=>$svc_acct->shell) %>
+% }
+
+% if ($svc_acct->quota ne '' && ! $opt{'communigate'} ) {
+
+ <% include('/view/elements/tr.html', label=>'Quota', value=>$svc_acct->quota) %>
+
+% } elsif ( $opt{'communigate'} ) {
+
+ <% include( 'communigate.html', %opt ) %>
+
+% }
+
+% if ($svc_acct->slipip) {
+ <% include('/view/elements/tr.html',
+ label=>'IP address',
+ value=> ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' )
+ ? "<I>(Dynamic)</I>"
+ : $svc_acct->slipip
+ )
+ %>
+% }
+
+<% include('usage.html',
+ 'svc_acct' => $svc_acct,
+ )
+%>
+
+% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) {
+% $attribute =~ /^radius_(.*)$/;
+% my $pattribute = $FS::raddb::attrib{$1};
+ <% include('/view/elements/tr.html', label=>"Radius (reply) $pattribute",
+ value=>$svc_acct->getfield($attribute)
+ )
+ %>
+% }
+
+% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) {
+% $attribute =~ /^rc_(.*)$/;
+% my $pattribute = $FS::raddb::attrib{$1};
+ <% include('/view/elements/tr.html', label=>"Radius (check) $pattribute",
+ value=>$svc_acct->getfield($attribute)
+ )
+ %>
+% }
+
+<% include('/view/elements/tr.html', label=>'RADIUS groups',
+ value=>join('<BR>', $svc_acct->radius_groups) ) %>
+
+%# 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) {
+ <% $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %>
+% }
+
+</TABLE></TD></TR></TABLE>
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+
+my $svc_acct = $opt{'svc_acct'};
+my $part_svc = $opt{'part_svc'};
+
+die 'Empty domsvc for svc_acct.svcnum '. $svc_acct->svcnum
+ unless $svc_acct->domsvc;
+my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } );
+die 'Unknown domain (domsvc '. $svc_acct->domsvc.
+ ' for svc_acct.svcnum '. $svc_acct->svcnum. ')'
+ unless $svc_domain;
+my $domain = $svc_domain->domain;
+
+</%init>
diff --git a/httemplate/view/svc_acct/change_svc.html b/httemplate/view/svc_acct/change_svc.html
new file mode 100644
index 0000000..33d44a7
--- /dev/null
+++ b/httemplate/view/svc_acct/change_svc.html
@@ -0,0 +1,21 @@
+% if ( @part_svc || $opt{'showall'} ) {
+
+| <SELECT NAME="svcpart" onChange="enable_change()">
+ <OPTION VALUE="">Change service</OPTION>
+ <OPTION VALUE="">--------------</OPTION>
+% foreach my $opt_part_svc ( @part_svc ) {
+
+ <OPTION VALUE="<% $opt_part_svc->svcpart %>"><% $opt_part_svc->svc %></OPTION>
+% }
+
+ </SELECT>
+ <INPUT NAME="submit" TYPE="submit" VALUE="Change" disabled>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my @part_svc = @{ $opt{'part_svc'} };
+
+</%init>
diff --git a/httemplate/view/svc_acct/change_svc_form.html b/httemplate/view/svc_acct/change_svc_form.html
new file mode 100644
index 0000000..4f10922
--- /dev/null
+++ b/httemplate/view/svc_acct/change_svc_form.html
@@ -0,0 +1,23 @@
+% if ( @part_svc || $opt{'showall'} ) {
+ <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 %>">
+% }
+<%init>
+
+my %opt = @_;
+my @part_svc = @{ $opt{'part_svc'} };
+my $svcnum = $opt{'svcnum'};
+my $pkgnum = $opt{'pkgnum'};
+
+</%init>
diff --git a/httemplate/view/svc_acct/communigate.html b/httemplate/view/svc_acct/communigate.html
new file mode 100644
index 0000000..9d66807
--- /dev/null
+++ b/httemplate/view/svc_acct/communigate.html
@@ -0,0 +1,77 @@
+%# settings
+
+ <% include('/view/elements/tr.html', label=>'Mailbox type', value=>$svc_acct->cgp_type) %>
+
+ <% include('/view/elements/tr.html', label=>'Enabled services',
+ value=>$svc_acct->cgp_accessmodes ) %>
+
+ <% include('/view/elements/tr.html', label=>'Mail storage limit',
+ value=>$svc_acct->quota ) %>
+
+ <% include('/view/elements/tr.html', label=>'File storage limit',
+ value=>$svc_acct->file_quota ) %>
+
+ <% include('/view/elements/tr.html', label=>'Number of files limit',
+ value=>$svc_acct->file_maxnum ) %>
+
+ <% include('/view/elements/tr.html', label=>'File size limit',
+ value=>$svc_acct->file_maxsize ) %>
+
+ <% include('/view/elements/tr.html', label=>'Password recovery',
+ value=>$svc_acct->password_recover ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Allowed mail rules',
+ value=>$svc_acct->cgp_rulesallowed || 'default (No)') %>
+
+ <% include('/view/elements/tr.html', label=>'RPOP modifications',
+ value=>$svc_acct->cgp_rpopallowed ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Accepts mail to "all"',
+ value=>$svc_acct->cgp_mailtoall ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Add trailer to sent mail',
+ value=>$svc_acct->cgp_addmailtrailer ? 'YES' : 'NO' ) %>
+
+%# preferences
+
+ <% include('/view/elements/tr.html', label=>'Message delete method',
+ value=>$svc_acct->cgp_deletemode ) %>
+
+ <% include('/view/elements/tr.html', label=>'On logout remove trash',
+ value=>$svc_acct->cgp_emptytrash ) %>
+
+ <% include('/view/elements/tr.html', label=>'Language',
+ value=>$svc_acct->cgp_language || 'default (English)' ) %>
+ <% include('/view/elements/tr.html', label=>'Time zone',
+ value=>$svc_acct->cgp_timezone || 'default (HostOS)' ) %>
+ <% include('/view/elements/tr.html', label=>'Layout',
+ value=>$svc_acct->cgp_skinname || 'default (***)' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Pronto style',
+ value=>$svc_acct->cgp_prontoskinname ) %>
+
+ <% include('/view/elements/tr.html', label=>'Send read receipts',
+ value=>$svc_acct->cgp_sendmdnmode ) %>
+
+%#XXX vacation message, redirect all mail
+
+%# mail rules
+
+ <% include('/view/elements/tr.html', label=>'Mail rules',
+ value=>$rule_link,
+ )
+ %>
+
+<%init>
+
+my %opt = @_;
+
+#my $conf = new FS::Conf;
+
+my $svc_acct = $opt{'svc_acct'};
+#my $part_svc = $opt{'part_svc'};
+
+my $rule_link = qq(<A HREF="${p}browse/cgp_rule.html?svcnum=).
+ $svc_acct->svcnum. '">View/edit mail rules</A>';
+
+</%init>
diff --git a/httemplate/view/svc_acct/hosting.html b/httemplate/view/svc_acct/hosting.html
new file mode 100644
index 0000000..1d83603
--- /dev/null
+++ b/httemplate/view/svc_acct/hosting.html
@@ -0,0 +1,38 @@
+% if ( @svc_www || $opt{'showall'} ) {
+ Hosting
+ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+% foreach my $svc_www (@svc_www) {
+% my($label, $value) = $svc_www->cust_svc->label;
+% my $link = $p. 'view/svc_www.cgi?'. $svc_www->svcnum;
+ <TR>
+ <TD BGCOLOR="#ffffff">
+ <A HREF="<% $link %>"><% "$label: $value" %></A>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD></TR></TABLE>
+ <BR><BR>
+% }
+<%init>
+
+my %opt = @_;
+
+#false laziness w/view_svc_acct.cgi and a zillion other places
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my @svc_www = qsearch({
+ 'select' => 'svc_www.*',
+ 'table' => 'svc_www',
+ 'addl_from' => $addl_from,
+ 'hashref' => { 'usersvc' => $opt{'svcnum'} },
+ #XXX shit outta luck if you somehow got them linked across agents
+ # maybe we should show but not link to them? kinda makes sense...
+ # (maybe a specific ACL for this situation???)
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+
+</%init>
diff --git a/httemplate/view/svc_acct/radius_usage.html b/httemplate/view/svc_acct/radius_usage.html
new file mode 100644
index 0000000..e2253a3
--- /dev/null
+++ b/httemplate/view/svc_acct/radius_usage.html
@@ -0,0 +1,77 @@
+% if ( $part_svc->part_export_usage ) {
+%
+% 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>
+ Last Login: <B><% $svc_acct->last_login_text %></B><BR>
+% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=!. $svc_acct->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>
+% }
+<%init>
+
+my %opt = @_;
+
+my $svc_acct = $opt{'svc_acct'};
+my $part_svc = $opt{'part_svc'};
+my $cust_pkg = $opt{'cust_pkg'};
+
+</%init>
diff --git a/httemplate/view/svc_acct/usage.html b/httemplate/view/svc_acct/usage.html
new file mode 100644
index 0000000..9758d83
--- /dev/null
+++ b/httemplate/view/svc_acct/usage.html
@@ -0,0 +1,27 @@
+% my %ulabel = ( seconds => 'Time',
+% upbytes => 'Upload bytes',
+% downbytes => 'Download bytes',
+% totalbytes => 'Total bytes',
+% );
+% foreach my $uf ( keys %ulabel ) {
+% my $tf = $uf . "_threshold";
+% if ( $svc_acct->$uf ne '' ) {
+% my $v = $uf eq 'seconds'
+% #? (($svc_acct->$uf < 0 ? '-' : ''). duration_exact($svc_acct->$uf) )
+% ? ($svc_acct->$uf < 0 ? '-' : '').
+% int(abs($svc_acct->$uf)/3600). "hr ".
+% sprintf("%02d",(abs($svc_acct->$uf)%3600)/60). "min"
+% : FS::UI::bytecount::display_bytecount($svc_acct->$uf);
+ <TR>
+ <TD ALIGN="right"><% $ulabel{$uf} %> remaining</TD>
+ <TD BGCOLOR="#ffffff"><% $v %></TD>
+ </TR>
+
+% }
+% }
+<%init>
+
+my %opt = @_;
+my $svc_acct = $opt{'svc_acct'};
+
+</%init>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644
index 0000000..f552e9b
--- /dev/null
+++ b/httemplate/view/svc_broadband.cgi
@@ -0,0 +1,219 @@
+<%include("/elements/header.html",'Broadband Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ )
+))
+%>
+
+<% include('/elements/init_overlib.html') %>
+
+<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">Description</TD>
+ <TD BGCOLOR="#ffffff"><%$description%></TD>
+ </TR>
+
+% if ( $router ) {
+ <TR>
+ <TD ALIGN="right">Router</TD>
+ <TD BGCOLOR="#ffffff"><%$router->routernum%>: <%$router->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>
+
+% if ( $ip_addr ) {
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff">
+ <%$ip_addr%>
+ (<% include('/elements/popup_link-ping.html', 'ip'=>$ip_addr ) %>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Netmask</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->NetAddr->mask%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Gateway</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->ip_gateway%></TD>
+ </TR>
+% }
+
+ <TR>
+ <TD ALIGN="right">MAC Address</TD>
+ <TD BGCOLOR="#ffffff"><%$mac_addr%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Latitude</TD>
+ <TD BGCOLOR="#ffffff"><%$latitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Longitude</TD>
+ <TD BGCOLOR="#ffffff"><%$longitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Altitude</TD>
+ <TD BGCOLOR="#ffffff"><%$altitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">VLAN Profile</TD>
+ <TD BGCOLOR="#ffffff"><%$vlan_profile%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Authentication Key</TD>
+ <TD BGCOLOR="#ffffff"><%$auth_key%></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 @sb_addr_block;
+% if (@sb_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 (@sb_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)%>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_broadband = qsearchs({
+ 'select' => 'svc_broadband.*',
+ 'table' => 'svc_broadband',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) 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, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $addr_block = $svc_broadband->addr_block;
+my $router = $addr_block->router if $addr_block;
+
+#if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+
+my (
+ $speed_down,
+ $speed_up,
+ $ip_addr,
+ $mac_addr,
+ $latitude,
+ $longitude,
+ $altitude,
+ $vlan_profile,
+ $auth_key,
+ $description,
+ ) = (
+ $svc_broadband->getfield('speed_down'),
+ $svc_broadband->getfield('speed_up'),
+ $svc_broadband->getfield('ip_addr'),
+ $svc_broadband->mac_addr,
+ $svc_broadband->latitude,
+ $svc_broadband->longitude,
+ $svc_broadband->altitude,
+ $svc_broadband->vlan_profile,
+ $svc_broadband->auth_key,
+ $svc_broadband->description,
+ );
+
+</%init>
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
new file mode 100755
index 0000000..3938a34
--- /dev/null
+++ b/httemplate/view/svc_domain.cgi
@@ -0,0 +1,82 @@
+% if ( $custnum ) {
+
+%# <% include("/elements/header.html","View $svcdomain") %>
+ <% include("/elements/header.html","View domain") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html",'View domain', menubar(
+ "Cancel this (unaudited) domain" =>
+ "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum', 'Delete $domain and all records?')",
+ ))
+ %>
+
+% }
+
+<% include('/elements/error.html') %>
+
+<% include('svc_domain/basics.html', $svc_domain,
+ 'part_svc' => $part_svc,
+ 'custnum' => $custnum,
+ )
+%>
+<BR>
+
+<% include('svc_domain/acct_defaults.html', $svc_domain,
+ 'part_svc' => $part_svc,
+ )
+%>
+<BR>
+
+<% include('svc_domain/dns.html', $svc_domain ) %>
+<BR>
+
+<% include('elements/svc_export_settings.html', $svc_domain) %>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_domain = qsearchs({
+ 'select' => 'svc_domain.*',
+ 'table' => 'svc_domain',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+die "Unknown svcnum" unless $svc_domain;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs('cust_pkg', {'pkgnum'=>$pkgnum} );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $domain = $svc_domain->domain;
+
+</%init>
diff --git a/httemplate/view/svc_domain/acct_defaults.html b/httemplate/view/svc_domain/acct_defaults.html
new file mode 100644
index 0000000..b6d03e2
--- /dev/null
+++ b/httemplate/view/svc_domain/acct_defaults.html
@@ -0,0 +1,141 @@
+% if ( $communigate ) {
+
+ Account defaults
+ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+%# settings
+
+ <% include('/view/elements/tr.html',
+ label=>'Password modification',
+ value=>$svc_domain->acct_def_password_selfchange ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Password recovery',
+ value=>$svc_domain->acct_def_password_recover ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Enabled services',
+ value=>$svc_domain->acct_def_cgp_accessmodes,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Mail storage limit',
+ value=>$svc_domain->acct_def_quota,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'File storage limit',
+ value=>$svc_domain->acct_def_file_quota,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Files limt',
+ value=>$svc_domain->acct_def_file_maxnum,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'File size limit',
+ value=>$svc_domain->acct_def_file_maxsize,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Allowed mail rules',
+ value=>$svc_domain->acct_def_cgp_rulesallowed || 'default (No)',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'RPOP modifications',
+ value=>$svc_domain->acct_def_cgp_rpopallowed ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Accepts mail to "all"',
+ value=>$svc_domain->acct_def_cgp_mailtoall ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Add trailer to sent mail',
+ value=>$svc_domain->acct_def_cgp_addmailtrailer ? 'YES' : 'NO',
+ )
+ %>
+
+%# preferences
+
+ <% include('/view/elements/tr.html',
+ label=>'Message delete method',
+ value=>$svc_domain->acct_def_cgp_deletemode,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'On logout remove trash',
+ value=>$svc_domain->acct_def_cgp_emptytrash,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Language',
+ value=>$svc_domain->acct_def_cgp_language || 'default (English)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Time zone',
+ value=>$svc_domain->acct_def_cgp_timezone || 'default (HostOS)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Layout',
+ value=>$svc_domain->acct_def_cgp_skinname || 'default (***)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Pronto style',
+ value=>$svc_domain->acct_def_cgp_prontoskinname
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Send read receipts',
+ value=>$svc_domain->acct_def_cgp_sendmdnmode
+ )
+ %>
+
+%# mail
+%#XXX rules, archive rule, spam foldering rule(s)
+
+ </TABLE></TD></TR></TABLE>
+
+% }
+<%init>
+
+my($svc_domain, %opt) = @_;
+
+my $part_svc = $opt{'part_svc'};
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my %rulesallowed = (
+ -1 => 'default (No)', #No always the default?
+ 0 => 'No',
+ 1 => 'Filter Only',
+ 2 => 'All But Exec',
+ 3 => 'Any',
+);
+
+</%init>
diff --git a/httemplate/view/svc_domain/basics.html b/httemplate/view/svc_domain/basics.html
new file mode 100644
index 0000000..1915719
--- /dev/null
+++ b/httemplate/view/svc_domain/basics.html
@@ -0,0 +1,151 @@
+Service #<B><% $svcnum %></B>
+% #if ( $conf->exists('svc_domain-edit_domain') ) {
+ | <A HREF="<%$p%>edit/svc_domain.cgi?<%$svcnum%>">Edit this domain</A>
+% #}
+
+<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#ffffff"><% $part_svc->svc %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD BGCOLOR="#ffffff">
+ <B><% $domain %></B>
+ <A HREF="<% ${p} %>misc/whois.cgi?custnum=<%$custnum%>;svcnum=<%$svcnum%>;domain=<%$domain%>">(view whois information)</A>
+ </TD>
+</TR>
+
+% if ($export) {
+ <TR>
+ <TD ALIGN="right">Registration status</TD>
+ <TD BGCOLOR="#ffffff"><B><% $status %></B>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Manage domain registration') ) {
+% if ( defined($ops{'register'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=register&svcnum=<% $svcnum %>">Register at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'transfer'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=transfer&svcnum=<% $svcnum %>">Transfer to <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'renew'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=renew&svcnum=<% $svcnum %>&period=1">Renew at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'revoke'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=revoke&svcnum=<% $svcnum %>">Revoke</A>
+% }
+% }
+
+ </TD>
+ </TR>
+% }
+
+% if ( $communigate ) {
+
+ <TR>
+ <TD ALIGN="right">Administrator domain</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $svc_domain->parent_svcnum ) {
+% #XXX agent-virt aware the link
+ <A HREF="svc_domain.cgi?<% $svc_domain->parent_svcnum %>"><% $svc_domain->parent_svc_x->domain %></A>
+% } else {
+ <I>(none)</I>
+% }
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Aliases</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->cgp_aliases %></TD>
+ </TR>
+
+% }
+
+% if ( $communigate && $svc_domain->max_accounts ) {
+ <TR>
+ <TD ALIGN="right">Maximum number of Accounts</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->max_accounts %></TD>
+ </TR>
+% }
+
+<TR>
+ <TD ALIGN="right">Catch all email</TD>
+ <TD BGCOLOR="#ffffff"><% $email ? "<B>$email</B>" : '<I>(none)</I>' %>
+% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall') ) {
+ <A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>
+% }
+ </TD>
+</TR>
+
+% if ( $svc_domain->cgp_accessmodes ) {
+ <TR>
+ <TD ALIGN="right">Enabled services</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->cgp_accessmodes %></TD>
+ </TR>
+% }
+
+% if ( $svc_domain->trailer ) {
+ <TR>
+ <TD ALIGN="right">Mail trailer</TD>
+ <TD BGCOLOR="#ffffff"><PRE><% $svc_domain->trailer |h %></PRE></TD>
+ </TR>
+% }
+
+% if ( $communigate ) {
+% my $rule_url = $p. 'browse/cgp_rule.html?svcnum='. $svc_domain->svcnum;
+ <TR>
+ <TD ALIGN="right">Doimain mail rules</TD>
+ <TD BGCOLOR="#ffffff"><A HREF="<% $rule_url %>">View/Edit domain mail rules</A></TD>
+ </TR>
+% }
+
+</TABLE></TD></TR></TABLE>
+
+<%init>
+
+my($svc_domain, %opt) = @_;
+my $svcnum = $svc_domain->svcnum;
+my $domain = $svc_domain->domain;
+my $custnum = $opt{'custnum'};
+my $part_svc = $opt{'part_svc'};
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+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;
+}
+
+# Find the first export that does domain registration
+my @exports = grep $_->can('registrar'), $part_svc->part_export;
+my $export = $exports[0];
+# If we have a domain registration export, get the registrar object
+my $registrar;
+my $status = 'Unknown';
+my %ops = ();
+if ($export) {
+ $registrar = $export->registrar;
+ my $domstat = $export->get_status( $svc_domain );
+ if (defined($domstat->{'message'})) {
+ $status = $domstat->{'message'};
+ } elsif (defined($domstat->{'unregistered'})) {
+ $status = 'Not registered';
+ $ops{'register'} = "Register";
+ } elsif (defined($domstat->{'status'})) {
+ $status = $domstat->{'status'} . ' ' . $domstat->{'contact_email'} . ' ' . $domstat->{'last_update_time'};
+ } elsif (defined($domstat->{'expdate'})) {
+ $status = "Expires " . $domstat->{'expdate'};
+ $ops{'renew'} = "Renew";
+ $ops{'revoke'} = "Revoke";
+ } else {
+ $status = $domstat->{'reason'};
+ $ops{'transfer'} = "Transfer";
+ }
+}
+
+</%init>
diff --git a/httemplate/view/svc_domain/dns.html b/httemplate/view/svc_domain/dns.html
new file mode 100644
index 0000000..f6f8c71
--- /dev/null
+++ b/httemplate/view/svc_domain/dns.html
@@ -0,0 +1,94 @@
+<SCRIPT>
+ function areyousure(href, message) {
+ if ( confirm(message) == true )
+ window.location.href = href;
+ }
+ function slave_areyousure() {
+ return confirm("Remove all records and slave from " + document.SlaveForm.recdata.value + "?");
+ }
+</SCRIPT>
+
+DNS records
+% my @records; if ( @records = $svc_domain->domain_record ) {
+
+ <% include('/elements/table-grid.html') %>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = $bgcolor2;
+
+ <tr>
+ <th CLASS="grid" BGCOLOR="#cccccc">Zone</th>
+ <th CLASS="grid" BGCOLOR="#cccccc">Type</th>
+ <th CLASS="grid" BGCOLOR="#cccccc">Data</th>
+ </tr>
+
+% foreach my $domain_record ( @records ) {
+% my $type = $domain_record->rectype eq '_mstr'
+% ? "(slave)"
+% : $domain_record->recaf. ' '. $domain_record->rectype;
+
+
+ <tr>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->reczone %></td>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $type %></td>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->recdata %>
+
+% unless ( $domain_record->rectype eq 'SOA'
+% || ! $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice')
+% ) {
+% ( my $recdata = $domain_record->recdata ) =~ s/"/\\'\\'/g;
+ (<A HREF="javascript:areyousure('<%$p%>misc/delete-domain_record.cgi?<%$domain_record->recnum%>', 'Delete \'<% $domain_record->reczone %> <% $type %> <% $recdata %>\' ?' )">delete</A>)
+% }
+ </td>
+ </tr>
+
+
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+% }
+
+ </table>
+% }
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice') ) {
+ <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 TXT) ) {
+ <OPTION VALUE="<%$_%>"><%$_%></OPTION>
+% }
+ </SELECT>
+ <INPUT TYPE="text" NAME="recdata">
+ <INPUT TYPE="submit" VALUE="Add record">
+ </FORM>
+
+ <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
+% }
+ Or 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>
+
+% }
+
+<%init>
+
+my($svc_domain, %opt) = @_;
+my $svcnum = $svc_domain->svcnum;
+
+</%init>
+
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
new file mode 100644
index 0000000..77679d8
--- /dev/null
+++ b/httemplate/view/svc_external.cgi
@@ -0,0 +1,65 @@
+<% include("/elements/header.html",'External Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) external service" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+)) %>
+
+<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) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_external = qsearchs({
+ 'select' => 'svc_external.*',
+ 'table' => 'svc_external',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) 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, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+</%init>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
new file mode 100755
index 0000000..43d8a4e
--- /dev/null
+++ b/httemplate/view/svc_forward.cgi
@@ -0,0 +1,121 @@
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View mail forward") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html",'View mail forward', menubar(
+ "Cancel this (unaudited) mail forward" =>
+ "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum')",
+ ))
+ %>
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this mail forward?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+% }
+
+<A HREF="<% $p %>edit/svc_forward.cgi?<% $svcnum %>">Edit this information</A>
+
+<% 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"><% $svc %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Email to</TD>
+ <TD BGCOLOR="#ffffff"><% $source %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Forwards to </TD>
+ <TD BGCOLOR="#ffffff"><% $destination %></TD>
+ </TR>
+
+% foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+ <% $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)) %>
+% }
+
+</TABLE>
+
+<BR>
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_forward = qsearchs({
+ 'select' => 'svc_forward.*',
+ 'table' => 'svc_forward',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+die "Unknown svcnum" unless $svc_forward;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $custnum=$cust_pkg->getfield('custnum');
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } )
+ or die "Unknown svcpart";
+
+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;
+}
+
+</%init>
diff --git a/httemplate/view/svc_mailinglist.cgi b/httemplate/view/svc_mailinglist.cgi
new file mode 100644
index 0000000..f646a41
--- /dev/null
+++ b/httemplate/view/svc_mailinglist.cgi
@@ -0,0 +1,71 @@
+<% include('elements/svc_Common.html',
+ 'table' => 'svc_mailinglist',
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = ();
+
+my $info = FS::svc_mailinglist->table_info;
+
+$opt{'name'} = $info->{'name'};
+
+my $fields = $info->{'fields'};
+my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+
+#$opt{'fields'} = [ keys %$fields ];
+$opt{'fields'} = [
+ 'username',
+ 'domain',
+ 'listname',
+ 'reply_to',
+ 'remove_from',
+ 'reject_auto',
+ 'remove_to_and_cc',
+];
+
+$opt{'labels'} = \%labels;
+
+$opt{'html_foot'} = sub {
+ my $svc_mailinglist = shift;
+ my $listnum = $svc_mailinglist->listnum;
+
+ my $sql = 'SELECT COUNT(*) FROM mailinglistmember WHERE listnum = ?';
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute($listnum) or die $sth->errstr;
+ my $num = $sth->fetchrow_arrayref->[0];
+
+ my $add_url = $p."edit/mailinglistmember.html?listnum=$listnum";
+
+ my $add_link = include('/elements/init_overlib.html').
+ include('/elements/popup_link.html',
+ 'action' => $add_url,
+ 'label' => 'add',
+ 'actionlabel' => 'Add list member',
+ 'width' => 392,
+ 'height' => 192,
+ );
+
+ ntable('#cccccc').'<TR><TD>'.ntable('#cccccc',2). qq[
+ <TR>
+ <TD>List members</TD>
+ <TD BGCOLOR="#ffffff">
+ $num members
+ ( <A HREF="${p}search/mailinglistmember.html?listnum=$listnum">view</A>
+ | $add_link )
+ </TD>
+ </TR>
+ </TABLE></TD></TR></TABLE>
+
+ <BR><BR>
+ ]. include('svc_export_settings.html', $svc_mailinglist);
+
+};
+
+</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
new file mode 100644
index 0000000..75591c7
--- /dev/null
+++ b/httemplate/view/svc_phone.cgi
@@ -0,0 +1,153 @@
+<% include('elements/svc_Common.html',
+ 'table' => 'svc_phone',
+ 'fields' => \@fields,
+ 'labels' => {
+ 'countrycode' => 'Country code',
+ 'phonenum' => 'Phone number',
+ 'domain' => 'Domain',
+ 'pbx_title' => 'PBX',
+ 'sip_password' => 'SIP password',
+ 'pin' => 'PIN',
+ 'phone_name' => 'Name',
+ },
+ 'html_foot' => $html_foot,
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+
+my @fields = qw( countrycode phonenum );
+push @fields, 'domain' if $conf->exists('svc_phone-domain');
+push @fields, qw( pbx_title sip_password pin phone_name );
+
+my $html_foot = sub {
+ my $svc_phone = shift;
+
+ ###
+ # E911 Info
+ ###
+
+ my $e911 =
+ 'E911 Information'.
+ &ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2).
+ '<TR><TD>Location</TD>'.
+ '<TD BGCOLOR="#FFFFFF">'.
+ $svc_phone->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ ).
+ '</TD></TR>'.
+ '</TABLE></TD></TR></TABLE>'.
+ '<BR>'
+ ;
+
+ ###
+ # Devices
+ ###
+
+ my $devices = '';
+
+ my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device") #WHERE disabled = '' OR disabled IS NULL;");
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $num_part_device = $sth->fetchrow_arrayref->[0];
+
+ my @phone_device = $svc_phone->phone_device;
+ if ( @phone_device || $num_part_device ) {
+ my $svcnum = $svc_phone->svcnum;
+ $devices .=
+ qq[Devices (<A HREF="${p}edit/phone_device.html?svcnum=$svcnum">Add device</A>)<BR>];
+ if ( @phone_device ) {
+
+ $devices .= qq!
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Are you sure you want to delete this device?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+ !;
+
+
+ $devices .=
+ include('/elements/table-grid.html').
+ '<TR>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+ '</TR>';
+ my $bgcolor1 = '#eeeeee';
+ my $bgcolor2 = '#ffffff';
+ my $bgcolor = '';
+
+ foreach my $phone_device ( @phone_device ) {
+
+ if ( $bgcolor eq $bgcolor1 ) {
+ $bgcolor = $bgcolor2;
+ } else {
+ $bgcolor = $bgcolor1;
+ }
+ my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
+
+ my $devicenum = $phone_device->devicenum;
+ my $export_links = join( '<BR>', @{ $phone_device->export_links } );
+
+ $devices .= '<TR>'.
+ $td. $phone_device->part_device->devicename. '</TD>'.
+ $td. $phone_device->mac_addr. '</TD>'.
+ $td. $export_links. '</TD>'.
+ "$td( ".
+ qq(<A HREF="${p}edit/phone_device.html?$devicenum">edit</A> | ).
+ qq(<A HREF="javascript:areyousure('${p}misc/delete-phone_device.html?$devicenum')">delete</A>).
+ ' )</TD>'.
+ '</TR>';
+ }
+ $devices .= '</TABLE><BR>';
+ }
+ $devices .= '<BR>';
+ }
+
+ ##
+ # CDR links
+ ##
+
+ tie my %what, 'Tie::IxHash',
+ 'pending' => 'NULL',
+ 'billed' => 'done',
+ ;
+
+ #XXX src & charged party (& default prefix) as per voip_cdr.pm
+ #XXX handle toll free too
+
+ my $number = $svc_phone->phonenum;
+ $number = $svc_phone->countrycode. $number
+ unless $svc_phone->countrycode eq '1';
+
+ #my @links = map {
+ # qq(<A HREF="${p}search/cdr.html?src=$number;freesidestatus=$what{$_}">).
+ # "View $_ CDRs</A>";
+ #} keys(%what);
+ my @links = map {
+ qq(<A HREF="${p}search/cdr.html?cdrbatchnum=__ALL__;charged_party=$number;freesidestatus=$what{$_}">).
+ "View $_ CDRs</A>";
+ } keys(%what);
+
+ my @ilinks = ( qq(<A HREF="${p}search/cdr.html?cdrbatchnum=__ALL__;dst=$number">).
+ 'View incoming CDRs</A>' );
+
+ ###
+ # concatenate & return
+ ###
+
+ $e911.
+ $devices.
+ join(' | ', @links ). '<BR>'.
+ join(' | ', @ilinks). '<BR>';
+
+};
+
+</%init>
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
new file mode 100644
index 0000000..935d139
--- /dev/null
+++ b/httemplate/view/svc_www.cgi
@@ -0,0 +1,106 @@
+<% include("/elements/header.html", "Website View", menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ ))
+%>
+
+<A HREF="<% $p %>edit/svc_www.cgi?<% $svcnum %>">Edit this information</A><BR>
+
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><% $svcnum %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Website name</TD>
+ <TD BGCOLOR="#ffffff"><A HREF="http://<% $www %>"><% $www %></A></TD>
+ </TR>
+
+% if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F'
+% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) {
+
+ <TR>
+ <TD ALIGN="right">Account</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $usersvc ) {
+ <A HREF="<% $p %>view/svc_acct.cgi?<% $usersvc %>"><% $email %></A>
+% } else {
+ </i>(none)</i>
+% }
+ </TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TD ALIGN="right">Config lines</TD>
+ <TD BGCOLOR="#ffffff"><PRE><% join("\n", $svc_www->config) |h %></PRE></TD>
+ </TR>
+
+% foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+ <% $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)) %>
+% }
+
+</TABLE>
+
+<BR>
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_www = qsearchs({
+ 'select' => 'svc_www.*',
+ 'table' => 'svc_www',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) 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, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $part_svc=qsearchs('part_svc',{'svcpart'=>$cust_svc->svcpart})
+ or die "svc_www: Unknown svcpart" . $cust_svc->svcpart;
+
+my $usersvc = $svc_www->usersvc;
+my $svc_acct = '';
+my $email = '';
+if ( $usersvc ) {
+ $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } )
+ or die "svc_www: Unknown usersvc $usersvc";
+ $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;
+
+</%init>
diff --git a/init.d/freeside-init b/init.d/freeside-init
new file mode 100644
index 0000000..b4609d4
--- /dev/null
+++ b/init.d/freeside-init
@@ -0,0 +1,119 @@
+#!/bin/sh
+#
+# chkconfig: 345 86 16
+# description: Freeside daemons
+
+QUEUED_USER=%%%QUEUED_USER%%%
+
+SELFSERVICE_USER=%%%SELFSERVICE_USER%%%
+SELFSERVICE_MACHINES="%%%SELFSERVICE_MACHINES%%%"
+
+IF=eth0
+
+#INSTALLSCRIPT/INSTALLSITEBIN from Makefile.PL
+PATH="$PATH:/usr/local/bin"
+export PATH
+
+[ -r /etc/default/freeside ] && . /etc/default/freeside
+
+case "$1" in
+ start)
+ # Start daemons.
+ echo -n "Starting freeside-queued: "
+ #perl -MDBIx::Profile /usr/local/bin/freeside-queued $QUEUED_USER
+ freeside-queued $QUEUED_USER
+ #export NYTPROF="file=/usr/local/etc/freeside/nytprof.out"
+ #PERL5OPT="-d:NYTProf" freeside-queued $QUEUED_USER
+ echo "done."
+
+ echo -n "Starting freeside-sqlradius-radacctd: "
+ freeside-sqlradius-radacctd $QUEUED_USER
+ echo "done."
+
+ echo -n "Starting freeside-prepaidd: "
+ freeside-prepaidd $QUEUED_USER
+ echo "done."
+
+ echo -n "Starting freeside-cdrrewrited: "
+ freeside-cdrrewrited $QUEUED_USER
+ echo "done."
+
+ echo -n "Starting freeside-cdrd: "
+ freeside-cdrd $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
+
+ #ip=`/sbin/ifconfig $IF | grep 'inet addr:' | cut -d: -f2- | cut -d' ' -f1`
+ #cp /opt/rt3/etc/RT_SiteConfig.pm.ORIG /opt/rt3/etc/RT_SiteConfig.pm
+ #perl -pi -e "s/localhost/$ip/" /opt/rt3/etc/RT_SiteConfig.pm
+
+ ;;
+ stop)
+ # Stop daemons.
+ echo -n "Stopping freeside-queued: "
+ [ -e /var/run/freeside-queued.pid ] && kill `cat /var/run/freeside-queued.pid`
+ #and
+ sleep 2;
+ killall freeside-queued
+ echo "done."
+
+ if [ -e /var/run/freeside-sqlradius-radacctd.pid ]; then
+ echo -n "Stopping freeside-sqlradius-radacctd: "
+ kill `cat /var/run/freeside-sqlradius-radacctd.pid`
+ echo "done."
+ fi
+
+ if [ -e /var/run/freeside-prepaidd.pid ]; then
+ echo -n "Stopping freeside-prepaidd: "
+ kill `cat /var/run/freeside-prepaidd.pid`
+ echo "done."
+ fi
+
+ if [ -e /var/run/freeside-cdrd.pid ]; then
+ echo -n "Stopping freeside-cdrd: "
+ kill `cat /var/run/freeside-cdrd.pid`
+ echo "done."
+ fi
+
+ if [ -e /var/run/freeside-cdrrewrited.pid ]; then
+ echo -n "Stopping freeside-cdrrewrited: "
+ kill `cat /var/run/freeside-cdrrewrited.pid`
+ echo "done."
+ fi
+
+ 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
+ echo "done."
+ fi
+
+ if [ -z "$SELFSERVICE_MACHINES" ]; then SELFSERVICE_MACHINES='localhost'; fi
+ for MACHINE in $SELFSERVICE_MACHINES; do
+ if [ -e /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid ]
+ then
+ echo -n "Stopping freeside-selfservice-server to $MACHINE: "
+ kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid`
+ echo "done."
+ fi
+ done
+
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Usage: freeside {start|stop|restart}"
+ exit 1
+esac
+
+exit 0
+
diff --git a/rpm/INSTALL b/rpm/INSTALL
new file mode 100644
index 0000000..d39bf70
--- /dev/null
+++ b/rpm/INSTALL
@@ -0,0 +1,4 @@
+See httemplates/docs/install-rpm.html and documentation on the wiki.
+
+This directory contains files that are RPM-specific and are referenced by the spec file during a build of the Freeside RPMs.
+
diff --git a/rpm/freeside-selfservice.conf b/rpm/freeside-selfservice.conf
new file mode 100644
index 0000000..f2c103b
--- /dev/null
+++ b/rpm/freeside-selfservice.conf
@@ -0,0 +1,11 @@
+ScriptAlias /selfservice %%%FREESIDE_SELFSERVICE_DOCUMENT_ROOT%%%/cgi
+
+<Directory %%%FREESIDE_SELFSERVICE_DOCUMENT_ROOT%%%/cgi>
+SSLRequireSSL
+DirectoryIndex selfservice.cgi
+AllowOverride None
+Options +ExecCGI -Includes
+Order deny,allow
+Allow from all
+SetHandler cgi-script
+</Directory>
diff --git a/rpm/freeside.spec b/rpm/freeside.spec
new file mode 100644
index 0000000..d9d2596
--- /dev/null
+++ b/rpm/freeside.spec
@@ -0,0 +1,492 @@
+%{!?_initrddir:%define _initrddir /etc/rc.d/init.d}
+%{!?version:%define version 2.1.0}
+%{!?release:%define release 8}
+
+Summary: Freeside ISP Billing System
+Name: freeside
+Version: %{version}
+Release: %{release}
+License: AGPLv3
+Group: Applications/Internet
+URL: http://www.sisd.com/freeside/
+Vendor: Freeside
+Source: http://www.sisd.com/freeside/%{name}-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+BuildArch: noarch
+Requires: %{name}-frontend
+Requires: %{name}-backend
+%if "%{_vendor}" != "suse"
+Requires: tetex-latex
+Requires: ghostscript
+%else
+Requires: te_latex
+Requires: ghostscript-library
+%endif
+Requires: perl-Fax-Hylafax-Client
+
+%if "%{_vendor}" != "suse"
+%define apache_conffile /etc/httpd/conf/httpd.conf
+%define apache_confdir /etc/httpd/conf.d
+%define apache_version 2
+%define freeside_document_root /var/www/freeside
+%define freeside_selfservice_document_root /var/www/freeside-selfservice
+%else
+%define apache_conffile /etc/apache2/uid.conf
+%define apache_confdir /etc/apache2/conf.d
+%define apache_version 2
+%define freeside_document_root /srv/www/freeside
+%define freeside_selfservice_document_root /srv/www/freeside-selfservice
+%endif
+# Can change this back to /var/cache/subsys/freeside when cache relocation is fixed and released
+%define freeside_cache /etc/freeside
+%define freeside_conf /etc/freeside
+%define freeside_export /etc/freeside
+%define freeside_lock /var/lock/freeside
+%define freeside_log /var/log/freeside
+%define freeside_socket /etc/freeside
+%define rt_enabled 0
+%define fs_queue_user fs_queue
+%define fs_selfservice_user fs_selfservice
+%define fs_cron_user fs_daily
+%define db_types Pg mysql
+
+%define texmflocal /usr/share/texmf
+
+%define _rpmlibdir /usr/lib/rpm
+%define rpmfiles rpm
+
+%description
+Freeside is a flexible ISP billing system
+
+%package mason
+Summary: HTML::Mason interface for %{name}
+Group: Applications/Internet
+Prefix: %{freeside_document_root}
+%if "%{_vendor}" != "suse"
+Requires: mod_ssl
+%endif
+Requires: perl-Apache-DBI
+Provides: %{name}-frontend = %{version}
+BuildArch: noarch
+
+%description mason
+This package includes the HTML::Mason web interface for %{name}.
+You should install only one %{name} web interface.
+
+%package postgresql
+Summary: PostgreSQL backend for %{name}
+Group: Applications/Internet
+Requires: perl-DBI
+Requires: perl-DBD-Pg >= 1.32
+Requires: %{name}
+Conflicts: %{name}-mysql
+Provides: %{name}-backend = %{version}
+
+%description postgresql
+This package includes the PostgreSQL database backend for %{name}.
+You should install only one %{name} database backend.
+Please note that this RPM does not create the database or database user; it only installs the required drivers.
+
+%package mysql
+Summary: MySQL database backend for %{name}
+Group: Applications/Internet
+Requires: perl-DBI
+Requires: perl-DBD-MySQL
+Requires: %{name}
+Conflicts: %{name}-postgresql
+Provides: %{name}-backend = %{version}
+
+%description mysql
+This package includes the MySQL database backend for %{name}.
+You should install only one %{name} database backend.
+Please note that this RPM does not create the database or database user; it only installs the required drivers.
+
+%package selfservice
+Summary: Self-service interface for %{name}
+Group: Applications/Internet
+Requires: %{name}-selfservice-cgi
+
+%description selfservice
+This package installs the Perl modules and CGI scripts for the self-service interface for %{name}.
+For security reasons, it is set to conflict with %{name} as you should not install the billing system and self-service interface on the same computer.
+
+%package selfservice-core
+Summary: Core Perl libraries for the self-service interface for %{name}
+Group: Applications/Internet
+
+%description selfservice-core
+This package installs the Perl modules and client daemon for the self-service interface for %{name}. It does not install the CGI interface and can be used with a different front-end.
+For security reasons, it is set to conflict with %{name} as you should not install the billing system and self-service interface on the same computer.
+
+%package selfservice-cgi
+Summary: CGI scripts for the self-service interface for %{name}
+Group: Applications/Internet
+Requires: %{name}-selfservice-core
+Prefix: %{freeside_selfservice_document_root}
+
+%description selfservice-cgi
+This package installs the CGI scripts for the self-service interface for %{name}. The scripts use some core libraries packaged in a separate RPM.
+For security reasons, it is set to conflict with %{name} as you should not install the billing system and self-service interface on the same computer.
+
+%package selfservice-php
+Summary: Sample PHP files for the self-service interface for %{name}
+Group: Applications/Internet
+Prefix: %{freeside_selfservice_document_root}
+
+%description selfservice-php
+This package installs the sample PHP scripts for the self-service interface for %{name}.
+For security reasons, it is set to conflict with %{name} as you should not install the billing system and self-service interface on the same computer.
+
+%prep
+%setup -q
+%{__rm} -f bin/pod2x # Only useful to Ivan Kohler now
+perl -pi -e 's|/usr/local/bin|%{_bindir}|g' FS/Makefile.PL
+# RPM handles changing file ownership, so Makefile shouldn't
+perl -pi -e 's/\s+-o\s+(freeside|root)(\s+-g\s+\$\{\w+\})?\s+/ /g' Makefile
+perl -ni -e 'print if !/\s+chown\s+/;' Makefile
+
+# Fix-ups for self-service. Should merge this into Makefile
+perl -pi -e 's|/usr/local/sbin|%{_sbindir}|g' FS/bin/freeside-selfservice-server
+perl -pi -e 's|/usr/local/bin|%{_bindir}|g' fs_selfservice/FS-SelfService/Makefile.PL
+perl -pi -e 's|/usr/local/sbin|%{_sbindir}|g' fs_selfservice/FS-SelfService/Makefile.PL
+perl -pi -e 's|/usr/local/freeside|%{freeside_socket}|g' fs_selfservice/FS-SelfService/*.pm
+perl -pi -e 's|socket\s*=\s*"/usr/local/freeside|socket = "%{freeside_socket}|g' fs_selfservice/FS-SelfService/freeside-selfservice-*
+perl -pi -e 's|log_file\s*=\s*"/usr/local/freeside|log_file = "%{freeside_log}|g' fs_selfservice/FS-SelfService/freeside-selfservice-*
+perl -pi -e 's|lock_file\s*=\s*"/usr/local/freeside|lock_file = "%{freeside_lock}|g' fs_selfservice/FS-SelfService/freeside-selfservice-*
+
+# Fix-ups for SuSE
+%if "%{_vendor}" == "suse"
+perl -pi -e 's|htpasswd|/usr/sbin/htpasswd2|g if /system/;' FS/FS/access_user.pm
+perl -pi -e 'print "Order deny,allow\nAllow from all\n" if /<Files/i;' htetc/freeside*.conf
+%endif
+
+# Override find-requires/find-provides to supplement Perl requires for HTML::Mason file handler.pl
+cat << \EOF > %{name}-req
+#!/bin/sh
+tee %{_tmppath}/filelist | %{_rpmlibdir}/rpmdeps --requires | grep -v -E '^perl\(the\)$' \
+| grep -v -E '^perl\((lib|strict|vars|RT)\)$' \
+| grep -v -E '^perl\(RT::' \
+| grep -v -E '^perl\(FS::' \
+| sort -u
+grep handler.pl %{_tmppath}/filelist | xargs %{_rpmlibdir}/perldeps.pl --requires \
+| grep -v -E '^perl\((lib|strict|vars|RT)\)$' \
+| grep -v -E '^perl\(RT::' \
+| sort -u
+EOF
+
+%define __find_provides %{_rpmlibdir}/rpmdeps --provides
+%define __find_requires %{_builddir}/%{name}-%{version}/%{name}-req
+%{__chmod} +x %{__find_requires}
+%define _use_internal_dependency_generator 0
+
+%build
+
+# False laziness...
+# The htmlman target now makes wiki documentation. Let's pretend we made it.
+touch htmlman
+%{__make} alldocs
+
+#perl -pi -e 's|%%%%%%VERSION%%%%%%|%{version}|g' FS/bin/*
+cd FS
+if [ "%{_vendor}" = "suse" ]; then
+ CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL
+else
+ CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL PREFIX=$RPM_BUILD_ROOT%{_prefix} SITELIBEXP=$RPM_BUILD_ROOT%{perl_sitelib} SITEARCHEXP=$RPM_BUILD_ROOT%{perl_sitearch} INSTALLSCRIPT=$RPM_BUILD_ROOT%{_bindir}
+fi
+%{__make} OPTIMIZE="$RPM_OPT_FLAGS"
+cd ..
+%{__make} perl-modules RT_ENABLED=%{rt_enabled} FREESIDE_CACHE=%{freeside_cache} FREESIDE_CONF=%{freeside_conf} FREESIDE_EXPORT=%{freeside_export} FREESIDE_LOCK=%{freeside_lock} FREESIDE_LOG=%{freeside_log}
+touch perl-modules
+
+cd fs_selfservice/FS-SelfService
+if [ "%{_vendor}" = "suse" ]; then
+ CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL
+else
+ CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL PREFIX=$RPM_BUILD_ROOT%{_prefix} SITELIBEXP=$RPM_BUILD_ROOT%{perl_sitelib} SITEARCHEXP=$RPM_BUILD_ROOT%{perl_sitearch} INSTALLSCRIPT=$RPM_BUILD_ROOT%{_sbindir}
+fi
+%{__make} OPTIMIZE="$RPM_OPT_FLAGS"
+cd ../..
+
+%install
+%{__rm} -rf %{buildroot}
+
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_document_root}
+
+touch install-perl-modules perl-modules
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_cache}
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_conf}
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_export}
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_lock}
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_log}
+for DBTYPE in %{db_types}; do
+ %{__mkdir_p} $RPM_BUILD_ROOT/tmp
+ [ -d $RPM_BUILD_ROOT%{freeside_conf}/default_conf ] && %{__rm} -rf $RPM_BUILD_ROOT%{freeside_conf}/default_conf
+ %{__make} create-config DB_TYPE=$DBTYPE DATASOURCE=DBI:$DBTYPE:dbname=%{name} RT_ENABLED=%{rt_enabled} FREESIDE_CACHE=$RPM_BUILD_ROOT%{freeside_cache} FREESIDE_CONF=$RPM_BUILD_ROOT/tmp FREESIDE_EXPORT=$RPM_BUILD_ROOT%{freeside_export} FREESIDE_LOCK=$RPM_BUILD_ROOT%{freeside_lock} FREESIDE_LOG=$RPM_BUILD_ROOT%{freeside_log} DIST_CONF=$RPM_BUILD_ROOT%{freeside_conf}/default_conf
+ %{__mv} $RPM_BUILD_ROOT/tmp/secrets $RPM_BUILD_ROOT%{freeside_conf}
+ %{__rm} -rf $RPM_BUILD_ROOT/tmp
+done
+%{__rm} install-perl-modules perl-modules $RPM_BUILD_ROOT%{freeside_conf}/default_conf/ticket_system
+
+touch docs
+%{__perl} -pi -e "s|%%%%%%FREESIDE_DOCUMENT_ROOT%%%%%%|%{freeside_document_root}|g" htetc/handler.pl
+%{__make} install-docs RT_ENABLED=%{rt_enabled} PREFIX=$RPM_BUILD_ROOT%{_prefix} TEMPLATE=mason FREESIDE_DOCUMENT_ROOT=$RPM_BUILD_ROOT%{freeside_document_root} MASON_HANDLER=$RPM_BUILD_ROOT%{freeside_conf}/handler.pl MASONDATA=$RPM_BUILD_ROOT%{freeside_cache}/masondata
+%{__perl} -pi -e "s|$RPM_BUILD_ROOT||g" $RPM_BUILD_ROOT%{freeside_conf}/handler.pl
+%{__rm} docs
+
+# Install the init script
+%{__mkdir_p} $RPM_BUILD_ROOT%{_initrddir}
+%{__make} install-init INSTALLGROUP=root INIT_FILE=$RPM_BUILD_ROOT%{_initrddir}/%{name} QUEUED_USER=%{fs_queue_user} SELFSERVICE_USER=%{fs_selfservice_user} SELFSERVICE_MACHINES= INIT_INSTALL=
+%{__perl} -pi -e "\
+ s|/etc/default|/etc/sysconfig|g;\
+ " $RPM_BUILD_ROOT%{_initrddir}/%{name}
+
+# Install the HTTPD configuration snippet for HTML::Mason
+%{__mkdir_p} $RPM_BUILD_ROOT%{apache_confdir}
+%{__make} install-apache FREESIDE_DOCUMENT_ROOT=%{freeside_document_root} RT_ENABLED=%{rt_enabled} APACHE_CONF=$RPM_BUILD_ROOT%{apache_confdir} APACHE_VERSION=%{apache_version} FREESIDE_CONF=%{freeside_conf} MASON_HANDLER=%{freeside_conf}/handler.pl
+%{__perl} -pi -e 'print "Alias /%{name} %{freeside_document_root}\n\n" if /^<Directory/;' $RPM_BUILD_ROOT%{apache_confdir}/freeside-*.conf
+%{__perl} -pi -e 'print "SSLRequireSSL\n" if /^AuthName/i;' $RPM_BUILD_ROOT%{apache_confdir}/freeside-*.conf
+
+# Make lists of the database-specific configuration files
+for DBTYPE in %{db_types}; do
+ echo "%%attr(600,freeside,freeside) %{freeside_conf}/secrets" > %{name}-%{version}-%{release}-$DBTYPE-filelist
+ for DIR in `echo -e "%{freeside_conf}\n%{freeside_cache}\n%{freeside_export}\n" | sort | uniq`; do
+ find $RPM_BUILD_ROOT$DIR -type f -print | \
+ grep ":$DBTYPE:" | \
+ sed "s@^$RPM_BUILD_ROOT@%%attr(640,freeside,freeside) %%config(noreplace) @g" >> %{name}-%{version}-%{release}-$DBTYPE-filelist
+ find $RPM_BUILD_ROOT$DIR -type d -print | \
+ grep ":$DBTYPE:" | \
+ sed "s@^$RPM_BUILD_ROOT@%%attr(711,freeside,freeside) %%dir @g" >> %{name}-%{version}-%{release}-$DBTYPE-filelist
+ done
+ if [ "$(cat %{name}-%{version}-%{release}-$DBTYPE-filelist)X" = "X" ] ; then
+ echo "ERROR: EMPTY FILE LIST"
+ exit 1
+ fi
+done
+
+# Make a list of the Mason files before adding self-service, etc.
+echo "%attr(-,freeside,freeside) %{freeside_conf}/handler.pl" > %{name}-%{version}-%{release}-mason-filelist
+find $RPM_BUILD_ROOT%{freeside_document_root} -type f -print | \
+ sed "s@^$RPM_BUILD_ROOT@@g" >> %{name}-%{version}-%{release}-mason-filelist
+if [ "$(cat %{name}-%{version}-%{release}-mason-filelist)X" = "X" ] ; then
+ echo "ERROR: EMPTY FILE LIST"
+ exit 1
+fi
+
+# Install all the miscellaneous binaries into /usr/share or similar
+%{__mkdir_p} $RPM_BUILD_ROOT%{_datadir}/%{name}-%{version}/bin
+%{__install} bin/* $RPM_BUILD_ROOT%{_datadir}/%{name}-%{version}/bin
+
+%{__mkdir_p} $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig
+%{__install} %{rpmfiles}/freeside.sysconfig $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/%{name}
+
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi/images
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi/misc
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/php
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/templates
+%{__install} fs_selfservice/FS-SelfService/cgi/{*.cgi,*.html,*.gif} $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi
+%{__install} fs_selfservice/FS-SelfService/cgi/images/* $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi/images
+%{__install} fs_selfservice/FS-SelfService/cgi/misc/* $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/cgi/misc
+%{__install} fs_selfservice/php/* $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/php
+%{__install} fs_selfservice/FS-SelfService/*.template $RPM_BUILD_ROOT%{freeside_selfservice_document_root}/templates
+
+# Install the main billing server Perl files
+cd FS
+eval `perl '-V:installarchlib'`
+%{__mkdir_p} $RPM_BUILD_ROOT$installarchlib
+%makeinstall PREFIX=$RPM_BUILD_ROOT%{_prefix}
+%{__rm} -f `find $RPM_BUILD_ROOT -type f -name perllocal.pod -o -name .packlist`
+
+[ -x %{_rpmlibdir}/brp-compress ] && %{_rpmlibdir}/brp-compress
+
+find $RPM_BUILD_ROOT%{_prefix} -type f -print | \
+ grep -v '/etc/freeside/conf' | \
+ grep -v '/etc/freeside/secrets' | \
+ sed "s@^$RPM_BUILD_ROOT@@g" > %{name}-%{version}-%{release}-filelist
+if [ "$(cat %{name}-%{version}-%{release}-filelist)X" = "X" ] ; then
+ echo "ERROR: EMPTY FILE LIST"
+ exit 1
+fi
+cd ..
+
+# Install the self-service interface Perl files
+cd fs_selfservice/FS-SelfService
+%{__mkdir_p} $RPM_BUILD_ROOT%{_prefix}/local/bin
+%makeinstall
+%{__rm} -f `find $RPM_BUILD_ROOT -type f -name perllocal.pod -o -name .packlist`
+
+[ -x %{_rpmlibdir}/brp-compress ] && %{_rpmlibdir}/brp-compress
+
+find $RPM_BUILD_ROOT%{_prefix} -type f -print | \
+ grep -v '/etc/freeside/conf' | \
+ grep -v '/etc/freeside/secrets' | \
+ sed "s@^$RPM_BUILD_ROOT@@g" > %{name}-%{version}-%{release}-temp-filelist
+cat ../../FS/%{name}-%{version}-%{release}-filelist %{name}-%{version}-%{release}-temp-filelist | sort | uniq -u > %{name}-%{version}-%{release}-selfservice-core-filelist
+if [ "$(cat %{name}-%{version}-%{release}-selfservice-core-filelist)X" = "X" ] ; then
+ echo "ERROR: EMPTY FILE LIST"
+ exit 1
+fi
+cd ../..
+
+# Install the Apache configuration file for self-service
+%{__install} %{rpmfiles}/freeside-selfservice.conf $RPM_BUILD_ROOT%{apache_confdir}/%{name}-selfservice.conf
+%{__perl} -pi -e "s|%%%%%%FREESIDE_SELFSERVICE_DOCUMENT_ROOT%%%%%%|%{freeside_selfservice_document_root}|g" $RPM_BUILD_ROOT%{apache_confdir}/%{name}-selfservice.conf
+
+# This is part of Makefile's install-texmf. The rest is in triggers. These files are not in the filelist
+%{__install} -D etc/fslongtable.sty $RPM_BUILD_ROOT%{texmflocal}/tex/generic/fslongtable.sty
+
+%pre
+if ! %{__id} freeside &>/dev/null; then
+%if "%{_vendor}" == "suse"
+ /usr/sbin/groupadd freeside
+%endif
+ /usr/sbin/useradd -m freeside
+fi
+
+%pre mason
+if ! %{__id} freeside &>/dev/null; then
+%if "%{_vendor}" == "suse"
+ /usr/sbin/groupadd freeside
+%endif
+ /usr/sbin/useradd -m freeside
+fi
+
+%pre postgresql
+if ! %{__id} freeside &>/dev/null; then
+%if "%{_vendor}" == "suse"
+ /usr/sbin/groupadd freeside
+%endif
+ /usr/sbin/useradd -m freeside
+fi
+
+%pre mysql
+if ! %{__id} freeside &>/dev/null; then
+%if "%{_vendor}" == "suse"
+ /usr/sbin/groupadd freeside
+%endif
+ /usr/sbin/useradd -m freeside
+fi
+
+%pre selfservice-cgi
+if ! %{__id} freeside &>/dev/null; then
+%if "%{_vendor}" == "suse"
+ /usr/sbin/groupadd freeside
+%endif
+ /usr/sbin/useradd -m freeside
+fi
+
+%post
+if [ -x /sbin/chkconfig ]; then
+ /sbin/chkconfig --add freeside
+fi
+#if [ $1 -eq 2 -a -x /usr/bin/freeside-upgrade ]; then
+# /usr/bin/freeside-upgrade
+#fi
+
+%post postgresql
+if [ -f %{freeside_conf}/secrets ]; then
+ perl -p -i.fsbackup -e 's/^DBI:.*?:/DBI:Pg:/' %{freeside_conf}/secrets
+fi
+
+%post mysql
+if [ -f %{freeside_conf}/secrets ]; then
+ perl -p -i.fsbackup -e 's/^DBI:.*?:/DBI:mysql:/' %{freeside_conf}/secrets
+fi
+
+%post mason
+# Make local httpd run with User/Group = freeside
+if [ -f %{apache_conffile} ]; then
+%if "%{_vendor}" != "suse"
+ perl -p -i.fsbackup -e 's/^(User|Group) .*/$1 freeside/' %{apache_conffile}
+%else
+ perl -p -i.fsbackup -e 's/^(User) .*/$1 freeside/' %{apache_conffile}
+%endif
+fi
+# Fix up environment so pslatex will run
+%if "%{_vendor}" == "suse"
+if ! %{__grep} TEXINPUTS /etc/profile.local >/dev/null; then
+ echo "unset TEXINPUTS" >>/etc/profile.local
+fi
+if ! %{__grep} TEXINPUTS /etc/init.d/apache2 >/dev/null; then
+ perl -p -i.fsbackup -e 'print "unset TEXINPUTS\n\n" if /^httpd_conf\s*=\s*/;' /etc/init.d/apache2
+fi
+%endif
+
+%triggerin -- tetex
+#texhash `kpsewhich -expand-var \$TEXMFLOCAL`
+texhash %{texmflocal}
+
+%clean
+%{__rm} -rf %{buildroot}
+
+%files -f FS/%{name}-%{version}-%{release}-filelist
+%attr(0711,root,root) %{_initrddir}/%{name}
+%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/sysconfig/%{name}
+%defattr(-,freeside,freeside,-)
+%doc README INSTALL CREDITS AGPL
+%attr(-,freeside,freeside) %dir %{freeside_conf}
+%attr(-,freeside,freeside) %dir %{freeside_lock}
+%attr(-,freeside,freeside) %dir %{freeside_log}
+%attr(0711,freeside,freeside) %config(noreplace) %{freeside_conf}/default_conf
+%attr(0644,freeside,freeside) %config(noreplace) %{freeside_conf}/default_conf/*
+%attr(444,root,root) %{texmflocal}/tex/generic/fslongtable.sty
+
+%files mason -f %{name}-%{version}-%{release}-mason-filelist
+%defattr(-, freeside, freeside, 0755)
+%attr(-,freeside,freeside) %{freeside_cache}/masondata
+%attr(0644,root,root) %config(noreplace) %{apache_confdir}/%{name}-base%{apache_version}.conf
+
+%files postgresql -f %{name}-%{version}-%{release}-Pg-filelist
+
+%files mysql -f %{name}-%{version}-%{release}-mysql-filelist
+
+%files selfservice
+%defattr(-, freeside, freeside, 0644)
+%attr(0644,root,root) %config(noreplace) %{apache_confdir}/%{name}-selfservice.conf
+
+%files selfservice-core -f fs_selfservice/FS-SelfService/%{name}-%{version}-%{release}-selfservice-core-filelist
+%defattr(-, freeside, freeside, 0644)
+%attr(-,freeside,freeside) %dir %{freeside_socket}
+%attr(-,freeside,freeside) %dir %{freeside_lock}
+%attr(-,freeside,freeside) %dir %{freeside_log}
+
+%files selfservice-cgi
+%defattr(-, freeside, freeside, 0644)
+%attr(0711,freeside,freeside) %{freeside_selfservice_document_root}/cgi
+%attr(0644,freeside,freeside) %{freeside_selfservice_document_root}/templates
+
+%files selfservice-php
+%defattr(-, freeside, freeside, 0644)
+%attr(0755,freeside,freeside) %{freeside_selfservice_document_root}/php
+
+%changelog
+* Thu Jun 11 2009 Richard Siddall <richard.siddall@elirion.net> - 1.9-8
+- Since configuration is now kept in the RDBMS, don't install a configuration folder
+
+* Mon Dec 22 2008 Richard Siddall <richard.siddall@elirion.net> - 1.9-5
+- Modifications to make self-service work if you really insist on installing it on the same machine as Freeside
+
+* Tue Dec 9 2008 Richard Siddall <richard.siddall@elirion.net> - 1.9-4
+- Cleaning up after rpmlint
+
+* Tue Aug 26 2008 Richard Siddall <richard.siddall@elirion.net> - 1.9-3
+- More revisions for self-service interface
+
+* Sat Aug 23 2008 Richard Siddall <richard.siddall@elirion.net> - 1.7.3-2
+- Revisions for self-service interface
+- RT support is still missing
+
+* Sun Jul 8 2007 Richard Siddall <richard.siddall@elirion.net> - 1.7.3
+- Updated for upcoming Freeside 1.7.3
+- RT support is still missing
+
+* Fri Jun 29 2007 Richard Siddall <richard.siddall@elirion.net> - 1.7.2
+- Updated for Freeside 1.7.2
+- Removed support for Apache::ASP
+
+* Wed Oct 12 2005 Richard Siddall <richard.siddall@elirion.net> - 1.5.7
+- Added self-service package
+
+* Sun Feb 06 2005 Richard Siddall <richard.siddall@elirion.net> - 1.5.0pre6-1
+- Initial package
diff --git a/rpm/freeside.sysconfig b/rpm/freeside.sysconfig
new file mode 100644
index 0000000..baa0462
--- /dev/null
+++ b/rpm/freeside.sysconfig
@@ -0,0 +1,5 @@
+QUEUED_USER=fs_queue
+#RADACCTD_USER=
+#
+SELFSERVICE_USER=fs_selfservice
+#SELFSERVICE_MACHINES=
diff --git a/rpm/rpm2Bundle b/rpm/rpm2Bundle
new file mode 100755
index 0000000..1bc8771
--- /dev/null
+++ b/rpm/rpm2Bundle
@@ -0,0 +1,111 @@
+#!/usr/bin/perl -Tw
+#
+# Make a bundle file from an RPM
+#
+use strict;
+
+$ENV{PATH} = '/bin:/usr/bin/';
+
+my $verbose = 0;
+
+# These are Perl dependencies that should be ignored/suppressed
+my %suppress;
+
+foreach (qw/strict subs vars base lib warnings FS/) {
+ $suppress{$_} = $_;
+}
+
+# These are Perl modules corresponding to RPM names.
+# Add entries when the mapping isn't simply "remove leading 'perl-' and replace - with ::"
+my %rpm2mod=(
+ 'DBD-MySQL' => 'DBD::mysql',
+);
+
+## These are root packages that shouldn't be cited multiple times
+## Should figure this out with CPAN
+#my %rootpkgs;
+#
+#foreach (qw/FS/) {
+# $rootpkgs{$_} = 1;
+#}
+
+foreach my $rawrpm (@ARGV) {
+ $rawrpm =~ /^([-\.a-z0-9\/]+)\s*$/i;
+ my $rpm = $1 or next;
+ my @parts = split '/', $rpm;
+ my $name = pop @parts;
+ my $version = 0.01;
+ if ($name =~ m<([^/]+?)[-._]?v?-?([-_.\d]+[a-z]*?\d*)\.\w+\.rpm$>) {
+ $name = $1;
+ $version = $2;
+ }
+ print STDERR "rpm: $rpm ($name, $version)\n";
+ my @deps = sort `rpm -qp --requires $rpm`;
+
+ my %mods;
+
+ foreach (@deps) {
+ if (/^perl\((.*?)\)\s*((>=|=|<=)\s+([\d\.]+))?$/
+ || /^perl-(.*?)\s*((>=|=|<=)\s+([\d\.]+))?$/) {
+ my ($mod, $rel, $ver) = ($1, $3, $4);
+ if (/^perl-/) {
+ print STDERR "\"$mod\"\n" if $verbose;
+ $mod = $rpm2mod{$mod} if exists($rpm2mod{$mod});
+ $mod =~ s/-/::/g
+ }
+ next if exists($suppress{$mod});
+ my @parts = split /::/, $mod;
+ if (scalar @parts > 1) {
+ next if exists($suppress{$parts[0]});
+ }
+ if ($verbose) {
+ print STDERR "$mod";
+ print STDERR " $rel $ver" if $ver;
+ print STDERR "\n";
+ }
+ $mods{$mod} = $ver ? $ver : undef; # Should also save $rel
+ }
+ }
+
+ my $hdr =<<END;
+# -*- perl -*-
+
+package Bundle::$name;
+
+\$VERSION = '$version';
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bundle::$name - A bundle to install prerequisites for the $name package
+
+=head1 SYNOPSIS
+
+C<perl -MCPAN -e 'install Bundle::$name'>
+
+=head1 CONTENTS
+
+END
+
+ my $ftr =<<END;
+=head1 DESCRIPTION
+
+This bundle includes all prerequisites needed by the $name package.
+
+=cut
+END
+
+ print $hdr;
+ foreach (sort keys %mods) {
+ print "$_";
+ print " $mods{$_}" if exists($mods{$_}) && $mods{$_};
+ print " -\n\n";
+ }
+ print $ftr;
+}
+
+1;
+
diff --git a/rt/.gitignore b/rt/.gitignore
deleted file mode 100644
index baa3d63..0000000
--- a/rt/.gitignore
+++ /dev/null
@@ -1,42 +0,0 @@
-bin/mason_handler.fcgi
-bin/mason_handler.scgi
-bin/mason_handler.svc
-bin/rt-crontool
-bin/rt-mailgate
-bin/rt
-bin/standalone_httpd
-bin/webmux.pl
-etc/RT_Config.pm
-etc/upgrade/3.8-branded-queues-extension
-etc/upgrade/3.8-ical-extension
-lib/RT.pm
-Makefile
-t/data/gnupg/keyrings/random_seed
-t/data/configs/apache2.2+mod_perl.conf
-sbin/rt-attributes-viewer
-sbin/rt-clean-sessions
-sbin/rt-dump-database
-sbin/rt-email-dashboards
-sbin/rt-email-digest
-sbin/rt-email-group-admin
-sbin/rt-server
-sbin/rt-setup-database
-sbin/rt-shredder
-sbin/rt-test-dependencies
-sbin/rt-validator
-var/mason_data/
-autom4te.cache
-configure
-config.status
-config.log
-config.pld
-*~
-*.swp
-*.swo
-
-# RT4 junk
-META.yml
-inc/
-rt4
-var/
-etc/site_config.yml
diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED
new file mode 100644
index 0000000..d1f2146
--- /dev/null
+++ b/rt/FREESIDE_MODIFIED
@@ -0,0 +1,50 @@
+ sbin/rt-setup-database.in # just a small password bugfix now
+config.layout
+config.layout.in
+ etc/RT_Config.pm.in
+ etc/RT_Config.pm
+ etc/RT_SiteConfig.pm
+ lib/RT/Config.pm
+lib/RT/Interface/Web_Vendor.pm
+ lib/RT/Record.pm
+lib/RT/SearchBuilder.pm #need DBIx::SearchBuilder >= 1.36 for Pg 8.1+
+lib/RT/Transaction_Overlay.pm
+ lib/RT/Ticket_Overlay.pm
+ lib/RT/Users_Overlay.pm
+ lib/RT/Groups_Overlay.pm
+lib/RT/URI/freeside.pm
+lib/RT/URI/freeside/Internal.pm
+lib/RT/URI/freeside/XMLRPC.pm
+ share/html/Admin/Users/Modify.html
+ share/html/Elements/ColumnMap
+ share/html/Elements/CollectionList
+ share/html/Elements/Header
+ share/html/Elements/PageLayout
+ #html/Elements/QuickCreate
+ share/html/Elements/SelectDate
+ share/html/Elements/Footer
+ html/Ticket/Create.html #XXX TODO
+ share/html/Search/Build.html
+ share/html/Ticket/Display.html
+share/html/Ticket/Elements/AddCustomers
+share/html/Ticket/Elements/EditCustomers
+share/html/Ticket/Elements/ShowCustomers
+ share/html/Ticket/Elements/ShowSummary
+ share/html/Ticket/Elements/ShowTransactionAttachments
+ share/html/Ticket/Elements/Tabs
+share/html/Ticket/ModifyCustomers.html
+ html/NoAuth/css/3.5-default/main.css
+ html/NoAuth/css/3.5-default/misc.css
+ html/NoAuth/css/3.5-default/titlebox.css
+share/html/NoAuth/css/freeside2.1/freeside.css
+share/html/NoAuth/css/freeside2.1/nav.css
+share/html/NoAuth/css/freeside2.1/base.css
+share/html/NoAuth/css/freeside2.1/layout.css
+
+share/html/Elements/AddCustomers
+share/html/Elements/EditCustomers
+
+ share/html/User/Prefs.html
+ share/html/Prefs/SearchOptions.html
+
+ share/html/Widgets/TitleBoxEnd
diff --git a/rt/HOWTO/README b/rt/HOWTO/README
deleted file mode 100644
index 942096b..0000000
--- a/rt/HOWTO/README
+++ /dev/null
@@ -1,14 +0,0 @@
-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
deleted file mode 100644
index de31645..0000000
--- a/rt/HOWTO/change.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-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
deleted file mode 100644
index 285041c..0000000
--- a/rt/HOWTO/release.txt
+++ /dev/null
@@ -1,124 +0,0 @@
-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
deleted file mode 100644
index 06babfd..0000000
--- a/rt/HOWTO/version-control.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-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
index 7e5554e..9d5fde0 100644
--- a/rt/Makefile
+++ b/rt/Makefile
@@ -52,9 +52,9 @@
PERL = /usr/bin/perl
-INSTALL = /opt/local/bin/ginstall -c
+INSTALL = /usr/bin/install -c
-RT_LAYOUT = relative
+RT_LAYOUT = Freeside
CONFIG_FILE_PATH = /opt/rt3/etc
CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm
@@ -70,7 +70,7 @@ 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 = www
+RTGROUP = freeside
# User which should own rt binaries.
@@ -82,11 +82,11 @@ LIBS_OWNER = root
# Group that should own all of RT's libraries, generally root.
LIBS_GROUP = bin
-WEB_USER = www
-WEB_GROUP = www
+WEB_USER = freeside
+WEB_GROUP = freeside
-APACHECTL = /usr/sbin/apachectl
+APACHECTL =
# {{{ Files and directories
@@ -112,9 +112,9 @@ LOCAL_PLUGIN_PATH = /opt/rt3/local/plugins
LOCAL_ETC_PATH = /opt/rt3/local/etc
LOCAL_LIB_PATH = /opt/rt3/local/lib
LOCAL_LEXICON_PATH = /opt/rt3/local/po
-MASON_HTML_PATH = /opt/rt3/share/html
+MASON_HTML_PATH = /var/www/freeside/rt
MASON_LOCAL_HTML_PATH = /opt/rt3/local/html
-MASON_DATA_PATH = /opt/rt3/var/mason_data
+MASON_DATA_PATH = /usr/local/etc/freeside/masondata
MASON_SESSION_PATH = /opt/rt3/var/session_data
RT_LOG_PATH = /opt/rt3/var/log
@@ -193,7 +193,7 @@ ETC_FILES = acl.Informix \
# {{{ Web frontend
-WEB_HANDLER = fastcgi
+WEB_HANDLER = modperl2
# }}}
@@ -205,7 +205,7 @@ WEB_HANDLER = fastcgi
# "Pg" is known to work
# "Informix" is known to work
-DB_TYPE = mysql
+DB_TYPE = Pg
# Set DBA to the name of a unix account with the proper permissions and
# environment to run your commandline SQL sbin
@@ -217,7 +217,7 @@ DB_TYPE = mysql
# For Oracle, you want 'system'
# For Informix, you want 'informix'
-DB_DBA = root
+DB_DBA = freeside
DB_HOST = localhost
@@ -243,9 +243,9 @@ 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
+DB_DATABASE = freeside
+DB_RT_USER = freeside
+DB_RT_PASS =
# }}}
@@ -316,7 +316,7 @@ fixperms:
chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH)
chmod 0755 $(DESTDIR)$(RT_ETC_PATH)
- cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES)
+ cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) || true
#TODO: the config file should probably be able to have its
# owner set separately from the binaries.
diff --git a/rt/Makefile.in b/rt/Makefile.in
index 898dc8a..bef1025 100644
--- a/rt/Makefile.in
+++ b/rt/Makefile.in
@@ -316,7 +316,7 @@ fixperms:
chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH)
chmod 0755 $(DESTDIR)$(RT_ETC_PATH)
- cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES)
+ cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) || true
#TODO: the config file should probably be able to have its
# owner set separately from the binaries.
diff --git a/rt/bin/mason_handler.fcgi b/rt/bin/mason_handler.fcgi
index 0439a3b..881d638 100755
--- a/rt/bin/mason_handler.fcgi
+++ b/rt/bin/mason_handler.fcgi
@@ -25,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
diff --git a/rt/bin/mason_handler.scgi b/rt/bin/mason_handler.scgi
index b576966..1a497de 100755
--- a/rt/bin/mason_handler.scgi
+++ b/rt/bin/mason_handler.scgi
@@ -25,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
diff --git a/rt/bin/mason_handler.svc b/rt/bin/mason_handler.svc
index 3cde20c..4276b6e 100644
--- a/rt/bin/mason_handler.svc
+++ b/rt/bin/mason_handler.svc
@@ -25,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
diff --git a/rt/bin/rt b/rt/bin/rt
deleted file mode 100755
index 1174eb4..0000000
--- a/rt/bin/rt
+++ /dev/null
@@ -1,2586 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-# Designed and implemented for Best Practical Solutions, LLC by
-# Abhijit Menon-Sen <ams@wiw.org>
-
-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 Text::ParseWords;
-use HTTP::Request::Common;
-use HTTP::Headers;
-use Term::ReadLine;
-use Time::Local; # used in prettyshow
-
-# strong (GSSAPI based) authentication is supported if the server does provide
-# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
-# it can be suppressed by setting externalauth=0 (default is undef)
-eval { require GSSAPI };
-my $no_strong_auth = 'missing perl module GSSAPI';
-if ( ! $@ ) {
- eval {require LWP::Authen::Negotiate};
- $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0;
-}
-
-# We derive configuration information from hardwired defaults, dotfiles,
-# and the RT* environment variables (in increasing order of precedence).
-# 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/',
- query => "Status!='resolved' and Status!='rejected'",
- orderby => 'id',
- queue => undef,
-# to protect against unlimited searches a better choice would be
-# queue => 'Unknown_Queue',
-# setting externalauth => undef will try GSSAPI auth if the corresponding perl
-# modules are installed, externalauth => 0 is the backward compatible choice
- externalauth => 0,
- ),
- config_from_file($ENV{RTCONFIG} || ".rtrc"),
- config_from_env()
-);
-my $session = new Session("$HOME/.rt_sessions");
-my $REST = "$config{server}/REST/1.0";
-$no_strong_auth = 'switched off by externalauth=0'
- if defined $config{externalauth};
-
-
-my $prompt = 'rt> ';
-
-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 $CF_name = '[\sa-z0-9_ :()/-]+';
-my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})';
-my $label = '[a-zA-Z0-9@_.+-]+';
-my $labels = "(?:$label,)*$label";
-my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
-
-# Our command line looks like this:
-#
-# 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"],
- shell => ["shell"],
- 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"],
- take => ["take", "steal", "untake"],
- quit => ["quit", "exit"],
- setcommand => ["del", "delete", "give", "res", "resolve",
- "subject"],
-);
-
-my %actions;
-foreach my $fn (keys %handlers) {
- foreach my $alias (@{ $handlers{$fn} }) {
- $actions{$alias} = \&{"$fn"};
- }
-}
-
-# Once we find and call an appropriate handler, we're done.
-
-sub handler {
- my $action;
-
- push @ARGV, 'shell' if (!@ARGV); # default to shell mode
- shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt'
- if (@ARGV && exists $actions{$ARGV[0]}) {
- $action = shift @ARGV;
- return $actions{$action}->($action);
- }
- else {
- print STDERR "rt: Unknown command '@ARGV'.\n";
- print STDERR "rt: For help, run 'rt help'.\n";
- return 1;
- }
-}
-
-exit handler();
-
-# Handler functions.
-# ------------------
-#
-# The following subs are handlers for each entry in %actions.
-
-sub shell {
- $|=1;
- my $term = new Term::ReadLine 'RT CLI';
- while ( defined ($_ = $term->readline($prompt)) ) {
- next if /^#/ || /^\s*$/;
-
- @ARGV = shellwords($_);
- handler();
- }
-}
-
-sub version {
- print "rt $VERSION\n";
- return 0;
-}
-
-sub logout {
- submit("$REST/logout") if defined $session->cookie;
- return 0;
-}
-
-sub quit {
- logout();
- exit;
-}
-
-my %help;
-sub help {
- my ($action, $type, $rv) = @_;
- $rv = defined $rv ? $rv : 0;
- my $key;
-
- # What help topics do we know about?
- if (!%help) {
- 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";
- return $rv;
-}
-
-# Displays a list of objects that match some specified condition.
-
-sub list {
- my ($q, $type, %data);
- my $orderby = $config{orderby};
-
- if ($config{orderby}) {
- $data{orderby} = $config{orderby};
- }
- my $bad = 0;
- my $rawprint = 0;
- my $reverse_sort = 0;
- my $queue = $config{queue};
-
- 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$/) {
- $data{'orderby'} = shift @ARGV;
- }
- elsif (/^-([isl])$/) {
- $data{format} = $1;
- $rawprint = 1;
- }
- elsif (/^-q$/) {
- $queue = shift @ARGV;
- }
- elsif (/^-r$/) {
- $reverse_sort = 1;
- }
- elsif (/^-f$/) {
- if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
- whine "No valid field list in '-f $ARGV[0]'.";
- $bad = 1; last;
- }
- $data{fields} = shift @ARGV;
- $data{format} = 's' if ! $data{format};
- $rawprint = 1;
- }
- elsif (!defined $q && !/^-/) {
- $q = $_;
- }
- else {
- my $datum = /^-/ ? "option" : "argument";
- whine "Unrecognised $datum '$_'.";
- $bad = 1; last;
- }
- }
- if ( ! $rawprint and ! exists $data{format} ) {
- $data{format} = 'l';
- }
- if ( $reverse_sort and $data{orderby} =~ /^-/ ) {
- $data{orderby} =~ s/^-/+/;
- } elsif ($reverse_sort) {
- $data{orderby} =~ s/^\+?(.*)/-$1/;
- }
-
- if (!defined $q) {
- $q = $config{query};
- }
-
- $q =~ s/^#//; # get rid of leading hash
- if ($q =~ /^\d+$/) {
- # only digits, must be an id, formulate a correct query
- $q = "id=$q" if $q =~ /^\d+$/;
- } else {
- # a string only, take it as an owner or requestor (quoting done later)
- $q = "(Owner=$q or Requestor like $q) and $config{query}"
- if $q =~ /^[\w\-]+$/;
- # always add a query for a specific queue or (comma separated) queues
- $queue =~ s/,/ or Queue=/g if $queue;
- $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i
- and $q !~ /id\s*=/i;
- }
- # correctly quote strings in a query
- $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g;
-
- $type ||= "ticket";
- unless ($type && defined $q) {
- my $item = $type ? "query string" : "object type";
- whine "No $item specified.";
- $bad = 1;
- }
- #return help("list", $type) if $bad;
- return suggest_help("list", $type, $bad) if $bad;
-
- print "Query:$q\n" if ! $rawprint;
- my $r = submit("$REST/search/$type", { query => $q, %data });
- if ( $rawprint ) {
- print $r->content;
- } else {
- my $forms = Form::parse($r->content);
- prettylist ($forms);
- }
- return 0;
-}
-
-# Displays selected information about a single object.
-
-sub show {
- my ($type, @objects, %data);
- my $slurped = 0;
- my $bad = 0;
- my $rawprint = 0;
- my $histspec;
-
- while (@ARGV) {
- $_ = shift @ARGV;
- s/^#// if /^#\d+/; # get rid of leading hash
- if (/^-t$/) {
- $bad = 1, last unless defined($type = get_type_argument());
- }
- elsif (/^-S$/) {
- $bad = 1, last unless get_var_argument(\%data);
- }
- elsif (/^-([isl])$/) {
- $data{format} = $1;
- $rawprint = 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;
- # option f requires short raw listing format
- $data{format} = 's';
- $rawprint = 1;
- }
- elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
- push @objects, $spc2;
- $histspec = is_object_spec("ticket/$_/history", $type);
- }
- elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) {
- push @objects, $spc3;
- $rawprint = 1 if $_ =~ /\/content$/;
- }
- elsif (my $spec = is_object_spec($_, $type)) {
- push @objects, $spec;
- $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/;
- }
- else {
- my $datum = /^-/ ? "option" : "argument";
- whine "Unrecognised $datum '$_'.";
- $bad = 1; last;
- }
- }
- if ( ! $rawprint ) {
- push @objects, $histspec if $histspec;
- $data{format} = 'l' if ! exists $data{format};
- }
-
- unless (@objects) {
- whine "No objects specified.";
- $bad = 1;
- }
- #return help("show", $type) if $bad;
- return suggest_help("show", $type, $bad) if $bad;
-
- my $r = submit("$REST/show", { id => \@objects, %data });
- my $c = $r->content;
- # if this isn't a text reply, remove the trailing newline so we
- # don't corrupt things like tarballs when people do
- # show ticket/id/attachments/id/content > foo.tar.gz
- if ($r->content_type !~ /^text\//) {
- chomp($c);
- $rawprint = 1;
- }
- if ( $rawprint ) {
- print $c;
- } else {
- # I do not know how to get more than one form correctly returned
- $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg;
- my $forms = Form::parse($c);
- prettyshow ($forms);
- }
- return 0;
-}
-
-# 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;
- s/^#// if /^#\d+/; # get rid of leading hash
-
- 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)([+-]?=)(.*)$/s) {
- 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)=(.*)$/s) {
- 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 (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) {
- push @objects, $spc2;
- }
- 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") if defined($type);
- }
- #return help($action, $type) if $bad;
- return suggest_help($action, $type, $bad) if $bad;
-
- # We need a form to make changes to. We usually ask the server for
- # one, but we can avoid that if we are fed one on STDIN, or if the
- # user doesn't want to edit the form by hand, and the command line
- # specifies only simple variable assignments. We *should* get a
- # form if we're creating a new ticket, so that the default values
- # get filled in properly.
-
- my @new_objects = grep /\/new$/, @objects;
-
- if ($input) {
- local $/ = undef;
- $text = <STDIN>;
- }
- elsif ($edit || %add || %del || !$cl || @new_objects) {
- 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;
- return 0;
- }
-
- 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;
- return 0;
- }
- }
- print $r->content;
- }
- return 0;
-}
-
-# handler for special edit commands. A valid edit command is constructed and
-# further work is delegated to the edit handler
-
-sub setcommand {
- my ($action) = @_;
- my ($id, $bad, $what);
- if ( @ARGV ) {
- $_ = shift @ARGV;
- $id = $1 if (m|^(?:ticket/)?($idlist)$|);
- }
- if ( ! $id ) {
- $bad = 1;
- whine "No ticket number specified.";
- }
- if ( @ARGV ) {
- if ($action eq 'subject') {
- my $subject = '"'.join (" ", @ARGV).'"';
- @ARGV = ();
- $what = "subject=$subject";
- } elsif ($action eq 'give') {
- my $owner = shift @ARGV;
- $what = "owner=$owner";
- }
- } else {
- if ( $action eq 'delete' or $action eq 'del' ) {
- $what = "status=deleted";
- } elsif ($action eq 'resolve' or $action eq 'res' ) {
- $what = "status=resolved";
- } elsif ($action eq 'take' ) {
- $what = "owner=$config{user}";
- } elsif ($action eq 'untake') {
- $what = "owner=Nobody";
- }
- }
- if (@ARGV) {
- $bad = 1;
- whine "Extraneous arguments for action $action: @ARGV.";
- }
- if ( ! $what ) {
- $bad = 1;
- whine "unrecognized action $action.";
- }
- return help("edit", undef, $bad) if $bad;
- @ARGV = ( $id, "set", $what );
- print "Executing: rt edit @ARGV\n";
- return edit("edit");
-}
-
-# We roll "comment" and "correspond" into the same handler.
-
-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]'.";
- return 0;
- }
- push @files, shift @ARGV;
- }
- elsif (/-([bc])/) {
- my $a = $_ eq "-b" ? \@bcc : \@cc;
- @$a = split /\s*,\s*/, shift @ARGV;
- }
- elsif (/-m/) {
- $msg = shift @ARGV;
- if ( $msg =~ /^-$/ ) {
- undef $msg;
- while (<STDIN>) { $msg .= $_ }
- }
- }
-
- 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;
- return suggest_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 || '',
- Status => ''
- }
- ];
-
- my $text = Form::compose([ $form ]);
-
- if ($edit || !$msg) {
- my $error = 0;
- my ($c, $o, $k, $e);
-
- do {
- my $ntext = vi($text);
- return 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) {
- return 0;
- }
- @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/$id/comment", \%data);
- print $r->content;
- return 0;
-}
-
-# Merge one ticket into another.
-
-sub merge {
- my @id;
- my $bad = 0;
-
- while (@ARGV) {
- $_ = shift @ARGV;
- s/^#// if /^#\d+/; # get rid of leading hash
-
- 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;
- return suggest_help("merge", "ticket", $bad) if $bad;
-
- my $r = submit("$REST/ticket/$id[0]/merge/$id[1]");
- print $r->content;
- return 0;
-}
-
-# Link one ticket to another.
-
-sub link {
- my ($bad, $del, %data) = (0, 0, ());
- my $type;
-
- my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
- ReferredToBy HasMember MemberOf);
-
- while (@ARGV && $ARGV[0] =~ /^-/) {
- $_ = shift @ARGV;
-
- if (/^-d$/) {
- $del = 1;
- }
- elsif (/^-t$/) {
- $bad = 1, last unless defined($type = get_type_argument());
- }
- else {
- whine "Unrecognised option: '$_'.";
- $bad = 1; last;
- }
- }
-
- $type = "ticket" unless $type; # default type to tickets
-
- if (@ARGV == 3) {
- my ($from, $rel, $to) = @ARGV;
- if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
- my $bad = $from =~ /^\d+$/ ? $to : $from;
- whine "Invalid $type ID '$bad' specified.";
- $bad = 1;
- }
- if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
- whine "Invalid link '$rel' for type $type specified.";
- $bad = 1;
- }
- %data = (id => $from, rel => $rel, to => $to, del => $del);
- }
- else {
- my $bad = @ARGV < 3 ? "few" : "many";
- whine "Too $bad arguments specified.";
- $bad = 1;
- }
- return suggest_help("link", $type, $bad) if $bad;
-
- my $r = submit("$REST/$type/link", \%data);
- print $r->content;
- return 0;
-}
-
-# Take/steal a ticket
-sub take {
- my ($cmd) = @_;
- my ($bad, %data) = (0, ());
-
- my $id;
-
- # get the ticket id
- if (@ARGV == 1) {
- ($id) = @ARGV;
- unless ($id =~ /^\d+$/) {
- whine "Invalid ticket ID $id specified.";
- $bad = 1;
- }
- my $form = [
- "",
- [ "Ticket", "Action" ],
- {
- Ticket => $id,
- Action => $cmd,
- Status => '',
- }
- ];
-
- my $text = Form::compose([ $form ]);
- $data{content} = $text;
- }
- else {
- $bad = @ARGV < 1 ? "few" : "many";
- whine "Too $bad arguments specified.";
- $bad = 1;
- }
- return suggest_help("take", "ticket", $bad) if $bad;
-
- my $r = submit("$REST/ticket/$id/take", \%data);
- print $r->content;
- return 0;
-}
-
-# Grant/revoke a user's rights.
-
-sub grant {
- my ($cmd) = @_;
-
- my $revoke = 0;
- while (@ARGV) {
- }
-
- $revoke = 1 if $cmd->{action} eq 'revoke';
- return 0;
-}
-
-# 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);
- my $h = HTTP::Headers->new;
-
- # 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?
- my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted';
- (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/;
- if ($config{externalauth}) {
- $h->authorization_basic($config{user}, $config{passwd} || read_passwd() );
- print " Password will be sent to $server $how\n",
- " Press CTRL-C now if you do not want to continue\n"
- if ! $config{passwd};
- } elsif ( $no_strong_auth ) {
- if (!defined $session->cookie) {
- print " Strong encryption not available, $no_strong_auth\n",
- " Password will be sent to $server $how\n",
- " Press CTRL-C now if you do not want to continue\n"
- if ! $config{passwd};
- push @$data, ( user => $config{user} );
- push @$data, ( pass => $config{passwd} || read_passwd() );
- }
- }
-
- # Now, we construct the request.
- if (@$data) {
- $req = POST($uri, $data, Content_Type => 'form-data');
- }
- else {
- $req = GET($uri);
- }
- $session->add_cookie_header($req);
- if ($config{externalauth}) {
- $req->header(%$h);
- }
-
- # Then we send the request and parse the response.
- DEBUG(3, $req->as_string);
- 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/ if ($text);
-
- # "RT/3.0.1 401 Credentials required"
- if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) {
- warn "rt: Malformed RT response from $config{server}.\n";
- warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
- exit -1;
- }
-
- # 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_(.[^;,\s]+=[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+ [^;,\s]+=[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] if $_[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 (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) {
-
- 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) = @_;
- local $_; # $_ may be aliased to a constant, from line 1163
-
- open(CFG, $file) && do {
- while (<CFG>) {
- chomp;
- next if (/^#/ || /^\s*$/);
-
- if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\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 0;
-}
-
-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+$//;
- my ( $a, $b ) = split /,/, $line, 2;
-
- while ($a) {
- no warnings 'uninitialized';
- if ( $a =~ /^'/ ) {
- my $s = $a;
- while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/
- && $a =~ /(\\)+'$/ )) {
- ( $a, $b ) = split /,/, $b, 2;
- $s .= ',' . $a;
- }
- push @words, $s;
- }
- elsif ( $a =~ /^q{/ ) {
- my $s = $a;
- while ( $a !~ /}$/ ) {
- ( $a, $b ) =
- split /,/, $b, 2;
- $s .= ',' . $a;
- }
- $s =~ s/^q{/'/;
- $s =~ s/}/'/;
- push @words, $s;
- }
- else {
- push @words, $a;
- }
- ( $a, $b ) = split /,/, $b, 2;
- }
-
-
- }
-
- return \@words;
-}
-
-# WARN: this code is duplicated in lib/RT/Interface/REST.pm
-# change both functions at once
-sub expand_list {
- my ($list) = @_;
-
- my @elts;
- foreach (split /,/, $list) {
- push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_;
- }
-
- return map $_->[0], # schwartzian transform
- sort {
- defined $a->[1] && defined $b->[1]?
- # both numbers
- $a->[1] <=> $b->[1]
- :!defined $a->[1] && !defined $b->[1]?
- # both letters
- $a->[2] cmp $b->[2]
- # mix, number must be first
- :defined $a->[1]? -1: 1
- }
- map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ],
- @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 0;
-}
-
-sub suggest_help {
- my ($action, $type, $rv) = @_;
-
- print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action;
- print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type;
- return $rv;
-}
-
-sub str2time {
- # simplified procedure for parsing date, avoid loading Date::Parse
- my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5,
- Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11);
- $_ = shift;
- my ($mon, $day, $hr, $min, $sec, $yr, $monstr);
- if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) {
- ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6);
- $mon = $month{$monstr} if exists $month{$monstr};
- } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) {
- ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6);
- }
- if ( $yr and defined $mon and $day and defined $hr and defined $sec ) {
- return timelocal($sec,$min,$hr,$day,$mon,$yr);
- } else {
- print "Unknown date format in parsedate: $_\n";
- return undef;
- }
-}
-
-sub date_diff {
- my ($old, $new) = @_;
- $new = time() if ! $new;
- $old = str2time($old) if $old !~ /^\d+$/;
- $new = str2time($new) if $new !~ /^\d+$/;
- return "???" if ! $old or ! $new;
-
- my %seconds = (min => 60,
- hr => 60*60,
- day => 60*60*24,
- wk => 60*60*24*7,
- mth => 60*60*24*30,
- yr => 60*60*24*365);
-
- my $diff = $new - $old;
- my $what = 'sec';
- my $howmuch = $diff;
- for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) {
- last if $diff < $seconds{$_};
- $what = $_;
- $howmuch = int($diff/$seconds{$_});
- }
- return "$howmuch $what";
-}
-
-sub prettyshow {
- my $forms = shift;
- my ($form) = grep { exists $_->[2]->{Queue} } @$forms;
- my $k = $form->[2];
- # dates are in local time zone
- if ( $k ) {
- print "Date: $k->{Created}\n";
- print "From: $k->{Requestors}\n";
- print "Cc: $k->{Cc}\n" if $k->{Cc};
- print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc};
- print "X-Queue: $k->{Queue}\n";
- print "Subject: [rt #$k->{id}] $k->{Subject}\n\n";
- }
- # dates in these attributes are in GMT and will be converted
- foreach my $form (@$forms) {
- my ($c, $o, $k, $e) = @$form;
- next if ! $k->{id} or exists $k->{Queue};
- if ( exists $k->{Created} ) {
- my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/);
- $m--;
- my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y));
- if ( exists $k->{Description} ) {
- print "===> $k->{Description} on $created\n";
- }
- }
- print "$k->{Content}\n" if exists $k->{Content} and
- $k->{Content} !~ /to have no content$/ and
- $k->{Type} ne 'EmailRecord';
- print "$k->{Attachments}\n" if exists $k->{Attachments} and
- $k->{Attachments};
- }
-}
-
-sub prettylist {
- my $forms = shift;
- my $heading = "Ticket Owner Queue Age Told Status Requestor Subject\n";
- $heading .= '-' x 80 . "\n";
- my (@open, @me);
- foreach my $form (@$forms) {
- my ($c, $o, $k, $e) = @$form;
- next if ! $k->{id};
- print $heading if $heading;
- $heading = '';
- my $id = $k->{id};
- $id =~ s!^ticket/!!;
- my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner};
- $owner = substr($owner, 0, 5);
- my $queue = substr($k->{Queue}, 0, 5);
- my $subject = substr($k->{Subject}, 0, 30);
- my $age = date_diff($k->{Created});
- my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told});
- my $status = substr($k->{Status}, 0, 6);
- my $requestor = substr($k->{Requestors}, 0, 9);
- my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n",
- $id, $owner, $queue, $age, $told, $status, $requestor, $subject;
- if ( $k->{Owner} eq 'Nobody' ) {
- push @open, $line;
- } elsif ($k->{Owner} eq $config{user} ) {
- push @me, $line;
- } else {
- print $line;
- }
- }
- print "No matches found\n" if $heading;
- printf "========== my %2d open tickets ==========\n", scalar @me if @me;
- print @me if @me;
- printf "========== %2d unowned tickets ==========\n", scalar @open if @open;
- print @open if @open;
-}
-
-__DATA__
-
-Title: intro
-Title: introduction
-Text:
-
- This is a command-line interface to RT 3.0 or newer.
-
- 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 usage (syntax information)
- - rt help objects (how to specify objects)
- - rt help actions (a list of possible actions)
- - rt help types (a list of object types)
-
- - 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]
- or
- rt shell
-
- 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).
-
- You may also call "rt shell", which will give you an 'rt>' prompt at
- which you can issue commands of the form "<action> [options]
- [arguments]". See "rt help shell" for details.
-
- 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)
- - rt help shell (how to use the shell)
-
---
-
-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.
- - query <RT Query> Default RT Query for list action
- - orderby <order> Default RT order for list action
- - queue <queuename> Default RT Queue for list action
- - externalauth <0|1> Use HTTP Basic authentication
- explicitely setting externalauth to 0 inhibits also GSSAPI based
- authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed
-
- Blank and #-commented lines are ignored.
-
- Sample configuration file contents:
-
- server https://rt.somewhere.com/
- # more than one queue can be given (by adding a query expression)
- queue helpdesk or queue=support
- query Status != resolved and Owner=myaccount
-
-
- Environment variables:
-
- The following environment variables override any corresponding
- values defined in configuration files:
-
- - RTUSER
- - RTPASSWD
- - RTEXTERNALAUTH
- - RTSERVER
- - RTDEBUG Numeric debug level. (Set to 3 for full logs.)
- - RTCONFIG Specifies a name other than ".rtrc" for the
- configuration file.
- - RTQUERY Default RT Query for rt list
- - RTORDERBY Default order for rt list
-
---
-
-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-10".
-
- If just a number is given as object specification it will be
- interpreted as ticket/<number>
-
- Examples:
-
- 1 # the same as ticket/1
- 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)
-
- The following actions on tickets are also possible:
-
- - comment Add comments to a ticket
- - correspond Add comments to a ticket
- - merge Merge one ticket into another
- - link Link one ticket to another
- - take Take a ticket (steal and untake are possible as well)
-
- For several edit set subcommands that are frequently used abbreviations
- have been introduced. These abbreviations are:
-
- - delete or del delete a ticket (edit set status=deleted)
- - resolve or res resolve a ticket (edit set status=resolved)
- - subject change subject of ticket (edit set subject=string)
- - give give a ticket to somebody (edit set owner=user)
-
---
-
-Title: types
-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
- - take
- - steal
- - untake
- - give
- - resolve
- - delete
- - subject
-
- 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: subject
-Text:
-
- Syntax:
-
- rt subject <id> <new subject text>
-
- Change the subject of a ticket whose ticket id is given.
-
---
-
-Title: give
-Text:
-
- Syntax:
-
- rt give <id> <accountname>
-
- Give a ticket whose ticket id is given to another user.
-
---
-
-Title: steal
-Text:
-
- rt steal <id>
-
- Steal a ticket whose ticket id is given, i.e. set the owner to myself.
-
---
-
-Title: take
-Text:
-
- Syntax:
-
- rt take <id>
-
- Take a ticket whose ticket id is given, i.e. set the owner to myself.
-
---
-
-Title: untake
-Text:
-
- Syntax:
-
- rt untake <id>
-
- Untake a ticket whose ticket id is given, i.e. set the owner to Nobody.
-
---
-
-Title: resolve
-Title: res
-Text:
-
- Syntax:
-
- rt resolve <id>
-
- Resolves a ticket whose ticket id is given.
-
---
-
-Title: delete
-Title: del
-Text:
-
- Syntax:
-
- rt delete <id>
-
- Deletes a ticket whose ticket id is given.
-
---
-
-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.
- -f <field[s] Display only the fields listed and the ticket id
-
- In addition,
-
- -o +/-<field> Orders the returned list by the specified field.
- -r reversed order (useful if a default was given)
- -q queue[s] restricts the query to the queue[s] given
- multiple queues are separated by comma
- -S var=val Submits the specified variable with the request.
- -t type Specifies the type of object to look for. (The
- default is "ticket".)
-
- Examples:
-
- rt ls "Priority > 5 and Status=new"
- rt ls -o +Subject "Priority > 5 and Status=new"
- rt ls -o -Created "Priority > 5 and Status=new"
- rt ls -i "Priority > 5"|rt edit - set status=resolved
- rt ls -t ticket "Subject like '[PATCH]%'"
- rt ls -q systems
- rt ls -f owner,subject
-
---
-
-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.
-
- If only a number is given it will be interpreted as the objects
- ticket/number and ticket/number/history
-
- This command writes a set of forms representing the requested object
- data to STDOUT.
-
- Options:
-
- The following options control how much information is displayed
- about each matching object:
-
- Without any formatting options prettyprinted output is generated.
- Giving any of the two options below reverts to raw output.
- -s Short description (history and attachments only).
- -l Longer description (history and attachments only).
-
- In addition,
- - Read IDs from STDIN instead of the command-line.
- -t type Specifies object type.
- -f a,b,c Restrict the display to the specified fields.
- -S var=val Submits the specified variable with the request.
-
- 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 ticket/3/history
- rt show -l ticket/3/history
- rt show -t user 2
- rt show 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.
-
- A purely numeric object id nnn is translated into ticket/nnn
-
- If, instead of "edit", an action of "new" or "create" is specified,
- then a new object is created. In this case, no numeric object IDs
- may be specified, but the syntax and behaviour remain otherwise
- 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 due=tomorrow
- rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved
- rt edit ticket/4 set priority=3 owner=bar@example.com \
- add cc=foo@example.com bcc=quux@example.net
- rt create -t ticket set 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 -m 'Not worth fixing.' -a stddisclaimer.h 23
-
---
-
-Title: merge
-Text:
-
- Syntax:
-
- rt merge <from-id> <to-id>
-
- Merges the first ticket specified into the second ticket specified.
-
---
-
-Title: link
-Text:
-
- Syntax:
-
- rt link [-d] <id-A> <link> <id-B>
-
- Creates (or, with -d, deletes) a link between the specified tickets.
- The link 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 links, 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?)
-
- Until it exists here a short description of important constructs:
-
- The two simple forms of query expressions are the constructs
- Attribute like Value and
- Attribute = Value or Attribute != Value
-
- Whether attributes can be matched using like or using = is built into RT.
- The attributes id, Queue, Owner Priority and Status require the = or !=
- tests.
-
- If Value is a string it must be quoted and may contain the wildcard
- character %. If the string does not contain white space, the quoting
- may however be omitted, it will be added automatically when parsing
- the input.
-
- Simple query expressions can be combined using and, or and parentheses
- can be used to group expressions.
-
- As a special case a standalone string (which would not form a correct
- query) is transformed into (Owner='string' or Requestor like 'string%')
- and added to the default query, i.e. the query is narrowed down.
-
- If no Queue=name clause is contained in the query, a default clause
- Queue=$config{queue} is added.
-
- Examples:
- Status!='resolved' and Status!='rejected'
- (Owner='myaccount' or Requestor like 'myaccount%') and Status!='resolved'
-
---
-
-Title: form
-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:
-
- Syntax:
-
- rt help <topic>
-
- Get 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:
-
- some useful examples
-
- All the following list requests will be restricted to the default queue.
- That can be changed by adding the option -q queuename
-
- List all tickets that are not rejected/resolved
- rt ls
- List all tickets that are new and do not have an owner
- rt ls "status=new and owner=nobody"
- List all tickets which I have sent or of which I am the owner
- rt ls myaccount
- List all attributes for the ticket 6977 (ls -l instead of ls)
- rt ls -l 6977
- Show the content of ticket 6977
- rt show 6977
- Show all attributes in the ticket and in the history of the ticket
- rt show -l 6977
- Comment a ticket (mail is sent to all queue watchers, i.e. AdminCc's)
- rt comment 6977
- This will open an editor and lets you add text (attribute Text:)
- Other attributes may be changed as well, but usually don't do that.
- Correspond a ticket (like comment, but mail is also sent to requestors)
- rt correspond 6977
- Edit a ticket (generic change, interactive using the editor)
- rt edit 6977
- Change the owner of a ticket non interactively
- rt edit 6977 set owner=myaccount
- or
- rt give 6977 account
- or
- rt take 6977
- Change the status of a ticket
- rt edit 6977 set status=resolved
- or
- rt resolve 6977
- Change the status of all tickets I own to resolved !!!
- rt ls -i owner=myaccount | rt edit - set status=resolved
-
---
-
-Title: shell
-Text:
-
- Syntax:
-
- rt shell
-
- Opens an interactive shell, at which you can issue commands of
- the form "<action> [options] [arguments]".
-
- To exit the shell, type "quit" or "exit".
-
- Commands can be given at the shell in the same form as they would
- be given at the command line without the leading 'rt' invocation.
-
- Example:
- $ rt shell
- rt> create -t ticket set subject='new' add cc=foo@example.com
- # Ticket 8 created.
- rt> quit
- $
-
---
-
-Title: take
-Title: untake
-Title: steal
-Text:
-
- Syntax:
-
- rt <take|untake|steal> <ticket-id>
-
- Sets the owner of the specified ticket to the current user,
- assuming said user has the bits to do so, or releases the
- ticket.
-
- 'Take' is used on tickets which are not currently owned
- (Owner: Nobody), 'steal' is used on tickets which *are*
- currently owned, and 'untake' is used to "release" a ticket
- (reset its Owner to Nobody). 'Take' cannot be used on
- tickets which are currently owned.
-
- Example:
- alice$ rt create -t ticket set subject="New ticket"
- # Ticket 7 created.
- alice$ rt take 7
- # Owner changed from Nobody to alice
- alice$ su bob
- bob$ rt steal 7
- # Owner changed from alice to bob
- bob$ rt untake 7
- # Owner changed from bob to Nobody
-
---
-
-Title: quit
-Title: exit
-Text:
-
- Use "quit" or "exit" to leave the shell. Only valid within shell
- mode.
-
- Example:
- $ rt shell
- rt> quit
- $
diff --git a/rt/bin/rt-commit-handler b/rt/bin/rt-commit-handler
index 29e443e..bf23a6c 100644
--- a/rt/bin/rt-commit-handler
+++ b/rt/bin/rt-commit-handler
@@ -26,7 +26,7 @@
# {{{ Docs
# -*-Perl-*-
#
-#ident "@(#)ccvs/contrib:$Name: $:$Id: rt-commit-handler,v 1.1 2003-07-15 13:16:15 ivan Exp $"
+#ident "@(#)ccvs/contrib:$Name: $:$Id: rt-commit-handler,v 1.2 2007-08-01 22:20:32 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
diff --git a/rt/bin/rt-commit-handler.in b/rt/bin/rt-commit-handler.in
deleted file mode 100644
index 02b01ab..0000000
--- a/rt/bin/rt-commit-handler.in
+++ /dev/null
@@ -1,846 +0,0 @@
-#!@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
index 563a272..13c11bf 100644
--- a/rt/bin/rt-crontool
+++ b/rt/bin/rt-crontool
@@ -25,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
diff --git a/rt/bin/rt-mailgate b/rt/bin/rt-mailgate
index abe7311..d9e85a7 100755
--- a/rt/bin/rt-mailgate
+++ b/rt/bin/rt-mailgate
@@ -25,7 +25,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
diff --git a/rt/bin/standalone_httpd b/rt/bin/standalone_httpd
deleted file mode 100755
index 7b44705..0000000
--- a/rt/bin/standalone_httpd
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-use warnings;
-use strict;
-
-# fix lib paths, some may be relative
-BEGIN {
- require File::Spec;
- my @libs = ("lib", "local/lib");
- my $bin_path;
-
- for my $lib (@libs) {
- unless ( File::Spec->file_name_is_absolute($lib) ) {
- unless ($bin_path) {
- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
- }
- else {
- require FindBin;
- no warnings "once";
- $bin_path = $FindBin::Bin;
- }
- }
- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
- }
- unshift @INC, $lib;
- }
-
-}
-
-use RT;
-RT::LoadConfig();
-RT->InitLogging();
-if (RT->Config->Get('DevelMode')) { require Module::Refresh; }
-
-RT::CheckPerlRequirements();
-RT->InitPluginPaths();
-
-my $explicit_port = shift @ARGV;
-my $port = $explicit_port || RT->Config->Get('WebPort') || '8080';
-
-
-require RT::Handle;
-my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
-
-unless ( $integrity ) {
- print STDERR <<EOF;
-
-RT couldn't connect to the database where tickets are stored.
-If this is a new installation of RT, you should visit the URL below
-to configure RT and initialize your database.
-
-If this is an existing RT installation, this may indicate a database
-connectivity problem.
-
-The error RT got back when trying to connect to your database was:
-
-$msg
-
-EOF
-
- require RT::Installer;
- # don't enter install mode if the file exists but is unwritable
- if (-e RT::Installer->ConfigFile && !-w _) {
- die 'Since your configuration exists ('
- . RT::Installer->ConfigFile
- . ") but is not writable, I'm refusing to do anything.\n";
- }
-
- RT->Config->Set( 'LexiconLanguages' => '*' );
- RT::I18N->Init;
-
- RT->InstallMode(1);
-} else {
- RT->ConnectToDatabase();
- RT->InitSystemObjects();
- RT->InitClasses( Heavy => 1 );
- RT->InitPlugins();
- RT->Config->PostLoadCheck();
-
- my ($status, $msg) = RT::Handle->CheckCompatibility(
- $RT::Handle->dbh, 'post'
- );
- unless ( $status ) {
- print STDERR $msg, "\n\n";
- exit -1;
- }
-}
-
-require RT::Interface::Web::Standalone;
-my $server = RT::Interface::Web::Standalone->new;
-run_server($port);
-exit 0;
-
-sub run_server {
- my $port = shift;
- $server->port($port);
- eval { $server->run() };
-
- if ( my $err = $@ ) {
- handle_startup_error($err);
- }
-}
-
-sub handle_startup_error {
- my $err = shift;
- if ( $err =~ /bind: Permission denied/ ) {
- handle_bind_error();
- } else {
- die
- "Something went wrong while trying to run RT's standalone web server:\n\t"
- . $err;
- }
-}
-
-
-sub handle_bind_error {
-
- print STDERR <<EOF;
-WARNING: RT couldn't start up a web server on port @{[$port]}.
-This is often the case if you're running @{[$0]} as
-someone other than your system's "root" user.
-EOF
-
- if ($explicit_port) {
- print STDERR
- "Please check your system configuration or choose another port\n\n";
- } else {
- print STDERR "\nFor now, RT has chosen an alternate port to run on.\n\n";
- if ( !$integrity ) {
- print STDERR <<EOF;
-You can use this server to configure and explore RT. While configuring
-RT, you'll have a chance to set a permanent port and URL for your
-server.
-
-EOF
- }
- run_server( 8000 + int( rand(1024) ) );
- }
-}
diff --git a/rt/bin/webmux.pl b/rt/bin/webmux.pl
deleted file mode 100755
index c1a0c59..0000000
--- a/rt/bin/webmux.pl
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/perl
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-use strict;
-
-package HTML::Mason::Commands;
-our %session;
-
-package RT::Mason;
-
-our ($Nobody, $SystemUser, $Handler, $r);
-
-sub handler {
- ($r) = @_;
-
- local $SIG{__WARN__};
- local $SIG{__DIE__};
- RT::InitSignalHandlers();
-
- if ($r->content_type =~ m/^httpd\b.*\bdirectory/i) {
- use File::Spec::Unix;
- # Our DirectoryIndex is always index.html, regardless of httpd settings
- $r->filename( File::Spec::Unix->catfile( $r->filename, 'index.html' ) );
- }
-
- Module::Refresh->refresh if RT->Config->Get('DevelMode');
-
- RT::ConnectToDatabase();
-
- my (%session, $status);
- {
- local $@;
- $status = eval { $Handler->handle_request($r) };
- $RT::Logger->crit( $@ ) if $@;
- }
- undef %session;
-
- RT::Interface::Web::Handler->CleanupRequest();
-
- return $status;
-}
-
-package main;
-
-# check mod_perl version if it's mod_perl
-BEGIN {
- die "RT does not support mod_perl 1.99. Please upgrade to mod_perl 2.0"
- if $ENV{'MOD_PERL'}
- and $ENV{'MOD_PERL'} =~ m{mod_perl/(?:1\.9)};
-}
-
-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'};
-
- # bring this in before mason, to make sure we
- # use private tempfiles
- use CGI qw(-private_tempfiles);
-}
-
-# fix lib paths, some may be relative
-BEGIN {
- require File::Spec;
- my @libs = ("lib", "local/lib");
- my $bin_path;
-
- for my $lib (@libs) {
- unless ( File::Spec->file_name_is_absolute($lib) ) {
- unless ($bin_path) {
- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
- }
- else {
- require FindBin;
- no warnings "once";
- $bin_path = $FindBin::Bin;
- }
- }
- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
- }
- unshift @INC, $lib;
- }
-
-}
-
-require RT;
-RT::LoadConfig();
-if ( RT->Config->Get('DevelMode') ) {
- require Module::Refresh;
-}
-RT::Init();
-
-# check compatibility of the DB
-{
- my $dbh = $RT::Handle->dbh;
- if ( $dbh ) {
- my ($status, $msg) = $RT::Handle->CheckCompatibility( $dbh, 'post' );
- die $msg unless $status;
- }
-}
-
-require RT::Interface::Web::Handler;
-$RT::Mason::Handler = RT::Interface::Web::Handler->new(
- RT->Config->Get('MasonParameters')
-);
-
-# load more for mod_perl before forking
-RT::InitClasses( Heavy => 1 ) if $ENV{'MOD_PERL'} || $ENV{RT_WEBMUX_HEAVY_LOAD};
-
-# we must disconnect DB before fork
-$RT::Handle->dbh(undef);
-undef $RT::Handle;
-
-if ( $ENV{'MOD_PERL'} && !RT->Config->Get('DevelMode')) {
- # Under static_source, we need to purge the component cache
- # each time we restart, so newer components may be reloaded.
- #
- # We can't do this in FastCGI or we'll blow away the component
- # root _every_ time a new server starts which happens every few
- # hits.
-
- require File::Path;
- require File::Glob;
- my @files = File::Glob::bsd_glob("$RT::MasonDataDir/obj/*");
- File::Path::rmtree([ @files ], 0, 1) if @files;
-}
-
-1;
diff --git a/rt/config b/rt/config
deleted file mode 100644
index b9418a6..0000000
--- a/rt/config
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * 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 b/rt/config.layout
deleted file mode 100644
index 87078a1..0000000
--- a/rt/config.layout
+++ /dev/null
@@ -1,188 +0,0 @@
-##
-## 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 fontdir
-## 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
- plugindir: ${prefix}/plugins
- libdir: ${prefix}/lib
- datadir: ${prefix}/share
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- 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: .
- exec_prefix: ${prefix}
- bindir: ${exec_prefix}/bin
- sbindir: ${exec_prefix}/sbin
- sysconfdir: ${prefix}/etc
- mandir: ${prefix}/man
- plugindir: ${prefix}/plugins
- libdir: ${prefix}/lib
- datadir: ${prefix}/share
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- 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 FHS>
- prefix: /usr/local
- exec_prefix: ${prefix}
- bindir: ${prefix}/bin
- sbindir: ${prefix}/sbin
- sysconfdir: /etc+
- datadir: ${prefix}/share
-# FIXME: missing support for lib64
- libdir: ${prefix}/lib
- mandir: ${datadir}/man
-# FIXME: no such directory in FHS; shouldn't go to somewhere in "${datadir}/rt/"?
- plugindir: ${datadir}/plugins
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- manualdir: ${datadir}/doc
- localstatedir: /var
- logfiledir: ${localstatedir}/log
-# XXX: "/var/cache/mason/*"?
- masonstatedir: ${localstatedir}/cache/mason_data
- sessionstatedir: ${localstatedir}/cache/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
- plugindir: ${prefix}/plugins
- libdir: ${prefix}/lib+
- datadir: ${prefix}/share+
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- 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
- plugindir: ${prefix}/plugins
- libdir: ${prefix}/lib
- datadir: ${prefix}
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- 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>
-
-# RH path layout.
-<Layout RH>
- prefix: /usr
- exec_prefix: ${prefix}
- bindir: ${exec_prefix}/bin
- sbindir: ${exec_prefix}/sbin
- sysconfdir: /etc/rt
- mandir: ${prefix}/man
- libdir: ${prefix}/lib/rt
- datadir: /var/rt
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- manualdir: ${datadir}/doc
- plugindir: ${datadir}/plugins
- localstatedir: /var
- logfiledir: ${localstatedir}/log/rt
- masonstatedir: ${localstatedir}/rt/mason_data
- sessionstatedir: ${localstatedir}/rt/session_data
- customdir: ${prefix}/local/rt
- custometcdir: ${customdir}/etc
- customhtmldir: ${customdir}/html
- customlexdir: ${customdir}/po
- customlibdir: ${customdir}/lib
-</Layout>
-
-<Layout relative>
- prefix: /opt/rt3
- exec_prefix: ${prefix}
- bindir: bin
- sbindir: sbin
- sysconfdir: etc
- mandir: man
- plugindir: plugins
- libdir: lib
- datadir: share
- htmldir: ${datadir}/html
- fontdir: ${datadir}/fonts
- manualdir: ${datadir}/doc
- localstatedir: var
- logfiledir: ${localstatedir}/log
- masonstatedir: ${localstatedir}/mason_data
- sessionstatedir: ${localstatedir}/session_data
- customdir: local
- custometcdir: ${customdir}/etc
- customhtmldir: ${customdir}/html
- customlexdir: ${customdir}/po
- customlibdir: ${customdir}/lib
-</Layout>
diff --git a/rt/config.layout.in b/rt/config.layout.in
new file mode 100644
index 0000000..c1ad964
--- /dev/null
+++ b/rt/config.layout.in
@@ -0,0 +1,128 @@
+##
+## 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
+ fontdir: ${datadir}/fonts
+</Layout>
+
diff --git a/rt/config.log b/rt/config.log
index a5ae616..ab4b65c 100644
--- a/rt/config.log
+++ b/rt/config.log
@@ -1,178 +1,62 @@
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.8.8, which was
-generated by GNU Autoconf 2.65. Invocation command line was
+It was created by RT configure 3.6.4, which was
+generated by GNU Autoconf 2.59. Invocation command line was
- $ ./configure
+ $ ./configure --enable-layout=Freeside --with-db-type=Pg --with-db-dba=freeside --with-db-database=freeside --with-db-rt-user=freeside --with-db-rt-pass= --with-web-user=freeside --with-web-group=freeside --with-rt-group=freeside
## --------- ##
## Platform. ##
## --------- ##
-hostname = Macintosh-2.local
-uname -m = i386
-uname -r = 9.8.0
-uname -s = Darwin
-uname -v = Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386
+hostname = rootwood
+uname -m = x86_64
+uname -r = 2.6.21-1-amd64
+uname -s = Linux
+uname -v = #1 SMP Sat May 26 17:22:54 CEST 2007
-/usr/bin/uname -p = i386
+/usr/bin/uname -p = unknown
/bin/uname -X = unknown
/bin/arch = unknown
/usr/bin/arch -k = unknown
/usr/convex/getsysinfo = unknown
-/usr/bin/hostinfo = Mach kernel version:
- Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386
-Kernel configured for up to 2 processors.
-2 processors are physically available.
-2 processors are logically available.
-Processor type: i486 (Intel 80486)
-Processors active: 0 1
-Primary memory available: 2.00 gigabytes
-Default processor set: 117 tasks, 405 threads, 2 processors
-Load average: 0.59, Mach factor: 1.56
+hostinfo = unknown
/bin/machine = unknown
/usr/bin/oslevel = unknown
/bin/universe = unknown
-PATH: /Users/ruz/projs/perl6/rakudo/parrot_install/bin
-PATH: /Users/ruz/bin
-PATH: /opt/local/bin
-PATH: /opt/local/sbin
-PATH: /opt/local/bin
-PATH: /opt/local/sbin
-PATH: /bin
-PATH: /sbin
-PATH: /usr/bin
+PATH: /usr/local/sbin
+PATH: /usr/local/bin
PATH: /usr/sbin
+PATH: /usr/bin
+PATH: /sbin
+PATH: /bin
## ----------- ##
## Core tests. ##
## ----------- ##
-configure:1979: checking for a BSD-compatible install
-configure:2047: result: /opt/local/bin/ginstall -c
-configure:2062: checking for gawk
-configure:2078: found /opt/local/bin/gawk
-configure:2089: result: gawk
-configure:2103: checking for perl
-configure:2134: result: /usr/bin/perl
-configure:2499: checking for chosen layout
-configure:2512: result: relative
-configure:2671: checking if user www exists
-configure:2674: result: found
-configure:2695: checking if group www exists
-configure:2698: result: found
-configure:2718: checking if group rt3 exists
-configure:2724: result: not found
-configure:2718: checking if group rt exists
-configure:2724: result: not found
-configure:2718: checking if group www exists
-configure:2721: result: found
-configure:2751: checking if database name is valid
-configure:2754: result: yes
-configure:2845: checking for gcc
-configure:2861: found /usr/bin/gcc
-configure:2872: result: gcc
-configure:3101: checking for C compiler version
-configure:3110: gcc --version >&5
-i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5490)
-Copyright (C) 2005 Free Software Foundation, Inc.
-This is free software; see the source for copying conditions. There is NO
-warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-configure:3121: $? = 0
-configure:3110: gcc -v >&5
-Using built-in specs.
-Target: i686-apple-darwin9
-Configured with: /var/tmp/gcc/gcc-5490~1/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
-Thread model: posix
-gcc version 4.0.1 (Apple Inc. build 5490)
-configure:3121: $? = 0
-configure:3110: gcc -V >&5
-gcc-4.0: argument to `-V' is missing
-configure:3121: $? = 1
-configure:3110: gcc -qversion >&5
-i686-apple-darwin9-gcc-4.0.1: no input files
-configure:3121: $? = 1
-configure:3141: checking whether the C compiler works
-configure:3163: gcc conftest.c >&5
-configure:3167: $? = 0
-configure:3216: result: yes
-configure:3219: checking for C compiler default output file name
-configure:3221: result: a.out
-configure:3227: checking for suffix of executables
-configure:3234: gcc -o conftest conftest.c >&5
-configure:3238: $? = 0
-configure:3260: result:
-configure:3282: checking whether we are cross compiling
-configure:3290: gcc -o conftest conftest.c >&5
-configure:3294: $? = 0
-configure:3301: ./conftest
-configure:3305: $? = 0
-configure:3320: result: no
-configure:3325: checking for suffix of object files
-configure:3347: gcc -c conftest.c >&5
-configure:3351: $? = 0
-configure:3372: result: o
-configure:3376: checking whether we are using the GNU C compiler
-configure:3395: gcc -c conftest.c >&5
-configure:3395: $? = 0
-configure:3404: result: yes
-configure:3413: checking whether gcc accepts -g
-configure:3433: gcc -c -g conftest.c >&5
-configure:3433: $? = 0
-configure:3474: result: yes
-configure:3491: checking for gcc option to accept ISO C89
-configure:3555: gcc -c -g -O2 conftest.c >&5
-configure:3555: $? = 0
-configure:3568: result: none needed
-configure:3589: checking for aginitlib in -lgraph
-configure:3614: gcc -o conftest -g -O2 conftest.c -lgraph >&5
-ld: library not found for -lgraph
-collect2: ld returned 1 exit status
-configure:3614: $? = 1
-configure: failed program was:
-| /* confdefs.h */
-| #define PACKAGE_NAME "RT"
-| #define PACKAGE_TARNAME "rt"
-| #define PACKAGE_VERSION "3.8.8"
-| #define PACKAGE_STRING "RT 3.8.8"
-| #define PACKAGE_BUGREPORT "rt-bugs@bestpractical.com"
-| #define PACKAGE_URL ""
-| /* end confdefs.h. */
-|
-| /* Override any GCC internal prototype to avoid an error.
-| Use char because int might match the return type of a GCC
-| builtin and then its argument prototype would still apply. */
-| #ifdef __cplusplus
-| extern "C"
-| #endif
-| char aginitlib ();
-| int
-| main ()
-| {
-| return aginitlib ();
-| ;
-| return 0;
-| }
-configure:3623: result: no
-configure:3649: checking for gdlib-config
-configure:3665: found /opt/local/bin/gdlib-config
-configure:3677: result: yes
-configure:3705: checking for gpg
-configure:3721: found /opt/local/bin/gpg
-configure:3733: result: yes
-configure:4032: creating ./config.status
+configure:1331: checking for a BSD-compatible install
+configure:1386: result: /usr/bin/install -c
+configure:1401: checking for gawk
+configure:1417: found /usr/bin/gawk
+configure:1427: result: gawk
+configure:1440: checking for perl
+configure:1458: found /usr/bin/perl
+configure:1471: result: /usr/bin/perl
+configure:1795: checking for chosen layout
+configure:1810: result: Freeside
+configure:2272: creating ./config.status
## ---------------------- ##
## Running config.status. ##
## ---------------------- ##
-This file was extended by RT config.status 3.8.8, which was
-generated by GNU Autoconf 2.65. Invocation command line was
+This file was extended by RT config.status 3.6.4, which was
+generated by GNU Autoconf 2.59. Invocation command line was
CONFIG_FILES =
CONFIG_HEADERS =
@@ -180,70 +64,38 @@ generated by GNU Autoconf 2.65. Invocation command line was
CONFIG_COMMANDS =
$ ./config.status
-on Macintosh-2.local
-
-config.status:858: creating etc/upgrade/3.8-branded-queues-extension
-config.status:858: creating etc/upgrade/3.8-ical-extension
-config.status:858: creating etc/upgrade/split-out-cf-categories
-config.status:858: creating sbin/rt-attributes-viewer
-config.status:858: creating sbin/rt-dump-database
-config.status:858: creating sbin/rt-setup-database
-config.status:858: creating sbin/rt-test-dependencies
-config.status:858: creating sbin/rt-email-digest
-config.status:858: creating sbin/rt-email-dashboards
-config.status:858: creating sbin/rt-clean-sessions
-config.status:858: creating sbin/rt-shredder
-config.status:858: creating sbin/rt-validator
-config.status:858: creating sbin/rt-email-group-admin
-config.status:858: creating sbin/rt-server
-config.status:858: creating bin/fastcgi_server
-config.status:858: creating bin/mason_handler.fcgi
-config.status:858: creating bin/mason_handler.scgi
-config.status:858: creating bin/standalone_httpd
-config.status:858: creating bin/rt-crontool
-config.status:858: creating bin/rt-mailgate
-config.status:858: creating bin/rt
-config.status:858: creating Makefile
-config.status:858: creating etc/RT_Config.pm
-config.status:858: creating lib/RT.pm
-config.status:858: creating bin/mason_handler.svc
-config.status:858: creating bin/webmux.pl
-config.status:858: creating t/data/configs/apache2.2+mod_perl.conf
-config.status:858: creating t/data/configs/apache2.2+fastcgi.conf
+on rootwood
+
+config.status:760: creating sbin/rt-dump-database
+config.status:760: creating sbin/rt-setup-database
+config.status:760: creating sbin/rt-test-dependencies
+config.status:760: creating bin/mason_handler.fcgi
+config.status:760: creating bin/mason_handler.scgi
+config.status:760: creating bin/standalone_httpd
+config.status:760: creating bin/rt-crontool
+config.status:760: creating bin/rt-mailgate
+config.status:760: creating bin/rt
+config.status:760: creating Makefile
+config.status:760: creating etc/RT_Config.pm
+config.status:760: creating lib/RT.pm
+config.status:760: creating bin/mason_handler.svc
+config.status:760: creating bin/webmux.pl
## ---------------- ##
## Cache variables. ##
## ---------------- ##
-ac_cv_c_compiler_gnu=yes
-ac_cv_env_CC_set=
-ac_cv_env_CC_value=
-ac_cv_env_CFLAGS_set=
-ac_cv_env_CFLAGS_value=
-ac_cv_env_CPPFLAGS_set=
-ac_cv_env_CPPFLAGS_value=
-ac_cv_env_LDFLAGS_set=
-ac_cv_env_LDFLAGS_value=
-ac_cv_env_LIBS_set=
-ac_cv_env_LIBS_value=
-ac_cv_env_PERL_set=set
-ac_cv_env_PERL_value=/usr/bin/perl
+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_lib_graph_aginitlib=no
-ac_cv_objext=o
ac_cv_path_PERL=/usr/bin/perl
-ac_cv_path_install='/opt/local/bin/ginstall -c'
+ac_cv_path_install='/usr/bin/install -c'
ac_cv_prog_AWK=gawk
-ac_cv_prog_RT_GD=yes
-ac_cv_prog_RT_GPG=yes
-ac_cv_prog_ac_ct_CC=gcc
-ac_cv_prog_cc_c89=
-ac_cv_prog_cc_g=yes
## ----------------- ##
## Output variables. ##
@@ -252,168 +104,123 @@ ac_cv_prog_cc_g=yes
APACHECTL='/usr/sbin/apachectl'
AWK='gawk'
BIN_OWNER='root'
-CC='gcc'
-CFLAGS='-g -O2'
-COMMENT_INPLACE_LAYOUT=''
-CONFIG_FILE_PATH='etc'
-CONFIG_FILE_PATH_R='/opt/rt3/etc'
-CPPFLAGS=''
+CONFIG_FILE_PATH='/opt/rt3/etc'
DATABASE_ENV_PREF=''
-DB_DATABASE='rt3'
-DB_DBA='root'
+DB_DATABASE='freeside'
+DB_DBA='freeside'
DB_HOST='localhost'
DB_PORT=''
DB_RT_HOST='localhost'
-DB_RT_PASS='rt_pass'
-DB_RT_USER='rt_user'
-DB_TYPE='mysql'
-DEFS='-DPACKAGE_NAME=\"RT\" -DPACKAGE_TARNAME=\"rt\" -DPACKAGE_VERSION=\"3.8.8\" -DPACKAGE_STRING=\"RT\ 3.8.8\" -DPACKAGE_BUGREPORT=\"rt-bugs@bestpractical.com\" -DPACKAGE_URL=\"\"'
-ECHO_C='\c'
-ECHO_N=''
+DB_RT_PASS=''
+DB_RT_USER='freeside'
+DB_TYPE='Pg'
+DEFS='-DPACKAGE_NAME=\"RT\" -DPACKAGE_TARNAME=\"rt\" -DPACKAGE_VERSION=\"3.6.4\" -DPACKAGE_STRING=\"RT\ 3.6.4\" -DPACKAGE_BUGREPORT=\"rt-bugs@bestpractical.com\" '
+DESTDIR='/opt/rt3'
+ECHO_C=''
+ECHO_N='-n'
ECHO_T=''
-EXEEXT=''
INSTALL_DATA='${INSTALL} -m 644'
INSTALL_PROGRAM='${INSTALL}'
INSTALL_SCRIPT='${INSTALL}'
-LDFLAGS=''
LIBOBJS=''
LIBS=''
LIBS_GROUP='bin'
LIBS_OWNER='root'
-LOCAL_ETC_PATH='local/etc'
-LOCAL_ETC_PATH_R='/opt/rt3/local/etc'
-LOCAL_LEXICON_PATH='local/po'
-LOCAL_LEXICON_PATH_R='/opt/rt3/local/po'
-LOCAL_LIB_PATH='local/lib'
-LOCAL_LIB_PATH_R='/opt/rt3/local/lib'
+LOCAL_ETC_PATH='/opt/rt3/local/etc'
+LOCAL_LEXICON_PATH='/opt/rt3/local/po'
+LOCAL_LIB_PATH='/opt/rt3/local/lib'
LTLIBOBJS=''
-MASON_DATA_PATH='var/mason_data'
-MASON_DATA_PATH_R='/opt/rt3/var/mason_data'
-MASON_HTML_PATH='share/html'
-MASON_HTML_PATH_R='/opt/rt3/share/html'
-MASON_LOCAL_HTML_PATH='local/html'
-MASON_LOCAL_HTML_PATH_R='/opt/rt3/local/html'
-MASON_SESSION_PATH='var/session_data'
-MASON_SESSION_PATH_R='/opt/rt3/var/session_data'
-OBJEXT='o'
+MASON_DATA_PATH='/usr/local/etc/freeside/masondata'
+MASON_HTML_PATH='/var/www/freeside/rt'
+MASON_LOCAL_HTML_PATH='/opt/rt3/local/html'
+MASON_SESSION_PATH='/opt/rt3/var/session_data'
PACKAGE_BUGREPORT='rt-bugs@bestpractical.com'
PACKAGE_NAME='RT'
-PACKAGE_STRING='RT 3.8.8'
+PACKAGE_STRING='RT 3.6.4'
PACKAGE_TARNAME='rt'
-PACKAGE_URL=''
-PACKAGE_VERSION='3.8.8'
+PACKAGE_VERSION='3.6.4'
PATH_SEPARATOR=':'
PERL='/usr/bin/perl'
-RTGROUP='www'
-RT_BIN_PATH='bin'
-RT_BIN_PATH_R='/opt/rt3/bin'
+RTGROUP='freeside'
+RT_BIN_PATH='/opt/rt3/bin'
RT_DEVEL_MODE='0'
-RT_DOC_PATH='share/doc'
-RT_DOC_PATH_R='/opt/rt3/share/doc'
-RT_ETC_PATH='etc'
-RT_ETC_PATH_R='/opt/rt3/etc'
-RT_FONT_PATH='share/fonts'
-RT_FONT_PATH_R='/opt/rt3/share/fonts'
-RT_GD='1'
-RT_GPG='1'
-RT_GRAPHVIZ='0'
-RT_LIB_PATH='lib'
-RT_LIB_PATH_R='/opt/rt3/lib'
-RT_LOCAL_PATH='local'
-RT_LOCAL_PATH_R='/opt/rt3/local'
-RT_LOG_PATH='var/log'
-RT_LOG_PATH_R='/opt/rt3/var/log'
-RT_MAN_PATH='man'
-RT_MAN_PATH_R='/opt/rt3/man'
+RT_DOC_PATH='/opt/rt3/share/doc'
+RT_ETC_PATH='/opt/rt3/etc'
+RT_LIB_PATH='/opt/rt3/lib'
+RT_LOCAL_PATH='/opt/rt3/local'
+RT_LOG_PATH='/opt/rt3/var/log'
+RT_MAN_PATH='/opt/rt3/man'
RT_PATH='/opt/rt3'
-RT_PATH_R='/opt/rt3'
-RT_PLUGIN_PATH='plugins'
-RT_PLUGIN_PATH_R='/opt/rt3/plugins'
-RT_SBIN_PATH='sbin'
-RT_SBIN_PATH_R='/opt/rt3/sbin'
-RT_VAR_PATH='var'
-RT_VAR_PATH_R='/opt/rt3/var'
+RT_SBIN_PATH='/opt/rt3/sbin'
+RT_STANDALONE='0'
+RT_VAR_PATH='/opt/rt3/var'
RT_VERSION_MAJOR='3'
-RT_VERSION_MINOR='8'
-RT_VERSION_PATCH='8'
+RT_VERSION_MINOR='6'
+RT_VERSION_PATCH='4'
SHELL='/bin/sh'
SPEEDY_BIN='/usr/local/bin/speedy'
-WEB_GROUP='www'
-WEB_HANDLER='fastcgi'
-WEB_USER='www'
-ac_ct_CC='gcc'
-bindir='bin'
+WEB_GROUP='freeside'
+WEB_USER='freeside'
+bindir='/opt/rt3/bin'
build_alias=''
-customdir='local'
-custometcdir='local/etc'
-customhtmldir='local/html'
-customlexdir='local/po'
-customlibdir='local/lib'
-datadir='share'
-datarootdir='${prefix}/share'
-docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
-dvidir='${docdir}'
+customdir='/opt/rt3/local'
+custometcdir='/opt/rt3/local/etc'
+customhtmldir='/opt/rt3/local/html'
+customlexdir='/opt/rt3/local/po'
+customlibdir='/opt/rt3/local/lib'
+datadir='/opt/rt3/share'
exec_prefix='/opt/rt3'
-exp_bindir='bin'
-exp_customdir='local'
-exp_custometcdir='local/etc'
-exp_customhtmldir='local/html'
-exp_customlexdir='local/po'
-exp_customlibdir='local/lib'
-exp_datadir='share'
+exp_bindir='/opt/rt3/bin'
+exp_customdir='/opt/rt3/local'
+exp_custometcdir='/opt/rt3/local/etc'
+exp_customhtmldir='/opt/rt3/local/html'
+exp_customlexdir='/opt/rt3/local/po'
+exp_customlibdir='/opt/rt3/local/lib'
+exp_datadir='/opt/rt3/share'
exp_exec_prefix='/opt/rt3'
-exp_fontdir='share/fonts'
-exp_htmldir='share/html'
-exp_libdir='lib'
-exp_localstatedir='var'
-exp_logfiledir='var/log'
-exp_mandir='man'
-exp_manualdir='share/doc'
-exp_masonstatedir='var/mason_data'
-exp_plugindir='plugins'
+exp_htmldir='/var/www/freeside/rt'
+exp_libdir='/opt/rt3/lib'
+exp_localstatedir='/opt/rt3/var'
+exp_logfiledir='/opt/rt3/var/log'
+exp_mandir='/opt/rt3/man'
+exp_manualdir='/opt/rt3/share/doc'
+exp_masonstatedir='/usr/local/etc/freeside/masondata'
exp_prefix='/opt/rt3'
-exp_sbindir='sbin'
-exp_sessionstatedir='var/session_data'
-exp_sysconfdir='etc'
-fontdir='share/fonts'
+exp_sbindir='/opt/rt3/sbin'
+exp_sessionstatedir='/opt/rt3/var/session_data'
+exp_sysconfdir='/opt/rt3/etc'
host_alias=''
-htmldir='share/html'
+htmldir='/var/www/freeside/rt'
includedir='${prefix}/include'
-infodir='${datarootdir}/info'
-libdir='lib'
+infodir='${prefix}/info'
+libdir='/opt/rt3/lib'
libexecdir='${exec_prefix}/libexec'
-localedir='${datarootdir}/locale'
-localstatedir='var'
-logfiledir='var/log'
-mandir='man'
-manualdir='share/doc'
-masonstatedir='var/mason_data'
+localstatedir='/opt/rt3/var'
+logfiledir='/opt/rt3/var/log'
+mandir='/opt/rt3/man'
+manualdir='/opt/rt3/share/doc'
+masonstatedir='/usr/local/etc/freeside/masondata'
oldincludedir='/usr/include'
-pdfdir='${docdir}'
-plugindir='plugins'
prefix='/opt/rt3'
program_transform_name='s,x,x,'
-psdir='${docdir}'
-rt_layout_name='relative'
+rt_layout_name='Freeside'
rt_version_major='3'
-rt_version_minor='8'
-rt_version_patch='8'
-sbindir='sbin'
-sessionstatedir='var/session_data'
+rt_version_minor='6'
+rt_version_patch='4'
+sbindir='/opt/rt3/sbin'
+sessionstatedir='/opt/rt3/var/session_data'
sharedstatedir='${prefix}/com'
-sysconfdir='etc'
+sysconfdir='/opt/rt3/etc'
target_alias=''
## ----------- ##
## confdefs.h. ##
## ----------- ##
-/* confdefs.h */
+#define PACKAGE_BUGREPORT "rt-bugs@bestpractical.com"
#define PACKAGE_NAME "RT"
+#define PACKAGE_STRING "RT 3.6.4"
#define PACKAGE_TARNAME "rt"
-#define PACKAGE_VERSION "3.8.8"
-#define PACKAGE_STRING "RT 3.8.8"
-#define PACKAGE_BUGREPORT "rt-bugs@bestpractical.com"
-#define PACKAGE_URL ""
+#define PACKAGE_VERSION "3.6.4"
configure: exit 0
diff --git a/rt/config.pld b/rt/config.pld
deleted file mode 100644
index c37c784..0000000
--- a/rt/config.pld
+++ /dev/null
@@ -1,21 +0,0 @@
-(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=bin
-sbindir=sbin
-sysconfdir=etc
-mandir=man
-(test "x$plugindir" = "xNONE" || test "x$plugindir" = "x") && plugindir=plugins
-libdir=lib
-datadir=share
-htmldir=${datadir}/html
-(test "x$fontdir" = "xNONE" || test "x$fontdir" = "x") && fontdir=${datadir}/fonts
-(test "x$manualdir" = "xNONE" || test "x$manualdir" = "x") && manualdir=${datadir}/doc
-localstatedir=var
-(test "x$logfiledir" = "xNONE" || test "x$logfiledir" = "x") && logfiledir=${localstatedir}/log
-(test "x$masonstatedir" = "xNONE" || test "x$masonstatedir" = "x") && masonstatedir=${localstatedir}/mason_data
-(test "x$sessionstatedir" = "xNONE" || test "x$sessionstatedir" = "x") && sessionstatedir=${localstatedir}/session_data
-(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=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/etc/RT_Config.pm b/rt/etc/RT_Config.pm
index a5dacd3..a976fb3 100644
--- a/rt/etc/RT_Config.pm
+++ b/rt/etc/RT_Config.pm
@@ -104,7 +104,7 @@ Valid types are "mysql", "Oracle" and "Pg"
=cut
-Set($DatabaseType , 'mysql');
+Set($DatabaseType , 'Pg');
=item C<$DatabaseHost>, C<$DatabaseRTHost>
@@ -133,7 +133,7 @@ The name of the database user (inside the database)
=cut
-Set($DatabaseUser , 'rt_user');
+Set($DatabaseUser , 'freeside');
=item C<$DatabasePassword>
@@ -141,7 +141,7 @@ Password the C<$DatabaseUser> should use to access the database
=cut
-Set($DatabasePassword , 'rt_pass');
+Set($DatabasePassword , '');
=item C<$DatabaseName>
@@ -150,7 +150,7 @@ it's SID, DB objects are created in L<$DatabaseUser>'s schema.
=cut
-Set($DatabaseName , 'rt3');
+Set($DatabaseName , 'freeside');
=item C<$DatabaseRequireSSL>
@@ -738,7 +738,7 @@ NOTE that options with '-' character MUST be quoted.
=cut
Set(%GnuPGOptions,
- homedir => 'var/data/gpg',
+ homedir => '/opt/rt3/var/data/gpg',
# URL of a keyserver
# keyserver => 'hkp://subkeys.pgp.net',
@@ -792,7 +792,7 @@ direct file logging.
=cut
Set($LogToFile , undef);
-Set($LogDir, 'var/log');
+Set($LogDir, '/opt/rt3/var/log');
Set($LogToFileNamed , "rt.log"); #log to rt.log
=item C<$LogStackTraces>
@@ -846,6 +846,9 @@ RT ships with several themes by default:
3.4-compat A 3.4 compatibility stylesheet to make RT look
(mostly) like 3.4
+This bundled distibution of RT also includes (enabled by default):
+ freeside2.1 Integration with Freeside
+
This value actually specifies a directory in F<share/html/NoAuth/css/>
from which RT will try to load the file main.css (which should
@import any other files the stylesheet needs). This allows you to
@@ -854,7 +857,7 @@ option can be overridden by users in their preferences.
=cut
-Set($WebDefaultStylesheet, 'web2');
+Set($WebDefaultStylesheet, 'freeside2.1');
=item C<$UsernameFormat>
@@ -987,7 +990,7 @@ login if you change it.
=cut
-Set($WebNoAuthRegex, qr{^ (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
+Set($WebNoAuthRegex, qr{^ /rt (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
=item C<$SelfServiceRegex>
@@ -1343,7 +1346,7 @@ customized homepage ("RT at a glance").
=cut
-Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
=item C<@MasonParameters>
@@ -1775,7 +1778,7 @@ C<Set(@Plugins, (qw(Extension::QuickDelete RT::FM)));>
=cut
-Set(@Plugins, ());
+Set(@Plugins, (qw(RTx::Calendar)));
=back
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index 9bd809a..564682b 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -846,6 +846,9 @@ RT ships with several themes by default:
3.4-compat A 3.4 compatibility stylesheet to make RT look
(mostly) like 3.4
+This bundled distibution of RT also includes (enabled by default):
+ freeside2.1 Integration with Freeside
+
This value actually specifies a directory in F<share/html/NoAuth/css/>
from which RT will try to load the file main.css (which should
@import any other files the stylesheet needs). This allows you to
@@ -854,7 +857,7 @@ option can be overridden by users in their preferences.
=cut
-Set($WebDefaultStylesheet, 'web2');
+Set($WebDefaultStylesheet, 'freeside2.1');
=item C<$UsernameFormat>
@@ -987,7 +990,7 @@ login if you change it.
=cut
-Set($WebNoAuthRegex, qr{^ (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
+Set($WebNoAuthRegex, qr{^ /rt (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
=item C<$SelfServiceRegex>
@@ -1343,7 +1346,7 @@ customized homepage ("RT at a glance").
=cut
-Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
=item C<@MasonParameters>
@@ -1775,7 +1778,7 @@ C<Set(@Plugins, (qw(Extension::QuickDelete RT::FM)));>
=cut
-Set(@Plugins, ());
+Set(@Plugins, (qw(RTx::Calendar)));
=back
diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm
index 1661e4d..bfdfbbb 100644
--- a/rt/etc/RT_SiteConfig.pm
+++ b/rt/etc/RT_SiteConfig.pm
@@ -14,6 +14,39 @@
#
# perl -c /path/to/your/etc/RT_SiteConfig.pm
-Set( $rtname, 'example.com');
+#Set( $rtname, 'example.com');
+
+# These settings should have been inserted by the initial Freeside install.
+# Sometimes you may want to change domain, timezone, or freeside::URL later,
+# everything else should probably stay untouched.
+
+Set($rtname, '%%%RT_DOMAIN%%%');
+Set($Organization, '%%%RT_DOMAIN%%%');
+
+Set($Timezone, '%%%RT_TIMEZONE%%%');
+
+Set($WebExternalAuth, 1);
+Set($WebFallbackToInternal, 1); #no
+Set($WebExternalAuto, 1);
+
+$RT::URI::freeside::IntegrationType = 'Internal';
+$RT::URI::freeside::URL = '%%%FREESIDE_URL%%%';
+
+$RT::URI::freeside::URL =~ m(^(https?://[^/]+)(/.*)$)i;
+Set($WebBaseURL, $1);
+Set($WebPath, "$2/rt");
+
+Set($DatabaseHost , '');
+
+# These settings are user-editable.
+
+Set($WebDefaultStylesheet, 'freeside2.1');
+
+#uncomment to use
+#Set($DefaultSummaryRows, 10);
+
+Set($MessageBoxWidth, 80);
+Set($MessageBoxRichTextHeight, 368);
+
#Set(@Plugins,(qw(Extension::QuickDelete RT::FM)));
1;
diff --git a/rt/etc/rt.spec b/rt/etc/rt.spec
deleted file mode 100644
index 14200c1..0000000
--- a/rt/etc/rt.spec
+++ /dev/null
@@ -1,137 +0,0 @@
-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.Oracle b/rt/etc/schema.Oracle
deleted file mode 100644
index 693e75a..0000000
--- a/rt/etc/schema.Oracle
+++ /dev/null
@@ -1,399 +0,0 @@
-
-CREATE SEQUENCE ATTACHMENTS_seq;
-CREATE TABLE Attachments (
- id NUMBER(11,0)
- CONSTRAINT Attachments_Key PRIMARY KEY,
- TransactionId NUMBER(11,0) NOT NULL,
- Parent NUMBER(11,0) DEFAULT 0 NOT NULL,
- MessageId VARCHAR2(160),
- Subject VARCHAR2(255),
- Filename VARCHAR2(255),
- ContentType VARCHAR2(80),
- ContentEncoding VARCHAR2(80),
- Content CLOB,
- Headers CLOB,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE
-);
-CREATE INDEX Attachments2 ON Attachments (TransactionId);
-CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId);
-
-
-CREATE SEQUENCE QUEUES_seq;
-CREATE TABLE Queues (
- id NUMBER(11,0)
- CONSTRAINT Queues_Key PRIMARY KEY,
- Name VARCHAR2(200) CONSTRAINT Queues_Name_Unique UNIQUE NOT NULL,
- Description VARCHAR2(255),
- CorrespondAddress VARCHAR2(120),
- CommentAddress VARCHAR2(120),
- InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL,
- FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL,
- DefaultDueIn NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
- CREATE INDEX Queues1 ON Queues (LOWER(Name));
-CREATE INDEX Queues2 ON Queues (Disabled);
-
-
-CREATE SEQUENCE LINKS_seq;
-CREATE TABLE Links (
- id NUMBER(11,0)
- CONSTRAINT Links_Key PRIMARY KEY,
- Base VARCHAR2(240),
- Target VARCHAR2(240),
- Type VARCHAR2(20) NOT NULL,
- LocalTarget NUMBER(11,0) DEFAULT 0 NOT NULL,
- LocalBase NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE
-);
-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 SEQUENCE PRINCIPALS_seq;
-CREATE TABLE Principals (
- id NUMBER(11,0)
- CONSTRAINT Principals_Key PRIMARY KEY,
- PrincipalType VARCHAR2(16),
- ObjectId NUMBER(11,0),
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-CREATE UNIQUE INDEX Principals2 ON Principals (ObjectId);
-
-
-CREATE SEQUENCE GROUPS_seq;
-CREATE TABLE Groups (
- id NUMBER(11,0)
- CONSTRAINT Groups_Key PRIMARY KEY,
- Name VARCHAR2(200),
- Description VARCHAR2(255),
- Domain VARCHAR2(64),
- Type VARCHAR2(64),
- Instance NUMBER(11,0) DEFAULT 0 -- NOT NULL
--- Instance VARCHAR2(64)
-);
-CREATE INDEX Groups1 ON Groups (LOWER(Domain), Instance, LOWER(Type), id);
-CREATE INDEX Groups2 ON Groups (LOWER(Type), Instance, LOWER(Domain));
-
-
-CREATE SEQUENCE SCRIPCONDITIONS_seq;
-CREATE TABLE ScripConditions (
- id NUMBER(11, 0)
- CONSTRAINT ScripConditions_Key PRIMARY KEY,
- Name VARCHAR2(200),
- Description VARCHAR2(255),
- ExecModule VARCHAR2(60),
- Argument VARCHAR2(255),
- ApplicableTransTypes VARCHAR2(60),
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-
-CREATE SEQUENCE TRANSACTIONS_seq;
-CREATE TABLE Transactions (
- id NUMBER(11,0)
- CONSTRAINT Transactions_Key PRIMARY KEY,
- ObjectType VARCHAR2(255),
- ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
- TimeTaken NUMBER(11,0) DEFAULT 0 NOT NULL,
- Type VARCHAR2(20),
- Field VARCHAR2(40),
- OldValue VARCHAR2(255),
- NewValue VARCHAR2(255),
- ReferenceType VARCHAR2(255),
- OldReference NUMBER(11,0),
- NewReference NUMBER(11,0),
- Data VARCHAR2(255),
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE
-);
-CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
-
-
-CREATE SEQUENCE SCRIPS_seq;
-CREATE TABLE Scrips (
- id NUMBER(11,0)
- CONSTRAINT Scrips_Key PRIMARY KEY,
- Description VARCHAR2(255),
- ScripCondition NUMBER(11,0) DEFAULT 0 NOT NULL,
- ScripAction NUMBER(11,0) DEFAULT 0 NOT NULL,
- ConditionRules CLOB,
- ActionRules CLOB,
- CustomIsApplicableCode CLOB,
- CustomPrepareCode CLOB,
- CustomCommitCode CLOB,
- Stage VARCHAR2(32),
- Queue NUMBER(11,0) DEFAULT 0 NOT NULL,
- Template NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-
-CREATE SEQUENCE ACL_seq;
-CREATE TABLE ACL (
- id NUMBER(11,0)
- CONSTRAINT ACL_Key PRIMARY KEY,
- PrincipalType VARCHAR2(25) NOT NULL,
- PrincipalId NUMBER(11,0) NOT NULL,
- RightName VARCHAR2(25) NOT NULL,
- ObjectType VARCHAR2(25) NOT NULL,
- ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
- DelegatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- DelegatedFrom NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-CREATE INDEX ACL1 ON ACL(RightName, ObjectType, ObjectId, PrincipalType, PrincipalId);
-
-
-CREATE SEQUENCE GROUPMEMBERS_seq;
-CREATE TABLE GroupMembers (
- id NUMBER(11,0)
- CONSTRAINT GroupMembers_Key PRIMARY KEY,
- GroupId NUMBER(11,0) DEFAULT 0 NOT NULL,
- MemberId NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers (GroupId, MemberId);
-
-
-CREATE SEQUENCE CachedGroupMembers_seq;
-CREATE TABLE CachedGroupMembers (
- id NUMBER(11,0)
- CONSTRAINT CachedGroupMembers_Key PRIMARY KEY,
- GroupId NUMBER(11,0),
- MemberId NUMBER(11,0),
- Via NUMBER(11,0),
- ImmediateParentId NUMBER(11,0),
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled);
-CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId);
-CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
-
-
-CREATE SEQUENCE USERS_seq;
-CREATE TABLE Users (
- id NUMBER(11,0)
- CONSTRAINT Users_Key PRIMARY KEY,
- Name VARCHAR2(200) CONSTRAINT Users_Name_Unique
- unique NOT NULL,
- Password VARCHAR2(40),
- Comments CLOB,
- Signature CLOB,
- EmailAddress VARCHAR2(120),
- FreeFormContactInfo CLOB,
- Organization VARCHAR2(200),
- RealName VARCHAR2(120),
- NickName VARCHAR2(16),
- Lang VARCHAR2(16),
- EmailEncoding VARCHAR2(16),
- WebEncoding VARCHAR2(16),
- ExternalContactInfoId VARCHAR2(100),
- ContactInfoSystem VARCHAR2(30),
- ExternalAuthId VARCHAR2(100),
- AuthSystem VARCHAR2(30),
- Gecos VARCHAR2(16),
- HomePhone VARCHAR2(30),
- WorkPhone VARCHAR2(30),
- MobilePhone VARCHAR2(30),
- PagerPhone VARCHAR2(30),
- Address1 VARCHAR2(200),
- Address2 VARCHAR2(200),
- City VARCHAR2(100),
- State VARCHAR2(100),
- Zip VARCHAR2(16),
- Country VARCHAR2(50),
- Timezone VARCHAR2(50),
- PGPKey CLOB,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
--- CREATE UNIQUE INDEX Users1 ON Users (Name);
-
-CREATE INDEX Users2 ON Users( LOWER(Name));
-CREATE INDEX Users4 ON Users (LOWER(EmailAddress));
-
-
-CREATE SEQUENCE TICKETS_seq;
-CREATE TABLE Tickets (
- id NUMBER(11, 0)
- CONSTRAINT Tickets_Key PRIMARY KEY,
- EffectiveId NUMBER(11,0) DEFAULT 0 NOT NULL,
- Queue NUMBER(11,0) DEFAULT 0 NOT NULL,
- Type VARCHAR2(16),
- IssueStatement NUMBER(11,0) DEFAULT 0 NOT NULL,
- Resolution NUMBER(11,0) DEFAULT 0 NOT NULL,
- Owner NUMBER(11,0) DEFAULT 0 NOT NULL,
- Subject VARCHAR2(200) DEFAULT '[no subject]',
- InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL,
- FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL,
- Priority NUMBER(11,0) DEFAULT 0 NOT NULL,
- TimeEstimated NUMBER(11,0) DEFAULT 0 NOT NULL,
- TimeWorked NUMBER(11,0) DEFAULT 0 NOT NULL,
- Status VARCHAR2(10),
- TimeLeft NUMBER(11,0) DEFAULT 0 NOT NULL,
- Told DATE,
- Starts DATE,
- Started DATE,
- Due DATE,
- Resolved DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-CREATE INDEX Tickets1 ON Tickets (Queue, Status);
-CREATE INDEX Tickets2 ON Tickets (Owner);
-CREATE INDEX Tickets4 ON Tickets (id, Status);
-CREATE INDEX Tickets5 ON Tickets (id, EffectiveId);
-CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type);
-
-
-CREATE SEQUENCE SCRIPACTIONS_seq;
-CREATE TABLE ScripActions (
- id NUMBER(11,0)
- CONSTRAINT ScripActions_Key PRIMARY KEY,
- Name VARCHAR2(200),
- Description VARCHAR2(255),
- ExecModule VARCHAR2(60),
- Argument VARCHAR2(255),
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-
-CREATE SEQUENCE TEMPLATES_seq;
-CREATE TABLE Templates (
- id NUMBER(11,0)
- CONSTRAINT Templates_Key PRIMARY KEY,
- Queue NUMBER(11,0) DEFAULT 0 NOT NULL,
- Name VARCHAR2(200) NOT NULL,
- Description VARCHAR2(255),
- Type VARCHAR2(16),
- Language VARCHAR2(16),
- TranslationOf NUMBER(11,0) DEFAULT 0 NOT NULL,
- Content CLOB,
- LastUpdated DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE
-);
-
-
-CREATE SEQUENCE OBJECTCUSTOMFIELDS_seq;
-CREATE TABLE ObjectCustomFields (
- id NUMBER(11,0)
- CONSTRAINT ObjectCustomFields_Key PRIMARY KEY,
- CustomField NUMBER(11,0) NOT NULL,
- ObjectId NUMBER(11,0) NOT NULL,
- SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-
-CREATE SEQUENCE OBJECTCUSTOMFIELDVALUES_seq;
-CREATE TABLE ObjectCustomFieldValues (
- id NUMBER(11,0)
- CONSTRAINT ObjectCustomFieldValues_Key PRIMARY KEY,
- CustomField NUMBER(11,0) NOT NULL,
- ObjectType VARCHAR2(25) NOT NULL,
- ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
- SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
- Content VARCHAR2(255),
- LargeContent CLOB,
- ContentType VARCHAR2(80),
- ContentEncoding VARCHAR2(80),
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-
-CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content);
-CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId);
-
-CREATE SEQUENCE CUSTOMFIELDS_seq;
-CREATE TABLE CustomFields (
- id NUMBER(11,0)
- CONSTRAINT CustomFields_Key PRIMARY KEY,
- Name VARCHAR2(200),
- Type VARCHAR2(200),
- MaxValues NUMBER(11,0) DEFAULT 0 NOT NULL,
- Pattern CLOB,
- Repeated NUMBER(11,0) DEFAULT 0 NOT NULL,
- Description VARCHAR2(255),
- SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
- LookupType VARCHAR2(255),
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE,
- Disabled NUMBER(11,0) DEFAULT 0 NOT NULL
-);
-
-
-CREATE SEQUENCE CUSTOMFIELDVALUES_seq;
-CREATE TABLE CustomFieldValues (
- id NUMBER(11,0)
- CONSTRAINT CustomFieldValues_Key PRIMARY KEY,
- CustomField NUMBER(11,0),
- Name VARCHAR2(200),
- Description VARCHAR2(255),
- SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
-
-CREATE SEQUENCE ATTRIBUTES_seq;
-CREATE TABLE Attributes (
- id NUMBER(11,0) PRIMARY KEY,
- Name VARCHAR2(255) NOT NULL,
- Description VARCHAR2(255),
- Content CLOB,
- ContentType VARCHAR(16),
- ObjectType VARCHAR2(25) NOT NULL,
- ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL,
- Creator NUMBER(11,0) DEFAULT 0 NOT NULL,
- Created DATE,
- LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
- LastUpdated DATE
-);
-
-CREATE INDEX Attributes1 on Attributes(Name);
-CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId);
-
-
-CREATE TABLE sessions (
- id VARCHAR2(32)
- CONSTRAINT Sessions_Key PRIMARY KEY,
- a_session CLOB,
- LastUpdated DATE
-);
-
diff --git a/rt/etc/schema.mysql b/rt/etc/schema.mysql
deleted file mode 100755
index b7d53f8..0000000
--- a/rt/etc/schema.mysql
+++ /dev/null
@@ -1,463 +0,0 @@
-# {{{ Attachments
-
-CREATE TABLE Attachments (
- id INTEGER NOT NULL AUTO_INCREMENT,
- TransactionId integer NOT NULL ,
- Parent integer NOT NULL DEFAULT 0 ,
- MessageId varchar(160) NULL ,
- Subject varchar(255) NULL ,
- Filename varchar(255) NULL ,
- ContentType varchar(80) NULL ,
- ContentEncoding varchar(80) NULL ,
- Content LONGTEXT NULL ,
- Headers LONGTEXT NULL ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
-CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
-# }}}
-
-# {{{ Queues
-CREATE TABLE Queues (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NOT NULL ,
- Description varchar(255) NULL ,
- CorrespondAddress varchar(120) NULL ,
- CommentAddress varchar(120) NULL ,
- InitialPriority integer NOT NULL DEFAULT 0 ,
- FinalPriority integer NOT NULL DEFAULT 0 ,
- DefaultDueIn integer NOT NULL DEFAULT 0 ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- Disabled int2 NOT NULL DEFAULT 0 ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
-CREATE INDEX Queues2 ON Queues (Disabled) ;
-
-# }}}
-
-# {{{ Links
-
-CREATE TABLE Links (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Base varchar(240) NULL ,
- Target varchar(240) NULL ,
- Type varchar(20) NOT NULL ,
- LocalTarget integer NOT NULL DEFAULT 0 ,
- LocalBase integer NOT NULL DEFAULT 0 ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Links2 ON Links (Base, Type) ;
-CREATE INDEX Links3 ON Links (Target, Type) ;
-CREATE INDEX Links4 ON Links (Type,LocalBase);
-
-# }}}
-
-# {{{ Principals
-
-CREATE TABLE Principals (
- id INTEGER AUTO_INCREMENT not null,
- PrincipalType VARCHAR(16) not null,
- ObjectId integer, # foreign key to Users or Groups, depending
- Disabled int2 NOT NULL DEFAULT 0 ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Principals2 ON Principals (ObjectId);
-
-# }}}
-
-# {{{ Groups
-
-CREATE TABLE Groups (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NULL ,
- Description varchar(255) NULL ,
- Domain varchar(64),
- Type varchar(64),
- Instance integer,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id);
-CREATE INDEX Groups2 On Groups (Type, Instance);
-
-# }}}
-
-# {{{ ScripConditions
-
-CREATE TABLE ScripConditions (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NULL ,
- Description varchar(255) NULL ,
- ExecModule varchar(60) NULL ,
- Argument varchar(255) NULL ,
- ApplicableTransTypes varchar(60) NULL ,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ Transactions
-CREATE TABLE Transactions (
- id INTEGER NOT NULL AUTO_INCREMENT,
- ObjectType varchar(64) NOT NULL,
- ObjectId integer NOT NULL DEFAULT 0 ,
- TimeTaken integer NOT NULL DEFAULT 0 ,
- Type varchar(20) NULL ,
- Field varchar(40) NULL ,
- OldValue varchar(255) NULL ,
- NewValue varchar(255) NULL ,
- ReferenceType varchar(255) NULL,
- OldReference integer NULL ,
- NewReference integer NULL ,
- Data varchar(255) NULL ,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
-
-# }}}
-
-# {{{ Scrips
-
-CREATE TABLE Scrips (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Description varchar(255),
- ScripCondition integer NOT NULL DEFAULT 0 ,
- ScripAction integer NOT NULL DEFAULT 0 ,
- ConditionRules text NULL ,
- ActionRules text NULL ,
- CustomIsApplicableCode text NULL ,
- CustomPrepareCode text NULL ,
- CustomCommitCode text NULL ,
- Stage varchar(32) NULL ,
- Queue integer NOT NULL DEFAULT 0 ,
- Template integer NOT NULL DEFAULT 0 ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ ACL
-CREATE TABLE ACL (
- id INTEGER NOT NULL AUTO_INCREMENT,
- PrincipalType varchar(25) NOT NULL, #"User" "Group", "Owner", "Cc" "AdminCc", "Requestor", "Requestor"
-
- PrincipalId integer NOT NULL , #Foreign key to principals
- RightName varchar(25) NOT NULL ,
- ObjectType varchar(25) NOT NULL ,
- ObjectId integer NOT NULL default 0,
- DelegatedBy integer NOT NULL default 0, #foreign key to principals with a userid
- DelegatedFrom integer NOT NULL default 0, #foreign key to ACL
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX ACL1 on ACL(RightName, ObjectType, ObjectId,PrincipalType,PrincipalId);
-
-# }}}
-
-# {{{ GroupMembers
-
-CREATE TABLE GroupMembers (
- id INTEGER NOT NULL AUTO_INCREMENT,
- GroupId integer NOT NULL DEFAULT 0,
- MemberId integer NOT NULL DEFAULT 0, #Foreign key to Principals
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-CREATE UNIQUE INDEX GroupMembers1 on GroupMembers (GroupId, MemberId);
-
-
-# }}}
-
-# {{{ GroupMembersCache
-
-create table CachedGroupMembers (
- id int auto_increment,
- GroupId int, # foreign key to Principals
- MemberId int, # foreign key to Principals
- Via int, #foreign key to CachedGroupMembers. (may point to $self->id)
- ImmediateParentId int, #foreign key to prinicpals.
- # this points to the group that the member is
- # a member of, for ease of deletes.
- Disabled int2 NOT NULL DEFAULT 0 , # if this cached group member is a member of this group by way of a disabled
- # group or this group is disabled, this will be set to 1
- # this allows us to not find members of disabled subgroups when listing off
- # group members recursively.
- # Also, this allows us to have the ACL system elide members of disabled groups
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled);
-
-# }}}
-
-# {{{ Users
-
-CREATE TABLE Users (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NOT NULL ,
- Password varchar(40) NULL ,
- Comments blob NULL ,
- Signature blob NULL ,
- EmailAddress varchar(120) NULL ,
- FreeformContactInfo blob NULL ,
- Organization varchar(200) NULL ,
- RealName varchar(120) NULL ,
- NickName varchar(16) NULL ,
- Lang varchar(16) NULL ,
- EmailEncoding varchar(16) NULL ,
- WebEncoding varchar(16) NULL ,
- ExternalContactInfoId varchar(100) NULL ,
- ContactInfoSystem varchar(30) NULL ,
- ExternalAuthId varchar(100) NULL ,
- AuthSystem varchar(30) NULL ,
- Gecos varchar(16) NULL ,
- HomePhone varchar(30) NULL ,
- WorkPhone varchar(30) NULL ,
- MobilePhone varchar(30) NULL ,
- PagerPhone varchar(30) NULL ,
- Address1 varchar(200) NULL ,
- Address2 varchar(200) NULL ,
- City varchar(100) NULL ,
- State varchar(100) NULL ,
- Zip varchar(16) NULL ,
- Country varchar(50) NULL ,
- Timezone varchar(50) NULL ,
- PGPKey text NULL,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-
-CREATE UNIQUE INDEX Users1 ON Users (Name) ;
-CREATE INDEX Users4 ON Users (EmailAddress);
-
-
-# }}}
-
-# {{{ Tickets
-
-CREATE TABLE Tickets (
- id INTEGER NOT NULL AUTO_INCREMENT,
- EffectiveId integer NOT NULL DEFAULT 0 ,
- Queue integer NOT NULL DEFAULT 0 ,
- Type varchar(16) NULL ,
- IssueStatement integer NOT NULL DEFAULT 0 ,
- Resolution integer NOT NULL DEFAULT 0 ,
- Owner integer NOT NULL DEFAULT 0 ,
- Subject varchar(200) NULL DEFAULT '[no subject]' ,
- InitialPriority integer NOT NULL DEFAULT 0 ,
- FinalPriority integer NOT NULL DEFAULT 0 ,
- Priority integer NOT NULL DEFAULT 0 ,
- TimeEstimated integer NOT NULL DEFAULT 0 ,
- TimeWorked integer NOT NULL DEFAULT 0 ,
- Status varchar(10) NULL ,
- TimeLeft integer NOT NULL DEFAULT 0 ,
- Told DATETIME NULL ,
- Starts DATETIME NULL ,
- Started DATETIME NULL ,
- Due DATETIME NULL ,
- Resolved DATETIME NULL ,
-
-
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- Disabled int2 NOT NULL DEFAULT 0 ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Tickets1 ON Tickets (Queue, Status) ;
-CREATE INDEX Tickets2 ON Tickets (Owner) ;
-CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type) ;
-
-# }}}
-
-# {{{ ScripActions
-
-CREATE TABLE ScripActions (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NULL ,
- Description varchar(255) NULL ,
- ExecModule varchar(60) NULL ,
- Argument varchar(255) NULL ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ Templates
-
-CREATE TABLE Templates (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Queue integer NOT NULL DEFAULT 0 ,
- Name varchar(200) NOT NULL ,
- Description varchar(255) NULL ,
- Type varchar(16) NULL ,
- Language varchar(16) NULL ,
- TranslationOf integer NOT NULL DEFAULT 0 ,
- Content blob NULL ,
- LastUpdated DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ ObjectCustomFieldValues
-
-CREATE TABLE ObjectCustomFieldValues (
- id INTEGER NOT NULL AUTO_INCREMENT,
- CustomField int NOT NULL ,
- ObjectType varchar(255) NOT NULL, # Final target of the Object
- ObjectId int NOT NULL , # New -- Replaces Ticket
- SortOrder integer NOT NULL DEFAULT 0 , # New -- ordering for multiple values
-
- Content varchar(255) NULL ,
- LargeContent LONGTEXT NULL, # New -- to hold 255+ strings
- ContentType varchar(80) NULL, # New -- only text/* gets searched
- ContentEncoding varchar(80) NULL , # New -- for binary Content
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- Disabled int2 NOT NULL DEFAULT 0 , # New -- whether the value was current
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content);
-CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId);
-
-# }}}
-
-# {{{ CustomFields
-
-CREATE TABLE CustomFields (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(200) NULL ,
- Type varchar(200) NULL , # Changed -- 'Single' and 'Multiple' is moved out
- MaxValues integer, # New -- was 'Single'(1) and 'Multiple'(0)
- Pattern varchar(255) NULL , # New -- Must validate against this
- Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry
- Description varchar(255) NULL ,
- SortOrder integer NOT NULL DEFAULT 0 ,
- LookupType varchar(255) NOT NULL,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- Disabled int2 NOT NULL DEFAULT 0 ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ ObjectCustomFields
-
-CREATE TABLE ObjectCustomFields (
- id INTEGER NOT NULL AUTO_INCREMENT,
- CustomField int NOT NULL ,
- ObjectId integer NOT NULL,
- SortOrder integer NOT NULL DEFAULT 0 ,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-# }}}
-
-# {{{ CustomFieldValues
-
-CREATE TABLE CustomFieldValues (
- id INTEGER NOT NULL AUTO_INCREMENT,
- CustomField int NOT NULL ,
- Name varchar(200) NULL ,
- Description varchar(255) NULL ,
- SortOrder integer NOT NULL DEFAULT 0 ,
-
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
-
-# }}}
-
-
-# {{{ Attributes
-
-CREATE TABLE Attributes (
- id INTEGER NOT NULL AUTO_INCREMENT,
- Name varchar(255) NULL ,
- Description varchar(255) NULL ,
- Content text,
- ContentType varchar(16),
- ObjectType varchar(64),
- ObjectId integer, # foreign key to anything
- Creator integer NOT NULL DEFAULT 0 ,
- Created DATETIME NULL ,
- LastUpdatedBy integer NOT NULL DEFAULT 0 ,
- LastUpdated DATETIME NULL ,
- PRIMARY KEY (id)
-) TYPE=InnoDB;
-
-CREATE INDEX Attributes1 on Attributes(Name);
-CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId);
-
-# }}}
-
-# {{{ Sessions
-
-# sessions is used by Apache::Session to keep sessions in the database.
-# We should have a reaper script somewhere.
-
-CREATE TABLE sessions (
- id char(32) NOT NULL,
- a_session LONGTEXT,
- LastUpdated TIMESTAMP,
- PRIMARY KEY (id)
-);
-
-# }}}
diff --git a/rt/etc/upgrade/2.1.71 b/rt/etc/upgrade/2.1.71
deleted file mode 100644
index cb89a3a..0000000
--- a/rt/etc/upgrade/2.1.71
+++ /dev/null
@@ -1,211 +0,0 @@
-@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/CustomFields/GroupRights.html b/rt/html/Admin/CustomFields/GroupRights.html
deleted file mode 100644
index 86dd0d2..0000000
--- a/rt/html/Admin/CustomFields/GroupRights.html
+++ /dev/null
@@ -1,119 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/CustomFieldTabs,
- id => $id,
- current_tab => "Admin/CustomFields/GroupRights.html?id=".$id,
- Title => $title
-&>
-<& /Elements/ListActions, actions => \@results &>
-
- <form method="post" action="GroupRights.html">
- <input type="hidden" class="hidden" name="id" value="<% $CustomFieldObj->id %>" />
-
-
-<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 => $CustomFieldObj &>
- </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 => $CustomFieldObj &>
- </td>
- </tr>
-% }
-</table>
-
- <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
-
- </form>
-
-<%INIT>
-
-
-
-
-
-
-if (!defined $id) {
- $m->comp("/Elements/Error", Why => loc("No CustomField defined"));
-}
-
-my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
-$CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't load CustomField [_1]",$id));
-
-my $Groups;
-my @results = ProcessACLChanges(\%ARGS);
-my $title = loc('Modify group rights for custom field [_1]', $CustomFieldObj->Name);
-
-</%INIT>
-
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/CustomFields/Modify.html b/rt/html/Admin/CustomFields/Modify.html
deleted file mode 100644
index e3dfad7..0000000
--- a/rt/html/Admin/CustomFields/Modify.html
+++ /dev/null
@@ -1,258 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/CustomFieldTabs,
- id => $CustomFieldObj->Id ,
- current_tab => $current_tab,
- Title => $title &>
-<& /Elements/ListActions, actions => \@results &>
-
-
-<form method="post" action="Modify.html" name="ModifyCustomField">
-<input type="hidden" class="hidden" name="id" value="<%$id %>" />
-<table>
-<tr>
-<td class="label"><&|/l&>Name</&></td>
-<td><input name="Name" value="<%$CustomFieldObj->Name%>" size="20" /></td></tr>
-<tr>
-<td class="label"><&|/l&>Description</&></td>
-<td><input name="Description" value="<%$CustomFieldObj->Description%>" size="80" /></td>
-</tr>
-
-<tr>
-<td class="label"><&|/l&>Type</&></td>
-<td><& /Admin/Elements/SelectCustomFieldType,
- Name => "TypeComposite",
- Default => $CustomFieldObj->TypeComposite, &>
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Applies to</&></td>
-<td><& /Admin/Elements/SelectCustomFieldLookupType,
- Name => "LookupType",
- Default => $CustomFieldObj->LookupType, &>
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Validation</&></td>
-<td>
-<& /Widgets/ComboBox,
- Name => 'Pattern',
- Default => $CustomFieldObj->Pattern,
- Size => 20,
- Values => [
- '(?#Mandatory).',
- '(?#Digits)^[\d.]+$',
- '(?#Year)^[12]\d{3}$',
- ],
-&>
-</td></tr>
-</tr>
-<tr>
-<td class="label">&nbsp;</td>
-<td>
-<input type="hidden" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> /> <&|/l&>Enabled (Unchecking this box disables this custom field)</&>
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Link values to</&></td>
-</td>
-<td><input size="60" name="LinkValueTo" value="<%$CustomFieldObj->LinkValueTo%>" />
-<div class="hints">
-<&|/l&>RT can make this custom field's values into hyperlinks to another service.</&>
-<&|/l&>Fill in this field with a URL.</&>
-<&|/l&>RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively</&>
-</div>
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Include page</&></td>
-</td>
-<td><input size="60" name="IncludeContentForValue" value="<%$CustomFieldObj->IncludeContentForValue%>" />
-<div class="hints">
-<&|/l&>RT can include content from another web service when showing this custom field.</&>
-<&|/l&>Fill in this field with a URL.</&>
-<&|/l&>RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively</&>
-<i><&|/l&>Some browsers may only load content from the same domain as your RT server.</&></i>
-</div>
-</td>
-</tr>
-
-
-
-</table>
-<br />
-% if ($CustomFieldObj->Id && $CustomFieldObj->IsSelectionType) {
-<h2><&|/l&>Values</&></h2>
-<div>
-<& /Admin/Elements/EditCustomFieldValues, CustomField => $CustomFieldObj &>
-<& /Admin/Elements/AddCustomFieldValue, CustomField => $CustomFieldObj &>
-</div>
-% }
-<&/Elements/Submit&>
-</form>
-
-
-
-<%INIT>
-
-
-
-my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
-my ( $title, @results, $EnabledChecked, $Disabled);
-$EnabledChecked = "CHECKED";
-
-if ( !$id ) {
- $title = loc("Create a CustomField");
- $id = 'new';
-}
-else {
-
- if ( $id eq 'new' ) {
- my ( $val, $msg ) = $CustomFieldObj->Create(Name => $Name,
- TypeComposite => $TypeComposite,
- LookupType => $LookupType,
- Description => $Description,
- Pattern => $Pattern,);
- $m->comp("/Elements/Error", Why => loc( "Could not create CustomField", $msg ) ) unless ($val);
- push @results, $msg;
- $title = loc( 'Created CustomField [_1]', $CustomFieldObj->Name() );
- }
- else {
- $CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc('No CustomField') );
- $title = loc( 'Editing CustomField [_1]', $CustomFieldObj->Name() );
-
- my @attribs = qw( Pattern Name TypeComposite LookupType Description LinkValueTo IncludeContentForValue);
- my @aresults = UpdateRecordObject( AttributesRef => \@attribs,
- Object => $CustomFieldObj,
- ARGSRef => \%ARGS );
-
- push @results, @aresults;
-
- #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 != $CustomFieldObj->Disabled) ) {
- my ($code, $msg) = $CustomFieldObj->SetDisabled($Disabled);
- push @results, loc('Enabled status: [_1]', loc_fuzzy($msg));
- }
-
- if ($CustomFieldObj->Disabled()) {
- $EnabledChecked ="";
- }
- }
-
- $id = $CustomFieldObj->id;
-}
-
-
-
-
-my $paramtag = "CustomField-".$CustomFieldObj->Id."-Value-";
-# Delete any fields that want to be deleted
-foreach my $key (keys %ARGS) {
-
- next unless ($key =~ /^Delete-$paramtag(\d+)$/);
- my ($val, $msg) = $CustomFieldObj->DeleteValue($1);
- push (@results, $msg);
-
-
-}
-# Update any existing values
-my $values = $CustomFieldObj->ValuesObj;
-while (my $value = $values->Next) {
- foreach my $attr qw(Name Description SortOrder Category) {
- my $param = $paramtag.$value->Id."-".$attr;
-
- if ( $ARGS{$param} && ($value->$attr() ne $ARGS{$param})) {
- my $mutator = "Set$attr";
- my ($id, $msg) = $value->$mutator($ARGS{$param});
- push (@results, $msg);
- }
- }
-
-
-}
-
-
-
-# Add any new values
-if ($ARGS{$paramtag."new-Name"}) {
- my ($id, $msg) = $CustomFieldObj->AddValue (
- map { $_ => $ARGS{$paramtag."new-$_"} }
- qw( Name Description SortOrder Category )
- );
- push (@results, $msg);
-}
-
-my $current_tab;
-if ($ARGS{'Create'}){
- $current_tab = "Admin/CustomFields/Modify.html?Create=1";
-} else {
- $current_tab = "Admin/CustomFields/Modify.html?id=".$id;
- }
-
-
-</%INIT>
-<%ARGS>
-$id => undef
-$TypeComposite => undef
-$LookupType => undef
-$MaxValues => undef
-$SortOrder => undef
-$Description => undef
-$Pattern => undef
-$Name => undef
-$SetEnabled => undef
-$Enabled => undef
-</%ARGS>
diff --git a/rt/html/Admin/CustomFields/Objects.html b/rt/html/Admin/CustomFields/Objects.html
deleted file mode 100644
index d5a7c35..0000000
--- a/rt/html/Admin/CustomFields/Objects.html
+++ /dev/null
@@ -1,147 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/CustomFieldTabs,
- id => $id,
- current_tab => "Admin/CustomFields/Objects.html?id=".$id,
- Title => $title
- &>
-
-<& /Elements/ListActions, actions => \@results &>
-
-<form action="Objects.html" method="post">
-<input type="hidden" class="hidden" name="id" value="<% $id %>" />
-<input type="hidden" class="hidden" name="UpdateObjs" value="1" />
-
-<h2><&|/l&>Selected objects</&></h2>
-<& /Admin/Elements/PickObjects, Objects => \@AssignedObjs, id => $id, Checked => 1 &>
-<h2><&|/l&>Unselected objects</&></h2>
-<& /Admin/Elements/PickObjects, Objects => \@UnassignedObjs, id => $id &>
-
-<& /Elements/Submit, CheckAll => 1, ClearAll => 1 &>
-</form>
-
-<%INIT>
-my $CF = RT::CustomField->new($session{'CurrentUser'});
-$CF->Load($id) or Abort(loc("Could not load CustomField [_1]"), $id);
-my $LookupType = $CF->LookupType;
-$LookupType =~ /^(.*?)-/ ||
- Abort(loc("Object of type [_1] cannot take custom fields", $LookupType));
-
-my $Class = $1;
-my $CollectionClass;
-if (UNIVERSAL::can($Class.'Collection', 'new') ) {
-$CollectionClass = $Class.'Collection';
-
-} elsif (UNIVERSAL::can($Class.'es', 'new') ) {
- $CollectionClass = $Class.'es';
-
-} elsif (UNIVERSAL::can($Class.'s', 'new') ) {
- $CollectionClass = $Class.'s';
-
-} else {
- Abort(loc("Can't find a collection class for '[_1]'", $Class));
-}
-
-
-my $title = loc('Modify associated objects for [_1]', $CF->Name);
-
-my $Objects = $CollectionClass->new($session{'CurrentUser'});
-my (@results);
-my (@AssignedObjs, @UnassignedObjs);
-
-$Objects->UnLimit;
-$Objects->OrderBy( FIELD => 'Name' );
-
-
-my $ObjectCFs;
-$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
-$ObjectCFs->UnLimit;
-$ObjectCFs->LimitToCustomField($id);
-
-my %seen;
-while (my $OCF = $ObjectCFs->Next) {
- $seen{$OCF->ObjectId}++;
-}
-
-while (my $obj = $Objects->Next) {
- my $obj_id = $obj->Id;
-
- if ($UpdateObjs) {
- # Go through and delete all the custom field relationships that this object
- # no longer has
- my $key = "Object-$obj_id-CF-$id";
- if ($ARGS{$key}) {
- if (!$seen{$obj_id}) {
- my ($val, $msg) = $CF->AddToObject($obj);
- push (@results, $msg);
- push @UnassignedObjs, $obj if !$val;
- }
- }
- else {
- push @UnassignedObjs, $obj;
- if ($seen{$obj_id}) {
- my ($val, $msg) = $CF->RemoveFromObject($obj);
- push (@results, $msg);
- pop @UnassignedObjs if !$val;
- }
- }
- }
- elsif (!$seen{$obj_id}) {
- push @UnassignedObjs, $obj;
- }
- next if @UnassignedObjs and $UnassignedObjs[-1] == $obj;
- push @AssignedObjs, $obj;
-}
-
-</%INIT>
-<%ARGS>
-$id => undef
-$FindDisabledObjects => 0
-$UpdateObjs => 0
-</%ARGS>
diff --git a/rt/html/Admin/CustomFields/UserRights.html b/rt/html/Admin/CustomFields/UserRights.html
deleted file mode 100644
index 01158fd..0000000
--- a/rt/html/Admin/CustomFields/UserRights.html
+++ /dev/null
@@ -1,170 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/CustomFieldTabs, id => $id,
-current_tab => "Admin/CustomFields/UserRights.html?id=".$id,
-Title => $title, &>
-<& /Elements/ListActions, actions => \@results &>
-
- <form method="post" action="UserRights.html">
- <input type="hidden" class="hidden" name="id" value="<% $CustomFieldObj->id %>" />
-
-
-<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 => $CustomFieldObj &>
- </td>
- </tr>
-% }
- </table>
-
- <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
-
- </form>
-
-<%INIT>
-
- #Update the acls.
- my @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::CustomField') {
- $obj = RT::CustomField->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::CustomField') {
- $obj = RT::CustomField->new($session{'CurrentUser'});
- $obj->Load($object_id);
- } else {
- 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);
- }
-}
-
-
-# {{{ Deal with setting up the display of current rights.
-
-
-
-if (!defined $id) {
- $m->comp("/Elements/Error", Why => loc("No Class defined"));
-}
-
-my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
-$CustomFieldObj->Load($id) || $m->comp("/Elements/Error", Why => loc("Couldn't load Class [_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 $title = loc('Modify user rights for custom field [_1]', $CustomFieldObj->Name);
-
-# }}}
-
-</%INIT>
-
-<%ARGS>
-$id => undef
-$UserString => undef
-$UserOp => undef
-$UserField => undef
-</%ARGS>
diff --git a/rt/html/Admin/CustomFields/index.html b/rt/html/Admin/CustomFields/index.html
deleted file mode 100644
index 49a56ea..0000000
--- a/rt/html/Admin/CustomFields/index.html
+++ /dev/null
@@ -1,93 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc('Select a Custom Field') &>
-<& /Admin/Elements/CustomFieldTabs,
- current_tab => 'Admin/CustomFields/',
- Title => loc('Select a Custom Field') &>
-
-% my @types;
-% my $prev_lookup = '';
-% while (my $CustomFieldObj = $CustomFields->Next) {
-% $CustomFieldObj->CurrentUserHasRight('AdminCustomField') or next;
-% my $lookup = $CustomFieldObj->FriendlyLookupType;
-% if ($lookup ne $prev_lookup) {
-% if ($prev_lookup) {
-</ul>
-% }
-<h2><% loc("Custom Fields for [_1]", $lookup) %></h2>
-<ul>
-% $prev_lookup = $lookup;
-% push @types, [$lookup, $CustomFieldObj->LookupType];
-% }
-%
-<li>
-<a href="Modify.html?id=<%$CustomFieldObj->id()%>"><%$CustomFieldObj->Name%>: <%$CustomFieldObj->Description%></a>
-</li>
-% }
-% if ($prev_lookup) {
-</ul>
-% }
-
-<form action="<%$RT::WebPath%>/Admin/CustomFields/index.html" method="get">
-<&|/l&>Only show custom fields for:</&>
-<select name="type">
-% for (@types) {
-<option value="<% $_->[1] %>"><% $_->[0] %></option>
-% }
-</select> <input type="submit" value="<%loc('Go!')%>" />
-</form>
-
-<%args>
-$type => undef
-</%args>
-<%INIT>
-my $CustomFields = RT::CustomFields->new($session{'CurrentUser'});
-$CustomFields->UnLimit();
-$CustomFields->LimitToLookupType($type) if defined $type;
-$CustomFields->OrderByCols( { FIELD => 'LookupType' }, { FIELD => 'Name' } );
-</%INIT>
diff --git a/rt/html/Admin/Elements/AddCustomFieldValue b/rt/html/Admin/Elements/AddCustomFieldValue
deleted file mode 100644
index 82a4a6e..0000000
--- a/rt/html/Admin/Elements/AddCustomFieldValue
+++ /dev/null
@@ -1,74 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<b><&|/l&>Add Value</&></b>
-<table border="0">
-<tr><td>
-<&|/l&>Sort</&>:<br />
-<input size="3" name="CustomField-<%$CustomField->Id%>-Value-new-SortOrder" />
-</td>
-<td>
-<&|/l&>Name</&>:<br />
-<input type="text" size="30" name="CustomField-<%$CustomField->Id%>-Value-new-Name" />
-</td>
-<td>
-<&|/l&>Description</&>:<br />
-<input type="text" size="50" name="CustomField-<%$CustomField->Id%>-Value-new-Description" />
-</td>
-% if ($CustomField->Type ne 'Combobox') {
-<td>
-<&|/l&>Category</&>:<br />
-<input type="text" size="10" name="CustomField-<%$CustomField->Id%>-Value-new-Category" />
-</td>
-% }
-</tr>
-</table>
-<%init>
-</%init>
-<%args>
-$CustomField => undef
-</%args>
diff --git a/rt/html/Admin/Elements/ConfigureMyRT b/rt/html/Admin/Elements/ConfigureMyRT
deleted file mode 100644
index a111fa5..0000000
--- a/rt/html/Admin/Elements/ConfigureMyRT
+++ /dev/null
@@ -1,80 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Action
-$OnSave
-$items
-$panes
-$current_portlets
-</%args>
-<%init>
-my $portlets = $current_portlets;
-my @panes;
-for my $pane (@$panes) {
- push @panes, $m->comp(
- '/Widgets/SelectionBox:new',
- Action => $Action,
- Name => $pane,
- Available => $items,
- AutoSave => 1,
- OnSubmit => sub {
- my $sel = shift;
- $portlets->{$pane} = [
- map { m/(\w+)-(.*)$}/;
- { type => $1,
- name => $2 } } @{ $sel->{Current} }
- ];
- $OnSave->( $portlets, $pane );
- },
- Selected => [ map { join( '-', @{$_}{qw/type name/} ) }
- @{ $portlets->{$pane} } ]
- );
-}
-
-return @panes;
-</%init>
diff --git a/rt/html/Admin/Elements/CreateUserCalled b/rt/html/Admin/Elements/CreateUserCalled
deleted file mode 100644
index b7ef0ba..0000000
--- a/rt/html/Admin/Elements/CreateUserCalled
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form method="get" action="<% $RT::WebPath %>/Admin/Users/Create.html">
-<&|/l&>New user called</&> <input name="Name" size="10" /><input type="submit" class="button" value="<&|/l&>Create</&>" />
-</form>
diff --git a/rt/html/Admin/Elements/CustomFieldTabs b/rt/html/Admin/Elements/CustomFieldTabs
deleted file mode 100644
index 0043eba..0000000
--- a/rt/html/Admin/Elements/CustomFieldTabs
+++ /dev/null
@@ -1,118 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Tabs,
- current_tab => 'Admin/CustomFields/',
- subtabs => $tabs,
- current_subtab => $current_tab,
- Title => $Title &>
-<%INIT>
-my $tabs;
-
-if ($id) {
- my $cf = RT::CustomField->new( $session{'CurrentUser'} );
- $cf->Load($id);
- $tabs = {
- this => {
- title => $cf->Name,
- path => "Admin/CustomFields/Modify.html?id=" . $id,
- current_subtab => $current_tab,
-
- subtabs => {
-
- C => { title => loc('Basics'),
- path => "Admin/CustomFields/Modify.html?id=" . $id,
- },
- F => { title => loc('Group Rights'),
- path => "Admin/CustomFields/GroupRights.html?id="
- . $id, },
- G => {
- title => loc('User Rights'),
- path => "Admin/CustomFields/UserRights.html?id=" . $id,
- },
-
- } }
-
- };
-
-
- if ($cf->LookupType =~ /^RT::Queue/io) {
- $tabs->{'this'}->{subtabs}->{D} = {
- title => loc('Applies to'),
- path => "Admin/CustomFields/Objects.html?id=" . $id,
- };
- }
-}
-
-if ($session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminCustomField')) {
- $tabs->{"A"} = { title => loc('Select custom field'),
- path => "Admin/CustomFields/",
- };
- $tabs->{"B"} = { title => loc('New custom field'),
- path => "Admin/CustomFields/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;
- }
-}
-if( $id ) { $current_tab = "Admin/CustomFields/Modify.html?id=" . $id }
-</%INIT>
-<%ARGS>
-$Title => undef
-$id => undef
-$current_tab => undef
-$subtabs => undef
-$current_subtab => undef
-</%ARGS>
diff --git a/rt/html/Admin/Elements/EditCustomField b/rt/html/Admin/Elements/EditCustomField
deleted file mode 100644
index e19c00b..0000000
--- a/rt/html/Admin/Elements/EditCustomField
+++ /dev/null
@@ -1,159 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ListActions, actions => \@results &>
-
-
-<form method="post" action="CustomField.html">
-<input type="hidden" class="hidden" name="CustomField" value="<%$id %>" />
-<input type="hidden" class="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" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="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, Label => loc('Create') &>
-</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,
- );
-
- # if there is an error, then abort. But since at this point there's
- # stuff already printed, clear it out.
- # (This only works in conjunction with temporarily turning autoflush
- # off in the files that use this component.)
- unless ($val) {
- $m->clear_buffer;
- Abort(loc("Could not create CustomField: [_1]", $msg));
- }
- 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
deleted file mode 100644
index 8592779..0000000
--- a/rt/html/Admin/Elements/EditCustomFieldValues
+++ /dev/null
@@ -1,96 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if (!$values->Count) {
-<p><em><&|/l&>(no values)</&></em></p>
-% return;
-% }
-<i><&|/l&>(Check box to delete)</&></i>
-<table>
-<tr>
-<td>&nbsp;</td>
-<td><&|/l&>Sort</&></td>
-<td><&|/l&>Name</&></td>
-<td><&|/l&>Description</&></td>
-% if ($CustomField->Type ne 'Combobox') {
-<td><&|/l&>Category</&></td>
-% }
-</tr>
-% while (my $value = $values->Next) {
-<tr>
-<td>
-<input type="checkbox" class="checkbox" name="Delete-CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>" />
-</td>
-<td>
-<input size="3" name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-SortOrder" value="<%$value->SortOrder%>" />
-</td>
-<td>
-<input type="text" size="30" name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-Name" value="<%$value->Name%>" />
-</td>
-<td>
-<font size="-1">
-<input type="text" size="50" name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-Description" value="<%$value->Description%>" />
-</font>
-</td>
-% if ($CustomField->Type ne 'Combobox') {
-<td>
-<font size="-1">
-<input type="text" size="10" name="CustomField-<%$CustomField->Id%>-Value-<%$value->Id%>-Category" value="<%$value->Category%>" />
-</font>
-</td>
-% }
-</tr>
-% }
-</table>
-<%init>
-
-my $values = $CustomField->ValuesObj();
-
-</%init>
-<%args>
-$CustomField => undef
-</%args>
diff --git a/rt/html/Admin/Elements/EditCustomFields b/rt/html/Admin/Elements/EditCustomFields
deleted file mode 100644
index 77eadbd..0000000
--- a/rt/html/Admin/Elements/EditCustomFields
+++ /dev/null
@@ -1,205 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ListActions, actions => \@results &>
-
-<form action="<%$RT::WebPath%><% $m->request_comp->path |n %>" method="post" name="EditCustomFields">
-<input type="hidden" class="hidden" name="id" value="<% $Object->Id %>" />
-<input type="hidden" class="hidden" name="ObjectType" value="<% $ObjectType %>" />
-<input type="hidden" class="hidden" name="SubType" value="<% $SubType %>" />
-<input type="hidden" class="hidden" name="UpdateCFs" value="1" />
-
-% if ($Object->Id) {
-<h2><&|/l&>Global Custom Fields</&></h2>
-<& PickCustomFields, CustomFields => \@GlobalCFs, ReadOnly => 1, id => $id, SubType => $SubType &>
-% }
-<h2><&|/l&>Selected Custom Fields</&></h2>
-<& PickCustomFields, CustomFields => [$ObjectCFs->CustomFields], id => $id, Checked => 1, SubType => $SubType &>
-<h2><&|/l&>Unselected Custom Fields</&></h2>
-<& PickCustomFields, CustomFields => \@UnassignedCFs, id => $id, SubType => $SubType &>
-
-<& /Elements/Submit, CheckAll => 1, ClearAll => 1 &>
-</form>
-
-
-<%INIT>
-my $CustomFields = RT::CustomFields->new($session{'CurrentUser'});
-my @results;
-my (@GlobalCFs, @UnassignedCFs);
-
-my $id = $Object->Id;
-if ($id and !$Object->CurrentUserHasRight('AssignCustomFields')) {
- $m->out('<p><i>', loc('(No custom fields)'), '</i></p>');
- return;
-}
-
-my $lookup = $ObjectType;
-$lookup .= "-$SubType" if $SubType;
-
-$CustomFields->LimitToLookupType($lookup);
-$CustomFields->OrderBy( FIELD => 'Name' );
-
-
-my ($GlobalCFs, $ObjectCFs);
-$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
-$ObjectCFs->UnLimit;
-$ObjectCFs->LimitToObjectId($id);
-$ObjectCFs->LimitToLookupType($lookup);
-
-# Check sanity of SortOrders
-my %SortOrders;
-$SortOrders{$_->SortOrder}++
- while ($_ = $ObjectCFs->Next);
-
-# If there are duplicates, run though and squash them
-if (grep {$_ > 1} values %SortOrders) {
- my $i = 1;
- while (my $ObjectCF = $ObjectCFs->Next) {
- $ObjectCF->SetSortOrder($i++);
- }
- $ObjectCFs->GotoFirstItem;
-}
-
-# {{{ deal with moving sortorder of custom fields
-if ($CustomField and $Move) {
- my $SourceObj = RT::ObjectCustomField->new($session{'CurrentUser'});
- $SourceObj->LoadByCols( ObjectId => $id, CustomField => $CustomField );
-
- my $TargetObj;
- my $target_order = $SourceObj->SortOrder + $Move;
- while (my $ObjectCF = $ObjectCFs->Next) {
- my $this_order = $ObjectCF->SortOrder;
-
- # if we have an exact match, finish the loop now
- ($TargetObj = $ObjectCF, 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 = $ObjectCF;
- }
-
- 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
- }
-
- $ObjectCFs->GotoFirstItem;
-}
-# }}}
-
-if ($id) {
- $GlobalCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
- $GlobalCFs->LimitToObjectId(0);
- $GlobalCFs->LimitToLookupType($lookup);
-}
-
-while (my $cf = $CustomFields->Next) {
- my $cf_id = $cf->Id;
-
- if ($GlobalCFs and $GlobalCFs->HasEntryForCustomField($cf_id)) {
- push @GlobalCFs, $cf;
- next;
- }
-
- if ($UpdateCFs) {
- # Go through and delete all the custom field relationships that this object
- # no longer has
- my $key = "Object-$id-CF-$cf_id";
- if ($ARGS{$key}) {
- if (!$ObjectCFs->HasEntryForCustomField($cf_id)) {
- my ($val, $msg) = $cf->AddToObject($Object);
- push (@results, $msg);
- push @UnassignedCFs, $cf if !$val;
- }
- }
- else {
- push @UnassignedCFs, $cf;
- if ($ObjectCFs->HasEntryForCustomField($cf_id)) {
- my ($val, $msg) = $cf->RemoveFromObject($Object);
- push (@results, $msg);
- pop @UnassignedCFs if !$val;
- }
- }
- }
- elsif (!$ObjectCFs->HasEntryForCustomField($cf_id)) {
- push @UnassignedCFs, $cf;
- }
- else {
- }
-}
-
-# redo search...
-$ObjectCFs = RT::ObjectCustomFields->new($session{'CurrentUser'});
-$ObjectCFs->UnLimit;
-$ObjectCFs->LimitToObjectId($id);
-$ObjectCFs->LimitToLookupType($lookup);
-
-</%INIT>
-<%ARGS>
-$title => undef
-$Move => undef
-$Source => undef
-$CustomField => undef
-$FindDisabledCustomFields => undef
-$UpdateCFs => 0
-$Object
-$ObjectType
-$SubType => ''
-</%ARGS>
diff --git a/rt/html/Admin/Elements/EditQueueWatchers b/rt/html/Admin/Elements/EditQueueWatchers
deleted file mode 100644
index 1a1f18b..0000000
--- a/rt/html/Admin/Elements/EditQueueWatchers
+++ /dev/null
@@ -1,78 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%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" class="checkbox" name="Queue-<%$QueueObj->Id%>-DeleteWatcher-Type-<%$Watchers->Type%>-Principal-<%$watcher->MemberId%>" value="1"
- 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
deleted file mode 100644
index f3ad18b..0000000
--- a/rt/html/Admin/Elements/EditScrip
+++ /dev/null
@@ -1,183 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ListActions, actions => \@actions &>
-
-<form method="post" action="Scrip.html">
-<input type="hidden" class="hidden" name="id" value="<% $id %>" />
-<input type="hidden" class="hidden" name="Queue" value="<% $Queue %>" />
-
-<&| /Widgets/TitleBox, title => loc('Scrip Fields') &>
-<table>
-
-<tr><td align="right"><&|/l&>Description</&>:</td><td>
-<input name="Scrip-<% $id %>-Description" value="<% $ARGS{"Scrip-$id-Description"} || $scrip->Description %>" />
-</td></tr>
-
-<tr><td align="right"><&|/l&>Condition</&>:</td><td>
-<& /Admin/Elements/SelectScripCondition,
- Name => "Scrip-$id-ScripCondition",
- Default => $ARGS{"Scrip-$id-ScripCondition"} || $scrip->ConditionObj->Id,
-&></td></tr>
-
-<tr><td align="right"><&|/l&>Action</&>:</td><td>
-<& /Admin/Elements/SelectScripAction,
- Name => "Scrip-$id-ScripAction",
- Default => $ARGS{"Scrip-$id-ScripAction"} || $scrip->ActionObj->Id,
-&></td></tr>
-
-<tr><td align="right"><&|/l&>Template</&>:</td><td>
-<& /Admin/Elements/SelectTemplate,
- Name => "Scrip-$id-Template",
- Default => $ARGS{"Scrip-$id-Template"} || $scrip->TemplateObj->Id,
- Queue => $Queue,
-&></td></tr>
-
-<tr><td align="right"><&|/l&>Stage</&>:</td><td>
-<& /Admin/Elements/SelectStage,
- Name => "Scrip-$id-Stage",
- Default => $ARGS{"Scrip-$id-Stage"} || $scrip->Stage,
-&></td></tr>
-
-</table>
-</&>
-
-<& /Elements/Submit,
- Label => $SubmitLabel,
- Caption => loc("Be sure to save your changes"),
- Reset => 1,
-&><br />
-
-<&| /Widgets/TitleBox, title => loc('User Defined conditions and actions') &>
-<table>
-<tr><td colspan="2">
-<i><&|/l&>(Use these fields when you choose 'User Defined' for a condition or action)</&></i>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom condition</&>:</td><td>
-<textarea cols="80" rows="5" name="Scrip-<% $id %>-CustomIsApplicableCode"><% $ARGS{"Scrip-$id-CustomIsApplicableCode"} || $scrip->CustomIsApplicableCode %></textarea>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom action preparation code</&>:</td><td>
-<textarea cols="80" rows="5" name="Scrip-<% $id %>-CustomPrepareCode"><% $ARGS{"Scrip-$id-CustomPrepareCode"} || $scrip->CustomPrepareCode %></textarea>
-</td></tr>
-
-<tr><td class="labeltop"><&|/l&>Custom action cleanup code</&>:</td><td>
-<textarea cols="80" rows="5" name="Scrip-<% $id %>-CustomCommitCode"><% $ARGS{"Scrip-$id-CustomCommitCode"} || $scrip->CustomCommitCode %></textarea>
-</td></tr>
-
-</table>
-</&>
-
-<& /Elements/Submit, Label => $SubmitLabel, Reset => 1 &>
-
-</form>
-<%init>
-
-my (@actions, $SubmitLabel);
-
-my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-
-if ( $id ) {
- $scrip->Load( $id );
- unless ( $id = $scrip->id ) {
- push @actions, loc("Couldn't load scrip #[_1]", $id);
- }
- $SubmitLabel = loc('Update');
-}
-
-unless ( $id ) {
- $id = 'new';
- $SubmitLabel = loc('Create');
-}
-
-</%init>
-
-<%ARGS>
-$id => undef
-$title => undef
-$Queue => 0
-</%ARGS>
-
-<%METHOD Process>
-<%ARGS>
-$id => undef
-$Queue => undef
-</%ARGS>
-<%INIT>
-return ($id) unless $id;
-
-my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-if ( $id eq 'new' ) {
- return $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"},
- );
-}
-else {
- $scrip->Load( $id );
- return (undef, loc("Couldn't load scrip #[_1]", $id))
- unless $scrip->id;
-
- 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
- );
- return ($scrip->id, @results);
-}
-</%INIT>
-</%METHOD>
diff --git a/rt/html/Admin/Elements/EditScrips b/rt/html/Admin/Elements/EditScrips
deleted file mode 100644
index 40a526d..0000000
--- a/rt/html/Admin/Elements/EditScrips
+++ /dev/null
@@ -1,125 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ListActions, actions => \@actions &>
-
-<form action="Scrips.html" method="post">
-<input type="hidden" class="hidden" name="id" value="<%$id%>" />
-
-<h2><&|/l&>Current Scrips</&></h2>
-% if ($Scrips->Count == 0 ) {
-<p><i><&|/l&>(No scrips)</&></i></p>
-% } else {
-<table width="100%">
-<p><i><&|/l&>(Check box to delete)</&></i></p>
-
-% while (my $scrip = $Scrips->Next ) {
-<tr>
-<td>
-<input type="checkbox" class="checkbox" name="DeleteScrip-<%$scrip->Id%>" value="1" />
-</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();
-}
-
-$Scrips->OrderBy( FIELD => 'description' );
-
-
-
-# {{{ 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
deleted file mode 100644
index 9881299..0000000
--- a/rt/html/Admin/Elements/EditTemplates
+++ /dev/null
@@ -1,128 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ListActions, actions => \@actions &>
-
-<form method="get" action="Templates.html">
-<input type="hidden" class="hidden" name="id" value="<%$id%>" />
-
-% if ($Templates->Count == 0 ) {
-<p><i><&|/l&>(No templates)</&></i></p>
-% } else {
-<table width="100%">
-<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" class="checkbox" name="DeleteTemplate-<%$TemplateObj->Id%>" value="1" />
-</td>
-<td>
-<a href="Template.html?Queue=<%$id%>&Template=<%$TemplateObj->id()%>">
-<strong><% loc($TemplateObj->Name) %></strong></a>
-<br /><% loc($TemplateObj->Description) %>
-</td>
-</tr>
-
-% }
-</table>
-% }
-
-<& /Elements/Submit, Label => loc('Delete Template') &>
-</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
deleted file mode 100644
index 40d8b19..0000000
--- a/rt/html/Admin/Elements/EditUserComments
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => "Comments about $name" &>
-<&|/l&>These comments aren't generally visible to the user</&>:<br />
-<input type="hidden" class="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/GlobalCustomFieldTabs b/rt/html/Admin/Elements/GlobalCustomFieldTabs
deleted file mode 100755
index ba315b0..0000000
--- a/rt/html/Admin/Elements/GlobalCustomFieldTabs
+++ /dev/null
@@ -1,95 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/SystemTabs, subtabs => $tabs,
- current_tab => 'Admin/Global/CustomFields/index.html',
- current_subtab => $current_tab,
- Title => $Title &>
-<%INIT>
-
-
- my $tabs = {
-
- A => { title => loc('Users'),
- text => loc('Modify scrips which apply to all queues'),
- path => 'Admin/Global/CustomFields/Users.html',
- },
- B => { title => loc('Groups'),
- text => loc('Edit system templates'),
- path => 'Admin/Global/CustomFields/Groups.html',
- },
-
- F => { title => loc('Tickets'),
- text => loc('Modify global custom fields'),
- path => 'Admin/Global/CustomFields/Queue-Tickets.html',
- },
-
- G => { title => loc('Ticket Transactions'),
- text => loc('Modify global group rights'),
- path => 'Admin/Global/CustomFields/Queue-Transactions.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/GroupTabs b/rt/html/Admin/Elements/GroupTabs
deleted file mode 100644
index ade02dc..0000000
--- a/rt/html/Admin/Elements/GroupTabs
+++ /dev/null
@@ -1,102 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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, },
- H => { title => loc('History'),
- path => "Admin/Groups/History.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
deleted file mode 100644
index eeeab30..0000000
--- a/rt/html/Admin/Elements/Header
+++ /dev/null
@@ -1,52 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, %ARGS &>
-
-<%ARGS>
-$Title => undef
-</%ARGS>
diff --git a/rt/html/Admin/Elements/ListGlobalCustomFields b/rt/html/Admin/Elements/ListGlobalCustomFields
deleted file mode 100644
index 55d7d32..0000000
--- a/rt/html/Admin/Elements/ListGlobalCustomFields
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% 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
deleted file mode 100644
index be819ec..0000000
--- a/rt/html/Admin/Elements/ListGlobalScrips
+++ /dev/null
@@ -1,76 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if ($Scrips->Count == 0) {
-
-<p><i><&|/l&>(No scrips)</&></i></p>
-
-% } else {
-
-<ul>
-
-% while (my $scrip = $Scrips->Next ) {
-<li>
-<a href="<%$RT::WebPath%>/Admin/Global/Scrip.html?id=<%$scrip->Id%>&Queue=<%0%>">
-% if ($scrip->Description) {
-<% $scrip->Description %>
-% } else {
-<i>(<&|/l, $scrip->Id&>Scrip #[_1]</&>)</i>
-% }
-</a><br />
-<small><&|/l, loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name) &>[_1] [_2] with template [_3]</&></small>
-</li>
-% }
-
-</ul>
-
-% }
-
-<%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
deleted file mode 100644
index 36f9ce1..0000000
--- a/rt/html/Admin/Elements/ModifyQueue
+++ /dev/null
@@ -1,78 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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, Label => loc('Save Changes') &>
-</form>
-<& /Elements/TitleBoxEnd &>
-
-<%INIT>
-
-</%INIT>
-
-<%ARGS>
-
-
-$QueueObj => undef
-</%ARGS>
diff --git a/rt/html/Admin/Elements/ModifyTemplate b/rt/html/Admin/Elements/ModifyTemplate
deleted file mode 100644
index 377379b..0000000
--- a/rt/html/Admin/Elements/ModifyTemplate
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 2faefef..0000000
--- a/rt/html/Admin/Elements/ModifyUser
+++ /dev/null
@@ -1,99 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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, Label => loc('Save Changes') &>
-</form>
-<& /Elements/TitleBoxEnd &>
-
-<%INIT>
-
-</%INIT>
-
-<%ARGS>
-
-
-$UserObj => undef
-</%ARGS>
diff --git a/rt/html/Admin/Elements/ObjectCustomFields b/rt/html/Admin/Elements/ObjectCustomFields
deleted file mode 100644
index d618878..0000000
--- a/rt/html/Admin/Elements/ObjectCustomFields
+++ /dev/null
@@ -1,111 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& $ObjectTabs,
-$id ? (
- id => $Object->id,
- current_tab => "Admin/$Types/CustomFields.html?$sub_type_url&id=".$id,
- current_subtab => "Admin/$Types/CustomFields.html?$sub_type_url&id=".$id,
- "${Type}Obj" => $Object,
-) : (
- current_tab => "Admin/Global/CustomFields/${QualifiedType}s.html",
-),
- Title => $title
- &>
-
-<& /Admin/Elements/EditCustomFields, %ARGS, title => $title, Object => $Object &>
-<%INIT>
-# XXX TODO: Validate here?
-#$ObjectType =~ /^RT::(Queue|User|Group)$/
-# or Abort(loc("Object of type [_1] cannot take custom fields", $ObjectType));
-
-
-
-my $Type = $1;
-my $Types = $Type.'s';
-my $ObjectTabs;
-my $Object = $ObjectType->new($session{'CurrentUser'});
-
-
-my $QualifiedType;
-my $FriendlySubTypes;
-if ($SubType =~/^RT::(.*)$/) {
- $FriendlySubTypes = RT::CustomField->new($session{'CurrentUser'})->FriendlyLookupType($Object->CustomFieldLookupType);
- $QualifiedType = "$Type-$1";
-} else {
- $QualifiedType = $Type;
-}
-
-if ($id) {
- $Object->Load($id) || Abort(loc("Couldn't load object [_1]", $id));
- $ObjectTabs = "/Admin/Elements/${Type}Tabs";
-} else {
- $ObjectTabs = "/Admin/Elements/GlobalCustomFieldTabs";
-
-}
-
-my $title;
-if ($id) {
-$title = loc('Edit Custom Fields for [_1]', $Object->Name);
-}
-elsif ($SubType) {
-
- $title= loc("Modify Custom Fields which apply to [_1] for all [_2]", loc(lc($FriendlySubTypes)), loc(lc($Types)));
-} else {
- $title =loc("Modify Custom Fields which apply to all [_1]", loc(lc($Types)));
-
-}
-my $sub_type_url;
-$sub_type_url = "SubType=$SubType" if $SubType;
-
-</%INIT>
-<%ARGS>
-$id => undef
-$ObjectType
-$SubType => undef
-</%ARGS>
diff --git a/rt/html/Admin/Elements/PickCustomFields b/rt/html/Admin/Elements/PickCustomFields
deleted file mode 100644
index c3b5550..0000000
--- a/rt/html/Admin/Elements/PickCustomFields
+++ /dev/null
@@ -1,98 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if (@CustomFields == 0) {
-<p><i><&|/l&>(None)</&></i></p>
-% } else {
-<table cellspacing="0" cellpadding="2">
-% my $count;
-% foreach my $CustomFieldObj (@CustomFields) {
-<tr>
-% if (!$ReadOnly) {
- <td valign="top">
-<input type="checkbox" class="checkbox" name="Object-<%$id%>-CF-<%$CustomFieldObj->Id%>" value="1" <% $Checked ? 'CHECKED' : '' %>
-/>
- </td>
-% }
- <td valign="top">
- <a href="<%$RT::WebPath%>/Admin/CustomFields/Modify.html?id=<%$CustomFieldObj->id()%>">
-% if ($CustomFieldObj->Name) {
-<b><%$CustomFieldObj->Name%></b>
-% } else {
-<i>(<&|/l&>no name</&>)</i>
-% }
-</a><br />
- <%$CustomFieldObj->Description%>
- </td>
- <td valign="top">
- <i><% $CustomFieldObj->FriendlyTypeComposite %></i>
- </td>
-% # show 'move up' unless it's the first item
-% if ($count++ and $Checked) {
- <td valign="top">
- [<a href="<%$RT::WebPath%><% $m->request_comp->path |n %>?id=<%$id%>&SubType=<%$SubType%>&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 ($count != @CustomFields and $Checked) {
-% $m->print(' | ') if $count > 1;
- [<a href="<%$RT::WebPath%><% $m->request_comp->path |n %>?id=<%$id%>&SubType=<%$SubType%>&CustomField=<%$CustomFieldObj->id%>&Move=1"><&|/l&>Move down</&></a>]
-% }
- </td>
-</tr>
-% }
-</table>
-% }
-<%ARGS>
-@CustomFields
-$id
-$ReadOnly => 0
-$Checked => 0
-$SubType
-</%ARGS>
diff --git a/rt/html/Admin/Elements/PickObjects b/rt/html/Admin/Elements/PickObjects
deleted file mode 100644
index b2da495..0000000
--- a/rt/html/Admin/Elements/PickObjects
+++ /dev/null
@@ -1,81 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if (@Objects == 0) {
-<p><i><&|/l&>(None)</&></i></p>
-% } else {
-<table cellspacing="0" cellpadding="2">
-% my $count;
-% foreach my $Object (@Objects) {
-<tr>
-% my $id = "Object-".$Object->id."-CF-".$id;
-% if (!$ReadOnly) {
- <td valign="top">
-<input type="checkbox" id="<% $id %>" name="<% $id %>" value="1" <% $Checked ? 'CHECKED' : ''%>
-/>
- </td>
-% }
- <td valign="top">
- <label for="<% $id %>">
-% if ($Object->Name) {
- <b><%$Object->Name%></b><br />
-% } else {
- <i>(<%loc("no name")%>)</i><br />
-% }
- <%$Object->can('Description') && $Object->Description%>
- </label>
- </td>
-</tr>
-% }
-</table>
-% }
-<%ARGS>
-@Objects
-$id
-$ReadOnly => 0
-$Checked => 0
-</%ARGS>
diff --git a/rt/html/Admin/Elements/QueueRightsForUser b/rt/html/Admin/Elements/QueueRightsForUser
deleted file mode 100644
index 52bb1ff..0000000
--- a/rt/html/Admin/Elements/QueueRightsForUser
+++ /dev/null
@@ -1,64 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<ul>
-%while(my $ACE = $ACL->Next) {
-
-<li><checkbox name="delete_ace_<%$ACE->id%>" value="1"> <% 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
deleted file mode 100644
index 379d152..0000000
--- a/rt/html/Admin/Elements/QueueTabs
+++ /dev/null
@@ -1,120 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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,
- },
-
- G1 => { title => loc('Ticket Custom Fields'),
- path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket&id='.$id,
- },
-
- G2 => { title => loc('Transaction Custom Fields'),
- path => 'Admin/Queues/CustomFields.html?SubType=RT::Ticket-RT::Transaction&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/SelectCustomFieldLookupType b/rt/html/Admin/Elements/SelectCustomFieldLookupType
deleted file mode 100644
index ebd3807..0000000
--- a/rt/html/Admin/Elements/SelectCustomFieldLookupType
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select NAME ="<%$Name%>">
-%for my $option ($cf->LookupTypes) {
-<option value="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyLookupType($option) %></option>
-%}
-</select>
-<%INIT>
-my $cf = RT::CustomField->new($session{'CurrentUser'});
-
-</%INIT>
-<%ARGS>
-$Default=>undef
-$Name => 'LookupType'
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectCustomFieldType b/rt/html/Admin/Elements/SelectCustomFieldType
deleted file mode 100644
index 7dd4713..0000000
--- a/rt/html/Admin/Elements/SelectCustomFieldType
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select NAME ="<%$Name%>">
-%for my $option ($cf->TypeComposites) {
-<option value="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyTypeComposite($option) %></option>
-%}
-</select>
-<%INIT>
-my $cf = RT::CustomField->new($session{'CurrentUser'});
-
-</%INIT>
-<%ARGS>
-$Default=>undef
-$Name => 'TypeComposite'
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectGroups b/rt/html/Admin/Elements/SelectGroups
deleted file mode 100644
index c49f8ae..0000000
--- a/rt/html/Admin/Elements/SelectGroups
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 7820e9f..0000000
--- a/rt/html/Admin/Elements/SelectModifyGroup
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%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
deleted file mode 100644
index af24e27..0000000
--- a/rt/html/Admin/Elements/SelectModifyQueue
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%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
deleted file mode 100644
index 73b67c8..0000000
--- a/rt/html/Admin/Elements/SelectModifyUser
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%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
deleted file mode 100644
index 6235729..0000000
--- a/rt/html/Admin/Elements/SelectNewGroupMembers
+++ /dev/null
@@ -1,99 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if ($Show ne 'Groups') {
-<b><&|/l&>Users</&></b>
-<select multiple name="<%$Name%>Users" size="10">
-%while (my $user = $users->Next) {
-%next if $SkipUsers->{$user->id};
-<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) {
-%next if $SkipGroups->{$group->id};
-<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 => '!=',
- ENTRYAGGREGATOR => 'AND'
-);
-$users->Limit(
- FIELD => 'id',
- VALUE => $RT::Nobody->id,
- OPERATOR => '!=',
- ENTRYAGGREGATOR => 'AND'
-);
-$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
-$SkipUsers => {}
-$SkipGroups => {}
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectRights b/rt/html/Admin/Elements/SelectRights
deleted file mode 100644
index a79c0f3..0000000
--- a/rt/html/Admin/Elements/SelectRights
+++ /dev/null
@@ -1,118 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<input type="hidden" class="hidden" name="CheckACL" value="<%$ACLDesc%>" />
- <table border="0">
-<tr>
-<td valign="top" width="180" align="left">
-% my %current_rights;
-<h3><&|/l&>Current rights</&></h3>
-% if ($ACLObj->Count() == 0) {
-<i><&|/l&>No rights granted.</&></i> <br />
-% } else {
-<i>(<&|/l&>Check box to revoke right</&>)</i> <br />
-% while (my $right = $ACLObj->Next()) {
-% if ($right->RightName) {
-% $current_rights{$right->RightName} = 1;
-<input type="checkbox" class="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) {
-% next if $current_rights{$right};
- <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
deleted file mode 100644
index 2ce3c2e..0000000
--- a/rt/html/Admin/Elements/SelectScrip
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 0bc8251..0000000
--- a/rt/html/Admin/Elements/SelectScripAction
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-<option value=""
-<% ! defined $Default && 'SELECTED' %>
->-</option>
-%while (my $ScripAction = $ScripActions->Next) {
-<option value="<%$ScripAction->Id%>"
-<% defined $Default && $ScripAction->Id == $Default && 'SELECTED' %>
-><% loc($ScripAction->Name) %>
-</option>
-%}
-</select>
-
-<%INIT>
-my $ScripActions = RT::ScripActions->new($session{'CurrentUser'});
-$ScripActions->UnLimit;
-$ScripActions->OrderBy(FIELD => 'Name');
-
-
-
-</%INIT>
-<%ARGS>
-
-$Default => undef
-$Name => 'ScripAction'
-
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectScripCondition b/rt/html/Admin/Elements/SelectScripCondition
deleted file mode 100644
index 6d9201e..0000000
--- a/rt/html/Admin/Elements/SelectScripCondition
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-<option value=""
-<% ! defined $Default && 'SELECTED' %>
->-</option>
-%while (my $ScripCondition = $ScripConditions->Next) {
-<option value="<%$ScripCondition->Id%>"
-<% defined $Default && $ScripCondition->Id == $Default && 'SELECTED' %>
-><% loc($ScripCondition->Name) %>
-</option>
-%}
-</select>
-
-<%INIT>
-my $ScripConditions = RT::ScripConditions->new($session{'CurrentUser'});
-$ScripConditions->UnLimit;
-$ScripConditions->OrderBy(FIELD => 'Name');
-
-
-</%INIT>
-<%ARGS>
-
-$Default => undef
-$Name => 'ScripCondition'
-
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectSingleOrMultiple b/rt/html/Admin/Elements/SelectSingleOrMultiple
deleted file mode 100644
index 1b13a91..0000000
--- a/rt/html/Admin/Elements/SelectSingleOrMultiple
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- <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
deleted file mode 100644
index f48f684..0000000
--- a/rt/html/Admin/Elements/SelectStage
+++ /dev/null
@@ -1,66 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-% foreach my $stage (@stages) {
-<option value="<%$stage%>"
-<% ($stage eq $Default) && 'SELECTED' %>
-><% loc($stage) %>
-</option>
-% }
-<%INIT>
-if ($Default eq '') {
- $Default = 'TransactionCreate';
-}
-my @stages = 'TransactionCreate';
-push @stages, 'TransactionBatch' if $RT::UseTransactionBatch;
-push @stages, 'Disabled';
-</%INIT>
-<%ARGS>
-$Default => 'TransactionCreate'
-$Name => 'Stage'
-</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectTemplate b/rt/html/Admin/Elements/SelectTemplate
deleted file mode 100644
index e42adfe..0000000
--- a/rt/html/Admin/Elements/SelectTemplate
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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);
-$PrimaryTemplates->OrderBy(FIELD => 'Name');
-}
-
-my $OtherTemplates = RT::Templates->new($session{'CurrentUser'});
-$OtherTemplates->LimitToGlobal($DefaultQueue);
-$OtherTemplates->OrderBy(FIELD => 'Name');
-
-</%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
deleted file mode 100644
index 5426f42..0000000
--- a/rt/html/Admin/Elements/SelectUsers
+++ /dev/null
@@ -1,64 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index afd611c..0000000
--- a/rt/html/Admin/Elements/SystemTabs
+++ /dev/null
@@ -1,97 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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/index.html',
- },
-
- G => { title => loc('Group Rights'),
- path => 'Admin/Global/GroupRights.html',
- },
- H => { title => loc('User Rights'),
- path => 'Admin/Global/UserRights.html',
- },
- I => { title => loc('RT at a glance'),
- path => 'Admin/Global/MyRT.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
deleted file mode 100644
index 1fc54ef..0000000
--- a/rt/html/Admin/Elements/Tabs
+++ /dev/null
@@ -1,93 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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('Custom Fields'),
- path => 'Admin/CustomFields/',
- },
- E => { 'title' => loc('Global'),
- path => 'Admin/Global/',
- },
- F => { 'title' => loc('Tools'),
- path => 'Admin/Tools/',
- },
- };
-
- # 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/ToolTabs b/rt/html/Admin/Elements/ToolTabs
deleted file mode 100755
index 94d19ca..0000000
--- a/rt/html/Admin/Elements/ToolTabs
+++ /dev/null
@@ -1,80 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Tabs, subtabs => $tabs,
- current_tab => 'Admin/Tools/',
- current_subtab => $current_tab,
- Title => $Title &>
-
-<%INIT>
- my $tabs = {
-
- A => { title => loc('System Configuration'),
- path => 'Admin/Tools/Configuration.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/UserTabs b/rt/html/Admin/Elements/UserTabs
deleted file mode 100644
index c6050c1..0000000
--- a/rt/html/Admin/Elements/UserTabs
+++ /dev/null
@@ -1,113 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Tabs,
- subtabs => $tabs,
- current_tab => 'Admin/Users/',
- current_subtab => $current_tab,
- Title => $Title &>
-<%INIT>
-my $tabs;
-if ($id) {
-$tabs->{'this'} = { title => eval { $UserObj->Name },
-
- path => "Admin/Users/Modify.html?id=".$id,
-subtabs => {
- Basics => { title => loc('Basics'),
- path => "Admin/Users/Modify.html?id=".$id
- },
- Memberships => { title => loc('Memberships'),
- path => "Admin/Users/Memberships.html?id=".$id
- },
- History => { title => loc('History'),
- path => "Admin/Users/History.html?id=".$id
- },
- 'MyRT' => { title => loc('RT at a glance'),
- path => "Admin/Users/MyRT.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;
-# }
-#}
-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/Users/Modify.html?id=".$id if $id;
-</%INIT>
-<%ARGS>
-$UserObj => undef
-$id => undef
-$current_tab => undef
-$subtabs => undef
-$current_subtab => undef
-$Title => undef
-</%ARGS>
diff --git a/rt/html/Admin/Global/CustomField.html b/rt/html/Admin/Global/CustomField.html
deleted file mode 100644
index 3871d89..0000000
--- a/rt/html/Admin/Global/CustomField.html
+++ /dev/null
@@ -1,86 +0,0 @@
-%# {{{ BEGIN BPS TAGGED BLOCK
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# }}} END BPS TAGGED BLOCK
-<& /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>
-<%ATTR>
-AutoFlush => 0
-</%ATTR>
diff --git a/rt/html/Admin/Global/CustomFields.html b/rt/html/Admin/Global/CustomFields.html
deleted file mode 100644
index 5930402..0000000
--- a/rt/html/Admin/Global/CustomFields.html
+++ /dev/null
@@ -1,69 +0,0 @@
-%# {{{ BEGIN BPS TAGGED BLOCK
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# }}} END BPS TAGGED BLOCK
-<& /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/CustomFields/Groups.html b/rt/html/Admin/Global/CustomFields/Groups.html
deleted file mode 100644
index fe2545a..0000000
--- a/rt/html/Admin/Global/CustomFields/Groups.html
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GlobalCustomFieldTabs,
- current_tab => "Admin/Global/CustomFields/Groups.html",
- current_subtab => "Admin/Global/CustomFields/Groups.html",
- Title => $title
- &>
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, ObjectType => 'RT::Group', Object=> $object &>
-<%INIT>
- my $title = loc( 'Edit Custom Fields for all groups');
- my $object = RT::Group->new($session{'CurrentUser'});
-</%INIT>
diff --git a/rt/html/Admin/Global/CustomFields/Queue-Tickets.html b/rt/html/Admin/Global/CustomFields/Queue-Tickets.html
deleted file mode 100755
index 8ef3083..0000000
--- a/rt/html/Admin/Global/CustomFields/Queue-Tickets.html
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GlobalCustomFieldTabs,
- current_tab => "Admin/Global/CustomFields/Queue-Tickets.html",
- current_subtab => "Admin/Global/CustomFields/Queue-Tickets.html",
- Title => $title
- &>
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, ObjectType => 'RT::Queue', Object=> $object, SubType => 'RT::Ticket' &>
-<%INIT>
- my $title = loc( 'Edit Custom Fields for tickets in all queues');
- my $object = RT::Queue->new($session{'CurrentUser'});
-</%INIT>
diff --git a/rt/html/Admin/Global/CustomFields/Queue-Transactions.html b/rt/html/Admin/Global/CustomFields/Queue-Transactions.html
deleted file mode 100755
index 98aee5f..0000000
--- a/rt/html/Admin/Global/CustomFields/Queue-Transactions.html
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GlobalCustomFieldTabs,
- current_tab => "Admin/Global/CustomFields/Queue-Transactions.html",
- current_subtab => "Admin/Global/CustomFields/Queue-Transactions.html",
- Title => $title
- &>
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, ObjectType => 'RT::Queue', Object=> $object, SubType => 'RT::Ticket-RT::Transaction' &>
-<%INIT>
- my $title = loc( 'Edit Custom Fields for tickets in all queues');
- my $object = RT::Queue->new($session{'CurrentUser'});
-</%INIT>
diff --git a/rt/html/Admin/Global/CustomFields/Users.html b/rt/html/Admin/Global/CustomFields/Users.html
deleted file mode 100644
index 11133a5..0000000
--- a/rt/html/Admin/Global/CustomFields/Users.html
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GlobalCustomFieldTabs,
- current_tab => "Admin/Global/CustomFields/Users.html",
- current_subtab => "Admin/Global/CustomFields/Users.html",
- Title => $title
- &>
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, ObjectType => 'RT::User', Object=> $object &>
-<%INIT>
- my $title = loc( 'Edit Custom Fields for all users');
- my $object = RT::User->new($session{'CurrentUser'});
-</%INIT>
diff --git a/rt/html/Admin/Global/CustomFields/index.html b/rt/html/Admin/Global/CustomFields/index.html
deleted file mode 100644
index 3ef08f8..0000000
--- a/rt/html/Admin/Global/CustomFields/index.html
+++ /dev/null
@@ -1,93 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Callback, tabs => $tabs, %ARGS &>
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GlobalCustomFieldTabs, Title => $title &>
-
-<ul>
-% foreach my $key (sort keys %$tabs) {
-<li><span><a href="<% $tabs->{$key}{path} %>"><% $tabs->{$key}{title} %></a></span><br />
-<% $tabs->{$key}{text} %>
-</li>
-% }
-</ul>
-
-<%INIT>
-my $title = loc("Global custom field configuration");
-
-my $tabs = {
-
- A => {
- title => loc('Users'),
- text => loc('Select custom fields for all users'),
- path => 'Users.html',
- },
- B => {
- title => loc('Groups'),
- text => loc('Select custom fields for all user groups'),
- path => 'Groups.html',
- },
-
- F => {
- title => loc('Tickets'),
- text => loc('Select custom fields for tickets in all queues'),
- path => 'Queue-Tickets.html',
- },
-
- G => {
- title => loc('Ticket Transactions'),
- text =>
- loc('Select custom fields for transactions on tickets in all queues'),
- path => 'Queue-Transactions.html',
- },
-
-};
-
-
-$m->comp('/Elements/Callback', tabs => $tabs);
-</%INIT>
diff --git a/rt/html/Admin/Global/GroupRights.html b/rt/html/Admin/Global/GroupRights.html
deleted file mode 100644
index 3101eae..0000000
--- a/rt/html/Admin/Global/GroupRights.html
+++ /dev/null
@@ -1,123 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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">
-
-<&| /Widgets/TitleBox, 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/Submit, Label => loc('Modify Group Rights'), Reset => 1 &>
-
- </form>
-
-<%INIT>
-
- #Update the acls.
- my @results = ProcessACLChanges(\%ARGS);
-
-
-my $Groups;
-
-</%INIT>
-
-<%ARGS>
-</%ARGS>
diff --git a/rt/html/Admin/Global/MyRT.html b/rt/html/Admin/Global/MyRT.html
deleted file mode 100644
index 52793f4..0000000
--- a/rt/html/Admin/Global/MyRT.html
+++ /dev/null
@@ -1,111 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc("RT at a glance") &>
-<& /Admin/Elements/SystemTabs,
- current_tab => 'Admin/Global/MyRT.html',
- Title => loc("RT at a glance"),
-&>
-
-<& /Widgets/SelectionBox:header, nojs => 1 &>
-
-<& /Elements/ListActions, actions => \@actions &>
-<br />
-% for my $pane (@panes) {
-<&|/Widgets/TitleBox, title => loc('RT at a glance').': '.loc($pane->{Name}), bodyclass => "" &>
-<& /Widgets/SelectionBox:show, self => $pane, nojs => 1 &></&>
-<br />
-% }
-<%init>
-my @actions;
-
-my @items = map { [ "component-$_", $_ ] } sort @{$RT::HomepageComponents};
-my $sys = RT::System->new( $session{'CurrentUser'} );
-# XXX: put this in savedsearches_to_portlet_items
-for ( $m->comp( "/Search/Elements/SearchesForObject",
- Object => $sys )) {
- my ( $desc, $search ) = @$_;
- my $SearchType = $search->Content->{'SearchType'} || 'Ticket';
- if ( $SearchType eq 'Ticket' ) {
- push @items, [ "system-$desc", $desc ];
- } else {
- my $oid = ref($sys) . '-' . $sys->Id . '-SavedSearch-' . $search->Id;
- my $type =
- ( $SearchType eq 'Ticket' )
- ? 'Saved Search' : $SearchType; # loc
- push @items, [ "saved-$oid", loc($type) . ": $desc" ];
- }
-}
-
-my ($default_portlets) = $sys->Attributes->Named('HomepageSettings');
-
-my $has_right = $session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser');
-
-my @panes = $m->comp(
- '/Admin/Elements/ConfigureMyRT',
- panes => ['body', 'summary'],
- Action => 'MyRT.html',
- items => \@items,
- current_portlets => $default_portlets->Content,
- OnSave => sub {
- my ( $conf, $pane ) = @_;
- if (!$has_right) {
- push @actions, loc( 'Permission denied' );
- }
- else {
- $default_portlets->SetContent( $conf );
- push @actions, loc( 'Global portlet [_1] saved.', $pane );
- }
- }
-);
-
-$m->comp( '/Widgets/SelectionBox:process', %ARGS, self => $_, nojs => 1 )
- for @panes;
-
-
-</%init>
-
diff --git a/rt/html/Admin/Global/Scrip.html b/rt/html/Admin/Global/Scrip.html
deleted file mode 100644
index d4af7d5..0000000
--- a/rt/html/Admin/Global/Scrip.html
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/SystemTabs,
- current_tab => 'Admin/Global/Scrips.html',
- current_subtab => $current_subtab,
- subtabs => $subtabs,
- Title => $title &>
-
-<& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title, %ARGS, id => $id &>
-
-<%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 $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-my ($id, @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
-
-my ($title, $current_subtab);
-if ( $id ) {
- $current_subtab = "Admin/Global/Scrip.html?id=$id&Queue=0";
- $title = loc("Modify a scrip that applies to all queues");
- $subtabs->{"C"} = {
- title => loc('Scrip #[_1]', $id),
- path => "Admin/Global/Scrip.html?id=$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
deleted file mode 100644
index 479d39b..0000000
--- a/rt/html/Admin/Global/Scrips.html
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index b44a912..0000000
--- a/rt/html/Admin/Global/Template.html
+++ /dev/null
@@ -1,122 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/SystemTabs,
- current_tab => 'Admin/Global/Templates.html',
- current_subtab => $current_subtab,
- subtabs => $subtabs,
- Title => $title &>
-<& /Elements/ListActions, actions => \@results &>
-
-<form method="post" action="Template.html">
-%if ($Create ) {
-<input type="hidden" class="hidden" name="Template" value="new" />
-% } else {
-<input type="hidden" class="hidden" name="Template" value="<%$TemplateObj->Id%>" />
-% }
-
-%# hang onto the queue id
-<input type="hidden" class="hidden" name="Queue" value="<%$Queue%>" />
-<& /Admin/Elements/ModifyTemplate, Name => $TemplateObj->Name, Description =>
-$TemplateObj->Description, Content => $TemplateObj->Content &>
-<& /Elements/Submit, Label => loc('Create'), 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) {
- $title = loc("Create a template");
- $current_subtab = "Admin/Global/Template.html?Create=1&Queue=0";
-}
-
-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
deleted file mode 100644
index e953c02..0000000
--- a/rt/html/Admin/Global/Templates.html
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title, FeedURI => 'templates' &>
-<& /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
deleted file mode 100644
index 6691e14..0000000
--- a/rt/html/Admin/Global/UserRights.html
+++ /dev/null
@@ -1,103 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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">
-
-<&| /Widgets/TitleBox, title => loc('Modify global user rights.') &>
-
-<table>
-
-% while (my $UserObj = $Users->Next()) {
-% my $group = RT::Group->new($session{'CurrentUser'});
-% $group->LoadACLEquivalenceGroup($UserObj);
- <tr align="right">
- <td valign="top">
- <% $UserObj->Name %>
- </td>
- <td>
- <& /Admin/Elements/SelectRights, PrincipalId => $group->PrincipalId,
- Object => $RT::System &>
- </td>
- </tr>
-% }
-</table>
-
- </&>
- <& /Elements/Submit, Label => loc('Modify User Rights'), 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->UserMembersObj();
-$Users->OrderBy( FIELD => $UserOrderBy, ORDER => $UserOrder );
-
-
-
-# }}}
-
-</%INIT>
-
-<%ARGS>
-$UserOrderBy => 'Name'
-$UserOrder => 'ASC'
-</%ARGS>
diff --git a/rt/html/Admin/Global/index.html b/rt/html/Admin/Global/index.html
deleted file mode 100644
index e84fc7f..0000000
--- a/rt/html/Admin/Global/index.html
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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><span><a href="<% $tabs->{$key}{path} %>"><% $tabs->{$key}{title} %></a></span><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 global custom fields'),
- path => 'CustomFields/index.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',
- },
- I => { title => loc('RT at a glance'),
- text => loc('Modify the default "RT at a glance" view'),
- path => 'MyRT.html',
- },
-
-
-
-};
-</%INIT>
diff --git a/rt/html/Admin/Groups/CustomFields.html b/rt/html/Admin/Groups/CustomFields.html
deleted file mode 100644
index 3e614f0..0000000
--- a/rt/html/Admin/Groups/CustomFields.html
+++ /dev/null
@@ -1,48 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/ObjectCustomFields, %ARGS, ObjectType => 'RT::Group' &>
diff --git a/rt/html/Admin/Groups/GroupRights.html b/rt/html/Admin/Groups/GroupRights.html
deleted file mode 100644
index bf430b7..0000000
--- a/rt/html/Admin/Groups/GroupRights.html
+++ /dev/null
@@ -1,119 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="<% $GroupObj->id %>" />
-
-<&| /Widgets/TitleBox, 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/Submit, Label => loc('Modify Group Rights'), 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/History.html b/rt/html/Admin/Groups/History.html
deleted file mode 100644
index b811181..0000000
--- a/rt/html/Admin/Groups/History.html
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GroupTabs,
- id => $id,
- GroupObj => $GroupObj,
- current_subtab => $current_tab,
- Title => $title &>
-
-<& /Ticket/Elements/ShowHistory,
- Ticket => $GroupObj,
- ShowDisplayModes => 0,
-&>
-
-<%INIT>
-my $current_tab = 'Admin/Groups/History.html?id='.$id;
-my $GroupObj = new RT::Group($session{'CurrentUser'});
-$GroupObj->Load($id) || Abort("Couldn't load group '$id'");
-my $title = loc("History of the group [_1]", $GroupObj->Name);
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/Groups/Members.html b/rt/html/Admin/Groups/Members.html
deleted file mode 100644
index c054b9b..0000000
--- a/rt/html/Admin/Groups/Members.html
+++ /dev/null
@@ -1,168 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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 &>
-
-<form action="<%$RT::WebPath%>/Admin/Groups/Members.html" method="post">
-<input type="hidden" class="hidden" name="id" value="<%$Group->Id%>" />
-
-<&| /Widgets/TitleBox, title => loc('Editing membership for group [_1]', $Group->Name) &>
-
-<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 ) {
-<em><&|/l&>(No members)</&></em>
-% } else {
-<em><&|/l&>(Check box to delete)</&></em>
-<br />
-<br />
-<&|/l&>Users</&>
-% my $Users = $Group->UserMembersObj;
-% $Users->OrderBy( FIELD => $UserOrderBy, ORDER => $UserOrder );
-<ul>
-% while (my $user = $Users->Next()) {
-% $UsersSeen{$user->id} = 1 if $SkipSeenUsers;
-<li><input type="checkbox" class="checkbox" name="DeleteMember-<%$user->PrincipalObj->Id%>" value="1" />
-<%$user->Name%> (<%$user->RealName%>)
-% }
-</ul>
-<&|/l&>Groups</&>
-<ul>
-% my $GroupMembers = $Group->MembersObj;
-% $GroupMembers->LimitToGroups();
-% while (my $member = $GroupMembers->Next()) {
-% $GroupsSeen{$member->MemberId} = 1 if $SkipSeenGroups;
-<li><input type="checkbox" class="checkbox" name="DeleteMember-<%$member->MemberId%>" value="1" />
-<%$member->MemberObj->Object->Name%>
-% }
-</ul>
-% }
-</td>
-<td valign="top">
-<& /Admin/Elements/SelectNewGroupMembers, Name => "AddMembers", Group => $Group,
- SkipUsers => \%UsersSeen, SkipGroups => \%GroupsSeen &>
-</td>
-</tr>
-</table>
-</&>
-<& /Elements/Submit, Label => loc('Modify Members'), 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);
-}
-
-my %UsersSeen;
-my %GroupsSeen;
-$GroupsSeen{$Group->id} = 1; # can't be a member of ourself
-
-</%INIT>
-
-<%ARGS>
-$AddMembersUsers => undef
-$AddMembersGroups => undef
-$id => undef
-$UserOrderBy => 'Name'
-$UserOrder => 'ASC'
-$SkipSeenUsers => 1
-$SkipSeenGroups => 1
-</%ARGS>
diff --git a/rt/html/Admin/Groups/Modify.html b/rt/html/Admin/Groups/Modify.html
deleted file mode 100644
index 5fa28b3..0000000
--- a/rt/html/Admin/Groups/Modify.html
+++ /dev/null
@@ -1,174 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" enctype="multipart/form-data">
-
-%unless ($Group->Id) {
-<input type="hidden" class="hidden" name="id" value="new" />
-% } else {
-<input type="hidden" class="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>
-% my $CFs = $Group->CustomFields;
-% while (my $CF = $CFs->Next) {
-<tr valign="top"><td align="right">
-<% $CF->Name %>:
-</td><td>
-<& /Elements/EditCustomField, CustomField => $CF,
- Object => $Group,
- ($Create ? (NamePrefix => 'Object-RT::Group--CustomField-')
- : () )&>
-</td></tr>
-% }
-<tr>
-<td colspan="2">
-<input type="hidden" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> /> <&|/l&>Enabled (Unchecking this box disables this group)</&><br />
-</td>
-</tr>
-<& /Elements/Callback, GroupObj => $Group, results => \@results, %ARGS &>
-</table>
-<& /Elements/Submit, Label => loc('Save 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);
- push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $Group );
-}
-
-#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
deleted file mode 100644
index 7a5342a..0000000
--- a/rt/html/Admin/Groups/UserRights.html
+++ /dev/null
@@ -1,116 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="<% $GroupObj->id %>" />
-
-<&| /Widgets/TitleBox, 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/Submit, Label => loc('Modify User Rights'), 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
deleted file mode 100644
index 08aa214..0000000
--- a/rt/html/Admin/Groups/index.html
+++ /dev/null
@@ -1,113 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/GroupTabs, current_tab => 'Admin/Groups/',
- current_subtab => 'Admin/Groups/',
- Title => $title &>
-<%$caption%>:<br /> <ul>
-%if ($Groups->Count == 0) {
-<li> <em><&|/l&>No groups matching search criteria found.</&></em>
-% }
-%my @ids;
-%while ( my $Group = $Groups->Next) {
-% push @ids, $Group->Id;
-<li><a href="Modify.html?id=<%$Group->id%>"><%$Group->Name || loc('(empty)')%></a><br />
-%}
-</ul>
-%if (my $ids = join(',', @ids)) {
-<em>(<a href="<%$RT::WebPath%>/Download/Tabular/Group/<% $ids %>/Groups.tsv"><&|/l&>Download as a tab-delimited file</&></a>)</em><br />
-%}
-<br /><br />
-<form method="post" action="<% $RT::WebPath %>/Admin/Groups/index.html">
-<input type="checkbox" class="checkbox" name="FindDisabledGroups" value="1" /> <&|/l&>Include disabled groups in listing.</&>
-<br />
-<div align="right"><input type="submit" class="button" value="<&|/l&>Go!</&>" /></div>
-</form>
-
-<br /><br />
-<form method="post" action="<% $RT::WebPath %>/Admin/Groups/index.html">
-<&|/l&>Find groups whose</&> <& /Elements/SelectGroups &><br />
-<div align="right"><input type="submit" class="button" value="<&|/l&>Go!</&>" /></div>
-</form>
-<%INIT>
-my $Groups = RT::Groups->new($session{'CurrentUser'});
-$Groups->LimitToUserDefinedGroups();
-my $title = loc('Select a group');
-my $caption;
-
-if ($FindDisabledGroups) {
- $Groups->FindAllRows();
-}
-
-if (length $GroupString) {
- $caption = loc("Groups matching search criteria");
- if ($GroupField =~ /^CustomField-(\d+)/) {
- $Groups->LimitCustomField(
- CUSTOMFIELD => $1,
- OPERATOR => $GroupOp,
- VALUE => $GroupString,
- );
- }
- else {
- $Groups->Limit(
- FIELD => $GroupField,
- OPERATOR => $GroupOp,
- VALUE => $GroupString,
- );
- }
-}
-else {
- $caption = loc("User-defined groups");
-}
-</%INIT>
-<%ARGS>
-$GroupString => undef
-$GroupOp => '='
-$GroupField => 'Name'
-$FindDisabledGroups => 0
-</%ARGS>
diff --git a/rt/html/Admin/Queues/CustomField.html b/rt/html/Admin/Queues/CustomField.html
deleted file mode 100644
index 7c53402..0000000
--- a/rt/html/Admin/Queues/CustomField.html
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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 => 0
-</%ARGS>
-<%ATTR>
-AutoFlush => 0
-</%ATTR>
diff --git a/rt/html/Admin/Queues/CustomFields.html b/rt/html/Admin/Queues/CustomFields.html
deleted file mode 100644
index 679e654..0000000
--- a/rt/html/Admin/Queues/CustomFields.html
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/QueueTabs,
- id => $Object->id,
- current_tab => "Admin/Queues/CustomFields.html?SubType=$SubType&id=$id",
- current_subtab => "Admin/Queues/CustomFields.html?SubType=$SubType&id=$id",
- QueueObj => $Object,
- Title => $title
- &>
-
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, Object => $Object, ObjectType => 'RT::Queue' &>
-<%INIT>
-my $Object = RT::Queue->new( $session{'CurrentUser'} );
-
-$Object->Load($id) || Abort( loc( "Couldn't load object [_1]", $id ) );
-my $FriendlySubTypes =
- RT::CustomField->new( $session{'CurrentUser'} )
- ->FriendlyLookupType( $Object->CustomFieldLookupType );
-
-my $title = loc( 'Edit Custom Fields for [_1]', $Object->Name );
-
-</%INIT>
-<%ARGS>
-$id => undef
-$SubType => 'RT::Queue-RT::Ticket'
-</%ARGS>
diff --git a/rt/html/Admin/Queues/GroupRights.html b/rt/html/Admin/Queues/GroupRights.html
deleted file mode 100644
index 9b4223b..0000000
--- a/rt/html/Admin/Queues/GroupRights.html
+++ /dev/null
@@ -1,134 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="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, Label => loc('Modify Group Rights'), 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
deleted file mode 100644
index 7d231bd..0000000
--- a/rt/html/Admin/Queues/Modify.html
+++ /dev/null
@@ -1,193 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/QueueTabs, id => $QueueObj->id,
- QueueObj => $QueueObj,
- current_tab => $current_tab,
- Title => $title &>
-<& /Elements/ListActions, actions => \@results &>
-
-
-
-<form action="<%$RT::WebPath%>/Admin/Queues/Modify.html" method="post">
-%if ($Create ) {
-<input type="hidden" class="hidden" name="id" value="new" />
-% } else {
-<input type="hidden" class="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&>Reply Address</&>:
-</td><td>
-<input name="CorrespondAddress" value="<% ($Create) ? "" : $QueueObj->CorrespondAddress %>" />
-<br /><span><em><&|/l , $RT::CorrespondAddress&>(If left blank, will default to [_1])</&></em></span>
-</td>
-<td align="right">
-
-<&|/l&>Comment Address</&>: </td><td>
-<input name="CommentAddress" value="<% ($Create) ? "" : $QueueObj->CommentAddress %>" />
-<br /><span><em><&|/l , $RT::CommentAddress&>(If left blank, will default to [_1])</&></em></span>
-</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" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="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, Label => loc('Save Changes') &>
-</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 ="";
- }
-
- my @linkresults;
- $m->comp('/Elements/Callback', results => \@linkresults,
- RecordObj => $QueueObj, ARGSRef => \%ARGS,
- _CallbackName => 'ProcessLinks');
- push @results, @linkresults;
-}
-</%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
deleted file mode 100644
index a85fe44..0000000
--- a/rt/html/Admin/Queues/People.html
+++ /dev/null
@@ -1,210 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="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" class="button" name="OnlySearchForPeople" value="<&|/l&>Go!</&>" />
-<br />
-<&|/l&>Find groups whose</&><br />
-<& /Elements/SelectGroups &>
-<input type="submit" class="button" name="OnlySearchForGroup" value="<&|/l&>Go!</&>" />
-
-<p>
-<&|/l&>Add new watchers</&>:<br />
-<p>
-<strong><&|/l&>Users</&></strong>
-% if ($user_msg) {
-<br />
-<em><%$user_msg%></em>
-% } 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>
-<strong><&|/l&>Groups</&></strong>
-
-% if ($group_msg) {
-<br />
-<em><%$group_msg%></em>
-% } 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-DeleteWatcher-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
deleted file mode 100644
index 9e12a35..0000000
--- a/rt/html/Admin/Queues/Scrip.html
+++ /dev/null
@@ -1,100 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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 &>
-
-<& /Elements/ListActions, actions => \@results &>
-<& /Admin/Elements/EditScrip, title => $title, %ARGS, id => $id &>
-
-<%init>
-my $QueueObj = RT::Queue->new( $session{'CurrentUser'} );
-$QueueObj->Load( $Queue );
-unless( $QueueObj->id ) {
- Abort(loc("Queue [_1] not found", $id));
-}
-
-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,
- },
-};
-
-my $scrip = RT::Scrip->new( $session{'CurrentUser'} );
-($id, my @results) = $m->comp( '/Admin/Elements/EditScrip:Process', %ARGS );
-
-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]",$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
deleted file mode 100644
index 1fc1fa0..0000000
--- a/rt/html/Admin/Queues/Scrips.html
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index b842f9e..0000000
--- a/rt/html/Admin/Queues/Template.html
+++ /dev/null
@@ -1,130 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/QueueTabs, id => $Queue,
- QueueObj => $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" class="hidden" name="Template" value="new" />
-% } else {
-<input type="hidden" class="hidden" name="Template" value="<%$TemplateObj->Id%>" />
-% }
-
-%# hang onto the queue id
-<input type="hidden" class="hidden" name="Queue" value="<%$Queue%>" />
-<& /Admin/Elements/ModifyTemplate, Name => $TemplateObj->Name, Description =>
-$TemplateObj->Description, Content => $TemplateObj->Content &>
-<& /Elements/Submit, Label => loc('Create'), 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()));
-
-
-}
-my $QueueObj;
-if ($TemplateObj->Id()) {
- $Queue = $TemplateObj->Queue;
- $QueueObj = $TemplateObj->QueueObj;
-
- 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;
-} else {
- $QueueObj = RT::Queue->new($session{'CurrentUser'});
- $QueueObj->Load($Queue);
-}
-
-</%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
deleted file mode 100644
index 2da737f..0000000
--- a/rt/html/Admin/Queues/Templates.html
+++ /dev/null
@@ -1,81 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 2984774..0000000
--- a/rt/html/Admin/Queues/UserRights.html
+++ /dev/null
@@ -1,114 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="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, Label => loc('Modify User Rights'), 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
deleted file mode 100644
index 09d1fb3..0000000
--- a/rt/html/Admin/Queues/index.html
+++ /dev/null
@@ -1,86 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc("Admin queues") &>
-<& /Admin/Elements/QueueTabs, current_tab => 'Admin/Queues/',
- current_subtab => 'Admin/Queues/',
- Title => loc("Admin queues") &>
-
-
-
-<h1><%$caption%></h1>
-<p><&|/l&>Select a queue</&>:</p>
-<ul>
-%if ($queues->Count == 0) {
-<li><em><&|/l&>No queues matching search criteria found.</&></em></li>
-% }
-%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" class="checkbox" name="FindDisabledQueues" value="1" /> <&|/l&>Include disabled queues in listing.</&>
-<div align="right"><input type="submit" class="button" 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/Tools/Configuration.html b/rt/html/Admin/Tools/Configuration.html
deleted file mode 100644
index 3576b5c..0000000
--- a/rt/html/Admin/Tools/Configuration.html
+++ /dev/null
@@ -1,100 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-require Module::Versions::Report;
-my $title = loc('System Configuration');
-unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
- Abort(loc('This feature is only available to system administrators'));
-}
-
-
-</%init>
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/ToolTabs,
- current_tab => 'Admin/Tools/Configuration.html',
- current_subtab => 'Admin/Tools/Configuration.html',
- Title => $title &>
-
-
-
-
-<h2><&|/l&>Loaded perl modules</&></h2>
-<pre>
-% my $report = Module::Versions::Report::report();
-% my @report = grep { /v\d/ } split("\n",$report);
-<%join('<br />', @report)|n%>
-
-
-</pre>
-
-<h2><&|/l&>RT Variables</&></h2>
-<table>
-%{
-%no strict qw/refs/;
-
-%foreach my $key (sort keys %{*RT::}) {
-% next unless (${'RT::'.$key} );
-% next if (ref ${'RT::'.$key} );
-<tr><td>RT::<%$key%></td>
-<td>
-% if ($key =~ /Password(?!Length)/i) {
-<em>Password not printed</em>
-% } else {
-<%${'RT::'.$key} %>
-% }
-</td>
-</tr>
-% }
-%}
-</table>
-
-<h2><&|/l&>Perl configuration</&></h2>
-% require Config;
-<pre>
-<%Config::myconfig()%>
-</pre>
diff --git a/rt/html/Admin/Tools/index.html b/rt/html/Admin/Tools/index.html
deleted file mode 100644
index 730fa7e..0000000
--- a/rt/html/Admin/Tools/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-my $title = loc('System Tools');
-</%init>
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/ToolTabs,
- current_tab => 'Admin/Tools/index.html',
- current_subtab => 'Admin/Tools/Configuration.html',
- Title => $title &>
diff --git a/rt/html/Admin/Users/CustomFields.html b/rt/html/Admin/Users/CustomFields.html
deleted file mode 100644
index 3943cbf..0000000
--- a/rt/html/Admin/Users/CustomFields.html
+++ /dev/null
@@ -1,71 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/UserTabs,
- id => $Object->id,
- current_tab => "Admin/Users/CustomFields.html?$id=".$id,
- current_subtab => "Admin/Users/CustomFields.html?id=".$id,
- UserObj => $Object,
- Title => $title
- &>
-
- <& /Admin/Elements/EditCustomFields, %ARGS, title => $title, Object => $Object, ObjectType => 'RT::User' &>
-<%INIT>
-my $Object = RT::User->new( $session{'CurrentUser'} );
-
-$Object->Load($id) || Abort( loc( "Couldn't load object [_1]", $id ) );
-my $FriendlySubTypes =
- RT::CustomField->new( $session{'CurrentUser'} )
- ->FriendlyLookupType( $Object->CustomFieldLookupType );
-
-my $title = loc( 'Edit Custom Fields for [_1]', $Object->Name );
-
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/Users/History.html b/rt/html/Admin/Users/History.html
deleted file mode 100644
index a4782d1..0000000
--- a/rt/html/Admin/Users/History.html
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/UserTabs,
- id => $id,
- UserObj => $UserObj,
- current_tab => $current_tab,
- Title => $title &>
-
-<& /Ticket/Elements/ShowHistory,
- Ticket => $UserObj,
- ShowDisplayModes => 0,
-&>
-
-<%INIT>
-my $current_tab = 'Admin/Users/History.html?id='.$id;
-my $UserObj = new RT::User($session{'CurrentUser'});
-$UserObj->Load($id) || Abort("Couldn't load user '$id'");
-my $title = loc("History of the user [_1]", $UserObj->Name);
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/Users/Memberships.html b/rt/html/Admin/Users/Memberships.html
deleted file mode 100644
index 4b4d1ca..0000000
--- a/rt/html/Admin/Users/Memberships.html
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/UserTabs,
- id => $id,
- UserObj => $UserObj,
- current_tab => $current_tab,
- Title => $title &>
-
-<h2><&|/l&>Groups</&></h2>
-
-<& /Elements/ShowMemberships, UserObj => $UserObj &>
-
-<%INIT>
-my $UserObj = RT::User->new($session{'CurrentUser'});
-$UserObj->Load($id) || Abort("Couldn't load user '$id'");
-my $title = loc("Memberships of the user [_1]", $UserObj->Name);
-my $current_tab = 'Admin/Users/Memberships.html?id='.$id;
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/Users/Modify.html b/rt/html/Admin/Users/Modify.html
deleted file mode 100644
index 75a7696..0000000
--- a/rt/html/Admin/Users/Modify.html
+++ /dev/null
@@ -1,433 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/UserTabs,
- id => $id,
- UserObj => $UserObj,
- current_tab => $current_tab,
- Title => $title &>
-
-<& /Elements/ListActions, actions => \@results &>
-
-<form action="<%$RT::WebPath%>/Admin/Users/Modify.html" method="post" enctype="multipart/form-data">
-%if ($Create) {
-<input type="hidden" class="hidden" name="id" value="new" />
-% } else {
-<input type="hidden" class="hidden" name="id" value="<%$UserObj->Id%>" />
-% }
-<table width="100%" border="0">
-<tr>
-
-<td valign="top" class="boxcontainer">
-<&| /Widgets/TitleBox, title => loc('Identity') &>
-
-<table>
-<tr><td align="right">
-<&|/l&>Username</&>:
-</td><td>
-<input name="Name" value="<%$UserObj->Name%>" /> <strong><&|/l&>(required)</&></strong>
-</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&>Language</&>:
-</td><td>
-<& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &>
-</td></tr>
-<tr><td align="right">
-<&|/l&>Extra info</&>:
-</td><td>
-<textarea name="FreeformContactInfo" cols="20" rows="5"><%$UserObj->FreeformContactInfo%></textarea>
-</td></tr>
-</table>
-</&>
-<br />
-<&| /Widgets/TitleBox, title => loc('Access control') &>
-<input type="hidden" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> />
-<&|/l&>Let this user access RT</&><br />
-
-
-<input type="hidden" class="hidden" name="SetPrivileged" value="1" />
-<input type="checkbox" class="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/Callback, _CallbackName => 'LeftColumnBottom', UserObj => $UserObj, %ARGS &>
-</td>
-
-<td valign="top" class="boxcontainer">
-<&| /Widgets/TitleBox, 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>
-</&>
-<br />
-<&| /Widgets/TitleBox, 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 />
-</td>
-</tr>
-</table>
-</&>
-<br />
-<&| /Widgets/TitleBox, title => loc('Custom Fields') &>
-<table>
-% my $CFs = $UserObj->CustomFields;
-% while (my $CF = $CFs->Next) {
-<tr valign="top"><td align="right">
-<% $CF->Name %>:
-</td><td>
-% if ($UserObj->id) {
-<& /Elements/EditCustomField, %ARGS, Object => $UserObj, CustomField => $CF &>
-% } else {
-<& /Elements/EditCustomField, %ARGS, NamePrefix => 'Object-RT::User-new-CustomField-', CustomField => $CF &>
-% }
-</td></tr>
-% }
-</table>
-</&>
-<& /Elements/Callback, _CallbackName => 'RightColumnBottom', UserObj => $UserObj, %ARGS &>
-</td></tr>
-<tr>
-<td colspan="2">
-<&| /Widgets/TitleBox, title => loc('Comments about this user') &>
-<textarea class="comments" name="Comments" cols="80" rows="5" wrap="virtual"><%$UserObj->Comments%></textarea>
-</&>
-%if ($UserObj->Privileged) {
-<br />
-<&| /Widgets/TitleBox, title => loc('Signature') &>
-<textarea class="signature" cols="80" rows="5" name="Signature" wrap="hard"><%$UserObj->Signature%></textarea>
-</&>
-% }
-
-</td>
-</tr>
-</table>
-
-<& /Elements/Submit, Label => loc('Save Changes') &>
-</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'},
- Name => $ARGS{'Name'},
- 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'},
- Gecos => $ARGS{'Gecos'},
- ExternalAuthId => $ARGS{'ExternalAuthId'},
- AuthSystem => $ARGS{'AuthSystem'},
- 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'},
- Privileged => $ARGS{'Privileged'},
- Disabled => ($ARGS{'Enabled'} ? 0 : 1)
- );
-
- if ($val) {
- push @results, $msg;
- foreach my $key ( keys %ARGS) {
- # Convert custom fields on the "new" object to custom fields on the one we've just created
- if ($key =~ /^Object-RT::User-new-CustomField-(.*)$/) {
- $ARGS{'Object-RT::User-'.$val.'-CustomField-'.$1} = delete $ARGS{$key};
- }
- }
- push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj );
- } else {
- push @results, loc('User could not be created: [_1]', $msg);
- }
- } 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 && $id ne 'new') {
-
- 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);
- push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj );
-
-
- # {{{ Deal with special fields: Privileged, Enabled
- 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.
- $Disabled = $Enabled ? 0 : 1;
-
- if ( ($SetEnabled) and ( $Disabled != $UserObj->Disabled) ) {
- my ($code, $msg) = $UserObj->SetDisabled($Disabled);
- push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
- }
-
-
- # }}}
-}
-
-if ( $UserObj->Id ) {
- my $password_not_set;
- # Deal with Password field
- if ( !$Pass1 and !$Pass2 ) {
- $password_not_set = 1;
- } elsif ( $Pass1 ne $Pass2 ) {
- $password_not_set = 1;
- push @results, loc("Passwords do not match.");
- } elsif ( $Pass1 eq $Pass2 and !$UserObj->IsPassword($Pass1) ) {
- my ($code, $msg) = $UserObj->SetPassword($Pass1);
- push @results, loc_fuzzy($msg);
- $password_not_set = 1 unless $code;
- }
- if ($id eq 'new' and $password_not_set) {
- push @results, loc("A password was not set, so user won't be able to login.");
- }
-}
-
-
-# {{{ Do some setup for the ui
-unless ($UserObj->Disabled()) {
- $EnabledChecked ="CHECKED";
-}
-
-if ($UserObj->Privileged()) {
- $PrivilegedChecked = "CHECKED";
-}
-
-# }}}
-
-# set the id, so the the menu will have the right info, this needs to
-# be done here to avoid creating and then modifying a user
-$id = $UserObj->Id;
-
-</%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/MyRT.html b/rt/html/Admin/Users/MyRT.html
deleted file mode 100644
index a963b66..0000000
--- a/rt/html/Admin/Users/MyRT.html
+++ /dev/null
@@ -1,132 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => $title &>
-<& /Admin/Elements/UserTabs,
- id => $id,
- UserObj => $UserObj,
- current_tab => $current_tab,
- Title => $title &>
-
-<& /Widgets/SelectionBox:header, nojs => 1 &>
-
-<& /Elements/ListActions, actions => \@actions &>
-
-<form method="post" action="MyRT.html">
-<input type="hidden" name="id" value="<% $id %>" />
-<input type="hidden" name="Reset" value="1" />
-<input type="submit" class="button" value="<%loc('Reset to default')%>">
-</form>
-
-<br />
-
-% for my $pane (@panes) {
-<&|/Widgets/TitleBox, title => loc('RT at a glance').': '.loc($pane->{Name}), bodyclass => "" &>
-<& /Widgets/SelectionBox:show, self => $pane, nojs => 1 &></&>
-<br />
-% }
-
-<%init>
-my $current_tab = 'Admin/Users/MyRT.html?id='.$id;
-my $UserObj = new RT::User($session{'CurrentUser'});
-$UserObj->Load($id) || Abort("Couldn't load user '$id'");
-my $title = loc("RT at a glance for the user [_1]", $UserObj->Name);
-
-if ($ARGS{Reset}) {
- $UserObj->SetPreferences('HomepageSettings', {});
-}
-
-my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
-my $portlets = $UserObj->Preferences('HomepageSettings', $default_portlets ? $default_portlets->Content : {});
-
-my %allowed_components = map {$_ => 1} @{$RT::HomepageComponents};
-my @items;
-
-push @items, map {["component-$_", $_]} sort keys %allowed_components;
-
-my $sys = RT::System->new( RT::CurrentUser->new($UserObj) );
-my @objs = ($sys);
-
-push @objs, RT::SavedSearches->new( RT::CurrentUser->new( $UserObj ) )->_PrivacyObjects;
-my @actions;
-
-for my $object (@objs) {
- for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
- my ($desc, $search) = @$_;
- my $SearchType = $search->Content->{'SearchType'} || 'Ticket';
- if ($object eq $sys && $SearchType eq 'Ticket') {
- push @items, ["system-$desc", $desc];
- }
- else {
- my $oid = ref($object).'-'.$object->Id.'-SavedSearch-'.$search->Id;
- my $type = ($SearchType eq 'Ticket')
- ? 'Saved Search' : $SearchType; # loc
- push @items, ["saved-$oid", loc($type).": $desc"];
- }
- }
-}
-
-my @panes = $m->comp(
- '/Admin/Elements/ConfigureMyRT',
- panes => ['body', 'summary'],
- Action => "MyRT.html?id=$id",
- items => \@items,
- current_portlets => $portlets,
- OnSave => sub {
- my ( $conf, $pane ) = @_;
- $UserObj->SetPreferences( 'HomepageSettings', $conf );
- push @actions, loc( 'Preferences [_1] for user [_2] .', $pane, $UserObj->Name );
- }
-);
-
-$m->comp( '/Widgets/SelectionBox:process', %ARGS, self => $_, nojs => 1 )
- for @panes;
-
-</%init>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Admin/Users/Prefs.html b/rt/html/Admin/Users/Prefs.html
deleted file mode 100644
index 0bba9fa..0000000
--- a/rt/html/Admin/Users/Prefs.html
+++ /dev/null
@@ -1,122 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index 4d24b8f..0000000
--- a/rt/html/Admin/Users/index.html
+++ /dev/null
@@ -1,115 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc('Select a user') &>
-<& /Admin/Elements/UserTabs, current_tab => 'Admin/Users/',
- current_subtab => 'Admin/Users/',
- Title => loc('Select a user') &>
-
-<h1><%$caption%></h1>
-<p><&|/l&>Select a user</&>:</p>
-<ul>
-%if ($users->Count == 0) {
-<li><em><&|/l&>No users matching search criteria found.</&></em></li>
-% }
-%my @ids;
-%while ( $user = $users->Next) {
-% push @ids, $user->Id;
-<li><a href="Modify.html?id=<%$user->id%>"><%$user->Name || loc('(no name listed)')%></a></li>
-%}
-</ul>
-%if (my $ids = join(',', @ids)) {
-<em>(<a href="<%$RT::WebPath%>/Download/Tabular/User/<% $ids %>/Users.tsv"><&|/l&>Download as a tab-delimited file</&></a>)</em><br />
-%}
-
-<br /><br />
-<form method="post" action="<% $RT::WebPath %>/Admin/Users/index.html">
-
-<&|/l&>Find people whose</&> <& /Elements/SelectUsers &><br />
-<input type="checkbox" class="checkbox" name="FindDisabledUsers" value="1" /> <&|/l&>Include disabled users in search.</&>
-<br />
-<div align="right"><input type="submit" class="button" value="<&|/l&>Go!</&>" /></div>
-</form>
-
-<%INIT>
-my ($user, $caption);
-my $users = new RT::Users($session{'CurrentUser'});
-
-if ($FindDisabledUsers) {
- $users->{'find_disabled_rows'} = 1;
-}
-
-if (length $UserString) {
- $caption = loc("Users matching search criteria");
- if ($UserField =~ /^CustomField-(\d+)/) {
- $users->LimitCustomField(
- CUSTOMFIELD => $1,
- OPERATOR => $UserOp,
- VALUE => $UserString,
- );
- }
- else {
- $users->Limit(
- FIELD => $UserField,
- OPERATOR => $UserOp,
- VALUE => $UserString,
- );
- }
-}
-else {
- $caption = loc("Privileged users");
- $users->LimitToPrivileged;
-}
-</%INIT>
-<%ARGS>
-$UserString => undef
-$UserOp => '='
-$UserField => 'Name'
-$IdLike => undef
-$EmailLike => undef
-$FindDisabledUsers => 0
-</%ARGS>
diff --git a/rt/html/Admin/autohandler b/rt/html/Admin/autohandler
deleted file mode 100644
index 28437e9..0000000
--- a/rt/html/Admin/autohandler
+++ /dev/null
@@ -1,53 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-$m->call_next(%ARGS) if $session{'CurrentUser'}->UserObj->HasRight(
- Right => 'ShowConfigTab',
- Object => $RT::System,
-);
-</%init> \ No newline at end of file
diff --git a/rt/html/Admin/index.html b/rt/html/Admin/index.html
deleted file mode 100644
index ec6d0a2..0000000
--- a/rt/html/Admin/index.html
+++ /dev/null
@@ -1,101 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Admin/Elements/Header, Title => loc('RT Administration') &>
-<& /Admin/Elements/Tabs, Title => loc('RT Administration') &>
-
-<ul>
-% foreach my $key (sort keys %$tabs) {
-<li><span><a href="<%$RT::WebPath%>/<%$tabs->{$key}->{'path'}|n %>"><%$tabs->{$key} ->{'title'}%></a></span><br />
-<%$tabs->{$key}->{description}%>
-</li>
-%}
-</ul>
-<%init>
-
-
-
-my $tabs = {
- A => {
- title => loc('Users'),
- path => 'Admin/Users/index.html',
- description => loc('Manage users and passwords'),
- },
- B => {
- title => loc('Groups'),
- path => 'Admin/Groups/index.html',
- description => loc('Manage groups and group membership'),
- },
- C => {
- title => loc('Queues'),
- path => 'Admin/Queues/index.html',
- description => loc('Manage queues and queue-specific properties'),
- },
- D => {
- 'title' => loc('Custom Fields'),
- description => loc('Manage custom fields and custom field values'),
- path => 'Admin/CustomFields/index.html',
- },
- E => {
- 'title' => loc('Global'),
- path => 'Admin/Global/index.html',
- description =>
- loc('Manage properties and configuration which apply to all queues'),
- },
- F => {
- 'title' => loc('Tools'),
- path => 'Admin/Tools/index.html',
- description => loc('Use other RT administrative tools')
- },
-};
-
- $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
-
-
-
-
-</%init>
diff --git a/rt/html/Approvals/Display.html b/rt/html/Approvals/Display.html
deleted file mode 100644
index 3735df5..0000000
--- a/rt/html/Approvals/Display.html
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-
-<& Elements/Tabs,
- current_tab => "Approvals/Display.html",
- Title => $title &>
-<form method="post" action="<%$RT::WebPath%>/Approvals/index.html">
-
-<&| /Widgets/TitleBox, title => $title &>
-<& /Ticket/Elements/ShowHistory , Ticket => $Ticket, Collapsed => 0, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowTitleBarCommands => 0 &>
-<hr>
-<& Elements/Approve, ticket => $Ticket, ShowApproving => 0 &>
-</&>
-<& /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
deleted file mode 100644
index 65d2276..0000000
--- a/rt/html/Approvals/Elements/Approve
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="approval">
- <div class="name">
- <a href="<%$RT::WebPath%>/Approvals/Display.html?id=<%$ticket->Id%>"><% loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject) %></a> (<%loc($ticket->Status)%>)
- </div>
-% if ($ShowApproving) {
-% foreach my $approving ( $ticket->AllDependedOnBy( Type => 'ticket' ) ) {
- <div class="originating-ticket">
- <span class="link"><a href="<%$RT::WebPath%>/Ticket/Display.html?id=<% $approving->Id %>"><&|/l, $approving->Id, $approving->Subject &>Originating ticket: #[_1]</&></a></span>
- <div class="info">
-% if ($ShowCustomFields) {
- <& /Ticket/Elements/ShowCustomFields, Ticket => $approving &>
-% }
-% if ($ShowHistory) {
- <& /Ticket/Elements/ShowHistory, Ticket => $approving, Collapsed => 0, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowTitleBarCommands => 0 &>
-% }
- </div>
- </div>
-% }
-% }
- <div class="form">
- <div class="action">
- <div>
- <input type="radio" class="radio" id="Approval-<%$ticket->Id%>-Action-approve" name="Approval-<%$ticket->Id%>-Action" value="approve" />
- <label for="Approval-<%$ticket->Id%>-Action-approve"><&|/l&>Approve</&></label>
- </div>
- <div>
- <input type="radio" class="radio" id="Approval-<%$ticket->Id%>-Action-deny" name="Approval-<%$ticket->Id%>-Action" value="deny" />
- <label for="Approval-<%$ticket->Id%>-Action-deny"><&|/l&>Deny</&></label>
- </div>
- <div>
- <input type="radio" class="radio" id="Approval-<%$ticket->Id%>-Action-none" name="Approval-<%$ticket->Id%>-Action" value="none" checked="checked" />
- <label for="Approval-<%$ticket->Id%>-Action-none"><&|/l&>No action</&></label>
- </div>
- </div>
- <div class="notes">
- <label for="Approval-<%$ticket->Id%>-Notes"><&|/l&>Notes</&></label>
- <textarea name="Approval-<%$ticket->Id%>-Notes" id="Approval-<%$ticket->Id%>-Notes" rows="2" cols="70"></textarea>
- </div>
- <div class="clear"></div>
- </div>
-</div>
-<%ARGS>
-$ShowApproving => 1
-$ShowCustomFields => 1
-$ShowHistory => 1
-$ticket => undef
-</%ARGS>
diff --git a/rt/html/Approvals/Elements/PendingMyApproval b/rt/html/Approvals/Elements/PendingMyApproval
deleted file mode 100644
index 741e638..0000000
--- a/rt/html/Approvals/Elements/PendingMyApproval
+++ /dev/null
@@ -1,111 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<script type="text/javascript"><!--
- onLoadHook('createCalendarLink("CreatedBefore");');
- onLoadHook('createCalendarLink("CreatedAfter");');
---></script>
-% 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
-<& Approve, ticket => $ticket &>
-% }
-% }
-
-<&| /Widgets/TitleBox, title => loc("Search for approvals") &>
-<input type="checkbox" class="checkbox" value="1" name="ShowPending"
- <%((!$ARGS{'ShowRejected'} && !$ARGS{'ShowResolved'}) ||
- $ARGS{'ShowPending'})
- && "checked"%> /> <&|/l&>Show pending requests</&><br />
-<input type="checkbox" class="checkbox" value="1" name="ShowResolved" <%$ARGS{'ShowResolved'} && "checked"%> /> <&|/l&>Show approved requests</&><br />
-<input type="checkbox" class="checkbox" value="1" name="ShowRejected" <%$ARGS{'ShowRejected'} && "checked"%> /> <&|/l&>Show denied requests</&><br />
-<input type="checkbox" class="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' id='CreatedBefore' />"&>Only show approvals for requests created before [_1]</&><br />
-
-<&|/l, "<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='CreatedAfter' />"&>Only show approvals for requests created after [_1]</&>
-</&>
-
-<%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'} );
-$group_tickets->LimitWatcher( VALUE => $session{'CurrentUser'}->UserObj->EmailAddress, TYPE => 'AdminCc' );
-
-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
deleted file mode 100644
index 8be815c..0000000
--- a/rt/html/Approvals/Elements/ShowDependency
+++ /dev/null
@@ -1,109 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% 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('/Widgets/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('/Widgets/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('/Widgets/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('/Widgets/TitleBoxEnd');
- $text .= $m->scomp('/Widgets/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
deleted file mode 100644
index 3a4ba7c..0000000
--- a/rt/html/Approvals/Elements/Tabs
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 06f0539..0000000
--- a/rt/html/Approvals/index.html
+++ /dev/null
@@ -1,90 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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, Label => loc('Go!') &>
-</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/Callbacks/ActivityReports/Elements/Tabs/Default b/rt/html/Callbacks/ActivityReports/Elements/Tabs/Default
new file mode 100644
index 0000000..f85d2e0
--- /dev/null
+++ b/rt/html/Callbacks/ActivityReports/Elements/Tabs/Default
@@ -0,0 +1,7 @@
+<%init>
+if ($ARGS{current_toptab} eq "Tools/Offline.html") {
+ $ARGS{tabs}{r} ||= { path => 'Reports/Activity/index.html',
+ title => 'Reports',
+ };
+}
+</%init> \ No newline at end of file
diff --git a/rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default b/rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default
new file mode 100644
index 0000000..30480f7
--- /dev/null
+++ b/rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default
@@ -0,0 +1,71 @@
+table.miniplot {
+ width: 100%;
+ border-collapse: collapse;
+}
+table.miniplot td {
+ margin: 0;
+ padding: 0;
+ border-bottom: 1px solid black;
+}
+table.miniplot .graph {
+ margin-left: auto;
+ margin-right: auto;
+ position: relative;
+ width: 60px;
+}
+table.miniplot .graph ul {
+ height: 100px;
+ margin: 0;
+ padding: 0;
+}
+table.miniplot .graph ul li {
+ list-style: none;
+ position: absolute;
+ bottom: 0px;
+ padding: 0 !important;
+ margin: 0 !important;
+ border-bottom: none;
+}
+table.miniplot .graph ul li .data {
+ display: none;
+}
+
+.miniplot .demoblock { margin: 0 10px; padding: 0 30px; }
+
+.miniplot .c1 { border: 2px solid #990000; background: #ff0000; }
+.miniplot .c2 { border: 2px solid #996600; background: #ff9900; }
+.miniplot .c3 { border: 2px solid #009900; background: #00ff00; }
+.miniplot .c4 { border: 2px solid #009999; background: #00ffff; }
+.miniplot .c5 { border: 2px solid #000099; background: #0000ff; }
+.miniplot .c6 { border: 2px solid #990099; background: #ff00ff; }
+graph .c5 { border: 2px solid #000099; background: #0000ff; }
+.graph .c6 { border: 2px solid #990099; background: #ff00ff; }
+
+tr.titlerow th {
+
+ border-bottom: solid black 1px;
+ margin: 0;
+ font-size:80%;
+ text-wrap: none;
+
+}
+
+tr.grandtotal td{
+ border-top: 1px solid black;
+}
+
+tr.grandtotal th{
+ border-top: 1px solid black;
+}
+
+th.label {
+ align: left;
+
+}
+
+table.miniplot th.legend {
+ font-style: normal;
+ font-size: 80%;
+
+}
+
diff --git a/rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions b/rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions
new file mode 100644
index 0000000..4775a9a
--- /dev/null
+++ b/rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions
@@ -0,0 +1,7 @@
+<a href="<% $RT::WebPath %>/Reports/Activity/index.html?<% $QueryString %>">Generate reports</a>
+<%init>
+use YAML;
+my %args = $m->caller_args(2);
+
+my $QueryString = $m->comp('/Elements/QueryString', query => $args{Query});
+</%init> \ No newline at end of file
diff --git a/rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default b/rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default
new file mode 100644
index 0000000..db74ced
--- /dev/null
+++ b/rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default
@@ -0,0 +1,13 @@
+%# The day after tomorrow is the third day of the rest of your life.
+<%INIT>
+if ($session{'CurrentUser'}->UserObj->HasRight(
+ Right => 'SuperUser',
+ Object => $RT::System,
+)) {
+ $toptabs->{'ZZ-RT-WebCronTool'} = { title =>loc("Web CronTool"),
+ path => "Developer/CronTool/index.html" };
+}
+</%init>
+<%args>
+$toptabs =>undef
+</%args>
diff --git a/rt/html/Callbacks/kStatistics/Elements/Tabs/Default b/rt/html/Callbacks/kStatistics/Elements/Tabs/Default
new file mode 100644
index 0000000..d4ca2b9
--- /dev/null
+++ b/rt/html/Callbacks/kStatistics/Elements/Tabs/Default
@@ -0,0 +1,11 @@
+<%init>
+use RTx::Statistics;
+if (($Statistics::RestrictAccess == 0) || ($session{'CurrentUser'}->HasRight( Right => 'ShowConfigTab',
+ Object => $RT::System ))) {
+ $toptabs->{'ZZ-RTx-STATS'} = { title => 'RTx-Statistics',
+ path => "RTx/Statistics/index.html" };
+}
+</%init>
+<%args>
+ $toptabs =>undef
+</%args>
diff --git a/rt/html/Developer/CronTool/autohandler b/rt/html/Developer/CronTool/autohandler
new file mode 100644
index 0000000..7daa09e
--- /dev/null
+++ b/rt/html/Developer/CronTool/autohandler
@@ -0,0 +1,9 @@
+%# All theoretical chemistry is really physics;
+%# and all theoretical chemists know it.
+%# -- Richard P. Feynman
+<%INIT>
+$m->call_next(%ARGS) if $session{'CurrentUser'}->UserObj->HasRight(
+ Right => 'SuperUser',
+ Object => $RT::System,
+);
+</%INIT>
diff --git a/rt/html/Developer/CronTool/index.html b/rt/html/Developer/CronTool/index.html
new file mode 100644
index 0000000..67c9e56
--- /dev/null
+++ b/rt/html/Developer/CronTool/index.html
@@ -0,0 +1,116 @@
+% if ($@) {
+<P><FONT Color="red"><% $@ %></FONT></P>
+% }
+% if (!$NoUI) {
+<HR>
+<FORM Action="index.html" Method="POST">
+<TABLE>
+% foreach my $class (qw( Search Condition Action )) {
+<TR><TH>
+<% loc($class) %>
+</TH><TD>
+<SELECT NAME="<% $class %>">
+% require File::Find;
+% my @modules;
+% File::Find::find(sub {
+% push @modules, $1 if /^(?!Generic|UserDefined)(\w+)\.pm$/i;
+% }, grep -d, map "$_/RT/$class", @INC);
+<OPTION <% $ARGS{$class} ? '' : 'SELECTED' %>></OPTION>
+% foreach my $module (sort @modules) {
+% my $fullname = "RT::$class\::$module";
+ <OPTION VALUE="<% $fullname %>" <% ($fullname eq $ARGS{$class}) ? 'SELECTED' : '' %>><% $module %></OPTION>
+% }
+</SELECT>
+</TD><TH>
+<&|/l&>Parameter</&>
+</TH><TD>
+<INPUT NAME="<% $class %>Arg" VALUE="<% $ARGS{$class.'Arg'} %>">
+</TD></TR>
+% }
+<TR>
+<TD COLSPAN="4" ALIGN="Right">
+<LABEL>
+<INPUT TYPE="CheckBox" NAME="Verbose" <% $Verbose ? 'CHECKED' : '' %>><&|/l&>Verbose</&>
+</LABEL>
+<INPUT TYPE="Submit" VALUE="<&|/l&>Run</&>">
+</TD>
+</TABLE>
+</FORM>
+<HR>
+% }
+<%INIT>
+$m->print("<H1>", loc("Web CronTool"), "</H1>");
+if ($Search) {
+ my $load_module = sub {
+ my $modname = $_[0];
+ $modname =~ s{::}{/}g;
+ require "$modname.pm" or die (
+ loc( "Failed to load module [_1]. ([_2])", $_[0], $@ ) . "\n"
+ );
+ };
+ $m->print(loc("Starting..."), "<UL>");
+ eval {
+ $load_module->($Search);
+ $load_module->($Action) if $Action;
+ $load_module->($Condition) if $Condition;
+
+ if ($TemplateId and !$TemplateObj) {
+ $TemplateObj = RT::Template->new($RT::Nobody);
+ $TemplateObj->LoadById($TemplateId);
+ }
+
+ my $tickets = RT::Tickets->new($RT::SystemUser);
+ my $search = $Search->new( TicketsObj => $tickets, Argument => $SearchArg );
+ $search->Prepare;
+ my $tickets_found = $search->TicketsObj;
+
+ #for each ticket we've found
+ while ( my $ticket = $tickets_found->Next ) {
+ $m->print("<LI>" . $ticket->Id . ": ") if $Verbose;
+ $m->print(loc("Checking...")) if $Verbose;
+
+ # perform some more advanced check
+ if ($Condition) {
+ my $ConditionObj = $Condition->new(
+ TicketObj => $ticket,
+ Argument => $ConditionArg
+ );
+
+ # if the condition doesn't apply, get out of here
+ next unless ( $ConditionObj->IsApplicable );
+ $m->print(loc("Condition matches...")) if $Verbose;
+ }
+
+ if ($Action) {
+ #prepare our action
+ my $ActionObj = $Action->new(
+ TicketObj => $ticket,
+ TemplateObj => $TemplateObj,
+ Argument => $ActionArg
+ );
+
+ #if our preparation, move onto the next ticket
+ next unless ( $ActionObj->Prepare );
+ $m->print(loc("Action prepared...")) if $Verbose;
+
+ #commit our action.
+ next unless ( $ActionObj->Commit );
+ $m->print(loc("Action committed.")) if $Verbose;
+ }
+ }
+ };
+ $m->print('</UL>', loc("Finished."));
+}
+</%INIT>
+<%ARGS>
+$Search => undef
+$SearchArg => undef
+$Condition => undef
+$ConditionArg => undef
+$Action => undef
+$ActionArg => undef
+$TemplateId => undef
+$TemplateObj => undef
+$Verbose => 1
+$NoUI => 0
+</%ARGS>
diff --git a/rt/html/Download/CustomFieldValue/dhandler b/rt/html/Download/CustomFieldValue/dhandler
deleted file mode 100644
index e713807..0000000
--- a/rt/html/Download/CustomFieldValue/dhandler
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%perl>
-my $id;
-my $arg = $m->dhandler_arg; # get rest of path
-if ($arg =~ /^(\d+)\//) {
- $id = $1;
-}
-else {
- Abort("Corrupted customfieldvalue URL.");
-}
-my $OCFV = RT::ObjectCustomFieldValue->new($session{'CurrentUser'});
-$OCFV->Load($id) || Abort("OCFV '$id' could not be loaded");
-
-unless ($OCFV->id) {
- Abort("Bad OCFV id. Couldn't find OCFV '$id'\n");
-}
-
-my $content_type = $OCFV->ContentType || 'text/plain';
-
-unless ($RT::TrustHTMLAttachments) {
- $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);
-}
-
-$r->content_type( $content_type );
-$m->clear_buffer();
-$m->out($OCFV->LargeContent);
-$m->abort;
-</%perl>
-<%attr>
-AutoFlush => 0
-</%attr>
diff --git a/rt/html/Download/Tabular/dhandler b/rt/html/Download/Tabular/dhandler
deleted file mode 100644
index 5cad794..0000000
--- a/rt/html/Download/Tabular/dhandler
+++ /dev/null
@@ -1,76 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%perl>
-my ($class, $filename, @ids);
-my $arg = $m->dhandler_arg; # get rest of path
-if ($arg =~ /^(\w+)\/([,\d]+)(?:\/([^\/]+))?/) {
- $class = "RT::$1";
- $filename = $3 || "$1s.tsv";
- @ids = sort split(/,+/, $2);
-}
-else {
- Abort("Corrupted tabular URL.");
-}
-
-my @cols = $class->BasicColumns or return;
-
-#$r->content_type( 'application/octet-stream' );
-$r->content_type( 'text/plain' );
-$r->headers_out->{'Content-Disposition'} = "attachment; filename=$filename";
-$m->clear_buffer();
-$m->out(join("\t", "Id", map $_->[1], @cols), "\n");
-foreach my $id (@ids) {
- my $obj = $class->new;
- $obj->Load($id) or next;
- $m->out(join("\t", map $obj->$_, "Id", map $_->[0], @cols), "\n");
-}
-$m->abort;
-</%perl>
-<%attr>
-AutoFlush => 0
-</%attr>
diff --git a/rt/html/Elements/BevelBoxRaisedEnd b/rt/html/Elements/BevelBoxRaisedEnd
deleted file mode 100644
index be60dfb..0000000
--- a/rt/html/Elements/BevelBoxRaisedEnd
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- </td>
-</tr>
-</table>
diff --git a/rt/html/Elements/BevelBoxRaisedStart b/rt/html/Elements/BevelBoxRaisedStart
deleted file mode 100644
index 6468187..0000000
--- a/rt/html/Elements/BevelBoxRaisedStart
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 04864c6..0000000
--- a/rt/html/Elements/Callback
+++ /dev/null
@@ -1,92 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%once>
-my %cache;
-</%once>
-<%init>
-$Page = $m->callers(1)->path unless ($Page);
-
-my $CacheKey = "Callback--$Page--$_CallbackName";
-my $callbacks = $cache{$CacheKey} || $m->notes($CacheKey);
-
-if (!$callbacks) {
- my $path = "/Callbacks/*$Page/$_CallbackName";
-
- # Due to API changes after Mason 1.28, we have to check for which
- # version we're running when getting the component roots
- my @roots = map { $_->[1] }
- $HTML::Mason::VERSION <= 1.28
- ? $m->interp->resolver->comp_root_array
- : $m->interp->comp_root_array;
-
- my %seen;
-
- for my $root (@roots) {
- push @$callbacks,
- # Skip backup files, files without a leading package name,
- # and files we've already seen
- grep { !/\/\.|~$/
- and $_ ne "/Callbacks/$Page/$_CallbackName"
- and not $seen{$_}++ }
- $m->interp->resolver->glob_path($path, $root);
- }
-
- $m->notes($CacheKey => $callbacks);
- $cache{$CacheKey} = $callbacks if !$RT::DevelMode;
-}
-
-my @rv;
-foreach my $comp (sort @$callbacks) {
- push @rv, $m->comp($comp, %ARGS);
-}
-return @rv;
-</%init>
-<%args>
-$_CallbackName => 'Default'
-$Page => undef
-</%args>
diff --git a/rt/html/Elements/Checkbox b/rt/html/Elements/Checkbox
deleted file mode 100644
index e1d7f3d..0000000
--- a/rt/html/Elements/Checkbox
+++ /dev/null
@@ -1,63 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<input type="checkbox" class="checkbox" name="<%$Name%>" value="1" <%$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/CollectionAsTable/Header b/rt/html/Elements/CollectionAsTable/Header
deleted file mode 100644
index cdcd2fd..0000000
--- a/rt/html/Elements/CollectionAsTable/Header
+++ /dev/null
@@ -1,125 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ARGS>
-@Format => undef
-$FormatString => undef
-$AllowSorting => undef
-$Order=>undef
-$BaseURL => undef
-$Query => undef
-$Rows => undef
-$Page => undef
-$maxitems => undef
-</%ARGS>
-<tr class="collection-as-table">
-<%perl>
-
-my %generic_query_args = ( Query => $Query, Rows => $Rows, Page => $Page, Format => $FormatString );
-
-my $item = 0;
-foreach my $col (@Format) {
- $item++;
- if ( $col->{title} && ($col->{title} eq 'NEWLINE') ) {
- while ( $item < $maxitems ) {
- $m->out(qq{<th class="collection-as-table">&nbsp;</th>\n});
- $item++;
- }
-
- $item = 0;
- $m->out(qq{</tr>\n<tr class="collection-as-table">});
- }
- else {
- $m->out('<th class="collection-as-table">');
- my $title = $col->{title} || '';
- $title =~ s/^__(.*)__$/$1/o;
- $title = (
- $m->comp(
- '/Elements/RT__Ticket/ColumnMap',
- Name => $title,
- Attr => 'title'
- )
- || $title
- );
- if (
- $AllowSorting
- && $col->{'attribute'}
- && $m->comp(
- '/Elements/RT__Ticket/ColumnMap',
- Name => $col->{'attribute'},
- Attr => 'attribute'
- )
- )
- {
-
- $m->out(
- '<a href="' . $BaseURL
- . $m->comp(
- '/Elements/QueryString',
- %generic_query_args,
- OrderBy => (
- $m->comp(
- '/Elements/RT__Ticket/ColumnMap',
- Name => $col->{'attribute'},
- Attr => 'attribute'
- )
- || $col->{'attribute'}
- ),
- Order => ( $ARGS{'Order'} eq 'ASC' ? 'DESC' : 'ASC' )
- )
- . '">'
- . loc($title) . '</a>'
- );
- }
- else {
- $m->out( loc($title) );
- }
- $m->out('</th>');
- }
-}
-</%perl>
-</tr>
diff --git a/rt/html/Elements/CollectionAsTable/ParseFormat b/rt/html/Elements/CollectionAsTable/ParseFormat
deleted file mode 100644
index a85da31..0000000
--- a/rt/html/Elements/CollectionAsTable/ParseFormat
+++ /dev/null
@@ -1,106 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ARGS>
-$Format
-</%ARGS>
-
-<%init>
-use Regexp::Common qw/delimited/;
-my @Columns;
-
-while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
- my $col = $1;
-
- if ($col =~ /^$RE{quoted}$/o) {
- substr($col,0,1) = "";
- substr($col,-1,1) = "";
- }
-
- my $colref = {
- title => '',
- };
-
- if ( $col =~ s!/STYLE:([^/]+)!!io ) {
- $colref->{'style'} = $1;
- }
- if ( $col =~ s!/CLASS:([^/]+)!!io ) {
- $colref->{'class'} = $1;
- }
- if ( $col =~ s!/TITLE:([^/]+)!!io ) {
- $colref->{'title'} = $1;
- }
- if ( $col =~ s!/ALIGN:([^\/]+)!!io ) {
- $colref->{'align'} = $1;
- }
- if ( $col =~ /__(.*?)__/gio ) {
- my @subcols;
- while ( $col =~ s/^(.*?)__(.*?)__//o ) {
- push ( @subcols, $1 ) if ($1);
- push ( @subcols, "__$2__" );
- $colref->{'attribute'} = $2;
- }
- push ( @subcols, $col );
- @{ $colref->{'output'} } = @subcols;
- }
- else {
- @{ $colref->{'output'} } = ( "__" . $col . "__" );
- $colref->{'attribute'} = $col;
- }
-
- if ( !$colref->{'title'} && grep { /^__(.+?)__$/io }
- @{ $colref->{'output'} } )
- {
- $colref->{'title'} = $1;
- $colref->{'attribute'} = $1;
- }
-
-
- push @Columns, $colref;
-}
- return(@Columns);
-</%init>
diff --git a/rt/html/Elements/CollectionAsTable/Row b/rt/html/Elements/CollectionAsTable/Row
deleted file mode 100644
index bb90321..0000000
--- a/rt/html/Elements/CollectionAsTable/Row
+++ /dev/null
@@ -1,117 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ARGS>
-$i => undef
-@Format => undef
-$record => undef
-$maxitems => undef
-$Depth => undef
-$Warning => undef
-</%ARGS>
-
-<%PERL>
-use HTML::Entities;
-$m->out('<tr class="' . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) . '" >' );
-my $item;
-foreach my $column (@Format) {
- if ( defined $column->{title} && $column->{title} eq 'NEWLINE' ) {
- while ( $item < $maxitems ) {
- $m->out(qq{<td class="collection-as-table">&nbsp;</td>\n});
- $item++;
- }
- $item = 0;
- $m->out('</tr>');
- $m->out('<tr class="'
- . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' )
- . '" >' );
- next;
- }
- $item++;
- my $class = $column->{class}
- ? encode_entities($column->{class}, q{'"&<>}) : 'collection-as-table';
- $m->out(qq{<td class="$class" });
- $m->out( 'align="' . $column->{align} . '"' ) if ( $column->{align} );
- $m->out( 'style="' . $column->{style} . '"' ) if ( $column->{style} );
- $m->out('>');
- foreach my $subcol ( @{ $column->{output} } ) {
- if ( $subcol =~ /^__(.*?)__$/o ) {
- my $col = $1;
- my $value = $m->comp(
- '/Elements/RT__Ticket/ColumnMap',
- Name => $col,
- Attr => 'value'
- );
- my @out;
-
- if ( $value && ref($value) ) {
-
- # All HTML snippets are returned by the callback function
- # as scalar references. Data fetched from the objects are
- # plain scalars, and needs to be escaped properly.
- @out =
- map {
- ref($_) ? $$_ : $m->interp->apply_escapes( $_ => 'h' )
- } &{$value}( $record, $i )
- ;
- }
- else {
-
- # Simple value; just escape it.
- @out = $m->interp->apply_escapes( $value => 'h' );
- }
- s/\n/<br \/>/gs for @out;
- $m->out( @out );
- }
- else {
- $m->out($subcol);
- }
- }
- $m->out('</td>');
-}
-$m->out('</tr>');
-</%PERL>
diff --git a/rt/html/Elements/CreateTicket b/rt/html/Elements/CreateTicket
deleted file mode 100644
index 6fb4972..0000000
--- a/rt/html/Elements/CreateTicket
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form action="<% $RT::WebPath %>/Ticket/Create.html" name="CreateTicketInQueue">
-<&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" class="button" value="New ticket in" />&nbsp;[_1]</&>
-</form>
diff --git a/rt/html/Elements/EditCustomField b/rt/html/Elements/EditCustomField
deleted file mode 100644
index 85641ef..0000000
--- a/rt/html/Elements/EditCustomField
+++ /dev/null
@@ -1,99 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-my $Values;
-if ($Object and $Object->id) {
- $Values = $Object->CustomFieldValues($CustomField->id);
- $Values->Columns( qw( id CustomField ObjectType ObjectId Disabled Content ContentType ContentEncoding ) );
- $NamePrefix ||= join('-', 'Object', ref($Object), $Object->Id, 'CustomField', '');
-} elsif (not $Default) {
- my %TOP = $m->request_args;
- $Default = $TOP{ $NamePrefix .$CustomField->Id . '-Values' }
- || $TOP{ $NamePrefix .$CustomField->Id . '-Value' };
-}
-my $Type = $CustomField->Type;
-
-return unless ($Type); # if we can't see the type, all hell will break loose.
-
-my $MaxValues = $CustomField->MaxValues;
-if ($MaxValues == 1 and $Object and $Values) {
- # what exactly is this doing? Without the "unless" it breaks RTFM
- # transaction extraction into articles.
- $Default = ($Values->First ? $Values->First->Content : '') unless $Default;
- $Values->GotoFirstItem;
-}
-# The "Magic" hidden input causes RT to know that we were trying to edit the field, even if
-# we don't see a value later, since browsers aren't compelled to submit empty form fields
-$m->out("\n".'<input type="hidden" class="hidden" name="'.$NamePrefix.$CustomField->Id.'-Values-Magic" value="1" />'."\n");
-
-my $EditComponent = "EditCustomField$Type";
-$m->comp('/Elements/Callback', _CallbackName => 'EditComponentName', Name => \$EditComponent, CustomField => $CustomField, Object => $Object );
-$EditComponent = "EditCustomField$Type" unless $m->comp_exists($EditComponent);
-
-return $m->comp(
- $EditComponent,
- %ARGS,
- Rows => $Rows,
- Cols => $Cols,
- Default => $Default,
- Object => $Object,
- Values => $Values,
- MaxValues => $MaxValues,
- Multiple => ($MaxValues != 1),
- NamePrefix => $NamePrefix,
- CustomField => $CustomField,
-);
-</%INIT>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Rows => 5
-$Cols => 15
-$Default => undef
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldBinary b/rt/html/Elements/EditCustomFieldBinary
deleted file mode 100644
index 81368d7..0000000
--- a/rt/html/Elements/EditCustomFieldBinary
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ($Values and my $value = $Values->Next ) {
-%# XXX - let user download the file(s) here?
-<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" value="<% $value->Id %>" /><a href="<%$RT::WebPath%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content %>"><% $value->Content %></a><br />
-% }
-% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<input type="file" name="<%$NamePrefix%><%$CustomField->Id%>-Upload" />
-% }
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$MaxValues => undef
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldCombobox b/rt/html/Elements/EditCustomFieldCombobox
deleted file mode 100644
index 37a388c..0000000
--- a/rt/html/Elements/EditCustomFieldCombobox
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ($Values and my $value = $Values->Next and $Multiple) {
-<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" value="<% $value->Id %>" /><% $value->Content %>
-<br />
-% }
-% (!$Multiple or !$MaxValues or !$Values or $Values->Count < $MaxValues) or return;
-<& /Widgets/ComboBox,
- Name => $NamePrefix . $CustomField->Id . "-Value",
- Default => $Default,
- Rows => $Rows,
- Values => [map {$_->Name} @{$CustomField->Values->ItemsArrayRef}],
-&>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$Multiple => 0
-$Rows => undef
-$MaxValues => undef
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldFreeform b/rt/html/Elements/EditCustomFieldFreeform
deleted file mode 100644
index 57073b0..0000000
--- a/rt/html/Elements/EditCustomFieldFreeform
+++ /dev/null
@@ -1,74 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% my $name = $NamePrefix . $CustomField->Id . '-Value';
-% if ($Multiple) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$name%>s" id="<%$name%>s" ><%$Default%></textarea>
-% } else {
-<input name="<%$name%>" id="<%$name%>" size="<%$Cols%>" value="<%$Default ? $Default : ''%>" />
-% }
-<%INIT>
-if ($Multiple and $Values) {
- $Default = '';
- while (my $value = $Values->Next ) {
- $Default .= $value->Content."\n";
- }
-}
-elsif ( ! $Multiple ) {
- $Default =~ s/\s*\n\s*/ /g if $Default;
-}
-</%INIT>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$Multiple => undef
-$Cols
-$Rows
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldImage b/rt/html/Elements/EditCustomFieldImage
deleted file mode 100644
index c9fd8dd..0000000
--- a/rt/html/Elements/EditCustomFieldImage
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ($Values and my $value = $Values->Next ) {
-<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" value="<% $value->Id %>" /><& ShowCustomFieldImage, Object => $value &>
-<br />
-% }
-% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<input type="file" name="<%$NamePrefix%><%$CustomField->Id%>-Upload" />
-% }
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$MaxValues => undef
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldSelect b/rt/html/Elements/EditCustomFieldSelect
deleted file mode 100644
index 6df5576..0000000
--- a/rt/html/Elements/EditCustomFieldSelect
+++ /dev/null
@@ -1,128 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# Build up the set of cascading select boxes as "guides"
-%# each one limits the options of the final one a bit
-%# (perhaps by tweaking the .display style?)
-% my $selected = 0;
-% my @category;
-% my $id = $NamePrefix . $CustomField->Id;
-% my $out = $m->scomp('SELF:options', %ARGS, SelectedRef => \$selected, CategoryRef => \@category);
-% if (@category) {
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/cascaded.js"></script>
-%# XXX - Hide this select from w3m?
- <select onchange="filter_cascade('<% $id %>-Values', this.value)" name="<%$id%>-Category">
- <option value="" <% !$selected && 'SELECTED' %>><&|/l&>-</&></option>
-% foreach my $cat (@category) {
-% my ($depth, $name) = @$cat;
- <option value="<% $name %>"><% '&nbsp;' x $depth |n %><%$name%></option>
-% }
- </select><br />
-% }
- <select name="<%$id%>-Values" id="<%$id%>-Values"
-% if ($Multiple or !@category) {
- size="<%$Rows%>"
-% }
- <% $Multiple && 'MULTIPLE' %>>
- <option value="" <% !$selected && 'SELECTED' %>><&|/l&>(no value)</&></option>
-% $m->out($out);
- </select>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$Multiple => 0
-$Rows => undef
-</%ARGS>
-
-<%method options>
-% my $selected;
-% my $CFVs = $CustomField->Values;
-% my @levels;
-% while ($CFVs and my $value = $CFVs->Next ) {
-% my $category = $value->Category;
-% if (1) { # length $category) {
-% my $level = (split(/:/, $category || ''))[0];
-% while (@levels) {
-% if ($levels[-1] eq $level) {
-% undef $level;
-% last;
-% } elsif (index($level, $levels[-1]) != 0) {
- </optgroup>
-% pop @levels;
-% } else {
-% last;
-% }
-% }
-% if ($level) {
-% push @$CategoryRef, [0+@levels, $level];
- <optgroup style="padding-left: <% @levels/2 %>em" label="<%$category%>">
-% push @levels, $level;
-% }
-% }
- <option value="<%$value->Name%>"
-% if ($Values) {
- <% ($Values->HasEntry($value->Name)||'') && ($$SelectedRef = 1) && 'SELECTED' %>
-% } elsif ($Default) {
- <% (ref $Default ? (grep {$_ eq $value->Name} @{$Default}) : ($Default eq $value->Name))
- && ($$SelectedRef = 1) && 'SELECTED' %>
-% }
- ><% $value->Name%></option>
-% }
-% for (@levels) {
- </optgroup>
-% }
-<%args>
-$CustomField => undef
-$Default => undef
-$Values => undef
-$SelectedRef => undef
-$CategoryRef => undef
-</%args>
-</%method>
diff --git a/rt/html/Elements/EditCustomFieldText b/rt/html/Elements/EditCustomFieldText
deleted file mode 100644
index b4892ec..0000000
--- a/rt/html/Elements/EditCustomFieldText
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values"><% $value->Content %></textarea><br />
-% }
-% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values"><% $Default %></textarea>
-% }
-<%INIT>
-# XXX - MultiValue textarea is for now outlawed.
-$MaxValues = 1;
-</%INIT>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$MaxValues => undef
-$Cols
-$Rows
-</%ARGS>
diff --git a/rt/html/Elements/EditCustomFieldWikitext b/rt/html/Elements/EditCustomFieldWikitext
deleted file mode 100644
index b4892ec..0000000
--- a/rt/html/Elements/EditCustomFieldWikitext
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ($Values and my $value = $Values->Next ) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values"><% $value->Content %></textarea><br />
-% }
-% if (!$MaxValues or !$Values or $Values->Count < $MaxValues) {
-<textarea cols="<%$Cols%>" rows="<%$Rows%>" name="<%$NamePrefix%><%$CustomField->Id%>-Values"><% $Default %></textarea>
-% }
-<%INIT>
-# XXX - MultiValue textarea is for now outlawed.
-$MaxValues = 1;
-</%INIT>
-<%ARGS>
-$Object => undef
-$CustomField => undef
-$NamePrefix => undef
-$Default => undef
-$Values => undef
-$MaxValues => undef
-$Cols
-$Rows
-</%ARGS>
diff --git a/rt/html/Elements/EditLinks b/rt/html/Elements/EditLinks
deleted file mode 100755
index 8fd1d62..0000000
--- a/rt/html/Elements/EditLinks
+++ /dev/null
@@ -1,177 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table width="100%">
- <tr>
- <td valign="top" width="50%">
- <h3><&|/l&>Current Links</&></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 = $Object->DependsOn->Next) {
- <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
- <& ShowLink, URI => $link->TargetURI &><br />
-% }
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Depended on by</&>:</td>
- <td class="value">
-% while (my $link = $Object->DependedOnBy->Next) {
- <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
- <& ShowLink, URI => $link->BaseURI &><br />
-% }
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Parents</&>:</td>
- <td class="value">
-% while (my $link = $Object->MemberOf->Next) {
- <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
- <& ShowLink, URI => $link->TargetURI &><br />
-% }
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Children</&>:</td>
- <td class="value">
-% while (my $link = $Object->Members->Next) {
- <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
- <& ShowLink, URI => $link->BaseURI &><br />
-% }
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Refers to</&>:</td>
- <td class="value">
-% while (my $link = $Object->RefersTo->Next) {
- <input type="checkbox" class="checkbox" name="DeleteLink--<%$link->Type%>-<%$link->Target%>" value="1" />
- <& ShowLink, URI => $link->TargetURI &><br />
-%}
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Referred to by</&>:</td>
- <td class="value">
-% while (my $link = $Object->ReferredToBy->Next) {
-% # Skip reminders
-% next if (UNIVERSAL::isa($link->BaseObj, 'RT::Ticket') && $link->BaseObj->Type eq 'reminder');
- <input type="checkbox" class="checkbox" name="DeleteLink-<%$link->Base%>-<%$link->Type%>-" value="1" />
- <& ShowLink, URI => $link->BaseURI &><br />
-% }
- </td>
- </tr>
-</table>
-
-</td>
-<td valign="top">
-<h3><&|/l&>New Links</&></h3>
-% if (ref($Object) eq 'RT::Ticket') {
-<i><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&>
-<& /Elements/Callback, _CallbackName => 'ExtraLinkInstructions' &>
-</i><br />
-% } elsif (ref($Object) eq 'RT::Queue') {
-<i><&|/l&>Enter queues or URIs to link queues to. Separate multiple entries with spaces.</&>
-</i><br />
-% } else {
-<i><&|/l&>Enter objects or URIs to link objects to. Separate multiple entries with spaces.</&></i><br />
-% }
-<table>
-% if ($Merge) {
- <tr>
- <td class="label"><&|/l&>Merge into</&>:</td>
- <td class="entry"><input name="<%$id%>-MergeInto" /> <i><&|/l&>(only one ticket)</&></i></td>
- </tr>
-% }
- <tr>
- <td class="label"><&|/l&>Depends on</&>:</td>
- <td class="entry"><input name="<%$id%>-DependsOn" /></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Depended on by</&>:</td>
- <td class="entry"><input name="DependsOn-<%$id%>" /></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Parents</&>:</td>
- <td class="entry"><input name="<%$id%>-MemberOf" /></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Children</&>:</td>
- <td class="entry"> <input name="MemberOf-<%$id%>" /></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Refers to</&>:</td>
- <td class="entry"><input name="<%$id%>-RefersTo" /></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Referred to by</&>:</td>
- <td class="entry"> <input name="RefersTo-<%$id%>" /></td>
- </tr>
-</table>
-</td>
-</tr>
-</table>
-
-<%INIT>
-my $id;
-if ($Object && $Object->Id) {
- $id = $Object->Id;
-} else {
- $id = 'new';
-}
-</%INIT>
-
-<%ARGS>
-$Object => undef
-$Merge => 0
-</%ARGS>
diff --git a/rt/html/Elements/EmailInput b/rt/html/Elements/EmailInput
deleted file mode 100644
index c1b2755..0000000
--- a/rt/html/Elements/EmailInput
+++ /dev/null
@@ -1,47 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
diff --git a/rt/html/Elements/Error b/rt/html/Elements/Error
deleted file mode 100644
index 666017f..0000000
--- a/rt/html/Elements/Error
+++ /dev/null
@@ -1,86 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Callback, %ARGS, error => $error &>
-<& /Elements/Header, Code => $Code, Why => $Why, Title => $Title &>
-<& /Elements/Tabs, Title => $Title &>
-<div class="error">
-<%$Why%>
-<br />
-<%$Details%>
-</div>
-
-<%cleanup>
-$m->comp('/Elements/Footer');
-$m->abort();
-</%cleanup>
-
-<%args>
-$Code => undef
-$Details =>''
-$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 ( defined ($session{'SessionType'}) && $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
deleted file mode 100644
index c3d7662..0000000
--- a/rt/html/Elements/Footer
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# End of div#body from /Elements/PageLayout
-</div>
-<& /Elements/Callback, %ARGS &>
-<div id="footer">
- <p id="time">
- <span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span>
- </p>
-
- <p id="bpscredits">
- <span>
-<&|/l, '&#187;&#124;&#171;', $RT::VERSION, '2009', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</span>
-</p>
-% if (!$Menu) {
- <p id="legal">
-<&|/l&>Distributed under version 2 <a href="http://www.gnu.org/copyleft/gpl.html"> of the GNU GPL.</a></&><br />
-<&|/l, '<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br />
- </p>
-% }
-
-</div>
-% if ($Debug >= 2 ) {
-% require Data::Dumper;
-% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
-<pre>
-<%$d->Dump() %>
-</pre>
-% }
-
- </body>
-</html>
-% $m->abort();
-
-<%ARGS>
-$Debug => 0
-$Menu => 1
-</%ARGS>
diff --git a/rt/html/Elements/GotoTicket b/rt/html/Elements/GotoTicket
deleted file mode 100644
index 55dacda..0000000
--- a/rt/html/Elements/GotoTicket
+++ /dev/null
@@ -1,48 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form action="<%$RT::WebPath%>/Ticket/Display.html"><input type="submit" class="button" 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
deleted file mode 100644
index 02450b1..0000000
--- a/rt/html/Elements/Header
+++ /dev/null
@@ -1,133 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
-
-<title><%$Title%></title>
-
-% if ($Refresh && $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/css/<% $RT::WebDefaultStylesheet %>/main-squished.css" type="text/css" media="all" />
-<link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/css/print.css" type="text/css" media="print" />
-
-% if ( $RSSAutoDiscovery ) {
- <link rel="alternate" href="<%$RSSAutoDiscovery%>" type="application/rss+xml" title="RSS RT Search" />
-% }
-
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/util.js"></script>
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/ahah.js"></script>
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/titlebox-state.js"></script>
-<script type="text/javascript"><!--
- onLoadHook("loadTitleBoxStates()");
-% if ( $Focus ) {
- onLoadHook("focusElementById('<% $Focus %>')");
-% }
-% if ( $onload ) {
- onLoadHook("<% $onload |n %>");
-% }
---></script>
-
-<& /Elements/Callback, _CallbackName => 'Head', %ARGS &>
-
-</head>
- <body<% $id && qq[ id="comp-$id"] |n %>>
-
-% if ($ShowBar) {
-<& /Elements/Logo &>
-
-<div id="quickbar">
- <div id="quick-personal">
- <span class="hide"><a href="#skipnav"><&|/l&>Skip Menu</&></a> | </span>
-% if ($session{'CurrentUser'}->Name) {
- <&|/l, "<span>".$session{'CurrentUser'}->Name."</span>" &>Logged in as [_1]</&>
-% if ($session{'CurrentUser'}->HasRight( Right => 'ModifySelf', Object => $RT::System )) {
- | <a href="<%$RT::WebPath%><%$Prefs%>"><&|/l&>Preferences</&></a>
-% }
-% } else {
- <&|/l&>Not logged in.</&>
-% }
- <& /Elements/Callback, %ARGS &>
-% unless (!$session{'CurrentUser'}->Name
-% or ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth)) {
- | <a href="<%$RT::WebPath%>/NoAuth/Logout.html<%$URL ? "?URL=".$URL : ''%>"><&|/l&>Logout</&></a>
-% }
- </div>
-% }
-
-<%INIT>
-$r->headers_out->{'Pragma'} = 'no-cache';
-$r->headers_out->{'Cache-control'} = 'no-cache';
-
-my $id = $m->request_comp->path;
-$id =~ s|^/||g;
-$id =~ s|/|-|g;
-$id =~ s|\.html$||g;
-$id =~ s|index$||g
- if $id ne 'index';
-$id =~ s|-$||g;
-</%INIT>
-
-<%ARGS>
-$Prefs => '/User/Prefs.html'
-#$Focus => 'focus'
-$Focus => ''
-$Title => 'RT'
-$Code => undef
-$Refresh => 0
-$Why => undef
-$ShowBar => 1
-$URL => undef
-$RSSAutoDiscovery => undef
-$onload => undef
-</%ARGS>
diff --git a/rt/html/Elements/ListActions b/rt/html/Elements/ListActions
deleted file mode 100644
index 0d9788b..0000000
--- a/rt/html/Elements/ListActions
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc('Results') &>
- <ul class="action-results">
-% 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>
-</&>
-<%init>
-@actions = grep $_, @actions;
-return unless @actions;
-</%init>
-<%ARGS>
-@actions => undef
-</%ARGS>
diff --git a/rt/html/Elements/Login b/rt/html/Elements/Login
deleted file mode 100644
index 8cad96f..0000000
--- a/rt/html/Elements/Login
+++ /dev/null
@@ -1,138 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%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;
-}
-
-my $req_uri;
-
-if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/(.*)}) {
- $req_uri = $1;
-}
-
-my $form_action = defined $goto ? $goto
- : defined $req_uri ? $req_uri
- : $RT::WebPath
- ;
-</%INIT>
-
-<& /Elements/Callback, %ARGS, _CallbackName => 'Header' &>
-<& /Elements/Header, Title => loc('Login'), Focus => 'user' &>
-
-%# End of div#quickbar from /Elements/Header
-</div>
-
-<div id="body" class="login-body">
-
-% if ($Error) {
-<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0 &>
-<% $Error %>
-</&>
-% }
-
-<& /Elements/Callback, %ARGS, _CallbackName => 'BeforeForm' &>
-
-<div id="login-box">
-<&| /Widgets/TitleBox, title => loc('Login'), titleright => $RT::VERSION, hideable => 0 &>
-
-% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
-<form id="login" name="login" method="post" action="<% $form_action %>">
-
-<div class="input-row">
- <span class="label"><&|/l&>Username</&>:</span>
- <span class="input"><input name="user" value="<%$user%>" id="user" /></span>
-</div>
-
-<div class="input-row">
- <span class="label"><&|/l&>Password</&>:</span>
- <span class="input"><input type="password" name="pass" /></span>
-</div>
-
-<div class="button-row">
- <span class="input"><input type="submit" class="button" value="<&|/l&>Login</&>" /></span>
-</div>
-
-%# Give callbacks a chance to add more control elements
-<& /Elements/Callback, %ARGS &>
-
-% # 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" class="hidden" name="<%$key %>" value="<% $val %>" />
-% }
-% }
-% else {
-<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" />
-% }
-% }
-% }
-</form>
-% }
-</&>
-</div><!-- #login-box -->
-<& /Elements/Callback, %ARGS, _CallbackName => 'AfterForm' &>
-<& /Elements/Footer, Menu => 0 &>
-<%ARGS>
-$user => ""
-$pass => undef
-$goto => undef
-$Error => undef
-</%ARGS>
diff --git a/rt/html/Elements/Logo b/rt/html/Elements/Logo
deleted file mode 100644
index 9db7665..0000000
--- a/rt/html/Elements/Logo
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- <div id="logo">
- <a href="http://bestpractical.com"><img src="<%$RT::WebImagesURL%>/bplogo.gif" alt="<%loc("Best Practical Solutions, LLC corporate logo")%>" width="177" height="33" /></a>
-% if ($show_name) {
- <div class="rtname"><% loc("RT for [_1]", $RT::rtname) %></div>
-% }
- </div>
-<%args>
- $show_name => 1
-</%args>
diff --git a/rt/html/Elements/Menu b/rt/html/Elements/Menu
deleted file mode 100644
index 48fceeb..0000000
--- a/rt/html/Elements/Menu
+++ /dev/null
@@ -1,134 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<ul<% !$level ? ' id="system-menu"' : ''|n %><% $menu_class ? qq[ class="$menu_class"] : ''|n %>>
-<div<% $menu_class ? qq[ class="$menu_class"] : ''|n %>><div class="wrapper">
-% my $sep = 0;
-% my $postsep = 0;
-% my $accesskey = 1;
-%
-% $count = 0;
-% $class = {};
-%
-% foreach $tab (sort keys %{$toptabs}) {
-% $count++;
-%
-% my $current = $current_toptab || "";
-% my $path = $toptabs->{$tab}->{'path'} || "";
-%
-% $path =~ s#/index.html$##gi;
-% $current =~ s#/index.html$##gi;
-%
-% $sep = $toptabs->{$tab}->{'separator'} ? 1 : 0;
-%
-% my @aclass;
-% push @aclass, 'selected'
-% if $path eq $current;
-%
-% push @aclass, 'odd'
-% if $level % 2;
-%
-% $class->{a} = join ' ', @aclass;
-%
-% my @li;
-% push @li, 'first'
-% if $count == 1;
-%
-% push @li, 'pre-separator'
-% if $sep;
-%
-% push @li, 'post-separator'
-% if $postsep;
-%
-% $class->{li} = join ' ', @li;
-%
-% my $url = ($toptabs->{$tab}->{'path'}||'') =~ /^https?:/i
-% ? $toptabs->{$tab}->{'path'} || ''
-% : $RT::WebPath . "/" . $toptabs->{$tab}->{'path'};
-%
- <li<% $class->{li} ? qq[ class="$class->{li}"] : ''|n %>>
- <% $count > 1 && !$postsep && qq[<span class="bullet">&#183; </span>]|n%>
- <a href="<% $url %>"
- <% $class->{a} && qq[ class="$class->{a}"] |n%>
- <% !$level && " accesskey='".$accesskey++."'" |n%>>
- <% $toptabs->{$tab}->{'title'} || ''%></a>
-%# Second-level items
-% if ($toptabs->{$tab}->{'subtabs'}
-% and keys %{$toptabs->{$tab}->{'subtabs'}})
-% {
- <& /Elements/Menu, level => $level+1,
- current_toptab => $toptabs->{$tab}->{'current_subtab'},
- toptabs => $toptabs->{$tab}->{'subtabs'},
- last_level => $toptabs->{$tab}->{last_system_menu_level} &>
-% }
- </li>
-% if ($sep) {
- <li class="separator">&#183;&#183;&#183;</li>
-% }
-%
-% $postsep = $sep;
-% }
-</div></div>
-</ul>
-
-<%INIT>
-my ($tab, $class, $count);
-
-my @ul;
-push @ul, 'last-menu-level'
- if $last_level;
-push @ul, 'odd'
- if $level % 2;
-my $menu_class = join ' ', @ul;
-</%INIT>
-
-<%ARGS>
-$current_toptab => ""
-$toptabs => undef
-$level => 0
-$last_level => 0
-</%ARGS>
diff --git a/rt/html/Elements/MessageBox b/rt/html/Elements/MessageBox
deleted file mode 100644
index ac90349..0000000
--- a/rt/html/Elements/MessageBox
+++ /dev/null
@@ -1,74 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<textarea class="messagebox" cols="<%$Width%>" rows="<%$Height%>" 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 ($IncludeSignature && $session{'CurrentUser'}->UserObj->Signature) {
- $signature = "-- \n".$session{'CurrentUser'}->UserObj->Signature;
-}
-
-</%INIT>
-<%ARGS>
-$QuoteTransaction => undef
-$Name => 'Content'
-$Default => ''
-$Width => $RT::MessageBoxWidth || 72
-$Height => $RT::MessageBoxHeight || 15
-$Wrap => $RT::MessageBoxWrap || 'HARD'
-$IncludeSignature => 1
-</%ARGS>
-
diff --git a/rt/html/Elements/MyAdminQueues b/rt/html/Elements/MyAdminQueues
deleted file mode 100644
index ddfc22c..0000000
--- a/rt/html/Elements/MyAdminQueues
+++ /dev/null
@@ -1,54 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&|/Widgets/TitleBox, title => loc("Queues I administer"), bodyclass => "" &>
-<& /Elements/QueueSummary,
- cache => 'my_admin_queues',
- queue_filter => sub { $_->CurrentUserHasRight('AdminQueue') },
- conditions => [ {cond => "Status = 'new'", name => loc ('new') },
- {cond => "Status = 'open'", name => loc ('open') }] &>
-</&>
diff --git a/rt/html/Elements/MyRT b/rt/html/Elements/MyRT
deleted file mode 100644
index f98a7c2..0000000
--- a/rt/html/Elements/MyRT
+++ /dev/null
@@ -1,100 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table border="0" width="100%">
-<tr valign="top">
-
-<td class="boxcontainer" <% $summary? 'width="70%"': '' |n %>>
-% $show_cb->($_) foreach @$body;
-</td>
-
-% if ( $summary ) {
-<td class="boxcontainer">
-% $show_cb->($_) foreach @$summary;
-</td>
-% }
-
-</tr>
-</table>
-
-<%INIT>
-
-my $user = $session{'CurrentUser'}->UserObj;
-unless (exists $session{'my_rt_portlets'}) {
- my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- $session{'my_rt_portlets'} = $user->Preferences(
- HomepageSettings => $default_portlets? $default_portlets->Content: {},
- );
-}
-
-my ($body, $summary) = @{$session{'my_rt_portlets'}}{qw(body summary)};
-unless( $body && @$body ) {
- $body = $summary || [];
- $summary = undef;
-}
-$summary = undef unless $summary && @$summary;
-
-my $Rows = $user->Preferences( 'SummaryRows', ( $RT::DefaultSummaryRows || 10 ) );
-
-my $show_cb = sub {
- my $entry = shift;
- my $type = $entry->{type};
- if ( $type eq 'component' ) {
- my $name = $entry->{name};
-
- # security check etc.
- $m->comp( $name, %{ $entry->{arguments} || {} } );
- } elsif ( $type eq 'system' ) {
- $m->comp( '/Elements/ShowSearch', Name => $entry->{name}, Override => { Rows => $Rows } );
- } elsif ( $type eq 'saved' ) {
- $m->comp( '/Elements/ShowSearch', SavedSearch => $entry->{name}, Override => { Rows => $Rows } );
- } else {
- $RT::Logger->error("unknown portlet type $type");
- }
-};
-
-</%INIT>
diff --git a/rt/html/Elements/MyReminders b/rt/html/Elements/MyReminders
deleted file mode 100755
index 6863225..0000000
--- a/rt/html/Elements/MyReminders
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# DEPRECATED
-<&|/Widgets/TitleBox,
- title => loc("Reminders") &>
-<table width="100%">
-% my $i =0;
-% while (my $reminder = $reminders->Next) {
-% $i++;
-% if ($reminder->RefersTo->First) {
-% my $ticket= $reminder->RefersTo->First->TargetObj;
-<tr class="<%$i%2 ? 'evenline' : 'oddline'%>"><td><a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$ticket->id%>"><%$reminder->Subject%></a><br />
-<blockquote>
-#<%$ticket->id%>: <%$ticket->Subject%><br />
-<%$reminder->OwnerObj->Name %> <%$reminder->DueObj->Unix >0 ? '&bull; '.$reminder->DueObj->AgeAsString : '' |n %>
-</blockquote>
-</td>
-</tr>
-% }}
-</table>
-</&>
-
-<%init>
-my $reminders = RT::Tickets->new($session{'CurrentUser'});
-$reminders->FromSQL('(Owner = "Nobody" OR Owner = "'.$session{'CurrentUser'}->Name.'")' .
- ' AND Type = "reminder" AND (Status = "new" OR Status = "open") AND Due > "1970-01-01"');
-$reminders->OrderBy(FIELD => 'Due', ORDER => 'DESC');
-</%init>
diff --git a/rt/html/Elements/MyRequests b/rt/html/Elements/MyRequests
deleted file mode 100644
index 9a6d0a3..0000000
--- a/rt/html/Elements/MyRequests
+++ /dev/null
@@ -1,49 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# DEPRECATED
-<& /Elements/ShowSearch, Name => 'My Requests' &>
diff --git a/rt/html/Elements/MySupportQueues b/rt/html/Elements/MySupportQueues
deleted file mode 100644
index 6dec1e1..0000000
--- a/rt/html/Elements/MySupportQueues
+++ /dev/null
@@ -1,54 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&|/Widgets/TitleBox, title => loc("Queues I'm an AdminCc for"), bodyclass => "" &>
-<& /Elements/QueueSummary,
- cache => 'my_support_queues',
- queue_filter => sub { $_->IsAdminCc($session{'CurrentUser'}->Id) },
- conditions => [ {cond => "Status = 'new'", name => loc ('new') },
- {cond => "Status = 'open'", name => loc ('open') }] &>
-</&>
diff --git a/rt/html/Elements/MyTickets b/rt/html/Elements/MyTickets
deleted file mode 100644
index 771abbd..0000000
--- a/rt/html/Elements/MyTickets
+++ /dev/null
@@ -1,49 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# DEPRECATED
-<& /Elements/ShowSearch, Name => 'My Tickets' &>
diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout
deleted file mode 100644
index 6897ede..0000000
--- a/rt/html/Elements/PageLayout
+++ /dev/null
@@ -1,237 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- <div id="topactions">
-% foreach my $action (reverse sort keys %{$topactions}) {
- <span class="topaction">
-% $m->out($topactions->{"$action"}->{'html'});
- </span>
-% }
- </div>
-
-%# End of div#quickbar from /Elements/Header
-</div>
-
-% if ( $show_menu ) {
-<div id="nav">
-<& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &>
-</div>
-% }
-
-<div id="header">
- <h1><%$title%></h1>
-
-% my $sep = 0;
-% my $postsep = 0;
-% my $count = 0;
-% my $class = { };
-%
- <ul id="page-menu"<% (($actions && %$actions) || ($subactions && %$subactions)) && q[ class="actions-present"] | n %>>
- <div><div><div>
-% if ($page_tabs) {
-% foreach my $tab (sort keys %{$page_tabs}) {
-% next if $tab =~ /^(?:current_toptab|this)$/;
-% $count++;
-%
-% my $current = $page_tabs->{current_toptab} || "";
-% my $path = $page_tabs->{$tab}->{'path'} || "";
-%
-% $path =~ s#/index.html$##gi;
-% $current =~ s#/index.html$##gi;
-%
-% $sep = $toptabs->{$tab}->{'separator'} ? 1 : 0;
-%
-% $class->{a} = $path eq $current ? ' class="selected"' : undef;
-%
-% my @li;
-% push @li, 'first'
-% if $count == 1;
-%
-% push @li, 'pre-separator'
-% if $sep;
-%
-% push @li, 'post-separator'
-% if $postsep;
-%
-% $class->{li} = join ' ', @li;
-%
-%
- <li<% $class->{li} ? qq[ class="$class->{li}"] : ''|n %>><% $count > 1 && !$postsep && "&#183; "|n%><a href="<%$RT::WebPath%>/<%$page_tabs->{$tab}->{'path'}%>"<%$class->{a}|n%><% $class->{a} ? ' name="focus"' : ''|n %>><% $page_tabs->{$tab}->{'title'} %></a></li>
-%
-% if ($sep) {
- <li class="separator">&#183;&#183;&#183;</li>
-% }
-% $postsep = $sep;
-% }
-% } else {
-&nbsp;
-% }
- </div></div></div>
- </ul>
-
-% if (($actions && %$actions) || ($subactions && %$subactions)) {
- <ul id="actions-menu">
- <div><div><div>
-% $sep = 0;
-% $postsep = 0;
-% $count = 0;
-% $class = { };
-%
-% for my $type ($actions, $subactions) {
-%
-% if ($type && %$type) {
-% foreach my $action (sort keys %{$type}) {
-% $count++;
-%
-% $sep = $type->{$action}->{'separator'} ? 1 : 0;
-%
-% my @li;
-% push @li, 'first'
-% if $count == 1;
-%
-% push @li, 'pre-separator'
-% if $sep;
-%
-% push @li, 'post-separator'
-% if $postsep;
-%
-% $class->{li} = join ' ', @li;
-%
- <li<% $class->{li} ? qq[ class="$class->{li}"] : ''|n %>><% $count > 1 && !$postsep && qq[<span class="bullet">&#183; </span>]|n%>
-% if ($type->{"$action"}->{'html'}) {
- <% $type->{"$action"}->{'html'} | n %>
-% } else {
- <a href="<%$RT::WebPath%>/<%$type->{$action}->{'path'}%>"<% $type->{$action}->{class} && ' class="'.$type->{$action}->{class}.'"' |n %><% $type->{$action}->{id} && ' id="'.$type->{$action}->{id}.'"' |n %>><%$type->{$action}->{'title'}%></a>
-% }
- </li>
-% if ($sep) {
- <li class="separator">&#183;&#183;&#183;</li>
-% }
-% $postsep = $sep;
-% }
-% }
-% }
- </div></div></div>
- </ul>
-% }
-</div>
-
-<div id="body">
-<& /Elements/Callback, _CallbackName => 'BeforeBody', %ARGS &>
-%$m->flush_buffer(); # we've got the page laid out, let's flush the buffer;
-
-<%INIT>
- foreach my $tab (sort keys %{$toptabs}) {
- if ($current_toptab && $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);
-}
-
-my ($menu_depth, $almost_last, $page_tabs);
-
-if ($RT::WebDefaultStylesheet ne '3.4-compat') {
- ($menu_depth, $almost_last) = @{$m->comp('.menu_recurse', data => $toptabs)};
-
- if (defined $almost_last->{subtabs} and %{$almost_last->{subtabs}}) {
- $page_tabs = {
- current_toptab => $almost_last->{current_subtab},
- %{$almost_last->{subtabs}},
- };
-
- delete $almost_last->{subtabs};
- delete $almost_last->{current_subtab};
- }
-}
-</%INIT>
-
-%# There's probably a better way to do this that involves three times as
-%# much work and redoing the whole menu/tab system... which would seem a
-%# bit out of scope.
-%#
-%# This function recurses through the menu and returns the second to
-%# last menu, that is, the menu holding the last reference to
-%# and submenu. It also returns the number of menu levels minus
-%# the last submenu.
-<%def .menu_recurse>
- <%args>
- $data => { }
- $pdata => { }
- $ppdata => { }
- $level => 0
- </%args>
- <%init>
- for my $key (keys %$data) {
- return $m->comp('.menu_recurse', data => $data->{$key}->{subtabs},
- pdata => $data->{$key},
- ppdata => $pdata,
- level => $level+1)
- if ref($data->{$key}) eq 'HASH'
- and defined $data->{$key}->{subtabs}
- and %{$data->{$key}->{subtabs}};
- }
- $ppdata->{last_system_menu_level}++;
- return [$level, $pdata];
- </%init>
-</%def>
-
-<%ARGS>
-$current_toptab => undef
-$current_tab => undef
-$toptabs => undef
-$topactions => undef
-$tabs => undef
-$actions => undef
-$subactions => undef
-$title => $m->callers(-1)->path
-$AppName => undef
-$show_menu => 1
-</%ARGS>
diff --git a/rt/html/Elements/QueryString b/rt/html/Elements/QueryString
deleted file mode 100644
index bade07f..0000000
--- a/rt/html/Elements/QueryString
+++ /dev/null
@@ -1,63 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-my @params;
-while ( my ($key, $value) = each %ARGS ){
- if( UNIVERSAL::isa( $value, 'ARRAY' ) ) {
- push @params, map $key."=".$m->interp->apply_escapes($_,'u'), @$value;
- } else {
- if (ref $value eq "ARRAY") {
- push @params, $key."=".$m->interp->apply_escapes($_, 'u')
- for @{$value};
- } else {
- push @params, $key."=".$m->interp->apply_escapes($value||"",'u');
- }
- }
-}
-return(join('&',@params));
-</%init>
diff --git a/rt/html/Elements/QueueSummary b/rt/html/Elements/QueueSummary
deleted file mode 100644
index 8ad371a..0000000
--- a/rt/html/Elements/QueueSummary
+++ /dev/null
@@ -1,92 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table border="0" cellspacing="0" cellpadding="1" width="100%">
-<tr>
- <th class="collection-as-table"><&|/l&>Queue</&></th>
-% for my $condition (@$conditions) {
- <th class="collection-as-table"><% $condition->{name} %></th>
-% }
-</tr>
-% my $i;
-% for my $queue (@queues) {
-% $i++;
-% my $queue_cond = "Queue = '$queue->{Name}' AND ";
-% my $all_q = $queue_cond . "(Status = 'open' OR Status = 'new' OR Status = 'stalled')";
-<tr class="<% $i%2 ? 'oddline' : 'evenline'%>" >
-<td><a href="<% $RT::WebPath%>/Search/Results.html?Query=<% $all_q |u,n %>" title="<% $queue->{Description} %>"><% $queue->{Name} %></a></td>
-% for my $condition (@$conditions) {
-% $Tickets->FromSQL( "Queue = $queue->{id} AND ". $condition->{cond} );
-<td align="right"><a href="<% $RT::WebPath%>/Search/Results.html?Query=<% $queue_cond.$condition->{cond} |u,n %>"><% $Tickets->Count %></a></td>
-% }
-</tr>
-% }
-</table>
-<%INIT>
-my @queues;
-
-if ($cache && exists $session{$cache}) {
- @queues = @{$session{$cache}};
-}
-else {
- my $Queues = RT::Queues->new($session{'CurrentUser'});
- $Queues->UnLimit();
- @queues = map {
- { Name => $_->Name, Description => $_->Description,
- id => $_->Id } }
- grep $queue_filter->($_), @{$Queues->ItemsArrayRef};
-
- $session{$cache} = \@queues if $cache;
-}
-
-my $Tickets = RT::Tickets->new($session{'CurrentUser'});
-</%INIT>
-<%ARGS>
-$cache => undef
-$queue_filter => undef
-$conditions => ()
-</%ARGS>
diff --git a/rt/html/Elements/QuickCreate b/rt/html/Elements/QuickCreate
deleted file mode 100644
index 5669a45..0000000
--- a/rt/html/Elements/QuickCreate
+++ /dev/null
@@ -1,71 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="quick-create">
-<&| /Widgets/TitleBox, title => loc('Quick ticket creation') &>
-<form method="post" action="<%$RT::WebPath%>/index.html">
-<input type="hidden" class="hidden" name="QuickCreate" value="1" />
-<table>
-<tr><td>
-<&|/l&>Subject</&>:<br /><input size="15" name="Subject" />
-</td><td>
-<&|/l&>Queue</&>:<br /><& /Elements/SelectNewTicketQueue, Name => 'Queue', ShowNullOption => 0 &>
-</td><td>
-<&|/l&>Owner</&>:<br />
-<select type="select" name="Owner">
-<option value="<%$session{'CurrentUser'}->id%>" selected><%$session{'CurrentUser'}->Name %></option>
-<option value="<%$RT::Nobody->id%>"><%loc('Nobody')%></option>
-</select>
-</td>
-</tr>
-%#<tr><td colspan="3"><textarea cols="50" rows="3"></textarea></td></tr>
-</table>
-<div align="right"><input type="submit" class="button" value="<%loc('Create')%>" /></div>
-</form>
-</&>
-</div>
-
diff --git a/rt/html/Elements/Quicksearch b/rt/html/Elements/Quicksearch
deleted file mode 100644
index eb21b3b..0000000
--- a/rt/html/Elements/Quicksearch
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="ticket-overview">
-<&|/Widgets/TitleBox, title => loc("Quick search"), bodyclass => "",
- titleright => loc("Edit"), titleright_href => $RT::WebPath.'/Prefs/Quicksearch.html' &>
-<& /Elements/QueueSummary,
- cache => 'quick_search_queues',
- queue_filter => sub { $_->CurrentUserHasRight('ShowTicket') && !exists $unwanted->{$_->Name} },
- conditions => [ {cond => "Status = 'new'", name => loc ('new') },
- {cond => "Status = 'open'", name => loc ('open') },
- {cond => "Status = 'stalled'", name => loc ('stalled') }] &>
-</&>
-</div>
-<%INIT>
-my $unwanted = $session{'CurrentUser'}->UserObj->Preferences('QuickSearch', {});
-</%INIT>
diff --git a/rt/html/Elements/RT__Ticket/ColumnMap b/rt/html/Elements/RT__Ticket/ColumnMap
deleted file mode 100644
index ae96226..0000000
--- a/rt/html/Elements/RT__Ticket/ColumnMap
+++ /dev/null
@@ -1,319 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ARGS>
-$Name => undef
-$Attr => undef
-</%ARGS>
-
-
-<%ONCE>
-our ( $COLUMN_MAP );
-
-my $ColumnMap = sub {
- my $name = shift;
- my $attr = shift;
-
- # First deal with the simple things from the map
- if ( $COLUMN_MAP->{$name} ) {
- return ( $COLUMN_MAP->{$name}->{$attr} );
- }
-
- # now, let's deal with harder things, like Custom Fields
-
- elsif ( $name =~ /^(?:CF|CustomField)\.\{(.+)\}$/ ) {
- my $field = $1;
-
- if ( $attr eq 'attribute' ) {
- return ("CF.{$field}");
- }
- elsif ( $attr eq 'title' ) {
- return ( $field );
- }
- elsif ( $attr eq 'value' ) {
- # Display custom field contents, separated by newlines.
- # For Image custom fields we also show a thumbnail here.
- return sub {
- my $values = $_[0]->CustomFieldValues($field);
- my @values = map {
- (
- ($_->CustomFieldObj->Type eq 'Image')
- ? \($m->scomp( '/Elements/ShowCustomFieldImage', Object => $_ ))
- : $_->Content
- ),
- \'<br />',
- } @{ $values->ItemsArrayRef };
- pop @values; # Remove that last <br />
- return @values;
- };
- }
- }
-};
-
-my $LinkCallback = sub {
- my $method = shift;
-
- my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode};
- my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type};
- my $other_mode = ($mode eq "Target" ? "Base" : "Target");
- my $mode_uri = $mode.'URI';
- my $local_type = 'Local'.$mode;
-
- return sub {
- map {
- \'<a href="',
- $_->$mode_uri->Resolver->HREF,
- \'">',
- ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ),
- \'</a><br />',
- } @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef }
- }
-};
-
-$COLUMN_MAP = {
- Queue => {
- attribute => 'Queue',
- title => 'Queue id',
- value => sub { return $_[0]->Queue }
- },
- QueueName => {
- attribute => 'Queue',
- title => 'Queue',
- value => sub { return $_[0]->QueueObj->Name }
- },
- OwnerName => {
- title => 'Owner',
- attribute => 'Owner',
- value => sub { return $_[0]->OwnerObj->Name }
- },
- id => {
- attribute => 'id',
- align => 'right',
- value => sub { return $_[0]->id }
- },
- Status => {
- attribute => 'Status',
- value => sub { return loc($_[0]->Status) }
- },
- Subject => {
- attribute => 'Subject',
- value => sub { return $_[0]->Subject || "(" . loc('No subject') . ")" }
- },
- ExtendedStatus => {
- title => 'Status',
- attribute => 'Status',
- value => sub {
- my $Ticket = shift;
-
- if ( $Ticket->HasUnresolvedDependencies ) {
- if ( $Ticket->HasUnresolvedDependencies( Type => 'approval' )
- or $Ticket->HasUnresolvedDependencies( Type => 'code' ) )
- {
- return \'<em>', loc('(pending approval)'), \'</em>';
- }
- else {
- return \'<em>', loc('(pending other Collection)'), \'</em>';
- }
- }
- else {
- return loc( $Ticket->Status );
- }
-
- }
- },
- Priority => {
- attribute => 'Priority',
- value => sub { return $_[0]->Priority }
- },
- InitialPriority => {
- attribute => 'InitialPriority',
- name => 'Initial Priority',
- value => sub { return $_[0]->InitialPriority }
- },
- FinalPriority => {
- attribute => 'FinalPriority',
- name => 'Final Priority',
- value => sub { return $_[0]->FinalPriority }
- },
- EffectiveId => {
- attribute => 'EffectiveId',
- value => sub { return $_[0]->EffectiveId }
- },
- Type => {
- attribute => 'Type',
- value => sub { return $_[0]->Type }
- },
- TimeWorked => {
- attribute => 'TimeWorked',
- title => 'Time Worked',
- value => sub { return $_[0]->TimeWorked }
- },
- TimeLeft => {
- attribute => 'TimeLeft',
- title => 'Time Left',
- value => sub { return $_[0]->TimeLeft }
- },
- TimeEstimated => {
- attribute => 'TimeEstimated',
- title => 'Time Estimated',
- value => sub { return $_[0]->TimeEstimated }
- },
- Requestors => {
- attribute => 'Requestor.EmailAddress',
- value => sub { return $_[0]->Requestors->MemberEmailAddressesAsString }
- },
- Cc => {
- attribute => 'Cc.EmailAddress',
- value => sub { return $_[0]->Cc->MemberEmailAddressesAsString }
- },
- AdminCc => {
- attribute => 'AdminCc.EmailAddress',
- value => sub { return $_[0]->AdminCc->MemberEmailAddressesAsString }
- },
- StartsRelative => {
- title => 'Starts',
- attribute => 'Starts',
- value => sub { return $_[0]->StartsObj->AgeAsString }
- },
- StartedRelative => {
- title => 'Started',
- attribute => 'Started',
- value => sub { return $_[0]->StartedObj->AgeAsString }
- },
- CreatedRelative => {
- title => 'Created',
- attribute => 'Created',
- value => sub { return $_[0]->CreatedObj->AgeAsString }
- },
- LastUpdatedRelative => {
- title => 'Last Updated',
- attribute => 'LastUpdated',
- value => sub { return $_[0]->LastUpdatedObj->AgeAsString }
- },
- ToldRelative => {
- title => 'Told',
- attribute => 'Told',
- value => sub { return $_[0]->ToldObj->AgeAsString }
- },
- DueRelative => {
- title => 'Due',
- attribute => 'Due',
- value => sub {
- my $date = $_[0]->DueObj;
- if ($date && $date->Unix > 0 && $date->Unix < time()) {
- return (\'<span class="overdue">' , $date->AgeAsString , \'</span>');
- } else {
- return $date->AgeAsString;
- }
- }
- },
- ResolvedRelative => {
- title => 'Resolved',
- attribute => 'Resolved',
- value => sub { return $_[0]->ResolvedObj->AgeAsString }
- },
- Starts => {
- attribute => 'Starts',
- value => sub { return $_[0]->StartsObj->AsString }
- },
- Started => {
- attribute => 'Started',
- value => sub { return $_[0]->StartedObj->AsString }
- },
- Created => {
- attribute => 'Created',
- value => sub { return $_[0]->CreatedObj->AsString }
- },
- CreatedBy => {
- attribute => 'Creator',
- title => 'Created By',
- value => sub { return $_[0]->CreatorObj->Name }
- },
- LastUpdated => {
- attribute => 'LastUpdated',
- title => 'Last Updated',
- value => sub { return $_[0]->LastUpdatedObj->AsString }
- },
- LastUpdatedBy => {
- attribute => 'LastUpdatedBy',
- title => 'Last Updated By',
- value => sub { return $_[0]->LastUpdatedByObj->Name }
- },
- Told => {
- attribute => 'Told',
- value => sub { return $_[0]->ToldObj->AsString }
- },
- Due => {
- attribute => 'Due',
- value => sub { return $_[0]->DueObj->AsString }
- },
- Resolved => {
- attribute => 'Resolved',
- value => sub { return $_[0]->ResolvedObj->AsString }
- },
-
- # Everything from LINKTYPEMAP
- (map {
- $_ => { value => $LinkCallback->( $_ ) }
- } keys %RT::Ticket::LINKTYPEMAP),
-
- '_CLASS' => {
- value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' }
- },
- '_CHECKBOX' => {
- attribute => 'checkbox',
- title => loc('Update'),
- align => 'right',
- value => sub { return \('<input type="checkbox" class="checkbox" name="UpdateTicket'.$_[0]->id.'" value="1" checked />') }
- },
-
-};
-</%ONCE>
-<%init>
-$m->comp( '/Elements/Callback', COLUMN_MAP => $COLUMN_MAP, _CallbackName => 'ColumnMap');
-return $ColumnMap->( $Name, $Attr );
-</%init>
diff --git a/rt/html/Elements/Refresh b/rt/html/Elements/Refresh
deleted file mode 100644
index 91ad042..0000000
--- a/rt/html/Elements/Refresh
+++ /dev/null
@@ -1,69 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-<option value="-1"
-%unless ($Default) {
- selected
-%}
-><&|/l&>Don't refresh this page.</&></option>
-%foreach my $value (@refreshevery) {
-<option value="<%$value%>"
-% if ( $Default && ($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/RefreshHomepage b/rt/html/Elements/RefreshHomepage
deleted file mode 100644
index daed6b7..0000000
--- a/rt/html/Elements/RefreshHomepage
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form method="get" action="<%$RT::WebPath%>/index.html">
-<& /Elements/Refresh, Name => 'HomeRefreshInterval', Default => $session {'home_refresh_interval'} &>
-<div align="right"><input type="submit" class="button" value="<&|/l&>Go!</&>" /></div>
-</form>
diff --git a/rt/html/Elements/ScrubHTML b/rt/html/Elements/ScrubHTML
deleted file mode 100644
index 10d5f90..0000000
--- a/rt/html/Elements/ScrubHTML
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-my $scrubber = HTML::Scrubber->new();
-
-$scrubber->default(
- 0,
- {
- '*' => 0,
- id => 1,
- class => 1,
- # Match http, ftp and relative urls
- href => qr{^(?:http:|ftp:|https:|/|__Web(?:Path|BaseURL|URL)__)}i,
- face => 1,
- size => 1,
- target => 1
- }
-);
-
-$scrubber->deny(qw[*]);
-$scrubber->allow(
- qw[A B U P BR I HR BR SMALL EM FONT SPAN DIV UL OL LI DL DT DD PRE]);
-$scrubber->comment(0);
-return ( $scrubber->scrub($Content) );
-</%init>
-<%args>
-$Content => undef
-</%args>
diff --git a/rt/html/Elements/Section b/rt/html/Elements/Section
deleted file mode 100644
index cbccde1..0000000
--- a/rt/html/Elements/Section
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<h1><%$title%></h1>
-<%ARGS>
-$title => undef
-</%ARGS>
diff --git a/rt/html/Elements/SelectAttachmentField b/rt/html/Elements/SelectAttachmentField
deleted file mode 100644
index d0d080e..0000000
--- a/rt/html/Elements/SelectAttachmentField
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-<option value="Subject"><&|/l&>Subject</&></option>
-<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
deleted file mode 100644
index 77d27a2..0000000
--- a/rt/html/Elements/SelectBoolean
+++ /dev/null
@@ -1,71 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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 = '';
-my $FalseDefault ='';
-if ($Default && $Default !~ /true/i) {
- $FalseDefault = "SELECTED";
-}
-else {
- $TrueDefault = "SELECTED";
-}
-</%INIT>
diff --git a/rt/html/Elements/SelectCustomFieldOperator b/rt/html/Elements/SelectCustomFieldOperator
deleted file mode 100644
index ba19095..0000000
--- a/rt/html/Elements/SelectCustomFieldOperator
+++ /dev/null
@@ -1,64 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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 => ''
-</%ARGS>
diff --git a/rt/html/Elements/SelectCustomFieldValue b/rt/html/Elements/SelectCustomFieldValue
deleted file mode 100644
index 73897c0..0000000
--- a/rt/html/Elements/SelectCustomFieldValue
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 5767074..0000000
--- a/rt/html/Elements/SelectDate
+++ /dev/null
@@ -1,75 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<script type="text/javascript"><!--
- onLoadHook('createCalendarLink("<% $Name %>");');
---></script>
-<input type="text" id="<% $Name %>" name="<% $Name %>" value="<% $Default %>" size="<% $Size %>" />
-<%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 => ''
-$Name => undef
-$Size => 16
-</%args>
diff --git a/rt/html/Elements/SelectDateRelation b/rt/html/Elements/SelectDateRelation
deleted file mode 100644
index 056ad48..0000000
--- a/rt/html/Elements/SelectDateRelation
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index ff01543..0000000
--- a/rt/html/Elements/SelectDateType
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 85c1031..0000000
--- a/rt/html/Elements/SelectEqualityOperator
+++ /dev/null
@@ -1,64 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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 =>''
-</%ARGS>
diff --git a/rt/html/Elements/SelectGroups b/rt/html/Elements/SelectGroups
deleted file mode 100644
index 8b6e485..0000000
--- a/rt/html/Elements/SelectGroups
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="GroupField">
-% foreach my $col (RT::Group->BasicColumns) {
-<option value="<% $col->[0] %>"><% loc($col->[1]) %></option>
-% }
-% while (my $CF = $CFs->Next) {
-<option value="CustomField-<% $CF->Id %>"><&|/l&>CustomField</&>: <% $CF->Name %></option>
-% }
-</select>
-<& /Elements/SelectMatch, Name=> 'GroupOp' &>
-<input size="8" name="GroupString" />
-<%INIT>
-my $CFs = RT::CustomFields->new($session{'CurrentUser'});
-$CFs->LimitToChildType('RT::Group');
-$CFs->OrderBy( FIELD => 'Name' );
-</%INIT>
diff --git a/rt/html/Elements/SelectLang b/rt/html/Elements/SelectLang
deleted file mode 100644
index 490307f..0000000
--- a/rt/html/Elements/SelectLang
+++ /dev/null
@@ -1,80 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 3d2c27c..0000000
--- a/rt/html/Elements/SelectLinkType
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 828d304..0000000
--- a/rt/html/Elements/SelectMatch
+++ /dev/null
@@ -1,82 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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 = '';
-my $FalseDefault='';
-my $LikeDefault='';
-my $NotLikeDefault ='';
-
-if ($Default && $Default =~ /false/i) {
- $FalseDefault = "SELECTED";
-}
-elsif ($Default && $Default =~ /true/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
deleted file mode 100644
index 528a37d..0000000
--- a/rt/html/Elements/SelectNewTicketQueue
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<label accesskey="9">
- <& /Elements/SelectQueue, Name => 'Queue', %ARGS, ShowNullOption => 0, ShowAllQueues => 0 &>
-</label>
diff --git a/rt/html/Elements/SelectOwner b/rt/html/Elements/SelectOwner
deleted file mode 100644
index dbe2f8c..0000000
--- a/rt/html/Elements/SelectOwner
+++ /dev/null
@@ -1,110 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-%if ($DefaultValue) {
-<option <% !$Default ? "SELECTED" : '' %> value=""><%$DefaultLabel%></option>
-%}
-%foreach my $User ( @users) {
-<option <% ($User->Id == $Default) ? "SELECTED" : ''%>
-%if ($ValueAttribute eq 'id') {
- value="<%$User->id%>"
-%} elsif ($ValueAttribute eq 'Name') {
- value="<%$User->Name%>"
-%}
-><%$User->Name()%></option>
-%}
-</select>
-<%INIT>
-my @objects;
-my @users;
-
-if ($TicketObj) {
- @objects = ($TicketObj);
-}
-elsif ($QueueObj) {
- @objects = ($QueueObj);
-}
-elsif ($cfqueues) {
- @objects = keys %{$cfqueues};
-}
-else {
- # Let's check rights on an empty queue object. that will do a search for any queue.
- my $queue = RT::Queue->new($session{'CurrentUser'});
- push( @objects, $queue );
-}
-
-my %user_uniq_hash;
-
-
-foreach my $object (@objects) {
- my $Users = RT::Users->new($session{CurrentUser});
- $Users->WhoHaveRight(Right => 'OwnTicket', Object => $object, IncludeSystemRights => 1, IncludeSuperusers => 0);
- while (my $User = $Users->Next()) {
- next if ($User->id == $RT::Nobody->id); # skip nobody here, so we can make them first later
- $user_uniq_hash{$User->Id()} = $User;
- }
-}
-
-@users = sort { uc($a->Name) cmp uc($b->Name) } values %user_uniq_hash;
-unshift(@users, $RT::Nobody);
-
-
-
-</%INIT>
-
-<%ARGS>
-$QueueObj => undef
-$Name => undef
-$Default => 0
-$User => undef
-$TicketObj => undef
-$DefaultValue => 1
-$DefaultLabel => "-"
-$ValueAttribute => 'id'
-$cfqueues => undef
-</%ARGS>
diff --git a/rt/html/Elements/SelectQueue b/rt/html/Elements/SelectQueue
deleted file mode 100644
index 21d379d..0000000
--- a/rt/html/Elements/SelectQueue
+++ /dev/null
@@ -1,97 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if ($Lite) {
-% my $d = new RT::Queue($session{'CurrentUser'});
-% $d->Load($Default);
-<input name="<%$Name%>" size="25" value="<%$d->Name%>" />
-% }
-% else {
-<select name="<%$Name%>">
-% if ($ShowNullOption) {
- <option value="">-</option>
-% }
-% for my $queue (@{$session{$cache_key}}) {
- <option value="<% ($NamedValues ? $queue->{Name} : $queue->{Id}) %>" <% ($queue->{Id} eq $Default ? 'selected="selected"' : '') |n %>>
- <%$queue->{Name}%>
-% if ($Verbose and $queue->{Description}) {
- (<%$queue->{Description}%>)
-% }
- </option>
-% }
-</select>
-% }
-<%args>
-$CheckQueueRight => 'CreateTicket'
-$ShowNullOption => 1
-$ShowAllQueues => 1
-$Name => undef
-$Verbose => undef
-$NamedValues => 0
-$Default => 0
-$Lite => 0
-</%args>
-<%init>
-my $cache_key = "SelectQueue---"
- . $session{'CurrentUser'}->Id
- . "---$CheckQueueRight---$ShowAllQueues";
-
-if (not defined $session{$cache_key} and not $Lite) {
- my $q = new RT::Queues($session{'CurrentUser'});
- $q->UnLimit;
-
- while (my $queue = $q->Next) {
- if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) {
- push @{$session{$cache_key}}, {
- Id => $queue->Id,
- Name => $queue->Name,
- Description => $queue->Description,
- };
- }
- }
-}
-</%init>
diff --git a/rt/html/Elements/SelectResultsPerPage b/rt/html/Elements/SelectResultsPerPage
deleted file mode 100644
index 4b1fa7f..0000000
--- a/rt/html/Elements/SelectResultsPerPage
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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));
-$Default = 50 unless defined $Default;
-</%INIT>
-<%ARGS>
-
-$Name => undef
-$Default => 50
-
-</%ARGS>
diff --git a/rt/html/Elements/SelectSortOrder b/rt/html/Elements/SelectSortOrder
deleted file mode 100644
index 4d2423a..0000000
--- a/rt/html/Elements/SelectSortOrder
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 9250e73..0000000
--- a/rt/html/Elements/SelectStatus
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select NAME ="<%$Name%>">
-%if ($DefaultValue) {
-<option <% !$Default && "SELECTED" %> value=""><%$DefaultLabel%></option>
-%}
-%foreach my $status (@status) {
-%next if ($SkipDeleted && $status eq 'deleted');
-<option <% ($status eq $Default) && "SELECTED" %> value="<%$status%>"><%loc($status)%></option>
-% }
-</select>
-<%ONCE>
-my $queue = new RT::Queue($session{'CurrentUser'});
-my @status = $queue->StatusArray();
-</%ONCE>
-<%ARGS>
-$Name => undef
-$Default => ''
-$SkipDeleted => 0
-$DefaultValue => 1
-$DefaultLabel => "-"
-</%ARGS>
diff --git a/rt/html/Elements/SelectTicketSortBy b/rt/html/Elements/SelectTicketSortBy
deleted file mode 100644
index 1ae7f83..0000000
--- a/rt/html/Elements/SelectTicketSortBy
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index dba61e8..0000000
--- a/rt/html/Elements/SelectTicketTypes
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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/SelectTimeUnits b/rt/html/Elements/SelectTimeUnits
deleted file mode 100755
index c218d8a..0000000
--- a/rt/html/Elements/SelectTimeUnits
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<% $Name %>">
-<option value="minutes" selected><% loc('Minutes') %></option>
-<option value="hours"><% loc('Hours') %></option>
-</select>
-<%INIT>
-$Name .= '-TimeUnits' unless $Name =~ /-TimeUnits$/io;
-</%INIT>
-<%ARGS>
-$Name => ''
-</%ARGS>
diff --git a/rt/html/Elements/SelectUsers b/rt/html/Elements/SelectUsers
deleted file mode 100644
index 8535cab..0000000
--- a/rt/html/Elements/SelectUsers
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="UserField">
-% foreach my $col (RT::User->BasicColumns) {
-<option value="<% $col->[0] %>"><% loc($col->[1]) %></option>
-% }
-% while (my $CF = $CFs->Next) {
-<option value="CustomField-<% $CF->Id %>"><&|/l&>CustomField</&>: <% $CF->Name %></option>
-% }
-</select>
-<& /Elements/SelectMatch, Name=> 'UserOp' &>
-<input size="8" name="UserString" />
-<%INIT>
-my $CFs = RT::CustomFields->new($session{'CurrentUser'});
-$CFs->LimitToChildType('RT::User');
-$CFs->OrderBy( FIELD => 'Name' );
-</%INIT>
diff --git a/rt/html/Elements/SelectWatcherType b/rt/html/Elements/SelectWatcherType
deleted file mode 100644
index 8f15276..0000000
--- a/rt/html/Elements/SelectWatcherType
+++ /dev/null
@@ -1,71 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select NAME ="<%$Name%>">
-% if ($AllowNull) {
-<option value="">-</option>
-% }
-%for my $option (@types) {
-<option value="<%$option%>" <%defined($Default) && $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
deleted file mode 100644
index 3225c0d..0000000
--- a/rt/html/Elements/SetupSessionCookie
+++ /dev/null
@@ -1,133 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%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'};
-$SessionCookie = $cookies{$cookiename} ? $cookies{$cookiename}->value : undef;
-
-my %backends = (
- mysql => 'Apache::Session::MySQL',
- Pg => 'Apache::Session::Postgres',
-
- # Oracle => 'Apache::Session::Oracle',
-);
-
-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();
-}
-
-my $session_properties;
-if ( $session_class eq 'Apache::Session::File' ) {
- $session_properties = {
- Directory => $RT::MasonSessionDir,
- LockDirectory => $RT::MasonSessionDir,
- Transaction => 1
- };
-} else {
- $session_properties = {
- Handle => $RT::Handle->dbh,
- LockHandle => $RT::Handle->dbh,
- Transaction => 1
- };
-}
-
-eval {
- tie %session, $session_class, $SessionCookie, $session_properties
-};
-if ($@) {
-
- # If the session is invalid, create a new session.
- eval {
- tie %session, $session_class, undef, $session_properties;
- undef $cookies{$cookiename};
- };
-}
-elsif ( !($session{'CurrentUser'} && $session{'CurrentUser'}->id) ) {
- eval {
- undef $cookies{$cookiename};
- tied(%session)->delete;
- tie %session, $session_class, undef, $session_properties;
- }
-}
-
-if ($@) {
- die loc("RT couldn't store your session.") . "\n"
- . loc(
- "This may mean that that the directory '[_1]' isn't writable or a database table is missing or corrupt.",
- $RT::MasonSessionDir
- )
- . "\n\n"
- . $@;
-}
-
-if ( !$cookies{$cookiename} ) {
- my $cookie = new CGI::Cookie(
- -name => $cookiename,
- -value => $session{_session_id},
- -path => $RT::WebPath,
- -secure => ($RT::WebSecureCookies ? 1 :0)
- );
- $r->headers_out->{'Set-Cookie'} = $cookie->as_string;
-
-}
-
-return ();
-</%init>
-<%args>
-$SessionCookie => undef
-</%args>
diff --git a/rt/html/Elements/ShadedBox b/rt/html/Elements/ShadedBox
deleted file mode 100644
index 36b9cae..0000000
--- a/rt/html/Elements/ShadedBox
+++ /dev/null
@@ -1,33 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index e9fb69e..0000000
--- a/rt/html/Elements/ShadedInputRow
+++ /dev/null
@@ -1,35 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index 8947fcd..0000000
--- a/rt/html/Elements/ShadedRow
+++ /dev/null
@@ -1,31 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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/ShowCustomFieldBinary b/rt/html/Elements/ShowCustomFieldBinary
deleted file mode 100644
index e8fb2e7..0000000
--- a/rt/html/Elements/ShowCustomFieldBinary
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<a href="<%$RT::WebPath%>/Download/CustomFieldValue/<% $Object->Id %>/<% $Object->Content %>"><% $Object->Content %></a>
-<%ARGS>
-$Object => undef
-</%ARGS>
diff --git a/rt/html/Elements/ShowCustomFieldImage b/rt/html/Elements/ShowCustomFieldImage
deleted file mode 100644
index ee93f54..0000000
--- a/rt/html/Elements/ShowCustomFieldImage
+++ /dev/null
@@ -1,53 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% my $url = $RT::WebPath . "/Download/CustomFieldValue/".$Object->Id.'/'.$Object->Content;
-<a href="<% $url %>"><% $Object->Content %></a>
-<img type="<% $Object->ContentType %>" height="64" src="<% $url %>" align="middle" />
-<%ARGS>
-$Object
-</%ARGS>
diff --git a/rt/html/Elements/ShowCustomFieldWikitext b/rt/html/Elements/ShowCustomFieldWikitext
deleted file mode 100644
index c4393ae..0000000
--- a/rt/html/Elements/ShowCustomFieldWikitext
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% my $content = $Object->LargeContent || $Object->Content;
-% $content = $m->comp('/Elements/ScrubHTML', Content => $content);
-% my $base = $Object->Object->WikiBase;
-% my $wiki_content = Text::WikiFormat::format( $content."\n" , {}, { extended => 1, absolute_links => 1, implicit_links => $RT::WikiImplicitLinks, prefix => $base} );
-<%$wiki_content|n%>
-<%init>
-use Text::WikiFormat;
-</%init>
-<%ARGS>
-$Object
-</%ARGS>
diff --git a/rt/html/Elements/ShowCustomFields b/rt/html/Elements/ShowCustomFields
deleted file mode 100644
index cf6127e..0000000
--- a/rt/html/Elements/ShowCustomFields
+++ /dev/null
@@ -1,115 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
-% while ( my $CustomField = $CustomFields->Next ) {
-% my $Values = $Object->CustomFieldValues( $CustomField->Id );
-% my $count = $Values->Count;
- <tr id="CF-<%$CustomField->id%>-ShowRow">
- <td class="label"><% $CustomField->Name %>:</td>
- <td class="value">
-% unless ( $count ) {
-<i><&|/l&>(no value)</&></i>
-% } elsif ( $count == 1 ) {
-% $print_value->( $CustomField, $Values->First );
-% } else {
-<ul>
-% while ( my $Value = $Values->Next ) {
-<li>
-% $print_value->( $CustomField, $Value );
-</li>
-% }
-</ul>
-% }
- </td>
- </tr>
-% }
-</table>
-<%INIT>
-my $CustomFields = $Object->CustomFields;
-$m->comp('/Elements/Callback', _CallbackName => 'MassageCustomFields',
- CustomFields => $CustomFields);
-
-my $print_value = sub {
- my ($cf, $value) = @_;
- my $linked = $cf->LinkValueTo;
- if ( $linked ) {
- $m->out('<a href="'. $value->LinkValueTo .'" target="_new">');
- }
- my $comp = "ShowCustomField". $cf->Type;
- $m->comp('/Elements/Callback',
- _CallbackName => 'ShowComponentName',
- Name => \$comp,
- CustomField => $cf,
- Object => $Object
- );
- if ( $m->comp_exists( $comp ) ) {
- $m->comp( $comp, Object => $value );
- } else {
- $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) );
- }
- $m->out('</a>') if $linked;
-
- # This section automatically populates a div with the "IncludeContentForValue" for this custom
- # field if it's been defined
- if ( $cf->IncludeContentForValue ) {
- my $vid = $value->id;
- $m->out( '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' );
- $m->print( loc("See also:") );
- $m->out( '<a href="'. $value->IncludeContentForValue .'">' );
- $m->print( $value->IncludeContentForValue );
- $m->out( qq{</a></div>\n} );
- $m->out( qq{<script><!--\nahah('} );
- $m->print( $value->IncludeContentForValue );
- $m->out( qq{', 'object_cf_value_$vid');\n--></script>\n} );
- }
-};
-
-</%INIT>
-<%ARGS>
-$Object => undef
-</%ARGS>
diff --git a/rt/html/Elements/ShowLink b/rt/html/Elements/ShowLink
deleted file mode 100644
index 1b615c0..0000000
--- a/rt/html/Elements/ShowLink
+++ /dev/null
@@ -1,64 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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/Elements/ShowLinks b/rt/html/Elements/ShowLinks
deleted file mode 100755
index 8160c85..0000000
--- a/rt/html/Elements/ShowLinks
+++ /dev/null
@@ -1,112 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
- <tr>
- <td class="labeltop"><&|/l&>Depends on</&>:</td>
- <td class="value">
-<ul>
-% while (my $Link = $Ticket->DependsOn->Next) {
-<li><& ShowLink, URI => $Link->TargetURI &></li>
-% }
-</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 &></li>
-% }
-</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 &></li>
-% }
-</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 &></li>
-% }
-</ul>
- </td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Referred to by</&>:</td>
- <td class="value">
- <ul>
-% while (my $Link = $Ticket->ReferredToBy->Next) {
-% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->Type eq 'reminder');
-<li><& ShowLink, URI => $Link->BaseURI &></li>
-% }
-</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/Elements/ShowMemberships b/rt/html/Elements/ShowMemberships
deleted file mode 100644
index a0c83ad..0000000
--- a/rt/html/Elements/ShowMemberships
+++ /dev/null
@@ -1,88 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<ul>
-% while ( my $GroupMember = $GroupMembers->Next ) {
-% my $Group = RT::Group->new($session{'CurrentUser'});
-% $Group->Load($GroupMember->GroupId) or next;
-% if ($Group->Domain eq 'UserDefined') {
-<li><a href="<%$RT::WebPath%>/Admin/Groups/Modify.html?id=<% $Group->Id %>"><% $Group->Name %></a></li>
-% } elsif ($Group->Domain eq 'SystemInternal') {
-<li><em><% loc($Group->Type) %></em></li>
-% }
-% }
-</ul>
-<%INIT>
-my $GroupMembers = RT::GroupMembers->new($session{'CurrentUser'});
-$GroupMembers->Limit( FIELD => 'MemberId', VALUE => $UserObj->Id );
-my $alias = $GroupMembers->Join(
- TYPE => 'left',
- ALIAS1 => 'main',
- FIELD1 => 'GroupId',
- TABLE2 => 'Groups',
- FIELD2 => 'id'
-);
-$GroupMembers->Limit(
- ALIAS => $alias,
- FIELD => 'Domain',
- OPERATOR => '=',
- VALUE => 'SystemInternal',
-);
-$GroupMembers->Limit(
- ALIAS => $alias,
- FIELD => 'Domain',
- OPERATOR => '=',
- VALUE => 'UserDefined',
-);
-$GroupMembers->OrderByCols(
- { ALIAS => $alias, FIELD => 'Domain' },
- { ALIAS => $alias, FIELD => 'Name' },
-);
-</%INIT>
-<%ARGS>
-$UserObj
-</%ARGS>
diff --git a/rt/html/Elements/ShowSearch b/rt/html/Elements/ShowSearch
deleted file mode 100644
index e940121..0000000
--- a/rt/html/Elements/ShowSearch
+++ /dev/null
@@ -1,125 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&|/Widgets/TitleBox,
- title => loc($search->Description, $ProcessedSearchArg->{'Rows'}),
- title_href => $query_link_url.$QueryString,
- titleright => $customize ? loc('Edit') : '',
- titleright_href => $customize &>
-<& $query_display_component, %$ProcessedSearchArg, ShowNavigation => 0 &>
-</&>
-<%init>
-my $search;
-my $user = $session{'CurrentUser'}->UserObj;
-my $SearchArg;
-my $customize;
-my $query_display_component = '/Elements/TicketList';
-my $query_link_url = 'Search/Results.html';
-
-if ($SavedSearch) {
- my ( $container_object, $search_id ) = _parse_saved_search($SavedSearch);
- $search = $container_object->Attributes->WithId($search_id);
- unless ( $search->Id && ref( $SearchArg = $search->Content ) eq 'HASH' ) {
- $m->out("Saved Search $SavedSearch not found");
- return;
- }
- $SearchArg->{'SearchType'} ||= 'Ticket';
- if ( $SearchArg->{SearchType} ne 'Ticket' ) {
-
- # XXX: dispatch to different handler here
- $query_display_component
- = '/Search/Elements/' . $SearchArg->{SearchType};
- $query_link_url = "Search/$SearchArg->{SearchType}.html";
- } else {
- $customize = $RT::WebPath . '/Search/Build.html?'
- . $m->comp( '/Elements/QueryString',
- LoadSavedSearch => $SavedSearch );
- }
-} else {
- ($search) = RT::System->new( $session{'CurrentUser'} ) ->Attributes->Named( 'Search - ' . $Name );
- unless ( $search && $search->Id ) {
- my (@custom_searches) = RT::System->new( $session{'CurrentUser'} )->Attributes->Named('SavedSearch');
- foreach my $custom (@custom_searches) {
- if ($custom->Description eq $Name) { $search = $custom; last }
- }
- unless ($search && $search->id) {
- $m->out("Predefined search $Name not found");
- return;
- }
- }
-
- $SearchArg = $user->Preferences( $search, $search->Content );
- $customize = $RT::WebPath . '/Prefs/Search.html?'
- . $m->comp( '/Elements/QueryString',
- name => ref($search) . '-' . $search->Id );
-}
-
-# ProcessedSearchArg is a search with overridings, but for link we use
-# orginal search's poperties
-my $ProcessedSearchArg = $SearchArg;
-$ProcessedSearchArg = { %$SearchArg, %Override } if keys %Override;
-
-$m->comp(
- '/Elements/Callback', %ARGS,
- _CallbackName => 'ModifySearch',
- OriginalSearch => $SearchArg,
- Search => $ProcessedSearchArg,
-);
-
-foreach ( $SearchArg, $ProcessedSearchArg ) {
- $_->{'Format'} =~ s/__WebPath__/$RT::WebPath/g;
- $_->{'Format'} =~ s/__loc\(["']?(\w+)["']?\)__/loc("$1")/ge;
-}
-
-my $QueryString = '?' . $m->comp( '/Elements/QueryString', %$SearchArg );
-
-</%init>
-<%ARGS>
-$Name => undef
-$SavedSearch => undef
-%Override => ()
-</%ARGS>
diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch
deleted file mode 100644
index 2876a29..0000000
--- a/rt/html/Elements/SimpleSearch
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form action="<% $RT::WebPath %>/Search/Simple.html">
- <input size="12" name="q" autocomplete="off" accesskey="0" class="field" />
- <input type="submit" class="button" value="<&|/l&>Search</&>" />
-</form>
diff --git a/rt/html/Elements/Submit b/rt/html/Elements/Submit
deleted file mode 100644
index e2dd377..0000000
--- a/rt/html/Elements/Submit
+++ /dev/null
@@ -1,86 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="submit">
- <div class="extra-buttons">
-% if ($CheckAll) {
- <input type="button" value="<%$CheckAllLabel%>" onclick="setCheckbox(this.form, '<% $CheckboxName %>', true);return false;" class="button" />
-% }
-% if ($ClearAll) {
- <input type="button" value="<%$ClearAllLabel%>" onclick="setCheckbox(this.form, '<% $CheckboxName %>', false);return false;" class="button" />
-% }
-% if ($Reset) {
- <input type="reset" value="<%$ResetLabel%>" class="button" />
-% }
- </div>
- <div class="buttons">
-% if ($AlternateLabel) {
- <span class="caption"><%$AlternateCaption%></span>
- <input type="submit" <% $Name && qq[ name="$Name"] | n %> value="<%$AlternateLabel%>" class="button" />
-% } else {
- <span class="caption"><%$Caption%></span>
- <input type="submit" <% $Name && qq[ name="$Name"] | n %> value="<%$Label%>" class="button" />
-% }
- </div>
- <div class="submit-clear"></div>
-</div>
-
-<%ARGS>
-$color => undef
-$Caption => ''
-$AlternateCaption => undef
-$AlternateLabel => undef
-$Label => loc('Submit')
-$Name => undef
-$CheckAll => undef
-$CheckAllLabel => loc('Check All')
-$ClearAll => undef
-$ClearAllLabel => loc('Clear All')
-$CheckboxName => ''
-$Reset => undef
-$ResetLabel => loc('Reset')
-</%ARGS>
diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs
deleted file mode 100644
index 3b0f3da..0000000
--- a/rt/html/Elements/Tabs
+++ /dev/null
@@ -1,122 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/PageLayout,
- current_toptab => $current_toptab,
- current_tab => $current_tab,
- toptabs => $toptabs,
- topactions => $topactions,
- tabs => $tabs,
- actions => $actions,
- subactions => $subactions,
- title => $Title,
- show_menu => $show_menu,
-&>
-<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 => '',
- },
- Ab => { title => loc('Simple Search'),
- path => 'Search/Simple.html'
- },
- B => { title => loc('Tickets'),
- path => 'Search/Build.html'
- },
- C => { title => loc('Tools'),
- path => 'Tools/index.html'
- },
- P => { title => loc('Approval'),
- path => 'Approvals/'
- },
- };
-
-if ($session{'CurrentUser'}->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )) {
- $basetabs->{E} = { title => loc('Configuration'),
- path => 'Admin/',
- };
-}
-
-if ($session{'CurrentUser'}->HasRight( Right => 'ModifySelf',
- Object => $RT::System )) {
- $basetabs->{K} = { title => loc('Preferences'),
- path => 'User/Prefs.html'
- };
-}
-
-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
-$show_menu => 1
-</%ARGS>
diff --git a/rt/html/Elements/TicketList b/rt/html/Elements/TicketList
deleted file mode 100644
index 81e265d..0000000
--- a/rt/html/Elements/TicketList
+++ /dev/null
@@ -1,178 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table border="0" cellspacing="0" cellpadding="1" width="100%">
-
-% if ($ShowHeader) {
-<& /Elements/CollectionAsTable/Header,
- Format => \@Format,
- FormatString => $Format,
- AllowSorting => $AllowSorting,
- Order => $Order,
- Query => $Query,
- Rows => $Rows,
- Page => $Page,
- OrderBy => $OrderBy ,
- BaseURL => $BaseURL,
- maxitems => $maxitems &>
-% }
-
-% my $i;
-% while (my $record = $Collection->Next) {
-% $i++;
-% # Every ten rows, flush the buffer and put something on the page.
-% $m->flush_buffer() unless ($i % 10);
-<& /Elements/CollectionAsTable/Row, Format => \@Format, i => $i, record => $record, maxitems => $maxitems &>
-% }
-
-</table>
-
-% if ($Rows && $ShowNavigation) {
-<hr>
-% my $oddRows;
-% if (($TotalFound % $Rows) == 0) {
-% $oddRows = 0;
-% } else { $oddRows = 1; }
-% my $pages = int($TotalFound/$Rows)+$oddRows;
-% $pages = 1 if $pages < 1;
-<&|/l, $Page, $pages &>Page [_1] of [_2]</&>
-
-<%perl>
-my $prev = $m->comp(
- '/Elements/QueryString',
- Query => $Query,
- Format => $Format,
- Rows => $Rows,
- OrderBy => $OrderBy,
- Order => $Order,
- Page => ( $Page - 1 )
-);
-my $next = $m->comp(
- '/Elements/QueryString',
- Query => $Query,
- Format => $Format,
- Rows => $Rows,
- OrderBy => $OrderBy,
- Order => $Order,
- Page => ( $Page + 1 )
-);
-</%perl>
-% if ($Page > 1) {
-<a href="<%$BaseURL%><%$prev%>"><&|/l&>Previous Page</&></a>
-% }
-% if (($Page * $Rows) < $TotalFound) {
-<a href="<%$BaseURL%><%$next%>"><&|/l&>Next Page</&></a>
-% }
-% }
-<%INIT>
-my $maxitems = 0;
-
-$Format ||= $RT::DefaultSearchResultFormat;
-
-# DisplayFormat lets us use a "temporary" format for display, while
-# still using our original format for next/prev page links.
-# bulk update uses this feature to add checkboxes
-
-
-$DisplayFormat ||= $Format;
-
-# Scrub the html of the format string to remove any potential nasties.
-$Format = $m->comp('/Elements/ScrubHTML', Content => $Format);
-$DisplayFormat = $m->comp('/Elements/ScrubHTML', Content => $DisplayFormat);
-
-
-unless ($Collection) {
- $Collection = RT::Tickets->new($session{'CurrentUser'});
- $Collection->FromSQL($Query);
-}
-
-my (@Format) = $m->comp('/Elements/CollectionAsTable/ParseFormat', Format => $DisplayFormat);
-
-# Find the maximum number of items in any row, so we can pad the table.
-my $item = 0;
-foreach my $col (@Format) {
- $item++;
- if ( $col->{title} && ($col->{title} eq 'NEWLINE') ) {
- $item = 0;
- }
- else {
- $maxitems = $item if $item > $maxitems;
- }
-}
-
-if ($OrderBy =~ /\|/) {
- # Multiple Sorts
- my @OrderBy = split /\|/,$OrderBy;
- my @Order = split /\|/,$Order;
- $Collection->OrderByCols(
- map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } ( 0
- .. $#OrderBy ) );;
-} else {
- $Collection->OrderBy(FIELD => $OrderBy, ORDER => $Order);
-}
-
-$Collection->RowsPerPage($Rows) if ($Rows);
-$Page = 1 unless $Page > 0; # workaround problems with Page = '' or undef
-$Collection->GotoPage( $Page - 1 ); # SB uses page 0 as the first page
-my $TotalFound = $Collection->CountAll();
-
-</%INIT>
-<%ARGS>
-$Query => undef
-$Rows => 50
-$Page => 1
-$Title => 'Ticket Search'
-$Collection => undef
-$AllowSorting => undef
-$Order => undef
-$OrderBy => undef
-$BaseURL => undef
-$Format => $RT::DefaultSearchResultFormat
-$DisplayFormat => undef
-$ShowNavigation => 1
-$ShowHeader => 1
-</%ARGS>
diff --git a/rt/html/Elements/TitleBox b/rt/html/Elements/TitleBox
deleted file mode 100644
index 6597323..0000000
--- a/rt/html/Elements/TitleBox
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-# For compatibility with 3.4
-$m->comp('/Widgets/TitleBox', %ARGS );
-</%init>
diff --git a/rt/html/Elements/TitleBoxEnd b/rt/html/Elements/TitleBoxEnd
deleted file mode 100644
index 42626ff..0000000
--- a/rt/html/Elements/TitleBoxEnd
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-# For compatibility with 3.4
-$m->comp('/Widgets/TitleBoxEnd', %ARGS );
-</%init>
diff --git a/rt/html/Elements/TitleBoxStart b/rt/html/Elements/TitleBoxStart
deleted file mode 100644
index da04f8b..0000000
--- a/rt/html/Elements/TitleBoxStart
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-# For compatibility with 3.4
-$m->comp('/Widgets/TitleBoxStart', %ARGS );
-</%init>
diff --git a/rt/html/Elements/ValidateCustomFields b/rt/html/Elements/ValidateCustomFields
deleted file mode 100644
index c043d40..0000000
--- a/rt/html/Elements/ValidateCustomFields
+++ /dev/null
@@ -1,81 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-my $valid = 1;
-$CustomFields->GotoFirstItem;
-while (my $CF = $CustomFields->Next) {
- my $pattern = $CF->Pattern;
- my $field = $NamePrefix . $CF->Id . "-Value";
- my $value;
-
- if ($ARGSRef->{"${field}s-Magic"} and exists $ARGSRef->{"${field}s"}) {
- $value = $ARGSRef->{"${field}s"};
-
- # We only validate Single Combos -- multis can never be user input
- next if ref $value;
- }
- else {
- $value = $ARGSRef->{$field};
- }
-
- $m->notes(('Field-' . $CF->Id) => $value);
- next if $CF->MatchPattern($value);
- $m->notes(
- ('InvalidField-' . $CF->Id)
- => (loc("Input must match [_1]", $CF->FriendlyPattern))
- );
- $valid = 0;
-}
-$m->notes('ValidFields', $valid);
-return $valid;
-</%INIT>
-<%ARGS>
-$CustomFields
-$ARGSRef
-$NamePrefix => "Object-RT::Ticket--CustomField-"
-</%ARGS>
diff --git a/rt/html/Elements/ViewUser b/rt/html/Elements/ViewUser
deleted file mode 100644
index 6572724..0000000
--- a/rt/html/Elements/ViewUser
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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/Helpers/CalPopup.html b/rt/html/Helpers/CalPopup.html
deleted file mode 100644
index dc5acf7..0000000
--- a/rt/html/Helpers/CalPopup.html
+++ /dev/null
@@ -1,129 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, ShowBar => 0 &>
-%# From /Elements/Header
-</div>
-<div id="body" class="calpopup">
-
-<a href="#" onclick="window.close(); return false;"><&|/l&>Close window</&></a>
-
-<div class="calendar">
- <table>
- <caption>
- <a class="prev" href="CalPopup.html?DisplayedMonth=<%$prev_month%>&DisplayedYear=<%$prev_year%>&field=<%$field%>"><&|/l&>Prev</&></a>
- <span class="month"><% $months[$DisplayedMonth-1] %> <% $DisplayedYear %></span>
- <a class="next" href="CalPopup.html?DisplayedMonth=<%$next_month%>&DisplayedYear=<%$next_year%>&field=<%$field%>"><&|/l&>Next</&></a>
- </caption>
- <tr>
-% foreach my $wday (@weekdays) {
- <th><%$wday%></th>
-% }
- </tr>
-% foreach my $week (@cal) {
- <tr>
-% foreach my $day (@{$week}) {
- <td>
-% if ($day) {
-% my $datestr = sprintf('%04d-%02d-%02d', $DisplayedYear, $DisplayedMonth, $day);
- <a href="#" onclick="updateParentField('<% $field %>','<% $datestr %>'); return false;"><% $day %></a>
-% } else {
- &nbsp;
-% }
- </td>
-% } #foreach $day
- </tr>
-% } # foreach $week
- </table>
-</div>
-</div>
-</body>
-</html>
-% $m->abort();
-
-<%init>
-use Calendar::Simple;
-my @today = localtime(time());
-
-my @weekdays;
-push @weekdays, loc($_)
- for qw(Sun Mon Tue Wed Thu Fri Sat);
-
-my @months;
-push @months, loc($_)
- for qw(January February March April May June July August
- September October November December);
-
-unless ($DisplayedYear) {
- $DisplayedMonth = $today[4] + 1;
- $DisplayedYear = ($today[5] + 1900);
-}
-
-my ($prev_year, $next_year, $prev_month, $next_month);
-$prev_month = $next_month = $DisplayedMonth;
-$prev_year = $next_year = $DisplayedYear;
-
-$next_month++;
-$prev_month--;
-
-if ($DisplayedMonth == 12) {
- $next_year++;
- $next_month = 1;
-}
-elsif ($DisplayedMonth == 1) {
- $prev_month = 12;
- $prev_year--;
-}
-
-my @cal = calendar($DisplayedMonth, $DisplayedYear);
-</%init>
-
-<%args>
-$field => 'none'
-$DisplayedMonth => undef
-$DisplayedYear => undef
-</%args>
diff --git a/rt/html/Helpers/EmailAutocomplete b/rt/html/Helpers/EmailAutocomplete
deleted file mode 100644
index c1b2755..0000000
--- a/rt/html/Helpers/EmailAutocomplete
+++ /dev/null
@@ -1,47 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
diff --git a/rt/html/NoAuth/Logout.html b/rt/html/NoAuth/Logout.html
deleted file mode 100644
index 9af4a93..0000000
--- a/rt/html/NoAuth/Logout.html
+++ /dev/null
@@ -1,74 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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>.
-
-% $m->abort();
-
-<%INIT>
-$m->comp('/Elements/Callback', _CallbackName => 'BeforeSessionDelete', %ARGS);
-
-if (defined %session) {
- tied(%session)->delete;
-}
-
-$m->comp('/Elements/Callback', _CallbackName => 'AfterSessionDelete', %ARGS);
-</%INIT>
-
-<%ARGS>
-$URL => $RT::WebPath."/"
-</%ARGS>
diff --git a/rt/html/NoAuth/Reminder.html b/rt/html/NoAuth/Reminder.html
deleted file mode 100644
index 18bde44..0000000
--- a/rt/html/NoAuth/Reminder.html
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, title => loc('Password Reminder') &>
-
-<&|/l&>Not yet implemented.</&>
diff --git a/rt/html/NoAuth/css/3.4-compat/body.css b/rt/html/NoAuth/css/3.4-compat/body.css
deleted file mode 100644
index 81442f6..0000000
--- a/rt/html/NoAuth/css/3.4-compat/body.css
+++ /dev/null
@@ -1,75 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#body {
- margin: 0.5em 0.5em 0 0.5em;
- float: left;
- width: 80%;
-}
-
-#body h1 { font-size: 1.5em; }
-#body h2 { font-size: 1.3em; }
-#body h3 { font-size: 1.1em; }
-#body h4 { font-size: 1em; }
-#body h5 { font-size: 0.9em; }
-#body h6 { font-size: 0.8em; }
-
-#body h1, #body h2, #body h3, #body h4, #body h5, #body h6 {
- font-weight: bold;
-}
-
-#body :link { color: black; }
-
-#body :link, #body :visited {
- font-weight: bold;
- text-decoration: none;
-}
-
-#body :link:hover, #body :visited:hover {
- text-decoration: underline;
-}
-
diff --git a/rt/html/NoAuth/css/3.4-compat/footer.css b/rt/html/NoAuth/css/3.4-compat/footer.css
deleted file mode 100644
index 326ff64..0000000
--- a/rt/html/NoAuth/css/3.4-compat/footer.css
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#footer {
- clear: both;
- font-size: 0.8em;
- margin-top: 5em;
- padding-bottom: 2em;
- color: #888;
-}
-
-#footer p {
- text-align: right;
- padding: 0 0.5em 0 0;
- margin: 0;
-}
-
diff --git a/rt/html/NoAuth/css/3.4-compat/forms.css b/rt/html/NoAuth/css/3.4-compat/forms.css
deleted file mode 100644
index 539952b..0000000
--- a/rt/html/NoAuth/css/3.4-compat/forms.css
+++ /dev/null
@@ -1,104 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-form .label, form label {
- font-weight: bold;
-}
-
-.submit {
- background: #069;
-%# These borders are needed so the container actually surrounds the floats inside it
- border-top: 1px solid white;
- border-bottom: 1px solid white;
- color: #ffdb00;
- font-weight: bold;
-}
-
-.submit .buttons { float: right; }
-.submit .extra-buttons { float: left; }
-.submit .button { font-size: 0.9em; }
-
-.submit .submit-clear { clear: right; }
-
-.input-row {
- clear: both;
- padding: 0.25em;
-}
-
-%# ComboBox styles... some properties like height and width must be dynamically
-%# set in the JS (at least for now).
-.combobox {
- border: 2px inset ButtonHighlight;
- padding-left: 0.5em;
- padding-bottom: 0.1em;
-}
-
-.combobox .combo-button {
- padding: 0 2px 0 2px;
- margin: 0;
- background: ButtonFace;
- color: ButtonText;
- border: 2px outset ButtonHighlight;
- cursor: default;
- font-size: 8pt;
-}
-
-.combobox .combo-text {
- border: none;
- margin: 0;
- padding: 0;
-}
-
-.combobox .combo-list {
- z-index: 200;
-}
-
-#quickbar #topactions form {
- display: inline;
- margin-left: 2em;
-}
-
diff --git a/rt/html/NoAuth/css/3.4-compat/header.css b/rt/html/NoAuth/css/3.4-compat/header.css
deleted file mode 100644
index 30ce6f1..0000000
--- a/rt/html/NoAuth/css/3.4-compat/header.css
+++ /dev/null
@@ -1,88 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#header #page-menu {
- display: none;
-}
-
-#header {
- background: #4282b5;
- margin-top: 0;
- padding-bottom: 0.2em;
- float: left;
- width: 82%;
-}
-
-#header h1 {
- background: #4282b5;
- color: white;
- font-size: 1.7em;
- margin: 0;
- padding: 0;
-}
-
-#header #actions-menu {
- display: block;
- margin: 0 1em 0 0;
- padding: 0;
- color: white;
- text-align: right;
- font-size: 1.2em;
-}
-
-#header #actions-menu li {
- display: inline;
-}
-
-#header #actions-menu :link, #header #actions-menu :visited {
- color: white;
- text-decoration: none;
-}
-
-#header #actions-menu :link:hover, #header #actions-menu :visited:hover {
- text-decoration: underline;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/login.css b/rt/html/NoAuth/css/3.4-compat/login.css
deleted file mode 100644
index 7b8fad6..0000000
--- a/rt/html/NoAuth/css/3.4-compat/login.css
+++ /dev/null
@@ -1,54 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#body.login-body {
- width: 98%;
-}
-
-#login-box {
- width: 30em;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/main.css b/rt/html/NoAuth/css/3.4-compat/main.css
deleted file mode 100644
index b376b3c..0000000
--- a/rt/html/NoAuth/css/3.4-compat/main.css
+++ /dev/null
@@ -1,69 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# Import the 3.5 styles we want to build off of...
-@import "../3.5-default/logo.css";
-@import "../3.5-default/misc.css";
-@import "../3.5-default/transactions.css";
-@import "../3.5-default/approvals.css";
-@import "../3.5-default/login.css";
-@import "../3.5-default/quickbar.css";
-@import "../3.5-default/ticket.css";
-
-%# ...and then import the 3.4 compat styles afterwards so they can cascade
-@import "nav.css";
-@import "footer.css";
-@import "body.css";
-@import "titlebox.css";
-@import "header.css";
-@import "forms.css";
-@import "transactions.css";
-@import "ticket.css";
-@import "login.css";
-@import "quickbar.css";
-@import "misc.css";
-
diff --git a/rt/html/NoAuth/css/3.4-compat/misc.css b/rt/html/NoAuth/css/3.4-compat/misc.css
deleted file mode 100644
index c75b4d8..0000000
--- a/rt/html/NoAuth/css/3.4-compat/misc.css
+++ /dev/null
@@ -1,49 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.oddline { background: white; }
-.evenline { background: #cecfef; }
diff --git a/rt/html/NoAuth/css/3.4-compat/nav.css b/rt/html/NoAuth/css/3.4-compat/nav.css
deleted file mode 100644
index b170c29..0000000
--- a/rt/html/NoAuth/css/3.4-compat/nav.css
+++ /dev/null
@@ -1,106 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#nav {
- clear: left;
- float: left;
- width: 18%;
- font-size: 1.4em;
- color: #eee;
- margin: 0;
- background: #4282b5 url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right;
-}
-
-#nav #system-menu {
- padding: 0 0.2em 0.2em 0.2em;
- margin-top: 0;
-/* background: transparent url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right; */
-}
-
-#nav ul {
- list-style: none;
- padding-left: 0.5em;
- margin-left: 0;
-}
-
-#nav ul .bullet, #nav ul .separator {
- display: none;
-}
-
-#nav ul li {
- padding: 0.4em 0 0.4em 0.2em;
- border-bottom: 1px solid white;
-}
-
-#nav li ul {
- font-size: 0.9em;
-}
-
-#nav li ul li {
- border-bottom: none;
- padding: 0.2em 0 0 0;
-}
-
-#nav :link, #nav :visited {
- text-decoration: none;
- color: #eee;
-}
-
-#nav :link:hover,
-#nav :visited:hover,
-#nav :link.selected,
-#nav :visited.selected
-{
- color: #ff6;
-}
-
-#nav :link.selected,
-#nav :visited.selected
-{
- text-decoration: underline;
- font-weight: bold;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/quickbar.css b/rt/html/NoAuth/css/3.4-compat/quickbar.css
deleted file mode 100644
index a7b23d5..0000000
--- a/rt/html/NoAuth/css/3.4-compat/quickbar.css
+++ /dev/null
@@ -1,82 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#quickbar {
- border: 1px solid transparent;
-}
-
-#quickbar #quick-personal {
- display: inline;
- color: #888;
- padding: 0.5em 1em 0 0;
- float: right;
-}
-
-#quickbar #quick-personal span {
- font-weight: bold;
-}
-
-#quickbar #quick-personal :link,
-#quickbar #quick-personal :visited
-{
- color: #888;
- font-weight: bold;
-}
-
-#quickbar #quick-personal :link:hover,
-#quickbar #quick-personal :visited:hover
-{
- color: black;
-}
-
-#quickbar #topactions {
- color: white;
- font-size: 0.9em;
- position: relative;
- right: 1em;
- float: right;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/ticket.css b/rt/html/NoAuth/css/3.4-compat/ticket.css
deleted file mode 100644
index b51c701..0000000
--- a/rt/html/NoAuth/css/3.4-compat/ticket.css
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.value {
- font-weight: bold;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/titlebox.css b/rt/html/NoAuth/css/3.4-compat/titlebox.css
deleted file mode 100644
index d48704e..0000000
--- a/rt/html/NoAuth/css/3.4-compat/titlebox.css
+++ /dev/null
@@ -1,103 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.titlebox {
- margin: 0.5em 0;
-}
-
-.titlebox .titlebox-content {
- padding: 0.05em;
-}
-
-.titlebox .titlebox-title {
- background: #069;
- padding: 0.2em 0.5em;
- color: white;
- border-top: 1px solid black;
- border-bottom: 1px solid black;
- font-weight: bold;
- position: relative;
-}
-
-.titlebox .titlebox-title .right {
- position: absolute;
- right: 1.5em;
- font-size: 0.9em;
-}
-
-#body .titlebox .titlebox-title :link, #body .titlebox .titlebox-title :visited {
- color: white;
-}
-
-#body .titlebox .titlebox-title .widget :link, #body .titlebox .titlebox-title .widget :visited {
- color: black;
-}
-
-.titlebox .titlebox-content hr.clear {
- visibility: hidden;
-}
-
-%# TRS: I wish there was a more elegant way to do this... I essentially need to
-%# select all elements X that do NOT have element Y as a descendant... which I can
-%# fake with the child selector of CSS2, but IE is stupid and does not support that.
-
-% for (qw(index
-% Search-Build
-% User-Prefs
-% Approvals
-% Admin-Users-Modify
-% SelfService
-% SelfService-Closed
-% Ticket-ModifyAll
-% ))
-% {
-#comp-<%$_%> .titlebox .titlebox-content,
-% }
-.titlebox .titlebox .titlebox-content
-{
- background: #cecfce;
-}
diff --git a/rt/html/NoAuth/css/3.4-compat/transactions.css b/rt/html/NoAuth/css/3.4-compat/transactions.css
deleted file mode 100644
index 1331bfa..0000000
--- a/rt/html/NoAuth/css/3.4-compat/transactions.css
+++ /dev/null
@@ -1,83 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.ticket-transaction {
- margin: 0;
- border: none;
-}
-
-.ticket-transaction .type {
- width: 1em;
-}
-
-.ticket-transaction.even {
- background: #cecfef;
-}
-
-.ticket-transaction.basics { border-color: #9c3031; }
-.ticket-transaction.basics .type { background: #9c3031; }
-.ticket-summary .ticket-info-basics .titlebox-content { border-left: none; }
-.ticket-summary .ticket-info-basics .titlebox-title { background: #9c3031; }
-
-.ticket-transaction.people { border-color: #31309c; }
-.ticket-transaction.people .type { background: #31309c; }
-.ticket-summary .ticket-info-people .titlebox-content { border-left: none; }
-.ticket-summary .ticket-info-people .titlebox-title { background: #31309c; }
-
-.ticket-transaction.links { border-color: #316531; }
-.ticket-transaction.links .type { background: #316531; }
-.ticket-summary .ticket-info-links .titlebox-content { border-left: none; }
-.ticket-summary .ticket-info-links .titlebox-title { background: #316531; }
-
-.ticket-transaction.dates { border-color: #633063; }
-.ticket-transaction.dates .type { background: #633063; }
-.ticket-summary .ticket-info-dates .titlebox-content { border-left: none; }
-.ticket-summary .ticket-info-dates .titlebox-title { background: #633063; }
-
-.ticket-transaction.message { border-color: #069; }
-.ticket-transaction.message .type { background: #069; }
-
diff --git a/rt/html/NoAuth/css/3.5-default/approvals.css b/rt/html/NoAuth/css/3.5-default/approvals.css
deleted file mode 100644
index 60629cd..0000000
--- a/rt/html/NoAuth/css/3.5-default/approvals.css
+++ /dev/null
@@ -1,97 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.approval {
- margin-bottom: 3em;
- padding: 0 0 1em 0;
- border: 1px solid #069;
-}
-
-.approval .name {
- background: #069;
- color: white;
- font-size: 1.1em;
- padding: 0.2em 0 0.4em 0.2em;
-}
-
-#body .approval .name :link, #body .approval .name :visited {
- color: white;
-}
-
-.approval .originating-ticket {
- margin: 0.5em;
- border: 1px solid #aaa;
-}
-
-.approval .originating-ticket .link {
- display: block;
- background: #aaa;
- padding: 0.2em 0 0.4em 0.2em;
-}
-
-.approval .originating-ticket .info {
- padding: 0.5em;
-}
-
-#body .approval .originating-ticket .link :link,
-#body .approval .originating-ticket .link :visited {
- color: black;
-}
-
-.approval .form {
- margin: 1em 0.5em 0.5em 0.5em;
-}
-
-.approval .form .action, .approval .form .notes {
- float: left;
- margin-left: 1em;
-}
-
-.approval .form .action { padding-top: 1em; }
-
-.approval .form .action label { font-weight: normal; }
-.approval .form .notes label { display: block; }
diff --git a/rt/html/NoAuth/css/3.5-default/body.css b/rt/html/NoAuth/css/3.5-default/body.css
deleted file mode 100755
index dc02d01..0000000
--- a/rt/html/NoAuth/css/3.5-default/body.css
+++ /dev/null
@@ -1,81 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#body {
- clear: both;
- margin: 0 0.75em 0 2em;
- padding-top: 0.5em;
-}
-
-#body h1 {
- border-bottom: 1px dotted #069;
- padding-left: 0.5em;
-}
-
-#body h1 { font-size: 1.5em; }
-#body h2 { font-size: 1.3em; }
-#body h3 { font-size: 1.1em; }
-#body h4 { font-size: 1em; }
-#body h5 { font-size: 0.9em; }
-#body h6 { font-size: 0.8em; }
-
-#body h1, #body h2, #body h3, #body h4, #body h5, #body h6 {
- color: #930;
- font-weight: bold;
-}
-
-#body :link { color: #069; }
-
-#body :link, #body :visited {
- font-weight: bold;
- text-decoration: none;
-}
-
-#body :link:hover, #body :visited:hover {
- text-decoration: underline;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/footer.css b/rt/html/NoAuth/css/3.5-default/footer.css
deleted file mode 100644
index fd8c8f3..0000000
--- a/rt/html/NoAuth/css/3.5-default/footer.css
+++ /dev/null
@@ -1,91 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#footer {
- clear: both;
- font-size: 0.8em;
- margin-top: 5em;
- padding-bottom: 3em;
-}
-
-#footer p { float: left; }
-
-#footer #time {
- color: white;
- background: #069 url(<%$RT::WebImagesURL%>/css/ctr-b2g.gif) no-repeat top right;
- padding: 0.2em 0 0.3em 0;
- margin: 0;
- position: relative;
- z-index: 2;
-}
-
-#footer #time span {
- padding: 0.2em 2em 0.3em 3em;
- background: url(<%$RT::WebImagesURL%>/css/cbr-b2g.gif) no-repeat bottom right;
-}
-
-#footer #bpscredits {
- background: #ccc url(<%$RT::WebImagesURL%>/css/ctr-gray.gif) no-repeat top right;
- padding: 0.2em 0 0.3em 0;
- margin: 0;
- position: relative;
- left: -10px;
- z-index: 1;
-}
-
-#footer #bpscredits span {
- padding: 0.2em 2em 0.3em 3em;
- background: url(<%$RT::WebImagesURL%>/css/cbr-gray.gif) no-repeat bottom right;
-}
-
-#footer #legal {
- float: none;
- color: #888;
- padding: 1em 0 0 2em;
- clear: both;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/forms.css b/rt/html/NoAuth/css/3.5-default/forms.css
deleted file mode 100755
index 3b7f2d6..0000000
--- a/rt/html/NoAuth/css/3.5-default/forms.css
+++ /dev/null
@@ -1,136 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-form input.button {
- border: 3px double #069;
- border-top-color: #08c;
- border-left-color: #08c;
- padding: 0.25em;
- background: white;
- font-weight: bold;
- font-size: 1em;
- margin: 0.5em 0.5em 0 0.5em;
-}
-
-form input.button:active {
- border: 3px double #08c;
- border-top-color: #069;
- border-left-color: #069;
-}
-
-form select {
- border: 1px solid #069;
- padding: 1px;
-}
-
-form input.field, form input, form textarea {
- border: 1px solid #069;
- padding: 3px;
-}
-
-form input.checkbox, form input.radio {
- border: none;
- padding: 0;
-}
-
-/* form .entry input, form .value input */
-
-.label, form label, .labeltop {
- font-weight: bold;
-}
-
-.labeltop {
- vertical-align: top;
-}
-
-.submit {
- font-weight: bold;
- color: #a00;
- font-size: 1.1em;
- padding: 0.3em 1.5em 0 1.5em;
- border-top: 1px solid #930;
- margin: 1.5em 0 2.5em 0;
-}
-
-.submit .buttons { float: right; }
-.submit .extra-buttons { float: left; }
-.submit .button { font-size: 0.9em; }
-.submit .submit-clear { display: none; }
-
-.input-row {
- clear: both;
- padding: 0.25em;
-}
-
-%# ComboBox styles... some properties like height and width must be dynamically
-%# set in the JS (at least for now).
-.combobox {
- border: 1px solid #069;
- padding: 4px;
-}
-
-.combobox .combo-button {
- padding: 0 2px 0 2px;
- margin: 0;
- background: ButtonFace;
- color: ButtonText;
- border: 2px outset ButtonHighlight;
- cursor: default;
- font-size: 8pt;
-}
-
-.combobox .combo-text {
- border: none;
- margin: 0;
- padding: 1px;
-}
-
-.combobox .combo-list {
- border: 1px outset;
- z-index: 200;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/header.css b/rt/html/NoAuth/css/3.5-default/header.css
deleted file mode 100644
index 4e0ce6d..0000000
--- a/rt/html/NoAuth/css/3.5-default/header.css
+++ /dev/null
@@ -1,152 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#header {
- clear: both;
- margin: 0 0.75em 0 0.75em;
- padding-top: 1em;
-}
-
-#header h1 {
- margin: 0;
- padding: 0;
- color: #930;
- position: relative;
- font-size: 2em;
- font-weight: bold;
- left: 1.3em;
- top: 0.15em;
- z-index: 3;
- width: 95%;
-}
-
-#header ul {
- margin: 0;
- padding: 0;
- color: #eee;
- float: left;
-}
-
-#header #page-menu {
- position: relative;
- z-index: 2;
- background: #069 url(<%$RT::WebImagesURL%>/css/ct.gif) no-repeat top left;
- min-width: 65%;
-}
-
-%# This is an interesting bit of CSS. expression() is an IE-only extension to
-%# it's CSS implementation. Just in case other browsers might choke on it,
-%# the rule is enclosed in a selector only IE will (wrongly) match to an element.
-%#
-%# The expression() function takes Javascript, and basically what it's doing here
-%# is checking to see if the width of the menu would be greater than 65% of the body
-%# width. If it is, great, leave it alone to automatically resize. If it is not, set
-%# it to 65% of the body width. This amounts to emulating the min-width rule that
-%# compliant browsers understand above.
-* html #header ul#page-menu {
- width: expression(document.body.clientWidth*0.65 < document.getElementById('page-menu').clientWidth ? "auto" : "65%");
- overflow: visible;
-}
-
-#page-menu div {
- position: relative;
- z-index: 3;
-}
-
-#page-menu div { background: url(<%$RT::WebImagesURL%>/css/cb.gif) no-repeat bottom left; }
-#page-menu div div { background: url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-#page-menu div div div {
- background: url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right;
- padding: 0.2em 1em 0.4em 1em;
-}
-
-#page-menu.actions-present div div { background: url(<%$RT::WebImagesURL%>/css/cbr-b2lb.gif) no-repeat bottom right; }
-#page-menu.actions-present div div div { background: url(<%$RT::WebImagesURL%>/css/ctr-b2lb.gif) no-repeat top right; }
-
-#header ul li {
- display: inline;
-}
-
-#header #actions-menu {
- position: relative;
- background: #08c;
-}
-
-#actions-menu div {
- position: relative;
- z-index: 2;
-}
-
-/*#actions-menu div { background: url(<%$RT::WebImagesURL%>/css/cb.gif) no-repeat bottom left; }*/
-#actions-menu div div { background: url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-#actions-menu div div div {
- background: url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right;
- padding: 0.2em 1em 0.4em 1em;
-}
-
-#header :link,
-#header :visited
-{
- color: white;
- text-decoration: none;
-}
-
-#header :link.selected,
-#header :visited.selected,
-#header :link:hover,
-#header :visited:hover
-{
- color: #fc6; /*#ff6;*/
-}
-
-#header :link.selected,
-#header :visited.selected
-{
- font-weight: bold;
- text-decoration: underline;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/login.css b/rt/html/NoAuth/css/3.5-default/login.css
deleted file mode 100644
index 5aec376..0000000
--- a/rt/html/NoAuth/css/3.5-default/login.css
+++ /dev/null
@@ -1,85 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#login-box {
- width: 50%;
- margin: 0 auto 4em auto;
-}
-
-%# More rules only IE will recognize (but are still valid!) to correct for
-%# IE's incorrect handling of auto margins and the W3C defined behavior.
-%# text-align will affect a block element in IE, therefore centering it, like
-%# left and right auto margins *should*
-* html #login-box {
- text-align: center;
-}
-
-%# ... and align the text back the way it should be
-* html #login-box .titlebox {
- text-align: left;
-}
-
-#login-box .input-row {
- padding: 0.5em;
-}
-
-#login-box .input-row .label {
- width: 8em;
- float: left;
- text-align: right;
- padding: 0.2em 1em 0 0;
-}
-
-#login-box .input-row .input {
- float: left;
-}
-
-#login-box .button-row {
- clear: both;
- padding: 0.5em;
- float: right;
-}
diff --git a/rt/html/NoAuth/css/3.5-default/logo.css b/rt/html/NoAuth/css/3.5-default/logo.css
deleted file mode 100644
index d2a1737..0000000
--- a/rt/html/NoAuth/css/3.5-default/logo.css
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#logo {
- float: left;
- clear: left;
-
- margin: 0.5em 0 0.5em 10px;
-}
-
-#logo img { border: none; }
-#logo div.rtname {
- text-align: center;
- font-weight: bold;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/main.css b/rt/html/NoAuth/css/3.5-default/main.css
deleted file mode 100644
index 3a5fdfb..0000000
--- a/rt/html/NoAuth/css/3.5-default/main.css
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-@import "misc.css";
-@import "login.css";
-@import "logo.css";
-@import "quickbar.css";
-@import "body.css";
-@import "approvals.css";
-@import "titlebox.css";
-@import "forms.css";
-@import "ticket.css";
-@import "transactions.css";
-@import "nav.css";
-@import "header.css";
-@import "footer.css";
-
diff --git a/rt/html/NoAuth/css/3.5-default/misc.css b/rt/html/NoAuth/css/3.5-default/misc.css
deleted file mode 100755
index 038e65d..0000000
--- a/rt/html/NoAuth/css/3.5-default/misc.css
+++ /dev/null
@@ -1,91 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-body {
- font-family: Verdana, sans-serif;
- font-size: 76%;
- margin: 0;
- background-color: white;
-}
-
-.hide, .hidden { display: none !important; }
-
-#body.calpopup {
- margin-left: 2em;
-}
-
-.calendar {
- text-align: center;
- margin: 2em 0 0 0;
-}
-
-.calendar td, .calendar th { padding: 0.1em 0.25em 0.1em 0.25em; }
-
-.calendar caption .month {
- padding: 0 1em 0 1em;
- font-size: 1.5em;
-}
-
-.evenline { background-color: white; }
-.oddline { background-color: #ddd; }
-
-td {
- padding: 0.1em 0.5em 0.1em 0.5em;
-}
-
-.clear { clear: both; }
-
-ul.action-results {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-#comp-Search-Build .titlebox-content {
- padding-left: 0.7em;
- padding-right: 0.3em;
-}
-
diff --git a/rt/html/NoAuth/css/3.5-default/nav.css b/rt/html/NoAuth/css/3.5-default/nav.css
deleted file mode 100644
index d63628d..0000000
--- a/rt/html/NoAuth/css/3.5-default/nav.css
+++ /dev/null
@@ -1,163 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#nav {
- clear: both;
- font-size: 1.1em;
-}
-
-#nav #system-menu,
-#nav ul
-{
- min-width: 85%;
-}
-
-%# This is an interesting bit of CSS. expression() is an IE-only extension to
-%# it's CSS implementation. Just in case other browsers might choke on it,
-%# the rule is enclosed in a selector only IE will (wrongly) match to an element.
-%#
-%# The expression() function takes Javascript, and basically what it's doing here
-%# is checking to see if the width of the menu would be greater than 85% of the body
-%# width. If it is, great, leave it alone to automatically resize. If it is not, set
-%# it to 85% of the body width. This amounts to emulating the min-width rule that
-%# compliant browsers understand above.
-* html #nav #system-menu {
- width: expression(document.body.clientWidth*0.85 < document.getElementById('page-menu').clientWidth ? "auto" : "85%");
- overflow: visible;
-}
-
-#nav ul {
- float: left;
- clear: left;
-
- color: #eee;
- font-weight: bold;
-
- margin: 0;
- padding: 0;
-
- list-style: none;
-}
-
-#nav li ul {
- margin-top: 0.75em;
-}
-
-/*
-%# We need the extra padding above for browsers where we display the arrows
-%# but those don't work in IE so we don't want as much spacing
-%#
-%# IE wrongly matches the selector below even though there isn't an element
-%# above <html> in the doc tree
-*/
-* html #nav li ul {
- margin-top: 0.25em;
-}
-
-#nav li {
- display: inline;
- margin-bottom: 1em;
- padding: 0.2em 0 0.4em 0;
-}
-
-#nav li.first { padding-left: 1em; }
-
-#nav ul div div.wrapper {
- text-align: left;
- padding: 0.2em 1em 0.4em 0;
-}
-
-/****/
-
-#nav :link,
-#nav :visited
-{
- color: #ececec;
- text-decoration: none;
-}
-
-#nav :link.selected,
-#nav :visited.selected,
-#nav :link:hover,
-#nav :visited:hover
-{
- color: #fc6; /*#ff6;*/
-}
-
-#nav :link.selected,
-#nav :visited.selected
-{
- text-decoration: underline;
-}
-
-html>body #nav :link.selected,
-html>body #nav :visited.selected
-{
- padding-bottom: 0.8em;
- background: transparent url(<%$RT::WebImagesURL%>/css/dark-arrow.png) no-repeat bottom center;
-}
-
-html>body #nav :link.selected.odd,
-html>body #nav :visited.selected.odd
-{
- padding-bottom: 0.8em;
- background: transparent url(<%$RT::WebImagesURL%>/css/light-arrow.png) no-repeat bottom center;
-}
-
-/*
-#nav ul { background: #069 url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right; }
-#nav ul div { background: transparent url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-#nav ul.odd { background: #08c url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right; }
-#nav ul.odd div { background: transparent url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-*/
-
-
-#nav ul div.wrapper { background: transparent url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right; }
-#nav ul div { background: #069 url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-#nav ul.odd div.wrapper { background: transparent url(<%$RT::WebImagesURL%>/css/ctr.gif) no-repeat top right; }
-#nav ul div.odd { background: #08c url(<%$RT::WebImagesURL%>/css/cbr.gif) no-repeat bottom right; }
-
diff --git a/rt/html/NoAuth/css/3.5-default/quickbar.css b/rt/html/NoAuth/css/3.5-default/quickbar.css
deleted file mode 100644
index 3637695..0000000
--- a/rt/html/NoAuth/css/3.5-default/quickbar.css
+++ /dev/null
@@ -1,98 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-#quickbar #quick-personal {
- display: inline;
- color: #888;
- padding: 0.5em 1em 0 0;
- float: right;
-}
-
-#quickbar #quick-personal span {
- font-weight: bold;
-}
-
-#quickbar #quick-personal :link,
-#quickbar #quick-personal :visited
-{
- color: #888;
- font-weight: bold;
-}
-
-#quickbar #quick-personal :link:hover,
-#quickbar #quick-personal :visited:hover
-{
- color: black;
-}
-
-#quickbar #topactions {
- float: right;
- clear: right;
-
- font-size: 0.9em;
- padding: 1em;
-}
-
-#quickbar #topactions form {
- display: inline;
- margin-left: 1em;
-}
-
-#quickbar #topactions form .button {
- padding: 0 2px 0 2px;
- font-size: 1em;
- margin: 0;
-}
-
-#quickbar #topactions form .field {
- padding: 1px;
- font-size: 0.9em;
-}
-
-#quickbar #topactions form input.field {
- padding: 3px;
-}
diff --git a/rt/html/NoAuth/css/3.5-default/ticket.css b/rt/html/NoAuth/css/3.5-default/ticket.css
deleted file mode 100644
index 7fa0e9e..0000000
--- a/rt/html/NoAuth/css/3.5-default/ticket.css
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.ticket-info-cfs .label {
- vertical-align: top;
-}
-
-.ticket-info-cfs ul {
- margin: 0;
- padding: 0;
- margin-left: 0.5em;
- list-style: none;
-}
diff --git a/rt/html/NoAuth/css/3.5-default/titlebox.css b/rt/html/NoAuth/css/3.5-default/titlebox.css
deleted file mode 100644
index 3bd4e97..0000000
--- a/rt/html/NoAuth/css/3.5-default/titlebox.css
+++ /dev/null
@@ -1,179 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.titlebox {
- margin-bottom: 1em;
-}
-
-.titlebox .titlebox-content {
- margin-top: -1px;
- padding: 1em 2em 0.5em 2em;
- margin: 0;
- /*margin: 1em 2em 0.5em 2em;*/
-}
-
-.titlebox th { font-size: 0.8em; }
-
-%# TRS: I wish there was a more elegant way to do this... I essentially need to
-%# select all elements X that do NOT have element Y as a descendant... which I can
-%# fake with the child selector of CSS2, but IE is stupid and does not support that.
-
-% for (qw(index
-% Search-Build
-% User-Prefs
-% Approvals
-% Admin-Users-Modify
-% SelfService
-% SelfService-Closed
-% ))
-% {
-#comp-<%$_%> .titlebox .titlebox-content,
-% }
-.titlebox .titlebox .titlebox-content
-{
- background: #eee;
- border-bottom: 1px solid #ccc;
- border-right: 1px solid #ccc;
- border-left: 0.5em solid #069;
-}
-
-#login-box .titlebox .titlebox-content
-{
- background: none;
- border: none;
-}
-
-.titlebox .titlebox-title {
- position: relative;
- font-weight: bold;
- color: #930;
- font-size: 1.2em;
- padding: 0.2em 0 0.2em 4em;
- border-bottom: 1px solid #069;
-}
-
-.titlebox .titlebox-title .right {
- position: absolute;
- top: 0.5em;
- right: 1.5em;
- font-size: 0.9em;
- color: #888;
-}
-
-.titlebox .titlebox-title .right .selected { color: #930; }
-
-#body .titlebox .titlebox-title .right :link,
-#body .titlebox .titlebox-title .right :visited {
- color: #888;
-}
-
-#body .titlebox .titlebox-title .right :link:hover,
-#body .titlebox .titlebox-title .right :visited:hover {
- color: #930;
-}
-
-.titlebox .titlebox-title .widget a {
- display: block;
- padding-top: 1em;
- width: 20px;
-
- background: url(<%$RT::WebImagesURL%>/css/rollup-arrow.gif) no-repeat center center;
-
- margin: 0;
- text-indent: -9999px;
-
- position: absolute;
- top: 0.4em;
- left: 0.75em;
- float: left;
-
-%# Basically IE5 will see those crazy backslashes and prematurely end the rule.
-%# This allows values for IE 5's broken box model to be set before the hack and
-%# the real values to be set after. We also set voice-family back to whatever it
-%# would have been on the off chance it's actually used.
- /* WIN IE5 hack */
- height: 7px;
- voice-family: "\"}\"";
- voice-family: inherit;
- height: 0;
- overflow: hidden;
-}
-
-%# IE also doesn't support the child selector ">", so we can use it to set values
-%# that only other browsers will see (in this case, playing nice with Opera, which
-%# also suffers from the backslash hack above.)
-html>body .titlebox .titlebox-title .widget a {
- height: 0;
- overflow: hidden;
-}
-
-%# Compensates for IE's bad box model by hiding this rule from other browsers
-* html .titlebox .titlebox-title .widget a {
- background-position: center 0.3em;
- left: -3.5em;
-}
-
-.titlebox.rolled-up .titlebox-title .widget a {
- background-image: url(<%$RT::WebImagesURL%>/css/rolldown-arrow.gif);
-}
-
-#body .titlebox .titlebox-title :link,
-#body .titlebox .titlebox-title :visited
-{
- color: #930;
- text-decoration: none;
-}
-
-#body .titlebox .titlebox-title :link:hover,
-#body .titlebox .titlebox-title :visited:hover
-{
- text-decoration: underline;
-}
-
-.titlebox .titlebox-content hr.clear {
- visibility: hidden;
-}
diff --git a/rt/html/NoAuth/css/3.5-default/transactions.css b/rt/html/NoAuth/css/3.5-default/transactions.css
deleted file mode 100755
index fdf8ea8..0000000
--- a/rt/html/NoAuth/css/3.5-default/transactions.css
+++ /dev/null
@@ -1,146 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.ticket-transaction {
- border-bottom: 1px solid #ddd;
- border-right: 3px solid #069;
-}
-
-#ticket-history .ticket-transaction {
- border-bottom-color: #ccc;
-}
-
-.ticket-transaction.even {
- background: #eee;
-}
-
-.ticket-transaction .date {
- font-size: 0.9em;
- width: 10em;
-}
-
-.ticket-transaction .description {
- font-weight: bold;
- font-size: 0.9em;
- text-align: left;
-}
-
-.ticket-transaction .actions {
- text-align: right;
- font-weight: bold;
-}
-
-.ticket-transaction .type {
- background: #888;
- width: 1.2em;
- color: white;
- text-align: center;
- font-size: 1em;
-}
-
-#body .ticket-transaction .type :link,
-#body .ticket-transaction .type :visited
-{
- color: white;
- font-weight: normal;
-}
-
-.ticket-transaction.basics { border-color: #b32; }
-.ticket-transaction.basics .type { background: #b32; }
-.ticket-summary .ticket-info-basics .titlebox-content { border-left: 0.5em solid #b32; }
-
-.ticket-transaction.people { border-color: #48c; }
-.ticket-transaction.people .type { background: #48c; }
-.ticket-summary .ticket-info-people .titlebox-content { border-left: 0.5em solid #48c; }
-
-%# light green - #ad8
-.ticket-transaction.links { border-color: #316531; }
-.ticket-transaction.links .type { background: #316531; }
-.ticket-summary .ticket-info-links .titlebox-content { border-left: 0.5em solid #316531; }
-
-%# orange - #d71
-.ticket-transaction.dates { border-color: #633063; }
-.ticket-transaction.dates .type { background: #633063; }
-.ticket-summary .ticket-info-dates .titlebox-content { border-left: 0.5em solid #633063; }
-
-.ticket-transaction.message { border-color: #069; }
-.ticket-transaction.message .type { background: #069; }
-
-.ticket-transaction.other { border-color: #888; }
-
-.ticket-transaction td .message-header-value {
- padding: 0;
-}
-
-.ticket-transaction td .message-header-key {
- padding: 0 1em 0 1.5em;
- font-weight: bold;
-}
-
-.ticket-transaction .downloadattachment {
- float: right;
- font-size: 0.9em;
- text-align: right;
-}
-
-.ticket-transaction .messagebody {
- clear: both;
- padding-left: 3em;
- padding-bottom: 1em;
-}
-
-%# Message stanza colors
-.message-stanza-depth-0 { color: #000; }
-.message-stanza-depth-1 { color: #600; }
-.message-stanza-depth-2 { color: #060; }
-.message-stanza-depth-3 { color: #006; }
-.message-stanza-depth-4 { color: #c00; }
-.message-stanza-depth-5 { color: #0c0; }
-.message-stanza-depth-6 { color: #00c; }
-.message-stanza-depth-7 { color: #f00; }
-.message-stanza-depth-8 { color: #0f0; }
-.message-stanza-depth-9 { color: #00f; }
diff --git a/rt/html/NoAuth/css/autohandler b/rt/html/NoAuth/css/autohandler
deleted file mode 100644
index a4eda4e..0000000
--- a/rt/html/NoAuth/css/autohandler
+++ /dev/null
@@ -1,53 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-RT::Interface::Web::StaticFileHeaders();
-$r->content_type('text/css');
-$m->call_next();
-return();
-</%init>
diff --git a/rt/html/NoAuth/css/dhandler b/rt/html/NoAuth/css/dhandler
deleted file mode 100644
index 6f1f5e9..0000000
--- a/rt/html/NoAuth/css/dhandler
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ONCE>
-my $squisher;
-</%ONCE>
-<%INIT>
-my $arg = $m->dhandler_arg;
-my $path;
-if ( $arg =~ m{^(.*)-squished(\.[^\.]+)$} ) {
- $path = $m->current_comp->dir_path .'/'. $1 . $2;
-}
-else {
- return $m->decline;
-}
-
-$squisher = new RT::CSS::Squish unless $squisher;
-$squisher->{'mason'} = $m;
-
-$m->out( $squisher->concatenate( $path ) );
-
-package RT::CSS::Squish;
-use CSS::Squish '0.06';
-use base qw(CSS::Squish);
-sub file_handle {
- my $self = shift;
- my $file = shift;
- my $content = $self->{'mason'}->scomp($file);
- open my $fh, '<', \$content or die "$!";
- return $fh;
-}
-
-</%INIT>
diff --git a/rt/html/NoAuth/css/print.css b/rt/html/NoAuth/css/print.css
deleted file mode 100644
index 80a0c78..0000000
--- a/rt/html/NoAuth/css/print.css
+++ /dev/null
@@ -1,85 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-* {
- float: none;
- position: static;
-}
-
-body {
- margin: 1em;
- font-size: 10pt;
-}
-
-#body {
- margin: 0;
-}
-
-#header h1 {
- margin-bottom: 2em;
-}
-
-#header {
- padding: 0 !important;
-}
-
-#quickbar,
-#nav,
-#header #page-menu,
-#header #actions-menu,
-.titlebox .title .widget,
-#footer
-{
-display: none;
-}
-
-a:link, a:visited {
- background: transparent;
- font-weight: bold !important;
- text-decoration: underline !important;
-}
-
diff --git a/rt/html/NoAuth/images/autohandler b/rt/html/NoAuth/images/autohandler
deleted file mode 100644
index 7209798..0000000
--- a/rt/html/NoAuth/images/autohandler
+++ /dev/null
@@ -1,28 +0,0 @@
-<%INIT>
-&RT::Interface::Web::StaticFileHeaders();
-
-# This autohandler will spit out RT's images if the user hasn't
-# properly configured their webserver to stop RT from passing
-# images through the mason handler.
-my $file = $m->base_comp->source_file;
-
-
-my $type = "application/octet-stream";
-if ($file =~ /\.(gif|png|jpe?g)$/i) {
- $type = "image/$1";
- $type =~ s/jpg/jpeg/gi;
-}
-
-die "file not found" unless -f $file && -r _;
-
-$r->content_type($type);
-open my $fh, "<$file" or die "couldn't open file: $!";
-binmode($fh);
-{
- local $/ = \16384;
- $m->out($_) while (<$fh>);
- $m->flush_buffer;
-}
-close $fh;
-$m->abort;
-</%INIT>
diff --git a/rt/html/NoAuth/images/back_home.gif b/rt/html/NoAuth/images/back_home.gif
deleted file mode 100644
index 40b19c1..0000000
--- a/rt/html/NoAuth/images/back_home.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/bplogo.gif b/rt/html/NoAuth/images/bplogo.gif
deleted file mode 100644
index 1bb0adf..0000000
--- a/rt/html/NoAuth/images/bplogo.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cb-light.gif b/rt/html/NoAuth/images/css/cb-light.gif
deleted file mode 100644
index d5e3059..0000000
--- a/rt/html/NoAuth/images/css/cb-light.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cb.gif b/rt/html/NoAuth/images/css/cb.gif
deleted file mode 100644
index 53bb2ae..0000000
--- a/rt/html/NoAuth/images/css/cb.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cbr-b2g.gif b/rt/html/NoAuth/images/css/cbr-b2g.gif
deleted file mode 100644
index 6bca03d..0000000
--- a/rt/html/NoAuth/images/css/cbr-b2g.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cbr-b2lb.gif b/rt/html/NoAuth/images/css/cbr-b2lb.gif
deleted file mode 100644
index d207f84..0000000
--- a/rt/html/NoAuth/images/css/cbr-b2lb.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cbr-gray.gif b/rt/html/NoAuth/images/css/cbr-gray.gif
deleted file mode 100644
index d732710..0000000
--- a/rt/html/NoAuth/images/css/cbr-gray.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cbr-trans.gif b/rt/html/NoAuth/images/css/cbr-trans.gif
deleted file mode 100644
index dc272ee..0000000
--- a/rt/html/NoAuth/images/css/cbr-trans.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/cbr.gif b/rt/html/NoAuth/images/css/cbr.gif
deleted file mode 100644
index 754cee1..0000000
--- a/rt/html/NoAuth/images/css/cbr.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ct-light.gif b/rt/html/NoAuth/images/css/ct-light.gif
deleted file mode 100644
index 55125b0..0000000
--- a/rt/html/NoAuth/images/css/ct-light.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ct.gif b/rt/html/NoAuth/images/css/ct.gif
deleted file mode 100644
index d16a5c5..0000000
--- a/rt/html/NoAuth/images/css/ct.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ctr-b2g.gif b/rt/html/NoAuth/images/css/ctr-b2g.gif
deleted file mode 100644
index 540e6d0..0000000
--- a/rt/html/NoAuth/images/css/ctr-b2g.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ctr-b2lb.gif b/rt/html/NoAuth/images/css/ctr-b2lb.gif
deleted file mode 100644
index c98b18c..0000000
--- a/rt/html/NoAuth/images/css/ctr-b2lb.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ctr-gray.gif b/rt/html/NoAuth/images/css/ctr-gray.gif
deleted file mode 100644
index 8d5e5dd..0000000
--- a/rt/html/NoAuth/images/css/ctr-gray.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ctr-trans.gif b/rt/html/NoAuth/images/css/ctr-trans.gif
deleted file mode 100644
index bb316cf..0000000
--- a/rt/html/NoAuth/images/css/ctr-trans.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/ctr.gif b/rt/html/NoAuth/images/css/ctr.gif
deleted file mode 100644
index 9754e15..0000000
--- a/rt/html/NoAuth/images/css/ctr.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/dark-arrow-up.png b/rt/html/NoAuth/images/css/dark-arrow-up.png
deleted file mode 100644
index 443096a..0000000
--- a/rt/html/NoAuth/images/css/dark-arrow-up.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/dark-arrow.png b/rt/html/NoAuth/images/css/dark-arrow.png
deleted file mode 100644
index a83500a..0000000
--- a/rt/html/NoAuth/images/css/dark-arrow.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/fieldbg-autocomplete.gif b/rt/html/NoAuth/images/css/fieldbg-autocomplete.gif
deleted file mode 100644
index aa7eed0..0000000
--- a/rt/html/NoAuth/images/css/fieldbg-autocomplete.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/light-arrow-up.png b/rt/html/NoAuth/images/css/light-arrow-up.png
deleted file mode 100644
index c209d43..0000000
--- a/rt/html/NoAuth/images/css/light-arrow-up.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/light-arrow.png b/rt/html/NoAuth/images/css/light-arrow.png
deleted file mode 100644
index 575d4e5..0000000
--- a/rt/html/NoAuth/images/css/light-arrow.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/rolldown-arrow.gif b/rt/html/NoAuth/images/css/rolldown-arrow.gif
deleted file mode 100644
index 3c296dc..0000000
--- a/rt/html/NoAuth/images/css/rolldown-arrow.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/rolldown-arrow.png b/rt/html/NoAuth/images/css/rolldown-arrow.png
deleted file mode 100644
index 33d8ab1..0000000
--- a/rt/html/NoAuth/images/css/rolldown-arrow.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/css/rollup-arrow.gif b/rt/html/NoAuth/images/css/rollup-arrow.gif
deleted file mode 100644
index f009ff4..0000000
--- a/rt/html/NoAuth/images/css/rollup-arrow.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/favicon.png b/rt/html/NoAuth/images/favicon.png
deleted file mode 100644
index ed1ee37..0000000
--- a/rt/html/NoAuth/images/favicon.png
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/head_requestracker.gif b/rt/html/NoAuth/images/head_requestracker.gif
deleted file mode 100644
index 73315e9..0000000
--- a/rt/html/NoAuth/images/head_requestracker.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/rt.jpg b/rt/html/NoAuth/images/rt.jpg
deleted file mode 100644
index a137a93..0000000
--- a/rt/html/NoAuth/images/rt.jpg
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/space.gif b/rt/html/NoAuth/images/space.gif
deleted file mode 100644
index 1d11fa9..0000000
--- a/rt/html/NoAuth/images/space.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/images/squares_blue.gif b/rt/html/NoAuth/images/squares_blue.gif
deleted file mode 100644
index a28da5c..0000000
--- a/rt/html/NoAuth/images/squares_blue.gif
+++ /dev/null
Binary files differ
diff --git a/rt/html/NoAuth/js/ahah.js b/rt/html/NoAuth/js/ahah.js
deleted file mode 100644
index e54a2c6..0000000
--- a/rt/html/NoAuth/js/ahah.js
+++ /dev/null
@@ -1,80 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-/*
-% $r->content_type('application/x-javascript');
-*/
-// Fetched from http://www.opendarwin.org/~drernie/src/ahah.js
-function ahah(url, target, delay) {
- // document.getElementById(target).innerHTML = 'Loading <a href="'+url+'">'+url +'</a>...';
- if (window.XMLHttpRequest) {
- req = new XMLHttpRequest();
- } else if (window.ActiveXObject) {
- req = new ActiveXObject("Microsoft.XMLHTTP");
- }
- if (req != undefined) {
- req.onreadystatechange = function() {ahahDone(url, target, delay);};
- req.open("GET", url, true);
- req.send("");
- }
-}
-
-function ahahDone(url, target, delay) {
- if (req.readyState == 4) { // only if req is "loaded"
- if (req.status == 200) { // only if "OK"
- document.getElementById(target).innerHTML = req.responseText;
- } else {
- document.getElementById(target).innerHTML="Error loading '"+url+"':\n"+req.statusText;
- }
- if (delay != undefined) {
- setTimeout("ahah(url,target,delay)", delay); // resubmit after delay
- //server should ALSO delay before responding
- }
- }
-}
-
-% $m->abort();
diff --git a/rt/html/NoAuth/js/autohandler b/rt/html/NoAuth/js/autohandler
deleted file mode 100644
index 8fab38d..0000000
--- a/rt/html/NoAuth/js/autohandler
+++ /dev/null
@@ -1,53 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-&RT::Interface::Web::StaticFileHeaders();
-$r->content_type('application/x-javascript');
-$m->call_next();
-return();
-</%init>
diff --git a/rt/html/NoAuth/js/cascaded.js b/rt/html/NoAuth/js/cascaded.js
deleted file mode 100644
index 34b99b5..0000000
--- a/rt/html/NoAuth/js/cascaded.js
+++ /dev/null
@@ -1,66 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-function filter_cascade (id, val) {
- var select = document.getElementById(id);
- if (!select) { return };
- var i;
- var children = select.childNodes;
- for (i in children) {
- var style = children[i].style;
- if (!style) { continue };
- if (val == '') {
- style.display = 'block';
- continue;
- }
- if (children[i].label.substr(0, val.length) == val) {
- style.display = 'block';
- continue;
- }
- style.display = 'none';
- }
-}
diff --git a/rt/html/NoAuth/js/class.js b/rt/html/NoAuth/js/class.js
deleted file mode 100644
index ee8e30c..0000000
--- a/rt/html/NoAuth/js/class.js
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-/* by TKirby, released under GPL */
-
- function _ClassSetup(Object) {
- this.prototype = Object;
- return this;
- }
-
- function Class(name) {
- var _newclass_;
- eval("window."+name+" = new Function('this."+name+".apply(this,arguments);');");
- eval("window."+name+".define = _ClassSetup;");
- eval("_newclass_ = window."+name+";");
- return _newclass_;
- }
-
diff --git a/rt/html/NoAuth/js/combobox.js b/rt/html/NoAuth/js/combobox.js
deleted file mode 100644
index 443dd9d..0000000
--- a/rt/html/NoAuth/js/combobox.js
+++ /dev/null
@@ -1,265 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-function ComboBox_InitWith(n) {
- if ( typeof( window.addEventListener ) != "undefined" ) {
- window.addEventListener("load", ComboBox_Init(n), false);
- } else if ( typeof( window.attachEvent ) != "undefined" ) {
- window.attachEvent("onload", ComboBox_Init(n));
- } else {
- ComboBox_Init(n)();
- }
-}
-function ComboBox_Init(n) {
- return function () {
- if ( ComboBox_UplevelBrowser( n ) ) {
- ComboBox_Load( n );
- }
- }
-}
-function ComboBox_UplevelBrowser( n ) {
- if( typeof( document.getElementById ) == "undefined" ) return false;
- var combo = document.getElementById( n + "_Container" );
- if( combo == null || typeof( combo ) == "undefined" ) return false;
- if( typeof( combo.style ) == "undefined" ) return false;
- if( typeof( combo.innerHTML ) == "undefined" ) return false;
- return true;
-}
-function ComboBox_Load( comboId ) {
- var combo = document.getElementById( comboId + "_Container" );
- var button = document.getElementById( comboId + "_Button" );
- var list = document.getElementById( comboId + "_List" );
- var text = document.getElementById( comboId );
-
-
- combo.List = list;
- combo.Button = button;
- combo.Text = text;
-
- button.Container = combo;
- button.Toggle = ComboBox_ToggleList;
- button.onclick = button.Toggle;
- button.onmouseover = function(e) { this.Container.List.DisableBlur(e); };
- button.onmouseout = function(e) { this.Container.List.EnableBlur(e); };
- button.innerHTML = "\u25BC";
- button.onselectstart = function(e){ return false; };
- button.style.height = ( list.offsetHeight - 4 ) + "px";
-
- text.Container = combo;
- text.TypeDown = ComboBox_TextTypeDown;
- text.KeyAccess = ComboBox_TextKeyAccess;
- text.onkeyup = function(e) { this.KeyAccess(e); this.TypeDown(e); };
- text.style.width = ( list.offsetWidth ) + "px";
-
- list.Container = combo;
- list.Show = ComboBox_ShowList;
- list.Hide = ComboBox_HideList;
- list.EnableBlur = ComboBox_ListEnableBlur;
- list.DisableBlur = ComboBox_ListDisableBlur;
- list.Select = ComboBox_ListItemSelect;
- list.ClearSelection = ComboBox_ListClearSelection;
- list.KeyAccess = ComboBox_ListKeyAccess;
- list.FireTextChange = ComboBox_ListFireTextChange;
- list.onchange = null;
- list.onclick = function(e){ this.Select(e); this.ClearSelection(); this.FireTextChange(); };
- list.onkeyup = function(e) { this.KeyAccess(e); };
- list.EnableBlur(null);
- list.style.position = "absolute";
- list.size = ComboBox_GetListSize( list );
- list.IsShowing = true;
- list.Hide();
-
-}
-function ComboBox_InitEvent( e ) {
- if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event;
- if( e == null ) e = new Object();
- return e;
-}
-function ComboBox_ListClearSelection() {
- if ( typeof( this.Container.Text.createTextRange ) == "undefined" ) return;
- var rNew = this.Container.Text.createTextRange();
- rNew.moveStart('character', this.Container.Text.value.length) ;
- rNew.select();
-}
-function ComboBox_GetListSize( theList ) {
- ComboBox_EnsureListSize( theList );
- return theList.listSize;
-}
-function ComboBox_EnsureListSize( theList ) {
- if ( typeof( theList.listSize ) == "undefined" ) {
- if( typeof( theList.getAttribute ) != "undefined" ) {
- if( theList.getAttribute( "size" ) != null && theList.getAttribute( "size" ) != "" ) {
- theList.listSize = theList.getAttribute( "size" );
- return;
- }
- }
- if( theList.options.length > 0 ) {
- theList.listSize = theList.options.length;
- return;
- }
- theList.listSize = 4;
- }
-}
-function ComboBox_ListKeyAccess(e) { //Make enter/space and escape do the right thing :)
- e = ComboBox_InitEvent( e );
- if( e.keyCode == 13 || e.keyCode == 32 ) {
- this.Select();
- return;
- }
- if( e.keyCode == 27 ) {
- this.Hide();
- this.Container.Text.focus();
- return;
- }
-}
-function ComboBox_TextKeyAccess(e) { //Make alt+arrow expand the list
- e = ComboBox_InitEvent( e );
- if( e.altKey && (e.keyCode == 38 || e.keyCode == 40) ) {
- this.Container.List.Show();
- }
-}
-function ComboBox_TextTypeDown(e) { //Make the textbox do a type-down on the list
- e = ComboBox_InitEvent( e );
- var items = this.Container.List.options;
- if( this.value == "" ) return;
- var ctrlKeys = Array( 8, 46, 37, 38, 39, 40, 33, 34, 35, 36, 45, 16, 20 );
- for( var i = 0; i < ctrlKeys.length; i++ ) {
- if( e.keyCode == ctrlKeys[i] ) return;
- }
- for( var i = 0; i < items.length; i++ ) {
- var item = items[i];
- if( item.text.toLowerCase().indexOf( this.value.toLowerCase() ) == 0 ) {
- this.Container.List.selectedIndex = i;
- if ( typeof( this.Container.Text.createTextRange ) != "undefined" ) {
- this.Container.List.Select();
- }
- break;
- }
- }
-}
-function ComboBox_ListFireTextChange() {
- var textOnChange = this.Container.Text.onchange;
- if ( textOnChange != null && typeof(textOnChange) == "function" ) {
- textOnChange();
- }
-}
-function ComboBox_ListEnableBlur(e) {
- this.onblur = this.Hide;
-}
-function ComboBox_ListDisableBlur(e) {
- this.onblur = null;
-}
-function ComboBox_ListItemSelect(e) {
- if( this.options.length > 0 ) {
- var text = this.Container.Text;
- var oldValue = text.value;
- var newValue = this.options[ this.selectedIndex ].text;
- text.value = newValue;
- if ( typeof( text.createTextRange ) != "undefined" ) {
- if (newValue != oldValue) {
- var rNew = text.createTextRange();
- rNew.moveStart('character', oldValue.length) ;
- rNew.select();
- }
- }
- }
- this.Hide();
- this.Container.Text.focus();
-}
-function ComboBox_ToggleList(e) {
- if( this.Container.List.IsShowing == true ) {
- this.Container.List.Hide();
- } else {
- this.Container.List.Show();
- }
-}
-function ComboBox_ShowList(e) {
- if ( !this.IsShowing && !this.disabled ) {
- this.style.width = ( this.Container.offsetWidth ) + "px";
- this.style.top = ( this.Container.offsetHeight + ComboBox_RecursiveOffsetTop(this.Container,true) ) + "px";
- this.style.left = ( ComboBox_RecursiveOffsetLeft(this.Container,true) + 1 ) + "px";
- ComboBox_SetVisibility(this,true);
- this.focus();
- this.IsShowing = true;
- }
-}
-function ComboBox_HideList(e) {
- if( this.IsShowing ) {
- ComboBox_SetVisibility(this,false);
- this.IsShowing = false;
- }
-}
-function ComboBox_SetVisibility(theList, isVisible) {
- setVisibility(theList, isVisible);
-}
-function ComboBox_RecursiveOffsetTop(thisObject,isFirst) {
- if(thisObject.offsetParent) {
- if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
- return 0;
- }
- return (thisObject.offsetTop + ComboBox_RecursiveOffsetTop(thisObject.offsetParent,false));
- } else {
- return thisObject.offsetTop;
- }
-}
-function ComboBox_RecursiveOffsetLeft(thisObject,isFirst) {
- if(thisObject.offsetParent) {
- if ( thisObject.style.position == "absolute" && !isFirst && typeof(document.designMode) != "undefined" ) {
- return 0;
- }
- return (thisObject.offsetLeft + ComboBox_RecursiveOffsetLeft(thisObject.offsetParent,false));
- } else {
- return thisObject.offsetLeft;
- }
-}
-function ComboBox_SimpleAttach(selectElement,textElement) {
- textElement.value = selectElement.options[ selectElement.options.selectedIndex ].text;
- var textOnChange = textElement.onchange;
- if ( textOnChange != null && typeof( textOnChange ) == "function" ) {
- textOnChange();
- }
-}
diff --git a/rt/html/NoAuth/js/list.js b/rt/html/NoAuth/js/list.js
deleted file mode 100644
index 85a2ec2..0000000
--- a/rt/html/NoAuth/js/list.js
+++ /dev/null
@@ -1,159 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-/* by TKirby, released under GPL */
-/* Define the "list" Class */
-Class("list").define({
- name : null,
- xml : null,
- sels : null,
- list : function (src, esrc, name) { this.init(src, esrc, name); },
- read : function () {
- var i = 0;
- if(this.xml.readyState!=4) { setTimeout(this.name+".read()", 100); }
- else if(this.xml.status!=200) alert("Document not available.");
- else {
- var doc = this.xml.responseXML;
- var nNode = null;
- if(doc.childNodes[0].nodeName=="parseerror") alert("Parse Error.");
- doc = doc.getElementsByTagName("list")[0];
- for(i=0;i<doc.childNodes.length;i++) {
- if(doc.childNodes[i].childNodes.length>0) {
- nNode = document.createElement("option");
- nNode.appendChild(document.createTextNode(doc.childNodes[i].childNodes[0].nodeValue));
- this.sels[0].appendChild(nNode);
- }
- }
- }
- },
-
- init : function (src,esrc,name) {
- if(!src) return;
- this.name = name;
- this.sels = new Array();
- var i = 0;
- for(i=0;i<src.childNodes.length;i++) {
- if(src.childNodes[i].nodeName=="select" || src.childNodes[i].nodeName=="SELECT") {
- this.sels.push(src.childNodes[i]);
- }
-
- if((src.childNodes[i].nodeName=="input" || src.childNodes[i].nodeName=="INPUT")
- && (src.childNodes[i].name=="fromjs")) {
- src.childNodes[i].value = 1;
- }
-
- if((src.childNodes[i].nodeName=="input" || src.childNodes[i].nodeName=="INPUT")
- && (src.childNodes[i].type=="submit" || src.childNodes[i].type=="SUBMIT")) {
-
- if (src.childNodes[i].name.indexOf("Save") < 0) {
- var tmp = document.createElement("input");
- tmp.type = "button";
- tmp.name = src.childNodes[i].name;
- tmp.value = src.childNodes[i].value;
- src.replaceChild(tmp,src.childNodes[i]);
- }
-
- if(src.childNodes[i].name=="add")
- src.childNodes[i].onclick = new Function(this.name+".add();");
- if(src.childNodes[i].name=="remove")
- src.childNodes[i].onclick = new Function(this.name+".remove();");
- if(src.childNodes[i].name=="moveup")
- src.childNodes[i].onclick = new Function(this.name+".moveup();");
- if(src.childNodes[i].name=="movedown")
- src.childNodes[i].onclick = new Function(this.name+".movedown();");
- }
- }
- if (esrc) {
- this.xml = (window.navigator.appName!="Microsoft Internet Explorer"
- ?new XMLHttpRequest():new ActiveXObject("Microsoft.XMLHTTP"));
- this.xml.open("GET", esrc);
- this.xml.send("");
- setTimeout(this.name+".read()", 100);
- }
- },
-
- add : function() {
- var i, j = 0;
- var dNode = null;
- for(i=0;i<this.sels[0].length;i++) if(this.sels[0][i].selected) {
- for(j=0;j<this.sels[1].length;j++) if(this.sels[1][j].value==this.sels[0][i].value) break;
- if(j==this.sels[1].length) dNode = this.sels[0][i].cloneNode(true),
- this.sels[1].appendChild(dNode);
- }
- },
-
- moveup : function() { this.move(-1); },
- movedown : function() { this.move(1); },
- move : function(v) {
- var i = 0;
- if(v<0) for(i=0;i<this.sels[1].length;i++) this.moveOne(v, i);
- else if(v>0) for(i=this.sels[1].length-1;i>=0;i--)this.moveOne(v, i);
- },
-
- moveOne : function(v, i) {
- var ins = v + i;
- if(ins<0 || ins>=this.sels[1].length) return;
- if(this.sels[1][ins].selected) return;
- if(this.sels[1][i].selected) {
- Node = this.sels[1][i];
- this.sels[1].removeChild(Node);
- this.sels[1].insertBefore(Node, this.sels[1][ins]);
- }
- },
-
- remove : function() {
- var i = 0;
- for(i=this.sels[1].length-1;i>=0;i--) if(this.sels[1][i].selected)
- this.sels[1].removeChild(this.sels[1][i]);
- },
-
- selectAll: function() {
- var i = 0;
- for(i=0;i<this.sels[0].length;i++) this.sels[0][i].selected = false;
- for(i=0;i<this.sels[1].length;i++) this.sels[1][i].selected = true;
- }
-});
diff --git a/rt/html/NoAuth/js/titlebox-state.js b/rt/html/NoAuth/js/titlebox-state.js
deleted file mode 100644
index 024cfd5..0000000
--- a/rt/html/NoAuth/js/titlebox-state.js
+++ /dev/null
@@ -1,83 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-function createCookie(name,value,days) {
- var path = "<%$RT::WebPath%>" ? "<%$RT::WebPath%>" : "/";
-
- if (days) {
- var date = new Date();
- date.setTime(date.getTime()+(days*24*60*60*1000));
- var expires = "; expires="+date.toGMTString();
- }
- else
- expires = "";
-
- document.cookie = name+"="+value+expires+"; path="+path;
-}
-
-function loadTitleBoxStates() {
- var cookies = document.cookie.split(/;\s*/);
- var len = cookies.length;
-
- for (var i = 0; i < len; i++) {
- var c = cookies[i].split('=');
-
- if (c[0].match(/^TitleBox--/)) {
- var e = document.getElementById(c[0]);
- if (e) {
- var e2 = e.parentNode;
-
- if (c[1] != 0) {
- set_rollup_state(e,e2,'shown');
- }
- else {
- set_rollup_state(e,e2,'hidden');
- }
- }
- }
- }
-}
diff --git a/rt/html/NoAuth/js/util.js b/rt/html/NoAuth/js/util.js
deleted file mode 100644
index eac77e1..0000000
--- a/rt/html/NoAuth/js/util.js
+++ /dev/null
@@ -1,250 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-/* $(...)
- Returns DOM node or array of nodes (if more then one argument passed).
- If argument is node object allready then do nothing.
- // Stolen from Prototype
-*/
-function $() {
- var elements = new Array();
-
- for (var i = 0; i < arguments.length; i++) {
- var element = arguments[i];
- if (typeof element == 'string')
- element = document.getElementById(element);
-
- if (arguments.length == 1)
- return element;
-
- elements.push(element);
- }
-
- return elements;
-}
-
-/* Visibility */
-
-function show(id) { delClass( id, 'hidden' ) }
-function hide(id) { addClass( id, 'hidden' ) }
-
-function hideshow(id) { return toggleVisibility( id ) }
-function toggleVisibility(id) {
- var e = $(id);
-
- if ( e.className.match( /\bhidden\b/ ) )
- show(e);
- else
- hide(e);
-
- return false;
-}
-
-function setVisibility(id, visibility) {
- if ( visibility ) show(id);
- else hide(id);
-}
-
-function switchVisibility(id1, id2) {
- // Show both and then hide the one we want
- show(id1);
- show(id2);
- hide(id2);
- return false;
-}
-
-/* Classes */
-
-function addClass(id, value) {
- var e = $(id);
- if ( e.className.match( new RegExp('\b'+ value +'\b') ) )
- return;
- e.className += e.className? ' '+value : value;
-}
-
-function delClass(id, value) {
- var e = $(id);
- e.className = e.className.replace( new RegExp('\\s?\\b'+ value +'\\b', 'g'), '' );
-}
-
-/* Rollups */
-
-function rollup(id) {
- var e = $(id);
- var e2 = e.parentNode;
-
- if (e.className.match(/\bhidden\b/)) {
- set_rollup_state(e,e2,'shown');
- createCookie(id,1,365);
- }
- else {
- set_rollup_state(e,e2,'hidden');
- createCookie(id,0,365);
- }
- return false;
-}
-
-function set_rollup_state(e,e2,state) {
- if (e && e2) {
- if (state == 'shown') {
- show(e);
- delClass( e2, 'rolled-up' );
- }
- else if (state == 'hidden') {
- hide(e);
- addClass( e2, 'rolled-up' );
- }
- }
-}
-
-
-/* onload handlers */
-
-var onLoadStack = new Array();
-var onLoadLastStack = new Array();
-var onLoadExecuted = 0;
-
-function onLoadHook(commandStr) {
- if(typeof(commandStr) == "string") {
- onLoadStack[ onLoadStack.length ] = commandStr;
- return true;
- }
- return false;
-}
-
-// some things *really* need to be done after everything else
-function onLoadLastHook(commandStr) {
- if(typeof(commandStr) == "string"){
- onLoadLastStack[onLoadLastStack.length] = commandStr;
- return true;
- }
- return false;
-}
-
-function doOnLoadHooks() {
- if(onLoadExecuted) return;
-
- var i;
- for ( i in onLoadStack ) {
- eval( onLoadStack[i] );
- }
- for ( i in onLoadLastStack ) {
- eval( onLoadLastStack[i] );
- }
- onLoadExecuted = 1;
-}
-
-window.onload = doOnLoadHooks;
-
-/* calendar functions */
-
-function openCalWindow(field) {
- var objWindow = window.open('<%$RT::WebPath%>/Helpers/CalPopup.html?field='+field,
- 'RT_Calendar',
- 'height=235,width=285,scrollbars=1');
- objWindow.focus();
-}
-
-function createCalendarLink(input) {
- var e = $(input);
- if (e) {
- var link = document.createElement('a');
- link.setAttribute('href', '#');
-
- clickevent = function clickevent(e) { openCalWindow(input); return false; };
- if (! addEvent(link, "click", clickevent)) {
- return false;
- }
-
- var text = document.createTextNode('<% loc("Choose a date") %>');
- link.appendChild(text);
-
- var space = document.createTextNode(' ');
-
- e.parentNode.insertBefore(link, e.nextSibling);
- e.parentNode.insertBefore(space, e.nextSibling);
-
- return true;
- }
- return false;
-}
-
-/* other utils */
-
-function focusElementById(id) {
- var e = $(id);
- if (e) e.focus();
-}
-
-function updateParentField(field, value) {
- if (window.opener) {
- window.opener.$(field).value = value;
- window.close();
- }
-}
-
-function addEvent(obj, sType, fn) {
- if (obj.addEventListener) {
- obj.addEventListener(sType, fn, false);
- } else if (obj.attachEvent) {
- var r = obj.attachEvent("on"+sType, fn);
- } else {
- return false;
- }
- return true;
-}
-
-function setCheckbox(form, name, val) {
- var myfield = form.getElementsByTagName('input');
- for ( var i = 0; i < myfield.length; i++ ) {
- if ( name && myfield[i].name != name ) continue;
- if ( myfield[i].type != 'checkbox' ) continue;
-
- myfield[i].checked = val;
- }
-}
-
diff --git a/rt/html/NoAuth/printrt.css b/rt/html/NoAuth/printrt.css
deleted file mode 100644
index 72e7e8b..0000000
--- a/rt/html/NoAuth/printrt.css
+++ /dev/null
@@ -1,77 +0,0 @@
-%# {{{ BEGIN BPS TAGGED BLOCK
-%#
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# }}} END BPS TAGGED BLOCK
-%#
-%# Special stylesheet for printing tickets
-%# Koos van den Hout koos@cs.uu.nl 2005-11-21
-%#
-
-SPAN.nav { display: none !important; }
-.nav2 { display: none !important; }
-.nav { display: none !important; }
-.topnav { display: none !important; }
-.blue { display: none !important; }
-.darkblue { display: none !important; }
-.blueright { display: none !important; }
-.currentnav { display: none !important; }
-th.titlebox { border-top: none; border-bottom: none; }
-th.titleboxright { display:none !important; border-top: none; border-bottom: none; }
-.titlebox { border-top: none; border-bottom: none; }
-
-div.downloadattachment, div.downloadcontenttype {
- display: none !important;
-}
-
-
-a[href$="Respond"], a[href$="Comment"], a[href*="ShowEmailRecord"] {
- display: none !important;
-}
-
-
-%# Provide a callback for adding/modifying the style sheet.
-%# http://www.w3.org/TR/REC-CSS1 - section 3.2, says:
-%# "latter specified rule wins"
-<& /Elements/Callback &>
-<%flags>
-inherit => undef
-</%flags>
-<%init>
-$r->content_type('text/css');
-$r->headers_out->{'Expires'} = '+30m';
-</%init>
diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css
deleted file mode 100644
index 7fa2f83..0000000
--- a/rt/html/NoAuth/webrt.css
+++ /dev/null
@@ -1,628 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-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}
-
-%# .topnav is the original RT class for the sidebar navigation tabs.
-%# Font-sizing by level depth was originally hard-coded into Elements/Menu.
-%# This modification sets a different class name for each level, allowing
-%# style sheet control over the formats.
-
-a.topnav-0 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 16px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-a.topnav-1 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 14px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-a.topnav-2 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-a.topnav-3 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-a.topnav-4 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-a.topnav-5 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFFFF;
- text-decoration: none;
- white-space: nowrap}
-li.topnav-0-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-1-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-2-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-3-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-4-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-5-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.topnav-0-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.topnav-1-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.topnav-2-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.topnav-3-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.topnav-4-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.topnav-5-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-
-.currenttopnav { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 16px;
- font-weight: bold;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-
-%# .currenttopnav is the original RT class for the sidebar navigation tabs.
-%# Font-sizing by level depth was originally hard-coded into Elements/Menu.
-%# This modification sets a different class name for each level, allowing
-%# style sheet control over the formats
-
-a.currenttopnav-0 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 16px;
- font-weight: bold;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-a.currenttopnav-1 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 14px;
- font-weight: bold;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-a.currenttopnav-2 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 12px;
- font-weight: normal;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-a.currenttopnav-3 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-a.currenttopnav-4 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-a.currenttopnav-5 { font-family: Verdana, Arial, Helvetica, sans-serif;
- font-size: 11px;
- font-weight: normal;
- color: #FFFF66;
- text-decoration: none;
- white-space: nowrap}
-li.currenttopnav-0-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-1-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-2-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-3-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-4-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-5-minor {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-li.currenttopnav-0-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.currenttopnav-1-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.currenttopnav-2-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.currenttopnav-3-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.currenttopnav-4-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-li.currenttopnav-5-major {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-
-.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;
- }
-%# Actually the "topactions" section
-.blueright { background-color: #4682B4;
- background-position: left top;
- vertical-align: top;
- text-align: right;
- padding-right: 1em;
- }
-.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;
- }
-.overdue {
- color: red;
-}
-
-div.messagebody {
- padding: 2em;
-
-}
-
-
-div.downloadattachment {
- font-size: 10px;
- 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: 1em;
- padding-right: 1em;
- margin-left: 1em;
- margin-right: 1em;
-}
-
-td.boxcontainer + td.boxcontainer {
- margin-left: 1em;
- padding-left: 1em;
- border-collapse: collapse;
-}
-
-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: Verdana, Arial, 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: Verdana, Arial, 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, SPAN.titleboxclose {
- font-size: 80%;
- color: #ffffff;
- vertical-align: middle;
- text-align: left;
- }
-SPAN.titleboxtitle a {
- color: #ffffff;
-}
-SPAN.titleboxtitle a:after {
- content: "...";
-}
-
-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;
-}
-
-DIV.page-stats { font-size: 0.8em;
- color: #cccccc;
- text-align: right;
- }
-
-
-BLOCKQUOTE {
- font-style: italic;
-}
-
-.emphasized {
- font-weight: bold
-}
-
-
-.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;
-}
-
-.menu-major-separator {
- border-bottom: solid white 1px;
- padding-top: .25em;
- padding-bottom: .5em;
-}
-
-.menu-minor-separator {
- border-top: solid #999999 1px;
- padding-top: .1em;
- margin-top: .5em;
-}
-
-TH.collection-as-table { text-align: center;
- font-size: 0.8em;
- padding-left: .5em;
- padding-right: .5em;
- color: #333333;
- background-color: #cccccc;
- white-space: nowrap;
- }
-
-TD.collection-as-table { text-align: left;
- padding-left: .5em;
- padding-right: .5em;
- }
-
-textarea.signature {
- width: 100%;
-}
-textarea.comments {
- width: 100%;
-}
-
-textarea.messagebox {
- width: 100%;
-}
-
-%# Provide a callback for adding/modifying the style sheet.
-%# http://www.w3.org/TR/REC-CSS1 - section 3.2, says:
-%# "latter specified rule wins"
-<& /Elements/Callback &>
-<%flags>
-inherit => undef
-</%flags>
-<%init>
-$r->content_type('text/css');
-#$r->headers_out->{'Expires'} = '+30m';
-</%init>
diff --git a/rt/html/Prefs/Elements/Tabs b/rt/html/Prefs/Elements/Tabs
deleted file mode 100644
index 8765e74..0000000
--- a/rt/html/Prefs/Elements/Tabs
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /User/Elements/Tabs,
- subtabs => $tabs,
- current_tab => 'Prefs/MyRT.html',
- current_subtab => $current_subtab,
- Title => $Title &>
-
-<%INIT>
-my $tabs;
-unless ($Searches) {
- $Searches = [$m->comp("/Search/Elements/SearchesForObject", Object => RT::System->new($session{'CurrentUser'}))];
-}
-
-$tabs->{a} = { title => loc('Quick search'),
- path => 'Prefs/Quicksearch.html' };
-for my $search (@$Searches) {
- $tabs->{$search->[0]} = { title => $search->[0],
- path => "Prefs/Search.html?".$m->comp('/Elements/QueryString', name => ref($search->[1]).'-'.$search->[1]->Id) };
-}
-</%INIT>
-<%ARGS>
-$GroupObj => undef
-$current_subtab => undef
-$Title => undef
-$Searches => undef
-</%ARGS>
diff --git a/rt/html/Prefs/MyRT.html b/rt/html/Prefs/MyRT.html
deleted file mode 100644
index e69a0cf..0000000
--- a/rt/html/Prefs/MyRT.html
+++ /dev/null
@@ -1,151 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Prefs/Elements/Tabs,
- current_tab => 'Prefs/MyRT.html',
- Title => $title,
- Searches => \@sys_searches
-&>
-
-<& /Widgets/SelectionBox:header, nojs => 1 &>
-
-<& /Elements/ListActions, actions => \@actions &>
-<br />
-
-<form method="post" action="MyRT.html">
-<input type="hidden" name="Reset" value="1" />
-<input type="submit" class="button" value="<%loc('Reset to default')%>">
-</form>
-
-<br />
-
-% for my $pane (@panes) {
-<&|/Widgets/TitleBox, title => loc('RT at a glance').': '.loc($pane->{Name}), bodyclass => "" &>
-<& /Widgets/SelectionBox:show, self => $pane, nojs => 1 &></&>
-<br />
-% }
-<&|/Widgets/TitleBox, title => loc('Options'), bodyclass => "" &>
-<form method="post" action="MyRT.html">
- <&|/l&>Rows per box</&>:<input name="SummaryRows" value="<% $ARGS{SummaryRows} %>" /> <input type="submit" class="button" value="<%loc('Save')%>" />
-</form>
-</&>
-<%INIT>
-my @actions;
-
-my $title = loc("Customize").' '.loc("RT at a glance");
-my $user = $session{'CurrentUser'}->UserObj;
-
-if ($ARGS{Reset}) {
- $user->SetPreferences('HomepageSettings', {});
- delete $session{'my_rt_portlets'};
-}
-
-unless (exists $session{'my_rt_portlets'}) {
- my ($default_portlets) = RT::System->new($session{'CurrentUser'})->Attributes->Named('HomepageSettings');
- my $portlets = $default_portlets ? $default_portlets->Content : {};
- $session{'my_rt_portlets'} = $user->Preferences('HomepageSettings', $portlets);
-}
-if ($ARGS{SummaryRows}) {
- $user->SetPreferences('SummaryRows', $ARGS{SummaryRows});
- push @actions, loc ('Preferences saved for [_1].', loc('summary rows'));
-}
-else {
- $ARGS{SummaryRows} = $user->Preferences('SummaryRows', $RT::DefaultSummaryRows);
-}
-
-
-my $portlets = $session{'my_rt_portlets'};
-
-my %allowed_components = map {$_ => 1} @{$RT::HomepageComponents};
-my @items;
-
-push @items, map {["component-$_", $_]} sort keys %allowed_components;
-
-my $sys = RT::System->new($session{'CurrentUser'});
-my @objs = ($sys);
-
-push @objs, RT::SavedSearches->new( $session{CurrentUser} )->_PrivacyObjects
- if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
- Object => $RT::System );
-
-my @sys_searches;
-for my $object (@objs) {
- for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
- my ($desc, $search) = @$_;
- my $SearchType = $search->Content->{'SearchType'} || 'Ticket';
- if ($object eq $sys && $SearchType eq 'Ticket') {
- push @items, ["system-$desc", $desc];
- push @sys_searches, [$desc, $search];
- }
- else {
- my $oid = ref($object).'-'.$object->Id.'-SavedSearch-'.$search->Id;
- my $type = ($SearchType eq 'Ticket')
- ? 'Saved Search' : $SearchType; # loc
- push @items, ["saved-$oid", loc($type).": $desc"];
- }
- }
-}
-
-my @panes = $m->comp(
- '/Admin/Elements/ConfigureMyRT',
- panes => ['body', 'summary'],
- Action => 'MyRT.html',
- items => \@items,
- current_portlets => $portlets,
- OnSave => sub {
- my ( $conf, $pane ) = @_;
- $user->SetPreferences( 'HomepageSettings', $conf );
- push @actions, loc( 'Preferences saved for [_1].', $pane );
- delete $session{'my_rt_portlets'};
- }
-);
-
-$m->comp( '/Widgets/SelectionBox:process', %ARGS, self => $_, nojs => 1 )
- for @panes;
-
-</%INIT>
diff --git a/rt/html/Prefs/Quicksearch.html b/rt/html/Prefs/Quicksearch.html
deleted file mode 100644
index 8372c03..0000000
--- a/rt/html/Prefs/Quicksearch.html
+++ /dev/null
@@ -1,96 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Prefs/Elements/Tabs,
- current_tab => 'Prefs/MyRT.html',
- current_subtab => 'Prefs/Quicksearch.html',
- Title => $title
-&>
-<& /Elements/ListActions, actions => \@actions &>
-<h1><&|/l&>Select queues to be displayed on the "RT at a glance" page</&></h1>
-<form method="post" action="Quicksearch.html" name="Preferences">
-<ul>
-% for my $queue (@queues) {
-<li><input type="checkbox" class="checkbox" name="Want-<%$queue->Name%>" value="1"
-% unless ($unwanted->{$queue->Name}) {
-checked
-% }
-/><%$queue->Name%>: <%$queue->Description%></li>
-% }
-</ul>
-<& /Elements/Submit, Caption => loc("Save Changes"), Label => loc('Save'), Name => 'Save'&>
-
-</form>
-
-<%INIT>
-my @actions;
-my $title = loc("Customize").' '.loc("Quick search");
-# The queue list is not loaded from cache, so it might be a bit inconsistent
-my $user = $session{'CurrentUser'}->UserObj;
-my $unwanted = $user->Preferences('QuickSearch', {});
-my $Queues = RT::Queues->new($session{'CurrentUser'});
-$Queues->UnLimit;
-my @queues = grep {$_->CurrentUserHasRight('ShowTicket')} @{$Queues->ItemsArrayRef};
-
-if ($ARGS{'Save'}) {
- for my $queue (@queues) {
- if ($ARGS{"Want-".$queue->Name}) {
- delete $unwanted->{$queue->Name};
- }
- else {
- ++$unwanted->{$queue->Name};
- }
- }
-
- $user->SetPreferences('QuickSearch', $unwanted);
- push @actions, loc ('Preferences saved.');
- # Let QueueSummary rebuild the cache
- delete $session{'quick_search_queues'};
-}
-
-</%INIT>
diff --git a/rt/html/Prefs/Search.html b/rt/html/Prefs/Search.html
deleted file mode 100644
index 673a074..0000000
--- a/rt/html/Prefs/Search.html
+++ /dev/null
@@ -1,108 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Prefs/Elements/Tabs,
- current_tab => 'Prefs/MyRT.html',
-# current_subtab => 'Prefs/Search.html?name='.$m->comp('/Elements/QueryString', name => $ARGS{name}),
- current_subtab => 'Prefs/Search.html?name='.$ARGS{name},
- Title => $title
-&>
-<& /Elements/ListActions, actions => \@actions &>
-% if ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
-<p>
- <&|/l&>You can also edit the predefined search itself</&>:
- <a href="<% $RT::WebPath.'/Search/Build.html?'.
- $m->comp('/Elements/QueryString',
- LoadSavedSearch => 'RT::System-1-SavedSearch-'.$id) %>"><% $search->Name %></a>
-</p>
-% }
-
-<form method="post" action="Search.html" name="BuildQuery">
-<input type="hidden" name="name" value="<%$ARGS{name}%>" class="hidden" />
-<input type="hidden" name="Format" value="<%$ARGS{Format}%>" class="hidden" />
-
-<& /Search/Elements/DisplayOptions, %$SearchArg, %ARGS,
- AvailableColumns => $AvailableColumns, CurrentFormat => $CurrentFormat &>
-<& /Elements/Submit, Caption => loc("Save"), Label => loc('Save'), Name => 'Save'&>
-
-</form>
-
-<%INIT>
-my @actions;
-my $title = loc("Customize").' ';
-
-my @fields = qw(Format Order OrderBy RowsPerPage);
-my ($class, $id) = ( $ARGS{name} =~ m/^(.*)-(\d+)$/ );
-
-Abort('No search specified')
- unless $class eq 'RT::Attribute';
-
-my $search = $class->new ($session{'CurrentUser'});
-$search->LoadById ($id);
-$title .= loc ($search->Description, loc ('"N"'));
-my $user = $session{'CurrentUser'}->UserObj;
-my $SearchArg = $user->Preferences($search, $search->Content);
-for (@fields) {
- $ARGS{$_} = $SearchArg->{$_} unless defined $ARGS{$_};
-}
-$ARGS{'Order'} = join '|', grep defined && /\S/, (ref $ARGS{'Order'})? @{$ARGS{'Order'}}: $ARGS{'Order'};
-$ARGS{'OrderBy'} = join '|', grep defined && /\S/, (ref $ARGS{'OrderBy'})? @{$ARGS{'OrderBy'}}: $ARGS{'OrderBy'};
-
-my ( $AvailableColumns, $CurrentFormat );
-( $ARGS{Format}, $AvailableColumns, $CurrentFormat ) = $m->comp(
- '/Search/Elements/BuildFormatString',
- cfqueues => {}, %ARGS
-);
-
-if ($ARGS{'Save'}) {
- my $hash = {map { $_ => $ARGS{$_}} @fields};
- my $pref = $user->SetPreferences ($search, $hash);
- push @actions, loc ('Preferences saved.');
-}
-
-</%INIT>
diff --git a/rt/html/Prefs/SearchOptions.html b/rt/html/Prefs/SearchOptions.html
deleted file mode 100644
index 655d6ec..0000000
--- a/rt/html/Prefs/SearchOptions.html
+++ /dev/null
@@ -1,114 +0,0 @@
-
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Search Preferences") &>
-<& /User/Elements/Tabs,
- current_tab => "Prefs/SearchOptions.html",
- Title => loc("Search Preferences")
-&>
-
-<form method="post" action="SearchOptions.html">
-<input type="hidden" class="hidden" name="Format" value="<%$Format%>" />
- <& /Search/Elements/DisplayOptions, %ARGS,
- Format=> $Format,
- AvailableColumns => $AvailableColumns,
- CurrentFormat => $CurrentFormat,
- RowsPerPage => $RowsPerPage,
- OrderBy => $OrderBy,
- Order => $Order &>
-
-<& /Elements/Submit, Name => 'SavePreferences', Label => loc('Save Changes') &>
-</form>
-
-<%INIT>
-
-# {{{ If we're saving search preferences, do that now
-$Order = join '|', grep defined && /\S/, (ref $Order)? @{$Order}: $Order;
-$OrderBy = join '|', grep defined && /\S/, (ref $OrderBy)? @{$OrderBy}: $OrderBy;
-
-if ($ARGS{'SavePreferences'}) {
- $session{'CurrentUser'}->UserObj->SetPreferences("SearchDisplay",
- {
- Format => $Format,
- Order => $Order,
- OrderBy => $OrderBy,
- RowsPerPage => $RowsPerPage,
- });
-}
-
-# }}}
-
-
-
-
-
-
-
-# Read from user preferences
-my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
-
-$Format ||= $prefs->{'Format'};
-$Order ||= $prefs->{'Order'} || 'ASC';
-$OrderBy ||= $prefs->{'OrderBy'} || 'id';
-($RowsPerPage = defined( $prefs->{'RowsPerPage'} ) ? $prefs->{'RowsPerPage'} : 50) unless defined ($RowsPerPage);
-
-my ( $AvailableColumns, $CurrentFormat );
-( $Format, $AvailableColumns, $CurrentFormat ) = $m->comp(
- '/Search/Elements/BuildFormatString',
- %ARGS, Format => $Format
-);
-</%INIT>
-
-<%ARGS>
-$Format => undef
-$Description => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-</%ARGS>
-
diff --git a/rt/html/REST/1.0/Forms/queue/default b/rt/html/REST/1.0/Forms/queue/default
deleted file mode 100644
index ca9cf69..0000000
--- a/rt/html/REST/1.0/Forms/queue/default
+++ /dev/null
@@ -1,170 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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
deleted file mode 100644
index 0cb594b..0000000
--- a/rt/html/REST/1.0/Forms/queue/ns
+++ /dev/null
@@ -1,62 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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
deleted file mode 100644
index f1d209d..0000000
--- a/rt/html/REST/1.0/Forms/ticket/attachments
+++ /dev/null
@@ -1,135 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/attachments
-%#
-<%ARGS>
-$id
-$args => undef
-</%ARGS>
-<%INIT>
-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->OriginalContent;
- # if we're sending a binary attachment (and only the attachment)
- # flag it so bin/rt knows to special case it
- if ($attachment->ContentType !~ /^text\//) {
- $r->content_type($attachment->ContentType);
- }
- } 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) {
- my $size = length($a->Content);
- if ($size > 1024) { $size = int($size/102.4)/10 . "k" }
- else { $size .= "b" }
- push @attachments, $a->Id.": ".$a->Filename." (".$a->ContentType . " / ".$size.")";
- }
- }
-
- if (@attachments) {
- $o = [ "id", "Attachments" ];
- $k = {
- id => "ticket/".$ticket->Id."/attachments",
- Attachments => \@attachments
- };
- }
-}
-
-return [ $c, $o, $k, $e ];
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/comment b/rt/html/REST/1.0/Forms/ticket/comment
deleted file mode 100755
index dd033d1..0000000
--- a/rt/html/REST/1.0/Forms/ticket/comment
+++ /dev/null
@@ -1,152 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/comment
-%#
-<%ARGS>
-$id
-%changes
-</%ARGS>
-<%INIT>
-use MIME::Entity;
-use LWP::MediaTypes;
-use RT::Interface::REST;
-use File::Temp qw(tempfile);
-
-$RT::Logger->debug("Got ticket id=$id for comment");
-$RT::Logger->debug("Got args @{[keys(%changes)]}.");
-
-my $ticket = new RT::Ticket $session{CurrentUser};
-my ($c, $o, $k, $e) = ("", [], {}, 0);
-
-# http://.../REST/1.0/ticket/1/comment
-$ticket->Load($id);
-if (!$ticket->Id) {
- $e = 1;
- $c = "# Ticket $id does not exist.";
- goto OUTPUT;
-}
-
-my $action;
-($action = $changes{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/;
-unless ($action =~ /^(?:Comment|Correspond)$/) {
- $e = 1;
- $c = "# Invalid action: `$action'.";
- goto OUTPUT;
-}
-
-my $text = $changes{Text};
-my @atts = @{ vsplit($changes{Attachment}) };
-
-if (!$changes{Text} && @atts == 0) {
- $e = 1;
- $c = "# Empty comment with no attachments submitted.";
- goto OUTPUT;
-}
-
-my $cgi = $m->cgi_object;
-my $ent = MIME::Entity->build(Type => "multipart/mixed");
-$ent->attach(Data => $changes{Text}) if $changes{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 {
- $e = 1;
- $c = "# No attachment for $att.";
- goto OUTPUT;
- }
-
- $i++;
-}
-
-unless ($ticket->CurrentUserHasRight('ModifyTicket') ||
- ($action eq "Comment" &&
- $ticket->CurrentUserHasRight("CommentOnTicket")) ||
- ($action eq "Correspond" &&
- $ticket->CurrentUserHasRight("ReplyToTicket")))
-{
- $e = 1;
- $c = "# You are not allowed to $action on ticket $id.";
- goto OUTPUT;
-}
-
-my $cc = join ", ", @{ vsplit($changes{Cc}) };
-my $bcc = join ", ", @{ vsplit($changes{Bcc}) };
-my ($n, $s) = $ticket->$action(MIMEObj => $ent,
- CcMessageTo => $cc,
- BccMessageTo => $bcc,
- TimeTaken => $changes{TimeWorked} || 0);
-$c = "# ".$s;
-if ($changes{Status}) {
- my ($status_n, $status_s) = $ticket->SetStatus($changes{'Status'} );
- $c .= "\n# ".$status_s;
-}
-
-OUTPUT:
-return [ $c, $o, $k, $e ];
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/default b/rt/html/REST/1.0/Forms/ticket/default
deleted file mode 100644
index 918f360..0000000
--- a/rt/html/REST/1.0/Forms/ticket/default
+++ /dev/null
@@ -1,345 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/default
-%#
-<%ARGS>
-$id
-$changes => {}
-$fields => undef
-$args => undef
-</%ARGS>
-<%INIT>
-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 LastUpdated);
-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 (!keys(%data)) {
- # 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: id, Queue",
- [ 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};
- }
- # Set custom field
- elsif ($k =~ /^CF-/i) {
- my $cf = RT::CustomField->new( $RT::SystemUser );
- my $cfk = $k;
- $cfk =~ s/^CF-//i;
- unless($cf->LoadByName( Name => $cfk )) {
- push @comments, "# Invalid custom field name ($cfk)";
- delete $data{$k};
- next;
- }
- $v{"CustomField-".$cf->Id()} = delete $data{$k};
- }
- elsif (lc $k eq 'text') {
- $text = delete $data{$k};
- }
- }
-
- # people fields allow multiple values
- $v{$_} = vsplit($v{$_}) foreach ( grep $create{lc $_}, @people );
-
- if ($text) {
- $v{MIMEObj} =
- MIME::Entity->build(
- From => $session{CurrentUser}->EmailAddress,
- Subject => $v{Subject},
- Data => $text
- );
- }
-
- my($tid,$trid,$terr) = $ticket->Create(%v);
- unless ($tid) {
- push(@comments, "# Could not create ticket.");
- push(@comments, "# " . $terr);
- goto DONE;
- }
-
- delete $data{id};
- $id = $ticket->Id;
- push(@comments, "# Ticket $id created.");
- # see if the hash is empty
- goto DONE if ! keys(%data);
- }
-}
-
-# Now we know we're dealing with an existing ticket.
-if (!keys(%data)) {
- 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 = "$val minutes" if $val;
- push @data, [ $key => $val ];
- }
-
- # Display custom fields
- my $CustomFields = $ticket->QueueObj->TicketCustomFields();
- while (my $cf = $CustomFields->Next()) {
- next unless (!%$fields || (exists $fields->{"cf-".lc $cf->Name}));
- my $vals = $ticket->CustomFieldValues($cf->Id());
- my @out = ();
- while (my $v = $vals->Next()) {
- push @out, $v->Content;
- }
- push @data, [ 'CF-' . $cf->Name => join ',', @out ];
- }
-
- 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)|| ($ticket->$key =~ /^\d+$/ && $val == $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/^# //;
- }
- }
- # Set custom field
- elsif ($key =~ /^CF-/i) {
- my $cf = RT::CustomField->new( $RT::SystemUser );
- $key =~ s/^CF-//i;
- if (not $cf->LoadByName( Name => $key )) {
- $n = 0;
- $s = "Unknown custom field.";
- }
- else {
- ($n, $s) = $ticket->AddCustomFieldValue(
- Field => $cf, Value => $val );
- $s =~ s/^# // if defined $s;
- }
- }
- elsif ($key ne 'id' && $key ne 'type' && $key ne 'creator') {
- $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];
-
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/history b/rt/html/REST/1.0/Forms/ticket/history
deleted file mode 100644
index b5ae187..0000000
--- a/rt/html/REST/1.0/Forms/ticket/history
+++ /dev/null
@@ -1,200 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/history
-%#
-<%ARGS>
-$id
-$args => undef
-$format => undef
-$fields => undef
-</%ARGS>
-<%INIT>
-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};
-
- # this paragraph limits the transaction ID query to transactions on this ticket.
- # Otherwise you can query any transaction from any ticket, which makes no sense.
- my $Transactions = $ticket->Transactions;
- my $tok=0;
- while (my $T = $Transactions->Next()) {
- $tok=1 if ($T->Id == $tid)
- }
- if ($tok) {
- $t->Load($tid);
- } else {
- return [ "# Transaction $tid is not related to Ticket $id", [], {}, 1 ];
- }
-
- push @data, [ id => $t->Id ];
- 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("/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 ];
-
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/links b/rt/html/REST/1.0/Forms/ticket/links
deleted file mode 100644
index 12e1b1f..0000000
--- a/rt/html/REST/1.0/Forms/ticket/links
+++ /dev/null
@@ -1,172 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/links
-%#
-<%ARGS>
-$id
-$format => 's'
-$changes => undef
-</%ARGS>
-<%INIT>
-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 ];
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/merge b/rt/html/REST/1.0/Forms/ticket/merge
deleted file mode 100755
index 3921da7..0000000
--- a/rt/html/REST/1.0/Forms/ticket/merge
+++ /dev/null
@@ -1,96 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/merge
-%#
-<%ARGS>
-$id
-$args
-</%ARGS>
-<%INIT>
-use RT::Interface::REST;
-
-my $into = $args;
-
-my $ticket = new RT::Ticket $session{CurrentUser};
-my $ticket_into = new RT::Ticket $session{CurrentUser};
-my ($c, $o, $k, $e) = ("", [], {}, 0);
-
-# http://.../REST/1.0/ticket/1/merge/6 (merges ticket 1 into ticket 6)
-
-$ticket->Load($id);
-if (!$ticket->Id) {
- $e = 1;
- $c = "# Ticket $id does not exist.";
- goto OUTPUT;
-}
-$ticket_into->Load($into);
-if (!$ticket_into->Id) {
- $e = 1;
- $c = "# Ticket $into does not exist.";
- goto OUTPUT;
-}
-
-if (!$ticket->CurrentUserHasRight('ModifyTicket')) {
- $e = 1;
- $c = "# You are not allowed to modify ticket $id.";
- goto OUTPUT;
-}
-
-my ($n, $s) = $ticket->MergeInto($into);
-
-if ($n == 0) {
- $e = 1;
- $c = "# Could not complete the merge.";
-}
-else {
- $c = "# Merge completed.";
-}
-
-OUTPUT:
-return [ $c, $o, $k, $e ];
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/ticket/take b/rt/html/REST/1.0/Forms/ticket/take
deleted file mode 100755
index f8d1457..0000000
--- a/rt/html/REST/1.0/Forms/ticket/take
+++ /dev/null
@@ -1,135 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/ticket/take
-%#
-<%ARGS>
-$id
-%changes
-</%ARGS>
-<%INIT>
-use RT::Interface::REST;
-
-my $ticket = new RT::Ticket $session{CurrentUser};
-my ($c, $o, $k, $e) = ("", [], {}, 0);
-
-# http://.../REST/1.0/ticket/1/take
-$ticket->Load( $id );
-unless ( $ticket->Id ) {
- $e = 1;
- $c = "# Ticket $id does not exist.";
- goto OUTPUT;
-}
-
-my $action;
-
-my @comments;
-
-($action = $changes{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/;
-unless ($action =~ /^(?:Take|Steal|Untake)$/) {
- $e = 1;
- $c = "# Invalid action: `$action'.";
- goto OUTPUT;
-}
-
-my ($status, $msg) = $ticket->$action();
-$c = "# $msg";
-$e = 1 unless $status;
-goto OUTPUT;
-
-#unless ($ticket->CurrentUserHasRight('ModifyTicket') ||
-# ( ($action eq "Take" || $action eq 'Untake') &&
-# $ticket->CurrentUserHasRight("TakeTicket")) ||
-# ($action eq "Steal" &&
-# $ticket->CurrentUserHasRight("StealTicket")))
-#{
-# $e = 1;
-# $c = "# You are not allowed to $action ticket $id.";
-# goto OUTPUT;
-#}
-
-#if ( keys %changes ) {
-#}
-#else {
-# # process the form data structure
-# my ($key, $val);
-#
-# foreach $key (keys %data) {
-# $val = $data{$key};
-#
-# if ($key =~ /^force$/i) {
-# if ($val !~ /^(?:0|1)$/) {
-# push(@comments, "# invalid value for 'force': $val");
-# goto DONE;
-# }
-# my ($ret_id, $msg);
-#
-# ### take
-# if ($val == 0) {
-# ($ret_id, $msg) = $ticket->Take;
-# if (!$ret_id) {
-# push(@comments, "# Couldn't take ticket $id: $msg");
-# goto DONE;
-# }
-# push(@comments, "# Ticket $id taken.");
-# }
-# ### steal
-# else {
-# ($ret_id, $msg) = $ticket->Steal;
-# if (!$ret_id) {
-# push(@comments, "# Couldn't steal ticket $id: $msg");
-# goto DONE;
-# }
-# push(@comments, "# Ticket $id stolen.");
-# }
-# }
-# }
-#}
-
-OUTPUT:
-return [ $c, $o, $k, $e ];
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/transaction/default b/rt/html/REST/1.0/Forms/transaction/default
deleted file mode 100644
index 053f65e..0000000
--- a/rt/html/REST/1.0/Forms/transaction/default
+++ /dev/null
@@ -1,143 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/Forms/transaction
-%#
-<%ARGS>
-$id
-$args => undef
-$format => undef
-$fields => undef
-</%ARGS>
-<%INIT>
-my $trans = new RT::Transactions $session{CurrentUser};
-my ($c, $o, $k, $e) = ("", [], {} , "");
-
-chomp $args;
-my @arglist = split('/', $args);
-my $tid = $id;
-
-$trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid);
-
-if ($tid) {
- my @data;
- my $t = new RT::Transaction $session{CurrentUser};
- $t->Load($tid);
- if ($format eq "l") {
- push @data, [ id => $t->Id ];
- 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];
- }
-
- } else {
- push @data, [ id => $t->Id ];
- push @data, [ Description => $t->Description ];
- }
-
- 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;
-# }
-# }
-#}
-
-return [ $c, $o, $k, $e ];
-
-</%INIT>
diff --git a/rt/html/REST/1.0/Forms/user/default b/rt/html/REST/1.0/Forms/user/default
deleted file mode 100644
index dd383f9..0000000
--- a/rt/html/REST/1.0/Forms/user/default
+++ /dev/null
@@ -1,188 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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
deleted file mode 100644
index 4bbcbd7..0000000
--- a/rt/html/REST/1.0/Forms/user/ns
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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
deleted file mode 100644
index 25dc5da..0000000
--- a/rt/html/REST/1.0/NoAuth/mail-gateway
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%flags>
-inherit => undef # inhibit UTF8 conversion done in /autohandler
-</%flags>
-<%ARGS>
-$queue => 1
-$action => "correspond"
-$ticket => undef
-</%ARGS>
-<%init>
-$m->comp('/Elements/Callback', _CallbackName => 'Pre', %ARGS);
-use RT::Interface::Email (); # It's an exporter, but we don't care
-$r->content_type('text/plain; charset=utf-8');
-$m->error_format('text');
-my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway( \%ARGS );
-if ( $status == 1 ) {
- $m->out('ok');
- if ( $Ticket->Id ) {
- $m->out( 'Ticket: ' . ($Ticket->Id || '') );
- $m->out( 'Queue: ' . ($Ticket->QueueObj->Name || '') );
- $m->out( 'Owner: ' . ($Ticket->OwnerObj->Name || '') );
- $m->out( 'Status: ' . ($Ticket->Status || '') );
- $m->out( 'Subject: ' . ($Ticket->Subject || '') );
- $m->out(
- 'Requestor: ' . ($Ticket->Requestors->MemberEmailAddressesAsString || '') );
- }
-}
-else {
- $RT::Logger->error( "Could not record email: " . $error );
- if ( $status == -75 ) {
- $m->out( "temporary failure - " . $error );
- }
- else {
- $m->out( 'not ok - ' . $error );
- }
-}
-$m->abort();
-</%init>
diff --git a/rt/html/REST/1.0/autohandler b/rt/html/REST/1.0/autohandler
deleted file mode 100644
index 1b4b070..0000000
--- a/rt/html/REST/1.0/autohandler
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# REST/1.0/autohandler
-%#
-<%INIT>
-use RT::Interface::REST;
-$r->content_type('text/plain; charset=utf-8');
-$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
deleted file mode 100644
index bb6b262..0000000
--- a/rt/html/REST/1.0/dhandler
+++ /dev/null
@@ -1,316 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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_-]|\s+)*';
-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/comment
-# 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) {
- if (@$o) {
- $status = "409 Syntax Error";
- }
- else {
- $status = "400 Bad Request";
- }
- push @output, [ $c, $o, $k ];
- }
- else {
- push @comments, $c;
- }
- }
-}
-
-unshift(@output, [ join "\n", @comments ]) if @comments;
-$output = form_compose(\@output);
-
-OUTPUT:
-$m->out("RT/".$RT::VERSION ." ".$status ."\n\n$output\n") if ($output || $status != 200);
-return;
-</%INIT>
diff --git a/rt/html/REST/1.0/logout b/rt/html/REST/1.0/logout
deleted file mode 100644
index bb5359e..0000000
--- a/rt/html/REST/1.0/logout
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%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
deleted file mode 100644
index 1a43bf8..0000000
--- a/rt/html/REST/1.0/search/dhandler
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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
deleted file mode 100644
index bd3d63f..0000000
--- a/rt/html/REST/1.0/search/ticket
+++ /dev/null
@@ -1,158 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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_-]|\s+)*';
-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++;
-
- my $id = $ticket->Id;
- if ($format eq "i") {
- $output .= "ticket/" . $id . "\n";
- }
- elsif ($format eq "s") {
- if ($fields) {
- my $result = $m->comp("/REST/1.0/Forms/ticket/default", id => $id, format => $format, fields => \%fields);
- my ($notes, $order, $key_values, $errors) = @$result;
- # If it's the first time through, add our header
- if ($n == 1) {
- $output .= join("\t",@$order)."\n";
- }
- # Cut off the annoying ticket/ before the id;
- $key_values->{'id'} = $id;
- $output .= join("\t", map {$key_values->{$_}} @$order)."\n";
-
-
- } else {
- $output .= $ticket->Id . ": ". $ticket->Subject . "\n";
- }
- }
- else {
- my $d = $m->comp("/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:
-$m->out("RT/". $RT::VERSION . " " . $status ."\n\n");
-
-$m->out($output );
-return();
-</%INIT>
diff --git a/rt/html/REST/1.0/ticket/comment b/rt/html/REST/1.0/ticket/comment
deleted file mode 100644
index 72a9b83..0000000
--- a/rt/html/REST/1.0/ticket/comment
+++ /dev/null
@@ -1,177 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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/1/comment
-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;
-if ($k->{Status}) {
- my ($status_n, $status_s) = $ticket->SetStatus($k->{'Status'} );
- $output .= "\n".$status_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
deleted file mode 100644
index 7b06546..0000000
--- a/rt/html/REST/1.0/ticket/link
+++ /dev/null
@@ -1,123 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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#^/REST/1.0/ticket/link##;
-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 link: '$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";
-} else {
- my $action = $del ? "Deleted" : "Created";
- $output .= " $action link " . $ticket->Id . " $rel $to";
-}
-
-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
deleted file mode 100644
index 18d671b..0000000
--- a/rt/html/REST/1.0/ticket/merge
+++ /dev/null
@@ -1,102 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# 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/RTx/Statistics/CallsMultiQueue/Elements/Chart b/rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart
new file mode 100755
index 0000000..02a183b
--- /dev/null
+++ b/rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart
@@ -0,0 +1,39 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+</%perl>
+<em><&|/l, $#data+1&>[_1] Plot Elements</&></em><p>
+% foreach my $value (@data) {
+<% $value %><p>
+% }
+<em><&|/l&>x_labels</&>:</em><p>
+<% $ARGS{x_labels} %>
+<p>
+<em><&|/l&>legend</&>:</em><p>
+<% $ARGS{set_legend} %>
+<p>
+<em><&|/l, (keys %ARGS) - 2&>[_1] data sets</&>:</em><p>
+
+% for (1..(scalar keys %ARGS)-2) {
+<% $_ %> <% $ARGS{"data$_"} %><p>
+% }
+
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+ x_label => 'Day of Week',
+ y_label => 'Tickets per day');
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+my $format = $graph->export_format;
+push @data, [split /,/ , $ARGS{x_labels}];
+for (1..((scalar keys %ARGS)-2)) {
+ push @data, [split /,/ , $ARGS{"data".$_}];
+}
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/CallsMultiQueue/index.html b/rt/html/RTx/Statistics/CallsMultiQueue/index.html
new file mode 100755
index 0000000..abf8aa7
--- /dev/null
+++ b/rt/html/RTx/Statistics/CallsMultiQueue/index.html
@@ -0,0 +1,330 @@
+<& /Elements/Header, Title => loc('Tickets per day in Multiple queues') &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc('Tickets per day in Multiple Queues by status') &>
+
+<h3>Description</h3>
+<p>This chart shows details of tickets per day by their status. You can select multiple queues to display at the same time, but only one status. You can chose any of the defined status values.
+There is also the option to display all available queues at the same time.
+The default display shows tickets resolved in your default queue (General unless altered locally).
+The line chart below shows the same information in a graphical form.
+
+<br />
+
+<form method="POST" action="index.html">
+
+%# Build Legend
+% my @legend;
+% for (sort keys %queues_to_show) {
+% push @legend, $_;
+% }
+
+%my $title = "Tickets with Status $status in " . join(', ', @queues) . ", per day from " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+
+<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%">
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header,
+ Format => \@RowFormat,
+ FormatString => $RowFormat,
+ AllowSorting => $AllowSorting,
+ Order => $Order,
+ Query => undef,
+ Rows => $Rows,
+ Page => $Page,
+ OrderBy => $OrderBy ,
+ BaseURL => $BaseURL,
+ maxitems => $maxitems &>
+% }
+% my $line = 0;
+% LINE: for my $d (0..$#dates) {
+% if ($d == $#dates ){
+% next LINE;
+% }
+% $line++;
+% my $x = 1;
+% $values{Statistics_Date} = Statistics::FormatDate($dateformat, $dates[$d]);
+% my $row_total=0;
+% foreach my $q (sort keys %queues_to_show) {
+% my $tix = new RT::Tickets($session{'CurrentUser'});
+% if ($status eq "resolved") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% elsif ($status eq "new") {
+% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% elsif ($status eq "deleted") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% elsif ($status eq "stalled") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% elsif ($status eq "open") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% elsif ($status eq "rejected") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% }
+% $tix->LimitQueue (VALUE => $q);
+% $values{$q} = $tix->Count;
+% $row_total += $tix->Count;
+% $data[$x++][$d] = $tix->Count;
+% }
+% $values{Statistics_Totals} = $row_total;
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &>
+% }
+</table>
+<& /Elements/TitleBoxEnd&>
+
+<hr>
+
+<BR />
+<BR />
+
+<!-- <td>Show:</td>
+ <td COLSPAN=2><SELECT NAME="status">
+% for (qw(resolved new deleted stalled rejected open)) {
+ <OPTION VALUE="<% $_ %>" <% $_ eq $status && "SELECTED" %>>
+ <% loc($_) %></OPTION>
+% }
+--!>
+
+<%perl>
+# Create the graph URL
+my $url = 'Elements/Chart?x_labels=';
+#$url .= join ",", @{ shift @data } . "&";
+for (0..$max) {
+ $url .= $m->interp->apply_escapes($data[0][$_],'u') . ",";
+}
+chop $url;
+$url .= "&";
+shift @data;
+$url .= 'set_legend='.(join ",", @legend)."&";
+for (0..$#data) {
+ $url .= "data".(1+$_)."=". (join ",", @{$data[$_]})."&";
+}
+chop $url;
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox,
+ Title => "Change Status, Queues or Dates",
+ ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+ eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+ weekends => $weekends,
+ ShowMultiQueues => 1, queues_ref => \@queues,
+ ShowStatus => 1, Status => $status
+ &>
+
+</form>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a>
+%# | <a href="<%$RT::WebPath%>/RTx/Statistics/CallsMultiQueue/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+<%ARGS>
+$status => $Statistics::MultiQueueStatus
+$max => $Statistics::MultiQueueMaxRows
+@queues => @Statistics::MultiQueueQueueList
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$dateformat => $Statistics::MultiQueueDateFormat
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+$AddAllCheck => undef
+</%ARGS>
+
+<%INIT>
+
+use RTx::Statistics;
+use Time::Local;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $selected;
+my $diff;
+my %queues_to_show;
+my $secsPerDay=86400;
+my $sEpoch;
+my $eEpoch;
+my $QueryString;
+my $maxitems;
+my $RowFormat;
+my $BoldRowFormat;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+ Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+
+ # Handle the Add All Checkbox
+ if($AddAllCheck eq "on") {
+ $AddAllCheck = undef;
+ undef (@queues);
+ my $q=new RT::Queues($session{'CurrentUser'});
+ $q->UnLimit;
+ while (my $queue=$q->Next) {
+ next if !$queue->CurrentUserHasRight('SeeQueue');
+ push @queues, $queue->Name;
+ }
+ }
+
+ # If the user has the right to see the queue, put it into the map
+ for my $q (@queues) {
+ my $Queueobj = new RT::Queue($session{'CurrentUser'});
+ $Queueobj->Load($q);
+ next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+ $queues_to_show{$q} = 1;
+ }
+
+ $maxitems = (scalar @queues) + 2;
+
+ # Build the format strings
+ $RowFormat = "'__Statistics_Date__'";
+ $BoldRowFormat = "'<B>__Statistics_Date__</B>'";
+ for my $q (@queues) {
+ $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+ $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+ }
+ $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+ $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+ # Parse the formats into structures.
+ my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat);
+ my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat);
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+ $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+ $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+ $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+ # This case happens when the page is first loaded
+ my @local = localtime(time);
+ ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+ $eYear += 1900;
+ $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+ # We have an end, but not a start, or, overlapping.
+
+ # if $currentMonth is set, just set the day to 1
+ if($currentMonth) {
+ # set start vars from end, but with day set to 1
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+ $sDay=1;
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+ } else {
+ # If the user has specified how many days back to go, use that,
+ # If not, set start to configured default period before end
+ if(defined $days) {
+ $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+ } else {
+ $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+ }
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+ }
+ $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+# Build the new query string
+$QueryString = "queues=" . join("&queues=", @queues);
+$QueryString .= "&sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends";
+
+
+
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+$n = 0;
+until ($#dates == $diff) {
+ my $date = new RT::Date($session{CurrentUser});
+ $date->Set(Value=>$endRange - $n, Format => 'unix');
+ # Note: we used to adjust the time to local midnight, but
+ # none of the other date entry fields in RT seem to adjust, so we've stopped.
+ #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+ $n+= $Statistics::secsPerDay;
+ # If we aren't showing weekends and this is one, decrement the number
+ # of days to show and skip to the next date.
+ if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+ unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+ unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+
+# We put an extra day into the lists to cover up till midnight of the next day,
+# But we don't want that to appear in the labels, so pop it off.
+pop( @{ $data[0] } );
+
+my $queue = new RT::Queues($session{CurrentUser});
+$queue->UnLimit;
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($queue);
+</%INIT>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart b/rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart
new file mode 100755
index 0000000..9a3a505
--- /dev/null
+++ b/rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart
@@ -0,0 +1,29 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+ x_label => 'Day of Week',
+ y_label => 'Tickets per Day',
+ x_labels_vertical => 1,
+ );
+my $format = $graph->export_format;
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/Results.tsv b/rt/html/RTx/Statistics/CallsQueueDay/Results.tsv
new file mode 100644
index 0000000..23f0c69
--- /dev/null
+++ b/rt/html/RTx/Statistics/CallsQueueDay/Results.tsv
@@ -0,0 +1,191 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Queue => undef
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my @dates;
+my $n = 0;
+my %Totals;
+my $now = new RT::Date($session{CurrentUser});
+my $sEpoch;
+my $eEpoch;
+
+if (!defined $Queue) {
+ $Queue = $Statistics::PerDayQueue;
+}
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+ $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+ $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+ $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+ # This case happens when the page is first loaded
+ my @local = localtime(time);
+ ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+ $eYear += 1900;
+ $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+ # We have an end, but not a start, or, overlapping.
+
+ # if $currentMonth is set, just set the day to 1
+ if($currentMonth) {
+ # set start vars from end, but with day set to 1
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+ $sDay=1;
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+ } else {
+ # If the user has specified how many days back to go, use that,
+ # If not, set start to configured default period before end
+ if(defined $days) {
+ $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+ } else {
+ $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+ }
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+ }
+ $sYear += 1900;
+}
+
+# set content type
+$r->content_type('application/vnd.ms-excel');
+
+# Put out some data about the generation of this file
+$m->out("Tickets per day for Queue:\t" . $Queue . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n");
+
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+my $diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+
+# Build array of dates
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+until ($#dates == $diff) {
+ my $date = new RT::Date($session{CurrentUser});
+ $date->Set(Value=>$endRange - $n, Format => 'unix');
+ # Note: we used to adjust the time to local midnight, but
+ # none of the other date entry fields in RT seem to adjust, so we've stopped.
+ #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+ $n+= $Statistics::secsPerDay;
+ # If we aren't showing weekends and this is one, decrement the number
+ # of days to show and skip to the next date.
+ if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+ unshift @dates, $date;
+}
+
+# Output header row
+$m->out("Date\tcreate\tresolved\tdeleted\n");
+
+
+LINE: for my $d (0..$#dates) {
+ if ($d == $#dates){
+ next LINE;
+ }
+ my $x = 1;
+ # Output the date for this row
+ $m->out(Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]));
+
+ # output the 3 columns for this row
+ for my $status (qw(created resolved deleted)) {
+ my $tix = new RT::Tickets($session{'CurrentUser'});
+ if ($status eq "created") {
+ $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+ if ($dates[$d+1]) {
+ $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+ }
+ } elsif ($status eq "resolved") {
+ $tix->LimitStatus(VALUE => $status);
+ $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+ if ($dates[$d+1]) {
+ $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+ }
+ } elsif ($status eq "deleted") {
+ $tix->LimitStatus(VALUE => $status);
+ $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+ if ($dates[$d+1]) {
+ $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+ }
+ }
+ $tix->LimitQueue (VALUE => $Queue);
+ $m->out( "\t" . $tix->Count );
+ $Totals{$status} += $tix->Count;
+ }
+ $m->out("\n");
+}
+
+# Output the totals
+$m->out("Totals\t$Totals{created}\t$Totals{resolved}\t$Totals{deleted}\n");
+
+$m->abort();
+</%INIT>
diff --git a/rt/html/RTx/Statistics/CallsQueueDay/index.html b/rt/html/RTx/Statistics/CallsQueueDay/index.html
new file mode 100755
index 0000000..06fc484
--- /dev/null
+++ b/rt/html/RTx/Statistics/CallsQueueDay/index.html
@@ -0,0 +1,275 @@
+<& /Elements/Header, Title => loc("Tickets per day in Queue:" . $QueueObj->Name()) &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc("Tickets by status per day in Queue:" . $QueueObj->Name()) &>
+
+<h3>Description</h3>
+<p>This page displays details about tickets in the selected queue over the date range chosen. It shows how many tickets were created on
+each day in the chosen range, and how many of those were either Resolved or Deleted.</p>
+<p>To always show the current month to date, bookmark this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?currentMonth=1">link</a>, or
+for a spreadsheet, use this <a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?currentMonth=1">link</a>.</p>
+
+<form method="POST" action="index.html">
+
+% Statistics::DebugLog("queue name=" . $QueueObj->Name() . "\n");
+
+%my $title = "Ticket counts in " . $QueueObj->Name() . " by status per day from " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+<&|/Elements/TitleBox,
+ title => $title,
+ title_href => "/RTx/Statistics/CallsQueueDay/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header,
+ Format => \@Format,
+ FormatString => $Format,
+ AllowSorting => $AllowSorting,
+ Order => $Order,
+ Query => undef,
+ Rows => $Rows,
+ Page => $Page,
+ OrderBy => $OrderBy ,
+ BaseURL => $BaseURL,
+ maxitems => $maxitems &>
+% }
+% my $line = 1;
+% LINE: for my $d (0..$#dates) {
+% if ($d == $#dates){
+% next LINE;
+% }
+% my $x = 1;
+% $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]);
+%# NOTE need to handle all status values here....
+% for my $status (qw(created resolved deleted)) {
+% my $tix = new RT::Tickets($session{'CurrentUser'});
+% $tix->LimitQueue (VALUE => $Queue);
+% if ($status eq "created") {
+% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% $values{Statistics_Created_Count} = $tix->Count;
+% $Totals{Statistics_Created_Count} += $tix->Count;
+% }
+% elsif ($status eq "resolved") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitResolved(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitResolved(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% $values{Statistics_Resolved_Count} = $tix->Count;
+% $Totals{Statistics_Resolved_Count} += $tix->Count;
+% }
+% elsif ($status eq "deleted") {
+% $tix->LimitStatus(VALUE => $status);
+% $tix->LimitLastUpdated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitLastUpdated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% $values{Statistics_Deleted_Count} = $tix->Count;
+% $Totals{Statistics_Deleted_Count} += $tix->Count;
+% }
+% $data[$x++][$d] = $tix->Count;
+% }
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+% $line++;
+% }
+% $values {Statistics_Date} = "Totals";
+% $values {Statistics_Created_Count} = $Totals{Statistics_Created_Count};
+% $values {Statistics_Resolved_Count} = $Totals{Statistics_Resolved_Count};
+% $values {Statistics_Deleted_Count} = $Totals{Statistics_Deleted_Count};
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &>
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+# Create the graph URL
+my $url= 'Elements/Chart?x_labels=';
+for (1..$diff) {
+ $url .= $data[0][$_] . ",";
+}
+chop $url;
+$url .= "&";
+shift @data;
+for (0..$#data) {
+ $url .= "data".(1+$_)."=".(join ",", @{$data[$_]})."&";
+}
+chop $url;
+$url .= "&set_legend=Created,Resolved,Deleted";
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox,
+ Title => "Change Queue or Dates",
+ ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+ eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+ weekends => $weekends,
+ ShowSingleQueue => 1, Queue => $Queue
+ &>
+
+</form>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a> |
+<a href="<%$RT::WebPath%>/RTx/Statistics/CallsQueueDay/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+
+% Statistics::DebugLog("ref of eMonth is " . ref($eMonth) . "\n");
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+$Queue => undef
+$weekends => $Statistics::PerDayWeekends;
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my $selected;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $diff;
+my $sEpoch=0;
+my $eEpoch=0;
+my %Totals;
+my $QueryString;
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+
+# If debugging, set things up and display all the args
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+ Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $Format = qq{ Statistics_Date,
+ '__Statistics_Created_Count__/STYLE:text-align:right;',
+ '__Statistics_Resolved_Count__/STYLE:text-align:right;',
+ '__Statistics_Deleted_Count__/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>',
+ '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' };
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n");
+
+if (!defined $Queue) {
+ my $QueueObj = new RT::Queue($session{'CurrentUser'});
+ $QueueObj->Load($Statistics::PerDayQueue);
+ $Queue = $QueueObj->Id();
+}
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+ $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+ $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+ $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+ # This case happens when the page is first loaded
+ my @local = localtime(time);
+ ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+ $eYear += 1900;
+ $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+ # We have an end, but not a start, or, overlapping.
+
+ # if $currentMonth is set, just set the day to 1
+ if($currentMonth) {
+ # set start vars from end, but with day set to 1
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+ $sDay=1;
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+ } else {
+ # If the user has specified how many days back to go, use that,
+ # If not, set start to configured default period before end
+ if(defined $days) {
+ $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+ } else {
+ $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+ }
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+ }
+ $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+# Set up the string for the current query for bookmarkable link
+$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue";
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+$n = 0;
+until ($#dates == $diff) {
+ my $date = new RT::Date($session{CurrentUser});
+ $date->Set(Value=>$endRange - $n, Format => 'unix');
+ # Note: we used to adjust the time to local midnight, but
+ # none of the other date entry fields in RT seem to adjust, so we've stopped.
+ #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+ $n+= $Statistics::secsPerDay;
+ # If we aren't showing weekends and this is one, decrement the number
+ # of days to show and skip to the next date.
+ if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+ unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+ unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+
+# We put an extra day into the lists to cover up till midnight of the next day,
+# But we don't want that to appear in the labels, so pop it off.
+pop( @{ $data[0] } );
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/DayOfWeek/Elements/Chart b/rt/html/RTx/Statistics/DayOfWeek/Elements/Chart
new file mode 100755
index 0000000..239c095
--- /dev/null
+++ b/rt/html/RTx/Statistics/DayOfWeek/Elements/Chart
@@ -0,0 +1,26 @@
+% $r->content_type("image/$format");
+% $m->print($graph->plot(\@data)->$format());
+% $m->abort();
+<&|/l, $#data+1&>[_1] Elements</&>:<p>
+% for (0..$#data) {
+<% $data[$_] %><p>
+% }
+<%INIT>
+use GD::Graph::bars;
+
+my @data;
+my $graph = GD::Graph::bars->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+ x_label => 'Day of Week',
+ y_label => 'Ticket actions per Day by type');
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/DayOfWeek/index.html b/rt/html/RTx/Statistics/DayOfWeek/index.html
new file mode 100755
index 0000000..2e82b9c
--- /dev/null
+++ b/rt/html/RTx/Statistics/DayOfWeek/index.html
@@ -0,0 +1,155 @@
+<& /Elements/Header, Title =>loc('Tickets by Day Of Week in Queue:' . $QueueObj->Name()) &>
+<& /RTx/Statistics/Elements/Tabs, Title =>loc('Trends in ticket status by Day Of Week in Queue:' . $QueueObj->Name()) &>
+
+<h3>Description</h3>
+<p>The purpose of this page is to show historical trends for each day of the week.
+It displays details of number of tickets created in your
+selected queue for each day. It also hows how many of those created tickets were Resolved or Deleted</p>
+
+<form method="POST" action="index.html">
+
+
+%my $title = "Ticket counts by day of week in " . $QueueObj->Name();
+<&|/Elements/TitleBox,
+ title => $title,
+ title_href => "/RTx/Statistics/DayOfWeek/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header,
+ Format => \@Format,
+ FormatString => $Format,
+ AllowSorting => $AllowSorting,
+ Order => $Order,
+ Query => undef,
+ Rows => $Rows,
+ Page => $Page,
+ OrderBy => $OrderBy ,
+ BaseURL => $BaseURL,
+ maxitems => $maxitems &>
+% }
+% my $line = 1;
+% for my $d (0..$#days) {
+% my $x = 1;
+% $values{Statistics_Date} = $days[$d];
+%# NOTE Show all status values???
+% $values{Statistics_Created_Count} = $counts[$d]{new};
+% $values{Statistics_Resolved_Count} = $counts[$d]{resolved};
+% $values{Statistics_Deleted_Count} = $counts[$d]{deleted};
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+% $line++;
+% }
+% $values {Statistics_Date} = "Totals";
+% $values {Statistics_Created_Count} = $Totals{new};
+% $values {Statistics_Resolved_Count} = $Totals{resolved};
+% $values {Statistics_Deleted_Count} = $Totals{deleted};
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldFormat, i => $line, record => $record, maxitems => $maxitems &>
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+my $url = 'Elements/Chart?&x_labels=';
+for (0..$#days) {
+ $url .= $days[$_] . "," ;
+}
+chop $url;
+$url .= "&";
+
+my @things = qw(new resolved deleted);
+for my $th (0..$#things) {
+ $url .= "data".(1+$th)."=".(join ",", map { $counts[$_]{$things[$th]} } (0..6))."&";
+}
+chop $url;
+$url .= '&set_legend=Created,Resolved,Deleted';
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+% Statistics::DebugLog("queue name=" . $QueueObj->Id() . "\n");
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox,
+ Title => "Change Queue",
+ ShowSingleQueue => 1, Queue => $QueueObj->Id()
+ &>
+
+</form>
+
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+$Queue => $Statistics::DayOfWeekQueue
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use GD::Graph;
+use RTx::Statistics;
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+my %Totals = (
+ resolved => 0,
+ deleted => 0,
+ new => 0
+);
+my $QueryString = "Queue=$Queue";
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+my $Format = qq{ Statistics_Date,
+ '__Statistics_Created_Count__/STYLE:text-align:right;',
+ '__Statistics_Resolved_Count__/STYLE:text-align:right;',
+ '__Statistics_Deleted_Count__/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>',
+ '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Deleted_Count__</B>/STYLE:text-align:right;' };
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+$RT::Logger->warning("Loaded queue $Queue, name=". $QueueObj->Name());
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $Queue);
+$tix->UnLimit;
+if ($tix->Count) {
+ # Initialize the counters to zero, so that all the cells show up
+ foreach my $day (0..@days) {
+ $counts[$day]{resolved} = 0;
+ $counts[$day]{deleted} = 0;
+ $counts[$day]{new} = 0;
+ }
+ while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK
+ if($t->Status eq "resolved") {
+ $counts[(localtime($t->ResolvedObj->Unix))[6]]{resolved}++;
+ $Totals{resolved}++;
+ }
+ if($t->Status eq "deleted") {
+ $counts[(localtime($t->LastUpdatedObj->Unix))[6]]{deleted}++;
+ $Totals{deleted}++;
+ }
+ $counts[(localtime($t->CreatedObj->Unix))[6]]{new}++;
+ $Totals{new}++;
+ }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/DurationAsString b/rt/html/RTx/Statistics/DurationAsString
new file mode 100755
index 0000000..c0b4d9a
--- /dev/null
+++ b/rt/html/RTx/Statistics/DurationAsString
@@ -0,0 +1,18 @@
+<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%>
+<%INIT>
+
+my $MINUTE = 60;
+my $HOUR = $MINUTE*60;
+my $DAY = $HOUR * 24;
+my $WEEK = $DAY * 7;
+my $days = int($Duration / $DAY);
+$Duration = $Duration % $DAY;
+my $hours = int($Duration / $HOUR);
+$hours = sprintf("%02d", $hours);
+$Duration = $Duration % $HOUR;
+my $minutes = int($Duration/$MINUTE);
+$minutes = sprintf("%02d", $minutes);
+</%INIT>
+<%ARGS>
+$Duration => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/Header b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Header
new file mode 100644
index 0000000..cecb02e
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Header
@@ -0,0 +1,126 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+@Format => undef
+$FormatString => undef
+$AllowSorting => undef
+$Order=>undef
+$BaseURL => undef
+$Query => undef
+$Rows => undef
+$Page => undef
+$maxitems => undef
+</%ARGS>
+<TR class="collection-as-table">
+<%perl>
+
+my %generic_query_args = ( Query => $Query, Rows => $Rows, Page => $Page, Format => $FormatString );
+
+my $item = 0;
+foreach my $col (@Format) {
+ $item++;
+ if ( $col->{title} eq 'NEWLINE' ) {
+ while ( $item < $maxitems ) {
+ $m->out(qq{<th class="collection-as-table">&nbsp;</th>\n});
+ $item++;
+ }
+
+ $item = 0;
+ $m->out(qq{</TR>\n<TR class="collection-as-table">});
+ }
+ else {
+ $m->out('<TH class="collection-as-table" ');
+ $m->out( 'align="' . $col->{align} . '"' ) if ( $col->{align} );
+ $m->out( 'style="' . $col->{style} . '"' ) if ( $col->{style} );
+ $m->out('>');
+ my $title = $col->{title};
+ $title =~ s/^__(.*)__$/$1/o;
+ $title = (
+ $m->comp(
+ '/RTx/Statistics/Elements/StatColumnMap',
+ Name => $title,
+ Attr => 'title'
+ )
+ || $title
+ );
+ if (
+ $AllowSorting
+ && $col->{'attribute'}
+ && $m->comp(
+ '/RTx/Statistics/Elements/StatColumnMap',
+ Name => $col->{'attribute'},
+ Attr => 'attribute'
+ )
+ )
+ {
+
+ $m->out(
+ '<a href="' . $BaseURL
+ . $m->comp(
+ '/Elements/QueryString',
+ %generic_query_args,
+ OrderBy => (
+ $m->comp(
+ '/RTx/Statistics/Elements/StatColumnMap',
+ Name => $col->{'attribute'},
+ Attr => 'attribute'
+ )
+ || $col->{'attribute'}
+ ),
+ Order => ( $ARGS{'Order'} eq 'ASC' ? 'DESC' : 'ASC' )
+ )
+ . '">'
+ . loc($title) . '</a>'
+ );
+ }
+ else {
+ $m->out( loc($title) );
+ }
+ $m->out('</TH>');
+ }
+}
+</%perl>
+</TR>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat b/rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat
new file mode 100644
index 0000000..a482f81
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat
@@ -0,0 +1,109 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Format
+</%ARGS>
+
+<%init>
+use Regexp::Common;
+my @Columns;
+
+while ($Format =~ /($RE{delimited}{-delim=>qq{\'"}}|[{}\w.]+)/go) {
+ my $col = $1;
+
+ if ($col =~ /^$RE{quoted}$/o) {
+ substr($col,0,1) = "";
+ substr($col,-1,1) = "";
+ }
+
+ my $colref;
+
+ # kfh at mqsoftware.com added this to be able
+ # to create columns where the actual heading and value
+ # aren't know ahead of time. For instance queue names.
+ # it will work with subcols, but all subcols will have the same KEY
+ if ( $col =~ s!/KEY:([^/]+)!!io ) {
+ $colref->{'keyname'} = $1;
+ }
+ if ( $col =~ s!/STYLE:([^/]+)!!io ) {
+ $colref->{'style'} = $1;
+ }
+ if ( $col =~ s!/CLASS:([^/]+)!!io ) {
+ $colref->{'class'} = $1;
+ }
+ if ( $col =~ s!/TITLE:([^/]+)!!io ) {
+ $colref->{'title'} = $1;
+ }
+ if ( $col =~ s!/ALIGN:([^\/]+)!!io ) {
+ $colref->{'align'} = $1;
+ }
+ if ( $col =~ /__(.*?)__/gio ) {
+ my @subcols;
+ while ( $col =~ s/^(.*?)__(.*?)__//o ) {
+ push ( @subcols, $1 ) if ($1);
+ push ( @subcols, "__$2__" );
+ $colref->{'attribute'} = $2;
+ }
+ push ( @subcols, $col );
+ @{ $colref->{'output'} } = @subcols;
+ }
+ else {
+ @{ $colref->{'output'} } = ( "__" . $col . "__" );
+ $colref->{'attribute'} = $col;
+ }
+
+ if ( !$colref->{'title'} && grep { /^__(.*?)__$/io }
+ @{ $colref->{'output'} } )
+ {
+ $colref->{'title'} = $1;
+ $colref->{'attribute'} = $1;
+ }
+
+
+ push @Columns, $colref;
+}
+ return(@Columns);
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/CollectionAsTable/Row b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Row
new file mode 100644
index 0000000..bcfabe5
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/CollectionAsTable/Row
@@ -0,0 +1,112 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$i => undef
+@Format => undef
+$record => undef
+$maxitems => undef
+$Depth => undef
+$Warning => undef
+</%ARGS>
+
+<%PERL>
+$m->out('<TR class="' . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) . '" >' );
+my $item;
+foreach my $column (@Format) {
+ if ( $column->{title} eq 'NEWLINE' ) {
+ while ( $item < $maxitems ) {
+ $m->out(qq{<td class="collection-as-table">&nbsp;</td>\n});
+ $item++;
+ }
+ $item = 0;
+ $m->out('</TR>');
+ $m->out('<TR class="'
+ . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' )
+ . '" >' );
+ next;
+ }
+ $item++;
+ $m->out('<td class="collection-as-table" ');
+ $m->out( 'align="' . $column->{align} . '"' ) if ( $column->{align} );
+ $m->out( 'style="' . $column->{style} . '"' ) if ( $column->{style} );
+ $m->out('>');
+ foreach my $subcol ( @{ $column->{output} } ) {
+ if ( $subcol =~ /^__(.*?)__$/o ) {
+ my $col = $1;
+ my $value = $m->comp(
+ '/RTx/Statistics/Elements/StatColumnMap',
+ Name => $col,
+ Attr => 'value'
+ );
+ my @out;
+
+ if ( $value && ref($value) ) {
+
+ # All HTML snippets are returned by the callback function
+ # as scalar references. Data fetched from the objects are
+ # plain scalars, and needs to be escaped properly.
+ @out =
+ map {
+ ref($_) ? $$_ : $m->interp->apply_escapes( $_ => 'h' )
+ } &{$value}( $record, $i, $column->{keyname} );
+ ;
+ }
+ else {
+
+ # Simple value; just escape it.
+ @out = $m->interp->apply_escapes( $value => 'h' );
+ }
+ s/\n/<br>/gs for @out;
+ $m->out( @out );
+ }
+ else {
+ $m->out($subcol);
+ }
+ }
+ $m->out('</td>');
+}
+$m->out('</TR>');
+</%PERL>
diff --git a/rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox b/rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox
new file mode 100644
index 0000000..ce043e2
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox
@@ -0,0 +1,103 @@
+<table class="box" bgcolor="#336699" style="border-style:none solid solid solid;border-width:1px;border-color:#2E2E8C;" cellpadding="0" cellspacing="0">
+ <tbody>
+ <tr>
+ <th style="color: rgb(51, 102, 153);" class="titlebox">
+ <span class="titleboxclose">
+ <a href="#" onclick="hideshow('stats_control')">X</a></span>&nbsp;
+ <span class="titleboxtitle" style="color: rgb(255, 255, 255);">
+ <b><% $Title %></b></span>
+ </th>
+ <th style="color: rgb(51, 102, 153);" class="titleboxright">
+ <span class="titleboxright">&nbsp;</span>
+ </th>
+ </tr>
+ <tr id="element-stats_control">
+ <td colspan="3" class="" bgcolor="#dddddd">
+ <table border="0" cellpadding="1" cellspacing="0">
+% if (defined $ShowStatus) {
+ <tr>
+ <td class="collection-as-table" style="text-align:left;">Show Status:</td>
+ <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+ <& /Elements/SelectStatus, Name=>"status", Default => $Status, DefaultValue => undef &>
+ </td>
+ </tr>
+% }
+% if (defined $ShowSingleQueue) {
+ <tr>
+ <td class="collection-as-table" style="text-align:left;">Show Queue:</td>
+ <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+ <& /Elements/SelectQueue, Name=>"Queue", Default=>$Queue ,ShowNullOption=>0,
+ CheckQueueRight=>'SeeQueue' &>
+ </td>
+ </tr>
+% }
+% if (defined $ShowDates) {
+ <tr>
+ <& /RTx/Statistics/Elements/DateSelectRow, Label => "Start Date:",
+ refMonth => $sMonth, nameMonth => "sMonth",
+ refDay => $sDay, nameDay => "sDay",
+ refYear => $sYear, nameYear => "sYear" &>
+ </tr>
+ <tr>
+ <& /RTx/Statistics/Elements/DateSelectRow, Label => "End Date:",
+ refMonth => $eMonth, nameMonth => "eMonth",
+ refDay => $eDay, nameDay => "eDay",
+ refYear => $eYear, nameYear => "eYear" &>
+ </tr>
+ <tr>
+ <td class="collection-as-table" style="text-align:left;">Show Weekends:</td>
+ <td class="collection-as-table" style="text-align:left;">
+ <select name=weekends>
+ <option value=0 <% (!$weekends) && 'selected' %> >No</option>
+ <option value=1 <% $weekends && 'selected' %> >Yes</option>
+ </select>
+ </td>
+ </tr>
+% }
+% if (defined $ShowMultiQueues) {
+ <tr>
+% if (defined $ShowDates) {
+%# If we're showing the dates, we put these side by side.
+ <td COLSPAN=2 class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td>
+ <td COLSPAN=3 class="collection-as-table" >
+ <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref,
+ ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &>
+ </td>
+% } else {
+ <td COLSPAN=3 class="collection-as-table" style="text-align:left;">
+ <& /RTx/Statistics/Elements/SelectMultiQueue, Name=>"queues", Selected=>$queues_ref,
+ ShowNullOption=>0, CheckQueueRight=>'SeeQueue', Size => 10, NamedValues => 1 &>
+ </td>
+ </tr>
+ <tr>
+ <td class="collection-as-table" style="text-align:left;">Select All Queues: <input type=checkbox name="AddAllCheck"></td>
+% }
+ </tr>
+% }
+ <& /RTx/Statistics/Elements/ControlsAsTable/UpdatePage &>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<BR>
+<%args>
+$Title => undef
+$ShowMultiQueues => undef
+$queues_ref => undef
+$ShowDates => undef
+$sMonth => undef
+$sDay => undef
+$sYear => undef
+$eMonth => undef
+$eDay => undef
+$eYear => undef
+$weekends => undef
+$ShowSingleQueue => undef
+$Queue => undef
+$ShowStatus => undef
+$Status => undef
+</%args>
+
diff --git a/rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage b/rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage
new file mode 100644
index 0000000..b4ccfd5
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage
@@ -0,0 +1,5 @@
+<tr>
+ <td colspan="4" style="text-align:center;padding-top:3px; background-color:#C8C8C8;">
+ <INPUT TYPE="submit" VALUE="<&|/l&>Update Page</&>">
+ </td>
+</tr>
diff --git a/rt/html/RTx/Statistics/Elements/DateSelectRow b/rt/html/RTx/Statistics/Elements/DateSelectRow
new file mode 100644
index 0000000..325e168
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/DateSelectRow
@@ -0,0 +1,55 @@
+ <td class="collection-as-table" style="text-align:left;"><% $Label %></td>
+ <td class="collection-as-table" style="text-align:left;">
+ <select name=<% $nameMonth %> >
+% for ($n=0;$n<=$#Statistics::months;$n++){
+% if ($$refMonth eq $n){
+% $selected ="selected";
+% }else {
+% $selected ="";
+% }
+ <option value=<% $n %> <% $selected %> ><% $Statistics::months[$n] %></option>
+%}
+ </select>
+ </td>
+ <td class="collection-as-table" style="text-align:left;">
+ <select name=<% $nameDay %> >
+% for ($n=1;$n<=31;$n++){
+% if ($$refDay == $n ){
+% $selected ="selected";
+% }else {
+% $selected ="";
+% }
+ <option value=<% $n %> <% $selected %> ><% $n %></option>
+% }
+ </select>
+ </td>
+ <td class="collection-as-table" style="text-align:left;">
+ <select name=<% $nameYear %> >
+%
+% for ($n=0;$n <= scalar @Statistics::years-1;$n++){
+% if ($Statistics::years[$n] == $$refYear){
+% $selected ="selected";
+% }else{
+% $selected ="";
+% }
+ <option value=<% $Statistics::years[$n] %> <% $selected %> ><% $Statistics::years[$n] %></option>
+% }
+ </select>
+ </td>
+
+
+<%args>
+$Label => undef
+$refMonth => undef
+$nameMonth => undef
+$refDay => undef
+$nameDay => undef
+$refYear => undef
+$nameYear => undef
+</%args>
+<%init>
+use RTx::Statistics;
+my $n;
+my $selected;
+
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/DurationAsString b/rt/html/RTx/Statistics/Elements/DurationAsString
new file mode 100755
index 0000000..c0b4d9a
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/DurationAsString
@@ -0,0 +1,18 @@
+<%$days|'00'%> days <%$hours|'00'%>:<%$minutes|'00'%>
+<%INIT>
+
+my $MINUTE = 60;
+my $HOUR = $MINUTE*60;
+my $DAY = $HOUR * 24;
+my $WEEK = $DAY * 7;
+my $days = int($Duration / $DAY);
+$Duration = $Duration % $DAY;
+my $hours = int($Duration / $HOUR);
+$hours = sprintf("%02d", $hours);
+$Duration = $Duration % $HOUR;
+my $minutes = int($Duration/$MINUTE);
+$minutes = sprintf("%02d", $minutes);
+</%INIT>
+<%ARGS>
+$Duration => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Elements/GraphBox b/rt/html/RTx/Statistics/Elements/GraphBox
new file mode 100644
index 0000000..3dc0697
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/GraphBox
@@ -0,0 +1,27 @@
+<div style="float:left; padding-right:30px;">
+<table class="box" bgcolor="#336699" style="border-style:none solid solid solid;border-width:1px;border-color:#2E2E8C;" cellpadding="0" cellspacing="0">
+ <tbody><tr>
+ <th style="color: rgb(51, 102, 153);" class="titlebox">
+ <span class="titleboxclose">
+ <a href="#" onclick="hideshow('stats_chart')">X</a></span>&nbsp;
+
+ <span class="titleboxtitle">
+ <b><a href="<% $GraphURL %>">Download Chart as Image</a></b>
+ </span>
+ </th>
+ <th style="color: rgb(51, 102, 153);" class="titleboxright">
+ <span class="titleboxright">&nbsp;</span>
+ </th>
+ </tr>
+
+ <tr id="element-stats_chart">
+ <td colspan="3" class="" bgcolor="#dddddd">
+ <img src="<% $GraphURL %>" ALT="Result Graph" >
+ </td>
+ </tr>
+ </tbody>
+</table>
+</div>
+<%args>
+$GraphURL => undef
+</%args>
diff --git a/rt/html/RTx/Statistics/Elements/SelectMultiQueue b/rt/html/RTx/Statistics/Elements/SelectMultiQueue
new file mode 100755
index 0000000..637f6dc
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/SelectMultiQueue
@@ -0,0 +1,81 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<SELECT NAME ="<%$Name%>" multiple size="<% $Size %>">
+% if ($ShowNullOption) {
+<OPTION VALUE="">-</OPTION>
+% }
+% while (my $queue=$q->Next) {
+% if ($ShowAllQueues || $queue->CurrentUserHasRight($CheckQueueRight)) {
+% my $targ="," . $queue->Name . ",";
+<OPTION VALUE="<%($NamedValues ? $queue->Name : $queue->Id) %>" <%( ($sel_list =~ m/$targ/) ? 'SELECTED' : '')%>><%$queue->Name%>
+% if (($Verbose) and ($queue->Description) ){
+(<%$queue->Description%>)
+% }
+</OPTION>
+% }
+% }
+</SELECT>
+<%ARGS>
+$CheckQueueRight => 'CreateTicket'
+$ShowNullOption => 1
+$ShowAllQueues => 1
+$Name => undef
+$Verbose => undef
+$NamedValues => 0
+$Selected => undef # ref to array containing selected queue names
+$Lite => 0
+$Size => 5
+</%ARGS>
+
+<%INIT>
+
+# put list of queue names into string, starting and ending with commas
+my $sel_list = "," . join(",", @$Selected) . ",";
+
+my $q=new RT::Queues($session{'CurrentUser'});
+$q->UnLimit;
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/Elements/StatColumnMap b/rt/html/RTx/Statistics/Elements/StatColumnMap
new file mode 100644
index 0000000..aef9e2f
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/StatColumnMap
@@ -0,0 +1,173 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+$Name => undef
+$Attr => undef
+</%ARGS>
+
+
+<%ONCE>
+our ( $STAT_COLUMN_MAP );
+
+sub StatColumnMap {
+ my $name = shift;
+ my $attr = shift;
+
+ # First deal with the simple things from the map
+ if ( $STAT_COLUMN_MAP->{$name} ) {
+ return ( $STAT_COLUMN_MAP->{$name}->{$attr} );
+ }
+
+ # now, let's deal with harder things, like Custom Fields
+
+ elsif ( $name =~ /^(?:CF|CustomField)\.\{(.+)\}$/ ) {
+ my $field = $1;
+
+ if ( $attr eq 'attribute' ) {
+ return (undef);
+ }
+ elsif ( $attr eq 'title' ) {
+ return ( $field );
+ }
+ elsif ( $attr eq 'value' ) {
+ # Display custom field contents, separated by newlines.
+ # For Image custom fields we also show a thumbnail here.
+ return sub {
+ my $values = $_[0]->CustomFieldValues($field);
+ return map {
+ (
+ ($_->CustomFieldObj->Type eq 'Image')
+ ? \($m->scomp( '/Elements/ShowCustomFieldImage', Object => $_ ))
+ : $_->Content
+ ),
+ \'<br>',
+ } @{ $values->ItemsArrayRef }
+ };
+ }
+ }
+}
+
+sub LinkCallback {
+ my $method = shift;
+
+ my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode};
+ my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type};
+ my $mode_uri = $mode.'URI';
+ my $local_type = 'Local'.$mode;
+
+ return sub {
+ map {
+ \'<A HREF="',
+ $_->$mode_uri->Resolver->HREF,
+ \'">',
+ ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ),
+ \'</A><BR>',
+ } @{ $_[0]->Links($mode,$type)->ItemsArrayRef }
+ }
+}
+
+$STAT_COLUMN_MAP = {
+ LastUpdated => {
+ attribute => 'LastUpdated',
+ title => 'Last Updated',
+ value => sub { return $_[0]->LastUpdatedObj->AsString }
+ },
+
+ Statistics_Date => {
+ title => 'Date',
+ value => sub { return $_[0]{values}{Statistics_Date} }
+ },
+
+ Statistics_Created_Count => {
+ title => 'Created',
+ value => sub { return $_[0]{values}{Statistics_Created_Count} }
+ },
+
+ Statistics_Resolved_Count => {
+ title => 'Resolved',
+ value => sub { return $_[0]{values}{Statistics_Resolved_Count} }
+ },
+
+ Statistics_Deleted_Count => {
+ title => 'Deleted',
+ value => sub { return $_[0]{values}{Statistics_Deleted_Count} }
+ },
+
+ Statistics_Totals => {
+ title => 'Totals',
+ value => sub { return $_[0]{values}{Statistics_Totals} }
+ },
+
+ Statistics_Status => {
+ title => 'Status',
+ value => sub { return $_[0]{values}{Statistics_Status} }
+ },
+
+ Statistics_Dynamic => {
+ # Depends on having a KEY as second param
+ value => sub {
+ my $record = shift;
+ my $line = shift;
+ my $key = shift;
+ return $$record{values}{$key}
+ }
+ },
+
+ # Everything from LINKTYPEMAP
+ (map {
+ $_ => { value => LinkCallback( $_ ) }
+ } keys %RT::Ticket::LINKTYPEMAP),
+
+ '_CLASS' => {
+ value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' }
+ },
+
+};
+</%ONCE>
+<%init>
+$m->comp( '/Elements/Callback', STAT_COLUMN_MAP => $STAT_COLUMN_MAP, _CallbackName => 'StatColumnMap');
+return StatColumnMap($Name, $Attr);
+</%init>
diff --git a/rt/html/RTx/Statistics/Elements/Tabs b/rt/html/RTx/Statistics/Elements/Tabs
new file mode 100755
index 0000000..4fde113
--- /dev/null
+++ b/rt/html/RTx/Statistics/Elements/Tabs
@@ -0,0 +1,72 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this 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 => 'RTx/Statistics/index.html',
+ current_tab => $current_tab,
+ Title => $Title &>
+
+<%INIT>
+ my $tabs = { A => { title => loc('Tickets per Day'),
+ path => 'RTx/Statistics/CallsQueueDay/index.html',
+ },
+ B => { title => loc('Tickets by status'),
+ path => 'RTx/Statistics/OpenStalled/index.html',
+ },
+ C => { title => loc('Multiple Queues'),
+ path => 'RTx/Statistics/CallsMultiQueue/index.html',
+ },
+ D => { title => loc('Ticket Trends by Day'),
+ path => 'RTx/Statistics/DayOfWeek/index.html',
+ },
+ E => { 'title' => loc('Time to Resolve'),
+ path => 'RTx/Statistics/Resolution/index.html',
+ },
+ F => { 'title' => loc('Resolve Time Graph'),
+ path => 'RTx/Statistics/TimeToResolve/index.html',
+ },
+ Z => { 'title' => loc('FAQ'),
+ path => 'RTx/Statistics/FAQ/index.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>
+$subtabs => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/FAQ/index.html b/rt/html/RTx/Statistics/FAQ/index.html
new file mode 100644
index 0000000..e7839ea
--- /dev/null
+++ b/rt/html/RTx/Statistics/FAQ/index.html
@@ -0,0 +1,23 @@
+<& /Elements/Header, Title => 'FAQ and known issues' &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc("FAQ and Known Issues") &>
+<hr noshade size="1">
+<p>This page will be used to contain known issues and FAQ`s for the Statistics
+package<br />
+This will also be used to clarify limitations of the package as they stand.</p>
+
+<p><strong>What Version of the Statistics package is this?</strong></p>
+<p>0.1.8</p>
+
+<p><strong>What time zone are the charts set to?</strong></p>
+<p>Because of the new programming method of the date functions, the charts are currently built in GMT(UTC). This may once again be
+customisable in a future release.</p>
+
+<p><strong>What is the default date period and queue?</strong></p>
+<p>The default date period is the previous 10 days, except where the chart is over a fixed 7 day period. The default queue is either
+General, or another queue set in your local configuration.</p>
+
+<p><strong>What are the limitations of the date function?</strong></p>
+<p>It has few, but it will not let you chose less than one day. you cannot select an end date before the start date and it is not
+recommended to select a date in the future or an illegal date, such at 30th February. Code has been put in place to trap these, but it may
+not be fool proof.</p>
+<hr size="1" noshade>
diff --git a/rt/html/RTx/Statistics/OpenStalled/Elements/Chart b/rt/html/RTx/Statistics/OpenStalled/Elements/Chart
new file mode 100755
index 0000000..9505881
--- /dev/null
+++ b/rt/html/RTx/Statistics/OpenStalled/Elements/Chart
@@ -0,0 +1,27 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::bars;
+
+my @data;
+my $graph = GD::Graph::bars->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+ x_label => 'Queue name',
+ y_label => 'Total per queue by status');
+my $format = $graph->export_format;
+$graph->set_legend(split /,/ , $ARGS{set_legend});
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/OpenStalled/Results.tsv b/rt/html/RTx/Statistics/OpenStalled/Results.tsv
new file mode 100644
index 0000000..2ec1e0c
--- /dev/null
+++ b/rt/html/RTx/Statistics/OpenStalled/Results.tsv
@@ -0,0 +1,114 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<%ARGS>
+@queues => @Statistics::OpenStalledQueueList
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+
+ my $n = 0;
+ my @data;
+ my @msgs;
+ my %totals;
+ my $QueryString;
+ my $now = new RT::Date($session{CurrentUser});
+ my $tix = new RT::Tickets($session{'CurrentUser'});
+
+ my %queues = map {
+ $_ => 1;
+ } (@queues);
+
+ # set content type
+ $r->content_type('application/vnd.ms-excel');
+
+ $QueryString = "queues=" . join("&queues=", @queues);
+
+ my $queue = new RT::Queues($session{CurrentUser});
+ $queue->UnLimit;
+
+ my $QueueObj = new RT::Queue($session{'CurrentUser'});
+ $QueueObj->Load($queue);
+
+ # Put out some data about the generation of this file
+ $m->out("Tickets by Status by Queue for Queues:\t" . join(',', @queues) . "\tGenerated at:\t" . Statistics::FormatDate("%x %X", $now). "\n\n");
+
+ # basically the same as index.html
+
+ # Output header row
+ $m->out("Status");
+ for ( sort keys %queues) {
+ push @data, $_;
+ my $Queueobj = new RT::Queue($session{'CurrentUser'});
+ $Queueobj->Load($_);
+ next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+ $m->out("\t" . $_);
+ }
+ $m->out("\tTotals\n");
+
+ foreach my $s (qw(new open stalled)) {
+ $m->out("$s");
+ my $total=0;
+ foreach my $q (sort keys %queues) {
+ $tix = new RT::Tickets($session{'CurrentUser'});
+ $tix->LimitQueue(VALUE => "$q");
+ $tix->LimitStatus(VALUE => "$s");
+ $totals{$q} += $tix->Count; # Add up columns for each queue
+ $m->out("\t" . $tix->Count);
+ $total += $tix->Count;
+ }
+ $m->out("\t$total\n");
+ $totals{"Totals"} += $total;
+ }
+ $m->out("Totals");
+ foreach my $q (sort keys %queues) {
+ $m->out("\t" . $totals{$q});
+ }
+ $m->out("\t" . $totals{"Totals"} . "\n");
+
+ $m->abort();
+</%INIT>
diff --git a/rt/html/RTx/Statistics/OpenStalled/index.html b/rt/html/RTx/Statistics/OpenStalled/index.html
new file mode 100755
index 0000000..d0cd9f1
--- /dev/null
+++ b/rt/html/RTx/Statistics/OpenStalled/index.html
@@ -0,0 +1,188 @@
+<& /Elements/Header, Title => loc('New, Open and Stalled tickets by Queue') &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc('New, Open and Stalled tickets by Queue') &>
+
+<h3>Description</h3>
+<p>The purpose of this page is to show a snapshot of the current status of tickets by Queue. You can multi select Queues from the dropdown
+list or simply show all available queues. This will indicate how many tickets have not yet been viewed (New), how many have been at least
+viewed once (Open) and how many have had their status changed to stalled.</p>
+
+<form method="POST" action="index.html">
+
+%my $tix = new RT::Tickets($session{'CurrentUser'});
+%if ($queue) {
+% $tix->LimitQueue (VALUE => $queue);
+%}
+
+
+%my $title = "New, Open and Stalled Tickets in " . join(', ', @queues);
+<& /Elements/TitleBoxStart, title => $title, title_href => "/RTx/Statistics/OpenStalled/index.html?$QueryString"&>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH="100%">
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header,
+ Format => \@RowFormat,
+ FormatString => $RowFormat,
+ AllowSorting => $AllowSorting,
+ Order => $Order,
+ Query => undef,
+ Rows => $Rows,
+ Page => $Page,
+ OrderBy => $OrderBy ,
+ BaseURL => $BaseURL,
+ maxitems => $maxitems &>
+% }
+
+% for ( sort keys %queues_to_show) {
+% push @data, $_;
+% }
+% my @legend;
+% my $total = 0;
+% my $line = 0;
+%# NOTE need to handle all status values (see share/html/Elements/SelectStatus).
+% foreach my $s (qw(new open stalled)) {
+% $line++;
+% push @legend, $s;
+% $total=0;
+% foreach my $q (sort keys %queues_to_show) {
+% $tix = new RT::Tickets($session{'CurrentUser'});
+% $tix->LimitQueue(VALUE => "$q");
+% $tix->LimitStatus(VALUE => "$s");
+% push @data, $tix->Count;
+% $totals{$q} += $tix->Count; # Add up columns for each queue
+% $total += $tix->Count;
+% $values{$q} = $tix->Count;
+% }
+% $totals{"Totals"} += $total;
+% $values{Statistics_Status} = $s;
+% $values{Statistics_Totals} = $total;
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@RowFormat, i => $line, record => $record, maxitems => $maxitems &>
+% }
+% $values{Statistics_Status} = "Totals";
+% foreach my $q (sort keys %queues_to_show) {
+% $values{$q} = $totals{$q};
+% }
+% $values{Statistics_Totals} = $totals{"Totals"};
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@BoldRowFormat, i => $line+1, record => $record, maxitems => $maxitems &>
+</table>
+<& /Elements/TitleBoxEnd&>
+
+<hr>
+
+<BR />
+<BR />
+
+% use Data::Dumper;
+% Statistics::DebugLog("Dump of data array is " . Dumper(@data) . "\n");
+% my $url = 'Elements/Chart?x_labels=';
+% for (1..(scalar keys %queues_to_show)) {
+% $url .= $m->interp->apply_escapes((shift @data),'u') . ',';
+% }
+% chop $url;
+% $url .= '&data1=' ;
+% for (1..(scalar keys %queues_to_show)) {
+% $url .= $m->interp->apply_escapes((shift @data),'u') . ',';
+% }
+% chop $url;
+% $url .= '&data2=' ;
+% for (1..(scalar keys %queues_to_show)) {
+% $url .= $m->interp->apply_escapes((shift @data),'u') . ',';
+% }
+% chop $url;
+% $url .= '&data3=' ;
+% for (1..(scalar keys %queues_to_show)) {
+% $url .= $m->interp->apply_escapes((shift @data),'u') . ',';
+% }
+% $url .= '&set_legend='.(join ",", @legend);
+
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox, Title => "Select Queues", ShowMultiQueues => 1, queues_ref => \@queues &>
+
+<a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/index.html?<% $QueryString %>"><&|/l&>Bookmarkable link</&></a>
+%# | <a href="<%$RT::WebPath%>/RTx/Statistics/OpenStalled/Results.tsv?<%$QueryString%>"><&|/l&>spreadsheet</&></a>
+<BR>
+<BR>
+
+</FORM>
+
+% Statistics::DebugInit( $m );
+
+<%ARGS>
+@queues => @Statistics::OpenStalledQueueList
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+$AddAllCheck => undef
+</%ARGS>
+
+<%INIT>
+ use RTx::Statistics;
+
+ my $n = 0;
+ my @data;
+ my @msgs;
+ my %totals;
+ my $QueryString;
+ my %queues_to_show;
+ my $maxitems;
+ my $RowFormat;
+ my $BoldRowFormat;
+ my %record;
+ my %values;
+ my $record = \%record;
+
+ $record{values} = \%values;
+
+ Statistics::DebugClear();
+
+ # Handle the Add All Checkbox
+ if($AddAllCheck eq "on") {
+ $AddAllCheck = undef;
+ undef (@queues);
+ my $q=new RT::Queues($session{'CurrentUser'});
+ $q->UnLimit;
+ while (my $queue=$q->Next) {
+ next if !$queue->CurrentUserHasRight('SeeQueue');
+ push @queues, $queue->Name;
+ }
+ }
+
+ # If the user has the right to see the queue, put it into the map
+ for my $q (@queues) {
+ my $Queueobj = new RT::Queue($session{'CurrentUser'});
+ $Queueobj->Load($q);
+ next if !$Queueobj->CurrentUserHasRight('SeeQueue');
+ $queues_to_show{$q} = 1;
+ }
+
+ $maxitems = (scalar @queues) + 2;
+
+ # Build the new query string
+ $QueryString = "queues=" . join("&queues=", @queues);
+
+ # Build the format strings
+ $RowFormat = "'__Statistics_Status__'";
+ $BoldRowFormat = "'<B>__Statistics_Status__</B>'";
+ for my $q (@queues) {
+ $RowFormat .= ",'__Statistics_Dynamic__/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+ $BoldRowFormat .= ",'<B>__Statistics_Dynamic__</B>/KEY:$q/TITLE:$q/STYLE:text-align:right;'";
+ }
+ $RowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+ $BoldRowFormat .= ",'<B>__Statistics_Totals__</B>/STYLE:text-align:right;'";
+ # Parse the formats into structures.
+ my (@RowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $RowFormat);
+ my (@BoldRowFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldRowFormat);
+
+
+ my $queue = new RT::Queues($session{CurrentUser});
+ $queue->UnLimit;
+
+ my $QueueObj = new RT::Queue($session{'CurrentUser'});
+ $QueueObj->Load($queue);
+
+</%INIT>
diff --git a/rt/html/RTx/Statistics/Resolution/Elements/Chart b/rt/html/RTx/Statistics/Resolution/Elements/Chart
new file mode 100755
index 0000000..fa0ac55
--- /dev/null
+++ b/rt/html/RTx/Statistics/Resolution/Elements/Chart
@@ -0,0 +1,29 @@
+<%perl>
+$r->content_type("image/$format");
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new($Statistics::GraphWidth,$Statistics::GraphHeight);
+$graph->set(export_format => "png",
+ x_label => 'Days',
+ y_label => 'Average time in Days');
+
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+#$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/Resolution/index.html b/rt/html/RTx/Statistics/Resolution/index.html
new file mode 100644
index 0000000..d9885b0
--- /dev/null
+++ b/rt/html/RTx/Statistics/Resolution/index.html
@@ -0,0 +1,269 @@
+<& /Elements/Header, Title => 'Time to Resolution' &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc("Time To Resolve tickets by Queue for : " .$QueueObj->Name()) &>
+<h3>Description</h3>
+<p>This page shows details of resolution of tickets in the selected queue. It displays tickets created on each day in your selected date
+range. Of those tickets created on that day, how many have been resolved and the total time it has taken for all tickets created on that
+day to be resolved.</p>
+<p>At the bottom of the chart is shows total time taken to resolve all tickets
+in the selected date range and the average time per ticket to
+resolve.</p>
+
+<form method="POST" action="index.html">
+
+%my $title = "Time to resolve in " . $QueueObj->Name() . " per day from " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[0]) . " through " .
+% Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$#dates-1]);
+<&|/Elements/TitleBox,
+ title => $title,
+ title_href => "/RTx/Statistics/Resolution/index.html?$QueryString" &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+% if ($ShowHeader) {
+<& /RTx/Statistics/Elements/CollectionAsTable/Header,
+ Format => \@Format,
+ FormatString => $Format,
+ AllowSorting => $AllowSorting,
+ Order => $Order,
+ Query => undef,
+ Rows => $Rows,
+ Page => $Page,
+ OrderBy => $OrderBy ,
+ BaseURL => $BaseURL,
+ maxitems => $maxitems &>
+% }
+% my $line = 1;
+% LINE: for my $d (0..$#dates ) {
+% if ($d == $#dates ){
+% next LINE;
+% }
+% my $x = 1;
+% $values{Statistics_Date} = Statistics::FormatDate($Statistics::PerDayDateFormat, $dates[$d]);
+% my $tix = new RT::Tickets($session{'CurrentUser'});
+% $tix->LimitCreated(VALUE => $dates[$d]->ISO, OPERATOR => ">=");
+% if ($dates[$d+1]) {
+% $tix->LimitCreated(VALUE => $dates[$d+1]->ISO, OPERATOR => "<=");
+% }
+% if ($Queue) {
+% $tix->LimitQueue (VALUE => $Queue);
+% }
+% $values{Statistics_Created_Count} = $tix->Count;
+% $tix->LimitStatus(VALUE => "resolved");
+% $values{Statistics_Resolved_Count} = $tix->Count;
+% if ($tix->Count) {
+% my @tix = @{$tix->ItemsArrayRef};
+% my $total;
+% $total += ($_->ResolvedObj->Unix - $_->CreatedObj->Unix) for @tix;
+% $size+= ($#tix +1);
+% $grandtotal += $total;
+% $values{Duration} = Statistics::DurationAsString($total);
+% $data[$x++][$d] = int ($total );
+% } else {
+% $values{Duration} = "N/A";
+% }
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@Format, i => $line, record => $record, maxitems => $maxitems &>
+% $line++;
+%}
+% $size =1 if $size==0;
+% $values{text} = "Average time to resolve = " . Statistics::DurationAsString($grandtotal / $size);
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &>
+% $line++;
+% $values{text} = "Total time to resolve = " . Statistics::DurationAsString( $grandtotal );
+<& /RTx/Statistics/Elements/CollectionAsTable/Row, Format => \@OneCellFormat, i => $line, record => $record, maxitems => $maxitems &>
+% $line++;
+</table>
+</&>
+
+<hr>
+
+<BR />
+<BR />
+
+<%perl>
+# Create the graph URL
+
+# change the total time to resolve to a floating point number of days
+foreach my $dat(@{$data[1]} ){
+ $dat = ($dat / $Statistics::secsPerDay);
+ $dat = sprintf("%0.4f", $dat);
+}
+
+my $url = 'Elements/Chart?x_labels=';
+for (0..$diff-1) {
+ $url .= $data[0][$_] . ",";
+}
+chop $url;
+shift @data;
+$url .= "&data1=";
+for(0..$diff-1) {
+ $data[0][$_] = 0 if !$data[0][$_];
+ $url .= $data[0][$_] . ",";
+}
+</%perl>
+
+<& /RTx/Statistics/Elements/GraphBox, GraphURL => $url &>
+
+<& /RTx/Statistics/Elements/ControlsAsTable/ControlBox,
+ Title => "Change Queue or Dates",
+ ShowDates => 1, sMonth => \$sMonth, sDay => \$sDay, sYear => \$sYear,
+ eMonth => \$eMonth, eDay => \$eDay, eYear => \$eYear,
+ weekends => $weekends,
+ ShowSingleQueue => 1, Queue => $Queue
+ &>
+
+</form>
+
+<%ARGS>
+$max => $Statistics::TimeToResolveMaxRows
+$Queue => undef
+$weekends =>$Statistics::TimeToResolveWeekends
+$sMonth=>undef
+$sDay=>undef
+$sYear=>undef
+$eMonth=>undef
+$eDay=>undef
+$eYear=>undef
+$days=>undef
+$currentMonth=>undef
+
+$AllowSorting => undef
+$Order => undef
+$OrderBy => undef
+$ShowNavigation => 1
+$ShowHeader => 1
+$Rows => 50
+$Page => 1
+$BaseURL => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+use Time::Local;
+my $n = 0;
+my @data = ([]);
+my @dates;
+my @msgs;
+my $size;
+my $selected;
+my $grandtotal = 0;
+my $diff;
+my $sEpoch=0;
+my $eEpoch=0;
+my $QueryString;
+
+my $maxitems = 4;
+my %record;
+my %values;
+my $record = \%record;
+
+$record{values} = \%values;
+
+
+# If debugging, set things up and display all the args
+Statistics::DebugClear();
+Statistics::DebugLog("CallsQueueDay/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+ Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $Format = qq{ Statistics_Date,
+ '__Statistics_Created_Count__/STYLE:text-align:right;',
+ '__Statistics_Resolved_Count__/STYLE:text-align:right;',
+ '__Statistics_Dynamic__/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' };
+my $BoldFormat = qq{ '<B>__Statistics_Date__</B>',
+ '<B>__Statistics_Created_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Resolved_Count__</B>/STYLE:text-align:right;',
+ '<B>__Statistics_Dynamic__</B>/KEY:Duration/TITLE:Time To Resolve/STYLE:text-align:right;' };
+
+# TODO need way to make this cell do colspan
+my $OneCellFormat = qq{ '<B>__Statistics_Dynamic__</B>/KEY:text/STYLE:text-align:left;','','','' };
+
+my (@Format) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $Format);
+my (@BoldFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $BoldFormat);
+my (@OneCellFormat) = $m->comp('/RTx/Statistics/Elements/CollectionAsTable/ParseFormat', Format => $OneCellFormat);
+
+Statistics::DebugLog("CallsQueueDay/index.html Format array=" . join(',', @Format) . "\n");
+
+if ($sDay > $Statistics::monthsMaxDay{$sMonth}) {
+ $sDay = $Statistics::monthsMaxDay{$sMonth};
+}
+
+if ($eDay > $Statistics::monthsMaxDay{$eMonth}) {
+ $eDay = $Statistics::monthsMaxDay{$eMonth};
+}
+
+if ($sYear){
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear-1900);
+}
+if ($eYear){
+Statistics::DebugLog("eMonth = " . $eMonth . "\n");
+ $eEpoch = timelocal(0, 0, 0, $eDay, $eMonth, $eYear-1900);
+} else {
+ # This case happens when the page is first loaded
+ my @local = localtime(time);
+ ($eDay, $eMonth, $eYear) = ($local[3], $local[4], $local[5]);
+ $eYear += 1900;
+ $eEpoch = timelocal(0, 0, 0, $local[3], $local[4], $local[5], $local[6], $local[7], $local[8]);
+Statistics::DebugLog("Setting eEpoch=$eEpoch from current time.\n");
+}
+
+if (($eEpoch < $sEpoch) || ($sEpoch == 0)) {
+ # We have an end, but not a start, or, overlapping.
+
+ # if $currentMonth is set, just set the day to 1
+ if($currentMonth) {
+ # set start vars from end, but with day set to 1
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($eEpoch);
+ $sDay=1;
+ $sEpoch = timelocal(0, 0, 0, $sDay, $sMonth, $sYear);
+ } else {
+ # If the user has specified how many days back to go, use that,
+ # If not, set start to configured default period before end
+ if(defined $days) {
+ $sEpoch = $eEpoch - ($days * $Statistics::secsPerDay);
+ } else {
+ $sEpoch = $eEpoch - ($Statistics::PerDayPeriod * $Statistics::secsPerDay);
+ }
+ (undef, undef, undef, $sDay, $sMonth, $sYear) = localtime($sEpoch);
+ }
+ $sYear += 1900;
+}
+
+# Compute days to chart.
+# The +1 is because we need to generate one more date. If the user
+# selected a 10 day range, we need to generate 11 days.
+$diff = int(($eEpoch - $sEpoch + $Statistics::secsPerDay - 1) / $Statistics::secsPerDay)+1;
+Statistics::DebugLog("Setting diff=$diff\n");
+
+Statistics::DebugLog("sEpoch=$sEpoch, components=" . join(',', localtime($sEpoch)) . "\n");
+Statistics::DebugLog("eEpoch=$eEpoch, components=" . join(',', localtime($eEpoch)) . "\n");
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+if (!defined $Queue) {
+ $QueueObj->Load($Statistics::TimeToResolveQueue);
+ $Queue = $QueueObj->Id();
+}
+
+# Set up the string for the current query for bookmarkable link
+$QueryString = "sDay=$sDay&sMonth=$sMonth&sYear=$sYear&eDay=$eDay&eMonth=$eMonth&eYear=$eYear&weekends=$weekends&Queue=$Queue";
+
+# Set up the end date to be midnight(morning) of the date after the one the user wanted.
+my $endRange = $eEpoch + $Statistics::secsPerDay;
+$QueueObj->Load($Queue);
+# NOTE: list loop starts at the end of the date range, unshifting dates onto
+# the arrays, so that they end up in start to finish order.
+$eEpoch += $Statistics::secsPerDay;
+$n = 0;
+until ($#dates == $diff ) {
+ my $date = new RT::Date($session{CurrentUser});
+ $date->Set(Value=>$endRange - $n, Format => 'unix');
+ # Note: we used to adjust the time to local midnight, but
+ # none of the other date entry fields in RT seem to adjust, so we've stopped.
+ #Statistics::DebugLog("Before adjust to midnight date " . Statistics::FormatDate("%c", $date) . "\n");
+ $n+= $Statistics::secsPerDay;
+ # If we aren't showing weekends and this is one, decrement the number
+ # of days to show and skip to the next date.
+ if(!$weekends and Statistics::RTDateIsWeekend($date)) {$diff--; next;}
+ unshift @dates, $date;
+Statistics::DebugLog("pushing date " . Statistics::FormatDate("%c", $date) . "\n");
+ unshift @{ $data[0] }, Statistics::FormatDate($Statistics::PerDayLabelDateFormat, $date);
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/TimeToResolve/Elements/Chart b/rt/html/RTx/Statistics/TimeToResolve/Elements/Chart
new file mode 100755
index 0000000..a069a7b
--- /dev/null
+++ b/rt/html/RTx/Statistics/TimeToResolve/Elements/Chart
@@ -0,0 +1,23 @@
+<%perl>
+print $graph->plot(\@data)->$format();
+$m->abort();
+</%perl>
+<%INIT>
+use GD::Graph::points;
+
+my @data;
+my $graph = GD::Graph::points->new(400,300);
+$graph->set(export_format => "png",
+ marker_size => $ARGS{marker_size},
+ x_label => 'Average time to resolve (Days)',
+ y_label => 'Number of tickets resolved' );
+#$r->content_type("image/$format");
+my $format = $graph->export_format;
+push @data, [split /,/ , $ARGS{x_labels}];
+for (1..((scalar keys %ARGS)-2)) {
+ push @data, [split /,/ , $ARGS{"data".$_}];
+}
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/TimeToResolve/index.html b/rt/html/RTx/Statistics/TimeToResolve/index.html
new file mode 100755
index 0000000..2124b53
--- /dev/null
+++ b/rt/html/RTx/Statistics/TimeToResolve/index.html
@@ -0,0 +1,75 @@
+<& /Elements/Header, Title => 'Time to Resolve in Queue' &>
+<& /RTx/Statistics/Elements/Tabs, Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &>
+
+<h3>Description</h3>
+<p>This page displays the same information as the Time to Resolve chart, but in a scattergraph format and only for the previous 7 calendar
+days. It only displays data for tickets which have been resolved. Each division on the Days axis is one day and the granularity of this chart
+is 30 minutes.</p>
+<form method="POST">
+
+<table>
+ <tr>
+ <td>Show Queue:</td>
+ <td COLSPAN=3><& /Elements/SelectQueue, Name=>"queue", Default=>$queue ,ShowNullOption=>0,
+ CheckQueueRight=>'SeeQueue' &></td>
+ </tr>
+</table>
+<INPUT TYPE="submit" VALUE="Update Page"</INPUT>
+</form>
+
+<BR>
+% my $url = 'Elements/Chart?x_labels=';
+% my $i;
+% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer";
+% $url .= '&';
+% $url .= "marker_size=1&";
+% $url .= "data1=".(join ",", map { $_ || () } @counts)."&";
+% chop $url;
+<IMG SRC="<% $url %>">
+
+<BR>
+
+%Statistics::DebugInit($m);
+
+<%ARGS>
+$queue => undef
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+
+Statistics::DebugClear();
+Statistics::DebugLog("TimeToResolve/index.html ARGS:\n");
+for my $key (keys %ARGS) {
+ Statistics::DebugLog("ARG{ $key }=" . $ARGS{$key} . "\n");
+}
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+if (!defined $queue) {
+ $QueueObj->Load($Statistics::TimeToResolveGraphQueue);
+ $queue = $QueueObj->Id();
+} else {
+ $QueueObj->Load($queue);
+}
+
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $queue) if $queue;
+$tix->LimitStatus(VALUE => "resolved");
+$tix->UnLimit;
+if ($tix->Count) {
+ while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK
+ my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix;
+ next unless $when > 0; # Doubly bloody hack
+ my $max = (60*60*24*2) / 1800;
+ my $x = int($when / 1800);
+ $counts[$x > $max ? $max : $x]++;
+ }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/UserTest/Elements/Chart b/rt/html/RTx/Statistics/UserTest/Elements/Chart
new file mode 100755
index 0000000..99eb2a2
--- /dev/null
+++ b/rt/html/RTx/Statistics/UserTest/Elements/Chart
@@ -0,0 +1,28 @@
+<%perl>
+print $graph->plot(\@data)->$format();
+$m->abort();
+print $#data+1 . " Elements:<p>";
+for (0..$#data) {
+print $data[$_];
+print "<p>";
+}
+</%perl>
+<%INIT>
+use GD::Graph::lines;
+
+my @data;
+my $graph = GD::Graph::lines->new(640,480);
+$graph->set(export_format => "png",
+ x_label => 'Days',
+ y_label => 'Average time in Days');
+
+push @data, [split /,/ , $ARGS{x_labels}];
+push @data, [split /,/ , $ARGS{data1}];
+push @data, [split /,/ , $ARGS{data2}];
+push @data, [split /,/ , $ARGS{data3}];
+
+my $format = $graph->export_format;
+#$r->content_type("image/$format");
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/RTx/Statistics/UserTest/index.html b/rt/html/RTx/Statistics/UserTest/index.html
new file mode 100755
index 0000000..7bc25da
--- /dev/null
+++ b/rt/html/RTx/Statistics/UserTest/index.html
@@ -0,0 +1,54 @@
+<& /Elements/Header, Title => 'Time to Resolve in Queue' &>
+<& /RTx/Statistics/Elements/Tabs, Title => 'Time to Resolve, by ticket in Queue:' . $QueueObj->Name() &>
+
+
+<form method="POST">
+
+See Queue:<BR>
+<& /Elements/SelectQueue, Name=>"queue", Default => "$queue" &>
+<BR>
+<INPUT TYPE="submit" VALUE="Go!"</INPUT>
+</form>
+
+<BR>
+% my $url = 'Elements/Chart?x_labels=';
+% my $i;
+% $url .= join ",", (map {(int($_/2) == $_/2 && (++$i)%2) ? $_/2 : ""} grep {$counts[$_]} 0..($#counts-1)), "longer";
+% $url .= '&';
+% $url .= "marker_size=1&";
+% $url .= "data1=".(join ",", map { $_ || () } @counts)."&";
+% chop $url;
+<IMG SRC="<% $url %>">
+
+<BR>
+
+<%ARGS>
+$queue => $Statistics::TimeToResolveGraphQueue;
+</%ARGS>
+
+<%INIT>
+use RTx::Statistics;
+
+my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
+my $n = 0;
+my @data = ([]);
+my @msgs;
+my @counts;
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($queue);
+
+my $tix = new RT::Tickets($session{'CurrentUser'});
+$tix->LimitQueue (VALUE => $queue) if $queue;
+$tix->LimitStatus(VALUE => "resolved");
+$tix->UnLimit;
+if ($tix->Count) {
+ while (my $t = $tix->RT::SearchBuilder::Next) { # BLOODY HACK
+ my $when = $t->ResolvedObj->Unix - $t->CreatedObj->Unix;
+ next unless $when > 0; # Doubly bloody hack
+ my $max = (60*60*24*2) / 1800;
+ my $x = int($when / 1800);
+ $counts[$x > $max ? $max : $x]++;
+ }
+}
+</%INIT>
diff --git a/rt/html/RTx/Statistics/index.html b/rt/html/RTx/Statistics/index.html
new file mode 100755
index 0000000..41490de
--- /dev/null
+++ b/rt/html/RTx/Statistics/index.html
@@ -0,0 +1,59 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# Copyright this file (c) 2003 Harald Wagener <hwagener@hamburg.fcb.com>
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this 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 Statistics') &>
+<& /RTx/Statistics/Elements/Tabs, Title => loc('RT Statistics') &>
+
+<&|/l&><h2>Description</h2>
+<p>These 6 options below enable you to display management data from the RT Database in table and graphical forms, enabling trends, bottlenecks, load problems etc to be identified.
+Each contains a description of how the data is displayed and describes the options available to you.</p></&>
+<ul>
+<li><strong><a href="CallsQueueDay/index.html">
+<&|/l&>Tickets per day per Queue</&></a></strong><br />
+<&|/l&>View the number of tickets created, resolved or deleted in a<br /> specific Queue, over the requested period of days</&>
+</li>
+<li><strong><a href="OpenStalled/index.html">
+<&|/l&>Tickets status by Queue</&></a></strong><br>
+<&|/l&>View numbers of new, open and stalled tickets in a selected Queue</&>
+</li>
+<li><strong><a href="CallsMultiQueue/index.html">
+<&|/l&>Tickets per Day in Multiple Queues</&>
+</a></strong><br>
+<&|/l&>View tickets created, resolved or deleted on in one or more Queues<br /> over a specified time period</&>
+</li>
+<li><strong><a href="DayOfWeek/index.html">
+<&|/l&>Tickets per Day of Week (absolute)</&></a></strong><br>
+<&|/l&>View trends showing when tickets are created, resolved or deleted</&>
+</li>
+<li><strong><a href="Resolution/index.html">
+<&|/l&>Time to Resolve</&></a></strong><br>
+<&|/l&>View how long tickets take to be resolved by Queue</&>
+</li>
+</li>
+<li><strong><a href="TimeToResolve/index.html">
+<&|/l&>Time to Resolve (scatter graph)</&></a></strong><br>
+<&|/l&>View a detailed scatter graph of time to resolve tickets by Queue</&>
+</li>
+</ul>
diff --git a/rt/html/Reports/Activity/ActivityDetail.html b/rt/html/Reports/Activity/ActivityDetail.html
new file mode 100644
index 0000000..ef0d830
--- /dev/null
+++ b/rt/html/Reports/Activity/ActivityDetail.html
@@ -0,0 +1,83 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Activity detail"),
+ path => "Reports/Activity/ActivityDetail.html",
+ &>
+
+<& Elements/MiniPlot, data => \%counts &>
+
+<table style="width: 100%">
+<tr class="titlerow">
+<th>Queue</th><th>Activity</th><th>Date</th><th>Time</th><th>Ticket #</th><th>User</th><th>Short description</th>
+</tr>
+% for my $item (@items) {
+<tr>
+<td><% $item->{queue} %></td>
+<td><% $item->{status} %></td>
+<td><% $item->{date} %></td>
+<td><% $item->{time} %></td>
+<td><% $item->{id} %></td>
+<td><% $item->{actor} %></td>
+<td><% $item->{notes} %></td>
+</tr>
+% }
+</table>
+
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end => "2006/01/01"
+</%args>
+<%init>
+
+
+my $summary_tickets = RT::Tickets->new($session{'CurrentUser'});
+$summary_tickets->FromSQL($query . " AND ( Updated >= '$start' AND Updated <= '$end')");
+my %counts;
+while (my $ticket = $summary_tickets->Next) {
+ my $txns = $ticket->Transactions;
+ $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+ $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+ # I think they really don't just want status changes
+ $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+ $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+ while (my $txn = $txns->Next){
+ my $date = substr($txn->Created, 0, 10);
+ # we don't have data on the status of a new ticket, default to 'new'
+ $counts{$date}{$txn->NewValue || 'new'}++;
+ }
+}
+
+
+my $tickets = RT::Tickets->new($session{'CurrentUser'});
+$tickets->FromSQL($query);
+my @items;
+while (my $ticket = $tickets->Next) {
+ my $txns = $ticket->Transactions;
+ $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+ $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+ # I think they really don't just want status changes
+ $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+ $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+ while (my $txn = $txns->Next) {
+ push @items, { queue => $txn->TicketObj->QueueObj->Name,
+ id => $txn->TicketObj->id,
+ date => (split ' ', $txn->CreatedObj->ISO)[0],
+ time => (split ' ', $txn->CreatedObj->ISO)[1],
+ status => $txn->NewValue || 'new',
+ actor => $txn->CreatorObj->Name,
+ notes => ($txn->Content ne 'This transaction appears to have no content' ? substr($txn->Content, 0, 60) : $txn->BriefDescription)
+ };
+ }
+}
+
+@items = sort {
+ $a->{queue} cmp $b->{'queue'}
+ || $a->{'status'} cmp $b->{'status'}
+ || $a->{'id'} <=> $b->{'id'}
+ || $a->{'actor'} cmp $b->{'actor'}
+ || $a->{'notes'} <=> $b->{'notes'}
+} @items;
+
+</%init>
diff --git a/rt/html/Reports/Activity/ActivitySummary.html b/rt/html/Reports/Activity/ActivitySummary.html
new file mode 100644
index 0000000..7bb756f
--- /dev/null
+++ b/rt/html/Reports/Activity/ActivitySummary.html
@@ -0,0 +1,61 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Activity summary"),
+ path => "Reports/Activity/ActivitySummary.html",
+ &>
+
+<& Elements/MiniPlot, data => \%queues &>
+
+<table style="width: 100%">
+<tr class="titlerow">
+<th>Queue</th>
+% for my $status (sort keys %status) {
+<th><% $status %></th>
+% }
+<th>Total</th>
+</tr>
+% for my $queue (sort keys %queues) {
+<th class="label"><% $queue %></th>
+% for my $status (sort keys %status) {
+<td><% $queues{$queue}{$status} || 0 %>
+% }
+<td><% $total{$queue} %></td>
+</tr>
+% }
+<tr class="grandtotal">
+<th class="label" >Grand Total</th>
+% for my $status (sort keys %status) {
+<td><% $status{$status} %></td>
+% }
+<td><% $total %></td>
+</table>
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end => "2006/01/01"
+</%args>
+<%init>
+
+my $tickets = RT::Tickets->new($session{'CurrentUser'});
+$tickets->FromSQL($query . " AND ( Updated >= '$start' AND Updated <= '$end')");
+
+my %queues;
+my %status;
+my %total;
+my $total;
+while (my $ticket = $tickets->Next) {
+ my $txns = $ticket->Transactions;
+ $txns->Limit(FIELD => 'Created', OPERATOR => '>=', VALUE => $start);
+ $txns->Limit(FIELD => 'Created', OPERATOR => '<=', VALUE => $end);
+ $txns->Limit(FIELD => 'Type', VALUE => 'Status', ENTRYAGGREGATOR => 'OR');
+ $txns->Limit(FIELD => 'Type', VALUE => 'Create');
+
+ while (my $txn = $txns->Next) {
+ $queues{$txn->TicketObj->QueueObj->Name}{$txn->NewValue || 'new'}++;
+ $status{$txn->NewValue || 'new'}++;
+ $total{$txn->TicketObj->QueueObj->Name}++;
+ $total++;
+ }
+}
+
+
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/LimitReport b/rt/html/Reports/Activity/Elements/LimitReport
new file mode 100644
index 0000000..7c4aac7
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/LimitReport
@@ -0,0 +1,23 @@
+<form action="index.html" method="POST" enctype="multipart/form-data">
+Query:
+<textarea name="query" rows="5" cols="80"><% $query %></textarea><br />
+
+Report type: <select name="type">
+<option value="ActivityDetail" <% $ARGS{path} =~ /ActivityDetail/ ? 'selected' : '' %>>Activity detail</option>
+<option value="ActivitySummary" <% $ARGS{path} =~ /ActivitySummary/ ? 'selected' : '' %>>Activity summary</option>
+<option value="ResolutionComments" <% $ARGS{path} =~ /ResolutionComments/ ? 'selected' : '' %>>Resolution comments</option>
+<option value="ResolutionStatistics" <% $ARGS{path} =~ /ResolutionStatistics/ ? 'selected' : '' %>>Resolution statistics</option>
+</select><br />
+
+Start date: <input type="text" name="start" value="<% $start %>" /><br />
+End date: <input type="text" name="end" value="<% $end %>" /><br />
+<& /Elements/Submit, Label => loc('Report') &>
+</form>
+<%args>
+$type => undef
+$start => undef
+$end => undef
+$query => undef
+</%args>
+<%init>
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/MiniPlot b/rt/html/Reports/Activity/Elements/MiniPlot
new file mode 100644
index 0000000..f920328
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/MiniPlot
@@ -0,0 +1,57 @@
+<table class="miniplot"><tr>
+% for my $major (@major) {
+<td><div class="graph">
+ <ul>
+% my $i = 0;
+% for my $minor (@minor) {
+% my $percent = int( 100 * ($data->{$major}{$minor} || 0) / $max );
+ <li class="c<% ($i % 6) + 1%>" style="width: <% $barwidth %>%;
+ left: <% $baroffset + $each * $i %>%;
+ height: <% $percent %>%;"><div class="data"><% $minor %>: <% $percent %>%</div></li>
+% $i++;
+% }
+ </ul>
+</div></td>
+% }
+</tr><tr>
+% for my $major (@major) {
+<th class="legend"><% $major %></th>
+% }
+</tr>
+</table>
+
+<table class="miniplot"><tr>
+% my $i = 0;
+% for my $minor (@minor) {
+<th><span class="demoblock c<% ($i++ % 6) + 1 %>"></span> <% $minor %></th>
+% }
+</tr>
+</table>
+
+<%args>
+$data
+$major => undef
+$minor => undef
+</%args>
+<%init>
+
+my $max = 1;
+
+my %minor;
+for my $major (keys %{$data}) {
+ for (keys %{$data->{$major}}) {
+ $minor{$_}++;
+ $max = $data->{$major}{$_} if $data->{$major}{$_} > $max;
+ }
+}
+
+my @major = $major ? @{$major} : sort keys %{$data};
+my @minor = $minor ? @{$minor} : sort keys %minor;
+
+return unless @minor and @major;
+
+my $each = int( (100 / @minor) );
+my $barwidth = int( (100 / @minor) * (3/4) );
+my $baroffset = int( (100 / @minor) * (1/8) );
+
+</%init>
diff --git a/rt/html/Reports/Activity/Elements/PrintFooter b/rt/html/Reports/Activity/Elements/PrintFooter
new file mode 100644
index 0000000..fa9f475
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/PrintFooter
@@ -0,0 +1,7 @@
+<hr/>
+<div style="text-align: center;">
+<%$RT::ReportFooterMessage || 'Proprietary and Confidential' %>
+</div>
+</body>
+</html>
+%$m->abort();
diff --git a/rt/html/Reports/Activity/Elements/PrintHeader b/rt/html/Reports/Activity/Elements/PrintHeader
new file mode 100644
index 0000000..b7c4b34
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/PrintHeader
@@ -0,0 +1,32 @@
+<%args>
+$title => undef
+$path => undef
+$query => undef
+</%args>
+<HTML>
+<HEAD>
+<TITLE><%$title%></TITLE>
+<link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png" />
+<link media="all" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css" />
+<link media="print" rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/printrt.css" type="text/css" />
+%# XXX TODO THIS SHOULD NOT BE A TABLE
+<body>
+<table width="100%">
+<tr>
+<td align="left">
+<div id="username">User: <%$session{'CurrentUser'}->Name%></div>
+<div id="reportdate">
+%my $d= RT::Date->new($session{'CurrentUser'}); $d->SetToNow;
+<%$d->AsString%></div>
+</td>
+<td align="center">
+<h1><%$title%></h1>
+</td>
+<td align="right">
+<img src="<%$RT::LogoURL%>" alt="RT Logo"/>
+</td>
+</tr>
+</table>
+<hr/>
+<&|/l&>Report criteria:</&> <%$query%>
+<hr />
diff --git a/rt/html/Reports/Activity/Elements/ScreenFooter b/rt/html/Reports/Activity/Elements/ScreenFooter
new file mode 100644
index 0000000..235b7b3
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/ScreenFooter
@@ -0,0 +1,13 @@
+<& LimitReport, %ARGS &>
+% if ($show_print_link) {
+<div align="right">
+% my %printable_args = %ARGS;
+% delete $printable_args{$_} for (qw/path title mode/);
+% $printable_args{'mode'} = 'print';
+% my $url = $ARGS{'path'} .'?'. join(';', map { $_."=".$printable_args{$_} } keys %printable_args);
+<a href="<%$RT::WebPath|n%>/<%$url|n%>"><&|/l&>Printable version</&></a>
+</div>
+% }
+<%args>
+$show_print_link => 1
+</%args>
diff --git a/rt/html/Reports/Activity/Elements/ScreenHeader b/rt/html/Reports/Activity/Elements/ScreenHeader
new file mode 100644
index 0000000..080efc0
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/ScreenHeader
@@ -0,0 +1,8 @@
+<%args>
+$title => undef
+$path => undef
+</%args>
+<& /Elements/Header, Title => $title &>
+<& Tabs,
+ current_subtab => $path,
+ Title => $title &>
diff --git a/rt/html/Reports/Activity/Elements/Tabs b/rt/html/Reports/Activity/Elements/Tabs
new file mode 100644
index 0000000..a949820
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/Tabs
@@ -0,0 +1,52 @@
+<& /Elements/Tabs,
+ tabs => $tabs,
+ subtabs => $subtabs,
+ current_toptab => 'Tools/Offline.html',
+ current_tab => 'Reports/Activity/index.html'.$args,
+ Title => $Title &>
+
+<%INIT>
+my $subtabs = {};
+
+my $top = $m->caller_args(-1);
+my $args = "?" . $m->comp( '/Elements/QueryString',
+ query => $top->{query},
+ start => $top->{start},
+ end => $top->{end});
+if ($m->caller_args(-1)->{'query'}) {
+ $current_subtab .= $args;
+ $subtabs = {
+ a => { title => 'Activity detail',
+ path => 'Reports/Activity/ActivityDetail.html'.$args,
+ },
+ b => { title => 'Activity summary',
+ path => 'Reports/Activity/ActivitySummary.html'.$args,
+ },
+ c => { title => 'Resolution comments',
+ path => 'Reports/Activity/ResolutionComments.html'.$args,
+ },
+ d => { title => 'Resolution statistics',
+ path => 'Reports/Activity/ResolutionStatistics.html'.$args,
+ },
+ };
+}
+
+my $tabs = {
+ a => { title => loc('Offline'),
+ path => 'Tools/Offline.html',
+ },
+ r => { title => loc('Reports'),
+ path => 'Reports/Activity/index.html'.$args,
+ subtabs => $subtabs,
+ current_subtab => $current_subtab,
+ }
+ };
+
+</%INIT>
+
+
+<%ARGS>
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Reports/Activity/Elements/Wrapper b/rt/html/Reports/Activity/Elements/Wrapper
new file mode 100644
index 0000000..6f81f5f
--- /dev/null
+++ b/rt/html/Reports/Activity/Elements/Wrapper
@@ -0,0 +1,16 @@
+<%args>
+$mode => 'screen'
+</%args>
+
+% if ($mode eq 'print') {
+<& PrintHeader, %ARGS &>
+%} else {
+<& ScreenHeader, %ARGS &>
+% }
+<%$m->content |n%>
+% if ($mode eq 'print') {
+<& PrintFooter, %ARGS &>
+%} else {
+<& ScreenFooter, %ARGS &>
+% }
+
diff --git a/rt/html/Reports/Activity/ResolutionComments.html b/rt/html/Reports/Activity/ResolutionComments.html
new file mode 100644
index 0000000..81ca301
--- /dev/null
+++ b/rt/html/Reports/Activity/ResolutionComments.html
@@ -0,0 +1,62 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Resolution Comments"),
+ path => "Reports/Activity/ResolutionComments.html",
+ &>
+
+<table style="width: 100%">
+<tr>
+<th>Queue</th><th>Ticket #</th><th>Created</th><th>Resolved</th><th>Time to resolve</th>
+</tr>
+<tr>
+<th colspan="5">Resolution comments</th>
+</tr>
+% for my $item (@items) {
+<tr class="titlerow">
+<td><% $item->{queue} %></td>
+<td><% $item->{id} %></td>
+<td><% $item->{created} %></td>
+<td><% $item->{resolved} %></td>
+<td><% $item->{duration} %></td>
+</tr>
+<tr>
+<td colspan="5"><% $item->{whiteboard} %></td>
+</tr>
+% }
+</table>
+</&>
+
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end => "2006/01/01"
+</%args>
+<%init>
+
+use Time::Duration;
+
+my $summary_tickets = RT::Tickets->new( $session{'CurrentUser'} );
+$summary_tickets->FromSQL(
+ $query . " AND (Status = 'resolved') AND ( Updated >= '$start' AND Updated <= '$end')" );
+
+my @items;
+while ( my $ticket = $summary_tickets->Next ) {
+ push @items, {
+ queue => $ticket->QueueObj->Name,
+ id => $ticket->id,
+ created => $ticket->CreatedObj->AsString,
+ resolved => $ticket->ResolvedObj->AsString,
+ duration => Time::Duration::concise(
+ Time::Duration::duration(
+ $ticket->ResolvedObj->Unix - $ticket->CreatedObj->Unix
+ )
+ ),
+ whiteboard => $ticket->FirstCustomFieldValue('Whiteboard')
+ };
+}
+
+@items = sort { $a->{queue} cmp $b->{queue} || $a->{id} <=> $b->{id} } @items;
+
+
+
+
+
+</%init>
diff --git a/rt/html/Reports/Activity/ResolutionStatistics.html b/rt/html/Reports/Activity/ResolutionStatistics.html
new file mode 100644
index 0000000..4ecde2c
--- /dev/null
+++ b/rt/html/Reports/Activity/ResolutionStatistics.html
@@ -0,0 +1,95 @@
+<&|Elements/Wrapper, %ARGS, title => loc("Resolution statistics"),
+ path => "Reports/Activity/ResolutionStatistics.html",
+ &>
+
+<& Elements/MiniPlot,
+ data => \%plot,
+ major => ['Date range','Last 30 days','Last 60 days','Last 90 days','Ever'],
+ minor => [(sort keys %queues), "Average"]
+ &>
+
+<table style="width: 100%">
+<tr>
+<td></td><th colspan="4">Number of tickets closed / Average resolution time per ticket</th>
+</tr>
+<tr class="titlerow">
+<th>Queue</th>
+<th>Date range</th>
+<th>Last 30 days</th>
+<th>Last 60 days</th>
+<th>Last 90 days</th>
+<th>Ever</th>
+</tr>
+% for my $queue (sort keys %queues) {
+<tr>
+<th><% $queue %></th>
+% for my $period ('Date range','Last 30 days','Last 60 days','Last 90 days','Ever') {
+<td><% scalar @{$closed{$period}{$queue}} %> / <% $average_resolve_times{$period}{$queue} %></td>
+% }
+</tr>
+% }
+<tr class="grandtotal">
+<th>Ticket average</th>
+% for my $period ('Date range','Last 30 days','Last 60 days','Last 90 days','Ever') {
+<td><% $average_resolve_times{$period}{_all_count} %> / <% $average_resolve_times{$period}{_all} %></td>
+% }
+</tr>
+</table>
+
+</&>
+<%args>
+$query => 'id > 0'
+$start => "2005/01/01"
+$end => "2006/01/01"
+</%args>
+<%init>
+
+my $in_30_days = RT::Date->new($session{'CurrentUser'});
+$in_30_days->Set(Format => 'Unix', Value => ( time - (86400*30)));
+my $in_60_days = RT::Date->new($session{'CurrentUser'});
+$in_60_days->Set(Format => 'Unix', Value => ( time - (86400*60)));
+my $in_90_days = RT::Date->new($session{'CurrentUser'});
+$in_90_days->Set(Format => 'Unix', Value => ( time - (86400*90)));
+
+my %queries;
+$queries{'Date range'} = "(Resolved >= '$start' AND Resolved <= '$end')";
+$queries{'Last 30 days'} = "(Resolved >= '".$in_30_days->ISO."')";
+$queries{'Last 60 days'} = "(Resolved >= '".$in_60_days->ISO."')";
+$queries{'Last 90 days'} = "(Resolved >= '".$in_90_days->ISO."')";
+$queries{'Ever'} = "(Status = 'resolved' OR Status = 'rejected')";
+
+
+my %closed;
+my %queues;
+foreach my $period (keys %queries) {
+ my $tix = RT::Tickets->new($session{'CurrentUser'});
+ $tix->FromSQL($query . " AND " .$queries{$period});
+
+ while (my $ticket = $tix->Next) {
+ push @{ $closed{$period}{$ticket->QueueObj->Name}}, $ticket;
+ $queues{$ticket->QueueObj->Name}++;
+ }
+}
+
+my %restimes;
+my %average_resolve_times;
+my %plot;
+use Time::Duration;
+foreach my $period ( keys %closed ) {
+ foreach my $queue ( keys %{$closed{$period}} ) {
+ foreach my $ticket (@{$closed{$period}{$queue}} ) {
+ push @{$restimes{$period}{$queue}}, ( $ticket->ResolvedObj->Unix - $ticket->CreatedObj->Unix);
+ }
+
+ my $total_time = 0;
+ $total_time+= $_ for @{$restimes{$period}{$queue}};
+ $average_resolve_times{$period}{'_all_time'} += $total_time;
+ $average_resolve_times{$period}{'_all_count'} += @{$restimes{$period}{$queue}};
+ $plot{$period}{$queue} = $total_time / @{$restimes{$period}{$queue}};
+ $average_resolve_times{$period}{$queue} = Time::Duration::concise(Time::Duration::duration($plot{$period}{$queue}));
+ }
+ $plot{$period}{Average} = $average_resolve_times{$period}{'_all_time'} / $average_resolve_times{$period}{'_all_count'};
+ $average_resolve_times{$period}{'_all'} = Time::Duration::concise(Time::Duration::duration($plot{$period}{Average}));
+}
+
+</%init>
diff --git a/rt/html/Reports/Activity/index.html b/rt/html/Reports/Activity/index.html
new file mode 100644
index 0000000..1f6ddb0
--- /dev/null
+++ b/rt/html/Reports/Activity/index.html
@@ -0,0 +1,29 @@
+<&| Elements/Wrapper, %ARGS, title => loc("Activity reports"), show_print_link => 0 &>
+
+
+</&>
+
+<%args>
+$type => undef
+$start => undef
+$end => undef
+$query => "Status = 'resolved'"
+</%args>
+<%init>
+
+unless ($start) {
+ my $then = RT::Date->new($session{'CurrentUser'});
+ $then->Set(Format => 'Unix', Value => time - (86400*7));
+ $ARGS{start} = substr($then->ISO,0,10);
+}
+
+unless ($end) {
+ my $now = RT::Date->new($session{'CurrentUser'});
+ $now->SetToNow();
+ $ARGS{end} = substr($now->ISO,0,10);
+}
+
+if ($type) {
+ $m->redirect($type . ".html?" . $m->comp('/Elements/QueryString', query => $query, start => $start, end => $end));
+}
+</%init>
diff --git a/rt/html/Search/Build.html b/rt/html/Search/Build.html
deleted file mode 100644
index fa84f42..0000000
--- a/rt/html/Search/Build.html
+++ /dev/null
@@ -1,832 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%#
-%# Data flow here:
-%# The page receives a Query from the previous page, and maybe arguments
-%# corresponding to actions. (If it doesn't get a Query argument, it pulls
-%# one out of the session hash. Also, it could be getting just a raw query from
-%# Build/Edit.html (Advanced).)
-%#
-%# After doing some stuff with default arguments and saved searches, the ParseQuery
-%# function (which is similar to, but not the same as, _parser in RT/Tickets_Overlay_SQL)
-%# converts the Query into a RT::Interface::Web::QueryBuilder::Tree. This mason file
-%# then adds stuff to or modifies the tree based on the actions that had been requested
-%# by clicking buttons. It then calls GetQueryAndOptionList on the tree to generate
-%# the SQL query (which is saved as a hidden input) and the option list for the Clauses
-%# box in the top right corner.
-%#
-%# Worthwhile refactoring: the tree manipulation code for the actions could use some cleaning
-%# up. The node-adding code is different in the "add" actions from in ParseQuery, which leads
-%# to things like ParseQuery correctly not quoting numbers in numerical fields, while the "add"
-%# action does quote it (this breaks SQLite).
-%#
-<& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Build.html".$QueryString,
- Title => $title,
- Format => $Format,
- Query => $Query,
- Order => $Order,
- OrderBy => $OrderBy,
- Rows => $RowsPerPage
-&>
-
-<form method="post" action="Build.html" name="BuildQuery">
-<input type="hidden" class="hidden" name="SearchId" value="<%$SearchId%>" />
-<input type="hidden" class="hidden" name="Query" value="<%$Query%>" />
-<input type="hidden" class="hidden" name="Format" value="<%$Format%>" />
-<table width="100%" border="0" cellpadding="5">
-<tr valign="top">
-<td class="boxcontainer" rowspan="2" width="65%">
-<& Elements/PickCriteria, query => $Query, cfqueues => $queues &>
-<& /Elements/Submit, Caption => loc('Add these terms to your search'), Label => loc('Add'), Name => 'AddClause'&>
-</td>
-
-<td>
-<& Elements/EditQuery,
- %ARGS,
- actions => \@actions,
- optionlist => $optionlist,
- Description => $Description &>
-<& /Elements/Submit, Label => loc('Add and Search'), Name => 'DoSearch'&>
-</td>
-</tr>
-
-<tr valign="top">
-<td>
-<& Elements/EditSearches, CurrentSearch => $search_hash, Dirty => $dirty, SearchId => $SearchId &>
-</td>
-</tr>
-
-<tr>
-<td colspan="2" class="boxcontainer">
-
-<& Elements/DisplayOptions, %ARGS, Format=> $Format,
-AvailableColumns => $AvailableColumns, CurrentFormat => $CurrentFormat, RowsPerPage => $RowsPerPage, OrderBy => $OrderBy, Order => $Order &>
-<& /Elements/Submit, Label => loc('Add and Search'), Name => 'DoSearch'&>
-</td>
-</tr>
-</table>
-</form>
-
-<%INIT>
-use RT::Interface::Web::QueryBuilder;
-use RT::Interface::Web::QueryBuilder::Tree;
-
-my $search_hash = {};
-my $search;
-my $title = loc("Query Builder");
-
-# {{{ Clear out unwanted data
-if ( $NewQuery or $ARGS{'Delete'} ) {
-
- # Wipe all data-carrying variables clear if we want a new
- # search, or we're deleting an old one..
- $Query = '';
- $Format = '';
- $Description = '';
- $SearchId = '';
- $Order = '';
- $OrderBy = '';
- $RowsPerPage = undef;
-
- # ($search hasn't been set yet; no need to clear)
-
- # ..then wipe the session out..
- undef $session{'CurrentSearchHash'};
-
- # ..and the search results.
- $session{'tickets'}->CleanSlate() if defined $session{'tickets'};
-}
-
-# }}}
-
-if (ref $OrderBy eq "ARRAY") {
- $OrderBy = join("|", @$OrderBy);
-}
-if (ref $Order eq "ARRAY") {
- $Order = join("|", @$Order);
-}
-
-# {{{ Attempt to load what we can from the session, set defaults
-
-# We don't read or write to the session again until the end
-$search_hash = $session{'CurrentSearchHash'};
-
-# Read from user preferences
-my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
-
-# These variables are what define a search_hash; this is also
-# where we give sane defaults.
-$Query ||= $search_hash->{'Query'};
-$Format ||= $search_hash->{'Format'} || $prefs->{'Format'};
-$Description ||= $search_hash->{'Description'};
-$SearchId ||= $search_hash->{'SearchId'} || 'new';
-$Order ||= $search_hash->{'Order'} || $prefs->{'Order'} || 'ASC';
-$OrderBy ||= $search_hash->{'OrderBy'} || $prefs->{'OrderBy'} || 'id';
-
-unless ( defined $RowsPerPage ) {
- if ( defined $search_hash->{'RowsPerPage'} ) {
- $RowsPerPage = $search_hash->{'RowsPerPage'};
- }
- elsif ( defined $prefs->{'RowsPerPage'} ) {
- $RowsPerPage = $prefs->{'RowsPerPage'};
- }
- else {
- $RowsPerPage = 50;
- }
-}
-
- $search ||= $search_hash->{'Object'};
-
-# }}}
-
-my @actions = ();
-
-# Clean unwanted junk from the format
-$Format = $m->comp( '/Elements/ScrubHTML', Content => $Format ) if ($Format);
-
-# {{{ If we're asked to delete the current search, make it go away and reset the search parameters
-if ( $ARGS{'Delete'} ) {
-
- # We set $SearchId to 'new' above already, so peek into the %ARGS
- my ($container_object, $search_id) = _parse_saved_search ($ARGS{'SearchId'});
- if ($container_object && $container_object->id) {
- # We have the object the entry is an attribute on; delete the
- # entry..
- $container_object->Attributes->DeleteEntry(
- Name => 'SavedSearch',
- id => $search_id
- );
- }
-}
-
-# }}}
-
-# {{{ If the user wants to copy a search, uncouple from the one that this was based on, but don't erase the $Query or $Format
-if ( $ARGS{'CopySearch'} ) {
- $SearchId = 'new';
- $search = undef;
- $Description = loc( "[_1] copy", $Description );
-}
-
-# }}}
-
-# {{{ if we're asked to revert the current search, we just want to load it
-if ( $ARGS{'Revert'} ) {
- $ARGS{'LoadSavedSearch'} = $SearchId;
-}
-
-# }}}
-
-# {{{ if we're asked to load a search, load it.
-
-if ( my ($container_object, $search_id ) = _parse_saved_search ($ARGS{'LoadSavedSearch'})) {
- $search = $container_object->Attributes->WithId($search_id);
-
- # We have a $search and now; import the others
- $SearchId = $ARGS{'LoadSavedSearch'};
- $Description = $search->Description;
- $Format = $search->SubValue('Format');
- $Query = $search->SubValue('Query');
- $Order = $search->SubValue('Order');
- $OrderBy = $search->SubValue('OrderBy');
- $RowsPerPage = $search->SubValue('RowsPerPage');
-}
-
-# }}}
-
-# {{{ if we're asked to save the current search, save it
-if ( $ARGS{'Save'} ) {
- if ( $search && $search->id ) {
- # permission check
- if ($search->Object->isa('RT::System')) {
- unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
- Abort("No permission to save system-wide searches");
- }
- }
-
- # This search is based on a previously loaded search -- so
- # just update the current search object with new values
- $search->SetSubValues(
- Format => $Format,
- Query => $Query,
- Order => $Order,
- OrderBy => $OrderBy,
- RowsPerPage => $RowsPerPage,
- );
- $search->SetDescription($Description);
-
- }
- elsif ( $SearchId eq 'new' ) {
- my $saved_search = RT::SavedSearch->new( $session{'CurrentUser'} );
- my ( $ok, $search_msg ) = $saved_search->Save(
- Privacy => $ARGS{'Owner'},
- Name => $Description,
- SearchParams => {
- Format => $Format,
- Query => $Query,
- Order => $Order,
- OrderBy => $OrderBy,
- RowsPerPage => $RowsPerPage } );
-
- if ($ok) {
- $search = $session{'CurrentUser'}->UserObj->Attributes->WithId($saved_search->Id);
- # Build new SearchId
- $SearchId =
- ref( $session{'CurrentUser'}->UserObj ) . '-'
- . $session{'CurrentUser'}->UserObj->Id
- . '-SavedSearch-'
- . $search->Id;
- }
- else {
- push @actions, [ loc("Can't find a saved search to work with").': '.loc($search_msg), 0 ];
- }
- }
- else {
- push @actions, [ loc("Can't save this search"), 0 ];
- }
-
-}
-
-# }}}
-
-
-# {{{ Parse the query
-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 $_match = sub {
-
- # 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;
-};
-
-my $ParseQuery = sub {
- my $string = shift;
- my $tree = shift;
- my $actions = shift;
- my $want = KEYWORD | PAREN;
- my $last = undef;
-
- my $depth = 1;
-
- # make a tree root
- $$tree = RT::Interface::Web::QueryBuilder::Tree->new;
- my $root = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $$tree );
- my $parentnode = $root;
-
- # on new searches, we're passed undef but still need to construct the
- # RT::Interface::Web::QueryBuilder::Tree. Quiet warning
- return unless defined $string;
-
- # get the FIELDS from Tickets_Overlay
- my $tickets = new RT::Tickets( $session{'CurrentUser'} );
- my %FIELDS = %{ $tickets->FIELDS };
-
- # Lower Case version of FIELDS, for case insensitivity
- my %lcfields = map { ( lc($_) => $_ ) } ( keys %FIELDS );
-
- 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'\(|\)';
-
- # assume that $ea is AND if it is not set
- my ( $ea, $key, $op, $value ) = ( "AND", "", "", "" );
-
- # 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
- my $token = $tokens[ ( ( log $want ) / ( log 2 ) ) ];
- push @$actions,
- [
- loc("Error near ->[_1]<- expecting a [_2] in '[_3]'",
- $val, $token, $string ),
- -1
- ];
- }
-
- # State Machine:
- my $parentdepth = $depth;
-
- # Parens are highest priority
- if ( $current & PAREN ) {
- if ( $val eq "(" ) {
- $depth++;
-
- # make a new node that the clauses can be children of
- $parentnode = RT::Interface::Web::QueryBuilder::Tree->new( $ea, $parentnode );
- }
- else {
- $depth--;
- $parentnode = $parentnode->getParent();
- }
-
- $want = KEYWORD | PAREN | AGGREG;
- }
- elsif ( $current & AGGREG ) {
- $ea = $val;
- $parentnode->setNodeValue($ea);
- $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;
-
- my $class;
-
- my ($key_base, $subkey) = split(/\./,$key,2);
- $key_base =~ s/\..*$//; # Strip off .EmailAddress, for example
-
- if ( exists $lcfields{lc $key_base } ) {
- $key = $lcfields{lc $key_base } . (defined $subkey ? '.'.$subkey : '');
- $class = $FIELDS{$key_base}->[0];
- }
- elsif ( $key =~ /^C(?:ustom)?F(?:ield)?\.{(.*)}$/i ) {
- $class = $FIELDS{'CF'}->[0];
- }
-
- if ( $class ne 'INT' ) {
- $val = "'$val'";
- }
-
- push @$actions, [ loc("Unknown field: [_1]", $key), -1 ] unless $class;
-
- $want = PAREN | AGGREG;
- }
- else {
- push @$actions, [ loc("I'm lost"), -1 ];
- }
-
- if ( $current & VALUE ) {
- if ( $key =~ /^CF./ ) {
- $key = "'" . $key . "'";
- }
- my $clause = {
- Key => $key,
- Op => $op,
- Value => $val
- };
-
- # explicity add a child to it
- RT::Interface::Web::QueryBuilder::Tree->new( $clause, $parentnode );
-
- ( $ea, $key, $op, $value ) = ( "", "", "", "" );
-
- }
-
- $last = $current;
- } # while
-
- push @$actions, [ loc("Incomplete query"), -1 ]
- unless ( ( $want | PAREN ) || ( $want | KEYWORD ) );
-
- push @$actions, [ loc("Incomplete Query"), -1 ]
- unless ( $last && ( $last | PAREN ) || ( $last || VALUE ) );
-
- # This will never happen, because the parser will complain
- push @$actions, [ loc("Mismatched parentheses"), -1 ]
- unless $depth == 1;
-};
-
-my $tree;
-{
- my @parsing_errors;
- $ParseQuery->( $Query, \$tree, \@parsing_errors );
-
- # if parsing went poorly, send them to the edit page
- # to fix it
- if ( @parsing_errors ) {
- return $m->comp(
- "Edit.html",
- Query => $Query,
- actions => \@parsing_errors
- );
- }
-}
-
-$Query = "";
-
-my @options = $tree->GetDisplayedNodes;
-
-my @current_values = grep { defined } @options[@clauses];
-
-# {{{ Move things around
-if ( $ARGS{"Up"} ) {
- if (@current_values) {
- foreach my $value (@current_values) {
- my $index = $value->getIndex();
- if ( $value->getIndex() > 0 ) {
- my $parent = $value->getParent();
- $parent->removeChild($index);
- $parent->insertChild( $index - 1, $value );
- $value = $parent->getChild( $index - 1 );
- }
- else {
- push( @actions, [ loc("error: can't move up"), -1 ] );
- }
- }
- }
- else {
- push( @actions, [ loc("error: nothing to move"), -1 ] );
- }
-}
-elsif ( $ARGS{"Down"} ) {
- if (@current_values) {
- foreach my $value (@current_values) {
- my $index = $value->getIndex();
- my $parent = $value->getParent();
- if ( $value->getIndex() < ( $parent->getChildCount - 1 ) ) {
- $parent->removeChild($index);
- $parent->insertChild( $index + 1, $value );
- $value = $parent->getChild( $index + 1 );
- }
- else {
- push( @actions, [ loc("error: can't move down"), -1 ] );
- }
- }
- }
- else {
- push( @actions, [ loc("error: nothing to move"), -1 ] );
- }
-}
-elsif ( $ARGS{"Left"} ) {
- if (@current_values) {
- foreach my $value (@current_values) {
- my $parent = $value->getParent();
- my $grandparent = $parent->getParent();
- if ( !$grandparent->isRoot ) {
- my $index = $parent->getIndex();
- $parent->removeChild($value);
- $grandparent->insertChild( $index, $value );
- if ( $parent->isLeaf() ) {
- $grandparent->removeChild($parent);
- }
- }
- else {
- push( @actions, [ loc("error: can't move left"), -1 ] );
- }
- }
- }
- else {
- push( @actions, [ loc("error: nothing to move"), -1 ] );
- }
-}
-elsif ( $ARGS{"Right"} ) {
- if (@current_values) {
- foreach my $value (@current_values) {
- my $parent = $value->getParent();
- my $index = $value->getIndex();
- my $newparent;
- if ( $index > 0 ) {
- my $sibling = $parent->getChild( $index - 1 );
- if ( ref( $sibling->getNodeValue ) ) {
- $parent->removeChild($value);
- my $newtree = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent );
- $newtree->addChild($value);
- }
- else {
- $parent->removeChild($index);
- $sibling->addChild($value);
- }
- }
- else {
- $parent->removeChild($value);
- $newparent = RT::Interface::Web::QueryBuilder::Tree->new( 'AND', $parent );
- $newparent->addChild($value);
- }
- }
- }
- else {
- push( @actions, [ loc("error: nothing to move"), -1 ] );
- }
-}
-elsif ( $ARGS{"DeleteClause"} ) {
- if (@current_values) {
- $_->getParent()->removeChild($_) for @current_values;
- @current_values = ();
- }
- else {
- push( @actions, [ loc("error: nothing to delete"), -1 ] );
- }
-}
-elsif ( $ARGS{"Toggle"} ) {
- my $ea;
- if (@current_values) {
- foreach my $value (@current_values) {
- my $parent = $value->getParent();
-
- if ( $parent->getNodeValue eq 'AND' ) {
- $parent->setNodeValue('OR');
- }
- else {
- $parent->setNodeValue('AND');
- }
- }
- }
- else {
- push( @actions, [ loc("error: nothing to toggle"), -1 ] );
- }
-}
-
-# {{{ Try to find if we're adding a clause
-foreach my $arg ( keys %ARGS ) {
- if (
- $arg =~ m/^ValueOf(\w+|'CF.{.*?}')$/
- && ( ref $ARGS{$arg} eq "ARRAY"
- ? grep { $_ ne "" } @{ $ARGS{$arg} }
- : $ARGS{$arg} ne "" )
- )
- {
-
- # We're adding a $1 clause
- my $field = $1;
- my ( $keyword, $op, $value );
-
- #figure out if it's a grouping
- if ( $ARGS{ $field . "Field" } ) {
- $keyword = $ARGS{ $field . "Field" };
- }
- else {
- $keyword = $field;
- }
-
- my ( @ops, @values );
- if ( ref $ARGS{ 'ValueOf' . $field } eq "ARRAY" ) {
-
- # we have many keys/values to iterate over, because there is
- # more than one CF with the same name.
- @ops = @{ $ARGS{ $field . 'Op' } };
- @values = @{ $ARGS{ 'ValueOf' . $field } };
- }
- else {
- @ops = ( $ARGS{ $field . 'Op' } );
- @values = ( $ARGS{ 'ValueOf' . $field } );
- }
- $RT::Logger->error("Bad Parameters passed into Query Builder")
- unless @ops == @values;
-
- for my $i ( 0 .. @ops - 1 ) {
- my ( $op, $value ) = ( $ops[$i], $values[$i] );
- next if $value eq "";
-
- if ( $value eq 'NULL' && $op =~ /=/ ) {
- if ( $op eq '=' ) {
- $op = "IS";
- }
- elsif ( $op eq '!=' ) {
- $op = "IS NOT";
- }
-
- # This isn't "right", but...
- # It has to be this way until #5182 is fixed
- $value = "'NULL'";
- }
- else {
- $value = "'$value'";
- }
-
- my $clause = {
- Key => $keyword,
- Op => $op,
- Value => $value
- };
-
- my $newnode = RT::Interface::Web::QueryBuilder::Tree->new($clause);
- if (@current_values) {
- foreach my $value (@current_values) {
- my $newindex = $value->getIndex() + 1;
- $value->insertSibling( $newindex, $newnode );
- $value = $newnode;
- }
- }
- else {
- $tree->getChild(0)->addChild($newnode);
- @current_values = $newnode;
- }
- $newnode->getParent()->setNodeValue( $ARGS{'AndOr'} );
- }
- }
-}
-
-# }}}
-
-$tree->PruneChildlessAggregators;
-
-# }}}
-
-# {{{ Rebuild $Query based on the additions / movements
-$Query = "";
-my $optionlist_arrayref;
-
-($Query, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values);
-
-my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>)
- . ("&nbsp;" x (5 * $_->{DEPTH}))
- . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref;
-
-
-
-
-# }}}
-
-# }}}
-
-my $queues = $tree->GetReferencedQueues;
-
-# {{{ Deal with format changes
-my ( $AvailableColumns, $CurrentFormat );
-( $Format, $AvailableColumns, $CurrentFormat ) = $m->comp(
- 'Elements/BuildFormatString',
- cfqueues => $queues,
- %ARGS, Format => $Format
-);
-
-# }}}
-
-# {{{ If we're modifying an old query, check if it has changed
-my $dirty = 0;
-$dirty = 1
- if defined $search
- and ($search->SubValue('Format') ne $Format
- or $search->SubValue('Query') ne $Query
- or $search->SubValue('Order') ne $Order
- or $search->SubValue('OrderBy') ne $OrderBy
- or $search->SubValue('RowsPerPage') ne $RowsPerPage );
-
-# }}}
-
-# {{{ Push the updates into the session so we don't loose 'em
-$search_hash->{'SearchId'} = $SearchId;
-$search_hash->{'Format'} = $Format;
-$search_hash->{'Query'} = $Query;
-$search_hash->{'Description'} = $Description;
-$search_hash->{'Object'} = $search;
-$search_hash->{'Order'} = $Order;
-$search_hash->{'OrderBy'} = $OrderBy;
-$search_hash->{'RowsPerPage'} = $RowsPerPage;
-
-$session{'CurrentSearchHash'} = $search_hash;
-
-# }}}
-
-# {{{ Show the results, if we were asked.
-if ( $ARGS{"DoSearch"}) {
- $m->comp(
- "Results.html",
- Query => $Query,
- Format => $Format,
- Order => $Order,
- OrderBy => $OrderBy,
- Rows => $RowsPerPage
- );
- $m->comp('/Elements/Footer');
- $m->abort();
-}
-
-# }}}
-
-# {{{ Build a querystring for the tabs
-
-my $QueryString;
-if ($NewQuery) {
- $QueryString = '?NewQuery=1';
-}
-else {
- $QueryString = '?'
- . $m->comp(
- '/Elements/QueryString',
- Query => $Query,
- Format => $Format,
- Order => $Order,
- OrderBy => $OrderBy,
- Rows => $RowsPerPage
- )
- if ($Query);
-}
-
-# }}}
-
-</%INIT>
-
-<%ARGS>
-$NewQuery => 0
-$SearchId => undef
-$Query => undef
-$Format => undef
-$Description => undef
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$HideResults => 0
-@clauses => ()
-</%ARGS>
-
diff --git a/rt/html/Search/Bulk.html b/rt/html/Search/Bulk.html
deleted file mode 100644
index 69ffba8..0000000
--- a/rt/html/Search/Bulk.html
+++ /dev/null
@@ -1,397 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Bulk.html",
- Title => $title,
- Format => $ARGS{'Format'}, # we don't want the locally modified one
- Query => $Query,
- Rows => $Rows,
- OrderBy => $OrderBy,
- Order => $Order &>
-
-<& /Elements/ListActions, actions => \@results &>
-<form method="post" action="<%$RT::WebPath%>/Search/Bulk.html" enctype="multipart/form-data">
-% foreach my $var qw(Query Format OrderBy Order Rows Page) {
-<input type="hidden" class="hidden" name="<%$var%>" value="<%$ARGS{$var}%>" />
-%}
-<& /Elements/TicketList, Query => $Query,
- DisplayFormat => $Format,
- Format => $ARGS{'Format'},
- Verbatim => 1,
- AllowSorting => 1,
- OrderBy => $OrderBy,
- Order => $Order,
- Rows => $Rows,
- Page => $Page,
- BaseURL => $RT::WebPath."/Search/Bulk.html?"
- &>
-
-<hr>
-
-<& /Elements/Submit, Label => loc('Update'), CheckAll => 1, ClearAll => 1 &>
-<br />
-<&|/Widgets/TitleBox, title => $title &>
-<table>
-<tr>
-<td valign="top">
-<table>
-<tr><td class="label"> <&|/l&>Make Owner</&>: </td>
-<td class="value"> <& /Elements/SelectOwner, Name => "Owner" &> (<input type="checkbox" class="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>
-</&>
-<&| /Widgets/TitleBox, 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&>Reply to requestors</&></option>
-</select>
-</td></tr>
-<tr><td align="right"><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject" size="60" value="" /></td></tr>
-% while (my $CF = $TxnCFs->Next()) {
-<tr>
-<td align="right"><% $CF->Name %>:</td>
-<td><& /Elements/EditCustomField,
- CustomField => $CF,
- NamePrefix => "Object-RT::Transaction--CustomField-"
- &><em><% $CF->FriendlyType %></em></td>
-</td></tr>
-% } # end if while
- <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>
-
-</&>
-<&|/Widgets/TitleBox, title => loc('Edit Custom Fields'), color => "#336633"&>
-<%perl>
-my $cfs = RT::CustomFields->new($session{'CurrentUser'});
-$cfs->LimitToGlobal();
-$cfs->LimitToQueue($_) for keys %$seen_queues;
-</%perl>
-<table>
-<tr>
-<th><&|/l&>Name</&></th>
-<th><&|/l&>Add values</&></th>
-<th><&|/l&>Delete values</&></th>
-</tr>
-% while (my $cf = $cfs->Next()) {
-<tr>
-<td class="label"><%$cf->Name%><br />
-<em>(<%$cf->FriendlyType%>)</em></td>
-% my $rows = 5;
-% my @add = (NamePrefix => 'Bulk-Add-CustomField-', CustomField => $cf, Rows => $rows, Multiple => ($cf->MaxValues ==1 ? 0 : 1) , Cols => 25);
-% my @del = (NamePrefix => 'Bulk-Delete-CustomField-', CustomField => $cf, Rows => $rows, Multiple => 1, Cols => 25);
-% if ($cf->Type eq 'Select') {
-<td><& /Elements/EditCustomFieldSelect, @add &></td>
-<td><& /Elements/EditCustomFieldSelect, @del &></td>
-% } elsif ($cf->Type eq 'Combobox') {
-<td><& /Elements/EditCustomFieldCombobox, @add &></td>
-<td><& /Elements/EditCustomFieldCombobox, @del &></td>
-% } elsif ($cf->Type eq 'Freeform') {
-<td><& /Elements/EditCustomFieldFreeform, @add &></td>
-<td><& /Elements/EditCustomFieldFreeform, @del &></td>
-% } elsif ($cf->Type eq 'Text') {
-<td><& /Elements/EditCustomFieldText, @add &></td>
-<td>&nbsp;</td>
-% } else {
-% $RT::Logger->crit("Unknown CustomField type: " . $cf->Type);
-% }
-</tr>
-% }
-</table>
-</&>
-
-<&|/Widgets/TitleBox, title => loc('Edit Links'), color => "#336633"&>
-<em><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&></em><br />
-<& /Ticket/Elements/BulkLinks &>
-</&>
-
-<& /Elements/Submit, Label => loc('Update') &>
-
-
-</form>
-
-
-<%INIT>
-my $title = loc("Update multiple tickets");
-
-# Iterate through the ARGS hash and remove anything with a null value.
-map ( $ARGS{$_} =~ /^$/ && ( delete $ARGS{$_} ), keys %ARGS );
-
-my (@results);
-
-$Page ||= 1;
-
-$Format ||= $RT::DefaultSearchResultFormat;
-
-# inject _CHECKBOX to the first field.
-$Format =~ s/'?([^']+)'?,/'___CHECKBOX__$1',/;
-
-my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
-$Tickets->FromSQL($Query);
-if ( $OrderBy =~ /\|/ ) {
-
- # Multiple Sorts
- my @OrderBy = split /\|/, $OrderBy;
- my @Order = split /\|/, $Order;
- $Tickets->OrderByCols(
- map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } }
- ( 0 .. $#OrderBy ) );
-}
-else {
- $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
-}
-
-$Tickets->RowsPerPage($Rows) if ($Rows);
-$Tickets->GotoPage( $Page - 1 ); # SB uses page 0 as the first page
-
-Abort( loc("No search to operate on.") ) unless ($Tickets);
-
-# build up a list of all custom fields for tickets that we're displaying, so
-# we can display sane edit widgets.
-
-my $fields = {};
-my $seen_queues = {};
-while ( my $ticket = $Tickets->Next ) {
- next if $seen_queues->{ $ticket->Queue }++;
-
- my $custom_fields = $ticket->QueueObj->TicketCustomFields;
- while ( my $field = $custom_fields->Next ) {
- $fields->{ $field->id } = $field;
- }
-}
-
-my $do_comment_reply = 0;
-
-# Prepare for ticket updates
-if ($ARGS{'UpdateContent'}) {
- $ARGS{'UpdateContent'} =~ s/\r\n/\n/g;
- chomp( $ARGS{'UpdateContent'} );
-
- if ($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;
-my %queues;
-
-$Tickets->RedoSearch();
-
-# pull out the labels for any custom fields we want to update
-
-my $cf_del_keys;
-@$cf_del_keys = grep { /^Bulk-Delete-CustomField/ } keys %ARGS;
-my $cf_add_keys;
-@$cf_add_keys = grep { /^Bulk-Add-CustomField/ } keys %ARGS;
-
-
-while ( my $Ticket = $Tickets->Next ) {
- next unless ( $ARGS{ "UpdateTicket" . $Ticket->Id } );
-
- #Update the links
- $ARGS{'id'} = $Ticket->id;
- $queues{ $Ticket->QueueObj->Id }++;
-
- my @updateresults;
- if ($do_comment_reply) {
- ProcessUpdateMessage(
- TicketObj => $Ticket,
- ARGSRef => \%ARGS,
- Actions => \@updateresults
- );
- }
-
- #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 );
-
- foreach my $type qw(MergeInto DependsOn MemberOf RefersTo) {
- $ARGS{ $Ticket->id . "-" . $type } = $ARGS{"Ticket-$type"};
- $ARGS{ $type . "-" . $Ticket->id } = $ARGS{"$type-Ticket"};
- }
- @linkresults =
- ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS );
- foreach my $type qw(MergeInto DependsOn MemberOf RefersTo) {
- delete $ARGS{ $type . "-" . $Ticket->id };
- delete $ARGS{ $Ticket->id . "-" . $type };
- }
-
- my @cfresults;
-
- foreach my $list ( $cf_add_keys, $cf_del_keys ) {
- next unless $list->[0];
-
-
- my $op;
- if ( $list->[0] =~ /Add/ ) {
- $op = 'add';
-
- }
- elsif ( $list->[0] =~ /Del/ ) {
- $op = 'del';
- }
- else {
- $RT::Logger->crit(
- "Got an op that was neither add nor delete. can never happen"
- . $list->[0] );
- last;
- }
-
- foreach my $key (@$list) {
- my ( $cfid, $cf );
- next if $key =~ /CustomField-(\d+)-Category$/;
- if ( $key =~ /CustomField-(\d+)-/ ) {
- $cfid = $1;
- $cf = RT::CustomField->new( $session{'CurrentUser'} );
- $cf->Load($cfid);
- }
- else {next}
- my @values =
- ref( $ARGS{$key} ) eq 'ARRAY'
- ? @{ $ARGS{$key} }
- : ( $ARGS{$key} );
- map { s/(\r\n|\r)/\n/g; } @values; # fix the newlines
- # now break the multiline values into multivalues
- @values = map { split( /\n/, $_ ) } @values
- unless ( $cf->SingleValue );
-
- my $current_values = $Ticket->CustomFieldValues($cfid);
- foreach my $value (@values) {
- if ( $op eq 'del' && $current_values->HasEntry($value) ) {
- my ( $id, $msg ) = $Ticket->DeleteCustomFieldValue(
- Field => $cfid,
- Value => $value
- );
- push @cfresults, $msg;
- }
-
- elsif ( $op eq 'add' && !$current_values->HasEntry($value) ) {
- my ( $id, $msg ) = $Ticket->AddCustomFieldValue(
- Field => $cfid,
- Value => $value
- );
- push @cfresults, $msg;
- }
- }
- }
- }
- my @tempresults = (
- @watchresults, @basicresults, @dateresults,
- @updateresults, @linkresults, @cfresults
- );
-
- @tempresults =
- map { loc( "Ticket [_1]: [_2]", $Ticket->Id, $_ ) } @tempresults;
-
- @results = ( @results, @tempresults );
-}
-
-my $TxnCFs = RT::CustomFields->new( $session{CurrentUser} );
-$TxnCFs->LimitToLookupType( RT::Transaction->CustomFieldLookupType );
-$TxnCFs->LimitToGlobalOrObjectId( sort keys %queues );
-
-</%INIT>
-<%args>
-$Format => undef
-$Page => 1
-$Rows => undef
-$Order => 'ASC'
-$OrderBy => 'id'
-$Query => undef
-</%args>
diff --git a/rt/html/Search/Chart b/rt/html/Search/Chart
deleted file mode 100644
index 26249a7..0000000
--- a/rt/html/Search/Chart
+++ /dev/null
@@ -1,188 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Query => "id > 0"
-$PrimaryGroupBy => 'Queue'
-$SecondaryGroupBy => undef
-$ChartStyle => 'bars'
-</%args>
-<%init>
-my @keys;
-my @values;
-my $chart_class;
-use GD;
-use GD::Text;
-
-if ($ChartStyle eq 'pie') {
- require GD::Graph::pie;
- $chart_class = "GD::Graph::pie";
-} else {
- require GD::Graph::bars;
- $chart_class = "GD::Graph::bars";
-}
-
-use RT::Report::Tickets;
-my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} );
-$tix->FromSQL( $Query );
-my $count_name = $tix->Column( FUNCTION => 'COUNT', FIELD => 'id' );
-$tix->GroupBy( FIELD => $PrimaryGroupBy );
-my $value_name = $tix->Column( FIELD => $PrimaryGroupBy );
-
-my $chart = $chart_class->new( 600 => 400 );
-
-my $font = $RT::ChartFont || ['verdana', 'arial', gdMediumBoldFont];
-$chart->set_title_font( $font, 12 ) if $chart->can('set_title_font');
-$chart->set_legend_font( $font, 12 ) if $chart->can('set_legend_font');
-$chart->set_x_label_font( $font, 10 ) if $chart->can('set_x_label_font');
-$chart->set_y_label_font( $font, 10 ) if $chart->can('set_y_label_font');
-$chart->set_label_font( $font, 10 ) if $chart->can('set_label_font');
-$chart->set_x_axis_font( $font, 9 ) if $chart->can('set_x_axis_font');
-$chart->set_y_axis_font( $font, 9 ) if $chart->can('set_y_axis_font');
-$chart->set_values_font( $font, 9 ) if $chart->can('set_values_font');
-$chart->set_value_font( $font, 9 ) if $chart->can('set_value_font');
-
-# Pie charts don't like having no input, so we show a special image
-# that indicates an error message. Because this is used in an <img>
-# context, it can't be a simple error message. Without this check,
-# the chart will just be a non-loading image.
-if ($tix->Count == 0) {
- my $plot = GD::Image->new(600 => 400);
- $plot->colorAllocate(255, 255, 255); # background
- my $black = $plot->colorAllocate(0, 0, 0);
-
- require GD::Text::Wrap;
- my $error = GD::Text::Wrap->new($plot,
- color => $black,
- text => loc("No tickets found."),
- );
- $error->set_font( $font, 12 );
- $error->draw(0, 0);
-
- $m->comp( 'SELF:Plot', plot => $plot, %ARGS );
-}
-
-if ($chart_class eq "GD::Graph::bars") {
- $chart->set(
- x_label => $tix->Label( $PrimaryGroupBy ),
- x_labels_vertical => 1,
- y_label => loc('Tickets'),
- show_values => 1
- );
-}
-
-my %class = (
- Queue => 'RT::Queue',
- Owner => 'RT::User',
-);
-my $class = $class{ $PrimaryGroupBy };
-
-while ( my $entry = $tix->Next ) {
- if ( $class ) {
- my $q = $class->new( $session{'CurrentUser'} );
- $q->Load( $entry->__Value( $value_name ) );
- push @keys, $q->Name;
- }
- else {
- push @keys, $entry->__Value($value_name);
- }
-
- $keys[-1] ||= loc('(no value)');
- if ($chart_class eq 'GD::Graph::pie') {
- $keys[-1] .= " - ". $entry->__Value( $count_name );
- }
- push @values, $entry->__Value($count_name);
-}
-
-# XXX: Convert 1970-01-01 date to the 'Not Set'
-# this code should be generalized!!!
-if ( $PrimaryGroupBy =~ /(Daily|Monthly|Annually)$/ ) {
- my $re;
- $re = qr{1970-01-01} if $PrimaryGroupBy =~ /Daily$/;
- $re = qr{1970-01} if $PrimaryGroupBy =~ /Monthly$/;
- $re = qr{1970} if $PrimaryGroupBy =~ /Annually$/;
- foreach (@keys) {
- s/^$re/loc('Not Set')/e;
- }
-}
-
-unless (@keys && @values) {
- @keys = ('');
- @values = (0);
-}
-
-my %data;
-foreach my $key (@keys) { $data{$key} = shift @values; }
-my @sorted_keys = sort @keys;
-my @sorted_values = map { $data{$_}} @sorted_keys;
-
-
-
-my $plot = $chart->plot( [ [@sorted_keys], [@sorted_values] ] ) or die $chart->error;
-$m->comp( 'SELF:Plot', plot => $plot, %ARGS );
-</%init>
-
-<%METHOD Plot>
-<%ARGS>
-$plot => undef
-</%ARGS>
-<%INIT>
-my @types = ('png', 'gif');
-
-for my $type (@types) {
- $plot->can($type)
- or next;
-
- $r->content_type("image/$type");
- $m->out( $plot->$type );
- $m->abort();
-}
-
-die "Your GD library appears to support none of the following image types: " . join(', ', @types);
-</%INIT>
-
-</%METHOD>
diff --git a/rt/html/Search/Chart.html b/rt/html/Search/Chart.html
deleted file mode 100644
index 9fca23b..0000000
--- a/rt/html/Search/Chart.html
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Query => "id > 0"
-$PrimaryGroupBy => 'Queue'
-$SecondaryGroupBy => ''
-$ChartStyle => 'bars'
-$Description => undef
-</%args>
-<%init>
-$ARGS{SecondaryGroupBy} ||= '';
-
-my $title = loc( "Search results grouped by [_1]", $PrimaryGroupBy );
-
-my $saved_search = $m->comp( '/Widgets/SavedSearch:new',
- SearchType => 'Chart',
- SearchFields => [qw(Query PrimaryGroupBy SecondaryGroupBy ChartStyle)] );
-
-my @actions = $m->comp( '/Widgets/SavedSearch:process', args => \%ARGS, self => $saved_search );
-
-</%init>
-<& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs, Title => $title &>
-<& /Elements/ListActions, actions => \@actions &>
-<& /Search/Elements/Chart, %ARGS &>
-
-
-<& /Widgets/SavedSearch:show, %ARGS, Action => 'Chart.html', self => $saved_search, Title => 'Saved charts' &>
diff --git a/rt/html/Search/Edit.html b/rt/html/Search/Edit.html
deleted file mode 100755
index b7708f7..0000000
--- a/rt/html/Search/Edit.html
+++ /dev/null
@@ -1,88 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title&>
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Edit.html?".$QueryString,
- Title => $title,
- Format => $Format,
- Query => $Query,
- Rows => $ARGS{'Rows'},
- OrderBy => $ARGS{'OrderBy'},
- Order => $ARGS{'Order'} &>
-
-<& Elements/NewListActions, actions => \@actions &>
-
-<form method="post" action="Build.html">
-<input type="hidden" class="hidden" name="SearchId" value="<%$SearchId%>" />
-<textarea name="Query" rows="8" cols="72"><%$Query%></textarea>
-<br />
-<textarea name="Format" rows="8" cols="72"><%$Format%></textarea>
-<br />
-<& /Elements/Submit, Label => loc("Apply"), Reset => 1, Caption => loc("Apply your changes")&>
-</form>
-
-<%INIT>
-my $title = loc("Edit Query");
-$Format = $m->comp('/Elements/ScrubHTML', Content => $Format);
-my $QueryString = $m->comp('/Elements/QueryString',
- Query => $Query,
- Format => $Format,
- Rows => $ARGS{'Rows'},
- OrderBy => $ARGS{'OrderBy'},
- Order => $ARGS{'Order'},
- );
-
-</%INIT>
-
-
-<%ARGS>
-$Query => undef
-$Format => undef
-$SearchId => 'new'
-@actions => undef
-</%ARGS>
diff --git a/rt/html/Search/Elements/BuildFormatString b/rt/html/Search/Elements/BuildFormatString
deleted file mode 100644
index 0526333..0000000
--- a/rt/html/Search/Elements/BuildFormatString
+++ /dev/null
@@ -1,244 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Format => undef
-%cfqueues => undef
-$Face => undef
-$Size => undef
-$Link => undef
-$Title => undef
-$AddCol => undef
-$RemoveCol => undef
-$ColUp => undef
-$ColDown => undef
-$SelectDisplayColumns => undef
-$CurrentDisplayColumns => undef
-</%args>
-<%init>
-
-unless ($Format) {
- $Format = $RT::DefaultSearchResultFormat;
-}
-
-
-# All the things we can display in the format string by default
-my @fields = qw(
- id
- Status
- ExtendedStatus
- Subject
- QueueName
- OwnerName
- Priority
- InitialPriority
- FinalPriority
- Type
- TimeWorked
- TimeLeft
- TimeEstimated
- CreatedBy
- LastUpdatedBy
- Requestors
- Cc
- AdminCc
- Starts
- StartsRelative
- Started
- StartedRelative
- Created
- CreatedRelative
- LastUpdated
- LastUpdatedRelative
- Told
- ToldRelative
- Due
- DueRelative
- Resolved
- ResolvedRelative
- RefersTo
- ReferredToBy
- DependsOn
- DependedOnBy
- MemberOf
- Members
- Parents
- Children
- NEWLINE
-);
-
-my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %cfqueues) {
- # What does this _do_? What are the keys to cfqueues
- $id =~ s/^.'*(.*).'*$/$1/;
- # Gotta load up the $queue object, since queues get stored by name now.
- my $queue = RT::Queue->new($session{'CurrentUser'});
- $queue->Load($id);
- $CustomFields->LimitToQueue($queue->Id);
-}
-$CustomFields->LimitToGlobal;
-
-while ( my $CustomField = $CustomFields->Next ) {
- push @fields, "CustomField.{" . $CustomField->Name . "}";
-}
-
-my ( @seen);
-
-my @format = split( /,\s*/, $Format );
-foreach my $field (@format) {
- my %column = ();
- $field =~ s/'(.*)'/$1/;
- my ( $prefix, $suffix );
- if ( $field =~ m/(.*)__(.*)__(.*)/ ) {
- $prefix = $1;
- $suffix = $3;
- $field = $2;
- }
- $field = "<blank>" if !$field;
- $column{Prefix} = $prefix;
- $column{Suffix} = $suffix;
- $field =~ s/\s*(.*)\s*/$1/;
- $column{Column} = $field;
- push @seen, \%column;
-}
-
-if ( $RemoveCol ) {
- my $index = $CurrentDisplayColumns;
- my $column = $seen[$index];
- if ($index) {
- delete $seen[$index];
- my @temp = @seen;
- @seen = ();
- foreach my $element (@temp) {
- next unless $element;
- push @seen, $element;
- }
- }
-}
-elsif ( $AddCol ) {
- if ( defined $SelectDisplayColumns ) {
- my $selected = $SelectDisplayColumns;
- my @columns;
- if (ref($selected) eq 'ARRAY') {
- @columns = @$selected;
- } else {
- push @columns, $selected;
- }
- foreach my $col (@columns) {
- my %column = ();
- $column{Column} = $col;
-
- if ( $Face eq "Bold" ) {
- $column{Prefix} .= "<b>";
- $column{Suffix} .= "</b>";
- }
- if ( $Face eq "Italic" ) {
- $column{Prefix} .= "<i>";
- $column{Suffix} .= "</i>";
- }
- if ($Size) {
- $column{Prefix} .= "<" . $m->interp->apply_escapes( $Size, 'h' ) . ">";
- $column{Suffix} .= "</" . $m->interp->apply_escapes( $Size, 'h' ) . ">";
- }
- if ( $Link eq "Display" ) {
- $column{Prefix} .=
- "<a HREF=\"" . $RT::WebPath . "/Ticket/Display.html?id=__id__\">";
- $column{Suffix} .= "</a>";
- }
- elsif ( $Link eq "Take" ) {
- $column{Prefix} .= "<a HREF=\"" . $RT::WebPath
- . "/Ticket/Display.html?Action=Take&id=__id__\">";
- $column{Suffix} .= "</a>";
- }
-
- if ($Title) {
- $column{Suffix} .= "/TITLE:" . $m->interp->apply_escapes( $Title, 'h' );
- }
- push @seen, \%column;
-}
-}
-}
-elsif ( $ColUp ) {
- my $index = $CurrentDisplayColumns;
- if ( defined $index && ( $index - 1 ) >= 0 ) {
- my $column = $seen[$index];
- $seen[$index] = $seen[ $index - 1 ];
- $seen[ $index - 1 ] = $column;
- $CurrentDisplayColumns = $index - 1;
- }
-}
-elsif ( $ColDown ) {
- my $index = $CurrentDisplayColumns;
- if ( defined $index && ( $index + 1 ) < scalar @seen ) {
- my $column = $seen[$index];
- $seen[$index] = $seen[ $index + 1 ];
- $seen[ $index + 1 ] = $column;
- $CurrentDisplayColumns = $index + 1;
- }
-}
-
-
-my @format_string;
-foreach my $field (@seen) {
- next unless $field;
- my $row = "'";
- $row .= $field->{Prefix} if $field->{Prefix};
- $row .= "__" . ($field->{Column} =~ m/\(/ ? $field->{Column} # func, don't escape
- : $m->interp->apply_escapes( $field->{Column}, 'h' )) . "__"
- unless ( $field->{Column} eq "<blank>" );
- $row .= $field->{Suffix} if $field->{Suffix};
- $row .= "'";
- push( @format_string, $row );
-}
-
-$Format = join(",\n", @format_string);
-
-
-return($Format, \@fields, \@seen);
-
-</%init>
-
diff --git a/rt/html/Search/Elements/Chart b/rt/html/Search/Elements/Chart
deleted file mode 100644
index 37a4da2..0000000
--- a/rt/html/Search/Elements/Chart
+++ /dev/null
@@ -1,139 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Query => "id > 0"
-$PrimaryGroupBy => 'Queue'
-$SecondaryGroupBy => undef
-$ChartStyle => 'bars'
-</%args>
-<%init>
-use RT::Report::Tickets;
-my $tix = RT::Report::Tickets->new( $session{'CurrentUser'} );
-$tix->FromSQL( $Query );
-my $count_name = $tix->Column( FUNCTION => 'COUNT', FIELD => 'id' );
-$tix->GroupBy( FIELD => $PrimaryGroupBy );
-my $value_name = $tix->Column( FIELD => $PrimaryGroupBy );
-
-my %class = (
- Queue => 'RT::Queue',
- Owner => 'RT::User',
-);
-my $class = $class{ $PrimaryGroupBy };
-
-my (@keys, @values);
-while ( my $entry = $tix->Next ) {
- if ($class) {
- my $q = $class->new( $session{'CurrentUser'} );
- $q->Load( $entry->__Value( $value_name ) );
- push @keys, $q->Name;
- }
- else {
- push @keys, $entry->__Value( $value_name );
- }
- $keys[-1] ||= loc('(no value)');
- push @values, $entry->__Value( $count_name );
-}
-
-# XXX: Convert 1970-01-01 date to the 'Not Set'
-# this code should be generalized!!!
-if ( $PrimaryGroupBy =~ /(Daily|Monthly|Annually)$/ ) {
- my $re;
- $re = qr{1970-01-01} if $PrimaryGroupBy =~ /Daily$/;
- $re = qr{1970-01} if $PrimaryGroupBy =~ /Monthly$/;
- $re = qr{1970} if $PrimaryGroupBy =~ /Annually$/;
- foreach (@keys) {
- s/^$re/loc('Not Set')/e;
- }
-}
-
-my %data;
-foreach my $key (@keys) { $data{$key} = shift @values; }
-my @sorted_keys = sort @keys;
-my @sorted_values = map { $data{$_}} @sorted_keys;
-
-
-my $query_string = $m->comp('/Elements/QueryString', %ARGS);
-</%init>
-
-<% loc('Query:') %>&nbsp;<% $Query %><br />
-
-<img src="<%$RT::WebPath%>/Search/Chart?<%$query_string|n%>" /><br />
-
-<table class="collection-as-table">
-<tr>
-<th class="collection-as-table"><% $tix->Label($PrimaryGroupBy) %>
-</th>
-<th class="collection-as-table"><&|/l&>Tickets</&>
-</th>
-</tr>
-% my ($i,$total);
-% while (my $key = shift @sorted_keys) {
-% $i++;
-% my $value = shift @sorted_values;
-% $total += $value;
-<tr class="<%$i%2 ? 'evenline' : 'oddline' %>">
-<td class="label collection-as-table">
-<%$key%>
-</td>
-<td class="value collection-as-table">
-<%$value%>
-</td>
-</tr>
-% }
-
-%$i++;
-<tr class="<%$i%2 ? 'evenline' : 'oddline' %>">
-<td class="label collection-as-table">
-<%loc('Total')%>
-</td>
-<td class="value collection-as-table">
-<%$total%>
-</td>
-</tr>
-
-</table>
diff --git a/rt/html/Search/Elements/DisplayOptions b/rt/html/Search/Elements/DisplayOptions
deleted file mode 100644
index 1ddbafd..0000000
--- a/rt/html/Search/Elements/DisplayOptions
+++ /dev/null
@@ -1,144 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc("Display Columns") &>
-<table width="100%">
-<tr>
-<td>
-<& EditFormat, %ARGS &>
-</td>
-<td valign="top">
-<table valign="top">
-
-% for my $o (0..3) {
-<tr>
-<td class="label">
-% if ($o == 0) {
-<&|/l&>Order by</&>:
-% }
-</td>
-<td class="value">
-<select name="OrderBy">
-% if ($o > 0) {
-<option value=""><&|/l&>~[none~]</&></option>
-% }
-% foreach my $field (sort keys %fields) {
-% next unless $field;
-<option value="<%$field%>"
-% if (defined $OrderBy[$o] and $field eq $OrderBy[$o]) {
-selected
-% }
-><&|/l&><%$field%></&></option>
-% }
-</select>
-<select name="Order">
-<option value="ASC"
-% unless ( ($Order[$o]||'') eq "DESC" ) {
-selected
-% }
-><&|/l&>Asc</&></option>
-<option value="DESC"
-% if ( ($Order[$o]||'') eq "DESC" ) {
-selected
-% }
-><&|/l&>Desc</&></option>
-</select>
-</td>
-</tr>
-% }
-<tr>
-<td class="label">
-<&|/l&>Rows per page</&>:
-</td><td class="value">
-<& /Elements/SelectResultsPerPage,
- Name => "RowsPerPage",
- Default => $RowsPerPage &>
-</td>
-</tr>
-</table>
-</td>
-</tr>
-</table>
-</&>
-
-<%INIT>
-my $tickets = new RT::Tickets($session{'CurrentUser'});
-my %fields = %{$tickets->FIELDS};
-map { $fields{$_}->[0] =~ /^(?:ENUM|INT|DATE|STRING)$/ || delete $fields{$_} } keys %fields;
-delete $fields{'EffectiveId'};
-$fields{'Owner'} = 1;
-$fields{ $_ . '.EmailAddress' } = 1 foreach( qw(Requestor Cc AdminCc) );
-
-# Add all available CustomFields to the list of sortable columns.
-my @cfs = grep /^CustomField/, @{$ARGS{AvailableColumns}};
-$fields{$_}=1 for @cfs;
-
-# Add PAW sort
-$fields{'Custom.Ownership'} = 1;
-
-my @Order;
-my @OrderBy;
-if ($OrderBy =~ /\|/) {
- @OrderBy = split /\|/, $OrderBy;
-} else {
- @OrderBy = ( $OrderBy );
-}
-if ($Order =~ /\|/) {
- @Order = split /\|/, $Order;
-} else {
- @Order = ( $Order );
-}
-
-</%INIT>
-
-<%ARGS>
-$Order => undef
-$OrderBy => undef
-$RowsPerPage => undef
-$Format => undef
-$GroupBy => 'id'
-</%ARGS>
diff --git a/rt/html/Search/Elements/EditFormat b/rt/html/Search/Elements/EditFormat
deleted file mode 100644
index 31cc215..0000000
--- a/rt/html/Search/Elements/EditFormat
+++ /dev/null
@@ -1,116 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
-<tr>
-<td>
-<&|/l&>Add Columns</&>:
-</td>
-<td>
-<&|/l&>Format</&>:
-</td>
-<td></td>
-<td>
-<&|/l&>Show Columns</&>:
-</td>
-<tr>
-<td valign="top">
-<select size="6" name="SelectDisplayColumns" multiple>
-% foreach my $field ( @$AvailableColumns) {
-<option value="<%$field%>"><% loc( $field) %></option>
-%# $m->comp( '/Elements/RT__Ticket/ColumnMap', Name => $field, Attr => 'title') ||
-% }
-</select>
-</td>
-<td>
-<&|/l&>Link</&>:
-<select name="Link">
-<option value="None">-</option>
-<option value="Display"><&|/l&>Display</&></option>
-<option value="Take"><&|/l&>Take</&></option>
-</select>
-<br /><&|/l&>Title</&>: <input name="Title" size="10" />
-<br /><&|/l&>Size</&>:
-<select name="Size">
-<option value="">-</option>
-<option value="Small"><&|/l&>Small</&></option>
-<option value="Large"><&|/l&>Large</&></option>
-</select>
-<br /><&|/l&>Style</&>:
-<select name="Face">
-<option value="">-</option>
-<option value="Bold"><&|/l&>Bold</&></option>
-<option value="Italic"><&|/l&>Italic</&></option>
-</select>
-</td>
-<td>
-<input type="submit" class="button" name="AddCol" value=" &rarr; " />
-</td>
-<td valign="top">
-<select size="4" name="CurrentDisplayColumns">
-% my $i=0;
-% foreach my $field (@$CurrentFormat) {
-<option value="<%$i++%>><%$field->{Column}%>">
-<%loc( $field->{Column}) %></option>
-% }
-</select>
-<br />
-<center>
-<input type="submit" class="button" name="ColUp" value=" &uarr; " />
-<input type="submit" class="button" name="ColDown" value=" &darr; " />
-<input type="submit" class="button" name="RemoveCol" value="<%loc('Delete')%>" />
-</center>
-</td>
-<td colspan="3" align="center">
-</td>
-</tr>
-</table>
-
-<%ARGS>
-$CurrentFormat => undef
-$AvailableColumns => undef
-</%ARGS>
diff --git a/rt/html/Search/Elements/EditQuery b/rt/html/Search/Elements/EditQuery
deleted file mode 100644
index 3b08c8b..0000000
--- a/rt/html/Search/Elements/EditQuery
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& NewListActions, actions => $actions &>
-<&|/Widgets/TitleBox, title => join(': ', grep defined, loc("Current search"), $Description) &>
-<select size="10" name="clauses" style="width: 100%" multiple>
-% $m->out($optionlist);
-</select>
-<p align="center">
-<input type="submit" class="button" name="Up" value=" &uarr; " />
-<input type="submit" class="button" name="Down" value=" &darr; " />
-<input type="submit" class="button" name="Left" value=" &larr; " />
-<input type="submit" class="button" name="Right" value=" &rarr; " />
-<input type="submit" class="button" name="Toggle" value="<&|/l&>And/Or</&>" />
-<input type="submit" class="button" name="DeleteClause" value="<&|/l&>Delete</&>" />
-%#<input type="submit" class="button" name="EditQuery" value="Advanced" />
-</p>
-</&>
-<%ARGS>
-$Description
-$optionlist
-$actions
-</%ARGS>
diff --git a/rt/html/Search/Elements/EditSearches b/rt/html/Search/Elements/EditSearches
deleted file mode 100644
index a22dc4b..0000000
--- a/rt/html/Search/Elements/EditSearches
+++ /dev/null
@@ -1,103 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc($Title)&>
-%# Hide all the save functionality if the user shouldn't see it.
-% if ($session{'CurrentUser'}->HasRight( Right => 'CreateSavedSearch',
-% Object=> $RT::System )) {
-<&|/l&>Privacy:</&>
-% if ($CurrentSearch->{'Object'} && $CurrentSearch->{'Object'}->id) {
-<& SearchPrivacy, Object => $CurrentSearch->{'Object'}->Object &><br />
-% } else {
-<& SelectSearchObject, Name => 'Owner', Objects => \@Objects &><br />
-% }
-<&|/l&>Description</&>:<br>
-<font size="-1"><input size="25" name="Description" value="<%$CurrentSearch->{'Description'} || ''%>" /></font>
-% if ($SearchId ne 'new') {
-<nobr>
-% if ($Dirty) {
-<input type="submit" class="button" name="Revert" value="<%loc('Revert')%>" />
-% }
-<input type="submit" class="button" name="Delete" value="<%loc('Delete')%>" />
-% if ($AllowCopy) {
-<input type="submit" class="button" name="CopySearch" value="<%loc('Copy')%>" />
-% }
-</nobr>
-
-% }
-<input type="submit" name="Save" value="<%loc('Save')%>" class="button" />
-<hr />
-% }
-<&|/l&>Load saved search:</&><br />
-<& SelectSearchesForObjects, Name => 'LoadSavedSearch', Objects => \@Objects, SearchType => $SearchType &>
-<input value="<%loc('Load')%>" type="submit" class="button" />
-</&>
-
-<%init>
-unless ($session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
- Object=> $RT::System )) {
- return;
-}
-
-use RT::SavedSearches;
-my @Objects = RT::SavedSearches->new($session{CurrentUser})->_PrivacyObjects;
-push @Objects, RT::System->new($session{'CurrentUser'})
- if $session{'CurrentUser'}->HasRight( Object=> $RT::System,
- Right => 'SuperUser');
-
-</%INIT>
-
-<%ARGS>
-$SearchType => 'Ticket'
-$SearchId => undef
-$CurrentSearch => undef
-$Description => undef
-$HideResults => 0
-$Dirty => 0
-$AllowCopy => 1
-$Title => loc('Saved searches')
-</%ARGS>
diff --git a/rt/html/Search/Elements/NewListActions b/rt/html/Search/Elements/NewListActions
deleted file mode 100644
index 33fc360..0000000
--- a/rt/html/Search/Elements/NewListActions
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if ($actions[0] ) {
-<b><%loc('Results')%></b><br />
-% foreach my $action (@actions) {
-% next unless ($action);
-% my @item = @$action;
-% if ($item[1] < 0) {
-<font color="red">
-% }
-&nbsp;<%$item[0]%><br />
-% if ($item[1] < 0) {
-</font>
-% }
-% }
-<br />
-% }
-<%init>
-@actions = grep (/./,@actions);
-</%init>
-<%ARGS>
-@actions => undef
-</%ARGS>
diff --git a/rt/html/Search/Elements/PickBasics b/rt/html/Search/Elements/PickBasics
deleted file mode 100644
index b91fde3..0000000
--- a/rt/html/Search/Elements/PickBasics
+++ /dev/null
@@ -1,176 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<tr>
-<td class="label">
-<&|/l&>id</&>
-</td><td>
-<& /Elements/SelectEqualityOperator, Name => "idOp" &>
-</td><td>
-<input name="ValueOfid" size="5" />
-</td>
-</tr>
-
-<tr><td>
-<& /Elements/SelectAttachmentField, Name => 'AttachmentField' &>
-</td><td>
-<& /Elements/SelectBoolean, Name => "AttachmentOp",
- True => loc("matches"),
- False => loc("does not match"),
- TrueVal => 'LIKE',
- FalseVal => 'NOT LIKE'
-&>
-</td><td>
-<input name="ValueOfAttachment" size="20" />
-</td>
-</tr>
-<tr>
-<td class="label">
-<&|/l&>Queue</&>
-</td><td>
-<& /Elements/SelectBoolean, Name => "QueueOp" ,
- True => loc("is"),
- False => loc("isn't"),
- TrueVal=> '=',
- FalseVal => '!=' &>
-</td><td>
-<& /Elements/SelectQueue,
- Name => "ValueOfQueue",
- NamedValues => 1,
- CheckQueueRight => 'ShowTicket' &>
-</td>
-</tr>
-<tr>
-<td class="label">
-<&|/l&>Status</&>
-</td><td>
-<& /Elements/SelectBoolean, Name => "StatusOp",
- True => loc("is"),
- False => loc("isn't"),
- TrueVal=> '=',
- FalseVal => '!='
-&>
-</td><td>
-<& /Elements/SelectStatus, Name => "ValueOfStatus", SkipDeleted => 1 &>
-</td>
-</tr>
-<tr><td class="label">
-<select name="ActorField">
-<option value="Owner"><&|/l&>Owner</&></option>
-<option value="Creator"><&|/l&>Creator</&></option>
-<option value="LastUpdatedBy"><&|/l&>LastUpdatedBy</&></option>
-</select>
-</td><td>
-<& /Elements/SelectBoolean, Name => "ActorOp",
- TrueVal=> '=',
- FalseVal => '!='
-&>
-</td><td>
-<& /Elements/SelectOwner, Name => "ValueOfActor", ValueAttribute => 'Name' &>
-</td>
-</tr>
-<tr>
-<td class="label">
-<& SelectPersonType, Name => 'WatcherField', Default => 'Requestor' &>
-</td><td>
-<& /Elements/SelectMatch, Name => "WatcherOp" &>
-</td><td>
-<input name="ValueOfWatcher" size="20" />
-</tr>
-<tr>
-<td class="label">
-<& /Elements/SelectDateType, Name=>"DateField" &>
-</td><td>
-<& /Elements/SelectDateRelation, Name=>"DateOp" &>
-</td><td>
-<& /Elements/SelectDate, Name => "ValueOfDate", ShowTime => 0, Default => '' &>
-</td></tr>
-<tr>
-<td class="label">
-<select name="TimeField">
-<option value="TimeWorked"><&|/l&>Time Worked</&></option>
-<option value="TimeEstimated"><&|/l&>Time Estimated</&></option>
-<option value="TimeLeft"><&|/l&>Time Left</&></option>
-</select>
-</td><td>
-<& /Elements/SelectEqualityOperator, Name => "TimeOp" &>
-</td><td>
-<input name="ValueOfTime" size="5" />
-<& /Elements/SelectTimeUnits, Name =>'ValueOfTime' &>
-</td>
-</tr>
-<tr>
-<td class="label">
-<select name="PriorityField">
-<option value="Priority"><&|/l&>Priority</&></option>
-<option value="InitialPriority"><&|/l&>Initial Priority</&></option>
-<option value="FinalPriority"><&|/l&>Final Priority</&></option>
-</select>
-</td><td>
-<& /Elements/SelectEqualityOperator, Name => "PriorityOp" &>
-</td><td>
-<input name="ValueOfPriority" size="5" />
-</td>
-</tr>
-<tr>
-<td class="label">
-<& SelectLinks, Name=>"LinksField" &>
-</td><td>
-<& /Elements/SelectBoolean, Name => "LinksOp",
- True => loc("is"),
- False => loc("isn't"),
- TrueVal=> '=',
- FalseVal => '!=' &>
-</td><td>
-<input name="ValueOfLinks" value="" size="5" />
-</td></tr>
-<%INIT>
-my @people = ('Actor',
- 'Watcher',
- 'WatcherGroup',
- );
-</%INIT>
diff --git a/rt/html/Search/Elements/PickCFs b/rt/html/Search/Elements/PickCFs
deleted file mode 100644
index 0a50fad..0000000
--- a/rt/html/Search/Elements/PickCFs
+++ /dev/null
@@ -1,80 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% while ( my $CustomField = $CustomFields->Next ) {
-% my $name = "'CF.{" . $CustomField->Name . "}'";
-<tr><td class="label">
-<% $CustomField->Name %>
-</td>
-<td>
- <& /Elements/SelectCustomFieldOperator, Name => $name . "Op",
- True => loc("is"),
- False => loc("isn't"),
- TrueVal=> '=', FalseVal => '!=' &>
-</td>
-<td>
-<& /Elements/SelectCustomFieldValue, Name => "ValueOf" . $name,
- CustomField => $CustomField,
- &>
-</td></tr>
-% }
-<%INIT>
-my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %cfqueues) {
- $id =~ s/^.'*(.*).'*$/$1/;
- # Gotta load up the $queue object, since queues get stored by name now. my $id
- my $queue = RT::Queue->new($session{'CurrentUser'});
- $queue->Load($id);
- $CustomFields->LimitToQueue($queue->Id);
-}
-$CustomFields->LimitToGlobal();
-
-</%INIT>
-
-<%ARGS>
-%cfqueues => undef
-</%ARGS>
diff --git a/rt/html/Search/Elements/PickCriteria b/rt/html/Search/Elements/PickCriteria
deleted file mode 100644
index 153715c..0000000
--- a/rt/html/Search/Elements/PickCriteria
+++ /dev/null
@@ -1,82 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => loc('Add Criteria')&>
-<table width="100%" cellspacing="0" cellpadding="0" border="0">
- <tr>
- <td>
- <table cellspacing="0" border="0">
- <tr><td class="label">
- <&|/l&>Aggregator</&>:
- </td>
- <td><& SelectAndOr, Name => "AndOr" &>
- </td></tr>
- </table>
- </td></tr>
- <tr>
- <td>
- <hr>
- </td>
- </tr>
- <tr>
- <td valign="top">
- <table cellspacing="0" border="0">
- <& PickBasics &>
- <& PickCFs, cfqueues => \%cfqueues &>
- </table>
- </td>
- </tr>
- <tr><td>&nbsp;</td></tr>
-</table>
-
-</&>
-
-<%ARGS>
-$addquery => 0
-$query => undef
-%cfqueues => undef
-</%ARGS>
diff --git a/rt/html/Search/Elements/PickRestriction b/rt/html/Search/Elements/PickRestriction
deleted file mode 100644
index ff9b86b..0000000
--- a/rt/html/Search/Elements/PickRestriction
+++ /dev/null
@@ -1,142 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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%>/Search/Listing.html" METHOD="GET">
-<INPUT TYPE=HIDDEN NAME="Bookmark" VALUE="<% $session{'tickets'}->FreezeLimits()%>">
-<& /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/SearchPrivacy b/rt/html/Search/Elements/SearchPrivacy
deleted file mode 100644
index e439485..0000000
--- a/rt/html/Search/Elements/SearchPrivacy
+++ /dev/null
@@ -1,55 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Object => undef
-</%args>
-% if (ref($Object) eq 'RT::User' && $Object->id == $session{'CurrentUser'}->Id) {
-<&|/l&>My saved searches</&>
-% } else {
-<&|/l, $Object->Name&>[_1]'s saved searches</&>
-% }
diff --git a/rt/html/Search/Elements/SearchesForObject b/rt/html/Search/Elements/SearchesForObject
deleted file mode 100644
index 1a7ad32..0000000
--- a/rt/html/Search/Elements/SearchesForObject
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Object => undef
-</%args>
-<%init>
-# Returns an array of search objects associated on $Object,
-# in the form of [Description, searchObj]
-my @result;
-while (my $search = $Object->Attributes->Next) {
- my $desc;
- if ($search->Name eq 'SavedSearch') {
- push @result, [$search->Description, $search];
- }
- elsif ($search->Name =~ m/^Search - (.*)/) {
- push @result, [$1, $search];
- }
-}
-return @result;
-</%init>
diff --git a/rt/html/Search/Elements/SelectAndOr b/rt/html/Search/Elements/SelectAndOr
deleted file mode 100644
index 0a5ccc5..0000000
--- a/rt/html/Search/Elements/SelectAndOr
+++ /dev/null
@@ -1,53 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<input type="radio" class="radio" name="<%$Name%>" checked value="AND" /><&|/l&>AND</&></input>
-<input type="radio" class="radio" name="<%$Name%>" value="OR" /><&|/l&>OR</&></input>
-
-<%ARGS>
-$Name => "Operator"
-</%ARGS>
diff --git a/rt/html/Search/Elements/SelectChartType b/rt/html/Search/Elements/SelectChartType
deleted file mode 100644
index cbbf5e0..0000000
--- a/rt/html/Search/Elements/SelectChartType
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Name => 'ChartType'
-$Default => 'bar'
-</%args>
-<select name="<%$Name%>">
-% foreach my $option qw(bar pie) {
-<option value="<%$option%>" <% $option eq $Default ? 'SELECTED' : '' %>><%loc($option)%></option>
-% }
-</select>
diff --git a/rt/html/Search/Elements/SelectGroup b/rt/html/Search/Elements/SelectGroup
deleted file mode 100644
index 60c0bbb..0000000
--- a/rt/html/Search/Elements/SelectGroup
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-% if ($AllowNull) {
-<option value="">-</option>
-% }
-%while (my $group = $groups->Next) {
-<option value="<%$group->id%>" <%$group->id eq $Default && "SELECTED"%>><%$group->Name%></option>
-%}
-</select>
-
-<%INIT>
-my $groups = new RT::Groups($session{'CurrentUser'});
-$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => $Domain);
-
-</%INIT>
-<%ARGS>
-$AllowNull => 1
-$Default=> ''
-$Name => 'Group'
-$Domain => 'UserDefined';
-</%ARGS>
diff --git a/rt/html/Search/Elements/SelectGroupBy b/rt/html/Search/Elements/SelectGroupBy
deleted file mode 100644
index e7ab934..0000000
--- a/rt/html/Search/Elements/SelectGroupBy
+++ /dev/null
@@ -1,63 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Name => 'GroupBy'
-$Default => 'Status'
-$Query => ''
-</%args>
-<select name="<% $Name %>">
-% while (@options) {
-% my ($text, $value) = (shift @options, shift @options);
-<option value="<% $value %>" <% $value eq $Default ? 'selected' : '' %>><% loc($text) %></option>
-% }
-</select>
-<%init>
-use RT::Report::Tickets;
-my $report = RT::Report::Tickets->new( $session{'CurrentUser'} );
-my @options = $report->Groupings( Query => $Query );
-</%init>
diff --git a/rt/html/Search/Elements/SelectLinks b/rt/html/Search/Elements/SelectLinks
deleted file mode 100644
index 54505a4..0000000
--- a/rt/html/Search/Elements/SelectLinks
+++ /dev/null
@@ -1,66 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select name="<%$Name%>">
-% foreach (@fields) {
-<option value="<%$_%>"><&|/l&><%$_%></&></option>
-% }
-</select>
-<%ARGS>
-$Name => 'LinksField'
-</%ARGS>
-
-<%INIT>
-my @fields = ('HasMember',
- 'MemberOf',
- 'DependsOn',
- 'DependedOnBy',
- 'RefersTo',
- 'ReferredToBy',
- 'LinkedTo',
- );
-</%INIT>
diff --git a/rt/html/Search/Elements/SelectPersonType b/rt/html/Search/Elements/SelectPersonType
deleted file mode 100644
index e2a9a21..0000000
--- a/rt/html/Search/Elements/SelectPersonType
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<select NAME ="<%$Name%>">
-% if ($AllowNull) {
-<option value="">-</option>
-% }
-% for my $option (@types) {
-% if ($Suffix) {
-<option value="<% $option %><% $Suffix %>" <%$option eq $Default && "SELECTED"%> ><%loc($option)%></option>
-% next;
-% }
-% foreach my $subtype (@subtypes) {
-<option value="<%"$option.$subtype"%>" <%$option eq $Default && $subtype eq 'EmailAddress' && "SELECTED"%> ><% loc($option) %> <% loc($subtype) %></option>
-% }
-% }
-</select>
-
-<%INIT>
-my @types;
-if ($Scope =~ 'queue') {
- @types = qw(Cc AdminCc);
-}
-elsif ($Suffix eq 'Group') {
- @types = qw(Requestor Cc AdminCc Watcher);
-}
-else {
- @types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher);
-}
-
-my @subtypes = qw(EmailAddress Name RealName Nickname Organization Address1 Address2 WorkPhone HomePhone MobilePhone PagerPhone id);
-
-</%INIT>
-<%ARGS>
-$AllowNull => 1
-$Suffix => ''
-$Default=>undef
-$Scope => 'ticket'
-$Name => 'WatcherType'
-</%ARGS>
diff --git a/rt/html/Search/Elements/SelectSearchObject b/rt/html/Search/Elements/SelectSearchObject
deleted file mode 100644
index 81e22e1..0000000
--- a/rt/html/Search/Elements/SelectSearchObject
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-@Objects => undef
-$Name => undef
-</%args>
-<select name="<%$Name%>">
-% foreach my $object (@Objects) {
-% if (ref($object) eq 'RT::User' && $object->id == $session{'CurrentUser'}->Id) {
-<option value="<%ref($object)%>-<%$object->id%>"><&|/l&>My saved searches</&></option>
-% } else {
-<option value="<%ref($object)%>-<%$object->id%>"><&|/l, $object->Name&>[_1]'s saved searches</&></option>
-% }
-% }
-</select>
diff --git a/rt/html/Search/Elements/SelectSearchesForObjects b/rt/html/Search/Elements/SelectSearchesForObjects
deleted file mode 100644
index b2a83cf..0000000
--- a/rt/html/Search/Elements/SelectSearchesForObjects
+++ /dev/null
@@ -1,69 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-@Objects => undef
-$Name => undef
-$SearchType => 'Ticket',
-</%args>
-<select name="<%$Name%>">
-% foreach my $object (@Objects) {
-% if (ref($object) eq 'RT::User' && $object->id == $session{'CurrentUser'}->Id) {
-<option value=""><&|/l&>My saved searches</&></option>
-% } else {
-<option value=""></option>
-<option value=""><&|/l, $object->Name&>[_1]'s saved searches</&></option>
-% }
-% my @searches = $object->Attributes->Named('SavedSearch');
-% foreach my $search (@searches) {
-% # Skip it if it is not of search type we want.
-% next if ($search->SubValue('SearchType')
-% && $search->SubValue('SearchType') ne $SearchType);
-<option value="<%ref($object)%>-<%$object->id%>-SavedSearch-<%$search->Id%>"> -<%$search->Description||loc('Unnamed search')%></option>
-% }
-% }
-</select>
diff --git a/rt/html/Search/Elements/TicketHeader b/rt/html/Search/Elements/TicketHeader
deleted file mode 100644
index ed2f60e..0000000
--- a/rt/html/Search/Elements/TicketHeader
+++ /dev/null
@@ -1,40 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index 5def9ea..0000000
--- a/rt/html/Search/Elements/TicketHeaderCell
+++ /dev/null
@@ -1,55 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index 5d1ad20..0000000
--- a/rt/html/Search/Elements/TicketRow
+++ /dev/null
@@ -1,55 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index 68b1fd7..0000000
--- a/rt/html/Search/Listing.html
+++ /dev/null
@@ -1,113 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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="<%$RT::WebPath%>/Search/Listing.html?GotoPage=1"><&|/l&>First page</&></a>
-&nbsp;&nbsp;
-% if ( $session{'tickets'}->FirstRow >= $session{'tickets_rows_per_page'}-1 ) {
-<a href="<%$RT::WebPath%>/Search/Listing.html?GotoPage=Prev">&lt;<&|/l&>Previous page</&></a>
-&nbsp;&nbsp;
-% }
-% if ( $session{'tickets'}->FirstRow + $session{'tickets_rows_per_page'} < $ticketcount ) {
-<a href="<%$RT::WebPath%>/Search/Listing.html?GotoPage=Next"><&|/l&>Next page</&>&gt;</a>
-% }
-%#&nbsp;&nbsp;<form method=get action="<%$RT::WebPath%>/Search/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="<%$RT::WebPath%>/Search/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()|nu%>&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;
- $session{'tickets'}->CleanSlate;
- }
-}
- 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/Search/Results.html b/rt/html/Search/Results.html
deleted file mode 100755
index e2c6be7..0000000
--- a/rt/html/Search/Results.html
+++ /dev/null
@@ -1,177 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title, Refresh => $session{'tickets_refresh_interval'},
- RSSAutoDiscovery => $RSSFeedURL &>
-<& /Ticket/Elements/Tabs,
- current_tab => "Search/Results.html".$QueryString,
- Title => $title,
- Format => $Format,
- Query => $Query,
- Rows => $Rows,
- OrderBy => $OrderBy,
- Order => $Order &>
-<& /Elements/TicketList,
- Query => $Query,
- AllowSorting => 1,
- OrderBy => $OrderBy,
- Order => $Order,
- Rows => $Rows,
- Page => $Page,
- Format => $Format,
- BaseURL => $RT::WebPath."/Search/Results.html?"
-
- &>
-% my %hiddens = (Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order, HideResults => $HideResults, Page => $Page );
-<div align="right">
-<form method="get" action="<%$RT::WebPath%>/Search/Results.html">
-%foreach my $key (keys(%hiddens)) {
-<input type="hidden" class="hidden" name="<%$key%>" value="<%defined($hiddens{$key})?$hiddens{$key}:''%>"/>
-%}
-<& /Elements/Refresh, Name => 'TicketsRefreshInterval', Default => $session {'tickets_refresh_interval'} &>
-<input type="submit" class="button" value="<&|/l&>Go!</&>" />
-</form>
-</div>
-<div align="right">
-<a href="<%$RT::WebPath%>/Search/Bulk.html<%$QueryString%>"><&|/l&>Update multiple tickets</&></a><br />
-<a href="<%$RT::WebPath%>/Search/Results.html<%$QueryString%>"><&|/l&>Bookmarkable link</&></a><br />
-<a href="<%$RT::WebPath%>/Search/Results.tsv<%$QueryString%>"><&|/l&>spreadsheet</&></a> |
-<a href="<%$RSSFeedURL%>"><&|/l&>RSS</&></a> |
-<a href="<%$RT::WebPath%>/Tools/Offline.html<%$ShortQueryString%>"><&|/l&>Work offline</&></a><br />
-<form method="get" action="<%$RT::WebPath%>/Search/Chart.html"><&|/l&>chart</&>
-% %hiddens = (Query => $Query, Format => $Format, Rows => $Rows, OrderBy => $OrderBy, Order => $Order);
-%foreach my $key (keys(%hiddens)) {
-<input type="hidden" class="hidden" name="<%$key%>" value="<%defined($hiddens{$key})?$hiddens{$key}:''%>"/>
-%}
-<&|/l, $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query) &>grouped by [_1]</&>
-<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle') &>style: [_1]</&>
-<input type="submit" class="button" value="<%loc('Go!')%>" />
-</form>
-<& /Elements/Callback, _CallbackName => 'SearchActions', QueryString => $QueryString&>
-</div>
-<%INIT>
-# Read from user preferences
-my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
-
-# These variables are what define a search_hash; this is also
-# where we give sane defaults.
-$Format ||= $prefs->{'Format'};
-$Order ||= $prefs->{'Order'} || 'ASC';
-$OrderBy ||= $prefs->{'OrderBy'} || 'id';
-
-# Some forms pass in "RowsPerPage" rather than "Rows"
-# We call it RowsPerPage everywhere else.
-
-if ( !defined($Rows) ) {
- if ( $ARGS{'RowsPerPage'} ) {
- $Rows = $ARGS{'RowsPerPage'};
- } elsif ( defined $prefs->{'RowsPerPage'} ) {
- $Rows = $prefs->{'RowsPerPage'};
- } else {
- $Rows = 50;
- }
-}
-
-my ($title, $ticketcount);
-$session{'i'}++;
-$session{'tickets'} = RT::Tickets->new($session{'CurrentUser'}) ;
-$session{'tickets'}->FromSQL($Query) if ($Query);
-
-if ($OrderBy =~ /\|/) {
- # Multiple Sorts
- my @OrderBy = split /\|/,$OrderBy;
- my @Order = split /\|/,$Order;
- $session{'tickets'}->OrderByCols(
- map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } } ( 0
- .. $#OrderBy ) );;
-} else {
- $session{'tickets'}->OrderBy(FIELD => $OrderBy, ORDER => $Order);
-}
-
-$session{'CurrentSearchHash'} = {
- Format => $Format,
- Query => $Query,
- Page => $Page,
- Order => $Order,
- OrderBy => $OrderBy,
- RowsPerPage => $Rows
- };
-
-
-if ( $session{'tickets'}->Query()) {
- $ticketcount = $session{tickets}->CountAll();
- $title = loc('Found [quant,_1,ticket]', $ticketcount);
-} else {
- $title = loc("Find tickets");
-}
-
-my $QueryString = "?".$m->comp('/Elements/QueryString',
- Query => $Query,
- Format => $Format,
- Rows => $Rows,
- OrderBy => $OrderBy,
- Order => $Order,
- Page => $Page);
-my $ShortQueryString = "?".$m->comp('/Elements/QueryString', Query => $Query);
-my $RSSFeedURL = "$RT::WebPath/Search/Results.rdf$ShortQueryString";
-
-if ($ARGS{'TicketsRefreshInterval'}) {
- $session{'tickets_refresh_interval'} = $ARGS{'TicketsRefreshInterval'};
-}
-</%INIT>
-<%CLEANUP>
-$session{'tickets'}->PrepForSerialization();
-</%CLEANUP>
-<%ARGS>
-$Query => undef
-$Format => undef
-$HideResults => 0
-$Rows => undef
-$Page => 1
-$OrderBy => undef
-$Order => undef
-</%ARGS>
diff --git a/rt/html/Search/Results.rdf b/rt/html/Search/Results.rdf
deleted file mode 100644
index 7bcbe92..0000000
--- a/rt/html/Search/Results.rdf
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-
-my $Tickets = RT::Tickets->new($session{'CurrentUser'});
-$Tickets->FromSQL($ARGS{'Query'});
-$r->content_type('application/rss+xml');
-
-
-
- # create an RSS 1.0 file (http://purl.org/rss/1.0/)
- use XML::RSS;
- my $rss = new XML::RSS (version => '1.0');
- $rss->channel(
- title => "$RT::rtname: Syndicated Search",
- link => $RT::WebURL,
- description => "",
- dc => {
- },
- syn => {
- updatePeriod => "hourly",
- updateFrequency => "1",
- updateBase => "1901-01-01T00:00+00:00",
- },
- );
-
-
- while ( my $Ticket = $Tickets->Next()) {
- my $row;
- $rss->add_item(
- title => $Ticket->Subject,
- link => $RT::WebURL."/Ticket/Display.html?id=".$Ticket->id,
- description => $Ticket->Transactions->First->Content,
- dc => {
- subject => ($Ticket->Subject || loc('No subject')),
- creator => $Ticket->CreatorObj->RealName . "<".$Ticket->CreatorObj->EmailAddress.">",
- },
- );
- }
-$m->out($rss->as_string);
-$m->abort();
-</%INIT>
diff --git a/rt/html/Search/Results.tsv b/rt/html/Search/Results.tsv
deleted file mode 100644
index b7c9a42..0000000
--- a/rt/html/Search/Results.tsv
+++ /dev/null
@@ -1,134 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%ARGS>
-$OrderBy => 'id'
-$Order => 'ASC'
-</%ARGS>
-<%INIT>
-
-my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
-$Tickets->FromSQL( $ARGS{'Query'} );
-if ( $OrderBy =~ /\|/ ) {
-
- # Multiple Sorts
- my @OrderBy = split /\|/, $OrderBy;
- my @Order = split /\|/, $Order;
- $Tickets->OrderByCols(
- map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } }
- ( 0 .. $#OrderBy ) );
-}
-else {
- $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
-}
-
-my @rows;
-my %known_cfs;
-
-my @attrs = qw( id QueueObj->Name Subject Status TimeEstimated TimeWorked TimeLeft Priority FinalPriority OwnerObj->Name
- Requestors->MemberEmailAddressesAsString Cc->MemberEmailAddressesAsString AdminCc->MemberEmailAddressesAsString
- DueObj->ISO ToldObj->ISO CreatedObj->ISO ResolvedObj->ISO LastUpdatedObj->ISO);
-
-$r->content_type('application/vnd.ms-excel');
-while ( my $Ticket = $Tickets->Next()) {
- my $row;
- foreach my $attr (@attrs) {
- if ($attr =~ /(.*)->ISO$/ and $Ticket->$1->Unix <= 0) {
- $row->{$attr} = "";
- } else {
- my $method = '$Ticket->'.$attr.'()';
- $row->{$attr} = eval $method;
- if ($@) {die "Failed to find $attr - ". $@};
- }
- }
-
- my $cfs = $Ticket->QueueObj->TicketCustomFields();
- while (my $cf = $cfs->Next) {
- my @content;
- my $values = $Ticket->CustomFieldValues($cf->Id);
- while (my $value = $values->Next) {
- push @content, $value->Content;
- }
- $row->{'CustomField-'.$cf->Id} = join(', ',@content);
- if ($row->{'CustomField-'.$cf->Id}) {
- $known_cfs{$cf->Id} = $cf->Name;
- }
- }
- push @rows, $row;
-}
-
-{
- my @header;
- foreach my $attr (@attrs) {
- my $label = $attr;
- $label =~ s'Obj-.(?:AsString|Name|ISO)''g;
- $label =~ s'-\>MemberEmailAddressesAsString''g;
- push @header, $label;
- }
- foreach my $id (sort keys %known_cfs) {
- push @header, "CF-".$known_cfs{$id};
- }
- $m->out(join("\t", @header));
- $m->out("\n");
-}
-
-foreach my $row (@rows) {
- my @row;
- foreach my $attr(@attrs) {
- push @row, $row->{"$attr"};
- }
- foreach my $id (sort keys %known_cfs) {
- my $val = $row->{'CustomField-'.$id};
- $val =~ s/(\n|\r)//g;
- push @row, $val;
- }
- $m->out(join("\t",@row));
- $m->out("\n");
-}
-
-$m->abort();
-</%INIT>
diff --git a/rt/html/Search/Simple.html b/rt/html/Search/Simple.html
deleted file mode 100644
index c531bd1..0000000
--- a/rt/html/Search/Simple.html
+++ /dev/null
@@ -1,107 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Elements/Tabs,
- current_toptab => "Search/Simple.html",
- Title => $title
-&>
-
-<& /Elements/Callback, _CallbackName => 'PreForm', %ARGS &>
-
-<div id="SimpleSearchForm">
-<form action="Simple.html" method="get">
-
-<p><&|/l&>Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>.</&></p>
-
-<p><&|/l&>Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>.</&></p>
-<p><&|/l&>RT will look for anything else you enter in ticket subjects.</&></p>
-
-<br />
-<br />
-<div align="center">
-<input name="q" size="60" /><input type="submit" class="button" value="<&|/l&>Search</&>" />
-</div>
-
-</form>
-
-<& /Elements/Callback, _CallbackName => 'PostForm', %ARGS &>
-
-</div>
-
-<%INIT>
-my $title = loc("Search for tickets");
-use RT::Search::Googleish;
-
-if ($q) {
- my $tickets = new RT::Tickets( $session{'CurrentUser'} );
-
- $m->comp('/Elements/Callback', %ARGS, _CallbackName => 'ModifyQuery', query => \$q);
-
- if ($q =~ /^(\d+)$/) {
- RT::Interface::Web::Redirect($RT::WebURL."/Ticket/Display.html?id=".$q);
- }
-
- my %args = (
- Argument => $q,
- TicketsObj => $tickets,
- );
-
- $m->comp('/Elements/Callback', %ARGS, _CallbackName => 'SearchArgs', args => \%args);
-
- my $search = RT::Search::Googleish->new(%args);
-
- $m->comp( "Results.html", Query => $search->QueryToSQL() );
- $m->comp( "/Elements/Footer" );
- $m->abort();
-}
-</%INIT>
-
-<%ARGS>
-$q => undef
-</%ARGS>
-
diff --git a/rt/html/SelfService/Attachment/dhandler b/rt/html/SelfService/Attachment/dhandler
deleted file mode 100644
index 592062b..0000000
--- a/rt/html/SelfService/Attachment/dhandler
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-$m->comp('/Ticket/Attachment/dhandler', %ARGS);
-$m->abort;
-</%init>
diff --git a/rt/html/SelfService/Closed.html b/rt/html/SelfService/Closed.html
deleted file mode 100644
index 1f53655..0000000
--- a/rt/html/SelfService/Closed.html
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /SelfService/Elements/Header, Title => loc('Closed tickets') &>
-
-<& /SelfService/Elements/MyRequests, status => ['rejected', 'resolved'],
- friendly_status => loc('closed'),
- BaseURL => $RT::WebPath . "/SelfService/Closed.html?",
- Page => $Page &>
-<%ARGS>
-$Page => 1
-</%ARGS>
diff --git a/rt/html/SelfService/Create.html b/rt/html/SelfService/Create.html
deleted file mode 100644
index 9a64b5f..0000000
--- a/rt/html/SelfService/Create.html
+++ /dev/null
@@ -1,177 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& Elements/Header, Title => loc("Create a ticket") &>
-
-<& /Elements/ListActions, actions => \@results &>
-
-<form action="<% $RT::WebPath %>/SelfService/Create.html" method="post" enctype="multipart/form-data" name="TicketCreate">
-<input type="hidden" class="hidden" name="id" value="new" />
-
-<table>
-<tr>
-<td class="label"><&|/l&>Queue</&>:</td>
-<td class="value">
- <input type="hidden" class="hidden" name="Queue" value="<% $queue_obj->id %>" />
- <strong><% $queue_obj->Name %></strong> (<%$queue_obj->Description || ''%>)
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Requestors</&>:</td>
-<td class="value">
-<input name="Requestors" value="<% $ARGS{'Requestors'} || $session{CurrentUser}->EmailAddress %>" size="20" />
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Cc</&>:</td>
-<td class="value">
-<input name="Cc" size="20" value="<% $ARGS{'Cc'} || '' %>" />
-</td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Subject</&>:</td>
-<td class="value">
-<input name="Subject" size="60" maxsize="200" value="<% $ARGS{'Subject'} || '' %>" />
-</td>
-</tr>
-<tr>
- <td colspan="2">
- <& /Ticket/Elements/EditCustomFields, QueueObj => $queue_obj &>
- </td>
-</tr>
-<tr>
-<td class="label"><&|/l&>Attach file</&>:</td>
-<td class="value">
-<input name="Attach" type="file" />
-</td>
-</tr>
-<tr>
-<td colspan="2">
-<&|/l&>Describe the issue below</&>:<br />
-% if (exists $ARGS{Content}) {
-<& /Elements/MessageBox, Default => $ARGS{Content}, IncludeSignature => 0 &>
-% } else {
-<& /Elements/MessageBox &>
-% }
-</td>
-</tr>
-</table>
-<& /Elements/Submit, Label => loc("Create ticket")&>
-
-
-</form>
-<%args>
-$Queue => undef
-</%args>
-<%init>
-my $queue_obj = RT::Queue->new($session{'CurrentUser'});
-$queue_obj->Load($Queue) || Abort(loc("Queue could not be loaded."));
-$queue_obj->Disabled && Abort(loc("Cannot create tickets in a disabled queue."));
-
-my ($checks_failure, $skip_create, @results) = (0, 0, ());
-$skip_create = 1 unless ($ARGS{'id'}||'') eq 'new';
-
-$m->comp('/Elements/Callback',
- QueueObj => $queue_obj, ARGSRef => \%ARGS,
- skip_create => \$skip_create, checks_failure => \$checks_failure,
- results => \@results
-);
-
-$skip_create = 1 if exists $ARGS{'AddMoreAttach'};
-
-# 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 };
-}
-
-unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
- delete $session{'Attachments'};
-}
-
-my $CFs = $queue_obj->TicketCustomFields;
-my $ValidCFs = $m->comp(
- '/Elements/ValidateCustomFields',
- CustomFields => $CFs,
- ARGSRef => \%ARGS
-);
-unless ( $ValidCFs ) {
- $checks_failure = 1;
- while ( my $CF = $CFs->Next ) {
- my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
- push @results, $CF->Name . ': ' . $msg
- if ($ARGS{'id'}||'') eq 'new';
- }
-}
-
-if ( !$checks_failure && !$skip_create ) {
- $m->comp('Display.html', %ARGS);
- $RT::Logger->crit("After display call; error is $@");
- $m->abort();
-}
-</%init>
diff --git a/rt/html/SelfService/CreateTicketInQueue.html b/rt/html/SelfService/CreateTicketInQueue.html
deleted file mode 100755
index 3162f20..0000000
--- a/rt/html/SelfService/CreateTicketInQueue.html
+++ /dev/null
@@ -1,63 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& Elements/Header, Title => loc("Create a ticket") &>
-
-<h1><&|/l&>Select a queue for your new ticket</&></h1>
-
-<dl>
-% while (my $queue = $queues->Next) {
-% next unless $queue->CurrentUserHasRight('CreateTicket');
-
-<dt><a href="<%$RT::WebPath%>/SelfService/Create.html?Queue=<%$queue->id%>"><%$queue->Name%></a></dt>
-<dd><%$queue->Description%></dd>
-% }
-</dl>
-<%init>
-my $queues = RT::Queues->new($session{'CurrentUser'});
-$queues->UnLimit;
-</%init>
diff --git a/rt/html/SelfService/Display.html b/rt/html/SelfService/Display.html
deleted file mode 100644
index 3c9ba85..0000000
--- a/rt/html/SelfService/Display.html
+++ /dev/null
@@ -1,235 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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%" class="boxcontainer">
- <&| /Widgets/TitleBox, title => loc('The Basics'),
- title_class=> 'inverse',
- color => "#993333" &>
- <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
- <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket &>
- </&>
-</td>
- <td valign="top" width="50%" class="boxcontainer">
- <&| /Widgets/TitleBox, title => loc("Dates"),
- title_class=> 'inverse',
- color => "#663366" &>
- <& /Ticket/Elements/ShowDates, Ticket => $Ticket, UpdatedLink => 0 &>
- </&>
-</td>
-</tr>
-</table>
-
-
-
-%#!!pape: selfservice_find_attachments.patch {{
-<& /Ticket/Elements/ShowHistory,
- Ticket => $Ticket,
- URIFile => "Display.html",
- ShowHeaders => $ARGS{'ShowHeaders'},
- AttachPath => "Attachment",
- Attachments => $attachments,
- UpdatePath => "Update.html"
-&>
-%#!!pape: selfservice_find_attachments.patch }}
-
-
-
-<%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'} );
-
-# 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 ( $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;
- }
-
-
- ( $Ticket, @results ) =
- CreateTicket( Attachments => $session{'Attachments'}, %ARGS, Status => 'new' );
-
- unless ( $Ticket->id ) {
- $m->comp( 'Error.html', Why => join( "\n", @results ));
- $m->abort();
- }
-
- # }}}
-
- # 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();
- }
-
- my ( $code, $msg );
-
- #Update the status
- if ( ( defined $ARGS{'Status'} )
- and $ARGS{'Status'}
- and ( $ARGS{'Status'} ne $Ticket->Status ) )
- {
- ( $code, $msg ) = $Ticket->SetStatus( $ARGS{'Status'} );
- push @results, "$msg";
- }
-
- # }}}
-
- if (
- $session{'Attachments'}
- || ( defined $ARGS{'UpdateContent'}
- && $ARGS{'UpdateContent'} ne ''
- && $ARGS{'UpdateContent'} ne "-- \n"
- . $session{'CurrentUser'}->UserObj->Signature )
- )
- {
- $ARGS{UpdateAttachments} = $session{'Attachments'};
- }
- ProcessUpdateMessage(
- ARGSRef => \%ARGS,
- Actions => \@results,
- TicketObj => $Ticket
- );
- delete $session{'Attachments'};
-
- # delete temporary storage entry to make WebUI clean
- unless ( keys %{ $session{'Attachments'} } and $ARGS{'UpdateAttach'} ) {
- delete $session{'Attachments'};
- }
-
- my @cfupdates = ProcessObjectCustomFieldUpdates(Object => $Ticket, ARGSRef => \%ARGS);
- push (@results, @cfupdates);
-
- # }}}
-
- }
-
- # This code does automatic redirection if any updates happen.
-
- unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
- $m->comp( 'Error.html',
- Why => loc("No permission to display that ticket") );
- $m->abort();
- }
-
- if (@results) {
- # We've done something, so we need to clear the decks to avoid
- # resubmission on refresh.
- # But we need to store Actions somewhere too, so we don't lose them.
- $session{"Actions"} = \@results;
- RT::Interface::Web::Redirect($RT::WebURL."SelfService/Display.html?id="
- . $Ticket->id);
- } else {
- @results = @{ delete $session{"Actions"} || [] };
- }
-
- my $Transactions = $Ticket->Transactions;
-
- my $attachments =
- $m->comp( '/Ticket/Elements/FindAttachments', Ticket => $Ticket );
-
-</%INIT>
-
-
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/SelfService/Elements/GotoTicket b/rt/html/SelfService/Elements/GotoTicket
deleted file mode 100644
index f2ad07a..0000000
--- a/rt/html/SelfService/Elements/GotoTicket
+++ /dev/null
@@ -1,48 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<form action="<%$RT::WebPath%>/SelfService/Display.html"><input type="submit" class="button" 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
deleted file mode 100644
index f26d191..0000000
--- a/rt/html/SelfService/Elements/Header
+++ /dev/null
@@ -1,49 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 21f8ada..0000000
--- a/rt/html/SelfService/Elements/MyRequests
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => $title &>
-<& /Elements/TicketList, Title => $title,
- Format => $Format,
- Query => $Query,
- Order => $Order,
- OrderBy => $OrderBy,
- BaseURL => $BaseURL,
- Page => $Page &>
-</&>
-
-<%INIT>
-my $id = $session{'CurrentUser'}->id;
-my $Query = "( "
- . join( ' OR ', map "$_.id = $id", @roles )
- . ")";
-if ( @status ) {
- $Query .= " AND ( "
- . join( ' OR ', map "Status = '$_'", @status )
- . " )";
-}
-my $Order = "ASC";
-my $OrderBy = "Created";
-my $Format = qq{
- '<B><A HREF="$RT::WebPath/SelfService/Display.html?id=__id__">__id__</a></B>/TITLE:#',
- '<B><A HREF="$RT::WebPath/SelfService/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
- Status,
- Requestors,
- OwnerName};
-</%INIT>
-<%ARGS>
-$friendly_status => loc('open')
-$title => loc("My [_1] tickets", $friendly_status)
-@roles => ('Watcher')
-@status => ('open', 'new', 'stalled')
-$BaseURL => undef
-$Page => 1
-</%ARGS>
diff --git a/rt/html/SelfService/Elements/Tabs b/rt/html/SelfService/Elements/Tabs
deleted file mode 100644
index adc019f..0000000
--- a/rt/html/SelfService/Elements/Tabs
+++ /dev/null
@@ -1,113 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Callback, tabs => $tabs, %ARGS &>
-<& /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>
-my $queues = RT::Queues->new($session{'CurrentUser'});
-$queues->UnLimit;
-
-my $queue_count = 0;
-my $queue_id = 1;
-
-while (my $queue = $queues->Next) {
- next unless $queue->CurrentUserHasRight('CreateTicket');
- $queue_id = $queue->id;
- $queue_count++;
- last if ($queue_count > 1);
-}
-
-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',
- },
- };
-
-if ($queue_count > 1) {
- $tabs->{C} = { title => loc('New ticket'),
- path => 'SelfService/CreateTicketInQueue.html'
- };
-} else {
- $tabs->{C} = { title => loc('New ticket'),
- path => 'SelfService/Create.html?Queue=' . $queue_id
- };
-}
-
-if ($session{'CurrentUser'}->HasRight( Right => 'ModifySelf',
- Object => $RT::System )) {
- $tabs->{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
deleted file mode 100644
index 17fa793..0000000
--- a/rt/html/SelfService/Error.html
+++ /dev/null
@@ -1,70 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /SelfService/Elements/Header, Title => loc('Error') &>
-<h2 class="title"><%loc('Error')%></h2>
-<&| /Widgets/TitleBox, title => $Title &>
-<%$Why%>
-<br />
-<font size="-1">
-<%$Details%>
-</font>
-</&>
-</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
deleted file mode 100644
index 304ed5b..0000000
--- a/rt/html/SelfService/Prefs.html
+++ /dev/null
@@ -1,92 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /SelfService/Elements/Header, Title => loc('Preferences') &>
-
-<& /Elements/ListActions, actions => \@results &>
-<form method="post">
-
-% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
-<&| /Widgets/TitleBox, title => loc('Change password') &>
-<&|/l&>New password</&>: <input type="password" name="NewPass1" size="16" />
-<&|/l&>Confirm</&>: <input type="password" name="NewPass2" size="16" />
-</&>
-<br />
-% }
-<& /Elements/Submit, Label => loc('Save Changes') &>
- </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
deleted file mode 100644
index 9cdb4ed..0000000
--- a/rt/html/SelfService/Update.html
+++ /dev/null
@@ -1,129 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /SelfService/Elements/Header,
- Title =>loc('Update ticket #[_1]', $Ticket->id)
-&>
-
-
-<form action="Display.html" method="post" enctype="multipart/form-data">
-<input type="hidden" class="hidden" name="UpdateType" value="response" />
-<input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
-<table>
- <tr>
- <td class="label">
- <&|/l&>Status</&>
- </td>
- <td class="value">
- <& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)",loc($DefaultStatus)) &>
- </td>
- </tr>
- <tr>
- <td class="label">
- <&|/l&>Subject</&>
- </td>
- <td class="value">
- <input name="UpdateSubject" size="60" value="<% $Ticket->Subject %>" />
- </td>
-
- </tr>
-% if (exists $session{'Attachments'}) {
-<tr>
- <td class="label">
- <&|/l&>Attached file</&>
- </td>
- <td colspan="5" class="value">
- <&|/l&>Check box to delete</&><br />
-% foreach my $attach_name (keys %{$session{'Attachments'}}) {
- <input type="checkbox" class="checkbox" name="DeleteAttach-<%$attach_name%>" value="1" /><%$attach_name%><br />
-% } # end of foreach
- </td>
-</tr>
-% } # end of if
-<tr>
- <td class"label">
- <&|/l&>Attach</&>
- </td>
- <td class="value">
- <input name="Attach" type="file" />
- <input type="hidden" class="hidden" name="UpdateAttach" value="1" />
- </td>
- </tr>
-</table>
-<& /Ticket/Elements/EditCustomFields, TicketObj => $Ticket &>
-<& /Elements/MessageBox,
- Name => "UpdateContent",
- QuoteTransaction => $ARGS{QuoteTransaction}
- &>
- <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
deleted file mode 100644
index 517cb18..0000000
--- a/rt/html/SelfService/index.html
+++ /dev/null
@@ -1,54 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /SelfService/Elements/Header, Title => loc('Open tickets') &>
-
-<& /SelfService/Elements/MyRequests, BaseURL => $RT::WebPath . "/SelfService/?",
- Page => $Page &>
-<%ARGS>
-$Page => 1
-</%ARGS>
diff --git a/rt/html/Ticket/Attachment/dhandler b/rt/html/Ticket/Attachment/dhandler
deleted file mode 100644
index 9d3c7b4..0000000
--- a/rt/html/Ticket/Attachment/dhandler
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%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
deleted file mode 100644
index c35ed91..0000000
--- a/rt/html/Ticket/Create.html
+++ /dev/null
@@ -1,406 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header,
- Title => loc("Create a new ticket"),
- onload => "hide(document.getElementById('Ticket-Create-details'));" &>
-<& /Elements/Tabs,
- current_toptab => "Ticket/Create.html",
- Title => loc("Create a new ticket"),
- actions => $actions &>
-<& /Elements/ListActions, actions => \@results &>
-<form action="<%$RT::WebPath%>/Ticket/Create.html" method="post" enctype="multipart/form-data" name="TicketCreate">
-<input type="hidden" class="hidden" name="id" value="new" />
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-
-<div id="Ticket-Create-basics">
-<a name="basics"></a>
-<&| /Widgets/TitleBox, title => loc("Create a new ticket") &>
-<table border="0" cellpadding="0" cellspacing="0">
-<tr><td class="label"><&|/l&>Queue</&>:</td>
-<td class="value"><& Elements/ShowQueue, QueueObj => $QueueObj &>
-<input type="hidden" class="hidden" name="Queue" value="<% $QueueObj->Name %>" />
-</td>
-<td class="label"><&|/l&>Status</&>:
-</td>
-<td class="value">
-<& /Elements/SelectStatus, Name => "Status", Default => $ARGS{Status}||'new', DefaultValue => 0 &>
-</td>
-<td class="label">
-<&|/l&>Owner</&>:
-</td>
-<td class="value">
-<& /Elements/SelectOwner, Name => "Owner", QueueObj => $QueueObj, Default => $ARGS{Owner}||$RT::Nobody->Id, DefaultValue => 0 &>
-</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="label">
-<&|/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 <strong>will</strong> receive future updates.)</&></font></i>
-</td>
-</tr>
-<tr>
-<td class="label">
-<&|/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 <strong>will</strong> 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="200" value="<%$ARGS{Subject} || ''%>" />
-</td>
-</tr>
-<tr>
-<td colspan="6">
-<& /Ticket/Elements/EditCustomFields, QueueObj => $QueueObj &>
-</td>
-</tr>
-% if ($TxnCFs->Count) {
-% while (my $CF = $TxnCFs->Next()) {
-<tr>
-<td align="right"><% $CF->Name %>:</td>
-<td><& /Elements/EditCustomField, CustomField => $CF, NamePrefix =>
- "Object-RT::Transaction--CustomField-" &><em><% $CF->FriendlyType %></em></td>
-</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" class="checkbox" name="DeleteAttach-<%$attach_name%>" value="1" /><%$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" class="button" name="AddMoreAttach" value="<&|/l&>Add More Files</&>" />
-</td>
-</tr>
-<tr>
-<td colspan="6">
-<&|/l&>Describe the issue below</&>:<br />
-<& /Elements/Callback, _CallbackName => 'BeforeMessageBox', QueueObj => $QueueObj, %ARGS &>
-% if (exists $ARGS{Content}) {
-<& /Elements/MessageBox, Default => $ARGS{Content}, IncludeSignature => 0 &>
-% } else {
-<& /Elements/MessageBox, QuoteTransaction => $QuoteTransaction &>
-%}
-
-<br />
-</td>
-</tr>
-<tr>
-<td align="right" colspan="2">
-</td>
-</tr>
-</table>
-</&>
-<& /Elements/Submit, Label => loc("Create")&>
-</div>
-
-<div id="Ticket-Create-details">
-<a name="details"></a>
-<table width="100%" border="0">
-<tr>
-<td width="50%" valign="top">
-
- <&| /Widgets/TitleBox, 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 Estimated</&>:</td>
-<td>
-<input size="3" name="TimeEstimated" value="<%$ARGS{TimeEstimated}%>" />
-<& /Elements/SelectTimeUnits, Name =>'TimeEstimated' &>
-
-</td></tr>
-<tr><td align="right"><&|/l&>Time Worked</&>:</td>
-<td>
-<input size="3" name="TimeWorked" value="<%$ARGS{TimeWorked}%>" />
-<& /Elements/SelectTimeUnits, Name =>'TimeWorked' &>
-
-</td></tr>
-<tr>
-<td align="right"><&|/l&>Time Left</&>:</td>
-<td><input size="3" name="TimeLeft" value="<%$ARGS{TimeLeft}%>" />
-<& /Elements/SelectTimeUnits, Name =>'TimeLeft' &>
-</td></tr>
-</table>
-</&>
-<br />
-<&|/Widgets/TitleBox, title => loc("Dates"),
- title_class=> 'inverse',
- color => "#663366" &>
-
-<table>
-<tr><td class="label"><&|/l&>Starts</&>:</td><td><& /Elements/SelectDate, Name => "Starts", Default => $ARGS{Starts} || '' &></td></tr>
-<tr><td class="label"><&|/l&>Due</&>:</td><td><& /Elements/SelectDate, Name => "Due", Default => $ARGS{Due} || '' &></td></tr>
-</table>
-</&>
-<br />
-</td>
-
-<td valign="top">
-<&| /Widgets/TitleBox, title => loc('Links'), title_class=> 'inverse' &>
-
-<em><&|/l&>(Enter ticket ids or URLs, separated with spaces)</&></em>
-<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>
-</&>
-<br />
-
-</td>
-</tr>
-</table>
-<& /Elements/Submit, Label => loc("Create") &>
-</div>
-</form>
-
-<%INIT>
-
-my $CloneTicketObj;
-if ( $CloneTicket ) {
- $CloneTicketObj = RT::Ticket->new( $session{CurrentUser} );
- $CloneTicketObj->Load($CloneTicket) or Abort(loc("Ticket could not be loaded"));
-
- my $clone = {
- Requestors => join( ',', $CloneTicketObj->RequestorAddresses ),
- Cc => join( ',', $CloneTicketObj->CcAddresses),
- AdminCc => join( ',', $CloneTicketObj->AdminCcAddresses),
- InitialPriority => $CloneTicketObj->Priority,
- };
-
- $clone->{$_} = $CloneTicketObj->$_()
- for qw/Owner Subject FinalPriority TimeEstimated TimeWorked
- Status TimeLeft Starts Started Due Resolved/;
-
- my $members = $CloneTicketObj->Members;
- my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by );
- while ( my $member = $members->Next ) {
- push @members, $member->LocalBase;
- }
- $clone->{'MemberOf-new'} = join ' ', @members;
-
- my $members_of = $CloneTicketObj->MemberOf;
- while ( my $member_of = $members_of->Next ) {
- push @members_of, $member_of->LocalTarget;
- }
- $clone->{'new-MemberOf'} = join ' ', @members_of;
-
- my $refers = $CloneTicketObj->RefersTo;
- while ( my $refer = $refers->Next ) {
- push @refers, $refer->LocalTarget;
- }
- $clone->{'new-RefersTo'} = join ' ', @refers;
-
- my $refers_by = $CloneTicketObj->ReferredToBy;
- while ( my $refer_by = $refers_by->Next ) {
- push @refers_by, $refer_by->LocalBase;
- }
- $clone->{'RefersTo-new'} = join ' ', @refers_by;
-
- my $depends = $CloneTicketObj->DependsOn;
- while ( my $depend = $depends->Next ) {
- push @depends, $depend->LocalTarget;
- }
- $clone->{'new-DependsOn'} = join ' ', @depends;
-
- my $depends_by = $CloneTicketObj->DependedOnBy;
- while ( my $depend_by = $depends_by->Next ) {
- push @depends_by, $depend_by->LocalBase;
- }
- $clone->{'DependsOn-new'} = join ' ', @depends_by;
-
-
-
- my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields();
- while ( my $cf = $cfs->Next ) {
- my $cf_id = $cf->id;
- my $cf_values = $CloneTicketObj->CustomFieldValues( $cf->id );
- my @cf_values;
- while ( my $cf_value = $cf_values->Next ) {
- push @cf_values, $cf_value->Content;
- }
- $clone->{"Object-RT::Ticket--CustomField-$cf_id-Value"}
- = join "\n", @cf_values;
- }
-
- for ( keys %$clone ) {
- $ARGS{$_} = $clone->{$_} if not defined $ARGS{$_};
- }
-
-}
-
-my @results;
-my $QueueObj = new RT::Queue($session{'CurrentUser'});
-$QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded."));
-my $CFs = $QueueObj->TicketCustomFields();
-my $TxnCFs = $QueueObj->TicketTransactionCustomFields();
-
-my $ValidCFs = $m->comp(
- '/Elements/ValidateCustomFields',
- CustomFields => $CFs,
- ARGSRef => \%ARGS
-);
-
-# if no due date has been set explicitly, then use the
-# queue's default if it exists
-if ($QueueObj->DefaultDueIn && !$ARGS{'Due'}) {
- 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'}) and ($ARGS{'id'} eq 'new')) { # new ticket?
- if ($ValidCFs) {
- $m->comp('Display.html', %ARGS);
- $RT::Logger->crit("After display call; error is $@");
- $m->abort();
- }
- else {
- # Invalid CFs
- while (my $CF = $CFs->Next) {
- my $msg = $m->notes('InvalidField-' . $CF->Id) or next;
- push @results, $CF->Name . ': ' . $msg;
- }
- }
-}
-
-my $actions = {
- A => {
- html => q[<a href="#basics" onclick="return switchVisibility('Ticket-Create-basics','Ticket-Create-details');">] . loc('Show basics') . q[</a>],
- },
- B => {
- html => q[<a href="#details" onclick="return switchVisibility('Ticket-Create-details','Ticket-Create-basics');">] . loc('Show details') . q[</a>],
- },
-};
-</%INIT>
-
-<%ARGS>
-$DependsOn => undef
-$DependedOnBy => undef
-$MemberOf => undef
-$QuoteTransaction => undef
-$Queue => undef
-$CloneTicket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Display.html b/rt/html/Ticket/Display.html
deleted file mode 100644
index 7bdd57f..0000000
--- a/rt/html/Ticket/Display.html
+++ /dev/null
@@ -1,184 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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 &>
-<& /Elements/Callback, _CallbackName => 'BeforeShowSummary', Ticket => $TicketObj, %ARGS &>
-<&| /Widgets/TitleBox, title => loc('Ticket metadata') &>
-<& /Ticket/Elements/ShowSummary, Ticket => $TicketObj, Attachments => $attachments &>
-</&>
-
-<br />
-
-<& /Elements/Callback, _CallbackName => 'BeforeShowHistory', Ticket => $TicketObj, %ARGS &>
-
-<& /Ticket/Elements/ShowHistory ,
- Ticket => $TicketObj,
- Tickets => $Tickets,
- Collapsed => $ARGS{'Collapsed'},
- ShowHeaders => $ARGS{'ShowHeaders'},
- Attachments => $attachments,
- AttachmentContent => $attachment_content
-
- &>
-
-<& /Elements/Callback, _CallbackName => 'AfterShowHistory', Ticket => $TicketObj,
-current_tab => 'Ticket/Display.html?id=' . $TicketObj->id, %ARGS &>
-
-<%ARGS>
-$id => undef
-$Create => undef
-$ShowHeaders => 0
-$Collapsed => undef
-$TicketObj => undef
-</%ARGS>
-
-<%INIT>
-
-$m->comp('/Elements/Callback', _CallbackName => 'Initial', TicketObj => $TicketObj, ARGSRef => \%ARGS);
-
-my ($linkid, $message, $tid, @Actions, $Tickets);
-
-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");
- }
- }
-
- $m->comp('/Elements/Callback', _CallbackName => 'BeforeProcessArguments',
- TicketObj => $TicketObj, Tickets => $Tickets,
- ActionsRef => \@Actions, ARGSRef => \%ARGS);
-
- if (defined $ARGS{'Action'}) {
- if ($ARGS{'Action'} =~ /^(Steal|Kill|Take|SetTold)$/) {
- my $action = $1;
- my ($res, $msg)=$TicketObj->$action();
- push(@Actions, $msg);
- }
- }
-
- $ARGS{'UpdateContent'} =~ s/\r\n/\n/g if defined $ARGS{'UpdateContent'};
- if ( ( defined $ARGS{'UpdateContent'}
- && $ARGS{'UpdateContent'} ne ''
- && $ARGS{'UpdateContent'} ne "-- \n"
- . $session{'CurrentUser'}->UserObj->Signature ) || $session{'Attachments'} )
- {
- $ARGS{UpdateAttachments} = $session{'Attachments'};
- ProcessUpdateMessage(
- ARGSRef => \%ARGS,
- Actions => \@Actions,
- TicketObj => $TicketObj,
- );
- delete $session{'Attachments'};
- } elsif ( $ARGS{'UpdateTimeWorked'} ) {
- # Add UpdateTimeWorked to TimeWorked (processed below with ProcessTicketBasics)
- $ARGS{'TimeWorked'} = $TicketObj->TimeWorked + $ARGS{'UpdateTimeWorked'};
- }
- #Process status updates
- my @PeopleActions = ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj=>$TicketObj);
- my @BasicActions = ProcessTicketBasics(ARGSRef => \%ARGS, TicketObj=>$TicketObj);
- my @results = ProcessTicketLinks( TicketObj => $TicketObj, ARGSRef => \%ARGS);
-
- push (@Actions, @PeopleActions, @BasicActions, @results);
-}
-
-$m->comp('/Elements/Callback', _CallbackName => 'BeforeDisplay',
- TicketObj => \$TicketObj,
- Tickets => \$Tickets,
- Actions => \@Actions,
- ARGSRef => \%ARGS,
-);
-
-# This code does automatic redirection if any updates happen.
-
-if (@Actions) {
- # We've done something, so we need to clear the decks to avoid
- # resubmission on refresh.
- # But we need to store Actions somewhere too, so we don't lose them.
- $session{"Actions"} = \@Actions;
- RT::Interface::Web::Redirect($RT::WebURL."Ticket/Display.html?id=".$TicketObj->id);
-} else {
- @Actions = @{ delete $session{"Actions"} || [] };
-}
-
-my $attachments = $m->comp('Elements/FindAttachments', Ticket => $TicketObj, Tickets => $Tickets);
-my $attachment_content = $m->comp('Elements/LoadTextAttachments', Ticket => $TicketObj);
-
-</%INIT>
diff --git a/rt/html/Ticket/Elements/AddWatchers b/rt/html/Ticket/Elements/AddWatchers
deleted file mode 100644
index 7440069..0000000
--- a/rt/html/Ticket/Elements/AddWatchers
+++ /dev/null
@@ -1,123 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index b92f503..0000000
--- a/rt/html/Ticket/Elements/BulkLinks
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index 584bba8..0000000
--- a/rt/html/Ticket/Elements/EditBasics
+++ /dev/null
@@ -1,117 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
- <tr>
- <td class="label"><&|/l&>Subject</&>:</td>
- <td class="value"><input name="Subject" value="<%$TicketObj->Subject|h%>" size="50" /></td>
- </tr>
-
- <tr>
- <td class="label"><&|/l&>Status</&>:</td>
- <td class="value"><%$SelectStatus|n%></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Queue</&>:</td>
- <td class="value"><%$SelectQueue|n%></td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Owner</&>:</td>
- <td class="value"><& /Elements/SelectOwner,
- Name => 'Owner',
- QueueObj => $TicketObj->QueueObj,
- TicketObj => $TicketObj,
- Default => $TicketObj->OwnerObj->Id,
- DefaultValue => 0,
- &></td>
- </tr>
-
- <tr>
- <td class="label"><&|/l&>Time Estimated</&>:</td>
- <td class="value"><input name="TimeEstimated" value="<%$TicketObj->TimeEstimated|h%>" size="5" />
- <& /Elements/SelectTimeUnits, Name =>'TimeEstimated' &>
-</td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Time Worked</&>:</td>
- <td class="value"><input name="TimeWorked" value="<%$TicketObj->TimeWorked|h%>" size="5" />
- <& /Elements/SelectTimeUnits, Name =>'TimeWorked' &>
-</td>
-
- </tr>
- <tr>
- <td class="label"><&|/l&>Time Left</&>:</td>
- <td class="value"><input name="TimeLeft" value="<%$TicketObj->TimeLeft|h%>" size="5" />
- <& /Elements/SelectTimeUnits, Name =>'TimeLeft' &>
- </td>
- </tr>
-
- <tr>
- <td class="label"><&|/l&>Priority</&>:</td>
- <td class="value"><input name="Priority" value="<%$TicketObj->Priority|h%>" size="5" /></td>
- </tr>
-
- <tr>
- <td class="label"><&|/l&>Final Priority</&>:</td>
- <td class="value"><input name="FinalPriority" value="<%$TicketObj->FinalPriority|h%>" size="5" /></td>
- </tr>
-
-
-
-<& /Elements/Callback, _CallbackName => 'EndOfList', TicketObj => $TicketObj, %ARGS &>
-</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', DefaultLabel => loc("[_1] (Unchanged)",loc($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
deleted file mode 100644
index 399b4a5..0000000
--- a/rt/html/Ticket/Elements/EditCustomField
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
-
-# RT 3.2 API compatibility glue
-
-$RT::Logger->debug("Ticket/Elements/EditCustomField is deprecated in RT 3.4 and will be removed in 3.6.");
-
-$ARGS{'NamePrefix'} =~ s/^Ticket-/Object-RT::Ticket-/;
-$ARGS{'NamePrefix'} =~ s/^CustomField-/Object-RT::Ticket--CustomField-/;
-$m->comp('/Elements/EditCustomField', %ARGS, Object=> $ARGS{'TicketObj'});
-</%init>
diff --git a/rt/html/Ticket/Elements/EditCustomFields b/rt/html/Ticket/Elements/EditCustomFields
deleted file mode 100644
index 14a5681..0000000
--- a/rt/html/Ticket/Elements/EditCustomFields
+++ /dev/null
@@ -1,110 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
-% my $i = 0;
-% while ( my $CustomField = $CustomFields->Next ) {
-% next unless $CustomField->CurrentUserHasRight('ModifyCustomField');
-% $i++;
-% if ( $i % 2 ) {
-<tr>
-% }
-<td width="50%">
-<table>
- <tr id="CF-<%$CustomField->id%>-EditRow">
- <td class="labeltop">
- <b><%$CustomField->Name%></b><br />
- <i><%$CustomField->FriendlyType%></i>
- </td>
- <td class="entry"><& /Elements/EditCustomField,
- Object => $TicketObj,
- CustomField => $CustomField,
- NamePrefix => $NamePrefix ,
- Default => $m->notes('Field-' . $CustomField->Id),
- &>
-% if (my $msg = $m->notes('InvalidField-' . $CustomField->Id)) {
- <br />
- <em style="color: red"><% $msg %></em>
-% }
- </td>
- </tr>
-</table>
-</td>
-
-% unless ( $i % 2 ) {
-</tr>
-% }
-
-% }
-
-%# close row if required
-% if ( $i % 2 ) {
-</tr>
-% }
-
-</table>
-<%INIT>
-my $CustomFields;
-my $NamePrefix;
-
-if ($TicketObj) {
- $CustomFields = $TicketObj->CustomFields();
- $NamePrefix = "Object-RT::Ticket-".$TicketObj->Id."-CustomField-";
-
-} else {
- $CustomFields = $QueueObj->TicketCustomFields();
- $NamePrefix = "Object-RT::Ticket--CustomField-";
-}
-
- $m->comp('/Elements/Callback', _CallbackName => 'MassageCustomFields',
- CustomFields => $CustomFields);
-
-</%INIT>
-<%ARGS>
-$TicketObj => undef
-$QueueObj => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditDates b/rt/html/Ticket/Elements/EditDates
deleted file mode 100644
index 16ee2d2..0000000
--- a/rt/html/Ticket/Elements/EditDates
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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
deleted file mode 100644
index bdb8a6b..0000000
--- a/rt/html/Ticket/Elements/EditLinks
+++ /dev/null
@@ -1,133 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index a933b7b..0000000
--- a/rt/html/Ticket/Elements/EditPeople
+++ /dev/null
@@ -1,93 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table width="100%">
-<tr>
-<td valign="top">
-
-<h3><&|/l&>New watchers</&></h3>
-<&|/l&>Find people whose</&><br />
-<& /Elements/SelectUsers &>
-<input type="submit" class="button" name="OnlySearchForPeople" value="<&|/l&>Go!</&>" />
-<br />
-<&|/l&>Find groups whose</&><br />
-<& /Elements/SelectGroups &>
-<input type="submit" class="button" 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, DefaultValue => 0&>
-<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
deleted file mode 100644
index 68d16ad..0000000
--- a/rt/html/Ticket/Elements/EditWatchers
+++ /dev/null
@@ -1,81 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<ul>
-%# Print out a placeholder if there are none.
-%if ($Members->Count == 0 ) {
-<li><i><&|/l&>none</&></i></li>
-% }
-
-
-%while (my $watcher=$Members->Next) {
-<li>
-<input type="checkbox" class="checkbox" name="Ticket-DeleteWatcher-Type-<%$Watchers->Type%>-Principal-<%$watcher->MemberId%>" value="1" unchecked />
-%if ($watcher->MemberObj->IsUser) {
-<a href="<%$RT::WebPath%>/Admin/Users/Modify.html?id=<%$watcher->MemberObj->Object->id%>">
-<%$watcher->MemberObj->Object->Name%></a>
-% if ($TicketObj and grep { $_->Content eq $watcher->MemberObj->Object->EmailAddress } $TicketObj->SquelchMailTo) {
-<b><&|/l&>(Will not be sent email)</&></b>
-% }
-
-%} else {
-<a href="<%$RT::WebPath%>/Admin/Groups/Modify.html?id=<%$watcher->MemberObj->Object->id%>">
-<%$watcher->MemberObj->Object->Name%></a>
-%}
-</li>
-% }
-</ul>
-<%INIT>
-my $Members = $Watchers->MembersObj;
-</%INIT>
-<%ARGS>
-$TicketObj => undef
-$Watchers => undef
-</%ARGS>
-
-
-
diff --git a/rt/html/Ticket/Elements/FindAttachments b/rt/html/Ticket/Elements/FindAttachments
deleted file mode 100755
index ba562db..0000000
--- a/rt/html/Ticket/Elements/FindAttachments
+++ /dev/null
@@ -1,95 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-my %documents;
-
-#A default implementation here loops through all transactions and pulls out all their attachments.
-# We end up doing an end-run around that to get a bit more performance
-
-# We force the cache of ticket transactions to get populated up front. otherwise, the
-# code that looks at attachments will look at each one in turn.
-my $attachments = RT::Attachments->new( $session{'CurrentUser'} );
-
-$attachments->Columns( qw( Id Filename ContentType Headers Subject Parent ContentEncoding ContentType TransactionId Created));
-
-my $transactions = $attachments->NewAlias('Transactions');
-$attachments->Join( ALIAS1 => 'main',
- FIELD1 => 'TransactionId',
- ALIAS2 => $transactions,
- FIELD2 => 'id' );
-
-my $tickets = $attachments->NewAlias('Tickets');
-
- $attachments->Join( ALIAS1 => $transactions,
- FIELD1 => 'ObjectId',
- ALIAS2 => $tickets,
- FIELD2 => 'id' );
-
- $attachments->Limit( ALIAS => $transactions,
- FIELD => 'ObjectType',
- VALUE => 'RT::Ticket');
-if ($Tickets) {
- while ($Ticket = $Tickets->Next) {
- $attachments->Limit( ALIAS => $tickets,
- FIELD => 'EffectiveId',
- VALUE => $Ticket->id() );
- }
-} else {
- $attachments->Limit( ALIAS => $tickets,
- FIELD => 'EffectiveId',
- VALUE => $Ticket->id() );
-}
-
-
-return ($attachments);
-</%INIT>
-<%ARGS>
-$Ticket => undef
-$Tickets => undef
-</%ARGS>
-
diff --git a/rt/html/Ticket/Elements/LoadTextAttachments b/rt/html/Ticket/Elements/LoadTextAttachments
deleted file mode 100755
index cc9558e..0000000
--- a/rt/html/Ticket/Elements/LoadTextAttachments
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-
-my $attachments = RT::Attachments->new( $session{'CurrentUser'} );
-
-$attachments->Columns( qw(id Content ContentType TransactionId ContentEncoding));
-
-if ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
- my $transactions = $attachments->NewAlias('Transactions');
- $attachments->Join( ALIAS1 => 'main',
- FIELD1 => 'TransactionId',
- ALIAS2 => $transactions,
- FIELD2 => 'id' );
-
- my $tickets = $attachments->NewAlias('Tickets');
-
-
- $attachments->Join( ALIAS1 => $transactions,
- FIELD1 => 'ObjectId',
- ALIAS2 => $tickets,
- FIELD2 => 'id' );
-
- $attachments->Limit( ALIAS => $transactions,
- FIELD => 'ObjectType',
- VALUE => 'RT::Ticket');
-
-
- $attachments->Limit( ALIAS => $tickets,
- FIELD => 'EffectiveId',
- VALUE => $Ticket->id() );
- # if the user may not see comments do not return them
- unless ( $Ticket->CurrentUserHasRight('ShowTicketComments') ) {
- $attachments->Limit( ALIAS => $transactions, FIELD => 'Type', OPERATOR => '!=', VALUE => "Comment" );
- }
-
- $attachments->Limit ( FIELD => 'ContentType', OPERATOR => '=', VALUE => 'text/plain');
- $attachments->Limit ( FIELD => 'ContentType', OPERATOR => 'STARTSWITH', VALUE => 'message/');
- $attachments->Limit ( FIELD => 'ContentType', OPERATOR => '=', VALUE => 'text');
- if ($RT::SuppressInlineTextFiles) {
- $attachments->Limit ( FIELD => 'Filename', OPERATOR => 'IS', VALUE => 'NULL');
- }
-}
-return ($attachments);
-</%INIT>
-<%ARGS>
-$Ticket => undef
-</%ARGS>
-
diff --git a/rt/html/Ticket/Elements/PreviewScrips b/rt/html/Ticket/Elements/PreviewScrips
deleted file mode 100755
index 5edf8b5..0000000
--- a/rt/html/Ticket/Elements/PreviewScrips
+++ /dev/null
@@ -1,133 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$TicketObj => undef
-
-</%args>
-<%init>
-
-my $arg = 'Ticket-'.$TicketObj->Id.'-SquelchMailTo';
-my @squelchto = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} : ($ARGS{$arg});
-
-foreach my $address (@squelchto) {
- $TicketObj->SquelchMailTo($address) if ($address);
-}
-
-
-$arg = 'Ticket-'.$TicketObj->Id.'-UnsquelchMailTo';
-my @unsquelchto = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} : ($ARGS{$arg});
-
-foreach my $address (@unsquelchto) {
- $TicketObj->UnsquelchMailTo($address) if ($address);
-}
-
-
-my $action;
-
-if (( $ARGS{'UpdateType'} eq 'response' ) || ($ARGS{'Action'} eq 'Respond' )) {
- $action = 'Correspond';
-}
-else {
- $action = 'Comment';
-}
-
-my $Message = MakeMIMEEntity(
- Subject => $ARGS{'UpdateSubject'},
- Body => $ARGS{'UpdateContent'},
-);
-
-my ( $Transaction, $Description, $Object ) = $TicketObj->$action(
- CcMessageTo => $ARGS{'UpdateCc'},
- BccMessageTo => $ARGS{'UpdateBcc'},
- MIMEObj => $Message,
- TimeTaken => $ARGS{'UpdateTimeWorked'},
- DryRun => 1
-);
-unless ( $Transaction ) {
- $RT::Logger->error("Coulfn't fire '$action' action: $Description");
-}
-
-
-my @non_recipients = $TicketObj->SquelchMailTo;
-</%init>
-<h2><&|/l&>This message will be sent to...</&></h2>
-
-% if ( $Object ) {
-<i><&|/l&>(Check boxes to disable notifications to the listed recipients)</&></i><br />
-% foreach my $scrip (@{$Object->Scrips->Prepared}) {
-% next unless $scrip->ActionObj->Action->isa('RT::Action::SendEmail');
-<b><% $scrip->Description %></b><br />
-<&|/l, loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name)&>[_1] [_2] with template [_3]</&>
-<br />
-%foreach my $type qw(To Cc Bcc) {
-%my @addresses = $scrip->ActionObj->Action->$type();
-<ul>
-%foreach my $addr (@addresses) {
-<li> <b><%loc($type)%></b>: <input type="checkbox" class="checkbox" name="Ticket-<%$TicketObj->id%>-SquelchMailTo" value="<%$addr->address%>" /> <%$addr->address%>
-% }
-</ul>
-% }
-% if ($RT::PreviewScripMessages) {
-<textarea cols="80" rows="5">
-<%$scrip->ActionObj->TemplateObj->MIMEObj->as_string%>
-</textarea>
-% }
-% }
-% }
-<br />
-
-<h2><&|/l&>Messages about this ticket will not be sent to...</&></h2>
-<i><&|/l&>(Check boxes to enable notifications to the listed recipients)</&></i>
-<br />
-<ul>
-% foreach my $recipient (@non_recipients) {
-<li><input type="checkbox" class="checkbox" name="Ticket-<%$TicketObj->id%>-UnsquelchMailTo" value="<%$recipient->Content%>" />
-<% $recipient->Content %>
-% }
-</ul>
-<& /Elements/Submit, Value => 'UpdatePreview', Label => loc('Save changes')&>
diff --git a/rt/html/Ticket/Elements/Reminders b/rt/html/Ticket/Elements/Reminders
deleted file mode 100644
index ae72162..0000000
--- a/rt/html/Ticket/Elements/Reminders
+++ /dev/null
@@ -1,168 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Ticket => undef
-$id => undef
-$ShowCompleted => 0
-$Edit => 0
-</%args>
-<%init>
-
-$Ticket = LoadTicket($id) if ($id);
-
-my $request_args = $m->request_args();
-
-my $reminder_collection = $Ticket->Reminders->Collection;
-
-if ( $request_args->{'update-reminders'} ) {
- while ( my $reminder = $reminder_collection->Next ) {
- if ( $reminder->Status ne 'resolved' && $request_args->{ 'Complete-Reminder-' . $reminder->id } ) {
- $Ticket->Reminders->Resolve($reminder);
- }
- elsif ( $reminder->Status eq 'resolved' && !$request_args->{ 'Complete-Reminder-' . $reminder->id } ) {
- $Ticket->Reminders->Open($reminder);
- }
-
- if ( exists( $request_args->{ 'Reminder-Subject-' . $reminder->id } ) && ( $reminder->Subject ne $request_args->{ 'Reminder-Subject-' . $reminder->id } )) {
- $reminder->SetSubject( $request_args->{ 'Reminder-Subject-' . $reminder->id } ) ;
- }
-
- if ( exists( $request_args->{ 'Reminder-Owner-' . $reminder->id } ) && ( $reminder->Owner != $request_args->{ 'Reminder-Owner-' . $reminder->id } )) {
- $reminder->SetOwner( $request_args->{ 'Reminder-Owner-' . $reminder->id } , "Force" ) ;
- }
-
- if ( exists( $request_args->{ 'Reminder-Due-' . $reminder->id } ) && ( $reminder->DueObj->Date ne $request_args->{ 'Reminder-Due-' . $reminder->id } )) {
- $reminder->SetDue( $request_args->{ 'Reminder-Due-' . $reminder->id } ) ;
- }
- }
-}
-
-if ( $request_args->{'NewReminder-Subject'} ) {
- my $due_obj = RT::Date->new( $session{'CurrentUser'} );
- my $date = Time::ParseDate::parsedate(
- $request_args->{'NewReminder-Due'},
- UK => $RT::DateDayBeforeMonth,
- PREFER_PAST => 0,
- PREFER_FUTURE => 1
- );
- $due_obj->Set( Value => $date, Format => 'unix' );
- my ( $add_id, $msg, $txnid ) = $Ticket->Reminders->Add(
-
- Subject => $request_args->{'NewReminder-Subject'},
- Owner => $request_args->{'NewReminder-Owner'},
- Due => $due_obj->ISO
- );
-}
-
-# We've made changes, let's reload our search
-
-$reminder_collection = $Ticket->Reminders->Collection;
-</%init>
-<input type="hidden" class="hidden" name="id" value="<% $Ticket->id %>" />
-<input type="hidden" class="hidden" name="update-reminders" value="1" />
-<div>
-% while (my $reminder = $reminder_collection->Next) {
-% if ($reminder->Status eq 'resolved' && !$ShowCompleted) {
-<input type="hidden" class="hidden" name="Complete-Reminder-<% $reminder->id %>" value="1" />
-% } elsif ($Edit) {
-<& SELF:EditEntry, Reminder => $reminder, Ticket => $Ticket &>
-% } else {
-<& SELF:ShowEntry, Reminder => $reminder, Ticket => $Ticket &>
-% }
-% }
-</div>
-<div>
-<h3><&|/l&>New reminder:</&></h3>
-<& SELF:NewReminder, Ticket => $Ticket &>
-<%method NewReminder>
-<%args>
-$Ticket
-</%args>
-<div class="input-row">
-<label class="horizontal" for="NewReminder-Subject" ><&|/l&>Subject</&>:</label>
-<input type="text" size="15" name="NewReminder-Subject" />
-</div>
-<div class="input-row">
-<label class="horizontal" for="NewReminder-Owner" ><&|/l&>Owner</&>:</label>
-<& /Elements/SelectOwner, Name => 'NewReminder-Owner', QueueObj => $Ticket->QueueObj, DefaultValue => 0 &>
-</div>
-<div class="input-row">
-<label class="horizontal" for="NewReminder-Due" ><&|/l&>Due</&> <&|/l&>(yyyy/mm/dd)</&>:</label>
-<& /Elements/SelectDate, Name => "NewReminder-Due", Default => "" &>
-</div>
-</div>
-</%method>
-<%method EditEntry>
-<%args>
-$Reminder
-$Ticket
-</%args>
-<input
- type="checkbox"
- name="Complete-Reminder-<%$Reminder->id%>"
- <% $Reminder->Status eq 'resolved' ? 'CHECKED' : '' %>
-/>
- <input type="text" size="15" name="Reminder-Subject-<% $Reminder->id %>" value="<%$Reminder->Subject%>" /> &bull;
- <& /Elements/SelectOwner, Name => 'Reminder-Owner-'.$Reminder->id, Queue => $Ticket->QueueObj, Default => $Reminder->Owner, DefaultValue => 0 &>
- <& /Elements/SelectDate, Name => 'Reminder-Due-'.$Reminder->id, Default => $Reminder->DueObj->Date &>
- (<%$Reminder->DueObj->Unix>0 ? $Reminder->DueObj->AgeAsString : '' %>)<br />
-</%method>
-<%method ShowEntry>
-<%args>
-$Reminder
-$Ticket
-</%args>
-<input
- type="checkbox"
- name="Complete-Reminder-<%$Reminder->id%>"
- <% $Reminder->Status eq 'resolved' ? 'CHECKED' : '' %>
-/>
- <%$Reminder->Subject%> &bull;
- <%$Reminder->OwnerObj->Name%>
- <%$Reminder->DueObj->Unix>0 ? "&bull; ". $Reminder->DueObj->AgeAsString : '' |n%><br />
-</%method>
diff --git a/rt/html/Ticket/Elements/ShowAttachments b/rt/html/Ticket/Elements/ShowAttachments
deleted file mode 100644
index e2c5f9c..0000000
--- a/rt/html/Ticket/Elements/ShowAttachments
+++ /dev/null
@@ -1,104 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if (keys %documents) {
-<&| /Widgets/TitleBox, title => loc('Attachments'),
- title_class=> 'inverse',
- color => "#336699" &>
-
-% foreach my $key (keys %documents) {
-
-<%$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 size="-2">
-<a href="<%$RT::WebPath%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | u%>">
-<&|/l, $rev->CreatedAsString, $size, $rev->CreatorObj->Name &>[_1] ([_2]) by [_3]</&>
-</a>
-</font></li>
-% }
-% }
-</ul>
-
-% }
-</&>
-
-<br />
-% }
-
-<%INIT>
-
-# If we haven't been passed in an Attachments object (through the precaching mechanism)
-# then we need to find one
-$Attachments ||= $m->comp('FindAttachments', Ticket => $Ticket);
-
-my %documents;
-while ( my $attach = $Attachments->Next() ) {
- next unless ($attach->Filename());
- unshift( @{ $documents{ $attach->Filename } }, $attach );
-}
-
-</%INIT>
-<%ARGS>
-$Ticket => undef
-$Attachments => undef
-</%ARGS>
-
diff --git a/rt/html/Ticket/Elements/ShowBasics b/rt/html/Ticket/Elements/ShowBasics
deleted file mode 100644
index fe20831..0000000
--- a/rt/html/Ticket/Elements/ShowBasics
+++ /dev/null
@@ -1,85 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
- <tr>
- <td class="label id"><&|/l&>Id</&>:</td>
- <td class="value id"><%$Ticket->Id %></td>
- </tr>
- <tr>
- <td class="label status"><&|/l&>Status</&>:</td>
- <td class="value status"><&|/l&><% $Ticket->Status%></&></td>
- </tr>
-% if ($Ticket->TimeEstimated) {
- <tr>
- <td class="label time estimated"><&|/l&>Estimated</&>:</td>
- <td class="value time estimated"><& ShowTime, minutes => $Ticket->TimeEstimated &></td>
- </tr>
-% }
-% if ($Ticket->TimeWorked) {
- <tr>
- <td class="label time worked"><&|/l&>Worked</&>:</td>
- <td class="value time worked"><& ShowTime, minutes => $Ticket->TimeWorked &></td>
- </tr>
-% }
- <tr>
- <td class="label time left"><&|/l&>Left</&>:</td>
- <td class="value time left"><& ShowTime, minutes => $Ticket->TimeLeft &></td>
- </tr>
- <tr>
- <td class="label priority"><&|/l&>Priority</&>:</td>
- <td class="value priority"><%$Ticket->Priority%>/<%$Ticket->FinalPriority %></td>
- </tr>
- <tr>
- <td class="label queue"><&|/l&>Queue</&>:</td>
- <td class="value queue"><& ShowQueue, QueueObj => $Ticket->QueueObj &></td>
- </tr>
-<& /Elements/Callback, _CallbackName => 'EndOfList', TicketObj => $Ticket, %ARGS &>
-</table>
-<%ARGS>
-$Ticket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowCustomFields b/rt/html/Ticket/Elements/ShowCustomFields
deleted file mode 100644
index 17da78e..0000000
--- a/rt/html/Ticket/Elements/ShowCustomFields
+++ /dev/null
@@ -1,51 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/ShowCustomFields, Object => $Ticket &>
-<%ARGS>
-$Ticket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowDates b/rt/html/Ticket/Elements/ShowDates
deleted file mode 100644
index 9217b79..0000000
--- a/rt/html/Ticket/Elements/ShowDates
+++ /dev/null
@@ -1,86 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
- <tr>
- <td class="label date created"><&|/l&>Created</&>:</td>
- <td class="value date created"><% $Ticket->CreatedObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date starts"><&|/l&>Starts</&>:</td>
- <td class="value date starts"><% $Ticket->StartsObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date started"><&|/l&>Started</&>:</td>
- <td class="value date started"><% $Ticket->StartedObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date told"><a href="<% $RT::WebPath %>/Ticket/Display.html?id=<% $Ticket->id %>&Action=SetTold"><&|/l&>Last Contact</&></a>:</td>
- <td class="value date told"><% $Ticket->ToldObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date due"><&|/l&>Due</&>:</td>
- <td class="value date due"><% $Ticket->DueObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date resolved"><&|/l&>Closed</&>:</td>
- <td class="value date resolved"><% $Ticket->ResolvedObj->AsString %></td>
- </tr>
- <tr>
- <td class="label date updated"><&|/l&>Updated</&>:</td>
-% my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) : loc("Never");
-% if ($UpdatedLink) {
- <td class="value date updated"><A HREF="#lasttrans"><% $UpdatedString | h %></a></td>
-% } else {
- <td class="value date updated"><% $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
deleted file mode 100644
index ef093ee..0000000
--- a/rt/html/Ticket/Elements/ShowDependencies
+++ /dev/null
@@ -1,65 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<&|/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/ShowGroupMembers b/rt/html/Ticket/Elements/ShowGroupMembers
deleted file mode 100644
index 5c0a064..0000000
--- a/rt/html/Ticket/Elements/ShowGroupMembers
+++ /dev/null
@@ -1,63 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# Released under the terms of version 2 of the GNU Public License
-
-% my $UserMembers = $Group->UserMembersObj;
-% while (my $member = $UserMembers->Next()) {
-<& ShowUserEntry, User => $member, Ticket => $Ticket &><br />
-% }
-% my $GroupMembers = $Group->MembersObj;
-% $GroupMembers->LimitToGroups();
-% while (my $member = $GroupMembers->Next()) {
-<&|/l&>Group</&>: <%$member->MemberObj->Object->Name%><br />
-% }
-
-<%ARGS>
-$Group => undef
-$Ticket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowHistory b/rt/html/Ticket/Elements/ShowHistory
deleted file mode 100644
index a40aece..0000000
--- a/rt/html/Ticket/Elements/ShowHistory
+++ /dev/null
@@ -1,166 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%doc>
-# This is (ab)used in Admin/(Users|Groups)/History.html and should probably
-# be generalized at some point.
-</%doc>
-<%perl>
-if ($ShowDisplayModes or $ShowTitle) {
- my $title = $ShowTitle
- ? loc('History')
- : '&nbsp;';
-
- my $titleright;
-
- if ($ShowDisplayModes) {
- $titleright = q[<span style="color: black">] . loc('Display mode') . ':</span> ';
-
- if ($ShowHeaders) {
- $titleright .= qq{<a href="$URIFile?id=} .
- $Ticket->id.qq{">} .
- loc("Brief headers") .
- qq{</a> &mdash; };
- $titleright .= q[<span class="selected">] . loc("Full headers") . "</span>";
- }
- else {
- $titleright .= q[<span class="selected">] . loc("Brief headers") . "</span> &mdash; ";
- $titleright .= qq{<a href="$URIFile?ShowHeaders=1;id=} .
- $Ticket->id.qq{">} .
- loc("Full headers") .
- qq{</a>};
- }
- }
-</%perl>
-<& /Widgets/TitleBoxStart, title => $title, titleright => $titleright &>
-% }
-
-<div id="ticket-history">
-<%perl>
-my @attachments = @{$Attachments->ItemsArrayRef()};
-my @attachment_content = @{$AttachmentContent->ItemsArrayRef()};
-
-while ( my $Transaction = $Transactions->Next ) {
- my $skip = 0;
- $m->comp( '/Elements/Callback',
- _CallbackName => 'SkipTransaction',
- Transaction => $Transaction,
- skip => \$skip,
- %ARGS );
- next if $skip;
- $i++;
-
- my @trans_attachments = grep { $_->TransactionId == $Transaction->Id } @attachments;
-
- my $trans_content = {};
- grep { ($_->TransactionId == $Transaction->Id ) && ($trans_content->{$_->Id} = $_) } @attachment_content;
-
-
- #Args is first because we're clobbering the "Attachments" parameter
- $m->comp( 'ShowTransaction',
- %ARGS,
-
- AttachPath => $AttachPath,
- UpdatePath => $UpdatePath,
- Ticket => $Ticket,
- Transaction => $Transaction,
- ShowHeaders => $ShowHeaders,
- Collapsed => $Collapsed,
- RowNum => $i,
- ShowTitleBarCommands => $ShowTitleBarCommands,
- Attachments => \@trans_attachments,
- AttachmentContent => $trans_content,
- LastTransaction => $Transactions->IsLast
- );
-
-# manually flush the content buffer after each txn, so the user sees
-# some update
-$m->flush_buffer();
-}
-
-</%perl>
-</div>
-% if ($ShowDisplayModes or $ShowTitle) {
-<& /Widgets/TitleBoxEnd &>
-% }
-<%INIT>
-my $Transactions = new RT::Transactions($session{'CurrentUser'});
-if ($Tickets) {
- while (my $t = $Tickets->Next) {
- $Transactions->LimitToTicket($t->id);
- }
-} else {
- $Transactions = $Ticket->Transactions;
-}
-
-
-my $OldestFirst = $RT::OldestTransactionsFirst? 'ASC': 'DESC';
-$Transactions->OrderByCols( { FIELD => 'Created',
- ORDER => $OldestFirst },
- { FIELD => 'id',
- ORDER => $OldestFirst },
- );
-
-my $i;
-$Attachments ||= $m->comp('/Ticket/Elements/FindAttachments', Ticket => $Ticket, Tickets => $Tickets || undef);
-$AttachmentContent ||= $m->comp('/Ticket/Elements/LoadTextAttachments', Ticket => $Ticket);
-
-</%INIT>
-<%ARGS>
-$URIFile => $RT::WebPath."/Ticket/Display.html"
-$Ticket => undef
-$Tickets => undef
-$Attachments => undef
-$AttachmentContent => undef
-$ShowHeaders => undef
-$Collapsed => undef
-$ShowTitle => 1
-$ShowDisplayModes => 1
-$ShowTitleBarCommands => 1
-$AttachPath => $RT::WebPath."/Ticket/Attachment"
-$UpdatePath => $RT::WebPath."/Ticket/Update.html"
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowLink b/rt/html/Ticket/Elements/ShowLink
deleted file mode 100644
index 493fd95..0000000
--- a/rt/html/Ticket/Elements/ShowLink
+++ /dev/null
@@ -1,40 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index f88a600..0000000
--- a/rt/html/Ticket/Elements/ShowLinks
+++ /dev/null
@@ -1,87 +0,0 @@
-%# BEGIN LICENSE BLOCK
-%#
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-%#
-%# (Except where explictly superceded by other copyright notices)
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this 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
deleted file mode 100644
index e443132..0000000
--- a/rt/html/Ticket/Elements/ShowMemberOf
+++ /dev/null
@@ -1,57 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<UL>
-% my $memberof = $Ticket->MemberOf;
-% while (my $member_of = $memberof->Next) {
-<LI><a href="<%$RT::WebPath%>/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
deleted file mode 100644
index 37e4ece..0000000
--- a/rt/html/Ticket/Elements/ShowMembers
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<ul>
-% while (my $link = $members->Next) {
-<li><& /Elements/ShowLink, URI => $link->BaseURI &><br />
-% if ($depth < 8) {
-<& /Ticket/Elements/ShowMembers, Ticket => $link->BaseObj, depth => ($depth+1) &>
-% }
-</li>
-% }
-</ul>
-
-<%INIT>
-
-my $members = $Ticket->Members;
-return unless $members->Count;
-
-</%INIT>
-
-<%ARGS>
-$Ticket => undef
-$depth => 1
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMessageHeaders b/rt/html/Ticket/Elements/ShowMessageHeaders
deleted file mode 100644
index 40b5c84..0000000
--- a/rt/html/Ticket/Elements/ShowMessageHeaders
+++ /dev/null
@@ -1,92 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
-% foreach my $header (@headers) {
-% next unless $display_headers{_all}
-% or ($display_headers{ lc $header->{Tag} }
-% and length $header->{Value});
- <tr>
- <td align="right" class="message-header-key"><%$header->{'Tag'}%>:</td>
- <td class="message-header-value"><%$header->{'Value'} | n%></td>
- </tr>
-% }
-</table>
-<%INIT>
-my $content = $Headers;
-$m->comp('/Elements/Callback', content => \$content, %ARGS);
-
-# apply html escaping on the original content
-# we'll display the value without escaping later (for MakeClicky et al.)
-$content = $m->interp->apply_escapes($content, 'h');
-
-my @lines = split /\n/, $content;
-my $in_header = 0;
-my @headers;
-
-for (@lines) {
- if (/^(\S+):\s+(.*)$/) {
- push @headers, { Tag => $1, Value => $2 };
- }
- elsif (/^\s+/) {
- $headers[-1]->{'Value'} .= $_;
- }
- else {
- s/:$//;
- push @headers, { Tag => $_, Value => '' };
- }
-}
-
-my %display_headers = map { lc($_) => 1 } @$DisplayHeaders;
-
-$m->comp('/Elements/Callback', _CallbackName => 'Headers', content => \$content, headers => \@headers, display_headers => \%display_headers, %ARGS);
-
-</%INIT>
-<%ARGS>
-$Headers => undef
-$DisplayHeaders => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMessageStanza b/rt/html/Ticket/Elements/ShowMessageStanza
deleted file mode 100644
index f166fbb..0000000
--- a/rt/html/Ticket/Elements/ShowMessageStanza
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if (ref($Message)) {
-<div class="message-stanza-depth-<% $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{$}{<br />}mg
- if defined $content;
-
-</%perl>
-<%$content |n%>
-% }
-% } # end foreach
-</div>
-% } else {
-% my $content = $Message;
-% RT::Interface::Web::EscapeUTF8(\$content);
-% $m->comp('/Elements/Callback', content => \$content, %ARGS);
-% $content =~ s{$}{<br />}mg;
-<%$content |n%>
-% }
-<%INIT>
-use URI::URL;
-</%INIT>
-<%ARGS>
-$Message => undef
-$Depth => 0
-$Transaction => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowPeople b/rt/html/Ticket/Elements/ShowPeople
deleted file mode 100644
index d00db52..0000000
--- a/rt/html/Ticket/Elements/ShowPeople
+++ /dev/null
@@ -1,68 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<table>
- <tr>
- <td class="label"><&|/l&>Owner</&>:</td>
- <td class="value"><& ShowUserEntry, User => $Ticket->OwnerObj, Ticket => $Ticket &></td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Requestors</&>:</td>
- <td class="value"><& ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket &></td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>Cc</&>:</td>
- <td class="value"><& ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket &></td>
- </tr>
- <tr>
- <td class="labeltop"><&|/l&>AdminCc</&>:</td>
- <td class="value"><& ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket &></td>
- </tr>
-</table>
-<%ARGS>
-$Ticket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowQueue b/rt/html/Ticket/Elements/ShowQueue
deleted file mode 100644
index da94d39..0000000
--- a/rt/html/Ticket/Elements/ShowQueue
+++ /dev/null
@@ -1,56 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<% $QueueObj->Name %>
-<%ARGS>
-$QueueObj
-</%ARGS>
-<%INIT>
-my $value = $QueueObj->Name;
-$value = '#'. $QueueObj->id
- unless defined $value && length $value;
-</%INIT>
diff --git a/rt/html/Ticket/Elements/ShowReferences b/rt/html/Ticket/Elements/ShowReferences
deleted file mode 100644
index bb323f6..0000000
--- a/rt/html/Ticket/Elements/ShowReferences
+++ /dev/null
@@ -1,72 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<UL>
-% while (my $Link = $Ticket->RefersTo->Next) {
-<LI>
-% if ($Link->TargetURI->IsLocal) {
-% 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>
-% } 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="<%$RT::WebPath%>/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
deleted file mode 100644
index 315664b..0000000
--- a/rt/html/Ticket/Elements/ShowRequestor
+++ /dev/null
@@ -1,89 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%PERL>
-my $rows = 10;
-my $has_right_adminusers = $session{'CurrentUser'}->HasRight(Object => $RT::System, Right => 'AdminUsers');
-my $people = $Ticket->Requestors->UserMembersObj;
-while (my $requestor=$people->Next) {
-next if $requestor->Privileged;
-my $name=$requestor->RealName || $requestor->EmailAddress;
-my $tickets = RT::Tickets->new($session{'CurrentUser'});
-$tickets->FromSQL( "Requestor.id = ". $requestor->id ." AND (Status = 'open' OR Status = 'new')" );
-$tickets->RowsPerPage($rows);
-$tickets->OrderBy(FIELD => 'Priority', ORDER => 'DESC');
-</%PERL>
-
-<&| /Widgets/TitleBox,
- title_href => $has_right_adminusers ? "$RT::WebPath/Admin/Users/Modify.html?id=".$requestor->id : undef,
- title=> loc("More about [_1]", $name),
-&>
-
-%# Additional information about this user. Empty by default.
-<& /Elements/Callback, _CallbackName => 'AboutThisUser', requestor => $requestor, %ARGS &>
-
-<&|/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>
-
-<&|/l&>Groups this user belongs to</&>:<br />
-
-<& /Elements/ShowMemberships, UserObj => $requestor &>
-
-</&>
-
-%}
-<%ARGS>
-$Ticket=>undef
-$DisplayPath => "/Ticket/Display.html"
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowSummary b/rt/html/Ticket/Elements/ShowSummary
deleted file mode 100644
index aeec0fd..0000000
--- a/rt/html/Ticket/Elements/ShowSummary
+++ /dev/null
@@ -1,114 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- <table width="100%" class="ticket-summary">
- <tr>
- <td valign="top" width="50%" class="boxcontainer">
- <&| /Widgets/TitleBox, title => loc('The Basics'),
- title_href =>"$RT::WebPath/Ticket/Modify.html?id=".$Ticket->Id,
- class => 'ticket-info-basics' &>
- <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
- </&>
-
-% if ($Ticket->QueueObj->TicketCustomFields->First) {
- <&| /Widgets/TitleBox, title => loc('Custom Fields'),
- title_href =>"$RT::WebPath/Ticket/Modify.html?id=".$Ticket->Id,
- class => 'ticket-info-cfs' &>
- <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket &>
- </&>
-% }
- <&| /Widgets/TitleBox, title => loc('People'),
- title_href =>"$RT::WebPath/Ticket/ModifyPeople.html?id=".$Ticket->Id,
- class => 'ticket-info-people' &>
- <& /Ticket/Elements/ShowPeople, Ticket => $Ticket &>
- </&>
-
- <& /Ticket/Elements/ShowAttachments, Ticket => $Ticket, Attachments => $Attachments &>
- <br />
- <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &>
-
- <& /Elements/Callback, %ARGS, _CallbackName => 'LeftColumn' &>
- </td>
- <td valign="top" width="50%" class="boxcontainer">
- <&|/Widgets/TitleBox, title => loc("Reminders"),
- title_href =>"$RT::WebPath/Ticket/Reminders.html?id=".$Ticket->Id,
- class => 'ticket-info-reminders' &>
- <table>
- <tr>
- <td>
- <form action="<%$RT::WebPath%>/Ticket/Display.html" method="post">
- <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &>
- <div align="right"><input type="submit" class="button" value="Save" /></div>
- </form>
- </td>
- </tr>
- </table>
- </&>
- <&| /Widgets/TitleBox, title => loc("Dates"),
- title_href =>"$RT::WebPath/Ticket/ModifyDates.html?id=".$Ticket->Id,
- class => 'ticket-info-dates' &>
- <& /Ticket/Elements/ShowDates, Ticket => $Ticket &>
- </&>
-
- <&| /Widgets/TitleBox, title => loc('Links'),
- title_href => "$RT::WebPath/Ticket/ModifyLinks.html?id=".$Ticket->Id,
- class => 'ticket-info-links' &>
- <& /Elements/ShowLinks, Ticket => $Ticket &>
- </&>
- <& /Elements/Callback, %ARGS, _CallbackName => 'RightColumn' &>
-
- </td>
- </tr>
- </table>
-<%ARGS>
-$Ticket => undef
-$Attachments => undef
-</%ARGS>
-
-
-
-
diff --git a/rt/html/Ticket/Elements/ShowTime b/rt/html/Ticket/Elements/ShowTime
deleted file mode 100644
index 2ce0311..0000000
--- a/rt/html/Ticket/Elements/ShowTime
+++ /dev/null
@@ -1,55 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-% if ($minutes < 60) {
-<&|/l, $minutes &>[_1] min</&>
-% } else {
-<&|/l, sprintf("%.1f",$minutes / 60) &>[quant,_1,hour]</&> (<&|/l, $minutes &>[_1] min</&>)
-% }
-<%ARGS>
-$minutes
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowTransaction b/rt/html/Ticket/Elements/ShowTransaction
deleted file mode 100644
index 9fe08ce..0000000
--- a/rt/html/Ticket/Elements/ShowTransaction
+++ /dev/null
@@ -1,197 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="ticket-transaction<% $type_class && " $type_class" %><% $RowNum % 2 ? ' odd' : ' even' %>">
-% $m->comp('/Elements/Callback', _CallbackName => 'ModifyDisplay', titlebar_cmd => \$titlebar_commands, Transaction => $Transaction, %ARGS);
-
-<table width="100%" cellspacing="0" cellpadding="2" border="0">
- <tr>
- <td rowspan="2" valign="top" class="type">
- <a name="txn-<%$Transaction->Id%>" href="<% $DisplayPath %>#txn-<%$Transaction->Id%>">#</a>
- <% $LastTransaction ? '<a name="lasttrans">&nbsp;</a>' : '&nbsp;' |n %>
- </td>
- <td class="date"><% $transdate|n %></td>
-% my $desc = $Transaction->BriefDescription;
-% $m->comp('/Elements/Callback', _CallbackName => 'ModifyDisplay', text => \$desc, Transaction => $Transaction, %ARGS);
- <td class="description">
- <%$Transaction->CreatorObj->Name%> - <%$TicketString%> <%$desc%>
- </td>
- <td class="time-taken"><%$TimeTaken%></td>
- <td class="actions"><%$titlebar_commands|n%></td>
- </tr>
-
- <tr>
- <td colspan="4" class="content">
-% if ($Transaction->CustomFieldValues->Count) {
- <& /Elements/ShowCustomFields, Object => $Transaction &>
-% }
-% $m->comp('ShowTransactionAttachments', %ARGS, Parent => 0) unless ($Collapsed ||!$ShowBody);
- </td>
- </tr>
-
-</table>
-</div>
-
-<%ARGS>
-$Ticket => undef
-$Transaction => undef
-$ShowHeaders => 0
-$Collapsed => undef
-$ShowTitleBarCommands => 1
-$RowNum => 1
-$DisplayPath => $RT::WebPath."/Ticket/Display.html?id=".$Ticket->id
-$AttachPath => $RT::WebPath."/Ticket/Attachment"
-$UpdatePath => $RT::WebPath."/Ticket/Update.html"
-$EmailRecordPath => $RT::WebPath."/Ticket/ShowEmailRecord.html"
-$Attachments => undef
-$AttachmentContent => undef
-$ShowBody => 1
-$LastTransaction => 0
-</%ARGS>
-
-<%INIT>
-
-my ( $TimeTaken, $TicketString, $type_class );
-
-my $transdate = $Transaction->CreatedAsString();
-$transdate =~ s/\s/&nbsp;/g;
-
-if ( $Transaction->Type =~ /^(Create|Correspond|Comment$)/ ) {
- if ( $Transaction->IsInbound ) {
- $type_class = 'message';
- }
- else {
- $type_class = 'message';
- }
-}
-elsif ( ( $Transaction->Field =~ /^Owner$/ )
- or ( $Transaction->Type =~ /^(AddWatcher|DelWatcher)$/ ) ) {
- $type_class = 'people';
-
-}
-elsif ( $Transaction->Type =~ /^(AddLink|DeleteLink)$/ ) {
- $type_class = 'links';
-}
-elsif ( $Transaction->Type =~ /^(Status|Set|Told)$/ ) {
- if ( $Transaction->Field =~ /^(Told|Starts|Started|Due)$/ ) {
- $type_class = 'dates';
- }
- else {
- $type_class = 'basics';
- }
-}
-else {
- $type_class = 'other';
-}
-
-if ( $Ticket->Id != $Transaction->Ticket ) {
- $TicketString = "Ticket " . $Transaction->Ticket . ": ";
-}
-$TicketString ||= '';
-
-if ( $Transaction->TimeTaken != 0 ) {
- $TimeTaken = $Transaction->TimeTaken . " min";
-} else {
- $TimeTaken = '';
-}
-
-unless ($Attachments) {
- my $attachments = $Transaction->Attachments;
- $attachments->Columns( qw( Id Filename ContentType Headers Subject Parent ContentEncoding ContentType TransactionId) );
- $Attachments = $attachments->ItemsArrayRef();
-}
-my $titlebar_commands = '&nbsp;';
-
-my @DisplayHeaders=qw ( _all);
-
-if ( $Transaction->Type =~ /EmailRecord$/ ) {
- @DisplayHeaders = qw(To Cc Bcc);
-
- $titlebar_commands .=
- "[<a target=\"_blank\" href=\"$EmailRecordPath?id="
- . $Transaction->Ticket
- . "&Transaction="
- . $Transaction->Id
- . "&Attachment="
- . ( $Attachments->[0] && $Attachments->[0]->id )
- . '">' . loc('Show') . "</a>]&nbsp;";
- $ShowBody = 0;
-}
-
-
-# If the transaction has anything attached to it at all
-else {
-
- unless ( $ShowHeaders ) {
- @DisplayHeaders = qw(To From RT-Send-Cc Cc Bcc Date Subject);
- }
-
- if ( $Attachments->[0] && $ShowTitleBarCommands ) {
- if ( $Transaction->TicketObj->CurrentUserHasRight('ReplyToTicket')
- or $Transaction->TicketObj->CurrentUserHasRight('ModifyTicket')) {
- $titlebar_commands .=
- "[<a href=\"".$UpdatePath."?id="
- . $Transaction->Ticket
- . "&QuoteTransaction="
- . $Transaction->Id
- . "&Action=Respond\">"
- . loc('Reply')
- . "</a>]&nbsp;";
- }
- if ( $Transaction->TicketObj->CurrentUserHasRight('CommentOnTicket')
- or $Transaction->TicketObj->CurrentUserHasRight('ModifyTicket')) {
- $titlebar_commands .=
- "[<a href=\"".$UpdatePath."?id="
- . $Transaction->Ticket
- . "&QuoteTransaction="
- . $Transaction->Id
- . "&Action=Comment\">"
- . loc('Comment') . "</a>]";
- }
- }
-}
-</%INIT>
diff --git a/rt/html/Ticket/Elements/ShowTransactionAttachments b/rt/html/Ticket/Elements/ShowTransactionAttachments
deleted file mode 100644
index 662b744..0000000
--- a/rt/html/Ticket/Elements/ShowTransactionAttachments
+++ /dev/null
@@ -1,209 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%perl>
-# Find all the attachments which have parent $Parent
-# For each of these attachments
-foreach my $message ( grep { $_->Parent == $Parent } @$Attachments ) {
-
- # {{{ show the headers
- my $headers = $message->Headers;
- chomp $headers;
-
- # localize the common headers (like 'Subject:'), too.
- $headers =~ s/^([^:]+)(?=:)/loc($1)/em;
- $m->comp(
- 'ShowMessageHeaders',
- Headers => $headers,
- Transaction => $Transaction,
- DisplayHeaders => \@DisplayHeaders
- );
-
- # }}}
- # {{{ if there's any size at all, show the download link
- my $size = $message->ContentLength;
- if ($size) {
-
-</%perl>
-<div class="downloadattachment">
-<%perl>
-
- # show a download link
- if ( $size > 1024 ) {
- $size = loc( "[_1]k", int( $size / 102.4 ) / 10 );
- }
- else {
- $size = loc( "[_1]b", $size );
- }
-
-</%PERL>
-<a href="<%$AttachPath%>/<%$Transaction->Id%>/<%$message->Id%>/<%$message->Filename | u%>"><&|/l&>Download</&> <%$message->Filename || loc('(untitled)') %></a>
-<span class="downloadcontenttype">
-[<%$message->ContentType%> <% $size %>]
-</span>
-</div>
-% }
-% # }}}
-<div class="messagebody">
-<%perl>
-# {{{ if it has a content-disposition: attachment, don't show inline
-unless ( ($message->GetHeader('Content-Disposition')||"") =~ /attachment/i ) {
-
- my $content;
-
- # If it's text
- if ( $message->ContentType =~ m{^(text|message)}i
- && !($RT::SuppressInlineTextFiles && $message->Filename)
- && $message->ContentLength <= $RT::MaxInlineBody )
- {
-
- if (
-
- # it's a toplevel object
- !$ParentObj
-
- # or its parent isn't a multipart alternative
- || ( $ParentObj->ContentType !~ m{^multipart/alternative$}i )
-
- # or it's of our prefered alterative type
- || (
- (
- $RT::PreferRichText
- && ( $message->ContentType =~ m{^text/(?:html|enriched)$} )
- )
- || ( !$RT::PreferRichText
- && ( $message->ContentType !~ m{^text/(?:html|enriched)$} )
- )
- )
- )
- {
-
- if ( $AttachmentContent->{ $message->id } ) {
- $content = $AttachmentContent->{ $message->id }->Content;
- }
- else {
- $content = $message->Content;
- }
-
- # if it's a text/html clean the body and show it
- if ( $message->ContentType =~ m{^text/(?:html|enriched)$}i ) {
- $content =
- $m->comp( '/Elements/ScrubHTML', Content => $content );
- $m->out($content);
- }
-
- # if it's a text/plain show the body
- elsif ( $message->ContentType =~ m{^(text|message|text)}i ) {
-
- eval { require Text::Quoted; $content = Text::Quoted::extract($content); };
- if ($@) { 1; }
-
- $m->comp(
- 'ShowMessageStanza',
- Depth => 0,
- Message => $content,
- Transaction => $Transaction
- );
- }
- }
-
- }
-
- # if it's an image, show it as an image
- elsif ( $RT::ShowTransactionImages and $message->ContentType =~ /^image\//i ) {
- $m->out('<img src="'
- . $AttachPath . '/'
- . $Transaction->Id . '/'
- . $message->Id
- . '/" />' );
- }
- elsif ( $message->ContentLength > 0 ) {
- $m->out(
- loc( 'Message body not shown because it is too large or is not plain text.' )
- );
- }
-}
-
-# }}}
-
-$m->comp(
- 'ShowTransactionAttachments', %ARGS,
- Parent => $message->id,
- ParentObj => $message
-);
-
-</%PERL>
-</div>
-% }
-<%ARGS>
-$Ticket => undef
-$Transaction => undef
-$ShowHeaders => 0
-$Collapsed => undef
-$ShowTitleBarCommands => 1
-$RowNum => 1
-$AttachPath => $RT::WebPath."/Ticket/Attachment"
-$UpdatePath => $RT::WebPath."/Ticket/Update.html"
-$EmailRecordPath => $RT::WebPath."/Ticket/ShowEmailRecord.html"
-$Attachments => undef
-$AttachmentContent => undef
-$ShowBody => 1
-$Parent => 0
-$ParentObj => 0
-</%ARGS>
-<%INIT>
-my @DisplayHeaders=qw( _all);
-
-if ( $Transaction->Type =~ /EmailRecord$/ ) {
- @DisplayHeaders = qw(To Cc Bcc);
-}
-
-# If the transaction has anything attached to it at all
-elsif (!$ShowHeaders) {
- @DisplayHeaders = qw(To From RT-Send-Cc Cc Bcc Date Subject);
-}
-</%INIT>
diff --git a/rt/html/Ticket/Elements/ShowUserEntry b/rt/html/Ticket/Elements/ShowUserEntry
deleted file mode 100644
index eb6e836..0000000
--- a/rt/html/Ticket/Elements/ShowUserEntry
+++ /dev/null
@@ -1,61 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# Released under the terms of version 2 of the GNU Public License
-
-<%$User->Name%>
-% if ($User->EmailAddress && $User->EmailAddress ne $User->Name) {
-&lt;<%$User->EmailAddress%>&gt;
-% }
-% if ($Ticket and grep { $_->Content eq $User->EmailAddress } $Ticket->SquelchMailTo) {
-<b><&|/l&>(Will not be sent email)</&></b>
-% }
-
-<%ARGS>
-$User => undef
-$Ticket => undef
-</%ARGS>
diff --git a/rt/html/Ticket/Elements/Tabs b/rt/html/Ticket/Elements/Tabs
deleted file mode 100644
index 98ed143..0000000
--- a/rt/html/Ticket/Elements/Tabs
+++ /dev/null
@@ -1,248 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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/Build.html",
-my $searchtabs = {};
-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 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('Reminders'),
- path => "Ticket/Reminders.html?id=" . $id,
- separator => 1, },
- _X => { title => loc('Jumbo'),
- path => "Ticket/ModifyAll.html?id=" . $id,
- },
-
-};
-
-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;
-
-my %can = (
- ModifyTicket => $Ticket->CurrentUserHasRight('ModifyTicket'),
-);
-
-if ( $can{'ModifyTicket'} or $Ticket->CurrentUserHasRight('ReplyToTicket') ) {
- $actions->{'F'} = {
- title => loc('Reply'),
- path => "Ticket/Update.html?Action=Respond&id=" . $id,
- };
-}
-
-if ( $can{'ModifyTicket'} ) {
- if ( $Ticket->Status ne 'resolved' ) {
- $actions->{'G'} = {
- path => "Ticket/Update.html?Action=Comment&DefaultStatus=resolved&id=" . $id,
- title => loc('Resolve') };
- }
- if ( $Ticket->Status ne 'open' ) {
- $actions->{'A'} = { path => "Ticket/Display.html?Status=open&id=" . $id,
- title => loc('Open it') };
- }
-}
-
-if ( $Ticket->CurrentUserHasRight('OwnTicket') ) {
- if ( $Ticket->OwnerObj->Id == $RT::Nobody->id
- and ( $can{'ModifyTicket'} or $Ticket->CurrentUserHasRight('TakeTicket') ) )
- {
- $actions->{'B'} = {
- path => "Ticket/Display.html?Action=Take&id=" . $id,
- title => loc('Take'),
- };
- }
- elsif ( $Ticket->OwnerObj->id != $session{CurrentUser}->id
- and ( $can{'ModifyTicket'} or $Ticket->CurrentUserHasRight('StealTicket') ) )
- {
- $actions->{'C'} = {
- path => "Ticket/Display.html?Action=Steal&id=" . $id,
- title => loc('Steal'),
- };
- }
-}
-
-if ( $can{'ModifyTicket'} or $Ticket->CurrentUserHasRight('CommentOnTicket') ) {
- $actions->{'E'} = {
- title => loc('Comment'),
- path => "Ticket/Update.html?Action=Comment&id=" . $id,
- };
-}
-}
-
-if ( (defined $actions->{A} || defined $actions->{B} || defined $actions->{C})
- && (defined $actions->{E} || defined $actions->{F} || defined $actions->{G}) ) {
-
- if (defined $actions->{C}) { $actions->{C}->{separator} = 1 }
- elsif (defined $actions->{B}) { $actions->{B}->{separator} = 1 }
- elsif (defined $actions->{A}) { $actions->{A}->{separator} = 1 }
-}
-
-my $args;
-$args= "?" . $m->comp(
- '/Elements/QueryString',
- Query => $ARGS{'Query'} || $session{'CurrentSearchHash'}->{'Query'},
- Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
- OrderBy => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'},
- Order => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'},
- Page => $ARGS{'Page'} || $session{'CurrentSearchHash'}->{'Page'},
- Rows => $ARGS{'Rows'},
- ) if ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'});
-$args ||= '';
-
-$tabs->{"f"} = { path => "Search/Build.html?NewQuery=1",
- title => loc('New Search')};
-$tabs->{"g"} = { path => "Search/Build.html$args",
- title => loc('Edit Search')};
-$tabs->{"h"} = { path => "Search/Edit.html$args",
- title => loc('Advanced'),
- separator => 1 };
-if ($args) {
- $tabs->{"i"} = { path => "Search/Results.html$args",
- title => loc('Show Results'),
- };
- if ($current_tab =~ m{Search/Results.html}) {
- $current_tab = "Search/Results.html$args";
- }
- $tabs->{"j"} = { path => "Search/Bulk.html$args",
- title => loc('Bulk Update'),
- };
- if ($current_tab =~ m{Search/Bulk.html}) {
- $current_tab = "Search/Bulk.html$args";
- }
- foreach my $searchtab (keys %{$searchtabs}) {
- ($searchtab =~ /^_/) ? $tabs->{"s".$searchtab} = $searchtabs->{$searchtab} : $tabs->{"z_".$searchtab} = $searchtabs->{$searchtab};
- }
-}
-
-
-</%INIT>
-
-
-<%ARGS>
-$Ticket => undef
-$subtabs => undef
-$current_tab => ''
-$current_subtab => ''
-$Title => undef
-</%ARGS>
diff --git a/rt/html/Ticket/History.html b/rt/html/Ticket/History.html
deleted file mode 100644
index 0f0a930..0000000
--- a/rt/html/Ticket/History.html
+++ /dev/null
@@ -1,89 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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',
- Attachments => $attachments,
- AttachmentContent => $attachment_content
- &>
-
-<& /Elements/Callback, _CallbackName => 'AfterShowHistory', Ticket => $Ticket,
-current_tab => 'Ticket/History.html?id=' . $Ticket->id, %ARGS &>
-
-<%ARGS>
-$id => undef
-</%ARGS>
-
-<%INIT>
-
-
-
-my $Ticket = LoadTicket ($id);
-
-unless ($Ticket->CurrentUserHasRight('ShowTicket')) {
- Abort("No permission to view ticket");
-}
-
-my $attachments = $m->comp('Elements/FindAttachments', Ticket => $Ticket);
-my $attachment_content = $m->comp('Elements/LoadTextAttachments', Ticket =>
-$Ticket);
-
-
-</%INIT>
-
-
-
-
diff --git a/rt/html/Ticket/Modify.html b/rt/html/Ticket/Modify.html
deleted file mode 100644
index 877bc0c..0000000
--- a/rt/html/Ticket/Modify.html
+++ /dev/null
@@ -1,91 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" enctype="multipart/form-data">
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<input type="hidden" class="hidden" name="id" value="<%$TicketObj->Id%>" />
-<&| /Widgets/TitleBox, title => loc('Modify ticket #[_1]',$TicketObj->Id) &>
-<& Elements/EditBasics, TicketObj => $TicketObj &>
-<& Elements/EditCustomFields, TicketObj => $TicketObj &>
-</&>
-
-<& /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->TicketCustomFields();
-
-# Now let callbacks have a chance at editing %ARGS
-$m->comp('/Elements/Callback', TicketObj => $TicketObj, CustomFields => $CustomFields, ARGSRef => \%ARGS);
-
-my @results = ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS);
-my @cf_results = ProcessObjectCustomFieldUpdates(Object => $TicketObj, ARGSRef => \%ARGS);
-push (@results, @cf_results);
-
-# undef so that TransactionBatch scrips run and update the ticket
-$TicketObj = undef;
-$TicketObj = LoadTicket($id);
-
-# 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
deleted file mode 100644
index 7ad5b87..0000000
--- a/rt/html/Ticket/ModifyAll.html
+++ /dev/null
@@ -1,225 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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">
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<input type="hidden" class="hidden" name="id" value="<%$Ticket->Id%>" />
-
-<&| /Widgets/TitleBox, title => loc('Modify ticket # [_1]', $Ticket->Id) &>
-<& Elements/EditBasics, TicketObj => $Ticket &>
-<& Elements/EditCustomFields, TicketObj => $Ticket &>
-</&>
-
-<br />
-
-<&| /Widgets/TitleBox, title => loc('Dates') &>
-<& Elements/EditDates, TicketObj => $Ticket &>
-</&>
-
-<br />
-
-
-<&| /Widgets/TitleBox, title => loc('People') &>
-<& Elements/EditPeople, Ticket => $Ticket, UserField => $UserField, UserString => $UserString, UserOp => $UserOp, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField &>
-</&>
-
-<br />
-
-<&| /Widgets/TitleBox, title => loc('Links') &>
-<& /Elements/EditLinks, Object => $Ticket, Merge => 1 &>
-</&>
-
-<br />
-
-<&| /Widgets/TitleBox, 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&>Reply to requestors</&></option>
-% }
- </select>
- </td>
- </tr>
- <tr>
- <td class="label"><&|/l&>Subject</&>:</td>
- <td class="entry"><input name="UpdateSubject" size="60" value="<%$Ticket->Subject%>" /></td>
- </tr>
-% if (my $TxnCFs = $Ticket->TransactionCustomFields) {
-% while (my $CF = $TxnCFs->Next()) {
-<tr>
-<td class="label"><% $CF->Name %>:</td>
-<td class="entry"><& /Elements/EditCustomField,
- CustomField => $CF,
- NamePrefix => "Object-RT::Transaction--CustomField-"
- &><em><% $CF->FriendlyType %></em></td>
-</td></tr>
-% } # end if while
-% } # end of if
- <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/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') );
-
-
-$m->comp('/Elements/Callback', TicketObj => $Ticket, ARGSRef => \%ARGS);
-my (@wresults, @results, @dresults, @lresults, @cf_results);
-
-unless ($OnlySearchForPeople or $OnlySearchForGroup ) {
- # There might be two owners.
- if ( ref ($ARGS{'Owner'} )) {
- my @owners =@{$ARGS{'Owner'}};
- delete $ARGS{'Owner'};
- foreach my $owner(@owners){
- $ARGS{'Owner'} = $owner unless ($Ticket->OwnerObj->id == $owner);
- }
-
- }
-
- @wresults = ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
- @cf_results = ProcessObjectCustomFieldUpdates( Object => $Ticket, ARGSRef => \%ARGS);
- @dresults = ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS);
- @lresults = ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
-
- if ($ARGS{'UpdateAttachment'}) {
- my $subject = "$ARGS{'UpdateAttachment'}";
- # 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 => 'UpdateAttachment'
- );
- delete $ARGS{'UpdateAttachment'};
- $ARGS{'UpdateAttachments'}->{ $subject } = $attachment;
- }
-
- $ARGS{'UpdateContent'} =~ s/\r+\n/\n/g if $ARGS{'UpdateContent'};
-
- if ($ARGS{'UpdateAttachments'} || ( $ARGS{'UpdateContent'} && $ARGS{'UpdateContent'} ne "-- \n" .
- $session{'CurrentUser'}->UserObj->Signature)) {
- ProcessUpdateMessage(TicketObj => $Ticket, ARGSRef=>\%ARGS, Actions=>\@results);
- }
- @results = ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS);
-}
-push @results, @wresults;
-push @results, @dresults;
-push @results, @lresults;
-push @results, @cf_results;
-
-# undef so that TransactionBatch scrips run and update the ticket
-$Ticket = undef;
-$Ticket = LoadTicket($id);
-
-# 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
-$OnlySearchForGroup => undef
-$UserField => undef
-$UserOp => undef
-$UserString => undef
-$GroupString => undef
-$GroupOp => undef
-$GroupField => undef
-$id => undef
-</%ARGS>
-
diff --git a/rt/html/Ticket/ModifyDates.html b/rt/html/Ticket/ModifyDates.html
deleted file mode 100644
index 189594f..0000000
--- a/rt/html/Ticket/ModifyDates.html
+++ /dev/null
@@ -1,77 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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">
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<input type="hidden" class="hidden" name="id" value="<%$TicketObj->Id%>" />
-<&| /Widgets/TitleBox,title => loc('Modify dates for ticket # [_1]', $TicketObj->Id) &>
-<& Elements/EditDates, TicketObj => $TicketObj &>
-</&>
-<& /Elements/Submit, Label => loc('Save Changes') &>
-</form>
-
-
-<%INIT>
-
-my $TicketObj = LoadTicket($id);
-$m->comp('/Elements/Callback', TicketObj => $TicketObj, ARGSRef => \%ARGS);
-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
deleted file mode 100644
index 1310f68..0000000
--- a/rt/html/Ticket/ModifyLinks.html
+++ /dev/null
@@ -1,82 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="<%$Ticket->id%>" />
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<&| /Widgets/TitleBox, title => loc('Edit Links') &>
-
-<& /Elements/EditLinks, Object => $Ticket, Merge => 1 &>
-</&>
-<& /Elements/Submit, Label => loc('Save Changes') &>
-</form>
-
-
-
-
-<%INIT>
-
-my $Ticket = LoadTicket($id);
-
-my @results;
-$m->comp('/Elements/Callback', TicketObj => $Ticket, ARGSRef => \%ARGS, Results => \@results );
-push @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
deleted file mode 100644
index 5b5db16..0000000
--- a/rt/html/Ticket/ModifyPeople.html
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="<%$Ticket->Id%>" />
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<&| /Widgets/TitleBox, 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/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);
-$m->comp('/Elements/Callback', TicketObj => $Ticket, ARGSRef => \%ARGS);
-
-# 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/Reminders.html b/rt/html/Ticket/Reminders.html
deleted file mode 100755
index e2245a6..0000000
--- a/rt/html/Ticket/Reminders.html
+++ /dev/null
@@ -1,71 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Reminder ticket #[_1]", $Ticket->Id) &>
-<& /Ticket/Elements/Tabs,
- Ticket => $Ticket,
- current_tab => "Ticket/Reminders.html?id=".$Ticket->Id,
- Title => loc("Reminders for ticket #[_1]", $Ticket->Id) &>
-<form action="<%$RT::WebPath%>/Ticket/Reminders.html" method="post">
-<&|/Widgets/TitleBox, title => loc("Reminders"),
- title_class=> 'inverse',
- color => "#666699" &>
-
-<& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 1, Edit => 1 &>
-</&>
-<& /Elements/Submit, Label => 'Save'&>
-</form>
-
-
-<%INIT>
-
-my $Ticket = LoadTicket($id);
-
-</%INIT>
-<%ARGS>
-$id => undef
-</%ARGS>
diff --git a/rt/html/Ticket/ShowEmailRecord.html b/rt/html/Ticket/ShowEmailRecord.html
deleted file mode 100644
index b63da84..0000000
--- a/rt/html/Ticket/ShowEmailRecord.html
+++ /dev/null
@@ -1,73 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Attachment => undef
-$Transaction => undef
-</%ARGS>
-<%init>
- my $AttachmentObj = new RT::Attachment($session{'CurrentUser'});
- $AttachmentObj->Load($Attachment) || Abort(loc("Attachment '[_1]' could not be loaded", $Attachment));
-
-
- unless ($AttachmentObj->id) {
- Abort(loc("Attachment '[_1]' could not be loaded", $Attachment));
- }
- unless ($AttachmentObj->TransactionId() == $Transaction ) {
- Abort(loc("Attachment '[_1]' could not be loaded", $Attachment));
- }
-
-</%init>
-<& /Elements/Header, ShowBar => 0 &>
-<pre style="padding: 2em;">
-<%$AttachmentObj->Headers%>
-
-<%$AttachmentObj->Content%>
-</pre>
-</body>
-</html>
-%$m->abort;
diff --git a/rt/html/Ticket/Update.html b/rt/html/Ticket/Update.html
deleted file mode 100644
index 171a0cb..0000000
--- a/rt/html/Ticket/Update.html
+++ /dev/null
@@ -1,228 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Ticket/Elements/Tabs,
- Ticket => $TicketObj,
- Title=> $title &>
-
-<form action="Update.html" name="TicketUpdate"
- method="post" enctype="multipart/form-data">
-<& /Elements/Callback, _CallbackName => 'FormStart',ARGSRef =>\%ARGS &>
-<input type="hidden" class="hidden" name="QuoteTransaction" value="<% $ARGS{QuoteTransaction} %>" />
-<input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus %>" />
-<input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action} %>" />
-<table width="100%" border="0">
-
-<tr><td align="right"><&|/l&>Status</&>:</td>
-<td>
-<& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)", loc($TicketObj->Status)), Default => $ARGS{'Status'} || ($TicketObj->Status eq $DefaultStatus ? undef : $DefaultStatus)&>
-<&|/l&>Owner</&>:
-<& /Elements/SelectOwner, Name=>"Owner", DefaultLabel => loc("[_1] (Unchanged)", $TicketObj->OwnerObj->Name()), QueueObj => $TicketObj->QueueObj, TicketObj => $TicketObj, Default => $ARGS{'Owner'} &>
-<&|/l&>Worked</&>: <input size="4" name="UpdateTimeWorked" value="<% $ARGS{UpdateTimeWorked} %>" />
-<& /Elements/SelectTimeUnits, Name => 'UpdateTimeWorked'&>
-</td></tr>
-% my $skip;
-<& /Elements/Callback, _CallbackName => 'BeforeUpdateType', skip => \$skip, %ARGS &>
-% if (!$skip) {
-<input type="hidden" class="hidden" name="id" value="<%$TicketObj->Id%>" /><br />
-% }
-<tr><td align="right"><&|/l&>Update Type</&>:</td>
-<td><select name="UpdateType">
-% if ($CanComment) {
- <option value="private" <%$ARGS{'UpdateType'} eq "private" ? "SELECTED" : !$ARGS{'UpdateType'}&&$CommentDefault%>><&|/l&>Comments (Not sent to requestors)</&></option>
-% }
-% if ($CanRespond) {
- <option value="response" <%$ARGS{'UpdateType'} eq "response" ? "SELECTED" : !$ARGS{'UpdateType'}&&$ResponseDefault%>><&|/l&>Reply to requestors</&></option>
-% }
-</select>
-</td></tr>
-<tr><td align="right"><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject" size="60" value="<% $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 <strong>not</strong> 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 <strong>not</strong> 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" class="checkbox" name="DeleteAttach-<%$attach_name%>" value="1" /><%$attach_name%><br />
-% } # end of foreach
-</td>
-</tr>
-<tr>
-% } # end of if
-
-% if (my $TxnCFs = $TicketObj->TransactionCustomFields) {
-% while (my $CF = $TxnCFs->Next()) {
-<tr>
-<td align="right"><% $CF->Name %>:</td>
-<td><& /Elements/EditCustomField, CustomField => $CF, NamePrefix =>
- "Object-RT::Transaction--CustomField-" &><em><% $CF->FriendlyType %></em></td>
-</tr>
-% } # end if while
-% } # end of if
-
-<tr><td align="right"><&|/l&>Attach</&>:</td><td><input name="Attach" type="file" /><input type="submit" class="button" name="AddMoreAttach" value="<&|/l&>Add More Files</&>" /><input type="hidden" class="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}) {
-% # preserve QuoteTransaction so we can use it to set up sane references/in/reply to
-% my $temp = $ARGS{'QuoteTransaction'};
-% delete $ARGS{'QuoteTransaction'};
-<& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS&>
-% $ARGS{'QuoteTransaction'} = $temp;
-% } else {
-<& /Elements/MessageBox, Name=>"UpdateContent", %ARGS &>
-% }
-</td></tr>
-</table>
-
-
-
-
-<& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket' &>
-% if ($TicketObj->CurrentUserHasRight('ShowOutgoingEmail')) {
-<& /Ticket/Elements/PreviewScrips, TicketObj => $TicketObj, %ARGS &>
-% }
-</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 ne 'Respond') {
- $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(
- Filename => $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/Tools/Elements/Tabs b/rt/html/Tools/Elements/Tabs
deleted file mode 100644
index b341c3c..0000000
--- a/rt/html/Tools/Elements/Tabs
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Tabs,
- tabs => $tabs,
- current_toptab => 'Tools/index.html',
- current_tab => $current_tab,
- Title => $Title &>
-
-<%INIT>
-my $tabs = {
- a => {
- title => loc('Offline'),
- path => 'Tools/Offline.html',
- },
- b => {
- title => loc('Reports'),
- path => 'Tools/Reports/index.html',
- },
- c => {
- title => loc('My Day'),
- path => 'Tools/MyDay.html',
- },
-};
-
-$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/Tools/MyDay.html b/rt/html/Tools/MyDay.html
deleted file mode 100644
index 20758f8..0000000
--- a/rt/html/Tools/MyDay.html
+++ /dev/null
@@ -1,117 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => $title &>
-<& /Tools/Elements/Tabs, current_tab => "Tools/MyDay.html", Title => $title &>
-
-<& /Elements/ListActions, actions => \@results &>
-
-<&|/l, $session{'CurrentUser'}->Name&>(displaying new and open tickets for [_1])</&>
-<form method="post" action="MyDay.html">
-<table width="100%" cellpadding="0" cellspacing="0">
-% while ( my $Ticket = $Tickets->Next()) {
-% my $class;
-% $i++;
-% if ($i % 2 ) {
-% $class = 'class="oddline"';
-% }
-<tr <%$class|n%>><td colspan="2"><h2><a
-href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$Ticket->Id%>"><%$Ticket->Id%>:
-<%$Ticket->Subject%></a></h2></td></tr>
-<tr <%$class|n%>><td><span class="label"><&|/l&>Worked</&>:</span><input size="3" name="UpdateTimeWorked-<%$Ticket->Id%>" /> <&|/l&>minutes</&>
-</td>
-<td rowspan="2"><span class="label"><&|/l&>Comments</&>:<br /></span><textarea name="UpdateContent-<%$Ticket->Id%>" rows="5"
-cols="60"></textarea></td></tr>
-<tr <%$class|n%>>
-<td><span class="label"><&|/l&>Status</&>:</span> <& /Elements/SelectStatus, Name=> 'UpdateStatus-'.$Ticket->Id,
- DefaultLabel => loc("[_1] (Unchanged)",loc($Ticket->Status())) &></td>
- </tr>
-
-% }
-</table>
-<& /Elements/Submit, Label => loc('Record all updates') , Reset => 1 &>
-</form>
-</html>
-<%INIT>
-my $title = loc("What I did today");
-
-my $i = 0;
-my @results;
-foreach my $arg ( keys %ARGS ) {
- next unless ( $arg =~ /^UpdateStatus-(\d*)$/ );
- my $id = $1;
- my $ticket = LoadTicket($id);
- next unless ( $ticket->id );
- if ( my $content = $ARGS{'UpdateContent-'.$id} ) {
- my ( $val, $msg ) = $ticket->Comment(
- Content => $content,
- TimeTaken => $ARGS{ 'UpdateTimeWorked-' . $id }
- );
- push @results, loc( "Ticket [_1]: [_2]", $id, $msg );
- } elsif ( my $worked = $ARGS{ 'UpdateTimeWorked-' . $id } ) {
- my ( $val, $msg ) = $ticket->SetTimeWorked( $worked + $ticket->TimeWorked );
- push @results, loc( "Ticket [_1]: [_2]", $id, $msg );
- }
-
- if ( my $status = $ARGS{ 'UpdateStatus-' . $id } ) {
- if ( $status ne $ticket->Status ) {
- my ( $val, $msg ) = $ticket->SetStatus($status);
- push @results, loc( "Ticket [_1]: [_2]", $id, $msg );
-
- }
- }
-
-}
-
-my $Tickets = RT::Tickets->new($session{'CurrentUser'});
-$Tickets->LimitOwner(VALUE => $session{'CurrentUser'}->Id);
-$Tickets->LimitStatus( VALUE => 'open' );
-$Tickets->LimitStatus ( VALUE => 'new');
-$Tickets->OrderBy ( FIELD => 'Priority', ORDER => 'DESC');
-
-
-</%INIT>
diff --git a/rt/html/Tools/Offline.html b/rt/html/Tools/Offline.html
deleted file mode 100644
index 4558abd..0000000
--- a/rt/html/Tools/Offline.html
+++ /dev/null
@@ -1,166 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Offline upload") &>
-<& Elements/Tabs,
- current_tab => "Tools/Offline.html",
- Title => loc("Offline edits") &>
-
-<& /Elements/ListActions, actions => \@results &>
-
-<& /Elements/Callback, Requestor => \$requestoraddress,
- Queue => \$qname, %ARGS &>
-
-<form action="Offline.html" name="TicketUpdate"
- method="post" enctype="multipart/form-data">
-<table>
-<tr>
-<td class="label">
-<&|/l&>Default Queue</&>:
-</td>
-<td>
-<& /Elements/SelectQueue, Name => "qname", NamedValues => 1 &>
-<em><&|/l&>If no queue is specified, create tickets in this queue.</&></em>
-</td>
-</tr>
-<tr>
-<td class="label">
-<&|/l&>Default Requestor</&>:
-</td>
-<td>
-<input name="requestoraddress" value="<%$requestoraddress%>" />
-<em><&|/l&>If no Requestor is specified, create tickets with this requestor.</&></em>
-</td>
-</tr>
-<tr><td class="labeltop">
-<&|/l&>Template</&>:
-</td>
-<td colspan="2">
-<textarea name="string" cols="80" rows="30"><%$string%></textarea>
-</td>
-</tr>
-<tr><td class="label">
-<&|/l&>Get template from file</&>:
-</td>
-<td>
-<input name="Template" type="file" value="foo" />
-<input type="submit" class="button" name="Parse" value="<&|/l&>Go!</&>" />
-</td>
-</tr>
-</table>
-<& /Elements/Submit, Name => 'UpdateTickets', Label => loc('Upload'), Caption => loc("Upload your changes"), color => "#993333" &>
-
-</form>
-<%args>
-$requestoraddress => ''
-$qname => undef
-$string => undef
-</%args>
-<%INIT>
-
-my @results;
-use RT::Action::CreateTickets;
-my $action = RT::Action::CreateTickets->new(CurrentUser => $session{'CurrentUser'});
-;
-if ($ARGS{'Parse'} && $ARGS{'Template'}) {
- $string = "";
- my $cgi_object = $m->cgi_object;
- my $fh = $cgi_object->upload('Template');
- my $filename = "$fh";
-
- my ($buffer, $template);
- while ( my $bytesread = read( $fh, $buffer, 4096 ) ) {
- $template .= $buffer;
- }
- $template =~ s/\r\n/\n/gs;
- $action->Parse(Content => $template, Queue => $qname, Requestor => $requestoraddress);
- foreach ( @{ $action->{'create_tickets'} } ) {
- my $id = $_;
- $id =~ s/^create\-//;
- $string .= "===Create-Ticket: $id\n";
- $string .= $action->{'templates'}->{$_} . "\n";
- }
- foreach ( @{ $action->{'update_tickets'} } ) {
- my $id = $_;
- $id =~ s/^update\-//;
- $string .= "===Update-Ticket: $id\n";
- $string .= $action->{'templates'}->{$_} . "\n";
- }
-
-
-} elsif ($ARGS{'UpdateTickets'}) {
- $action->Parse(Content => $ARGS{string}, Queue => $qname, Requestor=> $requestoraddress);
- push @results, $action->CreateByTemplate();
- push @results, $action->UpdateByTemplate();
-} else {
- if ($ARGS{'Query'}) {
- my $Tickets = RT::Tickets->new($session{'CurrentUser'});
- $Tickets->FromSQL($ARGS{'Query'});
-
- while (my $t = $Tickets->Next) {
- $string .= "===Update-Ticket: " . $t->Id . "\n";
- $string .= $action->GetUpdateTemplate($t);
- $string .= "" . "\n";
- }
-
- $string .= "" . "\n";
- $string .= "===# DO NOT EDIT BELOW THIS LINE#===\n";
- $string .= "" . "\n";
-
- while (my $t = $Tickets->Next) {
- $string .= "===# DO NOT EDIT #===\n";
- $string .= "===Base-Ticket: " . $t->Id . "\n";
- $string .= $action->GetBaseTemplate($t);
- $string .= "===# DO NOT EDIT #===\n";
- $string .= "" . "\n";
- }
- } else {
- $string .= "===Create-Ticket: ticket1\n";
- $string .= $action->GetCreateTemplate();
- }
-}
-</%INIT>
diff --git a/rt/html/Tools/Reports/CreatedByDates.html b/rt/html/Tools/Reports/CreatedByDates.html
deleted file mode 100644
index 3df67ee..0000000
--- a/rt/html/Tools/Reports/CreatedByDates.html
+++ /dev/null
@@ -1,94 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Queue => undef
-$CreatedBefore => undef
-$CreatedAfter => undef
-</%args>
-<%init>
-my $title = loc("Created tickets in period, grouped by status");
-my $q = RT::Queue->new($session{'CurrentUser'});
-my $before = RT::Date->new($session{'CurrentUser'});
-my $after = RT::Date->new($session{'CurrentUser'});
-my $query = 'Status != "deleted" ';
-
-
-if ($CreatedAfter) {
- $after->Set(Format => 'unknown', Value => $CreatedAfter);
- $CreatedAfter = $after->AsString;
-}
-if ($CreatedBefore) {
- $before->Set(Format => 'unknown', Value => $CreatedBefore);
- $CreatedBefore = $before->AsString;
-}
-
-
-$q->LoadByCols(Name => $Queue);
-</%init>
-<& /Elements/Header, Title => $title &>
-<& /Tools/Reports/Elements/Tabs, current_tab => 'Tools/Reports/CreatedByDates.html', Title => $title &>
-<form method="post" action="CreatedByDates.html">
-% if ($Queue|| $CreatedBefore ||$CreatedAfter) {
-% # if we have a queue, do the search
-% if ($Queue) { $query .= " AND Queue = '$Queue'"}
-% if ($CreatedBefore) { $query .= " AND Created < '".$before->ISO."'"; }
-% if ($CreatedAfter) { $query .= " AND Created > '".$after->ISO."'"}
-% my $groupby = 'Status';
-<& /Search/Elements/Chart, Query => $query, PrimaryGroupBy => $groupby &>
-% }
-
-<hr>
-
-<br /><&|/l&>Queue</&>: <& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $q->id &>
-<br /><&|/l&>Tickets created after</&>:
-<input size="20" name="CreatedAfter" value="<%$CreatedAfter%>" />
-<br /><&|/l&>Tickets created before</&>:
-<input size="20" name="CreatedBefore" value="<%$CreatedBefore%>" />
-
-<& /Elements/Submit&>
-</form>
diff --git a/rt/html/Tools/Reports/Elements/Tabs b/rt/html/Tools/Reports/Elements/Tabs
deleted file mode 100644
index 7fa7de8..0000000
--- a/rt/html/Tools/Reports/Elements/Tabs
+++ /dev/null
@@ -1,89 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Tools/Elements/Tabs,
- subtabs => $tabs,
- current_tab => 'Tools/Reports/index.html',
- current_subtab => $current_tab,
- Title => $Title &>
-
-<%INIT>
-my $tabs = {
- a => {
- title => loc('Resolved by owner'),
- path => 'Tools/Reports/ResolvedByOwner.html',
- },
- b => {
- title => loc('Resolved in date range'),
- path => 'Tools/Reports/ResolvedByDates.html',
- },
- c => {
- title => loc('Created in a date range'),
- path => 'Tools/Reports/CreatedByDates.html',
- },
-};
-
-
-
-
-$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/Tools/Reports/ResolvedByDates.html b/rt/html/Tools/Reports/ResolvedByDates.html
deleted file mode 100644
index b0a66f4..0000000
--- a/rt/html/Tools/Reports/ResolvedByDates.html
+++ /dev/null
@@ -1,95 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Queue => undef
-$ResolvedBefore => undef
-$ResolvedAfter => undef
-</%args>
-<%init>
-my $title = loc("Resolved tickets in period, grouped by owner");
-my $q = RT::Queue->new($session{'CurrentUser'});
-my $before = RT::Date->new($session{'CurrentUser'});
-my $after = RT::Date->new($session{'CurrentUser'});
-my $query = '';
-
-
-if ($ResolvedAfter) {
- $after->Set(Format => 'unknown', Value => $ResolvedAfter);
- $ResolvedAfter = $after->AsString;
-}
-if ($ResolvedBefore) {
- $before->Set(Format => 'unknown', Value => $ResolvedBefore);
- $ResolvedBefore = $before->AsString;
-}
-
-
-$q->LoadByCols(Name => $Queue);
-</%init>
-<& /Elements/Header, Title => $title &>
-<& /Tools/Reports/Elements/Tabs, current_tab => 'Tools/Reports/ResolvedByDates.html', Title => $title &>
-<form method="post" action="ResolvedByDates.html">
-% if ($Queue|| $ResolvedBefore ||$ResolvedAfter) {
-% # if we have a queue, do the search
-% $query = "Status = 'resolved'";
-% if ($Queue) { $query .= " AND Queue = '$Queue'"}
-% if ($ResolvedBefore) { $query .= " AND Resolved < '".$before->ISO."'"; }
-% if ($ResolvedAfter) { $query .= " AND Resolved > '".$after->ISO."'"}
-% my $groupby = 'Owner';
-<& /Search/Elements/Chart, Query => $query, PrimaryGroupBy => $groupby &>
-% }
-
-<hr>
-
-<br /><&|/l&>Queue</&>: <& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $q->id &>
-<br /><&|/l&>Tickets resolved after</&>:
-<input size="20" name="ResolvedAfter" value="<%$ResolvedAfter%>" />
-<br /><&|/l&>Tickets resolved before</&>:
-<input size="20" name="ResolvedBefore" value="<%$ResolvedBefore%>" />
-
-<& /Elements/Submit&>
-</form>
diff --git a/rt/html/Tools/Reports/ResolvedByOwner.html b/rt/html/Tools/Reports/ResolvedByOwner.html
deleted file mode 100644
index 7e60a13..0000000
--- a/rt/html/Tools/Reports/ResolvedByOwner.html
+++ /dev/null
@@ -1,70 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%args>
-$Queue => undef
-</%args>
-<%init>
-my $title = loc("Resolved tickets, grouped by owner");
-my $q = RT::Queue->new($session{'CurrentUser'});
-$q->LoadByCols(Name => $Queue);
-</%init>
-<& /Elements/Header, Title => $title &>
-<& /Tools/Reports/Elements/Tabs, current_tab => '/Tools/Reports/ResolvedByOwner.html', Title => $title &>
-<form method="post" action="ResolvedByOwner.html">
-% if ($Queue) {
-% # if we have a queue, do the search
-% my $query = "Status = 'resolved' AND Queue = '$Queue'";
-% my $groupby = 'Owner';
-<& /Search/Elements/Chart, Query => $query, PrimaryGroupBy => $groupby &>
-% }
-
-<hr>
-
-<&|/l&>Queue</&>: <& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $q->id &>
-<& /Elements/Submit&>
-</form>
diff --git a/rt/html/Tools/Reports/index.html b/rt/html/Tools/Reports/index.html
deleted file mode 100644
index 0ba28c7..0000000
--- a/rt/html/Tools/Reports/index.html
+++ /dev/null
@@ -1,50 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc('Reports') &>
-<& /Tools/Reports/Elements/Tabs, Title => loc('Reports') &>
-<& /Elements/Callback &>
diff --git a/rt/html/Tools/index.html b/rt/html/Tools/index.html
deleted file mode 100644
index f49868a..0000000
--- a/rt/html/Tools/index.html
+++ /dev/null
@@ -1,52 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Tools") &>
-<& Elements/Tabs,
- current_tab => "Tools/index.html",
- Title => loc("Tools") &>
-
diff --git a/rt/html/User/Delegation.html b/rt/html/User/Delegation.html
deleted file mode 100644
index a85a31c..0000000
--- a/rt/html/User/Delegation.html
+++ /dev/null
@@ -1,107 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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, Label => loc('Modify Rights') &>
-</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
deleted file mode 100644
index e519146..0000000
--- a/rt/html/User/Elements/DelegateRights
+++ /dev/null
@@ -1,109 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<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" value="1" <%$ del_hash->{$pg->PrincipalId} && 'CHECKED' %> />
-% if ( $del_hash->{$pg->PrincipalId}) {
-<input type="hidden" class="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
deleted file mode 100644
index e02953d..0000000
--- a/rt/html/User/Elements/GroupTabs
+++ /dev/null
@@ -1,84 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 24faa8b..0000000
--- a/rt/html/User/Elements/Tabs
+++ /dev/null
@@ -1,89 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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',
- },
- f => { title => loc('Search options'),
- path => 'Prefs/SearchOptions.html',
- },
- r => { title => loc('RT at a glance'),
- path => 'Prefs/MyRT.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>
-$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
deleted file mode 100644
index a02aa32..0000000
--- a/rt/html/User/Groups/Members.html
+++ /dev/null
@@ -1,160 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="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 ) {
-<em><&|/l&>(No members)</&></em>
-% } else {
-<em><&|/l&>(Check box to delete)</&></em>
-<br />
-<br />
-<&|/l&>Users</&>
-% my $UserMembers = $Group->MembersObj;
-% $UserMembers->LimitToUsers();
-<ul>
-% while (my $member = $UserMembers->Next()) {
-<li><input type="checkbox" class="checkbox" name="DeleteMember-<%$member->MemberId%>" value="1" />
-<%$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" class="checkbox" name="DeleteMember-<%$member->MemberId%>" value="1" />
-<%$member->MemberObj->Object->Name%>
-% }
-</ul>
-% }
-</td>
-</tr>
-</table>
-<& /Elements/Submit, Label => loc('Modify Members') &>
-</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
deleted file mode 100644
index f0e31f4..0000000
--- a/rt/html/User/Groups/Modify.html
+++ /dev/null
@@ -1,157 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="new" />
-% } else {
-<input type="hidden" class="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" class="hidden" name="SetEnabled" value="1" />
-<input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> /> <&|/l&>Enabled (Unchecking this box disables this group)</&><br />
-</tr>
-</table>
-<& /Elements/Submit, Label => loc('Save 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
deleted file mode 100644
index 5e44feb..0000000
--- a/rt/html/User/Groups/index.html
+++ /dev/null
@@ -1,67 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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
deleted file mode 100644
index 54a950d..0000000
--- a/rt/html/User/Prefs.html
+++ /dev/null
@@ -1,289 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /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" class="hidden" name="id" value="<%$UserObj->Id%>" />
-
-<table width="100%" border="0">
-<tr>
-
-<td valign="top" class="boxcontainer">
-<&| /Widgets/TitleBox, title => loc('Identity'), id => "user-prefs-identity" &>
-
-<input type="hidden" class="hidden" name="Name" value="<%$UserObj->Name%>" />
-<table cellspacing="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>
-</&>
-<&| /Widgets/TitleBox, title => loc('Phone numbers'), id => "user-prefs-phone" &>
-<table cellspacing="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/Callback, _CallbackName => 'FormLeftColumn', UserObj => $UserObj, %ARGS &>
-</td>
-<td valign="top" class="boxcontainer">
-% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
-<&| /Widgets/TitleBox, title => loc('Password'), id => "user-prefs-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>
-</&>
-% }
-
-<&| /Widgets/TitleBox, title => loc('Location'), id => "user-prefs-location" &>
-<table cellspacing="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/Callback, _CallbackName => 'FormRightColumn', UserObj => $UserObj, %ARGS &>
-</td>
-</tr>
-<tr>
-
-
-
-<td colspan="2" valign="top" class="boxcontainer">
-%if ($UserObj->Privileged) {
-<br />
-<&| /Widgets/TitleBox, title => loc('Signature') &>
-<textarea cols="80" rows="5" name="Signature" class="signature" wrap="hard">
-<%$UserObj->Signature%></textarea>
-</&>
-% }
-
-</td>
-
-</tr>
-</table>
-
-<& /Elements/Callback, _CallbackName => 'FormEnd', UserObj => $UserObj, %ARGS &>
-
-<& /Elements/Submit, Label => loc('Save Preferences') &>
-</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
- );
-
- $m->comp('/Elements/Callback', _CallbackName => 'UpdateLogic',
- fields => \@fields,
- results => \@results,
- UserObj => $UserObj,
- ARGSRef => \%ARGS);
-
- my @fieldresults = UpdateRecordObject ( AttributesRef => \@fields,
- Object => $UserObj,
- ARGSRef => \%ARGS );
- if ($Lang) {
- $session{'CurrentUser'}->LanguageHandle($Lang);
- $session{'CurrentUser'} = $session{'CurrentUser'}; # force writeback
- }
-
- 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/Widgets/ComboBox b/rt/html/Widgets/ComboBox
deleted file mode 100644
index 164749c..0000000
--- a/rt/html/Widgets/ComboBox
+++ /dev/null
@@ -1,70 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<nobr>
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/combobox.js"></script>
-
-<span id="<% $Name %>_Container" class="combobox">
-<input name="<% $Name %>" id="<% $Name %>" class="combo-text" value="<% $Default %>" type="text" <% $Size ? "size='$Size'" : '' |n %> autocomplete="off" />
-<br style="display: none" /><span id="<% $Name %>_Button" class="combo-button"></span><select name="List-<% $Name %>" id="<% $Name %>_List" class="combo-list" onchange="ComboBox_SimpleAttach(this, this.form['<% $Name %>']); " size="<% $Rows %>">
-<option style="display: none" value="">-</option>
-% foreach my $value (@Values) {
- <option value="<%$value%>"><% $value%></option>
-% }
-</select>
-</span>
-<script language="javascript"><!--
-ComboBox_InitWith('<% $Name %>');
-//--></script>
-</nobr>
-<%ARGS>
-$Name
-$Size => undef
-$Rows => 5
-$Default => ''
-@Values => ()
-</%ARGS>
diff --git a/rt/html/Widgets/SavedSearch b/rt/html/Widgets/SavedSearch
deleted file mode 100644
index b315212..0000000
--- a/rt/html/Widgets/SavedSearch
+++ /dev/null
@@ -1,158 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%method new>
-<%init>
-return \%ARGS;
-</%init>
-</%method>
-
-<%method process>
-
-<%init>
-my @actions;
-my @Objects = RT::SavedSearches->new( $session{CurrentUser} )->_PrivacyObjects;
-push @Objects, RT::System->new($session{'CurrentUser'})
- if $session{'CurrentUser'}->HasRight( Object=> $RT::System,
- Right => 'SuperUser');
-$self->{SearchId} ||= 'new';
-my $SearchParams = { map { $_ => $args->{$_} } @{$self->{SearchFields}} };
-
-if ( my ( $container_object, $search_id ) = _parse_saved_search( $args->{'LoadSavedSearch'} ) ) {
- my $search = $container_object->Attributes->WithId($search_id);
- # We have a $search and now; import the others
- $self->{SearchId} = $args->{'LoadSavedSearch'};
- $self->{CurrentSearch}{Object} = $search;
- $args->{$_} = $search->SubValue($_) for @{ $self->{SearchFields} };
-}
-
-# look for the current one in the available saved searches
-if ($self->{SearchId} eq 'new') {
- for my $obj (@Objects) {
- for ( $m->comp( "/Search/Elements/SearchesForObject", Object => $obj ) ) {
- my ( $desc, $search ) = @$_;
- use Data::Dumper;
- # FFS
- local $Data::Dumper::Sortkeys = 1;
- if ( Dumper( $search->Content ) eq
- Dumper( { %$SearchParams, SearchType => $self->{SearchType} } ) ) {
- $self->{CurrentSearch}{Object} = $search;
- $self->{SearchId} = $search->Id;
- }
- }
- }
-}
-
-if ( $args->{Save} ) {
- if ( my $search = $self->{CurrentSearch}{Object} ) {
- # rename
- $search->SetDescription( $args->{Description} );
- push @actions, loc($self->{SearchType}).loc( ' [_1] renamed to [_2].', $self->{CurrentSearch}{Description}, $args->{Description} );
- }
- else {
- # new saved search
- my $saved_search = RT::SavedSearch->new( $session{'CurrentUser'} );
- my ( $ok, $search_msg ) = $saved_search->Save(
- Privacy => $args->{'Owner'},
- Name => $args->{'Description'},
- Type => $self->{'SearchType'},
- SearchParams => $SearchParams
- );
- if ($ok) {
- $self->{CurrentSearch}{Object} = $saved_search->{Attribute};
- push @actions, loc($self->{SearchType}).loc( ' [_1] saved.', $args->{Description} );
- } else {
- push @actions,
- [ loc("Can't save [_1]", loc($self->{SearchType})) . ': ' . loc($search_msg), 0 ];
- }
- }
-}
-
-if ( $args->{Delete} && $self->{CurrentSearch}{Object} ) {
- my ($ok, $msg) = $self->{CurrentSearch}{Object}->Delete;
- push @actions, $ok ? loc($self->{SearchType}).loc( ' [_1] deleted.', $self->{CurrentSearch}{Object}->Description ) : $msg;
- delete $self->{CurrentSearch}{Object};
- delete $self->{SearchId};
-
-}
-
-$self->{CurrentSearch}{Description} = $self->{CurrentSearch}{Object}->Description
- if $self->{CurrentSearch}{Object};
-
-return @actions;
-</%init>
-<%ARGS>
-$self
-$args
-</%ARGS>
-
-</%method>
-
-<%method show>
-<form method="post" action="<% $Action %>" name="SaveSearch">
-<& /Search/Elements/EditSearches, Name => 'Owner', SearchType => $self->{SearchType}, AllowCopy => 0,
- CurrentSearch => $self->{CurrentSearch}, SearchId => $self->{SearchId}, Title => $Title &><br />
-<%PERL>
-foreach my $field ( @{$self->{SearchFields}} ) {
- if ( ref($ARGS{$field}) && ref($ARGS{$field}) ne 'ARRAY' ) {
- $RT::Logger->error("Couldn't store '$field'. it's reference to ". ref($ARGS{$field}) );
- next;
- }
- foreach my $value ( grep defined, ref($ARGS{$field})? @{ $ARGS{$field} } : $ARGS{$field} ) {
-</%PERL>
-<input type="hidden" class="hidden" name="<% $field %>" value="<% $value %>" />
-% }
-% }
-</form>
-<%ARGS>
-$self => undef
-$Action => ''
-$Title => loc('Saved searches')
-</%ARGS>
-<%init>
-</%init>
-</%method>
diff --git a/rt/html/Widgets/SelectionBox b/rt/html/Widgets/SelectionBox
deleted file mode 100644
index c58a0a1..0000000
--- a/rt/html/Widgets/SelectionBox
+++ /dev/null
@@ -1,243 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-%# The SelectionBox Widget
-%#
-%# SYNOPSIS
-%#
-%# include javascript:
-%# <& /Widgets/SelectionBox:header &>
-%#
-%# <%init>:
-%# my $sel = $m->comp ('/Widgets/SelectionBox:new',
-%# Action => me.html',
-%# Name => 'my-selection',
-%# Available => \@items,
-%# # you can do things with @{$sel->{Current}} in the
-%# # OnSubmit callback
-%# OnSubmit => sub { my $sel = shift; },
-%# Selected => \@selected);
-%#
-%# $m->comp ('/Widgets/SelectionBox:process', %ARGS, self => $sel)
-%#
-%# where @items is an arrayref, each element is [value, label],
-%# and @selected is an arrayref of selected values from @items.
-%#
-%# and in html:
-%# <& /Widgets/SelectionBox:sow, self => $sel &>
-%#
-%# if the SelectionBox is created with AutoSave option, OnSubmit will be called
-%# on every button clicked in non-js mode.
-<%method header>
-% unless ($nojs) {
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/class.js"></script>
-<script type="text/javascript" src="<%$RT::WebPath%>/NoAuth/js/list.js"></script>
-% }
-<%ARGS>
-$nojs => 0
-</%ARGS>
-</%method>
-
-<%method new>
-<%init>
-$ARGS{_item_map} = {map {$_->[0] => $_->[1]} @{$ARGS{Available}}};
-return \%ARGS;
-</%init>
-</%method>
-
-<%method process>
-<%init>
-unless ($ARGS{$self->{Name}.'-Submit'}) {
- # init
- $self->{Current} = $self->{Selected};
- $self->{Selected} = [];
- return;
-}
-
-$self->{Selected} = $ARGS{$self->{Name}.'-Selected'};
-if ($self->{Selected} && !ref($self->{Selected})) {
- $self->{Selected} = [$self->{Selected}];
-}
-
-if ($ARGS{fromjs}) {
- $self->{Current} = $self->{Selected};
-}
-else {
- my $current = $self->{Current} = $ARGS{$self->{Name}.'-Current'};
- ++$self->{Modified};
- if ($current && !ref ($current)) {
- $current = [$current];
- }
-
- if ($ARGS{add}) {
- my $choosed = $ARGS{$self->{Name}.'-Available'};
- for my $add (ref($choosed) ? @$choosed : $choosed) {
- next if grep { $_ eq $add } @$current;
- push @$current, $add;
- }
- }
-
- if ($ARGS{remove}) {
- my $choosed = $ARGS{$self->{Name}.'-Selected'};
- for my $del (ref($choosed) ? @$choosed : $choosed) {
- @$current = map { $_ eq $del ? () : $_ } @$current;
- }
- }
-
- if ($ARGS{moveup} or $ARGS{movedown}) {
- my $offset = $ARGS{moveup} ? 1 : 0;
- my $choosed = $ARGS{$self->{Name}.'-Selected'};
- $choosed = [$choosed] unless ref ($choosed);
- my $canmove = 0; # not in the cornor
- for my $i ($ARGS{moveup} ? 0..$#{$current} : reverse 0..$#{$current}) {
- if (grep {$_ eq $current->[$i]} @$choosed) {
- if ($canmove) {
- splice (@$current, $i-$offset, 2,
- @{$current}[$i+1-$offset,$i-$offset]);
- }
- }
- else {
- ++$canmove;
- }
- }
- }
-
- if ($ARGS{clear}) {
- $current = [];
- }
-
- $self->{Current} = $current;
-}
-
-@{$self->{Current}} = grep { exists $self->{_item_map}{$_} } @{$self->{Current}};
-
-if ($self->{AutoSave} or $ARGS{$self->{Name}.'-Save'}) {
- $self->{OnSubmit}->($self);
- delete $self->{Modified};
-}
-
-</%init>
-<%ARGS>
-$self => undef
-</%ARGS>
-
-</%method>
-
-<%method current>
-% for (@{$self->{Current}}) {
-<input type="hidden" class="hidden" name="<% $self->{Name} %>-Current" value="<%$_%>" />
-% }
-<%INIT>
-</%INIT>
-<%ARGS>
-$self => undef
-</%ARGS>
-
-</%method>
-
-<%method show>
-<form method="post" action="<%$self->{Action}%>" name="SelectionBox-<% $name %>" id="SelectionBox-<% $name %>"
-% unless ($nojs) {
-onsubmit="list_<% $name %>.selectAll();"
-% }
->
-<input type="hidden" class="hidden" name="<% $self->{Name} %>-Submit" value="1" />
-<& SelectionBox:current, self => $self &>
-<input type="hidden" class="hidden" name="fromjs" value="0" />
-<&|/l&>Available</&>:
-<br />
-<select name="<%$name%>-Available" id="<%$name%>-Available" size="<%$size%>" multiple="multiple">
-% for (@{$self->{Available}}) {
-<option value="<% $_->[0] %>"><% $_->[1] %></option>
-% }
-</select>
-<input name="add" type="submit" class="button" value=" &rarr; " />
-<select name="<%$name%>-Selected" id="<%$name%>-Selected" size="<%$size%>" multiple="multiple">
-% for (@{$self->{Current}}) {
-<option value="<% $_ %>"
-% if (exists $selected{$_}) {
-selected="selected"
-% }
-><% $self->{_item_map}{$_} %></option>
-% }
-</select>
-% unless ($ARGS{'NoArrows'}) {
- <input name="moveup" type="submit" class="button" value=" &uarr; " />
- <input name="movedown" type="submit" class="button" value=" &darr; " />
-% }
- <input name="remove" type="submit" class="button" value="<&|/l&>Delete</&>" />
-% if ($ARGS{'Clear'}) {
- <input name="clear" type="submit" class="button" value="<&|/l&>Clear</&>" />
-% }
-
-% my $caption = "";
-% unless ($self->{'AutoSave'}) {
-% if ($self->{Modified}) {
-% $caption = loc('Selections modified. Please save your changes');
-% }
-<& /Elements/Submit, Caption => loc($caption), Label => loc('Save'), Name => $name.'-Save' &>
-% }
-</form>
-
-% unless ($nojs) {
-<script type="text/javascript">
-//<![CDATA[
-var list_<%$name%> = new list(document.getElementById("SelectionBox-<% $name %>"), 0, "list_<%$name%>");
-//]]>
-</script>
-% }
-<%ARGS>
-$self => undef
-$size => 10
-$nojs => 0
-</%ARGS>
-<%INIT>
-my $name = $self->{Name};
-my %selected = map {$_ => 1} @{$self->{Selected}};
-</%INIT>
-
-</%method>
diff --git a/rt/html/Widgets/TitleBox b/rt/html/Widgets/TitleBox
deleted file mode 100644
index 3e4afa0..0000000
--- a/rt/html/Widgets/TitleBox
+++ /dev/null
@@ -1,54 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="<% $class %>">
- <& TitleBoxStart, %ARGS &><% $m->content | n %><& TitleBoxEnd &>
-</div>
-<%ARGS>
-$class => ''
-</%ARGS>
-
diff --git a/rt/html/Widgets/TitleBoxEnd b/rt/html/Widgets/TitleBoxEnd
deleted file mode 100755
index a1f18df..0000000
--- a/rt/html/Widgets/TitleBoxEnd
+++ /dev/null
@@ -1,59 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
- <hr class="clear" />
- </div>
-</div>
-
-% #Manually flush the content buffer after each titlebox is displayed
-% $m->flush_buffer();
-
-<%ARGS>
-$title => undef
-$content => undef
-</%ARGS>
-
diff --git a/rt/html/Widgets/TitleBoxStart b/rt/html/Widgets/TitleBoxStart
deleted file mode 100755
index 602106e..0000000
--- a/rt/html/Widgets/TitleBoxStart
+++ /dev/null
@@ -1,86 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<div class="titlebox <% $class %>" id="<% $id %>">
- <div class="titlebox-title<% $title_class && " $title_class" %>">
-% if ($hideable) {
- <span class="widget"><a href="#" onclick="return rollup('<%$tid%>');" onfocus="this.blur(); return false;" title="Toggle visibility">X</a></span>
-% }
- <span class="left"><% $title_href && qq[<a href="$title_href">] | n %><% $title |n %><% $title_href && "</a>" |n%></span>
- <span class="right"><% $titleright_href && qq[<a href="$titleright_href">] | n %><% $titleright |n %><% $titleright_href && "</a>" |n%></span>
- </div>
- <div class="titlebox-content <% $bodyclass %>" id="<%$tid%>">
-
-<%ARGS>
-$width => undef
-$class => ''
-$bodyclass => ''
-$title_href => undef
-$title => ''
-$title_class => ''
-$titleright_href => undef
-$titleright => undef
-$id => ''
-$hideable => 1
-</%ARGS>
-
-<%init>
-#
-# This should be pretty bulletproof
-#
-my $page = $m->request_comp->path;
-
-my $tid = "TitleBox--$page--" .
- join '--', ($class, $bodyclass, $title, $id);
-
-$tid =~ s{/}{_}g;
-
-my $i = 0;
-$i++ while $m->notes("$tid-$i");
-$m->notes("$tid-$i" => 1);
-$tid = "$tid-$i";
-</%init>
diff --git a/rt/html/autohandler b/rt/html/autohandler
deleted file mode 100644
index 57ab22a..0000000
--- a/rt/html/autohandler
+++ /dev/null
@@ -1,331 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%INIT>
-
-# Roll back any dangling transactions from a previous failed connection
-$RT::Handle->ForceRollback() if $RT::Handle->TransactionDepth;
-
-
-if ($RT::StatementLog) {
- $RT::Handle->ClearSQLStatementLog;
- $RT::Handle->LogSQLStatements(1);
-}
-
-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::is_utf8($_)
- ? $_
- : Encode::decode( 'UTF-8' => $_, Encode::FB_PERLQQ )
- : ( $type eq 'ARRAY' )
- ? [
- map {
- ( ref($_) or Encode::is_utf8($_) )
- ? $_
- : Encode::decode( 'UTF-8' => $_, Encode::FB_PERLQQ )
- } @$_
- ]
- : ( $type eq 'HASH' )
- ? {
- map {
- ( ref($_) or Encode::is_utf8($_) )
- ? $_
- : Encode::decode( 'UTF-8' => $_, Encode::FB_PERLQQ )
- } %$_
- }
- : $_
-} %ARGS;
-
-# Latter in the code we use
-# $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS );
-# instead of $m->call_next to avoid problems with UTF8 keys in arguments.
-# The call_next method pass through original arguments and if you have
-# an argument with unicode key then in a next component you'll get two
-# records in the args hash: one with key without UTF8 flag and another
-# with the flag, which may result into errors. "{ base_comp => $m->request_comp }"
-# is copied from mason's source to get the same results as we get from
-# call_next method, this feature is not documented, so we just leave it
-# here to avoid possible side effects.
-
-# This code canonicalizes time inputs in hours into minutes
-foreach my $field ( keys %ARGS ) {
- next unless $field =~ /^(.*)-TimeUnits$/i && $ARGS{$1};
- my $local = $1;
- $ARGS{$local} =~ s{\b (?: (\d+) \s+ )? (\d+)/(\d+) \b}
- {($1 || 0) + $3 ? $2 / $3 : 0}xe;
- if ( $ARGS{$field} && $ARGS{$field} =~ /hours/i ) {
- $ARGS{$local} *= 60;
- }
- delete $ARGS{$field};
-}
-
-$m->{'rt_base_time'} = [ Time::HiRes::gettimeofday() ];
-
-$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 =~ $RT::WebNoAuthRegex ) {
- $m->comp( { base_comp => $m->request_comp }, $m->fetch_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('RT_System') );
-
- 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'
- )
- {
- $m->comp( '/Elements/Callback', %ARGS,
- _CallbackName => 'NewUser' );
-
- 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('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 $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);
-
- unless ( $session{'CurrentUser'}->id
- && $session{'CurrentUser'}->IsPassword($pass) )
- {
- delete $session{'CurrentUser'};
- $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}");
- $m->comp( '/Elements/Login', %ARGS,
- Error => loc('Your username or password is incorrect') );
- $m->comp( '/Elements/Callback', %ARGS, _CallbackName => 'FailedLogin' );
- $m->abort;
- }
- else {
- $RT::Logger->info(
- "Successful login for $user from $ENV{'REMOTE_ADDR'}");
- $m->comp( '/Elements/Callback', %ARGS, _CallbackName => 'SuccessfulLogin' );
- }
-}
-
-# 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 ( not $session{'CurrentUser'}->Privileged ) {
-
- # if the user is trying to access a ticket, redirect them
- if ( $m->request_comp->path =~ '^(/+)Ticket/Display.html'
- and $ARGS{'id'} )
- {
- RT::Interface::Web::Redirect($RT::WebURL."SelfService/Display.html?id=".$ARGS{'id'});
- }
-
- # otherwise, drop the user at the SelfService default page
- elsif ( $m->base_comp->path !~ $RT::SelfServiceRegex ) {
- RT::Interface::Web::Redirect($RT::WebURL."SelfService/");
- }
- else {
- $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS);
- }
- }
- else {
- $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS);
- }
-}
-
-# If we have no credentials
-else {
- $m->comp( '/Elements/Login', %ARGS );
- $m->abort();
-}
-
-if ($RT::StatementLog) {
- my @log = $RT::Handle->SQLStatementLog;
- $RT::Handle->ClearSQLStatementLog;
- for my $stmt (@log) {
- my ( $time, $sql, $bind, $duration ) = @{$stmt};
- my @bind;
- if ( ref $bind ) {
- @bind = @{$bind};
- }
- else {
-
- # Older DBIx-SB
- $duration = $bind;
- }
- $RT::Logger->log(
- level => $RT::StatementLog,
- message => "SQL(" . sprintf( "%.2f", $duration ) . "s): $sql;"
- . (
- @bind ? " [ bound values: @{[map{qq|'$_'|} @bind]} ]" : ""
- )
- );
- }
-}
-
-</%INIT>
-<& /Elements/Footer, %ARGS &>
-<%ARGS>
-$user => undef
-$pass => undef
-$menu => undef
-</%ARGS>
diff --git a/rt/html/index.html b/rt/html/index.html
deleted file mode 100644
index a74f3e5..0000000
--- a/rt/html/index.html
+++ /dev/null
@@ -1,117 +0,0 @@
-<& /Elements/Header, Title=>loc("RT at a glance"), Refresh => $session{'home_refresh_interval'} &>
-<!--
-% $m->out('--'.'>');
-% if (0) {
-%# -->
-<html><head>
-<meta http-equiv="refresh" content="30; url=http://bestpractical.com/rt/rt-broken-install.html">
-<title>Almost there!</title></head>
-<body>
-
-<img src="http://www.bestpractical.com/images/unconfigured-rtlogo.jpg" />
-<br /><br />
-<h1>You're almost there!</h1>
-You haven't yet configured your webserver to run RT.
-
-You appear to have installed RT's web interface correctly, but haven't yet configured your web
-server to "run" the RT server which powers the web interface.
-
-The next step is to edit your webserver's configuration file to instruct it to use
-RT's <strong>mod_perl</strong>, <strong>FastCGI</strong> or <strong>SpeedyCGI</strong> handler.
-
-If you need commercial support, please contact us at sales@bestpractical.com.
-
-
-<!--
-% }
-
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<& /Elements/Tabs,
- current_toptab => '',
- Title=>loc("RT at a glance"),
- actions => $actions,
- &>
-<& /Elements/ListActions, actions => \@results &>
-<& /Elements/MyRT &>
-<%init>
-
-my @results;
-
-if ($ARGS{'QuickCreate'} ) {
- my $ticket = RT::Ticket->new($session{'CurrentUser'});
- my ($tid, $trans, $tmsg) = $ticket->Create(Queue => $ARGS{'Queue'},
- Owner => $ARGS{'Owner'},
- Requestor => $session{'CurrentUser'}->UserObj->EmailAddress,
- Subject => $ARGS{'Subject'});
-
-
- push (@results, $tmsg);
-}
-
-
-if ( $ARGS{'q'} ) {
- RT::Interface::Web::Redirect($RT::WebURL."Search/Simple.html?q=".$m->interp->apply_escapes($ARGS{q}));
-}
-
-if ($ARGS{'HomeRefreshInterval'}) {
- $session{'home_refresh_interval'} = $ARGS{'HomeRefreshInterval'};
-}
-
-my $actions;
-if ($session{'CurrentUser'}->HasRight(Right => 'ModifySelf', Object => $RT::System)) {
- $actions = {
- A => { title => loc('Edit'),
- path => 'Prefs/MyRT.html',
- },
- };
-}
-
-</%init>
-
-%# --></body></html>
diff --git a/rt/html/l b/rt/html/l
deleted file mode 100644
index a65cd2a..0000000
--- a/rt/html/l
+++ /dev/null
@@ -1,52 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-%# <jesse@bestpractical.com>
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-<%init>
- my $hand = ($session{'CurrentUser'} ||= RT::CurrentUser->new)->LanguageHandle;
- $m->print($hand->maketext($m->content,@_));
- return(1);
-</%init>
diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm
index ddfb041..5d2e704 100644
--- a/rt/lib/RT.pm
+++ b/rt/lib/RT.pm
@@ -62,34 +62,34 @@ our $VERSION = '3.8.8';
our $BasePath = '/opt/rt3';
-our $EtcPath = 'etc';
-our $BinPath = 'bin';
-our $SbinPath = 'sbin';
-our $VarPath = 'var';
-our $PluginPath = 'plugins';
-our $LocalPath = 'local';
-our $LocalEtcPath = 'local/etc';
-our $LocalLibPath = 'local/lib';
-our $LocalLexiconPath = 'local/po';
+our $EtcPath = '/opt/rt3/etc';
+our $BinPath = '/opt/rt3/bin';
+our $SbinPath = '/opt/rt3/sbin';
+our $VarPath = '/opt/rt3/var';
+our $PluginPath = '';
+our $LocalPath = '/opt/rt3/local';
+our $LocalEtcPath = '/opt/rt3/local/etc';
+our $LocalLibPath = '/opt/rt3/local/lib';
+our $LocalLexiconPath = '/opt/rt3/local/po';
our $LocalPluginPath = $LocalPath."/plugins";
# $MasonComponentRoot is where your rt instance keeps its mason html files
-our $MasonComponentRoot = 'share/html';
+our $MasonComponentRoot = '/var/www/freeside/rt';
# $MasonLocalComponentRoot is where your rt instance keeps its site-local
# mason html files.
-our $MasonLocalComponentRoot = 'local/html';
+our $MasonLocalComponentRoot = '/opt/rt3/local/html';
# $MasonDataDir Where mason keeps its datafiles
-our $MasonDataDir = 'var/mason_data';
+our $MasonDataDir = '/usr/local/etc/freeside/masondata';
# RT needs to put session data (for preserving state between connections
# via the web interface)
-our $MasonSessionDir = 'var/session_data';
+our $MasonSessionDir = '/opt/rt3/var/session_data';
unless ( File::Spec->file_name_is_absolute($EtcPath) ) {
@@ -180,6 +180,8 @@ L<preloads classes /InitClasses> and L<set up logging /InitLogging>.
sub Init {
+ my @arg = @_;
+
CheckPerlRequirements();
InitPluginPaths();
@@ -188,7 +190,7 @@ sub Init {
ConnectToDatabase();
InitSystemObjects();
InitClasses();
- InitLogging();
+ InitLogging(@arg);
InitPlugins();
RT->Config->PostLoadCheck;
@@ -215,6 +217,8 @@ Create the Logger object and set up signal handlers.
sub InitLogging {
+ my %arg = @_;
+
# We have to set the record separator ($, man perlvar)
# or Log::Dispatch starts getting
# really pissy, as some other module we use unsets it.
@@ -350,39 +354,45 @@ sub InitLogging {
));
}
}
- InitSignalHandlers();
+ InitSignalHandlers(%arg);
}
sub InitSignalHandlers {
+ my %arg = @_;
+
# 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 {
- # The 'wide character' warnings has to be silenced for now, at least
- # until HTML::Mason offers a sane way to process both raw output and
- # unicode strings.
- # use 'goto &foo' syntax to hide ANON sub from stack
- if( index($_[0], 'Wide character in ') != 0 ) {
- unshift @_, $RT::Logger, qw(level warning message);
- goto &Log::Dispatch::log;
- }
- };
+ unless ( $arg{'NoSignalHandlers'} ) {
+
+ $SIG{__WARN__} = sub {
+ # The 'wide character' warnings has to be silenced for now, at least
+ # until HTML::Mason offers a sane way to process both raw output and
+ # unicode strings.
+ # use 'goto &foo' syntax to hide ANON sub from stack
+ if( index($_[0], 'Wide character in ') != 0 ) {
+ unshift @_, $RT::Logger, qw(level warning message);
+ goto &Log::Dispatch::log;
+ }
+ };
-#When we call die, trap it and log->crit with the value of the die.
+ #When we call die, trap it and log->crit with the value of the die.
- $SIG{__DIE__} = sub {
- # if we are not in eval and perl is not parsing code
- # then rollback transactions and log RT error
- unless ($^S || !defined $^S ) {
- $RT::Handle->Rollback(1) if $RT::Handle;
- $RT::Logger->crit("$_[0]") if $RT::Logger;
- }
- die $_[0];
- };
+ $SIG{__DIE__} = sub {
+ # if we are not in eval and perl is not parsing code
+ # then rollback transactions and log RT error
+ unless ($^S || !defined $^S ) {
+ $RT::Handle->Rollback(1) if $RT::Handle;
+ $RT::Logger->crit("$_[0]") if $RT::Logger;
+ }
+ die $_[0];
+ };
+
+ }
}
diff --git a/rt/lib/RT.pm.in b/rt/lib/RT.pm.in
index 1cf2443..77b9d4c 100644
--- a/rt/lib/RT.pm.in
+++ b/rt/lib/RT.pm.in
@@ -180,6 +180,8 @@ L<preloads classes /InitClasses> and L<set up logging /InitLogging>.
sub Init {
+ my @arg = @_;
+
CheckPerlRequirements();
InitPluginPaths();
@@ -188,7 +190,7 @@ sub Init {
ConnectToDatabase();
InitSystemObjects();
InitClasses();
- InitLogging();
+ InitLogging(@arg);
InitPlugins();
RT->Config->PostLoadCheck;
@@ -215,6 +217,8 @@ Create the Logger object and set up signal handlers.
sub InitLogging {
+ my %arg = @_;
+
# We have to set the record separator ($, man perlvar)
# or Log::Dispatch starts getting
# really pissy, as some other module we use unsets it.
@@ -350,39 +354,45 @@ sub InitLogging {
));
}
}
- InitSignalHandlers();
+ InitSignalHandlers(%arg);
}
sub InitSignalHandlers {
+ my %arg = @_;
+
# 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 {
- # The 'wide character' warnings has to be silenced for now, at least
- # until HTML::Mason offers a sane way to process both raw output and
- # unicode strings.
- # use 'goto &foo' syntax to hide ANON sub from stack
- if( index($_[0], 'Wide character in ') != 0 ) {
- unshift @_, $RT::Logger, qw(level warning message);
- goto &Log::Dispatch::log;
- }
- };
+ unless ( $arg{'NoSignalHandlers'} ) {
+
+ $SIG{__WARN__} = sub {
+ # The 'wide character' warnings has to be silenced for now, at least
+ # until HTML::Mason offers a sane way to process both raw output and
+ # unicode strings.
+ # use 'goto &foo' syntax to hide ANON sub from stack
+ if( index($_[0], 'Wide character in ') != 0 ) {
+ unshift @_, $RT::Logger, qw(level warning message);
+ goto &Log::Dispatch::log;
+ }
+ };
-#When we call die, trap it and log->crit with the value of the die.
+ #When we call die, trap it and log->crit with the value of the die.
- $SIG{__DIE__} = sub {
- # if we are not in eval and perl is not parsing code
- # then rollback transactions and log RT error
- unless ($^S || !defined $^S ) {
- $RT::Handle->Rollback(1) if $RT::Handle;
- $RT::Logger->crit("$_[0]") if $RT::Logger;
- }
- die $_[0];
- };
+ $SIG{__DIE__} = sub {
+ # if we are not in eval and perl is not parsing code
+ # then rollback transactions and log RT error
+ unless ($^S || !defined $^S ) {
+ $RT::Handle->Rollback(1) if $RT::Handle;
+ $RT::Logger->crit("$_[0]") if $RT::Logger;
+ }
+ die $_[0];
+ };
+
+ }
}
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index f8d121c..3f8581c 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -173,7 +173,7 @@ our %META = (
WidgetArguments => {
Description => 'Theme', #loc
# XXX: we need support for 'get values callback'
- Values => [qw(3.5-default 3.4-compat web2)],
+ Values => [qw(3.5-default 3.4-compat web2 freeside2.1)],
},
},
MessageBoxRichText => {
@@ -847,7 +847,7 @@ sub SetFromConfig {
# XXX skip references to scalars or other references.
# Otherwie 5.10 goes boom. may be we should skip any
# reference
- return if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
+ next if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
my $entry_ref = *{$entry}{ ref($ref) };
next unless $entry_ref;
diff --git a/rt/lib/RT/Extension/ActivityReports.pm b/rt/lib/RT/Extension/ActivityReports.pm
new file mode 100644
index 0000000..52d8ba6
--- /dev/null
+++ b/rt/lib/RT/Extension/ActivityReports.pm
@@ -0,0 +1,3 @@
+package RT::Extension::ActivityReports;
+
+our $VERSION = '0.2';
diff --git a/rt/lib/RT/Groups_Overlay.pm b/rt/lib/RT/Groups_Overlay.pm
index 793a85d..1b3ef51 100644
--- a/rt/lib/RT/Groups_Overlay.pm
+++ b/rt/lib/RT/Groups_Overlay.pm
@@ -323,6 +323,7 @@ sub WithRight {
$from_group->WithGroupRight( %args );
#XXX: DIRTY HACK
+ use DBIx::SearchBuilder 1.50; #no version on ::Union :(
use DBIx::SearchBuilder::Union;
my $union = new DBIx::SearchBuilder::Union;
$union->add($from_role);
diff --git a/rt/lib/RT/I18N/en_malkovich.po b/rt/lib/RT/I18N/en_malkovich.po
deleted file mode 100644
index 74769f1..0000000
--- a/rt/lib/RT/I18N/en_malkovich.po
+++ /dev/null
@@ -1,3973 +0,0 @@
-msgid ""
-msgstr ""
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: 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
-#. ($TicketObj->Id, $TicketObj->Subject)
-#. ($Ticket->id, $Ticket->Subject)
-#. ($ticket->Id, $ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "#%1: %2"
-msgstr "#%1: %2"
-
-#: html/Search/Elements/SelectPersonType:30 lib/RT/Date.pm:337
-#. ($s, $time_unit)
-#. ($option, $subtype)
-msgid "%1 %2"
-msgstr "%1 %2"
-
-#: lib/RT/Tickets_Overlay.pm:828
-#. ($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:3451 lib/RT/Transaction_Overlay.pm:550 lib/RT/Transaction_Overlay.pm:593
-#. ($cf->Name, $new_value->Content)
-#. ($field, $self->NewValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 added"
-msgstr "%1 %2 Malkovich"
-
-#: lib/RT/Date.pm:334
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "%1 %2 ago"
-
-#: lib/RT/Ticket_Overlay.pm:3457 lib/RT/Transaction_Overlay.pm:557
-#. ($cf->Name, $old_value, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 %2 Malkovich to %3"
-
-#: lib/RT/Ticket_Overlay.pm:3454 lib/RT/Transaction_Overlay.pm:553 lib/RT/Transaction_Overlay.pm:599
-#. ($cf->Name, $old_value)
-#. ($field, $self->OldValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 deleted"
-msgstr "%1 %2 Malkovich"
-
-#: html/Admin/Elements/EditScrips:43 html/Admin/Elements/ListGlobalScrips:27 html/Ticket/Elements/PreviewScrips:53
-#. ($scrip->ConditionObj->Name, $scrip->ActionObj->Name, $scrip->TemplateObj->Name)
-#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
-msgid "%1 %2 with template %3"
-msgstr "%1 %2 Malkovich %3"
-
-#: bin/rt-crontool:165 bin/rt-crontool:172 bin/rt-crontool:178
-#. ("--search-argument", "--search")
-#. ("--condition-argument", "--condition")
-#. ("--action-argument", "--action")
-msgid "%1 - An argument to pass to %2"
-msgstr "%1 - A Malkovich to pass to %2"
-
-#: bin/rt-crontool:181
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr "%1 - Malkovich Malkovich to MALKOVICH"
-
-#: bin/rt-crontool:175
-#. ("--action")
-msgid "%1 - Specify the action module you want to use"
-msgstr "%1 - Malkovich the Malkovich Malkovich to use"
-
-#: bin/rt-crontool:169
-#. ("--condition")
-msgid "%1 - Specify the condition module you want to use"
-msgstr "%1 - Malkovich the Malkovich Malkovich to use"
-
-#: bin/rt-crontool:162
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr "%1 - Malkovich the Malkovich Malkovich to use"
-
-#: lib/RT/ScripAction_Overlay.pm:114
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr "%1 Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3484
-#. ($args{'Value'}, $cf->Name)
-msgid "%1 added as a value for %2"
-msgstr "%1 Malkovich as a Malkovich %2"
-
-#: lib/RT/Link_Overlay.pm:111 lib/RT/Link_Overlay.pm:118
-#. ($args{'Base'})
-#. ($args{'Target'})
-msgid "%1 appears to be a local object, but can't be found in the database"
-msgstr "%1 Malkovich to be a Malkovich, but can't be Malkovich in the Malkovich"
-
-#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:458
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-msgid "%1 by %2"
-msgstr "%1 by %2"
-
-#: lib/RT/Transaction_Overlay.pm:512 lib/RT/Transaction_Overlay.pm:688 lib/RT/Transaction_Overlay.pm:697 lib/RT/Transaction_Overlay.pm:700
-#. ($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 Malkovich %2 to %3"
-
-#: lib/RT/Record.pm:739
-msgid "%1 could not be set to %2."
-msgstr "%1 Malkovich be set to %2."
-
-#: lib/RT/Ticket_Overlay.pm:2739
-#. ($self)
-msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
-msgstr "%1 couldn't Malkovich to Malkovich. RT's Malkovich be Malkovich."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I own..."
-msgstr "%1 Malkovich Malkovich I Malkovich..."
-
-#: html/Elements/MyTickets:26
-#. ($rows)
-msgid "%1 highest priority tickets I requested..."
-msgstr "%1 Malkovich Malkovich I Malkovich..."
-
-#: bin/rt-crontool:157
-#. ($0)
-msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
-msgstr "%1 is a tool to act on Malkovich a Malkovich Malkovich, such as cron."
-
-#: lib/RT/Queue_Overlay.pm:784
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this queue."
-msgstr "%1 is no Malkovich a %2 Malkovich."
-
-#: lib/RT/Ticket_Overlay.pm:3540
-#. ($args{'Value'}, $cf->Name)
-msgid "%1 is no longer a value for custom field %2"
-msgstr "%1 is no Malkovich a Malkovich Malkovich %2"
-
-#: html/Ticket/Create.html:155 html/Ticket/Create.html:156 html/Ticket/Elements/ShowBasics:36 html/Ticket/Elements/ShowBasics:42 html/Ticket/Elements/ShowBasics:47
-#. ('<input size=3 name="TimeWorked" value="'.$ARGS{TimeWorked}.'">')
-#. ('<input size=3 name="TimeLeft" value="'.$ARGS{TimeLeft}.'">')
-#. ($Ticket->TimeEstimated)
-#. ($Ticket->TimeWorked)
-#. ($Ticket->TimeLeft)
-msgid "%1 min"
-msgstr "%1 min"
-
-#: html/User/Elements/DelegateRights:75
-#. (loc($ObjectType =~ /^RT::(.*)$/))
-msgid "%1 rights"
-msgstr "%1 Malkovich"
-
-#: lib/RT/Action/ResolveMembers.pm:41
-#. (ref $self)
-msgid "%1 will resolve all members of a resolved group ticket."
-msgstr "%1 Malkovich Malkovich of a Malkovich Malkovich."
-
-#: lib/RT/Transaction_Overlay.pm:408
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1: no Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:56
-#. ($size)
-msgid "%1b"
-msgstr "%1b"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:53
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr "%1k"
-
-#: lib/RT/Ticket_Overlay.pm:1252
-#. ($args{'Status'})
-msgid "'%1' is an invalid value for status"
-msgstr "'%1' is a Malkovich Malkovich"
-
-#: 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/Elements/EditLinks:32 html/Ticket/Elements/EditPeople:45 html/User/Groups/Members.html:54
-msgid "(Check box to delete)"
-msgstr "(Malkovich to Malkovich)"
-
-#: html/Ticket/Elements/PreviewScrips:49
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr "(Malkovich to Malkovich Malkovich to the Malkovich Malkovich)"
-
-#: html/Ticket/Elements/PreviewScrips:71
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr "(Malkovich to Malkovich Malkovich to the Malkovich Malkovich)"
-
-#: NOT FOUND IN SOURCE
-msgid "(Enter ticket ids or URLs, seperated with spaces)"
-msgstr "(Malkovich Malkovich or URLs, Malkovich Malkovich)"
-
-#: 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 "(If Malkovich, Malkovich to %1"
-
-#: html/Admin/Elements/EditCustomFields:32 html/Admin/Elements/ListGlobalCustomFields:31
-msgid "(No custom fields)"
-msgstr "(No Malkovich)"
-
-#: html/Admin/Groups/Members.html:49 html/User/Groups/Members.html:52
-msgid "(No members)"
-msgstr "(No Malkovich)"
-
-#: html/Admin/Elements/EditScrips:31 html/Admin/Elements/ListGlobalScrips:31
-msgid "(No scrips)"
-msgstr "(No Malkovich)"
-
-#: html/Admin/Elements/EditTemplates:30
-msgid "(No templates)"
-msgstr "(No Malkovich)"
-
-#: html/Ticket/Update.html:66
-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 "(Malkovich a Malkovich-copy of Malkovich to a Malkovich-Malkovich of Malkovich. Does <b>not</b> Malkovich Malkovich Malkovich Malkovich.)"
-
-#: 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 "(Malkovich a Malkovich-copy of Malkovich to a Malkovich-Malkovich of Malkovich Malkovich Malkovich. Malkovich <b>will</b> Malkovich Malkovich.)"
-
-#: html/Ticket/Update.html:62
-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 "(Malkovich a Malkovich-copy of Malkovich to a Malkovich-Malkovich of Malkovich. Does <b>not</b> Malkovich Malkovich Malkovich Malkovich.)"
-
-#: 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 "(Malkovich a Malkovich-copy of Malkovich to a Malkovich-Malkovich of Malkovich. Malkovich <b>will</b> Malkovich Malkovich.)"
-
-#: html/Admin/Groups/index.html:32 html/User/Groups/index.html:32
-msgid "(empty)"
-msgstr "(Malkovich)"
-
-#: html/Admin/Users/index.html:38
-msgid "(no name listed)"
-msgstr "(no Malkovich)"
-
-#: html/Admin/Elements/SelectRights:47 html/Elements/SelectCustomFieldValue:29 html/Ticket/Elements/EditCustomField:64 html/Ticket/Elements/ShowCustomFields:35 lib/RT/Transaction_Overlay.pm:511
-msgid "(no value)"
-msgstr "(no Malkovich)"
-
-#: html/Elements/EditLinks:105 html/Ticket/Elements/BulkLinks:27
-msgid "(only one ticket)"
-msgstr "(Malkovich)"
-
-#: html/Elements/TicketList:167
-msgid "(pending approval)"
-msgstr "(Malkovich Malkovich)"
-
-#: html/Elements/TicketList:170
-msgid "(pending other Collection)"
-msgstr "(Malkovich Malkovich)"
-
-#: NOT FOUND IN SOURCE
-msgid "(pending other tickets)"
-msgstr "(Malkovich Malkovich)"
-
-#: html/Admin/Users/Modify.html:49
-msgid "(required)"
-msgstr "(Malkovich)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:60
-msgid "(untitled)"
-msgstr "(Malkovich)"
-
-#: 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 lib/RT/StyleGuide.pod:767
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
-msgstr "<input type=\"submit\" value=\"Malkovich in\">&nbsp;%1"
-
-#: etc/initialdata:218
-msgid "A blank template"
-msgstr "A Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:156 lib/RT/Principal_Overlay.pm:180
-msgid "ACE not found"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:830
-msgid "ACEs can only be created and deleted."
-msgstr "Malkovich be Malkovich and Malkovich."
-
-#: NOT FOUND IN SOURCE
-msgid "Aborting to avoid unintended ticket modifications.\\n"
-msgstr "Malkovich to Malkovich Malkovich Malkovich Malkovich.\\n"
-
-#: html/User/Elements/Tabs:31
-msgid "About me"
-msgstr "Malkovich me"
-
-#: html/Admin/Users/Modify.html:79
-msgid "Access control"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrip:49
-msgid "Action"
-msgstr "Malkovich"
-
-#: lib/RT/Scrip_Overlay.pm:148
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "Malkovich %1 Malkovich"
-
-#: bin/rt-crontool:119
-msgid "Action committed."
-msgstr "Malkovich Malkovich."
-
-#: bin/rt-crontool:115
-msgid "Action prepared..."
-msgstr "Malkovich..."
-
-#: html/Search/Bulk.html:93
-msgid "Add AdminCc"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:89
-msgid "Add Cc"
-msgstr "Add Cc"
-
-#: html/Ticket/Create.html:113 html/Ticket/Update.html:81
-msgid "Add More Files"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:85
-msgid "Add Requestor"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/AddCustomFieldValue:24
-msgid "Add Value"
-msgstr "Malkovich"
-
-#: html/Admin/Global/Scrip.html:54
-msgid "Add a scrip which will apply to all queues"
-msgstr "Add a Malkovich Malkovich to Malkovich"
-
-#: html/Search/Bulk.html:125
-msgid "Add comments or replies to selected tickets"
-msgstr "Malkovich or Malkovich to Malkovich Malkovich"
-
-#: html/Admin/Groups/Members.html:41 html/User/Groups/Members.html:38
-msgid "Add members"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/People.html:65 html/Ticket/Elements/AddWatchers:27
-msgid "Add new watchers"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:684
-#. ($args{'Type'})
-msgid "Added principal as a %1 for this queue"
-msgstr "Malkovich as a %1 Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1547
-#. ($self->loc($args{'Type'}))
-msgid "Added principal as a %1 for this ticket"
-msgstr "Malkovich as a %1 Malkovich"
-
-#: html/Admin/Users/Modify.html:119 html/User/Prefs.html:111
-msgid "Address1"
-msgstr "Malkovich1"
-
-#: html/Admin/Users/Modify.html:124 html/User/Prefs.html:115
-msgid "Address2"
-msgstr "Malkovich2"
-
-#: html/Ticket/Create.html:73
-msgid "Admin Cc"
-msgstr "Malkovich Cc"
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "Malkovich"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/index.html:24 html/Admin/Queues/index.html:27
-msgid "Admin queues"
-msgstr "Malkovich"
-
-#: html/Admin/Global/index.html:25 html/Admin/Global/index.html:27
-msgid "Admin/Global configuration"
-msgstr "Malkovich/Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Queue/Basics"
-msgstr "Malkovich/Malkovich/Malkovich"
-
-#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:38 lib/RT/ACE_Overlay.pm:88
-msgid "AdminCc"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:73
-msgid "AdminCustomFields"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Group_Overlay.pm:146
-msgid "AdminGroup"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:148
-msgid "AdminGroupMembership"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/System.pm:58
-msgid "AdminOwnPersonalGroups"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:69
-msgid "AdminQueue"
-msgstr "Malkovich"
-
-#: lib/RT/System.pm:59
-msgid "AdminUsers"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/People.html:47 html/Ticket/Elements/EditPeople:53
-msgid "Administrative Cc"
-msgstr "Malkovich Cc"
-
-#: html/Elements/SelectDateRelation:35
-msgid "After"
-msgstr "Malkovich"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Elements/EditCustomFields:94
-msgid "All Custom Fields"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/index.html:52
-msgid "All Queues"
-msgstr "Malkovich"
-
-#: html/Elements/Tabs:58
-msgid "Approval"
-msgstr "Malkovich"
-
-#: 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 "Malkovich #%1: %2"
-
-#: html/Approvals/index.html:53
-#. ($ticket->Id)
-msgid "Approval #%1: Notes not recorded due to a system error"
-msgstr "Malkovich #%1: Malkovich Malkovich to a Malkovich"
-
-#: html/Approvals/index.html:51
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr "Malkovich #%1: Malkovich"
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr "Malkovich"
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr "Malkovich Malkovich"
-
-#: html/Approvals/Elements/Approve:43
-msgid "Approve"
-msgstr "Malkovich"
-
-#: etc/initialdata:504
-msgid "Approver's notes: %1"
-msgstr "Malkovich's Malkovich: %1"
-
-#: lib/RT/Date.pm:414
-msgid "Apr."
-msgstr "Apr."
-
-#: html/Elements/SelectSortOrder:34 html/Search/Elements/DisplayOptions:52
-msgid "Ascending"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:134 html/SelfService/Update.html:47 html/Ticket/ModifyAll.html:82 html/Ticket/Update.html:81
-msgid "Attach"
-msgstr "Malkovich"
-
-#: html/SelfService/Create.html:64 html/Ticket/Create.html:109
-msgid "Attach file"
-msgstr "Malkovich"
-
-#: html/SelfService/Update.html:36 html/Ticket/Create.html:97 html/Ticket/Update.html:70
-msgid "Attached file"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:416
-msgid "Attachment created"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1251
-msgid "Attachment filename"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowAttachments:25
-msgid "Attachments"
-msgstr "Malkovich"
-
-#: lib/RT/Attributes_Overlay.pm:158
-msgid "Attribute Deleted"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Date.pm:418
-msgid "Aug."
-msgstr "Aug."
-
-#: NOT FOUND IN SOURCE
-msgid "AuthSystem"
-msgstr "Malkovich"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "Malkovich"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "Malkovich To Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Bad data in %1"
-msgstr "Malkovich in %1"
-
-#: html/Admin/Elements/GroupTabs:38 html/Admin/Elements/QueueTabs:38 html/Admin/Elements/UserTabs:37 html/Ticket/Elements/Tabs:91 html/User/Elements/GroupTabs:37
-msgid "Basics"
-msgstr "Malkovich"
-
-#: html/Ticket/Update.html:64
-msgid "Bcc"
-msgstr "Bcc"
-
-#: html/Admin/Elements/EditScrip:73
-msgid "Be sure to save your changes"
-msgstr "Be sure to Malkovich Malkovich"
-
-#: html/Elements/SelectDateRelation:33 lib/RT/CurrentUser.pm:336
-msgid "Before"
-msgstr "Malkovich"
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/ShowHistory:38 html/Ticket/Elements/ShowHistory:44
-msgid "Brief headers"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:24 html/Search/Bulk.html:25
-msgid "Bulk ticket update"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:1533
-msgid "Can not modify system users"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:68
-msgid "Can this principal see this queue"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:211
-msgid "Can't add a custom field value without a name"
-msgstr "Can't add a Malkovich Malkovich Malkovich a name"
-
-#: lib/RT/Link_Overlay.pm:126
-msgid "Can't link a ticket to itself"
-msgstr "Can't link a Malkovich to Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2716
-msgid "Can't merge into a merged ticket. You should never get this error"
-msgstr "Can't Malkovich a Malkovich. Malkovich Malkovich Malkovich"
-
-#: lib/RT/Record.pm:1060 lib/RT/Record.pm:1138
-msgid "Can't specifiy both base and target"
-msgstr "Can't Malkovich Malkovich and Malkovich"
-
-#: html/autohandler:132
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "Malkovich Malkovich: %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:59 lib/RT/ACE_Overlay.pm:87
-msgid "Cc"
-msgstr "Cc"
-
-#: html/SelfService/Prefs.html:30
-msgid "Change password"
-msgstr "Malkovich"
-
-#: html/SelfService/Update.html:39 html/Ticket/Create.html:100 html/Ticket/Update.html:73
-msgid "Check box to delete"
-msgstr "Malkovich to Malkovich"
-
-#: html/Admin/Elements/SelectRights:30
-msgid "Check box to revoke right"
-msgstr "Malkovich to Malkovich"
-
-#: html/Elements/EditLinks:121 html/Elements/EditLinks:63 html/Elements/ShowLinks:56 html/Ticket/Create.html:183 html/Ticket/Elements/BulkLinks:42
-msgid "Children"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:129 html/User/Prefs.html:119
-msgid "City"
-msgstr "City"
-
-#: html/Ticket/Elements/ShowDates:47
-msgid "Closed"
-msgstr "Malkovich"
-
-#: html/SelfService/Closed.html:24
-msgid "Closed Tickets"
-msgstr "Malkovich"
-
-#: html/SelfService/Elements/Tabs:44
-msgid "Closed tickets"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/ShowTransaction:152 html/Ticket/Elements/Tabs:154
-msgid "Comment"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/Modify.html:57
-msgid "Comment Address"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:88
-msgid "Comment on tickets"
-msgstr "Malkovich on Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:88
-msgid "CommentOnTicket"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments"
-msgstr "Malkovich"
-
-#: html/Ticket/ModifyAll.html:69 html/Ticket/Update.html:51
-msgid "Comments (Not sent to requestors)"
-msgstr "Malkovich (Malkovich to Malkovich)"
-
-#: html/Search/Bulk.html:129
-msgid "Comments (not sent to requestors)"
-msgstr "Malkovich (Malkovich to Malkovich)"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments about %1"
-msgstr "Malkovich %1"
-
-#: html/Admin/Users/Modify.html:182 html/Ticket/Elements/ShowRequestor:45
-msgid "Comments about this user"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:537
-msgid "Comments added"
-msgstr "Malkovich"
-
-#: lib/RT/Action/Generic.pm:149
-msgid "Commit Stubbed"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrip:41
-msgid "Condition"
-msgstr "Malkovich"
-
-#: bin/rt-crontool:105
-msgid "Condition matches..."
-msgstr "Malkovich Malkovich..."
-
-#: lib/RT/Scrip_Overlay.pm:164
-msgid "Condition not found"
-msgstr "Malkovich Malkovich"
-
-#: html/Elements/Tabs:52
-msgid "Configuration"
-msgstr "Malkovich"
-
-#: html/SelfService/Prefs.html:32
-msgid "Confirm"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "ContactInfoSystem"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Elements/ModifyTemplate:43 html/Elements/SelectAttachmentField:26 html/Ticket/ModifyAll.html:86
-msgid "Content"
-msgstr "Malkovich"
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence Address"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:533
-msgid "Correspondence added"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3471
-msgid "Could not add new custom field value for ticket. "
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich. "
-
-#: lib/RT/Ticket_Overlay.pm:2967 lib/RT/Ticket_Overlay.pm:2975 lib/RT/Ticket_Overlay.pm:2992
-msgid "Could not change owner. "
-msgstr "Malkovich Malkovich. "
-
-#: html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditCustomFields:164
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/User/Groups/Modify.html:76 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
-msgid "Could not create group"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Global/Template.html:74 html/Admin/Queues/Template.html:71
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr "Malkovich Malkovich: %1"
-
-#: lib/RT/Ticket_Overlay.pm:1185 lib/RT/Ticket_Overlay.pm:364
-msgid "Could not create ticket. Queue not set"
-msgstr "Malkovich Malkovich. Malkovich"
-
-#: lib/RT/User_Overlay.pm:226 lib/RT/User_Overlay.pm:240 lib/RT/User_Overlay.pm:249 lib/RT/User_Overlay.pm:258 lib/RT/User_Overlay.pm:267 lib/RT/User_Overlay.pm:281 lib/RT/User_Overlay.pm:291 lib/RT/User_Overlay.pm:462
-msgid "Could not create user"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:662 lib/RT/Ticket_Overlay.pm:1515
-msgid "Could not find or create that user"
-msgstr "Malkovich or Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:723 lib/RT/Ticket_Overlay.pm:1596
-msgid "Could not find that principal"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Groups/Members.html:87 html/User/Groups/Members.html:89 html/User/Groups/Modify.html:81
-msgid "Could not load group"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:682
-#. ($args{'Type'})
-msgid "Could not make that principal a %1 for this queue"
-msgstr "Malkovich Malkovich Malkovich a %1 Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1536
-#. ($self->loc($args{'Type'}))
-msgid "Could not make that principal a %1 for this ticket"
-msgstr "Malkovich Malkovich Malkovich a %1 Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:781
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this queue"
-msgstr "Malkovich Malkovich Malkovich as a %1 Malkovich"
-
-#: lib/RT/Group_Overlay.pm:977
-msgid "Couldn't add member to group"
-msgstr "Couldn't Malkovich to Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3481 lib/RT/Ticket_Overlay.pm:3537
-#. ($Msg)
-msgid "Couldn't create a transaction: %1"
-msgstr "Couldn't Malkovich a Malkovich: %1"
-
-#: lib/RT/Record.pm:748
-msgid "Couldn't find row"
-msgstr "Couldn't Malkovich"
-
-#: lib/RT/Group_Overlay.pm:951
-msgid "Couldn't find that principal"
-msgstr "Couldn't Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:245
-msgid "Couldn't find that value"
-msgstr "Couldn't Malkovich"
-
-#: lib/RT/CurrentUser.pm:123
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr "Couldn't load %1 from the Malkovich.\\n"
-
-#: html/Admin/Groups/GroupRights.html:87 html/Admin/Groups/UserRights.html:74
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr "Couldn't Malkovich %1"
-
-#: lib/RT/Link_Overlay.pm:169 lib/RT/Link_Overlay.pm:178 lib/RT/Link_Overlay.pm:205
-msgid "Couldn't load link"
-msgstr "Couldn't Malkovich"
-
-#: html/Admin/Elements/EditCustomFields:145 html/Admin/Queues/CustomFields.html:35 html/Admin/Queues/People.html:120
-#. ($id)
-msgid "Couldn't load queue"
-msgstr "Couldn't Malkovich"
-
-#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:71
-#. ($id)
-msgid "Couldn't load queue %1"
-msgstr "Couldn't Malkovich %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load that user (%1)"
-msgstr "Couldn't Malkovich (%1)"
-
-#: html/SelfService/Display.html:116
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr "Couldn't Malkovich '%1'"
-
-#: html/Admin/Users/Modify.html:146 html/User/Prefs.html:131
-msgid "Country"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/CreateUserCalled:25 html/Admin/Elements/EditCustomField:62 html/Admin/Elements/EditScrip:110 html/Admin/Groups/Modify.html:55 html/Admin/Queues/Template.html:44 html/Elements/QuickCreate:23 html/Ticket/Create.html:134 html/Ticket/Create.html:195 html/User/Groups/Modify.html:55
-msgid "Create"
-msgstr "Malkovich"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:74
-msgid "Create a CustomField"
-msgstr "Malkovich a Malkovich"
-
-#: html/Admin/Queues/CustomField.html:47
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr "Malkovich a Malkovich Malkovich %1"
-
-#: html/Admin/Global/CustomField.html:47
-msgid "Create a CustomField which applies to all queues"
-msgstr "Malkovich a Malkovich Malkovich to Malkovich"
-
-#: html/Admin/Groups/Modify.html:66 html/Admin/Groups/Modify.html:92
-msgid "Create a new group"
-msgstr "Malkovich a Malkovich"
-
-#: html/User/Groups/Modify.html:66 html/User/Groups/Modify.html:91
-msgid "Create a new personal group"
-msgstr "Malkovich a Malkovich Malkovich"
-
-#: html/Ticket/Create.html:24 html/Ticket/Create.html:27 html/Ticket/Create.html:35
-msgid "Create a new ticket"
-msgstr "Malkovich a Malkovich"
-
-#: html/Admin/Users/Modify.html:211 html/Admin/Users/Modify.html:268
-msgid "Create a new user"
-msgstr "Malkovich a Malkovich"
-
-#: html/Admin/Queues/Modify.html:103
-msgid "Create a queue"
-msgstr "Malkovich a Malkovich"
-
-#: html/Admin/Queues/Scrip.html:58
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr "Malkovich a Malkovich %1"
-
-#: html/Admin/Global/Template.html:68 html/Admin/Queues/Template.html:64
-msgid "Create a template"
-msgstr "Malkovich a Malkovich"
-
-#: html/SelfService/Create.html:24
-msgid "Create a ticket"
-msgstr "Malkovich a Malkovich"
-
-#: etc/initialdata:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "Malkovich Malkovich on Malkovich's Malkovich"
-
-#: html/SelfService/Create.html:77
-msgid "Create ticket"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "Create tickets in this queue"
-msgstr "Malkovich in Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:73
-msgid "Create, delete and modify custom fields"
-msgstr "Malkovich, Malkovich and Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:69
-msgid "Create, delete and modify queues"
-msgstr "Malkovich, Malkovich and Malkovich"
-
-#: lib/RT/System.pm:58
-msgid "Create, delete and modify the members of personal groups"
-msgstr "Malkovich, Malkovich and Malkovich the Malkovich of Malkovich"
-
-#: lib/RT/System.pm:59
-msgid "Create, delete and modify users"
-msgstr "Malkovich, Malkovich and Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "CreateTicket"
-msgstr "Malkovich"
-
-#: html/Elements/SelectDateType:25 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1279
-msgid "Created"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:87
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Elements/EditLinks:27
-msgid "Current Links"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Elements/EditScrips:29
-msgid "Current Scrips"
-msgstr "Malkovich"
-
-#: html/Admin/Groups/Members.html:38 html/User/Groups/Members.html:41
-msgid "Current members"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectRights:28
-msgid "Current rights"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Current search criteria"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/People.html:40 html/Ticket/Elements/EditPeople:44
-msgid "Current watchers"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Global/CustomField.html:54
-#. ($CustomField)
-msgid "Custom Field #%1"
-msgstr "Malkovich #%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 "Malkovich"
-
-#: html/Admin/Elements/EditScrip:101
-msgid "Custom action cleanup code"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Elements/EditScrip:93
-msgid "Custom action preparation code"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Elements/EditScrip:85
-msgid "Custom condition"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1693
-#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
-msgid "Custom field %1 %2 %3"
-msgstr "Malkovich %1 %2 %3"
-
-#: lib/RT/Tickets_Overlay.pm:1688
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr "Malkovich %1 has a Malkovich."
-
-#: lib/RT/Tickets_Overlay.pm:1685
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr "Malkovich %1 has no Malkovich."
-
-#: lib/RT/Ticket_Overlay.pm:3373
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr "Malkovich %1 Malkovich"
-
-#: html/Admin/Elements/EditCustomFields:195
-msgid "Custom field deleted"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3523
-msgid "Custom field not found"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:355
-#. ($args{'Content'}, $self->Name)
-msgid "Custom field value %1 could not be found for custom field %2"
-msgstr "Malkovich Malkovich %1 Malkovich be Malkovich Malkovich %2"
-
-#: lib/RT/CustomField_Overlay.pm:255
-msgid "Custom field value could not be deleted"
-msgstr "Malkovich Malkovich Malkovich be Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:361
-msgid "Custom field value could not be found"
-msgstr "Malkovich Malkovich Malkovich be Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:253 lib/RT/CustomField_Overlay.pm:363
-msgid "Custom field value deleted"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:541
-msgid "CustomField"
-msgstr "Malkovich"
-
-#: html/SelfService/Display.html:38 html/Ticket/Create.html:160 html/Ticket/Elements/ShowSummary:54 html/Ticket/Elements/Tabs:94 html/Ticket/ModifyAll.html:43
-msgid "Dates"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:422
-msgid "Dec."
-msgstr "Dec."
-
-#: etc/initialdata:222
-msgid "Default Autoresponse template"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:519
-#. ($type, $self->Field, $self->OldValue, $self->NewValue)
-msgid "Default: %1/%2 changed from %3 to %4"
-msgstr "Malkovich: %1/%2 Malkovich %3 to %4"
-
-#: html/User/Delegation.html:24 html/User/Delegation.html:27
-msgid "Delegate rights"
-msgstr "Malkovich"
-
-#: lib/RT/System.pm:62
-msgid "Delegate specific rights which have been granted to you."
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich to you."
-
-#: lib/RT/System.pm:62
-msgid "DelegateRights"
-msgstr "Malkovich"
-
-#: html/User/Elements/Tabs:37
-msgid "Delegation"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrips:53 html/Search/Elements/EditFormat:66 html/Search/Elements/EditSearches:15
-msgid "Delete"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrips:52
-msgid "Delete selected scrips"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "Delete tickets"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "DeleteTicket"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:162
-msgid "Deleting this object could break referential integrity"
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:329
-msgid "Deleting this object would break referential integrity"
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:478
-msgid "Deleting this object would violate referential integrity"
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich Malkovich"
-
-#: html/Approvals/Elements/Approve:44
-msgid "Deny"
-msgstr "Deny"
-
-#: html/Elements/EditLinks:113 html/Elements/EditLinks:44 html/Elements/ShowLinks:36 html/Ticket/Create.html:181 html/Ticket/Elements/BulkLinks:34 html/Ticket/Elements/ShowDependencies:31
-msgid "Depended on by"
-msgstr "Malkovich on by"
-
-#: lib/RT/Transaction_Overlay.pm:621
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr "Malkovich by %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:661
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr "Malkovich by %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:618
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr "Malkovich on %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:658
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr "Malkovich on %1 Malkovich"
-
-#: html/Elements/EditLinks:109 html/Elements/EditLinks:35 html/Elements/SelectLinkType:26 html/Elements/ShowLinks:26 html/Ticket/Create.html:180 html/Ticket/Elements/BulkLinks:30 html/Ticket/Elements/ShowDependencies:24
-msgid "Depends on"
-msgstr "Malkovich on"
-
-#: html/Elements/SelectSortOrder:34 html/Search/Elements/DisplayOptions:57
-msgid "Descending"
-msgstr "Malkovich"
-
-#: html/SelfService/Create.html:72 html/Ticket/Create.html:118
-msgid "Describe the issue below"
-msgstr "Malkovich the Malkovich"
-
-#: html/Admin/Elements/AddCustomFieldValue:35 html/Admin/Elements/EditCustomField:38 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyTemplate:35 html/Admin/Groups/Modify.html:48 html/Admin/Queues/Modify.html:47 html/Elements/SelectGroups:26 html/Search/Elements/EditSearches:8 html/User/Groups/Modify.html:48
-msgid "Description"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/Tabs:86
-msgid "Display"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:70
-msgid "Display Access Control List"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:76
-msgid "Display Scrip templates for this queue"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:79
-msgid "Display Scrips for this queue"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowHistory:34
-msgid "Display mode"
-msgstr "Malkovich"
-
-#: lib/RT/System.pm:53
-msgid "Do anything and everything"
-msgstr "Do Malkovich and Malkovich"
-
-#: html/Elements/Refresh:29
-msgid "Don't refresh this page."
-msgstr "Don't Malkovich Malkovich."
-
-#: NOT FOUND IN SOURCE
-msgid "Don't show search results"
-msgstr "Don't Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:60
-msgid "Download"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Download all the tickets as a tab delimited file"
-msgstr "Malkovich the Malkovich as a Malkovich Malkovich"
-
-#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:44 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1283
-msgid "Due"
-msgstr "Due"
-
-#: NOT FOUND IN SOURCE
-msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
-msgstr "MALKOVICH: Couldn't Malkovich '%1': %2.\\n"
-
-#: html/Admin/Queues/CustomFields.html:45
-#. ($Queue->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Search/Bulk.html:141 html/Ticket/ModifyLinks.html:35
-msgid "Edit Links"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/Templates.html:41
-#. ($QueueObj->Name)
-msgid "Edit Templates for queue %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Admin/Global/index.html:45
-msgid "Edit system templates"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/Modify.html:118
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr "Malkovich Malkovich Malkovich %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing Configuration for user %1"
-msgstr "Malkovich Malkovich Malkovich %1"
-
-#: html/Admin/Elements/EditCustomField:90
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Admin/Groups/Members.html:31
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr "Malkovich Malkovich Malkovich %1"
-
-#: html/User/Groups/Members.html:128
-#. ($Group->Name)
-msgid "Editing membership for personal group %1"
-msgstr "Malkovich Malkovich Malkovich Malkovich %1"
-
-#: lib/RT/Record.pm:1075 lib/RT/Record.pm:1152
-msgid "Either base or target must be specified"
-msgstr "Malkovich or Malkovich be Malkovich"
-
-#: html/Admin/Users/Modify.html:52 html/Elements/SelectUsers:26 html/Ticket/Elements/AddWatchers:55 html/User/Prefs.html:43
-msgid "Email"
-msgstr "Malkovich"
-
-#: lib/RT/User_Overlay.pm:206
-msgid "Email address in use"
-msgstr "Malkovich in use"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailAddress"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailEncoding"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:50
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "Malkovich (Malkovich Malkovich Malkovich Malkovich Malkovich)"
-
-#: html/Admin/Groups/Modify.html:52 html/User/Groups/Modify.html:52
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "Malkovich (Malkovich Malkovich Malkovich Malkovich)"
-
-#: html/Admin/Queues/Modify.html:83
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "Malkovich (Malkovich Malkovich Malkovich Malkovich)"
-
-#: html/Admin/Elements/EditCustomFields:97
-msgid "Enabled Custom Fields"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/index.html:55
-msgid "Enabled Queues"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:106 html/Admin/Groups/Modify.html:116 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:308 html/User/Groups/Modify.html:116
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr "Malkovich %1"
-
-#: lib/RT/CustomField_Overlay.pm:433
-msgid "Enter multiple values"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:430
-msgid "Enter one value"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:142
-msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
-msgstr "Malkovich or URIs to Malkovich to. Malkovich Malkovich Malkovich Malkovich."
-
-#: html/Elements/Login:39 html/SelfService/Error.html:24 html/SelfService/Error.html:25
-msgid "Error"
-msgstr "Error"
-
-#: lib/RT/Queue_Overlay.pm:593
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr "Malkovich in Malkovich to Malkovich->Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Queue->DelWatcher"
-msgstr "Malkovich in Malkovich to Malkovich->Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1468
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr "Malkovich in Malkovich to Malkovich->Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Ticket->DelWatcher"
-msgstr "Malkovich in Malkovich to Malkovich->Malkovich"
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "Malkovich"
-
-#: bin/rt-crontool:190
-msgid "Example:"
-msgstr "Malkovich:"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalAuthId"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalContactInfoId"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Users/Modify.html:72
-msgid "Extra info"
-msgstr "Malkovich"
-
-#: lib/RT/User_Overlay.pm:342
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr "Malkovich to find 'Malkovich' Malkovich Malkovich."
-
-#: lib/RT/User_Overlay.pm:349
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr "Malkovich to find 'Malkovich' Malkovich Malkovich"
-
-#: bin/rt-crontool:134
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr "Malkovich to Malkovich %1. (%2)"
-
-#: lib/RT/Date.pm:412
-msgid "Feb."
-msgstr "Feb."
-
-#: html/Search/Elements/PickBasics:60 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:57 lib/RT/Tickets_Overlay.pm:1153
-msgid "Final Priority"
-msgstr "Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1274
-msgid "FinalPriority"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/People.html:60 html/Ticket/Elements/EditPeople:33
-msgid "Find group whose"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Queues/People.html:56 html/Admin/Users/index.html:45 html/Ticket/Elements/EditPeople:29
-msgid "Find people whose"
-msgstr "Malkovich Malkovich"
-
-#: html/Search/Results.html:72
-msgid "Find tickets"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/Tabs:59
-msgid "First"
-msgstr "Malkovich"
-
-#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:746
-msgid "Foo Bar Baz"
-msgstr "Malkovich"
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:737
-msgid "Foo!"
-msgstr "Foo!"
-
-#: html/Search/Bulk.html:84
-msgid "Force change"
-msgstr "Malkovich"
-
-#: html/Search/Results.html:70
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr "Malkovich %quant(%1,Malkovich)"
-
-#: lib/RT/Record.pm:750
-msgid "Found Object"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformContactInfo"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:37
-msgid "FreeformMultiple"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:36
-msgid "FreeformSingle"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:392
-msgid "Fri."
-msgstr "Fri."
-
-#: html/Ticket/Elements/ShowHistory:40 html/Ticket/Elements/ShowHistory:50
-msgid "Full headers"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:587
-#. ($New->Name)
-msgid "Given to %1"
-msgstr "Malkovich to %1"
-
-#: html/Admin/Elements/Tabs:40 html/Admin/index.html:37
-msgid "Global"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectTemplate:37
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr "Malkovich: %1"
-
-#: html/Tools/Offline.html:69
-msgid "Go"
-msgstr "Go"
-
-#: html/Admin/Elements/EditCustomFields:73 html/Admin/Groups/index.html:39 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:69
-msgid "Go!"
-msgstr "Go!"
-
-#: html/Elements/GotoTicket:24 html/SelfService/Elements/GotoTicket:24
-msgid "Goto ticket"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/AddWatchers:45 html/Ticket/Elements/ShowGroupMembers:33 html/User/Elements/DelegateRights:77
-msgid "Group"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:957
-msgid "Group already has member"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Groups/Modify.html:76
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "Malkovich be Malkovich: %1"
-
-#: lib/RT/Group_Overlay.pm:497
-msgid "Group created"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:1129
-msgid "Group has no such member"
-msgstr "Malkovich no Malkovich"
-
-#: lib/RT/Group_Overlay.pm:937 lib/RT/Queue_Overlay.pm:669 lib/RT/Queue_Overlay.pm:729 lib/RT/Ticket_Overlay.pm:1522 lib/RT/Ticket_Overlay.pm:1602
-msgid "Group not found"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:963
-msgid "Groups can't be members of their members"
-msgstr "Malkovich can't be Malkovich of Malkovich"
-
-#: lib/RT/Interface/CLI.pm:72 lib/RT/Interface/CLI.pm:72
-msgid "Hello!"
-msgstr "Malkovich!"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:753
-#. ($name)
-msgid "Hello, %1"
-msgstr "Malkovich, %1"
-
-#: html/Ticket/Elements/ShowHistory:29 html/Ticket/Elements/Tabs:89
-msgid "History"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "HomePhone"
-msgstr "Malkovich"
-
-#: html/Elements/Tabs:43
-msgid "Homepage"
-msgstr "Malkovich"
-
-#: lib/RT/Base.pm:86
-#. (6)
-msgid "I have %quant(%1,concrete mixer)."
-msgstr "I have %quant(%1,Malkovich)."
-
-#: html/Search/Elements/PickBasics:104 html/Ticket/Elements/ShowBasics:26 lib/RT/Tickets_Overlay.pm:1080
-msgid "Id"
-msgstr "Id"
-
-#: html/Admin/Users/Modify.html:43 html/User/Prefs.html:38
-msgid "Identity"
-msgstr "Malkovich"
-
-#: etc/initialdata:429
-msgid "If an approval is rejected, reject the original and delete pending approvals"
-msgstr "If a Malkovich is Malkovich, Malkovich the Malkovich and Malkovich Malkovich"
-
-#: bin/rt-crontool:186
-msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
-msgstr "If Malkovich Malkovich, a Malkovich Malkovich Malkovich Malkovich to Malkovich Malkovich Malkovich to 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 "If you've Malkovich Malkovich, be sure to"
-
-#: lib/RT/Record.pm:742
-msgid "Illegal value for %1"
-msgstr "Malkovich Malkovich %1"
-
-#: lib/RT/Record.pm:745
-msgid "Immutable field"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomFields:72
-msgid "Include disabled custom fields in listing."
-msgstr "Malkovich Malkovich Malkovich in Malkovich."
-
-#: html/Admin/Queues/index.html:42
-msgid "Include disabled queues in listing."
-msgstr "Malkovich Malkovich in Malkovich."
-
-#: html/Admin/Users/index.html:46
-msgid "Include disabled users in search."
-msgstr "Malkovich Malkovich in Malkovich."
-
-#: html/Search/Elements/PickBasics:59 lib/RT/Tickets_Overlay.pm:1129
-msgid "Initial Priority"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1273 lib/RT/Ticket_Overlay.pm:1275
-msgid "InitialPriority"
-msgstr "Malkovich"
-
-#: lib/RT/ScripAction_Overlay.pm:97
-msgid "Input error"
-msgstr "Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3797
-msgid "Internal Error"
-msgstr "Malkovich"
-
-#: lib/RT/Record.pm:186
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "Malkovich: %1"
-
-#: lib/RT/Group_Overlay.pm:644
-msgid "Invalid Group Type"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Principal_Overlay.pm:127
-msgid "Invalid Right"
-msgstr "Malkovich"
-
-#: lib/RT/Record.pm:747
-msgid "Invalid data"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid owner. Defaulting to 'nobody'."
-msgstr "Malkovich. Malkovich to 'Malkovich'."
-
-#: lib/RT/Scrip_Overlay.pm:133 lib/RT/Template_Overlay.pm:251
-msgid "Invalid queue"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: lib/RT/Record.pm:161
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "Malkovich Malkovich %1"
-
-#: lib/RT/Ticket_Overlay.pm:3380
-msgid "Invalid value for custom field"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:385
-msgid "Invalid value for status"
-msgstr "Malkovich Malkovich"
-
-#: bin/rt-crontool:187
-msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
-msgstr "It is Malkovich Malkovich Malkovich Malkovich Malkovich be Malkovich to Malkovich."
-
-#: bin/rt-crontool:188
-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 Malkovich Malkovich a non-Malkovich Malkovich the Malkovich Malkovich and RT Malkovich to Malkovich."
-
-#: bin/rt-crontool:159
-msgid "It takes several arguments:"
-msgstr "It Malkovich Malkovich:"
-
-#: lib/RT/Date.pm:411
-msgid "Jan."
-msgstr "Jan."
-
-#: lib/RT/Group_Overlay.pm:149
-msgid "Join or leave this group"
-msgstr "Join or Malkovich Malkovich"
-
-#: lib/RT/Date.pm:417
-msgid "Jul."
-msgstr "Jul."
-
-#: html/Ticket/Elements/Tabs:100
-msgid "Jumbo"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:416
-msgid "Jun."
-msgstr "Jun."
-
-#: NOT FOUND IN SOURCE
-msgid "Lang"
-msgstr "Lang"
-
-#: html/User/Prefs.html:54
-msgid "Language"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/Tabs:74
-msgid "Last"
-msgstr "Last"
-
-#: html/Ticket/Elements/EditDates:37 html/Ticket/Elements/ShowDates:39
-msgid "Last Contact"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Contact</a>"
-msgstr "Malkovich</a>"
-
-#: html/Elements/SelectDateType:28
-msgid "Last Contacted"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Notified"
-msgstr "Malkovich"
-
-#: html/Elements/SelectDateType:29
-msgid "Last Updated"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:82
-msgid "Let this user access RT"
-msgstr "Malkovich Malkovich RT"
-
-#: html/Admin/Users/Modify.html:86
-msgid "Let this user be granted rights"
-msgstr "Malkovich be Malkovich"
-
-#: lib/RT/Record.pm:1086
-msgid "Link already exists"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Record.pm:1100
-msgid "Link could not be created"
-msgstr "Malkovich be Malkovich"
-
-#: lib/RT/Record.pm:1106
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "Malkovich (%1)"
-
-#: lib/RT/Record.pm:1167
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "Malkovich (%1)"
-
-#: lib/RT/Record.pm:1173
-msgid "Link not found"
-msgstr "Malkovich"
-
-#: html/Ticket/ModifyLinks.html:24 html/Ticket/ModifyLinks.html:28
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr "Malkovich #%1"
-
-#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:61 html/Ticket/Elements/Tabs:98 html/Ticket/ModifyAll.html:56
-msgid "Links"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:111 html/User/Prefs.html:104
-msgid "Location"
-msgstr "Malkovich"
-
-#: lib/RT.pm:184
-#. ($RT::LogDir)
-msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
-msgstr "Malkovich %1 Malkovich or couldn't be Malkovich.\\n RT can't run."
-
-#: html/Elements/Header:69
-#. ("<b>".$session{'CurrentUser'}->Name."</b>")
-msgid "Logged in as %1"
-msgstr "Malkovich in as %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 "Malkovich"
-
-#: html/Elements/Header:66
-msgid "Logout"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:83
-msgid "Make Owner"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:107
-msgid "Make Status"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:115
-msgid "Make date Due"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:117
-msgid "Make date Resolved"
-msgstr "Malkovich Malkovich"
-
-#: html/Search/Bulk.html:111
-msgid "Make date Started"
-msgstr "Malkovich Malkovich"
-
-#: html/Search/Bulk.html:109
-msgid "Make date Starts"
-msgstr "Malkovich Malkovich"
-
-#: html/Search/Bulk.html:113
-msgid "Make date Told"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:103
-msgid "Make priority"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:105
-msgid "Make queue"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:101
-msgid "Make subject"
-msgstr "Malkovich"
-
-#: html/Admin/index.html:32
-msgid "Manage groups and group membership"
-msgstr "Malkovich and Malkovich Malkovich"
-
-#: html/Admin/index.html:38
-msgid "Manage properties and configuration which apply to all queues"
-msgstr "Malkovich Malkovich and Malkovich Malkovich to Malkovich"
-
-#: html/Admin/index.html:35
-msgid "Manage queues and queue-specific properties"
-msgstr "Malkovich and Malkovich-Malkovich Malkovich"
-
-#: html/Admin/index.html:29
-msgid "Manage users and passwords"
-msgstr "Malkovich and Malkovich"
-
-#: lib/RT/Date.pm:413
-msgid "Mar."
-msgstr "Mar."
-
-#: lib/RT/Date.pm:415
-msgid "May."
-msgstr "May."
-
-#: lib/RT/Transaction_Overlay.pm:634
-#. ($value)
-msgid "Member %1 added"
-msgstr "Malkovich %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:674
-#. ($value)
-msgid "Member %1 deleted"
-msgstr "Malkovich %1 Malkovich"
-
-#: lib/RT/Group_Overlay.pm:974
-msgid "Member added"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:1136
-msgid "Member deleted"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:1140
-msgid "Member not deleted"
-msgstr "Malkovich Malkovich"
-
-#: html/Elements/SelectLinkType:25
-msgid "Member of"
-msgstr "Malkovich of"
-
-#: html/Admin/Elements/GroupTabs:41 html/User/Elements/GroupTabs:41
-msgid "Members"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:631
-#. ($value)
-msgid "Membership in %1 added"
-msgstr "Malkovich in %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:671
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr "Malkovich in %1 Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2813
-msgid "Merge Successful"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2733
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr "Malkovich. Couldn't Malkovich"
-
-#: html/Elements/EditLinks:104 html/Ticket/Elements/BulkLinks:26
-msgid "Merge into"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:135 html/Ticket/Update.html:83
-msgid "Message"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr "Malkovich Malkovich Malkovich it is Malkovich or is Malkovich."
-
-#: lib/RT/Ticket_Overlay.pm:2514
-msgid "Message could not be recorded"
-msgstr "Malkovich Malkovich be Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Message recipients"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2517
-msgid "Message recorded"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Record.pm:749
-msgid "Missing a primary key?: %1"
-msgstr "Malkovich a Malkovich?: %1"
-
-#: html/Admin/Users/Modify.html:166 html/User/Prefs.html:71
-msgid "Mobile"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "MobilePhone"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:71
-msgid "Modify Access Control List"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Global/CustomFields.html:43 html/Admin/Global/index.html:50
-msgid "Modify Custom Fields which apply to all queues"
-msgstr "Malkovich Malkovich Malkovich to Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:74
-msgid "Modify Scrip templates for this queue"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:77
-msgid "Modify Scrips for this queue"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Queues/CustomField.html:44
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr "Malkovich a Malkovich Malkovich %1"
-
-#: html/Admin/Global/CustomField.html:52
-msgid "Modify a CustomField which applies to all queues"
-msgstr "Malkovich a Malkovich Malkovich to Malkovich"
-
-#: html/Admin/Queues/Scrip.html:53
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "Malkovich a Malkovich %1"
-
-#: html/Admin/Global/Scrip.html:47
-msgid "Modify a scrip which applies to all queues"
-msgstr "Malkovich a Malkovich Malkovich to Malkovich"
-
-#: html/Ticket/ModifyDates.html:24 html/Ticket/ModifyDates.html:28
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "Malkovich Malkovich #%1"
-
-#: html/Ticket/ModifyDates.html:34
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "Malkovich Malkovich # %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 "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Global/GroupRights.html:32
-msgid "Modify global group rights."
-msgstr "Malkovich Malkovich Malkovich."
-
-#: html/Admin/Global/UserRights.html:24 html/Admin/Global/UserRights.html:27 html/Admin/Global/index.html:59
-msgid "Modify global user rights"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Global/UserRights.html:32
-msgid "Modify global user rights."
-msgstr "Malkovich Malkovich."
-
-#: lib/RT/Group_Overlay.pm:146
-msgid "Modify group metadata or delete group"
-msgstr "Malkovich Malkovich or Malkovich"
-
-#: 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 "Malkovich Malkovich Malkovich %1"
-
-#: html/Admin/Queues/GroupRights.html:24 html/Admin/Queues/GroupRights.html:28
-#. ($QueueObj->Name)
-msgid "Modify group rights for queue %1"
-msgstr "Malkovich Malkovich Malkovich %1"
-
-#: lib/RT/Group_Overlay.pm:148
-msgid "Modify membership roster for this group"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/System.pm:60
-msgid "Modify one's own RT account"
-msgstr "Malkovich's own RT Malkovich"
-
-#: html/Admin/Queues/People.html:24 html/Admin/Queues/People.html:28
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "Malkovich Malkovich to Malkovich %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 "Malkovich Malkovich to Malkovich #%1"
-
-#: html/Admin/Queues/Scrips.html:45
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Admin/Global/Scrips.html:43 html/Admin/Global/index.html:41
-msgid "Modify scrips which apply to all queues"
-msgstr "Malkovich Malkovich to Malkovich"
-
-#: 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 "Malkovich %1"
-
-#: html/Admin/Global/Templates.html:43
-msgid "Modify templates which apply to all queues"
-msgstr "Malkovich Malkovich Malkovich to Malkovich"
-
-#: html/Admin/Groups/Modify.html:86 html/User/Groups/Modify.html:85
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "Malkovich the Malkovich %1"
-
-#: lib/RT/Queue_Overlay.pm:72
-msgid "Modify the queue watchers"
-msgstr "Malkovich the Malkovich"
-
-#: html/Admin/Users/Modify.html:263
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "Malkovich the user %1"
-
-#: html/Ticket/ModifyAll.html:36
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "Malkovich # %1"
-
-#: html/Ticket/Modify.html:24 html/Ticket/Modify.html:27 html/Ticket/Modify.html:33
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "Malkovich #%1"
-
-#: lib/RT/Queue_Overlay.pm:90
-msgid "Modify tickets"
-msgstr "Malkovich"
-
-#: 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 "Malkovich Malkovich Malkovich %1"
-
-#: html/Admin/Queues/UserRights.html:24 html/Admin/Queues/UserRights.html:28
-#. ($QueueObj->Name)
-msgid "Modify user rights for queue %1"
-msgstr "Malkovich Malkovich Malkovich %1"
-
-#: lib/RT/Queue_Overlay.pm:71
-msgid "ModifyACL"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:149
-msgid "ModifyOwnMembership"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:72
-msgid "ModifyQueueWatchers"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:77
-msgid "ModifyScrips"
-msgstr "Malkovich"
-
-#: lib/RT/System.pm:60
-msgid "ModifySelf"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:74
-msgid "ModifyTemplate"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:90
-msgid "ModifyTicket"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:388
-msgid "Mon."
-msgstr "Mon."
-
-#: html/Ticket/Elements/ShowRequestor:40
-#. ($name)
-msgid "More about %1"
-msgstr "Malkovich %1"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Move down"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:26
-msgid "Multiple"
-msgstr "Malkovich"
-
-#: lib/RT/User_Overlay.pm:197
-msgid "Must specify 'Name' attribute"
-msgstr "Malkovich 'Name' Malkovich"
-
-#: html/SelfService/Elements/MyRequests:48
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "My %1 Malkovich"
-
-#: html/Approvals/index.html:24 html/Approvals/index.html:25
-msgid "My approvals"
-msgstr "My Malkovich"
-
-#: html/Admin/Elements/AddCustomFieldValue:31 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/ModifyTemplate:27 html/Admin/Groups/Modify.html:43 html/Elements/SelectGroups:25 html/Elements/SelectUsers:27 html/User/Groups/Modify.html:43
-msgid "Name"
-msgstr "Name"
-
-#: lib/RT/User_Overlay.pm:204
-msgid "Name in use"
-msgstr "Name in use"
-
-#: html/Ticket/Elements/ShowDates:52
-msgid "Never"
-msgstr "Malkovich"
-
-#: html/Elements/Quicksearch:29
-msgid "New"
-msgstr "New"
-
-#: html/Elements/EditLinks:93
-msgid "New Links"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Users/Modify.html:92 html/User/Prefs.html:87
-msgid "New Password"
-msgstr "Malkovich"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "New Search"
-msgstr "Malkovich"
-
-#: 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 "Malkovich Malkovich"
-
-#: html/Admin/Elements/GroupTabs:53 html/User/Elements/GroupTabs:51
-msgid "New group"
-msgstr "Malkovich"
-
-#: html/SelfService/Prefs.html:31
-msgid "New password"
-msgstr "Malkovich"
-
-#: lib/RT/User_Overlay.pm:773
-msgid "New password notification sent"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Elements/QueueTabs:69
-msgid "New queue"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectRights:41
-msgid "New rights"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: 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 "Malkovich"
-
-#: html/SelfService/Elements/Tabs:47
-msgid "New ticket"
-msgstr "Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2700
-msgid "New ticket doesn't exist"
-msgstr "Malkovich doesn't Malkovich"
-
-#: html/Admin/Elements/UserTabs:50
-msgid "New user"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/CreateUserCalled:25
-msgid "New user called"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/People.html:54 html/Ticket/Elements/EditPeople:28
-msgid "New watchers"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "New window setting"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/Elements/Tabs:70
-msgid "Next"
-msgstr "Next"
-
-#: NOT FOUND IN SOURCE
-msgid "NickName"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:62 html/User/Prefs.html:50
-msgid "Nickname"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:89 html/Admin/Elements/EditCustomFields:103
-msgid "No CustomField"
-msgstr "No Malkovich"
-
-#: html/Admin/Groups/GroupRights.html:83 html/Admin/Groups/UserRights.html:70
-msgid "No Group defined"
-msgstr "No Malkovich"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:452
-msgid "No Query"
-msgstr "No Malkovich"
-
-#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:67
-msgid "No Queue defined"
-msgstr "No Malkovich"
-
-#: bin/rt-crontool:52
-msgid "No RT user found. Please consult your RT administrator.\\n"
-msgstr "No RT Malkovich. Malkovich Malkovich RT Malkovich.\\n"
-
-#: html/Admin/Global/Template.html:78 html/Admin/Queues/Template.html:75
-msgid "No Template"
-msgstr "No Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "No Ticket specified. Aborting ticket "
-msgstr "No Malkovich Malkovich. Malkovich "
-
-#: html/Approvals/Elements/Approve:45
-msgid "No action"
-msgstr "No Malkovich"
-
-#: lib/RT/Record.pm:744
-msgid "No column specified"
-msgstr "No Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowRequestor:46
-msgid "No comment entered about this user"
-msgstr "No Malkovich Malkovich Malkovich"
-
-#: lib/RT/Action/Generic.pm:159 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 "No Malkovich %1"
-
-#: lib/RT/Users_Overlay.pm:159
-msgid "No group specified"
-msgstr "No Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2475
-msgid "No message attached"
-msgstr "No Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:991
-msgid "No password set"
-msgstr "No Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:296
-msgid "No permission to create queues"
-msgstr "No Malkovich to Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "No permission to create tickets in the queue '%1'"
-msgstr "No Malkovich to Malkovich in the Malkovich '%1'"
-
-#: lib/RT/User_Overlay.pm:157
-msgid "No permission to create users"
-msgstr "No Malkovich to Malkovich"
-
-#: html/SelfService/Display.html:125
-msgid "No permission to display that ticket"
-msgstr "No Malkovich to Malkovich Malkovich"
-
-#: html/SelfService/Update.html:68
-msgid "No permission to view update ticket"
-msgstr "No Malkovich to Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:716 lib/RT/Ticket_Overlay.pm:1581
-msgid "No principal specified"
-msgstr "No Malkovich Malkovich"
-
-#: html/Admin/Queues/People.html:153 html/Admin/Queues/People.html:163
-msgid "No principals selected."
-msgstr "No Malkovich Malkovich."
-
-#: html/Admin/Queues/index.html:34
-msgid "No queues matching search criteria found."
-msgstr "No Malkovich Malkovich Malkovich Malkovich."
-
-#: html/Admin/Elements/SelectRights:81
-msgid "No rights found"
-msgstr "No Malkovich"
-
-#: html/Admin/Elements/SelectRights:32
-msgid "No rights granted."
-msgstr "No Malkovich."
-
-#: html/Search/Bulk.html:162
-msgid "No search to operate on."
-msgstr "No Malkovich to Malkovich on."
-
-#: lib/RT/Transaction_Overlay.pm:455 lib/RT/Transaction_Overlay.pm:493
-msgid "No transaction type specified"
-msgstr "No Malkovich Malkovich Malkovich"
-
-#: html/Admin/Users/index.html:35
-msgid "No users matching search criteria found."
-msgstr "No Malkovich Malkovich Malkovich Malkovich."
-
-#: NOT FOUND IN SOURCE
-msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
-msgstr "No Malkovich RT Malkovich. RT Malkovich Malkovich. Malkovich Malkovich RT Malkovich.\\n"
-
-#: lib/RT/Record.pm:741
-msgid "No value sent to _Set!\\n"
-msgstr "No Malkovich to _Set!\\n"
-
-#: lib/RT/Record.pm:746
-msgid "Nonexistant field?"
-msgstr "Malkovich Malkovich?"
-
-#: html/Elements/Header:71
-msgid "Not logged in."
-msgstr "Malkovich in."
-
-#: lib/RT/Date.pm:369
-msgid "Not set"
-msgstr "Malkovich"
-
-#: html/NoAuth/Reminder.html:26
-msgid "Not yet implemented."
-msgstr "Malkovich Malkovich."
-
-#: html/Approvals/Elements/Approve:48
-msgid "Notes"
-msgstr "Malkovich"
-
-#: lib/RT/User_Overlay.pm:776
-msgid "Notification could not be sent"
-msgstr "Malkovich Malkovich be sent"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr "Malkovich"
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "Malkovich as Malkovich"
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr "Malkovich Malkovich"
-
-#: etc/initialdata:124
-msgid "Notify Other Recipients as Comment"
-msgstr "Malkovich Malkovich as Malkovich"
-
-#: etc/initialdata:85
-msgid "Notify Owner"
-msgstr "Malkovich"
-
-#: etc/initialdata:81
-msgid "Notify Owner as Comment"
-msgstr "Malkovich as Malkovich"
-
-#: etc/initialdata:376
-msgid "Notify Owner of their rejected ticket"
-msgstr "Malkovich of Malkovich Malkovich"
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr "Malkovich of Malkovich Malkovich Malkovich by Malkovich"
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr "Malkovich of Malkovich Malkovich Malkovich by Malkovich"
-
-#: etc/initialdata:334
-msgid "Notify Owners and AdminCcs of new items pending their approval"
-msgstr "Malkovich and Malkovich of Malkovich Malkovich Malkovich"
-
-#: etc/initialdata:77
-msgid "Notify Requestors"
-msgstr "Malkovich Malkovich"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "Malkovich Malkovich and Ccs"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "Malkovich Malkovich and Ccs as Malkovich"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "Malkovich Malkovich, Ccs and Malkovich"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "Malkovich Malkovich, Ccs and Malkovich as Malkovich"
-
-#: lib/RT/Date.pm:421
-msgid "Nov."
-msgstr "Nov."
-
-#: lib/RT/Record.pm:200
-msgid "Object could not be created"
-msgstr "Malkovich Malkovich be Malkovich"
-
-#: lib/RT/Record.pm:219
-msgid "Object created"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:420
-msgid "Oct."
-msgstr "Oct."
-
-#: html/Elements/SelectDateRelation:34
-msgid "On"
-msgstr "On"
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "On Malkovich"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "On Malkovich"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "On Malkovich"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "On Malkovich"
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "On Malkovich"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "On Malkovich"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "On Malkovich"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "On Malkovich"
-
-#: 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 "Malkovich Malkovich Malkovich Malkovich Malkovich %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 "Malkovich Malkovich Malkovich Malkovich Malkovich %1"
-
-#: html/Elements/Quicksearch:30
-msgid "Open"
-msgstr "Open"
-
-#: html/Ticket/Elements/Tabs:137
-msgid "Open it"
-msgstr "Open it"
-
-#: html/SelfService/Elements/Tabs:41
-msgid "Open tickets"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in a new window"
-msgstr "Malkovich (Malkovich) in a Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in another window"
-msgstr "Malkovich (Malkovich) in Malkovich"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "Malkovich on Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Ordering and sorting"
-msgstr "Malkovich and Malkovich"
-
-#: html/Admin/Users/Modify.html:114 html/Elements/SelectUsers:28 html/User/Prefs.html:107
-msgid "Organization"
-msgstr "Malkovich"
-
-#: html/Approvals/Elements/Approve:32
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr "Malkovich Malkovich: #%1"
-
-#: html/Admin/Queues/Modify.html:68
-msgid "Over time, priority moves toward"
-msgstr "Malkovich, Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:89
-msgid "Own tickets"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:89
-msgid "OwnTicket"
-msgstr "Malkovich"
-
-#: etc/initialdata:38 html/Elements/QuickCreate:13 html/Search/Elements/PickBasics:114 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:40 lib/RT/ACE_Overlay.pm:85 lib/RT/Tickets_Overlay.pm:1306
-msgid "Owner"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:575
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "Malkovich Malkovich Malkovich %1 to %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner is"
-msgstr "Malkovich is"
-
-#: html/Admin/Users/Modify.html:171 html/User/Prefs.html:75
-msgid "Pager"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "PagerPhone"
-msgstr "Malkovich"
-
-#: html/Elements/EditLinks:117 html/Elements/EditLinks:54 html/Elements/ShowLinks:46 html/Ticket/Create.html:182 html/Ticket/Elements/BulkLinks:38
-msgid "Parents"
-msgstr "Malkovich"
-
-#: html/Elements/Login:52 html/User/Prefs.html:83
-msgid "Password"
-msgstr "Malkovich"
-
-#: html/NoAuth/Reminder.html:24
-msgid "Password Reminder"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:185 lib/RT/User_Overlay.pm:994
-msgid "Password too short"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Users/Modify.html:316 html/User/Prefs.html:209
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "Malkovich: %1"
-
-#: html/Admin/Users/Modify.html:318
-msgid "Passwords do not match."
-msgstr "Malkovich do Malkovich."
-
-#: html/User/Prefs.html:211
-msgid "Passwords do not match. Your password has not been changed"
-msgstr "Malkovich do Malkovich. Malkovich Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowSummary:44 html/Ticket/Elements/Tabs:97 html/Ticket/ModifyAll.html:50
-msgid "People"
-msgstr "Malkovich"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr "Malkovich a user-Malkovich"
-
-#: 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/Attribute_Overlay.pm:135 lib/RT/Attribute_Overlay.pm:141 lib/RT/Attribute_Overlay.pm:379 lib/RT/Attribute_Overlay.pm:388 lib/RT/Attribute_Overlay.pm:401 lib/RT/CurrentUser.pm:103 lib/RT/CurrentUser.pm:94 lib/RT/CustomField_Overlay.pm:100 lib/RT/CustomField_Overlay.pm:207 lib/RT/CustomField_Overlay.pm:239 lib/RT/CustomField_Overlay.pm:517 lib/RT/CustomField_Overlay.pm:90 lib/RT/Group_Overlay.pm:1091 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1104 lib/RT/Group_Overlay.pm:1155 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1165 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:896 lib/RT/Group_Overlay.pm:900 lib/RT/Group_Overlay.pm:913 lib/RT/Queue_Overlay.pm:117 lib/RT/Queue_Overlay.pm:135 lib/RT/Queue_Overlay.pm:578 lib/RT/Queue_Overlay.pm:588 lib/RT/Queue_Overlay.pm:602 lib/RT/Queue_Overlay.pm:740 lib/RT/Queue_Overlay.pm:749 lib/RT/Queue_Overlay.pm:762 lib/RT/Queue_Overlay.pm:975 lib/RT/Scrip_Overlay.pm:125 lib/RT/Scrip_Overlay.pm:136 lib/RT/Scrip_Overlay.pm:201 lib/RT/Scrip_Overlay.pm:473 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:87 lib/RT/Template_Overlay.pm:93 lib/RT/Ticket_Overlay.pm:1453 lib/RT/Ticket_Overlay.pm:1463 lib/RT/Ticket_Overlay.pm:1477 lib/RT/Ticket_Overlay.pm:1614 lib/RT/Ticket_Overlay.pm:1624 lib/RT/Ticket_Overlay.pm:1638 lib/RT/Ticket_Overlay.pm:1755 lib/RT/Ticket_Overlay.pm:2075 lib/RT/Ticket_Overlay.pm:2213 lib/RT/Ticket_Overlay.pm:2381 lib/RT/Ticket_Overlay.pm:2428 lib/RT/Ticket_Overlay.pm:2582 lib/RT/Ticket_Overlay.pm:2640 lib/RT/Ticket_Overlay.pm:2691 lib/RT/Ticket_Overlay.pm:2706 lib/RT/Ticket_Overlay.pm:2905 lib/RT/Ticket_Overlay.pm:2915 lib/RT/Ticket_Overlay.pm:2920 lib/RT/Ticket_Overlay.pm:3143 lib/RT/Ticket_Overlay.pm:3147 lib/RT/Ticket_Overlay.pm:3350 lib/RT/Ticket_Overlay.pm:3512 lib/RT/Ticket_Overlay.pm:3564 lib/RT/Ticket_Overlay.pm:3791 lib/RT/Transaction_Overlay.pm:443 lib/RT/Transaction_Overlay.pm:450 lib/RT/Transaction_Overlay.pm:479 lib/RT/Transaction_Overlay.pm:486 lib/RT/User_Overlay.pm:1088 lib/RT/User_Overlay.pm:1536 lib/RT/User_Overlay.pm:335 lib/RT/User_Overlay.pm:696 lib/RT/User_Overlay.pm:731 lib/RT/User_Overlay.pm:987
-msgid "Permission Denied"
-msgstr "Malkovich Malkovich"
-
-#: html/User/Elements/Tabs:34
-msgid "Personal Groups"
-msgstr "Malkovich"
-
-#: html/User/Groups/index.html:29 html/User/Groups/index.html:39
-msgid "Personal groups"
-msgstr "Malkovich"
-
-#: html/User/Elements/DelegateRights:36
-msgid "Personal groups:"
-msgstr "Malkovich:"
-
-#: html/Admin/Users/Modify.html:153 html/User/Prefs.html:60
-msgid "Phone numbers"
-msgstr "Malkovich"
-
-#: html/Elements/Header:63 html/Elements/Tabs:55 html/SelfService/Elements/Tabs:50 html/SelfService/Prefs.html:24 html/User/Prefs.html:24 html/User/Prefs.html:27
-msgid "Preferences"
-msgstr "Malkovich"
-
-#: lib/RT/Action/Generic.pm:169
-msgid "Prepare Stubbed"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/Tabs:62
-msgid "Prev"
-msgstr "Prev"
-
-#: 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 "Malkovich %1 Malkovich."
-
-#: html/Search/Elements/PickBasics:58 html/Ticket/Create.html:153 html/Ticket/Elements/EditBasics:52 html/Ticket/Elements/ShowBasics:50 lib/RT/Tickets_Overlay.pm:1104
-msgid "Priority"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/Modify.html:64
-msgid "Priority starts at"
-msgstr "Malkovich at"
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:296 html/User/Prefs.html:200
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr "Malkovich Malkovich: %1"
-
-#: html/Admin/Users/index.html:61
-msgid "Privileged users"
-msgstr "Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Projects"
-msgstr "Malkovich"
-
-#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
-msgid "Pseudogroup for internal use"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Elements/QuickCreate:10 html/Elements/Quicksearch:28 html/Search/Elements/PickBasics:94 html/SelfService/Create.html:32 html/Ticket/Create.html:37 html/Ticket/Elements/EditBasics:35 html/Ticket/Elements/ShowBasics:54 html/User/Elements/DelegateRights:79 lib/RT/Tickets_Overlay.pm:945
-msgid "Queue"
-msgstr "Malkovich"
-
-#: 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 "Malkovich %1 Malkovich"
-
-#: html/Admin/Queues/Modify.html:42
-msgid "Queue Name"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:300
-msgid "Queue already exists"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:309 lib/RT/Queue_Overlay.pm:315
-msgid "Queue could not be created"
-msgstr "Malkovich not be Malkovich"
-
-#: html/Ticket/Create.html:208
-msgid "Queue could not be loaded."
-msgstr "Malkovich be Malkovich."
-
-#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:319 lib/RT/StyleGuide.pod:789
-msgid "Queue created"
-msgstr "Malkovich"
-
-#: html/SelfService/Display.html:72 lib/RT/CustomField_Overlay.pm:97
-msgid "Queue not found"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/Tabs:37 html/Admin/index.html:34
-msgid "Queues"
-msgstr "Malkovich"
-
-#: html/Elements/Quicksearch:24
-msgid "Quick search"
-msgstr "Malkovich"
-
-#: 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 "RT %1 for %2"
-
-#: NOT FOUND IN SOURCE
-msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
-msgstr "RT %1 from <a href=\"http://Malkovich.com\">Malkovich Malkovich, LLC</a>."
-
-#: html/Admin/index.html:24 html/Admin/index.html:25
-msgid "RT Administration"
-msgstr "RT Malkovich"
-
-#: html/Elements/Error:41 html/SelfService/Error.html:40
-msgid "RT Error"
-msgstr "RT Malkovich"
-
-#: html/index.html:50 html/index.html:53
-msgid "RT at a glance"
-msgstr "RT at a Malkovich"
-
-#: html/Elements/PageLayout:85
-#. ($RT::rtname)
-msgid "RT for %1"
-msgstr "RT for %1"
-
-#: NOT FOUND IN SOURCE
-msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent <jesse@bestpractical.com>. 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; Malkovich 1996-%1 Malkovich <Malkovich@Malkovich.com>. It is Malkovich Malkovich <a href=\"http://www.gnu.org/copyleft/gpl.html\">Malkovich 2 of the Malkovich Malkovich Malkovich.</a>"
-
-#: html/Admin/Users/Modify.html:57 html/User/Prefs.html:47
-msgid "Real Name"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "RealName"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:628
-#. ($value)
-msgid "Reference by %1 added"
-msgstr "Malkovich by %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:668
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr "Malkovich by %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:625
-#. ($value)
-msgid "Reference to %1 added"
-msgstr "Malkovich to %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:665
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr "Malkovich to %1 Malkovich"
-
-#: html/Elements/EditLinks:129 html/Elements/EditLinks:81 html/Elements/ShowLinks:70 html/Ticket/Create.html:185 html/Ticket/Elements/BulkLinks:50
-msgid "Referred to by"
-msgstr "Malkovich to by"
-
-#: html/Elements/EditLinks:125 html/Elements/EditLinks:72 html/Elements/SelectLinkType:27 html/Elements/ShowLinks:60 html/Ticket/Create.html:184 html/Ticket/Elements/BulkLinks:46
-msgid "Refers to"
-msgstr "Malkovich to"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine search"
-msgstr "Malkovich"
-
-#: html/Elements/Refresh:35
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "Malkovich Malkovich %1 Malkovich."
-
-#: html/Search/Bulk.html:95
-msgid "Remove AdminCc"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:91
-msgid "Remove Cc"
-msgstr "Malkovich Cc"
-
-#: html/Search/Bulk.html:87
-msgid "Remove Requestor"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/Elements/ShowTransaction:142 html/Ticket/Elements/Tabs:123
-msgid "Reply"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "Reply to tickets"
-msgstr "Malkovich to Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "ReplyToTicket"
-msgstr "Malkovich"
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:86
-msgid "Requestor"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor email address"
-msgstr "Malkovich Malkovich"
-
-#: html/SelfService/Create.html:40 html/Ticket/Create.html:55 html/Ticket/Elements/EditPeople:47 html/Ticket/Elements/ShowPeople:30
-msgid "Requestors"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/Modify.html:74
-msgid "Requests should be due in"
-msgstr "Malkovich be due in"
-
-#: html/Elements/Submit:61
-msgid "Reset"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:63
-msgid "Residence"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/Tabs:133
-msgid "Resolve"
-msgstr "Malkovich"
-
-#: html/Ticket/Update.html:119
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "Malkovich #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:27 lib/RT/Ticket_Overlay.pm:1282
-msgid "Resolved"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Response to requestors"
-msgstr "Malkovich to Malkovich"
-
-#: html/Elements/ListActions:25 html/Search/Elements/NewListActions:25
-msgid "Results"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Results per page"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Users/Modify.html:99 html/User/Prefs.html:94
-msgid "Retype Password"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:612
-msgid "Right Delegated"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:302
-msgid "Right Granted"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:160
-msgid "Right Loaded"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:677 lib/RT/ACE_Overlay.pm:692
-msgid "Right could not be revoked"
-msgstr "Malkovich be Malkovich"
-
-#: html/User/Delegation.html:63
-msgid "Right not found"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:542 lib/RT/ACE_Overlay.pm:637
-msgid "Right not loaded."
-msgstr "Malkovich Malkovich."
-
-#: lib/RT/ACE_Overlay.pm:688
-msgid "Right revoked"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Rights"
-msgstr "Malkovich"
-
-#: lib/RT/Interface/Web.pm:869
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr "Malkovich Malkovich be Malkovich %1"
-
-#: lib/RT/Interface/Web.pm:899
-#. ($object_type)
-msgid "Rights could not be revoked for %1"
-msgstr "Malkovich Malkovich be Malkovich %1"
-
-#: html/Admin/Global/GroupRights.html:50 html/Admin/Queues/GroupRights.html:52
-msgid "Roles"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:393
-msgid "Sat."
-msgstr "Sat."
-
-#: html/Admin/Global/Template.html:45 html/Admin/Queues/Modify.html:89 html/Admin/Queues/People.html:104 html/Admin/Users/Modify.html:198 html/SelfService/Prefs.html:36 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyDates.html:38 html/Ticket/ModifyLinks.html:38 html/Ticket/ModifyPeople.html:37
-msgid "Save Changes"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/PreviewScrips:79
-msgid "Save changes"
-msgstr "Malkovich"
-
-#: html/Admin/Global/Scrip.html:48 html/Admin/Queues/Scrip.html:54
-#. ($id)
-#. ($ARGS{'id'})
-msgid "Scrip #%1"
-msgstr "Malkovich #%1"
-
-#: lib/RT/Scrip_Overlay.pm:180
-msgid "Scrip Created"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrips:85
-msgid "Scrip deleted"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/QueueTabs:45 html/Admin/Elements/SystemTabs:32 html/Admin/Global/index.html:40
-msgid "Scrips"
-msgstr "Malkovich"
-
-#: html/Admin/Queues/Scrips.html:33
-msgid "Scrips which apply to all queues"
-msgstr "Malkovich Malkovich to Malkovich"
-
-#: html/Elements/SimpleSearch:26 html/Search/Elements/DisplayOptions:73
-msgid "Search"
-msgstr "Malkovich"
-
-#: html/Approvals/Elements/PendingMyApproval:38
-msgid "Search for approvals"
-msgstr "Malkovich Malkovich"
-
-#: bin/rt-crontool:184
-msgid "Security:"
-msgstr "Malkovich:"
-
-#: lib/RT/Queue_Overlay.pm:68
-msgid "SeeQueue"
-msgstr "Malkovich"
-
-#: html/Admin/Groups/index.html:50
-msgid "Select a group"
-msgstr "Malkovich a Malkovich"
-
-#: html/Admin/Users/index.html:24 html/Admin/Users/index.html:27
-msgid "Select a user"
-msgstr "Malkovich a user"
-
-#: html/Admin/Global/CustomField.html:37 html/Admin/Global/CustomFields.html:35
-msgid "Select custom field"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Elements/GroupTabs:51 html/User/Elements/GroupTabs:49
-msgid "Select group"
-msgstr "Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:427
-msgid "Select multiple values"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:424
-msgid "Select one value"
-msgstr "Malkovich Malkovich"
-
-#: html/Admin/Elements/QueueTabs:66
-msgid "Select queue"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: 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 "Malkovich"
-
-#: html/Admin/Elements/UserTabs:46
-msgid "Select user"
-msgstr "Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:35
-msgid "SelectMultiple"
-msgstr "Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:34
-msgid "SelectSingle"
-msgstr "Malkovich"
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "Malkovich to Malkovich"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "Malkovich to Malkovich as a \"Malkovich\""
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr "Malkovich to Malkovich and Ccs"
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr "Malkovich to Malkovich and Ccs as a Malkovich"
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr "Malkovich a Malkovich to the Malkovich"
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr "Malkovich to Malkovich Malkovich and Bccs"
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr "Malkovich to the Malkovich Malkovich"
-
-#: etc/initialdata:98
-msgid "Sends mail to the administrative Ccs as a comment"
-msgstr "Malkovich to the Malkovich Malkovich as a Malkovich"
-
-#: etc/initialdata:82 etc/initialdata:86
-msgid "Sends mail to the owner"
-msgstr "Malkovich to the Malkovich"
-
-#: lib/RT/Date.pm:419
-msgid "Sep."
-msgstr "Sep."
-
-#: html/Approvals/Elements/PendingMyApproval:43
-msgid "Show approved requests"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
-msgid "Show basics"
-msgstr "Malkovich"
-
-#: html/Approvals/Elements/PendingMyApproval:44
-msgid "Show denied requests"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
-msgid "Show details"
-msgstr "Malkovich"
-
-#: html/Approvals/Elements/PendingMyApproval:42
-msgid "Show pending requests"
-msgstr "Malkovich Malkovich"
-
-#: html/Approvals/Elements/PendingMyApproval:45
-msgid "Show requests awaiting other approvals"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket private commentary"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket summaries"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:70
-msgid "ShowACL"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:79
-msgid "ShowScrips"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:76
-msgid "ShowTemplate"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:80
-msgid "ShowTicket"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:81
-msgid "ShowTicketComments"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "Sign up as a ticket Requestor or ticket or queue Cc"
-msgstr "Sign up as a Malkovich Malkovich or Malkovich or Malkovich Cc"
-
-#: lib/RT/Queue_Overlay.pm:85
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr "Sign up as a Malkovich or Malkovich"
-
-#: html/Admin/Users/Modify.html:188 html/User/Prefs.html:145
-msgid "Signature"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:25
-msgid "Single"
-msgstr "Malkovich"
-
-#: html/Elements/Header:62
-msgid "Skip Menu"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/AddCustomFieldValue:27
-msgid "Sort"
-msgstr "Sort"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort results by"
-msgstr "Malkovich by"
-
-#: NOT FOUND IN SOURCE
-msgid "Squelched message recipients"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/Admin/Elements/EditScrip:65
-msgid "Stage"
-msgstr "Malkovich"
-
-#: html/Elements/SelectDateType:26 html/Ticket/Elements/EditDates:31 html/Ticket/Elements/ShowDates:35
-msgid "Started"
-msgstr "Malkovich"
-
-#: html/Elements/SelectDateType:30 html/Ticket/Create.html:165 html/Ticket/Elements/EditDates:26 html/Ticket/Elements/ShowDates:31
-msgid "Starts"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:135 html/User/Prefs.html:123
-msgid "State"
-msgstr "Malkovich"
-
-#: html/Search/Elements/PickBasics:77 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:30 html/Ticket/Create.html:41 html/Ticket/Elements/EditBasics:31 html/Ticket/Elements/ShowBasics:30 html/Ticket/Update.html:37 lib/RT/Ticket_Overlay.pm:1276 lib/RT/Tickets_Overlay.pm:970
-msgid "Status"
-msgstr "Malkovich"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:505
-#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
-msgid "Status changed from %1 to %2"
-msgstr "Malkovich Malkovich %1 to %2"
-
-#: html/Ticket/Elements/Tabs:148
-msgid "Steal"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "Steal tickets"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "StealTicket"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:581
-#. ($Old->Name)
-msgid "Stolen from %1 "
-msgstr "Malkovich %1 "
-
-#: html/Elements/QuickCreate:7 html/Elements/SelectAttachmentField:25 html/Search/Bulk.html:133 html/SelfService/Create.html:56 html/SelfService/Elements/MyRequests:27 html/SelfService/Update.html:31 html/Ticket/Create.html:83 html/Ticket/Elements/EditBasics:26 html/Ticket/ModifyAll.html:78 html/Ticket/Update.html:58 lib/RT/Ticket_Overlay.pm:1272 lib/RT/Tickets_Overlay.pm:1049
-msgid "Subject"
-msgstr "Malkovich"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:795 lib/RT/Transaction_Overlay.pm:603
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "Malkovich to %1"
-
-#: html/Elements/Submit:58
-msgid "Submit"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:749
-msgid "Succeeded"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:394
-msgid "Sun."
-msgstr "Sun."
-
-#: lib/RT/System.pm:53
-msgid "SuperUser"
-msgstr "Malkovich"
-
-#: html/User/Elements/DelegateRights:76
-msgid "System"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:566 lib/RT/Interface/Web.pm:868 lib/RT/Interface/Web.pm:898
-msgid "System Error"
-msgstr "Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:615
-msgid "System error. Right not delegated."
-msgstr "Malkovich. Malkovich Malkovich."
-
-#: 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 "Malkovich. Malkovich Malkovich."
-
-#: html/Admin/Global/GroupRights.html:34 html/Admin/Groups/GroupRights.html:36 html/Admin/Queues/GroupRights.html:35
-msgid "System groups"
-msgstr "Malkovich"
-
-#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
-msgid "SystemRolegroup for internal use"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/CurrentUser.pm:334
-msgid "TEST_STRING"
-msgstr "TEST_MALKOVICH"
-
-#: html/Elements/MyRequests:27 html/Ticket/Elements/Tabs:144
-msgid "Take"
-msgstr "Take"
-
-#: lib/RT/Queue_Overlay.pm:92
-msgid "Take tickets"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:92
-msgid "TakeTicket"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:566
-msgid "Taken"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditScrip:57 html/Tools/Offline.html:56
-msgid "Template"
-msgstr "Malkovich"
-
-#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:89
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "Malkovich #%1"
-
-#: html/Admin/Elements/EditTemplates:88
-msgid "Template deleted"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Scrip_Overlay.pm:156
-msgid "Template not found"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Template_Overlay.pm:348
-msgid "Template parsed"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/QueueTabs:48 html/Admin/Elements/SystemTabs:35 html/Admin/Global/index.html:44
-msgid "Templates"
-msgstr "Malkovich"
-
-#: lib/RT/Record.pm:740
-msgid "That is already the current value"
-msgstr "That is Malkovich the Malkovich"
-
-#: lib/RT/CustomField_Overlay.pm:248
-msgid "That is not a value for this custom field"
-msgstr "That is not a Malkovich Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2086
-msgid "That is the same value"
-msgstr "That is the Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:287 lib/RT/ACE_Overlay.pm:596
-msgid "That principal already has that right"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:674
-#. ($args{'Type'})
-msgid "That principal is already a %1 for this queue"
-msgstr "Malkovich is Malkovich a %1 Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1527
-#. ($self->loc($args{'Type'}))
-msgid "That principal is already a %1 for this ticket"
-msgstr "Malkovich is Malkovich a %1 Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:773
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr "That Malkovich is not a %1 Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2082
-msgid "That queue does not exist"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3152
-msgid "That ticket has unresolved dependencies"
-msgstr "Malkovich Malkovich Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2956
-msgid "That user already owns that ticket"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2928
-msgid "That user does not exist"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:355
-msgid "That user is already privileged"
-msgstr "Malkovich is Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:376
-msgid "That user is already unprivileged"
-msgstr "Malkovich is Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:368
-msgid "That user is now privileged"
-msgstr "Malkovich is Malkovich"
-
-#: lib/RT/User_Overlay.pm:389
-msgid "That user is now unprivileged"
-msgstr "Malkovich is Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:2949
-msgid "That user may not own tickets in that queue"
-msgstr "Malkovich Malkovich Malkovich in Malkovich"
-
-#: lib/RT/Link_Overlay.pm:200
-msgid "That's not a numerical id"
-msgstr "That's not a Malkovich id"
-
-#: html/SelfService/Display.html:31 html/Ticket/Create.html:149 html/Ticket/Elements/ShowSummary:27
-msgid "The Basics"
-msgstr "The Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:87
-msgid "The CC of a ticket"
-msgstr "The CC of a Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:88
-msgid "The administrative CC of a ticket"
-msgstr "The Malkovich CC of a Malkovich"
-
-#: bin/rt-crontool:194
-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 Malkovich Malkovich Malkovich Malkovich Malkovich in the Malkovich 'Malkovich' and Malkovich Malkovich to 99 if they haven't Malkovich in 4 Malkovich:"
-
-#: NOT FOUND IN SOURCE
-msgid "The following commands were not proccessed:\\n\\n"
-msgstr "The Malkovich Malkovich Malkovich Malkovich:\\n\\n"
-
-#: lib/RT/Record.pm:743
-msgid "The new value has been set."
-msgstr "The Malkovich Malkovich."
-
-#: lib/RT/ACE_Overlay.pm:85
-msgid "The owner of a ticket"
-msgstr "The Malkovich of a Malkovich"
-
-#: lib/RT/ACE_Overlay.pm:86
-msgid "The requestor of a ticket"
-msgstr "The Malkovich of a Malkovich"
-
-#: html/Admin/Elements/EditUserComments:25
-msgid "These comments aren't generally visible to the user"
-msgstr "Malkovich aren't Malkovich Malkovich to the user"
-
-#: bin/rt-crontool:185
-msgid "This tool allows the user to run arbitrary perl modules from within RT."
-msgstr "Malkovich Malkovich the user to Malkovich Malkovich Malkovich Malkovich RT."
-
-#: lib/RT/Transaction_Overlay.pm:226
-msgid "This transaction appears to have no content"
-msgstr "Malkovich Malkovich to have no Malkovich"
-
-#: html/Ticket/Elements/ShowRequestor:48
-#. ($rows)
-msgid "This user's %1 highest priority tickets"
-msgstr "Malkovich's %1 Malkovich Malkovich"
-
-#: lib/RT/Date.pm:391
-msgid "Thu."
-msgstr "Thu."
-
-#: html/Ticket/ModifyAll.html:24 html/Ticket/ModifyAll.html:28
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr "Malkovich #%1 Malkovich: %2"
-
-#: html/Approvals/Elements/ShowDependency:45
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "Malkovich #%1: %2"
-
-#: lib/RT/Ticket_Overlay.pm:696 lib/RT/Ticket_Overlay.pm:720
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "Malkovich %1 Malkovich in Malkovich '%2'"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket %1 loaded\\n"
-msgstr "Malkovich %1 Malkovich\\n"
-
-#: html/Search/Bulk.html:216
-#. ($Ticket->Id,$_)
-msgid "Ticket %1: %2"
-msgstr "Malkovich %1: %2"
-
-#: html/Ticket/History.html:24 html/Ticket/History.html:27
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "Malkovich # %1 %2"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket attachment"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1228
-msgid "Ticket content"
-msgstr "Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1274
-msgid "Ticket content type"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:565 lib/RT/Ticket_Overlay.pm:579 lib/RT/Ticket_Overlay.pm:590 lib/RT/Ticket_Overlay.pm:707
-msgid "Ticket could not be created due to an internal error"
-msgstr "Malkovich Malkovich be Malkovich to a Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:497
-msgid "Ticket created"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:502
-msgid "Ticket deleted"
-msgstr "Malkovich"
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "Malkovich Malkovich"
-
-#: html/Elements/Tabs:46
-msgid "Tickets"
-msgstr "Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1452
-#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
-msgid "Tickets %1 %2"
-msgstr "Malkovich %1 %2"
-
-#: lib/RT/Tickets_Overlay.pm:1410
-#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
-msgid "Tickets %1 by %2"
-msgstr "Malkovich %1 by %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets from %1"
-msgstr "Malkovich %1"
-
-#: html/Approvals/Elements/ShowDependency:26
-msgid "Tickets which depend on this approval:"
-msgstr "Malkovich Malkovich on Malkovich:"
-
-#: html/Search/Elements/PickBasics:70 html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:47
-msgid "Time Left"
-msgstr "Malkovich"
-
-#: html/Search/Elements/PickBasics:68 html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:43
-msgid "Time Worked"
-msgstr "Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1201
-msgid "Time left"
-msgstr "Malkovich"
-
-#: html/Elements/Footer:44
-msgid "Time to display"
-msgstr "Time to Malkovich"
-
-#: lib/RT/Tickets_Overlay.pm:1177
-msgid "Time worked"
-msgstr "Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:1277
-msgid "TimeWorked"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:"
-msgstr "To Malkovich a diff of Malkovich:"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:\\n"
-msgstr "To Malkovich a diff of Malkovich:\\n"
-
-#: lib/RT/Ticket_Overlay.pm:1280
-msgid "Told"
-msgstr "Told"
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:705
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr "Malkovich %1 Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:136
-msgid "Transaction Created"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:92
-msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
-msgstr "Malkovich->Malkovich couldn't, as you didn't Malkovich a Malkovich id"
-
-#: lib/RT/Transaction_Overlay.pm:760
-msgid "Transactions are immutable"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Date.pm:389
-msgid "Tue."
-msgstr "Tue."
-
-#: 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:1278 lib/RT/Tickets_Overlay.pm:1021
-msgid "Type"
-msgstr "Type"
-
-#: lib/RT/ScripCondition_Overlay.pm:103
-msgid "Unimplemented"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:67
-msgid "Unix login"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "UnixUsername"
-msgstr "Malkovich"
-
-#: lib/RT/Attachment_Overlay.pm:233 lib/RT/Attachment_Overlay.pm:265
-#. ($self->ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr "Malkovich Malkovich %1"
-
-#: html/Elements/SelectResultsPerPage:36
-msgid "Unlimited"
-msgstr "Malkovich"
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr "Malkovich"
-
-#: lib/RT/Transaction_Overlay.pm:562
-msgid "Untaken"
-msgstr "Malkovich"
-
-#: html/Search/Bulk.html:32
-msgid "Update"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ID"
-msgstr "Malkovich ID"
-
-#: html/Search/Bulk.html:127 html/Ticket/ModifyAll.html:65 html/Ticket/Update.html:48
-msgid "Update Type"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Update all these tickets at once"
-msgstr "Malkovich Malkovich at once"
-
-#: NOT FOUND IN SOURCE
-msgid "Update email"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Update name"
-msgstr "Malkovich"
-
-#: lib/RT/Action/CreateTickets.pm:655 lib/RT/Interface/Web.pm:479
-msgid "Update not recorded."
-msgstr "Malkovich Malkovich."
-
-#: html/Search/Bulk.html:78
-msgid "Update selected tickets"
-msgstr "Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "Update signature"
-msgstr "Malkovich Malkovich"
-
-#: html/Ticket/ModifyAll.html:62
-msgid "Update ticket"
-msgstr "Malkovich"
-
-#: html/SelfService/Update.html:24 html/SelfService/Update.html:63
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "Malkovich #%1"
-
-#: html/Ticket/Update.html:121
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "Malkovich #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:653 lib/RT/Interface/Web.pm:477
-msgid "Update type was neither correspondence nor comment."
-msgstr "Malkovich Malkovich Malkovich Malkovich Malkovich."
-
-#: html/Elements/SelectDateType:32 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1281
-msgid "Updated"
-msgstr "Malkovich"
-
-#: etc/initialdata:132 etc/initialdata:206
-msgid "User Defined"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-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 "Malkovich"
-
-#: html/Admin/Users/Modify.html:252
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "Malkovich be Malkovich: %1"
-
-#: lib/RT/User_Overlay.pm:296
-msgid "User created"
-msgstr "Malkovich"
-
-#: html/Admin/Global/GroupRights.html:66 html/Admin/Groups/GroupRights.html:53 html/Admin/Queues/GroupRights.html:68
-msgid "User defined groups"
-msgstr "Malkovich Malkovich"
-
-#: lib/RT/User_Overlay.pm:558 lib/RT/User_Overlay.pm:575
-msgid "User loaded"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "User view"
-msgstr "Malkovich"
-
-#: html/Admin/Users/Modify.html:47 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:34
-msgid "Username"
-msgstr "Malkovich"
-
-#: 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 "Malkovich"
-
-#: html/Admin/Users/index.html:64
-msgid "Users matching search criteria"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:494
-msgid "Valid Query"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/EditCustomField:56
-msgid "Values"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "Watch"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:85
-msgid "WatchAsAdminCc"
-msgstr "Malkovich"
-
-#: html/Admin/Elements/QueueTabs:41
-msgid "Watchers"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "WebEncoding"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:390
-msgid "Wed."
-msgstr "Wed."
-
-#: etc/initialdata:521
-msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
-msgstr "When a Malkovich Malkovich by Malkovich, Malkovich Malkovich to the Malkovich"
-
-#: etc/initialdata:485
-msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
-msgstr "When a Malkovich Malkovich by Malkovich, Malkovich Malkovich to the Malkovich"
-
-#: etc/initialdata:146
-msgid "When a ticket is created"
-msgstr "When a Malkovich is Malkovich"
-
-#: etc/initialdata:418
-msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
-msgstr "When a Malkovich is Malkovich, Malkovich the Malkovich and Malkovich of the Malkovich Malkovich Malkovich"
-
-#: etc/initialdata:151
-msgid "When anything happens"
-msgstr "Malkovich Malkovich"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "Malkovich a Malkovich is Malkovich"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr "Malkovich a Malkovich's Malkovich"
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr "Malkovich a Malkovich's Malkovich"
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr "Malkovich a Malkovich's Malkovich"
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr "Malkovich a user-Malkovich Malkovich"
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr "Malkovich Malkovich in"
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr "Malkovich Malkovich Malkovich in"
-
-#: html/Admin/Users/Modify.html:161 html/User/Prefs.html:67
-msgid "Work"
-msgstr "Work"
-
-#: NOT FOUND IN SOURCE
-msgid "WorkPhone"
-msgstr "Malkovich"
-
-#: html/Ticket/Elements/ShowBasics:41 html/Ticket/Update.html:42
-msgid "Worked"
-msgstr "Malkovich"
-
-#: html/autohandler:150
-msgid "XXX CHANGEME You are not an authorized user"
-msgstr "MALKOVICH Malkovich a Malkovich"
-
-#: lib/RT/Ticket_Overlay.pm:3059
-msgid "You already own this ticket"
-msgstr "Malkovich Malkovich Malkovich"
-
-#: html/autohandler:142
-msgid "You are not an authorized user"
-msgstr "Malkovich a Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "You can access it with the Download button on the right."
-msgstr "Malkovich it with the Malkovich on the Malkovich."
-
-#: lib/RT/Ticket_Overlay.pm:2941
-msgid "You can only reassign tickets that you own or that are unowned"
-msgstr "Malkovich Malkovich Malkovich Malkovich or Malkovich Malkovich"
-
-#: docs/design_docs/string-extraction-guide.txt:47 lib/RT/StyleGuide.pod:760
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "Malkovich %1 Malkovich in Malkovich %2"
-
-#: html/NoAuth/Logout.html:30
-msgid "You have been logged out of RT."
-msgstr "Malkovich Malkovich of RT."
-
-#: html/SelfService/Display.html:79
-msgid "You have no permission to create tickets in that queue."
-msgstr "Malkovich no Malkovich to Malkovich in that Malkovich."
-
-#: lib/RT/Ticket_Overlay.pm:2095
-msgid "You may not create requests in that queue."
-msgstr "Malkovich Malkovich Malkovich in Malkovich."
-
-#: html/NoAuth/Logout.html:34
-msgid "You're welcome to login again"
-msgstr "You're Malkovich to Malkovich"
-
-#: etc/initialdata:502
-msgid "Your request has been approved by %1. Other approvals may still be pending."
-msgstr "Malkovich Malkovich Malkovich by %1. Malkovich Malkovich be Malkovich."
-
-#: etc/initialdata:540
-msgid "Your request has been approved."
-msgstr "Malkovich Malkovich Malkovich."
-
-#: etc/initialdata:445
-msgid "Your request was rejected."
-msgstr "Malkovich Malkovich."
-
-#: html/autohandler:177
-msgid "Your username or password is incorrect"
-msgstr "Malkovich or Malkovich is Malkovich"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:127
-msgid "Zip"
-msgstr "Zip"
-
-#: html/User/Elements/DelegateRights:58
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr "as Malkovich to %1"
-
-#: html/SelfService/Closed.html:27
-msgid "closed"
-msgstr "Malkovich"
-
-#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:33
-msgid "contains"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "content"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "content-type"
-msgstr "Malkovich-type"
-
-#: html/Admin/Queues/Modify.html:76 lib/RT/Date.pm:319
-msgid "days"
-msgstr "days"
-
-#: lib/RT/Queue_Overlay.pm:64
-msgid "deleted"
-msgstr "Malkovich"
-
-#: html/Search/Elements/PickBasics:33
-msgid "does not match"
-msgstr "Malkovich"
-
-#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:34
-msgid "doesn't contain"
-msgstr "doesn't Malkovich"
-
-#: html/Elements/SelectEqualityOperator:37
-msgid "equal to"
-msgstr "Malkovich to"
-
-#: NOT FOUND IN SOURCE
-msgid "filename"
-msgstr "Malkovich"
-
-#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
-msgid "greater than"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:196
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "Malkovich '%1'"
-
-#: lib/RT/Date.pm:315
-msgid "hours"
-msgstr "Malkovich"
-
-#: html/Elements/SelectBoolean:31 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:35 html/Search/Elements/PickBasics:49 html/Search/Elements/PickBasics:80 html/Search/Elements/PickBasics:97 html/Search/Elements/PickCFs:37
-msgid "is"
-msgstr "is"
-
-#: html/Elements/SelectBoolean:35 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:36 html/Search/Elements/PickBasics:50 html/Search/Elements/PickBasics:81 html/Search/Elements/PickBasics:98 html/Search/Elements/PickCFs:38
-msgid "isn't"
-msgstr "isn't"
-
-#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
-msgid "less than"
-msgstr "Malkovich"
-
-#: html/Search/Elements/PickBasics:32
-msgid "matches"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:311
-msgid "min"
-msgstr "min"
-
-#: html/Ticket/Update.html:42
-msgid "minutes"
-msgstr "Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "modifications\\n\\n"
-msgstr "Malkovich\\n\\n"
-
-#: lib/RT/Date.pm:327
-msgid "months"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:59
-msgid "new"
-msgstr "new"
-
-#: html/Admin/Elements/EditCustomFields:42
-msgid "no name"
-msgstr "no name"
-
-#: html/Admin/Elements/EditScrips:42
-msgid "no value"
-msgstr "no Malkovich"
-
-#: html/Admin/Elements/EditQueueWatchers:26 html/Ticket/Elements/EditWatchers:27
-msgid "none"
-msgstr "none"
-
-#: html/Elements/SelectEqualityOperator:37
-msgid "not equal to"
-msgstr "Malkovich to"
-
-#: html/SelfService/Elements/MyRequests:61 lib/RT/Queue_Overlay.pm:60
-msgid "open"
-msgstr "open"
-
-#: lib/RT/Group_Overlay.pm:201
-#. ($self->Name, $user->Name)
-msgid "personal group '%1' for user '%2'"
-msgstr "Malkovich '%1' Malkovich '%2'"
-
-#: lib/RT/Group_Overlay.pm:209
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "Malkovich %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:63
-msgid "rejected"
-msgstr "Malkovich"
-
-#: lib/RT/Queue_Overlay.pm:62
-msgid "resolved"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:307
-msgid "sec"
-msgstr "sec"
-
-#: lib/RT/Queue_Overlay.pm:61
-msgid "stalled"
-msgstr "Malkovich"
-
-#: lib/RT/Group_Overlay.pm:204
-#. ($self->Type)
-msgid "system %1"
-msgstr "Malkovich %1"
-
-#: lib/RT/Group_Overlay.pm:215
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr "Malkovich '%1'"
-
-#: html/Elements/Error:42 html/SelfService/Error.html:41
-msgid "the calling component did not specify why"
-msgstr "the Malkovich Malkovich Malkovich Malkovich"
-
-#: NOT FOUND IN SOURCE
-msgid "ticket #%1"
-msgstr "Malkovich #%1"
-
-#: lib/RT/Group_Overlay.pm:212
-#. ($self->Instance, $self->Type)
-msgid "ticket #%1 %2"
-msgstr "Malkovich #%1 %2"
-
-#: lib/RT/Group_Overlay.pm:218
-#. ($self->Id)
-msgid "undescribed group %1"
-msgstr "Malkovich Malkovich %1"
-
-#: lib/RT/Group_Overlay.pm:193
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "user %1"
-
-#: lib/RT/Date.pm:323
-msgid "weeks"
-msgstr "Malkovich"
-
-#: lib/RT/Date.pm:331
-msgid "years"
-msgstr "Malkovich"
-
diff --git a/rt/lib/RT/I18N/no.po b/rt/lib/RT/I18N/no.po
deleted file mode 100644
index 1132397..0000000
--- a/rt/lib/RT/I18N/no.po
+++ /dev/null
@@ -1,6563 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: RT 3.5.x\n"
-"POT-Creation-Date: 2003-04-01 06:06+0200\n"
-"PO-Revision-Date: 2006-12-20 20:59+0100\n"
-"Last-Translator: Ronny Pettersen <ronny.pettersen@edb.com>\n"
-"Language-Team: rt-devel <rt-devel@lists.bestpractical.com>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: html/Widgets/SavedSearch:70
-#. ($self->{CurrentSearch}{Object}->Description)
-msgid " %1 deleted."
-msgstr ""
-
-#: html/Widgets/SavedSearch:47
-#. ($self->{CurrentSearch}{Description}, $args->{Description})
-msgid " %1 renamed to %2."
-msgstr ""
-
-#: html/Widgets/SavedSearch:60
-#. ($args->{Description})
-msgid " %1 saved."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "#"
-msgstr "#"
-
-#: NOT FOUND IN SOURCE
-msgid "#%1"
-msgstr "#%1"
-
-#: html/Approvals/Elements/Approve:48 html/Approvals/Elements/ShowDependency:71 html/SelfService/Display.html:46 html/Ticket/Display.html:47 html/Ticket/Display.html:51
-#. ($Ticket->id, $Ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-#. ($ticket->Id, $ticket->Subject)
-#. ($TicketObj->Id, $TicketObj->Subject)
-msgid "#%1: %2"
-msgstr "#%1: %2"
-
-#: html/Elements/ShowSearch:105
-msgid "$1"
-msgstr "$1"
-
-#: lib/RT/Record.pm:940
-#. ($label)
-msgid "$prefix %1"
-msgstr "$prefix %1"
-
-#: lib/RT/URI/fsck_com_rt.pm:256
-#. ($self->ObjectType, $self->Object->Id)
-msgid "%1 #%2"
-msgstr ""
-
-#: lib/RT/Date.pm:365
-#. ($s, $time_unit)
-msgid "%1 %2"
-msgstr "%1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 %2 %3"
-msgstr "%1 %2 %3"
-
-#: lib/RT/Date.pm:401
-#. ($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/Record.pm:1685 lib/RT/Transaction_Overlay.pm:647 lib/RT/Transaction_Overlay.pm:690
-#. ($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:362
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "%1 %2 siden"
-
-#: lib/RT/Record.pm:1692 lib/RT/Transaction_Overlay.pm:654
-#. ($cf->Name, $old_content, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 %2 ble endret til %3"
-
-#: lib/RT/Record.pm:1689 lib/RT/Transaction_Overlay.pm:650 lib/RT/Transaction_Overlay.pm:696
-#. ($cf->Name, $old_value->Content)
-#. ($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:65 html/Admin/Elements/ListGlobalScrips:63 html/Ticket/Elements/PreviewScrips:103
-#. (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 saken\\n"
-
-#: html/Ticket/Elements/ShowAttachments:72
-#. ($rev->CreatedAsString, $size, $rev->CreatorObj->Name)
-msgid "%1 (%2) by %3"
-msgstr ""
-
-#: html/SelfService/Update.html:60 html/Ticket/Elements/EditBasics:108 html/Ticket/Update.html:61 html/Ticket/Update.html:63 html/Tools/MyDay.html:66
-#. (loc($DefaultStatus))
-#. (loc($Ticket->Status()))
-#. (loc($TicketObj->Status))
-#. ($TicketObj->OwnerObj->Name())
-msgid "%1 (Unchanged)"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "%1 - %2 shown"
-msgstr "%1 - %2 vist"
-
-#: bin/rt-crontool:237 bin/rt-crontool:244 bin/rt-crontool:250
-#. ("--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:262
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr "%1 - Viser statusoppdateringer til STDOUT"
-
-#: bin/rt-crontool:253
-#. ("--template-id")
-msgid "%1 - Specify id of the template you want to use"
-msgstr ""
-
-#: bin/rt-crontool:256
-#. ("--transaction")
-msgid "%1 - Specify if you want to use either 'first' or 'last' tarnsaction"
-msgstr ""
-
-#: bin/rt-crontool:247
-#. ("--action")
-msgid "%1 - Specify the action module you want to use"
-msgstr "%1 - Oppgi kommandomodulen du ønsker å bruke"
-
-#: bin/rt-crontool:241
-#. ("--condition")
-msgid "%1 - Specify the condition module you want to use"
-msgstr "%1 - Oppgi betingelsesmodulen du ønsker å bruke"
-
-#: bin/rt-crontool:234
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr "%1 - Oppgi søkemodulen du ønsker å bruke"
-
-#: bin/rt-crontool:259
-#. ("--transaction-type")
-msgid "%1 - Specify the type of a transaction you want to use"
-msgstr ""
-
-#: html/Elements/Footer:56
-#. ('&#187;&#124;&#171;', $RT::VERSION, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',)
-msgid "%1 RT %2 Copyright 1996-%3 %4."
-msgstr ""
-
-#: lib/RT/ScripAction_Overlay.pm:150
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr "%1 KommandoScript lastet"
-
-#: lib/RT/Record.pm:1722
-#. ($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:144 lib/RT/Link_Overlay.pm:151
-#. ($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:73 lib/RT/Transaction_Overlay.pm:531
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-msgid "%1 by %2"
-msgstr "%1 av %2"
-
-#: lib/RT/Transaction_Overlay.pm:788 lib/RT/Transaction_Overlay.pm:797 lib/RT/Transaction_Overlay.pm:800
-#. ($self->Field , $q1->Name , $q2->Name)
-#. ($self->Field, $t2->AsString, $t1->AsString)
-#. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'")
-msgid "%1 changed from %2 to %3"
-msgstr "%1 ble endret fra %2 til %3"
-
-#: html/Search/Build.html:213
-#. ($Description)
-msgid "%1 copy"
-msgstr "%1 kopi"
-
-#: lib/RT/Record.pm:944
-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:2787
-#. ($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."
-
-#: lib/RT/Transaction_Overlay.pm:571
-#. ($obj_type)
-msgid "%1 created"
-msgstr "%1 opprettet"
-
-#: lib/RT/Transaction_Overlay.pm:576
-#. ($obj_type)
-msgid "%1 deleted"
-msgstr "%1 slettet"
-
-#: etc/initialdata:593
-msgid "%1 highest priority tickets I own"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I own..."
-msgstr "Mine %1 høyst prioriterte saker..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I requested..."
-msgstr "Mine %1 høyst prioriterte forespørsler..."
-
-#: bin/rt-crontool:229
-#. ($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:863
-#. ($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."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 is no longer a %2 for this ticket."
-msgstr "%1 er ikke lenger en %2 for denne saken."
-
-#: NOT FOUND IN SOURCE
-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/ShowTime:47 html/Ticket/Elements/ShowTime:49
-#. ($minutes)
-msgid "%1 min"
-msgstr "%1 min"
-
-#: etc/initialdata:601
-msgid "%1 newest unowned tickets"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "%1 not shown"
-msgstr "%1 vises ikke"
-
-#: lib/RT/CustomField_Overlay.pm:893
-msgid "%1 objects"
-msgstr ""
-
-#: html/User/Elements/DelegateRights:97
-#. (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:63
-#. (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/CustomField_Overlay.pm:894
-msgid "%1's %2 objects"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:895
-msgid "%1's %2's %3 objects"
-msgstr ""
-
-#: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:57
-#. ($object->Name)
-#. ($Object->Name)
-msgid "%1's saved searches"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:481
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1: ingen vedlegg oppgitt"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:78
-#. ($size)
-msgid "%1b"
-msgstr "%1b"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:75
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr "%1k"
-
-#: html/Ticket/Elements/ShowTime:49
-#. (sprintf("%.1f",$minutes / 60))
-msgid "%quant(%1,hour)"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1142
-#. ($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:50 html/Admin/Elements/EditQueueWatchers:50 html/Admin/Elements/EditScrips:56 html/Admin/Elements/EditTemplates:57 html/Admin/Groups/Members.html:73 html/Elements/EditLinks:54 html/Ticket/Elements/EditPeople:67 html/User/Groups/Members.html:76
-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/Elements/PreviewScrips:99
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr ""
-
-#: html/Ticket/Elements/PreviewScrips:123
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr ""
-
-#: html/Ticket/Create.html:218
-msgid "(Enter ticket ids or URLs, separated with spaces)"
-msgstr "(Skriv inn referansenummer eller URler, separert med mellomrom)"
-
-#: html/Admin/Queues/Modify.html:75 html/Admin/Queues/Modify.html:81
-#. ($RT::CorrespondAddress)
-#. ($RT::CommentAddress)
-msgid "(If left blank, will default to %1)"
-msgstr "(Settes til standard %1 hvis blank)"
-
-#: NOT FOUND IN SOURCE
-msgid "(No Value)"
-msgstr "(Ingen Verdi)"
-
-#: html/Admin/Elements/EditCustomFields:74 html/Admin/Elements/ListGlobalCustomFields:53
-msgid "(No custom fields)"
-msgstr "(Ingen fleksifelt)"
-
-#: html/Admin/Groups/Members.html:71 html/User/Groups/Members.html:74
-msgid "(No members)"
-msgstr "(Ingen medlemmer)"
-
-#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ListGlobalScrips:48
-msgid "(No scrips)"
-msgstr "(Ingen scrips)"
-
-#: html/Admin/Elements/EditTemplates:52
-msgid "(No templates)"
-msgstr "(Ingen maler)"
-
-#: html/Admin/Elements/PickCustomFields:47 html/Admin/Elements/PickObjects:47
-msgid "(None)"
-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 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/Update.html:90
-msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> 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 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/Create.html:103
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> 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 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/Update.html:86
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> 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. 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/Ticket/Create.html:93
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:96
-msgid "(Use these fields when you choose 'User Defined' for a condition or action)"
-msgstr ""
-
-#: html/Ticket/Elements/EditWatchers:60 html/Ticket/Elements/ShowUserEntry:53
-msgid "(Will not be sent email)"
-msgstr ""
-
-#: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54
-msgid "(empty)"
-msgstr "(tom)"
-
-#: html/Admin/Users/index.html:60
-msgid "(no name listed)"
-msgstr "(navn ikke oppgitt)"
-
-#: NOT FOUND IN SOURCE
-msgid "(no subject)"
-msgstr "(ingen overskrift)"
-
-#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:69 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:54 html/Search/Chart:56 html/Search/Elements/Chart:76 lib/RT/Transaction_Overlay.pm:591
-msgid "(no value)"
-msgstr "(ingen verdi)"
-
-#: html/Admin/Elements/EditCustomFieldValues:47
-msgid "(no values)"
-msgstr ""
-
-#: html/Elements/EditLinks:132 html/Ticket/Elements/BulkLinks:49
-msgid "(only one ticket)"
-msgstr "(bare en sak)"
-
-#: html/Elements/RT__Ticket/ColumnMap:149
-msgid "(pending approval)"
-msgstr "(Venter på godkjenning)"
-
-#: html/Elements/RT__Ticket/ColumnMap:152
-msgid "(pending other Collection)"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-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:71
-msgid "(required)"
-msgstr "(nødvendig)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "(untitled)"
-msgstr "(ingen tittel)"
-
-#: html/Ticket/Elements/Reminders:133
-msgid "(yyyy/mm/dd)"
-msgstr ""
-
-#: html/Elements/EditCustomFieldSelect:57
-msgid "-"
-msgstr ""
-
-#: bin/rt-crontool:95
-msgid "--transaction argument could be only 'first' or 'last'"
-msgstr ""
-
-#: 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:53
-msgid "<% $Ticket->Status%>"
-msgstr "<% $Ticket-:Status%>"
-
-#: html/Elements/SelectTicketTypes:48
-msgid "<% $_ %>"
-msgstr "<% $_ %>"
-
-#: html/Search/Elements/SelectLinks:48
-msgid "<%$_%>"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:73
-msgid "<%$field%>"
-msgstr ""
-
-#: html/Elements/CreateTicket:47
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" class=\"button\" value=\"New ticket in\" />&nbsp;%1"
-msgstr ""
-
-#: docs/design_docs/string-extraction-guide.txt:54 lib/RT/StyleGuide.pod:787
-#. ($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:218
-msgid "A blank template"
-msgstr "En tom mal"
-
-#: html/Admin/Users/Modify.html:371
-msgid "A password was not set, so user won't be able to login."
-msgstr ""
-
-#: 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:174 lib/RT/Principal_Overlay.pm:219
-msgid "ACE not found"
-msgstr "ACE ikke funnet"
-
-#: lib/RT/ACE_Overlay.pm:853
-msgid "ACEs can only be created and deleted."
-msgstr "ACEr kan bare opprettes og slettes."
-
-#: html/Search/Elements/SelectAndOr:46
-msgid "AND"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Aborting to avoid unintended ticket modifications.\\n"
-msgstr "Avbryter for å ungå uånsket saksendring"
-
-#: html/User/Elements/Tabs:53
-msgid "About me"
-msgstr "Om meg"
-
-#: html/Admin/Users/Modify.html:106
-msgid "Access control"
-msgstr "Aksesskontroll"
-
-#: html/Admin/Elements/EditScrip:65
-msgid "Action"
-msgstr "Handling"
-
-#: lib/RT/Scrip_Overlay.pm:172
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "Handling %1 finnes ikke"
-
-#: NOT FOUND IN SOURCE
-msgid "Action committed."
-msgstr "Handling skrevet."
-
-#: bin/rt-crontool:171
-msgid "Action committed.\\n"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:168
-msgid "Action is mandatory argument"
-msgstr ""
-
-#: bin/rt-crontool:167
-msgid "Action prepared..."
-msgstr "Handling forberedt"
-
-#: html/Search/Build.html:85
-msgid "Add"
-msgstr ""
-
-#: html/Search/Bulk.html:92
-msgid "Add AdminCc"
-msgstr "Legg til AdminCc"
-
-#: html/Search/Bulk.html:88
-msgid "Add Cc"
-msgstr "Legg til Cc"
-
-#: html/Search/Elements/EditFormat:49
-msgid "Add Columns"
-msgstr ""
-
-#: html/Search/Elements/PickCriteria:46
-msgid "Add Criteria"
-msgstr ""
-
-#: html/Ticket/Create.html:147 html/Ticket/Update.html:116
-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:84
-msgid "Add Requestor"
-msgstr "Legg til kunde"
-
-#: html/Admin/Elements/AddCustomFieldValue:46
-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:83
-msgid "Add a scrip which will apply to all queues"
-msgstr "Legg til et Scrip som vil gjelde for alle køer"
-
-#: html/Search/Build.html:109 html/Search/Build.html:94
-msgid "Add and Search"
-msgstr ""
-
-#: html/Search/Bulk.html:124
-msgid "Add comments or replies to selected tickets"
-msgstr "Legg til kommentarer eller svar til denne saken"
-
-#: html/Admin/Groups/Members.html:63 html/User/Groups/Members.html:60
-msgid "Add members"
-msgstr "Legg til medlemmer"
-
-#: html/Admin/Queues/People.html:87 html/Ticket/Elements/AddWatchers:49
-msgid "Add new watchers"
-msgstr "Legg til overvåkere"
-
-#: html/Search/Build.html:85
-msgid "Add these terms to your search"
-msgstr ""
-
-#: html/Search/Bulk.html:158
-msgid "Add values"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "Add, delete and modify custom field values for objects"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "AddNextState"
-msgstr "AddNextState"
-
-#: lib/RT/Queue_Overlay.pm:763
-#. ($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:1455
-#. ($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/Users/Modify.html:146 html/User/Prefs.html:133
-msgid "Address1"
-msgstr "Adresse1"
-
-#: html/Admin/Users/Modify.html:151 html/User/Prefs.html:137
-msgid "Address2"
-msgstr "Adresse2"
-
-#: html/Ticket/Create.html:98
-msgid "Admin Cc"
-msgstr "Admin Cc"
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "Admin Kommentar"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr "Admin-korrespondanse"
-
-#: html/Admin/Queues/index.html:46 html/Admin/Queues/index.html:49
-msgid "Admin queues"
-msgstr "Adminkøer"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin users"
-msgstr "Adminbrukere"
-
-#: html/Admin/Global/index.html:47 html/Admin/Global/index.html:49
-msgid "Admin/Global configuration"
-msgstr "Admin/Global konfigurasjon"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Groups"
-msgstr "Admin/Grupper"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Queue/Basics"
-msgstr "Admin/Køer/Grunnleggende"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminAllPersonalGroups"
-msgstr "AdminAllePersonalGrupper"
-
-#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:60 lib/RT/ACE_Overlay.pm:113
-msgid "AdminCc"
-msgstr "AdminCc"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminComment"
-msgstr "AdminKommentar"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCorrespondence"
-msgstr "AdminKorrespondanse"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "AdminCustomField"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCustomFields"
-msgstr "AdminFleksifelt"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "AdminGroup"
-msgstr "AdminGruppe"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "AdminGroupMembership"
-msgstr "AdminGruppeMedlemskap"
-
-#: lib/RT/System.pm:80
-msgid "AdminOwnPersonalGroups"
-msgstr "AdminEgnePersonligeGrupper"
-
-#: lib/RT/Queue_Overlay.pm:92
-msgid "AdminQueue"
-msgstr "AdminKø"
-
-#: lib/RT/System.pm:81
-msgid "AdminUsers"
-msgstr "AdminBrukere"
-
-#: html/Admin/Queues/People.html:69 html/Ticket/Elements/EditPeople:75
-msgid "Administrative Cc"
-msgstr "Administrativ Cc"
-
-#: NOT FOUND IN SOURCE
-msgid "Admins"
-msgstr "Admin"
-
-#: html/Ticket/Elements/Tabs:216
-msgid "Advanced"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search"
-msgstr "Avansert Søk"
-
-#: html/Elements/SelectDateRelation:57
-msgid "After"
-msgstr "Etter"
-
-#: NOT FOUND IN SOURCE
-msgid "Age"
-msgstr "Alder"
-
-#: html/Search/Elements/PickCriteria:52
-msgid "Aggregator"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Alias"
-msgstr "Alias"
-
-#: NOT FOUND IN SOURCE
-msgid "Alias for"
-msgstr "Alias for"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "All Custom Fields"
-msgstr "Alle Fleksifelt"
-
-#: html/Admin/Queues/index.html:75
-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/Search/Elements/EditQuery:56
-msgid "And/Or"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:73 html/Admin/Elements/CustomFieldTabs:83
-msgid "Applies to"
-msgstr ""
-
-#: html/Search/Edit.html:64
-msgid "Apply"
-msgstr ""
-
-#: html/Search/Edit.html:64
-msgid "Apply your changes"
-msgstr ""
-
-#: html/Elements/Tabs:77
-msgid "Approval"
-msgstr "Godkjennelse"
-
-#: html/Approvals/Display.html:65 html/Approvals/Elements/ShowDependency:63 html/Approvals/index.html:86
-#. ($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:75
-#. ($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:73
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr "Godkjenning #%1: Notater lagret"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Details"
-msgstr "Godkjenning - Detaljer"
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr ""
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Approval diagram"
-msgstr "Godkjenningsdiagram"
-
-#: html/Approvals/Elements/Approve:69
-msgid "Approve"
-msgstr "Godkjenn"
-
-#: etc/initialdata:504
-msgid "Approver's notes: %1"
-msgstr "Godkjenners notater: %1"
-
-#: lib/RT/Date.pm:444
-msgid "Apr."
-msgstr "Apr."
-
-#: NOT FOUND IN SOURCE
-msgid "April"
-msgstr "April"
-
-#: html/Search/Elements/DisplayOptions:81
-msgid "Asc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:56
-msgid "Ascending"
-msgstr "Stigende"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "Assign and remove custom fields"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "AssignCustomFields"
-msgstr ""
-
-#: html/Search/Bulk.html:142 html/SelfService/Update.html:87 html/Ticket/ModifyAll.html:115 html/Ticket/Update.html:116
-msgid "Attach"
-msgstr "Legg Ved"
-
-#: html/SelfService/Create.html:92 html/Ticket/Create.html:143
-msgid "Attach file"
-msgstr "Legg ved fil"
-
-#: html/SelfService/Update.html:75 html/Ticket/Create.html:131 html/Ticket/Update.html:94
-msgid "Attached file"
-msgstr "Vedlagt fil"
-
-#: html/Ticket/ShowEmailRecord.html:52 html/Ticket/ShowEmailRecord.html:56 html/Ticket/ShowEmailRecord.html:59
-#. ($Attachment)
-msgid "Attachment '%1' could not be loaded"
-msgstr "Vedlegg '%1' kunne ikke lastes"
-
-#: lib/RT/Transaction_Overlay.pm:489
-msgid "Attachment created"
-msgstr "Vedlegg opprettet"
-
-#: lib/RT/Tickets_Overlay.pm:1945
-msgid "Attachment filename"
-msgstr "Vedleggsnavn"
-
-#: html/Ticket/Elements/ShowAttachments:47
-msgid "Attachments"
-msgstr "Vedlegg"
-
-#: lib/RT/Attributes_Overlay.pm:171
-msgid "Attribute Deleted"
-msgstr ""
-
-#: lib/RT/Date.pm:448
-msgid "Aug."
-msgstr "Aug."
-
-#: NOT FOUND IN SOURCE
-msgid "August"
-msgstr "August"
-
-#: NOT FOUND IN SOURCE
-msgid "AuthSystem"
-msgstr "AutSystem"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "Autosvar"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "Autosvar Til Kunde"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoreplyToRequestors"
-msgstr "AutosvarTilKunde"
-
-#: html/Widgets/SelectionBox:185
-msgid "Available"
-msgstr ""
-
-#: 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"
-
-#: NOT FOUND IN SOURCE
-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/CustomFieldTabs:65 html/Admin/Elements/GroupTabs:60 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/UserTabs:58 html/Ticket/Elements/Tabs:113 html/User/Elements/GroupTabs:59
-msgid "Basics"
-msgstr "Detaljer"
-
-#: html/Ticket/Update.html:88
-msgid "Bcc"
-msgstr "Bcc"
-
-#: html/Admin/CustomFields/GroupRights.html:91 html/Admin/CustomFields/UserRights.html:74 html/Admin/Elements/EditScrip:89
-msgid "Be sure to save your changes"
-msgstr "Sørg for å lagre endringene dine"
-
-#: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:361
-msgid "Before"
-msgstr "Før"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin Approval"
-msgstr "Begynn Godkjenning"
-
-#: html/Elements/Logo:47
-msgid "Best Practical Solutions, LLC corporate logo"
-msgstr ""
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "Blank"
-
-#: html/Search/Elements/EditFormat:84
-msgid "Bold"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Bookmarkable URL for this search"
-msgstr "URL som kan brukes som bokmerke for dette søket"
-
-#: html/Search/Results.html:79
-msgid "Bookmarkable link"
-msgstr ""
-
-#: html/Ticket/Elements/ShowHistory:64 html/Ticket/Elements/ShowHistory:69
-msgid "Brief headers"
-msgstr "Begrens headere"
-
-#: html/Ticket/Elements/Tabs:227
-msgid "Bulk Update"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Bulk ticket update"
-msgstr "Masseoppdatering av saker"
-
-#: lib/RT/User_Overlay.pm:1853
-msgid "Can not modify system users"
-msgstr "Kan ikke endre systembrukere"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "Can this principal see this queue"
-msgstr "Kan denne primæren se denne køen"
-
-#: lib/RT/CustomField_Overlay.pm:379
-msgid "Can't add a custom field value without a name"
-msgstr "Kan ikke legge til en verdi for et fleksifelt uten navn"
-
-#: html/Admin/CustomFields/Objects.html:86
-#. ($Class)
-msgid "Can't find a collection class for '%1'"
-msgstr ""
-
-#: html/Search/Build.html:286
-msgid "Can't find a saved search to work with"
-msgstr ""
-
-#: lib/RT/Link_Overlay.pm:159
-msgid "Can't link a ticket to itself"
-msgstr "Kan ikke koble en sak til seg selv"
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: html/Widgets/SavedSearch:63
-#. (loc($self->{SearchType}))
-msgid "Can't save %1"
-msgstr ""
-
-#: html/Search/Build.html:290
-msgid "Can't save this search"
-msgstr ""
-
-#: lib/RT/Record.pm:1282 lib/RT/Record.pm:1358
-msgid "Can't specifiy both base and target"
-msgstr "Kan ikke spesifisere både base og mål."
-
-#: html/autohandler:204
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "Kunne ikke oprette bruker: %1"
-
-#: html/Admin/Elements/AddCustomFieldValue:62 html/Admin/Elements/EditCustomFieldValues:58
-msgid "Category"
-msgstr ""
-
-#: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:88 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:83 lib/RT/ACE_Overlay.pm:112
-msgid "Cc"
-msgstr "Cc"
-
-#: html/SelfService/Prefs.html:52
-msgid "Change password"
-msgstr "Endre passord"
-
-#: html/Elements/Submit:78
-msgid "Check All"
-msgstr ""
-
-#: html/SelfService/Update.html:78 html/Ticket/Create.html:134 html/Ticket/Update.html:97
-msgid "Check box to delete"
-msgstr "Merk for å slette"
-
-#: html/Admin/Elements/SelectRights:55
-msgid "Check box to revoke right"
-msgstr "Merk for å trekke tilbake rettighet"
-
-#: html/Elements/EditLinks:148 html/Elements/EditLinks:85 html/Elements/ShowLinks:78 html/Ticket/Create.html:223 html/Ticket/Elements/BulkLinks:64
-msgid "Children"
-msgstr "Barn"
-
-#: html/NoAuth/js/util.js:201
-msgid "Choose a date"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:141
-msgid "City"
-msgstr "By"
-
-#: html/Elements/Submit:80
-msgid "Clear All"
-msgstr ""
-
-#: html/Helpers/CalPopup.html:51
-msgid "Close window"
-msgstr ""
-
-#: html/Ticket/Elements/ShowDates:68
-msgid "Closed"
-msgstr "Lukket"
-
-#: NOT FOUND IN SOURCE
-msgid "Closed Tickets"
-msgstr "Lukkede Saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Closed requests"
-msgstr "Lukkede forespørsler"
-
-#: html/SelfService/Closed.html:46 html/SelfService/Elements/Tabs:78
-msgid "Closed tickets"
-msgstr "Lukkede saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Code"
-msgstr "Kode"
-
-#: lib/RT/CustomField_Overlay.pm:89
-msgid "Combobox: Select or enter multiple values"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:90
-msgid "Combobox: Select or enter one value"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:91
-msgid "Combobox: Select or enter up to %1 values"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Command not understood!\\n"
-msgstr "Kunne ikke tolke kommando!\\n"
-
-#: html/Ticket/Elements/ShowTransaction:190 html/Ticket/Elements/Tabs:185
-msgid "Comment"
-msgstr "Kommenter"
-
-#: html/Admin/Queues/Modify.html:79
-msgid "Comment Address"
-msgstr "Kommentaraddresse"
-
-#: NOT FOUND IN SOURCE
-msgid "Comment not recorded"
-msgstr "Kommentaren ble ikke lagret"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "Comment on tickets"
-msgstr "Kommenter saker"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "CommentOnTicket"
-msgstr "KommenterSak"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments"
-msgstr "Kommentarer"
-
-#: html/Ticket/ModifyAll.html:91 html/Ticket/Update.html:75
-msgid "Comments (Not sent to requestors)"
-msgstr "Kommentarer (Ikke send til kunder)"
-
-#: html/Search/Bulk.html:128
-msgid "Comments (not sent to requestors)"
-msgstr "Kommentarer (ikke sendt til kunder)"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments about %1"
-msgstr "Kommentarer til %1"
-
-#: html/Admin/Users/Modify.html:225 html/Ticket/Elements/ShowRequestor:67
-msgid "Comments about this user"
-msgstr "Kommentarer om denne brukeren"
-
-#: lib/RT/Transaction_Overlay.pm:634
-msgid "Comments added"
-msgstr "La til kommentarer "
-
-#: lib/RT/Action/Generic.pm:175
-msgid "Commit Stubbed"
-msgstr "Lagring forkortet"
-
-#: NOT FOUND IN SOURCE
-msgid "Compile Restrictions"
-msgstr "Kompilatorrestriksjoner"
-
-#: html/Admin/Elements/EditScrip:59
-msgid "Condition"
-msgstr "Forutsetning"
-
-#: lib/RT/Scrip_Overlay.pm:184
-msgid "Condition is mandatory argument"
-msgstr ""
-
-#: bin/rt-crontool:151
-msgid "Condition matches..."
-msgstr "Forutsetning gjelder..."
-
-#: lib/RT/Scrip_Overlay.pm:188
-msgid "Condition not found"
-msgstr "Forutsetning ikke funnet"
-
-#: html/Elements/Tabs:84
-msgid "Configuration"
-msgstr "Konfigurasjon"
-
-#: html/SelfService/Prefs.html:54
-msgid "Confirm"
-msgstr "Bekreft"
-
-#: NOT FOUND IN SOURCE
-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:65 html/Elements/SelectAttachmentField:48 html/Ticket/ModifyAll.html:119
-msgid "Content"
-msgstr "Innhold"
-
-#: html/Elements/SelectAttachmentField:49
-msgid "Content-Type"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Coould not create group"
-msgstr "Kunne ikke opprette gruppen"
-
-#: html/Search/Elements/EditSearches:65
-msgid "Copy"
-msgstr ""
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "Korrespondanse"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence Address"
-msgstr "Korrespondanseaddresse"
-
-#: lib/RT/Transaction_Overlay.pm:630
-msgid "Correspondence added"
-msgstr "Korrespondanse lagt til"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence not recorded"
-msgstr "Korrespondansen ble ikke lagret"
-
-#: NOT FOUND IN SOURCE
-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/Record.pm:1707
-msgid "Could not add new custom field value. "
-msgstr ""
-
-#: lib/RT/Record.pm:1660
-#. (, $value_msg)
-msgid "Could not add new custom field value. %1 "
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3048 lib/RT/Ticket_Overlay.pm:3056 lib/RT/Ticket_Overlay.pm:3073
-msgid "Could not change owner. "
-msgstr "Kunne ikke endre eier. "
-
-#: html/Admin/CustomFields/Modify.html:161
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr "Kunne ikke opprette fleksifelt"
-
-#: html/Admin/Elements/EditCustomField:113
-#. ($msg)
-msgid "Could not create CustomField: %1"
-msgstr ""
-
-#: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:494 lib/RT/Group_Overlay.pm:501
-msgid "Could not create group"
-msgstr "Kunne ikke opprette gruppe"
-
-#: html/Admin/Global/Template.html:96 html/Admin/Queues/Template.html:93
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr "Kunne ikke opprette mal: %1"
-
-#: lib/RT/Ticket_Overlay.pm:1075 lib/RT/Ticket_Overlay.pm:407
-msgid "Could not create ticket. Queue not set"
-msgstr "Kunne ikke opprette sak. Kø ikke satt"
-
-#: lib/RT/User_Overlay.pm:255 lib/RT/User_Overlay.pm:269 lib/RT/User_Overlay.pm:278 lib/RT/User_Overlay.pm:287 lib/RT/User_Overlay.pm:296 lib/RT/User_Overlay.pm:310 lib/RT/User_Overlay.pm:320 lib/RT/User_Overlay.pm:496
-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:741 lib/RT/Ticket_Overlay.pm:1423
-msgid "Could not find or create that user"
-msgstr "Kunne ikke finne eller lage den brukeren"
-
-#: lib/RT/Queue_Overlay.pm:802 lib/RT/Ticket_Overlay.pm:1504
-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/CustomFields/Objects.html:69
-msgid "Could not load CustomField %1"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:112 html/User/Groups/Members.html:111 html/User/Groups/Modify.html:103
-msgid "Could not load group"
-msgstr "Kunne ikke hente gruppen"
-
-#: lib/RT/SavedSearch.pm:119
-#. ($privacy)
-msgid "Could not load object for %1"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:197
-msgid "Could not load search attribute"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:761
-#. ($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:1444
-#. ($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:860
-#. ($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"
-
-#: NOT FOUND IN SOURCE
-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/User_Overlay.pm:191
-msgid "Could not set user info"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:159
-msgid "Couldn't add attachment"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:1003
-msgid "Couldn't add member to group"
-msgstr "Kunne ikke legge til medlemmmer i gruppen"
-
-#: lib/RT/Record.pm:1719 lib/RT/Record.pm:1771
-#. ($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/Record.pm:953
-msgid "Couldn't find row"
-msgstr "Kunne ikke finne raden"
-
-#: lib/RT/Group_Overlay.pm:977
-msgid "Couldn't find that principal"
-msgstr "Kunne ikke finne primæren"
-
-#: lib/RT/CustomField_Overlay.pm:409
-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:145
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr "Kunne ikke laste %1 fra brukerdatabasen.\\n"
-
-#: html/Admin/CustomFields/UserRights.html:149
-#. ($id)
-msgid "Couldn't load Class %1"
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:107
-#. ($id)
-msgid "Couldn't load CustomField %1"
-msgstr ""
-
-#: 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."
-
-#: lib/RT/Ticket_Overlay.pm:2016
-#. ($self->Id)
-msgid "Couldn't load copy of ticket #%1."
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:109 html/Admin/Groups/UserRights.html:96
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr "Kunne ikke laste gruppen %1"
-
-#: lib/RT/Link_Overlay.pm:202 lib/RT/Link_Overlay.pm:211 lib/RT/Link_Overlay.pm:238
-msgid "Couldn't load link"
-msgstr "Kunne ikke laste linken"
-
-#: html/Admin/Elements/ObjectCustomFields:83 html/Admin/Queues/CustomFields.html:59 html/Admin/Users/CustomFields.html:59
-#. ($id)
-msgid "Couldn't load object %1"
-msgstr ""
-
-#: html/Admin/Queues/People.html:142
-#. ($id)
-msgid "Couldn't load queue"
-msgstr "Kunne ikke laste køen"
-
-#: html/Admin/Queues/GroupRights.html:122 html/Admin/Queues/UserRights.html:93
-#. ($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"
-
-#: html/Admin/Elements/EditScrip:126 html/Admin/Elements/EditScrip:167
-#. ($id)
-msgid "Couldn't load scrip #%1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load template"
-msgstr "Kunne ikke finne mal"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load that user (%1)"
-msgstr "Kunne ikke laste den brukeren (%1)"
-
-#: html/SelfService/Display.html:158 lib/RT/Action/CreateTickets.pm:680
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr "Kunne ikke laste saken '%1'"
-
-#: lib/RT/Ticket_Overlay.pm:2643
-#. ($args{'URI'})
-msgid "Couldn't resolve '%1' into a URI."
-msgstr ""
-
-#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:153
-msgid "Country"
-msgstr "Land"
-
-#: html/Admin/Elements/CreateUserCalled:47 html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditScrip:133 html/Admin/Queues/Template.html:66 html/Elements/QuickCreate:65 html/Ticket/Create.html:168 html/Ticket/Create.html:235
-msgid "Create"
-msgstr "Opprett"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "Opprett Saker"
-
-#: html/Admin/CustomFields/Modify.html:150 html/Admin/Elements/EditCustomField:96
-msgid "Create a CustomField"
-msgstr "Oprett et fleksifelt"
-
-#: html/Admin/Queues/CustomField.html:69
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr "Opprett et fleksifelt for køen %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a CustomField that 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:125 html/Admin/Groups/Modify.html:99
-msgid "Create a new group"
-msgstr "Opprett en ny gruppe"
-
-#: html/User/Groups/Modify.html:113 html/User/Groups/Modify.html:88
-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:47 html/Ticket/Create.html:51 html/Ticket/Create.html:60
-msgid "Create a new ticket"
-msgstr "Opprett en ny sak"
-
-#: html/Admin/Users/Modify.html:252 html/Admin/Users/Modify.html:314
-msgid "Create a new user"
-msgstr "Opprett en ny bruker"
-
-#: html/Admin/Queues/Modify.html:125
-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:89
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr "Opprett et scrip for køen %1"
-
-#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:86
-msgid "Create a template"
-msgstr "Opprett en mal"
-
-#: html/SelfService/Create.html:46 html/SelfService/CreateTicketInQueue.html:46
-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:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "Opprett nye saker basert på dette scripets mal"
-
-#: html/SelfService/Create.html:105
-msgid "Create ticket"
-msgstr "Opprett sak"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Create tickets in this queue"
-msgstr "Opprett saker i denne køen"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "Create, delete and modify custom fields"
-msgstr "Opprett, slett og modifiser fleksifelt"
-
-#: lib/RT/Queue_Overlay.pm:92
-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:80
-msgid "Create, delete and modify the members of personal groups"
-msgstr "Opprett, slett og modifiser medlemmene av personlige grupper"
-
-#: lib/RT/System.pm:81
-msgid "Create, delete and modify users"
-msgstr "Opprett, slett og modifiser brukere"
-
-#: lib/RT/System.pm:87
-msgid "CreateSavedSearch"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "CreateTicket"
-msgstr "OpprettSak"
-
-#: html/Elements/SelectDateType:47 html/Ticket/Elements/ShowDates:48 lib/RT/Ticket_Overlay.pm:1169
-msgid "Created"
-msgstr "Opprettet"
-
-#: html/Admin/CustomFields/Modify.html:163 html/Admin/Elements/EditCustomField:117
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "Opprettet Fleksifelt %1"
-
-#: html/Tools/Reports/Elements/Tabs:63
-msgid "Created in a date range"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Created template %1"
-msgstr "Opprettet malen %1"
-
-#: html/Tools/Reports/CreatedByDates.html:52
-msgid "Created tickets in period, grouped by status"
-msgstr ""
-
-#: html/Search/Elements/PickBasics:102
-msgid "Creator"
-msgstr ""
-
-#: html/Elements/EditLinks:49
-msgid "Current Links"
-msgstr "Eksisterende Forhold"
-
-#: html/Admin/Elements/EditScrips:51
-msgid "Current Scrips"
-msgstr "Eksisterende Scrips"
-
-#: html/Admin/Groups/Members.html:60 html/User/Groups/Members.html:63
-msgid "Current members"
-msgstr "Eksisterende medlemmer"
-
-#: html/Admin/Elements/SelectRights:51
-msgid "Current rights"
-msgstr "Eksisterende rettigheter"
-
-#: html/Search/Elements/EditQuery:47
-msgid "Current search"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Current search criteria"
-msgstr "Eksisterende søkekriterier"
-
-#: html/Admin/Queues/People.html:62 html/Ticket/Elements/EditPeople:66
-msgid "Current watchers"
-msgstr "Eksisterende overvåkere"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Field #%1"
-msgstr "Fleksifeltet #%1"
-
-#: html/Admin/Elements/SystemTabs:61 html/Admin/Elements/Tabs:62 html/Admin/Global/index.html:71 html/Admin/Users/Modify.html:205 html/Admin/index.html:77 html/Ticket/Elements/ShowSummary:56
-msgid "Custom Fields"
-msgstr "Fleksifelt"
-
-#: html/Admin/CustomFields/index.html:60
-#. ($lookup)
-msgid "Custom Fields for %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:107
-msgid "Custom action cleanup code"
-msgstr "Avsluttningskode"
-
-#: html/Admin/Elements/EditScrip:103
-msgid "Custom action preparation code"
-msgstr "Forberedelseskode"
-
-#: html/Admin/Elements/EditScrip:99
-msgid "Custom condition"
-msgstr "Forutsetning"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 %2 %3"
-msgstr "Fleksifeltet %1 %2 %3"
-
-#: lib/RT/Tickets_Overlay.pm:2424
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr "Fleksifeltet %1 har en verdi."
-
-#: lib/RT/Tickets_Overlay.pm:2420
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr "Fleksifeltet %1 har ingen verdi."
-
-#: lib/RT/Record.pm:1592 lib/RT/Record.pm:1754
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr "Fleksifeltet %1 kunne ikke finnes"
-
-#: lib/RT/Report/Tickets.pm:118 lib/RT/Report/Tickets.pm:121
-#. ($cf)
-#. ($obj->Name)
-msgid "Custom field '%1'"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field deleted"
-msgstr "Fleksifeltet slettet"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field not found"
-msgstr "Fleksifeltet kunne ikke finnes"
-
-#: lib/RT/CustomField_Overlay.pm:1157
-#. ($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:419
-msgid "Custom field value could not be deleted"
-msgstr "Fleksifeltets verdi kunne ikke slettes"
-
-#: lib/RT/CustomField_Overlay.pm:1169
-msgid "Custom field value could not be found"
-msgstr "Fleksifeltets verdi kunne ikke finnes"
-
-#: lib/RT/CustomField_Overlay.pm:1171 lib/RT/CustomField_Overlay.pm:417
-msgid "Custom field value deleted"
-msgstr "Fleksifeltverdi slettet"
-
-#: html/Elements/SelectGroups:51 html/Elements/SelectUsers:51 lib/RT/Transaction_Overlay.pm:638
-msgid "CustomField"
-msgstr "FleksiFelt"
-
-#: html/Prefs/MyRT.html:78 html/Prefs/Quicksearch.html:70 html/Prefs/Search.html:75
-msgid "Customize"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Data error"
-msgstr "Datafeil"
-
-#: html/SelfService/Display.html:61 html/Ticket/Create.html:203 html/Ticket/Elements/ShowSummary:83 html/Ticket/Elements/Tabs:116 html/Ticket/ModifyAll.html:65
-msgid "Dates"
-msgstr "Datoer"
-
-#: lib/RT/Date.pm:452
-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:222
-msgid "Default Autoresponse template"
-msgstr "Standard Autosvarmal"
-
-#: html/Tools/Offline.html:61
-msgid "Default Queue"
-msgstr ""
-
-#: html/Tools/Offline.html:70
-msgid "Default Requestor"
-msgstr ""
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr "Standard Adminkommentarmal"
-
-#: etc/initialdata:275
-msgid "Default admin correspondence template"
-msgstr "Standard Adminkorrespondensemal"
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr "Standard korrespondensemal"
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr "Standard transaksjonsmal"
-
-#: NOT FOUND IN SOURCE
-msgid "Default: %1/%2 changed from %3 to %4"
-msgstr "Standard: %1/%2 endret seg fra %3 til %4"
-
-#: html/User/Delegation.html:46 html/User/Delegation.html:49
-msgid "Delegate rights"
-msgstr "Deleger rettigheter"
-
-#: lib/RT/System.pm:84
-msgid "Delegate specific rights which have been granted to you."
-msgstr "Deleger spesifikke rettigheter som har blitt gitt til deg."
-
-#: lib/RT/System.pm:84
-msgid "DelegateRights"
-msgstr "DelegerRettigheter"
-
-#: html/User/Elements/Tabs:59
-msgid "Delegation"
-msgstr "Delegering"
-
-#: html/Admin/Elements/EditScrips:75 html/Search/Elements/EditFormat:103 html/Search/Elements/EditQuery:57 html/Search/Elements/EditSearches:63 html/Widgets/SelectionBox:204
-msgid "Delete"
-msgstr "Slett"
-
-#: html/Admin/Elements/EditTemplates:79
-msgid "Delete Template"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:220
-#. ($msg)
-msgid "Delete failed: %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:74
-msgid "Delete selected scrips"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "Delete tickets"
-msgstr "Slett saker"
-
-#: html/Search/Bulk.html:159
-msgid "Delete values"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "DeleteTicket"
-msgstr "SlettSak"
-
-#: lib/RT/SavedSearch.pm:218
-msgid "Deleted search"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Deleting this object could break referential integrity"
-msgstr "Sletting av dette objektet kan føre til inkonsistens"
-
-#: lib/RT/Queue_Overlay.pm:394
-msgid "Deleting this object would break referential integrity"
-msgstr "Sletting av dette objektet vil føre til inkonsistens"
-
-#: lib/RT/User_Overlay.pm:512
-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:73
-msgid "Deny"
-msgstr "Nekt"
-
-#: html/Elements/EditLinks:140 html/Elements/EditLinks:66 html/Elements/ShowLinks:58 html/Ticket/Create.html:221 html/Ticket/Elements/BulkLinks:56 html/Ticket/Elements/ShowDependencies:53
-msgid "Depended on by"
-msgstr "Avhengighet fra"
-
-#: NOT FOUND IN SOURCE
-msgid "Dependencies: \\n"
-msgstr "Avhengigheter: \\n"
-
-#: lib/RT/Transaction_Overlay.pm:718
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:758
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:715
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:755
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr ""
-
-#: html/Elements/EditLinks:136 html/Elements/EditLinks:57 html/Elements/SelectLinkType:48 html/Elements/ShowLinks:48 html/Ticket/Create.html:220 html/Ticket/Elements/BulkLinks:52 html/Ticket/Elements/ShowDependencies:46
-msgid "Depends on"
-msgstr "Avhengig av"
-
-#: NOT FOUND IN SOURCE
-msgid "DependsOn"
-msgstr "AvhengigAv"
-
-#: html/Search/Elements/DisplayOptions:86
-msgid "Desc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:56
-msgid "Descending"
-msgstr "Synkende"
-
-#: html/SelfService/Create.html:100 html/Ticket/Create.html:152
-msgid "Describe the issue below"
-msgstr "Beskriv problemet under"
-
-#: html/Admin/CustomFields/Modify.html:61 html/Admin/Elements/AddCustomFieldValue:57 html/Admin/Elements/EditCustomField:60 html/Admin/Elements/EditCustomFieldValues:56 html/Admin/Elements/EditScrip:55 html/Admin/Elements/ModifyTemplate:57 html/Admin/Groups/Modify.html:71 html/Admin/Queues/Modify.html:69 html/Search/Elements/EditSearches:56 html/User/Groups/Modify.html:70
-msgid "Description"
-msgstr "Beskrivelse"
-
-#: NOT FOUND IN SOURCE
-msgid "Details"
-msgstr "Detaljer"
-
-#: html/Search/Elements/EditFormat:71 html/Ticket/Elements/Tabs:108
-msgid "Display"
-msgstr "Vis"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "Display Access Control List"
-msgstr "Vis Rettigheter"
-
-#: html/Search/Elements/DisplayOptions:46
-msgid "Display Columns"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "Display Scrip templates for this queue"
-msgstr "Vis Scrip-maler for denne køen"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "Display Scrips for this queue"
-msgstr "Vis Scrip-maler for denne køen"
-
-#: html/Ticket/Elements/ShowHistory:59
-msgid "Display mode"
-msgstr "Visningsmodus"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "Display saved searches for this group"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Display ticket #%1"
-msgstr "Vis saken #%1"
-
-#: html/Elements/Footer:61
-msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>"
-msgstr ""
-
-#: lib/RT/System.pm:75
-msgid "Do anything and everything"
-msgstr "Gjør hva som helst"
-
-#: html/Elements/Refresh:51
-msgid "Don't refresh this page."
-msgstr "Ikke last denne siden på nytt"
-
-#: NOT FOUND IN SOURCE
-msgid "Don't show search results"
-msgstr "Ikke vis søkeresultat"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "Download"
-msgstr "Last ned"
-
-#: html/Admin/Groups/index.html:61 html/Admin/Users/index.html:64
-msgid "Download as a tab-delimited file"
-msgstr ""
-
-#: html/Elements/SelectDateType:53 html/Ticket/Create.html:209 html/Ticket/Elements/EditDates:66 html/Ticket/Elements/Reminders:133 html/Ticket/Elements/ShowDates:64 lib/RT/Ticket_Overlay.pm:1173
-msgid "Due"
-msgstr "Innen"
-
-#: NOT FOUND IN SOURCE
-msgid "Due date '%1' could not be parsed"
-msgstr "Innendato '%1' kunne ikke tolkes"
-
-#: NOT FOUND IN SOURCE
-msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
-msgstr "FEIL: Kunne ikke laste sak '%1': %2.\\n"
-
-#: html/Elements/Quicksearch:48 html/Elements/ShowSearch:49 html/index.html:107
-msgid "Edit"
-msgstr "Rediger"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Conditions"
-msgstr "Rediger Forhold"
-
-#: html/Search/Bulk.html:149
-msgid "Edit Custom Fields"
-msgstr ""
-
-#: html/Admin/Elements/ObjectCustomFields:92 html/Admin/Queues/CustomFields.html:64 html/Admin/Users/CustomFields.html:64
-#. ($Object->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "Rediger fleksifelt for %1"
-
-#: html/Admin/Global/CustomFields/Groups.html:54
-msgid "Edit Custom Fields for all groups"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/Users.html:54
-msgid "Edit Custom Fields for all users"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/Queue-Tickets.html:54 html/Admin/Global/CustomFields/Queue-Transactions.html:54
-msgid "Edit Custom Fields for tickets in all queues"
-msgstr ""
-
-#: html/Search/Bulk.html:188 html/Ticket/ModifyLinks.html:57
-msgid "Edit Links"
-msgstr "Rediger Forhold"
-
-#: html/Search/Edit.html:68
-msgid "Edit Query"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:214
-msgid "Edit Search"
-msgstr ""
-
-#: html/Admin/Queues/Templates.html:63
-#. ($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"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "Edit saved searches for this group"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Edit scrips"
-msgstr "Rediger scrips"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:60 html/Admin/Global/index.html:67
-msgid "Edit system templates"
-msgstr "Rediger systemmal"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit templates for %1"
-msgstr "Rediger maler for %1"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "EditSavedSearches"
-msgstr ""
-
-#: html/Admin/Queues/Modify.html:140
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr "Rediger Konfigurasjon for køen %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing Configuration for user %1"
-msgstr "Redigerer Konfigurasjonen av brukern %1"
-
-#: html/Admin/CustomFields/Modify.html:167 html/Admin/Elements/EditCustomField:120
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr "Redigerer Fleksifeltet %1"
-
-#: html/Admin/Groups/Members.html:53
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr "Redigerer medlemsskap for gruppen %1"
-
-#: html/User/Groups/Members.html:150
-#. ($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/Record.pm:1295 lib/RT/Record.pm:1372 lib/RT/Ticket_Overlay.pm:2518 lib/RT/Ticket_Overlay.pm:2608
-msgid "Either base or target must be specified"
-msgstr "Enten base eller mål må oppgis"
-
-#: html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65
-msgid "Email"
-msgstr "Epost"
-
-#: lib/RT/User_Overlay.pm:235
-msgid "Email address in use"
-msgstr "Epostaddresse i bruk"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailAddress"
-msgstr "EpostAddresse"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailEncoding"
-msgstr "EpostFormat"
-
-#: html/Admin/CustomFields/Modify.html:98 html/Admin/Elements/EditCustomField:72
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "Aktivt (Fjern merkingen for å deaktivere dette fleksifeltet)"
-
-#: html/Admin/Groups/Modify.html:84 html/User/Groups/Modify.html:74
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "Aktiv (Fjern merkingen for å deaktivere denne gruppen)"
-
-#: html/Admin/Queues/Modify.html:105
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "Aktiv (Fjern merkingen for å deaktivere denne køen)"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Custom Fields"
-msgstr "Aktive Fleksifelt"
-
-#: html/Admin/Queues/index.html:78
-msgid "Enabled Queues"
-msgstr "Aktive Køer"
-
-#: html/Admin/Elements/EditCustomField:136 html/Admin/Groups/Modify.html:150 html/Admin/Users/Modify.html:350 html/User/Groups/Modify.html:138
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr "Aktiv status %1"
-
-#: html/Admin/CustomFields/Modify.html:185 html/Admin/Queues/Modify.html:162
-#. (loc_fuzzy($msg))
-msgid "Enabled status: %1"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:64
-msgid "Enter multiple values"
-msgstr "Skriv multiple verdier"
-
-#: html/Elements/EditLinks:126
-msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces."
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:65
-msgid "Enter one value"
-msgstr "Skriv en verdi"
-
-#: html/Elements/EditLinks:123
-msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces."
-msgstr ""
-
-#: html/Elements/EditLinks:119 html/Search/Bulk.html:189
-msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces."
-msgstr "Skriv saker og/eller URIer som det skal linkes til. Separer dem med mellomrom"
-
-#: lib/RT/CustomField_Overlay.pm:66
-msgid "Enter up to %1 values"
-msgstr ""
-
-#: html/Elements/Login:76 html/SelfService/Error.html:46 html/SelfService/Error.html:47
-msgid "Error"
-msgstr "Feil"
-
-#: NOT FOUND IN SOURCE
-msgid "Error adding watcher"
-msgstr "Feilet ved opprettelse av Overvåker"
-
-#: lib/RT/Queue_Overlay.pm:672
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr "Feil i parameterne til Queue->AddWatcher"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Queue->DelWatcher"
-msgstr "Feil i parameterne til Queue->DelWatcher"
-
-#: lib/RT/Queue_Overlay.pm:833
-msgid "Error in parameters to Queue->DeleteWatcher"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1372
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr "Feil i parameterne til Ticket->AddWatcher"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Ticket->DelWatcher"
-msgstr "Feil i parameterne til Ticket->DelWatcher"
-
-#: lib/RT/Ticket_Overlay.pm:1538
-msgid "Error in parameters to Ticket->DeleteWatcher"
-msgstr ""
-
-#: bin/rt-crontool:285
-msgid "Escalate tickets"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:57
-msgid "Estimated"
-msgstr ""
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "Alle"
-
-#: bin/rt-crontool:271
-msgid "Example:"
-msgstr "Eksempel:"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalAuthId"
-msgstr "EksternAutId"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalContactInfoId"
-msgstr "EksternKontaktInfoId"
-
-#: html/Admin/Users/Modify.html:99
-msgid "Extra info"
-msgstr "Ekstra info"
-
-#: lib/RT/SavedSearch.pm:177
-msgid "Failed to create search attribute"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:376
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr "Kunne ikke finne pseudogruppen 'Privilgerte' brukere."
-
-#: lib/RT/User_Overlay.pm:383
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr "Kunne ikke finne 'pseudogruppen 'Upriviligerte' brukere"
-
-#: bin/rt-crontool:206
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr "Kunne ikke laste modulen %1. (%2)"
-
-#: lib/RT/SavedSearch.pm:152
-#. ($privacy)
-msgid "Failed to load object for %1"
-msgstr ""
-
-#: lib/RT/Date.pm:442
-msgid "Feb."
-msgstr "Feb."
-
-#: NOT FOUND IN SOURCE
-msgid "February"
-msgstr "Februar"
-
-#: html/Elements/SelectAttachmentField:50
-msgid "Filename"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:69
-msgid "Fill in multiple text areas"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:74
-msgid "Fill in multiple wikitext areas"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:70
-msgid "Fill in one text area"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:75
-msgid "Fill in one wikitext area"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:107 html/Admin/CustomFields/Modify.html:118
-msgid "Fill in this field with a URL."
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:71
-msgid "Fill in up to %1 text areas"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:76
-msgid "Fill in up to %1 wikitext areas"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Fin"
-msgstr "End"
-
-#: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:182 html/Ticket/Elements/EditBasics:97 lib/RT/Tickets_Overlay.pm:1841
-msgid "Final Priority"
-msgstr "Endelig Prioritet"
-
-#: lib/RT/Ticket_Overlay.pm:1164
-msgid "FinalPriority"
-msgstr "EndeligPrioritet"
-
-#: NOT FOUND IN SOURCE
-msgid "Find group whose"
-msgstr "Finn grupper hvor"
-
-#: html/Admin/Groups/index.html:72 html/Admin/Queues/People.html:82 html/Ticket/Elements/EditPeople:55
-msgid "Find groups whose"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Find new/open tickets"
-msgstr "Finn nye/Ã¥pne saker"
-
-#: html/Admin/Queues/People.html:78 html/Admin/Users/index.html:70 html/Ticket/Elements/EditPeople:51
-msgid "Find people whose"
-msgstr "Finn folk hvor"
-
-#: html/Search/Results.html:147
-msgid "Find tickets"
-msgstr "Finn saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Finish Approval"
-msgstr "Fullfør godkjennelse"
-
-#: html/Ticket/Elements/Tabs:81
-msgid "First"
-msgstr "Først"
-
-#: NOT FOUND IN SOURCE
-msgid "First page"
-msgstr "Første side"
-
-#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:766
-msgid "Foo Bar Baz"
-msgstr "Foo Bar Baz"
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:757
-msgid "Foo!"
-msgstr "Foo!"
-
-#: html/Search/Bulk.html:83
-msgid "Force change"
-msgstr "Tving gjennom endring"
-
-#: html/Search/Elements/EditFormat:52
-msgid "Format"
-msgstr ""
-
-#: html/Search/Results.html:145
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr "Fant %quant(%1) sak(er)"
-
-#: lib/RT/Record.pm:956
-msgid "Found Object"
-msgstr "Fant Objektet"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformContactInfo"
-msgstr "FriforkKontaktInfo"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformMultiple"
-msgstr "FriformMultipel"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformSingle"
-msgstr "FriformSingel"
-
-#: lib/RT/Date.pm:421
-msgid "Fri."
-msgstr "Fre."
-
-#: html/Ticket/Elements/ShowHistory:66 html/Ticket/Elements/ShowHistory:72
-msgid "Full headers"
-msgstr "Fulle headere"
-
-#: html/Tools/Offline.html:85
-msgid "Get template from file"
-msgstr ""
-
-#: 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:684
-#. ($New->Name)
-msgid "Given to %1"
-msgstr "Gitt til %1"
-
-#: html/Admin/Elements/Tabs:65 html/Admin/index.html:82
-msgid "Global"
-msgstr "Global"
-
-#: html/Admin/Elements/EditCustomFields:55
-msgid "Global Custom Fields"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Global Keyword Selections"
-msgstr "Globale Nøkkelordvalg"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Scrips"
-msgstr "Globale Scrip"
-
-#: html/Admin/Global/CustomFields/index.html:59
-msgid "Global custom field configuration"
-msgstr ""
-
-#: html/Admin/Global/MyRT.html:48
-#. ($pane)
-msgid "Global portlet %1 saved."
-msgstr ""
-
-#: html/Admin/Elements/SelectTemplate:59
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr "Globale maler: %1"
-
-#: html/Admin/CustomFields/index.html:80 html/Search/Results.html:90 html/Tools/Offline.html:89
-msgid "Go"
-msgstr ""
-
-#: html/Admin/Groups/index.html:67 html/Admin/Groups/index.html:73 html/Admin/Queues/People.html:80 html/Admin/Queues/People.html:84 html/Admin/Queues/index.html:66 html/Admin/Users/index.html:73 html/Elements/RefreshHomepage:48 html/Search/Results.html:74 html/Ticket/Elements/EditPeople:53 html/Ticket/Elements/EditPeople:57
-msgid "Go!"
-msgstr "Start!"
-
-#: NOT FOUND IN SOURCE
-msgid "Good pgp sig from %1\\n"
-msgstr "Gyldig pgp sig fra %1\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Goto page"
-msgstr "GÃ¥ til siden"
-
-#: html/Elements/GotoTicket:46 html/SelfService/Elements/GotoTicket:46
-msgid "Goto ticket"
-msgstr "GÃ¥ til saken"
-
-#: NOT FOUND IN SOURCE
-msgid "Grand"
-msgstr "Stor"
-
-#: html/Ticket/Elements/AddWatchers:67 html/Ticket/Elements/ShowGroupMembers:55 html/User/Elements/DelegateRights:99
-msgid "Group"
-msgstr "Gruppe"
-
-#: NOT FOUND IN SOURCE
-msgid "Group %1 %2: %3"
-msgstr "Gruppen %1 %2: %3"
-
-#: html/Admin/Elements/CustomFieldTabs:68 html/Admin/Elements/GroupTabs:66 html/Admin/Elements/QueueTabs:82 html/Admin/Elements/SystemTabs:65 html/Admin/Global/index.html:76
-msgid "Group Rights"
-msgstr "Grupperettigheter"
-
-#: lib/RT/Group_Overlay.pm:983
-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:109
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "Gruppen kunne ikke opprettes: %1"
-
-#: lib/RT/Group_Overlay.pm:521
-msgid "Group created"
-msgstr "Gruppen opprettet"
-
-#: lib/RT/Group_Overlay.pm:1155
-msgid "Group has no such member"
-msgstr "Gruppen har ikke det medlemmet"
-
-#: lib/RT/Group_Overlay.pm:963 lib/RT/Queue_Overlay.pm:748 lib/RT/Queue_Overlay.pm:808 lib/RT/Ticket_Overlay.pm:1430 lib/RT/Ticket_Overlay.pm:1510
-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/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:57 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:86 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1210
-msgid "Groups"
-msgstr "Grupper"
-
-#: lib/RT/Group_Overlay.pm:989
-msgid "Groups can't be members of their members"
-msgstr "Grupper kan ikke være medlemmer av sine medlemmer"
-
-#: html/Admin/Groups/index.html:86
-msgid "Groups matching search criteria"
-msgstr ""
-
-#: html/Ticket/Elements/ShowRequestor:77
-msgid "Groups this user belongs to"
-msgstr ""
-
-#: lib/RT/Interface/CLI.pm:94 lib/RT/Interface/CLI.pm:94
-msgid "Hello!"
-msgstr "Hallo!"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:773
-#. ($name)
-msgid "Hello, %1"
-msgstr "Hallo, %1"
-
-#: html/Admin/Elements/GroupTabs:70 html/Admin/Elements/UserTabs:64 html/Ticket/Elements/ShowHistory:53 html/Ticket/Elements/Tabs:111
-msgid "History"
-msgstr "Historikk"
-
-#: html/Admin/Groups/History.html:62
-#. ($GroupObj->Name)
-msgid "History of the group %1"
-msgstr ""
-
-#: html/Admin/Users/History.html:62
-#. ($UserObj->Name)
-msgid "History of the user %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "HomePhone"
-msgstr "HjemmeTelefon"
-
-#: html/Elements/Tabs:65
-msgid "Homepage"
-msgstr "Hjemmeside"
-
-#: html/Elements/SelectTimeUnits:48
-msgid "Hours"
-msgstr ""
-
-#: lib/RT/Base.pm:119
-#. (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/Search/Build.html:460 lib/RT/Report/Tickets.pm:415
-msgid "I'm lost"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1766
-msgid "Id"
-msgstr "Id"
-
-#: html/Admin/Users/Modify.html:65 html/User/Prefs.html:60
-msgid "Identity"
-msgstr "Identitet"
-
-#: etc/initialdata:429
-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"
-
-#: html/Tools/Offline.html:74
-msgid "If no Requestor is specified, create tickets with this requestor."
-msgstr ""
-
-#: html/Tools/Offline.html:65
-msgid "If no queue is specified, create tickets in this queue."
-msgstr ""
-
-#: bin/rt-crontool:267
-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:126 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:128 html/Ticket/ModifyPeople.html:60
-msgid "If you've updated anything above, be sure to"
-msgstr "Hvis du har oppdatert noe over, sørg for at"
-
-#: lib/RT/Record.pm:947
-msgid "Illegal value for %1"
-msgstr "Ugyldig verdig for %1"
-
-#: lib/RT/Record.pm:950
-msgid "Immutable field"
-msgstr "LÃ¥st felt"
-
-#: NOT FOUND IN SOURCE
-msgid "Include disabled custom fields in listing."
-msgstr "Inkluder deaktiverte fleksifelt i listen."
-
-#: html/Admin/Groups/index.html:65
-msgid "Include disabled groups in listing."
-msgstr ""
-
-#: html/Admin/Queues/index.html:65
-msgid "Include disabled queues in listing."
-msgstr "Inkluder deaktiverte køer i listen."
-
-#: html/Admin/Users/index.html:71
-msgid "Include disabled users in search."
-msgstr "Inkluder deaktiverte brukere i søket."
-
-#: html/Admin/CustomFields/Modify.html:113
-msgid "Include page"
-msgstr ""
-
-#: html/Search/Build.html:486 lib/RT/Report/Tickets.pm:441
-msgid "Incomplete Query"
-msgstr ""
-
-#: html/Search/Build.html:483 lib/RT/Report/Tickets.pm:438
-msgid "Incomplete query"
-msgstr ""
-
-#: html/Search/Elements/PickBasics:148 lib/RT/Tickets_Overlay.pm:1816
-msgid "Initial Priority"
-msgstr "Startprioritet"
-
-#: lib/RT/Ticket_Overlay.pm:1163 lib/RT/Ticket_Overlay.pm:1165
-msgid "InitialPriority"
-msgstr "StartPrioritet"
-
-#: lib/RT/ScripAction_Overlay.pm:133
-msgid "Input error"
-msgstr "Feil i inntasting"
-
-#: html/Elements/ValidateCustomFields:68 lib/RT/CustomField_Overlay.pm:1021 lib/RT/CustomField_Overlay.pm:1162
-#. ($self->FriendlyPattern)
-#. ($CF->FriendlyPattern)
-msgid "Input must match %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Interest noted"
-msgstr "Interesse registrert"
-
-#: lib/RT/Ticket_Overlay.pm:3503
-msgid "Internal Error"
-msgstr "Intern Feil"
-
-#: lib/RT/Record.pm:308
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "Intern Feil: %1"
-
-#: lib/RT/Group_Overlay.pm:668
-msgid "Invalid Group Type"
-msgstr "Ugyldig gruppetype"
-
-#: lib/RT/Principal_Overlay.pm:161
-msgid "Invalid Right"
-msgstr "Ugyldige rettigheter"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid Type"
-msgstr "Ugyldig Type"
-
-#: lib/RT/Record.pm:952
-msgid "Invalid data"
-msgstr "Ugyldig data"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid owner. Defaulting to 'nobody'."
-msgstr "Ugydlig eier. Setter til 'nobody'."
-
-#: lib/RT/CustomField_Overlay.pm:207 lib/RT/CustomField_Overlay.pm:678
-#. ($msg)
-msgid "Invalid pattern: %1"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:157 lib/RT/Template_Overlay.pm:244
-msgid "Invalid queue"
-msgstr "Ugyldig kø"
-
-#: lib/RT/ACE_Overlay.pm:264 lib/RT/ACE_Overlay.pm:273 lib/RT/ACE_Overlay.pm:279 lib/RT/ACE_Overlay.pm:290
-msgid "Invalid right"
-msgstr "Ugyldige rettigheter"
-
-#: lib/RT/Record.pm:283
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "Ugyldig verdi for %1"
-
-#: lib/RT/Record.pm:1610
-msgid "Invalid value for custom field"
-msgstr "Ugyldig verdi for fleksifeltet."
-
-#: lib/RT/Ticket_Overlay.pm:424
-msgid "Invalid value for status"
-msgstr "Ugyldig verdi for status"
-
-#: bin/rt-crontool:268
-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:269
-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:231
-msgid "It takes several arguments:"
-msgstr "Det tar flere parametere:"
-
-#: html/Search/Elements/EditFormat:85
-msgid "Italic"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Items pending my approval"
-msgstr "Ting som venter på min godkjenning"
-
-#: lib/RT/Date.pm:441
-msgid "Jan."
-msgstr "Jan."
-
-#: NOT FOUND IN SOURCE
-msgid "January"
-msgstr "Januar"
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "Join or leave this group"
-msgstr "Bli med i eller forlat denne gruppen"
-
-#: lib/RT/Date.pm:447
-msgid "Jul."
-msgstr "Jul."
-
-#: NOT FOUND IN SOURCE
-msgid "July"
-msgstr "Juli"
-
-#: html/Ticket/Elements/Tabs:125
-msgid "Jumbo"
-msgstr "Total"
-
-#: lib/RT/Date.pm:446
-msgid "Jun."
-msgstr "Jun."
-
-#: NOT FOUND IN SOURCE
-msgid "June"
-msgstr "Juni"
-
-#: NOT FOUND IN SOURCE
-msgid "Keyword"
-msgstr "Nøkkelord"
-
-#: NOT FOUND IN SOURCE
-msgid "Lang"
-msgstr "Språk"
-
-#: html/Admin/Users/Modify.html:94 html/User/Prefs.html:76
-msgid "Language"
-msgstr ""
-
-#: html/Search/Elements/EditFormat:79
-msgid "Large"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:96
-msgid "Last"
-msgstr "Siste"
-
-#: html/Ticket/Elements/EditDates:59 html/Ticket/Elements/ShowDates:60
-msgid "Last Contact"
-msgstr "Siste Kontakt"
-
-#: html/Elements/SelectDateType:50
-msgid "Last Contacted"
-msgstr "Sist kontaktet"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Notified"
-msgstr "Sist Informert"
-
-#: html/Elements/SelectDateType:51
-msgid "Last Updated"
-msgstr "Sist Oppdatert"
-
-#: NOT FOUND IN SOURCE
-msgid "LastUpdated"
-msgstr "SistOppdatert"
-
-#: html/Search/Elements/PickBasics:103
-msgid "LastUpdatedBy"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:68
-msgid "Left"
-msgstr "Igjen"
-
-#: html/Admin/Users/Modify.html:109
-msgid "Let this user access RT"
-msgstr "La denne brukeren få tilgang til RT"
-
-#: html/Admin/Users/Modify.html:113
-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"
-
-#: html/Search/Elements/EditFormat:68
-msgid "Link"
-msgstr ""
-
-#: lib/RT/Record.pm:1306
-msgid "Link already exists"
-msgstr "Lenke finnes alt"
-
-#: lib/RT/Record.pm:1320
-msgid "Link could not be created"
-msgstr "Lenke kunne ikke opprettes"
-
-#: lib/RT/Record.pm:1326
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "Lenke opprettet (%1)"
-
-#: lib/RT/Record.pm:1387
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "Lenke slettet (%1)"
-
-#: lib/RT/Record.pm:1393
-msgid "Link not found"
-msgstr "Lenke ble ikke funnet"
-
-#: html/Ticket/ModifyLinks.html:46 html/Ticket/ModifyLinks.html:50
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr "Knytt sak #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Link ticket %1"
-msgstr "Knytt sak %1"
-
-#: html/Admin/CustomFields/Modify.html:102
-msgid "Link values to"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:700
-msgid "Linking. Permission denied"
-msgstr ""
-
-#: html/Ticket/Create.html:216 html/Ticket/Elements/ShowSummary:89 html/Ticket/Elements/Tabs:120 html/Ticket/ModifyAll.html:78
-msgid "Links"
-msgstr "Lenker"
-
-#: html/Search/Elements/EditSearches:75
-msgid "Load"
-msgstr ""
-
-#: html/Search/Elements/EditSearches:73
-msgid "Load saved search:"
-msgstr ""
-
-#: lib/RT/System.pm:86
-msgid "LoadSavedSearch"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:64
-msgid "Loaded perl modules"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:111
-#. ($self->Name)
-msgid "Loaded search %1"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126
-msgid "Location"
-msgstr "Lokasjon"
-
-#: NOT FOUND IN SOURCE
-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:91
-#. ("<span>".$session{'CurrentUser'}->Name."</span>")
-msgid "Logged in as %1"
-msgstr "Logget inn som %1"
-
-#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:100 html/Elements/Login:68 html/Elements/Login:84 lib/RT/StyleGuide.pod:797
-msgid "Login"
-msgstr "Innlogging"
-
-#: html/Elements/Header:101
-msgid "Logout"
-msgstr "Logg av"
-
-#: lib/RT/CustomField_Overlay.pm:932
-msgid "Lookup type mismatch"
-msgstr ""
-
-#: html/Search/Bulk.html:82
-msgid "Make Owner"
-msgstr "Sett Eier"
-
-#: html/Search/Bulk.html:106
-msgid "Make Status"
-msgstr "Sett Status"
-
-#: html/Search/Bulk.html:114
-msgid "Make date Due"
-msgstr "Sett tidsfrist "
-
-#: html/Search/Bulk.html:116
-msgid "Make date Resolved"
-msgstr "Sett løsningsdato"
-
-#: html/Search/Bulk.html:110
-msgid "Make date Started"
-msgstr "Sett startdato"
-
-#: html/Search/Bulk.html:108
-msgid "Make date Starts"
-msgstr "Sett startdato"
-
-#: html/Search/Bulk.html:112
-msgid "Make date Told"
-msgstr "Sett informert dato"
-
-#: html/Search/Bulk.html:102
-msgid "Make priority"
-msgstr "Sett prioritet"
-
-#: html/Search/Bulk.html:104
-msgid "Make queue"
-msgstr "Sett Kø"
-
-#: html/Search/Bulk.html:100
-msgid "Make subject"
-msgstr "Sett Emne"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "Make this group visible to user"
-msgstr ""
-
-#: html/Admin/index.html:78
-msgid "Manage custom fields and custom field values"
-msgstr ""
-
-#: html/Admin/index.html:69
-msgid "Manage groups and group membership"
-msgstr "Sett grupper og gruppemedlemsskap"
-
-#: html/Admin/index.html:85
-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:74
-msgid "Manage queues and queue-specific properties"
-msgstr "Rediger køer og kø-spesifike egenskaper"
-
-#: html/Admin/index.html:64
-msgid "Manage users and passwords"
-msgstr "Rediger brukere og passord"
-
-#: lib/RT/Date.pm:443
-msgid "Mar."
-msgstr "Mar."
-
-#: NOT FOUND IN SOURCE
-msgid "March"
-msgstr "Mars"
-
-#: NOT FOUND IN SOURCE
-msgid "May"
-msgstr "Mai"
-
-#: lib/RT/Date.pm:445
-msgid "May."
-msgstr "Mai."
-
-#: lib/RT/Transaction_Overlay.pm:731
-#. ($value)
-msgid "Member %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:771
-#. ($value)
-msgid "Member %1 deleted"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:1000
-msgid "Member added"
-msgstr "Medlem lagt til"
-
-#: lib/RT/Group_Overlay.pm:1162
-msgid "Member deleted"
-msgstr "Medlem slettet"
-
-#: lib/RT/Group_Overlay.pm:1166
-msgid "Member not deleted"
-msgstr "Medlem ikke slettet"
-
-#: html/Elements/SelectLinkType:47
-msgid "Member of"
-msgstr "Medlem av"
-
-#: NOT FOUND IN SOURCE
-msgid "MemberOf"
-msgstr "MedlemAv"
-
-#: html/Admin/Elements/GroupTabs:63 html/User/Elements/GroupTabs:63
-msgid "Members"
-msgstr "Medlemmer"
-
-#: lib/RT/Transaction_Overlay.pm:728
-#. ($value)
-msgid "Membership in %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:768
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr ""
-
-#: html/Admin/Elements/UserTabs:61
-msgid "Memberships"
-msgstr ""
-
-#: html/Admin/Users/Memberships.html:60
-#. ($UserObj->Name)
-msgid "Memberships of the user %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2893
-msgid "Merge Successful"
-msgstr "Fletting vellykket"
-
-#: lib/RT/Ticket_Overlay.pm:2780
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr "Fletting feilet. Kunne ikke sette EffektivId"
-
-#: lib/RT/Ticket_Overlay.pm:2788
-msgid "Merge failed. Couldn't set Status"
-msgstr ""
-
-#: html/Elements/EditLinks:131 html/Ticket/Elements/BulkLinks:48
-msgid "Merge into"
-msgstr "Flett inn i"
-
-#: lib/RT/Transaction_Overlay.pm:734
-#. ($value)
-msgid "Merged into %1"
-msgstr ""
-
-#: html/Search/Bulk.html:143 html/Ticket/Update.html:118
-msgid "Message"
-msgstr "Melding"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:164
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2451
-msgid "Message could not be recorded"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2454
-msgid "Message recorded"
-msgstr ""
-
-#: html/Ticket/Elements/PreviewScrips:122
-msgid "Messages about this ticket will not be sent to..."
-msgstr ""
-
-#: html/Elements/SelectTimeUnits:47
-msgid "Minutes"
-msgstr ""
-
-#: html/Search/Build.html:490 lib/RT/Report/Tickets.pm:445
-msgid "Mismatched parentheses"
-msgstr ""
-
-#: lib/RT/Record.pm:954
-msgid "Missing a primary key?: %1"
-msgstr "Mangler en primærnøkkel?: %1"
-
-#: html/Admin/Users/Modify.html:193 html/User/Prefs.html:92
-msgid "Mobile"
-msgstr "Mobil"
-
-#: NOT FOUND IN SOURCE
-msgid "MobilePhone"
-msgstr "MobilTelefon"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "Modify Access Control List"
-msgstr "Endre Tilgangslister"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Custom Field %1"
-msgstr "Endre Fleksifeltet %1"
-
-#: html/Admin/Elements/ObjectCustomFields:96
-#. (loc(lc($FriendlySubTypes)), loc(lc($Types)))
-msgid "Modify Custom Fields which apply to %1 for all %2"
-msgstr ""
-
-#: html/Admin/Elements/ObjectCustomFields:98
-#. (loc(lc($Types)))
-msgid "Modify Custom Fields which apply to all %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Custom Fields which apply to all queues"
-msgstr "Endre Fleksifelt som gjelder for alle køer"
-
-#: html/Admin/Global/GroupRights.html:106 html/Admin/Groups/GroupRights.html:94 html/Admin/Queues/GroupRights.html:107
-msgid "Modify Group Rights"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:105 html/User/Groups/Members.html:101
-msgid "Modify Members"
-msgstr ""
-
-#: html/User/Delegation.html:58
-msgid "Modify Rights"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "Modify Scrip templates for this queue"
-msgstr "Endre Scripmaler for denne køen"
-
-#: lib/RT/Queue_Overlay.pm:100
-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/Global/UserRights.html:75 html/Admin/Groups/UserRights.html:76 html/Admin/Queues/UserRights.html:75
-msgid "Modify User Rights"
-msgstr ""
-
-#: html/Admin/Queues/CustomField.html:66
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr "Endre et fleksifelt for køen %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify a CustomField that applies to all queues"
-msgstr "Endre et fleksifelt som gjelder for alle køer"
-
-#: html/Admin/Queues/Scrip.html:82
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "Endre et scrip for køen %1"
-
-#: html/Admin/Global/Scrip.html:75
-msgid "Modify a scrip that applies to all queues"
-msgstr "Endre et scrip som gjelder for alle køer"
-
-#: html/Admin/CustomFields/Objects.html:90
-#. ($CF->Name)
-msgid "Modify associated objects for %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Modify dates for # %1"
-msgstr "Endre datoer for # %1"
-
-#: html/Ticket/ModifyDates.html:46 html/Ticket/ModifyDates.html:50
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "Endre datoer for #%1"
-
-#: html/Ticket/ModifyDates.html:57
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "Endre datoer for sak # %1"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:65 html/Admin/Global/index.html:72
-msgid "Modify global custom fields"
-msgstr ""
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:70 html/Admin/Global/GroupRights.html:46 html/Admin/Global/GroupRights.html:49 html/Admin/Global/index.html:77
-msgid "Modify global group rights"
-msgstr "Endre globale grupperettigheter"
-
-#: html/Admin/Global/GroupRights.html:54
-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:46 html/Admin/Global/UserRights.html:49 html/Admin/Global/index.html:81
-msgid "Modify global user rights"
-msgstr "Endre globale brukerrettigheter"
-
-#: html/Admin/Global/UserRights.html:54
-msgid "Modify global user rights."
-msgstr "Endre globale brukerrettigheter"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "Modify group metadata or delete group"
-msgstr "Endre gruppens metadata eller slette gruppen"
-
-#: html/Admin/CustomFields/GroupRights.html:164
-#. ($CustomFieldObj->Name)
-msgid "Modify group rights for custom field %1"
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:46 html/Admin/Groups/GroupRights.html:50 html/Admin/Groups/GroupRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify group rights for group %1"
-msgstr "Endre grupperettigheter for %1 gruppen"
-
-#: html/Admin/Queues/GroupRights.html:46 html/Admin/Queues/GroupRights.html:50
-#. ($QueueObj->Name)
-msgid "Modify group rights for queue %1"
-msgstr "Endre grupperettigheter %1 køen"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "Modify membership roster for this group"
-msgstr "Endre medlemsliste for denne gruppen"
-
-#: lib/RT/System.pm:82
-msgid "Modify one's own RT account"
-msgstr "Endre sin egen RT konto"
-
-#: html/Admin/Queues/People.html:46 html/Admin/Queues/People.html:50
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "Endre hvem som er relatert til %1 køen"
-
-#: html/Ticket/ModifyPeople.html:46 html/Ticket/ModifyPeople.html:50 html/Ticket/ModifyPeople.html:57
-#. ($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:67
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "Endre scrips for %1 køen"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:56 html/Admin/Global/Scrips.html:65 html/Admin/Global/index.html:63
-msgid "Modify scrips which apply to all queues"
-msgstr "Endre scrips som gjelder alle køer"
-
-#: html/Admin/Global/Template.html:102 html/Admin/Global/Template.html:46 html/Admin/Global/Template.html:51 html/Admin/Queues/Template.html:99
-#. (loc($TemplateObj->Name()))
-#. ($TemplateObj->id)
-msgid "Modify template %1"
-msgstr "Endre mal %1"
-
-#: html/Admin/Global/Templates.html:65
-msgid "Modify templates which apply to all queues"
-msgstr "Endre maler som gjelder for alle køer"
-
-#: html/Admin/Global/index.html:85
-msgid "Modify the default \"RT at a glance\" view"
-msgstr ""
-
-#: html/Admin/Groups/Modify.html:119 html/User/Groups/Modify.html:107
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "Endre gruppen %1"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "Modify the queue watchers"
-msgstr "Endre overvåkere for køen"
-
-#: html/Admin/Users/Modify.html:309
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "Endre brukeren %1"
-
-#: html/Ticket/ModifyAll.html:58
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "Endre sak # %1"
-
-#: html/Ticket/Modify.html:46 html/Ticket/Modify.html:49 html/Ticket/Modify.html:55
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "Endre sak #%1"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "Modify tickets"
-msgstr "Endre saker"
-
-#: html/Admin/CustomFields/UserRights.html:157
-#. ($CustomFieldObj->Name)
-msgid "Modify user rights for custom field %1"
-msgstr ""
-
-#: html/Admin/Groups/UserRights.html:46 html/Admin/Groups/UserRights.html:50 html/Admin/Groups/UserRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify user rights for group %1"
-msgstr "Endre brukerrettigheter for %1 gruppen"
-
-#: html/Admin/Queues/UserRights.html:46 html/Admin/Queues/UserRights.html:50
-#. ($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:94
-msgid "ModifyACL"
-msgstr "EndreACL"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "ModifyCustomField"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "ModifyOwnMembership"
-msgstr "EndreEgetMedlemskap"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "ModifyQueueWatchers"
-msgstr "EndreKøOvervåkere"
-
-#: lib/RT/Queue_Overlay.pm:100
-msgid "ModifyScrips"
-msgstr "EndreScrips"
-
-#: lib/RT/System.pm:82
-msgid "ModifySelf"
-msgstr "EndreSegSelv"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "ModifyTemplate"
-msgstr "EndreMal"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "ModifyTicket"
-msgstr "EndreSak"
-
-#: lib/RT/Date.pm:417
-msgid "Mon."
-msgstr "Man."
-
-#: html/Ticket/Elements/ShowRequestor:61
-#. ($name)
-msgid "More about %1"
-msgstr "Mer om %1"
-
-#: html/Admin/Elements/PickCustomFields:83
-msgid "Move down"
-msgstr "Flytt ned"
-
-#: html/Admin/Elements/PickCustomFields:75
-msgid "Move up"
-msgstr "Flytt opp"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:48
-msgid "Multiple"
-msgstr "Flere"
-
-#: lib/RT/User_Overlay.pm:226
-msgid "Must specify 'Name' attribute"
-msgstr "MÃ¥ spesifisere attributten 'Navn'"
-
-#: html/SelfService/Elements/MyRequests:57
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "Mine %1 saker"
-
-#: NOT FOUND IN SOURCE
-msgid "My Approvals"
-msgstr "Mine saker til godkjenning"
-
-#: html/Tools/Elements/Tabs:63
-msgid "My Day"
-msgstr ""
-
-#: html/Approvals/index.html:46 html/Approvals/index.html:47
-msgid "My approvals"
-msgstr "Mine saker til godkjenning"
-
-#: html/Search/Elements/SearchPrivacy:50 html/Search/Elements/SelectSearchObject:53 html/Search/Elements/SelectSearchesForObjects:54
-msgid "My saved searches"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:58 html/Admin/Elements/AddCustomFieldValue:53 html/Admin/Elements/EditCustomField:55 html/Admin/Elements/EditCustomFieldValues:55 html/Admin/Elements/ModifyTemplate:49 html/Admin/Groups/Modify.html:65 html/Search/Bulk.html:157 html/User/Groups/Modify.html:65
-msgid "Name"
-msgstr "Navn"
-
-#: lib/RT/User_Overlay.pm:233
-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:73
-msgid "Never"
-msgstr "Aldri"
-
-#: NOT FOUND IN SOURCE
-msgid "New"
-msgstr "Ny"
-
-#: html/Elements/EditLinks:117
-msgid "New Links"
-msgstr "Nye forhold"
-
-#: html/Admin/Users/Modify.html:119 html/User/Prefs.html:109
-msgid "New Password"
-msgstr "Nytt Passord"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "Ny, Venter på Godkjennelse"
-
-#: html/Ticket/Elements/Tabs:212
-msgid "New Search"
-msgstr "Nytt Søk"
-
-#: html/Admin/Elements/CustomFieldTabs:93 html/Admin/Queues/CustomField.html:73
-msgid "New custom field"
-msgstr "Nytt fleksifelt"
-
-#: html/Admin/Elements/GroupTabs:77 html/User/Elements/GroupTabs:73
-msgid "New group"
-msgstr "Ny gruppe"
-
-#: html/SelfService/Prefs.html:53
-msgid "New password"
-msgstr "Nytt passord"
-
-#: lib/RT/User_Overlay.pm:816
-msgid "New password notification sent"
-msgstr "Melding om nytt passord sendt"
-
-#: html/Admin/Elements/QueueTabs:95
-msgid "New queue"
-msgstr "Ny kø"
-
-#: html/Ticket/Elements/Reminders:118
-msgid "New reminder:"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "New request"
-msgstr "Ny forespørsel"
-
-#: html/Admin/Elements/SelectRights:65
-msgid "New rights"
-msgstr "Nye rettigheter"
-
-#: html/Admin/Global/Scrip.html:63 html/Admin/Global/Scrips.html:60 html/Admin/Queues/Scrip.html:71 html/Admin/Queues/Scrips.html:76
-msgid "New scrip"
-msgstr "Nytt scrip"
-
-#: NOT FOUND IN SOURCE
-msgid "New search"
-msgstr "Nytt søk"
-
-#: html/Admin/Global/Template.html:81 html/Admin/Global/Templates.html:60 html/Admin/Queues/Template.html:79 html/Admin/Queues/Templates.html:71
-msgid "New template"
-msgstr "Ny mal"
-
-#: html/SelfService/Elements/Tabs:84 html/SelfService/Elements/Tabs:88
-msgid "New ticket"
-msgstr "Ny sak"
-
-#: lib/RT/Ticket_Overlay.pm:2757
-msgid "New ticket doesn't exist"
-msgstr "Ny sak eksistere ikke"
-
-#: html/Admin/Elements/UserTabs:81
-msgid "New user"
-msgstr "Ny bruker"
-
-#: html/Admin/Elements/CreateUserCalled:47
-msgid "New user called"
-msgstr "Ny bruker kalt"
-
-#: html/Admin/Queues/People.html:76 html/Ticket/Elements/EditPeople:50
-msgid "New watchers"
-msgstr "Ny overvåker"
-
-#: NOT FOUND IN SOURCE
-msgid "New window setting"
-msgstr "Instillinger for nytt vindu"
-
-#: html/Helpers/CalPopup.html:58 html/Ticket/Elements/Tabs:92
-msgid "Next"
-msgstr "Neste"
-
-#: html/Elements/TicketList:104
-msgid "Next Page"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Next page"
-msgstr "Neste side"
-
-#: NOT FOUND IN SOURCE
-msgid "NickName"
-msgstr "KalleNavn"
-
-#: html/Admin/Users/Modify.html:84 html/User/Prefs.html:72
-msgid "Nickname"
-msgstr "Kallenavn"
-
-#: html/Admin/CustomFields/UserRights.html:145
-msgid "No Class defined"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:166 html/Admin/Elements/EditCustomField:119
-msgid "No CustomField"
-msgstr "Ingen FleksiFelt"
-
-#: html/Admin/CustomFields/GroupRights.html:103
-msgid "No CustomField defined"
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:105 html/Admin/Groups/UserRights.html:92
-msgid "No Group defined"
-msgstr "Ingen grupper definert"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:482
-msgid "No Query"
-msgstr ""
-
-#: html/Admin/Queues/GroupRights.html:118 html/Admin/Queues/UserRights.html:89
-msgid "No Queue defined"
-msgstr "Ingen kø definert"
-
-#: bin/rt-crontool:73
-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:100 html/Admin/Queues/Template.html:97
-msgid "No Template"
-msgstr "Ingen Mal"
-
-#: NOT FOUND IN SOURCE
-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:77
-msgid "No action"
-msgstr "Ingen handling"
-
-#: lib/RT/Record.pm:949
-msgid "No column specified"
-msgstr "Ingen kolonne spesifisert"
-
-#: NOT FOUND IN SOURCE
-msgid "No command found\\n"
-msgstr "Ingen kommando funnet\\n"
-
-#: html/Ticket/Elements/ShowRequestor:68
-msgid "No comment entered about this user"
-msgstr "Ingen kommentar skrevet om denne brukeren"
-
-#: NOT FOUND IN SOURCE
-msgid "No correspondence attached"
-msgstr "Ingen korrespondanse vedlagt"
-
-#: lib/RT/Action/Generic.pm:185 lib/RT/Condition/Generic.pm:197 lib/RT/Search/ActiveTicketsInQueue.pm:77 lib/RT/Search/Generic.pm:134 lib/RT/Search/Googleish.pm:78
-#. (ref $self)
-msgid "No description for %1"
-msgstr "Ingen beskrivelse for %1"
-
-#: lib/RT/Users_Overlay.pm:190
-msgid "No group specified"
-msgstr "Ingen gruppe spesifisert"
-
-#: html/Admin/Groups/index.html:52
-msgid "No groups matching search criteria found."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2393
-msgid "No message attached"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:1034
-msgid "No password set"
-msgstr "Passordet er ikke satt"
-
-#: lib/RT/Queue_Overlay.pm:361
-msgid "No permission to create queues"
-msgstr "Ingen tilgang til å opprette køer"
-
-#: lib/RT/Ticket_Overlay.pm:420
-#. ($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:186
-msgid "No permission to create users"
-msgstr "Ikke tilgang til å opprette brukere"
-
-#: html/SelfService/Display.html:167
-msgid "No permission to display that ticket"
-msgstr "Ikke tilgang til å vise den saken"
-
-#: lib/RT/SavedSearch.pm:156
-msgid "No permission to save system-wide searches"
-msgstr ""
-
-#: html/SelfService/Update.html:117
-msgid "No permission to view update ticket"
-msgstr "Ingen tilgang til å se oppdatering av saken"
-
-#: lib/RT/Queue_Overlay.pm:795 lib/RT/Ticket_Overlay.pm:1489
-msgid "No principal specified"
-msgstr "Ingen primær spesifisert"
-
-#: html/Admin/Queues/People.html:175 html/Admin/Queues/People.html:185
-msgid "No principals selected."
-msgstr "Ingen primære spesifisert"
-
-#: html/Admin/Queues/index.html:57
-msgid "No queues matching search criteria found."
-msgstr "Det er ingen køer som matcher søkekriteriet"
-
-#: html/Admin/Elements/SelectRights:106
-msgid "No rights found"
-msgstr "Ingen rettigheter funnet"
-
-#: html/Admin/Elements/SelectRights:53
-msgid "No rights granted."
-msgstr "Ingen rettigheter tildelt"
-
-#: lib/RT/SavedSearch.pm:196
-msgid "No search loaded"
-msgstr ""
-
-#: html/Search/Bulk.html:232
-msgid "No search to operate on."
-msgstr "Ingen søk å behandle"
-
-#: html/Elements/RT__Ticket/ColumnMap:137 html/Search/Results.rdf:78
-msgid "No subject"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "No ticket id specified"
-msgstr "Ingen saksid oppgitt"
-
-#: lib/RT/Transaction_Overlay.pm:528 lib/RT/Transaction_Overlay.pm:565
-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:55
-msgid "No users matching search criteria found."
-msgstr "Fant ingen brukere som treffer søkekriteriene."
-
-#: NOT FOUND IN SOURCE
-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/Record.pm:946
-msgid "No value sent to _Set!\\n"
-msgstr "Ingen verdi sendt til _Set!\\n"
-
-#: html/Elements/QuickCreate:59
-msgid "Nobody"
-msgstr "Ingen"
-
-#: lib/RT/Record.pm:951
-msgid "Nonexistant field?"
-msgstr "Ukjent felt?"
-
-#: html/Search/Chart:71 html/Search/Elements/Chart:88
-msgid "Not Set"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Not logged in"
-msgstr "Ikke logget inn"
-
-#: html/Elements/Header:96
-msgid "Not logged in."
-msgstr "Ikke logget inn."
-
-#: lib/RT/Date.pm:397
-msgid "Not set"
-msgstr "Ikke satt"
-
-#: html/NoAuth/Reminder.html:48
-msgid "Not yet implemented."
-msgstr "Ikke implementert enda."
-
-#: NOT FOUND IN SOURCE
-msgid "Not yet implemented...."
-msgstr "Ikke implementert enda...."
-
-#: html/Approvals/Elements/Approve:81
-msgid "Notes"
-msgstr "Notater"
-
-#: lib/RT/User_Overlay.pm:819
-msgid "Notification could not be sent"
-msgstr "Melding kunne ikke sendes"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr "Raporter til AdminCc"
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "Rapporter til AdminCc som kommentar"
-
-#: etc/initialdata:93 etc/upgrade/3.1.17/content:6
-msgid "Notify Ccs"
-msgstr ""
-
-#: etc/initialdata:89 etc/upgrade/3.1.17/content:2
-msgid "Notify Ccs as Comment"
-msgstr ""
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr "Rapporter til andre mottakere"
-
-#: etc/initialdata:124
-msgid "Notify Other Recipients as Comment"
-msgstr "Rapporter til andre mottakere som kommentar"
-
-#: etc/initialdata:85
-msgid "Notify Owner"
-msgstr "Rapporter til eier"
-
-#: etc/initialdata:81
-msgid "Notify Owner as Comment"
-msgstr "Rapportert til eier som kommentar"
-
-#: etc/initialdata:376
-msgid "Notify Owner of their rejected ticket"
-msgstr ""
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr ""
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr ""
-
-#: etc/initialdata:334
-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:77
-msgid "Notify Requestors"
-msgstr "Rapporter til kunde"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "Rapporter til Kunder og Cc"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "Rapporter til Kunder og Cc som kommentar"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "Rapporter til Kunder Cc og AdminCc"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "Rapporter til Kunder Cc og AdminCc som Kommentar"
-
-#: lib/RT/Date.pm:451
-msgid "Nov."
-msgstr "Nov."
-
-#: NOT FOUND IN SOURCE
-msgid "November"
-msgstr "November"
-
-#: html/Search/Elements/SelectAndOr:47
-msgid "OR"
-msgstr ""
-
-#: lib/RT/Record.pm:322
-msgid "Object could not be created"
-msgstr "Objekter kunne ikke opprettes"
-
-#: lib/RT/Record.pm:123
-msgid "Object could not be deleted"
-msgstr ""
-
-#: lib/RT/Record.pm:341
-msgid "Object created"
-msgstr "Objektet ble opprettet"
-
-#: lib/RT/Record.pm:120
-msgid "Object deleted"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:72 html/Admin/Elements/ObjectCustomFields:63
-#. ($ObjectType)
-#. ($LookupType)
-msgid "Object of type %1 cannot take custom fields"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:967
-msgid "Object type mismatch"
-msgstr ""
-
-#: lib/RT/Date.pm:450
-msgid "Oct."
-msgstr "Okt."
-
-#: NOT FOUND IN SOURCE
-msgid "October"
-msgstr "Oktober"
-
-#: html/Tools/Elements/Tabs:55
-msgid "Offline"
-msgstr ""
-
-#: html/Tools/Offline.html:49
-msgid "Offline edits"
-msgstr ""
-
-#: html/Tools/Offline.html:46
-msgid "Offline upload"
-msgstr ""
-
-#: html/Elements/SelectDateRelation:56
-msgid "On"
-msgstr "Ved"
-
-#: lib/RT/Transaction_Overlay.pm:326
-#. ($self->CreatedAsString(), $self->CreatorObj->Name())
-msgid "On %1, %2 wrote:"
-msgstr ""
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "Ved Kommentar"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "Ved Korrespondanse"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "Ved Opprettelse"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "Ved Eierskifte"
-
-#: etc/initialdata:177 etc/upgrade/3.1.17/content:15
-msgid "On Priority Change"
-msgstr ""
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "Ved Køendring"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "Ved Løsning"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "Ved statusendring"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "Ved Transaksjon"
-
-#: html/Approvals/Elements/PendingMyApproval:70
-#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='CreatedAfter' />")
-msgid "Only show approvals for requests created after %1"
-msgstr "Vis kun godkjennelse for saker opprettet etter %1"
-
-#: html/Approvals/Elements/PendingMyApproval:68
-#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore' id='CreatedBefore' />")
-msgid "Only show approvals for requests created before %1"
-msgstr "Bare vis godkjennelse for saker opprettet før %1"
-
-#: html/Admin/CustomFields/index.html:75
-msgid "Only show custom fields for:"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Open"
-msgstr "Ã…pne"
-
-#: html/SelfService/index.html:46
-msgid "Open Tickets"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:160
-msgid "Open it"
-msgstr "Ã…pne den"
-
-#: NOT FOUND IN SOURCE
-msgid "Open requests"
-msgstr "Åpne forespørsler"
-
-#: html/SelfService/Elements/Tabs:75
-msgid "Open tickets"
-msgstr "Ã…pne saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in a new window"
-msgstr "Ã…pne saker (fra utlisting) i et nytt vindu"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in another window"
-msgstr "Ã…pne saker (fra utlisting) it et annet vinud"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "Ã…pne saker ved korrespondanse"
-
-#: html/Prefs/MyRT.html:70
-msgid "Options"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:59
-msgid "Order by"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Ordering and sorting"
-msgstr "Rekkefølge og sortering"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:129
-msgid "Organization"
-msgstr "Organisasjon"
-
-#: html/Approvals/Elements/Approve:53
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr "Opprinnelig sak: #%1"
-
-#: lib/RT/Transaction_Overlay.pm:622
-msgid "Outgoing email about a comment recorded"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:626
-msgid "Outgoing email recorded"
-msgstr ""
-
-#: html/Admin/Queues/Modify.html:90
-msgid "Over time, priority moves toward"
-msgstr "Over tid beveger prioriteten seg mot"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "Own tickets"
-msgstr "Eie saker"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "OwnTicket"
-msgstr "EieSak"
-
-#: etc/initialdata:38 html/Elements/QuickCreate:56 html/Search/Elements/PickBasics:101 html/Ticket/Create.html:72 html/Ticket/Elements/EditBasics:61 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/Reminders:129 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:110 lib/RT/Tickets_Overlay.pm:2006
-msgid "Owner"
-msgstr "Eier"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner changed from %1 to %2"
-msgstr "Eier endret fra %1 til %2"
-
-#: lib/RT/Ticket_Overlay.pm:505
-msgid "Owner could not be set."
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:672
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "Eier ble tvunget til å endres fra %1 til %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner is"
-msgstr "Eier er"
-
-#: html/Elements/TicketList:78
-#. ($Page, int($TotalFound/$Rows)+$oddRows)
-msgid "Page %1 of %2"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:198 html/User/Prefs.html:96
-msgid "Pager"
-msgstr "Personsøker"
-
-#: NOT FOUND IN SOURCE
-msgid "PagerPhone"
-msgstr "PersonSøker"
-
-#: NOT FOUND IN SOURCE
-msgid "Parent"
-msgstr "Forelder"
-
-#: html/Elements/EditLinks:144 html/Elements/EditLinks:76 html/Elements/ShowLinks:68 html/Ticket/Create.html:222 html/Ticket/Elements/BulkLinks:60
-msgid "Parents"
-msgstr "Foreldre"
-
-#: html/Elements/Login:95 html/User/Prefs.html:105
-msgid "Password"
-msgstr "Passord"
-
-#: html/NoAuth/Reminder.html:46
-msgid "Password Reminder"
-msgstr "Passordhint"
-
-#: lib/RT/Transaction_Overlay.pm:781 lib/RT/User_Overlay.pm:1045
-msgid "Password changed"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:1037 lib/RT/User_Overlay.pm:214
-#. ($RT::MinimumPasswordLength)
-msgid "Password needs to be at least %1 characters long"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:1044
-msgid "Password set"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Password too short"
-msgstr "For kort passord"
-
-#: html/User/Prefs.html:240
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "Passord: %1"
-
-#: lib/RT/User_Overlay.pm:1030
-msgid "Password: Permission Denied"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:364
-msgid "Passwords do not match."
-msgstr "Passordene stemmer ikke overens."
-
-#: html/User/Prefs.html:242
-msgid "Passwords do not match. Your password has not been changed"
-msgstr "Passordene stemmer ikke overrens. Passordet ble ikke endret"
-
-#: html/Ticket/Elements/ShowSummary:62 html/Ticket/Elements/Tabs:119 html/Ticket/ModifyAll.html:72
-msgid "People"
-msgstr "Personer"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr "Kjør en brukerdefinert handling"
-
-#: html/Admin/Tools/Configuration.html:94
-msgid "Perl configuration"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:251 lib/RT/ACE_Overlay.pm:257 lib/RT/ACE_Overlay.pm:580 lib/RT/ACE_Overlay.pm:590 lib/RT/ACE_Overlay.pm:600 lib/RT/ACE_Overlay.pm:665 lib/RT/Attribute_Overlay.pm:158 lib/RT/Attribute_Overlay.pm:164 lib/RT/Attribute_Overlay.pm:405 lib/RT/Attribute_Overlay.pm:414 lib/RT/Attribute_Overlay.pm:427 lib/RT/CurrentUser.pm:116 lib/RT/CurrentUser.pm:125 lib/RT/CustomField_Overlay.pm:1017 lib/RT/CustomField_Overlay.pm:1138 lib/RT/CustomField_Overlay.pm:1281 lib/RT/CustomField_Overlay.pm:172 lib/RT/CustomField_Overlay.pm:189 lib/RT/CustomField_Overlay.pm:200 lib/RT/CustomField_Overlay.pm:374 lib/RT/CustomField_Overlay.pm:403 lib/RT/CustomField_Overlay.pm:763 lib/RT/CustomField_Overlay.pm:936 lib/RT/CustomField_Overlay.pm:971 lib/RT/Group_Overlay.pm:1117 lib/RT/Group_Overlay.pm:1121 lib/RT/Group_Overlay.pm:1130 lib/RT/Group_Overlay.pm:1240 lib/RT/Group_Overlay.pm:1244 lib/RT/Group_Overlay.pm:1250 lib/RT/Group_Overlay.pm:445 lib/RT/Group_Overlay.pm:542 lib/RT/Group_Overlay.pm:620 lib/RT/Group_Overlay.pm:628 lib/RT/Group_Overlay.pm:726 lib/RT/Group_Overlay.pm:730 lib/RT/Group_Overlay.pm:736 lib/RT/Group_Overlay.pm:922 lib/RT/Group_Overlay.pm:926 lib/RT/Group_Overlay.pm:939 lib/RT/Queue_Overlay.pm:1054 lib/RT/Queue_Overlay.pm:140 lib/RT/Queue_Overlay.pm:158 lib/RT/Queue_Overlay.pm:657 lib/RT/Queue_Overlay.pm:667 lib/RT/Queue_Overlay.pm:681 lib/RT/Queue_Overlay.pm:819 lib/RT/Queue_Overlay.pm:828 lib/RT/Queue_Overlay.pm:841 lib/RT/Scrip_Overlay.pm:149 lib/RT/Scrip_Overlay.pm:160 lib/RT/Scrip_Overlay.pm:224 lib/RT/Scrip_Overlay.pm:538 lib/RT/Template_Overlay.pm:108 lib/RT/Template_Overlay.pm:277 lib/RT/Ticket_Overlay.pm:1357 lib/RT/Ticket_Overlay.pm:1367 lib/RT/Ticket_Overlay.pm:1381 lib/RT/Ticket_Overlay.pm:1522 lib/RT/Ticket_Overlay.pm:1532 lib/RT/Ticket_Overlay.pm:1546 lib/RT/Ticket_Overlay.pm:1663 lib/RT/Ticket_Overlay.pm:1983 lib/RT/Ticket_Overlay.pm:2126 lib/RT/Ticket_Overlay.pm:2296 lib/RT/Ticket_Overlay.pm:2346 lib/RT/Ticket_Overlay.pm:2525 lib/RT/Ticket_Overlay.pm:2538 lib/RT/Ticket_Overlay.pm:2614 lib/RT/Ticket_Overlay.pm:2627 lib/RT/Ticket_Overlay.pm:2748 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2990 lib/RT/Ticket_Overlay.pm:3000 lib/RT/Ticket_Overlay.pm:3005 lib/RT/Ticket_Overlay.pm:3224 lib/RT/Ticket_Overlay.pm:3228 lib/RT/Ticket_Overlay.pm:3371 lib/RT/Ticket_Overlay.pm:3497 lib/RT/Transaction_Overlay.pm:516 lib/RT/Transaction_Overlay.pm:523 lib/RT/Transaction_Overlay.pm:551 lib/RT/Transaction_Overlay.pm:558 lib/RT/User_Overlay.pm:1176 lib/RT/User_Overlay.pm:1856 lib/RT/User_Overlay.pm:369 lib/RT/User_Overlay.pm:735 lib/RT/User_Overlay.pm:774
-msgid "Permission Denied"
-msgstr "Ingen Tilgang"
-
-#: lib/RT/Template_Overlay.pm:238 lib/RT/Template_Overlay.pm:247
-msgid "Permission denied"
-msgstr ""
-
-#: lib/RT/Template_Overlay.pm:372
-msgid "Permissions denied"
-msgstr ""
-
-#: html/User/Elements/Tabs:56
-msgid "Personal Groups"
-msgstr "Personlige Grupper"
-
-#: html/User/Groups/index.html:51 html/User/Groups/index.html:61
-msgid "Personal groups"
-msgstr "Personlige grupper"
-
-#: html/User/Elements/DelegateRights:58
-msgid "Personal groups:"
-msgstr "Personlige grupper:"
-
-#: html/Admin/Users/Modify.html:180 html/User/Prefs.html:81
-msgid "Phone numbers"
-msgstr "Telefonnummer"
-
-#: NOT FOUND IN SOURCE
-msgid "Placeholder"
-msgstr "Stedholder"
-
-#: NOT FOUND IN SOURCE
-msgid "Pref"
-msgstr "Pref"
-
-#: html/Elements/Header:93 html/Elements/Tabs:91 html/SelfService/Elements/Tabs:95 html/SelfService/Prefs.html:46 html/User/Prefs.html:46 html/User/Prefs.html:49
-msgid "Preferences"
-msgstr "Instillinger"
-
-#: html/Admin/Users/MyRT.html:75
-#. ($pane, $UserObj->Name)
-msgid "Preferences %1 for user %2 ."
-msgstr ""
-
-#: html/Prefs/MyRT.html:141
-#. ($pane)
-msgid "Preferences saved for %1."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Prefs"
-msgstr "Pref"
-
-#: lib/RT/Action/Generic.pm:195
-msgid "Prepare Stubbed"
-msgstr "Klargjør Forkortet"
-
-#: html/Helpers/CalPopup.html:56 html/Ticket/Elements/Tabs:84
-msgid "Prev"
-msgstr "Forrige"
-
-#: html/Elements/TicketList:101
-msgid "Previous Page"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Previous page"
-msgstr "Forrige side"
-
-#: NOT FOUND IN SOURCE
-msgid "Pri"
-msgstr "Pri"
-
-#: lib/RT/ACE_Overlay.pm:157 lib/RT/ACE_Overlay.pm:239 lib/RT/ACE_Overlay.pm:569
-#. ($args{'PrincipalId'})
-msgid "Principal %1 not found."
-msgstr "Primær %1 ikke funnet."
-
-#: html/Search/Elements/PickBasics:147 html/Ticket/Create.html:181 html/Ticket/Elements/EditBasics:92 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1790
-msgid "Priority"
-msgstr "Prioritet"
-
-#: html/Admin/Queues/Modify.html:86
-msgid "Priority starts at"
-msgstr "Prioritet starter på"
-
-#: html/Search/Elements/EditSearches:50
-msgid "Privacy:"
-msgstr ""
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "Priviligert"
-
-#: html/Admin/Users/Modify.html:342 html/User/Prefs.html:231
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr "Priviligert status: %1"
-
-#: html/Admin/Users/index.html:102
-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/Search/Build.html:121
-msgid "Query Builder"
-msgstr ""
-
-#: html/Search/Elements/Chart:101
-msgid "Query:"
-msgstr ""
-
-#: html/Elements/QueueSummary:48 html/Elements/QuickCreate:54 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:62 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/Tools/Reports/CreatedByDates.html:85 html/Tools/Reports/ResolvedByDates.html:86 html/Tools/Reports/ResolvedByOwner.html:66 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1617
-msgid "Queue"
-msgstr "Kø"
-
-#: html/Admin/Queues/CustomField.html:63 html/Admin/Queues/Scrip.html:61 html/Admin/Queues/Scrips.html:69 html/Admin/Queues/Templates.html:65
-#. ($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/Queues/Modify.html:64
-msgid "Queue Name"
-msgstr "Kønavn"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Scrips"
-msgstr "Køscrip"
-
-#: lib/RT/Queue_Overlay.pm:365
-msgid "Queue already exists"
-msgstr "Køen eksisterer allerede"
-
-#: lib/RT/Queue_Overlay.pm:374 lib/RT/Queue_Overlay.pm:380
-msgid "Queue could not be created"
-msgstr "Køen kunne ikke opprettes"
-
-#: html/Ticket/Create.html:244 lib/t/regression/01ticket_link_searching.t:17
-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:384 lib/RT/StyleGuide.pod:809
-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:126 lib/RT/CustomField_Overlay.pm:197
-msgid "Queue not found"
-msgstr "Køen ikke funnet"
-
-#: html/Admin/Elements/Tabs:59 html/Admin/index.html:72
-msgid "Queues"
-msgstr "Køer"
-
-#: html/Elements/MyAdminQueues:46
-msgid "Queues I administer"
-msgstr ""
-
-#: html/Elements/MySupportQueues:46
-msgid "Queues I'm an AdminCc for"
-msgstr ""
-
-#: html/Elements/Quicksearch:47 html/Prefs/Elements/Tabs:58 html/Prefs/Quicksearch.html:70
-msgid "Quick search"
-msgstr "Raskt søk"
-
-#: html/Elements/QuickCreate:47
-msgid "Quick ticket creation"
-msgstr ""
-
-#: html/Search/Results.html:81
-msgid "RSS"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "RT %1"
-msgstr "RT %1"
-
-#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:796
-#. ($RT::VERSION, $RT::rtname)
-msgid "RT %1 for %2"
-msgstr "RT %1 for %2"
-
-#: NOT FOUND IN SOURCE
-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:46 html/Admin/index.html:47
-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:63 html/SelfService/Error.html:62
-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/Admin/Tools/Configuration.html:73
-msgid "RT Variables"
-msgstr ""
-
-#: html/Admin/Elements/SystemTabs:71 html/Admin/Elements/UserTabs:67 html/Admin/Global/MyRT.html:1 html/Admin/Global/MyRT.html:12 html/Admin/Global/MyRT.html:4 html/Admin/Global/index.html:84 html/Admin/Users/MyRT.html:21 html/Prefs/MyRT.html:66 html/Prefs/MyRT.html:78 html/User/Elements/Tabs:65 html/index.html:1 html/index.html:75
-msgid "RT at a glance"
-msgstr "RT oversikt"
-
-#: html/Admin/Users/MyRT.html:30
-#. ($UserObj->Name)
-msgid "RT at a glance for the user %1"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:117
-msgid "RT can include content from another web service when showing this custom field."
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:106
-msgid "RT can make this custom field's values into hyperlinks to another service."
-msgstr ""
-
-#: 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"
-
-#: html/Elements/SetupSessionCookie:100
-msgid "RT couldn't store your session."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "RT couldn't validate this PGP signature. \\n"
-msgstr "RT kunne ikke validere denne PGP signaturen. \\n"
-
-#: html/Elements/Logo:49 html/Elements/PageLayout:172
-#. ($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"
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: html/Search/Simple.html:58
-msgid "RT will look for anything else you enter in ticket subjects."
-msgstr ""
-
-#: 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"
-
-#: html/Admin/CustomFields/Modify.html:108 html/Admin/CustomFields/Modify.html:119
-msgid "RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively"
-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 "RT's epost kommandomodus krever PGP autentisering. Meldingen din var enten ikke signert, eller signaturen din kunne ikke bekreftes."
-
-#: html/Admin/Users/Modify.html:79 html/User/Prefs.html:69
-msgid "Real Name"
-msgstr "Ekte Navn"
-
-#: NOT FOUND IN SOURCE
-msgid "RealName"
-msgstr "EkteNavn"
-
-#: lib/RT/Transaction_Overlay.pm:725
-#. ($value)
-msgid "Reference by %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:765
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:722
-#. ($value)
-msgid "Reference to %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:762
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr ""
-
-#: html/Elements/EditLinks:103 html/Elements/EditLinks:156 html/Elements/ShowLinks:92 html/Ticket/Create.html:225 html/Ticket/Elements/BulkLinks:72
-msgid "Referred to by"
-msgstr "Referert til av"
-
-#: html/Elements/EditLinks:152 html/Elements/EditLinks:94 html/Elements/SelectLinkType:49 html/Elements/ShowLinks:82 html/Ticket/Create.html:224 html/Ticket/Elements/BulkLinks:68
-msgid "Refers to"
-msgstr "Refererer til"
-
-#: NOT FOUND IN SOURCE
-msgid "RefersTo"
-msgstr "RefererTil"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine"
-msgstr "Redefiner"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine search"
-msgstr "Redefiner søket"
-
-#: html/Elements/Refresh:57
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "Last siden på nytt hvert %1 minutt."
-
-#: lib/RT/Transaction_Overlay.pm:811
-#. ($ticket->Subject)
-msgid "Reminder '%1' added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:824
-#. ($ticket->Subject)
-msgid "Reminder '%1' completed"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:817
-#. ($ticket->Subject)
-msgid "Reminder '%1' reopened"
-msgstr ""
-
-#: html/Ticket/Reminders.html:46
-#. ($Ticket->Id)
-msgid "Reminder ticket #%1"
-msgstr ""
-
-#: html/Elements/MyReminders:48 html/Ticket/Elements/ShowSummary:75 html/Ticket/Elements/Tabs:122 html/Ticket/Reminders.html:52
-msgid "Reminders"
-msgstr ""
-
-#: html/Ticket/Reminders.html:50
-#. ($Ticket->Id)
-msgid "Reminders for ticket #%1"
-msgstr ""
-
-#: html/Search/Bulk.html:94
-msgid "Remove AdminCc"
-msgstr "Fjern AdminCc"
-
-#: html/Search/Bulk.html:90
-msgid "Remove Cc"
-msgstr "Fjern Cc"
-
-#: html/Search/Bulk.html:86
-msgid "Remove Requestor"
-msgstr "Fjern Kunde"
-
-#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:147
-msgid "Reply"
-msgstr "Svar"
-
-#: html/Admin/Queues/Modify.html:72
-msgid "Reply Address"
-msgstr ""
-
-#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:94 html/Ticket/Update.html:78
-msgid "Reply to requestors"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "Reply to tickets"
-msgstr "Svar på sak"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "ReplyToTicket"
-msgstr "SvarPÃ¥Sak"
-
-#: html/Tools/Elements/Tabs:59 html/Tools/Reports/index.html:46 html/Tools/Reports/index.html:47
-msgid "Reports"
-msgstr ""
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:111
-msgid "Requestor"
-msgstr "Kunde"
-
-#: NOT FOUND IN SOURCE
-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:63 html/Ticket/Create.html:80 html/Ticket/Elements/EditPeople:69 html/Ticket/Elements/ShowPeople:52
-msgid "Requestors"
-msgstr "Kunder"
-
-#: html/Admin/Queues/Modify.html:96
-msgid "Requests should be due in"
-msgstr "Forespørsler skal være behandlet innen"
-
-#: lib/RT/Attribute_Overlay.pm:146
-#. ('Object')
-msgid "Required parameter '%1' not specified"
-msgstr ""
-
-#: html/Elements/Submit:83
-msgid "Reset"
-msgstr "Reset"
-
-#: html/Admin/Users/MyRT.html:15 html/Prefs/MyRT.html:60
-msgid "Reset to default"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:84
-msgid "Residence"
-msgstr "Hjemme"
-
-#: html/Ticket/Elements/Tabs:156
-msgid "Resolve"
-msgstr "Løs"
-
-#: html/Ticket/Update.html:156
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "Løs saknr #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:49 lib/RT/Ticket_Overlay.pm:1172
-msgid "Resolved"
-msgstr "Løst"
-
-#: html/Tools/Reports/Elements/Tabs:55
-msgid "Resolved by owner"
-msgstr ""
-
-#: html/Tools/Reports/Elements/Tabs:59
-msgid "Resolved in date range"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByDates.html:52
-msgid "Resolved tickets in period, grouped by owner"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByOwner.html:50
-msgid "Resolved tickets, grouped by owner"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Response to requestors"
-msgstr "Svar til kunder"
-
-#: html/Elements/ListActions:46 html/Search/Elements/NewListActions:47
-msgid "Results"
-msgstr "Resultater"
-
-#: NOT FOUND IN SOURCE
-msgid "Results per page"
-msgstr "Resultater per side"
-
-#: html/Admin/Users/Modify.html:126 html/User/Prefs.html:116
-msgid "Retype Password"
-msgstr "Skriv Passord igjen"
-
-#: html/Search/Elements/EditSearches:61
-msgid "Revert"
-msgstr ""
-
-#: 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:630
-msgid "Right Delegated"
-msgstr "Rettighet Deligert"
-
-#: lib/RT/ACE_Overlay.pm:320
-msgid "Right Granted"
-msgstr "Rettighet Tildelt"
-
-#: lib/RT/ACE_Overlay.pm:178
-msgid "Right Loaded"
-msgstr "Rettighet lastet"
-
-#: lib/RT/ACE_Overlay.pm:695 lib/RT/ACE_Overlay.pm:716
-msgid "Right could not be revoked"
-msgstr "Rettigheten kunne ikke trekkes tilbake"
-
-#: html/User/Delegation.html:85
-msgid "Right not found"
-msgstr "Rettighet ikke funnet"
-
-#: lib/RT/ACE_Overlay.pm:560 lib/RT/ACE_Overlay.pm:655
-msgid "Right not loaded."
-msgstr "Rettighet ikke lastet."
-
-#: lib/RT/ACE_Overlay.pm:712
-msgid "Right revoked"
-msgstr "Rettighet fjernet"
-
-#: html/Admin/Elements/UserTabs:70
-msgid "Rights"
-msgstr "Rettigheter"
-
-#: html/Admin/CustomFields/GroupRights.html:129 lib/RT/Interface/Web.pm:961
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr "Rettigheter kunne ikke tildeles for %1"
-
-#: html/Admin/CustomFields/GroupRights.html:156 lib/RT/Interface/Web.pm:990
-#. ($object_type)
-msgid "Rights could not be revoked for %1"
-msgstr "Rettigheter kunne ikke trekkes tilbake for %1"
-
-#: html/Admin/Global/GroupRights.html:72 html/Admin/Queues/GroupRights.html:74
-msgid "Roles"
-msgstr "Roller"
-
-#: NOT FOUND IN SOURCE
-msgid "RootApproval"
-msgstr "RootGodkjenning"
-
-#: html/Prefs/MyRT.html:72
-msgid "Rows per box"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:93
-msgid "Rows per page"
-msgstr ""
-
-#: lib/RT/Date.pm:422
-msgid "Sat."
-msgstr "Lør."
-
-#: html/Prefs/MyRT.html:72 html/Prefs/Quicksearch.html:64 html/Prefs/Search.html:69 html/Prefs/Search.html:69 html/Search/Elements/EditSearches:70 html/Widgets/SelectionBox:211
-msgid "Save"
-msgstr ""
-
-#: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:88 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html:239 html/Prefs/Quicksearch.html:64 html/Prefs/SearchOptions.html:63 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:61 html/Ticket/ModifyPeople.html:60 html/User/Groups/Modify.html:77
-msgid "Save Changes"
-msgstr "Lagre Endringer"
-
-#: html/User/Prefs.html:181
-msgid "Save Preferences"
-msgstr ""
-
-#: html/Ticket/Elements/PreviewScrips:131
-msgid "Save changes"
-msgstr "Lage endringer"
-
-#: lib/RT/SavedSearch.pm:173
-#. ($name)
-msgid "Saved search %1"
-msgstr ""
-
-#: html/Admin/Elements/ListGlobalScrips:60 html/Admin/Global/Scrip.html:77 html/Admin/Queues/Scrip.html:84
-#. ($scrip->Id)
-#. ($id)
-msgid "Scrip #%1"
-msgstr "Scrip #%1"
-
-#: lib/RT/Scrip_Overlay.pm:203
-msgid "Scrip Created"
-msgstr "Scrip Opprettet"
-
-#: html/Admin/Elements/EditScrip:52
-msgid "Scrip Fields"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:109
-msgid "Scrip deleted"
-msgstr "Scrip slettet"
-
-#: html/Admin/Elements/QueueTabs:67 html/Admin/Elements/SystemTabs:54 html/Admin/Global/index.html:62
-msgid "Scrips"
-msgstr "Scrip"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips for %1\\n"
-msgstr "Scrip for %1\\n"
-
-#: html/Admin/Queues/Scrips.html:55
-msgid "Scrips which apply to all queues"
-msgstr "Scrip som gjelder for alle køer"
-
-#: html/Elements/SimpleSearch:48 html/Search/Simple.html:63
-msgid "Search"
-msgstr "Søk"
-
-#: NOT FOUND IN SOURCE
-msgid "Search Criteria"
-msgstr "Søkekriteria"
-
-#: html/Prefs/SearchOptions.html:47 html/Prefs/SearchOptions.html:50
-msgid "Search Preferences"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:115
-msgid "Search attribute load failure"
-msgstr ""
-
-#: html/Approvals/Elements/PendingMyApproval:59
-msgid "Search for approvals"
-msgstr "Søk etter godkjenninger"
-
-#: html/Search/Simple.html:67
-msgid "Search for tickets"
-msgstr ""
-
-#: html/Search/Simple.html:55
-msgid "Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>. RT will look for anything else you enter in ticket bodies and attachments."
-msgstr ""
-
-#: html/User/Elements/Tabs:62
-msgid "Search options"
-msgstr ""
-
-#: html/Search/Chart.html:56
-#. ($PrimaryGroupBy)
-msgid "Search results grouped by %1"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:203
-#. ($msg)
-msgid "Search update: %1"
-msgstr ""
-
-#: html/Search/Simple.html:57
-msgid "Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>."
-msgstr ""
-
-#: bin/rt-crontool:265
-msgid "Security:"
-msgstr "Sikkerhet:"
-
-#: html/Elements/ShowCustomFields:98
-msgid "See also:"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "See custom fields"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "See exact outgoing email messages and their recipeients"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "See ticket private commentary"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "See ticket summaries"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "SeeCustomField"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "SeeGroup"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "SeeQueue"
-msgstr "SeKø"
-
-#: html/Admin/CustomFields/index.html:46 html/Admin/CustomFields/index.html:49
-msgid "Select a Custom Field"
-msgstr ""
-
-#: html/Admin/Groups/index.html:78
-msgid "Select a group"
-msgstr "Velg en gruppe"
-
-#: html/Admin/Queues/index.html:54
-msgid "Select a queue"
-msgstr "Velg en kø"
-
-#: html/SelfService/CreateTicketInQueue.html:48
-msgid "Select a queue for your new ticket"
-msgstr ""
-
-#: html/Admin/Users/index.html:46 html/Admin/Users/index.html:49 html/Admin/Users/index.html:52
-msgid "Select a user"
-msgstr "Velg en bruker"
-
-#: html/Admin/Elements/CustomFieldTabs:90
-msgid "Select custom field"
-msgstr "Velg fleksifelt"
-
-#: html/Admin/Global/CustomFields/index.html:70
-msgid "Select custom fields for all user groups"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:65
-msgid "Select custom fields for all users"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:76
-msgid "Select custom fields for tickets in all queues"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:83
-msgid "Select custom fields for transactions on tickets in all queues"
-msgstr ""
-
-#: html/Admin/Elements/GroupTabs:75 html/User/Elements/GroupTabs:71
-msgid "Select group"
-msgstr "Velg gruppe"
-
-#: lib/RT/CustomField_Overlay.pm:59
-msgid "Select multiple values"
-msgstr "Velg flere verdier"
-
-#: lib/RT/CustomField_Overlay.pm:60
-msgid "Select one value"
-msgstr "Velg en verdi"
-
-#: html/Admin/Elements/QueueTabs:92
-msgid "Select queue"
-msgstr "Velg kø"
-
-#: html/Prefs/Quicksearch.html:53
-msgid "Select queues to be displayed on the \"RT at a glance\" page"
-msgstr ""
-
-#: html/Admin/Global/Scrip.html:59 html/Admin/Global/Scrips.html:57 html/Admin/Queues/Scrip.html:67 html/Admin/Queues/Scrips.html:73
-msgid "Select scrip"
-msgstr "Velg scrip"
-
-#: html/Admin/Global/Template.html:78 html/Admin/Global/Templates.html:57 html/Admin/Queues/Template.html:76 html/Admin/Queues/Templates.html:68
-msgid "Select template"
-msgstr "Velg mal"
-
-#: lib/RT/CustomField_Overlay.pm:61
-msgid "Select up to %1 values"
-msgstr ""
-
-#: html/Admin/Elements/UserTabs:78
-msgid "Select user"
-msgstr "Velg bruker"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectMultiple"
-msgstr "VelgFlere"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectSingle"
-msgstr "VelgEnkelt"
-
-#: html/Admin/Elements/EditCustomFields:58
-msgid "Selected Custom Fields"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:59
-msgid "Selected objects"
-msgstr ""
-
-#: html/Widgets/SelectionBox:209
-msgid "Selections modified. Please save your changes"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Self Service"
-msgstr "Selvbetjening"
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "Send epost til alle overvåkere"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "Send epost til alle overvåkere som \"kommentar\""
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr "Send epost til kunder og Cc"
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr "Send epost til kunder og Cc som kommentar"
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr "Sender en melding til kundene"
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr "Send epost til eksplisit oppgitte Ccer og Bccer"
-
-#: etc/initialdata:94 etc/upgrade/3.1.17/content:7
-msgid "Sends mail to the Ccs"
-msgstr ""
-
-#: etc/initialdata:90 etc/upgrade/3.1.17/content:3
-msgid "Sends mail to the Ccs as a comment"
-msgstr ""
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr "Send epost til Administrative Ccer"
-
-#: etc/initialdata:98
-msgid "Sends mail to the administrative Ccs as a comment"
-msgstr "Sender epost til de administrative Ccene som kommentar"
-
-#: etc/initialdata:82 etc/initialdata:86
-msgid "Sends mail to the owner"
-msgstr "Sender epost til eieren"
-
-#: lib/RT/Date.pm:449
-msgid "Sep."
-msgstr "Sep."
-
-#: NOT FOUND IN SOURCE
-msgid "September"
-msgstr "September"
-
-#: html/Ticket/Elements/ShowTransaction:158
-msgid "Show"
-msgstr ""
-
-#: html/Approvals/index.html:52
-msgid "Show Approvals"
-msgstr ""
-
-#: html/Search/Elements/EditFormat:56
-msgid "Show Columns"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:220
-msgid "Show Results"
-msgstr "Vis Resultater"
-
-#: html/Approvals/Elements/PendingMyApproval:64
-msgid "Show approved requests"
-msgstr "Vis godkjente forespørsler"
-
-#: html/Ticket/Create.html:316
-msgid "Show basics"
-msgstr "Vis basisinfo"
-
-#: html/Approvals/Elements/PendingMyApproval:65
-msgid "Show denied requests"
-msgstr "Vis avviste forespørsler"
-
-#: html/Ticket/Create.html:319
-msgid "Show details"
-msgstr "Vis detaljer"
-
-#: html/Approvals/Elements/PendingMyApproval:63
-msgid "Show pending requests"
-msgstr "Vis ventende forespørsler"
-
-#: html/Approvals/Elements/PendingMyApproval:66
-msgid "Show requests awaiting other approvals"
-msgstr "Vis forespørsler som venter på andre godkjenninger"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket private commentary"
-msgstr "Vis sakens private kommentarer"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket summaries"
-msgstr "Vis sakssammendrag"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "ShowACL"
-msgstr "VisACL"
-
-#: lib/RT/System.pm:85
-msgid "ShowConfigTab"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "ShowOutgoingEmail"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "ShowSavedSearches"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "ShowScrips"
-msgstr "VisScrip"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "ShowTemplate"
-msgstr "VisMal"
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "ShowTicket"
-msgstr "VisSak"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "ShowTicketComments"
-msgstr "VisSaksKommentarer"
-
-#: lib/RT/Queue_Overlay.pm:107
-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:108
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr "Meld deg på som sak/kø AdminCc"
-
-#: html/Admin/Users/Modify.html:230 html/User/Prefs.html:168
-msgid "Signature"
-msgstr "Signatur"
-
-#: NOT FOUND IN SOURCE
-msgid "Signed in as %1"
-msgstr "Logget inn som %1"
-
-#: html/Elements/Tabs:68
-msgid "Simple Search"
-msgstr ""
-
-#: html/Admin/Elements/SelectSingleOrMultiple:47
-msgid "Single"
-msgstr "Enkel"
-
-#: html/Search/Elements/EditFormat:75
-msgid "Size"
-msgstr ""
-
-#: html/Elements/Header:89
-msgid "Skip Menu"
-msgstr "Dropp Meny"
-
-#: html/Search/Elements/EditFormat:78
-msgid "Small"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:120
-msgid "Some browsers may only load content from the same domain as your RT server."
-msgstr ""
-
-#: html/Admin/Elements/AddCustomFieldValue:49 html/Admin/Elements/EditCustomFieldValues:54
-msgid "Sort"
-msgstr "Sorter"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort key"
-msgstr "Sorter nøkkel"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort results by"
-msgstr "Sorter resultater etter"
-
-#: NOT FOUND IN SOURCE
-msgid "SortOrder"
-msgstr "SorteringsRekkefølge"
-
-#: html/Admin/Elements/EditScrip:78
-msgid "Stage"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Stalled"
-msgstr "Pauset"
-
-#: NOT FOUND IN SOURCE
-msgid "Start page"
-msgstr "Startside"
-
-#: html/Elements/SelectDateType:48 html/Ticket/Elements/EditDates:53 html/Ticket/Elements/ShowDates:56
-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:52 html/Ticket/Create.html:208 html/Ticket/Elements/EditDates:48 html/Ticket/Elements/ShowDates:52
-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/Users/Modify.html:162 html/User/Prefs.html:145
-msgid "State"
-msgstr "Stat"
-
-#: html/Search/Elements/PickBasics:87 html/SelfService/Update.html:57 html/Ticket/Create.html:66 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:1651
-msgid "Status"
-msgstr "Status"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "Statusendring"
-
-#: NOT FOUND IN SOURCE
-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:178
-msgid "Steal"
-msgstr "Stjel"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "Steal tickets"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "StealTicket"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:678
-#. ($Old->Name)
-msgid "Stolen from %1"
-msgstr "Stjålet fra %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Stolen from %1 "
-msgstr "Stjålet fra %1 "
-
-#: html/Search/Elements/EditFormat:81
-msgid "Style"
-msgstr ""
-
-#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:132 html/SelfService/Create.html:79 html/SelfService/Update.html:65 html/Ticket/Create.html:108 html/Ticket/Elements/EditBasics:48 html/Ticket/Elements/Reminders:125 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:82 lib/RT/Ticket_Overlay.pm:1162 lib/RT/Tickets_Overlay.pm:1733
-msgid "Subject"
-msgstr "Emne"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm:700
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "Endre emne til %1"
-
-#: html/Elements/Submit:75
-msgid "Submit"
-msgstr "Oppdater"
-
-#: NOT FOUND IN SOURCE
-msgid "Submit Workflow"
-msgstr "Send Arbeidsflyt"
-
-#: lib/RT/Group_Overlay.pm:774
-msgid "Succeeded"
-msgstr "Lykkes"
-
-#: lib/RT/Date.pm:423
-msgid "Sun."
-msgstr "Søn."
-
-#: lib/RT/System.pm:75
-msgid "SuperUser"
-msgstr "SuperBruker"
-
-#: html/User/Elements/DelegateRights:98
-msgid "System"
-msgstr "System"
-
-#: html/Admin/Elements/ToolTabs:54 html/Admin/Tools/Configuration.html:48
-msgid "System Configuration"
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:584 lib/RT/Interface/Web.pm:960 lib/RT/Interface/Web.pm:989
-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/Transaction_Overlay.pm:224 lib/RT/Transaction_Overlay.pm:230
-#. ($msg)
-msgid "System Error: %1"
-msgstr ""
-
-#: html/Admin/Tools/index.html:47
-msgid "System Tools"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:633
-msgid "System error. Right not delegated."
-msgstr "Systemfeil. Rettighet ikke tildelt."
-
-#: lib/RT/ACE_Overlay.pm:163 lib/RT/ACE_Overlay.pm:228 lib/RT/ACE_Overlay.pm:323 lib/RT/ACE_Overlay.pm:920
-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/CustomFields/GroupRights.html:58 html/Admin/Global/GroupRights.html:56 html/Admin/Groups/GroupRights.html:58 html/Admin/Queues/GroupRights.html:57
-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:357
-msgid "TEST_STRING"
-msgstr "TEST_STRENG"
-
-#: etc/initialdata:603 html/Search/Elements/EditFormat:72 html/Ticket/Elements/Tabs:170
-msgid "Take"
-msgstr "Ta"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "Take tickets"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "TakeTicket"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:663
-msgid "Taken"
-msgstr "Tatt"
-
-#: html/Admin/Elements/EditScrip:71 html/Tools/Offline.html:78
-msgid "Template"
-msgstr "Mal"
-
-#: html/Admin/Global/Template.html:112 html/Admin/Queues/Template.html:113
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "Mal #%1"
-
-#: html/Admin/Elements/EditTemplates:110
-msgid "Template deleted"
-msgstr "Mal slettet"
-
-#: lib/RT/Scrip_Overlay.pm:176
-msgid "Template is mandatory argument"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:180
-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:343
-msgid "Template parsed"
-msgstr "Mal tolket"
-
-#: lib/RT/Template_Overlay.pm:391
-msgid "Template parsing error"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:70 html/Admin/Elements/SystemTabs:57 html/Admin/Global/index.html:66
-msgid "Templates"
-msgstr "Maler"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates for %1\\n"
-msgstr "Maler for %1\\n"
-
-#: lib/RT/CustomField_Overlay.pm:943 lib/RT/Record.pm:945
-msgid "That is already the current value"
-msgstr "Verdien er allerede satt"
-
-#: lib/RT/CustomField_Overlay.pm:412
-msgid "That is not a value for this custom field"
-msgstr "Det er ikke en verdi for dette fleksifeltet"
-
-#: lib/RT/Ticket_Overlay.pm:1994
-msgid "That is the same value"
-msgstr "Det er den samme verdien"
-
-#: lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:614
-msgid "That principal already has that right"
-msgstr "Den primæren har allerede den rettigheten"
-
-#: lib/RT/Queue_Overlay.pm:753
-#. ($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:1435
-#. ($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:852
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr "Den primæren er ikke en %1 for denne køen"
-
-#: NOT FOUND IN SOURCE
-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:1990
-msgid "That queue does not exist"
-msgstr "Den køen eksisterer ikke"
-
-#: lib/RT/Ticket_Overlay.pm:3233
-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/Action/CreateTickets.pm:710 lib/RT/Ticket_Overlay.pm:3037
-msgid "That user already owns that ticket"
-msgstr "Den brukeren eier allerede den saken"
-
-#: lib/RT/Ticket_Overlay.pm:3012
-msgid "That user does not exist"
-msgstr "Den brukeren finnes ikke"
-
-#: lib/RT/User_Overlay.pm:389
-msgid "That user is already privileged"
-msgstr "Den brukeren er allerede priviligert"
-
-#: lib/RT/User_Overlay.pm:410
-msgid "That user is already unprivileged"
-msgstr "Den brukeren er allerede upriviligert"
-
-#: lib/RT/User_Overlay.pm:402
-msgid "That user is now privileged"
-msgstr "Denne brukeren er nå priviligert"
-
-#: lib/RT/User_Overlay.pm:423
-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:3031
-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:233
-msgid "That's not a numerical id"
-msgstr "Dette er ikke en numerisk id"
-
-#: html/SelfService/Display.html:53 html/Ticket/Create.html:177 html/Ticket/Elements/ShowSummary:49
-msgid "The Basics"
-msgstr "Detaljer"
-
-#: lib/RT/ACE_Overlay.pm:112
-msgid "The CC of a ticket"
-msgstr "CCen til en sak"
-
-#: lib/RT/ACE_Overlay.pm:113
-msgid "The administrative CC of a ticket"
-msgstr "Administrative CCer for en sak"
-
-#: NOT FOUND IN SOURCE
-msgid "The comment has been recorded"
-msgstr "Kommentarer er lagret"
-
-#: bin/rt-crontool:275
-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:"
-
-#: NOT FOUND IN SOURCE
-msgid "The following commands were not proccessed:\\n\\n"
-msgstr "De følgende kommandoene ble ikke behandlet:\\n\\n"
-
-#: lib/RT/Record.pm:948
-msgid "The new value has been set."
-msgstr "Den nye verdien har blitt satt."
-
-#: lib/RT/ACE_Overlay.pm:110
-msgid "The owner of a ticket"
-msgstr "Eieren av en sak"
-
-#: lib/RT/ACE_Overlay.pm:111
-msgid "The requestor of a ticket"
-msgstr "Forespørren av en sak"
-
-#: html/Admin/Elements/EditUserComments:47
-msgid "These comments aren't generally visible to the user"
-msgstr "Disse kommentarene er generelt ikke synlig for brukeren"
-
-#: lib/RT/CustomField_Overlay.pm:978
-msgid "This custom field does not apply to that object"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:50
-msgid "This feature is only available to system administrators"
-msgstr ""
-
-#: html/Ticket/Elements/PreviewScrips:96
-msgid "This message will be sent to..."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "This ticket %1 %2 (%3)\\n"
-msgstr "Denne saken %1 %2 (%3)\\n"
-
-#: bin/rt-crontool:266
-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:301
-msgid "This transaction appears to have no content"
-msgstr "Denne transaksjonen ser ikke ut til å ha noe innhold"
-
-#: html/Ticket/Elements/ShowRequestor:70
-#. ($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:420
-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:46 html/Ticket/ModifyAll.html:50
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr "Sak #%1 Jumbo oppdatering: %2"
-
-#: html/Approvals/Elements/ShowDependency:67
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "Sak #%1: %2"
-
-#: lib/RT/Action/CreateTickets.pm:1350 lib/RT/Action/CreateTickets.pm:1359 lib/RT/Action/CreateTickets.pm:605 lib/RT/Action/CreateTickets.pm:729 lib/RT/Action/CreateTickets.pm:741
-#. ($T::Tickets{$template_id}->Id)
-#. ($T::Tickets{$template_id}->id)
-#. ($ticket->Id)
-msgid "Ticket %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:755 lib/RT/Ticket_Overlay.pm:775
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "Sak %1 opprettet i '%2' køen"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket %1 loaded\\n"
-msgstr "Sak %1 lastet\\n"
-
-#: html/Search/Bulk.html:377
-#. ($Ticket->Id, $_)
-msgid "Ticket %1: %2"
-msgstr "Sak %1: %2"
-
-#: html/Admin/Elements/QueueTabs:74
-msgid "Ticket Custom Fields"
-msgstr ""
-
-#: html/Ticket/History.html:46 html/Ticket/History.html:49
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "Sakshistorikk # %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Id"
-msgstr "SaksId"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "Løst Sak"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1207
-msgid "Ticket Transactions"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket attachment"
-msgstr "Saks-vedlegg"
-
-#: lib/RT/Tickets_Overlay.pm:1920
-msgid "Ticket content"
-msgstr "Saks-innhold"
-
-#: lib/RT/Tickets_Overlay.pm:1969
-msgid "Ticket content type"
-msgstr "Sakens innholdstype"
-
-#: lib/RT/Ticket_Overlay.pm:603 lib/RT/Ticket_Overlay.pm:617 lib/RT/Ticket_Overlay.pm:628 lib/RT/Ticket_Overlay.pm:763
-msgid "Ticket could not be created due to an internal error"
-msgstr "Saken kunne ikke opprettes på grunn av en intern feil"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket created"
-msgstr "Sak opprettet"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket creation failed"
-msgstr "Saksopprettelse feilet"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket deleted"
-msgstr "Sak slettet"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket id not found"
-msgstr "Saksid ikke funnet"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket killed"
-msgstr "Sak drept"
-
-#: html/Ticket/Display.html:55
-msgid "Ticket metadata"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket not found"
-msgstr "Sak ikke funnet"
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "Saksstatus endret"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket watchers"
-msgstr "Saksovervåkere"
-
-#: lib/RT/Search/FromSQL.pm:82
-#. (ref $self)
-msgid "TicketSQL search module"
-msgstr ""
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:71 html/Search/Elements/Chart:109 lib/RT/CustomField_Overlay.pm:1206
-msgid "Tickets"
-msgstr "Saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 %2"
-msgstr "Saker %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 by %2"
-msgstr "Saker %1 av %2"
-
-#: html/Tools/Reports/CreatedByDates.html:86
-msgid "Tickets created after"
-msgstr ""
-
-#: html/Tools/Reports/CreatedByDates.html:88
-msgid "Tickets created before"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets from %1"
-msgstr "Saker fra %1"
-
-#: html/Tools/Reports/ResolvedByDates.html:87
-msgid "Tickets resolved after"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByDates.html:89
-msgid "Tickets resolved before"
-msgstr ""
-
-#: html/Approvals/Elements/ShowDependency:48
-msgid "Tickets which depend on this approval:"
-msgstr "Saker som er avhengige av denne godkjennelsen:"
-
-#: html/Search/Elements/PickBasics:134 html/Ticket/Create.html:183 html/Ticket/Elements/EditBasics:72
-msgid "Time Estimated"
-msgstr ""
-
-#: html/Search/Elements/PickBasics:135 html/Ticket/Create.html:196 html/Ticket/Elements/EditBasics:85
-msgid "Time Left"
-msgstr "Tid Igjen"
-
-#: html/Search/Elements/PickBasics:133 html/Ticket/Create.html:189 html/Ticket/Elements/EditBasics:78
-msgid "Time Worked"
-msgstr "Arbeidstid"
-
-#: lib/RT/Tickets_Overlay.pm:1891
-msgid "Time left"
-msgstr "Tid igjen"
-
-#: html/Elements/Footer:51
-msgid "Time to display"
-msgstr "Tid å vise"
-
-#: lib/RT/Tickets_Overlay.pm:1866
-msgid "Time worked"
-msgstr "Arbeidstid"
-
-#: NOT FOUND IN SOURCE
-msgid "TimeLeft"
-msgstr "TidIgjen"
-
-#: lib/RT/Ticket_Overlay.pm:1167
-msgid "TimeWorked"
-msgstr "ArbeidsTid"
-
-#: html/Search/Elements/EditFormat:74
-msgid "Title"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:"
-msgstr "For å generere en diff av denne bekreftelsen:"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:\\n"
-msgstr "For å genere en diff av denne bekreftelsen"
-
-#: html/Elements/Footer:62
-#. ('<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>')
-msgid "To inquire about support, training, custom development or licensing, please contact %1."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1170
-msgid "Told"
-msgstr "Fortalt"
-
-#: html/Admin/Elements/Tabs:68 html/Admin/index.html:88 html/Elements/Tabs:74 html/Tools/index.html:46 html/Tools/index.html:49
-msgid "Tools"
-msgstr ""
-
-#: html/Search/Elements/Chart:130
-msgid "Total"
-msgstr ""
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "Transaksjon"
-
-#: lib/RT/Transaction_Overlay.pm:805
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr "Transaksjon %1 slettet"
-
-#: lib/RT/Transaction_Overlay.pm:183
-msgid "Transaction Created"
-msgstr "Transaksjon Opprettet"
-
-#: html/Admin/Elements/QueueTabs:78
-msgid "Transaction Custom Fields"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-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:128
-msgid "Transaction->Create couldn't, as you didn't specify an object type and id"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:870
-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:418
-msgid "Tue."
-msgstr "Tir."
-
-#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1168 lib/RT/Tickets_Overlay.pm:1705
-msgid "Type"
-msgstr "Type"
-
-#: lib/RT/ScripCondition_Overlay.pm:128
-msgid "Unimplemented"
-msgstr "Uimplementert"
-
-#: html/Admin/Users/Modify.html:89
-msgid "Unix login"
-msgstr "Unix login"
-
-#: NOT FOUND IN SOURCE
-msgid "UnixUsername"
-msgstr "UnixBrukerNavn"
-
-#: lib/RT/Attachment_Overlay.pm:289 lib/RT/Record.pm:861
-#. ($self->ContentEncoding)
-#. ($ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr "Ukjent InnholdsFormatering %1"
-
-#: html/Search/Build.html:455 lib/RT/Report/Tickets.pm:410
-msgid "Unknown field: $key"
-msgstr ""
-
-#: html/Elements/SelectResultsPerPage:58
-msgid "Unlimited"
-msgstr "Ubegrenset"
-
-#: html/Search/Elements/SelectSearchesForObjects:64
-msgid "Unnamed search"
-msgstr ""
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr "Upriviligert"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Unselected Custom Fields"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:61
-msgid "Unselected objects"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:659
-msgid "Untaken"
-msgstr "Ikke tatt"
-
-#: html/Admin/Elements/EditScrip:128 html/Elements/RT__Ticket/ColumnMap:302 html/Search/Bulk.html:193 html/Search/Bulk.html:75
-msgid "Update"
-msgstr "Oppdater"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ID"
-msgstr "Oppdater ID"
-
-#: html/Ticket/Update.html:135
-msgid "Update Ticket"
-msgstr ""
-
-#: html/Search/Bulk.html:126 html/Ticket/ModifyAll.html:87 html/Ticket/Update.html:72
-msgid "Update Type"
-msgstr "Oppdater Type"
-
-#: NOT FOUND IN SOURCE
-msgid "Update all these tickets at once"
-msgstr "Oppdater alle disse sakene samtidig"
-
-#: NOT FOUND IN SOURCE
-msgid "Update email"
-msgstr "Oppdater epost"
-
-#: html/Search/Bulk.html:200 html/Search/Results.html:78
-msgid "Update multiple tickets"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Update name"
-msgstr "Oppdater navn"
-
-#: lib/RT/Action/CreateTickets.pm:750 lib/RT/Interface/Web.pm:584
-msgid "Update not recorded."
-msgstr "Oppdatering ikke lagret."
-
-#: NOT FOUND IN SOURCE
-msgid "Update selected tickets"
-msgstr "Oppdater valgte saker"
-
-#: NOT FOUND IN SOURCE
-msgid "Update signature"
-msgstr "Oppdater signatur"
-
-#: html/Ticket/ModifyAll.html:84
-msgid "Update ticket"
-msgstr "Oppdater sak"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ticket # %1"
-msgstr "Ooppdater sak # %1"
-
-#: html/SelfService/Update.html:112 html/SelfService/Update.html:47
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "Oppdater sak #%1"
-
-#: html/Ticket/Update.html:158
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "Oppdater sak #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:748 lib/RT/Interface/Web.pm:583
-msgid "Update type was neither correspondence nor comment."
-msgstr "Oppdateringstype var verken korrespondanse eller kommentar."
-
-#: html/Elements/SelectDateType:54 html/Ticket/Elements/ShowDates:72 lib/RT/CustomField_Overlay.pm:1284 lib/RT/Ticket_Overlay.pm:1171
-msgid "Updated"
-msgstr "Oppdatert"
-
-#: html/Tools/Offline.html:93
-msgid "Upload"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:84
-msgid "Upload multiple files"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:79
-msgid "Upload multiple images"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:85
-msgid "Upload one file"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:80
-msgid "Upload one image"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:86
-msgid "Upload up to %1 files"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:81
-msgid "Upload up to %1 images"
-msgstr ""
-
-#: html/Tools/Offline.html:93
-msgid "Upload your changes"
-msgstr ""
-
-#: html/Admin/index.html:90
-msgid "Use other RT administrative tools"
-msgstr ""
-
-#: 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"
-
-#: lib/RT/Ticket_Overlay.pm:506
-#. ($args{'Owner'})
-msgid "User '%1' could not be found."
-msgstr ""
-
-#: 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:132 etc/initialdata:206
-msgid "User Defined"
-msgstr "Bruker Definert"
-
-#: html/Admin/Elements/EditScrip:93
-msgid "User Defined conditions and actions"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "User ID"
-msgstr "BrukerID"
-
-#: NOT FOUND IN SOURCE
-msgid "User Id"
-msgstr "BrukerId"
-
-#: html/Admin/Elements/CustomFieldTabs:72 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:85 html/Admin/Elements/SystemTabs:68 html/Admin/Global/index.html:80
-msgid "User Rights"
-msgstr "Brukerrettigheter"
-
-#: html/Admin/Users/Modify.html:301
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "Bruker kunne ikke opprettes: %1"
-
-#: lib/RT/User_Overlay.pm:330
-msgid "User created"
-msgstr "Bruker opprettet"
-
-#: html/Admin/CustomFields/GroupRights.html:74 html/Admin/Global/GroupRights.html:88 html/Admin/Groups/GroupRights.html:75 html/Admin/Queues/GroupRights.html:90
-msgid "User defined groups"
-msgstr "Brukerdefinerte grupper"
-
-#: lib/RT/User_Overlay.pm:592 lib/RT/User_Overlay.pm:612
-msgid "User loaded"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "User notified"
-msgstr "Bruker informert"
-
-#: NOT FOUND IN SOURCE
-msgid "User view"
-msgstr "Brukervisning"
-
-#: html/Admin/Groups/index.html:103
-msgid "User-defined groups"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:69 html/Elements/Login:90 html/Ticket/Elements/AddWatchers:56
-msgid "Username"
-msgstr "Brukernavn"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:55 html/Admin/Elements/SelectNewGroupMembers:47 html/Admin/Elements/Tabs:53 html/Admin/Global/CustomFields/index.html:64 html/Admin/Groups/Members.html:76 html/Admin/Queues/People.html:89 html/Admin/index.html:62 html/User/Groups/Members.html:79 lib/RT/CustomField_Overlay.pm:1208
-msgid "Users"
-msgstr "Brukere"
-
-#: html/Admin/Users/index.html:85
-msgid "Users matching search criteria"
-msgstr "Brukere som treffer søkekriteria"
-
-#: bin/rt-crontool:134
-#. ($transaction->id)
-msgid "Using transaction #%1..."
-msgstr ""
-
-#: lib/RT/Tickets_Overlay_SQL.pm:528
-msgid "Valid Query"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:80
-msgid "Validation"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "ValueOfQueue"
-msgstr "KøVerdi"
-
-#: html/Admin/CustomFields/Modify.html:130 html/Admin/Elements/EditCustomField:78
-msgid "Values"
-msgstr "Verdier"
-
-#: NOT FOUND IN SOURCE
-msgid "VrijevormEnkele"
-msgstr "VrijevormEnkele"
-
-#: lib/RT/Queue_Overlay.pm:107
-msgid "Watch"
-msgstr "Overvåk"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "WatchAsAdminCc"
-msgstr "OvervåkSomAdminCc"
-
-#: NOT FOUND IN SOURCE
-msgid "Watcher loaded"
-msgstr "Overvåker lastet"
-
-#: html/Admin/Elements/QueueTabs:63
-msgid "Watchers"
-msgstr "Overvåkere"
-
-#: NOT FOUND IN SOURCE
-msgid "WebEncoding"
-msgstr "WebFormatering"
-
-#: lib/RT/Date.pm:419
-msgid "Wed."
-msgstr "Ons."
-
-#: html/Tools/MyDay.html:75
-msgid "What I did today"
-msgstr ""
-
-#: etc/initialdata:521
-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/initialdata:485
-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:146
-msgid "When a ticket is created"
-msgstr "NÃ¥r er sak er opprettet"
-
-#: etc/initialdata:418
-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:151
-msgid "When anything happens"
-msgstr "NÃ¥r noe skjer"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "Når en sak er løst"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr "Når en sak får ny eier"
-
-#: etc/initialdata:178 etc/upgrade/3.1.17/content:16
-msgid "Whenever a ticket's priority changes"
-msgstr ""
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr "Når en sak flyttes til en ny kø"
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr "NÃ¥r en saks status endres"
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr "NÃ¥r brukerdefinerte forhold intreffer"
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr "NÃ¥r kommentarer kommer inn"
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr "NÃ¥r korrespondanse kommer inn"
-
-#: html/Admin/Users/Modify.html:188 html/User/Prefs.html:88
-msgid "Work"
-msgstr "Arbeid"
-
-#: html/Search/Results.html:82
-msgid "Work offline"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "WorkPhone"
-msgstr "ArbeidsTelefon"
-
-#: html/Ticket/Elements/ShowBasics:63 html/Ticket/Update.html:64
-msgid "Worked"
-msgstr "Arbeidet"
-
-#: lib/RT/Ticket_Overlay.pm:3140
-msgid "You already own this ticket"
-msgstr "Du eier allerede denne saken"
-
-#: html/autohandler:214 html/autohandler:222
-msgid "You are not an authorized user"
-msgstr "Du er ikke en autorisert bruker"
-
-#: html/Prefs/Search.html:56
-msgid "You can also edit the predefined search itself"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3025
-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"
-
-#: lib/RT/Ticket_Overlay.pm:3021
-msgid "You can only take tickets that are unowned"
-msgstr ""
-
-#: 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 lib/RT/StyleGuide.pod:780
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "Du fant %1 saker i %2 køen"
-
-#: html/NoAuth/Logout.html:52
-msgid "You have been logged out of RT."
-msgstr ""
-
-#: html/SelfService/Display.html:133
-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:2003
-msgid "You may not create requests in that queue."
-msgstr "Du kan ikke opprette forespørsler i den køen."
-
-#: html/NoAuth/Logout.html:56
-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:502
-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:540
-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:445
-msgid "Your request was rejected."
-msgstr "Din forespørsel ble avvist"
-
-#: html/autohandler:251
-msgid "Your username or password is incorrect"
-msgstr "Ditt brukernavn/passord er ugyldig"
-
-#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:149
-msgid "Zip"
-msgstr "Zip"
-
-#: NOT FOUND IN SOURCE
-msgid "[no subject]"
-msgstr "[ikke noe emne]"
-
-#: lib/RT/System.pm:87
-msgid "allow creation of saved searches"
-msgstr ""
-
-#: lib/RT/System.pm:86
-msgid "allow loading of saved searches"
-msgstr ""
-
-#: html/User/Elements/DelegateRights:80
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr "som tildelt til %1"
-
-#: html/Search/Results.html:83
-msgid "chart"
-msgstr ""
-
-#: html/SelfService/Closed.html:49
-msgid "closed"
-msgstr "lukket"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:55
-msgid "contains"
-msgstr "inneholder"
-
-#: NOT FOUND IN SOURCE
-msgid "content"
-msgstr "innhold"
-
-#: NOT FOUND IN SOURCE
-msgid "content-type"
-msgstr "innholdstype"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence (probably) not sent"
-msgstr "korrespondanse (sansynligvis) ikke sendt"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence sent"
-msgstr "korrespondanse sendt"
-
-#: html/Admin/Queues/Modify.html:98 lib/RT/Date.pm:346
-msgid "days"
-msgstr "dager"
-
-#: NOT FOUND IN SOURCE
-msgid "dead"
-msgstr "død"
-
-#: NOT FOUND IN SOURCE
-msgid "delete"
-msgstr "slett"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "deleted"
-msgstr "slettet"
-
-#: html/Search/Elements/PickBasics:61
-msgid "does not match"
-msgstr "treffer ikke"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:56
-msgid "doesn't contain"
-msgstr "inneholder ikke"
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "equal to"
-msgstr "lik som"
-
-#: html/Search/Build.html:547
-msgid "error: can't move down"
-msgstr ""
-
-#: html/Search/Build.html:569
-msgid "error: can't move left"
-msgstr ""
-
-#: html/Search/Build.html:528
-msgid "error: can't move up"
-msgstr ""
-
-#: html/Search/Build.html:612
-msgid "error: nothing to delete"
-msgstr ""
-
-#: html/Search/Build.html:533 html/Search/Build.html:552 html/Search/Build.html:574 html/Search/Build.html:603
-msgid "error: nothing to move"
-msgstr ""
-
-#: html/Search/Build.html:630
-msgid "error: nothing to toggle"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "false"
-msgstr "usant"
-
-#: NOT FOUND IN SOURCE
-msgid "filename"
-msgstr "filnavn"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "greater than"
-msgstr "større enn"
-
-#: lib/RT/Group_Overlay.pm:214
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "gruppe '%1'"
-
-#: html/Search/Results.html:88
-#. ($m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query))
-msgid "grouped by %1"
-msgstr ""
-
-#: lib/RT/Date.pm:342
-msgid "hours"
-msgstr "timer"
-
-#: html/Search/Elements/PickBasics:48
-msgid "id"
-msgstr "id"
-
-#: html/Elements/SelectBoolean:53 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:57 html/Search/Elements/PickBasics:162 html/Search/Elements/PickBasics:74 html/Search/Elements/PickBasics:90 html/Search/Elements/PickCFs:53
-msgid "is"
-msgstr "er"
-
-#: html/Elements/SelectBoolean:57 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:58 html/Search/Elements/PickBasics:163 html/Search/Elements/PickBasics:75 html/Search/Elements/PickBasics:91 html/Search/Elements/PickCFs:54
-msgid "isn't"
-msgstr "er ikke"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "less than"
-msgstr "mindre enn"
-
-#: html/Search/Elements/PickBasics:60
-msgid "matches"
-msgstr "treffer"
-
-#: lib/RT/Date.pm:338
-msgid "min"
-msgstr "min"
-
-#: NOT FOUND IN SOURCE
-msgid "minutes"
-msgstr "minutter"
-
-#: NOT FOUND IN SOURCE
-msgid "modifications\\n\\n"
-msgstr "endringer\\n\\n"
-
-#: lib/RT/Date.pm:354
-msgid "months"
-msgstr "måneder"
-
-#: lib/RT/Queue_Overlay.pm:82
-msgid "new"
-msgstr "ny"
-
-#: html/Admin/Elements/PickCustomFields:64 html/Admin/Elements/PickObjects:65
-msgid "no name"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:64
-msgid "no value"
-msgstr "ingen verdi"
-
-#: html/Admin/Elements/EditQueueWatchers:48 html/Ticket/Elements/EditWatchers:49
-msgid "none"
-msgstr "ingen"
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "not equal to"
-msgstr "ikke lik som"
-
-#: NOT FOUND IN SOURCE
-msgid "notlike"
-msgstr "ikkelik"
-
-#: html/SelfService/Elements/MyRequests:82 lib/RT/Queue_Overlay.pm:83
-msgid "open"
-msgstr "Ã¥pen"
-
-#: lib/RT/Group_Overlay.pm:219
-#. ($self->Name, $user->Name)
-msgid "personal group '%1' for user '%2'"
-msgstr "personlig gruppe '%1' for bruker '%2'"
-
-#: lib/RT/Group_Overlay.pm:227
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "kø %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "rejected"
-msgstr "avvist"
-
-#: lib/RT/Queue_Overlay.pm:85
-msgid "resolved"
-msgstr "løst"
-
-#: lib/RT/Date.pm:334
-msgid "sec"
-msgstr "sek"
-
-#: lib/RT/System.pm:85
-msgid "show Configuration tab"
-msgstr ""
-
-#: html/Search/Results.html:80
-msgid "spreadsheet"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "stalled"
-msgstr "pauset"
-
-#: html/Search/Results.html:89
-#. ($m->scomp('Elements/SelectChartType', Name => 'ChartStyle'))
-msgid "style: %1"
-msgstr ""
-
-#: html/Prefs/MyRT.html:93
-msgid "summary rows"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:222
-#. ($self->Type)
-msgid "system %1"
-msgstr "system %1"
-
-#: lib/RT/Group_Overlay.pm:233
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr "systemgruppe '%1'"
-
-#: html/Elements/Error:64 html/SelfService/Error.html:63
-msgid "the calling component did not specify why"
-msgstr "den kallende komponenten oppga ikke hvorfor"
-
-#: lib/RT/Group_Overlay.pm:230
-#. ($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:236
-#. ($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:211
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "bruker %1"
-
-#: lib/RT/Date.pm:350
-msgid "weeks"
-msgstr "uker"
-
-#: NOT FOUND IN SOURCE
-msgid "with template %1"
-msgstr "med malen %1"
-
-#: lib/RT/Date.pm:358
-msgid "years"
-msgstr "Ã¥r"
-
diff --git a/rt/lib/RT/I18N/pt_br.po b/rt/lib/RT/I18N/pt_br.po
deleted file mode 100644
index 98fa206..0000000
--- a/rt/lib/RT/I18N/pt_br.po
+++ /dev/null
@@ -1,6528 +0,0 @@
-# translation of pt_br.po to Portugues Brasileiro
-# Header entry was created by KBabel!
-#
-# Fernando Frota Machado de Morais <frota@cecom.ufmg.br>, 2008.
-msgid ""
-msgstr ""
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"MIME-Version: 1.0\n"
-"PO-Revision-Date: 2008-02-23 11:48-0300\n"
-"Project-Id-Version: RT 3.6.x - pt_br\n"
-"Language-Team: Portugues Brasileiro <pt@li.org>\n"
-"X-Generator: KBabel 1.11.4\n"
-"MIME-Version: 1.0\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-"Last-Translator: Fernando Frota Machado de Morais <frota@cecom.ufmg.br>\n"
-
-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 e-mail do RT requer autenticação PGP. Ou você não "
-"assinou sua mensagem ou sua assinatura não pôde ser verificada."
-
-#: html/Widgets/SavedSearch:117
-#. ($self->{CurrentSearch}{Object}->Description)
-msgid " %1 deleted."
-msgstr " %1 removido."
-
-#: html/Widgets/SavedSearch:94
-#. ($self->{CurrentSearch}{Description}, $args->{Description})
-msgid " %1 renamed to %2."
-msgstr " %1 renomeado para %2."
-
-#: html/Widgets/SavedSearch:107
-#. ($args->{Description})
-msgid " %1 saved."
-msgstr " %1 salvo."
-
-#: html/Approvals/Elements/Approve:50 html/Approvals/Elements/ShowDependency:73 html/SelfService/Display.html:48 html/Ticket/Display.html:49 html/Ticket/Display.html:53
-#. ($ticket->Id, $ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-#. ($TicketObj->Id, $TicketObj->Subject)
-#. ($Ticket->id, $Ticket->Subject)
-msgid "#%1: %2"
-msgstr ""
-
-#: html/Elements/ShowSearch:116
-msgid "$1"
-msgstr ""
-
-#: lib/RT/Record.pm:957
-#. ($label)
-msgid "$prefix %1"
-msgstr "$prefixo %1"
-
-#: lib/RT/URI/fsck_com_rt.pm:258
-#. ($self->ObjectType, $self->Object->Id)
-msgid "%1 #%2"
-msgstr ""
-
-#: lib/RT/Date.pm:367
-#. ($s, $time_unit)
-msgid "%1 %2"
-msgstr ""
-
-#: lib/RT/Date.pm:403
-#. ($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/Record.pm:1707 lib/RT/Transaction_Overlay.pm:668 lib/RT/Transaction_Overlay.pm:711
-#. ($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:364
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "%1 %2 atrás"
-
-#: lib/RT/Record.pm:1714 lib/RT/Transaction_Overlay.pm:675
-#. ($cf->Name, $old_content, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 %2 mudado para %3"
-
-#: lib/RT/Record.pm:1711 lib/RT/Transaction_Overlay.pm:671 lib/RT/Transaction_Overlay.pm:717
-#. ($cf->Name, $old_value->Content)
-#. ($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:67 html/Admin/Elements/ListGlobalScrips:65 html/Ticket/Elements/PreviewScrips:105
-#. (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/Ticket/Elements/ShowAttachments:74
-#. ($rev->CreatedAsString, $size, $rev->CreatorObj->Name)
-msgid "%1 (%2) by %3"
-msgstr "%1 (%2) por %3"
-
-#: html/SelfService/Update.html:62 html/Ticket/Elements/EditBasics:110 html/Ticket/Update.html:63 html/Ticket/Update.html:65 html/Tools/MyDay.html:71
-#. (loc($TicketObj->Status))
-#. ($TicketObj->OwnerObj->Name())
-#. (loc($DefaultStatus))
-#. (loc($Ticket->Status()))
-msgid "%1 (Unchanged)"
-msgstr "%1 (Sem alteração)"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 - %2 shown"
-msgstr "%1 - %2 apresentados"
-
-#: bin/rt-crontool:239 bin/rt-crontool:246 bin/rt-crontool:252
-#. ("--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:264
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr "%1 - Mostra atualizações de estado no STDOUT"
-
-#: bin/rt-crontool:255
-#. ("--template-id")
-msgid "%1 - Specify id of the template you want to use"
-msgstr "%1 - Especifique o id do modelo que você quer usar"
-
-#: bin/rt-crontool:258
-#. ("--transaction")
-msgid "%1 - Specify if you want to use either 'first' or 'last' transaction"
-msgstr "%1 - Especifique se você quer usar a 'primeira' ou a 'última' transação"
-
-#: bin/rt-crontool:249
-#. ("--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:243
-#. ("--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:236
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr "%1 - Especifica o módulo de busca que você quer usar"
-
-#: bin/rt-crontool:261
-#. ("--transaction-type")
-msgid "%1 - Specify the type of a transaction you want to use"
-msgstr "%1 - Especifique o tipo de transação você quer usar"
-
-#: html/Elements/Footer:58
-#. ('&#187;&#124;&#171;', $RT::VERSION, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',)
-msgid "%1 RT %2 Copyright 1996-%3 %4."
-msgstr "%1 RT %2 Direitos Reservados 1996-%3 %4."
-
-#: lib/RT/ScripAction_Overlay.pm:152
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr "ScripAction %1 carregado"
-
-#: lib/RT/Record.pm:1744
-#. ($args{'Value'}, $cf->Name)
-msgid "%1 added as a value for %2"
-msgstr "%1 adicionado 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:146 lib/RT/Link_Overlay.pm:153
-#. ($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:75 lib/RT/Transaction_Overlay.pm:552
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-msgid "%1 by %2"
-msgstr "%1 por %2"
-
-#: lib/RT/Transaction_Overlay.pm:809 lib/RT/Transaction_Overlay.pm:818 lib/RT/Transaction_Overlay.pm:821
-#. ($self->Field , $q1->Name , $q2->Name)
-#. ($self->Field, $t2->AsString, $t1->AsString)
-#. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'")
-msgid "%1 changed from %2 to %3"
-msgstr "%1 mudado de %2 para %3"
-
-#: html/Search/Build.html:215
-#. ($Description)
-msgid "%1 copy"
-msgstr "%1 copiado"
-
-#: lib/RT/Record.pm:961
-msgid "%1 could not be set to %2."
-msgstr "%1 não pôde ser mudado 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"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
-msgstr ""
-"%1 não pôde definir o estado como resolvido. O banco de dados do RT pode "
-"estar inconsistente."
-
-#: lib/RT/Transaction_Overlay.pm:592
-#. ($obj_type)
-msgid "%1 created"
-msgstr "%1 criado"
-
-#: lib/RT/Transaction_Overlay.pm:597
-#. ($obj_type)
-msgid "%1 deleted"
-msgstr "%1 removido"
-
-#: etc/initialdata:593
-msgid "%1 highest priority tickets I own"
-msgstr "Meus %1 tíquetes de mais alta prioridade"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I own..."
-msgstr "%1 tíquetes de mais alta prioridade que eu possuo..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I requested..."
-msgstr "%1 tíquetes de mais alta prioridade que eu requeri..."
-
-#: bin/rt-crontool:231
-#. ($0)
-msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
-msgstr "%1 é uma ferramenta que age sobre os tíquetes a partir de uma ferramenta externa de agendamento, como cron."
-
-#: lib/RT/Queue_Overlay.pm:865
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this queue."
-msgstr "%1 não é mais um %2 desta fila."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 is no longer a %2 for this ticket."
-msgstr "%1 não é mais um %2 deste tíquete."
-
-#: NOT FOUND IN SOURCE
-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/ShowTime:49 html/Ticket/Elements/ShowTime:51
-#. ($minutes)
-msgid "%1 min"
-msgstr ""
-
-#: etc/initialdata:601
-msgid "%1 newest unowned tickets"
-msgstr "%1 tíquetes mais recentes sem proprietário"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 not shown"
-msgstr "%1 não mostrado"
-
-#: lib/RT/CustomField_Overlay.pm:896
-msgid "%1 objects"
-msgstr "%1 objetos"
-
-#: html/User/Elements/DelegateRights:99
-#. (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:65
-#. (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."
-
-#: lib/RT/CustomField_Overlay.pm:897
-msgid "%1's %2 objects"
-msgstr "%1's %2 objetos"
-
-#: lib/RT/CustomField_Overlay.pm:898
-msgid "%1's %2's %3 objects"
-msgstr "%1's %2's %3 objetos"
-
-#: html/Search/Elements/SearchPrivacy:54 html/Search/Elements/SelectSearchObject:57 html/Search/Elements/SelectSearchesForObjects:59
-#. ($object->Name)
-#. ($Object->Name)
-msgid "%1's saved searches"
-msgstr "Primeiras %1 buscas salvas"
-
-#: lib/RT/Transaction_Overlay.pm:502
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1: nenhum arquivo anexo especificado"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:80
-#. ($size)
-msgid "%1b"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTransactionAttachments:77
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTime:51
-#. (sprintf("%.1f",$minutes / 60))
-msgid "%quant(%1,hour)"
-msgstr "%quant(%1,hora)"
-
-#: lib/RT/Ticket_Overlay.pm:1144
-#. ($args{'Status'})
-msgid "'%1' is an invalid value for status"
-msgstr "'%1' é um valor inválido para 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 "(Marcar caixa para remover o membro do grupo)"
-
-#: NOT FOUND IN SOURCE
-msgid "(Check box to delete scrip)"
-msgstr "(Marcar caixa para remover o scrip)"
-
-#: html/Admin/Elements/EditCustomFieldValues:52 html/Admin/Elements/EditQueueWatchers:52 html/Admin/Elements/EditScrips:58 html/Admin/Elements/EditTemplates:59 html/Admin/Groups/Members.html:75 html/Elements/EditLinks:56 html/Ticket/Elements/EditPeople:69 html/User/Groups/Members.html:78
-msgid "(Check box to delete)"
-msgstr "(Marcar caixa para remover)"
-
-#: html/Ticket/Elements/PreviewScrips:101
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr "(Marcar caixas para desativar notificações para os destinatários listados)"
-
-#: html/Ticket/Elements/PreviewScrips:125
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr "(Marcar caixas para ativar notificações para os destinatários listados)"
-
-#: html/Ticket/Create.html:220
-msgid "(Enter ticket ids or URLs, separated with spaces)"
-msgstr "(Informar identificação de tíquetes ou URLs, separadas por espaço)"
-
-#: html/Admin/Queues/Modify.html:77 html/Admin/Queues/Modify.html:83
-#. ($RT::CorrespondAddress)
-#. ($RT::CommentAddress)
-msgid "(If left blank, will default to %1)"
-msgstr "(Se deixado em branco, será entendido como %)"
-
-#: NOT FOUND IN SOURCE
-msgid "(No Value)"
-msgstr "(Sem Valor)"
-
-#: html/Admin/Elements/EditCustomFields:76 html/Admin/Elements/ListGlobalCustomFields:55
-msgid "(No custom fields)"
-msgstr "(Nenhum campo personalizado)"
-
-#: html/Admin/Groups/Members.html:73 html/User/Groups/Members.html:76
-msgid "(No members)"
-msgstr "(Sem membros)"
-
-#: html/Admin/Elements/EditScrips:55 html/Admin/Elements/ListGlobalScrips:50
-msgid "(No scrips)"
-msgstr "(Sem scrips)"
-
-#: html/Admin/Elements/EditTemplates:54
-msgid "(No templates)"
-msgstr "(Nenhum modelo)"
-
-#: html/Admin/Elements/PickCustomFields:49 html/Admin/Elements/PickObjects:49
-msgid "(None)"
-msgstr "(Nenhum)"
-
-#: html/Ticket/Update.html:92
-msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr "(Envia uma cópia oculta desta atualização para uma lista de endereços de e-mails separados por ví­rgula.<strong>Não</strong> altera quem vai receber atualizações futuras.)"
-
-#: html/Ticket/Create.html:105
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr "(Envia uma cópia desta atualização para uma lista de endereços de e-mails administrativos separados por ví­rgula. Estas pessoas <strong>vão</strong> receber atualizações futuras.)"
-
-#: html/Ticket/Update.html:88
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr "(Envia uma cópia desta atualização para uma lista de endereços de e-mails separados por ví­rgula.<strong>Não</strong> altera quem vai receber atualizações futuras.)"
-
-#: html/Ticket/Create.html:95
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr "(Envia uma cópia desta atualização para uma lista de endereços de separados por ví­rgula. Estas pessoas <strong>vão</strong> receber atualizações futuras.)"
-
-#: html/Admin/Elements/EditScrip:98
-msgid "(Use these fields when you choose 'User Defined' for a condition or action)"
-msgstr ""
-"(Use estes campos quando você escolhar 'Definido pelo Usuário' para uma "
-"condição ou ação)"
-
-#: html/Ticket/Elements/EditWatchers:62 html/Ticket/Elements/ShowUserEntry:55
-msgid "(Will not be sent email)"
-msgstr "(Não enviará e-mail)"
-
-#: html/Tools/MyDay.html:53
-#. ($session{'CurrentUser'}->Name)
-msgid "(displaying new and open tickets for %1)"
-msgstr "(mostrando tíquetes novos e abertos pra %1)"
-
-#: html/Admin/Groups/index.html:59 html/User/Groups/index.html:56
-msgid "(empty)"
-msgstr "(vazio)"
-
-#: html/Admin/Users/index.html:62
-msgid "(no name listed)"
-msgstr "(nenhum nome listado)"
-
-#: NOT FOUND IN SOURCE
-msgid "(no subject)"
-msgstr "(Sem assunto)"
-
-#: html/Admin/Elements/SelectRights:74 html/Elements/EditCustomFieldSelect:71 html/Elements/SelectCustomFieldValue:53 html/Elements/ShowCustomFields:56 html/Search/Chart:134 html/Search/Elements/Chart:78 lib/RT/Transaction_Overlay.pm:612
-msgid "(no value)"
-msgstr "(sem valor)"
-
-#: html/Admin/Elements/EditCustomFieldValues:49
-msgid "(no values)"
-msgstr "(sem valores)"
-
-#: html/Elements/EditLinks:133 html/Ticket/Elements/BulkLinks:51
-msgid "(only one ticket)"
-msgstr "(somente um tíquete)"
-
-#: html/Elements/RT__Ticket/ColumnMap:151
-msgid "(pending approval)"
-msgstr "(aprovação pendente)"
-
-#: html/Elements/RT__Ticket/ColumnMap:154
-msgid "(pending other Collection)"
-msgstr "(outra Coleta pendente)"
-
-#: NOT FOUND IN SOURCE
-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:73
-msgid "(required)"
-msgstr "(requerido)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:84
-msgid "(untitled)"
-msgstr "(sem título)"
-
-#: html/Ticket/Elements/Reminders:135
-msgid "(yyyy/mm/dd)"
-msgstr "(aaaa/mm/dd)"
-
-#: html/Elements/EditCustomFieldSelect:59
-msgid "-"
-msgstr ""
-
-#: bin/rt-crontool:97
-msgid "--transaction argument could be only 'first' or 'last'"
-msgstr "--argumento da transação só pode ser 'first' (primeiro) ou 'last' (último)"
-
-#: 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:55
-msgid "<% $Ticket->Status%>"
-msgstr ""
-
-#: html/Elements/SelectTicketTypes:50
-msgid "<% $_ %>"
-msgstr ""
-
-#: html/Search/Elements/SelectLinks:50
-msgid "<%$_%>"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:75
-msgid "<%$field%>"
-msgstr ""
-
-#: html/Elements/CreateTicket:49
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" class=\"button\" value=\"New ticket in\" />&nbsp;%1"
-msgstr "<input type=\"submit\" class=\"button\" value=\"Novo tíquete em\" />&nbsp;%1"
-
-#: docs/design_docs/string-extraction-guide.txt:54 lib/RT/StyleGuide.pod:785
-#. ($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"
-
-#: etc/initialdata:218
-msgid "A blank template"
-msgstr "Um modelo vazio"
-
-#: html/Admin/Users/Modify.html:375
-msgid "A password was not set, so user won't be able to login."
-msgstr "Não foi definida uma senha, o usuário não estará apto a usar o sistema."
-
-#: 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:176 lib/RT/Principal_Overlay.pm:221
-msgid "ACE not found"
-msgstr "ACE não encontrado"
-
-#: lib/RT/ACE_Overlay.pm:855
-msgid "ACEs can only be created and deleted."
-msgstr "ACEs só podem ser criados e removidos."
-
-#: html/Search/Elements/SelectAndOr:48
-msgid "AND"
-msgstr "E"
-
-#: NOT FOUND IN SOURCE
-msgid "Aborting to avoid unintended ticket modifications.\\n"
-msgstr "Abortando para evitar modificações indesejadas no tíquete.\\n"
-
-#: html/User/Elements/Tabs:55
-msgid "About me"
-msgstr "Sobre mim"
-
-#: html/Admin/Users/Modify.html:108
-msgid "Access control"
-msgstr "Controle de acesso"
-
-#: html/Admin/Elements/EditScrip:67
-msgid "Action"
-msgstr "Ação"
-
-#: lib/RT/Scrip_Overlay.pm:174
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "Ação %1 não encontrada"
-
-#: NOT FOUND IN SOURCE
-msgid "Action committed."
-msgstr "Ação executada."
-
-#: bin/rt-crontool:173
-msgid "Action committed.\\n"
-msgstr "Ação executada.\\n"
-
-#: lib/RT/Scrip_Overlay.pm:170
-msgid "Action is mandatory argument"
-msgstr "Ação é um argumento obrigatório."
-
-#: bin/rt-crontool:169
-msgid "Action prepared..."
-msgstr "Ação preparada..."
-
-#: html/Search/Build.html:87
-msgid "Add"
-msgstr "Adicionar"
-
-#: html/Search/Bulk.html:94
-msgid "Add AdminCc"
-msgstr "Adicionar AdminCc"
-
-#: html/Search/Bulk.html:90
-msgid "Add Cc"
-msgstr "Adicionar Cc"
-
-#: html/Search/Elements/EditFormat:51
-msgid "Add Columns"
-msgstr "Adicionar Colunas"
-
-#: html/Search/Elements/PickCriteria:48
-msgid "Add Criteria"
-msgstr "Adicionar Critério"
-
-#: html/Ticket/Create.html:149 html/Ticket/Update.html:118
-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:86
-msgid "Add Requestor"
-msgstr "Adicionar Requisitante"
-
-#: html/Admin/Elements/AddCustomFieldValue:48
-msgid "Add Value"
-msgstr "Adicionar Valor"
-
-#: NOT FOUND IN SOURCE
-msgid "Add a Scrip to this queue"
-msgstr "Adicionar um Scrip a esta 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:85
-msgid "Add a scrip which will apply to all queues"
-msgstr "Adicionar um scrip que se aplicará a todas as filas "
-
-#: html/Search/Build.html:111 html/Search/Build.html:96
-msgid "Add and Search"
-msgstr "Adicionar e Buscar"
-
-#: html/Search/Bulk.html:126
-msgid "Add comments or replies to selected tickets"
-msgstr "Adicionar comentários ou respostas aos tíquetes selecionados"
-
-#: html/Admin/Groups/Members.html:65 html/User/Groups/Members.html:62
-msgid "Add members"
-msgstr "Adicionar membros"
-
-#: html/Admin/Queues/People.html:89 html/Ticket/Elements/AddWatchers:51
-msgid "Add new watchers"
-msgstr "Adicionar novos observadores"
-
-#: html/Search/Build.html:87
-msgid "Add these terms to your search"
-msgstr "Adicionar estes termos à sua busca"
-
-#: html/Search/Bulk.html:160
-msgid "Add values"
-msgstr "Adicionar valores"
-
-#: lib/RT/CustomField_Overlay.pm:110
-msgid "Add, delete and modify custom field values for objects"
-msgstr "Adicionar, remover e modificar valores de campos personalizados para objetos"
-
-#: NOT FOUND IN SOURCE
-msgid "AddNextState"
-msgstr "AddNextState"
-
-#: lib/RT/Queue_Overlay.pm:765
-#. ($args{'Type'})
-msgid "Added principal as a %1 for this queue"
-msgstr "Usuário/Grupo adicionado como um %1 desta fila"
-
-#: lib/RT/Ticket_Overlay.pm:1457
-#. ($self->loc($args{'Type'}))
-msgid "Added principal as a %1 for this ticket"
-msgstr "Usuário/Grupo adicionado como um %1 deste tíquete"
-
-#: html/Admin/Users/Modify.html:149 html/User/Prefs.html:135
-msgid "Address1"
-msgstr "Endereço 1"
-
-#: html/Admin/Users/Modify.html:154 html/User/Prefs.html:139
-msgid "Address2"
-msgstr "Endereço 2"
-
-#: html/Ticket/Create.html:100
-msgid "Admin Cc"
-msgstr "Admin Cc"
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "Comentário do Administrador"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr "Correspondência do Administrador"
-
-#: html/Admin/Queues/index.html:48 html/Admin/Queues/index.html:51
-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:49 html/Admin/Global/index.html:51
-msgid "Admin/Global configuration"
-msgstr "Administração da configuração global"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Groups"
-msgstr "Administração de Grupos"
-
-#: NOT FOUND IN SOURCE
-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:62 lib/RT/ACE_Overlay.pm:115
-msgid "AdminCc"
-msgstr "AdminCc"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminComment"
-msgstr "ComentarioAdministrador"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCorrespondence"
-msgstr "CorrespondenciaAdministrador"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "AdminCustomField"
-msgstr "AdministrarCampoPersonalizado"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCustomFields"
-msgstr "AdministrarCamposPersonalizados"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "AdminGroup"
-msgstr "AdministrarGrupo"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "AdminGroupMembership"
-msgstr "AdministrarAfiliacaoGrupop"
-
-#: lib/RT/System.pm:82
-msgid "AdminOwnPersonalGroups"
-msgstr "AdminOwnPersonalGroups"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "AdminQueue"
-msgstr "AdministrarFila"
-
-#: lib/RT/System.pm:83
-msgid "AdminUsers"
-msgstr "AdministrarUsuários"
-
-#: html/Admin/Queues/People.html:71 html/Ticket/Elements/EditPeople:77
-msgid "Administrative Cc"
-msgstr "Cc Administrativo"
-
-#: NOT FOUND IN SOURCE
-msgid "Admins"
-msgstr "Administradores"
-
-#: html/Ticket/Elements/Tabs:218
-msgid "Advanced"
-msgstr "Avançado"
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search"
-msgstr "Busca avançada"
-
-#: html/Elements/SelectDateRelation:59
-msgid "After"
-msgstr "Depois de"
-
-#: NOT FOUND IN SOURCE
-msgid "Age"
-msgstr "Idade"
-
-#: html/Search/Elements/PickCriteria:54
-msgid "Aggregator"
-msgstr "Agregador"
-
-#: NOT FOUND IN SOURCE
-msgid "Alias for"
-msgstr "Aliás para"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr "Todas as Aprovações Concedidas"
-
-#: NOT FOUND IN SOURCE
-msgid "All Custom Fields"
-msgstr "Todos os Campos Personalizados"
-
-#: html/Admin/Queues/index.html:77
-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/Search/Elements/EditQuery:58
-msgid "And/Or"
-msgstr "E/Ou"
-
-#: html/Admin/CustomFields/Modify.html:75 html/Admin/Elements/CustomFieldTabs:85
-msgid "Applies to"
-msgstr "Aplica-se a"
-
-#: html/Search/Edit.html:66
-msgid "Apply"
-msgstr "Aplicar"
-
-#: html/Search/Edit.html:66
-msgid "Apply your changes"
-msgstr "Aplicar suas alterações"
-
-#: html/Elements/Tabs:80
-msgid "Approval"
-msgstr "Aprovação"
-
-#: html/Approvals/Display.html:67 html/Approvals/Elements/ShowDependency:65 html/Approvals/index.html:88
-#. ($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:77
-#. ($ticket->Id)
-msgid "Approval #%1: Notes not recorded due to a system error"
-msgstr "Aprovação #%1: Anotações não registradas devido a um erro de sistema"
-
-#: html/Approvals/index.html:75
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr "Aprovação #%1: Anotações registradas"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Details"
-msgstr "Detalhes da Aprovação"
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr "Aprovação Concedida"
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr "Aprovação Rejeitada"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval diagram"
-msgstr "Diagrama da aprovação"
-
-#: html/Approvals/Elements/Approve:71
-msgid "Approve"
-msgstr "Aprove"
-
-#: etc/initialdata:504
-msgid "Approver's notes: %1"
-msgstr "Anotações do aprovador: %1"
-
-#: lib/RT/Date.pm:446
-msgid "Apr."
-msgstr "Abr."
-
-#: NOT FOUND IN SOURCE
-msgid "April"
-msgstr "Abril"
-
-#: html/Search/Elements/DisplayOptions:83
-msgid "Asc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:58
-msgid "Ascending"
-msgstr "Ascendente"
-
-#: lib/RT/Queue_Overlay.pm:98
-msgid "Assign and remove custom fields"
-msgstr "Definir e remover campos personalizados"
-
-#: lib/RT/Queue_Overlay.pm:98
-msgid "AssignCustomFields"
-msgstr "DefinirCamposPersonalizados"
-
-#: html/Search/Bulk.html:144 html/SelfService/Update.html:89 html/Ticket/ModifyAll.html:117 html/Ticket/Update.html:118
-msgid "Attach"
-msgstr "Anexar"
-
-#: html/SelfService/Create.html:94 html/Ticket/Create.html:145
-msgid "Attach file"
-msgstr "Anexar arquivo"
-
-#: html/SelfService/Update.html:77 html/Ticket/Create.html:133 html/Ticket/Update.html:96
-msgid "Attached file"
-msgstr "Arquivo anexado"
-
-#: html/Ticket/ShowEmailRecord.html:54 html/Ticket/ShowEmailRecord.html:58 html/Ticket/ShowEmailRecord.html:61
-#. ($Attachment)
-msgid "Attachment '%1' could not be loaded"
-msgstr "Arquivo anexo '%1' não pôde ser carregado"
-
-#: lib/RT/Transaction_Overlay.pm:510
-msgid "Attachment created"
-msgstr "Arquivo anexo criado"
-
-#: lib/RT/Tickets_Overlay.pm:2061
-msgid "Attachment filename"
-msgstr "Nome do arquivo anexo"
-
-#: html/Ticket/Elements/ShowAttachments:49
-msgid "Attachments"
-msgstr "Arquivos anexos"
-
-#: lib/RT/Attributes_Overlay.pm:173
-msgid "Attribute Deleted"
-msgstr "Atributo Removido"
-
-#: lib/RT/Date.pm:450
-msgid "Aug."
-msgstr "Ago."
-
-#: NOT FOUND IN SOURCE
-msgid "August"
-msgstr "Agosto"
-
-#: NOT FOUND IN SOURCE
-msgid "AuthSystem"
-msgstr "Sistema de autenticação"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "RespostaAutomatica"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "Responder Automaticamente para Requisitantes"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoreplyToRequestors"
-msgstr "AutoreplyToRequestors"
-
-#: html/Widgets/SelectionBox:191
-msgid "Available"
-msgstr "Disponível"
-
-#: NOT FOUND IN SOURCE
-msgid "Bad PGP Signature: %1\\n"
-msgstr "Assinatura PGP inválida: %1\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
-msgstr ""
-"Identificador de arquivo anexo inválido. Não foi possível encontrar o arquivo '%"
-"1'\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Bad data in %1"
-msgstr "Dados inválidos em %1"
-
-#: NOT FOUND IN SOURCE
-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/CustomFieldTabs:67 html/Admin/Elements/GroupTabs:62 html/Admin/Elements/QueueTabs:62 html/Admin/Elements/UserTabs:60 html/Ticket/Elements/Tabs:115 html/User/Elements/GroupTabs:61
-msgid "Basics"
-msgstr "Básicos"
-
-#: html/Ticket/Update.html:90
-msgid "Bcc"
-msgstr "Bcc"
-
-#: html/Admin/CustomFields/GroupRights.html:93 html/Admin/CustomFields/UserRights.html:76 html/Admin/Elements/EditScrip:91
-msgid "Be sure to save your changes"
-msgstr "Não se esqueça de salvar suas alterações"
-
-#: html/Elements/SelectDateRelation:57 lib/RT/CurrentUser.pm:363
-msgid "Before"
-msgstr "Antes de"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin Approval"
-msgstr "Incício da Aprovação"
-
-#: html/Elements/Logo:49
-msgid "Best Practical Solutions, LLC corporate logo"
-msgstr ""
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "Vazio"
-
-#: html/Search/Elements/EditFormat:86
-msgid "Bold"
-msgstr "Negrito"
-
-#: NOT FOUND IN SOURCE
-msgid "Bookmarkable URL for this search"
-msgstr "URL para guardar esta busca em Favoritos"
-
-#: html/Search/Results.html:81
-msgid "Bookmarkable link"
-msgstr "Atalho para Favoritos"
-
-#: html/Ticket/Elements/ShowHistory:66 html/Ticket/Elements/ShowHistory:71
-msgid "Brief headers"
-msgstr "Cabeçalhos resumidos"
-
-#: html/Ticket/Elements/Tabs:228
-msgid "Bulk Update"
-msgstr "Atualização em Massa"
-
-#: NOT FOUND IN SOURCE
-msgid "Bulk ticket update"
-msgstr "Atualização de tíquetes em lote"
-
-#: lib/RT/User_Overlay.pm:1855
-msgid "Can not modify system users"
-msgstr "Não é possível modificar os usuários do sistema"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "Can this principal see this queue"
-msgstr "Este Usuário/Grupo pode ver esta fila"
-
-#: lib/RT/CustomField_Overlay.pm:382
-msgid "Can't add a custom field value without a name"
-msgstr "Não é possível adicionar um valor de campo personalizado sem um nome"
-
-#: html/Admin/CustomFields/Objects.html:88
-#. ($Class)
-msgid "Can't find a collection class for '%1'"
-msgstr "Não foi encontrada uma classe de coleta para '%1'"
-
-#: html/Search/Build.html:288
-msgid "Can't find a saved search to work with"
-msgstr "Não foi encontrada uma busca salva para ser trabalhada"
-
-#: lib/RT/Link_Overlay.pm:161
-msgid "Can't link a ticket to itself"
-msgstr "Não é possível vincular um tíquete a ele mesmo"
-
-#: NOT FOUND IN SOURCE
-msgid "Can't merge into a merged ticket. You should never get this error"
-msgstr "Não é possível unir a um tíquete já unido. Você nunca deve obter este erro"
-
-#: html/Widgets/SavedSearch:110
-#. (loc($self->{SearchType}))
-msgid "Can't save %1"
-msgstr "Não é possível salvar %1"
-
-#: html/Search/Build.html:292
-msgid "Can't save this search"
-msgstr "Não é possível salvar esta busca"
-
-#: lib/RT/Record.pm:1304 lib/RT/Record.pm:1380
-msgid "Can't specifiy both base and target"
-msgstr "Não especifique origem e destino simultaneamente"
-
-#: html/autohandler:206
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "Não é possível criar o usuário: %1"
-
-#: html/Admin/Elements/AddCustomFieldValue:64 html/Admin/Elements/EditCustomFieldValues:60
-msgid "Category"
-msgstr "Categoria"
-
-#: etc/initialdata:50 html/Admin/Queues/People.html:67 html/SelfService/Create.html:73 html/Ticket/Create.html:90 html/Ticket/Elements/EditPeople:74 html/Ticket/Elements/ShowPeople:58 html/Ticket/Update.html:85 lib/RT/ACE_Overlay.pm:114
-msgid "Cc"
-msgstr ""
-
-#: html/SelfService/Prefs.html:54
-msgid "Change password"
-msgstr "Mudar a senha"
-
-#: html/Elements/Submit:80
-msgid "Check All"
-msgstr "Marcar Tudo"
-
-#: html/SelfService/Update.html:80 html/Ticket/Create.html:136 html/Ticket/Update.html:99
-msgid "Check box to delete"
-msgstr "Marcar caixa para remover"
-
-#: html/Admin/Elements/SelectRights:57
-msgid "Check box to revoke right"
-msgstr "Marcar caixa para revogar o direito de acesso"
-
-#: html/Elements/EditLinks:149 html/Elements/EditLinks:86 html/Elements/ShowLinks:80 html/Ticket/Create.html:225 html/Ticket/Elements/BulkLinks:66
-msgid "Children"
-msgstr "Filhos"
-
-#: html/NoAuth/js/util.js:203
-msgid "Choose a date"
-msgstr "Escolher uma data"
-
-#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:143
-msgid "City"
-msgstr "Cidade"
-
-#: html/Widgets/SelectionBox:214
-msgid "Clear"
-msgstr "Limpar"
-
-#: html/Elements/Submit:82
-msgid "Clear All"
-msgstr "Limpar Tudo"
-
-#: html/Helpers/CalPopup.html:53
-msgid "Close window"
-msgstr "Fechar janela"
-
-#: html/Ticket/Elements/ShowDates:70
-msgid "Closed"
-msgstr "Fechado"
-
-#: NOT FOUND IN SOURCE
-msgid "Closed requests"
-msgstr "Requisições fechadas"
-
-#: html/SelfService/Closed.html:48 html/SelfService/Elements/Tabs:81
-msgid "Closed tickets"
-msgstr "Tíquetes fechados"
-
-#: NOT FOUND IN SOURCE
-msgid "Code"
-msgstr "Código"
-
-#: lib/RT/CustomField_Overlay.pm:91
-msgid "Combobox: Select or enter multiple values"
-msgstr "CaixaCombinada: Selecionar on informar múltiplos valores"
-
-#: lib/RT/CustomField_Overlay.pm:92
-msgid "Combobox: Select or enter one value"
-msgstr "CaixaCombinada: Selecionar ou informar um valor"
-
-#: lib/RT/CustomField_Overlay.pm:93
-msgid "Combobox: Select or enter up to %1 values"
-msgstr "CaixaCombinada: Selecionar ou informar até %1 valores"
-
-#: NOT FOUND IN SOURCE
-msgid "Command not understood!\\n"
-msgstr "Comando não entendido!\\n"
-
-#: html/Ticket/Elements/ShowTransaction:190 html/Ticket/Elements/Tabs:187
-msgid "Comment"
-msgstr "Comentar"
-
-#: html/Admin/Queues/Modify.html:81
-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:113
-msgid "Comment on tickets"
-msgstr "Comente sobre os tíquetes"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "CommentOnTicket"
-msgstr "CommetarioNoTiquete"
-
-#: html/Tools/MyDay.html:67
-msgid "Comments"
-msgstr "Comentários"
-
-#: html/Ticket/ModifyAll.html:93 html/Ticket/Update.html:77
-msgid "Comments (Not sent to requestors)"
-msgstr "Comentários (não enviados aos requisitantes)"
-
-#: html/Search/Bulk.html:130
-msgid "Comments (not sent to requestors)"
-msgstr "Comentários (não enviados aos requisitantes)"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments about %1"
-msgstr "Comentários sobre %1"
-
-#: html/Admin/Users/Modify.html:229 html/Ticket/Elements/ShowRequestor:69
-msgid "Comments about this user"
-msgstr "Comentários sobre este usuário"
-
-#: lib/RT/Transaction_Overlay.pm:655
-msgid "Comments added"
-msgstr "Comentários adicionados"
-
-#: lib/RT/Action/Generic.pm:177
-msgid "Commit Stubbed"
-msgstr "Execução Abortada"
-
-#: NOT FOUND IN SOURCE
-msgid "Compile Restrictions"
-msgstr "Compilar restrições"
-
-#: html/Admin/Elements/EditScrip:61
-msgid "Condition"
-msgstr "Condição"
-
-#: lib/RT/Scrip_Overlay.pm:186
-msgid "Condition is mandatory argument"
-msgstr "Condição é um argumento obrigatório"
-
-#: bin/rt-crontool:153
-msgid "Condition matches..."
-msgstr "Condição satisfeita..."
-
-#: lib/RT/Scrip_Overlay.pm:190
-msgid "Condition not found"
-msgstr "Condição não encontrada"
-
-#: html/Elements/Tabs:87
-msgid "Configuration"
-msgstr "Configuração"
-
-#: html/SelfService/Prefs.html:56
-msgid "Confirm"
-msgstr "Confirmar"
-
-#: NOT FOUND IN SOURCE
-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:67 html/Elements/SelectAttachmentField:50 html/Ticket/ModifyAll.html:121
-msgid "Content"
-msgstr "Conteúdo"
-
-#: html/Elements/SelectAttachmentField:51
-msgid "Content-Type"
-msgstr "Tipo-de-Conteúdo"
-
-#: NOT FOUND IN SOURCE
-msgid "Coould not create group"
-msgstr "Não foi possível criar o grupo"
-
-#: html/Search/Elements/EditSearches:67
-msgid "Copy"
-msgstr "Copiar"
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "Correspondência"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence Address"
-msgstr "Endereço de correspondência"
-
-#: lib/RT/Transaction_Overlay.pm:651
-msgid "Correspondence added"
-msgstr "Correspondência adicionada"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence not recorded"
-msgstr "Correspondência não registrada"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not add new custom field value for ticket. "
-msgstr "Não foi possível 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 foi possível adicionar novo valor de campo personalizado para o tíquete. %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not add new custom field value. "
-msgstr "Não foi possível adicionar novo valor do campo personalizado."
-
-#: NOT FOUND IN SOURCE
-msgid "Could not add new custom field value. %1 "
-msgstr "Não foi possível adicionar novo valor do campo personalizado. %1"
-
-#: lib/RT/Record.pm:1682 lib/RT/Record.pm:1729
-#. ($value_msg)
-msgid "Could not add new custom field value: %1"
-msgstr "Não foi possível adicionar novo valor do campo personalizado: %1"
-
-#: lib/RT/Ticket_Overlay.pm:3071 lib/RT/Ticket_Overlay.pm:3079 lib/RT/Ticket_Overlay.pm:3096
-msgid "Could not change owner. "
-msgstr "Não foi possível mudar o proprietário. "
-
-#: html/Admin/CustomFields/Modify.html:163
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr "Não foi possível criar CampoPersonalizado"
-
-#: html/Admin/Elements/EditCustomField:115
-#. ($msg)
-msgid "Could not create CustomField: %1"
-msgstr "Não foi possível criar CampoPersonalizado: %1"
-
-#: html/User/Groups/Modify.html:100 lib/RT/Group_Overlay.pm:496 lib/RT/Group_Overlay.pm:503
-msgid "Could not create group"
-msgstr "Não foi possível criar o grupo"
-
-#: html/Admin/Global/Template.html:98 html/Admin/Queues/Template.html:95
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr "Não foi possível criar o modelo: %1"
-
-#: lib/RT/Ticket_Overlay.pm:1077 lib/RT/Ticket_Overlay.pm:409
-msgid "Could not create ticket. Queue not set"
-msgstr "Não foi possível criar o tíquete. Fila não definida"
-
-#: lib/RT/User_Overlay.pm:257 lib/RT/User_Overlay.pm:271 lib/RT/User_Overlay.pm:280 lib/RT/User_Overlay.pm:289 lib/RT/User_Overlay.pm:298 lib/RT/User_Overlay.pm:312 lib/RT/User_Overlay.pm:322 lib/RT/User_Overlay.pm:498
-msgid "Could not create user"
-msgstr "Não foi possível criar o usuário"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create watcher for requestor"
-msgstr "Não foi possível criar um observador para o requisitante"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not find a ticket with id %1"
-msgstr "Não foi possível encontrar um tíquete com identificador %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not find group %1."
-msgstr "Não foi possível encontrar o grupo %1."
-
-#: lib/RT/Queue_Overlay.pm:743 lib/RT/Ticket_Overlay.pm:1425
-msgid "Could not find or create that user"
-msgstr "Não foi possível encontrar ou criar este usuário"
-
-#: lib/RT/Queue_Overlay.pm:804 lib/RT/Ticket_Overlay.pm:1506
-msgid "Could not find that principal"
-msgstr "Não foi possível encontrar este usuário/grupo"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not find user %1."
-msgstr "Não foi possível encontrar o usuário %1."
-
-#: html/Admin/CustomFields/Objects.html:71
-msgid "Could not load CustomField %1"
-msgstr "Não foi possível caarregar CampoPersonalizado %1"
-
-#: html/Admin/Groups/Members.html:114 html/User/Groups/Members.html:113 html/User/Groups/Modify.html:105
-msgid "Could not load group"
-msgstr "Não foi possível carregar o grupo"
-
-#: lib/RT/SavedSearch.pm:121
-#. ($privacy)
-msgid "Could not load object for %1"
-msgstr "Não foi possível carregar objeto para %1"
-
-#: lib/RT/SavedSearch.pm:199
-msgid "Could not load search attribute"
-msgstr "Não foi possível carregar atributo de busca"
-
-#: lib/RT/Queue_Overlay.pm:763
-#. ($args{'Type'})
-msgid "Could not make that principal a %1 for this queue"
-msgstr "Não foi possível fazer deste usuário/grupo um %1 desta fila"
-
-#: lib/RT/Ticket_Overlay.pm:1446
-#. ($self->loc($args{'Type'}))
-msgid "Could not make that principal a %1 for this ticket"
-msgstr "Não foi possível fazer deste usuário/grupo um %1 deste tíquete"
-
-#: lib/RT/Queue_Overlay.pm:862
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this queue"
-msgstr "Não foi possível remover este usuário/grupo como um %1 desta fila"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not remove that principal as a %1 for this ticket"
-msgstr "Não foi possível remover este usuário/grupo como um %1 deste tíquete"
-
-#: lib/RT/User_Overlay.pm:193
-msgid "Could not set user info"
-msgstr "Não foi possível definir informações sobre usuário"
-
-#: lib/RT/Transaction_Overlay.pm:161
-msgid "Couldn't add attachment"
-msgstr "Não foi possível adicionar anexo"
-
-#: lib/RT/Group_Overlay.pm:1005
-msgid "Couldn't add member to group"
-msgstr "Não foi possível adicionar o membro ao grupo"
-
-#: lib/RT/Record.pm:1741 lib/RT/Record.pm:1793
-#. ($Msg)
-msgid "Couldn't create a transaction: %1"
-msgstr "Não foi possível 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/Record.pm:970
-msgid "Couldn't find row"
-msgstr "Não foi possível encontrar o registro"
-
-#: lib/RT/Group_Overlay.pm:979
-msgid "Couldn't find that principal"
-msgstr "Não foi possível encontrar este usuário/grupo"
-
-#: lib/RT/CustomField_Overlay.pm:412
-msgid "Couldn't find that value"
-msgstr "Não foi possível encontrar este valor"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't find that watcher"
-msgstr "Não foi possível encontrar este observador"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't find user\\n"
-msgstr "Não foi possível encontrar o usuário\\n"
-
-#: lib/RT/CurrentUser.pm:147
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr "Não foi possível carregar %1 do banco de dados de usuários.\\n"
-
-#: html/Admin/CustomFields/UserRights.html:151
-#. ($id)
-msgid "Couldn't load Class %1"
-msgstr "Não foi possível carregar Classe %1"
-
-#: html/Admin/CustomFields/GroupRights.html:109
-#. ($id)
-msgid "Couldn't load CustomField %1"
-msgstr "Não foi possível carregar CampoPersonalizado %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load KeywordSelects."
-msgstr "Não foi possível carregar os KeywordSelects."
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load RT config file '%1' %2"
-msgstr "Não foi possível carregar o arquivo de configuração do RT '%1' %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load Scrips."
-msgstr "Não foi possível carregar os Scrips."
-
-#: lib/RT/Ticket_Overlay.pm:2018
-#. ($self->Id)
-msgid "Couldn't load copy of ticket #%1."
-msgstr "Não foi possível carregar cópia do tíquete #%1."
-
-#: html/Admin/Groups/GroupRights.html:111 html/Admin/Groups/UserRights.html:98
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr "Não foi possível carregar o grupo %1"
-
-#: lib/RT/Link_Overlay.pm:204 lib/RT/Link_Overlay.pm:213 lib/RT/Link_Overlay.pm:240
-msgid "Couldn't load link"
-msgstr "Não foi possível carregar o vínculo"
-
-#: html/Admin/Elements/ObjectCustomFields:85 html/Admin/Queues/CustomFields.html:61 html/Admin/Users/CustomFields.html:61
-#. ($id)
-msgid "Couldn't load object %1"
-msgstr "Não foi possível carregar objeto %1"
-
-#: html/Admin/Queues/People.html:144
-#. ($id)
-msgid "Couldn't load queue"
-msgstr "Não foi possível carregar a fila"
-
-#
-#: html/Admin/Queues/GroupRights.html:124 html/Admin/Queues/UserRights.html:95
-#. ($id)
-msgid "Couldn't load queue %1"
-msgstr "Não foi possível carregar a fila %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load scrip"
-msgstr "Não foi possível carregar o scrip"
-
-#: html/Admin/Elements/EditScrip:128 html/Admin/Elements/EditScrip:169
-#. ($id)
-msgid "Couldn't load scrip #%1"
-msgstr "Não foi possível carregar scrip #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load template"
-msgstr "Não foi possível carregar o modelo"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load that user (%1)"
-msgstr "Não foi possível carregar este usuário (%1)"
-
-#
-#: html/SelfService/Display.html:160 lib/RT/Action/CreateTickets.pm:682
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr "Não foi possível carregar o tíquete '%1'"
-
-#: lib/RT/Ticket_Overlay.pm:2646
-#. ($args{'URI'})
-msgid "Couldn't resolve '%1' into a URI."
-msgstr "Não foi possível resolver '%1' dentro de uma URI."
-
-#: html/Admin/Users/Modify.html:176 html/User/Prefs.html:155
-msgid "Country"
-msgstr "País"
-
-#: html/Admin/Elements/CreateUserCalled:49 html/Admin/Elements/EditCustomField:86 html/Admin/Elements/EditScrip:135 html/Admin/Queues/Template.html:68 html/Elements/QuickCreate:67 html/Ticket/Create.html:170 html/Ticket/Create.html:237
-msgid "Create"
-msgstr "Criar"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "Criar Tíquetes"
-
-#: html/Admin/CustomFields/Modify.html:152 html/Admin/Elements/EditCustomField:98
-msgid "Create a CustomField"
-msgstr "Criar um CampoPersonalizado"
-
-#: html/Admin/Queues/CustomField.html:71
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr "Criar um Campo Personalizado para a fila %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a CustomField that 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:105 html/Admin/Groups/Modify.html:131
-msgid "Create a new group"
-msgstr "Criar um novo grupo"
-
-#: html/User/Groups/Modify.html:115 html/User/Groups/Modify.html:90
-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/Ticket/Create.html:49 html/Ticket/Create.html:53 html/Ticket/Create.html:62
-msgid "Create a new ticket"
-msgstr "Criar um novo tíquete"
-
-#: html/Admin/Users/Modify.html:256 html/Admin/Users/Modify.html:318
-msgid "Create a new user"
-msgstr "Criar um novo usuário"
-
-#: html/Admin/Queues/Modify.html:127
-msgid "Create a queue"
-msgstr "Criar uma fila"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a queue called"
-msgstr "Criar uma fila chamada"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a request"
-msgstr "Criar uma requisição"
-
-#: html/Admin/Queues/Scrip.html:91
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr "Criar um scrip para a fila %1"
-
-#: html/Admin/Global/Template.html:92 html/Admin/Queues/Template.html:88
-msgid "Create a template"
-msgstr "Criar um modelo"
-
-#: html/SelfService/Create.html:48 html/SelfService/CreateTicketInQueue.html:48
-msgid "Create a ticket"
-msgstr "Criar um tíquete"
-
-#: 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:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "Criar novos tíquetes baseados no modelo deste scrip"
-
-#: html/SelfService/Create.html:107
-msgid "Create ticket"
-msgstr "Criar um tíquete"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "Create tickets in this queue"
-msgstr "Criar tíquetes nesta fila"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "Create, delete and modify custom fields"
-msgstr "Criar, remover e modificar campos personalizados"
-
-#: lib/RT/Queue_Overlay.pm:94
-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:82
-msgid "Create, delete and modify the members of personal groups"
-msgstr "Criar, remover e modificar os membros de grupos pessoais"
-
-#: lib/RT/System.pm:83
-msgid "Create, delete and modify users"
-msgstr "Criar, remover e modificar usuários"
-
-#: lib/RT/System.pm:89
-msgid "CreateSavedSearch"
-msgstr "CriarBuscaSalva"
-
-#
-#: lib/RT/Queue_Overlay.pm:111
-msgid "CreateTicket"
-msgstr "CriarTiquete"
-
-#: html/Elements/SelectDateType:49 html/Ticket/Elements/ShowDates:50 lib/RT/Ticket_Overlay.pm:1171
-msgid "Created"
-msgstr "Criado"
-
-#: html/Admin/CustomFields/Modify.html:165 html/Admin/Elements/EditCustomField:119
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "CampoPersonalizado %1 criado"
-
-#: html/Tools/Reports/Elements/Tabs:65
-msgid "Created in a date range"
-msgstr "Criados em um intervalo de datas"
-
-#: NOT FOUND IN SOURCE
-msgid "Created template %1"
-msgstr "Modelo %1 criado"
-
-#: html/Tools/Reports/CreatedByDates.html:54
-msgid "Created tickets in period, grouped by status"
-msgstr "Tíquetes criados no período, agrupados por estado"
-
-#: html/Search/Elements/PickBasics:104
-msgid "Creator"
-msgstr "Criador"
-
-#: html/Elements/EditLinks:51
-msgid "Current Links"
-msgstr "Relações atuais"
-
-#
-#: html/Admin/Elements/EditScrips:53
-msgid "Current Scrips"
-msgstr "Scrips Atuais"
-
-#: html/Admin/Groups/Members.html:62 html/User/Groups/Members.html:65
-msgid "Current members"
-msgstr "Membros atuais"
-
-#: html/Admin/Elements/SelectRights:53
-msgid "Current rights"
-msgstr "Direitos de acesso atuais"
-
-#: html/Search/Elements/EditQuery:49
-msgid "Current search"
-msgstr "Busca atual"
-
-#: NOT FOUND IN SOURCE
-msgid "Current search criteria"
-msgstr "Critério de busca atual"
-
-#: html/Admin/Queues/People.html:64 html/Ticket/Elements/EditPeople:68
-msgid "Current watchers"
-msgstr "Observadores atuais"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Field #%1"
-msgstr "Campo Personalizado #%1"
-
-#: html/Admin/Elements/SystemTabs:63 html/Admin/Elements/Tabs:64 html/Admin/Global/index.html:73 html/Admin/Users/Modify.html:209 html/Admin/index.html:79 html/Ticket/Elements/ShowSummary:58
-msgid "Custom Fields"
-msgstr "Campos Personalizados"
-
-#: html/Admin/CustomFields/index.html:62
-#. ($lookup)
-msgid "Custom Fields for %1"
-msgstr "Campos Personalizados para %1"
-
-#
-#: html/Admin/Elements/EditScrip:109
-msgid "Custom action cleanup code"
-msgstr "Código de finalização de ação personalizada"
-
-#
-#: html/Admin/Elements/EditScrip:105
-msgid "Custom action preparation code"
-msgstr "Código de preparação de ação personalizada"
-
-#
-#: html/Admin/Elements/EditScrip:101
-msgid "Custom condition"
-msgstr "Condição personalizada"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 %2 %3"
-msgstr "Campo personalizado %1 %2 %3"
-
-#
-#: lib/RT/Tickets_Overlay.pm:2540
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr "Campo personalizado %1 tem um valor."
-
-#
-#: lib/RT/Tickets_Overlay.pm:2536
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr "Campo personalizado %1 não tem valor."
-
-#: lib/RT/Record.pm:1614 lib/RT/Record.pm:1776
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr "Campo personalizado %1 não encontrado"
-
-#: lib/RT/Report/Tickets.pm:120 lib/RT/Report/Tickets.pm:123
-#. ($cf)
-#. ($obj->Name)
-msgid "Custom field '%1'"
-msgstr "Campo prsonalizado '%1'"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field deleted"
-msgstr "Campo personalizado removido"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field not found"
-msgstr "Campo personalizado não encontrado"
-
-#: lib/RT/CustomField_Overlay.pm:1160
-#. ($args{'Content'}, $self->Name)
-msgid "Custom field value %1 could not be found for custom field %2"
-msgstr "Valor de campo %1 não pôde ser encontrado para campo personalizado %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field value changed from %1 to %2"
-msgstr "O valor do campo personalizado foi mudado de %1 para %2"
-
-#: lib/RT/CustomField_Overlay.pm:422
-msgid "Custom field value could not be deleted"
-msgstr "Valor do campo personalizado não pôde ser removido"
-
-#: lib/RT/CustomField_Overlay.pm:1172
-msgid "Custom field value could not be found"
-msgstr "Valor de campo personalizado não pôde ser encontrado"
-
-#: lib/RT/CustomField_Overlay.pm:1174 lib/RT/CustomField_Overlay.pm:420
-msgid "Custom field value deleted"
-msgstr "Valor do campo personalizado removido"
-
-#: html/Elements/SelectGroups:53 html/Elements/SelectUsers:53 lib/RT/Transaction_Overlay.pm:659
-msgid "CustomField"
-msgstr "CampoPersonalizado"
-
-#: html/Prefs/MyRT.html:80 html/Prefs/Quicksearch.html:72 html/Prefs/Search.html:77
-msgid "Customize"
-msgstr "Personalizar"
-
-#: NOT FOUND IN SOURCE
-msgid "Data error"
-msgstr "Erro de dado"
-
-#: html/SelfService/Display.html:63 html/Ticket/Create.html:205 html/Ticket/Elements/ShowSummary:91 html/Ticket/Elements/Tabs:118 html/Ticket/ModifyAll.html:67
-msgid "Dates"
-msgstr "Datas"
-
-#: lib/RT/Date.pm:454
-msgid "Dec."
-msgstr "Dez."
-
-#: NOT FOUND IN SOURCE
-msgid "December"
-msgstr "Dezembro"
-
-#: NOT FOUND IN SOURCE
-msgid "Default Autoresponse Template"
-msgstr "Modelo Padrão de RespostaAutomatica"
-
-#: etc/initialdata:222
-msgid "Default Autoresponse template"
-msgstr "Modelo Padrão de RespostaAutomatica"
-
-#: html/Tools/Offline.html:63
-msgid "Default Queue"
-msgstr "Fila Padrão"
-
-#: html/Tools/Offline.html:72
-msgid "Default Requestor"
-msgstr "Requisitante Padrão"
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr "Modelo padrão de comentário administrativo"
-
-#: etc/initialdata:275
-msgid "Default admin correspondence template"
-msgstr "Modelo padrão de correspondência administrativa"
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr "Modelo padrão de correspondência"
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr "Modelo padrão de transação"
-
-#: NOT FOUND IN SOURCE
-msgid "Default: %1/%2 changed from %3 to %4"
-msgstr "Padrão: %1/%2 mudou de %3 para %4"
-
-#: html/User/Delegation.html:48 html/User/Delegation.html:51
-msgid "Delegate rights"
-msgstr "Delegar direitos de acesso"
-
-#: lib/RT/System.pm:86
-msgid "Delegate specific rights which have been granted to you."
-msgstr "Delegar direitos específicos que foram outorgados a você."
-
-#: lib/RT/System.pm:86
-msgid "DelegateRights"
-msgstr "DelegateRights"
-
-#: html/User/Elements/Tabs:61
-msgid "Delegation"
-msgstr "Delegação"
-
-#: html/Admin/Elements/EditScrips:77 html/Search/Elements/EditFormat:105 html/Search/Elements/EditQuery:59 html/Search/Elements/EditSearches:65 html/Widgets/SelectionBox:212
-msgid "Delete"
-msgstr "Remover"
-
-#: html/Admin/Elements/EditTemplates:81
-msgid "Delete Template"
-msgstr "Remover Modelo"
-
-#: lib/RT/SavedSearch.pm:222
-#. ($msg)
-msgid "Delete failed: %1"
-msgstr "Remoção falhou: %1"
-
-#: html/Admin/Elements/EditScrips:76
-msgid "Delete selected scrips"
-msgstr "Remover scrips selecionados"
-
-#: lib/RT/Queue_Overlay.pm:116
-msgid "Delete tickets"
-msgstr "Remover tíquetes"
-
-#: html/Search/Bulk.html:161
-msgid "Delete values"
-msgstr "Remover valores"
-
-#: lib/RT/Queue_Overlay.pm:116
-msgid "DeleteTicket"
-msgstr "DeleteTicket"
-
-#: lib/RT/SavedSearch.pm:220
-msgid "Deleted search"
-msgstr "Busca removida"
-
-#: NOT FOUND IN SOURCE
-msgid "Deleting this object could break referential integrity"
-msgstr "Ao remover este objeto você pode quebrar a integridade referencial"
-
-#: lib/RT/Queue_Overlay.pm:396
-msgid "Deleting this object would break referential integrity"
-msgstr "A remoção deste objeto quebra a integridade referencial"
-
-#: lib/RT/User_Overlay.pm:514
-msgid "Deleting this object would violate referential integrity"
-msgstr "A remoção deste objeto 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:75
-msgid "Deny"
-msgstr "Negar"
-
-#: html/Elements/EditLinks:141 html/Elements/EditLinks:68 html/Elements/ShowLinks:60 html/Ticket/Create.html:223 html/Ticket/Elements/BulkLinks:58 html/Ticket/Elements/ShowDependencies:55
-msgid "Depended on by"
-msgstr "Dependem deste tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "Dependencies: \\n"
-msgstr "Dependências: \\n"
-
-#: lib/RT/Transaction_Overlay.pm:739
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr "Dependência por %1 adicionada"
-
-#: lib/RT/Transaction_Overlay.pm:779
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr "Dependência por %1 removida"
-
-#: lib/RT/Transaction_Overlay.pm:736
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr "Dependência de %1 adicionada"
-
-#: lib/RT/Transaction_Overlay.pm:776
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr "Dependência de %1 removida"
-
-#: html/Elements/EditLinks:137 html/Elements/EditLinks:59 html/Elements/SelectLinkType:50 html/Elements/ShowLinks:50 html/Ticket/Create.html:222 html/Ticket/Elements/BulkLinks:54 html/Ticket/Elements/ShowDependencies:48
-msgid "Depends on"
-msgstr "Depende de"
-
-#: NOT FOUND IN SOURCE
-msgid "DependsOn"
-msgstr "DependeDe"
-
-#: html/Search/Elements/DisplayOptions:88
-msgid "Desc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:58
-msgid "Descending"
-msgstr "Descendente"
-
-#: html/SelfService/Create.html:102 html/Ticket/Create.html:154
-msgid "Describe the issue below"
-msgstr "Descreva o problema abaixo"
-
-#: html/Admin/CustomFields/Modify.html:63 html/Admin/Elements/AddCustomFieldValue:59 html/Admin/Elements/EditCustomField:62 html/Admin/Elements/EditCustomFieldValues:58 html/Admin/Elements/EditScrip:57 html/Admin/Elements/ModifyTemplate:59 html/Admin/Groups/Modify.html:73 html/Admin/Queues/Modify.html:71 html/Search/Elements/EditSearches:58 html/User/Groups/Modify.html:72
-msgid "Description"
-msgstr "Descrição"
-
-#: NOT FOUND IN SOURCE
-msgid "Details"
-msgstr "Detalhes"
-
-#: html/Search/Elements/EditFormat:73 html/Ticket/Elements/Tabs:110
-msgid "Display"
-msgstr "Mostrar"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "Display Access Control List"
-msgstr "Mostrar Lista de Controle de Acesso"
-
-#: html/Search/Elements/DisplayOptions:48
-msgid "Display Columns"
-msgstr "Mostrar Colunas"
-
-#: lib/RT/Queue_Overlay.pm:101
-msgid "Display Scrip templates for this queue"
-msgstr "Mostrar os modelos de Scrip desta fila"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "Display Scrips for this queue"
-msgstr "Mostrar os Scrips desta fila"
-
-#: html/Ticket/Elements/ShowHistory:61
-msgid "Display mode"
-msgstr "Modo de apresentação"
-
-#: lib/RT/Group_Overlay.pm:170
-msgid "Display saved searches for this group"
-msgstr "Mostrar buscas salvas deste grupo"
-
-#: NOT FOUND IN SOURCE
-msgid "Display ticket #%1"
-msgstr "Apresentar o tíquete #%1"
-
-#: html/Elements/Footer:63
-msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>"
-msgstr "Distribuido sob a versão 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> da GNU GPL.</a>"
-
-#: lib/RT/System.pm:77
-msgid "Do anything and everything"
-msgstr "Fazer qualquer coisa"
-
-#: html/Elements/Refresh:53
-msgid "Don't refresh this page."
-msgstr "Não recarregar esta página."
-
-#: NOT FOUND IN SOURCE
-msgid "Don't show search results"
-msgstr "Não mostrar resultados da busca"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:84
-msgid "Download"
-msgstr "Baixar"
-
-#: html/Admin/Groups/index.html:63 html/Admin/Users/index.html:66
-msgid "Download as a tab-delimited file"
-msgstr "Baixar como um arquivo com campos delimitados por tabulação"
-
-#: html/Elements/SelectDateType:55 html/Ticket/Create.html:211 html/Ticket/Elements/EditDates:68 html/Ticket/Elements/Reminders:135 html/Ticket/Elements/ShowDates:66 lib/RT/Ticket_Overlay.pm:1175
-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"
-
-#: NOT FOUND IN SOURCE
-msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
-msgstr "ERRO: Não foi possível carregar o tíquete '%1': %2.\\n"
-
-#: html/Elements/Quicksearch:50 html/Elements/ShowSearch:51 html/index.html:109
-msgid "Edit"
-msgstr "Editar"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Conditions"
-msgstr "Editar Condições"
-
-#: html/Search/Bulk.html:151
-msgid "Edit Custom Fields"
-msgstr "Editar Campos Personalizados"
-
-#: html/Admin/Elements/ObjectCustomFields:94 html/Admin/Queues/CustomFields.html:66 html/Admin/Users/CustomFields.html:66
-#. ($Object->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "Editar Campos Personalizados para %1"
-
-#: html/Admin/Global/CustomFields/Groups.html:56
-msgid "Edit Custom Fields for all groups"
-msgstr "Editar Campos Personalizados para todos os grupos"
-
-#: html/Admin/Global/CustomFields/Users.html:56
-msgid "Edit Custom Fields for all users"
-msgstr "Editar Campos Personalizados para todos os usuários"
-
-#: html/Admin/Global/CustomFields/Queue-Tickets.html:56 html/Admin/Global/CustomFields/Queue-Transactions.html:56
-msgid "Edit Custom Fields for tickets in all queues"
-msgstr "Editar Campos Personalizados para tíquetes em todas as filas"
-
-#: html/Search/Bulk.html:190 html/Ticket/ModifyLinks.html:59
-msgid "Edit Links"
-msgstr "Editar Vínculos"
-
-#: html/Search/Edit.html:70
-msgid "Edit Query"
-msgstr "Editar Consulta"
-
-#: html/Ticket/Elements/Tabs:216
-msgid "Edit Search"
-msgstr "Editar Busca"
-
-#: html/Admin/Queues/Templates.html:65
-#. ($QueueObj->Name)
-msgid "Edit Templates for queue %1"
-msgstr "Editar Modelos para a fila %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit keywords"
-msgstr "Editar palavras chave"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "Edit saved searches for this group"
-msgstr "Editar buscas salvas deste grupo"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit scrips"
-msgstr "Editar scrips"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:62 html/Admin/Global/index.html:69
-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"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "EditSavedSearches"
-msgstr "EditBuscasSalvas"
-
-#: html/Admin/Queues/Modify.html:142
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr "Editando a configuração para a fila %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing Configuration for user %1"
-msgstr "Editando a configuração para o usuário %1"
-
-#: html/Admin/CustomFields/Modify.html:169 html/Admin/Elements/EditCustomField:122
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr "Editando o campo %1"
-
-#: html/Admin/Groups/Members.html:55
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr "Editando afiliados do grupo %1"
-
-#: html/User/Groups/Members.html:152
-#. ($Group->Name)
-msgid "Editing membership for personal group %1"
-msgstr "Editando afiliados do grupo pessoal %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing template %1"
-msgstr "Editando o modelo %1"
-
-#: lib/RT/Record.pm:1317 lib/RT/Record.pm:1394 lib/RT/Ticket_Overlay.pm:2521 lib/RT/Ticket_Overlay.pm:2611
-msgid "Either base or target must be specified"
-msgstr "Você deve especificar a origem ou o destinatário"
-
-#: html/Admin/Users/Modify.html:76 html/Ticket/Elements/AddWatchers:79 html/User/Prefs.html:67
-msgid "Email"
-msgstr "E-mail"
-
-#: lib/RT/User_Overlay.pm:237
-msgid "Email address in use"
-msgstr "O endereço de e-mail já está em uso"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailAddress"
-msgstr "Correio Eletrônico"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailEncoding"
-msgstr "CodificaçãoDeE-mail"
-
-#: html/Admin/CustomFields/Modify.html:100 html/Admin/Elements/EditCustomField:74
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "Ativo (Desmarcar esta caixa desativa este campo personalizado)"
-
-#: html/Admin/Groups/Modify.html:89 html/User/Groups/Modify.html:76
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "Ativo (Desmarcar esta caixa desativa este grupo)"
-
-#: html/Admin/Queues/Modify.html:107
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "Ativa (Desmarcar esta caixa desativa esta fila"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Custom Fields"
-msgstr "Campos Personalizados Habilitados"
-
-#: html/Admin/Queues/index.html:80
-msgid "Enabled Queues"
-msgstr "Filas Ativas"
-
-#: html/Admin/Elements/EditCustomField:138 html/Admin/Groups/Modify.html:156 html/Admin/Users/Modify.html:354 html/User/Groups/Modify.html:140
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr "Estado %1 ativado"
-
-#: html/Admin/CustomFields/Modify.html:187 html/Admin/Queues/Modify.html:164
-#. (loc_fuzzy($msg))
-msgid "Enabled status: %1"
-msgstr "Ativado estado: %1"
-
-#: lib/RT/CustomField_Overlay.pm:66
-msgid "Enter multiple values"
-msgstr "Informar valores múltiplos"
-
-#: html/Elements/EditLinks:127
-msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces."
-msgstr "Informar objetos ou URIs para vincular aos objetos. Separar entradas múltiplas com espaço."
-
-#: lib/RT/CustomField_Overlay.pm:67
-msgid "Enter one value"
-msgstr "Informar um valor"
-
-#: html/Elements/EditLinks:124
-msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces."
-msgstr "Informar filas ou URIs para ligar às filas. Separar entradas múltiplas com espaço."
-
-#: html/Elements/EditLinks:120 html/Search/Bulk.html:191
-msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces."
-msgstr "Informar identificadores de tíquete ou URIs que levam ao tíquete. Separar entradas mútiplas com espaço."
-
-#: lib/RT/CustomField_Overlay.pm:68
-msgid "Enter up to %1 values"
-msgstr "Informar até %1 valores"
-
-#: html/Elements/Login:78 html/SelfService/Error.html:48 html/SelfService/Error.html:49
-msgid "Error"
-msgstr "Erro"
-
-#: NOT FOUND IN SOURCE
-msgid "Error adding watcher"
-msgstr "Erro ao adicionar um observador"
-
-#: lib/RT/Queue_Overlay.pm:674
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr "Erro nos parâmetros para Queue->AddWatcher"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Queue->DelWatcher"
-msgstr "Erro nos parâmetros para Queue->DelWatcher"
-
-#: lib/RT/Queue_Overlay.pm:835
-msgid "Error in parameters to Queue->DeleteWatcher"
-msgstr "Erro em parâmetros para Fila->RemoverObservador"
-
-#: lib/RT/Ticket_Overlay.pm:1374
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr "Erro nos parâmetros para Ticket->AddWatcher"
-
-#: NOT FOUND IN SOURCE
-msgid "Error in parameters to Ticket->DelWatcher"
-msgstr "Erro nos parâmetros para Ticket->DelWatcher"
-
-#: lib/RT/Ticket_Overlay.pm:1540
-msgid "Error in parameters to Ticket->DeleteWatcher"
-msgstr "Erro em parâmetros para Tíquete->RemoverObservador"
-
-#: bin/rt-crontool:287
-msgid "Escalate tickets"
-msgstr "Escalonar tíquetes"
-
-#: html/Ticket/Elements/ShowBasics:59
-msgid "Estimated"
-msgstr "Estimado"
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "Todos"
-
-#: bin/rt-crontool:273
-msgid "Example:"
-msgstr "Exemplo:"
-
-#: html/Admin/Users/Modify.html:101
-msgid "Extra info"
-msgstr "Informação adicional"
-
-#: lib/RT/SavedSearch.pm:179
-msgid "Failed to create search attribute"
-msgstr "Falha ao criar atributo de busca"
-
-#: lib/RT/User_Overlay.pm:378
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr "Não foi possível encontrar o pseudogrupo de usuários 'Privileged'."
-
-#: lib/RT/User_Overlay.pm:385
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr "Não foi possível encontrar o pseudogrupo de usuários 'Unprivileged'"
-
-#: bin/rt-crontool:208
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr "Falha ao carregar o módulo %1. (%2)"
-
-#: lib/RT/SavedSearch.pm:154
-#. ($privacy)
-msgid "Failed to load object for %1"
-msgstr "Falha ao carregar objeto para %1"
-
-#: lib/RT/Date.pm:444
-msgid "Feb."
-msgstr "Fev."
-
-#: NOT FOUND IN SOURCE
-msgid "February"
-msgstr "Fevereiro"
-
-#: html/Elements/SelectAttachmentField:52
-msgid "Filename"
-msgstr "Nome de arquivo"
-
-#: lib/RT/CustomField_Overlay.pm:71
-msgid "Fill in multiple text areas"
-msgstr "Preencher múltiplas áreas de texto"
-
-#: lib/RT/CustomField_Overlay.pm:76
-msgid "Fill in multiple wikitext areas"
-msgstr "Preencher múltiplas áreas de texto wiki"
-
-#: lib/RT/CustomField_Overlay.pm:72
-msgid "Fill in one text area"
-msgstr "Preencher uma única área de texto"
-
-#: lib/RT/CustomField_Overlay.pm:77
-msgid "Fill in one wikitext area"
-msgstr "Preencher uma única [area de texto wiki"
-
-#: html/Admin/CustomFields/Modify.html:109 html/Admin/CustomFields/Modify.html:120
-msgid "Fill in this field with a URL."
-msgstr "Preencher este campo com uma URL."
-
-#: lib/RT/CustomField_Overlay.pm:73
-msgid "Fill in up to %1 text areas"
-msgstr "Preencher até %1 áreas de texto"
-
-#: lib/RT/CustomField_Overlay.pm:78
-msgid "Fill in up to %1 wikitext areas"
-msgstr "Preencher at[e %1 áreas de texto wiki"
-
-#: html/Search/Elements/PickBasics:151 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:99 lib/RT/Tickets_Overlay.pm:1957
-msgid "Final Priority"
-msgstr "Prioridade Final"
-
-#: lib/RT/Ticket_Overlay.pm:1166
-msgid "FinalPriority"
-msgstr "PrioridadeFinal"
-
-#: NOT FOUND IN SOURCE
-msgid "Find group whose"
-msgstr "Encontrar grupo que"
-
-#: html/Admin/Groups/index.html:74 html/Admin/Queues/People.html:84 html/Ticket/Elements/EditPeople:57
-msgid "Find groups whose"
-msgstr "Encontrar grupos que"
-
-#: NOT FOUND IN SOURCE
-msgid "Find new/open tickets"
-msgstr "Encontrar tíquetes novos/abertos"
-
-#: html/Admin/Queues/People.html:80 html/Admin/Users/index.html:72 html/Ticket/Elements/EditPeople:53
-msgid "Find people whose"
-msgstr "Encontrar pessoas que"
-
-#: html/Search/Results.html:149
-msgid "Find tickets"
-msgstr "Encontrar tíquetes"
-
-#: NOT FOUND IN SOURCE
-msgid "Finish Approval"
-msgstr "Terminar Aprovação"
-
-#: html/Ticket/Elements/Tabs:83
-msgid "First"
-msgstr "Primeiro"
-
-#: NOT FOUND IN SOURCE
-msgid "First page"
-msgstr "Primeira página"
-
-#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:764
-msgid "Foo Bar Baz"
-msgstr ""
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:755
-msgid "Foo!"
-msgstr ""
-
-#: html/Search/Bulk.html:85
-msgid "Force change"
-msgstr "Forçar alteração"
-
-#: html/Search/Elements/EditFormat:54
-msgid "Format"
-msgstr "Formato"
-
-#: html/Search/Results.html:147
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr "Encontrado(s) %quant(%1,tíquete(s)"
-
-#: lib/RT/Record.pm:973
-msgid "Found Object"
-msgstr "Objeto Encontrado"
-
-#: lib/RT/Date.pm:423
-msgid "Fri."
-msgstr "Sex."
-
-#: html/Ticket/Elements/ShowHistory:68 html/Ticket/Elements/ShowHistory:74
-msgid "Full headers"
-msgstr "Cabeçalhos completos"
-
-#: html/Tools/Offline.html:87
-msgid "Get template from file"
-msgstr "Pegar modelo do arquivo"
-
-#: 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:705
-#. ($New->Name)
-msgid "Given to %1"
-msgstr "Dado a %1"
-
-#: html/Admin/Elements/Tabs:67 html/Admin/index.html:84
-msgid "Global"
-msgstr ""
-
-#: html/Admin/Elements/EditCustomFields:57
-msgid "Global Custom Fields"
-msgstr "Campos Personalizados Globais"
-
-#: 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/Global/CustomFields/index.html:61
-msgid "Global custom field configuration"
-msgstr "Configuração de campos personalizados globais"
-
-#: html/Admin/Global/MyRT.html:95
-#. ($pane)
-msgid "Global portlet %1 saved."
-msgstr "Portlet global %1 salvo."
-
-#: html/Admin/Elements/SelectTemplate:61
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr "Modelo global: %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Go"
-msgstr "Ir"
-
-#: html/Admin/CustomFields/index.html:82 html/Admin/Groups/index.html:69 html/Admin/Groups/index.html:75 html/Admin/Queues/People.html:82 html/Admin/Queues/People.html:86 html/Admin/Queues/index.html:68 html/Admin/Users/index.html:75 html/Approvals/index.html:54 html/Elements/RefreshHomepage:50 html/Search/Results.html:76 html/Search/Results.html:92 html/Ticket/Elements/EditPeople:55 html/Ticket/Elements/EditPeople:59 html/Tools/Offline.html:91
-msgid "Go!"
-msgstr "Ir!"
-
-#: NOT FOUND IN SOURCE
-msgid "Good pgp sig from %1\\n"
-msgstr "Assinatura pgp válida de %1\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Goto page"
-msgstr "Ir para a página"
-
-#: html/Elements/GotoTicket:48 html/SelfService/Elements/GotoTicket:48
-msgid "Goto ticket"
-msgstr "Ir para o tíquete"
-
-#: html/Ticket/Elements/AddWatchers:69 html/Ticket/Elements/ShowGroupMembers:57 html/User/Elements/DelegateRights:101
-msgid "Group"
-msgstr "Grupo"
-
-#: NOT FOUND IN SOURCE
-msgid "Group %1 %2: %3"
-msgstr "Grupo %1 %2: %3"
-
-#: html/Admin/Elements/CustomFieldTabs:70 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:84 html/Admin/Elements/SystemTabs:67 html/Admin/Global/index.html:78
-msgid "Group Rights"
-msgstr "Direitos de Acesso do Grupo"
-
-#: lib/RT/Group_Overlay.pm:985
-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:115
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "O grupo não pôde ser criado: %1"
-
-#: lib/RT/Group_Overlay.pm:523
-msgid "Group created"
-msgstr "Grupo criado"
-
-#: lib/RT/Group_Overlay.pm:1157
-msgid "Group has no such member"
-msgstr "O grupo não contém este membro"
-
-#: lib/RT/Group_Overlay.pm:965 lib/RT/Queue_Overlay.pm:750 lib/RT/Queue_Overlay.pm:810 lib/RT/Ticket_Overlay.pm:1432 lib/RT/Ticket_Overlay.pm:1512
-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/GlobalCustomFieldTabs:61 html/Admin/Elements/SelectNewGroupMembers:59 html/Admin/Elements/Tabs:58 html/Admin/Global/CustomFields/index.html:71 html/Admin/Groups/Members.html:88 html/Admin/Queues/People.html:106 html/Admin/Users/Memberships.html:55 html/Admin/index.html:69 html/User/Groups/Members.html:90 lib/RT/CustomField_Overlay.pm:1213
-msgid "Groups"
-msgstr "Grupos"
-
-#: lib/RT/Group_Overlay.pm:991
-msgid "Groups can't be members of their members"
-msgstr "Grupos não podem ser membros de seus próprios membros"
-
-#: html/Admin/Groups/index.html:88
-msgid "Groups matching search criteria"
-msgstr "Grupos que satisfazem ao critério de busca"
-
-#: html/Ticket/Elements/ShowRequestor:79
-msgid "Groups this user belongs to"
-msgstr "Grupos a que este usuário pertence"
-
-#: lib/RT/Interface/CLI.pm:96 lib/RT/Interface/CLI.pm:96
-msgid "Hello!"
-msgstr "Olá!"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:771
-#. ($name)
-msgid "Hello, %1"
-msgstr "Olá, %1"
-
-#: html/Admin/Elements/GroupTabs:72 html/Admin/Elements/UserTabs:66 html/Ticket/Elements/ShowHistory:55 html/Ticket/Elements/Tabs:113
-msgid "History"
-msgstr "Histórico"
-
-#: html/Admin/Groups/History.html:64
-#. ($GroupObj->Name)
-msgid "History of the group %1"
-msgstr "Histórico do grupo %1"
-
-#: html/Admin/Users/History.html:64
-#. ($UserObj->Name)
-msgid "History of the user %1"
-msgstr "Histórico do usuário %1"
-
-#: NOT FOUND IN SOURCE
-msgid "HomePhone"
-msgstr "Telefone Residencial"
-
-#: html/Elements/Tabs:68
-msgid "Homepage"
-msgstr "Início"
-
-#: html/Elements/SelectTimeUnits:50
-msgid "Hours"
-msgstr "Horas"
-
-#: lib/RT/Base.pm:135
-#. (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/Search/Build.html:466 lib/RT/Report/Tickets.pm:417
-msgid "I'm lost"
-msgstr "Estou perdido"
-
-#: html/Ticket/Elements/ShowBasics:50 lib/RT/Tickets_Overlay.pm:1882
-msgid "Id"
-msgstr "Identificador"
-
-#: html/Admin/Users/Modify.html:67 html/User/Prefs.html:62
-msgid "Identity"
-msgstr "Identidade"
-
-#: etc/initialdata:429
-msgid "If an approval is rejected, reject the original and delete pending approvals"
-msgstr ""
-"Se uma aprovação não é concedida, rejeitar o original e remover aprovações "
-"pendentes"
-
-#: html/Tools/Offline.html:76
-msgid "If no Requestor is specified, create tickets with this requestor."
-msgstr ""
-"Se nenhum Requisitante for especificado, criar tíquetes com este "
-"requisitante."
-
-#: html/Tools/Offline.html:67
-msgid "If no queue is specified, create tickets in this queue."
-msgstr "Se nenhuma fila for especificada, criar tíquetes nesta fila."
-
-#: bin/rt-crontool:269
-msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
-msgstr "Se esta ferramenta estiver com setgid, um usuário local mal-intecionado pode conseguir acesso administrativo sobre o RT."
-
-#: html/Admin/Queues/People.html:128 html/Ticket/Modify.html:62 html/Ticket/ModifyAll.html:130 html/Ticket/ModifyPeople.html:62
-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/Record.pm:964
-msgid "Illegal value for %1"
-msgstr "Valor ilegal para %1"
-
-#: lib/RT/Record.pm:967
-msgid "Immutable field"
-msgstr "Campo imutável"
-
-#: NOT FOUND IN SOURCE
-msgid "Include disabled custom fields in listing."
-msgstr "Incluir campoas personalizados desabilitados na listagem."
-
-#: html/Admin/Groups/index.html:67
-msgid "Include disabled groups in listing."
-msgstr "Incluir grupos inativos na listagem."
-
-#: html/Admin/Queues/index.html:67
-msgid "Include disabled queues in listing."
-msgstr "Incluir filas inativas na listagem."
-
-#: html/Admin/Users/index.html:73
-msgid "Include disabled users in search."
-msgstr "Incluir usuários inativos na busca."
-
-#: html/Admin/CustomFields/Modify.html:115
-msgid "Include page"
-msgstr "Incluir página"
-
-#: html/Search/Build.html:492 lib/RT/Report/Tickets.pm:443
-msgid "Incomplete Query"
-msgstr "Consulta Incompleta"
-
-#: html/Search/Build.html:489 lib/RT/Report/Tickets.pm:440
-msgid "Incomplete query"
-msgstr "Consulta incompleta"
-
-#: html/Search/Elements/PickBasics:150 lib/RT/Tickets_Overlay.pm:1932
-msgid "Initial Priority"
-msgstr "Prioridade Inicial"
-
-#: lib/RT/Ticket_Overlay.pm:1165 lib/RT/Ticket_Overlay.pm:1167
-msgid "InitialPriority"
-msgstr "InitialPriority"
-
-#: lib/RT/ScripAction_Overlay.pm:135
-msgid "Input error"
-msgstr "Erro de entrada"
-
-#: html/Elements/ValidateCustomFields:70 lib/RT/CustomField_Overlay.pm:1024 lib/RT/CustomField_Overlay.pm:1165
-#. ($CF->FriendlyPattern)
-#. ($self->FriendlyPattern)
-msgid "Input must match %1"
-msgstr "Entrada precisa satisfazer %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Interest noted"
-msgstr "Interesse notado"
-
-#: lib/RT/Ticket_Overlay.pm:3531
-msgid "Internal Error"
-msgstr "Erro Interno"
-
-#: lib/RT/Record.pm:315
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "Erro Interno: %1"
-
-#: lib/RT/Group_Overlay.pm:670
-msgid "Invalid Group Type"
-msgstr "Tipo Inválido de Grupo"
-
-#: lib/RT/Principal_Overlay.pm:163
-msgid "Invalid Right"
-msgstr "Direito Inválido"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid Type"
-msgstr "Tipo Inválido"
-
-#: lib/RT/Record.pm:969
-msgid "Invalid data"
-msgstr "Dado inválido"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid owner. Defaulting to 'nobody'."
-msgstr "Proprietário inválido. Usando 'nobody'."
-
-#: lib/RT/CustomField_Overlay.pm:210 lib/RT/CustomField_Overlay.pm:681
-#. ($msg)
-msgid "Invalid pattern: %1"
-msgstr "Padrão inválido: %1"
-
-#: lib/RT/Scrip_Overlay.pm:159 lib/RT/Template_Overlay.pm:246
-msgid "Invalid queue"
-msgstr "Fila inválida"
-
-#: lib/RT/ACE_Overlay.pm:266 lib/RT/ACE_Overlay.pm:275 lib/RT/ACE_Overlay.pm:281 lib/RT/ACE_Overlay.pm:292
-msgid "Invalid right"
-msgstr "Direito de acesso inválido"
-
-#: lib/RT/Record.pm:290
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "Valor inválido para %1"
-
-#: lib/RT/Record.pm:1632
-msgid "Invalid value for custom field"
-msgstr "Valor inválido para campo personalizado"
-
-#: lib/RT/Ticket_Overlay.pm:426
-msgid "Invalid value for status"
-msgstr "Valor inválido para estado"
-
-#: bin/rt-crontool:270
-msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
-msgstr "É muito importante que usuários não privilegiados não tenham permissão para utilizar esta ferramenta."
-
-#: bin/rt-crontool:271
-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 a criação de um usuário Unix não privilegiado com a correta filiação a grupo e com acesso ao RT para executar utilizar esta ferramenta."
-
-#: bin/rt-crontool:233
-msgid "It takes several arguments:"
-msgstr "Requer vários argumentos:"
-
-#: html/Search/Elements/EditFormat:87
-msgid "Italic"
-msgstr "Itálico"
-
-#: NOT FOUND IN SOURCE
-msgid "Items pending my approval"
-msgstr "Itens requerendo minha aprovação"
-
-#: lib/RT/Date.pm:443
-msgid "Jan."
-msgstr "Jan."
-
-#: NOT FOUND IN SOURCE
-msgid "January"
-msgstr "Janeiro"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "Join or leave this group"
-msgstr "Entre ou deixe este grupo"
-
-#: lib/RT/Date.pm:449
-msgid "Jul."
-msgstr "Jul."
-
-#: NOT FOUND IN SOURCE
-msgid "July"
-msgstr "Julho"
-
-#: html/Ticket/Elements/Tabs:127
-msgid "Jumbo"
-msgstr "Jumbo"
-
-#: lib/RT/Date.pm:448
-msgid "Jun."
-msgstr "Jun."
-
-#: NOT FOUND IN SOURCE
-msgid "June"
-msgstr "Junho"
-
-#: NOT FOUND IN SOURCE
-msgid "Keyword"
-msgstr "Palavra chave"
-
-#: NOT FOUND IN SOURCE
-msgid "Lang"
-msgstr "Líng"
-
-#: html/Admin/Users/Modify.html:96 html/User/Prefs.html:78
-msgid "Language"
-msgstr "Língua"
-
-#: html/Search/Elements/EditFormat:81
-msgid "Large"
-msgstr "Grande"
-
-#: html/Ticket/Elements/Tabs:98
-msgid "Last"
-msgstr "Último"
-
-#: html/Ticket/Elements/EditDates:61 html/Ticket/Elements/ShowDates:62
-msgid "Last Contact"
-msgstr "Último Contato"
-
-#: html/Elements/SelectDateType:52
-msgid "Last Contacted"
-msgstr "Contactado em"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Notified"
-msgstr "Notificado em"
-
-#: html/Elements/SelectDateType:53
-msgid "Last Updated"
-msgstr "Atualizado em"
-
-#: NOT FOUND IN SOURCE
-msgid "LastUpdated"
-msgstr "LastUpdated"
-
-#: html/Search/Elements/PickBasics:105
-msgid "LastUpdatedBy"
-msgstr "UltimaAtualizacaoPor"
-
-#: html/Ticket/Elements/ShowBasics:70
-msgid "Left"
-msgstr "Resta"
-
-#: html/Admin/Users/Modify.html:111
-msgid "Let this user access RT"
-msgstr "Deixar este usuário acessar RT"
-
-#: html/Admin/Users/Modify.html:115
-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"
-
-#: html/Search/Elements/EditFormat:70
-msgid "Link"
-msgstr "Vínculo"
-
-#: lib/RT/Record.pm:1328
-msgid "Link already exists"
-msgstr "O vínculo já existe"
-
-#: lib/RT/Record.pm:1342
-msgid "Link could not be created"
-msgstr "O vínculo não pôde ser criado"
-
-#: lib/RT/Record.pm:1348
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "Vínculo criado (%1)"
-
-#: lib/RT/Record.pm:1409
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "Vínculo removido (%1)"
-
-#: lib/RT/Record.pm:1415
-msgid "Link not found"
-msgstr "Vínculo não encontrado"
-
-#: html/Ticket/ModifyLinks.html:48 html/Ticket/ModifyLinks.html:52
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr "Vincular o tíquete #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Link ticket %1"
-msgstr "Vincular o tíquete %1"
-
-#: html/Admin/CustomFields/Modify.html:104
-msgid "Link values to"
-msgstr "Vincular valores a"
-
-#: lib/RT/Ticket_Overlay.pm:702
-msgid "Linking. Permission denied"
-msgstr "Vinculando. Permissão negada"
-
-#: html/Ticket/Create.html:218 html/Ticket/Elements/ShowSummary:97 html/Ticket/Elements/Tabs:122 html/Ticket/ModifyAll.html:80
-msgid "Links"
-msgstr "Vínculos"
-
-#: html/Search/Elements/EditSearches:77
-msgid "Load"
-msgstr "Carregar"
-
-#: html/Search/Elements/EditSearches:75
-msgid "Load saved search:"
-msgstr "Carregar buscas salvas:"
-
-#: lib/RT/System.pm:88
-msgid "LoadSavedSearch"
-msgstr "CarregarBuscaSalva"
-
-#: html/Admin/Tools/Configuration.html:66
-msgid "Loaded perl modules"
-msgstr "Módulos perl carregados"
-
-#: lib/RT/SavedSearch.pm:113
-#. ($self->Name)
-msgid "Loaded search %1"
-msgstr "Busca %1 carregada"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:128
-msgid "Location"
-msgstr "Localização"
-
-#: NOT FOUND IN SOURCE
-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:93
-#. ("<span>".$session{'CurrentUser'}->Name."</span>")
-msgid "Logged in as %1"
-msgstr "Assinado como %1"
-
-#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:102 html/Elements/Login:70 html/Elements/Login:86 lib/RT/StyleGuide.pod:795
-msgid "Login"
-msgstr "Entrar"
-
-#: html/Elements/Header:103
-msgid "Logout"
-msgstr "Sair"
-
-#: lib/RT/CustomField_Overlay.pm:935
-msgid "Lookup type mismatch"
-msgstr "Tipo de consulta não corresponde"
-
-#: html/Search/Bulk.html:84
-msgid "Make Owner"
-msgstr "Definir como proprietário"
-
-#: html/Search/Bulk.html:108
-msgid "Make Status"
-msgstr "Definir o estado"
-
-#: html/Search/Bulk.html:116
-msgid "Make date Due"
-msgstr "Definir o prazo final"
-
-#: html/Search/Bulk.html:118
-msgid "Make date Resolved"
-msgstr "Definir a data de resolução"
-
-#: html/Search/Bulk.html:112
-msgid "Make date Started"
-msgstr "Definir a data de iniciado"
-
-#: html/Search/Bulk.html:110
-msgid "Make date Starts"
-msgstr "Definir a data início"
-
-#: html/Search/Bulk.html:114
-msgid "Make date Told"
-msgstr "Definir a data de última alteração"
-
-#: html/Search/Bulk.html:104
-msgid "Make priority"
-msgstr "Definir a prioridade"
-
-#: html/Search/Bulk.html:106
-msgid "Make queue"
-msgstr "Definir a fila"
-
-#: html/Search/Bulk.html:102
-msgid "Make subject"
-msgstr "Definir o assunto"
-
-#: lib/RT/Group_Overlay.pm:171
-msgid "Make this group visible to user"
-msgstr "Fazer este grupo visível para o usuário"
-
-#: html/Admin/index.html:80
-msgid "Manage custom fields and custom field values"
-msgstr "Gerenciar campos personalizados e valores de campos personalizados"
-
-#: html/Admin/index.html:71
-msgid "Manage groups and group membership"
-msgstr "Administrar grupos e afiliações"
-
-#: html/Admin/index.html:87
-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:76
-msgid "Manage queues and queue-specific properties"
-msgstr "Administrar filas e suas propriedades específicas"
-
-#: html/Admin/index.html:66
-msgid "Manage users and passwords"
-msgstr "Administrar usuários e senhas"
-
-#: lib/RT/Date.pm:445
-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:447
-msgid "May."
-msgstr "Mai."
-
-#: lib/RT/Transaction_Overlay.pm:752
-#. ($value)
-msgid "Member %1 added"
-msgstr "Membro %1 adicionado"
-
-#: lib/RT/Transaction_Overlay.pm:792
-#. ($value)
-msgid "Member %1 deleted"
-msgstr "Membro %1 removido"
-
-#: lib/RT/Group_Overlay.pm:1002
-msgid "Member added"
-msgstr "Membro adicionado"
-
-#: lib/RT/Group_Overlay.pm:1164
-msgid "Member deleted"
-msgstr "Membro removido"
-
-#: lib/RT/Group_Overlay.pm:1168
-msgid "Member not deleted"
-msgstr "Membro não removido"
-
-#: html/Elements/SelectLinkType:49
-msgid "Member of"
-msgstr "Membro de"
-
-#: NOT FOUND IN SOURCE
-msgid "MemberOf"
-msgstr "MembroDe"
-
-#: html/Admin/Elements/GroupTabs:65 html/User/Elements/GroupTabs:65
-msgid "Members"
-msgstr "Membros"
-
-#: lib/RT/Transaction_Overlay.pm:749
-#. ($value)
-msgid "Membership in %1 added"
-msgstr "Filiação em %1 adicionada"
-
-#: lib/RT/Transaction_Overlay.pm:789
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr "Filiação em %1 removida"
-
-#: html/Admin/Elements/UserTabs:63
-msgid "Memberships"
-msgstr "Filiações"
-
-#: html/Admin/Users/Memberships.html:62
-#. ($UserObj->Name)
-msgid "Memberships of the user %1"
-msgstr "Filiações do usuário %1"
-
-#: lib/RT/Ticket_Overlay.pm:2905
-msgid "Merge Successful"
-msgstr "União bem sucedida"
-
-#: lib/RT/Ticket_Overlay.pm:2783
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr "União falhou. Não foi possível definir o EffectiveId"
-
-#: lib/RT/Ticket_Overlay.pm:2800
-msgid "Merge failed. Couldn't set Status"
-msgstr "Fusão falhou. Não foi possível definir Estado."
-
-#: html/Elements/EditLinks:132 html/Ticket/Elements/BulkLinks:50
-msgid "Merge into"
-msgstr "Unir a"
-
-#: lib/RT/Transaction_Overlay.pm:755
-#. ($value)
-msgid "Merged into %1"
-msgstr "Unido ao %1"
-
-#: html/Search/Bulk.html:145 html/Ticket/Update.html:120
-msgid "Message"
-msgstr "Mensagem"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:166
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr "Corpo da mensagem não mostrado porque é muito grande ou náo é um texto plano."
-
-#: lib/RT/Ticket_Overlay.pm:2454
-msgid "Message could not be recorded"
-msgstr "Mensagem não pode ser gravada"
-
-#: lib/RT/Ticket_Overlay.pm:2457
-msgid "Message recorded"
-msgstr "Mensagem gravada"
-
-#: html/Ticket/Elements/PreviewScrips:124
-msgid "Messages about this ticket will not be sent to..."
-msgstr "Mensagens sobre este tíquete não serão enviadas para..."
-
-#: html/Elements/SelectTimeUnits:49
-msgid "Minutes"
-msgstr "Minutos"
-
-#: html/Search/Build.html:496 lib/RT/Report/Tickets.pm:447
-msgid "Mismatched parentheses"
-msgstr "Parênteses sem correspondente"
-
-#: lib/RT/Record.pm:971
-msgid "Missing a primary key?: %1"
-msgstr "Faltando uma chave primária?: %1"
-
-#: html/Admin/Users/Modify.html:196 html/User/Prefs.html:94
-msgid "Mobile"
-msgstr "Móvel"
-
-#: NOT FOUND IN SOURCE
-msgid "MobilePhone"
-msgstr "Celular"
-
-#: lib/RT/Queue_Overlay.pm:96
-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/Elements/ObjectCustomFields:98
-#. (loc(lc($FriendlySubTypes)), loc(lc($Types)))
-msgid "Modify Custom Fields which apply to %1 for all %2"
-msgstr "Modificar Campos Personalizados que se aplicam a %1 para todos %2"
-
-#: html/Admin/Elements/ObjectCustomFields:100
-#. (loc(lc($Types)))
-msgid "Modify Custom Fields which apply to all %1"
-msgstr "Modificar Campos Personalizados que se aplicam a todos %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Custom Fields which apply to all queues"
-msgstr "Modificar Campos Personalizados que se aplicam a todas as filas"
-
-#: html/Admin/Global/GroupRights.html:108 html/Admin/Groups/GroupRights.html:96 html/Admin/Queues/GroupRights.html:109
-msgid "Modify Group Rights"
-msgstr "Modificar Direitos de Grupo"
-
-#: html/Admin/Groups/Members.html:107 html/User/Groups/Members.html:103
-msgid "Modify Members"
-msgstr "Modificar Membros"
-
-#: html/User/Delegation.html:60
-msgid "Modify Rights"
-msgstr "Modificar Direitos"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "Modify Scrip templates for this queue"
-msgstr "Modificar modelos de Scrip desta fila"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "Modify Scrips for this queue"
-msgstr "Modificar Scrips desta 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/Global/UserRights.html:77 html/Admin/Groups/UserRights.html:78 html/Admin/Queues/UserRights.html:77
-msgid "Modify User Rights"
-msgstr "Modificar Direitos de Usuário"
-
-#: html/Admin/Queues/CustomField.html:68
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr "Modificar um Campo Personalizado para a fila %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify a CustomField that applies to all queues"
-msgstr "Modificar um Campo Personalizado que se aplica a todas as filas"
-
-#: html/Admin/Queues/Scrip.html:84
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "Modificar um scrip para a fila %1"
-
-#: html/Admin/Global/Scrip.html:77
-msgid "Modify a scrip that applies to all queues"
-msgstr "Modificar um scrip aplicável a todas as filas"
-
-#: html/Admin/CustomFields/Objects.html:92
-#. ($CF->Name)
-msgid "Modify associated objects for %1"
-msgstr "Modificar objetos associados a %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify dates for # %1"
-msgstr "Modificar datas para # %1"
-
-#: html/Ticket/ModifyDates.html:48 html/Ticket/ModifyDates.html:52
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "Modificar as datas para #%1"
-
-#: html/Ticket/ModifyDates.html:59
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "Modificar as datas para o tíquete # %1"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:67 html/Admin/Global/index.html:74
-msgid "Modify global custom fields"
-msgstr "Modificar campos personalizados globais"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:72 html/Admin/Global/GroupRights.html:48 html/Admin/Global/GroupRights.html:51 html/Admin/Global/index.html:79
-msgid "Modify global group rights"
-msgstr "Modificar direitos de acesso globais de grupo"
-
-#: html/Admin/Global/GroupRights.html:56
-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:48 html/Admin/Global/UserRights.html:51 html/Admin/Global/index.html:83
-msgid "Modify global user rights"
-msgstr "Modificar direitos de acesso globais de usuário"
-
-#: html/Admin/Global/UserRights.html:56
-msgid "Modify global user rights."
-msgstr "Modificar direitos de acesso globais de usuário."
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "Modify group metadata or delete group"
-msgstr "Modificar metadados do grupo ou removê-lo"
-
-#: html/Admin/CustomFields/GroupRights.html:166
-#. ($CustomFieldObj->Name)
-msgid "Modify group rights for custom field %1"
-msgstr "Modificar direitos de grupo para campo personalizado %1"
-
-#: html/Admin/Groups/GroupRights.html:48 html/Admin/Groups/GroupRights.html:52 html/Admin/Groups/GroupRights.html:58
-#. ($GroupObj->Name)
-msgid "Modify group rights for group %1"
-msgstr "Modificar os direitos de acesso do grupo %1"
-
-#: html/Admin/Queues/GroupRights.html:48 html/Admin/Queues/GroupRights.html:52
-#. ($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:167
-msgid "Modify membership roster for this group"
-msgstr "Modificar afiliados deste grupo"
-
-#: lib/RT/System.pm:84
-msgid "Modify one's own RT account"
-msgstr "Modificar sua própria conta RT"
-
-#: html/Admin/Queues/People.html:48 html/Admin/Queues/People.html:52
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "Modificar as pessoas relacionadas à fila %1"
-
-#: html/Ticket/ModifyPeople.html:48 html/Ticket/ModifyPeople.html:52 html/Ticket/ModifyPeople.html:59
-#. ($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:69
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "Modificar os scrips da fila %1"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:58 html/Admin/Global/Scrips.html:67 html/Admin/Global/index.html:65
-msgid "Modify scrips which apply to all queues"
-msgstr "Modificar scrips aplicáveis a todas as filas"
-
-#: html/Admin/Global/Template.html:104 html/Admin/Global/Template.html:48 html/Admin/Global/Template.html:53 html/Admin/Queues/Template.html:101
-#. (loc($TemplateObj->Name()))
-#. ($TemplateObj->id)
-msgid "Modify template %1"
-msgstr "Modificar o modelo %1"
-
-#: html/Admin/Global/Templates.html:67
-msgid "Modify templates which apply to all queues"
-msgstr "Modificar modelos que se aplicam a todas as filas"
-
-#: html/Admin/Global/index.html:87
-msgid "Modify the default \"RT at a glance\" view"
-msgstr "Modificar \"RT por alto\" default"
-
-#: html/Admin/Groups/Modify.html:125 html/User/Groups/Modify.html:109
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "Modificar o grupo %1"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "Modify the queue watchers"
-msgstr "Modificar os observadores da fila"
-
-#: html/Admin/Users/Modify.html:313
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "Modificar o usuário %1"
-
-#: html/Ticket/ModifyAll.html:60
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "Modificar o tíquete # %1"
-
-#: html/Ticket/Modify.html:48 html/Ticket/Modify.html:51 html/Ticket/Modify.html:57
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "Modificar o tíquete #%1"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "Modify tickets"
-msgstr "Modificar tíquetes"
-
-#: html/Admin/CustomFields/UserRights.html:159
-#. ($CustomFieldObj->Name)
-msgid "Modify user rights for custom field %1"
-msgstr "Modificar direitos de usuário para campo customizado %1"
-
-#: html/Admin/Groups/UserRights.html:48 html/Admin/Groups/UserRights.html:52 html/Admin/Groups/UserRights.html:58
-#. ($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:48 html/Admin/Queues/UserRights.html:52
-#. ($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:96
-msgid "ModifyACL"
-msgstr "ModificarACL"
-
-#: lib/RT/CustomField_Overlay.pm:110
-msgid "ModifyCustomField"
-msgstr "ModificarCampoPersonalizado"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "ModifyOwnMembership"
-msgstr "ModificarFiliaçãoPrópria"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "ModifyQueueWatchers"
-msgstr "ModificarObservadoresdaFila"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "ModifyScrips"
-msgstr "ModificarScrips"
-
-#: lib/RT/System.pm:84
-msgid "ModifySelf"
-msgstr "AutoModificar-se"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "ModifyTemplate"
-msgstr "ModificarModelo"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "ModifyTicket"
-msgstr "ModificarTiquete"
-
-#: lib/RT/Date.pm:419
-msgid "Mon."
-msgstr "Seg."
-
-#: html/Ticket/Elements/ShowRequestor:63
-#. ($name)
-msgid "More about %1"
-msgstr "Mais sobre %1"
-
-#: html/Admin/Elements/PickCustomFields:85
-msgid "Move down"
-msgstr "Descer"
-
-#: html/Admin/Elements/PickCustomFields:77
-msgid "Move up"
-msgstr "Subir"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:50
-msgid "Multiple"
-msgstr "Múltiplo"
-
-#: lib/RT/User_Overlay.pm:228
-msgid "Must specify 'Name' attribute"
-msgstr "Necessário especificar atributo 'Nome'"
-
-#: html/SelfService/Elements/MyRequests:79
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "Meus %1 primeiros tíquetes"
-
-#: NOT FOUND IN SOURCE
-msgid "My Approvals"
-msgstr "Minhas Aprovações"
-
-#: html/Tools/Elements/Tabs:65
-msgid "My Day"
-msgstr "Meu Dia"
-
-#: html/Approvals/index.html:48 html/Approvals/index.html:49
-msgid "My approvals"
-msgstr "Minhas aprovações"
-
-#: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:56
-msgid "My saved searches"
-msgstr "Minhas buscas salvas"
-
-#: html/Admin/CustomFields/Modify.html:60 html/Admin/Elements/AddCustomFieldValue:55 html/Admin/Elements/EditCustomField:57 html/Admin/Elements/EditCustomFieldValues:57 html/Admin/Elements/ModifyTemplate:51 html/Admin/Groups/Modify.html:67 html/Search/Bulk.html:159 html/User/Groups/Modify.html:67
-msgid "Name"
-msgstr "Nome"
-
-#: lib/RT/User_Overlay.pm:235
-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:75
-msgid "Never"
-msgstr "Nunca"
-
-#: NOT FOUND IN SOURCE
-msgid "New"
-msgstr "Novo"
-
-#: html/Elements/EditLinks:118
-msgid "New Links"
-msgstr "Novos Vínculos"
-
-#: html/Admin/Users/Modify.html:121 html/User/Prefs.html:111
-msgid "New Password"
-msgstr "Nova Senha"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "Nova Aprovação Pendente"
-
-#: html/Ticket/Elements/Tabs:214
-msgid "New Search"
-msgstr "Nova busca"
-
-#: html/Admin/Elements/CustomFieldTabs:95 html/Admin/Queues/CustomField.html:75
-msgid "New custom field"
-msgstr "Novo campo personalizado"
-
-#: html/Admin/Elements/GroupTabs:79 html/User/Elements/GroupTabs:75
-msgid "New group"
-msgstr "Novo grupo"
-
-#: html/SelfService/Prefs.html:55
-msgid "New password"
-msgstr "Nova senha"
-
-#: lib/RT/User_Overlay.pm:818
-msgid "New password notification sent"
-msgstr "Notificação de nova senha enviada"
-
-#: html/Admin/Elements/QueueTabs:97
-msgid "New queue"
-msgstr "Nova fila"
-
-#: html/Ticket/Elements/Reminders:120
-msgid "New reminder:"
-msgstr "Novo lembrete:"
-
-#: NOT FOUND IN SOURCE
-msgid "New request"
-msgstr "Nova requisição"
-
-#: html/Admin/Elements/SelectRights:67
-msgid "New rights"
-msgstr "Novos direitos de acesso"
-
-#: html/Admin/Global/Scrip.html:65 html/Admin/Global/Scrips.html:62 html/Admin/Queues/Scrip.html:73 html/Admin/Queues/Scrips.html:78
-msgid "New scrip"
-msgstr "Novo scrip"
-
-#: NOT FOUND IN SOURCE
-msgid "New search"
-msgstr "Nova busca"
-
-#: html/Admin/Global/Template.html:83 html/Admin/Global/Templates.html:62 html/Admin/Queues/Template.html:81 html/Admin/Queues/Templates.html:73
-msgid "New template"
-msgstr "Novo modelo"
-
-#: html/SelfService/Elements/Tabs:87 html/SelfService/Elements/Tabs:91
-msgid "New ticket"
-msgstr "Novo tíquete"
-
-#: lib/RT/Ticket_Overlay.pm:2760
-msgid "New ticket doesn't exist"
-msgstr "O novo tíquete não existe"
-
-#: html/Admin/Elements/UserTabs:83
-msgid "New user"
-msgstr "Novo usuário"
-
-#: html/Admin/Elements/CreateUserCalled:49
-msgid "New user called"
-msgstr "Novo usuário chamado"
-
-#: html/Admin/Queues/People.html:78 html/Ticket/Elements/EditPeople:52
-msgid "New watchers"
-msgstr "Novos observadores"
-
-#: NOT FOUND IN SOURCE
-msgid "New window setting"
-msgstr "Abrir nova janela"
-
-#: html/Helpers/CalPopup.html:60 html/Ticket/Elements/Tabs:94
-msgid "Next"
-msgstr "Próximo"
-
-#: html/Elements/TicketList:108
-msgid "Next Page"
-msgstr "Próxima Página"
-
-#: NOT FOUND IN SOURCE
-msgid "Next page"
-msgstr "Próxima página"
-
-#: NOT FOUND IN SOURCE
-msgid "NickName"
-msgstr "Apelido"
-
-#: html/Admin/Users/Modify.html:86 html/User/Prefs.html:74
-msgid "Nickname"
-msgstr "Apelido"
-
-#: html/Admin/CustomFields/UserRights.html:147
-msgid "No Class defined"
-msgstr "Nenhuma Classe definida"
-
-#: html/Admin/CustomFields/Modify.html:168 html/Admin/Elements/EditCustomField:121
-msgid "No CustomField"
-msgstr "Não há Campo Personalizado"
-
-#: html/Admin/CustomFields/GroupRights.html:105
-msgid "No CustomField defined"
-msgstr "Nenhum Campo Personalizado definido"
-
-#: html/Admin/Groups/GroupRights.html:107 html/Admin/Groups/UserRights.html:94
-msgid "No Group defined"
-msgstr "Não há Grupo definido"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:484
-msgid "No Query"
-msgstr "Nenhuma Consulta"
-
-#: html/Admin/Queues/GroupRights.html:120 html/Admin/Queues/UserRights.html:91
-msgid "No Queue defined"
-msgstr "Não há Fila definida"
-
-#: bin/rt-crontool:75
-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:102 html/Admin/Queues/Template.html:99
-msgid "No Template"
-msgstr "Não há Modelo"
-
-#: NOT FOUND IN SOURCE
-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:79
-msgid "No action"
-msgstr "Não há ação"
-
-#: lib/RT/Record.pm:966
-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/Ticket/Elements/ShowRequestor:70
-msgid "No comment entered about this user"
-msgstr "Não há comentário sobre este usuário"
-
-#: NOT FOUND IN SOURCE
-msgid "No correspondence attached"
-msgstr "Não há nenhum arquivo anexado"
-
-#: lib/RT/Action/Generic.pm:187 lib/RT/Condition/Generic.pm:199 lib/RT/Search/ActiveTicketsInQueue.pm:79 lib/RT/Search/Generic.pm:136 lib/RT/Search/Googleish.pm:90
-#. (ref $self)
-msgid "No description for %1"
-msgstr "Não há descrição para %1"
-
-#: lib/RT/Users_Overlay.pm:192
-msgid "No group specified"
-msgstr "Não há grupo especificado"
-
-#: html/Admin/Groups/index.html:54
-msgid "No groups matching search criteria found."
-msgstr "Nenhum grupo satisfaz o critério de busca."
-
-#: lib/RT/Ticket_Overlay.pm:2395
-msgid "No message attached"
-msgstr "Nenhuma mensagem anexada"
-
-#: lib/RT/User_Overlay.pm:1036
-msgid "No password set"
-msgstr "Não há senha especificada"
-
-#: lib/RT/Queue_Overlay.pm:363
-msgid "No permission to create queues"
-msgstr "Não há permissão para criar filas"
-
-#: lib/RT/Ticket_Overlay.pm:422
-#. ($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:188
-msgid "No permission to create users"
-msgstr "Sem permissão para criar usuários"
-
-#: html/SelfService/Display.html:210
-msgid "No permission to display that ticket"
-msgstr "Sem permissão para mostrar o tíquete"
-
-#: lib/RT/SavedSearch.pm:158
-msgid "No permission to save system-wide searches"
-msgstr "Sem permissão para salvar buscas com abrangência em todo sistema"
-
-#: html/SelfService/Update.html:119
-msgid "No permission to view update ticket"
-msgstr "sem permissão para ver modificar o tíquete"
-
-#: lib/RT/Queue_Overlay.pm:797 lib/RT/Ticket_Overlay.pm:1491
-msgid "No principal specified"
-msgstr "Não há usuário/grupo especificado"
-
-#: html/Admin/Queues/People.html:177 html/Admin/Queues/People.html:187
-msgid "No principals selected."
-msgstr "Não há usuário/grupo selecionado."
-
-#: html/Admin/Queues/index.html:59
-msgid "No queues matching search criteria found."
-msgstr "Nenhuma fila satisfaz o critério de busca."
-
-#: html/Admin/Elements/SelectRights:108
-msgid "No rights found"
-msgstr "Nenhum direito encontrado"
-
-#: html/Admin/Elements/SelectRights:55
-msgid "No rights granted."
-msgstr "Nenhum direito outorgado."
-
-#: lib/RT/SavedSearch.pm:198
-msgid "No search loaded"
-msgstr "Nenhuma busca carregada"
-
-#: html/Search/Bulk.html:234
-msgid "No search to operate on."
-msgstr "Não há busca a realizar"
-
-#: html/Elements/RT__Ticket/ColumnMap:139 html/Search/Results.rdf:80
-msgid "No subject"
-msgstr "Sem assunto"
-
-#: NOT FOUND IN SOURCE
-msgid "No ticket id specified"
-msgstr "Nenhum identificador de tíquete especificado"
-
-#: html/Search/Chart:101
-msgid "No tickets found."
-msgstr "Nenhum tíquete encontrado."
-
-#: lib/RT/Transaction_Overlay.pm:549 lib/RT/Transaction_Overlay.pm:586
-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 e-mail especificado"
-
-#: html/Admin/Users/index.html:57
-msgid "No users matching search criteria found."
-msgstr "Nenhum usuário satisfaz o critério de busca."
-
-#: lib/RT/Record.pm:963
-msgid "No value sent to _Set!\\n"
-msgstr "Nenhum valor enviado a _Set!\\n"
-
-#: html/Elements/QuickCreate:61
-msgid "Nobody"
-msgstr "Ninguém"
-
-#: lib/RT/Record.pm:968
-msgid "Nonexistant field?"
-msgstr "Campo inexistente?"
-
-#: html/Search/Chart:149 html/Search/Elements/Chart:90
-msgid "Not Set"
-msgstr "Nao Definido"
-
-#: NOT FOUND IN SOURCE
-msgid "Not logged in"
-msgstr "Não logado"
-
-#: html/Elements/Header:98
-msgid "Not logged in."
-msgstr "Não entrou."
-
-#: lib/RT/Date.pm:399
-msgid "Not set"
-msgstr "Não definido"
-
-#: html/NoAuth/Reminder.html:50
-msgid "Not yet implemented."
-msgstr "Ainda não implementado."
-
-#: NOT FOUND IN SOURCE
-msgid "Not yet implemented...."
-msgstr "Ainda não implementado..."
-
-#: html/Approvals/Elements/Approve:83
-msgid "Notes"
-msgstr "Anotações"
-
-#: lib/RT/User_Overlay.pm:821
-msgid "Notification could not be sent"
-msgstr "A notificação não pôde ser enviada"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr "Notificar AdminCcs"
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "Notificar AdminCcs como Comentário"
-
-#: etc/initialdata:93 etc/upgrade/3.1.17/content:6
-msgid "Notify Ccs"
-msgstr "Notificar Ccs"
-
-#: etc/initialdata:89 etc/upgrade/3.1.17/content:2
-msgid "Notify Ccs as Comment"
-msgstr "Notificar Ccs como Comentário"
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr "Notificar Outros Destinatários"
-
-#: etc/initialdata:124
-msgid "Notify Other Recipients as Comment"
-msgstr "Notificar Outros Destinatários como Comentário"
-
-#: etc/initialdata:85
-msgid "Notify Owner"
-msgstr "Notificar Proprietário"
-
-#: etc/initialdata:81
-msgid "Notify Owner as Comment"
-msgstr "Notificar Proprietário como Comentário"
-
-#: etc/initialdata:376
-msgid "Notify Owner of their rejected ticket"
-msgstr "Notificar Proprietário sobre seus tíquetes rejeitados"
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr "Notificar Proprietário que todas as aprovações foram concedidas a seu tíquete"
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr "Notificar Proprietário que seu alguma aprovação foi concedida a seu tíquete"
-
-#: etc/initialdata:334
-msgid "Notify Owners and AdminCcs of new items pending their approval"
-msgstr ""
-"Notificar Proprietários e AdminCcs sobre novos itens dependendo de suas "
-"aprovações"
-
-#: etc/initialdata:77
-msgid "Notify Requestors"
-msgstr "Notificar Requisitantes"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "Notificar Requisitantes e Ccs"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "Notificar Requisitantes e Ccs como Comentário"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "Notificar Requisitantes, Ccs e AdminCcs"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "Notificar Requisitantes, Ccs e AdminCcs como Comentário"
-
-#: lib/RT/Date.pm:453
-msgid "Nov."
-msgstr "Nov."
-
-#: NOT FOUND IN SOURCE
-msgid "November"
-msgstr "Novembro"
-
-#: html/Search/Elements/SelectAndOr:49
-msgid "OR"
-msgstr "OU"
-
-#: lib/RT/Record.pm:329
-msgid "Object could not be created"
-msgstr "Objeto não pôde ser criado"
-
-#: lib/RT/Record.pm:130
-msgid "Object could not be deleted"
-msgstr "Objeto não pode ser removido"
-
-#: lib/RT/Record.pm:348
-msgid "Object created"
-msgstr "Objeto criado"
-
-#: lib/RT/Record.pm:127
-msgid "Object deleted"
-msgstr "Objeto removido"
-
-#: html/Admin/CustomFields/Objects.html:74 html/Admin/Elements/ObjectCustomFields:65
-#. ($ObjectType)
-#. ($LookupType)
-msgid "Object of type %1 cannot take custom fields"
-msgstr "Objeto do tipo %1 não aceitam campos customizados"
-
-#: lib/RT/CustomField_Overlay.pm:970
-msgid "Object type mismatch"
-msgstr "Tipo de objeto não corresponde"
-
-#: lib/RT/Date.pm:452
-msgid "Oct."
-msgstr "Out."
-
-#: NOT FOUND IN SOURCE
-msgid "October"
-msgstr "Outubro"
-
-#: html/Tools/Elements/Tabs:57
-msgid "Offline"
-msgstr "Offline"
-
-#: html/Tools/Offline.html:51
-msgid "Offline edits"
-msgstr "Edições offline"
-
-#: html/Tools/Offline.html:48
-msgid "Offline upload"
-msgstr "Envio offline"
-
-#: html/Elements/SelectDateRelation:58
-msgid "On"
-msgstr "Em"
-
-#: lib/RT/Transaction_Overlay.pm:349
-#. ($self->CreatedAsString(), $self->CreatorObj->Name())
-msgid "On %1, %2 wrote:"
-msgstr "Em %1, %2 escreveu:"
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "Num Comentário"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "Numa Correspondência"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "Na Criação"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "Na Mudança de Proprietário"
-
-#: etc/initialdata:177 etc/upgrade/3.1.17/content:15
-msgid "On Priority Change"
-msgstr "Na Mudança de Prioridade"
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "Na Mudança de Fila"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "Na Resolução"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "Na Mudança de Estado"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "Numa Transação"
-
-#: html/Approvals/Elements/PendingMyApproval:72
-#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='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:70
-#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore' id='CreatedBefore' />")
-msgid "Only show approvals for requests created before %1"
-msgstr "Só mostrar aprovações para requisições criadas antes de %1"
-
-#: html/Admin/CustomFields/index.html:77
-msgid "Only show custom fields for:"
-msgstr "Somente mostrar campos personalizados para:"
-
-#: NOT FOUND IN SOURCE
-msgid "Open"
-msgstr "Aberto"
-
-#: etc/initialdata:139
-msgid "Open Tickets"
-msgstr "Abrir Tíquetes"
-
-#: html/Ticket/Elements/Tabs:162
-msgid "Open it"
-msgstr "Abrir"
-
-#: NOT FOUND IN SOURCE
-msgid "Open requests"
-msgstr "Requisições abertas"
-
-#: html/SelfService/Elements/Tabs:78 html/SelfService/index.html:48
-msgid "Open tickets"
-msgstr "Abrir tíquetes"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in a new window"
-msgstr "Abrir tíquetes (da listagem) em uma nova janela"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in another window"
-msgstr "Abrir tíquetes (da listagem) em outra janela"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "Abrir tíquetes na correspondência"
-
-#: html/Prefs/MyRT.html:72
-msgid "Options"
-msgstr "Opções"
-
-#: html/Search/Elements/DisplayOptions:61
-msgid "Order by"
-msgstr "Ordenado por"
-
-#: NOT FOUND IN SOURCE
-msgid "Ordering and sorting"
-msgstr "Requisitando e ordenando"
-
-#: html/Admin/Users/Modify.html:144 html/User/Prefs.html:131
-msgid "Organization"
-msgstr "Organização"
-
-#: html/Approvals/Elements/Approve:55
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr "Tíquete originador: #%1"
-
-#: lib/RT/Transaction_Overlay.pm:643
-msgid "Outgoing email about a comment recorded"
-msgstr "E-mail de saida sobre um comentário gravado"
-
-#: lib/RT/Transaction_Overlay.pm:647
-msgid "Outgoing email recorded"
-msgstr "E-mail de saida gravado"
-
-#: html/Admin/Queues/Modify.html:92
-msgid "Over time, priority moves toward"
-msgstr "Após a data, a prioridade tende a"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "Own tickets"
-msgstr "Próprios tíquetes"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "OwnTicket"
-msgstr "OwnTicket"
-
-#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:103 html/Ticket/Create.html:74 html/Ticket/Elements/EditBasics:63 html/Ticket/Elements/EditPeople:66 html/Ticket/Elements/EditPeople:67 html/Ticket/Elements/Reminders:131 html/Ticket/Elements/ShowPeople:50 html/Ticket/Update.html:64 lib/RT/ACE_Overlay.pm:112 lib/RT/Tickets_Overlay.pm:2122
-msgid "Owner"
-msgstr "Proprietário"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner changed from %1 to %2"
-msgstr "Proprietário mudou de %1 para %2"
-
-#: lib/RT/Ticket_Overlay.pm:507
-msgid "Owner could not be set."
-msgstr "Proprietário não pode ser definido."
-
-#: lib/RT/Transaction_Overlay.pm:693
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "Proprietário mudado à força de %1 para %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner is"
-msgstr "O proprietário é"
-
-#: html/Elements/TicketList:82
-#. ($Page, $pages)
-msgid "Page %1 of %2"
-msgstr "Página %1 de %2"
-
-#: html/Admin/Users/Modify.html:201 html/User/Prefs.html:98
-msgid "Pager"
-msgstr "Pager"
-
-#: NOT FOUND IN SOURCE
-msgid "PagerPhone"
-msgstr "Telefone do Pager"
-
-#: NOT FOUND IN SOURCE
-msgid "Parent"
-msgstr "Pai"
-
-#: html/Elements/EditLinks:145 html/Elements/EditLinks:77 html/Elements/ShowLinks:70 html/Ticket/Create.html:224 html/Ticket/Elements/BulkLinks:62
-msgid "Parents"
-msgstr "Pais"
-
-#: html/Elements/Login:97 html/User/Prefs.html:107
-msgid "Password"
-msgstr "Senha"
-
-#: html/NoAuth/Reminder.html:48
-msgid "Password Reminder"
-msgstr "Lembrete de Senha"
-
-#: lib/RT/Transaction_Overlay.pm:802 lib/RT/User_Overlay.pm:1047
-msgid "Password changed"
-msgstr "Senha trocada"
-
-#: lib/RT/User_Overlay.pm:1039 lib/RT/User_Overlay.pm:216
-#. ($RT::MinimumPasswordLength)
-msgid "Password needs to be at least %1 characters long"
-msgstr "Senhas precisam ter no mínimo %1 caracteres"
-
-#: lib/RT/User_Overlay.pm:1046
-msgid "Password set"
-msgstr "Senha definida"
-
-#: NOT FOUND IN SOURCE
-msgid "Password too short"
-msgstr "Senha muito curta"
-
-#: html/User/Prefs.html:242
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "Senha: %1"
-
-#: lib/RT/User_Overlay.pm:1032
-msgid "Password: Permission Denied"
-msgstr "Senha: Permissão Negada"
-
-#: html/Admin/Users/Modify.html:368
-msgid "Passwords do not match."
-msgstr "Senhas não coincidem."
-
-#: html/User/Prefs.html:244
-msgid "Passwords do not match. Your password has not been changed"
-msgstr "Senhas não coincidem. Sua senha não foi mudada"
-
-#: html/Ticket/Elements/ShowSummary:64 html/Ticket/Elements/Tabs:121 html/Ticket/ModifyAll.html:74
-msgid "People"
-msgstr "Pessoas"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr "Realizar uma ação definida pelo usuário"
-
-#: html/Admin/Tools/Configuration.html:96
-msgid "Perl configuration"
-msgstr "Configuração perl"
-
-#: lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:582 lib/RT/ACE_Overlay.pm:592 lib/RT/ACE_Overlay.pm:602 lib/RT/ACE_Overlay.pm:667 lib/RT/Attribute_Overlay.pm:160 lib/RT/Attribute_Overlay.pm:166 lib/RT/Attribute_Overlay.pm:407 lib/RT/Attribute_Overlay.pm:416 lib/RT/Attribute_Overlay.pm:429 lib/RT/CurrentUser.pm:118 lib/RT/CurrentUser.pm:127 lib/RT/CustomField_Overlay.pm:1020 lib/RT/CustomField_Overlay.pm:1141 lib/RT/CustomField_Overlay.pm:1284 lib/RT/CustomField_Overlay.pm:174 lib/RT/CustomField_Overlay.pm:191 lib/RT/CustomField_Overlay.pm:202 lib/RT/CustomField_Overlay.pm:377 lib/RT/CustomField_Overlay.pm:406 lib/RT/CustomField_Overlay.pm:766 lib/RT/CustomField_Overlay.pm:939 lib/RT/CustomField_Overlay.pm:974 lib/RT/Group_Overlay.pm:1119 lib/RT/Group_Overlay.pm:1123 lib/RT/Group_Overlay.pm:1132 lib/RT/Group_Overlay.pm:1242 lib/RT/Group_Overlay.pm:1246 lib/RT/Group_Overlay.pm:1252 lib/RT/Group_Overlay.pm:447 lib/RT/Group_Overlay.pm:544 lib/RT/Group_Overlay.pm:622 lib/RT/Group_Overlay.pm:630 lib/RT/Group_Overlay.pm:728 lib/RT/Group_Overlay.pm:732 lib/RT/Group_Overlay.pm:738 lib/RT/Group_Overlay.pm:924 lib/RT/Group_Overlay.pm:928 lib/RT/Group_Overlay.pm:941 lib/RT/Queue_Overlay.pm:1056 lib/RT/Queue_Overlay.pm:142 lib/RT/Queue_Overlay.pm:160 lib/RT/Queue_Overlay.pm:659 lib/RT/Queue_Overlay.pm:669 lib/RT/Queue_Overlay.pm:683 lib/RT/Queue_Overlay.pm:821 lib/RT/Queue_Overlay.pm:830 lib/RT/Queue_Overlay.pm:843 lib/RT/Scrip_Overlay.pm:151 lib/RT/Scrip_Overlay.pm:162 lib/RT/Scrip_Overlay.pm:226 lib/RT/Scrip_Overlay.pm:540 lib/RT/Template_Overlay.pm:110 lib/RT/Template_Overlay.pm:279 lib/RT/Ticket_Overlay.pm:1359 lib/RT/Ticket_Overlay.pm:1369 lib/RT/Ticket_Overlay.pm:1383 lib/RT/Ticket_Overlay.pm:1524 lib/RT/Ticket_Overlay.pm:1534 lib/RT/Ticket_Overlay.pm:1548 lib/RT/Ticket_Overlay.pm:1665 lib/RT/Ticket_Overlay.pm:1985 lib/RT/Ticket_Overlay.pm:2128 lib/RT/Ticket_Overlay.pm:2298 lib/RT/Ticket_Overlay.pm:2348 lib/RT/Ticket_Overlay.pm:2528 lib/RT/Ticket_Overlay.pm:2541 lib/RT/Ticket_Overlay.pm:2617 lib/RT/Ticket_Overlay.pm:2630 lib/RT/Ticket_Overlay.pm:2751 lib/RT/Ticket_Overlay.pm:2765 lib/RT/Ticket_Overlay.pm:3016 lib/RT/Ticket_Overlay.pm:3027 lib/RT/Ticket_Overlay.pm:3033 lib/RT/Ticket_Overlay.pm:3250 lib/RT/Ticket_Overlay.pm:3254 lib/RT/Ticket_Overlay.pm:3397 lib/RT/Ticket_Overlay.pm:3525 lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:544 lib/RT/Transaction_Overlay.pm:572 lib/RT/Transaction_Overlay.pm:579 lib/RT/User_Overlay.pm:1178 lib/RT/User_Overlay.pm:1858 lib/RT/User_Overlay.pm:371 lib/RT/User_Overlay.pm:737 lib/RT/User_Overlay.pm:776
-msgid "Permission Denied"
-msgstr "Permissão Negada"
-
-#: lib/RT/Template_Overlay.pm:240 lib/RT/Template_Overlay.pm:249
-msgid "Permission denied"
-msgstr "Permissão negada"
-
-#: lib/RT/Template_Overlay.pm:379
-msgid "Permissions denied"
-msgstr "Permissões negadas"
-
-#: html/User/Elements/Tabs:58
-msgid "Personal Groups"
-msgstr "Grupos Pessoais"
-
-#: html/User/Groups/index.html:53 html/User/Groups/index.html:63
-msgid "Personal groups"
-msgstr "Grupos pessoais"
-
-#: html/User/Elements/DelegateRights:60
-msgid "Personal groups:"
-msgstr "Grupos pessoais:"
-
-#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:83
-msgid "Phone numbers"
-msgstr "Telefones"
-
-#: html/Elements/Header:95 html/Elements/Tabs:94 html/SelfService/Elements/Tabs:98 html/SelfService/Prefs.html:48 html/User/Prefs.html:48 html/User/Prefs.html:51
-msgid "Preferences"
-msgstr "Preferências"
-
-#: html/Admin/Users/MyRT.html:122
-#. ($pane, $UserObj->Name)
-msgid "Preferences %1 for user %2 ."
-msgstr "Preferências %1 para usuário %2."
-
-#: html/Prefs/MyRT.html:143
-#. ($pane)
-msgid "Preferences saved for %1."
-msgstr "Preferências salvas para %1."
-
-#: NOT FOUND IN SOURCE
-msgid "Prefs"
-msgstr "Prefs"
-
-#: lib/RT/Action/Generic.pm:197
-msgid "Prepare Stubbed"
-msgstr "Preparação Abortada"
-
-#: html/Helpers/CalPopup.html:58 html/Ticket/Elements/Tabs:86
-msgid "Prev"
-msgstr "Anterior"
-
-#: html/Elements/TicketList:105
-msgid "Previous Page"
-msgstr "Página Anterior"
-
-#: NOT FOUND IN SOURCE
-msgid "Previous page"
-msgstr "Página anterior"
-
-#: lib/RT/ACE_Overlay.pm:159 lib/RT/ACE_Overlay.pm:241 lib/RT/ACE_Overlay.pm:571
-#. ($args{'PrincipalId'})
-msgid "Principal %1 not found."
-msgstr "Usuário/Grupo %1 não encontrado."
-
-#: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:183 html/Ticket/Elements/EditBasics:94 html/Ticket/Elements/ShowBasics:74 lib/RT/Tickets_Overlay.pm:1906
-msgid "Priority"
-msgstr "Prioridade"
-
-#: html/Admin/Queues/Modify.html:88
-msgid "Priority starts at"
-msgstr "A prioridade inicia em"
-
-#: html/Search/Elements/EditSearches:52
-msgid "Privacy:"
-msgstr "Privacidade:"
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "Privilegiado"
-
-#: html/Admin/Users/Modify.html:346 html/User/Prefs.html:233
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr "Estado privilegiado: %1"
-
-#: html/Admin/Users/index.html:104
-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/Search/Build.html:123
-msgid "Query Builder"
-msgstr "Construtor de Consulta"
-
-#: html/Search/Elements/Chart:103
-msgid "Query:"
-msgstr "Consulta:"
-
-#: html/Elements/QueueSummary:50 html/Elements/QuickCreate:56 html/Search/Elements/PickBasics:73 html/SelfService/Create.html:56 html/Ticket/Create.html:64 html/Ticket/Elements/EditBasics:59 html/Ticket/Elements/ShowBasics:78 html/Tools/Reports/CreatedByDates.html:87 html/Tools/Reports/ResolvedByDates.html:88 html/Tools/Reports/ResolvedByOwner.html:68 html/User/Elements/DelegateRights:103 lib/RT/Tickets_Overlay.pm:1733
-msgid "Queue"
-msgstr "Fila"
-
-#: html/Admin/Queues/CustomField.html:65 html/Admin/Queues/Scrip.html:63 html/Admin/Queues/Scrips.html:71 html/Admin/Queues/Templates.html:67
-#. ($id)
-#. ($Queue)
-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/Queues/Modify.html:66
-msgid "Queue Name"
-msgstr "Nome da Fila"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Scrips"
-msgstr "Scrips da Fila"
-
-#: lib/RT/Queue_Overlay.pm:367
-msgid "Queue already exists"
-msgstr "A fila já existe"
-
-#: lib/RT/Queue_Overlay.pm:376 lib/RT/Queue_Overlay.pm:382
-msgid "Queue could not be created"
-msgstr "A fila não pôde ser criada"
-
-#: html/Ticket/Create.html:318 lib/t/regression/01ticket_link_searching.t:17
-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:386 lib/RT/StyleGuide.pod:807
-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:128 lib/RT/CustomField_Overlay.pm:199
-msgid "Queue not found"
-msgstr "Fila não encontrada"
-
-#: html/Admin/Elements/Tabs:61 html/Admin/index.html:74
-msgid "Queues"
-msgstr "Filas"
-
-#: html/Elements/MyAdminQueues:48
-msgid "Queues I administer"
-msgstr "Filas que eu administro"
-
-#: html/Elements/MySupportQueues:48
-msgid "Queues I'm an AdminCc for"
-msgstr "Filas nas quais sou AdminCc"
-
-#: html/Elements/Quicksearch:49 html/Prefs/Elements/Tabs:60 html/Prefs/Quicksearch.html:72
-msgid "Quick search"
-msgstr "Busca rápida"
-
-#: html/Elements/QuickCreate:49
-msgid "Quick ticket creation"
-msgstr "Criação rápida de tíquete"
-
-#: html/Search/Results.html:83
-msgid "RSS"
-msgstr "RSS"
-
-#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:794
-#. ($RT::VERSION, $RT::rtname)
-msgid "RT %1 for %2"
-msgstr "RT %1 para %2"
-
-#: 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:48 html/Admin/index.html:49
-msgid "RT Administration"
-msgstr "Administraçã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 "Ricochete 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:65 html/SelfService/Error.html:64
-msgid "RT Error"
-msgstr "Erro no RT"
-
-#: NOT FOUND IN SOURCE
-msgid "RT Received mail (%1) from itself."
-msgstr "O RT recebeu e-mail (%1) dele mesmo."
-
-#: NOT FOUND IN SOURCE
-msgid "RT Recieved mail (%1) from itself."
-msgstr "O RT recebeu e-mail (%1) dele mesmo."
-
-#: html/SelfService/Elements/Tabs:72 html/SelfService/Elements/Tabs:74
-msgid "RT Self Service"
-msgstr "Auto-Serviço RT"
-
-#: NOT FOUND IN SOURCE
-msgid "RT Self Service / Closed Tickets"
-msgstr "Auto-serviço do RT / Tíquetes Fechados"
-
-#: html/Admin/Tools/Configuration.html:75
-msgid "RT Variables"
-msgstr "Variáveis RT"
-
-#: html/Admin/Elements/SystemTabs:73 html/Admin/Elements/UserTabs:69 html/Admin/Global/MyRT.html:48 html/Admin/Global/MyRT.html:51 html/Admin/Global/MyRT.html:59 html/Admin/Global/index.html:86 html/Admin/Users/MyRT.html:68 html/Prefs/MyRT.html:68 html/Prefs/MyRT.html:80 html/User/Elements/Tabs:67 html/index.html:1 html/index.html:77
-msgid "RT at a glance"
-msgstr "RT por alto"
-
-#: html/Admin/Users/MyRT.html:77
-#. ($UserObj->Name)
-msgid "RT at a glance for the user %1"
-msgstr "RT por alto para o usuário %1"
-
-#: html/Admin/CustomFields/Modify.html:119
-msgid "RT can include content from another web service when showing this custom field."
-msgstr "RT pode incluir o conteúdo de algum outro serviço web quando estiver mostrando este campo personalizado."
-
-#: html/Admin/CustomFields/Modify.html:108
-msgid "RT can make this custom field's values into hyperlinks to another service."
-msgstr ""
-"RT pode transformar estes valores de campo customizado em hiperlinks para "
-"outro serviço."
-
-#: 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"
-
-#: html/Elements/SetupSessionCookie:102
-msgid "RT couldn't store your session."
-msgstr "RT não pode armazenar sua sessão."
-
-#: 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/Logo:51 html/Elements/PageLayout:176
-#. ($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"
-
-#: NOT FOUND IN SOURCE
-msgid "RT thinks this message may be a bounce"
-msgstr "O RT crê que esta mensagem seja um ricochete"
-
-#: html/Search/Simple.html:62
-msgid "RT will look for anything else you enter in ticket subjects."
-msgstr ""
-"RT vai procurar por qualquer outra coisa que você informar nos assuntos dos "
-"tíquetes."
-
-#: 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"
-
-#: html/Admin/CustomFields/Modify.html:110 html/Admin/CustomFields/Modify.html:121
-msgid "RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively"
-msgstr "RT vai substituir <tt>__id__</tt> e <tt>__CustomField__</tt> respectivamente com o id do registro e o valor do campo personalizado"
-
-#: html/Admin/Users/Modify.html:81 html/User/Prefs.html:71
-msgid "Real Name"
-msgstr "Nome real"
-
-#: NOT FOUND IN SOURCE
-msgid "RealName"
-msgstr "NomeReal"
-
-#: lib/RT/Transaction_Overlay.pm:746
-#. ($value)
-msgid "Reference by %1 added"
-msgstr "Referenciado por %1 adicionado"
-
-#: lib/RT/Transaction_Overlay.pm:786
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr "Referenciado por %1 removido"
-
-#: lib/RT/Transaction_Overlay.pm:743
-#. ($value)
-msgid "Reference to %1 added"
-msgstr "Referência a %1 adicionada"
-
-#: lib/RT/Transaction_Overlay.pm:783
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr "Referência a %1 removida"
-
-#: html/Elements/EditLinks:104 html/Elements/EditLinks:157 html/Elements/ShowLinks:94 html/Ticket/Create.html:227 html/Ticket/Elements/BulkLinks:74
-msgid "Referred to by"
-msgstr "Referenciado por"
-
-#: html/Elements/EditLinks:153 html/Elements/EditLinks:95 html/Elements/SelectLinkType:51 html/Elements/ShowLinks:84 html/Ticket/Create.html:226 html/Ticket/Elements/BulkLinks:70
-msgid "Refers to"
-msgstr "Faz referência a"
-
-#: NOT FOUND IN SOURCE
-msgid "RefersTo"
-msgstr "FazReferenciaA"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine"
-msgstr "Refinar"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine search"
-msgstr "Refinar a busca"
-
-#: html/Elements/Refresh:59
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "Recarregar esta página a cada %1 minutos."
-
-#: lib/RT/Transaction_Overlay.pm:832
-#. ($ticket->Subject)
-msgid "Reminder '%1' added"
-msgstr "Lembrete '%1' adicionado"
-
-#: lib/RT/Transaction_Overlay.pm:845
-#. ($ticket->Subject)
-msgid "Reminder '%1' completed"
-msgstr "Lembrete '%1' completado"
-
-#: lib/RT/Transaction_Overlay.pm:838
-#. ($ticket->Subject)
-msgid "Reminder '%1' reopened"
-msgstr "Lembrete '%1' reaberto"
-
-#: html/Ticket/Reminders.html:48
-#. ($Ticket->Id)
-msgid "Reminder ticket #%1"
-msgstr "Lembrete tíquete #%1"
-
-#: html/Elements/MyReminders:50 html/Ticket/Elements/ShowSummary:77 html/Ticket/Elements/Tabs:124 html/Ticket/Reminders.html:54
-msgid "Reminders"
-msgstr "Lembretes"
-
-#: html/Ticket/Reminders.html:52
-#. ($Ticket->Id)
-msgid "Reminders for ticket #%1"
-msgstr "Lembretes para tíquete #%1"
-
-#: html/Search/Bulk.html:96
-msgid "Remove AdminCc"
-msgstr "Remover AdminCc"
-
-#: html/Search/Bulk.html:92
-msgid "Remove Cc"
-msgstr "Remover Cc"
-
-#: html/Search/Bulk.html:88
-msgid "Remove Requestor"
-msgstr "Remover Requisitante"
-
-#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:149
-msgid "Reply"
-msgstr "Responder"
-
-#: html/Admin/Queues/Modify.html:74
-msgid "Reply Address"
-msgstr "Endereço para Resposta"
-
-#: html/Search/Bulk.html:131 html/Ticket/ModifyAll.html:96 html/Ticket/Update.html:80
-msgid "Reply to requestors"
-msgstr "Responder para requisitantes"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "Reply to tickets"
-msgstr "Responder aos tíquetes"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "ReplyToTicket"
-msgstr "ReplyToTicket"
-
-#: html/Tools/Elements/Tabs:61 html/Tools/Reports/index.html:48 html/Tools/Reports/index.html:49
-msgid "Reports"
-msgstr "Relatórios"
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:113
-msgid "Requestor"
-msgstr "Requisitante"
-
-#: NOT FOUND IN SOURCE
-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:65 html/Ticket/Create.html:82 html/Ticket/Elements/EditPeople:71 html/Ticket/Elements/ShowPeople:54
-msgid "Requestors"
-msgstr "Requisitantes"
-
-#: html/Admin/Queues/Modify.html:98
-msgid "Requests should be due in"
-msgstr "A requisições vencem em"
-
-#: lib/RT/Attribute_Overlay.pm:148
-#. ('Object')
-msgid "Required parameter '%1' not specified"
-msgstr "Parâmetro '%1' requerido e não especificado"
-
-#: html/Elements/Submit:85
-msgid "Reset"
-msgstr "Restaurar"
-
-#: html/Admin/Users/MyRT.html:62 html/Prefs/MyRT.html:62
-msgid "Reset to default"
-msgstr "Voltar para padrão"
-
-#: html/Admin/Users/Modify.html:186 html/User/Prefs.html:86
-msgid "Residence"
-msgstr "Residência"
-
-#: html/Ticket/Elements/Tabs:158
-msgid "Resolve"
-msgstr "Resolver"
-
-#: html/Ticket/Update.html:158
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "Resolver tíquete #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:51 lib/RT/Ticket_Overlay.pm:1174
-msgid "Resolved"
-msgstr "Resolvido"
-
-#: html/Tools/Reports/Elements/Tabs:57
-msgid "Resolved by owner"
-msgstr "Resolvidos por proprietário"
-
-#: html/Tools/Reports/Elements/Tabs:61
-msgid "Resolved in date range"
-msgstr "Resolvidos num intervalo de datas"
-
-#: html/Tools/Reports/ResolvedByDates.html:54
-msgid "Resolved tickets in period, grouped by owner"
-msgstr "Tíquetes resolvidos no período, agrupados por proprietário"
-
-#: html/Tools/Reports/ResolvedByOwner.html:52
-msgid "Resolved tickets, grouped by owner"
-msgstr "Tíquetes resolvidos, agrupados por proprietário"
-
-#: NOT FOUND IN SOURCE
-msgid "Response to requestors"
-msgstr "Resposta aos requisitantes"
-
-#: html/Elements/ListActions:48 html/Search/Elements/NewListActions:49
-msgid "Results"
-msgstr "Resultados"
-
-#: NOT FOUND IN SOURCE
-msgid "Results per page"
-msgstr "Resultados por página"
-
-#: html/Admin/Users/Modify.html:128 html/User/Prefs.html:118
-msgid "Retype Password"
-msgstr "Confirmar a Senha"
-
-#: html/Search/Elements/EditSearches:63
-msgid "Revert"
-msgstr "Reverter"
-
-#: 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:632
-msgid "Right Delegated"
-msgstr "Direito de Acesso Delegado"
-
-#: lib/RT/ACE_Overlay.pm:322
-msgid "Right Granted"
-msgstr "Direito de Acesso Outorgado"
-
-#: lib/RT/ACE_Overlay.pm:180
-msgid "Right Loaded"
-msgstr "Direito de Acesso Carregado"
-
-#: lib/RT/ACE_Overlay.pm:697 lib/RT/ACE_Overlay.pm:718
-msgid "Right could not be revoked"
-msgstr "Direito de acesso não pôde ser revogado"
-
-#: html/User/Delegation.html:87
-msgid "Right not found"
-msgstr "Direito de acesso não encontrado"
-
-#: lib/RT/ACE_Overlay.pm:562 lib/RT/ACE_Overlay.pm:657
-msgid "Right not loaded."
-msgstr "Direito de acesso não carregado."
-
-#: lib/RT/ACE_Overlay.pm:714
-msgid "Right revoked"
-msgstr "Direito de acesso revogado"
-
-#: html/Admin/Elements/UserTabs:72
-msgid "Rights"
-msgstr "Direitos de Acesso"
-
-#: html/Admin/CustomFields/GroupRights.html:131 lib/RT/Interface/Web.pm:987
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr "Direitos de acesso não puderam ser outorgados a %1"
-
-#: html/Admin/CustomFields/GroupRights.html:158 lib/RT/Interface/Web.pm:1016
-#. ($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:74 html/Admin/Queues/GroupRights.html:76
-msgid "Roles"
-msgstr "Papéis"
-
-#: NOT FOUND IN SOURCE
-msgid "RootApproval"
-msgstr "RootApproval"
-
-#: html/Prefs/MyRT.html:74
-msgid "Rows per box"
-msgstr "Linhas por caixa"
-
-#: html/Search/Elements/DisplayOptions:95
-msgid "Rows per page"
-msgstr "Linhas por página"
-
-#: lib/RT/Date.pm:424
-msgid "Sat."
-msgstr "Sáb."
-
-#: html/Prefs/MyRT.html:74 html/Prefs/Quicksearch.html:66 html/Prefs/Search.html:71 html/Prefs/Search.html:71 html/Search/Elements/EditSearches:72 html/Widgets/SelectionBox:222
-msgid "Save"
-msgstr "Salvar"
-
-#: html/Admin/Global/Template.html:69 html/Admin/Groups/Modify.html:94 html/Admin/Queues/Modify.html:113 html/Admin/Queues/People.html:128 html/Admin/Users/Modify.html:243 html/Prefs/Quicksearch.html:66 html/Prefs/SearchOptions.html:65 html/SelfService/Prefs.html:60 html/Ticket/Modify.html:62 html/Ticket/ModifyAll.html:129 html/Ticket/ModifyDates.html:62 html/Ticket/ModifyLinks.html:63 html/Ticket/ModifyPeople.html:62 html/User/Groups/Modify.html:79
-msgid "Save Changes"
-msgstr "Salvar as Alterações"
-
-#: html/User/Prefs.html:183
-msgid "Save Preferences"
-msgstr "Salvar Preferências"
-
-#: html/Ticket/Elements/PreviewScrips:133
-msgid "Save changes"
-msgstr "Salvar as alterações"
-
-#: lib/RT/SavedSearch.pm:175
-#. ($name)
-msgid "Saved search %1"
-msgstr "Busca salva %1"
-
-#: html/Admin/Elements/ListGlobalScrips:62 html/Admin/Global/Scrip.html:79 html/Admin/Queues/Scrip.html:86
-#. ($scrip->Id)
-#. ($id)
-msgid "Scrip #%1"
-msgstr "Scrip #%1"
-
-#: lib/RT/Scrip_Overlay.pm:205
-msgid "Scrip Created"
-msgstr "Scrip Criado"
-
-#: html/Admin/Elements/EditScrip:54
-msgid "Scrip Fields"
-msgstr "Campos de Scrip"
-
-#: html/Admin/Elements/EditScrips:111
-msgid "Scrip deleted"
-msgstr "Scrip removido"
-
-#: html/Admin/Elements/QueueTabs:69 html/Admin/Elements/SystemTabs:56 html/Admin/Global/index.html:64
-msgid "Scrips"
-msgstr "Scrips"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips for %1\\n"
-msgstr "Scrips para %1\\n"
-
-#: html/Admin/Queues/Scrips.html:57
-msgid "Scrips which apply to all queues"
-msgstr "Scrips aplicáveis a todas as filas"
-
-#: html/Elements/SimpleSearch:50 html/Search/Simple.html:67
-msgid "Search"
-msgstr "Buscar"
-
-#: NOT FOUND IN SOURCE
-msgid "Search Criteria"
-msgstr "Critérios de Busca"
-
-#: html/Prefs/SearchOptions.html:49 html/Prefs/SearchOptions.html:52
-msgid "Search Preferences"
-msgstr "Buscar Preferências"
-
-#: lib/RT/SavedSearch.pm:117
-msgid "Search attribute load failure"
-msgstr "Falha na carga de atributos de busca"
-
-#: html/Approvals/Elements/PendingMyApproval:61
-msgid "Search for approvals"
-msgstr "Buscar por aprovações"
-
-#: html/Search/Simple.html:77
-msgid "Search for tickets"
-msgstr "Busca por tíquetes"
-
-#: html/Search/Simple.html:59
-msgid "Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>. RT will look for anything else you enter in ticket bodies and attachments."
-msgstr "Busca por tí­quetes. Informar <strong>id</strong> por número, <strong>filas</strong> por nome, Proprietários por <strong>nomedeusuário</strong> e Requisitantes por <strong>endereço de e-email</strong>. RT vai procurar por qualquer outra coisa no corpo e anexos dos tí­quetes."
-
-#: html/User/Elements/Tabs:64
-msgid "Search options"
-msgstr "Opções de busca"
-
-#: html/Search/Chart.html:58
-#. ($PrimaryGroupBy)
-msgid "Search results grouped by %1"
-msgstr "Resultados da busca agrupado por %1"
-
-#: lib/RT/SavedSearch.pm:205
-#. ($msg)
-msgid "Search update: %1"
-msgstr "Busca atualizada: %1"
-
-#: html/Search/Simple.html:61
-msgid "Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>."
-msgstr "Pesquisar o texto completo de todos os tí­quetes pode gastar muito tempo, mas se você precisa disto, é possível procurar por qualquer palavra no histórico completo do tíquete teclando <b>fulltext:<i>palavra</i></b>."
-
-#: bin/rt-crontool:267
-msgid "Security:"
-msgstr "Segurança:"
-
-#: html/Elements/ShowCustomFields:102
-msgid "See also:"
-msgstr "Ver também:"
-
-#: lib/RT/CustomField_Overlay.pm:107
-msgid "See custom fields"
-msgstr "Ver campos personalizados"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "See exact outgoing email messages and their recipeients"
-msgstr "Ver mensagens de saída e destinatários"
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "See ticket private commentary"
-msgstr "Ver comentários privados do tíquete"
-
-#: lib/RT/Queue_Overlay.pm:105
-msgid "See ticket summaries"
-msgstr "Ver sumários de tíquetes"
-
-#: lib/RT/CustomField_Overlay.pm:107
-msgid "SeeCustomField"
-msgstr "VerCampoPersonalizado"
-
-#: lib/RT/Group_Overlay.pm:171
-msgid "SeeGroup"
-msgstr "VerGrupo"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "SeeQueue"
-msgstr "SeeQueue"
-
-#: html/Admin/CustomFields/index.html:48 html/Admin/CustomFields/index.html:51
-msgid "Select a Custom Field"
-msgstr "Selecionar um Campo Personalizado"
-
-#: html/Admin/Groups/index.html:80
-msgid "Select a group"
-msgstr "Selecionar um grupo"
-
-#: html/Admin/Queues/index.html:56
-msgid "Select a queue"
-msgstr "Selecionar uma fila"
-
-#: html/SelfService/CreateTicketInQueue.html:50
-msgid "Select a queue for your new ticket"
-msgstr "Selecionar uma fila para seu novo tíquete"
-
-#: html/Admin/Users/index.html:48 html/Admin/Users/index.html:51 html/Admin/Users/index.html:54
-msgid "Select a user"
-msgstr "Selecionar um usuário"
-
-#: html/Admin/Elements/CustomFieldTabs:92
-msgid "Select custom field"
-msgstr "Selecionar um campo personalizado"
-
-#: html/Admin/Global/CustomFields/index.html:72
-msgid "Select custom fields for all user groups"
-msgstr "Selecionar campos personalizados para todos grupos de usuário"
-
-#: html/Admin/Global/CustomFields/index.html:67
-msgid "Select custom fields for all users"
-msgstr "Selecionar campos personalizados para todos usuários"
-
-#: html/Admin/Global/CustomFields/index.html:78
-msgid "Select custom fields for tickets in all queues"
-msgstr "Selecionar campos personalizados para todas filas"
-
-#: html/Admin/Global/CustomFields/index.html:85
-msgid "Select custom fields for transactions on tickets in all queues"
-msgstr ""
-"Selecionar campos personalizados para transações em tíquetes de todas as "
-"filas"
-
-#: html/Admin/Elements/GroupTabs:77 html/User/Elements/GroupTabs:73
-msgid "Select group"
-msgstr "Selecionar um grupo"
-
-#: lib/RT/CustomField_Overlay.pm:61
-msgid "Select multiple values"
-msgstr "Selecionar valores múltiplos"
-
-#: lib/RT/CustomField_Overlay.pm:62
-msgid "Select one value"
-msgstr "Selecionar um valor"
-
-#: html/Admin/Elements/QueueTabs:94
-msgid "Select queue"
-msgstr "Selecionar uma fila"
-
-#: html/Prefs/Quicksearch.html:55
-msgid "Select queues to be displayed on the \"RT at a glance\" page"
-msgstr "Selecionar filas a serem mostradas na página \"RT por alto\""
-
-#: html/Admin/Global/Scrip.html:61 html/Admin/Global/Scrips.html:59 html/Admin/Queues/Scrip.html:69 html/Admin/Queues/Scrips.html:75
-msgid "Select scrip"
-msgstr "Selecionar um scrip"
-
-#: html/Admin/Global/Template.html:80 html/Admin/Global/Templates.html:59 html/Admin/Queues/Template.html:78 html/Admin/Queues/Templates.html:70
-msgid "Select template"
-msgstr "Selecionar um modelo"
-
-#: lib/RT/CustomField_Overlay.pm:63
-msgid "Select up to %1 values"
-msgstr "Selecionar até %1 valores"
-
-#: html/Admin/Elements/UserTabs:80
-msgid "Select user"
-msgstr "Selecionar um usuário"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectMultiple"
-msgstr "SelectMultiple"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectSingle"
-msgstr "SelectSingle"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Selected Custom Fields"
-msgstr "Selecionar Campos Personalizados"
-
-#: html/Admin/CustomFields/Objects.html:61
-msgid "Selected objects"
-msgstr "Selecionar Objetos"
-
-#: html/Widgets/SelectionBox:220
-msgid "Selections modified. Please save your changes"
-msgstr "Seleções mudadas.Por favor, salve suas alterações"
-
-#: NOT FOUND IN SOURCE
-msgid "Self Service"
-msgstr "Auto-serviço"
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "Enviar mensagem a todos os observadores"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "Enviar mensagem a todos os observadores como um \"comentário\""
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr "Enviar mensagem aos requisitantes e Ccs"
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr "Enviar mensagem aos requisitantes e Ccs como um comentário"
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr "Envia uma mensagem aos requisitantes"
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr "Envia uma mensagem aos Ccs e Bccs explicitamente listados"
-
-#: etc/initialdata:94 etc/upgrade/3.1.17/content:7
-msgid "Sends mail to the Ccs"
-msgstr "Envie mail para os Ccs"
-
-#: etc/initialdata:90 etc/upgrade/3.1.17/content:3
-msgid "Sends mail to the Ccs as a comment"
-msgstr "Envie mail para os Ccs como um comentário"
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr "Envia uma mensagem aos Ccs administrativos"
-
-#: etc/initialdata:98
-msgid "Sends mail to the administrative Ccs as a comment"
-msgstr "Envia uma mensagem aos Ccs administrativos como um comentário"
-
-#: etc/initialdata:82 etc/initialdata:86
-msgid "Sends mail to the owner"
-msgstr "Envia uma mensagem ao proprietário"
-
-#: lib/RT/Date.pm:451
-msgid "Sep."
-msgstr "Set."
-
-#: NOT FOUND IN SOURCE
-msgid "September"
-msgstr "Setembro"
-
-#: html/Ticket/Elements/ShowTransaction:158
-msgid "Show"
-msgstr "Mostrar"
-
-#: NOT FOUND IN SOURCE
-msgid "Show Approvals"
-msgstr "Mostrar Aprovações"
-
-#: html/Search/Elements/EditFormat:58
-msgid "Show Columns"
-msgstr "Mostrar Colunas"
-
-#: html/Ticket/Elements/Tabs:222
-msgid "Show Results"
-msgstr "Mostrar os Resultados"
-
-#: html/Approvals/Elements/PendingMyApproval:66
-msgid "Show approved requests"
-msgstr "Mostrar requisições aprovadas"
-
-#: html/Ticket/Create.html:390
-msgid "Show basics"
-msgstr "Mostrar o sumário"
-
-#: html/Approvals/Elements/PendingMyApproval:67
-msgid "Show denied requests"
-msgstr "Mostrar requisições negadas"
-
-#: html/Ticket/Create.html:393
-msgid "Show details"
-msgstr "Mostrar os detalhes"
-
-#: html/Approvals/Elements/PendingMyApproval:65
-msgid "Show pending requests"
-msgstr "Mostrar requisições pendentes"
-
-#: html/Approvals/Elements/PendingMyApproval:68
-msgid "Show requests awaiting other approvals"
-msgstr "Mostrar requisições aguardando outras aprovações"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket private commentary"
-msgstr "Mostrar comentário privado do tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket summaries"
-msgstr "Mostrar sumários do tíquete"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "ShowACL"
-msgstr "MostrarACL"
-
-#: lib/RT/System.pm:87
-msgid "ShowConfigTab"
-msgstr "MostarAbaDeConfiguracao"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "ShowOutgoingEmail"
-msgstr "MostrarE-maildeSaida"
-
-#: lib/RT/Group_Overlay.pm:170
-msgid "ShowSavedSearches"
-msgstr "MostrarBuscasSalvas"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "ShowScrips"
-msgstr "MostrarScrips"
-
-#: lib/RT/Queue_Overlay.pm:101
-msgid "ShowTemplate"
-msgstr "MostrarModelo"
-
-#: lib/RT/Queue_Overlay.pm:105
-msgid "ShowTicket"
-msgstr "MostrarTiquete"
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "ShowTicketComments"
-msgstr "MostrarComentariosdeTiquete"
-
-#: lib/RT/Queue_Overlay.pm:109
-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:110
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr "Cadastrar como um AdminCC de tíquete ou fila"
-
-#: html/Admin/Users/Modify.html:234 html/User/Prefs.html:170
-msgid "Signature"
-msgstr "Assinatura"
-
-#: NOT FOUND IN SOURCE
-msgid "Signed in as %1"
-msgstr "Assinado como %1"
-
-#: html/Elements/Tabs:71
-msgid "Simple Search"
-msgstr "Busca Simples"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:49
-msgid "Single"
-msgstr "Único"
-
-#: html/Search/Elements/EditFormat:77
-msgid "Size"
-msgstr "Tamanho"
-
-#: html/Elements/Header:91
-msgid "Skip Menu"
-msgstr "Saltar Menu"
-
-#: html/Search/Elements/EditFormat:80
-msgid "Small"
-msgstr "Pequeno"
-
-#: html/Admin/CustomFields/Modify.html:122
-msgid "Some browsers may only load content from the same domain as your RT server."
-msgstr ""
-"Alguns navegadores somente carregam conteúdo do mesmo domínio que seu "
-"servidor RT."
-
-#: html/Admin/Elements/AddCustomFieldValue:51 html/Admin/Elements/EditCustomFieldValues:56
-msgid "Sort"
-msgstr "Ordenar"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort key"
-msgstr "Chave de ordenação"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort results by"
-msgstr "Ordenar os resultados por"
-
-#: NOT FOUND IN SOURCE
-msgid "SortOrder"
-msgstr "Ordenação"
-
-#: html/Admin/Elements/EditScrip:80
-msgid "Stage"
-msgstr "Estágio"
-
-#: NOT FOUND IN SOURCE
-msgid "Stalled"
-msgstr "Pendente"
-
-#: NOT FOUND IN SOURCE
-msgid "Start page"
-msgstr "Página inicial"
-
-#: html/Elements/SelectDateType:50 html/Ticket/Elements/EditDates:55 html/Ticket/Elements/ShowDates:58
-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:54 html/Ticket/Create.html:210 html/Ticket/Elements/EditDates:50 html/Ticket/Elements/ShowDates:54
-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/Users/Modify.html:165 html/User/Prefs.html:147
-msgid "State"
-msgstr "Estado"
-
-#: html/Search/Elements/PickBasics:89 html/SelfService/Update.html:59 html/Ticket/Create.html:68 html/Ticket/Elements/EditBasics:55 html/Ticket/Elements/ShowBasics:54 html/Ticket/Update.html:61 html/Tools/MyDay.html:70 lib/RT/Ticket_Overlay.pm:1168 lib/RT/Tickets_Overlay.pm:1767
-msgid "Status"
-msgstr "Estado"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "Mudança de Estado"
-
-#: NOT FOUND IN SOURCE
-msgid "Status changed from %1 to %2"
-msgstr "Estado mudado de %1 para %2"
-
-#: NOT FOUND IN SOURCE
-msgid "StatusChange"
-msgstr "MudancadeEstado"
-
-#: html/Ticket/Elements/Tabs:180
-msgid "Steal"
-msgstr "Roubar"
-
-#: lib/RT/Queue_Overlay.pm:119
-msgid "Steal tickets"
-msgstr "Roubar tíquetes"
-
-#: lib/RT/Queue_Overlay.pm:119
-msgid "StealTicket"
-msgstr "RoubarTiquete"
-
-#: lib/RT/Transaction_Overlay.pm:699
-#. ($Old->Name)
-msgid "Stolen from %1"
-msgstr "Roubado de %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Stolen from %1 "
-msgstr "Roubado de %1 "
-
-#: html/Search/Elements/EditFormat:83
-msgid "Style"
-msgstr "Estilo"
-
-#: html/Elements/QuickCreate:54 html/Elements/SelectAttachmentField:49 html/Search/Bulk.html:134 html/SelfService/Create.html:81 html/SelfService/Update.html:67 html/Ticket/Create.html:110 html/Ticket/Elements/EditBasics:50 html/Ticket/Elements/Reminders:127 html/Ticket/ModifyAll.html:102 html/Ticket/Update.html:84 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:1849
-msgid "Subject"
-msgstr "Assunto"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:813 lib/RT/Transaction_Overlay.pm:721
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "Assunto mudou para %1"
-
-#: html/Elements/Submit:77
-msgid "Submit"
-msgstr "Enviar"
-
-#: NOT FOUND IN SOURCE
-msgid "Submit Workflow"
-msgstr "Enviar Workflow"
-
-#: lib/RT/Group_Overlay.pm:776
-msgid "Succeeded"
-msgstr "Deu certo"
-
-#: lib/RT/Date.pm:425
-msgid "Sun."
-msgstr "Dom."
-
-#: lib/RT/System.pm:77
-msgid "SuperUser"
-msgstr "SuperUsuário"
-
-#: html/User/Elements/DelegateRights:100
-msgid "System"
-msgstr "Sistema"
-
-#: html/Admin/Elements/ToolTabs:56 html/Admin/Tools/Configuration.html:50
-msgid "System Configuration"
-msgstr "Configuração do Sistema"
-
-#: html/Admin/CustomFields/GroupRights.html:130 html/Admin/CustomFields/GroupRights.html:157 html/Admin/CustomFields/UserRights.html:100 html/Admin/CustomFields/UserRights.html:130 html/Admin/Elements/SelectRights:108 lib/RT/ACE_Overlay.pm:586 lib/RT/Interface/Web.pm:1015 lib/RT/Interface/Web.pm:986
-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/Transaction_Overlay.pm:226 lib/RT/Transaction_Overlay.pm:232
-#. ($msg)
-msgid "System Error: %1"
-msgstr "Erro do Sistema: %1"
-
-#: html/Admin/Tools/index.html:49
-msgid "System Tools"
-msgstr "Ferramentas do Sistema"
-
-#: lib/RT/ACE_Overlay.pm:635
-msgid "System error. Right not delegated."
-msgstr "Erro do sistema. Direito de acesso não delegado."
-
-#: lib/RT/ACE_Overlay.pm:165 lib/RT/ACE_Overlay.pm:230 lib/RT/ACE_Overlay.pm:325
-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 é possível outorgar direitos de acesso."
-
-#: html/Admin/CustomFields/GroupRights.html:60 html/Admin/Global/GroupRights.html:58 html/Admin/Groups/GroupRights.html:60 html/Admin/Queues/GroupRights.html:59
-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:359
-msgid "TEST_STRING"
-msgstr ""
-
-#: etc/initialdata:603 html/Search/Elements/EditFormat:74 html/Ticket/Elements/Tabs:172
-msgid "Take"
-msgstr "Tomar"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "Take tickets"
-msgstr "Tomar tíquetes"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "TakeTicket"
-msgstr "TomarTiquete"
-
-#: lib/RT/Transaction_Overlay.pm:684
-msgid "Taken"
-msgstr "Tomado"
-
-#: html/Admin/Elements/EditScrip:73 html/Tools/Offline.html:80
-msgid "Template"
-msgstr "Modelo"
-
-#
-#: html/Admin/Global/Template.html:114 html/Admin/Queues/Template.html:115
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "Modelo #%1"
-
-#: html/Admin/Elements/EditTemplates:112
-msgid "Template deleted"
-msgstr "Modelo removido"
-
-#: lib/RT/Scrip_Overlay.pm:178
-msgid "Template is mandatory argument"
-msgstr "Modelo é um argumento obrigatório"
-
-#: lib/RT/Scrip_Overlay.pm:182
-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:346
-msgid "Template parsed"
-msgstr "Modelo processado"
-
-#: lib/RT/Template_Overlay.pm:398
-msgid "Template parsing error"
-msgstr "Erro de análise gramatical do modelo"
-
-#: html/Admin/Elements/QueueTabs:72 html/Admin/Elements/SystemTabs:59 html/Admin/Global/index.html:68
-msgid "Templates"
-msgstr "Modelos"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates for %1\\n"
-msgstr "Modelos de %1\\n"
-
-#: lib/RT/CustomField_Overlay.pm:946 lib/RT/Record.pm:962
-msgid "That is already the current value"
-msgstr "Este já é o valor atual"
-
-#: lib/RT/CustomField_Overlay.pm:415
-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:1996
-msgid "That is the same value"
-msgstr "Este é o mesmo valor"
-
-#: lib/RT/ACE_Overlay.pm:307 lib/RT/ACE_Overlay.pm:616
-msgid "That principal already has that right"
-msgstr "Este usuário/grupo já tem este direito."
-
-#: lib/RT/Queue_Overlay.pm:755
-#. ($args{'Type'})
-msgid "That principal is already a %1 for this queue"
-msgstr "Este usuário/grupo já é um %1 desta fila"
-
-#: lib/RT/Ticket_Overlay.pm:1437
-#. ($self->loc($args{'Type'}))
-msgid "That principal is already a %1 for this ticket"
-msgstr "Este usuário/grupo já é um %1 deste tíquete"
-
-#: lib/RT/Queue_Overlay.pm:854
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr "Este usuário/grupo não é um %1 desta fila"
-
-#: NOT FOUND IN SOURCE
-msgid "That principal is not a %1 for this ticket"
-msgstr "Este principal não é um %1 deste tíquete"
-
-#: lib/RT/Ticket_Overlay.pm:1992
-msgid "That queue does not exist"
-msgstr "Esta fila não existe"
-
-#: lib/RT/Ticket_Overlay.pm:3259
-msgid "That ticket has unresolved dependencies"
-msgstr "Este tíquete tem dependências não resolvidas"
-
-#: NOT FOUND IN SOURCE
-msgid "That user already has that right"
-msgstr "Este usuário já tem este direito de acesso"
-
-#: lib/RT/Action/CreateTickets.pm:712 lib/RT/Ticket_Overlay.pm:3062
-msgid "That user already owns that ticket"
-msgstr "Este usuário já possui este tíquete"
-
-#: lib/RT/Ticket_Overlay.pm:3005
-msgid "That user does not exist"
-msgstr "Este usuário não existe"
-
-#: lib/RT/User_Overlay.pm:391
-msgid "That user is already privileged"
-msgstr "Este usuário já tem privilégios"
-
-#: lib/RT/User_Overlay.pm:412
-msgid "That user is already unprivileged"
-msgstr "Este usuário já não tem privilégios"
-
-#: lib/RT/User_Overlay.pm:404
-msgid "That user is now privileged"
-msgstr "Este usuário agora tem privilégios"
-
-#: lib/RT/User_Overlay.pm:425
-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:3055
-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:235
-msgid "That's not a numerical id"
-msgstr "Este não é um identificador numérico"
-
-#: html/SelfService/Display.html:55 html/Ticket/Create.html:179 html/Ticket/Elements/ShowSummary:51
-msgid "The Basics"
-msgstr "Sumário"
-
-#: lib/RT/ACE_Overlay.pm:114
-msgid "The CC of a ticket"
-msgstr "O CC de um tíquete"
-
-#: lib/RT/ACE_Overlay.pm:115
-msgid "The administrative CC of a ticket"
-msgstr "O CC administrativo de um tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "The comment has been recorded"
-msgstr "O comentário foi registrado"
-
-#: bin/rt-crontool:277
-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 há 4 horas:"
-
-#: NOT FOUND IN SOURCE
-msgid "The following commands were not proccessed:\\n\\n"
-msgstr "Os seguintes comandos não foram processados:\\n\\n"
-
-#: lib/RT/Record.pm:965
-msgid "The new value has been set."
-msgstr "O novo valor foi atribuído."
-
-#: lib/RT/ACE_Overlay.pm:112
-msgid "The owner of a ticket"
-msgstr "O proprietário de um tíquete"
-
-#: lib/RT/ACE_Overlay.pm:113
-msgid "The requestor of a ticket"
-msgstr "O requisitante de um tíquete"
-
-#: html/Admin/Elements/EditUserComments:49
-msgid "These comments aren't generally visible to the user"
-msgstr "Estes comandos geralmente não estão visíveis para o usuário"
-
-#: lib/RT/CustomField_Overlay.pm:981
-msgid "This custom field does not apply to that object"
-msgstr "Este campo personalizado não se aplica a este objeto"
-
-#: html/Admin/Tools/Configuration.html:52
-msgid "This feature is only available to system administrators"
-msgstr "Esta função só está disponível para administradores do sistema"
-
-#: html/Ticket/Elements/PreviewScrips:98
-msgid "This message will be sent to..."
-msgstr "Esta mensagem será enviada para..."
-
-#: NOT FOUND IN SOURCE
-msgid "This ticket %1 %2 (%3)\\n"
-msgstr "Este tíquete %1 %2 (%3)\\n"
-
-#: bin/rt-crontool:268
-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:327
-msgid "This transaction appears to have no content"
-msgstr "Parece que esta transação não tem conteúdo"
-
-#: html/Ticket/Elements/ShowRequestor:72
-#. ($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:422
-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:48 html/Ticket/ModifyAll.html:52
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr "Tíquete #%1 Atualização jumbo: %2"
-
-#: html/Approvals/Elements/ShowDependency:69
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "Tíquete #%1: %2"
-
-#: lib/RT/Action/CreateTickets.pm:1352 lib/RT/Action/CreateTickets.pm:1361 lib/RT/Action/CreateTickets.pm:607 lib/RT/Action/CreateTickets.pm:731 lib/RT/Action/CreateTickets.pm:743
-#. ($T::Tickets{$template_id}->Id)
-#. ($T::Tickets{$template_id}->id)
-#. ($ticket->Id)
-msgid "Ticket %1"
-msgstr "Tíquete %1"
-
-#: lib/RT/Ticket_Overlay.pm:757 lib/RT/Ticket_Overlay.pm:777
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "Tíquete %1 criado na fila '%2'"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket %1 loaded\\n"
-msgstr "Tíquete %1 carregado\\n"
-
-#: html/Search/Bulk.html:379 html/Tools/MyDay.html:103 html/Tools/MyDay.html:94 html/Tools/MyDay.html:97
-#. ($Ticket->Id, $_)
-#. ($id, $msg)
-msgid "Ticket %1: %2"
-msgstr "Tíquete %1: %2"
-
-#: html/Admin/Elements/QueueTabs:76
-msgid "Ticket Custom Fields"
-msgstr "Campos Personalizados do Tíquete"
-
-#: html/Ticket/History.html:48 html/Ticket/History.html:51
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "Histórico do Tíquete # %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Id"
-msgstr "Identificador do tíquete"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "Tíquete Resolvido"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:71 html/Admin/Global/CustomFields/index.html:83 lib/RT/CustomField_Overlay.pm:1210
-msgid "Ticket Transactions"
-msgstr "Transações do Tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket attachment"
-msgstr "Arquivo anexo do tíquete"
-
-#: lib/RT/Tickets_Overlay.pm:2036
-msgid "Ticket content"
-msgstr "Conteúdo do tíquete"
-
-#: lib/RT/Tickets_Overlay.pm:2085
-msgid "Ticket content type"
-msgstr "Tipo do conteúdo do tíquete"
-
-#: lib/RT/Ticket_Overlay.pm:605 lib/RT/Ticket_Overlay.pm:619 lib/RT/Ticket_Overlay.pm:630 lib/RT/Ticket_Overlay.pm:765
-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"
-
-#: html/Ticket/Create.html:246
-msgid "Ticket could not be loaded"
-msgstr "Tíquete não pode ser carregado"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket created"
-msgstr "Tíquete criado"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket creation failed"
-msgstr "A criação do tíquete falhou"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket deleted"
-msgstr "Tíquete removido"
-
-#: NOT FOUND IN SOURCE
-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/Ticket/Display.html:57
-msgid "Ticket metadata"
-msgstr "Metadados do tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket not found"
-msgstr "Tíquete não encontrado"
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "O estado do tíquete mudou"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket watchers"
-msgstr "Observadores do tíquete"
-
-#: lib/RT/Search/FromSQL.pm:84
-#. (ref $self)
-msgid "TicketSQL search module"
-msgstr "Módulo de busca TiqueteSQL"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:66 html/Admin/Global/CustomFields/index.html:77 html/Elements/Tabs:74 html/Search/Chart:113 html/Search/Elements/Chart:111 lib/RT/CustomField_Overlay.pm:1209
-msgid "Tickets"
-msgstr "Tíquetes"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 %2"
-msgstr "Tíquetes %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 by %2"
-msgstr "Tíquetes %1 por %2"
-
-#: html/Tools/Reports/CreatedByDates.html:88
-msgid "Tickets created after"
-msgstr "Tíquetes criados depois de"
-
-#: html/Tools/Reports/CreatedByDates.html:90
-msgid "Tickets created before"
-msgstr "Tíquetes criados antes de"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets from %1"
-msgstr "Tíquetes de %1"
-
-#: html/Tools/Reports/ResolvedByDates.html:89
-msgid "Tickets resolved after"
-msgstr "Tíquetes resolvidos depois de"
-
-#: html/Tools/Reports/ResolvedByDates.html:91
-msgid "Tickets resolved before"
-msgstr "Tíquetes resolvidos antes de"
-
-#: html/Approvals/Elements/ShowDependency:50
-msgid "Tickets which depend on this approval:"
-msgstr "Tíquetes dependentes desta aprovação:"
-
-#: html/Search/Elements/PickBasics:136 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:74
-msgid "Time Estimated"
-msgstr "Tempo Estimado"
-
-#: html/Search/Elements/PickBasics:137 html/Ticket/Create.html:198 html/Ticket/Elements/EditBasics:87 lib/RT/Tickets_Overlay.pm:2007
-msgid "Time Left"
-msgstr "Tempo Restante"
-
-#: html/Search/Elements/PickBasics:135 html/Ticket/Create.html:191 html/Ticket/Elements/EditBasics:80 lib/RT/Tickets_Overlay.pm:1982
-msgid "Time Worked"
-msgstr "Tempo Trabalhado"
-
-#: NOT FOUND IN SOURCE
-msgid "Time left"
-msgstr "Tempo restante"
-
-#: html/Elements/Footer:53
-msgid "Time to display"
-msgstr "Tempo de apresentação"
-
-#: NOT FOUND IN SOURCE
-msgid "Time worked"
-msgstr "Tempo trabalhado"
-
-#: NOT FOUND IN SOURCE
-msgid "TimeLeft"
-msgstr "TempoRestanrte"
-
-#: lib/RT/Ticket_Overlay.pm:1169
-msgid "TimeWorked"
-msgstr "TempoTrabalhado"
-
-#: html/Search/Elements/EditFormat:76
-msgid "Title"
-msgstr "Título"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:"
-msgstr "Para gerar as diferenças desta transação"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:\\n"
-msgstr "Para gerar as diferenças desta transação:\\n"
-
-#: html/Elements/Footer:64
-#. ('<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>')
-msgid "To inquire about support, training, custom development or licensing, please contact %1."
-msgstr "Para pedir informações sobre suporte, treinamento, desenvolvimento personalizado ou licenciamento, por favor, contacte %1."
-
-#: lib/RT/Ticket_Overlay.pm:1172
-msgid "Told"
-msgstr "Última atualização"
-
-#: html/Admin/Elements/Tabs:70 html/Admin/index.html:90 html/Elements/Tabs:77 html/Tools/index.html:48 html/Tools/index.html:51
-msgid "Tools"
-msgstr "Ferramentas"
-
-#: html/Search/Elements/Chart:132
-msgid "Total"
-msgstr "Total"
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "Transação"
-
-#: lib/RT/Transaction_Overlay.pm:826
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr "Transação %1 removida"
-
-#: lib/RT/Transaction_Overlay.pm:185
-msgid "Transaction Created"
-msgstr "Transação Criada"
-
-#: html/Admin/Elements/QueueTabs:80
-msgid "Transaction Custom Fields"
-msgstr "Campos Personalizados da Transação"
-
-#: NOT FOUND IN SOURCE
-msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
-msgstr "Transaction->Create não foi feito, já que você não especificou um id de tíquete"
-
-#: lib/RT/Transaction_Overlay.pm:130
-msgid "Transaction->Create couldn't, as you didn't specify an object type and id"
-msgstr "Transaction->Create não foi feito, já que você não especificou um tipo de objeto e id de tíquete"
-
-#: lib/RT/Transaction_Overlay.pm:891
-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:420
-msgid "Tue."
-msgstr "Ter."
-
-#: html/Admin/CustomFields/Modify.html:68 html/Admin/Elements/EditCustomField:67 html/Ticket/Elements/AddWatchers:56 html/Ticket/Elements/AddWatchers:67 html/Ticket/Elements/AddWatchers:77 lib/RT/Ticket_Overlay.pm:1170 lib/RT/Tickets_Overlay.pm:1821
-msgid "Type"
-msgstr "Tipo"
-
-#: lib/RT/ScripCondition_Overlay.pm:130
-msgid "Unimplemented"
-msgstr "Não implementado"
-
-#: html/Admin/Users/Modify.html:91
-msgid "Unix login"
-msgstr "Usuário Unix"
-
-#: NOT FOUND IN SOURCE
-msgid "UnixUsername"
-msgstr "NomeUsuárioUnix"
-
-#: lib/RT/Attachment_Overlay.pm:291 lib/RT/Record.pm:863
-#. ($ContentEncoding)
-#. ($self->ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr "Codificação de conteúdo desconhecida %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Unknown field: $key"
-msgstr "Campo desconhecido: $key"
-
-#: html/Search/Build.html:461 lib/RT/Report/Tickets.pm:412
-#. ($key)
-msgid "Unknown field: %1"
-msgstr "Campo desconhecido: %1"
-
-#: html/Elements/SelectResultsPerPage:60
-msgid "Unlimited"
-msgstr "Ilimitado"
-
-#: html/Search/Elements/SelectSearchesForObjects:66
-msgid "Unnamed search"
-msgstr "Busca sen nome"
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr "Não privilegiado"
-
-#: html/Admin/Elements/EditCustomFields:62
-msgid "Unselected Custom Fields"
-msgstr "Campos Personalizados não selecionados"
-
-#: html/Admin/CustomFields/Objects.html:63
-msgid "Unselected objects"
-msgstr "Objetos não selecionados"
-
-#: lib/RT/Transaction_Overlay.pm:680
-msgid "Untaken"
-msgstr "Não tomado"
-
-#: html/Admin/Elements/EditScrip:130 html/Elements/RT__Ticket/ColumnMap:304 html/Search/Bulk.html:195 html/Search/Bulk.html:77
-msgid "Update"
-msgstr "Atualizar"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ID"
-msgstr "Identificador de atualização"
-
-#: html/Ticket/Update.html:137
-msgid "Update Ticket"
-msgstr "Atualizar Tíquete"
-
-#: html/Search/Bulk.html:128 html/Ticket/ModifyAll.html:89 html/Ticket/Update.html:74
-msgid "Update Type"
-msgstr "Tipo de atualização"
-
-#: NOT FOUND IN SOURCE
-msgid "Update all these tickets at once"
-msgstr "Atualizar todos estes tíquetes de uma vez"
-
-#: NOT FOUND IN SOURCE
-msgid "Update email"
-msgstr "Atualizar e-mail"
-
-#: html/Search/Bulk.html:202 html/Search/Results.html:80
-msgid "Update multiple tickets"
-msgstr "Atualizar múltiplos tíquetes"
-
-#: NOT FOUND IN SOURCE
-msgid "Update name"
-msgstr "Atualizar nome"
-
-#: lib/RT/Action/CreateTickets.pm:752 lib/RT/Interface/Web.pm:606
-msgid "Update not recorded."
-msgstr "Atualização não registrada."
-
-#: NOT FOUND IN SOURCE
-msgid "Update selected tickets"
-msgstr "Atualizar os tíquetes selecionados"
-
-#: NOT FOUND IN SOURCE
-msgid "Update signature"
-msgstr "Atualizar assinatura"
-
-#: html/Ticket/ModifyAll.html:86
-msgid "Update ticket"
-msgstr "Atualizar o tíquete"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ticket # %1"
-msgstr "Atualizar o tíquete # %1"
-
-#: html/SelfService/Update.html:114 html/SelfService/Update.html:49
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "Atualizar o tíquete #%1"
-
-#: html/Ticket/Update.html:160
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "Atualizar tíquete #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:750 lib/RT/Interface/Web.pm:605
-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:56 html/Ticket/Elements/ShowDates:74 lib/RT/CustomField_Overlay.pm:1287 lib/RT/Ticket_Overlay.pm:1173
-msgid "Updated"
-msgstr "Atualizado"
-
-#: html/Tools/Offline.html:95
-msgid "Upload"
-msgstr "Enviar"
-
-#: lib/RT/CustomField_Overlay.pm:86
-msgid "Upload multiple files"
-msgstr "Enviar múltiplos arquivos"
-
-#: lib/RT/CustomField_Overlay.pm:81
-msgid "Upload multiple images"
-msgstr "Enviar múltiplas imagens"
-
-#: lib/RT/CustomField_Overlay.pm:87
-msgid "Upload one file"
-msgstr "Enviar um arquivo"
-
-#: lib/RT/CustomField_Overlay.pm:82
-msgid "Upload one image"
-msgstr "Enviar uma imagem"
-
-#: lib/RT/CustomField_Overlay.pm:88
-msgid "Upload up to %1 files"
-msgstr "Enviar até %1 arquivos"
-
-#: lib/RT/CustomField_Overlay.pm:83
-msgid "Upload up to %1 images"
-msgstr "Enviar até %1 imagens"
-
-#: html/Tools/Offline.html:95
-msgid "Upload your changes"
-msgstr "Enviar suas alterações"
-
-#: html/Admin/index.html:92
-msgid "Use other RT administrative tools"
-msgstr "Usar outras ferramentas administrativas RT"
-
-#: 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"
-
-#: lib/RT/Ticket_Overlay.pm:508
-#. ($args{'Owner'})
-msgid "User '%1' could not be found."
-msgstr "Usuário '%1' não encontrado."
-
-#: 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:132 etc/initialdata:206
-msgid "User Defined"
-msgstr "Definido pelo Usuário"
-
-#: html/Admin/Elements/EditScrip:95
-msgid "User Defined conditions and actions"
-msgstr "Condições e ações definidas pelo usuário"
-
-#: NOT FOUND IN SOURCE
-msgid "User ID"
-msgstr "Identificador de usuário"
-
-#: NOT FOUND IN SOURCE
-msgid "User Id"
-msgstr "Identificador do usuário"
-
-#: html/Admin/Elements/CustomFieldTabs:74 html/Admin/Elements/GroupTabs:70 html/Admin/Elements/QueueTabs:87 html/Admin/Elements/SystemTabs:70 html/Admin/Global/index.html:82
-msgid "User Rights"
-msgstr "Direitos de Acesso de Usuário"
-
-#: html/Admin/Users/Modify.html:305
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "O usuário não pôde ser criado: %1"
-
-#: lib/RT/User_Overlay.pm:332
-msgid "User created"
-msgstr "Usuário criado"
-
-#: html/Admin/CustomFields/GroupRights.html:76 html/Admin/Global/GroupRights.html:90 html/Admin/Groups/GroupRights.html:77 html/Admin/Queues/GroupRights.html:92
-msgid "User defined groups"
-msgstr "Grupos definidos pelo usuário"
-
-#: lib/RT/User_Overlay.pm:594 lib/RT/User_Overlay.pm:614
-msgid "User loaded"
-msgstr "Usuário carregado"
-
-#: NOT FOUND IN SOURCE
-msgid "User notified"
-msgstr "Usuário notificado"
-
-#: NOT FOUND IN SOURCE
-msgid "User view"
-msgstr "Visualização de usuário"
-
-#: html/Admin/Groups/index.html:105
-msgid "User-defined groups"
-msgstr "Grupos definidos pelo usuário"
-
-#: html/Admin/Users/Modify.html:71 html/Elements/Login:92 html/Ticket/Elements/AddWatchers:58
-msgid "Username"
-msgstr "Nome de usuário"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:57 html/Admin/Elements/SelectNewGroupMembers:49 html/Admin/Elements/Tabs:55 html/Admin/Global/CustomFields/index.html:66 html/Admin/Groups/Members.html:78 html/Admin/Queues/People.html:91 html/Admin/index.html:64 html/User/Groups/Members.html:81 lib/RT/CustomField_Overlay.pm:1211
-msgid "Users"
-msgstr "Usuários"
-
-#: html/Admin/Users/index.html:87
-msgid "Users matching search criteria"
-msgstr "Usuários que satisfazem o critério de busca"
-
-#: bin/rt-crontool:136
-#. ($transaction->id)
-msgid "Using transaction #%1..."
-msgstr "Usando transação #%1"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:530
-msgid "Valid Query"
-msgstr "Consulta Válida"
-
-#: html/Admin/CustomFields/Modify.html:82
-msgid "Validation"
-msgstr "Validação"
-
-#: NOT FOUND IN SOURCE
-msgid "ValueOfQueue"
-msgstr "Valor da fila"
-
-#: html/Admin/CustomFields/Modify.html:132 html/Admin/Elements/EditCustomField:80
-msgid "Values"
-msgstr "Valores"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Watch"
-msgstr "Observar"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "WatchAsAdminCc"
-msgstr "ObservarcomoAdminCC"
-
-#: NOT FOUND IN SOURCE
-msgid "Watcher loaded"
-msgstr "Observador carregado"
-
-#: html/Admin/Elements/QueueTabs:65
-msgid "Watchers"
-msgstr "Observadores"
-
-#: NOT FOUND IN SOURCE
-msgid "WebEncoding"
-msgstr "Codificação de Web"
-
-#: lib/RT/Date.pm:421
-msgid "Wed."
-msgstr "Qua."
-
-#: html/Tools/MyDay.html:80
-msgid "What I did today"
-msgstr "O que eu fiz hoje"
-
-#: etc/initialdata:521
-msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
-msgstr "Quando todas as aprovações de um tí­quete forem concedidas, adicionar uma correspondência ao tí­quete original"
-
-#: etc/initialdata:485
-msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
-msgstr "Quando uma aprovação for concedida a um tí­quete, adicionar uma correspondência ao tíquete original"
-
-#: etc/initialdata:146
-msgid "When a ticket is created"
-msgstr "Quando um tíquete é criado"
-
-#: etc/initialdata:418
-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 í­tem aguardando por aprovação"
-
-#: etc/initialdata:151
-msgid "When anything happens"
-msgstr "Quando qualquer coisa acontecer"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "Sempre que um tíquete for resolvido"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr "Sempre que mudar o proprietário de um tíquete"
-
-#: etc/initialdata:178 etc/upgrade/3.1.17/content:16
-msgid "Whenever a ticket's priority changes"
-msgstr "Sempre que a prioridade de um tíquete for mudada"
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr "Sempre que um tíquete mudar de fila"
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr "Sempre que o estado de um tíquete mudar"
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr "Sempre que ocorrer uma condição definida por usuário"
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr "Sempre que um novo comentário é adicionado"
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr "Sempre que uma nova correspondência é adicionada"
-
-#: html/Admin/Users/Modify.html:191 html/User/Prefs.html:90
-msgid "Work"
-msgstr "Trabalho"
-
-#: html/Search/Results.html:84
-msgid "Work offline"
-msgstr "Trabalhar offline"
-
-#: NOT FOUND IN SOURCE
-msgid "WorkPhone"
-msgstr "Telefone de trabalho"
-
-#: html/Ticket/Elements/ShowBasics:65 html/Ticket/Update.html:66 html/Tools/MyDay.html:65
-msgid "Worked"
-msgstr "Trabalhado"
-
-#: lib/RT/Ticket_Overlay.pm:3166
-msgid "You already own this ticket"
-msgstr "Você já é proprietário deste tíquete"
-
-#: html/autohandler:216 html/autohandler:224
-msgid "You are not an authorized user"
-msgstr "Você não é um usuário autorizado"
-
-#: html/Prefs/Search.html:58
-msgid "You can also edit the predefined search itself"
-msgstr "Você também pode editar as buscas pré-definidas"
-
-#: lib/RT/Ticket_Overlay.pm:3048
-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"
-
-#: lib/RT/Ticket_Overlay.pm:3044
-msgid "You can only take tickets that are unowned"
-msgstr "Você apenas pode pegar tíquetes que não tem 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 lib/RT/StyleGuide.pod:778
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "Você encontrou %1 tíquetes na fila %2"
-
-#: html/NoAuth/Logout.html:54
-msgid "You have been logged out of RT."
-msgstr "Você foi desconectado do RT."
-
-#: html/SelfService/Display.html:135
-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:2005
-msgid "You may not create requests in that queue."
-msgstr "Você não pode criar requisições nesta fila."
-
-#: html/NoAuth/Logout.html:58
-msgid "You're welcome to login again"
-msgstr "Volte sempre"
-
-#: NOT FOUND IN SOURCE
-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:502
-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:540
-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:445
-msgid "Your request was rejected."
-msgstr "Sua requisição foi rejeitada."
-
-#: html/autohandler:253
-msgid "Your username or password is incorrect"
-msgstr "Nome de usuário ou senha incorretos"
-
-#: html/Admin/Users/Modify.html:171 html/User/Prefs.html:151
-msgid "Zip"
-msgstr "CEP"
-
-#: NOT FOUND IN SOURCE
-msgid "[no subject]"
-msgstr "[sem assunto]"
-
-#: html/Search/Elements/DisplayOptions:67
-msgid "[none]"
-msgstr "[nenhum]"
-
-#: lib/RT/System.pm:89
-msgid "allow creation of saved searches"
-msgstr "permite a criação de buscas salvas"
-
-#: lib/RT/System.pm:88
-msgid "allow loading of saved searches"
-msgstr "permite a carga de buscas salvas"
-
-#: html/User/Elements/DelegateRights:82
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr "como outorgado a %1"
-
-#: html/Search/Results.html:85
-msgid "chart"
-msgstr "gráfico"
-
-#: html/SelfService/Closed.html:51
-msgid "closed"
-msgstr "fechado"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:57
-msgid "contains"
-msgstr "contém"
-
-#: NOT FOUND IN SOURCE
-msgid "content"
-msgstr "conteúdo"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence (probably) not sent"
-msgstr "correspondência (provavelmente) não enviada"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence sent"
-msgstr "correspondência enviada"
-
-#: html/Admin/Queues/Modify.html:100 lib/RT/Date.pm:348
-msgid "days"
-msgstr "dias"
-
-#: NOT FOUND IN SOURCE
-msgid "dead"
-msgstr "morto"
-
-#: NOT FOUND IN SOURCE
-msgid "delete"
-msgstr "remover"
-
-#: lib/RT/Queue_Overlay.pm:89
-msgid "deleted"
-msgstr "removido"
-
-#: html/Search/Elements/PickBasics:63
-msgid "does not match"
-msgstr "não satisfaz"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:58
-msgid "doesn't contain"
-msgstr "não contém"
-
-#: html/Elements/SelectEqualityOperator:61
-msgid "equal to"
-msgstr "igual a"
-
-#: html/Search/Build.html:553
-msgid "error: can't move down"
-msgstr "erro: não pode mover para baixo"
-
-#: html/Search/Build.html:575
-msgid "error: can't move left"
-msgstr "erro: não pode mover para a esquerda"
-
-#: html/Search/Build.html:534
-msgid "error: can't move up"
-msgstr "erro: não pode mover para cima"
-
-#: html/Search/Build.html:618
-msgid "error: nothing to delete"
-msgstr "erro: nada para remover"
-
-#: html/Search/Build.html:539 html/Search/Build.html:558 html/Search/Build.html:580 html/Search/Build.html:609
-msgid "error: nothing to move"
-msgstr "erro: nada para mover"
-
-#: html/Search/Build.html:636
-msgid "error: nothing to toggle"
-msgstr "erro: nada para alternar"
-
-#: NOT FOUND IN SOURCE
-msgid "false"
-msgstr "falso"
-
-#: NOT FOUND IN SOURCE
-msgid "filename"
-msgstr "nome do arquivo"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectEqualityOperator:61
-msgid "greater than"
-msgstr "maior que"
-
-#: lib/RT/Group_Overlay.pm:216
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "grupo '%1'"
-
-#: html/Search/Results.html:90
-#. ($m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query))
-msgid "grouped by %1"
-msgstr "agrupado por %1"
-
-#: lib/RT/Date.pm:344
-msgid "hours"
-msgstr "horas"
-
-#: html/Search/Elements/PickBasics:50
-msgid "id"
-msgstr "identificador"
-
-#: html/Elements/SelectBoolean:55 html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:59 html/Search/Elements/PickBasics:164 html/Search/Elements/PickBasics:76 html/Search/Elements/PickBasics:92 html/Search/Elements/PickCFs:55
-msgid "is"
-msgstr "é"
-
-#: html/Elements/SelectBoolean:59 html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:60 html/Search/Elements/PickBasics:165 html/Search/Elements/PickBasics:77 html/Search/Elements/PickBasics:93 html/Search/Elements/PickCFs:56
-msgid "isn't"
-msgstr "não é"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectEqualityOperator:61
-msgid "less than"
-msgstr "menor que"
-
-#: html/Search/Elements/PickBasics:62
-msgid "matches"
-msgstr "satisfazem"
-
-#: lib/RT/Date.pm:340
-msgid "min"
-msgstr ""
-
-#: html/Tools/MyDay.html:65
-msgid "minutes"
-msgstr "minutos"
-
-#: NOT FOUND IN SOURCE
-msgid "modifications\\n\\n"
-msgstr "modificações\\n\\n"
-
-#: lib/RT/Date.pm:356
-msgid "months"
-msgstr "meses"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "new"
-msgstr "novo"
-
-#: html/Admin/Elements/PickCustomFields:66 html/Admin/Elements/PickObjects:67
-msgid "no name"
-msgstr "sem nome"
-
-#: html/Admin/Elements/EditScrips:66
-msgid "no value"
-msgstr "sem valor"
-
-#: html/Admin/Elements/EditQueueWatchers:50 html/Ticket/Elements/EditWatchers:51
-msgid "none"
-msgstr "nenhum"
-
-#: html/Elements/SelectEqualityOperator:61
-msgid "not equal to"
-msgstr "diferente de"
-
-#: NOT FOUND IN SOURCE
-msgid "notlike"
-msgstr "diferente"
-
-#: html/SelfService/Elements/MyRequests:78 lib/RT/Queue_Overlay.pm:85
-msgid "open"
-msgstr "aberto"
-
-#: lib/RT/Group_Overlay.pm:221
-#. ($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:229
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "fila %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:88
-msgid "rejected"
-msgstr "rejeitado"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "resolved"
-msgstr "resolvido"
-
-#: lib/RT/Date.pm:336
-msgid "sec"
-msgstr "seg"
-
-#: lib/RT/System.pm:87
-msgid "show Configuration tab"
-msgstr "mostrar aba de Configuração"
-
-#: html/Search/Results.html:82
-msgid "spreadsheet"
-msgstr "planilha"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "stalled"
-msgstr "pendente"
-
-#: html/Search/Results.html:91
-#. ($m->scomp('Elements/SelectChartType', Name => 'ChartStyle'))
-msgid "style: %1"
-msgstr "Estilo: %1"
-
-#: html/Prefs/MyRT.html:95
-msgid "summary rows"
-msgstr "linhas do sumário"
-
-#: lib/RT/Group_Overlay.pm:224
-#. ($self->Type)
-msgid "system %1"
-msgstr "sistema %1"
-
-#: lib/RT/Group_Overlay.pm:235
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr "grupo do sistema '%1'"
-
-#: html/Elements/Error:66 html/SelfService/Error.html:65
-msgid "the calling component did not specify why"
-msgstr "o componente chamador não especificou por que"
-
-#: lib/RT/Group_Overlay.pm:232
-#. ($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:238
-#. ($self->Id)
-msgid "undescribed group %1"
-msgstr "grupo %1 sem descrição "
-
-#: NOT FOUND IN SOURCE
-msgid "undescripbed group %1"
-msgstr "grupo sem descrição %1"
-
-#: lib/RT/Group_Overlay.pm:213
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "usuário %1"
-
-#: lib/RT/Date.pm:352
-msgid "weeks"
-msgstr "semanas"
-
-#: NOT FOUND IN SOURCE
-msgid "with template %1"
-msgstr "com modelo %1"
-
-#: lib/RT/Date.pm:360
-msgid "years"
-msgstr "anos"
-
diff --git a/rt/lib/RT/I18N/pt_pt.po b/rt/lib/RT/I18N/pt_pt.po
deleted file mode 100644
index fa32e0e..0000000
--- a/rt/lib/RT/I18N/pt_pt.po
+++ /dev/null
@@ -1,5194 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: RT 3.5.x\n"
-"PO-Revision-Date: 2008-05-12 12:00-0000\n"
-"Last-Translator: RICARDO OLIVEIRA <rmo@eurotux.com>\n"
-"Language-Team: rt-devel <rt-devel@lists.bestpractical.com>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: html/Widgets/SavedSearch:117
-#. ($self->{CurrentSearch}{Object}->Description)
-msgid " %1 deleted."
-msgstr " %1 apagado"
-
-#: html/Widgets/SavedSearch:94
-#. ($self->{CurrentSearch}{Description}, $args->{Description})
-msgid " %1 renamed to %2."
-msgstr " %1 alterado para %2."
-
-#: html/Widgets/SavedSearch:107
-#. ($args->{Description})
-msgid " %1 saved."
-msgstr " %1 gravado"
-
-#: html/Approvals/Elements/Approve:50 html/Approvals/Elements/ShowDependency:73 html/SelfService/Display.html:48 html/Ticket/Display.html:49 html/Ticket/Display.html:53
-#. ($Ticket->id, $Ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-#. ($ticket->Id, $ticket->Subject)
-#. ($TicketObj->Id, $TicketObj->Subject)
-msgid "#%1: %2"
-msgstr "#%1: %2"
-
-#: html/Elements/ShowSearch:116
-msgid "$1"
-msgstr "$1"
-
-#: lib/RT/Record.pm:957
-#. ($label)
-msgid "$prefix %1"
-msgstr "$prefix %1"
-
-#: lib/RT/URI/fsck_com_rt.pm:258
-#. ($self->ObjectType, $self->Object->Id)
-msgid "%1 #%2"
-msgstr "%1 #%2"
-
-#: lib/RT/Date.pm:367
-#. ($s, $time_unit)
-msgid "%1 %2"
-msgstr "%1 %2"
-
-#: lib/RT/Tickets_Overlay.pm:1684
-#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
-msgid "%1 %2 %3"
-msgstr ""
-
-#: lib/RT/Date.pm:403
-#. ($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/Record.pm:1707 lib/RT/Transaction_Overlay.pm:668 lib/RT/Transaction_Overlay.pm:711
-#. ($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:364
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "há %1 %2"
-
-#: lib/RT/Record.pm:1714 lib/RT/Transaction_Overlay.pm:675
-#. ($cf->Name, $old_content, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 %2 alterado para %3"
-
-#: lib/RT/Record.pm:1711 lib/RT/Transaction_Overlay.pm:671 lib/RT/Transaction_Overlay.pm:717
-#. ($cf->Name, $old_value->Content)
-#. ($field, $self->OldValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 deleted"
-msgstr "%1 %2 apagado"
-
-#: html/Admin/Elements/EditScrips:67 html/Admin/Elements/ListGlobalScrips:65 html/Ticket/Elements/PreviewScrips:105
-#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
-msgid "%1 %2 with template %3"
-msgstr ""
-
-#: html/Ticket/Elements/ShowAttachments:74
-#. ($rev->CreatedAsString, $size, $rev->CreatorObj->Name)
-msgid "%1 (%2) by %3"
-msgstr "%1 (%2) por %3"
-
-#: html/SelfService/Update.html:62 html/Ticket/Elements/EditBasics:110 html/Ticket/Update.html:63 html/Ticket/Update.html:65 html/Tools/MyDay.html:71
-#. (loc($DefaultStatus))
-#. (loc($Ticket->Status()))
-#. (loc($TicketObj->Status))
-#. ($TicketObj->OwnerObj->Name())
-msgid "%1 (Unchanged)"
-msgstr "%1 (inalterado)"
-
-#: bin/rt-crontool:239 bin/rt-crontool:246 bin/rt-crontool:252
-#. ("--search-argument", "--search")
-#. ("--condition-argument", "--condition")
-#. ("--action-argument", "--action")
-msgid "%1 - An argument to pass to %2"
-msgstr ""
-
-#: bin/rt-crontool:264
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr ""
-
-#: bin/rt-crontool:255
-#. ("--template-id")
-msgid "%1 - Specify id of the template you want to use"
-msgstr ""
-
-#: bin/rt-crontool:258
-#. ("--transaction")
-msgid "%1 - Specify if you want to use either 'first' or 'last' transaction"
-msgstr ""
-
-#: bin/rt-crontool:249
-#. ("--action")
-msgid "%1 - Specify the action module you want to use"
-msgstr ""
-
-#: bin/rt-crontool:243
-#. ("--condition")
-msgid "%1 - Specify the condition module you want to use"
-msgstr ""
-
-#: bin/rt-crontool:236
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr ""
-
-#: bin/rt-crontool:261
-#. ("--transaction-type")
-msgid "%1 - Specify the type of a transaction you want to use"
-msgstr "%1 - Especifique o tipo de transacção que quer usar"
-
-#: html/Elements/Footer:58
-#. ('&#187;&#124;&#171;', $RT::VERSION, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',)
-msgid "%1 RT %2 Copyright 1996-%3 %4."
-msgstr ""
-
-#: lib/RT/ScripAction_Overlay.pm:152
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr ""
-
-#: lib/RT/Record.pm:1744
-#. ($args{'Value'}, $cf->Name)
-msgid "%1 added as a value for %2"
-msgstr "%1 adicionado como valor de %2"
-
-#: lib/RT/Link_Overlay.pm:146 lib/RT/Link_Overlay.pm:153
-#. ($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:75 lib/RT/Transaction_Overlay.pm:552
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-msgid "%1 by %2"
-msgstr "%1 por %2"
-
-#: lib/RT/Record.pm:534 lib/RT/Transaction_Overlay.pm:619 lib/RT/Transaction_Overlay.pm:809 lib/RT/Transaction_Overlay.pm:818 lib/RT/Transaction_Overlay.pm:821
-#. ($args{'Field'}, ( $old_val ? "'$old_val'" : $self->loc("(no value)") ), '"' . $self->__Value( $args{'Field'}) . '"')
-#. ($self->Field, ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ), "'" . $self->NewValue . "'")
-#. ($self->Field , $q1->Name , $q2->Name)
-#. ($self->Field, $t2->AsString, $t1->AsString)
-#. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'")
-msgid "%1 changed from %2 to %3"
-msgstr "%1 alterado de %2 para %3"
-
-#: html/Search/Build.html:215
-#. ($Description)
-msgid "%1 copy"
-msgstr "cópia %1"
-
-#: lib/RT/Record.pm:961
-msgid "%1 could not be set to %2."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2789
-#. ($self)
-msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:592
-#. ($obj_type)
-msgid "%1 created"
-msgstr "%1 criado"
-
-#: lib/RT/Transaction_Overlay.pm:597
-#. ($obj_type)
-msgid "%1 deleted"
-msgstr "%1 apagado"
-
-#: etc/initialdata:593
-msgid "%1 highest priority tickets I own"
-msgstr "%1 tickets com maior prioridade da minha responsabilidade"
-
-#: bin/rt-crontool:231
-#. ($0)
-msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:867
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this queue."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1576
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this ticket."
-msgstr ""
-
-#: lib/RT/Record.pm:1801
-#. ($TransactionObj->OldValue, $cf->Name)
-msgid "%1 is no longer a value for custom field %2"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTime:49 html/Ticket/Elements/ShowTime:51
-#. ($minutes)
-msgid "%1 min"
-msgstr "%1 min"
-
-#: etc/initialdata:601
-msgid "%1 newest unowned tickets"
-msgstr "%1 tickets mais recentes sem responsável atribuído"
-
-#: lib/RT/CustomField_Overlay.pm:896
-msgid "%1 objects"
-msgstr ""
-
-#: html/User/Elements/DelegateRights:99
-#. (loc($ObjectType =~ /^RT::(.*)$/))
-msgid "%1 rights"
-msgstr ""
-
-#: lib/RT/Action/ResolveMembers.pm:65
-#. (ref $self)
-msgid "%1 will resolve all members of a resolved group ticket."
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:897
-msgid "%1's %2 objects"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:898
-msgid "%1's %2's %3 objects"
-msgstr ""
-
-#: html/Search/Elements/SearchPrivacy:54 html/Search/Elements/SelectSearchObject:57 html/Search/Elements/SelectSearchesForObjects:59
-#. ($object->Name)
-#. ($Object->Name)
-msgid "%1's saved searches"
-msgstr "Pesquisas gravadas de %1"
-
-#: lib/RT/Transaction_Overlay.pm:502
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1: anexo não especificado"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:80
-#. ($size)
-msgid "%1b"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTransactionAttachments:77
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTime:51
-#. (sprintf("%.1f",$minutes / 60))
-msgid "%quant(%1,hour)"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1144
-#. ($args{'Status'})
-msgid "'%1' is an invalid value for status"
-msgstr ""
-
-#: html/Admin/Elements/EditCustomFieldValues:52 html/Admin/Elements/EditQueueWatchers:52 html/Admin/Elements/EditScrips:58 html/Admin/Elements/EditTemplates:59 html/Admin/Groups/Members.html:75 html/Elements/EditLinks:56 html/Ticket/Elements/EditPeople:69 html/User/Groups/Members.html:78
-msgid "(Check box to delete)"
-msgstr "(Seleccione caixa para apagar)"
-
-#: html/Ticket/Elements/PreviewScrips:101
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr "(Seleccione caixas para desactivar notificações para os destinatários listados)"
-
-#: html/Ticket/Elements/PreviewScrips:125
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr "(Seleccione caixas para activar notificações para os destinatários listados)"
-
-#: html/Ticket/Create.html:221
-msgid "(Enter ticket ids or URLs, separated with spaces)"
-msgstr "(Insira identificadores de tickets, separados por espaços)"
-
-#: html/Admin/Queues/Modify.html:77 html/Admin/Queues/Modify.html:83
-#. ($RT::CorrespondAddress)
-#. ($RT::CommentAddress)
-msgid "(If left blank, will default to %1)"
-msgstr "(Por omissão será %1)"
-
-#: html/Admin/Elements/EditCustomFields:76 html/Admin/Elements/ListGlobalCustomFields:55
-msgid "(No custom fields)"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:73 html/User/Groups/Members.html:76
-msgid "(No members)"
-msgstr "(Sem membros)"
-
-#: html/Admin/Elements/EditScrips:55 html/Admin/Elements/ListGlobalScrips:50
-msgid "(No scrips)"
-msgstr ""
-
-#: html/Admin/Elements/EditTemplates:54
-msgid "(No templates)"
-msgstr ""
-
-#: html/Admin/Elements/PickCustomFields:49 html/Admin/Elements/PickObjects:49
-msgid "(None)"
-msgstr "(Nada)"
-
-#: html/Ticket/Update.html:92
-msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr ""
-
-#: html/Ticket/Create.html:105
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr ""
-
-#: html/Ticket/Update.html:88
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr ""
-
-#: html/Ticket/Create.html:95
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:98
-msgid "(Use these fields when you choose 'User Defined' for a condition or action)"
-msgstr ""
-
-#: html/Ticket/Elements/EditWatchers:62 html/Ticket/Elements/ShowUserEntry:55
-msgid "(Will not be sent email)"
-msgstr "(Não vai ser enviado email)"
-
-#: html/Tools/MyDay.html:53
-#. ($session{'CurrentUser'}->Name)
-msgid "(displaying new and open tickets for %1)"
-msgstr "(mostrar tickets novos e abertos de %1)"
-
-#: html/Admin/Groups/index.html:59 html/User/Groups/index.html:56
-msgid "(empty)"
-msgstr "(vazio)"
-
-#: html/Admin/Users/index.html:62
-msgid "(no name listed)"
-msgstr "(sem nome)"
-
-#: html/Admin/Elements/SelectRights:74 html/Elements/EditCustomFieldSelect:71 html/Elements/SelectCustomFieldValue:53 html/Elements/ShowCustomFields:56 html/Search/Chart:134 html/Search/Elements/Chart:78 lib/RT/Transaction_Overlay.pm:612
-msgid "(no value)"
-msgstr "(sem valor)"
-
-#: html/Admin/Elements/EditCustomFieldValues:49
-msgid "(no values)"
-msgstr "(sem valores)"
-
-#: html/Elements/EditLinks:133 html/Ticket/Elements/BulkLinks:51
-msgid "(only one ticket)"
-msgstr "(apenas um ticket)"
-
-#: html/Elements/RT__Ticket/ColumnMap:151
-msgid "(pending approval)"
-msgstr "(aprovações pendentes)"
-
-#: html/Elements/RT__Ticket/ColumnMap:154
-msgid "(pending other Collection)"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:73
-msgid "(required)"
-msgstr "(obrigatório)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:84
-msgid "(untitled)"
-msgstr ""
-
-#: html/Ticket/Elements/Reminders:135
-msgid "(yyyy/mm/dd)"
-msgstr ""
-
-#: html/Elements/EditCustomFieldSelect:59
-msgid "-"
-msgstr ""
-
-#: bin/rt-crontool:97
-msgid "--transaction argument could be only 'first' or 'last'"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:55
-msgid "<% $Ticket->Status%>"
-msgstr "<% $Ticket->Status%>"
-
-#: html/Elements/SelectTicketTypes:50
-msgid "<% $_ %>"
-msgstr "<% $_ %>"
-
-#: html/Search/Elements/SelectLinks:50
-msgid "<%$_%>"
-msgstr "<%$_%>"
-
-#: html/Search/Elements/DisplayOptions:75
-msgid "<%$field%>"
-msgstr "<%$field%>"
-
-#: html/Elements/CreateTicket:49
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" class=\"button\" value=\"New ticket in\" />&nbsp;%1"
-msgstr "<input type=\"submit\" class=\"button\" value=\"Novo Pedido em\" />&nbsp;%1"
-
-#: docs/design_docs/string-extraction-guide.txt:54 lib/RT/StyleGuide.pod:785
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
-msgstr "<input type=\"submit\" value=\"Novo Pedido em\">&nbsp;%1"
-
-#: etc/initialdata:218
-msgid "A blank template"
-msgstr "Template em branco"
-
-#: html/Admin/Users/Modify.html:375
-msgid "A password was not set, so user won't be able to login."
-msgstr "Password não foi definida, portanto o utilizador não vai conseguir efectuar login."
-
-#: lib/RT/ACE_Overlay.pm:176 lib/RT/Principal_Overlay.pm:221
-msgid "ACE not found"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:855
-msgid "ACEs can only be created and deleted."
-msgstr ""
-
-#: html/Search/Elements/SelectAndOr:48
-msgid "AND"
-msgstr "E"
-
-#: html/User/Elements/Tabs:55
-msgid "About me"
-msgstr "Sobre mim"
-
-#: html/Admin/Users/Modify.html:108
-msgid "Access control"
-msgstr "Controle de Acesso"
-
-#: html/Admin/Elements/EditScrip:67
-msgid "Action"
-msgstr "Acção"
-
-#: lib/RT/Scrip_Overlay.pm:174
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "Acção %1 não encontrada"
-
-#: bin/rt-crontool:173
-msgid "Action committed.\\n"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:170
-msgid "Action is mandatory argument"
-msgstr ""
-
-#: bin/rt-crontool:169
-msgid "Action prepared..."
-msgstr ""
-
-#: html/Search/Build.html:87
-msgid "Add"
-msgstr "Adicionar"
-
-#: html/Search/Bulk.html:94
-msgid "Add AdminCc"
-msgstr "Adicionar AdminCc"
-
-#: html/Search/Bulk.html:90
-msgid "Add Cc"
-msgstr "Adicionar Cc"
-
-#: html/Search/Elements/EditFormat:51
-msgid "Add Columns"
-msgstr "Adicionar colunas"
-
-#: html/Search/Elements/PickCriteria:48
-msgid "Add Criteria"
-msgstr "Adicionar critérios"
-
-#: html/Ticket/Create.html:149 html/Ticket/Update.html:118
-msgid "Add More Files"
-msgstr "Adicionar mais ficheiros"
-
-#: html/Search/Bulk.html:86
-msgid "Add Requestor"
-msgstr ""
-
-#: html/Admin/Elements/AddCustomFieldValue:48
-msgid "Add Value"
-msgstr ""
-
-#: html/Admin/Global/Scrip.html:85
-msgid "Add a scrip which will apply to all queues"
-msgstr ""
-
-#: html/Search/Build.html:111 html/Search/Build.html:96
-msgid "Add and Search"
-msgstr "Adicionar e pesquisar"
-
-#: html/Search/Bulk.html:126
-msgid "Add comments or replies to selected tickets"
-msgstr "Adicionar comentários ou respostas aos tickets seleccionados"
-
-#: html/Admin/Groups/Members.html:65 html/User/Groups/Members.html:62
-msgid "Add members"
-msgstr "Adicionar membros"
-
-#: html/Admin/Queues/People.html:89 html/Ticket/Elements/AddWatchers:51
-msgid "Add new watchers"
-msgstr "Adicionar novos watchers"
-
-#: html/Search/Build.html:87
-msgid "Add these terms to your search"
-msgstr "Adicionar estes termos à sua pesquisa"
-
-#: html/Search/Bulk.html:160
-msgid "Add values"
-msgstr "Adicionar valores"
-
-#: lib/RT/CustomField_Overlay.pm:110
-msgid "Add, delete and modify custom field values for objects"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:758
-#. ($args{'Type'})
-msgid "Added principal as a %1 for this queue"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1448
-#. ($self->loc($args{'Type'}))
-msgid "Added principal as a %1 for this ticket"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:149 html/User/Prefs.html:135
-msgid "Address1"
-msgstr "Endereço (1)"
-
-#: html/Admin/Users/Modify.html:154 html/User/Prefs.html:139
-msgid "Address2"
-msgstr "Endereço (2)"
-
-#: html/Ticket/Create.html:100
-msgid "Admin Cc"
-msgstr ""
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "Comentário de Admin"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr ""
-
-#: html/Admin/Queues/index.html:48 html/Admin/Queues/index.html:51
-msgid "Admin queues"
-msgstr ""
-
-#: html/Admin/Global/index.html:49 html/Admin/Global/index.html:51
-msgid "Admin/Global configuration"
-msgstr ""
-
-#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:62 lib/RT/ACE_Overlay.pm:115
-msgid "AdminCc"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "AdminCustomField"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "AdminGroup"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "AdminGroupMembership"
-msgstr ""
-
-#: lib/RT/System.pm:82
-msgid "AdminOwnPersonalGroups"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "AdminQueue"
-msgstr ""
-
-#: lib/RT/System.pm:83
-msgid "AdminUsers"
-msgstr ""
-
-#: html/Admin/Queues/People.html:71 html/Ticket/Elements/EditPeople:77
-msgid "Administrative Cc"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:218
-msgid "Advanced"
-msgstr "Avançado"
-
-#: html/Elements/SelectDateRelation:59
-msgid "After"
-msgstr ""
-
-#: html/Search/Elements/PickCriteria:54
-msgid "Aggregator"
-msgstr "Agregador"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr "Todas as aprovações tratadas"
-
-#: html/Admin/Queues/index.html:77
-msgid "All Queues"
-msgstr "Todas as Queues"
-
-#: html/Search/Elements/EditQuery:58
-msgid "And/Or"
-msgstr "E/Ou"
-
-#: html/Admin/CustomFields/Modify.html:75 html/Admin/Elements/CustomFieldTabs:85
-msgid "Applies to"
-msgstr "Aplica-se a"
-
-#: html/Search/Edit.html:66
-msgid "Apply"
-msgstr "Aplicar"
-
-#: html/Search/Edit.html:66
-msgid "Apply your changes"
-msgstr "Aplicar as alterações"
-
-#: html/Elements/Tabs:80
-msgid "Approval"
-msgstr "Aprovação"
-
-#: html/Approvals/Display.html:67 html/Approvals/Elements/ShowDependency:65 html/Approvals/index.html:88
-#. ($Ticket->Id, $Ticket->Subject)
-#. ($ticket->id, $msg)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Approval #%1: %2"
-msgstr "Aprovação #%1: %2"
-
-#: html/Approvals/index.html:77
-#. ($ticket->Id)
-msgid "Approval #%1: Notes not recorded due to a system error"
-msgstr ""
-
-#: html/Approvals/index.html:75
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr ""
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr ""
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr ""
-
-#: html/Approvals/Elements/Approve:71
-msgid "Approve"
-msgstr ""
-
-#: etc/initialdata:504 etc/initialdata:506
-#. (# loc $note)
-msgid "Approver's notes: %1"
-msgstr ""
-
-#: lib/RT/Date.pm:446
-msgid "Apr."
-msgstr "Apr"
-
-#: html/Search/Elements/DisplayOptions:83
-msgid "Asc"
-msgstr "Asc"
-
-#: html/Elements/SelectSortOrder:58
-msgid "Ascending"
-msgstr "Ascendente"
-
-#: lib/RT/Queue_Overlay.pm:98
-msgid "Assign and remove custom fields"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:98
-msgid "AssignCustomFields"
-msgstr ""
-
-#: html/Search/Bulk.html:144 html/SelfService/Update.html:89 html/Ticket/ModifyAll.html:117 html/Ticket/Update.html:118
-msgid "Attach"
-msgstr "Anexar"
-
-#: html/SelfService/Create.html:94 html/Ticket/Create.html:145
-msgid "Attach file"
-msgstr "Anexar ficheiro"
-
-#: html/SelfService/Update.html:77 html/Ticket/Create.html:133 html/Ticket/Update.html:96
-msgid "Attached file"
-msgstr "Ficheiro anexo"
-
-#: html/Ticket/ShowEmailRecord.html:54 html/Ticket/ShowEmailRecord.html:58 html/Ticket/ShowEmailRecord.html:61
-#. ($Attachment)
-msgid "Attachment '%1' could not be loaded"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:510
-msgid "Attachment created"
-msgstr "Anexo criado"
-
-#: lib/RT/Tickets_Overlay.pm:2134
-msgid "Attachment filename"
-msgstr ""
-
-#: html/Ticket/Elements/ShowAttachments:49
-msgid "Attachments"
-msgstr "Anexos"
-
-#: lib/RT/Attributes_Overlay.pm:173
-msgid "Attribute Deleted"
-msgstr ""
-
-#: lib/RT/Date.pm:450
-msgid "Aug."
-msgstr "Aug"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "Resposta automática"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "Resposta automática para utilizadores"
-
-#: html/Widgets/SelectionBox:191
-msgid "Available"
-msgstr "Disponível"
-
-#: html/Admin/Elements/CustomFieldTabs:67 html/Admin/Elements/GroupTabs:62 html/Admin/Elements/QueueTabs:62 html/Admin/Elements/UserTabs:60 html/Ticket/Elements/Tabs:115 html/User/Elements/GroupTabs:61
-msgid "Basics"
-msgstr "Informação básica"
-
-#: html/Ticket/Update.html:90
-msgid "Bcc"
-msgstr "Bcc"
-
-#: html/Admin/CustomFields/GroupRights.html:93 html/Admin/CustomFields/UserRights.html:76 html/Admin/Elements/EditScrip:91
-msgid "Be sure to save your changes"
-msgstr ""
-
-#: html/Elements/SelectDateRelation:57 lib/RT/CurrentUser.pm:363
-msgid "Before"
-msgstr "Antes"
-
-#: html/Elements/Logo:49
-msgid "Best Practical Solutions, LLC corporate logo"
-msgstr ""
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "Branco"
-
-#: html/Search/Elements/EditFormat:86
-msgid "Bold"
-msgstr ""
-
-#: html/Search/Results.html:81
-msgid "Bookmarkable link"
-msgstr ""
-
-#: html/Ticket/Elements/ShowHistory:66 html/Ticket/Elements/ShowHistory:71
-msgid "Brief headers"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:228
-msgid "Bulk Update"
-msgstr "Actualização em bloco"
-
-#: lib/RT/User_Overlay.pm:1855
-msgid "Can not modify system users"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "Can this principal see this queue"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:382
-msgid "Can't add a custom field value without a name"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:88
-#. ($Class)
-msgid "Can't find a collection class for '%1'"
-msgstr ""
-
-#: html/Search/Build.html:288
-msgid "Can't find a saved search to work with"
-msgstr "Impossível encontrar a pesquisa gravada definida"
-
-#: lib/RT/Link_Overlay.pm:161
-msgid "Can't link a ticket to itself"
-msgstr ""
-
-#: html/Widgets/SavedSearch:110
-#. (loc($self->{SearchType}))
-msgid "Can't save %1"
-msgstr ""
-
-#: html/Search/Build.html:292
-msgid "Can't save this search"
-msgstr "Não é possível gravar esta pesquisa"
-
-#: lib/RT/Record.pm:1304 lib/RT/Record.pm:1380
-msgid "Can't specifiy both base and target"
-msgstr ""
-
-#: html/autohandler:206
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "Não é possível criar utilizador: %1"
-
-#: html/Admin/Elements/AddCustomFieldValue:64 html/Admin/Elements/EditCustomFieldValues:60
-msgid "Category"
-msgstr "Categoria"
-
-#: etc/initialdata:50 html/Admin/Queues/People.html:67 html/SelfService/Create.html:73 html/Ticket/Create.html:90 html/Ticket/Elements/EditPeople:74 html/Ticket/Elements/ShowPeople:58 html/Ticket/Update.html:85 lib/RT/ACE_Overlay.pm:114
-msgid "Cc"
-msgstr ""
-
-#: html/SelfService/Prefs.html:54
-msgid "Change password"
-msgstr "Mudar password"
-
-#: html/Elements/Submit:80
-msgid "Check All"
-msgstr "Seleccionar todos"
-
-#: html/SelfService/Update.html:80 html/Ticket/Create.html:136 html/Ticket/Update.html:99
-msgid "Check box to delete"
-msgstr "Seleccione caixa para apagar"
-
-#: html/Admin/Elements/SelectRights:57
-msgid "Check box to revoke right"
-msgstr ""
-
-#: html/Elements/EditLinks:149 html/Elements/EditLinks:86 html/Elements/ShowLinks:80 html/Ticket/Create.html:226 html/Ticket/Elements/BulkLinks:66
-msgid "Children"
-msgstr "Filhos"
-
-#: html/NoAuth/js/util.js:203
-msgid "Choose a date"
-msgstr "Escolha uma data"
-
-#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:143
-msgid "City"
-msgstr "Cidade"
-
-#: html/Widgets/SelectionBox:214
-msgid "Clear"
-msgstr ""
-
-#: html/Elements/Submit:82
-msgid "Clear All"
-msgstr "Limpar todos"
-
-#: html/Helpers/CalPopup.html:53
-msgid "Close window"
-msgstr "Fechar janela"
-
-#: html/Ticket/Elements/ShowDates:70
-msgid "Closed"
-msgstr "Fechado"
-
-#: html/SelfService/Closed.html:48 html/SelfService/Elements/Tabs:81
-msgid "Closed tickets"
-msgstr "Tickets fechados"
-
-#: lib/RT/CustomField_Overlay.pm:91
-msgid "Combobox: Select or enter multiple values"
-msgstr "Seleccione ou insira valores múltiplos"
-
-#: lib/RT/CustomField_Overlay.pm:92
-msgid "Combobox: Select or enter one value"
-msgstr "Seleccione ou insira um valor"
-
-#: lib/RT/CustomField_Overlay.pm:93
-msgid "Combobox: Select or enter up to %1 values"
-msgstr "Seleccione ou insira até %1 valores"
-
-#: html/Ticket/Elements/ShowTransaction:193 html/Ticket/Elements/Tabs:187
-msgid "Comment"
-msgstr "Comentário"
-
-#: html/Admin/Queues/Modify.html:81
-msgid "Comment Address"
-msgstr "Morada de Comentário"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "Comment on tickets"
-msgstr "Comentar tickets"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "CommentOnTicket"
-msgstr ""
-
-#: html/Tools/MyDay.html:67
-msgid "Comments"
-msgstr "Comentários"
-
-#: html/Ticket/ModifyAll.html:93 html/Ticket/Update.html:77
-msgid "Comments (Not sent to requestors)"
-msgstr "Comentários (não so enviados para utilizadores)"
-
-#: html/Search/Bulk.html:130
-msgid "Comments (not sent to requestors)"
-msgstr "Comentários (não so enviados para utilizadores)"
-
-#: html/Admin/Users/Modify.html:229 html/Ticket/Elements/ShowRequestor:69
-msgid "Comments about this user"
-msgstr "Comentários sobre este utilizador"
-
-#: lib/RT/Transaction_Overlay.pm:655
-msgid "Comments added"
-msgstr "Comentários adicionados"
-
-#: lib/RT/Action/Generic.pm:177
-msgid "Commit Stubbed"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:61
-msgid "Condition"
-msgstr "Condição"
-
-#: lib/RT/Scrip_Overlay.pm:186
-msgid "Condition is mandatory argument"
-msgstr "Condição é argumento obrigatório"
-
-#: bin/rt-crontool:153
-msgid "Condition matches..."
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:190
-msgid "Condition not found"
-msgstr ""
-
-#: html/Elements/Tabs:87
-msgid "Configuration"
-msgstr "Configuração"
-
-#: html/SelfService/Prefs.html:56
-msgid "Confirm"
-msgstr "Confirmar"
-
-#: html/Admin/Elements/ModifyTemplate:67 html/Elements/SelectAttachmentField:50 html/Ticket/ModifyAll.html:121
-msgid "Content"
-msgstr "Conteúdo"
-
-#: html/Elements/SelectAttachmentField:51
-msgid "Content-Type"
-msgstr ""
-
-#: html/Search/Elements/EditSearches:67
-msgid "Copy"
-msgstr "Copiar"
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "Correspondência"
-
-#: lib/RT/Transaction_Overlay.pm:651
-msgid "Correspondence added"
-msgstr "Correspondência adicionada"
-
-#: lib/RT/Record.pm:1682 lib/RT/Record.pm:1729
-#. ($value_msg)
-msgid "Could not add new custom field value: %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3062 lib/RT/Ticket_Overlay.pm:3070 lib/RT/Ticket_Overlay.pm:3087
-msgid "Could not change owner. "
-msgstr "Não foi possível alterar responsável"
-
-#: html/Admin/CustomFields/Modify.html:163
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr ""
-
-#: html/Admin/Elements/EditCustomField:115
-#. ($msg)
-msgid "Could not create CustomField: %1"
-msgstr ""
-
-#: html/User/Groups/Modify.html:100 lib/RT/Group_Overlay.pm:496 lib/RT/Group_Overlay.pm:503
-msgid "Could not create group"
-msgstr ""
-
-#: html/Admin/Global/Template.html:94 html/Admin/Queues/Template.html:95
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1077 lib/RT/Ticket_Overlay.pm:409
-msgid "Could not create ticket. Queue not set"
-msgstr "Não é possível criar pedido. Especifique uma fila."
-
-#: lib/RT/User_Overlay.pm:257 lib/RT/User_Overlay.pm:271 lib/RT/User_Overlay.pm:280 lib/RT/User_Overlay.pm:289 lib/RT/User_Overlay.pm:298 lib/RT/User_Overlay.pm:312 lib/RT/User_Overlay.pm:322 lib/RT/User_Overlay.pm:498
-msgid "Could not create user"
-msgstr "Não foi possível criar utilizador"
-
-#: lib/RT/Queue_Overlay.pm:737 lib/RT/Ticket_Overlay.pm:1416
-msgid "Could not find or create that user"
-msgstr "Não foi possível criar ou encontrar esse utilizador"
-
-#: lib/RT/Queue_Overlay.pm:804 lib/RT/Ticket_Overlay.pm:1497
-msgid "Could not find that principal"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:71
-msgid "Could not load CustomField %1"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:114 html/User/Groups/Members.html:113 html/User/Groups/Modify.html:105
-msgid "Could not load group"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:121
-#. ($privacy)
-msgid "Could not load object for %1"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:199
-msgid "Could not load search attribute"
-msgstr "Não é possível carregar o atributo da pesquisa"
-
-#: lib/RT/Queue_Overlay.pm:756
-#. ($args{'Type'})
-msgid "Could not make that principal a %1 for this queue"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1437
-#. ($self->loc($args{'Type'}))
-msgid "Could not make that principal a %1 for this ticket"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:864
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this queue"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1564
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this ticket"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:193
-msgid "Could not set user info"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:161
-msgid "Couldn't add attachment"
-msgstr "Não foi possível adicionar anexo"
-
-#: lib/RT/Group_Overlay.pm:1005
-msgid "Couldn't add member to group"
-msgstr "Não foi possível adicionar membro a grupo"
-
-#: lib/RT/Record.pm:1741 lib/RT/Record.pm:1793
-#. ($Msg)
-msgid "Couldn't create a transaction: %1"
-msgstr "Não foi possível criar uma transacção: %1"
-
-#: lib/RT/Record.pm:970
-msgid "Couldn't find row"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:979
-msgid "Couldn't find that principal"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:412
-msgid "Couldn't find that value"
-msgstr ""
-
-#: lib/RT/CurrentUser.pm:147
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr ""
-
-#: html/Admin/CustomFields/UserRights.html:151
-#. ($id)
-msgid "Couldn't load Class %1"
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:109
-#. ($id)
-msgid "Couldn't load CustomField %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2009
-#. ($self->Id)
-msgid "Couldn't load copy of ticket #%1."
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:111 html/Admin/Groups/UserRights.html:98
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr ""
-
-#: lib/RT/Link_Overlay.pm:204 lib/RT/Link_Overlay.pm:213 lib/RT/Link_Overlay.pm:240
-msgid "Couldn't load link"
-msgstr ""
-
-#: html/Admin/Elements/ObjectCustomFields:85 html/Admin/Queues/CustomFields.html:61 html/Admin/Users/CustomFields.html:61
-#. ($id)
-msgid "Couldn't load object %1"
-msgstr ""
-
-#: html/Admin/Queues/People.html:144
-#. ($id)
-msgid "Couldn't load queue"
-msgstr ""
-
-#: html/Admin/Queues/GroupRights.html:124 html/Admin/Queues/UserRights.html:95
-#. ($id)
-msgid "Couldn't load queue %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:128 html/Admin/Elements/EditScrip:169
-#. ($id)
-msgid "Couldn't load scrip #%1"
-msgstr ""
-
-#: html/SelfService/Display.html:160 lib/RT/Action/CreateTickets.pm:682
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2637
-#. ($args{'URI'})
-msgid "Couldn't resolve '%1' into a URI."
-msgstr ""
-
-#: lib/RT/Link_Overlay.pm:113
-#. ($args{'Base'})
-msgid "Couldn't resolve base '%1' into a URI."
-msgstr ""
-
-#: lib/RT/Link_Overlay.pm:128
-#. ($args{'Target'})
-msgid "Couldn't resolve target '%1' into a URI."
-msgstr ""
-
-#: html/Admin/Users/Modify.html:176 html/User/Prefs.html:155
-msgid "Country"
-msgstr "País"
-
-#: html/Admin/Elements/CreateUserCalled:49 html/Admin/Elements/EditCustomField:86 html/Admin/Elements/EditScrip:135 html/Admin/Global/Template.html:67 html/Admin/Queues/Template.html:68 html/Elements/QuickCreate:67 html/Ticket/Create.html:171 html/Ticket/Create.html:238
-msgid "Create"
-msgstr "Criar"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "Criar tickets"
-
-#: html/Admin/CustomFields/Modify.html:152 html/Admin/Elements/EditCustomField:98
-msgid "Create a CustomField"
-msgstr ""
-
-#: html/Admin/Queues/CustomField.html:71
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr ""
-
-#: html/Admin/Groups/Modify.html:105 html/Admin/Groups/Modify.html:131
-msgid "Create a new group"
-msgstr "Criar novo grupo"
-
-#: html/User/Groups/Modify.html:115 html/User/Groups/Modify.html:90
-msgid "Create a new personal group"
-msgstr ""
-
-#: html/Ticket/Create.html:49 html/Ticket/Create.html:53 html/Ticket/Create.html:62
-msgid "Create a new ticket"
-msgstr "Criar novo ticket"
-
-#: html/Admin/Users/Modify.html:256 html/Admin/Users/Modify.html:318
-msgid "Create a new user"
-msgstr "Criar novo utilizador"
-
-#: html/Admin/Queues/Modify.html:127
-msgid "Create a queue"
-msgstr "Criar queue"
-
-#: html/Admin/Queues/Scrip.html:91
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr ""
-
-#: html/Admin/Global/Template.html:87 html/Admin/Queues/Template.html:88
-msgid "Create a template"
-msgstr "Criar template"
-
-#: html/SelfService/Create.html:48 html/SelfService/CreateTicketInQueue.html:48
-msgid "Create a ticket"
-msgstr "Criar ticket"
-
-#: etc/initialdata:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "Criar novo pedido baseado num modelo existente"
-
-#: html/SelfService/Create.html:107
-msgid "Create ticket"
-msgstr "Criar ticket"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "Create tickets in this queue"
-msgstr "Criar tickets nesta queue"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "Create, delete and modify custom fields"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "Create, delete and modify queues"
-msgstr ""
-
-#: lib/RT/System.pm:82
-msgid "Create, delete and modify the members of personal groups"
-msgstr ""
-
-#: lib/RT/System.pm:83
-msgid "Create, delete and modify users"
-msgstr ""
-
-#: lib/RT/System.pm:89
-msgid "CreateSavedSearch"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "CreateTicket"
-msgstr ""
-
-#: html/Elements/SelectDateType:49 html/Ticket/Elements/ShowDates:50 lib/RT/Ticket_Overlay.pm:1171
-msgid "Created"
-msgstr "Criado"
-
-#: html/Admin/CustomFields/Modify.html:165 html/Admin/Elements/EditCustomField:119
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "Campo Personalisado %1 criado"
-
-#: html/Tools/Reports/Elements/Tabs:65
-msgid "Created in a date range"
-msgstr ""
-
-#: html/Tools/Reports/CreatedByDates.html:54
-msgid "Created tickets in period, grouped by status"
-msgstr "Pedidos criados dentro de um periodo de tempo, agrupados por estado"
-
-#: html/Search/Elements/PickBasics:104
-msgid "Creator"
-msgstr ""
-
-#: html/Elements/EditLinks:51
-msgid "Current Links"
-msgstr "Links actuais"
-
-#: html/Admin/Elements/EditScrips:53
-msgid "Current Scrips"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:62 html/User/Groups/Members.html:65
-msgid "Current members"
-msgstr "Membros actuais"
-
-#: html/Admin/Elements/SelectRights:53
-msgid "Current rights"
-msgstr "Direitos actuais"
-
-#: html/Search/Elements/EditQuery:49
-msgid "Current search"
-msgstr "Pesquisa actual"
-
-#: html/Admin/Queues/People.html:64 html/Ticket/Elements/EditPeople:68
-msgid "Current watchers"
-msgstr ""
-
-#: html/Admin/Elements/SystemTabs:63 html/Admin/Elements/Tabs:64 html/Admin/Global/index.html:73 html/Admin/Users/Modify.html:209 html/Admin/index.html:79 html/Ticket/Elements/ShowSummary:58
-msgid "Custom Fields"
-msgstr ""
-
-#: html/Admin/CustomFields/index.html:62
-#. ($lookup)
-msgid "Custom Fields for %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:109
-msgid "Custom action cleanup code"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:105
-msgid "Custom action preparation code"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:101
-msgid "Custom condition"
-msgstr ""
-
-#: lib/RT/Tickets_Overlay.pm:2619
-#. ($CF->Name, $args{OPERATOR}, $args{VALUE})
-msgid "Custom field %1 %2 %3"
-msgstr ""
-
-#: lib/RT/Record.pm:1625
-#. ($args{'Field'})
-msgid "Custom field %1 does not apply to this object"
-msgstr ""
-
-#: lib/RT/Tickets_Overlay.pm:2613
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr ""
-
-#: lib/RT/Tickets_Overlay.pm:2609
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr ""
-
-#: lib/RT/Record.pm:1614 lib/RT/Record.pm:1776
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr ""
-
-#: lib/RT/Report/Tickets.pm:120 lib/RT/Report/Tickets.pm:123
-#. ($cf)
-#. ($obj->Name)
-msgid "Custom field '%1'"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:1160
-#. ($args{'Content'}, $self->Name)
-msgid "Custom field value %1 could not be found for custom field %2"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:422
-msgid "Custom field value could not be deleted"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:1172
-msgid "Custom field value could not be found"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:1174 lib/RT/CustomField_Overlay.pm:420
-msgid "Custom field value deleted"
-msgstr ""
-
-#: html/Elements/SelectGroups:53 html/Elements/SelectUsers:53 lib/RT/Transaction_Overlay.pm:659
-msgid "CustomField"
-msgstr ""
-
-#: html/Prefs/MyRT.html:80 html/Prefs/Quicksearch.html:72 html/Prefs/Search.html:77
-msgid "Customize"
-msgstr ""
-
-#: html/SelfService/Display.html:63 html/Ticket/Create.html:206 html/Ticket/Elements/ShowSummary:91 html/Ticket/Elements/Tabs:118 html/Ticket/ModifyAll.html:67
-msgid "Dates"
-msgstr "Datas"
-
-#: lib/RT/Date.pm:454
-msgid "Dec."
-msgstr "Dec"
-
-#: etc/initialdata:222
-msgid "Default Autoresponse template"
-msgstr ""
-
-#: html/Tools/Offline.html:63
-msgid "Default Queue"
-msgstr "Queue por omissão"
-
-#: html/Tools/Offline.html:72
-msgid "Default Requestor"
-msgstr ""
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr ""
-
-#: etc/initialdata:275
-msgid "Default admin correspondence template"
-msgstr ""
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr ""
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:637
-#. ($type, $self->Field, ( $self->OldValue ? "'" . $self->OldValue . "'" : $self->loc("(no value)") ), "'" . $self->NewValue . "'")
-msgid "Default: %1/%2 changed from %3 to %4"
-msgstr ""
-
-#: html/User/Delegation.html:48 html/User/Delegation.html:51
-msgid "Delegate rights"
-msgstr ""
-
-#: lib/RT/System.pm:86
-msgid "Delegate specific rights which have been granted to you."
-msgstr ""
-
-#: lib/RT/System.pm:86
-msgid "DelegateRights"
-msgstr ""
-
-#: html/User/Elements/Tabs:61
-msgid "Delegation"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:77 html/Search/Elements/EditFormat:105 html/Search/Elements/EditQuery:59 html/Search/Elements/EditSearches:65 html/Widgets/SelectionBox:212
-msgid "Delete"
-msgstr "Apagar"
-
-#: html/Admin/Elements/EditTemplates:81
-msgid "Delete Template"
-msgstr "Apagar template"
-
-#: lib/RT/SavedSearch.pm:222
-#. ($msg)
-msgid "Delete failed: %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:76
-msgid "Delete selected scrips"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:116
-msgid "Delete tickets"
-msgstr "Apagar tickets"
-
-#: html/Search/Bulk.html:161
-msgid "Delete values"
-msgstr "Apagar valores"
-
-#: lib/RT/Queue_Overlay.pm:116
-msgid "DeleteTicket"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:220
-msgid "Deleted search"
-msgstr "Pesquisa apagada"
-
-#: lib/RT/Queue_Overlay.pm:396
-msgid "Deleting this object would break referential integrity"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:514
-msgid "Deleting this object would violate referential integrity"
-msgstr ""
-
-#: html/Approvals/Elements/Approve:75
-msgid "Deny"
-msgstr ""
-
-#: html/Elements/EditLinks:141 html/Elements/EditLinks:68 html/Elements/ShowLinks:60 html/Ticket/Create.html:224 html/Ticket/Elements/BulkLinks:58 html/Ticket/Elements/ShowDependencies:55
-msgid "Depended on by"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:739
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:779
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:736
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:776
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr ""
-
-#: html/Elements/EditLinks:137 html/Elements/EditLinks:59 html/Elements/SelectLinkType:50 html/Elements/ShowLinks:50 html/Ticket/Create.html:223 html/Ticket/Elements/BulkLinks:54 html/Ticket/Elements/ShowDependencies:48
-msgid "Depends on"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:88
-msgid "Desc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:58
-msgid "Descending"
-msgstr "Descendente"
-
-#: html/SelfService/Create.html:102 html/Ticket/Create.html:154
-msgid "Describe the issue below"
-msgstr "Descreva o pedido, abaixo"
-
-#: html/Admin/CustomFields/Modify.html:63 html/Admin/Elements/AddCustomFieldValue:59 html/Admin/Elements/EditCustomField:62 html/Admin/Elements/EditCustomFieldValues:58 html/Admin/Elements/EditScrip:57 html/Admin/Elements/ModifyTemplate:59 html/Admin/Groups/Modify.html:73 html/Admin/Queues/Modify.html:71 html/Search/Elements/EditSearches:58 html/User/Groups/Modify.html:72
-msgid "Description"
-msgstr "Descrição"
-
-#: html/Search/Elements/EditFormat:73 html/Ticket/Elements/Tabs:110
-msgid "Display"
-msgstr "Mostrar"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "Display Access Control List"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:48
-msgid "Display Columns"
-msgstr "Visualizar Colunas"
-
-#: lib/RT/Queue_Overlay.pm:101
-msgid "Display Scrip templates for this queue"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "Display Scrips for this queue"
-msgstr ""
-
-#: html/Ticket/Elements/ShowHistory:61
-msgid "Display mode"
-msgstr "Modo de visualização"
-
-#: lib/RT/Group_Overlay.pm:170
-msgid "Display saved searches for this group"
-msgstr "Visualizar a pesquisa gravada para este grupo"
-
-#: html/Elements/Footer:63
-msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>"
-msgstr ""
-
-#: lib/RT/System.pm:77
-msgid "Do anything and everything"
-msgstr ""
-
-#: html/Elements/Refresh:53
-msgid "Don't refresh this page."
-msgstr ""
-
-#: html/Ticket/Elements/ShowTransactionAttachments:84
-msgid "Download"
-msgstr "Descarregar"
-
-#: html/Admin/Groups/index.html:63 html/Admin/Users/index.html:66
-msgid "Download as a tab-delimited file"
-msgstr "Descarregar num ficheiro separado por tabs"
-
-#: html/Elements/SelectDateType:55 html/Ticket/Create.html:212 html/Ticket/Elements/EditDates:68 html/Ticket/Elements/Reminders:135 html/Ticket/Elements/ShowDates:66 lib/RT/Ticket_Overlay.pm:1175
-msgid "Due"
-msgstr "Prazo"
-
-#: html/Elements/Quicksearch:50 html/Elements/ShowSearch:51 html/index.html:109
-msgid "Edit"
-msgstr "Editar"
-
-#: html/Search/Bulk.html:151
-msgid "Edit Custom Fields"
-msgstr "Editar Campos Personalizados"
-
-#: html/Admin/Elements/ObjectCustomFields:94 html/Admin/Queues/CustomFields.html:66 html/Admin/Users/CustomFields.html:66
-#. ($Object->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "Editar Campos Personalizados de %1"
-
-#: html/Admin/Global/CustomFields/Groups.html:56
-msgid "Edit Custom Fields for all groups"
-msgstr "Editar Campos Personalizados para todos os grupos"
-
-#: html/Admin/Global/CustomFields/Users.html:56
-msgid "Edit Custom Fields for all users"
-msgstr "Editar Campos Personalizados para todos os utilizadores"
-
-#: html/Admin/Global/CustomFields/Queue-Tickets.html:56 html/Admin/Global/CustomFields/Queue-Transactions.html:56
-msgid "Edit Custom Fields for tickets in all queues"
-msgstr "Editar \"Campos Personalizados\" para todos os pedidos"
-
-#: html/Search/Bulk.html:190 html/Ticket/ModifyLinks.html:59
-msgid "Edit Links"
-msgstr "Editar ligações"
-
-#: html/Search/Edit.html:70
-msgid "Edit Query"
-msgstr "Editar Pesquisa"
-
-#: html/Ticket/Elements/Tabs:216
-msgid "Edit Search"
-msgstr "Editar Pesquisa"
-
-#: html/Admin/Queues/Templates.html:65
-#. ($QueueObj->Name)
-msgid "Edit Templates for queue %1"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "Edit saved searches for this group"
-msgstr "Editar Pesquisas para este grupo"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:62 html/Admin/Global/index.html:69
-msgid "Edit system templates"
-msgstr "Editar modelos de sistema"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "EditSavedSearches"
-msgstr ""
-
-#: html/Admin/Queues/Modify.html:142
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:169 html/Admin/Elements/EditCustomField:122
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr ""
-
-#: html/Admin/Groups/Members.html:57
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr ""
-
-#: html/User/Groups/Members.html:152
-#. ($Group->Name)
-msgid "Editing membership for personal group %1"
-msgstr ""
-
-#: lib/RT/Record.pm:1317 lib/RT/Record.pm:1394 lib/RT/Ticket_Overlay.pm:2512 lib/RT/Ticket_Overlay.pm:2602
-msgid "Either base or target must be specified"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:76 html/Ticket/Elements/AddWatchers:79 html/User/Prefs.html:67
-msgid "Email"
-msgstr "Email"
-
-#: lib/RT/User_Overlay.pm:237
-msgid "Email address in use"
-msgstr "Endereço de email já utilizado"
-
-#: html/Admin/CustomFields/Modify.html:100 html/Admin/Elements/EditCustomField:74
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "Activo (remover selecção desta caixa desactiva este campo)"
-
-#: html/Admin/Groups/Modify.html:89 html/User/Groups/Modify.html:76
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "Activo (remover selecção desta caixa desactiva este grupo)"
-
-#: html/Admin/Queues/Modify.html:107
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "Activo (remover selecção desta caixa desactiva esta queue)"
-
-#: html/Admin/Queues/index.html:80
-msgid "Enabled Queues"
-msgstr "Queues activas"
-
-#: html/Admin/Elements/EditCustomField:138 html/Admin/Groups/Modify.html:156 html/Admin/Users/Modify.html:354 html/User/Groups/Modify.html:140
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:187 html/Admin/Queues/Modify.html:164
-#. (loc_fuzzy($msg))
-msgid "Enabled status: %1"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:66
-msgid "Enter multiple values"
-msgstr ""
-
-#: html/Elements/EditLinks:127
-msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces."
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:67
-msgid "Enter one value"
-msgstr "Inserir um valor"
-
-#: html/Elements/EditLinks:124
-msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces."
-msgstr "Escreva as filas ou URIs para ligar as filas. Separe várias entradas com espaços."
-
-#: html/Elements/EditLinks:120 html/Search/Bulk.html:191
-msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces."
-msgstr "Escreva o número dos pedidos ou URIs para ligar os pedidos. Separe várias entradas com espaços."
-
-#: lib/RT/CustomField_Overlay.pm:68
-msgid "Enter up to %1 values"
-msgstr "Inserir até %1 valores"
-
-#: html/Elements/Login:78 html/SelfService/Error.html:48 html/SelfService/Error.html:49
-msgid "Error"
-msgstr "Erro"
-
-#: lib/RT/Queue_Overlay.pm:681
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:837
-msgid "Error in parameters to Queue->DeleteWatcher"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1376
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1531
-msgid "Error in parameters to Ticket->DeleteWatcher"
-msgstr ""
-
-#: html/Search/Build.html:390
-#. ($val, $token, $string)
-msgid "Error near ->%1<- expecting a %2 in '%3'"
-msgstr ""
-
-#: bin/rt-crontool:287
-msgid "Escalate tickets"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:59
-msgid "Estimated"
-msgstr "Estimado"
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "Todos"
-
-#: bin/rt-crontool:273
-msgid "Example:"
-msgstr "Exemplo:"
-
-#: html/Admin/Users/Modify.html:101
-msgid "Extra info"
-msgstr "Informação adicional"
-
-#: lib/RT/SavedSearch.pm:179
-msgid "Failed to create search attribute"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:378
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:385
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr ""
-
-#: bin/rt-crontool:208
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:154
-#. ($privacy)
-msgid "Failed to load object for %1"
-msgstr ""
-
-#: lib/RT/Date.pm:444
-msgid "Feb."
-msgstr "Feb"
-
-#: html/Elements/SelectAttachmentField:52
-msgid "Filename"
-msgstr "Ficheiro"
-
-#: lib/RT/CustomField_Overlay.pm:71
-msgid "Fill in multiple text areas"
-msgstr "Preencher múltiplas áreas de texto"
-
-#: lib/RT/CustomField_Overlay.pm:76
-msgid "Fill in multiple wikitext areas"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:72
-msgid "Fill in one text area"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:77
-msgid "Fill in one wikitext area"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:109 html/Admin/CustomFields/Modify.html:120
-msgid "Fill in this field with a URL."
-msgstr "Preencha este campo com um URL"
-
-#: lib/RT/CustomField_Overlay.pm:73
-msgid "Fill in up to %1 text areas"
-msgstr "Preencha até %1 áreas de texto"
-
-#: lib/RT/CustomField_Overlay.pm:78
-msgid "Fill in up to %1 wikitext areas"
-msgstr ""
-
-#: html/Search/Elements/PickBasics:151 html/Ticket/Create.html:185 html/Ticket/Elements/EditBasics:99 lib/RT/Tickets_Overlay.pm:2030
-msgid "Final Priority"
-msgstr "Prioridade final"
-
-#: lib/RT/Ticket_Overlay.pm:1166
-msgid "FinalPriority"
-msgstr ""
-
-#: html/Admin/Groups/index.html:74 html/Admin/Queues/People.html:84 html/Ticket/Elements/EditPeople:57
-msgid "Find groups whose"
-msgstr "Encontrar grupos cujo"
-
-#: html/Admin/Queues/People.html:80 html/Admin/Users/index.html:72 html/Ticket/Elements/EditPeople:53
-msgid "Find people whose"
-msgstr "Encontrar pessoas cujo"
-
-#: html/Search/Results.html:149
-msgid "Find tickets"
-msgstr "Encontrar tickets"
-
-#: html/Ticket/Elements/Tabs:83
-msgid "First"
-msgstr "Primeiro"
-
-#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:764
-msgid "Foo Bar Baz"
-msgstr ""
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:755
-msgid "Foo!"
-msgstr ""
-
-#: html/Search/Bulk.html:85
-msgid "Force change"
-msgstr "Forçar alteraçao"
-
-#: html/Search/Elements/EditFormat:54
-msgid "Format"
-msgstr ""
-
-#: html/Search/Results.html:147
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr ""
-
-#: lib/RT/Record.pm:973
-msgid "Found Object"
-msgstr ""
-
-#: lib/RT/Date.pm:423
-msgid "Fri."
-msgstr "Fri"
-
-#: html/Ticket/Elements/ShowHistory:68 html/Ticket/Elements/ShowHistory:74
-msgid "Full headers"
-msgstr "Cabeçalhos completos"
-
-#: html/Tools/Offline.html:87
-msgid "Get template from file"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:705
-#. ($New->Name)
-msgid "Given to %1"
-msgstr ""
-
-#: html/Admin/Elements/Tabs:67 html/Admin/index.html:84
-msgid "Global"
-msgstr ""
-
-#: html/Admin/Elements/EditCustomFields:57
-msgid "Global Custom Fields"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:61
-msgid "Global custom field configuration"
-msgstr ""
-
-#: html/Admin/Global/MyRT.html:95
-#. ($pane)
-msgid "Global portlet %1 saved."
-msgstr ""
-
-#: html/Admin/Elements/SelectTemplate:61
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr ""
-
-#: html/Admin/CustomFields/index.html:82 html/Admin/Groups/index.html:69 html/Admin/Groups/index.html:75 html/Admin/Queues/People.html:82 html/Admin/Queues/People.html:86 html/Admin/Queues/index.html:68 html/Admin/Users/index.html:75 html/Approvals/index.html:54 html/Elements/RefreshHomepage:50 html/Search/Results.html:76 html/Search/Results.html:92 html/Ticket/Elements/EditPeople:55 html/Ticket/Elements/EditPeople:59 html/Tools/Offline.html:91
-msgid "Go!"
-msgstr ""
-
-#: html/Elements/GotoTicket:48 html/SelfService/Elements/GotoTicket:48
-msgid "Goto ticket"
-msgstr "Ir para ticket"
-
-#: html/Ticket/Elements/AddWatchers:69 html/Ticket/Elements/ShowGroupMembers:57 html/User/Elements/DelegateRights:101
-msgid "Group"
-msgstr "Grupo"
-
-#: html/Admin/Elements/CustomFieldTabs:70 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:84 html/Admin/Elements/SystemTabs:67 html/Admin/Global/index.html:78
-msgid "Group Rights"
-msgstr "Direitos do grupo"
-
-#: lib/RT/Group_Overlay.pm:985
-msgid "Group already has member"
-msgstr "Grupo já tem membro"
-
-#: html/Admin/Groups/Modify.html:115
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "Grupo não pôde ser criado: %1"
-
-#: lib/RT/Group_Overlay.pm:523
-msgid "Group created"
-msgstr "Grupo criado"
-
-#: lib/RT/Group_Overlay.pm:1157
-msgid "Group has no such member"
-msgstr "O grupo não tem esse membro"
-
-#: lib/RT/Group_Overlay.pm:965 lib/RT/Queue_Overlay.pm:743 lib/RT/Queue_Overlay.pm:810 lib/RT/Ticket_Overlay.pm:1423 lib/RT/Ticket_Overlay.pm:1503
-msgid "Group not found"
-msgstr "Grupo não encontrado"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:61 html/Admin/Elements/SelectNewGroupMembers:59 html/Admin/Elements/Tabs:58 html/Admin/Global/CustomFields/index.html:71 html/Admin/Groups/Members.html:88 html/Admin/Queues/People.html:106 html/Admin/Users/Memberships.html:55 html/Admin/index.html:69 html/User/Groups/Members.html:90 lib/RT/CustomField_Overlay.pm:1213
-msgid "Groups"
-msgstr "Grupos"
-
-#: lib/RT/Group_Overlay.pm:991
-msgid "Groups can't be members of their members"
-msgstr "Grupos não podem ser membros dos seus membros"
-
-#: html/Admin/Groups/index.html:88
-msgid "Groups matching search criteria"
-msgstr "Grupos que preenchem critérios"
-
-#: html/Ticket/Elements/ShowRequestor:79
-msgid "Groups this user belongs to"
-msgstr "Grupos a que este utilizador pertence"
-
-#: lib/RT/Interface/CLI.pm:96 lib/RT/Interface/CLI.pm:96
-msgid "Hello!"
-msgstr "Olá!"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:771
-#. ($name)
-msgid "Hello, %1"
-msgstr "Olá, %1"
-
-#: html/Admin/Elements/GroupTabs:72 html/Admin/Elements/UserTabs:66 html/Ticket/Elements/ShowHistory:55 html/Ticket/Elements/Tabs:113
-msgid "History"
-msgstr "Histórico"
-
-#: html/Admin/Groups/History.html:64
-#. ($GroupObj->Name)
-msgid "History of the group %1"
-msgstr "Histórico do grupo %1"
-
-#: html/Admin/Users/History.html:64
-#. ($UserObj->Name)
-msgid "History of the user %1"
-msgstr "Histórico do utilizador %1"
-
-#: html/Elements/Tabs:68
-msgid "Homepage"
-msgstr "Home"
-
-#: html/Elements/SelectTimeUnits:50
-msgid "Hours"
-msgstr "Horas"
-
-#: lib/RT/Base.pm:135
-#. (6)
-msgid "I have %quant(%1,concrete mixer)."
-msgstr ""
-
-#: html/Search/Build.html:466 lib/RT/Report/Tickets.pm:415
-msgid "I'm lost"
-msgstr "Estou perdido"
-
-#: html/Ticket/Elements/ShowBasics:50 lib/RT/Tickets_Overlay.pm:1955
-msgid "Id"
-msgstr "Id"
-
-#: html/Admin/Users/Modify.html:67 html/User/Prefs.html:62
-msgid "Identity"
-msgstr "Identidade"
-
-#: etc/initialdata:429
-msgid "If an approval is rejected, reject the original and delete pending approvals"
-msgstr ""
-
-#: html/Tools/Offline.html:76
-msgid "If no Requestor is specified, create tickets with this requestor."
-msgstr "Se nenhum Requerente for especificado, criar um pedido sem Requerente."
-
-#: html/Tools/Offline.html:67
-msgid "If no queue is specified, create tickets in this queue."
-msgstr "Se nenhuma queue foi definida, criar tickets nesta queue"
-
-#: bin/rt-crontool:269
-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:128 html/Ticket/Modify.html:62 html/Ticket/ModifyAll.html:130 html/Ticket/ModifyPeople.html:62
-msgid "If you've updated anything above, be sure to"
-msgstr "Se actualizou algo acima, certifique-se que"
-
-#: lib/RT/Record.pm:964
-msgid "Illegal value for %1"
-msgstr ""
-
-#: lib/RT/Record.pm:967
-msgid "Immutable field"
-msgstr ""
-
-#: html/Admin/Groups/index.html:67
-msgid "Include disabled groups in listing."
-msgstr "Incluir grupos desactivados na listagem"
-
-#: html/Admin/Queues/index.html:67
-msgid "Include disabled queues in listing."
-msgstr "Incluir queues desactivadas na listagem"
-
-#: html/Admin/Users/index.html:73
-msgid "Include disabled users in search."
-msgstr "Incluir utilizadores desactivados na listagem"
-
-#: html/Admin/CustomFields/Modify.html:115
-msgid "Include page"
-msgstr "Incluir página"
-
-#: html/Search/Build.html:492 lib/RT/Report/Tickets.pm:441
-msgid "Incomplete Query"
-msgstr "Query incompleta"
-
-#: html/Search/Build.html:489 lib/RT/Report/Tickets.pm:438
-msgid "Incomplete query"
-msgstr "Query incompleta"
-
-#: html/Search/Elements/PickBasics:150 lib/RT/Tickets_Overlay.pm:2005
-msgid "Initial Priority"
-msgstr "Prioridade Inicial"
-
-#: lib/RT/Ticket_Overlay.pm:1165 lib/RT/Ticket_Overlay.pm:1167
-msgid "InitialPriority"
-msgstr ""
-
-#: lib/RT/ScripAction_Overlay.pm:135
-msgid "Input error"
-msgstr ""
-
-#: html/Elements/ValidateCustomFields:70 lib/RT/CustomField_Overlay.pm:1024 lib/RT/CustomField_Overlay.pm:1165
-#. ($self->FriendlyPattern)
-#. ($CF->FriendlyPattern)
-msgid "Input must match %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3522
-msgid "Internal Error"
-msgstr "Erro interno"
-
-#: lib/RT/Record.pm:315
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "Erro interno: %1"
-
-#: lib/RT/Group_Overlay.pm:670
-msgid "Invalid Group Type"
-msgstr ""
-
-#: lib/RT/Principal_Overlay.pm:163
-msgid "Invalid Right"
-msgstr "Direito Inválido"
-
-#: lib/RT/Record.pm:969
-msgid "Invalid data"
-msgstr "Dados Inválidos"
-
-#: lib/RT/CustomField_Overlay.pm:210 lib/RT/CustomField_Overlay.pm:681
-#. ($msg)
-msgid "Invalid pattern: %1"
-msgstr "Padrão inválido: %1"
-
-#: lib/RT/Scrip_Overlay.pm:159 lib/RT/Template_Overlay.pm:246
-msgid "Invalid queue"
-msgstr "Queue inválida"
-
-#: lib/RT/ACE_Overlay.pm:266 lib/RT/ACE_Overlay.pm:275 lib/RT/ACE_Overlay.pm:281 lib/RT/ACE_Overlay.pm:292
-msgid "Invalid right"
-msgstr "Direito inválido"
-
-#: lib/RT/Record.pm:290
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "Valor inválido para %1"
-
-#: lib/RT/Record.pm:1632
-msgid "Invalid value for custom field"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:426
-msgid "Invalid value for status"
-msgstr "Valor inválido para estado"
-
-#: bin/rt-crontool:270
-msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
-msgstr "É muito importante que os utilizadores não privilegiados não possam executar esta ferramenta."
-
-#: bin/rt-crontool:271
-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 "É sugerido que crie um utilizador não privilegiado com o grupo correcto e acesso ao RT para utilizar esta ferramenta."
-
-#: bin/rt-crontool:233
-msgid "It takes several arguments:"
-msgstr "Necessários vários argumentos:"
-
-#: html/Search/Elements/EditFormat:87
-msgid "Italic"
-msgstr "Itálico"
-
-#: lib/RT/Date.pm:443
-msgid "Jan."
-msgstr "Jan"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "Join or leave this group"
-msgstr ""
-
-#: lib/RT/Date.pm:449
-msgid "Jul."
-msgstr "Jul"
-
-#: html/Ticket/Elements/Tabs:127
-msgid "Jumbo"
-msgstr ""
-
-#: lib/RT/Date.pm:448
-msgid "Jun."
-msgstr "Jun"
-
-#: html/Admin/Users/Modify.html:96 html/User/Prefs.html:78
-msgid "Language"
-msgstr "Língua"
-
-#: html/Search/Elements/EditFormat:81
-msgid "Large"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:98
-msgid "Last"
-msgstr "Último"
-
-#: html/Ticket/Elements/EditDates:61 html/Ticket/Elements/ShowDates:62
-msgid "Last Contact"
-msgstr "Último Contacto"
-
-#: html/Elements/SelectDateType:52
-msgid "Last Contacted"
-msgstr ""
-
-#: html/Elements/SelectDateType:53
-msgid "Last Updated"
-msgstr "Última actualização"
-
-#: html/Search/Elements/PickBasics:105
-msgid "LastUpdatedBy"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:70
-msgid "Left"
-msgstr "Restante"
-
-#: html/Admin/Users/Modify.html:111
-msgid "Let this user access RT"
-msgstr "Permitir que este utilizador aceda ao RT"
-
-#: html/Admin/Users/Modify.html:115
-msgid "Let this user be granted rights"
-msgstr "Permitir que este utilizador tenha direitos"
-
-#: html/Search/Elements/EditFormat:70
-msgid "Link"
-msgstr "Ligação"
-
-#: lib/RT/Record.pm:1328
-msgid "Link already exists"
-msgstr "Ligação já existe"
-
-#: lib/RT/Record.pm:1342
-msgid "Link could not be created"
-msgstr "Ligação não pôde ser criada"
-
-#: lib/RT/Record.pm:1348
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "Ligação criada (%1)"
-
-#: lib/RT/Record.pm:1409
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "Ligação apagada (%1)"
-
-#: lib/RT/Record.pm:1415
-msgid "Link not found"
-msgstr "Ligação não encontrada"
-
-#: html/Ticket/ModifyLinks.html:48 html/Ticket/ModifyLinks.html:52
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:104
-msgid "Link values to"
-msgstr "Ligar valores a"
-
-#: lib/RT/Ticket_Overlay.pm:702
-msgid "Linking. Permission denied"
-msgstr "Ligação. Permissão negada"
-
-#: html/Ticket/Create.html:219 html/Ticket/Elements/ShowSummary:97 html/Ticket/Elements/Tabs:122 html/Ticket/ModifyAll.html:80
-msgid "Links"
-msgstr "Ligações"
-
-#: html/Search/Elements/EditSearches:77
-msgid "Load"
-msgstr "Carregar"
-
-#: html/Search/Elements/EditSearches:75
-msgid "Load saved search:"
-msgstr "Carregar pesquisa gravada:"
-
-#: lib/RT/System.pm:88
-msgid "LoadSavedSearch"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:66
-msgid "Loaded perl modules"
-msgstr "Módulos perl carregados"
-
-#: lib/RT/SavedSearch.pm:113
-#. ($self->Name)
-msgid "Loaded search %1"
-msgstr "Pesquisa %1 carregada"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:128
-msgid "Location"
-msgstr "Localização"
-
-#: html/Elements/Header:93
-#. ("<span>".$session{'CurrentUser'}->Name."</span>")
-msgid "Logged in as %1"
-msgstr "Ligado como %1"
-
-#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:102 html/Elements/Login:70 html/Elements/Login:86 lib/RT/StyleGuide.pod:795
-msgid "Login"
-msgstr "Entrar"
-
-#: html/Elements/Header:103
-msgid "Logout"
-msgstr "Sair"
-
-#: lib/RT/CustomField_Overlay.pm:935
-msgid "Lookup type mismatch"
-msgstr ""
-
-#: html/Search/Bulk.html:84
-msgid "Make Owner"
-msgstr "Definir Proprietário"
-
-#: html/Search/Bulk.html:108
-msgid "Make Status"
-msgstr "Definir estado"
-
-#: html/Search/Bulk.html:116
-msgid "Make date Due"
-msgstr "Definir data como prazo"
-
-#: html/Search/Bulk.html:118
-msgid "Make date Resolved"
-msgstr ""
-
-#: html/Search/Bulk.html:112
-msgid "Make date Started"
-msgstr ""
-
-#: html/Search/Bulk.html:110
-msgid "Make date Starts"
-msgstr ""
-
-#: html/Search/Bulk.html:114
-msgid "Make date Told"
-msgstr ""
-
-#: html/Search/Bulk.html:104
-msgid "Make priority"
-msgstr "Definir prioridade"
-
-#: html/Search/Bulk.html:106
-msgid "Make queue"
-msgstr ""
-
-#: html/Search/Bulk.html:102
-msgid "Make subject"
-msgstr "Definir assunto"
-
-#: lib/RT/Group_Overlay.pm:171
-msgid "Make this group visible to user"
-msgstr ""
-
-#: html/Admin/index.html:80
-msgid "Manage custom fields and custom field values"
-msgstr ""
-
-#: html/Admin/index.html:71
-msgid "Manage groups and group membership"
-msgstr "Gerir grupos e membros de grupos"
-
-#: html/Admin/index.html:87
-msgid "Manage properties and configuration which apply to all queues"
-msgstr "Gerir propriedades e configuração que se aplica a todas as queues"
-
-#: html/Admin/index.html:76
-msgid "Manage queues and queue-specific properties"
-msgstr "Gerir queues e propriedades específicas das queues"
-
-#: html/Admin/index.html:66
-msgid "Manage users and passwords"
-msgstr "Gerir utilizadores e passwords"
-
-#: lib/RT/Date.pm:445
-msgid "Mar."
-msgstr "Mar"
-
-#: lib/RT/Date.pm:447
-msgid "May."
-msgstr "May"
-
-#: lib/RT/Transaction_Overlay.pm:752
-#. ($value)
-msgid "Member %1 added"
-msgstr "Membro %1 adicionado"
-
-#: lib/RT/Transaction_Overlay.pm:792
-#. ($value)
-msgid "Member %1 deleted"
-msgstr "Membro %1 apagado"
-
-#: lib/RT/Group_Overlay.pm:1002
-msgid "Member added"
-msgstr "Membro adicionado"
-
-#: lib/RT/Group_Overlay.pm:1164
-msgid "Member deleted"
-msgstr "Membro apagado"
-
-#: lib/RT/Group_Overlay.pm:1168
-msgid "Member not deleted"
-msgstr "Membro não apagado"
-
-#: html/Elements/SelectLinkType:49
-msgid "Member of"
-msgstr "Membro de"
-
-#: html/Admin/Elements/GroupTabs:65 html/User/Elements/GroupTabs:65
-msgid "Members"
-msgstr "Membros"
-
-#: lib/RT/Transaction_Overlay.pm:749
-#. ($value)
-msgid "Membership in %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:789
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr ""
-
-#: html/Admin/Elements/UserTabs:63
-msgid "Memberships"
-msgstr ""
-
-#: html/Admin/Users/Memberships.html:62
-#. ($UserObj->Name)
-msgid "Memberships of the user %1"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2896
-msgid "Merge Successful"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2774
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:2791
-msgid "Merge failed. Couldn't set Status"
-msgstr ""
-
-#: html/Elements/EditLinks:132 html/Ticket/Elements/BulkLinks:50
-msgid "Merge into"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:755
-#. ($value)
-msgid "Merged into %1"
-msgstr ""
-
-#: html/Search/Bulk.html:145 html/Ticket/Update.html:120
-msgid "Message"
-msgstr "Mensagem"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:166
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr "Corpo da mensagem não apresentado porque é grande demais ou não é texto"
-
-#: lib/RT/Ticket_Overlay.pm:2445
-msgid "Message could not be recorded"
-msgstr "Mensagem não pôde ser gravada"
-
-#: lib/RT/Ticket_Overlay.pm:2448
-msgid "Message recorded"
-msgstr "Mensagem gravada"
-
-#: html/Ticket/Elements/PreviewScrips:124
-msgid "Messages about this ticket will not be sent to..."
-msgstr "Mensagens sobre este ticket não serão enviadas a..."
-
-#: html/Elements/SelectTimeUnits:49
-msgid "Minutes"
-msgstr "Minutos"
-
-#: html/Search/Build.html:496 lib/RT/Report/Tickets.pm:445
-msgid "Mismatched parentheses"
-msgstr ""
-
-#: lib/RT/Record.pm:971
-msgid "Missing a primary key?: %1"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:196 html/User/Prefs.html:94
-msgid "Mobile"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "Modify Access Control List"
-msgstr "Alterar Lista de Controle de Acessos"
-
-#: html/Admin/Elements/ObjectCustomFields:98
-#. (loc(lc($FriendlySubTypes)), loc(lc($Types)))
-msgid "Modify Custom Fields which apply to %1 for all %2"
-msgstr ""
-
-#: html/Admin/Elements/ObjectCustomFields:100
-#. (loc(lc($Types)))
-msgid "Modify Custom Fields which apply to all %1"
-msgstr ""
-
-#: html/Admin/Global/GroupRights.html:108 html/Admin/Groups/GroupRights.html:96 html/Admin/Queues/GroupRights.html:109
-msgid "Modify Group Rights"
-msgstr "Alterar Direitos de Grupo"
-
-#: html/Admin/Groups/Members.html:107 html/User/Groups/Members.html:103
-msgid "Modify Members"
-msgstr "Alterar Membros"
-
-#: html/User/Delegation.html:60
-msgid "Modify Rights"
-msgstr "Alterar Direitos"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "Modify Scrip templates for this queue"
-msgstr "Alterar templates dos Scrips para esta queue"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "Modify Scrips for this queue"
-msgstr "Alterar Scrips para esta queue"
-
-#: html/Admin/Global/UserRights.html:76 html/Admin/Groups/UserRights.html:78 html/Admin/Queues/UserRights.html:77
-msgid "Modify User Rights"
-msgstr "Alterar Direitos de Utilizadores"
-
-#: html/Admin/Queues/CustomField.html:68
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr ""
-
-#: html/Admin/Queues/Scrip.html:84
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "Alterar uma scrip da queue %1"
-
-#: html/Admin/Global/Scrip.html:77
-msgid "Modify a scrip that applies to all queues"
-msgstr "Alterar uma scrip que se aplica a todas as queues"
-
-#: html/Admin/CustomFields/Objects.html:92
-#. ($CF->Name)
-msgid "Modify associated objects for %1"
-msgstr ""
-
-#: html/Ticket/ModifyDates.html:48 html/Ticket/ModifyDates.html:52
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "Alterar datas de #%1"
-
-#: html/Ticket/ModifyDates.html:59
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "Alterar datas do ticket # %1"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:67 html/Admin/Global/index.html:74
-msgid "Modify global custom fields"
-msgstr ""
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:72 html/Admin/Global/GroupRights.html:48 html/Admin/Global/GroupRights.html:51 html/Admin/Global/index.html:79
-msgid "Modify global group rights"
-msgstr "Alterar direitos de grupo globais"
-
-#: html/Admin/Global/GroupRights.html:56
-msgid "Modify global group rights."
-msgstr "Alterar direitos de grupo globais"
-
-#: html/Admin/Global/UserRights.html:48 html/Admin/Global/UserRights.html:51 html/Admin/Global/index.html:83
-msgid "Modify global user rights"
-msgstr "Alterar direitos de utilizador globais"
-
-#: html/Admin/Global/UserRights.html:56
-msgid "Modify global user rights."
-msgstr "Alterar direitos de utilizador globais"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "Modify group metadata or delete group"
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:113
-#. ($CustomFieldObj->Name)
-msgid "Modify group rights for custom field %1"
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:48 html/Admin/Groups/GroupRights.html:52 html/Admin/Groups/GroupRights.html:58
-#. ($GroupObj->Name)
-msgid "Modify group rights for group %1"
-msgstr "Alterar direitos de grupo para o grupo %1"
-
-#: html/Admin/Queues/GroupRights.html:48 html/Admin/Queues/GroupRights.html:52
-#. ($QueueObj->Name)
-msgid "Modify group rights for queue %1"
-msgstr "Alterar direitos de grupo para a queue %1"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "Modify membership roster for this group"
-msgstr ""
-
-#: lib/RT/System.pm:84
-msgid "Modify one's own RT account"
-msgstr ""
-
-#: html/Admin/Queues/People.html:48 html/Admin/Queues/People.html:52
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "Alterar pessoas relacionadas com a queue %1"
-
-#: html/Ticket/ModifyPeople.html:48 html/Ticket/ModifyPeople.html:52 html/Ticket/ModifyPeople.html:59
-#. ($Ticket->id)
-#. ($Ticket->Id)
-msgid "Modify people related to ticket #%1"
-msgstr "Alterar pessoas relacionadas com o ticket %1"
-
-#: html/Admin/Queues/Scrips.html:69
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "Alterar scrips da queue %1"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:58 html/Admin/Global/Scrips.html:67 html/Admin/Global/index.html:65
-msgid "Modify scrips which apply to all queues"
-msgstr "Alterar scrips que se aplicam a todas as queues"
-
-#: html/Admin/Global/Template.html:100 html/Admin/Queues/Template.html:101
-#. (loc($TemplateObj->Name()))
-msgid "Modify template %1"
-msgstr "Alterar template %1"
-
-#: html/Admin/Global/Templates.html:67
-msgid "Modify templates which apply to all queues"
-msgstr "Alterar templates que se aplicam a todas as queues"
-
-#: html/Admin/Global/index.html:87
-msgid "Modify the default \"RT at a glance\" view"
-msgstr "Alterar a página principal"
-
-#: html/Admin/Groups/Modify.html:125 html/User/Groups/Modify.html:109
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "Alterar o grupo %1"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "Modify the queue watchers"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:313
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "Alterar o utilizador %1"
-
-#: html/Ticket/ModifyAll.html:60
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "Alterar o ticket # %1"
-
-#: html/Ticket/Modify.html:48 html/Ticket/Modify.html:51 html/Ticket/Modify.html:57
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "Alterar ticket # %1"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "Modify tickets"
-msgstr "Alterar tickets"
-
-#: html/Admin/CustomFields/UserRights.html:159
-#. ($CustomFieldObj->Name)
-msgid "Modify user rights for custom field %1"
-msgstr ""
-
-#: html/Admin/Groups/UserRights.html:48 html/Admin/Groups/UserRights.html:52 html/Admin/Groups/UserRights.html:58
-#. ($GroupObj->Name)
-msgid "Modify user rights for group %1"
-msgstr "Alterar direitos de utilizadores para o grupo %1"
-
-#: html/Admin/Queues/UserRights.html:48 html/Admin/Queues/UserRights.html:52
-#. ($QueueObj->Name)
-msgid "Modify user rights for queue %1"
-msgstr "Alterar direitos de utilizador para a queue %1"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "ModifyACL"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:110
-msgid "ModifyCustomField"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "ModifyOwnMembership"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "ModifyQueueWatchers"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "ModifyScrips"
-msgstr ""
-
-#: lib/RT/System.pm:84
-msgid "ModifySelf"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "ModifyTemplate"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "ModifyTicket"
-msgstr ""
-
-#: lib/RT/Date.pm:419
-msgid "Mon."
-msgstr "Mon"
-
-#: html/Ticket/Elements/ShowRequestor:63
-#. ($name)
-msgid "More about %1"
-msgstr "Mais sobre %1"
-
-#: html/Admin/Elements/PickCustomFields:85
-msgid "Move down"
-msgstr "Mover para baixo"
-
-#: html/Admin/Elements/PickCustomFields:77
-msgid "Move up"
-msgstr "Mover para cima"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:50
-msgid "Multiple"
-msgstr "Múltiplo"
-
-#: lib/RT/User_Overlay.pm:228
-msgid "Must specify 'Name' attribute"
-msgstr ""
-
-#: html/SelfService/Elements/MyRequests:79
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "Os meus %1 tickets"
-
-#: html/Tools/Elements/Tabs:65
-msgid "My Day"
-msgstr ""
-
-#: html/Approvals/index.html:48 html/Approvals/index.html:49
-msgid "My approvals"
-msgstr "As minhas aprovações"
-
-#: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:56
-msgid "My saved searches"
-msgstr "As minhas pesquisas guardadas"
-
-#: html/Admin/CustomFields/Modify.html:60 html/Admin/Elements/AddCustomFieldValue:55 html/Admin/Elements/EditCustomField:57 html/Admin/Elements/EditCustomFieldValues:57 html/Admin/Elements/ModifyTemplate:51 html/Admin/Groups/Modify.html:67 html/Search/Bulk.html:159 html/User/Groups/Modify.html:67
-msgid "Name"
-msgstr "Nome"
-
-#: lib/RT/User_Overlay.pm:235
-msgid "Name in use"
-msgstr "Nome em utilização"
-
-#: html/Ticket/Elements/ShowDates:75
-msgid "Never"
-msgstr "Nunca"
-
-#: html/Elements/EditLinks:118
-msgid "New Links"
-msgstr "Novas Ligações"
-
-#: html/Admin/Users/Modify.html:121 html/User/Prefs.html:111
-msgid "New Password"
-msgstr "Nova Password"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "Novas aprovações pendentes"
-
-#: html/Ticket/Elements/Tabs:214
-msgid "New Search"
-msgstr "Nova Pesquisa"
-
-#: html/Admin/Elements/CustomFieldTabs:95 html/Admin/Queues/CustomField.html:75
-msgid "New custom field"
-msgstr ""
-
-#: html/Admin/Elements/GroupTabs:79 html/User/Elements/GroupTabs:75
-msgid "New group"
-msgstr "Novo grupo"
-
-#: html/SelfService/Prefs.html:55
-msgid "New password"
-msgstr "Nova password"
-
-#: lib/RT/User_Overlay.pm:818
-msgid "New password notification sent"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:97
-msgid "New queue"
-msgstr "Nova queue"
-
-#: html/Ticket/Elements/Reminders:120
-msgid "New reminder:"
-msgstr "Nova Nota"
-
-#: html/Admin/Elements/SelectRights:67
-msgid "New rights"
-msgstr "Novos direitos"
-
-#: html/Admin/Global/Scrip.html:65 html/Admin/Global/Scrips.html:62 html/Admin/Queues/Scrip.html:73 html/Admin/Queues/Scrips.html:78
-msgid "New scrip"
-msgstr "Novo scrip"
-
-#: html/Admin/Global/Template.html:80 html/Admin/Global/Templates.html:62 html/Admin/Queues/Template.html:81 html/Admin/Queues/Templates.html:73
-msgid "New template"
-msgstr "Novo template"
-
-#: html/SelfService/Elements/Tabs:87 html/SelfService/Elements/Tabs:91
-msgid "New ticket"
-msgstr "Novo ticket"
-
-#: lib/RT/Ticket_Overlay.pm:2751
-msgid "New ticket doesn't exist"
-msgstr "Novo ticket não existe"
-
-#: html/Admin/Elements/UserTabs:83
-msgid "New user"
-msgstr "Novo utilizador"
-
-#: html/Admin/Elements/CreateUserCalled:49
-msgid "New user called"
-msgstr "Novo utilizador chamado"
-
-#: html/Admin/Queues/People.html:78 html/Ticket/Elements/EditPeople:52
-msgid "New watchers"
-msgstr ""
-
-#: html/Helpers/CalPopup.html:60 html/Ticket/Elements/Tabs:94
-msgid "Next"
-msgstr "Próximo"
-
-#: html/Elements/TicketList:108
-msgid "Next Page"
-msgstr "Próxima Página"
-
-#: html/Admin/Users/Modify.html:86 html/User/Prefs.html:74
-msgid "Nickname"
-msgstr "Nick"
-
-#: html/Admin/CustomFields/UserRights.html:147
-msgid "No Class defined"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:168 html/Admin/Elements/EditCustomField:121
-msgid "No CustomField"
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:105
-msgid "No CustomField defined"
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:107 html/Admin/Groups/UserRights.html:94
-msgid "No Group defined"
-msgstr "Sem Grupo definido"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:484
-msgid "No Query"
-msgstr ""
-
-#: html/Admin/Queues/GroupRights.html:120 html/Admin/Queues/UserRights.html:91
-msgid "No Queue defined"
-msgstr ""
-
-#: bin/rt-crontool:75
-msgid "No RT user found. Please consult your RT administrator.\\n"
-msgstr ""
-
-#: html/Admin/Global/Template.html:98 html/Admin/Queues/Template.html:99
-msgid "No Template"
-msgstr ""
-
-#: html/Approvals/Elements/Approve:79
-msgid "No action"
-msgstr ""
-
-#: lib/RT/Record.pm:966
-msgid "No column specified"
-msgstr ""
-
-#: html/Ticket/Elements/ShowRequestor:70
-msgid "No comment entered about this user"
-msgstr ""
-
-#: lib/RT/Action/Generic.pm:187 lib/RT/Condition/Generic.pm:199 lib/RT/Search/ActiveTicketsInQueue.pm:79 lib/RT/Search/Generic.pm:136 lib/RT/Search/Googleish.pm:90
-#. (ref $self)
-msgid "No description for %1"
-msgstr "Sem descrição para %1"
-
-#: lib/RT/Users_Overlay.pm:192
-msgid "No group specified"
-msgstr "Grupo não especificado"
-
-#: html/Admin/Groups/index.html:54
-msgid "No groups matching search criteria found."
-msgstr "Nenhum grupo verificou o critério de pesquisa especificado."
-
-#: lib/RT/Ticket_Overlay.pm:2386
-msgid "No message attached"
-msgstr "Sem mensagem anexada"
-
-#: lib/RT/User_Overlay.pm:1036
-msgid "No password set"
-msgstr "Password não definida"
-
-#: lib/RT/Queue_Overlay.pm:363
-msgid "No permission to create queues"
-msgstr "Sem permissão para criar queues"
-
-#: lib/RT/Ticket_Overlay.pm:1090 lib/RT/Ticket_Overlay.pm:422
-#. ($QueueObj->Name)
-msgid "No permission to create tickets in the queue '%1'"
-msgstr "Sem permissão para criar tickets na queue '%1'"
-
-#: lib/RT/User_Overlay.pm:188
-msgid "No permission to create users"
-msgstr "Sem permissão para criar utilizadores"
-
-#: html/SelfService/Display.html:210
-msgid "No permission to display that ticket"
-msgstr "Sem permissão para ver esse ticket"
-
-#: lib/RT/SavedSearch.pm:158
-msgid "No permission to save system-wide searches"
-msgstr "Não tem permissão para gravar uma pesquisa de sistema"
-
-#: html/SelfService/Update.html:119
-msgid "No permission to view update ticket"
-msgstr "Sem permissão para ver ou actualizar esse ticket"
-
-#: lib/RT/Ticket_Overlay.pm:1482
-msgid "No principal specified"
-msgstr ""
-
-#: html/Admin/Queues/People.html:177 html/Admin/Queues/People.html:187
-msgid "No principals selected."
-msgstr ""
-
-#: html/Admin/Queues/index.html:59
-msgid "No queues matching search criteria found."
-msgstr "Nenhuma fila verificou os critérios de pesquisa especificados"
-
-#: html/Admin/Elements/SelectRights:108
-msgid "No rights found"
-msgstr ""
-
-#: html/Admin/Elements/SelectRights:55
-msgid "No rights granted."
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:198
-msgid "No search loaded"
-msgstr "Nenhuma pesquisa carregada"
-
-#: html/Search/Bulk.html:234
-msgid "No search to operate on."
-msgstr "Nenhuma pesquisa possível"
-
-#: html/Elements/RT__Ticket/ColumnMap:139 html/Search/Results.rdf:80
-msgid "No subject"
-msgstr "Sem assunto"
-
-#: html/Search/Chart:101
-msgid "No tickets found."
-msgstr "Tickets não encontrados"
-
-#: lib/RT/Transaction_Overlay.pm:549 lib/RT/Transaction_Overlay.pm:586
-msgid "No transaction type specified"
-msgstr ""
-
-#: html/Admin/Users/index.html:57
-msgid "No users matching search criteria found."
-msgstr "Nenhum proprietário verificou o critério de pesquisa."
-
-#: lib/RT/Record.pm:963
-msgid "No value sent to _Set!\\n"
-msgstr ""
-
-#: html/Elements/QuickCreate:61
-msgid "Nobody"
-msgstr "Nobody"
-
-#: lib/RT/Record.pm:968
-msgid "Nonexistant field?"
-msgstr ""
-
-#: html/Search/Chart:149 html/Search/Elements/Chart:90
-msgid "Not Set"
-msgstr "Não definido"
-
-#: html/Elements/Header:98
-msgid "Not logged in."
-msgstr "Desligado"
-
-#: lib/RT/Date.pm:399
-msgid "Not set"
-msgstr ""
-
-#: html/NoAuth/Reminder.html:50
-msgid "Not yet implemented."
-msgstr ""
-
-#: html/Approvals/Elements/Approve:83
-msgid "Notes"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:821
-msgid "Notification could not be sent"
-msgstr "Notificação não pôde ser enviada"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr ""
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "Notificar AdminCCS como Comentário"
-
-#: etc/initialdata:93 etc/upgrade/3.1.17/content:6
-msgid "Notify Ccs"
-msgstr ""
-
-#: etc/initialdata:89 etc/upgrade/3.1.17/content:2
-msgid "Notify Ccs as Comment"
-msgstr "Notificar CCs como Comentário"
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr ""
-
-#: etc/initialdata:124
-msgid "Notify Other Recipients as Comment"
-msgstr "Notificar outros recipientes como comentário"
-
-#: etc/initialdata:85
-msgid "Notify Owner"
-msgstr "Notificar Proprietário"
-
-#: etc/initialdata:81
-msgid "Notify Owner as Comment"
-msgstr "Notificar Proprietário como comentário"
-
-#: etc/initialdata:376
-msgid "Notify Owner of their rejected ticket"
-msgstr ""
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr ""
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr ""
-
-#: etc/initialdata:334
-msgid "Notify Owners and AdminCcs of new items pending their approval"
-msgstr ""
-
-#: etc/initialdata:77
-msgid "Notify Requestors"
-msgstr "Notificar Requerentes"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "Notificar Requerentes e CCs"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "Notificar Requerentes e CCs como comentário"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "Notificar Requerentes, CCs e AdminCCs"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "Notificar Requerentes, CCs e AdminCCs como comentário"
-
-#: lib/RT/Date.pm:453
-msgid "Nov."
-msgstr "Nov"
-
-#: html/Search/Elements/SelectAndOr:49
-msgid "OR"
-msgstr "OU"
-
-#: lib/RT/Record.pm:329
-msgid "Object could not be created"
-msgstr ""
-
-#: lib/RT/Record.pm:130
-msgid "Object could not be deleted"
-msgstr ""
-
-#: lib/RT/Record.pm:348
-msgid "Object created"
-msgstr ""
-
-#: lib/RT/Record.pm:127
-msgid "Object deleted"
-msgstr "Objecto apagado"
-
-#: html/Admin/CustomFields/Objects.html:74 html/Admin/Elements/ObjectCustomFields:65
-#. ($ObjectType)
-#. ($LookupType)
-msgid "Object of type %1 cannot take custom fields"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:970
-msgid "Object type mismatch"
-msgstr ""
-
-#: lib/RT/Date.pm:452
-msgid "Oct."
-msgstr "Oct"
-
-#: html/Tools/Elements/Tabs:57
-msgid "Offline"
-msgstr "Offline"
-
-#: html/Tools/Offline.html:51
-msgid "Offline edits"
-msgstr ""
-
-#: html/Tools/Offline.html:48
-msgid "Offline upload"
-msgstr ""
-
-#: html/Elements/SelectDateRelation:58
-msgid "On"
-msgstr "Em"
-
-#: lib/RT/Transaction_Overlay.pm:349
-#. ($self->CreatedAsString(), $self->CreatorObj->Name())
-msgid "On %1, %2 wrote:"
-msgstr ""
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "Em comentário"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "Em Resposta"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "Em Criação"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "Em Alteração de Dono"
-
-#: etc/initialdata:177 etc/upgrade/3.1.17/content:15
-msgid "On Priority Change"
-msgstr "Em Alteração de Prioridade"
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "Em Alteração de Queue"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "Em Resolução"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "Em Alteração de Estado"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "Em Transacção"
-
-#: html/Approvals/Elements/PendingMyApproval:72
-#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='CreatedAfter' />")
-msgid "Only show approvals for requests created after %1"
-msgstr ""
-
-#: html/Approvals/Elements/PendingMyApproval:70
-#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore' id='CreatedBefore' />")
-msgid "Only show approvals for requests created before %1"
-msgstr ""
-
-#: html/Admin/CustomFields/index.html:77
-msgid "Only show custom fields for:"
-msgstr ""
-
-#: etc/initialdata:139
-msgid "Open Tickets"
-msgstr "Tickets Abertos"
-
-#: html/Ticket/Elements/Tabs:162
-msgid "Open it"
-msgstr "Abrir"
-
-#: html/SelfService/Elements/Tabs:78 html/SelfService/index.html:48
-msgid "Open tickets"
-msgstr "Tickets Abertos"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "Abrir Tickets em resposta"
-
-#: html/Prefs/MyRT.html:72
-msgid "Options"
-msgstr "Opções"
-
-#: html/Search/Elements/DisplayOptions:61
-msgid "Order by"
-msgstr "Ordenar por"
-
-#: html/Admin/Users/Modify.html:144 html/User/Prefs.html:131
-msgid "Organization"
-msgstr "Organização"
-
-#: html/Approvals/Elements/Approve:55
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:643
-msgid "Outgoing email about a comment recorded"
-msgstr "Registado email sobre um comentário"
-
-#: lib/RT/Transaction_Overlay.pm:647
-msgid "Outgoing email recorded"
-msgstr "Registado email"
-
-#: html/Admin/Queues/Modify.html:92
-msgid "Over time, priority moves toward"
-msgstr "Com o passar do tempo, a prioridade altera-se para"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "Own tickets"
-msgstr "Próprios tickets"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "OwnTicket"
-msgstr ""
-
-#: etc/initialdata:38 html/Elements/QuickCreate:58 html/Search/Elements/PickBasics:103 html/Ticket/Create.html:74 html/Ticket/Elements/EditBasics:63 html/Ticket/Elements/EditPeople:66 html/Ticket/Elements/EditPeople:67 html/Ticket/Elements/Reminders:131 html/Ticket/Elements/ShowPeople:50 html/Ticket/Update.html:64 lib/RT/ACE_Overlay.pm:112 lib/RT/Tickets_Overlay.pm:2195
-msgid "Owner"
-msgstr "Dono"
-
-#: lib/RT/Ticket_Overlay.pm:539
-#. ($Owner->Name)
-msgid "Owner '%1' does not have rights to own this ticket."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3100
-#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
-msgid "Owner changed from %1 to %2"
-msgstr "Dono alterado de %1 para %2"
-
-#: lib/RT/Ticket_Overlay.pm:507
-msgid "Owner could not be set."
-msgstr "Dono não pôde ser definido."
-
-#: lib/RT/Transaction_Overlay.pm:693
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "Proprietário forçado de %1 para %2"
-
-#: html/Elements/TicketList:82
-#. ($Page, $pages)
-msgid "Page %1 of %2"
-msgstr "Página %1 de %2"
-
-#: html/Admin/Users/Modify.html:201 html/User/Prefs.html:98
-msgid "Pager"
-msgstr ""
-
-#: html/Elements/EditLinks:145 html/Elements/EditLinks:77 html/Elements/ShowLinks:70 html/Ticket/Create.html:225 html/Ticket/Elements/BulkLinks:62
-msgid "Parents"
-msgstr ""
-
-#: html/Elements/Login:97 html/User/Prefs.html:107
-msgid "Password"
-msgstr "Password"
-
-#: html/NoAuth/Reminder.html:48
-msgid "Password Reminder"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:802 lib/RT/User_Overlay.pm:1047
-msgid "Password changed"
-msgstr "Password alterada"
-
-#: lib/RT/User_Overlay.pm:1039 lib/RT/User_Overlay.pm:216
-#. ($RT::MinimumPasswordLength)
-msgid "Password needs to be at least %1 characters long"
-msgstr "A Password tem de ter pelo menos %1 caracteres"
-
-#: lib/RT/User_Overlay.pm:1046
-msgid "Password set"
-msgstr "Password definida"
-
-#: html/User/Prefs.html:242
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "Password: %1"
-
-#: lib/RT/User_Overlay.pm:1032
-msgid "Password: Permission Denied"
-msgstr "Password: Permissão Negada"
-
-#: html/Admin/Users/Modify.html:368
-msgid "Passwords do not match."
-msgstr "As passwords não coincidem."
-
-#: html/User/Prefs.html:244
-msgid "Passwords do not match. Your password has not been changed"
-msgstr "As passwords não coincidem. A sua password não foi alterada"
-
-#: html/Ticket/Elements/ShowSummary:64 html/Ticket/Elements/Tabs:121 html/Ticket/ModifyAll.html:74
-msgid "People"
-msgstr "Pessoas"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:96
-msgid "Perl configuration"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:582 lib/RT/ACE_Overlay.pm:592 lib/RT/ACE_Overlay.pm:602 lib/RT/ACE_Overlay.pm:667 lib/RT/Attribute_Overlay.pm:160 lib/RT/Attribute_Overlay.pm:166 lib/RT/Attribute_Overlay.pm:407 lib/RT/Attribute_Overlay.pm:416 lib/RT/Attribute_Overlay.pm:429 lib/RT/CurrentUser.pm:118 lib/RT/CurrentUser.pm:127 lib/RT/CustomField_Overlay.pm:1020 lib/RT/CustomField_Overlay.pm:1141 lib/RT/CustomField_Overlay.pm:1284 lib/RT/CustomField_Overlay.pm:174 lib/RT/CustomField_Overlay.pm:191 lib/RT/CustomField_Overlay.pm:202 lib/RT/CustomField_Overlay.pm:377 lib/RT/CustomField_Overlay.pm:406 lib/RT/CustomField_Overlay.pm:766 lib/RT/CustomField_Overlay.pm:939 lib/RT/CustomField_Overlay.pm:974 lib/RT/Group_Overlay.pm:1119 lib/RT/Group_Overlay.pm:1123 lib/RT/Group_Overlay.pm:1132 lib/RT/Group_Overlay.pm:1242 lib/RT/Group_Overlay.pm:1246 lib/RT/Group_Overlay.pm:1252 lib/RT/Group_Overlay.pm:447 lib/RT/Group_Overlay.pm:544 lib/RT/Group_Overlay.pm:622 lib/RT/Group_Overlay.pm:630 lib/RT/Group_Overlay.pm:728 lib/RT/Group_Overlay.pm:732 lib/RT/Group_Overlay.pm:738 lib/RT/Group_Overlay.pm:924 lib/RT/Group_Overlay.pm:928 lib/RT/Group_Overlay.pm:941 lib/RT/Queue_Overlay.pm:1058 lib/RT/Queue_Overlay.pm:142 lib/RT/Queue_Overlay.pm:160 lib/RT/Queue_Overlay.pm:685 lib/RT/Queue_Overlay.pm:823 lib/RT/Queue_Overlay.pm:832 lib/RT/Queue_Overlay.pm:845 lib/RT/Scrip_Overlay.pm:151 lib/RT/Scrip_Overlay.pm:162 lib/RT/Scrip_Overlay.pm:226 lib/RT/Scrip_Overlay.pm:540 lib/RT/Template_Overlay.pm:110 lib/RT/Template_Overlay.pm:279 lib/RT/Ticket_Overlay.pm:1380 lib/RT/Ticket_Overlay.pm:1515 lib/RT/Ticket_Overlay.pm:1525 lib/RT/Ticket_Overlay.pm:1539 lib/RT/Ticket_Overlay.pm:1656 lib/RT/Ticket_Overlay.pm:1976 lib/RT/Ticket_Overlay.pm:2119 lib/RT/Ticket_Overlay.pm:2289 lib/RT/Ticket_Overlay.pm:2339 lib/RT/Ticket_Overlay.pm:2519 lib/RT/Ticket_Overlay.pm:2532 lib/RT/Ticket_Overlay.pm:2608 lib/RT/Ticket_Overlay.pm:2621 lib/RT/Ticket_Overlay.pm:2742 lib/RT/Ticket_Overlay.pm:2756 lib/RT/Ticket_Overlay.pm:3007 lib/RT/Ticket_Overlay.pm:3018 lib/RT/Ticket_Overlay.pm:3024 lib/RT/Ticket_Overlay.pm:3241 lib/RT/Ticket_Overlay.pm:3245 lib/RT/Ticket_Overlay.pm:3388 lib/RT/Ticket_Overlay.pm:3516 lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:544 lib/RT/Transaction_Overlay.pm:572 lib/RT/Transaction_Overlay.pm:579 lib/RT/User_Overlay.pm:1178 lib/RT/User_Overlay.pm:1858 lib/RT/User_Overlay.pm:371 lib/RT/User_Overlay.pm:737 lib/RT/User_Overlay.pm:776
-msgid "Permission Denied"
-msgstr "Permissão Negada"
-
-#: lib/RT/Template_Overlay.pm:240 lib/RT/Template_Overlay.pm:249
-msgid "Permission denied"
-msgstr "Permissão Negada"
-
-#: lib/RT/Template_Overlay.pm:379
-msgid "Permissions denied"
-msgstr "Permissão Negada"
-
-#: html/User/Elements/Tabs:58
-msgid "Personal Groups"
-msgstr "Grupos Pessoais"
-
-#: html/User/Groups/index.html:53 html/User/Groups/index.html:63
-msgid "Personal groups"
-msgstr "Grupos Pessoais"
-
-#: html/User/Elements/DelegateRights:60
-msgid "Personal groups:"
-msgstr "Grupos Pessoais:"
-
-#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:83
-msgid "Phone numbers"
-msgstr "Números de telefone"
-
-#: html/Elements/Header:95 html/Elements/Tabs:94 html/SelfService/Elements/Tabs:98 html/SelfService/Prefs.html:48 html/User/Prefs.html:48 html/User/Prefs.html:51
-msgid "Preferences"
-msgstr "Preferências"
-
-#: html/Admin/Users/MyRT.html:122
-#. ($pane, $UserObj->Name)
-msgid "Preferences %1 for user %2 ."
-msgstr "Preferências %1 para utilizador %2 ."
-
-#: html/Prefs/MyRT.html:143
-#. ($pane)
-msgid "Preferences saved for %1."
-msgstr "Preferências gravadas para %1."
-
-#: lib/RT/Action/Generic.pm:197
-msgid "Prepare Stubbed"
-msgstr ""
-
-#: html/Helpers/CalPopup.html:58 html/Ticket/Elements/Tabs:86
-msgid "Prev"
-msgstr ""
-
-#: html/Elements/TicketList:105
-msgid "Previous Page"
-msgstr "Página Anterior"
-
-#: lib/RT/ACE_Overlay.pm:159 lib/RT/ACE_Overlay.pm:241 lib/RT/ACE_Overlay.pm:571
-#. ($args{'PrincipalId'})
-msgid "Principal %1 not found."
-msgstr ""
-
-#: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:184 html/Ticket/Elements/EditBasics:94 html/Ticket/Elements/ShowBasics:74 lib/RT/Tickets_Overlay.pm:1979
-msgid "Priority"
-msgstr "Prioridade"
-
-#: html/Admin/Queues/Modify.html:88
-msgid "Priority starts at"
-msgstr "Prioridade começa em"
-
-#: html/Search/Elements/EditSearches:52
-msgid "Privacy:"
-msgstr "Privacidade:"
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "Privilegiados"
-
-#: html/Admin/Users/Modify.html:346 html/User/Prefs.html:233
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr ""
-
-#: html/Admin/Users/index.html:104
-msgid "Privileged users"
-msgstr "Utilizadores privilegiados"
-
-#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
-msgid "Pseudogroup for internal use"
-msgstr ""
-
-#: html/Search/Build.html:123
-msgid "Query Builder"
-msgstr "Construtor de Pesquisas"
-
-#: html/Search/Elements/Chart:103
-msgid "Query:"
-msgstr "Pesquisa"
-
-#: html/Elements/QueueSummary:50 html/Elements/QuickCreate:56 html/Search/Elements/PickBasics:73 html/SelfService/Create.html:56 html/Ticket/Create.html:64 html/Ticket/Elements/EditBasics:59 html/Ticket/Elements/ShowBasics:78 html/Tools/Reports/CreatedByDates.html:87 html/Tools/Reports/ResolvedByDates.html:88 html/Tools/Reports/ResolvedByOwner.html:68 html/User/Elements/DelegateRights:103 lib/RT/Tickets_Overlay.pm:1806
-msgid "Queue"
-msgstr "Queue"
-
-#: html/Admin/Queues/CustomField.html:65 html/Admin/Queues/Scrip.html:63 html/Admin/Queues/Scrips.html:71 html/Admin/Queues/Templates.html:67
-#. ($Queue)
-#. ($id)
-msgid "Queue %1 not found"
-msgstr "Queue %1 não encontrada"
-
-#: html/Admin/Queues/Modify.html:66
-msgid "Queue Name"
-msgstr "Nome da Queue"
-
-#: lib/RT/Queue_Overlay.pm:367
-msgid "Queue already exists"
-msgstr "Essa Queue já existe"
-
-#: lib/RT/Queue_Overlay.pm:376 lib/RT/Queue_Overlay.pm:382
-msgid "Queue could not be created"
-msgstr "A Queue não pôde ser criada"
-
-#: html/Ticket/Create.html:319 lib/t/regression/01ticket_link_searching.t:17
-msgid "Queue could not be loaded."
-msgstr ""
-
-#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:386 lib/RT/StyleGuide.pod:807
-msgid "Queue created"
-msgstr "Queue criada"
-
-#: html/SelfService/Display.html:128 lib/RT/CustomField_Overlay.pm:199
-msgid "Queue not found"
-msgstr "Queue não encontrada"
-
-#: html/Admin/Elements/Tabs:61 html/Admin/index.html:74
-msgid "Queues"
-msgstr "Queues"
-
-#: html/Elements/MyAdminQueues:48
-msgid "Queues I administer"
-msgstr ""
-
-#: html/Elements/MySupportQueues:48
-msgid "Queues I'm an AdminCc for"
-msgstr ""
-
-#: html/Elements/Quicksearch:49 html/Prefs/Elements/Tabs:60 html/Prefs/Quicksearch.html:72
-msgid "Quick search"
-msgstr "Pesquisa rápida"
-
-#: html/Elements/QuickCreate:49
-msgid "Quick ticket creation"
-msgstr "Criação de tickets rápida"
-
-#: html/Search/Results.html:83
-msgid "RSS"
-msgstr "RSS"
-
-#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:794
-#. ($RT::VERSION, $RT::rtname)
-msgid "RT %1 for %2"
-msgstr "RT %1 para %2"
-
-#: html/Admin/index.html:48 html/Admin/index.html:49
-msgid "RT Administration"
-msgstr "Administração RT"
-
-#: html/Elements/Error:65 html/SelfService/Error.html:64
-msgid "RT Error"
-msgstr "Erro RT"
-
-#: html/SelfService/Elements/Tabs:72 html/SelfService/Elements/Tabs:74
-msgid "RT Self Service"
-msgstr "RT Self Service"
-
-#: html/Admin/Tools/Configuration.html:75
-msgid "RT Variables"
-msgstr ""
-
-#: html/Admin/Elements/SystemTabs:73 html/Admin/Elements/UserTabs:69 html/Admin/Global/MyRT.html:48 html/Admin/Global/MyRT.html:51 html/Admin/Global/MyRT.html:59 html/Admin/Global/index.html:86 html/Admin/Users/MyRT.html:68 html/Prefs/MyRT.html:68 html/Prefs/MyRT.html:80 html/User/Elements/Tabs:67 html/index.html:1 html/index.html:77
-msgid "RT at a glance"
-msgstr "RT no geral"
-
-#: html/Admin/Users/MyRT.html:77
-#. ($UserObj->Name)
-msgid "RT at a glance for the user %1"
-msgstr "\"RT no geral\" do utilizador %1"
-
-#: html/Admin/CustomFields/Modify.html:119
-msgid "RT can include content from another web service when showing this custom field."
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:108
-msgid "RT can make this custom field's values into hyperlinks to another service."
-msgstr ""
-
-#: html/Elements/SetupSessionCookie:102
-msgid "RT couldn't store your session."
-msgstr ""
-
-#: html/Elements/Logo:51 html/Elements/PageLayout:176
-#. ($RT::rtname)
-msgid "RT for %1"
-msgstr "RT para %1"
-
-#: html/Search/Simple.html:62
-msgid "RT will look for anything else you enter in ticket subjects."
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:110 html/Admin/CustomFields/Modify.html:121
-msgid "RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:81 html/User/Prefs.html:71
-msgid "Real Name"
-msgstr "Nome"
-
-#: html/Tools/MyDay.html:76
-msgid "Record all updates"
-msgstr "Gravar todas as actualizações"
-
-#: lib/RT/Transaction_Overlay.pm:746
-#. ($value)
-msgid "Reference by %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:786
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:743
-#. ($value)
-msgid "Reference to %1 added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:783
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr ""
-
-#: html/Elements/EditLinks:104 html/Elements/EditLinks:157 html/Elements/ShowLinks:94 html/Ticket/Create.html:228 html/Ticket/Elements/BulkLinks:74
-msgid "Referred to by"
-msgstr "Referido por"
-
-#: html/Elements/EditLinks:153 html/Elements/EditLinks:95 html/Elements/SelectLinkType:51 html/Elements/ShowLinks:84 html/Ticket/Create.html:227 html/Ticket/Elements/BulkLinks:70
-msgid "Refers to"
-msgstr "Refere-se a"
-
-#: html/Elements/Refresh:59
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "Refrescar esta pagina de %1 em %1 minutos."
-
-#: lib/RT/Transaction_Overlay.pm:832
-#. ($ticket->Subject)
-msgid "Reminder '%1' added"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:845
-#. ($ticket->Subject)
-msgid "Reminder '%1' completed"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:838
-#. ($ticket->Subject)
-msgid "Reminder '%1' reopened"
-msgstr ""
-
-#: html/Ticket/Reminders.html:48
-#. ($Ticket->Id)
-msgid "Reminder ticket #%1"
-msgstr ""
-
-#: html/Elements/MyReminders:50 html/Ticket/Elements/ShowSummary:77 html/Ticket/Elements/Tabs:124 html/Ticket/Reminders.html:54
-msgid "Reminders"
-msgstr "Lembretes"
-
-#: html/Ticket/Reminders.html:52
-#. ($Ticket->Id)
-msgid "Reminders for ticket #%1"
-msgstr "Notas para o pedido #%1"
-
-#: html/Search/Bulk.html:96
-msgid "Remove AdminCc"
-msgstr ""
-
-#: html/Search/Bulk.html:92
-msgid "Remove Cc"
-msgstr ""
-
-#: html/Search/Bulk.html:88
-msgid "Remove Requestor"
-msgstr ""
-
-#: html/Ticket/Elements/ShowTransaction:182 html/Ticket/Elements/Tabs:149
-msgid "Reply"
-msgstr "Responder"
-
-#: html/Admin/Queues/Modify.html:74
-msgid "Reply Address"
-msgstr "Endereço de Resposta"
-
-#: html/Search/Bulk.html:131 html/Ticket/ModifyAll.html:96 html/Ticket/Update.html:80
-msgid "Reply to requestors"
-msgstr "Responder aos Requerentes"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "Reply to tickets"
-msgstr "Resposta a tickets"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "ReplyToTicket"
-msgstr ""
-
-#: html/Tools/Elements/Tabs:61 html/Tools/Reports/index.html:48 html/Tools/Reports/index.html:49
-msgid "Reports"
-msgstr "Relatórios"
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:113
-msgid "Requestor"
-msgstr ""
-
-#: html/SelfService/Create.html:65 html/Ticket/Create.html:82 html/Ticket/Elements/EditPeople:71 html/Ticket/Elements/ShowPeople:54
-msgid "Requestors"
-msgstr "Requerentes"
-
-#: html/Admin/Queues/Modify.html:98
-msgid "Requests should be due in"
-msgstr ""
-
-#: lib/RT/Attribute_Overlay.pm:148
-#. ('Object')
-msgid "Required parameter '%1' not specified"
-msgstr ""
-
-#: html/Elements/Submit:85
-msgid "Reset"
-msgstr ""
-
-#: html/Admin/Users/MyRT.html:62 html/Prefs/MyRT.html:62
-msgid "Reset to default"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:186 html/User/Prefs.html:86
-msgid "Residence"
-msgstr "Residência"
-
-#: html/Ticket/Elements/Tabs:158
-msgid "Resolve"
-msgstr "Resolver"
-
-#: html/Ticket/Update.html:158
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "Resolver ticket #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:51 lib/RT/Ticket_Overlay.pm:1174
-msgid "Resolved"
-msgstr "Resolvido"
-
-#: html/Tools/Reports/Elements/Tabs:57
-msgid "Resolved by owner"
-msgstr "Resolvido pelo dono"
-
-#: html/Tools/Reports/Elements/Tabs:61
-msgid "Resolved in date range"
-msgstr "Resolvido dentro do intervalo de datas"
-
-#: html/Tools/Reports/ResolvedByDates.html:54
-msgid "Resolved tickets in period, grouped by owner"
-msgstr "Tickets resolvidos no período, agrupados por dono"
-
-#: html/Tools/Reports/ResolvedByOwner.html:52
-msgid "Resolved tickets, grouped by owner"
-msgstr "Tickets resolvidos, agrupados por dono"
-
-#: html/Elements/ListActions:48 html/Search/Elements/NewListActions:49
-msgid "Results"
-msgstr "Resultados"
-
-#: html/Admin/Users/Modify.html:128 html/User/Prefs.html:118
-msgid "Retype Password"
-msgstr "Repita Password"
-
-#: html/Search/Elements/EditSearches:63
-msgid "Revert"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:632
-msgid "Right Delegated"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:322
-msgid "Right Granted"
-msgstr "Direito concedido"
-
-#: lib/RT/ACE_Overlay.pm:180
-msgid "Right Loaded"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:697 lib/RT/ACE_Overlay.pm:718
-msgid "Right could not be revoked"
-msgstr ""
-
-#: html/User/Delegation.html:87
-msgid "Right not found"
-msgstr "Direito não encontrado"
-
-#: lib/RT/ACE_Overlay.pm:562 lib/RT/ACE_Overlay.pm:657
-msgid "Right not loaded."
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:714
-msgid "Right revoked"
-msgstr ""
-
-#: html/Admin/Elements/UserTabs:72
-msgid "Rights"
-msgstr "Direitos"
-
-#: html/Admin/CustomFields/UserRights.html:102 lib/RT/Interface/Web.pm:992
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr ""
-
-#: html/Admin/CustomFields/UserRights.html:132 lib/RT/Interface/Web.pm:1021
-#. ($object_type)
-msgid "Rights could not be revoked for %1"
-msgstr ""
-
-#: html/Admin/Global/GroupRights.html:74 html/Admin/Queues/GroupRights.html:76
-msgid "Roles"
-msgstr "Perfis"
-
-#: html/Prefs/MyRT.html:74
-msgid "Rows per box"
-msgstr "Linhas por caixa"
-
-#: html/Search/Elements/DisplayOptions:95
-msgid "Rows per page"
-msgstr "Linhas por página"
-
-#: lib/RT/Date.pm:424
-msgid "Sat."
-msgstr "Sat"
-
-#: html/Prefs/MyRT.html:74 html/Prefs/Quicksearch.html:66 html/Prefs/Search.html:71 html/Prefs/Search.html:71 html/Search/Elements/EditSearches:72 html/Widgets/SelectionBox:222
-msgid "Save"
-msgstr "Gravar"
-
-#: html/Admin/Groups/Modify.html:94 html/Admin/Queues/Modify.html:113 html/Admin/Queues/People.html:128 html/Admin/Users/Modify.html:243 html/Prefs/Quicksearch.html:66 html/Prefs/SearchOptions.html:65 html/SelfService/Prefs.html:60 html/Ticket/Modify.html:62 html/Ticket/ModifyAll.html:129 html/Ticket/ModifyDates.html:62 html/Ticket/ModifyLinks.html:63 html/Ticket/ModifyPeople.html:62 html/User/Groups/Modify.html:79
-msgid "Save Changes"
-msgstr "Gravar Alterações"
-
-#: html/User/Prefs.html:183
-msgid "Save Preferences"
-msgstr "Gravar Preferências"
-
-#: html/Ticket/Elements/PreviewScrips:133
-msgid "Save changes"
-msgstr "Gravar alterações"
-
-#: lib/RT/SavedSearch.pm:175
-#. ($name)
-msgid "Saved search %1"
-msgstr "Pesquisa gravada %1"
-
-#: html/Search/Elements/EditSearches:102 html/Widgets/SavedSearch:154
-msgid "Saved searches"
-msgstr ""
-
-#: html/Admin/Elements/ListGlobalScrips:62 html/Admin/Global/Scrip.html:79 html/Admin/Queues/Scrip.html:86
-#. ($scrip->Id)
-#. ($id)
-msgid "Scrip #%1"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:205
-msgid "Scrip Created"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:54
-msgid "Scrip Fields"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:111
-msgid "Scrip deleted"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:69 html/Admin/Elements/SystemTabs:56 html/Admin/Global/index.html:64
-msgid "Scrips"
-msgstr ""
-
-#: html/Admin/Queues/Scrips.html:57
-msgid "Scrips which apply to all queues"
-msgstr ""
-
-#: html/Elements/SimpleSearch:50 html/Search/Simple.html:67
-msgid "Search"
-msgstr "Procurar"
-
-#: html/Prefs/SearchOptions.html:49 html/Prefs/SearchOptions.html:52
-msgid "Search Preferences"
-msgstr "Preferências de pesquisa"
-
-#: lib/RT/SavedSearch.pm:117
-msgid "Search attribute load failure"
-msgstr "Erro a carregar o atributo de pesquisa"
-
-#: html/Approvals/Elements/PendingMyApproval:61
-msgid "Search for approvals"
-msgstr "Procurar nas aprovações"
-
-#: html/Search/Simple.html:77
-msgid "Search for tickets"
-msgstr "Procurar tickets"
-
-#: html/Search/Simple.html:59
-msgid "Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>."
-msgstr ""
-
-#: html/User/Elements/Tabs:64
-msgid "Search options"
-msgstr "Opções de pesquisa"
-
-#: html/Search/Chart.html:58
-#. ($PrimaryGroupBy)
-msgid "Search results grouped by %1"
-msgstr "Resultados de pesquisa agrupados por %1"
-
-#: lib/RT/SavedSearch.pm:205
-#. ($msg)
-msgid "Search update: %1"
-msgstr "Actualização de pesquisa: %1"
-
-#: html/Search/Simple.html:61
-msgid "Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>."
-msgstr "Procurar com o texto completo pode levar muito tempo a realizar, mas se for realmente necessário, pode efectuar uma pesquisa por qualquer palavra no historial do pedido ao especificar <b>fulltext:<i>palavra</i></b>."
-
-#: bin/rt-crontool:267
-msgid "Security:"
-msgstr "Segurança:"
-
-#: html/Elements/ShowCustomFields:102
-msgid "See also:"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:107
-msgid "See custom fields"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "See exact outgoing email messages and their recipeients"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "See ticket private commentary"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:105
-msgid "See ticket summaries"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:107
-msgid "SeeCustomField"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:171
-msgid "SeeGroup"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "SeeQueue"
-msgstr ""
-
-#: html/Admin/CustomFields/index.html:48 html/Admin/CustomFields/index.html:51
-msgid "Select a Custom Field"
-msgstr ""
-
-#: html/Admin/Groups/index.html:80
-msgid "Select a group"
-msgstr "Escolha um grupo"
-
-#: html/Admin/Queues/index.html:56
-msgid "Select a queue"
-msgstr "Escolha uma queue"
-
-#: html/SelfService/CreateTicketInQueue.html:50
-msgid "Select a queue for your new ticket"
-msgstr "Escolha uma queue para o novo ticket"
-
-#: html/Admin/Users/index.html:48 html/Admin/Users/index.html:51 html/Admin/Users/index.html:54
-msgid "Select a user"
-msgstr "Escolha um utilizador"
-
-#: html/Admin/Elements/CustomFieldTabs:92
-msgid "Select custom field"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:72
-msgid "Select custom fields for all user groups"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:67
-msgid "Select custom fields for all users"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/index.html:78
-msgid "Select custom fields for tickets in all queues"
-msgstr "Seleccioned os \"Campos Personalizados\" para os pedidos em todas as filas"
-
-#: html/Admin/Global/CustomFields/index.html:85
-msgid "Select custom fields for transactions on tickets in all queues"
-msgstr ""
-
-#: html/Admin/Elements/GroupTabs:77 html/User/Elements/GroupTabs:73
-msgid "Select group"
-msgstr "Escolha grupo"
-
-#: lib/RT/CustomField_Overlay.pm:61
-msgid "Select multiple values"
-msgstr "Escolha múltiplos valores"
-
-#: lib/RT/CustomField_Overlay.pm:62
-msgid "Select one value"
-msgstr "Escolha um valor"
-
-#: html/Admin/Elements/QueueTabs:94
-msgid "Select queue"
-msgstr "Escolha a queue"
-
-#: html/Prefs/Quicksearch.html:55
-msgid "Select queues to be displayed on the \"RT at a glance\" page"
-msgstr "Escolha queues para apresentação na página principal"
-
-#: html/Admin/Global/Scrip.html:61 html/Admin/Global/Scrips.html:59 html/Admin/Queues/Scrip.html:69 html/Admin/Queues/Scrips.html:75
-msgid "Select scrip"
-msgstr ""
-
-#: html/Admin/Global/Template.html:77 html/Admin/Global/Templates.html:59 html/Admin/Queues/Template.html:78 html/Admin/Queues/Templates.html:70
-msgid "Select template"
-msgstr "Escolha template"
-
-#: lib/RT/CustomField_Overlay.pm:63
-msgid "Select up to %1 values"
-msgstr ""
-
-#: html/Admin/Elements/UserTabs:80
-msgid "Select user"
-msgstr "Escolha utilizador"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Selected Custom Fields"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:61
-msgid "Selected objects"
-msgstr ""
-
-#: html/Widgets/SelectionBox:220
-msgid "Selections modified. Please save your changes"
-msgstr ""
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "Enviar email para todos os watchers"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "Enviar email para todos os watchers como um comentário"
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr ""
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr ""
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr ""
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr ""
-
-#: etc/initialdata:94 etc/upgrade/3.1.17/content:7
-msgid "Sends mail to the Ccs"
-msgstr ""
-
-#: etc/initialdata:90 etc/upgrade/3.1.17/content:3
-msgid "Sends mail to the Ccs as a comment"
-msgstr ""
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr ""
-
-#: etc/initialdata:98
-msgid "Sends mail to the administrative Ccs as a comment"
-msgstr ""
-
-#: etc/initialdata:82 etc/initialdata:86
-msgid "Sends mail to the owner"
-msgstr "Enviar email para o dono"
-
-#: lib/RT/Date.pm:451
-msgid "Sep."
-msgstr "Sep"
-
-#: html/Ticket/Elements/ShowTransaction:161
-msgid "Show"
-msgstr "Mostrar"
-
-#: html/Search/Elements/EditFormat:58
-msgid "Show Columns"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:222
-msgid "Show Results"
-msgstr "Mostrar Resultados"
-
-#: html/Approvals/Elements/PendingMyApproval:66
-msgid "Show approved requests"
-msgstr ""
-
-#: html/Ticket/Create.html:391
-msgid "Show basics"
-msgstr "Mostrar informação básica"
-
-#: html/Approvals/Elements/PendingMyApproval:67
-msgid "Show denied requests"
-msgstr ""
-
-#: html/Ticket/Create.html:394
-msgid "Show details"
-msgstr "Mostrar detalhes"
-
-#: html/Approvals/Elements/PendingMyApproval:65
-msgid "Show pending requests"
-msgstr "Mostrar pedidos pendentes"
-
-#: html/Approvals/Elements/PendingMyApproval:68
-msgid "Show requests awaiting other approvals"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "ShowACL"
-msgstr ""
-
-#: lib/RT/System.pm:87
-msgid "ShowConfigTab"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "ShowOutgoingEmail"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:170
-msgid "ShowSavedSearches"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "ShowScrips"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:101
-msgid "ShowTemplate"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:105
-msgid "ShowTicket"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "ShowTicketComments"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Sign up as a ticket Requestor or ticket or queue Cc"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:234 html/User/Prefs.html:170
-msgid "Signature"
-msgstr "Assinatura"
-
-#: html/Elements/Tabs:71
-msgid "Simple Search"
-msgstr "Pesquisa Simples"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:49
-msgid "Single"
-msgstr ""
-
-#: html/Search/Elements/EditFormat:77
-msgid "Size"
-msgstr "Tamanho"
-
-#: html/Elements/Header:91
-msgid "Skip Menu"
-msgstr ""
-
-#: html/Search/Elements/EditFormat:80
-msgid "Small"
-msgstr "Pequeno"
-
-#: html/Admin/CustomFields/Modify.html:122
-msgid "Some browsers may only load content from the same domain as your RT server."
-msgstr ""
-
-#: html/Admin/Elements/AddCustomFieldValue:51 html/Admin/Elements/EditCustomFieldValues:56
-msgid "Sort"
-msgstr "Ordenar"
-
-#: html/Admin/Elements/EditScrip:80
-msgid "Stage"
-msgstr ""
-
-#: html/Elements/SelectDateType:50 html/Ticket/Elements/EditDates:55 html/Ticket/Elements/ShowDates:58
-msgid "Started"
-msgstr "Iniciado"
-
-#: html/Elements/SelectDateType:54 html/Ticket/Create.html:211 html/Ticket/Elements/EditDates:50 html/Ticket/Elements/ShowDates:54
-msgid "Starts"
-msgstr "Começa"
-
-#: html/Admin/Users/Modify.html:165 html/User/Prefs.html:147
-msgid "State"
-msgstr "Estado"
-
-#: html/Search/Elements/PickBasics:89 html/SelfService/Update.html:59 html/Ticket/Create.html:68 html/Ticket/Elements/EditBasics:55 html/Ticket/Elements/ShowBasics:54 html/Ticket/Update.html:61 html/Tools/MyDay.html:70 lib/RT/Ticket_Overlay.pm:1168 lib/RT/Tickets_Overlay.pm:1840
-msgid "Status"
-msgstr "Estado"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "Alteração de Estado"
-
-#: lib/RT/Transaction_Overlay.pm:605
-#. ("'" . $self->loc( $self->OldValue ) . "'", "'" . $self->loc( $self->NewValue ) . "'")
-msgid "Status changed from %1 to %2"
-msgstr "Alteração de estado de %1 para %2"
-
-#: html/Ticket/Elements/Tabs:180
-msgid "Steal"
-msgstr "Roubar"
-
-#: lib/RT/Queue_Overlay.pm:119
-msgid "Steal tickets"
-msgstr "Roubar tickets"
-
-#: lib/RT/Queue_Overlay.pm:119
-msgid "StealTicket"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:699
-#. ($Old->Name)
-msgid "Stolen from %1"
-msgstr "Roubado de %1"
-
-#: html/Search/Elements/EditFormat:83
-msgid "Style"
-msgstr ""
-
-#: html/Elements/QuickCreate:54 html/Elements/SelectAttachmentField:49 html/Search/Bulk.html:134 html/SelfService/Create.html:81 html/SelfService/Update.html:67 html/Ticket/Create.html:110 html/Ticket/Elements/EditBasics:50 html/Ticket/Elements/Reminders:127 html/Ticket/ModifyAll.html:102 html/Ticket/Update.html:84 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:1922
-msgid "Subject"
-msgstr "Assunto"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:813 lib/RT/Transaction_Overlay.pm:721
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "Assunto alterado para %1"
-
-#: html/Elements/Submit:77
-msgid "Submit"
-msgstr "Enviar"
-
-#: lib/RT/Group_Overlay.pm:776
-msgid "Succeeded"
-msgstr ""
-
-#: lib/RT/Date.pm:425
-msgid "Sun."
-msgstr "Sun"
-
-#: lib/RT/System.pm:77
-msgid "SuperUser"
-msgstr ""
-
-#: html/User/Elements/DelegateRights:100
-msgid "System"
-msgstr ""
-
-#: html/Admin/Elements/ToolTabs:56 html/Admin/Tools/Configuration.html:50
-msgid "System Configuration"
-msgstr "Configuração de Sistema"
-
-#: html/Admin/CustomFields/UserRights.html:100 html/Admin/CustomFields/UserRights.html:130 html/Admin/Elements/SelectRights:108 lib/RT/ACE_Overlay.pm:586 lib/RT/Interface/Web.pm:1020 lib/RT/Interface/Web.pm:991
-msgid "System Error"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:226 lib/RT/Transaction_Overlay.pm:232
-#. ($msg)
-msgid "System Error: %1"
-msgstr ""
-
-#: html/Admin/Tools/index.html:49
-msgid "System Tools"
-msgstr "Ferramentas de Sistema"
-
-#: lib/RT/ACE_Overlay.pm:635
-msgid "System error. Right not delegated."
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:165 lib/RT/ACE_Overlay.pm:230 lib/RT/ACE_Overlay.pm:325
-msgid "System error. Right not granted."
-msgstr ""
-
-#: html/Admin/CustomFields/GroupRights.html:60 html/Admin/Global/GroupRights.html:58 html/Admin/Groups/GroupRights.html:60 html/Admin/Queues/GroupRights.html:59
-msgid "System groups"
-msgstr "Grupos de sistema"
-
-#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
-msgid "SystemRolegroup for internal use"
-msgstr ""
-
-#: lib/RT/CurrentUser.pm:359
-msgid "TEST_STRING"
-msgstr ""
-
-#: etc/initialdata:603 html/Search/Elements/EditFormat:74 html/Ticket/Elements/Tabs:172
-msgid "Take"
-msgstr "Tomar"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "Take tickets"
-msgstr "Tomar pedidos"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "TakeTicket"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:684
-msgid "Taken"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:73 html/Tools/Offline.html:80
-msgid "Template"
-msgstr "Template"
-
-#: html/Admin/Global/Template.html:110 html/Admin/Queues/Template.html:115
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "Template $%1"
-
-#: html/Admin/Elements/EditTemplates:112
-msgid "Template deleted"
-msgstr "Template apagado"
-
-#: lib/RT/Scrip_Overlay.pm:178
-msgid "Template is mandatory argument"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:182
-msgid "Template not found"
-msgstr ""
-
-#: lib/RT/Template_Overlay.pm:346
-msgid "Template parsed"
-msgstr ""
-
-#: lib/RT/Template_Overlay.pm:398
-msgid "Template parsing error"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:72 html/Admin/Elements/SystemTabs:59 html/Admin/Global/index.html:68
-msgid "Templates"
-msgstr "Templates"
-
-#: lib/RT/CustomField_Overlay.pm:946 lib/RT/Record.pm:962
-msgid "That is already the current value"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:415
-msgid "That is not a value for this custom field"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1987
-msgid "That is the same value"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:307 lib/RT/ACE_Overlay.pm:616
-msgid "That principal already has that right"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:748
-#. ($args{'Type'})
-msgid "That principal is already a %1 for this queue"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1428
-#. ($self->loc($args{'Type'}))
-msgid "That principal is already a %1 for this ticket"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:856
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1550
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this ticket"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1983
-msgid "That queue does not exist"
-msgstr "Essa queue não existe"
-
-#: lib/RT/Ticket_Overlay.pm:3250
-msgid "That ticket has unresolved dependencies"
-msgstr ""
-
-#: lib/RT/Action/CreateTickets.pm:712 lib/RT/Ticket_Overlay.pm:3053
-msgid "That user already owns that ticket"
-msgstr "Esse utilizador já é dono desse ticket"
-
-#: lib/RT/Ticket_Overlay.pm:2996
-msgid "That user does not exist"
-msgstr "Esse utilizador não existe"
-
-#: lib/RT/User_Overlay.pm:391
-msgid "That user is already privileged"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:412
-msgid "That user is already unprivileged"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:404
-msgid "That user is now privileged"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:425
-msgid "That user is now unprivileged"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3046
-msgid "That user may not own tickets in that queue"
-msgstr "Esse utilizador não pode ser dono de tickets nessa queue"
-
-#: lib/RT/Link_Overlay.pm:235
-msgid "That's not a numerical id"
-msgstr ""
-
-#: html/SelfService/Display.html:55 html/Ticket/Create.html:180 html/Ticket/Elements/ShowSummary:51
-msgid "The Basics"
-msgstr "O Básico"
-
-#: lib/RT/ACE_Overlay.pm:114
-msgid "The CC of a ticket"
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:115
-msgid "The administrative CC of a ticket"
-msgstr ""
-
-#: bin/rt-crontool:277
-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 ""
-
-#: lib/RT/Record.pm:965
-msgid "The new value has been set."
-msgstr ""
-
-#: lib/RT/ACE_Overlay.pm:112
-msgid "The owner of a ticket"
-msgstr "O dono de um ticket"
-
-#: lib/RT/ACE_Overlay.pm:113
-msgid "The requestor of a ticket"
-msgstr ""
-
-#: html/Admin/Elements/EditUserComments:49
-msgid "These comments aren't generally visible to the user"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:981
-msgid "This custom field does not apply to that object"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:52
-msgid "This feature is only available to system administrators"
-msgstr "Esta funcionalidade está disponível apenas para os administradores do sistema"
-
-#: html/Elements/SetupSessionCookie:106
-#. ($RT::MasonSessionDir)
-msgid "This may mean that that the directory '%1' isn't writable or a database table is missing or corrupt."
-msgstr ""
-
-#: html/Ticket/Elements/PreviewScrips:98
-msgid "This message will be sent to..."
-msgstr "Esta mensagem será enviada para..."
-
-#: bin/rt-crontool:268
-msgid "This tool allows the user to run arbitrary perl modules from within RT."
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:327
-msgid "This transaction appears to have no content"
-msgstr "Esta transacção não parece ter conteúdo"
-
-#: html/Ticket/Elements/ShowRequestor:72
-#. ($rows)
-msgid "This user's %1 highest priority tickets"
-msgstr "%1 tickets deste utilizador com maior prioridade"
-
-#: lib/RT/Date.pm:422
-msgid "Thu."
-msgstr "Thu"
-
-#: html/Ticket/ModifyAll.html:48 html/Ticket/ModifyAll.html:52
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr ""
-
-#: html/Approvals/Elements/ShowDependency:69
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "Ticket #%1: %2"
-
-#: lib/RT/Action/CreateTickets.pm:1352 lib/RT/Action/CreateTickets.pm:1361 lib/RT/Action/CreateTickets.pm:607 lib/RT/Action/CreateTickets.pm:731 lib/RT/Action/CreateTickets.pm:743
-#. ($T::Tickets{$template_id}->Id)
-#. ($T::Tickets{$template_id}->id)
-#. ($ticket->Id)
-msgid "Ticket %1"
-msgstr "Ticket %1"
-
-#: lib/RT/Ticket_Overlay.pm:757 lib/RT/Ticket_Overlay.pm:777
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "Ticket %1 criado na queue '%2'"
-
-#: html/Search/Bulk.html:380 html/Tools/MyDay.html:103 html/Tools/MyDay.html:94 html/Tools/MyDay.html:97
-#. ($Ticket->Id, $_)
-#. ($id, $msg)
-msgid "Ticket %1: %2"
-msgstr "Ticket %1: %2"
-
-#: html/Admin/Elements/QueueTabs:76
-msgid "Ticket Custom Fields"
-msgstr ""
-
-#: html/Ticket/History.html:48 html/Ticket/History.html:51
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "Histórico do ticket # %1 %2"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "Ticket Resolvido"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:71 html/Admin/Global/CustomFields/index.html:83 lib/RT/CustomField_Overlay.pm:1210
-msgid "Ticket Transactions"
-msgstr "Transacções do ticket"
-
-#: lib/RT/Tickets_Overlay.pm:2109
-msgid "Ticket content"
-msgstr "Conteúdo do ticket"
-
-#: lib/RT/Tickets_Overlay.pm:2158
-msgid "Ticket content type"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:605 lib/RT/Ticket_Overlay.pm:619 lib/RT/Ticket_Overlay.pm:630 lib/RT/Ticket_Overlay.pm:765
-msgid "Ticket could not be created due to an internal error"
-msgstr ""
-
-#: html/Ticket/Create.html:247
-msgid "Ticket could not be loaded"
-msgstr ""
-
-#: html/Ticket/Display.html:57
-msgid "Ticket metadata"
-msgstr ""
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "Estado do ticket alterado"
-
-#: lib/RT/Search/FromSQL.pm:84
-#. (ref $self)
-msgid "TicketSQL search module"
-msgstr ""
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:66 html/Admin/Global/CustomFields/index.html:77 html/Elements/Tabs:74 html/Search/Chart:113 html/Search/Elements/Chart:111 lib/RT/CustomField_Overlay.pm:1209
-msgid "Tickets"
-msgstr "Tickets"
-
-#: lib/RT/Tickets_Overlay.pm:2350
-#. ($self->loc( $args{'TYPE'} ), ( $args{'BASE'} || $args{'TICKET'} ))
-msgid "Tickets %1 %2"
-msgstr "Tickets %1 %2"
-
-#: lib/RT/Tickets_Overlay.pm:2302
-#. ($self->loc( $args{'TYPE'} ), ( $args{'TARGET'} || $args{'TICKET'} ))
-msgid "Tickets %1 by %2"
-msgstr "Tickets %1 por %2"
-
-#: html/Tools/Reports/CreatedByDates.html:88
-msgid "Tickets created after"
-msgstr "Tickets criados depois de"
-
-#: html/Tools/Reports/CreatedByDates.html:90
-msgid "Tickets created before"
-msgstr "Tickets criados antes de"
-
-#: html/Tools/Reports/ResolvedByDates.html:89
-msgid "Tickets resolved after"
-msgstr "Tickets resolvidos depois de"
-
-#: html/Tools/Reports/ResolvedByDates.html:91
-msgid "Tickets resolved before"
-msgstr "Tickets resolvidos antes de"
-
-#: html/Approvals/Elements/ShowDependency:50
-msgid "Tickets which depend on this approval:"
-msgstr "Pedidos que dependem desta aprovação"
-
-#: html/Search/Elements/PickBasics:136 html/Ticket/Create.html:186 html/Ticket/Elements/EditBasics:74
-msgid "Time Estimated"
-msgstr "Tempo previsto"
-
-#: html/Search/Elements/PickBasics:137 html/Ticket/Create.html:199 html/Ticket/Elements/EditBasics:87 lib/RT/Tickets_Overlay.pm:2080
-msgid "Time Left"
-msgstr "Tempo disponível"
-
-#: html/Search/Elements/PickBasics:135 html/Ticket/Create.html:192 html/Ticket/Elements/EditBasics:80 lib/RT/Tickets_Overlay.pm:2055
-msgid "Time Worked"
-msgstr "Tempo de trabalho"
-
-#: html/Elements/Footer:53
-msgid "Time to display"
-msgstr "Tempo usado para disponibilizar página"
-
-#: lib/RT/Ticket_Overlay.pm:1169
-msgid "TimeWorked"
-msgstr ""
-
-#: html/Search/Elements/EditFormat:76
-msgid "Title"
-msgstr ""
-
-#: html/Elements/Footer:64
-#. ('<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>')
-msgid "To inquire about support, training, custom development or licensing, please contact %1."
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:1172
-msgid "Told"
-msgstr ""
-
-#: html/Admin/Elements/Tabs:70 html/Admin/index.html:90 html/Elements/Tabs:77 html/Tools/index.html:48 html/Tools/index.html:51
-msgid "Tools"
-msgstr "Ferramentas"
-
-#: html/Search/Elements/Chart:132
-msgid "Total"
-msgstr "Total"
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "Transacção"
-
-#: lib/RT/Transaction_Overlay.pm:826
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:185
-msgid "Transaction Created"
-msgstr "Transacção Criada"
-
-#: html/Admin/Elements/QueueTabs:80
-msgid "Transaction Custom Fields"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:130
-msgid "Transaction->Create couldn't, as you didn't specify an object type and id"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:891
-msgid "Transactions are immutable"
-msgstr ""
-
-#: lib/RT/Date.pm:420
-msgid "Tue."
-msgstr "Tue"
-
-#: html/Admin/CustomFields/Modify.html:68 html/Admin/Elements/EditCustomField:67 html/Ticket/Elements/AddWatchers:56 html/Ticket/Elements/AddWatchers:67 html/Ticket/Elements/AddWatchers:77 lib/RT/Ticket_Overlay.pm:1170 lib/RT/Tickets_Overlay.pm:1894
-msgid "Type"
-msgstr "Tipo"
-
-#: lib/RT/ScripCondition_Overlay.pm:130
-msgid "Unimplemented"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:91
-msgid "Unix login"
-msgstr ""
-
-#: lib/RT/Attachment_Overlay.pm:291 lib/RT/Record.pm:863
-#. ($self->ContentEncoding)
-#. ($ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr ""
-
-#: html/Search/Build.html:461 lib/RT/Report/Tickets.pm:410
-#. ($key)
-msgid "Unknown field: %1"
-msgstr ""
-
-#: html/Elements/SelectResultsPerPage:60
-msgid "Unlimited"
-msgstr ""
-
-#: html/Search/Elements/SelectSearchesForObjects:66
-msgid "Unnamed search"
-msgstr "Procura sem nome"
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr ""
-
-#: html/Admin/Elements/EditCustomFields:62
-msgid "Unselected Custom Fields"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:63
-msgid "Unselected objects"
-msgstr ""
-
-#: lib/RT/Transaction_Overlay.pm:680
-msgid "Untaken"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:130 html/Elements/RT__Ticket/ColumnMap:304 html/Search/Bulk.html:195 html/Search/Bulk.html:77
-msgid "Update"
-msgstr "Actualizar"
-
-#: html/Ticket/Update.html:137
-msgid "Update Ticket"
-msgstr "Actualizar Ticket"
-
-#: html/Search/Bulk.html:128 html/Ticket/ModifyAll.html:89 html/Ticket/Update.html:74
-msgid "Update Type"
-msgstr "Tipo de actualização"
-
-#: html/Search/Bulk.html:202 html/Search/Results.html:80
-msgid "Update multiple tickets"
-msgstr "Actualizar múltiplos tickets"
-
-#: lib/RT/Action/CreateTickets.pm:752 lib/RT/Interface/Web.pm:611
-msgid "Update not recorded."
-msgstr ""
-
-#: html/Ticket/ModifyAll.html:86
-msgid "Update ticket"
-msgstr "Actualizar ticket"
-
-#: html/SelfService/Update.html:114 html/SelfService/Update.html:49
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "Actualizar ticket #%1"
-
-#: html/Ticket/Update.html:160
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "Actualizar ticket #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:750 lib/RT/Interface/Web.pm:610
-msgid "Update type was neither correspondence nor comment."
-msgstr ""
-
-#: html/Elements/SelectDateType:56 html/Ticket/Elements/ShowDates:74 lib/RT/CustomField_Overlay.pm:1287 lib/RT/Ticket_Overlay.pm:1173
-msgid "Updated"
-msgstr "Actualizado"
-
-#: html/Tools/Offline.html:95
-msgid "Upload"
-msgstr "Carregar"
-
-#: lib/RT/CustomField_Overlay.pm:86
-msgid "Upload multiple files"
-msgstr "Carregar múltiplos ficheiros"
-
-#: lib/RT/CustomField_Overlay.pm:81
-msgid "Upload multiple images"
-msgstr "Carregar múltiplas imagens"
-
-#: lib/RT/CustomField_Overlay.pm:87
-msgid "Upload one file"
-msgstr "Carregar um ficheiro"
-
-#: lib/RT/CustomField_Overlay.pm:82
-msgid "Upload one image"
-msgstr "Carregar uma imagem"
-
-#: lib/RT/CustomField_Overlay.pm:88
-msgid "Upload up to %1 files"
-msgstr "Carregar até %1 ficheiros"
-
-#: lib/RT/CustomField_Overlay.pm:83
-msgid "Upload up to %1 images"
-msgstr "Carregar até %1 imagens"
-
-#: html/Tools/Offline.html:95
-msgid "Upload your changes"
-msgstr ""
-
-#: html/Admin/index.html:92
-msgid "Use other RT administrative tools"
-msgstr "Utilizar outras ferramentas administrativas do RT"
-
-#: lib/RT/Ticket_Overlay.pm:508
-#. ($args{'Owner'})
-msgid "User '%1' could not be found."
-msgstr "Utilizador '%1' não encontrado"
-
-#: etc/initialdata:132 etc/initialdata:206
-msgid "User Defined"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:95
-msgid "User Defined conditions and actions"
-msgstr ""
-
-#: html/Admin/Elements/CustomFieldTabs:74 html/Admin/Elements/GroupTabs:70 html/Admin/Elements/QueueTabs:87 html/Admin/Elements/SystemTabs:70 html/Admin/Global/index.html:82
-msgid "User Rights"
-msgstr "Direitos de utilizador"
-
-#: lib/RT/Interface/Web.pm:1392
-#. ($cf->Name, ref $args{'Object'}, $args{'Object'}->id)
-msgid "User asked for an unknown update type for custom field %1 for %2 object #%3"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:305
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "Utilizador não criado: %1"
-
-#: lib/RT/User_Overlay.pm:332
-msgid "User created"
-msgstr "Utilizador criado"
-
-#: html/Admin/CustomFields/GroupRights.html:76 html/Admin/Global/GroupRights.html:90 html/Admin/Groups/GroupRights.html:77 html/Admin/Queues/GroupRights.html:92
-msgid "User defined groups"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:594 lib/RT/User_Overlay.pm:614
-msgid "User loaded"
-msgstr "Utilizador carregado"
-
-#: html/Admin/Groups/index.html:105
-msgid "User-defined groups"
-msgstr "Grupos definidos por utilizadores"
-
-#: html/Admin/Users/Modify.html:71 html/Elements/Login:92 html/Ticket/Elements/AddWatchers:58
-msgid "Username"
-msgstr "Username"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:57 html/Admin/Elements/SelectNewGroupMembers:49 html/Admin/Elements/Tabs:55 html/Admin/Global/CustomFields/index.html:66 html/Admin/Groups/Members.html:78 html/Admin/Queues/People.html:91 html/Admin/index.html:64 html/User/Groups/Members.html:81 lib/RT/CustomField_Overlay.pm:1211
-msgid "Users"
-msgstr "Utilizadores"
-
-#: html/Admin/Users/index.html:87
-msgid "Users matching search criteria"
-msgstr "Utilizadores que verificam o critério de pesquisa"
-
-#: bin/rt-crontool:136
-#. ($transaction->id)
-msgid "Using transaction #%1..."
-msgstr ""
-
-#: lib/RT/Tickets_Overlay_SQL.pm:530
-msgid "Valid Query"
-msgstr "Query válida"
-
-#: html/Admin/CustomFields/Modify.html:82
-msgid "Validation"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:132 html/Admin/Elements/EditCustomField:80
-msgid "Values"
-msgstr "Valores"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Watch"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "WatchAsAdminCc"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:65
-msgid "Watchers"
-msgstr ""
-
-#: lib/RT/Date.pm:421
-msgid "Wed."
-msgstr "Wed"
-
-#: html/Tools/MyDay.html:80
-msgid "What I did today"
-msgstr ""
-
-#: etc/initialdata:521
-msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
-msgstr ""
-
-#: etc/initialdata:485
-msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
-msgstr ""
-
-#: etc/initialdata:146
-msgid "When a ticket is created"
-msgstr "Quando um ticket é criado"
-
-#: etc/initialdata:418
-msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
-msgstr ""
-
-#: etc/initialdata:151
-msgid "When anything happens"
-msgstr "Quando algo acontece"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "Quando um ticket é resolvido"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr ""
-
-#: etc/initialdata:178 etc/upgrade/3.1.17/content:16
-msgid "Whenever a ticket's priority changes"
-msgstr ""
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr ""
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr ""
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr ""
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr ""
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:191 html/User/Prefs.html:90
-msgid "Work"
-msgstr ""
-
-#: html/Search/Results.html:84
-msgid "Work offline"
-msgstr ""
-
-#: html/Ticket/Elements/ShowBasics:65 html/Ticket/Update.html:66 html/Tools/MyDay.html:65
-msgid "Worked"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3157
-msgid "You already own this ticket"
-msgstr "Este ticket já é seu"
-
-#: html/autohandler:216 html/autohandler:224
-msgid "You are not an authorized user"
-msgstr ""
-
-#: html/Prefs/Search.html:58
-msgid "You can also edit the predefined search itself"
-msgstr "Também pode editar a própria procura personalizada"
-
-#: lib/RT/Ticket_Overlay.pm:3039
-msgid "You can only reassign tickets that you own or that are unowned"
-msgstr "Só pode atribuir um pedido que seja seu ou que não proprietário"
-
-#: lib/RT/Ticket_Overlay.pm:3035
-msgid "You can only take tickets that are unowned"
-msgstr "Só pode responsabilizar-se por tickets que não têm dono"
-
-#: docs/design_docs/string-extraction-guide.txt:47 lib/RT/StyleGuide.pod:778
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "Encontrou %1 tickets na queue %2"
-
-#: html/NoAuth/Logout.html:54
-msgid "You have been logged out of RT."
-msgstr "Saiu do RT"
-
-#: html/SelfService/Display.html:135
-msgid "You have no permission to create tickets in that queue."
-msgstr "Não tem permissão para criar tickets nessa queue."
-
-#: lib/RT/Ticket_Overlay.pm:1996
-msgid "You may not create requests in that queue."
-msgstr "Não pode criar pedidos nessa queue"
-
-#: html/NoAuth/Logout.html:58
-msgid "You're welcome to login again"
-msgstr ""
-
-#: etc/initialdata:502 etc/initialdata:504
-#. (# loc $self->TransactionObj->CreatorObj->Name,)
-msgid "Your request has been approved by %1. Other approvals may still be pending."
-msgstr ""
-
-#: etc/initialdata:540
-msgid "Your request has been approved."
-msgstr ""
-
-#: etc/initialdata:445
-msgid "Your request was rejected."
-msgstr ""
-
-#: html/autohandler:253
-msgid "Your username or password is incorrect"
-msgstr "Login ou password errados"
-
-#: html/Admin/Users/Modify.html:171 html/User/Prefs.html:151
-msgid "Zip"
-msgstr "Código Postal"
-
-#: html/Search/Elements/DisplayOptions:67
-msgid "[none]"
-msgstr ""
-
-#: lib/RT/System.pm:89
-msgid "allow creation of saved searches"
-msgstr ""
-
-#: lib/RT/System.pm:88
-msgid "allow loading of saved searches"
-msgstr ""
-
-#: html/User/Elements/DelegateRights:82
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr ""
-
-#: html/Search/Results.html:85
-msgid "chart"
-msgstr ""
-
-#: html/SelfService/Closed.html:51
-msgid "closed"
-msgstr "fechado"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:57
-msgid "contains"
-msgstr "contém"
-
-#: lib/RT/Report/Tickets.pm:347
-msgid "current: $current, want $want, Error near ->$val<- expecting a $token in '$string'\\n"
-msgstr ""
-
-#: html/Admin/Queues/Modify.html:100 lib/RT/Date.pm:348
-msgid "days"
-msgstr "dias"
-
-#: lib/RT/Queue_Overlay.pm:89
-msgid "deleted"
-msgstr "apagado"
-
-#: html/Search/Elements/PickBasics:63
-msgid "does not match"
-msgstr ""
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:58
-msgid "doesn't contain"
-msgstr "não contém"
-
-#: html/Elements/SelectEqualityOperator:61
-msgid "equal to"
-msgstr "igual a"
-
-#: html/Search/Build.html:553
-msgid "error: can't move down"
-msgstr ""
-
-#: html/Search/Build.html:575
-msgid "error: can't move left"
-msgstr ""
-
-#: html/Search/Build.html:534
-msgid "error: can't move up"
-msgstr ""
-
-#: html/Search/Build.html:618
-msgid "error: nothing to delete"
-msgstr ""
-
-#: html/Search/Build.html:539 html/Search/Build.html:558 html/Search/Build.html:580 html/Search/Build.html:609
-msgid "error: nothing to move"
-msgstr ""
-
-#: html/Search/Build.html:636
-msgid "error: nothing to toggle"
-msgstr ""
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectEqualityOperator:61
-msgid "greater than"
-msgstr "maior do que"
-
-#: lib/RT/Group_Overlay.pm:216
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "grupo '%1'"
-
-#: html/Search/Results.html:90
-#. ($m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query))
-msgid "grouped by %1"
-msgstr "agrupado por %1"
-
-#: lib/RT/Date.pm:344
-msgid "hours"
-msgstr "horas"
-
-#: html/Search/Elements/PickBasics:50
-msgid "id"
-msgstr "id"
-
-#: html/Elements/SelectBoolean:55 html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:59 html/Search/Elements/PickBasics:164 html/Search/Elements/PickBasics:76 html/Search/Elements/PickBasics:92 html/Search/Elements/PickCFs:55
-msgid "is"
-msgstr "é"
-
-#: html/Elements/SelectBoolean:59 html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectMatch:60 html/Search/Elements/PickBasics:165 html/Search/Elements/PickBasics:77 html/Search/Elements/PickBasics:93 html/Search/Elements/PickCFs:56
-msgid "isn't"
-msgstr "não é"
-
-#: html/Elements/SelectCustomFieldOperator:61 html/Elements/SelectEqualityOperator:61
-msgid "less than"
-msgstr "menos do que"
-
-#: html/Search/Elements/PickBasics:62
-msgid "matches"
-msgstr "coincide"
-
-#: lib/RT/Date.pm:340
-msgid "min"
-msgstr ""
-
-#: html/Tools/MyDay.html:65
-msgid "minutes"
-msgstr "minutos"
-
-#: lib/RT/Date.pm:356
-msgid "months"
-msgstr "meses"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "new"
-msgstr "novo"
-
-#: html/Admin/Elements/PickCustomFields:66 html/Admin/Elements/PickObjects:67
-msgid "no name"
-msgstr "sem nome"
-
-#: html/Admin/Elements/EditScrips:66
-msgid "no value"
-msgstr "sem valor"
-
-#: html/Admin/Elements/EditQueueWatchers:50 html/Ticket/Elements/EditWatchers:51
-msgid "none"
-msgstr "nenhum"
-
-#: html/Elements/SelectEqualityOperator:61
-msgid "not equal to"
-msgstr "diferente de"
-
-#: html/SelfService/Elements/MyRequests:78 lib/RT/Queue_Overlay.pm:85
-msgid "open"
-msgstr "aberto"
-
-#: lib/RT/Group_Overlay.pm:221
-#. ($self->Name, $user->Name)
-msgid "personal group '%1' for user '%2'"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:229
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "queue %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:88
-msgid "rejected"
-msgstr "rejeitado"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "resolved"
-msgstr "resolvido"
-
-#: lib/RT/Date.pm:336
-msgid "sec"
-msgstr ""
-
-#: lib/RT/System.pm:87
-msgid "show Configuration tab"
-msgstr ""
-
-#: html/Search/Results.html:82
-msgid "spreadsheet"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "stalled"
-msgstr "pendente"
-
-#: html/Search/Results.html:91
-#. ($m->scomp('Elements/SelectChartType', Name => 'ChartStyle'))
-msgid "style: %1"
-msgstr ""
-
-#: html/Prefs/MyRT.html:95
-msgid "summary rows"
-msgstr "linhas de sumário"
-
-#: lib/RT/Group_Overlay.pm:224
-#. ($self->Type)
-msgid "system %1"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:235
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr ""
-
-#: html/Elements/Error:66 html/SelfService/Error.html:65
-msgid "the calling component did not specify why"
-msgstr ""
-
-#: lib/RT/Group_Overlay.pm:232
-#. ($self->Instance, $self->Type)
-msgid "ticket #%1 %2"
-msgstr "ticket #%1 %2"
-
-#: lib/RT/Group_Overlay.pm:238
-#. ($self->Id)
-msgid "undescribed group %1"
-msgstr "grupo indefinido %1"
-
-#: lib/RT/Group_Overlay.pm:213
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "utilizador %1"
-
-#: lib/RT/Date.pm:352
-msgid "weeks"
-msgstr "semanas"
-
-#: lib/RT/Date.pm:360
-msgid "years"
-msgstr "anos"
-
diff --git a/rt/lib/RT/I18N/zh_cn.po b/rt/lib/RT/I18N/zh_cn.po
deleted file mode 100644
index 23dae6f..0000000
--- a/rt/lib/RT/I18N/zh_cn.po
+++ /dev/null
@@ -1,8423 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: RT 3.6.x\n"
-"PO-Revision-Date: 2007-12-09 13:05+0800\n"
-"Last-Translator: Audrey Tang <cpan@audreyt.org>\n"
-"Language-Team: rt-devel <rt-devel@lists.bestpractical.com>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: html/Widgets/SavedSearch:70
-#. ($self->{CurrentSearch}{Object}->Description)
-msgid " %1 deleted."
-msgstr " 已删除 %1。"
-
-#: html/Widgets/SavedSearch:47
-#. ($self->{CurrentSearch}{Description}, $args->{Description})
-msgid " %1 renamed to %2."
-msgstr " %1 已更å为 %2。"
-
-#: html/Widgets/SavedSearch:60
-#. ($args->{Description})
-msgid " %1 saved."
-msgstr " %1 已储存。"
-
-#: NOT FOUND IN SOURCE
-msgid "#"
-msgstr "#"
-
-#: NOT FOUND IN SOURCE
-msgid "#%1"
-msgstr "#%1"
-
-#: html/Approvals/Elements/Approve:48 html/Approvals/Elements/ShowDependency:71 html/SelfService/Display.html:46 html/Ticket/Display.html:47 html/Ticket/Display.html:51
-#. ($Ticket->id, $Ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-#. ($ticket->Id, $ticket->Subject)
-#. ($TicketObj->Id, $TicketObj->Subject)
-msgid "#%1: %2"
-msgstr "#%1: %2"
-
-#: html/Elements/ShowSearch:105
-msgid "$1"
-msgstr "$1"
-
-#: lib/RT/Record.pm:940
-#. ($label)
-msgid "$prefix %1"
-msgstr "$prefix %1"
-
-#: 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/URI/fsck_com_rt.pm:256
-#. ($self->ObjectType, $self->Object->Id)
-msgid "%1 #%2"
-msgstr "%1 #%2"
-
-#: lib/RT/Date.pm:365
-#. ($s, $time_unit)
-msgid "%1 %2"
-msgstr "%1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 %2 %3"
-msgstr "%1 %2 %3"
-
-#: lib/RT/Date.pm:401
-#. ($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/Record.pm:1685 lib/RT/Transaction_Overlay.pm:647 lib/RT/Transaction_Overlay.pm:690
-#. ($cf->Name, $new_value->Content)
-#. ($field, $self->NewValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 added"
-msgstr "%2 已新增为 %1"
-
-#: lib/RT/Date.pm:362
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "%1 %2 之å‰"
-
-#: lib/RT/Record.pm:1692 lib/RT/Transaction_Overlay.pm:654
-#. ($cf->Name, $old_content, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 已从 %2 改为 %3"
-
-#: lib/RT/Record.pm:1689 lib/RT/Transaction_Overlay.pm:650 lib/RT/Transaction_Overlay.pm:696
-#. ($cf->Name, $old_value->Content)
-#. ($field, $self->OldValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 deleted"
-msgstr "%2 已自 %1 删除"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 %2 of group %3"
-msgstr "%3 群组的 %1 %2"
-
-#: html/Admin/Elements/EditScrips:65 html/Admin/Elements/ListGlobalScrips:63 html/Ticket/Elements/PreviewScrips:103
-#. (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/Ticket/Elements/ShowAttachments:72
-#. ($rev->CreatedAsString, $size, $rev->CreatorObj->Name)
-msgid "%1 (%2) by %3"
-msgstr "%1 (%2) - %3"
-
-#: html/SelfService/Update.html:60 html/Ticket/Elements/EditBasics:108 html/Ticket/Update.html:61 html/Ticket/Update.html:63 html/Tools/MyDay.html:66
-#. (loc($DefaultStatus))
-#. (loc($Ticket->Status()))
-#. (loc($TicketObj->Status))
-#. ($TicketObj->OwnerObj->Name())
-msgid "%1 (Unchanged)"
-msgstr "%1 (未更改)"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 - %2 shown"
-msgstr "显示第 %1 - %2 笔"
-
-#: bin/rt-crontool:237 bin/rt-crontool:244 bin/rt-crontool:250
-#. ("--search-argument", "--search")
-#. ("--condition-argument", "--condition")
-#. ("--action-argument", "--action")
-msgid "%1 - An argument to pass to %2"
-msgstr "%1 - 传递给 %2 的一个å‚æ•°"
-
-#: bin/rt-crontool:262
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr "%1 - 将更新状æ€è¾“出到 STDOUT"
-
-#: bin/rt-crontool:253
-#. ("--template-id")
-msgid "%1 - Specify id of the template you want to use"
-msgstr "%1 - 指定欲使用的模æ¿ç¼–å·"
-
-#: bin/rt-crontool:256
-#. ("--transaction")
-msgid "%1 - Specify if you want to use either 'first' or 'last' tarnsaction"
-msgstr "%1 - 指定欲使用的更动为 'first' (第一项) 或 'last' (最åŽä¸€é¡¹)"
-
-#: bin/rt-crontool:247
-#. ("--action")
-msgid "%1 - Specify the action module you want to use"
-msgstr "%1 - 指定欲使用的动作模å—"
-
-#: bin/rt-crontool:241
-#. ("--condition")
-msgid "%1 - Specify the condition module you want to use"
-msgstr "%1 - 指定欲使用的æ¡ä»¶æ¨¡å—"
-
-#: bin/rt-crontool:234
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr "%1 - 指定欲使用的查询模å—"
-
-#: bin/rt-crontool:259
-#. ("--transaction-type")
-msgid "%1 - Specify the type of a transaction you want to use"
-msgstr "%1 - 指定欲使用的更动类别"
-
-#: html/Elements/Footer:56
-#. ('&#187;&#124;&#171;', $RT::VERSION, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',)
-msgid "%1 RT %2 Copyright 1996-%3 %4."
-msgstr "%1 RT %2 版,%4 版æƒæ‰€æœ‰ï¼Œ1996-%3。"
-
-#: lib/RT/ScripAction_Overlay.pm:150
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr "加载手续 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 Total"
-msgstr "共 %1 笔"
-
-#: lib/RT/Record.pm:1722
-#. ($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:144 lib/RT/Link_Overlay.pm:151
-#. ($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:73 lib/RT/Transaction_Overlay.pm:531
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-msgid "%1 by %2"
-msgstr "%1 (%2)"
-
-#: lib/RT/Transaction_Overlay.pm:788 lib/RT/Transaction_Overlay.pm:797 lib/RT/Transaction_Overlay.pm:800
-#. ($self->Field , $q1->Name , $q2->Name)
-#. ($self->Field, $t2->AsString, $t1->AsString)
-#. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'")
-msgid "%1 changed from %2 to %3"
-msgstr "%1 的值从 %2 改为 %3"
-
-#: html/Search/Build.html:213
-#. ($Description)
-msgid "%1 copy"
-msgstr "%1 å¤åˆ¶"
-
-#: lib/RT/Record.pm:944
-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:2787
-#. ($self)
-msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
-msgstr "%1 无法将现况设æˆå·²è§£å†³ã€‚RT æ•°æ®åº“内容å¯èƒ½ä¸ä¸€è‡´ã€‚"
-
-#: lib/RT/Transaction_Overlay.pm:571
-#. ($obj_type)
-msgid "%1 created"
-msgstr "已建立 %1"
-
-#: lib/RT/Transaction_Overlay.pm:576
-#. ($obj_type)
-msgid "%1 deleted"
-msgstr "已删除 %1"
-
-#: etc/initialdata:593
-msgid "%1 highest priority tickets I own"
-msgstr "å‰ %1 份待处ç†ç”³è¯·å•"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I own..."
-msgstr "å‰ %1 份待处ç†ç”³è¯·å•..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I requested..."
-msgstr "å‰ %1 份é€å‡ºçš„申请å•..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets pending my approval..."
-msgstr "å‰ %1 份待签核申请å•..."
-
-#: bin/rt-crontool:229
-#. ($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:863
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this queue."
-msgstr "%1 å·²ä¸å†æ˜¯æ­¤è¡¨å•çš„ %2。"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 is no longer a %2 for this ticket."
-msgstr "%1 å·²ä¸å†æ˜¯æ­¤ç”³è¯·å•çš„ %2。"
-
-#: NOT FOUND IN SOURCE
-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/ShowTime:47 html/Ticket/Elements/ShowTime:49
-#. ($minutes)
-msgid "%1 min"
-msgstr "%1 分钟"
-
-#: etc/initialdata:601
-msgid "%1 newest unowned tickets"
-msgstr "å‰ %1 份待认领的申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 not shown"
-msgstr "没有显示 %1"
-
-#: lib/RT/CustomField_Overlay.pm:893
-msgid "%1 objects"
-msgstr "%1 对象"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 recent tickets I own..."
-msgstr "最新 %1 份待处ç†ç”³è¯·å•..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 recent tickets I requested..."
-msgstr "最新 %1 份é€å‡ºçš„申请å•..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 result(s) found"
-msgstr "找到 %1 项结果"
-
-#: html/User/Elements/DelegateRights:97
-#. (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:63
-#. (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/CustomField_Overlay.pm:894
-msgid "%1's %2 objects"
-msgstr "%1 内的 %2 对象"
-
-#: lib/RT/CustomField_Overlay.pm:895
-msgid "%1's %2's %3 objects"
-msgstr "%1 内的 %2 的 %3 对象"
-
-#: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:57
-#. ($object->Name)
-#. ($Object->Name)
-msgid "%1's saved searches"
-msgstr "%1 的预存查询"
-
-#: lib/RT/Transaction_Overlay.pm:481
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1:未指定附件"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:78
-#. ($size)
-msgid "%1b"
-msgstr "%1 字节"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:75
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr "%1k 字节"
-
-#: html/Ticket/Elements/ShowTime:49
-#. (sprintf("%.1f",$minutes / 60))
-msgid "%quant(%1,hour)"
-msgstr "%1 å°æ—¶"
-
-#: NOT FOUND IN SOURCE
-msgid "%quant(%1,result) found"
-msgstr "找到 %1 项结果"
-
-#: lib/RT/Ticket_Overlay.pm:1142
-#. ($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:50 html/Admin/Elements/EditQueueWatchers:50 html/Admin/Elements/EditScrips:56 html/Admin/Elements/EditTemplates:57 html/Admin/Groups/Members.html:73 html/Elements/EditLinks:54 html/Ticket/Elements/EditPeople:67 html/User/Groups/Members.html:76
-msgid "(Check box to delete)"
-msgstr "(点选欲删除的项目)"
-
-#: NOT FOUND IN SOURCE
-msgid "(Check boxes to delete)"
-msgstr "(点选欲删除的项目)"
-
-#: html/Ticket/Elements/PreviewScrips:99
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr "(点选欲åœç”¨é€šçŸ¥çš„收件人)"
-
-#: html/Ticket/Elements/PreviewScrips:123
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr "(点选欲å¯ç”¨é€šçŸ¥çš„收件人)"
-
-#: html/Ticket/Create.html:218
-msgid "(Enter ticket ids or URLs, separated with spaces)"
-msgstr "(键入申请å•ç¼–å·æˆ–网å€ï¼Œä»¥ç©ºç™½åˆ†éš”)"
-
-#: html/Admin/Queues/Modify.html:75 html/Admin/Queues/Modify.html:81
-#. ($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:74 html/Admin/Elements/ListGlobalCustomFields:53
-msgid "(No custom fields)"
-msgstr "(没有自订字段)"
-
-#: html/Admin/Groups/Members.html:71 html/User/Groups/Members.html:74
-msgid "(No members)"
-msgstr "(没有æˆå‘˜)"
-
-#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ListGlobalScrips:48
-msgid "(No scrips)"
-msgstr "(没有手续)"
-
-#: html/Admin/Elements/EditTemplates:52
-msgid "(No templates)"
-msgstr "没有模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "(No workflows)"
-msgstr "没有æµç¨‹"
-
-#: html/Admin/Elements/PickCustomFields:47 html/Admin/Elements/PickObjects:47
-msgid "(None)"
-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 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/Update.html:90
-msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„密件副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<strong>ä¸ä¼š</strong>更改åŽç»­çš„收件者åå•ã€‚)"
-
-#: NOT FOUND IN SOURCE
-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/Create.html:103
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的管ç†å‘˜ç”µå­é‚®ä»¶åœ°å€ã€‚è¿™<strong>将会</strong>更改åŽç»­çš„收件者åå•ã€‚)"
-
-#: 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 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/Update.html:86
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> change who will receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<strong>ä¸ä¼š</strong>更改åŽç»­çš„收件者åå•ã€‚)"
-
-#: NOT FOUND IN SOURCE
-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/Create.html:93
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<strong>将会</strong>更改åŽç»­çš„收件者åå•ã€‚)"
-
-#: html/Admin/Elements/EditScrip:96
-msgid "(Use these fields when you choose 'User Defined' for a condition or action)"
-msgstr "(当æ¡ä»¶æˆ–动作设为‘使用者自订’时,请填入这些字段)"
-
-#: html/Ticket/Elements/EditWatchers:60 html/Ticket/Elements/ShowUserEntry:53
-msgid "(Will not be sent email)"
-msgstr "(ä¸ä¼šæ”¶åˆ°é‚®ä»¶)"
-
-#: NOT FOUND IN SOURCE
-msgid "(default delegate)"
-msgstr "(预设代ç†äºº)"
-
-#: NOT FOUND IN SOURCE
-msgid "(delete)"
-msgstr "(删除)"
-
-#: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54
-msgid "(empty)"
-msgstr "(空白)"
-
-#: NOT FOUND IN SOURCE
-msgid "(new)"
-msgstr "(新增)"
-
-#: html/Admin/Users/index.html:60
-msgid "(no name listed)"
-msgstr "(没有列出姓å)"
-
-#: NOT FOUND IN SOURCE
-msgid "(no subject)"
-msgstr "(没有主题)"
-
-#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:69 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:54 html/Search/Chart:56 html/Search/Elements/Chart:76 lib/RT/Transaction_Overlay.pm:591
-msgid "(no value)"
-msgstr "(æ— )"
-
-#: html/Admin/Elements/EditCustomFieldValues:47
-msgid "(no values)"
-msgstr "(没有值)"
-
-#: html/Elements/EditLinks:132 html/Ticket/Elements/BulkLinks:49
-msgid "(only one ticket)"
-msgstr "(仅能指定一份申请å•)"
-
-#: html/Elements/RT__Ticket/ColumnMap:149
-msgid "(pending approval)"
-msgstr "(等待签核)"
-
-#: html/Elements/RT__Ticket/ColumnMap:152
-msgid "(pending other Collection)"
-msgstr "(等待其它集åˆ)"
-
-#: NOT FOUND IN SOURCE
-msgid "(pending other tickets)"
-msgstr "(等待其它申请å•)"
-
-#: NOT FOUND IN SOURCE
-msgid "(requestor's group)"
-msgstr "(申请人所属)"
-
-#: html/Admin/Users/Modify.html:71
-msgid "(required)"
-msgstr "(å¿…å¡«)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "(untitled)"
-msgstr "(未命å)"
-
-#: html/Ticket/Elements/Reminders:133
-msgid "(yyyy/mm/dd)"
-msgstr "(yyyy/mm/dd)"
-
-#: NOT FOUND IN SOURCE
-msgid "*"
-msgstr "★"
-
-#: html/Elements/EditCustomFieldSelect:57
-msgid "-"
-msgstr "-"
-
-#: bin/rt-crontool:95
-msgid "--transaction argument could be only 'first' or 'last'"
-msgstr "--transaction 的值仅能为 'first' 或 'last'"
-
-#: NOT FOUND IN SOURCE
-msgid ":"
-msgstr ":"
-
-#: html/Ticket/Elements/ShowBasics:53
-msgid "<% $Ticket->Status%>"
-msgstr "<% $Ticket->Status%>"
-
-#: html/Elements/SelectTicketTypes:48
-msgid "<% $_ %>"
-msgstr "<% $_ %>"
-
-#: html/Search/Elements/SelectLinks:48
-msgid "<%$_%>"
-msgstr "<%$_%>"
-
-#: html/Search/Elements/DisplayOptions:73
-msgid "<%$field%>"
-msgstr "<%$field%>"
-
-#: html/Elements/CreateTicket:47
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" class=\"button\" value=\"New ticket in\" />&nbsp;%1"
-msgstr "<input type=\"submit\" class=\"button\" value=\"æ出申请å•\" />&nbsp;%1"
-
-#: docs/design_docs/string-extraction-guide.txt:54 lib/RT/StyleGuide.pod:787
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
-msgstr "<input type=\"submit\" value=\"æ出申请å•\">&nbsp;%1"
-
-#: etc/initialdata:218
-msgid "A blank template"
-msgstr "空白模æ¿"
-
-#: html/Admin/Users/Modify.html:371
-msgid "A password was not set, so user won't be able to login."
-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:174 lib/RT/Principal_Overlay.pm:219
-msgid "ACE not found"
-msgstr "找ä¸åˆ° ACE 设定"
-
-#: lib/RT/ACE_Overlay.pm:853
-msgid "ACEs can only be created and deleted."
-msgstr "祇能新增或删除 ACE 设定。"
-
-#: NOT FOUND IN SOURCE
-msgid "ACLEquivalence"
-msgstr "ACLEquivalence"
-
-#: html/Search/Elements/SelectAndOr:46
-msgid "AND"
-msgstr "AND"
-
-#: NOT FOUND IN SOURCE
-msgid "Aborting to avoid unintended ticket modifications.\\n"
-msgstr "离开以å…ä¸å°å¿ƒæ›´æ”¹åˆ°ç”³è¯·å•ã€‚\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "About Me"
-msgstr "个人信æ¯"
-
-#: html/User/Elements/Tabs:53
-msgid "About me"
-msgstr "个人信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Access Right"
-msgstr "系统使用登录æƒé™"
-
-#: html/Admin/Users/Modify.html:106
-msgid "Access control"
-msgstr "å­˜å–æƒé™"
-
-#: html/Admin/Elements/EditScrip:65
-msgid "Action"
-msgstr "动作"
-
-#: lib/RT/Scrip_Overlay.pm:172
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "动作 %1 找ä¸åˆ°"
-
-#: NOT FOUND IN SOURCE
-msgid "Action committed."
-msgstr "动作执行完毕"
-
-#: bin/rt-crontool:171
-msgid "Action committed.\\n"
-msgstr "动作执行完毕。\\n"
-
-#: lib/RT/Scrip_Overlay.pm:168
-msgid "Action is mandatory argument"
-msgstr "动作为必填字段"
-
-#: bin/rt-crontool:167
-msgid "Action prepared..."
-msgstr "动作准备完毕..."
-
-#: NOT FOUND IN SOURCE
-msgid "Activated Date"
-msgstr "申请激活时间"
-
-#: html/Search/Build.html:85
-msgid "Add"
-msgstr "新增"
-
-#: html/Search/Bulk.html:92
-msgid "Add AdminCc"
-msgstr "新增管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: html/Search/Bulk.html:88
-msgid "Add Cc"
-msgstr "新增副本收件人"
-
-#: html/Search/Elements/EditFormat:49
-msgid "Add Columns"
-msgstr "新增字段"
-
-#: html/Search/Elements/PickCriteria:46
-msgid "Add Criteria"
-msgstr "新增æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Add Entry"
-msgstr "新增列"
-
-#: html/Ticket/Create.html:147 html/Ticket/Update.html:116
-msgid "Add More Files"
-msgstr "新增更多附件"
-
-#: NOT FOUND IN SOURCE
-msgid "Add Next State"
-msgstr "新增下一项关å¡"
-
-#: html/Search/Bulk.html:84
-msgid "Add Requestor"
-msgstr "新增申请人"
-
-#: html/Admin/Elements/AddCustomFieldValue:46
-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:83
-msgid "Add a scrip which will apply to all queues"
-msgstr "新增一é“用于所有表å•çš„手续"
-
-#: NOT FOUND IN SOURCE
-msgid "Add additional criteria"
-msgstr "新增查询æ¡ä»¶"
-
-#: html/Search/Build.html:109 html/Search/Build.html:94
-msgid "Add and Search"
-msgstr "新增并开始查询"
-
-#: html/Search/Bulk.html:124
-msgid "Add comments or replies to selected tickets"
-msgstr "新增评论或回å¤åˆ°æŒ‡å®šçš„申请å•"
-
-#: html/Admin/Groups/Members.html:63 html/User/Groups/Members.html:60
-msgid "Add members"
-msgstr "新增æˆå‘˜"
-
-#: html/Admin/Queues/People.html:87 html/Ticket/Elements/AddWatchers:49
-msgid "Add new watchers"
-msgstr "新增视察员"
-
-#: html/Search/Build.html:85
-msgid "Add these terms to your search"
-msgstr "将这些æ¡ä»¶åŠ è¿›æŸ¥è¯¢å†…"
-
-#: html/Search/Bulk.html:158
-msgid "Add values"
-msgstr "新增值"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "Add, delete and modify custom field values for objects"
-msgstr "新增ã€åˆ é™¤åŠä¿®æ”¹å¯¹è±¡çš„自订字段值"
-
-#: NOT FOUND IN SOURCE
-msgid "AddNextState"
-msgstr "新增下一项关å¡"
-
-#: lib/RT/Queue_Overlay.pm:763
-#. ($args{'Type'})
-msgid "Added principal as a %1 for this queue"
-msgstr "å•ä½å·²æ–°å¢žä¸ºæ­¤è¡¨å•çš„ %1"
-
-#: lib/RT/Ticket_Overlay.pm:1455
-#. ($self->loc($args{'Type'}))
-msgid "Added principal as a %1 for this ticket"
-msgstr "å•ä½å·²æ–°å¢žä¸ºæ­¤ç”³è¯·å•çš„ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Additional Hints"
-msgstr "é¢å¤–æ示"
-
-#: html/Admin/Users/Modify.html:146 html/User/Prefs.html:133
-msgid "Address1"
-msgstr "ä½å€"
-
-#: html/Admin/Users/Modify.html:151 html/User/Prefs.html:137
-msgid "Address2"
-msgstr "ä½å€(ç»­)"
-
-#: NOT FOUND IN SOURCE
-msgid "Adjust Blinking Rate"
-msgstr "调整闪çƒé€Ÿåº¦å¿«æ…¢"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin"
-msgstr "管ç†å‘˜"
-
-#: html/Ticket/Create.html:98
-msgid "Admin Cc"
-msgstr "管ç†å‘˜å‰¯æœ¬"
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "管ç†å‘˜è¯„论"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr "管ç†å‘˜å›žå¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin Rights"
-msgstr "管ç†å‘˜æƒé™"
-
-#: html/Admin/Queues/index.html:46 html/Admin/Queues/index.html:49
-msgid "Admin queues"
-msgstr "表å•ç®¡ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin users"
-msgstr "使用者管ç†"
-
-#: html/Admin/Global/index.html:47 html/Admin/Global/index.html:49
-msgid "Admin/Global configuration"
-msgstr "管ç†/全域设定"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Groups"
-msgstr "管ç†/群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Queue/Basics"
-msgstr "管ç†/表å•/基本信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminAddress"
-msgstr "管ç†å‘˜ Email"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminAllPersonalGroups"
-msgstr "管ç†æ‰€æœ‰ä»£ç†äººç¾¤ç»„"
-
-#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:60 lib/RT/ACE_Overlay.pm:113
-msgid "AdminCc"
-msgstr "管ç†å‘˜å‰¯æœ¬"
-
-msgid "AdminCc.EmailAddress"
-msgstr "管ç†å‘˜å‰¯æœ¬: 电å­é‚®ä»¶ä¿¡ç®±"
-
-msgid "Cc.EmailAddress"
-msgstr "副本: 电å­é‚®ä»¶ä¿¡ç®±"
-
-msgid "Requestor.EmailAddress"
-msgstr "申请人: 电å­é‚®ä»¶ä¿¡ç®±"
-
-msgid "Custom.Ownership"
-msgstr "自订: 承办状æ€"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminComment"
-msgstr "管ç†å‘˜è¯„论"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCorrespondence"
-msgstr "管ç†å‘˜å›žå¤"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "AdminCustomField"
-msgstr "管ç†è‡ªè®¢å­—段"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCustomFields"
-msgstr "管ç†è‡ªè®¢å­—段"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "AdminGroup"
-msgstr "管ç†ç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminGroupDescription"
-msgstr "管ç†ç¾¤ç»„æè¿°"
-
-#: lib/RT/Group_Overlay.pm:165
-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:80
-msgid "AdminOwnPersonalGroups"
-msgstr "管ç†ä»£ç†äººç¾¤ç»„"
-
-#: lib/RT/Queue_Overlay.pm:92
-msgid "AdminQueue"
-msgstr "管ç†è¡¨å•"
-
-#: lib/RT/System.pm:81
-msgid "AdminUsers"
-msgstr "管ç†ä½¿ç”¨è€…"
-
-#: NOT FOUND IN SOURCE
-msgid "Administrative"
-msgstr "行政类"
-
-#: html/Admin/Queues/People.html:69 html/Ticket/Elements/EditPeople:75
-msgid "Administrative Cc"
-msgstr "管ç†å‘˜å‰¯æœ¬"
-
-#: NOT FOUND IN SOURCE
-msgid "Admins"
-msgstr "主管"
-
-#: html/Ticket/Elements/Tabs:216
-msgid "Advanced"
-msgstr "进阶"
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search"
-msgstr "进阶查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search Criteria"
-msgstr "进阶查询æ¡ä»¶"
-
-#: html/Elements/SelectDateRelation:57
-msgid "After"
-msgstr "晚于"
-
-#: NOT FOUND IN SOURCE
-msgid "Age"
-msgstr "ç»åŽ†æ—¶é—´"
-
-#: html/Search/Elements/PickCriteria:52
-msgid "Aggregator"
-msgstr "结åˆæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Alias"
-msgstr "执行其它æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Alias for"
-msgstr "相当于"
-
-#: NOT FOUND IN SOURCE
-msgid "All"
-msgstr "全部"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr "完æˆå…¨éƒ¨ç­¾æ ¸"
-
-#: NOT FOUND IN SOURCE
-msgid "All Condition"
-msgstr "所有æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "All Custom Fields"
-msgstr "所有自订字段"
-
-#: html/Admin/Queues/index.html:75
-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/Search/Elements/EditQuery:56
-msgid "And/Or"
-msgstr "AND/OR"
-
-#: NOT FOUND IN SOURCE
-msgid "Any Condition"
-msgstr "ä»»æ„æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Applies To"
-msgstr "套用于"
-
-#: html/Admin/CustomFields/Modify.html:73 html/Admin/Elements/CustomFieldTabs:83
-msgid "Applies to"
-msgstr "套用于"
-
-#: html/Search/Edit.html:64
-msgid "Apply"
-msgstr "套用"
-
-#: NOT FOUND IN SOURCE
-msgid "Apply Template"
-msgstr "引用模æ¿"
-
-#: html/Search/Edit.html:64
-msgid "Apply your changes"
-msgstr "套用更动"
-
-#: html/Elements/Tabs:77
-msgid "Approval"
-msgstr "签核"
-
-#: html/Approvals/Display.html:65 html/Approvals/Elements/ShowDependency:63 html/Approvals/index.html:86
-#. ($Ticket->Id, $Ticket->Subject)
-#. ($ticket->id, $msg)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Approval #%1: %2"
-msgstr "ç­¾æ ¸å• #%1:%2"
-
-#: html/Approvals/index.html:75
-#. ($ticket->Id)
-msgid "Approval #%1: Notes not recorded due to a system error"
-msgstr "ç­¾æ ¸å• #%1:系统错误,记录失败"
-
-#: html/Approvals/index.html:73
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr "ç­¾æ ¸å• #%1:记录完毕"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Details"
-msgstr "签核细节"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Due"
-msgstr "签核时é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Notes"
-msgstr "签核æ„è§"
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr "完æˆæŸé¡¹ç­¾æ ¸"
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr "驳回æŸé¡¹ç­¾æ ¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Result"
-msgstr "签核结果"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Status"
-msgstr "核准结果"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Type"
-msgstr "签核ç§ç±»"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval diagram"
-msgstr "签核æµç¨‹"
-
-#: html/Approvals/Elements/Approve:69
-msgid "Approve"
-msgstr "核准"
-
-#: NOT FOUND IN SOURCE
-msgid "Approver"
-msgstr "签核人"
-
-#: NOT FOUND IN SOURCE
-msgid "Approver Setting"
-msgstr "执行签核人设定"
-
-#: etc/initialdata:504
-msgid "Approver's notes: %1"
-msgstr "签核备注:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Apr"
-msgstr "四月"
-
-#: lib/RT/Date.pm:444
-msgid "Apr."
-msgstr "04"
-
-#: NOT FOUND IN SOURCE
-msgid "April"
-msgstr "四月"
-
-#: NOT FOUND IN SOURCE
-msgid "Are you sure to delete checked items?"
-msgstr "您确定è¦åˆ é™¤ï¼Ÿ"
-
-#: html/Search/Elements/DisplayOptions:81
-msgid "Asc"
-msgstr "递增"
-
-#: html/Elements/SelectSortOrder:56
-msgid "Ascending"
-msgstr "递增"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "Assign and remove custom fields"
-msgstr "指派åŠç§»é™¤è‡ªè®¢å­—段"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "AssignCustomFields"
-msgstr "指派自订字段"
-
-#: html/Search/Bulk.html:142 html/SelfService/Update.html:87 html/Ticket/ModifyAll.html:115 html/Ticket/Update.html:116
-msgid "Attach"
-msgstr "附件"
-
-#: html/SelfService/Create.html:92 html/Ticket/Create.html:143
-msgid "Attach file"
-msgstr "附加档案"
-
-#: html/SelfService/Update.html:75 html/Ticket/Create.html:131 html/Ticket/Update.html:94
-msgid "Attached file"
-msgstr "现有附件"
-
-#: html/Ticket/ShowEmailRecord.html:52 html/Ticket/ShowEmailRecord.html:56 html/Ticket/ShowEmailRecord.html:59
-#. ($Attachment)
-msgid "Attachment '%1' could not be loaded"
-msgstr "无法加载附件 '%1'"
-
-#: lib/RT/Transaction_Overlay.pm:489
-msgid "Attachment created"
-msgstr "附件新增完毕"
-
-#: lib/RT/Tickets_Overlay.pm:1945
-msgid "Attachment filename"
-msgstr "附件档å"
-
-#: html/Ticket/Elements/ShowAttachments:47
-msgid "Attachments"
-msgstr "附件"
-
-#: lib/RT/Attributes_Overlay.pm:171
-msgid "Attribute Deleted"
-msgstr "已删除该属性"
-
-#: NOT FOUND IN SOURCE
-msgid "Attributes"
-msgstr "属性"
-
-#: NOT FOUND IN SOURCE
-msgid "Aug"
-msgstr "八月"
-
-#: lib/RT/Date.pm:448
-msgid "Aug."
-msgstr "08"
-
-#: NOT FOUND IN SOURCE
-msgid "August"
-msgstr "八月"
-
-#: NOT FOUND IN SOURCE
-msgid "AuthSystem"
-msgstr "认è¯æ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoReject"
-msgstr "自动驳回表å•"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoResolve"
-msgstr "自动完æˆè¡¨å•å¤„ç†"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "自动回å¤"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "自动对申请人回å¤"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoreplyToRequestors"
-msgstr "自动对申请人回å¤"
-
-#: html/Widgets/SelectionBox:185
-msgid "Available"
-msgstr "å¯ç”¨"
-
-#: NOT FOUND IN SOURCE
-msgid "Available Columns"
-msgstr "å¯ç”¨çš„字段:"
-
-#: NOT FOUND IN SOURCE
-msgid "Available Rights:"
-msgstr "æƒé™é¡¹ç›®åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Back to Homepage"
-msgstr "回到首页"
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: NOT FOUND IN SOURCE
-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/CustomFieldTabs:65 html/Admin/Elements/GroupTabs:60 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/UserTabs:58 html/Ticket/Elements/Tabs:113 html/User/Elements/GroupTabs:59
-msgid "Basics"
-msgstr "基本信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Batch Approval"
-msgstr "批次签核"
-
-#: html/Ticket/Update.html:88
-msgid "Bcc"
-msgstr "密件副本"
-
-#: html/Admin/CustomFields/GroupRights.html:91 html/Admin/CustomFields/UserRights.html:74 html/Admin/Elements/EditScrip:89
-msgid "Be sure to save your changes"
-msgstr "请别忘了储存修改。"
-
-#: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:361
-msgid "Before"
-msgstr "早于"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin Approval"
-msgstr "开始签核"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin From "
-msgstr "起始日"
-
-#: html/Elements/Logo:47
-msgid "Best Practical Solutions, LLC corporate logo"
-msgstr "Best Practical Solutions, LLC å…¬å¸è¯†åˆ«å›¾æ¡ˆ"
-
-#: NOT FOUND IN SOURCE
-msgid "Binary"
-msgstr "档案"
-
-#: NOT FOUND IN SOURCE
-msgid "Birthday"
-msgstr "生日"
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "空白模æ¿"
-
-#: html/Search/Elements/EditFormat:84
-msgid "Bold"
-msgstr "粗体"
-
-#: NOT FOUND IN SOURCE
-msgid "Bookmarkable URL for this search"
-msgstr "将查询结果转为å¯æ”¾å…¥ä¹¦ç­¾çš„网å€"
-
-#: html/Search/Results.html:79
-msgid "Bookmarkable link"
-msgstr "å¯æ”¾å…¥ä¹¦ç­¾çš„网å€"
-
-#: html/Ticket/Elements/ShowHistory:64 html/Ticket/Elements/ShowHistory:69
-msgid "Brief headers"
-msgstr "精简标头档"
-
-#: html/Ticket/Elements/Tabs:227
-msgid "Bulk Update"
-msgstr "整批更新"
-
-#: NOT FOUND IN SOURCE
-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:1853
-msgid "Can not modify system users"
-msgstr "无法更改系统使用者"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "Can this principal see this queue"
-msgstr "该å•ä½æ˜¯å¦èƒ½æŸ¥é˜…此表å•"
-
-#: lib/RT/CustomField_Overlay.pm:379
-msgid "Can't add a custom field value without a name"
-msgstr "ä¸èƒ½æ–°å¢žæ²¡æœ‰å称的自订字段值"
-
-#: html/Admin/CustomFields/Objects.html:86
-#. ($Class)
-msgid "Can't find a collection class for '%1'"
-msgstr "找ä¸åˆ°â€˜%1’的集åˆç±»åˆ«"
-
-#: html/Search/Build.html:286
-msgid "Can't find a saved search to work with"
-msgstr "找ä¸åˆ°é¢„存查询"
-
-#: lib/RT/Link_Overlay.pm:159
-msgid "Can't link a ticket to itself"
-msgstr "申请å•ä¸èƒ½é“¾æŽ¥è‡ªå·±ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Can't merge into a merged ticket. You should never get this error"
-msgstr "ä¸èƒ½æ•´åˆè¿›å·²æ•´åˆè¿‡çš„申请å•ã€‚这个错误ä¸è¯¥å‘生。"
-
-#: html/Widgets/SavedSearch:63
-#. (loc($self->{SearchType}))
-msgid "Can't save %1"
-msgstr "无法储存 %1"
-
-#: html/Search/Build.html:290
-msgid "Can't save this search"
-msgstr "无法储存此项查询"
-
-#: lib/RT/Record.pm:1282 lib/RT/Record.pm:1358
-msgid "Can't specifiy both base and target"
-msgstr "ä¸èƒ½åŒæ—¶æŒ‡å®šèµ·å§‹ç”³è¯·å•ä¸Žç›®çš„申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Cancel"
-msgstr "å–消"
-
-#: html/autohandler:204
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "无法新增使用者:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Cannot login: Your system clock differs from server's by %1 seconds!"
-msgstr "您的系统时钟和æœåŠ¡å™¨ç›¸å·® %1 秒,无法登入ï¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Card No."
-msgstr "å¡å·"
-
-#: NOT FOUND IN SOURCE
-msgid "Categories"
-msgstr "分类管ç†"
-
-#: html/Admin/Elements/AddCustomFieldValue:62 html/Admin/Elements/EditCustomFieldValues:58
-msgid "Category"
-msgstr "分类"
-
-#: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:88 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:83 lib/RT/ACE_Overlay.pm:112
-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:52
-msgid "Change password"
-msgstr "更改å£ä»¤"
-
-#: NOT FOUND IN SOURCE
-msgid "ChangeOwnerUI"
-msgstr "å¯å¦é€‰æ‹©è¡¨å•æ‰¿åŠžäºº"
-
-#: html/Elements/Submit:78
-msgid "Check All"
-msgstr "全部选å–"
-
-#: html/SelfService/Update.html:78 html/Ticket/Create.html:134 html/Ticket/Update.html:97
-msgid "Check box to delete"
-msgstr "选择欲删除的项目"
-
-#: html/Admin/Elements/SelectRights:55
-msgid "Check box to revoke right"
-msgstr "选择欲撤消的æƒåˆ©"
-
-#: html/Elements/EditLinks:148 html/Elements/EditLinks:85 html/Elements/ShowLinks:78 html/Ticket/Create.html:223 html/Ticket/Elements/BulkLinks:64
-msgid "Children"
-msgstr "å­ç”³è¯·å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Chinese Name"
-msgstr "中文姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "Chinese/English"
-msgstr "中英文"
-
-#: html/NoAuth/js/util.js:201
-msgid "Choose a date"
-msgstr "选择日期"
-
-#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:141
-msgid "City"
-msgstr "所在城市"
-
-#: NOT FOUND IN SOURCE
-msgid "ClassicUI"
-msgstr "传统接å£"
-
-#: html/Elements/Submit:80
-msgid "Clear All"
-msgstr "全部清除"
-
-#: html/Helpers/CalPopup.html:51
-msgid "Close window"
-msgstr "关闭窗å£"
-
-#: html/Ticket/Elements/ShowDates:68
-msgid "Closed"
-msgstr "已解决"
-
-#: NOT FOUND IN SOURCE
-msgid "Closed Tickets"
-msgstr "已解决的申请å•"
-
-#: html/SelfService/Closed.html:46 html/SelfService/Elements/Tabs:78
-msgid "Closed tickets"
-msgstr "已解决的申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Code"
-msgstr "执行程åºç "
-
-#: lib/RT/CustomField_Overlay.pm:89
-msgid "Combobox: Select or enter multiple values"
-msgstr "下拉文字框:选择或键入多é‡é¡¹ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:90
-msgid "Combobox: Select or enter one value"
-msgstr "下拉文字框:选择或键入å•ä¸€é¡¹ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:91
-msgid "Combobox: Select or enter up to %1 values"
-msgstr "下拉文字框:选择或键入最多 %1 个项目"
-
-#: NOT FOUND IN SOURCE
-msgid "Command not understood!\\n"
-msgstr "指令无法辨识ï¼\\n"
-
-#: html/Ticket/Elements/ShowTransaction:190 html/Ticket/Elements/Tabs:185
-msgid "Comment"
-msgstr "评论"
-
-#: html/Admin/Queues/Modify.html:79
-msgid "Comment Address"
-msgstr "评论电å­é‚®ä»¶åœ°å€"
-
-#: NOT FOUND IN SOURCE
-msgid "Comment not recorded"
-msgstr "评论未被纪录"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "Comment on tickets"
-msgstr "对申请å•æ出评论"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "CommentOnTicket"
-msgstr "评论申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments"
-msgstr "评论"
-
-#: html/Ticket/ModifyAll.html:91 html/Ticket/Update.html:75
-msgid "Comments (Not sent to requestors)"
-msgstr "评论(ä¸é€ç»™ç”³è¯·äºº)"
-
-#: html/Search/Bulk.html:128
-msgid "Comments (not sent to requestors)"
-msgstr "评论(ä¸é€ç»™ç”³è¯·äºº)"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments about %1"
-msgstr "对 %1 的评论"
-
-#: html/Admin/Users/Modify.html:225 html/Ticket/Elements/ShowRequestor:67
-msgid "Comments about this user"
-msgstr "使用者æè¿°"
-
-#: lib/RT/Transaction_Overlay.pm:634
-msgid "Comments added"
-msgstr "新增评论完毕"
-
-#: NOT FOUND IN SOURCE
-msgid "Commit"
-msgstr "确认"
-
-#: lib/RT/Action/Generic.pm:175
-msgid "Commit Stubbed"
-msgstr "消除更动完毕"
-
-#: NOT FOUND IN SOURCE
-msgid "Company Name"
-msgstr "å…¬å¸å称"
-
-#: NOT FOUND IN SOURCE
-msgid "CompanySpecific"
-msgstr "å„å…¬å¸ç‹¬ç«‹æ˜¾ç¤º"
-
-#: NOT FOUND IN SOURCE
-msgid "Compile Restrictions"
-msgstr "设定查询æ¡ä»¶"
-
-#: html/Admin/Elements/EditScrip:59
-msgid "Condition"
-msgstr "æ¡ä»¶"
-
-#: lib/RT/Scrip_Overlay.pm:184
-msgid "Condition is mandatory argument"
-msgstr "æ¡ä»¶æ˜¯å¿…填字段"
-
-#: bin/rt-crontool:151
-msgid "Condition matches..."
-msgstr "符åˆæ¡ä»¶..."
-
-#: lib/RT/Scrip_Overlay.pm:188
-msgid "Condition not found"
-msgstr "未找到符åˆçš„现况"
-
-#: html/Elements/Tabs:84
-msgid "Configuration"
-msgstr "设定"
-
-#: html/SelfService/Prefs.html:54
-msgid "Confirm"
-msgstr "确认å£ä»¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Confirm Password"
-msgstr "å£ä»¤ç¡®è®¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Confirm Submit"
-msgstr "确定é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "Contact System Administrator"
-msgstr "连络系统管ç†å‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "ContactInfoSystem"
-msgstr "连络信æ¯ç³»ç»Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Contacted date '%1' could not be parsed"
-msgstr "无法解读è”络日期 '%1'"
-
-#: html/Admin/Elements/ModifyTemplate:65 html/Elements/SelectAttachmentField:48 html/Ticket/ModifyAll.html:119
-msgid "Content"
-msgstr "内容"
-
-#: html/Elements/SelectAttachmentField:49
-msgid "Content-Type"
-msgstr "内容类型"
-
-#: NOT FOUND IN SOURCE
-msgid "Coould not create group"
-msgstr "无法新增群组"
-
-#: html/Search/Elements/EditSearches:65
-msgid "Copy"
-msgstr "å¤åˆ¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Copy Field From:"
-msgstr "欲å¤åˆ¶å­—段:"
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "回å¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence Address"
-msgstr "申请å•å›žå¤åœ°å€"
-
-#: lib/RT/Transaction_Overlay.pm:630
-msgid "Correspondence added"
-msgstr "新增申请å•å›žå¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence not recorded"
-msgstr "未纪录申请å•å›žå¤"
-
-#: NOT FOUND IN SOURCE
-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/Record.pm:1707
-msgid "Could not add new custom field value. "
-msgstr "ä¸èƒ½æ–°å¢žè‡ªè®¢å­—段的值。"
-
-#: lib/RT/Record.pm:1660
-#. (, $value_msg)
-msgid "Could not add new custom field value. %1 "
-msgstr "ä¸èƒ½æ–°å¢žè‡ªè®¢å­—段的值。%1 "
-
-#: lib/RT/Ticket_Overlay.pm:3048 lib/RT/Ticket_Overlay.pm:3056 lib/RT/Ticket_Overlay.pm:3073
-msgid "Could not change owner. "
-msgstr "ä¸èƒ½æ›´æ”¹æ‰¿åŠžäººã€‚"
-
-#: html/Admin/CustomFields/Modify.html:161
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr "无法新增自订字段"
-
-#: html/Admin/Elements/EditCustomField:113
-#. ($msg)
-msgid "Could not create CustomField: %1"
-msgstr "无法新增自订字段:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create Scrip"
-msgstr "无法建立讯æ¯é€šçŸ¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create Template"
-msgstr "无法建立通知模æ¿"
-
-#: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:494 lib/RT/Group_Overlay.pm:501
-msgid "Could not create group"
-msgstr "无法新增群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create item"
-msgstr "无法新增项目"
-
-#: html/Admin/Global/Template.html:96 html/Admin/Queues/Template.html:93
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr "无法新增模æ¿ï¼š%1"
-
-#: lib/RT/Ticket_Overlay.pm:1075 lib/RT/Ticket_Overlay.pm:407
-msgid "Could not create ticket. Queue not set"
-msgstr "无法新增申请å•ã€‚尚未指定表å•ã€‚"
-
-#: lib/RT/User_Overlay.pm:255 lib/RT/User_Overlay.pm:269 lib/RT/User_Overlay.pm:278 lib/RT/User_Overlay.pm:287 lib/RT/User_Overlay.pm:296 lib/RT/User_Overlay.pm:310 lib/RT/User_Overlay.pm:320 lib/RT/User_Overlay.pm:496
-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 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:741 lib/RT/Ticket_Overlay.pm:1423
-msgid "Could not find or create that user"
-msgstr "找ä¸åˆ°æˆ–无法新增该å使用者"
-
-#: lib/RT/Queue_Overlay.pm:802 lib/RT/Ticket_Overlay.pm:1504
-msgid "Could not find that principal"
-msgstr "找ä¸åˆ°è¯¥å•ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not find user %1."
-msgstr "找ä¸åˆ°ä½¿ç”¨è€… %1。"
-
-#: html/Admin/CustomFields/Objects.html:69
-msgid "Could not load CustomField %1"
-msgstr "无法加载字段 %1"
-
-#: html/Admin/Groups/Members.html:112 html/User/Groups/Members.html:111 html/User/Groups/Modify.html:103
-msgid "Could not load group"
-msgstr "无法加载群组"
-
-#: lib/RT/SavedSearch.pm:119
-#. ($privacy)
-msgid "Could not load object for %1"
-msgstr "无法为 %1 加载对象"
-
-#: lib/RT/SavedSearch.pm:197
-msgid "Could not load search attribute"
-msgstr "无法加载查询属性"
-
-#: lib/RT/Queue_Overlay.pm:761
-#. ($args{'Type'})
-msgid "Could not make that principal a %1 for this queue"
-msgstr "无法将该å•ä½è®¾ä¸ºæ­¤è¡¨å•çš„ %1。"
-
-#: lib/RT/Ticket_Overlay.pm:1444
-#. ($self->loc($args{'Type'}))
-msgid "Could not make that principal a %1 for this ticket"
-msgstr "无法将该å•ä½è®¾ä¸ºæ­¤ç”³è¯·å•çš„ %1。"
-
-#: lib/RT/Queue_Overlay.pm:860
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this queue"
-msgstr "无法将å•ä½ %1 从表å•ç§»é™¤ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not remove that principal as a %1 for this ticket"
-msgstr "无法将å•ä½ %1 从申请å•ç§»é™¤ã€‚"
-
-#: lib/RT/User_Overlay.pm:191
-msgid "Could not set user info"
-msgstr "无法设定使用者信æ¯"
-
-#: lib/RT/Transaction_Overlay.pm:159
-msgid "Couldn't add attachment"
-msgstr "无法新增附件"
-
-#: lib/RT/Group_Overlay.pm:1003
-msgid "Couldn't add member to group"
-msgstr "无法新增æˆå‘˜è‡³ç¾¤ç»„"
-
-#: lib/RT/Record.pm:1719 lib/RT/Record.pm:1771
-#. ($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/Record.pm:953
-msgid "Couldn't find row"
-msgstr "找ä¸åˆ°æ­¤åˆ—æ•°æ®"
-
-#: lib/RT/Group_Overlay.pm:977
-msgid "Couldn't find that principal"
-msgstr "找ä¸åˆ°è¯¥å•ä½"
-
-#: lib/RT/CustomField_Overlay.pm:409
-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:145
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr "无法从使用者数æ®åº“加载 %1。\\n"
-
-#: html/Admin/CustomFields/UserRights.html:149
-#. ($id)
-msgid "Couldn't load Class %1"
-msgstr "无法加载类别 %1"
-
-#: html/Admin/CustomFields/GroupRights.html:107
-#. ($id)
-msgid "Couldn't load CustomField %1"
-msgstr "无法加载自订字段 %1"
-
-#: 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 "无法加载手续。"
-
-#: lib/RT/Ticket_Overlay.pm:2016
-#. ($self->Id)
-msgid "Couldn't load copy of ticket #%1."
-msgstr "æ— æ³•åŠ è½½ç”³è¯·å• %1 çš„å¤æœ¬ã€‚"
-
-#: html/Admin/Groups/GroupRights.html:109 html/Admin/Groups/UserRights.html:96
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr "无法加载手续 %1"
-
-#: lib/RT/Link_Overlay.pm:202 lib/RT/Link_Overlay.pm:211 lib/RT/Link_Overlay.pm:238
-msgid "Couldn't load link"
-msgstr "无法加载链接。"
-
-#: html/Admin/Elements/ObjectCustomFields:83 html/Admin/Queues/CustomFields.html:59 html/Admin/Users/CustomFields.html:59
-#. ($id)
-msgid "Couldn't load object %1"
-msgstr "无法加载对象 %1"
-
-#: html/Admin/Queues/People.html:142
-#. ($id)
-msgid "Couldn't load queue"
-msgstr "无法加载表å•"
-
-#: html/Admin/Queues/GroupRights.html:122 html/Admin/Queues/UserRights.html:93
-#. ($id)
-msgid "Couldn't load queue %1"
-msgstr "æ— æ³•åŠ è½½è¡¨å• %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load scrip"
-msgstr "无法加载手续"
-
-#: html/Admin/Elements/EditScrip:126 html/Admin/Elements/EditScrip:167
-#. ($id)
-msgid "Couldn't load scrip #%1"
-msgstr "无法加载手续 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load template"
-msgstr "无法加载模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load that user (%1)"
-msgstr "无法加载该å使用者(%1)"
-
-#: html/SelfService/Display.html:158 lib/RT/Action/CreateTickets.pm:680
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr "æ— æ³•åŠ è½½ç”³è¯·å• '%1'"
-
-#: lib/RT/Ticket_Overlay.pm:2643
-#. ($args{'URI'})
-msgid "Couldn't resolve '%1' into a URI."
-msgstr "无法将‘%1’解读为网å€"
-
-#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:153
-msgid "Country"
-msgstr "国家"
-
-#: html/Admin/Elements/CreateUserCalled:47 html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditScrip:133 html/Admin/Queues/Template.html:66 html/Elements/QuickCreate:65 html/Ticket/Create.html:168 html/Ticket/Create.html:235
-msgid "Create"
-msgstr "新增"
-
-#: NOT FOUND IN SOURCE
-msgid "Create Subgroup:"
-msgstr "新增å­ç¾¤ç»„:"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "新增申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Create User:"
-msgstr "新增æˆå‘˜ï¼š"
-
-#: html/Admin/CustomFields/Modify.html:150 html/Admin/Elements/EditCustomField:96
-msgid "Create a CustomField"
-msgstr "新增自订字段"
-
-#: html/Admin/Queues/CustomField.html:69
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr "为 %1 表å•æ–°å¢žè‡ªè®¢å­—段"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a CustomField that 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:125 html/Admin/Groups/Modify.html:99
-msgid "Create a new group"
-msgstr "新增群组"
-
-#: html/User/Groups/Modify.html:113 html/User/Groups/Modify.html:88
-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:47 html/Ticket/Create.html:51 html/Ticket/Create.html:60
-msgid "Create a new ticket"
-msgstr "新增申请å•"
-
-#: html/Admin/Users/Modify.html:252 html/Admin/Users/Modify.html:314
-msgid "Create a new user"
-msgstr "新增使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a new workflow"
-msgstr "新增æµç¨‹"
-
-#: html/Admin/Queues/Modify.html:125
-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:89
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr "为 %1 表å•æ–°å¢žæ‰‹ç»­"
-
-#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:86
-msgid "Create a template"
-msgstr "新增模æ¿"
-
-#: html/SelfService/Create.html:46 html/SelfService/CreateTicketInQueue.html:46
-msgid "Create a ticket"
-msgstr "æ出申请å•"
-
-#: NOT FOUND IN SOURCE
-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:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "ä¾æ®æ­¤é¡¹æ‰‹ç»­å†…的模版,新增申请å•"
-
-#: html/SelfService/Create.html:105
-msgid "Create ticket"
-msgstr "新增申请å•"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Create tickets in this queue"
-msgstr "在此表å•ä¸­æ–°å¢žç”³è¯·å•"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "Create, delete and modify custom fields"
-msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹è‡ªè®¢å­—段"
-
-#: lib/RT/Queue_Overlay.pm:92
-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:80
-msgid "Create, delete and modify the members of personal groups"
-msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹ä»£ç†äººç¾¤ç»„"
-
-#: lib/RT/System.pm:81
-msgid "Create, delete and modify users"
-msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹ä½¿ç”¨è€…"
-
-#: lib/RT/System.pm:87
-msgid "CreateSavedSearch"
-msgstr "新增预存查询"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "CreateTicket"
-msgstr "新增申请å•"
-
-#: html/Elements/SelectDateType:47 html/Ticket/Elements/ShowDates:48 lib/RT/Ticket_Overlay.pm:1169
-msgid "Created"
-msgstr "新增日"
-
-#: html/Admin/CustomFields/Modify.html:163 html/Admin/Elements/EditCustomField:117
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "自订字段 %1 新增æˆåŠŸ"
-
-#: html/Tools/Reports/Elements/Tabs:63
-msgid "Created in a date range"
-msgstr "在指定日期内建立"
-
-#: NOT FOUND IN SOURCE
-msgid "Created template %1"
-msgstr "æ¨¡æ¿ %1 新增æˆåŠŸ"
-
-#: html/Tools/Reports/CreatedByDates.html:52
-msgid "Created tickets in period, grouped by status"
-msgstr "在指定日期内建立的申请å•ï¼Œä¾çŠ¶æ€åˆ†ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "Created workflow %1"
-msgstr "æµç¨‹ %1 新增æˆåŠŸ"
-
-#: html/Search/Elements/PickBasics:102
-msgid "Creator"
-msgstr "建立者"
-
-#: 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 "现有自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Groups:"
-msgstr "现有群组列表:"
-
-#: html/Elements/EditLinks:49
-msgid "Current Links"
-msgstr "现有关系"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Rights:"
-msgstr "现有æƒé™ï¼š"
-
-#: html/Admin/Elements/EditScrips:51
-msgid "Current Scrips"
-msgstr "现有手续"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Status"
-msgstr "ç›®å‰çŠ¶æ€"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Templates"
-msgstr "现有模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Watchers"
-msgstr "现有视察员"
-
-#: html/Admin/Groups/Members.html:60 html/User/Groups/Members.html:63
-msgid "Current members"
-msgstr "现有æˆå‘˜"
-
-#: html/Admin/Elements/SelectRights:51
-msgid "Current rights"
-msgstr "现有æƒé™"
-
-#: html/Search/Elements/EditQuery:47
-msgid "Current search"
-msgstr "现有查询æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Current search criteria"
-msgstr "现有查询æ¡ä»¶"
-
-#: html/Admin/Queues/People.html:62 html/Ticket/Elements/EditPeople:66
-msgid "Current watchers"
-msgstr "现有视察员"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Field #%1"
-msgstr "自订字段 #%1"
-
-#: html/Admin/Elements/SystemTabs:61 html/Admin/Elements/Tabs:62 html/Admin/Global/index.html:71 html/Admin/Users/Modify.html:205 html/Admin/index.html:77 html/Ticket/Elements/ShowSummary:56
-msgid "Custom Fields"
-msgstr "自订字段"
-
-#: html/Admin/CustomFields/index.html:60
-#. ($lookup)
-msgid "Custom Fields for %1"
-msgstr "%1 的自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Fields which apply to all queues"
-msgstr "适用于所有表å•çš„自订字段"
-
-#: html/Admin/Elements/EditScrip:107
-msgid "Custom action cleanup code"
-msgstr "动作åŽæ‰§è¡Œç¨‹åº"
-
-#: html/Admin/Elements/EditScrip:103
-msgid "Custom action preparation code"
-msgstr "动作å‰æ‰§è¡Œç¨‹åº"
-
-#: html/Admin/Elements/EditScrip:99
-msgid "Custom condition"
-msgstr "自订æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 %2 %3"
-msgstr "自订字段 %1 %2 %3"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 does not apply to this object"
-msgstr "自订字段 %1 ä¸é€‚用于此对象"
-
-#: lib/RT/Tickets_Overlay.pm:2424
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr "自订字段 %1 已有值"
-
-#: lib/RT/Tickets_Overlay.pm:2420
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr "自订字段 %1 没有值"
-
-#: lib/RT/Record.pm:1592 lib/RT/Record.pm:1754
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr "找ä¸åˆ°è‡ªè®¢å­—段 %1"
-
-#: lib/RT/Report/Tickets.pm:118 lib/RT/Report/Tickets.pm:121
-#. ($cf)
-#. ($obj->Name)
-msgid "Custom field '%1'"
-msgstr "自订字段‘%1’"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field deleted"
-msgstr "自订字段已删除"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field not found"
-msgstr "找ä¸åˆ°è‡ªè®¢å­—段"
-
-#: lib/RT/CustomField_Overlay.pm:1157
-#. ($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:419
-msgid "Custom field value could not be deleted"
-msgstr "无法删除自订字段值"
-
-#: lib/RT/CustomField_Overlay.pm:1169
-msgid "Custom field value could not be found"
-msgstr "找ä¸åˆ°è‡ªè®¢å­—段值"
-
-#: lib/RT/CustomField_Overlay.pm:1171 lib/RT/CustomField_Overlay.pm:417
-msgid "Custom field value deleted"
-msgstr "自订字段值删除æˆåŠŸ"
-
-#: html/Elements/SelectGroups:51 html/Elements/SelectUsers:51 lib/RT/Transaction_Overlay.pm:638
-msgid "CustomField"
-msgstr "自订字段"
-
-#: html/Prefs/MyRT.html:78 html/Prefs/Quicksearch.html:70 html/Prefs/Search.html:75
-msgid "Customize"
-msgstr "自订"
-
-#: NOT FOUND IN SOURCE
-msgid "Data error"
-msgstr "æ•°æ®é”™è¯¯"
-
-#: NOT FOUND IN SOURCE
-msgid "DatabaseBindRemote"
-msgstr "容许外部è”机"
-
-#: NOT FOUND IN SOURCE
-msgid "DatabaseName"
-msgstr "MySQLæ•°æ®åº“"
-
-#: NOT FOUND IN SOURCE
-msgid "Date of Departure"
-msgstr "出å‘日期"
-
-#: html/SelfService/Display.html:61 html/Ticket/Create.html:203 html/Ticket/Elements/ShowSummary:83 html/Ticket/Elements/Tabs:116 html/Ticket/ModifyAll.html:65
-msgid "Dates"
-msgstr "日期"
-
-#: NOT FOUND IN SOURCE
-msgid "Dec"
-msgstr "å二月"
-
-#: lib/RT/Date.pm:452
-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:222
-msgid "Default Autoresponse template"
-msgstr "预设自动å“应模æ¿"
-
-#: html/Tools/Offline.html:61
-msgid "Default Queue"
-msgstr "预设表å•"
-
-#: html/Tools/Offline.html:70
-msgid "Default Requestor"
-msgstr "预设申请人"
-
-#: NOT FOUND IN SOURCE
-msgid "Default Value"
-msgstr "预设值"
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr "预设管ç†å‘˜è¯„论模æ¿"
-
-#: etc/initialdata:275
-msgid "Default admin correspondence template"
-msgstr "预设管ç†å‘˜å›žå¤æ¨¡æ¿"
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr "预设回å¤æ¨¡æ¿"
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr "预设更动模æ¿"
-
-#: NOT FOUND IN SOURCE
-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:46 html/User/Delegation.html:49
-msgid "Delegate rights"
-msgstr "代ç†äººæƒé™"
-
-#: lib/RT/System.pm:84
-msgid "Delegate specific rights which have been granted to you."
-msgstr "将拥有的æƒé™å§”托他人代ç†"
-
-#: lib/RT/System.pm:84
-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 "代ç†è¡¨å•ç§ç±»"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates"
-msgstr "代ç†äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates Enabled Status"
-msgstr "代ç†æ¿€æ´»çŠ¶æ€"
-
-#: NOT FOUND IN SOURCE
-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 "代ç†äººè®¾å®š"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates Status"
-msgstr "代ç†çŠ¶æ€"
-
-#: html/User/Elements/Tabs:59
-msgid "Delegation"
-msgstr "代ç†äººæƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegation Groups"
-msgstr "代ç†äººç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegation Rights"
-msgstr "代ç†äººæƒé™"
-
-#: html/Admin/Elements/EditScrips:75 html/Search/Elements/EditFormat:103 html/Search/Elements/EditQuery:57 html/Search/Elements/EditSearches:63 html/Widgets/SelectionBox:204
-msgid "Delete"
-msgstr "删除"
-
-#: html/Admin/Elements/EditTemplates:79
-msgid "Delete Template"
-msgstr "删除模æ¿"
-
-#: lib/RT/SavedSearch.pm:220
-#. ($msg)
-msgid "Delete failed: %1"
-msgstr "删除失败:%1"
-
-#: html/Admin/Elements/EditScrips:74
-msgid "Delete selected scrips"
-msgstr "删除指定的手续"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "Delete tickets"
-msgstr "删除申请å•"
-
-#: html/Search/Bulk.html:159
-msgid "Delete values"
-msgstr "删除值"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "DeleteTicket"
-msgstr "删除申请å•"
-
-#: lib/RT/SavedSearch.pm:218
-msgid "Deleted search"
-msgstr "已删除的æœå¯»"
-
-#: NOT FOUND IN SOURCE
-msgid "Deleting this object could break referential integrity"
-msgstr "删除此对象å¯èƒ½ç ´åå‚考完整性"
-
-#: lib/RT/Queue_Overlay.pm:394
-msgid "Deleting this object would break referential integrity"
-msgstr "删除此对象å¯èƒ½ç ´åå‚考完整性"
-
-#: lib/RT/User_Overlay.pm:512
-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:73
-msgid "Deny"
-msgstr "驳回"
-
-#: NOT FOUND IN SOURCE
-msgid "Department"
-msgstr "部门"
-
-#: NOT FOUND IN SOURCE
-msgid "Department ID"
-msgstr "部门代ç "
-
-#: NOT FOUND IN SOURCE
-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/Elements/EditLinks:140 html/Elements/EditLinks:66 html/Elements/ShowLinks:58 html/Ticket/Create.html:221 html/Ticket/Elements/BulkLinks:56 html/Ticket/Elements/ShowDependencies:53
-msgid "Depended on by"
-msgstr "å¯æŽ¥ç»­å¤„ç†çš„申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Dependencies: \\n"
-msgstr "附属性:\\n"
-
-#: lib/RT/Transaction_Overlay.pm:718
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr "已加入å¯æŽ¥ç»­å¤„ç†çš„ç”³è¯·å• %1"
-
-#: lib/RT/Transaction_Overlay.pm:758
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr "已移除å¯æŽ¥ç»­å¤„ç†çš„ç”³è¯·å• %1"
-
-#: lib/RT/Transaction_Overlay.pm:715
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr "已加入需先处ç†çš„ç”³è¯·å• %1"
-
-#: lib/RT/Transaction_Overlay.pm:755
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr "已移除需先处ç†çš„ç”³è¯·å• %1"
-
-#: html/Elements/EditLinks:136 html/Elements/EditLinks:57 html/Elements/SelectLinkType:48 html/Elements/ShowLinks:48 html/Ticket/Create.html:220 html/Ticket/Elements/BulkLinks:52 html/Ticket/Elements/ShowDependencies:46
-msgid "Depends on"
-msgstr "需先处ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "DependsOn"
-msgstr "需先处ç†"
-
-#: html/Search/Elements/DisplayOptions:86
-msgid "Desc"
-msgstr "递å‡"
-
-#: html/Elements/SelectSortOrder:56
-msgid "Descending"
-msgstr "递å‡"
-
-#: html/SelfService/Create.html:100 html/Ticket/Create.html:152
-msgid "Describe the issue below"
-msgstr "在以下字段æ述主题"
-
-#: html/Admin/CustomFields/Modify.html:61 html/Admin/Elements/AddCustomFieldValue:57 html/Admin/Elements/EditCustomField:60 html/Admin/Elements/EditCustomFieldValues:56 html/Admin/Elements/EditScrip:55 html/Admin/Elements/ModifyTemplate:57 html/Admin/Groups/Modify.html:71 html/Admin/Queues/Modify.html:69 html/Search/Elements/EditSearches:56 html/User/Groups/Modify.html:70
-msgid "Description"
-msgstr "æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Description of Responsibility"
-msgstr "ç»åŠžä¸šåŠ¡è¯´æ˜Ž"
-
-#: NOT FOUND IN SOURCE
-msgid "Description:"
-msgstr "æ述:"
-
-#: NOT FOUND IN SOURCE
-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 "残障类别"
-
-#: NOT FOUND IN SOURCE
-msgid "Disabled"
-msgstr "åœç”¨"
-
-#: html/Search/Elements/EditFormat:71 html/Ticket/Elements/Tabs:108
-msgid "Display"
-msgstr "显示内容"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "Display Access Control List"
-msgstr "显示æƒé™æŽ§åˆ¶æ¸…å•"
-
-#: html/Search/Elements/DisplayOptions:46
-msgid "Display Columns"
-msgstr "显示字段"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "Display Scrip templates for this queue"
-msgstr "显示此表å•çš„模æ¿"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "Display Scrips for this queue"
-msgstr "显示此表å•çš„手续"
-
-#: html/Ticket/Elements/ShowHistory:59
-msgid "Display mode"
-msgstr "显示模å¼"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "Display saved searches for this group"
-msgstr "显示此群组的预存查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Display ticket #%1"
-msgstr "显示第%1å·ç”³è¯·å•"
-
-#: html/Elements/Footer:61
-msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>"
-msgstr "ä¾ <a href=\"http://www.gnu.org/copyleft/gpl.html\">GNU 通用公共授æƒ</a> 第二版散布。"
-
-#: lib/RT/System.pm:75
-msgid "Do anything and everything"
-msgstr "å…许一切æ“作"
-
-#: html/Elements/Refresh:51
-msgid "Don't refresh this page."
-msgstr "ä¸æ›´æ–°æ­¤é¡µé¢ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Don't show search results"
-msgstr "ä¸æ˜¾ç¤ºæŸ¥è¯¢ç»“æžœ"
-
-#: NOT FOUND IN SOURCE
-msgid "Done"
-msgstr "完æˆ"
-
-#: NOT FOUND IN SOURCE
-msgid "Down"
-msgstr "下一页"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "Download"
-msgstr "下载"
-
-#: html/Admin/Groups/index.html:61 html/Admin/Users/index.html:64
-msgid "Download as a tab-delimited file"
-msgstr "下载以 Tab 分隔的档案"
-
-#: NOT FOUND IN SOURCE
-msgid "Dr."
-msgstr "åšå£«"
-
-#: html/Elements/SelectDateType:53 html/Ticket/Create.html:209 html/Ticket/Elements/EditDates:66 html/Ticket/Elements/Reminders:133 html/Ticket/Elements/ShowDates:64 lib/RT/Ticket_Overlay.pm:1173
-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'"
-
-#: NOT FOUND IN SOURCE
-msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
-msgstr "æ— æ³•åŠ è½½ç”³è¯·å• '%1':%2.\\n"
-
-#: html/Elements/Quicksearch:48 html/Elements/ShowSearch:49 html/index.html:107
-msgid "Edit"
-msgstr "编辑"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Conditions"
-msgstr "编辑å‰ç½®æ¡ä»¶"
-
-#: html/Search/Bulk.html:149
-msgid "Edit Custom Fields"
-msgstr "编辑自订字段"
-
-#: html/Admin/Elements/ObjectCustomFields:92 html/Admin/Queues/CustomFields.html:64 html/Admin/Users/CustomFields.html:64
-#. ($Object->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "编辑 %1 的自订字段"
-
-#: html/Admin/Global/CustomFields/Groups.html:54
-msgid "Edit Custom Fields for all groups"
-msgstr "编辑适用于所有群组的自订字段"
-
-#: html/Admin/Global/CustomFields/Users.html:54
-msgid "Edit Custom Fields for all users"
-msgstr "编辑适用于所有使用者的自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Custom Fields for queue %1"
-msgstr "ç¼–è¾‘è¡¨å• %1 的自订字段"
-
-#: html/Admin/Global/CustomFields/Queue-Tickets.html:54 html/Admin/Global/CustomFields/Queue-Transactions.html:54
-msgid "Edit Custom Fields for tickets in all queues"
-msgstr "编辑适用于所有表å•å†…申请å•çš„自订字段"
-
-#: html/Search/Bulk.html:188 html/Ticket/ModifyLinks.html:57
-msgid "Edit Links"
-msgstr "编辑申请å•å…³ç³»"
-
-#: html/Search/Edit.html:68
-msgid "Edit Query"
-msgstr "编辑查询"
-
-#: html/Ticket/Elements/Tabs:214
-msgid "Edit Search"
-msgstr "编辑查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Subgroups"
-msgstr "新增/维护å­ç¾¤ç»„"
-
-#: html/Admin/Queues/Templates.html:63
-#. ($QueueObj->Name)
-msgid "Edit Templates for queue %1"
-msgstr "ç¼–è¾‘è¡¨å• %1 的模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Workflows for queue %1"
-msgstr "ç¼–è¾‘è¡¨å• %1 çš„æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit keywords"
-msgstr "编辑关键è¯"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "Edit saved searches for this group"
-msgstr "编辑此群组的预存查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit scrips"
-msgstr "编辑手续"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:60 html/Admin/Global/index.html:67
-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 çš„æµç¨‹"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "EditSavedSearches"
-msgstr "编辑预存查询"
-
-#: html/Admin/Queues/Modify.html:140
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr "ç¼–è¾‘è¡¨å• %1 的设定"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing Configuration for user %1"
-msgstr "编辑使用者 %1 的设定"
-
-#: html/Admin/CustomFields/Modify.html:167 html/Admin/Elements/EditCustomField:120
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr "编辑自订字段 %1"
-
-#: html/Admin/Groups/Members.html:53
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr "编辑群组 %1 çš„æˆå‘˜ä¿¡æ¯"
-
-#: html/User/Groups/Members.html:150
-#. ($Group->Name)
-msgid "Editing membership for personal group %1"
-msgstr "编辑代ç†äººç¾¤ç»„ %1 çš„æˆå‘˜ä¿¡æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing template %1"
-msgstr "ç¼–è¾‘æ¨¡æ¿ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing workflow %1"
-msgstr "编辑æµç¨‹ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Education"
-msgstr "最高学历"
-
-#: NOT FOUND IN SOURCE
-msgid "EffectiveId"
-msgstr "有效编å·"
-
-#: lib/RT/Record.pm:1295 lib/RT/Record.pm:1372 lib/RT/Ticket_Overlay.pm:2518 lib/RT/Ticket_Overlay.pm:2608
-msgid "Either base or target must be specified"
-msgstr "需è¦æŒ‡å®šèµ·å§‹ç”³è¯·å•æˆ–目的申请å•"
-
-#: html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65
-msgid "Email"
-msgstr "电å­é‚®ä»¶ä¿¡ç®±"
-
-#: NOT FOUND IN SOURCE
-msgid "Email Address"
-msgstr "电å­é‚®ä»¶ä¿¡ç®±"
-
-#: lib/RT/User_Overlay.pm:235
-msgid "Email address in use"
-msgstr "此电å­é‚®ä»¶ä¿¡ç®±å·²è¢«ä½¿ç”¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailAddress"
-msgstr "电å­é‚®ä»¶ä¿¡ç®±åœ°å€"
-
-#: NOT FOUND IN SOURCE
-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/CustomFields/Modify.html:98 html/Admin/Elements/EditCustomField:72
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤è‡ªè®¢å­—段)"
-
-#: html/Admin/Groups/Modify.html:84 html/User/Groups/Modify.html:74
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤ç¾¤ç»„)"
-
-#: html/Admin/Queues/Modify.html:105
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤è¡¨å•)"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Custom Fields"
-msgstr "å·²å¯ç”¨çš„自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Date"
-msgstr "å¯ç”¨æ—¥æœŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Date:"
-msgstr "激活日期:"
-
-#: html/Admin/Queues/index.html:78
-msgid "Enabled Queues"
-msgstr "å·²å¯ç”¨çš„表å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Status"
-msgstr "å¯ç”¨çŠ¶æ€"
-
-#: html/Admin/Elements/EditCustomField:136 html/Admin/Groups/Modify.html:150 html/Admin/Users/Modify.html:350 html/User/Groups/Modify.html:138
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr "å¯ç”¨çŠ¶æ€ %1"
-
-#: html/Admin/CustomFields/Modify.html:185 html/Admin/Queues/Modify.html:162
-#. (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:64
-msgid "Enter multiple values"
-msgstr "键入多é‡é¡¹ç›®"
-
-#: html/Elements/EditLinks:126
-msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces."
-msgstr "键入欲将对象连结至的对象或 URI。项目之间请以空白隔开。"
-
-#: NOT FOUND IN SOURCE
-msgid "Enter one or more conditions below to search for users"
-msgstr "键入下列å•ä¸€æˆ–å¤å¼æ¡ä»¶ï¼ŒæŸ¥è¯¢ç”¨æˆ·æ•°æ®"
-
-#: lib/RT/CustomField_Overlay.pm:65
-msgid "Enter one value"
-msgstr "键入å•ä¸€é¡¹ç›®"
-
-#: html/Elements/EditLinks:123
-msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces."
-msgstr "键入欲将表å•è¿žç»“至的对象或 URI。项目之间请以空白隔开。"
-
-#: html/Elements/EditLinks:119 html/Search/Bulk.html:189
-msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces."
-msgstr "键入申请å•å¯é“¾æŽ¥åˆ°çš„申请å•ç¼–å·æˆ–网å€ã€‚项目之间请以空白隔开。"
-
-#: lib/RT/CustomField_Overlay.pm:66
-msgid "Enter up to %1 values"
-msgstr "键入最多 %1 个项目"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryBoolean"
-msgstr "是éžå¡«è¡¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryDate"
-msgstr "日期填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryExternal"
-msgstr "系统填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryFreeform"
-msgstr "输入填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryMultiple"
-msgstr "多选填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryNumber"
-msgstr "数值填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntrySelect"
-msgstr "å•é€‰å¡«è¡¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryTime"
-msgstr "时间填表"
-
-#: html/Elements/Login:76 html/SelfService/Error.html:46 html/SelfService/Error.html:47
-msgid "Error"
-msgstr "错误"
-
-#: NOT FOUND IN SOURCE
-msgid "Error adding watcher"
-msgstr "新增视察员失败"
-
-#: lib/RT/Queue_Overlay.pm:672
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr "表å•->新增视察员的å‚数有误"
-
-#: lib/RT/Queue_Overlay.pm:833
-msgid "Error in parameters to Queue->DeleteWatcher"
-msgstr "表å•->删除视察员的å‚数有误"
-
-#: lib/RT/Ticket_Overlay.pm:1372
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr "申请å•->新增视察员的å‚数有误"
-
-#: lib/RT/Ticket_Overlay.pm:1538
-msgid "Error in parameters to Ticket->DeleteWatcher"
-msgstr "申请å•->删除视察员的å‚数有误"
-
-#: bin/rt-crontool:285
-msgid "Escalate tickets"
-msgstr "调整申请å•ä¼˜å…ˆç­‰çº§"
-
-#: NOT FOUND IN SOURCE
-msgid "Estimate"
-msgstr "预计"
-
-#: html/Ticket/Elements/ShowBasics:57
-msgid "Estimated"
-msgstr "预计"
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "所有人"
-
-#: bin/rt-crontool:271
-msgid "Example:"
-msgstr "范例:"
-
-#: NOT FOUND IN SOURCE
-msgid "Existing user renamed from %1 to %2"
-msgstr "现有使用者 %1 已改å为 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Export"
-msgstr "汇出"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalAuthId"
-msgstr "外部认è¯å¸å·"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalContactInfoId"
-msgstr "外部è”络方å¼å¸å·"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabaseDSN"
-msgstr "外部数æ®åº“连结字符串"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabasePass"
-msgstr "外部数æ®åº“å£ä»¤"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabaseUser"
-msgstr "外部数æ®åº“用户"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalURL"
-msgstr "外部接å£ç½‘å€"
-
-#: html/Admin/Users/Modify.html:99
-msgid "Extra info"
-msgstr "备注"
-
-#: lib/RT/SavedSearch.pm:177
-msgid "Failed to create search attribute"
-msgstr "查询属性建立失败"
-
-#: lib/RT/User_Overlay.pm:376
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr "找ä¸åˆ°â€˜å†…部æˆå‘˜â€™è™šæ‹Ÿç¾¤ç»„的使用者。"
-
-#: lib/RT/User_Overlay.pm:383
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr "找ä¸åˆ°â€˜éžå†…部æˆå‘˜â€™è™šæ‹Ÿç¾¤ç»„的使用者。"
-
-#: bin/rt-crontool:206
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr "æ— æ³•åŠ è½½æ¨¡å— %1。(%2)"
-
-#: lib/RT/SavedSearch.pm:152
-#. ($privacy)
-msgid "Failed to load object for %1"
-msgstr "无法为 %1 加载对象。"
-
-#: NOT FOUND IN SOURCE
-msgid "Feb"
-msgstr "二月"
-
-#: lib/RT/Date.pm:442
-msgid "Feb."
-msgstr "02"
-
-#: NOT FOUND IN SOURCE
-msgid "February"
-msgstr "二月"
-
-#: NOT FOUND IN SOURCE
-msgid "Female"
-msgstr "女"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Content:"
-msgstr "字段内容:"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Description"
-msgstr "字段æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Name"
-msgstr "字段å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Type"
-msgstr "字段类别"
-
-#: html/Elements/SelectAttachmentField:50
-msgid "Filename"
-msgstr "æ¡£å"
-
-#: lib/RT/CustomField_Overlay.pm:69
-msgid "Fill in multiple text areas"
-msgstr "填入多个文字框"
-
-#: lib/RT/CustomField_Overlay.pm:74
-msgid "Fill in multiple wikitext areas"
-msgstr "填入多个 Wiki 文字框"
-
-#: lib/RT/CustomField_Overlay.pm:70
-msgid "Fill in one text area"
-msgstr "填入一个文字框"
-
-#: lib/RT/CustomField_Overlay.pm:75
-msgid "Fill in one wikitext area"
-msgstr "填入一个 Wiki 文字框"
-
-#: html/Admin/CustomFields/Modify.html:107 html/Admin/CustomFields/Modify.html:118
-msgid "Fill in this field with a URL."
-msgstr "填入一个网å€"
-
-#: lib/RT/CustomField_Overlay.pm:71
-msgid "Fill in up to %1 text areas"
-msgstr "填入最多 %1 个文字框"
-
-#: lib/RT/CustomField_Overlay.pm:76
-msgid "Fill in up to %1 wikitext areas"
-msgstr "填入最多 %1 个 Wiki 文字框"
-
-#: NOT FOUND IN SOURCE
-msgid "Filter"
-msgstr "筛选"
-
-#: NOT FOUND IN SOURCE
-msgid "Filter people"
-msgstr "对象筛选"
-
-#: NOT FOUND IN SOURCE
-msgid "Filtered list:"
-msgstr "筛选列表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Fin"
-msgstr "最终"
-
-#: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:182 html/Ticket/Elements/EditBasics:97 lib/RT/Tickets_Overlay.pm:1841
-msgid "Final Priority"
-msgstr "最终顺ä½"
-
-#: lib/RT/Ticket_Overlay.pm:1164
-msgid "FinalPriority"
-msgstr "最终顺ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Financial Department:"
-msgstr "财务部:"
-
-#: NOT FOUND IN SOURCE
-msgid "Find group whose"
-msgstr "寻找群组的"
-
-#: html/Admin/Groups/index.html:72 html/Admin/Queues/People.html:82 html/Ticket/Elements/EditPeople:55
-msgid "Find groups whose"
-msgstr "寻找群组的"
-
-#: NOT FOUND IN SOURCE
-msgid "Find new/open tickets"
-msgstr "寻找/å¼€å¯ç”³è¯·å•"
-
-#: html/Admin/Queues/People.html:78 html/Admin/Users/index.html:70 html/Ticket/Elements/EditPeople:51
-msgid "Find people whose"
-msgstr "寻找人员的"
-
-#: NOT FOUND IN SOURCE
-msgid "Find queues whose"
-msgstr "寻找表å•çš„"
-
-#: html/Search/Results.html:147
-msgid "Find tickets"
-msgstr "寻找申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Finish Approval"
-msgstr "签核完毕"
-
-#: html/Ticket/Elements/Tabs:81
-msgid "First"
-msgstr "第一项"
-
-#: NOT FOUND IN SOURCE
-msgid "First page"
-msgstr "第一页"
-
-#: NOT FOUND IN SOURCE
-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:766
-msgid "Foo Bar Baz"
-msgstr "甲 乙 丙"
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:757
-msgid "Foo!"
-msgstr "甲ï¼"
-
-#: html/Search/Bulk.html:83
-msgid "Force change"
-msgstr "强制更æ¢"
-
-#: NOT FOUND IN SOURCE
-msgid "Form Processing"
-msgstr "电å­è¡¨å•ä½œä¸šåŒº"
-
-#: html/Search/Elements/EditFormat:52
-msgid "Format"
-msgstr "æ ¼å¼"
-
-#: html/Search/Results.html:145
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr "找到 %1 张申请å•"
-
-#: lib/RT/Record.pm:956
-msgid "Found Object"
-msgstr "已找到对象"
-
-#: NOT FOUND IN SOURCE
-msgid "Fourth-"
-msgstr "å››"
-
-#: NOT FOUND IN SOURCE
-msgid "Freeform"
-msgstr "输入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformContactInfo"
-msgstr "è”络方å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformDate"
-msgstr "日期输入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformExternal"
-msgstr "系统字段"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformMultiple"
-msgstr "多é‡è¾“å…¥"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformNumber"
-msgstr "数值输入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformPassword"
-msgstr "å£ä»¤è¾“å…¥"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformSingle"
-msgstr "å•ä¸€è¾“å…¥"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformTime"
-msgstr "时间输入"
-
-#: NOT FOUND IN SOURCE
-msgid "Fri"
-msgstr "星期五"
-
-#: lib/RT/Date.pm:421
-msgid "Fri."
-msgstr "星期五"
-
-#: html/Ticket/Elements/ShowHistory:66 html/Ticket/Elements/ShowHistory:72
-msgid "Full headers"
-msgstr "完整标头档"
-
-#: NOT FOUND IN SOURCE
-msgid "Gecos"
-msgstr "登入å¸å·"
-
-#: NOT FOUND IN SOURCE
-msgid "Gender"
-msgstr "性别"
-
-#: html/Tools/Offline.html:85
-msgid "Get template from file"
-msgstr "å–出档案里的模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Getting the current user from a pgp sig\\n"
-msgstr "å–å¾—ç›®å‰ä½¿ç”¨è€…çš„ pgp 签章\\n"
-
-#: lib/RT/Transaction_Overlay.pm:684
-#. ($New->Name)
-msgid "Given to %1"
-msgstr "交予 %1"
-
-#: html/Admin/Elements/Tabs:65 html/Admin/index.html:82
-msgid "Global"
-msgstr "全域设定"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Approval"
-msgstr "全域签核"
-
-#: html/Admin/Elements/EditCustomFields:55
-msgid "Global Custom Fields"
-msgstr "全域自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Keyword Selections"
-msgstr "全域关键è¯é€‰å–"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Rights:"
-msgstr "拥有全域æƒé™åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Scrips"
-msgstr "全域手续"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Setup"
-msgstr "全域设定"
-
-#: html/Admin/Global/CustomFields/index.html:59
-msgid "Global custom field configuration"
-msgstr "全域自订字段设定"
-
-#: html/Admin/Global/MyRT.html:48
-#. ($pane)
-msgid "Global portlet %1 saved."
-msgstr "æˆåŠŸå‚¨å­˜å…¨åŸŸå…¥å£ç»„件 %1。"
-
-#: html/Admin/Elements/SelectTemplate:59
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr "全域模æ¿ï¼š%1"
-
-#: NOT FOUND IN SOURCE
-msgid "GlobalApproval"
-msgstr "全域签核"
-
-#: html/Admin/CustomFields/index.html:80 html/Search/Results.html:90 html/Tools/Offline.html:89
-msgid "Go"
-msgstr "执行"
-
-#: html/Admin/Groups/index.html:67 html/Admin/Groups/index.html:73 html/Admin/Queues/People.html:80 html/Admin/Queues/People.html:84 html/Admin/Queues/index.html:66 html/Admin/Users/index.html:73 html/Elements/RefreshHomepage:48 html/Search/Results.html:74 html/Ticket/Elements/EditPeople:53 html/Ticket/Elements/EditPeople:57
-msgid "Go!"
-msgstr "执行"
-
-#: NOT FOUND IN SOURCE
-msgid "Good pgp sig from %1\\n"
-msgstr "%1 的 pgp 签章是正确的\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Goto page"
-msgstr "到页é¢"
-
-#: html/Elements/GotoTicket:46 html/SelfService/Elements/GotoTicket:46
-msgid "Goto ticket"
-msgstr "跳到申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Grand"
-msgstr "上"
-
-#: html/Ticket/Elements/AddWatchers:67 html/Ticket/Elements/ShowGroupMembers:55 html/User/Elements/DelegateRights:99
-msgid "Group"
-msgstr "群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Group %1 %2: %3"
-msgstr "群组 %1 %2:%3"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Admin"
-msgstr "群组管ç†å‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Description"
-msgstr "群组æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Management"
-msgstr "群组管ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Members"
-msgstr "群组æˆå‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Name"
-msgstr "群组å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Name:"
-msgstr "群组å称:"
-
-#: html/Admin/Elements/CustomFieldTabs:68 html/Admin/Elements/GroupTabs:66 html/Admin/Elements/QueueTabs:82 html/Admin/Elements/SystemTabs:65 html/Admin/Global/index.html:76
-msgid "Group Rights"
-msgstr "群组æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Rights:"
-msgstr "拥有群组æƒé™åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Setup"
-msgstr "群组设定"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Status"
-msgstr "群组状æ€"
-
-#: lib/RT/Group_Overlay.pm:983
-msgid "Group already has member"
-msgstr "群组内已有此æˆå‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Group could not be created."
-msgstr "无法新增群组"
-
-#: html/Admin/Groups/Modify.html:109
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "无法新增群组:%1"
-
-#: lib/RT/Group_Overlay.pm:521
-msgid "Group created"
-msgstr "群组新增完毕"
-
-#: NOT FOUND IN SOURCE
-msgid "Group created: %1"
-msgstr "群组 %1 新增完毕"
-
-#: lib/RT/Group_Overlay.pm:1155
-msgid "Group has no such member"
-msgstr "群组没有这个æˆå‘˜"
-
-#: lib/RT/Group_Overlay.pm:963 lib/RT/Queue_Overlay.pm:748 lib/RT/Queue_Overlay.pm:808 lib/RT/Ticket_Overlay.pm:1430 lib/RT/Ticket_Overlay.pm:1510
-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 "拥有表å•æƒé™ç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "Group's"
-msgstr "群组之"
-
-#: NOT FOUND IN SOURCE
-msgid "Group:"
-msgstr "群组:"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:57 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:86 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1210
-msgid "Groups"
-msgstr "群组"
-
-#: lib/RT/Group_Overlay.pm:989
-msgid "Groups can't be members of their members"
-msgstr "ä¸èƒ½å°†ç¾¤ç»„设为群组内æˆå‘˜"
-
-#: html/Admin/Groups/index.html:86
-msgid "Groups matching search criteria"
-msgstr "符åˆæŸ¥è¯¢æ¡ä»¶çš„群组"
-
-#: html/Ticket/Elements/ShowRequestor:77
-msgid "Groups this user belongs to"
-msgstr "使用者所属的群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Groups with Global Rights"
-msgstr "拥有全域æƒé™ç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "HRMSDefined"
-msgstr "组织架构"
-
-#: NOT FOUND IN SOURCE
-msgid "HTML Attributes"
-msgstr "HTML 属性"
-
-#: NOT FOUND IN SOURCE
-msgid "Health Insurance"
-msgstr "å¥ä¿è¡¥åŠ©èº«ä»½"
-
-#: lib/RT/Interface/CLI.pm:94 lib/RT/Interface/CLI.pm:94
-msgid "Hello!"
-msgstr "å—¨ï¼"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:773
-#. ($name)
-msgid "Hello, %1"
-msgstr "嗨,%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Help"
-msgstr "说明"
-
-#: NOT FOUND IN SOURCE
-msgid "Help Desks"
-msgstr "å„项业务窗å£"
-
-#: NOT FOUND IN SOURCE
-msgid "Hidden"
-msgstr "éšè—"
-
-#: html/Admin/Elements/GroupTabs:70 html/Admin/Elements/UserTabs:64 html/Ticket/Elements/ShowHistory:53 html/Ticket/Elements/Tabs:111
-msgid "History"
-msgstr "纪录"
-
-#: html/Admin/Groups/History.html:62
-#. ($GroupObj->Name)
-msgid "History of the group %1"
-msgstr "群组 %1 的纪录"
-
-#: html/Admin/Users/History.html:62
-#. ($UserObj->Name)
-msgid "History of the user %1"
-msgstr "使用者 %1 的纪录"
-
-#: NOT FOUND IN SOURCE
-msgid "HomePhone"
-msgstr "ä½å¤„电è¯"
-
-#: html/Elements/Tabs:65
-msgid "Homepage"
-msgstr "主页"
-
-#: NOT FOUND IN SOURCE
-msgid "Hotel Expense"
-msgstr "ä½å®¿è´¹"
-
-#: html/Elements/SelectTimeUnits:48
-msgid "Hours"
-msgstr "å°æ—¶"
-
-#: lib/RT/Base.pm:119
-#. (6)
-msgid "I have %quant(%1,concrete mixer)."
-msgstr "我有 %quant(%1,份固体æ…拌器)。"
-
-#: html/Search/Build.html:460 lib/RT/Report/Tickets.pm:415
-msgid "I'm lost"
-msgstr "我æ˜äº†"
-
-#: NOT FOUND IN SOURCE
-msgid "ID Number"
-msgstr "身分è¯å·"
-
-#: NOT FOUND IN SOURCE
-msgid "ID Type"
-msgstr "身分类别"
-
-#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1766
-msgid "Id"
-msgstr "ç¼–å·"
-
-#: html/Admin/Users/Modify.html:65 html/User/Prefs.html:60
-msgid "Identity"
-msgstr "身份"
-
-#: etc/initialdata:429
-msgid "If an approval is rejected, reject the original and delete pending approvals"
-msgstr "若签核å•é­åˆ°é©³å›žï¼Œåˆ™è¿žå¸¦é©³å›žåŽŸç”³è¯·å•ï¼Œå¹¶åˆ é™¤å…¶å®ƒç›¸å…³çš„待签核事项"
-
-#: html/Tools/Offline.html:74
-msgid "If no Requestor is specified, create tickets with this requestor."
-msgstr "若没有指定申请者,则以此使用者作为申请者"
-
-#: html/Tools/Offline.html:65
-msgid "If no queue is specified, create tickets in this queue."
-msgstr "申请å•è‹¥æ²¡æœ‰æŒ‡å®šè¡¨å•ï¼Œåˆ™å°†å®ƒæ–°å¢žåœ¨æ­¤è¡¨å•å†…"
-
-#: bin/rt-crontool:267
-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:126 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:128 html/Ticket/ModifyPeople.html:60
-msgid "If you've updated anything above, be sure to"
-msgstr "若您已更新以上数æ®ï¼Œè¯·è®°å¾—按一下"
-
-#: lib/RT/Record.pm:947
-msgid "Illegal value for %1"
-msgstr "%1 的值错误"
-
-#: NOT FOUND IN SOURCE
-msgid "Image"
-msgstr "图片"
-
-#: lib/RT/Record.pm:950
-msgid "Immutable field"
-msgstr "此字段值ä¸å¯æ›´åŠ¨"
-
-#: NOT FOUND IN SOURCE
-msgid "Import"
-msgstr "汇入"
-
-#: NOT FOUND IN SOURCE
-msgid "Include disabled custom fields in listing."
-msgstr "列出åœç”¨çš„自订字段"
-
-#: html/Admin/Groups/index.html:65
-msgid "Include disabled groups in listing."
-msgstr "列出åœç”¨çš„群组"
-
-#: html/Admin/Queues/index.html:65
-msgid "Include disabled queues in listing."
-msgstr "列出åœç”¨çš„表å•"
-
-#: html/Admin/Users/index.html:71
-msgid "Include disabled users in search."
-msgstr "列出åœç”¨çš„使用者"
-
-#: html/Admin/CustomFields/Modify.html:113
-msgid "Include page"
-msgstr "引入页é¢"
-
-#: html/Search/Build.html:486 lib/RT/Report/Tickets.pm:441
-msgid "Incomplete Query"
-msgstr "ä¸å®Œæ•´çš„查询"
-
-#: html/Search/Build.html:483 lib/RT/Report/Tickets.pm:438
-msgid "Incomplete query"
-msgstr "ä¸å®Œæ•´çš„查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Indirect Employee"
-msgstr "直接/间接员工"
-
-#: html/Search/Elements/PickBasics:148 lib/RT/Tickets_Overlay.pm:1816
-msgid "Initial Priority"
-msgstr "åˆå§‹ä¼˜å…ˆé¡ºä½"
-
-#: lib/RT/Ticket_Overlay.pm:1163 lib/RT/Ticket_Overlay.pm:1165
-msgid "InitialPriority"
-msgstr "åˆå§‹ä¼˜å…ˆé¡ºä½"
-
-#: lib/RT/ScripAction_Overlay.pm:133
-msgid "Input error"
-msgstr "输入错误"
-
-#: html/Elements/ValidateCustomFields:68 lib/RT/CustomField_Overlay.pm:1021 lib/RT/CustomField_Overlay.pm:1162
-#. ($self->FriendlyPattern)
-#. ($CF->FriendlyPattern)
-msgid "Input must match %1"
-msgstr "è¾“å…¥å¿…é¡»ç¬¦åˆ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Interest noted"
-msgstr "登记æˆåŠŸ"
-
-#: lib/RT/Ticket_Overlay.pm:3503
-msgid "Internal Error"
-msgstr "内部错误"
-
-#: lib/RT/Record.pm:308
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "内部错误:%1"
-
-#: lib/RT/Group_Overlay.pm:668
-msgid "Invalid Group Type"
-msgstr "错误的群组类别"
-
-#: lib/RT/Principal_Overlay.pm:161
-msgid "Invalid Right"
-msgstr "错误的æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid Type"
-msgstr "错误的类型"
-
-#: lib/RT/Record.pm:952
-msgid "Invalid data"
-msgstr "错误的数æ®"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid owner. Defaulting to 'nobody'."
-msgstr "错误的承办人。改为预设承办人‘nobody’。"
-
-#: lib/RT/CustomField_Overlay.pm:207 lib/RT/CustomField_Overlay.pm:678
-#. ($msg)
-msgid "Invalid pattern: %1"
-msgstr "ä¸åˆç†çš„æ ·å¼ï¼š%1"
-
-#: lib/RT/Scrip_Overlay.pm:157 lib/RT/Template_Overlay.pm:244
-msgid "Invalid queue"
-msgstr "错误的表å•"
-
-#: lib/RT/ACE_Overlay.pm:264 lib/RT/ACE_Overlay.pm:273 lib/RT/ACE_Overlay.pm:279 lib/RT/ACE_Overlay.pm:290
-msgid "Invalid right"
-msgstr "错误的æƒé™"
-
-#: lib/RT/Record.pm:283
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "%1 的值错误"
-
-#: lib/RT/Record.pm:1610
-msgid "Invalid value for custom field"
-msgstr "错误的自订字段值"
-
-#: lib/RT/Ticket_Overlay.pm:424
-msgid "Invalid value for status"
-msgstr "错误的状æ€å€¼"
-
-#: NOT FOUND IN SOURCE
-msgid "IssueStatement"
-msgstr "é€å‡ºé™ˆè¿°"
-
-#: bin/rt-crontool:268
-msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
-msgstr "请ç»å¯¹ä¸è¦è®©æœªå…·æƒé™çš„使用者执行此工具程åºã€‚"
-
-#: bin/rt-crontool:269
-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:231
-msgid "It takes several arguments:"
-msgstr "它接å—下列å‚数:"
-
-#: html/Search/Elements/EditFormat:85
-msgid "Italic"
-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:441
-msgid "Jan."
-msgstr "01"
-
-#: NOT FOUND IN SOURCE
-msgid "January"
-msgstr "一月"
-
-#: NOT FOUND IN SOURCE
-msgid "Job"
-msgstr "èŒç§°"
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "Join or leave this group"
-msgstr "加入或离开此群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Jul"
-msgstr "七月"
-
-#: lib/RT/Date.pm:447
-msgid "Jul."
-msgstr "07"
-
-#: NOT FOUND IN SOURCE
-msgid "July"
-msgstr "七月"
-
-#: html/Ticket/Elements/Tabs:125
-msgid "Jumbo"
-msgstr "全部信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Jun"
-msgstr "六月"
-
-#: lib/RT/Date.pm:446
-msgid "Jun."
-msgstr "06"
-
-#: NOT FOUND IN SOURCE
-msgid "June"
-msgstr "六月"
-
-#: NOT FOUND IN SOURCE
-msgid "Keyword"
-msgstr "关键è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelAttachments"
-msgstr "附件å·æ ‡"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelContent"
-msgstr "内容å·æ ‡"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelSubject"
-msgstr "主题å·æ ‡"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelURL"
-msgstr "链接å·æ ‡"
-
-#: NOT FOUND IN SOURCE
-msgid "Lang"
-msgstr "使用语言"
-
-#: html/Admin/Users/Modify.html:94 html/User/Prefs.html:76
-msgid "Language"
-msgstr "语言"
-
-#: html/Search/Elements/EditFormat:79
-msgid "Large"
-msgstr "大"
-
-#: html/Ticket/Elements/Tabs:96
-msgid "Last"
-msgstr "上次更新"
-
-#: html/Ticket/Elements/EditDates:59 html/Ticket/Elements/ShowDates:60
-msgid "Last Contact"
-msgstr "上次è”络"
-
-#: html/Elements/SelectDateType:50
-msgid "Last Contacted"
-msgstr "上次è”络日期"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Notified"
-msgstr "上次通知"
-
-#: html/Elements/SelectDateType:51
-msgid "Last Updated"
-msgstr "上次更新"
-
-#: NOT FOUND IN SOURCE
-msgid "LastUpdated"
-msgstr "上次更新"
-
-#: html/Search/Elements/PickBasics:103
-msgid "LastUpdatedBy"
-msgstr "上次更新者"
-
-#: html/Ticket/Elements/ShowBasics:68
-msgid "Left"
-msgstr "剩馀时间"
-
-#: html/Admin/Users/Modify.html:109
-msgid "Let this user access RT"
-msgstr "å…许这å使用者登入"
-
-#: html/Admin/Users/Modify.html:113
-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/Search/Elements/EditFormat:68
-msgid "Link"
-msgstr "链接"
-
-#: NOT FOUND IN SOURCE
-msgid "Link a Queue"
-msgstr "申请表å•è¿žç»“"
-
-#: lib/RT/Record.pm:1306
-msgid "Link already exists"
-msgstr "此链接已存在"
-
-#: lib/RT/Record.pm:1320
-msgid "Link could not be created"
-msgstr "无法新增链接"
-
-#: lib/RT/Record.pm:1326
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "链接(%1)新增完毕"
-
-#: lib/RT/Record.pm:1387
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "链接(%1)删除完毕"
-
-#: lib/RT/Record.pm:1393
-msgid "Link not found"
-msgstr "找ä¸åˆ°é“¾æŽ¥"
-
-#: html/Ticket/ModifyLinks.html:46 html/Ticket/ModifyLinks.html:50
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr "é“¾æŽ¥ç”³è¯·å• #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Link ticket %1"
-msgstr "é“¾æŽ¥ç”³è¯·å• %1"
-
-#: html/Admin/CustomFields/Modify.html:102
-msgid "Link values to"
-msgstr "将值连结至"
-
-#: lib/RT/Ticket_Overlay.pm:700
-msgid "Linking. Permission denied"
-msgstr "连结中。æƒé™ä¸è¶³"
-
-#: html/Ticket/Create.html:216 html/Ticket/Elements/ShowSummary:89 html/Ticket/Elements/Tabs:120 html/Ticket/ModifyAll.html:78
-msgid "Links"
-msgstr "链接"
-
-#: NOT FOUND IN SOURCE
-msgid "List All Users"
-msgstr "列出所有用户数æ®"
-
-#: html/Search/Elements/EditSearches:75
-msgid "Load"
-msgstr "加载"
-
-#: html/Search/Elements/EditSearches:73
-msgid "Load saved search:"
-msgstr "加载预存查询:"
-
-#: lib/RT/System.pm:86
-msgid "LoadSavedSearch"
-msgstr "加载预存查询"
-
-#: html/Admin/Tools/Configuration.html:64
-msgid "Loaded perl modules"
-msgstr "已加载的 Perl 模å—"
-
-#: lib/RT/SavedSearch.pm:111
-#. ($self->Name)
-msgid "Loaded search %1"
-msgstr "已加载查询 %1"
-
-#: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126
-msgid "Location"
-msgstr "ä½ç½®"
-
-#: NOT FOUND IN SOURCE
-msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
-msgstr "登入目录 %1 找ä¸åˆ°æˆ–无法写入\\n。无法执行 RT。"
-
-#: NOT FOUND IN SOURCE
-msgid "LogToFile"
-msgstr "纪录等级"
-
-#: NOT FOUND IN SOURCE
-msgid "LogToFileNamed"
-msgstr "纪录档å"
-
-#: html/Elements/Header:91
-#. ("<span>".$session{'CurrentUser'}->Name."</span>")
-msgid "Logged in as %1"
-msgstr "使用者:%1"
-
-#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:100 html/Elements/Login:68 html/Elements/Login:84 lib/RT/StyleGuide.pod:797
-msgid "Login"
-msgstr "登入"
-
-#: html/Elements/Header:101
-msgid "Logout"
-msgstr "注销"
-
-#: NOT FOUND IN SOURCE
-msgid "Long-term contractor"
-msgstr "长期契约员工"
-
-#: lib/RT/CustomField_Overlay.pm:932
-msgid "Lookup type mismatch"
-msgstr "对应的类别ä¸ç¬¦"
-
-#: html/Search/Bulk.html:82
-msgid "Make Owner"
-msgstr "新增承办人"
-
-#: html/Search/Bulk.html:106
-msgid "Make Status"
-msgstr "新增现况"
-
-#: html/Search/Bulk.html:114
-msgid "Make date Due"
-msgstr "新增到期日"
-
-#: html/Search/Bulk.html:116
-msgid "Make date Resolved"
-msgstr "新增解决日期"
-
-#: html/Search/Bulk.html:110
-msgid "Make date Started"
-msgstr "新增实际起始日期"
-
-#: html/Search/Bulk.html:108
-msgid "Make date Starts"
-msgstr "新增应起始日期"
-
-#: html/Search/Bulk.html:112
-msgid "Make date Told"
-msgstr "新增报告日期"
-
-#: html/Search/Bulk.html:102
-msgid "Make priority"
-msgstr "新增优先顺ä½"
-
-#: html/Search/Bulk.html:104
-msgid "Make queue"
-msgstr "新增表å•"
-
-#: html/Search/Bulk.html:100
-msgid "Make subject"
-msgstr "新增主题"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "Make this group visible to user"
-msgstr "让此群组能被使用者看è§"
-
-#: NOT FOUND IN SOURCE
-msgid "Male"
-msgstr "ç”·"
-
-#: html/Admin/index.html:78
-msgid "Manage custom fields and custom field values"
-msgstr "管ç†è‡ªè®¢å­—段åŠå­—段值"
-
-#: html/Admin/index.html:69
-msgid "Manage groups and group membership"
-msgstr "管ç†ç¾¤ç»„åŠæ‰€å±žæˆå‘˜"
-
-#: html/Admin/index.html:85
-msgid "Manage properties and configuration which apply to all queues"
-msgstr "管ç†é€‚用于所有表å•çš„属性与设定"
-
-#: html/Admin/index.html:74
-msgid "Manage queues and queue-specific properties"
-msgstr "管ç†å„表å•åŠç›¸å…³å±žæ€§"
-
-#: html/Admin/index.html:64
-msgid "Manage users and passwords"
-msgstr "管ç†ä½¿ç”¨è€…与å£ä»¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Manager"
-msgstr "ç»ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Mar"
-msgstr "三月"
-
-#: lib/RT/Date.pm:443
-msgid "Mar."
-msgstr "03"
-
-#: NOT FOUND IN SOURCE
-msgid "March"
-msgstr "三月"
-
-#: NOT FOUND IN SOURCE
-msgid "Marketing Department"
-msgstr "行销部"
-
-#: NOT FOUND IN SOURCE
-msgid "Match Pattern"
-msgstr "符åˆæ ·å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "May"
-msgstr "五月"
-
-#: lib/RT/Date.pm:445
-msgid "May."
-msgstr "05"
-
-#: lib/RT/Transaction_Overlay.pm:731
-#. ($value)
-msgid "Member %1 added"
-msgstr "æˆå‘˜ %1 新增完毕"
-
-#: lib/RT/Transaction_Overlay.pm:771
-#. ($value)
-msgid "Member %1 deleted"
-msgstr "æˆå‘˜ %1 删除完毕"
-
-#: lib/RT/Group_Overlay.pm:1000
-msgid "Member added"
-msgstr "新增æˆå‘˜å®Œæ¯•"
-
-#: lib/RT/Group_Overlay.pm:1162
-msgid "Member deleted"
-msgstr "æˆå‘˜å·²åˆ é™¤"
-
-#: lib/RT/Group_Overlay.pm:1166
-msgid "Member not deleted"
-msgstr "æˆå‘˜æœªåˆ é™¤"
-
-#: html/Elements/SelectLinkType:47
-msgid "Member of"
-msgstr "隶属于"
-
-#: NOT FOUND IN SOURCE
-msgid "Member since"
-msgstr "注册日期"
-
-#: NOT FOUND IN SOURCE
-msgid "MemberOf"
-msgstr "隶属于"
-
-#: html/Admin/Elements/GroupTabs:63 html/User/Elements/GroupTabs:63
-msgid "Members"
-msgstr "æˆå‘˜"
-
-#: lib/RT/Transaction_Overlay.pm:728
-#. ($value)
-msgid "Membership in %1 added"
-msgstr "所属群组 %1 加入完毕"
-
-#: lib/RT/Transaction_Overlay.pm:768
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr "所属群组 %1 移除完毕"
-
-#: html/Admin/Elements/UserTabs:61
-msgid "Memberships"
-msgstr "所属群组"
-
-#: html/Admin/Users/Memberships.html:60
-#. ($UserObj->Name)
-msgid "Memberships of the user %1"
-msgstr "使用者 %1 的所属群组"
-
-#: lib/RT/Ticket_Overlay.pm:2893
-msgid "Merge Successful"
-msgstr "æ•´åˆå®Œæ¯•"
-
-#: lib/RT/Ticket_Overlay.pm:2780
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr "æ•´åˆå¤±è´¥ã€‚无法设定 EffectiveId"
-
-#: lib/RT/Ticket_Overlay.pm:2788
-msgid "Merge failed. Couldn't set Status"
-msgstr "æ•´åˆå¤±è´¥ã€‚无法设定 Status"
-
-#: html/Elements/EditLinks:131 html/Ticket/Elements/BulkLinks:48
-msgid "Merge into"
-msgstr "æ•´åˆè¿›"
-
-#: lib/RT/Transaction_Overlay.pm:734
-#. ($value)
-msgid "Merged into %1"
-msgstr "已整åˆè¿› %1"
-
-#: html/Search/Bulk.html:143 html/Ticket/Update.html:118
-msgid "Message"
-msgstr "讯æ¯"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:164
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr "信件内文ä¸æ˜¯çº¯æ–‡å­—,因此无法显示。"
-
-#: lib/RT/Ticket_Overlay.pm:2451
-msgid "Message could not be recorded"
-msgstr "无法纪录讯æ¯"
-
-#: lib/RT/Ticket_Overlay.pm:2454
-msgid "Message recorded"
-msgstr "讯æ¯çºªå½•æˆåŠŸ"
-
-#: html/Ticket/Elements/PreviewScrips:122
-msgid "Messages about this ticket will not be sent to..."
-msgstr "此申请å•çš„相关讯æ¯ä¸ä¼šå¯„é€ç»™..."
-
-#: html/Elements/SelectTimeUnits:47
-msgid "Minutes"
-msgstr "分钟"
-
-#: NOT FOUND IN SOURCE
-msgid "Misc. Expense"
-msgstr "æ‚è´¹"
-
-#: html/Search/Build.html:490 lib/RT/Report/Tickets.pm:445
-msgid "Mismatched parentheses"
-msgstr "未对é½çš„括å·"
-
-#: lib/RT/Record.pm:954
-msgid "Missing a primary key?: %1"
-msgstr "缺少主键值?(%1)"
-
-#: NOT FOUND IN SOURCE
-msgid "Missing mandatory fields"
-msgstr "缺少必填字段"
-
-#: html/Admin/Users/Modify.html:193 html/User/Prefs.html:92
-msgid "Mobile"
-msgstr "行动电è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "MobilePhone"
-msgstr "行动电è¯"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "Modify Access Control List"
-msgstr "更改æƒé™æŽ§åˆ¶æ¸…å•"
-
-#: html/Admin/Elements/ObjectCustomFields:96
-#. (loc(lc($FriendlySubTypes)), loc(lc($Types)))
-msgid "Modify Custom Fields which apply to %1 for all %2"
-msgstr "更改适用于 %1 内所有 %2 的自订字段"
-
-#: html/Admin/Elements/ObjectCustomFields:98
-#. (loc(lc($Types)))
-msgid "Modify Custom Fields which apply to all %1"
-msgstr "更改适用于所有%1的自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Custom Fields which apply to all queues"
-msgstr "更改适用于所有表å•çš„自订字段"
-
-#: html/Admin/Global/GroupRights.html:106 html/Admin/Groups/GroupRights.html:94 html/Admin/Queues/GroupRights.html:107
-msgid "Modify Group Rights"
-msgstr "更改群组æƒé™"
-
-#: html/Admin/Groups/Members.html:105 html/User/Groups/Members.html:101
-msgid "Modify Members"
-msgstr "更改æˆå‘˜"
-
-#: html/User/Delegation.html:58
-msgid "Modify Rights"
-msgstr "更改æƒé™"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "Modify Scrip templates for this queue"
-msgstr "更改此表å•çš„模æ¿"
-
-#: lib/RT/Queue_Overlay.pm:100
-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"
-
-#: html/Admin/Global/UserRights.html:75 html/Admin/Groups/UserRights.html:76 html/Admin/Queues/UserRights.html:75
-msgid "Modify User Rights"
-msgstr "更改使用者æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Workflow"
-msgstr "更改æµç¨‹"
-
-#: html/Admin/Queues/CustomField.html:66
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr "更改 %1 表å•å†…的自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify a CustomField that applies to all queues"
-msgstr "更改适用于所有表å•çš„自订字段"
-
-#: html/Admin/Queues/Scrip.html:82
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "更改 %1 表å•å†…的手续"
-
-#: html/Admin/Global/Scrip.html:75
-msgid "Modify a scrip that applies to all queues"
-msgstr "更改适用于所有表å•çš„手续"
-
-#: html/Admin/CustomFields/Objects.html:90
-#. ($CF->Name)
-msgid "Modify associated objects for %1"
-msgstr "更改适用 %1 的对象"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify dates for # %1"
-msgstr "更改 # %1 的日期"
-
-#: html/Ticket/ModifyDates.html:46 html/Ticket/ModifyDates.html:50
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "更改 #%1 的日期"
-
-#: html/Ticket/ModifyDates.html:57
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "æ›´æ”¹ç”³è¯·å• # %1 的日期"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:65 html/Admin/Global/index.html:72
-msgid "Modify global custom fields"
-msgstr "更改全域自订字段"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:70 html/Admin/Global/GroupRights.html:46 html/Admin/Global/GroupRights.html:49 html/Admin/Global/index.html:77
-msgid "Modify global group rights"
-msgstr "更改全域设定的群组æƒé™"
-
-#: html/Admin/Global/GroupRights.html:54
-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:46 html/Admin/Global/UserRights.html:49 html/Admin/Global/index.html:81
-msgid "Modify global user rights"
-msgstr "更改全域设定的使用者æƒé™"
-
-#: html/Admin/Global/UserRights.html:54
-msgid "Modify global user rights."
-msgstr "更改全域设定的使用者æƒé™ã€‚"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "Modify group metadata or delete group"
-msgstr "更改群组数æ®åŠåˆ é™¤ç¾¤ç»„"
-
-#: html/Admin/CustomFields/GroupRights.html:164
-#. ($CustomFieldObj->Name)
-msgid "Modify group rights for custom field %1"
-msgstr "更改自订字段 %1 的群组æƒé™"
-
-#: html/Admin/Groups/GroupRights.html:46 html/Admin/Groups/GroupRights.html:50 html/Admin/Groups/GroupRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify group rights for group %1"
-msgstr "更改群组 %1 的群组æƒé™"
-
-#: html/Admin/Queues/GroupRights.html:46 html/Admin/Queues/GroupRights.html:50
-#. ($QueueObj->Name)
-msgid "Modify group rights for queue %1"
-msgstr "æ›´æ”¹è¡¨å• %1 的群组æƒé™"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "Modify membership roster for this group"
-msgstr "更改此群组的æˆå‘˜åå•"
-
-#: lib/RT/System.pm:82
-msgid "Modify one's own RT account"
-msgstr "更改个人的å¸å·ä¿¡æ¯"
-
-#: html/Admin/Queues/People.html:46 html/Admin/Queues/People.html:50
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "æ›´æ”¹é“¾æŽ¥åˆ°è¡¨å• %1 的人员"
-
-#: html/Ticket/ModifyPeople.html:46 html/Ticket/ModifyPeople.html:50 html/Ticket/ModifyPeople.html:57
-#. ($Ticket->id)
-#. ($Ticket->Id)
-msgid "Modify people related to ticket #%1"
-msgstr "æ›´æ”¹ç”³è¯·å• #%1 链接到的人员"
-
-#: html/Admin/Queues/Scrips.html:67
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "æ›´æ”¹è¡¨å• %1 的手续"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:56 html/Admin/Global/Scrips.html:65 html/Admin/Global/index.html:63
-msgid "Modify scrips which apply to all queues"
-msgstr "更改适用于所有表å•çš„手续"
-
-#: html/Admin/Global/Template.html:102 html/Admin/Global/Template.html:46 html/Admin/Global/Template.html:51 html/Admin/Queues/Template.html:99
-#. (loc($TemplateObj->Name()))
-#. ($TemplateObj->id)
-msgid "Modify template %1"
-msgstr "æ›´æ”¹æ¨¡æ¿ %1"
-
-#: html/Admin/Global/Templates.html:65
-msgid "Modify templates which apply to all queues"
-msgstr "更改适用于所有表å•çš„模æ¿"
-
-#: html/Admin/Global/index.html:85
-msgid "Modify the default \"RT at a glance\" view"
-msgstr "更改预设的‘RT 一览’检视"
-
-#: html/Admin/Groups/Modify.html:119 html/User/Groups/Modify.html:107
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "更改群组 %1"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "Modify the queue watchers"
-msgstr "更改表å•è§†å¯Ÿå‘˜"
-
-#: html/Admin/Users/Modify.html:309
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "更改使用者 %1"
-
-#: html/Ticket/ModifyAll.html:58
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "æ›´æ”¹ç”³è¯·å• # %1"
-
-#: html/Ticket/Modify.html:46 html/Ticket/Modify.html:49 html/Ticket/Modify.html:55
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "æ›´æ”¹ç”³è¯·å• # %1"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "Modify tickets"
-msgstr "更改申请å•"
-
-#: html/Admin/CustomFields/UserRights.html:157
-#. ($CustomFieldObj->Name)
-msgid "Modify user rights for custom field %1"
-msgstr "更改自订字段 %1 的使用者æƒé™"
-
-#: html/Admin/Groups/UserRights.html:46 html/Admin/Groups/UserRights.html:50 html/Admin/Groups/UserRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify user rights for group %1"
-msgstr "更改群组 %1 的使用者æƒé™"
-
-#: html/Admin/Queues/UserRights.html:46 html/Admin/Queues/UserRights.html:50
-#. ($QueueObj->Name)
-msgid "Modify user rights for queue %1"
-msgstr "æ›´æ”¹è¡¨å• %1 的使用者æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify watchers for queue '%1'"
-msgstr "更改 '%1' 的视察员"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify workflow %1"
-msgstr "更改æµç¨‹ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify workflows which apply to all queues"
-msgstr "更改适用于所有表å•çš„æµç¨‹"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "ModifyACL"
-msgstr "更改æƒé™æ¸…å•"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "ModifyCustomField"
-msgstr "更改自订字段"
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "ModifyOwnMembership"
-msgstr "更改自己是å¦å±žäºŽæŸç¾¤ç»„"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "ModifyQueueWatchers"
-msgstr "更改表å•è§†å¯Ÿå‘˜"
-
-#: lib/RT/Queue_Overlay.pm:100
-msgid "ModifyScrips"
-msgstr "更改手续"
-
-#: lib/RT/System.pm:82
-msgid "ModifySelf"
-msgstr "更改个人å¸å·"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "ModifyTemplate"
-msgstr "更改模æ¿"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "ModifyTicket"
-msgstr "更改申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Mon"
-msgstr "星期一"
-
-#: lib/RT/Date.pm:417
-msgid "Mon."
-msgstr "星期一"
-
-#: NOT FOUND IN SOURCE
-msgid "More"
-msgstr "更多"
-
-#: html/Ticket/Elements/ShowRequestor:61
-#. ($name)
-msgid "More about %1"
-msgstr "关于 %1 的进一步信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Morning Shift"
-msgstr "æ—©ç­"
-
-#: NOT FOUND IN SOURCE
-msgid "Move"
-msgstr "移动"
-
-#: NOT FOUND IN SOURCE
-msgid "Move All"
-msgstr "全移"
-
-#: html/Admin/Elements/PickCustomFields:83
-msgid "Move down"
-msgstr "下移"
-
-#: html/Admin/Elements/PickCustomFields:75
-msgid "Move up"
-msgstr "上移"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:48
-msgid "Multiple"
-msgstr "多é‡"
-
-#: lib/RT/User_Overlay.pm:226
-msgid "Must specify 'Name' attribute"
-msgstr "必须指定 'Name' 的属性"
-
-#: html/SelfService/Elements/MyRequests:57
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "我的 %1 申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "My Approvals"
-msgstr "表å•ç­¾æ ¸"
-
-#: html/Tools/Elements/Tabs:63
-msgid "My Day"
-msgstr "今日事"
-
-#: NOT FOUND IN SOURCE
-msgid "My Requests"
-msgstr "表å•ç”³è¯·è¿½è¸ª"
-
-#: NOT FOUND IN SOURCE
-msgid "My Tickets"
-msgstr "表å•å¤„ç†"
-
-#: html/Approvals/index.html:46 html/Approvals/index.html:47
-msgid "My approvals"
-msgstr "表å•ç­¾æ ¸"
-
-#: html/Search/Elements/SearchPrivacy:50 html/Search/Elements/SelectSearchObject:53 html/Search/Elements/SelectSearchesForObjects:54
-msgid "My saved searches"
-msgstr "我的预存查询"
-
-#: html/Admin/CustomFields/Modify.html:58 html/Admin/Elements/AddCustomFieldValue:53 html/Admin/Elements/EditCustomField:55 html/Admin/Elements/EditCustomFieldValues:55 html/Admin/Elements/ModifyTemplate:49 html/Admin/Groups/Modify.html:65 html/Search/Bulk.html:157 html/User/Groups/Modify.html:65
-msgid "Name"
-msgstr "å称"
-
-#: lib/RT/User_Overlay.pm:233
-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:73
-msgid "Never"
-msgstr "从未更动"
-
-#: NOT FOUND IN SOURCE
-msgid "New"
-msgstr "新建立"
-
-#: html/Elements/EditLinks:117
-msgid "New Links"
-msgstr "新增关系"
-
-#: html/Admin/Users/Modify.html:119 html/User/Prefs.html:109
-msgid "New Password"
-msgstr "æ–°çš„å£ä»¤"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "新的待签核事项"
-
-#: NOT FOUND IN SOURCE
-msgid "New Query"
-msgstr "新增查询"
-
-#: NOT FOUND IN SOURCE
-msgid "New Request"
-msgstr "表å•ç”³è¯·"
-
-#: html/Ticket/Elements/Tabs:212
-msgid "New Search"
-msgstr "新增查询"
-
-#: NOT FOUND IN SOURCE
-msgid "New Watchers"
-msgstr "新增视察员"
-
-#: html/Admin/Elements/CustomFieldTabs:93 html/Admin/Queues/CustomField.html:73
-msgid "New custom field"
-msgstr "新增自订字段"
-
-#: html/Admin/Elements/GroupTabs:77 html/User/Elements/GroupTabs:73
-msgid "New group"
-msgstr "新增群组"
-
-#: html/SelfService/Prefs.html:53
-msgid "New password"
-msgstr "æ–°çš„å£ä»¤"
-
-#: lib/RT/User_Overlay.pm:816
-msgid "New password notification sent"
-msgstr "é€å‡ºæ–°å£ä»¤é€šçŸ¥"
-
-#: html/Admin/Elements/QueueTabs:95
-msgid "New queue"
-msgstr "新增表å•"
-
-#: html/Ticket/Elements/Reminders:118
-msgid "New reminder:"
-msgstr "新增æ醒项目:"
-
-#: NOT FOUND IN SOURCE
-msgid "New request"
-msgstr "æ出申请å•"
-
-#: html/Admin/Elements/SelectRights:65
-msgid "New rights"
-msgstr "新增æƒé™"
-
-#: html/Admin/Global/Scrip.html:63 html/Admin/Global/Scrips.html:60 html/Admin/Queues/Scrip.html:71 html/Admin/Queues/Scrips.html:76
-msgid "New scrip"
-msgstr "新增手续"
-
-#: NOT FOUND IN SOURCE
-msgid "New search"
-msgstr "é‡æ–°æŸ¥è¯¢"
-
-#: html/Admin/Global/Template.html:81 html/Admin/Global/Templates.html:60 html/Admin/Queues/Template.html:79 html/Admin/Queues/Templates.html:71
-msgid "New template"
-msgstr "新增模æ¿"
-
-#: html/SelfService/Elements/Tabs:84 html/SelfService/Elements/Tabs:88
-msgid "New ticket"
-msgstr "æ出申请å•"
-
-#: lib/RT/Ticket_Overlay.pm:2757
-msgid "New ticket doesn't exist"
-msgstr "没有新申请å•"
-
-#: html/Admin/Elements/UserTabs:81
-msgid "New user"
-msgstr "新增使用者"
-
-#: html/Admin/Elements/CreateUserCalled:47
-msgid "New user called"
-msgstr "新使用者åå­—"
-
-#: html/Admin/Queues/People.html:76 html/Ticket/Elements/EditPeople:50
-msgid "New watchers"
-msgstr "新视察员"
-
-#: NOT FOUND IN SOURCE
-msgid "New window setting"
-msgstr "更新窗å£è®¾å®š"
-
-#: NOT FOUND IN SOURCE
-msgid "New workflow"
-msgstr "新增æµç¨‹"
-
-#: html/Helpers/CalPopup.html:58 html/Ticket/Elements/Tabs:92
-msgid "Next"
-msgstr "下一项"
-
-#: html/Elements/TicketList:104
-msgid "Next Page"
-msgstr "下一页"
-
-#: NOT FOUND IN SOURCE
-msgid "Next page"
-msgstr "下一页"
-
-#: NOT FOUND IN SOURCE
-msgid "NickName"
-msgstr "昵称"
-
-#: html/Admin/Users/Modify.html:84 html/User/Prefs.html:72
-msgid "Nickname"
-msgstr "昵称"
-
-#: NOT FOUND IN SOURCE
-msgid "Night Shift"
-msgstr "å°å¤œç­"
-
-#: NOT FOUND IN SOURCE
-msgid "No"
-msgstr "å¦"
-
-#: html/Admin/CustomFields/UserRights.html:145
-msgid "No Class defined"
-msgstr "尚未定义类别"
-
-#: html/Admin/CustomFields/Modify.html:166 html/Admin/Elements/EditCustomField:119
-msgid "No CustomField"
-msgstr "无自订字段"
-
-#: html/Admin/CustomFields/GroupRights.html:103
-msgid "No CustomField defined"
-msgstr "尚未定义自订字段"
-
-#: html/Admin/Groups/GroupRights.html:105 html/Admin/Groups/UserRights.html:92
-msgid "No Group defined"
-msgstr "尚未定义群组"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:482
-msgid "No Query"
-msgstr "没有查询"
-
-#: html/Admin/Queues/GroupRights.html:118 html/Admin/Queues/UserRights.html:89
-msgid "No Queue defined"
-msgstr "尚未定义表å•"
-
-#: bin/rt-crontool:73
-msgid "No RT user found. Please consult your RT administrator.\\n"
-msgstr "找ä¸åˆ° RT ä½¿ç”¨è€…ã€‚è¯·å‘ RT 管ç†å‘˜æŸ¥è¯¢ã€‚\\n"
-
-#: html/Admin/Global/Template.html:100 html/Admin/Queues/Template.html:97
-msgid "No Template"
-msgstr "没有模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "No Ticket specified. Aborting ticket "
-msgstr "未指定申请å•ã€‚é€€å‡ºç”³è¯·å• "
-
-#: NOT FOUND IN SOURCE
-msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
-msgstr "未指定申请å•ã€‚退出申请å•æ›´æ”¹\\n\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "No Workflow"
-msgstr "没有æµç¨‹"
-
-#: html/Approvals/Elements/Approve:77
-msgid "No action"
-msgstr "æš‚ä¸å¤„ç†"
-
-#: lib/RT/Record.pm:949
-msgid "No column specified"
-msgstr "未指定字段"
-
-#: NOT FOUND IN SOURCE
-msgid "No command found\\n"
-msgstr "找ä¸åˆ°å‘½ä»¤"
-
-#: html/Ticket/Elements/ShowRequestor:68
-msgid "No comment entered about this user"
-msgstr "没有对这å使用者的评论"
-
-#: NOT FOUND IN SOURCE
-msgid "No correspondence attached"
-msgstr "没有附上申请å•å›žå¤"
-
-#: lib/RT/Action/Generic.pm:185 lib/RT/Condition/Generic.pm:197 lib/RT/Search/ActiveTicketsInQueue.pm:77 lib/RT/Search/Generic.pm:134 lib/RT/Search/Googleish.pm:78
-#. (ref $self)
-msgid "No description for %1"
-msgstr "没有对 %1 çš„æè¿°"
-
-#: lib/RT/Users_Overlay.pm:190
-msgid "No group specified"
-msgstr "未指定群组"
-
-#: html/Admin/Groups/index.html:52
-msgid "No groups matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è¯¢æ¡ä»¶çš„群组。"
-
-#: lib/RT/Ticket_Overlay.pm:2393
-msgid "No message attached"
-msgstr "没有附上讯æ¯"
-
-#: lib/RT/User_Overlay.pm:1034
-msgid "No password set"
-msgstr "没有设定å£ä»¤"
-
-#: lib/RT/Queue_Overlay.pm:361
-msgid "No permission to create queues"
-msgstr "没有新增表å•çš„æƒé™"
-
-#: lib/RT/Ticket_Overlay.pm:420
-#. ($QueueObj->Name)
-msgid "No permission to create tickets in the queue '%1'"
-msgstr "æ²¡æœ‰åœ¨è¡¨å• '%1' 新增申请å•çš„æƒé™"
-
-#: lib/RT/User_Overlay.pm:186
-msgid "No permission to create users"
-msgstr "没有新增使用者的æƒé™"
-
-#: html/SelfService/Display.html:167
-msgid "No permission to display that ticket"
-msgstr "没有显示该申请å•çš„æƒé™"
-
-#: lib/RT/SavedSearch.pm:156
-msgid "No permission to save system-wide searches"
-msgstr "没有储存全域预存查询的æƒé™"
-
-#: html/SelfService/Update.html:117
-msgid "No permission to view update ticket"
-msgstr "没有检视申请å•æ›´æ–°çš„æƒé™"
-
-#: lib/RT/Queue_Overlay.pm:795 lib/RT/Ticket_Overlay.pm:1489
-msgid "No principal specified"
-msgstr "未指定å•ä½"
-
-#: html/Admin/Queues/People.html:175 html/Admin/Queues/People.html:185
-msgid "No principals selected."
-msgstr "未指定å•ä½ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "No protocol specified in %1"
-msgstr "%1 内未指定åè®®"
-
-#: html/Admin/Queues/index.html:57
-msgid "No queues matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è¯¢æ¡ä»¶çš„表å•ã€‚"
-
-#: html/Admin/Elements/SelectRights:106
-msgid "No rights found"
-msgstr "找ä¸åˆ°æƒé™"
-
-#: html/Admin/Elements/SelectRights:53
-msgid "No rights granted."
-msgstr "没有选定æƒé™"
-
-#: lib/RT/SavedSearch.pm:196
-msgid "No search loaded"
-msgstr "尚未加载查询"
-
-#: html/Search/Bulk.html:232
-msgid "No search to operate on."
-msgstr "没有è¦è¿›è¡Œçš„查询"
-
-#: html/Elements/RT__Ticket/ColumnMap:137 html/Search/Results.rdf:78
-msgid "No subject"
-msgstr "没有标题"
-
-#: NOT FOUND IN SOURCE
-msgid "No ticket id specified"
-msgstr "未指定申请å•ç¼–å·"
-
-#: lib/RT/Transaction_Overlay.pm:528 lib/RT/Transaction_Overlay.pm:565
-msgid "No transaction type specified"
-msgstr "未指定更动报告类别"
-
-#: NOT FOUND IN SOURCE
-msgid "No user or email address specified"
-msgstr "未指定使用者或电å­é‚®ä»¶åœ°å€"
-
-#: html/Admin/Users/index.html:55
-msgid "No users matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è¯¢æ¡ä»¶çš„使用者。"
-
-#: NOT FOUND IN SOURCE
-msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
-msgstr "找ä¸åˆ°åˆæ ¼çš„ RT 使用者。RT cvs 处ç†å™¨å·²åœç”¨ã€‚è¯·å‘ RT 管ç†è€…询问。\\n"
-
-#: lib/RT/Record.pm:946
-msgid "No value sent to _Set!\\n"
-msgstr "_Set 没有收到任何值!\\n"
-
-#: html/Elements/QuickCreate:59
-msgid "Nobody"
-msgstr "没有人"
-
-#: lib/RT/Record.pm:951
-msgid "Nonexistant field?"
-msgstr "字段ä¸å­˜åœ¨ï¼Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Normal Users"
-msgstr "一般用户群组"
-
-#: html/Search/Chart:71 html/Search/Elements/Chart:88
-msgid "Not Set"
-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:96
-msgid "Not logged in."
-msgstr "尚未登入"
-
-#: lib/RT/Date.pm:397
-msgid "Not set"
-msgstr "尚未设定"
-
-#: html/NoAuth/Reminder.html:48
-msgid "Not yet implemented."
-msgstr "尚未完工。"
-
-#: NOT FOUND IN SOURCE
-msgid "Not yet implemented...."
-msgstr "尚未完工..."
-
-#: html/Approvals/Elements/Approve:81
-msgid "Notes"
-msgstr "备注"
-
-#: NOT FOUND IN SOURCE
-msgid "Notes:"
-msgstr "备注:"
-
-#: lib/RT/User_Overlay.pm:819
-msgid "Notification could not be sent"
-msgstr "无法é€å‡ºé€šçŸ¥"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr "通知管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "以评论方å¼é€šçŸ¥ç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:93 etc/upgrade/3.1.17/content:6
-msgid "Notify Ccs"
-msgstr "通知副本收件人"
-
-#: etc/initialdata:89 etc/upgrade/3.1.17/content:2
-msgid "Notify Ccs as Comment"
-msgstr "以评论方å¼é€šçŸ¥å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr "通知其它收件人"
-
-#: etc/initialdata:124
-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:376
-msgid "Notify Owner of their rejected ticket"
-msgstr "通知承办人申请å•å·²é©³å›ž"
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr "通知承办人申请å•å·²å®Œæˆå…¨éƒ¨ç­¾æ ¸"
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr "通知承办人申请å•å·²å®ŒæˆæŸé¡¹ç­¾æ ¸"
-
-#: etc/initialdata:334
-msgid "Notify Owners and AdminCcs of new items pending their approval"
-msgstr "æ•´ç†å¾…签核事项,通知承办人åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:77
-msgid "Notify Requestors"
-msgstr "通知申请人"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "通知申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "以评论方å¼é€šçŸ¥ç”³è¯·äººåŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "通知申请人ã€å‰¯æœ¬åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "以评论方å¼é€šçŸ¥ç”³è¯·äººã€å‰¯æœ¬åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Notify people:"
-msgstr "通知对象"
-
-#: NOT FOUND IN SOURCE
-msgid "Nov"
-msgstr "å一月"
-
-#: lib/RT/Date.pm:451
-msgid "Nov."
-msgstr "11"
-
-#: NOT FOUND IN SOURCE
-msgid "November"
-msgstr "å一月"
-
-#: NOT FOUND IN SOURCE
-msgid "OIN104"
-msgstr "104eHRMS 接å£"
-
-#: NOT FOUND IN SOURCE
-msgid "OK"
-msgstr "确定"
-
-#: html/Search/Elements/SelectAndOr:47
-msgid "OR"
-msgstr "OR"
-
-#: lib/RT/Record.pm:322
-msgid "Object could not be created"
-msgstr "无法新增对象"
-
-#: lib/RT/Record.pm:123
-msgid "Object could not be deleted"
-msgstr "无法删除对象"
-
-#: lib/RT/Record.pm:341
-msgid "Object created"
-msgstr "对象新增完毕"
-
-#: lib/RT/Record.pm:120
-msgid "Object deleted"
-msgstr "对象删除完毕"
-
-#: html/Admin/CustomFields/Objects.html:72 html/Admin/Elements/ObjectCustomFields:63
-#. ($ObjectType)
-#. ($LookupType)
-msgid "Object of type %1 cannot take custom fields"
-msgstr "自订字段ä¸é€‚用于类别为 %1 的对象"
-
-#: lib/RT/CustomField_Overlay.pm:967
-msgid "Object type mismatch"
-msgstr "对象类别ä¸ç¬¦"
-
-#: NOT FOUND IN SOURCE
-msgid "Occupation Status"
-msgstr "在èŒçŠ¶æ€"
-
-#: NOT FOUND IN SOURCE
-msgid "Oct"
-msgstr "å月"
-
-#: lib/RT/Date.pm:450
-msgid "Oct."
-msgstr "10"
-
-#: NOT FOUND IN SOURCE
-msgid "October"
-msgstr "å月"
-
-#: NOT FOUND IN SOURCE
-msgid "Office Phone"
-msgstr "办公室电è¯"
-
-#: html/Tools/Elements/Tabs:55
-msgid "Offline"
-msgstr "离线"
-
-#: html/Tools/Offline.html:49
-msgid "Offline edits"
-msgstr "离线编辑"
-
-#: html/Tools/Offline.html:46
-msgid "Offline upload"
-msgstr "离线上载"
-
-#: html/Elements/SelectDateRelation:56
-msgid "On"
-msgstr "等于"
-
-#: lib/RT/Transaction_Overlay.pm:326
-#. ($self->CreatedAsString(), $self->CreatorObj->Name())
-msgid "On %1, %2 wrote:"
-msgstr "在 %1 时,%2 写到:"
-
-#: NOT FOUND IN SOURCE
-msgid "On Change"
-msgstr "更改申请å•æ—¶"
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "评论时"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "回å¤ç”³è¯·å•æ—¶"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "新增申请å•æ—¶"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "承办人改å˜æ—¶"
-
-#: etc/initialdata:177 etc/upgrade/3.1.17/content:15
-msgid "On Priority Change"
-msgstr "优先顺ä½æ”¹å˜æ—¶"
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "表å•æ”¹å˜æ—¶"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "解决申请å•æ—¶"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "现况改å˜æ—¶"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "å‘生更动时"
-
-#: html/Approvals/Elements/PendingMyApproval:70
-#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='CreatedAfter' />")
-msgid "Only show approvals for requests created after %1"
-msgstr "仅显示 %1 之åŽæ–°å¢žçš„申请å•"
-
-#: html/Approvals/Elements/PendingMyApproval:68
-#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore' id='CreatedBefore' />")
-msgid "Only show approvals for requests created before %1"
-msgstr "仅显示 %1 之å‰æ–°å¢žçš„申请å•"
-
-#: html/Admin/CustomFields/index.html:75
-msgid "Only show custom fields for:"
-msgstr "仅显示适用于下列项目的自订字段:"
-
-#: NOT FOUND IN SOURCE
-msgid "Open"
-msgstr "å¼€å¯"
-
-#: html/SelfService/index.html:46
-msgid "Open Tickets"
-msgstr "å¼€å¯ç”³è¯·å•"
-
-#: html/Ticket/Elements/Tabs:160
-msgid "Open it"
-msgstr "å¼€å¯"
-
-#: html/SelfService/Elements/Tabs:75
-msgid "Open tickets"
-msgstr "å¼€å¯çš„申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in a new window"
-msgstr "在新窗å£å¼€å¯(列表的)申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in another window"
-msgstr "在å¦ä¸€ä¸ªçª—å£å¼€å¯(列表的)申请å•"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "收到回å¤æ—¶å³å¼€å¯ç”³è¯·å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Opened Tickets"
-msgstr "已申请è¿è¡Œä¸­è¡¨å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Opinion"
-msgstr "æ„è§"
-
-#: NOT FOUND IN SOURCE
-msgid "Option Description"
-msgstr "选项æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Option Name"
-msgstr "选项å称"
-
-#: html/Prefs/MyRT.html:70
-msgid "Options"
-msgstr "选项"
-
-#: html/Search/Elements/DisplayOptions:59
-msgid "Order by"
-msgstr "排åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Ordering and sorting"
-msgstr "顺åºä¸ŽæŽ’åºæ–¹å¼"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:129
-msgid "Organization"
-msgstr "组织å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Organization:"
-msgstr "组织:"
-
-#: html/Approvals/Elements/Approve:53
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr "原申请å•ï¼š#%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Other comma-delimited email addresses"
-msgstr "其它e-mailå¸å· (ä»…e-mail通知;多笔å¸å·è¯·ç”¨é€—å·','区隔)"
-
-#: NOT FOUND IN SOURCE
-msgid "Out of range"
-msgstr "期é™å¤–"
-
-#: lib/RT/Transaction_Overlay.pm:622
-msgid "Outgoing email about a comment recorded"
-msgstr "已纪录å‘é€çš„评论邮件"
-
-#: lib/RT/Transaction_Overlay.pm:626
-msgid "Outgoing email recorded"
-msgstr "已纪录å‘é€çš„邮件"
-
-#: html/Admin/Queues/Modify.html:90
-msgid "Over time, priority moves toward"
-msgstr "优先顺ä½éšæ—¶é—´å¢žåŠ è°ƒæ•´ä¸º"
-
-#: NOT FOUND IN SOURCE
-msgid "Override current custom fields with fields from %1"
-msgstr "以 %1 表å•çš„自订字段å–代现有字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Override global rights"
-msgstr "å–代全域æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "OverrideGlobalACL status %1"
-msgstr "å–代全域æƒé™ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Overview"
-msgstr "总览"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "Own tickets"
-msgstr "承办申请å•"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "OwnTicket"
-msgstr "承办申请å•"
-
-#: etc/initialdata:38 html/Elements/QuickCreate:56 html/Search/Elements/PickBasics:101 html/Ticket/Create.html:72 html/Ticket/Elements/EditBasics:61 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/Reminders:129 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:110 lib/RT/Tickets_Overlay.pm:2006
-msgid "Owner"
-msgstr "承办人"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner changed from %1 to %2"
-msgstr "承办人已从 %1 改为 %2"
-
-#: lib/RT/Ticket_Overlay.pm:505
-msgid "Owner could not be set."
-msgstr "无法设定承办人。"
-
-#: lib/RT/Transaction_Overlay.pm:672
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "强制将承办人从 %1 改为 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner is"
-msgstr "承办人"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner's Phone"
-msgstr "承办人电è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Page #"
-msgstr " "
-
-#: html/Elements/TicketList:78
-#. ($Page, int($TotalFound/$Rows)+$oddRows)
-msgid "Page %1 of %2"
-msgstr "第 %1/%2 页"
-
-#: html/Admin/Users/Modify.html:198 html/User/Prefs.html:96
-msgid "Pager"
-msgstr "呼å«å™¨"
-
-#: NOT FOUND IN SOURCE
-msgid "PagerPhone"
-msgstr "呼å«å™¨å·ç "
-
-#: NOT FOUND IN SOURCE
-msgid "Parameter"
-msgstr "呼å«å‚æ•°"
-
-#: NOT FOUND IN SOURCE
-msgid "Parent"
-msgstr "上级"
-
-#: html/Elements/EditLinks:144 html/Elements/EditLinks:76 html/Elements/ShowLinks:68 html/Ticket/Create.html:222 html/Ticket/Elements/BulkLinks:60
-msgid "Parents"
-msgstr "æ¯ç”³è¯·å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Park Space"
-msgstr "åœè½¦ä½ç”³è¯·"
-
-#: html/Elements/Login:95 html/User/Prefs.html:105
-msgid "Password"
-msgstr "å£ä»¤"
-
-#: html/NoAuth/Reminder.html:46
-msgid "Password Reminder"
-msgstr "å£ä»¤æ示"
-
-#: lib/RT/Transaction_Overlay.pm:781 lib/RT/User_Overlay.pm:1045
-msgid "Password changed"
-msgstr "å£ä»¤æ›´æ”¹å®Œæ¯•"
-
-#: lib/RT/User_Overlay.pm:1037 lib/RT/User_Overlay.pm:214
-#. ($RT::MinimumPasswordLength)
-msgid "Password needs to be at least %1 characters long"
-msgstr "å£ä»¤é•¿åº¦è‡³å°‘必须为 %1 个字元"
-
-#: lib/RT/User_Overlay.pm:1044
-msgid "Password set"
-msgstr "å£ä»¤å·²è®¾å®š"
-
-#: NOT FOUND IN SOURCE
-msgid "Password too short"
-msgstr "å£ä»¤å¤ªçŸ­"
-
-#: html/User/Prefs.html:240
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "å£ä»¤ï¼š%1"
-
-#: lib/RT/User_Overlay.pm:1030
-msgid "Password: Permission Denied"
-msgstr "å£ä»¤ï¼šæƒé™ä¸è¶³"
-
-#: html/Admin/Users/Modify.html:364
-msgid "Passwords do not match."
-msgstr "å£ä»¤ç¡®è®¤å¤±è´¥ã€‚"
-
-#: html/User/Prefs.html:242
-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:62 html/Ticket/Elements/Tabs:119 html/Ticket/ModifyAll.html:72
-msgid "People"
-msgstr "人员"
-
-#: NOT FOUND IN SOURCE
-msgid "People with Queue Rights"
-msgstr "拥有表å•æƒé™äººå‘˜"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr "执行使用者自订的动作"
-
-#: html/Admin/Tools/Configuration.html:94
-msgid "Perl configuration"
-msgstr "Perl 设定"
-
-#: lib/RT/ACE_Overlay.pm:251 lib/RT/ACE_Overlay.pm:257 lib/RT/ACE_Overlay.pm:580 lib/RT/ACE_Overlay.pm:590 lib/RT/ACE_Overlay.pm:600 lib/RT/ACE_Overlay.pm:665 lib/RT/Attribute_Overlay.pm:158 lib/RT/Attribute_Overlay.pm:164 lib/RT/Attribute_Overlay.pm:405 lib/RT/Attribute_Overlay.pm:414 lib/RT/Attribute_Overlay.pm:427 lib/RT/CurrentUser.pm:116 lib/RT/CurrentUser.pm:125 lib/RT/CustomField_Overlay.pm:1017 lib/RT/CustomField_Overlay.pm:1138 lib/RT/CustomField_Overlay.pm:1281 lib/RT/CustomField_Overlay.pm:172 lib/RT/CustomField_Overlay.pm:189 lib/RT/CustomField_Overlay.pm:200 lib/RT/CustomField_Overlay.pm:374 lib/RT/CustomField_Overlay.pm:403 lib/RT/CustomField_Overlay.pm:763 lib/RT/CustomField_Overlay.pm:936 lib/RT/CustomField_Overlay.pm:971 lib/RT/Group_Overlay.pm:1117 lib/RT/Group_Overlay.pm:1121 lib/RT/Group_Overlay.pm:1130 lib/RT/Group_Overlay.pm:1240 lib/RT/Group_Overlay.pm:1244 lib/RT/Group_Overlay.pm:1250 lib/RT/Group_Overlay.pm:445 lib/RT/Group_Overlay.pm:542 lib/RT/Group_Overlay.pm:620 lib/RT/Group_Overlay.pm:628 lib/RT/Group_Overlay.pm:726 lib/RT/Group_Overlay.pm:730 lib/RT/Group_Overlay.pm:736 lib/RT/Group_Overlay.pm:922 lib/RT/Group_Overlay.pm:926 lib/RT/Group_Overlay.pm:939 lib/RT/Queue_Overlay.pm:1054 lib/RT/Queue_Overlay.pm:140 lib/RT/Queue_Overlay.pm:158 lib/RT/Queue_Overlay.pm:657 lib/RT/Queue_Overlay.pm:667 lib/RT/Queue_Overlay.pm:681 lib/RT/Queue_Overlay.pm:819 lib/RT/Queue_Overlay.pm:828 lib/RT/Queue_Overlay.pm:841 lib/RT/Scrip_Overlay.pm:149 lib/RT/Scrip_Overlay.pm:160 lib/RT/Scrip_Overlay.pm:224 lib/RT/Scrip_Overlay.pm:538 lib/RT/Template_Overlay.pm:108 lib/RT/Template_Overlay.pm:277 lib/RT/Ticket_Overlay.pm:1357 lib/RT/Ticket_Overlay.pm:1367 lib/RT/Ticket_Overlay.pm:1381 lib/RT/Ticket_Overlay.pm:1522 lib/RT/Ticket_Overlay.pm:1532 lib/RT/Ticket_Overlay.pm:1546 lib/RT/Ticket_Overlay.pm:1663 lib/RT/Ticket_Overlay.pm:1983 lib/RT/Ticket_Overlay.pm:2126 lib/RT/Ticket_Overlay.pm:2296 lib/RT/Ticket_Overlay.pm:2346 lib/RT/Ticket_Overlay.pm:2525 lib/RT/Ticket_Overlay.pm:2538 lib/RT/Ticket_Overlay.pm:2614 lib/RT/Ticket_Overlay.pm:2627 lib/RT/Ticket_Overlay.pm:2748 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2990 lib/RT/Ticket_Overlay.pm:3000 lib/RT/Ticket_Overlay.pm:3005 lib/RT/Ticket_Overlay.pm:3224 lib/RT/Ticket_Overlay.pm:3228 lib/RT/Ticket_Overlay.pm:3371 lib/RT/Ticket_Overlay.pm:3497 lib/RT/Transaction_Overlay.pm:516 lib/RT/Transaction_Overlay.pm:523 lib/RT/Transaction_Overlay.pm:551 lib/RT/Transaction_Overlay.pm:558 lib/RT/User_Overlay.pm:1176 lib/RT/User_Overlay.pm:1856 lib/RT/User_Overlay.pm:369 lib/RT/User_Overlay.pm:735 lib/RT/User_Overlay.pm:774
-msgid "Permission Denied"
-msgstr "æƒé™ä¸è¶³"
-
-#: NOT FOUND IN SOURCE
-msgid "Permission Settings"
-msgstr "æƒé™è®¾å®š"
-
-#: lib/RT/Template_Overlay.pm:238 lib/RT/Template_Overlay.pm:247
-msgid "Permission denied"
-msgstr "æƒé™ä¸è¶³"
-
-#: lib/RT/Template_Overlay.pm:372
-msgid "Permissions denied"
-msgstr "æƒé™ä¸è¶³"
-
-#: NOT FOUND IN SOURCE
-msgid "Permitted Queues:"
-msgstr "拥有æƒé™è¡¨å•åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Personal"
-msgstr "代ç†äººç¾¤ç»„"
-
-#: html/User/Elements/Tabs:56
-msgid "Personal Groups"
-msgstr "代ç†äººç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "Personal Homepage"
-msgstr "个人首页"
-
-#: NOT FOUND IN SOURCE
-msgid "Personal Todo"
-msgstr "ç§äººå¾…办事项"
-
-#: html/User/Groups/index.html:51 html/User/Groups/index.html:61
-msgid "Personal groups"
-msgstr "代ç†äººç¾¤ç»„"
-
-#: html/User/Elements/DelegateRights:58
-msgid "Personal groups:"
-msgstr "代ç†äººç¾¤ç»„:"
-
-#: NOT FOUND IN SOURCE
-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 "电è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Phone number"
-msgstr "电è¯å·ç "
-
-#: html/Admin/Users/Modify.html:180 html/User/Prefs.html:81
-msgid "Phone numbers"
-msgstr "电è¯å·ç "
-
-#: NOT FOUND IN SOURCE
-msgid "Pick"
-msgstr "挑选"
-
-#: NOT FOUND IN SOURCE
-msgid "Place of Departure"
-msgstr "出å‘地点"
-
-#: NOT FOUND IN SOURCE
-msgid "Placeholder"
-msgstr "尚未完工"
-
-#: NOT FOUND IN SOURCE
-msgid "Please Select"
-msgstr "请选择"
-
-#: NOT FOUND IN SOURCE
-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 "èŒç­‰"
-
-#: NOT FOUND IN SOURCE
-msgid "Position Name"
-msgstr "èŒåŠ¡å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Position Number"
-msgstr "èŒåŠ¡ä»£ç "
-
-#: NOT FOUND IN SOURCE
-msgid "Position Rank"
-msgstr "èŒçº§"
-
-#: NOT FOUND IN SOURCE
-msgid "Pref"
-msgstr "å好"
-
-#: html/Elements/Header:93 html/Elements/Tabs:91 html/SelfService/Elements/Tabs:95 html/SelfService/Prefs.html:46 html/User/Prefs.html:46 html/User/Prefs.html:49
-msgid "Preferences"
-msgstr "å好"
-
-#: html/Admin/Users/MyRT.html:75
-#. ($pane, $UserObj->Name)
-msgid "Preferences %1 for user %2 ."
-msgstr "使用者 %2 çš„ %1 å好。"
-
-#: html/Prefs/MyRT.html:141
-#. ($pane)
-msgid "Preferences saved for %1."
-msgstr "æˆåŠŸå‚¨å­˜ %1 çš„å好。"
-
-#: NOT FOUND IN SOURCE
-msgid "Prefs"
-msgstr "个人信æ¯"
-
-#: lib/RT/Action/Generic.pm:195
-msgid "Prepare Stubbed"
-msgstr "预备动作完毕"
-
-#: html/Helpers/CalPopup.html:56 html/Ticket/Elements/Tabs:84
-msgid "Prev"
-msgstr "上一项"
-
-#: html/Elements/TicketList:101
-msgid "Previous Page"
-msgstr "上一页"
-
-#: NOT FOUND IN SOURCE
-msgid "Previous page"
-msgstr "å‰ä¸€é¡µ"
-
-#: NOT FOUND IN SOURCE
-msgid "Pri"
-msgstr "优先顺ä½"
-
-#: lib/RT/ACE_Overlay.pm:157 lib/RT/ACE_Overlay.pm:239 lib/RT/ACE_Overlay.pm:569
-#. ($args{'PrincipalId'})
-msgid "Principal %1 not found."
-msgstr "找ä¸åˆ°å•ä½ %1。"
-
-#: html/Search/Elements/PickBasics:147 html/Ticket/Create.html:181 html/Ticket/Elements/EditBasics:92 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1790
-msgid "Priority"
-msgstr "优先顺ä½"
-
-#: html/Admin/Queues/Modify.html:86
-msgid "Priority starts at"
-msgstr "优先顺ä½èµ·å§‹å€¼"
-
-#: html/Search/Elements/EditSearches:50
-msgid "Privacy:"
-msgstr "éšç§è®¾å®šï¼š"
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "内部æˆå‘˜"
-
-#: html/Admin/Users/Modify.html:342 html/User/Prefs.html:231
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr "内部æˆå‘˜çŠ¶æ€ï¼š%1"
-
-#: html/Admin/Users/index.html:102
-msgid "Privileged users"
-msgstr "内部æˆå‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Process Status"
-msgstr "处ç†çŠ¶æ€"
-
-#: NOT FOUND IN SOURCE
-msgid "Project"
-msgstr "项目"
-
-#: NOT FOUND IN SOURCE
-msgid "Project Name"
-msgstr "项目å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Projects"
-msgstr "项目"
-
-#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
-msgid "Pseudogroup for internal use"
-msgstr "内部用的虚拟群组"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Description"
-msgstr "公开说明"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Info"
-msgstr "公开信æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Service"
-msgstr "公共事务区"
-
-#: NOT FOUND IN SOURCE
-msgid "Purging stale data: %1"
-msgstr "移除过期数æ®: %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Query"
-msgstr "查询"
-
-#: html/Search/Build.html:121
-msgid "Query Builder"
-msgstr "建立查询"
-
-#: html/Search/Elements/Chart:101
-msgid "Query:"
-msgstr "查询:"
-
-#: html/Elements/QueueSummary:48 html/Elements/QuickCreate:54 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:62 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/Tools/Reports/CreatedByDates.html:85 html/Tools/Reports/ResolvedByDates.html:86 html/Tools/Reports/ResolvedByOwner.html:66 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1617
-msgid "Queue"
-msgstr "表å•"
-
-#: html/Admin/Queues/CustomField.html:63 html/Admin/Queues/Scrip.html:61 html/Admin/Queues/Scrips.html:69 html/Admin/Queues/Templates.html:65
-#. ($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/Queues/Modify.html:64
-msgid "Queue Name"
-msgstr "表å•å称"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Owner"
-msgstr "业务承办人"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Priority"
-msgstr "优先等级"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Rights"
-msgstr "表å•æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Scrips"
-msgstr "表å•æ‰‹ç»­"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Setup"
-msgstr "表å•è®¾å®š"
-
-#: lib/RT/Queue_Overlay.pm:365
-msgid "Queue already exists"
-msgstr "表å•å·²å­˜åœ¨"
-
-#: lib/RT/Queue_Overlay.pm:374 lib/RT/Queue_Overlay.pm:380
-msgid "Queue could not be created"
-msgstr "无法新增表å•"
-
-#: html/Ticket/Create.html:244 lib/t/regression/01ticket_link_searching.t:17
-msgid "Queue could not be loaded."
-msgstr "无法加载表å•"
-
-#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:384 lib/RT/StyleGuide.pod:809
-msgid "Queue created"
-msgstr "表å•æ–°å¢žå®Œæ¯•"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue is not specified."
-msgstr "未指定表å•ã€‚"
-
-#: html/SelfService/Display.html:126 lib/RT/CustomField_Overlay.pm:197
-msgid "Queue not found"
-msgstr "找ä¸åˆ°è¡¨å•"
-
-#: html/Admin/Elements/Tabs:59 html/Admin/index.html:72
-msgid "Queues"
-msgstr "表å•"
-
-#: html/Elements/MyAdminQueues:46
-msgid "Queues I administer"
-msgstr "由我管ç†çš„表å•"
-
-#: html/Elements/MySupportQueues:46
-msgid "Queues I'm an AdminCc for"
-msgstr "管ç†å‘˜å‰¯æœ¬æœ‰æˆ‘的表å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Quick Search"
-msgstr "表å•çŽ°å†µ"
-
-#: html/Elements/Quicksearch:47 html/Prefs/Elements/Tabs:58 html/Prefs/Quicksearch.html:70
-msgid "Quick search"
-msgstr "表å•ä¸€è§ˆ"
-
-#: html/Elements/QuickCreate:47
-msgid "Quick ticket creation"
-msgstr "快速建立申请å•"
-
-#: html/Search/Results.html:81
-msgid "RSS"
-msgstr "RSS"
-
-#: NOT FOUND IN SOURCE
-msgid "RT %1"
-msgstr "RT %1"
-
-#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:796
-#. ($RT::VERSION, $RT::rtname)
-msgid "RT %1 for %2"
-msgstr "%2:RT %1 版"
-
-#: NOT FOUND IN SOURCE
-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:46 html/Admin/index.html:47
-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:63 html/SelfService/Error.html:62
-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"
-msgstr "RT 自助æœåŠ¡"
-
-#: html/Admin/Tools/Configuration.html:73
-msgid "RT Variables"
-msgstr "RT çš„å˜æ•°"
-
-#: html/Admin/Elements/SystemTabs:71 html/Admin/Elements/UserTabs:67 html/Admin/Global/MyRT.html:1 html/Admin/Global/MyRT.html:12 html/Admin/Global/MyRT.html:4 html/Admin/Global/index.html:84 html/Admin/Users/MyRT.html:21 html/Prefs/MyRT.html:66 html/Prefs/MyRT.html:78 html/User/Elements/Tabs:65 html/index.html:1 html/index.html:75
-msgid "RT at a glance"
-msgstr "RT 一览"
-
-#: html/Admin/Users/MyRT.html:30
-#. ($UserObj->Name)
-msgid "RT at a glance for the user %1"
-msgstr "使用者 %1 的 RT 一览"
-
-#: html/Admin/CustomFields/Modify.html:117
-msgid "RT can include content from another web service when showing this custom field."
-msgstr "RT å¯äºŽæ˜¾ç¤ºæ­¤è‡ªè®¢å­—段时引入其它网站的内容"
-
-#: html/Admin/CustomFields/Modify.html:106
-msgid "RT can make this custom field's values into hyperlinks to another service."
-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"
-
-#: html/Elements/SetupSessionCookie:100
-msgid "RT couldn't store your session."
-msgstr "RT 无法储存您的登入阶段。"
-
-#: NOT FOUND IN SOURCE
-msgid "RT couldn't validate this PGP signature. \\n"
-msgstr "RT 无法确认这个 PGP 签章。\\n"
-
-#: html/Elements/Logo:49 html/Elements/PageLayout:172
-#. ($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 已执行您的命令"
-
-#: NOT FOUND IN SOURCE
-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 认为这å¯èƒ½æ˜¯é€€ä¿¡"
-
-#: html/Search/Simple.html:58
-msgid "RT will look for anything else you enter in ticket subjects."
-msgstr "RT 会在申请å•ä¸»æ—¨å†…æœå¯»å°†æ‚¨é”®å…¥çš„任何其它字样"
-
-#: NOT FOUND IN SOURCE
-msgid "RT will process this message as if it were unsigned.\\n"
-msgstr "RT 以未签章方å¼å¤„ç†è¿™å°é‚®ä»¶ã€‚\\n"
-
-#: html/Admin/CustomFields/Modify.html:108 html/Admin/CustomFields/Modify.html:119
-msgid "RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively"
-msgstr "RT 会将 <tt>__id__</tt> åŠ <tt>__CustomField__</tt> ç½®æ¢æˆçºªå½•ç¼–å·åŠè‡ªè®¢å­—段"
-
-#: 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 "申请å•è¿è¡Œè§’色"
-
-#: NOT FOUND IN SOURCE
-msgid "RT_System"
-msgstr "系统讯æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Read Only"
-msgstr "åªè¯»"
-
-#: html/Admin/Users/Modify.html:79 html/User/Prefs.html:69
-msgid "Real Name"
-msgstr "真实姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "RealName"
-msgstr "真实姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "Really reject this ticket?"
-msgstr "您确定è¦é©³å›žè¿™å¼ ç”³è¯·å•å—?"
-
-#: lib/RT/Transaction_Overlay.pm:725
-#. ($value)
-msgid "Reference by %1 added"
-msgstr "已加入 %1 为å‚考本申请å•"
-
-#: lib/RT/Transaction_Overlay.pm:765
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr "已移除 %1 为å‚考本申请å•"
-
-#: lib/RT/Transaction_Overlay.pm:722
-#. ($value)
-msgid "Reference to %1 added"
-msgstr "已加入å‚è€ƒç”³è¯·å• %1"
-
-#: lib/RT/Transaction_Overlay.pm:762
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr "已移除å‚è€ƒç”³è¯·å• %1"
-
-#: html/Elements/EditLinks:103 html/Elements/EditLinks:156 html/Elements/ShowLinks:92 html/Ticket/Create.html:225 html/Ticket/Elements/BulkLinks:72
-msgid "Referred to by"
-msgstr "被å‚考"
-
-#: html/Elements/EditLinks:152 html/Elements/EditLinks:94 html/Elements/SelectLinkType:49 html/Elements/ShowLinks:82 html/Ticket/Create.html:224 html/Ticket/Elements/BulkLinks:68
-msgid "Refers to"
-msgstr "å‚考"
-
-#: NOT FOUND IN SOURCE
-msgid "RefersTo"
-msgstr "å‚考"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine"
-msgstr "在结果范围内查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine search"
-msgstr "调整查询æ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Refresh"
-msgstr "æ›´æ–°"
-
-#: html/Elements/Refresh:57
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "æ¯ %1 分钟更新页é¢"
-
-#: lib/RT/Transaction_Overlay.pm:811
-#. ($ticket->Subject)
-msgid "Reminder '%1' added"
-msgstr "已建立æ醒项目‘%1’"
-
-#: lib/RT/Transaction_Overlay.pm:824
-#. ($ticket->Subject)
-msgid "Reminder '%1' completed"
-msgstr "已完æˆæ醒项目‘%1’"
-
-#: lib/RT/Transaction_Overlay.pm:817
-#. ($ticket->Subject)
-msgid "Reminder '%1' reopened"
-msgstr "å·²é‡æ–°å¼€å¯æ醒项目‘%1’"
-
-#: html/Ticket/Reminders.html:46
-#. ($Ticket->Id)
-msgid "Reminder ticket #%1"
-msgstr "æ醒项目 #%1"
-
-#: html/Elements/MyReminders:48 html/Ticket/Elements/ShowSummary:75 html/Ticket/Elements/Tabs:122 html/Ticket/Reminders.html:52
-msgid "Reminders"
-msgstr "æ醒项目"
-
-#: html/Ticket/Reminders.html:50
-#. ($Ticket->Id)
-msgid "Reminders for ticket #%1"
-msgstr "ç”³è¯·å• #%1 çš„æ醒项目"
-
-#: NOT FOUND IN SOURCE
-msgid "Remove"
-msgstr "移除"
-
-#: html/Search/Bulk.html:94
-msgid "Remove AdminCc"
-msgstr "移除管ç†å‘˜å‰¯æœ¬"
-
-#: html/Search/Bulk.html:90
-msgid "Remove Cc"
-msgstr "移除副本"
-
-#: html/Search/Bulk.html:86
-msgid "Remove Requestor"
-msgstr "移除申请人"
-
-#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:147
-msgid "Reply"
-msgstr "回å¤"
-
-#: html/Admin/Queues/Modify.html:72
-msgid "Reply Address"
-msgstr "回å¤åœ°å€"
-
-#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:94 html/Ticket/Update.html:78
-msgid "Reply to requestors"
-msgstr "回å¤ç”³è¯·äºº"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "Reply to tickets"
-msgstr "对申请å•è¿›è¡Œå›žå¤"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "ReplyToTicket"
-msgstr "回å¤ç”³è¯·å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Report to Duty"
-msgstr "上下ç­åˆ·å¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Reported on"
-msgstr "到èŒæ—¥æœŸ"
-
-#: html/Tools/Elements/Tabs:59 html/Tools/Reports/index.html:46 html/Tools/Reports/index.html:47
-msgid "Reports"
-msgstr "报表"
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:111
-msgid "Requestor"
-msgstr "申请人"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor email address"
-msgstr "申请人电å­é‚®ä»¶ä¿¡ç®±åœ°å€"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's"
-msgstr "申请人所属之第上"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's Dept."
-msgstr "申请人所属部门之"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's Phone"
-msgstr "申请人电è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor(s)"
-msgstr "申请人"
-
-#: NOT FOUND IN SOURCE
-msgid "RequestorAddresses"
-msgstr "申请人地å€"
-
-#: html/SelfService/Create.html:63 html/Ticket/Create.html:80 html/Ticket/Elements/EditPeople:69 html/Ticket/Elements/ShowPeople:52
-msgid "Requestors"
-msgstr "申请人"
-
-#: html/Admin/Queues/Modify.html:96
-msgid "Requests should be due in"
-msgstr "申请å•å¤„ç†æœŸé™"
-
-#: lib/RT/Attribute_Overlay.pm:146
-#. ('Object')
-msgid "Required parameter '%1' not specified"
-msgstr "未指定必è¦çš„å‚数‘%1’"
-
-#: html/Elements/Submit:83
-msgid "Reset"
-msgstr "é‡è®¾"
-
-#: html/Admin/Users/MyRT.html:15 html/Prefs/MyRT.html:60
-msgid "Reset to default"
-msgstr "é‡è®¾ä¸ºé¢„设值"
-
-#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:84
-msgid "Residence"
-msgstr "ä½å¤„"
-
-#: NOT FOUND IN SOURCE
-msgid "Resolution"
-msgstr "解决状æ€"
-
-#: html/Ticket/Elements/Tabs:156
-msgid "Resolve"
-msgstr "解决"
-
-#: html/Ticket/Update.html:156
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "è§£å†³ç”³è¯·å• #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:49 lib/RT/Ticket_Overlay.pm:1172
-msgid "Resolved"
-msgstr "已解决"
-
-#: html/Tools/Reports/Elements/Tabs:55
-msgid "Resolved by owner"
-msgstr "已由承办人解决"
-
-#: html/Tools/Reports/Elements/Tabs:59
-msgid "Resolved in date range"
-msgstr "已在指定日期内解决"
-
-#: html/Tools/Reports/ResolvedByDates.html:52
-msgid "Resolved tickets in period, grouped by owner"
-msgstr "已在指定日期内内解决,ä¾æ‰¿åŠžäººåˆ†ç»„"
-
-#: html/Tools/Reports/ResolvedByOwner.html:50
-msgid "Resolved tickets, grouped by owner"
-msgstr "已解决的申请å•ï¼Œä¾æ‰¿åŠžäººåˆ†ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "Response to requestors"
-msgstr "回å¤ç”³è¯·äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Responsibility Type"
-msgstr "责任区分"
-
-#: html/Elements/ListActions:46 html/Search/Elements/NewListActions:47
-msgid "Results"
-msgstr "结果"
-
-#: NOT FOUND IN SOURCE
-msgid "Results per page"
-msgstr "æ¯é¡µåˆ—出几笔结果"
-
-#: html/Admin/Users/Modify.html:126 html/User/Prefs.html:116
-msgid "Retype Password"
-msgstr "å†æ¬¡è¾“å…¥å£ä»¤"
-
-#: html/Search/Elements/EditSearches:61
-msgid "Revert"
-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:630
-msgid "Right Delegated"
-msgstr "æƒé™ä»£ç†å®Œæ¯•"
-
-#: lib/RT/ACE_Overlay.pm:320
-msgid "Right Granted"
-msgstr "æƒé™è®¾å®šå®Œæ¯•"
-
-#: lib/RT/ACE_Overlay.pm:178
-msgid "Right Loaded"
-msgstr "æƒé™åŠ è½½å®Œæ¯•"
-
-#: lib/RT/ACE_Overlay.pm:695 lib/RT/ACE_Overlay.pm:716
-msgid "Right could not be revoked"
-msgstr "无法撤消æƒé™"
-
-#: html/User/Delegation.html:85
-msgid "Right not found"
-msgstr "找ä¸åˆ°æƒé™"
-
-#: lib/RT/ACE_Overlay.pm:560 lib/RT/ACE_Overlay.pm:655
-msgid "Right not loaded."
-msgstr "æƒé™å¹¶æœªåŠ è½½ã€‚"
-
-#: lib/RT/ACE_Overlay.pm:712
-msgid "Right revoked"
-msgstr "æƒé™æ’¤æ¶ˆå®Œæ¯•"
-
-#: html/Admin/Elements/UserTabs:70
-msgid "Rights"
-msgstr "æƒé™åŠä»£ç†äºº"
-
-#: html/Admin/CustomFields/GroupRights.html:129 lib/RT/Interface/Web.pm:961
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr "无法将æƒé™èµ‹äºˆ %1"
-
-#: html/Admin/CustomFields/GroupRights.html:156 lib/RT/Interface/Web.pm:990
-#. ($object_type)
-msgid "Rights could not be revoked for %1"
-msgstr "无法撤消 %1 çš„æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Role Members"
-msgstr "角色æˆå‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Role Name"
-msgstr "角色å称"
-
-#: html/Admin/Global/GroupRights.html:72 html/Admin/Queues/GroupRights.html:74
-msgid "Roles"
-msgstr "角色"
-
-#: NOT FOUND IN SOURCE
-msgid "RootApproval"
-msgstr "交由系统管ç†å‘˜ç­¾æ ¸"
-
-#: html/Prefs/MyRT.html:72
-msgid "Rows per box"
-msgstr "æ¯æ ¼ç¬”æ•°"
-
-#: html/Search/Elements/DisplayOptions:93
-msgid "Rows per page"
-msgstr "æ¯é¡µç¬”æ•°"
-
-#: NOT FOUND IN SOURCE
-msgid "Run Approval"
-msgstr "签核执行"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPDebug"
-msgstr "SMTP 侦错纪录"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPFrom"
-msgstr "SMTP 寄件地å€"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPServer"
-msgstr "SMTP æœåŠ¡å™¨"
-
-#: NOT FOUND IN SOURCE
-msgid "Sat"
-msgstr "星期六"
-
-#: lib/RT/Date.pm:422
-msgid "Sat."
-msgstr "星期六"
-
-#: html/Prefs/MyRT.html:72 html/Prefs/Quicksearch.html:64 html/Prefs/Search.html:69 html/Prefs/Search.html:69 html/Search/Elements/EditSearches:70 html/Widgets/SelectionBox:211
-msgid "Save"
-msgstr "储存"
-
-#: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:88 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html:239 html/Prefs/Quicksearch.html:64 html/Prefs/SearchOptions.html:63 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:61 html/Ticket/ModifyPeople.html:60 html/User/Groups/Modify.html:77
-msgid "Save Changes"
-msgstr "储存更改"
-
-#: html/User/Prefs.html:181
-msgid "Save Preferences"
-msgstr "储存å好"
-
-#: html/Ticket/Elements/PreviewScrips:131
-msgid "Save changes"
-msgstr "储存更改"
-
-#: lib/RT/SavedSearch.pm:173
-#. ($name)
-msgid "Saved search %1"
-msgstr "æˆåŠŸå‚¨å­˜æŸ¥è¯¢ï¼š%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Saved searches"
-msgstr "预存查询"
-
-#: html/Admin/Elements/ListGlobalScrips:60 html/Admin/Global/Scrip.html:77 html/Admin/Queues/Scrip.html:84
-#. ($scrip->Id)
-#. ($id)
-msgid "Scrip #%1"
-msgstr "手续 #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Action"
-msgstr "讯æ¯é€šçŸ¥åŠ¨ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Condition"
-msgstr "讯æ¯é€šçŸ¥æ¡ä»¶"
-
-#: lib/RT/Scrip_Overlay.pm:203
-msgid "Scrip Created"
-msgstr "手续新增完毕"
-
-#: html/Admin/Elements/EditScrip:52
-msgid "Scrip Fields"
-msgstr "手续字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Name"
-msgstr "讯æ¯å称"
-
-#: html/Admin/Elements/EditScrips:109
-msgid "Scrip deleted"
-msgstr "手续删除完毕"
-
-#: html/Admin/Elements/QueueTabs:67 html/Admin/Elements/SystemTabs:54 html/Admin/Global/index.html:62
-msgid "Scrips"
-msgstr "手续"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips "
-msgstr "讯æ¯é€šçŸ¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips for %1\\n"
-msgstr "%1 的手续\\n"
-
-#: html/Admin/Queues/Scrips.html:55
-msgid "Scrips which apply to all queues"
-msgstr "适用于所有表å•çš„手续"
-
-#: html/Elements/SimpleSearch:48 html/Search/Simple.html:63
-msgid "Search"
-msgstr "查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Search Criteria"
-msgstr "查询æ¡ä»¶"
-
-#: html/Prefs/SearchOptions.html:47 html/Prefs/SearchOptions.html:50
-msgid "Search Preferences"
-msgstr "æœå¯»å好"
-
-#: lib/RT/SavedSearch.pm:115
-msgid "Search attribute load failure"
-msgstr "æœå¯»å±žæ€§åŠ è½½å¤±è´¥"
-
-#: html/Approvals/Elements/PendingMyApproval:59
-msgid "Search for approvals"
-msgstr "签核å•æŸ¥è¯¢"
-
-#: html/Search/Simple.html:67
-msgid "Search for tickets"
-msgstr "申请å•æŸ¥è¯¢"
-
-#: html/Search/Simple.html:55
-msgid "Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>. RT will look for anything else you enter in ticket bodies and attachments."
-msgstr "æœå¯»ç”³è¯·å•ã€‚请键入<strong>ç¼–å·</strong>ã€<strong>表å•å称</strong>ã€æ‰¿åŠžäººçš„<strong>使用者å称</strong>ã€æˆ–申请人的<strong>电å­é‚®ä»¶åœ°å€</strong>。以上格å¼ä¹‹å¤–的文字,则会在申请å•å†…æ–‡åŠé™„件内检索。"
-
-#: html/User/Elements/Tabs:62
-msgid "Search options"
-msgstr "æœå¯»é€‰é¡¹"
-
-#: html/Search/Chart.html:56
-#. ($PrimaryGroupBy)
-msgid "Search results grouped by %1"
-msgstr "æœå¯»ç»“æžœï¼Œä¾ %1 分组"
-
-#: lib/RT/SavedSearch.pm:203
-#. ($msg)
-msgid "Search update: %1"
-msgstr "更新查询:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Searches can't be associated with that kind of object"
-msgstr "ä¸èƒ½å¯¹æ­¤ç±»å¯¹è±¡è¿›è¡ŒæŸ¥è¯¢"
-
-#: html/Search/Simple.html:57
-msgid "Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>."
-msgstr "对所有申请å•çš„全文进行检索,å¯èƒ½ä¼šéœ€è¦å¾ˆä¹…的时间。但如果您真的有需è¦ï¼Œå¯é”®å…¥ <b>fulltext:<i>文字</i></b> æ¥æœå¯»ç”³è¯·å•çš„所有纪录。"
-
-#: NOT FOUND IN SOURCE
-msgid "Second-"
-msgstr "二"
-
-#: NOT FOUND IN SOURCE
-msgid "Second-level Users"
-msgstr "二阶主管员工"
-
-#: bin/rt-crontool:265
-msgid "Security:"
-msgstr "安全性:"
-
-#: html/Elements/ShowCustomFields:98
-msgid "See also:"
-msgstr "å‚è§ï¼š"
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "See custom fields"
-msgstr "查阅自订字段"
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "See exact outgoing email messages and their recipeients"
-msgstr "查阅é€å‡ºçš„电å­é‚®ä»¶åŠæ”¶ä»¶äºº"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "See ticket private commentary"
-msgstr "查阅申请å•å†…çš„ç§äººè¯„论"
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "See ticket summaries"
-msgstr "查阅申请å•æ€»è§ˆ"
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "SeeCustomField"
-msgstr "查阅自订字段"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "SeeGroup"
-msgstr "查阅群组"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "SeeQueue"
-msgstr "查阅表å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Select"
-msgstr "选择"
-
-#: NOT FOUND IN SOURCE
-msgid "Select All"
-msgstr "全选"
-
-#: html/Admin/CustomFields/index.html:46 html/Admin/CustomFields/index.html:49
-msgid "Select a Custom Field"
-msgstr "选择自订字段"
-
-#: html/Admin/Groups/index.html:78
-msgid "Select a group"
-msgstr "选择群组"
-
-#: html/Admin/Queues/index.html:54
-msgid "Select a queue"
-msgstr "选择表å•"
-
-#: html/SelfService/CreateTicketInQueue.html:48
-msgid "Select a queue for your new ticket"
-msgstr "为您新的申请å•é€‰æ‹©ä¸€ä¸ªè¡¨å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Select a queue to link to"
-msgstr "请选择欲连结表å•"
-
-#: html/Admin/Users/index.html:46 html/Admin/Users/index.html:49 html/Admin/Users/index.html:52
-msgid "Select a user"
-msgstr "选择使用者"
-
-#: html/Admin/Elements/CustomFieldTabs:90
-msgid "Select custom field"
-msgstr "选择自订字段"
-
-#: html/Admin/Global/CustomFields/index.html:70
-msgid "Select custom fields for all user groups"
-msgstr "选择适用于所有使用者群组的自订字段"
-
-#: html/Admin/Global/CustomFields/index.html:65
-msgid "Select custom fields for all users"
-msgstr "选择适用于所有使用者的自订字段"
-
-#: html/Admin/Global/CustomFields/index.html:76
-msgid "Select custom fields for tickets in all queues"
-msgstr "选择适用于所有表å•å†…申请å•çš„自订字段"
-
-#: html/Admin/Global/CustomFields/index.html:83
-msgid "Select custom fields for transactions on tickets in all queues"
-msgstr "选择适用于所有表å•å†…申请å•ä¹‹æ›´åŠ¨çš„自订字段"
-
-#: html/Admin/Elements/GroupTabs:75 html/User/Elements/GroupTabs:71
-msgid "Select group"
-msgstr "选择群组"
-
-#: lib/RT/CustomField_Overlay.pm:59
-msgid "Select multiple values"
-msgstr "选择多é‡é¡¹ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:60
-msgid "Select one value"
-msgstr "选择å•ä¸€é¡¹ç›®"
-
-#: html/Admin/Elements/QueueTabs:92
-msgid "Select queue"
-msgstr "选择表å•"
-
-#: html/Prefs/Quicksearch.html:53
-msgid "Select queues to be displayed on the \"RT at a glance\" page"
-msgstr "选择è¦åœ¨â€˜RT 一览’页é¢æ˜¾ç¤ºçš„表å•"
-
-#: html/Admin/Global/Scrip.html:59 html/Admin/Global/Scrips.html:57 html/Admin/Queues/Scrip.html:67 html/Admin/Queues/Scrips.html:73
-msgid "Select scrip"
-msgstr "选择手续"
-
-#: html/Admin/Global/Template.html:78 html/Admin/Global/Templates.html:57 html/Admin/Queues/Template.html:76 html/Admin/Queues/Templates.html:68
-msgid "Select template"
-msgstr "选择模æ¿"
-
-#: lib/RT/CustomField_Overlay.pm:61
-msgid "Select up to %1 values"
-msgstr "选择最多 %1 个值"
-
-#: html/Admin/Elements/UserTabs:78
-msgid "Select user"
-msgstr "选择使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "Select workflow"
-msgstr "选择æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectExternal"
-msgstr "系统选项"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectMultiple"
-msgstr "多é‡é€‰é¡¹"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectSingle"
-msgstr "å•ä¸€é€‰é¡¹"
-
-#: html/Admin/Elements/EditCustomFields:58
-msgid "Selected Custom Fields"
-msgstr "已选å–的自订字段"
-
-#: html/Admin/CustomFields/Objects.html:59
-msgid "Selected objects"
-msgstr "已选å–的对象"
-
-#: NOT FOUND IN SOURCE
-msgid "Selected users:"
-msgstr "已选å–的使用者:"
-
-#: html/Widgets/SelectionBox:209
-msgid "Selections modified. Please save your changes"
-msgstr "选å–的项目已更改。请储存您的更动"
-
-#: NOT FOUND IN SOURCE
-msgid "Self Service"
-msgstr "自助æœåŠ¡"
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "寄信给所有视察员"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "以评论方å¼å¯„信给所有视察员"
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr "寄信给申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr "以评论方å¼å¯„信给申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr "寄信给申请人"
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr "寄信给特定的副本åŠå¯†ä»¶å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:94 etc/upgrade/3.1.17/content:7
-msgid "Sends mail to the Ccs"
-msgstr "寄信给副本收件人"
-
-#: etc/initialdata:90 etc/upgrade/3.1.17/content:3
-msgid "Sends mail to the Ccs as a comment"
-msgstr "以评论方å¼å¯„信给副本收件人"
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr "寄信给管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:98
-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:449
-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 "ç­åˆ«å±žæ€§"
-
-#: html/Ticket/Elements/ShowTransaction:158
-msgid "Show"
-msgstr "显示"
-
-#: html/Approvals/index.html:52
-msgid "Show Approvals"
-msgstr "显示待签核申请å•"
-
-#: html/Search/Elements/EditFormat:56
-msgid "Show Columns"
-msgstr "显示字段"
-
-#: html/Ticket/Elements/Tabs:220
-msgid "Show Results"
-msgstr "显示结果"
-
-#: html/Approvals/Elements/PendingMyApproval:64
-msgid "Show approved requests"
-msgstr "显示已批准的签核å•"
-
-#: html/Ticket/Create.html:316
-msgid "Show basics"
-msgstr "显示基本信æ¯"
-
-#: html/Approvals/Elements/PendingMyApproval:65
-msgid "Show denied requests"
-msgstr "显示已驳回的签核å•"
-
-#: html/Ticket/Create.html:319
-msgid "Show details"
-msgstr "显示细节"
-
-#: html/Approvals/Elements/PendingMyApproval:63
-msgid "Show pending requests"
-msgstr "显示待处ç†çš„签核å•"
-
-#: html/Approvals/Elements/PendingMyApproval:66
-msgid "Show requests awaiting other approvals"
-msgstr "显示尚待他人批准的签核å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket private commentary"
-msgstr "显示申请å•å†…çš„ç§äººè¯„论"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket summaries"
-msgstr "显示申请å•æ‘˜è¦"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "ShowACL"
-msgstr "显示æƒé™æ¸…å•"
-
-#: lib/RT/System.pm:85
-msgid "ShowConfigTab"
-msgstr "显示设定页签"
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "ShowOutgoingEmail"
-msgstr "显示寄é€é‚®ä»¶"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "ShowSavedSearches"
-msgstr "显示预存查询"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "ShowScrips"
-msgstr "显示手续"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "ShowTemplate"
-msgstr "显示模æ¿"
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "ShowTicket"
-msgstr "显示申请å•"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "ShowTicketComments"
-msgstr "显示申请å•çš„评论"
-
-#: lib/RT/Queue_Overlay.pm:107
-msgid "Sign up as a ticket Requestor or ticket or queue Cc"
-msgstr "登记æˆä¸ºç”³è¯·äººæˆ–副本收件人"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr "登记æˆä¸ºç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: html/Admin/Users/Modify.html:230 html/User/Prefs.html:168
-msgid "Signature"
-msgstr "ç­¾åæ¡£"
-
-#: NOT FOUND IN SOURCE
-msgid "Signed in as %1"
-msgstr "使用者:%1"
-
-#: html/Elements/Tabs:68
-msgid "Simple Search"
-msgstr "简易查询"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:47
-msgid "Single"
-msgstr "å•ä¸€"
-
-#: html/Search/Elements/EditFormat:75
-msgid "Size"
-msgstr "大å°"
-
-#: html/Elements/Header:89
-msgid "Skip Menu"
-msgstr "略过选å•"
-
-#: html/Search/Elements/EditFormat:78
-msgid "Small"
-msgstr "å°"
-
-#: html/Admin/CustomFields/Modify.html:120
-msgid "Some browsers may only load content from the same domain as your RT server."
-msgstr "æŸäº›æµè§ˆå™¨åªå…许加载和 RT æœåŠ¡å™¨åŒä¸€ä¸ªç½‘域的内容。"
-
-#: html/Admin/Elements/AddCustomFieldValue:49 html/Admin/Elements/EditCustomFieldValues:54
-msgid "Sort"
-msgstr "顺åº"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort key"
-msgstr "排åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort results by"
-msgstr "结果排åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "SortOrder"
-msgstr "排åºé¡ºåº"
-
-#: html/Admin/Elements/EditScrip:78
-msgid "Stage"
-msgstr "å…³å¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Stage Action"
-msgstr "å…³å¡è¿è¡ŒåŠ¨ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "Stage Condition"
-msgstr "å…³å¡è¿è¡Œæ¡ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Stalled"
-msgstr "延宕"
-
-#: NOT FOUND IN SOURCE
-msgid "Start page"
-msgstr "首页"
-
-#: html/Elements/SelectDateType:48 html/Ticket/Elements/EditDates:53 html/Ticket/Elements/ShowDates:56
-msgid "Started"
-msgstr "实际起始日"
-
-#: NOT FOUND IN SOURCE
-msgid "Started date '%1' could not be parsed"
-msgstr "无法解读起始日期 '%1"
-
-#: html/Elements/SelectDateType:52 html/Ticket/Create.html:208 html/Ticket/Elements/EditDates:48 html/Ticket/Elements/ShowDates:52
-msgid "Starts"
-msgstr "应起始日"
-
-msgid "StartsRelative"
-msgstr "应起始日(相对值)"
-
-msgid "StartedRelative"
-msgstr "实际起始日(相对值)"
-
-msgid "CreatedRelative"
-msgstr "实际新增日(相对值)"
-
-msgid "LastUpdatedRelative"
-msgstr "上次更新(相对值)"
-
-msgid "ToldRelative"
-msgstr "告知日(相对值)"
-
-msgid "DueRelative"
-msgstr "到期日(相对值)"
-
-msgid "ResolvedRelative"
-msgstr "解决日(相对值)"
-
-msgid "ReferredToBy"
-msgstr "被å‚考"
-
-msgid "DependedOnBy"
-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/Users/Modify.html:162 html/User/Prefs.html:145
-msgid "State"
-msgstr "å·ž"
-
-#: html/Search/Elements/PickBasics:87 html/SelfService/Update.html:57 html/Ticket/Create.html:66 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:1651
-msgid "Status"
-msgstr "现况"
-
-msgid "ExtendedStatus"
-msgstr "é¢å¤–现况"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "现况改å˜æ—¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Status changed from %1 to %2"
-msgstr "现况从 %1 改为 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "StatusChange"
-msgstr "现况改å˜æ—¶"
-
-#: html/Ticket/Elements/Tabs:178
-msgid "Steal"
-msgstr "强制更æ¢æ‰¿åŠžäºº"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "Steal tickets"
-msgstr "强制承办申请å•"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "StealTicket"
-msgstr "强制承办申请å•"
-
-#: lib/RT/Transaction_Overlay.pm:678
-#. ($Old->Name)
-msgid "Stolen from %1"
-msgstr "承办人从 %1 强制更æ¢"
-
-#: NOT FOUND IN SOURCE
-msgid "Stolen from %1 "
-msgstr "承办人从 %1 å¼ºåˆ¶æ›´æ¢ "
-
-#: html/Search/Elements/EditFormat:81
-msgid "Style"
-msgstr "æ ·å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Subgroup"
-msgstr "å­ç¾¤ç»„"
-
-#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:132 html/SelfService/Create.html:79 html/SelfService/Update.html:65 html/Ticket/Create.html:108 html/Ticket/Elements/EditBasics:48 html/Ticket/Elements/Reminders:125 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:82 lib/RT/Ticket_Overlay.pm:1162 lib/RT/Tickets_Overlay.pm:1733
-msgid "Subject"
-msgstr "主题"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm:700
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "标题已改为 %1"
-
-#: html/Elements/Submit:75
-msgid "Submit"
-msgstr "é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "Submit Workflow"
-msgstr "é€å‡ºæµç¨‹"
-
-#: lib/RT/Group_Overlay.pm:774
-msgid "Succeeded"
-msgstr "设定æˆåŠŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Sun"
-msgstr "星期日"
-
-#: lib/RT/Date.pm:423
-msgid "Sun."
-msgstr "星期日"
-
-#: lib/RT/System.pm:75
-msgid "SuperUser"
-msgstr "系统管ç†å‘˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Sync now"
-msgstr "执行åŒæ­¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Sync104HRMS"
-msgstr "自动åŒæ­¥104HRMS"
-
-#: NOT FOUND IN SOURCE
-msgid "Synchronizing HRMS data. This may take a while..."
-msgstr "正在åŒæ­¥åŒ– HRMS 人事系统数æ®ã€‚请ç¨å¾…..."
-
-#: html/User/Elements/DelegateRights:98
-msgid "System"
-msgstr "系统"
-
-#: html/Admin/Elements/ToolTabs:54 html/Admin/Tools/Configuration.html:48
-msgid "System Configuration"
-msgstr "系统设定"
-
-#: NOT FOUND IN SOURCE
-msgid "System Defined"
-msgstr "系统定义"
-
-#: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:584 lib/RT/Interface/Web.pm:960 lib/RT/Interface/Web.pm:989
-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/Transaction_Overlay.pm:224 lib/RT/Transaction_Overlay.pm:230
-#. ($msg)
-msgid "System Error: %1"
-msgstr "系统错误:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "System Rights"
-msgstr "系统æƒé™"
-
-#: html/Admin/Tools/index.html:47
-msgid "System Tools"
-msgstr "系统工具"
-
-#: lib/RT/ACE_Overlay.pm:633
-msgid "System error. Right not delegated."
-msgstr "系统错误。æƒé™ä»£ç†å¤±è´¥ã€‚"
-
-#: lib/RT/ACE_Overlay.pm:163 lib/RT/ACE_Overlay.pm:228 lib/RT/ACE_Overlay.pm:323 lib/RT/ACE_Overlay.pm:920
-msgid "System error. Right not granted."
-msgstr "系统错误。设定æƒé™å¤±è´¥ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "System error. Unable to grant rights."
-msgstr "系统错误。无法设定æƒé™ã€‚"
-
-#: html/Admin/CustomFields/GroupRights.html:58 html/Admin/Global/GroupRights.html:56 html/Admin/Groups/GroupRights.html:58 html/Admin/Queues/GroupRights.html:57
-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:357
-msgid "TEST_STRING"
-msgstr "TEST_STRING"
-
-#: NOT FOUND IN SOURCE
-msgid "TabbedUI"
-msgstr "页签接å£"
-
-#: etc/initialdata:603 html/Search/Elements/EditFormat:72 html/Ticket/Elements/Tabs:170
-msgid "Take"
-msgstr "å—ç†"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "Take tickets"
-msgstr "自行承办申请å•"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "TakeTicket"
-msgstr "自行承办申请å•"
-
-#: lib/RT/Transaction_Overlay.pm:663
-msgid "Taken"
-msgstr "å·²å—ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Task"
-msgstr "工作事项"
-
-#: html/Admin/Elements/EditScrip:71 html/Tools/Offline.html:78
-msgid "Template"
-msgstr "模æ¿"
-
-#: html/Admin/Global/Template.html:112 html/Admin/Queues/Template.html:113
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "æ¨¡æ¿ #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Content"
-msgstr "通知模æ¿å†…容"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Description"
-msgstr "通知模æ¿æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Name"
-msgstr "通知模æ¿å称"
-
-#: html/Admin/Elements/EditTemplates:110
-msgid "Template deleted"
-msgstr "模æ¿å·²åˆ é™¤"
-
-#: lib/RT/Scrip_Overlay.pm:176
-msgid "Template is mandatory argument"
-msgstr "模æ¿æ˜¯å¿…填字段"
-
-#: lib/RT/Scrip_Overlay.pm:180
-msgid "Template not found"
-msgstr "找ä¸åˆ°æ¨¡æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Template not found\\n"
-msgstr "找ä¸åˆ°æ¨¡æ¿\\n"
-
-#: lib/RT/Template_Overlay.pm:343
-msgid "Template parsed"
-msgstr "模æ¿å‰–æžå®Œæ¯•"
-
-#: lib/RT/Template_Overlay.pm:391
-msgid "Template parsing error"
-msgstr "模æ¿å‰–æžé”™è¯¯"
-
-#: html/Admin/Elements/QueueTabs:70 html/Admin/Elements/SystemTabs:57 html/Admin/Global/index.html:66
-msgid "Templates"
-msgstr "模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates "
-msgstr "通知模æ¿"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates for %1\\n"
-msgstr "找ä¸åˆ° %1 的模æ¿\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Text"
-msgstr "文字"
-
-#: lib/RT/CustomField_Overlay.pm:943 lib/RT/Record.pm:945
-msgid "That is already the current value"
-msgstr "å·²ç»æ˜¯ç›®å‰å­—段的值"
-
-#: lib/RT/CustomField_Overlay.pm:412
-msgid "That is not a value for this custom field"
-msgstr "è¿™ä¸æ˜¯è¯¥è‡ªè®¢å­—段的值"
-
-#: lib/RT/Ticket_Overlay.pm:1994
-msgid "That is the same value"
-msgstr "åŒæ ·çš„值"
-
-#: lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:614
-msgid "That principal already has that right"
-msgstr "这项å•ä½å·²ç»æ‹¥æœ‰è¯¥æƒé™"
-
-#: lib/RT/Queue_Overlay.pm:753
-#. ($args{'Type'})
-msgid "That principal is already a %1 for this queue"
-msgstr "这项å•ä½å·²ç»æ˜¯è¿™ä¸ªè¡¨å•çš„ %1"
-
-#: lib/RT/Ticket_Overlay.pm:1435
-#. ($self->loc($args{'Type'}))
-msgid "That principal is already a %1 for this ticket"
-msgstr "这项å•ä½å·²ç»æ˜¯è¿™ä»½ç”³è¯·å•çš„ %1"
-
-#: lib/RT/Queue_Overlay.pm:852
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr "这项å•ä½ä¸æ˜¯è¿™ä¸ªè¡¨å•çš„ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "That principal is not a %1 for this ticket"
-msgstr "这项å•ä½ä¸æ˜¯è¿™ä»½ç”³è¯·å•çš„ %1"
-
-#: lib/RT/Ticket_Overlay.pm:1990
-msgid "That queue does not exist"
-msgstr "此表å•ä¸å­˜åœ¨"
-
-#: lib/RT/Ticket_Overlay.pm:3233
-msgid "That ticket has unresolved dependencies"
-msgstr "这份申请å•æœ‰å°šæœªè§£å†³çš„附属申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "That user already has that right"
-msgstr "使用者已具有该项æƒé™"
-
-#: lib/RT/Action/CreateTickets.pm:710 lib/RT/Ticket_Overlay.pm:3037
-msgid "That user already owns that ticket"
-msgstr "该使用者已ç»æ‰¿åŠžè¿™ä»½ç”³è¯·å•"
-
-#: lib/RT/Ticket_Overlay.pm:3012
-msgid "That user does not exist"
-msgstr "使用者ä¸å­˜åœ¨"
-
-#: lib/RT/User_Overlay.pm:389
-msgid "That user is already privileged"
-msgstr "è¿™å使用者已ç»æ˜¯å†…部æˆå‘˜"
-
-#: lib/RT/User_Overlay.pm:410
-msgid "That user is already unprivileged"
-msgstr "è¿™å使用者属于éžå†…部æˆå‘˜ç¾¤ç»„"
-
-#: lib/RT/User_Overlay.pm:402
-msgid "That user is now privileged"
-msgstr "使用者加入内部æˆå‘˜ç¾¤ç»„完毕"
-
-#: lib/RT/User_Overlay.pm:423
-msgid "That user is now unprivileged"
-msgstr "è¿™å使用者已加入éžå†…部æˆå‘˜ç¾¤ç»„"
-
-#: NOT FOUND IN SOURCE
-msgid "That user is now unprivilegedileged"
-msgstr "è¿™å使用者已加入éžå†…部æˆå‘˜ç¾¤ç»„"
-
-#: lib/RT/Ticket_Overlay.pm:3031
-msgid "That user may not own tickets in that queue"
-msgstr "使用者å¯èƒ½æ²¡æœ‰æ‰¿åŠžè¡¨å•é‡Œçš„申请å•"
-
-#: lib/RT/Link_Overlay.pm:233
-msgid "That's not a numerical id"
-msgstr "è¿™ä¸æ˜¯ä¸€ä¸ªæ•°å­—ç¼–å·"
-
-#: html/SelfService/Display.html:53 html/Ticket/Create.html:177 html/Ticket/Elements/ShowSummary:49
-msgid "The Basics"
-msgstr "基本信æ¯"
-
-#: lib/RT/ACE_Overlay.pm:112
-msgid "The CC of a ticket"
-msgstr "申请å•çš„副本收件人"
-
-#: lib/RT/ACE_Overlay.pm:113
-msgid "The administrative CC of a ticket"
-msgstr "申请å•çš„管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "The comment has been recorded"
-msgstr "评论已被纪录"
-
-#: bin/rt-crontool:275
-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:"
-
-#: NOT FOUND IN SOURCE
-msgid "The following commands were not proccessed:\\n\\n"
-msgstr "以下命令未被执行:\\n\\n"
-
-#: lib/RT/Record.pm:948
-msgid "The new value has been set."
-msgstr "新的字段值设定完æˆã€‚"
-
-#: lib/RT/ACE_Overlay.pm:110
-msgid "The owner of a ticket"
-msgstr "申请å•çš„承办人"
-
-#: lib/RT/ACE_Overlay.pm:111
-msgid "The requestor of a ticket"
-msgstr "申请å•çš„申请人"
-
-#: html/Admin/Elements/EditUserComments:47
-msgid "These comments aren't generally visible to the user"
-msgstr "该使用者ä¸ä¼šçœ‹è§è¿™äº›è¯„论"
-
-#: NOT FOUND IN SOURCE
-msgid "Third-"
-msgstr "三"
-
-#: lib/RT/CustomField_Overlay.pm:978
-msgid "This custom field does not apply to that object"
-msgstr "此自订字段ä¸é€‚用于该对象"
-
-#: html/Admin/Tools/Configuration.html:50
-msgid "This feature is only available to system administrators"
-msgstr "此项功能仅é™ç³»ç»Ÿç®¡ç†å‘˜ä½¿ç”¨"
-
-#: html/Ticket/Elements/PreviewScrips:96
-msgid "This message will be sent to..."
-msgstr "此讯æ¯ä¼šå¯„ç»™..."
-
-#: NOT FOUND IN SOURCE
-msgid "This ticket %1 %2 (%3)\\n"
-msgstr "ç”³è¯·å• %1 %2 (%3)\\n"
-
-#: bin/rt-crontool:266
-msgid "This tool allows the user to run arbitrary perl modules from within RT."
-msgstr "此工具程åºä¼šè®©ä½¿ç”¨è€…ç»ç”± RT 执行任æ„命令。"
-
-#: lib/RT/Transaction_Overlay.pm:301
-msgid "This transaction appears to have no content"
-msgstr "此项更动报告没有内容"
-
-#: html/Ticket/Elements/ShowRequestor:70
-#. ($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:420
-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 "æ›´æ–°ç”³è¯·å• # %1 的全部信æ¯ï¼š%2"
-
-#: html/Ticket/ModifyAll.html:46 html/Ticket/ModifyAll.html:50
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr "æ›´æ–°ç”³è¯·å• #%1 的全部信æ¯ï¼š%2"
-
-#: html/Approvals/Elements/ShowDependency:67
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "ç”³è¯·å• #%1: %2"
-
-#: lib/RT/Action/CreateTickets.pm:1350 lib/RT/Action/CreateTickets.pm:1359 lib/RT/Action/CreateTickets.pm:605 lib/RT/Action/CreateTickets.pm:729 lib/RT/Action/CreateTickets.pm:741
-#. ($T::Tickets{$template_id}->Id)
-#. ($T::Tickets{$template_id}->id)
-#. ($ticket->Id)
-msgid "Ticket %1"
-msgstr "ç”³è¯·å• %1"
-
-#: lib/RT/Ticket_Overlay.pm:755 lib/RT/Ticket_Overlay.pm:775
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "ç”³è¯·å• #%1 æˆåŠŸæ–°å¢žäºŽ '%2' 表å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket %1 loaded\\n"
-msgstr "åŠ è½½ç”³è¯·å• %1\\n"
-
-#: html/Search/Bulk.html:377
-#. ($Ticket->Id, $_)
-msgid "Ticket %1: %2"
-msgstr "ç”³è¯·å• %1:%2"
-
-#: html/Admin/Elements/QueueTabs:74
-msgid "Ticket Custom Fields"
-msgstr "申请å•çš„自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Due"
-msgstr "表å•å¤„ç†æœŸé™"
-
-#: html/Ticket/History.html:46 html/Ticket/History.html:49
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "申请å•å¤„ç†çºªå½• # %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket ID"
-msgstr "å•å·"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Id"
-msgstr "申请å•ç¼–å·"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Processing Due"
-msgstr "表å•è¿è¡ŒæœŸé™"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "申请å•å·²è§£å†³"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1207
-msgid "Ticket Transactions"
-msgstr "申请å•çš„更动"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Type"
-msgstr "表å•ç§ç±»"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket attachment"
-msgstr "申请å•é™„件"
-
-#: lib/RT/Tickets_Overlay.pm:1920
-msgid "Ticket content"
-msgstr "申请å•å†…容"
-
-#: lib/RT/Tickets_Overlay.pm:1969
-msgid "Ticket content type"
-msgstr "申请å•å†…容类别"
-
-#: lib/RT/Ticket_Overlay.pm:603 lib/RT/Ticket_Overlay.pm:617 lib/RT/Ticket_Overlay.pm:628 lib/RT/Ticket_Overlay.pm:763
-msgid "Ticket could not be created due to an internal error"
-msgstr "内部错误,无法新增申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket created"
-msgstr "申请å•æ–°å¢žå®Œæ¯•"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket creation failed"
-msgstr "申请å•æ–°å¢žå¤±è´¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket deleted"
-msgstr "申请å•åˆ é™¤å®Œæ¯•"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket id not found"
-msgstr "找ä¸åˆ°ç”³è¯·å•ç¼–å·"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket killed"
-msgstr "申请å•åˆ é™¤å®Œæ¯•"
-
-#: html/Ticket/Display.html:55
-msgid "Ticket metadata"
-msgstr "申请å•çš„æè¿°ä¿¡æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket not found"
-msgstr "找ä¸åˆ°ç”³è¯·å•"
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "申请å•çŽ°å†µå·²æ”¹å˜"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket watchers"
-msgstr "申请å•è§†å¯Ÿå‘˜"
-
-#: lib/RT/Search/FromSQL.pm:82
-#. (ref $self)
-msgid "TicketSQL search module"
-msgstr "TicketSQL 查询模å—"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:71 html/Search/Elements/Chart:109 lib/RT/CustomField_Overlay.pm:1206
-msgid "Tickets"
-msgstr "申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 %2"
-msgstr "ç”³è¯·å• %1 %2"
-
-#: NOT FOUND IN SOURCE
-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 "é€å‡ºçš„申请å•"
-
-msgid "CreatedBy"
-msgstr "建立人"
-
-#: html/Tools/Reports/CreatedByDates.html:86
-msgid "Tickets created after"
-msgstr "申请å•å»ºç«‹èµ·å§‹æ—¥"
-
-#: html/Tools/Reports/CreatedByDates.html:88
-msgid "Tickets created before"
-msgstr "申请å•å»ºç«‹æˆªæ­¢æ—¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets from %1"
-msgstr "%1 的申请å•"
-
-#: html/Tools/Reports/ResolvedByDates.html:87
-msgid "Tickets resolved after"
-msgstr "申请å•è§£å†³èµ·å§‹æ—¥"
-
-#: html/Tools/Reports/ResolvedByDates.html:89
-msgid "Tickets resolved before"
-msgstr "申请å•è§£å†³æˆªæ­¢æ—¥"
-
-#: html/Approvals/Elements/ShowDependency:48
-msgid "Tickets which depend on this approval:"
-msgstr "批准之åŽï¼Œå¯æŽ¥ç»­å¤„ç†ï¼š"
-
-#: html/Search/Elements/PickBasics:134 html/Ticket/Create.html:183 html/Ticket/Elements/EditBasics:72
-msgid "Time Estimated"
-msgstr "预计时间"
-
-msgid "TimeEstimated"
-msgstr "预计时间"
-
-#: html/Search/Elements/PickBasics:135 html/Ticket/Create.html:196 html/Ticket/Elements/EditBasics:85
-msgid "Time Left"
-msgstr "剩馀时间"
-
-#: html/Search/Elements/PickBasics:133 html/Ticket/Create.html:189 html/Ticket/Elements/EditBasics:78
-msgid "Time Worked"
-msgstr "处ç†æ—¶é—´"
-
-#: lib/RT/Tickets_Overlay.pm:1891
-msgid "Time left"
-msgstr "剩馀时间"
-
-#: html/Elements/Footer:51
-msgid "Time to display"
-msgstr "显示时间"
-
-#: lib/RT/Tickets_Overlay.pm:1866
-msgid "Time worked"
-msgstr "已处ç†æ—¶é—´"
-
-#: NOT FOUND IN SOURCE
-msgid "TimeLeft"
-msgstr "剩馀时间"
-
-#: lib/RT/Ticket_Overlay.pm:1167
-msgid "TimeWorked"
-msgstr "已处ç†æ—¶é—´"
-
-#: html/Search/Elements/EditFormat:74
-msgid "Title"
-msgstr "标题"
-
-msgid "QueueName"
-msgstr "表å•å称"
-
-msgid "OwnerName"
-msgstr "承办人å称"
-
-msgid "<blank>"
-msgstr "<留空>"
-
-msgid "NEWLINE"
-msgstr "(æ¢åˆ—)"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:"
-msgstr "产生这次更动的差异档:"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:\\n"
-msgstr "产生这次更动的差异档:\\n"
-
-#: html/Elements/Footer:62
-#. ('<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>')
-msgid "To inquire about support, training, custom development or licensing, please contact %1."
-msgstr "如果有支æŒã€æ•™è‚²è®­ç»ƒåŠå®šåˆ¶å¼€å‘的需è¦ï¼Œè¯·è¿žç»œ %1。"
-
-#: NOT FOUND IN SOURCE
-msgid "Todo"
-msgstr "待办事项"
-
-#: lib/RT/Ticket_Overlay.pm:1170
-msgid "Told"
-msgstr "告知日"
-
-#: html/Admin/Elements/Tabs:68 html/Admin/index.html:88 html/Elements/Tabs:74 html/Tools/index.html:46 html/Tools/index.html:49
-msgid "Tools"
-msgstr "工具"
-
-#: html/Search/Elements/Chart:130
-msgid "Total"
-msgstr "页"
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "更动"
-
-#: lib/RT/Transaction_Overlay.pm:805
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr "清除更动报告 %1"
-
-#: lib/RT/Transaction_Overlay.pm:183
-msgid "Transaction Created"
-msgstr "更动报告已新增"
-
-#: html/Admin/Elements/QueueTabs:78
-msgid "Transaction Custom Fields"
-msgstr "更动的自订字段"
-
-#: NOT FOUND IN SOURCE
-msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
-msgstr "未指定申请å•ç¼–å·ï¼Œæ— æ³•æ–°å¢žæ›´åŠ¨"
-
-#: lib/RT/Transaction_Overlay.pm:128
-msgid "Transaction->Create couldn't, as you didn't specify an object type and id"
-msgstr "未指定对象类别åŠç¼–å·ï¼Œæ— æ³•æ–°å¢žæ›´åŠ¨"
-
-#: NOT FOUND IN SOURCE
-msgid "TransactionBatch"
-msgstr "批次更动时"
-
-#: NOT FOUND IN SOURCE
-msgid "TransactionCreate"
-msgstr "新增更动时"
-
-#: lib/RT/Transaction_Overlay.pm:870
-msgid "Transactions are immutable"
-msgstr "ä¸å¯æ›´æ”¹æ›´åŠ¨æŠ¥å‘Š"
-
-#: NOT FOUND IN SOURCE
-msgid "Transfer to"
-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:418
-msgid "Tue."
-msgstr "星期二"
-
-#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1168 lib/RT/Tickets_Overlay.pm:1705
-msgid "Type"
-msgstr "类别"
-
-#: lib/RT/ScripCondition_Overlay.pm:128
-msgid "Unimplemented"
-msgstr "尚无实作"
-
-#: html/Admin/Users/Modify.html:89
-msgid "Unix login"
-msgstr "外部系统登入å¸å·"
-
-#: NOT FOUND IN SOURCE
-msgid "UnixUsername"
-msgstr "外部系统登入å¸å·"
-
-#: lib/RT/Attachment_Overlay.pm:289 lib/RT/Record.pm:861
-#. ($self->ContentEncoding)
-#. ($ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr "ä¸å¯è§£çš„内容文字编ç æ–¹å¼ %1"
-
-#: html/Search/Build.html:455 lib/RT/Report/Tickets.pm:410
-msgid "Unknown field: %1"
-msgstr "未知的字段:%1"
-
-#: html/Elements/SelectResultsPerPage:58
-msgid "Unlimited"
-msgstr "全数显示"
-
-#: html/Search/Elements/SelectSearchesForObjects:64
-msgid "Unnamed search"
-msgstr "未命å的查询"
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr "éžå†…部æˆå‘˜"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Unselected Custom Fields"
-msgstr "未选å–的自订字段"
-
-#: html/Admin/CustomFields/Objects.html:61
-msgid "Unselected objects"
-msgstr "未选å–的对象"
-
-#: lib/RT/Transaction_Overlay.pm:659
-msgid "Untaken"
-msgstr "未被å—ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Untitled search"
-msgstr "未命å的查询"
-
-#: NOT FOUND IN SOURCE
-msgid "Up"
-msgstr "上一页"
-
-#: html/Admin/Elements/EditScrip:128 html/Elements/RT__Ticket/ColumnMap:302 html/Search/Bulk.html:193 html/Search/Bulk.html:75
-msgid "Update"
-msgstr "处ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Update All"
-msgstr "全部更新"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ID"
-msgstr "æ›´æ–°ç¼–å·"
-
-#: html/Ticket/Update.html:135
-msgid "Update Ticket"
-msgstr "更新申请å•"
-
-#: html/Search/Bulk.html:126 html/Ticket/ModifyAll.html:87 html/Ticket/Update.html:72
-msgid "Update Type"
-msgstr "更新类别"
-
-#: NOT FOUND IN SOURCE
-msgid "Update all these tickets at once"
-msgstr "整批更新申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Update email"
-msgstr "更新电å­é‚®ä»¶ä¿¡ç®±"
-
-#: html/Search/Bulk.html:200 html/Search/Results.html:78
-msgid "Update multiple tickets"
-msgstr "批次更新申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Update name"
-msgstr "æ›´æ–°å¸å·"
-
-#: lib/RT/Action/CreateTickets.pm:750 lib/RT/Interface/Web.pm:584
-msgid "Update not recorded."
-msgstr "更新未被记录"
-
-#: NOT FOUND IN SOURCE
-msgid "Update selected tickets"
-msgstr "更新选择的申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Update signature"
-msgstr "更新签章"
-
-#: html/Ticket/ModifyAll.html:84
-msgid "Update ticket"
-msgstr "更新申请å•"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ticket # %1"
-msgstr "æ›´æ–°ç”³è¯·å• # %1"
-
-#: html/SelfService/Update.html:112 html/SelfService/Update.html:47
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "æ›´æ–°ç”³è¯·å• #%1"
-
-#: html/Ticket/Update.html:158
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "æ›´æ–°ç”³è¯·å• #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:748 lib/RT/Interface/Web.pm:583
-msgid "Update type was neither correspondence nor comment."
-msgstr "更新的内容并éžç”³è¯·å•å›žå¤ä¹Ÿä¸æ˜¯è¯„论"
-
-#: html/Elements/SelectDateType:54 html/Ticket/Elements/ShowDates:72 lib/RT/CustomField_Overlay.pm:1284 lib/RT/Ticket_Overlay.pm:1171
-msgid "Updated"
-msgstr "å‰æ¬¡æ›´æ–°"
-
-#: html/Tools/Offline.html:93
-msgid "Upload"
-msgstr "上载"
-
-#: lib/RT/CustomField_Overlay.pm:84
-msgid "Upload multiple files"
-msgstr "上载多个档案"
-
-#: lib/RT/CustomField_Overlay.pm:79
-msgid "Upload multiple images"
-msgstr "上载多份图片"
-
-#: lib/RT/CustomField_Overlay.pm:85
-msgid "Upload one file"
-msgstr "上载一个档案"
-
-#: lib/RT/CustomField_Overlay.pm:80
-msgid "Upload one image"
-msgstr "上载一份图片"
-
-#: lib/RT/CustomField_Overlay.pm:86
-msgid "Upload up to %1 files"
-msgstr "上载最多 %1 个档案"
-
-#: lib/RT/CustomField_Overlay.pm:81
-msgid "Upload up to %1 images"
-msgstr "上载最多 %1 份图片"
-
-#: html/Tools/Offline.html:93
-msgid "Upload your changes"
-msgstr "上载您的更动"
-
-#: html/Admin/index.html:90
-msgid "Use other RT administrative tools"
-msgstr "使用其它的 RT 管ç†å·¥å…·"
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: lib/RT/Ticket_Overlay.pm:506
-#. ($args{'Owner'})
-msgid "User '%1' could not be found."
-msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'。"
-
-#: 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:132 etc/initialdata:206
-msgid "User Defined"
-msgstr "使用者自订"
-
-#: html/Admin/Elements/EditScrip:93
-msgid "User Defined conditions and actions"
-msgstr "使用者自订的æ¡ä»¶åŠåŠ¨ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "User ID"
-msgstr "使用者 ID"
-
-#: NOT FOUND IN SOURCE
-msgid "User Id"
-msgstr "使用者 ID"
-
-#: NOT FOUND IN SOURCE
-msgid "User Number"
-msgstr "员工编å·"
-
-#: html/Admin/Elements/CustomFieldTabs:72 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:85 html/Admin/Elements/SystemTabs:68 html/Admin/Global/index.html:80
-msgid "User Rights"
-msgstr "使用者æƒé™"
-
-#: NOT FOUND IN SOURCE
-msgid "User Setup"
-msgstr "使用者设定"
-
-#: NOT FOUND IN SOURCE
-msgid "User Shift"
-msgstr "员工ç­åˆ«"
-
-#: NOT FOUND IN SOURCE
-msgid "User asked for an unknown update type for custom field %1 for %2 object #%3"
-msgstr "使用者试图在 %2 对象 #%3 的自订字段 %1 上执行未知的更新æ“作"
-
-#: html/Admin/Users/Modify.html:301
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "无法新增使用者:%1"
-
-#: lib/RT/User_Overlay.pm:330
-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/CustomFields/GroupRights.html:74 html/Admin/Global/GroupRights.html:88 html/Admin/Groups/GroupRights.html:75 html/Admin/Queues/GroupRights.html:90
-msgid "User defined groups"
-msgstr "使用者定义的群组"
-
-#: lib/RT/User_Overlay.pm:592 lib/RT/User_Overlay.pm:612
-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"
-
-#: NOT FOUND IN SOURCE
-msgid "User view"
-msgstr "使用者ç§äººæ•°æ®"
-
-#: html/Admin/Groups/index.html:103
-msgid "User-defined groups"
-msgstr "使用者自定群组"
-
-#: NOT FOUND IN SOURCE
-msgid "UserDefined"
-msgstr "使用者自定"
-
-#: html/Admin/Users/Modify.html:69 html/Elements/Login:90 html/Ticket/Elements/AddWatchers:56
-msgid "Username"
-msgstr "å¸å·"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:55 html/Admin/Elements/SelectNewGroupMembers:47 html/Admin/Elements/Tabs:53 html/Admin/Global/CustomFields/index.html:64 html/Admin/Groups/Members.html:76 html/Admin/Queues/People.html:89 html/Admin/index.html:62 html/User/Groups/Members.html:79 lib/RT/CustomField_Overlay.pm:1208
-msgid "Users"
-msgstr "使用者"
-
-#: html/Admin/Users/index.html:85
-msgid "Users matching search criteria"
-msgstr "符åˆæŸ¥è¯¢æ¡ä»¶çš„使用者"
-
-#: bin/rt-crontool:134
-#. ($transaction->id)
-msgid "Using transaction #%1..."
-msgstr "使用更动 #%1..."
-
-#: lib/RT/Tickets_Overlay_SQL.pm:528
-msgid "Valid Query"
-msgstr "åˆç†çš„查询"
-
-#: html/Admin/CustomFields/Modify.html:80
-msgid "Validation"
-msgstr "验è¯"
-
-#: NOT FOUND IN SOURCE
-msgid "ValueOfQueue"
-msgstr "选择表å•"
-
-#: html/Admin/CustomFields/Modify.html:130 html/Admin/Elements/EditCustomField:78
-msgid "Values"
-msgstr "字段值"
-
-#: NOT FOUND IN SOURCE
-msgid "View log"
-msgstr "检视纪录档"
-
-#: lib/RT/Queue_Overlay.pm:107
-msgid "Watch"
-msgstr "视察"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "WatchAsAdminCc"
-msgstr "以管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äººèº«ä»½è§†å¯Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Watcher loaded"
-msgstr "æˆåŠŸåŠ è½½è§†å¯Ÿå‘˜ä¿¡æ¯"
-
-#: html/Admin/Elements/QueueTabs:63
-msgid "Watchers"
-msgstr "视察员"
-
-#: NOT FOUND IN SOURCE
-msgid "WebEncoding"
-msgstr "网页文字编ç æ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Wed"
-msgstr "星期三"
-
-#: lib/RT/Date.pm:419
-msgid "Wed."
-msgstr "星期三"
-
-#: html/Tools/MyDay.html:75
-msgid "What I did today"
-msgstr "今日工作一览"
-
-#: etc/initialdata:521
-msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
-msgstr "当申请å•é€šè¿‡æ‰€æœ‰ç­¾æ ¸åŽï¼Œå°†æ­¤è®¯æ¯å›žå¤åˆ°åŽŸç”³è¯·å•"
-
-#: etc/initialdata:485
-msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
-msgstr "当申请å•é€šè¿‡æŸé¡¹ç­¾æ ¸åŽï¼Œå°†æ­¤è®¯æ¯å›žå¤åˆ°åŽŸç”³è¯·å•"
-
-#: etc/initialdata:146
-msgid "When a ticket is created"
-msgstr "新增申请å•æ—¶"
-
-#: etc/initialdata:418
-msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
-msgstr "签核å•æ–°å¢žä¹‹åŽï¼Œé€šçŸ¥åº”å—ç†çš„承办人åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:151
-msgid "When anything happens"
-msgstr "当任何事情å‘生时"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "当申请å•è§£å†³æ—¶"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr "当申请å•æ›´æ¢æ‰¿åŠžäººæ—¶"
-
-#: etc/initialdata:178 etc/upgrade/3.1.17/content:16
-msgid "Whenever a ticket's priority changes"
-msgstr "当申请å•çš„优先顺åºæ”¹å˜æ—¶"
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr "当申请å•æ›´æ¢è¡¨å•æ—¶"
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr "当申请å•æ›´æ–°çŽ°å†µæ—¶"
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr "当使用者自订的情况å‘生时"
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr "当评论é€è¾¾æ—¶"
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr "当回å¤é€è¾¾æ—¶"
-
-#: html/Admin/Users/Modify.html:188 html/User/Prefs.html:88
-msgid "Work"
-msgstr "å…¬å¸"
-
-#: html/Search/Results.html:82
-msgid "Work offline"
-msgstr "离线工作"
-
-#: NOT FOUND IN SOURCE
-msgid "WorkPhone"
-msgstr "å…¬å¸ç”µè¯"
-
-#: html/Ticket/Elements/ShowBasics:63 html/Ticket/Update.html:64
-msgid "Worked"
-msgstr "处ç†æ—¶é—´"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow #%1"
-msgstr "æµç¨‹ #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow Begin"
-msgstr "æµç¨‹å¼€å§‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow End"
-msgstr "æµç¨‹ç»“æŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow deleted"
-msgstr "æµç¨‹å·²åˆ é™¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflows"
-msgstr "æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Writable"
-msgstr "å¯è¯»å†™"
-
-#: NOT FOUND IN SOURCE
-msgid "XXX CHANGEME You are not an authorized user"
-msgstr "XXX CHANGEME 您是未ç»æŽˆæƒçš„使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "Yes"
-msgstr "是"
-
-#: lib/RT/Ticket_Overlay.pm:3140
-msgid "You already own this ticket"
-msgstr "您已是这份申请å•çš„承办人"
-
-#: html/autohandler:214 html/autohandler:222
-msgid "You are not an authorized user"
-msgstr "您ä¸æ˜¯è¢«æŽˆæƒçš„使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "You can access it with the Download button on the right."
-msgstr "您å¯ä»¥æŒ‰å³æ–¹çš„‘下载’键æ¥å–得。"
-
-#: html/Prefs/Search.html:56
-msgid "You can also edit the predefined search itself"
-msgstr "您也å¯ä»¥ç›´æŽ¥ç¼–辑预先定义的æœå¯»æ–¹å¼"
-
-#: lib/RT/Ticket_Overlay.pm:3025
-msgid "You can only reassign tickets that you own or that are unowned"
-msgstr "祇能é‡æ–°æŒ‡æ´¾æ‚¨æ‰€æ‰¿åŠžæˆ–是没有承办人的申请å•"
-
-#: lib/RT/Ticket_Overlay.pm:3021
-msgid "You can only take tickets 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:780
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "æ‚¨ä¼šåœ¨è¡¨å• %2 找到 %1 的申请å•"
-
-#: html/NoAuth/Logout.html:52
-msgid "You have been logged out of RT."
-msgstr "您已注销 RT。"
-
-#: html/SelfService/Display.html:133
-msgid "You have no permission to create tickets in that queue."
-msgstr "您没有在该表å•æ–°å¢žç”³è¯·å•çš„æƒé™ã€‚"
-
-#: lib/RT/Ticket_Overlay.pm:2003
-msgid "You may not create requests in that queue."
-msgstr "您ä¸èƒ½åœ¨è¯¥è¡¨å•ä¸­æ出申请。"
-
-#: NOT FOUND IN SOURCE
-msgid "You need to restart the Request Tracker service for saved changes to take effect."
-msgstr "您必须é‡æ–°æ¿€æ´» Request Tracker æœåŠ¡ï¼Œå‚¨å­˜çš„更动æ‰ä¼šç”Ÿæ•ˆã€‚"
-
-#: html/NoAuth/Logout.html:56
-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:502
-msgid "Your request has been approved by %1. Other approvals may still be pending."
-msgstr "申请å•å·²ç”± %1 批准。å¯èƒ½è¿˜æœ‰å…¶å®ƒå¾…签核的步骤。"
-
-#: etc/initialdata:540
-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:445
-msgid "Your request was rejected."
-msgstr "您的申请å•å·²è¢«é©³å›žã€‚"
-
-#: html/autohandler:251
-msgid "Your username or password is incorrect"
-msgstr "您的å¸å·æˆ–å£ä»¤æœ‰è¯¯"
-
-#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:149
-msgid "Zip"
-msgstr "邮政编ç "
-
-#: NOT FOUND IN SOURCE
-msgid "[no subject]"
-msgstr "[没有标题]"
-
-msgid "[none]"
-msgstr "[æ— ]"
-
-#: NOT FOUND IN SOURCE
-msgid "ago"
-msgstr "过期"
-
-#: NOT FOUND IN SOURCE
-msgid "alert"
-msgstr "急讯"
-
-#: lib/RT/System.pm:87
-msgid "allow creation of saved searches"
-msgstr "å…许建立预存查询"
-
-#: lib/RT/System.pm:86
-msgid "allow loading of saved searches"
-msgstr "å…许加载预存查询"
-
-#: NOT FOUND IN SOURCE
-msgid "approving"
-msgstr "待签核"
-
-#: html/User/Elements/DelegateRights:80
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr "æƒé™åŒ %1"
-
-#: html/Search/Results.html:83
-msgid "chart"
-msgstr "图表"
-
-#: html/SelfService/Closed.html:49
-msgid "closed"
-msgstr "已解决"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:55
-msgid "contains"
-msgstr "包å«"
-
-#: NOT FOUND IN SOURCE
-msgid "content"
-msgstr "内容"
-
-#: NOT FOUND IN SOURCE
-msgid "content-type"
-msgstr "类型"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence (probably) not sent"
-msgstr "申请å•å›žå¤(å¯èƒ½)未é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence sent"
-msgstr "申请å•å›žå¤å·²é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "critical"
-msgstr "严é‡"
-
-#: html/Admin/Queues/Modify.html:98 lib/RT/Date.pm:346
-msgid "days"
-msgstr "天"
-
-#: NOT FOUND IN SOURCE
-msgid "dead"
-msgstr "æ‹’ç»å¤„ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "debug"
-msgstr "侦错"
-
-#: NOT FOUND IN SOURCE
-msgid "delete"
-msgstr "删除"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "deleted"
-msgstr "已删除"
-
-#: html/Search/Elements/PickBasics:61
-msgid "does not match"
-msgstr "ä¸ç¬¦åˆ"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:56
-msgid "doesn't contain"
-msgstr "ä¸åŒ…å«"
-
-#: NOT FOUND IN SOURCE
-msgid "email address"
-msgstr "电å­é‚®ä»¶ä¿¡ç®±"
-
-#: NOT FOUND IN SOURCE
-msgid "emergency"
-msgstr "å±éš¾"
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "equal to"
-msgstr "等于"
-
-#: NOT FOUND IN SOURCE
-msgid "error"
-msgstr "错误"
-
-#: html/Search/Build.html:547
-msgid "error: can't move down"
-msgstr "错误:无法下移"
-
-#: html/Search/Build.html:569
-msgid "error: can't move left"
-msgstr "错误:无法左移"
-
-#: html/Search/Build.html:528
-msgid "error: can't move up"
-msgstr "错误:无法上移"
-
-#: html/Search/Build.html:612
-msgid "error: nothing to delete"
-msgstr "错误:没有å¯åˆ é™¤çš„对象"
-
-#: html/Search/Build.html:533 html/Search/Build.html:552 html/Search/Build.html:574 html/Search/Build.html:603
-msgid "error: nothing to move"
-msgstr "错误:没有å¯ç§»åŠ¨çš„对象"
-
-#: html/Search/Build.html:630
-msgid "error: nothing to toggle"
-msgstr "错误:没有å¯åˆ‡æ¢çš„对象"
-
-#: NOT FOUND IN SOURCE
-msgid "false"
-msgstr "å‡"
-
-#: NOT FOUND IN SOURCE
-msgid "filename"
-msgstr "æ¡£å"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "greater than"
-msgstr "大于"
-
-#: lib/RT/Group_Overlay.pm:214
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "群组 '%1'"
-
-#: html/Search/Results.html:88
-#. ($m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query))
-msgid "grouped by %1"
-msgstr "ä¾ %1 分组"
-
-#: lib/RT/Date.pm:342
-msgid "hours"
-msgstr "å°æ—¶"
-
-#: html/Search/Elements/PickBasics:48
-msgid "id"
-msgstr "ç¼–å·"
-
-#: NOT FOUND IN SOURCE
-msgid "info"
-msgstr "ä¿¡æ¯"
-
-#: html/Elements/SelectBoolean:53 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:57 html/Search/Elements/PickBasics:162 html/Search/Elements/PickBasics:74 html/Search/Elements/PickBasics:90 html/Search/Elements/PickCFs:53
-msgid "is"
-msgstr "是"
-
-#: html/Elements/SelectBoolean:57 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:58 html/Search/Elements/PickBasics:163 html/Search/Elements/PickBasics:75 html/Search/Elements/PickBasics:91 html/Search/Elements/PickCFs:54
-msgid "isn't"
-msgstr "ä¸æ˜¯"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "less than"
-msgstr "å°äºŽ"
-
-#: NOT FOUND IN SOURCE
-msgid "level Admin"
-msgstr "层主管"
-
-#: html/Search/Elements/PickBasics:60
-msgid "matches"
-msgstr "符åˆ"
-
-#: lib/RT/Date.pm:338
-msgid "min"
-msgstr "分"
-
-#: NOT FOUND IN SOURCE
-msgid "minutes"
-msgstr "分钟"
-
-#: NOT FOUND IN SOURCE
-msgid "modifications\\n\\n"
-msgstr "更改\\n\\n"
-
-#: lib/RT/Date.pm:354
-msgid "months"
-msgstr "月"
-
-#: lib/RT/Queue_Overlay.pm:82
-msgid "new"
-msgstr "新建立"
-
-#: html/Admin/Elements/PickCustomFields:64 html/Admin/Elements/PickObjects:65
-msgid "no name"
-msgstr "没有å称"
-
-#: html/Admin/Elements/EditScrips:64
-msgid "no value"
-msgstr "没有值"
-
-#: html/Admin/Elements/EditQueueWatchers:48 html/Ticket/Elements/EditWatchers:49
-msgid "none"
-msgstr "æ— "
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "not equal to"
-msgstr "ä¸ç­‰äºŽ"
-
-#: NOT FOUND IN SOURCE
-msgid "notice"
-msgstr "æ示"
-
-#: NOT FOUND IN SOURCE
-msgid "notlike"
-msgstr "ä¸ç¬¦åˆ"
-
-#: NOT FOUND IN SOURCE
-msgid "number"
-msgstr "å·"
-
-#: html/SelfService/Elements/MyRequests:82 lib/RT/Queue_Overlay.pm:83
-msgid "open"
-msgstr "å¼€å¯"
-
-#: NOT FOUND IN SOURCE
-msgid "opened"
-msgstr "已开å¯"
-
-#: lib/RT/Group_Overlay.pm:219
-#. ($self->Name, $user->Name)
-msgid "personal group '%1' for user '%2'"
-msgstr "使用者‘%2’的‘%1’代ç†äººç¾¤ç»„"
-
-#: lib/RT/Group_Overlay.pm:227
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "è¡¨å• %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "rejected"
-msgstr "已驳回"
-
-#: lib/RT/Queue_Overlay.pm:85
-msgid "resolved"
-msgstr "已处ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "rtname"
-msgstr "æœåŠ¡å™¨å称"
-
-#: lib/RT/Date.pm:334
-msgid "sec"
-msgstr "秒"
-
-#: lib/RT/System.pm:85
-msgid "show Configuration tab"
-msgstr "显示设定页签"
-
-#: html/Search/Results.html:80
-msgid "spreadsheet"
-msgstr "电å­è¡¨æ ¼"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "stalled"
-msgstr "延宕"
-
-#: html/Search/Results.html:89
-#. ($m->scomp('Elements/SelectChartType', Name => 'ChartStyle'))
-msgid "style: %1"
-msgstr "æ ·å¼ï¼š%1"
-
-#: html/Prefs/MyRT.html:93
-msgid "summary rows"
-msgstr "加总列"
-
-#: lib/RT/Group_Overlay.pm:222
-#. ($self->Type)
-msgid "system %1"
-msgstr "系统 %1"
-
-#: lib/RT/Group_Overlay.pm:233
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr "系统群组 '%1'"
-
-#: html/Elements/Error:64 html/SelfService/Error.html:63
-msgid "the calling component did not specify why"
-msgstr "呼å«ç»„件未指明原因"
-
-#: NOT FOUND IN SOURCE
-msgid "ticket #%1"
-msgstr "ç”³è¯·å• #%1"
-
-#: lib/RT/Group_Overlay.pm:230
-#. ($self->Instance, $self->Type)
-msgid "ticket #%1 %2"
-msgstr "ç”³è¯·å• #%1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "till"
-msgstr "至"
-
-#: NOT FOUND IN SOURCE
-msgid "to"
-msgstr "到"
-
-#: NOT FOUND IN SOURCE
-msgid "true"
-msgstr "真"
-
-#: lib/RT/Group_Overlay.pm:236
-#. ($self->Id)
-msgid "undescribed group %1"
-msgstr "没有æ述的群组 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "unresolved"
-msgstr "未处ç†"
-
-#: lib/RT/Group_Overlay.pm:211
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "使用者 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "warning"
-msgstr "警告"
-
-#: lib/RT/Date.pm:350
-msgid "weeks"
-msgstr "周"
-
-#: NOT FOUND IN SOURCE
-msgid "with template %1"
-msgstr "模æ¿ï¼š%1"
-
-#: lib/RT/Date.pm:358
-msgid "years"
-msgstr "å¹´"
-
-msgid "Press 'Esc' to close this window."
-msgstr "按 'Esc' é”®å¯å…³é—­æœ¬çª—å£ã€‚"
-
-msgid "HasMember"
-msgstr "拥有æˆå‘˜"
-
-msgid "LinkedTo"
-msgstr "连结至"
-
-msgid "Watcher"
-msgstr "视察员"
-
-msgid "(displaying new and open tickets for %1)"
-msgstr "(显示 %1 å下新建立åŠå¼€å¯ä¸­çš„申请å•)"
diff --git a/rt/lib/RT/I18N/zh_tw.po b/rt/lib/RT/I18N/zh_tw.po
deleted file mode 100644
index 7552a6b..0000000
--- a/rt/lib/RT/I18N/zh_tw.po
+++ /dev/null
@@ -1,8360 +0,0 @@
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: RT 3.6.x\n"
-"PO-Revision-Date: 2007-12-09 13:05+0800\n"
-"Last-Translator: Audrey Tang <cpan@audreyt.org>\n"
-"Language-Team: rt-devel <rt-devel@lists.bestpractical.com>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: html/Widgets/SavedSearch:70
-#. ($self->{CurrentSearch}{Object}->Description)
-msgid " %1 deleted."
-msgstr ""
-
-#: html/Widgets/SavedSearch:47
-#. ($self->{CurrentSearch}{Description}, $args->{Description})
-msgid " %1 renamed to %2."
-msgstr ""
-
-#: html/Widgets/SavedSearch:60
-#. ($args->{Description})
-msgid " %1 saved."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "#"
-msgstr "#"
-
-#: NOT FOUND IN SOURCE
-msgid "#%1"
-msgstr "#%1"
-
-#: html/Approvals/Elements/Approve:48 html/Approvals/Elements/ShowDependency:71 html/SelfService/Display.html:46 html/Ticket/Display.html:47 html/Ticket/Display.html:51
-#. ($Ticket->id, $Ticket->Subject)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-#. ($ticket->Id, $ticket->Subject)
-#. ($TicketObj->Id, $TicketObj->Subject)
-msgid "#%1: %2"
-msgstr "#%1: %2"
-
-#: html/Elements/ShowSearch:105
-msgid "$1"
-msgstr "$1"
-
-#: lib/RT/Record.pm:940
-#. ($label)
-msgid "$prefix %1"
-msgstr "$prefix %1"
-
-#: 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/URI/fsck_com_rt.pm:256
-#. ($self->ObjectType, $self->Object->Id)
-msgid "%1 #%2"
-msgstr "%1 #%2"
-
-#: lib/RT/Date.pm:365
-#. ($s, $time_unit)
-msgid "%1 %2"
-msgstr "%1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 %2 %3"
-msgstr "%1 %2 %3"
-
-#: lib/RT/Date.pm:401
-#. ($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/Record.pm:1685 lib/RT/Transaction_Overlay.pm:647 lib/RT/Transaction_Overlay.pm:690
-#. ($cf->Name, $new_value->Content)
-#. ($field, $self->NewValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 added"
-msgstr "%2 已新增為 %1"
-
-#: lib/RT/Date.pm:362
-#. ($s, $time_unit)
-msgid "%1 %2 ago"
-msgstr "%1 %2 之å‰"
-
-#: lib/RT/Record.pm:1692 lib/RT/Transaction_Overlay.pm:654
-#. ($cf->Name, $old_content, $new_value->Content)
-#. ($field, $self->OldValue, $self->NewValue)
-msgid "%1 %2 changed to %3"
-msgstr "%1 已從 %2 改為 %3"
-
-#: lib/RT/Record.pm:1689 lib/RT/Transaction_Overlay.pm:650 lib/RT/Transaction_Overlay.pm:696
-#. ($cf->Name, $old_value->Content)
-#. ($field, $self->OldValue)
-#. ($self->Field, $principal->Object->Name)
-msgid "%1 %2 deleted"
-msgstr "%2 已自 %1 刪除"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 %2 of group %3"
-msgstr "%3 群組的 %1 %2"
-
-#: html/Admin/Elements/EditScrips:65 html/Admin/Elements/ListGlobalScrips:63 html/Ticket/Elements/PreviewScrips:103
-#. (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/Ticket/Elements/ShowAttachments:72
-#. ($rev->CreatedAsString, $size, $rev->CreatorObj->Name)
-msgid "%1 (%2) by %3"
-msgstr "%1 (%2) - %3"
-
-#: html/SelfService/Update.html:60 html/Ticket/Elements/EditBasics:108 html/Ticket/Update.html:61 html/Ticket/Update.html:63 html/Tools/MyDay.html:66
-#. (loc($DefaultStatus))
-#. (loc($Ticket->Status()))
-#. (loc($TicketObj->Status))
-#. ($TicketObj->OwnerObj->Name())
-msgid "%1 (Unchanged)"
-msgstr "%1 (未更改)"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 - %2 shown"
-msgstr "顯示第 %1 - %2 筆"
-
-#: bin/rt-crontool:237 bin/rt-crontool:244 bin/rt-crontool:250
-#. ("--search-argument", "--search")
-#. ("--condition-argument", "--condition")
-#. ("--action-argument", "--action")
-msgid "%1 - An argument to pass to %2"
-msgstr "%1 - 傳éžçµ¦ %2 的一個åƒæ•¸"
-
-#: bin/rt-crontool:262
-#. ("--verbose")
-msgid "%1 - Output status updates to STDOUT"
-msgstr "%1 - 將更新狀態輸出到 STDOUT"
-
-#: bin/rt-crontool:253
-#. ("--template-id")
-msgid "%1 - Specify id of the template you want to use"
-msgstr "%1 - 指定欲使用的範本編號"
-
-#: bin/rt-crontool:256
-#. ("--transaction")
-msgid "%1 - Specify if you want to use either 'first' or 'last' tarnsaction"
-msgstr "%1 - 指定欲使用的更動為 'first' (第一項) 或 'last' (最後一項)"
-
-#: bin/rt-crontool:247
-#. ("--action")
-msgid "%1 - Specify the action module you want to use"
-msgstr "%1 - 指定欲使用的動作模組"
-
-#: bin/rt-crontool:241
-#. ("--condition")
-msgid "%1 - Specify the condition module you want to use"
-msgstr "%1 - 指定欲使用的æ¢ä»¶æ¨¡çµ„"
-
-#: bin/rt-crontool:234
-#. ("--search")
-msgid "%1 - Specify the search module you want to use"
-msgstr "%1 - 指定欲使用的查詢模組"
-
-#: bin/rt-crontool:259
-#. ("--transaction-type")
-msgid "%1 - Specify the type of a transaction you want to use"
-msgstr "%1 - 指定欲使用的更動類別"
-
-#: html/Elements/Footer:56
-#. ('&#187;&#124;&#171;', $RT::VERSION, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>',)
-msgid "%1 RT %2 Copyright 1996-%3 %4."
-msgstr "%1 RT %2 版,%4 版權所有,1996-%3。"
-
-#: lib/RT/ScripAction_Overlay.pm:150
-#. ($self->Id)
-msgid "%1 ScripAction loaded"
-msgstr "載入手續 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 Total"
-msgstr "å…± %1 ç­†"
-
-#: lib/RT/Record.pm:1722
-#. ($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:144 lib/RT/Link_Overlay.pm:151
-#. ($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:73 lib/RT/Transaction_Overlay.pm:531
-#. ($self->BriefDescription , $self->CreatorObj->Name)
-#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
-msgid "%1 by %2"
-msgstr "%1 (%2)"
-
-#: lib/RT/Transaction_Overlay.pm:788 lib/RT/Transaction_Overlay.pm:797 lib/RT/Transaction_Overlay.pm:800
-#. ($self->Field , $q1->Name , $q2->Name)
-#. ($self->Field, $t2->AsString, $t1->AsString)
-#. ($self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'")
-msgid "%1 changed from %2 to %3"
-msgstr "%1 的值從 %2 改為 %3"
-
-#: html/Search/Build.html:213
-#. ($Description)
-msgid "%1 copy"
-msgstr "%1 複製"
-
-#: lib/RT/Record.pm:944
-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:2787
-#. ($self)
-msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
-msgstr "%1 無法將ç¾æ³è¨­æˆå·²è§£æ±ºã€‚RT 資料庫內容å¯èƒ½ä¸ä¸€è‡´ã€‚"
-
-#: lib/RT/Transaction_Overlay.pm:571
-#. ($obj_type)
-msgid "%1 created"
-msgstr "已建立 %1"
-
-#: lib/RT/Transaction_Overlay.pm:576
-#. ($obj_type)
-msgid "%1 deleted"
-msgstr "已刪除 %1"
-
-#: etc/initialdata:593
-msgid "%1 highest priority tickets I own"
-msgstr "å‰ %1 份待處ç†ç”³è«‹å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I own..."
-msgstr "å‰ %1 份待處ç†ç”³è«‹å–®..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets I requested..."
-msgstr "å‰ %1 份é€å‡ºçš„申請單..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 highest priority tickets pending my approval..."
-msgstr "å‰ %1 份待簽核申請單..."
-
-#: bin/rt-crontool:229
-#. ($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:863
-#. ($principal->Object->Name, $args{'Type'})
-msgid "%1 is no longer a %2 for this queue."
-msgstr "%1 å·²ä¸å†æ˜¯æ­¤è¡¨å–®çš„ %2。"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 is no longer a %2 for this ticket."
-msgstr "%1 å·²ä¸å†æ˜¯æ­¤ç”³è«‹å–®çš„ %2。"
-
-#: NOT FOUND IN SOURCE
-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/ShowTime:47 html/Ticket/Elements/ShowTime:49
-#. ($minutes)
-msgid "%1 min"
-msgstr "%1 分é˜"
-
-#: etc/initialdata:601
-msgid "%1 newest unowned tickets"
-msgstr "å‰ %1 份待èªé ˜çš„申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 not shown"
-msgstr "沒有顯示 %1"
-
-#: lib/RT/CustomField_Overlay.pm:893
-msgid "%1 objects"
-msgstr "%1 物件"
-
-#: NOT FOUND IN SOURCE
-msgid "%1 recent tickets I own..."
-msgstr "最新 %1 份待處ç†ç”³è«‹å–®..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 recent tickets I requested..."
-msgstr "最新 %1 份é€å‡ºçš„申請單..."
-
-#: NOT FOUND IN SOURCE
-msgid "%1 result(s) found"
-msgstr "找到 %1 é …çµæžœ"
-
-#: html/User/Elements/DelegateRights:97
-#. (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:63
-#. (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/CustomField_Overlay.pm:894
-msgid "%1's %2 objects"
-msgstr "%1 內的 %2 物件"
-
-#: lib/RT/CustomField_Overlay.pm:895
-msgid "%1's %2's %3 objects"
-msgstr "%1 內的 %2 的 %3 物件"
-
-#: html/Search/Elements/SearchPrivacy:52 html/Search/Elements/SelectSearchObject:55 html/Search/Elements/SelectSearchesForObjects:57
-#. ($object->Name)
-#. ($Object->Name)
-msgid "%1's saved searches"
-msgstr "%1 已儲存的查詢"
-
-#: lib/RT/Transaction_Overlay.pm:481
-#. ($self)
-msgid "%1: no attachment specified"
-msgstr "%1:未指定附件"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:78
-#. ($size)
-msgid "%1b"
-msgstr "%1 ä½å…ƒçµ„"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:75
-#. (int( $size / 102.4 ) / 10)
-msgid "%1k"
-msgstr "%1k ä½å…ƒçµ„"
-
-#: html/Ticket/Elements/ShowTime:49
-#. (sprintf("%.1f",$minutes / 60))
-msgid "%quant(%1,hour)"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "%quant(%1,result) found"
-msgstr "找到 %1 é …çµæžœ"
-
-#: lib/RT/Ticket_Overlay.pm:1142
-#. ($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:50 html/Admin/Elements/EditQueueWatchers:50 html/Admin/Elements/EditScrips:56 html/Admin/Elements/EditTemplates:57 html/Admin/Groups/Members.html:73 html/Elements/EditLinks:54 html/Ticket/Elements/EditPeople:67 html/User/Groups/Members.html:76
-msgid "(Check box to delete)"
-msgstr "(點é¸æ¬²åˆªé™¤çš„é …ç›®)"
-
-#: NOT FOUND IN SOURCE
-msgid "(Check boxes to delete)"
-msgstr "(點é¸æ¬²åˆªé™¤çš„é …ç›®)"
-
-#: html/Ticket/Elements/PreviewScrips:99
-msgid "(Check boxes to disable notifications to the listed recipients)"
-msgstr "(點é¸æ¬²åœç”¨é€šçŸ¥çš„收件人)"
-
-#: html/Ticket/Elements/PreviewScrips:123
-msgid "(Check boxes to enable notifications to the listed recipients)"
-msgstr "(點é¸æ¬²å•Ÿç”¨é€šçŸ¥çš„收件人)"
-
-#: html/Ticket/Create.html:218
-msgid "(Enter ticket ids or URLs, separated with spaces)"
-msgstr "(éµå…¥ç”³è«‹å–®ç·¨è™Ÿæˆ–網å€ï¼Œä»¥ç©ºç™½åˆ†éš”)"
-
-#: html/Admin/Queues/Modify.html:75 html/Admin/Queues/Modify.html:81
-#. ($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:74 html/Admin/Elements/ListGlobalCustomFields:53
-msgid "(No custom fields)"
-msgstr "(沒有自訂欄ä½)"
-
-#: html/Admin/Groups/Members.html:71 html/User/Groups/Members.html:74
-msgid "(No members)"
-msgstr "(沒有æˆå“¡)"
-
-#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ListGlobalScrips:48
-msgid "(No scrips)"
-msgstr "(沒有手續)"
-
-#: html/Admin/Elements/EditTemplates:52
-msgid "(No templates)"
-msgstr "沒有範本"
-
-#: NOT FOUND IN SOURCE
-msgid "(No workflows)"
-msgstr "沒有æµç¨‹"
-
-#: html/Admin/Elements/PickCustomFields:47 html/Admin/Elements/PickObjects:47
-msgid "(None)"
-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 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/Update.html:90
-msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> 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 administrative email addresses. These people <b>will</b> receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的管ç†å“¡é›»å­éƒµä»¶ä½å€ã€‚這<b>將會</b>更改後續的收件者å單。)"
-
-#: html/Ticket/Create.html:103
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <strong>will</strong> 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 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/Update.html:86
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <strong>not</strong> 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. These people <b>will</b> receive future updates.)"
-msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>將會</b>更改後續的收件者å單。)"
-
-#: html/Ticket/Create.html:93
-msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <strong>will</strong> receive future updates.)"
-msgstr ""
-
-#: html/Admin/Elements/EditScrip:96
-msgid "(Use these fields when you choose 'User Defined' for a condition or action)"
-msgstr "(當æ¢ä»¶æˆ–動作設為「使用者自訂ã€æ™‚,請填入這些欄ä½)"
-
-#: html/Ticket/Elements/EditWatchers:60 html/Ticket/Elements/ShowUserEntry:53
-msgid "(Will not be sent email)"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "(default delegate)"
-msgstr "(é è¨­ä»£ç†äºº)"
-
-#: NOT FOUND IN SOURCE
-msgid "(delete)"
-msgstr "(刪除)"
-
-#: html/Admin/Groups/index.html:57 html/User/Groups/index.html:54
-msgid "(empty)"
-msgstr "(空白)"
-
-#: NOT FOUND IN SOURCE
-msgid "(new)"
-msgstr "(新增)"
-
-#: html/Admin/Users/index.html:60
-msgid "(no name listed)"
-msgstr "(沒有列出姓å)"
-
-#: NOT FOUND IN SOURCE
-msgid "(no subject)"
-msgstr "(沒有主題)"
-
-#: html/Admin/Elements/SelectRights:72 html/Elements/EditCustomFieldSelect:69 html/Elements/SelectCustomFieldValue:51 html/Elements/ShowCustomFields:54 html/Search/Chart:56 html/Search/Elements/Chart:76 lib/RT/Transaction_Overlay.pm:591
-msgid "(no value)"
-msgstr "(ç„¡)"
-
-#: html/Admin/Elements/EditCustomFieldValues:47
-msgid "(no values)"
-msgstr "(沒有值)"
-
-#: html/Elements/EditLinks:132 html/Ticket/Elements/BulkLinks:49
-msgid "(only one ticket)"
-msgstr "(僅能指定一份申請單)"
-
-#: html/Elements/RT__Ticket/ColumnMap:149
-msgid "(pending approval)"
-msgstr "(等待簽核)"
-
-#: html/Elements/RT__Ticket/ColumnMap:152
-msgid "(pending other Collection)"
-msgstr "(等待其他集åˆ)"
-
-#: NOT FOUND IN SOURCE
-msgid "(pending other tickets)"
-msgstr "(等待其他申請單)"
-
-#: NOT FOUND IN SOURCE
-msgid "(requestor's group)"
-msgstr "(申請人所屬)"
-
-#: html/Admin/Users/Modify.html:71
-msgid "(required)"
-msgstr "(å¿…å¡«)"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "(untitled)"
-msgstr "(未命å)"
-
-#: html/Ticket/Elements/Reminders:133
-msgid "(yyyy/mm/dd)"
-msgstr "(yyyy/mm/dd)"
-
-#: NOT FOUND IN SOURCE
-msgid "*"
-msgstr "★"
-
-#: html/Elements/EditCustomFieldSelect:57
-msgid "-"
-msgstr "-"
-
-#: bin/rt-crontool:95
-msgid "--transaction argument could be only 'first' or 'last'"
-msgstr "--transaction 的值僅能為 'first' 或 'last'"
-
-#: NOT FOUND IN SOURCE
-msgid ":"
-msgstr ":"
-
-#: html/Ticket/Elements/ShowBasics:53
-msgid "<% $Ticket->Status%>"
-msgstr "<% $Ticket->Status%>"
-
-#: html/Elements/SelectTicketTypes:48
-msgid "<% $_ %>"
-msgstr "<% $_ %>"
-
-#: html/Search/Elements/SelectLinks:48
-msgid "<%$_%>"
-msgstr "<%$_%>"
-
-#: html/Search/Elements/DisplayOptions:73
-msgid "<%$field%>"
-msgstr "<%$field%>"
-
-#: html/Elements/CreateTicket:47
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" class=\"button\" value=\"New ticket in\" />&nbsp;%1"
-msgstr "<input type=\"submit\" class=\"button\" value=\"æ出申請單\" />&nbsp;%1"
-
-#: docs/design_docs/string-extraction-guide.txt:54 lib/RT/StyleGuide.pod:787
-#. ($m->scomp('/Elements/SelectNewTicketQueue'))
-msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
-msgstr "<input type=\"submit\" value=\"æ出申請單\">&nbsp;%1"
-
-#: etc/initialdata:218
-msgid "A blank template"
-msgstr "空白範本"
-
-#: html/Admin/Users/Modify.html:371
-msgid "A password was not set, so user won't be able to login."
-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:174 lib/RT/Principal_Overlay.pm:219
-msgid "ACE not found"
-msgstr "找ä¸åˆ° ACE 設定"
-
-#: lib/RT/ACE_Overlay.pm:853
-msgid "ACEs can only be created and deleted."
-msgstr "祇能新增或刪除 ACE 設定。"
-
-#: NOT FOUND IN SOURCE
-msgid "ACLEquivalence"
-msgstr "ACLEquivalence"
-
-#: html/Search/Elements/SelectAndOr:46
-msgid "AND"
-msgstr "AND"
-
-#: NOT FOUND IN SOURCE
-msgid "Aborting to avoid unintended ticket modifications.\\n"
-msgstr "離開以å…ä¸å°å¿ƒæ›´æ”¹åˆ°ç”³è«‹å–®ã€‚\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "About Me"
-msgstr "個人資訊"
-
-#: html/User/Elements/Tabs:53
-msgid "About me"
-msgstr "個人資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Access Right"
-msgstr "系統使用登錄權é™"
-
-#: html/Admin/Users/Modify.html:106
-msgid "Access control"
-msgstr "å­˜å–權é™"
-
-#: html/Admin/Elements/EditScrip:65
-msgid "Action"
-msgstr "動作"
-
-#: lib/RT/Scrip_Overlay.pm:172
-#. ($args{'ScripAction'})
-msgid "Action %1 not found"
-msgstr "動作 %1 找ä¸åˆ°"
-
-#: NOT FOUND IN SOURCE
-msgid "Action committed."
-msgstr "動作執行完畢"
-
-#: bin/rt-crontool:171
-msgid "Action committed.\\n"
-msgstr "動作執行完畢。\\n"
-
-#: lib/RT/Scrip_Overlay.pm:168
-msgid "Action is mandatory argument"
-msgstr ""
-
-#: bin/rt-crontool:167
-msgid "Action prepared..."
-msgstr "動作準備完畢..."
-
-#: NOT FOUND IN SOURCE
-msgid "Activated Date"
-msgstr "申請啟動時間"
-
-#: html/Search/Build.html:85
-msgid "Add"
-msgstr "新增"
-
-#: html/Search/Bulk.html:92
-msgid "Add AdminCc"
-msgstr "新增管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: html/Search/Bulk.html:88
-msgid "Add Cc"
-msgstr "新增副本收件人"
-
-#: html/Search/Elements/EditFormat:49
-msgid "Add Columns"
-msgstr ""
-
-#: html/Search/Elements/PickCriteria:46
-msgid "Add Criteria"
-msgstr "新增æ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Add Entry"
-msgstr "新增列"
-
-#: html/Ticket/Create.html:147 html/Ticket/Update.html:116
-msgid "Add More Files"
-msgstr "新增更多附件"
-
-#: NOT FOUND IN SOURCE
-msgid "Add Next State"
-msgstr "新增下一項關å¡"
-
-#: html/Search/Bulk.html:84
-msgid "Add Requestor"
-msgstr "新增申請人"
-
-#: html/Admin/Elements/AddCustomFieldValue:46
-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:83
-msgid "Add a scrip which will apply to all queues"
-msgstr "新增一é“用於所有表單的手續"
-
-#: NOT FOUND IN SOURCE
-msgid "Add additional criteria"
-msgstr "新增查詢æ¢ä»¶"
-
-#: html/Search/Build.html:109 html/Search/Build.html:94
-msgid "Add and Search"
-msgstr ""
-
-#: html/Search/Bulk.html:124
-msgid "Add comments or replies to selected tickets"
-msgstr "新增評論或回覆到指定的申請單"
-
-#: html/Admin/Groups/Members.html:63 html/User/Groups/Members.html:60
-msgid "Add members"
-msgstr "新增æˆå“¡"
-
-#: html/Admin/Queues/People.html:87 html/Ticket/Elements/AddWatchers:49
-msgid "Add new watchers"
-msgstr "新增視察員"
-
-#: html/Search/Build.html:85
-msgid "Add these terms to your search"
-msgstr ""
-
-#: html/Search/Bulk.html:158
-msgid "Add values"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "Add, delete and modify custom field values for objects"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "AddNextState"
-msgstr "新增下一項關å¡"
-
-#: lib/RT/Queue_Overlay.pm:763
-#. ($args{'Type'})
-msgid "Added principal as a %1 for this queue"
-msgstr "å–®ä½å·²æ–°å¢žç‚ºæ­¤è¡¨å–®çš„ %1"
-
-#: lib/RT/Ticket_Overlay.pm:1455
-#. ($self->loc($args{'Type'}))
-msgid "Added principal as a %1 for this ticket"
-msgstr "å–®ä½å·²æ–°å¢žç‚ºæ­¤ç”³è«‹å–®çš„ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Additional Hints"
-msgstr "é¡å¤–æ示"
-
-#: html/Admin/Users/Modify.html:146 html/User/Prefs.html:133
-msgid "Address1"
-msgstr "ä½å€"
-
-#: html/Admin/Users/Modify.html:151 html/User/Prefs.html:137
-msgid "Address2"
-msgstr "ä½å€(續)"
-
-#: NOT FOUND IN SOURCE
-msgid "Adjust Blinking Rate"
-msgstr "調整閃çˆé€Ÿåº¦å¿«æ…¢"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin"
-msgstr "管ç†å“¡"
-
-#: html/Ticket/Create.html:98
-msgid "Admin Cc"
-msgstr "管ç†å“¡å‰¯æœ¬"
-
-#: etc/initialdata:295
-msgid "Admin Comment"
-msgstr "管ç†å“¡è©•è«–"
-
-#: etc/initialdata:274
-msgid "Admin Correspondence"
-msgstr "管ç†å“¡å›žè¦†"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin Rights"
-msgstr "管ç†å“¡æ¬Šé™"
-
-#: html/Admin/Queues/index.html:46 html/Admin/Queues/index.html:49
-msgid "Admin queues"
-msgstr "表單管ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin users"
-msgstr "使用者管ç†"
-
-#: html/Admin/Global/index.html:47 html/Admin/Global/index.html:49
-msgid "Admin/Global configuration"
-msgstr "管ç†/全域設定"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Groups"
-msgstr "管ç†/群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Admin/Queue/Basics"
-msgstr "管ç†/表單/基本資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminAddress"
-msgstr "管ç†å“¡ Email"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminAllPersonalGroups"
-msgstr "管ç†æ‰€æœ‰ä»£ç†äººç¾¤çµ„"
-
-#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:60 lib/RT/ACE_Overlay.pm:113
-msgid "AdminCc"
-msgstr "管ç†å“¡å‰¯æœ¬"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminComment"
-msgstr "管ç†å“¡è©•è«–"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCorrespondence"
-msgstr "管ç†å“¡å›žè¦†"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "AdminCustomField"
-msgstr "管ç†è‡ªè¨‚欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminCustomFields"
-msgstr "管ç†è‡ªè¨‚欄ä½"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "AdminGroup"
-msgstr "管ç†ç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "AdminGroupDescription"
-msgstr "管ç†ç¾¤çµ„æè¿°"
-
-#: lib/RT/Group_Overlay.pm:165
-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:80
-msgid "AdminOwnPersonalGroups"
-msgstr "管ç†ä»£ç†äººç¾¤çµ„"
-
-#: lib/RT/Queue_Overlay.pm:92
-msgid "AdminQueue"
-msgstr "管ç†è¡¨å–®"
-
-#: lib/RT/System.pm:81
-msgid "AdminUsers"
-msgstr "管ç†ä½¿ç”¨è€…"
-
-#: NOT FOUND IN SOURCE
-msgid "Administrative"
-msgstr "行政類"
-
-#: html/Admin/Queues/People.html:69 html/Ticket/Elements/EditPeople:75
-msgid "Administrative Cc"
-msgstr "管ç†å“¡å‰¯æœ¬"
-
-#: NOT FOUND IN SOURCE
-msgid "Admins"
-msgstr "主管"
-
-#: html/Ticket/Elements/Tabs:216
-msgid "Advanced"
-msgstr "進階"
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search"
-msgstr "進階查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "Advanced Search Criteria"
-msgstr "進階查詢æ¢ä»¶"
-
-#: html/Elements/SelectDateRelation:57
-msgid "After"
-msgstr "晚於"
-
-#: NOT FOUND IN SOURCE
-msgid "Age"
-msgstr "經歷時間"
-
-#: html/Search/Elements/PickCriteria:52
-msgid "Aggregator"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Alias"
-msgstr "執行其他æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Alias for"
-msgstr "相當於"
-
-#: NOT FOUND IN SOURCE
-msgid "All"
-msgstr "全部"
-
-#: etc/initialdata:363
-msgid "All Approvals Passed"
-msgstr "完æˆå…¨éƒ¨ç°½æ ¸"
-
-#: NOT FOUND IN SOURCE
-msgid "All Condition"
-msgstr "所有æ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "All Custom Fields"
-msgstr "所有自訂欄ä½"
-
-#: html/Admin/Queues/index.html:75
-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/Search/Elements/EditQuery:56
-msgid "And/Or"
-msgstr "AND/OR"
-
-#: NOT FOUND IN SOURCE
-msgid "Any Condition"
-msgstr "ä»»æ„æ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Applies To"
-msgstr "套用於"
-
-#: html/Admin/CustomFields/Modify.html:73 html/Admin/Elements/CustomFieldTabs:83
-msgid "Applies to"
-msgstr "套用於"
-
-#: html/Search/Edit.html:64
-msgid "Apply"
-msgstr "套用"
-
-#: NOT FOUND IN SOURCE
-msgid "Apply Template"
-msgstr "引用範本"
-
-#: html/Search/Edit.html:64
-msgid "Apply your changes"
-msgstr "套用更動"
-
-#: html/Elements/Tabs:77
-msgid "Approval"
-msgstr "簽核"
-
-#: html/Approvals/Display.html:65 html/Approvals/Elements/ShowDependency:63 html/Approvals/index.html:86
-#. ($Ticket->Id, $Ticket->Subject)
-#. ($ticket->id, $msg)
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Approval #%1: %2"
-msgstr "簽核單 #%1:%2"
-
-#: html/Approvals/index.html:75
-#. ($ticket->Id)
-msgid "Approval #%1: Notes not recorded due to a system error"
-msgstr "簽核單 #%1:系統錯誤,記錄失敗"
-
-#: html/Approvals/index.html:73
-#. ($ticket->Id)
-msgid "Approval #%1: Notes recorded"
-msgstr "簽核單 #%1:記錄完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Details"
-msgstr "簽核細節"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Due"
-msgstr "簽核時é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Notes"
-msgstr "簽核æ„見"
-
-#: etc/initialdata:351
-msgid "Approval Passed"
-msgstr "完æˆæŸé …簽核"
-
-#: etc/initialdata:374
-msgid "Approval Rejected"
-msgstr "é§å›žæŸé …簽核"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Result"
-msgstr "簽核çµæžœ"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Status"
-msgstr "核准çµæžœ"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval Type"
-msgstr "簽核種類"
-
-#: NOT FOUND IN SOURCE
-msgid "Approval diagram"
-msgstr "簽核æµç¨‹"
-
-#: html/Approvals/Elements/Approve:69
-msgid "Approve"
-msgstr "核准"
-
-#: NOT FOUND IN SOURCE
-msgid "Approver"
-msgstr "簽核人"
-
-#: NOT FOUND IN SOURCE
-msgid "Approver Setting"
-msgstr "執行簽核人設定"
-
-#: etc/initialdata:504
-msgid "Approver's notes: %1"
-msgstr "簽核備註:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Apr"
-msgstr "四月"
-
-#: lib/RT/Date.pm:444
-msgid "Apr."
-msgstr "04"
-
-#: NOT FOUND IN SOURCE
-msgid "April"
-msgstr "四月"
-
-#: NOT FOUND IN SOURCE
-msgid "Are you sure to delete checked items?"
-msgstr "您確定è¦åˆªé™¤ï¼Ÿ"
-
-#: html/Search/Elements/DisplayOptions:81
-msgid "Asc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:56
-msgid "Ascending"
-msgstr "éžå¢ž"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "Assign and remove custom fields"
-msgstr "指派åŠç§»é™¤è‡ªè¨‚欄ä½"
-
-#: lib/RT/Queue_Overlay.pm:96
-msgid "AssignCustomFields"
-msgstr "指派自訂欄ä½"
-
-#: html/Search/Bulk.html:142 html/SelfService/Update.html:87 html/Ticket/ModifyAll.html:115 html/Ticket/Update.html:116
-msgid "Attach"
-msgstr "附件"
-
-#: html/SelfService/Create.html:92 html/Ticket/Create.html:143
-msgid "Attach file"
-msgstr "附加檔案"
-
-#: html/SelfService/Update.html:75 html/Ticket/Create.html:131 html/Ticket/Update.html:94
-msgid "Attached file"
-msgstr "ç¾æœ‰é™„件"
-
-#: html/Ticket/ShowEmailRecord.html:52 html/Ticket/ShowEmailRecord.html:56 html/Ticket/ShowEmailRecord.html:59
-#. ($Attachment)
-msgid "Attachment '%1' could not be loaded"
-msgstr "無法載入附件 '%1'"
-
-#: lib/RT/Transaction_Overlay.pm:489
-msgid "Attachment created"
-msgstr "附件新增完畢"
-
-#: lib/RT/Tickets_Overlay.pm:1945
-msgid "Attachment filename"
-msgstr "附件檔å"
-
-#: html/Ticket/Elements/ShowAttachments:47
-msgid "Attachments"
-msgstr "附件"
-
-#: lib/RT/Attributes_Overlay.pm:171
-msgid "Attribute Deleted"
-msgstr "已刪除該屬性"
-
-#: NOT FOUND IN SOURCE
-msgid "Attributes"
-msgstr "屬性"
-
-#: NOT FOUND IN SOURCE
-msgid "Aug"
-msgstr "八月"
-
-#: lib/RT/Date.pm:448
-msgid "Aug."
-msgstr "08"
-
-#: NOT FOUND IN SOURCE
-msgid "August"
-msgstr "八月"
-
-#: NOT FOUND IN SOURCE
-msgid "AuthSystem"
-msgstr "èªè­‰æ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoReject"
-msgstr "自動é§å›žè¡¨å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoResolve"
-msgstr "自動完æˆè¡¨å–®è™•ç†"
-
-#: etc/initialdata:221
-msgid "Autoreply"
-msgstr "自動回覆"
-
-#: etc/initialdata:72
-msgid "Autoreply To Requestors"
-msgstr "自動å°ç”³è«‹äººå›žè¦†"
-
-#: NOT FOUND IN SOURCE
-msgid "AutoreplyToRequestors"
-msgstr "自動å°ç”³è«‹äººå›žè¦†"
-
-#: html/Widgets/SelectionBox:185
-msgid "Available"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Available Columns"
-msgstr "å¯ç”¨çš„欄ä½ï¼š"
-
-#: NOT FOUND IN SOURCE
-msgid "Available Rights:"
-msgstr "權é™é …目列表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Back to Homepage"
-msgstr "回到首é "
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: NOT FOUND IN SOURCE
-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/CustomFieldTabs:65 html/Admin/Elements/GroupTabs:60 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/UserTabs:58 html/Ticket/Elements/Tabs:113 html/User/Elements/GroupTabs:59
-msgid "Basics"
-msgstr "基本資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Batch Approval"
-msgstr "批次簽核"
-
-#: html/Ticket/Update.html:88
-msgid "Bcc"
-msgstr "密件副本"
-
-#: html/Admin/CustomFields/GroupRights.html:91 html/Admin/CustomFields/UserRights.html:74 html/Admin/Elements/EditScrip:89
-msgid "Be sure to save your changes"
-msgstr "請別忘了儲存修改。"
-
-#: html/Elements/SelectDateRelation:55 lib/RT/CurrentUser.pm:361
-msgid "Before"
-msgstr "æ—©æ–¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin Approval"
-msgstr "開始簽核"
-
-#: NOT FOUND IN SOURCE
-msgid "Begin From "
-msgstr "起始日"
-
-#: html/Elements/Logo:47
-msgid "Best Practical Solutions, LLC corporate logo"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Binary"
-msgstr "檔案"
-
-#: NOT FOUND IN SOURCE
-msgid "Birthday"
-msgstr "生日"
-
-#: etc/initialdata:217
-msgid "Blank"
-msgstr "空白範本"
-
-#: html/Search/Elements/EditFormat:84
-msgid "Bold"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Bookmarkable URL for this search"
-msgstr "將查詢çµæžœè½‰ç‚ºå¯æ”¾å…¥æ›¸ç±¤çš„網å€"
-
-#: html/Search/Results.html:79
-msgid "Bookmarkable link"
-msgstr "å¯æ”¾å…¥æ›¸ç±¤çš„網å€"
-
-#: html/Ticket/Elements/ShowHistory:64 html/Ticket/Elements/ShowHistory:69
-msgid "Brief headers"
-msgstr "精簡標頭檔"
-
-#: html/Ticket/Elements/Tabs:227
-msgid "Bulk Update"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-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:1853
-msgid "Can not modify system users"
-msgstr "無法更改系統使用者"
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "Can this principal see this queue"
-msgstr "該單ä½æ˜¯å¦èƒ½æŸ¥é–±æ­¤è¡¨å–®"
-
-#: lib/RT/CustomField_Overlay.pm:379
-msgid "Can't add a custom field value without a name"
-msgstr "ä¸èƒ½æ–°å¢žæ²’有å稱的自訂欄ä½å€¼"
-
-#: html/Admin/CustomFields/Objects.html:86
-#. ($Class)
-msgid "Can't find a collection class for '%1'"
-msgstr ""
-
-#: html/Search/Build.html:286
-msgid "Can't find a saved search to work with"
-msgstr "找ä¸åˆ°å·²å„²å­˜çš„查詢"
-
-#: lib/RT/Link_Overlay.pm:159
-msgid "Can't link a ticket to itself"
-msgstr "申請單ä¸èƒ½éˆçµè‡ªå·±ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Can't merge into a merged ticket. You should never get this error"
-msgstr "ä¸èƒ½æ•´åˆé€²å·²æ•´åˆéŽçš„申請單。這個錯誤ä¸è©²ç™¼ç”Ÿã€‚"
-
-#: html/Widgets/SavedSearch:63
-#. (loc($self->{SearchType}))
-msgid "Can't save %1"
-msgstr "無法儲存 %1"
-
-#: html/Search/Build.html:290
-msgid "Can't save this search"
-msgstr "無法儲存此項查詢"
-
-#: lib/RT/Record.pm:1282 lib/RT/Record.pm:1358
-msgid "Can't specifiy both base and target"
-msgstr "ä¸èƒ½åŒæ™‚指定起始申請單與目的申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Cancel"
-msgstr "å–消"
-
-#: html/autohandler:204
-#. ($msg)
-msgid "Cannot create user: %1"
-msgstr "無法新增使用者:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Cannot login: Your system clock differs from server's by %1 seconds!"
-msgstr "您的系統時é˜å’Œä¼ºæœå™¨ç›¸å·® %1 秒,無法登入ï¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Card No."
-msgstr "å¡è™Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Categories"
-msgstr "分類管ç†"
-
-#: html/Admin/Elements/AddCustomFieldValue:62 html/Admin/Elements/EditCustomFieldValues:58
-msgid "Category"
-msgstr "分類"
-
-#: etc/initialdata:50 html/Admin/Queues/People.html:65 html/SelfService/Create.html:71 html/Ticket/Create.html:88 html/Ticket/Elements/EditPeople:72 html/Ticket/Elements/ShowPeople:56 html/Ticket/Update.html:83 lib/RT/ACE_Overlay.pm:112
-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:52
-msgid "Change password"
-msgstr "更改密碼"
-
-#: NOT FOUND IN SOURCE
-msgid "ChangeOwnerUI"
-msgstr "å¯å¦é¸æ“‡è¡¨å–®æ‰¿è¾¦äºº"
-
-#: html/Elements/Submit:78
-msgid "Check All"
-msgstr "全部é¸å–"
-
-#: html/SelfService/Update.html:78 html/Ticket/Create.html:134 html/Ticket/Update.html:97
-msgid "Check box to delete"
-msgstr "é¸æ“‡æ¬²åˆªé™¤çš„é …ç›®"
-
-#: html/Admin/Elements/SelectRights:55
-msgid "Check box to revoke right"
-msgstr "é¸æ“‡æ¬²æ’¤æ¶ˆçš„權利"
-
-#: html/Elements/EditLinks:148 html/Elements/EditLinks:85 html/Elements/ShowLinks:78 html/Ticket/Create.html:223 html/Ticket/Elements/BulkLinks:64
-msgid "Children"
-msgstr "å­ç”³è«‹å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "Chinese Name"
-msgstr "中文姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "Chinese/English"
-msgstr "中英文"
-
-#: html/NoAuth/js/util.js:201
-msgid "Choose a date"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:141
-msgid "City"
-msgstr "所在城市"
-
-#: NOT FOUND IN SOURCE
-msgid "ClassicUI"
-msgstr "傳統介é¢"
-
-#: html/Elements/Submit:80
-msgid "Clear All"
-msgstr "全部清除"
-
-#: html/Helpers/CalPopup.html:51
-msgid "Close window"
-msgstr "關閉視窗"
-
-#: html/Ticket/Elements/ShowDates:68
-msgid "Closed"
-msgstr "已解決"
-
-#: NOT FOUND IN SOURCE
-msgid "Closed Tickets"
-msgstr "已解決的申請單"
-
-#: html/SelfService/Closed.html:46 html/SelfService/Elements/Tabs:78
-msgid "Closed tickets"
-msgstr "已解決的申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Code"
-msgstr "執行程å¼ç¢¼"
-
-#: lib/RT/CustomField_Overlay.pm:89
-msgid "Combobox: Select or enter multiple values"
-msgstr "下拉文字框:é¸æ“‡æˆ–éµå…¥å¤šé‡é …ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:90
-msgid "Combobox: Select or enter one value"
-msgstr "下拉文字框:é¸æ“‡æˆ–éµå…¥å–®ä¸€é …ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:91
-msgid "Combobox: Select or enter up to %1 values"
-msgstr "下拉文字框:é¸æ“‡æˆ–éµå…¥æœ€å¤š %1 個項目"
-
-#: NOT FOUND IN SOURCE
-msgid "Command not understood!\\n"
-msgstr "指令無法辨識ï¼\\n"
-
-#: html/Ticket/Elements/ShowTransaction:190 html/Ticket/Elements/Tabs:185
-msgid "Comment"
-msgstr "è©•è«–"
-
-#: html/Admin/Queues/Modify.html:79
-msgid "Comment Address"
-msgstr "è©•è«–é›»å­éƒµä»¶åœ°å€"
-
-#: NOT FOUND IN SOURCE
-msgid "Comment not recorded"
-msgstr "評論未被紀錄"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "Comment on tickets"
-msgstr "å°ç”³è«‹å–®æ出評論"
-
-#: lib/RT/Queue_Overlay.pm:111
-msgid "CommentOnTicket"
-msgstr "評論申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments"
-msgstr "è©•è«–"
-
-#: html/Ticket/ModifyAll.html:91 html/Ticket/Update.html:75
-msgid "Comments (Not sent to requestors)"
-msgstr "è©•è«–(ä¸é€çµ¦ç”³è«‹äºº)"
-
-#: html/Search/Bulk.html:128
-msgid "Comments (not sent to requestors)"
-msgstr "è©•è«–(ä¸é€çµ¦ç”³è«‹äºº)"
-
-#: NOT FOUND IN SOURCE
-msgid "Comments about %1"
-msgstr "å° %1 çš„è©•è«–"
-
-#: html/Admin/Users/Modify.html:225 html/Ticket/Elements/ShowRequestor:67
-msgid "Comments about this user"
-msgstr "使用者æè¿°"
-
-#: lib/RT/Transaction_Overlay.pm:634
-msgid "Comments added"
-msgstr "新增評論完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Commit"
-msgstr "確èª"
-
-#: lib/RT/Action/Generic.pm:175
-msgid "Commit Stubbed"
-msgstr "消除更動完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Company Name"
-msgstr "å…¬å¸å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "CompanySpecific"
-msgstr "å„å…¬å¸ç¨ç«‹é¡¯ç¤º"
-
-#: NOT FOUND IN SOURCE
-msgid "Compile Restrictions"
-msgstr "設定查詢æ¢ä»¶"
-
-#: html/Admin/Elements/EditScrip:59
-msgid "Condition"
-msgstr "æ¢ä»¶"
-
-#: lib/RT/Scrip_Overlay.pm:184
-msgid "Condition is mandatory argument"
-msgstr "æ¢ä»¶æ˜¯å¿…填欄ä½"
-
-#: bin/rt-crontool:151
-msgid "Condition matches..."
-msgstr "符åˆæ¢ä»¶..."
-
-#: lib/RT/Scrip_Overlay.pm:188
-msgid "Condition not found"
-msgstr "未找到符åˆçš„ç¾æ³"
-
-#: html/Elements/Tabs:84
-msgid "Configuration"
-msgstr "設定"
-
-#: html/SelfService/Prefs.html:54
-msgid "Confirm"
-msgstr "確èªå¯†ç¢¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Confirm Password"
-msgstr "密碼確èª"
-
-#: NOT FOUND IN SOURCE
-msgid "Confirm Submit"
-msgstr "確定é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "Contact System Administrator"
-msgstr "連絡系統管ç†å“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "ContactInfoSystem"
-msgstr "連絡資訊系統"
-
-#: NOT FOUND IN SOURCE
-msgid "Contacted date '%1' could not be parsed"
-msgstr "無法解讀è¯çµ¡æ—¥æœŸ '%1'"
-
-#: html/Admin/Elements/ModifyTemplate:65 html/Elements/SelectAttachmentField:48 html/Ticket/ModifyAll.html:119
-msgid "Content"
-msgstr "內容"
-
-#: html/Elements/SelectAttachmentField:49
-msgid "Content-Type"
-msgstr "內容類型"
-
-#: NOT FOUND IN SOURCE
-msgid "Coould not create group"
-msgstr "無法新增群組"
-
-#: html/Search/Elements/EditSearches:65
-msgid "Copy"
-msgstr "複製"
-
-#: NOT FOUND IN SOURCE
-msgid "Copy Field From:"
-msgstr "欲複製欄ä½ï¼š"
-
-#: etc/initialdata:286
-msgid "Correspondence"
-msgstr "回覆"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence Address"
-msgstr "申請單回覆地å€"
-
-#: lib/RT/Transaction_Overlay.pm:630
-msgid "Correspondence added"
-msgstr "新增申請單回覆"
-
-#: NOT FOUND IN SOURCE
-msgid "Correspondence not recorded"
-msgstr "未紀錄申請單回覆"
-
-#: NOT FOUND IN SOURCE
-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/Record.pm:1707
-msgid "Could not add new custom field value. "
-msgstr "ä¸èƒ½æ–°å¢žè‡ªè¨‚欄ä½çš„值。"
-
-#: lib/RT/Record.pm:1660
-#. (, $value_msg)
-msgid "Could not add new custom field value. %1 "
-msgstr "ä¸èƒ½æ–°å¢žè‡ªè¨‚欄ä½çš„值。%1 "
-
-#: lib/RT/Ticket_Overlay.pm:3048 lib/RT/Ticket_Overlay.pm:3056 lib/RT/Ticket_Overlay.pm:3073
-msgid "Could not change owner. "
-msgstr "ä¸èƒ½æ›´æ”¹æ‰¿è¾¦äººã€‚"
-
-#: html/Admin/CustomFields/Modify.html:161
-#. ($msg)
-msgid "Could not create CustomField"
-msgstr "無法新增自訂欄ä½"
-
-#: html/Admin/Elements/EditCustomField:113
-#. ($msg)
-msgid "Could not create CustomField: %1"
-msgstr "無法新增自訂欄ä½ï¼š%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create Scrip"
-msgstr "無法建立訊æ¯é€šçŸ¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create Template"
-msgstr "無法建立通知範本"
-
-#: html/User/Groups/Modify.html:98 lib/RT/Group_Overlay.pm:494 lib/RT/Group_Overlay.pm:501
-msgid "Could not create group"
-msgstr "無法新增群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not create item"
-msgstr "無法新增項目"
-
-#: html/Admin/Global/Template.html:96 html/Admin/Queues/Template.html:93
-#. ($msg)
-msgid "Could not create template: %1"
-msgstr "無法新增範本:%1"
-
-#: lib/RT/Ticket_Overlay.pm:1075 lib/RT/Ticket_Overlay.pm:407
-msgid "Could not create ticket. Queue not set"
-msgstr "無法新增申請單。尚未指定表單。"
-
-#: lib/RT/User_Overlay.pm:255 lib/RT/User_Overlay.pm:269 lib/RT/User_Overlay.pm:278 lib/RT/User_Overlay.pm:287 lib/RT/User_Overlay.pm:296 lib/RT/User_Overlay.pm:310 lib/RT/User_Overlay.pm:320 lib/RT/User_Overlay.pm:496
-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 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:741 lib/RT/Ticket_Overlay.pm:1423
-msgid "Could not find or create that user"
-msgstr "找ä¸åˆ°æˆ–無法新增該å使用者"
-
-#: lib/RT/Queue_Overlay.pm:802 lib/RT/Ticket_Overlay.pm:1504
-msgid "Could not find that principal"
-msgstr "找ä¸åˆ°è©²å–®ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not find user %1."
-msgstr "找ä¸åˆ°ä½¿ç”¨è€… %1。"
-
-#: html/Admin/CustomFields/Objects.html:69
-msgid "Could not load CustomField %1"
-msgstr "ç„¡æ³•è¼‰å…¥æ¬„ä½ %1"
-
-#: html/Admin/Groups/Members.html:112 html/User/Groups/Members.html:111 html/User/Groups/Modify.html:103
-msgid "Could not load group"
-msgstr "無法載入群組"
-
-#: lib/RT/SavedSearch.pm:119
-#. ($privacy)
-msgid "Could not load object for %1"
-msgstr "無法為 %1 載入物件"
-
-#: lib/RT/SavedSearch.pm:197
-msgid "Could not load search attribute"
-msgstr "無法載入查詢屬性"
-
-#: lib/RT/Queue_Overlay.pm:761
-#. ($args{'Type'})
-msgid "Could not make that principal a %1 for this queue"
-msgstr "無法將該單ä½è¨­ç‚ºæ­¤è¡¨å–®çš„ %1。"
-
-#: lib/RT/Ticket_Overlay.pm:1444
-#. ($self->loc($args{'Type'}))
-msgid "Could not make that principal a %1 for this ticket"
-msgstr "無法將該單ä½è¨­ç‚ºæ­¤ç”³è«‹å–®çš„ %1。"
-
-#: lib/RT/Queue_Overlay.pm:860
-#. ($args{'Type'})
-msgid "Could not remove that principal as a %1 for this queue"
-msgstr "ç„¡æ³•å°‡å–®ä½ %1 從表單移除。"
-
-#: NOT FOUND IN SOURCE
-msgid "Could not remove that principal as a %1 for this ticket"
-msgstr "ç„¡æ³•å°‡å–®ä½ %1 從申請單移除。"
-
-#: lib/RT/User_Overlay.pm:191
-msgid "Could not set user info"
-msgstr "無法設定使用者資訊"
-
-#: lib/RT/Transaction_Overlay.pm:159
-msgid "Couldn't add attachment"
-msgstr "無法新增附件"
-
-#: lib/RT/Group_Overlay.pm:1003
-msgid "Couldn't add member to group"
-msgstr "無法新增æˆå“¡è‡³ç¾¤çµ„"
-
-#: lib/RT/Record.pm:1719 lib/RT/Record.pm:1771
-#. ($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/Record.pm:953
-msgid "Couldn't find row"
-msgstr "找ä¸åˆ°æ­¤åˆ—資料"
-
-#: lib/RT/Group_Overlay.pm:977
-msgid "Couldn't find that principal"
-msgstr "找ä¸åˆ°è©²å–®ä½"
-
-#: lib/RT/CustomField_Overlay.pm:409
-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:145
-#. ($self->Id)
-msgid "Couldn't load %1 from the users database.\\n"
-msgstr "無法從使用者資料庫載入 %1。\\n"
-
-#: html/Admin/CustomFields/UserRights.html:149
-#. ($id)
-msgid "Couldn't load Class %1"
-msgstr "無法載入類別 %1"
-
-#: html/Admin/CustomFields/GroupRights.html:107
-#. ($id)
-msgid "Couldn't load CustomField %1"
-msgstr "ç„¡æ³•è¼‰å…¥è‡ªè¨‚æ¬„ä½ %1"
-
-#: 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 "無法載入手續。"
-
-#: lib/RT/Ticket_Overlay.pm:2016
-#. ($self->Id)
-msgid "Couldn't load copy of ticket #%1."
-msgstr ""
-
-#: html/Admin/Groups/GroupRights.html:109 html/Admin/Groups/UserRights.html:96
-#. ($id)
-msgid "Couldn't load group %1"
-msgstr "無法載入手續 %1"
-
-#: lib/RT/Link_Overlay.pm:202 lib/RT/Link_Overlay.pm:211 lib/RT/Link_Overlay.pm:238
-msgid "Couldn't load link"
-msgstr "無法載入éˆçµã€‚"
-
-#: html/Admin/Elements/ObjectCustomFields:83 html/Admin/Queues/CustomFields.html:59 html/Admin/Users/CustomFields.html:59
-#. ($id)
-msgid "Couldn't load object %1"
-msgstr "無法載入物件 %1"
-
-#: html/Admin/Queues/People.html:142
-#. ($id)
-msgid "Couldn't load queue"
-msgstr "無法載入表單"
-
-#: html/Admin/Queues/GroupRights.html:122 html/Admin/Queues/UserRights.html:93
-#. ($id)
-msgid "Couldn't load queue %1"
-msgstr "無法載入表單 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load scrip"
-msgstr "無法載入手續"
-
-#: html/Admin/Elements/EditScrip:126 html/Admin/Elements/EditScrip:167
-#. ($id)
-msgid "Couldn't load scrip #%1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load template"
-msgstr "無法載入範本"
-
-#: NOT FOUND IN SOURCE
-msgid "Couldn't load that user (%1)"
-msgstr "無法載入該å使用者(%1)"
-
-#: html/SelfService/Display.html:158 lib/RT/Action/CreateTickets.pm:680
-#. ($id)
-msgid "Couldn't load ticket '%1'"
-msgstr "無法載入申請單 '%1'"
-
-#: lib/RT/Ticket_Overlay.pm:2643
-#. ($args{'URI'})
-msgid "Couldn't resolve '%1' into a URI."
-msgstr ""
-
-#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:153
-msgid "Country"
-msgstr "國家"
-
-#: html/Admin/Elements/CreateUserCalled:47 html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditScrip:133 html/Admin/Queues/Template.html:66 html/Elements/QuickCreate:65 html/Ticket/Create.html:168 html/Ticket/Create.html:235
-msgid "Create"
-msgstr "新增"
-
-#: NOT FOUND IN SOURCE
-msgid "Create Subgroup:"
-msgstr "新增å­ç¾¤çµ„:"
-
-#: etc/initialdata:135
-msgid "Create Tickets"
-msgstr "新增申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Create User:"
-msgstr "新增æˆå“¡ï¼š"
-
-#: html/Admin/CustomFields/Modify.html:150 html/Admin/Elements/EditCustomField:96
-msgid "Create a CustomField"
-msgstr "新增自訂欄ä½"
-
-#: html/Admin/Queues/CustomField.html:69
-#. ($QueueObj->Name())
-msgid "Create a CustomField for queue %1"
-msgstr "為 %1 表單新增自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a CustomField that 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:125 html/Admin/Groups/Modify.html:99
-msgid "Create a new group"
-msgstr "新增群組"
-
-#: html/User/Groups/Modify.html:113 html/User/Groups/Modify.html:88
-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:47 html/Ticket/Create.html:51 html/Ticket/Create.html:60
-msgid "Create a new ticket"
-msgstr "新增申請單"
-
-#: html/Admin/Users/Modify.html:252 html/Admin/Users/Modify.html:314
-msgid "Create a new user"
-msgstr "新增使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "Create a new workflow"
-msgstr "新增æµç¨‹"
-
-#: html/Admin/Queues/Modify.html:125
-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:89
-#. ($QueueObj->Name)
-msgid "Create a scrip for queue %1"
-msgstr "為 %1 表單新增手續"
-
-#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:86
-msgid "Create a template"
-msgstr "新增範本"
-
-#: html/SelfService/Create.html:46 html/SelfService/CreateTicketInQueue.html:46
-msgid "Create a ticket"
-msgstr "æ出申請單"
-
-#: NOT FOUND IN SOURCE
-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:137
-msgid "Create new tickets based on this scrip's template"
-msgstr "ä¾æ“šæ­¤é …手續內的模版,新增申請單"
-
-#: html/SelfService/Create.html:105
-msgid "Create ticket"
-msgstr "新增申請單"
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "Create tickets in this queue"
-msgstr "在此表單中新增申請單"
-
-#: lib/RT/CustomField_Overlay.pm:106
-msgid "Create, delete and modify custom fields"
-msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹è‡ªè¨‚欄ä½"
-
-#: lib/RT/Queue_Overlay.pm:92
-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:80
-msgid "Create, delete and modify the members of personal groups"
-msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹ä»£ç†äººç¾¤çµ„"
-
-#: lib/RT/System.pm:81
-msgid "Create, delete and modify users"
-msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹ä½¿ç”¨è€…"
-
-#: lib/RT/System.pm:87
-msgid "CreateSavedSearch"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:109
-msgid "CreateTicket"
-msgstr "新增申請單"
-
-#: html/Elements/SelectDateType:47 html/Ticket/Elements/ShowDates:48 lib/RT/Ticket_Overlay.pm:1169
-msgid "Created"
-msgstr "新增日"
-
-#: html/Admin/CustomFields/Modify.html:163 html/Admin/Elements/EditCustomField:117
-#. ($CustomFieldObj->Name())
-msgid "Created CustomField %1"
-msgstr "è‡ªè¨‚æ¬„ä½ %1 新增æˆåŠŸ"
-
-#: html/Tools/Reports/Elements/Tabs:63
-msgid "Created in a date range"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Created template %1"
-msgstr "範本 %1 新增æˆåŠŸ"
-
-#: html/Tools/Reports/CreatedByDates.html:52
-msgid "Created tickets in period, grouped by status"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Created workflow %1"
-msgstr "æµç¨‹ %1 新增æˆåŠŸ"
-
-#: html/Search/Elements/PickBasics:102
-msgid "Creator"
-msgstr "建立者"
-
-#: 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 "ç¾æœ‰è‡ªè¨‚欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Groups:"
-msgstr "ç¾æœ‰ç¾¤çµ„列表:"
-
-#: html/Elements/EditLinks:49
-msgid "Current Links"
-msgstr "ç¾æœ‰é—œä¿‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Rights:"
-msgstr "ç¾æœ‰æ¬Šé™ï¼š"
-
-#: html/Admin/Elements/EditScrips:51
-msgid "Current Scrips"
-msgstr "ç¾æœ‰æ‰‹çºŒ"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Status"
-msgstr "ç›®å‰ç‹€æ…‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Templates"
-msgstr "ç¾æœ‰ç¯„本"
-
-#: NOT FOUND IN SOURCE
-msgid "Current Watchers"
-msgstr "ç¾æœ‰è¦–察員"
-
-#: html/Admin/Groups/Members.html:60 html/User/Groups/Members.html:63
-msgid "Current members"
-msgstr "ç¾æœ‰æˆå“¡"
-
-#: html/Admin/Elements/SelectRights:51
-msgid "Current rights"
-msgstr "ç¾æœ‰æ¬Šé™"
-
-#: html/Search/Elements/EditQuery:47
-msgid "Current search"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Current search criteria"
-msgstr "ç¾æœ‰æŸ¥è©¢æ¢ä»¶"
-
-#: html/Admin/Queues/People.html:62 html/Ticket/Elements/EditPeople:66
-msgid "Current watchers"
-msgstr "ç¾æœ‰è¦–察員"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Field #%1"
-msgstr "è‡ªè¨‚æ¬„ä½ #%1"
-
-#: html/Admin/Elements/SystemTabs:61 html/Admin/Elements/Tabs:62 html/Admin/Global/index.html:71 html/Admin/Users/Modify.html:205 html/Admin/index.html:77 html/Ticket/Elements/ShowSummary:56
-msgid "Custom Fields"
-msgstr "自訂欄ä½"
-
-#: html/Admin/CustomFields/index.html:60
-#. ($lookup)
-msgid "Custom Fields for %1"
-msgstr "%1 的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom Fields which apply to all queues"
-msgstr "é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
-
-#: html/Admin/Elements/EditScrip:107
-msgid "Custom action cleanup code"
-msgstr "動作後執行程å¼"
-
-#: html/Admin/Elements/EditScrip:103
-msgid "Custom action preparation code"
-msgstr "動作å‰åŸ·è¡Œç¨‹å¼"
-
-#: html/Admin/Elements/EditScrip:99
-msgid "Custom condition"
-msgstr "自訂æ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 %2 %3"
-msgstr "è‡ªè¨‚æ¬„ä½ %1 %2 %3"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field %1 does not apply to this object"
-msgstr "è‡ªè¨‚æ¬„ä½ %1 ä¸é©ç”¨æ–¼æ­¤ç‰©ä»¶"
-
-#: lib/RT/Tickets_Overlay.pm:2424
-#. ($CF->Name)
-msgid "Custom field %1 has a value."
-msgstr "è‡ªè¨‚æ¬„ä½ %1 已有值"
-
-#: lib/RT/Tickets_Overlay.pm:2420
-#. ($CF->Name)
-msgid "Custom field %1 has no value."
-msgstr "è‡ªè¨‚æ¬„ä½ %1 沒有值"
-
-#: lib/RT/Record.pm:1592 lib/RT/Record.pm:1754
-#. ($args{'Field'})
-msgid "Custom field %1 not found"
-msgstr "找ä¸åˆ°è‡ªè¨‚æ¬„ä½ %1"
-
-#: lib/RT/Report/Tickets.pm:118 lib/RT/Report/Tickets.pm:121
-#. ($cf)
-#. ($obj->Name)
-msgid "Custom field '%1'"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field deleted"
-msgstr "自訂欄ä½å·²åˆªé™¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Custom field not found"
-msgstr "找ä¸åˆ°è‡ªè¨‚欄ä½"
-
-#: lib/RT/CustomField_Overlay.pm:1157
-#. ($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:419
-msgid "Custom field value could not be deleted"
-msgstr "無法刪除自訂欄ä½å€¼"
-
-#: lib/RT/CustomField_Overlay.pm:1169
-msgid "Custom field value could not be found"
-msgstr "找ä¸åˆ°è‡ªè¨‚欄ä½å€¼"
-
-#: lib/RT/CustomField_Overlay.pm:1171 lib/RT/CustomField_Overlay.pm:417
-msgid "Custom field value deleted"
-msgstr "自訂欄ä½å€¼åˆªé™¤æˆåŠŸ"
-
-#: html/Elements/SelectGroups:51 html/Elements/SelectUsers:51 lib/RT/Transaction_Overlay.pm:638
-msgid "CustomField"
-msgstr "自訂欄ä½"
-
-#: html/Prefs/MyRT.html:78 html/Prefs/Quicksearch.html:70 html/Prefs/Search.html:75
-msgid "Customize"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Data error"
-msgstr "資料錯誤"
-
-#: NOT FOUND IN SOURCE
-msgid "DatabaseBindRemote"
-msgstr "容許外部連線"
-
-#: NOT FOUND IN SOURCE
-msgid "DatabaseName"
-msgstr "MySQL資料庫"
-
-#: NOT FOUND IN SOURCE
-msgid "Date of Departure"
-msgstr "出發日期"
-
-#: html/SelfService/Display.html:61 html/Ticket/Create.html:203 html/Ticket/Elements/ShowSummary:83 html/Ticket/Elements/Tabs:116 html/Ticket/ModifyAll.html:65
-msgid "Dates"
-msgstr "日期"
-
-#: NOT FOUND IN SOURCE
-msgid "Dec"
-msgstr "å二月"
-
-#: lib/RT/Date.pm:452
-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:222
-msgid "Default Autoresponse template"
-msgstr "é è¨­è‡ªå‹•å›žæ‡‰ç¯„本"
-
-#: html/Tools/Offline.html:61
-msgid "Default Queue"
-msgstr "é è¨­è¡¨å–®"
-
-#: html/Tools/Offline.html:70
-msgid "Default Requestor"
-msgstr "é è¨­ç”³è«‹äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Default Value"
-msgstr "é è¨­å€¼"
-
-#: etc/initialdata:296
-msgid "Default admin comment template"
-msgstr "é è¨­ç®¡ç†å“¡è©•è«–範本"
-
-#: etc/initialdata:275
-msgid "Default admin correspondence template"
-msgstr "é è¨­ç®¡ç†å“¡å›žè¦†ç¯„本"
-
-#: etc/initialdata:287
-msgid "Default correspondence template"
-msgstr "é è¨­å›žè¦†ç¯„本"
-
-#: etc/initialdata:253
-msgid "Default transaction template"
-msgstr "é è¨­æ›´å‹•ç¯„本"
-
-#: NOT FOUND IN SOURCE
-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:46 html/User/Delegation.html:49
-msgid "Delegate rights"
-msgstr "代ç†äººæ¬Šé™"
-
-#: lib/RT/System.pm:84
-msgid "Delegate specific rights which have been granted to you."
-msgstr "å°‡æ“有的權é™å§”託他人代ç†"
-
-#: lib/RT/System.pm:84
-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 "代ç†è¡¨å–®ç¨®é¡ž"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates"
-msgstr "代ç†äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates Enabled Status"
-msgstr "代ç†å•Ÿå‹•ç‹€æ…‹"
-
-#: NOT FOUND IN SOURCE
-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 "代ç†äººè¨­å®š"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegates Status"
-msgstr "代ç†ç‹€æ…‹"
-
-#: html/User/Elements/Tabs:59
-msgid "Delegation"
-msgstr "代ç†äººæ¬Šé™"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegation Groups"
-msgstr "代ç†äººç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "Delegation Rights"
-msgstr "代ç†äººæ¬Šé™"
-
-#: html/Admin/Elements/EditScrips:75 html/Search/Elements/EditFormat:103 html/Search/Elements/EditQuery:57 html/Search/Elements/EditSearches:63 html/Widgets/SelectionBox:204
-msgid "Delete"
-msgstr "刪除"
-
-#: html/Admin/Elements/EditTemplates:79
-msgid "Delete Template"
-msgstr "刪除範本"
-
-#: lib/RT/SavedSearch.pm:220
-#. ($msg)
-msgid "Delete failed: %1"
-msgstr ""
-
-#: html/Admin/Elements/EditScrips:74
-msgid "Delete selected scrips"
-msgstr "刪除指定的手續"
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "Delete tickets"
-msgstr "刪除申請單"
-
-#: html/Search/Bulk.html:159
-msgid "Delete values"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:114
-msgid "DeleteTicket"
-msgstr "刪除申請單"
-
-#: lib/RT/SavedSearch.pm:218
-msgid "Deleted search"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Deleting this object could break referential integrity"
-msgstr "刪除此物件å¯èƒ½ç ´å£žåƒè€ƒå®Œæ•´æ€§"
-
-#: lib/RT/Queue_Overlay.pm:394
-msgid "Deleting this object would break referential integrity"
-msgstr "刪除此物件å¯èƒ½ç ´å£žåƒè€ƒå®Œæ•´æ€§"
-
-#: lib/RT/User_Overlay.pm:512
-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:73
-msgid "Deny"
-msgstr "é§å›ž"
-
-#: NOT FOUND IN SOURCE
-msgid "Department"
-msgstr "部門"
-
-#: NOT FOUND IN SOURCE
-msgid "Department ID"
-msgstr "部門代碼"
-
-#: NOT FOUND IN SOURCE
-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/Elements/EditLinks:140 html/Elements/EditLinks:66 html/Elements/ShowLinks:58 html/Ticket/Create.html:221 html/Ticket/Elements/BulkLinks:56 html/Ticket/Elements/ShowDependencies:53
-msgid "Depended on by"
-msgstr "å¯æŽ¥çºŒè™•ç†çš„申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Dependencies: \\n"
-msgstr "附屬性:\\n"
-
-#: lib/RT/Transaction_Overlay.pm:718
-#. ($value)
-msgid "Dependency by %1 added"
-msgstr "已加入å¯æŽ¥çºŒè™•ç†çš„申請單 %1"
-
-#: lib/RT/Transaction_Overlay.pm:758
-#. ($value)
-msgid "Dependency by %1 deleted"
-msgstr "已移除å¯æŽ¥çºŒè™•ç†çš„申請單 %1"
-
-#: lib/RT/Transaction_Overlay.pm:715
-#. ($value)
-msgid "Dependency on %1 added"
-msgstr "已加入需先處ç†çš„申請單 %1"
-
-#: lib/RT/Transaction_Overlay.pm:755
-#. ($value)
-msgid "Dependency on %1 deleted"
-msgstr "已移除需先處ç†çš„申請單 %1"
-
-#: html/Elements/EditLinks:136 html/Elements/EditLinks:57 html/Elements/SelectLinkType:48 html/Elements/ShowLinks:48 html/Ticket/Create.html:220 html/Ticket/Elements/BulkLinks:52 html/Ticket/Elements/ShowDependencies:46
-msgid "Depends on"
-msgstr "需先處ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "DependsOn"
-msgstr "需先處ç†"
-
-#: html/Search/Elements/DisplayOptions:86
-msgid "Desc"
-msgstr ""
-
-#: html/Elements/SelectSortOrder:56
-msgid "Descending"
-msgstr "éžæ¸›"
-
-#: html/SelfService/Create.html:100 html/Ticket/Create.html:152
-msgid "Describe the issue below"
-msgstr "在以下欄ä½æ述主題"
-
-#: html/Admin/CustomFields/Modify.html:61 html/Admin/Elements/AddCustomFieldValue:57 html/Admin/Elements/EditCustomField:60 html/Admin/Elements/EditCustomFieldValues:56 html/Admin/Elements/EditScrip:55 html/Admin/Elements/ModifyTemplate:57 html/Admin/Groups/Modify.html:71 html/Admin/Queues/Modify.html:69 html/Search/Elements/EditSearches:56 html/User/Groups/Modify.html:70
-msgid "Description"
-msgstr "æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Description of Responsibility"
-msgstr "經辦業務說明"
-
-#: NOT FOUND IN SOURCE
-msgid "Description:"
-msgstr "æ述:"
-
-#: NOT FOUND IN SOURCE
-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 "殘障類別"
-
-#: NOT FOUND IN SOURCE
-msgid "Disabled"
-msgstr "åœç”¨"
-
-#: html/Search/Elements/EditFormat:71 html/Ticket/Elements/Tabs:108
-msgid "Display"
-msgstr "顯示內容"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "Display Access Control List"
-msgstr "顯示權é™æŽ§åˆ¶æ¸…å–®"
-
-#: html/Search/Elements/DisplayOptions:46
-msgid "Display Columns"
-msgstr "顯示欄ä½"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "Display Scrip templates for this queue"
-msgstr "顯示此表單的範本"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "Display Scrips for this queue"
-msgstr "顯示此表單的手續"
-
-#: html/Ticket/Elements/ShowHistory:59
-msgid "Display mode"
-msgstr "顯示模å¼"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "Display saved searches for this group"
-msgstr "顯示此群組已儲存的查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "Display ticket #%1"
-msgstr "顯示第%1號申請單"
-
-#: html/Elements/Footer:61
-msgid "Distributed under version 2 <a href=\"http://www.gnu.org/copyleft/gpl.html\"> of the GNU GPL.</a>"
-msgstr "ä¾ <a href=\"http://www.gnu.org/copyleft/gpl.html\">GNU 通用公共授權</a> 第二版散布。"
-
-#: lib/RT/System.pm:75
-msgid "Do anything and everything"
-msgstr "å…許一切æ“作"
-
-#: html/Elements/Refresh:51
-msgid "Don't refresh this page."
-msgstr "ä¸æ›´æ–°æ­¤é é¢ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Don't show search results"
-msgstr "ä¸é¡¯ç¤ºæŸ¥è©¢çµæžœ"
-
-#: NOT FOUND IN SOURCE
-msgid "Done"
-msgstr "完æˆ"
-
-#: NOT FOUND IN SOURCE
-msgid "Down"
-msgstr "下一é "
-
-#: html/Ticket/Elements/ShowTransactionAttachments:82
-msgid "Download"
-msgstr "下載"
-
-#: html/Admin/Groups/index.html:61 html/Admin/Users/index.html:64
-msgid "Download as a tab-delimited file"
-msgstr "下載以 Tab 分隔的檔案"
-
-#: NOT FOUND IN SOURCE
-msgid "Dr."
-msgstr "åšå£«"
-
-#: html/Elements/SelectDateType:53 html/Ticket/Create.html:209 html/Ticket/Elements/EditDates:66 html/Ticket/Elements/Reminders:133 html/Ticket/Elements/ShowDates:64 lib/RT/Ticket_Overlay.pm:1173
-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'"
-
-#: NOT FOUND IN SOURCE
-msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
-msgstr "無法載入申請單 '%1':%2.\\n"
-
-#: html/Elements/Quicksearch:48 html/Elements/ShowSearch:49 html/index.html:107
-msgid "Edit"
-msgstr "編輯"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Conditions"
-msgstr "編輯å‰ç½®æ¢ä»¶"
-
-#: html/Search/Bulk.html:149
-msgid "Edit Custom Fields"
-msgstr ""
-
-#: html/Admin/Elements/ObjectCustomFields:92 html/Admin/Queues/CustomFields.html:64 html/Admin/Users/CustomFields.html:64
-#. ($Object->Name)
-msgid "Edit Custom Fields for %1"
-msgstr "編輯 %1 的自訂欄ä½"
-
-#: html/Admin/Global/CustomFields/Groups.html:54
-msgid "Edit Custom Fields for all groups"
-msgstr ""
-
-#: html/Admin/Global/CustomFields/Users.html:54
-msgid "Edit Custom Fields for all users"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Custom Fields for queue %1"
-msgstr "編輯表單 %1 的自訂欄ä½"
-
-#: html/Admin/Global/CustomFields/Queue-Tickets.html:54 html/Admin/Global/CustomFields/Queue-Transactions.html:54
-msgid "Edit Custom Fields for tickets in all queues"
-msgstr ""
-
-#: html/Search/Bulk.html:188 html/Ticket/ModifyLinks.html:57
-msgid "Edit Links"
-msgstr "編輯申請單關係"
-
-#: html/Search/Edit.html:68
-msgid "Edit Query"
-msgstr "編輯查詢"
-
-#: html/Ticket/Elements/Tabs:214
-msgid "Edit Search"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Subgroups"
-msgstr "新增/維護å­ç¾¤çµ„"
-
-#: html/Admin/Queues/Templates.html:63
-#. ($QueueObj->Name)
-msgid "Edit Templates for queue %1"
-msgstr "編輯表單 %1 的範本"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit Workflows for queue %1"
-msgstr "編輯表單 %1 çš„æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit keywords"
-msgstr "編輯關éµå­—"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "Edit saved searches for this group"
-msgstr "編輯此群組已儲存的查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "Edit scrips"
-msgstr "編輯手續"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:60 html/Admin/Global/index.html:67
-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 çš„æµç¨‹"
-
-#: lib/RT/Group_Overlay.pm:167
-msgid "EditSavedSearches"
-msgstr "編輯已儲存的查詢"
-
-#: html/Admin/Queues/Modify.html:140
-#. ($QueueObj->Name)
-msgid "Editing Configuration for queue %1"
-msgstr "編輯表單 %1 的設定"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing Configuration for user %1"
-msgstr "編輯使用者 %1 的設定"
-
-#: html/Admin/CustomFields/Modify.html:167 html/Admin/Elements/EditCustomField:120
-#. ($CustomFieldObj->Name())
-msgid "Editing CustomField %1"
-msgstr "ç·¨è¼¯è‡ªè¨‚æ¬„ä½ %1"
-
-#: html/Admin/Groups/Members.html:53
-#. ($Group->Name)
-msgid "Editing membership for group %1"
-msgstr "編輯群組 %1 çš„æˆå“¡è³‡è¨Š"
-
-#: html/User/Groups/Members.html:150
-#. ($Group->Name)
-msgid "Editing membership for personal group %1"
-msgstr "編輯代ç†äººç¾¤çµ„ %1 çš„æˆå“¡è³‡è¨Š"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing template %1"
-msgstr "編輯範本 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Editing workflow %1"
-msgstr "編輯æµç¨‹ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Education"
-msgstr "最高學歷"
-
-#: NOT FOUND IN SOURCE
-msgid "EffectiveId"
-msgstr "有效編號"
-
-#: lib/RT/Record.pm:1295 lib/RT/Record.pm:1372 lib/RT/Ticket_Overlay.pm:2518 lib/RT/Ticket_Overlay.pm:2608
-msgid "Either base or target must be specified"
-msgstr "需è¦æŒ‡å®šèµ·å§‹ç”³è«‹å–®æˆ–目的申請單"
-
-#: html/Admin/Users/Modify.html:74 html/Ticket/Elements/AddWatchers:77 html/User/Prefs.html:65
-msgid "Email"
-msgstr "é›»å­éƒµä»¶ä¿¡ç®±"
-
-#: NOT FOUND IN SOURCE
-msgid "Email Address"
-msgstr "é›»å­éƒµä»¶ä¿¡ç®±"
-
-#: lib/RT/User_Overlay.pm:235
-msgid "Email address in use"
-msgstr "此電å­éƒµä»¶ä¿¡ç®±å·²è¢«ä½¿ç”¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EmailAddress"
-msgstr "é›»å­éƒµä»¶ä¿¡ç®±ä½å€"
-
-#: NOT FOUND IN SOURCE
-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/CustomFields/Modify.html:98 html/Admin/Elements/EditCustomField:72
-msgid "Enabled (Unchecking this box disables this custom field)"
-msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤è‡ªè¨‚欄ä½)"
-
-#: html/Admin/Groups/Modify.html:84 html/User/Groups/Modify.html:74
-msgid "Enabled (Unchecking this box disables this group)"
-msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤ç¾¤çµ„)"
-
-#: html/Admin/Queues/Modify.html:105
-msgid "Enabled (Unchecking this box disables this queue)"
-msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤è¡¨å–®)"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Custom Fields"
-msgstr "已啟用的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Date"
-msgstr "啟用日期"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Date:"
-msgstr "啟動日期:"
-
-#: html/Admin/Queues/index.html:78
-msgid "Enabled Queues"
-msgstr "已啟用的表單"
-
-#: NOT FOUND IN SOURCE
-msgid "Enabled Status"
-msgstr "啟用狀態"
-
-#: html/Admin/Elements/EditCustomField:136 html/Admin/Groups/Modify.html:150 html/Admin/Users/Modify.html:350 html/User/Groups/Modify.html:138
-#. (loc_fuzzy($msg))
-msgid "Enabled status %1"
-msgstr "啟用狀態 %1"
-
-#: html/Admin/CustomFields/Modify.html:185 html/Admin/Queues/Modify.html:162
-#. (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:64
-msgid "Enter multiple values"
-msgstr "éµå…¥å¤šé‡é …ç›®"
-
-#: html/Elements/EditLinks:126
-msgid "Enter objects or URIs to link objects to. Separate multiple entries with spaces."
-msgstr "éµå…¥æ¬²å°‡ç‰©ä»¶é€£çµè‡³çš„物件或 URI。項目之間請以空白隔開。"
-
-#: NOT FOUND IN SOURCE
-msgid "Enter one or more conditions below to search for users"
-msgstr "éµå…¥ä¸‹åˆ—單一或複å¼æ¢ä»¶ï¼ŒæŸ¥è©¢ç”¨æˆ¶è³‡æ–™"
-
-#: lib/RT/CustomField_Overlay.pm:65
-msgid "Enter one value"
-msgstr "éµå…¥å–®ä¸€é …ç›®"
-
-#: html/Elements/EditLinks:123
-msgid "Enter queues or URIs to link queues to. Separate multiple entries with spaces."
-msgstr "éµå…¥æ¬²å°‡è¡¨å–®é€£çµè‡³çš„物件或 URI。項目之間請以空白隔開。"
-
-#: html/Elements/EditLinks:119 html/Search/Bulk.html:189
-msgid "Enter tickets or URIs to link tickets to. Separate multiple entries with spaces."
-msgstr "éµå…¥ç”³è«‹å–®å¯éˆçµåˆ°çš„申請單編號或網å€ã€‚項目之間請以空白隔開。"
-
-#: lib/RT/CustomField_Overlay.pm:66
-msgid "Enter up to %1 values"
-msgstr "éµå…¥æœ€å¤š %1 個項目"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryBoolean"
-msgstr "是éžå¡«è¡¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryDate"
-msgstr "日期填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryExternal"
-msgstr "系統填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryFreeform"
-msgstr "輸入填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryMultiple"
-msgstr "多é¸å¡«è¡¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryNumber"
-msgstr "數值填表"
-
-#: NOT FOUND IN SOURCE
-msgid "EntrySelect"
-msgstr "å–®é¸å¡«è¡¨"
-
-#: NOT FOUND IN SOURCE
-msgid "EntryTime"
-msgstr "時間填表"
-
-#: html/Elements/Login:76 html/SelfService/Error.html:46 html/SelfService/Error.html:47
-msgid "Error"
-msgstr "錯誤"
-
-#: NOT FOUND IN SOURCE
-msgid "Error adding watcher"
-msgstr "新增視察員失敗"
-
-#: lib/RT/Queue_Overlay.pm:672
-msgid "Error in parameters to Queue->AddWatcher"
-msgstr "表單->新增視察員的åƒæ•¸æœ‰èª¤"
-
-#: lib/RT/Queue_Overlay.pm:833
-msgid "Error in parameters to Queue->DeleteWatcher"
-msgstr "表單->刪除視察員的åƒæ•¸æœ‰èª¤"
-
-#: lib/RT/Ticket_Overlay.pm:1372
-msgid "Error in parameters to Ticket->AddWatcher"
-msgstr "申請單->新增視察員的åƒæ•¸æœ‰èª¤"
-
-#: lib/RT/Ticket_Overlay.pm:1538
-msgid "Error in parameters to Ticket->DeleteWatcher"
-msgstr "申請單->刪除視察員的åƒæ•¸æœ‰èª¤"
-
-#: bin/rt-crontool:285
-msgid "Escalate tickets"
-msgstr "調整申請單優先等級"
-
-#: NOT FOUND IN SOURCE
-msgid "Estimate"
-msgstr "é è¨ˆ"
-
-#: html/Ticket/Elements/ShowBasics:57
-msgid "Estimated"
-msgstr "é è¨ˆ"
-
-#: etc/initialdata:20
-msgid "Everyone"
-msgstr "所有人"
-
-#: bin/rt-crontool:271
-msgid "Example:"
-msgstr "範例:"
-
-#: NOT FOUND IN SOURCE
-msgid "Existing user renamed from %1 to %2"
-msgstr "ç¾æœ‰ä½¿ç”¨è€… %1 已改å為 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Export"
-msgstr "匯出"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalAuthId"
-msgstr "外部èªè­‰å¸³è™Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalContactInfoId"
-msgstr "外部è¯çµ¡æ–¹å¼å¸³è™Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabaseDSN"
-msgstr "外部資料庫連çµå­—串"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabasePass"
-msgstr "外部資料庫密碼"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalDatabaseUser"
-msgstr "外部資料庫用戶"
-
-#: NOT FOUND IN SOURCE
-msgid "ExternalURL"
-msgstr "外部介é¢ç¶²å€"
-
-#: html/Admin/Users/Modify.html:99
-msgid "Extra info"
-msgstr "備註"
-
-#: lib/RT/SavedSearch.pm:177
-msgid "Failed to create search attribute"
-msgstr "查詢屬性建立失敗"
-
-#: lib/RT/User_Overlay.pm:376
-msgid "Failed to find 'Privileged' users pseudogroup."
-msgstr "找ä¸åˆ°ã€Œå…§éƒ¨æˆå“¡ã€è™›æ“¬ç¾¤çµ„的使用者。"
-
-#: lib/RT/User_Overlay.pm:383
-msgid "Failed to find 'Unprivileged' users pseudogroup"
-msgstr "找ä¸åˆ°ã€Œéžå…§éƒ¨æˆå“¡ã€è™›æ“¬ç¾¤çµ„的使用者。"
-
-#: bin/rt-crontool:206
-#. ($modname, $@)
-msgid "Failed to load module %1. (%2)"
-msgstr "無法載入模組 %1. (%2)"
-
-#: lib/RT/SavedSearch.pm:152
-#. ($privacy)
-msgid "Failed to load object for %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Feb"
-msgstr "二月"
-
-#: lib/RT/Date.pm:442
-msgid "Feb."
-msgstr "02"
-
-#: NOT FOUND IN SOURCE
-msgid "February"
-msgstr "二月"
-
-#: NOT FOUND IN SOURCE
-msgid "Female"
-msgstr "女"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Content:"
-msgstr "欄ä½å…§å®¹ï¼š"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Description"
-msgstr "欄ä½æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Name"
-msgstr "欄ä½å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Field Type"
-msgstr "欄ä½é¡žåˆ¥"
-
-#: html/Elements/SelectAttachmentField:50
-msgid "Filename"
-msgstr "檔å"
-
-#: lib/RT/CustomField_Overlay.pm:69
-msgid "Fill in multiple text areas"
-msgstr "填入多個文字框"
-
-#: lib/RT/CustomField_Overlay.pm:74
-msgid "Fill in multiple wikitext areas"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:70
-msgid "Fill in one text area"
-msgstr "填入一個文字框"
-
-#: lib/RT/CustomField_Overlay.pm:75
-msgid "Fill in one wikitext area"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:107 html/Admin/CustomFields/Modify.html:118
-msgid "Fill in this field with a URL."
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:71
-msgid "Fill in up to %1 text areas"
-msgstr "填入最多 %1 個文字框"
-
-#: lib/RT/CustomField_Overlay.pm:76
-msgid "Fill in up to %1 wikitext areas"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Filter"
-msgstr "篩é¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Filter people"
-msgstr "å°è±¡ç¯©é¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Filtered list:"
-msgstr "篩é¸åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Fin"
-msgstr "最終"
-
-#: html/Search/Elements/PickBasics:149 html/Ticket/Create.html:182 html/Ticket/Elements/EditBasics:97 lib/RT/Tickets_Overlay.pm:1841
-msgid "Final Priority"
-msgstr "最終順ä½"
-
-#: lib/RT/Ticket_Overlay.pm:1164
-msgid "FinalPriority"
-msgstr "最終順ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Financial Department:"
-msgstr "財務部:"
-
-#: NOT FOUND IN SOURCE
-msgid "Find group whose"
-msgstr "尋找群組的"
-
-#: html/Admin/Groups/index.html:72 html/Admin/Queues/People.html:82 html/Ticket/Elements/EditPeople:55
-msgid "Find groups whose"
-msgstr "尋找群組的"
-
-#: NOT FOUND IN SOURCE
-msgid "Find new/open tickets"
-msgstr "尋找/開啟申請單"
-
-#: html/Admin/Queues/People.html:78 html/Admin/Users/index.html:70 html/Ticket/Elements/EditPeople:51
-msgid "Find people whose"
-msgstr "尋找人員的"
-
-#: NOT FOUND IN SOURCE
-msgid "Find queues whose"
-msgstr "尋找表單的"
-
-#: html/Search/Results.html:147
-msgid "Find tickets"
-msgstr "尋找申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Finish Approval"
-msgstr "簽核完畢"
-
-#: html/Ticket/Elements/Tabs:81
-msgid "First"
-msgstr "第一項"
-
-#: NOT FOUND IN SOURCE
-msgid "First page"
-msgstr "第一é "
-
-#: NOT FOUND IN SOURCE
-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:766
-msgid "Foo Bar Baz"
-msgstr "甲 乙 丙"
-
-#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:757
-msgid "Foo!"
-msgstr "甲ï¼"
-
-#: html/Search/Bulk.html:83
-msgid "Force change"
-msgstr "強制更æ›"
-
-#: NOT FOUND IN SOURCE
-msgid "Form Processing"
-msgstr "é›»å­è¡¨å–®ä½œæ¥­å€"
-
-#: html/Search/Elements/EditFormat:52
-msgid "Format"
-msgstr ""
-
-#: html/Search/Results.html:145
-#. ($ticketcount)
-msgid "Found %quant(%1,ticket)"
-msgstr "找到 %1 張申請單"
-
-#: lib/RT/Record.pm:956
-msgid "Found Object"
-msgstr "已找到物件"
-
-#: NOT FOUND IN SOURCE
-msgid "Fourth-"
-msgstr "å››"
-
-#: NOT FOUND IN SOURCE
-msgid "Freeform"
-msgstr "輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformContactInfo"
-msgstr "è¯çµ¡æ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformDate"
-msgstr "日期輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformExternal"
-msgstr "系統欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformMultiple"
-msgstr "多é‡è¼¸å…¥"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformNumber"
-msgstr "數值輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformPassword"
-msgstr "密碼輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformSingle"
-msgstr "單一輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "FreeformTime"
-msgstr "時間輸入"
-
-#: NOT FOUND IN SOURCE
-msgid "Fri"
-msgstr "星期五"
-
-#: lib/RT/Date.pm:421
-msgid "Fri."
-msgstr "星期五"
-
-#: html/Ticket/Elements/ShowHistory:66 html/Ticket/Elements/ShowHistory:72
-msgid "Full headers"
-msgstr "完整標頭檔"
-
-#: NOT FOUND IN SOURCE
-msgid "Gecos"
-msgstr "登入帳號"
-
-#: NOT FOUND IN SOURCE
-msgid "Gender"
-msgstr "性別"
-
-#: html/Tools/Offline.html:85
-msgid "Get template from file"
-msgstr "å–出檔案裡的範本"
-
-#: NOT FOUND IN SOURCE
-msgid "Getting the current user from a pgp sig\\n"
-msgstr "å–å¾—ç›®å‰ä½¿ç”¨è€…çš„ pgp 簽章\\n"
-
-#: lib/RT/Transaction_Overlay.pm:684
-#. ($New->Name)
-msgid "Given to %1"
-msgstr "交予 %1"
-
-#: html/Admin/Elements/Tabs:65 html/Admin/index.html:82
-msgid "Global"
-msgstr "全域設定"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Approval"
-msgstr "全域簽核"
-
-#: html/Admin/Elements/EditCustomFields:55
-msgid "Global Custom Fields"
-msgstr "全域自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Keyword Selections"
-msgstr "全域關éµå­—é¸å–"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Rights:"
-msgstr "æ“有全域權é™åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Scrips"
-msgstr "全域手續"
-
-#: NOT FOUND IN SOURCE
-msgid "Global Setup"
-msgstr "全域設定"
-
-#: html/Admin/Global/CustomFields/index.html:59
-msgid "Global custom field configuration"
-msgstr ""
-
-#: html/Admin/Global/MyRT.html:48
-#. ($pane)
-msgid "Global portlet %1 saved."
-msgstr ""
-
-#: html/Admin/Elements/SelectTemplate:59
-#. (loc($Template->Name))
-msgid "Global template: %1"
-msgstr "全域範本:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "GlobalApproval"
-msgstr "全域簽核"
-
-#: html/Admin/CustomFields/index.html:80 html/Search/Results.html:90 html/Tools/Offline.html:89
-msgid "Go"
-msgstr "執行"
-
-#: html/Admin/Groups/index.html:67 html/Admin/Groups/index.html:73 html/Admin/Queues/People.html:80 html/Admin/Queues/People.html:84 html/Admin/Queues/index.html:66 html/Admin/Users/index.html:73 html/Elements/RefreshHomepage:48 html/Search/Results.html:74 html/Ticket/Elements/EditPeople:53 html/Ticket/Elements/EditPeople:57
-msgid "Go!"
-msgstr "執行"
-
-#: NOT FOUND IN SOURCE
-msgid "Good pgp sig from %1\\n"
-msgstr "%1 的 pgp 簽章是正確的\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Goto page"
-msgstr "到é é¢"
-
-#: html/Elements/GotoTicket:46 html/SelfService/Elements/GotoTicket:46
-msgid "Goto ticket"
-msgstr "跳到申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Grand"
-msgstr "上"
-
-#: html/Ticket/Elements/AddWatchers:67 html/Ticket/Elements/ShowGroupMembers:55 html/User/Elements/DelegateRights:99
-msgid "Group"
-msgstr "群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Group %1 %2: %3"
-msgstr "群組 %1 %2:%3"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Admin"
-msgstr "群組管ç†å“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Description"
-msgstr "群組æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Management"
-msgstr "群組管ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Members"
-msgstr "群組æˆå“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Name"
-msgstr "群組å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Name:"
-msgstr "群組å稱:"
-
-#: html/Admin/Elements/CustomFieldTabs:68 html/Admin/Elements/GroupTabs:66 html/Admin/Elements/QueueTabs:82 html/Admin/Elements/SystemTabs:65 html/Admin/Global/index.html:76
-msgid "Group Rights"
-msgstr "群組權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Rights:"
-msgstr "æ“有群組權é™åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Setup"
-msgstr "群組設定"
-
-#: NOT FOUND IN SOURCE
-msgid "Group Status"
-msgstr "群組狀態"
-
-#: lib/RT/Group_Overlay.pm:983
-msgid "Group already has member"
-msgstr "群組內已有此æˆå“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Group could not be created."
-msgstr "無法新增群組"
-
-#: html/Admin/Groups/Modify.html:109
-#. ($create_msg)
-msgid "Group could not be created: %1"
-msgstr "無法新增群組:%1"
-
-#: lib/RT/Group_Overlay.pm:521
-msgid "Group created"
-msgstr "群組新增完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Group created: %1"
-msgstr "群組 %1 新增完畢"
-
-#: lib/RT/Group_Overlay.pm:1155
-msgid "Group has no such member"
-msgstr "群組沒有這個æˆå“¡"
-
-#: lib/RT/Group_Overlay.pm:963 lib/RT/Queue_Overlay.pm:748 lib/RT/Queue_Overlay.pm:808 lib/RT/Ticket_Overlay.pm:1430 lib/RT/Ticket_Overlay.pm:1510
-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 "æ“有表單權é™ç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "Group's"
-msgstr "群組之"
-
-#: NOT FOUND IN SOURCE
-msgid "Group:"
-msgstr "群組:"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:59 html/Admin/Elements/SelectNewGroupMembers:57 html/Admin/Elements/Tabs:56 html/Admin/Global/CustomFields/index.html:69 html/Admin/Groups/Members.html:86 html/Admin/Queues/People.html:104 html/Admin/Users/Memberships.html:53 html/Admin/index.html:67 html/User/Groups/Members.html:88 lib/RT/CustomField_Overlay.pm:1210
-msgid "Groups"
-msgstr "群組"
-
-#: lib/RT/Group_Overlay.pm:989
-msgid "Groups can't be members of their members"
-msgstr "ä¸èƒ½å°‡ç¾¤çµ„設為群組內æˆå“¡"
-
-#: html/Admin/Groups/index.html:86
-msgid "Groups matching search criteria"
-msgstr "符åˆæŸ¥è©¢æ¢ä»¶çš„群組"
-
-#: html/Ticket/Elements/ShowRequestor:77
-msgid "Groups this user belongs to"
-msgstr "使用者所屬的群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Groups with Global Rights"
-msgstr "æ“有全域權é™ç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "HRMSDefined"
-msgstr "組織架構"
-
-#: NOT FOUND IN SOURCE
-msgid "HTML Attributes"
-msgstr "HTML 屬性"
-
-#: NOT FOUND IN SOURCE
-msgid "Health Insurance"
-msgstr "å¥ä¿è£œåŠ©èº«ä»½"
-
-#: lib/RT/Interface/CLI.pm:94 lib/RT/Interface/CLI.pm:94
-msgid "Hello!"
-msgstr "å—¨ï¼"
-
-#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:773
-#. ($name)
-msgid "Hello, %1"
-msgstr "嗨,%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Help"
-msgstr "說明"
-
-#: NOT FOUND IN SOURCE
-msgid "Help Desks"
-msgstr "å„項業務窗å£"
-
-#: NOT FOUND IN SOURCE
-msgid "Hidden"
-msgstr "éš±è—"
-
-#: html/Admin/Elements/GroupTabs:70 html/Admin/Elements/UserTabs:64 html/Ticket/Elements/ShowHistory:53 html/Ticket/Elements/Tabs:111
-msgid "History"
-msgstr "紀錄"
-
-#: html/Admin/Groups/History.html:62
-#. ($GroupObj->Name)
-msgid "History of the group %1"
-msgstr "群組 %1 的紀錄"
-
-#: html/Admin/Users/History.html:62
-#. ($UserObj->Name)
-msgid "History of the user %1"
-msgstr "使用者 %1 的紀錄"
-
-#: NOT FOUND IN SOURCE
-msgid "HomePhone"
-msgstr "ä½è™•é›»è©±"
-
-#: html/Elements/Tabs:65
-msgid "Homepage"
-msgstr "主é "
-
-#: NOT FOUND IN SOURCE
-msgid "Hotel Expense"
-msgstr "ä½å®¿è²»"
-
-#: html/Elements/SelectTimeUnits:48
-msgid "Hours"
-msgstr ""
-
-#: lib/RT/Base.pm:119
-#. (6)
-msgid "I have %quant(%1,concrete mixer)."
-msgstr "我有 %quant(%1,份固體攪拌器)。"
-
-#: html/Search/Build.html:460 lib/RT/Report/Tickets.pm:415
-msgid "I'm lost"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "ID Number"
-msgstr "身分證號"
-
-#: NOT FOUND IN SOURCE
-msgid "ID Type"
-msgstr "身分類別"
-
-#: html/Ticket/Elements/ShowBasics:48 lib/RT/Tickets_Overlay.pm:1766
-msgid "Id"
-msgstr "編號"
-
-#: html/Admin/Users/Modify.html:65 html/User/Prefs.html:60
-msgid "Identity"
-msgstr "身份"
-
-#: etc/initialdata:429
-msgid "If an approval is rejected, reject the original and delete pending approvals"
-msgstr "若簽核單é­åˆ°é§å›žï¼Œå‰‡é€£å¸¶é§å›žåŽŸç”³è«‹å–®ï¼Œä¸¦åˆªé™¤å…¶ä»–相關的待簽核事項"
-
-#: html/Tools/Offline.html:74
-msgid "If no Requestor is specified, create tickets with this requestor."
-msgstr "若沒有指定申請者,則以此使用者作為申請者"
-
-#: html/Tools/Offline.html:65
-msgid "If no queue is specified, create tickets in this queue."
-msgstr "申請單若沒有指定表單,則將它新增在此表單內"
-
-#: bin/rt-crontool:267
-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:126 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:128 html/Ticket/ModifyPeople.html:60
-msgid "If you've updated anything above, be sure to"
-msgstr "若您已更新以上資料,請記得按一下"
-
-#: lib/RT/Record.pm:947
-msgid "Illegal value for %1"
-msgstr "%1 的值錯誤"
-
-#: NOT FOUND IN SOURCE
-msgid "Image"
-msgstr "圖片"
-
-#: lib/RT/Record.pm:950
-msgid "Immutable field"
-msgstr "此欄ä½å€¼ä¸å¯æ›´å‹•"
-
-#: NOT FOUND IN SOURCE
-msgid "Import"
-msgstr "匯入"
-
-#: NOT FOUND IN SOURCE
-msgid "Include disabled custom fields in listing."
-msgstr "列出åœç”¨çš„自訂欄ä½"
-
-#: html/Admin/Groups/index.html:65
-msgid "Include disabled groups in listing."
-msgstr "列出åœç”¨çš„群組"
-
-#: html/Admin/Queues/index.html:65
-msgid "Include disabled queues in listing."
-msgstr "列出åœç”¨çš„表單"
-
-#: html/Admin/Users/index.html:71
-msgid "Include disabled users in search."
-msgstr "列出åœç”¨çš„使用者"
-
-#: html/Admin/CustomFields/Modify.html:113
-msgid "Include page"
-msgstr ""
-
-#: html/Search/Build.html:486 lib/RT/Report/Tickets.pm:441
-msgid "Incomplete Query"
-msgstr ""
-
-#: html/Search/Build.html:483 lib/RT/Report/Tickets.pm:438
-msgid "Incomplete query"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Indirect Employee"
-msgstr "直接/間接員工"
-
-#: html/Search/Elements/PickBasics:148 lib/RT/Tickets_Overlay.pm:1816
-msgid "Initial Priority"
-msgstr "åˆå§‹å„ªå…ˆé †ä½"
-
-#: lib/RT/Ticket_Overlay.pm:1163 lib/RT/Ticket_Overlay.pm:1165
-msgid "InitialPriority"
-msgstr "åˆå§‹å„ªå…ˆé †ä½"
-
-#: lib/RT/ScripAction_Overlay.pm:133
-msgid "Input error"
-msgstr "輸入錯誤"
-
-#: html/Elements/ValidateCustomFields:68 lib/RT/CustomField_Overlay.pm:1021 lib/RT/CustomField_Overlay.pm:1162
-#. ($self->FriendlyPattern)
-#. ($CF->FriendlyPattern)
-msgid "Input must match %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Interest noted"
-msgstr "登記æˆåŠŸ"
-
-#: lib/RT/Ticket_Overlay.pm:3503
-msgid "Internal Error"
-msgstr "內部錯誤"
-
-#: lib/RT/Record.pm:308
-#. ($id->{error_message})
-msgid "Internal Error: %1"
-msgstr "內部錯誤:%1"
-
-#: lib/RT/Group_Overlay.pm:668
-msgid "Invalid Group Type"
-msgstr "錯誤的群組類別"
-
-#: lib/RT/Principal_Overlay.pm:161
-msgid "Invalid Right"
-msgstr "錯誤的權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid Type"
-msgstr "錯誤的類型"
-
-#: lib/RT/Record.pm:952
-msgid "Invalid data"
-msgstr "錯誤的資料"
-
-#: NOT FOUND IN SOURCE
-msgid "Invalid owner. Defaulting to 'nobody'."
-msgstr "錯誤的承辦人。改為é è¨­æ‰¿è¾¦äººã€Œnobodyã€ã€‚"
-
-#: lib/RT/CustomField_Overlay.pm:207 lib/RT/CustomField_Overlay.pm:678
-#. ($msg)
-msgid "Invalid pattern: %1"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:157 lib/RT/Template_Overlay.pm:244
-msgid "Invalid queue"
-msgstr "錯誤的表單"
-
-#: lib/RT/ACE_Overlay.pm:264 lib/RT/ACE_Overlay.pm:273 lib/RT/ACE_Overlay.pm:279 lib/RT/ACE_Overlay.pm:290
-msgid "Invalid right"
-msgstr "錯誤的權é™"
-
-#: lib/RT/Record.pm:283
-#. ($key)
-msgid "Invalid value for %1"
-msgstr "%1 的值錯誤"
-
-#: lib/RT/Record.pm:1610
-msgid "Invalid value for custom field"
-msgstr "錯誤的自訂欄ä½å€¼"
-
-#: lib/RT/Ticket_Overlay.pm:424
-msgid "Invalid value for status"
-msgstr "錯誤的狀態值"
-
-#: NOT FOUND IN SOURCE
-msgid "IssueStatement"
-msgstr "é€å‡ºé™³è¿°"
-
-#: bin/rt-crontool:268
-msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
-msgstr "請絕å°ä¸è¦è®“未具權é™çš„使用者執行此工具程å¼ã€‚"
-
-#: bin/rt-crontool:269
-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:231
-msgid "It takes several arguments:"
-msgstr "它接å—下列åƒæ•¸ï¼š"
-
-#: html/Search/Elements/EditFormat:85
-msgid "Italic"
-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:441
-msgid "Jan."
-msgstr "01"
-
-#: NOT FOUND IN SOURCE
-msgid "January"
-msgstr "一月"
-
-#: NOT FOUND IN SOURCE
-msgid "Job"
-msgstr "è·ç¨±"
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "Join or leave this group"
-msgstr "加入或離開此群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Jul"
-msgstr "七月"
-
-#: lib/RT/Date.pm:447
-msgid "Jul."
-msgstr "07"
-
-#: NOT FOUND IN SOURCE
-msgid "July"
-msgstr "七月"
-
-#: html/Ticket/Elements/Tabs:125
-msgid "Jumbo"
-msgstr "全部資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Jun"
-msgstr "六月"
-
-#: lib/RT/Date.pm:446
-msgid "Jun."
-msgstr "06"
-
-#: NOT FOUND IN SOURCE
-msgid "June"
-msgstr "六月"
-
-#: NOT FOUND IN SOURCE
-msgid "Keyword"
-msgstr "é—œéµå­—"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelAttachments"
-msgstr "附件標籤"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelContent"
-msgstr "內容標籤"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelSubject"
-msgstr "主題標籤"
-
-#: NOT FOUND IN SOURCE
-msgid "LabelURL"
-msgstr "éˆçµæ¨™ç±¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Lang"
-msgstr "使用語言"
-
-#: html/Admin/Users/Modify.html:94 html/User/Prefs.html:76
-msgid "Language"
-msgstr "語言"
-
-#: html/Search/Elements/EditFormat:79
-msgid "Large"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:96
-msgid "Last"
-msgstr "上次更新"
-
-#: html/Ticket/Elements/EditDates:59 html/Ticket/Elements/ShowDates:60
-msgid "Last Contact"
-msgstr "上次è¯çµ¡"
-
-#: html/Elements/SelectDateType:50
-msgid "Last Contacted"
-msgstr "上次è¯çµ¡æ—¥æœŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Last Notified"
-msgstr "上次通知"
-
-#: html/Elements/SelectDateType:51
-msgid "Last Updated"
-msgstr "上次更新"
-
-#: NOT FOUND IN SOURCE
-msgid "LastUpdated"
-msgstr "上次更新"
-
-#: html/Search/Elements/PickBasics:103
-msgid "LastUpdatedBy"
-msgstr "上次更新者"
-
-#: html/Ticket/Elements/ShowBasics:68
-msgid "Left"
-msgstr "剩餘時間"
-
-#: html/Admin/Users/Modify.html:109
-msgid "Let this user access RT"
-msgstr "å…許這å使用者登入"
-
-#: html/Admin/Users/Modify.html:113
-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/Search/Elements/EditFormat:68
-msgid "Link"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Link a Queue"
-msgstr "申請表單連çµ"
-
-#: lib/RT/Record.pm:1306
-msgid "Link already exists"
-msgstr "æ­¤éˆçµå·²å­˜åœ¨"
-
-#: lib/RT/Record.pm:1320
-msgid "Link could not be created"
-msgstr "無法新增éˆçµ"
-
-#: lib/RT/Record.pm:1326
-#. ($TransString)
-msgid "Link created (%1)"
-msgstr "éˆçµ(%1)新增完畢"
-
-#: lib/RT/Record.pm:1387
-#. ($TransString)
-msgid "Link deleted (%1)"
-msgstr "éˆçµ(%1)刪除完畢"
-
-#: lib/RT/Record.pm:1393
-msgid "Link not found"
-msgstr "找ä¸åˆ°éˆçµ"
-
-#: html/Ticket/ModifyLinks.html:46 html/Ticket/ModifyLinks.html:50
-#. ($Ticket->Id)
-msgid "Link ticket #%1"
-msgstr "éˆçµç”³è«‹å–® #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Link ticket %1"
-msgstr "éˆçµç”³è«‹å–® %1"
-
-#: html/Admin/CustomFields/Modify.html:102
-msgid "Link values to"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:700
-msgid "Linking. Permission denied"
-msgstr ""
-
-#: html/Ticket/Create.html:216 html/Ticket/Elements/ShowSummary:89 html/Ticket/Elements/Tabs:120 html/Ticket/ModifyAll.html:78
-msgid "Links"
-msgstr "éˆçµ"
-
-#: NOT FOUND IN SOURCE
-msgid "List All Users"
-msgstr "列出所有用戶資料"
-
-#: html/Search/Elements/EditSearches:75
-msgid "Load"
-msgstr "載入"
-
-#: html/Search/Elements/EditSearches:73
-msgid "Load saved search:"
-msgstr "載入已儲存的查詢:"
-
-#: lib/RT/System.pm:86
-msgid "LoadSavedSearch"
-msgstr ""
-
-#: html/Admin/Tools/Configuration.html:64
-msgid "Loaded perl modules"
-msgstr "已載入的 Perl 模組"
-
-#: lib/RT/SavedSearch.pm:111
-#. ($self->Name)
-msgid "Loaded search %1"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:138 html/User/Prefs.html:126
-msgid "Location"
-msgstr "ä½ç½®"
-
-#: NOT FOUND IN SOURCE
-msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
-msgstr "登入目錄 %1 找ä¸åˆ°æˆ–無法寫入\\n。無法執行 RT。"
-
-#: NOT FOUND IN SOURCE
-msgid "LogToFile"
-msgstr "紀錄等級"
-
-#: NOT FOUND IN SOURCE
-msgid "LogToFileNamed"
-msgstr "紀錄檔å"
-
-#: html/Elements/Header:91
-#. ("<span>".$session{'CurrentUser'}->Name."</span>")
-msgid "Logged in as %1"
-msgstr "使用者:%1"
-
-#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:100 html/Elements/Login:68 html/Elements/Login:84 lib/RT/StyleGuide.pod:797
-msgid "Login"
-msgstr "登入"
-
-#: html/Elements/Header:101
-msgid "Logout"
-msgstr "登出"
-
-#: NOT FOUND IN SOURCE
-msgid "Long-term contractor"
-msgstr "長期契約員工"
-
-#: lib/RT/CustomField_Overlay.pm:932
-msgid "Lookup type mismatch"
-msgstr "å°æ‡‰çš„類別ä¸ç¬¦"
-
-#: html/Search/Bulk.html:82
-msgid "Make Owner"
-msgstr "新增承辦人"
-
-#: html/Search/Bulk.html:106
-msgid "Make Status"
-msgstr "新增ç¾æ³"
-
-#: html/Search/Bulk.html:114
-msgid "Make date Due"
-msgstr "新增到期日"
-
-#: html/Search/Bulk.html:116
-msgid "Make date Resolved"
-msgstr "新增解決日期"
-
-#: html/Search/Bulk.html:110
-msgid "Make date Started"
-msgstr "新增實際起始日期"
-
-#: html/Search/Bulk.html:108
-msgid "Make date Starts"
-msgstr "新增應起始日期"
-
-#: html/Search/Bulk.html:112
-msgid "Make date Told"
-msgstr "新增報告日期"
-
-#: html/Search/Bulk.html:102
-msgid "Make priority"
-msgstr "新增優先順ä½"
-
-#: html/Search/Bulk.html:104
-msgid "Make queue"
-msgstr "新增表單"
-
-#: html/Search/Bulk.html:100
-msgid "Make subject"
-msgstr "新增主題"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "Make this group visible to user"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Male"
-msgstr "ç”·"
-
-#: html/Admin/index.html:78
-msgid "Manage custom fields and custom field values"
-msgstr "管ç†è‡ªè¨‚欄ä½åŠæ¬„ä½å€¼"
-
-#: html/Admin/index.html:69
-msgid "Manage groups and group membership"
-msgstr "管ç†ç¾¤çµ„åŠæ‰€å±¬æˆå“¡"
-
-#: html/Admin/index.html:85
-msgid "Manage properties and configuration which apply to all queues"
-msgstr "管ç†é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„屬性與設定"
-
-#: html/Admin/index.html:74
-msgid "Manage queues and queue-specific properties"
-msgstr "管ç†å„表單åŠç›¸é—œå±¬æ€§"
-
-#: html/Admin/index.html:64
-msgid "Manage users and passwords"
-msgstr "管ç†ä½¿ç”¨è€…與密碼"
-
-#: NOT FOUND IN SOURCE
-msgid "Manager"
-msgstr "經ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Mar"
-msgstr "三月"
-
-#: lib/RT/Date.pm:443
-msgid "Mar."
-msgstr "03"
-
-#: NOT FOUND IN SOURCE
-msgid "March"
-msgstr "三月"
-
-#: NOT FOUND IN SOURCE
-msgid "Marketing Department"
-msgstr "行銷部"
-
-#: NOT FOUND IN SOURCE
-msgid "Match Pattern"
-msgstr "符åˆæ¨£å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "May"
-msgstr "五月"
-
-#: lib/RT/Date.pm:445
-msgid "May."
-msgstr "05"
-
-#: lib/RT/Transaction_Overlay.pm:731
-#. ($value)
-msgid "Member %1 added"
-msgstr "æˆå“¡ %1 新增完畢"
-
-#: lib/RT/Transaction_Overlay.pm:771
-#. ($value)
-msgid "Member %1 deleted"
-msgstr "æˆå“¡ %1 刪除完畢"
-
-#: lib/RT/Group_Overlay.pm:1000
-msgid "Member added"
-msgstr "新增æˆå“¡å®Œç•¢"
-
-#: lib/RT/Group_Overlay.pm:1162
-msgid "Member deleted"
-msgstr "æˆå“¡å·²åˆªé™¤"
-
-#: lib/RT/Group_Overlay.pm:1166
-msgid "Member not deleted"
-msgstr "æˆå“¡æœªåˆªé™¤"
-
-#: html/Elements/SelectLinkType:47
-msgid "Member of"
-msgstr "隸屬於"
-
-#: NOT FOUND IN SOURCE
-msgid "Member since"
-msgstr "註冊日期"
-
-#: NOT FOUND IN SOURCE
-msgid "MemberOf"
-msgstr "隸屬於"
-
-#: html/Admin/Elements/GroupTabs:63 html/User/Elements/GroupTabs:63
-msgid "Members"
-msgstr "æˆå“¡"
-
-#: lib/RT/Transaction_Overlay.pm:728
-#. ($value)
-msgid "Membership in %1 added"
-msgstr "所屬群組 %1 加入完畢"
-
-#: lib/RT/Transaction_Overlay.pm:768
-#. ($value)
-msgid "Membership in %1 deleted"
-msgstr "所屬群組 %1 移除完畢"
-
-#: html/Admin/Elements/UserTabs:61
-msgid "Memberships"
-msgstr "所屬群組"
-
-#: html/Admin/Users/Memberships.html:60
-#. ($UserObj->Name)
-msgid "Memberships of the user %1"
-msgstr "使用者 %1 的所屬群組"
-
-#: lib/RT/Ticket_Overlay.pm:2893
-msgid "Merge Successful"
-msgstr "æ•´åˆå®Œç•¢"
-
-#: lib/RT/Ticket_Overlay.pm:2780
-msgid "Merge failed. Couldn't set EffectiveId"
-msgstr "æ•´åˆå¤±æ•—。無法設定 EffectiveId"
-
-#: lib/RT/Ticket_Overlay.pm:2788
-msgid "Merge failed. Couldn't set Status"
-msgstr ""
-
-#: html/Elements/EditLinks:131 html/Ticket/Elements/BulkLinks:48
-msgid "Merge into"
-msgstr "æ•´åˆé€²"
-
-#: lib/RT/Transaction_Overlay.pm:734
-#. ($value)
-msgid "Merged into %1"
-msgstr "已整åˆé€² %1"
-
-#: html/Search/Bulk.html:143 html/Ticket/Update.html:118
-msgid "Message"
-msgstr "訊æ¯"
-
-#: html/Ticket/Elements/ShowTransactionAttachments:164
-msgid "Message body not shown because it is too large or is not plain text."
-msgstr "信件內文ä¸æ˜¯ç´”文字,因此無法顯示。"
-
-#: lib/RT/Ticket_Overlay.pm:2451
-msgid "Message could not be recorded"
-msgstr "無法紀錄訊æ¯"
-
-#: lib/RT/Ticket_Overlay.pm:2454
-msgid "Message recorded"
-msgstr "訊æ¯ç´€éŒ„æˆåŠŸ"
-
-#: html/Ticket/Elements/PreviewScrips:122
-msgid "Messages about this ticket will not be sent to..."
-msgstr "此申請單的相關訊æ¯ä¸æœƒå¯„é€çµ¦..."
-
-#: html/Elements/SelectTimeUnits:47
-msgid "Minutes"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Misc. Expense"
-msgstr "雜費"
-
-#: html/Search/Build.html:490 lib/RT/Report/Tickets.pm:445
-msgid "Mismatched parentheses"
-msgstr "未å°é½Šçš„括號"
-
-#: lib/RT/Record.pm:954
-msgid "Missing a primary key?: %1"
-msgstr "缺少主éµå€¼ï¼Ÿ(%1)"
-
-#: NOT FOUND IN SOURCE
-msgid "Missing mandatory fields"
-msgstr "缺少必填欄ä½"
-
-#: html/Admin/Users/Modify.html:193 html/User/Prefs.html:92
-msgid "Mobile"
-msgstr "行動電話"
-
-#: NOT FOUND IN SOURCE
-msgid "MobilePhone"
-msgstr "行動電話"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "Modify Access Control List"
-msgstr "更改權é™æŽ§åˆ¶æ¸…å–®"
-
-#: html/Admin/Elements/ObjectCustomFields:96
-#. (loc(lc($FriendlySubTypes)), loc(lc($Types)))
-msgid "Modify Custom Fields which apply to %1 for all %2"
-msgstr "更改é©ç”¨æ–¼ %1 內所有 %2 的自訂欄ä½"
-
-#: html/Admin/Elements/ObjectCustomFields:98
-#. (loc(lc($Types)))
-msgid "Modify Custom Fields which apply to all %1"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰%1的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Custom Fields which apply to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
-
-#: html/Admin/Global/GroupRights.html:106 html/Admin/Groups/GroupRights.html:94 html/Admin/Queues/GroupRights.html:107
-msgid "Modify Group Rights"
-msgstr "更改群組權é™"
-
-#: html/Admin/Groups/Members.html:105 html/User/Groups/Members.html:101
-msgid "Modify Members"
-msgstr "更改æˆå“¡"
-
-#: html/User/Delegation.html:58
-msgid "Modify Rights"
-msgstr "更改權é™"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "Modify Scrip templates for this queue"
-msgstr "更改此表單的範本"
-
-#: lib/RT/Queue_Overlay.pm:100
-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"
-
-#: html/Admin/Global/UserRights.html:75 html/Admin/Groups/UserRights.html:76 html/Admin/Queues/UserRights.html:75
-msgid "Modify User Rights"
-msgstr "更改使用者權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify Workflow"
-msgstr "更改æµç¨‹"
-
-#: html/Admin/Queues/CustomField.html:66
-#. ($QueueObj->Name())
-msgid "Modify a CustomField for queue %1"
-msgstr "更改 %1 表單內的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify a CustomField that applies to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
-
-#: html/Admin/Queues/Scrip.html:82
-#. ($QueueObj->Name)
-msgid "Modify a scrip for queue %1"
-msgstr "更改 %1 表單內的手續"
-
-#: html/Admin/Global/Scrip.html:75
-msgid "Modify a scrip that applies to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
-
-#: html/Admin/CustomFields/Objects.html:90
-#. ($CF->Name)
-msgid "Modify associated objects for %1"
-msgstr "更改é©ç”¨ %1 的物件"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify dates for # %1"
-msgstr "更改 # %1 的日期"
-
-#: html/Ticket/ModifyDates.html:46 html/Ticket/ModifyDates.html:50
-#. ($TicketObj->Id)
-msgid "Modify dates for #%1"
-msgstr "更改 #%1 的日期"
-
-#: html/Ticket/ModifyDates.html:57
-#. ($TicketObj->Id)
-msgid "Modify dates for ticket # %1"
-msgstr "更改申請單 # %1 的日期"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:65 html/Admin/Global/index.html:72
-msgid "Modify global custom fields"
-msgstr "更改全域自訂欄ä½"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:70 html/Admin/Global/GroupRights.html:46 html/Admin/Global/GroupRights.html:49 html/Admin/Global/index.html:77
-msgid "Modify global group rights"
-msgstr "更改全域設定的群組權é™"
-
-#: html/Admin/Global/GroupRights.html:54
-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:46 html/Admin/Global/UserRights.html:49 html/Admin/Global/index.html:81
-msgid "Modify global user rights"
-msgstr "更改全域設定的使用者權é™"
-
-#: html/Admin/Global/UserRights.html:54
-msgid "Modify global user rights."
-msgstr "更改全域設定的使用者權é™ã€‚"
-
-#: lib/RT/Group_Overlay.pm:163
-msgid "Modify group metadata or delete group"
-msgstr "更改群組資料åŠåˆªé™¤ç¾¤çµ„"
-
-#: html/Admin/CustomFields/GroupRights.html:164
-#. ($CustomFieldObj->Name)
-msgid "Modify group rights for custom field %1"
-msgstr "æ›´æ”¹è‡ªè¨‚æ¬„ä½ %1 的群組權é™"
-
-#: html/Admin/Groups/GroupRights.html:46 html/Admin/Groups/GroupRights.html:50 html/Admin/Groups/GroupRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify group rights for group %1"
-msgstr "更改群組 %1 的群組權é™"
-
-#: html/Admin/Queues/GroupRights.html:46 html/Admin/Queues/GroupRights.html:50
-#. ($QueueObj->Name)
-msgid "Modify group rights for queue %1"
-msgstr "更改表單 %1 的群組權é™"
-
-#: lib/RT/Group_Overlay.pm:165
-msgid "Modify membership roster for this group"
-msgstr "更改此群組的æˆå“¡åå–®"
-
-#: lib/RT/System.pm:82
-msgid "Modify one's own RT account"
-msgstr "更改個人的帳號資訊"
-
-#: html/Admin/Queues/People.html:46 html/Admin/Queues/People.html:50
-#. ($QueueObj->Name)
-msgid "Modify people related to queue %1"
-msgstr "更改éˆçµåˆ°è¡¨å–® %1 的人員"
-
-#: html/Ticket/ModifyPeople.html:46 html/Ticket/ModifyPeople.html:50 html/Ticket/ModifyPeople.html:57
-#. ($Ticket->id)
-#. ($Ticket->Id)
-msgid "Modify people related to ticket #%1"
-msgstr "更改申請單 #%1 éˆçµåˆ°çš„人員"
-
-#: html/Admin/Queues/Scrips.html:67
-#. ($QueueObj->Name)
-msgid "Modify scrips for queue %1"
-msgstr "更改表單 %1 的手續"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:56 html/Admin/Global/Scrips.html:65 html/Admin/Global/index.html:63
-msgid "Modify scrips which apply to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
-
-#: html/Admin/Global/Template.html:102 html/Admin/Global/Template.html:46 html/Admin/Global/Template.html:51 html/Admin/Queues/Template.html:99
-#. (loc($TemplateObj->Name()))
-#. ($TemplateObj->id)
-msgid "Modify template %1"
-msgstr "更改範本 %1"
-
-#: html/Admin/Global/Templates.html:65
-msgid "Modify templates which apply to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„範本"
-
-#: html/Admin/Global/index.html:85
-msgid "Modify the default \"RT at a glance\" view"
-msgstr "更改é è¨­çš„「RT 一覽ã€æª¢è¦–"
-
-#: html/Admin/Groups/Modify.html:119 html/User/Groups/Modify.html:107
-#. ($Group->Name)
-msgid "Modify the group %1"
-msgstr "更改群組 %1"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "Modify the queue watchers"
-msgstr "更改表單視察員"
-
-#: html/Admin/Users/Modify.html:309
-#. ($UserObj->Name)
-msgid "Modify the user %1"
-msgstr "更改使用者 %1"
-
-#: html/Ticket/ModifyAll.html:58
-#. ($Ticket->Id)
-msgid "Modify ticket # %1"
-msgstr "更改申請單 # %1"
-
-#: html/Ticket/Modify.html:46 html/Ticket/Modify.html:49 html/Ticket/Modify.html:55
-#. ($TicketObj->Id)
-msgid "Modify ticket #%1"
-msgstr "更改申請單 # %1"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "Modify tickets"
-msgstr "更改申請單"
-
-#: html/Admin/CustomFields/UserRights.html:157
-#. ($CustomFieldObj->Name)
-msgid "Modify user rights for custom field %1"
-msgstr "æ›´æ”¹è‡ªè¨‚æ¬„ä½ %1 的使用者權é™"
-
-#: html/Admin/Groups/UserRights.html:46 html/Admin/Groups/UserRights.html:50 html/Admin/Groups/UserRights.html:56
-#. ($GroupObj->Name)
-msgid "Modify user rights for group %1"
-msgstr "更改群組 %1 的使用者權é™"
-
-#: html/Admin/Queues/UserRights.html:46 html/Admin/Queues/UserRights.html:50
-#. ($QueueObj->Name)
-msgid "Modify user rights for queue %1"
-msgstr "更改表單 %1 的使用者權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify watchers for queue '%1'"
-msgstr "更改 '%1' 的視察員"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify workflow %1"
-msgstr "更改æµç¨‹ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Modify workflows which apply to all queues"
-msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„æµç¨‹"
-
-#: lib/RT/Queue_Overlay.pm:94
-msgid "ModifyACL"
-msgstr "更改權é™æ¸…å–®"
-
-#: lib/RT/CustomField_Overlay.pm:108
-msgid "ModifyCustomField"
-msgstr "更改自訂欄ä½"
-
-#: lib/RT/Group_Overlay.pm:166
-msgid "ModifyOwnMembership"
-msgstr "更改自己是å¦å±¬æ–¼æŸç¾¤çµ„"
-
-#: lib/RT/Queue_Overlay.pm:95
-msgid "ModifyQueueWatchers"
-msgstr "更改表單視察員"
-
-#: lib/RT/Queue_Overlay.pm:100
-msgid "ModifyScrips"
-msgstr "更改手續"
-
-#: lib/RT/System.pm:82
-msgid "ModifySelf"
-msgstr "更改個人帳號"
-
-#: lib/RT/Queue_Overlay.pm:97
-msgid "ModifyTemplate"
-msgstr "更改範本"
-
-#: lib/RT/Queue_Overlay.pm:113
-msgid "ModifyTicket"
-msgstr "更改申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Mon"
-msgstr "星期一"
-
-#: lib/RT/Date.pm:417
-msgid "Mon."
-msgstr "星期一"
-
-#: NOT FOUND IN SOURCE
-msgid "More"
-msgstr "更多"
-
-#: html/Ticket/Elements/ShowRequestor:61
-#. ($name)
-msgid "More about %1"
-msgstr "關於 %1 的進一步資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Morning Shift"
-msgstr "æ—©ç­"
-
-#: NOT FOUND IN SOURCE
-msgid "Move"
-msgstr "移動"
-
-#: NOT FOUND IN SOURCE
-msgid "Move All"
-msgstr "全移"
-
-#: html/Admin/Elements/PickCustomFields:83
-msgid "Move down"
-msgstr "下移"
-
-#: html/Admin/Elements/PickCustomFields:75
-msgid "Move up"
-msgstr "上移"
-
-#: html/Admin/Elements/SelectSingleOrMultiple:48
-msgid "Multiple"
-msgstr "多é‡"
-
-#: lib/RT/User_Overlay.pm:226
-msgid "Must specify 'Name' attribute"
-msgstr "必須指定 'Name' 的屬性"
-
-#: html/SelfService/Elements/MyRequests:57
-#. ($friendly_status)
-msgid "My %1 tickets"
-msgstr "我的 %1 申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "My Approvals"
-msgstr "表單簽核"
-
-#: html/Tools/Elements/Tabs:63
-msgid "My Day"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "My Requests"
-msgstr "表單申請追蹤"
-
-#: NOT FOUND IN SOURCE
-msgid "My Tickets"
-msgstr "表單處ç†"
-
-#: html/Approvals/index.html:46 html/Approvals/index.html:47
-msgid "My approvals"
-msgstr "表單簽核"
-
-#: html/Search/Elements/SearchPrivacy:50 html/Search/Elements/SelectSearchObject:53 html/Search/Elements/SelectSearchesForObjects:54
-msgid "My saved searches"
-msgstr "我已儲存的查詢"
-
-#: html/Admin/CustomFields/Modify.html:58 html/Admin/Elements/AddCustomFieldValue:53 html/Admin/Elements/EditCustomField:55 html/Admin/Elements/EditCustomFieldValues:55 html/Admin/Elements/ModifyTemplate:49 html/Admin/Groups/Modify.html:65 html/Search/Bulk.html:157 html/User/Groups/Modify.html:65
-msgid "Name"
-msgstr "å稱"
-
-#: lib/RT/User_Overlay.pm:233
-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:73
-msgid "Never"
-msgstr "從未更動"
-
-#: NOT FOUND IN SOURCE
-msgid "New"
-msgstr "新建立"
-
-#: html/Elements/EditLinks:117
-msgid "New Links"
-msgstr "新增關係"
-
-#: html/Admin/Users/Modify.html:119 html/User/Prefs.html:109
-msgid "New Password"
-msgstr "新的密碼"
-
-#: etc/initialdata:332
-msgid "New Pending Approval"
-msgstr "新的待簽核事項"
-
-#: NOT FOUND IN SOURCE
-msgid "New Query"
-msgstr "新增查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "New Request"
-msgstr "表單申請"
-
-#: html/Ticket/Elements/Tabs:212
-msgid "New Search"
-msgstr "新增查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "New Watchers"
-msgstr "新增視察員"
-
-#: html/Admin/Elements/CustomFieldTabs:93 html/Admin/Queues/CustomField.html:73
-msgid "New custom field"
-msgstr "新增自訂欄ä½"
-
-#: html/Admin/Elements/GroupTabs:77 html/User/Elements/GroupTabs:73
-msgid "New group"
-msgstr "新增群組"
-
-#: html/SelfService/Prefs.html:53
-msgid "New password"
-msgstr "新的密碼"
-
-#: lib/RT/User_Overlay.pm:816
-msgid "New password notification sent"
-msgstr "é€å‡ºæ–°å¯†ç¢¼é€šçŸ¥"
-
-#: html/Admin/Elements/QueueTabs:95
-msgid "New queue"
-msgstr "新增表單"
-
-#: html/Ticket/Elements/Reminders:118
-msgid "New reminder:"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "New request"
-msgstr "æ出申請單"
-
-#: html/Admin/Elements/SelectRights:65
-msgid "New rights"
-msgstr "新增權é™"
-
-#: html/Admin/Global/Scrip.html:63 html/Admin/Global/Scrips.html:60 html/Admin/Queues/Scrip.html:71 html/Admin/Queues/Scrips.html:76
-msgid "New scrip"
-msgstr "新增手續"
-
-#: NOT FOUND IN SOURCE
-msgid "New search"
-msgstr "é‡æ–°æŸ¥è©¢"
-
-#: html/Admin/Global/Template.html:81 html/Admin/Global/Templates.html:60 html/Admin/Queues/Template.html:79 html/Admin/Queues/Templates.html:71
-msgid "New template"
-msgstr "新增範本"
-
-#: html/SelfService/Elements/Tabs:84 html/SelfService/Elements/Tabs:88
-msgid "New ticket"
-msgstr "æ出申請單"
-
-#: lib/RT/Ticket_Overlay.pm:2757
-msgid "New ticket doesn't exist"
-msgstr "沒有新申請單"
-
-#: html/Admin/Elements/UserTabs:81
-msgid "New user"
-msgstr "新增使用者"
-
-#: html/Admin/Elements/CreateUserCalled:47
-msgid "New user called"
-msgstr "新使用者åå­—"
-
-#: html/Admin/Queues/People.html:76 html/Ticket/Elements/EditPeople:50
-msgid "New watchers"
-msgstr "新視察員"
-
-#: NOT FOUND IN SOURCE
-msgid "New window setting"
-msgstr "更新視窗設定"
-
-#: NOT FOUND IN SOURCE
-msgid "New workflow"
-msgstr "新增æµç¨‹"
-
-#: html/Helpers/CalPopup.html:58 html/Ticket/Elements/Tabs:92
-msgid "Next"
-msgstr "下一項"
-
-#: html/Elements/TicketList:104
-msgid "Next Page"
-msgstr "下一é "
-
-#: NOT FOUND IN SOURCE
-msgid "Next page"
-msgstr "下一é "
-
-#: NOT FOUND IN SOURCE
-msgid "NickName"
-msgstr "暱稱"
-
-#: html/Admin/Users/Modify.html:84 html/User/Prefs.html:72
-msgid "Nickname"
-msgstr "暱稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Night Shift"
-msgstr "å°å¤œç­"
-
-#: NOT FOUND IN SOURCE
-msgid "No"
-msgstr "å¦"
-
-#: html/Admin/CustomFields/UserRights.html:145
-msgid "No Class defined"
-msgstr "尚未定義類別"
-
-#: html/Admin/CustomFields/Modify.html:166 html/Admin/Elements/EditCustomField:119
-msgid "No CustomField"
-msgstr "無自訂欄ä½"
-
-#: html/Admin/CustomFields/GroupRights.html:103
-msgid "No CustomField defined"
-msgstr "尚未定義自訂欄ä½"
-
-#: html/Admin/Groups/GroupRights.html:105 html/Admin/Groups/UserRights.html:92
-msgid "No Group defined"
-msgstr "尚未定義群組"
-
-#: lib/RT/Tickets_Overlay_SQL.pm:482
-msgid "No Query"
-msgstr "沒有查詢"
-
-#: html/Admin/Queues/GroupRights.html:118 html/Admin/Queues/UserRights.html:89
-msgid "No Queue defined"
-msgstr "尚未定義表單"
-
-#: bin/rt-crontool:73
-msgid "No RT user found. Please consult your RT administrator.\\n"
-msgstr "找ä¸åˆ° RT ä½¿ç”¨è€…ã€‚è«‹å‘ RT 管ç†å“¡æŸ¥è©¢ã€‚\\n"
-
-#: html/Admin/Global/Template.html:100 html/Admin/Queues/Template.html:97
-msgid "No Template"
-msgstr "沒有範本"
-
-#: NOT FOUND IN SOURCE
-msgid "No Ticket specified. Aborting ticket "
-msgstr "未指定申請單。退出申請單 "
-
-#: NOT FOUND IN SOURCE
-msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
-msgstr "未指定申請單。退出申請單更改\\n\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "No Workflow"
-msgstr "沒有æµç¨‹"
-
-#: html/Approvals/Elements/Approve:77
-msgid "No action"
-msgstr "æš«ä¸è™•ç†"
-
-#: lib/RT/Record.pm:949
-msgid "No column specified"
-msgstr "未指定欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "No command found\\n"
-msgstr "找ä¸åˆ°å‘½ä»¤"
-
-#: html/Ticket/Elements/ShowRequestor:68
-msgid "No comment entered about this user"
-msgstr "沒有å°é€™å使用者的評論"
-
-#: NOT FOUND IN SOURCE
-msgid "No correspondence attached"
-msgstr "沒有附上申請單回覆"
-
-#: lib/RT/Action/Generic.pm:185 lib/RT/Condition/Generic.pm:197 lib/RT/Search/ActiveTicketsInQueue.pm:77 lib/RT/Search/Generic.pm:134 lib/RT/Search/Googleish.pm:78
-#. (ref $self)
-msgid "No description for %1"
-msgstr "æ²’æœ‰å° %1 çš„æè¿°"
-
-#: lib/RT/Users_Overlay.pm:190
-msgid "No group specified"
-msgstr "未指定群組"
-
-#: html/Admin/Groups/index.html:52
-msgid "No groups matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è©¢æ¢ä»¶çš„群組。"
-
-#: lib/RT/Ticket_Overlay.pm:2393
-msgid "No message attached"
-msgstr "沒有附上訊æ¯"
-
-#: lib/RT/User_Overlay.pm:1034
-msgid "No password set"
-msgstr "沒有設定密碼"
-
-#: lib/RT/Queue_Overlay.pm:361
-msgid "No permission to create queues"
-msgstr "沒有新增表單的權é™"
-
-#: lib/RT/Ticket_Overlay.pm:420
-#. ($QueueObj->Name)
-msgid "No permission to create tickets in the queue '%1'"
-msgstr "沒有在表單 '%1' 新增申請單的權é™"
-
-#: lib/RT/User_Overlay.pm:186
-msgid "No permission to create users"
-msgstr "沒有新增使用者的權é™"
-
-#: html/SelfService/Display.html:167
-msgid "No permission to display that ticket"
-msgstr "沒有顯示該申請單的權é™"
-
-#: lib/RT/SavedSearch.pm:156
-msgid "No permission to save system-wide searches"
-msgstr "沒有儲存全域é å­˜æŸ¥è©¢çš„權é™"
-
-#: html/SelfService/Update.html:117
-msgid "No permission to view update ticket"
-msgstr "沒有檢視申請單更新的權é™"
-
-#: lib/RT/Queue_Overlay.pm:795 lib/RT/Ticket_Overlay.pm:1489
-msgid "No principal specified"
-msgstr "未指定單ä½"
-
-#: html/Admin/Queues/People.html:175 html/Admin/Queues/People.html:185
-msgid "No principals selected."
-msgstr "未指定單ä½ã€‚"
-
-#: NOT FOUND IN SOURCE
-msgid "No protocol specified in %1"
-msgstr "%1 內未指定å”定"
-
-#: html/Admin/Queues/index.html:57
-msgid "No queues matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è©¢æ¢ä»¶çš„表單。"
-
-#: html/Admin/Elements/SelectRights:106
-msgid "No rights found"
-msgstr "找ä¸åˆ°æ¬Šé™"
-
-#: html/Admin/Elements/SelectRights:53
-msgid "No rights granted."
-msgstr "沒有é¸å®šæ¬Šé™"
-
-#: lib/RT/SavedSearch.pm:196
-msgid "No search loaded"
-msgstr "尚未載入查詢"
-
-#: html/Search/Bulk.html:232
-msgid "No search to operate on."
-msgstr "沒有è¦é€²è¡Œçš„查詢"
-
-#: html/Elements/RT__Ticket/ColumnMap:137 html/Search/Results.rdf:78
-msgid "No subject"
-msgstr "沒有標題"
-
-#: NOT FOUND IN SOURCE
-msgid "No ticket id specified"
-msgstr "未指定申請單編號"
-
-#: lib/RT/Transaction_Overlay.pm:528 lib/RT/Transaction_Overlay.pm:565
-msgid "No transaction type specified"
-msgstr "未指定更動報告類別"
-
-#: NOT FOUND IN SOURCE
-msgid "No user or email address specified"
-msgstr "未指定使用者或電å­éƒµä»¶åœ°å€"
-
-#: html/Admin/Users/index.html:55
-msgid "No users matching search criteria found."
-msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è©¢æ¢ä»¶çš„使用者。"
-
-#: NOT FOUND IN SOURCE
-msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
-msgstr "找ä¸åˆ°åˆæ ¼çš„ RT 使用者。RT cvs 處ç†å™¨å·²åœç”¨ã€‚è«‹å‘ RT 管ç†è€…è©¢å•ã€‚\\n"
-
-#: lib/RT/Record.pm:946
-msgid "No value sent to _Set!\\n"
-msgstr "_Set 沒有收到任何值!\\n"
-
-#: html/Elements/QuickCreate:59
-msgid "Nobody"
-msgstr "沒有人"
-
-#: lib/RT/Record.pm:951
-msgid "Nonexistant field?"
-msgstr "欄ä½ä¸å­˜åœ¨ï¼Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Normal Users"
-msgstr "一般用戶群組"
-
-#: html/Search/Chart:71 html/Search/Elements/Chart:88
-msgid "Not Set"
-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:96
-msgid "Not logged in."
-msgstr "尚未登入"
-
-#: lib/RT/Date.pm:397
-msgid "Not set"
-msgstr "尚未設定"
-
-#: html/NoAuth/Reminder.html:48
-msgid "Not yet implemented."
-msgstr "尚未完工。"
-
-#: NOT FOUND IN SOURCE
-msgid "Not yet implemented...."
-msgstr "尚未完工..."
-
-#: html/Approvals/Elements/Approve:81
-msgid "Notes"
-msgstr "備註"
-
-#: NOT FOUND IN SOURCE
-msgid "Notes:"
-msgstr "備註:"
-
-#: lib/RT/User_Overlay.pm:819
-msgid "Notification could not be sent"
-msgstr "無法é€å‡ºé€šçŸ¥"
-
-#: etc/initialdata:101
-msgid "Notify AdminCcs"
-msgstr "通知管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:97
-msgid "Notify AdminCcs as Comment"
-msgstr "以評論方å¼é€šçŸ¥ç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:93 etc/upgrade/3.1.17/content:6
-msgid "Notify Ccs"
-msgstr "通知副本收件人"
-
-#: etc/initialdata:89 etc/upgrade/3.1.17/content:2
-msgid "Notify Ccs as Comment"
-msgstr "以評論方å¼é€šçŸ¥å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:128
-msgid "Notify Other Recipients"
-msgstr "通知其他收件人"
-
-#: etc/initialdata:124
-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:376
-msgid "Notify Owner of their rejected ticket"
-msgstr "通知承辦人申請單已é§å›ž"
-
-#: etc/initialdata:365
-msgid "Notify Owner of their ticket has been approved by all approvers"
-msgstr "通知承辦人申請單已完æˆå…¨éƒ¨ç°½æ ¸"
-
-#: etc/initialdata:353
-msgid "Notify Owner of their ticket has been approved by some approver"
-msgstr "通知承辦人申請單已完æˆæŸé …簽核"
-
-#: etc/initialdata:334
-msgid "Notify Owners and AdminCcs of new items pending their approval"
-msgstr "æ•´ç†å¾…簽核事項,通知承辦人åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:77
-msgid "Notify Requestors"
-msgstr "通知申請人"
-
-#: etc/initialdata:111
-msgid "Notify Requestors and Ccs"
-msgstr "通知申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:106
-msgid "Notify Requestors and Ccs as Comment"
-msgstr "以評論方å¼é€šçŸ¥ç”³è«‹äººåŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:120
-msgid "Notify Requestors, Ccs and AdminCcs"
-msgstr "通知申請人ã€å‰¯æœ¬åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:116
-msgid "Notify Requestors, Ccs and AdminCcs as Comment"
-msgstr "以評論方å¼é€šçŸ¥ç”³è«‹äººã€å‰¯æœ¬åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "Notify people:"
-msgstr "通知å°è±¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Nov"
-msgstr "å一月"
-
-#: lib/RT/Date.pm:451
-msgid "Nov."
-msgstr "11"
-
-#: NOT FOUND IN SOURCE
-msgid "November"
-msgstr "å一月"
-
-#: NOT FOUND IN SOURCE
-msgid "OIN104"
-msgstr "104eHRMS 介é¢"
-
-#: NOT FOUND IN SOURCE
-msgid "OK"
-msgstr "確定"
-
-#: html/Search/Elements/SelectAndOr:47
-msgid "OR"
-msgstr "OR"
-
-#: lib/RT/Record.pm:322
-msgid "Object could not be created"
-msgstr "無法新增物件"
-
-#: lib/RT/Record.pm:123
-msgid "Object could not be deleted"
-msgstr ""
-
-#: lib/RT/Record.pm:341
-msgid "Object created"
-msgstr "物件新增完畢"
-
-#: lib/RT/Record.pm:120
-msgid "Object deleted"
-msgstr ""
-
-#: html/Admin/CustomFields/Objects.html:72 html/Admin/Elements/ObjectCustomFields:63
-#. ($ObjectType)
-#. ($LookupType)
-msgid "Object of type %1 cannot take custom fields"
-msgstr "自訂欄ä½ä¸é©ç”¨æ–¼é¡žåˆ¥ç‚º %1 的物件"
-
-#: lib/RT/CustomField_Overlay.pm:967
-msgid "Object type mismatch"
-msgstr "物件類別ä¸ç¬¦"
-
-#: NOT FOUND IN SOURCE
-msgid "Occupation Status"
-msgstr "在è·ç‹€æ…‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Oct"
-msgstr "å月"
-
-#: lib/RT/Date.pm:450
-msgid "Oct."
-msgstr "10"
-
-#: NOT FOUND IN SOURCE
-msgid "October"
-msgstr "å月"
-
-#: NOT FOUND IN SOURCE
-msgid "Office Phone"
-msgstr "辦公室電話"
-
-#: html/Tools/Elements/Tabs:55
-msgid "Offline"
-msgstr "離線"
-
-#: html/Tools/Offline.html:49
-msgid "Offline edits"
-msgstr "離線編輯"
-
-#: html/Tools/Offline.html:46
-msgid "Offline upload"
-msgstr "離線上載"
-
-#: html/Elements/SelectDateRelation:56
-msgid "On"
-msgstr "等於"
-
-#: lib/RT/Transaction_Overlay.pm:326
-#. ($self->CreatedAsString(), $self->CreatorObj->Name())
-msgid "On %1, %2 wrote:"
-msgstr "在 %1 時,%2 寫到:"
-
-#: NOT FOUND IN SOURCE
-msgid "On Change"
-msgstr "更改申請單時"
-
-#: etc/initialdata:163
-msgid "On Comment"
-msgstr "評論時"
-
-#: etc/initialdata:156
-msgid "On Correspond"
-msgstr "回覆申請單時"
-
-#: etc/initialdata:145
-msgid "On Create"
-msgstr "新增申請單時"
-
-#: etc/initialdata:184
-msgid "On Owner Change"
-msgstr "承辦人改變時"
-
-#: etc/initialdata:177 etc/upgrade/3.1.17/content:15
-msgid "On Priority Change"
-msgstr "優先順ä½æ”¹è®Šæ™‚"
-
-#: etc/initialdata:192
-msgid "On Queue Change"
-msgstr "表單改變時"
-
-#: etc/initialdata:198
-msgid "On Resolve"
-msgstr "解決申請單時"
-
-#: etc/initialdata:169
-msgid "On Status Change"
-msgstr "ç¾æ³æ”¹è®Šæ™‚"
-
-#: etc/initialdata:150
-msgid "On Transaction"
-msgstr "發生更動時"
-
-#: html/Approvals/Elements/PendingMyApproval:70
-#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter' id='CreatedAfter' />")
-msgid "Only show approvals for requests created after %1"
-msgstr "僅顯示 %1 之後新增的申請單"
-
-#: html/Approvals/Elements/PendingMyApproval:68
-#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore' id='CreatedBefore' />")
-msgid "Only show approvals for requests created before %1"
-msgstr "僅顯示 %1 之å‰æ–°å¢žçš„申請單"
-
-#: html/Admin/CustomFields/index.html:75
-msgid "Only show custom fields for:"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Open"
-msgstr "é–‹å•Ÿ"
-
-#: html/SelfService/index.html:46
-msgid "Open Tickets"
-msgstr ""
-
-#: html/Ticket/Elements/Tabs:160
-msgid "Open it"
-msgstr "é–‹å•Ÿ"
-
-#: html/SelfService/Elements/Tabs:75
-msgid "Open tickets"
-msgstr "開啟的申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in a new window"
-msgstr "在新視窗開啟(列表的)申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Open tickets (from listing) in another window"
-msgstr "在å¦ä¸€å€‹è¦–窗開啟(列表的)申請單"
-
-#: etc/initialdata:140
-msgid "Open tickets on correspondence"
-msgstr "收到回覆時å³é–‹å•Ÿç”³è«‹å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "Opened Tickets"
-msgstr "已申請é‹è¡Œä¸­è¡¨å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "Opinion"
-msgstr "æ„見"
-
-#: NOT FOUND IN SOURCE
-msgid "Option Description"
-msgstr "é¸é …æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Option Name"
-msgstr "é¸é …å稱"
-
-#: html/Prefs/MyRT.html:70
-msgid "Options"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:59
-msgid "Order by"
-msgstr "排åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Ordering and sorting"
-msgstr "é †åºèˆ‡æŽ’åºæ–¹å¼"
-
-#: html/Admin/Users/Modify.html:141 html/User/Prefs.html:129
-msgid "Organization"
-msgstr "組織å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Organization:"
-msgstr "組織:"
-
-#: html/Approvals/Elements/Approve:53
-#. ($approving->Id, $approving->Subject)
-msgid "Originating ticket: #%1"
-msgstr "原申請單:#%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Other comma-delimited email addresses"
-msgstr "其他e-mail帳號 (僅e-mail通知;多筆帳號請用逗號','å€éš”)"
-
-#: NOT FOUND IN SOURCE
-msgid "Out of range"
-msgstr "期é™å¤–"
-
-#: lib/RT/Transaction_Overlay.pm:622
-msgid "Outgoing email about a comment recorded"
-msgstr "已紀錄發é€çš„評論郵件"
-
-#: lib/RT/Transaction_Overlay.pm:626
-msgid "Outgoing email recorded"
-msgstr "已紀錄發é€çš„郵件"
-
-#: html/Admin/Queues/Modify.html:90
-msgid "Over time, priority moves toward"
-msgstr "優先順ä½éš¨æ™‚間增加調整為"
-
-#: NOT FOUND IN SOURCE
-msgid "Override current custom fields with fields from %1"
-msgstr "以 %1 表單的自訂欄ä½å–代ç¾æœ‰æ¬„ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Override global rights"
-msgstr "å–代全域權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "OverrideGlobalACL status %1"
-msgstr "å–ä»£å…¨åŸŸæ¬Šé™ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Overview"
-msgstr "總覽"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "Own tickets"
-msgstr "承辦申請單"
-
-#: lib/RT/Queue_Overlay.pm:112
-msgid "OwnTicket"
-msgstr "承辦申請單"
-
-#: etc/initialdata:38 html/Elements/QuickCreate:56 html/Search/Elements/PickBasics:101 html/Ticket/Create.html:72 html/Ticket/Elements/EditBasics:61 html/Ticket/Elements/EditPeople:64 html/Ticket/Elements/EditPeople:65 html/Ticket/Elements/Reminders:129 html/Ticket/Elements/ShowPeople:48 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:110 lib/RT/Tickets_Overlay.pm:2006
-msgid "Owner"
-msgstr "承辦人"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner changed from %1 to %2"
-msgstr "承辦人已從 %1 改為 %2"
-
-#: lib/RT/Ticket_Overlay.pm:505
-msgid "Owner could not be set."
-msgstr "無法設定承辦人。"
-
-#: lib/RT/Transaction_Overlay.pm:672
-#. ($Old->Name , $New->Name)
-msgid "Owner forcibly changed from %1 to %2"
-msgstr "強制將承辦人從 %1 改為 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner is"
-msgstr "承辦人"
-
-#: NOT FOUND IN SOURCE
-msgid "Owner's Phone"
-msgstr "承辦人電話"
-
-#: NOT FOUND IN SOURCE
-msgid "Page #"
-msgstr " "
-
-#: html/Elements/TicketList:78
-#. ($Page, int($TotalFound/$Rows)+$oddRows)
-msgid "Page %1 of %2"
-msgstr "第 %1/%2 é "
-
-#: html/Admin/Users/Modify.html:198 html/User/Prefs.html:96
-msgid "Pager"
-msgstr "呼å«å™¨"
-
-#: NOT FOUND IN SOURCE
-msgid "PagerPhone"
-msgstr "呼å«å™¨è™Ÿç¢¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Parameter"
-msgstr "呼å«åƒæ•¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Parent"
-msgstr "上級"
-
-#: html/Elements/EditLinks:144 html/Elements/EditLinks:76 html/Elements/ShowLinks:68 html/Ticket/Create.html:222 html/Ticket/Elements/BulkLinks:60
-msgid "Parents"
-msgstr "æ¯ç”³è«‹å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "Park Space"
-msgstr "åœè»Šä½ç”³è«‹"
-
-#: html/Elements/Login:95 html/User/Prefs.html:105
-msgid "Password"
-msgstr "密碼"
-
-#: html/NoAuth/Reminder.html:46
-msgid "Password Reminder"
-msgstr "密碼æ示"
-
-#: lib/RT/Transaction_Overlay.pm:781 lib/RT/User_Overlay.pm:1045
-msgid "Password changed"
-msgstr ""
-
-#: lib/RT/User_Overlay.pm:1037 lib/RT/User_Overlay.pm:214
-#. ($RT::MinimumPasswordLength)
-msgid "Password needs to be at least %1 characters long"
-msgstr "密碼長度至少必須為 %1 個字元"
-
-#: lib/RT/User_Overlay.pm:1044
-msgid "Password set"
-msgstr "密碼已設定"
-
-#: NOT FOUND IN SOURCE
-msgid "Password too short"
-msgstr "密碼太短"
-
-#: html/User/Prefs.html:240
-#. (loc_fuzzy($msg))
-msgid "Password: %1"
-msgstr "密碼:%1"
-
-#: lib/RT/User_Overlay.pm:1030
-msgid "Password: Permission Denied"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:364
-msgid "Passwords do not match."
-msgstr "密碼確èªå¤±æ•—。"
-
-#: html/User/Prefs.html:242
-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:62 html/Ticket/Elements/Tabs:119 html/Ticket/ModifyAll.html:72
-msgid "People"
-msgstr "人員"
-
-#: NOT FOUND IN SOURCE
-msgid "People with Queue Rights"
-msgstr "æ“有表單權é™äººå“¡"
-
-#: etc/initialdata:133
-msgid "Perform a user-defined action"
-msgstr "執行使用者自訂的動作"
-
-#: html/Admin/Tools/Configuration.html:94
-msgid "Perl configuration"
-msgstr "Perl 設定"
-
-#: lib/RT/ACE_Overlay.pm:251 lib/RT/ACE_Overlay.pm:257 lib/RT/ACE_Overlay.pm:580 lib/RT/ACE_Overlay.pm:590 lib/RT/ACE_Overlay.pm:600 lib/RT/ACE_Overlay.pm:665 lib/RT/Attribute_Overlay.pm:158 lib/RT/Attribute_Overlay.pm:164 lib/RT/Attribute_Overlay.pm:405 lib/RT/Attribute_Overlay.pm:414 lib/RT/Attribute_Overlay.pm:427 lib/RT/CurrentUser.pm:116 lib/RT/CurrentUser.pm:125 lib/RT/CustomField_Overlay.pm:1017 lib/RT/CustomField_Overlay.pm:1138 lib/RT/CustomField_Overlay.pm:1281 lib/RT/CustomField_Overlay.pm:172 lib/RT/CustomField_Overlay.pm:189 lib/RT/CustomField_Overlay.pm:200 lib/RT/CustomField_Overlay.pm:374 lib/RT/CustomField_Overlay.pm:403 lib/RT/CustomField_Overlay.pm:763 lib/RT/CustomField_Overlay.pm:936 lib/RT/CustomField_Overlay.pm:971 lib/RT/Group_Overlay.pm:1117 lib/RT/Group_Overlay.pm:1121 lib/RT/Group_Overlay.pm:1130 lib/RT/Group_Overlay.pm:1240 lib/RT/Group_Overlay.pm:1244 lib/RT/Group_Overlay.pm:1250 lib/RT/Group_Overlay.pm:445 lib/RT/Group_Overlay.pm:542 lib/RT/Group_Overlay.pm:620 lib/RT/Group_Overlay.pm:628 lib/RT/Group_Overlay.pm:726 lib/RT/Group_Overlay.pm:730 lib/RT/Group_Overlay.pm:736 lib/RT/Group_Overlay.pm:922 lib/RT/Group_Overlay.pm:926 lib/RT/Group_Overlay.pm:939 lib/RT/Queue_Overlay.pm:1054 lib/RT/Queue_Overlay.pm:140 lib/RT/Queue_Overlay.pm:158 lib/RT/Queue_Overlay.pm:657 lib/RT/Queue_Overlay.pm:667 lib/RT/Queue_Overlay.pm:681 lib/RT/Queue_Overlay.pm:819 lib/RT/Queue_Overlay.pm:828 lib/RT/Queue_Overlay.pm:841 lib/RT/Scrip_Overlay.pm:149 lib/RT/Scrip_Overlay.pm:160 lib/RT/Scrip_Overlay.pm:224 lib/RT/Scrip_Overlay.pm:538 lib/RT/Template_Overlay.pm:108 lib/RT/Template_Overlay.pm:277 lib/RT/Ticket_Overlay.pm:1357 lib/RT/Ticket_Overlay.pm:1367 lib/RT/Ticket_Overlay.pm:1381 lib/RT/Ticket_Overlay.pm:1522 lib/RT/Ticket_Overlay.pm:1532 lib/RT/Ticket_Overlay.pm:1546 lib/RT/Ticket_Overlay.pm:1663 lib/RT/Ticket_Overlay.pm:1983 lib/RT/Ticket_Overlay.pm:2126 lib/RT/Ticket_Overlay.pm:2296 lib/RT/Ticket_Overlay.pm:2346 lib/RT/Ticket_Overlay.pm:2525 lib/RT/Ticket_Overlay.pm:2538 lib/RT/Ticket_Overlay.pm:2614 lib/RT/Ticket_Overlay.pm:2627 lib/RT/Ticket_Overlay.pm:2748 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2990 lib/RT/Ticket_Overlay.pm:3000 lib/RT/Ticket_Overlay.pm:3005 lib/RT/Ticket_Overlay.pm:3224 lib/RT/Ticket_Overlay.pm:3228 lib/RT/Ticket_Overlay.pm:3371 lib/RT/Ticket_Overlay.pm:3497 lib/RT/Transaction_Overlay.pm:516 lib/RT/Transaction_Overlay.pm:523 lib/RT/Transaction_Overlay.pm:551 lib/RT/Transaction_Overlay.pm:558 lib/RT/User_Overlay.pm:1176 lib/RT/User_Overlay.pm:1856 lib/RT/User_Overlay.pm:369 lib/RT/User_Overlay.pm:735 lib/RT/User_Overlay.pm:774
-msgid "Permission Denied"
-msgstr "權é™ä¸è¶³"
-
-#: NOT FOUND IN SOURCE
-msgid "Permission Settings"
-msgstr "權é™è¨­å®š"
-
-#: lib/RT/Template_Overlay.pm:238 lib/RT/Template_Overlay.pm:247
-msgid "Permission denied"
-msgstr ""
-
-#: lib/RT/Template_Overlay.pm:372
-msgid "Permissions denied"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Permitted Queues:"
-msgstr "æ“有權é™è¡¨å–®åˆ—表:"
-
-#: NOT FOUND IN SOURCE
-msgid "Personal"
-msgstr "代ç†äººç¾¤çµ„"
-
-#: html/User/Elements/Tabs:56
-msgid "Personal Groups"
-msgstr "代ç†äººç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "Personal Homepage"
-msgstr "個人首é "
-
-#: NOT FOUND IN SOURCE
-msgid "Personal Todo"
-msgstr "ç§äººå¾…辦事項"
-
-#: html/User/Groups/index.html:51 html/User/Groups/index.html:61
-msgid "Personal groups"
-msgstr "代ç†äººç¾¤çµ„"
-
-#: html/User/Elements/DelegateRights:58
-msgid "Personal groups:"
-msgstr "代ç†äººç¾¤çµ„:"
-
-#: NOT FOUND IN SOURCE
-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 "電話"
-
-#: NOT FOUND IN SOURCE
-msgid "Phone number"
-msgstr "電話號碼"
-
-#: html/Admin/Users/Modify.html:180 html/User/Prefs.html:81
-msgid "Phone numbers"
-msgstr "電話號碼"
-
-#: NOT FOUND IN SOURCE
-msgid "Pick"
-msgstr "挑é¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Place of Departure"
-msgstr "出發地點"
-
-#: NOT FOUND IN SOURCE
-msgid "Placeholder"
-msgstr "尚未完工"
-
-#: NOT FOUND IN SOURCE
-msgid "Please Select"
-msgstr "è«‹é¸æ“‡"
-
-#: NOT FOUND IN SOURCE
-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 "è·ç­‰"
-
-#: NOT FOUND IN SOURCE
-msgid "Position Name"
-msgstr "è·å‹™å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Position Number"
-msgstr "è·å‹™ä»£ç¢¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Position Rank"
-msgstr "è·ç´š"
-
-#: NOT FOUND IN SOURCE
-msgid "Pref"
-msgstr "å好"
-
-#: html/Elements/Header:93 html/Elements/Tabs:91 html/SelfService/Elements/Tabs:95 html/SelfService/Prefs.html:46 html/User/Prefs.html:46 html/User/Prefs.html:49
-msgid "Preferences"
-msgstr "å好"
-
-#: html/Admin/Users/MyRT.html:75
-#. ($pane, $UserObj->Name)
-msgid "Preferences %1 for user %2 ."
-msgstr "使用者 %2 çš„ %1 å好。"
-
-#: html/Prefs/MyRT.html:141
-#. ($pane)
-msgid "Preferences saved for %1."
-msgstr "æˆåŠŸå„²å­˜ %1 çš„å好。"
-
-#: NOT FOUND IN SOURCE
-msgid "Prefs"
-msgstr "個人資訊"
-
-#: lib/RT/Action/Generic.pm:195
-msgid "Prepare Stubbed"
-msgstr "é å‚™å‹•ä½œå®Œç•¢"
-
-#: html/Helpers/CalPopup.html:56 html/Ticket/Elements/Tabs:84
-msgid "Prev"
-msgstr "上一項"
-
-#: html/Elements/TicketList:101
-msgid "Previous Page"
-msgstr "上一é "
-
-#: NOT FOUND IN SOURCE
-msgid "Previous page"
-msgstr "å‰ä¸€é "
-
-#: NOT FOUND IN SOURCE
-msgid "Pri"
-msgstr "優先順ä½"
-
-#: lib/RT/ACE_Overlay.pm:157 lib/RT/ACE_Overlay.pm:239 lib/RT/ACE_Overlay.pm:569
-#. ($args{'PrincipalId'})
-msgid "Principal %1 not found."
-msgstr "找ä¸åˆ°å–®ä½ %1。"
-
-#: html/Search/Elements/PickBasics:147 html/Ticket/Create.html:181 html/Ticket/Elements/EditBasics:92 html/Ticket/Elements/ShowBasics:72 lib/RT/Tickets_Overlay.pm:1790
-msgid "Priority"
-msgstr "優先順ä½"
-
-#: html/Admin/Queues/Modify.html:86
-msgid "Priority starts at"
-msgstr "優先順ä½èµ·å§‹å€¼"
-
-#: html/Search/Elements/EditSearches:50
-msgid "Privacy:"
-msgstr "éš±ç§è¨­å®šï¼š"
-
-#: etc/initialdata:25
-msgid "Privileged"
-msgstr "內部æˆå“¡"
-
-#: html/Admin/Users/Modify.html:342 html/User/Prefs.html:231
-#. (loc_fuzzy($msg))
-msgid "Privileged status: %1"
-msgstr "內部æˆå“¡ç‹€æ…‹ï¼š%1"
-
-#: html/Admin/Users/index.html:102
-msgid "Privileged users"
-msgstr "內部æˆå“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Process Status"
-msgstr "處ç†ç‹€æ…‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Project"
-msgstr "專案"
-
-#: NOT FOUND IN SOURCE
-msgid "Project Name"
-msgstr "專案å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Projects"
-msgstr "專案"
-
-#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
-msgid "Pseudogroup for internal use"
-msgstr "內部用的虛擬群組"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Description"
-msgstr "公開說明"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Info"
-msgstr "公開資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Public Service"
-msgstr "公共事務å€"
-
-#: NOT FOUND IN SOURCE
-msgid "Purging stale data: %1"
-msgstr "移除éŽæœŸè³‡æ–™: %1"
-
-#: NOT FOUND IN SOURCE
-msgid "Query"
-msgstr "查詢"
-
-#: html/Search/Build.html:121
-msgid "Query Builder"
-msgstr "建立查詢"
-
-#: html/Search/Elements/Chart:101
-msgid "Query:"
-msgstr ""
-
-#: html/Elements/QueueSummary:48 html/Elements/QuickCreate:54 html/Search/Elements/PickBasics:71 html/SelfService/Create.html:54 html/Ticket/Create.html:62 html/Ticket/Elements/EditBasics:57 html/Ticket/Elements/ShowBasics:76 html/Tools/Reports/CreatedByDates.html:85 html/Tools/Reports/ResolvedByDates.html:86 html/Tools/Reports/ResolvedByOwner.html:66 html/User/Elements/DelegateRights:101 lib/RT/Tickets_Overlay.pm:1617
-msgid "Queue"
-msgstr "表單"
-
-#: html/Admin/Queues/CustomField.html:63 html/Admin/Queues/Scrip.html:61 html/Admin/Queues/Scrips.html:69 html/Admin/Queues/Templates.html:65
-#. ($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/Queues/Modify.html:64
-msgid "Queue Name"
-msgstr "表單å稱"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Owner"
-msgstr "業務承辦人"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Priority"
-msgstr "優先等級"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Rights"
-msgstr "表單權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Scrips"
-msgstr "表單手續"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue Setup"
-msgstr "表單設定"
-
-#: lib/RT/Queue_Overlay.pm:365
-msgid "Queue already exists"
-msgstr "表單已存在"
-
-#: lib/RT/Queue_Overlay.pm:374 lib/RT/Queue_Overlay.pm:380
-msgid "Queue could not be created"
-msgstr "無法新增表單"
-
-#: html/Ticket/Create.html:244 lib/t/regression/01ticket_link_searching.t:17
-msgid "Queue could not be loaded."
-msgstr "無法載入表單"
-
-#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:384 lib/RT/StyleGuide.pod:809
-msgid "Queue created"
-msgstr "表單新增完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Queue is not specified."
-msgstr "未指定表單。"
-
-#: html/SelfService/Display.html:126 lib/RT/CustomField_Overlay.pm:197
-msgid "Queue not found"
-msgstr "找ä¸åˆ°è¡¨å–®"
-
-#: html/Admin/Elements/Tabs:59 html/Admin/index.html:72
-msgid "Queues"
-msgstr "表單"
-
-#: html/Elements/MyAdminQueues:46
-msgid "Queues I administer"
-msgstr ""
-
-#: html/Elements/MySupportQueues:46
-msgid "Queues I'm an AdminCc for"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Quick Search"
-msgstr "表單ç¾æ³"
-
-#: html/Elements/Quicksearch:47 html/Prefs/Elements/Tabs:58 html/Prefs/Quicksearch.html:70
-msgid "Quick search"
-msgstr "表單一覽"
-
-#: html/Elements/QuickCreate:47
-msgid "Quick ticket creation"
-msgstr "快速建立申請單"
-
-#: html/Search/Results.html:81
-msgid "RSS"
-msgstr "RSS"
-
-#: NOT FOUND IN SOURCE
-msgid "RT %1"
-msgstr "RT %1"
-
-#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:796
-#. ($RT::VERSION, $RT::rtname)
-msgid "RT %1 for %2"
-msgstr "%2:RT %1 版"
-
-#: NOT FOUND IN SOURCE
-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:46 html/Admin/index.html:47
-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:63 html/SelfService/Error.html:62
-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/Admin/Tools/Configuration.html:73
-msgid "RT Variables"
-msgstr "RT 的變數"
-
-#: html/Admin/Elements/SystemTabs:71 html/Admin/Elements/UserTabs:67 html/Admin/Global/MyRT.html:1 html/Admin/Global/MyRT.html:12 html/Admin/Global/MyRT.html:4 html/Admin/Global/index.html:84 html/Admin/Users/MyRT.html:21 html/Prefs/MyRT.html:66 html/Prefs/MyRT.html:78 html/User/Elements/Tabs:65 html/index.html:1 html/index.html:75
-msgid "RT at a glance"
-msgstr "RT 一覽"
-
-#: html/Admin/Users/MyRT.html:30
-#. ($UserObj->Name)
-msgid "RT at a glance for the user %1"
-msgstr "使用者 %1 的 RT 一覽"
-
-#: html/Admin/CustomFields/Modify.html:117
-msgid "RT can include content from another web service when showing this custom field."
-msgstr "RT å¯æ–¼é¡¯ç¤ºæ­¤è‡ªè¨‚欄ä½æ™‚引入其他網站的內容"
-
-#: html/Admin/CustomFields/Modify.html:106
-msgid "RT can make this custom field's values into hyperlinks to another service."
-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"
-
-#: html/Elements/SetupSessionCookie:100
-msgid "RT couldn't store your session."
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "RT couldn't validate this PGP signature. \\n"
-msgstr "RT 無法確èªé€™å€‹ PGP 簽章。\\n"
-
-#: html/Elements/Logo:49 html/Elements/PageLayout:172
-#. ($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 已執行您的命令"
-
-#: NOT FOUND IN SOURCE
-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 èªç‚ºé€™å¯èƒ½æ˜¯é€€ä¿¡"
-
-#: html/Search/Simple.html:58
-msgid "RT will look for anything else you enter in ticket subjects."
-msgstr "RT 會在申請單主旨內æœå°‹å°‡æ‚¨éµå…¥çš„任何其他字樣"
-
-#: NOT FOUND IN SOURCE
-msgid "RT will process this message as if it were unsigned.\\n"
-msgstr "RT 以未簽章方å¼è™•ç†é€™å°éƒµä»¶ã€‚\\n"
-
-#: html/Admin/CustomFields/Modify.html:108 html/Admin/CustomFields/Modify.html:119
-msgid "RT will replace <tt>__id__</tt> and <tt>__CustomField__</tt> with the record id and custom field value, respectively"
-msgstr "RT 會將 <tt>__id__</tt> åŠ <tt>__CustomField__</tt> ç½®æ›æˆç´€éŒ„編號åŠè‡ªè¨‚欄ä½"
-
-#: 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 "申請單é‹è¡Œè§’色"
-
-#: NOT FOUND IN SOURCE
-msgid "RT_System"
-msgstr "系統訊æ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "Read Only"
-msgstr "唯讀"
-
-#: html/Admin/Users/Modify.html:79 html/User/Prefs.html:69
-msgid "Real Name"
-msgstr "真實姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "RealName"
-msgstr "真實姓å"
-
-#: NOT FOUND IN SOURCE
-msgid "Really reject this ticket?"
-msgstr "您確定è¦é§å›žé€™å¼µç”³è«‹å–®å—Žï¼Ÿ"
-
-#: lib/RT/Transaction_Overlay.pm:725
-#. ($value)
-msgid "Reference by %1 added"
-msgstr "已加入 %1 為åƒè€ƒæœ¬ç”³è«‹å–®"
-
-#: lib/RT/Transaction_Overlay.pm:765
-#. ($value)
-msgid "Reference by %1 deleted"
-msgstr "已移除 %1 為åƒè€ƒæœ¬ç”³è«‹å–®"
-
-#: lib/RT/Transaction_Overlay.pm:722
-#. ($value)
-msgid "Reference to %1 added"
-msgstr "已加入åƒè€ƒç”³è«‹å–® %1"
-
-#: lib/RT/Transaction_Overlay.pm:762
-#. ($value)
-msgid "Reference to %1 deleted"
-msgstr "已移除åƒè€ƒç”³è«‹å–® %1"
-
-#: html/Elements/EditLinks:103 html/Elements/EditLinks:156 html/Elements/ShowLinks:92 html/Ticket/Create.html:225 html/Ticket/Elements/BulkLinks:72
-msgid "Referred to by"
-msgstr "被åƒè€ƒ"
-
-#: html/Elements/EditLinks:152 html/Elements/EditLinks:94 html/Elements/SelectLinkType:49 html/Elements/ShowLinks:82 html/Ticket/Create.html:224 html/Ticket/Elements/BulkLinks:68
-msgid "Refers to"
-msgstr "åƒè€ƒ"
-
-#: NOT FOUND IN SOURCE
-msgid "RefersTo"
-msgstr "åƒè€ƒ"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine"
-msgstr "在çµæžœç¯„åœå…§æŸ¥è©¢"
-
-#: NOT FOUND IN SOURCE
-msgid "Refine search"
-msgstr "調整查詢æ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Refresh"
-msgstr "æ›´æ–°"
-
-#: html/Elements/Refresh:57
-#. ($value/60)
-msgid "Refresh this page every %1 minutes."
-msgstr "æ¯ %1 分é˜æ›´æ–°é é¢"
-
-#: lib/RT/Transaction_Overlay.pm:811
-#. ($ticket->Subject)
-msgid "Reminder '%1' added"
-msgstr "已建立æ醒項目「%1ã€"
-
-#: lib/RT/Transaction_Overlay.pm:824
-#. ($ticket->Subject)
-msgid "Reminder '%1' completed"
-msgstr "已完æˆæ醒項目「%1ã€"
-
-#: lib/RT/Transaction_Overlay.pm:817
-#. ($ticket->Subject)
-msgid "Reminder '%1' reopened"
-msgstr "å·²é‡æ–°é–‹å•Ÿæ醒項目「%1ã€"
-
-#: html/Ticket/Reminders.html:46
-#. ($Ticket->Id)
-msgid "Reminder ticket #%1"
-msgstr "æ醒項目 #%1"
-
-#: html/Elements/MyReminders:48 html/Ticket/Elements/ShowSummary:75 html/Ticket/Elements/Tabs:122 html/Ticket/Reminders.html:52
-msgid "Reminders"
-msgstr ""
-
-#: html/Ticket/Reminders.html:50
-#. ($Ticket->Id)
-msgid "Reminders for ticket #%1"
-msgstr "申請單 #%1 çš„æ醒項目"
-
-#: NOT FOUND IN SOURCE
-msgid "Remove"
-msgstr "移除"
-
-#: html/Search/Bulk.html:94
-msgid "Remove AdminCc"
-msgstr "移除管ç†å“¡å‰¯æœ¬"
-
-#: html/Search/Bulk.html:90
-msgid "Remove Cc"
-msgstr "移除副本"
-
-#: html/Search/Bulk.html:86
-msgid "Remove Requestor"
-msgstr "移除申請人"
-
-#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:147
-msgid "Reply"
-msgstr "回覆"
-
-#: html/Admin/Queues/Modify.html:72
-msgid "Reply Address"
-msgstr "回覆地å€"
-
-#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:94 html/Ticket/Update.html:78
-msgid "Reply to requestors"
-msgstr "回覆申請人"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "Reply to tickets"
-msgstr "å°ç”³è«‹å–®é€²è¡Œå›žè¦†"
-
-#: lib/RT/Queue_Overlay.pm:110
-msgid "ReplyToTicket"
-msgstr "回覆申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Report to Duty"
-msgstr "上下ç­åˆ·å¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Reported on"
-msgstr "到è·æ—¥æœŸ"
-
-#: html/Tools/Elements/Tabs:59 html/Tools/Reports/index.html:46 html/Tools/Reports/index.html:47
-msgid "Reports"
-msgstr ""
-
-#: etc/initialdata:44 lib/RT/ACE_Overlay.pm:111
-msgid "Requestor"
-msgstr "申請人"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor email address"
-msgstr "申請人電å­éƒµä»¶ä¿¡ç®±ä½å€"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's"
-msgstr "申請人所屬之第上"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's Dept."
-msgstr "申請人所屬部門之"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor's Phone"
-msgstr "申請人電話"
-
-#: NOT FOUND IN SOURCE
-msgid "Requestor(s)"
-msgstr "申請人"
-
-#: NOT FOUND IN SOURCE
-msgid "RequestorAddresses"
-msgstr "申請人地å€"
-
-#: html/SelfService/Create.html:63 html/Ticket/Create.html:80 html/Ticket/Elements/EditPeople:69 html/Ticket/Elements/ShowPeople:52
-msgid "Requestors"
-msgstr "申請人"
-
-#: html/Admin/Queues/Modify.html:96
-msgid "Requests should be due in"
-msgstr "申請單處ç†æœŸé™"
-
-#: lib/RT/Attribute_Overlay.pm:146
-#. ('Object')
-msgid "Required parameter '%1' not specified"
-msgstr "未指定必è¦çš„åƒæ•¸ã€Œ%1ã€"
-
-#: html/Elements/Submit:83
-msgid "Reset"
-msgstr "é‡è¨­"
-
-#: html/Admin/Users/MyRT.html:15 html/Prefs/MyRT.html:60
-msgid "Reset to default"
-msgstr ""
-
-#: html/Admin/Users/Modify.html:183 html/User/Prefs.html:84
-msgid "Residence"
-msgstr "ä½è™•"
-
-#: NOT FOUND IN SOURCE
-msgid "Resolution"
-msgstr "解決狀態"
-
-#: html/Ticket/Elements/Tabs:156
-msgid "Resolve"
-msgstr "解決"
-
-#: html/Ticket/Update.html:156
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Resolve ticket #%1 (%2)"
-msgstr "解決申請單 #%1 (%2)"
-
-#: etc/initialdata:323 html/Elements/SelectDateType:49 lib/RT/Ticket_Overlay.pm:1172
-msgid "Resolved"
-msgstr "已解決"
-
-#: html/Tools/Reports/Elements/Tabs:55
-msgid "Resolved by owner"
-msgstr ""
-
-#: html/Tools/Reports/Elements/Tabs:59
-msgid "Resolved in date range"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByDates.html:52
-msgid "Resolved tickets in period, grouped by owner"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByOwner.html:50
-msgid "Resolved tickets, grouped by owner"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Response to requestors"
-msgstr "回覆申請人"
-
-#: NOT FOUND IN SOURCE
-msgid "Responsibility Type"
-msgstr "責任å€åˆ†"
-
-#: html/Elements/ListActions:46 html/Search/Elements/NewListActions:47
-msgid "Results"
-msgstr "çµæžœ"
-
-#: NOT FOUND IN SOURCE
-msgid "Results per page"
-msgstr "æ¯é åˆ—出幾筆çµæžœ"
-
-#: html/Admin/Users/Modify.html:126 html/User/Prefs.html:116
-msgid "Retype Password"
-msgstr "å†æ¬¡è¼¸å…¥å¯†ç¢¼"
-
-#: html/Search/Elements/EditSearches:61
-msgid "Revert"
-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:630
-msgid "Right Delegated"
-msgstr "權é™ä»£ç†å®Œç•¢"
-
-#: lib/RT/ACE_Overlay.pm:320
-msgid "Right Granted"
-msgstr "權é™è¨­å®šå®Œç•¢"
-
-#: lib/RT/ACE_Overlay.pm:178
-msgid "Right Loaded"
-msgstr "權é™è¼‰å…¥å®Œç•¢"
-
-#: lib/RT/ACE_Overlay.pm:695 lib/RT/ACE_Overlay.pm:716
-msgid "Right could not be revoked"
-msgstr "無法撤消權é™"
-
-#: html/User/Delegation.html:85
-msgid "Right not found"
-msgstr "找ä¸åˆ°æ¬Šé™"
-
-#: lib/RT/ACE_Overlay.pm:560 lib/RT/ACE_Overlay.pm:655
-msgid "Right not loaded."
-msgstr "權é™ä¸¦æœªè¼‰å…¥ã€‚"
-
-#: lib/RT/ACE_Overlay.pm:712
-msgid "Right revoked"
-msgstr "權é™æ’¤æ¶ˆå®Œç•¢"
-
-#: html/Admin/Elements/UserTabs:70
-msgid "Rights"
-msgstr "權é™åŠä»£ç†äºº"
-
-#: html/Admin/CustomFields/GroupRights.html:129 lib/RT/Interface/Web.pm:961
-#. ($object_type)
-msgid "Rights could not be granted for %1"
-msgstr "無法將權é™è³¦äºˆ %1"
-
-#: html/Admin/CustomFields/GroupRights.html:156 lib/RT/Interface/Web.pm:990
-#. ($object_type)
-msgid "Rights could not be revoked for %1"
-msgstr "無法撤消 %1 的權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "Role Members"
-msgstr "角色æˆå“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Role Name"
-msgstr "角色å稱"
-
-#: html/Admin/Global/GroupRights.html:72 html/Admin/Queues/GroupRights.html:74
-msgid "Roles"
-msgstr "角色"
-
-#: NOT FOUND IN SOURCE
-msgid "RootApproval"
-msgstr "交由系統管ç†å“¡ç°½æ ¸"
-
-#: html/Prefs/MyRT.html:72
-msgid "Rows per box"
-msgstr ""
-
-#: html/Search/Elements/DisplayOptions:93
-msgid "Rows per page"
-msgstr "æ¯é ç­†æ•¸"
-
-#: NOT FOUND IN SOURCE
-msgid "Run Approval"
-msgstr "簽核執行"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPDebug"
-msgstr "SMTP åµéŒ¯ç´€éŒ„"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPFrom"
-msgstr "SMTP 寄件ä½å€"
-
-#: NOT FOUND IN SOURCE
-msgid "SMTPServer"
-msgstr "SMTP 伺æœå™¨"
-
-#: NOT FOUND IN SOURCE
-msgid "Sat"
-msgstr "星期六"
-
-#: lib/RT/Date.pm:422
-msgid "Sat."
-msgstr "星期六"
-
-#: html/Prefs/MyRT.html:72 html/Prefs/Quicksearch.html:64 html/Prefs/Search.html:69 html/Prefs/Search.html:69 html/Search/Elements/EditSearches:70 html/Widgets/SelectionBox:211
-msgid "Save"
-msgstr "儲存"
-
-#: html/Admin/Global/Template.html:67 html/Admin/Groups/Modify.html:88 html/Admin/Queues/Modify.html:111 html/Admin/Queues/People.html:126 html/Admin/Users/Modify.html:239 html/Prefs/Quicksearch.html:64 html/Prefs/SearchOptions.html:63 html/SelfService/Prefs.html:58 html/Ticket/Modify.html:60 html/Ticket/ModifyAll.html:127 html/Ticket/ModifyDates.html:60 html/Ticket/ModifyLinks.html:61 html/Ticket/ModifyPeople.html:60 html/User/Groups/Modify.html:77
-msgid "Save Changes"
-msgstr "儲存更改"
-
-#: html/User/Prefs.html:181
-msgid "Save Preferences"
-msgstr "儲存å好"
-
-#: html/Ticket/Elements/PreviewScrips:131
-msgid "Save changes"
-msgstr "儲存更改"
-
-#: lib/RT/SavedSearch.pm:173
-#. ($name)
-msgid "Saved search %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Saved searches"
-msgstr "已儲存的查詢"
-
-#: html/Admin/Elements/ListGlobalScrips:60 html/Admin/Global/Scrip.html:77 html/Admin/Queues/Scrip.html:84
-#. ($scrip->Id)
-#. ($id)
-msgid "Scrip #%1"
-msgstr "手續 #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Action"
-msgstr "訊æ¯é€šçŸ¥å‹•ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Condition"
-msgstr "訊æ¯é€šçŸ¥æ¢ä»¶"
-
-#: lib/RT/Scrip_Overlay.pm:203
-msgid "Scrip Created"
-msgstr "手續新增完畢"
-
-#: html/Admin/Elements/EditScrip:52
-msgid "Scrip Fields"
-msgstr "手續欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrip Name"
-msgstr "訊æ¯å稱"
-
-#: html/Admin/Elements/EditScrips:109
-msgid "Scrip deleted"
-msgstr "手續刪除完畢"
-
-#: html/Admin/Elements/QueueTabs:67 html/Admin/Elements/SystemTabs:54 html/Admin/Global/index.html:62
-msgid "Scrips"
-msgstr "手續"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips "
-msgstr "訊æ¯é€šçŸ¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Scrips for %1\\n"
-msgstr "%1 的手續\\n"
-
-#: html/Admin/Queues/Scrips.html:55
-msgid "Scrips which apply to all queues"
-msgstr "é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
-
-#: html/Elements/SimpleSearch:48 html/Search/Simple.html:63
-msgid "Search"
-msgstr "查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "Search Criteria"
-msgstr "查詢æ¢ä»¶"
-
-#: html/Prefs/SearchOptions.html:47 html/Prefs/SearchOptions.html:50
-msgid "Search Preferences"
-msgstr ""
-
-#: lib/RT/SavedSearch.pm:115
-msgid "Search attribute load failure"
-msgstr "æœå°‹å±¬æ€§è¼‰å…¥å¤±æ•—"
-
-#: html/Approvals/Elements/PendingMyApproval:59
-msgid "Search for approvals"
-msgstr "簽核單查詢"
-
-#: html/Search/Simple.html:67
-msgid "Search for tickets"
-msgstr ""
-
-#: html/Search/Simple.html:55
-msgid "Search for tickets. Enter <strong>id</strong> numbers, <strong>queues</strong> by name, Owners by <strong>username</strong> and Requestors by <strong>email address</strong>. RT will look for anything else you enter in ticket bodies and attachments."
-msgstr "æœå°‹ç”³è«‹å–®ã€‚è«‹éµå…¥<strong>編號</strong>ã€<strong>表單å稱</strong>ã€æ‰¿è¾¦äººçš„<strong>使用者å稱</strong>ã€æˆ–申請人的<strong>é›»å­éƒµä»¶åœ°å€</strong>。以上格å¼ä¹‹å¤–的文字,則會在申請單內文åŠé™„件內檢索。"
-
-#: html/User/Elements/Tabs:62
-msgid "Search options"
-msgstr ""
-
-#: html/Search/Chart.html:56
-#. ($PrimaryGroupBy)
-msgid "Search results grouped by %1"
-msgstr "æœå°‹çµæžœï¼Œä¾ %1 分組"
-
-#: lib/RT/SavedSearch.pm:203
-#. ($msg)
-msgid "Search update: %1"
-msgstr "更新查詢:%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Searches can't be associated with that kind of object"
-msgstr "ä¸èƒ½å°æ­¤é¡žç‰©ä»¶é€²è¡ŒæŸ¥è©¢"
-
-#: html/Search/Simple.html:57
-msgid "Searching the full text of every ticket can take a long time, but if you need to do it, you can search for any word in full ticket history for any word by typing <b>fulltext:<i>word</i></b>."
-msgstr "å°æ‰€æœ‰ç”³è«‹å–®çš„全文進行檢索,å¯èƒ½æœƒéœ€è¦å¾ˆä¹…的時間。但如果您真的有需è¦ï¼Œå¯éµå…¥ <b>fulltext:<i>文字</i></b> 來æœå°‹ç”³è«‹å–®çš„所有紀錄。"
-
-#: NOT FOUND IN SOURCE
-msgid "Second-"
-msgstr "二"
-
-#: NOT FOUND IN SOURCE
-msgid "Second-level Users"
-msgstr "二階主管員工"
-
-#: bin/rt-crontool:265
-msgid "Security:"
-msgstr "安全性:"
-
-#: html/Elements/ShowCustomFields:98
-msgid "See also:"
-msgstr ""
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "See custom fields"
-msgstr "查閱自訂欄ä½"
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "See exact outgoing email messages and their recipeients"
-msgstr "查閱é€å‡ºçš„é›»å­éƒµä»¶åŠæ”¶ä»¶äºº"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "See ticket private commentary"
-msgstr "查閱申請單內的ç§äººè©•è«–"
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "See ticket summaries"
-msgstr "查閱申請單總覽"
-
-#: lib/RT/CustomField_Overlay.pm:105
-msgid "SeeCustomField"
-msgstr "查閱自訂欄ä½"
-
-#: lib/RT/Group_Overlay.pm:169
-msgid "SeeGroup"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:91
-msgid "SeeQueue"
-msgstr "查閱表單"
-
-#: NOT FOUND IN SOURCE
-msgid "Select"
-msgstr "é¸æ“‡"
-
-#: NOT FOUND IN SOURCE
-msgid "Select All"
-msgstr "å…¨é¸"
-
-#: html/Admin/CustomFields/index.html:46 html/Admin/CustomFields/index.html:49
-msgid "Select a Custom Field"
-msgstr "é¸æ“‡è‡ªè¨‚欄ä½"
-
-#: html/Admin/Groups/index.html:78
-msgid "Select a group"
-msgstr "é¸æ“‡ç¾¤çµ„"
-
-#: html/Admin/Queues/index.html:54
-msgid "Select a queue"
-msgstr "é¸æ“‡è¡¨å–®"
-
-#: html/SelfService/CreateTicketInQueue.html:48
-msgid "Select a queue for your new ticket"
-msgstr "為您新的申請單é¸æ“‡ä¸€å€‹è¡¨å–®"
-
-#: NOT FOUND IN SOURCE
-msgid "Select a queue to link to"
-msgstr "è«‹é¸æ“‡æ¬²é€£çµè¡¨å–®"
-
-#: html/Admin/Users/index.html:46 html/Admin/Users/index.html:49 html/Admin/Users/index.html:52
-msgid "Select a user"
-msgstr "é¸æ“‡ä½¿ç”¨è€…"
-
-#: html/Admin/Elements/CustomFieldTabs:90
-msgid "Select custom field"
-msgstr "é¸æ“‡è‡ªè¨‚欄ä½"
-
-#: html/Admin/Global/CustomFields/index.html:70
-msgid "Select custom fields for all user groups"
-msgstr "é¸æ“‡é©ç”¨æ–¼æ‰€æœ‰ä½¿ç”¨è€…群組的自訂欄ä½"
-
-#: html/Admin/Global/CustomFields/index.html:65
-msgid "Select custom fields for all users"
-msgstr "é¸æ“‡é©ç”¨æ–¼æ‰€æœ‰ä½¿ç”¨è€…的自訂欄ä½"
-
-#: html/Admin/Global/CustomFields/index.html:76
-msgid "Select custom fields for tickets in all queues"
-msgstr "é¸æ“‡é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®å…§ç”³è«‹å–®çš„自訂欄ä½"
-
-#: html/Admin/Global/CustomFields/index.html:83
-msgid "Select custom fields for transactions on tickets in all queues"
-msgstr "é¸æ“‡é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®å…§ç”³è«‹å–®ä¹‹æ›´å‹•çš„自訂欄ä½"
-
-#: html/Admin/Elements/GroupTabs:75 html/User/Elements/GroupTabs:71
-msgid "Select group"
-msgstr "é¸æ“‡ç¾¤çµ„"
-
-#: lib/RT/CustomField_Overlay.pm:59
-msgid "Select multiple values"
-msgstr "é¸æ“‡å¤šé‡é …ç›®"
-
-#: lib/RT/CustomField_Overlay.pm:60
-msgid "Select one value"
-msgstr "é¸æ“‡å–®ä¸€é …ç›®"
-
-#: html/Admin/Elements/QueueTabs:92
-msgid "Select queue"
-msgstr "é¸æ“‡è¡¨å–®"
-
-#: html/Prefs/Quicksearch.html:53
-msgid "Select queues to be displayed on the \"RT at a glance\" page"
-msgstr ""
-
-#: html/Admin/Global/Scrip.html:59 html/Admin/Global/Scrips.html:57 html/Admin/Queues/Scrip.html:67 html/Admin/Queues/Scrips.html:73
-msgid "Select scrip"
-msgstr "é¸æ“‡æ‰‹çºŒ"
-
-#: html/Admin/Global/Template.html:78 html/Admin/Global/Templates.html:57 html/Admin/Queues/Template.html:76 html/Admin/Queues/Templates.html:68
-msgid "Select template"
-msgstr "é¸æ“‡ç¯„本"
-
-#: lib/RT/CustomField_Overlay.pm:61
-msgid "Select up to %1 values"
-msgstr "é¸æ“‡æœ€å¤š %1 個值"
-
-#: html/Admin/Elements/UserTabs:78
-msgid "Select user"
-msgstr "é¸æ“‡ä½¿ç”¨è€…"
-
-#: NOT FOUND IN SOURCE
-msgid "Select workflow"
-msgstr "é¸æ“‡æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectExternal"
-msgstr "系統é¸é …"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectMultiple"
-msgstr "多é‡é¸é …"
-
-#: NOT FOUND IN SOURCE
-msgid "SelectSingle"
-msgstr "單一é¸é …"
-
-#: html/Admin/Elements/EditCustomFields:58
-msgid "Selected Custom Fields"
-msgstr "å·²é¸å–的自訂欄ä½"
-
-#: html/Admin/CustomFields/Objects.html:59
-msgid "Selected objects"
-msgstr "å·²é¸å–的物件"
-
-#: NOT FOUND IN SOURCE
-msgid "Selected users:"
-msgstr "å·²é¸å–的使用者:"
-
-#: html/Widgets/SelectionBox:209
-msgid "Selections modified. Please save your changes"
-msgstr "é¸å–的項目已更改。請儲存您的更動"
-
-#: NOT FOUND IN SOURCE
-msgid "Self Service"
-msgstr "自助æœå‹™"
-
-#: etc/initialdata:121
-msgid "Send mail to all watchers"
-msgstr "寄信給所有視察員"
-
-#: etc/initialdata:117
-msgid "Send mail to all watchers as a \"comment\""
-msgstr "以評論方å¼å¯„信給所有視察員"
-
-#: etc/initialdata:112
-msgid "Send mail to requestors and Ccs"
-msgstr "寄信給申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:107
-msgid "Send mail to requestors and Ccs as a comment"
-msgstr "以評論方å¼å¯„信給申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:78
-msgid "Sends a message to the requestors"
-msgstr "寄信給申請人"
-
-#: etc/initialdata:125 etc/initialdata:129
-msgid "Sends mail to explicitly listed Ccs and Bccs"
-msgstr "寄信給特定的副本åŠå¯†ä»¶å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:94 etc/upgrade/3.1.17/content:7
-msgid "Sends mail to the Ccs"
-msgstr "寄信給副本收件人"
-
-#: etc/initialdata:90 etc/upgrade/3.1.17/content:3
-msgid "Sends mail to the Ccs as a comment"
-msgstr "以評論方å¼å¯„信給副本收件人"
-
-#: etc/initialdata:102
-msgid "Sends mail to the administrative Ccs"
-msgstr "寄信給管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:98
-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:449
-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 "ç­åˆ¥å±¬æ€§"
-
-#: html/Ticket/Elements/ShowTransaction:158
-msgid "Show"
-msgstr "顯示"
-
-#: html/Approvals/index.html:52
-msgid "Show Approvals"
-msgstr "顯示待簽核申請單"
-
-#: html/Search/Elements/EditFormat:56
-msgid "Show Columns"
-msgstr "顯示欄ä½"
-
-#: html/Ticket/Elements/Tabs:220
-msgid "Show Results"
-msgstr "顯示çµæžœ"
-
-#: html/Approvals/Elements/PendingMyApproval:64
-msgid "Show approved requests"
-msgstr "顯示已批准的簽核單"
-
-#: html/Ticket/Create.html:316
-msgid "Show basics"
-msgstr "顯示基本資訊"
-
-#: html/Approvals/Elements/PendingMyApproval:65
-msgid "Show denied requests"
-msgstr "顯示已é§å›žçš„簽核單"
-
-#: html/Ticket/Create.html:319
-msgid "Show details"
-msgstr "顯示細節"
-
-#: html/Approvals/Elements/PendingMyApproval:63
-msgid "Show pending requests"
-msgstr "顯示待處ç†çš„簽核單"
-
-#: html/Approvals/Elements/PendingMyApproval:66
-msgid "Show requests awaiting other approvals"
-msgstr "顯示尚待他人批准的簽核單"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket private commentary"
-msgstr "顯示申請單內的ç§äººè©•è«–"
-
-#: NOT FOUND IN SOURCE
-msgid "Show ticket summaries"
-msgstr "顯示申請單摘è¦"
-
-#: lib/RT/Queue_Overlay.pm:93
-msgid "ShowACL"
-msgstr "顯示權é™æ¸…å–®"
-
-#: lib/RT/System.pm:85
-msgid "ShowConfigTab"
-msgstr ""
-
-#: lib/RT/Queue_Overlay.pm:106
-msgid "ShowOutgoingEmail"
-msgstr "顯示寄é€éƒµä»¶"
-
-#: lib/RT/Group_Overlay.pm:168
-msgid "ShowSavedSearches"
-msgstr "顯示已儲存的查詢"
-
-#: lib/RT/Queue_Overlay.pm:102
-msgid "ShowScrips"
-msgstr "顯示手續"
-
-#: lib/RT/Queue_Overlay.pm:99
-msgid "ShowTemplate"
-msgstr "顯示範本"
-
-#: lib/RT/Queue_Overlay.pm:103
-msgid "ShowTicket"
-msgstr "顯示申請單"
-
-#: lib/RT/Queue_Overlay.pm:104
-msgid "ShowTicketComments"
-msgstr "顯示申請單的評論"
-
-#: lib/RT/Queue_Overlay.pm:107
-msgid "Sign up as a ticket Requestor or ticket or queue Cc"
-msgstr "登記æˆç‚ºç”³è«‹äººæˆ–副本收件人"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "Sign up as a ticket or queue AdminCc"
-msgstr "登記æˆç‚ºç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: html/Admin/Users/Modify.html:230 html/User/Prefs.html:168
-msgid "Signature"
-msgstr "ç°½å檔"
-
-#: NOT FOUND IN SOURCE
-msgid "Signed in as %1"
-msgstr "使用者:%1"
-
-#: html/Elements/Tabs:68
-msgid "Simple Search"
-msgstr ""
-
-#: html/Admin/Elements/SelectSingleOrMultiple:47
-msgid "Single"
-msgstr "單一"
-
-#: html/Search/Elements/EditFormat:75
-msgid "Size"
-msgstr ""
-
-#: html/Elements/Header:89
-msgid "Skip Menu"
-msgstr "ç•¥éŽé¸å–®"
-
-#: html/Search/Elements/EditFormat:78
-msgid "Small"
-msgstr ""
-
-#: html/Admin/CustomFields/Modify.html:120
-msgid "Some browsers may only load content from the same domain as your RT server."
-msgstr "æŸäº›ç€è¦½å™¨åªå…許載入和 RT 伺æœå™¨åŒä¸€å€‹ç¶²åŸŸçš„內容。"
-
-#: html/Admin/Elements/AddCustomFieldValue:49 html/Admin/Elements/EditCustomFieldValues:54
-msgid "Sort"
-msgstr "é †åº"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort key"
-msgstr "排åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Sort results by"
-msgstr "çµæžœæŽ’åºæ–¹å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "SortOrder"
-msgstr "排åºé †åº"
-
-#: html/Admin/Elements/EditScrip:78
-msgid "Stage"
-msgstr "é—œå¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Stage Action"
-msgstr "é—œå¡é‹è¡Œå‹•ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "Stage Condition"
-msgstr "é—œå¡é‹è¡Œæ¢ä»¶"
-
-#: NOT FOUND IN SOURCE
-msgid "Stalled"
-msgstr "延宕"
-
-#: NOT FOUND IN SOURCE
-msgid "Start page"
-msgstr "首é "
-
-#: html/Elements/SelectDateType:48 html/Ticket/Elements/EditDates:53 html/Ticket/Elements/ShowDates:56
-msgid "Started"
-msgstr "實際起始日"
-
-#: NOT FOUND IN SOURCE
-msgid "Started date '%1' could not be parsed"
-msgstr "無法解讀起始日期 '%1"
-
-#: html/Elements/SelectDateType:52 html/Ticket/Create.html:208 html/Ticket/Elements/EditDates:48 html/Ticket/Elements/ShowDates:52
-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/Users/Modify.html:162 html/User/Prefs.html:145
-msgid "State"
-msgstr "å·ž"
-
-#: html/Search/Elements/PickBasics:87 html/SelfService/Update.html:57 html/Ticket/Create.html:66 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:52 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:1651
-msgid "Status"
-msgstr "ç¾æ³"
-
-#: etc/initialdata:309
-msgid "Status Change"
-msgstr "ç¾æ³æ”¹è®Šæ™‚"
-
-#: NOT FOUND IN SOURCE
-msgid "Status changed from %1 to %2"
-msgstr "ç¾æ³å¾ž %1 改為 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "StatusChange"
-msgstr "ç¾æ³æ”¹è®Šæ™‚"
-
-#: html/Ticket/Elements/Tabs:178
-msgid "Steal"
-msgstr "強制更æ›æ‰¿è¾¦äºº"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "Steal tickets"
-msgstr "強制承辦申請單"
-
-#: lib/RT/Queue_Overlay.pm:117
-msgid "StealTicket"
-msgstr "強制承辦申請單"
-
-#: lib/RT/Transaction_Overlay.pm:678
-#. ($Old->Name)
-msgid "Stolen from %1"
-msgstr "承辦人從 %1 強制更æ›"
-
-#: NOT FOUND IN SOURCE
-msgid "Stolen from %1 "
-msgstr "承辦人從 %1 å¼·åˆ¶æ›´æ› "
-
-#: html/Search/Elements/EditFormat:81
-msgid "Style"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Subgroup"
-msgstr "å­ç¾¤çµ„"
-
-#: html/Elements/QuickCreate:52 html/Elements/SelectAttachmentField:47 html/Search/Bulk.html:132 html/SelfService/Create.html:79 html/SelfService/Update.html:65 html/Ticket/Create.html:108 html/Ticket/Elements/EditBasics:48 html/Ticket/Elements/Reminders:125 html/Ticket/ModifyAll.html:100 html/Ticket/Update.html:82 lib/RT/Ticket_Overlay.pm:1162 lib/RT/Tickets_Overlay.pm:1733
-msgid "Subject"
-msgstr "主題"
-
-#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:815 lib/RT/Transaction_Overlay.pm:700
-#. ($self->Data)
-msgid "Subject changed to %1"
-msgstr "標題已改為 %1"
-
-#: html/Elements/Submit:75
-msgid "Submit"
-msgstr "é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "Submit Workflow"
-msgstr "é€å‡ºæµç¨‹"
-
-#: lib/RT/Group_Overlay.pm:774
-msgid "Succeeded"
-msgstr "設定æˆåŠŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Sun"
-msgstr "星期日"
-
-#: lib/RT/Date.pm:423
-msgid "Sun."
-msgstr "星期日"
-
-#: lib/RT/System.pm:75
-msgid "SuperUser"
-msgstr "系統管ç†å“¡"
-
-#: NOT FOUND IN SOURCE
-msgid "Sync now"
-msgstr "執行åŒæ­¥"
-
-#: NOT FOUND IN SOURCE
-msgid "Sync104HRMS"
-msgstr "自動åŒæ­¥104HRMS"
-
-#: NOT FOUND IN SOURCE
-msgid "Synchronizing HRMS data. This may take a while..."
-msgstr "正在åŒæ­¥åŒ– HRMS 人事系統資料。請ç¨å¾…..."
-
-#: html/User/Elements/DelegateRights:98
-msgid "System"
-msgstr "系統"
-
-#: html/Admin/Elements/ToolTabs:54 html/Admin/Tools/Configuration.html:48
-msgid "System Configuration"
-msgstr "系統設定"
-
-#: NOT FOUND IN SOURCE
-msgid "System Defined"
-msgstr "系統定義"
-
-#: html/Admin/CustomFields/GroupRights.html:128 html/Admin/CustomFields/GroupRights.html:155 html/Admin/CustomFields/UserRights.html:128 html/Admin/CustomFields/UserRights.html:98 html/Admin/Elements/SelectRights:106 lib/RT/ACE_Overlay.pm:584 lib/RT/Interface/Web.pm:960 lib/RT/Interface/Web.pm:989
-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/Transaction_Overlay.pm:224 lib/RT/Transaction_Overlay.pm:230
-#. ($msg)
-msgid "System Error: %1"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "System Rights"
-msgstr "系統權é™"
-
-#: html/Admin/Tools/index.html:47
-msgid "System Tools"
-msgstr "系統工具"
-
-#: lib/RT/ACE_Overlay.pm:633
-msgid "System error. Right not delegated."
-msgstr "系統錯誤。權é™ä»£ç†å¤±æ•—。"
-
-#: lib/RT/ACE_Overlay.pm:163 lib/RT/ACE_Overlay.pm:228 lib/RT/ACE_Overlay.pm:323 lib/RT/ACE_Overlay.pm:920
-msgid "System error. Right not granted."
-msgstr "系統錯誤。設定權é™å¤±æ•—。"
-
-#: NOT FOUND IN SOURCE
-msgid "System error. Unable to grant rights."
-msgstr "系統錯誤。無法設定權é™ã€‚"
-
-#: html/Admin/CustomFields/GroupRights.html:58 html/Admin/Global/GroupRights.html:56 html/Admin/Groups/GroupRights.html:58 html/Admin/Queues/GroupRights.html:57
-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:357
-msgid "TEST_STRING"
-msgstr "TEST_STRING"
-
-#: NOT FOUND IN SOURCE
-msgid "TabbedUI"
-msgstr "é ç±¤ä»‹é¢"
-
-#: etc/initialdata:603 html/Search/Elements/EditFormat:72 html/Ticket/Elements/Tabs:170
-msgid "Take"
-msgstr "å—ç†"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "Take tickets"
-msgstr "自行承辦申請單"
-
-#: lib/RT/Queue_Overlay.pm:115
-msgid "TakeTicket"
-msgstr "自行承辦申請單"
-
-#: lib/RT/Transaction_Overlay.pm:663
-msgid "Taken"
-msgstr "å·²å—ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Task"
-msgstr "工作事項"
-
-#: html/Admin/Elements/EditScrip:71 html/Tools/Offline.html:78
-msgid "Template"
-msgstr "範本"
-
-#: html/Admin/Global/Template.html:112 html/Admin/Queues/Template.html:113
-#. ($TemplateObj->Id())
-msgid "Template #%1"
-msgstr "範本 #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Content"
-msgstr "通知範本內容"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Description"
-msgstr "通知範本æè¿°"
-
-#: NOT FOUND IN SOURCE
-msgid "Template Name"
-msgstr "通知範本å稱"
-
-#: html/Admin/Elements/EditTemplates:110
-msgid "Template deleted"
-msgstr "範本已刪除"
-
-#: lib/RT/Scrip_Overlay.pm:176
-msgid "Template is mandatory argument"
-msgstr ""
-
-#: lib/RT/Scrip_Overlay.pm:180
-msgid "Template not found"
-msgstr "找ä¸åˆ°ç¯„本"
-
-#: NOT FOUND IN SOURCE
-msgid "Template not found\\n"
-msgstr "找ä¸åˆ°ç¯„本\\n"
-
-#: lib/RT/Template_Overlay.pm:343
-msgid "Template parsed"
-msgstr "範本剖æžå®Œç•¢"
-
-#: lib/RT/Template_Overlay.pm:391
-msgid "Template parsing error"
-msgstr ""
-
-#: html/Admin/Elements/QueueTabs:70 html/Admin/Elements/SystemTabs:57 html/Admin/Global/index.html:66
-msgid "Templates"
-msgstr "範本"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates "
-msgstr "通知範本"
-
-#: NOT FOUND IN SOURCE
-msgid "Templates for %1\\n"
-msgstr "找ä¸åˆ° %1 的範本\\n"
-
-#: NOT FOUND IN SOURCE
-msgid "Text"
-msgstr "文字"
-
-#: lib/RT/CustomField_Overlay.pm:943 lib/RT/Record.pm:945
-msgid "That is already the current value"
-msgstr "已經是目å‰æ¬„ä½çš„值"
-
-#: lib/RT/CustomField_Overlay.pm:412
-msgid "That is not a value for this custom field"
-msgstr "這ä¸æ˜¯è©²è‡ªè¨‚欄ä½çš„值"
-
-#: lib/RT/Ticket_Overlay.pm:1994
-msgid "That is the same value"
-msgstr "åŒæ¨£çš„值"
-
-#: lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:614
-msgid "That principal already has that right"
-msgstr "這項單ä½å·²ç¶“æ“有該權é™"
-
-#: lib/RT/Queue_Overlay.pm:753
-#. ($args{'Type'})
-msgid "That principal is already a %1 for this queue"
-msgstr "這項單ä½å·²ç¶“是這個表單的 %1"
-
-#: lib/RT/Ticket_Overlay.pm:1435
-#. ($self->loc($args{'Type'}))
-msgid "That principal is already a %1 for this ticket"
-msgstr "這項單ä½å·²ç¶“是這份申請單的 %1"
-
-#: lib/RT/Queue_Overlay.pm:852
-#. ($args{'Type'})
-msgid "That principal is not a %1 for this queue"
-msgstr "這項單ä½ä¸æ˜¯é€™å€‹è¡¨å–®çš„ %1"
-
-#: NOT FOUND IN SOURCE
-msgid "That principal is not a %1 for this ticket"
-msgstr "這項單ä½ä¸æ˜¯é€™ä»½ç”³è«‹å–®çš„ %1"
-
-#: lib/RT/Ticket_Overlay.pm:1990
-msgid "That queue does not exist"
-msgstr "此表單ä¸å­˜åœ¨"
-
-#: lib/RT/Ticket_Overlay.pm:3233
-msgid "That ticket has unresolved dependencies"
-msgstr "這份申請單有尚未解決的附屬申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "That user already has that right"
-msgstr "使用者已具有該項權é™"
-
-#: lib/RT/Action/CreateTickets.pm:710 lib/RT/Ticket_Overlay.pm:3037
-msgid "That user already owns that ticket"
-msgstr "該使用者已經承辦這份申請單"
-
-#: lib/RT/Ticket_Overlay.pm:3012
-msgid "That user does not exist"
-msgstr "使用者ä¸å­˜åœ¨"
-
-#: lib/RT/User_Overlay.pm:389
-msgid "That user is already privileged"
-msgstr "這å使用者已經是內部æˆå“¡"
-
-#: lib/RT/User_Overlay.pm:410
-msgid "That user is already unprivileged"
-msgstr "這å使用者屬於éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
-
-#: lib/RT/User_Overlay.pm:402
-msgid "That user is now privileged"
-msgstr "使用者加入內部æˆå“¡ç¾¤çµ„完畢"
-
-#: lib/RT/User_Overlay.pm:423
-msgid "That user is now unprivileged"
-msgstr "這å使用者已加入éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
-
-#: NOT FOUND IN SOURCE
-msgid "That user is now unprivilegedileged"
-msgstr "這å使用者已加入éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
-
-#: lib/RT/Ticket_Overlay.pm:3031
-msgid "That user may not own tickets in that queue"
-msgstr "使用者å¯èƒ½æ²’有承辦表單裡的申請單"
-
-#: lib/RT/Link_Overlay.pm:233
-msgid "That's not a numerical id"
-msgstr "這ä¸æ˜¯ä¸€å€‹æ•¸å­—編號"
-
-#: html/SelfService/Display.html:53 html/Ticket/Create.html:177 html/Ticket/Elements/ShowSummary:49
-msgid "The Basics"
-msgstr "基本資訊"
-
-#: lib/RT/ACE_Overlay.pm:112
-msgid "The CC of a ticket"
-msgstr "申請單的副本收件人"
-
-#: lib/RT/ACE_Overlay.pm:113
-msgid "The administrative CC of a ticket"
-msgstr "申請單的管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: NOT FOUND IN SOURCE
-msgid "The comment has been recorded"
-msgstr "評論已被紀錄"
-
-#: bin/rt-crontool:275
-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:"
-
-#: NOT FOUND IN SOURCE
-msgid "The following commands were not proccessed:\\n\\n"
-msgstr "以下命令未被執行:\\n\\n"
-
-#: lib/RT/Record.pm:948
-msgid "The new value has been set."
-msgstr "新的欄ä½å€¼è¨­å®šå®Œæˆã€‚"
-
-#: lib/RT/ACE_Overlay.pm:110
-msgid "The owner of a ticket"
-msgstr "申請單的承辦人"
-
-#: lib/RT/ACE_Overlay.pm:111
-msgid "The requestor of a ticket"
-msgstr "申請單的申請人"
-
-#: html/Admin/Elements/EditUserComments:47
-msgid "These comments aren't generally visible to the user"
-msgstr "該使用者ä¸æœƒçœ‹è¦‹é€™äº›è©•è«–"
-
-#: NOT FOUND IN SOURCE
-msgid "Third-"
-msgstr "三"
-
-#: lib/RT/CustomField_Overlay.pm:978
-msgid "This custom field does not apply to that object"
-msgstr "此自訂欄ä½ä¸é©ç”¨æ–¼è©²ç‰©ä»¶"
-
-#: html/Admin/Tools/Configuration.html:50
-msgid "This feature is only available to system administrators"
-msgstr "此項功能僅é™ç³»çµ±ç®¡ç†å“¡ä½¿ç”¨"
-
-#: html/Ticket/Elements/PreviewScrips:96
-msgid "This message will be sent to..."
-msgstr "此訊æ¯æœƒå¯„給..."
-
-#: NOT FOUND IN SOURCE
-msgid "This ticket %1 %2 (%3)\\n"
-msgstr "申請單 %1 %2 (%3)\\n"
-
-#: bin/rt-crontool:266
-msgid "This tool allows the user to run arbitrary perl modules from within RT."
-msgstr "此工具程å¼æœƒè®“使用者經由 RT 執行任æ„命令。"
-
-#: lib/RT/Transaction_Overlay.pm:301
-msgid "This transaction appears to have no content"
-msgstr "此項更動報告沒有內容"
-
-#: html/Ticket/Elements/ShowRequestor:70
-#. ($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:420
-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 "更新申請單 # %1 的全部資訊:%2"
-
-#: html/Ticket/ModifyAll.html:46 html/Ticket/ModifyAll.html:50
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket #%1 Jumbo update: %2"
-msgstr "更新申請單 #%1 的全部資訊:%2"
-
-#: html/Approvals/Elements/ShowDependency:67
-#. ($link->BaseObj->Id, $link->BaseObj->Subject)
-msgid "Ticket #%1: %2"
-msgstr "申請單 #%1: %2"
-
-#: lib/RT/Action/CreateTickets.pm:1350 lib/RT/Action/CreateTickets.pm:1359 lib/RT/Action/CreateTickets.pm:605 lib/RT/Action/CreateTickets.pm:729 lib/RT/Action/CreateTickets.pm:741
-#. ($T::Tickets{$template_id}->Id)
-#. ($T::Tickets{$template_id}->id)
-#. ($ticket->Id)
-msgid "Ticket %1"
-msgstr "申請單 %1"
-
-#: lib/RT/Ticket_Overlay.pm:755 lib/RT/Ticket_Overlay.pm:775
-#. ($self->Id, $QueueObj->Name)
-msgid "Ticket %1 created in queue '%2'"
-msgstr "申請單 #%1 æˆåŠŸæ–°å¢žæ–¼ '%2' 表單"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket %1 loaded\\n"
-msgstr "載入申請單 %1\\n"
-
-#: html/Search/Bulk.html:377
-#. ($Ticket->Id, $_)
-msgid "Ticket %1: %2"
-msgstr "申請單 %1:%2"
-
-#: html/Admin/Elements/QueueTabs:74
-msgid "Ticket Custom Fields"
-msgstr "申請單的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Due"
-msgstr "表單處ç†æœŸé™"
-
-#: html/Ticket/History.html:46 html/Ticket/History.html:49
-#. ($Ticket->Id, $Ticket->Subject)
-msgid "Ticket History # %1 %2"
-msgstr "申請單處ç†ç´€éŒ„ # %1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket ID"
-msgstr "單號"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Id"
-msgstr "申請單編號"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Processing Due"
-msgstr "表單é‹è¡ŒæœŸé™"
-
-#: etc/initialdata:324
-msgid "Ticket Resolved"
-msgstr "申請單已解決"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:69 html/Admin/Global/CustomFields/index.html:81 lib/RT/CustomField_Overlay.pm:1207
-msgid "Ticket Transactions"
-msgstr "申請單的更動"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket Type"
-msgstr "表單種類"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket attachment"
-msgstr "申請單附件"
-
-#: lib/RT/Tickets_Overlay.pm:1920
-msgid "Ticket content"
-msgstr "申請單內容"
-
-#: lib/RT/Tickets_Overlay.pm:1969
-msgid "Ticket content type"
-msgstr "申請單內容類別"
-
-#: lib/RT/Ticket_Overlay.pm:603 lib/RT/Ticket_Overlay.pm:617 lib/RT/Ticket_Overlay.pm:628 lib/RT/Ticket_Overlay.pm:763
-msgid "Ticket could not be created due to an internal error"
-msgstr "內部錯誤,無法新增申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket created"
-msgstr "申請單新增完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket creation failed"
-msgstr "申請單新增失敗"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket deleted"
-msgstr "申請單刪除完畢"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket id not found"
-msgstr "找ä¸åˆ°ç”³è«‹å–®ç·¨è™Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket killed"
-msgstr "申請單刪除完畢"
-
-#: html/Ticket/Display.html:55
-msgid "Ticket metadata"
-msgstr "申請單的æ述資訊"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket not found"
-msgstr "找ä¸åˆ°ç”³è«‹å–®"
-
-#: etc/initialdata:310
-msgid "Ticket status changed"
-msgstr "申請單ç¾æ³å·²æ”¹è®Š"
-
-#: NOT FOUND IN SOURCE
-msgid "Ticket watchers"
-msgstr "申請單視察員"
-
-#: lib/RT/Search/FromSQL.pm:82
-#. (ref $self)
-msgid "TicketSQL search module"
-msgstr ""
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:64 html/Admin/Global/CustomFields/index.html:75 html/Elements/Tabs:71 html/Search/Elements/Chart:109 lib/RT/CustomField_Overlay.pm:1206
-msgid "Tickets"
-msgstr "申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets %1 %2"
-msgstr "申請單 %1 %2"
-
-#: NOT FOUND IN SOURCE
-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/Tools/Reports/CreatedByDates.html:86
-msgid "Tickets created after"
-msgstr ""
-
-#: html/Tools/Reports/CreatedByDates.html:88
-msgid "Tickets created before"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "Tickets from %1"
-msgstr "%1 的申請單"
-
-#: html/Tools/Reports/ResolvedByDates.html:87
-msgid "Tickets resolved after"
-msgstr ""
-
-#: html/Tools/Reports/ResolvedByDates.html:89
-msgid "Tickets resolved before"
-msgstr ""
-
-#: html/Approvals/Elements/ShowDependency:48
-msgid "Tickets which depend on this approval:"
-msgstr "批准之後,å¯æŽ¥çºŒè™•ç†ï¼š"
-
-#: html/Search/Elements/PickBasics:134 html/Ticket/Create.html:183 html/Ticket/Elements/EditBasics:72
-msgid "Time Estimated"
-msgstr "é è¨ˆæ™‚é–“"
-
-#: html/Search/Elements/PickBasics:135 html/Ticket/Create.html:196 html/Ticket/Elements/EditBasics:85
-msgid "Time Left"
-msgstr "剩餘時間"
-
-#: html/Search/Elements/PickBasics:133 html/Ticket/Create.html:189 html/Ticket/Elements/EditBasics:78
-msgid "Time Worked"
-msgstr "處ç†æ™‚é–“"
-
-#: lib/RT/Tickets_Overlay.pm:1891
-msgid "Time left"
-msgstr "剩餘時間"
-
-#: html/Elements/Footer:51
-msgid "Time to display"
-msgstr "顯示時間"
-
-#: lib/RT/Tickets_Overlay.pm:1866
-msgid "Time worked"
-msgstr "已處ç†æ™‚é–“"
-
-#: NOT FOUND IN SOURCE
-msgid "TimeLeft"
-msgstr "剩餘時間"
-
-#: lib/RT/Ticket_Overlay.pm:1167
-msgid "TimeWorked"
-msgstr "已處ç†æ™‚é–“"
-
-#: html/Search/Elements/EditFormat:74
-msgid "Title"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:"
-msgstr "產生這次更動的差異檔:"
-
-#: NOT FOUND IN SOURCE
-msgid "To generate a diff of this commit:\\n"
-msgstr "產生這次更動的差異檔:\\n"
-
-#: html/Elements/Footer:62
-#. ('<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>')
-msgid "To inquire about support, training, custom development or licensing, please contact %1."
-msgstr "如果有支æ´ã€æ•™è‚²è¨“ç·´åŠå®šè£½é–‹ç™¼çš„需è¦ï¼Œè«‹é€£çµ¡ %1。"
-
-#: NOT FOUND IN SOURCE
-msgid "Todo"
-msgstr "待辦事項"
-
-#: lib/RT/Ticket_Overlay.pm:1170
-msgid "Told"
-msgstr "告知日期"
-
-#: html/Admin/Elements/Tabs:68 html/Admin/index.html:88 html/Elements/Tabs:74 html/Tools/index.html:46 html/Tools/index.html:49
-msgid "Tools"
-msgstr "工具"
-
-#: html/Search/Elements/Chart:130
-msgid "Total"
-msgstr "é "
-
-#: etc/initialdata:252
-msgid "Transaction"
-msgstr "æ›´å‹•"
-
-#: lib/RT/Transaction_Overlay.pm:805
-#. ($self->Data)
-msgid "Transaction %1 purged"
-msgstr "清除更動報告 %1"
-
-#: lib/RT/Transaction_Overlay.pm:183
-msgid "Transaction Created"
-msgstr "更動報告已新增"
-
-#: html/Admin/Elements/QueueTabs:78
-msgid "Transaction Custom Fields"
-msgstr "更動的自訂欄ä½"
-
-#: NOT FOUND IN SOURCE
-msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
-msgstr "未指定申請單編號,無法新增更動"
-
-#: lib/RT/Transaction_Overlay.pm:128
-msgid "Transaction->Create couldn't, as you didn't specify an object type and id"
-msgstr "未指定物件類別åŠç·¨è™Ÿï¼Œç„¡æ³•æ–°å¢žæ›´å‹•"
-
-#: NOT FOUND IN SOURCE
-msgid "TransactionBatch"
-msgstr "批次更動時"
-
-#: NOT FOUND IN SOURCE
-msgid "TransactionCreate"
-msgstr "新增更動時"
-
-#: lib/RT/Transaction_Overlay.pm:870
-msgid "Transactions are immutable"
-msgstr "ä¸å¯æ›´æ”¹æ›´å‹•å ±å‘Š"
-
-#: NOT FOUND IN SOURCE
-msgid "Transfer to"
-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:418
-msgid "Tue."
-msgstr "星期二"
-
-#: html/Admin/CustomFields/Modify.html:66 html/Admin/Elements/EditCustomField:65 html/Ticket/Elements/AddWatchers:54 html/Ticket/Elements/AddWatchers:65 html/Ticket/Elements/AddWatchers:75 lib/RT/Ticket_Overlay.pm:1168 lib/RT/Tickets_Overlay.pm:1705
-msgid "Type"
-msgstr "類別"
-
-#: lib/RT/ScripCondition_Overlay.pm:128
-msgid "Unimplemented"
-msgstr "尚無實作"
-
-#: html/Admin/Users/Modify.html:89
-msgid "Unix login"
-msgstr "外部系統登入帳號"
-
-#: NOT FOUND IN SOURCE
-msgid "UnixUsername"
-msgstr "外部系統登入帳號"
-
-#: lib/RT/Attachment_Overlay.pm:289 lib/RT/Record.pm:861
-#. ($self->ContentEncoding)
-#. ($ContentEncoding)
-msgid "Unknown ContentEncoding %1"
-msgstr "ä¸å¯è§£çš„å…§å®¹æ–‡å­—ç·¨ç¢¼æ–¹å¼ %1"
-
-#: html/Search/Build.html:455 lib/RT/Report/Tickets.pm:410
-msgid "Unknown field: $key"
-msgstr ""
-
-#: html/Elements/SelectResultsPerPage:58
-msgid "Unlimited"
-msgstr "全數顯示"
-
-#: html/Search/Elements/SelectSearchesForObjects:64
-msgid "Unnamed search"
-msgstr "未命å的查詢"
-
-#: etc/initialdata:32
-msgid "Unprivileged"
-msgstr "éžå…§éƒ¨æˆå“¡"
-
-#: html/Admin/Elements/EditCustomFields:60
-msgid "Unselected Custom Fields"
-msgstr "未é¸å–的自訂欄ä½"
-
-#: html/Admin/CustomFields/Objects.html:61
-msgid "Unselected objects"
-msgstr "未é¸å–的物件"
-
-#: lib/RT/Transaction_Overlay.pm:659
-msgid "Untaken"
-msgstr "未被å—ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Untitled search"
-msgstr "未命å的查詢"
-
-#: NOT FOUND IN SOURCE
-msgid "Up"
-msgstr "上一é "
-
-#: html/Admin/Elements/EditScrip:128 html/Elements/RT__Ticket/ColumnMap:302 html/Search/Bulk.html:193 html/Search/Bulk.html:75
-msgid "Update"
-msgstr "處ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "Update All"
-msgstr "全部更新"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ID"
-msgstr "更新編號"
-
-#: html/Ticket/Update.html:135
-msgid "Update Ticket"
-msgstr "更新申請單"
-
-#: html/Search/Bulk.html:126 html/Ticket/ModifyAll.html:87 html/Ticket/Update.html:72
-msgid "Update Type"
-msgstr "更新類別"
-
-#: NOT FOUND IN SOURCE
-msgid "Update all these tickets at once"
-msgstr "整批更新申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Update email"
-msgstr "æ›´æ–°é›»å­éƒµä»¶ä¿¡ç®±"
-
-#: html/Search/Bulk.html:200 html/Search/Results.html:78
-msgid "Update multiple tickets"
-msgstr "批次更新申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Update name"
-msgstr "更新帳號"
-
-#: lib/RT/Action/CreateTickets.pm:750 lib/RT/Interface/Web.pm:584
-msgid "Update not recorded."
-msgstr "更新未被記錄"
-
-#: NOT FOUND IN SOURCE
-msgid "Update selected tickets"
-msgstr "æ›´æ–°é¸æ“‡çš„申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Update signature"
-msgstr "更新簽章"
-
-#: html/Ticket/ModifyAll.html:84
-msgid "Update ticket"
-msgstr "更新申請單"
-
-#: NOT FOUND IN SOURCE
-msgid "Update ticket # %1"
-msgstr "更新申請單 # %1"
-
-#: html/SelfService/Update.html:112 html/SelfService/Update.html:47
-#. ($Ticket->id)
-msgid "Update ticket #%1"
-msgstr "更新申請單 #%1"
-
-#: html/Ticket/Update.html:158
-#. ($TicketObj->id, $TicketObj->Subject)
-msgid "Update ticket #%1 (%2)"
-msgstr "更新申請單 #%1 (%2)"
-
-#: lib/RT/Action/CreateTickets.pm:748 lib/RT/Interface/Web.pm:583
-msgid "Update type was neither correspondence nor comment."
-msgstr "更新的內容並éžç”³è«‹å–®å›žè¦†ä¹Ÿä¸æ˜¯è©•è«–"
-
-#: html/Elements/SelectDateType:54 html/Ticket/Elements/ShowDates:72 lib/RT/CustomField_Overlay.pm:1284 lib/RT/Ticket_Overlay.pm:1171
-msgid "Updated"
-msgstr "å‰æ¬¡æ›´æ–°"
-
-#: html/Tools/Offline.html:93
-msgid "Upload"
-msgstr "上載"
-
-#: lib/RT/CustomField_Overlay.pm:84
-msgid "Upload multiple files"
-msgstr "上載多個檔案"
-
-#: lib/RT/CustomField_Overlay.pm:79
-msgid "Upload multiple images"
-msgstr "上載多份圖片"
-
-#: lib/RT/CustomField_Overlay.pm:85
-msgid "Upload one file"
-msgstr "上載一個檔案"
-
-#: lib/RT/CustomField_Overlay.pm:80
-msgid "Upload one image"
-msgstr "上載一份圖片"
-
-#: lib/RT/CustomField_Overlay.pm:86
-msgid "Upload up to %1 files"
-msgstr "上載最多 %1 個檔案"
-
-#: lib/RT/CustomField_Overlay.pm:81
-msgid "Upload up to %1 images"
-msgstr "上載最多 %1 份圖片"
-
-#: html/Tools/Offline.html:93
-msgid "Upload your changes"
-msgstr "上載您的更動"
-
-#: html/Admin/index.html:90
-msgid "Use other RT administrative tools"
-msgstr ""
-
-#: NOT FOUND IN SOURCE
-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"
-
-#: lib/RT/Ticket_Overlay.pm:506
-#. ($args{'Owner'})
-msgid "User '%1' could not be found."
-msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'。"
-
-#: 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:132 etc/initialdata:206
-msgid "User Defined"
-msgstr "使用者自訂"
-
-#: html/Admin/Elements/EditScrip:93
-msgid "User Defined conditions and actions"
-msgstr "使用者自訂的æ¢ä»¶åŠå‹•ä½œ"
-
-#: NOT FOUND IN SOURCE
-msgid "User ID"
-msgstr "使用者 ID"
-
-#: NOT FOUND IN SOURCE
-msgid "User Id"
-msgstr "使用者 ID"
-
-#: NOT FOUND IN SOURCE
-msgid "User Number"
-msgstr "員工編號"
-
-#: html/Admin/Elements/CustomFieldTabs:72 html/Admin/Elements/GroupTabs:68 html/Admin/Elements/QueueTabs:85 html/Admin/Elements/SystemTabs:68 html/Admin/Global/index.html:80
-msgid "User Rights"
-msgstr "使用者權é™"
-
-#: NOT FOUND IN SOURCE
-msgid "User Setup"
-msgstr "使用者設定"
-
-#: NOT FOUND IN SOURCE
-msgid "User Shift"
-msgstr "å“¡å·¥ç­åˆ¥"
-
-#: NOT FOUND IN SOURCE
-msgid "User asked for an unknown update type for custom field %1 for %2 object #%3"
-msgstr "使用者試圖在 %2 物件 #%3 çš„è‡ªè¨‚æ¬„ä½ %1 上執行未知的更新æ“作"
-
-#: html/Admin/Users/Modify.html:301
-#. ($msg)
-msgid "User could not be created: %1"
-msgstr "無法新增使用者:%1"
-
-#: lib/RT/User_Overlay.pm:330
-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/CustomFields/GroupRights.html:74 html/Admin/Global/GroupRights.html:88 html/Admin/Groups/GroupRights.html:75 html/Admin/Queues/GroupRights.html:90
-msgid "User defined groups"
-msgstr "使用者定義的群組"
-
-#: lib/RT/User_Overlay.pm:592 lib/RT/User_Overlay.pm:612
-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"
-
-#: NOT FOUND IN SOURCE
-msgid "User view"
-msgstr "使用者ç§äººè³‡æ–™"
-
-#: html/Admin/Groups/index.html:103
-msgid "User-defined groups"
-msgstr "使用者自定群組"
-
-#: NOT FOUND IN SOURCE
-msgid "UserDefined"
-msgstr "使用者自定"
-
-#: html/Admin/Users/Modify.html:69 html/Elements/Login:90 html/Ticket/Elements/AddWatchers:56
-msgid "Username"
-msgstr "帳號"
-
-#: html/Admin/Elements/GlobalCustomFieldTabs:55 html/Admin/Elements/SelectNewGroupMembers:47 html/Admin/Elements/Tabs:53 html/Admin/Global/CustomFields/index.html:64 html/Admin/Groups/Members.html:76 html/Admin/Queues/People.html:89 html/Admin/index.html:62 html/User/Groups/Members.html:79 lib/RT/CustomField_Overlay.pm:1208
-msgid "Users"
-msgstr "使用者"
-
-#: html/Admin/Users/index.html:85
-msgid "Users matching search criteria"
-msgstr "符åˆæŸ¥è©¢æ¢ä»¶çš„使用者"
-
-#: bin/rt-crontool:134
-#. ($transaction->id)
-msgid "Using transaction #%1..."
-msgstr "使用更動 #%1..."
-
-#: lib/RT/Tickets_Overlay_SQL.pm:528
-msgid "Valid Query"
-msgstr "åˆç†çš„查詢"
-
-#: html/Admin/CustomFields/Modify.html:80
-msgid "Validation"
-msgstr "é©—è­‰"
-
-#: NOT FOUND IN SOURCE
-msgid "ValueOfQueue"
-msgstr "é¸æ“‡è¡¨å–®"
-
-#: html/Admin/CustomFields/Modify.html:130 html/Admin/Elements/EditCustomField:78
-msgid "Values"
-msgstr "欄ä½å€¼"
-
-#: NOT FOUND IN SOURCE
-msgid "View log"
-msgstr "檢視紀錄檔"
-
-#: lib/RT/Queue_Overlay.pm:107
-msgid "Watch"
-msgstr "視察"
-
-#: lib/RT/Queue_Overlay.pm:108
-msgid "WatchAsAdminCc"
-msgstr "以管ç†å“¡å‰¯æœ¬æ”¶ä»¶äººèº«ä»½è¦–察"
-
-#: NOT FOUND IN SOURCE
-msgid "Watcher loaded"
-msgstr "æˆåŠŸè¼‰å…¥è¦–察員資訊"
-
-#: html/Admin/Elements/QueueTabs:63
-msgid "Watchers"
-msgstr "視察員"
-
-#: NOT FOUND IN SOURCE
-msgid "WebEncoding"
-msgstr "網é æ–‡å­—編碼方å¼"
-
-#: NOT FOUND IN SOURCE
-msgid "Wed"
-msgstr "星期三"
-
-#: lib/RT/Date.pm:419
-msgid "Wed."
-msgstr "星期三"
-
-#: html/Tools/MyDay.html:75
-msgid "What I did today"
-msgstr ""
-
-#: etc/initialdata:521
-msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
-msgstr "當申請單通éŽæ‰€æœ‰ç°½æ ¸å¾Œï¼Œå°‡æ­¤è¨Šæ¯å›žè¦†åˆ°åŽŸç”³è«‹å–®"
-
-#: etc/initialdata:485
-msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
-msgstr "當申請單通éŽæŸé …簽核後,將此訊æ¯å›žè¦†åˆ°åŽŸç”³è«‹å–®"
-
-#: etc/initialdata:146
-msgid "When a ticket is created"
-msgstr "新增申請單時"
-
-#: etc/initialdata:418
-msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
-msgstr "簽核單新增之後,通知應å—ç†çš„承辦人åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
-
-#: etc/initialdata:151
-msgid "When anything happens"
-msgstr "當任何事情發生時"
-
-#: etc/initialdata:199
-msgid "Whenever a ticket is resolved"
-msgstr "當申請單解決時"
-
-#: etc/initialdata:185
-msgid "Whenever a ticket's owner changes"
-msgstr "當申請單更æ›æ‰¿è¾¦äººæ™‚"
-
-#: etc/initialdata:178 etc/upgrade/3.1.17/content:16
-msgid "Whenever a ticket's priority changes"
-msgstr "當申請單的優先順åºæ”¹è®Šæ™‚"
-
-#: etc/initialdata:193
-msgid "Whenever a ticket's queue changes"
-msgstr "當申請單更æ›è¡¨å–®æ™‚"
-
-#: etc/initialdata:170
-msgid "Whenever a ticket's status changes"
-msgstr "當申請單更新ç¾æ³æ™‚"
-
-#: etc/initialdata:207
-msgid "Whenever a user-defined condition occurs"
-msgstr "當使用者自訂的情æ³ç™¼ç”Ÿæ™‚"
-
-#: etc/initialdata:164
-msgid "Whenever comments come in"
-msgstr "當評論é€é”時"
-
-#: etc/initialdata:157
-msgid "Whenever correspondence comes in"
-msgstr "當回覆é€é”時"
-
-#: html/Admin/Users/Modify.html:188 html/User/Prefs.html:88
-msgid "Work"
-msgstr "å…¬å¸"
-
-#: html/Search/Results.html:82
-msgid "Work offline"
-msgstr "離線工作"
-
-#: NOT FOUND IN SOURCE
-msgid "WorkPhone"
-msgstr "å…¬å¸é›»è©±"
-
-#: html/Ticket/Elements/ShowBasics:63 html/Ticket/Update.html:64
-msgid "Worked"
-msgstr "處ç†æ™‚é–“"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow #%1"
-msgstr "æµç¨‹ #%1"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow Begin"
-msgstr "æµç¨‹é–‹å§‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow End"
-msgstr "æµç¨‹çµæŸ"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflow deleted"
-msgstr "æµç¨‹å·²åˆªé™¤"
-
-#: NOT FOUND IN SOURCE
-msgid "Workflows"
-msgstr "æµç¨‹"
-
-#: NOT FOUND IN SOURCE
-msgid "Writable"
-msgstr "å¯è®€å¯«"
-
-#: NOT FOUND IN SOURCE
-msgid "XXX CHANGEME You are not an authorized user"
-msgstr "XXX CHANGEME 您是未經授權的使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "Yes"
-msgstr "是"
-
-#: lib/RT/Ticket_Overlay.pm:3140
-msgid "You already own this ticket"
-msgstr "您已是這份申請單的承辦人"
-
-#: html/autohandler:214 html/autohandler:222
-msgid "You are not an authorized user"
-msgstr "您ä¸æ˜¯è¢«æŽˆæ¬Šçš„使用者"
-
-#: NOT FOUND IN SOURCE
-msgid "You can access it with the Download button on the right."
-msgstr "您å¯ä»¥æŒ‰å³æ–¹çš„「下載ã€éµä¾†å–得。"
-
-#: html/Prefs/Search.html:56
-msgid "You can also edit the predefined search itself"
-msgstr ""
-
-#: lib/RT/Ticket_Overlay.pm:3025
-msgid "You can only reassign tickets that you own or that are unowned"
-msgstr "祇能é‡æ–°æŒ‡æ´¾æ‚¨æ‰€æ‰¿è¾¦æˆ–是沒有承辦人的申請單"
-
-#: lib/RT/Ticket_Overlay.pm:3021
-msgid "You can only take tickets 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:780
-#. ($num, $queue)
-msgid "You found %1 tickets in queue %2"
-msgstr "您會在表單 %2 找到 %1 的申請單"
-
-#: html/NoAuth/Logout.html:52
-msgid "You have been logged out of RT."
-msgstr "您已登出 RT。"
-
-#: html/SelfService/Display.html:133
-msgid "You have no permission to create tickets in that queue."
-msgstr "您沒有在該表單新增申請單的權é™ã€‚"
-
-#: lib/RT/Ticket_Overlay.pm:2003
-msgid "You may not create requests in that queue."
-msgstr "您ä¸èƒ½åœ¨è©²è¡¨å–®ä¸­æ出申請。"
-
-#: NOT FOUND IN SOURCE
-msgid "You need to restart the Request Tracker service for saved changes to take effect."
-msgstr "您必須é‡æ–°å•Ÿå‹• Request Tracker æœå‹™ï¼Œå„²å­˜çš„更動纔會生效。"
-
-#: html/NoAuth/Logout.html:56
-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:502
-msgid "Your request has been approved by %1. Other approvals may still be pending."
-msgstr "申請單已由 %1 批准。å¯èƒ½é‚„有其他待簽核的步驟。"
-
-#: etc/initialdata:540
-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:445
-msgid "Your request was rejected."
-msgstr "您的申請單已被é§å›žã€‚"
-
-#: html/autohandler:251
-msgid "Your username or password is incorrect"
-msgstr "您的帳號或密碼有誤"
-
-#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:149
-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 "急訊"
-
-#: lib/RT/System.pm:87
-msgid "allow creation of saved searches"
-msgstr "å…許建立é å­˜æŸ¥è©¢"
-
-#: lib/RT/System.pm:86
-msgid "allow loading of saved searches"
-msgstr "å…許載入é å­˜æŸ¥è©¢"
-
-#: NOT FOUND IN SOURCE
-msgid "approving"
-msgstr "待簽核"
-
-#: html/User/Elements/DelegateRights:80
-#. ($right->PrincipalObj->Object->SelfDescription)
-msgid "as granted to %1"
-msgstr "權é™åŒ %1"
-
-#: html/Search/Results.html:83
-msgid "chart"
-msgstr ""
-
-#: html/SelfService/Closed.html:49
-msgid "closed"
-msgstr "已解決"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:55
-msgid "contains"
-msgstr "包å«"
-
-#: NOT FOUND IN SOURCE
-msgid "content"
-msgstr "內容"
-
-#: NOT FOUND IN SOURCE
-msgid "content-type"
-msgstr "é¡žåž‹"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence (probably) not sent"
-msgstr "申請單回覆(å¯èƒ½)未é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "correspondence sent"
-msgstr "申請單回覆已é€å‡º"
-
-#: NOT FOUND IN SOURCE
-msgid "critical"
-msgstr "åš´é‡"
-
-#: html/Admin/Queues/Modify.html:98 lib/RT/Date.pm:346
-msgid "days"
-msgstr "天"
-
-#: NOT FOUND IN SOURCE
-msgid "dead"
-msgstr "拒絕處ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "debug"
-msgstr "åµéŒ¯"
-
-#: NOT FOUND IN SOURCE
-msgid "delete"
-msgstr "刪除"
-
-#: lib/RT/Queue_Overlay.pm:87
-msgid "deleted"
-msgstr "已刪除"
-
-#: html/Search/Elements/PickBasics:61
-msgid "does not match"
-msgstr "ä¸ç¬¦åˆ"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:56
-msgid "doesn't contain"
-msgstr "ä¸åŒ…å«"
-
-#: NOT FOUND IN SOURCE
-msgid "email address"
-msgstr "é›»å­éƒµä»¶ä¿¡ç®±"
-
-#: NOT FOUND IN SOURCE
-msgid "emergency"
-msgstr "å±é›£"
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "equal to"
-msgstr "等於"
-
-#: NOT FOUND IN SOURCE
-msgid "error"
-msgstr "錯誤"
-
-#: html/Search/Build.html:547
-msgid "error: can't move down"
-msgstr "錯誤:無法下移"
-
-#: html/Search/Build.html:569
-msgid "error: can't move left"
-msgstr "錯誤:無法左移"
-
-#: html/Search/Build.html:528
-msgid "error: can't move up"
-msgstr "錯誤:無法上移"
-
-#: html/Search/Build.html:612
-msgid "error: nothing to delete"
-msgstr "錯誤:沒有å¯åˆªé™¤çš„å°è±¡"
-
-#: html/Search/Build.html:533 html/Search/Build.html:552 html/Search/Build.html:574 html/Search/Build.html:603
-msgid "error: nothing to move"
-msgstr "錯誤:沒有å¯ç§»å‹•çš„å°è±¡"
-
-#: html/Search/Build.html:630
-msgid "error: nothing to toggle"
-msgstr "錯誤:沒有å¯åˆ‡æ›çš„å°è±¡"
-
-#: NOT FOUND IN SOURCE
-msgid "false"
-msgstr "å‡"
-
-#: NOT FOUND IN SOURCE
-msgid "filename"
-msgstr "檔å"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "greater than"
-msgstr "大於"
-
-#: lib/RT/Group_Overlay.pm:214
-#. ($self->Name)
-msgid "group '%1'"
-msgstr "群組 '%1'"
-
-#: html/Search/Results.html:88
-#. ($m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $Query))
-msgid "grouped by %1"
-msgstr "ä¾ %1 分組"
-
-#: lib/RT/Date.pm:342
-msgid "hours"
-msgstr "å°æ™‚"
-
-#: html/Search/Elements/PickBasics:48
-msgid "id"
-msgstr "編號"
-
-#: NOT FOUND IN SOURCE
-msgid "info"
-msgstr "資訊"
-
-#: html/Elements/SelectBoolean:53 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:57 html/Search/Elements/PickBasics:162 html/Search/Elements/PickBasics:74 html/Search/Elements/PickBasics:90 html/Search/Elements/PickCFs:53
-msgid "is"
-msgstr "是"
-
-#: html/Elements/SelectBoolean:57 html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectMatch:58 html/Search/Elements/PickBasics:163 html/Search/Elements/PickBasics:75 html/Search/Elements/PickBasics:91 html/Search/Elements/PickCFs:54
-msgid "isn't"
-msgstr "ä¸æ˜¯"
-
-#: html/Elements/SelectCustomFieldOperator:59 html/Elements/SelectEqualityOperator:59
-msgid "less than"
-msgstr "å°æ–¼"
-
-#: NOT FOUND IN SOURCE
-msgid "level Admin"
-msgstr "層主管"
-
-#: html/Search/Elements/PickBasics:60
-msgid "matches"
-msgstr "符åˆ"
-
-#: lib/RT/Date.pm:338
-msgid "min"
-msgstr "分"
-
-#: NOT FOUND IN SOURCE
-msgid "minutes"
-msgstr "分é˜"
-
-#: NOT FOUND IN SOURCE
-msgid "modifications\\n\\n"
-msgstr "更改\\n\\n"
-
-#: lib/RT/Date.pm:354
-msgid "months"
-msgstr "月"
-
-#: lib/RT/Queue_Overlay.pm:82
-msgid "new"
-msgstr "新建立"
-
-#: html/Admin/Elements/PickCustomFields:64 html/Admin/Elements/PickObjects:65
-msgid "no name"
-msgstr "沒有å稱"
-
-#: html/Admin/Elements/EditScrips:64
-msgid "no value"
-msgstr "沒有值"
-
-#: html/Admin/Elements/EditQueueWatchers:48 html/Ticket/Elements/EditWatchers:49
-msgid "none"
-msgstr "ç„¡"
-
-#: html/Elements/SelectEqualityOperator:59
-msgid "not equal to"
-msgstr "ä¸ç­‰æ–¼"
-
-#: NOT FOUND IN SOURCE
-msgid "notice"
-msgstr "æ示"
-
-#: NOT FOUND IN SOURCE
-msgid "notlike"
-msgstr "ä¸ç¬¦åˆ"
-
-#: NOT FOUND IN SOURCE
-msgid "number"
-msgstr "號"
-
-#: html/SelfService/Elements/MyRequests:82 lib/RT/Queue_Overlay.pm:83
-msgid "open"
-msgstr "é–‹å•Ÿ"
-
-#: NOT FOUND IN SOURCE
-msgid "opened"
-msgstr "已開啟"
-
-#: lib/RT/Group_Overlay.pm:219
-#. ($self->Name, $user->Name)
-msgid "personal group '%1' for user '%2'"
-msgstr "使用者「%2ã€çš„「%1ã€ä»£ç†äººç¾¤çµ„"
-
-#: lib/RT/Group_Overlay.pm:227
-#. ($queue->Name, $self->Type)
-msgid "queue %1 %2"
-msgstr "表單 %1 %2"
-
-#: lib/RT/Queue_Overlay.pm:86
-msgid "rejected"
-msgstr "å·²é§å›ž"
-
-#: lib/RT/Queue_Overlay.pm:85
-msgid "resolved"
-msgstr "已處ç†"
-
-#: NOT FOUND IN SOURCE
-msgid "rtname"
-msgstr "伺æœå™¨å稱"
-
-#: lib/RT/Date.pm:334
-msgid "sec"
-msgstr "秒"
-
-#: lib/RT/System.pm:85
-msgid "show Configuration tab"
-msgstr "顯示設定é ç±¤"
-
-#: html/Search/Results.html:80
-msgid "spreadsheet"
-msgstr "試算表"
-
-#: lib/RT/Queue_Overlay.pm:84
-msgid "stalled"
-msgstr "延宕"
-
-#: html/Search/Results.html:89
-#. ($m->scomp('Elements/SelectChartType', Name => 'ChartStyle'))
-msgid "style: %1"
-msgstr ""
-
-#: html/Prefs/MyRT.html:93
-msgid "summary rows"
-msgstr "加總列"
-
-#: lib/RT/Group_Overlay.pm:222
-#. ($self->Type)
-msgid "system %1"
-msgstr "系統 %1"
-
-#: lib/RT/Group_Overlay.pm:233
-#. ($self->Type)
-msgid "system group '%1'"
-msgstr "系統群組 '%1'"
-
-#: html/Elements/Error:64 html/SelfService/Error.html:63
-msgid "the calling component did not specify why"
-msgstr "呼å«å…ƒä»¶æœªæŒ‡æ˜ŽåŽŸå› "
-
-#: NOT FOUND IN SOURCE
-msgid "ticket #%1"
-msgstr "申請單 #%1"
-
-#: lib/RT/Group_Overlay.pm:230
-#. ($self->Instance, $self->Type)
-msgid "ticket #%1 %2"
-msgstr "申請單 #%1 %2"
-
-#: NOT FOUND IN SOURCE
-msgid "till"
-msgstr "至"
-
-#: NOT FOUND IN SOURCE
-msgid "to"
-msgstr "到"
-
-#: NOT FOUND IN SOURCE
-msgid "true"
-msgstr "真"
-
-#: lib/RT/Group_Overlay.pm:236
-#. ($self->Id)
-msgid "undescribed group %1"
-msgstr "沒有æ述的群組 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "unresolved"
-msgstr "未處ç†"
-
-#: lib/RT/Group_Overlay.pm:211
-#. ($user->Object->Name)
-msgid "user %1"
-msgstr "使用者 %1"
-
-#: NOT FOUND IN SOURCE
-msgid "warning"
-msgstr "警告"
-
-#: lib/RT/Date.pm:350
-msgid "weeks"
-msgstr "週"
-
-#: NOT FOUND IN SOURCE
-msgid "with template %1"
-msgstr "範本:%1"
-
-#: lib/RT/Date.pm:358
-msgid "years"
-msgstr "å¹´"
-
-msgid "Press 'Esc' to close this window."
-msgstr "按 'Esc' éµå¯é—œé–‰æœ¬è¦–窗。"
-
-msgid "HasMember"
-msgstr "æ“有æˆå“¡"
-
-msgid "LinkedTo"
-msgstr "連çµè‡³"
-
-msgid "Watcher"
-msgstr "視察員"
-
-msgid "(displaying new and open tickets for %1)"
-msgstr "(顯示 %1 å下新建立åŠé–‹å•Ÿä¸­çš„申請單)"
diff --git a/rt/lib/RT/Interface/Web_Vendor.pm b/rt/lib/RT/Interface/Web_Vendor.pm
new file mode 100644
index 0000000..1999096
--- /dev/null
+++ b/rt/lib/RT/Interface/Web_Vendor.pm
@@ -0,0 +1,201 @@
+# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+# Copyright (c) 2008 Freeside Internet Services, Inc.
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+=head1 NAME
+
+RT::Interface::Web_Vendor
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+Freeside vendor overlay for RT::Interface::Web.
+
+=begin testing
+
+use_ok(RT::Interface::Web_Vendor);
+
+=end testing
+
+=cut
+
+#package RT::Interface::Web;
+#use strict;
+
+package HTML::Mason::Commands;
+use strict;
+
+=head2 ProcessTicketCustomers
+
+=cut
+
+sub ProcessTicketCustomers {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ Debug => 0,
+ @_
+ );
+ my @results = ();
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+ my $Debug = $args{'Debug'};
+ my $me = 'ProcessTicketCustomers';
+
+ ### false laziness w/RT::Interface::Web::ProcessTicketLinks
+ # 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;
+
+ }
+
+ }
+ ###
+
+ ###
+ #find new customers
+ ###
+
+ my @custnums = map { /^Ticket-AddCustomer-(\d+)$/; $1 }
+ grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+ keys %$ARGSRef;
+
+ #my @delete_custnums =
+ # map { /^Ticket-AddCustomer-(\d+)$/; $1 }
+ # grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+ # keys %$ARGSRef;
+
+ ###
+ #figure out if we're going to auto-link requestors, and find them if so
+ ###
+
+ my $num_cur_cust = $Ticket->Customers->Count;
+ my $num_new_cust = scalar(@custnums);
+ warn "$me: $num_cur_cust current customers / $num_new_cust new customers\n"
+ if $Debug;
+
+ #if we're linking the first ticket to one customer
+ my $link_requestors = ( $num_cur_cust == 0 && $num_new_cust == 1 );
+ warn "$me: adding a single customer to a previously customerless".
+ " ticket, so linking customers to requestor too\n"
+ if $Debug && $link_requestors;
+
+ my @Requestors = ();
+ if ( $link_requestors ) {
+
+ #find any requestors without customers
+ @Requestors =
+ grep { ! $_->Customers->Count }
+ @{ $Ticket->Requestors->UserMembersObj->ItemsArrayRef };
+
+ warn "$me: found ". scalar(@Requestors). " requestors without".
+ " customers; linking them\n"
+ if $Debug;
+
+ }
+
+ ###
+ #link ticket (and requestors) to customers
+ ###
+
+ foreach my $custnum ( @custnums ) {
+
+ my @link = ( 'Type' => 'MemberOf',
+ 'Target' => "freeside://freeside/cust_main/$custnum",
+ );
+
+ my( $val, $msg ) = $Ticket->AddLink(@link);
+ push @results, $msg;
+
+ #add customer links to requestors
+ foreach my $Requestor ( @Requestors ) {
+ my( $val, $msg ) = $Requestor->AddLink(@link);
+ push @results, $msg;
+ warn "$me: linking requestor to custnum $custnum: $msg\n"
+ if $Debug > 1;
+ }
+
+ }
+
+ return @results;
+
+}
+
+#false laziness w/above... eventually it should go away in favor of this
+sub ProcessObjectCustomers {
+ my %args = (
+ Object => undef,
+ ARGSRef => undef,
+ @_
+ );
+ my @results = ();
+
+ my $Object = $args{'Object'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ ### false laziness w/RT::Interface::Web::ProcessTicketLinks
+ # 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 ) = $Object->DeleteLink( Base => $base,
+ Type => $type,
+ Target => $target );
+
+ push @results, $msg;
+
+ }
+
+ }
+ ###
+
+ #my @delete_custnums =
+ # map { /^Object-AddCustomer-(\d+)$/; $1 }
+ # grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+ # keys %$ARGSRef;
+
+ my @custnums = map { /^Object-AddCustomer-(\d+)$/; $1 }
+ grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+ keys %$ARGSRef;
+
+ foreach my $custnum ( @custnums ) {
+ my( $val, $msg ) =
+ $Object->AddLink( 'Type' => 'MemberOf',
+ 'Target' => "freeside://freeside/cust_main/$custnum",
+ );
+ push @results, $msg;
+ }
+
+ return @results;
+
+}
+
+1;
+
diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm
index 9ecad76..bee9442 100755
--- a/rt/lib/RT/Record.pm
+++ b/rt/lib/RT/Record.pm
@@ -67,6 +67,7 @@ use strict;
use warnings;
use RT::Date;
+use RT::I18N;
use RT::User;
use RT::Attributes;
use Encode qw();
@@ -803,6 +804,7 @@ sub _DecodeLOB {
elsif ( $ContentEncoding && $ContentEncoding ne 'none' ) {
return ( $self->loc( "Unknown ContentEncoding [_1]", $ContentEncoding ) );
}
+
if ( RT::I18N::IsTextualContentType($ContentType) ) {
$Content = Encode::decode_utf8($Content) unless Encode::is_utf8($Content);
}
@@ -1171,8 +1173,37 @@ sub DependsOn {
# }}}
+# {{{ Customers
+
+=head2 Customers
+
+ This returns an RT::Links object which references all the customers that this object is a member of.
+
+=cut
+
+sub Customers {
+ my( $self, %opt ) = @_;
+ my $Debug = $opt{'Debug'};
+
+ unless ( $self->{'Customers'} ) {
+ $self->{'Customers'} = $self->MemberOf->Clone;
+ $self->{'Customers'}->Limit(
+ FIELD => 'Target',
+ OPERATOR => 'STARTSWITH',
+ VALUE => 'freeside://freeside/cust_main/',
+ );
+ }
+
+ warn "->Customers method called on $self; returning ".
+ ref($self->{'Customers'}). ' object'
+ if $Debug;
+
+ return $self->{'Customers'};
+}
+
+# }}}
# {{{ sub _Links
diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm
index 3bae0ee..671d880 100644
--- a/rt/lib/RT/SearchBuilder.pm
+++ b/rt/lib/RT/SearchBuilder.pm
@@ -65,7 +65,7 @@
package RT::SearchBuilder;
use RT::Base;
-use DBIx::SearchBuilder "1.40";
+use DBIx::SearchBuilder "1.50";
use strict;
use warnings;
diff --git a/rt/lib/RT/TicketCustomFieldValue.pm b/rt/lib/RT/TicketCustomFieldValue.pm
deleted file mode 100644
index 7176472..0000000
--- a/rt/lib/RT/TicketCustomFieldValue.pm
+++ /dev/null
@@ -1,308 +0,0 @@
-# {{{ BEGIN BPS TAGGED BLOCK
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# }}} END BPS TAGGED BLOCK
-# 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(@_);
-}
-
-
-
-
-
-=head2 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'},
-);
-
-}
-
-
-
-=head2 id
-
-Returns the current value of id.
-(In the database, id is stored as int(11).)
-
-
-=cut
-
-
-=head2 Ticket
-
-Returns the current value of Ticket.
-(In the database, Ticket is stored as int(11).)
-
-
-
-=head2 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
-
-
-=head2 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);
-}
-
-=head2 CustomField
-
-Returns the current value of CustomField.
-(In the database, CustomField is stored as int(11).)
-
-
-
-=head2 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
-
-
-=head2 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);
-}
-
-=head2 Content
-
-Returns the current value of Content.
-(In the database, Content is stored as varchar(255).)
-
-
-
-=head2 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
-
-
-=head2 Creator
-
-Returns the current value of Creator.
-(In the database, Creator is stored as int(11).)
-
-
-=cut
-
-
-=head2 Created
-
-Returns the current value of Created.
-(In the database, Created is stored as datetime.)
-
-
-=cut
-
-
-=head2 LastUpdatedBy
-
-Returns the current value of LastUpdatedBy.
-(In the database, LastUpdatedBy is stored as int(11).)
-
-
-=cut
-
-
-=head2 LastUpdated
-
-Returns the current value of LastUpdated.
-(In the database, LastUpdated is stored as datetime.)
-
-
-=cut
-
-
-
-sub _CoreAccessible {
- {
-
- 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
deleted file mode 100644
index 270c593..0000000
--- a/rt/lib/RT/TicketCustomFieldValue_Overlay.pm
+++ /dev/null
@@ -1,74 +0,0 @@
-# {{{ BEGIN BPS TAGGED BLOCK
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# }}} END BPS TAGGED BLOCK
-use strict;
-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
deleted file mode 100644
index 2174afe..0000000
--- a/rt/lib/RT/TicketCustomFieldValues.pm
+++ /dev/null
@@ -1,137 +0,0 @@
-# {{{ BEGIN BPS TAGGED BLOCK
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# }}} END BPS TAGGED BLOCK
-# 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(@_) );
-}
-
-
-=head2 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
deleted file mode 100644
index 8cbaca5..0000000
--- a/rt/lib/RT/TicketCustomFieldValues_Overlay.pm
+++ /dev/null
@@ -1,108 +0,0 @@
-# {{{ BEGIN BPS TAGGED BLOCK
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# }}} END BPS TAGGED BLOCK
-use strict;
-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
index 186853e..83737c1 100644
--- a/rt/lib/RT/Ticket_Overlay.pm
+++ b/rt/lib/RT/Ticket_Overlay.pm
@@ -560,6 +560,68 @@ sub Create {
# }}}
+ # {{{ Deal with auto-customer association
+
+ #unless we already have (a) customer(s)...
+ unless ( $self->Customers->Count ) {
+
+ #first find any requestors with emails but *without* customer targets
+ my @NoCust_Requestors =
+ grep { $_->EmailAddress && ! $_->Customers->Count }
+ @{ $self->_Requestors->UserMembersObj->ItemsArrayRef };
+
+ for my $Requestor (@NoCust_Requestors) {
+
+ #perhaps the stuff in here should be in a User method??
+ my @Customers =
+ &RT::URI::freeside::email_search( email=>$Requestor->EmailAddress );
+
+ foreach my $custnum ( map $_->{'custnum'}, @Customers ) {
+
+ ## false laziness w/RT/Interface/Web_Vendor.pm
+ my @link = ( 'Type' => 'MemberOf',
+ 'Target' => "freeside://freeside/cust_main/$custnum",
+ );
+
+ my( $val, $msg ) = $Requestor->_AddLink(@link);
+ #XXX should do something with $msg# push @non_fatal_errors, $msg;
+
+ }
+
+ }
+
+ #find any requestors with customer targets
+
+ my %cust_target = ();
+
+ my @Requestors =
+ grep { $_->Customers->Count }
+ @{ $self->_Requestors->UserMembersObj->ItemsArrayRef };
+
+ foreach my $Requestor ( @Requestors ) {
+ foreach my $cust_link ( @{ $Requestor->Customers->ItemsArrayRef } ) {
+ $cust_target{ $cust_link->Target } = 1;
+ }
+ }
+
+ #and then auto-associate this ticket with those customers
+
+ foreach my $cust_target ( keys %cust_target ) {
+
+ my @link = ( 'Type' => 'MemberOf',
+ #'Target' => "freeside://freeside/cust_main/$custnum",
+ 'Target' => $cust_target,
+ );
+
+ my( $val, $msg ) = $self->_AddLink(@link);
+ push @non_fatal_errors, $msg;
+
+ }
+
+ }
+
+ # }}}
+
# {{{ Add all the custom fields
foreach my $arg ( keys %args ) {
@@ -646,7 +708,6 @@ sub Create {
} else {
$Owner = $DeferOwner;
$self->__Set(Field => 'Owner', Value => $Owner->id);
-
}
$self->OwnerGroup->_AddMember(
PrincipalId => $Owner->PrincipalId,
@@ -1428,6 +1489,25 @@ sub Requestors {
# }}}
+# {{{ sub _Requestors
+
+=head2 _Requestors
+
+Private non-ACLed variant of Reqeustors so that we can look them up for the
+purposes of customer auto-association during create.
+
+=cut
+
+sub _Requestors {
+ my $self = shift;
+
+ my $group = RT::Group->new($RT::SystemUser);
+ $group->LoadTicketRoleGroup(Type => 'Requestor', Ticket => $self->Id);
+ return ($group);
+}
+
+# }}}
+
# {{{ sub Cc
=head2 Cc
diff --git a/rt/lib/RT/Transaction_Overlay.pm b/rt/lib/RT/Transaction_Overlay.pm
index 96ccf51..e51e5b5 100644
--- a/rt/lib/RT/Transaction_Overlay.pm
+++ b/rt/lib/RT/Transaction_Overlay.pm
@@ -286,7 +286,7 @@ textual part (as defined in RT::I18N::IsTextualContentType). Otherwise,
returns undef.
Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message
-at $args{'Wrap'}. $args{'Wrap'} defaults to 70.
+at $args{'Wrap'}. $args{'Wrap'} defaults to $RT::MessageBoxWidth - 2 or 70.
If $args{'Type'} is set to C<text/html>, this will return an HTML
part of the message, if available. Otherwise it looks for a text/plain
@@ -302,6 +302,7 @@ sub Content {
Type => $PreferredContentType || 'text/plain',
Quote => 0,
Wrap => 70,
+ Wrap => ( $RT::MessageBoxWidth || 72 ) - 2,
@_
);
@@ -346,7 +347,7 @@ sub Content {
$max = length if length > $max;
}
- if ( $max > 76 ) {
+ if ( $max > $args{'Wrap'}+6 ) { # 76 ) {
require Text::Wrapper;
my $wrapper = new Text::Wrapper(
columns => $args{'Wrap'},
diff --git a/rt/lib/RT/URI/freeside.pm b/rt/lib/RT/URI/freeside.pm
new file mode 100644
index 0000000..6194fd0
--- /dev/null
+++ b/rt/lib/RT/URI/freeside.pm
@@ -0,0 +1,297 @@
+# 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 base qw( RT::URI::base );
+use strict;
+use vars qw( $IntegrationType $URL );
+use Carp qw( cluck );
+
+
+=head1 NAME
+
+RT::URI::freeside
+
+=head1 DESCRIPTION
+
+URI handler for Freeside URIs. See http://www.freeside.biz/ for more
+information on Freeside.
+
+
+=head1 Public subroutines
+
+=over 4
+
+=item FreesideGetConfig CONFKEY
+
+Subroutine that returns the freeside's configuration value(s) for CONFKEY
+as a scalar or list.
+
+=cut
+
+sub FreesideGetConfig { return undef; }
+
+
+=item FreesideURL
+
+Returns the URL for freeside's web interface.
+
+=cut
+
+sub FreesideURL { return $URL; }
+
+
+=item FreesideVersion
+
+Returns a string describing the freeside version being used.
+
+=cut
+
+sub FreesideVersion { return undef; }
+
+
+=item smart_search
+
+A wrapper for the FS::cust_main::smart_search subroutine.
+
+=cut
+
+sub smart_search { return undef; }
+
+
+=item small_custview
+
+A wrapper for the FS::CGI::small_custview subroutine.
+
+=cut
+
+sub small_custview { return 'Freeside integration error!</A>'; }
+
+
+=back
+
+=head1 Private methods
+
+=over 4
+
+=item _FreesideGetRecord
+
+Method returns a hashref of the freeside record referenced in the URI.
+Must be called after ParseURI.
+
+=cut
+
+sub _FreesideGetRecord { return undef; }
+
+
+=item _FreesideURIPrefix
+
+Method that returns the URI prefix for freeside URIs.
+
+=cut
+
+sub _FreesideURIPrefix {
+
+ my $self = shift;
+ return($self->Scheme . '://freeside');
+
+}
+
+=item _FreesideURILabel
+
+Method that returns a short string describing the customer referenced
+in the URI.
+
+=cut
+
+sub _FreesideURILabel {
+
+ my $self = shift;
+
+ #$RT::Logger->debug("Called _FreesideURILabel()");
+
+ return unless (exists($self->{'fstable'}) and
+ exists($self->{'fspkey'}));
+
+ my $label;
+ my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'});
+
+ #if ($table ne 'cust_main') {
+ # warn "FS::${table} not currently supported";
+ # return;
+ #}
+
+ my $rec = $self->_FreesideGetRecord();
+
+ if (ref($rec) eq 'HASH' && $table eq 'cust_main') {
+ my $name = $rec->{'last'} . ', ' . $rec->{'first'};
+ $name = $rec->{'company'} . " ($name)" if $rec->{'company'};
+ $label = "$pkey: $name";
+ } elsif ( $table eq 'cust_svc' && ref($rec) && $rec->{'_object'} ) {
+ #Internal only
+ my($l,$v) = $rec->{'_object'}->label;
+ $label = "$l: $v";
+ } else {
+ $label = "$pkey: $table";
+ }
+
+ if ($label and !$@) {
+ return($label);
+ } else {
+ return;
+ }
+
+}
+
+=item _FreesideURILabelLong
+
+Method that returns a longer string describing the customer referenced
+in the URI.
+
+=cut
+
+sub _FreesideURILabelLong {
+
+ my $self = shift;
+
+ return $self->_FreesideURILabel();
+
+}
+
+=back
+
+=head1 Public methods
+
+=over 4
+
+=cut
+
+sub ParseURI {
+ my $self = shift;
+ my $uri = shift;
+ my ($table, $pkey);
+
+ my $uriprefix = $self->_FreesideURIPrefix;
+ if ($uri =~ /^$uriprefix\/(\w+)\/(\d*)$/) {
+
+ $table = $1;
+ $pkey = $2;
+
+ unless ( $pkey ) {
+ cluck "bad URL $uri";
+ return(undef);
+ }
+
+ $self->{'scheme'} = $self->Scheme;
+
+ } else {
+ return(undef);
+ }
+
+ $self->{'uri'} = "${uriprefix}/${table}/${pkey}";
+ $self->{'fstable'} = $table;
+ $self->{'fspkey'} = $pkey;
+
+
+ my $url = $self->FreesideURL();
+
+ if ($url ne '') {
+ $self->{'href'} = "${url}/view/${table}.cgi?${pkey}";
+ } else {
+ $self->{'href'} = $self->{'uri'};
+ }
+
+ $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;
+}
+
+=item 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;
+ }
+}
+
+=item AsStringLong
+
+Return a longer (HTML) string representing the URI object.
+
+=cut
+
+sub AsStringLong {
+ my $self = shift;
+ my $prettystring;
+ if ($prettystring = $self->_FreesideURILabelLong || $self->_FreesideURILabel){
+ return $prettystring;
+ } else {
+ return $self->URI;
+ }
+}
+
+$IntegrationType ||= 'Internal';
+eval "require RT::URI::freeside::${RT::URI::freeside::IntegrationType}";
+warn $@ if $@;
+if ($@ &&
+ $@ !~ qr(^Can't locate RT/URI/freeside/${RT::URI::freeside::IntegrationType}.pm)) {
+ die $@;
+};
+
+=back
+
+=cut
+
+1;
diff --git a/rt/lib/RT/URI/freeside/Internal.pm b/rt/lib/RT/URI/freeside/Internal.pm
new file mode 100644
index 0000000..bd7c42c
--- /dev/null
+++ b/rt/lib/RT/URI/freeside/Internal.pm
@@ -0,0 +1,145 @@
+# 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
+#
+use strict;
+no warnings qw(redefine);
+
+#use vars qw($conf);
+
+use FS;
+use FS::UID qw(dbh);
+use FS::CGI qw(popurl);
+use FS::UI::Web::small_custview qw(small_custview);
+use FS::Conf;
+use FS::Record qw(qsearchs qsearch dbdef);
+use FS::cust_main;
+use FS::cust_svc;
+
+=head1 NAME
+
+RT::URI::freeside::Internal
+
+=head1 DESCRIPTION
+
+Overlay for the RT::URI::freeside URI handler implementing the Internal integration type.
+
+See L<RT::URI::freeside> for public/private interface documentation.
+
+=cut
+
+
+
+sub _FreesideGetRecord {
+
+ my $self = shift;
+ my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'});
+
+ $RT::Logger->debug("Called _FreesideGetRecord()");
+
+ #eval "use FS::$table;";
+
+ my $dbdef = dbdef;
+ unless ($dbdef) {
+ $RT::Logger->error("Using Internal freeside integration type, ".
+ "but it doesn't look like we're running under ".
+ "freeside's Mason handler.");
+ return;
+ }
+
+ my $pkeyfield = $dbdef->table($table)->primary_key;
+ unless ($pkeyfield) {
+ $RT::Logger->error("No primary key for freeside table '$table'");
+ return;
+ }
+
+ my $fsrec = qsearchs($table, { $pkeyfield => $pkey });
+ unless ($fsrec) {
+ $RT::Logger->error("Record with '$pkeyfield' == '$pkey' does " .
+ "not exist in table $table");
+ return;
+ }
+
+ return { $fsrec->hash, '_object' => $fsrec };
+
+}
+
+sub FreesideVersion {
+
+ return $FS::VERSION;
+
+}
+
+sub FreesideGetConfig {
+
+ #$conf = new FS::Conf unless ref($conf);
+ my $conf = new FS::Conf;
+
+ return scalar($conf->config(@_));
+
+}
+
+sub smart_search { #Subroutine
+
+ return map { { $_->hash } } &FS::cust_main::smart_search(@_);
+
+}
+
+sub email_search { #Subroutine
+
+ return map { { $_->hash } } &FS::cust_main::email_search(@_);
+
+}
+
+sub small_custview {
+
+ return &FS::UI::Web::small_custview::small_custview(@_);
+
+}
+
+sub _FreesideURILabelLong {
+
+ my $self = shift;
+
+ my $table = $self->{'fstable'};
+
+ if ( $table eq 'cust_main' ) {
+
+ my $rec = $self->_FreesideGetRecord();
+ return small_custview( $rec->{'_object'},
+ scalar(FS::Conf->new->config('countrydefault')),
+ 1 #nobalance
+ );
+
+ } else {
+
+ return $self->_FreesideURILabel();
+
+ }
+
+}
+
+1;
diff --git a/rt/lib/RT/URI/freeside/XMLRPC.pm b/rt/lib/RT/URI/freeside/XMLRPC.pm
new file mode 100644
index 0000000..916c20d
--- /dev/null
+++ b/rt/lib/RT/URI/freeside/XMLRPC.pm
@@ -0,0 +1,122 @@
+# 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
+
+use strict;
+no warnings qw(redefine);
+
+use vars qw($XMLRPC_URL $_FS_VERSION);
+
+use Frontier::Client;
+
+=head1 NAME
+
+RT::URI::freeside::XMLRPC
+
+=head1 DESCRIPTION
+
+Overlay for the RT::URI::freeside URI handler implementing the XMLRPC integration type.
+
+See L<RT::URI::freeside> for public/private interface documentation.
+
+=cut
+
+
+sub _XMLRPCRequest { #Subroutine
+
+ my $method = shift;
+ my @args = @_;
+
+ my $result;
+ eval {
+ my $server = new Frontier::Client ( url => $XMLRPC_URL );
+ $result = $server->call($method, @args);
+ };
+
+ if (not $@ and ref($result) eq 'ARRAY') {
+ return (scalar(@$result) == 1) ? @$result[0] : @$result;
+ } else {
+ $RT::Logger->debug("Freeside XMLRPC: " . $result || $@);
+ return ();
+ }
+
+}
+
+sub _FreesideGetRecord {
+
+ my $self = shift;
+ my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'});
+ my $record;
+
+ $RT::Logger->debug("Called XMLRPC::_FreesideGetRecord()");
+
+ #FIXME: Need a better way to get primary keys.
+ # Maybe create a method for it and cache them like version?
+ my %table_pkeys = (
+ cust_main => 'custnum',
+ );
+
+ my $method = 'Record.qsearchs';
+ my @args = ($table, { $table_pkeys{$table} => $pkey });
+ my ($record) = &_XMLRPCRequest($method, @args);
+
+ return $record;
+
+}
+
+
+sub FreesideGetConfig {
+
+ return _XMLRPCRequest('Conf.config', @_);
+
+}
+
+
+sub FreesideVersion {
+
+ return $_FS_VERSION if ($_FS_VERSION =~ /^\d+\.\d+\.\d+/);
+
+ $RT::Logger->debug("Requesting freeside version...");
+ ($_FS_VERSION) = &_XMLRPCRequest('version');
+ $RT::Logger->debug("Cached freeside version: ${_FS_VERSION}");
+
+ return $_FS_VERSION;
+
+}
+
+sub smart_search { #Subroutine
+
+ return _XMLRPCRequest('cust_main.smart_search', @_);
+
+}
+
+sub small_custview {
+
+ return _XMLRPCRequest('Web.UI.small_custview.small_custview', @_);
+
+}
+
+1;
diff --git a/rt/lib/RT/User_Overlay.pm b/rt/lib/RT/User_Overlay.pm
index 8582d78..2115c32 100644
--- a/rt/lib/RT/User_Overlay.pm
+++ b/rt/lib/RT/User_Overlay.pm
@@ -1331,6 +1331,268 @@ sub OwnGroups {
return $groups;
}
+# }}}
+
+# {{{ Links
+
+#much false laziness w/Ticket_Overlay.pm. now with RT 3.8!
+
+# A helper table for links 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', },
+ MergedInto => { Base => 'MergedInto',
+ Target => 'MergedInto', },
+
+);
+
+sub LINKDIRMAP { return \%LINKDIRMAP }
+
+#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"} );
+#}
+
+=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,
+ @_
+ );
+
+ unless ( $args{'Target'} || $args{'Base'} ) {
+ $RT::Logger->error("Base or Target must be specified\n");
+ return ( 0, $self->loc('Either base or target must be specified') );
+ }
+
+ #check acls
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('AdminUsers');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+# # If the other URI is an RT::Ticket, we want to make sure the user
+# # can modify it too...
+# my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
+# return (0, $msg) unless $status;
+# if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
+# $right++;
+# }
+# if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
+# ( $RT::StrictLinkACL && $right < 2 ) )
+# {
+# return ( 0, $self->loc("Permission Denied") );
+# }
+
+ my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
+
+ if ( !$val ) {
+ $RT::Logger->debug("Couldn't find that link\n");
+ return ( 0, $Msg );
+ }
+
+ my ($direction, $remote_link);
+
+ if ( $args{'Base'} ) {
+ $remote_link = $args{'Base'};
+ $direction = 'Target';
+ }
+ elsif ( $args{'Target'} ) {
+ $remote_link = $args{'Target'};
+ $direction='Base';
+ }
+
+ if ( $args{'Silent'} ) {
+ return ( $val, $Msg );
+ }
+ else {
+ my $remote_uri = RT::URI->new( $self->CurrentUser );
+ $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
+ );
+
+ if ( $remote_uri->IsLocal ) {
+
+ my $OtherObj = $remote_uri->Object;
+ my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
+ Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
+ : $LINKDIRMAP{$args{'Type'}}->{Target},
+ OldValue => $self->URI,
+ ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
+ TimeTaken => 0 );
+ }
+
+ return ( $Trans, $Msg );
+ }
+}
+
+sub AddLink {
+ my $self = shift;
+ my %args = ( Target => '',
+ Base => '',
+ Type => '',
+ Silent => undef,
+ @_ );
+
+ unless ( $args{'Target'} || $args{'Base'} ) {
+ $RT::Logger->error("Base or Target must be specified\n");
+ return ( 0, $self->loc('Either base or target must be specified') );
+ }
+
+ my $right = 0;
+ $right++ if $self->CurrentUserHasRight('AdminUsers');
+ if ( !$right && $RT::StrictLinkACL ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+# # If the other URI is an RT::Ticket, we want to make sure the user
+# # can modify it too...
+# my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
+# return (0, $msg) unless $status;
+# if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
+# $right++;
+# }
+# if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
+# ( $RT::StrictLinkACL && $right < 2 ) )
+# {
+# return ( 0, $self->loc("Permission Denied") );
+# }
+
+ return $self->_AddLink(%args);
+}
+
+#sub __GetTicketFromURI {
+# my $self = shift;
+# my %args = ( URI => '', @_ );
+#
+# # If the other URI is an RT::Ticket, we want to make sure the user
+# # can modify it too...
+# my $uri_obj = RT::URI->new( $self->CurrentUser );
+# $uri_obj->FromURI( $args{'URI'} );
+#
+# unless ( $uri_obj->Resolver && $uri_obj->Scheme ) {
+# my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} );
+# $RT::Logger->warning( "$msg\n" );
+# return( 0, $msg );
+# }
+# my $obj = $uri_obj->Resolver->Object;
+# unless ( UNIVERSAL::isa($obj, 'RT::Ticket') && $obj->id ) {
+# return (1, 'Found not a ticket', undef);
+# }
+# return (1, 'Found ticket', $obj);
+#}
+
+=head2 _AddLink
+
+Private non-acled variant of AddLink so that links can be added during create.
+
+=cut
+
+sub _AddLink {
+ my $self = shift;
+ my %args = ( Target => '',
+ Base => '',
+ Type => '',
+ Silent => undef,
+ @_ );
+
+ my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
+ return ($val, $msg) if !$val || $exist;
+
+ my ($direction, $remote_link);
+ if ( $args{'Target'} ) {
+ $remote_link = $args{'Target'};
+ $direction = 'Base';
+ } elsif ( $args{'Base'} ) {
+ $remote_link = $args{'Base'};
+ $direction = 'Target';
+ }
+
+ # Don't write the transaction if we're doing this on create
+ if ( $args{'Silent'} ) {
+ return ( $val, $msg );
+ }
+ else {
+ my $remote_uri = RT::URI->new( $self->CurrentUser );
+ $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 );
+
+ if ( $remote_uri->IsLocal ) {
+
+ my $OtherObj = $remote_uri->Object;
+ my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
+ Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
+ : $LINKDIRMAP{$args{'Type'}}->{Target},
+ NewValue => $self->URI,
+ ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
+ TimeTaken => 0 );
+ }
+ return ( $val, $Msg );
+ }
+
+}
+
+
+
+# }}}
+
=head2 HasRight
Shim around PrincipalObj->HasRight. See L<RT::Principal>.
diff --git a/rt/lib/RT/Users_Overlay.pm b/rt/lib/RT/Users_Overlay.pm
index 4bd7783..ea12dbe 100644
--- a/rt/lib/RT/Users_Overlay.pm
+++ b/rt/lib/RT/Users_Overlay.pm
@@ -379,6 +379,7 @@ sub WhoHaveRight {
$from_group->WhoHaveGroupRight( %args );
#XXX: DIRTY HACK
+ use DBIx::SearchBuilder 1.50; #no version on ::Union :(
use DBIx::SearchBuilder::Union;
my $union = new DBIx::SearchBuilder::Union;
$union->add( $from_group );
diff --git a/rt/lib/RTx/Calendar.pm b/rt/lib/RTx/Calendar.pm
new file mode 100644
index 0000000..20568e8
--- /dev/null
+++ b/rt/lib/RTx/Calendar.pm
@@ -0,0 +1,233 @@
+package RTx::Calendar;
+
+use strict;
+use base qw( Exporter );
+use DateTime;
+use DateTime::Set;
+
+our $VERSION = "0.07";
+
+our @EXPORT_OK = qw( FirstDay LastDay );
+
+sub FirstDay {
+ my ($year, $month, $matchday) = @_;
+ my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->subtract( days => 1 ) }
+ );
+
+ my $day = DateTime->new( year => $year, month => $month );
+
+ $day = $set->next($day) while $day->day_of_week != $matchday;
+ $day;
+
+}
+
+sub LastDay {
+ my ($year, $month, $matchday) = @_;
+ my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
+ );
+
+ my $day = DateTime->last_day_of_month( year => $year, month => $month );
+
+ $day = $set->next($day) while $day->day_of_week != $matchday;
+ $day;
+}
+
+# we can't use RT::Date::Date because it uses gmtime
+# and we need localtime
+sub LocalDate {
+ my $ts = shift;
+ my ($d,$m,$y) = (localtime($ts))[3..5];
+ sprintf "%4d-%02d-%02d", ($y + 1900), ++$m, $d;
+}
+
+sub DatesClauses {
+ my ($Dates, $begin, $end) = @_;
+
+ my $clauses = "";
+
+ my @DateClauses = map {
+ "($_ >= '" . $begin . "' AND $_ <= '" . $end . "')"
+ } @$Dates;
+ $clauses .= " AND " . " ( " . join(" OR ", @DateClauses) . " ) "
+ if @DateClauses;
+
+ return $clauses
+}
+
+sub FindTickets {
+ my ($CurrentUser, $Query, $Dates, $begin, $end) = @_;
+
+ $Query .= DatesClauses($Dates, $begin, $end)
+ if $begin and $end;
+
+ my $Tickets = RT::Tickets->new($CurrentUser);
+ $Tickets->FromSQL($Query);
+
+ my %Tickets;
+ my %AlreadySeen;
+
+ while ( my $Ticket = $Tickets->Next()) {
+
+ # How to find the LastContacted date ?
+ for my $Date (@$Dates) {
+ my $DateObj = $Date . "Obj";
+ push @{ $Tickets{ LocalDate($Ticket->$DateObj->Unix) } }, $Ticket
+ # if reminder, check it's refering to a ticket
+ unless ($Ticket->Type eq 'reminder' and not $Ticket->RefersTo->First)
+ or $AlreadySeen{ LocalDate($Ticket->$DateObj->Unix) }{ $Ticket }++;
+ }
+ }
+ return %Tickets;
+}
+
+#
+# Take a user object and return the search with Description "calendar" if it exists
+#
+sub SearchDefaultCalendar {
+ my $CurrentUser = shift;
+ my $Description = "calendar";
+
+ # I'm quite sure the loop isn't usefull but...
+ my @Objects = $CurrentUser->UserObj;
+ for my $object (@Objects) {
+ next unless ref($object) eq 'RT::User' && $object->id == $CurrentUser->Id;
+ my @searches = $object->Attributes->Named('SavedSearch');
+ for my $search (@searches) {
+ next if ($search->SubValue('SearchType')
+ && $search->SubValue('SearchType') ne 'Ticket');
+
+ return $search
+ if "calendar" eq $search->Description;
+ }
+ }
+}
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+RTx::Calendar - Calendar for RT due tasks
+
+=head1 VERSION
+
+This document describes version 0.07 of RTx::Calendar
+
+=head1 DESCRIPTION
+
+This RT extension provides a calendar view for your tickets and your
+reminders so you see when is your next due ticket. You can find it in
+the menu Search->Calendar.
+
+There's a portlet to put on your home page (see Prefs/MyRT.html)
+
+You can also enable ics (ICal) feeds for your default calendar and all
+your private searches in Prefs/Calendar.html. Authentication is magic
+number based so that you can give those feeds to other people.
+
+You can find screenshots on
+http://gaspard.mine.nu/dotclear/index.php?tag/rtx-calendar
+
+=head1 INSTALLATION
+
+If you upgrade from 0.02, see next part before.
+
+You need to install those three modules :
+
+ * Date::ICal
+ * Data::ICal
+ * DateTime::Set
+
+Install it like a standard perl module
+
+ perl Makefile.PL
+ make
+ make install
+
+If your RT is not in the default path (/opt/rt3) you must set RTHOME
+before doing the Makefile.PL
+
+=head1 CONFIGURATION
+
+=head2 Base configuration
+
+In RT 3.8 and later, to enable calendar plugin, you must add something
+like that in your etc/RT_SiteConfig.pm :
+
+ Set(@Plugins,(qw(RTx::Calendar)));
+
+To use MyCalendar portlet you must add MyCalendar to
+$HomepageComponents in etc/RT_SiteConfig.pm like that :
+
+ Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
+ MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
+
+To enable private searches ICal feeds, you need to give
+CreateSavedSearch and LoadSavedSearch rights to your users.
+
+=head2 Display configuration
+
+You can show the owner in each day box by adding this line to your
+etc/RT_SiteConfig.pm :
+
+ Set($CalendarDisplayOwner, 1);
+
+You can change which fields show up in the popup display when you
+mouse over a date in etc/RT_SiteConfig.pm :
+
+ @CalendarPopupFields = ('Status', 'OwnerObj->Name', 'DueObj->ISO');
+
+=head2 ICAL feed configuration
+
+By default, tickets are todo and reminders event. You can change this
+by setting $RT::ICalTicketType and $RT::ICalReminderType in etc/RT_SiteConfig.pm :
+
+ Set($ICalTicketType, "Data::ICal::Entry::Event");
+ Set($ICalReminderType ,"Data::ICal::Entry::Todo");
+
+=head1 USAGE
+
+A small help section is available in /Prefs/Calendar.html
+
+=head1 UPGRADE FROM 0.02
+
+As I've change directory structure, if you upgrade from 0.02 you need
+to delete old files manually. Go in RTHOME/share/html (by default
+/opt/rt3/share/html) and delete those files :
+
+ rm -rf Callbacks/RTx-Calendar
+ rm Tools/Calendar.html
+
+RTx-Calendar may work without this but it's not very clean.
+
+=head1 BUGS
+
+=over
+
+=item *
+compatible only with RT 3.6 for the moment. If someone need
+compatibility with 3.4 I can work on this. And I will work on 3.7
+compatibility later.
+
+=back
+
+=head1 AUTHORS
+
+Nicolas Chuche E<lt>nchuche@barna.beE<gt>
+
+Idea borrowed from redmine's calendar (Thanks Jean-Philippe).
+
+=head1 COPYRIGHT
+
+Copyright 2007 by Nicolas Chuche E<lt>nchuche@barna.beE<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/lib/RTx/Statistics.pm b/rt/lib/RTx/Statistics.pm
new file mode 100755
index 0000000..8b9d6e4
--- /dev/null
+++ b/rt/lib/RTx/Statistics.pm
@@ -0,0 +1,239 @@
+package Statistics;
+
+use vars qw(
+$MultiQueueStatus $MultiQueueDateFormat @MultiQueueQueueList $MultiQueueMaxRows $MultiQueueWeekends $MultiQueueLabelDateFormat
+$PerDayStatus $PerDayDateFormat $PerDayQueue $PerDayMaxRows $PerDayWeekends $PerDayLabelDateFormat $PerDayPeriod
+$DayOfWeekQueue
+@OpenStalledQueueList $OpenStalledWeekends
+$TimeToResolveDateFormat $TimeToResolveQueue $TimeToResolveMaxRows $TimeToResolveWeekends $TimeToResolveLabelDateFormat
+$TimeToResolveGraphQueue
+@years @months %monthsMaxDay
+$secsPerDay
+$RestrictAccess
+$GraphWidth $GraphHeight
+);
+
+use Time::Local;
+
+# I couldn't figure out a way to override these in RT_SiteConfig, which would be
+# preferable.
+
+# Width and Height of all graphics
+$GraphWidth=500;
+$GraphHeight=400;
+
+# Initial settings for the CallsMultiQueue stat page
+$MultiQueueStatus = "resolved";
+$MultiQueueDateFormat = "%a %b %d %Y"; # format for dates on Multi Queue report, see "man strftime" for options
+@MultiQueueQueueList = ("General"); # list of queues to start Multi Queue per day reports
+$MultiQueueMaxRows = 10;
+$MultiQueueWeekends = 1;
+$MultiQueueLabelDateFormat = "%a";
+
+# Initial settings for the CallsQueueDay stat page
+$PerDayStatus = "resolved";
+$PerDayDateFormat = "%a %b %d %Y";
+$PerDayQueue = "General";
+$PerDayMaxRows = 10;
+$PerDayWeekends = 1;
+$PerDayLabelDateFormat = "%a";
+$PerDayPeriod = 10;
+
+# Initial settings for the DayOfWeek stat page
+$DayOfWeekQueue = "General";
+
+# Initial settings for the OpenStalled stat page
+@OpenStalledQueueList = ("General");
+$OpenStalledWeekends = 1;
+
+# Initial settings for the TimeToResolve stat page
+$TimeToResolveDateFormat = "%a %b %d";
+$TimeToResolveQueue = "General";
+$TimeToResolveMaxRows = 10;
+$TimeToResolveWeekends = 1;
+$TimeToResolveLabelDateFormat = "%a";
+
+# Initial settings for the TimeToResolve Graph page
+$TimeToResolveGraphQueue = "General";
+
+$secsPerDay = 86400;
+
+# List of years and months to populate drop down lists
+@years =('2010', '2009', '2008', '2007', '2006', '2005', '2004', '2003' ,'2003' ,'2002');
+@months=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
+%monthsMaxDay = (
+ 0 => 31, # January
+ 1 => 29, # February, allow for leap year
+ 2 => 31, # March
+ 3 => 30, # April
+ 4 => 31, # May
+ 5 => 30, # June
+ 6 => 31, # July
+ 7 => 31, # August
+ 8 => 30, # September
+ 9 => 31, # October
+ 10=> 30, # November
+ 11=> 31 # December
+ );
+
+# Set to one to prevent users without the ShowConfigTab right from seeing Statistics
+$RestrictAccess = 0;
+
+# Variables to control debugging
+my $debugging=0; # set to 1 to enable debugging
+my $debugtext="";
+
+=head2 FormatDate
+
+Returns a string representing the specified date formatted by the specified string
+
+=cut
+sub FormatDate {
+ my $fmt = shift;
+ my $self = shift;
+ return POSIX::strftime($fmt, localtime($self->Unix));
+}
+
+
+=head2 RTDateSetToLocalMidnight
+
+Sets the date to midnight (at the beginning of the day) local time
+Returns the unixtime at midnight.
+
+=cut
+sub RTDateSetToLocalMidnight {
+ my $self = shift;
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+ $self->Unix(timelocal (0,0,0,$mday,$mon,$year,$wday,$yday));
+
+ return ($self->Unix);
+}
+
+=head2 RTDateIsWeekend
+
+Returns 1 if the date is on saturday or sunday
+
+=cut
+sub RTDateIsWeekend {
+ my $self = shift;
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+ return 1 if (($wday==6) || ($wday==0));
+ 0;
+}
+
+=head2 RTDateGetDateWeekday
+
+Returns the localized name of the day specified by date
+
+=cut
+sub RTDateGetDateWeekday {
+ my $self = shift;
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime($self->Unix);
+ return $self->GetWeekday($wday);
+}
+
+=head2 RTDateSubDay
+
+Subtracts 24 hours from the current time
+
+=cut
+
+sub RTDateSubDay {
+ my $self = shift;
+ $self->AddSeconds(0 - $DAY);
+}
+
+=head2 RTDateSubDays $DAYS
+
+Subtracts 24 hours * $DAYS from the current time
+
+=cut
+
+sub RTDateSubDays {
+ my $self = shift;
+ my $days = shift;
+ $self->AddSeconds(0 - ($days * $DAY));
+}
+
+=head2 DebugInit
+
+Creates a text area on the page if debugging is on.
+
+=cut
+
+sub DebugInit {
+ if($debugging) {
+ my $m = shift;
+ $m->print("<TEXTAREA NAME=debugarea COLS=120 ROWS=50>$debugtext</TEXTAREA>\n");
+ }
+}
+
+=head2 DebugLog $logmsg
+
+Adds a message to the debug area
+
+=cut
+
+sub DebugLog {
+ if($debugging) {
+ my $line = shift;
+ $debugtext .= $line;
+ $RT::Logger->debug($line);
+ }
+}
+
+=head2 DebugClear
+
+Clears the current debug string, otherwise it builds from page to page
+
+=cut
+
+sub DebugClear {
+ if($debugging) {
+ $debugtext = undef;
+ }
+}
+
+=head2 DurationAsString
+
+Returns a string representing the specified duration
+
+=cut
+
+sub DurationAsString {
+ my $Duration = shift;
+ my $MINUTE = 60;
+ my $HOUR = $MINUTE*60;
+ my $DAY = $HOUR * 24;
+ my $WEEK = $DAY * 7;
+ my $days = int($Duration / $DAY);
+ $Duration = $Duration % $DAY;
+ my $hours = int($Duration / $HOUR);
+ $hours = sprintf("%02d", $hours);
+ $Duration = $Duration % $HOUR;
+ my $minutes = int($Duration/$MINUTE);
+ $minutes = sprintf("%02d", $minutes);
+ $Duration = $Duration % $MINUTE;
+ my $secs = sprintf("%02d", $Duration);
+
+ if(!$days) {
+ $days = "00";
+ }
+ if(!$hours) {
+ $hours = "00";
+ }
+ if(!$minutes) {
+ $minutes = "00";
+ }
+ if(!$secs) {
+ $secs = "00";
+ }
+ return "$days days $hours:$minutes:$secs";
+}
+
+1;
+
+
diff --git a/rt/lib/RTx/WebCronTool.pm b/rt/lib/RTx/WebCronTool.pm
new file mode 100644
index 0000000..5f086a2
--- /dev/null
+++ b/rt/lib/RTx/WebCronTool.pm
@@ -0,0 +1,41 @@
+package RTx::WebCronTool;
+$RTx::WebCronTool::VERSION = "0.01";
+
+1;
+
+__END__
+
+=head1 NAME
+
+RTx::WebCronTool - Web interface to rt-crontool
+
+=head1 VERSION
+
+This document describes version 0.01 of RTx::WebCronTool, released
+July 11, 2004.
+
+=head1 DESCRIPTION
+
+This RT extension provides a web interface for the built-in F<rt-crontool>
+utility, allowing scheduled processes to be launched remotely.
+
+After installation, log in as superuser, and click on the "Web CronTool" menu
+on the bottom of the navigation pane.
+
+To use it, simply submit the modules and arguments. All progress, error messages
+and debug information will then be displayed online.
+
+=head1 AUTHORS
+
+Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2004 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/lib/t/00smoke.t b/rt/lib/t/00smoke.t
deleted file mode 100644
index 9e9bf4a..0000000
--- a/rt/lib/t/00smoke.t
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More qw(no_plan);
-
-use RT;
-ok(RT::LoadConfig);
-ok(RT::Init, "Basic initialization and DB connectivity");
-
-use File::Find;
-File::Find::find({wanted => \&wanted}, 'lib/');
-sub wanted { /^*\.pm\z/s && ok(require $_, "Requiring '$_'"); }
-
-
diff --git a/rt/lib/t/00smoke.t.in b/rt/lib/t/00smoke.t.in
deleted file mode 100644
index 288dd4a..0000000
--- a/rt/lib/t/00smoke.t.in
+++ /dev/null
@@ -1,14 +0,0 @@
-#!@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}, 'lib/');
-sub wanted { /^*\.pm\z/s && ok(require $_, "Requiring '$_'"); }
-
-
diff --git a/rt/lib/t/01harness.t.in b/rt/lib/t/01harness.t.in
deleted file mode 100644
index d132330..0000000
--- a/rt/lib/t/01harness.t.in
+++ /dev/null
@@ -1,12 +0,0 @@
-#!@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
index 4504cc7..4cc1318 100644
--- a/rt/lib/t/02regression.t
+++ b/rt/lib/t/02regression.t
@@ -34,11 +34,14 @@ is($q2->CommentAddress, 'comment@a');
use File::Find;
-File::Find::find({wanted => \&wanted_autogen}, 'lib/t/autogen');
+File::Find::find({wanted => \&wanted_autogen,
+ preprocess => sub {return sort @_}}, 'lib/t/autogen');
sub wanted_autogen { /^autogen.*\.t\z/s && require $_; }
-File::Find::find({wanted => \&wanted_regression}, 'lib/t/regression');
+File::Find::find({wanted => \&wanted_regression,
+ preprocess => sub {return sort @_}}, '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";
+require "/opt/rt3/lib/t/05cronsupport.pl";
diff --git a/rt/lib/t/02regression.t.in b/rt/lib/t/02regression.t.in
deleted file mode 100644
index c2e3277..0000000
--- a/rt/lib/t/02regression.t.in
+++ /dev/null
@@ -1,47 +0,0 @@
-#!@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,
- preprocess => sub {return sort @_}}, 'lib/t/autogen');
-sub wanted_autogen { /^autogen.*\.t\z/s && require $_; }
-
-File::Find::find({wanted => \&wanted_regression,
- preprocess => sub {return sort @_}}, '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";
-require "@RT_LIB_PATH@/t/05cronsupport.pl";
diff --git a/rt/lib/t/03web.pl b/rt/lib/t/03web.pl
index 94ad3e9..597ad10 100644
--- a/rt/lib/t/03web.pl
+++ b/rt/lib/t/03web.pl
@@ -67,7 +67,83 @@ ok( $agent->{'content'} =~ qr{$string} , "Found the content");
# }}}
+# {{{ Query Builder tests
+
+my $response = $agent->get($url."Search/Build.html");
+ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
+
+# Parsing TicketSQL
+#
+# Adding items
+
+# set the first value
+ok($agent->form_name('BuildQuery'));
+$agent->field("AttachmentField", "Subject");
+$agent->field("AttachmentOp", "LIKE");
+$agent->field("ValueOfAttachment", "aaa");
+$agent->submit();
+
+# set the next value
+ok($agent->form_name('BuildQuery'));
+$agent->field("AttachmentField", "Subject");
+$agent->field("AttachmentOp", "LIKE");
+$agent->field("ValueOfAttachment", "bbb");
+$agent->submit();
+
+ok($agent->form_name('BuildQuery'));
+
+# get the query
+my $query = $agent->current_form->find_input("Query")->value;
+# strip whitespace from ends
+$query =~ s/^\s*//g;
+$query =~ s/\s*$//g;
+
+# collapse other whitespace
+$query =~ s/\s+/ /g;
+
+is ($query, "Subject LIKE 'aaa' AND Subject LIKE 'bbb'");
+
+# - new items go one level down
+# - add items at currently selected level
+# - if nothing is selected, add at end, one level down
+#
+# move left
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move left if you're at the top level
+#
+# move right
+# - error if nothing selected
+# - same item should be selected after move
+# - can always move right (no max depth...should there be?)
+#
+# move up
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move up if you're first in the list
+#
+# move down
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move down if you're last in the list
+#
+# toggle
+# - error if nothing selected
+# - change all aggregators in the grouping
+# - don't change any others
+#
+# delete
+# - error if nothing selected
+# - delete currently selected item
+# - delete all children of a grouping
+# - if delete leaves a node with no children, delete that, too
+# - what should be selected?
+#
+# Clear
+# - clears entire query
+# - clears it from the session, too
+# }}}
use File::Find;
find ( \&wanted , 'html/');
@@ -83,7 +159,7 @@ sub test_get {
$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'} =~ /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");
diff --git a/rt/lib/t/03web.pl.in b/rt/lib/t/03web.pl.in
deleted file mode 100644
index 25c26e7..0000000
--- a/rt/lib/t/03web.pl.in
+++ /dev/null
@@ -1,170 +0,0 @@
-#!@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");
-
-
-
-# }}}
-
-# {{{ Query Builder tests
-
-my $response = $agent->get($url."Search/Build.html");
-ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
-
-# Parsing TicketSQL
-#
-# Adding items
-
-# set the first value
-ok($agent->form_name('BuildQuery'));
-$agent->field("AttachmentField", "Subject");
-$agent->field("AttachmentOp", "LIKE");
-$agent->field("ValueOfAttachment", "aaa");
-$agent->submit();
-
-# set the next value
-ok($agent->form_name('BuildQuery'));
-$agent->field("AttachmentField", "Subject");
-$agent->field("AttachmentOp", "LIKE");
-$agent->field("ValueOfAttachment", "bbb");
-$agent->submit();
-
-ok($agent->form_name('BuildQuery'));
-
-# get the query
-my $query = $agent->current_form->find_input("Query")->value;
-# strip whitespace from ends
-$query =~ s/^\s*//g;
-$query =~ s/\s*$//g;
-
-# collapse other whitespace
-$query =~ s/\s+/ /g;
-
-is ($query, "Subject LIKE 'aaa' AND Subject LIKE 'bbb'");
-
-# - new items go one level down
-# - add items at currently selected level
-# - if nothing is selected, add at end, one level down
-#
-# move left
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move left if you're at the top level
-#
-# move right
-# - error if nothing selected
-# - same item should be selected after move
-# - can always move right (no max depth...should there be?)
-#
-# move up
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move up if you're first in the list
-#
-# move down
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move down if you're last in the list
-#
-# toggle
-# - error if nothing selected
-# - change all aggregators in the grouping
-# - don't change any others
-#
-# delete
-# - error if nothing selected
-# - delete currently selected item
-# - delete all children of a grouping
-# - if delete leaves a node with no children, delete that, too
-# - what should be selected?
-#
-# Clear
-# - clears entire query
-# - clears it from the session, too
-
-# }}}
-
-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
index c384eed..973d9d2 100644
--- a/rt/lib/t/04_send_email.pl
+++ b/rt/lib/t/04_send_email.pl
@@ -476,6 +476,31 @@ sub crashes_redef_sendmessage {
# }}}
+# {{{ test a multi-line RT-Send-CC header
+
+my $content = `cat /opt/rt3/lib/t/data/rt-send-cc` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+
+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);
+
+my $cc = $tick->Transactions->First->Attachments->First->GetHeader('RT-Send-Cc');
+ok ($cc =~ /test1/, "Found test 1");
+ok ($cc =~ /test2/, "Found test 2");
+ok ($cc =~ /test3/, "Found test 3");
+ok ($cc =~ /test4/, "Found test 4");
+ok ($cc =~ /test5/, "Found test 5");
+
+# }}}
+
# 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
deleted file mode 100644
index 39ab0d2..0000000
--- a/rt/lib/t/04_send_email.pl.in
+++ /dev/null
@@ -1,506 +0,0 @@
-#!@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 { }';
-}
-
-
-
-# }}}
-
-# {{{ test a multi-line RT-Send-CC header
-
-my $content = `cat @RT_LIB_PATH@/t/data/rt-send-cc` || die "couldn't find new content";
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-
-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);
-
-my $cc = $tick->Transactions->First->Attachments->First->GetHeader('RT-Send-Cc');
-ok ($cc =~ /test1/, "Found test 1");
-ok ($cc =~ /test2/, "Found test 2");
-ok ($cc =~ /test3/, "Found test 3");
-ok ($cc =~ /test4/, "Found test 4");
-ok ($cc =~ /test5/, "Found test 5");
-
-# }}}
-
-# Don't taint the environment
-$everyone->PrincipalObj->RevokeRight(Right =>'SuperUser');
-1;
diff --git a/rt/lib/t/05cronsupport.pl.in b/rt/lib/t/05cronsupport.pl.in
deleted file mode 100644
index a6b3d74..0000000
--- a/rt/lib/t/05cronsupport.pl.in
+++ /dev/null
@@ -1,84 +0,0 @@
-#!@PERL@ -w
-
-use strict;
-
-### Set up some testing data. Test the testing data because why not?
-
-# Create a user with rights, a queue, and some tickets.
-my $user_obj = RT::User->new($RT::SystemUser);
-my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('tara@example.com');
-ok($ret, 'record test user creation');
-$user_obj->SetName('tara');
-$user_obj->PrincipalObj->GrantRight(Right => 'SuperUser');
-my $CurrentUser = RT::CurrentUser->new('tara');
-
-# Create our template, which will be used for tests of RT::Action::Record*.
-
-my $template_content = 'RT-Send-Cc: tla@example.com
-RT-Send-Bcc: jesse@example.com
-
-This is a content string with no content.';
-
-my $template_obj = RT::Template->new($CurrentUser);
-$template_obj->Create(Queue => '0',
- Name => 'recordtest',
- Description => 'testing Record actions',
- Content => $template_content,
- );
-
-# Create a queue and some tickets.
-
-my $queue_obj = RT::Queue->new($CurrentUser);
-($ret, $msg) = $queue_obj->Create(Name => 'recordtest', Description => 'queue for Action::Record testing');
-ok($ret, 'record test queue creation');
-
-my $ticket1 = RT::Ticket->new($CurrentUser);
-my ($id, $tobj, $msg2) = $ticket1->Create(Queue => $queue_obj,
- Requestor => ['tara@example.com'],
- Subject => 'bork bork bork',
- Priority => 22,
- );
-ok($id, 'record test ticket creation 1');
-my $ticket2 = RT::Ticket->new($CurrentUser);
-($id, $tobj, $msg2) = $ticket2->Create(Queue => $queue_obj,
- Requestor => ['root@localhost'],
- Subject => 'hurdy gurdy'
- );
-ok($id, 'record test ticket creation 2');
-
-
-### OK. Have data, will travel.
-
-# First test the search.
-
-ok(require RT::Search::FromSQL, "Search::FromSQL loaded");
-my $ticketsqlstr = "Requestor.EmailAddress = '" . $CurrentUser->EmailAddress .
- "' AND Priority > '20'";
-my $search = RT::Search::FromSQL->new(Argument => $ticketsqlstr, TicketsObj => RT::Tickets->new($CurrentUser),
- );
-is(ref($search), 'RT::Search::FromSQL', "search created");
-ok($search->Prepare(), "fromsql search run");
-my $counter = 0;
-while(my $t = $search->TicketsObj->Next() ) {
- is($t->Id(), $ticket1->Id(), "fromsql search results 1");
- $counter++;
-}
-is ($counter, 1, "fromsql search results 2");
-
-# Right. Now test the actions.
-
-ok(require RT::Action::RecordComment);
-ok(require RT::Action::RecordCorrespondence);
-
-my ($comment_act, $correspond_act);
-ok($comment_act = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordComment created");
-ok($correspond_act = RT::Action::RecordCorrespondence->new(TicketObj => $ticket2, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordCorrespondence created");
-ok($comment_act->Prepare(), "Comment prepared");
-ok($correspond_act->Prepare(), "Correspond prepared");
-ok($comment_act->Commit(), "Comment committed");
-ok($correspond_act->Commit(), "Correspondence committed");
-
-# Now test for loop suppression.
-my ($trans, $desc, $transaction) = $ticket2->Comment(MIMEObj => $template_obj->MIMEObj);
-my $bogus_action = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, TransactionObj => $transaction, CurrentUser => $CurrentUser);
-ok(!$bogus_action->Prepare(), "Comment aborted to prevent loop");
diff --git a/rt/lib/t/create_data.pl b/rt/lib/t/create_data.pl
deleted file mode 100644
index 35226ea..0000000
--- a/rt/lib/t/create_data.pl
+++ /dev/null
@@ -1,136 +0,0 @@
-#!@PERL@ -w
-use strict;
-
-use Test::More qw/no_plan/;
-use Text::Lorem;
-use RT;
-RT::LoadConfig;
-RT::Init;
-
-#### Generate some number of RT accounts. Come up with random
-#### usernames if requested, otherwise use provided ones. Take
-#### $subdomain argument so that we can generate customer accounts,
-#### etc. Group memberships should also be provided.
-
-=head2 create_users
-
-=over 4
-
-This subroutine creates a number of RT users, if they don't already
-exist, and places them in the specified group. It also creates the
-group if it needs to. Returns a ref to a list containing the user
-objects.
-
-If a list of names is specified, users with those names are created.
-Otherwise, it will make names up, checking to be sure that a user with
-the random name does not yet exist. Each user will have an email
-address in "example.com".
-
-Takes a hash of the following arguments:
-number => How many users to create. Default is 1.
-names => A ref to a list of usernames to use. Optional.
-subdomain => The subdomain of example.com which should be used for
- email addresses.
-group => The name of the group these users should belong to. Creates
- the group if it does not yet exist.
-privileged => Whether the users should be able to be granted rights.
- Default is 1.
-attributes => a ref to a list of hashrefs containing the arguments for
- any unsupported attribute we should add to the user (for example, a
- user saved search.)
-
-=back
-
-=cut
-
-sub create_users {
- my %ARGS = (number => 1,
- subdomain => undef,
- privileged => 1,
- @_);
- my $lorem = Text::Lorem->new();
- my @users_returned;
-
- my @usernames;
- my $anon;
- if ($ARGS{'users'}) {
- @usernames = @{$ARGS{'users'}};
- $anon = 0;
- } else {
- @usernames = split(/\s+/, $lorem->words($ARGS{'number'}));
- $anon = 1;
- }
-
- my $domain = 'example.com';
- $domain = $ARGS{'subdomain'} . ".$domain" if $ARGS{'subdomain'};
-
- foreach my $user (@usernames) {
- my $user_obj = RT::User->new($RT::SystemUser);
- $user_obj->Load($user);
- if ($user_obj->Id() && !$anon) {
- # Use this user; assume we know what we're doing. Don't
- # modify it, other than adding it to any group specified.
- push(@users_returned, $user_obj);
- } elsif ($user_obj->Id()) {
- # Oops. Get a different username and stick it on the back
- # of the list.
- append(@users, $lorem->words(1));
- } else {
- $user_obj->Create(Name => $user,
- Password => $user."pass",
- EmailAddress => $user.'@'.$domain,
- RealName => "$user ipsum",
- Privileged => $ARGS{'privileged'},
- );
- push(@users_returned, $user_obj);
- }
- }
-
- # Now we have our list of users. Did we have groups to add them
- # to?
-
- if ($ARGS{'groups'}) {
- my @groups = @{$ARGS{'groups'}};
- foreach my $group (@groups) {
- my $group_obj = RT::Group->new();
- $group_obj->LoadUserDefinedGroup($group);
- unless ($group_obj->Id()) {
- # Create it.
- $group_obj->CreateUserDefinedGroup(
- Name => $group,
- Description => "lorem defined group $group",
- );
- }
- foreach (@users_returned) {
- $group_obj->AddMember($_->Id);
- }
- }
- }
-
- # Do we have attributes to apply to the users?
- if ($ARGS{'attributes'}) {
- foreach my $attrib (@{$ARGS{'attributes'}}) {
- my %attr_args = %{$attrib};
- foreach (@users_returned) {
- $_->AddAttribute(%attr_args);
- }
- }
- }
-
- # Return our list of users.
- return \@users_returned;
-}
-
-#### Generate any RT groups. These ought to be named, by function.
-#### The group names should be given either as part of user creation,
-#### or as a name with a number of subgroups which should be members.
-
-
-#### Generate some queues. Users/groups who have permissions on
-#### queues need to be specified on this point. Permissions can be
-#### specified by role, e.g. "client" or "staffmember" or "admin" for
-#### each queue. If the queue should have anything special like a
-#### custom field, say so here.
-
-
-#### Generate some tickets and transactions.
diff --git a/rt/lib/t/data/8859-15-message-series/dir b/rt/lib/t/data/8859-15-message-series/dir
deleted file mode 100644
index b9f8ec3..0000000
--- a/rt/lib/t/data/8859-15-message-series/dir
+++ /dev/null
@@ -1,356 +0,0 @@
-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
deleted file mode 100644
index cc99c40..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg1
+++ /dev/null
@@ -1,36 +0,0 @@
-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
deleted file mode 100644
index dc442cf..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg2
+++ /dev/null
@@ -1,36 +0,0 @@
-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
deleted file mode 100644
index e23866d..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg3
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 831695c..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg4
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 272c93c..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg5
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 3ae9d3b..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg6
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 6149dd6..0000000
--- a/rt/lib/t/data/8859-15-message-series/msg7
+++ /dev/null
@@ -1,36 +0,0 @@
-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
deleted file mode 100644
index da1913e..0000000
--- a/rt/lib/t/data/crashes-file-based-parser
+++ /dev/null
@@ -1,193 +0,0 @@
-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/lorem-ipsum b/rt/lib/t/data/lorem-ipsum
deleted file mode 100644
index 1aceb14..0000000
--- a/rt/lib/t/data/lorem-ipsum
+++ /dev/null
@@ -1,5 +0,0 @@
-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
-labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
-nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit
-esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
-culpa qui officia deserunt mollit anim id est laborum.
diff --git a/rt/lib/t/data/multipart-alternative-with-umlaut b/rt/lib/t/data/multipart-alternative-with-umlaut
deleted file mode 100644
index 1ad4fe3..0000000
--- a/rt/lib/t/data/multipart-alternative-with-umlaut
+++ /dev/null
@@ -1,62 +0,0 @@
-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
deleted file mode 100644
index 538e0c8..0000000
--- a/rt/lib/t/data/multipart-report
+++ /dev/null
@@ -1,66 +0,0 @@
-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
deleted file mode 100644
index 8b85d94..0000000
--- a/rt/lib/t/data/nested-mime-sample
+++ /dev/null
@@ -1,396 +0,0 @@
-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
deleted file mode 100644
index d4f118d..0000000
--- a/rt/lib/t/data/nested-rfc-822
+++ /dev/null
@@ -1,253 +0,0 @@
-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
deleted file mode 100644
index 299392d..0000000
--- a/rt/lib/t/data/new-ticket-from-iso-8859-1
+++ /dev/null
@@ -1,31 +0,0 @@
-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
deleted file mode 100644
index 493ca15..0000000
--- a/rt/lib/t/data/new-ticket-from-iso-8859-1-full
+++ /dev/null
@@ -1,38 +0,0 @@
-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
deleted file mode 100644
index f27fdf8..0000000
--- a/rt/lib/t/data/notes-uuencoded
+++ /dev/null
@@ -1,2368 +0,0 @@
-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/rt-send-cc b/rt/lib/t/data/rt-send-cc
deleted file mode 100644
index da8c4da..0000000
--- a/rt/lib/t/data/rt-send-cc
+++ /dev/null
@@ -1,5 +0,0 @@
-From: rt@example.com
-subject: testing send-cc headers
-RT-Send-Cc: this-is-a-sample-test1e@example.com, second-this-is-a-sample-test2@example.com, test-sample-sample-sample-test3@example.com,
- afourthtest4@example.com,
- test5@example.com
diff --git a/rt/lib/t/data/russian-subject-no-content-type b/rt/lib/t/data/russian-subject-no-content-type
deleted file mode 100644
index 03d95b8..0000000
--- a/rt/lib/t/data/russian-subject-no-content-type
+++ /dev/null
@@ -1,42 +0,0 @@
-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/subject-with-folding-ws b/rt/lib/t/data/subject-with-folding-ws
deleted file mode 100644
index c082632..0000000
--- a/rt/lib/t/data/subject-with-folding-ws
+++ /dev/null
@@ -1,10 +0,0 @@
-Subject: =?ISO-8859-1?Q?te?=
- =?ISO-8859-1?Q?st?=
-Date: Mon, 02 Jun 2003 20:58:30 +0200
-To: rt@example.com
-From: foo@example.com
-Mime-Version: 1.0
-Content-Type: text/plain; charset="iso-8859-1"
-Content-Transfer-Encoding: 8bit
-
-test
diff --git a/rt/lib/t/data/text-html-in-russian b/rt/lib/t/data/text-html-in-russian
deleted file mode 100644
index b965b1b..0000000
--- a/rt/lib/t/data/text-html-in-russian
+++ /dev/null
@@ -1,87 +0,0 @@
-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
deleted file mode 100644
index 90e5d3f..0000000
--- a/rt/lib/t/data/text-html-with-umlaut
+++ /dev/null
@@ -1,35 +0,0 @@
-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/data/very-long-subject b/rt/lib/t/data/very-long-subject
deleted file mode 100644
index ad420d0..0000000
--- a/rt/lib/t/data/very-long-subject
+++ /dev/null
@@ -1,12 +0,0 @@
-Subject: 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
-Date: Mon, 02 Jun 2003 20:58:30 +0200
-To: rt@example.com
-From: foo@example.com
-Mime-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: 8bit
-
-This email has a very long subject. Our DB allows you to use subject
-no longer than 200 chars, but we creat ticket, don't generate an
-error and trancate long line.
-
diff --git a/rt/lib/t/regression/00-mason-syntax.t b/rt/lib/t/regression/00-mason-syntax.t
deleted file mode 100644
index ce692a3..0000000
--- a/rt/lib/t/regression/00-mason-syntax.t
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Test::More tests => 1;
-
-my $ok = 1;
-
-use File::Find;
-find( {
- no_chdir => 1,
- wanted => sub {
- return if /\.(?:jpe?g|png|gif|rej|\~)$/i;
- return unless -f $_;
- diag "testing $_" if $ENV{'TEST_VERBOSE'};
- eval { compile_file($_) } and return;
- $ok = 0;
- diag "error in ${File::Find::name}:\n$@";
- },
-}, 'html');
-ok($ok, "mason syntax is ok");
-
-use HTML::Mason;
-use HTML::Mason::Compiler;
-use HTML::Mason::Compiler::ToObject;
-
-sub compile_file {
- my $file = shift;
-
- open my $fh, '<:utf8', $file or die "couldn't open '$file': $!";
- my $text = do { local $/; <$fh> };
- close $fh or die "couldn't close '$file': $!";
-
- my $compiler = new HTML::Mason::Compiler::ToObject;
- $compiler->compile(
- comp_source => $text,
- name => 'my',
- $HTML::Mason::VERSION >= 1.36? (comp_path => 'my'): (),
- );
- return 1;
-}
-
diff --git a/rt/lib/t/regression/00placeholder b/rt/lib/t/regression/00placeholder
deleted file mode 100644
index 0afc604..0000000
--- a/rt/lib/t/regression/00placeholder
+++ /dev/null
@@ -1 +0,0 @@
-1;
diff --git a/rt/lib/t/regression/01ticket_link_searching.t b/rt/lib/t/regression/01ticket_link_searching.t
deleted file mode 100644
index a402c73..0000000
--- a/rt/lib/t/regression/01ticket_link_searching.t
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/perl -w
-
-use Test::More tests => 30;
-use strict;
-use RT;
-
-# Load the config file
-RT::LoadConfig();
-
-#Connect to the database and get RT::SystemUser and RT::Nobody loaded
-RT::Init();
-
-#Get the current user all loaded
-my $CurrentUser = $RT::SystemUser;
-
-my $queue = new RT::Queue($CurrentUser);
-$queue->Load('General') || Abort(loc("Queue could not be loaded."));
-
-my $child_ticket = new RT::Ticket( $CurrentUser );
-my ($childid) = $child_ticket->Create(
- Subject => 'test child',
- Queue => $queue->Id,
-);
-ok($childid, "We created a child ticket");
-
-my $parent_ticket = new RT::Ticket( $CurrentUser );
-my ($parentid) = $parent_ticket->Create(
- Subject => 'test parent',
- Children => [ $childid ],
- Queue => $queue->Id,
-);
-ok($parentid, "We created a parent ticket");
-
-
-my $Collection = RT::Tickets->new($CurrentUser);
-$Collection->LimitMemberOf( $parentid );
-is($Collection->Count,1, "We found only one result");
-ok($Collection->First);
-is($Collection->First->id, $childid, "We found the collection of all children of $parentid with Limit");
-
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("MemberOf = $parentid");
-is($Collection->Count, 1, "We found only one result");
-ok($Collection->First);
-is($Collection->First->id, $childid, "We found the collection of all children of $parentid with TicketSQL");
-
-
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->LimitHasMember ($childid);
-is($Collection->Count,1, "We found only one result");
-ok($Collection->First);
-is($Collection->First->id, $parentid, "We found the collection of all parents of $childid with Limit");
-
-
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("HasMember = $childid");
-is($Collection->Count,1, "We found only one result");
-ok($Collection->First);
-is($Collection->First->id, $parentid, "We found the collection of all parents of $childid with TicketSQL");
-
-
-# Now we find a collection of all the tickets which have no members. they should have no children.
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->LimitHasMember('');
-# must contain child; must not contain parent
-my %has;
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( $has{$childid}, "The collection has our child - $childid");
-ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid");
-
-
-# Now we find a collection of all the tickets which are not members of anything. they should have no parents.
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->LimitMemberOf('');
-# must contain parent; must not contain child
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok ($has{$parentid} , "The collection has our parent - $parentid");
-ok( !$has{$childid}, "The collection doesn't have our child - $childid");
-
-
-# Do it all over with TicketSQL
-#
-
-
-
-# Now we find a collection of all the tickets which have no members. they should have no children.
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL ("HasMember IS NULL");
-# must contain parent; must not contain child
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid");
-ok( $has{$childid}, "The collection has our child - $childid");
-
-
-# Now we find a collection of all the tickets which have no members. they should have no children.
-# Alternate syntax
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("HasMember = ''");
-# must contain parent; must not contain child
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( !$has{$parentid}, "The collection doesn't have our parent - $parentid");
-ok( $has{$childid}, "The collection has our child - $childid");
-
-
-# Now we find a collection of all the tickets which are not members of anything. they should have no parents.
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("MemberOf IS NULL");
-# must not contain parent; must contain parent
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( $has{$parentid}, "The collection has our parent - $parentid");
-ok( !$has{$childid}, "The collection doesn't have our child - $childid");
-
-
-# Now we find a collection of all the tickets which are not members of anything. they should have no parents.
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("MemberOf = ''");
-# must not contain parent; must contain parent
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( $has{$parentid}, "The collection has our parent - $parentid");
-ok( !$has{$childid}, "The collection doesn't have our child - $childid");
-
-
-# Now we find a collection of all the tickets which are not members of the parent ticket
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->FromSQL("MemberOf != $parentid");
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( $has{$parentid}, "The collection has our parent - $parentid");
-ok( !$has{$childid}, "The collection doesn't have our child - $childid");
-
-$Collection = RT::Tickets->new($CurrentUser);
-$Collection->LimitMemberOf($parentid, OPERATOR => '!=');
-%has = ();
-while (my $t = $Collection->Next) {
- ++$has{$t->id};
-}
-ok( $has{$parentid}, "The collection has our parent - $parentid");
-ok( !$has{$childid}, "The collection doesn't have our child - $childid");
-
-1;
diff --git a/rt/lib/t/regression/02basic_web.t b/rt/lib/t/regression/02basic_web.t
deleted file mode 100644
index 3b8619b..0000000
--- a/rt/lib/t/regression/02basic_web.t
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use Test::More tests => 19;
-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);
-
-use RT;
-RT::LoadConfig();
-# get the top page
-my $url = $RT::WebURL;
-diag $url;
-$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_number(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' => "Ticket with utf8 body");
-$agent->field('Content' => $string);
-ok($agent->submit(), "Created new ticket with $string as Content");
-like( $agent->{'content'}, qr{$string} , "Found the content");
-ok($agent->{redirected_uri}, "Did redirection");
-
-
-$agent->get($url."Ticket/Create.html?Queue=1");
-is ($agent->{'status'}, 200, "Loaded Create.html");
-$agent->form_number(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' => "Ticket with utf8 subject");
-ok($agent->submit(), "Created new ticket with $string as Subject");
-
-like( $agent->{'content'}, qr{$string} , "Found the content");
-
-# Update time worked in hours
-$agent->follow_link( text_regex => qr/Basics/ );
-$agent->submit_form( form_number => 3,
- fields => { TimeWorked => 5, 'TimeWorked-TimeUnits' => "hours" }
-);
-
-like ($agent->{'content'}, qr/to &#39;300&#39;/, "5 hours is 300 minutes");
-
-# }}}
-
-# {{{ Query Builder tests
-
-my $response = $agent->get($url."Search/Build.html");
-ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
-
-# Parsing TicketSQL
-#
-# Adding items
-
-# set the first value
-ok($agent->form_name('BuildQuery'));
-$agent->field("AttachmentField", "Subject");
-$agent->field("AttachmentOp", "LIKE");
-$agent->field("ValueOfAttachment", "aaa");
-$agent->submit("AddClause");
-
-# set the next value
-ok($agent->form_name('BuildQuery'));
-$agent->field("AttachmentField", "Subject");
-$agent->field("AttachmentOp", "LIKE");
-$agent->field("ValueOfAttachment", "bbb");
-$agent->submit("AddClause");
-
-ok($agent->form_name('BuildQuery'));
-
-# get the query
-my $query = $agent->current_form->find_input("Query")->value;
-# strip whitespace from ends
-$query =~ s/^\s*//g;
-$query =~ s/\s*$//g;
-
-# collapse other whitespace
-$query =~ s/\s+/ /g;
-
-is ($query, "Subject LIKE 'aaa' AND Subject LIKE 'bbb'");
-
-# - new items go one level down
-# - add items at currently selected level
-# - if nothing is selected, add at end, one level down
-#
-# move left
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move left if you're at the top level
-#
-# move right
-# - error if nothing selected
-# - same item should be selected after move
-# - can always move right (no max depth...should there be?)
-#
-# move up
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move up if you're first in the list
-#
-# move down
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move down if you're last in the list
-#
-# toggle
-# - error if nothing selected
-# - change all aggregators in the grouping
-# - don't change any others
-#
-# delete
-# - error if nothing selected
-# - delete currently selected item
-# - delete all children of a grouping
-# - if delete leaves a node with no children, delete that, too
-# - what should be selected?
-#
-# Clear
-# - clears entire query
-# - clears it from the session, too
-
-# }}}
-
-
-1;
diff --git a/rt/lib/t/regression/03web_compiliation_errors.t b/rt/lib/t/regression/03web_compiliation_errors.t
deleted file mode 100644
index 29e56d6..0000000
--- a/rt/lib/t/regression/03web_compiliation_errors.t
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use Test::More qw/no_plan/;
-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);
-
-use RT;
-RT::LoadConfig();
-
-# get the top page
-my $url = $RT::WebURL;
-diag "Base URL is '$url'" if $ENV{TEST_VERBOSE};
-$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");
-
-
-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/##;
- diag( "testing $url/$file" ) if $ENV{TEST_VERBOSE};
- 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'} !~ /raw error/i, "Didn't get a Mason compilation error on $file");
-}
-
-# }}}
-
-1;
diff --git a/rt/lib/t/regression/04send_email.t b/rt/lib/t/regression/04send_email.t
deleted file mode 100644
index a175ffa..0000000
--- a/rt/lib/t/regression/04send_email.t
+++ /dev/null
@@ -1,549 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Test::More tests => 142;
-
-use RT;
-RT::LoadConfig();
-RT::Init;
-
-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");
- }
-}
-
-# some utils
-sub first_txn { return $_[0]->Transactions->First }
-sub first_attach { return first_txn($_[0])->Attachments->First }
-
-sub count_txns { return $_[0]->Transactions->Count }
-sub count_attachs { return first_txn($_[0])->Attachments->Count }
-
-sub file_content
-{
- open my $fh, "<:raw", $_[0] or die "couldn't open file '$_[0]': $!";
- local $/;
- return scalar <$fh>;
-}
-
-# 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 = file_content("$RT::BasePath/lib/t/data/multipart-report");
-# 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();
-isa_ok($tick, "RT::Ticket", "got a ticket object");
-ok ($tick->Id, "found ticket ".$tick->Id);
-
-ok (first_txn($tick)->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, undef, $msg ) = $ticket->Create(Requestor => ['root@localhost'], Queue => 'general', Subject => 'I18NTest', MIMEObj => $parser->Entity);
-ok ($id,$msg);
-$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 '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 = ();
-
-$content = file_content("$RT::BasePath/lib/t/data/new-ticket-from-iso-8859-1");
-
-
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-use RT::Interface::Email;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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 (first_txn($tick)->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);
- ($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 = ();
-
- $content = file_content("$RT::BasePath/lib/t/data/new-ticket-from-iso-8859-1");
-# be as much like the mail gateway as possible.
-use RT::Interface::Email;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
-$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 (first_txn($tick)->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);
- ($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
-
- $content = file_content("$RT::BasePath/lib/t/data/multipart-alternative-with-umlaut");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&umlauts_redef_sendmessage;
-
-%args = (message => $content, queue => 1, action => 'correspond');
-RT::Interface::Email::Gateway(\%args);
-$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 (first_txn($tick)->Content =~ /causes Error/, "We recorded the content right as text-plain");
-is (count_attachs($tick) , 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
-
- $content = file_content("$RT::BasePath/lib/t/data/text-html-with-umlaut");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&text_html_umlauts_redef_sendmessage;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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 (first_attach($tick)->Content =~ /causes Error/, "We recorded the content as containing 'causes error'") or diag( first_attach($tick)->Content );
-ok (first_attach($tick)->ContentType =~ /text\/html/, "We recorded the content as text/html");
-is (count_attachs($tick), 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;
- return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
- 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
-
- $content = file_content("$RT::BasePath/lib/t/data/text-html-in-russian");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&text_html_russian_redef_sendmessage;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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 (first_attach($tick)->ContentType =~ /text\/html/, "We recorded the content right as text-html");
-ok (count_attachs($tick) ==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';
-$content = file_content("$RT::BasePath/lib/t/data/russian-subject-no-content-type");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&text_plain_russian_redef_sendmessage;
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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 (first_attach($tick)->ContentType =~ /text\/plain/, "We recorded the content type right");
-ok (count_attachs($tick) ==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
-
- $content = file_content("$RT::BasePath/lib/t/data/nested-rfc-822");
-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;
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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);
-is ($tick->Subject, "[Jonas Liljegren] Re: [Para] Niv\x{e5}er?");
-ok (first_attach($tick)->ContentType =~ /multipart\/mixed/, "We recorded the content type right");
-is (count_attachs($tick) , 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
-
- $content = file_content("$RT::BasePath/lib/t/data/notes-uuencoded");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&notes_redef_sendmessage;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
-$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 (first_txn($tick)->Content =~ /from Lotus Notes/, "We recorded the content right");
-is (count_attachs($tick) , 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
-
- $content = file_content("$RT::BasePath/lib/t/data/crashes-file-based-parser");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-# be as much like the mail gateway as possible.
-&crashes_redef_sendmessage;
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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 (first_txn($tick)->Content =~ /FYI/, "We recorded the content right");
-is (count_attachs($tick) , 5 , "Has three attachments");
-
-sub crashes_redef_sendmessage {
- no warnings qw/redefine/;
- eval 'sub RT::Action::SendEmail::SendMessage { }';
-}
-
-
-
-# }}}
-
-# {{{ test a multi-line RT-Send-CC header
-
- $content = file_content("$RT::BasePath/lib/t/data/rt-send-cc");
-
-$parser->ParseMIMEEntityFromScalar($content);
-
-
-
- %args = (message => $content, queue => 1, action => 'correspond');
- RT::Interface::Email::Gateway(\%args);
- $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);
-
-my $cc = first_attach($tick)->GetHeader('RT-Send-Cc');
-ok ($cc =~ /test1/, "Found test 1");
-ok ($cc =~ /test2/, "Found test 2");
-ok ($cc =~ /test3/, "Found test 3");
-ok ($cc =~ /test4/, "Found test 4");
-ok ($cc =~ /test5/, "Found test 5");
-
-# }}}
-
-diag q{regression test for #5248 from rt3.fsck.com} if $ENV{TEST_VERBOSE};
-{
- my $content = file_content("$RT::BasePath/lib/t/data/subject-with-folding-ws");
- my ($status, $msg, $ticket) = RT::Interface::Email::Gateway(
- { message => $content, queue => 1, action => 'correspond' }
- );
- ok ($status, 'created ticket') or diag "error: $msg";
- ok ($ticket->id, "found ticket ". $ticket->id);
- is ($ticket->Subject, 'test', 'correct subject');
-}
-
-diag q{regression test for #5248 from rt3.fsck.com} if $ENV{TEST_VERBOSE};
-{
- my $content = file_content("$RT::BasePath/lib/t/data/very-long-subject");
- my ($status, $msg, $ticket) = RT::Interface::Email::Gateway(
- { message => $content, queue => 1, action => 'correspond' }
- );
- ok ($status, 'created ticket') or diag "error: $msg";
- ok ($ticket->id, "found ticket ". $ticket->id);
- is ($ticket->Subject, '0123456789'x20, 'correct subject');
-}
-
-
-
-# Don't taint the environment
-$everyone->PrincipalObj->RevokeRight(Right =>'SuperUser');
-1;
diff --git a/rt/lib/t/regression/05cronsupport.t b/rt/lib/t/regression/05cronsupport.t
deleted file mode 100644
index 8e5bd75..0000000
--- a/rt/lib/t/regression/05cronsupport.t
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Test::More qw/no_plan/;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-### Set up some testing data. Test the testing data because why not?
-
-# Create a user with rights, a queue, and some tickets.
-my $user_obj = RT::User->new($RT::SystemUser);
-my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('tara@example.com');
-ok($ret, 'record test user creation');
-$user_obj->SetName('tara');
-$user_obj->PrincipalObj->GrantRight(Right => 'SuperUser');
-my $CurrentUser = RT::CurrentUser->new('tara');
-
-# Create our template, which will be used for tests of RT::Action::Record*.
-
-my $template_content = 'RT-Send-Cc: tla@example.com
-RT-Send-Bcc: jesse@example.com
-
-This is a content string with no content.';
-
-my $template_obj = RT::Template->new($CurrentUser);
-$template_obj->Create(Queue => '0',
- Name => 'recordtest',
- Description => 'testing Record actions',
- Content => $template_content,
- );
-
-# Create a queue and some tickets.
-
-my $queue_obj = RT::Queue->new($CurrentUser);
-($ret, $msg) = $queue_obj->Create(Name => 'recordtest', Description => 'queue for Action::Record testing');
-ok($ret, 'record test queue creation');
-
-my $ticket1 = RT::Ticket->new($CurrentUser);
-my ($id, $tobj, $msg2) = $ticket1->Create(Queue => $queue_obj,
- Requestor => ['tara@example.com'],
- Subject => 'bork bork bork',
- Priority => 22,
- );
-ok($id, 'record test ticket creation 1');
-my $ticket2 = RT::Ticket->new($CurrentUser);
-($id, $tobj, $msg2) = $ticket2->Create(Queue => $queue_obj,
- Requestor => ['root@localhost'],
- Subject => 'hurdy gurdy'
- );
-ok($id, 'record test ticket creation 2');
-
-
-### OK. Have data, will travel.
-
-# First test the search.
-
-ok(require RT::Search::FromSQL, "Search::FromSQL loaded");
-my $ticketsqlstr = "Requestor.EmailAddress = '" . $CurrentUser->EmailAddress .
- "' AND Priority > '20'";
-my $search = RT::Search::FromSQL->new(Argument => $ticketsqlstr, TicketsObj => RT::Tickets->new($CurrentUser),
- );
-is(ref($search), 'RT::Search::FromSQL', "search created");
-ok($search->Prepare(), "fromsql search run");
-my $counter = 0;
-while(my $t = $search->TicketsObj->Next() ) {
- is($t->Id(), $ticket1->Id(), "fromsql search results 1");
- $counter++;
-}
-is ($counter, 1, "fromsql search results 2");
-
-# Right. Now test the actions.
-
-ok(require RT::Action::RecordComment);
-ok(require RT::Action::RecordCorrespondence);
-
-my ($comment_act, $correspond_act);
-ok($comment_act = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordComment created");
-ok($correspond_act = RT::Action::RecordCorrespondence->new(TicketObj => $ticket2, TemplateObj => $template_obj, CurrentUser => $CurrentUser), "RecordCorrespondence created");
-ok($comment_act->Prepare(), "Comment prepared");
-ok($correspond_act->Prepare(), "Correspond prepared");
-ok($comment_act->Commit(), "Comment committed");
-ok($correspond_act->Commit(), "Correspondence committed");
-
-# Now test for loop suppression.
-my ($trans, $desc, $transaction) = $ticket2->Comment(MIMEObj => $template_obj->MIMEObj);
-my $bogus_action = RT::Action::RecordComment->new(TicketObj => $ticket1, TemplateObj => $template_obj, TransactionObj => $transaction, CurrentUser => $CurrentUser);
-ok(!$bogus_action->Prepare(), "Comment aborted to prevent loop");
-
-1;
diff --git a/rt/lib/t/regression/06-mime_decoding.t b/rt/lib/t/regression/06-mime_decoding.t
deleted file mode 100644
index 2dca4f1..0000000
--- a/rt/lib/t/regression/06-mime_decoding.t
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Test::More tests => 7;
-
-use_ok("RT");
-
-RT::LoadConfig();
-RT::Init();
-
-use_ok('RT::I18N');
-
-diag q{'=' char in a leading part before an encoded part} if $ENV{TEST_VERBOSE};
-{
- my $str = 'key="plain"; key="=?UTF-8?B?0LzQvtC5X9GE0LDQudC7LmJpbg==?="';
- is(
- RT::I18N::DecodeMIMEWordsToUTF8($str),
- 'key="plain"; key="мой_файл.bin"',
- "right decoding"
- );
-}
-
-diag q{not compliant with standards, but MUAs send such field when attachment has non-ascii in name}
- if $ENV{TEST_VERBOSE};
-{
- my $str = 'attachment; filename="=?UTF-8?B?0LzQvtC5X9GE0LDQudC7LmJpbg==?="';
- is(
- RT::I18N::DecodeMIMEWordsToUTF8($str),
- 'attachment; filename="мой_файл.bin"',
- "right decoding"
- );
-}
-
-diag q{'=' char in a trailing part after an encoded part} if $ENV{TEST_VERBOSE};
-{
- my $str = 'attachment; filename="=?UTF-8?B?0LzQvtC5X9GE0LDQudC7LmJpbg==?="; some_prop="value"';
- is(
- RT::I18N::DecodeMIMEWordsToUTF8($str),
- 'attachment; filename="мой_файл.bin"; some_prop="value"',
- "right decoding"
- );
-}
-
-diag q{regression test for #5248 from rt3.fsck.com} if $ENV{TEST_VERBOSE};
-{
- my $str = qq{Subject: =?ISO-8859-1?Q?Re=3A_=5BXXXXXX=23269=5D_=5BComment=5D_Frag?=}
- . qq{\n =?ISO-8859-1?Q?e_zu_XXXXXX--xxxxxx_/_Xxxxx=FCxxxxxxxxxx?=};
- is(
- RT::I18N::DecodeMIMEWordsToUTF8($str),
- qq{Subject: Re: [XXXXXX#269] [Comment] Frage zu XXXXXX--xxxxxx / Xxxxxüxxxxxxxxxx},
- "right decoding"
- );
-}
-
-diag q{newline and encoded file name} if $ENV{TEST_VERBOSE};
-{
- my $str = qq{application/vnd.ms-powerpoint;\n\tname="=?ISO-8859-1?Q?Main_presentation.ppt?="};
- is(
- RT::I18N::DecodeMIMEWordsToUTF8($str),
- qq{application/vnd.ms-powerpoint;\tname="Main presentation.ppt"},
- "right decoding"
- );
-}
-
diff --git a/rt/lib/t/regression/06mailgateway.t b/rt/lib/t/regression/06mailgateway.t
deleted file mode 100644
index 5fc5029..0000000
--- a/rt/lib/t/regression/06mailgateway.t
+++ /dev/null
@@ -1,663 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
-# <jesse.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-
-=head1 NAME
-
-rt-mailgate - Mail interface to RT3.
-
-=cut
-
-use strict;
-use Test::More tests => 109;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-use RT::I18N;
-use Digest::MD5 qw(md5_base64);
-
-no warnings 'once';
-my $url = join( ':', grep $_, "http://localhost", $RT::WebPort ) . $RT::WebPath ."/";
-
-# Make sure that when we call the mailgate wrong, it tempfails
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url http://this.test.for.non-connection.is.expected.to.generate.an.error"), "Opened the mailgate - The error below is expected - $@");
-print MAIL <<EOF;
-From: root\@localhost
-To: rt\@$RT::rtname
-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
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --debug --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: root\@localhost
-To: rt\@$RT::rtname
-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");
-
-# }}}
-
-# {{{ Test new ticket creation without --action argument
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --debug --url $url --queue general"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: root\@localhost
-To: rt\@$RT::rtname
-Subject: using mailgate without --action arg
-
-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;
-isa_ok ($tick,'RT::Ticket');
-ok ($tick->Id, "found ticket ".$tick->Id);
-is ($tick->Subject, 'using mailgate without --action arg', "using mailgate without --action arg");
-
-# }}}
-
-# {{{This is a test of new ticket creation as an unknown user
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist\@$RT::rtname
-To: rt\@$RT::rtname
-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\@$RT::rtname");
-ok( !$u->Id, " 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");
-
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist\@$RT::rtname
-To: rt\@$RT::rtname
-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");
- $u = RT::User->new($RT::SystemUser);
-$u->Load("doesnotexist\@$RT::rtname");
-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");
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist-2\@$RT::rtname
-To: rt\@$RT::rtname
-Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a reply as an unknown user
-
-Blah! (Should not work.)
-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@$RT::rtname');
-ok( !$u->Id, " 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");
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist-2\@$RT::rtname
-To: rt\@$RT::rtname
-Subject: [$RT::rtname #@{[$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\@$RT::rtname");
-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");
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action comment"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist-3\@$RT::rtname
-To: rt\@$RT::rtname
-Subject: [$RT::rtname #@{[$tick->Id]}] This is a test of a comment as an unknown user
-
-Blah! (Should not work.)
-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\@$RT::rtname");
-ok( !$u->Id, " 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");
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action comment"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: doesnotexist-3\@$RT::rtname
-To: rt\@$RT::rtname
-Subject: [$RT::rtname #@{[$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\@$RT::rtname");
-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
-
-my $LOGO_FILE = $RT::MasonComponentRoot.'/NoAuth/images/bplogo.gif';
-
-$entity->attach(Path => $LOGO_FILE,
- Type => 'image/gif',
- Encoding => 'base64');
-
-# Create a ticket with a binary attachment
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --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");
-
-$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 $LOGO_FILE`;
-ok ($file, "Read in the logo image");
-
-
-diag( "for the raw file the content is ". 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;
-ok($attachment->Id);
-my $acontent = $attachment->Content;
-
-diag( "coming from the database, the content is ". 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 = "$url/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/bplogo.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
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-
-print MAIL <<EOF;
-From: root\@localhost
-To: rtemail\@$RT::rtname
-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.
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue general --action correspond"), "Opened the mailgate - $!");
-
-print MAIL <<EOF;
-From: root\@localhost
-To: rtemail\@$RT::rtname
-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");
-
-
-
-$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);
-
-##=for later
-
-SKIP: {
-skip "Advanced mailgate actions require an unsafe configuration", 47 unless $RT::UnsafeEmailCommands;
-
-#create new queue to be shure we don't mess with rights
-use RT::Queue;
-my $queue = RT::Queue->new($RT::SystemUser);
-my ($qid) = $queue->Create( Name => 'ext-mailgate');
-ok( $qid, 'queue created for ext-mailgate tests' );
-
-# {{{ Check take and resolve actions
-
-# create ticket that is owned by nobody
-use RT::Ticket;
-$tick = RT::Ticket->new($RT::SystemUser);
-my ($id) = $tick->Create( Queue => 'ext-mailgate', Subject => 'test');
-ok( $id, 'new ticket created' );
-is( $tick->Owner, $RT::Nobody->Id, 'owner of the new ticket is nobody' );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action take"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: root\@localhost
-Subject: [$RT::rtname \#$id] test
-
-EOF
-close (MAIL);
-is ($? >> 8, 0, "The mail gateway exited normally");
-
-$tick = RT::Ticket->new($RT::SystemUser);
-$tick->Load( $id );
-is( $tick->Id, $id, 'load correct ticket');
-is( $tick->OwnerObj->EmailAddress, 'root@localhost', 'successfuly take ticket via email');
-
-# check that there is no text transactions writen
-is( $tick->Transactions->Count, 2, 'no superfluous transactions');
-
-my $status;
-($status, $msg) = $tick->SetOwner( $RT::Nobody->Id, 'Force' );
-ok( $status, 'successfuly changed owner: '. ($msg||'') );
-is( $tick->Owner, $RT::Nobody->Id, 'set owner back to nobody');
-
-
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $RT::WebURL --queue ext-mailgate --action take-correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: root\@localhost
-Subject: [$RT::rtname \#$id] correspondence
-
-test
-EOF
-close (MAIL);
-is ($? >> 8, 0, "The mail gateway exited normally");
-
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-$tick = RT::Ticket->new($RT::SystemUser);
-$tick->Load( $id );
-is( $tick->Id, $id, "load correct ticket #$id");
-is( $tick->OwnerObj->EmailAddress, 'root@localhost', 'successfuly take ticket via email');
-my $txns = $tick->Transactions;
-$txns->Limit( FIELD => 'Type', VALUE => 'Correspond');
-$txns->OrderBy( FIELD => 'id', ORDER => 'DESC' );
-# +1 because of auto open
-is( $tick->Transactions->Count, 6, 'no superfluous transactions');
-is( $txns->First->Subject, "[$RT::rtname \#$id] correspondence", 'successfuly add correspond within take via email' );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action resolve --debug"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: root\@localhost
-Subject: [$RT::rtname \#$id] test
-
-EOF
-close (MAIL);
-is ($? >> 8, 0, "The mail gateway exited normally");
-
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-$tick = RT::Ticket->new($RT::SystemUser);
-$tick->Load( $id );
-is( $tick->Id, $id, 'load correct ticket');
-is( $tick->Status, 'resolved', 'successfuly resolved ticket via email');
-is( $tick->Transactions->Count, 7, 'no superfluous transactions');
-
-use RT::User;
-my $user = RT::User->new( $RT::SystemUser );
-my ($uid) = $user->Create( Name => 'ext-mailgate',
- EmailAddress => 'ext-mailgate@localhost',
- Privileged => 1,
- Password => 'qwe123',
- );
-ok( $uid, 'user created for ext-mailgate tests' );
-ok( !$user->HasRight( Right => 'OwnTicket', Object => $queue ), "User can't own ticket" );
-
-$tick = RT::Ticket->new($RT::SystemUser);
-($id) = $tick->Create( Queue => $qid, Subject => 'test' );
-ok( $id, 'create new ticket' );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action take"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: ext-mailgate\@localhost
-Subject: [example.com \#$id] test
-
-EOF
-close (MAIL);
-is ( $? >> 8, 0, "mailgate exited normally" );
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
-
-($status, $msg) = $user->PrincipalObj->GrantRight( Object => $queue, Right => 'ReplyToTicket' );
-ok( $status, "successfuly granted right: $msg" );
-my $ace_id = $status;
-ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can reply to ticket" );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action correspond-take"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: ext-mailgate\@localhost
-Subject: [example.com \#$id] test
-
-correspond-take
-EOF
-close (MAIL);
-is ( $? >> 8, 0, "mailgate exited normally" );
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
-is( $tick->Transactions->Count, 3, "one transactions added" );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action take-correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: ext-mailgate\@localhost
-Subject: [example.com \#$id] test
-
-correspond-take
-EOF
-close (MAIL);
-is ( $? >> 8, 0, "mailgate exited normally" );
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-cmp_ok( $tick->Owner, '!=', $user->id, "we didn't change owner" );
-is( $tick->Transactions->Count, 3, "no transactions added, user can't take ticket first" );
-
-# revoke ReplyToTicket right
-use RT::ACE;
-my $ace = RT::ACE->new($RT::SystemUser);
-$ace->Load( $ace_id );
-$ace->Delete;
-my $acl = RT::ACL->new($RT::SystemUser);
-$acl->Limit( FIELD => 'RightName', VALUE => 'ReplyToTicket' );
-$acl->LimitToObject( $RT::System );
-while( my $ace = $acl->Next ) {
- $ace->Delete;
-}
-
-ok( !$user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "User can't reply to ticket any more" );
-
-
-my $group = RT::Group->new( $RT::SystemUser );
-ok( $group->LoadQueueRoleGroup( Queue => $qid, Type=> 'Owner' ), "load queue owners role group" );
-$ace = RT::ACE->new( $RT::SystemUser );
-($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ReplyToTicket', Object => $queue );
-ok( $ace_id, "Granted queue owners role group with ReplyToTicket right" );
-
-($status, $msg) = $user->PrincipalObj->GrantRight( Object => $queue, Right => 'OwnTicket' );
-ok( $status, "successfuly granted right: $msg" );
-($status, $msg) = $user->PrincipalObj->GrantRight( Object => $queue, Right => 'TakeTicket' );
-ok( $status, "successfuly granted right: $msg" );
-
-$! = 0;
-ok(open(MAIL, "|$RT::BinPath/rt-mailgate --url $url --queue ext-mailgate --action take-correspond"), "Opened the mailgate - $!");
-print MAIL <<EOF;
-From: ext-mailgate\@localhost
-Subject: [example.com \#$id] test
-
-take-correspond with reply right granted to owner role
-EOF
-close (MAIL);
-is ( $? >> 8, 0, "mailgate exited normally" );
-DBIx::SearchBuilder::Record::Cachable->FlushCache;
-
-$tick->Load( $id );
-is( $tick->Owner, $user->id, "we changed owner" );
-ok( $user->HasRight( Right => 'ReplyToTicket', Object => $tick ), "owner can reply to ticket" );
-is( $tick->Transactions->Count, 5, "transactions added" );
-
-
-# }}}
-};
-
-
-1;
-
diff --git a/rt/lib/t/regression/07acl.t b/rt/lib/t/regression/07acl.t
deleted file mode 100644
index efd8701..0000000
--- a/rt/lib/t/regression/07acl.t
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-use WWW::Mechanize;
-use HTTP::Cookies;
-
-use Test::More tests => 34;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-# Create a user with basically no rights, to start.
-my $user_obj = RT::User->new($RT::SystemUser);
-my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer-'.$$.'@example.com');
-ok($ret, 'ACL test user creation');
-$user_obj->SetName('customer-'.$$);
-$user_obj->SetPrivileged(1);
-($ret, $msg) = $user_obj->SetPassword('customer');
-ok($ret, "ACL test password set. $msg");
-
-# Now test the web interface, making sure objects come and go as
-# required.
-
-
-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);
-
-no warnings 'once';
-# get the top page
-login($agent, $user_obj);
-
-# Test for absence of Configure and Preferences tabs.
-ok(!$agent->find_link( url => $RT::WebPath . "/Admin/",
- text => 'Configuration'), "No config tab" );
-ok(!$agent->find_link( url => $RT::WebPath . "/User/Prefs.html",
- text => 'Preferences'), "No prefs pane" );
-
-# Now test for their presence, one at a time. Sleep for a bit after
-# ACL changes, thanks to the 10s ACL cache.
-my ($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ShowConfigTab', Object => $RT::System);
-
-ok($grantid,$grantmsg);
-
-$agent->reload;
-
-ok($agent->{'content'} =~ /Logout/i, "Reloaded page successfully");
-ok($agent->find_link( url => $RT::WebPath . "/Admin/",
- text => 'Configuration'), "Found config tab" );
-my ($revokeid,$revokemsg) =$user_obj->PrincipalObj->RevokeRight(Right => 'ShowConfigTab');
-ok ($revokeid,$revokemsg);
-($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
-ok ($grantid,$grantmsg);
-$agent->reload();
-ok($agent->{'content'} =~ /Logout/i, "Reloaded page successfully");
-ok($agent->find_link( url => $RT::WebPath . "/User/Prefs.html",
- text => 'Preferences'), "Found prefs pane" );
-($revokeid,$revokemsg) = $user_obj->PrincipalObj->RevokeRight(Right => 'ModifySelf');
-ok ($revokeid,$revokemsg);
-# Good. Now load the search page and test Load/Save Search.
-$agent->follow_link( url => $RT::WebPath . "/Search/Build.html",
- text => 'Tickets');
-is($agent->{'status'}, 200, "Fetched search builder page");
-ok($agent->{'content'} !~ /Load saved search/i, "No search loading box");
-ok($agent->{'content'} !~ /Saved searches/i, "No saved searches box");
-
-($grantid,$grantmsg) = $user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
-ok($grantid,$grantmsg);
-$agent->reload();
-ok($agent->{'content'} =~ /Load saved search/i, "Search loading box exists");
-ok($agent->{'content'} !~ /input\s+type=.submit.\s+name=.Save./i,
- "Still no saved searches box");
-
-($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
-ok ($grantid,$grantmsg);
-$agent->reload();
-ok($agent->{'content'} =~ /Load saved search/i,
- "Search loading box still exists");
-ok($agent->{'content'} =~ /input\s+type=.submit.\s+name=.Save./i,
- "Saved searches box exists");
-
-# Create a group, and a queue, so we can test limited user visibility
-# via SelectOwner.
-
-my $queue_obj = RT::Queue->new($RT::SystemUser);
-($ret, $msg) = $queue_obj->Create(Name => 'CustomerQueue-'.$$,
- Description => 'queue for SelectOwner testing');
-ok($ret, "SelectOwner test queue creation. $msg");
-my $group_obj = RT::Group->new($RT::SystemUser);
-($ret, $msg) = $group_obj->CreateUserDefinedGroup(Name => 'CustomerGroup-'.$$,
- Description => 'group for SelectOwner testing');
-ok($ret, "SelectOwner test group creation. $msg");
-
-# Add our customer to the customer group, and give it queue rights.
-($ret, $msg) = $group_obj->AddMember($user_obj->PrincipalObj->Id());
-ok($ret, "Added customer to its group. $msg");
-($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'OwnTicket',
- Object => $queue_obj);
-
-ok($grantid,$grantmsg);
-($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'SeeQueue',
- Object => $queue_obj);
-ok ($grantid,$grantmsg);
-# Now. When we look at the search page we should be able to see
-# ourself in the list of possible owners.
-
-$agent->reload();
-ok($agent->form_name('BuildQuery'), "Yep, form is still there");
-my $input = $agent->current_form->find_input('ValueOfActor');
-ok(grep(/customer-$$/, $input->value_names()), "Found self in the actor listing");
-
-sub login {
- my $agent = shift;
-
- my $url = $RT::WebURL;
- $agent->get($url);
- is( $agent->{'status'}, 200,
- "Loaded a page - $url" );
-
- # {{{ 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' => $user_obj->Name );
- $agent->field( 'pass' => 'customer' );
-
- # 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" );
-}
-1;
diff --git a/rt/lib/t/regression/07rights.t b/rt/lib/t/regression/07rights.t
deleted file mode 100644
index 6c35a07..0000000
--- a/rt/lib/t/regression/07rights.t
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
-# <jesse.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-
-use Test::More tests => 26;
-use RT;
-RT::LoadConfig();
-RT::Init();
-use RT::I18N;
-use strict;
-no warnings 'once';
-
-use RT::Queue;
-use RT::ACE;
-use RT::User;
-use RT::Group;
-use RT::Ticket;
-
-
-# clear all global right
-my $acl = RT::ACL->new($RT::SystemUser);
-$acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' );
-$acl->LimitToObject( $RT::System );
-while( my $ace = $acl->Next ) {
- $ace->Delete;
-}
-
-# create new queue to be shure we don't mess with rights
-my $queue = RT::Queue->new($RT::SystemUser);
-my ($queue_id) = $queue->Create( Name => 'rights');
-ok( $queue_id, 'queue created for rights tests' );
-
-# new privileged user to check rights
-my $user = RT::User->new( $RT::SystemUser );
-my ($user_id) = $user->Create( Name => 'rights',
- EmailAddress => 'rights@localhost',
- Privileged => 1,
- Password => 'qwe123',
- );
-ok( !$user->HasRight( Right => 'OwnTicket', Object => $queue ), "user can't own ticket" );
-ok( !$user->HasRight( Right => 'ReplyToTicket', Object => $queue ), "user can't reply to ticket" );
-
-my $group = RT::Group->new( $RT::SystemUser );
-ok( $group->LoadQueueRoleGroup( Queue => $queue_id, Type=> 'Owner' ), "load queue owners role group" );
-my $ace = RT::ACE->new( $RT::SystemUser );
-my ($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ReplyToTicket', Object => $queue );
-ok( $ace_id, "Granted queue owners role group with ReplyToTicket right: $msg" );
-ok( $group->PrincipalObj->HasRight( Right => 'ReplyToTicket', Object => $queue ), "role group can reply to ticket" );
-ok( !$user->HasRight( Right => 'ReplyToTicket', Object => $queue ), "user can't reply to ticket" );
-
-# new ticket
-my $ticket = RT::Ticket->new($RT::SystemUser);
-my ($ticket_id) = $ticket->Create( Queue => $queue_id, Subject => 'test');
-ok( $ticket_id, 'new ticket created' );
-is( $ticket->Owner, $RT::Nobody->Id, 'owner of the new ticket is nobody' );
-
-my $status;
-($status, $msg) = $user->PrincipalObj->GrantRight( Object => $queue, Right => 'OwnTicket' );
-ok( $status, "successfuly granted right: $msg" );
-ok( $user->HasRight( Right => 'OwnTicket', Object => $queue ), "user can own ticket" );
-
-($status, $msg) = $ticket->SetOwner( $user_id );
-ok( $status, "successfuly set owner: $msg" );
-is( $ticket->Owner, $user_id, "set correct owner" );
-
-ok( $user->HasRight( Right => 'ReplyToTicket', Object => $ticket ), "user is owner and can reply to ticket" );
-
-# Testing of EquivObjects
-$group = RT::Group->new( $RT::SystemUser );
-ok( $group->LoadQueueRoleGroup( Queue => $queue_id, Type=> 'AdminCc' ), "load queue AdminCc role group" );
-$ace = RT::ACE->new( $RT::SystemUser );
-($ace_id, $msg) = $group->PrincipalObj->GrantRight( Right => 'ModifyTicket', Object => $queue );
-ok( $ace_id, "Granted queue AdminCc role group with ModifyTicket right: $msg" );
-ok( $group->PrincipalObj->HasRight( Right => 'ModifyTicket', Object => $queue ), "role group can modify ticket" );
-ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is not AdminCc and can't modify ticket" );
-($status, $msg) = $ticket->AddWatcher(Type => 'AdminCc', PrincipalId => $user->PrincipalId);
-ok( $status, "successfuly added user as AdminCc");
-ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket ), "user is AdminCc and can modify ticket" );
-
-my $ticket2 = RT::Ticket->new($RT::SystemUser);
-my ($ticket2_id) = $ticket2->Create( Queue => $queue_id, Subject => 'test2');
-ok( $ticket2_id, 'new ticket created' );
-ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2 ), "user is not AdminCc and can't modify ticket2" );
-
-# now we can finally test EquivObjects
-my $equiv = [ $ticket ];
-ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv ),
- "user is not AdminCc but can modify ticket2 because of EquivObjects" );
-
-# the first a third test below are the same, so they should both pass
-my $equiv2 = [];
-ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv2 ),
- "user is not AdminCc and can't modify ticket2" );
-ok( $user->HasRight( Right => 'ModifyTicket', Object => $ticket, EquivObjects => $equiv2 ),
- "user is AdminCc and can modify ticket" );
-ok( !$user->HasRight( Right => 'ModifyTicket', Object => $ticket2, EquivObjects => $equiv2 ),
- "user is not AdminCc and can't modify ticket2 (same question different answer)" );
diff --git a/rt/lib/t/regression/08web_cf_access.t b/rt/lib/t/regression/08web_cf_access.t
deleted file mode 100644
index c352bbc..0000000
--- a/rt/lib/t/regression/08web_cf_access.t
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Test::More tests => 15;
-BEGIN {
- use RT;
- RT::LoadConfig;
- RT::Init;
-}
-use Test::WWW::Mechanize;
-
-use constant BaseURL => $RT::WebURL;
-use constant ImageFile => $RT::MasonComponentRoot .'/NoAuth/images/bplogo.gif';
-use constant ImageFileContent => do {
- local $/;
- open my $fh, '<', ImageFile or die $!;
- binmode($fh);
- scalar <$fh>;
-};
-
-my $m = Test::WWW::Mechanize->new;
-isa_ok($m, 'Test::WWW::Mechanize');
-
-$m->get( BaseURL."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-$m->follow_link( text => 'Configuration' );
-$m->title_is(q/RT Administration/, 'admin screen');
-$m->follow_link( text => 'Custom Fields' );
-$m->title_is(q/Select a Custom Field/, 'admin-cf screen');
-$m->follow_link( text => 'New custom field' );
-$m->submit_form(
- form_name => "ModifyCustomField",
- fields => {
- TypeComposite => 'Image-0',
- LookupType => 'RT::Queue-RT::Ticket',
- Name => 'img',
- Description => 'img',
- },
-);
-$m->title_is(q/Created CustomField img/, 'admin-cf created');
-$m->follow_link( text => 'Queues' );
-$m->title_is(q/Admin queues/, 'admin-queues screen');
-$m->follow_link( text => 'General' );
-$m->title_is(q/Editing Configuration for queue General/, 'admin-queue: general');
-$m->follow_link( text => 'Ticket Custom Fields' );
-
-$m->title_is(q/Edit Custom Fields for General/, 'admin-queue: general tcf');
-$m->form_name('EditCustomFields');
-
-# Sort by numeric IDs in names
-my @names = map { $_->[1] }
- sort { $a->[0] <=> $b->[0] }
- map { /Object-1-CF-(\d+)/ ? [ $1 => $_ ] : () }
- map $_->name, $m->current_form->inputs;
-my $tcf = pop(@names);
-$m->field( $tcf => 1 ); # Associate the new CF with this queue
-$m->field( $_ => undef ) for @names; # ...and not any other. ;-)
-$m->submit;
-
-$m->content_like( qr/Object created/, 'TCF added to the queue' );
-
-$m->submit_form(
- form_name => "CreateTicketInQueue",
- fields => { Queue => 'General' },
-);
-
-$m->content_like(qr/Upload multiple images/, 'has a upload image field');
-
-$tcf =~ /(\d+)$/ or die "Hey this is impossible dude";
-my $upload_field = "Object-RT::Ticket--CustomField-$1-Upload";
-
-$m->submit_form(
- form_name => "TicketCreate",
- fields => {
- $upload_field => ImageFile,
- Subject => 'testing img cf creation',
- },
-);
-
-$m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully");
-
-my $id = $1 if $m->content =~ /Ticket (\d+) created/;
-
-$m->title_like(qr/testing img cf creation/, "its title is the Subject");
-
-$m->follow_link( text => 'bplogo.gif' );
-$m->content_is(ImageFileContent, "it links to the uploaded image");
-
-$m->get( BaseURL );
-
-$m->follow_link( text => 'Tickets' );
-$m->follow_link( text => 'New Query' );
-
-$m->title_is(q/Query Builder/, 'Query building');
-$m->submit_form(
- form_name => "BuildQuery",
- fields => {
- idOp => '=',
- ValueOfid => $id,
- ValueOfQueue => 'General',
- },
- button => 'AddClause',
-);
-
-$m->form_name('BuildQuery');
-
-my $col = ($m->current_form->find_input('SelectDisplayColumns'))[-1];
-$col->value( ($col->possible_values)[-1] );
-
-$m->click('AddCol');
-
-$m->form_name('BuildQuery');
-$m->click('DoSearch');
-
-$m->follow_link( text_regex => qr/bplogo\.gif/ );
-$m->content_is(ImageFileContent, "it links to the uploaded image");
-
-__END__
-[FC] Bulk Update does not have custom fields.
diff --git a/rt/lib/t/regression/09record_cf_api.t b/rt/lib/t/regression/09record_cf_api.t
deleted file mode 100644
index 78f111b..0000000
--- a/rt/lib/t/regression/09record_cf_api.t
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings FATAL => 'all';
-use Test::More tests => 133;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-# Before we get going, ditch all object_cfs; this will remove
-# all custom fields systemwide;
-my $object_cfs = RT::ObjectCustomFields->new($RT::SystemUser);
-$object_cfs->UnLimit();
-while (my $ocf = $object_cfs->Next) {
- $ocf->Delete();
-}
-
-
-my $queue = RT::Queue->new( $RT::SystemUser );
-$queue->Create( Name => 'RecordCustomFields-'.$$ );
-ok ($queue->id, "Created the queue");
-
-my $queue2 = RT::Queue->new( $RT::SystemUser );
-$queue2->Create( Name => 'RecordCustomFields2' );
-
-my $ticket = RT::Ticket->new( $RT::SystemUser );
-$ticket->Create(
- Queue => $queue->Id,
- Requestor => 'root@localhost',
- Subject => 'RecordCustomFields1',
-);
-
-my $cfs = $ticket->CustomFields;
-is( $cfs->Count, 0 );
-
-# Check that record has no any CF values yet {{{
-my $cfvs = $ticket->CustomFieldValues;
-is( $cfvs->Count, 0 );
-is( $ticket->FirstCustomFieldValue, undef );
-
-my $local_cf1 = RT::CustomField->new( $RT::SystemUser );
-$local_cf1->Create( Name => 'RecordCustomFields1-'.$$, Type => 'SelectSingle', Queue => $queue->id );
-$local_cf1->AddValue( Name => 'RecordCustomFieldValues11' );
-$local_cf1->AddValue( Name => 'RecordCustomFieldValues12' );
-
-my $local_cf2 = RT::CustomField->new( $RT::SystemUser );
-$local_cf2->Create( Name => 'RecordCustomFields2-'.$$, Type => 'SelectSingle', Queue => $queue->id );
-$local_cf2->AddValue( Name => 'RecordCustomFieldValues21' );
-$local_cf2->AddValue( Name => 'RecordCustomFieldValues22' );
-
-my $global_cf3 = RT::CustomField->new( $RT::SystemUser );
-$global_cf3->Create( Name => 'RecordCustomFields3-'.$$, Type => 'SelectSingle', Queue => 0 );
-$global_cf3->AddValue( Name => 'RecordCustomFieldValues31' );
-$global_cf3->AddValue( Name => 'RecordCustomFieldValues32' );
-
-my $local_cf4 = RT::CustomField->new( $RT::SystemUser );
-$local_cf4->Create( Name => 'RecordCustomFields4', Type => 'SelectSingle', Queue => $queue2->id );
-$local_cf4->AddValue( Name => 'RecordCustomFieldValues41' );
-$local_cf4->AddValue( Name => 'RecordCustomFieldValues42' );
-
-
-my @custom_fields = ($local_cf1, $local_cf2, $global_cf3);
-
-
-$cfs = $ticket->CustomFields;
-is( $cfs->Count, 3 );
-
-# Check that record has no any CF values yet {{{
-$cfvs = $ticket->CustomFieldValues;
-is( $cfvs->Count, 0 );
-is( $ticket->FirstCustomFieldValue, undef );
-
-# CF with ID -1 shouldnt exist at all
-$cfvs = $ticket->CustomFieldValues( -1 );
-is( $cfvs->Count, 0 );
-is( $ticket->FirstCustomFieldValue( -1 ), undef );
-
-$cfvs = $ticket->CustomFieldValues( 'SomeUnexpedCustomFieldName' );
-is( $cfvs->Count, 0 );
-is( $ticket->FirstCustomFieldValue( 'SomeUnexpedCustomFieldName' ), undef );
-
-for (@custom_fields) {
- $cfvs = $ticket->CustomFieldValues( $_->id );
- is( $cfvs->Count, 0 );
-
- $cfvs = $ticket->CustomFieldValues( $_->Name );
- is( $cfvs->Count, 0 );
- is( $ticket->FirstCustomFieldValue( $_->id ), undef );
- is( $ticket->FirstCustomFieldValue( $_->Name ), undef );
-}
-# }}}
-
-# try to add field value with fields that do not exist {{{
-my ($status, $msg) = $ticket->AddCustomFieldValue( Field => -1 , Value => 'foo' );
-ok(!$status, "shouldn't add value" );
-($status, $msg) = $ticket->AddCustomFieldValue( Field => 'SomeUnexpedCustomFieldName' , Value => 'foo' );
-ok(!$status, "shouldn't add value" );
-# }}}
-
-# {{{
-SKIP: {
-
- skip "TODO: We want fields that are not allowed to set unexpected values", 10;
- for (@custom_fields) {
- ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_ , Value => 'SomeUnexpectedCFValue' );
- ok( !$status, 'value doesn\'t exist');
-
- ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_->id , Value => 'SomeUnexpectedCFValue' );
- ok( !$status, 'value doesn\'t exist');
-
- ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_->Name , Value => 'SomeUnexpectedCFValue' );
- ok( !$status, 'value doesn\'t exist');
- }
-
- # Let check that we did not add value to be sure
- # using only FirstCustomFieldValue sub because
- # we checked other variants allready
- for (@custom_fields) {
- is( $ticket->FirstCustomFieldValue( $_->id ), undef );
- }
-
-}
-# Add some values to our custom fields
-for (@custom_fields) {
- # this should be tested elsewhere
- $_->AddValue( Name => 'Foo' );
- $_->AddValue( Name => 'Bar' );
-}
-
-my $test_add_delete_cycle = sub {
- my $cb = shift;
- for (@custom_fields) {
- ($status, $msg) = $ticket->AddCustomFieldValue( Field => $cb->($_) , Value => 'Foo' );
- ok( $status, "message: $msg");
- }
-
- # does it exist?
- $cfvs = $ticket->CustomFieldValues;
- is( $cfvs->Count, 3, "We found all three custom fields on our ticket" );
- for (@custom_fields) {
- $cfvs = $ticket->CustomFieldValues( $_->id );
- is( $cfvs->Count, 1 , "we found one custom field when searching by id");
-
- $cfvs = $ticket->CustomFieldValues( $_->Name );
- is( $cfvs->Count, 1 , " We found one custom field when searching by name for " . $_->Name);
- is( $ticket->FirstCustomFieldValue( $_->id ), 'Foo' , "first value by id is foo");
- is( $ticket->FirstCustomFieldValue( $_->Name ), 'Foo' , "first value by name is foo");
- }
- # because our CFs are SingleValue then new value addition should override
- for (@custom_fields) {
- ($status, $msg) = $ticket->AddCustomFieldValue( Field => $_ , Value => 'Bar' );
- ok( $status, "message: $msg");
- }
- $cfvs = $ticket->CustomFieldValues;
- is( $cfvs->Count, 3 );
- for (@custom_fields) {
- $cfvs = $ticket->CustomFieldValues( $_->id );
- is( $cfvs->Count, 1 );
-
- $cfvs = $ticket->CustomFieldValues( $_->Name );
- is( $cfvs->Count, 1 );
- is( $ticket->FirstCustomFieldValue( $_->id ), 'Bar' );
- is( $ticket->FirstCustomFieldValue( $_->Name ), 'Bar' );
- }
- # delete it
- for (@custom_fields ) {
- ($status, $msg) = $ticket->DeleteCustomFieldValue( Field => $_ , Value => 'Bar' );
- ok( $status, "Deleted a custom field value 'Bar' for field ".$_->id.": $msg");
- }
- $cfvs = $ticket->CustomFieldValues;
- is( $cfvs->Count, 0, "The ticket (".$ticket->id.") no longer has any custom field values" );
- for (@custom_fields) {
- $cfvs = $ticket->CustomFieldValues( $_->id );
- is( $cfvs->Count, 0, $ticket->id." has no values for cf ".$_->id );
-
- $cfvs = $ticket->CustomFieldValues( $_->Name );
- is( $cfvs->Count, 0 , $ticket->id." has no values for cf '".$_->Name. "'" );
- is( $ticket->FirstCustomFieldValue( $_->id ), undef , "There is no first custom field value when loading by id" );
- is( $ticket->FirstCustomFieldValue( $_->Name ), undef, "There is no first custom field value when loading by Name" );
- }
-};
-
-# lets test cycle via CF id
-$test_add_delete_cycle->( sub { return $_[0]->id } );
-# lets test cycle via CF object reference
-$test_add_delete_cycle->( sub { return $_[0] } );
-
-$ticket->AddCustomFieldValue( Field => $local_cf2->id , Value => 'Baz' );
-$ticket->AddCustomFieldValue( Field => $global_cf3->id , Value => 'Baz' );
-# now if we ask for cf values on RecordCustomFields4 we should not get any
-$cfvs = $ticket->CustomFieldValues( 'RecordCustomFields4' );
-is( $cfvs->Count, 0, "No custom field values for non-Queue cf" );
-is( $ticket->FirstCustomFieldValue( 'RecordCustomFields4' ), undef, "No first custom field value for non-Queue cf" );
-
-
-#SKIP: {
-# skip "TODO: should we add CF values to objects via CF Name?", 48;
-# names are not unique
- # lets test cycle via CF Name
-# $test_add_delete_cycle->( sub { return $_[0]->Name } );
-#}
-
-
diff --git a/rt/lib/t/regression/10merge.t b/rt/lib/t/regression/10merge.t
deleted file mode 100644
index 8bca952..0000000
--- a/rt/lib/t/regression/10merge.t
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/perl
-
-use warnings;
-use strict;
-
-
-#
-# This test script validates that when merging two tickets, the comments from both tickets
-# are integrated into the new ticket
-
-use Test::More tests => 13;
-use RT;
-RT::LoadConfig;
-RT::Init;
-
-use_ok('RT::Ticket');
-use_ok('RT::Queue');
-
-my $queue = RT::Queue->new($RT::SystemUser);
-my ($id,$msg) = $queue->Create(Name => 'MergeTest-'.rand(25));
-ok ($id,$msg);
-
-my $t1 = RT::Ticket->new($RT::SystemUser);
-my ($tid,$transid, $t1msg) =$t1->Create ( Queue => $queue->Name, Subject => 'Merge test. orig');
-ok ($tid, $t1msg);
-($id, $msg) = $t1->Comment(Content => 'This is a Comment on the original');
-ok($id,$msg);
-
-my $txns = $t1->Transactions;
-my $Comments = 0;
-while (my $txn = $txns->Next) {
-$Comments++ if ($txn->Type eq 'Comment');
-}
-is($Comments,1, "our first ticket has only one Comment");
-
-my $t2 = RT::Ticket->new($RT::SystemUser);
-my ($t2id,$t2transid, $t2msg) =$t2->Create ( Queue => $queue->Name, Subject => 'Merge test. duplicate');
-ok ($t2id, $t2msg);
-
-
-
-($id, $msg) = $t2->Comment(Content => 'This is a commet on the duplicate');
-ok($id,$msg);
-
-
-$txns = $t2->Transactions;
- $Comments = 0;
-while (my $txn = $txns->Next) {
- $Comments++ if ($txn->Type eq 'Comment');
-}
-is($Comments,1, "our second ticket has only one Comment");
-
-($id, $msg) = $t1->Comment(Content => 'This is a second Comment on the original');
-ok($id,$msg);
-
-$txns = $t1->Transactions;
-$Comments = 0;
-while (my $txn = $txns->Next) {
- $Comments++ if ($txn->Type eq 'Comment');
-}
-is($Comments,2, "our first ticket now has two Comments");
-
-($id,$msg) = $t2->MergeInto($t1->id);
-
-ok($id,$msg);
-$txns = $t1->Transactions;
-$Comments = 0;
-while (my $txn = $txns->Next) {
- $Comments++ if ($txn->Type eq 'Comment');
-}
-is($Comments,3, "our first ticket now has three Comments - we merged safely");
-
diff --git a/rt/lib/t/regression/11-template-insert.t b/rt/lib/t/regression/11-template-insert.t
deleted file mode 100644
index 8681ce6..0000000
--- a/rt/lib/t/regression/11-template-insert.t
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/perl
-
-use warnings;
-use strict;
-
-use Test::More tests => 7;
-
-use RT;
-RT::LoadConfig();
-RT::Init;
-
-
-# This tiny little test script triggers an interaction bug between DBD::Oracle 1.16, SB 1.15 and RT 3.4
-
-use_ok('RT::Template');
-my $template = RT::Template->new($RT::SystemUser);
-
-isa_ok($template, 'RT::Template');
-my ($val,$msg) = $template->Create(Queue => 1,
- Name => 'InsertTest',
- Content => 'This is template content');
-ok($val,$msg);
-is($template->Name, 'InsertTest');
-is($template->Content, 'This is template content', "We created the object right");
-($val, $msg) = $template->SetContent( 'This is new template content');
-ok($val,$msg);
-is($template->Content, 'This is new template content', "We managed to _Set_ the content");
diff --git a/rt/lib/t/regression/12-search.t b/rt/lib/t/regression/12-search.t
deleted file mode 100644
index c775f93..0000000
--- a/rt/lib/t/regression/12-search.t
+++ /dev/null
@@ -1,281 +0,0 @@
-#!/opt/perl/bin/perl -w
-
-# tests relating to searching. Especially around custom fields, and
-# corner cases.
-
-use strict;
-use warnings;
-
-use Test::More tests => 44;
-use_ok('RT');
-RT::LoadConfig();
-RT::Init();
-
-# setup the queue
-
-my $q = RT::Queue->new($RT::SystemUser);
-my $queue = 'SearchTests-'.$$;
-$q->Create(Name => $queue);
-ok ($q->id, "Created the queue");
-
-
-# and setup the CFs
-# we believe the Type shouldn't matter.
-
-my $cf = RT::CustomField->new($RT::SystemUser);
-$cf->Create(Name => 'SearchTest', Type => 'Freeform', MaxValues => 0, Queue => $q->id);
-ok($cf->id, "Created the SearchTest CF");
-my $cflabel = "CustomField-".$cf->id;
-
-my $cf2 = RT::CustomField->new($RT::SystemUser);
-$cf2->Create(Name => 'SearchTest2', Type => 'Freeform', MaxValues => 0, Queue => $q->id);
-ok($cf2->id, "Created the SearchTest2 CF");
-my $cflabel2 = "CustomField-".$cf2->id;
-
-my $cf3 = RT::CustomField->new($RT::SystemUser);
-$cf3->Create(Name => 'SearchTest3', Type => 'Freeform', MaxValues => 0, Queue => $q->id);
-ok($cf3->id, "Created the SearchTest3 CF");
-my $cflabel3 = "CustomField-".$cf3->id;
-
-
-# There was a bug involving a missing join to ObjectCustomFields that
-# caused spurious results on negative searches if another custom field
-# with the same name existed on a different queue. Hence, we make
-# duplicate CFs on a different queue here
-my $dup = RT::Queue->new($RT::SystemUser);
-$dup->Create(Name => $queue . "-Copy");
-ok ($dup->id, "Created the duplicate queue");
-my $dupcf = RT::CustomField->new($RT::SystemUser);
-$dupcf->Create(Name => 'SearchTest', Type => 'Freeform', MaxValues => 0, Queue => $dup->id);
-ok($dupcf->id, "Created the duplicate SearchTest CF");
-$dupcf = RT::CustomField->new($RT::SystemUser);
-$dupcf->Create(Name => 'SearchTest2', Type => 'Freeform', MaxValues => 0, Queue => $dup->id);
-ok($dupcf->id, "Created the SearchTest2 CF");
-$dupcf = RT::CustomField->new($RT::SystemUser);
-$dupcf->Create(Name => 'SearchTest3', Type => 'Freeform', MaxValues => 0, Queue => $dup->id);
-ok($dupcf->id, "Created the SearchTest3 CF");
-
-
-# setup some tickets
-# we'll need a small pile of them, to test various combinations and nulls.
-# there's probably a way to think harder and do this with fewer
-
-
-my $t1 = RT::Ticket->new($RT::SystemUser);
-my ( $id, undef $msg ) = $t1->Create(
- Queue => $q->id,
- Subject => 'SearchTest1',
- Requestor => ['search1@example.com'],
- $cflabel => 'foo1',
- $cflabel2 => 'bar1',
- $cflabel3 => 'qux1',
-);
-ok( $id, $msg );
-
-
-my $t2 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t2->Create(
- Queue => $q->id,
- Subject => 'SearchTest2',
- Requestor => ['search2@example.com'],
-# $cflabel => 'foo2',
- $cflabel2 => 'bar2',
- $cflabel3 => 'qux2',
-);
-ok( $id, $msg );
-
-my $t3 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t3->Create(
- Queue => $q->id,
- Subject => 'SearchTest3',
- Requestor => ['search3@example.com'],
- $cflabel => 'foo3',
-# $cflabel2 => 'bar3',
- $cflabel3 => 'qux3',
-);
-ok( $id, $msg );
-
-my $t4 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t4->Create(
- Queue => $q->id,
- Subject => 'SearchTest4',
- Requestor => ['search4@example.com'],
- $cflabel => 'foo4',
- $cflabel2 => 'bar4',
-# $cflabel3 => 'qux4',
-);
-ok( $id, $msg );
-
-my $t5 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t5->Create(
- Queue => $q->id,
-# Subject => 'SearchTest5',
- Requestor => ['search5@example.com'],
- $cflabel => 'foo5',
- $cflabel2 => 'bar5',
- $cflabel3 => 'qux5',
-);
-ok( $id, $msg );
-
-my $t6 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t6->Create(
- Queue => $q->id,
- Subject => 'SearchTest6',
-# Requestor => ['search6@example.com'],
- $cflabel => 'foo6',
- $cflabel2 => 'bar6',
- $cflabel3 => 'qux6',
-);
-ok( $id, $msg );
-
-my $t7 = RT::Ticket->new($RT::SystemUser);
-( $id, undef, $msg ) = $t7->Create(
- Queue => $q->id,
- Subject => 'SearchTest7',
- Requestor => ['search7@example.com'],
-# $cflabel => 'foo7',
-# $cflabel2 => 'bar7',
- $cflabel3 => 'qux7',
-);
-ok( $id, $msg );
-
-# we have tickets. start searching
-my $tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue'");
-is($tix->Count, 7, "found all the tickets")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-
-# very simple searches. both CF and normal
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest = 'foo1'");
-is($tix->Count, 1, "matched identical subject")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo1'");
-is($tix->Count, 1, "matched LIKE subject")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest = 'foo'");
-is($tix->Count, 0, "IS a regexp match")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest LIKE 'foo'");
-is($tix->Count, 5, "matched LIKE subject")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest IS NULL");
-is($tix->Count, 2, "IS null CF")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Requestors LIKE 'search1'");
-is($tix->Count, 1, "LIKE requestor")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Requestors = 'search1\@example.com'");
-is($tix->Count, 1, "IS requestor")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Requestors LIKE 'search'");
-is($tix->Count, 6, "LIKE requestor")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Requestors IS NULL");
-is($tix->Count, 1, "Search for no requestor")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Subject = 'SearchTest1'");
-is($tix->Count, 1, "IS subject")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Subject LIKE 'SearchTest1'");
-is($tix->Count, 1, "LIKE subject")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Subject = ''");
-is($tix->Count, 1, "found one ticket")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Subject LIKE 'SearchTest'");
-is($tix->Count, 6, "found two ticket")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND Subject LIKE 'qwerty'");
-is($tix->Count, 0, "found zero ticket")
- or diag "wrong results from SQL:\n". $tix->BuildSelectCountQuery;
-
-
-
-
-# various combinations
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar1'");
-is($tix->Count, 1, "LIKE cf and LIKE cf");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest = 'foo1' AND CF.SearchTest2 = 'bar1'");
-is($tix->Count, 1, "is cf and is cf");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest = 'foo' AND CF.SearchTest2 LIKE 'bar1'");
-is($tix->Count, 0, "is cf and like cf");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar' AND CF.SearchTest3 LIKE 'qux'");
-is($tix->Count, 3, "like cf and like cf and like cf");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest LIKE 'foo' AND CF.SearchTest2 LIKE 'bar' AND CF.SearchTest3 LIKE 'qux6'");
-is($tix->Count, 1, "like cf and like cf and is cf");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest LIKE 'foo' AND Subject LIKE 'SearchTest'");
-is($tix->Count, 4, "like cf and like subject");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest IS NULL AND CF.SearchTest2 = 'bar2'");
-is($tix->Count, 1, "null cf and is cf");
-
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("Queue = '$queue' AND CF.SearchTest IS NULL AND CF.SearchTest2 IS NULL");
-is($tix->Count, 1, "null cf and null cf");
-
-# tests with the same CF listed twice
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.{SearchTest} = 'foo1'");
-is($tix->Count, 1, "is cf.{name} format");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3'");
-is($tix->Count, 2, "is cf1 or is cf1");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest IS NULL");
-is($tix->Count, 3, "is cf1 or null cf1");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("(CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3') AND (CF.SearchTest2 = 'bar1' OR CF.SearchTest2 = 'bar2')");
-is($tix->Count, 1, "(is cf1 or is cf1) and (is cf2 or is cf2)");
-
-$tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL("CF.SearchTest = 'foo1' OR CF.SearchTest = 'foo3' OR CF.SearchTest2 = 'bar1' OR CF.SearchTest2 = 'bar2'");
-is($tix->Count, 3, "is cf1 or is cf1 or is cf2 or is cf2");
-
diff --git a/rt/lib/t/regression/13-attribute-tests.t b/rt/lib/t/regression/13-attribute-tests.t
deleted file mode 100644
index fdac94e..0000000
--- a/rt/lib/t/regression/13-attribute-tests.t
+++ /dev/null
@@ -1,87 +0,0 @@
-use strict;
-use warnings;
-use Test::More tests => 34;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-
-my $runid = rand(200);
-
-my $attribute = "squelch-$runid";
-
-ok(require RT::Attributes);
-
-my $user = RT::User->new($RT::SystemUser);
-ok (UNIVERSAL::isa($user, 'RT::User'));
-my ($id,$msg) = $user->Create(Name => 'attrtest-'.$runid);
-ok ($id, $msg);
-ok($user->id, "Created a test user");
-
-ok(1, $user->Attributes->BuildSelectQuery);
-my $attr = $user->Attributes;
-# XXX: Order by id as some tests depend on it
-$attr->OrderByCols({ FIELD => 'id' });
-
-ok(1, $attr->BuildSelectQuery);
-
-
-ok (UNIVERSAL::isa($attr,'RT::Attributes'), 'got the attributes object');
-
-($id, $msg) = $user->AddAttribute(Name => 'TestAttr', Content => 'The attribute has content');
-ok ($id, $msg);
-is ($attr->Count,1, " One attr after adidng a first one");
-
-my $first_attr = $user->FirstAttribute('TestAttr');
-ok($first_attr, "got some sort of attribute");
-isa_ok($first_attr, 'RT::Attribute');
-is($first_attr->Content, 'The attribute has content', "got the right content back");
-
-($id, $msg) = $attr->DeleteEntry(Name => $runid);
-ok(!$id, "Deleted non-existant entry - $msg");
-is ($attr->Count,1, "1 attr after deleting an empty attr");
-
-my @names = $attr->Names;
-is ("@names", "TestAttr");
-
-
-($id, $msg) = $user->AddAttribute(Name => $runid, Content => "First");
-ok($id, $msg);
-
-my $runid_attr = $user->FirstAttribute($runid);
-ok($runid_attr, "got some sort of attribute");
-isa_ok($runid_attr, 'RT::Attribute');
-is($runid_attr->Content, 'First', "got the right content back");
-
-is ($attr->Count,2, " Two attrs after adding an attribute named $runid");
-($id, $msg) = $user->AddAttribute(Name => $runid, Content => "Second");
-ok($id, $msg);
-
-$runid_attr = $user->FirstAttribute($runid);
-ok($runid_attr, "got some sort of attribute");
-isa_ok($runid_attr, 'RT::Attribute');
-is($runid_attr->Content, 'First', "got the first content back still");
-
-is ($attr->Count,3, " Three attrs after adding a secondvalue to $runid");
-($id, $msg) = $attr->DeleteEntry(Name => $runid, Content => "First");
-ok($id, $msg);
-is ($attr->Count,2);
-
-#$attr->_DoSearch();
-($id, $msg) = $attr->DeleteEntry(Name => $runid, Content => "Second");
-ok($id, $msg);
-is ($attr->Count,1);
-
-#$attr->_DoSearch();
-ok(1, $attr->BuildSelectQuery);
-($id, $msg) = $attr->DeleteEntry(Name => "moose");
-ok(!$id, "Deleted non-existant entry - $msg");
-is ($attr->Count,1);
-
-ok(1, $attr->BuildSelectQuery);
-@names = $attr->Names;
-is("@names", "TestAttr");
-
-
-
-1;
diff --git a/rt/lib/t/regression/14linking.t b/rt/lib/t/regression/14linking.t
deleted file mode 100644
index c8e57ea..0000000
--- a/rt/lib/t/regression/14linking.t
+++ /dev/null
@@ -1,243 +0,0 @@
-use Test::More tests => '70';
-use_ok('RT');
-use_ok('RT::Ticket');
-use_ok('RT::ScripConditions');
-use_ok('RT::ScripActions');
-use_ok('RT::Template');
-use_ok('RT::Scrips');
-use_ok('RT::Scrip');
-RT::LoadConfig();
-RT::Init();
-
-use File::Temp qw/tempfile/;
-my ($fh, $filename) = tempfile( UNLINK => 1, SUFFIX => '.rt');
-my $link_scrips_orig = $RT::LinkTransactionsRun1Scrip;
-my $link_acl_chacks_orig = $RT::StrictLinkACL;
-$RT::LinkTransactionsRun1Scrip = 1;
-$RT::StrictLinkACL = 1;
-
-my $condition = RT::ScripCondition->new( $RT::SystemUser );
-$condition->Load('User Defined');
-ok($condition->id);
-my $action = RT::ScripAction->new( $RT::SystemUser );
-$action->Load('User Defined');
-ok($action->id);
-my $template = RT::Template->new( $RT::SystemUser );
-$template->Load('Blank');
-ok($template->id);
-
-my $q1 = RT::Queue->new($RT::SystemUser);
-my ($id,$msg) = $q1->Create(Name => "LinkTest1.$$");
-ok ($id,$msg);
-my $q2 = RT::Queue->new($RT::SystemUser);
-($id,$msg) = $q2->Create(Name => "LinkTest2.$$");
-ok ($id,$msg);
-
-my $commit_code = <<END;
-open(FILE, "<$filename");
-my \$data = <FILE>;
-chomp \$data;
-close FILE;
-open(FILE, ">$filename");
-if (\$self->TransactionObj->Type eq 'AddLink') {
- print FILE \$data+1, "\n";
-}
-else {
- print FILE \$data-1, "\n";
-}
-close FILE;
-1;
-END
-
-my $Scrips = RT::Scrips->new( $RT::SystemUser );
-$Scrips->UnLimit;
-while ( my $Scrip = $Scrips->Next ) {
- $Scrip->Delete if $Scrip->Description =~ /Add or Delete Link \d+/;
-}
-
-
-my $scrip = RT::Scrip->new($RT::SystemUser);
-($id,$msg) = $scrip->Create( Description => "Add or Delete Link $$",
- ScripCondition => $condition->id,
- ScripAction => $action->id,
- Template => $template->id,
- Stage => 'TransactionCreate',
- Queue => 0,
- CustomIsApplicableCode => '$self->TransactionObj->Type =~ /(Add|Delete)Link/;',
- CustomPrepareCode => '1;',
- CustomCommitCode => $commit_code,
- );
-ok($id, "Scrip created");
-
-my $u1 = RT::User->new($RT::SystemUser);
-($id,$msg) = $u1->Create(Name => "LinkTestUser.$$");
-ok ($id,$msg);
-
-my $creator = RT::CurrentUser->new($u1->id);
-
-($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'CreateTicket');
-ok ($id,$msg);
-
-diag('Create tickets without rights to link') if $ENV{'TEST_VERBOSE'};
-{
- # on q2 we have no rights, yet
- my $parent = RT::Ticket->new( $RT::SystemUser );
- ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id );
- ok($id,$msg);
- my $child = RT::Ticket->new( $creator );
- ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id, MemberOf => $parent->id );
- ok($id,$msg);
- $child->CurrentUser( $RT::SystemUser );
- is($child->_Links('Base')->Count, 0, 'link was not created, no permissions');
- is($child->_Links('Target')->Count, 0, 'link was not create, no permissions');
-}
-
-diag('Create tickets with rights checks on one end of a link') if $ENV{'TEST_VERBOSE'};
-{
- # on q2 we have no rights, but use checking one only on thing
- local $RT::StrictLinkACL = 0;
- my $parent = RT::Ticket->new( $RT::SystemUser );
- ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id );
- ok($id,$msg);
- my $child = RT::Ticket->new( $creator );
- ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id, MemberOf => $parent->id );
- ok($id,$msg);
- $child->CurrentUser( $RT::SystemUser );
- is($child->_Links('Base')->Count, 1, 'link was created');
- is($child->_Links('Target')->Count, 0, 'link was created only one');
- # no scrip run on second ticket accroding to config option
- is(link_count($filename), 0, "scrips ok");
-}
-
-($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q1, Right => 'ModifyTicket');
-ok ($id,$msg);
-
-diag('try to add link without rights') if $ENV{'TEST_VERBOSE'};
-{
- # on q2 we have no rights, yet
- my $parent = RT::Ticket->new( $RT::SystemUser );
- ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id );
- ok($id,$msg);
- my $child = RT::Ticket->new( $creator );
- ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id );
- ok($id,$msg);
- my ($id, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id);
- ok(!$id, $msg);
- is(link_count($filename), 0, "scrips ok");
- $child->CurrentUser( $RT::SystemUser );
- is($child->_Links('Base')->Count, 0, 'link was not created, no permissions');
- is($child->_Links('Target')->Count, 0, 'link was not create, no permissions');
-}
-
-diag('add link with rights only on base') if $ENV{'TEST_VERBOSE'};
-{
- # on q2 we have no rights, but use checking one only on thing
- local $RT::StrictLinkACL = 0;
- my $parent = RT::Ticket->new( $RT::SystemUser );
- ($id,$tid,$msg) = $parent->Create( Subject => 'Link test 1', Queue => $q2->id );
- ok($id,$msg);
- my $child = RT::Ticket->new( $creator );
- ($id,$tid,$msg) = $child->Create( Subject => 'Link test 1', Queue => $q1->id );
- ok($id,$msg);
- my ($id, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id);
- ok($id, $msg);
- is(link_count($filename), 1, "scrips ok");
- $child->CurrentUser( $RT::SystemUser );
- is($child->_Links('Base')->Count, 1, 'link was created');
- is($child->_Links('Target')->Count, 0, 'link was created only one');
- $child->CurrentUser( $creator );
-
- # turn off feature and try to delete link, we should fail
- $RT::StrictLinkACL = 1;
- my ($id, $msg) = $child->AddLink(Type => 'MemberOf', Target => $parent->id);
- ok(!$id, $msg);
- is(link_count($filename), 1, "scrips ok");
- $child->CurrentUser( $RT::SystemUser );
- $child->_Links('Base')->_DoCount;
- is($child->_Links('Base')->Count, 1, 'link was not deleted');
- $child->CurrentUser( $creator );
-
- # try to delete link, we should success as feature is active
- $RT::StrictLinkACL = 0;
- my ($id, $msg) = $child->DeleteLink(Type => 'MemberOf', Target => $parent->id);
- ok($id, $msg);
- is(link_count($filename), 0, "scrips ok");
- $child->CurrentUser( $RT::SystemUser );
- $child->_Links('Base')->_DoCount;
- is($child->_Links('Base')->Count, 0, 'link was deleted');
-}
-
-my $tid;
-my $ticket = RT::Ticket->new( $creator);
-ok($ticket->isa('RT::Ticket'));
-($id,$tid, $msg) = $ticket->Create(Subject => 'Link test 1', Queue => $q1->id);
-ok ($id,$msg);
-
-diag('try link to itself') if $ENV{'TEST_VERBOSE'};
-{
- my ($id, $msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket->id);
- ok(!$id, $msg);
- is(link_count($filename), 0, "scrips ok");
-}
-
-my $ticket2 = RT::Ticket->new($RT::SystemUser);
-($id, $tid, $msg) = $ticket2->Create(Subject => 'Link test 2', Queue => $q2->id);
-ok ($id, $msg);
-($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id);
-ok(!$id,$msg);
-ok(link_count($filename) == 0, "scrips ok");
-
-($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'CreateTicket');
-ok ($id,$msg);
-($id,$msg) = $u1->PrincipalObj->GrantRight ( Object => $q2, Right => 'ModifyTicket');
-ok ($id,$msg);
-($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id);
-ok($id,$msg);
-ok(link_count($filename) == 1, "scrips ok");
-($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => -1);
-ok(!$id,$msg);
-ok(link_count($filename) == 1, "scrips ok");
-($id,$msg) = $ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id);
-ok($id,$msg);
-is(link_count($filename), 1, "scrips ok");
-
-my $transactions = $ticket2->Transactions;
-$transactions->Limit( FIELD => 'Type', VALUE => 'AddLink' );
-ok( $transactions->Count == 1, "Transaction found in other ticket" );
-ok( $transactions->First->Field eq 'ReferredToBy');
-ok( $transactions->First->NewValue eq $ticket->URI );
-
-($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id);
-ok($id,$msg);
-ok(link_count($filename) == 0, "scrips ok");
-$transactions = $ticket2->Transactions;
-$transactions->Limit( FIELD => 'Type', VALUE => 'DeleteLink' );
-ok( $transactions->Count == 1, "Transaction found in other ticket" );
-ok( $transactions->First->Field eq 'ReferredToBy');
-ok( $transactions->First->OldValue eq $ticket->URI );
-
-$RT::LinkTransactionsRun1Scrip = 0;
-
-($id,$msg) =$ticket->AddLink(Type => 'RefersTo', Target => $ticket2->id);
-ok($id,$msg);
-ok(link_count($filename) == 2, "scrips ok");
-($id,$msg) =$ticket->DeleteLink(Type => 'RefersTo', Target => $ticket2->id);
-ok($id,$msg);
-ok(link_count($filename) == 0, "scrips ok");
-
-# restore
-$RT::LinkTransactionsRun1Scrip = $link_scrips_orig;
-$RT::StrictLinkACL = $link_acl_checks_orig;
-
-exit(0);
-
-sub link_count {
-
- my $file = shift;
- open(FILE, "<$file");
- my $data = <FILE>;
- chomp $data;
- return $data + 0;
- close FILE;
-
-}
diff --git a/rt/lib/t/regression/14merge.t b/rt/lib/t/regression/14merge.t
deleted file mode 100644
index c916251..0000000
--- a/rt/lib/t/regression/14merge.t
+++ /dev/null
@@ -1,31 +0,0 @@
-
-use Test::More tests => '6';
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-# when you try to merge duplicate links on postgres, eveyrything goes to hell due to referential integrity constraints.
-
-
-my $t = RT::Ticket->new($RT::SystemUser);
-$t->Create(Subject => 'Main', Queue => 'general');
-
-ok ($t->id);
-my $t2 = RT::Ticket->new($RT::SystemUser);
-$t2->Create(Subject => 'Second', Queue => 'general');
-ok ($t2->id);
-
-my $t3 = RT::Ticket->new($RT::SystemUser);
-$t3->Create(Subject => 'Third', Queue => 'general');
-
-ok ($t3->id);
-
-my ($id,$val);
-($id,$val) = $t->AddLink(Type => 'DependsOn', Target => $t3->id);
-ok($id,$val);
-($id,$val) = $t2->AddLink(Type => 'DependsOn', Target => $t3->id);
-ok($id,$val);
-
-
-($id,$val) = $t->MergeInto($t2->id);
-ok($id,$val);
diff --git a/rt/lib/t/regression/15cf_combo_cascade.t b/rt/lib/t/regression/15cf_combo_cascade.t
deleted file mode 100644
index df663a1..0000000
--- a/rt/lib/t/regression/15cf_combo_cascade.t
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/perl
-use warnings;
-use strict;
-use Test::More tests => 11;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-sub fails { ok(!$_[0], "This should fail: $_[1]") }
-sub works { ok($_[0], $_[1] || 'This works') }
-
-sub new (*) {
- my $class = shift;
- return $class->new($RT::SystemUser);
-}
-
-my $q = new(RT::Queue);
-works($q->Create(Name => "CF-Pattern-".$$));
-
-my $cf = new(RT::CustomField);
-my @cf_args = (Name => $q->Name, Type => 'Combobox', Queue => $q->id);
-
-works($cf->Create(@cf_args));
-
-# Set some CFVs with Category markers
-
-my $t = new(RT::Ticket);
-my ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Test');
-works($id,$msg);
-
-sub add_works {
- works(
- $cf->AddValue(Name => $_[0], Description => $_[0], Category => $_[1])
- );
-};
-
-add_works('value1', '1. Category A');
-add_works('value2');
-add_works('value3', '1.1. A-sub one');
-add_works('value4', '1.2. A-sub two');
-add_works('value5', '');
-
-my $cfv = $cf->Values->First;
-is($cfv->Category, '1. Category A');
-works($cfv->SetCategory('1. Category AAA'));
-is($cfv->Category, '1. Category AAA');
-
-1;
diff --git a/rt/lib/t/regression/15cf_pattern.t b/rt/lib/t/regression/15cf_pattern.t
deleted file mode 100644
index ea2b5b8..0000000
--- a/rt/lib/t/regression/15cf_pattern.t
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/perl
-use warnings;
-use strict;
-use Test::More tests => 17;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-sub fails { ok(!$_[0], "This should fail: $_[1]") }
-sub works { ok($_[0], $_[1] || 'This works') }
-
-sub new (*) {
- my $class = shift;
- return $class->new($RT::SystemUser);
-}
-
-my $q = new(RT::Queue);
-works($q->Create(Name => "CF-Pattern-".$$));
-
-my $cf = new(RT::CustomField);
-my @cf_args = (Name => $q->Name, Type => 'Freeform', Queue => $q->id, MaxValues => 1);
-
-fails($cf->Create(@cf_args, Pattern => ')))bad!regex((('));
-works($cf->Create(@cf_args, Pattern => 'good regex'));
-
-my $t = new(RT::Ticket);
-my ($id,undef,$msg) = $t->Create(Queue => $q->id, Subject => 'CF Test');
-works($id,$msg);
-
-# OK, I'm thoroughly brain washed by HOP at this point now...
-sub cnt { $t->CustomFieldValues($cf->id)->Count };
-sub add { $t->AddCustomFieldValue(Field => $cf->id, Value => $_[0]) };
-sub del { $t->DeleteCustomFieldValue(Field => $cf->id, Value => $_[0]) };
-
-is(cnt(), 0, "No values yet");
-fails(add('not going to match'));
-is(cnt(), 0, "No values yet");
-works(add('here is a good regex'));
-is(cnt(), 1, "Value filled");
-fails(del('here is a good regex'));
-is(cnt(), 1, "Single CF - Value _not_ deleted");
-
-$cf->SetMaxValues(0); # Unlimited MaxValues
-
-works(del('here is a good regex'));
-is(cnt(), 0, "Multiple CF - Value deleted");
-
-fails($cf->SetPattern('(?{ "insert evil code here" })'));
-works($cf->SetPattern('(?!)')); # reject everything
-fails(add(''));
-fails(add('...'));
-
-1;
diff --git a/rt/lib/t/regression/15cf_single_values_are_single.t b/rt/lib/t/regression/15cf_single_values_are_single.t
deleted file mode 100644
index dcfa2e5..0000000
--- a/rt/lib/t/regression/15cf_single_values_are_single.t
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/perl
-use warnings;
-use strict;
-use Test::More tests => 8;
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-
-my $q = RT::Queue->new($RT::SystemUser);
-my ($id,$msg) =$q->Create(Name => "CF-Single-".$$);
-ok($id,$msg);
-
-my $cf = RT::CustomField->new($RT::SystemUser);
-($id,$msg) = $cf->Create(Name => 'Single-'.$$, Type => 'Select', MaxValues => '1', Queue => $q->id);
-ok($id,$msg);
-
-
-($id,$msg) =$cf->AddValue(Name => 'First');
-ok($id,$msg);
-
-($id,$msg) =$cf->AddValue(Name => 'Second');
-ok($id,$msg);
-
-
-my $t = RT::Ticket->new($RT::SystemUser);
-($id,undef,$msg) = $t->Create(Queue => $q->id,
- Subject => 'CF Test');
-
-ok($id,$msg);
-is($t->CustomFieldValues($cf->id)->Count, 0, "No values yet");
-$t->AddCustomFieldValue(Field => $cf->id, Value => 'First');
-is($t->CustomFieldValues($cf->id)->Count, 1, "One now");
-
-$t->AddCustomFieldValue(Field => $cf->id, Value => 'Second');
-is($t->CustomFieldValues($cf->id)->Count, 1, "Still one");
-
-1;
diff --git a/rt/lib/t/regression/16-transaction_cf_tests.t b/rt/lib/t/regression/16-transaction_cf_tests.t
deleted file mode 100644
index 9e1e86c..0000000
--- a/rt/lib/t/regression/16-transaction_cf_tests.t
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/perl
-
-use warnings;
-use strict;
-use Data::Dumper;
-use Test::More qw/no_plan/;
-
-use_ok('RT');
-use_ok('RT::Transactions');
-RT::LoadConfig();
-RT::Init();
-
-my $q = RT::Queue->new($RT::SystemUser);
-my ($id,$msg) = $q->Create( Name => 'TxnCFTest'.$$);
-ok($id,$msg);
-
-my $cf = RT::CustomField->new($RT::SystemUser);
-($id,$msg) = $cf->Create(Name => 'Txnfreeform-'.$$, Type => 'Freeform', MaxValues => '0', LookupType => RT::Transaction->CustomFieldLookupType );
-
-ok($id,$msg);
-
-($id,$msg) = $cf->AddToObject($q);
-
-ok($id,$msg);
-
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-
-my $transid;
-($id,$transid, $msg) = $ticket->Create(Queue => $q->id,
- Subject => 'TxnCF test',
- );
-ok($id,$msg);
-
-my $trans = RT::Transaction->new($RT::SystemUser);
-$trans->Load($transid);
-
-is($trans->ObjectId,$id);
-is ($trans->ObjectType, 'RT::Ticket');
-is ($trans->Type, 'Create');
-my $txncfs = $trans->CustomFields;
-is ($txncfs->Count, 1, "We have one custom field");
-my $txn_cf = $txncfs->First;
-is ($txn_cf->id, $cf->id, "It's the right custom field");
-my $values = $trans->CustomFieldValues($txn_cf->id);
-is ($values->Count, 0, "It has no values");
-
-# Old API
-my %cf_updates = ( 'CustomField-'.$cf->id => 'Testing');
-$trans->UpdateCustomFields( ARGSRef => \%cf_updates);
-
- $values = $trans->CustomFieldValues($txn_cf->id);
-is ($values->Count, 1, "It has one value");
-
-# New API
-
-$trans->UpdateCustomFields( 'CustomField-'.$cf->id => 'Test two');
- $values = $trans->CustomFieldValues($txn_cf->id);
-is ($values->Count, 2, "it has two values");
-
-# TODO ok(0, "Should updating custom field values remove old values?");
diff --git a/rt/lib/t/regression/17custom_search.t b/rt/lib/t/regression/17custom_search.t
deleted file mode 100644
index 8e53f44..0000000
--- a/rt/lib/t/regression/17custom_search.t
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Test::More tests => 10;
-BEGIN {
- use RT;
- RT::LoadConfig;
- RT::Init;
-}
-use Test::WWW::Mechanize;
-
-use constant BaseURL => $RT::WebURL;
-
-# reset preferences for easier test?
-
-my $t = RT::Ticket->new($RT::SystemUser);
-$t->Create(Subject => 'for custom search', Queue => 'general',
- Owner => 'root', Requestor => 'customsearch@localhost');
-ok(my $id = $t->id, 'created ticket for custom search');
-
-my $m = Test::WWW::Mechanize->new ( autocheck => 1 );
-isa_ok($m, 'Test::WWW::Mechanize');
-
-$m->get( BaseURL."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-
-my $t_link = $m->find_link( text => "for custom search" );
-like ($t_link->url, qr/$id/, 'link to the ticket we created');
-
-$m->content_lacks ('customsearch@localhost', 'requestor not displayed ');
-$m->get ( BaseURL.'Prefs/MyRT.html' );
-my $cus_hp = $m->find_link( text => "My Tickets" );
-my $cus_qs = $m->find_link( text => "Quick search" );
-$m->get ($cus_hp);
-$m->content_like (qr'highest priority tickets');
-
-# add Requestor to the fields
-$m->form_name ('BuildQuery');
-# can't use submit form for mutli-valued select as it uses set_fields
-$m->field (SelectDisplayColumns => ['Requestors']);
-$m->click_button (name => 'AddCol') ;
-
-$m->form_name ('BuildQuery');
-$m->click_button (name => 'Save');
-
-$m->get( BaseURL );
-$m->content_contains ('customsearch@localhost', 'requestor now displayed ');
-
-
-# now remove Requestor from the fields
-$m->get ($cus_hp);
-
-$m->form_name ('BuildQuery');
-$m->field (CurrentDisplayColumns => 'Requestors');
-$m->click_button (name => 'RemoveCol') ;
-
-$m->form_name ('BuildQuery');
-$m->click_button (name => 'Save');
-
-$m->get( BaseURL );
-$m->content_lacks ('customsearch@localhost', 'requestor not displayed ');
-
-
-# try to disable General from quick search
-
-# Note that there's a small problem in the current implementation,
-# since ticked quese are wanted, we do the invesrsion. So any
-# queue added during the quicksearch setting will be unticked.
-my $nlinks = $#{$m->find_all_links( text => "General" )};
-warn $nlinks;
-$m->get ($cus_qs);
-$m->form_name ('Preferences');
-$m->untick('Want-General', '1');
-$m->click_button (name => 'Save');
-
-$m->get( BaseURL );
-is ($#{$m->find_all_links( text => "General" )}, $nlinks - 1,
- 'General gone from quicksearch list');
-
-# get it back
-$m->get ($cus_qs);
-$m->form_name ('Preferences');
-$m->tick('Want-General', '1');
-$m->click_button (name => 'Save');
-
-$m->get( BaseURL );
-is ($#{$m->find_all_links( text => "General" )}, $nlinks,
- 'General back in quicksearch list');
diff --git a/rt/lib/t/regression/17multiple_deleg_revocation.t b/rt/lib/t/regression/17multiple_deleg_revocation.t
deleted file mode 100644
index 1ed0404..0000000
--- a/rt/lib/t/regression/17multiple_deleg_revocation.t
+++ /dev/null
@@ -1,135 +0,0 @@
-#!/usr/bin/perl -w
-
-use Test::More qw(no_plan);
-
-use RT;
-
-ok( RT::LoadConfig, "Locating config files" );
-ok( RT::Init, "Basic initialization and DB connectivity" );
-
-my ($u1, $g1, $pg1, $pg2, $ace, @groups, @users, @principals);
-@groups = (\$g1, \$pg1, \$pg2);
-@users = (\$u1);
-@principals = (@groups, @users);
-
-my($ret, $msg);
-
-$u1 = RT::User->new($RT::SystemUser);
-( $ret, $msg ) = $u1->LoadOrCreateByEmail('delegtest1@example.com');
-ok( $ret, "Load / Create test user 1: $msg" );
-$u1->SetPrivileged(1);
-
-$g1 = RT::Group->new($RT::SystemUser);
-( $ret, $msg) = $g1->LoadUserDefinedGroup('dg1');
-unless ($ret) {
- ( $ret, $msg ) = $g1->CreateUserDefinedGroup( Name => 'dg1' );
-}
-$pg1 = RT::Group->new($RT::SystemUser);
-( $ret, $msg ) = $pg1->LoadPersonalGroup( Name => 'dpg1',
- User => $u1->PrincipalId );
-unless ($ret) {
- ( $ret, $msg ) = $pg1->CreatePersonalGroup( Name => 'dpg1',
- PrincipalId => $u1->PrincipalId );
-}
-ok( $ret, "Load / Create test personal group 1: $msg" );
-$pg2 = RT::Group->new($RT::SystemUser);
-( $ret, $msg ) = $pg2->LoadPersonalGroup( Name => 'dpg2',
- User => $u1->PrincipalId );
-unless ($ret) {
- ( $ret, $msg ) = $pg2->CreatePersonalGroup( Name => 'dpg2',
- PrincipalId => $u1->PrincipalId );
-}
-ok( $ret, "Load / Create test personal group 2: $msg" );
-
-clear_acls_and_groups();
-
-( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
-ok( $ret, "Grant DelegateRights to u1: $msg" );
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
-ok( $ret, "Grant ShowConfigTab to g1: $msg" );
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'ShowConfigTab',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g1->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
-
-ok(( $pg1->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System ) and
- $pg2->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal groups have ShowConfigTab right after delegation" );
-
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete test user 1 from g1: $msg" );
-
-ok( not( $pg1->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal group 1 lacks ShowConfigTab after user removed from g1" );
-ok( not( $pg2->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal group 2 lacks ShowConfigTab after user removed from g1" );
-
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
-
-ok(( $pg1->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System ) and
- $pg2->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal groups have ShowConfigTab right after delegation" );
-
-( $ret, $msg ) = $g1->PrincipalObj->RevokeRight( Right => 'ShowConfigTab' );
-ok( $ret, "Revoke ShowConfigTab from g1: $msg" );
-
-ok( not( $pg1->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal group 1 lacks ShowConfigTab after user removed from g1" );
-ok( not( $pg2->PrincipalObj->HasRight( Right => 'ShowConfigTab',
- Object => $RT::System )),
- "Test personal group 2 lacks ShowConfigTab after user removed from g1" );
-
-
-
-#######
-
-sub clear_acls_and_groups {
- # Revoke all rights granted to our cast
- my $acl = RT::ACL->new($RT::SystemUser);
- foreach (@principals) {
- $acl->LimitToPrincipal(Type => $$_->PrincipalObj->PrincipalType,
- Id => $$_->PrincipalObj->Id);
- }
- while (my $ace = $acl->Next()) {
- $ace->Delete();
- }
-
- # Remove all group memberships
- my $members = RT::GroupMembers->new($RT::SystemUser);
- foreach (@groups) {
- $members->LimitToMembersOfGroup( $$_->PrincipalId );
- }
- while (my $member = $members->Next()) {
- $member->Delete();
- }
-
- $acl->RedoSearch();
- ok( $acl->Count() == 0,
- "All principals have no rights after clearing ACLs" );
- $members->RedoSearch();
- ok( $members->Count() == 0,
- "All groups have no members after clearing groups" );
-}
diff --git a/rt/lib/t/regression/18custom_frontpage.t b/rt/lib/t/regression/18custom_frontpage.t
deleted file mode 100644
index cf77e35..0000000
--- a/rt/lib/t/regression/18custom_frontpage.t
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Test::More tests => 7;
-BEGIN {
- use RT;
- RT::LoadConfig;
- RT::Init;
-}
-use Test::WWW::Mechanize;
-
-use constant BaseURL => $RT::WebURL;
-
-
-my $user_obj = RT::User->new($RT::SystemUser);
-my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
-ok($ret, 'ACL test user creation');
-$user_obj->SetName('customer');
-$user_obj->SetPrivileged(1);
-($ret, $msg) = $user_obj->SetPassword('customer');
-$user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
-$user_obj->PrincipalObj->GrantRight(Right => 'EditSavedSearch');
-$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
-$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
-
-my $m = Test::WWW::Mechanize->new ( autocheck => 1 );
-isa_ok($m, 'Test::WWW::Mechanize');
-
-$m->get( BaseURL."?user=customer;pass=customer" );
-
-$m->content_like(qr/Logout/, 'we did log in');
-
-$m->get ( BaseURL."Search/Build.html");
-
-#create a saved search
-$m->form_name ('BuildQuery');
-
-$m->field ( "ValueOfAttachment" => 'stupid');
-$m->field ( "Description" => 'stupid tickets');
-$m->click_button (name => 'Save');
-
-$m->get ( BaseURL.'Prefs/MyRT.html' );
-$m->content_like (qr/stupid tickets/, 'saved search listed in rt at a glance items');
-
-$m->follow_link (text => 'Logout');
-
-$m->get( BaseURL."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-
-$m->get ( BaseURL.'Prefs/MyRT.html' );
-$m->form_name ('SelectionBox-body');
-# can't use submit form for mutli-valued select as it uses set_fields
-$m->field ('body-Selected' => ['component-QuickCreate', 'system-Unowned Tickets', 'system-My Tickets']);
-$m->click_button (name => 'remove');
-$m->form_name ('SelectionBox-body');
-#$m->click_button (name => 'body-Save');
-$m->get ( BaseURL );
-$m->content_lacks ('highest priority tickets', 'remove everything from body pane');
-
-$m->get ( BaseURL.'Prefs/MyRT.html' );
-$m->form_name ('SelectionBox-body');
-$m->field ('body-Available' => ['component-QuickCreate', 'system-Unowned Tickets', 'system-My Tickets']);
-$m->click_button (name => 'add');
-
-$m->form_name ('SelectionBox-body');
-$m->field ('body-Selected' => ['component-QuickCreate']);
-$m->click_button (name => 'movedown');
-
-$m->form_name ('SelectionBox-body');
-$m->click_button (name => 'movedown');
-
-$m->form_name ('SelectionBox-body');
-#$m->click_button (name => 'body-Save');
-$m->get ( BaseURL );
-$m->content_like (qr'highest priority tickets', 'adds them back');
diff --git a/rt/lib/t/regression/18stale_delegations_cleanup.t b/rt/lib/t/regression/18stale_delegations_cleanup.t
deleted file mode 100644
index 84e666e..0000000
--- a/rt/lib/t/regression/18stale_delegations_cleanup.t
+++ /dev/null
@@ -1,458 +0,0 @@
-#!/usr/bin/perl -w
-
-# Regression test suite for http://rt3.fsck.com/Ticket/Display.html?id=6184
-# and related corner cases related to cleanup of delegated ACEs when
-# the delegator loses the right to delegate. This causes complexities
-# due to the fact that multiple ACEs can grant different delegation
-# rights to a principal, and because DelegateRights and SuperUser can
-# themselves be delegated.
-
-# The case where the "parent" delegated ACE is removed is handled in
-# the embedded regression tests in lib/RT/ACE_Overlay.pm .
-
-use Test::More qw(no_plan);
-
-use RT;
-
-ok( RT::LoadConfig, "Locating config files" );
-ok( RT::Init, "Basic initialization and DB connectivity" );
-
-my ($u1, $u2, $g1, $g2, $g3, $pg1, $pg2, $ace, @groups, @users, @principals);
-@groups = (\$g1, \$g2, \$g3, \$pg1, \$pg2);
-@users = (\$u1, \$u2);
-@principals = (@groups, @users);
-
-my($ret, $msg);
-
-$u1 = RT::User->new($RT::SystemUser);
-( $ret, $msg ) = $u1->LoadOrCreateByEmail('delegtest1@example.com');
-ok( $ret, "Load / Create test user 1: $msg" );
-$u1->SetPrivileged(1);
-$u2 = RT::User->new($RT::SystemUser);
-( $ret, $msg ) = $u2->LoadOrCreateByEmail('delegtest2@example.com');
-ok( $ret, "Load / Create test user 2: $msg" );
-$u2->SetPrivileged(1);
-$g1 = RT::Group->new($RT::SystemUser);
-( $ret, $msg) = $g1->LoadUserDefinedGroup('dg1');
-unless ($ret) {
- ( $ret, $msg ) = $g1->CreateUserDefinedGroup( Name => 'dg1' );
-}
-ok( $ret, "Load / Create test group 1: $msg" );
-$g2 = RT::Group->new($RT::SystemUser);
-( $ret, $msg) = $g2->LoadUserDefinedGroup('dg2');
-unless ($ret) {
- ( $ret, $msg ) = $g2->CreateUserDefinedGroup( Name => 'dg2' );
-}
-ok( $ret, "Load / Create test group 2: $msg" );
-$g3 = RT::Group->new($RT::SystemUser);
-( $ret, $msg) = $g3->LoadUserDefinedGroup('dg3');
-unless ($ret) {
- ( $ret, $msg ) = $g3->CreateUserDefinedGroup( Name => 'dg3' );
-}
-ok( $ret, "Load / Create test group 3: $msg" );
-$pg1 = RT::Group->new($RT::SystemUser);
-( $ret, $msg ) = $pg1->LoadPersonalGroup( Name => 'dpg1',
- User => $u1->PrincipalId );
-unless ($ret) {
- ( $ret, $msg ) = $pg1->CreatePersonalGroup( Name => 'dpg1',
- PrincipalId => $u1->PrincipalId );
-}
-ok( $ret, "Load / Create test personal group 1: $msg" );
-$pg2 = RT::Group->new($RT::SystemUser);
-( $ret, $msg ) = $pg2->LoadPersonalGroup( Name => 'dpg2',
- User => $u2->PrincipalId );
-unless ($ret) {
- ( $ret, $msg ) = $pg2->CreatePersonalGroup( Name => 'dpg2',
- PrincipalId => $u2->PrincipalId );
-}
-ok( $ret, "Load / Create test personal group 2: $msg" );
-
-
-
-# Basic case: u has global DelegateRights through g1 and ShowConfigTab
-# through g2; then u is removed from g1.
-
-clear_acls_and_groups();
-
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
-ok( $ret, "Grant DelegateRights to g1: $msg" );
-( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
-ok( $ret, "Grant ShowConfigTab to g2: $msg" );
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-ok(
- $u1->PrincipalObj->HasRight(
- Right => 'DelegateRights',
- Object => $RT::System
- ),
- "test user 1 has DelegateRights after joining g1"
-);
-( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g2: $msg" );
-ok(
- $u1->PrincipalObj->HasRight(
- Right => 'ShowConfigTab',
- Object => $RT::System
- ),
- "test user 1 has ShowConfigTab after joining g2"
-);
-
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'ShowConfigTab',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-ok(
- $pg1->PrincipalObj->HasRight(
- Right => 'ShowConfigTab',
- Object => $RT::System
- ),
- "Test personal group 1 has ShowConfigTab right after delegation"
-);
-
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete test user 1 from g1: $msg" );
-ok(
- not(
- $pg1->PrincipalObj->HasRight(
- Right => 'ShowConfigTab',
- Object => $RT::System
- )
- ),
- "Test personal group 1 lacks ShowConfigTab right after user removed from g1"
-);
-
-# Basic case: u has global DelegateRights through g1 and ShowConfigTab
-# through g2; then DelegateRights revoked from g1.
-
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-( $ret, $msg ) = $g1->PrincipalObj->RevokeRight( Right => 'DelegateRights' );
-ok( $ret, "Revoke DelegateRights from g1: $msg" );
-ok(
- not(
- $pg1->PrincipalObj->HasRight(
- Right => 'ShowConfigTab',
- Object => $RT::System
- )
- ),
- "Test personal group 1 lacks ShowConfigTab right after DelegateRights revoked from g1"
-);
-
-
-
-# Corner case - restricted delegation: u has DelegateRights on pg1
-# through g1 and AdminGroup on pg1 through g2; then DelegateRights
-# revoked from g1.
-
-clear_acls_and_groups();
-
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $pg1);
-ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
-( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'AdminGroup',
- Object => $pg1);
-ok( $ret, "Grant AdminGroup on pg1 to g2: $msg" );
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g2: $msg" );
-ok( $u1->PrincipalObj->HasRight(
- Right => 'DelegateRights',
- Object => $pg1 ),
- "test user 1 has DelegateRights on pg1 after joining g1" );
-ok( not( $u1->PrincipalObj->HasRight(
- Right => 'DelegateRights',
- Object => $RT::System )),
- "Test personal group 1 lacks global DelegateRights after joining g1" );
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'AdminGroup',
- Object => $pg1,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
-ok( $pg1->PrincipalObj->HasRight(
- Right => 'AdminGroup',
- Object => $pg1 ),
- "Test personal group 1 has AdminGroup right on pg1 after delegation" );
-( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights',
- Object => $pg1 );
-ok( $ret, "Revoke DelegateRights on pg1 from g1: $msg" );
-ok( not( $pg1->PrincipalObj->HasRight(
- Right => 'AdminGroup',
- Object => $pg1 )),
- "Test personal group 1 lacks AdminGroup right on pg1 after DelegateRights revoked from g1" );
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $pg1);
-
-# Corner case - restricted delegation: u has DelegateRights on pg1
-# through g1 and AdminGroup on pg1 through g2; then u removed from g1.
-
-ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
-ok( $pg1->PrincipalObj->HasRight(
- Right => 'AdminGroup',
- Object => $pg1 ),
- "Test personal group 1 has AdminGroup right on pg1 after delegation" );
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete test user 1 from g1: $msg" );
-ok( not( $pg1->PrincipalObj->HasRight(
- Right => 'AdminGroup',
- Object => $pg1 )),
- "Test personal group 1 lacks AdminGroup right on pg1 after user removed from g1" );
-
-clear_acls_and_groups();
-
-
-
-# Corner case - multiple delegation rights: u has global
-# DelegateRights directly and DelegateRights on pg1 through g1, and
-# AdminGroup on pg1 through g2; then u removed from g1 (delegation
-# should remain); then DelegateRights revoked from u (delegation
-# should not remain).
-
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $pg1);
-ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
-( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'AdminGroup',
- Object => $pg1);
-ok( $ret, "Grant AdminGroup on pg1 to g2: $msg" );
-( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $RT::System);
-ok( $ret, "Grant DelegateRights to user: $msg" );
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g2: $msg" );
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'AdminGroup',
- Object => $pg1,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete test user 1 from g1: $msg" );
-ok( $pg1->PrincipalObj->HasRight(Right => 'AdminGroup',
- Object => $pg1),
- "Test personal group 1 retains AdminGroup right on pg1 after user removed from g1" );
-( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
- Object => $RT::System );
-ok( not ($pg1->PrincipalObj->HasRight(Right => 'AdminGroup',
- Object => $pg1)),
- "Test personal group 1 lacks AdminGroup right on pg1 after DelegateRights revoked");
-
-# Corner case - multiple delegation rights and selectivity: u has
-# DelegateRights globally and on g2 directly and DelegateRights on pg1
-# through g1, and AdminGroup on pg1 through g2; then global
-# DelegateRights revoked from u (delegation should remain),
-# DelegateRights on g2 revoked from u (delegation should remain), and
-# u removed from g1 (delegation should not remain).
-
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $RT::System);
-ok( $ret, "Grant DelegateRights to user: $msg" );
-( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
- Object => $g2);
-ok( $ret, "Grant DelegateRights on g2 to user: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
-( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
- Object => $RT::System );
-ok( $pg1->PrincipalObj->HasRight(Right => 'AdminGroup',
- Object => $pg1),
- "Test personal group 1 retains AdminGroup right on pg1 after global DelegateRights revoked" );
-( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
- Object => $g2 );
-ok( $pg1->PrincipalObj->HasRight(Right => 'AdminGroup',
- Object => $pg1),
- "Test personal group 1 retains AdminGroup right on pg1 after DelegateRights on g2 revoked" );
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete test user 1 from g1: $msg" );
-ok( not ($pg1->PrincipalObj->HasRight(Right => 'AdminGroup',
- Object => $pg1)),
- "Test personal group 1 lacks AdminGroup right on pg1 after user removed from g1");
-
-
-
-# Corner case - indirect delegation rights: u has DelegateRights
-# through g1 via g3, and ShowConfigTab via g2; then g3 removed from
-# g1.
-
-clear_acls_and_groups();
-
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
-ok( $ret, "Grant DelegateRights to g1: $msg" );
-( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
-ok( $ret, "Grant ShowConfigTab to g2: $msg" );
-( $ret, $msg ) = $g1->AddMember( $g3->PrincipalId );
-ok( $ret, "Add g3 to g1: $msg" );
-( $ret, $msg ) = $g3->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g3: $msg" );
-( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g2: $msg" );
-
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'ShowConfigTab',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-
-( $ret, $msg ) = $g1->DeleteMember( $g3->PrincipalId );
-ok( $ret, "Delete g3 from g1: $msg" );
-ok( not ($pg1->PrincipalObj->HasRight(Right => 'ShowConfigTab',
- Object => $RT::System)),
- "Test personal group 1 lacks ShowConfigTab right after g3 removed from g1");
-
-# Corner case - indirect delegation rights: u has DelegateRights
-# through g1 via g3, and ShowConfigTab via g2; then DelegateRights
-# revoked from g1.
-
-( $ret, $msg ) = $g1->AddMember( $g3->PrincipalId );
-ok( $ret, "Add g3 to g1: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
-( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights' );
-ok( $ret, "Revoke DelegateRights from g1: $msg" );
-
-ok( not ($pg1->PrincipalObj->HasRight(Right => 'ShowConfigTab',
- Object => $RT::System)),
- "Test personal group 1 lacks ShowConfigTab right after DelegateRights revoked from g1");
-
-
-
-# Corner case - delegation of DelegateRights: u1 has DelegateRights
-# via g1 and delegates DelegateRights to pg1; u2 has DelegateRights
-# via pg1 and ShowConfigTab via g2; then u1 removed from g1.
-
-clear_acls_and_groups();
-
-( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
-ok( $ret, "Grant DelegateRights to g1: $msg" );
-( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
-ok( $ret, "Grant ShowConfigTab to g2: $msg" );
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add test user 1 to g1: $msg" );
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'DelegateRights',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g1->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate DelegateRights to pg1: $msg" );
-
-( $ret, $msg ) = $pg1->AddMember( $u2->PrincipalId );
-ok( $ret, "Add test user 2 to pg1: $msg" );
-( $ret, $msg ) = $g2->AddMember( $u2->PrincipalId );
-ok( $ret, "Add test user 2 to g2: $msg" );
-$ace = RT::ACE->new($u2);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'ShowConfigTab',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
-
-ok( $pg2->PrincipalObj->HasRight(Right => 'ShowConfigTab',
- Object => $RT::System),
- "Test personal group 2 has ShowConfigTab right after delegation");
-( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
-ok( $ret, "Delete u1 from g1: $msg" );
-ok( not ($pg2->PrincipalObj->HasRight(Right => 'ShowConfigTab',
- Object => $RT::System)),
- "Test personal group 2 lacks ShowConfigTab right after u1 removed from g1");
-
-# Corner case - delegation of DelegateRights: u1 has DelegateRights
-# via g1 and delegates DelegateRights to pg1; u2 has DelegateRights
-# via pg1 and ShowConfigTab via g2; then DelegateRights revoked from
-# g1.
-
-( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
-ok( $ret, "Add u1 to g1: $msg" );
-$ace = RT::ACE->new($u1);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'DelegateRights',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g1->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
-ok( $ret, "Delegate DelegateRights to pg1: $msg" );
-$ace = RT::ACE->new($u2);
-( $ret, $msg ) = $ace->LoadByValues(
- RightName => 'ShowConfigTab',
- Object => $RT::System,
- PrincipalType => 'Group',
- PrincipalId => $g2->PrincipalId
-);
-ok( $ret, "Look up ACE to be delegated: $msg" );
-( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
-ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
-
-( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights' );
-ok( $ret, "Revoke DelegateRights from g1: $msg" );
-ok( not ($pg2->PrincipalObj->HasRight(Right => 'ShowConfigTab',
- Object => $RT::System)),
- "Test personal group 2 lacks ShowConfigTab right after DelegateRights revoked from g1");
-
-
-
-
-#######
-
-sub clear_acls_and_groups {
- # Revoke all rights granted to our cast
- my $acl = RT::ACL->new($RT::SystemUser);
- foreach (@principals) {
- $acl->LimitToPrincipal(Type => $$_->PrincipalObj->PrincipalType,
- Id => $$_->PrincipalObj->Id);
- }
- while (my $ace = $acl->Next()) {
- $ace->Delete();
- }
-
- # Remove all group memberships
- my $members = RT::GroupMembers->new($RT::SystemUser);
- foreach (@groups) {
- $members->LimitToMembersOfGroup( $$_->PrincipalId );
- }
- while (my $member = $members->Next()) {
- $member->Delete();
- }
-
- $acl->RedoSearch();
- ok( $acl->Count() == 0,
- "All principals have no rights after clearing ACLs" );
- $members->RedoSearch();
- ok( $members->Count() == 0,
- "All groups have no members after clearing groups" );
-}
diff --git a/rt/lib/t/regression/19-rtname.t b/rt/lib/t/regression/19-rtname.t
deleted file mode 100644
index b654df2..0000000
--- a/rt/lib/t/regression/19-rtname.t
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use Test::More qw/no_plan/;
-
-use_ok("RT");
-
-RT::LoadConfig();
-RT::Init();
-
-use RT::Interface::Email;
-
-# normal use case, regexp set to rtname
-$RT::rtname = "site";
-$RT::EmailSubjectTagRegex = qr/$RT::rtname/ ;
-$RT::rtname = undef;
-is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123);
-is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef);
-
-# oops usecase, where the regexp is scragged
-$RT::rtname = "site";
-$RT::EmailSubjectTagRegex = undef;
-is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123);
-is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef);
-
-# set to a simple regexp. NOTE: we no longer match "site"
-$RT::rtname = "site";
-$RT::EmailSubjectTagRegex = qr/newsite/;
-is(RT::Interface::Email::ParseTicketId("[site #123] test"), undef);
-is(RT::Interface::Email::ParseTicketId("[newsite #123] test"), 123);
-
-# set to a more complex regexp
-$RT::rtname = "site";
-$RT::EmailSubjectTagRegex = qr/newsite||site/;
-is(RT::Interface::Email::ParseTicketId("[site #123] test"), 123);
-is(RT::Interface::Email::ParseTicketId("[newsite #123] test"), 123);
-is(RT::Interface::Email::ParseTicketId("[othersite #123] test"), undef);
-
diff --git a/rt/lib/t/regression/19quicksearch.t b/rt/lib/t/regression/19quicksearch.t
deleted file mode 100644
index 7744787..0000000
--- a/rt/lib/t/regression/19quicksearch.t
+++ /dev/null
@@ -1,39 +0,0 @@
-
-#!/usr/bin/perl -w
-
-use strict;
-use warnings;
-
-use Test::More qw/no_plan/;
-use_ok('RT');
-RT::LoadConfig();
-RT::Init();
-
-my $q = RT::Queue->new($RT::SystemUser);
-my $queue = 'SearchTests-'.$$;
-$q->Create(Name => $queue);
-ok ($q->id, "Created the queue");
-
-my $t1 = RT::Ticket->new($RT::SystemUser);
-my ( $id, undef, $msg ) = $t1->Create(
- Queue => $q->id,
- Subject => 'SearchTest1',
- Requestor => ['search2@example.com'],
-);
-ok( $id, $msg );
-
-use_ok("RT::Search::Googleish");
-my $tickets = RT::Tickets->new($RT::SystemUser);
-my $quick = RT::Search::Googleish->new(Argument => "",
- TicketsObj => $tickets);
-my @tests = (
- "General new open root" => "( Owner = 'root' ) AND ( Queue = 'General' ) AND ( Status = 'new' OR Status = 'open' )",
- "fulltext:jesse" => "( Content LIKE 'jesse' )",
- $queue => "( Queue = '$queue' )",
- "root $queue" => "( Owner = 'root' ) AND ( Queue = '$queue' )",
- "notauser $queue" => "( Queue = '$queue' ) AND ( Subject LIKE 'notauser' )",
- "notauser $queue root" => "( Owner = 'root' ) AND ( Queue = '$queue' ) AND ( Subject LIKE 'notauser' )");
-
-while (my ($from, $to) = splice @tests, 0, 2) {
- is($quick->QueryToSQL($from), $to, "<$from> -> <$to>");
-}
diff --git a/rt/lib/t/regression/20-sort-by-queue.t b/rt/lib/t/regression/20-sort-by-queue.t
deleted file mode 100644
index 16eabe9..0000000
--- a/rt/lib/t/regression/20-sort-by-queue.t
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More tests => 8;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-#########################################################
-# Test sorting by Queue, we sort by its name
-#########################################################
-
-
-diag "Create queues to test with.";
-my @qids;
-my @queues;
-# create them in reverse order to avoid false positives
-foreach my $name ( qw(sort-by-queue-Z sort-by-queue-A) ) {
- my $queue = RT::Queue->new( $RT::SystemUser );
- my ($ret, $msg) = $queue->Create(
- Name => $name ."-$$",
- Description => 'queue to test sorting by queue'
- );
- ok($ret, "test queue creation. $msg");
- push @queues, $queue;
- push @qids, $queue->id;
-}
-
-my ($total, @data, @tickets, @test) = (0, ());
-
-sub add_tix_from_data {
- my @res = ();
- @data = sort { rand(100) <=> rand(100) } @data;
- while (@data) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my %args = %{ shift(@data) };
- my ( $id, undef, $msg ) = $t->Create( %args );
- ok( $id, "ticket created" ) or diag("error: $msg");
- push @res, $t;
- $total++;
- }
- return @res;
-}
-
-sub run_tests {
- my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
- foreach my $test ( @test ) {
- my $query = join " AND ", map "( $_ )", grep defined && length,
- $query_prefix, $test->{'Query'};
-
- foreach my $order (qw(ASC DESC)) {
- my $error = 0;
- my $tix = RT::Tickets->new( $RT::SystemUser );
- $tix->FromSQL( $query );
- $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order );
-
- ok($tix->Count, "found ticket(s)")
- or $error = 1;
-
- my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz');
- while ( my $t = $tix->Next ) {
- my $tmp;
- if ( $order eq 'ASC' ) {
- $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]);
- } else {
- $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]);
- }
- if ( $tmp > 0 ) {
- $order_ok = 0; last;
- }
- $last = $t->Subject;
- }
-
- ok( $order_ok, "$order order of tickets is good" )
- or $error = 1;
-
- if ( $error ) {
- diag "Wrong SQL query:". $tix->BuildSelectQuery;
- $tix->GotoFirstItem;
- while ( my $t = $tix->Next ) {
- diag sprintf "%02d - %s", $t->id, $t->Subject;
- }
- }
- }
- }
-}
-
-@data = (
- { Queue => $qids[0], Subject => 'z' },
- { Queue => $qids[1], Subject => 'a' },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "Queue" },
-);
-run_tests();
-
diff --git a/rt/lib/t/regression/20-sort-by-requestor.t b/rt/lib/t/regression/20-sort-by-requestor.t
deleted file mode 100644
index e6903b4..0000000
--- a/rt/lib/t/regression/20-sort-by-requestor.t
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/perl -w
-use strict; use warnings;
-
-use Test::More qw/no_plan/;
-use_ok('RT');
-RT::LoadConfig();
-RT::Init();
-use RT::Ticket;
-
-my $q = RT::Queue->new($RT::SystemUser);
-my $queue = 'SearchTests-'.rand(200);
-$q->Create(Name => $queue);
-
-my @requestors = ( ('bravo@example.com') x 6, ('alpha@example.com') x 6,
- ('delta@example.com') x 6, ('charlie@example.com') x 6,
- (undef) x 6);
-my @subjects = ("first test", "second test", "third test", "fourth test", "fifth test") x 6;
-while (@requestors) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my ( $id, undef $msg ) = $t->Create(
- Queue => $q->id,
- Subject => shift @subjects,
- Requestor => [ shift @requestors ]
- );
- ok( $id, $msg );
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue'");
- is($tix->Count, 30, "found thirty tickets");
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND requestor = 'alpha\@example.com'");
- $tix->OrderByCols({ FIELD => "Subject" });
- my @subjects;
- while (my $t = $tix->Next) { push @subjects, $t->Subject; }
- is(@subjects, 6, "found six tickets");
- is_deeply( \@subjects, [ sort @subjects ], "Subjects are sorted");
-}
-
-sub check_emails_order
-{
- my ($tix,$count,$order) = (@_);
- my @mails;
- while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; }
- is(@mails, $count, "found $count tickets for ". $tix->Query);
- my @required_order;
- if( $order =~ /asc/i ) {
- @required_order = sort { $a? ($b? ($a cmp $b) : -1) : 1} @mails;
- } else {
- @required_order = sort { $a? ($b? ($b cmp $a) : -1) : 1} @mails;
- }
- foreach( reverse splice @mails ) {
- if( $_ ) { unshift @mails, $_ }
- else { push @mails, $_ }
- }
- is_deeply( \@mails, \@required_order, "Addresses are sorted");
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND subject = 'first test' AND Requestor.EmailAddress LIKE 'example.com'");
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- check_emails_order($tix, 5, 'ASC');
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' });
- check_emails_order($tix, 5, 'DESC');
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'");
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- check_emails_order($tix, 6, 'ASC');
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' });
- check_emails_order($tix, 6, 'DESC');
-}
-
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'");
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- check_emails_order($tix, 6, 'ASC');
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' });
- check_emails_order($tix, 6, 'DESC');
-}
-
-{
- # create ticket with group as member of the requestors group
- my $t = RT::Ticket->new($RT::SystemUser);
- my ( $id, $msg ) = $t->Create(
- Queue => $q->id,
- Subject => "first test",
- Requestor => 'badaboom@example.com',
- );
- ok( $id, "ticket created" ) or diag( "error: $msg" );
-
- my $g = RT::Group->new($RT::SystemUser);
-
- my ($gid);
- ($gid, $msg) = $g->CreateUserDefinedGroup(Name => '20-sort-by-requestor.t-'.rand(200));
- ok($gid, "created group") or diag("error: $msg");
-
- ($id, $msg) = $t->Requestors->AddMember( $gid );
- ok($id, "added group to requestors group") or diag("error: $msg");
-}
-
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Subject = 'first test'");
-TODO: {
- local $TODO = "if group has non users members we get wrong order";
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- check_emails_order($tix, 7, 'ASC');
-}
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress", ORDER => 'DESC' });
- check_emails_order($tix, 7, 'DESC');
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue'");
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- $tix->RowsPerPage(30);
- my @mails;
- while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; }
- is(@mails, 30, "found thirty tickets");
- is_deeply( [grep {$_} @mails], [ sort grep {$_} @mails ], "Paging works (exclude nulls, which are db-dependant)");
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue'");
- $tix->OrderByCols({ FIELD => "Requestor.EmailAddress" });
- $tix->RowsPerPage(30);
- my @mails;
- while (my $t = $tix->Next) { push @mails, $t->RequestorAddresses; }
- is(@mails, 30, "found thirty tickets");
- is_deeply( [grep {$_} @mails], [ sort grep {$_} @mails ], "Paging works (exclude nulls, which are db-dependant)");
-}
-
-# vim:ft=perl:
diff --git a/rt/lib/t/regression/20-sort-by-user.t b/rt/lib/t/regression/20-sort-by-user.t
deleted file mode 100644
index 04bf2e7..0000000
--- a/rt/lib/t/regression/20-sort-by-user.t
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More tests => 32;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-#########################################################
-# Test sorting by Owner, Creator and LastUpdatedBy
-# we sort by user name
-#########################################################
-
-diag "Create a queue to test with.";
-my $queue_name = "OwnerSortQueue$$";
-my $queue;
-{
- $queue = RT::Queue->new( $RT::SystemUser );
- my ($ret, $msg) = $queue->Create(
- Name => $queue,
- Description => 'queue for custom field sort testing'
- );
- ok($ret, "$queue test queue creation. $msg");
-}
-
-my @uids;
-my @users;
-# create them in reverse order to avoid false positives
-foreach my $u (qw(Z A)) {
- my $name = $u ."-user-to-test-ordering-$$";
- my $user = RT::User->new( $RT::SystemUser );
- my ($uid) = $user->Create(
- Name => $name,
- Privileged => 1,
- );
- ok $uid, "created user #$uid";
-
- my ($status, $msg) = $user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $queue );
- ok $status, "granted right";
- ($status, $msg) = $user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $queue );
- ok $status, "granted right";
-
- push @users, $user;
- push @uids, $user->id;
-}
-
-my ($total, @data, @tickets, @test) = (0, ());
-
-sub add_tix_from_data {
- my @res = ();
- @data = sort { rand(100) <=> rand(100) } @data;
- while (@data) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my %args = %{ shift(@data) };
-
- my ( $id, undef, $msg ) = $t->Create( %args, Queue => $queue->id );
- if ( $args{'Owner'} ) {
- is $t->Owner, $args{'Owner'}, "owner is correct";
- }
- if ( $args{'Creator'} ) {
- is $t->Creator, $args{'Creator'}, "creator is correct";
- }
- # hackish, but simpler
- if ( $args{'LastUpdatedBy'} ) {
- $t->__Set( Field => 'LastUpdatedBy', Value => $args{'LastUpdatedBy'} );
- }
- ok( $id, "ticket created" ) or diag("error: $msg");
- push @res, $t;
- $total++;
- }
- return @res;
-}
-
-sub run_tests {
- my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
- foreach my $test ( @test ) {
- my $query = join " AND ", map "( $_ )", grep defined && length,
- $query_prefix, $test->{'Query'};
-
- foreach my $order (qw(ASC DESC)) {
- my $error = 0;
- my $tix = RT::Tickets->new( $RT::SystemUser );
- $tix->FromSQL( $query );
- $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order );
-
- ok($tix->Count, "found ticket(s)")
- or $error = 1;
-
- my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz');
- while ( my $t = $tix->Next ) {
- my $tmp;
- if ( $order eq 'ASC' ) {
- $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]);
- } else {
- $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]);
- }
- if ( $tmp > 0 ) {
- $order_ok = 0; last;
- }
- $last = $t->Subject;
- }
-
- ok( $order_ok, "$order order of tickets is good" )
- or $error = 1;
-
- if ( $error ) {
- diag "Wrong SQL query:". $tix->BuildSelectQuery;
- $tix->GotoFirstItem;
- while ( my $t = $tix->Next ) {
- diag sprintf "%02d - %s", $t->id, $t->Subject;
- }
- }
- }
- }
-}
-
-@data = (
- { Subject => 'Nobody' },
- { Subject => 'Z', Owner => $uids[0] },
- { Subject => 'A', Owner => $uids[1] },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "Owner" },
-);
-run_tests();
-
-@data = (
- { Subject => 'RT' },
- { Subject => 'Z', Creator => $uids[0] },
- { Subject => 'A', Creator => $uids[1] },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "Creator" },
-);
-run_tests();
-
-@data = (
- { Subject => 'RT' },
- { Subject => 'Z', LastUpdatedBy => $uids[0] },
- { Subject => 'A', LastUpdatedBy => $uids[1] },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "LastUpdatedBy" },
-);
-run_tests();
-
diff --git a/rt/lib/t/regression/20savedsearch.t b/rt/lib/t/regression/20savedsearch.t
deleted file mode 100644
index f4439f9..0000000
--- a/rt/lib/t/regression/20savedsearch.t
+++ /dev/null
@@ -1,180 +0,0 @@
-use RT;
-use Test::More tests => 26;
-use RT::User;
-use RT::Group;
-use RT::Ticket;
-use RT::Queue;
-
-use_ok(RT::SavedSearch);
-use_ok(RT::SavedSearches);
-
-RT::LoadConfig();
-RT::Init();
-
-# Set up some infrastructure. These calls are tested elsewhere.
-
-my $searchuser = RT::User->new($RT::SystemUser);
-my ($ret, $msg) = $searchuser->Create(Name => 'searchuser'.$$,
- Privileged => 1,
- EmailAddress => "searchuser\@p$$.example.com",
- RealName => 'Search user');
-ok($ret, "created searchuser: $msg");
-$searchuser->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
-$searchuser->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
-$searchuser->PrincipalObj->GrantRight(Right => 'ModifySelf');
-
-# This is the group whose searches searchuser should be able to see.
-my $ingroup = RT::Group->new($RT::SystemUser);
-$ingroup->CreateUserDefinedGroup(Name => 'searchgroup1'.$$);
-$ingroup->AddMember($searchuser->Id);
-$searchuser->PrincipalObj->GrantRight(Right => 'EditSavedSearches',
- Object => $ingroup);
-$searchuser->PrincipalObj->GrantRight(Right => 'ShowSavedSearches',
- Object => $ingroup);
-
-# This is the group whose searches searchuser should not be able to see.
-my $outgroup = RT::Group->new($RT::SystemUser);
-$outgroup->CreateUserDefinedGroup(Name => 'searchgroup2'.$$);
-$outgroup->AddMember($RT::SystemUser->Id);
-
-my $queue = RT::Queue->new($RT::SystemUser);
-$queue->Create(Name => 'SearchQueue'.$$);
-$searchuser->PrincipalObj->GrantRight(Right => 'SeeQueue', Object => $queue);
-$searchuser->PrincipalObj->GrantRight(Right => 'ShowTicket', Object => $queue);
-$searchuser->PrincipalObj->GrantRight(Right => 'OwnTicket', Object => $queue);
-
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-$ticket->Create(Queue => $queue->Id,
- Requestor => [ $searchuser->Name ],
- Owner => $searchuser,
- Subject => 'saved search test');
-
-
-# Now start the search madness.
-my $curruser = RT::CurrentUser->new($searchuser);
-my $format = '\' <b><a href="/Ticket/Display.html?id=__id__">__id__</a></b>/TITLE:#\',
-\'<b><a href="/Ticket/Display.html?id=__id__">__Subject__</a></b>/TITLE:Subject\',
-\'__Status__\',
-\'__QueueName__\',
-\'__OwnerName__\',
-\'__Priority__\',
-\'__NEWLINE__\',
-\'\',
-\'<small>__Requestors__</small>\',
-\'<small>__CreatedRelative__</small>\',
-\'<small>__ToldRelative__</small>\',
-\'<small>__LastUpdatedRelative__</small>\',
-\'<small>__TimeLeft__</small>\'';
-
-my ($ret, $msg);
-my $mysearch = RT::SavedSearch->new($curruser);
-($ret, $msg) = $mysearch->Save(Privacy => 'RT::User-' . $searchuser->Id,
- Type => 'Ticket',
- Name => 'owned by me',
- SearchParams => {'Format' => $format,
- 'Query' => "Owner = '"
- . $searchuser->Name
- . "'"});
-ok($ret, "mysearch was created");
-
-
-my $groupsearch = RT::SavedSearch->new($curruser);
-($ret, $msg) = $groupsearch->Save(Privacy => 'RT::Group-' . $ingroup->Id,
- Type => 'Ticket',
- Name => 'search queue',
- SearchParams => {'Format' => $format,
- 'Query' => "Queue = '"
- . $queue->Name . "'"});
-ok($ret, "groupsearch was created");
-
-my $othersearch = RT::SavedSearch->new($curruser);
-($ret, $msg) = $othersearch->Save(Privacy => 'RT::Group-' . $outgroup->Id,
- Type => 'Ticket',
- Name => 'searchuser requested',
- SearchParams => {'Format' => $format,
- 'Query' =>
- "Requestor.Name LIKE 'search'"});
-ok(!$ret, "othersearch NOT created");
-like($msg, qr/Failed to load object for/, "...for the right reason");
-
-$othersearch = RT::SavedSearch->new($RT::SystemUser);
-($ret, $msg) = $othersearch->Save(Privacy => 'RT::Group-' . $outgroup->Id,
- Type => 'Ticket',
- Name => 'searchuser requested',
- SearchParams => {'Format' => $format,
- 'Query' =>
- "Requestor.Name LIKE 'search'"});
-ok($ret, "othersearch created by systemuser");
-
-# Now try to load some searches.
-
-# This should work.
-my $loadedsearch1 = RT::SavedSearch->new($curruser);
-$loadedsearch1->Load('RT::User-'.$curruser->Id, $mysearch->Id);
-is($loadedsearch1->Id, $mysearch->Id, "Loaded mysearch");
-like($loadedsearch1->GetParameter('Query'), qr/Owner/,
- "Retrieved query of mysearch");
-# Check through the other accessor methods.
-is($loadedsearch1->Privacy, 'RT::User-' . $curruser->Id,
- "Privacy of mysearch correct");
-is($loadedsearch1->Name, 'owned by me', "Name of mysearch correct");
-is($loadedsearch1->Type, 'Ticket', "Type of mysearch correct");
-
-# See if it can be used to search for tickets.
-my $tickets = RT::Tickets->new($curruser);
-$tickets->FromSQL($loadedsearch1->GetParameter('Query'));
-is($tickets->Count, 1, "Found a ticket");
-
-# This should fail -- wrong object.
-# my $loadedsearch2 = RT::SavedSearch->new($curruser);
-# $loadedsearch2->Load('RT::User-'.$curruser->Id, $groupsearch->Id);
-# isnt($loadedsearch2->Id, $othersearch->Id, "Didn't load groupsearch as mine");
-# ...but this should succeed.
-my $loadedsearch3 = RT::SavedSearch->new($curruser);
-$loadedsearch3->Load('RT::Group-'.$ingroup->Id, $groupsearch->Id);
-is($loadedsearch3->Id, $groupsearch->Id, "Loaded groupsearch");
-like($loadedsearch3->GetParameter('Query'), qr/Queue/,
- "Retrieved query of groupsearch");
-# Can it get tickets?
-$tickets = RT::Tickets->new($curruser);
-$tickets->FromSQL($loadedsearch3->GetParameter('Query'));
-is($tickets->Count, 1, "Found a ticket");
-
-# This should fail -- no permission.
-my $loadedsearch4 = RT::SavedSearch->new($curruser);
-$loadedsearch4->Load($othersearch->Privacy, $othersearch->Id);
-isnt($loadedsearch4->Id, $othersearch->Id, "Did not load othersearch");
-
-# Try to update an existing search.
-$loadedsearch1->Update( SearchParams => {'Format' => $format,
- 'Query' => "Queue = '" . $queue->Name . "'" } );
-like($loadedsearch1->GetParameter('Query'), qr/Queue/,
- "Updated mysearch parameter");
-is($loadedsearch1->Type, 'Ticket', "mysearch is still for tickets");
-is($loadedsearch1->Privacy, 'RT::User-'.$curruser->Id,
- "mysearch still belongs to searchuser");
-like($mysearch->GetParameter('Query'), qr/Queue/, "other mysearch object updated");
-
-
-## Right ho. Test the pseudo-collection object.
-
-my $genericsearch = RT::SavedSearch->new($curruser);
-$genericsearch->Save(Name => 'generic search',
- Type => 'all',
- SearchParams => {'Query' => "Queue = 'General'"});
-
-my $ticketsearches = RT::SavedSearches->new($curruser);
-$ticketsearches->LimitToPrivacy('RT::User-'.$curruser->Id, 'Ticket');
-is($ticketsearches->Count, 1, "Found searchuser's ticket searches");
-
-my $allsearches = RT::SavedSearches->new($curruser);
-$allsearches->LimitToPrivacy('RT::User-'.$curruser->Id);
-is($allsearches->Count, 2, "Found all searchuser's searches");
-
-# Delete a search.
-($ret, $msg) = $genericsearch->Delete;
-ok($ret, "Deleted genericsearch");
-$allsearches->LimitToPrivacy('RT::User-'.$curruser->Id);
-is($allsearches->Count, 1, "Found all searchuser's searches after deletion");
-
diff --git a/rt/lib/t/regression/21query-builder.t b/rt/lib/t/regression/21query-builder.t
deleted file mode 100644
index a0cecb2..0000000
--- a/rt/lib/t/regression/21query-builder.t
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Test::More tests => 39;
-use Test::WWW::Mechanize;
-use HTTP::Request::Common;
-use HTTP::Cookies;
-use LWP;
-use Encode;
-
-my $cookie_jar = HTTP::Cookies->new;
-my $agent = Test::WWW::Mechanize->new();
-
-# give the agent a place to stash the cookies
-
-$agent->cookie_jar($cookie_jar);
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-# create a regression queue if it doesn't exist
-{
- my $queue = RT::Queue->new( $RT::SystemUser );
- $queue->Load( 'Regression' );
- if ( $queue->id ) {
- ok(1, "queue 'Regression' exists - #". $queue->id );
- } else {
- $queue->Create( Name => 'Regression' );
- ok($queue->id, "created queue 'Regression'");
- }
-}
-
-# get the top page
-my $url = $RT::WebURL;
-$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");
-
-# }}}
-
-# {{{ Query Builder tests
-
-my $response = $agent->get($url."Search/Build.html");
-ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
-
-# Adding items
-
-# set the first value
-ok($agent->form_name('BuildQuery'), "found the form once");
-$agent->field("ActorField", "Owner");
-$agent->field("ActorOp", "=");
-$agent->field("ValueOfActor", "Nobody");
-$agent->submit();
-
-# set the next value
-ok($agent->form_name('BuildQuery'), "found the form again");
-$agent->field("QueueOp", "!=");
-$agent->field("ValueOfQueue", "Regression");
-$agent->submit();
-
-ok($agent->form_name('BuildQuery'), "found the form a third time");
-
-sub getQueryFromForm {
- $agent->form_name('BuildQuery');
- # This pulls out the "hidden input" query from the page
- my $q = $agent->current_form->find_input("Query")->value;
- $q =~ s/^\s+//g;
- $q =~ s/\s+$//g;
- $q =~ s/\s+/ /g;
- return $q;
-}
-
-is (getQueryFromForm, "Owner = 'Nobody' AND Queue != 'Regression'");
-
-# We're going to delete the owner
-
-$agent->select("clauses", ["0"] );
-
-$agent->click("DeleteClause");
-
-ok($agent->form_name('BuildQuery'), "found the form a fourth time");
-
-is (getQueryFromForm, "Queue != 'Regression'");
-
-$agent->field("AndOr", "OR");
-
-$agent->select("idOp", ">");
-
-$agent->field("ValueOfid" => "1234");
-
-$agent->click("AddClause");
-
-ok($agent->form_name('BuildQuery'), "found the form again");
-TODO: {
- local $TODO = "query builder incorrectly quotes numbers";
- is(getQueryFromForm, "Queue != 'Regression' OR id > 1234", "added something as OR, and number not quoted");
-}
-
-sub selectedClauses {
- my @clauses = grep { defined } map { $_->value } $agent->current_form->find_input("clauses");
- return [ @clauses ];
-}
-
-
-is_deeply(selectedClauses, ["1"], 'the id that we just entered is still selected');
-
-# Move the second one up a level
-$agent->click("Up");
-
-ok($agent->form_name('BuildQuery'), "found the form again");
-is(getQueryFromForm, "id > 1234 OR Queue != 'Regression'", "moved up one");
-
-is_deeply(selectedClauses, ["0"], 'the one we moved up is selected');
-
-$agent->click("Right");
-
-ok($agent->form_name('BuildQuery'), "found the form again");
-is(getQueryFromForm, "Queue != 'Regression' OR ( id > 1234 )", "moved over to the right (and down)");
-is_deeply(selectedClauses, ["2"], 'the one we moved right is selected');
-
-$agent->select("clauses", ["1"]);
-
-$agent->click("Up");
-
-ok($agent->form_name('BuildQuery'), "found the form again");
-is(getQueryFromForm, "( id > 1234 ) OR Queue != 'Regression'", "moved up");
-
-$agent->select("clauses", ["0"]); # this is a null clause
-$agent->click("Up");
-ok($agent->form_name('BuildQuery'), "found the form again");
-$agent->content_like(qr/error: can\S+t move up/, "i shouldn't have been able to hit up");
-
-$agent->click("Left");
-ok($agent->form_name('BuildQuery'), "found the form again");
-$agent->content_like(qr/error: can\S+t move left/, "i shouldn't have been able to hit left");
-
-$agent->select("clauses", ["1"]);
-$agent->select("ValueOfStatus" => "stalled");
-$agent->submit;
-ok($agent->form_name('BuildQuery'), "found the form again");
-is_deeply(selectedClauses, ["2"], 'the one we added is selected');
-is( getQueryFromForm, "( id > 1234 AND Status = 'stalled' ) OR Queue != 'Regression'", "added new one" );
-
-# click advanced, enter "C1 OR ( C2 AND C3 )", apply, aggregators should stay the same.
-{
- my $response = $agent->get($url."Search/Edit.html");
- ok( $response->is_success, "Fetched /Search/Edit.html" );
- ok($agent->form_number(3), "found the form");
- $agent->field("Query", "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )");
- $agent->submit;
- is( getQueryFromForm,
- "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )",
- "no aggregators change"
- );
-}
-
-# - new items go one level down
-# - add items at currently selected level
-# - if nothing is selected, add at end, one level down
-#
-# move left
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move left if you're at the top level
-#
-# move right
-# - error if nothing selected
-# - same item should be selected after move
-# - can always move right (no max depth...should there be?)
-#
-# move up
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move up if you're first in the list
-#
-# move down
-# - error if nothing selected
-# - same item should be selected after move
-# - can't move down if you're last in the list
-#
-# toggle
-# - error if nothing selected
-# - change all aggregators in the grouping
-# - don't change any others
-#
-# delete
-# - error if nothing selected
-# - delete currently selected item
-# - delete all children of a grouping
-# - if delete leaves a node with no children, delete that, too
-# - what should be selected?
-#
-# Clear
-# - clears entire query
-# - clears it from the session, too
-
-# }}}
-
-# create a custom field with nonascii name and try to add a condition
-{
- my $cf = RT::CustomField->new( $RT::SystemUser );
- $cf->LoadByName( Name => "\x{442}", Queue => 0 );
- if ( $cf->id ) {
- is($cf->Type, 'Freeform', 'loaded and type is correct');
- } else {
- my ($return, $msg) = $cf->Create(
- Name => "\x{442}",
- Queue => 0,
- Type => 'Freeform',
- );
- ok($return, 'created CF') or diag "error: $msg";
- }
-
- my $response = $agent->get($url."Search/Build.html?NewQuery=1");
- ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
-
- ok($agent->form_name('BuildQuery'), "found the form once");
- $agent->field("ValueOf'CF.{\321\202}'", "\321\201");
- $agent->submit();
- is( getQueryFromForm,
- "'CF.{\321\202}' LIKE '\321\201'",
- "no changes, no duplicate condition with badly encoded text"
- );
-
- $cf->delete();
-}
-
-1;
diff --git a/rt/lib/t/regression/22search_tix_by_txn.t b/rt/lib/t/regression/22search_tix_by_txn.t
deleted file mode 100644
index bec61b5..0000000
--- a/rt/lib/t/regression/22search_tix_by_txn.t
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/perl
-
-use warnings;
-use strict;
-
-use Test::More tests => 10;
-
-BEGIN{ $ENV{'TZ'} = 'GMT'};
-
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-my $SUBJECT = "Search test - ".$$;
-
-use_ok('RT::Tickets');
-my $tix = RT::Tickets->new($RT::SystemUser);
-can_ok($tix, 'FromSQL');
-$tix->FromSQL('Updated = "2005-08-05" AND Subject = "$SUBJECT"');
-
-ok(! $tix->Count, "Searching for tickets updated on a random date finds nothing" . $tix->Count);
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-$ticket->Create(Queue => 'General', Subject => $SUBJECT);
-ok ($ticket->id, "We created a ticket");
-my ($id, $txnid, $txnobj) = $ticket->Comment( Content => 'A comment that happend on 2004-01-01');
-
-isa_ok($txnobj, 'RT::Transaction');
-
-ok($txnobj->CreatedObj->ISO);
-my ( $sid,$smsg) = $txnobj->__Set(Field => 'Created', Value => '2005-08-05 20:00:56');
-ok($sid,$smsg);
-is($txnobj->Created,'2005-08-05 20:00:56');
-is($txnobj->CreatedObj->ISO,'2005-08-05 20:00:56');
-
-$tix->FromSQL(qq{Updated = "2005-08-05" AND Subject = "$SUBJECT"});
-is( $tix->Count, 1);
-
diff --git a/rt/lib/t/regression/22search_tix_by_watcher.t b/rt/lib/t/regression/22search_tix_by_watcher.t
deleted file mode 100644
index 204c41c..0000000
--- a/rt/lib/t/regression/22search_tix_by_watcher.t
+++ /dev/null
@@ -1,279 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use warnings;
-
-use Test::More tests => 119;
-use_ok('RT');
-RT::LoadConfig();
-RT::Init();
-use RT::Ticket;
-
-my $q = RT::Queue->new( $RT::SystemUser );
-my $queue = 'SearchTests-'. rand(200);
-$q->Create( Name => $queue );
-
-my ($total, @data, @tickets, %test) = (0, ());
-
-sub add_tix_from_data {
- my @res = ();
- while (@data) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my ( $id, undef $msg ) = $t->Create(
- Queue => $q->id,
- %{ shift(@data) },
- );
- ok( $id, "ticket created" ) or diag("error: $msg");
- push @res, $t;
- $total++;
- }
- return @res;
-}
-
-sub run_tests {
- my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
- foreach my $key ( sort keys %test ) {
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL( "( $query_prefix ) AND ( $key )" );
-
- my $error = 0;
-
- my $count = 0;
- $count++ foreach grep $_, values %{ $test{$key} };
- is($tix->Count, $count, "found correct number of ticket(s) by '$key'") or $error = 1;
-
- my $good_tickets = 1;
- while ( my $ticket = $tix->Next ) {
- next if $test{$key}->{ $ticket->Subject };
- diag $ticket->Subject ." ticket has been found when it's not expected";
- $good_tickets = 0;
- }
- ok( $good_tickets, "all tickets are good with '$key'" ) or $error = 1;
-
- diag "Wrong SQL query for '$key':". $tix->BuildSelectQuery if $error;
- }
-}
-
-@data = (
- { Subject => 'xy', Requestor => ['x@example.com', 'y@example.com'] },
- { Subject => 'x', Requestor => 'x@example.com' },
- { Subject => 'y', Requestor => 'y@example.com' },
- { Subject => '-', },
- { Subject => 'z', Requestor => 'z@example.com' },
-);
-%test = (
- 'Requestor = "x@example.com"' => { xy => 1, x => 1, y => 0, '-' => 0, z => 0 },
- 'Requestor != "x@example.com"' => { xy => 0, x => 0, y => 1, '-' => 1, z => 1 },
-
- 'Requestor = "y@example.com"' => { xy => 1, x => 0, y => 1, '-' => 0, z => 0 },
- 'Requestor != "y@example.com"' => { xy => 0, x => 1, y => 0, '-' => 1, z => 1 },
-
- 'Requestor LIKE "@example.com"' => { xy => 1, x => 1, y => 1, '-' => 0, z => 1 },
- 'Requestor NOT LIKE "@example.com"' => { xy => 0, x => 0, y => 0, '-' => 1, z => 0 },
-
- 'Requestor IS NULL' => { xy => 0, x => 0, y => 0, '-' => 1, z => 0 },
- 'Requestor IS NOT NULL' => { xy => 1, x => 1, y => 1, '-' => 0, z => 1 },
-
-# this test is a todo, we run it later
-# 'Requestor = "x@example.com" AND Requestor = "y@example.com"' => { xy => 1, x => 0, y => 0, '-' => 0, z => 0 },
- 'Requestor = "x@example.com" OR Requestor = "y@example.com"' => { xy => 1, x => 1, y => 1, '-' => 0, z => 0 },
-
- 'Requestor != "x@example.com" AND Requestor != "y@example.com"' => { xy => 0, x => 0, y => 0, '-' => 1, z => 1 },
- 'Requestor != "x@example.com" OR Requestor != "y@example.com"' => { xy => 0, x => 1, y => 1, '-' => 1, z => 1 },
-
- 'Requestor = "x@example.com" AND Requestor != "y@example.com"' => { xy => 0, x => 1, y => 0, '-' => 0, z => 0 },
- 'Requestor = "x@example.com" OR Requestor != "y@example.com"' => { xy => 1, x => 1, y => 0, '-' => 1, z => 1 },
-
- 'Requestor != "x@example.com" AND Requestor = "y@example.com"' => { xy => 0, x => 0, y => 1, '-' => 0, z => 0 },
- 'Requestor != "x@example.com" OR Requestor = "y@example.com"' => { xy => 1, x => 0, y => 1, '-' => 1, z => 1 },
-);
-@tickets = add_tix_from_data();
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue'");
- is($tix->Count, $total, "found $total tickets");
-}
-run_tests();
-
-# mixing searches by watchers with other conditions
-# http://rt3.fsck.com/Ticket/Display.html?id=9322
-%test = (
- 'Subject LIKE "x" AND Requestor = "y@example.com"' =>
- { xy => 1, x => 0, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" AND Requestor = "y@example.com"' =>
- { xy => 0, x => 0, y => 1, '-' => 0, z => 0 },
- 'Subject LIKE "x" AND Requestor != "y@example.com"' =>
- { xy => 0, x => 1, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" AND Requestor != "y@example.com"' =>
- { xy => 0, x => 0, y => 0, '-' => 1, z => 1 },
-
- 'Subject LIKE "x" OR Requestor = "y@example.com"' =>
- { xy => 1, x => 1, y => 1, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" OR Requestor = "y@example.com"' =>
- { xy => 1, x => 0, y => 1, '-' => 1, z => 1 },
- 'Subject LIKE "x" OR Requestor != "y@example.com"' =>
- { xy => 1, x => 1, y => 0, '-' => 1, z => 1 },
- 'Subject NOT LIKE "x" OR Requestor != "y@example.com"' =>
- { xy => 0, x => 1, y => 1, '-' => 1, z => 1 },
-
-# group of cases when user doesn't exist in DB at all
- 'Subject LIKE "x" AND Requestor = "not-exist@example.com"' =>
- { xy => 0, x => 0, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" AND Requestor = "not-exist@example.com"' =>
- { xy => 0, x => 0, y => 0, '-' => 0, z => 0 },
- 'Subject LIKE "x" AND Requestor != "not-exist@example.com"' =>
- { xy => 1, x => 1, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" AND Requestor != "not-exist@example.com"' =>
- { xy => 0, x => 0, y => 1, '-' => 1, z => 1 },
- 'Subject LIKE "x" OR Requestor = "not-exist@example.com"' =>
- { xy => 1, x => 1, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "x" OR Requestor = "not-exist@example.com"' =>
- { xy => 0, x => 0, y => 1, '-' => 1, z => 1 },
- 'Subject LIKE "x" OR Requestor != "not-exist@example.com"' =>
- { xy => 1, x => 1, y => 1, '-' => 1, z => 1 },
- 'Subject NOT LIKE "x" OR Requestor != "not-exist@example.com"' =>
- { xy => 1, x => 1, y => 1, '-' => 1, z => 1 },
-
- 'Subject LIKE "z" AND (Requestor = "x@example.com" OR Requestor = "y@example.com")' =>
- { xy => 0, x => 0, y => 0, '-' => 0, z => 0 },
- 'Subject NOT LIKE "z" AND (Requestor = "x@example.com" OR Requestor = "y@example.com")' =>
- { xy => 1, x => 1, y => 1, '-' => 0, z => 0 },
- 'Subject LIKE "z" OR (Requestor = "x@example.com" OR Requestor = "y@example.com")' =>
- { xy => 1, x => 1, y => 1, '-' => 0, z => 1 },
- 'Subject NOT LIKE "z" OR (Requestor = "x@example.com" OR Requestor = "y@example.com")' =>
- { xy => 1, x => 1, y => 1, '-' => 1, z => 0 },
-);
-run_tests();
-
-TODO: {
- local $TODO = "we can't generate this query yet";
- %test = (
- 'Requestor = "x@example.com" AND Requestor = "y@example.com"'
- => { xy => 1, x => 0, y => 0, '-' => 0, z => 0 },
- );
- run_tests();
-}
-
-@data = (
- { Subject => 'xy', Cc => ['x@example.com'], Requestor => [ 'y@example.com' ] },
- { Subject => 'x-', Cc => ['x@example.com'], Requestor => [] },
- { Subject => '-y', Cc => [], Requestor => [ 'y@example.com' ] },
- { Subject => '-', },
- { Subject => 'zz', Cc => ['z@example.com'], Requestor => [ 'z@example.com' ] },
- { Subject => 'z-', Cc => ['z@example.com'], Requestor => [] },
- { Subject => '-z', Cc => [], Requestor => [ 'z@example.com' ] },
-);
-%test = (
- 'Cc = "x@example.com" AND Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 0, '-y' => 0, '-' => 0, zz => 0, 'z-' => 0, '-z' => 0 },
- 'Cc = "x@example.com" OR Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 1, '-y' => 1, '-' => 0, zz => 0, 'z-' => 0, '-z' => 0 },
-
- 'Cc != "x@example.com" AND Requestor = "y@example.com"' =>
- { xy => 0, 'x-' => 0, '-y' => 1, '-' => 0, zz => 0, 'z-' => 0, '-z' => 0 },
- 'Cc != "x@example.com" OR Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 0, '-y' => 1, '-' => 1, zz => 1, 'z-' => 1, '-z' => 1 },
-
- 'Cc IS NULL AND Requestor = "y@example.com"' =>
- { xy => 0, 'x-' => 0, '-y' => 1, '-' => 0, zz => 0, 'z-' => 0, '-z' => 0 },
- 'Cc IS NULL OR Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 0, '-y' => 1, '-' => 1, zz => 0, 'z-' => 0, '-z' => 1 },
-
- 'Cc IS NOT NULL AND Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 0, '-y' => 0, '-' => 0, zz => 0, 'z-' => 0, '-z' => 0 },
- 'Cc IS NOT NULL OR Requestor = "y@example.com"' =>
- { xy => 1, 'x-' => 1, '-y' => 1, '-' => 0, zz => 1, 'z-' => 1, '-z' => 0 },
-);
-@tickets = add_tix_from_data();
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue'");
- is($tix->Count, $total, "found $total tickets");
-}
-run_tests();
-
-
-# owner is special watcher because reference is duplicated in two places,
-# owner was an ENUM field now it's WATCHERFIELD, but should support old
-# style ENUM searches for backward compatibility
-my $nobody = RT::Nobody();
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->id ."'");
- ok($tix->Count, "found ticket(s)");
-}
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner = '". $nobody->Name ."'");
- ok($tix->Count, "found ticket(s)");
-}
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->id ."'");
- is($tix->Count, 0, "found ticket(s)");
-}
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner != '". $nobody->Name ."'");
- is($tix->Count, 0, "found ticket(s)");
-}
-
-{
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner.Name LIKE 'nob'");
- ok($tix->Count, "found ticket(s)");
-}
-
-{
- # create ticket and force type to not a 'ticket' value
- # bug #6898@rt3.fsck.com
- # and http://marc.theaimsgroup.com/?l=rt-devel&m=112662934627236&w=2
- @data = ( { Subject => 'not a ticket' } );
- my($t) = add_tix_from_data();
- $t->_Set( Field => 'Type',
- Value => 'not a ticket',
- CheckACL => 0,
- RecordTransaction => 0,
- );
- $total--;
-
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND Owner = 'Nobody'");
- is($tix->Count, $total, "found ticket(s)");
-}
-
-{
- my $everyone = RT::Group->new( $RT::SystemUser );
- $everyone->LoadSystemInternalGroup('Everyone');
- ok($everyone->id, "loaded 'everyone' group");
- my($id, $msg) = $everyone->PrincipalObj->GrantRight( Right => 'OwnTicket',
- Object => $q
- );
- ok($id, "granted OwnTicket right to Everyone on '$queue'") or diag("error: $msg");
-
- my $u = RT::User->new( $RT::SystemUser );
- $u->LoadOrCreateByEmail('alpha@example.com');
- ok($u->id, "loaded user");
- @data = ( { Subject => '4', Owner => $u->id } );
- my($t) = add_tix_from_data();
- is( $t->Owner, $u->id, "created ticket with custom owner" );
- my $u_alpha_id = $u->id;
-
- $u = RT::User->new( $RT::SystemUser );
- $u->LoadOrCreateByEmail('bravo@example.com');
- ok($u->id, "loaded user");
- @data = ( { Subject => '5', Owner => $u->id } );
- ($t) = add_tix_from_data();
- is( $t->Owner, $u->id, "created ticket with custom owner" );
- my $u_bravo_id = $u->id;
-
- my $tix = RT::Tickets->new($RT::SystemUser);
- $tix->FromSQL("Queue = '$queue' AND
- ( Owner = '$u_alpha_id' OR
- Owner = '$u_bravo_id' )"
- );
- is($tix->Count, 2, "found ticket(s)");
-}
-
-
-exit(0)
diff --git a/rt/lib/t/regression/23-batch-upload-csv.t b/rt/lib/t/regression/23-batch-upload-csv.t
deleted file mode 100644
index fc9436a..0000000
--- a/rt/lib/t/regression/23-batch-upload-csv.t
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/perl -w
-use strict; use warnings;
-
-use Test::More qw/no_plan/;
-use_ok('RT');
-RT::LoadConfig();
-RT::Init();
-use_ok('RT::Action::CreateTickets');
-
-my $QUEUE = 'uploadtest-'.$$;
-
-my $queue_obj = RT::Queue->new($RT::SystemUser);
-$queue_obj->Create(Name => $QUEUE);
-
-my $cf = RT::CustomField->new($RT::SystemUser);
-my ($val,$msg) = $cf->Create(Name => 'Work Package-'.$$, Type => 'Freeform', LookupType => RT::Ticket->CustomFieldLookupType, MaxValues => 1);
-ok($cf->id);
-ok($val,$msg);
-($val, $msg) = $cf->AddToObject($queue_obj);
-ok($val,$msg);
-ok($queue_obj->TicketCustomFields()->Count, "We have a custom field, at least");
-
-
-my $data = <<EOF;
-id,Queue,Subject,Status,Requestor,@{[$cf->Name]}
-create-1,$QUEUE,hi,new,root,2.0
-create-2,$QUEUE,hello,new,root,3.0
-EOF
-
-my $action = RT::Action::CreateTickets->new(CurrentUser => RT::CurrentUser->new('root'));
-ok ($action->CurrentUser->id , "WE have a current user");
-
-$action->Parse(Content => $data);
-my @results = $action->CreateByTemplate();
-
-my $tix = RT::Tickets->new($RT::SystemUser);
-$tix->FromSQL ("Queue = '". $QUEUE."'");
-$tix->OrderBy( FIELD => 'id', ORDER => 'ASC' );
-ok($tix->Count);
-my $first = $tix->First();
-is($first->Subject(), 'hi');
-is($first->FirstCustomFieldValue($cf->id), '2.0');
-
-my $second = $tix->Next;
-is($second->Subject(), 'hello');
-is($second->FirstCustomFieldValue($cf->id), '3.0');
-1;
diff --git a/rt/lib/t/regression/23-web_attachments.t b/rt/lib/t/regression/23-web_attachments.t
deleted file mode 100644
index adc38ad..0000000
--- a/rt/lib/t/regression/23-web_attachments.t
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Test::More tests => 15;
-use RT;
-RT::LoadConfig;
-RT::Init;
-use Test::WWW::Mechanize;
-
-$RT::WebURL ||= 0; # avoid stupid warning
-my $BaseURL = $RT::WebURL;
-use constant LogoFile => $RT::MasonComponentRoot .'/NoAuth/images/bplogo.gif';
-use constant FaviconFile => $RT::MasonComponentRoot .'/NoAuth/images/favicon.png';
-
-my $queue_name = 'General';
-
-my $m = Test::WWW::Mechanize->new;
-isa_ok($m, 'Test::WWW::Mechanize');
-
-$m->get_ok( $BaseURL."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-
-my $qid;
-{
- $m->content =~ /<SELECT\s+NAME\s*="Queue"\s*>.*?<OPTION\s+VALUE="(\d+)".*?>\s*\Q$queue_name\E\s*<\/OPTION>/msig;
- ok( $qid = $1, "found id of the '$queue_name' queue");
-}
-
-$m->form_name('CreateTicketInQueue');
-$m->field('Queue', $qid);
-$m->submit;
-is($m->status, 200, "request successful");
-$m->content_like(qr/Create a new ticket/, 'ticket create page');
-
-$m->form_name('TicketCreate');
-$m->field('Subject', 'Attachments test');
-$m->field('Attach', LogoFile);
-$m->field('Content', 'Some content');
-$m->submit;
-is($m->status, 200, "request successful");
-
-$m->content_like(qr/Attachments test/, 'we have subject on the page');
-$m->content_like(qr/Some content/, 'and content');
-$m->content_like(qr/Download bplogo\.gif/, 'page has file name');
-
-$m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
-$m->form_name('TicketUpdate');
-$m->field('Attach', LogoFile);
-$m->click('AddMoreAttach');
-is($m->status, 200, "request successful");
-
-$m->form_name('TicketUpdate');
-$m->field('Attach', FaviconFile);
-$m->field('UpdateContent', 'Message');
-$m->click('SubmitTicket');
-is($m->status, 200, "request successful");
-
-$m->content_like(qr/Download bplogo\.gif/, 'page has file name');
-$m->content_like(qr/Download favicon\.png/, 'page has file name');
-
diff --git a/rt/lib/t/regression/23cfsort-freeform-multiple.t b/rt/lib/t/regression/23cfsort-freeform-multiple.t
deleted file mode 100644
index 453e83c..0000000
--- a/rt/lib/t/regression/23cfsort-freeform-multiple.t
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More tests => 24;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-# Test Sorting by custom fields.
-
-diag "Create a queue to test with.";
-my $queue_name = "CFSortQueue-$$";
-my $queue;
-{
- $queue = RT::Queue->new( $RT::SystemUser );
- my ($ret, $msg) = $queue->Create(
- Name => $queue_name,
- Description => 'queue for custom field sort testing'
- );
- ok($ret, "$queue_name - test queue creation. $msg");
-}
-
-diag "create a CF\n";
-my $cf_name = "Order$$";
-my $cf;
-{
- $cf = RT::CustomField->new( $RT::SystemUser );
- my ($ret, $msg) = $cf->Create(
- Name => $cf_name,
- Queue => $queue->id,
- Type => 'FreeformMultiple',
- );
- ok($ret, "Custom Field Order created");
-}
-
-my ($total, @data, @tickets, @test) = (0, ());
-
-sub add_tix_from_data {
- my @res = ();
- @data = sort { rand(100) <=> rand(100) } @data;
- while (@data) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my %args = %{ shift(@data) };
- my @values = ();
- if ( exists $args{'CF'} && ref $args{'CF'} ) {
- @values = @{ delete $args{'CF'} };
- } elsif ( exists $args{'CF'} ) {
- @values = (delete $args{'CF'});
- }
- $args{ 'CustomField-'. $cf->id } = \@values
- if @values;
- my $subject = join(",", sort @values) || '-';
- my ( $id, undef $msg ) = $t->Create(
- %args,
- Queue => $queue->id,
- Subject => $subject,
- );
- ok( $id, "ticket created" ) or diag("error: $msg");
- push @res, $t;
- $total++;
- }
- return @res;
-}
-
-sub run_tests {
- my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
- foreach my $test ( @test ) {
- my $query = join " AND ", map "( $_ )", grep defined && length,
- $query_prefix, $test->{'Query'};
-
- foreach my $order (qw(ASC DESC)) {
- my $error = 0;
- my $tix = RT::Tickets->new( $RT::SystemUser );
- $tix->FromSQL( $query );
- $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order );
-
- ok($tix->Count, "found ticket(s)")
- or $error = 1;
-
- my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz');
- while ( my $t = $tix->Next ) {
- my $tmp;
- if ( $order eq 'ASC' ) {
- $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]);
- } else {
- $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]);
- }
- if ( $tmp > 0 ) {
- $order_ok = 0; last;
- }
- $last = $t->Subject;
- }
-
- ok( $order_ok, "$order order of tickets is good" )
- or $error = 1;
-
- if ( $error ) {
- diag "Wrong SQL query:". $tix->BuildSelectQuery;
- $tix->GotoFirstItem;
- while ( my $t = $tix->Next ) {
- diag sprintf "%02d - %s", $t->id, $t->Subject;
- }
- }
- }
- }
-}
-
-@data = (
- { },
- { CF => ['b', 'd'] },
- { CF => ['a', 'c'] },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "CF.{$cf_name}" },
- { Order => "CF.$queue_name.{$cf_name}" },
-);
-run_tests();
-
-@data = (
- { CF => ['m', 'a'] },
- { CF => ['m'] },
- { CF => ['m', 'o'] },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "CF.{$cf_name}", Query => "CF.{$cf_name} = 'm'" },
- { Order => "CF.$queue_name.{$cf_name}", Query => "CF.{$cf_name} = 'm'" },
-);
-run_tests();
-
diff --git a/rt/lib/t/regression/23cfsort-freeform-single.t b/rt/lib/t/regression/23cfsort-freeform-single.t
deleted file mode 100644
index 277befe..0000000
--- a/rt/lib/t/regression/23cfsort-freeform-single.t
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More tests => 57;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-# Test Sorting by FreeformSingle custom field.
-
-diag "Create a queue to test with.";
-my $queue_name = "CFSortQueue$$";
-my $queue;
-{
- $queue = RT::Queue->new( $RT::SystemUser );
- my ($ret, $msg) = $queue->Create(
- Name => $queue,
- Description => 'queue for custom field sort testing'
- );
- ok($ret, "$queue test queue creation. $msg");
-}
-
-# CFs for testing, later we create another one
-my %CF;
-my $cf_name;
-
-diag "create a CF\n";
-{
- $cf_name = $CF{'CF'}{'name'} = "Order$$";
- $CF{'CF'}{'obj'} = RT::CustomField->new( $RT::SystemUser );
- my ($ret, $msg) = $CF{'CF'}{'obj'}->Create(
- Name => $CF{'CF'}{'name'},
- Queue => $queue->id,
- Type => 'FreeformSingle',
- );
- ok($ret, "Custom Field $CF{'CF'}{'name'} created");
-}
-
-my ($total, @data, @tickets, @test) = (0, ());
-
-sub add_tix_from_data {
- my @res = ();
- @data = sort { rand(100) <=> rand(100) } @data;
- while (@data) {
- my $t = RT::Ticket->new($RT::SystemUser);
- my %args = %{ shift(@data) };
-
- my $subject = '-';
- foreach my $e ( grep exists $CF{$_} && defined $CF{$_}, keys %args ) {
- my @values = ();
- if ( ref $args{ $e } ) {
- @values = @{ delete $args{ $e } };
- } else {
- @values = (delete $args{ $e });
- }
- $args{ 'CustomField-'. $CF{ $e }{'obj'}->id } = \@values
- if @values;
- $subject = join(",", sort @values) || '-'
- if $e eq 'CF';
- }
-
- my ( $id, undef $msg ) = $t->Create(
- %args,
- Queue => $queue->id,
- Subject => $subject,
- );
- ok( $id, "ticket created" ) or diag("error: $msg");
- push @res, $t;
- $total++;
- }
- return @res;
-}
-
-sub run_tests {
- my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
- foreach my $test ( @test ) {
- my $query = join " AND ", map "( $_ )", grep defined && length,
- $query_prefix, $test->{'Query'};
-
- foreach my $order (qw(ASC DESC)) {
- my $error = 0;
- my $tix = RT::Tickets->new( $RT::SystemUser );
- $tix->FromSQL( $query );
- $tix->OrderBy( FIELD => $test->{'Order'}, ORDER => $order );
-
- ok($tix->Count, "found ticket(s)")
- or $error = 1;
-
- my ($order_ok, $last) = (1, $order eq 'ASC'? '-': 'zzzzzz');
- while ( my $t = $tix->Next ) {
- my $tmp;
- if ( $order eq 'ASC' ) {
- $tmp = ((split( /,/, $last))[0] cmp (split( /,/, $t->Subject))[0]);
- } else {
- $tmp = -((split( /,/, $last))[-1] cmp (split( /,/, $t->Subject))[-1]);
- }
- if ( $tmp > 0 ) {
- $order_ok = 0; last;
- }
- $last = $t->Subject;
- }
-
- ok( $order_ok, "$order order of tickets is good" )
- or $error = 1;
-
- if ( $error ) {
- diag "Wrong SQL query:". $tix->BuildSelectQuery;
- $tix->GotoFirstItem;
- while ( my $t = $tix->Next ) {
- diag sprintf "%02d - %s", $t->id, $t->Subject;
- }
- }
- }
- }
-}
-
-@data = (
- { },
- { CF => 'a' },
- { CF => 'b' },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "CF.{$cf_name}" },
- { Order => "CF.$queue_name.{$cf_name}" },
-);
-run_tests();
-
-@data = (
- { },
- { CF => 'aa' },
- { CF => 'ab' },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Query => "CF.{$cf_name} LIKE 'a'", Order => "CF.{$cf_name}" },
- { Query => "CF.{$cf_name} LIKE 'a'", Order => "CF.$queue_name.{$cf_name}" },
-);
-run_tests();
-
-@data = (
- { Subject => '-', },
- { Subject => 'a', CF => 'a' },
- { Subject => 'b', CF => 'b' },
- { Subject => 'c', CF => 'c' },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Query => "CF.{$cf_name} != 'c'", Order => "CF.{$cf_name}" },
- { Query => "CF.{$cf_name} != 'c'", Order => "CF.$queue_name.{$cf_name}" },
-);
-run_tests();
-
-
-
-diag "create another CF\n";
-{
- $CF{'AnotherCF'}{'name'} = "OrderAnother$$";
- $CF{'AnotherCF'}{'obj'} = RT::CustomField->new( $RT::SystemUser );
- my ($ret, $msg) = $CF{'AnotherCF'}{'obj'}->Create(
- Name => $CF{'AnotherCF'}{'name'},
- Queue => $queue->id,
- Type => 'FreeformSingle',
- );
- ok($ret, "Custom Field $CF{'AnotherCF'}{'name'} created");
-}
-
-# test that order is not affect by other fields (had such problem)
-@data = (
- { Subject => '-', },
- { Subject => 'a', CF => 'a', AnotherCF => 'za' },
- { Subject => 'b', CF => 'b', AnotherCF => 'ya' },
- { Subject => 'c', CF => 'c', AnotherCF => 'xa' },
-);
-@tickets = add_tix_from_data();
-@test = (
- { Order => "CF.{$cf_name}" },
- { Order => "CF.$queue_name.{$cf_name}" },
- { Query => "CF.{$cf_name} != 'c'", Order => "CF.{$cf_name}" },
- { Query => "CF.{$cf_name} != 'c'", Order => "CF.$queue_name.{$cf_name}" },
-);
-run_tests();
-
-
-
diff --git a/rt/lib/t/regression/23cfsort.t b/rt/lib/t/regression/23cfsort.t
deleted file mode 100644
index ba404f2..0000000
--- a/rt/lib/t/regression/23cfsort.t
+++ /dev/null
@@ -1,177 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More tests => 21;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-my($ret,$msg);
-
-
-# Test Sorting by custom fields.
-# TODO: it's hard to read this file, conver to new style,
-# for example look at 23cfsort-freeform-single.t
-
-# ---- Create a queue to test with.
-my $queue = "CFSortQueue-$$";
-my $queue_obj = RT::Queue->new( $RT::SystemUser );
-($ret, $msg) = $queue_obj->Create(
- Name => $queue,
- Description => 'queue for custom field sort testing'
-);
-ok($ret, "$queue test queue creation. $msg");
-
-# ---- Create some custom fields. We're not currently using all of
-# them to test with, but the more the merrier.
-my $cfO = RT::CustomField->new($RT::SystemUser);
-my $cfA = RT::CustomField->new($RT::SystemUser);
-my $cfB = RT::CustomField->new($RT::SystemUser);
-my $cfC = RT::CustomField->new($RT::SystemUser);
-
-($ret, $msg) = $cfO->Create( Name => 'Order',
- Queue => 0,
- SortOrder => 1,
- Description => q{Something to compare results for, since we can't guarantee ticket ID},
- Type=> 'FreeformSingle');
-ok($ret, "Custom Field Order created");
-
-($ret, $msg) = $cfA->Create( Name => 'Alpha',
- Queue => $queue_obj->id,
- SortOrder => 1,
- Description => 'A Testing custom field',
- Type=> 'FreeformSingle');
-ok($ret, "Custom Field Alpha created");
-
-($ret, $msg) = $cfB->Create( Name => 'Beta',
- Queue => $queue_obj->id,
- Description => 'A Testing custom field',
- Type=> 'FreeformSingle');
-ok($ret, "Custom Field Beta created");
-
-($ret, $msg) = $cfC->Create( Name => 'Charlie',
- Queue => $queue_obj->id,
- Description => 'A Testing custom field',
- Type=> 'FreeformSingle');
-ok($ret, "Custom Field Charlie created");
-
-# ----- Create some tickets to test with. Assign them some values to
-# make it easy to sort with.
-my $t1 = RT::Ticket->new($RT::SystemUser);
-$t1->Create( Queue => $queue_obj->Id,
- Subject => 'One',
- );
-$t1->AddCustomFieldValue(Field => $cfO->Id, Value => '1');
-$t1->AddCustomFieldValue(Field => $cfA->Id, Value => '2');
-$t1->AddCustomFieldValue(Field => $cfB->Id, Value => '1');
-$t1->AddCustomFieldValue(Field => $cfC->Id, Value => 'BBB');
-
-my $t2 = RT::Ticket->new($RT::SystemUser);
-$t2->Create( Queue => $queue_obj->Id,
- Subject => 'Two',
- );
-$t2->AddCustomFieldValue(Field => $cfO->Id, Value => '2');
-$t2->AddCustomFieldValue(Field => $cfA->Id, Value => '1');
-$t2->AddCustomFieldValue(Field => $cfB->Id, Value => '2');
-$t2->AddCustomFieldValue(Field => $cfC->Id, Value => 'AAA');
-
-# helper
-sub check_order {
- my ($tx, @order) = @_;
- my @results;
- while (my $t = $tx->Next) {
- push @results, $t->CustomFieldValues($cfO->Id)->First->Content;
- }
- my $results = join (" ",@results);
- my $order = join(" ",@order);
- @_ = ($results, $order , "Ordered correctly: $order");
- goto \&is;
-}
-
-# The real tests start here
-my $tx = new RT::Tickets( $RT::SystemUser );
-
-
-# Make sure we can sort in both directions on a queue specific field.
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderBy( FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' );
-is($tx->Count,2 ,"We found 2 tickets when lookign for cf charlie");
-check_order( $tx, 1, 2);
-
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderBy( FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' );
-is($tx->Count,2, "We found two tickets when sorting by cf charlie without limiting to it" );
-check_order( $tx, 2, 1);
-
-# When ordering by _global_ CustomFields, if more than one queue has a
-# CF named Charlie, things will go bad. So, these results are uniqued
-# in Tickets_Overlay.
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderBy( FIELD => "CF.{Charlie}", ORDER => 'DESC' );
-diag $tx->BuildSelectQuery;
-is($tx->Count,2);
-check_order( $tx, 1, 2);
-
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderBy( FIELD => "CF.{Charlie}", ORDER => 'ASC' );
-diag $tx->BuildSelectQuery;
-is($tx->Count,2);
-check_order( $tx, 2, 1);
-
-# Add a new ticket, to test sorting on multiple columns.
-my $t3 = RT::Ticket->new($RT::SystemUser);
-$t3->Create( Queue => $queue_obj->Id,
- Subject => 'Three',
- );
-$t3->AddCustomFieldValue(Field => $cfO->Id, Value => '3');
-$t3->AddCustomFieldValue(Field => $cfA->Id, Value => '3');
-$t3->AddCustomFieldValue(Field => $cfB->Id, Value => '2');
-$t3->AddCustomFieldValue(Field => $cfC->Id, Value => 'AAA');
-
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderByCols(
- { FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' },
- { FIELD => "CF.${queue}.{Alpha}", ORDER => 'DES' },
-);
-is($tx->Count,3);
-check_order( $tx, 3, 2, 1);
-
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderByCols(
- { FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' },
- { FIELD => "CF.${queue}.{Alpha}", ORDER => 'ASC' },
-);
-is($tx->Count,3);
-check_order( $tx, 1, 2, 3);
-
-# Reverse the order of the secondary column, which changes the order
-# of the first two tickets.
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderByCols(
- { FIELD => "CF.${queue}.{Charlie}", ORDER => 'ASC' },
- { FIELD => "CF.${queue}.{Alpha}", ORDER => 'ASC' },
-);
-is($tx->Count,3);
-check_order( $tx, 2, 3, 1);
-
-$tx = new RT::Tickets( $RT::SystemUser );
-$tx->FromSQL(qq[queue="$queue"] );
-$tx->OrderByCols(
- { FIELD => "CF.${queue}.{Charlie}", ORDER => 'DES' },
- { FIELD => "CF.${queue}.{Alpha}", ORDER => 'DES' },
-);
-is($tx->Count,3);
-check_order( $tx, 1, 3, 2);
-
diff --git a/rt/lib/t/regression/24-watchers.t b/rt/lib/t/regression/24-watchers.t
deleted file mode 100644
index 69bc8ac..0000000
--- a/rt/lib/t/regression/24-watchers.t
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
-# <jesse.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-
-use Test::More tests => 28;
-use RT;
-RT::LoadConfig();
-RT::Init();
-use strict;
-no warnings 'once';
-
-use RT::Queue;
-use RT::User;
-use RT::Group;
-use RT::Ticket;
-use RT::CurrentUser;
-
-
-# clear all global right
-my $acl = RT::ACL->new($RT::SystemUser);
-$acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' );
-$acl->LimitToObject( $RT::System );
-while( my $ace = $acl->Next ) {
- $ace->Delete;
-}
-
-# create new queue to be sure we do not mess with rights
-my $queue = RT::Queue->new($RT::SystemUser);
-my ($queue_id) = $queue->Create( Name => 'watcher tests '.$$);
-ok( $queue_id, 'queue created for watcher tests' );
-
-# new privileged user to check rights
-my $user = RT::User->new( $RT::SystemUser );
-my ($user_id) = $user->Create( Name => 'watcher'.$$,
- EmailAddress => "watcher$$".'@localhost',
- Privileged => 1,
- Password => 'qwe123',
- );
-my $cu= RT::CurrentUser->new($user);
-
-# make sure user can see tickets in the queue
-my $principal = $user->PrincipalObj;
-ok( $principal, "principal loaded" );
-$principal->GrantRight( Right => 'ShowTicket', Object => $queue );
-$principal->GrantRight( Right => 'SeeQueue' , Object => $queue );
-
-ok( $user->HasRight( Right => 'SeeQueue', Object => $queue ), "user can see queue" );
-ok( $user->HasRight( Right => 'ShowTicket', Object => $queue ), "user can show queue tickets" );
-ok( !$user->HasRight( Right => 'ModifyTicket', Object => $queue ), "user can't modify queue tickets" );
-ok( !$user->HasRight( Right => 'Watch', Object => $queue ), "user can't watch queue tickets" );
-
-my $ticket = RT::Ticket->new( $RT::SystemUser );
-my ($rv, $msg) = $ticket->Create( Subject => 'watcher tests', Queue => $queue->Name );
-ok( $ticket->id, "ticket created" );
-
-my $ticket2 = RT::Ticket->new( $cu );
-$ticket2->Load( $ticket->id );
-ok( $ticket2->Subject, "ticket load by user" );
-
-# user can add self to ticket only after getting Watch right
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( !$rv, "user can't add self as Cc" );
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId );
-ok( !$rv, "user can't add self as Requestor" );
-$principal->GrantRight( Right => 'Watch' , Object => $queue );
-ok( $user->HasRight( Right => 'Watch', Object => $queue ), "user can watch queue tickets" );
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( $rv, "user can add self as Cc by PrincipalId" );
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId );
-ok( $rv, "user can add self as Requestor by PrincipalId" );
-
-# remove user and try adding with Email address
-($rv, $msg) = $ticket->DeleteWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( $rv, "watcher removed by PrincipalId" );
-($rv, $msg) = $ticket->DeleteWatcher( Type => 'Requestor', Email => $user->EmailAddress );
-ok( $rv, "watcher removed by Email" );
-
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Cc', Email => $user->EmailAddress );
-ok( $rv, "user can add self as Cc by Email" );
-($rv, $msg) = $ticket2->AddWatcher( Type => 'Requestor', Email => $user->EmailAddress );
-ok( $rv, "user can add self as Requestor by Email" );
-
-# Queue watcher tests
-$principal->RevokeRight( Right => 'Watch' , Object => $queue );
-ok( !$user->HasRight( Right => 'Watch', Object => $queue ), "user queue watch right revoked" );
-
-my $queue2 = RT::Queue->new( $cu );
-($rv, $msg) = $queue2->Load( $queue->id );
-ok( $rv, "user loaded queue" );
-
-# user can add self to queue only after getting Watch right
-($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( !$rv, "user can't add self as Cc" );
-($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId );
-ok( !$rv, "user can't add self as Requestor" );
-$principal->GrantRight( Right => 'Watch' , Object => $queue );
-ok( $user->HasRight( Right => 'Watch', Object => $queue ), "user can watch queue queues" );
-($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( $rv, "user can add self as Cc by PrincipalId" );
-($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', PrincipalId => $user->PrincipalId );
-ok( $rv, "user can add self as Requestor by PrincipalId" );
-
-# remove user and try adding with Email address
-($rv, $msg) = $queue->DeleteWatcher( Type => 'Cc', PrincipalId => $user->PrincipalId );
-ok( $rv, "watcher removed by PrincipalId" );
-($rv, $msg) = $queue->DeleteWatcher( Type => 'Requestor', Email => $user->EmailAddress );
-ok( $rv, "watcher removed by Email" );
-
-($rv, $msg) = $queue2->AddWatcher( Type => 'Cc', Email => $user->EmailAddress );
-ok( $rv, "user can add self as Cc by Email" );
-($rv, $msg) = $queue2->AddWatcher( Type => 'Requestor', Email => $user->EmailAddress );
-ok( $rv, "user can add self as Requestor by Email" );
-
-
diff --git a/rt/lib/t/regression/24pawsort.t b/rt/lib/t/regression/24pawsort.t
deleted file mode 100644
index 665c325..0000000
--- a/rt/lib/t/regression/24pawsort.t
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More qw/no_plan/;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-use strict;
-use warnings;
-
-use RT::Tickets;
-use RT::Queue;
-use RT::CustomField;
-
-my($ret,$msg);
-
-# Test Paw Sort
-
-
-
-# ---- Create a queue to test with.
-my $queue = "PAWSortQueue-$$";
-my $queue_obj = RT::Queue->new($RT::SystemUser);
-($ret, $msg) = $queue_obj->Create(Name => $queue,
- Description => 'queue for custom field sort testing');
-ok($ret, "$queue test queue creation. $msg");
-
-
-# ---- Create some users
-
-my $me = RT::User->new($RT::SystemUser);
-($ret, $msg) = $me->Create(Name => "Me$$", EmailAddress => $$.'create-me-1@example.com');
-($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'OwnTicket');
-($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'SeeQueue');
-($ret, $msg) = $me->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'ShowTicket');
-my $you = RT::User->new($RT::SystemUser);
-($ret, $msg) = $you->Create(Name => "You$$", EmailAddress => $$.'create-you-1@example.com');
-($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'OwnTicket');
-($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'SeeQueue');
-($ret, $msg) = $you->PrincipalObj->GrantRight(Object =>$queue_obj, Right => 'ShowTicket');
-
-my $nobody = RT::User->new($RT::SystemUser);
-$nobody->Load('nobody');
-
-
-# ----- Create some tickets to test with. Assign them some values to
-# make it easy to sort with.
-
-my @tickets = (
- [qw[1 10], $me],
- [qw[2 20], $me],
- [qw[3 20], $you],
- [qw[4 30], $you],
- [qw[5 5], $nobody],
- [qw[6 55], $nobody],
- );
-for (@tickets) {
- my $t = RT::Ticket->new($RT::SystemUser);
- $t->Create( Queue => $queue_obj->Id,
- Subject => $_->[0],
- Owner => $_->[2]->Id,
- Priority => $_->[1],
- );
-}
-
-sub check_order {
- my ($tx, @order) = @_;
- my @results;
- while (my $t = $tx->Next) {
- push @results, $t->Subject;
- }
- my $results = join (" ",@results);
- my $order = join(" ",@order);
- is( $results, $order );
-}
-
-
-# The real tests start here
-
-my $cme = new RT::CurrentUser( $me );
-my $metx = new RT::Tickets( $cme );
-# Make sure we can sort in both directions on a queue specific field.
-$metx->FromSQL(qq[queue="$queue"] );
-$metx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'ASC' );
-is($metx->Count,6);
-check_order( $metx, qw[2 1 6 5 4 3]);
-
-$metx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'DESC' );
-is($metx->Count,6);
-check_order( $metx, reverse qw[2 1 6 5 4 3]);
-
-
-
-my $cyou = new RT::CurrentUser( $you );
-my $youtx = new RT::Tickets( $cyou );
-# Make sure we can sort in both directions on a queue specific field.
-$youtx->FromSQL(qq[queue="$queue"] );
-$youtx->OrderBy( FIELD => "Custom.Ownership", ORDER => 'ASC' );
-is($youtx->Count,6);
-check_order( $youtx, qw[4 3 6 5 2 1]);
-
-__END__
-
-
diff --git a/rt/lib/t/regression/25scrip_order.t b/rt/lib/t/regression/25scrip_order.t
deleted file mode 100644
index 0e11989..0000000
--- a/rt/lib/t/regression/25scrip_order.t
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Test::More tests => 7;
-
-use RT;
-RT::LoadConfig();
-RT::Init;
-
-# {{{ test scrip ordering based on description
-
-my $scrip_queue = RT::Queue->new($RT::SystemUser);
-my ($queue_id, $msg) = $scrip_queue->Create( Name => "ScripOrdering-$$",
- Description => 'Test scrip ordering by description' );
-ok($queue_id, "Created scrip-ordering test queue? ".$msg);
-
-my $priority_ten_scrip = RT::Scrip->new($RT::SystemUser);
-(my $id, $msg) = $priority_ten_scrip->Create(
- Description => "10 set priority $$",
- Queue => $queue_id,
- ScripCondition => 'On Create',
- ScripAction => 'User Defined',
- CustomPrepareCode => '$RT::Logger->debug("Setting priority to 10..."); return 1;',
- CustomCommitCode => '$self->TicketObj->SetPriority(10);',
- Template => 'Blank',
- Stage => 'TransactionCreate',
-);
-ok($id, "Created priority-10 scrip? ".$msg);
-
-my $priority_five_scrip = RT::Scrip->new($RT::SystemUser);
-($id, $msg) = $priority_ten_scrip->Create(
- Description => "05 set priority $$",
- Queue => $queue_id,
- ScripCondition => 'On Create',
- ScripAction => 'User Defined',
- CustomPrepareCode => '$RT::Logger->debug("Setting priority to 5..."); return 1;',
- CustomCommitCode => '$self->TicketObj->SetPriority(5);',
- Template => 'Blank',
- Stage => 'TransactionCreate',
-);
-ok($id, "Created priority-5 scrip? ".$msg);
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-($id, $msg) = $ticket->Create(
- Queue => $queue_id,
- Requestor => 'order@example.com',
- Subject => "Scrip order test $$",
-);
-ok($ticket->id, "Created ticket? id=$id");
-
-ok($ticket->Priority != 0, "Ticket shouldn't be priority 0");
-ok($ticket->Priority != 5, "Ticket shouldn't be priority 5");
-ok($ticket->Priority == 10, "Ticket should be priority 10");
-
-# }}}
-
-1;
diff --git a/rt/lib/t/regression/26command_line.t b/rt/lib/t/regression/26command_line.t
deleted file mode 100644
index 7021383..0000000
--- a/rt/lib/t/regression/26command_line.t
+++ /dev/null
@@ -1,450 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Test::Expect;
-#use Test::More qw/no_plan/;
-use Test::More tests => 222;
-
-use RT;
-RT::LoadConfig();
-RT::Init;
-
-use RT::User;
-use RT::Queue;
-
-my $rt_tool_path = "$RT::BinPath/rt";
-
-# {{{ test configuration options
-
-# config directives:
-# (in $CWD/.rtrc)
-# - server <URL> URL to RT server.
-# - user <username> RT username.
-# - passwd <passwd> RT user's password.
-# - query <RT Query> Default RT Query for list action
-# - orderby <order> Default RT order for list action
-#
-# Blank and #-commented lines are ignored.
-
-# environment variables
-# The following environment variables override any corresponding
-# values defined in configuration files:
-#
-# - RTUSER
-$ENV{'RTUSER'} = 'root';
-# - RTPASSWD
-$ENV{'RTPASSWD'} = 'password';
-# - RTSERVER
-$RT::Logger->debug("Connecting to server at $RT::WebBaseURL...");
-$ENV{'RTSERVER'} = $RT::WebBaseURL;
-# - RTDEBUG Numeric debug level. (Set to 3 for full logs.)
-$ENV{'RTDEBUG'} = '1';
-# - RTCONFIG Specifies a name other than ".rtrc" for the
-# configuration file.
-#
-# - RTQUERY Default RT Query for rt list
-# - RTORDERBY Default order for rt list
-
-
-# }}}
-
-# {{{ test ticket manipulation
-
-# create a ticket
-expect_run(
- command => "$rt_tool_path shell",
- prompt => 'rt> ',
- quit => 'quit',
-);
-expect_send(q{create -t ticket set subject='new ticket' add cc=foo@example.com}, "Creating a ticket...");
-expect_like(qr/Ticket \d+ created/, "Created the ticket");
-expect_handle->before() =~ /Ticket (\d+) created/;
-my $ticket_id = $1;
-ok($ticket_id, "Got ticket id=$ticket_id");
-expect_send(q{create -t ticket set subject='new ticket'}, "Creating a ticket as just a subject...");
-expect_like(qr/Ticket \d+ created/, "Created the ticket");
-
-# make sure we can request things as 'rt foo'
-expect_send(q{rt create -t ticket set subject='rt ticket'}, "Creating a ticket with 'rt create'...");
-expect_like(qr/Ticket \d+ created/, "Created the ticket");
-
-# {{{ test queue manipulation
-
-# creating queues
-expect_send("create -t queue set Name='NewQueue$$'", 'Creating a queue...');
-expect_like(qr/Queue \d+ created/, 'Created the queue');
-expect_handle->before() =~ /Queue (\d+) created/;
-my $queue_id = $1;
-ok($queue_id, "Got queue id=$queue_id");
-# updating users
-expect_send("edit queue/$queue_id set Name='EditedQueue$$'", 'Editing the queue');
-expect_like(qr/Queue $queue_id updated/, 'Edited the queue');
-expect_send("show queue/$queue_id", 'Showing the queue...');
-expect_like(qr/id: queue\/$queue_id/, 'Saw the queue');
-expect_like(qr/Name: EditedQueue$$/, 'Saw the modification');
-TODO: {
- todo_skip "Listing non-ticket items doesn't work", 2;
- expect_send("list -t queue 'id > 0'", 'Listing the queues...');
- expect_like(qr/$queue_id: EditedQueue$$/, 'Found the queue');
-}
-
-# }}}
-
-
-# Set up a custom field for editing tests
-my $cf = RT::CustomField->new($RT::SystemUser);
-my ($val,$msg) = $cf->Create(Name => 'MyCF'.$$, Type => 'FreeformSingle', Queue => $queue_id);
-ok($val,$msg);
-
-my $othercf = RT::CustomField->new($RT::SystemUser);
-($val,$msg) = $othercf->Create(Name => 'My CF'.$$, Type => 'FreeformSingle', Queue => $queue_id);
-ok($val,$msg);
-
-
-
-# add a comment to ticket
- expect_send("comment -m 'comment-$$' $ticket_id", "Adding a comment...");
- expect_like(qr/Message recorded/, "Added the comment");
- ### should test to make sure it actually got added
- # add correspondance to ticket (?)
- expect_send("correspond -m 'correspond-$$' $ticket_id", "Adding correspondence...");
- expect_like(qr/Message recorded/, "Added the correspondence");
- ### should test to make sure it actually got added
-
- # add attachments to a ticket
- # text attachment
- check_attachment("$RT::BasePath/lib/t/data/lorem-ipsum");
- # binary attachment
- check_attachment($RT::MasonComponentRoot.'/NoAuth/images/bplogo.gif');
-
-# change a ticket's Owner
-expect_send("edit ticket/$ticket_id set owner=root", 'Changing owner...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed owner');
-expect_send("show ticket/$ticket_id -f owner", 'Verifying change...');
-expect_like(qr/Owner: root/, 'Verified change');
-# change a ticket's Requestor
-expect_send("edit ticket/$ticket_id set requestors=foo\@example.com", 'Changing Requestor...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed Requestor');
-expect_send("show ticket/$ticket_id -f requestors", 'Verifying change...');
-expect_like(qr/Requestors: foo\@example.com/, 'Verified change');
-# change a ticket's Cc
-expect_send("edit ticket/$ticket_id set cc=bar\@example.com", 'Changing Cc...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed Cc');
-expect_send("show ticket/$ticket_id -f cc", 'Verifying change...');
-expect_like(qr/Cc: bar\@example.com/, 'Verified change');
-# change a ticket's priority
-expect_send("edit ticket/$ticket_id set priority=10", 'Changing priority...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed priority');
-expect_send("show ticket/$ticket_id -f priority", 'Verifying change...');
-expect_like(qr/Priority: 10/, 'Verified change');
-# move a ticket to a different queue
-expect_send("edit ticket/$ticket_id set queue=EditedQueue$$", 'Changing queue...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed queue');
-expect_send("show ticket/$ticket_id -f queue", 'Verifying change...');
-expect_like(qr/Queue: EditedQueue$$/, 'Verified change');
-# cannot move ticket to a nonexistent queue
-expect_send("edit ticket/$ticket_id set queue=nonexistent-$$", 'Changing to nonexistent queue...');
-expect_like(qr/queue does not exist/i, 'Errored out');
-expect_send("show ticket/$ticket_id -f queue", 'Verifying lack of change...');
-expect_like(qr/Queue: EditedQueue$$/, 'Verified lack of change');
-
-# Test reading and setting custom fields without spaces
-expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking initial value');
-expect_like(qr/CF-myCF$$:/i, 'Verified initial empty value');
-expect_send("edit ticket/$ticket_id set 'CF-myCF$$=VALUE' ", 'Changing CF...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
-expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
-expect_like(qr/CF-myCF$$: VALUE/i, 'Verified change');
-# Test setting 0 as value of the custom field
-expect_send("edit ticket/$ticket_id set 'CF-myCF$$=0' ", 'Changing CF...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
-expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
-expect_like(qr/CF-myCF$$: 0/i, 'Verified change');
-# Test reading and setting custom fields with spaces
-expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking initial value');
-expect_like(qr/my CF$$:/i, 'Verified change');
-expect_send("edit ticket/$ticket_id set 'CF-my CF$$=VALUE' ", 'Changing CF...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
-expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking new value');
-expect_like(qr/my CF$$: VALUE/i, 'Verified change');
-expect_send("ls 'id = $ticket_id' -f 'CF-my CF$$'", 'Checking new value');
-expect_like(qr/my CF$$: VALUE/i, 'Verified change');
-
-# ...
-# change a ticket's ...[other properties]...
-# ...
-# stall a ticket
-expect_send("edit ticket/$ticket_id set status=stalled", 'Changing status to "stalled"...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed status');
-expect_send("show ticket/$ticket_id -f status", 'Verifying change...');
-expect_like(qr/Status: stalled/, 'Verified change');
-# resolve a ticket
-expect_send("edit ticket/$ticket_id set status=resolved", 'Changing status to "resolved"...');
-expect_like(qr/Ticket $ticket_id updated/, 'Changed status');
-expect_send("show ticket/$ticket_id -f status", 'Verifying change...');
-expect_like(qr/Status: resolved/, 'Verified change');
-# try to set status to an illegal value
-expect_send("edit ticket/$ticket_id set status=quux", 'Changing status to an illegal value...');
-expect_like(qr/illegal value/i, 'Errored out');
-expect_send("show ticket/$ticket_id -f status", 'Verifying lack of change...');
-expect_like(qr/Status: resolved/, 'Verified change');
-
-# }}}
-
-# {{{ display
-
-# show ticket list
-expect_send("ls -s -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets...');
-expect_like(qr/$ticket_id: new ticket/, 'Found our ticket');
-# show ticket list verbosely
-expect_send("ls -l -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets verbosely...');
-expect_like(qr/id: ticket\/$ticket_id/, 'Found our ticket');
-# show ticket
-expect_send("show -t ticket $ticket_id", 'Showing our ticket...');
-expect_like(qr/id: ticket\/$ticket_id/, 'Got our ticket');
-# show ticket history
-expect_send("show ticket/$ticket_id/history", 'Showing our ticket\'s history...');
-expect_like(qr/Ticket created by root/, 'Got our history');
-TODO: {
- local $TODO = "Cannot show verbose ticket history right now";
- # show ticket history verbosely
- expect_send("show -v ticket/$ticket_id/history", 'Showing our ticket\'s history verbosely...');
- expect_like(qr/Ticket created by root/, 'Got our history');
-}
-# get attachments from a ticket
-expect_send("show ticket/$ticket_id/attachments", 'Showing ticket attachments...');
-expect_like(qr/id: ticket\/$ticket_id\/attachments/, 'Got our ticket\'s attachments');
-expect_like(qr/Attachments: \d+:\s*\(\S+ \/ \d+\w+\)/, 'Our ticket has an attachment');
-expect_handle->before() =~ /Attachments: (\d+):\s*\((\S+)/;
-my $attachment_id = $1;
-my $attachment_type = $2;
-ok($attachment_id, "Got attachment id=$attachment_id $attachment_type");
-expect_send("show ticket/$ticket_id/attachments/$attachment_id", "Showing attachment $attachment_id...");
-expect_like(qr/ContentType: $attachment_type/, 'Got the attachment');
-
-# }}}
-
-# {{{ test user manipulation
-
-# creating users
-expect_send("create -t user set Name='NewUser$$' EmailAddress='fbar$$\@example.com'", 'Creating a user...');
-expect_like(qr/User \d+ created/, 'Created the user');
-expect_handle->before() =~ /User (\d+) created/;
-my $user_id = $1;
-ok($user_id, "Got user id=$user_id");
-# updating users
-expect_send("edit user/$user_id set Name='EditedUser$$'", 'Editing the user');
-expect_like(qr/User $user_id updated/, 'Edited the user');
-expect_send("show user/$user_id", 'Showing the user...');
-expect_like(qr/id: user\/$user_id/, 'Saw the user');
-expect_like(qr/Name: EditedUser$$/, 'Saw the modification');
-TODO: {
- todo_skip "Listing non-ticket items doesn't work", 2;
- expect_send("list -t user 'id > 0'", 'Listing the users...');
- expect_like(qr/$user_id: EditedUser$$/, 'Found the user');
-}
-
-# }}}
-
-# {{{ test group manipulation
-
-TODO: {
-todo_skip "Group manipulation doesn't work right now", 8;
-# creating groups
-expect_send("create -t group set Name='NewGroup$$'", 'Creating a group...');
-expect_like(qr/Group \d+ created/, 'Created the group');
-expect_handle->before() =~ /Group (\d+) created/;
-my $group_id = $1;
-ok($group_id, "Got group id=$group_id");
-# updating groups
-expect_send("edit group/$group_id set Name='EditedGroup$$'", 'Editing the group');
-expect_like(qr/Group $group_id updated/, 'Edited the group');
-expect_send("show group/$group_id", 'Showing the group...');
-expect_like(qr/id: group\/$group_id/, 'Saw the group');
-expect_like(qr/Name: EditedGroup$$/, 'Saw the modification');
-TODO: {
- local $TODO = "Listing non-ticket items doesn't work";
- expect_send("list -t group 'id > 0'", 'Listing the groups...');
- expect_like(qr/$group_id: EditedGroup$$/, 'Found the group');
-}
-}
-
-# }}}
-
-TODO: {
-todo_skip "Custom field manipulation not yet implemented", 8;
-# {{{ test custom field manipulation
-
-# creating custom fields
-expect_send("create -t custom_field set Name='NewCF$$'", 'Creating a custom field...');
-expect_like(qr/Custom Field \d+ created/, 'Created the custom field');
-expect_handle->before() =~ /Custom Field (\d+) created/;
-my $cf_id = $1;
-ok($cf_id, "Got custom field id=$cf_id");
-# updating custom fields
-expect_send("edit cf/$cf_id set Name='EditedCF$$'", 'Editing the custom field');
-expect_like(qr/Custom field $cf_id updated/, 'Edited the custom field');
-expect_send("show cf/$cf_id", 'Showing the queue...');
-expect_like(qr/id: custom_field\/$cf_id/, 'Saw the custom field');
-expect_like(qr/Name: EditedCF$$/, 'Saw the modification');
-TODO: {
- todo_skip "Listing non-ticket items doesn't work", 2;
- expect_send("list -t custom_field 'id > 0'", 'Listing the CFs...');
- expect_like(qr/$cf_id: EditedCF$$/, 'Found the custom field');
-}
-}
-
-# }}}
-
-# {{{ test merging tickets
-expect_send("create -t ticket set subject='CLIMergeTest1-$$'", 'Creating first ticket to merge...');
-expect_like(qr/Ticket \d+ created/, 'Created first ticket');
-expect_handle->before() =~ /Ticket (\d+) created/;
-my $merge_ticket_A = $1;
-ok($merge_ticket_A, "Got first ticket to merge id=$merge_ticket_A");
-expect_send("create -t ticket set subject='CLIMergeTest2-$$'", 'Creating second ticket to merge...');
-expect_like(qr/Ticket \d+ created/, 'Created second ticket');
-expect_handle->before() =~ /Ticket (\d+) created/;
-my $merge_ticket_B = $1;
-ok($merge_ticket_B, "Got second ticket to merge id=$merge_ticket_B");
-expect_send("merge $merge_ticket_B $merge_ticket_A", 'Merging the tickets...');
-expect_like(qr/Merge completed/, 'Merged the tickets');
-expect_send("show ticket/$merge_ticket_A/history", 'Checking merge on first ticket');
-expect_like(qr/Merged into ticket #$merge_ticket_A by root/, 'Merge recorded in first ticket');
-expect_send("show ticket/$merge_ticket_B/history", 'Checking merge on second ticket');
-expect_like(qr/Merged into ticket #$merge_ticket_A by root/, 'Merge recorded in second ticket');
-# }}}
-
-# {{{ test taking/stealing tickets
-{
- # create a user; give them privileges to take and steal
- ### TODO: implement 'grant' in the CLI tool; use that here instead.
- ### this breaks the abstraction barrier, like, a lot.
- my $steal_user = RT::User->new($RT::SystemUser);
- my ($steal_user_id, $msg) = $steal_user->Create( Name => "fooser$$",
- EmailAddress => "fooser$$\@localhost",
- Privileged => 1,
- Password => 'foobar',
- );
- ok($steal_user_id, "Created the user? $msg");
- my $steal_queue = RT::Queue->new($RT::SystemUser);
- my $steal_queue_id;
- ($steal_queue_id, $msg) = $steal_queue->Create( Name => "Steal$$" );
- ok($steal_queue_id, "Got the queue? $msg");
- ok($steal_queue->id, "queue obj has id");
- my $status;
- ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'ShowTicket', Object => $steal_queue );
- ok($status, "Gave 'SeeTicket' to our user? $msg");
- ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $steal_queue );
- ok($status, "Gave 'OwnTicket' to our user? $msg");
- ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'StealTicket', Object => $steal_queue );
- ok($status, "Gave 'StealTicket' to our user? $msg");
- ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'TakeTicket', Object => $steal_queue );
- ok($status, "Gave 'TakeTicket' to our user? $msg");
-
- # create a ticket to take/steal
- expect_send("create -t ticket set queue=$steal_queue_id subject='CLIStealTest-$$'", 'Creating ticket to steal...');
- expect_like(qr/Ticket \d+ created/, 'Created ticket');
- expect_handle->before() =~ /Ticket (\d+) created/;
- my $steal_ticket_id = $1;
- ok($steal_ticket_id, "Got ticket to steal id=$steal_ticket_id");
-
- # root takes the ticket
- expect_send("take $steal_ticket_id", 'root takes the ticket...');
- expect_like(qr/Owner changed from Nobody to root/, 'root took the ticket');
-
- # log in as the non-root user
- #expect_quit(); # this is apparently unnecessary, but I'll leave it in
- # until I'm sure
- $ENV{'RTUSER'} = "fooser$$";
- $ENV{'RTPASSWD'} = 'foobar';
- expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',);
-
- # user tries to take the ticket, fails
- # shouldn't be able to 'take' a ticket which someone else has taken out from
- # under you; that should produce an error. should have to explicitly
- # 'steal' it back from them. 'steal' can automatically 'take' a ticket,
- # though.
- expect_send("take $steal_ticket_id", 'user tries to take the ticket...');
- expect_like(qr/You can only take tickets that are unowned/, '...and fails.');
- expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...');
- expect_like(qr/Owner: root/, '...no change.');
-
- # user steals the ticket
- expect_send("steal $steal_ticket_id", 'user tries to *steal* the ticket...');
- expect_like(qr/Owner changed from root to fooser$$/, '...and succeeds!');
- expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...');
- expect_like(qr/Owner: fooser$$/, '...yup, it worked.');
-
- # log back in as root
- #expect_quit(); # ditto
- $ENV{'RTUSER'} = 'root';
- $ENV{'RTPASSWD'} = 'password';
- expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',);
-
- # root steals the ticket back
- expect_send("steal $steal_ticket_id", 'root steals the ticket back...');
- expect_like(qr/Owner changed from fooser$$ to root/, '...and succeeds.');
-}
-# }}}
-
-# {{{ test ticket linking
- my @link_relns = ( 'DependsOn', 'DependedOnBy', 'RefersTo', 'ReferredToBy',
- 'MemberOf', 'HasMember', );
- my %display_relns = map { $_ => $_ } @link_relns;
- $display_relns{HasMember} = 'Members';
-
- my $link1_id = ok_create_ticket( "LinkTicket1-$$" );
- my $link2_id = ok_create_ticket( "LinkTicket2-$$" );
-
- foreach my $reln (@link_relns) {
- # create link
- expect_send("link $link1_id $reln $link2_id", "Link by $reln...");
- expect_like(qr/Created link $link1_id $reln $link2_id/, 'Linked');
- expect_send("show ticket/$link1_id/links", "Checking creation of $reln...");
- expect_like(qr/$display_relns{reln}: [\w\d\.\-]+:\/\/[\w\d\.]+\/ticket\/$link2_id/, "Created link $reln");
-
- # delete link
- expect_send("link -d $link1_id $reln $link2_id", "Delete $reln...");
- expect_like(qr/Deleted link $link1_id $reln $link2_id/, 'Deleted');
- expect_send("show ticket/$link1_id/links", "Checking removal of $reln...");
- ok( expect_handle->before() !~ /\Q$display_relns{$reln}: \E[\w\d\.\-]+:\/\/[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln" );
- #expect_unlike(qr/\Q$reln: \E[\w\d\.]+\Q://\E[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln");
-
- }
-# }}}
-
-
-# helper function
-sub ok_create_ticket {
- my $subject = shift;
-
- expect_send("create -t ticket set subject='$subject'", 'Creating ticket...');
- expect_like(qr/Ticket \d+ created/, "Created ticket '$subject'");
- expect_handle->before() =~ /Ticket (\d+) created/;
- my $id = $1;
- ok($id, "Got ticket id=$id");
-
- return $id;
-}
-
-# wrap up all the file handling stuff for attachment testing
-sub check_attachment {
- my $attachment_path = shift;
- (my $filename = $attachment_path) =~ s/.*\/(.*?)$/$1/;
- expect_send("comment -m 'attach file' -a $attachment_path $ticket_id", "Adding an attachment ($filename)");
- expect_like(qr/Message recorded/, "Added the attachment");
- expect_send("show ticket/$ticket_id/attachments","Finding Attachment");
- my $attachment_regex = qr/(\d+):\s+$filename/;
- expect_like($attachment_regex,"Attachment Uploaded");
- expect_handle->before() =~ $attachment_regex;
- my $attachment_id = $1;
- expect_send("show ticket/$ticket_id/attachments/$attachment_id/content","Fetching Attachment");
- open (my $fh, $attachment_path) or die "Can't open $attachment_path: $!";
- my $attachment_content = do { local($/); <$fh> };
- close $fh;
- chomp $attachment_content;
- expect_is($attachment_content,"Attachment contains original text");
-}
-
-1;
diff --git a/rt/lib/t/regression/27verp.t b/rt/lib/t/regression/27verp.t
deleted file mode 100644
index 856681b..0000000
--- a/rt/lib/t/regression/27verp.t
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Test::More tests => 1;
-
-TODO: {
- todo_skip "No tests written for VERP yet", 1;
- ok(1,"a test to skip");
-}
diff --git a/rt/lib/t/regression/mime_tests b/rt/lib/t/regression/mime_tests
deleted file mode 100644
index 26e4dbf..0000000
--- a/rt/lib/t/regression/mime_tests
+++ /dev/null
@@ -1,19 +0,0 @@
-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/lib/t/setup_regression.t b/rt/lib/t/setup_regression.t
deleted file mode 100644
index 36f809b..0000000
--- a/rt/lib/t/setup_regression.t
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/perl
-
-use Test::More qw(no_plan);
-
-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');
-
-
diff --git a/rt/sbin/extract_pod_tests b/rt/sbin/extract_pod_tests
deleted file mode 100644
index 897564d..0000000
--- a/rt/sbin/extract_pod_tests
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/perl
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-use strict;
-use 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;
-}
-
-
-print $outfh <<EOF;
-
-use Test::More qw/no_plan/;
-use RT;
-RT::LoadConfig();
-RT::Init();
-
-EOF
-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/regression_harness b/rt/sbin/regression_harness
deleted file mode 100644
index 7460135..0000000
--- a/rt/sbin/regression_harness
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/perl
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-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-dump-database b/rt/sbin/rt-dump-database
deleted file mode 100755
index ce023ad..0000000
--- a/rt/sbin/rt-dump-database
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-use strict;
-
-# As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure
-# it is UTF-8 so further XMLin will not break
-binmode(STDOUT, ":utf8");
-
-# fix lib paths, some may be relative
-BEGIN {
- require File::Spec;
- my @libs = ("lib", "local/lib");
- my $bin_path;
-
- for my $lib (@libs) {
- unless ( File::Spec->file_name_is_absolute($lib) ) {
- unless ($bin_path) {
- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
- }
- else {
- require FindBin;
- no warnings "once";
- $bin_path = $FindBin::Bin;
- }
- }
- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
- }
- unshift @INC, $lib;
- }
-
-}
-
-use RT;
-use XML::Simple;
-
-RT::LoadConfig();
-RT::Init();
-
-my $LocalOnly = @ARGV ? shift(@ARGV) : 1;
-
-my %RV;
-my %Ignore = (
- All => [qw(
- id Created Creator LastUpdated LastUpdatedBy
- )],
- Templates => [qw(
- TranslationOf
- )],
-);
-
-my $SystemUserId = $RT::SystemUser->Id;
-my @classes = qw(
- Users Groups Queues ScripActions ScripConditions
- Templates Scrips ACL CustomFields
-);
-foreach my $class (@classes) {
- require "RT/$class.pm";
- my $objects = "RT::$class"->new($RT::SystemUser);
- $objects->{find_disabled_rows} = 1;
- $objects->UnLimit;
-
- if ($class eq 'CustomFields') {
- $objects->OrderByCols(
- { FIELD => 'LookupType' },
- { FIELD => 'SortOrder' },
- { FIELD => 'Id' },
- );
- }
- else {
- $objects->OrderBy( FIELD => 'Id' );
- }
-
- if ($LocalOnly) {
- next if $class eq 'ACL'; # XXX - would go into infinite loop - XXX
- $objects->Limit( FIELD => 'LastUpdatedBy', OPERATOR => '!=', VALUE => $SystemUserId )
- unless $class eq 'Groups';
- $objects->Limit( FIELD => 'Id', OPERATOR => '!=', VALUE => $SystemUserId )
- if $class eq 'Users';
- $objects->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined' )
- if $class eq 'Groups';
- }
-
- my %fields;
- while (my $obj = $objects->Next) {
- next if $obj->can('LastUpdatedBy') and $obj->LastUpdatedBy == $SystemUserId;
-
- if (!%fields) {
- %fields = map { $_ => 1 } keys %{$obj->_ClassAccessible};
- delete @fields{
- @{$Ignore{$class}||=[]},
- @{$Ignore{All}||=[]},
- };
- }
-
- my $rv;
- # next if $obj-> # skip default names
- foreach my $field (sort keys %fields) {
- my $value = $obj->__Value($field);
- $rv->{$field} = $value if ( defined ($value) && length($value) );
- }
- delete $rv->{Disabled} unless $rv->{Disabled};
-
- foreach my $record (map { /ACL/ ? 'ACE' : substr($_, 0, -1) } @classes) {
- foreach my $key (map "$record$_", ('', 'Id')) {
- next unless exists $rv->{$key};
- my $id = $rv->{$key} or next;
- my $obj = "RT::$record"->new($RT::SystemUser);
- $obj->LoadByCols( Id => $id ) or next;
- $rv->{$key} = $obj->__Value('Name') || 0;
- }
- }
-
- if ($class eq 'Users' and defined $obj->Privileged) {
- $rv->{Privileged} = int($obj->Privileged);
- }
- elsif ($class eq 'CustomFields') {
- my $values = $obj->Values;
- while (my $value = $values->Next) {
- push @{$rv->{Values}}, {
- map { ($_ => $value->__Value($_)) } qw(
- Name Description SortOrder
- ),
- };
- }
- }
-
- if (eval { require RT::Attributes; 1 }) {
- my $attributes = $obj->Attributes;
- while (my $attribute = $attributes->Next) {
- my $content = $attribute->Content;
- $rv->{Attributes}{$attribute->Name} = $content if length($content);
- }
- }
-
- push @{$RV{$class}}, $rv;
- }
-}
-
-print(<< ".");
-no strict; use XML::Simple; *_ = XMLin(do { local \$/; readline(DATA) }, ForceArray => [qw(
- @classes Values
-)], NoAttr => 1, SuppressEmpty => ''); *\$_ = (\$_{\$_} || []) for keys \%_; 1; # vim: ft=xml
-__DATA__
-.
-
-print XMLout(
- { map { ($_ => ($RV{$_} || [])) } @classes },
- RootName => 'InitialData',
- NoAttr => 1,
- SuppressEmpty => '',
- XMLDecl => '<?xml version="1.0" encoding="UTF-8"?>',
-);
diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database
deleted file mode 100644
index d4a256f..0000000
--- a/rt/sbin/rt-setup-database
+++ /dev/null
@@ -1,474 +0,0 @@
-#!/usr/bin/perl
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-use strict;
-use warnings;
-
-use vars qw($Nobody $SystemUser $item);
-
-# fix lib paths, some may be relative
-BEGIN {
- require File::Spec;
- my @libs = ("lib", "local/lib");
- my $bin_path;
-
- for my $lib (@libs) {
- unless ( File::Spec->file_name_is_absolute($lib) ) {
- unless ($bin_path) {
- if ( File::Spec->file_name_is_absolute(__FILE__) ) {
- $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
- }
- else {
- require FindBin;
- no warnings "once";
- $bin_path = $FindBin::Bin;
- }
- }
- $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
- }
- unshift @INC, $lib;
- }
-
-}
-
-#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
-BEGIN {
- use RT;
- RT::LoadConfig();
- RT::InitClasses();
-}
-
-use Term::ReadKey;
-use Getopt::Long;
-
-$| = 1; # unbuffer all output.
-
-my %args;
-GetOptions(
- \%args,
- 'action=s',
- 'force', 'debug',
- 'dba=s', 'dba-password=s', 'prompt-for-dba-password',
- 'datafile=s', 'datadir=s'
-);
-
-unless ( $args{'action'} ) {
- help();
- exit(-1);
-}
-
-# check and setup @actions
-my @actions = grep $_, split /,/, $args{'action'};
-if ( @actions > 1 && $args{'datafile'} ) {
- print STDERR "You can not use --datafile option with multiple actions.\n";
- exit(-1);
-}
-foreach ( @actions ) {
- unless ( /^(?:init|create|drop|schema|acl|coredata|insert|upgrade)$/ ) {
- print STDERR "$0 called with an invalid --action parameter.\n";
- exit(-1);
- }
- if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) {
- print STDERR "You can not mix init, drop or upgrade action with any action.\n";
- exit(-1);
- }
-}
-
-# convert init to multiple actions
-my $init = 0;
-if ( $actions[0] eq 'init' ) {
- @actions = qw(create schema acl coredata insert);
- $init = 1;
-}
-
-# set options from environment
-foreach my $key(qw(Type Host Name User Password)) {
- next unless exists $ENV{ 'RT_DB_'. uc $key };
- print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n";
- RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key });
-}
-
-my $db_type = RT->Config->Get('DatabaseType') || '';
-my $db_host = RT->Config->Get('DatabaseHost') || '';
-my $db_name = RT->Config->Get('DatabaseName') || '';
-my $db_user = RT->Config->Get('DatabaseUser') || '';
-my $db_pass = RT->Config->Get('DatabasePassword') || '';
-
-# load it here to get error immidiatly if DB type is not supported
-require RT::Handle;
-
-if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
- $db_name = File::Spec->catfile($RT::VarPath, $db_name);
- RT->Config->Set( DatabaseName => $db_name );
-}
-
-my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || '';
-my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'};
-
-if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
- $dba_pass = get_dba_password();
- chomp $dba_pass if defined($dba_pass);
-}
-
-print "Working with:\n"
- ."Type:\t$db_type\nHost:\t$db_host\nName:\t$db_name\n"
- ."User:\t$db_user\nDBA:\t$dba_user\n";
-
-foreach my $action ( @actions ) {
- no strict 'refs';
- my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args );
- error($action, $msg) unless $status;
- print $msg ."\n" if $msg;
- print "Done.\n";
-}
-
-sub action_create {
- my %args = @_;
- my $dbh = get_system_dbh();
- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
- return ($status, $msg) unless $status;
-
- print "Now creating a $db_type database $db_name for RT.\n";
- return RT::Handle->CreateDatabase( $dbh );
-}
-
-sub action_drop {
- my %args = @_;
-
- print "Dropping $db_type database $db_name.\n";
- unless ( $args{'force'} ) {
- print <<END;
-
-About to drop $db_type database $db_name on $db_host.
-WARNING: This will erase all data in $db_name.
-
-END
- exit(-2) unless _yesno();
- }
-
- my $dbh = get_system_dbh();
- return RT::Handle->DropDatabase( $dbh );
-}
-
-sub action_schema {
- my %args = @_;
- my $dbh = get_admin_dbh();
- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
- return ($status, $msg) unless $status;
-
- print "Now populating database schema.\n";
- return RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} );
-}
-
-sub action_acl {
- my %args = @_;
- my $dbh = get_admin_dbh();
- my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
- return ($status, $msg) unless $status;
-
- print "Now inserting database ACLs\n";
- return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} );
-}
-
-sub action_coredata {
- my %args = @_;
- $RT::Handle = new RT::Handle;
- $RT::Handle->dbh( undef );
- RT::ConnectToDatabase();
- RT::InitLogging();
- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
- return ($status, $msg) unless $status;
-
- print "Now inserting RT core system objects\n";
- return $RT::Handle->InsertInitialData;
-}
-
-sub action_insert {
- my %args = @_;
- $RT::Handle = new RT::Handle;
- RT::Init();
- my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
- return ($status, $msg) unless $status;
-
- print "Now inserting data\n";
- my $file = $args{'datafile'};
- $file = $RT::EtcPath . "/initialdata" if $init && !$file;
- $file ||= $args{'datadir'}."/content";
- return $RT::Handle->InsertData( $file );
-}
-
-sub action_upgrade {
- my %args = @_;
- my $base_dir = $args{'datadir'} || "./etc/upgrade";
- return (0, "Couldn't read dir '$base_dir' with upgrade data")
- unless -d $base_dir || -r _;
-
- my $upgrading_from = undef;
- do {
- if ( defined $upgrading_from ) {
- print "Doesn't match #.#.#: ";
- } else {
- print "Enter RT version you're upgrading from: ";
- }
- $upgrading_from = scalar <STDIN>;
- chomp $upgrading_from;
- $upgrading_from =~ s/\s+//g;
- } while $upgrading_from !~ /^\d+\.\d+\.\d+$/;
-
- my $upgrading_to = $RT::VERSION;
- return (0, "The current version $upgrading_to is lower than $upgrading_from")
- if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0;
-
- return (1, "The version $upgrading_to you're upgrading to is up to date")
- if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0;
-
- my @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
-
- return (1, "No DB changes between $upgrading_from and $upgrading_to")
- unless @versions;
-
- print "\nGoing to apply following upgrades:\n";
- print map "* $_\n", @versions;
-
- {
- my $custom_upgrading_to = undef;
- do {
- if ( defined $custom_upgrading_to ) {
- print "Doesn't match #.#.#: ";
- } else {
- print "\nEnter RT version if you want to stop upgrade at some point,\n";
- print " or leave it blank if you want apply above upgrades: ";
- }
- $custom_upgrading_to = scalar <STDIN>;
- chomp $custom_upgrading_to;
- $custom_upgrading_to =~ s/\s+//g;
- last unless $custom_upgrading_to;
- } while $custom_upgrading_to !~ /^\d+\.\d+\.\d+$/;
-
- if ( $custom_upgrading_to ) {
- return (
- 0, "The version you entered ($custom_upgrading_to) is lower than\n"
- ."version you're upgrading from ($upgrading_from)"
- ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0;
-
- return (1, "The version you're upgrading to is up to date")
- if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0;
-
- if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) {
- print "Version you entered is greater than installed ($RT::VERSION).\n";
- _yesno() or exit(-2);
- }
- # ok, checked everything no let's refresh list
- $upgrading_to = $custom_upgrading_to;
- @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
-
- return (1, "No DB changes between $upgrading_from and $upgrading_to")
- unless @versions;
-
- print "\nGoing to apply following upgrades:\n";
- print map "* $_\n", @versions;
- }
- }
-
- print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n";
- _yesno() or exit(-2) unless $args{'force'};
-
- foreach my $v ( @versions ) {
- print "Processing $v\n";
- my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef);
- if ( -e "$base_dir/$v/schema.$db_type" ) {
- action_schema( %tmp );
- }
- if ( -e "$base_dir/$v/acl.$db_type" ) {
- action_acl( %tmp );
- }
- if ( -e "$base_dir/$v/content" ) {
- action_insert( %tmp );
- }
- }
- return 1;
-}
-
-sub get_versions_from_to {
- my ($base_dir, $from, $to) = @_;
-
- opendir my $dh, $base_dir or die "couldn't open dir: $!";
- my @versions = grep -d "$base_dir/$_" && /\d+\.\d+\.\d+/, readdir $dh;
- closedir $dh;
-
- return
- grep RT::Handle::cmp_version($_, $to) <= 0,
- grep RT::Handle::cmp_version($_, $from) > 0,
- sort RT::Handle::cmp_version @versions;
-}
-
-sub error {
- my ($action, $msg) = @_;
- print STDERR "Couldn't finish '$action' step.\n\n";
- print STDERR "ERROR: $msg\n\n";
- exit(-1);
-}
-
-sub get_dba_password {
- print "In order to create or update your RT database,"
- . " this script needs to connect to your "
- . " $db_type instance on $db_host as $dba_user\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');
- print "\n";
- return ($password);
-}
-
-=head2 get_system_dbh
-
-Returns L<DBI> database handle connected to B<system> with DBA credentials.
-
-See also L<RT::Handle/SystemDSN>.
-
-=cut
-
-sub get_system_dbh {
- return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass );
-}
-
-sub get_admin_dbh {
- return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass );
-}
-
-=head2 get_rt_dbh [USER, PASSWORD]
-
-Returns L<DBI> database handle connected to RT database,
-you may specify credentials(USER and PASSWORD) to connect
-with. By default connects with credentials from RT config.
-
-=cut
-
-sub get_rt_dbh {
- return _get_dbh( RT::Handle->DSN, $db_user, $db_pass );
-}
-
-sub _get_dbh {
- my ($dsn, $user, $pass) = @_;
- my $dbh = DBI->connect(
- $dsn, $user, $pass,
- { RaiseError => 0, PrintError => 0 },
- );
- unless ( $dbh ) {
- my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr;
- if ( $args{'debug'} ) {
- require Carp; Carp::confess( $msg );
- } else {
- print STDERR $msg; exit -1;
- }
- }
- return $dbh;
-}
-
-sub _yesno {
- print "Proceed [y/N]:";
- my $x = scalar(<STDIN>);
- $x =~ /^y/i;
-}
-
-sub help {
-
- print <<EOF;
-
-$0: Set up RT's database
-
---action init Initialize the database. This is combination of
- multiple actions listed below. Create DB, schema,
- setup acl, insert core data and initial data.
-
- upgrade Apply all needed schema/acl/content updates (will ask
- for version to upgrade from)
-
- create Create the database.
-
- drop Drop the database.
- This will ERASE ALL YOUR DATA
-
- schema Initialize only the database schema
- To use a local or supplementary datafile, specify it
- using the '--datadir' option below.
-
- acl Initialize only the database ACLs
- To use a local or supplementary datafile, specify it
- using the '--datadir' option below.
-
- coredata Insert data into RT's database. This data is required
- for normal functioning of any RT instance.
-
- 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.
-
-Several actions can be combined using comma separated list.
-
---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
index a51076f..ea9b99b 100644
--- a/rt/sbin/rt-setup-database.in
+++ b/rt/sbin/rt-setup-database.in
@@ -150,7 +150,9 @@ if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
}
my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || '';
-my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'};
+my $dba_pass = exists($args{'dba-password'})
+ ? $args{'dba-password'}
+ : $ENV{'RT_DBA_PASSWORD'};
if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
$dba_pass = get_dba_password();
diff --git a/rt/sbin/rt-test-dependencies b/rt/sbin/rt-test-dependencies
deleted file mode 100644
index d6e018a..0000000
--- a/rt/sbin/rt-test-dependencies
+++ /dev/null
@@ -1,600 +0,0 @@
-#!/usr/bin/perl
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-#
-# 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;
-my %args;
-my %deps;
-GetOptions(
- \%args, 'v|verbose',
- 'install', 'with-MYSQL',
- 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
- 'with-ORACLE', 'with-FASTCGI', 'with-FASTCGI-SERVER',
- 'with-SPEEDYCGI', 'with-MODPERL1',
- 'with-MODPERL2', 'with-DEV',
- 'with-STANDALONE',
-
- 'with-GPG',
- 'with-ICAL',
- 'with-SMTP',
- 'with-GRAPHVIZ',
- 'with-GD',
- 'with-DASHBOARDS',
-
- 'download=s',
- 'repository=s',
- 'list-deps'
-);
-
-unless (keys %args) {
- help();
- exit(1);
-}
-
-# Set up defaults
-my %default = (
- 'with-MASON' => 1,
- 'with-CORE' => 1,
- 'with-CLI' => 1,
- 'with-MAILGATE' => 1,
- 'with-DEV' => 0,
- 'with-STANDALONE' => 1,
- 'with-GPG' => 1,
- 'with-ICAL' => 1,
- 'with-SMTP' => 1,
- 'with-GRAPHVIZ' => 0,
- 'with-GD' => 1,
- 'with-DASHBOARDS' => 1
-);
-$args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
-
-{
- my $section;
- my %always_show_sections = (
- perl => 1,
- users => 1,
- );
-
- sub section {
- my $s = shift;
- $section = $s;
- print "$s:\n" unless $args{'list-deps'};
- }
-
- sub print_found {
- my $msg = shift;
- my $test = shift;
- my $extra = shift;
-
- unless ( $args{'list-deps'} ) {
- if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
- print "\t$msg ...";
- print $test ? "found" : "MISSING";
- print "\n";
- }
-
- print "\t\t$extra\n" if defined $extra;
- }
- }
-}
-
-sub conclude {
- my %missing_by_type = @_;
-
- unless ( $args{'list-deps'} ) {
- unless ( keys %missing_by_type ) {
- print "\nAll dependencies have been found.\n";
- return;
- }
-
- print "\nSOME DEPENDENCIES WERE MISSING.\n";
-
- for my $type ( keys %missing_by_type ) {
- my $missing = $missing_by_type{$type};
-
- print "$type missing dependencies:\n";
- for my $name ( keys %$missing ) {
- my $module = $missing->{$name};
- my $version = $module->{version};
- my $error = $module->{error};
- print_found( $name . ( $version && !$error ? " >= $version" : "" ),
- 0, $module->{error} );
- }
- }
- exit 1;
- }
-}
-
-
-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-oracle Database interface for Oracle
- --with-sqlite Database interface and driver for SQLite (unsupported)
-
- --with-standalone Libraries needed to support the standalone simple pure perl server
- --with-fastcgi-server Libraries needed to support the external fastcgi server
- --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
-
-You can also specify -v or --verbose to list the status of all dependencies,
-rather than just the missing ones.
-
-The "RT_FIX_DEPS_CMD" environment variable, if set, will be used
-instead of the standard CPAN shell by --install to install any
-required modules. It will be called with the module name, or, if
-"RT_FIX_DEPS_CMD" contains a "%s", will replace the "%s" with the
-module name before calling the program.
-.
-}
-
-
-sub text_to_hash {
- my %hash;
- for my $line ( split /\n/, $_[0] ) {
- my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
- $value ||= '';
- $hash{$key} = $value;
- }
-
- return %hash;
-}
-
-$deps{'CORE'} = [ text_to_hash( << '.') ];
-Digest::base
-Digest::MD5 2.27
-DBI 1.37
-Class::ReturnValue 0.40
-DBIx::SearchBuilder 1.54
-Text::Template 1.44
-File::ShareDir
-File::Spec 0.8
-HTML::Entities
-HTML::Scrubber 0.08
-Log::Dispatch 2.0
-Sys::Syslog 0.16
-Locale::Maketext 1.06
-Locale::Maketext::Lexicon 0.32
-Locale::Maketext::Fuzzy
-MIME::Entity 5.425
-Mail::Mailer 1.57
-Email::Address
-Text::Wrapper
-Time::ParseDate
-Time::HiRes
-File::Temp 0.18
-Text::Quoted 2.02
-Tree::Simple 1.04
-UNIVERSAL::require
-Regexp::Common
-Scalar::Util
-Module::Versions::Report 1.05
-Cache::Simple::TimedExpiry
-Calendar::Simple
-Encode 2.21
-CSS::Squish 0.06
-File::Glob
-Devel::StackTrace 1.19
-.
-
-$deps{'MASON'} = [ text_to_hash( << '.') ];
-HTML::Mason 1.36
-Errno
-Digest::MD5 2.27
-CGI::Cookie 1.20
-Storable 2.08
-Apache::Session 1.53
-XML::RSS 1.05
-Text::WikiFormat 0.76
-CSS::Squish 0.06
-Devel::StackTrace 1.19
-.
-
-$deps{'STANDALONE'} = [ text_to_hash( << '.') ];
-HTTP::Server::Simple 0.34
-HTTP::Server::Simple::Mason 0.09
-Net::Server
-.
-
-$deps{'MAILGATE'} = [ text_to_hash( << '.') ];
-HTML::TreeBuilder
-HTML::FormatText
-Getopt::Long
-LWP::UserAgent
-Pod::Usage
-.
-
-$deps{'CLI'} = [ text_to_hash( << '.') ];
-Getopt::Long 2.24
-LWP
-HTTP::Request::Common
-Text::ParseWords
-Term::ReadLine
-Term::ReadKey
-.
-
-$deps{'DEV'} = [ text_to_hash( << '.') ];
-HTML::Form
-HTML::TokeParser
-WWW::Mechanize
-Test::WWW::Mechanize 1.04
-Module::Refresh 0.03
-Test::Expect 0.31
-XML::Simple
-File::Find
-Test::Deep 0 # needed for shredder tests
-String::ShellQuote 0 # needed for gnupg-incoming.t
-Test::HTTP::Server::Simple 0.09
-Test::HTTP::Server::Simple::StashWarnings 0.02
-Log::Dispatch::Perl
-Test::Warn
-Test::Builder 0.77 # needed to fix TODO test
-IPC::Run3
-Test::MockTime
-HTTP::Server::Simple::Mason 0.13
-Log::Dispatch::Perl
-.
-
-$deps{'FASTCGI'} = [ text_to_hash( << '.') ];
-CGI 3.38
-FCGI
-CGI::Fast
-.
-
-$deps{'FASTCGI-SERVER'} = [ text_to_hash( << '.') ];
-CGI 3.38
-CGI::Fast
-FCGI::ProcManager
-File::Basename
-File::Spec
-Getopt::Long
-Pod::Usage
-.
-
-$deps{'SPEEDYCGI'} = [ text_to_hash( << '.') ];
-CGI 3.38
-CGI::SpeedyCGI
-.
-
-
-$deps{'MODPERL1'} = [ text_to_hash( << '.') ];
-CGI 3.38
-Apache::Request
-Apache::DBI 0.92
-.
-
-$deps{'MODPERL2'} = [ text_to_hash( << '.') ];
-CGI 3.38
-Apache::DBI
-HTML::Mason 1.36
-.
-
-$deps{'MYSQL'} = [ text_to_hash( << '.') ];
-DBD::mysql 2.1018
-.
-
-$deps{'ORACLE'} = [ text_to_hash( << '.') ];
-DBD::Oracle
-.
-
-$deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
-DBD::Pg 1.43
-.
-
-$deps{'SQLITE'} = [ text_to_hash( << '.') ];
-DBD::SQLite 1.00
-.
-
-$deps{'GPG'} = [ text_to_hash( << '.') ];
-GnuPG::Interface
-PerlIO::eol
-.
-
-$deps{'ICAL'} = [ text_to_hash( << '.') ];
-Data::ICal
-.
-
-$deps{'SMTP'} = [ text_to_hash( << '.') ];
-Net::SMTP
-.
-
-$deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
-HTML::RewriteAttributes 0.02
-MIME::Types
-.
-
-$deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
-GraphViz
-IPC::Run
-IPC::Run::SafeHandles
-.
-
-$deps{'GD'} = [ text_to_hash( << '.') ];
-GD
-GD::Graph
-GD::Text
-.
-
-my %AVOID = (
- 'DBD::Oracle' => [qw(1.23)],
-);
-
-if ($args{'download'}) {
- download_mods();
-}
-
-
-check_perl_version();
-
-check_users();
-
-my %Missing_By_Type = ();
-foreach my $type (sort grep $args{$_}, keys %args) {
- next unless ($type =~ /^with-(.*?)$/);
-
- $type = $1;
- section("$type dependencies");
-
- my @missing;
- my @deps = @{ $deps{$type} };
-
- my %missing = test_deps(@deps);
-
- if ( $args{'install'} ) {
- for my $module (keys %missing) {
- resolve_dep($module, $missing{$module}{version});
- delete $missing{$module}
- if test_dep($module, $missing{$module}{version}, $AVOID{$module});
- }
- }
-
- $Missing_By_Type{$type} = \%missing if keys %missing;
-}
-
-conclude(%Missing_By_Type);
-
-sub test_deps {
- my @deps = @_;
-
- my %missing;
- while(@deps) {
- my $module = shift @deps;
- my $version = shift @deps;
- my($test, $error) = test_dep($module, $version, $AVOID{$module});
- my $msg = $module . ($version && !$error ? " >= $version" : '');
- print_found($msg, $test, $error);
-
- $missing{$module} = { version => $version, error => $error } unless $test;
- }
-
- return %missing;
-}
-
-sub test_dep {
- my $module = shift;
- my $version = shift;
- my $avoid = shift;
-
- if ( $args{'list-deps'} ) {
- print $module, ': ', $version || 0, "\n";
- }
- else {
- eval "use $module $version ()";
- if ( my $error = $@ ) {
- return 0 unless wantarray;
-
- $error =~ s/\n(.*)$//s;
- $error =~ s/at \(eval \d+\) line \d+\.$//;
- undef $error if $error =~ /this is only/;
-
- return ( 0, $error );
- }
-
- if ( $avoid ) {
- my $version = $module->VERSION;
- if ( grep $version eq $_, @$avoid ) {
- return 0 unless wantarray;
- return (0, "It's known that there are problems with RT and version '$version' of '$module' module. If it's the latest available version of the module then you have to downgrade manually.");
- }
- }
-
- return 1;
- }
-}
-
-sub resolve_dep {
- my $module = shift;
- my $version = shift;
-
- print "\nInstall module $module\n";
-
- my $ext = $ENV{'RT_FIX_DEPS_CMD'};
- unless( $ext ) {
- my $configured = 1;
- {
- local @INC = @INC;
- if ( $ENV{'HOME'} ) {
- unshift @INC, "$ENV{'HOME'}/.cpan";
- }
- $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
- }
- unless ( $configured ) {
- print <<END;
-You haven't configured the CPAN shell yet.
-Please run `/usr/bin/perl -MCPAN -e shell` to configure it.
-END
- exit(1);
- }
- my $rv = eval { require CPAN; CPAN::Shell->install($module) };
- return $rv unless $@;
-
- print <<END;
-Failed to load module CPAN.
-
--------- Error ---------
-$@
-------------------------
-
-When we tried to start installing RT's perl dependencies,
-we were unable to load the CPAN client. This module is usually distributed
-with Perl. This usually indicates that your vendor has shipped an unconfigured
-or incorrectly configured CPAN client.
-The error above may (or may not) give you a hint about what went wrong
-
-You have several choices about how to install dependencies in
-this situatation:
-
-1) use a different tool to install dependencies by running setting the following
- shell environment variable and rerunning this tool:
- RT_FIX_DEPS_CMD='/usr/bin/perl -MCPAN -e"install %s"'
-2) Attempt to configure CPAN by running:
- `/usr/bin/perl -MCPAN -e shell` program from shell.
- If this fails, you may have to manually upgrade CPAN (see below)
-3) Try to update the CPAN client. Download it from:
- http://search.cpan.org/dist/CPAN and try again
-4) Install each dependency manually by downloading them one by one from
- http://search.cpan.org
-
-END
- exit(1);
- }
-
- if( $ext =~ /\%s/) {
- $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
- } else {
- $ext .= " $module";
- }
- print "\t\tcommand: '$ext'\n";
- return scalar `$ext 1>&2`;
-}
-
-sub download_mods {
- my %modules;
- use CPAN;
-
- foreach my $key (keys %deps) {
- my @deps = (@{$deps{$key}});
- while (@deps) {
- my $mod = shift @deps;
- my $ver = shift @deps;
- next if ($mod =~ /^(DBD-|Apache-Request)/);
- $modules{$mod} = $ver;
- }
- }
- my @mods = keys %modules;
- CPAN::get();
- my $moddir = $args{'download'};
- foreach my $mod (@mods) {
- $CPAN::Config->{'build_dir'} = $moddir;
- CPAN::get($mod);
- }
-
- opendir(DIR, $moddir);
- while ( my $dir = readdir(DIR)) {
- print "Dir is $dir\n";
- next if ( $dir =~ /^\.\.?$/);
-
- # Skip things we've previously tagged
- my $out = `svn ls $args{'repository'}/tags/$dir`;
- next if ($out);
-
- if ($dir =~ /^(.*)-(.*?)$/) {
- `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
- `rm -rf $moddir/$dir`;
-
- }
-
- }
- closedir(DIR);
- exit;
-}
-
-sub check_perl_version {
- section("perl");
- eval {require 5.008003};
- if ($@) {
- print_found("5.8.3", 0,"RT is known to be non-functional on versions of perl older than 5.8.3. Please upgrade to 5.8.3 or newer.");
- exit(1);
- } else {
- print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
- }
-}
-
-sub check_users {
- section("users");
- print_found("rt group (www)", defined getgrnam("www"));
- print_found("bin owner (root)", defined getpwnam("root"));
- print_found("libs owner (root)", defined getpwnam("root"));
- print_found("libs group (bin)", defined getgrnam("bin"));
- print_found("web owner (www)", defined getpwnam("www"));
- print_found("web group (www)", defined getgrnam("www"));
-}
-
-
-
-1;
diff --git a/rt/share/html/Admin/Users/Modify.html b/rt/share/html/Admin/Users/Modify.html
index 6af7bf3..70650a4 100755
--- a/rt/share/html/Admin/Users/Modify.html
+++ b/rt/share/html/Admin/Users/Modify.html
@@ -105,6 +105,12 @@
</table>
</&>
<br />
+
+<&| /Widgets/TitleBox, title => loc('Customers') &>
+<& /Elements/EditCustomers, Object => $UserObj, CustomerString=> $CustomerString, ServiceString => $ServiceString &>
+</&>
+<br />
+
<&| /Widgets/TitleBox, title => loc('Access control') &>
<input type="hidden" class="hidden" name="SetEnabled" value="1" />
<input type="checkbox" class="checkbox" name="Enabled" value="1" <%$EnabledChecked%> />
@@ -330,6 +336,8 @@ if ($UserObj->Id && $id ne 'new') {
push (@results,@fieldresults);
push @results, ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $UserObj );
+ #deal with freeside customer links
+ push @results, ProcessObjectCustomers( ARGSRef => \%ARGS, Object => $UserObj );
# {{{ Deal with special fields: Privileged, Enabled
if ( $SetPrivileged and $Privileged != $UserObj->Privileged ) {
@@ -418,4 +426,8 @@ $CurrentPass => undef
$Pass1 => undef
$Pass2 => undef
$Create=> undef
+$OnlySearchForCustomers => undef
+$OnlySearchForServices => undef
+$CustomerString => undef
+$ServiceString => undef
</%ARGS>
diff --git a/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head
new file mode 100644
index 0000000..c1f24c2
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/Elements/Header/Head
@@ -0,0 +1,2 @@
+<link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/css/calendar.css" type="text/css" media="all" />
+
diff --git a/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
new file mode 100644
index 0000000..cb46fda
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
@@ -0,0 +1,19 @@
+<%init>
+my $args;
+$args= "?" . $m->comp(
+ '/Elements/QueryString',
+ Query => $ARGS{'Query'} || $session{'CurrentSearchHash'}->{'Query'},
+ Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
+ OrderBy => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'},
+ Order => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'},
+ Page => $ARGS{'Page'} || $session{'CurrentSearchHash'}->{'Page'},
+ Rows => $ARGS{'Rows'},
+ ) if ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'});
+$args ||= '';
+
+$tabs->{'zz'} = { title =>loc("Calendar"),
+ path => "Search/Calendar.html$args" };
+</%init>
+<%args>
+$tabs
+</%args>
diff --git a/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
new file mode 100644
index 0000000..06413e2
--- /dev/null
+++ b/rt/share/html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
@@ -0,0 +1,9 @@
+<%init>
+ $tabs->{'z'} = { title =>loc("Calendar"),
+ path => "Prefs/Calendar.html" };
+</%init>
+<%args>
+$tabs
+$current_subtab => undef
+$Searches => undef
+</%args>
diff --git a/rt/share/html/Elements/AddCustomers b/rt/share/html/Elements/AddCustomers
new file mode 100644
index 0000000..aaf8ca8
--- /dev/null
+++ b/rt/share/html/Elements/AddCustomers
@@ -0,0 +1,59 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%# Copyright (c) 2008 Freeside Internet Services, Inc.
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<BR>
+<%$msg%><br>
+
+% if (@Customers) {
+
+<br><i>(Check box to link)<i>
+<table>
+% foreach my $customer (@Customers) {
+<tr>
+ <td>
+ <input type="checkbox" name="Object-AddCustomer-<% $customer->{'custnum'} %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>>
+ <A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $customer->{'custnum'} %>"><% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %>
+ </td>
+</tr>
+% }
+</table>
+
+% }
+
+<%INIT>
+my ($msg);
+
+my $freeside_url = &RT::URI::freeside::FreesideURL();
+
+warn "/Elements/AddCustomers called with CustomerString $CustomerString\n"
+ if $Debug;
+
+my @Customers = ();
+if ( $CustomerString ) {
+ @Customers = &RT::URI::freeside::smart_search( 'search' => $CustomerString );
+}
+
+my @Services = ();
+if ($ServiceString) {
+ @Services = (); #service_search();
+}
+
+warn "/Elements/AddCustomers displaying ". scalar(@Customers). " customers\n"
+ if $Debug;
+
+</%INIT>
+
+<%ARGS>
+$CustomerString => undef
+$ServiceString => undef
+$Debug => 0
+</%ARGS>
diff --git a/rt/share/html/Elements/CalendarEvent b/rt/share/html/Elements/CalendarEvent
new file mode 100644
index 0000000..3a6b00b
--- /dev/null
+++ b/rt/share/html/Elements/CalendarEvent
@@ -0,0 +1,129 @@
+<%args>
+$Date => undef
+$Object => undef
+$DateTypes => undef
+</%args>
+<div class="tooltip">
+<small>
+
+% if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/reminder.png" />
+
+% } elsif ($DateTypes->{Resolved}
+% and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/resolved.png" />
+
+% } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
+% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today ) {
+ <img src="<%$RT::WebImagesURL%>/starts_due.png" />
+
+% } elsif ($DateTypes->{Due} and $DateTypes->{Created}
+% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today ) {
+ <img src="<%$RT::WebImagesURL%>/created_due.png" />
+
+% } elsif ($DateTypes->{Starts}
+% and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/starts.png" />
+
+% } elsif ($DateTypes->{Due}
+% and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/due.png" />
+
+% } elsif ($DateTypes->{Created}
+% and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/created.png" />
+
+% } elsif ($DateTypes->{Started}
+% and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/started.png" />
+
+% } elsif ($DateTypes->{LastUpdated}
+% and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) {
+ <img src="<%$RT::WebImagesURL%>/updated.png" />
+
+% }
+
+ <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+ <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
+ <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
+ <span class="tip">
+ <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+ <% $Object->QueueObj->Name %> #<% $TicketId %>
+ </a>
+ :</strong> <% $subject%><br />
+ <br />
+
+%# logic taken from Ticket/Search/Results.tsv
+% foreach my $attr (@display_fields) {
+% my $value;
+%
+% if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
+% $value = '-';
+% } else {
+% my $method = '$Object->'.$attr.'()';
+% $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
+% $value = eval $method;
+% if ($@) {die "<b>Check your CalendarPopupFields config in etc/RT_SiteConfig.pm</b>.<br /><br />Failed to find \"$attr\" - ". $@};
+% }
+ <strong><&|/l&><% $label_of{$attr} %></&>:</strong> <% $value %><br />
+% }
+
+<br />
+ </span>
+</div>
+
+<%init>
+use RTx::Calendar;
+
+my $today = $Date->strftime("%F");
+
+my $TicketId;
+
+my $ticket;
+my $subject;
+my $IsReminder;
+
+if ($Object->Type eq 'reminder') {
+ $IsReminder = 1;
+ if ($Object->RefersTo->First) {
+ $ticket = $Object->RefersTo->First->TargetObj;
+ $TicketId = $ticket->Id;
+ $subject = $Object->Subject . " (" . $ticket->Subject . ")";
+ }
+} else {
+ $TicketId = $Object->Id;
+ $subject = $Object->Subject;
+}
+
+my $display_owner = $RT::CalendarDisplayOwner;
+$display_owner ||= RT->Config->Get('CalendarDisplayOwner')
+ if RT->can('Config');
+
+
+# 3.6 config
+my @display_fields = @RT::CalendarPopupFields;
+
+# 3.8 config
+# the if condition is weird but it doesn't work with 3.8.0 without the last part
+@display_fields = RT->Config->Get('CalendarPopupFields')
+ if 0 == @display_fields and RT->can('Config') and RT->Config->Get('CalendarPopupFields');
+
+# default
+if (0 == @display_fields) {
+ @display_fields = qw(OwnerObj->Name CreatedObj->ISO StartsObj->ISO
+ StartedObj->ISO LastUpdatedObj->ISO DueObj->ISO
+ ResolvedObj->ISO Status Priority
+ Requestors->MemberEmailAddressesAsString);
+}
+
+
+my %label_of;
+for my $field (@display_fields) {
+ my $label = $field;
+ $label =~ s'Obj-.(?:AsString|Name|ISO)''g;
+ $label =~ s'-\>MemberEmailAddressesAsString''g;
+ $label_of{$field} = $label;
+}
+
+</%init>
diff --git a/rt/share/html/Elements/CollectionList b/rt/share/html/Elements/CollectionList
index c7bdfd9..4e06e3f 100644
--- a/rt/share/html/Elements/CollectionList
+++ b/rt/share/html/Elements/CollectionList
@@ -123,7 +123,9 @@ if ( $ShowHeader ) {
my ($i, $column_map) = (0, {});
while ( my $record = $Collection->Next ) {
# Every ten rows, flush the buffer and put something on the page.
- $m->flush_buffer unless ++$i % 10;
+ #broken w/FS, causes rows to be output prematurely
+ #$m->flush_buffer unless ++$i % 10;
+ ++$i;
my $warning = 0;
my $Classes = '';
diff --git a/rt/share/html/Elements/ColumnMap b/rt/share/html/Elements/ColumnMap
index a1475a9..6a1c237 100644
--- a/rt/share/html/Elements/ColumnMap
+++ b/rt/share/html/Elements/ColumnMap
@@ -157,7 +157,12 @@ my $COLUMN_MAP = {
} qw(WebPath WebBaseURL WebURL)),
WebRequestPath => { value => sub { substr( $m->request_path, 1 ) } },
- WebRequestPathDir => { value => sub { substr( $m->request_comp->dir_path, 1 ) } },
+ #WebRequestPathDir => { value => sub { substr( $m->request_comp->dir_path, 1 ) } },
+ WebRequestPathDir => { value => sub {
+ my $wrpd = substr( $m->request_comp->dir_path, 1 );
+ $wrpd =~ s(^rt/)(); #hacky, dunno why this happens
+ $wrpd;
+ } },
};
$COLUMN_MAP->{'CF'} = $COLUMN_MAP->{'CustomField'};
diff --git a/rt/share/html/Elements/EditCustomers b/rt/share/html/Elements/EditCustomers
new file mode 100644
index 0000000..68efb5f
--- /dev/null
+++ b/rt/share/html/Elements/EditCustomers
@@ -0,0 +1,63 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%# Copyright (c) 2008 Freeside Internet Services, Inc.
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<TABLE width=100%>
+ <TR>
+ <TD VALIGN=TOP WIDTH=50%>
+ <h3><&|/l&>Current Customers</&></h3>
+
+<table>
+ <tr>
+ <td><i><&|/l&>(Check box to disassociate)</&></i></td>
+ </tr>
+ <tr>
+ <td class="value">
+% foreach my $link ( @{ $Object->Customers->ItemsArrayRef } ) {
+
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
+%# <& ShowLink, URI => $link->TargetURI &><br>
+ <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %></A>
+ <BR>
+% }
+ </td>
+ </tr>
+</table>
+
+</TD>
+
+<TD VALIGN=TOP>
+<h3><&|/l&>New Customer Links</&></h3>
+<&|/l&>Find customer</&><BR>
+<input name="CustomerString">
+<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>">
+<br><i>cust #, name, company or phone</i>
+<BR>
+%#<BR>
+%#<&|/l&>Find service</&><BR>
+%#<input name="ServiceString">
+%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>">
+%#<br><i>username, username@domain, domain, or IP address</i>
+%#<BR>
+
+<& AddCustomers, Object => $Object,
+ CustomerString => $CustomerString,
+ ServiceString => $ServiceString, &>
+
+</TD>
+</TR>
+</TABLE>
+
+<%ARGS>
+$CustomerString => undef
+$ServiceString => undef
+$Object => undef
+</%ARGS>
diff --git a/rt/share/html/Elements/Footer b/rt/share/html/Elements/Footer
index 2fcdee9..59547e2 100755
--- a/rt/share/html/Elements/Footer
+++ b/rt/share/html/Elements/Footer
@@ -48,25 +48,6 @@
%# End of div#body from /Elements/PageLayout
</div>
% $m->callback( %ARGS );
-<div id="footer">
-% if ($m->{'rt_base_time'}) {
- <p id="time">
- <span><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></span>
- </p>
-%}
- <p id="bpscredits">
- <span>
-<&|/l, '&#187;&#124;&#171;', $RT::VERSION, '2009', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
-</span>
-</p>
-% if (!$Menu) {
- <p id="legal">
-<&|/l&>Distributed under version 2 <a href="http://www.gnu.org/copyleft/gpl.html"> of the GNU GPL.</a></&><br />
-<&|/l, '<a href="mailto:sales@bestpractical.com">sales@bestpractical.com</a>' &>To inquire about support, training, custom development or licensing, please contact [_1].</&><br />
- </p>
-% }
-
-</div>
% if ($Debug >= 2 ) {
% require Data::Dumper;
% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
diff --git a/rt/share/html/Elements/Header b/rt/share/html/Elements/Header
index 953efcb..d970ac5 100755
--- a/rt/share/html/Elements/Header
+++ b/rt/share/html/Elements/Header
@@ -45,61 +45,67 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<% include( '/elements/header.html', {
+ 'title' => $Title,
+ 'head' => $head,
+ 'etc' => $etc,
+ 'nobr' => 1,
+ 'nocss' => 1,
+ }) |n
+%>
+<%INIT>
+$r->headers_out->{'Pragma'} = 'no-cache';
+$r->headers_out->{'Cache-control'} = 'no-cache';
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
-<title><%$Title%></title>
+my $id = $m->request_comp->path;
+$id =~ s|^/||g;
+$id =~ s|/|-|g;
+$id =~ s|\.html$||g;
+$id =~ s|index$||g
+ if $id ne 'index';
+$id =~ s|-$||g;
+my $head = '';
-% if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) {
- <meta http-equiv="refresh" content="<% $Refresh %>" />
-% }
+if ($Refresh && $Refresh =~ /^(\d+)/ && $1 > 0) {
+ $head .= '<meta http-equiv="refresh" content="$Refresh" />';
+}
-<link rel="shortcut icon" href="<%RT->Config->Get('WebImagesURL')%>/favicon.png" type="image/png" />
-<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/<% RT->Config->Get( 'WebDefaultStylesheet', $session{'CurrentUser'} ) %>/main<% RT->Config->Get('DevelMode')? '' : '-squished' %>.css" type="text/css" media="all" />
-<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/print.css" type="text/css" media="print" />
+my $WebPath = RT->Config->Get('WebPath');
+my $WebImagesURL = RT->Config->Get('WebImagesURL');
+my $WebDefaultStylesheet =
+ RT->Config->Get('WebDefaultStylesheet', $session{'CurrentUser'});
+my $squished = RT->Config->Get('DevelMode') ? '' : '-squished';
-% for (keys %{$LinkRel || {}}) {
- <link rel="<% $_ %>" href="<% RT->Config->Get('WebPath') . $LinkRel->{$_} %>" />
-% }
+$head .= <<END;
+<link rel="shortcut icon" href="$WebImagesURL/favicon.png" type="image/png" />
+<link rel="stylesheet" href="$WebPath/NoAuth/css/$WebDefaultStylesheet/main$squished.css" type="text/css" media="all" />
+<link rel="stylesheet" href="$WebPath/NoAuth/css/print.css" type="text/css" media="print" />
+END
-% if ( $RSSAutoDiscovery ) {
- <link rel="alternate" href="<%$RSSAutoDiscovery%>" type="application/rss+xml" title="RSS RT Search" />
-% }
+for (keys %{$LinkRel || {}}) {
+ $head .= qq(<link rel="$_" href="$WebPath) . $LinkRel->{$_} . '" />';
+}
-% if ($JavaScript) {
-<& HeaderJavascript, focus => $Focus, onload => $onload &>
-% }
+if ( $RSSAutoDiscovery ) {
+ $head .= qq(<link rel="alternate" href="$RSSAutoDiscovery" type="application/rss+xml" title="RSS RT Search" />);
+}
-% my $stylesheet_plugin = "/NoAuth/css/". RT->Config->Get( 'WebDefaultStylesheet', $session{'CurrentUser'} )."/InHeader";
-% if ($m->comp_exists($stylesheet_plugin) ) {
-<& $stylesheet_plugin &>
-% }
-% $m->callback( %ARGS, CallbackName => 'Head' );
+if ($JavaScript) {
+ $head .= $m->scomp('HeaderJavascript', focus => $Focus, onload => $onload);
+}
-</head>
- <body<% $id && qq[ id="comp-$id"] |n %>>
+my $stylesheet_plugin = "/NoAuth/css/$WebDefaultStylesheet/InHeader";
+if ($m->comp_exists($stylesheet_plugin) ) {
+ $head .= $m->scomp($stylesheet_plugin);
+}
-% if ($ShowBar) {
-<& /Elements/Logo, %ARGS &>
+# $m->callback( %ARGS, CallbackName => 'Head' );
+$head .= $m->scomp( '/Elements/Callback', _CallbackName => 'Head', %ARGS );
-<div id="quickbar">
- <& /Elements/PersonalQuickbar, %ARGS &>
-% }
+my $etc = '';
+$etc .= qq[ id="comp-$id"] if $id;
-<%INIT>
-$r->headers_out->{'Pragma'} = 'no-cache';
-$r->headers_out->{'Cache-control'} = 'no-cache';
-
-my $id = $m->request_comp->path;
-$id =~ s|^/||g;
-$id =~ s|/|-|g;
-$id =~ s|\.html$||g;
-$id =~ s|index$||g
- if $id ne 'index';
-$id =~ s|-$||g;
</%INIT>
<%ARGS>
diff --git a/rt/share/html/Elements/MyCalendar b/rt/share/html/Elements/MyCalendar
new file mode 100644
index 0000000..a54ab39
--- /dev/null
+++ b/rt/share/html/Elements/MyCalendar
@@ -0,0 +1,78 @@
+<&|/Widgets/TitleBox,
+ title => loc("Calendar"),
+ title_href => "Search/Calendar.html" &>
+
+<table class="rtxcalendar">
+<thead>
+<tr>
+% my $date = $begin->clone;
+% while ( $date <= $end ) {
+<th width="14%"><%$rtdate->GetWeekday($date->day_of_week % 7)%></th>
+% $date = $set->next($date);
+% }
+</tr>
+</thead>
+<tbody>
+<tr>
+% $date = $begin->clone;
+% while ($date <= $end) {
+<td>
+<p class="date"><%$date->day%></p>
+% for my $t (@{ $Tickets{$date->strftime("%F")} }) {
+<& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+% }
+</td>
+% $date = $set->next($date);
+% }
+</tr>
+</tbody>
+</table>
+
+ </&>
+
+<%INIT>
+
+use RTx::Calendar;
+
+my $title = loc("Calendar");
+
+my $rtdate = RT::Date->new($session{'CurrentUser'});
+
+my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
+
+my $today = DateTime->today;
+
+# this line is used to debug MyCalendar
+# $today = DateTime->new(year => 2007, month => 4, day => 11);
+
+my $begin = $today->clone->subtract( days => 3);
+my $end = $today->clone->add( days => 3);
+
+# use this to loop over days until $end
+my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
+);
+
+my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
+ AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' )
+ AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
+my $Format = "__Starts__ __Due__";
+
+if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) {
+ $Format = $Search->SubValue('Format');
+ $Query = $Search->SubValue('Query');
+}
+
+# we search all date types in Format string
+my @Dates = grep { $Format =~ m/__${_}(Relative)?__/ } @DateTypes;
+
+# used to display or not a date in Element/CalendarEvent
+my %DateTypes = map { $_ => 1 } @Dates;
+
+$Query .= RTx::Calendar::DatesClauses(\@Dates, $begin->strftime("%F"), $end->strftime("%F"));
+
+# print STDERR $Query, "\n";
+
+my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $Query, \@Dates);
+
+</%INIT>
diff --git a/rt/share/html/Elements/PageLayout b/rt/share/html/Elements/PageLayout
index 981e58a..f30d815 100755
--- a/rt/share/html/Elements/PageLayout
+++ b/rt/share/html/Elements/PageLayout
@@ -45,23 +45,27 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
+
+<% include('/elements/init_calendar.html') |n %>
+
+% if (0) { ## new ticket via customer, and we already have a ticket search box
<div id="topactions">
% foreach my $action (reverse sort keys %{$topactions}) {
<span class="topaction" id="topaction-<%$action%>"><% $topactions->{"$action"}->{'html'} |n %></span>
% }
</div>
+% }
-%# End of div#quickbar from /Elements/Header
-</div>
-
+% if (0) { ##FREESIDE MENUS INSTEAD## if ( $show_menu ) {
% if ( $show_menu ) {
<div id="nav">
<& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &>
</div>
% }
+% }
<div id="header">
-<h1><% $title %></h1>
+%#already shown <h1><% $title %></h1>
<div id="page-navigation">
% my $sep = 0;
% my $postsep = 0;
diff --git a/rt/share/html/Elements/SelectDate b/rt/share/html/Elements/SelectDate
index 32a173d..183086f 100755
--- a/rt/share/html/Elements/SelectDate
+++ b/rt/share/html/Elements/SelectDate
@@ -45,10 +45,21 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<script type="text/javascript"><!--
- onLoadHook('createCalendarLink("<% $Name %>");');
---></script>
+%# in PageLayout instead, once <% include('/elements/init_calendar.html') |n %>
<input type="text" id="<% $Name %>" name="<% $Name %>" value="<% $Value %>" size="<% $Size %>" />
+<IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $Name %>_date_button" STYLE="cursor: pointer" TITLE="Select date">
+<script type="text/javascript">
+Calendar.setup({
+ inputField: "<%$Name%>",
+% if ( defined($ShowTime) && $ShowTime ) {
+ ifFormat: "%Y-%m-%d %H:%M",
+ showsTime: true,
+% } else {
+ ifFormat: "%Y-%m-%d",
+% }
+ button: "<%$Name%>_date_button",
+});
+</script>
<%init>
unless ((defined $Default) or
($current <= 0)) {
diff --git a/rt/share/html/NoAuth/Calendar/dhandler b/rt/share/html/NoAuth/Calendar/dhandler
new file mode 100644
index 0000000..4b4aa63
--- /dev/null
+++ b/rt/share/html/NoAuth/Calendar/dhandler
@@ -0,0 +1,159 @@
+<%init>
+
+use Data::ICal;
+use Data::ICal::Entry::Todo;
+use Data::ICal::Entry::Event;
+use Date::ICal;
+
+$RT::ICalTicketType ||= "Data::ICal::Entry::Todo";
+$RT::ICalReminderType ||= "Data::ICal::Entry::Event";
+
+my ($UserId, $SearchId, $MagicNumber);
+my $arg = $m->dhandler_arg;
+
+if ($arg =~ m{^(\d+)@(\d+)/(.*)$}) {
+ $UserId = $1;
+ $SearchId = $2;
+ $MagicNumber = $3;
+} elsif ($arg =~ m{^(\d+)/(.*)}) {
+ $UserId = $1;
+ $MagicNumber = $2;
+} else {
+ Abort("Corrupted URL.");
+}
+
+my $CurrentUser = new RT::CurrentUser();
+$CurrentUser->LoadById($UserId);
+my $user = $CurrentUser->Name;
+
+# if no user, abort
+unless ($CurrentUser->Id) {
+ $RT::Logger->error("No such user id $UserId from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+# verify user has LoadSavedSearch right
+if ($SearchId and not $CurrentUser->HasRight( Right => 'LoadSavedSearch',
+ Object=> $RT::System )) {
+ $RT::Logger->error("not enough rights for user $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+
+# if MagicNumber doesn't match the one stored in database, abort
+my $Search;
+my $ICalAttribute;
+if ($SearchId) {
+ $Search = $CurrentUser->Attributes->WithId($SearchId);
+ $ICalAttribute = $Search->FirstAttribute('ICalURL');
+} else {
+ $ICalAttribute = $CurrentUser->UserObj->FirstAttribute('ICalURL');
+}
+
+unless ($ICalAttribute) {
+ $RT::Logger->error("No such ICal feed for $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+
+if ($MagicNumber ne $ICalAttribute->Content) {
+ $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}");
+ $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
+ $m->abort;
+}
+
+my $Tickets = RT::Tickets->new($CurrentUser);
+
+my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
+ AND ( Owner = '" . $CurrentUser->Id ."' OR Owner = 'Nobody' )
+ AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
+
+$Query = $Search->SubValue('Query')
+ if $Search;
+
+$Query .= " AND ( Due > '1970-01-01' OR Starts > '1970-01-01' )";
+
+$Tickets->FromSQL($Query);
+
+$Tickets->OrderBy(FIELD => 'Due', ORDER => 'ASC');
+
+my $calendar = Data::ICal->new();
+
+my ($uid) = $RT::WebURL =~ m{https?://([^:]+)};
+
+while (my $Ticket = $Tickets->Next ) {
+
+ my $event;
+ if ($Ticket->Type eq 'ticket') {
+ $event = add_todo($Ticket, $uid);
+ } else {
+ $event = add_event($Ticket, $uid);
+ }
+ next unless $event;
+ $calendar->add_entry($event);
+}
+
+my $cal = $calendar->as_string;
+
+$r->content_type('text/calendar;charset=utf-8');
+$m->clear_buffer();
+$m->out($cal);
+$m->abort;
+
+sub add_event {
+ my ($Reminder, $uid) = @_;
+
+ return unless defined $Reminder->RefersTo->First;
+ my $Ticket = $Reminder->RefersTo->First->TargetObj;
+
+ my %event = (
+ summary => $Reminder->Subject ? $Reminder->Subject : '',
+ url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
+ uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Reminder->Id . "@" . $uid,
+ categories => $Ticket->QueueObj->Name,
+ dtstart => Date::ICal->new( epoch => $Reminder->DueObj->Unix )->ical,
+ );
+
+ my $event = $RT::ICalReminderType->new();
+ $event->add_properties(%event);
+
+ return $event;
+}
+
+sub add_todo {
+ my ($Ticket, $uid) = @_;
+
+ my %vtodo = (
+ summary => $Ticket->Subject ? $Ticket->Subject : '',
+ dtstart => Date::ICal->new( epoch => $Ticket->CreatedObj->Unix )->ical,
+ url => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
+ uid => Date::ICal->new( epoch => time() )->ical() . "-" . $Ticket->Id . "@" . $uid,
+ categories => $Ticket->QueueObj->Name,
+ );
+
+ $vtodo{due} = Date::ICal->new( epoch => $Ticket->DueObj->Unix )->ical,
+ if $Ticket->DueObj;
+
+ if ($Ticket->OwnerObj->Id != $RT::Nobody->Id and $Ticket->OwnerObj->EmailAddress) {
+ $vtodo{organizer} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
+ $vtodo{attendee} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
+ } elsif ($Ticket->QueueObj->CommentAddress) {
+ $vtodo{organizer} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
+ $vtodo{attendee} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
+ }
+
+ $vtodo{priority} = $Ticket->Priority
+ if $Ticket->Priority;
+
+ my $vtodo = $RT::ICalTicketType->new();
+ $vtodo->add_properties(%vtodo);
+
+ return $vtodo;
+}
+
+
+
+</%init>
diff --git a/rt/share/html/NoAuth/css/calendar.css b/rt/share/html/NoAuth/css/calendar.css
new file mode 100644
index 0000000..c6b584e
--- /dev/null
+++ b/rt/share/html/NoAuth/css/calendar.css
@@ -0,0 +1,75 @@
+.tooltip{position:relative;z-index:1;}
+.tooltip:hover{z-index:5;color:#000;}
+.tooltip span.tip{display: none; text-align:left;}
+
+div.tooltip:hover span.tip{
+display:block;
+position:absolute;
+top:12px; left:24px; width:350px;
+border:1px solid #555;
+background-color:#fff;
+padding: 4px;
+font-size: 0.8em;
+color:#505050;
+}
+
+.calendardate {
+ text-align: right;
+ background-color: #f8f8ff;
+ width:100%;
+}
+
+.offmonthcalendardate {
+ text-align: right;
+ background-color: #f8f8f8;
+ width:100%;
+}
+
+.todayscalendardate {
+ text-align: right;
+ background-color: #fc6; /*#fad163*/
+ width:100%;
+}
+
+table.rtxcalendar {
+ width:100%;
+ border-collapse: collapse;
+ border: 1px solid #d0d0d0;
+ margin-bottom: 6px;
+}
+
+table.rtxcalendar td {
+ border: 1px solid #d7d7d7;
+ background: #fff;
+ vertical-align: top;
+ width: 14%;
+}
+
+table.rtxcalendar th {
+ border: 1px solid #d7d7d7;
+ background: #eef;
+}
+table.rtxcalendar tbody th {
+ border: 1px solid #d7d7d7;
+ background: #eee;
+ font-weight: normal;
+}
+
+table.rtxcalendar td.offmonth {
+ background: #f8f8f8;
+ color: #aaa;
+}
+
+table.rtxcalendar td.today {
+ background: #ffe; /*#fed;*/
+ border: 1px solid #fc6;
+}
+
+table.rtxcalendar td.yesterday {
+ border-right: none;
+}
+
+table.rtxcalendar td.aweekago {
+ border-bottom: none;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/InHeader b/rt/share/html/NoAuth/css/freeside2.1/InHeader
new file mode 100644
index 0000000..904535f
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/InHeader
@@ -0,0 +1,54 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<!--[if lt IE 8]>
+<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/msie.css" type="text/css" media="all" />
+
+<![endif]-->
+<!--[if lt IE 7]>
+<link rel="stylesheet" href="<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/msie6.css" type="text/css" media="all" />
+<![endif]-->
diff --git a/rt/share/html/NoAuth/css/freeside2.1/admin.css b/rt/share/html/NoAuth/css/freeside2.1/admin.css
new file mode 100644
index 0000000..63385bf
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/admin.css
@@ -0,0 +1,60 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+ul.list-menu .menu-item {
+ font-size: 1.25em;
+}
+ul.list-menu {
+ list-style: none;
+
+}
+ul.list-menu .description {
+ display: block;
+ padding: 0.5em;
+ font-style: italic;
+ padding-left: 1em;
+}
diff --git a/rt/share/html/NoAuth/css/freeside2.1/base.css b/rt/share/html/NoAuth/css/freeside2.1/base.css
new file mode 100644
index 0000000..a38854f
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/base.css
@@ -0,0 +1,63 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+a {
+ color: #000;
+ text-decoration: none;
+}
+
+
+div#body a:visited {
+ color: #666;
+
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+textarea:focus, input:focus { background-color: #ffd; }
diff --git a/rt/share/html/NoAuth/css/freeside2.1/boxes.css b/rt/share/html/NoAuth/css/freeside2.1/boxes.css
new file mode 100644
index 0000000..fbd9af1
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/boxes.css
@@ -0,0 +1,192 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+.titlebox {
+ border-left: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ background-color: #efefef;
+ padding-top: 1em;
+ margin-top: 1em;
+ margin-left: 1em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ margin-bottom: 2em;
+ border-bottom: 2px solid #aaa;
+ border-right: 2px solid #aaa;
+ padding-right: 1em;
+}
+
+* html .titlebox {
+ border-top: none;
+ border-left: none;
+}
+
+.titlebox .titlebox {
+
+ background-color: #ffffff;
+ margin-top: 1em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ margin-right: 0.25em;
+
+}
+
+
+.titlebox {
+ margin-left: 0em;
+ margin-right: 0em;
+ min-height: 1.25em;
+
+}
+
+
+
+.titlebox .titlebox-title {
+ position: relative;
+ margin-top: -1.5em;
+ padding-bottom: 0.25em;
+ padding-left: 1em;
+ margin-right: -1em;
+
+}
+
+.titlebox .titlebox-title a {
+ text-decoration: none;
+ color: black;
+
+}
+
+.titlebox .titlebox-title a:hover {
+ text-decoration: underline;
+
+}
+
+.titlebox .titlebox-title a:visited {
+ color: #fff;
+}
+
+.titlebox .titlebox-title .left {
+ font-weight: bold;
+ background: #ccc;
+ margin-left: 0.75em;
+ padding:0.5em;
+ padding-left: 0.75em;
+ padding-right: 0.75em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ border-bottom: 2px solid #aaa;
+ border-right: 2px solid #aaa;
+
+
+}
+
+.titlebox .titlebox-title .right-empty {
+ display:none;
+}
+
+.titlebox .titlebox-title .right {
+ position: absolute;
+ right: 0;
+ top: 0.5em;
+ font-size: 0.9em;
+ background: #dedede;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding-right: 0.4em;
+ padding-left: 0.4em;
+ padding-bottom: 0.2em;
+ padding-top: 0.5em;
+ -moz-border-radius-bottomleft: 0.25em;
+ -webkit-border-bottom-left-radius: 0.25em;
+
+
+ -moz-border-radius-topright: 0.25em;
+ -webkit-border-top-right-radius: 0.25em;
+
+}
+
+.titlebox .titlebox-title .right a {
+ color: #000;
+}
+
+.titlebox .titlebox-content {
+ padding-top: 0.5em;
+ padding-left: 1em;
+ padding-bottom: 1em;
+
+}
+
+.titlebox .titlebox-title .widget a {
+ display: block;
+ margin: 0;
+ margin-top: 0.5em;
+ width: 20px;
+
+ background: url(<%RT->Config->Get('WebPath')%>/NoAuth/images/css/rollup-arrow.gif) no-repeat center center;
+
+ position: absolute;
+ top: -1em;
+ left: 0.15em;
+ float: left;
+
+ padding: 11px 0 0 0;
+ overflow: hidden;
+}
+
+* html .titlebox .titlebox-title .widget a {
+ background-position: center 0.3em;
+ top: 0em;
+ left: -1.5em;
+}
+
+.titlebox.rolled-up .titlebox-title .widget a {
+ background-image: url(<%RT->Config->Get('WebPath')%>/NoAuth/images/css/rolldown-arrow.gif);
+}
+
+.titlebox hr.clear {
+ display: none;
+}
diff --git a/rt/share/html/NoAuth/css/freeside2.1/collection.css b/rt/share/html/NoAuth/css/freeside2.1/collection.css
new file mode 100644
index 0000000..cbc8cef
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/collection.css
@@ -0,0 +1,52 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+table.collection td:first-child, table.collection th:first-child {
+ padding-left: 1em;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/forms.css b/rt/share/html/NoAuth/css/freeside2.1/forms.css
new file mode 100755
index 0000000..8afedcb
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/forms.css
@@ -0,0 +1,242 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+form {
+
+ background: none;
+ border: none;
+ margin: 0;
+}
+
+
+.input-row .label {
+ position: relative;
+ text-align: right;
+ width: 4em;
+}
+
+.input-row .input {
+ position: relative;
+ left: 1em;
+ width: 10em;
+ text-align: right;
+}
+
+.value {
+ font-size: 0.85em;
+
+}
+
+
+
+div.button-row {
+ text-align: right;
+ padding-right: 0.5em;
+}
+
+
+input[type=reset], input[type=submit], input[class=button] {
+ color: #fff;
+ background: #3858a3;
+ padding: 0.25em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+}
+
+input.button:hover, button:hover, input[type=reset]:hover, input[type=submit]:hover, input[class=button]:hover {
+ background: #1D3B7D;
+}
+
+input.button:focus, button:focus, input[type=reset]:focus, input[type=submit]:focus, input[class=button]:focus {
+ background: #1D3B7D;
+}
+
+div.error div.error {
+ border: 2px solid #aa0000;
+ border-top: 1px solid #bb0000;
+ border-left: 1px solid #bb0000;
+ background-color: #fcc;
+}
+
+div.error .titlebox-title span.left {
+ background-color: #f00;
+ color: #fff;
+ border: 1px solid #cc0000;
+ border-right: 2px solid #bb0000;
+ border-bottom: 2px solid #bb0000;
+
+}
+
+
+div.results .titlebox-title .left, div.results .titlebox {
+ border: 1px solid #aa9;
+ border-bottom: 2px solid #990;
+ border-right: 2px solid #990;
+}
+
+div.results .titlebox-title .left {
+ background: #ff9;
+
+}
+
+div.results .titlebox {
+ background: #ffc;
+
+}
+
+div.results .titlebox-content {
+ padding: 0;
+}
+
+
+.label, .labeltop {
+ text-align: right;
+ font-size: 0.8em;
+ padding-right: .5em;
+
+}
+
+.cflabel {
+ text-align: right;
+ font-size: 0.8em;
+ padding-right: .5em;
+ width: 25%;
+}
+
+.labeltop, .label, .value {
+ padding-top: 0.25em;
+}
+
+div.ticket-info-basics div.titlebox-content .labeltop{
+ width: 10em;
+}
+
+div.submit {
+ text-align: right;
+}
+
+div.submit .extra-buttons {
+ text-align: left;
+}
+
+
+div.widget {
+ padding-bottom: 0.5em;
+}
+
+div.widget .label {
+ text-align: right;
+ display: block;
+ width: 15em;
+ float: left;
+ clear: both;
+ font-size: 0.9em;
+ padding-right: 0.5em;
+}
+
+div.widget .hints {
+
+ display: block;
+ padding-left: 14em;
+ font-style: italic;
+}
+
+
+%# ComboBox styles... some properties like height and width must be dynamically
+%# set in the JS (at least for now).
+.combobox {
+ position: relative;
+ width: 11.5em;
+}
+
+.combobox .combo-button {
+ right: 0;
+ padding: 0;
+ margin-top: 0;
+ cursor: default;
+ color: ButtonFace;
+ background: ButtonFace;
+ border: 2px outset ButtonHighlight;
+}
+
+/* this style replaces the default down-triangle with one that looks more like
+ * native widget sets. It does not work in IE as it's an :after pseudo element
+ * with a "content" value. but that's ok because IE can't display unicode 25be
+ * anyway */
+
+.combobox .combo-button:after {
+ color: ButtonText;
+ margin: 0;
+ padding: 0;
+ margin-top: -0.5em;
+ margin-left: -0.8em;
+ content: "\25be";
+}
+
+.combobox .combo-text {
+ border: 1px inset ButtonHighlight;
+ margin: 0;
+ padding: 0;
+}
+
+.combobox .combo-list {
+ border: 1px outset;
+ z-index: 150;
+}
+
+.value .TimeUnits{
+ margin-left: .5em;
+ width: 7em;
+}
+
+.cfinvalidfield {
+ font-style: italic;
+ color: red;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/freeside.css b/rt/share/html/NoAuth/css/freeside2.1/freeside.css
new file mode 100644
index 0000000..6e5f3b5
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/freeside.css
@@ -0,0 +1,9 @@
+
+%# div.titlebox {
+%# background: #d4d4d4;
+%# }
+%#
+%# div.titlebox-title {
+%# background: #e8e8e8;
+%# }
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/images/dhandler b/rt/share/html/NoAuth/css/freeside2.1/images/dhandler
new file mode 100644
index 0000000..6ec9dea
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/images/dhandler
@@ -0,0 +1,8 @@
+<%INIT>
+use File::Basename;
+my $arg = $m->dhandler_arg;
+my $file = dirname($m->current_comp->source_file) . '/source/'. $arg;
+RT::Interface::Web->SendStaticFile( File => $file );
+
+$m->abort;
+</%INIT>
diff --git a/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png b/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png
new file mode 100644
index 0000000..9c126c7
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/images/source/background-gradient.png
Binary files differ
diff --git a/rt/share/html/NoAuth/css/freeside2.1/layout.css b/rt/share/html/NoAuth/css/freeside2.1/layout.css
new file mode 100644
index 0000000..0e7912d
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/layout.css
@@ -0,0 +1,237 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+/* body */
+
+body {
+
+
+ padding:0;
+ margin:0;
+
+ /*background: #547CCC url(<%RT->Config->Get('WebPath')%>/NoAuth/css/freeside2.1/images/background-gradient.png) top left repeat-x ; */
+ background: #f8f8f8;
+ font-family: arial, helvetica, sans-serif;
+
+ color: #000000;
+}
+
+div#body {
+ position: relative;
+ padding: 1em;
+ padding-top: 1.8em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ /* margin-left: 10.5em; */
+ /* margin-top: 5.2em; */
+ margin-left: .5em;
+ margin-top: 3.0em;
+ margin-right: 1em;
+ margin-bottom: 0em;
+ min-height: 10%;
+ background: #fff;
+ border-top: 2px solid #ccc;
+ border-left: 2px solid #ccc;
+ z-index:1;
+
+
+}
+
+#topactions {
+ position: absolute;
+ background: transparent;
+ top: 3.8em;
+ right: 1em;
+ width: auto;
+ min-width: 42em;
+ font-size: 0.9em;
+ z-index: 99;
+}
+
+#topactions form * {
+ vertical-align: top;
+}
+
+#topactions button, #topactions select, #topactions input{
+ padding-top: 0em;
+ padding-bottom: 0em;
+ width: 8em;
+
+}
+
+#topactions form {
+ display: block;
+
+}
+
+#topactions #CreateTicketInQueue {
+ text-align: right;
+
+}
+#topactions #simple-search {
+ float: right;
+}
+
+#topactions #simple-search .field{
+ margin-left: 1em;
+ color: #787;
+ }
+
+#topactions #simple-search .field:focus {
+ color: #000;
+ }
+
+#topactions #GotoTicket {
+ text-align: right;
+
+}
+
+div#footer {
+ position: absolute;
+ right: 0;
+ text-align: right;
+ font-size: 0.9em;
+ margin-top: 2em;
+ background: #fff;
+ margin-bottom: 0;
+ padding-left: 3em;
+ padding-right: 1em;
+
+
+
+
+
+ border-top: 2px solid #aaa;
+ border-left: 2px solid #aaa;
+
+
+
+ -moz-border-radius-topleft: 0.5em;
+ -webkit-border-top-left-radius: 0.5em;
+ -moz-border-radius-bottomleft: 0.5em;
+ -webkit-border-bottom-left-radius: 0.5em;
+}
+
+div#footer #time {
+display: none ;
+}
+
+div#footer #bpscredits {
+ text-align: right;
+ background: url(<%RT->Config->Get('WebPath')%>/NoAuth/images//bplogo.gif) no-repeat top right;
+ padding-top: 4em;
+}
+
+
+/* logo stuff */
+
+div#logo {
+}
+
+
+div#logo a {
+ display: none;
+ position: absolute;
+ left: 0;
+ bottom: 0;
+}
+div#logo a img {
+ border: 0;
+}
+div#logo .rtname {
+ position: absolute;
+ font-weight: bold;
+ top: 1em;
+ left: 1em;
+}
+
+
+div#quickbar, div#logo {
+ font-size: 0.9em;
+}
+div#quickbar a, div#logo a {
+ color: #000;
+}
+
+
+div#quickbar {
+ background: #eaeaea;
+ padding-top: 1em;
+ padding-left: 1em;
+ padding-bottom: 0.5em;
+ height: 1em;
+ border-bottom: 1px solid #ccc;
+
+}
+div#quick-personal {
+ float: right;
+ margin-right: 1em;
+}
+
+
+div#header h1 {
+ position: absolute;
+ left: 7.25em;
+ right: 20em;
+ overflow: hidden;
+ height: 1em;
+ font-size: 1.4em;
+ margin-top: 0.4em;
+ padding: 0.25em;
+ color: #fff;
+}
+
+/* in multi-column layouts, make sure we have an internal gutter */
+
+tr .boxcontainer {
+ padding-right: 1em;
+}
+
+tr .boxcontainer:last-child {
+ padding-right: 0;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/login.css b/rt/share/html/NoAuth/css/freeside2.1/login.css
new file mode 100644
index 0000000..2eb4238
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/login.css
@@ -0,0 +1,82 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+#login-box hr {
+ display: none;
+}
+
+#login-box {
+
+ width: 30em;
+
+margin-right:auto;margin-left:auto;
+ padding-top: 2em;
+ padding-bottom: 2em;
+
+
+}
+
+
+#login-box .input-row {
+ position: relative;
+ height: 1.5em;
+ padding-top: 1em;
+}
+
+#login-box .input-row .label {
+
+ float: left;
+ width: 8em;
+ text-align: right;
+ font-weight: bold;
+
+
+}
+
+#login-box .button-row {
+ margin-top: 0.5em;
+}
diff --git a/rt/share/html/NoAuth/css/freeside2.1/main.css b/rt/share/html/NoAuth/css/freeside2.1/main.css
new file mode 100644
index 0000000..69e7f44
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/main.css
@@ -0,0 +1,71 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+% $m->callback(CallbackName => 'Begin');
+
+@import "yui-fonts.css";
+@import "base.css";
+
+@import "layout.css";
+@import "nav.css";
+@import "forms.css";
+@import "boxes.css";
+
+@import "login.css";
+@import "ticket-lists.css";
+@import "ticket-search.css";
+@import "portlets.css";
+@import "ticket.css";
+@import "tools.css";
+@import "admin.css";
+@import "collection.css";
+@import "misc.css";
+
+@import "freeside.css";
+
+% $m->callback(CallbackName => 'End');
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/misc.css b/rt/share/html/NoAuth/css/freeside2.1/misc.css
new file mode 100644
index 0000000..80d7ce0
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/misc.css
@@ -0,0 +1,87 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+
+@import "../base/misc.css";
+
+#body.calpopup {
+ margin-left: 1em;
+ margin-top: 1em;
+}
+
+#body.calpopup a.today {
+ font-size: 1em;
+ font-weight: bold;
+}
+
+#body.calpopup a {
+ font-size: 0.8em;
+}
+
+.calendar {
+ text-align: center;
+ margin: 0 0 0 0;
+}
+
+.calendar td, .calendar th { padding: 0.1em 0.1em 0.1em 0.1em; }
+
+.calendar caption .month {
+ padding: 0 0.25em 0 0.25em;
+ font-size: 1.5em;
+}
+
+.comment {
+ padding-left: 0.5em;
+ color: #999;
+
+}
+
+#comp-Ticket-ShowEmailRecord #body {
+ margin-left: 1em;
+ margin-top: 1em;
+ overflow: auto;
+}
diff --git a/rt/share/html/NoAuth/css/freeside2.1/msie.css b/rt/share/html/NoAuth/css/freeside2.1/msie.css
new file mode 100644
index 0000000..2297c30
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/msie.css
@@ -0,0 +1,246 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+div#body {
+ left: 0.25em;
+ height: 100%;
+ top: 1em;
+
+}
+
+
+#footer {
+ padding: 1em;
+}
+
+
+div#header h1 {
+ position: absolute;
+ left: 7.25em;
+ overflow: hidden;
+ height: 1em;
+ font-size: 1.4em;
+ margin-top: 0.4em;
+ right: 23.5em;
+ padding: 0.25em;
+}
+
+
+#topactions {
+ top: 4.1em;
+ width: auto;
+}
+.topaction form * {
+ vertical-align: top;
+}
+
+.topaction form button, .topaction form input {
+ height: 2em;
+}
+
+.topaction form input.field {
+ height: 1.6em;
+}
+
+.topaction .select-queue {
+ margin-top: 0.2em;
+}
+
+div#page-navigation ul#actions-menu {
+ margin-top: -2.9em;
+ margin-right: -0.2em;
+ border-top: 1px solid #ccc;
+ border-right: none;
+}
+
+
+div#page-navigation {
+ position: absolute;
+ top: 6.2em;
+ height: 1.8em;
+ background: #fff;
+ border-top: 2px solid #ccc;
+}
+
+
+
+div#page-navigation ul#page-menu {
+ margin-top: -2.5em;
+ margin-left: 4em;
+ background: none;
+ border: none;
+}
+
+
+div#quickbar { height: 1.2em;
+
+
+}
+
+#pick-criteria td.label select {
+ width: 10em;
+}
+
+
+#editquery {
+ margin-top: 0.2em;
+ width: 39%;
+ left: 60%;
+}
+
+div#nav li.first {
+ margin-top: 0.75em;
+ border-top: none;
+}
+div#nav ul ul li.first {
+ border-top: 1px solid #cccccc;
+ margin-top: 0.25em;
+}
+
+div#nav li.last {
+ border-bottom: none;
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+
+.ticket-transaction .type a { font-weight: normal; text-decoration: none; color: #fff; }
+
+
+.titlebox {
+ border-top: none;
+ border-left: none;
+}
+
+.titlebox .titlebox-title .left {
+ padding: 0.25em;
+ padding-left: 0.5em;
+}
+
+.titlebox {
+}
+
+.titlebox .titlebox-title .right {
+ border-right: 2px solid #aaa;
+
+}
+
+
+.titlebox .titlebox-content {
+ padding-top: 2.2em;
+}
+
+.titlebox table.ticket-list, .titlebox table.queue-summary {
+ width: 95%;
+ padding: 0.5em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+th.collection-as-table {
+ padding: 0.25em;
+}
+
+table.queue-summary td, td.collection-as-table {
+ padding: 0.25em;
+}
+
+ .titlebox-title {
+ position: relative;
+}
+
+.titlebox-title .widget {
+ position: absolute;
+ top: -0.25em;
+ left: -0.25em;
+
+}
+.titlebox-title .left {
+ position: absolute;
+ top: -0.75em;
+ left: 0.5em;
+}
+
+
+.titlebox .titlebox-title .right{
+ top: 0.2em;
+ right: -0.2em;
+}
+
+/* nested things. like the ticket dates tab */
+.titlebox .titlebox .titlebox-title .right{
+ top: 0.25em;
+}
+
+.combobox {
+ float: left;
+}
+
+.combobox .combo-button {
+ color: ButtonText;
+ padding: 0;
+}
+
+.combobox .combo-list {
+ margin-top:0.5em;
+ margin-left: -0.2em;
+}
+
+#pick-criteria td.label {
+ width: auto;
+}
+
+#pick-criteria td.operator {
+ width: 7.5em;
+}
+
+.plain-text-white-space {
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+ white-space: pre; /* IE only hack to re-specify in addition to
+ word-wrap */
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/msie6.css b/rt/share/html/NoAuth/css/freeside2.1/msie6.css
new file mode 100644
index 0000000..bf6b1ed
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/msie6.css
@@ -0,0 +1,88 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+.topaction .select-queue {
+ margin-top: 0;
+}
+
+div#page-navigation ul#page-menu {
+ margin-top: -3.2em;
+}
+
+.titlebox-title .widget {
+ top: -1em;
+ left: 0.5em;
+
+}
+.titlebox .titlebox-title .right{
+ position: absolute;
+ top: 0.25em;
+ right: 1em;
+}
+
+/* nested things. like the ticket dates tab */
+.titlebox .titlebox .titlebox-title .right{
+ right: 1.3em;
+}
+
+#login-box .titlebox .titlebox-title .right {
+ margin-top: -0.1em;
+ right: 0em;
+}
+
+.titlebox
+{
+ height: auto !important;
+ height: 1.25em;
+}
+
+
+.ticket-transaction .messagebody img {
+ /* ie6 does not support max-width */
+ width: expression(this.width > 401 ? 400 : true);
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/nav.css b/rt/share/html/NoAuth/css/freeside2.1/nav.css
new file mode 100644
index 0000000..8a52e62
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/nav.css
@@ -0,0 +1,206 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+div#nav {
+ position: absolute;
+ left: 0;
+ font-size: 0.9em;
+ top: 3.2em;
+ width: 10.5em;
+ background: #fff;
+ -moz-border-radius-bottomright: 0.5em;
+ -webkit-border-bottom-right-radius: 0.5em;
+ border-left: 1px solid #999;
+border-top: 1px solid #999;
+
+ -moz-border-radius-topright: 0.5em;
+ -webkit-border-top-right-radius: 0.5em;
+ z-index: 99;
+
+
+}
+
+div#nav ul {
+ padding-left: 0.75em;
+ margin-left: 0;
+ padding-right: 0.75em;
+ list-style-type: none;
+}
+
+div#nav li:first-child {
+ border-top: 1px solid #ccc;
+ padding-top: 0.25em;
+
+}
+
+div#nav li {
+ padding: 0.125em;
+ padding-bottom: 0.25em;
+ margin-bottom: 0.25em;
+ border-bottom: 1px solid #ccc;
+ padding-left: 0.5em;
+ margin-right: 0.25em;
+ margin-left: 0em;
+}
+
+div#nav li li:first-child {
+ margin-top: 0.25em;
+}
+div#nav li li {
+ margin-left: -0.5em;
+ padding-left: 0.25em;
+ margin-right: -0.5em;
+}
+
+div#nav li li:last-child {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border: none;
+}
+
+div#nav .bullet {
+ display: none;
+}
+
+div#nav .separator {
+display: none;
+}
+
+
+div#nav a, div#page-navigation a{
+ text-decoration: none;
+ font-weight: normal;
+ color: #000;
+}
+
+div#nav a:hover, div#page-navigation a:hover {
+ text-decoration: underline;
+}
+
+
+
+div#nav a.selected, div#page-navigation a.selected {
+ font-weight: bold;
+}
+
+
+div#nav a.selected:after {
+/* content: " > " */
+}
+
+div#page-navigation {
+ background: white;
+ position: relative;
+ /* width:100%; */
+ z-index: 10;
+
+}
+
+
+div#page-navigation ul {
+
+}
+
+div#page-navigation ul#page-menu {
+ display: block;
+ /* position: absolute; */
+ float: left;
+ left: 8em;
+ font-size: 0.9em;
+ top: 2.3em;
+ min-height: 1em;
+ background-color: white;
+ right: 0em;
+ padding-top:0.3em;
+ padding-bottom:0.5em;
+ padding-right: 4em;
+ border-top: 1px solid #aaa;
+
+}
+
+/* ie hack */
+* html div#page-navigation ul#page-menu {
+ left: 6.5em;
+ top: 3.2em;
+ padding-left: 2em;
+}
+
+
+div#page-navigation ul#actions-menu {
+ /* position: absolute; */
+ float: right;
+ right: 1em;
+ top: 5.2em;
+ margin-top: 0em;
+ padding: 0.25em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+
+ background: #dedede;
+ border-left: 1px solid #aaa;
+ border-bottom: 2px solid #aaa;
+ -moz-border-radius-bottomleft: 0.5em;
+ -webkit-border-bottom-left-radius: 0.5em;
+ -moz-border-radius-topright: 0.25em;
+ -webkit-border-top-right-radius: 0.25em;
+
+
+
+}
+
+
+
+div#page-navigation ul li{
+ display: inline;
+
+}
+
+
+ul.page-navigation ul.page-menu {
+ float: right;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/portlets.css b/rt/share/html/NoAuth/css/freeside2.1/portlets.css
new file mode 100644
index 0000000..d96d5a9
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/portlets.css
@@ -0,0 +1,71 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+table.myrt {
+ width: 100%;
+}
+
+table.dashboard {
+ width: 100%;
+ border: 0;
+}
+
+.quick-create .select-queue {
+ width: 12em;
+}
+
+.quick-create input[type="text"], .quick-create textarea {
+ width: 100%;
+
+}
+
+.reminders blockquote {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ margin-left: 1em;
+ margin-right: 1em;
+}
diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css b/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css
new file mode 100644
index 0000000..799a391
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/ticket-lists.css
@@ -0,0 +1,172 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+table.ticket-list, table.queue-summary, table.collection {
+ margin-top: 0.75em;
+ font-size: 0.9em;
+ border: 1px solid #aaa;
+ border-bottom: 2px solid #999;
+ border-right: 2px solid #999;
+
+
+}
+
+table.queue-summary tr>*:first-child {
+ padding-left: 1em;
+
+}
+
+
+table.queue-summary tr>*:last-child {
+ padding-right: 1em;
+
+}
+
+table.ticket-list a, table.queue-summary a, table.collection a {
+ font-weight: bold;
+}
+
+
+table.ticket-list th.collection-as-table, table.collection th.collection-as-table {
+ background: #ddd;
+ font-size: 0.9em;
+ margin-bottom: 0.5em;
+ text-align: left;
+
+}
+
+
+table.queue-summary th.collection-as-table {
+ font-size: 0.9em;
+ margin-bottom: 0.5em;
+ text-align: right;
+
+}
+
+table.queue-summary th.collection-as-table:first-child {
+ text-align: left;
+
+}
+
+
+tr.collection-as-table+tr.collection-as-table th {
+ border-bottom: 2px solid grey;
+
+}
+
+
+
+
+table.queue-summary td {
+ background: #efefef;
+ border-bottom: 1px solid #ccc;
+}
+
+
+
+tr.evenline td {
+ background: #eee;
+}
+
+tr.oddline td {
+ background: #fff;
+
+}
+
+tr.evenline td, tr.oddline td {
+ padding-top: 0.5em;
+}
+
+
+
+tr.evenline+tr.evenline td, tr.oddline+tr.oddline td{
+ padding-top: 0;
+ border: none;
+}
+
+
+
+table.ticket-list td:first-child, table.ticket-list th:first-child {
+ padding-left: 1em;
+}
+
+table.ticket-list td:last-child, table.ticket-list th:last-child {
+ padding-right: 1em;
+}
+
+th.collection-as-table , td.collection-as-table {
+ padding-right: 0.5em;
+}
+
+.pagenum.a:hover, .paging a.nav:hover{
+text-decoration: underline;
+}
+
+
+.pagenum *, .paging a.nav{
+padding: .5em;
+}
+
+.currentpage{
+text-decoration: none;
+font-weight: bold;
+background: #eee;
+}
+
+div.paging{
+text-align: center;
+padding-bottom: 1em;
+}
+
+
+/* full-page ticket lists */
+#body>table.ticket-list {
+ margin-bottom: 2em;
+
+}
+
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css b/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css
new file mode 100644
index 0000000..7a31d3e
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/ticket-search.css
@@ -0,0 +1,199 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+#comp-Search-Build #body {
+ position: relative;
+}
+
+#pick-criteria select {
+ width: 8em;
+}
+
+#pick-criteria tr {
+ height: 1.5em;
+}
+
+#pick-criteria td.label {
+ font: message-box;
+ padding-right: 0.5em;
+ width: 11em;
+}
+
+#pick-criteria td.label * {
+ width: 8.5em;
+}
+
+#pick-criteria td.label select {
+ text-align: right;
+}
+
+#pick-criteria td.operator {
+ padding-right: 0.5em;
+ text-align: left;
+ vertical-align: bottom;
+ width: 7em;
+}
+
+#pick-criteria td.operator select {
+ text-align: right;
+}
+
+#pick-criteria td.value input,
+#pick-criteria td.value select {
+ width: 10em;
+}
+
+#pick-criteria td.value #ValueOfDate {
+ width: 6em;
+}
+
+
+#pick-criteria td.value #ValueOfTime {
+ width: 4em;
+
+}
+
+#pick-criteria td.value #ValueOfTime-TimeUnits{
+ width: 5.5em;
+}
+
+#pick-criteria td.value {
+ padding-right: 0.5em;
+ text-align: left;
+ font: message-box;
+}
+
+#editquery, #editsearches{
+ position: absolute;
+ margin-top: 0.2em;
+ right: 1em;
+ left: 60%;
+ top: 1em;
+/* margin-top: -1em; */
+}
+
+#editquery {
+ top: 1.3em;
+}
+
+
+#editsearches {
+ top: 24em;
+}
+
+
+#pick-criteria {
+ width: 58%;
+ padding-top: 0em;
+ margin-top: 0em;
+}
+
+#pick-criteria .titlebox-content {
+ overflow-x: auto;
+}
+
+#comp-Search-Build .submit {
+ width: 58%;
+}
+
+
+#sorting.titlebox {
+ width: 55%;
+ padding-right: 1em;
+}
+
+#comp-Search-Build #columns {
+}
+
+#display-options .submit {
+ width: 100%;
+}
+
+
+
+.search-result-views {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin-top: -2px;
+ margin-right: 0em;
+ padding: 0.25em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ background-color: #ccc;
+ border-left: 1px solid #999;
+ border-bottom: 1px solid #999;
+ -moz-border-radius-bottomleft: 0.5em;
+ -webkit-border-bottom-left-radius: 0.5em;
+}
+
+
+
+.search-result-views li {
+
+ display: inline;
+}
+
+.search-result-views li:after {
+ content: " \00b7 ";
+}
+
+.search-result-views li:last-child:after {
+ content: "";
+
+}
+
+
+.refresh {
+ float: left;
+}
+
+/* Force some widget to fit at max parent box */
+#HomeRefreshInterval, #SavedSearchLoad, #SavedSearchOwner {
+ max-width: 100%;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/ticket.css b/rt/share/html/NoAuth/css/freeside2.1/ticket.css
new file mode 100644
index 0000000..78477e0
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/ticket.css
@@ -0,0 +1,230 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+div#ticket-history div.ticket-transaction {
+ border-top: 1px solid #ccc;
+ padding-bottom: 0.25em;
+
+}
+
+div#ticket-history div.odd {
+ background-color: #fff;
+}
+
+div#ticket-history {
+
+ margin-top: 0.75em;
+ border-left: 1px solid #ccc;
+
+ border-right: 2px solid #999;
+ border-bottom: 2px solid #999;
+
+}
+
+.ticket-transaction div.metadata span.actions {
+ position: absolute;
+ right: 2.3em;
+ padding: 0em;
+ background: #ccc;
+ text-align: right;
+ border-left: 1px solid #999;
+ border-bottom: 1px solid #999;
+ color: #ccc;
+ -moz-border-radius-bottomleft: 0.5em;
+ -webkit-border-bottom-left-radius: 0.5em;
+ white-space: nowrap;
+}
+
+.ticket-transaction div.metadata span.type {
+ text-align: center;
+ float: left;
+ margin: 0.25em 0.70em 0.25em 0.25em;
+ width: 1em;
+ height: 1.25em;
+ padding: 0.75em 0 0 0;
+ border-right: 1px solid #999;
+ border-bottom: 1px solid #999;
+ -moz-border-radius: 0.25em;
+ -webkit-border-bottom-right-radius: 0.25em;
+}
+
+div#ticket-history span.type a {
+ color: #fff;
+}
+
+
+div#ticket-history span.date {
+ width: 10em;
+}
+
+
+div#ticket-history span.description {
+ margin-left: 1em;
+ font-weight: bold;
+}
+
+div#ticket-history span.time-taken {
+ margin-left: 1em;
+}
+
+div#ticket-history div.content {
+ padding-right: 1em;
+ padding-bottom: 0.7em;
+ font-size: 1.1em;
+ margin-left: 1.5em;
+}
+
+.plain-text-white-space {
+ white-space: pre-wrap;
+ font-family: monospace;
+}
+
+.ticket-transaction .messagebody {
+ font-size: 1em;
+ padding-left: 1em;
+ margin-top: 0.5em;
+ padding-top: 0.5em;
+ border-top: 1px solid #ccc;
+ /*overflow: auto; */
+ min-height: 2.5em;
+ /* To avoid overlapping of "downloadattachment" by messagebody */
+ clear: left;
+}
+
+.ticket-transaction .messagebody img {
+ max-width: 100%;
+}
+
+div#ticket-history div.downloadattachment {
+float: right;
+clear: both;
+font-size: 0.9em;
+text-align: right;
+background: #ddd;
+padding: 0.5em;
+margin-left: 1em;
+
+border: 1px solid #ccc;
+border-right: 2px solid #aaa;
+border-bottom: 2px solid #aaa;
+margin-top: 0.5em;
+-moz-border-radius: 0.5em;
+-webkit-border-radius: 0.5em;
+
+}
+
+div#ticket-history div.downloadattachment .downloadcontenttype{
+color: #666;
+padding-right:0.25em;
+}
+
+
+div#ticket-history .message-header-key {
+ width: 7em;
+ font-weight: bold;
+ color: #666;
+}
+
+
+div#ticket-history .messagebody .messagebody{
+ font-size: 1em;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+
+
+.ticket-transaction.basics .type { background: #b32; }
+.ticket-transaction.cfs .type { background: #b32; }
+.ticket-transaction.people .type { background: #48c; }
+.ticket-transaction.links .type { background: #316531; }
+.ticket-transaction.dates .type { background: #633063; }
+.ticket-transaction.message .type { background: #069; }
+.ticket-transaction.reminders .type { background: #369; }
+.ticket-transaction.other .type { background: #abc; }
+
+
+
+
+.ticket-info-cfs .titlebox-title .left { background-color: #b32; color: #fff;}
+.ticket-info-basics .titlebox-title .left { background-color: #b32; color: #fff;}
+.ticket-info-people .titlebox-title .left { background-color: #48c; color: #fff;}
+.ticket-info-requestor .titlebox-title .left { white-space: nowrap; background-color: #48c; color: #fff;}
+.ticket-info-links .titlebox-title .left { background-color: #316531; color: #fff;}
+.ticket-info-reminders .titlebox-title .left { background-color: #369; color: #fff;}
+.ticket-info-dates .titlebox-title .left { background-color: #633063; color: #fff;}
+.ticket-info-attachments .titlebox-title .left { background-color: #993366; color: #fff;}
+
+
+.ticket-summary .titlebox-title a, div#body .ticket-summary .titlebox-title a:visited { color: #fff;}
+
+.unread-messages .titlebox , .unread-messages .titlebox-title .left {
+ border: 1px solid #99a;
+ border-right: 2px solid #aab;
+ border-bottom: 2px solid #aab;
+
+}
+
+
+.unread-messages .titlebox {
+ background-color: #dde;
+}
+
+.unread-messages .titlebox-title .left {
+ background-color: #cce;
+}
+
+.ticket-inactive {
+ text-decoration: line-through;
+ color: #666
+}
+
+table.ticket-summary td.boxcontainer:first-child {
+ width: 50%;
+}
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/tools.css b/rt/share/html/NoAuth/css/freeside2.1/tools.css
new file mode 100644
index 0000000..843feb2
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/tools.css
@@ -0,0 +1,56 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%# <jesse@bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+table.myday td {
+ padding: 1em;
+}
+
+ol.dashboard-queries {
+ padding-left: 1.5em;
+}
+
+
diff --git a/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css b/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css
new file mode 100644
index 0000000..fdae8d9
--- /dev/null
+++ b/rt/share/html/NoAuth/css/freeside2.1/yui-fonts.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.1
+*/
+body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}
diff --git a/rt/share/html/NoAuth/images/created.png b/rt/share/html/NoAuth/images/created.png
new file mode 100644
index 0000000..4d5eeb9
--- /dev/null
+++ b/rt/share/html/NoAuth/images/created.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/created_due.png b/rt/share/html/NoAuth/images/created_due.png
new file mode 100644
index 0000000..52dfc96
--- /dev/null
+++ b/rt/share/html/NoAuth/images/created_due.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/due.png b/rt/share/html/NoAuth/images/due.png
new file mode 100644
index 0000000..30a3aff
--- /dev/null
+++ b/rt/share/html/NoAuth/images/due.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/reminder.png b/rt/share/html/NoAuth/images/reminder.png
new file mode 100644
index 0000000..4370b69
--- /dev/null
+++ b/rt/share/html/NoAuth/images/reminder.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/resolved.png b/rt/share/html/NoAuth/images/resolved.png
new file mode 100644
index 0000000..09db36d
--- /dev/null
+++ b/rt/share/html/NoAuth/images/resolved.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/started.png b/rt/share/html/NoAuth/images/started.png
new file mode 100644
index 0000000..e177add
--- /dev/null
+++ b/rt/share/html/NoAuth/images/started.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/starts.png b/rt/share/html/NoAuth/images/starts.png
new file mode 100644
index 0000000..88064ba
--- /dev/null
+++ b/rt/share/html/NoAuth/images/starts.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/starts_due.png b/rt/share/html/NoAuth/images/starts_due.png
new file mode 100644
index 0000000..16a4de4
--- /dev/null
+++ b/rt/share/html/NoAuth/images/starts_due.png
Binary files differ
diff --git a/rt/share/html/NoAuth/images/updated.png b/rt/share/html/NoAuth/images/updated.png
new file mode 100644
index 0000000..680e79a
--- /dev/null
+++ b/rt/share/html/NoAuth/images/updated.png
Binary files differ
diff --git a/rt/share/html/Prefs/Calendar.html b/rt/share/html/Prefs/Calendar.html
new file mode 100644
index 0000000..5fbdd27
--- /dev/null
+++ b/rt/share/html/Prefs/Calendar.html
@@ -0,0 +1,123 @@
+<%args>
+$ChangeURL => undef
+$ResetURL => undef
+$SearchType => 'Ticket'
+$HiddenField => undef
+</%args>
+
+<& /Elements/Header, Title => $title &>
+<& /User/Elements/Tabs,
+ current_tab => 'Prefs/Calendar.html',
+ Title => $title
+&>
+
+<&| /Widgets/TitleBox, title => loc('ICal Feeds (ics)') &>
+
+<&| /Widgets/TitleBox, title => 'Help' &>
+
+<h3>displaying reminders :</h3>
+<p>If you want to have reminders in a search you need to go in the <a
+href="<%$RT::WebPath%>/Search/Edit.html"><%loc("Edit Query")%></a> tab
+of the <%loc("query builder")%> and add something like that :
+
+ <pre>
+ AND ( Type = 'ticket' OR Type = 'reminder' )
+</pre>
+</p>
+
+<h3>displaying other kind of dates :</h3>
+<p>By default RTx::Calendar display Due and Starts dates. You can
+select other kind of events you want with the <%loc("Display
+Columns")%> section in the <a
+href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query
+Builder")%></a>. The following one will display the two latter and
+LastUpdated dates :
+
+<pre>
+ '&lt;small&gt;__Due__&lt;/small&gt;',
+ '&lt;small&gt;__Starts__&lt;/small&gt;',
+ '&lt;small&gt;__LastUpdated__&lt;/small&gt;'
+</pre>
+</p>
+
+<h3>changing the default query :</h3>
+<p>You can change the default Query of Calendar.html and MyCalendar
+portlet by saving a query with the name <code>calendar</code> in the
+<a href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query
+Builder")%></a>.</p>
+
+</&>
+
+<& /Prefs/Elements/CalendarFeed &>
+
+% # only allow this part if
+% if ($AllowSearch) {
+
+% my $search_count;
+
+% # I'm quite sure the loop isn't usefull but...
+% my @Objects = $session{CurrentUser}->UserObj;
+% for my $object (@Objects) {
+% next unless ref($object) eq 'RT::User' && $object->id == $session{'CurrentUser'}->Id;
+% my @searches = $object->Attributes->Named('SavedSearch');
+% for my $search (@searches) {
+% next if ($search->SubValue('SearchType')
+% && $search->SubValue('SearchType') ne $SearchType);
+% $search_count++;
+<& /Prefs/Elements/CalendarFeed, Object => $object, Search => $search &>
+
+% }
+% }
+% unless ($search_count) {
+
+<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds')
+ , title_class=> 'inverse'
+ , color => "#993333" &>
+
+You can add private ICal feeds by saving new queries in <a
+href="<%$RT::WebPath . '/Search/Build.html'%>">the Query Builder</a>
+
+</&>
+
+% }
+% } else {
+%#<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds')
+%# , title_class=> 'inverse'
+%# , color => "#993333" &>
+%#
+%#<%loc('Private search ICal feeds disabled. To enable them, ask your admin for "[_1]" and "[_2]" rights',
+%# loc('CreateSavedSearch'),
+%# loc('LoadSavedSearch') )%>
+%#
+%#</&>
+% }
+
+</&>
+
+<%INIT>
+use Digest::SHA1;
+use RT::SavedSearches;
+
+my $title = loc("Calendar Prefs");
+my $AllowSearch;
+
+$AllowSearch = 1
+ if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
+ Object=> $RT::System );
+
+my $object;
+
+if ($HiddenField eq 'Private') {
+ $object = $session{CurrentUser}->UserObj;
+} elsif ($AllowSearch and my ($SearchId) = $HiddenField =~ m/SavedSearch\-(\d+)/) {
+ $object = $session{CurrentUser}->Attributes->WithId($SearchId);
+}
+
+if (defined $ChangeURL) {
+ my @args = $object->SetAttribute(Name => 'ICalURL', Content => Digest::SHA1::sha1_base64(time));
+} elsif (defined $ResetURL) {
+ my @args = $object->DeleteAttribute('ICalURL');
+}
+
+
+</%INIT>
diff --git a/rt/share/html/Prefs/Elements/CalendarFeed b/rt/share/html/Prefs/Elements/CalendarFeed
new file mode 100644
index 0000000..4689343
--- /dev/null
+++ b/rt/share/html/Prefs/Elements/CalendarFeed
@@ -0,0 +1,68 @@
+<%args>
+$Search => undef
+$Object => undef
+$HiddenField => undef
+</%args>
+
+<&| /Widgets/TitleBox, title => $title &>
+
+% if ($FeedText) {
+<p><%$FeedText%></p>
+% } else {
+This feed will show tickets with due date find with query:<br />
+"<%$Search->SubValue('Query')%>".
+% }
+
+% if ($ICalURL) {
+<p>Your can paste this url in your calendar : <b><a href="<%$link%>"><%$link%></a></b><p>
+<table>
+<tr>
+<td>
+<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
+<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
+<input type="submit" class="button" name="ResetURL" value="<%loc('Disable Feed')%>" />
+</form>
+</td>
+<td>
+<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
+<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
+<input type="submit" class="button" name="ChangeURL" value="<%loc('Change Feed URL')%>" />
+</form>
+</td>
+</tr>
+</table>
+% } else {
+
+<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
+<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
+<input type="submit" class="button" name="ChangeURL" value="<%loc('Enable Feed')%>" />
+</form>
+% }
+
+</&>
+
+<%init>
+my $title;
+my $ICalURL;
+my $Id;
+my $FeedText;
+my $link;
+
+if ($Object) {
+ $title = loc('Feed for "') . ($Search->Description || loc('Unnamed search')) . '" search';
+ $HiddenField = "SavedSearch-" . $Search->Id;
+ $ICalURL = $Search->FirstAttribute('ICalURL');
+ $Id = $session{CurrentUser}->Id . "@" . $Search->Id;
+ $title .= " (disabled)" unless $ICalURL;
+} else {
+ $title = loc('Feed for default calendar');
+ $HiddenField = "Private";
+ $ICalURL = $session{CurrentUser}->UserObj->FirstAttribute('ICalURL');
+ $Id = $session{CurrentUser}->Id;
+ $FeedText = "This feed will show yours and Nobody's tasks with due date.";
+}
+
+$link = $RT::WebURL . "NoAuth/Calendar/" . $Id . "/" . $ICalURL->Content
+ if $ICalURL;
+
+</%init> \ No newline at end of file
diff --git a/rt/share/html/Prefs/SearchOptions.html b/rt/share/html/Prefs/SearchOptions.html
index e40265f..5e5ed46 100644
--- a/rt/share/html/Prefs/SearchOptions.html
+++ b/rt/share/html/Prefs/SearchOptions.html
@@ -45,7 +45,7 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title => loc("Search Preferences") &>
+<& /Elements/Header, Title => loc("Ticketing Search Preferences") &>
<& /User/Elements/Tabs,
current_tab => "Prefs/SearchOptions.html",
Title => loc("Search Preferences")
diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html
index 0fd7974..c5067d5 100644
--- a/rt/share/html/Search/Build.html
+++ b/rt/share/html/Search/Build.html
@@ -85,9 +85,9 @@
<div id="pick-criteria">
<& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &>
-</div>
<& /Elements/Submit, Label => loc('Add these terms'), Name => 'AddClause'&>
<& /Elements/Submit, Label => loc('Add these terms and Search'), Name => 'DoSearch'&>
+</div>
<div id="editquery">
diff --git a/rt/share/html/Search/Calendar.html b/rt/share/html/Search/Calendar.html
new file mode 100644
index 0000000..9d2b6f5
--- /dev/null
+++ b/rt/share/html/Search/Calendar.html
@@ -0,0 +1,238 @@
+<%args>
+$Month => (localtime)[4]
+$Year => (localtime)[5] + 1900
+$Query => undef
+$Format => undef
+$Order => undef
+$OrderBy => undef
+$RowsPerPage => undef
+$NewQuery => 0
+</%args>
+
+<& /Elements/Header, Title => $title &>
+<& /Ticket/Elements/Tabs,
+ current_tab => "Search/Calendar.html?$QueryString",
+ Title => $title &>
+<&| /Widgets/TitleBox,
+ title => loc('Calendar for ') . $rtdate->GetMonth($Month) . " $Year" ,
+ title_class=> 'inverse',
+ color => "#993333" &>
+
+<table width="100%">
+<tr>
+<td align="left">
+% my ($PMonth, $PYear) = ($Month - 1, $Year);
+% if ($PMonth < 0) {
+% $PYear--;
+% $PMonth = 11;
+% }
+<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">«<%$rtdate->GetMonth($PMonth)%></a>
+</td>
+<th align="center">
+ <font size="+1"><% $rtdate->GetMonth($Month). " $Year" %></font>
+</th>
+<td align="right">
+% my ($NMonth, $NYear) = ($Month + 1, $Year);
+% if ($NMonth > 11) {
+% $NYear++;
+% $NMonth = 0;
+% }
+<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%>»</a>
+</td>
+</tr>
+</table>
+
+<table class="rtxcalendar">
+
+<thead>
+<tr>
+% for ( @{$week{$weekstart}} ) {
+<th width="14%"><%$rtdate->GetWeekday($_)%></th>
+% }
+</tr>
+</thead>
+
+<tbody>
+<tr>
+% while ($date <= $end) {
+%
+% my $offmonth = $date->month != ($Month + 1);
+% my $is_today = (DateTime->compare($today, $date) == 0);
+% my $is_yesterday = (DateTime->compare($yesterday, $date) == 0);
+% my $is_aweekago = (DateTime->compare($aweekago, $date) == 0);
+
+ <td class="<% $offmonth ? 'offmonth'
+ : $is_today ? 'today'
+ : $is_yesterday ? 'yesterday'
+ : $is_aweekago ? 'aweekago'
+ : ''
+ %>"
+ >
+ <div class="<% $is_today ? 'todays'
+ : $offmonth ? 'offmonth'
+ :'' %>calendardate"
+ ><%$date->day%></div>
+
+% my $sp = 3;
+% for my $t ( @{ $Tickets{$date->strftime("%F")} } ) {
+% $sp--;
+ <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
+% }
+ <% ($sp>0) ? '<BR>'x$sp : '' |n %>
+
+ </td>
+
+% $date = $set->next($date);
+% if ( $date->day_of_week == $startday_of_week ) {
+ </tr><tr>
+% }
+
+% }
+</tr>
+</tbody>
+</table>
+
+<table width="100%">
+<tr>
+<td align="left">
+<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$PMonth%>&Year=<%$PYear%>&<%$QueryString%>">«<%$rtdate->GetMonth($PMonth)%></a>
+</td>
+
+<td valign="top" align="center">
+<form action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+
+<select name="Month">
+% for (0..11) {
+<option value="<%$_%>" <% $_ == $Month ? 'selected' : ''%> ><%$rtdate->GetMonth($_)%></option>
+% }
+</select>
+% my $year = (localtime)[5] + 1900;
+<select name="Year">
+% for ( ($year-5) .. ($year+5)) {
+<option value="<%$_%>" <% $_ == $Year ? 'selected' : ''%>><%$_%></option>
+% }
+</select>
+
+%# <& /Elements/Submit&>
+<input type="submit" value="<% loc('Submit') %>" class="button" />
+
+</form>
+</td>
+
+<td align="right">
+<a href="<%$RT::WebPath%>/Search/Calendar.html?Month=<%$NMonth%>&Year=<%$NYear%>&<%$QueryString%>"><%$rtdate->GetMonth($NMonth)%>»</a>
+</td>
+</tr>
+</table>
+
+<table width="100%">
+<tr>
+
+<td valign="top" rowspan=9>
+ <BR>
+ <a href="<%$RT::WebPath%>/Prefs/Calendar.html">Calendar Preferences and Help</a>
+</td>
+
+% foreach my $legend (keys %legend) {
+ <tr>
+ <td align="right">
+ <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
+ </td>
+ <td align="left">
+% my $more = 0;
+% foreach ( @{$legend{$legend}} ) {
+ <% $more++ ? ', ' : '' %>
+ <&|/l&><% $_ %></&>
+% }
+ </td>
+ </tr>
+% }
+
+</table>
+
+</&>
+
+<%ONCE>
+
+my %legend = (
+ 'created' => ['Created'],
+ 'due' => ['Due'],
+ 'resolved' => ['Resolved'],
+ 'updated' => ['Last Updated'],
+ 'created_due' => ['Created','Due'],
+ 'reminder' => ['Reminders'],
+ 'started' => ['Started'],
+ 'starts_due' => ['Starts','Due'],
+);
+
+</%ONCE>
+<%INIT>
+use RTx::Calendar qw(FirstDay LastDay);
+
+my $title = loc("Calendar");
+
+my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
+
+my $rtdate = RT::Date->new($session{'CurrentUser'});
+
+my $weekstart = 'Sunday'; #RT::SiteConfig? user pref?
+my %week = (
+ 'Saturday' => [6,0..5],
+ 'Sunday' => [0..6],
+ 'Monday' => [1..6,0],
+);
+my $startday_of_week = ${$week{$weekstart}}[0] || 7;
+my $endday_of_week = ${$week{$weekstart}}[-1] || 7;
+
+my $today = DateTime->today;
+my $yesterday = $today->clone->subtract( days=>1 );
+my $aweekago = $today->clone->subtract( days=>7 );
+my $date = FirstDay($Year, $Month + 1, $startday_of_week );
+my $end = LastDay ($Year, $Month + 1, $endday_of_week );
+
+# use this to loop over days until $end
+my $set = DateTime::Set->from_recurrence(
+ next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
+);
+
+my $QueryString =
+ $m->comp(
+ '/Elements/QueryString',
+ Query => $Query,
+ Format => $Format,
+ Order => $Order,
+ OrderBy => $OrderBy,
+ Rows => $RowsPerPage
+ )
+ if ($Query);
+
+$QueryString ||= 'NewQuery=1';
+
+# Default Query and Format
+my $TempFormat = "__Starts__ __Due__";
+my $TempQuery = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
+ AND ( Owner = '" . $session{CurrentUser}->Id ."' OR Owner = 'Nobody' )
+ AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
+
+if ( my $Search = RTx::Calendar::SearchDefaultCalendar($session{CurrentUser}) ) {
+ $TempFormat = $Search->SubValue('Format');
+ $TempQuery = $Search->SubValue('Query');
+}
+
+# we overide them if needed
+$TempQuery = $Query if $Query;
+$TempFormat = $Format if $Format;
+
+# we search all date types in Format string
+my @Dates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
+
+# used to display or not a date in Element/CalendarEvent
+my %DateTypes = map { $_ => 1 } @Dates;
+
+$TempQuery .= RTx::Calendar::DatesClauses(\@Dates, $date->strftime("%F"), $end->strftime("%F"));
+
+# print STDERR ("-" x 30), "\n", $TempQuery, "\n";
+
+my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
+
+</%INIT>
diff --git a/rt/share/html/Ticket/Display.html b/rt/share/html/Ticket/Display.html
index f8eaf39..c1984bd 100755
--- a/rt/share/html/Ticket/Display.html
+++ b/rt/share/html/Ticket/Display.html
@@ -46,12 +46,12 @@
%#
%# END BPS TAGGED BLOCK }}}
<& /Elements/Header,
- Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject),
+ Title => loc("Ticket #[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject),
LinkRel => \%link_rel &>
<& /Ticket/Elements/Tabs,
Ticket => $TicketObj,
current_tab => 'Ticket/Display.html?id='.$TicketObj->id,
- Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &>
+ Title => loc("Ticket #[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &>
% $m->callback(CallbackName => 'BeforeActionList', %ARGS, Actions => \@Actions, ARGSRef => \%ARGS, Ticket => $TicketObj);
diff --git a/rt/share/html/Ticket/Elements/AddCustomers b/rt/share/html/Ticket/Elements/AddCustomers
new file mode 100644
index 0000000..e04c077
--- /dev/null
+++ b/rt/share/html/Ticket/Elements/AddCustomers
@@ -0,0 +1,52 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%# Copyright (c) 2008 Freeside Internet Services, Inc.
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<BR>
+<%$msg%><br>
+
+% if (@Customers) {
+
+<br><i>(Check box to link)<i>
+<table>
+% foreach my $customer (@Customers) {
+<tr>
+ <td>
+ <input type="checkbox" name="Ticket-AddCustomer-<% $customer->{'custnum'} %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>>
+ <A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $customer->{'custnum'} %>"><% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %>
+ </td>
+</tr>
+% }
+</table>
+
+% }
+
+<%INIT>
+my ($msg);
+
+my $freeside_url = &RT::URI::freeside::FreesideURL();
+
+my @Customers = ();
+if ( $CustomerString ) {
+ @Customers = &RT::URI::freeside::smart_search( 'search' => $CustomerString );
+}
+
+my @Services = ();
+if ($ServiceString) {
+ @Services = (); #service_search();
+}
+
+</%INIT>
+
+<%ARGS>
+$CustomerString => undef
+$ServiceString => undef
+</%ARGS>
diff --git a/rt/share/html/Ticket/Elements/EditCustomers b/rt/share/html/Ticket/Elements/EditCustomers
new file mode 100644
index 0000000..0ba6e44
--- /dev/null
+++ b/rt/share/html/Ticket/Elements/EditCustomers
@@ -0,0 +1,63 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%# Copyright (c) 2008 Freeside Internet Services, Inc.
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<TABLE width=100%>
+ <TR>
+ <TD VALIGN=TOP WIDTH=50%>
+ <h3><&|/l&>Current Customers</&></h3>
+
+<table>
+ <tr>
+ <td><i><&|/l&>(Check box to disassociate)</&></i></td>
+ </tr>
+ <tr>
+ <td class="value">
+% foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
+
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
+%# <& ShowLink, URI => $link->TargetURI &><br>
+ <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %></A>
+ <BR>
+% }
+ </td>
+ </tr>
+</table>
+
+</TD>
+
+<TD VALIGN=TOP>
+<h3><&|/l&>New Customer Links</&></h3>
+<&|/l&>Find customer</&><BR>
+<input name="CustomerString">
+<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>">
+<br><i>cust #, name, company or phone</i>
+<BR>
+%#<BR>
+%#<&|/l&>Find service</&><BR>
+%#<input name="ServiceString">
+%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>">
+%#<br><i>username, username@domain, domain, or IP address</i>
+%#<BR>
+
+<& AddCustomers, Ticket => $Ticket,
+ CustomerString => $CustomerString,
+ ServiceString => $ServiceString, &>
+
+</TD>
+</TR>
+</TABLE>
+
+<%ARGS>
+$CustomerString => undef
+$ServiceString => undef
+$Ticket => undef
+</%ARGS>
diff --git a/rt/share/html/Ticket/Elements/ShowCustomers b/rt/share/html/Ticket/Elements/ShowCustomers
new file mode 100644
index 0000000..3acf92d
--- /dev/null
+++ b/rt/share/html/Ticket/Elements/ShowCustomers
@@ -0,0 +1,38 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<table>
+% my $cust = 0;
+% foreach my $custResolver ( map { $_->TargetURI->Resolver }
+% @{ $Ticket->Customers->ItemsArrayRef }
+% )
+% {
+% $cust++;
+% my $cust_main = '';
+ <tr>
+ <td class="value">
+ <A HREF="<% $custResolver->HREF %>"><% $custResolver->AsStringLong |n %></A>
+ </td>
+ </tr>
+% }
+% unless ( $cust ) {
+ <tr>
+ <td class="labeltop">
+ <i>(none)<i>
+ </td>
+ </tr>
+
+% }
+</table>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
+
diff --git a/rt/share/html/Ticket/Elements/ShowSummary b/rt/share/html/Ticket/Elements/ShowSummary
index 21c53c5..295ac6d 100755
--- a/rt/share/html/Ticket/Elements/ShowSummary
+++ b/rt/share/html/Ticket/Elements/ShowSummary
@@ -66,6 +66,13 @@
</&>
% }
+ <&| /Widgets/TitleBox, title => loc('Customers'),
+ title_href => RT->Config->Get('WebPath')."/Ticket/ModifyCustomers.html?id=".$Ticket->Id,
+ class => 'ticket-info-customers'
+ &>
+ <& /Ticket/Elements/ShowCustomers, Ticket => $Ticket &>
+ </&>
+
<&| /Widgets/TitleBox, title => loc('People'),
title_href => RT->Config->Get('WebPath')."/Ticket/ModifyPeople.html?id=".$Ticket->Id,
class => 'ticket-info-people',
diff --git a/rt/share/html/Ticket/Elements/ShowTransactionAttachments b/rt/share/html/Ticket/Elements/ShowTransactionAttachments
index a8cdfc1..95ffcbd 100644
--- a/rt/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/rt/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -210,8 +210,14 @@ my $render_attachment = sub {
# if it's a text/plain show the body
elsif ( $message->ContentType =~ m{^(text|message)}i ) {
- eval { require Text::Quoted; $content = Text::Quoted::extract($content); };
- if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) }
+ #don't want to use this even if it is installed, its
+ #segfaulting on weird characters and silently truncating the
+ #ticket history output
+ #see:
+ # r44838@pinglin: jesse | 2006-11-14 15:53:18 -0500
+ # * Move Text::Quoted back to being a run-time require. So that it's possible to turn off the feature if it causes your perl to segfault. (Text::Tabs is...not robust in the face of perl bugs)
+ #eval { require Text::Quoted; $content = Text::Quoted::extract($content); };
+ #if ($@) { $RT::Logger->warning( "Text::Quoted failed: $@" ) }
$m->comp(
'ShowMessageStanza',
diff --git a/rt/share/html/Ticket/Elements/Tabs b/rt/share/html/Ticket/Elements/Tabs
index a9e697a..c893a35 100755
--- a/rt/share/html/Ticket/Elements/Tabs
+++ b/rt/share/html/Ticket/Elements/Tabs
@@ -142,6 +142,10 @@ if ($Ticket) {
title => loc('Links'),
path => "Ticket/ModifyLinks.html?id=" . $id,
},
+ _Eb=> {
+ title => loc('Customers'),
+ path => "Ticket/ModifyCustomers.html?id=" . $id,
+ },
_X => {
title => loc('Jumbo'),
path => "Ticket/ModifyAll.html?id=" . $id,
diff --git a/rt/share/html/Ticket/ModifyCustomers.html b/rt/share/html/Ticket/ModifyCustomers.html
new file mode 100644
index 0000000..72d103b
--- /dev/null
+++ b/rt/share/html/Ticket/ModifyCustomers.html
@@ -0,0 +1,49 @@
+%# Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+<& /Elements/Header, Title => loc("Customers for ticket #[_1]", $Ticket->Id) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $Ticket,
+ current_tab => "Ticket/ModifyCustomers.html?id=".$Ticket->Id,
+ Title => loc("Customers for ticket #[_1]", $Ticket->Id) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<form action="ModifyCustomers.html" method="post">
+<input type="hidden" name="id" value="<%$Ticket->id%>">
+
+<& /Elements/TitleBoxStart, title => loc('Edit Customer Links'), color => "#7f007b"&>
+<& Elements/EditCustomers, Ticket => $Ticket, CustomerString => $CustomerString, ServiceString => $ServiceString &>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, color => "#7f007b", Label => loc('Save Changes') &>
+</form>
+
+
+<%INIT>
+
+my @results = ();
+my $Ticket = LoadTicket($id);
+
+# if we're trying to search for customers/services and nothing else
+unless ( $OnlySearchForCustomers || $OnlySearchForServices) {
+ @results = ProcessTicketCustomers( TicketObj => $Ticket, ARGSRef => \%ARGS);
+}
+
+</%INIT>
+
+
+<%ARGS>
+$OnlySearchForCustomers => undef
+$OnlySearchForServices => undef
+$CustomerString => undef
+$ServiceString => undef
+$id => undef
+</%ARGS>
diff --git a/rt/share/html/User/Prefs.html b/rt/share/html/User/Prefs.html
index 884cf85..e3a00f6 100755
--- a/rt/share/html/User/Prefs.html
+++ b/rt/share/html/User/Prefs.html
@@ -45,7 +45,7 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-<& /Elements/Header, Title=>loc("Preferences") &>
+<& /Elements/Header, Title=>loc("Ticketing Preferences") &>
<& /User/Elements/Tabs,
current_tab => 'User/Prefs.html',
Title => loc("Preferences") &>
diff --git a/rt/share/html/Widgets/TitleBoxEnd b/rt/share/html/Widgets/TitleBoxEnd
index 402124e..4a698e9 100755
--- a/rt/share/html/Widgets/TitleBoxEnd
+++ b/rt/share/html/Widgets/TitleBoxEnd
@@ -50,7 +50,7 @@
</div>
% #Manually flush the content buffer after each titlebox is displayed
-% $m->flush_buffer();
+% #wtf? this causes us to lose content #$m->flush_buffer();
<%ARGS>
$title => undef
diff --git a/test/cgi-test b/test/cgi-test
new file mode 100755
index 0000000..85074d2
--- /dev/null
+++ b/test/cgi-test
@@ -0,0 +1,558 @@
+#!/usr/bin/perl -Tw
+#
+# 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;
+
+}
+